From d71d53e8ecc1edd300c7a9dd22b8fbc39c095423 Mon Sep 17 00:00:00 2001 From: Jim Gray Date: Thu, 4 Apr 2013 14:32:05 -0700 Subject: [PATCH] Initial commit. --- LICENSE.txt | 339 + README.md | 11 +- base/default.cfg | 109 + base/ext_data/MP/netf_overrides.txt | 155 + base/ext_data/MP/psf_overrides.txt | 181 + base/ext_data/MP/vssver.scc | Bin 0 -> 64 bytes base/ext_data/dms.dat | 1757 ++ base/ext_data/items.dat | 782 + base/ext_data/npcs/Bartender.npc | 25 + base/ext_data/npcs/BespinCop.npc | 57 + base/ext_data/npcs/Desann.npc | 53 + base/ext_data/npcs/Elder.npc | 55 + base/ext_data/npcs/Galak.npc | 22 + base/ext_data/npcs/Galak_Mech.npc | 36 + base/ext_data/npcs/Glider.npc | 21 + base/ext_data/npcs/Gran.npc | 125 + base/ext_data/npcs/HazardTrooper.npc | 92 + base/ext_data/npcs/Howler.npc | 23 + base/ext_data/npcs/ImpCommander.npc | 34 + base/ext_data/npcs/ImpOfficer.npc | 30 + base/ext_data/npcs/ImpWorker.npc | 101 + base/ext_data/npcs/Imperial.npc | 29 + base/ext_data/npcs/Jan.npc | 30 + base/ext_data/npcs/Jedi.npc | 102 + base/ext_data/npcs/JediF.npc | 49 + base/ext_data/npcs/JediMaster.npc | 47 + base/ext_data/npcs/JediTrainer.npc | 48 + base/ext_data/npcs/Kyle.npc | 91 + base/ext_data/npcs/Lando.npc | 62 + base/ext_data/npcs/Luke.npc | 49 + base/ext_data/npcs/Merchant.npc | 27 + base/ext_data/npcs/Minemonster.npc | 24 + base/ext_data/npcs/MonMothma.npc | 25 + base/ext_data/npcs/MorganKatarn.npc | 25 + base/ext_data/npcs/Noghri.npc | 27 + base/ext_data/npcs/Prisoner.npc | 54 + base/ext_data/npcs/Ragnos.npc | 20 + base/ext_data/npcs/Rax.npc | 25 + base/ext_data/npcs/Rebel.npc | 61 + base/ext_data/npcs/Rebel2.npc | 27 + base/ext_data/npcs/Reborn.npc | 50 + base/ext_data/npcs/RebornAcrobat.npc | 51 + base/ext_data/npcs/RebornBoss.npc | 51 + base/ext_data/npcs/RebornChiss.npc | 54 + base/ext_data/npcs/RebornFencer.npc | 52 + base/ext_data/npcs/RebornForceUser.npc | 51 + base/ext_data/npcs/RebornRodian.npc | 52 + base/ext_data/npcs/RebornTrandoshan.npc | 50 + base/ext_data/npcs/RebornWeequay.npc | 49 + base/ext_data/npcs/Reborn_dual.npc | 103 + base/ext_data/npcs/Reborn_new.npc | 104 + base/ext_data/npcs/Reborn_staff.npc | 103 + base/ext_data/npcs/Reborn_twin.npc | 275 + base/ext_data/npcs/Reelo.npc | 30 + base/ext_data/npcs/RocketTrooper.npc | 37 + base/ext_data/npcs/Rodian.npc | 66 + base/ext_data/npcs/STCommander.npc | 37 + base/ext_data/npcs/STOfficer.npc | 74 + base/ext_data/npcs/STOfficerAlt.npc | 37 + base/ext_data/npcs/ShadowTrooper.npc | 100 + base/ext_data/npcs/StormPilot.npc | 35 + base/ext_data/npcs/StormTrooper.npc | 67 + base/ext_data/npcs/SwampTrooper.npc | 63 + base/ext_data/npcs/Tavion.npc | 50 + base/ext_data/npcs/Tavion_new.npc | 152 + base/ext_data/npcs/Trandoshan.npc | 30 + base/ext_data/npcs/Ugnaught.npc | 55 + base/ext_data/npcs/Weequay.npc | 128 + base/ext_data/npcs/alora.npc | 103 + base/ext_data/npcs/assassin_droid.npc | 32 + base/ext_data/npcs/atst.npc | 36 + base/ext_data/npcs/atst_vehicle.npc | 13 + base/ext_data/npcs/boba_fett.npc | 44 + base/ext_data/npcs/chewie.npc | 86 + base/ext_data/npcs/cultist.npc | 38 + base/ext_data/npcs/cultist_destroyer.npc | 31 + base/ext_data/npcs/cultist_drain.npc | 37 + base/ext_data/npcs/cultist_grip.npc | 38 + base/ext_data/npcs/cultist_lightning.npc | 37 + base/ext_data/npcs/cultist_saber.npc | 294 + base/ext_data/npcs/cultist_saber_powers.npc | 305 + base/ext_data/npcs/cultistcommando.npc | 31 + base/ext_data/npcs/gonk.npc | 23 + base/ext_data/npcs/human_merc.npc | 372 + base/ext_data/npcs/interrogator.npc | 24 + base/ext_data/npcs/jawa.npc | 54 + base/ext_data/npcs/jedi_random.npc | 648 + base/ext_data/npcs/lambdashuttle.npc | 12 + base/ext_data/npcs/mark1.npc | 24 + base/ext_data/npcs/mark2.npc | 21 + base/ext_data/npcs/mouse.npc | 25 + base/ext_data/npcs/nullDriver.npc | 10 + base/ext_data/npcs/player.npc | 7 + base/ext_data/npcs/probe.npc | 33 + base/ext_data/npcs/protocol.npc | 24 + base/ext_data/npcs/protocol_imp.npc | 27 + base/ext_data/npcs/r2d2.npc | 31 + base/ext_data/npcs/r2d2_imp.npc | 32 + base/ext_data/npcs/r5d2.npc | 31 + base/ext_data/npcs/r5d2_imp.npc | 33 + base/ext_data/npcs/rancor.npc | 70 + base/ext_data/npcs/rancor_vehicle.npc | 14 + base/ext_data/npcs/remote.npc | 53 + base/ext_data/npcs/rockettrooper2.npc | 71 + base/ext_data/npcs/rockettrooper_w.npc | 35 + base/ext_data/npcs/rocks.npc | 28 + base/ext_data/npcs/rosh_penin.npc | 124 + base/ext_data/npcs/saber_droid.npc | 66 + base/ext_data/npcs/saboteur.npc | 61 + base/ext_data/npcs/saboteurpistol.npc | 29 + base/ext_data/npcs/saboteursniper.npc | 30 + base/ext_data/npcs/sand_creature.npc | 48 + base/ext_data/npcs/seeker.npc | 26 + base/ext_data/npcs/sentry.npc | 25 + base/ext_data/npcs/snowtrooper.npc | 33 + base/ext_data/npcs/swoop.npc | 115 + base/ext_data/npcs/tauntaun.npc | 11 + base/ext_data/npcs/test.npc | 8 + base/ext_data/npcs/tie-bomber.npc | 19 + base/ext_data/npcs/tie-fighter.npc | 11 + base/ext_data/npcs/tusken.npc | 27 + base/ext_data/npcs/tuskensniper.npc | 29 + base/ext_data/npcs/vssver.scc | Bin 0 -> 1952 bytes base/ext_data/npcs/wampa.npc | 35 + base/ext_data/npcs/wampa_vehicle.npc | 14 + base/ext_data/npcs/wildtauntaun.npc | 12 + base/ext_data/npcs/x-wing.npc | 11 + base/ext_data/npcs/yt-1300.npc | 12 + base/ext_data/npcs/z-95.npc | 11 + base/ext_data/sabers/NotUsed/exotic.sab | 270 + base/ext_data/sabers/NotUsed/extra.sab | 397 + base/ext_data/sabers/NotUsed/vssver.scc | Bin 0 -> 64 bytes base/ext_data/sabers/dual_1.sab | 21 + base/ext_data/sabers/dual_2.sab | 21 + base/ext_data/sabers/dual_3.sab | 21 + base/ext_data/sabers/dual_4.sab | 21 + base/ext_data/sabers/dual_5.sab | 21 + base/ext_data/sabers/empty.sab | 11 + base/ext_data/sabers/sabers.sab | 325 + base/ext_data/sabers/single_1.sab | 13 + base/ext_data/sabers/single_2.sab | 13 + base/ext_data/sabers/single_3.sab | 13 + base/ext_data/sabers/single_4.sab | 13 + base/ext_data/sabers/single_5.sab | 13 + base/ext_data/sabers/single_6.sab | 13 + base/ext_data/sabers/single_7.sab | 13 + base/ext_data/sabers/single_8.sab | 13 + base/ext_data/sabers/single_9.sab | 13 + base/ext_data/sabers/sith_sword.sab | 11 + base/ext_data/sabers/vssver.scc | Bin 0 -> 304 bytes base/ext_data/siege/classes/bountyhunter.scl | 26 + base/ext_data/siege/classes/cultist.scl | 18 + base/ext_data/siege/classes/darkforceass.scl | 20 + .../siege/classes/darkjediapprentice.scl | 21 + base/ext_data/siege/classes/darkjedidemo.scl | 21 + .../siege/classes/darkjedidestroyer.scl | 25 + .../siege/classes/darkjediduelist.scl | 24 + .../siege/classes/darkjediforceuser.scl | 21 + .../siege/classes/darkjediinterceptor.scl | 21 + .../siege/classes/darkjediinvader.scl | 22 + .../ext_data/siege/classes/darkjediknight.scl | 21 + base/ext_data/siege/classes/darkjeditech.scl | 23 + .../siege/classes/darksidemarauder.scl | 21 + .../ext_data/siege/classes/darksidemauler.scl | 20 + base/ext_data/siege/classes/imperialdemo.scl | 19 + .../ext_data/siege/classes/imperialltdemo.scl | 19 + .../siege/classes/imperialltsniper.scl | 19 + .../ext_data/siege/classes/imperialsniper.scl | 21 + base/ext_data/siege/classes/implttech.scl | 19 + base/ext_data/siege/classes/imppilot.scl | 18 + base/ext_data/siege/classes/impsupply.scl | 20 + base/ext_data/siege/classes/imptech.scl | 18 + .../ext_data/siege/classes/jediapprentice.scl | 21 + base/ext_data/siege/classes/jedidemo.scl | 21 + base/ext_data/siege/classes/jediduelist.scl | 26 + base/ext_data/siege/classes/jediforceuser.scl | 21 + base/ext_data/siege/classes/jediguardian.scl | 21 + base/ext_data/siege/classes/jedihealer.scl | 22 + base/ext_data/siege/classes/jedihunter.scl | 19 + base/ext_data/siege/classes/jediknight.scl | 21 + .../siege/classes/jedilightsabermaster.scl | 24 + base/ext_data/siege/classes/jediscout.scl | 21 + base/ext_data/siege/classes/jeditech.scl | 22 + base/ext_data/siege/classes/jediwarrior.scl | 20 + base/ext_data/siege/classes/kyle.scl | 18 + base/ext_data/siege/classes/mercassault.scl | 21 + base/ext_data/siege/classes/mercdemo.scl | 19 + base/ext_data/siege/classes/mercenary.scl | 17 + base/ext_data/siege/classes/mercheavy.scl | 19 + base/ext_data/siege/classes/mercsniper.scl | 24 + base/ext_data/siege/classes/protector.scl | 20 + base/ext_data/siege/classes/rebelassault.scl | 25 + base/ext_data/siege/classes/rebeldemo.scl | 20 + .../siege/classes/rebelhvyinfantry.scl | 18 + base/ext_data/siege/classes/rebelinfantry.scl | 18 + base/ext_data/siege/classes/rebelltdemo.scl | 19 + base/ext_data/siege/classes/rebelltsniper.scl | 19 + base/ext_data/siege/classes/rebellttech.scl | 18 + base/ext_data/siege/classes/rebelpilot.scl | 17 + base/ext_data/siege/classes/rebelsniper.scl | 21 + base/ext_data/siege/classes/rebelsupply.scl | 19 + base/ext_data/siege/classes/rebeltech.scl | 18 + base/ext_data/siege/classes/rockettrooper.scl | 22 + base/ext_data/siege/classes/smuggler.scl | 21 + base/ext_data/siege/classes/snowtrooper.scl | 19 + base/ext_data/siege/classes/stormtrooper.scl | 18 + base/ext_data/siege/classes/vssver.scc | Bin 0 -> 944 bytes base/ext_data/siege/classes/wookie.scl | 22 + base/ext_data/siege/teams/Allies.team | 16 + base/ext_data/siege/teams/DarkJedi.team | 16 + base/ext_data/siege/teams/Imperials.team | 16 + base/ext_data/siege/teams/Jedi.team | 16 + base/ext_data/siege/teams/Mercs.team | 16 + .../ext_data/siege/teams/Siege1_Imperial.team | 21 + base/ext_data/siege/teams/Siege1_Rebel.team | 20 + base/ext_data/siege/teams/Siege2_Merc.team | 22 + base/ext_data/siege/teams/Siege2_Rebel.team | 20 + .../ext_data/siege/teams/Siege3_DarkJedi.team | 20 + base/ext_data/siege/teams/Siege3_Jedi.team | 20 + .../ext_data/siege/teams/Siege4_Imperial.team | 20 + base/ext_data/siege/teams/Siege4_Rebel.team | 20 + base/ext_data/siege/teams/vssver.scc | Bin 0 -> 240 bytes base/ext_data/vehicles/YT-1300.veh | 157 + base/ext_data/vehicles/atst_vehicle.veh | 69 + base/ext_data/vehicles/lambdashuttle.veh | 145 + base/ext_data/vehicles/rancor_vehicle.veh | 41 + base/ext_data/vehicles/swoop.veh | 86 + base/ext_data/vehicles/swoop_cin.veh | 71 + base/ext_data/vehicles/swoop_mp.veh | 71 + base/ext_data/vehicles/swoop_mp2.veh | 73 + base/ext_data/vehicles/tauntaun.veh | 55 + base/ext_data/vehicles/template.veh | 121 + base/ext_data/vehicles/tie-bomber.veh | 118 + base/ext_data/vehicles/tie-bomber2.veh | 119 + base/ext_data/vehicles/tie-fighter.veh | 112 + base/ext_data/vehicles/vssver.scc | Bin 0 -> 304 bytes base/ext_data/vehicles/wampa_vehicle.veh | 41 + base/ext_data/vehicles/weapons/atst_laser.vwp | 18 + .../ext_data/vehicles/weapons/atst_rocket.vwp | 24 + base/ext_data/vehicles/weapons/bomb.vwp | 18 + .../weapons/conc_missile_straight.vwp | 25 + .../vehicles/weapons/concussion_missile.vwp | 28 + base/ext_data/vehicles/weapons/imp_laser.vwp | 18 + .../ext_data/vehicles/weapons/ion_blaster.vwp | 16 + base/ext_data/vehicles/weapons/mine.vwp | 23 + .../vehicles/weapons/proton_torpedo.vwp | 28 + .../ext_data/vehicles/weapons/rebel_laser.vwp | 18 + .../ext_data/vehicles/weapons/swoop_laser.vwp | 14 + .../vehicles/weapons/swoop_rocket.vwp | 23 + base/ext_data/vehicles/weapons/template.vwp | 26 + base/ext_data/vehicles/weapons/vssver.scc | Bin 0 -> 256 bytes .../vehicles/weapons/yt_turbolaser.vwp | 19 + base/ext_data/vehicles/wildtauntaun.veh | 55 + base/ext_data/vehicles/x-wing.veh | 125 + base/ext_data/vehicles/z-95.veh | 129 + base/ext_data/vssver.scc | Bin 0 -> 80 bytes base/ext_data/weapons.dat | 744 + base/high.cfg | 18 + base/low.cfg | 22 + base/med.cfg | 18 + base/mpdefault.cfg | 141 + base/noMotion.cfg | 5 + base/productid.txt | 1 + base/restoreMotion.cfg | 5 + base/ui/character.menu | 898 + base/ui/controls.menu | 3121 ++++ base/ui/credits.menu | 441 + base/ui/datapadforcepowers.menu | 368 + base/ui/datapadinventory.menu | 307 + base/ui/datapadmission.menu | 258 + base/ui/datapadmoves.menu | 458 + base/ui/datapadweapons.menu | 411 + base/ui/demo_ForceSelect.menu | 2490 +++ base/ui/demo_GotoTier.menu | 190 + base/ui/demo_MissionSelect.menu | 955 + base/ui/demo_WpnSelect.menu | 1471 ++ base/ui/demo_ingame.txt | 34 + base/ui/demo_ingameMissionSelect.menu | 847 + base/ui/demo_menus.txt | 28 + base/ui/demo_saber.menu | 1512 ++ base/ui/demo_sellscreen1.menu | 98 + base/ui/error.menu | 128 + base/ui/hud.menu | 1069 ++ base/ui/ingame.menu | 473 + base/ui/ingame.txt | 32 + base/ui/ingameForceHelp.menu | 127 + base/ui/ingameForceSelect.menu | 2571 +++ base/ui/ingameForceStatus.menu | 654 + base/ui/ingameGotoTier.menu | 854 + base/ui/ingameMissionSelect.menu | 2849 +++ base/ui/ingameMissionSelect1.menu | 1276 ++ base/ui/ingameMissionSelect2.menu | 1334 ++ base/ui/ingameMissionSelect3.menu | 1311 ++ base/ui/ingameWpnSelect.menu | 1638 ++ base/ui/ingameWpnSelectHelp.menu | 126 + base/ui/ingamecontrols.menu | 3247 ++++ base/ui/ingameload.menu | 726 + base/ui/ingamequit.menu | 784 + base/ui/ingamesave.menu | 663 + base/ui/ingamesetup.menu | 2740 +++ base/ui/ingamevid_warning.menu | 197 + base/ui/jahud.txt | 5 + base/ui/jamp/advancedcreateserver.menu | 1174 ++ base/ui/jamp/connect.menu | 21 + base/ui/jamp/controls.menu | 3765 ++++ base/ui/jamp/createfavorite.menu | 222 + base/ui/jamp/createserver.menu | 2011 ++ base/ui/jamp/credits.menu | 441 + base/ui/jamp/demo.menu | 553 + base/ui/jamp/error.menu | 127 + base/ui/jamp/findplayer.menu | 351 + base/ui/jamp/gameinfo.txt | 35 + base/ui/jamp/ingame.menu | 604 + base/ui/jamp/ingame_about.menu | 300 + base/ui/jamp/ingame_addbot.menu | 153 + base/ui/jamp/ingame_callvote.menu | 647 + base/ui/jamp/ingame_controls.menu | 3305 ++++ base/ui/jamp/ingame_join.menu | 393 + base/ui/jamp/ingame_leave.menu | 423 + base/ui/jamp/ingame_objectives.menu | 2957 +++ base/ui/jamp/ingame_orders.menu | 386 + base/ui/jamp/ingame_player.menu | 1173 ++ base/ui/jamp/ingame_player2.menu | 558 + base/ui/jamp/ingame_playerforce.menu | 1575 ++ base/ui/jamp/ingame_saber.menu | 1040 ++ base/ui/jamp/ingame_setup.menu | 2271 +++ base/ui/jamp/ingame_siegeobjectives.menu | 987 + base/ui/jamp/ingame_voicechat.menu | 1178 ++ base/ui/jamp/ingame_vote.menu | 112 + base/ui/jamp/joinserver.menu | 1224 ++ base/ui/jamp/main.menu | 523 + base/ui/jamp/menudef.h | 388 + base/ui/jamp/multiplayer.menu | 571 + base/ui/jamp/password.menu | 140 + base/ui/jamp/password_request.menu | 173 + base/ui/jamp/player.menu | 638 + base/ui/jamp/player2.menu | 821 + base/ui/jamp/quickbots.menu | 652 + base/ui/jamp/quickgame.menu | 431 + base/ui/jamp/quickgame2.menu | 865 + base/ui/jamp/quit.menu | 505 + base/ui/jamp/rules.menu | 595 + base/ui/jamp/rules_force.menu | 1830 ++ base/ui/jamp/rules_games.menu | 963 + base/ui/jamp/rules_items.menu | 1276 ++ base/ui/jamp/rules_moves.menu | 461 + base/ui/jamp/rules_weapons.menu | 1612 ++ base/ui/jamp/saber.menu | 1381 ++ base/ui/jamp/serverinfo.menu | 179 + base/ui/jamp/setup.menu | 2959 +++ base/ui/jamp/siege_class.menu | 4417 +++++ base/ui/jamp/siege_msg.menu | 123 + base/ui/jamp/siege_team.menu | 464 + base/ui/jamp/vid_warning.menu | 205 + base/ui/jamp/videodriver.menu | 114 + base/ui/jamp/vssver.scc | Bin 0 -> 880 bytes base/ui/jampingame.txt | 35 + base/ui/jampmenus.txt | 37 + base/ui/loadgame.menu | 666 + base/ui/loadscreen.menu | 179 + base/ui/main.menu | 549 + base/ui/menus.txt | 18 + base/ui/missionfailed.menu | 347 + base/ui/newgame.menu | 735 + base/ui/newgame_first.menu | 926 + base/ui/quit.menu | 506 + base/ui/saber.menu | 1820 ++ base/ui/saberstyle.menu | 347 + base/ui/setup.menu | 2750 +++ base/ui/tier1.txt | 5 + base/ui/tier2.txt | 5 + base/ui/tier3.txt | 5 + base/ui/vid_warning.menu | 204 + base/ui/videodriver.menu | 113 + base/ui/vssver.scc | Bin 0 -> 928 bytes base/vssver.scc | Bin 0 -> 160 bytes bin/BehavEd.bhc | 471 + bin/StarWars.qe4 | 34 + bin/StarWarsMP.qe4 | 31 + bin/stringed.cfg | 25 + bin/vssver.scc | Bin 0 -> 96 bytes code/0_compiled_first/0_SH_Leak.cpp | 774 + code/ALut.lib | Bin 0 -> 4702 bytes code/EaxMan.dll | Bin 0 -> 94208 bytes code/IFC22.dll | Bin 0 -> 200704 bytes code/JediAcademy.ncb | Bin 0 -> 5262336 bytes code/JediAcademy.sln | 86 + code/JediAcademy.sln.old | 76 + code/JediAcademy.suo | Bin 0 -> 27648 bytes code/OpenAL32.dll | Bin 0 -> 221184 bytes code/OpenAL32.lib | Bin 0 -> 16866 bytes code/RMG/RM_Area.cpp | 480 + code/RMG/RM_Area.h | 99 + code/RMG/RM_Headers.h | 71 + code/RMG/RM_Instance.cpp | 191 + code/RMG/RM_Instance.h | 122 + code/RMG/RM_InstanceFile.cpp | 200 + code/RMG/RM_InstanceFile.h | 28 + code/RMG/RM_Instance_BSP.cpp | 294 + code/RMG/RM_Instance_BSP.h | 35 + code/RMG/RM_Instance_Group.cpp | 343 + code/RMG/RM_Instance_Group.h | 41 + code/RMG/RM_Instance_Random.cpp | 187 + code/RMG/RM_Instance_Random.h | 40 + code/RMG/RM_Instance_Void.cpp | 53 + code/RMG/RM_Instance_Void.h | 18 + code/RMG/RM_Manager.cpp | 402 + code/RMG/RM_Manager.h | 55 + code/RMG/RM_Mission.cpp | 1930 ++ code/RMG/RM_Mission.h | 129 + code/RMG/RM_Objective.cpp | 134 + code/RMG/RM_Objective.h | 65 + code/RMG/RM_Path.cpp | 721 + code/RMG/RM_Path.h | 223 + code/RMG/RM_Terrain.cpp | 533 + code/RMG/RM_Terrain.h | 97 + code/SHDebug/HA312W32.DLL | Bin 0 -> 382464 bytes code/SHDebug/SHW32.DLL | Bin 0 -> 112720 bytes code/StarWars.opt | Bin 0 -> 84992 bytes code/bspthing/bsp.h | 231 + code/bspthing/bspthing.sln | 21 + code/bspthing/bspthing.vcproj | 113 + code/bspthing/main.cpp | 1111 ++ code/bspthing/pbsp.h | 132 + code/cgame/FX_ATSTMain.cpp | 105 + code/cgame/FX_Blaster.cpp | 95 + code/cgame/FX_Bowcaster.cpp | 66 + code/cgame/FX_BryarPistol.cpp | 156 + code/cgame/FX_Concussion.cpp | 98 + code/cgame/FX_DEMP2.cpp | 92 + code/cgame/FX_Disruptor.cpp | 98 + code/cgame/FX_Emplaced.cpp | 146 + code/cgame/FX_Flechette.cpp | 73 + code/cgame/FX_HeavyRepeater.cpp | 92 + code/cgame/FX_NoghriShot.cpp | 72 + code/cgame/FX_RocketLauncher.cpp | 66 + code/cgame/FX_TuskenShot.cpp | 70 + code/cgame/FxParsing.cpp | 5 + code/cgame/FxParsing.h | 6 + code/cgame/FxPrimitives.cpp | 2301 +++ code/cgame/FxPrimitives.h | 572 + code/cgame/FxScheduler.cpp | 2049 +++ code/cgame/FxScheduler.h | 497 + code/cgame/FxSystem.cpp | 215 + code/cgame/FxSystem.h | 84 + code/cgame/FxTemplate.cpp | 2370 +++ code/cgame/FxUtil.cpp | 1400 ++ code/cgame/FxUtil.h | 130 + code/cgame/animtable.h | 1792 ++ code/cgame/cg_camera.cpp | 2003 ++ code/cgame/cg_camera.h | 165 + code/cgame/cg_consolecmds.cpp | 324 + code/cgame/cg_credits.cpp | 654 + code/cgame/cg_draw.cpp | 4418 +++++ code/cgame/cg_drawtools.cpp | 491 + code/cgame/cg_effects.cpp | 1087 ++ code/cgame/cg_ents.cpp | 2673 +++ code/cgame/cg_event.cpp | 1281 ++ code/cgame/cg_headers.cpp | 3 + code/cgame/cg_headers.h | 20 + code/cgame/cg_info.cpp | 648 + code/cgame/cg_lights.cpp | 87 + code/cgame/cg_lights.h | 16 + code/cgame/cg_local.h | 1233 ++ code/cgame/cg_localents.cpp | 599 + code/cgame/cg_main.cpp | 4442 +++++ code/cgame/cg_marks.cpp | 264 + code/cgame/cg_media.h | 417 + code/cgame/cg_players.cpp | 8262 +++++++++ code/cgame/cg_playerstate.cpp | 364 + code/cgame/cg_predict.cpp | 780 + code/cgame/cg_public.h | 214 + code/cgame/cg_scoreboard.cpp | 187 + code/cgame/cg_servercmds.cpp | 259 + code/cgame/cg_snapshot.cpp | 407 + code/cgame/cg_syscalls.cpp | 646 + code/cgame/cg_text.cpp | 786 + code/cgame/cg_view.cpp | 2249 +++ code/cgame/cg_weapons.cpp | 3164 ++++ code/cgame/common_headers.h | 10 + code/cgame/strip_objectives.h | 82 + code/client/BinkVideo.cpp | 490 + code/client/BinkVideo.h | 72 + code/client/OpenAL/al.h | 564 + code/client/OpenAL/alc.h | 97 + code/client/OpenAL/alctypes.h | 133 + code/client/OpenAL/altypes.h | 351 + code/client/OpenAL/alu.h | 34 + code/client/OpenAL/alut.h | 24 + code/client/cl_bink_copier.cpp | 66 + code/client/cl_cgame.cpp | 1384 ++ code/client/cl_cin.cpp | 1955 ++ code/client/cl_cin_console.cpp | 478 + code/client/cl_console.cpp | 730 + code/client/cl_input.cpp | 1039 ++ code/client/cl_input_hotswap.cpp | 242 + code/client/cl_input_hotswap.h | 64 + code/client/cl_keys.cpp | 1479 ++ code/client/cl_main.cpp | 1629 ++ code/client/cl_mp3.cpp | 554 + code/client/cl_mp3.h | 87 + code/client/cl_mp3.org | 419 + code/client/cl_parse.cpp | 510 + code/client/cl_scrn.cpp | 575 + code/client/cl_ui.cpp | 503 + code/client/client.h | 474 + code/client/client_ui.h | 13 + code/client/eax/EaxMan.h | 171 + code/client/eax/eax.h | 1562 ++ code/client/fffx.h | 129 + code/client/keycodes.h | 347 + code/client/keys.h | 61 + code/client/snd_ambient.cpp | 1165 ++ code/client/snd_ambient.h | 118 + code/client/snd_dma.cpp | 6276 +++++++ code/client/snd_dma_console.cpp | 3259 ++++ code/client/snd_local.h | 228 + code/client/snd_local_console.h | 141 + code/client/snd_mem.cpp | 1015 ++ code/client/snd_mem_console.cpp | 368 + code/client/snd_mix.cpp | 471 + code/client/snd_music.cpp | 1155 ++ code/client/snd_music.h | 67 + code/client/snd_public.h | 59 + code/client/vmachine.cpp | 39 + code/client/vmachine.h | 93 + code/ff/IFC/FeelitAPI.h | 1228 ++ code/ff/IFC/IFC.h | 79 + code/ff/IFC/IFC22.dll | Bin 0 -> 200704 bytes code/ff/IFC/IFC22.lib | Bin 0 -> 199282 bytes code/ff/IFC/IFCErrors.h | 181 + code/ff/IFC/ImmBaseTypes.h | 359 + code/ff/IFC/ImmBox.h | 170 + code/ff/IFC/ImmCompoundEffect.h | 228 + code/ff/IFC/ImmCondition.h | 451 + code/ff/IFC/ImmConstant.h | 219 + code/ff/IFC/ImmDXDevice.h | 148 + code/ff/IFC/ImmDamper.h | 185 + code/ff/IFC/ImmDevice.h | 281 + code/ff/IFC/ImmDevices.h | 156 + code/ff/IFC/ImmEffect.h | 440 + code/ff/IFC/ImmEffectSuite.h | 103 + code/ff/IFC/ImmEllipse.h | 295 + code/ff/IFC/ImmEnclosure.h | 325 + code/ff/IFC/ImmFriction.h | 176 + code/ff/IFC/ImmGrid.h | 179 + code/ff/IFC/ImmIFR.h | 308 + code/ff/IFC/ImmInertia.h | 183 + code/ff/IFC/ImmMouse.h | 164 + code/ff/IFC/ImmPeriodic.h | 259 + code/ff/IFC/ImmProjects.h | 392 + code/ff/IFC/ImmRamp.h | 225 + code/ff/IFC/ImmSpring.h | 183 + code/ff/IFC/ImmTexture.h | 407 + code/ff/cl_ff.cpp | 72 + code/ff/cl_ff.h | 13 + code/ff/common_headers.h | 50 + code/ff/ff.cpp | 383 + code/ff/ff.h | 33 + code/ff/ff_ChannelCompound.h | 64 + code/ff/ff_ChannelSet.cpp | 162 + code/ff/ff_ChannelSet.h | 59 + code/ff/ff_ConfigParser.cpp | 483 + code/ff/ff_ConfigParser.h | 51 + code/ff/ff_HandleTable.cpp | 133 + code/ff/ff_HandleTable.h | 61 + code/ff/ff_MultiCompound.cpp | 201 + code/ff/ff_MultiCompound.h | 54 + code/ff/ff_MultiEffect.cpp | 281 + code/ff/ff_MultiEffect.h | 59 + code/ff/ff_MultiSet.cpp | 140 + code/ff/ff_MultiSet.h | 43 + code/ff/ff_console.cpp | 287 + code/ff/ff_ffset.cpp | 356 + code/ff/ff_ffset.h | 64 + code/ff/ff_local.h | 24 + code/ff/ff_public.h | 43 + code/ff/ff_snd.cpp | 503 + code/ff/ff_snd.h | 11 + code/ff/ff_system.cpp | 151 + code/ff/ff_system.h | 117 + code/ff/ff_utils.cpp | 105 + code/ff/ff_utils.h | 154 + code/game/AI_Animal.cpp | 390 + code/game/AI_AssassinDroid.cpp | 197 + code/game/AI_Atst.cpp | 315 + code/game/AI_BobaFett.cpp | 1244 ++ code/game/AI_Civilian.cpp | 43 + code/game/AI_Default.cpp | 958 + code/game/AI_Droid.cpp | 556 + code/game/AI_GalakMech.cpp | 743 + code/game/AI_Glider.cpp | 3 + code/game/AI_Grenadier.cpp | 669 + code/game/AI_HazardTrooper.cpp | 1571 ++ code/game/AI_Howler.cpp | 852 + code/game/AI_ImperialProbe.cpp | 597 + code/game/AI_Interrogator.cpp | 456 + code/game/AI_Jedi.cpp | 7610 ++++++++ code/game/AI_Mark1.cpp | 746 + code/game/AI_Mark2.cpp | 358 + code/game/AI_MineMonster.cpp | 269 + code/game/AI_Rancor.cpp | 1691 ++ code/game/AI_Remote.cpp | 389 + code/game/AI_RocketTrooper.cpp | 912 + code/game/AI_SaberDroid.cpp | 443 + code/game/AI_SandCreature.cpp | 818 + code/game/AI_Seeker.cpp | 539 + code/game/AI_Sentry.cpp | 569 + code/game/AI_Sniper.cpp | 911 + code/game/AI_Stormtrooper.cpp | 2722 +++ code/game/AI_Tusken.cpp | 512 + code/game/AI_Utils.cpp | 1055 ++ code/game/AI_Wampa.cpp | 906 + code/game/AnimalNPC.c | 1065 ++ code/game/AnimalNPC.cpp | 680 + code/game/Copy of game.vcproj | 2916 +++ code/game/FighterNPC.c | 1751 ++ code/game/G_Timer.cpp | 397 + code/game/NPC.cpp | 2724 +++ code/game/NPC_behavior.cpp | 2067 +++ code/game/NPC_combat.cpp | 3317 ++++ code/game/NPC_goal.cpp | 188 + code/game/NPC_misc.cpp | 79 + code/game/NPC_move.cpp | 844 + code/game/NPC_reactions.cpp | 1164 ++ code/game/NPC_senses.cpp | 1106 ++ code/game/NPC_sounds.cpp | 118 + code/game/NPC_spawn.cpp | 4351 +++++ code/game/NPC_stats.cpp | 4018 ++++ code/game/NPC_utils.cpp | 1668 ++ code/game/Q3_Interface.cpp | 11286 ++++++++++++ code/game/Q3_Interface.h | 704 + code/game/SpeederNPC.c | 1190 ++ code/game/WalkerNPC.c | 573 + code/game/ai.h | 134 + code/game/anims.h | 1797 ++ code/game/b_local.h | 353 + code/game/b_public.h | 401 + code/game/bg_lib.cpp | 656 + code/game/bg_local.h | 56 + code/game/bg_misc.cpp | 741 + code/game/bg_pangles.cpp | 1851 ++ code/game/bg_panimate.cpp | 6640 +++++++ code/game/bg_pmove.cpp | 15091 ++++++++++++++++ code/game/bg_public.h | 736 + code/game/bg_slidemove.cpp | 573 + code/game/bg_vehicleload.c | 1678 ++ code/game/bset.h | 24 + code/game/bstate.h | 29 + code/game/channels.h | 22 + code/game/characters.h | 52 + code/game/common_headers.h | 26 + code/game/dmstates.h | 15 + code/game/events.h | 10 + code/game/fields.h | 64 + code/game/g_active.cpp | 5804 ++++++ code/game/g_breakable.cpp | 1535 ++ code/game/g_camera.cpp | 256 + code/game/g_client.cpp | 2424 +++ code/game/g_cmds.cpp | 1456 ++ code/game/g_combat.cpp | 6966 +++++++ code/game/g_emplaced.cpp | 1133 ++ code/game/g_functions.cpp | 412 + code/game/g_functions.h | 630 + code/game/g_fx.cpp | 1236 ++ code/game/g_headers.cpp | 8 + code/game/g_headers.h | 32 + code/game/g_inventory.cpp | 137 + code/game/g_itemLoad.cpp | 730 + code/game/g_items.cpp | 1304 ++ code/game/g_items.h | 94 + code/game/g_local.h | 627 + code/game/g_main.cpp | 2300 +++ code/game/g_mem.cpp | 38 + code/game/g_misc.cpp | 3297 ++++ code/game/g_misc_model.cpp | 792 + code/game/g_missile.cpp | 1536 ++ code/game/g_mover.cpp | 2657 +++ code/game/g_nav.cpp | 407 + code/game/g_nav.h | 30 + code/game/g_navigator.cpp | 5553 ++++++ code/game/g_navigator.h | 269 + code/game/g_navnew.cpp | 227 + code/game/g_object.cpp | 356 + code/game/g_objectives.cpp | 85 + code/game/g_public.h | 531 + code/game/g_rail.cpp | 975 + code/game/g_ref.cpp | 402 + code/game/g_roff.cpp | 646 + code/game/g_roff.h | 88 + code/game/g_savegame.cpp | 1275 ++ code/game/g_session.cpp | 221 + code/game/g_shared.h | 931 + code/game/g_spawn.cpp | 1655 ++ code/game/g_svcmds.cpp | 1344 ++ code/game/g_target.cpp | 1239 ++ code/game/g_trigger.cpp | 1685 ++ code/game/g_turret.cpp | 2516 +++ code/game/g_usable.cpp | 251 + code/game/g_utils.cpp | 2181 +++ code/game/g_vehicleLoad.cpp | 432 + code/game/g_vehicles.c | 3201 ++++ code/game/g_vehicles.h | 626 + code/game/g_weapon.cpp | 5377 ++++++ code/game/g_weaponLoad.cpp | 1362 ++ code/game/game.def | 4 + code/game/game.vcproj | 2916 +++ code/game/game.vcproj.vspscc | 10 + code/game/genericparser2.cpp | 1136 ++ code/game/genericparser2.h | 211 + code/game/ghoul2_shared.h | 495 + code/game/hitlocs.h | 35 + code/game/npc_headers.h | 7 + code/game/objectives.h | 341 + code/game/q_math.cpp | 1273 ++ code/game/q_shared.cpp | 1213 ++ code/game/q_shared.h | 2425 +++ code/game/say.h | 30 + code/game/statindex.h | 26 + code/game/surfaceflags.h | 190 + code/game/teams.h | 92 + code/game/weapons.h | 149 + code/game/wp_saber.cpp | 13776 ++++++++++++++ code/game/wp_saber.h | 440 + code/game/wp_saberLoad.cpp | 945 + code/ghoul2/G2.h | 201 + code/ghoul2/G2_API.cpp | 2145 +++ code/ghoul2/G2_bolts.cpp | 253 + code/ghoul2/G2_bones.cpp | 4756 +++++ code/ghoul2/G2_misc.cpp | 1873 ++ code/ghoul2/G2_surfaces.cpp | 428 + code/ghoul2/ghoul2_gore.h | 191 + code/goblib/Copy of goblib.vcproj | 824 + code/goblib/debug/vc70.idb | Bin 0 -> 68608 bytes code/goblib/debug/vc70.pdb | Bin 0 -> 69632 bytes code/goblib/goblib.cpp | 1876 ++ code/goblib/goblib.h | 299 + code/goblib/goblib.vcproj | 824 + code/goblib/goblib.vcproj.old | 822 + code/icarus/BlockStream.cpp | 586 + code/icarus/IcarusImplementation.cpp | 809 + code/icarus/IcarusImplementation.h | 253 + code/icarus/IcarusInterface.h | 143 + code/icarus/Sequence.cpp | 674 + code/icarus/Sequencer.cpp | 2614 +++ code/icarus/StdAfx.h | 19 + code/icarus/TaskManager.cpp | 2036 +++ code/icarus/blockstream.h | 213 + code/icarus/sequence.h | 115 + code/icarus/sequencer.h | 163 + code/icarus/taskmanager.h | 227 + code/jpeg-6/jcapimin.cpp | 234 + code/jpeg-6/jccoefct.cpp | 454 + code/jpeg-6/jccolor.cpp | 465 + code/jpeg-6/jcdctmgr.cpp | 397 + code/jpeg-6/jchuff.cpp | 853 + code/jpeg-6/jchuff.h | 34 + code/jpeg-6/jcinit.cpp | 79 + code/jpeg-6/jcmainct.cpp | 302 + code/jpeg-6/jcmarker.cpp | 645 + code/jpeg-6/jcmaster.cpp | 584 + code/jpeg-6/jcomapi.cpp | 100 + code/jpeg-6/jconfig.h | 41 + code/jpeg-6/jcparam.cpp | 580 + code/jpeg-6/jcphuff.cpp | 835 + code/jpeg-6/jcprepct.cpp | 377 + code/jpeg-6/jcsample.cpp | 525 + code/jpeg-6/jctrans.cpp | 378 + code/jpeg-6/jdapimin.cpp | 404 + code/jpeg-6/jdapistd.cpp | 281 + code/jpeg-6/jdatadst.cpp | 158 + code/jpeg-6/jdatasrc.cpp | 210 + code/jpeg-6/jdcoefct.cpp | 731 + code/jpeg-6/jdcolor.cpp | 373 + code/jpeg-6/jdct.h | 176 + code/jpeg-6/jddctmgr.cpp | 276 + code/jpeg-6/jdhuff.cpp | 580 + code/jpeg-6/jdhuff.h | 202 + code/jpeg-6/jdinput.cpp | 386 + code/jpeg-6/jdmainct.cpp | 525 + code/jpeg-6/jdmarker.cpp | 1058 ++ code/jpeg-6/jdmaster.cpp | 562 + code/jpeg-6/jdpostct.cpp | 296 + code/jpeg-6/jdsample.cpp | 484 + code/jpeg-6/jdtrans.cpp | 129 + code/jpeg-6/jerror.cpp | 239 + code/jpeg-6/jerror.h | 273 + code/jpeg-6/jfdctflt.cpp | 174 + code/jpeg-6/jidctflt.cpp | 246 + code/jpeg-6/jinclude.h | 116 + code/jpeg-6/jmemmgr.cpp | 1120 ++ code/jpeg-6/jmemnobs.cpp | 111 + code/jpeg-6/jmemsys.h | 182 + code/jpeg-6/jmorecfg.h | 349 + code/jpeg-6/jpegint.h | 388 + code/jpeg-6/jpeglib.h | 1065 ++ code/jpeg-6/jutils.cpp | 179 + code/jpeg-6/jversion.h | 14 + code/mac/MacGamma.c | 487 + code/mac/MacGamma.cpp | 474 + code/mac/MacGamma.h | 82 + code/mac/MacQuake3 | Bin 0 -> 141015 bytes code/mac/mac_console.c | 119 + code/mac/mac_event.c | 357 + code/mac/mac_glimp.c | 829 + code/mac/mac_input.c | 212 + code/mac/mac_local.h | 321 + code/mac/mac_main.c | 693 + code/mac/mac_net.c | 527 + code/mac/mac_snddma.c | 140 + code/mac/macprefix.h | 3 + code/mac/q3.rsrc | 0 code/mp3code/cdct.c | 320 + code/mp3code/config.h | 136 + code/mp3code/copyright.h | 19 + code/mp3code/csbt.c | 355 + code/mp3code/csbtb.c | 279 + code/mp3code/csbtl3.c | 309 + code/mp3code/cup.c | 546 + code/mp3code/cupini.c | 401 + code/mp3code/cupl1.c | 325 + code/mp3code/cupl3.c | 1287 ++ code/mp3code/cwin.c | 470 + code/mp3code/cwinb.c | 465 + code/mp3code/cwinm.c | 55 + code/mp3code/htable.h | 999 + code/mp3code/hwin.c | 264 + code/mp3code/jdw.h | 28 + code/mp3code/l3.h | 187 + code/mp3code/l3dq.c | 262 + code/mp3code/l3init.c | 422 + code/mp3code/mdct.c | 229 + code/mp3code/mhead.c | 328 + code/mp3code/mhead.h | 102 + code/mp3code/mp3struct.h | 141 + code/mp3code/msis.c | 296 + code/mp3code/port.h | 80 + code/mp3code/small_header.h | 34 + code/mp3code/tableawd.h | 93 + code/mp3code/towave.c | 766 + code/mp3code/uph.c | 507 + code/mp3code/upsf.c | 404 + code/mp3code/wavep.c | 96 + code/null/mac_net.c | 44 + code/null/null_glimp.c | 39 + code/null/null_main.c | 94 + code/null/null_net.c | 43 + code/null/null_snddma.c | 27 + code/png/png.cpp | 783 + code/png/png.h | 73 + code/qcommon/MiniHeap.h | 67 + code/qcommon/chash.h | 162 + code/qcommon/cm_draw.cpp | 1488 ++ code/qcommon/cm_draw.h | 245 + code/qcommon/cm_landscape.h | 271 + code/qcommon/cm_load.cpp | 1298 ++ code/qcommon/cm_load_xbox.cpp | 1280 ++ code/qcommon/cm_local.h | 321 + code/qcommon/cm_patch.cpp | 2930 +++ code/qcommon/cm_patch.h | 121 + code/qcommon/cm_polylib.cpp | 711 + code/qcommon/cm_polylib.h | 51 + code/qcommon/cm_public.h | 72 + code/qcommon/cm_randomterrain.cpp | 1086 ++ code/qcommon/cm_randomterrain.h | 89 + code/qcommon/cm_shader.cpp | 529 + code/qcommon/cm_terrain.cpp | 1714 ++ code/qcommon/cm_terrainmap.cpp | 489 + code/qcommon/cm_terrainmap.h | 77 + code/qcommon/cm_test.cpp | 793 + code/qcommon/cm_trace.cpp | 1223 ++ code/qcommon/cmd.cpp | 722 + code/qcommon/common.cpp | 1662 ++ code/qcommon/cvar.cpp | 951 + code/qcommon/files.h | 119 + code/qcommon/files_common.cpp | 605 + code/qcommon/files_console.cpp | 1031 ++ code/qcommon/files_pc.cpp | 1741 ++ code/qcommon/fixedmap.h | 169 + code/qcommon/hstring.cpp | 525 + code/qcommon/hstring.h | 219 + code/qcommon/md4.cpp | 274 + code/qcommon/msg.cpp | 1248 ++ code/qcommon/net_chan.cpp | 566 + code/qcommon/platform.h | 17 + code/qcommon/qcommon.h | 855 + code/qcommon/qfiles.h | 634 + code/qcommon/sparc.h | 725 + code/qcommon/sstring.h | 120 + code/qcommon/stringed_ingame.cpp | 985 + code/qcommon/stringed_ingame.h | 53 + code/qcommon/stringed_interface.cpp | 215 + code/qcommon/stringed_interface.h | 21 + code/qcommon/stv_version.h | 13 + code/qcommon/tags.h | 42 + code/qcommon/timing.h | 62 + code/qcommon/tri_coll_test.cpp | 506 + code/qcommon/unzip.cpp | 1348 ++ code/qcommon/unzip.h | 286 + code/qcommon/xb_settings.cpp | 382 + code/qcommon/xb_settings.h | 87 + code/qcommon/z_memman_console.cpp | 1882 ++ code/qcommon/z_memman_pc.cpp | 958 + code/ragl/graph_region.h | 419 + code/ragl/graph_triangulate.h | 833 + code/ragl/graph_vs.h | 1776 ++ code/ragl/kdtree_vs.h | 458 + code/ragl/ragl_common.h | 232 + code/ratl/array_vs.h | 73 + code/ratl/bits_vs.h | 218 + code/ratl/grid_vs.h | 526 + code/ratl/handle_pool_vs.h | 291 + code/ratl/hash_pool_vs.h | 200 + code/ratl/heap_vs.h | 324 + code/ratl/list_vs.h | 751 + code/ratl/map_vs.h | 1629 ++ code/ratl/pool_vs.h | 570 + code/ratl/queue_vs.h | 231 + code/ratl/ratl.cpp | 130 + code/ratl/ratl_common.h | 1180 ++ code/ratl/scheduler_vs.h | 218 + code/ratl/stack_vs.h | 197 + code/ratl/string_vs.h | 366 + code/ratl/vector_vs.h | 757 + code/ravl/CBounds.cpp | 366 + code/ravl/CBounds.h | 188 + code/ravl/CMatrix.h | 165 + code/ravl/CVec.cpp | 1154 ++ code/ravl/CVec.h | 1002 + code/renderer/amd3d.h | 471 + code/renderer/glext.h | 2920 +++ code/renderer/glext_console.h | 2521 +++ code/renderer/matcomp.c | 361 + code/renderer/matcomp.h | 31 + code/renderer/mdx_format.h | 434 + code/renderer/qgl.h | 734 + code/renderer/qgl_console.h | 1207 ++ code/renderer/qgl_linked.h | 336 + code/renderer/ref_trin.def | 2 + code/renderer/tr_WorldEffects.cpp | 2295 +++ code/renderer/tr_WorldEffects.h | 43 + code/renderer/tr_animation.cpp | 478 + code/renderer/tr_arioche.cpp | 149 + code/renderer/tr_backend.cpp | 2073 +++ code/renderer/tr_bsp.cpp | 1458 ++ code/renderer/tr_bsp_xbox.cpp | 1699 ++ code/renderer/tr_cmds.cpp | 512 + code/renderer/tr_curve.cpp | 903 + code/renderer/tr_draw.cpp | 1155 ++ code/renderer/tr_flares.cpp | 427 + code/renderer/tr_font.cpp | 1785 ++ code/renderer/tr_font.h | 34 + code/renderer/tr_ghoul2.cpp | 4906 +++++ code/renderer/tr_image.cpp | 2678 +++ code/renderer/tr_init.cpp | 1633 ++ code/renderer/tr_jpeg_interface.cpp | 541 + code/renderer/tr_jpeg_interface.h | 40 + code/renderer/tr_landscape.h | 193 + code/renderer/tr_light.cpp | 572 + code/renderer/tr_lightmanager.cpp | 944 + code/renderer/tr_lightmanager.h | 70 + code/renderer/tr_local.h | 2188 +++ code/renderer/tr_main.cpp | 1726 ++ code/renderer/tr_marks.cpp | 500 + code/renderer/tr_mesh.cpp | 447 + code/renderer/tr_model.cpp | 1203 ++ code/renderer/tr_noise.cpp | 89 + code/renderer/tr_public.h | 147 + code/renderer/tr_quicksprite.cpp | 229 + code/renderer/tr_quicksprite.h | 48 + code/renderer/tr_scene.cpp | 405 + code/renderer/tr_shade.cpp | 2821 +++ code/renderer/tr_shade_calc.cpp | 1632 ++ code/renderer/tr_shader.cpp | 4143 +++++ code/renderer/tr_shadows.cpp | 801 + code/renderer/tr_sky.cpp | 845 + code/renderer/tr_stl.cpp | 82 + code/renderer/tr_stl.h | 31 + code/renderer/tr_surface.cpp | 2477 +++ code/renderer/tr_surfacesprites.cpp | 1492 ++ code/renderer/tr_terrain.cpp | 1047 ++ code/renderer/tr_types.h | 244 + code/renderer/tr_world.cpp | 1021 ++ code/rufl/hfile.cpp | 378 + code/rufl/hfile.h | 61 + code/rufl/hstring.cpp | 192 + code/rufl/hstring.h | 106 + code/rufl/random.cpp | 0 code/rufl/random.h | 0 code/server/exe_headers.cpp | 5 + code/server/exe_headers.h | 13 + code/server/server.h | 321 + code/server/sv_ccmds.cpp | 484 + code/server/sv_client.cpp | 605 + code/server/sv_game.cpp | 724 + code/server/sv_init.cpp | 752 + code/server/sv_main.cpp | 572 + code/server/sv_savegame.cpp | 2998 +++ code/server/sv_snapshot.cpp | 749 + code/server/sv_world.cpp | 1012 ++ code/smartheap/HAW32M.LIB | Bin 0 -> 212108 bytes code/smartheap/HEAPAGNT.H | 442 + code/smartheap/SMRTHEAP.C | 54 + code/smartheap/SMRTHEAP.H | 847 + code/smartheap/smrtheap.hpp | 197 + code/starwars.plg | 16 + code/starwars.vcproj | 6784 +++++++ code/tonet.bat | 10 + code/tosend.bat | 6 + code/ui/gameinfo.cpp | 35 + code/ui/gameinfo.h | 24 + code/ui/menudef.h | 143 + code/ui/ui.def | 4 + code/ui/ui_atoms.cpp | 504 + code/ui/ui_connect.cpp | 127 + code/ui/ui_debug.cpp | 747 + code/ui/ui_local.h | 259 + code/ui/ui_main.cpp | 8580 +++++++++ code/ui/ui_playerinfo.h | 74 + code/ui/ui_public.h | 250 + code/ui/ui_saber.cpp | 866 + code/ui/ui_shared.cpp | 12322 +++++++++++++ code/ui/ui_shared.h | 535 + code/ui/ui_splash.cpp | 372 + code/ui/ui_splash.h | 11 + code/ui/ui_syscalls.cpp | 177 + code/unix/Makefile | 988 + code/unix/linux_glimp.c | 1387 ++ code/unix/linux_qgl.c | 4111 +++++ code/unix/linux_snd.c | 244 + code/unix/matha.s | 402 + code/unix/q3test.spec.sh | 41 + code/unix/qasm.h | 459 + code/unix/quake3.gif | Bin 0 -> 1378 bytes code/unix/snd_mixa.s | 197 + code/unix/sys_dosa.s | 94 + code/unix/ui_video.c | 702 + code/unix/unix_glw.h | 17 + code/unix/unix_main.c | 809 + code/unix/unix_net.c | 443 + code/unix/unix_shared.c | 369 + code/win32/AutoVersion.h | 79 + code/win32/FeelIt/FEELitIFR.h | 247 + code/win32/FeelIt/FFC.h | 78 + code/win32/FeelIt/FFC10.dll | Bin 0 -> 126976 bytes code/win32/FeelIt/FFC10.lib | Bin 0 -> 232332 bytes code/win32/FeelIt/FFC10d.dll | Bin 0 -> 405591 bytes code/win32/FeelIt/FFC10d.lib | Bin 0 -> 232598 bytes code/win32/FeelIt/FFCErrors.h | 171 + code/win32/FeelIt/FeelBaseTypes.h | 265 + code/win32/FeelIt/FeelBox.h | 178 + code/win32/FeelIt/FeelCompoundEffect.h | 184 + code/win32/FeelIt/FeelCondition.h | 345 + code/win32/FeelIt/FeelConstant.h | 193 + code/win32/FeelIt/FeelDXDevice.h | 126 + code/win32/FeelIt/FeelDamper.h | 177 + code/win32/FeelIt/FeelDevice.h | 196 + code/win32/FeelIt/FeelEffect.h | 344 + code/win32/FeelIt/FeelEllipse.h | 253 + code/win32/FeelIt/FeelEnclosure.h | 268 + code/win32/FeelIt/FeelFriction.h | 172 + code/win32/FeelIt/FeelGrid.h | 171 + code/win32/FeelIt/FeelInertia.h | 178 + code/win32/FeelIt/FeelMouse.h | 143 + code/win32/FeelIt/FeelPeriodic.h | 227 + code/win32/FeelIt/FeelProjects.h | 302 + code/win32/FeelIt/FeelRamp.h | 194 + code/win32/FeelIt/FeelSpring.h | 191 + code/win32/FeelIt/FeelTexture.h | 287 + code/win32/FeelIt/FeelitAPI.h | 1252 ++ code/win32/FeelIt/fffx.cpp | 680 + code/win32/FeelIt/fffx_feel.cpp | 689 + code/win32/FeelIt/fffx_feel.h | 29 + code/win32/background.bmp | Bin 0 -> 197688 bytes code/win32/bink.h | 620 + code/win32/binkw32.lib | Bin 0 -> 58414 bytes code/win32/clear.bmp | Bin 0 -> 5174 bytes code/win32/dbg_console_xbox.cpp | 172 + code/win32/dbg_console_xbox.h | 34 + code/win32/game.rc | 104 + code/win32/glw_win.h | 30 + code/win32/glw_win_dx8.h | 184 + code/win32/rad.h | 962 + code/win32/resource.h | 21 + code/win32/shader_constants.h | 64 + code/win32/snd_fx_img.h | 85 + code/win32/starwars.ico | Bin 0 -> 3638 bytes code/win32/win_file.h | 33 + code/win32/win_file_xbox.cpp | 171 + code/win32/win_filecode.cpp | 350 + code/win32/win_gamma.cpp | 141 + code/win32/win_gamma_console.cpp | 73 + code/win32/win_glimp.cpp | 1815 ++ code/win32/win_glimp_console.cpp | 261 + code/win32/win_highdynamicrange.cpp | 673 + code/win32/win_highdynamicrange.h | 77 + code/win32/win_input.cpp | 1147 ++ code/win32/win_input.h | 101 + code/win32/win_input_console.cpp | 900 + code/win32/win_input_rumble.cpp | 686 + code/win32/win_input_xbox.cpp | 339 + code/win32/win_lighteffects.cpp | 977 + code/win32/win_lighteffects.h | 52 + code/win32/win_local.h | 79 + code/win32/win_main.cpp | 1241 ++ code/win32/win_main_common.cpp | 332 + code/win32/win_main_console.cpp | 821 + code/win32/win_qal_xbox.cpp | 1342 ++ code/win32/win_qgl.cpp | 4276 +++++ code/win32/win_qgl_dx8.cpp | 6963 +++++++ code/win32/win_shared.cpp | 281 + code/win32/win_snd.cpp | 414 + code/win32/win_stencilshadow.cpp | 474 + code/win32/win_stencilshadow.h | 63 + code/win32/win_stream_dx8.cpp | 511 + code/win32/win_syscon.cpp | 536 + code/win32/win_video.cpp | 325 + code/win32/win_wndproc.cpp | 532 + code/win32/winquake.rc | 101 + code/win32/xbox_texture_man.h | 126 + code/x_exe/Copy of x_exe.vcproj | 1518 ++ code/x_exe/title.bmp | Bin 0 -> 49206 bytes code/x_exe/title.rdf | 7 + code/x_exe/titleimage.xbx | Bin 0 -> 10240 bytes code/x_exe/x_exe.vcproj | 1476 ++ code/x_exe/x_exe.vcproj.old | 1516 ++ code/x_game/Copy of x_game.vcproj | 1264 ++ code/x_game/x_game.vcproj | 1263 ++ code/x_game/x_game.vcproj.old | 1262 ++ code/x_shaders/bump.psh | 30 + code/x_shaders/bump.vsh | 64 + code/x_shaders/bump.xpu | Bin 0 -> 244 bytes code/x_shaders/bump.xvu | Bin 0 -> 324 bytes code/x_shaders/dlight.psh | 38 + code/x_shaders/dlight.vsh | 56 + code/x_shaders/dlight.xpu | Bin 0 -> 244 bytes code/x_shaders/dlight.xvu | Bin 0 -> 244 bytes code/x_shaders/environment.vsh | 53 + code/x_shaders/environment.xvu | Bin 0 -> 324 bytes code/x_shaders/extracthot.psh | 25 + code/x_shaders/extracthot.xpu | Bin 0 -> 244 bytes code/x_shaders/hotblur.psh | 30 + code/x_shaders/hotblur.xpu | Bin 0 -> 244 bytes code/x_shaders/rain.psh | 10 + code/x_shaders/rain.vsh | 59 + code/x_shaders/shadow.vsh | 30 + code/x_shaders/shadow.xvu | Bin 0 -> 180 bytes code/x_shaders/specular_dynamic.psh | 15 + code/x_shaders/specular_dynamic.vsh | 68 + code/x_shaders/specular_dynamic.xpu | Bin 0 -> 244 bytes code/x_shaders/specular_dynamic.xvu | Bin 0 -> 356 bytes code/x_shaders/specular_static.psh | 10 + code/x_shaders/specular_static.vsh | 53 + code/x_shaders/specular_static.xpu | Bin 0 -> 244 bytes code/x_shaders/specular_static.xvu | Bin 0 -> 292 bytes code/zlib/adler32.c | 52 + code/zlib/compress.c | 71 + code/zlib/crc32.c | 165 + code/zlib/deflate.c | 1355 ++ code/zlib/deflate.h | 318 + code/zlib/infblock.c | 401 + code/zlib/infblock.h | 39 + code/zlib/infcodes.c | 260 + code/zlib/infcodes.h | 27 + code/zlib/inffast.c | 173 + code/zlib/inffast.h | 17 + code/zlib/inffixed.h | 151 + code/zlib/inflate.c | 369 + code/zlib/inftrees.c | 458 + code/zlib/inftrees.h | 58 + code/zlib/infutil.c | 90 + code/zlib/infutil.h | 98 + code/zlib/trees.c | 1217 ++ code/zlib/trees.h | 128 + code/zlib/uncompr.c | 61 + code/zlib/zconf.h | 279 + code/zlib/zlib.h | 893 + code/zlib/zutil.c | 228 + code/zlib/zutil.h | 220 + code/zlib32/deflate.cpp | 2078 +++ code/zlib32/deflate.h | 231 + code/zlib32/inflate.cpp | 1839 ++ code/zlib32/inflate.h | 145 + code/zlib32/zip.h | 195 + code/zlib32/zipcommon.cpp | 117 + codemp/CommandLine.txt | 3 + codemp/JKA_mp.sln | 76 + codemp/Splines/Splines.dsp | 156 + codemp/Splines/math_angles.cpp | 129 + codemp/Splines/math_angles.h | 174 + codemp/Splines/math_matrix.cpp | 113 + codemp/Splines/math_matrix.h | 202 + codemp/Splines/math_quaternion.cpp | 57 + codemp/Splines/math_quaternion.h | 169 + codemp/Splines/math_vector.cpp | 123 + codemp/Splines/math_vector.h | 553 + codemp/Splines/q_parse.cpp | 514 + codemp/Splines/q_shared.cpp | 955 + codemp/Splines/q_shared.h | 792 + codemp/Splines/splines.cpp | 1226 ++ codemp/Splines/splines.h | 1061 ++ codemp/Splines/util_list.h | 325 + codemp/Splines/util_str.cpp | 598 + codemp/Splines/util_str.h | 796 + codemp/WinDed.vcproj | 901 + codemp/alut.lib | Bin 0 -> 4702 bytes codemp/botlib/aasfile.h | 246 + codemp/botlib/be_aas_bsp.h | 72 + codemp/botlib/be_aas_bspq3.cpp | 470 + codemp/botlib/be_aas_cluster.cpp | 1528 ++ codemp/botlib/be_aas_cluster.h | 21 + codemp/botlib/be_aas_debug.cpp | 764 + codemp/botlib/be_aas_debug.h | 45 + codemp/botlib/be_aas_def.h | 295 + codemp/botlib/be_aas_entity.cpp | 420 + codemp/botlib/be_aas_entity.h | 46 + codemp/botlib/be_aas_file.cpp | 565 + codemp/botlib/be_aas_file.h | 25 + codemp/botlib/be_aas_funcs.h | 30 + codemp/botlib/be_aas_main.cpp | 412 + codemp/botlib/be_aas_main.h | 44 + codemp/botlib/be_aas_move.cpp | 1090 ++ codemp/botlib/be_aas_move.h | 54 + codemp/botlib/be_aas_optimize.cpp | 295 + codemp/botlib/be_aas_optimize.h | 16 + codemp/botlib/be_aas_reach.cpp | 4527 +++++ codemp/botlib/be_aas_reach.h | 51 + codemp/botlib/be_aas_route.cpp | 2192 +++ codemp/botlib/be_aas_route.h | 50 + codemp/botlib/be_aas_routealt.cpp | 223 + codemp/botlib/be_aas_routealt.h | 23 + codemp/botlib/be_aas_sample.cpp | 1377 ++ codemp/botlib/be_aas_sample.h | 52 + codemp/botlib/be_ai_char.cpp | 773 + codemp/botlib/be_ai_chat.cpp | 3000 +++ codemp/botlib/be_ai_gen.cpp | 117 + codemp/botlib/be_ai_goal.cpp | 1805 ++ codemp/botlib/be_ai_move.cpp | 3599 ++++ codemp/botlib/be_ai_weap.cpp | 526 + codemp/botlib/be_ai_weight.cpp | 895 + codemp/botlib/be_ai_weight.h | 66 + codemp/botlib/be_ea.cpp | 525 + codemp/botlib/be_interface.cpp | 688 + codemp/botlib/be_interface.h | 40 + codemp/botlib/botlib.dsp | 400 + codemp/botlib/botlib.vcproj | 434 + codemp/botlib/l_crc.cpp | 134 + codemp/botlib/l_crc.h | 16 + codemp/botlib/l_libvar.cpp | 277 + codemp/botlib/l_libvar.h | 46 + codemp/botlib/l_log.cpp | 152 + codemp/botlib/l_log.h | 29 + codemp/botlib/l_memory.cpp | 446 + codemp/botlib/l_memory.h | 59 + codemp/botlib/l_precomp.cpp | 3324 ++++ codemp/botlib/l_precomp.h | 168 + codemp/botlib/l_script.cpp | 1418 ++ codemp/botlib/l_script.h | 232 + codemp/botlib/l_struct.cpp | 445 + codemp/botlib/l_struct.h | 58 + codemp/botlib/l_utils.h | 18 + codemp/buildvms.bat | 8 + codemp/cgame/JK2_cgame.def | 3 + codemp/cgame/JK2_cgame.dsp | 412 + codemp/cgame/JK2_cgame.vcproj | 563 + codemp/cgame/animtable.h | 1792 ++ codemp/cgame/cg_consolecmds.c | 416 + codemp/cgame/cg_draw.c | 10281 +++++++++++ codemp/cgame/cg_drawtools.c | 544 + codemp/cgame/cg_effects.c | 1546 ++ codemp/cgame/cg_ents.c | 3888 ++++ codemp/cgame/cg_event.c | 3572 ++++ codemp/cgame/cg_info.c | 412 + codemp/cgame/cg_light.c | 85 + codemp/cgame/cg_lights.h | 16 + codemp/cgame/cg_local.h | 2631 +++ codemp/cgame/cg_localents.c | 869 + codemp/cgame/cg_main.c | 4319 +++++ codemp/cgame/cg_marks.c | 2279 +++ codemp/cgame/cg_media.h | 0 codemp/cgame/cg_newDraw.c | 893 + codemp/cgame/cg_playeranimate.c | 0 codemp/cgame/cg_players.c | 11435 ++++++++++++ codemp/cgame/cg_playerstate.c | 537 + codemp/cgame/cg_predict.c | 1539 ++ codemp/cgame/cg_public.h | 596 + codemp/cgame/cg_saga.c | 1106 ++ codemp/cgame/cg_scoreboard.c | 854 + codemp/cgame/cg_servercmds.c | 1764 ++ codemp/cgame/cg_snapshot.c | 497 + codemp/cgame/cg_strap.c | 73 + codemp/cgame/cg_syscalls.asm | 203 + codemp/cgame/cg_syscalls.c | 1118 ++ codemp/cgame/cg_turret.c | 242 + codemp/cgame/cg_view.c | 3454 ++++ codemp/cgame/cg_weaponinit.c | 592 + codemp/cgame/cg_weapons.c | 2750 +++ codemp/cgame/cgame.bat | 19 + codemp/cgame/fx_blaster.c | 65 + codemp/cgame/fx_bowcaster.c | 62 + codemp/cgame/fx_bryarpistol.c | 237 + codemp/cgame/fx_demp2.c | 259 + codemp/cgame/fx_disruptor.c | 148 + codemp/cgame/fx_flechette.c | 67 + codemp/cgame/fx_force.c | 16 + codemp/cgame/fx_heavyrepeater.c | 165 + codemp/cgame/fx_local.h | 63 + codemp/cgame/fx_rocketlauncher.c | 61 + codemp/cgame/holocronicons.h | 24 + codemp/cgame/tr_types.h | 352 + codemp/client/0_sh_leak.cpp | 412 + codemp/client/BinkVideo.cpp | 524 + codemp/client/BinkVideo.h | 73 + codemp/client/cl_cgame.cpp | 2147 +++ codemp/client/cl_cin.cpp | 1499 ++ codemp/client/cl_cin_console.cpp | 79 + codemp/client/cl_console.cpp | 467 + codemp/client/cl_data.cpp | 317 + codemp/client/cl_data.h | 272 + codemp/client/cl_input.cpp | 2757 +++ codemp/client/cl_input_hotswap.cpp | 258 + codemp/client/cl_input_hotswap.h | 63 + codemp/client/cl_keys.cpp | 1738 ++ codemp/client/cl_main.cpp | 2337 +++ codemp/client/cl_net_chan.cpp | 179 + codemp/client/cl_parse.cpp | 1064 ++ codemp/client/cl_scrn.cpp | 486 + codemp/client/cl_ui.cpp | 882 + codemp/client/client.h | 575 + codemp/client/eax/eax.h | 1562 ++ codemp/client/eax/eaxman.h | 171 + codemp/client/fffx.h | 129 + codemp/client/fxexport.cpp | 105 + codemp/client/fxexport.h | 23 + codemp/client/fxprimitives.cpp | 2497 +++ codemp/client/fxprimitives.h | 611 + codemp/client/fxscheduler.cpp | 1774 ++ codemp/client/fxscheduler.h | 505 + codemp/client/fxsystem.cpp | 130 + codemp/client/fxsystem.h | 226 + codemp/client/fxtemplate.cpp | 2386 +++ codemp/client/fxutil.cpp | 1247 ++ codemp/client/fxutil.h | 116 + codemp/client/keycodes.h | 347 + codemp/client/keys.h | 64 + codemp/client/openal/al.h | 564 + codemp/client/openal/alc.h | 97 + codemp/client/openal/alctypes.h | 133 + codemp/client/openal/altypes.h | 351 + codemp/client/openal/alu.h | 34 + codemp/client/openal/alut.h | 24 + codemp/client/snd_ambient.cpp | 1137 ++ codemp/client/snd_ambient.h | 118 + codemp/client/snd_dma.cpp | 6332 +++++++ codemp/client/snd_dma_console.cpp | 2990 +++ codemp/client/snd_local.h | 228 + codemp/client/snd_local_console.h | 134 + codemp/client/snd_mem.cpp | 1015 ++ codemp/client/snd_mem_console.cpp | 353 + codemp/client/snd_mix.cpp | 470 + codemp/client/snd_mp3.cpp | 553 + codemp/client/snd_mp3.h | 87 + codemp/client/snd_music.cpp | 1153 ++ codemp/client/snd_music.h | 65 + codemp/client/snd_public.h | 67 + codemp/encryption/buffer.cpp | 99 + codemp/encryption/buffer.h | 37 + codemp/encryption/cpp_interface.cpp | 419 + codemp/encryption/cpp_interface.h | 20 + codemp/encryption/encryption.h | 6 + codemp/encryption/sockets.cpp | 427 + codemp/encryption/sockets.h | 95 + codemp/ff/ff_console.cpp | 287 + codemp/game/AnimalNPC.c | 946 + codemp/game/FighterNPC.c | 1961 ++ codemp/game/JK2_game.def | 3 + codemp/game/JK2_game.dsp | 650 + codemp/game/JK2_game.vcproj | 744 + codemp/game/NPC.c | 2127 +++ codemp/game/NPC_AI_Atst.c | 308 + codemp/game/NPC_AI_Default.c | 957 + codemp/game/NPC_AI_Droid.c | 621 + codemp/game/NPC_AI_Grenadier.c | 679 + codemp/game/NPC_AI_Howler.c | 218 + codemp/game/NPC_AI_ImperialProbe.c | 609 + codemp/game/NPC_AI_Jedi.c | 6168 +++++++ codemp/game/NPC_AI_MineMonster.c | 278 + codemp/game/NPC_AI_Rancor.c | 961 + codemp/game/NPC_AI_Remote.c | 389 + codemp/game/NPC_AI_Seeker.c | 574 + codemp/game/NPC_AI_Sentry.c | 577 + codemp/game/NPC_AI_Sniper.c | 864 + codemp/game/NPC_AI_Stormtrooper.c | 2742 +++ codemp/game/NPC_AI_Utils.c | 1139 ++ codemp/game/NPC_AI_Wampa.c | 654 + codemp/game/NPC_behavior.c | 1748 ++ codemp/game/NPC_combat.c | 3144 ++++ codemp/game/NPC_goal.c | 267 + codemp/game/NPC_misc.c | 73 + codemp/game/NPC_move.c | 505 + codemp/game/NPC_reactions.c | 1127 ++ codemp/game/NPC_senses.c | 934 + codemp/game/NPC_sounds.c | 93 + codemp/game/NPC_spawn.c | 4272 +++++ codemp/game/NPC_stats.c | 3352 ++++ codemp/game/NPC_utils.c | 1788 ++ codemp/game/SpeederNPC.c | 1134 ++ codemp/game/WalkerNPC.c | 636 + codemp/game/ai.h | 126 + codemp/game/ai_main.c | 7649 ++++++++ codemp/game/ai_main.h | 411 + codemp/game/ai_util.c | 862 + codemp/game/ai_wpnav.c | 3813 ++++ codemp/game/anims.h | 1797 ++ codemp/game/b_local.h | 329 + codemp/game/b_public.h | 355 + codemp/game/be_aas.h | 205 + codemp/game/be_ai_char.h | 32 + codemp/game/be_ai_chat.h | 97 + codemp/game/be_ai_gen.h | 17 + codemp/game/be_ai_goal.h | 102 + codemp/game/be_ai_move.h | 126 + codemp/game/be_ai_weap.h | 88 + codemp/game/be_ea.h | 52 + codemp/game/bg_g2_utils.c | 124 + codemp/game/bg_lib.c | 1318 ++ codemp/game/bg_lib.h | 70 + codemp/game/bg_local.h | 109 + codemp/game/bg_misc.c | 3415 ++++ codemp/game/bg_panimate.c | 2967 +++ codemp/game/bg_pmove.c | 10880 +++++++++++ codemp/game/bg_public.h | 1673 ++ codemp/game/bg_saber.c | 3690 ++++ codemp/game/bg_saberLoad.c | 1501 ++ codemp/game/bg_saga.c | 1510 ++ codemp/game/bg_saga.h | 116 + codemp/game/bg_slidemove.c | 1059 ++ codemp/game/bg_strap.h | 38 + codemp/game/bg_vehicleLoad.c | 1678 ++ codemp/game/bg_vehicles.h | 628 + codemp/game/bg_weapons.c | 402 + codemp/game/bg_weapons.h | 113 + codemp/game/botlib.h | 508 + codemp/game/chars.h | 124 + codemp/game/g_ICARUScb.c | 5798 ++++++ codemp/game/g_ICARUScb.h | 15 + codemp/game/g_active.c | 3781 ++++ codemp/game/g_arenas.c | 343 + codemp/game/g_bot.c | 1316 ++ codemp/game/g_client.c | 3935 ++++ codemp/game/g_cmds.c | 3958 ++++ codemp/game/g_combat.c | 5651 ++++++ codemp/game/g_exphysics.c | 232 + codemp/game/g_headers.h | 4 + codemp/game/g_items.c | 3256 ++++ codemp/game/g_local.h | 1976 ++ codemp/game/g_log.c | 1776 ++ codemp/game/g_main.c | 4233 +++++ codemp/game/g_mem.c | 42 + codemp/game/g_misc.c | 3287 ++++ codemp/game/g_missile.c | 1010 ++ codemp/game/g_mover.c | 3267 ++++ codemp/game/g_nav.c | 1928 ++ codemp/game/g_nav.h | 79 + codemp/game/g_navnew.c | 865 + codemp/game/g_object.c | 287 + codemp/game/g_public.h | 920 + codemp/game/g_saga.c | 1900 ++ codemp/game/g_session.c | 322 + codemp/game/g_spawn.c | 1474 ++ codemp/game/g_strap.c | 73 + codemp/game/g_svcmds.c | 470 + codemp/game/g_syscalls.asm | 313 + codemp/game/g_syscalls.c | 1367 ++ codemp/game/g_target.c | 987 + codemp/game/g_team.c | 1225 ++ codemp/game/g_team.h | 50 + codemp/game/g_timer.c | 294 + codemp/game/g_trigger.c | 1794 ++ codemp/game/g_turret.c | 853 + codemp/game/g_turret_G2.c | 1281 ++ codemp/game/g_utils.c | 2378 +++ codemp/game/g_vehicleTurret.c | 435 + codemp/game/g_vehicles.c | 3051 ++++ codemp/game/g_weapon.c | 4953 +++++ codemp/game/game.bat | 19 + codemp/game/inv.h | 104 + codemp/game/match.h | 122 + codemp/game/npc_headers.h | 7 + codemp/game/q_math.c | 1684 ++ codemp/game/q_shared.c | 1378 ++ codemp/game/q_shared.h | 3094 ++++ codemp/game/say.h | 30 + codemp/game/surfaceflags.h | 122 + codemp/game/syn.h | 20 + codemp/game/teams.h | 79 + codemp/game/tri_coll_test.c | 292 + codemp/game/w_force.c | 5809 ++++++ codemp/game/w_saber.c | 9037 +++++++++ codemp/game/w_saber.h | 74 + codemp/ghoul2/g2.h | 40 + codemp/ghoul2/g2_api.cpp | 2698 +++ codemp/ghoul2/g2_bolts.cpp | 331 + codemp/ghoul2/g2_bones.cpp | 4905 +++++ codemp/ghoul2/g2_gore.h | 201 + codemp/ghoul2/g2_local.h | 226 + codemp/ghoul2/g2_misc.cpp | 1926 ++ codemp/ghoul2/g2_surfaces.cpp | 677 + codemp/ghoul2/ghoul2_shared.h | 472 + codemp/goblib/goblib.cpp | 1876 ++ codemp/goblib/goblib.h | 299 + codemp/goblib/goblib.vcproj | 463 + codemp/icarus/blockstream.cpp | 698 + codemp/icarus/blockstream.h | 198 + codemp/icarus/gameinterface.cpp | 733 + codemp/icarus/gameinterface.h | 36 + codemp/icarus/icarus.h | 32 + codemp/icarus/instance.cpp | 655 + codemp/icarus/instance.h | 81 + codemp/icarus/interface.cpp | 24 + codemp/icarus/interface.h | 72 + codemp/icarus/interpreter.cpp | 2506 +++ codemp/icarus/interpreter.h | 224 + codemp/icarus/memory.cpp | 20 + codemp/icarus/module.h | 1 + codemp/icarus/q3_interface.cpp | 1013 ++ codemp/icarus/q3_interface.h | 297 + codemp/icarus/q3_registers.cpp | 429 + codemp/icarus/q3_registers.h | 36 + codemp/icarus/sequence.cpp | 559 + codemp/icarus/sequence.h | 98 + codemp/icarus/sequencer.cpp | 2483 +++ codemp/icarus/sequencer.h | 189 + codemp/icarus/taskmanager.cpp | 1994 ++ codemp/icarus/taskmanager.h | 191 + codemp/icarus/tokenizer.cpp | 2837 +++ codemp/icarus/tokenizer.h | 601 + codemp/install.bat | 1 + codemp/installvms.bat | 4 + codemp/jk2mp.vcproj | 6986 +++++++ codemp/jpeg-6/jcapimin.cpp | 230 + codemp/jpeg-6/jccoefct.cpp | 450 + codemp/jpeg-6/jccolor.cpp | 461 + codemp/jpeg-6/jcdctmgr.cpp | 393 + codemp/jpeg-6/jchuff.cpp | 848 + codemp/jpeg-6/jchuff.h | 34 + codemp/jpeg-6/jcinit.cpp | 74 + codemp/jpeg-6/jcmainct.cpp | 298 + codemp/jpeg-6/jcmarker.cpp | 641 + codemp/jpeg-6/jcmaster.cpp | 580 + codemp/jpeg-6/jcomapi.cpp | 96 + codemp/jpeg-6/jconfig.h | 41 + codemp/jpeg-6/jcparam.cpp | 577 + codemp/jpeg-6/jcphuff.cpp | 831 + codemp/jpeg-6/jcprepct.cpp | 373 + codemp/jpeg-6/jcsample.cpp | 521 + codemp/jpeg-6/jctrans.cpp | 373 + codemp/jpeg-6/jdapimin.cpp | 400 + codemp/jpeg-6/jdapistd.cpp | 277 + codemp/jpeg-6/jdatadst.cpp | 153 + codemp/jpeg-6/jdatasrc.cpp | 206 + codemp/jpeg-6/jdcoefct.cpp | 727 + codemp/jpeg-6/jdcolor.cpp | 369 + codemp/jpeg-6/jdct.h | 176 + codemp/jpeg-6/jddctmgr.cpp | 272 + codemp/jpeg-6/jdhuff.cpp | 576 + codemp/jpeg-6/jdhuff.h | 202 + codemp/jpeg-6/jdinput.cpp | 383 + codemp/jpeg-6/jdmainct.cpp | 522 + codemp/jpeg-6/jdmarker.cpp | 1054 ++ codemp/jpeg-6/jdmaster.cpp | 559 + codemp/jpeg-6/jdpostct.cpp | 292 + codemp/jpeg-6/jdsample.cpp | 480 + codemp/jpeg-6/jdtrans.cpp | 124 + codemp/jpeg-6/jerror.cpp | 234 + codemp/jpeg-6/jerror.h | 273 + codemp/jpeg-6/jfdctflt.cpp | 170 + codemp/jpeg-6/jidctflt.cpp | 243 + codemp/jpeg-6/jinclude.h | 116 + codemp/jpeg-6/jmemmgr.cpp | 1117 ++ codemp/jpeg-6/jmemnobs.cpp | 107 + codemp/jpeg-6/jmemsys.h | 182 + codemp/jpeg-6/jmorecfg.h | 349 + codemp/jpeg-6/jpegint.h | 388 + codemp/jpeg-6/jpeglib.h | 1055 ++ codemp/jpeg-6/jutils.cpp | 177 + codemp/jpeg-6/jversion.h | 14 + codemp/mp3code/cdct.c | 320 + codemp/mp3code/config.h | 136 + codemp/mp3code/copyright.h | 19 + codemp/mp3code/csbt.c | 355 + codemp/mp3code/csbtb.c | 279 + codemp/mp3code/csbtl3.c | 309 + codemp/mp3code/cup.c | 546 + codemp/mp3code/cupini.c | 401 + codemp/mp3code/cupl1.c | 325 + codemp/mp3code/cupl3.c | 1287 ++ codemp/mp3code/cwin.c | 470 + codemp/mp3code/cwinb.c | 465 + codemp/mp3code/cwinm.c | 55 + codemp/mp3code/htable.h | 999 + codemp/mp3code/hwin.c | 264 + codemp/mp3code/jdw.h | 28 + codemp/mp3code/l3.h | 187 + codemp/mp3code/l3dq.c | 262 + codemp/mp3code/l3init.c | 422 + codemp/mp3code/mdct.c | 229 + codemp/mp3code/mhead.c | 328 + codemp/mp3code/mhead.h | 102 + codemp/mp3code/mp3struct.h | 140 + codemp/mp3code/msis.c | 296 + codemp/mp3code/port.h | 80 + codemp/mp3code/small_header.h | 34 + codemp/mp3code/tableawd.h | 93 + codemp/mp3code/towave.c | 760 + codemp/mp3code/uph.c | 507 + codemp/mp3code/upsf.c | 404 + codemp/mp3code/wavep.c | 96 + codemp/namespace_begin.h | 15 + codemp/namespace_end.h | 17 + codemp/null/mac_net.c | 44 + codemp/null/null_client.cpp | 68 + codemp/null/null_glimp.cpp | 74 + codemp/null/null_input.cpp | 14 + codemp/null/null_main.c | 95 + codemp/null/null_net.c | 43 + codemp/null/null_renderer.cpp | 21 + codemp/null/null_snddma.cpp | 49 + codemp/null/win_main.cpp | 1482 ++ codemp/openal32.dll | Bin 0 -> 221184 bytes codemp/openal32.lib | Bin 0 -> 16866 bytes codemp/png/png.cpp | 783 + codemp/png/png.h | 73 + codemp/qcommon/chash.h | 162 + codemp/qcommon/cm_draw.cpp | 1490 ++ codemp/qcommon/cm_draw.h | 250 + codemp/qcommon/cm_landscape.h | 271 + codemp/qcommon/cm_load.cpp | 1184 ++ codemp/qcommon/cm_load_xbox.cpp | 1155 ++ codemp/qcommon/cm_local.h | 310 + codemp/qcommon/cm_patch.cpp | 1809 ++ codemp/qcommon/cm_patch.h | 128 + codemp/qcommon/cm_patch_xbox.cpp | 1760 ++ codemp/qcommon/cm_polylib.cpp | 713 + codemp/qcommon/cm_polylib.h | 47 + codemp/qcommon/cm_public.h | 74 + codemp/qcommon/cm_randomterrain.cpp | 1091 ++ codemp/qcommon/cm_randomterrain.h | 89 + codemp/qcommon/cm_shader.cpp | 522 + codemp/qcommon/cm_terrain.cpp | 1720 ++ codemp/qcommon/cm_terrainmap.cpp | 497 + codemp/qcommon/cm_terrainmap.h | 83 + codemp/qcommon/cm_test.cpp | 575 + codemp/qcommon/cm_trace.cpp | 2002 ++ codemp/qcommon/cmd_common.cpp | 651 + codemp/qcommon/cmd_console.cpp | 165 + codemp/qcommon/cmd_pc.cpp | 174 + codemp/qcommon/cnetprofile.cpp | 97 + codemp/qcommon/common.cpp | 2400 +++ codemp/qcommon/cvar.cpp | 1031 ++ codemp/qcommon/disablewarnings.h | 38 + codemp/qcommon/exe_headers.cpp | 3 + codemp/qcommon/exe_headers.h | 5 + codemp/qcommon/files.h | 158 + codemp/qcommon/files_common.cpp | 512 + codemp/qcommon/files_console.cpp | 1068 ++ codemp/qcommon/files_pc.cpp | 3125 ++++ codemp/qcommon/fixedmap.h | 167 + codemp/qcommon/game_version.h | 14 + codemp/qcommon/genericparser2.cpp | 1203 ++ codemp/qcommon/genericparser2.h | 204 + codemp/qcommon/hstring.cpp | 501 + codemp/qcommon/hstring.h | 229 + codemp/qcommon/huffman.cpp | 541 + codemp/qcommon/inetprofile.h | 20 + codemp/qcommon/md4.cpp | 296 + codemp/qcommon/miniheap.h | 57 + codemp/qcommon/msg.cpp | 3097 ++++ codemp/qcommon/net_chan.cpp | 703 + codemp/qcommon/platform.h | 22 + codemp/qcommon/q_math.cpp | 4 + codemp/qcommon/q_shared.cpp | 4 + codemp/qcommon/qcommon.h | 1158 ++ codemp/qcommon/qfiles.h | 607 + codemp/qcommon/roffsystem.cpp | 1040 ++ codemp/qcommon/roffsystem.h | 185 + codemp/qcommon/sparc.h | 725 + codemp/qcommon/sstring.h | 120 + codemp/qcommon/stringed_ingame.cpp | 985 + codemp/qcommon/stringed_ingame.h | 53 + codemp/qcommon/stringed_interface.cpp | 215 + codemp/qcommon/stringed_interface.h | 21 + codemp/qcommon/strip.cpp | 1776 ++ codemp/qcommon/strip.h | 312 + codemp/qcommon/tags.h | 57 + codemp/qcommon/timing.h | 61 + codemp/qcommon/unzip.cpp | 1337 ++ codemp/qcommon/unzip.h | 289 + codemp/qcommon/vm.cpp | 954 + codemp/qcommon/vm_console.cpp | 229 + codemp/qcommon/vm_interpreted.cpp | 905 + codemp/qcommon/vm_local.h | 182 + codemp/qcommon/vm_ppc.cpp | 1274 ++ codemp/qcommon/vm_x86.cpp | 1166 ++ codemp/qcommon/xb_settings.cpp | 342 + codemp/qcommon/xb_settings.h | 83 + codemp/qcommon/z_memman_console.cpp | 1899 ++ codemp/qcommon/z_memman_pc.cpp | 832 + codemp/ratl/bits_vs.h | 218 + codemp/ratl/ratl_common.h | 1180 ++ codemp/ratl/vector_vs.h | 757 + codemp/ravl/CVec.h | 1002 + codemp/renderer/glext.h | 3037 ++++ codemp/renderer/glext_console.h | 2521 +++ codemp/renderer/matcomp.c | 293 + codemp/renderer/matcomp.h | 31 + codemp/renderer/mdx_format.h | 434 + codemp/renderer/modelmem.h | 310 + codemp/renderer/qgl.h | 757 + codemp/renderer/qgl_console.h | 1205 ++ codemp/renderer/tr_animation.cpp | 16 + codemp/renderer/tr_arioche.cpp | 117 + codemp/renderer/tr_backend.cpp | 2211 +++ codemp/renderer/tr_bsp.cpp | 2123 +++ codemp/renderer/tr_bsp_xbox.cpp | 1820 ++ codemp/renderer/tr_cmds.cpp | 484 + codemp/renderer/tr_curve.cpp | 612 + codemp/renderer/tr_curve_xbox.cpp | 536 + codemp/renderer/tr_flares.cpp | 433 + codemp/renderer/tr_font.cpp | 1747 ++ codemp/renderer/tr_font.h | 34 + codemp/renderer/tr_ghoul2.cpp | 5441 ++++++ codemp/renderer/tr_image.cpp | 3365 ++++ codemp/renderer/tr_image_xbox.cpp | 2644 +++ codemp/renderer/tr_init.cpp | 1554 ++ codemp/renderer/tr_landscape.h | 191 + codemp/renderer/tr_light.cpp | 475 + codemp/renderer/tr_lightmanager.cpp | 882 + codemp/renderer/tr_lightmanager.h | 34 + codemp/renderer/tr_local.h | 2351 +++ codemp/renderer/tr_main.cpp | 1646 ++ codemp/renderer/tr_marks.cpp | 449 + codemp/renderer/tr_mesh.cpp | 409 + codemp/renderer/tr_model.cpp | 2042 +++ codemp/renderer/tr_noise.cpp | 84 + codemp/renderer/tr_public.h | 116 + codemp/renderer/tr_quicksprite.cpp | 222 + codemp/renderer/tr_quicksprite.h | 47 + codemp/renderer/tr_scene.cpp | 883 + codemp/renderer/tr_shade.cpp | 2482 +++ codemp/renderer/tr_shade_calc.cpp | 1801 ++ codemp/renderer/tr_shader.cpp | 4351 +++++ codemp/renderer/tr_shadows.cpp | 724 + codemp/renderer/tr_sky.cpp | 849 + codemp/renderer/tr_surface.cpp | 2100 +++ codemp/renderer/tr_surfacesprites.cpp | 1463 ++ codemp/renderer/tr_terrain.cpp | 1056 ++ codemp/renderer/tr_world.cpp | 1939 ++ codemp/renderer/tr_worldeffects.cpp | 2025 +++ codemp/renderer/tr_worldeffects.h | 108 + codemp/rmg/rm_area.cpp | 478 + codemp/rmg/rm_area.h | 99 + codemp/rmg/rm_headers.h | 73 + codemp/rmg/rm_instance.cpp | 195 + codemp/rmg/rm_instance.h | 122 + codemp/rmg/rm_instance_bsp.cpp | 282 + codemp/rmg/rm_instance_bsp.h | 35 + codemp/rmg/rm_instance_group.cpp | 344 + codemp/rmg/rm_instance_group.h | 41 + codemp/rmg/rm_instance_random.cpp | 188 + codemp/rmg/rm_instance_random.h | 40 + codemp/rmg/rm_instance_void.cpp | 54 + codemp/rmg/rm_instance_void.h | 18 + codemp/rmg/rm_instancefile.cpp | 201 + codemp/rmg/rm_instancefile.h | 28 + codemp/rmg/rm_manager.cpp | 474 + codemp/rmg/rm_manager.h | 63 + codemp/rmg/rm_mission.cpp | 1940 ++ codemp/rmg/rm_mission.h | 129 + codemp/rmg/rm_objective.cpp | 135 + codemp/rmg/rm_objective.h | 65 + codemp/rmg/rm_path.cpp | 723 + codemp/rmg/rm_path.h | 223 + codemp/rmg/rm_terrain.cpp | 517 + codemp/rmg/rm_terrain.h | 97 + codemp/server/NPCNav/gameCallbacks.cpp | 49 + codemp/server/NPCNav/navigator.cpp | 2783 +++ codemp/server/NPCNav/navigator.h | 280 + codemp/server/exe_headers.cpp | 5 + codemp/server/exe_headers.h | 13 + codemp/server/server.h | 443 + codemp/server/sv_bot.cpp | 804 + codemp/server/sv_ccmds.cpp | 1040 ++ codemp/server/sv_client.cpp | 1897 ++ codemp/server/sv_game.cpp | 1776 ++ codemp/server/sv_init.cpp | 1156 ++ codemp/server/sv_main.cpp | 937 + codemp/server/sv_net_chan.cpp | 179 + codemp/server/sv_rankings.cpp | 1516 ++ codemp/server/sv_snapshot.cpp | 882 + codemp/server/sv_world.cpp | 894 + codemp/smartheap/heapagnt.h | 442 + codemp/smartheap/smrtheap.c | 54 + codemp/smartheap/smrtheap.h | 847 + codemp/smartheap/smrtheap.hpp | 197 + codemp/strings/con_text.h | 22 + codemp/strings/str_server.h | 23 + codemp/tosend.bat | 5 + codemp/ui/keycodes.h | 349 + codemp/ui/ui.bat | 19 + codemp/ui/ui.def | 3 + codemp/ui/ui.dsp | 267 + codemp/ui/ui.vcproj | 433 + codemp/ui/ui_atoms.c | 496 + codemp/ui/ui_force.c | 1576 ++ codemp/ui/ui_force.h | 45 + codemp/ui/ui_gameinfo.c | 333 + codemp/ui/ui_local.h | 1085 ++ codemp/ui/ui_main.c | 13881 ++++++++++++++ codemp/ui/ui_players.c | 1351 ++ codemp/ui/ui_public.h | 260 + codemp/ui/ui_saber.c | 1109 ++ codemp/ui/ui_shared.c | 9635 ++++++++++ codemp/ui/ui_shared.h | 630 + codemp/ui/ui_syscalls.asm | 148 + codemp/ui/ui_syscalls.c | 652 + codemp/ui/ui_util.c | 11 + codemp/unix/ftol.nasm | 131 + codemp/unix/linux_common.c | 323 + codemp/unix/linux_glimp.c | 1543 ++ codemp/unix/linux_joystick.c | 186 + codemp/unix/linux_local.h | 29 + codemp/unix/linux_qgl.c | 4132 +++++ codemp/unix/linux_snd.c | 237 + codemp/unix/snapvector.nasm | 75 + codemp/unix/unix_main.c | 1164 ++ codemp/unix/unix_net.c | 599 + codemp/unix/unix_shared.c | 350 + codemp/unix/vm_x86.c | 8 + codemp/update_MPents.bat | 1 + codemp/win32/AutoVersion.h | 98 + codemp/win32/JK2cgame.rc | 104 + codemp/win32/JK2game.rc | 104 + codemp/win32/WinDed.rc | 105 + codemp/win32/dbg_console_xbox.cpp | 172 + codemp/win32/dbg_console_xbox.h | 34 + codemp/win32/glw_win.h | 33 + codemp/win32/glw_win_dx8.h | 187 + codemp/win32/qe3.ico | Bin 0 -> 3638 bytes codemp/win32/resource.h | 23 + codemp/win32/shader_constants.h | 53 + codemp/win32/snd_fx_img.h | 85 + codemp/win32/ui.rc | 104 + codemp/win32/win_file.h | 32 + codemp/win32/win_file_xbox.cpp | 171 + codemp/win32/win_filecode.cpp | 345 + codemp/win32/win_gamma.cpp | 165 + codemp/win32/win_gamma_console.cpp | 73 + codemp/win32/win_glimp.cpp | 2095 +++ codemp/win32/win_glimp_console.cpp | 282 + codemp/win32/win_highdynamicrange.cpp | 632 + codemp/win32/win_highdynamicrange.h | 77 + codemp/win32/win_input.cpp | 1141 ++ codemp/win32/win_input.h | 103 + codemp/win32/win_input_console.cpp | 689 + codemp/win32/win_input_rumble.cpp | 755 + codemp/win32/win_input_xbox.cpp | 363 + codemp/win32/win_lighteffects.cpp | 908 + codemp/win32/win_lighteffects.h | 52 + codemp/win32/win_local.h | 85 + codemp/win32/win_main.cpp | 1661 ++ codemp/win32/win_main_common.cpp | 357 + codemp/win32/win_main_console.cpp | 1017 ++ codemp/win32/win_net.cpp | 1235 ++ codemp/win32/win_net_xbox.cpp | 747 + codemp/win32/win_qal_xbox.cpp | 1337 ++ codemp/win32/win_qgl.cpp | 4271 +++++ codemp/win32/win_qgl_dx8.cpp | 6523 +++++++ codemp/win32/win_shared.cpp | 547 + codemp/win32/win_snd.cpp | 382 + codemp/win32/win_stream_dx8.cpp | 456 + codemp/win32/win_syscon.cpp | 574 + codemp/win32/win_wndproc.cpp | 547 + codemp/win32/win_worldeffects.cpp | 1887 ++ codemp/win32/winquake.rc | 101 + codemp/win32/xbox_texture_man.h | 255 + codemp/x_botlib/x_botlib.vcproj | 403 + codemp/x_exe/x_exe.vcproj | 1051 ++ codemp/x_jk2cgame/x_jk2cgame.vcproj | 1375 ++ codemp/x_jk2game/x_jk2game.vcproj | 2189 +++ codemp/x_ui/x_ui.vcproj | 492 + codemp/xbox/JediAcademy.xms | 67 + codemp/xbox/XBLive.cpp | 1524 ++ codemp/xbox/XBLive.h | 250 + codemp/xbox/XBLive_Friends.cpp | 716 + codemp/xbox/XBLive_MM.cpp | 883 + codemp/xbox/XBLive_PL.cpp | 636 + codemp/xbox/XBVoice.cpp | 1473 ++ codemp/xbox/XBVoice.h | 302 + codemp/xbox/XBoxCommon.h | 147 + codemp/xbox/XHVVoiceManager.cpp | 413 + codemp/xbox/XHVVoiceManager.h | 84 + codemp/xbox/match.cpp | 1469 ++ codemp/xbox/match.h | 439 + codemp/xbox/xbSockAddr.cpp | 136 + codemp/xbox/xbSockAddr.h | 45 + codemp/xbox/xbsocket.cpp | 432 + codemp/xbox/xbsocket.h | 68 + codemp/zlib/adler32.c | 52 + codemp/zlib/compress.c | 71 + codemp/zlib/crc32.c | 165 + codemp/zlib/deflate.c | 1355 ++ codemp/zlib/deflate.h | 318 + codemp/zlib/infblock.c | 401 + codemp/zlib/infblock.h | 39 + codemp/zlib/infcodes.c | 260 + codemp/zlib/infcodes.h | 27 + codemp/zlib/inffast.c | 173 + codemp/zlib/inffast.h | 17 + codemp/zlib/inffixed.h | 151 + codemp/zlib/inflate.c | 369 + codemp/zlib/inftrees.c | 458 + codemp/zlib/inftrees.h | 58 + codemp/zlib/infutil.c | 90 + codemp/zlib/infutil.h | 98 + codemp/zlib/trees.c | 1217 ++ codemp/zlib/trees.h | 128 + codemp/zlib/uncompr.c | 61 + codemp/zlib/zconf.h | 279 + codemp/zlib/zlib.h | 893 + codemp/zlib/zutil.c | 228 + codemp/zlib/zutil.h | 220 + codemp/zlib32/deflate.cpp | 2078 +++ codemp/zlib32/deflate.h | 231 + codemp/zlib32/inflate.cpp | 1839 ++ codemp/zlib32/inflate.h | 145 + codemp/zlib32/zip.h | 195 + codemp/zlib32/zipcommon.cpp | 117 + tools/ModView/GenericParser2.cpp | 1085 ++ tools/ModView/GenericParser2.h | 192 + tools/ModView/ModView.dsp | 998 + tools/ModView/ModView.dsw | 37 + tools/ModView/ModView.ncb | 1 + tools/ModView/ModView.sln | 21 + tools/ModView/ModView.vcproj | 1078 ++ tools/ModView/Splash.cpp | 172 + tools/ModView/Splash.h | 57 + tools/ModView/Splsh16.bmp | Bin 0 -> 106630 bytes tools/ModView/_console_skin_list_ | 0 tools/ModView/_console_str_list_ | 0 tools/ModView/anims.cpp | 414 + tools/ModView/anims.h | 16 + tools/ModView/clipboard.cpp | 437 + tools/ModView/clipboard.h | 20 + tools/ModView/commarea.cpp | 482 + tools/ModView/commarea.h | 42 + tools/ModView/common_headers.h | 23 + tools/ModView/commtest/MainFrm.cpp | 521 + tools/ModView/commtest/MainFrm.h | 72 + tools/ModView/commtest/ReadMe.txt | 105 + tools/ModView/commtest/StdAfx.cpp | 6 + tools/ModView/commtest/StdAfx.h | 27 + tools/ModView/commtest/bits.h | 61 + tools/ModView/commtest/commtest.cpp | 172 + tools/ModView/commtest/commtest.dsp | 190 + tools/ModView/commtest/commtest.h | 51 + tools/ModView/commtest/commtest.rc | 404 + tools/ModView/commtest/commtestDoc.cpp | 84 + tools/ModView/commtest/commtestDoc.h | 57 + tools/ModView/commtest/commtestView.cpp | 471 + tools/ModView/commtest/commtestView.h | 74 + tools/ModView/commtest/res/Toolbar.bmp | Bin 0 -> 1078 bytes tools/ModView/commtest/res/commtest.ico | Bin 0 -> 1078 bytes tools/ModView/commtest/res/commtest.rc2 | 13 + tools/ModView/commtest/res/commtestDoc.ico | Bin 0 -> 1078 bytes tools/ModView/commtest/resource.h | 27 + tools/ModView/commtest/wintalk.cpp | 308 + tools/ModView/disablewarnings.h | 33 + tools/ModView/drag.cpp | 146 + tools/ModView/drag.h | 25 + tools/ModView/files.cpp | 335 + tools/ModView/files.h | 26 + tools/ModView/generic_stuff.cpp | 808 + tools/ModView/generic_stuff.h | 59 + tools/ModView/genericparser.cpp | 831 + tools/ModView/genericparser.h | 208 + tools/ModView/getstring.cpp | 66 + tools/ModView/getstring.h | 51 + tools/ModView/gl_bits.cpp | 521 + tools/ModView/gl_bits.h | 39 + tools/ModView/glm_code.cpp | 1708 ++ tools/ModView/glm_code.h | 101 + tools/ModView/image.cpp | 985 + tools/ModView/image.h | 21 + tools/ModView/includes.h | 107 + tools/ModView/jpeg6/jcomapi.c | 94 + tools/ModView/jpeg6/jconfig.h | 41 + tools/ModView/jpeg6/jdapimin.c | 398 + tools/ModView/jpeg6/jdapistd.c | 275 + tools/ModView/jpeg6/jdatasrc.c | 205 + tools/ModView/jpeg6/jdcoefct.c | 725 + tools/ModView/jpeg6/jdcolor.c | 367 + tools/ModView/jpeg6/jdct.h | 176 + tools/ModView/jpeg6/jddctmgr.c | 270 + tools/ModView/jpeg6/jdhuff.c | 574 + tools/ModView/jpeg6/jdhuff.h | 202 + tools/ModView/jpeg6/jdinput.c | 381 + tools/ModView/jpeg6/jdmainct.c | 512 + tools/ModView/jpeg6/jdmarker.c | 1052 ++ tools/ModView/jpeg6/jdmaster.c | 557 + tools/ModView/jpeg6/jdpostct.c | 290 + tools/ModView/jpeg6/jdsample.c | 478 + tools/ModView/jpeg6/jdtrans.c | 122 + tools/ModView/jpeg6/jerror.c | 240 + tools/ModView/jpeg6/jerror.h | 273 + tools/ModView/jpeg6/jidctflt.c | 241 + tools/ModView/jpeg6/jinclude.h | 105 + tools/ModView/jpeg6/jmemmgr.c | 1115 ++ tools/ModView/jpeg6/jmemnobs.c | 123 + tools/ModView/jpeg6/jmemsys.h | 182 + tools/ModView/jpeg6/jmorecfg.h | 352 + tools/ModView/jpeg6/jpegint.h | 388 + tools/ModView/jpeg6/jpeglib.h | 1082 ++ tools/ModView/jpeg6/jutils.c | 175 + tools/ModView/jpeg6/jversion.h | 14 + tools/ModView/jpeg_interface.cpp | 248 + tools/ModView/jpeg_interface.h | 41 + tools/ModView/mainfrm.cpp | 1825 ++ tools/ModView/mainfrm.h | 183 + tools/ModView/matcomp.cpp | 407 + tools/ModView/matcomp.h | 48 + tools/ModView/mc_compress2.cpp | 527 + tools/ModView/mc_compress2.h | 32 + tools/ModView/md3_format.h | 118 + tools/ModView/mdr_format.h | 103 + tools/ModView/mdx_format.h | 423 + tools/ModView/model.cpp | 5588 ++++++ tools/ModView/model.h | 437 + tools/ModView/modview.cpp | 263 + tools/ModView/modview.h | 50 + tools/ModView/modview.rc | 1050 ++ tools/ModView/modviewdoc.cpp | 181 + tools/ModView/modviewdoc.h | 58 + tools/ModView/modviewtreeview.cpp | 1634 ++ tools/ModView/modviewtreeview.h | 176 + tools/ModView/modviewview.cpp | 635 + tools/ModView/modviewview.h | 95 + tools/ModView/oldskins.cpp | 680 + tools/ModView/oldskins.h | 25 + tools/ModView/parser.cpp | 136 + tools/ModView/parser.h | 13 + tools/ModView/png/png.cpp | 836 + tools/ModView/png/png.h | 68 + tools/ModView/r_common.h | 512 + tools/ModView/r_glm.cpp | 1873 ++ tools/ModView/r_glm.h | 42 + tools/ModView/r_image.cpp | 1199 ++ tools/ModView/r_image.h | 18 + tools/ModView/r_md3.cpp | 22 + tools/ModView/r_md3.h | 17 + tools/ModView/r_mdr.cpp | 25 + tools/ModView/r_mdr.h | 17 + tools/ModView/r_model.cpp | 1237 ++ tools/ModView/r_model.h | 35 + tools/ModView/r_surface.cpp | 1392 ++ tools/ModView/r_surface.h | 48 + tools/ModView/readme.txt | 105 + tools/ModView/res/modview.ico | Bin 0 -> 766 bytes tools/ModView/res/modview.rc2 | 13 + tools/ModView/res/modviewdoc.ico | Bin 0 -> 1078 bytes tools/ModView/res/toolbar.bmp | Bin 0 -> 2518 bytes tools/ModView/resource.h | 192 + tools/ModView/script.cpp | 851 + tools/ModView/script.h | 54 + tools/ModView/sequence.cpp | 164 + tools/ModView/sequence.h | 27 + tools/ModView/shader.cpp | 775 + tools/ModView/shader.h | 139 + tools/ModView/skins.cpp | 1127 ++ tools/ModView/skins.h | 51 + tools/ModView/sof2npcviewer.cpp | 1910 ++ tools/ModView/sof2npcviewer.h | 71 + tools/ModView/special_defines.h | 16 + tools/ModView/stdafx.cpp | 8 + tools/ModView/stdafx.h | 33 + tools/ModView/stl.h | 33 + tools/ModView/text.cpp | 315 + tools/ModView/text.h | 26 + tools/ModView/textures.cpp | 1186 ++ tools/ModView/textures.h | 46 + tools/ModView/todo.h | 22 + tools/ModView/vssver.scc | Bin 0 -> 457 bytes tools/ModView/wintalk.cpp | 653 + tools/ModView/wintalk.h | 19 + tools/ModView/zlib/adler32.c | 48 + tools/ModView/zlib/crc32.cpp | 98 + tools/ModView/zlib/deflate.c | 1350 ++ tools/ModView/zlib/deflate.h | 318 + tools/ModView/zlib/infblock.c | 398 + tools/ModView/zlib/infblock.h | 39 + tools/ModView/zlib/infcodes.c | 257 + tools/ModView/zlib/infcodes.h | 27 + tools/ModView/zlib/inffast.c | 170 + tools/ModView/zlib/inffast.h | 17 + tools/ModView/zlib/inffixed.h | 151 + tools/ModView/zlib/inflate.c | 366 + tools/ModView/zlib/inftrees.c | 455 + tools/ModView/zlib/inftrees.h | 58 + tools/ModView/zlib/infutil.c | 87 + tools/ModView/zlib/infutil.h | 98 + tools/ModView/zlib/trees.c | 1214 ++ tools/ModView/zlib/trees.h | 128 + tools/ModView/zlib/zconf.h | 279 + tools/ModView/zlib/zlib.h | 893 + tools/ModView/zlib/zutil.c | 225 + tools/ModView/zlib/zutil.h | 220 + tools/_console_arena_list_ | 0 tools/_console_dir_list_ | 8 + tools/_console_npc_list_ | 0 tools/_console_sab_list_ | 0 tools/_console_scl_list_ | 0 tools/_console_shader_list_ | 0 tools/_console_skin_list_ | 0 tools/_console_str_list_ | 0 tools/_console_team_list_ | 0 tools/_console_veh_list_ | 0 tools/_console_vwp_list_ | 0 tools/bink_planets.bat | 14 + tools/create_soundbank/_console_skin_list_ | 0 tools/create_soundbank/_console_str_list_ | 0 tools/create_soundbank/create_soundbank.sln | 21 + .../create_soundbank/create_soundbank.vcproj | 196 + tools/create_soundbank/main.cpp | 171 + tools/do_dir_lists.bat | 50 + tools/jawa/_console_skin_list_ | 0 tools/jawa/_console_str_list_ | 0 tools/jawa/jawa.cpp | 202 + tools/jawa/jawa.sln | 30 + tools/jawa/jawa.sln.old | 30 + tools/jawa/jawa.suo | Bin 0 -> 6656 bytes tools/jawa/jawa.vcproj | 290 + tools/jawa/match.cpp | 1469 ++ tools/jawa/match.h | 439 + tools/jawa/stdafx.cpp | 8 + tools/jawa/stdafx.h | 12 + tools/lipsyncthing/_console_skin_list_ | 0 tools/lipsyncthing/_console_str_list_ | 0 tools/lipsyncthing/lipsyncthing.sln | 27 + tools/lipsyncthing/lipsyncthing.vcproj | 120 + tools/lipsyncthing/main.cpp | 420 + tools/lipthing2/_console_skin_list_ | 0 tools/lipthing2/_console_str_list_ | 0 tools/lipthing2/lipthing2.cpp | 254 + tools/lipthing2/lipthing2.vcproj | 138 + tools/lipthing2/stdafx.cpp | 8 + tools/lipthing2/stdafx.h | 16 + tools/maptool/_console_skin_list_ | 0 tools/maptool/_console_str_list_ | 0 tools/maptool/maptool.cpp | 164 + tools/mp3_wxb.bat | 1 + tools/pngtgaTool/TgaWiz.cpp | 65 + tools/pngtgaTool/TgaWiz.sln | 21 + tools/pngtgaTool/TgaWiz.vcproj | 150 + tools/pngtgaTool/_console_skin_list_ | 0 tools/pngtgaTool/_console_str_list_ | 0 tools/pngtgaTool/deflate.cpp | 2082 +++ tools/pngtgaTool/deflate.h | 231 + tools/pngtgaTool/inflate.cpp | 1843 ++ tools/pngtgaTool/inflate.h | 145 + tools/pngtgaTool/png.cpp | 822 + tools/pngtgaTool/png.h | 73 + tools/pngtgaTool/stdafx.cpp | 8 + tools/pngtgaTool/stdafx.h | 12 + tools/pngtgaTool/tga.cpp | 512 + tools/pngtgaTool/zip.h | 201 + tools/pngtgaTool/zipcommon.cpp | 117 + tools/shader_strip.txt | 6 + tools/wav_wxb.bat | 1 + tools/wav_wxb2.bat | 5 + ui/menudef.h | 400 + 2180 files changed, 1393544 insertions(+), 1 deletion(-) create mode 100644 LICENSE.txt create mode 100644 base/default.cfg create mode 100644 base/ext_data/MP/netf_overrides.txt create mode 100644 base/ext_data/MP/psf_overrides.txt create mode 100644 base/ext_data/MP/vssver.scc create mode 100644 base/ext_data/dms.dat create mode 100644 base/ext_data/items.dat create mode 100644 base/ext_data/npcs/Bartender.npc create mode 100644 base/ext_data/npcs/BespinCop.npc create mode 100644 base/ext_data/npcs/Desann.npc create mode 100644 base/ext_data/npcs/Elder.npc create mode 100644 base/ext_data/npcs/Galak.npc create mode 100644 base/ext_data/npcs/Galak_Mech.npc create mode 100644 base/ext_data/npcs/Glider.npc create mode 100644 base/ext_data/npcs/Gran.npc create mode 100644 base/ext_data/npcs/HazardTrooper.npc create mode 100644 base/ext_data/npcs/Howler.npc create mode 100644 base/ext_data/npcs/ImpCommander.npc create mode 100644 base/ext_data/npcs/ImpOfficer.npc create mode 100644 base/ext_data/npcs/ImpWorker.npc create mode 100644 base/ext_data/npcs/Imperial.npc create mode 100644 base/ext_data/npcs/Jan.npc create mode 100644 base/ext_data/npcs/Jedi.npc create mode 100644 base/ext_data/npcs/JediF.npc create mode 100644 base/ext_data/npcs/JediMaster.npc create mode 100644 base/ext_data/npcs/JediTrainer.npc create mode 100644 base/ext_data/npcs/Kyle.npc create mode 100644 base/ext_data/npcs/Lando.npc create mode 100644 base/ext_data/npcs/Luke.npc create mode 100644 base/ext_data/npcs/Merchant.npc create mode 100644 base/ext_data/npcs/Minemonster.npc create mode 100644 base/ext_data/npcs/MonMothma.npc create mode 100644 base/ext_data/npcs/MorganKatarn.npc create mode 100644 base/ext_data/npcs/Noghri.npc create mode 100644 base/ext_data/npcs/Prisoner.npc create mode 100644 base/ext_data/npcs/Ragnos.npc create mode 100644 base/ext_data/npcs/Rax.npc create mode 100644 base/ext_data/npcs/Rebel.npc create mode 100644 base/ext_data/npcs/Rebel2.npc create mode 100644 base/ext_data/npcs/Reborn.npc create mode 100644 base/ext_data/npcs/RebornAcrobat.npc create mode 100644 base/ext_data/npcs/RebornBoss.npc create mode 100644 base/ext_data/npcs/RebornChiss.npc create mode 100644 base/ext_data/npcs/RebornFencer.npc create mode 100644 base/ext_data/npcs/RebornForceUser.npc create mode 100644 base/ext_data/npcs/RebornRodian.npc create mode 100644 base/ext_data/npcs/RebornTrandoshan.npc create mode 100644 base/ext_data/npcs/RebornWeequay.npc create mode 100644 base/ext_data/npcs/Reborn_dual.npc create mode 100644 base/ext_data/npcs/Reborn_new.npc create mode 100644 base/ext_data/npcs/Reborn_staff.npc create mode 100644 base/ext_data/npcs/Reborn_twin.npc create mode 100644 base/ext_data/npcs/Reelo.npc create mode 100644 base/ext_data/npcs/RocketTrooper.npc create mode 100644 base/ext_data/npcs/Rodian.npc create mode 100644 base/ext_data/npcs/STCommander.npc create mode 100644 base/ext_data/npcs/STOfficer.npc create mode 100644 base/ext_data/npcs/STOfficerAlt.npc create mode 100644 base/ext_data/npcs/ShadowTrooper.npc create mode 100644 base/ext_data/npcs/StormPilot.npc create mode 100644 base/ext_data/npcs/StormTrooper.npc create mode 100644 base/ext_data/npcs/SwampTrooper.npc create mode 100644 base/ext_data/npcs/Tavion.npc create mode 100644 base/ext_data/npcs/Tavion_new.npc create mode 100644 base/ext_data/npcs/Trandoshan.npc create mode 100644 base/ext_data/npcs/Ugnaught.npc create mode 100644 base/ext_data/npcs/Weequay.npc create mode 100644 base/ext_data/npcs/alora.npc create mode 100644 base/ext_data/npcs/assassin_droid.npc create mode 100644 base/ext_data/npcs/atst.npc create mode 100644 base/ext_data/npcs/atst_vehicle.npc create mode 100644 base/ext_data/npcs/boba_fett.npc create mode 100644 base/ext_data/npcs/chewie.npc create mode 100644 base/ext_data/npcs/cultist.npc create mode 100644 base/ext_data/npcs/cultist_destroyer.npc create mode 100644 base/ext_data/npcs/cultist_drain.npc create mode 100644 base/ext_data/npcs/cultist_grip.npc create mode 100644 base/ext_data/npcs/cultist_lightning.npc create mode 100644 base/ext_data/npcs/cultist_saber.npc create mode 100644 base/ext_data/npcs/cultist_saber_powers.npc create mode 100644 base/ext_data/npcs/cultistcommando.npc create mode 100644 base/ext_data/npcs/gonk.npc create mode 100644 base/ext_data/npcs/human_merc.npc create mode 100644 base/ext_data/npcs/interrogator.npc create mode 100644 base/ext_data/npcs/jawa.npc create mode 100644 base/ext_data/npcs/jedi_random.npc create mode 100644 base/ext_data/npcs/lambdashuttle.npc create mode 100644 base/ext_data/npcs/mark1.npc create mode 100644 base/ext_data/npcs/mark2.npc create mode 100644 base/ext_data/npcs/mouse.npc create mode 100644 base/ext_data/npcs/nullDriver.npc create mode 100644 base/ext_data/npcs/player.npc create mode 100644 base/ext_data/npcs/probe.npc create mode 100644 base/ext_data/npcs/protocol.npc create mode 100644 base/ext_data/npcs/protocol_imp.npc create mode 100644 base/ext_data/npcs/r2d2.npc create mode 100644 base/ext_data/npcs/r2d2_imp.npc create mode 100644 base/ext_data/npcs/r5d2.npc create mode 100644 base/ext_data/npcs/r5d2_imp.npc create mode 100644 base/ext_data/npcs/rancor.npc create mode 100644 base/ext_data/npcs/rancor_vehicle.npc create mode 100644 base/ext_data/npcs/remote.npc create mode 100644 base/ext_data/npcs/rockettrooper2.npc create mode 100644 base/ext_data/npcs/rockettrooper_w.npc create mode 100644 base/ext_data/npcs/rocks.npc create mode 100644 base/ext_data/npcs/rosh_penin.npc create mode 100644 base/ext_data/npcs/saber_droid.npc create mode 100644 base/ext_data/npcs/saboteur.npc create mode 100644 base/ext_data/npcs/saboteurpistol.npc create mode 100644 base/ext_data/npcs/saboteursniper.npc create mode 100644 base/ext_data/npcs/sand_creature.npc create mode 100644 base/ext_data/npcs/seeker.npc create mode 100644 base/ext_data/npcs/sentry.npc create mode 100644 base/ext_data/npcs/snowtrooper.npc create mode 100644 base/ext_data/npcs/swoop.npc create mode 100644 base/ext_data/npcs/tauntaun.npc create mode 100644 base/ext_data/npcs/test.npc create mode 100644 base/ext_data/npcs/tie-bomber.npc create mode 100644 base/ext_data/npcs/tie-fighter.npc create mode 100644 base/ext_data/npcs/tusken.npc create mode 100644 base/ext_data/npcs/tuskensniper.npc create mode 100644 base/ext_data/npcs/vssver.scc create mode 100644 base/ext_data/npcs/wampa.npc create mode 100644 base/ext_data/npcs/wampa_vehicle.npc create mode 100644 base/ext_data/npcs/wildtauntaun.npc create mode 100644 base/ext_data/npcs/x-wing.npc create mode 100644 base/ext_data/npcs/yt-1300.npc create mode 100644 base/ext_data/npcs/z-95.npc create mode 100644 base/ext_data/sabers/NotUsed/exotic.sab create mode 100644 base/ext_data/sabers/NotUsed/extra.sab create mode 100644 base/ext_data/sabers/NotUsed/vssver.scc create mode 100644 base/ext_data/sabers/dual_1.sab create mode 100644 base/ext_data/sabers/dual_2.sab create mode 100644 base/ext_data/sabers/dual_3.sab create mode 100644 base/ext_data/sabers/dual_4.sab create mode 100644 base/ext_data/sabers/dual_5.sab create mode 100644 base/ext_data/sabers/empty.sab create mode 100644 base/ext_data/sabers/sabers.sab create mode 100644 base/ext_data/sabers/single_1.sab create mode 100644 base/ext_data/sabers/single_2.sab create mode 100644 base/ext_data/sabers/single_3.sab create mode 100644 base/ext_data/sabers/single_4.sab create mode 100644 base/ext_data/sabers/single_5.sab create mode 100644 base/ext_data/sabers/single_6.sab create mode 100644 base/ext_data/sabers/single_7.sab create mode 100644 base/ext_data/sabers/single_8.sab create mode 100644 base/ext_data/sabers/single_9.sab create mode 100644 base/ext_data/sabers/sith_sword.sab create mode 100644 base/ext_data/sabers/vssver.scc create mode 100644 base/ext_data/siege/classes/bountyhunter.scl create mode 100644 base/ext_data/siege/classes/cultist.scl create mode 100644 base/ext_data/siege/classes/darkforceass.scl create mode 100644 base/ext_data/siege/classes/darkjediapprentice.scl create mode 100644 base/ext_data/siege/classes/darkjedidemo.scl create mode 100644 base/ext_data/siege/classes/darkjedidestroyer.scl create mode 100644 base/ext_data/siege/classes/darkjediduelist.scl create mode 100644 base/ext_data/siege/classes/darkjediforceuser.scl create mode 100644 base/ext_data/siege/classes/darkjediinterceptor.scl create mode 100644 base/ext_data/siege/classes/darkjediinvader.scl create mode 100644 base/ext_data/siege/classes/darkjediknight.scl create mode 100644 base/ext_data/siege/classes/darkjeditech.scl create mode 100644 base/ext_data/siege/classes/darksidemarauder.scl create mode 100644 base/ext_data/siege/classes/darksidemauler.scl create mode 100644 base/ext_data/siege/classes/imperialdemo.scl create mode 100644 base/ext_data/siege/classes/imperialltdemo.scl create mode 100644 base/ext_data/siege/classes/imperialltsniper.scl create mode 100644 base/ext_data/siege/classes/imperialsniper.scl create mode 100644 base/ext_data/siege/classes/implttech.scl create mode 100644 base/ext_data/siege/classes/imppilot.scl create mode 100644 base/ext_data/siege/classes/impsupply.scl create mode 100644 base/ext_data/siege/classes/imptech.scl create mode 100644 base/ext_data/siege/classes/jediapprentice.scl create mode 100644 base/ext_data/siege/classes/jedidemo.scl create mode 100644 base/ext_data/siege/classes/jediduelist.scl create mode 100644 base/ext_data/siege/classes/jediforceuser.scl create mode 100644 base/ext_data/siege/classes/jediguardian.scl create mode 100644 base/ext_data/siege/classes/jedihealer.scl create mode 100644 base/ext_data/siege/classes/jedihunter.scl create mode 100644 base/ext_data/siege/classes/jediknight.scl create mode 100644 base/ext_data/siege/classes/jedilightsabermaster.scl create mode 100644 base/ext_data/siege/classes/jediscout.scl create mode 100644 base/ext_data/siege/classes/jeditech.scl create mode 100644 base/ext_data/siege/classes/jediwarrior.scl create mode 100644 base/ext_data/siege/classes/kyle.scl create mode 100644 base/ext_data/siege/classes/mercassault.scl create mode 100644 base/ext_data/siege/classes/mercdemo.scl create mode 100644 base/ext_data/siege/classes/mercenary.scl create mode 100644 base/ext_data/siege/classes/mercheavy.scl create mode 100644 base/ext_data/siege/classes/mercsniper.scl create mode 100644 base/ext_data/siege/classes/protector.scl create mode 100644 base/ext_data/siege/classes/rebelassault.scl create mode 100644 base/ext_data/siege/classes/rebeldemo.scl create mode 100644 base/ext_data/siege/classes/rebelhvyinfantry.scl create mode 100644 base/ext_data/siege/classes/rebelinfantry.scl create mode 100644 base/ext_data/siege/classes/rebelltdemo.scl create mode 100644 base/ext_data/siege/classes/rebelltsniper.scl create mode 100644 base/ext_data/siege/classes/rebellttech.scl create mode 100644 base/ext_data/siege/classes/rebelpilot.scl create mode 100644 base/ext_data/siege/classes/rebelsniper.scl create mode 100644 base/ext_data/siege/classes/rebelsupply.scl create mode 100644 base/ext_data/siege/classes/rebeltech.scl create mode 100644 base/ext_data/siege/classes/rockettrooper.scl create mode 100644 base/ext_data/siege/classes/smuggler.scl create mode 100644 base/ext_data/siege/classes/snowtrooper.scl create mode 100644 base/ext_data/siege/classes/stormtrooper.scl create mode 100644 base/ext_data/siege/classes/vssver.scc create mode 100644 base/ext_data/siege/classes/wookie.scl create mode 100644 base/ext_data/siege/teams/Allies.team create mode 100644 base/ext_data/siege/teams/DarkJedi.team create mode 100644 base/ext_data/siege/teams/Imperials.team create mode 100644 base/ext_data/siege/teams/Jedi.team create mode 100644 base/ext_data/siege/teams/Mercs.team create mode 100644 base/ext_data/siege/teams/Siege1_Imperial.team create mode 100644 base/ext_data/siege/teams/Siege1_Rebel.team create mode 100644 base/ext_data/siege/teams/Siege2_Merc.team create mode 100644 base/ext_data/siege/teams/Siege2_Rebel.team create mode 100644 base/ext_data/siege/teams/Siege3_DarkJedi.team create mode 100644 base/ext_data/siege/teams/Siege3_Jedi.team create mode 100644 base/ext_data/siege/teams/Siege4_Imperial.team create mode 100644 base/ext_data/siege/teams/Siege4_Rebel.team create mode 100644 base/ext_data/siege/teams/vssver.scc create mode 100644 base/ext_data/vehicles/YT-1300.veh create mode 100644 base/ext_data/vehicles/atst_vehicle.veh create mode 100644 base/ext_data/vehicles/lambdashuttle.veh create mode 100644 base/ext_data/vehicles/rancor_vehicle.veh create mode 100644 base/ext_data/vehicles/swoop.veh create mode 100644 base/ext_data/vehicles/swoop_cin.veh create mode 100644 base/ext_data/vehicles/swoop_mp.veh create mode 100644 base/ext_data/vehicles/swoop_mp2.veh create mode 100644 base/ext_data/vehicles/tauntaun.veh create mode 100644 base/ext_data/vehicles/template.veh create mode 100644 base/ext_data/vehicles/tie-bomber.veh create mode 100644 base/ext_data/vehicles/tie-bomber2.veh create mode 100644 base/ext_data/vehicles/tie-fighter.veh create mode 100644 base/ext_data/vehicles/vssver.scc create mode 100644 base/ext_data/vehicles/wampa_vehicle.veh create mode 100644 base/ext_data/vehicles/weapons/atst_laser.vwp create mode 100644 base/ext_data/vehicles/weapons/atst_rocket.vwp create mode 100644 base/ext_data/vehicles/weapons/bomb.vwp create mode 100644 base/ext_data/vehicles/weapons/conc_missile_straight.vwp create mode 100644 base/ext_data/vehicles/weapons/concussion_missile.vwp create mode 100644 base/ext_data/vehicles/weapons/imp_laser.vwp create mode 100644 base/ext_data/vehicles/weapons/ion_blaster.vwp create mode 100644 base/ext_data/vehicles/weapons/mine.vwp create mode 100644 base/ext_data/vehicles/weapons/proton_torpedo.vwp create mode 100644 base/ext_data/vehicles/weapons/rebel_laser.vwp create mode 100644 base/ext_data/vehicles/weapons/swoop_laser.vwp create mode 100644 base/ext_data/vehicles/weapons/swoop_rocket.vwp create mode 100644 base/ext_data/vehicles/weapons/template.vwp create mode 100644 base/ext_data/vehicles/weapons/vssver.scc create mode 100644 base/ext_data/vehicles/weapons/yt_turbolaser.vwp create mode 100644 base/ext_data/vehicles/wildtauntaun.veh create mode 100644 base/ext_data/vehicles/x-wing.veh create mode 100644 base/ext_data/vehicles/z-95.veh create mode 100644 base/ext_data/vssver.scc create mode 100644 base/ext_data/weapons.dat create mode 100644 base/high.cfg create mode 100644 base/low.cfg create mode 100644 base/med.cfg create mode 100644 base/mpdefault.cfg create mode 100644 base/noMotion.cfg create mode 100644 base/productid.txt create mode 100644 base/restoreMotion.cfg create mode 100644 base/ui/character.menu create mode 100644 base/ui/controls.menu create mode 100644 base/ui/credits.menu create mode 100644 base/ui/datapadforcepowers.menu create mode 100644 base/ui/datapadinventory.menu create mode 100644 base/ui/datapadmission.menu create mode 100644 base/ui/datapadmoves.menu create mode 100644 base/ui/datapadweapons.menu create mode 100644 base/ui/demo_ForceSelect.menu create mode 100644 base/ui/demo_GotoTier.menu create mode 100644 base/ui/demo_MissionSelect.menu create mode 100644 base/ui/demo_WpnSelect.menu create mode 100644 base/ui/demo_ingame.txt create mode 100644 base/ui/demo_ingameMissionSelect.menu create mode 100644 base/ui/demo_menus.txt create mode 100644 base/ui/demo_saber.menu create mode 100644 base/ui/demo_sellscreen1.menu create mode 100644 base/ui/error.menu create mode 100644 base/ui/hud.menu create mode 100644 base/ui/ingame.menu create mode 100644 base/ui/ingame.txt create mode 100644 base/ui/ingameForceHelp.menu create mode 100644 base/ui/ingameForceSelect.menu create mode 100644 base/ui/ingameForceStatus.menu create mode 100644 base/ui/ingameGotoTier.menu create mode 100644 base/ui/ingameMissionSelect.menu create mode 100644 base/ui/ingameMissionSelect1.menu create mode 100644 base/ui/ingameMissionSelect2.menu create mode 100644 base/ui/ingameMissionSelect3.menu create mode 100644 base/ui/ingameWpnSelect.menu create mode 100644 base/ui/ingameWpnSelectHelp.menu create mode 100644 base/ui/ingamecontrols.menu create mode 100644 base/ui/ingameload.menu create mode 100644 base/ui/ingamequit.menu create mode 100644 base/ui/ingamesave.menu create mode 100644 base/ui/ingamesetup.menu create mode 100644 base/ui/ingamevid_warning.menu create mode 100644 base/ui/jahud.txt create mode 100644 base/ui/jamp/advancedcreateserver.menu create mode 100644 base/ui/jamp/connect.menu create mode 100644 base/ui/jamp/controls.menu create mode 100644 base/ui/jamp/createfavorite.menu create mode 100644 base/ui/jamp/createserver.menu create mode 100644 base/ui/jamp/credits.menu create mode 100644 base/ui/jamp/demo.menu create mode 100644 base/ui/jamp/error.menu create mode 100644 base/ui/jamp/findplayer.menu create mode 100644 base/ui/jamp/gameinfo.txt create mode 100644 base/ui/jamp/ingame.menu create mode 100644 base/ui/jamp/ingame_about.menu create mode 100644 base/ui/jamp/ingame_addbot.menu create mode 100644 base/ui/jamp/ingame_callvote.menu create mode 100644 base/ui/jamp/ingame_controls.menu create mode 100644 base/ui/jamp/ingame_join.menu create mode 100644 base/ui/jamp/ingame_leave.menu create mode 100644 base/ui/jamp/ingame_objectives.menu create mode 100644 base/ui/jamp/ingame_orders.menu create mode 100644 base/ui/jamp/ingame_player.menu create mode 100644 base/ui/jamp/ingame_player2.menu create mode 100644 base/ui/jamp/ingame_playerforce.menu create mode 100644 base/ui/jamp/ingame_saber.menu create mode 100644 base/ui/jamp/ingame_setup.menu create mode 100644 base/ui/jamp/ingame_siegeobjectives.menu create mode 100644 base/ui/jamp/ingame_voicechat.menu create mode 100644 base/ui/jamp/ingame_vote.menu create mode 100644 base/ui/jamp/joinserver.menu create mode 100644 base/ui/jamp/main.menu create mode 100644 base/ui/jamp/menudef.h create mode 100644 base/ui/jamp/multiplayer.menu create mode 100644 base/ui/jamp/password.menu create mode 100644 base/ui/jamp/password_request.menu create mode 100644 base/ui/jamp/player.menu create mode 100644 base/ui/jamp/player2.menu create mode 100644 base/ui/jamp/quickbots.menu create mode 100644 base/ui/jamp/quickgame.menu create mode 100644 base/ui/jamp/quickgame2.menu create mode 100644 base/ui/jamp/quit.menu create mode 100644 base/ui/jamp/rules.menu create mode 100644 base/ui/jamp/rules_force.menu create mode 100644 base/ui/jamp/rules_games.menu create mode 100644 base/ui/jamp/rules_items.menu create mode 100644 base/ui/jamp/rules_moves.menu create mode 100644 base/ui/jamp/rules_weapons.menu create mode 100644 base/ui/jamp/saber.menu create mode 100644 base/ui/jamp/serverinfo.menu create mode 100644 base/ui/jamp/setup.menu create mode 100644 base/ui/jamp/siege_class.menu create mode 100644 base/ui/jamp/siege_msg.menu create mode 100644 base/ui/jamp/siege_team.menu create mode 100644 base/ui/jamp/vid_warning.menu create mode 100644 base/ui/jamp/videodriver.menu create mode 100644 base/ui/jamp/vssver.scc create mode 100644 base/ui/jampingame.txt create mode 100644 base/ui/jampmenus.txt create mode 100644 base/ui/loadgame.menu create mode 100644 base/ui/loadscreen.menu create mode 100644 base/ui/main.menu create mode 100644 base/ui/menus.txt create mode 100644 base/ui/missionfailed.menu create mode 100644 base/ui/newgame.menu create mode 100644 base/ui/newgame_first.menu create mode 100644 base/ui/quit.menu create mode 100644 base/ui/saber.menu create mode 100644 base/ui/saberstyle.menu create mode 100644 base/ui/setup.menu create mode 100644 base/ui/tier1.txt create mode 100644 base/ui/tier2.txt create mode 100644 base/ui/tier3.txt create mode 100644 base/ui/vid_warning.menu create mode 100644 base/ui/videodriver.menu create mode 100644 base/ui/vssver.scc create mode 100644 base/vssver.scc create mode 100644 bin/BehavEd.bhc create mode 100644 bin/StarWars.qe4 create mode 100644 bin/StarWarsMP.qe4 create mode 100644 bin/stringed.cfg create mode 100644 bin/vssver.scc create mode 100644 code/0_compiled_first/0_SH_Leak.cpp create mode 100644 code/ALut.lib create mode 100644 code/EaxMan.dll create mode 100644 code/IFC22.dll create mode 100644 code/JediAcademy.ncb create mode 100644 code/JediAcademy.sln create mode 100644 code/JediAcademy.sln.old create mode 100644 code/JediAcademy.suo create mode 100644 code/OpenAL32.dll create mode 100644 code/OpenAL32.lib create mode 100644 code/RMG/RM_Area.cpp create mode 100644 code/RMG/RM_Area.h create mode 100644 code/RMG/RM_Headers.h create mode 100644 code/RMG/RM_Instance.cpp create mode 100644 code/RMG/RM_Instance.h create mode 100644 code/RMG/RM_InstanceFile.cpp create mode 100644 code/RMG/RM_InstanceFile.h create mode 100644 code/RMG/RM_Instance_BSP.cpp create mode 100644 code/RMG/RM_Instance_BSP.h create mode 100644 code/RMG/RM_Instance_Group.cpp create mode 100644 code/RMG/RM_Instance_Group.h create mode 100644 code/RMG/RM_Instance_Random.cpp create mode 100644 code/RMG/RM_Instance_Random.h create mode 100644 code/RMG/RM_Instance_Void.cpp create mode 100644 code/RMG/RM_Instance_Void.h create mode 100644 code/RMG/RM_Manager.cpp create mode 100644 code/RMG/RM_Manager.h create mode 100644 code/RMG/RM_Mission.cpp create mode 100644 code/RMG/RM_Mission.h create mode 100644 code/RMG/RM_Objective.cpp create mode 100644 code/RMG/RM_Objective.h create mode 100644 code/RMG/RM_Path.cpp create mode 100644 code/RMG/RM_Path.h create mode 100644 code/RMG/RM_Terrain.cpp create mode 100644 code/RMG/RM_Terrain.h create mode 100644 code/SHDebug/HA312W32.DLL create mode 100644 code/SHDebug/SHW32.DLL create mode 100644 code/StarWars.opt create mode 100644 code/bspthing/bsp.h create mode 100644 code/bspthing/bspthing.sln create mode 100644 code/bspthing/bspthing.vcproj create mode 100644 code/bspthing/main.cpp create mode 100644 code/bspthing/pbsp.h create mode 100644 code/cgame/FX_ATSTMain.cpp create mode 100644 code/cgame/FX_Blaster.cpp create mode 100644 code/cgame/FX_Bowcaster.cpp create mode 100644 code/cgame/FX_BryarPistol.cpp create mode 100644 code/cgame/FX_Concussion.cpp create mode 100644 code/cgame/FX_DEMP2.cpp create mode 100644 code/cgame/FX_Disruptor.cpp create mode 100644 code/cgame/FX_Emplaced.cpp create mode 100644 code/cgame/FX_Flechette.cpp create mode 100644 code/cgame/FX_HeavyRepeater.cpp create mode 100644 code/cgame/FX_NoghriShot.cpp create mode 100644 code/cgame/FX_RocketLauncher.cpp create mode 100644 code/cgame/FX_TuskenShot.cpp create mode 100644 code/cgame/FxParsing.cpp create mode 100644 code/cgame/FxParsing.h create mode 100644 code/cgame/FxPrimitives.cpp create mode 100644 code/cgame/FxPrimitives.h create mode 100644 code/cgame/FxScheduler.cpp create mode 100644 code/cgame/FxScheduler.h create mode 100644 code/cgame/FxSystem.cpp create mode 100644 code/cgame/FxSystem.h create mode 100644 code/cgame/FxTemplate.cpp create mode 100644 code/cgame/FxUtil.cpp create mode 100644 code/cgame/FxUtil.h create mode 100644 code/cgame/animtable.h create mode 100644 code/cgame/cg_camera.cpp create mode 100644 code/cgame/cg_camera.h create mode 100644 code/cgame/cg_consolecmds.cpp create mode 100644 code/cgame/cg_credits.cpp create mode 100644 code/cgame/cg_draw.cpp create mode 100644 code/cgame/cg_drawtools.cpp create mode 100644 code/cgame/cg_effects.cpp create mode 100644 code/cgame/cg_ents.cpp create mode 100644 code/cgame/cg_event.cpp create mode 100644 code/cgame/cg_headers.cpp create mode 100644 code/cgame/cg_headers.h create mode 100644 code/cgame/cg_info.cpp create mode 100644 code/cgame/cg_lights.cpp create mode 100644 code/cgame/cg_lights.h create mode 100644 code/cgame/cg_local.h create mode 100644 code/cgame/cg_localents.cpp create mode 100644 code/cgame/cg_main.cpp create mode 100644 code/cgame/cg_marks.cpp create mode 100644 code/cgame/cg_media.h create mode 100644 code/cgame/cg_players.cpp create mode 100644 code/cgame/cg_playerstate.cpp create mode 100644 code/cgame/cg_predict.cpp create mode 100644 code/cgame/cg_public.h create mode 100644 code/cgame/cg_scoreboard.cpp create mode 100644 code/cgame/cg_servercmds.cpp create mode 100644 code/cgame/cg_snapshot.cpp create mode 100644 code/cgame/cg_syscalls.cpp create mode 100644 code/cgame/cg_text.cpp create mode 100644 code/cgame/cg_view.cpp create mode 100644 code/cgame/cg_weapons.cpp create mode 100644 code/cgame/common_headers.h create mode 100644 code/cgame/strip_objectives.h create mode 100644 code/client/BinkVideo.cpp create mode 100644 code/client/BinkVideo.h create mode 100644 code/client/OpenAL/al.h create mode 100644 code/client/OpenAL/alc.h create mode 100644 code/client/OpenAL/alctypes.h create mode 100644 code/client/OpenAL/altypes.h create mode 100644 code/client/OpenAL/alu.h create mode 100644 code/client/OpenAL/alut.h create mode 100644 code/client/cl_bink_copier.cpp create mode 100644 code/client/cl_cgame.cpp create mode 100644 code/client/cl_cin.cpp create mode 100644 code/client/cl_cin_console.cpp create mode 100644 code/client/cl_console.cpp create mode 100644 code/client/cl_input.cpp create mode 100644 code/client/cl_input_hotswap.cpp create mode 100644 code/client/cl_input_hotswap.h create mode 100644 code/client/cl_keys.cpp create mode 100644 code/client/cl_main.cpp create mode 100644 code/client/cl_mp3.cpp create mode 100644 code/client/cl_mp3.h create mode 100644 code/client/cl_mp3.org create mode 100644 code/client/cl_parse.cpp create mode 100644 code/client/cl_scrn.cpp create mode 100644 code/client/cl_ui.cpp create mode 100644 code/client/client.h create mode 100644 code/client/client_ui.h create mode 100644 code/client/eax/EaxMan.h create mode 100644 code/client/eax/eax.h create mode 100644 code/client/fffx.h create mode 100644 code/client/keycodes.h create mode 100644 code/client/keys.h create mode 100644 code/client/snd_ambient.cpp create mode 100644 code/client/snd_ambient.h create mode 100644 code/client/snd_dma.cpp create mode 100644 code/client/snd_dma_console.cpp create mode 100644 code/client/snd_local.h create mode 100644 code/client/snd_local_console.h create mode 100644 code/client/snd_mem.cpp create mode 100644 code/client/snd_mem_console.cpp create mode 100644 code/client/snd_mix.cpp create mode 100644 code/client/snd_music.cpp create mode 100644 code/client/snd_music.h create mode 100644 code/client/snd_public.h create mode 100644 code/client/vmachine.cpp create mode 100644 code/client/vmachine.h create mode 100644 code/ff/IFC/FeelitAPI.h create mode 100644 code/ff/IFC/IFC.h create mode 100644 code/ff/IFC/IFC22.dll create mode 100644 code/ff/IFC/IFC22.lib create mode 100644 code/ff/IFC/IFCErrors.h create mode 100644 code/ff/IFC/ImmBaseTypes.h create mode 100644 code/ff/IFC/ImmBox.h create mode 100644 code/ff/IFC/ImmCompoundEffect.h create mode 100644 code/ff/IFC/ImmCondition.h create mode 100644 code/ff/IFC/ImmConstant.h create mode 100644 code/ff/IFC/ImmDXDevice.h create mode 100644 code/ff/IFC/ImmDamper.h create mode 100644 code/ff/IFC/ImmDevice.h create mode 100644 code/ff/IFC/ImmDevices.h create mode 100644 code/ff/IFC/ImmEffect.h create mode 100644 code/ff/IFC/ImmEffectSuite.h create mode 100644 code/ff/IFC/ImmEllipse.h create mode 100644 code/ff/IFC/ImmEnclosure.h create mode 100644 code/ff/IFC/ImmFriction.h create mode 100644 code/ff/IFC/ImmGrid.h create mode 100644 code/ff/IFC/ImmIFR.h create mode 100644 code/ff/IFC/ImmInertia.h create mode 100644 code/ff/IFC/ImmMouse.h create mode 100644 code/ff/IFC/ImmPeriodic.h create mode 100644 code/ff/IFC/ImmProjects.h create mode 100644 code/ff/IFC/ImmRamp.h create mode 100644 code/ff/IFC/ImmSpring.h create mode 100644 code/ff/IFC/ImmTexture.h create mode 100644 code/ff/cl_ff.cpp create mode 100644 code/ff/cl_ff.h create mode 100644 code/ff/common_headers.h create mode 100644 code/ff/ff.cpp create mode 100644 code/ff/ff.h create mode 100644 code/ff/ff_ChannelCompound.h create mode 100644 code/ff/ff_ChannelSet.cpp create mode 100644 code/ff/ff_ChannelSet.h create mode 100644 code/ff/ff_ConfigParser.cpp create mode 100644 code/ff/ff_ConfigParser.h create mode 100644 code/ff/ff_HandleTable.cpp create mode 100644 code/ff/ff_HandleTable.h create mode 100644 code/ff/ff_MultiCompound.cpp create mode 100644 code/ff/ff_MultiCompound.h create mode 100644 code/ff/ff_MultiEffect.cpp create mode 100644 code/ff/ff_MultiEffect.h create mode 100644 code/ff/ff_MultiSet.cpp create mode 100644 code/ff/ff_MultiSet.h create mode 100644 code/ff/ff_console.cpp create mode 100644 code/ff/ff_ffset.cpp create mode 100644 code/ff/ff_ffset.h create mode 100644 code/ff/ff_local.h create mode 100644 code/ff/ff_public.h create mode 100644 code/ff/ff_snd.cpp create mode 100644 code/ff/ff_snd.h create mode 100644 code/ff/ff_system.cpp create mode 100644 code/ff/ff_system.h create mode 100644 code/ff/ff_utils.cpp create mode 100644 code/ff/ff_utils.h create mode 100644 code/game/AI_Animal.cpp create mode 100644 code/game/AI_AssassinDroid.cpp create mode 100644 code/game/AI_Atst.cpp create mode 100644 code/game/AI_BobaFett.cpp create mode 100644 code/game/AI_Civilian.cpp create mode 100644 code/game/AI_Default.cpp create mode 100644 code/game/AI_Droid.cpp create mode 100644 code/game/AI_GalakMech.cpp create mode 100644 code/game/AI_Glider.cpp create mode 100644 code/game/AI_Grenadier.cpp create mode 100644 code/game/AI_HazardTrooper.cpp create mode 100644 code/game/AI_Howler.cpp create mode 100644 code/game/AI_ImperialProbe.cpp create mode 100644 code/game/AI_Interrogator.cpp create mode 100644 code/game/AI_Jedi.cpp create mode 100644 code/game/AI_Mark1.cpp create mode 100644 code/game/AI_Mark2.cpp create mode 100644 code/game/AI_MineMonster.cpp create mode 100644 code/game/AI_Rancor.cpp create mode 100644 code/game/AI_Remote.cpp create mode 100644 code/game/AI_RocketTrooper.cpp create mode 100644 code/game/AI_SaberDroid.cpp create mode 100644 code/game/AI_SandCreature.cpp create mode 100644 code/game/AI_Seeker.cpp create mode 100644 code/game/AI_Sentry.cpp create mode 100644 code/game/AI_Sniper.cpp create mode 100644 code/game/AI_Stormtrooper.cpp create mode 100644 code/game/AI_Tusken.cpp create mode 100644 code/game/AI_Utils.cpp create mode 100644 code/game/AI_Wampa.cpp create mode 100644 code/game/AnimalNPC.c create mode 100644 code/game/AnimalNPC.cpp create mode 100644 code/game/Copy of game.vcproj create mode 100644 code/game/FighterNPC.c create mode 100644 code/game/G_Timer.cpp create mode 100644 code/game/NPC.cpp create mode 100644 code/game/NPC_behavior.cpp create mode 100644 code/game/NPC_combat.cpp create mode 100644 code/game/NPC_goal.cpp create mode 100644 code/game/NPC_misc.cpp create mode 100644 code/game/NPC_move.cpp create mode 100644 code/game/NPC_reactions.cpp create mode 100644 code/game/NPC_senses.cpp create mode 100644 code/game/NPC_sounds.cpp create mode 100644 code/game/NPC_spawn.cpp create mode 100644 code/game/NPC_stats.cpp create mode 100644 code/game/NPC_utils.cpp create mode 100644 code/game/Q3_Interface.cpp create mode 100644 code/game/Q3_Interface.h create mode 100644 code/game/SpeederNPC.c create mode 100644 code/game/WalkerNPC.c create mode 100644 code/game/ai.h create mode 100644 code/game/anims.h create mode 100644 code/game/b_local.h create mode 100644 code/game/b_public.h create mode 100644 code/game/bg_lib.cpp create mode 100644 code/game/bg_local.h create mode 100644 code/game/bg_misc.cpp create mode 100644 code/game/bg_pangles.cpp create mode 100644 code/game/bg_panimate.cpp create mode 100644 code/game/bg_pmove.cpp create mode 100644 code/game/bg_public.h create mode 100644 code/game/bg_slidemove.cpp create mode 100644 code/game/bg_vehicleload.c create mode 100644 code/game/bset.h create mode 100644 code/game/bstate.h create mode 100644 code/game/channels.h create mode 100644 code/game/characters.h create mode 100644 code/game/common_headers.h create mode 100644 code/game/dmstates.h create mode 100644 code/game/events.h create mode 100644 code/game/fields.h create mode 100644 code/game/g_active.cpp create mode 100644 code/game/g_breakable.cpp create mode 100644 code/game/g_camera.cpp create mode 100644 code/game/g_client.cpp create mode 100644 code/game/g_cmds.cpp create mode 100644 code/game/g_combat.cpp create mode 100644 code/game/g_emplaced.cpp create mode 100644 code/game/g_functions.cpp create mode 100644 code/game/g_functions.h create mode 100644 code/game/g_fx.cpp create mode 100644 code/game/g_headers.cpp create mode 100644 code/game/g_headers.h create mode 100644 code/game/g_inventory.cpp create mode 100644 code/game/g_itemLoad.cpp create mode 100644 code/game/g_items.cpp create mode 100644 code/game/g_items.h create mode 100644 code/game/g_local.h create mode 100644 code/game/g_main.cpp create mode 100644 code/game/g_mem.cpp create mode 100644 code/game/g_misc.cpp create mode 100644 code/game/g_misc_model.cpp create mode 100644 code/game/g_missile.cpp create mode 100644 code/game/g_mover.cpp create mode 100644 code/game/g_nav.cpp create mode 100644 code/game/g_nav.h create mode 100644 code/game/g_navigator.cpp create mode 100644 code/game/g_navigator.h create mode 100644 code/game/g_navnew.cpp create mode 100644 code/game/g_object.cpp create mode 100644 code/game/g_objectives.cpp create mode 100644 code/game/g_public.h create mode 100644 code/game/g_rail.cpp create mode 100644 code/game/g_ref.cpp create mode 100644 code/game/g_roff.cpp create mode 100644 code/game/g_roff.h create mode 100644 code/game/g_savegame.cpp create mode 100644 code/game/g_session.cpp create mode 100644 code/game/g_shared.h create mode 100644 code/game/g_spawn.cpp create mode 100644 code/game/g_svcmds.cpp create mode 100644 code/game/g_target.cpp create mode 100644 code/game/g_trigger.cpp create mode 100644 code/game/g_turret.cpp create mode 100644 code/game/g_usable.cpp create mode 100644 code/game/g_utils.cpp create mode 100644 code/game/g_vehicleLoad.cpp create mode 100644 code/game/g_vehicles.c create mode 100644 code/game/g_vehicles.h create mode 100644 code/game/g_weapon.cpp create mode 100644 code/game/g_weaponLoad.cpp create mode 100644 code/game/game.def create mode 100644 code/game/game.vcproj create mode 100644 code/game/game.vcproj.vspscc create mode 100644 code/game/genericparser2.cpp create mode 100644 code/game/genericparser2.h create mode 100644 code/game/ghoul2_shared.h create mode 100644 code/game/hitlocs.h create mode 100644 code/game/npc_headers.h create mode 100644 code/game/objectives.h create mode 100644 code/game/q_math.cpp create mode 100644 code/game/q_shared.cpp create mode 100644 code/game/q_shared.h create mode 100644 code/game/say.h create mode 100644 code/game/statindex.h create mode 100644 code/game/surfaceflags.h create mode 100644 code/game/teams.h create mode 100644 code/game/weapons.h create mode 100644 code/game/wp_saber.cpp create mode 100644 code/game/wp_saber.h create mode 100644 code/game/wp_saberLoad.cpp create mode 100644 code/ghoul2/G2.h create mode 100644 code/ghoul2/G2_API.cpp create mode 100644 code/ghoul2/G2_bolts.cpp create mode 100644 code/ghoul2/G2_bones.cpp create mode 100644 code/ghoul2/G2_misc.cpp create mode 100644 code/ghoul2/G2_surfaces.cpp create mode 100644 code/ghoul2/ghoul2_gore.h create mode 100644 code/goblib/Copy of goblib.vcproj create mode 100644 code/goblib/debug/vc70.idb create mode 100644 code/goblib/debug/vc70.pdb create mode 100644 code/goblib/goblib.cpp create mode 100644 code/goblib/goblib.h create mode 100644 code/goblib/goblib.vcproj create mode 100644 code/goblib/goblib.vcproj.old create mode 100644 code/icarus/BlockStream.cpp create mode 100644 code/icarus/IcarusImplementation.cpp create mode 100644 code/icarus/IcarusImplementation.h create mode 100644 code/icarus/IcarusInterface.h create mode 100644 code/icarus/Sequence.cpp create mode 100644 code/icarus/Sequencer.cpp create mode 100644 code/icarus/StdAfx.h create mode 100644 code/icarus/TaskManager.cpp create mode 100644 code/icarus/blockstream.h create mode 100644 code/icarus/sequence.h create mode 100644 code/icarus/sequencer.h create mode 100644 code/icarus/taskmanager.h create mode 100644 code/jpeg-6/jcapimin.cpp create mode 100644 code/jpeg-6/jccoefct.cpp create mode 100644 code/jpeg-6/jccolor.cpp create mode 100644 code/jpeg-6/jcdctmgr.cpp create mode 100644 code/jpeg-6/jchuff.cpp create mode 100644 code/jpeg-6/jchuff.h create mode 100644 code/jpeg-6/jcinit.cpp create mode 100644 code/jpeg-6/jcmainct.cpp create mode 100644 code/jpeg-6/jcmarker.cpp create mode 100644 code/jpeg-6/jcmaster.cpp create mode 100644 code/jpeg-6/jcomapi.cpp create mode 100644 code/jpeg-6/jconfig.h create mode 100644 code/jpeg-6/jcparam.cpp create mode 100644 code/jpeg-6/jcphuff.cpp create mode 100644 code/jpeg-6/jcprepct.cpp create mode 100644 code/jpeg-6/jcsample.cpp create mode 100644 code/jpeg-6/jctrans.cpp create mode 100644 code/jpeg-6/jdapimin.cpp create mode 100644 code/jpeg-6/jdapistd.cpp create mode 100644 code/jpeg-6/jdatadst.cpp create mode 100644 code/jpeg-6/jdatasrc.cpp create mode 100644 code/jpeg-6/jdcoefct.cpp create mode 100644 code/jpeg-6/jdcolor.cpp create mode 100644 code/jpeg-6/jdct.h create mode 100644 code/jpeg-6/jddctmgr.cpp create mode 100644 code/jpeg-6/jdhuff.cpp create mode 100644 code/jpeg-6/jdhuff.h create mode 100644 code/jpeg-6/jdinput.cpp create mode 100644 code/jpeg-6/jdmainct.cpp create mode 100644 code/jpeg-6/jdmarker.cpp create mode 100644 code/jpeg-6/jdmaster.cpp create mode 100644 code/jpeg-6/jdpostct.cpp create mode 100644 code/jpeg-6/jdsample.cpp create mode 100644 code/jpeg-6/jdtrans.cpp create mode 100644 code/jpeg-6/jerror.cpp create mode 100644 code/jpeg-6/jerror.h create mode 100644 code/jpeg-6/jfdctflt.cpp create mode 100644 code/jpeg-6/jidctflt.cpp create mode 100644 code/jpeg-6/jinclude.h create mode 100644 code/jpeg-6/jmemmgr.cpp create mode 100644 code/jpeg-6/jmemnobs.cpp create mode 100644 code/jpeg-6/jmemsys.h create mode 100644 code/jpeg-6/jmorecfg.h create mode 100644 code/jpeg-6/jpegint.h create mode 100644 code/jpeg-6/jpeglib.h create mode 100644 code/jpeg-6/jutils.cpp create mode 100644 code/jpeg-6/jversion.h create mode 100644 code/mac/MacGamma.c create mode 100644 code/mac/MacGamma.cpp create mode 100644 code/mac/MacGamma.h create mode 100644 code/mac/MacQuake3 create mode 100644 code/mac/mac_console.c create mode 100644 code/mac/mac_event.c create mode 100644 code/mac/mac_glimp.c create mode 100644 code/mac/mac_input.c create mode 100644 code/mac/mac_local.h create mode 100644 code/mac/mac_main.c create mode 100644 code/mac/mac_net.c create mode 100644 code/mac/mac_snddma.c create mode 100644 code/mac/macprefix.h create mode 100644 code/mac/q3.rsrc create mode 100644 code/mp3code/cdct.c create mode 100644 code/mp3code/config.h create mode 100644 code/mp3code/copyright.h create mode 100644 code/mp3code/csbt.c create mode 100644 code/mp3code/csbtb.c create mode 100644 code/mp3code/csbtl3.c create mode 100644 code/mp3code/cup.c create mode 100644 code/mp3code/cupini.c create mode 100644 code/mp3code/cupl1.c create mode 100644 code/mp3code/cupl3.c create mode 100644 code/mp3code/cwin.c create mode 100644 code/mp3code/cwinb.c create mode 100644 code/mp3code/cwinm.c create mode 100644 code/mp3code/htable.h create mode 100644 code/mp3code/hwin.c create mode 100644 code/mp3code/jdw.h create mode 100644 code/mp3code/l3.h create mode 100644 code/mp3code/l3dq.c create mode 100644 code/mp3code/l3init.c create mode 100644 code/mp3code/mdct.c create mode 100644 code/mp3code/mhead.c create mode 100644 code/mp3code/mhead.h create mode 100644 code/mp3code/mp3struct.h create mode 100644 code/mp3code/msis.c create mode 100644 code/mp3code/port.h create mode 100644 code/mp3code/small_header.h create mode 100644 code/mp3code/tableawd.h create mode 100644 code/mp3code/towave.c create mode 100644 code/mp3code/uph.c create mode 100644 code/mp3code/upsf.c create mode 100644 code/mp3code/wavep.c create mode 100644 code/null/mac_net.c create mode 100644 code/null/null_glimp.c create mode 100644 code/null/null_main.c create mode 100644 code/null/null_net.c create mode 100644 code/null/null_snddma.c create mode 100644 code/png/png.cpp create mode 100644 code/png/png.h create mode 100644 code/qcommon/MiniHeap.h create mode 100644 code/qcommon/chash.h create mode 100644 code/qcommon/cm_draw.cpp create mode 100644 code/qcommon/cm_draw.h create mode 100644 code/qcommon/cm_landscape.h create mode 100644 code/qcommon/cm_load.cpp create mode 100644 code/qcommon/cm_load_xbox.cpp create mode 100644 code/qcommon/cm_local.h create mode 100644 code/qcommon/cm_patch.cpp create mode 100644 code/qcommon/cm_patch.h create mode 100644 code/qcommon/cm_polylib.cpp create mode 100644 code/qcommon/cm_polylib.h create mode 100644 code/qcommon/cm_public.h create mode 100644 code/qcommon/cm_randomterrain.cpp create mode 100644 code/qcommon/cm_randomterrain.h create mode 100644 code/qcommon/cm_shader.cpp create mode 100644 code/qcommon/cm_terrain.cpp create mode 100644 code/qcommon/cm_terrainmap.cpp create mode 100644 code/qcommon/cm_terrainmap.h create mode 100644 code/qcommon/cm_test.cpp create mode 100644 code/qcommon/cm_trace.cpp create mode 100644 code/qcommon/cmd.cpp create mode 100644 code/qcommon/common.cpp create mode 100644 code/qcommon/cvar.cpp create mode 100644 code/qcommon/files.h create mode 100644 code/qcommon/files_common.cpp create mode 100644 code/qcommon/files_console.cpp create mode 100644 code/qcommon/files_pc.cpp create mode 100644 code/qcommon/fixedmap.h create mode 100644 code/qcommon/hstring.cpp create mode 100644 code/qcommon/hstring.h create mode 100644 code/qcommon/md4.cpp create mode 100644 code/qcommon/msg.cpp create mode 100644 code/qcommon/net_chan.cpp create mode 100644 code/qcommon/platform.h create mode 100644 code/qcommon/qcommon.h create mode 100644 code/qcommon/qfiles.h create mode 100644 code/qcommon/sparc.h create mode 100644 code/qcommon/sstring.h create mode 100644 code/qcommon/stringed_ingame.cpp create mode 100644 code/qcommon/stringed_ingame.h create mode 100644 code/qcommon/stringed_interface.cpp create mode 100644 code/qcommon/stringed_interface.h create mode 100644 code/qcommon/stv_version.h create mode 100644 code/qcommon/tags.h create mode 100644 code/qcommon/timing.h create mode 100644 code/qcommon/tri_coll_test.cpp create mode 100644 code/qcommon/unzip.cpp create mode 100644 code/qcommon/unzip.h create mode 100644 code/qcommon/xb_settings.cpp create mode 100644 code/qcommon/xb_settings.h create mode 100644 code/qcommon/z_memman_console.cpp create mode 100644 code/qcommon/z_memman_pc.cpp create mode 100644 code/ragl/graph_region.h create mode 100644 code/ragl/graph_triangulate.h create mode 100644 code/ragl/graph_vs.h create mode 100644 code/ragl/kdtree_vs.h create mode 100644 code/ragl/ragl_common.h create mode 100644 code/ratl/array_vs.h create mode 100644 code/ratl/bits_vs.h create mode 100644 code/ratl/grid_vs.h create mode 100644 code/ratl/handle_pool_vs.h create mode 100644 code/ratl/hash_pool_vs.h create mode 100644 code/ratl/heap_vs.h create mode 100644 code/ratl/list_vs.h create mode 100644 code/ratl/map_vs.h create mode 100644 code/ratl/pool_vs.h create mode 100644 code/ratl/queue_vs.h create mode 100644 code/ratl/ratl.cpp create mode 100644 code/ratl/ratl_common.h create mode 100644 code/ratl/scheduler_vs.h create mode 100644 code/ratl/stack_vs.h create mode 100644 code/ratl/string_vs.h create mode 100644 code/ratl/vector_vs.h create mode 100644 code/ravl/CBounds.cpp create mode 100644 code/ravl/CBounds.h create mode 100644 code/ravl/CMatrix.h create mode 100644 code/ravl/CVec.cpp create mode 100644 code/ravl/CVec.h create mode 100644 code/renderer/amd3d.h create mode 100644 code/renderer/glext.h create mode 100644 code/renderer/glext_console.h create mode 100644 code/renderer/matcomp.c create mode 100644 code/renderer/matcomp.h create mode 100644 code/renderer/mdx_format.h create mode 100644 code/renderer/qgl.h create mode 100644 code/renderer/qgl_console.h create mode 100644 code/renderer/qgl_linked.h create mode 100644 code/renderer/ref_trin.def create mode 100644 code/renderer/tr_WorldEffects.cpp create mode 100644 code/renderer/tr_WorldEffects.h create mode 100644 code/renderer/tr_animation.cpp create mode 100644 code/renderer/tr_arioche.cpp create mode 100644 code/renderer/tr_backend.cpp create mode 100644 code/renderer/tr_bsp.cpp create mode 100644 code/renderer/tr_bsp_xbox.cpp create mode 100644 code/renderer/tr_cmds.cpp create mode 100644 code/renderer/tr_curve.cpp create mode 100644 code/renderer/tr_draw.cpp create mode 100644 code/renderer/tr_flares.cpp create mode 100644 code/renderer/tr_font.cpp create mode 100644 code/renderer/tr_font.h create mode 100644 code/renderer/tr_ghoul2.cpp create mode 100644 code/renderer/tr_image.cpp create mode 100644 code/renderer/tr_init.cpp create mode 100644 code/renderer/tr_jpeg_interface.cpp create mode 100644 code/renderer/tr_jpeg_interface.h create mode 100644 code/renderer/tr_landscape.h create mode 100644 code/renderer/tr_light.cpp create mode 100644 code/renderer/tr_lightmanager.cpp create mode 100644 code/renderer/tr_lightmanager.h create mode 100644 code/renderer/tr_local.h create mode 100644 code/renderer/tr_main.cpp create mode 100644 code/renderer/tr_marks.cpp create mode 100644 code/renderer/tr_mesh.cpp create mode 100644 code/renderer/tr_model.cpp create mode 100644 code/renderer/tr_noise.cpp create mode 100644 code/renderer/tr_public.h create mode 100644 code/renderer/tr_quicksprite.cpp create mode 100644 code/renderer/tr_quicksprite.h create mode 100644 code/renderer/tr_scene.cpp create mode 100644 code/renderer/tr_shade.cpp create mode 100644 code/renderer/tr_shade_calc.cpp create mode 100644 code/renderer/tr_shader.cpp create mode 100644 code/renderer/tr_shadows.cpp create mode 100644 code/renderer/tr_sky.cpp create mode 100644 code/renderer/tr_stl.cpp create mode 100644 code/renderer/tr_stl.h create mode 100644 code/renderer/tr_surface.cpp create mode 100644 code/renderer/tr_surfacesprites.cpp create mode 100644 code/renderer/tr_terrain.cpp create mode 100644 code/renderer/tr_types.h create mode 100644 code/renderer/tr_world.cpp create mode 100644 code/rufl/hfile.cpp create mode 100644 code/rufl/hfile.h create mode 100644 code/rufl/hstring.cpp create mode 100644 code/rufl/hstring.h create mode 100644 code/rufl/random.cpp create mode 100644 code/rufl/random.h create mode 100644 code/server/exe_headers.cpp create mode 100644 code/server/exe_headers.h create mode 100644 code/server/server.h create mode 100644 code/server/sv_ccmds.cpp create mode 100644 code/server/sv_client.cpp create mode 100644 code/server/sv_game.cpp create mode 100644 code/server/sv_init.cpp create mode 100644 code/server/sv_main.cpp create mode 100644 code/server/sv_savegame.cpp create mode 100644 code/server/sv_snapshot.cpp create mode 100644 code/server/sv_world.cpp create mode 100644 code/smartheap/HAW32M.LIB create mode 100644 code/smartheap/HEAPAGNT.H create mode 100644 code/smartheap/SMRTHEAP.C create mode 100644 code/smartheap/SMRTHEAP.H create mode 100644 code/smartheap/smrtheap.hpp create mode 100644 code/starwars.plg create mode 100644 code/starwars.vcproj create mode 100644 code/tonet.bat create mode 100644 code/tosend.bat create mode 100644 code/ui/gameinfo.cpp create mode 100644 code/ui/gameinfo.h create mode 100644 code/ui/menudef.h create mode 100644 code/ui/ui.def create mode 100644 code/ui/ui_atoms.cpp create mode 100644 code/ui/ui_connect.cpp create mode 100644 code/ui/ui_debug.cpp create mode 100644 code/ui/ui_local.h create mode 100644 code/ui/ui_main.cpp create mode 100644 code/ui/ui_playerinfo.h create mode 100644 code/ui/ui_public.h create mode 100644 code/ui/ui_saber.cpp create mode 100644 code/ui/ui_shared.cpp create mode 100644 code/ui/ui_shared.h create mode 100644 code/ui/ui_splash.cpp create mode 100644 code/ui/ui_splash.h create mode 100644 code/ui/ui_syscalls.cpp create mode 100644 code/unix/Makefile create mode 100644 code/unix/linux_glimp.c create mode 100644 code/unix/linux_qgl.c create mode 100644 code/unix/linux_snd.c create mode 100644 code/unix/matha.s create mode 100644 code/unix/q3test.spec.sh create mode 100644 code/unix/qasm.h create mode 100644 code/unix/quake3.gif create mode 100644 code/unix/snd_mixa.s create mode 100644 code/unix/sys_dosa.s create mode 100644 code/unix/ui_video.c create mode 100644 code/unix/unix_glw.h create mode 100644 code/unix/unix_main.c create mode 100644 code/unix/unix_net.c create mode 100644 code/unix/unix_shared.c create mode 100644 code/win32/AutoVersion.h create mode 100644 code/win32/FeelIt/FEELitIFR.h create mode 100644 code/win32/FeelIt/FFC.h create mode 100644 code/win32/FeelIt/FFC10.dll create mode 100644 code/win32/FeelIt/FFC10.lib create mode 100644 code/win32/FeelIt/FFC10d.dll create mode 100644 code/win32/FeelIt/FFC10d.lib create mode 100644 code/win32/FeelIt/FFCErrors.h create mode 100644 code/win32/FeelIt/FeelBaseTypes.h create mode 100644 code/win32/FeelIt/FeelBox.h create mode 100644 code/win32/FeelIt/FeelCompoundEffect.h create mode 100644 code/win32/FeelIt/FeelCondition.h create mode 100644 code/win32/FeelIt/FeelConstant.h create mode 100644 code/win32/FeelIt/FeelDXDevice.h create mode 100644 code/win32/FeelIt/FeelDamper.h create mode 100644 code/win32/FeelIt/FeelDevice.h create mode 100644 code/win32/FeelIt/FeelEffect.h create mode 100644 code/win32/FeelIt/FeelEllipse.h create mode 100644 code/win32/FeelIt/FeelEnclosure.h create mode 100644 code/win32/FeelIt/FeelFriction.h create mode 100644 code/win32/FeelIt/FeelGrid.h create mode 100644 code/win32/FeelIt/FeelInertia.h create mode 100644 code/win32/FeelIt/FeelMouse.h create mode 100644 code/win32/FeelIt/FeelPeriodic.h create mode 100644 code/win32/FeelIt/FeelProjects.h create mode 100644 code/win32/FeelIt/FeelRamp.h create mode 100644 code/win32/FeelIt/FeelSpring.h create mode 100644 code/win32/FeelIt/FeelTexture.h create mode 100644 code/win32/FeelIt/FeelitAPI.h create mode 100644 code/win32/FeelIt/fffx.cpp create mode 100644 code/win32/FeelIt/fffx_feel.cpp create mode 100644 code/win32/FeelIt/fffx_feel.h create mode 100644 code/win32/background.bmp create mode 100644 code/win32/bink.h create mode 100644 code/win32/binkw32.lib create mode 100644 code/win32/clear.bmp create mode 100644 code/win32/dbg_console_xbox.cpp create mode 100644 code/win32/dbg_console_xbox.h create mode 100644 code/win32/game.rc create mode 100644 code/win32/glw_win.h create mode 100644 code/win32/glw_win_dx8.h create mode 100644 code/win32/rad.h create mode 100644 code/win32/resource.h create mode 100644 code/win32/shader_constants.h create mode 100644 code/win32/snd_fx_img.h create mode 100644 code/win32/starwars.ico create mode 100644 code/win32/win_file.h create mode 100644 code/win32/win_file_xbox.cpp create mode 100644 code/win32/win_filecode.cpp create mode 100644 code/win32/win_gamma.cpp create mode 100644 code/win32/win_gamma_console.cpp create mode 100644 code/win32/win_glimp.cpp create mode 100644 code/win32/win_glimp_console.cpp create mode 100644 code/win32/win_highdynamicrange.cpp create mode 100644 code/win32/win_highdynamicrange.h create mode 100644 code/win32/win_input.cpp create mode 100644 code/win32/win_input.h create mode 100644 code/win32/win_input_console.cpp create mode 100644 code/win32/win_input_rumble.cpp create mode 100644 code/win32/win_input_xbox.cpp create mode 100644 code/win32/win_lighteffects.cpp create mode 100644 code/win32/win_lighteffects.h create mode 100644 code/win32/win_local.h create mode 100644 code/win32/win_main.cpp create mode 100644 code/win32/win_main_common.cpp create mode 100644 code/win32/win_main_console.cpp create mode 100644 code/win32/win_qal_xbox.cpp create mode 100644 code/win32/win_qgl.cpp create mode 100644 code/win32/win_qgl_dx8.cpp create mode 100644 code/win32/win_shared.cpp create mode 100644 code/win32/win_snd.cpp create mode 100644 code/win32/win_stencilshadow.cpp create mode 100644 code/win32/win_stencilshadow.h create mode 100644 code/win32/win_stream_dx8.cpp create mode 100644 code/win32/win_syscon.cpp create mode 100644 code/win32/win_video.cpp create mode 100644 code/win32/win_wndproc.cpp create mode 100644 code/win32/winquake.rc create mode 100644 code/win32/xbox_texture_man.h create mode 100644 code/x_exe/Copy of x_exe.vcproj create mode 100644 code/x_exe/title.bmp create mode 100644 code/x_exe/title.rdf create mode 100644 code/x_exe/titleimage.xbx create mode 100644 code/x_exe/x_exe.vcproj create mode 100644 code/x_exe/x_exe.vcproj.old create mode 100644 code/x_game/Copy of x_game.vcproj create mode 100644 code/x_game/x_game.vcproj create mode 100644 code/x_game/x_game.vcproj.old create mode 100644 code/x_shaders/bump.psh create mode 100644 code/x_shaders/bump.vsh create mode 100644 code/x_shaders/bump.xpu create mode 100644 code/x_shaders/bump.xvu create mode 100644 code/x_shaders/dlight.psh create mode 100644 code/x_shaders/dlight.vsh create mode 100644 code/x_shaders/dlight.xpu create mode 100644 code/x_shaders/dlight.xvu create mode 100644 code/x_shaders/environment.vsh create mode 100644 code/x_shaders/environment.xvu create mode 100644 code/x_shaders/extracthot.psh create mode 100644 code/x_shaders/extracthot.xpu create mode 100644 code/x_shaders/hotblur.psh create mode 100644 code/x_shaders/hotblur.xpu create mode 100644 code/x_shaders/rain.psh create mode 100644 code/x_shaders/rain.vsh create mode 100644 code/x_shaders/shadow.vsh create mode 100644 code/x_shaders/shadow.xvu create mode 100644 code/x_shaders/specular_dynamic.psh create mode 100644 code/x_shaders/specular_dynamic.vsh create mode 100644 code/x_shaders/specular_dynamic.xpu create mode 100644 code/x_shaders/specular_dynamic.xvu create mode 100644 code/x_shaders/specular_static.psh create mode 100644 code/x_shaders/specular_static.vsh create mode 100644 code/x_shaders/specular_static.xpu create mode 100644 code/x_shaders/specular_static.xvu create mode 100644 code/zlib/adler32.c create mode 100644 code/zlib/compress.c create mode 100644 code/zlib/crc32.c create mode 100644 code/zlib/deflate.c create mode 100644 code/zlib/deflate.h create mode 100644 code/zlib/infblock.c create mode 100644 code/zlib/infblock.h create mode 100644 code/zlib/infcodes.c create mode 100644 code/zlib/infcodes.h create mode 100644 code/zlib/inffast.c create mode 100644 code/zlib/inffast.h create mode 100644 code/zlib/inffixed.h create mode 100644 code/zlib/inflate.c create mode 100644 code/zlib/inftrees.c create mode 100644 code/zlib/inftrees.h create mode 100644 code/zlib/infutil.c create mode 100644 code/zlib/infutil.h create mode 100644 code/zlib/trees.c create mode 100644 code/zlib/trees.h create mode 100644 code/zlib/uncompr.c create mode 100644 code/zlib/zconf.h create mode 100644 code/zlib/zlib.h create mode 100644 code/zlib/zutil.c create mode 100644 code/zlib/zutil.h create mode 100644 code/zlib32/deflate.cpp create mode 100644 code/zlib32/deflate.h create mode 100644 code/zlib32/inflate.cpp create mode 100644 code/zlib32/inflate.h create mode 100644 code/zlib32/zip.h create mode 100644 code/zlib32/zipcommon.cpp create mode 100644 codemp/CommandLine.txt create mode 100644 codemp/JKA_mp.sln create mode 100644 codemp/Splines/Splines.dsp create mode 100644 codemp/Splines/math_angles.cpp create mode 100644 codemp/Splines/math_angles.h create mode 100644 codemp/Splines/math_matrix.cpp create mode 100644 codemp/Splines/math_matrix.h create mode 100644 codemp/Splines/math_quaternion.cpp create mode 100644 codemp/Splines/math_quaternion.h create mode 100644 codemp/Splines/math_vector.cpp create mode 100644 codemp/Splines/math_vector.h create mode 100644 codemp/Splines/q_parse.cpp create mode 100644 codemp/Splines/q_shared.cpp create mode 100644 codemp/Splines/q_shared.h create mode 100644 codemp/Splines/splines.cpp create mode 100644 codemp/Splines/splines.h create mode 100644 codemp/Splines/util_list.h create mode 100644 codemp/Splines/util_str.cpp create mode 100644 codemp/Splines/util_str.h create mode 100644 codemp/WinDed.vcproj create mode 100644 codemp/alut.lib create mode 100644 codemp/botlib/aasfile.h create mode 100644 codemp/botlib/be_aas_bsp.h create mode 100644 codemp/botlib/be_aas_bspq3.cpp create mode 100644 codemp/botlib/be_aas_cluster.cpp create mode 100644 codemp/botlib/be_aas_cluster.h create mode 100644 codemp/botlib/be_aas_debug.cpp create mode 100644 codemp/botlib/be_aas_debug.h create mode 100644 codemp/botlib/be_aas_def.h create mode 100644 codemp/botlib/be_aas_entity.cpp create mode 100644 codemp/botlib/be_aas_entity.h create mode 100644 codemp/botlib/be_aas_file.cpp create mode 100644 codemp/botlib/be_aas_file.h create mode 100644 codemp/botlib/be_aas_funcs.h create mode 100644 codemp/botlib/be_aas_main.cpp create mode 100644 codemp/botlib/be_aas_main.h create mode 100644 codemp/botlib/be_aas_move.cpp create mode 100644 codemp/botlib/be_aas_move.h create mode 100644 codemp/botlib/be_aas_optimize.cpp create mode 100644 codemp/botlib/be_aas_optimize.h create mode 100644 codemp/botlib/be_aas_reach.cpp create mode 100644 codemp/botlib/be_aas_reach.h create mode 100644 codemp/botlib/be_aas_route.cpp create mode 100644 codemp/botlib/be_aas_route.h create mode 100644 codemp/botlib/be_aas_routealt.cpp create mode 100644 codemp/botlib/be_aas_routealt.h create mode 100644 codemp/botlib/be_aas_sample.cpp create mode 100644 codemp/botlib/be_aas_sample.h create mode 100644 codemp/botlib/be_ai_char.cpp create mode 100644 codemp/botlib/be_ai_chat.cpp create mode 100644 codemp/botlib/be_ai_gen.cpp create mode 100644 codemp/botlib/be_ai_goal.cpp create mode 100644 codemp/botlib/be_ai_move.cpp create mode 100644 codemp/botlib/be_ai_weap.cpp create mode 100644 codemp/botlib/be_ai_weight.cpp create mode 100644 codemp/botlib/be_ai_weight.h create mode 100644 codemp/botlib/be_ea.cpp create mode 100644 codemp/botlib/be_interface.cpp create mode 100644 codemp/botlib/be_interface.h create mode 100644 codemp/botlib/botlib.dsp create mode 100644 codemp/botlib/botlib.vcproj create mode 100644 codemp/botlib/l_crc.cpp create mode 100644 codemp/botlib/l_crc.h create mode 100644 codemp/botlib/l_libvar.cpp create mode 100644 codemp/botlib/l_libvar.h create mode 100644 codemp/botlib/l_log.cpp create mode 100644 codemp/botlib/l_log.h create mode 100644 codemp/botlib/l_memory.cpp create mode 100644 codemp/botlib/l_memory.h create mode 100644 codemp/botlib/l_precomp.cpp create mode 100644 codemp/botlib/l_precomp.h create mode 100644 codemp/botlib/l_script.cpp create mode 100644 codemp/botlib/l_script.h create mode 100644 codemp/botlib/l_struct.cpp create mode 100644 codemp/botlib/l_struct.h create mode 100644 codemp/botlib/l_utils.h create mode 100644 codemp/buildvms.bat create mode 100644 codemp/cgame/JK2_cgame.def create mode 100644 codemp/cgame/JK2_cgame.dsp create mode 100644 codemp/cgame/JK2_cgame.vcproj create mode 100644 codemp/cgame/animtable.h create mode 100644 codemp/cgame/cg_consolecmds.c create mode 100644 codemp/cgame/cg_draw.c create mode 100644 codemp/cgame/cg_drawtools.c create mode 100644 codemp/cgame/cg_effects.c create mode 100644 codemp/cgame/cg_ents.c create mode 100644 codemp/cgame/cg_event.c create mode 100644 codemp/cgame/cg_info.c create mode 100644 codemp/cgame/cg_light.c create mode 100644 codemp/cgame/cg_lights.h create mode 100644 codemp/cgame/cg_local.h create mode 100644 codemp/cgame/cg_localents.c create mode 100644 codemp/cgame/cg_main.c create mode 100644 codemp/cgame/cg_marks.c create mode 100644 codemp/cgame/cg_media.h create mode 100644 codemp/cgame/cg_newDraw.c create mode 100644 codemp/cgame/cg_playeranimate.c create mode 100644 codemp/cgame/cg_players.c create mode 100644 codemp/cgame/cg_playerstate.c create mode 100644 codemp/cgame/cg_predict.c create mode 100644 codemp/cgame/cg_public.h create mode 100644 codemp/cgame/cg_saga.c create mode 100644 codemp/cgame/cg_scoreboard.c create mode 100644 codemp/cgame/cg_servercmds.c create mode 100644 codemp/cgame/cg_snapshot.c create mode 100644 codemp/cgame/cg_strap.c create mode 100644 codemp/cgame/cg_syscalls.asm create mode 100644 codemp/cgame/cg_syscalls.c create mode 100644 codemp/cgame/cg_turret.c create mode 100644 codemp/cgame/cg_view.c create mode 100644 codemp/cgame/cg_weaponinit.c create mode 100644 codemp/cgame/cg_weapons.c create mode 100644 codemp/cgame/cgame.bat create mode 100644 codemp/cgame/fx_blaster.c create mode 100644 codemp/cgame/fx_bowcaster.c create mode 100644 codemp/cgame/fx_bryarpistol.c create mode 100644 codemp/cgame/fx_demp2.c create mode 100644 codemp/cgame/fx_disruptor.c create mode 100644 codemp/cgame/fx_flechette.c create mode 100644 codemp/cgame/fx_force.c create mode 100644 codemp/cgame/fx_heavyrepeater.c create mode 100644 codemp/cgame/fx_local.h create mode 100644 codemp/cgame/fx_rocketlauncher.c create mode 100644 codemp/cgame/holocronicons.h create mode 100644 codemp/cgame/tr_types.h create mode 100644 codemp/client/0_sh_leak.cpp create mode 100644 codemp/client/BinkVideo.cpp create mode 100644 codemp/client/BinkVideo.h create mode 100644 codemp/client/cl_cgame.cpp create mode 100644 codemp/client/cl_cin.cpp create mode 100644 codemp/client/cl_cin_console.cpp create mode 100644 codemp/client/cl_console.cpp create mode 100644 codemp/client/cl_data.cpp create mode 100644 codemp/client/cl_data.h create mode 100644 codemp/client/cl_input.cpp create mode 100644 codemp/client/cl_input_hotswap.cpp create mode 100644 codemp/client/cl_input_hotswap.h create mode 100644 codemp/client/cl_keys.cpp create mode 100644 codemp/client/cl_main.cpp create mode 100644 codemp/client/cl_net_chan.cpp create mode 100644 codemp/client/cl_parse.cpp create mode 100644 codemp/client/cl_scrn.cpp create mode 100644 codemp/client/cl_ui.cpp create mode 100644 codemp/client/client.h create mode 100644 codemp/client/eax/eax.h create mode 100644 codemp/client/eax/eaxman.h create mode 100644 codemp/client/fffx.h create mode 100644 codemp/client/fxexport.cpp create mode 100644 codemp/client/fxexport.h create mode 100644 codemp/client/fxprimitives.cpp create mode 100644 codemp/client/fxprimitives.h create mode 100644 codemp/client/fxscheduler.cpp create mode 100644 codemp/client/fxscheduler.h create mode 100644 codemp/client/fxsystem.cpp create mode 100644 codemp/client/fxsystem.h create mode 100644 codemp/client/fxtemplate.cpp create mode 100644 codemp/client/fxutil.cpp create mode 100644 codemp/client/fxutil.h create mode 100644 codemp/client/keycodes.h create mode 100644 codemp/client/keys.h create mode 100644 codemp/client/openal/al.h create mode 100644 codemp/client/openal/alc.h create mode 100644 codemp/client/openal/alctypes.h create mode 100644 codemp/client/openal/altypes.h create mode 100644 codemp/client/openal/alu.h create mode 100644 codemp/client/openal/alut.h create mode 100644 codemp/client/snd_ambient.cpp create mode 100644 codemp/client/snd_ambient.h create mode 100644 codemp/client/snd_dma.cpp create mode 100644 codemp/client/snd_dma_console.cpp create mode 100644 codemp/client/snd_local.h create mode 100644 codemp/client/snd_local_console.h create mode 100644 codemp/client/snd_mem.cpp create mode 100644 codemp/client/snd_mem_console.cpp create mode 100644 codemp/client/snd_mix.cpp create mode 100644 codemp/client/snd_mp3.cpp create mode 100644 codemp/client/snd_mp3.h create mode 100644 codemp/client/snd_music.cpp create mode 100644 codemp/client/snd_music.h create mode 100644 codemp/client/snd_public.h create mode 100644 codemp/encryption/buffer.cpp create mode 100644 codemp/encryption/buffer.h create mode 100644 codemp/encryption/cpp_interface.cpp create mode 100644 codemp/encryption/cpp_interface.h create mode 100644 codemp/encryption/encryption.h create mode 100644 codemp/encryption/sockets.cpp create mode 100644 codemp/encryption/sockets.h create mode 100644 codemp/ff/ff_console.cpp create mode 100644 codemp/game/AnimalNPC.c create mode 100644 codemp/game/FighterNPC.c create mode 100644 codemp/game/JK2_game.def create mode 100644 codemp/game/JK2_game.dsp create mode 100644 codemp/game/JK2_game.vcproj create mode 100644 codemp/game/NPC.c create mode 100644 codemp/game/NPC_AI_Atst.c create mode 100644 codemp/game/NPC_AI_Default.c create mode 100644 codemp/game/NPC_AI_Droid.c create mode 100644 codemp/game/NPC_AI_Grenadier.c create mode 100644 codemp/game/NPC_AI_Howler.c create mode 100644 codemp/game/NPC_AI_ImperialProbe.c create mode 100644 codemp/game/NPC_AI_Jedi.c create mode 100644 codemp/game/NPC_AI_MineMonster.c create mode 100644 codemp/game/NPC_AI_Rancor.c create mode 100644 codemp/game/NPC_AI_Remote.c create mode 100644 codemp/game/NPC_AI_Seeker.c create mode 100644 codemp/game/NPC_AI_Sentry.c create mode 100644 codemp/game/NPC_AI_Sniper.c create mode 100644 codemp/game/NPC_AI_Stormtrooper.c create mode 100644 codemp/game/NPC_AI_Utils.c create mode 100644 codemp/game/NPC_AI_Wampa.c create mode 100644 codemp/game/NPC_behavior.c create mode 100644 codemp/game/NPC_combat.c create mode 100644 codemp/game/NPC_goal.c create mode 100644 codemp/game/NPC_misc.c create mode 100644 codemp/game/NPC_move.c create mode 100644 codemp/game/NPC_reactions.c create mode 100644 codemp/game/NPC_senses.c create mode 100644 codemp/game/NPC_sounds.c create mode 100644 codemp/game/NPC_spawn.c create mode 100644 codemp/game/NPC_stats.c create mode 100644 codemp/game/NPC_utils.c create mode 100644 codemp/game/SpeederNPC.c create mode 100644 codemp/game/WalkerNPC.c create mode 100644 codemp/game/ai.h create mode 100644 codemp/game/ai_main.c create mode 100644 codemp/game/ai_main.h create mode 100644 codemp/game/ai_util.c create mode 100644 codemp/game/ai_wpnav.c create mode 100644 codemp/game/anims.h create mode 100644 codemp/game/b_local.h create mode 100644 codemp/game/b_public.h create mode 100644 codemp/game/be_aas.h create mode 100644 codemp/game/be_ai_char.h create mode 100644 codemp/game/be_ai_chat.h create mode 100644 codemp/game/be_ai_gen.h create mode 100644 codemp/game/be_ai_goal.h create mode 100644 codemp/game/be_ai_move.h create mode 100644 codemp/game/be_ai_weap.h create mode 100644 codemp/game/be_ea.h create mode 100644 codemp/game/bg_g2_utils.c create mode 100644 codemp/game/bg_lib.c create mode 100644 codemp/game/bg_lib.h create mode 100644 codemp/game/bg_local.h create mode 100644 codemp/game/bg_misc.c create mode 100644 codemp/game/bg_panimate.c create mode 100644 codemp/game/bg_pmove.c create mode 100644 codemp/game/bg_public.h create mode 100644 codemp/game/bg_saber.c create mode 100644 codemp/game/bg_saberLoad.c create mode 100644 codemp/game/bg_saga.c create mode 100644 codemp/game/bg_saga.h create mode 100644 codemp/game/bg_slidemove.c create mode 100644 codemp/game/bg_strap.h create mode 100644 codemp/game/bg_vehicleLoad.c create mode 100644 codemp/game/bg_vehicles.h create mode 100644 codemp/game/bg_weapons.c create mode 100644 codemp/game/bg_weapons.h create mode 100644 codemp/game/botlib.h create mode 100644 codemp/game/chars.h create mode 100644 codemp/game/g_ICARUScb.c create mode 100644 codemp/game/g_ICARUScb.h create mode 100644 codemp/game/g_active.c create mode 100644 codemp/game/g_arenas.c create mode 100644 codemp/game/g_bot.c create mode 100644 codemp/game/g_client.c create mode 100644 codemp/game/g_cmds.c create mode 100644 codemp/game/g_combat.c create mode 100644 codemp/game/g_exphysics.c create mode 100644 codemp/game/g_headers.h create mode 100644 codemp/game/g_items.c create mode 100644 codemp/game/g_local.h create mode 100644 codemp/game/g_log.c create mode 100644 codemp/game/g_main.c create mode 100644 codemp/game/g_mem.c create mode 100644 codemp/game/g_misc.c create mode 100644 codemp/game/g_missile.c create mode 100644 codemp/game/g_mover.c create mode 100644 codemp/game/g_nav.c create mode 100644 codemp/game/g_nav.h create mode 100644 codemp/game/g_navnew.c create mode 100644 codemp/game/g_object.c create mode 100644 codemp/game/g_public.h create mode 100644 codemp/game/g_saga.c create mode 100644 codemp/game/g_session.c create mode 100644 codemp/game/g_spawn.c create mode 100644 codemp/game/g_strap.c create mode 100644 codemp/game/g_svcmds.c create mode 100644 codemp/game/g_syscalls.asm create mode 100644 codemp/game/g_syscalls.c create mode 100644 codemp/game/g_target.c create mode 100644 codemp/game/g_team.c create mode 100644 codemp/game/g_team.h create mode 100644 codemp/game/g_timer.c create mode 100644 codemp/game/g_trigger.c create mode 100644 codemp/game/g_turret.c create mode 100644 codemp/game/g_turret_G2.c create mode 100644 codemp/game/g_utils.c create mode 100644 codemp/game/g_vehicleTurret.c create mode 100644 codemp/game/g_vehicles.c create mode 100644 codemp/game/g_weapon.c create mode 100644 codemp/game/game.bat create mode 100644 codemp/game/inv.h create mode 100644 codemp/game/match.h create mode 100644 codemp/game/npc_headers.h create mode 100644 codemp/game/q_math.c create mode 100644 codemp/game/q_shared.c create mode 100644 codemp/game/q_shared.h create mode 100644 codemp/game/say.h create mode 100644 codemp/game/surfaceflags.h create mode 100644 codemp/game/syn.h create mode 100644 codemp/game/teams.h create mode 100644 codemp/game/tri_coll_test.c create mode 100644 codemp/game/w_force.c create mode 100644 codemp/game/w_saber.c create mode 100644 codemp/game/w_saber.h create mode 100644 codemp/ghoul2/g2.h create mode 100644 codemp/ghoul2/g2_api.cpp create mode 100644 codemp/ghoul2/g2_bolts.cpp create mode 100644 codemp/ghoul2/g2_bones.cpp create mode 100644 codemp/ghoul2/g2_gore.h create mode 100644 codemp/ghoul2/g2_local.h create mode 100644 codemp/ghoul2/g2_misc.cpp create mode 100644 codemp/ghoul2/g2_surfaces.cpp create mode 100644 codemp/ghoul2/ghoul2_shared.h create mode 100644 codemp/goblib/goblib.cpp create mode 100644 codemp/goblib/goblib.h create mode 100644 codemp/goblib/goblib.vcproj create mode 100644 codemp/icarus/blockstream.cpp create mode 100644 codemp/icarus/blockstream.h create mode 100644 codemp/icarus/gameinterface.cpp create mode 100644 codemp/icarus/gameinterface.h create mode 100644 codemp/icarus/icarus.h create mode 100644 codemp/icarus/instance.cpp create mode 100644 codemp/icarus/instance.h create mode 100644 codemp/icarus/interface.cpp create mode 100644 codemp/icarus/interface.h create mode 100644 codemp/icarus/interpreter.cpp create mode 100644 codemp/icarus/interpreter.h create mode 100644 codemp/icarus/memory.cpp create mode 100644 codemp/icarus/module.h create mode 100644 codemp/icarus/q3_interface.cpp create mode 100644 codemp/icarus/q3_interface.h create mode 100644 codemp/icarus/q3_registers.cpp create mode 100644 codemp/icarus/q3_registers.h create mode 100644 codemp/icarus/sequence.cpp create mode 100644 codemp/icarus/sequence.h create mode 100644 codemp/icarus/sequencer.cpp create mode 100644 codemp/icarus/sequencer.h create mode 100644 codemp/icarus/taskmanager.cpp create mode 100644 codemp/icarus/taskmanager.h create mode 100644 codemp/icarus/tokenizer.cpp create mode 100644 codemp/icarus/tokenizer.h create mode 100644 codemp/install.bat create mode 100644 codemp/installvms.bat create mode 100644 codemp/jk2mp.vcproj create mode 100644 codemp/jpeg-6/jcapimin.cpp create mode 100644 codemp/jpeg-6/jccoefct.cpp create mode 100644 codemp/jpeg-6/jccolor.cpp create mode 100644 codemp/jpeg-6/jcdctmgr.cpp create mode 100644 codemp/jpeg-6/jchuff.cpp create mode 100644 codemp/jpeg-6/jchuff.h create mode 100644 codemp/jpeg-6/jcinit.cpp create mode 100644 codemp/jpeg-6/jcmainct.cpp create mode 100644 codemp/jpeg-6/jcmarker.cpp create mode 100644 codemp/jpeg-6/jcmaster.cpp create mode 100644 codemp/jpeg-6/jcomapi.cpp create mode 100644 codemp/jpeg-6/jconfig.h create mode 100644 codemp/jpeg-6/jcparam.cpp create mode 100644 codemp/jpeg-6/jcphuff.cpp create mode 100644 codemp/jpeg-6/jcprepct.cpp create mode 100644 codemp/jpeg-6/jcsample.cpp create mode 100644 codemp/jpeg-6/jctrans.cpp create mode 100644 codemp/jpeg-6/jdapimin.cpp create mode 100644 codemp/jpeg-6/jdapistd.cpp create mode 100644 codemp/jpeg-6/jdatadst.cpp create mode 100644 codemp/jpeg-6/jdatasrc.cpp create mode 100644 codemp/jpeg-6/jdcoefct.cpp create mode 100644 codemp/jpeg-6/jdcolor.cpp create mode 100644 codemp/jpeg-6/jdct.h create mode 100644 codemp/jpeg-6/jddctmgr.cpp create mode 100644 codemp/jpeg-6/jdhuff.cpp create mode 100644 codemp/jpeg-6/jdhuff.h create mode 100644 codemp/jpeg-6/jdinput.cpp create mode 100644 codemp/jpeg-6/jdmainct.cpp create mode 100644 codemp/jpeg-6/jdmarker.cpp create mode 100644 codemp/jpeg-6/jdmaster.cpp create mode 100644 codemp/jpeg-6/jdpostct.cpp create mode 100644 codemp/jpeg-6/jdsample.cpp create mode 100644 codemp/jpeg-6/jdtrans.cpp create mode 100644 codemp/jpeg-6/jerror.cpp create mode 100644 codemp/jpeg-6/jerror.h create mode 100644 codemp/jpeg-6/jfdctflt.cpp create mode 100644 codemp/jpeg-6/jidctflt.cpp create mode 100644 codemp/jpeg-6/jinclude.h create mode 100644 codemp/jpeg-6/jmemmgr.cpp create mode 100644 codemp/jpeg-6/jmemnobs.cpp create mode 100644 codemp/jpeg-6/jmemsys.h create mode 100644 codemp/jpeg-6/jmorecfg.h create mode 100644 codemp/jpeg-6/jpegint.h create mode 100644 codemp/jpeg-6/jpeglib.h create mode 100644 codemp/jpeg-6/jutils.cpp create mode 100644 codemp/jpeg-6/jversion.h create mode 100644 codemp/mp3code/cdct.c create mode 100644 codemp/mp3code/config.h create mode 100644 codemp/mp3code/copyright.h create mode 100644 codemp/mp3code/csbt.c create mode 100644 codemp/mp3code/csbtb.c create mode 100644 codemp/mp3code/csbtl3.c create mode 100644 codemp/mp3code/cup.c create mode 100644 codemp/mp3code/cupini.c create mode 100644 codemp/mp3code/cupl1.c create mode 100644 codemp/mp3code/cupl3.c create mode 100644 codemp/mp3code/cwin.c create mode 100644 codemp/mp3code/cwinb.c create mode 100644 codemp/mp3code/cwinm.c create mode 100644 codemp/mp3code/htable.h create mode 100644 codemp/mp3code/hwin.c create mode 100644 codemp/mp3code/jdw.h create mode 100644 codemp/mp3code/l3.h create mode 100644 codemp/mp3code/l3dq.c create mode 100644 codemp/mp3code/l3init.c create mode 100644 codemp/mp3code/mdct.c create mode 100644 codemp/mp3code/mhead.c create mode 100644 codemp/mp3code/mhead.h create mode 100644 codemp/mp3code/mp3struct.h create mode 100644 codemp/mp3code/msis.c create mode 100644 codemp/mp3code/port.h create mode 100644 codemp/mp3code/small_header.h create mode 100644 codemp/mp3code/tableawd.h create mode 100644 codemp/mp3code/towave.c create mode 100644 codemp/mp3code/uph.c create mode 100644 codemp/mp3code/upsf.c create mode 100644 codemp/mp3code/wavep.c create mode 100644 codemp/namespace_begin.h create mode 100644 codemp/namespace_end.h create mode 100644 codemp/null/mac_net.c create mode 100644 codemp/null/null_client.cpp create mode 100644 codemp/null/null_glimp.cpp create mode 100644 codemp/null/null_input.cpp create mode 100644 codemp/null/null_main.c create mode 100644 codemp/null/null_net.c create mode 100644 codemp/null/null_renderer.cpp create mode 100644 codemp/null/null_snddma.cpp create mode 100644 codemp/null/win_main.cpp create mode 100644 codemp/openal32.dll create mode 100644 codemp/openal32.lib create mode 100644 codemp/png/png.cpp create mode 100644 codemp/png/png.h create mode 100644 codemp/qcommon/chash.h create mode 100644 codemp/qcommon/cm_draw.cpp create mode 100644 codemp/qcommon/cm_draw.h create mode 100644 codemp/qcommon/cm_landscape.h create mode 100644 codemp/qcommon/cm_load.cpp create mode 100644 codemp/qcommon/cm_load_xbox.cpp create mode 100644 codemp/qcommon/cm_local.h create mode 100644 codemp/qcommon/cm_patch.cpp create mode 100644 codemp/qcommon/cm_patch.h create mode 100644 codemp/qcommon/cm_patch_xbox.cpp create mode 100644 codemp/qcommon/cm_polylib.cpp create mode 100644 codemp/qcommon/cm_polylib.h create mode 100644 codemp/qcommon/cm_public.h create mode 100644 codemp/qcommon/cm_randomterrain.cpp create mode 100644 codemp/qcommon/cm_randomterrain.h create mode 100644 codemp/qcommon/cm_shader.cpp create mode 100644 codemp/qcommon/cm_terrain.cpp create mode 100644 codemp/qcommon/cm_terrainmap.cpp create mode 100644 codemp/qcommon/cm_terrainmap.h create mode 100644 codemp/qcommon/cm_test.cpp create mode 100644 codemp/qcommon/cm_trace.cpp create mode 100644 codemp/qcommon/cmd_common.cpp create mode 100644 codemp/qcommon/cmd_console.cpp create mode 100644 codemp/qcommon/cmd_pc.cpp create mode 100644 codemp/qcommon/cnetprofile.cpp create mode 100644 codemp/qcommon/common.cpp create mode 100644 codemp/qcommon/cvar.cpp create mode 100644 codemp/qcommon/disablewarnings.h create mode 100644 codemp/qcommon/exe_headers.cpp create mode 100644 codemp/qcommon/exe_headers.h create mode 100644 codemp/qcommon/files.h create mode 100644 codemp/qcommon/files_common.cpp create mode 100644 codemp/qcommon/files_console.cpp create mode 100644 codemp/qcommon/files_pc.cpp create mode 100644 codemp/qcommon/fixedmap.h create mode 100644 codemp/qcommon/game_version.h create mode 100644 codemp/qcommon/genericparser2.cpp create mode 100644 codemp/qcommon/genericparser2.h create mode 100644 codemp/qcommon/hstring.cpp create mode 100644 codemp/qcommon/hstring.h create mode 100644 codemp/qcommon/huffman.cpp create mode 100644 codemp/qcommon/inetprofile.h create mode 100644 codemp/qcommon/md4.cpp create mode 100644 codemp/qcommon/miniheap.h create mode 100644 codemp/qcommon/msg.cpp create mode 100644 codemp/qcommon/net_chan.cpp create mode 100644 codemp/qcommon/platform.h create mode 100644 codemp/qcommon/q_math.cpp create mode 100644 codemp/qcommon/q_shared.cpp create mode 100644 codemp/qcommon/qcommon.h create mode 100644 codemp/qcommon/qfiles.h create mode 100644 codemp/qcommon/roffsystem.cpp create mode 100644 codemp/qcommon/roffsystem.h create mode 100644 codemp/qcommon/sparc.h create mode 100644 codemp/qcommon/sstring.h create mode 100644 codemp/qcommon/stringed_ingame.cpp create mode 100644 codemp/qcommon/stringed_ingame.h create mode 100644 codemp/qcommon/stringed_interface.cpp create mode 100644 codemp/qcommon/stringed_interface.h create mode 100644 codemp/qcommon/strip.cpp create mode 100644 codemp/qcommon/strip.h create mode 100644 codemp/qcommon/tags.h create mode 100644 codemp/qcommon/timing.h create mode 100644 codemp/qcommon/unzip.cpp create mode 100644 codemp/qcommon/unzip.h create mode 100644 codemp/qcommon/vm.cpp create mode 100644 codemp/qcommon/vm_console.cpp create mode 100644 codemp/qcommon/vm_interpreted.cpp create mode 100644 codemp/qcommon/vm_local.h create mode 100644 codemp/qcommon/vm_ppc.cpp create mode 100644 codemp/qcommon/vm_x86.cpp create mode 100644 codemp/qcommon/xb_settings.cpp create mode 100644 codemp/qcommon/xb_settings.h create mode 100644 codemp/qcommon/z_memman_console.cpp create mode 100644 codemp/qcommon/z_memman_pc.cpp create mode 100644 codemp/ratl/bits_vs.h create mode 100644 codemp/ratl/ratl_common.h create mode 100644 codemp/ratl/vector_vs.h create mode 100644 codemp/ravl/CVec.h create mode 100644 codemp/renderer/glext.h create mode 100644 codemp/renderer/glext_console.h create mode 100644 codemp/renderer/matcomp.c create mode 100644 codemp/renderer/matcomp.h create mode 100644 codemp/renderer/mdx_format.h create mode 100644 codemp/renderer/modelmem.h create mode 100644 codemp/renderer/qgl.h create mode 100644 codemp/renderer/qgl_console.h create mode 100644 codemp/renderer/tr_animation.cpp create mode 100644 codemp/renderer/tr_arioche.cpp create mode 100644 codemp/renderer/tr_backend.cpp create mode 100644 codemp/renderer/tr_bsp.cpp create mode 100644 codemp/renderer/tr_bsp_xbox.cpp create mode 100644 codemp/renderer/tr_cmds.cpp create mode 100644 codemp/renderer/tr_curve.cpp create mode 100644 codemp/renderer/tr_curve_xbox.cpp create mode 100644 codemp/renderer/tr_flares.cpp create mode 100644 codemp/renderer/tr_font.cpp create mode 100644 codemp/renderer/tr_font.h create mode 100644 codemp/renderer/tr_ghoul2.cpp create mode 100644 codemp/renderer/tr_image.cpp create mode 100644 codemp/renderer/tr_image_xbox.cpp create mode 100644 codemp/renderer/tr_init.cpp create mode 100644 codemp/renderer/tr_landscape.h create mode 100644 codemp/renderer/tr_light.cpp create mode 100644 codemp/renderer/tr_lightmanager.cpp create mode 100644 codemp/renderer/tr_lightmanager.h create mode 100644 codemp/renderer/tr_local.h create mode 100644 codemp/renderer/tr_main.cpp create mode 100644 codemp/renderer/tr_marks.cpp create mode 100644 codemp/renderer/tr_mesh.cpp create mode 100644 codemp/renderer/tr_model.cpp create mode 100644 codemp/renderer/tr_noise.cpp create mode 100644 codemp/renderer/tr_public.h create mode 100644 codemp/renderer/tr_quicksprite.cpp create mode 100644 codemp/renderer/tr_quicksprite.h create mode 100644 codemp/renderer/tr_scene.cpp create mode 100644 codemp/renderer/tr_shade.cpp create mode 100644 codemp/renderer/tr_shade_calc.cpp create mode 100644 codemp/renderer/tr_shader.cpp create mode 100644 codemp/renderer/tr_shadows.cpp create mode 100644 codemp/renderer/tr_sky.cpp create mode 100644 codemp/renderer/tr_surface.cpp create mode 100644 codemp/renderer/tr_surfacesprites.cpp create mode 100644 codemp/renderer/tr_terrain.cpp create mode 100644 codemp/renderer/tr_world.cpp create mode 100644 codemp/renderer/tr_worldeffects.cpp create mode 100644 codemp/renderer/tr_worldeffects.h create mode 100644 codemp/rmg/rm_area.cpp create mode 100644 codemp/rmg/rm_area.h create mode 100644 codemp/rmg/rm_headers.h create mode 100644 codemp/rmg/rm_instance.cpp create mode 100644 codemp/rmg/rm_instance.h create mode 100644 codemp/rmg/rm_instance_bsp.cpp create mode 100644 codemp/rmg/rm_instance_bsp.h create mode 100644 codemp/rmg/rm_instance_group.cpp create mode 100644 codemp/rmg/rm_instance_group.h create mode 100644 codemp/rmg/rm_instance_random.cpp create mode 100644 codemp/rmg/rm_instance_random.h create mode 100644 codemp/rmg/rm_instance_void.cpp create mode 100644 codemp/rmg/rm_instance_void.h create mode 100644 codemp/rmg/rm_instancefile.cpp create mode 100644 codemp/rmg/rm_instancefile.h create mode 100644 codemp/rmg/rm_manager.cpp create mode 100644 codemp/rmg/rm_manager.h create mode 100644 codemp/rmg/rm_mission.cpp create mode 100644 codemp/rmg/rm_mission.h create mode 100644 codemp/rmg/rm_objective.cpp create mode 100644 codemp/rmg/rm_objective.h create mode 100644 codemp/rmg/rm_path.cpp create mode 100644 codemp/rmg/rm_path.h create mode 100644 codemp/rmg/rm_terrain.cpp create mode 100644 codemp/rmg/rm_terrain.h create mode 100644 codemp/server/NPCNav/gameCallbacks.cpp create mode 100644 codemp/server/NPCNav/navigator.cpp create mode 100644 codemp/server/NPCNav/navigator.h create mode 100644 codemp/server/exe_headers.cpp create mode 100644 codemp/server/exe_headers.h create mode 100644 codemp/server/server.h create mode 100644 codemp/server/sv_bot.cpp create mode 100644 codemp/server/sv_ccmds.cpp create mode 100644 codemp/server/sv_client.cpp create mode 100644 codemp/server/sv_game.cpp create mode 100644 codemp/server/sv_init.cpp create mode 100644 codemp/server/sv_main.cpp create mode 100644 codemp/server/sv_net_chan.cpp create mode 100644 codemp/server/sv_rankings.cpp create mode 100644 codemp/server/sv_snapshot.cpp create mode 100644 codemp/server/sv_world.cpp create mode 100644 codemp/smartheap/heapagnt.h create mode 100644 codemp/smartheap/smrtheap.c create mode 100644 codemp/smartheap/smrtheap.h create mode 100644 codemp/smartheap/smrtheap.hpp create mode 100644 codemp/strings/con_text.h create mode 100644 codemp/strings/str_server.h create mode 100644 codemp/tosend.bat create mode 100644 codemp/ui/keycodes.h create mode 100644 codemp/ui/ui.bat create mode 100644 codemp/ui/ui.def create mode 100644 codemp/ui/ui.dsp create mode 100644 codemp/ui/ui.vcproj create mode 100644 codemp/ui/ui_atoms.c create mode 100644 codemp/ui/ui_force.c create mode 100644 codemp/ui/ui_force.h create mode 100644 codemp/ui/ui_gameinfo.c create mode 100644 codemp/ui/ui_local.h create mode 100644 codemp/ui/ui_main.c create mode 100644 codemp/ui/ui_players.c create mode 100644 codemp/ui/ui_public.h create mode 100644 codemp/ui/ui_saber.c create mode 100644 codemp/ui/ui_shared.c create mode 100644 codemp/ui/ui_shared.h create mode 100644 codemp/ui/ui_syscalls.asm create mode 100644 codemp/ui/ui_syscalls.c create mode 100644 codemp/ui/ui_util.c create mode 100644 codemp/unix/ftol.nasm create mode 100644 codemp/unix/linux_common.c create mode 100644 codemp/unix/linux_glimp.c create mode 100644 codemp/unix/linux_joystick.c create mode 100644 codemp/unix/linux_local.h create mode 100644 codemp/unix/linux_qgl.c create mode 100644 codemp/unix/linux_snd.c create mode 100644 codemp/unix/snapvector.nasm create mode 100644 codemp/unix/unix_main.c create mode 100644 codemp/unix/unix_net.c create mode 100644 codemp/unix/unix_shared.c create mode 100644 codemp/unix/vm_x86.c create mode 100644 codemp/update_MPents.bat create mode 100644 codemp/win32/AutoVersion.h create mode 100644 codemp/win32/JK2cgame.rc create mode 100644 codemp/win32/JK2game.rc create mode 100644 codemp/win32/WinDed.rc create mode 100644 codemp/win32/dbg_console_xbox.cpp create mode 100644 codemp/win32/dbg_console_xbox.h create mode 100644 codemp/win32/glw_win.h create mode 100644 codemp/win32/glw_win_dx8.h create mode 100644 codemp/win32/qe3.ico create mode 100644 codemp/win32/resource.h create mode 100644 codemp/win32/shader_constants.h create mode 100644 codemp/win32/snd_fx_img.h create mode 100644 codemp/win32/ui.rc create mode 100644 codemp/win32/win_file.h create mode 100644 codemp/win32/win_file_xbox.cpp create mode 100644 codemp/win32/win_filecode.cpp create mode 100644 codemp/win32/win_gamma.cpp create mode 100644 codemp/win32/win_gamma_console.cpp create mode 100644 codemp/win32/win_glimp.cpp create mode 100644 codemp/win32/win_glimp_console.cpp create mode 100644 codemp/win32/win_highdynamicrange.cpp create mode 100644 codemp/win32/win_highdynamicrange.h create mode 100644 codemp/win32/win_input.cpp create mode 100644 codemp/win32/win_input.h create mode 100644 codemp/win32/win_input_console.cpp create mode 100644 codemp/win32/win_input_rumble.cpp create mode 100644 codemp/win32/win_input_xbox.cpp create mode 100644 codemp/win32/win_lighteffects.cpp create mode 100644 codemp/win32/win_lighteffects.h create mode 100644 codemp/win32/win_local.h create mode 100644 codemp/win32/win_main.cpp create mode 100644 codemp/win32/win_main_common.cpp create mode 100644 codemp/win32/win_main_console.cpp create mode 100644 codemp/win32/win_net.cpp create mode 100644 codemp/win32/win_net_xbox.cpp create mode 100644 codemp/win32/win_qal_xbox.cpp create mode 100644 codemp/win32/win_qgl.cpp create mode 100644 codemp/win32/win_qgl_dx8.cpp create mode 100644 codemp/win32/win_shared.cpp create mode 100644 codemp/win32/win_snd.cpp create mode 100644 codemp/win32/win_stream_dx8.cpp create mode 100644 codemp/win32/win_syscon.cpp create mode 100644 codemp/win32/win_wndproc.cpp create mode 100644 codemp/win32/win_worldeffects.cpp create mode 100644 codemp/win32/winquake.rc create mode 100644 codemp/win32/xbox_texture_man.h create mode 100644 codemp/x_botlib/x_botlib.vcproj create mode 100644 codemp/x_exe/x_exe.vcproj create mode 100644 codemp/x_jk2cgame/x_jk2cgame.vcproj create mode 100644 codemp/x_jk2game/x_jk2game.vcproj create mode 100644 codemp/x_ui/x_ui.vcproj create mode 100644 codemp/xbox/JediAcademy.xms create mode 100644 codemp/xbox/XBLive.cpp create mode 100644 codemp/xbox/XBLive.h create mode 100644 codemp/xbox/XBLive_Friends.cpp create mode 100644 codemp/xbox/XBLive_MM.cpp create mode 100644 codemp/xbox/XBLive_PL.cpp create mode 100644 codemp/xbox/XBVoice.cpp create mode 100644 codemp/xbox/XBVoice.h create mode 100644 codemp/xbox/XBoxCommon.h create mode 100644 codemp/xbox/XHVVoiceManager.cpp create mode 100644 codemp/xbox/XHVVoiceManager.h create mode 100644 codemp/xbox/match.cpp create mode 100644 codemp/xbox/match.h create mode 100644 codemp/xbox/xbSockAddr.cpp create mode 100644 codemp/xbox/xbSockAddr.h create mode 100644 codemp/xbox/xbsocket.cpp create mode 100644 codemp/xbox/xbsocket.h create mode 100644 codemp/zlib/adler32.c create mode 100644 codemp/zlib/compress.c create mode 100644 codemp/zlib/crc32.c create mode 100644 codemp/zlib/deflate.c create mode 100644 codemp/zlib/deflate.h create mode 100644 codemp/zlib/infblock.c create mode 100644 codemp/zlib/infblock.h create mode 100644 codemp/zlib/infcodes.c create mode 100644 codemp/zlib/infcodes.h create mode 100644 codemp/zlib/inffast.c create mode 100644 codemp/zlib/inffast.h create mode 100644 codemp/zlib/inffixed.h create mode 100644 codemp/zlib/inflate.c create mode 100644 codemp/zlib/inftrees.c create mode 100644 codemp/zlib/inftrees.h create mode 100644 codemp/zlib/infutil.c create mode 100644 codemp/zlib/infutil.h create mode 100644 codemp/zlib/trees.c create mode 100644 codemp/zlib/trees.h create mode 100644 codemp/zlib/uncompr.c create mode 100644 codemp/zlib/zconf.h create mode 100644 codemp/zlib/zlib.h create mode 100644 codemp/zlib/zutil.c create mode 100644 codemp/zlib/zutil.h create mode 100644 codemp/zlib32/deflate.cpp create mode 100644 codemp/zlib32/deflate.h create mode 100644 codemp/zlib32/inflate.cpp create mode 100644 codemp/zlib32/inflate.h create mode 100644 codemp/zlib32/zip.h create mode 100644 codemp/zlib32/zipcommon.cpp create mode 100644 tools/ModView/GenericParser2.cpp create mode 100644 tools/ModView/GenericParser2.h create mode 100644 tools/ModView/ModView.dsp create mode 100644 tools/ModView/ModView.dsw create mode 100644 tools/ModView/ModView.ncb create mode 100644 tools/ModView/ModView.sln create mode 100644 tools/ModView/ModView.vcproj create mode 100644 tools/ModView/Splash.cpp create mode 100644 tools/ModView/Splash.h create mode 100644 tools/ModView/Splsh16.bmp create mode 100644 tools/ModView/_console_skin_list_ create mode 100644 tools/ModView/_console_str_list_ create mode 100644 tools/ModView/anims.cpp create mode 100644 tools/ModView/anims.h create mode 100644 tools/ModView/clipboard.cpp create mode 100644 tools/ModView/clipboard.h create mode 100644 tools/ModView/commarea.cpp create mode 100644 tools/ModView/commarea.h create mode 100644 tools/ModView/common_headers.h create mode 100644 tools/ModView/commtest/MainFrm.cpp create mode 100644 tools/ModView/commtest/MainFrm.h create mode 100644 tools/ModView/commtest/ReadMe.txt create mode 100644 tools/ModView/commtest/StdAfx.cpp create mode 100644 tools/ModView/commtest/StdAfx.h create mode 100644 tools/ModView/commtest/bits.h create mode 100644 tools/ModView/commtest/commtest.cpp create mode 100644 tools/ModView/commtest/commtest.dsp create mode 100644 tools/ModView/commtest/commtest.h create mode 100644 tools/ModView/commtest/commtest.rc create mode 100644 tools/ModView/commtest/commtestDoc.cpp create mode 100644 tools/ModView/commtest/commtestDoc.h create mode 100644 tools/ModView/commtest/commtestView.cpp create mode 100644 tools/ModView/commtest/commtestView.h create mode 100644 tools/ModView/commtest/res/Toolbar.bmp create mode 100644 tools/ModView/commtest/res/commtest.ico create mode 100644 tools/ModView/commtest/res/commtest.rc2 create mode 100644 tools/ModView/commtest/res/commtestDoc.ico create mode 100644 tools/ModView/commtest/resource.h create mode 100644 tools/ModView/commtest/wintalk.cpp create mode 100644 tools/ModView/disablewarnings.h create mode 100644 tools/ModView/drag.cpp create mode 100644 tools/ModView/drag.h create mode 100644 tools/ModView/files.cpp create mode 100644 tools/ModView/files.h create mode 100644 tools/ModView/generic_stuff.cpp create mode 100644 tools/ModView/generic_stuff.h create mode 100644 tools/ModView/genericparser.cpp create mode 100644 tools/ModView/genericparser.h create mode 100644 tools/ModView/getstring.cpp create mode 100644 tools/ModView/getstring.h create mode 100644 tools/ModView/gl_bits.cpp create mode 100644 tools/ModView/gl_bits.h create mode 100644 tools/ModView/glm_code.cpp create mode 100644 tools/ModView/glm_code.h create mode 100644 tools/ModView/image.cpp create mode 100644 tools/ModView/image.h create mode 100644 tools/ModView/includes.h create mode 100644 tools/ModView/jpeg6/jcomapi.c create mode 100644 tools/ModView/jpeg6/jconfig.h create mode 100644 tools/ModView/jpeg6/jdapimin.c create mode 100644 tools/ModView/jpeg6/jdapistd.c create mode 100644 tools/ModView/jpeg6/jdatasrc.c create mode 100644 tools/ModView/jpeg6/jdcoefct.c create mode 100644 tools/ModView/jpeg6/jdcolor.c create mode 100644 tools/ModView/jpeg6/jdct.h create mode 100644 tools/ModView/jpeg6/jddctmgr.c create mode 100644 tools/ModView/jpeg6/jdhuff.c create mode 100644 tools/ModView/jpeg6/jdhuff.h create mode 100644 tools/ModView/jpeg6/jdinput.c create mode 100644 tools/ModView/jpeg6/jdmainct.c create mode 100644 tools/ModView/jpeg6/jdmarker.c create mode 100644 tools/ModView/jpeg6/jdmaster.c create mode 100644 tools/ModView/jpeg6/jdpostct.c create mode 100644 tools/ModView/jpeg6/jdsample.c create mode 100644 tools/ModView/jpeg6/jdtrans.c create mode 100644 tools/ModView/jpeg6/jerror.c create mode 100644 tools/ModView/jpeg6/jerror.h create mode 100644 tools/ModView/jpeg6/jidctflt.c create mode 100644 tools/ModView/jpeg6/jinclude.h create mode 100644 tools/ModView/jpeg6/jmemmgr.c create mode 100644 tools/ModView/jpeg6/jmemnobs.c create mode 100644 tools/ModView/jpeg6/jmemsys.h create mode 100644 tools/ModView/jpeg6/jmorecfg.h create mode 100644 tools/ModView/jpeg6/jpegint.h create mode 100644 tools/ModView/jpeg6/jpeglib.h create mode 100644 tools/ModView/jpeg6/jutils.c create mode 100644 tools/ModView/jpeg6/jversion.h create mode 100644 tools/ModView/jpeg_interface.cpp create mode 100644 tools/ModView/jpeg_interface.h create mode 100644 tools/ModView/mainfrm.cpp create mode 100644 tools/ModView/mainfrm.h create mode 100644 tools/ModView/matcomp.cpp create mode 100644 tools/ModView/matcomp.h create mode 100644 tools/ModView/mc_compress2.cpp create mode 100644 tools/ModView/mc_compress2.h create mode 100644 tools/ModView/md3_format.h create mode 100644 tools/ModView/mdr_format.h create mode 100644 tools/ModView/mdx_format.h create mode 100644 tools/ModView/model.cpp create mode 100644 tools/ModView/model.h create mode 100644 tools/ModView/modview.cpp create mode 100644 tools/ModView/modview.h create mode 100644 tools/ModView/modview.rc create mode 100644 tools/ModView/modviewdoc.cpp create mode 100644 tools/ModView/modviewdoc.h create mode 100644 tools/ModView/modviewtreeview.cpp create mode 100644 tools/ModView/modviewtreeview.h create mode 100644 tools/ModView/modviewview.cpp create mode 100644 tools/ModView/modviewview.h create mode 100644 tools/ModView/oldskins.cpp create mode 100644 tools/ModView/oldskins.h create mode 100644 tools/ModView/parser.cpp create mode 100644 tools/ModView/parser.h create mode 100644 tools/ModView/png/png.cpp create mode 100644 tools/ModView/png/png.h create mode 100644 tools/ModView/r_common.h create mode 100644 tools/ModView/r_glm.cpp create mode 100644 tools/ModView/r_glm.h create mode 100644 tools/ModView/r_image.cpp create mode 100644 tools/ModView/r_image.h create mode 100644 tools/ModView/r_md3.cpp create mode 100644 tools/ModView/r_md3.h create mode 100644 tools/ModView/r_mdr.cpp create mode 100644 tools/ModView/r_mdr.h create mode 100644 tools/ModView/r_model.cpp create mode 100644 tools/ModView/r_model.h create mode 100644 tools/ModView/r_surface.cpp create mode 100644 tools/ModView/r_surface.h create mode 100644 tools/ModView/readme.txt create mode 100644 tools/ModView/res/modview.ico create mode 100644 tools/ModView/res/modview.rc2 create mode 100644 tools/ModView/res/modviewdoc.ico create mode 100644 tools/ModView/res/toolbar.bmp create mode 100644 tools/ModView/resource.h create mode 100644 tools/ModView/script.cpp create mode 100644 tools/ModView/script.h create mode 100644 tools/ModView/sequence.cpp create mode 100644 tools/ModView/sequence.h create mode 100644 tools/ModView/shader.cpp create mode 100644 tools/ModView/shader.h create mode 100644 tools/ModView/skins.cpp create mode 100644 tools/ModView/skins.h create mode 100644 tools/ModView/sof2npcviewer.cpp create mode 100644 tools/ModView/sof2npcviewer.h create mode 100644 tools/ModView/special_defines.h create mode 100644 tools/ModView/stdafx.cpp create mode 100644 tools/ModView/stdafx.h create mode 100644 tools/ModView/stl.h create mode 100644 tools/ModView/text.cpp create mode 100644 tools/ModView/text.h create mode 100644 tools/ModView/textures.cpp create mode 100644 tools/ModView/textures.h create mode 100644 tools/ModView/todo.h create mode 100644 tools/ModView/vssver.scc create mode 100644 tools/ModView/wintalk.cpp create mode 100644 tools/ModView/wintalk.h create mode 100644 tools/ModView/zlib/adler32.c create mode 100644 tools/ModView/zlib/crc32.cpp create mode 100644 tools/ModView/zlib/deflate.c create mode 100644 tools/ModView/zlib/deflate.h create mode 100644 tools/ModView/zlib/infblock.c create mode 100644 tools/ModView/zlib/infblock.h create mode 100644 tools/ModView/zlib/infcodes.c create mode 100644 tools/ModView/zlib/infcodes.h create mode 100644 tools/ModView/zlib/inffast.c create mode 100644 tools/ModView/zlib/inffast.h create mode 100644 tools/ModView/zlib/inffixed.h create mode 100644 tools/ModView/zlib/inflate.c create mode 100644 tools/ModView/zlib/inftrees.c create mode 100644 tools/ModView/zlib/inftrees.h create mode 100644 tools/ModView/zlib/infutil.c create mode 100644 tools/ModView/zlib/infutil.h create mode 100644 tools/ModView/zlib/trees.c create mode 100644 tools/ModView/zlib/trees.h create mode 100644 tools/ModView/zlib/zconf.h create mode 100644 tools/ModView/zlib/zlib.h create mode 100644 tools/ModView/zlib/zutil.c create mode 100644 tools/ModView/zlib/zutil.h create mode 100644 tools/_console_arena_list_ create mode 100644 tools/_console_dir_list_ create mode 100644 tools/_console_npc_list_ create mode 100644 tools/_console_sab_list_ create mode 100644 tools/_console_scl_list_ create mode 100644 tools/_console_shader_list_ create mode 100644 tools/_console_skin_list_ create mode 100644 tools/_console_str_list_ create mode 100644 tools/_console_team_list_ create mode 100644 tools/_console_veh_list_ create mode 100644 tools/_console_vwp_list_ create mode 100644 tools/bink_planets.bat create mode 100644 tools/create_soundbank/_console_skin_list_ create mode 100644 tools/create_soundbank/_console_str_list_ create mode 100644 tools/create_soundbank/create_soundbank.sln create mode 100644 tools/create_soundbank/create_soundbank.vcproj create mode 100644 tools/create_soundbank/main.cpp create mode 100644 tools/do_dir_lists.bat create mode 100644 tools/jawa/_console_skin_list_ create mode 100644 tools/jawa/_console_str_list_ create mode 100644 tools/jawa/jawa.cpp create mode 100644 tools/jawa/jawa.sln create mode 100644 tools/jawa/jawa.sln.old create mode 100644 tools/jawa/jawa.suo create mode 100644 tools/jawa/jawa.vcproj create mode 100644 tools/jawa/match.cpp create mode 100644 tools/jawa/match.h create mode 100644 tools/jawa/stdafx.cpp create mode 100644 tools/jawa/stdafx.h create mode 100644 tools/lipsyncthing/_console_skin_list_ create mode 100644 tools/lipsyncthing/_console_str_list_ create mode 100644 tools/lipsyncthing/lipsyncthing.sln create mode 100644 tools/lipsyncthing/lipsyncthing.vcproj create mode 100644 tools/lipsyncthing/main.cpp create mode 100644 tools/lipthing2/_console_skin_list_ create mode 100644 tools/lipthing2/_console_str_list_ create mode 100644 tools/lipthing2/lipthing2.cpp create mode 100644 tools/lipthing2/lipthing2.vcproj create mode 100644 tools/lipthing2/stdafx.cpp create mode 100644 tools/lipthing2/stdafx.h create mode 100644 tools/maptool/_console_skin_list_ create mode 100644 tools/maptool/_console_str_list_ create mode 100644 tools/maptool/maptool.cpp create mode 100644 tools/mp3_wxb.bat create mode 100644 tools/pngtgaTool/TgaWiz.cpp create mode 100644 tools/pngtgaTool/TgaWiz.sln create mode 100644 tools/pngtgaTool/TgaWiz.vcproj create mode 100644 tools/pngtgaTool/_console_skin_list_ create mode 100644 tools/pngtgaTool/_console_str_list_ create mode 100644 tools/pngtgaTool/deflate.cpp create mode 100644 tools/pngtgaTool/deflate.h create mode 100644 tools/pngtgaTool/inflate.cpp create mode 100644 tools/pngtgaTool/inflate.h create mode 100644 tools/pngtgaTool/png.cpp create mode 100644 tools/pngtgaTool/png.h create mode 100644 tools/pngtgaTool/stdafx.cpp create mode 100644 tools/pngtgaTool/stdafx.h create mode 100644 tools/pngtgaTool/tga.cpp create mode 100644 tools/pngtgaTool/zip.h create mode 100644 tools/pngtgaTool/zipcommon.cpp create mode 100644 tools/shader_strip.txt create mode 100644 tools/wav_wxb.bat create mode 100644 tools/wav_wxb2.bat create mode 100644 ui/menudef.h diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser General +Public License instead of this License. diff --git a/README.md b/README.md index fab58af..2129443 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ Jedi-Academy ============ -Star Wars Jedi Knight: Jedi Academy \ No newline at end of file +Final source code for [Star Wars Jedi Knight: Jedi Academy](https://en.wikipedia.org/wiki/Star_Wars_Jedi_Knight:_Jedi_Academy) + +> Activision and Raven are releasing this code for people to learn from and play with. +> This code is copyright Activision 2003. This source is released under GNU GPLv2. + +The initial release can be found [on SourceForge](http://sourceforge.net/projects/jediacademy/files/), posted by [James Monroe](http://www.ravensoft.com/culture/our-people/9/james+monroe/staff-info/). Thanks to Raven Software for making this available to us. + +The code here should be consistent with the released version. The existing CVS meta files have been removed and Git files have been added. + +No further updates to this repository are planned unless updates are posted by Raven Software. Please fork the repository if you want to contribute changes. diff --git a/base/default.cfg b/base/default.cfg new file mode 100644 index 0000000..b7dbcf6 --- /dev/null +++ b/base/default.cfg @@ -0,0 +1,109 @@ +// +// SP JEDI ACADEMY DEFAULT CONFIG +// + +unbindall + +// +// WEAPONS +// +bind 1 "weapon 1" +bind 2 "weapon 2" +bind 3 "weapon 3" +bind 4 "weapon 4" +bind 5 "weapon 5" +bind 6 "weapon 6" +bind 7 "weapon 7" +bind 8 "weapon 8" +bind 9 "weapon 13" +bind 0 "weapon 9" +bind - "weapon 10" +bind = "weapon 0" + +bind \ weapongrabbed +bind [ weapprev +bind ] weapnext +bind mwheelup weapprev +bind mwheeldown weapnext + +// +// CHARACTER CONTROLS +// +bind CTRL +attack +bind ALT +altattack +bind SHIFT +speed +bind v +strafe +bind PGUP +lookup +bind PGDN +lookdown +bind END centerview +bind c +movedown +bind SPACE +moveup +bind ENTER +use +bind r +use + + +bind UPARROW +forward +bind DOWNARROW +back +bind LEFTARROW +left +bind RIGHTARROW +right +bind w +forward +bind a +moveleft +bind s +back +bind d +moveright +bind , +moveleft +bind . +moveright + +// +// FORCE POWERS +// + +bind F1 force_throw +bind F2 force_pull +bind F3 force_speed +bind F4 force_sight + +bind f +useforce +bind e forcenext +bind q forceprev + +bind TAB datapad +bind m datapad +bind p "cg_thirdperson !" +bind l saberAttackCycle + +// +// INVENTORY +// + +// +// QUICK KEYS +// + +//single only +bind F9 "load quick" +bind F10 "uimenu ingameloadmenu" +bind F11 "uimenu ingamesavemenu" +bind F12 "save quick" + +// +// MOUSE OPTIONS +// + +bind / +mlook + +// +// MOUSE BUTTONS +// + +bind MOUSE1 +attack +bind MOUSE2 +altattack +bind MOUSE3 saberAttackCycle + + +// +// CLIENT ENVIRONMENT COMMANDS +// + +bind ~ "toggleconsole" +bind ` "toggleconsole" + diff --git a/base/ext_data/MP/netf_overrides.txt b/base/ext_data/MP/netf_overrides.txt new file mode 100644 index 0000000..68b4138 --- /dev/null +++ b/base/ext_data/MP/netf_overrides.txt @@ -0,0 +1,155 @@ +;rww - this file will allow you to override the number of bits any given +;entityState value is sent across the network in for your mod. Do not +;mess with this unless you know what you're doing as it's easily possible +;to mess something up terribly. Just remove the ; in front of any of the +;values that you want to override and use the desired bit num. 0 is a +;special-case value, it means to send across as a float. GENTITYNUM_BITS +;means to send in as many bits as it takes to send the highest possible +;entity number. +; +;pos.trTime, 32 +;pos.trBase[0], 0 +;pos.trBase[1], 0 +;pos.trDelta[0], 0 +;pos.trDelta[1], 0 +;pos.trBase[2], 0 +;apos.trBase[1], 0 +;pos.trDelta[2], 0 +;apos.trBase[0], 0 +;event, 10 +;angles2[1], 0 +;eType, 8 +;torsoAnim, 16 +;forceFrame, 16 +;eventParm, 8 +;legsAnim, 16 +;torsoFlip, 1 +;legsFlip, 1 +;groundEntityNum, GENTITYNUM_BITS +;pos.trType, 8 +;eFlags, 32 +;bolt1, 8 +;bolt2, GENTITYNUM_BITS +;trickedentindex, 16 +;trickedentindex2, 16 +;trickedentindex3, 16 +;trickedentindex4, 16 +;speed, 0 +;fireflag, 2 +;genericenemyindex, 32 +;activeForcePass, 6 +;emplacedOwner, 32 +;otherEntityNum, GENTITYNUM_BITS +;weapon, 32 +;clientNum, GENTITYNUM_BITS +;angles[1], 0 +;pos.trDuration, 32 +;apos.trType, 8 +;origin[0], 0 +;origin[1], 0 +;origin[2], 0 +;solid, 24 +;owner, GENTITYNUM_BITS +;teamowner, 8 +;shouldtarget, 1 +;powerups, 16 +;modelGhoul2, 8 +;g2radius, 8 +;modelindex, -16 +;otherEntityNum2, GENTITYNUM_BITS +;loopSound, 8 +;loopIsSoundset, 1 +;soundSetIndex, 8 +;generic1, 8 +;origin2[2], 0 +;origin2[0], 0 +;origin2[1], 0 +;modelindex2, 8 +;angles[0], 0 +;time, 32 +;apos.trTime, 32 +;apos.trDuration, 32 +;apos.trBase[2], 0 +;apos.trDelta[0], 0 +;apos.trDelta[1], 0 +;apos.trDelta[2], 0 +;time2, 32 +;angles[2], 0 +;angles2[0], 0 +;angles2[2], 0 +;constantLight, 32 +;frame, 16 +;saberInFlight, 1 +;saberEntityNum, GENTITYNUM_BITS +;saberMove, 8 +;forcePowersActive, 32 +;isJediMaster, 1 +;isPortalEnt, 1 +;heldByClient, 6 +;ragAttach, GENTITYNUM_BITS +;iModelScale, 10 +;brokenLimbs, 8 +;boltToPlayer, 6 +;hasLookTarget, 1 +;lookTarget, GENTITYNUM_BITS +; +;customRGBA[0], 8 +;customRGBA[1], 8 +;customRGBA[2], 8 +;customRGBA[3], 8 +; +;health, 10 +;maxhealth, 10 +; +;npcSaber1, 9 +;npcSaber2, 9 +; +;csSounds_Std, 8 +;csSounds_Combat, 8 +;csSounds_Extra, 8 +;csSounds_Jedi, 8 +; +;surfacesOn, 32 +;surfacesOff, 32 +; +;boneIndex1, 6 +;boneIndex2, 6 +;boneIndex3, 6 +;boneIndex4, 6 +; +;boneOrient, 9 +; +;boneAngles1[0], 0 +;boneAngles1[1], 0 +;boneAngles1[2], 0 +; +;boneAngles2[0], 0 +;boneAngles2[1], 0 +;boneAngles2[2], 0 +; +;boneAngles3[0], 0 +;boneAngles3[1], 0 +;boneAngles3[2], 0 +; +;boneAngles4[0], 0 +;boneAngles4[1], 0 +;boneAngles4[2], 0 +; +;NPC_class, 8 +; +;m_iVehicleNum, 10 +; +;eFlags2,10 +; +;userInt1, 1 +;userInt2, 1 +;userInt3, 1 +;userFloat1, 1 +;userFloat2, 1 +;userFloat3, 1 +;userVec1[0], 1 +;userVec1[1], 1 +;userVec1[2], 1 +;userVec2[0], 1 +;userVec2[1], 1 +;userVec2[2], 1 diff --git a/base/ext_data/MP/psf_overrides.txt b/base/ext_data/MP/psf_overrides.txt new file mode 100644 index 0000000..dd58fcc --- /dev/null +++ b/base/ext_data/MP/psf_overrides.txt @@ -0,0 +1,181 @@ +;rww - this file will allow you to override the number of bits any given +;playerState value is sent across the network in for your mod. Do not +;mess with this unless you know what you're doing as it's easily possible +;to mess something up terribly. Just remove the ; in front of any of the +;values that you want to override and use the desired bit num. 0 is a +;special-case value, it means to send across as a float. GENTITYNUM_BITS +;means to send in as many bits as it takes to send the highest possible +;entity number. +; +;commandTime, 32 +;origin[0], 0 +;origin[1], 0 +;bobCycle, 8 +;velocity[0], 0 +;velocity[1], 0 +;viewangles[1], 0 +;viewangles[0], 0 +; +;weaponTime, -16 +;weaponChargeTime, 32 +;weaponChargeSubtractTime, 32 +;origin[2], 0 +;velocity[2], 0 +;pm_time, -16 +;eventSequence, 16 +;torsoAnim, 16 +;torsoTimer, 16 +;legsAnim, 16 +;legsTimer, 16 +;legsFlip, 1 +;torsoFlip, 1 +;movementDir, 4 +;events[0], 10 +;events[1], 10 +;pm_flags, 16 +;groundEntityNum, GENTITYNUM_BITS +;weaponstate, 4 +;eFlags, 32 +;externalEvent, 10 +;gravity, 16 +;speed, -16 +;basespeed, -16 +;delta_angles[1], 16 +;externalEventParm, 8 +;viewheight, -8 +;damageEvent, 8 +;damageYaw, 8 +;damagePitch, 8 +;damageCount, 8 +;damageType, 2 +;generic1, 8 +;pm_type, 8 +;delta_angles[0], 16 +;delta_angles[2], 16 +;eventParms[0], -16 +;eventParms[1], 8 +;clientNum, GENTITYNUM_BITS +;weapon, 8 +;viewangles[2], 0 +; +;jumppad_ent, 10 +;loopSound, 16 +;loopIsSoundset, 1 +; +;zoomMode, 2 +;zoomTime, 32 +;zoomLocked, 1 +;zoomFov, 0 +; +;fd.forcePowersActive, 32 +;fd.forceMindtrickTargetIndex, 16 +;fd.forceMindtrickTargetIndex2, 16 +;fd.forceMindtrickTargetIndex3, 16 +;fd.forceMindtrickTargetIndex4, 16 +;fd.forceJumpZStart, 0 +;fd.forcePowerSelected, 8 +;fd.forcePowersKnown, 32 +;fd.forcePower, 8 +;fd.forceSide, 2 +;fd.sentryDeployed, 1 +;fd.forcePowerLevel[FP_LEVITATION], 2 +;fd.forcePowerLevel[FP_SEE], 2 +;fd.forceGripCripple, 1 +;genericEnemyIndex, 32 +;activeForcePass, 6 +;hasDetPackPlanted, 1 +;emplacedIndex, GENTITYNUM_BITS +;fd.forceRageRecoveryTime, 32 +;rocketLockIndex, GENTITYNUM_BITS +;rocketLockTime, 32 +;rocketTargetTime, 32 +;holocronBits, 32 +;isJediMaster, 1 +;forceRestricted, 1 +;trueJedi, 1 +;trueNonJedi, 1 +;fallingToDeath, 32 +;electrifyTime, 32 +; +;fd.forcePowerDebounce[FP_LEVITATION], 32 +; +;saberMove, 32 +;saberActive, 1 +;saberInFlight, 1 +;saberBlocked, 8 +;saberEntityNum, GENTITYNUM_BITS +;saberCanThrow, 1 +;forceHandExtend, 8 +;forceDodgeAnim, 16 +;fd.saberAnimLevel, 4 +;fd.saberDrawAnimLevel, 4 +;saberAttackChainCount, 4 +;saberHolstered, 1 +; +;jetpackFuel, 8 +;cloakFuel, 8 +; +;duelIndex, GENTITYNUM_BITS +;duelTime, 32 +;duelInProgress, 1 +; +;saberLockTime, 32 +;saberLockEnemy, GENTITYNUM_BITS +;saberLockFrame, 16 +;saberLockAdvance, 1 +; +;inAirAnim, 1 +; +;lastHitLoc[2], 0 +;lastHitLoc[0], 0 +;lastHitLoc[1], 0 +; +;heldByClient, 6 +;ragAttach, GENTITYNUM_BITS +;iModelScale, 10 +;brokenLimbs, 8 +;hasLookTarget, 1 +;lookTarget, GENTITYNUM_BITS +; +;customRGBA[0], 8 +;customRGBA[1], 8 +;customRGBA[2], 8 +;customRGBA[3], 8 +; +;standheight, 10 +;crouchheight, 10 +; +;m_iVehicleNum, GENTITYNUM_BITS +; +;vehOrientation[0], 0 +;vehOrientation[1], 0 +;vehOrientation[2], 0 +; +;vehSurfaces, 16 +; +;vehTurnaroundIndex, GENTITYNUM_BITS +;vehTurnaroundTime, 32 +; +;moveDir[0], 0 +;moveDir[1], 0 +;moveDir[2], 0 +; +;vehBoarding, 1 +; +;hackingTime, 32 +;hackingBaseTime, 16 +; +;eFlags2,10 +; +;userInt1, 1 +;userInt2, 1 +;userInt3, 1 +;userFloat1, 1 +;userFloat2, 1 +;userFloat3, 1 +;userVec1[0], 1 +;userVec1[1], 1 +;userVec1[2], 1 +;userVec2[0], 1 +;userVec2[1], 1 +;userVec2[2], 1 diff --git a/base/ext_data/MP/vssver.scc b/base/ext_data/MP/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..f17041417a551f24db88553a33561c68d7e07468 GIT binary patch literal 64 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiWQe=b?bCBgs(+(7!n{WQf-+?$s&14Vd%{KZ02 Mo@~l{mU0650J#McmjD0& literal 0 HcmV?d00001 diff --git a/base/ext_data/dms.dat b/base/ext_data/dms.dat new file mode 100644 index 0000000..8f50651 --- /dev/null +++ b/base/ext_data/dms.dat @@ -0,0 +1,1757 @@ +musicfiles +{ + kejimbase_explore + { + entry + { + marker0 0.000 + marker1 22.070 + marker2 53.723 + marker3 90.926 + } + exit + { + nextfile kejimbase_etr00 + time00 3.337 + time01 5.668 + time02 22.040 + time03 29.889 + time04 50.124 + time05 37.168 + time06 53.473 + time07 65.923 + time08 90.478 + time09 143.233 + time10 156.166 + time11 176.608 + time12 193.505 + } + exit + { + nextfile kejimbase_etr01 + time00 45.555 + time01 81.525 + time02 106.406 + time03 128.648 + time04 185.168 + time05 209.309 + } + } + kejimbase_action + { + entry + { + marker0 0.00 + marker1 42.585 + marker2 87.664 + } + exit + { + nextfile kejimbase_atr00 + nextmark marker0 + time00 69.347 + time01 72.142 + time02 80.444 + time03 87.608 + time04 90.185 + } + exit + { + nextfile kejimbase_atr01 + nextmark marker1 + time00 2.434 + time01 5.667 + time02 20.281 + } + exit + { + nextfile kejimbase_atr02 + nextmark marker2 + time00 62.894 + } + exit + { + nextfile kejimbase_atr03 + nextmark marker3 + time00 12.397 + time01 28.679 + time02 35.492 + time03 45.328 + } + } + ImpBaseB_Explore + { + entry + { + marker0 0 + marker1 37 + marker2 69.81 + marker3 119.97 + } + exit + { + nextfile ImpBaseB_Etr00 + time0 37 + time1 54.0 + time2 62.35 + time3 69.81 + time4 79.85 + time5 119.97 + time6 132.75 + time7 146.88 + } + exit + { + nextfile ImpBaseB_Etr01 + time0 13.67 + time1 26.96 + time2 89.42 + time3 96.92 + time4 107.77 + } + + } + ImpBaseB_Action + { + entry + { + marker0 0 + marker1 30.23 + marker2 45.45 + marker3 104.48 + } + exit + { + nextfile ImpBaseB_Atr00 + nextmark marker3 + time0 38.22 + time1 50.31 + time2 59.23 + time3 64.47 + time4 80.41 + time5 87.69 + time6 92.01 + time7 98.07 + time8 104.48 + } + exit + { + nextfile ImpBaseB_Atr01 + nextmark marker2 + time0 8.91 + time1 20.89 + } + exit + { + nextfile ImpBaseB_Atr02 + nextmark marker1 + time0 25.45 + time1 30.23 + } + exit + { + nextfile ImpBaseB_Atr03 + nextmark marker0 + time0 4.97 + time1 11.33 + time2 16.11 + time3 45.45 + time4 70.61 + time5 74.66 + } + } + ImpBaseC_explore + { + entry + { + marker0 0.000 + marker1 55.831 + marker2 11.160 + marker3 11.160 + + } + exit + { + nextfile ImpBaseC_etr00 + time00 42.904 + time01 71.172 + time02 127.721 + time03 150.290 + time04 171.618 + + } + exit + { + nextfile ImpBaseC_etr01 + time00 19.096 + time01 26.412 + time02 88.211 + time03 101.169 + + } + } + ImpBaseC_action + { + entry + { + marker0 0.00 + marker1 19.468 + marker2 33.480 + } + exit + { + nextfile ImpBaseC_atr00 + nextmark marker0 + time00 2.542 + time01 5.440 + time02 9.470 + time03 19.366 + } + exit + { + nextfile ImpBaseC_atr01 + nextmark marker1 + time00 25.544 + time01 29.140 + } + exit + { + nextfile ImpBaseC_atr02 + nextmark marker2 + time00 44.888 + } + exit + { + nextfile ImpBaseC_atr03 + nextmark marker3 + time00 34.272 + time01 65.224 + } + } + BespinA_Explore + { + entry + { + marker0 6.74 + marker1 17.51 + marker2 95.95 + marker3 149.87 + } + exit + { + nextfile BespinA_Etr00 + time00 42.01 + } + exit + { + nextfile BespinA_Etr01 + time00 165.65 + } + exit + { + nextfile BespinA_Etr02 + time00 17.51 + time01 29.50 + time02 54.80 + time03 70.21 + time04 90.25 + time05 106.40 + time06 120.53 + time07 149.87 + time08 178.36 + } + } + BespinA_Action + { + entry + { + marker0 0.00 + marker1 42.585 + marker2 87.664 + } + exit + { + nextfile BespinA_Atr00 + nextmark marker0 + time00 3.08 + time01 6.19 + time02 35.26 + } + exit + { + nextfile BespinA_Atr01 + nextmark marker1 + time00 8.98 + time01 14.76 + time02 20.65 + time03 44.34 + time04 47.28 + } + exit + { + nextfile BespinA_Atr02 + nextmark marker2 + time00 51.82 + time01 58.00 + } + exit + { + nextfile BespinA_Atr03 + nextmark marker3 + time00 28.90 + time01 64.78 + } + } + + besplat_explore + { + entry + { + marker0 46.78 + marker1 69.05 + marker2 85.85 + } + exit + { + nextfile besplat_etr00 + time0 6.89 + time1 21.26 + time3 55.52 + time4 69.75 + time5 85.87 + time6 98.12 + time7 119.44 + time8 131.95 + time9 157.58 + } + exit + { + nextfile besplat_etr01 + time0 34.15 + time1 141.84 + time2 174.72 + time3 183.05 + } + } + + besplat_action + { + entry + { + marker0 0 + marker1 16.56 + marker2 24.86 + marker3 62.37 + } + exit + { + nextfile besplat_atr00 + nextmark marker1 + time0 2.82 + time1 68.46 + } + exit + { + nextfile besplat_atr01 + nextmark marker2 + time0 41.23 + time1 54.20 + time2 59.31 + time3 76.51 + } + exit + { + nextfile besplat_atr02 + nextmark marker0 + time0 13.47 + time1 21.19 + time2 24.65 + time3 28.82 + time4 83.53 + } + } + + besplat_boss + { + } + + yavtrial_explore + { + entry + { + marker0 124.126 + marker1 102.326 + marker2 24.853 + marker3 0.00 + } + exit + { + nextfile yavtrial_etr00 + time00 3.095 + time01 23.931 + time02 24.937 + time03 46.905 + time04 61.268 + time05 79.042 + time06 101.750 + time07 118.136 + time08 147.951 + } + exit + { + nextfile yavtrial_etr01 + time00 161.783 + } + } + yavtrial_action + { + entry + { + marker0 0.00 + marker1 60.145 + marker2 85.922 + } + exit + { + nextfile yavtrial_atr00 + nextmark marker0 + time00 3.553 + time01 6.118 + time02 8.718 + time03 41.152 + time04 103.559 + } + exit + { + nextfile yavtrial_atr01 + nextmark marker1 + time00 9.307 + time01 12.310 + time02 16.719 + time03 20.045 + time04 26.229 + time05 35.160 + } + exit + { + nextfile yavtrial_atr02 + nextmark marker2 + time00 59.302 + time01 65.837 + time02 74.429 + } + exit + { + nextfile yavtrial_atr03 + nextmark marker3 + time00 38.325 + time01 49.857 + } + } + alienha_explore + { + entry + { + marker0 0 + marker1 65.84 + marker2 93.63 + } + exit + { + nextfile alienha_etr00 + time0 5.80 + time1 33.04 + time3 67.61 + } + exit + { + nextfile alienha_etr01 + time0 94.47 + time1 103.06 + time2 115.75 + time3 127.32 + time4 138.36 + time5 152.10 + time6 166.11 + time7 180.29 + } + } + alienha_action + { + entry + { + marker0 0 + marker1 40.96 + marker2 63.33 + } + exit + { + nextfile alienha_atr00 + nextmark marker2 + time0 3.35 + time1 6.85 + time3 15.70 + time4 22.61 + } + exit + { + nextfile alienha_atr01 + nextmark marker0 + time0 31.85 + time1 41.42 + time2 47.06 + } + exit + { + nextfile alienha_atr03 + nextmark marker1 + time0 53.01 + time1 58.57 + time2 62.87 + time3 72.03 + time4 89.64 + time5 96.94 + } + } + tunnels_explore + { + entry + { + marker0 0 + marker1 64.20 + marker2 96.47 + } + exit + { + nextfile tunnels_etr00 + time0 6.06 + time1 18.26 + time3 35.01 + time4 42.98 + time5 69.73 + time6 84.16 + } + exit + { + nextfile tunnels_etr01 + time0 94.81 + time1 111.25 + time2 121.77 + time3 134.68 + } + } + tunnels_action + { + entry + { + marker0 0 + marker1 22.39 + } + exit + { + nextfile tunnels_atr00 + nextmark marker0 + time0 0.29 + } + exit + { + nextfile tunnels_atr01 + nextmark marker2 + time0 15.62 + time1 22.94 + } + exit + { + nextfile tunnels_atr02 + nextmark marker2 + time0 29.14 + time1 35.63 + time2 45.33 + time3 51.78 + time4 58.67 + } + exit + { + nextfile tunnels_atr03 + nextmark marker1 + time0 64.81 + time1 68.53 + time2 72.28 + time3 75.89 + } + } + IMPBaseD_explore + { + entry + { + marker0 0.000 + marker1 66.790 + marker2 102.874 + marker3 150.554 + } + exit + { + nextfile IMPBaseD_etr00 + time00 7.997 + time01 16.678 + time02 44.664 + time03 70.836 + + } + exit + { + nextfile IMPBaseD_etr01 + time00 89.986 + time01 111.971 + time02 130.629 + time03 166.389 + time04 172.530 + } + } + IMPBaseD_action + { + entry + { + marker0 6.607 + marker1 60.118 + marker2 140.053 + } + exit + { + nextfile IMPBaseD_atr00 + nextmark marker0 + time00 6.457 + time01 13.265 + time02 18.757 + time03 25.194 + time04 152.772 + } + exit + { + nextfile IMPBaseD_atr01 + nextmark marker1 + time00 30.336 + time01 37.883 + time02 46.802 + time03 61.122 + } + exit + { + nextfile IMPBaseD_atr02 + nextmark marker2 + time00 78.257 + time01 85.312 + time02 92.170 + time03 140.866 + time04 149.597 + } + exit + { + nextfile IMPBaseD_atr03 + nextmark marker3 + time00 105.982 + time01 115.569 + time02 128.476 + } + } + swamp_explore + { + entry + { + marker0 0.000 + marker1 16.916 + marker2 80.714 + marker3 31.761 + + } + exit + { + nextfile swamp_etr00 + time00 11.185 + time01 20.989 + time02 51.408 + time03 63.196 + time04 71.293 + + } + exit + { + nextfile swamp_etr01 + time00 42.044 + time01 78.362 + time02 95.485 + time03 113.023 + + } + } + swamp_action + { + entry + { + marker0 0.00 + marker1 36.318 + marker2 45.982 + } + exit + { + nextfile swamp_atr00 + nextmark marker0 + time00 1.035 + } + exit + { + nextfile swamp_atr01 + nextmark marker1 + time00 6.835 + time01 11.323 + time02 18.592 + time03 36.230 + time04 59.793 + } + exit + { + nextfile swamp_atr02 + nextmark marker2 + time00 34.108 + time01 79.955 + time02 89.847 + time03 126.994 + + } + exit + { + nextfile swamp_atr03 + nextmark marker3 + time00 102.414 + time01 115.382 + time02 120.039 + } + } + yavtemp2_explore + { + entry + { + marker0 88.28 + marker1 48.10 + marker2 117.47 + marker3 0 + } + exit + { + nextfile yavtemp2_etr00 + time0 46.23 + time1 53.44 + time2 62.08 + time3 69.09 + time4 77.08 + time5 87.03 + time6 96.49 + } + exit + { + nextfile yavtemp2_etr01 + time0 15.24 + time1 31.79 + time2 42.78 + time3 114.18 + time4 134.25 + time5 144.89 + time6 160.03 + time7 168.03 + } + } + yavtemp2_action + { + entry + { + marker0 0 + marker1 17.35 + marker2 43.31 + marker3 60.39 + } + exit + { + nextfile yavtemp2_atr00 + nextmark marker0 + time0 0 + time1 8.78 + time2 13.47 + } + exit + { + nextfile yavtemp2_atr01 + nextmark marker1 + time0 23.89 + time1 28.62 + time2 41.11 + time3 46.73 + } + exit + { + nextfile yavtemp2_atr02 + nextmark marker2 + time0 48.95 + time1 60.59 + time2 69.13 + time3 97.63 + } + exit + { + nextfile yavtemp2_atr03 + nextmark marker3 + time0 91.11 + } + } + ImpBaseE_explore + { + entry + { + marker0 0.000 + marker1 13.805 + marker2 29.265 + marker3 137.915 + + } + exit + { + nextfile ImpBaseE_etr00 + time00 13.712 + time01 37.872 + time02 52.541 + time03 131.875 + time04 232.256 + + } + exit + { + nextfile ImpBaseE_etr01 + time00 120.057 + time01 157.080 + time02 176.388 + + } + } + ImpBaseE_action + { + entry + { + marker0 0.00 + marker1 18.336 + marker2 52.491 + } + exit + { + nextfile ImpBaseE_atr00 + nextmark marker0 + time00 22.012 + time01 32.861 + time02 73.853 + time03 77.808 + } + exit + { + nextfile ImpBaseE_atr01 + nextmark marker1 + time00 5.824 + time01 18.457 + time02 81.088 + time03 114.805 + } + exit + { + nextfile ImpBaseE_atr02 + nextmark marker2 + time00 1.923 + time01 26.892 + time02 30.703 + time03 59.294 + time04 65.331 + time05 88.803 + time06 91.876 + + } + exit + { + nextfile ImpBaseE_atr03 + nextmark marker3 + time00 11.958 + time01 43.027 + time02 48.014 + time04 96.757 + time05 107.858 + time06 130.437 + } + } + alienhb_explore + { + entry + { + marker0 127.71 + marker1 36.56 + marker2 89.94 + } + exit + { + nextfile alienhb_etr00 + time0 13.60 + time1 22.58 + time3 31.69 + time4 41.08 + time5 53.08 + time6 67.91 + time7 89.29 + time8 181.41 + } + exit + { + nextfile alienhb_etr01 + time0 109.08 + time1 123.18 + time2 134.76 + time3 149.54 + time4 160.53 + time5 174.10 + } + } + alienhb_action + { + entry + { + marker0 0 + marker1 6.85 + marker2 31.84 + marker3 66.77 + } + exit + { + nextfile alienhb_atr00 + nextmark marker0 + time0 5.29 + time1 21.54 + time3 26.21 + time4 120.30 + } + exit + { + nextfile alienhb_atr01 + nextmark marker1 + time0 10.35 + time1 17.03 + } + exit + { + nextfile alienhb_atr02 + nextmark marker2 + time0 60.27 + time1 70.21 + time2 80.36 + time3 93.55 + time4 100.47 + time5 111.75 + } + } + yavfinal_explore + { + entry + { + marker0 0.000 + marker1 18.664 + marker2 53.390 + marker3 97.161 + + } + exit + { + nextfile yavfinal_etr00 + time00 53.434 + time01 83.935 + + } + exit + { + nextfile yavfinal_etr01 + time00 9.852 + time01 94.171 + time02 104.106 + + } + } + yavfinal_action + { + entry + { + marker0 0.00 + marker1 43.094 + marker2 73.785 + } + exit + { + nextfile yavfinal_atr00 + nextmark marker0 + time00 53.502 + time01 61.386 + } + exit + { + nextfile yavfinal_atr01 + nextmark marker1 + time00 3.623 + time01 96.341 + } + exit + { + nextfile yavfinal_atr02 + nextmark marker2 + time00 9.019 + time01 11.707 + time02 15.535 + time03 29.107 + time04 35.847 + time05 75.327 + time06 101.992 + + } + exit + { + nextfile yavfinal_atr03 + nextmark marker3 + time00 21.257 + time01 25.167 + time02 43.800 + time03 106.249 + time04 113.638 + + } + } + yavfinal_boss + { + } + narshaada_explore + { + entry + { + marker0 123.18 + marker1 0 + marker2 43.30 + marker3 12.27 + } + exit + { + nextfile narshaada_etr00 + time0 55.86 + time1 70.17 + time2 78.63 + time3 88.61 + time4 106.57 + + } + exit + { + nextfile narshaada_etr01 + time0 13.75 + time1 31.29 + time2 43.84 + time4 123.91 + time5 134.92 + time6 149.29 + time7 168.16 + time8 184.19 + } + } + narshaada_action + { + entry + { + marker0 0 + marker1 15.65 + marker2 47.07 + } + exit + { + nextfile narshaada_atr00 + nextmark marker0 + time0 2.72 + time1 59.84 + time2 63.90 + time3 74.16 + time4 82.37 + } + exit + { + nextfile narshaada_atr01 + nextmark marker1 + time0 10.89 + time1 20.59 + time2 28.15 + time3 39.65 + time4 46.35 + } + exit + { + nextfile narshaada_atr02 + nextmark marker2 + time0 48.98 + time1 69.05 + } + exit + { + nextfile narshaada_atr03 + nextmark marker3 + time0 86.14 + time1 90.73 + time2 96.40 + time3 101.09 + } + } + tusken_explore + { + entry + { + marker0 0.000 + marker1 10.583 + marker2 71.039 + marker3 135.541 + } + exit + { + nextfile tusken_etr00 + time00 10.861 + time01 71.150 + time02 90.767 + time03 120.357 + } + exit + { + nextfile tusken_etr01 + time00 37.902 + time01 54.526 + time02 83.563 + } + } + tusken_action + { + entry + { + marker0 0.00 + marker1 12.897 + marker2 43.0 + marker3 91.709 + } + exit + { + nextfile tusken_atr00 + nextmark marker0 + time00 6.123 + time01 8.034 + time02 16.540 + time03 23.633 + time04 104.980 + } + exit + { + nextfile tusken_atr01 + nextmark marker1 + time00 57.368 + time01 63.503 + time02 66.717 + time03 77.926 + time04 90.472 + time05 93.797 + time06 98.102 + time07 108.327 + } + exit + { + nextfile tusken_atr02 + nextmark marker2 + time00 32.583 + } + exit + { + nextfile tusken_atr03 + nextmark marker3 + time00 31.374 + time01 40.811 + time02 43.581 + time03 46.745 + time04 53.173 + time05 71.815 + time06 74.780 + time07 82.316 + time08 87.553 + } + } + hoth2_explore + { + entry + { + marker0 0 + marker1 43.374 + marker2 101.295 + marker3 128 + } + exit + { + nextfile hoth2_etr00 + time00 0.6 + time01 68.013 + time02 102.813 + time03 140.937 + } + exit + { + nextfile hoth2_etr01 + time00 39.663 + time01 53.196 + time02 96.547 + time03 119.562 + time04 142.523 + } + + } + hoth2_action + { + entry + { + marker0 0 + marker1 27.263 + marker2 87.276 + marker3 117.919 + } + exit + { + nextfile hoth2_atr00 + nextmark marker0 + time00 7.092 + time01 14.241 + time02 20.347 + time03 67.703 + time04 69.517 + time05 71.844 + time06 75.125 + time07 78 + time08 82.965 + time09 129.589 + time10 158.426 + } + exit + { + nextfile hoth2_atr01 + nextmark marker1 + time00 25.993 + time01 29.983 + time02 35.6 + time03 38.318 + time04 40.995 + time05 42.335 + time06 52.587 + time07 57.297 + time08 60.622 + time09 62.617 + time10 84.422 + time11 96.308 + time12 98.358 + time13 102.459 + time14 104.409 + time15 109.8 + time16 118.721 + time17 141.128 + time18 145.861 + time19 150.9 + } + exit + { + nextfile hoth2_atr02 + nextmark marker2 + time00 32.915 + + } + exit + { + nextfile hoth2_atr03 + nextmark marker3 + time00 42.924 + time01 46.027 + time02 48.611 + time03 50.384 + time04 52.047 + time05 86.486 + time06 90.032 + time07 94.786 + time08 124.69 + time09 153.883 + } + } + dealsour_explore + { + entry + { + marker0 0.000 + marker1 100.492 + marker2 126.154 + + } + exit + { + nextfile dealsour_etr00 + time00 5.153 + time01 26.487 + time02 64.556 + time03 93.205 + time04 108.166 + time05 138.145 + } + exit + { + nextfile dealsour_etr01 + time00 9.143 + time01 21.223 + time02 43.388 + time03 87.830 + time04 116.589 + } + } + dealsour_action + { + entry + { + marker0 0.00 + marker1 65.166 + marker2 100.575 + } + exit + { + nextfile dealsour_atr00 + nextmark marker0 + time00 4.765 + time01 17.278 + time02 26.365 + time03 53.352 + time04 57.175 + time05 82.776 + } + exit + { + nextfile dealsour_atr01 + nextmark marker1 + time00 13.321 + time01 91.953 + time02 104.975 + } + exit + { + nextfile dealsour_atr02 + nextmark marker2 + time00 36.351 + time01 44.386 + time02 59.624 + time03 70.075 + time04 79.435 + time05 95.511 + } + } + rancor_explore + { + entry + { + marker0 0 + marker1 46.103 + marker2 88.328 + + } + exit + { + nextfile rancor_etr00 + time00 0.775 + time01 5.319 + time02 16.291 + time03 43.222 + time04 49.539 + time05 63.725 + time06 84.0 + time07 103.623 + + + } + exit + { + nextfile rancor_etr01 + time00 10.08 + time01 26.820 + time02 51.645 + time03 68.213 + time04 129.279 + } + + } + rancor_action + { + entry + { + marker0 0 + marker1 54.928 + marker2 88.315 + } + exit + { + nextfile rancor_atr00 + nextmark marker0 + time00 0.05 + time01 2.737 + time02 7.918 + time03 10.079 + time04 84.256 + time05 90.440 + time06 95.759 + time07 105.562 + time08 105.562 + time09 111.159 + time10 113.985 + time11 118.03 + } + exit + { + nextfile rancor_atr01 + nextmark marker1 + time00 8.921 + time01 24.975 + time02 31.458 + time03 35.586 + time04 38.523 + time05 56.3 + time06 60 + time07 65.549 + time08 73.223 + time09 77.467 + time10 102.791 + time11 123.9 + time12 141.193 + time13 144.02 + time14 146.296 + time15 151.234 + time16 155.057 + time17 157.329 + } + exit + { + nextfile rancor_atr02 + nextmark marker2 + time00 5.098 + time01 9.309 + time02 19.508 + time03 74.323 + time04 80.898 + time05 86.123 + time06 99.754 + time07 132.448 + time08 136.504 + time09 148.230 + time10 161.263 + } + } + korrib_lite_explore + { + entry + { + marker0 0.000 + marker1 20.170 + marker2 51.645 + marker3 73.201 + } + exit + { + nextfile korrib_lite_etr00 + time00 40.866 + time01 55.635 + time02 68.222 + } + exit + { + nextfile korrib_lite_etr01 + time00 0.200 + time01 15.472 + time02 77.914 + time03 80.977 + } + } + korrib_dark_explore + { + entry + { + marker0 0.000 + marker1 56.300 + marker2 81.901 + marker3 97.970 + } + exit + { + nextfile korrib_dark_etr00 + time00 0.100 + time01 55.719 + time02 69.607 + time03 105.397 + } + exit + { + nextfile korrib_dark_etr01 + time00 29.202 + time01 82.004 + } + } + korrib_action + { + entry + { + marker0 0.00 + marker1 41.495 + marker2 63.586 + marker3 96.124 + } + exit + { + nextfile korrib_atr00 + nextmark marker0 + time00 0.100 + time01 2.769 + time02 7.929 + time03 35.142 + time04 41.429 + time05 66.838 + time06 101.731 + time07 115.125 + } + exit + { + nextfile korrib_atr01 + nextmark marker1 + time00 10.161 + time01 31.271 + time02 33.499 + time03 45.925 + time04 48.117 + time05 53.043 + time06 63.397 + time07 105.512 + time08 108.174 + } + exit + { + nextfile korrib_atr02 + nextmark marker2 + time00 6.125 + time01 15.027 + time02 18.225 + time03 38.181 + time04 59.245 + time05 76.386 + time06 80.120 + time07 92.516 + } + exit + { + nextfile korrib_atr03 + nextmark marker3 + time00 20.844 + time01 24.075 + time02 74.372 + time03 85.475 + time04 89.990 + time05 117.480 + } + } + final_battle + { + } + vjun3_explore + { + + entry + { + marker0 0.000 + marker1 41.670 + marker2 146.402 + } + exit + { + nextfile vjun3_etr00 + time00 132.271 + time01 144.739 + } + exit + { + nextfile vjun3_etr01 + time00 13.138 + time01 25.994 + time02 85.175 + } + } + vjun3_action + { + entry + { + marker0 0.00 + marker1 8.256 + marker2 65.554 + } + exit + { + nextfile vjun3_atr00 + nextmark marker0 + time00 7.785 + time01 32.66 + time02 47.516 + time03 51.710 + time04 65.674 + time05 87.719 + time06 105.684 + time07 119.798 + time08 125.561 + time09 128.004 + } + exit + { + nextfile vjun3_atr01 + nextmark marker1 + time00 13.243 + time01 20.037 + time02 23.944 + time03 36.96 + time04 39.62 + time05 54.14 + time06 57.372 + time07 81.623 + time08 99.91 + } + exit + { + nextfile vjun3_atr02 + nextmark marker2 + time00 2.271 + time01 15.017 + time02 28.210 + time03 42.557 + time04 45.80 + time05 69.571 + time06 73.505 + time07 93.482 + time08 115.814 + } + } +} +levelmusic +{ + yavin1 + { + explore swamp_explore + action swamp_action + } + yavin1b + { + uses yavin1 + } + yavin2 + { + explore yavtemp2_explore + action yavtemp2_action + } + t1_fatal + { + explore tunnels_explore + action tunnels_action + } + t1_sour + { + explore dealsour_explore + action dealsour_action + } + t1_surprise + { + explore tusken_explore + action tusken_action + } + t1_danger + { + uses t1_surprise + } + hoth2 + { + explore hoth2_explore + action hoth2_action + } + hoth3 + { + uses hoth2 + } + t2_dpred + { + explore ImpBaseB_Explore + action ImpBaseB_Action + } + t2_wedge + { + explore besplat_explore + action besplat_action + boss besplat_boss + } + t2_rancor + { + explore rancor_explore + action rancor_action + } + t2_rogue + { + explore narshaada_explore + action narshaada_action + } + vjun1 + { + uses yavin2 + } + vjun2 + { + explore ImpBaseE_explore + action ImpBaseE_action + } + vjun3 + { + explore vjun3_explore + action vjun3_action + } + t3_bounty + { + uses t1_sour + } + t3_byss + { + explore alienhb_explore + action alienhb_action + } + t3_hevil + { + uses t2_wedge + } + t3_rift + { + uses yavin2 + } + taspir1 + { + uses vjun2 + } + taspir2 + { + uses vjun2 + } + kor_lite + { + explore korrib_lite_explore + action korrib_action + boss final_battle + } + kor_dark + { + explore korrib_dark_explore + action korrib_action + boss final_battle + } +} diff --git a/base/ext_data/items.dat b/base/ext_data/items.dat new file mode 100644 index 0000000..28daaf8 --- /dev/null +++ b/base/ext_data/items.dat @@ -0,0 +1,782 @@ +// EXTERNAL ITEM DATA +// + +//Fields +//pickupsound STRING; DEFAULT = sound/weapons/w_pkup.wav +//itemname STRING; +//classname STRING; +//count INT; ammount of ammo or health given with item +//icon STRING; +//min VECTOR; item bounds min, DEFAULT = -16 -16 -2 +//max VECTOR; item bounds max, DEFAULT = 16 16 16 +//pickupname STRING; name to show in inventory +//tag ENUM; WP_, or AMMO_ +//type ENUM; IT_WEAPON, IT_AMMO, IT_ARMOR, IT_HEALTH +//worldmodel STRING; model to show on ground or in hand + +{ +itemname ITM_SABER_PICKUP + +classname weapon_saber +worldmodel models/weapons2/saber/saber_w.md3 +icon gfx/hud/w_icon_lightsaber +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_SABER +min -16 -16 -8 +max 16 16 16 +} + + +{ +itemname ITM_BRYAR_PISTOL_PICKUP + +classname weapon_bryar_pistol +worldmodel models/weapons2/briar_pistol/briar_pistol_w.glm +icon gfx/hud/w_icon_briar +// Amount of ammo given with weapon +count 10 +type IT_WEAPON +tag WP_BRYAR_PISTOL +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_BLASTER_PISTOL_PICKUP + +classname weapon_blaster_pistol +worldmodel models/weapons2/blaster_pistol/blaster_pistol_w.glm +icon gfx/hud/w_icon_blaster_pistol +// Amount of ammo given with weapon +count 10 +type IT_WEAPON +tag WP_BLASTER_PISTOL +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_BLASTER_PICKUP + +classname weapon_blaster +worldmodel models/weapons2/blaster_r/blaster_w.glm +icon gfx/hud/w_icon_blaster +// Amount of ammo given with weapon +count 10 +type IT_WEAPON +tag WP_BLASTER +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_DISRUPTOR_PICKUP + +classname weapon_disruptor +worldmodel models/weapons2/disruptor/disruptor_w.glm +icon gfx/hud/w_icon_disruptor +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_DISRUPTOR +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_BOWCASTER_PICKUP + +classname weapon_bowcaster +worldmodel models/weapons2/bowcaster/bowcaster_w.glm +icon gfx/hud/w_icon_bowcaster +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_BOWCASTER +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_REPEATER_PICKUP + +classname weapon_repeater +worldmodel models/weapons2/heavy_repeater/heavy_repeater_w.glm +icon gfx/hud/w_icon_repeater +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_REPEATER +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_DEMP2_PICKUP + +classname weapon_demp2 +worldmodel models/weapons2/demp2/demp2_w.glm +icon gfx/hud/w_icon_demp2 +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_DEMP2 +min -10 -10 -2 +max 10 10 12 +} + + +{ +itemname ITM_FLECHETTE_PICKUP + +classname weapon_flechette +worldmodel models/weapons2/golan_arms/golan_arms_w.glm +icon gfx/hud/w_icon_flechette +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_FLECHETTE +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_CONCUSSION_RIFLE_PICKUP + +classname weapon_concussion_rifle +worldmodel models/weapons2/concussion/c_rifle_w.glm +icon gfx/hud/w_icon_c_rifle +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_CONCUSSION +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_ROCKET_LAUNCHER_PICKUP + +classname weapon_rocket_launcher +worldmodel models/weapons2/merr_sonn/merr_sonn_w.glm +icon gfx/hud/w_icon_merrsonn +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_ROCKET_LAUNCHER +min -10 -10 -2 +max 10 10 12 +} + + +{ +itemname ITM_THERMAL_DET_PICKUP + +classname weapon_thermal +worldmodel models/weapons2/thermal/thermal_pu.md3 +icon gfx/hud/w_icon_thermal +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_THERMAL +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_TRIP_MINE_PICKUP + +classname weapon_trip_mine +worldmodel models/weapons2/laser_trap/laser_trap_pu.md3 +icon gfx/hud/w_icon_tripmine +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_TRIP_MINE +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_DET_PACK_PICKUP + +classname weapon_det_pack +worldmodel models/weapons2/detpack/det_pack_pu.md3 +icon gfx/hud/w_icon_detpack +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_DET_PACK +min -10 -10 -2 +max 10 10 12 +} + +{ +itemname ITM_STUN_BATON_PICKUP + +classname weapon_stun_baton +worldmodel models/weapons2/stun_baton/stunbaton_w.glm +icon gfx/hud/w_icon_stunbaton +// Amount of ammo given with weapon +count 50 +type IT_WEAPON +tag WP_STUN_BATON +} + + +{ +itemname ITM_BOT_LASER_PICKUP + +classname weapon_botwelder +worldmodel models/weapons2/noweap/noweap.md3 +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_BOT_LASER +} + +{ +itemname ITM_MELEE + +classname weapon_melee +worldmodel models/weapons2/noweap/noweap.md3 +icon gfx/hud/w_icon_melee +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_MELEE +min -16 -16 -2 +max 16 16 16 +} + +{ +itemname ITM_EMPLACED_GUN_PICKUP + +classname weapon_emplaced_gun +worldmodel models/weapons2/noweap/noweap.md3 +// Amount of ammo given with weapon +count 800 +type IT_WEAPON +tag WP_EMPLACED_GUN +} + +{ +itemname ITM_TURRET_PICKUP + +classname weapon_turret +worldmodel models/weapons2/noweap/noweap.md3 +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_TURRET +} + +{ +itemname ITM_ATST_MAIN_PICKUP + +classname weapon_atst_main +worldmodel models/weapons2/noweap/noweap.md3 +icon gfx/hud/w_icon_atst +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_ATST_MAIN +} + +{ +itemname ITM_ATST_SIDE_PICKUP + +classname weapon_atst_side +worldmodel models/weapons2/noweap/noweap.md3 +icon gfx/hud/w_icon_atstside +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_ATST_SIDE +} + +{ +itemname ITM_TIE_FIGHTER_PICKUP + +classname weapon_tie_fighter +worldmodel models/weapons2/noweap/noweap.md3 +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_TIE_FIGHTER +} + +{ +itemname ITM_RAPID_FIRE_CONC_PICKUP + +classname weapon_rapid_concussion +worldmodel models/weapons2/noweap/noweap.md3 +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_RAPID_FIRE_CONC +} + +{ +itemname ITM_JAWA_PICKUP + +classname weapon_jawa +worldmodel models/weapons2/jawa/jawa_gun.md3 +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_JAWA +} + +{ +itemname ITM_TUSKEN_RIFLE_PICKUP + +classname weapon_tusken_rifle +worldmodel models/weapons2/tusken_rifle/tusken_rifle.md3 +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_TUSKEN_RIFLE +} + +{ +itemname ITM_TUSKEN_STAFF_PICKUP + +classname weapon_tusken_staff +worldmodel models/weapons2/tusken_staff/tusken_staff.md3 +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_TUSKEN_STAFF +} + +{ +itemname ITM_SCEPTER_PICKUP + +classname weapon_scepter +worldmodel models/weapons2/sith_scepter/sith_scepter.md3 +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_SCEPTER +} + +{ +itemname ITM_NOGHRI_STICK_PICKUP + +classname weapon_noghri_stick +worldmodel models/weapons2/noghri_stick/noghri_stick.md3 +// Amount of ammo given with weapon +count 400 +type IT_WEAPON +tag WP_NOGHRI_STICK +} +// +//Items +// + +// AMMO Items +//------------- +{ +itemname ITM_AMMO_FORCE_PICKUP + +classname ammo_force +worldmodel models/items/forcegem.md3 +pickupsound sound/player/enlightenment.wav +icon gfx/hud/forcegem_icon2 +count 100 +type IT_AMMO +tag AMMO_FORCE +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_AMMO_BLASTER_PICKUP + +classname ammo_blaster +worldmodel models/items/energy_cell.md3 +icon gfx/hud/energy_cell +count 25 +type IT_AMMO +tag AMMO_BLASTER +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_AMMO_POWERCELL_PICKUP + +classname ammo_powercell +worldmodel models/items/power_cell.md3 +icon gfx/hud/power_cell +count 100 +type IT_AMMO +tag AMMO_POWERCELL +max 8 8 16 +min -8 -8 -0 +} + + +{ +itemname ITM_AMMO_METAL_BOLTS_PICKUP + +classname ammo_metallic_bolts +worldmodel models/items/metallic_bolts.md3 +icon gfx/hud/metallic_bolts +count 100 +type IT_AMMO +tag AMMO_METAL_BOLTS +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_AMMO_ROCKETS_PICKUP + +classname ammo_rockets +worldmodel models/items/rockets.md3 +icon gfx/hud/rockets +count 3 +type IT_AMMO +tag AMMO_ROCKETS +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_AMMO_EMPLACED_PICKUP + +classname ammo_emplaced +worldmodel models/weapons2/noweap/noweap.md3 +count 100 +type IT_AMMO +tag AMMO_EMPLACED +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_AMMO_THERMAL_PICKUP + +classname ammo_thermal +worldmodel models/weapons2/thermal/thermal_pu.md3 +icon gfx/hud/w_icon_thermal +count 4 +type IT_AMMO +tag AMMO_THERMAL +max 16 16 16 +min -16 -16 -0 +} + +{ +itemname ITM_AMMO_TRIPMINE_PICKUP + +classname ammo_tripmine +worldmodel models/weapons2/laser_trap/laser_trap_pu.md3 +icon gfx/hud/w_icon_tripmine +count 3 +type IT_AMMO +tag AMMO_TRIPMINE +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_AMMO_DETPACK_PICKUP + +classname ammo_detpack +worldmodel models/weapons2/detpack/det_pack_pu.md3 +icon gfx/hud/w_icon_detpack +count 3 +type IT_AMMO +tag AMMO_DETPACK +max 8 8 16 +min -8 -8 -0 +} + + + +{ +itemname ITM_BATTERY_PICKUP + +classname item_battery +worldmodel models/items/battery.md3 +icon gfx/hud/battery +count 1000 +type IT_BATTERY +tag ITM_BATTERY_PICKUP +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_SEEKER_PICKUP +classname item_seeker +worldmodel models/items/remote.md3 +icon gfx/hud/i_icon_seeker +count 120 +type IT_HOLDABLE +tag INV_SEEKER +max 8 8 16 +min -8 -8 -4 +} + +{ +itemname ITM_SHIELD_PICKUP +classname item_enviro +worldmodel models/items/shield.md3 +icon gfx/hud/i_icon_shieldwall +count 100 +type IT_HOLDABLE +tag ITM_SHIELD_PICKUP +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_BACTA_PICKUP +classname item_bacta +worldmodel models/items/bacta.md3 +icon gfx/hud/i_icon_bacta +count 25 +type IT_HEALTH +tag ITM_MEDPAK_PICKUP +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_DATAPAD_PICKUP +classname item_datapad +worldmodel models/items/datapad.md3 +count 1 +type IT_HOLDABLE +tag ITM_DATAPAD_PICKUP +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_BINOCULARS_PICKUP +classname item_binoculars +worldmodel models/items/binoculars.md3 +icon gfx/hud/i_icon_zoom +count 1 +type IT_HOLDABLE +tag INV_ELECTROBINOCULARS +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_SENTRY_GUN_PICKUP +classname item_sentry_gun +worldmodel models/items/psgun.glm +icon gfx/hud/i_icon_sentrygun +count 120 +type IT_HOLDABLE +tag INV_SENTRY +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_LA_GOGGLES_PICKUP +classname item_la_goggles +worldmodel models/items/binoculars.md3 +icon gfx/hud/i_icon_goggles +count 30 +type IT_HOLDABLE +tag INV_LIGHTAMP_GOGGLES +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_MEDPAK_PICKUP +classname item_medpak_instant +worldmodel models/items/medpac.md3 +icon gfx/hud/i_icon_medkit +count 20 +type IT_HEALTH +tag ITM_MEDPAK_PICKUP +max 8 8 16 +min -8 -8 -3 +} + +{ +itemname ITM_SHIELD_SM_PICKUP +classname item_shield_sm_instant +worldmodel models/items/psd_sm.md3 +icon gfx/hud/psd_small +count 25 +type IT_ARMOR +tag ITM_SHIELD_SM_PICKUP +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_SHIELD_LRG_PICKUP +classname item_shield_lrg_instant +worldmodel models/items/psd.md3 +icon gfx/hud/psd_medium +count 50 +type IT_ARMOR +tag ITM_SHIELD_LRG_PICKUP +max 8 8 16 +min -8 -8 -4 +} + + +{ +itemname ITM_GOODIE_KEY_PICKUP +classname item_goodie_key +worldmodel models/items/key.md3 +icon gfx/hud/i_icon_goodie_key +type IT_HOLDABLE +tag INV_GOODIE_KEY +max 8 8 16 +min -8 -8 -0 +} + + +{ +itemname ITM_SECURITY_KEY_PICKUP +classname item_security_key +worldmodel models/items/key.md3 +icon gfx/hud/i_icon_security_key +type IT_HOLDABLE +tag INV_SECURITY_KEY +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_FORCE_HEAL_PICKUP + +classname holocron_force_heal +worldmodel models/map_objects/force_holocrons/heal.md3 +pickupsound sound/player/holocron.wav +icon gfx/hud/f_icon_heal +count 1 +type IT_HOLOCRON +tag FP_HEAL +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_FORCE_LEVITATION_PICKUP + +classname holocron_force_levitation +worldmodel models/map_objects/force_holocrons/jump.md3 +pickupsound sound/player/holocron.wav +icon gfx/hud/f_icon_levitation +count 1 +type IT_HOLOCRON +tag FP_LEVITATION +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_FORCE_SPEED_PICKUP + +classname holocron_force_speed +worldmodel models/map_objects/force_holocrons/speed.md3 +pickupsound sound/player/holocron.wav +icon gfx/hud/f_icon_speed +count 1 +type IT_HOLOCRON +tag FP_SPEED +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_FORCE_PUSH_PICKUP + +classname holocron_force_push +worldmodel models/map_objects/force_holocrons/push.md3 +pickupsound sound/player/holocron.wav +icon gfx/hud/f_icon_push +count 1 +type IT_HOLOCRON +tag FP_PUSH +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_FORCE_PULL_PICKUP + +classname holocron_force_pull +worldmodel models/map_objects/force_holocrons/pull.md3 +pickupsound sound/player/holocron.wav +icon gfx/hud/f_icon_pull +count 1 +type IT_HOLOCRON +tag FP_PULL +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_FORCE_TELEPATHY_PICKUP + +classname holocron_force_telepathy +worldmodel models/map_objects/force_holocrons/telepathy.md3 +pickupsound sound/player/holocron.wav +icon gfx/hud/f_icon_telepathy +count 1 +type IT_HOLOCRON +tag FP_TELEPATHY +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_FORCE_GRIP_PICKUP + +classname holocron_force_grip +worldmodel models/map_objects/force_holocrons/grip.md3 +pickupsound sound/player/holocron.wav +icon gfx/hud/f_icon_grip +count 1 +type IT_HOLOCRON +tag FP_GRIP +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_FORCE_LIGHTNING_PICKUP + +classname holocron_force_lightning +worldmodel models/map_objects/force_holocrons/lightning.md3 +pickupsound sound/player/holocron.wav +icon gfx/hud/f_icon_lightning +count 1 +type IT_HOLOCRON +tag FP_LIGHTNING +max 8 8 16 +min -8 -8 -0 +} + +{ +itemname ITM_FORCE_SABERTHROW_PICKUP + +classname holocron_force_saberthrow +worldmodel models/map_objects/force_holocrons/saberthrow.md3 +pickupsound sound/player/holocron.wav +icon gfx/hud/f_icon_saberthrow +count 1 +type IT_HOLOCRON +tag FP_SABERTHROW +max 8 8 16 +min -8 -8 -0 +} diff --git a/base/ext_data/npcs/Bartender.npc b/base/ext_data/npcs/Bartender.npc new file mode 100644 index 0000000..2e19752 --- /dev/null +++ b/base/ext_data/npcs/Bartender.npc @@ -0,0 +1,25 @@ +Bartender +{ + playerModel chiss + weapon WP_NONE + snd bartender + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race human + class CLASS_BARTENDER + walkSpeed 55 + runSpeed 200 + yawspeed 90 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/BespinCop.npc b/base/ext_data/npcs/BespinCop.npc new file mode 100644 index 0000000..0956903 --- /dev/null +++ b/base/ext_data/npcs/BespinCop.npc @@ -0,0 +1,57 @@ +BespinCop +{ + playerModel bespin_cop + weapon WP_BLASTER_PISTOL + health 40 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + walkSpeed 55 + runSpeed 200 + yawspeed 120 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +// race bespincop + class CLASS_BESPIN_COP + snd bespincop1 + sndcombat bespincop1 + sndextra bespincop1 +} + +BespinCop2 +{ + playerModel bespin_cop + weapon WP_BLASTER_PISTOL + health 40 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + walkSpeed 55 + runSpeed 200 + yawspeed 120 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +// race bespincop + class CLASS_BESPIN_COP + snd bespincop2 + sndcombat bespincop2 + sndextra bespincop2 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Desann.npc b/base/ext_data/npcs/Desann.npc new file mode 100644 index 0000000..0416aaf --- /dev/null +++ b/base/ext_data/npcs/Desann.npc @@ -0,0 +1,53 @@ +Desann +{ + playerModel desann + saber desann + weapon WP_SABER + saberStyle 4 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 0 + FP_GRIP 3 + FP_LIGHTNING 3 + FP_RAGE 3 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 3 + FP_SEE 3 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 50 + rank captain + reactions 3 + aim 3 + move 5 + aggression 3 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + scale 135 + height 78 + crouchheight 42 + width 18 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_DESANN + yawSpeed 120 + walkSpeed 55 + runSpeed 200 + snd desann + sndcombat desann + sndjedi desann + health 500 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Elder.npc b/base/ext_data/npcs/Elder.npc new file mode 100644 index 0000000..07367bf --- /dev/null +++ b/base/ext_data/npcs/Elder.npc @@ -0,0 +1,55 @@ +Elder +{ + playerModel prisoner + weapon WP_NONE + customSkin elder + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_PRISONER + snd prisoner1 + sndcombat prisoner1 + sndextra prisoner1 + walkSpeed 55 + runSpeed 200 + yawspeed 110 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +Elder2 +{ + playerModel prisoner + weapon WP_NONE + customSkin elder2 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_PRISONER + snd prisoner2 + sndcombat prisoner2 + sndextra prisoner2 + walkSpeed 55 + runSpeed 200 + yawspeed 110 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Galak.npc b/base/ext_data/npcs/Galak.npc new file mode 100644 index 0000000..ff1ead8 --- /dev/null +++ b/base/ext_data/npcs/Galak.npc @@ -0,0 +1,22 @@ +Galak +{ + playerModel galak + weapon WP_BLASTER + altFire 1 + rank commander + snd galak + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank captain + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_IMPERIAL + yawspeed 90 + walkSpeed 55 + runSpeed 200 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Galak_Mech.npc b/base/ext_data/npcs/Galak_Mech.npc new file mode 100644 index 0000000..c3e9817 --- /dev/null +++ b/base/ext_data/npcs/Galak_Mech.npc @@ -0,0 +1,36 @@ +Galak_Mech +{ + playerModel galak_mech + weapon WP_REPEATER + health 1000 + width 20 + height 88 + crouchheight 88 + snd galak_mech + reactions 3 + aim 5 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_GALAK_MECH + snd galak + sndcombat galak + sndextra galak + yawSpeed 50 + walkSpeed 45 + runSpeed 150 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbHands 0 + dismemberProbLegs 0 + dismemberProbWaist 0 + headPitchRangeUp 60 + headPitchRangeDown 60 + torsoPitchRangeUp 60 + torsoPitchRangeDown 60 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Glider.npc b/base/ext_data/npcs/Glider.npc new file mode 100644 index 0000000..a5b8003 --- /dev/null +++ b/base/ext_data/npcs/Glider.npc @@ -0,0 +1,21 @@ +Glider +{ + playerModel glider + weapon WP_MELEE + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race harvester + class CLASS_GLIDER + yawSpeed 60 + runSpeed 150 + walkSpeed 50 + hFOV 120 + vfov 45 + snd glider +} \ No newline at end of file diff --git a/base/ext_data/npcs/Gran.npc b/base/ext_data/npcs/Gran.npc new file mode 100644 index 0000000..85d6a0b --- /dev/null +++ b/base/ext_data/npcs/Gran.npc @@ -0,0 +1,125 @@ +Gran +{ + playerModel gran + customSkin sp + customRGBA random1 + weapon WP_THERMAL + weapon WP_MELEE + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + health 30 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_GRAN + snd gran1 + sndcombat gran1 + sndextra gran1 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +Gran2 +{ + playerModel gran + customSkin sp + customRGBA random1 + weapon WP_THERMAL + weapon WP_MELEE + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + health 30 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_GRAN + snd gran2 + sndcombat gran2 + sndextra gran2 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +GranBoxer +{ + playerModel gran + customSkin sp + customRGBA random1 + weapon WP_MELEE + surfOff "l_leg_kneeguard r_leg_kneeguard" + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 50 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_GRAN + snd gran2 + sndcombat gran2 + sndextra gran2 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +GranShooter +{ + playerModel gran + customSkin sp + customRGBA random1 + weapon WP_BLASTER + surfOff "l_leg_kneeguard" + reactions 3 + aim 5 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 40 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_GRAN + snd gran1 + sndcombat gran1 + sndextra gran1 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/HazardTrooper.npc b/base/ext_data/npcs/HazardTrooper.npc new file mode 100644 index 0000000..9a0aa7e --- /dev/null +++ b/base/ext_data/npcs/HazardTrooper.npc @@ -0,0 +1,92 @@ +hazardtrooper +{ + playerModel hazardtrooper + weapon WP_REPEATER + health 150 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_HAZARD_TROOPER + height 80 + crouchheight 48 + snd st1 + sndcombat st1 + sndextra st1 + yawspeed 100 + walkSpeed 55 + runSpeed 65 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +hazardtrooperconcussion +{ + playerModel hazardtrooper + weapon WP_CONCUSSION + health 150 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_HAZARD_TROOPER + height 80 + crouchheight 48 + snd st1 + sndcombat st1 + sndextra st1 + yawspeed 100 + walkSpeed 55 + runSpeed 65 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +hazardtrooperofficer +{ + playerModel hazardtrooper + weapon WP_FLECHETTE + health 150 + headPitchRangeDown 30 + reactions 4 + aim 3 + move 4 + aggression 5 + evasion 2 + intelligence 5 + rank lt + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_HAZARD_TROOPER + height 64 + crouchheight 48 + snd stofficer1 + sndcombat stofficer1 + sndextra stofficer1 + yawspeed 100 + walkSpeed 55 + runSpeed 65 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/Howler.npc b/base/ext_data/npcs/Howler.npc new file mode 100644 index 0000000..52bb620 --- /dev/null +++ b/base/ext_data/npcs/Howler.npc @@ -0,0 +1,23 @@ +Howler +{ + playerModel howler + weapon WP_MELEE + height 32 + crouchheight 32 + reactions 4 + aim 1 + move 5 + aggression 3 + evasion 5 + intelligence 2 + playerTeam TEAM_FREE + enemyTeam TEAM_FREE + class CLASS_HOWLER + yawSpeed 60 + runSpeed 300 + walkSpeed 100 + hFOV 120 + vfov 45 + snd howler + health 60 +} \ No newline at end of file diff --git a/base/ext_data/npcs/ImpCommander.npc b/base/ext_data/npcs/ImpCommander.npc new file mode 100644 index 0000000..31465e9 --- /dev/null +++ b/base/ext_data/npcs/ImpCommander.npc @@ -0,0 +1,34 @@ +ImpCommander +{ + playerModel imperial + weapon WP_BLASTER + altFire 1 + surfOff l_arm_key + customSkin commander + health 80 + reactions 4 + aim 4 + move 4 + aggression 4 + evasion 4 + intelligence 4 + rank commander + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_IMPERIAL +// snd io3 +// sndcombat io3 +// sndextra io3 + snd io1 + sndcombat io1 + sndextra io1 + yawspeed 110 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/ImpOfficer.npc b/base/ext_data/npcs/ImpOfficer.npc new file mode 100644 index 0000000..2db3124 --- /dev/null +++ b/base/ext_data/npcs/ImpOfficer.npc @@ -0,0 +1,30 @@ +ImpOfficer +{ + playerModel imperial + weapon WP_BLASTER + surfOff l_arm_key + customSkin officer + health 40 + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 3 + intelligence 3 + rank ltcomm + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_IMPERIAL + snd io1 + sndcombat io1 + sndextra io1 + yawspeed 110 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/ImpWorker.npc b/base/ext_data/npcs/ImpWorker.npc new file mode 100644 index 0000000..24f68d2 --- /dev/null +++ b/base/ext_data/npcs/ImpWorker.npc @@ -0,0 +1,101 @@ +ImpWorker +{ + playerModel imperial_worker + weapon WP_BLASTER_PISTOL + headPitchRangeDown 30 + health 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race imperial + class CLASS_IMPWORKER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd worker1 + sndcombat worker1 + sndextra worker1 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +ImpWorker2 +{ + playerModel imperial_worker + weapon WP_BLASTER_PISTOL + headPitchRangeDown 30 + health 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race imperial + class CLASS_IMPWORKER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd worker2 + sndcombat worker2 + sndextra worker2 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +ImpWorker3 +{ + playerModel imperial_worker + weapon WP_BLASTER_PISTOL + headPitchRangeDown 30 + health 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race imperial + class CLASS_IMPWORKER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd worker3 + sndcombat worker3 + sndextra worker3 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/Imperial.npc b/base/ext_data/npcs/Imperial.npc new file mode 100644 index 0000000..f7d6694 --- /dev/null +++ b/base/ext_data/npcs/Imperial.npc @@ -0,0 +1,29 @@ +Imperial +{ + playerModel imperial + weapon WP_BLASTER_PISTOL + surfOff l_arm_key + health 20 + reactions 2 + aim 2 + move 2 + aggression 2 + evasion 2 + intelligence 2 + rank lt + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_IMPERIAL + snd io2 + sndcombat io2 + sndextra io2 + yawspeed 110 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Jan.npc b/base/ext_data/npcs/Jan.npc new file mode 100644 index 0000000..7851c8a --- /dev/null +++ b/base/ext_data/npcs/Jan.npc @@ -0,0 +1,30 @@ +Jan +{ + playerModel jan + weapon WP_BLASTER + altFire 1 + rank lt + reactions 3 + aim 5 + move 3 + aggression 3 + evasion 3 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JAN + sex female + snd jan + sndcombat jan + sndextra jan + yawSpeed 140 + walkSpeed 55 + runSpeed 200 +// race human + snd jan + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbHands 1 + dismemberProbLegs 0 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Jedi.npc b/base/ext_data/npcs/Jedi.npc new file mode 100644 index 0000000..ab86242 --- /dev/null +++ b/base/ext_data/npcs/Jedi.npc @@ -0,0 +1,102 @@ +Jedi +{ + playerModel jedi + saber single_2 + saberColor yellow + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + snd jedi1 + sndcombat jedi1 + sndjedi jedi1 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +Jedi2 +{ + playerModel jedi + saber single_7 + saberColor orange + weapon WP_SABER + saberStyle 1 + saberStyle 2 + saberStyle 3 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 200 + forcePowerMax 75 + rank lt + customSkin j2 + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + snd jedi2 + sndcombat jedi2 + sndjedi jedi2 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/JediF.npc b/base/ext_data/npcs/JediF.npc new file mode 100644 index 0000000..3d9a56e --- /dev/null +++ b/base/ext_data/npcs/JediF.npc @@ -0,0 +1,49 @@ +JediF +{ + playerModel jan + surfOff "torso_vest hips_chaps torso_computer head_goggles torso_comp hips_belt" + surfOn "torso_augment hips_augment hips_torso" + saber dual_3 + saberColor random + weapon WP_SABER + saberStyle 7 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 2 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + sex female + snd jan + sndcombat jan + sndjedi jan + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/JediMaster.npc b/base/ext_data/npcs/JediMaster.npc new file mode 100644 index 0000000..e307f18 --- /dev/null +++ b/base/ext_data/npcs/JediMaster.npc @@ -0,0 +1,47 @@ +JediMaster +{ + playerModel jedi + customSkin master + saber dual_3 + saberColor green + weapon WP_SABER + saberStyle 7 + FP_HEAL 2 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 2 + FP_TELEPATHY 2 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 2 + FP_ABSORB 2 + FP_DRAIN 0 + FP_SEE 2 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank commander + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + snd jedi2 + sndcombat jedi2 + sndjedi jedi2 + health 400 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/JediTrainer.npc b/base/ext_data/npcs/JediTrainer.npc new file mode 100644 index 0000000..282e8cb --- /dev/null +++ b/base/ext_data/npcs/JediTrainer.npc @@ -0,0 +1,48 @@ +JediTrainer +{ + playerModel jeditrainer + saber jedi + saber2 jedi + saberColor purple + saber2Color purple + weapon WP_SABER + saberStyle 6 + FP_HEAL 2 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 2 + FP_TELEPATHY 2 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 2 + FP_ABSORB 2 + FP_DRAIN 0 + FP_SEE 2 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank commander + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + snd jedi2 + sndcombat jedi2 + sndjedi jedi2 + health 400 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Kyle.npc b/base/ext_data/npcs/Kyle.npc new file mode 100644 index 0000000..a289800 --- /dev/null +++ b/base/ext_data/npcs/Kyle.npc @@ -0,0 +1,91 @@ +Kyle +{ + playerModel kyle + saber kyle + rank commander + health 1000 + weapon WP_BRYAR_PISTOL + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 3 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 3 + FP_GRIP 3 + FP_LIGHTNING 2 + FP_SABERTHROW 3 + FP_RAGE 0 + FP_PROTECT 3 + FP_ABSORB 3 + FP_DRAIN 0 + FP_SEE 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forcePowerMax 200 + reactions 4 + aim 5 + move 3 + aggression 5 + evasion 5 + intelligence 5 + playerTeam TEAM_PLAYER + class CLASS_KYLE + snd kyle + sndcombat kyle + sndjedi kyle + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbHands 0 + dismemberProbLegs 0 + dismemberProbWaist 0 +} + +Kyle_boss +{ + playerModel kyle + saber kyle_boss + rank captain + health 300 + weapon WP_SABER + saberStyle 1 + saberStyle 2 + saberStyle 3 + FP_HEAL 3 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 3 + FP_GRIP 3 + FP_LIGHTNING 0 +// FP_SABERTHROW 3 + FP_SABERTHROW 0 + FP_RAGE 0 + FP_PROTECT 3 + FP_ABSORB 3 + FP_DRAIN 0 + FP_SEE 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + reactions 4 + aim 5 + move 3 + aggression 5 + evasion 5 + intelligence 5 + playerTeam TEAM_PLAYER + class CLASS_KYLE + snd kyle_boss + sndcombat kyle_boss + sndjedi kyle_boss + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbHands 0 + dismemberProbLegs 0 + dismemberProbWaist 0 +} + diff --git a/base/ext_data/npcs/Lando.npc b/base/ext_data/npcs/Lando.npc new file mode 100644 index 0000000..253f12b --- /dev/null +++ b/base/ext_data/npcs/Lando.npc @@ -0,0 +1,62 @@ +Lando +{ + playerModel lando + weapon WP_BLASTER + rank captain + altFire 1 + snd lando + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY +// race human + class CLASS_LANDO + snd lando + sndcombat lando + sndextra lando + sndjedi lando + walkSpeed 55 + runSpeed 200 + yawspeed 120 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbHands 0 + dismemberProbLegs 0 + dismemberProbWaist 0 +} + +Lando_cin +{ + playerModel lando + rank captain + altFire 1 + snd lando + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY +// race human + class CLASS_LANDO + snd lando + sndcombat lando + sndextra lando + sndjedi lando + walkSpeed 52 + runSpeed 200 + yawspeed 120 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbHands 0 + dismemberProbLegs 0 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/Luke.npc b/base/ext_data/npcs/Luke.npc new file mode 100644 index 0000000..220f833 --- /dev/null +++ b/base/ext_data/npcs/Luke.npc @@ -0,0 +1,49 @@ +Luke +{ + playerModel luke + saber luke + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 3 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 3 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_SABERTHROW 3 + FP_RAGE 0 + FP_PROTECT 3 + FP_ABSORB 3 + FP_DRAIN 0 + FP_SEE 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forcePowerMax 200 + rank captain + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 5 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_LUKE + yawSpeed 140 + walkSpeed 55 + runSpeed 200 +// race human + snd luke + sndcombat luke + sndjedi luke + health 200 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Merchant.npc b/base/ext_data/npcs/Merchant.npc new file mode 100644 index 0000000..f62f6d2 --- /dev/null +++ b/base/ext_data/npcs/Merchant.npc @@ -0,0 +1,27 @@ +Merchant +{ + playerModel prisoner + weapon WP_NONE + customSkin merchant + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_PRISONER + snd prisoner2 + sndcombat prisoner2 + sndextra prisoner2 + walkSpeed 55 + runSpeed 200 + yawspeed 110 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Minemonster.npc b/base/ext_data/npcs/Minemonster.npc new file mode 100644 index 0000000..b3677a7 --- /dev/null +++ b/base/ext_data/npcs/Minemonster.npc @@ -0,0 +1,24 @@ +Minemonster +{ + playerModel minemonster + weapon WP_MELEE + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_MINEMONSTER + snd mine + yawSpeed 160 + runSpeed 210 + walkSpeed 50 + hFOV 120 + vfov 45 + height 30 + width 9 + snd mine + health 40 +} \ No newline at end of file diff --git a/base/ext_data/npcs/MonMothma.npc b/base/ext_data/npcs/MonMothma.npc new file mode 100644 index 0000000..d90f884 --- /dev/null +++ b/base/ext_data/npcs/MonMothma.npc @@ -0,0 +1,25 @@ +MonMothma +{ + playerModel monmothma + weapon WP_NONE + snd monmothma + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY +// race human + class CLASS_MONMOTHMA + walkSpeed 55 + runSpeed 200 + yawspeed 90 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/MorganKatarn.npc b/base/ext_data/npcs/MorganKatarn.npc new file mode 100644 index 0000000..b3edec1 --- /dev/null +++ b/base/ext_data/npcs/MorganKatarn.npc @@ -0,0 +1,25 @@ +MorganKatarn +{ + playerModel morgan + weapon WP_NONE + snd morgan + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race human + class CLASS_MORGAN + walkSpeed 55 + runSpeed 200 + yawspeed 90 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Noghri.npc b/base/ext_data/npcs/Noghri.npc new file mode 100644 index 0000000..1430d46 --- /dev/null +++ b/base/ext_data/npcs/Noghri.npc @@ -0,0 +1,27 @@ +Noghri +{ + playerModel noghri + weapon WP_NOGHRI_STICK + reactions 4 + aim 2 + move 5 + aggression 3 + evasion 5 + intelligence 3 + rank crewman + health 100 + playerTeam TEAM_FREE + enemyTeam TEAM_FREE + class CLASS_NOGHRI + snd noghri1 + sndcombat noghri1 + sndextra noghri1 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Prisoner.npc b/base/ext_data/npcs/Prisoner.npc new file mode 100644 index 0000000..98197c5 --- /dev/null +++ b/base/ext_data/npcs/Prisoner.npc @@ -0,0 +1,54 @@ +Prisoner +{ + playerModel prisoner + weapon WP_NONE + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_PRISONER + snd prisoner1 + sndcombat prisoner1 + sndextra prisoner1 + walkSpeed 55 + runSpeed 200 + yawspeed 110 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +Prisoner2 +{ + playerModel prisoner + weapon WP_NONE + customSkin prisoner2 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_PRISONER + snd prisoner2 + sndcombat prisoner2 + sndextra prisoner2 + walkSpeed 55 + runSpeed 200 + yawspeed 110 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Ragnos.npc b/base/ext_data/npcs/Ragnos.npc new file mode 100644 index 0000000..737c73d --- /dev/null +++ b/base/ext_data/npcs/Ragnos.npc @@ -0,0 +1,20 @@ +Ragnos +{ + playerModel marka_ragnos + weapon WP_NONE + rank captain + snd ragnos + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + rank captain + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_GRAN + yawspeed 90 + walkSpeed 55 + runSpeed 200 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Rax.npc b/base/ext_data/npcs/Rax.npc new file mode 100644 index 0000000..c3ea5c6 --- /dev/null +++ b/base/ext_data/npcs/Rax.npc @@ -0,0 +1,25 @@ +Rax +{ + playerModel rax_joris + weapon WP_CONCUSSION + altFire 1 + rank commander + snd rax + sndcombat rax + sndextra rax + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 4 + rank captain + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_IMPERIAL + yawspeed 90 + walkSpeed 55 + runSpeed 200 + health 300 + visrange 3000 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Rebel.npc b/base/ext_data/npcs/Rebel.npc new file mode 100644 index 0000000..91015c6 --- /dev/null +++ b/base/ext_data/npcs/Rebel.npc @@ -0,0 +1,61 @@ +Rebel +{ + playerModel rebel + weapon WP_BLASTER + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY +// race human + class CLASS_REBEL +// snd rebel1 +// sndcombat rebel1 +// sndextra rebel1 + snd rebel_pilot1 + sndcombat rebel_pilot1 + sndextra rebel_pilot1 + yawspeed 120 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +Rebel2 +{ + playerModel rebel + weapon WP_BLASTER + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY +// race human + class CLASS_REBEL +// snd rebel2 +// sndcombat rebel2 +// sndextra rebel2 + snd rebel_pilot1 + sndcombat rebel_pilot1 + sndextra rebel_pilot1 + yawspeed 120 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Rebel2.npc b/base/ext_data/npcs/Rebel2.npc new file mode 100644 index 0000000..a4186be --- /dev/null +++ b/base/ext_data/npcs/Rebel2.npc @@ -0,0 +1,27 @@ +Rebel2 +{ + playerModel rebel + weapon WP_BLASTER + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY +// race human + class CLASS_REBEL + snd rebel_pilot1 + sndcombat rebel_pilot1 + sndextra rebel_pilot1 + yawspeed 120 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Reborn.npc b/base/ext_data/npcs/Reborn.npc new file mode 100644 index 0000000..99356e3 --- /dev/null +++ b/base/ext_data/npcs/Reborn.npc @@ -0,0 +1,50 @@ +Reborn +{ + playerModel reborn + saber reborn + weapon WP_SABER + saberStyle 1 + FP_HEAL 0 + FP_LEVITATION 1 + FP_SPEED 1 + FP_PUSH 0 + FP_PULL 0 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 1 + FP_SABER_OFFENSE 1 + forcePowerMax 50 + forceRegenRate 200 + reactions 1 + aim 1 + move 1 + aggression 1 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + scale 94 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd reborn1 + sndcombat reborn1 + sndjedi reborn1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 40 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/RebornAcrobat.npc b/base/ext_data/npcs/RebornAcrobat.npc new file mode 100644 index 0000000..91b89fd --- /dev/null +++ b/base/ext_data/npcs/RebornAcrobat.npc @@ -0,0 +1,51 @@ +RebornAcrobat +{ + playerModel reborn + customSkin acrobat + saber reborn + saberColor red + weapon WP_SABER + saberStyle 2 + FP_HEAL 0 + FP_LEVITATION 2 + FP_SPEED 1 + FP_PUSH 0 + FP_PULL 0 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 1 + FP_SABER_OFFENSE 1 + rank crewman + reactions 3 + aim 3 + move 5 + aggression 3 + evasion 3 + intelligence 5 + hfov 160 + vfov 160 + scale 96 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd reborn1 + sndcombat reborn1 + sndjedi reborn1 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/RebornBoss.npc b/base/ext_data/npcs/RebornBoss.npc new file mode 100644 index 0000000..e53cf18 --- /dev/null +++ b/base/ext_data/npcs/RebornBoss.npc @@ -0,0 +1,51 @@ +RebornBoss +{ + playerModel reborn + customSkin boss + saber shadowtrooper + weapon WP_SABER + saberStyle 1 + saberStyle 2 + saberStyle 3 + FP_HEAL 0 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 0 + FP_RAGE 1 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 1 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank lt + reactions 3 + aim 3 + move 5 + aggression 4 + evasion 3 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd reborn3 + sndcombat reborn3 + sndjedi reborn3 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/RebornChiss.npc b/base/ext_data/npcs/RebornChiss.npc new file mode 100644 index 0000000..517c332 --- /dev/null +++ b/base/ext_data/npcs/RebornChiss.npc @@ -0,0 +1,54 @@ +RebornChiss +{ + playerModel chiss + saber reborn + saber2 sabersai + saber2color red + weapon WP_SABER + saberStyle 6 + FP_HEAL 0 + FP_LEVITATION 0 + FP_SPEED 1 + FP_PUSH 2 + FP_PULL 0 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 1 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 2 + rank ltjg + reactions 3 + aim 3 + move 5 + aggression 4 + evasion 2 + intelligence 5 + hfov 160 + vfov 160 + scale 96 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd bartender + sndcombat bartender + sndjedi bartender +// snd reborn2 +// sndcombat reborn2 +// sndjedi reborn2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/RebornFencer.npc b/base/ext_data/npcs/RebornFencer.npc new file mode 100644 index 0000000..eb44642 --- /dev/null +++ b/base/ext_data/npcs/RebornFencer.npc @@ -0,0 +1,52 @@ +RebornFencer +{ + playerModel reborn + customSkin fencer + saber reborn + weapon WP_SABER + saberStyle 1 + FP_HEAL 0 + FP_LEVITATION 0 + FP_SPEED 1 + FP_PUSH 2 + FP_PULL 0 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 1 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 2 + forcePowerMax 75 + forceRegenRate 200 + rank ltjg + reactions 3 + aim 3 + move 5 + aggression 4 + evasion 2 + intelligence 5 + hfov 160 + vfov 160 + scale 96 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd reborn2 + sndcombat reborn2 + sndjedi reborn2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/RebornForceUser.npc b/base/ext_data/npcs/RebornForceUser.npc new file mode 100644 index 0000000..d0e2a9f --- /dev/null +++ b/base/ext_data/npcs/RebornForceUser.npc @@ -0,0 +1,51 @@ +RebornForceUser +{ + playerModel reborn + customSkin forceuser + saber reborn + weapon WP_SABER + saberStyle 2 + FP_HEAL 0 + FP_LEVITATION 1 + FP_SPEED 1 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 1 + FP_SABER_OFFENSE 1 + forceRegenRate 150 + rank ensign + reactions 3 + aim 3 + move 5 + aggression 2 + evasion 2 + intelligence 5 + hfov 160 + vfov 160 + scale 96 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd reborn2 + sndcombat reborn2 + sndjedi reborn2 + yawSpeed 80 + walkSpeed 55 + runSpeed 200 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/RebornRodian.npc b/base/ext_data/npcs/RebornRodian.npc new file mode 100644 index 0000000..7d8f607 --- /dev/null +++ b/base/ext_data/npcs/RebornRodian.npc @@ -0,0 +1,52 @@ +RebornRodian +{ + playerModel rodian + weapon WP_SABER + saber saberstar2 + saber2 saberstar2 + saberColor red + saber2Color red + saberStyle 6 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 2 + FP_PUSH 1 + FP_PULL 1 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 2 + rank lt + reactions 3 + aim 3 + move 5 + aggression 3 + evasion 3 + intelligence 5 + hfov 160 + vfov 160 + scale 96 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd rodian1 + sndcombat rodian1 + sndjedi rodian1 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/RebornTrandoshan.npc b/base/ext_data/npcs/RebornTrandoshan.npc new file mode 100644 index 0000000..f4f3727 --- /dev/null +++ b/base/ext_data/npcs/RebornTrandoshan.npc @@ -0,0 +1,50 @@ +RebornTrandoshan +{ + playerModel trandoshan + weapon WP_SABER + saber sabertrident + saberColor red + saberColor2 red + saberColor3 red + saberColor4 red + FP_HEAL 0 + FP_LEVITATION 2 + FP_SPEED 0 + FP_PUSH 2 + FP_PULL 2 + FP_TELEPATHY 0 + FP_GRIP 1 + FP_LIGHTNING 0 + FP_RAGE 1 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 1 + FP_SEE 0 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 2 + rank lt + reactions 3 + aim 3 + move 5 + aggression 4 + evasion 3 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd trandoshan1 + sndcombat trandoshan1 + sndjedi trandoshan1 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/RebornWeequay.npc b/base/ext_data/npcs/RebornWeequay.npc new file mode 100644 index 0000000..8dd8da5 --- /dev/null +++ b/base/ext_data/npcs/RebornWeequay.npc @@ -0,0 +1,49 @@ +RebornWeequay +{ + playerModel weequay + weapon WP_SABER + saber saberbroad + saberColor red + saberColor2 red + saberStyle 3 + FP_HEAL 0 + FP_LEVITATION 2 + FP_SPEED 0 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 0 + FP_GRIP 1 + FP_LIGHTNING 0 + FP_RAGE 2 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 1 + FP_SEE 0 + FP_SABERTHROW 1 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 2 + rank lt + reactions 3 + aim 3 + move 5 + aggression 4 + evasion 3 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd weequay + sndcombat weequay + sndjedi weequay + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Reborn_dual.npc b/base/ext_data/npcs/Reborn_dual.npc new file mode 100644 index 0000000..8fdb62d --- /dev/null +++ b/base/ext_data/npcs/Reborn_dual.npc @@ -0,0 +1,103 @@ +reborn_dual +{ + playerModel reborn_new + customSkin red + saber reborn + saber2 reborn + weapon WP_SABER + saberStyle 6 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 2 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 1 + FP_RAGE 2 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 2 + FP_SEE 2 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank ltcomm + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd reborn2 + sndcombat reborn2 + sndjedi reborn2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 300 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +reborn_dual2 +{ + playerModel reborn_new + customSkin blue + saber reborn + saber2 reborn + weapon WP_SABER + saberStyle 6 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 1 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 1 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 3 + rank lt + reactions 5 + aim 5 + move 5 + aggression 4 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd reborn2 + sndcombat reborn2 + sndjedi reborn2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 175 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/Reborn_new.npc b/base/ext_data/npcs/Reborn_new.npc new file mode 100644 index 0000000..e0ebbc5 --- /dev/null +++ b/base/ext_data/npcs/Reborn_new.npc @@ -0,0 +1,104 @@ +reborn_new +{ + playerModel reborn_new + saber reborn_new + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 2 + FP_TELEPATHY 0 + FP_GRIP 3 + FP_LIGHTNING 1 + FP_RAGE 2 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 2 + FP_SEE 2 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank ltcomm + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd reborn2 + sndcombat reborn2 + sndjedi reborn2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 300 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +reborn_new2 +{ + playerModel reborn_new + customSkin blue + saber shadowtrooper + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 1 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 0 + FP_RAGE 1 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 3 + rank lt + reactions 5 + aim 5 + move 5 + aggression 4 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd reborn2 + sndcombat reborn2 + sndjedi reborn2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 175 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/Reborn_staff.npc b/base/ext_data/npcs/Reborn_staff.npc new file mode 100644 index 0000000..705ac46 --- /dev/null +++ b/base/ext_data/npcs/Reborn_staff.npc @@ -0,0 +1,103 @@ +reborn_staff +{ + playerModel reborn_new + customSkin red + saber dual_1 + saberColor red + weapon WP_SABER + saberStyle 7 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 2 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 1 + FP_RAGE 2 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 2 + FP_SEE 2 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank ltcomm + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd reborn1 + sndcombat reborn1 + sndjedi reborn1 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 300 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +reborn_staff2 +{ + playerModel reborn_new + customSkin blue + saber dual_1 + saberColor red + weapon WP_SABER + saberStyle 7 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 1 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 1 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 3 + rank lt + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd reborn1 + sndcombat reborn1 + sndjedi reborn1 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 175 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/Reborn_twin.npc b/base/ext_data/npcs/Reborn_twin.npc new file mode 100644 index 0000000..680939e --- /dev/null +++ b/base/ext_data/npcs/Reborn_twin.npc @@ -0,0 +1,275 @@ +DKothos +{ + playerModel reborn_twin +// customSkin ? + weapon WP_MELEE + health 500 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 2 + FP_PUSH 3 + FP_PULL 0 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 2 + FP_RAGE 0 + FP_PROTECT 2 + FP_ABSORB 2 + FP_DRAIN 2 + FP_SEE 0 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 0 + FP_SABER_OFFENSE 0 + forcePowerMax 200 + forceRegenRate 50 + forceRegenAmount 2 + rank captain + reactions 5 + aim 5 + move 5 + aggression 1 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd kothos + sndcombat kothos + sndjedi kothos + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +VKothos +{ + playerModel reborn_twin +// customSkin ? + weapon WP_MELEE + health 500 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 2 + FP_PUSH 3 + FP_PULL 0 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 2 + FP_RAGE 0 + FP_PROTECT 2 + FP_ABSORB 2 + FP_DRAIN 2 + FP_SEE 0 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 0 + FP_SABER_OFFENSE 0 + forcePowerMax 200 + forceRegenRate 50 + forceRegenAmount 2 + rank captain + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + scale 100 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd kothos + sndcombat kothos + sndjedi kothos + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +RebornMaster +{ + playerModel reborn_twin + customSkin boss + saber rebornmaster + weapon WP_SABER + saberStyle 1 + saberStyle 2 + saberStyle 3 + health 200 + FP_HEAL 3 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 0 + FP_GRIP 3 + FP_LIGHTNING 3 + FP_RAGE 3 + FP_PROTECT 2 + FP_ABSORB 2 + FP_DRAIN 2 + FP_SEE 0 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forcePowerMax 200 + forceRegenRate 50 + forceRegenAmount 2 + rank captain + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + scale 100 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_SHADOWTROOPER + snd reborn1 + sndcombat reborn1 + sndjedi reborn1 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +RebornMasterDual +{ + playerModel reborn_twin + customSkin boss + saber shadowtrooper + saber2 shadowtrooper + weapon WP_SABER + saberStyle 6 + health 200 + FP_HEAL 3 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 0 + FP_GRIP 3 + FP_LIGHTNING 3 + FP_RAGE 3 + FP_PROTECT 2 + FP_ABSORB 2 + FP_DRAIN 2 + FP_SEE 0 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forcePowerMax 200 + forceRegenRate 50 + forceRegenAmount 2 + rank captain + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + scale 100 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_SHADOWTROOPER + snd reborn2 + sndcombat reborn2 + sndjedi reborn2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + + +RebornMasterStaff +{ + playerModel reborn_twin + customSkin boss + saber dual_1 + saberColor red + weapon WP_SABER + saberStyle 7 + health 200 + FP_HEAL 3 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 0 + FP_GRIP 3 + FP_LIGHTNING 3 + FP_RAGE 3 + FP_PROTECT 2 + FP_ABSORB 2 + FP_DRAIN 2 + FP_SEE 0 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forcePowerMax 200 + forceRegenRate 50 + forceRegenAmount 2 + rank captain + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + hfov 120 + vfov 120 + scale 100 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_SHADOWTROOPER + snd reborn2 + sndcombat reborn2 + sndjedi reborn2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + diff --git a/base/ext_data/npcs/Reelo.npc b/base/ext_data/npcs/Reelo.npc new file mode 100644 index 0000000..d78df3b --- /dev/null +++ b/base/ext_data/npcs/Reelo.npc @@ -0,0 +1,30 @@ +Reelo +{ + playerModel reelo + weapon WP_BLASTER + rank commander + altFire 1 + snd reelo + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_REELO + snd reelo + sndcombat reelo + sndextra reelo + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbHands 0 + dismemberProbLegs 0 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/RocketTrooper.npc b/base/ext_data/npcs/RocketTrooper.npc new file mode 100644 index 0000000..ad56be6 --- /dev/null +++ b/base/ext_data/npcs/RocketTrooper.npc @@ -0,0 +1,37 @@ +RocketTrooper +{ + playerModel stormtrooper +// surfOn torso_pauldron +// surfOff "torso_armor_neck_augment torso_body_neck_augment" + customSkin officer + weapon WP_ROCKET_LAUNCHER + health 60 + headPitchRangeDown 30 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + rank ensign + scale 110 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_STORMTROOPER + height 68 + crouchheight 52 + walkSpeed 51 + runSpeed 200 + snd stofficer1 + sndcombat stofficer1 + sndextra stofficer1 + yawspeed 100 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Rodian.npc b/base/ext_data/npcs/Rodian.npc new file mode 100644 index 0000000..c80f8f0 --- /dev/null +++ b/base/ext_data/npcs/Rodian.npc @@ -0,0 +1,66 @@ +Rodian +{ + playerModel rodian + customSkin sp + customRGBA random1 + weapon WP_DISRUPTOR + altFire 1 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 25 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_RODIAN + snd rodian1 + sndcombat rodian1 + sndextra rodian1 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + visrange 8192 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +Rodian2 +{ + playerModel rodian + customSkin sp + customRGBA random1 + weapon WP_BLASTER + altFire 1 + surfOff "hips_belt torso_vest" + surfOn "torso_augment" + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 20 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_RODIAN + snd rodian2 + sndcombat rodian2 + sndextra rodian2 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/STCommander.npc b/base/ext_data/npcs/STCommander.npc new file mode 100644 index 0000000..00c7c42 --- /dev/null +++ b/base/ext_data/npcs/STCommander.npc @@ -0,0 +1,37 @@ +STCommander +{ + playerModel stormtrooper +// surfOn torso_pauldron +// surfOff "torso_armor_neck_augment torso_body_neck_augment" + customSkin officer + weapon WP_REPEATER + health 60 + headPitchRangeDown 30 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + rank ensign + scale 105 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_STORMTROOPER + height 68 + crouchheight 52 + walkSpeed 51 + runSpeed 200 + snd stofficer1 + sndcombat stofficer1 + sndextra stofficer1 + yawspeed 110 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/STOfficer.npc b/base/ext_data/npcs/STOfficer.npc new file mode 100644 index 0000000..d7c6f52 --- /dev/null +++ b/base/ext_data/npcs/STOfficer.npc @@ -0,0 +1,74 @@ +STOfficer +{ + playerModel stormtrooper +// surfOn torso_pauldron +// surfOff "torso_armor_neck_augment torso_body_neck_augment" + customSkin officer + weapon WP_FLECHETTE + health 60 + headPitchRangeDown 30 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + rank ensign + scale 105 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 68 + crouchheight 52 + walkSpeed 51 + runSpeed 200 + snd stofficer1 + sndcombat stofficer1 + sndextra stofficer1 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +STOfficerAlt +{ + playerModel stormtrooper +// surfOn torso_pauldron +// surfOff "torso_armor_neck_augment torso_body_neck_augment" + customSkin officer + weapon WP_FLECHETTE + altFire 1 + health 60 + headPitchRangeDown 30 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + rank ensign + scale 105 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 68 + crouchheight 52 + walkSpeed 51 + runSpeed 200 + snd stofficer1 + sndcombat stofficer1 + sndextra stofficer1 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/STOfficerAlt.npc b/base/ext_data/npcs/STOfficerAlt.npc new file mode 100644 index 0000000..cc326e8 --- /dev/null +++ b/base/ext_data/npcs/STOfficerAlt.npc @@ -0,0 +1,37 @@ +STOfficerAlt +{ + playerModel stormtrooper + weapon WP_FLECHETTE + altFire 1 + surfOn torso_pauldron + surfOff "torso_armor_neck_augment torso_body_neck_augment" + health 60 + headPitchRangeDown 30 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + rank ensign + scale 105 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_STORMTROOPER + height 68 + crouchheight 52 + walkSpeed 51 + runSpeed 200 + snd stofficer1 + sndcombat stofficer1 + sndextra stofficer1 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/ShadowTrooper.npc b/base/ext_data/npcs/ShadowTrooper.npc new file mode 100644 index 0000000..e4c4d5f --- /dev/null +++ b/base/ext_data/npcs/ShadowTrooper.npc @@ -0,0 +1,100 @@ +ShadowTrooper +{ + playerModel shadowtrooper + saber shadowtrooper + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 2 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 1 + FP_RAGE 2 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 2 + FP_SEE 2 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank ltcomm + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_SHADOWTROOPER + snd shadow1 + sndcombat shadow1 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +ShadowTrooper2 +{ + playerModel shadowtrooper + saber shadowtrooper + saberColor red + weapon WP_SABER + saberStyle 3 + saberStyle 2 + saberStyle 1 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 2 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 1 + FP_RAGE 2 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 2 + FP_SEE 2 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank lt + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_SHADOWTROOPER + snd shadow2 + sndcombat shadow2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/StormPilot.npc b/base/ext_data/npcs/StormPilot.npc new file mode 100644 index 0000000..23aad7a --- /dev/null +++ b/base/ext_data/npcs/StormPilot.npc @@ -0,0 +1,35 @@ +StormPilot +{ + playerModel stormpilot + weapon WP_BLASTER_PISTOL + health 30 + headPitchRangeDown 30 + reactions 3 + aim 5 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 +// snd stofficer1 +// sndcombat stofficer1 +// sndextra stofficer1 + snd worker2 + sndcombat worker2 + sndextra worker2 + yawspeed 80 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/StormTrooper.npc b/base/ext_data/npcs/StormTrooper.npc new file mode 100644 index 0000000..97cf9fc --- /dev/null +++ b/base/ext_data/npcs/StormTrooper.npc @@ -0,0 +1,67 @@ +StormTrooper +{ + playerModel stormtrooper + weapon WP_BLASTER + health 30 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_STORMTROOPER + height 64 + crouchheight 38 + walkSpeed 51 + runSpeed 200 + snd st1 + sndcombat st1 + sndextra st1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +StormTrooper2 +{ + playerModel stormtrooper + weapon WP_BLASTER + health 30 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_STORMTROOPER + height 64 + crouchheight 38 + walkSpeed 51 + runSpeed 200 + snd st2 + sndcombat st2 + sndextra st2 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/SwampTrooper.npc b/base/ext_data/npcs/SwampTrooper.npc new file mode 100644 index 0000000..fab379a --- /dev/null +++ b/base/ext_data/npcs/SwampTrooper.npc @@ -0,0 +1,63 @@ +SwampTrooper +{ + playerModel swamptrooper + weapon WP_FLECHETTE + headPitchRangeDown 30 + health 70 + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 3 + intelligence 3 + scale 110 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_SWAMPTROOPER + height 68 + crouchheight 52 + snd swamp1 + sndcombat swamp1 + sndextra swamp1 + yawspeed 100 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +SwampTrooper2 +{ + playerModel swamptrooper + weapon WP_REPEATER + headPitchRangeDown 30 + health 70 + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 3 + intelligence 3 + scale 110 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_SWAMPTROOPER + height 68 + crouchheight 52 + snd swamp2 + sndcombat swamp2 + sndextra swamp2 + yawspeed 100 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Tavion.npc b/base/ext_data/npcs/Tavion.npc new file mode 100644 index 0000000..2094777 --- /dev/null +++ b/base/ext_data/npcs/Tavion.npc @@ -0,0 +1,50 @@ +Tavion +{ + playerModel tavion + rank commander + saber tavion + saberColor red + weapon WP_SABER + saberStyle 5 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 2 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 2 + FP_RAGE 2 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 1 + FP_SEE 1 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + reactions 3 + aim 3 + move 5 + aggression 3 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_TAVION + sex female + snd tavion + sndcombat tavion + sndjedi tavion + yawSpeed 120 + walkSpeed 55 + runSpeed 200 + health 300 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Tavion_new.npc b/base/ext_data/npcs/Tavion_new.npc new file mode 100644 index 0000000..925a9eb --- /dev/null +++ b/base/ext_data/npcs/Tavion_new.npc @@ -0,0 +1,152 @@ +Tavion_new +{ + playerModel tavion_new + rank commander + saber tavion + saberColor red + weapon WP_SABER + saberStyle 5 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 2 + FP_TELEPATHY 0 + FP_GRIP 3 + FP_LIGHTNING 2 + FP_RAGE 2 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 2 + FP_SEE 1 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + reactions 3 + aim 3 + move 5 + aggression 3 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_TAVION + sex female + snd tavion + sndcombat tavion + sndjedi tavion + yawSpeed 120 + walkSpeed 55 + runSpeed 200 + health 300 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} + +Tavion_scepter +{ + playerModel tavion_new + rank commander + saber tavion + saberColor red + weapon WP_SCEPTER + weapon WP_SABER + saberStyle 6 + health 400 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + sex female + snd tavion + sndcombat tavion + sndjedi tavion + yawSpeed 120 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} + +Tavion_sith_sword +{ + playerModel tavion_new + customSkin possessed + rank captain + weapon WP_SABER + saber sith_sword + saberStyle 4 + health 250 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 0 + FP_GRIP 3 + FP_LIGHTNING 3 + FP_RAGE 3 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 3 + FP_SEE 0 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_DESANN + sex female + snd tavion + sndcombat tavion + sndjedi tavion + yawSpeed 120 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} + diff --git a/base/ext_data/npcs/Trandoshan.npc b/base/ext_data/npcs/Trandoshan.npc new file mode 100644 index 0000000..4e79871 --- /dev/null +++ b/base/ext_data/npcs/Trandoshan.npc @@ -0,0 +1,30 @@ +Trandoshan +{ + playerModel trandoshan + customSkin sp + customRGBA random1 + weapon WP_REPEATER + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 40 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race klingon + class CLASS_TRANDOSHAN + snd trandoshan1 + sndcombat trandoshan1 + sndextra trandoshan1 + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/Ugnaught.npc b/base/ext_data/npcs/Ugnaught.npc new file mode 100644 index 0000000..6a8de5a --- /dev/null +++ b/base/ext_data/npcs/Ugnaught.npc @@ -0,0 +1,55 @@ +Ugnaught +{ + playerModel ugnaught + weapon WP_NONE + scale 75 + health 10 + snd ugnaught + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_PLAYER + class CLASS_UGNAUGHT + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +Ugnaught2 +{ + playerModel ugnaught + weapon WP_NONE + surfOff "l_hand_purse" + surfOn "r_hand_tool" + scale 75 + health 10 + snd ugnaught + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_PLAYER + class CLASS_UGNAUGHT + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/Weequay.npc b/base/ext_data/npcs/Weequay.npc new file mode 100644 index 0000000..4cd6e42 --- /dev/null +++ b/base/ext_data/npcs/Weequay.npc @@ -0,0 +1,128 @@ +Weequay +{ + playerModel weequay + customSkin sp + customRGBA random1 + weapon WP_BOWCASTER +//FIXME: randomize these somehow, also belt... + surfOff "hips_lowerarmor" + surfOn "hips_torso_augment" + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 30 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_WEEQUAY + snd weequay + sndcombat weequay + sndextra weequay + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +Weequay2 +{ + playerModel weequay + customSkin sp + customRGBA random1 + weapon WP_BOWCASTER + surfOff "head_l_hairback hips_r_strap hips_r_packsmall" + surfOn "head_r_hairshoulder hips_l_packsmall hips_l_strap" + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 30 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_WEEQUAY + snd weequay + sndcombat weequay + sndextra weequay + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +Weequay3 +{ + playerModel weequay + customSkin sp + customRGBA random1 + weapon WP_BOWCASTER + surfOff "head_l_hairback hips_l_packlarge hips_l_packwide hips_r_strap hips_r_packsmall" + surfOn "head_r_hairback hips_l_packsmall hips_l_strap hips_r_packlarge hips_r_packwide" + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 30 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_WEEQUAY + snd weequay + sndcombat weequay + sndextra weequay + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +Weequay4 +{ + playerModel weequay + customSkin sp + customRGBA random1 + weapon WP_BOWCASTER + surfOff "head_l_hairback hips_l_packlarge hips_l_packwide" + surfOn "head_l_hairshoulder hips_r_packlarge hips_r_packwide" + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 30 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_WEEQUAY + snd weequay + sndcombat weequay + sndextra weequay + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/alora.npc b/base/ext_data/npcs/alora.npc new file mode 100644 index 0000000..7d2c478 --- /dev/null +++ b/base/ext_data/npcs/alora.npc @@ -0,0 +1,103 @@ +alora +{ + playerModel alora + rank lt + saber single_4 + saberColor red + weapon WP_SABER + saberStyle 2 + saberStyle 1 + health 300 + FP_HEAL 0 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 2 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 2 + reactions 3 + aim 3 + move 5 + aggression 3 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + sex female + snd alora + sndcombat alora + sndjedi alora + yawSpeed 120 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} + +alora_dual +{ +// playerModel alora2 + playerModel alora + rank commander + saber single_4 + saberColor red + saber2 single_4 + saber2Color red + weapon WP_SABER + saberStyle 6 + health 500 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 3 + FP_PULL 3 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 2 + FP_RAGE 3 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 3 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_ALORA + sex female + snd alora + sndcombat alora + sndjedi alora + yawSpeed 120 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/assassin_droid.npc b/base/ext_data/npcs/assassin_droid.npc new file mode 100644 index 0000000..774379f --- /dev/null +++ b/base/ext_data/npcs/assassin_droid.npc @@ -0,0 +1,32 @@ +assassin_droid +{ + playerModel assassin_droid + weapon WP_BLASTER + health 150 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_ASSASSIN_DROID + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd shadowtrooper1 + sndcombat shadowtrooper1 + sndextra shadowtrooper1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 100 + dismemberProbArms 100 + dismemberProbLegs 100 + dismemberProbHands 100 + dismemberProbWaist 100 +} \ No newline at end of file diff --git a/base/ext_data/npcs/atst.npc b/base/ext_data/npcs/atst.npc new file mode 100644 index 0000000..1a4a977 --- /dev/null +++ b/base/ext_data/npcs/atst.npc @@ -0,0 +1,36 @@ +atst +{ + playerModel atst + weapon WP_ATST_MAIN + weapon WP_ATST_SIDE +// headModel atst + //torsoModel atst + //legsModel atst + headYawRangeLeft 80 + headYawRangeRight 80 + headPitchRangeUp 30 + headPitchRangeDown 30 + torsoYawRangeLeft 0 + torsoYawRangeRight 0 + torsoPitchRangeUp 0 + torsoPitchRangeDown 0 + health 200 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + height 272 + width 80 +// race bot + class CLASS_ATST + yawSpeed 60 + runSpeed 150 + walkSpeed 150 + hFOV 120 + vfov 45 + snd atst +} \ No newline at end of file diff --git a/base/ext_data/npcs/atst_vehicle.npc b/base/ext_data/npcs/atst_vehicle.npc new file mode 100644 index 0000000..1140aff --- /dev/null +++ b/base/ext_data/npcs/atst_vehicle.npc @@ -0,0 +1,13 @@ +ATST_vehicle +{ + weapon WP_BOWCASTER + weapon WP_ROCKET_LAUNCHER + weapon WP_EMPLACED_GUN + playerModel atst + playerTeam TEAM_FREE + enemyTeam TEAM_FREE + class CLASS_VEHICLE + height 272 + width 80 + health 3000 +} \ No newline at end of file diff --git a/base/ext_data/npcs/boba_fett.npc b/base/ext_data/npcs/boba_fett.npc new file mode 100644 index 0000000..3eca577 --- /dev/null +++ b/base/ext_data/npcs/boba_fett.npc @@ -0,0 +1,44 @@ + +boba_fett +{ + playerModel boba_fett + altFire 1 + weapon WP_MELEE + weapon WP_DISRUPTOR + weapon WP_ROCKET_LAUNCHER + weapon WP_BLASTER + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 0 + FP_PUSH 0 + FP_PULL 0 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 0 + FP_SABER_OFFENSE 0 + health 400 + headPitchRangeDown 30 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + earshot 2048 + visrange 4096 + hfov 120 + vfov 100 + rank captain + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_BOBAFETT + walkSpeed 80 + runSpeed 250 + snd boba + sndcombat boba + sndextra boba + yawspeed 180 +} + diff --git a/base/ext_data/npcs/chewie.npc b/base/ext_data/npcs/chewie.npc new file mode 100644 index 0000000..487ea98 --- /dev/null +++ b/base/ext_data/npcs/chewie.npc @@ -0,0 +1,86 @@ +chewie +{ + playerModel chewbacca + altFire 0 + weapon WP_BOWCASTER + FP_HEAL 0 + FP_LEVITATION 0 + FP_SPEED 0 + FP_PUSH 0 + FP_PULL 0 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 0 + FP_SABER_OFFENSE 0 + health 400 + scale 122 + width 18 + height 78 + crouchheight 42 + headPitchRangeDown 30 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + earshot 1024 + visrange 2048 + hfov 120 + vfov 100 + rank captain + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_REBEL + walkSpeed 60 + runSpeed 160 + snd chewbacca + sndcombat chewbacca + sndextra chewbacca + yawspeed 90 +} + +chewie_cin +{ + playerModel chewbacca + altFire 0 + FP_HEAL 0 + FP_LEVITATION 0 + FP_SPEED 0 + FP_PUSH 0 + FP_PULL 0 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 0 + FP_SABER_OFFENSE 0 + health 400 + scale 122 + width 18 + height 78 + crouchheight 42 + headPitchRangeDown 30 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + earshot 1024 + visrange 2048 + hfov 120 + vfov 100 + rank captain + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_REBEL + walkSpeed 60 + runSpeed 160 + snd chewbacca + sndcombat chewbacca + sndextra chewbacca + yawspeed 90 +} \ No newline at end of file diff --git a/base/ext_data/npcs/cultist.npc b/base/ext_data/npcs/cultist.npc new file mode 100644 index 0000000..9bf1050 --- /dev/null +++ b/base/ext_data/npcs/cultist.npc @@ -0,0 +1,38 @@ +cultist +{ + playerModel cultist + weapon WP_BLASTER + altfire 1 + FP_LEVITATION 3 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 2 + FP_GRIP 1 + FP_LIGHTNING 1 + FP_DRAIN 1 + rank ltcomm + reactions 1 + aim 1 + move 1 + aggression 1 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 50 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/cultist_destroyer.npc b/base/ext_data/npcs/cultist_destroyer.npc new file mode 100644 index 0000000..368d80b --- /dev/null +++ b/base/ext_data/npcs/cultist_destroyer.npc @@ -0,0 +1,31 @@ +cultist_destroyer +{ + playerModel cultist + customSkin red + weapon WP_MELEE + FP_LEVITATION 3 + rank ltcomm + reactions 1 + aim 1 + move 1 + aggression 5 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist3 + sndcombat cultist3 + sndjedi cultist3 + yawSpeed 60 + walkSpeed 72 + runSpeed 230 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/cultist_drain.npc b/base/ext_data/npcs/cultist_drain.npc new file mode 100644 index 0000000..857d587 --- /dev/null +++ b/base/ext_data/npcs/cultist_drain.npc @@ -0,0 +1,37 @@ +cultist_drain +{ + playerModel cultist + customSkin red + weapon WP_MELEE + FP_LEVITATION 1 + FP_PUSH 3 + FP_DRAIN 3 + forcePowerMax 200 + forceRegenRate 50 + forceRegenAmount 2 + rank captain + reactions 1 + aim 1 + move 1 + aggression 5 + evasion 3 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd cultist3 + sndcombat cultist3 + sndjedi cultist3 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/cultist_grip.npc b/base/ext_data/npcs/cultist_grip.npc new file mode 100644 index 0000000..1fb84f3 --- /dev/null +++ b/base/ext_data/npcs/cultist_grip.npc @@ -0,0 +1,38 @@ +cultist_grip +{ + playerModel cultist + weapon WP_MELEE + FP_LEVITATION 1 + FP_PUSH 3 + FP_PULL 2 +// FP_GRIP 3 + FP_GRIP 2 + forcePowerMax 200 + forceRegenRate 50 + forceRegenAmount 2 + rank captain + reactions 1 + aim 1 + move 1 + aggression 5 + evasion 3 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd cultist3 + sndcombat cultist3 + sndjedi cultist3 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/cultist_lightning.npc b/base/ext_data/npcs/cultist_lightning.npc new file mode 100644 index 0000000..a4a4f20 --- /dev/null +++ b/base/ext_data/npcs/cultist_lightning.npc @@ -0,0 +1,37 @@ +cultist_lightning +{ + playerModel cultist + customSkin blue + weapon WP_MELEE + FP_LEVITATION 1 + FP_PUSH 3 + FP_LIGHTNING 3 + forcePowerMax 200 + forceRegenRate 50 + forceRegenAmount 2 + rank captain + reactions 1 + aim 1 + move 1 + aggression 5 + evasion 3 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race human + class CLASS_REBORN + snd cultist3 + sndcombat cultist3 + sndjedi cultist3 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/cultist_saber.npc b/base/ext_data/npcs/cultist_saber.npc new file mode 100644 index 0000000..6d285b9 --- /dev/null +++ b/base/ext_data/npcs/cultist_saber.npc @@ -0,0 +1,294 @@ +cultist_saber +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 1 + FP_LEVITATION 1 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 1 + reactions 1 + aim 1 + move 1 + aggression 3 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_throw +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 1 + FP_LEVITATION 1 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 1 + FP_SABERTHROW 1 + reactions 1 + aim 1 + move 1 + aggression 2 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_med +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 2 + FP_LEVITATION 1 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 2 + rank crewman + reactions 1 + aim 1 + move 1 + aggression 3 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_med_throw +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 2 + FP_LEVITATION 1 + FP_SABER_DEFENSE 1 + FP_SABER_OFFENSE 2 + FP_SABERTHROW 2 + rank crewman + reactions 1 + aim 1 + move 1 + aggression 2 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_strong +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 3 + FP_LEVITATION 1 + FP_SABER_DEFENSE 1 + FP_SABER_OFFENSE 3 + rank ensign + reactions 1 + aim 1 + move 1 + aggression 3 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_strong_throw +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 3 + FP_LEVITATION 1 + FP_SABER_DEFENSE 1 + FP_SABER_OFFENSE 3 + FP_SABERTHROW 2 + rank ensign + reactions 1 + aim 1 + move 1 + aggression 2 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_all +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_LEVITATION 1 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank crewman + reactions 1 + aim 1 + move 1 + aggression 4 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_all_throw +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_LEVITATION 1 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 3 + FP_SABERTHROW 2 + rank crewman + reactions 1 + aim 1 + move 1 + aggression 3 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 100 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + diff --git a/base/ext_data/npcs/cultist_saber_powers.npc b/base/ext_data/npcs/cultist_saber_powers.npc new file mode 100644 index 0000000..21715cd --- /dev/null +++ b/base/ext_data/npcs/cultist_saber_powers.npc @@ -0,0 +1,305 @@ +cultist_saber2 +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 1 + rank ltjg + FP_PUSH 1 + FP_LEVITATION 1 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 2 + reactions 1 + aim 1 + move 1 + aggression 3 + evasion 2 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_throw2 +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 1 + rank ltjg + FP_PUSH 1 + FP_LEVITATION 1 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 2 + FP_SABERTHROW 1 + reactions 1 + aim 1 + move 1 + aggression 2 + evasion 2 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_med2 +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 2 + rank ensign + FP_PUSH 1 + FP_LEVITATION 1 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + reactions 1 + aim 1 + move 1 + aggression 3 + evasion 2 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_med_throw2 +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 2 + rank ensign + FP_PUSH 1 + FP_LEVITATION 1 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 2 + FP_SABERTHROW 2 + reactions 1 + aim 1 + move 1 + aggression 2 + evasion 2 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_strong2 +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 3 + rank ltjg + FP_PUSH 1 + FP_LEVITATION 1 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 3 + reactions 1 + aim 1 + move 1 + aggression 3 + evasion 2 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_strong_throw2 +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 3 + rank ltjg + FP_PUSH 1 + FP_LEVITATION 1 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 3 + FP_SABERTHROW 2 + reactions 1 + aim 1 + move 1 + aggression 2 + evasion 2 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_all2 +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + rank ltjg + FP_PUSH 1 + FP_LEVITATION 1 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank crewman + reactions 1 + aim 1 + move 1 + aggression 4 + evasion 2 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + +cultist_saber_all_throw2 +{ + playerModel cultist + customSkin brown + saber reborn + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + rank ltjg + FP_PUSH 1 + FP_LEVITATION 1 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 3 + FP_SABERTHROW 2 + reactions 1 + aim 1 + move 1 + aggression 3 + evasion 2 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd cultist1 + sndcombat cultist1 + sndjedi cultist1 + yawSpeed 60 + walkSpeed 45 + runSpeed 180 + health 150 + dismemberProbHead 0 + dismemberProbArms 20 + dismemberProbLegs 0 + dismemberProbHands 30 + dismemberProbWaist 0 +} + diff --git a/base/ext_data/npcs/cultistcommando.npc b/base/ext_data/npcs/cultistcommando.npc new file mode 100644 index 0000000..fb451dc --- /dev/null +++ b/base/ext_data/npcs/cultistcommando.npc @@ -0,0 +1,31 @@ +cultistcommando +{ + playerModel cultist + weapon WP_BLASTER_PISTOL + FP_LEVITATION 2 + rank ltcomm + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// class CLASS_COMMANDO + class CLASS_REBORN + snd cultist3 + sndcombat cultist3 + sndextra cultist3 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 250 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/gonk.npc b/base/ext_data/npcs/gonk.npc new file mode 100644 index 0000000..ad3b0fd --- /dev/null +++ b/base/ext_data/npcs/gonk.npc @@ -0,0 +1,23 @@ +gonk +{ + playerModel gonk + weapon WP_NONE + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race bot + class CLASS_GONK + yawSpeed 60 + runSpeed 40 + walkSpeed 30 + height 32 + width 12 + hFOV 120 + vfov 45 + snd gonk +} \ No newline at end of file diff --git a/base/ext_data/npcs/human_merc.npc b/base/ext_data/npcs/human_merc.npc new file mode 100644 index 0000000..d7fb00b --- /dev/null +++ b/base/ext_data/npcs/human_merc.npc @@ -0,0 +1,372 @@ +human_merc +{ + playerModel human_merc + weapon WP_BLASTER + surfOff "l_arm_pocket l_arm_key" + health 100 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd humanmerc1 + sndcombat humanmerc1 + sndextra humanmerc1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +human_merc_bow +{ + playerModel human_merc + surfOff "l_arm_pocket l_arm_key" + weapon WP_BOWCASTER + health 100 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd humanmerc2 + sndcombat humanmerc2 + sndextra humanmerc2 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +human_merc_rep +{ + playerModel human_merc + surfOff "l_arm_pocket l_arm_key" + weapon WP_REPEATER + health 100 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd humanmerc2 + sndcombat humanmerc2 + sndextra humanmerc2 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +human_merc_flc +{ + playerModel human_merc + surfOff "l_arm_pocket l_arm_key" + weapon WP_FLECHETTE + health 100 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd humanmerc1 + sndcombat humanmerc1 + sndextra humanmerc1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +human_merc_cnc +{ + playerModel human_merc + surfOff "l_arm_pocket l_arm_key" + weapon WP_CONCUSSION + health 100 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd humanmerc2 + sndcombat humanmerc2 + sndextra humanmerc2 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +human_merc +{ + playerModel human_merc + surfOff "l_arm_pocket l_arm_key" + weapon WP_BLASTER + health 100 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd st1 + sndcombat st1 + sndextra st1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +human_merc_bow +{ + playerModel human_merc + surfOff "l_arm_pocket l_arm_key" + weapon WP_BOWCASTER + health 100 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd st1 + sndcombat st1 + sndextra st1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +human_merc_rep +{ + playerModel human_merc + surfOff "l_arm_pocket l_arm_key" + weapon WP_REPEATER + health 100 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd st1 + sndcombat st1 + sndextra st1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +human_merc_flc +{ + playerModel human_merc + surfOff "l_arm_pocket l_arm_key" + weapon WP_FLECHETTE + health 100 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd st1 + sndcombat st1 + sndextra st1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +lannik_racto +{ + playerModel human_merc + customSkin racto + surfOff "l_arm_pocket l_arm_key" + weapon WP_CONCUSSION + health 300 + headPitchRangeDown 30 + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 5 + intelligence 5 + rank captain + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + snd lannik + sndcombat lannik + sndextra lannik + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +human_merc_key +{ + playerModel human_merc + customSkin key_carrier + weapon WP_BLASTER + health 100 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd st1 + sndcombat st1 + sndextra st1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/interrogator.npc b/base/ext_data/npcs/interrogator.npc new file mode 100644 index 0000000..554ca5e --- /dev/null +++ b/base/ext_data/npcs/interrogator.npc @@ -0,0 +1,24 @@ +interrogator +{ + playermodel interrogator + weapon WP_NONE + health 100 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race bot + class CLASS_INTERROGATOR + yawSpeed 120 + runSpeed 150 + walkSpeed 50 + height 24 + width 12 + hFOV 120 + vfov 45 + snd interrogator +} \ No newline at end of file diff --git a/base/ext_data/npcs/jawa.npc b/base/ext_data/npcs/jawa.npc new file mode 100644 index 0000000..2dc88e9 --- /dev/null +++ b/base/ext_data/npcs/jawa.npc @@ -0,0 +1,54 @@ +jawa +{ + playerModel jawa + weapon WP_NONE + scale 75 + health 10 + snd jawa + sndcombat jawa + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_FREE + class CLASS_JAWA + yawspeed 90 + walkSpeed 55 + runSpeed 150 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +jawa_armed +{ + playerModel jawa + weapon WP_JAWA + scale 75 + health 10 + snd jawa + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_FREE + class CLASS_JAWA + yawspeed 90 + walkSpeed 55 + runSpeed 100 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/jedi_random.npc b/base/ext_data/npcs/jedi_random.npc new file mode 100644 index 0000000..fa4b57c --- /dev/null +++ b/base/ext_data/npcs/jedi_random.npc @@ -0,0 +1,648 @@ + +jedi_hf1 +{ + playerModel jedi_hf + customSkin head_B1|torso_C1|lower_D1 +// customRGBA 255 157 114 + customRGBA jedi_hf + saber single_1 + saberColor random + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + sex female + snd female_jedi1 + sndcombat female_jedi1 + sndjedi female_jedi1 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_hf2 +{ + playerModel jedi_hf + customSkin head_C1|torso_A1|lower_B1 +// customRGBA 233 183 208 + customRGBA jedi_hf + saber single_2 + saberColor random + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + sex female + snd female_jedi1 + sndcombat female_jedi1 + sndjedi female_jedi1 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_hm1 +{ + playerModel jedi_hm + customSkin head_A1|torso_A1|lower_B1 +// customRGBA 112 153 161 + customRGBA jedi_hm + saber single_3 + saberColor random + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + snd jedi1 + sndcombat jedi1 + sndjedi jedi1 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_hm2 +{ + playerModel jedi_hm + customSkin head_B1|torso_C1|lower_A1 +// customRGBA 254 197 73 + customRGBA jedi_hm + saber single_4 + saberColor random + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + snd jedi2 + sndcombat jedi2 + sndjedi jedi2 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_kdm1 +{ + playerModel jedi_kdm + customSkin head_B1|torso_D1|lower_C1 +// customRGBA 138 83 0 + customRGBA jedi_kdm + saber dual_3 + saberColor random + weapon WP_SABER + saberStyle 7 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 1 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + snd jedi2 + sndcombat jedi2 + sndjedi jedi2 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_kdm2 +{ + playerModel jedi_kdm + customSkin head_C1|torso_B1|lower_B1 +// customRGBA 225 226 144 + customRGBA jedi_kdm + saber single_5 + saberColor random + weapon WP_SABER + saberStyle 2 + saberStyle 3 + FP_HEAL 2 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 3 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 2 + FP_ABSORB 2 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + snd jedi2 + sndcombat jedi2 + sndjedi jedi2 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_rm1 +{ + playerModel jedi_rm + customSkin head_A1|torso_A1|lower_A1 +// customRGBA 163 79 17 + customRGBA jedi_rm + saber single_6 + saberColor random + weapon WP_SABER + saberStyle 2 + saberStyle 1 + FP_HEAL 1 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 2 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + snd jedi1 + sndcombat jedi1 + sndjedi jedi1 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_rm2 +{ + playerModel jedi_rm + customSkin head_B1|torso_B1|lower_C1 +// customRGBA 49 155 131 + customRGBA jedi_rm + saber single_7 + saberColor random + weapon WP_SABER + saberStyle 2 + saberStyle 1 + FP_HEAL 2 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 2 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + snd jedi1 + sndcombat jedi1 + sndjedi jedi1 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_tf1 +{ + playerModel jedi_tf + customSkin head_A1|torso_A1|lower_D1 +// customRGBA 255 200 212 + customRGBA jedi_tf + saber dual_5 + saberColor random + weapon WP_SABER + saberStyle 7 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + sex female + snd female_jedi2 + sndcombat female_jedi2 + sndjedi female_jedi2 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_tf2 +{ + playerModel jedi_tf + customSkin head_B2|torso_C1|lower_C1 +// customRGBA 255 255 255 + customRGBA jedi_tf + saber single_8 + saberColor random + weapon WP_SABER + saberStyle 1 + saberStyle 2 + FP_HEAL 2 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 2 + FP_PULL 2 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 2 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + sex female + snd female_jedi2 + sndcombat female_jedi2 + sndjedi female_jedi2 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_zf1 +{ + playerModel jedi_zf + customSkin head_B1|torso_A1|lower_D1 +// customRGBA 255 164 59 + customRGBA jedi_zf + saber single_9 + saberColor random + weapon WP_SABER + saberStyle 1 + saberStyle 3 + saberStyle 2 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + sex female + snd female_jedi2 + sndcombat female_jedi2 + sndjedi female_jedi2 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + +jedi_zf2 +{ + playerModel jedi_zf + customSkin head_C1|torso_C1|lower_B1 +// customRGBA 161 226 240 + customRGBA jedi_zf + saber single_9 + saber2 single_1 + saberColor random + saber2Color random + weapon WP_SABER + saberStyle 6 + FP_HEAL 1 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 1 + FP_TELEPATHY 1 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 1 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + forceRegenRate 150 + forcePowerMax 90 + rank lt + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 2 + intelligence 3 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + sex female + snd female_jedi1 + sndcombat female_jedi1 + sndjedi female_jedi1 + health 200 + dismemberProbHead 0 + dismemberProbArms 5 + dismemberProbLegs 0 + dismemberProbHands 10 + dismemberProbWaist 0 +} + + diff --git a/base/ext_data/npcs/lambdashuttle.npc b/base/ext_data/npcs/lambdashuttle.npc new file mode 100644 index 0000000..af433f5 --- /dev/null +++ b/base/ext_data/npcs/lambdashuttle.npc @@ -0,0 +1,12 @@ + +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +LambdaShuttle +{ + weapon WP_NONE + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 250 + height 248 +} diff --git a/base/ext_data/npcs/mark1.npc b/base/ext_data/npcs/mark1.npc new file mode 100644 index 0000000..13f3d26 --- /dev/null +++ b/base/ext_data/npcs/mark1.npc @@ -0,0 +1,24 @@ +mark1 +{ + playerModel mark1 + weapon WP_BOT_LASER + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + height 120 + width 36 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + health 300 +// race bot + class CLASS_MARK1 + yawSpeed 60 + runSpeed 150 + walkSpeed 70 + hFOV 120 + vfov 45 + snd mark1 +} \ No newline at end of file diff --git a/base/ext_data/npcs/mark2.npc b/base/ext_data/npcs/mark2.npc new file mode 100644 index 0000000..b068136 --- /dev/null +++ b/base/ext_data/npcs/mark2.npc @@ -0,0 +1,21 @@ +mark2 +{ + playerModel mark2 + weapon WP_BOT_LASER + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race bot + class CLASS_MARK2 + yawSpeed 60 + runSpeed 150 + walkSpeed 75 + hFOV 120 + vfov 45 + snd mark2 +} \ No newline at end of file diff --git a/base/ext_data/npcs/mouse.npc b/base/ext_data/npcs/mouse.npc new file mode 100644 index 0000000..316c361 --- /dev/null +++ b/base/ext_data/npcs/mouse.npc @@ -0,0 +1,25 @@ +mouse +{ + headmodel none + torsomodel none + legsmodel mouse + weapon WP_NONE + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race bot + class CLASS_MOUSE + yawSpeed 120 + runSpeed 500 + walkSpeed 150 + height 16 + width 8 + hFOV 120 + vfov 45 + snd mouse +} \ No newline at end of file diff --git a/base/ext_data/npcs/nullDriver.npc b/base/ext_data/npcs/nullDriver.npc new file mode 100644 index 0000000..8577d3b --- /dev/null +++ b/base/ext_data/npcs/nullDriver.npc @@ -0,0 +1,10 @@ + +nullDriver +{ + playerModel kyle + weapon WP_NONE + playerTeam neutral + enemyTeam neutral + class kyle +} + diff --git a/base/ext_data/npcs/player.npc b/base/ext_data/npcs/player.npc new file mode 100644 index 0000000..1a80b65 --- /dev/null +++ b/base/ext_data/npcs/player.npc @@ -0,0 +1,7 @@ + +Player +{ + playerModel player + class CLASS_PLAYER +} + diff --git a/base/ext_data/npcs/probe.npc b/base/ext_data/npcs/probe.npc new file mode 100644 index 0000000..b419387 --- /dev/null +++ b/base/ext_data/npcs/probe.npc @@ -0,0 +1,33 @@ +probe +{ + playerModel probe + weapon WP_BOT_LASER + health 200 + headYawRangeLeft 180 + headYawRangeRight 180 + headPitchRangeUp 0 + headPitchRangeDown 0 + torsoYawRangeLeft 0 + torsoYawRangeRight 0 + torsoPitchRangeUp 10 + torsoPitchRangeDown 10 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race bot + class CLASS_PROBE + yawSpeed 60 + runSpeed 150 + walkSpeed 50 + height 110 + width 24 + hFOV 120 + vfov 45 + snd probe + moveType "flyswim" +} \ No newline at end of file diff --git a/base/ext_data/npcs/protocol.npc b/base/ext_data/npcs/protocol.npc new file mode 100644 index 0000000..befd1c1 --- /dev/null +++ b/base/ext_data/npcs/protocol.npc @@ -0,0 +1,24 @@ +protocol +{ + playerModel protocol + weapon WP_NONE + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race bot + class CLASS_PROTOCOL + snd protocol + yawSpeed 60 + runSpeed 150 + walkSpeed 50 + height 65 + width 12 + hFOV 120 + vfov 45 + snd protocol +} \ No newline at end of file diff --git a/base/ext_data/npcs/protocol_imp.npc b/base/ext_data/npcs/protocol_imp.npc new file mode 100644 index 0000000..e5708f8 --- /dev/null +++ b/base/ext_data/npcs/protocol_imp.npc @@ -0,0 +1,27 @@ +protocol_imp +{ + playerModel protocol + weapon WP_NONE + surfOn head_off + surfOff head + customSkin imp + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race bot + class CLASS_PROTOCOL + snd protocol + yawSpeed 60 + runSpeed 150 + walkSpeed 50 + height 65 + width 12 + hFOV 120 + vfov 45 + snd protocol +} \ No newline at end of file diff --git a/base/ext_data/npcs/r2d2.npc b/base/ext_data/npcs/r2d2.npc new file mode 100644 index 0000000..9a091b1 --- /dev/null +++ b/base/ext_data/npcs/r2d2.npc @@ -0,0 +1,31 @@ +r2d2 +{ + playermodel r2d2 + weapon WP_NONE + headYawRangeLeft 180 + headYawRangeRight 180 + headPitchRangeUp 0 + headPitchRangeDown 0 + torsoYawRangeLeft 0 + torsoYawRangeRight 0 + torsoPitchRangeUp 10 + torsoPitchRangeDown 10 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race bot + class CLASS_R2D2 + yawSpeed 120 + runSpeed 150 + walkSpeed 50 + height 40 + width 12 + hFOV 120 + vfov 45 + snd r2d2 +} \ No newline at end of file diff --git a/base/ext_data/npcs/r2d2_imp.npc b/base/ext_data/npcs/r2d2_imp.npc new file mode 100644 index 0000000..43fbd3d --- /dev/null +++ b/base/ext_data/npcs/r2d2_imp.npc @@ -0,0 +1,32 @@ +r2d2_imp +{ + playermodel r2d2 + weapon WP_NONE + customSkin imp + headYawRangeLeft 180 + headYawRangeRight 180 + headPitchRangeUp 0 + headPitchRangeDown 0 + torsoYawRangeLeft 0 + torsoYawRangeRight 0 + torsoPitchRangeUp 10 + torsoPitchRangeDown 10 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race bot + class CLASS_R2D2 + yawSpeed 120 + runSpeed 150 + walkSpeed 50 + height 40 + width 12 + hFOV 120 + vfov 45 + snd r2d2 +} \ No newline at end of file diff --git a/base/ext_data/npcs/r5d2.npc b/base/ext_data/npcs/r5d2.npc new file mode 100644 index 0000000..c69cb08 --- /dev/null +++ b/base/ext_data/npcs/r5d2.npc @@ -0,0 +1,31 @@ +r5d2 +{ + playerModel r5d2 + weapon WP_NONE + headYawRangeLeft 180 + headYawRangeRight 180 + headPitchRangeUp 0 + headPitchRangeDown 0 + torsoYawRangeLeft 0 + torsoYawRangeRight 0 + torsoPitchRangeUp 10 + torsoPitchRangeDown 10 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race bot + class CLASS_R5D2 + yawSpeed 60 + runSpeed 150 + walkSpeed 50 + height 40 + width 12 + hFOV 120 + vfov 45 + snd r5d2 +} \ No newline at end of file diff --git a/base/ext_data/npcs/r5d2_imp.npc b/base/ext_data/npcs/r5d2_imp.npc new file mode 100644 index 0000000..6a2909a --- /dev/null +++ b/base/ext_data/npcs/r5d2_imp.npc @@ -0,0 +1,33 @@ +r5d2_imp +{ + playerModel r5d2 + weapon WP_NONE +//oops, guess they never made this skin... +// customSkin imp + headYawRangeLeft 180 + headYawRangeRight 180 + headPitchRangeUp 0 + headPitchRangeDown 0 + torsoYawRangeLeft 0 + torsoYawRangeRight 0 + torsoPitchRangeUp 10 + torsoPitchRangeDown 10 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL +// race bot + class CLASS_R5D2 + yawSpeed 60 + runSpeed 150 + walkSpeed 50 + height 40 + width 12 + hFOV 120 + vfov 45 + snd r5d2 +} \ No newline at end of file diff --git a/base/ext_data/npcs/rancor.npc b/base/ext_data/npcs/rancor.npc new file mode 100644 index 0000000..8893a6e --- /dev/null +++ b/base/ext_data/npcs/rancor.npc @@ -0,0 +1,70 @@ + +rancor +{ + playerModel rancor + weapon WP_MELEE + health 2000 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_FREE + enemyTeam TEAM_FREE + class CLASS_RANCOR + height 160 + crouchheight 128 + width 60 + snd rancor + sndcombat rancor + sndextra rancor + yawspeed 40 +// walkSpeed 173 +// runSpeed 173 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} + +mutant_rancor +{ + playerModel mutant_rancor + weapon WP_MELEE + health 10000 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_FREE + enemyTeam TEAM_FREE + class CLASS_RANCOR + scale 200 + height 350 + crouchheight 350 + width 120 + snd mutant_rancor + sndcombat mutant_rancor + sndextra mutant_rancor + yawspeed 40 +// walkSpeed 173 +// runSpeed 173 + walkSpeed 110 + runSpeed 400 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} + diff --git a/base/ext_data/npcs/rancor_vehicle.npc b/base/ext_data/npcs/rancor_vehicle.npc new file mode 100644 index 0000000..3b2cf27 --- /dev/null +++ b/base/ext_data/npcs/rancor_vehicle.npc @@ -0,0 +1,14 @@ + +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +Rancor_vehicle +{ + weapon WP_NONE + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + snd rancor + height 128 + crouchheight 128 + width 60 +} diff --git a/base/ext_data/npcs/remote.npc b/base/ext_data/npcs/remote.npc new file mode 100644 index 0000000..e2bfa06 --- /dev/null +++ b/base/ext_data/npcs/remote.npc @@ -0,0 +1,53 @@ +remote +{ +// headmodel none +// torsomodel none +// legsmodel remote + playerModel remote + weapon WP_BOT_LASER + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 3 + intelligence 5 + playerTeam TEAM_FREE + enemyTeam TEAM_FREE + class CLASS_SEEKER + yawSpeed 120 + runSpeed 500 + walkSpeed 150 + height 32 + width 8 + hFOV 160 + vfov 45 + snd remote + moveType "flyswim" +} + +remote_sp +{ + headmodel none + torsomodel none + legsmodel remote_sp + weapon WP_BOT_LASER + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 3 + intelligence 5 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race bot + class CLASS_REMOTE + yawSpeed 120 + runSpeed 500 + walkSpeed 150 + height 32 + width 8 + hFOV 160 + vfov 45 + snd remote + moveType "flyswim" +} diff --git a/base/ext_data/npcs/rockettrooper2.npc b/base/ext_data/npcs/rockettrooper2.npc new file mode 100644 index 0000000..3197583 --- /dev/null +++ b/base/ext_data/npcs/rockettrooper2.npc @@ -0,0 +1,71 @@ +RocketTrooper2 +{ + playerModel rockettrooper + weapon WP_ROCKET_LAUNCHER + health 200 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_ROCKETTROOPER + height 72 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd st1 + sndcombat st1 + sndextra st1 + walkSpeed 55 + runSpeed 150 + yawspeed 50 + visrange 2048 + earshot 2048 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +RocketTrooper2Officer +{ + playerModel rockettrooper + weapon WP_REPEATER + health 400 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank lt + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_ROCKETTROOPER + height 72 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd st1 + sndcombat st1 + sndextra st1 + walkSpeed 55 + runSpeed 200 + yawspeed 50 + hfov 60 + vfov 80 + visrange 2048 + earshot 2048 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/rockettrooper_w.npc b/base/ext_data/npcs/rockettrooper_w.npc new file mode 100644 index 0000000..46425bf --- /dev/null +++ b/base/ext_data/npcs/rockettrooper_w.npc @@ -0,0 +1,35 @@ + +RocketTrooper_ver1 +{ + playerModel stormtrooper + weapon WP_BLASTER + health 30 + headPitchRangeDown 30 + reactions 3 + aim 5 + move 3 + aggression 3 + scaley 130 + scalex 120 + evasion 1 + intelligence 5 + rank crewman + playerTeam enemy + enemyTeam player + class stormtrooper + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd st1 + sndcombat st1 + sndextra st1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/rocks.npc b/base/ext_data/npcs/rocks.npc new file mode 100644 index 0000000..f396b6c --- /dev/null +++ b/base/ext_data/npcs/rocks.npc @@ -0,0 +1,28 @@ +rocks +{ + playerModel rocks + weapon WP_NONE + scale 100 + health 100 + snd jawa + sndcombat jawa + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_FREE + class CLASS_KYLE + yawspeed 90 + walkSpeed 55 + runSpeed 150 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} + diff --git a/base/ext_data/npcs/rosh_penin.npc b/base/ext_data/npcs/rosh_penin.npc new file mode 100644 index 0000000..5e5e67d --- /dev/null +++ b/base/ext_data/npcs/rosh_penin.npc @@ -0,0 +1,124 @@ +rosh_penin_noforce +{ + playerModel rosh_penin + weapon WP_MELEE + FP_HEAL 0 + FP_LEVITATION 0 + FP_SPEED 0 + FP_PUSH 0 + FP_PULL 1 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 0 + FP_SABER_DEFENSE 0 + FP_SABER_OFFENSE 0 + rank crewman + reactions 1 + aim 1 + move 1 + aggression 1 + evasion 1 + intelligence 1 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_REBEL + snd rosh + sndcombat rosh + sndjedi rosh + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbHands 0 + dismemberProbLegs 0 + dismemberProbWaist 0 +} + +rosh_penin +{ + playerModel rosh_penin + saber training + weapon WP_SABER + saberStyle 3 + FP_HEAL 0 + FP_LEVITATION 3 + FP_SPEED 3 + FP_PUSH 2 + FP_PULL 3 + FP_TELEPATHY 0 + FP_GRIP 0 + FP_LIGHTNING 0 + FP_RAGE 0 + FP_PROTECT 0 + FP_ABSORB 0 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank lt + reactions 2 + aim 2 + move 3 + aggression 5 + evasion 3 + intelligence 2 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY + class CLASS_JEDI + snd rosh + sndcombat rosh + sndjedi rosh + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbHands 0 + dismemberProbLegs 0 + dismemberProbWaist 0 +} + +rosh_dark +{ + playerModel rosh_penin + saber shadowtrooper + weapon WP_SABER + saberStyle 3 + health 500 + FP_HEAL 0 + FP_LEVITATION 2 + FP_SPEED 2 + FP_PUSH 2 + FP_PULL 2 + FP_TELEPATHY 0 + FP_GRIP 2 + FP_LIGHTNING 1 + FP_RAGE 1 + FP_PROTECT 1 + FP_ABSORB 1 + FP_DRAIN 0 + FP_SEE 0 + FP_SABERTHROW 2 + FP_SABER_DEFENSE 2 + FP_SABER_OFFENSE 2 + rank lt + reactions 2 + aim 2 + move 3 + aggression 5 + evasion 3 + intelligence 2 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_REBORN + snd rosh_boss + sndcombat rosh_boss + sndjedi rosh_boss + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbHands 0 + dismemberProbLegs 0 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/saber_droid.npc b/base/ext_data/npcs/saber_droid.npc new file mode 100644 index 0000000..2b42d9f --- /dev/null +++ b/base/ext_data/npcs/saber_droid.npc @@ -0,0 +1,66 @@ +saber_droid_training +{ + playerModel saber_droid + saber droid + weapon WP_SABER + saberStyle 1 + FP_SABER_DEFENSE 1 + FP_SABER_OFFENSE 1 + reactions 1 + aim 1 + move 1 + aggression 1 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_SABER_DROID + snd saber_droid + sndcombat saber_droid + sndjedi saber_droid + yawSpeed 100 + walkSpeed 60 + runSpeed 200 + health 50 + dismemberProbHead 100 + dismemberProbArms 100 + dismemberProbLegs 100 + dismemberProbHands 100 + dismemberProbWaist 100 +} + +saber_droid +{ + playerModel saber_droid + saber droid + weapon WP_SABER + saberStyle 1 + FP_SABER_DEFENSE 3 + FP_SABER_OFFENSE 3 + rank ensign + reactions 1 + aim 1 + move 1 + aggression 1 + evasion 1 + intelligence 1 + hfov 120 + vfov 120 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_SABER_DROID + snd saber_droid + sndcombat saber_droid + sndjedi saber_droid + yawSpeed 100 + walkSpeed 60 + runSpeed 200 + health 300 + dismemberProbHead 100 + dismemberProbArms 100 + dismemberProbLegs 100 + dismemberProbHands 100 + dismemberProbWaist 100 +} diff --git a/base/ext_data/npcs/saboteur.npc b/base/ext_data/npcs/saboteur.npc new file mode 100644 index 0000000..96b71b9 --- /dev/null +++ b/base/ext_data/npcs/saboteur.npc @@ -0,0 +1,61 @@ +saboteur +{ + playerModel saboteur + weapon WP_BLASTER_RIFLE + altFire 1 + rank crewman + reactions 5 + aim 3 + move 5 + aggression 5 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_SABOTEUR + snd sab1 + sndcombat sab1 + sndextra sab1 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} + +saboteurcommando +{ + playerModel saboteur + weapon WP_BLASTER_RIFLE + altFire 1 + rank crewman + reactions 5 + aim 3 + move 5 + aggression 5 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_SABOTEUR + snd sab2 + sndcombat sab2 + sndextra sab2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/saboteurpistol.npc b/base/ext_data/npcs/saboteurpistol.npc new file mode 100644 index 0000000..d5b5511 --- /dev/null +++ b/base/ext_data/npcs/saboteurpistol.npc @@ -0,0 +1,29 @@ +saboteurpistol +{ + playerModel saboteur + weapon WP_BLASTER_PISTOL + rank ltjg + reactions 5 + aim 4 + move 5 + aggression 5 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_SABOTEUR + snd sab1 + sndcombat sab1 + sndextra sab1 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 100 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/saboteursniper.npc b/base/ext_data/npcs/saboteursniper.npc new file mode 100644 index 0000000..5cc7adb --- /dev/null +++ b/base/ext_data/npcs/saboteursniper.npc @@ -0,0 +1,30 @@ +saboteursniper +{ + playerModel saboteur + weapon WP_DISRUPTOR + altFire 1 + rank crewman + reactions 5 + aim 5 + move 5 + aggression 5 + evasion 4 + intelligence 5 + hfov 160 + vfov 160 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_SABOTEUR + snd sab2 + sndcombat sab2 + sndextra sab2 + yawSpeed 140 + walkSpeed 55 + runSpeed 200 + health 100 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/sand_creature.npc b/base/ext_data/npcs/sand_creature.npc new file mode 100644 index 0000000..37daee8 --- /dev/null +++ b/base/ext_data/npcs/sand_creature.npc @@ -0,0 +1,48 @@ +sand_creature +{ + playermodel sand_creature + weapon WP_NONE +// health 500 + health 150 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_FREE + enemyTeam TEAM_FREE + class CLASS_SAND_CREATURE + yawSpeed 120 + runSpeed 300 + walkSpeed 125 + height 24 + width 48 + hFOV 120 + vfov 45 + snd sand_creature +} + +sand_creature_fast +{ + playermodel sand_creature + weapon WP_NONE + health 500 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_FREE + enemyTeam TEAM_FREE + class CLASS_SAND_CREATURE + yawSpeed 120 + runSpeed 400 + walkSpeed 400 + height 24 + width 48 + hFOV 120 + vfov 45 + snd sand_creature +} diff --git a/base/ext_data/npcs/seeker.npc b/base/ext_data/npcs/seeker.npc new file mode 100644 index 0000000..8c2e929 --- /dev/null +++ b/base/ext_data/npcs/seeker.npc @@ -0,0 +1,26 @@ +seeker +{ + headmodel none + torsomodel none + legsmodel remote + weapon WP_BOT_LASER + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 3 + intelligence 5 + playerTeam TEAM_PLAYER + enemyTeam TEAM_ENEMY +// race bot + class CLASS_SEEKER + yawSpeed 120 + runSpeed 500 + walkSpeed 150 + height 32 + width 8 + hFOV 160 + vfov 45 + snd remote + moveType "flyswim" +} \ No newline at end of file diff --git a/base/ext_data/npcs/sentry.npc b/base/ext_data/npcs/sentry.npc new file mode 100644 index 0000000..c070b51 --- /dev/null +++ b/base/ext_data/npcs/sentry.npc @@ -0,0 +1,25 @@ +sentry +{ + playermodel sentry + weapon WP_NONE + health 100 + reactions 3 + aim 3 + move 3 + aggression 3 + evasion 1 + intelligence 5 + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER +// race bot + class CLASS_SENTRY + health 150 + yawSpeed 120 + runSpeed 400 + walkSpeed 250 + height 48 + width 24 + hFOV 120 + vfov 160 + snd sentry +} \ No newline at end of file diff --git a/base/ext_data/npcs/snowtrooper.npc b/base/ext_data/npcs/snowtrooper.npc new file mode 100644 index 0000000..8432af0 --- /dev/null +++ b/base/ext_data/npcs/snowtrooper.npc @@ -0,0 +1,33 @@ + +SnowTrooper +{ + playerModel snowtrooper + weapon WP_BLASTER + health 30 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_STORMTROOPER + height 64 + crouchheight 48 + walkSpeed 51 + runSpeed 200 + snd st1 + sndcombat st1 + sndextra st1 + yawspeed 70 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 0 + dismemberProbHands 20 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/swoop.npc b/base/ext_data/npcs/swoop.npc new file mode 100644 index 0000000..d8b6be9 --- /dev/null +++ b/base/ext_data/npcs/swoop.npc @@ -0,0 +1,115 @@ + +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +Swoop +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + +Swoop_black +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + +Swoop_blue +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + +Swoop_gold +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + +Swoop_green +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + +Swoop_purple +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + +Swoop_red +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + +Swoop_silver +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + +Swoop_cin +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + +swoop_mp +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + +swoop_mp2 +{ + weapon WP_BLASTER + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 24 + height 32 +} + + + diff --git a/base/ext_data/npcs/tauntaun.npc b/base/ext_data/npcs/tauntaun.npc new file mode 100644 index 0000000..7d327b5 --- /dev/null +++ b/base/ext_data/npcs/tauntaun.npc @@ -0,0 +1,11 @@ + +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +Tauntaun +{ + weapon WP_NONE + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + snd tauntaun +} diff --git a/base/ext_data/npcs/test.npc b/base/ext_data/npcs/test.npc new file mode 100644 index 0000000..c736e96 --- /dev/null +++ b/base/ext_data/npcs/test.npc @@ -0,0 +1,8 @@ +test +{ + playerModel test + weapon WP_NONE + playerTeam TEAM_PLAYER + enemyTeam TEAM_NEUTRAL + class CLASS_KYLE +} \ No newline at end of file diff --git a/base/ext_data/npcs/tie-bomber.npc b/base/ext_data/npcs/tie-bomber.npc new file mode 100644 index 0000000..38b1500 --- /dev/null +++ b/base/ext_data/npcs/tie-bomber.npc @@ -0,0 +1,19 @@ +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +tie-bomber +{ + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 200 + height 256 +} + +tie-bomber2 +{ + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 200 + height 256 +} diff --git a/base/ext_data/npcs/tie-fighter.npc b/base/ext_data/npcs/tie-fighter.npc new file mode 100644 index 0000000..00e4f51 --- /dev/null +++ b/base/ext_data/npcs/tie-fighter.npc @@ -0,0 +1,11 @@ + +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +tie-fighter +{ + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 140 + height 300 +} diff --git a/base/ext_data/npcs/tusken.npc b/base/ext_data/npcs/tusken.npc new file mode 100644 index 0000000..7539204 --- /dev/null +++ b/base/ext_data/npcs/tusken.npc @@ -0,0 +1,27 @@ +tusken +{ + playerModel tusken + weapon WP_TUSKEN_STAFF + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 50 + rank crewman + playerTeam TEAM_FREE + enemyTeam TEAM_FREE + class CLASS_TUSKEN + snd tusken + sndcombat tusken + sndextra tusken + yawspeed 90 + walkSpeed 55 + runSpeed 200 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/tuskensniper.npc b/base/ext_data/npcs/tuskensniper.npc new file mode 100644 index 0000000..efdd260 --- /dev/null +++ b/base/ext_data/npcs/tuskensniper.npc @@ -0,0 +1,29 @@ +tuskensniper +{ + playerModel tusken + weapon WP_TUSKEN_RIFLE + altFire 1 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + health 50 + rank crewman + playerTeam TEAM_ENEMY + enemyTeam TEAM_PLAYER + class CLASS_TUSKEN + snd tusken + sndcombat tusken + sndextra tusken + yawspeed 90 + walkSpeed 55 + runSpeed 200 + visRange 3000 + dismemberProbHead 0 + dismemberProbArms 10 + dismemberProbLegs 1 + dismemberProbHands 20 + dismemberProbWaist 0 +} \ No newline at end of file diff --git a/base/ext_data/npcs/vssver.scc b/base/ext_data/npcs/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..1efdfbf065aeadaf33387a5901c39ca6d0e87996 GIT binary patch literal 1952 zcmXxldoqi$N={MoV;+b8p0c05c_q~W8%6W*?=#q`(p}tW=OB`UYhk~^G;YdKSCIl- z2e(_@*6MBeS|ksP;ebg+d&e7I6mNio%Z`gC`#i{vaE4_c_kvhReh&Aj%D?`l|0u0* zg56FX%&SU>rhd)vK!K5lj&CIS1uTd&whDWwEaIZyYdFzN`-GW5p6a#2_A30Hhlf%R#1-n;Q2qey zYPbIPU9MIlMeOHa_y>JIQ`YSH4#Hcea$75wTGBl4;ZLnk;(2H6$U|^maA$S`_ddhk z&oF$YTi&)k>n_9I#|T_o-?!2>rk-Kzjl#Y0k=7ZlwUqw>P76zqb=P`CmcqelK29a$ z&9r_T-hQJlV4rCNt(U=Ksruj5XFDi=5{_!{%MZWzit?vmC%KZ1?kHTPK z$tk-=Hqm_Z;lhX^X~kAAs{a|RVkN78610`#3*nb5#2qGe-W1n|ixwp+CtTe_=d~Cv zb}(;hm)}YG2JjQ-ox0t?&&e-Tj>y6;Y3Ay?1l|D3&F|6yH)2=iVMEOhL zix=`!_k9~k^-N$J=5kZxqgaZY!cGm5w^KBt7RpF>3H3SV1SWpUr3IKLT$Kax2!1+bv=p0>{^!_YnH;5V858uyLr0QO$jS z+J6{4ecU8BW{t0i?Ux9j*?en2|KOjrpHuMpvA%?I^Y29L^Z5=QjSVoU;B>nJ*DHcem{P63RHi-G}v;E zS%7?hn5QSPV4oNE`w{@pYp`A}wB*w~=`cTa*vq8&Aw5r5VQ1kgDSQ9y`>qy#==vf` z^NSIxFNSw#WeIz`A}GHF=1H7A*!{Bk<~q0|X>#@1SPOc8TfoBE*Cc}#f%Lvz4#zEx z_@UX>i1r@~`#cUR{r~su7922EI_KKx6{;V~$M+>QP?2Y|o%$bzv+ul@crS~g`VsJt MS)cO_?@m$tKLUk70ssI2 literal 0 HcmV?d00001 diff --git a/base/ext_data/npcs/wampa.npc b/base/ext_data/npcs/wampa.npc new file mode 100644 index 0000000..7475e8f --- /dev/null +++ b/base/ext_data/npcs/wampa.npc @@ -0,0 +1,35 @@ + +wampa +{ + playerModel wampa + scale 150 + weapon WP_MELEE + health 800 + headPitchRangeDown 30 + reactions 3 + aim 1 + move 3 + aggression 3 + evasion 1 + intelligence 5 + rank crewman + playerTeam TEAM_FREE + enemyTeam TEAM_FREE + class CLASS_WAMPA + height 128 + crouchheight 80 + width 30 + snd wampa + sndcombat wampa + sndextra wampa + yawspeed 80 + walkSpeed 120 + runSpeed 300 + hfov 100 + vfov 90 + dismemberProbHead 0 + dismemberProbArms 0 + dismemberProbLegs 0 + dismemberProbHands 0 + dismemberProbWaist 0 +} diff --git a/base/ext_data/npcs/wampa_vehicle.npc b/base/ext_data/npcs/wampa_vehicle.npc new file mode 100644 index 0000000..9b68e67 --- /dev/null +++ b/base/ext_data/npcs/wampa_vehicle.npc @@ -0,0 +1,14 @@ + +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +wampa_vehicle +{ + weapon WP_NONE + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + snd wampa + height 100 + crouchheight 80 + width 30 +} diff --git a/base/ext_data/npcs/wildtauntaun.npc b/base/ext_data/npcs/wildtauntaun.npc new file mode 100644 index 0000000..2213f12 --- /dev/null +++ b/base/ext_data/npcs/wildtauntaun.npc @@ -0,0 +1,12 @@ + +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +WildTauntaun +{ + weapon WP_NONE + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + customSkin wild + snd tauntaun +} diff --git a/base/ext_data/npcs/x-wing.npc b/base/ext_data/npcs/x-wing.npc new file mode 100644 index 0000000..1cd3e36 --- /dev/null +++ b/base/ext_data/npcs/x-wing.npc @@ -0,0 +1,11 @@ + +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +X-Wing +{ + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 230 + height 128 +} diff --git a/base/ext_data/npcs/yt-1300.npc b/base/ext_data/npcs/yt-1300.npc new file mode 100644 index 0000000..151369d --- /dev/null +++ b/base/ext_data/npcs/yt-1300.npc @@ -0,0 +1,12 @@ + +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +YT-1300 +{ + weapon WP_NONE + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 565 + height 323 +} diff --git a/base/ext_data/npcs/z-95.npc b/base/ext_data/npcs/z-95.npc new file mode 100644 index 0000000..6e6280a --- /dev/null +++ b/base/ext_data/npcs/z-95.npc @@ -0,0 +1,11 @@ + +//NOTE: for vehicle-class NPCs, the playerModel key is actually their entry in the vehicles.dat + +Z-95 +{ + playerTeam TEAM_NEUTRAL + enemyTeam TEAM_NEUTRAL + class CLASS_VEHICLE + width 230 + height 128 +} diff --git a/base/ext_data/sabers/NotUsed/exotic.sab b/base/ext_data/sabers/NotUsed/exotic.sab new file mode 100644 index 0000000..c5f4bd2 --- /dev/null +++ b/base/ext_data/sabers/NotUsed/exotic.sab @@ -0,0 +1,270 @@ + +SaberLance +{ + saberType SABER_LANCE + saberModel "models/weapons2/saber_maul/saber_w.glm" + customSkin "models/weapons2/saber_maul/saber_w.skin" + saberLength 80 + saberRadius 6 +// saberStyle lance + saberStyle strong + lockable 0 + throwable 0 + blocking 0 + twoHanded 1 + forceRestrict FP_SPEED + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + forceRestrict FP_SABERTHROW + parryBonus 2 + breakParryBonus 3 + disarmBonus 1 + maxChain 1 +} + +SaberShortStaff +{ + saberType SABER_STAFF + saberModel "models/weapons2/saber_reborn/saber_w.glm" + numBlades 2 + saberLength 20 + saberStyle tavion + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + lockBonus -1 + breakParryBonus -1 +} + +SaberStaff2 +{ + saberType SABER_STAFF + saberModel "models/weapons2/saber_maul/saber_w.glm" + customSkin "models/weapons2/saber_maul/saber_w.skin" + numBlades 2 + saberLength 40 + saberLength2 24 + saberStyle staff + throwable 0 + twoHanded 1 + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + forceRestrict FP_SABERTHROW +} + +SaberStaff3 +{ + saberType SABER_STAFF + saberModel "models/weapons2/saber_reborn/saber_w.glm" + numBlades 2 + saberLength 4 + saberLength2 24 +// saberStyle dagger + saberStyle fast +} + +SaberDagger +{ + saberType SABER_DAGGER + saberModel "models/weapons2/saber_reborn/saber_w.glm" + saberLength 16 + saberRadius 2 + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + lockBonus -1 + parryBonus -1 + breakParryBonus -1 + disarmBonus -1 + maxChain -1 +} + +SaberBroad +{ + saberType SABER_BROAD + saberModel "models/weapons2/saber_desann/saber_w.glm" + numBlades 2 + saberLength 24 + saberRadius 6 + saberStyle strong + twoHanded 1 + forceRestrict FP_SPEED + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + lockBonus 1 + breakParryBonus 2 + maxChain 2 +} + +SaberProng +{ + saberType SABER_PRONG + saberModel "models/weapons2/saber_reborn/saber_w.glm" + numBlades 2 + saberLength 24 +} + +SaberArc +{ + saberType SABER_ARC + saberModel "models/weapons2/saber_maul/saber_w.glm" + customSkin "models/weapons2/saber_maul/saber_w.skin" + numBlades 4 + saberLength 20 + saberLength2 10 + saberLength3 10 + saberLength4 20 +// saberStyle strong + throwable 0 +// twoHanded 1 + forceRestrict FP_SPEED + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + forceRestrict FP_SABERTHROW + lockBonus 2 + parryBonus 2 + breakParryBonus 2 + disarmBonus 2 +} + +SaberSai +{ + saberType SABER_SAI + saberModel "models/weapons2/saber/saber_w.glm" + numBlades 3 + saberLength 16 + saberLength2 8 + saberLength3 8 + saberRadius2 1 + saberRadius3 1 + saberStyle tavion +// throwable 0 + blocking 0 + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + parryBonus 1 + breakParryBonus -1 + disarmBonus 3 +} + +SaberClaw +{ + saberType SABER_CLAW + saberModel "models/weapons2/saber_reborn/saber_w.glm" + numBlades 3 + saberLength 16 + saberRadius 2 + saberStyle tavion + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + throwable 0 + disarmable 0 + blocking 0 + lockBonus 1 + parryBonus 1 +// breakParryBonus -1 + disarmBonus 1 + maxChain -1 +} + +SaberStar +{ + saberType SABER_STAR + saberModel "models/weapons2/saber_reborn/saber_w.glm" + numBlades 6 + saberLength 16 + saberRadius 2 + saberStyle tavion + blocking 0 + returnDamage 1 + forceRestrict FP_SPEED + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + parryBonus 2 + lockBonus -1 + breakParryBonus -1 + disarmBonus 1 + maxChain 3 +} + +SaberStar2 +{ + saberType SABER_STAR + saberModel "models/weapons2/saber_reborn/saber_w.glm" + numBlades 6 + saberLength 8 + saberRadius 1 + saberStyle tavion + returnDamage 1 + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + parryBonus 1 + breakParryBonus -1 + disarmBonus 2 + maxChain -1 +} + +SaberTrident +{ + saberType SABER_TRIDENT + saberModel "models/weapons2/saber_maul/saber_w.glm" + customSkin "models/weapons2/saber_maul/saber_w.skin" + numBlades 4 + saberLength 18 + saberLength2 10 + saberLength3 10 + saberLength4 8 + saberRadius2 2 + saberRadius3 2 + saberStyle strong + throwable 0 + blocking 0 + twoHanded 1 + forceRestrict FP_SPEED + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN + disarmBonus 1 + maxChain 1 +} + + + diff --git a/base/ext_data/sabers/NotUsed/extra.sab b/base/ext_data/sabers/NotUsed/extra.sab new file mode 100644 index 0000000..7adaaf6 --- /dev/null +++ b/base/ext_data/sabers/NotUsed/extra.sab @@ -0,0 +1,397 @@ + +alaris +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_alaris/saber_w.glm" + customSkin "models/weapons2/saber_alaris/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor purple + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +apex1 +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_apex1/saber_w.glm" + customSkin "models/weapons2/saber_apex1/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor blue + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +apex2 +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_apex1/saber_w.glm" + customSkin "models/weapons2/saber_apex1/saber_w2.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor green + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +apex3 +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_apex1/saber_w.glm" + customSkin "models/weapons2/saber_apex1/saber_w3.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor yellow + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +darksaber +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber/saber_w.glm" + customSkin "models/weapons2/saber/saber_w_dark.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor red + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +dooku +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_dooku/saber_w.glm" + customSkin "models/weapons2/saber_dooku/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor red + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +dooku2 +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_dooku2/saber_w.glm" + customSkin "models/weapons2/saber_dooku2/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor red + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +exarkun +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_exarkun/saber_w.glm" + customSkin "models/weapons2/saber_exarkun/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor red + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +gold +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_gold/saber_w.glm" + customSkin "models/weapons2/saber_gold/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor green + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +gold1 +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_gold1/saber_w.glm" + customSkin "models/weapons2/saber_gold1/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor yellow + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +sith1 +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_sith1/saber_w.glm" + customSkin "models/weapons2/saber_sith1/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor red + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +sith2 +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_sith2/saber_w.glm" + customSkin "models/weapons2/saber_sith2/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor red + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +sith3 +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_sith3/saber_w.glm" + customSkin "models/weapons2/saber_sith3/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor red + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +lukeANH +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_lukeANH/saber_w.glm" + customSkin "models/weapons2/saber_lukeANH/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor blue + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +obi +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_obi/saber_w.glm" + customSkin "models/weapons2/saber_obi/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor blue + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +plo_koon +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_plo_koon/saber_w.glm" + customSkin "models/weapons2/saber_plo_koon/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor green + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +QuiGon +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_QuiGon/saber_w.glm" + customSkin "models/weapons2/saber_QuiGon/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor green + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +rusted +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_rusted/saber_w.glm" + customSkin "models/weapons2/saber_rusted/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor yellow + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +kenobi +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_kenobi/saber_w.glm" + customSkin "models/weapons2/saber_kenobi/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor blue + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +vader2 +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_vader2/saber_w.glm" + customSkin "models/weapons2/saber_vader2/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor red + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +windu +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_windu/saber_w.glm" + customSkin "models/weapons2/saber_windu/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor purple + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + +yoda2 +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_yoda/saber_w.glm" + customSkin "models/weapons2/saber_yoda/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 32 + saberColor green + forceRestrict FP_PUSH + forceRestrict FP_PULL + forceRestrict FP_TELEPATHY + forceRestrict FP_GRIP + forceRestrict FP_LIGHTNING + forceRestrict FP_DRAIN +} + diff --git a/base/ext_data/sabers/NotUsed/vssver.scc b/base/ext_data/sabers/NotUsed/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..8165f75dd164c614fb1a0b08876fdb21994788db GIT binary patch literal 64 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiW!^CWsF2{V9!9*}NjQszBmdvZA=P(&Zd|7N&L K{?+^2U_JnPg%aEV literal 0 HcmV?d00001 diff --git a/base/ext_data/sabers/dual_1.sab b/base/ext_data/sabers/dual_1.sab new file mode 100644 index 0000000..e930b81 --- /dev/null +++ b/base/ext_data/sabers/dual_1.sab @@ -0,0 +1,21 @@ + +dual_1 +{ + name @MENUS_STAFF_HILT1 + saberType SABER_STAFF + saberModel "models/weapons2/saber_dual_1/saber_dual_1.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberColor random + numBlades 2 + saberLength 32 + saberStyle staff + throwable 0 + singleBladeStyle medium + singleBladeThrowable 1 + brokenSaber1 brokenstaff + brokenSaber2 brokenstaff + twoHanded 1 +} + diff --git a/base/ext_data/sabers/dual_2.sab b/base/ext_data/sabers/dual_2.sab new file mode 100644 index 0000000..c8b4771 --- /dev/null +++ b/base/ext_data/sabers/dual_2.sab @@ -0,0 +1,21 @@ + +dual_2 +{ + name @MENUS_STAFF_HILT2 + saberType SABER_STAFF + saberModel "models/weapons2/saber_dual_2/saber_dual_2.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberColor random + numBlades 2 + saberLength 32 + saberStyle staff + throwable 0 + singleBladeStyle medium + singleBladeThrowable 1 + brokenSaber1 brokenstaff + brokenSaber2 brokenstaff + twoHanded 1 +} + diff --git a/base/ext_data/sabers/dual_3.sab b/base/ext_data/sabers/dual_3.sab new file mode 100644 index 0000000..1667237 --- /dev/null +++ b/base/ext_data/sabers/dual_3.sab @@ -0,0 +1,21 @@ + +dual_3 +{ + name @MENUS_STAFF_HILT3 + saberType SABER_STAFF + saberModel "models/weapons2/saber_dual_3/saber_dual_3.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberColor random + numBlades 2 + saberLength 32 + saberStyle staff + throwable 0 + singleBladeStyle medium + singleBladeThrowable 1 + brokenSaber1 brokenstaff + brokenSaber2 brokenstaff + twoHanded 1 +} + diff --git a/base/ext_data/sabers/dual_4.sab b/base/ext_data/sabers/dual_4.sab new file mode 100644 index 0000000..8697480 --- /dev/null +++ b/base/ext_data/sabers/dual_4.sab @@ -0,0 +1,21 @@ + +dual_4 +{ + name @MENUS_STAFF_HILT4 + saberType SABER_STAFF + saberModel "models/weapons2/saber_dual_4/saber_dual_4.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberColor random + numBlades 2 + saberLength 32 + saberStyle staff + throwable 0 + singleBladeStyle medium + singleBladeThrowable 1 + brokenSaber1 brokenstaff + brokenSaber2 brokenstaff + twoHanded 1 +} + diff --git a/base/ext_data/sabers/dual_5.sab b/base/ext_data/sabers/dual_5.sab new file mode 100644 index 0000000..f51a85b --- /dev/null +++ b/base/ext_data/sabers/dual_5.sab @@ -0,0 +1,21 @@ + +dual_5 +{ + name @MENUS_STAFF_HILT5 + saberType SABER_STAFF + saberModel "models/weapons2/saber_dual_5/saber_dual_5.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberColor random + numBlades 2 + saberLength 32 + saberStyle staff + throwable 0 + singleBladeStyle medium + singleBladeThrowable 1 + brokenSaber1 brokenstaff + brokenSaber2 brokenstaff + twoHanded 1 +} + diff --git a/base/ext_data/sabers/empty.sab b/base/ext_data/sabers/empty.sab new file mode 100644 index 0000000..26a55f9 --- /dev/null +++ b/base/ext_data/sabers/empty.sab @@ -0,0 +1,11 @@ +empty +{ + saberType SABER_SINGLE + saberModel "models/weapons2/noweap/noweap.glm" + soundOn "sound/null.wav" + soundLoop "sound/null.wav" + soundOff "sound/null.wav" + saberLength 0 + saberColor blue + notInMP 1 +} \ No newline at end of file diff --git a/base/ext_data/sabers/sabers.sab b/base/ext_data/sabers/sabers.sab new file mode 100644 index 0000000..48fd67d --- /dev/null +++ b/base/ext_data/sabers/sabers.sab @@ -0,0 +1,325 @@ +//Lightsaber configurations, use by name + +/* +NOTE: add new sabers by putting their entries in a .sab file in ext_data/sabers + +defaults and explanations of fields: + //saber type + saberType SABER_NONE - what kind of saber to use ( SABER_SINGLE, SABER_STAFF) + + //saber hilt + saberModel "models/weapons2/saber_reborn/saber_w.glm" - what saber hilt model to use + + A note about saber hilt models: the surface names on saber models should *always* start with "w_", the code expects it. + + customSkin "" - a .skin file to apply to the saber hilt model, if any (overrides saber hilt model's internal texture mapping) + + soundOn "sound/weapons/saber/enemy_saber_on.wav" - turn on sound + soundLoop "sound/weapons/saber/saberhum3.wav" - loop sound (must be a .wav file) + soundOff "sound/weapons/saber/enemy_saber_off.wav" - turn off sound + + //blades + numBlades 1 - how many blades it has (min of 1, max of 8) + + A note about blade tags: each blade will be drawn from it's corresponding tag's position and in the direction (negative X axis in ModView) of that tag. + The naming of the tags must be as follows: + "tag_blade1" for the first blade. I made the code fall back to "tag_flash" if it can't find the tag_blades so that the code will still work pre-JKA sabers, but we you should always use the new name. + All following blades (if applicable) should be "tag_blade2", "tag_blade3", etc., to a maximum of 8 blades ("tag_blade8") + + //saber length + //NOTE: these should be inputted in order - setting "saberLength" automatically sets all of the others to the same length + saberLength 32 - how long the saber's first blade should be (minimum of 4) + saberLength2 saberLength - how long the saber's second blade should be (minimum of 4) + saberLength3 saberLength - how long the saber's third blade should be (minimum of 4) + saberLength4 saberLength - how long the saber's fourth blade should be (minimum of 4) + saberLength5 saberLength - how long the saber's fifth blade should be (minimum of 4) + saberLength6 saberLength - how long the saber's sixth blade should be (minimum of 4) + saberLength7 saberLength - how long the saber's seventh blade should be (minimum of 4) + saberLength8 saberLength - how long the saber's eigth blade should be (minimum of 4) + + //saber radius + //NOTE: these should be inputted in order - setting "saberLength" automatically sets all of the others to the same length + saberRadius 3 - how wide the saber's first blade should be (minimum of 0.25) + saberRadius2 saberRadius - how wide the saber's second blade should be (minimum of 0.25) + saberRadius3 saberRadius - how wide the saber's third blade should be (minimum of 0.25) + saberRadius4 saberRadius - how wide the saber's fourth blade should be (minimum of 0.25) + saberRadius5 saberRadius - how wide the saber's fifth blade should be (minimum of 0.25) + saberRadius6 saberRadius - how wide the saber's sixth blade should be (minimum of 0.25) + saberRadius7 saberRadius - how wide the saber's seventh blade should be (minimum of 0.25) + saberRadius8 saberRadius - how wide the saber's eigth blade should be (minimum of 0.25) + + //saberColor - valid colors: random, red, orange, yellow, green, blue, and purple + //NOTE: these should be inputted in order - setting "saberColor" automatically sets all of the others to the same color + saberColor red - what color the saber's 1st blade should be + saberColor2 saberColor - what color the saber's 2nd blade should be + saberColor3 saberColor - what color the saber's 3rd blade should be + saberColor4 saberColor - what color the saber's 4th blade should be + saberColor5 saberColor - what color the saber's 5th blade should be + saberColor6 saberColor - what color the saber's 6th blade should be + saberColor7 saberColor - what color the saber's 7th blade should be + saberColor8 saberColor - what color the saber's 8th blade should be + + //locked style + saberStyle none - what one style it's limited to, if any (fast, medium, strong, desann, tavion) + + //maxChain + maxChain 0 - how many moves can be chained in a row with this weapon (-1 is infinite, 0 is use default behavior) + + //throwable + lockable 1 - whether or not it can be stuck in a saber lock + + //throwable + throwable 1 - whether or not it can be thrown + + //disarmable + disarmable 1 - whether or not it can be disarmed (dropped) + + //blocking + blocking 1 - whether or not user will try to block incoming shots (shots always bounce off it, this just determines if the user *tries* to block with it) + + //twoHanded + twoHanded 0 - whether or not it requires 2 hands (makes it restrict force powers but makes it stronger in locks or parries) + + //force power restrictions + forceRestrict 0 - what force powers it restricts, use these keys, a seperate entry per power: + FP_HEAL + FP_LEVITATION + FP_SPEED + FP_PUSH + FP_PULL + FP_TELEPATHY + FP_GRIP + FP_LIGHTNING + FP_SABERTHROW + FP_SABER_DEFENSE + FP_SABER_OFFENSE + //new Jedi Academy powers + FP_RAGE + FP_PROTECT + FP_ABSORB + FP_DRAIN + FP_SEE + + //lockBonus + lockBonus 0 - this pushes harder/weaker in saberlocks + + //parryBonus + parryBonus 0 - this is stronger/weaker in a parry (harder to break, more likely to knockaway) + + //breakParryBonus + breakParryBonus 0 - this is more/less likely to break a parry + + //disarmBonus + disarmBonus 0 - this is more/less likely to disarm another saber in a saberlock or knockaway + + singleBladeStyle none - makes it so that you use a different style if you only have the first blade active + + singleBladeThrowable 0 - makes it so that you can throw this saber if only the first blade is on + + brokenSaber1 none - if saber is actually hit by another saber, it can be cut in half/broken and will be replaced with this saber in your right hand + brokenSaber2 none - if saber is actually hit by another saber, it can be cut in half/broken and will be replaced with this saber in your left hand + + returnDamage 0 - when returning from a saber throw, it keeps spinning and doing damage + +//===The following fields were added later:=========================================================================== + //done in cgame (client-side code) + noWallMarks 0 - if 1, stops the saber from drawing marks on the world (good for real-sword type mods) + noDlight 0 - if 1, stops the saber from drawing a dynamic light (good for real-sword type mods) + noBlade 0 - if 1, stops the saber from drawing a blade (good for real-sword type mods) + trailStyle 0 - default (0) is normal, 1 is a motion blur and 2 is no trail at all (good for real-sword type mods) + g2MarksShader none - if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + + //done in game (server-side code) + knockbackScale 0 - if non-zero, uses damage done to calculate an appropriate amount of knockback + damageScale 1 - scale up or down the damage done by the saber + noDismemberment 0 - if non-zero, the saber never does dismemberment (good for pointed/blunt melee weapons) + bounceOnWalls 0 - if non-zero, the saber will bounce back when it hits solid architecture (good for real-sword type mods) + noIdleEffect 0 - if non-zero, the saber will not do damage or any effects when it is idle (not in an attack anim). (good for real-sword type mods) + stickOnImpact 0 - if non-zero, the saber will stick in the wall when thrown and hits solid architecture (good for sabers that are meant to be thrown). + noAttack 0 - if non-zero, you cannot attack with the saber (for sabers/weapons that are meant to be thrown only, not used as melee weapons). +//========================================================================================================================================= +*/ + +Kyle +{ + name "Katarn" + saberType SABER_SINGLE + saberModel "models/weapons2/saber/saber_w.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor blue +} + +Kyle_boss +{ + name "Katarn" + saberType SABER_SINGLE + saberModel "models/weapons2/saber/saber_w.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor blue + lockBonus 2 + parryBonus 2 + disarmBonus 2 + notInMP 1 +} + +Luke +{ + name "Skywalker" + saberType SABER_SINGLE + saberModel "models/weapons2/saber_luke/saber_w.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum5.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor green + notInMP 1 +} + +Desann +{ + name "Retribution" + saberType SABER_SINGLE + saberModel "models/weapons2/saber_desann/saber_w.glm" + soundLoop "sound/weapons/saber/saberhum2.wav" +// saberLength 48 + saberLength 64 + saberRadius 6 + saberColor red + notInMP 1 +} + +RebornMaster +{ + name "RebornMaster" + saberType SABER_SINGLE + saberModel "models/weapons2/saber_desann/saber_w.glm" + soundLoop "sound/weapons/saber/saberhum2.wav" + saberLength 48 + saberColor red + lockBonus 1 + parryBonus 1 + breakParryBonus 1 + disarmBonus 2 + notInMP 1 +} + +Tavion +{ + name "Stinger" + saberType SABER_SINGLE + saberModel "models/weapons2/saber_reborn/saber_w.glm" + saberLength 40 + saberColor red + notInMP 1 +} + +reborn_new +{ + name "reborn_new" + saberType SABER_SINGLE + saberModel "models/weapons2/saber_reborn/saber_w.glm" + saberLength 40 + saberColor red + lockBonus 1 + parryBonus 1 + breakParryBonus 1 + disarmBonus 1 + notInMP 1 +} + +Shadowtrooper +{ + name "Shadowtrooper" + saberType SABER_SINGLE + saberModel "models/weapons2/saber_reborn/saber_w.glm" + saberLength 40 + saberColor red + notInMP 1 +} + +Reborn +{ + name "Reborn" + saberType SABER_SINGLE + saberModel "models/weapons2/saber_reborn/saber_w.glm" + saberLength 32 + saberColor red + notInMP 1 +} + +Training +{ + name "Training" + saberType SABER_SINGLE + saberModel "models/weapons2/saber_reborn/saber_w.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 32 + saberColor yellow + notInMP 1 +} + +Jedi +{ + name "Apprentice" + saberType SABER_SINGLE + saberModel "models/weapons2/saber_reborn/saber_w.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum1.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor random + notInMP 1 +} + +Yoda +{ + name "Eternal" + saberType SABER_SINGLE + saberModel "models/weapons2/saber_yoda/saber_w.glm" + customSkin "models/weapons2/saber_yoda/saber_w.skin" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum1.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 32 + saberColor green + saberStyle tavion + maxChain -1 + disarmable 0 + lockBonus 2 + parryBonus 5 + breakParryBonus 1 + disarmBonus 3 + notInMP 1 +} + +brokenStaff +{ + saberType SABER_SINGLE + //FIXME: need actual broken staff model + saberModel "models/weapons2/saber_reborn/saber_w.glm" + numBlades 1 + saberLength 32 + throwable 1 + twoHanded 0 + notInMP 1 +} + +droid +{ + saberType SABER_SINGLE + saberModel "models/weapons2/saber_reborn/saber_w.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor yellow + notInMP 1 +} \ No newline at end of file diff --git a/base/ext_data/sabers/single_1.sab b/base/ext_data/sabers/single_1.sab new file mode 100644 index 0000000..7b7c7e1 --- /dev/null +++ b/base/ext_data/sabers/single_1.sab @@ -0,0 +1,13 @@ + +single_1 +{ + name @MENUS_SINGLE_HILT1 + saberType SABER_SINGLE + saberModel "models/weapons2/saber_1/saber_1.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor random +} + diff --git a/base/ext_data/sabers/single_2.sab b/base/ext_data/sabers/single_2.sab new file mode 100644 index 0000000..0eebf09 --- /dev/null +++ b/base/ext_data/sabers/single_2.sab @@ -0,0 +1,13 @@ + +single_2 +{ + name @MENUS_SINGLE_HILT2 + saberType SABER_SINGLE + saberModel "models/weapons2/saber_2/saber_2.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor random +} + diff --git a/base/ext_data/sabers/single_3.sab b/base/ext_data/sabers/single_3.sab new file mode 100644 index 0000000..fb70c0a --- /dev/null +++ b/base/ext_data/sabers/single_3.sab @@ -0,0 +1,13 @@ + +single_3 +{ + name @MENUS_SINGLE_HILT3 + saberType SABER_SINGLE + saberModel "models/weapons2/saber_3/saber_3.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor random +} + diff --git a/base/ext_data/sabers/single_4.sab b/base/ext_data/sabers/single_4.sab new file mode 100644 index 0000000..9861cc3 --- /dev/null +++ b/base/ext_data/sabers/single_4.sab @@ -0,0 +1,13 @@ + +single_4 +{ + name @MENUS_SINGLE_HILT4 + saberType SABER_SINGLE + saberModel "models/weapons2/saber_4/saber_4.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor random +} + diff --git a/base/ext_data/sabers/single_5.sab b/base/ext_data/sabers/single_5.sab new file mode 100644 index 0000000..bd90d49 --- /dev/null +++ b/base/ext_data/sabers/single_5.sab @@ -0,0 +1,13 @@ + +single_5 +{ + name @MENUS_SINGLE_HILT5 + saberType SABER_SINGLE + saberModel "models/weapons2/saber_5/saber_5.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor random +} + diff --git a/base/ext_data/sabers/single_6.sab b/base/ext_data/sabers/single_6.sab new file mode 100644 index 0000000..513bcc6 --- /dev/null +++ b/base/ext_data/sabers/single_6.sab @@ -0,0 +1,13 @@ + +single_6 +{ + name @MENUS_SINGLE_HILT6 + saberType SABER_SINGLE + saberModel "models/weapons2/saber_6/saber_6.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor random +} + diff --git a/base/ext_data/sabers/single_7.sab b/base/ext_data/sabers/single_7.sab new file mode 100644 index 0000000..7af2207 --- /dev/null +++ b/base/ext_data/sabers/single_7.sab @@ -0,0 +1,13 @@ + +single_7 +{ + name @MENUS_SINGLE_HILT7 + saberType SABER_SINGLE + saberModel "models/weapons2/saber_7/saber_7.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor random +} + diff --git a/base/ext_data/sabers/single_8.sab b/base/ext_data/sabers/single_8.sab new file mode 100644 index 0000000..532be7e --- /dev/null +++ b/base/ext_data/sabers/single_8.sab @@ -0,0 +1,13 @@ + +single_8 +{ + name @MENUS_SINGLE_HILT8 + saberType SABER_SINGLE + saberModel "models/weapons2/saber_8/saber_8.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor random +} + diff --git a/base/ext_data/sabers/single_9.sab b/base/ext_data/sabers/single_9.sab new file mode 100644 index 0000000..32bdcff --- /dev/null +++ b/base/ext_data/sabers/single_9.sab @@ -0,0 +1,13 @@ + +single_9 +{ + name @MENUS_SINGLE_HILT9 + saberType SABER_SINGLE + saberModel "models/weapons2/saber_9/saber_9.glm" + soundOn "sound/weapons/saber/saberon.wav" + soundLoop "sound/weapons/saber/saberhum4.wav" + soundOff "sound/weapons/saber/saberoff.wav" + saberLength 40 + saberColor random +} + diff --git a/base/ext_data/sabers/sith_sword.sab b/base/ext_data/sabers/sith_sword.sab new file mode 100644 index 0000000..3336dfe --- /dev/null +++ b/base/ext_data/sabers/sith_sword.sab @@ -0,0 +1,11 @@ + +sith_sword +{ +//note: the SABER_SITH_SWORD type of saber draws no blade and does knockback + name "Glory of the Sith" + saberType SABER_SITH_SWORD + saberModel "models/weapons2/sith_scepter/sith_sword_w.glm" + saberLength 44 + notInMP 1 +} + diff --git a/base/ext_data/sabers/vssver.scc b/base/ext_data/sabers/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..b3650d7721bb340e2322e83685c5e44e173523b4 GIT binary patch literal 304 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZ7IH`U*A;bU%2Eq&sCq$NnF-hnz;RT8W0Qu8} zcDgH-3N2v+@&keVD}e|6>P`!Q`9VOwR<6bWTPx+j{9qtI=(w(0%8)Z3R>Hfy z6xBA~`Ah`Y!0D~?>*`Boj0e}j|Mt8ws}4~z1GpY;i#gEWnS7t(4`9XB?!TrZ_o!b3 zEDbZr-E~*R%#qLFj9V!YoVk9DaPCS|P_cD8vw|PO*E=S~{=ow@zY1R1J7xd)g)h}> zg4HEsa*;HI86&?L=KC#org;Q06SxIlaX4=JvEwA=x5K_wj=f%1p|n38+_(^T8Yx3w>>h~H>a(YzptUjK;hZ?TTF0P!( zI8XCV!ulJX3mQ=x)t`nvRpx`e2|U`bSy2PBI7d^l{CQM0J$ksA;yO44v2%8wS+hTx zHsZSQ#&BSm^Gpfl>%qdo*h20+aB+RO`PBRVKl{s>F3f|syDe=-WHwOT0FK$*v78d! zN;ZT=ktba00_0SW508ioccwBOiW|ZFK3B!Kz8P5n7q}~O%7m6w-x!`0FSf033TeFw z+>&M_P%E9uop58a#9UUDNA7}sf{Lp4$^Gd4y5Y;BbK%_o!Tn!7aJo;;7w$Z9=i@(j Cp%lvi literal 0 HcmV?d00001 diff --git a/base/ext_data/siege/classes/wookie.scl b/base/ext_data/siege/classes/wookie.scl new file mode 100644 index 0000000..a43432d --- /dev/null +++ b/base/ext_data/siege/classes/wookie.scl @@ -0,0 +1,22 @@ +//Siege class def file. + +ClassInfo +{ + name "Wookie" + weapons WP_MELEE|WP_BOWCASTER|WP_ROCKET_LAUNCHER|WP_CONCUSSION_RIFLE + //classflags CFL_HEAVYMELEE|CFL_SPEED_75 + // removing heavymelee for now because apparently it's WAY overpowered - it tears through walls far too quickly. Maybe some other way to fix later??? + classflags CFL_SPEED_75 + maxhealth 100 + starthealth 100 + maxarmor 0 + startarmor 0 + model "chewbacca" //this is optional, if it's here it forces the model to this. + skin "default" //this is optional, if it's here it forces the skin to this. + uishader "models/players/chewbacca/icon_default" + class_shader "gfx/mp/c_icon_heavy_weapons" + speed 0.75 +} + +description +"@SIEGE_HVY_REBEL_SPECIALIST" diff --git a/base/ext_data/siege/teams/Allies.team b/base/ext_data/siege/teams/Allies.team new file mode 100644 index 0000000..930324b --- /dev/null +++ b/base/ext_data/siege/teams/Allies.team @@ -0,0 +1,16 @@ +//Siege team definition file. + +name "New Alliance" +FriendlyShader "sprites/team_allies" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ + class1 "Rebel Driver" + class2 "Rebel Sniper" + class3 "Rebel Infantry" + class4 "Rebel Assault Specialist" + class5 "Wookie" + class6 "Rebel Demolitionist" + class7 "Kyle" + class8 "Luke" +} diff --git a/base/ext_data/siege/teams/DarkJedi.team b/base/ext_data/siege/teams/DarkJedi.team new file mode 100644 index 0000000..4f44420 --- /dev/null +++ b/base/ext_data/siege/teams/DarkJedi.team @@ -0,0 +1,16 @@ +//Siege team definition file. + +name "Dark Jedi" +FriendlyShader "sprites/team_darkjedi" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ + class1 "Dark Jedi Destroyer" + class2 "Dark Jedi Invader" + class3 "Dark Jedi Duelist" + class4 "Dark Jedi Knight" + class5 "Dark Side Marauder" + class6 "Dark Force Assassin" + class7 "Cultist" + class8 "Jedi Hunter" +} diff --git a/base/ext_data/siege/teams/Imperials.team b/base/ext_data/siege/teams/Imperials.team new file mode 100644 index 0000000..16e5303 --- /dev/null +++ b/base/ext_data/siege/teams/Imperials.team @@ -0,0 +1,16 @@ +//Siege team definition file. + +name "Imperial Remnant" +FriendlyShader "sprites/team_imperials" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ + class1 "Imperial Driver" + class2 "Imperial Sniper" + class3 "Imperial Stormtrooper" + class4 "Rocket Trooper" + class5 "Hazard Trooper" + class6 "Imperial Demolitionist" + class7 "Imperial Assassin" + class8 "Imperial Officer" +} diff --git a/base/ext_data/siege/teams/Jedi.team b/base/ext_data/siege/teams/Jedi.team new file mode 100644 index 0000000..a4347ef --- /dev/null +++ b/base/ext_data/siege/teams/Jedi.team @@ -0,0 +1,16 @@ +//Siege team definition file. + +name "Jedi" +FriendlyShader "sprites/team_jedi" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ + class1 "Jedi Healer" + class2 "Jedi Scout" + class3 "Jedi Duelist" + class4 "Jedi Force User" + class5 "Jedi Knight" + class6 "Jedi Guardian" + class7 "Kyle" + class8 "Protector" +} diff --git a/base/ext_data/siege/teams/Mercs.team b/base/ext_data/siege/teams/Mercs.team new file mode 100644 index 0000000..e2d950b --- /dev/null +++ b/base/ext_data/siege/teams/Mercs.team @@ -0,0 +1,16 @@ +//Siege team definition file. + +name "Mercenaries" +FriendlyShader "sprites/team_mercs" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ + class1 "Merc Driver" + class2 "Merc Sniper" + class3 "Mercenary" + class4 "Merc Assault Specialist" + class5 "Merc Heavy Weapons Specialist" + class6 "Merc Demolitionist" + class7 "Jedi Hunter" + class8 "Bounty Hunter" +} diff --git a/base/ext_data/siege/teams/Siege1_Imperial.team b/base/ext_data/siege/teams/Siege1_Imperial.team new file mode 100644 index 0000000..e6e2d06 --- /dev/null +++ b/base/ext_data/siege/teams/Siege1_Imperial.team @@ -0,0 +1,21 @@ +//Siege team definition file. + +name "Siege1_Imperials" +FriendlyShader "sprites/imperial_attack" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ +//Infantry + class1 "Imperial Snowtrooper" +//Heavy + class2 "Rocket Trooper" +//Demo + class3 "Imperial Demolitionist" +//Vanguard + class4 "Imperial Sniper" +//Support + class5 "Imperial Tech" +//Jedi + class6 "Dark Jedi Invader" +// class7 "Dark Side Marauder" +} diff --git a/base/ext_data/siege/teams/Siege1_Rebel.team b/base/ext_data/siege/teams/Siege1_Rebel.team new file mode 100644 index 0000000..d9167f4 --- /dev/null +++ b/base/ext_data/siege/teams/Siege1_Rebel.team @@ -0,0 +1,20 @@ +//Siege team definition file. + +name "Siege1_Rebels" +FriendlyShader "sprites/rebel_defend" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ +//Infantry + class1 "Rebel Infantry" +//Heavy + class2 "Wookie" +//Demo + class3 "Rebel Demolitionist" +//Vanguard + class4 "Rebel Sniper" +//Support + class5 "Rebel Tech" +//Jedi + class6 "Jedi Guardian" +} diff --git a/base/ext_data/siege/teams/Siege2_Merc.team b/base/ext_data/siege/teams/Siege2_Merc.team new file mode 100644 index 0000000..fc859ef --- /dev/null +++ b/base/ext_data/siege/teams/Siege2_Merc.team @@ -0,0 +1,22 @@ +//Siege team definition file. + +name "Siege2_Mercs" +FriendlyShader "sprites/imperial_defend" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ +//Infantry Class + class1 "Bounty Hunter" +// class1 "Mercenary" +//Heavy Class + class2 "Merc Assault Specialist" +// class2 "Merc Heavy Weapons Specialist" +//Demo Class + class3 "Merc Demolitionist" +//Vanguard Class + class4 "Merc Sniper" +//Support Class + class5 "Smuggler" +//Jedi Class + class6 "Dark Side Marauder" +} diff --git a/base/ext_data/siege/teams/Siege2_Rebel.team b/base/ext_data/siege/teams/Siege2_Rebel.team new file mode 100644 index 0000000..313b776 --- /dev/null +++ b/base/ext_data/siege/teams/Siege2_Rebel.team @@ -0,0 +1,20 @@ +//Siege team definition file. + +name "Siege2_Rebels" +FriendlyShader "sprites/rebel_attack" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ +//Infantry Class + class1 "Rebel Assault Specialist" +//Heavy Class + class2 "Wookie" +//Demo Class + class3 "Rebel Demolitionist" +//Vanguard Class + class4 "Rebel Sniper" +//Support Class + class5 "Rebel Tech" +//Jedi Class + class6 "Jedi Duelist" +} diff --git a/base/ext_data/siege/teams/Siege3_DarkJedi.team b/base/ext_data/siege/teams/Siege3_DarkJedi.team new file mode 100644 index 0000000..e34347a --- /dev/null +++ b/base/ext_data/siege/teams/Siege3_DarkJedi.team @@ -0,0 +1,20 @@ +//Siege team definition file. + +name "Siege3_DarkJedi" +FriendlyShader "sprites/imperial_defend" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ +//Infantry + class1 "Dark Side Mauler" +//Heavy + class2 "Dark Jedi Destroyer" +//Demo + class3 "Dark Jedi Demolitionist" +//Vanguard + class4 "Dark Jedi Interceptor" +//Support + class5 "Dark Jedi Tech" +//Jedi + class6 "Dark Jedi Duelist" +} diff --git a/base/ext_data/siege/teams/Siege3_Jedi.team b/base/ext_data/siege/teams/Siege3_Jedi.team new file mode 100644 index 0000000..afc33eb --- /dev/null +++ b/base/ext_data/siege/teams/Siege3_Jedi.team @@ -0,0 +1,20 @@ +//Siege team definition file. + +name "Siege3_Jedi" +FriendlyShader "sprites/rebel_attack" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ +//Infantry + class1 "Jedi Warrior" +//Heavy + class2 "Jedi Knight" +//Demo + class3 "Jedi Demolitionist" +//Vanguard + class4 "Jedi Scout" +//Support + class5 "Jedi Healer" +//Jedi + class6 "Jedi Lightsaber Master" +} diff --git a/base/ext_data/siege/teams/Siege4_Imperial.team b/base/ext_data/siege/teams/Siege4_Imperial.team new file mode 100644 index 0000000..af3fb1f --- /dev/null +++ b/base/ext_data/siege/teams/Siege4_Imperial.team @@ -0,0 +1,20 @@ +//Siege team definition file. + +name "Siege4_Imperials" +FriendlyShader "sprites/team_imperials" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ +//Infantry + class1 "Imperial Pilot" +//Heavy + class2 "Imperial Stormtrooper" +//Demo + class3 "Imperial Light Demolitionist" +//Vanguard + class4 "Imperial Light Sniper" +//Support + class5 "Imperial Light Tech" +//Jedi + class6 "Dark Jedi Apprentice" +} diff --git a/base/ext_data/siege/teams/Siege4_Rebel.team b/base/ext_data/siege/teams/Siege4_Rebel.team new file mode 100644 index 0000000..fd94ca2 --- /dev/null +++ b/base/ext_data/siege/teams/Siege4_Rebel.team @@ -0,0 +1,20 @@ +//Siege team definition file. + +name "Siege4_Rebels" +FriendlyShader "sprites/team_allies" //Name of shader to display above heads of teammates (to others on this team) + +Classes +{ +//Infantry + class1 "Rebel Pilot" +//Heavy + class2 "Rebel Heavy Infantry" +//Demo + class3 "Rebel Light Demolitionist" +//Vanguard + class4 "Rebel Light Sniper" +//Support + class5 "Rebel Light Tech" +//Jedi + class6 "Jedi Apprentice" +} diff --git a/base/ext_data/siege/teams/vssver.scc b/base/ext_data/siege/teams/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..cc9cf848d11fb368e009825c8c15194653204199 GIT binary patch literal 240 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiY$$473PD9iu`cYw4G$?EHoDi)3yc6KLGOA@EsEe@j?0@0{PP{au;VE zI<|}z$bSUn2kkDoc5J~BFrQC^fgw0cptkPm`DGyU`GNfP%aX#C*bjo`=K=Z0{e9=& wsy+ke&j<2vs~@W}yLW0CJ5W7?7z4u!xt6&gJ`<432;@)L9u_uf&j~Oe02ZK0IRF3v literal 0 HcmV?d00001 diff --git a/base/ext_data/vehicles/YT-1300.veh b/base/ext_data/vehicles/YT-1300.veh new file mode 100644 index 0000000..54529f0 --- /dev/null +++ b/base/ext_data/vehicles/YT-1300.veh @@ -0,0 +1,157 @@ +YT-1300 +{ +name YT-1300 +type VH_FIGHTER +numHands 2 +hideRider 1 +killRiderOnDeath 1 +lookYaw 45 +length 1280 +width 1024 +height 323 +g2radius 1280 +centerOfGravity "-0.222 0 0" +speedMax 2100 +turboSpeed 4000 +turboDuration 6000 +turboRecharge 8000 +speedMin 0 +acceleration 20 +decelIdle 5 +accelIdle 5 +speedIdle 0 +//test: speed stays at whatever you last set it to... +throttleSticks 1 +strafePerc 0.1 +bankingSpeed 4 +rollLimit 85 +pitchLimit 85 +braking 20 +mouseYaw 0.004 +mousePitch 0.004 +turningSpeed 8 +turnWhenStopped 0 +traction 18 +friction 1.5 +speedDependantTurning 1 +maxSlope 0.65 +mass 8000 +//armor 4000 +//malfunctionArmorLevel 1000 +//health_front 1000 +//health_back 1000 +//health_right 1000 +//health_left 1000 +//shields 3000 +armor 5000 +malfunctionArmorLevel 1200 +//armor 8000 +//malfunctionArmorLevel 2000 +health_front 1800 +health_back 1800 +health_right 1800 +health_left 1800 +shields 2500 +toughness 95.0 +model YT-1300 +//skin generic +riderAnim BOTH_VS_IDLE +radarIcon "gfx/menus/radar/yt-1300" +dmgIndicFrame "gfx/menus/radar/circle_base_frame_reb" +dmgIndicShield "gfx/menus/radar/circle_base_shield" +dmgIndicBackground "gfx/menus/radar/circle_base" +icon_front "gfx/menus/radar/yt-1300_front" +icon_back "gfx/menus/radar/yt-1300_back" +icon_right "gfx/menus/radar/yt-1300_right" +icon_left "gfx/menus/radar/yt-1300_left" +//shieldShader "gfx/misc/shields_green" +shieldShader "gfx/misc/shields_blue" +soundOn "sound/vehicles/yt-1300/on.wav" +soundLoop "sound/vehicles/yt-1300/loop.wav" +soundOff "sound/vehicles/yt-1300/off.wav" +soundFlyBy "sound/vehicles/yt-1300/flyby.wav" +soundFlyBy2 "sound/vehicles/yt-1300/flyby2.wav" +soundEngineStart "sound/vehicles/yt-1300/enginestart.wav" +soundHyper "sound/vehicles/common/hyperstartreb.wav" +soundTurbo "sound/vehicles/yt-1300/flyby.wav" +exhaustFX "ships/yt_exhaust" +turboFX "ships/yt_exhaust_turbo" +impactFX "ships/scrape_sparks" +explodeFX "ships/ship_explosion2" +dmgFX "ships/heavydmg" +injureFX "ships/lightdmg" +noseFX "ships/YT_nose" +lwingFX "ships/YT_lwing" +rwingFX "ships/YT_rwing" +hoverHeight 80 +hoverStrength 10 +explosionRadius 1000 +explosionDamage 1800 +maxPassengers 8 + +landingHeight 300 + +weap1 conc_missile_straight +weap1Delay 500 +weap1AmmoMax 30 + +weapMuzzle5 conc_missile_straight +weapMuzzle6 conc_missile_straight + +//turret 1 +turret1Weap yt_turbolaser +turret1Delay 100 +turret1AmmoMax 240 +turret1AmmoRechargeMS 500 +turret1YawBone guntop_yaw +//NOTE: we're setting YAW on ROLL because NPC_SetBoneAngles makes assumptions about our bone orientation that aren't true in this case +turret1YawAxis 2 +turret1PitchBone guntop_pitch +//NOTE: we're setting PITCH on YAW because NPC_SetBoneAngles makes assumptions about our bone orientation that aren't true in this case +turret1PitchAxis 1 +//turret1ClampYawL +//turret1ClampYawR +turret1ClampPitchU -90 +turret1ClampPitchD 20 +turret1Muzzle1 3 +turret1Muzzle2 4 +turret1TurnSpeed 10 +turret1Delay 100 +turret1AmmoMax 120 +turret1AI 1 +turret1AILead 1 +turret1AIRange 15000 +turret1PassengerNum 1 +turret1GunnerViewTag "*turretview_t" + +//turret 2 +turret2Weap yt_turbolaser +turret2Delay 100 +turret2AmmoMax 240 +turret2AmmoRechargeMS 500 +turret2YawBone gunbot_yaw +turret2YawAxis 2 +turret2PitchBone gunbot_pitch +turret2PitchAxis 1 +//turret2ClampYawL +//turret2ClampYawR +turret2ClampPitchU -20 +turret2ClampPitchD 90 +turret2Muzzle1 1 +turret2Muzzle2 2 +turret2TurnSpeed 10 +turret2Delay 100 +turret2AmmoMax 120 +turret2AI 1 +turret2AILead 1 +turret2AIRange 15000 +turret2PassengerNum 2 +turret2GunnerViewTag "*turretview_b" + +cameraOverride 1 +cameraRange 1000 +cameraVertOffset 220 +cameraPitchOffset 0 +cameraFOV 90 +cameraAlpha 0 +} diff --git a/base/ext_data/vehicles/atst_vehicle.veh b/base/ext_data/vehicles/atst_vehicle.veh new file mode 100644 index 0000000..5a852fa --- /dev/null +++ b/base/ext_data/vehicles/atst_vehicle.veh @@ -0,0 +1,69 @@ +ATST_vehicle +{ +name ATST_vehicle +type VH_WALKER +numHands 2 +hideRider 1 +killRiderOnDeath 1 +lookYaw 45 +lookPitch 45 +length 80 +width 80 +height 272 +centerOfGravity "-0.222 0 0" +speedMax 350 +speedMin -80 +acceleration 8 +decelIdle 20 +strafePerc 0.0 +bankingSpeed 0.0 +rollLimit 2 +pitchLimit 0 +braking 10 +mouseYaw 0.003 +mousePitch 0.01 +turningSpeed 5 +turnWhenStopped 0 +traction 100 +friction 100 +maxSlope 0.7 +mass 400 +armor 1500 +toughness 75.0 +model atst +//This just doesn't work on levels with fog, so... leave it off +//skin alpha +radarIcon "gfx/menus/radar/atst" +explosionRadius 400 +explosionDamage 1000 +explodeFX "ships/ship_explosion2" +explosionDelay 2450 +soundOn "sound/chars/atst/atst_hatch_close.mp3" +soundOff "sound/chars/atst/atst_hatch_open.mp3" + +weap1 atst_laser +weap1Delay 250 +weap1AmmoMax 50 +weap1AmmoRechargeMS 500 + +weapMuzzle1 atst_laser +weapMuzzle2 atst_laser + +//weapMuzzle3 ??? + +weap2 atst_rocket +weap2Delay 1000 +weap2Aim 1 +weap2AmmoMax 20 + +weapMuzzle4 atst_rocket + +cameraOverride 1 +cameraRange 300 +cameraVertOffset 150 +//cameraPitchOffset 0 +cameraFOV 100 +//This just doesn't work on levels with fog, so... leave it off +//cameraAlpha 0.25 +cameraPitchDependantVertOffset 0 +} \ No newline at end of file diff --git a/base/ext_data/vehicles/lambdashuttle.veh b/base/ext_data/vehicles/lambdashuttle.veh new file mode 100644 index 0000000..212e688 --- /dev/null +++ b/base/ext_data/vehicles/lambdashuttle.veh @@ -0,0 +1,145 @@ +LambdaShuttle +{ +name LambdaShuttle +type VH_FIGHTER +numHands 2 +hideRider 1 +killRiderOnDeath 1 +lookYaw 45 +length 500 +width 640 +height 248 +g2radius 640 +centerOfGravity "-0.222 0 0" +speedMax 1800 +turboSpeed 0 +speedMin 0 +acceleration 10 +decelIdle 5 +accelIdle 5 +speedIdle 200 +//test: speed stays at whatever you last set it to... +throttleSticks 1 +strafePerc 0 +bankingSpeed 0.5 +rollLimit 25 +pitchLimit 40 +braking 10 +mouseYaw 0.001 +mousePitch 0.001 +turningSpeed 2 +turnWhenStopped 0 +speedDependantTurning 1 +traction 12 +friction 1.5 +maxSlope 0.65 +mass 2500 +armor 2700 +malfunctionArmorLevel 2000 +shields 1500 +toughness 80.0 +model LambdaShuttle +//skin "models/players/lambdashuttle/model_mark2.skin" +riderAnim BOTH_VS_IDLE +radarIcon "gfx/menus/radar/shuttle" +dmgIndicFrame "gfx/menus/radar/circle_base_frame_reb" +dmgIndicShield "gfx/menus/radar/circle_base_shield" +dmgIndicBackground "gfx/menus/radar/circle_base" +icon_front "gfx/menus/radar/LS_front" +icon_back "gfx/menus/radar/LS_back" +icon_right "gfx/menus/radar/LS_right" +icon_left "gfx/menus/radar/LS_left" +crosshairShader "gfx/menus/radar/LS_reticle" +//shieldShader "gfx/misc/shields_green" +shieldShader "gfx/misc/shields_blue" +soundOn "sound/vehicles/shuttle/on.wav" +soundTakeOff "sound/vehicles/shuttle/takeoff.wav" +soundEngineStart "sound/vehicles/shuttle/enginestart.wav" +soundLoop "sound/vehicles/shuttle/loop.wav" +soundLand "sound/vehicles/shuttle/land.wav" +soundOff "sound/vehicles/shuttle/off.wav" +soundFlyBy "sound/vehicles/shuttle/flyby.wav" +soundFlyBy2 "sound/vehicles/shuttle/flyby2.wav" +soundHyper "sound/vehicles/common/hyperstartreb.wav" +exhaustFX "ships/shuttle_exhaust" +impactFX "ships/scrape_sparks" +explodeFX "ships/LS_explosion" +//trailFX "ships/wingtrail" +dmgFX "ships/heavydmg" +injureFX "ships/lightdmg" +noseFX "ships/LS_nose" +lwingFX "ships/LS_lwing" +rwingFX "ships/LS_rwing" +hoverHeight 80 +hoverStrength 10 +explosionRadius 400 +explosionDamage 1000 +maxPassengers 4 + +landingHeight 300 + +weap1 rebel_laser +weap1Aim 1 +weap1Delay 500 +weap1AmmoMax 80 +//NOTE: these are *always* linked... permanently linked muzzles don't use a cumulative delay, just the one that's specified here. +weap1link 2 + +weap2 mine +weap2Delay 1000 +weap2AmmoMax 10 + +weap1AmmoRechargeMS 500 + +//FIXME: just use a double-blaster shot effect on these +weapMuzzle1 rebel_laser +weapMuzzle2 rebel_laser +weapMuzzle3 rebel_laser +weapMuzzle4 rebel_laser + +//FIXME: these turrets need to animate to point forward when the wings are open (down) +//weapMuzzle5 rebel_laser +//weapMuzzle6 rebel_laser +//weapMuzzle7 rebel_laser +//weapMuzzle8 rebel_laser + +weapMuzzle9 mine +weapMuzzle10 mine + +//turret 1 +//turret1Weap rebel_laser +//turret1Delay 250 +//turret1AmmoMax 40 +//turret1AmmoRechargeMS 500 +//turret1PitchBone r_turret_bone +//turret1PitchAxis 2 +//turret1ClampPitchU -80 +//turret1ClampPitchD 0 +//turret1Muzzle1 5 +//turret1Muzzle2 6 +//turret1TurnSpeed 10 +//turret1PassengerNum 1 +//turret1GunnerViewTag "*turretview_r" + +//turret 2 +//turret2Weap rebel_laser +//turret2Delay 250 +//turret2AmmoMax 40 +//turret2AmmoRechargeMS 500 +//turret2PitchBone l_turret_bone +//turret2PitchAxis 2 +//turret2ClampPitchU -80 +//turret2ClampPitchD 0 +//turret2Muzzle1 7 +//turret2Muzzle2 8 +//turret2TurnSpeed 10 +//turret2PassengerNum 2 +//turret2GunnerViewTag "*turretview_l" + +cameraOverride 1 +cameraRange 900 +cameraVertOffset 256 +cameraPitchOffset 0 +cameraFOV 100 +cameraAlpha 0 +} diff --git a/base/ext_data/vehicles/rancor_vehicle.veh b/base/ext_data/vehicles/rancor_vehicle.veh new file mode 100644 index 0000000..7a9abcd --- /dev/null +++ b/base/ext_data/vehicles/rancor_vehicle.veh @@ -0,0 +1,41 @@ +Rancor_vehicle +{ +name Rancor_vehicle +type VH_ANIMAL +numHands 2 +lookYaw 45 +lookPitch 45 +length 128 +width 32 +height 32 +centerOfGravity "-0.222 0 0" +speedMax 350 +speedMin -80 +acceleration 8 +decelIdle 20 +strafePerc 0.0 +bankingSpeed 0.0 +rollLimit 2 +pitchLimit 0 +braking 10 +mouseYaw 0.006 +turningSpeed 5 +turnWhenStopped 0 +traction 100 +friction 100 +maxSlope 0.65 +mass 200 +armor 200 +toughness 30.0 +model rancor +riderAnim BOTH_FORCEHEAL_START +radarIcon "gfx/menus/radar/rancor" +soundOn "sound/vehicles/rancor/on.mp3" + +cameraOverride 1 +cameraRange 150 +cameraVertOffset 0 +cameraPitchOffset -30 +cameraFOV 80 +cameraAlpha 0 +} \ No newline at end of file diff --git a/base/ext_data/vehicles/swoop.veh b/base/ext_data/vehicles/swoop.veh new file mode 100644 index 0000000..9ad42ff --- /dev/null +++ b/base/ext_data/vehicles/swoop.veh @@ -0,0 +1,86 @@ +Swoop +{ +name Swoop +type VH_SPEEDER +numHands 2 +lookYaw 45 +lookPitch 20 +length 128 +width 32 +height 32 +centerOfGravity "-0.222 0 0" +speedMax 900 +speedMin -150 +turboSpeed 1900 +turboDuration 2000 +turboRecharge 8000 +acceleration 20 +decelIdle 10 +strafePerc 0.5 +bankingSpeed 0.5 +rollLimit 45 +pitchLimit 30 +braking 10 +mouseYaw 0.0038 +turningSpeed 5 +turnWhenStopped 0 +traction 12 +friction 1.5 +maxSlope 0.75 +mass 200 +armor 2000 +toughness 80.0 +malfunctionArmorLevel 1000 +model swoop +skin black|blue|gold|green|purple|default|silver +riderAnim BOTH_VS_IDLE +radarIcon "gfx/menus/radar/swoop" + +soundOn "sound/vehicles/swoop/on.mp3" +soundOff "sound/vehicles/swoop/off.mp3" +soundLoop "sound/vehicles/swoop/loop.wav" +soundTakeOff "sound/vehicles/swoop/on.mp3" +soundEngineStart "sound/vehicles/swoop/on.mp3" +soundSpin "sound/vehicles/swoop/loop.wav" +soundTurbo "sound/vehicles/swoop/sb_revup.mp3" +soundFlyBy "sound/vehicles/swoop/flyby1.mp3" +soundFlyBy2 "sound/vehicles/swoop/flyby2.mp3" +soundShift "sound/vehicles/swoop/sb_shift1.mp3" +soundShift2 "sound/vehicles/swoop/sb_shift2.mp3" +soundShift3 "sound/vehicles/swoop/sb_shift3.mp3" +soundShift4 "sound/vehicles/swoop/sb_shift4.mp3" + +exhaustFX "ships/burner" +impactFX "ships/scrape_sparks" +turboStartFX "ships/swoop_turbo_start" +armorLowFX "volumetric/black_smoke" +armorGoneFX "ships/fire" +flammable 1 +explosionRadius 100 +explosionDamage 250 +explodeFX "ships/swoop_explosion" +explosionDelay 4500 +wakeFX "ships/vehicle_wake" +gravity 800 +waterProof 1 +bouyancy 1 +hoverHeight 30 +hoverStrength 35 + +cameraOverride 1 +cameraRange 125 +cameraVertOffset 0 +cameraPitchOffset 0 +cameraFOV 100 +cameraAlpha 0 + +weap1 swoop_laser +weap1Aim 1 + +weap1Delay 100 + +weap1AmmoMax 2000 +weap1AmmoRechargeMS 200 + +weapMuzzle1 swoop_laser +} diff --git a/base/ext_data/vehicles/swoop_cin.veh b/base/ext_data/vehicles/swoop_cin.veh new file mode 100644 index 0000000..9e5afb5 --- /dev/null +++ b/base/ext_data/vehicles/swoop_cin.veh @@ -0,0 +1,71 @@ +Swoop_cin +{ +name Swoop_cin +type VH_SPEEDER +numHands 2 +lookYaw 45 +lookPitch 20 +length 128 +width 32 +height 32 +centerOfGravity "-0.222 0 0" +speedMax 800 +speedMin -50 +turboSpeed 2000 +acceleration 20 +decelIdle 10 +strafePerc 0.5 +bankingSpeed 0.5 +rollLimit 45 +pitchLimit 80 +braking 10 +mouseYaw 0.0038 +turningSpeed 5 +turnWhenStopped 0 +traction 12 +friction 1.5 +maxSlope 0.65 +mass 200 +armor 2000 +toughness 80.0 +malfunctionArmorLevel 1000 +model swoop +riderAnim BOTH_VS_IDLE +radarIcon "gfx/menus/radar/swoop" + +soundOn "sound/vehicles/swoop/on.mp3" +soundLoop "sound/vehicles/swoop/loop.wav" +lExhaustTag "*lexhaust" +rExhaustTag "*rexhaust" +exhaustFX "ships/burner" +impactFX "ships/scrape_sparks" +flammable 1 +explosionRadius 200 +explosionDamage 500 +explodeFX "ships/swoop_explosion" +explosionDelay 4500 +wakeFX "ships/vehicle_wake" +gravity 800 +waterProof 1 +bouyancy 1 +//only difference in cinematic one: doesn't hover +//hoverHeight 30 +hoverHeight 0 +hoverStrength 35 + +cameraOverride 1 +cameraRange 125 +cameraVertOffset 0 +cameraPitchOffset 0 +cameraFOV 100 +cameraAlpha 0 + +weap1 swoop_laser + +weap1Delay 100 + +weap1AmmoMax 2000 +weap1AmmoRechargeMS 200 + +weapMuzzle1 swoop_laser +} diff --git a/base/ext_data/vehicles/swoop_mp.veh b/base/ext_data/vehicles/swoop_mp.veh new file mode 100644 index 0000000..564c57c --- /dev/null +++ b/base/ext_data/vehicles/swoop_mp.veh @@ -0,0 +1,71 @@ +swoop_mp +{ +name swoop_mp +type VH_SPEEDER +numHands 2 +lookYaw 45 +lookPitch 20 +length 128 +width 32 +height 32 +centerOfGravity "-0.222 0 0" +speedMax 700 +speedMin -200 +turboSpeed 1400 +turboDuration 4000 +turboRecharge 8000 +acceleration 20 +decelIdle 10 +strafePerc 0.5 +bankingSpeed 0.5 +rollLimit 45 +pitchLimit 80 +braking 10 +mouseYaw 0.0038 +turningSpeed 5 +turnWhenStopped 0 +traction 12 +friction 1.5 +maxSlope 0.65 +mass 200 +armor 80 +toughness 80.0 +malfunctionArmorLevel 20 +model swoop +radarIcon "gfx/menus/radar/swoop" + +riderAnim BOTH_VS_IDLE +soundOn "sound/vehicles/swoop/on.mp3" +soundLoop "sound/vehicles/swoop/loop.wav" +exhaustFX "ships/burner" +impactFX "ships/scrape_sparks" +turboFX "ships/jet" +turboStartFX "ships/swoop_turbo_start" +flammable 1 +explosionRadius 200 +explosionDamage 500 +explodeFX "ships/swoop_explosion" +explosionDelay 4500 +wakeFX "ships/vehicle_wake" +gravity 800 +waterProof 1 +bouyancy 1 +hoverHeight 30 +hoverStrength 35 + +cameraOverride 1 +cameraRange 125 +cameraVertOffset 0 +cameraPitchOffset 0 +cameraFOV 100 +cameraAlpha 0 + +weap1 swoop_laser + +weap1Delay 100 + +weap1AmmoMax 2000 +weap1AmmoRechargeMS 200 + +weapMuzzle1 swoop_laser +} diff --git a/base/ext_data/vehicles/swoop_mp2.veh b/base/ext_data/vehicles/swoop_mp2.veh new file mode 100644 index 0000000..39c6e62 --- /dev/null +++ b/base/ext_data/vehicles/swoop_mp2.veh @@ -0,0 +1,73 @@ +swoop_mp2 +{ +name swoop_mp2 +type VH_SPEEDER +numHands 2 +lookYaw 45 +lookPitch 20 +length 128 +width 32 +height 32 +centerOfGravity "-0.222 0 0" +speedMax 700 +speedMin -200 +turboSpeed 1400 +turboDuration 4000 +turboRecharge 8000 +acceleration 20 +decelIdle 10 +strafePerc 0.5 +bankingSpeed 0.5 +rollLimit 45 +pitchLimit 80 +braking 10 +mouseYaw 0.0038 +turningSpeed 5 +turnWhenStopped 0 +traction 12 +friction 1.5 +maxSlope 0.65 +mass 200 +armor 80 +toughness 80.0 +malfunctionArmorLevel 20 +model swoop +skin blue +radarIcon "gfx/menus/radar/swoop" + +riderAnim BOTH_VS_IDLE +soundOn "sound/vehicles/swoop/on.mp3" +soundLoop "sound/vehicles/swoop/loop.wav" +exhaustFX "ships/burner" +impactFX "ships/scrape_sparks" +turboFX "ships/jet" +turboStartFX "ships/swoop_turbo_start" +flammable 1 +explosionRadius 200 +explosionDamage 500 +explodeFX "ships/swoop_explosion" +explosionDelay 4500 +wakeFX "ships/vehicle_wake" +gravity 800 +waterProof 1 +bouyancy 1 +hoverHeight 30 +hoverStrength 35 + +cameraOverride 1 +cameraRange 125 +cameraVertOffset 0 +cameraPitchOffset 0 +cameraFOV 100 +cameraAlpha 0 + +weap1 swoop_laser + +weap1Delay 100 + +weap1AmmoMax 2000 +weap1AmmoRechargeMS 200 + +weapMuzzle1 swoop_laser +} + diff --git a/base/ext_data/vehicles/tauntaun.veh b/base/ext_data/vehicles/tauntaun.veh new file mode 100644 index 0000000..edee274 --- /dev/null +++ b/base/ext_data/vehicles/tauntaun.veh @@ -0,0 +1,55 @@ +Tauntaun +{ +name Tauntaun +type VH_ANIMAL +numHands 2 +lookYaw 45 +lookPitch 45 +length 128 +width 32 +height 32 +centerOfGravity "-0.222 0 0" + +speedMax 500 +speedMin -50 +turboSpeed 800 +turboDuration 3000 +turboRecharge 8000 +acceleration 10 +decelIdle 20 + + +strafePerc 0.0 +bankingSpeed 0.0 +rollLimit 2 +pitchLimit 0 +braking 10 +mouseYaw 0.006 +turningSpeed 5 +turnWhenStopped 0 +traction 100 +friction 100 +maxSlope 0.55 +mass 200 +armor 300 +toughness 30.0 +model tauntaun +riderAnim BOTH_VS_IDLE +radarIcon "gfx/menus/radar/tauntaun" + +cameraOverride 1 +cameraRange 125 +cameraVertOffset 0 +cameraPitchOffset 0 +cameraFOV 80 +cameraAlpha 0 + + +soundOn "sound/chars/tauntaun/misc/buck1.mp3" +soundOff "sound/chars/tauntaun/misc/buck2.mp3" +soundTurbo "sound/chars/tauntaun/misc/anger1.mp3" +soundShift1 "sound/chars/tauntaun/misc/anger3.mp3" +soundShift2 "sound/chars/tauntaun/misc/chatter1.mp3" +soundShift3 "sound/chars/tauntaun/misc/chatter3.mp3" +soundShift4 "sound/chars/tauntaun/misc/pant1.mp3" +} \ No newline at end of file diff --git a/base/ext_data/vehicles/template.veh b/base/ext_data/vehicles/template.veh new file mode 100644 index 0000000..13cc952 --- /dev/null +++ b/base/ext_data/vehicles/template.veh @@ -0,0 +1,121 @@ + +/* + char *name; //unique name of the vehicle + + //general data + vehicleType_t type; //what kind of vehicle + int numHands; //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + float lookPitch; //How far you can look up and down off the forward of the vehicle + float lookYaw; //How far you can look left and right off the forward of the vehicle + float length; //how long it is - used for body length traces when turning/moving? + float width; //how wide it is - used for body length traces when turning/moving? + float height; //how tall it is - used for body length traces when turning/moving? + vec3_t centerOfGravity;//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats + float speedMax; //top speed + float turboSpeed; //turbo speed + float speedMin; //if < 0, can go in reverse + float speedIdle; //what speed it drifts to when no accel/decel input is given + float accelIdle; //if speedIdle > 0, how quickly it goes up to that speed + float acceleration; //when pressing on accelerator + float decelIdle; //when giving no input, how quickly it drops to speedIdle + float strafePerc; //multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + float bankingSpeed; //how quickly it pitches and rolls (not under player control) + float rollLimit; //how far it can roll to either side + float pitchLimit; //how far it can roll forward or backward + float braking; //when pressing on decelerator + float turningSpeed; //how quickly you can turn + qboolean turnWhenStopped;//whether or not you can turn when not moving + float traction; //how much your command input affects velocity + float friction; //how much velocity is cut on its own + float maxSlope; //the max slope that it can go up with control + + //durability stats + int mass; //for momentum and impact force (player mass is 10) + int armor; //total points of damage it can take + float toughness; //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + + //visuals & sounds + char *model; //what model to use - if make it an NPC's primary model, don't need this? + char *skin; //what skin to use - if make it an NPC's primary model, don't need this? + int riderAnim; //what animation the rider uses + + char *soundOn; //sound to play when get on it + char *soundLoop; //sound to loop while riding it + char *soundOff; //sound to play when get off + char *exhaustFX; //exhaust effect, played from "*exhaust" bolt(s) + char *trailFX; //trail effect, played from "*trail" bolt(s) + char *impactFX; //impact effect, for when it bumps into something + char *explodeFX; //explosion effect, for when it blows up (should have the sound built into explosion effect) + + //other misc stats + int gravity; //normal is 800 + float hoverHeight; //if 0, it's a ground vehicle + float hoverStrength; //how hard it pushes off ground when less than hover height... causes "bounce", like shocks + qboolean waterProof; //can drive underwater if it has to + float bouyancy; //when in water, how high it floats (1 is neutral bouyancy) + int fuelMax; //how much fuel it can hold (capacity) + int fuelRate; //how quickly is uses up fuel + int visibility; //for sight alerts + int loudness; //for sound alerts + float explosionRadius;//range of explosion + int explosionDamage;//damage of explosion + + int maxPassengers; // The max number of passengers this vehicle may have (Default = 0). + qboolean hideRider; // rider (and passengers?) should not be drawn + qboolean killRiderOnDeath;//if rider is on vehicle when it dies, they should die + qboolean flammable; //whether or not the vehicle should catch on fire before it explodes + int explosionDelay; //how long the vehicle should be on fire/dying before it explodes + //camera stuff + qboolean cameraOverride; //whether or not to use all of the following 3rd person camera override values + float cameraRange; //how far back the camera should be - normal is 80 + float cameraVertOffset;//how high over the vehicle origin the camera should be - normal is 16 + float cameraHorzOffset;//how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + float cameraPitchOffset;//a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + float cameraFOV; //third person camera FOV, default is 80 + qboolean cameraAlpha; //fade out the vehicle if it's in the way of the crosshair + +{ +name tie-fighter +type VH_FIGHTER +numHands 2 +lookYaw 45 +length 128 +width 32 +height 32 +centerOfGravity "-0.222 0 0" +speedMax 1400 +turboSpeed 2000 +speedMin -100 +acceleration 20 +decelIdle 0 +strafePerc 0.0 +bankingSpeed 0.5 +rollLimit 45 +pitchLimit 80 +braking 20 +turningSpeed 5 +turnWhenStopped 0 +traction 12 +friction 0.5 +maxSlope 0.65 +mass 2000 +armor 800 +toughness 3.5 +model x-wing +riderAnim BOTH_VS_IDLE +soundOn "sound/ships/swoop/on.mp3" +soundLoop "sound/ships/swoop/loop.wav" +exhaustFX "ships/burner" +impactFX "ships/scrape_sparks" +explodeFX "ships/ship_explosion2" +hoverHeight 80 +hoverStrength 10 +explosionRadius 400 +explosionDamage 1000 +} + +*/ \ No newline at end of file diff --git a/base/ext_data/vehicles/tie-bomber.veh b/base/ext_data/vehicles/tie-bomber.veh new file mode 100644 index 0000000..aa13737 --- /dev/null +++ b/base/ext_data/vehicles/tie-bomber.veh @@ -0,0 +1,118 @@ +tie-bomber +{ +name tie-bomber +type VH_FIGHTER +numHands 2 +hideRider 1 +killRiderOnDeath 1 +lookYaw 45 +length 360 +width 400 +height 256 +g2radius 400 +centerOfGravity "-0.222 0 0" +speedMax 2000 +turboSpeed 3200 +turboDuration 3000 +turboRecharge 6000 +speedMin 0 +acceleration 20 +decelIdle 5 +accelIdle 10 +speedIdle 500 +//test: speed stays at whatever you last set it to... +throttleSticks 1 +strafePerc 0.05 +bankingSpeed 0.5 +rollLimit 40 +pitchLimit 60 +braking 15 +mouseYaw 0.0025 +mousePitch 0.0025 +turningSpeed 4.5 +turnWhenStopped 0 +traction 10 +friction 0.4 +speedDependantTurning 1 +maxSlope 0.65 +mass 800 +//armor 1600 +//malfunctionArmorLevel 600 +//health_front 500 +//health_back 300 +//health_right 400 +//health_left 400 +armor 1700 +malfunctionArmorLevel 600 +//armor 3000 +//malfunctionArmorLevel 1000 +health_front 400 +health_back 600 +health_right 600 +health_left 600 +shields 0 +toughness 85.0 +model tie_bomber +riderAnim BOTH_VS_IDLE +radarIcon "gfx/menus/radar/tieB" +dmgIndicFrame "gfx/menus/radar/circle_base_frame_imp" +dmgIndicShield "gfx/menus/radar/circle_base_shield" +dmgIndicBackground "gfx/menus/radar/circle_base" +icon_front "gfx/menus/radar/TB_front" +icon_back "gfx/menus/radar/TB_back" +icon_right "gfx/menus/radar/TB_right" +icon_left "gfx/menus/radar/TB_left" +crosshairShader "gfx/menus/radar/TB_reticle" +soundOn "sound/vehicles/tie-bomber/on.wav" +soundLoop "sound/vehicles/tie-bomber/loop.wav" +soundOff "sound/vehicles/tie-bomber/off.wav" +soundFlyBy "sound/vehicles/tie-bomber/flyby.wav" +soundFlyBy2 "sound/vehicles/tie-bomber/flyby2.wav" +soundTurbo "sound/vehicles/tie-bomber/turbo.wav" +soundHyper "sound/vehicles/common/hyperstartimp.wav" +exhaustFX "ships/tiebomber_exhaust" +turboFX "ships/tiebomber_exhaust_turbo" +impactFX "ships/scrape_sparks" +explodeFX "ships/TB_explosion" +//trailFX "ships/wingtrail" +dmgFX "ships/heavydmg" +injureFX "ships/lightdmg" +noseFX "ships/TB_nose" +lwingFX "ships/TB_lwing" +rwingFX "ships/TB_rwing" +flammable 1 +hoverHeight 80 +hoverStrength 10 +explosionRadius 400 +explosionDamage 1000 + +surfDestruction 1 + +landingHeight 300 + +weap1 imp_laser +weap1Delay 150 +weap1Aim 1 +weap1AmmoMax 20 +weap1AmmoRechargeMS 500 + +weap2 proton_torpedo +weap2Delay 800 +weap2Aim 1 +weap2AmmoMax 12 + + +weapMuzzle1 proton_torpedo +weapMuzzle2 imp_laser +weapMuzzle3 imp_laser + +//should *all* ships really be able to do this? +weap1Link 1 + +cameraOverride 1 +cameraRange 500 +cameraVertOffset 174 +cameraPitchOffset 0 +cameraFOV 100 +cameraAlpha 0 +} \ No newline at end of file diff --git a/base/ext_data/vehicles/tie-bomber2.veh b/base/ext_data/vehicles/tie-bomber2.veh new file mode 100644 index 0000000..fe2a897 --- /dev/null +++ b/base/ext_data/vehicles/tie-bomber2.veh @@ -0,0 +1,119 @@ +tie-bomber2 +{ +name tie-bomber2 +type VH_FIGHTER +numHands 2 +hideRider 1 +killRiderOnDeath 1 +lookYaw 45 +length 360 +width 400 +height 256 +g2radius 400 +centerOfGravity "-0.222 0 0" +speedMax 2000 +turboSpeed 3200 +turboDuration 2000 +turboRecharge 6000 +speedMin 0 +acceleration 20 +decelIdle 5 +accelIdle 10 +speedIdle 500 +//test: speed stays at whatever you last set it to... +throttleSticks 1 +strafePerc 0.05 +bankingSpeed 0.5 +rollLimit 40 +pitchLimit 60 +braking 15 +mouseYaw 0.0025 +mousePitch 0.0025 +turningSpeed 4.5 +turnWhenStopped 0 +traction 10 +friction 0.4 +speedDependantTurning 1 +maxSlope 0.65 +mass 800 +//armor 1600 +//malfunctionArmorLevel 600 +//health_front 500 +//health_back 300 +//health_right 400 +//health_left 400 +armor 1700 +malfunctionArmorLevel 600 +//armor 3000 +//malfunctionArmorLevel 1000 +health_front 400 +health_back 600 +health_right 600 +health_left 600 +shields 0 +toughness 85.0 +model tie_bomber +riderAnim BOTH_VS_IDLE +radarIcon "gfx/menus/radar/tieB" +dmgIndicFrame "gfx/menus/radar/circle_base_frame_imp" +dmgIndicShield "gfx/menus/radar/circle_base_shield" +dmgIndicBackground "gfx/menus/radar/circle_base" +icon_front "gfx/menus/radar/TB_front" +icon_back "gfx/menus/radar/TB_back" +icon_right "gfx/menus/radar/TB_right" +icon_left "gfx/menus/radar/TB_left" +crosshairShader "gfx/menus/radar/TB_reticle" +soundOn "sound/vehicles/tie-bomber/on.wav" +soundLoop "sound/vehicles/tie-bomber/loop.wav" +soundOff "sound/vehicles/tie-bomber/off.wav" +soundFlyBy "sound/vehicles/tie-bomber/flyby.wav" +soundFlyBy2 "sound/vehicles/tie-bomber/flyby2.wav" +soundHyper "sound/vehicles/common/hyperstartimp.wav" +soundTurbo "sound/vehicles/tie-bomber/turbo.wav" +exhaustFX "ships/tiebomber_exhaust" +turboFX "ships/tiebomber_exhaust_turbo" +impactFX "ships/scrape_sparks" +explodeFX "ships/TB_explosion" +//trailFX "ships/wingtrail" +dmgFX "ships/heavydmg" +injureFX "ships/lightdmg" +noseFX "ships/TB_nose" +lwingFX "ships/TB_lwing" +rwingFX "ships/TB_rwing" +flammable 1 +hoverHeight 80 +hoverStrength 10 +explosionRadius 400 +explosionDamage 1000 + +surfDestruction 1 + +landingHeight 300 + +weap1 imp_laser +weap2 bomb + +weap1Delay 150 +weap2Delay 800 + +weap1Aim 1 + +weap1AmmoMax 20 +weap2AmmoMax 64 + +weap1AmmoRechargeMS 500 + +weapMuzzle2 imp_laser +weapMuzzle3 imp_laser +weapMuzzle4 bomb + +//should *all* ships really be able to do this? +weap1Link 1 + +cameraOverride 1 +cameraRange 500 +cameraVertOffset 174 +cameraPitchOffset 30 +cameraFOV 100 +cameraAlpha 0 +} \ No newline at end of file diff --git a/base/ext_data/vehicles/tie-fighter.veh b/base/ext_data/vehicles/tie-fighter.veh new file mode 100644 index 0000000..570d944 --- /dev/null +++ b/base/ext_data/vehicles/tie-fighter.veh @@ -0,0 +1,112 @@ +tie-fighter +{ +name tie-fighter +type VH_FIGHTER +numHands 2 +hideRider 1 +killRiderOnDeath 1 +lookYaw 45 +length 240 +width 320 +height 320 +g2radius 320 +centerOfGravity "-0.222 0 0" +speedMax 2400 +turboSpeed 4000 +turboDuration 2000 +turboRecharge 6000 +speedMin 0 +acceleration 25 +decelIdle 12 +accelIdle 20 +speedIdle 1200 +//test: speed stays at whatever you last set it to... +throttleSticks 1 +strafePerc 0.1125 +bankingSpeed 0.5 +rollLimit 45 +pitchLimit 80 +//rollLimit -1 +//pitchLimit -1 +braking 30 +mouseYaw 0.003 +mousePitch 0.003 +turningSpeed 6 +turnWhenStopped 0 +traction 18 +friction 0.75 +speedDependantTurning 1 +maxSlope 0.65 +mass 500 +//armor 1000 +//malfunctionArmorLevel 500 +//health_front 300 +//health_back 200 +//health_right 250 +//health_left 250 +armor 750 +malfunctionArmorLevel 400 +//armor 1800 +//malfunctionArmorLevel 600 +health_front 200 +health_back 200 +health_right 400 +health_left 400 +toughness 75.0 +model tie_fighter +riderAnim BOTH_VS_IDLE +radarIcon "gfx/menus/radar/tieF" +dmgIndicFrame "gfx/menus/radar/circle_base_frame_imp" +dmgIndicShield "gfx/menus/radar/circle_base_shield" +dmgIndicBackground "gfx/menus/radar/circle_base" +icon_front "gfx/menus/radar/TF_front" +icon_back "gfx/menus/radar/TF_back" +icon_right "gfx/menus/radar/TF_right" +icon_left "gfx/menus/radar/TF_left" +crosshairShader "gfx/menus/radar/TF_reticle" +soundOn "sound/vehicles/tie/on.wav" +soundLoop "sound/vehicles/tie/loop.wav" +soundOff "sound/vehicles/tie/off.wav" +soundFlyBy "sound/vehicles/tie/flyby.wav" +soundFlyBy2 "sound/vehicles/tie/flyby2.wav" +soundHyper "sound/vehicles/common/hyperstartimp.wav" +soundTurbo "sound/vehicles/tie/flyby.wav" +exhaustFX "ships/tiefighter_exhaust" +turboFX "ships/tiefighter_exhaust_turbo" +impactFX "ships/scrape_sparks" +explodeFX "ships/TF_explosion" +//trailFX "ships/wingtrail" +dmgFX "ships/heavydmg" +injureFX "ships/lightdmg" +noseFX "ships/TF_nose" +lwingFX "ships/TF_lwing" +rwingFX "ships/TF_rwing" +flammable 1 +hoverHeight 80 +hoverStrength 10 +explosionRadius 400 +explosionDamage 1000 + +surfDestruction 1 + +landingHeight 300 + +weap1 imp_laser +weap1Delay 50 + +weap1AmmoMax 50 +weap1AmmoRechargeMS 250 + +weapMuzzle1 imp_laser +weapMuzzle2 imp_laser + +//should *all* ships really be able to do this? +weap1Link 1 + +cameraOverride 1 +cameraRange 500 +cameraVertOffset 20 +cameraPitchOffset 10 +cameraFOV 100 +cameraAlpha 0 +} \ No newline at end of file diff --git a/base/ext_data/vehicles/vssver.scc b/base/ext_data/vehicles/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..eb8f1a933a9f1d9ab216e26100b9604476111e54 GIT binary patch literal 304 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiYeVGS-gCBy&*hQbUCKg$IUFlpUe<^>cn0`lLm zfAtbO`+S)RkZ%m+ubm!h(N^_#nIMpF0_3;NNHu?S?VcYdEHWaQen%o)gE3FNoT^GOW~`?yRO$X^BI-~3tq1SGEqtu2D12cB literal 0 HcmV?d00001 diff --git a/base/ext_data/vehicles/wampa_vehicle.veh b/base/ext_data/vehicles/wampa_vehicle.veh new file mode 100644 index 0000000..31f4d07 --- /dev/null +++ b/base/ext_data/vehicles/wampa_vehicle.veh @@ -0,0 +1,41 @@ +wampa_vehicle +{ +name wampa_vehicle +type VH_ANIMAL +numHands 2 +lookYaw 45 +lookPitch 45 +length 128 +width 32 +height 32 +centerOfGravity "-0.222 0 0" +speedMax 350 +speedMin -80 +acceleration 8 +decelIdle 20 +strafePerc 0.0 +bankingSpeed 0.0 +rollLimit 2 +pitchLimit 0 +braking 10 +mouseYaw 0.006 +turningSpeed 5 +turnWhenStopped 0 +traction 100 +friction 100 +maxSlope 0.65 +mass 200 +armor 200 +toughness 30.0 +model wampa +riderAnim BOTH_FORCEHEAL_START +radarIcon "gfx/menus/radar/wampa" +soundOn "sound/vehicles/wampa/on.mp3" + +cameraOverride 1 +cameraRange 150 +cameraVertOffset 0 +cameraPitchOffset -30 +cameraFOV 80 +cameraAlpha 0 +} \ No newline at end of file diff --git a/base/ext_data/vehicles/weapons/atst_laser.vwp b/base/ext_data/vehicles/weapons/atst_laser.vwp new file mode 100644 index 0000000..e3d2c79 --- /dev/null +++ b/base/ext_data/vehicles/weapons/atst_laser.vwp @@ -0,0 +1,18 @@ +atst_laser +{ +name atst_laser +projectile 1 +muzzleFX "atst/main_muzzleflash" +shotFX "atst/shot_red" +impactFX "atst/wall_impact" +g2MarkShader "gfx/damage/bodyburnmark1" +g2MarkSize 16 +speed 1300 +damage 75 +splashDamage 20 +splashRadius 50 +ammoPerShot 1 +width 8 +height 8 +lifetime 10000 +} diff --git a/base/ext_data/vehicles/weapons/atst_rocket.vwp b/base/ext_data/vehicles/weapons/atst_rocket.vwp new file mode 100644 index 0000000..2b7a4ef --- /dev/null +++ b/base/ext_data/vehicles/weapons/atst_rocket.vwp @@ -0,0 +1,24 @@ +atst_rocket +{ +name atst_rocket +projectile 1 +model "models/weapons2/merr_sonn/projectile.md3" +muzzleFX "atst/alt_muzzleflash" +shotFX "atst/side_alt_shot" +impactFX "atst/side_alt_explosion" +g2MarkShader "gfx/damage/bodybigburnmark1" +g2MarkSize 48 +loopSound "sound/weapons/rocket/missleloop.wav" +speed 1100 +homing 0 +lockOnTime 0 +damage 150 +splashDamage 150 +splashRadius 250 +ammoPerShot 1 +health 10 +width 10 +height 10 +lifetime 10000 +explodeOnExpire 1 +} diff --git a/base/ext_data/vehicles/weapons/bomb.vwp b/base/ext_data/vehicles/weapons/bomb.vwp new file mode 100644 index 0000000..4b7e39c --- /dev/null +++ b/base/ext_data/vehicles/weapons/bomb.vwp @@ -0,0 +1,18 @@ +bomb +{ +name bomb +projectile 1 +hasGravity 1 +muzzleFX "ships/bomb_launch" +shotFX "ships/bomb" +impactFX "ships/mine_impact" +g2MarkShader "gfx/damage/bodybigburnmark1" +g2MarkSize 100 +speed 50 +//damage 1000 +//splashDamage 100 +damage 1000 +splashDamage 300 +splashRadius 500 +ammoPerShot 1 +} diff --git a/base/ext_data/vehicles/weapons/conc_missile_straight.vwp b/base/ext_data/vehicles/weapons/conc_missile_straight.vwp new file mode 100644 index 0000000..672fa6e --- /dev/null +++ b/base/ext_data/vehicles/weapons/conc_missile_straight.vwp @@ -0,0 +1,25 @@ +conc_missile_straight +{ +name conc_missile_straight +projectile 1 +muzzleFX "ships/conc_muzzleflash" +shotFX "ships/reb_torpshot" +impactFX "ships/conc_impact" +g2MarkShader "gfx/damage/bodybigburnmark1" +g2MarkSize 72 +loopSound "sound/vehicles/weapons/conc_missile/loop.wav" +speed 10000 +homing 0 +lockOnTime 0 +//damage 300 +//splashDamage 200 +damage 500 +splashDamage 200 +splashRadius 200 +ammoPerShot 1 +health 10 +width 6 +height 6 +lifetime 10000 +explodeOnExpire 1 +} diff --git a/base/ext_data/vehicles/weapons/concussion_missile.vwp b/base/ext_data/vehicles/weapons/concussion_missile.vwp new file mode 100644 index 0000000..4b8fbc5 --- /dev/null +++ b/base/ext_data/vehicles/weapons/concussion_missile.vwp @@ -0,0 +1,28 @@ +concussion_missile +{ +name concussion_missile +projectile 1 +muzzleFX "ships/reb_torpmuzzleflash" +shotFX "ships/reb_torpshot" +impactFX "ships/conc_impact" +g2MarkShader "gfx/damage/bodybigburnmark1" +g2MarkSize 72 +loopSound "sound/vehicles/weapons/conc_missile/loop.wav" +speed 5000 +//homing 0.75 +//homingFOV -0.25 +homing 0.45 +homingFOV 0.2 +lockOnTime 2000 +//damage 300 +//splashDamage 200 +damage 500 +splashDamage 200 +splashRadius 200 +ammoPerShot 1 +health 10 +width 6 +height 6 +lifetime 30000 +explodeOnExpire 1 +} diff --git a/base/ext_data/vehicles/weapons/imp_laser.vwp b/base/ext_data/vehicles/weapons/imp_laser.vwp new file mode 100644 index 0000000..38d5ab7 --- /dev/null +++ b/base/ext_data/vehicles/weapons/imp_laser.vwp @@ -0,0 +1,18 @@ +imp_laser +{ +name imp_laser +projectile 1 +muzzleFX "ships/imp_blastermuzzleflash" +shotFX "ships/imp_blastershot" +impactFX "ships/imp_blasterimpact" +g2MarkShader "gfx/effects/scorch" +g2MarkSize 56 +//loopSound +speed 20000 +damage 100 +//damage 50 +splashDamage 50 +splashRadius 100 +ammoPerShot 1 +lifetime 2000 +} diff --git a/base/ext_data/vehicles/weapons/ion_blaster.vwp b/base/ext_data/vehicles/weapons/ion_blaster.vwp new file mode 100644 index 0000000..479eb01 --- /dev/null +++ b/base/ext_data/vehicles/weapons/ion_blaster.vwp @@ -0,0 +1,16 @@ +ion_blaster +{ +name ion_blaster +projectile 1 +ionWeapon 1 +muzzleFX "ships/ion_muzzleflash" +shotFX "ships/ion_blastershot" +impactFX "ships/ion_blasterimpact" +//loopSound +speed 20000 +//damage 40 +//splashDamage 50 +//splashRadius 80 +ammoPerShot 1 +lifetime 2000 +} diff --git a/base/ext_data/vehicles/weapons/mine.vwp b/base/ext_data/vehicles/weapons/mine.vwp new file mode 100644 index 0000000..049946b --- /dev/null +++ b/base/ext_data/vehicles/weapons/mine.vwp @@ -0,0 +1,23 @@ +mine +{ +name mine +projectile 1 +muzzleFX "ships/mine_launch" +shotFX "ships/mine" +impactFX "ships/mine_impact" +g2MarkShader "gfx/damage/bodybigburnmark1" +g2MarkSize 100 +loopSound "sound/vehicles/weapons/mine/loop.wav" +speed 0 +//damage 2000 +//splashDamage 500 +damage 2000 +splashDamage 500 +splashRadius 500 +ammoPerShot 1 +health 100 +width 12 +height 12 +lifetime 180000 +explodeOnExpire 1 +} diff --git a/base/ext_data/vehicles/weapons/proton_torpedo.vwp b/base/ext_data/vehicles/weapons/proton_torpedo.vwp new file mode 100644 index 0000000..0ede7ff --- /dev/null +++ b/base/ext_data/vehicles/weapons/proton_torpedo.vwp @@ -0,0 +1,28 @@ +proton_torpedo +{ +name proton_torpedo +projectile 1 +muzzleFX "ships/imp_torpmuzzleflash" +shotFX "ships/imp_torpshot" +impactFX "ships/proton_impact" +g2MarkShader "gfx/effects/scorch" +g2MarkSize 72 +loopSound "sound/vehicles/weapons/proton/loop.wav" +speed 4000 +//homing 0.5 +//homingFOV 0 +homing 0.25 +homingFOV 0.4 +lockOnTime 2400 +//damage 800 +//splashDamage 300 +damage 800 +splashDamage 300 +splashRadius 350 +ammoPerShot 1 +health 10 +width 6 +height 6 +lifetime 30000 +explodeOnExpire 1 +} diff --git a/base/ext_data/vehicles/weapons/rebel_laser.vwp b/base/ext_data/vehicles/weapons/rebel_laser.vwp new file mode 100644 index 0000000..20d0d78 --- /dev/null +++ b/base/ext_data/vehicles/weapons/rebel_laser.vwp @@ -0,0 +1,18 @@ +rebel_laser +{ +name rebel_laser +projectile 1 +muzzleFX "ships/reb_blastermuzzleflash" +shotFX "ships/reb_blastershot" +impactFX "ships/reb_blasterimpact" +g2MarkShader "gfx/effects/scorch" +g2MarkSize 52 +//loopSound +speed 20000 +damage 75 +//damage 40 +splashDamage 35 +splashRadius 80 +ammoPerShot 1 +lifetime 2000 +} diff --git a/base/ext_data/vehicles/weapons/swoop_laser.vwp b/base/ext_data/vehicles/weapons/swoop_laser.vwp new file mode 100644 index 0000000..9da2611 --- /dev/null +++ b/base/ext_data/vehicles/weapons/swoop_laser.vwp @@ -0,0 +1,14 @@ +swoop_laser +{ +name swoop_laser +projectile 1 +saberBlockable 1 +muzzleFX "ships/swoop_blastermuzzleflash" +shotFX "ships/swoop_blastershot" +impactFX "ships/swoop_blasterhit" +g2MarkShader "gfx/damage/bodyburnmark1" +g2MarkSize 16 +speed 2500 +damage 10 +ammoPerShot 1 +} diff --git a/base/ext_data/vehicles/weapons/swoop_rocket.vwp b/base/ext_data/vehicles/weapons/swoop_rocket.vwp new file mode 100644 index 0000000..ce7f226 --- /dev/null +++ b/base/ext_data/vehicles/weapons/swoop_rocket.vwp @@ -0,0 +1,23 @@ +swoop_rocket +{ +name swoop_rocket +projectile 1 +muzzleFX "ships/conc_muzzleflash" +shotFX "ships/reb_torpshot" +impactFX "ships/conc_impact" +g2MarkShader "gfx/damage/bodybigburnmark1" +g2MarkSize 48 +loopSound "sound/vehicles/weapons/conc_missile/loop.wav" +speed 10000 +homing 0 +lockOnTime 0 +damage 300 +splashDamage 200 +splashRadius 160 +ammoPerShot 1 +health 10 +width 6 +height 6 +lifetime 10000 +explodeOnExpire 1 +} diff --git a/base/ext_data/vehicles/weapons/template.vwp b/base/ext_data/vehicles/weapons/template.vwp new file mode 100644 index 0000000..a19d60e --- /dev/null +++ b/base/ext_data/vehicles/weapons/template.vwp @@ -0,0 +1,26 @@ +//name //(STRING)name of weapon, use same name in .veh file, use no spaces or special characters +//{ +//name //(STRING) Unique name of weapon, must be same as the name above +//projectile //(INT)0 = traceline, 1 = projectile (missile entity) +//hasGravity //(INT)0 = no grav, 1 = normal grav (on projectiles only) +//ionWeapon //(INT) 0 = not an ion weapon, 1 = disables ship shields and sends them out of control +//muzzleFX //(STRING)path to Muzzle Effect, starting from "effects" +//shotFX //(STRING)path to Shot Effect, starting from "effects" +//impactFX //(STRING)path to Impact Effect, starting from "effects" +//g2MarkShaderHandle //(STRING) name of shader to use for G2 marks made on other models when hit by this projectile +//g2MarkSize //(FLOAT) size (diameter) of the ghoul2 mark +//loopSound //(STRING)path to loopSound, starting from "base" +//speed //(FLOAT)speed of projectile/range of traceline +//homing //(FLOAT)0.0 = not homing, 0.5 = half vel to targ, half cur vel, 1.0 = all vel to targ +//homingFOV //(FLOAT, -1.0 to 1.0) default 0. Missile will lose lock on if DotProduct of missile direction and direction to target ever drops below this (-1 to 1, -1 = never lose target, 0 = lose if ship gets behind missile, 1 = pretty much will lose it's target right away) +//lockOnTime //(INT)0 = no lock time needed, else # of ms needed to lock on +//damage //(INT)damage done when traceline or projectile directly hits target +//splashDamage //(INT)damage done to ents in splashRadius of end of traceline or projectile origin on impact +//splashRadius //(FLOAT)radius that ent must be in to take splashDamage (linear fall-off) +//ammoPerShot //(INT)how much "ammo" each shot takes +//health //(INT)if non-zero, projectile can be shot, takes this much damage before being destroyed +//width //(FLOAT)width of traceline or bounding box of projecile (non-rotating!) +//height //(FLOAT)height of traceline or bounding box of projecile (non-rotating!) +//lifetime //(INT) after this many milliseconds, it will remove itself +//explodeOnExpire //(INT) if set to 1, it will explode when lifetime is up, not just remove itself +//} diff --git a/base/ext_data/vehicles/weapons/vssver.scc b/base/ext_data/vehicles/weapons/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..67407f1c0f9e7d283baf736114489e41cce5907a GIT binary patch literal 256 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiYKD)q0QCdvQ?(}8s3<3;e)5Ar}_<{UcK)x6Mj&=9k*MsF}1Nr9nj^DSswRRahP<{@O zZ+XYFV%ma*D@9$ RUl_<2^Jy}2lbH_Y0{}=3P}cwe literal 0 HcmV?d00001 diff --git a/base/ext_data/vehicles/weapons/yt_turbolaser.vwp b/base/ext_data/vehicles/weapons/yt_turbolaser.vwp new file mode 100644 index 0000000..bee6470 --- /dev/null +++ b/base/ext_data/vehicles/weapons/yt_turbolaser.vwp @@ -0,0 +1,19 @@ +yt_turbolaser +{ +name yt_turbolaser +projectile 1 +muzzleFX "ships/yt_turbomuzzleflash" +shotFX "ships/reb_blastershot" +impactFX "ships/reb_blasterimpact" +g2MarkShader "gfx/effects/scorch" +g2MarkSize 64 +//loopSound +speed 25000 +//damage 500 +//splashDamage 160 +damage 500 +splashDamage 160 +splashRadius 200 +ammoPerShot 1 +lifetime 2000 +} diff --git a/base/ext_data/vehicles/wildtauntaun.veh b/base/ext_data/vehicles/wildtauntaun.veh new file mode 100644 index 0000000..9dc13ef --- /dev/null +++ b/base/ext_data/vehicles/wildtauntaun.veh @@ -0,0 +1,55 @@ +WildTauntaun +{ +name WildTauntaun +type VH_ANIMAL +numHands 2 +lookYaw 45 +lookPitch 45 +length 128 +width 32 +height 32 +centerOfGravity "-0.222 0 0" +speedMax 500 +speedMin -50 +turboSpeed 800 +turboDuration 3000 +turboRecharge 8000 +acceleration 10 +decelIdle 20 + + +strafePerc 0.0 +bankingSpeed 0.0 +rollLimit 2 +pitchLimit 0 +braking 10 +mouseYaw 0.006 +turningSpeed 5 +turnWhenStopped 0 +traction 100 +friction 100 +maxSlope 0.55 +mass 200 +armor 300 +toughness 30.0 +model tauntaun +riderAnim BOTH_VT_IDLE +radarIcon "gfx/menus/radar/tauntaun" +skin "wild" + +cameraOverride 1 +cameraRange 125 +cameraVertOffset 0 +cameraPitchOffset 0 +cameraFOV 80 +cameraAlpha 0 + +soundOn "sound/chars/tauntaun/misc/buck1.mp3" +soundOff "sound/chars/tauntaun/misc/buck2.mp3" +soundTurbo "sound/chars/tauntaun/misc/anger1.mp3" +soundShift1 "sound/chars/tauntaun/misc/anger3.mp3" +soundShift2 "sound/chars/tauntaun/misc/chatter1.mp3" +soundShift3 "sound/chars/tauntaun/misc/chatter3.mp3" +soundShift4 "sound/chars/tauntaun/misc/pant1.mp3" + +} \ No newline at end of file diff --git a/base/ext_data/vehicles/x-wing.veh b/base/ext_data/vehicles/x-wing.veh new file mode 100644 index 0000000..0cd5244 --- /dev/null +++ b/base/ext_data/vehicles/x-wing.veh @@ -0,0 +1,125 @@ +X-Wing +{ +name X-Wing +type VH_FIGHTER +numHands 2 +hideRider 1 +killRiderOnDeath 1 +lookYaw 45 +length 460 +width 460 +height 128 +g2radius 460 +centerOfGravity "-0.222 0 0" +speedMax 2600 +turboSpeed 4000 +turboDuration 2000 +turboRecharge 6000 +speedMin 0 +acceleration 15 +decelIdle 8 +accelIdle 10 +speedIdle 800 +//test: speed stays at whatever you last set it to... +throttleSticks 1 +strafePerc 0.085 +bankingSpeed 0.5 +rollLimit 35 +pitchLimit 60 +braking 20 +mouseYaw 0.0022 +mousePitch 0.0022 +turningSpeed 4.2 +turnWhenStopped 0 +traction 12 +friction 0.4 +speedDependantTurning 1 +maxSlope 0.65 +mass 1000 +//armor 1000 +//malfunctionArmorLevel 666 +//health_front 300 +//health_back 200 +//health_right 250 +//health_left 250 +//shields 600 +armor 1200 +malfunctionArmorLevel 750 +//armor 2400 +//malfunctionArmorLevel 1200 +health_front 450 +health_back 350 +health_right 450 +health_left 450 +shields 500 +//ms per point of recharge +shieldRechargeMS 600 +toughness 90.0 +model x-wing +riderAnim BOTH_VS_IDLE +droidNPC "random" +radarIcon "gfx/menus/radar/XW" +dmgIndicFrame "gfx/menus/radar/circle_base_frame_reb" +dmgIndicShield "gfx/menus/radar/circle_base_shield" +dmgIndicBackground "gfx/menus/radar/circle_base" +icon_front "gfx/menus/radar/XW_front" +icon_back "gfx/menus/radar/XW_back" +icon_right "gfx/menus/radar/XW_right" +icon_left "gfx/menus/radar/XW_left" +crosshairShader "gfx/menus/radar/XW_reticle" +//shieldShader "gfx/misc/shields_green" +shieldShader "gfx/misc/shields_blue" +soundOn "sound/vehicles/x-wing/on.wav" +soundLoop "sound/vehicles/x-wing/loop.wav" +soundOff "sound/vehicles/x-wing/off.wav" +soundFlyBy "sound/vehicles/x-wing/flyby.wav" +soundFlyBy2 "sound/vehicles/x-wing/flyby2.wav" +soundHyper "sound/vehicles/common/hyperstartreb.wav" +soundTurbo "sound/vehicles/x-wing/xwingby.wav" +exhaustFX "ships/xwing_exhaust" +turboFX "ships/xwing_exhaust_turbo" +impactFX "ships/scrape_sparks" +explodeFX "ships/XW_explosion" +//trailFX "ships/wingtrail" +dmgFX "ships/heavydmg" +injureFX "ships/lightdmg" +noseFX "ships/XW_nose" +lwingFX "ships/XW_lwing" +rwingFX "ships/XW_rwing" +flammable 1 +hoverHeight 80 +hoverStrength 10 +explosionRadius 400 +explosionDamage 1000 + +landingHeight 300.0 + +surfDestruction 1 + +weap1 rebel_laser +weap1Delay 150 +weap1Aim 1 +weap1AmmoMax 40 +weap1AmmoRechargeMS 500 +weap1Link 1 + +weap2 concussion_missile +weap2Delay 2000 +weap2Aim 1 +weap2AmmoMax 8 +weap2Link 1 + +weapMuzzle1 rebel_laser +weapMuzzle2 rebel_laser +weapMuzzle3 rebel_laser +weapMuzzle4 rebel_laser +weapMuzzle5 concussion_missile +weapMuzzle6 concussion_missile + +cameraOverride 1 +cameraRange 500 +cameraVertOffset 110 +cameraPitchOffset 0 +cameraFOV 100 +cameraAlpha 0 +} \ No newline at end of file diff --git a/base/ext_data/vehicles/z-95.veh b/base/ext_data/vehicles/z-95.veh new file mode 100644 index 0000000..148af90 --- /dev/null +++ b/base/ext_data/vehicles/z-95.veh @@ -0,0 +1,129 @@ +//NOTE: this really acts more like an A-Wing right now, for variety's sake + +Z-95 +{ +name Z-95 +type VH_FIGHTER +numHands 2 +hideRider 1 +killRiderOnDeath 1 +lookYaw 45 +length 420 +width 460 +height 128 +g2radius 460 +centerOfGravity "-0.222 0 0" +//speedMax 2200 +//turboSpeed 4400 +speedMax 2400 +turboSpeed 4600 +turboDuration 3000 +turboRecharge 6000 +speedMin 0 +acceleration 26 +decelIdle 10 +accelIdle 15 +speedIdle 1000 +//test: speed stays at whatever you last set it to... +throttleSticks 1 +strafePerc 0.1 +bankingSpeed 0.5 +rollLimit 40 +pitchLimit 70 +braking 25 +//mouseYaw 0.0025 +//mousePitch 0.0025 +mouseYaw 0.00275 +mousePitch 0.00275 +//turningSpeed 5 +turningSpeed 5.25 +turnWhenStopped 0 +traction 14 +friction 0.5 +speedDependantTurning 1 +maxSlope 0.65 +mass 800 +//armor 1000 +//malfunctionArmorLevel 666 +//health_front 300 +//health_back 300 +//health_right 200 +//health_left 200 +//shields 300 +armor 900 +malfunctionArmorLevel 800 +//armor 2000 +//malfunctionArmorLevel 1300 +health_front 300 +health_back 300 +health_right 300 +health_left 300 +shields 450 +toughness 85.0 +model Z-95 +riderAnim BOTH_VS_IDLE +radarIcon "gfx/menus/radar/Z95" +dmgIndicFrame "gfx/menus/radar/circle_base_frame_reb" +dmgIndicShield "gfx/menus/radar/circle_base_shield" +dmgIndicBackground "gfx/menus/radar/circle_base" +icon_front "gfx/menus/radar/Z95_front" +icon_back "gfx/menus/radar/Z95_back" +icon_right "gfx/menus/radar/Z95_right" +icon_left "gfx/menus/radar/Z95_left" +crosshairShader "gfx/menus/radar/z95_reticle" +//shieldShader "gfx/misc/shields_green" +shieldShader "gfx/misc/shields_blue" +soundOn "sound/vehicles/z-95/on.wav" +soundLoop "sound/vehicles/z-95/loop.wav" +soundOff "sound/vehicles/z-95/off.wav" +soundFlyBy "sound/vehicles/z-95/flyby.wav" +soundFlyBy2 "sound/vehicles/z-95/flyby2.wav" +soundHyper "sound/vehicles/common/hyperstartreb.wav" +soundTurbo "sound/vehicles/z-95/flyby.wav" +exhaustFX "ships/z95_exhaust" +turboFX "ships/z95_exhaust_turbo" +impactFX "ships/scrape_sparks" +explodeFX "ships/Z95_explosion" +//trailFX "ships/wingtrail" +dmgFX "ships/heavydmg" +injureFX "ships/lightdmg" +noseFX "ships/Z95_nose" +lwingFX "ships/Z95_lwing" +rwingFX "ships/Z95_rwing" +flammable 1 +hoverHeight 80 +hoverStrength 10 +explosionRadius 400 +explosionDamage 1000 + +landingHeight 300.0 + +surfDestruction 1 + +weap1 rebel_laser +weap1Delay 100 +weap2 conc_missile_straight +weap2Delay 1000 + +weap1AmmoMax 30 +weap1AmmoRechargeMS 400 + +weap2AmmoMax 6 + +weap1Aim 1 +weap2Aim 1 + +weapMuzzle1 rebel_laser +weapMuzzle2 rebel_laser +weapMuzzle3 conc_missile_straight + +//should *all* ships really be able to do this? +weap1Link 1 + +cameraOverride 1 +cameraRange 500 +cameraVertOffset 110 +cameraPitchOffset 0 +cameraFOV 100 +cameraAlpha 0 +} \ No newline at end of file diff --git a/base/ext_data/vssver.scc b/base/ext_data/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..4d3d3e8c2a1e27123abb6152e3019e767299ed5d GIT binary patch literal 80 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiXTEo54(#?Jr->MRTltcwDEZfl#llm{rH0p!n) a;g4yWv=+?Q0`l!TU6)v0U$ImgC=UPyR~McD literal 0 HcmV?d00001 diff --git a/base/ext_data/weapons.dat b/base/ext_data/weapons.dat new file mode 100644 index 0000000..4f8871e --- /dev/null +++ b/base/ext_data/weapons.dat @@ -0,0 +1,744 @@ +// EXTERNAL WEAPON & AMMO DATA +// +// NOTE!!!!!!!!! Weapontype must start the block of weapon data. +// NOTE!!!!!!!!! Ammo must start the block of ammo data. +// +// Weapontype - weapon data is associated with which weapon (must be first) +// WP_SABER, // player and NPC weapon +// WP_BLASTER_PISTOL, // player and NPC weapon +// WP_BLASTER, // player and NPC weapon +// WP_DISRUPTOR, // player and NPC weapon +// WP_BOWCASTER, // NPC weapon - player can pick this up, but never starts with them +// WP_REPEATER, // NPC weapon - player can pick this up, but never starts with them +// WP_DEMP2, // NPC weapon - player can pick this up, but never starts with them +// WP_FLECHETTE, // NPC weapon - player can pick this up, but never starts with them +// WP_ROCKET_LAUNCHER, // NPC weapon - player can pick this up, but never starts with them +// WP_THERMAL, // player and NPC weapon +// WP_TRIP_MINE, // NPC weapon - player can pick this up, but never starts with them +// WP_DET_PACK, // NPC weapon - player can pick this up, but never starts with them +// WP_CONCUSSION, // NPC weapon - player can pick this up, but never starts with them +//extras +// WP_STUN_BATON, // NPC weapon - player can pick this up, but never starts with them +// WP_MELEE, // player and NPC weapon - Any ol' melee attack +// These can never be gotten directly by the player +//NPC weapons +// WP_BRYAR_PISTOL, // NPC weapon - player can pick this up, but never starts with them +// WP_EMPLACED_GUN, +// WP_BOT_LASER, // Probe droid - Laser blast +// WP_TURRET, // turret guns +// WP_ATST_MAIN, +// WP_ATST_SIDE, +// WP_TIE_FIGHTER, +// WP_RAPID_FIRE_CONC, +// WP_JAWA, +// WP_TUSKEN_RIFLE, +// WP_TUSKEN_STAFF, +// WP_SCEPTER, +// WP_NOGHRI_STICK, +// +// Weaponclass - weapon name +// Weaponmodel - weapon model used in game +// weaponicon - interface image +// Ammotype - type of power weapon needs to fire +// 0 - No power +// 1 - Star Fleet power +// 2 - Alien Crystal power +// 3 - Phaser power +// Ammolowcount - amount when "Low ammo" warning appears on screen +// Flashcolor - color generate by weapon flash (R,G,B) +// Firingsound - sound file used when _idling_! +// altfiringsound - sound file used when alt-firing +// flashsound - sound file used by flash +// altflashsound - sound file used by an alt-fire flash +// stopsound - sound file used when a firing sound stops +// Firetime - amount of time between firings +// altfireTime - for alt fire +// Range - range of weapon +// energyPerShot - amount of energy used per shot +// altenergypershot- for alt fire +// barrelcount - number of barrels the model has (weaponname_b?.md3) +// missileModel - missile .md3 +// altmissileModel - alternate missile .md3 +// missileSound - played while flying +// altmissileSound - alternate missile launch sound +// missileLight - intensity of lightsource for missile - if 0.0 then none (float) +// altmissileLight - alternate missile light +// missileLightColor - color in three float style R, G, B (0.0 to 1.0) - NOTE - if you have a light, you MUST HAVE THESE +// altmissileLightColor - alternate color in three float style R, G, B (0.0 to 1.0) +// missileHitSound - played on impact +// altmissileHitSound - for alt fire +// missileFuncName - missile fly function +// altmissileFuncName - for alt fire +// +// FUNCTION NAMES +// borgfunc +// scavengerfunc +// altscavengerfunc +// stasisfunc +// grenadefunc +// altgrenadefunc +// tetrionfunc +// dreadnoughtfunc +// quantumfunc +// quantumaltfunc +// botrocketfunc +// forgeprojfunc +// forgeprojfunc2 +// forgepsychfunc +// parasiteacidfunc +// stasisattackfunc +// loaderlaserfunc +// botprojfunc + +// +// For AMMO Types +// ammoicon - STRING +// ammomax - INT + + +// WP_NULL +{ +WEAPONTYPE WP_NONE +} + +// WP_STUN_BATON +{ +weapontype WP_STUN_BATON +weaponclass weapon_stun_baton +weaponmodel models/weapons2/stun_baton/baton.md3 +weaponIcon gfx/hud/w_icon_stunbaton +firingsound sound/weapons/baton/idle.wav +firingforce fffx/weapons/baton/idle +barrelcount 3 +ammotype 1 +ammolowcount 5 +energypershot 0 +firetime 400 +range 8192 +altenergypershot 0 +altfiretime 400 +altrange 8192 +} + +// WP_SABER +{ +weapontype WP_SABER +weaponclass weapon_saber +weaponmodel models/weapons2/saber/saber_w.md3 +weaponIcon gfx/hud/w_icon_lightsaber +firingsound sound/weapons/saber/saberhum1.wav +ammotype 1 +ammolowcount 5 +energypershot 1 +firetime 100 +range 8192 +altenergypershot 3 +altfiretime 100 +altrange 8192 +missilemodel models/weapons2/saber/saber_w.md3 +} + + +// WP_BRYAR_PISTOL +{ +weapontype WP_BRYAR_PISTOL +weaponclass weapon_bryar_pistol +weaponmodel models/weapons2/briar_pistol/briar_pistol.md3 +weaponIcon gfx/hud/w_icon_briar +missileFuncName bryar_func +altmissileFuncName bryar_alt_func +ammotype 2 +ammolowcount 15 +energypershot 1 +firetime 400 +range 8192 +altenergypershot 1 +altfiretime 400 +altrange 8192 +muzzleEffect bryar/muzzle_flash +altmuzzleEffect bryar/altmuzzle_flash +altchargesound sound/weapons/bryar/altcharge.wav +altchargeforce fffx/weapons/bryar/altcharge +selectSound sound/weapons/bryar/select.wav +selectforce fffx/weapons/bryar/select +} + +// WP_BLASTER +{ +weapontype WP_BLASTER +weaponclass weapon_blaster +weaponmodel models/weapons2/blaster_r/blaster.md3 +weaponIcon gfx/hud/w_icon_blaster +ammotype 2 +ammolowcount 15 +energypershot 1 +firetime 350 +range 8192 +altenergypershot 2 +altfiretime 150 +altrange 8192 +missileFuncName blaster_func +altmissileFuncName blaster_alt_func +muzzleEffect blaster/muzzle_flash +altmuzzleEffect blaster/altmuzzle_flash +selectSound sound/weapons/blaster/select.wav +selectforce fffx/weapons/blaster/select +} + +// WP_DISRUPTOR +{ +weapontype WP_DISRUPTOR +weaponclass weapon_disruptor +weaponmodel models/weapons2/disruptor/disruptor.md3 +weaponIcon gfx/hud/w_icon_disruptor +ammotype 3 +ammolowcount 15 +energypershot 3 +barrelcount 1 +firetime 600 +range 8192 +altenergypershot 3 +altfiretime 1300 +altrange 8192 +muzzleEffect disruptor/muzzle_flash +altmuzzleEffect disruptor/altmuzzle_flash +selectSound sound/weapons/disruptor/select.wav +selectforce fffx/weapons/disruptor/select +altchargesound sound/weapons/disruptor/altCharge.wav +altchargeforce fffx/weapons/disruptor/altcharge +} + +// WP_BOWCASTER +{ +weapontype WP_BOWCASTER +weaponclass weapon_bowcaster +weaponmodel models/weapons2/bowcaster/bowcaster.md3 +weaponIcon gfx/hud/w_icon_bowcaster +altchargesound sound/weapons/bowcaster/altcharge.wav +altchargeforce fffx/weapons/bowcaster/altcharge +ammotype 3 +ammolowcount 15 +energypershot 5 +firetime 750 +range 8192 +altenergypershot 5 +altfiretime 400 +altrange 8192 +missileFuncName bowcaster_func +altmissileFuncName bowcaster_func +muzzleEffect bowcaster/muzzle_flash +altmuzzleEffect bowcaster/altmuzzle_flash +selectSound sound/weapons/bowcaster/select.wav +selectforce fffx/weapons/bowcaster/select +chargesound sound/weapons/bowcaster/altcharge.wav +chargeforce fffx/weapons/bowcaster/altcharge +} + +// WP_REPEATER +{ +weapontype WP_REPEATER +weaponclass weapon_repeater +weaponmodel models/weapons2/heavy_repeater/heavy_repeater.md3 +weaponIcon gfx/hud/w_icon_repeater +ammotype 4 +ammolowcount 25 +energypershot 1 +firetime 50 +range 8192 +altenergypershot 8 +altfiretime 800 +altrange 8192 +barrelcount 1 +missileFuncName repeater_func +altmissileFuncName repeater_alt_func +muzzleEffect repeater/muzzle_flash +altmuzzleEffect repeater/altmuzzle_flash +selectSound sound/weapons/repeater/select.wav +selectforce fffx/weapons/repeater/select +} + +// WP_DEMP2 +{ +weapontype WP_DEMP2 +weaponclass weapon_demp2 +weaponmodel models/weapons2/demp2/demp2.md3 +weaponIcon gfx/hud/w_icon_demp2 +ammotype 3 +ammolowcount 15 +energypershot 8 +firetime 450 +range 8192 +altenergypershot 10 +altfiretime 1200 +altrange 8192 +missileFuncName demp2_func +muzzleEffect demp2/muzzle_flash +altmissileFuncName demp2_alt_func +altmuzzleEffect demp2/altmuzzle_flash +selectSound sound/weapons/demp2/select.wav +selectforce fffx/weapons/demp2/select +altchargesound sound/weapons/demp2/altCharge.wav +altchargeforce fffx/weapons/demp2/altcharge +} + + +// WP_FLECHETTE +{ +weapontype WP_FLECHETTE +weaponclass weapon_flechette +weaponmodel models/weapons2/golan_arms/golan_arms.md3 +barrelcount 1 +ammotype 4 +ammolowcount 15 +firetime 550 +energypershot 8 +range 8192 +weaponIcon gfx/hud/w_icon_flechette +altenergypershot 8 +altfiretime 400 +altrange 8192 +missileFuncName flechette_func +missileModel models/weapons2/golan_arms/projectileMain.md3 +altmissileFuncName flechette_alt_func +muzzleEffect flechette/muzzle_flash +altmuzzleEffect flechette/altmuzzle_flash +altmissileModel models/weapons2/golan_arms/projectile.md3 +selectSound sound/weapons/flechette/select.wav +selectforce fffx/weapons/flechette/select +} + +// WP_ROCKET_LAUNCHER +{ +weapontype WP_ROCKET_LAUNCHER +weaponclass weapon_rocket_launcher +weaponmodel models/weapons2/merr_sonn/merr_sonn.md3 +ammotype 5 +ammolowcount 1 +firetime 600 +energypershot 1 +range 8192 +weaponIcon gfx/hud/w_icon_merrsonn +barrelcount 1 +altenergypershot 1 +altfiretime 1000 +altrange 8192 +missileLight 125 +missileLightColor 1.0 1.0 0.5 +altmissileLight 125 +altmissileLightColor 1.0 1.0 0.5 +missileFuncName rocket_func +altmissileFuncName rocket_alt_func +muzzleEffect rocket/muzzle_flash2 +altmuzzleEffect rocket/altmuzzle_flash +missileModel models/weapons2/merr_sonn/projectile.md3 +altmissileModel models/weapons2/merr_sonn/projectile.md3 +missilesound sound/weapons/rocket/missleloop.wav +altmissilesound sound/weapons/rocket/missleloop.wav +selectSound sound/weapons/rocket/select.wav +selectforce fffx/weapons/rocket/select +} + +// WP_CONCUSSION +{ +weapontype WP_CONCUSSION +weaponclass weapon_concussion_rifle +weaponmodel models/weapons2/concussion/c_rifle.md3 +weaponIcon gfx/hud/w_icon_c_rifle +ammotype 4 +ammolowcount 120 +energypershot 40 +firingsound sound/weapons/concussion/idle_lp.wav +firetime 800 +range 8192 +missileFuncName conc_func +muzzleEffect concussion/muzzle_flash +missileLight 125 +missileLightColor 0.75 0.25 1.0 +missilesound sound/weapons/concussion/missleloop.wav +altenergypershot 50 +altfiretime 1200 +altrange 8192 +barrelcount 1 +altmuzzleEffect concussion/altmuzzle_flash +selectSound sound/weapons/concussion/select.wav +selectforce fffx/weapons/concussion/select +} + +// WP_THERMAL +{ +weapontype WP_THERMAL +weaponclass weapon_thermal +weaponmodel models/weapons2/thermal/thermal.md3 +weaponIcon gfx/hud/w_icon_thermal +ammotype 7 +ammolowcount 1 +energypershot 1 +firetime 800 +range 8192 +altenergypershot 1 +altfiretime 400 +altrange 8192 +missileModel models/weapons2/thermal/thermal_proj.md3 +altmissileModel models/weapons2/thermal/thermal_proj.md3 +barrelcount 0 +chargesound sound/weapons/thermal/charge.wav +chargeforce fffx/weapons/thermal/charge +altchargesound sound/weapons/thermal/charge.wav +altchargeforce fffx/weapons/thermal/charge +selectSound sound/weapons/thermal/select.wav +selectforce fffx/weapons/thermal/select +muzzleEffect thermal/muzzle_flash +} + +// WP_TRIP_MINE +{ +weapontype WP_TRIP_MINE +weaponclass weapon_trip_mine +weaponmodel models/weapons2/laser_trap/laser_trap.md3 +weaponIcon gfx/hud/w_icon_tripmine +ammotype 8 +ammolowcount 1 +energypershot 1 +firetime 800 +range 8192 +altenergypershot 1 +altfiretime 400 +altrange 8192 +missileModel models/weapons2/laser_trap/laser_trap_w.glm +altmissileModel models/weapons2/laser_trap/laser_trap_w.glm +selectSound sound/weapons/detpack/select.wav +selectforce fffx/weapons/detpack/select +muzzleEffect tripmine/muzzle_flash + +} + +// WP_DET_PACK +{ +weapontype WP_DET_PACK +weaponclass weapon_det_pack +weaponmodel models/weapons2/detpack/det_pack.md3 +weaponIcon gfx/hud/w_icon_detpack +ammotype 9 +ammolowcount 1 +energypershot 1 +firetime 800 +range 8192 +altenergypershot 0 +altfiretime 400 +altrange 8192 +missileModel models/weapons2/detpack/det_pack_proj.glm +selectSound sound/weapons/detpack/select.wav +selectforce fffx/weapons/detpack/select +muzzleEffect detpack/muzzle_flash +} + +// WP_EMPLACED_GUN +{ +weapontype WP_EMPLACED_GUN +weaponclass weapon_emplaced_gun +weaponmodel models/weapons2/noweap/noweap.md3 + +altenergypershot 1 +altrange 8192 +missileFuncName emplaced_func +altmissileFuncName emplaced_func +ammotype 6 +ammolowcount 15 +energypershot 1 +firetime 150 +altfiretime 150 +range 8192 +muzzleEffect emplaced/muzzle_flash +muzzleEffect eweb/muzzle_flash +} + +// WP_BOT_LASER +{ +weapontype WP_BOT_LASER +weaponclass weapon_bryar_pistol +weaponmodel models/weapons2/noweap/noweap.md3 + +//flashsound sound/weapons/probe/fire.wav +//altflashsound sound/weapons/probe/alt_fire.wav +altenergypershot 0 +altrange 8192 +missileFuncName bryar_func +ammotype 1 +ammolowcount 15 +energypershot 2 +firetime 1600 +range 8192 +} + +// WP_MELEE +{ +weapontype WP_MELEE +weaponclass weapon_melee +weaponmodel models/weapons2/noweap/noweap.md3 +weaponIcon gfx/hud/w_icon_melee +ammotype 3 +ammolowcount 5 +energypershot 0 +firetime 1000 +range 1024 +} + +// WP_ATST_MAIN +{ +weapontype WP_ATST_MAIN +weaponclass weapon_atst_main +weaponmodel models/weapons2/noweap/noweap.md3 +weaponIcon gfx/hud/w_icon_atst +//flashsound sound/weapons/atst/ATSTfire1.wav +//altflashsound sound/weapons/atst/ATSTfire2.wav +altenergypershot 1 +altrange 8192 +missileFuncName atstmain_func +altmissileFuncName atstmain_func +ammotype 6 +ammolowcount 15 +energypershot 1 +firetime 200 +altfiretime 150 +range 8192 +muzzleEffect emplaced/muzzle_flash +} + +// WP_ATST_SIDE +{ +weapontype WP_ATST_SIDE +weaponclass weapon_atst_side +weaponmodel models/weapons2/noweap/noweap.md3 +weaponIcon gfx/hud/w_icon_atstside +altenergypershot 1 +altrange 8192 + +altmissileModel models/weapons2/merr_sonn/projectile.md3 + +missileFuncName atst_side_main_func +altmissileFuncName atst_side_alt_func +muzzleEffect emplaced/muzzle_flash +altmuzzleEffect emplaced/muzzle_flash + +ammotype 6 +ammolowcount 15 +energypershot 1 +firetime 400 +altfiretime 1000 +range 8192 +} + +// WP_TIE_FIGHTER +{ +weapontype WP_TIE_FIGHTER +weaponclass weapon_tie_fighter +weaponmodel models/weapons2/noweap/noweap.md3 +altenergypershot 1 +altrange 8192 +missileFuncName emplaced_func +altmissileFuncName emplaced_func +ammotype 6 +ammolowcount 15 +energypershot 1 +firetime 400 +altfiretime 400 +range 8192 +muzzleEffect emplaced/muzzle_flash +} + +// WP_RAPID_FIRE_CONC +{ +weapontype WP_RAPID_FIRE_CONC +weaponclass weapon_radid_concussion +weaponmodel models/weapons2/noweap/noweap.md3 +altenergypershot 1 +altrange 8192 +missileFuncName emplaced_func +altmissileFuncName repeater_alt_func +ammotype 6 +ammolowcount 15 +energypershot 1 +firetime 400 +altfiretime 1000 +range 8192 +muzzleEffect emplaced/muzzle_flash +} + +// WP_BLASTER_PISTOL +{ +weapontype WP_BLASTER_PISTOL +weaponclass weapon_blaster_pistol +weaponmodel models/weapons2/blaster_pistol/blaster_pistol.md3 +weaponIcon gfx/hud/w_icon_blaster_pistol +missileFuncName bryar_func +altmissileFuncName bryar_alt_func +ammotype 2 +ammolowcount 15 +energypershot 1 +firetime 400 +range 8192 +altenergypershot 1 +altfiretime 400 +altrange 8192 +muzzleEffect bryar/muzzle_flash +altmuzzleEffect bryar/altmuzzle_flash +altchargesound sound/weapons/bryar/altcharge.wav +selectSound sound/weapons/bryar/select.wav +selectforce fffx/weapons/bryar/select +} + +// WP_TURRET +{ +weapontype WP_TURRET +weaponclass weapon_turret +weaponmodel models/weapons2/noweap/noweap.md3 +altenergypershot 1 +altrange 8192 +missileFuncName turret_func +ammotype 6 +ammolowcount 15 +energypershot 1 +firetime 400 +altfiretime 400 +range 8192 +muzzleEffect turret/muzzle_flash +} + +// WP_JAWA +{ +weapontype WP_JAWA +weaponclass weapon_jawa +weaponmodel models/weapons2/jawa/jawa_gun.md3 +missileFuncName bryar_func +altmissileFuncName bryar_alt_func +ammotype 2 +ammolowcount 15 +energypershot 2 +firetime 400 +range 8192 +altenergypershot 2 +altfiretime 400 +altrange 8192 +muzzleEffect bryar/muzzle_flash +} + +// WP_TUSKEN_RIFLE +{ +weapontype WP_TUSKEN_RIFLE +weaponclass weapon_tusken_rifle +weaponmodel models/weapons2/tusken_rifle/tusken_rifle.md3 +ammotype 3 +ammolowcount 15 +firetime 1000 +energypershot 3 +firetime 600 +missileFuncName tusk_shot_func +missileLight 50 +missileLightColor 1.0 0.75 0.25 +muzzleEffect tusken/muzzle_flash +altenergypershot 3 +altfiretime 1000 +altmissileFuncName tusk_shot_func +altmissileLight 50 +altmissileLightColor 1.0 0.75 0.25 +altmuzzleEffect tusken/muzzle_flash +selectSound sound/weapons/disruptor/select.wav +selectforce fffx/weapons/disruptor/select +} + +// WP_TUSKEN_STAFF +{ +weapontype WP_TUSKEN_STAFF +weaponclass weapon_tusken_staff +weaponmodel models/weapons2/tusken_staff/tusken_staff.md3 +ammotype 3 +ammolowcount 5 +energypershot 0 +firetime 1000 +range 1024 +} + +//WP_SCEPTER +{ +weapontype WP_SCEPTER +weaponclass weapon_scepter +weaponmodel models/weapons2/sith_scepter/sith_scepter.md3 +ammotype 3 +ammolowcount 5 +energypershot 0 +firetime 1000 +range 1024 +} + +//WP_NOGHRI_STICK +{ +weapontype WP_NOGHRI_STICK +weaponclass weapon_noghri_stick +weaponmodel models/weapons2/noghri_stick/noghri_stick.md3 +ammotype 3 +ammolowcount 5 +energypershot 1 +firetime 600 +missileFuncName noghri_shot_func +muzzleEffect noghri_stick/muzzle_flash +} + +// AMMO_NONE +{ +AMMO AMMO_NONE +AMMOMAX 0 +} + +// AMMO_FORCE +{ +AMMO AMMO_FORCE +AMMOMAX 100 +} + +// AMMO_BLASTER +{ +AMMO AMMO_BLASTER +AMMOMAX 300 +} + +// AMMO_POWERCELL +{ +AMMO AMMO_POWERCELL +AMMOMAX 300 +} + +// AMMO_METAL_BOLTS +{ +AMMO AMMO_METAL_BOLTS +AMMOMAX 400 +} + +// AMMO_ROCKETS +{ +AMMO AMMO_ROCKETS +AMMOMAX 10 +} + +// AMMO_EMPLACED +{ +AMMO AMMO_EMPLACED +AMMOMAX 999 +} + +// AMMO_THERMAL +{ +AMMO AMMO_THERMAL +AMMOMAX 10 +} + +// AMMO_TRIPMINE +{ +AMMO AMMO_TRIPMINE +AMMOMAX 5 +} + +// AMMO_DETPACK +{ +AMMO AMMO_DETPACK +AMMOMAX 5 +} \ No newline at end of file diff --git a/base/high.cfg b/base/high.cfg new file mode 100644 index 0000000..8b25e32 --- /dev/null +++ b/base/high.cfg @@ -0,0 +1,18 @@ +set r_picmip 0 +set r_lodbias 0 +set r_detailtextures 1 +set r_dynamiclight 1 +set r_flares 1 +set r_subdivisions 4 +set r_lodcurveError 250 +set r_lodscale 10 +set cg_shadows 2 +set r_texturebitslm 32 +set r_texturebits 32 +set r_colorbits 32 +set r_depthbits 32 + +set r_surfaceSprites 1 +set r_weatherScale 1 + +set r_noportals 0 \ No newline at end of file diff --git a/base/low.cfg b/base/low.cfg new file mode 100644 index 0000000..fee2aaf --- /dev/null +++ b/base/low.cfg @@ -0,0 +1,22 @@ +set s_khz 11 +set cg_VariantSoundCap 2 + +set r_picmip 2 +set r_lodbias 2 +set r_detailtextures 0 +set r_dynamiclight 0 +set r_flares 0 +set r_subdivisions 20 +set r_lodcurveError 500 +set r_lodscale 4 +set cg_shadows 0 +set cg_marks 0 + +set r_texturebits 16 +set r_colorbits 16 +set r_depthbits 16 + +set r_surfaceSprites 0 +set r_weatherScale 0 + +set r_noportals 1 \ No newline at end of file diff --git a/base/med.cfg b/base/med.cfg new file mode 100644 index 0000000..ad92c05 --- /dev/null +++ b/base/med.cfg @@ -0,0 +1,18 @@ +set sys_lowmem 0 +set r_picmip 1 +set r_lodbias 1 +set r_detailtextures 1 +set r_dynamiclight 1 +set r_flares 1 +set r_subdivisions 12 +set r_lodcurveError 250 +set r_lodscale 6 +set cg_shadows 1 +set r_texturebits 0 +set r_colorbits 0 +set r_depthbits 0 + +set r_surfaceSprites 1 +set r_weatherScale 1 + +set r_noportals 0 diff --git a/base/mpdefault.cfg b/base/mpdefault.cfg new file mode 100644 index 0000000..cf7a60d --- /dev/null +++ b/base/mpdefault.cfg @@ -0,0 +1,141 @@ +// +// MP JEDI ACADEMY DEFAULT CONFIG +// + +unbindall + +// +// weapons +// +bind 1 "weapon 1" +bind 2 "weapon 2" +bind 3 "weapon 3" +bind 4 "weapon 4" +bind 5 "weapon 5" +bind 6 "weapon 6" +bind 7 "weapon 7" +bind 8 "weapon 8" +bind 9 "weapon 13" +bind 0 "weapon 9" +bind - "weapon 10" +bind = "weapon 0" + + +// +// CHARACTER CONTROLS +// + +bind CTRL +attack +bind ALT +altattack +bind SHIFT +speed +bind z +strafe +bind PGUP +lookup +bind PGDN +lookdown +bind END centerview +bind c +movedown +bind SPACE +moveup +bind ENTER +use +bind r +use + + +bind UPARROW +forward +bind DOWNARROW +back +bind LEFTARROW +left +bind RIGHTARROW +right +bind w +forward +bind a +moveleft +bind s +back +bind d +moveright +bind , +moveleft +bind . +moveright + +// +// FORCE POWERS +// + +bind F1 force_throw +bind F2 force_pull +bind F3 force_speed +bind F4 force_seeing +bind F5 force_heal +bind F6 force_protect +bind F7 force_absorb +bind F8 force_distract +bind F9 +force_grip +bind F10 +force_lightning +bind F11 force_rage +bind F12 +force_drain +bind \ force_forcepowerother +bind ] force_healother + + +bind f +useforce +bind e forcenext +bind q forceprev + +bind TAB +scores +bind INS scoresUp +bind DEL scoresDown +bind p "cg_thirdperson !" +bind l saberAttackCycle + +// +// QUICK KEYS +// + + +//multi only +bind KP_HOME use_field + +//bind KP_RIGHTARROW use_field +//bind KP_UPARROW use_field +//bind KP_DOWNARROW use_field +//bind KP_END use_field +//bind KP_PGDN use_field +//bind KP_INS use_field +//bind KP_DEL use_field +//bind KP_ENTER use_field +// + + +// +// MOUSE OPTIONS +// + +bind / +mlook + +// +// MOUSE BUTTONS +// + +bind MOUSE1 +attack +bind MOUSE2 +altattack +bind MOUSE3 saberAttackCycle +bind mwheelup weapprev +bind mwheeldown weapnext + +// +// CLIENT ENVIRONMENT COMMANDS +// + +bind ~ "toggleconsole" +bind ` "toggleconsole" + + +//yell to all +bind y messagemode +//team talk +bind t messagemode2 +//target +bind u messagemode3 +//attacker +bind i messagemode4 +//voicechat +bind v voicechat +//Automap toggle +bind m automap_toggle +//Objectives menu +bind o "ui_opensiegemenu ingame_objectives" + +// Saber Challenge +bind k engage_duel diff --git a/base/noMotion.cfg b/base/noMotion.cfg new file mode 100644 index 0000000..7662bac --- /dev/null +++ b/base/noMotion.cfg @@ -0,0 +1,5 @@ +cg_runpitch 0 +cg_runroll 0 +cg_bobup 0 +cg_bobpitch 0 +cg_bobroll 0 \ No newline at end of file diff --git a/base/productid.txt b/base/productid.txt new file mode 100644 index 0000000..9e1663b --- /dev/null +++ b/base/productid.txt @@ -0,0 +1 @@ +This file is copyright 2003 Raven Software, and may not be duplicated except during a licensed installation of the full commercial version of Star Wars: Jedi Academy \ No newline at end of file diff --git a/base/restoreMotion.cfg b/base/restoreMotion.cfg new file mode 100644 index 0000000..1805938 --- /dev/null +++ b/base/restoreMotion.cfg @@ -0,0 +1,5 @@ +reset cg_runpitch +reset cg_runroll +reset cg_bobup +reset cg_bobpitch +reset cg_bobroll \ No newline at end of file diff --git a/base/ui/character.menu b/base/ui/character.menu new file mode 100644 index 0000000..8f3768c --- /dev/null +++ b/base/ui/character.menu @@ -0,0 +1,898 @@ +//---------------------------------------------------------------------------------------------- +// CHARACTER CREATION MENU +// +// +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "characterMenu" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + + onOpen + { + uiScript "getcharcvars" + uiScript "character" + hide heads + show torso + hide lower + } + + onESC + { + play "sound/interface/esc.wav" + close characterMenu + open newgameMenu + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 +// background "gfx/menus/main_background" + background "gfx/menus/charmenu" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // The starwars logo on the top + /*itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + }*/ + + + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 -60 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 -60 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// CHARACTER MENU specific stuff +//---------------------------------------------------------------------------------------------- + // CREATION title + itemDef + { + name creation_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CHARACTER_CREATION + rect 100 54 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name character + group models + type ITEM_TYPE_MODEL +// rect 52 84 900 1000 + rect 355 84 300 340 + + model_g2anim "BOTH_WALK1" + asset_model "ui_char_model" + model_angle 180 + model_g2mins -10 -15 -10 + model_g2maxs 20 15 30 + model_rotation 50 + model_fovx 50 + model_fovy 50 + isCharacter 1 + visible 1 + decoration + + } + + itemDef + { + name background2 + group none + style WINDOW_STYLE_SHADER + rect 320 360 320 120 + background "gfx/menus/charmenu_bottom" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// SPECIES BUTTON +//---------------------------------------------------------------------------------------------- + itemDef + { + name species + group none + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 30 88 140 24 + forecolor .549 .854 1 1 + text @MENUS_SPECIES + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + font 3 + textscale 1 + visible 1 + decoration + } + + itemDef + { + name speciesbut_glow + group none + style WINDOW_STYLE_SHADER + rect 176 92 150 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name speciesbut + group none + text " " + descText @MENUS_CHOOSE_SPECIES + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 176 92 150 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + backcolor 0 0 0 0 + forecolor .615 .615 .956 1 + feeder 16 //FEEDER_PLAYER_SPECIES + cvar "ui_char_model" + cvarStrList feeder + + visible 1 + + mouseEnter + { + show speciesbut_glow + } + mouseExit + { + hide speciesbut_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "characterchanged" + uiScript "resetcharacterlistboxes" + } + } + +//---------------------------------------------------------------------------------------------- +// COLOR TINT AREA +//---------------------------------------------------------------------------------------------- + itemDef + { + name color + group none + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 30 144 160 24 + forecolor .549 .854 1 1 + text @MENUS_COLOR + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + font 3 + textscale 1 + visible 1 + decoration + } + + itemDef + { + name colorbox + group tints + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + desctext @MENUS_CHANGE_PLAYER_TINT + elementwidth 32 + elementheight 32 + elementtype 1 //LISTBOX_IMAGE + feeder 20 //FEEDER_COLORCHOICES + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + rect 30 168 292 48 + visible 1 + action + { + play "sound/interface/choose_color.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// APPEARANCE TABS +//---------------------------------------------------------------------------------------------- + itemDef + { + name appear + group none + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 30 252 180 24 + forecolor .549 .854 1 1 + text @MENUS_APPEARANCE + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + font 3 + textscale 1 + visible 1 + decoration + } + + // HEAD BUTTON + itemDef + { + name headbut_glow + group none + style WINDOW_STYLE_SHADER + rect 30 280 90 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name headbut + group none + text @MENUS_HEAD + descText @MENUS_SELECT_HEAD + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 30 280 90 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show headbut_glow + } + mouseExit + { + hide headbut_glow + } + action + { + play "sound/interface/button1.wav" + show heads + hide torso + hide lower + transition2 character 52 84 900 1000 20 25 + + setitemcolor legsbut forecolor 1 .682 0 1 + setitemcolor torsobut forecolor 1 .682 0 1 + setitemcolor headbut forecolor 1 1 1 1 + + } + } + + + // TORSO BUTTON + itemDef + { + name torsobut_glow + group none + style WINDOW_STYLE_SHADER + rect 126 280 90 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name torsobut + group none + text @MENUS_TORSO + descText @MENUS_SELECT_TORSO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 126 280 90 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + backcolor 0 0 0 0 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show torsobut_glow + } + mouseExit + { + hide torsobut_glow + } + action + { + play "sound/interface/button1.wav" + show torso + hide heads + hide lower + transition2 character 355 84 300 340 20 25 + + setitemcolor legsbut forecolor 1 .682 0 1 + setitemcolor torsobut forecolor 1 1 1 1 + setitemcolor headbut forecolor 1 .682 0 1 + } + } + + // LEGS BUTTON + itemDef + { + name legsbut_glow + group none + style WINDOW_STYLE_SHADER + rect 224 280 90 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name legsbut + group none + text @MENUS_LEGS + descText @MENUS_SELECT_LEGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 224 280 90 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show legsbut_glow + } + mouseExit + { + hide legsbut_glow + } + action + { + play "sound/interface/button1.wav" + show lower + hide heads + hide torso + transition2 character 355 84 300 340 20 25 + + setitemcolor legsbut forecolor 1 1 1 1 + setitemcolor torsobut forecolor 1 .682 0 1 + setitemcolor headbut forecolor 1 .682 0 1 + } + } + +//---------------------------------------------------------------------------------------------- +// APPEARANCE LISTBOXES +//---------------------------------------------------------------------------------------------- + itemDef + { + name headlistbox + group heads + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + descText @MENUS_SELECT_HEAD + elementwidth 72 + elementheight 72 + elementtype 1 //LISTBOX_IMAGE + feeder 17 //FEEDER_PLAYER_SKIN_HEAD + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + forecolor -1 //use playercolor + rect 30 306 292 93 + visible 0 + action + { + play "sound/interface/choose_head.wav" + uiScript "char_skin" + } + } + + itemDef + { + name torsolistbox + group torso + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + descText @MENUS_SELECT_TORSO + elementwidth 72 + elementheight 72 + elementtype 1 //LISTBOX_IMAGE + feeder 18 //FEEDER_PLAYER_SKIN_TORSO + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + forecolor -1 //use playercolor + rect 30 306 292 93 + visible 1 + action + { + play "sound/interface/choose_torso.wav" + uiScript "char_skin" + } + } + + itemDef + { + name lowerlistbox + group lower + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + descText @MENUS_SELECT_LEGS + elementwidth 72 + elementheight 72 + elementtype 1 //LISTBOX_IMAGE + feeder 19 //FEEDER_PLAYER_SKIN_LEGS + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + forecolor -1 //use playercolor + rect 30 306 292 93 + visible 0 + action + { + play "sound/interface/choose_head.wav" + uiScript "char_skin" + } + } + +//---------------------------------------------------------------------------------------------- +// TOP MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Big button "NEW" + itemDef + { + name newbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 7 16 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 16 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show newbutton_glow + } + mouseExit + { + hide newbutton_glow + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 170 16 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 16 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + action + { + play "sound/interface/button1.wav" + close all + open loadgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 16 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 16 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" + close all + open controlsMenu + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 16 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 16 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" + close all + open setupMenu + } + } + +//---------------------------------------------------------------------------------------------- +// LOWER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name backbutton + group exit + text @MENUS_BACK + descText @MENUS_BACKUP_ONE_MENU + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open newgameMenu + } + } + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3" + close all + open quitMenu + } + } + + itemDef + { + name next_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name next + group none + text @MENUS_NEXT + descText @MENUS_NEXT_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 455 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + close all + uiScript "updatecharcvars" + setcvar saber_menu 0 + setcvar ui_saber_type single + open saberMenu + } + mouseEnter + { + show next_glow + } + mouseExit + { + hide next_glow + } + } + } +} \ No newline at end of file diff --git a/base/ui/controls.menu b/base/ui/controls.menu new file mode 100644 index 0000000..9f0e9ef --- /dev/null +++ b/base/ui/controls.menu @@ -0,0 +1,3121 @@ +//---------------------------------------------------------------------------------------------- +// +// CONTROLS MENU +// +// Player can change key bindings from main menu +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "controlsMenu" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript loadControls + setitemcolor side_buttons forecolor 1 .682 0 1 + + // fade in movement controls + show movecontrols + setitemcolor movecontrols forecolor .615 .615 .956 0.0 + fadein movecontrols + + // don't show any others + hide attackcontrols + setitemcolor attackcontrols forecolor .615 .615 .956 0.0 + + hide weaponcontrols + setitemcolor weaponcontrols forecolor .615 .615 .956 0.0 + + hide forcecontrols + setitemcolor forcecontrols forecolor .615 .615 .956 0.0 + + hide quickcontrols + setitemcolor quickcontrols forecolor .615 .615 .956 0.0 + + hide joycontrols + setitemcolor joycontrols forecolor .615 .615 .956 0.0 + + hide othercontrols + setitemcolor othercontrols forecolor .615 .615 .956 0.0 + + show setup_background + + setitemcolor movementcontrolbutton forecolor 1 1 1 1 + + hide ffwarning + } + + onClose + { + uiScript saveControls + hide ffwarning + } + + onESC + { + play "sound/interface/esc.wav" + hide button_glow + close controlsMenu + open mainMenu + hide ffwarning + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Big button "NEW" + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 7 124 130 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open newgameMenu + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 170 124 130 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open loadgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 340 126 130 24 + } + mouseExit + { + hide button_glow + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 502 126 130 24 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setupMenu ; + } + } + + +//---------------------------------------------------------------------------------------------- +// BOTTOM MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton + group exit + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 59 444 130 24 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open mainMenu + } + } + + // EXIT button in lower center + itemDef + { + name exitgamebutton_glow + group exit_glow + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 255 444 130 24 + } + mouseExit + { + hide button_glow + } + action + { + close all ; + open quitMenu + } + } + +//---------------------------------------------------------------------------------------------- +// SIDE BUTTONS +//---------------------------------------------------------------------------------------------- + // Configure Controls title + itemDef + { + name control_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CONFIGURE_CONTROLS + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// GLOW ON SIDE BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name sidebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 60 185 200 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MOVEMENT button +//---------------------------------------------------------------------------------------------- + itemDef + { + name movementcontrolbutton + group side_buttons + text @MENUS_MOVEMENT + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 60 185 200 24 + font 3 + textscale 0.9 + textalignx 190 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_MOVEMENT_KEYS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 184 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + + show movecontrols + fadein movecontrols + + hide attackcontrols + setitemcolor attackcontrols forecolor .615 .615 .956 0.0 + + hide weaponcontrols + setitemcolor weaponcontrols forecolor .615 .615 .956 0.0 + + hide forcecontrols + setitemcolor forcecontrols forecolor .615 .615 .956 0.0 + + hide quickcontrols + setitemcolor quickcontrols forecolor .615 .615 .956 0.0 + + hide joycontrols + setitemcolor joycontrols forecolor .615 .615 .956 0.0 + + hide othercontrols + setitemcolor othercontrols forecolor .615 .615 .956 0.0 + + setitemcolor side_buttons forecolor 1 .682 0 1 + setitemcolor movementcontrolbutton forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// INTERACTION button +//---------------------------------------------------------------------------------------------- + itemDef + { + name attackcontrolbutton + group side_buttons + text @MENUS_INTERACTION + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 209 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_INTERACTION_DESC + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 208 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + + hide movecontrols + setitemcolor movecontrols forecolor .615 .615 .956 0.0 + + show attackcontrols + fadein attackcontrols + + hide weaponcontrols + setitemcolor weaponcontrols forecolor .615 .615 .956 0.0 + + hide forcecontrols + setitemcolor forcecontrols forecolor .615 .615 .956 0.0 + + hide quickcontrols + setitemcolor quickcontrols forecolor .615 .615 .956 0.0 + + hide joycontrols + setitemcolor joycontrols forecolor .615 .615 .956 0.0 + + hide othercontrols + setitemcolor othercontrols forecolor .615 .615 .956 0.0 + + setitemcolor side_buttons forecolor 1 .682 0 1 + setitemcolor attackcontrolbutton forecolor 1 1 1 1 + } + } + + +//---------------------------------------------------------------------------------------------- +// WEAPONS button +//---------------------------------------------------------------------------------------------- + itemDef + { + name weaponscontrolbutton + group side_buttons + text @MENUS_WEAPONS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 233 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_WEAPON_CONTROLS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 232 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + + hide movecontrols + setitemcolor movecontrols forecolor .615 .615 .956 0.0 + + hide attackcontrols + setitemcolor attackcontrols forecolor .615 .615 .956 0.0 + + show weaponcontrols + fadein weaponcontrols + + hide forcecontrols + setitemcolor forcecontrols forecolor .615 .615 .956 0.0 + + hide quickcontrols + setitemcolor quickcontrols forecolor .615 .615 .956 0.0 + + hide joycontrols + setitemcolor joycontrols forecolor .615 .615 .956 0.0 + + hide othercontrols + setitemcolor othercontrols forecolor .615 .615 .956 0.0 + + setitemcolor side_buttons forecolor 1 .682 0 1 + setitemcolor weaponscontrolbutton forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// FORCE POWERS button +//---------------------------------------------------------------------------------------------- + itemDef + { + name forcecontrolbutton + group side_buttons + text @MENUS_FORCE_POWERS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 257 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_FORCE_POWER + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 256 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + hide movecontrols + setitemcolor movecontrols forecolor .615 .615 .956 0.0 + + hide attackcontrols + setitemcolor attackcontrols forecolor .615 .615 .956 0.0 + + hide weaponcontrols + setitemcolor weaponcontrols forecolor .615 .615 .956 0.0 + + show forcecontrols + fadein forcecontrols + + hide quickcontrols + setitemcolor quickcontrols forecolor .615 .615 .956 0.0 + + hide joycontrols + setitemcolor joycontrols forecolor .615 .615 .956 0.0 + + hide othercontrols + setitemcolor othercontrols forecolor .615 .615 .956 0.0 + + setitemcolor side_buttons forecolor 1 .682 0 1 + setitemcolor forcecontrolbutton forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// QUICK KEYS button +//---------------------------------------------------------------------------------------------- + itemDef + { + name quickcontrolbutton + group side_buttons + text @MENUS_QUICK_KEYS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 281 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_QUICK_KEYS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 280 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + + hide movecontrols + setitemcolor movecontrols forecolor .615 .615 .956 0.0 + + hide attackcontrols + setitemcolor attackcontrols forecolor .615 .615 .956 0.0 + + hide weaponcontrols + setitemcolor weaponcontrols forecolor .615 .615 .956 0.0 + + hide forcecontrols + setitemcolor forcecontrols forecolor .615 .615 .956 0.0 + + show quickcontrols + fadein quickcontrols + + hide joycontrols + setitemcolor joycontrols forecolor .615 .615 .956 0.0 + + hide othercontrols + setitemcolor othercontrols forecolor .615 .615 .956 0.0 + + setitemcolor side_buttons forecolor 1 .682 0 1 + setitemcolor quickcontrolbutton forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// MOUSE/JOYSTICK button +//---------------------------------------------------------------------------------------------- + itemDef + { + name mousejoystickcontrolbutton + group side_buttons + text @MENUS_MOUSE_JOYSTICK + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 305 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_MOUSE_AND_JOYSTICK + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 304 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + + hide movecontrols + setitemcolor movecontrols forecolor .615 .615 .956 0.0 + + hide attackcontrols + setitemcolor attackcontrols forecolor .615 .615 .956 0.0 + + hide weaponcontrols + setitemcolor weaponcontrols forecolor .615 .615 .956 0.0 + + hide forcecontrols + setitemcolor forcecontrols forecolor .615 .615 .956 0.0 + + hide quickcontrols + setitemcolor quickcontrols forecolor .615 .615 .956 0.0 + + show joycontrols + fadein joycontrols + + hide othercontrols + setitemcolor othercontrols forecolor .615 .615 .956 0.0 + + setitemcolor side_buttons forecolor 1 .682 0 1 + setitemcolor mousejoystickcontrolbutton forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// OTHER button +//---------------------------------------------------------------------------------------------- + itemDef + { + name othercontrolbutton + group side_buttons + text @MENUS_OTHER + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 329 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_ADDITIONAL + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 328 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + + hide movecontrols + setitemcolor movecontrols forecolor .615 .615 .956 0.0 + + hide attackcontrols + setitemcolor attackcontrols forecolor .615 .615 .956 0.0 + + hide weaponcontrols + setitemcolor weaponcontrols forecolor .615 .615 .956 0.0 + + hide forcecontrols + setitemcolor forcecontrols forecolor .615 .615 .956 0.0 + + hide quickcontrols + setitemcolor quickcontrols forecolor .615 .615 .956 0.0 + + hide joycontrols + setitemcolor joycontrols forecolor .615 .615 .956 0.0 + + show othercontrols + fadein othercontrols + + setitemcolor side_buttons forecolor 1 .682 0 1 + setitemcolor othercontrolbutton forecolor 1 1 1 1 + } + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 260 185 340 225 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MOVEMENT BINDING +//---------------------------------------------------------------------------------------------- + itemDef + { + name movement1 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_WALK_FORWARD + cvar "+forward" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_MOVES_PLAYER_FORWARD + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 188 340 20 + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name movement2 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_BACKPEDAL + cvar "+back" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_MOVES_PLAYER_BACKWARD + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 202 340 20 + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name movement3 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_TURN_LEFT + cvar "+left" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_ROTATES_PLAYER_LEFT + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 216 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name movement4 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_TURN_RIGHT + cvar "+right" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 0.0 + visible 0 + descText @MENUS_ROTATES_PLAYER_RIGHT + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 230 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name movement5 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_RUN_WALK + cvar "+speed" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 0.0 + visible 0 + descText @MENUS_IF_HELD_TOGGLES_BETWEEN + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 244 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name movement6 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_STEP_LEFT + cvar "+moveleft" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 0.0 + visible 0 + descText @MENUS_STEPS_PLAYER_TO_THE_LEFT + + action + { + play sound/interface/button1 + } + mouseenter + { + show button_glow + setitemrect button_glow 260 258 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name movement7 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_STEP_RIGHT + cvar "+moveright" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 0.0 + visible 0 + descText @MENUS_STEPS_PLAYER_TO_THE_RIGHT + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 272 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name movement8 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_SIDESTEP_TURN + cvar "+strafe" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 0.0 + visible 0 + descText @MENUS_HELD_ALLOWS_PLAYER_TO + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 286 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name movement12 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_UP_JUMP + cvar "+moveup" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 0.0 + visible 0 + descText @MENUS_MAKES_PLAYER_JUMP_IF + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 342 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name movement13 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_DOWN_CROUCH + cvar "+movedown" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 0.0 + visible 0 + descText @MENUS_MAKES_PLAYER_CROUCH_TO + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 356 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name movement11 + group movecontrols + text @MENUS_HOLD_USE_PLUS_STRAFE + font 4 + textscale 1 + rect 260 384 340 14 + textalign ITEM_ALIGN_CENTER + textalignx 170 + textaligny 0 + forecolor .79 .64 .22 .7 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// INTERACTION BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name attacklook1 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_ATTACK + cvar "+attack" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_ATTACKS_WITH_READIED + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 186 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name attacklook2 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_ALT_ATTACK + cvar "+altattack" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_ATTACKS_WITH_ALTERNATE + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 200 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name attacksaber + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LIGHTSABER_STYLE + cvar "saberAttackCycle" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_CYCLES_BETWEEN_AVAILABLE + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 214 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name attacklook3 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_USE + cvar "+use" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 0.0 + visible 0 + descText @MENUS_ACTIVATES_WORLD_DEVICES + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 242 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name attacklook4 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LOOK_UP + cvar "+lookup" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_TILTS_VIEW_UPWARDS + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 298 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + + itemDef + { + name attacklook5 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LOOK_DOWN + cvar "+lookdown" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_TILTS_VIEW_DOWNWARDS + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 312 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_MOUSE_LOOK + cvar "+mlook" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 0.0 + visible 0 + descText @MENUS_IF_HELD_ALLOWS_PLAYER + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 326 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_CENTERVIEW + cvar "centerview" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_RETURNS_VIEW_TO_HORIZONTAL + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 340 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// WEAPON BINDING +//---------------------------------------------------------------------------------------------- + itemDef + { + name weapon1 + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_MELEE_LIGHTSABER + cvar "weapon 1" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_READIES_LIGHTSABER + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 186 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name weapon3 + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_PISTOL + cvar "weapon 2" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_READIES_THE_BLASTER_PISTOL + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 200 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_RIFLE + cvar "weapon 3" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_READIES_THE_E_11_BLASTER + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 214 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_DISRUPTOR_RIFLE + cvar "weapon 4" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_READIES_THE_TENLOSS_DXR_6 + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 228 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_BOWCASTER + cvar "weapon 5" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_READIES_THE_WOOKIEE_BOWCASTER + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 242 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_HEAVY_REPEATER + cvar "weapon 6" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_READIES_THE_IMPERIAL + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 256 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_DEMP_2 + cvar "weapon 7" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_READIES_THE_DEMP2_GUN + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 270 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_FLECHETTE + cvar "weapon 8" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_READIES_THE_GOLAN_ARMS + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 284 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_CONC_RIFLE_SETUP + cvar "weapon 13" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_READIES_THE_CONC_RIFLE + + mouseenter + { + show button_glow + setitemrect button_glow 260 298 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_MERR_SONN + cvar "weapon 9" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_READIES_THE_MERR_SONN + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 312 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_THROWABLE_WEAPONS + cvar "weapon 10" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_TOGGLES_BETWEEN_DETONATORS + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 326 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_NEXT_WEAPON + cvar "weapnext" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_SELECTS_THE_NEXT_WEAPON + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 340 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_PREVIOUS_WEAPON + cvar "weapprev" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_SELECTS_THE_PREVIOUS + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 354 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// +// FORCE BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name force1 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PUSH + cvar "force_throw" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_FORCE_PUSH_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 186 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name force2 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PULL + cvar "force_pull" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_FORCE_PULL_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 200 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name force3 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_SPEED + cvar "force_speed" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_FORCE_SPEED_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 214 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name force4 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_SENSE + cvar "force_sight" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_FORCE_SENSE_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 228 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name force5 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_ABSORB + cvar "force_absorb" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_FORCE_ABSORB_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 242 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name force6 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_HEAL + cvar "force_heal" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_FORCE_HEAL_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 256 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name force7 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_MINDTRICK + cvar "force_distract" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_JEDI_MIND_TRICK + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 270 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name force8 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PROTECT + cvar "force_protect" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_FORCE_PROTECT_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 284 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name force9 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_DRAIN + cvar "+force_drain" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_DRAIN_FORCE_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 298 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name force10 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_GRIP + cvar "+force_grip" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_FORCE_GRIP_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 312 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + + itemDef + { + name force11 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_LIGHTNING + cvar "+force_lightning" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_FORCE_LIGHTNING + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 326 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name force12 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_DARK_RAGE + cvar "force_rage" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_RAGE_FORCE_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 340 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_USE_FORCE_POWER + cvar "+useforce" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_USES_CURRENTLY_SELECTED + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 354 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_NEXT + cvar "forcenext" + rect 260 370 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_SELECTS_NEXT_AVAILABLE + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 368 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PREVIOUS + cvar "forceprev" + rect 260 384 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_SELECTS_PREVIOUS_AVAILABLE + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 382 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// +// QUICK KEY BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name quickkeys1 + group quickcontrols + type ITEM_TYPE_BIND + text @MENUS_DATAPAD + cvar "datapad" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_CHECK_DATAPAD_FOR_MISSION + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 186 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name quickkeys + group quickcontrols + type ITEM_TYPE_BIND + text @MENUS_SAVE_MENU + cvar "uimenu ingamesavemenu" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_BRINGS_UP_SAVE_GAME_MENU + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 214 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name quickkeys + group quickcontrols + type ITEM_TYPE_BIND + text @MENUS_LOAD_MENU + cvar "uimenu ingameloadmenu" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_BRINGS_UP_LOAD_GAME_MENU + + action + { + play sound/interface/button1 + } + mouseenter + { + show button_glow + setitemrect button_glow 260 228 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name quickkeys + group quickcontrols + type ITEM_TYPE_BIND + text @MENUS_INSTANT_SAVE + cvar "save quick" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_AUTOMATICALLY_SAVES_GAME + + action + { + play sound/interface/button1 + } + mouseenter + { + show button_glow + setitemrect button_glow 260 242 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name quickkeys + group quickcontrols + type ITEM_TYPE_BIND + text @MENUS_INSTANT_LOAD + cvar "load quick" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_AUTOMATICALLY_LOADS_GAME + + action + { + play sound/interface/button1 + } + mouseenter + { + show button_glow + setitemrect button_glow 260 256 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + + //---------------------------------------------------------------------------------------------- + // + // MOUSE/JOYSTICK KEY BINDING + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_INVERT_MOUSE + cvar "ui_mousePitch" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_TOGGLE_TO_TILT_VIEW_IN + + action { + uiScript update ui_mousePitch + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 200 340 20 + } + + mouseexit + { + hide button_glow + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_SMOOTH_MOUSE + cvar "m_filter" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_WHEN_TURNED_ON_MOUSE + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 214 340 20 + } + + mouseexit + { + hide button_glow + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_SLIDER + text @MENUS_SENSITIVITY + cvarfloat "sensitivity" 5 2 30 + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_ADJUSTS_CHARACTER_REACTION + + action + { + play sound/interface/button1 + } + mouseenter + { + show button_glow + setitemrect button_glow 260 228 340 20 + } + mouseexit + { + hide button_glow + } + } + + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_ENABLE_JOYSTICK + cvar "in_joystick" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_TURNED_ON_GAME_SEARCHES + action + { + play sound/interface/button1 + exec in_restart + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 256 340 20 + } + + mouseexit + { + hide button_glow + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_X_AXIS_AS_BUTTONS + cvar "joy_xbutton" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_WHEN_OFF_HORIZONTAL + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 270 340 20 + } + + mouseexit + { + hide button_glow + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_Y_AXIS_AS_BUTTONS + cvar "joy_ybutton" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_WHEN_OFF_VERTICAL_STICK + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 284 340 20 + } + + mouseexit + { + hide button_glow + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_SLIDER + text @MENUS_JOYSTICK_THRESHOLD + cvarfloat "joy_threshold" .15 .05 .75 + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_ADJUSTS_THE_SIZE_OF_THE + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 298 340 20 + } + mouseexit + { + hide button_glow + } + } + +// +// Not shown +// + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_FORCE_FEEDBACK + cvar "use_ff" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_WHEN_TURNED_ON_GAME + action + { + play sound/interface/button1 + uiScript update ff + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 326 340 20 + show ffwarning + } + + mouseexit + { + hide button_glow + hide ffwarning + } + } + + itemDef + { + name ffwarning + type ITEM_TYPE_TEXT + text @MENUS_APPLY_FORCE_FEEDBACK + text2 @MENUS_AND_RETURN_TO_THE_MAIN + rect 260 360 340 14 + textalign ITEM_ALIGN_CENTER + textalignx 170 + text2aligny 18 + font 4 + textscale 1 + // foreColor 1 .682 0 .8 + forecolor 1 0 0 1 + decoration + visible 0 + } + + + //---------------------------------------------------------------------------------------------- + // + // OTHER + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name other1 + group othercontrols + type ITEM_TYPE_YESNO + text @MENUS_ALWAYS_RUN + cvar "cl_run" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_WHEN_ON_PLAYER_ALWAYS + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 186 340 20 + } + + mouseexit + { + hide button_glow + } + } + + itemDef + { + name other2 + group othercontrols + type ITEM_TYPE_MULTI + text @MENUS_AUTO_SWITCH + cvar "cg_autoswitch" + cvarFloatList + { + @MENUS_DON_T_SWITCH 0 + @MENUS_BEST_SAFE_WEAPON 1 + @MENUS_ALWAYS_BEST_WEAPON 2 + } + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_CHOOSE_WHETHER_TO_SWITCH + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 200 340 20 + } + + mouseexit + { + hide button_glow + } + + } + + itemDef + { + name other3 + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_3RD_PERSON + cvar "cg_thirdperson !" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_CHANGES_VIEW_BETWEEN + action + { + play sound/interface/button1 + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 228 340 20 + show keybindstatus + } + mouseexit + { + hide button_glow + hide keybindstatus + } + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // Text + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name keyBindStatus + group none + ownerdraw 250 // UI_KEYBINDSTATUS + text @MENUS_BLANK_1 + rect 375 425 0 0 + textStyle 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + forecolor 1 1 0 1 + visible 0 + decoration + } + + itemDef + { + name slider_message + group none + text @MENUS_MOVE_THE_SLIDER_TO_INCREASE + rect 375 425 0 0 + textStyle 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + visible 0 + decoration + } + + itemDef + { + name yesno_message + group none + text @MENUS_CLICK_ON_FIELD_TO_TOGGLE + rect 375 425 0 0 + textStyle 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + visible 0 + decoration + } + + itemDef + { + name multi_message + group none + text @MENUS_CLICK_ON_FIELD_TO_CHANGE + rect 375 425 0 0 + textStyle 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + visible 0 + decoration + } + + } +} diff --git a/base/ui/credits.menu b/base/ui/credits.menu new file mode 100644 index 0000000..6b27c2f --- /dev/null +++ b/base/ui/credits.menu @@ -0,0 +1,441 @@ +// CREDITS MENU +{ + menuDef + { + name "creditsMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + appearanceIncrement 75 // In miliseconds + descX 320 + descY 434 + descScale 1 + descColor .235 .882 .847 1 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + exec "music music/cairn_bay/impbasee_action" ; + setfocus newgamebutton ; + } + + onClose + { + exec "music music/cairn_bay/impbasee_explore" ; + } + + onESC + { + play "sound/interface/button1.wav" ; + close all ; + open mainMenu ; + } + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name frame_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/menu1" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + + // The saber glow on the left + itemDef + { + name saberglow + group none + style WINDOW_STYLE_SHADER + rect 30 0 90 480 + background "gfx/menus/menu3" // Frame + forecolor 0.8 0.8 0.8 1 + visible 1 + decoration + } + + + // The starwars logo on the top + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 143 12 470 93 + background "gfx/menus/menu4" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + + // The saber halo on the left + itemDef + { + name saberhalo + group none + style WINDOW_STYLE_SHADER + rect -425 -185 1000 1000 + background "gfx/menus/menu2" // Frame + forecolor 0.5 0.5 0.5 1 + visible 1 + decoration + } + + itemDef + { + name logomodel + group none + type ITEM_TYPE_MODEL + rect -123 48 400 400 + model_angle 90 + model_rotation 3.5 + asset_model "models/map_objects/bespin/jk2logo.md3" +// model_fovx 37 +// model_fovy 34 +// model_origin 100 100 100 + visible 1 + decoration + } + + // The saber halo on the left + itemDef + { + name saberhalo2 + group none + style WINDOW_STYLE_SHADER + rect -225 15 600 600 + background "gfx/menus/menu2b" // Frame + forecolor 0.25 0.25 0.25 1 + visible 1 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // TOP MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 115 115 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 115 115 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 0.9 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + forecolor 0.64 0.65 1 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open newgameMenu + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 245 115 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name loadgamebutton + group toprow + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 245 115 130 24 + textaligny 0 + font 3 + textscale 0.9 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + forecolor 0.64 0.65 1 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open loadgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 375 115 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 375 115 130 24 + font 3 + textscale 0.9 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + backcolor 0 0 0 0 + forecolor 0.64 0.65 1 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 505 115 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 505 115 130 24 + font 3 + textscale 0.9 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + backcolor 0 0 0 0 + forecolor 0.64 0.65 1 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setupMenu ; + } + } + + itemDef + { + name header_line + group toprow + style WINDOW_STYLE_SHADER + rect 125 136 500 4 + background "gfx/menus/menu_line" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 115 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 115 444 130 24 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 0.65 0.65 1 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // CREDITS FIELDS + // + //---------------------------------------------------------------------------------------------- + // Credits title + itemDef + { + name credits_title + group none + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CREDITS + rect 150 145 450 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor 1 1 1 1 + visible 1 + // appearance_slot 2 + decoration + } + + + itemDef + { + name confirm + group none + text @MENUS_TO_BE_DETERMINED + font 2 + textscale 1 + textstyle 3 + rect 320 220 110 20 + textalign ITEM_ALIGN_CENTER + textalignx 0 + textaligny 0 + decoration + forecolor .433 .703 .722 1 + visible 1 + // appearance_slot 2 + } + + } +} + + + + + + + + + + + diff --git a/base/ui/datapadforcepowers.menu b/base/ui/datapadforcepowers.menu new file mode 100644 index 0000000..e6cdc30 --- /dev/null +++ b/base/ui/datapadforcepowers.menu @@ -0,0 +1,368 @@ +// Data Pad : Force Powers Screen +// +// defines from ui_shared.h +{ + + menuDef + { + name "datapadForcePowersMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 444 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onESC + { + play "sound/interface/esc.wav" + uiScript closedatapad // Close menu + } + + onOpen + { + uiScript nextDataPadForcePower + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + rect 0 0 640 480 + style WINDOW_STYLE_SHADER + background "gfx/menus/datapad" + forecolor 1 1 1 1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// MISSION TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name forcepowers + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_OWNERDRAW + rect 50 40 95 40 + textscale .4 + forecolor .509 .609 .847 1 + visible 1 + ownerdraw 206 // UI_DATAPAD_FORCEPOWERS + } + + itemDef + { + name screen_title + type ITEM_TYPE_TEXT + rect 170 3 300 26 + text @MENUS_DATAPADTITLE + font 3 + forecolor .549 .854 1 1 + textscale 1.2 + textalign ITEM_ALIGN_CENTER + textalignx 150 + visible 1 + decoration + } + + itemDef + { + name prevpower_on + group none + style WINDOW_STYLE_SHADER + rect 245 382 32 26 + background "gfx/menus/dp_arrow_lon" // Frame + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name prevpower_off + group none + style WINDOW_STYLE_SHADER + rect 245 382 32 26 + background "gfx/menus/dp_arrow_l" // Frame + forecolor 1 1 1 1 + decoration + visible 1 + } + + itemDef + { + name prevpower + group none + descText @MENUS_DP_FORCE_PREV + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 245 382 32 26 + font 2 + textscale 1 + textalignx 48 + textalign ITEM_ALIGN_CENTER + visible 1 + + action + { + play "sound/interface/button1.wav" ; + uiScript prevDataPadForcePower + } + mouseEnter + { + hide prevpower_off + show prevpower_on + } + mouseExit + { + show prevpower_off + hide prevpower_on + } + } + + itemDef + { + name nextpower_on + group none + style WINDOW_STYLE_SHADER + rect 364 382 32 26 + background "gfx/menus/dp_arrow_ron" // Frame + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name nextpower_off + group none + style WINDOW_STYLE_SHADER + rect 364 382 32 26 + background "gfx/menus/dp_arrow_r" // Frame + forecolor 1 1 1 1 + decoration + visible 1 + } + + itemDef + { + name nextpower + group none + style WINDOW_STYLE_EMPTY + descText @MENUS_DP_FORCE_NEXT + type ITEM_TYPE_BUTTON + rect 364 382 32 26 + background "gfx/menus/dp_arrow_r" // Frame + font 2 + textscale 1 + textalignx 48 + textalign ITEM_ALIGN_CENTER + forecolor .509 .609 .847 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + uiScript nextDataPadForcePower + } + mouseEnter + { + hide nextpower_off + show nextpower_on + } + mouseExit + { + show nextpower_off + hide nextpower_on + } + } + +//---------------------------------------------------------------------------------------------- +// LOWER BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name mission + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 20 420 120 25 + text @MENUS_DP_MISSION + descText @MENUS_AN_OVERVIEW_OF_MISSION + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 10 419 150 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadMissionMenu + } + + } + + itemDef + { + name weapons + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 140 420 120 25 + text @MENUS_WEAPONS + descText @MENUS_VIEW_CURRENTLY_OWNED + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 130 419 150 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadWeaponsMenu + } + } + + itemDef + { + name force + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 260 420 120 25 + text @MENUS_DP_FORCE + descText @MENUS_VIEW_CURRENT_FORCE_POWERS + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + forecolor 1 1 1 1 + visible 1 + decoration + + } + + itemDef + { + name moves + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 380 420 120 25 + text @MENUS_MOVES + descText @MENUS_MOVES_DESC + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 370 419 150 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadMovesMenu + } + } + + itemDef + { + name exit + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 500 420 120 25 + text @MENUS_RESUME + descText @MENUS_RETURN_TO_GAME + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 490 419 150 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + uiScript closedatapad // Close menu + } + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + } +} diff --git a/base/ui/datapadinventory.menu b/base/ui/datapadinventory.menu new file mode 100644 index 0000000..cd6989f --- /dev/null +++ b/base/ui/datapadinventory.menu @@ -0,0 +1,307 @@ +// Data Pad : Inventory Screen +// +// defines from ui_shared.h +{ + menuDef + { + name "datapadInventoryMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + appearanceIncrement 75 // In miliseconds + descX 320 + descY 441 + descScale 1 + descColor .96 .933 .40 1 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + + onESC + { + uiScript closedatapad // Close menu + } + + onOpen + { + uiScript nextDataPadInventory + } + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name frame_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "*white" + forecolor 0 0 0 1 + visible 1 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // MISSION TEXT + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name mission2 + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_OWNERDRAW + rect 50 40 95 40 + textscale .4 + forecolor .509 .609 .847 1 + visible 1 + ownerdraw 205 // UI_DATAPAD_INVENTORY + } + + itemDef + { + name screen_title + type ITEM_TYPE_TEXT + rect 170 8 300 26 + text @MENUS_DATAPADTITLE + font 3 + forecolor .509 .609 .847 1 + textscale 1.25 + textalign ITEM_ALIGN_CENTER + textalignx 150 + visible 1 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // LOWER MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name mission + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 22 417 100 25 + text @MENUS_DP_MISSION + descText @MENUS_AN_OVERVIEW_OF_MISSION + font 2 + textscale 1 + textalignx 50 + textalign ITEM_ALIGN_CENTER + forecolor .79 .64 .22 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadMissionMenu + } + } + + + itemDef + { + name weapons + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 142 417 100 25 + text @MENUS_WEAPONS + descText @MENUS_VIEW_CURRENTLY_OWNED + font 2 + textscale 1 + textalignx 50 + textalign ITEM_ALIGN_CENTER + forecolor .79 .64 .22 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadWeaponsMenu + } + } + + itemDef + { + name force + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 262 417 100 25 + text @MENUS_DP_FORCE + descText @MENUS_VIEW_CURRENT_FORCE_POWERS + font 2 + textscale 1 + textalignx 50 + textalign ITEM_ALIGN_CENTER + forecolor .79 .64 .22 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadForcePowersMenu + } + } + + itemDef + { + name inventory + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 382 417 100 0 + text @MENUS_INVENTORY + descText @MENUS_VIEW_CURRENT_INVENTORY + font 2 + textscale 1 + textalignx 50 + textalign ITEM_ALIGN_CENTER + forecolor .79 .64 .22 1 + visible 1 + } + + itemDef + { + name exit + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 502 417 95 25 + text @MENUS_RESUME + descText @MENUS_RETURN_TO_GAME + font 2 + textscale 1 + textalignx 50 + textalign ITEM_ALIGN_CENTER + forecolor .79 .64 .22 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + uiScript closedatapad // Close menu + } + } + + itemDef + { + name previnv_on + group none + style WINDOW_STYLE_SHADER + rect 245 382 32 26 + background "gfx/menus/dp_arrow_lon" // Frame + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name previnv_off + group none + style WINDOW_STYLE_SHADER + rect 245 382 32 26 + background "gfx/menus/dp_arrow_l" // Frame + forecolor 1 1 1 1 + decoration + visible 1 + } + + itemDef + { + name previnv + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 245 382 32 26 + font 2 + textscale 1 + textalignx 48 + textalign ITEM_ALIGN_CENTER + forecolor .509 .609 .847 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + uiScript prevDataPadInventory + } + mouseEnter + { + hide previnv_off + show previnv_on + } + mouseExit + { + show previnv_off + hide previnv_on + } + } + + itemDef + { + name nextinv_on + group none + style WINDOW_STYLE_SHADER + rect 364 382 32 26 + background "gfx/menus/dp_arrow_ron" // Frame + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name nextinv_off + group none + style WINDOW_STYLE_SHADER + rect 364 382 32 26 + background "gfx/menus/dp_arrow_r" // Frame + forecolor 1 1 1 1 + decoration + visible 1 + } + + itemDef + { + name nextinv + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 364 382 32 26 + font 2 + textscale 1 + textalignx 48 + textalign ITEM_ALIGN_CENTER + forecolor .509 .609 .847 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + uiScript nextDataPadInventory + } + mouseEnter + { + hide nextinv_off + show nextinv_on + } + mouseExit + { + show nextinv_off + hide nextinv_on + } + } + } +} \ No newline at end of file diff --git a/base/ui/datapadmission.menu b/base/ui/datapadmission.menu new file mode 100644 index 0000000..858b883 --- /dev/null +++ b/base/ui/datapadmission.menu @@ -0,0 +1,258 @@ +//---------------------------------------------------------------------------------------------- +// Data Pad : Mission Objectives Screen +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "datapadMissionMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 444 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + onESC + { + play "sound/interface/esc.wav" + uiScript closedatapad // Close menu + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + rect 0 0 640 480 + style WINDOW_STYLE_SHADER + background "gfx/menus/datapad" + forecolor 1 1 1 1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// MISSION TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name mission2 + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_OWNERDRAW + rect 50 40 0 0 //This is set in the code + textscale .4 + forecolor .509 .609 .847 1 + visible 1 + ownerdraw 203 // UI_DATAPAD_MISSION + } + + itemDef + { + name screen_title + type ITEM_TYPE_TEXT + rect 170 3 300 26 + text @MENUS_DATAPADTITLE + font 3 + forecolor .549 .854 1 1 + textscale 1.2 + textalign ITEM_ALIGN_CENTER + textalignx 150 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// LOWER BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name mission + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 20 420 120 25 + text @MENUS_DP_MISSION + descText @MENUS_AN_OVERVIEW_OF_MISSION + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 1 1 1 + visible 1 + decoration + + } + + itemDef + { + name weapons + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 140 420 120 25 + text @MENUS_WEAPONS + descText @MENUS_VIEW_CURRENTLY_OWNED + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 130 419 150 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadWeaponsMenu + } + } + + itemDef + { + name force + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 260 420 120 25 + text @MENUS_DP_FORCE + descText @MENUS_VIEW_CURRENT_FORCE_POWERS + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 250 419 150 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadForcePowersMenu + } + } + + itemDef + { + name moves + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 380 420 120 25 + text @MENUS_MOVES + descText @MENUS_MOVES_DESC + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 370 419 150 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadMovesMenu + } + } + + itemDef + { + name exit + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 500 420 120 25 + text @MENUS_RESUME + descText @MENUS_RETURN_TO_GAME + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 490 419 150 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + uiScript closedatapad // Close menu + } + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + } +} + + + + + + + + + diff --git a/base/ui/datapadmoves.menu b/base/ui/datapadmoves.menu new file mode 100644 index 0000000..294e080 --- /dev/null +++ b/base/ui/datapadmoves.menu @@ -0,0 +1,458 @@ +// Data Pad : Force Powers Screen +// +// defines from ui_shared.h +{ + + menuDef + { + name "datapadMovesMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 444 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onESC + { + play "sound/interface/esc.wav" + uiScript closedatapad // Close menu + } + + onOpen + { + setcvar "ui_movesflag" 0 + setcvar "ui_move_title" 0 + uiScript setMoveCharacter + setcvar "ui_move_title" 2 + uiScript resetMovesDesc + uiScript setMovesListDefault + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + rect 0 0 640 480 + style WINDOW_STYLE_SHADER + background "gfx/menus/datapad2" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MISSION TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name screen_title + type ITEM_TYPE_TEXT + rect 170 3 300 26 + text @MENUS_DATAPADTITLE + font 3 + forecolor .549 .854 1 1 + textscale 1.2 + textalign ITEM_ALIGN_CENTER + textalignx 150 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// DATA +//---------------------------------------------------------------------------------------------- + itemDef + { + name move_titles_box + group none + style WINDOW_STYLE_FILLED + rect 25 47 255 23 + backcolor 0 0 .6 1 + visible 1 + decoration + } + itemDef + { + name move_titles_outline + group none + style WINDOW_STYLE_EMPTY + rect 24 68 255 30 + border 1 + bordersize 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + itemDef + { + name move_titles_title + type ITEM_TYPE_TEXT + rect 25 47 255 23 + text @MENUS_MOVE_TYPES + font 3 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 125 + visible 1 + decoration + } + + itemDef + { + name move_titles + group none + text " " + descText @MENUS_CHOOSE_MOVE_DESC + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 25 77 340 14 + font 2 + textscale .8 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle .8 + textalignx 0 + backcolor 0 0 0 0 + forecolor .615 .615 .956 1 + feeder 22 //FEEDER_MOVES_TITLES + cvar "ui_move_title" + cvarFloatList + { + "@MENUS_ACROBATICS" 0 + "@MENUS_SINGLE_FAST" 1 + "@MENUS_SINGLE_MEDIUM" 2 + "@MENUS_SINGLE_STRONG" 3 + "@MENUS_DUAL_SABERS" 4 + "@MENUS_SABER_STAFF" 5 + } + + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 0 73 320 24 + } + mouseExit + { + hide button_glow + } + action + { + uiScript resetMovesList + uiScript resetMovesDesc + play "sound/interface/button1.wav" + } + } + + itemDef + { + name moveslist_title_box + group none + style WINDOW_STYLE_FILLED + rect 26 110 253 23 + backcolor 0 0 .6 1 + visible 1 + decoration + } + + itemDef + { + name moveslist_title + type ITEM_TYPE_TEXT + rect 35 108 253 26 + text @MENUS_MOVES + font 3 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 125 + visible 1 + decoration + } + + itemDef + { + name moveslist + group none + rect 25 130 253 250 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 16 + font 4 + textaligny 8 + textscale 1 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder 21 + desctext @MENUS_MOVE_DESC2 + notselectable + visible 1 + columns 1 2 55 225 + mouseEnter + { + setitemcolor moveslist bordercolor 1 1 1 1 + } + mouseExit + { + setitemcolor moveslist bordercolor 0 0 .8 1 + } + action + { + uiScript resetMovesDesc + } + + doubleclick + { + play sound/interface/button1.wav + uiScript resetMovesDesc + } + } + + itemDef + { + name character_box + group none + style WINDOW_STYLE_EMPTY + rect 285 38 330 360 + border 1 + bordersize 2 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + itemDef + { + name character + group models + type ITEM_TYPE_MODEL + rect 295 -30 300 340 + + model_g2anim "BOTH_ROLL_F" + isSaber 1 + isCharacter 1 + asset_model "ui_char_model" + model_angle 180 + model_g2mins -20 -15 -10 + model_g2maxs 20 15 45 + model_rotation 50 + model_fovx 50 + model_fovy 50 + visible 1 + decoration + + } + + itemDef + { + name item_desc + type ITEM_TYPE_TEXTSCROLL + rect 295 300 320 90 + text " " + cvar ui_move_desc + font 4 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 6 + visible 1 + lineHeight 13 + + } + +//---------------------------------------------------------------------------------------------- +// LOWER BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name mission + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 20 420 120 25 + text @MENUS_DP_MISSION + descText @MENUS_AN_OVERVIEW_OF_MISSION + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 10 419 150 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadMissionMenu + } + } + + itemDef + { + name weapons + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 140 420 120 25 + text @MENUS_WEAPONS + descText @MENUS_VIEW_CURRENTLY_OWNED + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 130 419 150 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadWeaponsMenu + } + } + + itemDef + { + name force + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 260 420 120 25 + text @MENUS_DP_FORCE + descText @MENUS_VIEW_CURRENT_FORCE_POWERS + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 250 419 150 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadForcePowersMenu + } + } + + itemDef + { + name moves + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 380 420 120 25 + text @MENUS_MOVES + descText @MENUS_MOVES_DESC + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 1 1 1 + visible 1 + decoration + + } + + itemDef + { + name exit + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 500 420 120 25 + text @MENUS_RESUME + descText @MENUS_RETURN_TO_GAME + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 490 419 150 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + uiScript closedatapad // Close menu + } + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + } +} diff --git a/base/ui/datapadweapons.menu b/base/ui/datapadweapons.menu new file mode 100644 index 0000000..7011849 --- /dev/null +++ b/base/ui/datapadweapons.menu @@ -0,0 +1,411 @@ +//---------------------------------------------------------------------------------------------- +// Data Pad : Weapons Screen +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "datapadWeaponsMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 444 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onESC + { + play "sound/interface/esc.wav" + uiScript closedatapad // Close menu + } + + onOpen + { + uiScript nextDataPadWeapon + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + rect 0 0 640 480 + style WINDOW_STYLE_SHADER + background "gfx/menus/datapad" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MISSION TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name weapons + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_OWNERDRAW + rect 50 150 95 40 + textscale .8 + forecolor .509 .609 .847 1 + visible 1 + ownerdraw 204 // UI_DATAPAD_WEAPONS + } + + itemDef + { + name screen_title + type ITEM_TYPE_TEXT + rect 170 3 300 26 + text @MENUS_DATAPADTITLE + font 3 + forecolor .549 .854 1 1 + textscale 1.2 + textalign ITEM_ALIGN_CENTER + textalignx 150 + visible 1 + decoration + } + + itemDef + { + name weapon1 + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + forecolor 1 1 1 1 + visible 0 + decoration + } + + // Weapon Line 1 + itemDef + { + name weapon1_line1 + group none + text @MENUS_THIS_IS_LINE_1 + font 2 + textscale 1 + textstyle 3 + rect 320 220 110 20 + textalign ITEM_ALIGN_LEFT + textalignx 30 + textaligny 100 + decoration + forecolor .433 .703 .722 1 + visible 0 + } + + + itemDef + { + name prevweapon_on + group none + style WINDOW_STYLE_SHADER + rect 245 382 32 26 + background "gfx/menus/dp_arrow_lon" // Frame + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name prevweapon_off + group none + style WINDOW_STYLE_SHADER + rect 245 382 32 26 + background "gfx/menus/dp_arrow_l" // Frame + forecolor 1 1 1 1 + decoration + visible 1 + } + + itemDef + { + name prevweapon + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + desctext @MENUS_DP_WEAPON_PREV + rect 245 382 32 26 + font 2 + textscale 1 + textalignx 48 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + uiScript prevDataPadWeapon + } + + mouseEnter + { + hide prevweapon_off + show prevweapon_on + } + + mouseExit + { + show prevweapon_off + hide prevweapon_on + } + } + + itemDef + { + name nextweapon_on + group none + style WINDOW_STYLE_SHADER + rect 364 382 32 26 + background "gfx/menus/dp_arrow_ron" // Frame + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name nextweapon_off + group none + style WINDOW_STYLE_SHADER + rect 364 382 32 26 + background "gfx/menus/dp_arrow_r" // Frame + forecolor 1 1 1 1 + decoration + visible 1 + } + + itemDef + { + name nextweapon + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 364 382 32 26 + font 2 + desctext @MENUS_DP_WEAPON_NEXT + textscale 1 + textalignx 48 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + uiScript nextDataPadWeapon + } + + mouseEnter + { + hide nextweapon_off + show nextweapon_on + } + + mouseExit + { + show nextweapon_off + hide nextweapon_on + } + } + +//---------------------------------------------------------------------------------------------- +// LOWER BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name mission + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 20 420 120 25 + text @MENUS_DP_MISSION + descText @MENUS_AN_OVERVIEW_OF_MISSION + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 10 419 150 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadMissionMenu + } + + } + + itemDef + { + name weapons + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 140 420 120 25 + text @MENUS_WEAPONS + descText @MENUS_VIEW_CURRENTLY_OWNED + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name force + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 260 420 120 25 + text @MENUS_DP_FORCE + descText @MENUS_VIEW_CURRENT_FORCE_POWERS + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 250 419 150 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadForcePowersMenu + } + } + + itemDef + { + name moves + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 380 420 120 25 + text @MENUS_MOVES + descText @MENUS_MOVES_DESC + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 370 419 150 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open datapadMovesMenu + } + } + + itemDef + { + name exit + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 500 420 120 25 + text @MENUS_RESUME + descText @MENUS_RETURN_TO_GAME + font 2 + textscale 1 + textalignx 60 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 490 419 150 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + uiScript closedatapad // Close menu + } + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + } +} + + + + + + + + + + diff --git a/base/ui/demo_ForceSelect.menu b/base/ui/demo_ForceSelect.menu new file mode 100644 index 0000000..e45f03e --- /dev/null +++ b/base/ui/demo_ForceSelect.menu @@ -0,0 +1,2490 @@ +//---------------------------------------------------------------------------------------------- +// +// Force Select Menu +// +// The player is allowed to allocate one point to any of the given force powers +// +// +// Be sure not to change the names of the fields that end with +// _hexpic +// _fbutton +// _level1desc +// _level2desc +// _level3desc +// +// Also don't change +// allocate_text +// allocated_text +// +// +// The uiScript initallocforcepower sets the rank background for a force power +// +// The uiScript showforceleveldesc show the appropriate level description for that force power. +// +// The uiScript affectforcepowerlevel upgrades a force power rank. It +// also greys out or ungreys the icons. +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "demo_ForceSelect" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + descTextStyle 2 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 25 // how often fade happens in milliseconds + fadeAmount 0.05 // amount to adjust alpha per cycle + onOpen + { + stopVoice + + uiScript "demosetforcelevels" + + // Update force power pics to current force power levels + + + // Light powers + uiScript "initallocforcepower" "absorb" + uiScript "initallocforcepower" "heal" + uiScript "initallocforcepower" "mindtrick" + uiScript "initallocforcepower" "protect" + + // Core powers + uiScript "initallocforcepower" "jump" + uiScript "initallocforcepower" "pull" + uiScript "initallocforcepower" "push" + uiScript "initallocforcepower" "sense" + uiScript "initallocforcepower" "speed" + uiScript "initallocforcepower" "sabdef" + uiScript "initallocforcepower" "saboff" + uiScript "initallocforcepower" "sabthrow" + + // Dark powers + uiScript "initallocforcepower" "drain" + uiScript "initallocforcepower" "grip" + uiScript "initallocforcepower" "lightning" + uiScript "initallocforcepower" "rage" + + } + + onClose + { + uiScript recordforcelevels + } + + + onESC + { + play "sound/interface/menuroam.wav" + } + + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/forcemenu_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TEXT - Allocate Force power point +//---------------------------------------------------------------------------------------------- + // Allocate force point + itemDef + { + name allocate_text + type ITEM_TYPE_TEXT + rect 0 5 640 18 + text @MENUS_ALLOCATE_FP + font 2 + forecolor 1 1 1 1 + textscale .80 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 1 + decoration + } + + // Force point allocated + itemDef + { + name allocated_text + type ITEM_TYPE_TEXT + rect 0 5 640 18 + text @MENUS_ALLOCATED_FP + font 2 + forecolor 1 1 1 1 + textscale .80 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 0 + decoration + } + + // Core Power message + itemDef + { + name allocated_text_core + type ITEM_TYPE_TEXT + rect 0 5 640 18 + text @MENUS_COREPOWER_DESC + font 2 + forecolor 1 1 1 1 + textscale .80 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// So these will precache +//---------------------------------------------------------------------------------------------- + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_1" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_2" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_3" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_1_gold" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_2_gold" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_3_gold" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// DEALLOCATION BUTTON - this button is hidden until a power is chosen then this +// becomes active so the power can be deallocated +//---------------------------------------------------------------------------------------------- + itemDef + { + name deallocate_fbutton + group none + type ITEM_TYPE_BUTTON + rect 0 0 0 0 + forecolor 1 1 1 1 + visible 0 + // Decrement force power + action + { + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + + uiScript "decrementcurrentforcepower" + + hide deallocate_fbutton + + show force_button + show force_icon + show force_desc + +// setfocus absorb_fbutton + + } + } + +//---------------------------------------------------------------------------------------------- +// ABSORB BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name absorb_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name absorb_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 33 30 64 64 + background "gfx/mp/NEW_f_icon_lt_absorb" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name absorb_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 33 41 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { + setitemcolor absorb_iconpic forecolor 1 1 1 1 + setitemcolor absorb_hexpic forecolor 1 1 1 1 + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + } + + // Because this gets focus when the menu first opens. + onFocus + { + setitemcolor absorb_iconpic forecolor 1 1 1 1 + setitemcolor absorb_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "absorb" + + setitembackground force_icon "gfx/mp/NEW_f_icon_lt_absorb" + setitemtext force_desc @SP_INGAME_FORCE_ABSORB_DESC + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "absorb" + hide force_button + + setitemrect deallocate_fbutton 33 41 115 46 + show deallocate_fbutton + + setitemcolor absorb_iconpic forecolor 1 1 1 1 + setitemcolor absorb_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// HEAL BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name heal_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 76 95 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name heal_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 33 85 64 64 + background "gfx/mp/NEW_f_icon_lt_heal" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name heal_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 33 95 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor heal_iconpic forecolor 1 1 1 1 + setitemcolor heal_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "heal" + + setitembackground force_icon "gfx/mp/NEW_f_icon_lt_heal" + setitemtext force_desc @SP_INGAME_FORCE_HEAL_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor heal_iconpic forecolor .65 .65 .65 1 + setitemcolor heal_hexpic forecolor .65 .65 .65 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "heal" + hide force_button + + setitemrect deallocate_fbutton 33 95 115 46 + show deallocate_fbutton + + setitemcolor heal_iconpic forecolor 1 1 1 1 + setitemcolor heal_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// MINDTRICK BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name mindtrick_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 76 149 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name mindtrick_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 33 141 64 64 + background "gfx/mp/NEW_f_icon_lt_mind_trick" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name mindtrick_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 33 149 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor mindtrick_iconpic forecolor 1 1 1 1 + setitemcolor mindtrick_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "mindtrick" + + setitembackground force_icon "gfx/mp/NEW_f_icon_lt_mind_trick" + setitemtext force_desc @SP_INGAME_FORCE_MIND_TRICK_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor mindtrick_iconpic forecolor .65 .65 .65 1 + setitemcolor mindtrick_hexpic forecolor .65 .65 .65 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "mindtrick" + + hide force_button + + setitemrect deallocate_fbutton 33 149 115 46 + show deallocate_fbutton + + setitemcolor mindtrick_iconpic forecolor 1 1 1 1 + setitemcolor mindtrick_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// PROTECT BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name protect_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 76 205 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name protect_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 33 196 64 64 + background "gfx/mp/NEW_f_icon_lt_protect" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name protect_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 33 205 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor protect_iconpic forecolor 1 1 1 1 + setitemcolor protect_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "protect" + + setitembackground force_icon "gfx/mp/NEW_f_icon_lt_protect" + setitemtext force_desc @SP_INGAME_FORCE_PROTECT_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor protect_iconpic forecolor .65 .65 .65 1 + setitemcolor protect_hexpic forecolor .65 .65 .65 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "protect" + + hide force_button + + setitemrect deallocate_fbutton 33 205 115 46 + show deallocate_fbutton + + setitemcolor protect_iconpic forecolor 1 1 1 1 + setitemcolor protect_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// JUMP BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name jump_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 225 41 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name jump_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 184 30 64 64 + background "gfx/mp/NEW_f_icon_jump" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name jump_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 184 41 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor jump_iconpic forecolor 1 1 1 1 + setitemcolor jump_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "jump" + + setitembackground force_icon "gfx/mp/NEW_f_icon_jump" + setitemtext force_desc @SP_INGAME_FORCE_JUMP_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor jump_iconpic forecolor .65 .65 .65 1 + setitemcolor jump_hexpic forecolor .65 .65 .65 1 + + } + } + +//---------------------------------------------------------------------------------------------- +// PULL BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name pull_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 225 95 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name pull_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 184 85 64 64 + background "gfx/mp/NEW_f_icon_pull" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name pull_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 184 95 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor pull_iconpic forecolor 1 1 1 1 + setitemcolor pull_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "pull" + + setitembackground force_icon "gfx/mp/NEW_f_icon_pull" + setitemtext force_desc @SP_INGAME_FORCE_PULL_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor pull_iconpic forecolor .65 .65 .65 1 + setitemcolor pull_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// PUSH BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name push_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 225 149 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name push_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 184 141 64 64 + background "gfx/mp/NEW_f_icon_push" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name push_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 184 149 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor push_iconpic forecolor 1 1 1 1 + setitemcolor push_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "push" + + setitembackground force_icon "gfx/mp/NEW_f_icon_push" + setitemtext force_desc @SP_INGAME_FORCE_PUSH_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor push_iconpic forecolor .65 .65 .65 1 + setitemcolor push_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// SENSE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name sense_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 225 205 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name sense_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 184 196 64 64 + background "gfx/mp/NEW_f_icon_sight" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name sense_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 184 205 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor sense_iconpic forecolor 1 1 1 1 + setitemcolor sense_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "sense" + + setitembackground force_icon "gfx/mp/NEW_f_icon_sight" + setitemtext force_desc @SP_INGAME_FORCE_SENSE_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor sense_iconpic forecolor .65 .65 .65 1 + setitemcolor sense_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// SPEED BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name speed_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 380 41 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name speed_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 336 30 64 64 + background "gfx/mp/NEW_f_icon_speed" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name speed_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 336 41 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor speed_iconpic forecolor 1 1 1 1 + setitemcolor speed_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "speed" + + setitembackground force_icon "gfx/mp/NEW_f_icon_speed" + setitemtext force_desc @SP_INGAME_FORCE_SPEED_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor speed_iconpic forecolor .65 .65 .65 1 + setitemcolor speed_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// SABER DEFENSIVE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name sabdef_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 380 95 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name sabdef_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 336 85 64 64 + background "gfx/mp/NEW_f_icon_saber_defend" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name sabdef_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 336 95 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor sabdef_iconpic forecolor 1 1 1 1 + setitemcolor sabdef_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "sabdef" + + setitembackground force_icon "gfx/mp/NEW_f_icon_saber_defend" + setitemtext force_desc @SP_INGAME_FORCE_SABER_DEFENSE_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor sabdef_iconpic forecolor .65 .65 .65 1 + setitemcolor sabdef_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// SABER OFFENSIVE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name saboff_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 380 149 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name saboff_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 336 141 64 64 + background "gfx/mp/NEW_f_icon_saber_attack" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name saboff_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 336 149 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor saboff_iconpic forecolor 1 1 1 1 + setitemcolor saboff_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "saboff" + + setitembackground force_icon "gfx/mp/NEW_f_icon_saber_attack" + setitemtext force_desc @SP_INGAME_FORCE_SABER_OFFENSE_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor saboff_iconpic forecolor .65 .65 .65 1 + setitemcolor saboff_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// SABER THROW BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name sabthrow_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 380 205 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name sabthrow_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 336 196 64 64 + background "gfx/mp/NEW_f_icon_saber_throw" + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name sabthrow_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 336 205 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor sabthrow_iconpic forecolor 1 1 1 1 + setitemcolor sabthrow_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "sabthrow" + + setitembackground force_icon "gfx/mp/NEW_f_icon_saber_throw" + setitemtext force_desc @SP_INGAME_FORCE_SABER_THROW_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor sabthrow_iconpic forecolor .5 .5 .5 1 + setitemcolor sabthrow_hexpic forecolor .5 .5 .5 1 + } + } + +//---------------------------------------------------------------------------------------------- +// DRAIN BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name drain_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 527 41 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name drain_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 484 30 64 64 + background "gfx/mp/NEW_f_icon_dk_drain" + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name drain_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 484 41 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor drain_iconpic forecolor 1 1 1 1 + setitemcolor drain_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "drain" + + setitembackground force_icon "gfx/mp/NEW_f_icon_dk_drain" + setitemtext force_desc @SP_INGAME_FORCE_DRAIN_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor drain_iconpic forecolor .5 .5 .5 1 + setitemcolor drain_hexpic forecolor .5 .5 .5 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "drain" + + hide force_button + + setitemrect deallocate_fbutton 484 41 115 46 + show deallocate_fbutton + + setitemcolor drain_iconpic forecolor 1 1 1 1 + setitemcolor drain_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// GRIP BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name grip_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 527 95 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name grip_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 484 85 64 64 + background "gfx/mp/NEW_f_icon_dk_grip" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name grip_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 484 95 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor grip_iconpic forecolor 1 1 1 1 + setitemcolor grip_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "grip" + + setitembackground force_icon "gfx/mp/NEW_f_icon_dk_grip" + setitemtext force_desc @SP_INGAME_FORCE_GRIP_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor grip_iconpic forecolor .65 .65 .65 1 + setitemcolor grip_hexpic forecolor .65 .65 .65 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "grip" + + hide force_button + + setitemrect deallocate_fbutton 484 95 115 46 + show deallocate_fbutton + + setitemcolor grip_iconpic forecolor 1 1 1 1 + setitemcolor grip_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// LIGHTNING BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name lightning_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 527 149 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name lightning_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 484 141 64 64 + background "gfx/mp/NEW_f_icon_dk_l1" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name lightning_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 484 149 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor lightning_iconpic forecolor 1 1 1 1 + setitemcolor lightning_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "lightning" + + setitembackground force_icon "gfx/mp/NEW_f_icon_dk_l1" + setitemtext force_desc @SP_INGAME_FORCE_LIGHTNING_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor lightning_iconpic forecolor .65 .65 .65 1 + setitemcolor lightning_hexpic forecolor .65 .65 .65 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "lightning" + + hide force_button + + setitemrect deallocate_fbutton 484 149 115 46 + show deallocate_fbutton + + setitemcolor lightning_iconpic forecolor 1 1 1 1 + setitemcolor lightning_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// RAGE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name rage_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 527 205 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name rage_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 484 196 64 64 + background "gfx/mp/NEW_f_icon_dk_rage" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name rage_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 484 205 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor rage_iconpic forecolor 1 1 1 1 + setitemcolor rage_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "rage" + + setitembackground force_icon "gfx/mp/NEW_f_icon_dk_rage" + setitemtext force_desc @SP_INGAME_FORCE_RAGE_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor rage_iconpic forecolor .65 .65 .65 1 + setitemcolor rage_hexpic forecolor .65 .65 .65 1 + } + + // Increment/decrement force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "rage" + + hide force_button + + setitemrect deallocate_fbutton 484 205 115 46 + show deallocate_fbutton + + setitemcolor rage_iconpic forecolor 1 1 1 1 + setitemcolor rage_hexpic forecolor 1 1 1 1 + + } + } +//---------------------------------------------------------------------------------------------- +// BIG FORCE ICON AND FORCE DESCRIPTION +//---------------------------------------------------------------------------------------------- + + itemDef + { + name force_icon + group bigicons + style WINDOW_STYLE_SHADER + rect 30 265 128 128 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name force_desc + rect 157 264 460 100 + font 4 + type ITEM_TYPE_TEXTSCROLL + text "This is the text" + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + lineHeight 14 + decoration + } + +//---------------------------------------------------------------------------------------------- +// ABSORB LEVEL DESCRIPTION +//---------------------------------------------------------------------------------------------- + // Level 1 description + itemDef + { + name absorb_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_ABSORB_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + // Level 2 description + itemDef + { + name absorb_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_ABSORB_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + // Level 3 description + itemDef + { + name absorb_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_ABSORB_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// HEAL DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name heal_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_HEAL_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name heal_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_HEAL_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name heal_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_HEAL_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// MIND TRICK DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name mindtrick_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_MIND_TRICK_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name mindtrick_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_MIND_TRICK_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name mindtrick_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_MIND_TRICK_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// PROTECT DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name protect_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PROTECT_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name protect_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PROTECT_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name protect_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PROTECT_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// +// JUMP DESCRIPTION +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name jump_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_JUMP_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name jump_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_JUMP_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name jump_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_JUMP_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// PULL DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name pull_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PULL_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name pull_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PULL_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name pull_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PULL_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// PUSH DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name push_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PUSH_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name push_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PUSH_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name push_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PUSH_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// SENSE DESCRIPTION +//---------------------------------------------------------------------------------------------- + + itemDef + { + name big_sense + group bigicons + style WINDOW_STYLE_SHADER + rect 30 265 128 128 + background "gfx/mp/NEW_f_icon_sight" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name sense_text + group mainpowertext + rect 157 264 460 120 + text @SP_INGAME_FORCE_SENSE_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name sense_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SENSE_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sense_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SENSE_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sense_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SENSE_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// SPEED DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name speed_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SPEED_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name speed_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SPEED_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name speed_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SPEED_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// SABER DEFENSE DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name sabdef_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_DEFENSE_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sabdef_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_DEFENSE_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sabdef_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_DEFENSE_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// SABER OFFENSE DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name saboff_text + group mainpowertext + rect 157 264 460 120 + text @SP_INGAME_FORCE_SABER_OFFENSE_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name saboff_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_OFFENSE_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name saboff_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_OFFENSE_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name saboff_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_OFFENSE_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// SABER THROW DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name sabthrow_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_THROW_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sabthrow_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_THROW_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sabthrow_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_THROW_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// DRAIN DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name drain_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_DRAIN_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name drain_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_DRAIN_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name drain_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_DRAIN_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// GRIP DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name grip_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_GRIP_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name grip_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_GRIP_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name grip_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_GRIP_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// LIGHTNING DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name lightning_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_LIGHTNING_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name lightning_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_LIGHTNING_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name lightning_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_LIGHTNING_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// RAGE DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name rage_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_RAGE_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name rage_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_RAGE_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name rage_level3desc + group leveltext + rect 157 384 460 40 + text @SP_INGAME_FORCE_RAGE_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + +//---------------------------------------------------------------------------------------------- +// BOTTOM ROW OF BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name backbut + type ITEM_TYPE_BUTTON + rect 30 447 140 24 + text @MENUS_HELP + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_HELP_DESC1 + visible 1 + mouseEnter + { + } + mouseExit + { + } + + // If we're backing up then we have to reset levels to what they were + action + { + play "sound/interface/menuroam.wav" + open ingameForceHelp + } + } + + + // do not change the name of this because it's made active/inactive in code + itemDef + { + name weaponbutton + type ITEM_TYPE_BUTTON + rect 336 447 140 24 + text @MENUS_WEAPONS + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_CHOOSEWEAPONS + visible 1 + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + close all + open demo_WpnSelect + } + } + +//---------------------------------------------------------------------------------------------- +// +// SCANLINES +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + diff --git a/base/ui/demo_GotoTier.menu b/base/ui/demo_GotoTier.menu new file mode 100644 index 0000000..3861b26 --- /dev/null +++ b/base/ui/demo_GotoTier.menu @@ -0,0 +1,190 @@ +//---------------------------------------------------------------------------------------------- +// Mission Select Menu +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "demo_GotoTier" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 20 // how often fade happens in milliseconds + fadeAmount 0.1 // amount to adjust alpha per cycle + onOpen + { + fadein background + playVoice "sound/chars/storyinfo/1.mp3" + } + onESC + { + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/mission_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name eyecandy1 + group none + style WINDOW_STYLE_SHADER + rect 17 309 357 153 + background "gfx/menus/mission_bottomleft_grid" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name botlf + group none + style WINDOW_STYLE_SHADER + rect 0 300 80 180 + background "gfx/menus/mission_bottom_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MODELS FOR V-O +//---------------------------------------------------------------------------------------------- + itemDef + { + name luke + group storyheads + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// BUTTONS +// +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//Sound is automatic +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//coming from Yavin2 +//---------------------------------------------------------------------------------------------- + itemDef + { + name story1 + type ITEM_TYPE_BUTTON + rect 550 440 172 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open demo_MissionSelect + } + } + + itemDef + { + name story1text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER1_1 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 1 + autowrapped + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 48 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// SCANLINES OVER WHOLE MENU +//---------------------------------------------------------------------------------------------- + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + } +} + + + + + + + + + + + diff --git a/base/ui/demo_MissionSelect.menu b/base/ui/demo_MissionSelect.menu new file mode 100644 index 0000000..4872c96 --- /dev/null +++ b/base/ui/demo_MissionSelect.menu @@ -0,0 +1,955 @@ +//------------------------------------------------------------------------------------------------ +// Mission Select 1 Menu +// +// Select missions from the 1st tier. +// +//------------------------------------------------------------------------------------------------ +{ + menuDef + { + name "demo_MissionSelect" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 200 + descY 444 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 25 // how often fade happens in milliseconds + fadeAmount 0.05 // amount to adjust alpha per cycle + onOpen + { + show mission_button + stopVoice + } + onESC + { + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/mission_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name eyecandy1 + group none + style WINDOW_STYLE_SHADER + rect 17 309 357 153 + background "gfx/menus/mission_bottomleft_grid" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name botlf + group none + style WINDOW_STYLE_SHADER + rect 0 300 80 180 + background "gfx/menus/mission_bottom_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starfield + group none + style WINDOW_STYLE_FILLED + rect 20 20 600 256 + background "gfx/menus/star_field" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name stars_close + group none + style WINDOW_STYLE_FILLED + rect 20 20 600 256 + background "gfx/menus/star_field_zoom" + backcolor 1 1 1 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// BIG PLANET GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name tatooine + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/tatooine" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name chandrila + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/chandrila" + backcolor 1 1 1 1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// GALAXY MAP +//---------------------------------------------------------------------------------------------- + itemDef + { + name galaxy + group none + style WINDOW_STYLE_FILLED + rect 64 20 512 256 + background "gfx/menus/mission_galaxy" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name map_title + type ITEM_TYPE_TEXT + rect 40 14 600 18 + text @MENUS_GALACTIC_MAP + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 300 + textaligny -1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TIER 1 LOCATION MARKERS ON GALAXY MAP +//---------------------------------------------------------------------------------------------- + itemDef + { + name loc_but01_off + group location_marker_off + style WINDOW_STYLE_SHADER + rect 239 188 32 32 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_sour" + } + cvarSubString + } + + itemDef + { + name loc_but01_on + group location_marker_on + style WINDOW_STYLE_SHADER + rect 239 188 32 32 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_sour" + } + cvarSubString + } + + itemDef + { + name loc_but02_off + group location_marker + style WINDOW_STYLE_SHADER + rect 270 109 20 20 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_rift" + } + cvarSubString + } + + itemDef + { + name loc_but02_on + group location_marker + style WINDOW_STYLE_SHADER + rect 270 109 20 20 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_rift" + } + cvarSubString + } + + itemDef + { + name planet_name + type ITEM_TYPE_TEXT + rect 52 316 288 24 + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + autowrapped + decoration + } + +//---------------------------------------------------------------------------------------------- +// TIER 1 MISSION BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name map_title + type ITEM_TYPE_TEXT + rect 10 305 400 20 + text @MENUS_MISSIONS + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny -1 + visible 1 + } + + itemDef + { + name miss1 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 330 224 20 + text @MENUS_T1_SOUR_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t1_sour" + } + cvarSubString + + mouseEnter + { + hide loc_but01_off + show loc_but01_on + + show planet_name + setitemtext planet_name @MENUS_TATOOINE + setitemrect planet_name 150 230 120 26 + + show button_glow + setitemrect button_glow 20 328 380 22 + + } + + mouseExit + { + hide loc_but01_on + show loc_but01_off + + hide planet_name + hide button_glow + } + + action + { + play "sound/weapons/force/protect.wav" + + fadeout starfield + fadeout galaxy + + fadein stars_close + + transition2 loc_but01_off 255 204 0 0 20 25 + transition2 loc_but02_off 270 109 0 0 20 25 + + transition2 planet_name 270 250 256 20 20 15 + + transition2 tatooine 192 20 256 256 10 15 + + hide mission_button + hide location_marker_on + + show backbut + show accbut1 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T1_SOUR + + hide button_glow + hide map_title + } + } + + itemDef + { + name FAKE_miss0 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 353 224 20 + text @MENUS_T1_FATAL_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + enablecvar + { + "fake" + } + + + mouseEnter + { + hide loc_but02_off + show loc_but02_on + + show planet_name + setitemtext planet_name @MENUS_BAKURA + setitemrect planet_name 150 230 120 26 + + show button_glow + setitemrect button_glow 20 351 380 22 + } + + mouseExit + { + hide loc_but02_on + show loc_but02_off + + hide planet_name + hide button_glow + } + } + + itemDef + { + name NA_button1 + group na_button1 + type ITEM_TYPE_BUTTON + rect 52 353 224 20 + desctext @OBJECTIVES_DEMO_NOT_AVAILABLE + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + + mouseEnter + { + + } + + mouseExit + { + + } + } + + + itemDef + { + name FAKE_miss1 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 376 224 20 + text @MENUS_T1_RAIL_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + enablecvar + { + "fake" + } + + + mouseEnter + { + hide loc_but02_off + show loc_but02_on + + show planet_name + setitemtext planet_name @MENUS_CORELLIA + setitemrect planet_name 150 230 120 26 + + show button_glow + setitemrect button_glow 20 351 380 22 + } + + mouseExit + { + hide loc_but02_on + show loc_but02_off + + hide planet_name + hide button_glow + } + } + + itemDef + { + name NA_button2 + group na_button2 + type ITEM_TYPE_BUTTON + rect 52 376 224 20 + desctext @OBJECTIVES_DEMO_NOT_AVAILABLE + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + + mouseEnter + { + + } + + mouseExit + { + + } + } + + itemDef + { + name miss2 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 399 224 20 + text @MENUS_T3_RIFT_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t3_rift" + } + cvarSubString + mouseEnter + { + hide loc_but02_off + show loc_but02_on + + show planet_name + setitemtext planet_name @MENUS_CHANDRILA + setitemrect planet_name 150 90 120 26 + + show button_glow + setitemrect button_glow 20 397 380 22 + } + mouseExit + { + hide loc_but02_on + show loc_but02_off + + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + + fadeout starfield + fadeout galaxy + + fadein stars_close + + transition2 loc_but01_off 280 119 0 0 20 25 + transition2 loc_but02_off 395 137 0 0 20 25 + transition2 loc_but03_off 181 163 0 0 20 25 + transition2 loc_but04_off 305 114 0 0 20 25 + transition2 loc_but05_off 389 79 0 0 20 25 + + hide loc_but01_on + hide loc_but02_on + + transition2 chandrila 192 20 256 256 10 15 + + transition2 planet_name 270 250 256 256 20 15 + + hide mission_button + + show backbut + show accbut2 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T3_RIFT + + hide map_title + hide button_glow + + } + } + + itemDef + { + name miss4 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 422 224 20 + text @MENUS_T3_BYSS_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + enablecvar + { + "fake" + } + mouseEnter + { + hide loc_but04_off + show loc_but04_on + + show planet_name + setitemtext planet_name @MENUS_BYSS + setitemrect planet_name 240 40 120 26 + + show button_glow + setitemrect button_glow 20 397 380 22 + } + mouseExit + { + hide loc_but04_on + show loc_but04_off + hide planet_name + hide button_glow + } + } + + itemDef + { + name NA_button1 + group na_button1 + type ITEM_TYPE_BUTTON + rect 52 422 224 20 + desctext @OBJECTIVES_DEMO_NOT_AVAILABLE + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + + mouseEnter + { + + } + + mouseExit + { + + } + } + + +//---------------------------------------------------------------------------------------------- +// MISSION BRIEF TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name briefing_background + group none + style WINDOW_STYLE_FILLED + rect 42 314 320 114 + backcolor 0 0 .35 .7 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 0 + decoration + } + + itemDef + { + name briefing_text + type ITEM_TYPE_TEXTSCROLL + rect 42 314 320 114 + text @BRIEFINGS_T1_SOUR + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +// GENERAL BACK BUTTON - if you don't want this mission +//---------------------------------------------------------------------------------------------- + itemDef + { + name backbut + type ITEM_TYPE_BUTTON + rect 52 430 172 24 + text @MENUS_BACK_CAPS + desctext @MENUS_DIFF_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 15 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/esc.wav" + fadein starfield + fadein galaxy + fadeout stars_close + + transition2 loc_but01_off 239 188 32 32 20 25 + transition2 loc_but02_off 270 109 32 32 20 25 + + hide backbut + + hide planet_name + + hide accbuttons + + hide briefing_text + hide tatooine + hide chandrila + hide briefing_background + + hide button_glow + show mission_button + show map_title + } + } + +//---------------------------------------------------------------------------------------------- +// ACCEPT BUTTONS - IF YOU CHOOSE A PARTICULAR MISSION +//---------------------------------------------------------------------------------------------- + itemDef + { + name accbut1 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + + action + { + hide backbut + hide briefing_text + show kyle + show nextscreen_button + show accept1text + playVoice "sound/chars/kyle/07kyk001.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t1_sour" + setcvar ui_demo_level "t1_sour" + hide button_glow + hide briefing_background + } + } + + itemDef + { + name accbut2 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide backbut + hide briefing_text + show kyle + show nextscreen_button + show accept2text + playVoice "sound/chars/kyle/demo_rift_intro.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t3_rift" + setcvar ui_demo_level "t3_rift" + hide button_glow + hide briefing_background + } + } + + +//---------------------------------------------------------------------------------------------- +// MODELS FOR V-O +//---------------------------------------------------------------------------------------------- + itemDef + { + name luke + group models + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name kyle + group models + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// BRIEFING SUBTITLES +//---------------------------------------------------------------------------------------------- + itemDef + { + name accept1text + type ITEM_TYPE_TEXTSCROLL + rect 30 316 320 130 + text @T1_SOUR_07KYK001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + + itemDef + { + name accept2text + type ITEM_TYPE_TEXT + rect 30 316 320 130 + text @OBJECTIVES_DEMO_RIFT_INTRO + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + autowrapped + } + + +//---------------------------------------------------------------------------------------------- +// NEXT BUTTONS - to go to next screen +//---------------------------------------------------------------------------------------------- + itemDef + { + name nextscreen_button + type ITEM_TYPE_BUTTON + rect 530 440 172 24 + text @MENUS_NEXT + desctext @MENUS_ADVANCE_NEXT + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + close all + open demo_ForceSelect + } + } + +//---------------------------------------------------------------------------------------------- +// SCANLINES OVER WHOLE MENU +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + + diff --git a/base/ui/demo_WpnSelect.menu b/base/ui/demo_WpnSelect.menu new file mode 100644 index 0000000..566f28d --- /dev/null +++ b/base/ui/demo_WpnSelect.menu @@ -0,0 +1,1471 @@ +//---------------------------------------------------------------------------------------------- +// +// Weapon Select Menu +// +// The player is allowed to choose from a list of weapons. The weapons in the list are +// based on the value in tier_storyinfo +// +// Weapon index values +// SABER - 1 +// BLASTER PISTOL - 2 +// BLASTER - 3 +// DISRUPTOR - 4 +// BOWCASTER - 5 +// REPEATER - 6 +// DEMP - 7 +// FLECHETTE - 8 +// ROCKET LAUNCHER - 9 +// THERMAL DET. - 10 +// TRIP MINE - 11 +// DET PACK - 12 +// CONCUSSION - 13 +// +// Ammo index values amounts to give +// SABER - FORCE - 1 - 100 +// BLASTER PISTOL - BLASTER - 2 - 300 +// BLASTER - BLASTER - 2 - 300 +// DISRUPTOR - POWER CELL - 3 - 300 +// BOWCASTER - POWER CELL - 3 - 300 +// REPEATER - METAL BOLTS - 4 - 400 +// DEMP - POWER CELL - 3 - 300 +// FLECHETTE - METAL BOLTS - 4 - 400 +// ROCKET LAUNCHER - ROCKETS - 5 - 10 +// THERMAL DET. - THERMAL - 7 - 10 +// TRIP MINE - TRIP MINE - 8 - 5 +// DET. PACK - DETPACK - 9 - 5 +// CONCUSSION - METAL BOLTS - 4 - 400 +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "demo_WpnSelect" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 25 // how often fade happens in milliseconds + fadeAmount 0.05 // amount to adjust alpha per cycle + + onOpen + { + uiScript "initweaponselect" // Perform screen init + uiScript "clearweapons" // Get rid of any weapons + uiScript "clearinventory" // Get rid of any inventory + uiScript "giveweapon" "1" // Give saber + uiScript "giveweapon" "2" // Give blaster + uiScript "equipweapon" "1" // start with saber + + } + + + onESC + { + play "sound/interface/menuroam.wav" + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/weaponmenu_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TEXT - Choose weapons/Weapons chosen. +//---------------------------------------------------------------------------------------------- + itemDef + { + name choose_weapons_text + type ITEM_TYPE_TEXT + rect 0 15 640 18 + text @MENUS_CHOOSEWEAPONS + font 2 + forecolor 1 1 1 1 + textscale .80 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 1 + decoration + } + + itemDef + { + name weapons_chosen_text + type ITEM_TYPE_TEXT + rect 0 20 640 18 + text @MENUS_WEAPONSCHOSEN + font 2 + forecolor .308 .980 .277 1 + textscale .80 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// INSTRUCTIONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name setup_background + group inst_stuff + style WINDOW_STYLE_FILLED + rect 40 195 455 145 + backcolor 0 0 .35 .9 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 0 + decoration + } + + itemDef + { + name instruction_title + group inst_stuff + type ITEM_TYPE_TEXT + rect 50 195 470 18 + text @MENUS_INSTRUCTIONS + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_CENTER + textalignx 230 + visible 0 + decoration + } + + itemDef + { + name instruction_text + group inst_stuff + type ITEM_TYPE_TEXTSCROLL + rect 45 220 475 130 + text @MENUS_WEAPON_SELECTION_HELP + font 4 + forecolor 1 1 1 1 + textscale 1.0 + textalign ITEM_ALIGN_LEFT + visible 0 + decoration autowrapped + textaligny 0 + lineHeight 13 + + } + + itemDef + { + name inst_done_button + group inst_stuff + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 495 255 130 64 + text @MENUS_DONE_CAPS + descText @MENUS_HELP_DESC + font 3 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor .79 .64 .22 1 + visible 0 + + mouseEnter + { + show button_glow + setitemrect button_glow 490 255 140 25 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + hide button_glow + hide inst_stuff + + show weapon_button + } + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// SABER BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name saber_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 28 40 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 1 1 + visible 1 + decoration + } + + itemDef + { + name saber_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 24 30 80 80 + background "gfx/hud/w_icon_lightsaber_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name saber_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 24 30 80 80 + background "gfx/hud/w_icon_lightsaber" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name saber_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 28 40 79 64 + forecolor 1 1 1 1 + visible 1 + mouseEnter + { + hide saber_icon + show saber_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_lightsaber_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_SABER_DESC + } + mouseExit + { + show saber_icon + hide saber_icon_lit + } + action + { + } + } + +//---------------------------------------------------------------------------------------------- +// BLASTER PISTOL BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name bpistol_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 28 109 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 1 1 + visible 1 + decoration + } + + itemDef + { + name bpistol_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 24 96 80 80 + background "gfx/hud/w_icon_blaster_pistol_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name bpistol_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 24 96 80 80 + background "gfx/hud/w_icon_blaster_pistol" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name bpistol_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 28 109 79 64 + forecolor 1 1 1 1 +// desctext @MENU_CLICKDESCRIPTION + visible 1 + mouseEnter + { + hide bpistol_icon + show bpistol_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_blaster_pistol_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_NEW_BLASTER_PISTOL_DESC + } + mouseExit + { + show bpistol_icon + hide bpistol_icon_lit + } + action + { + } + } + +//---------------------------------------------------------------------------------------------- +// BLASTER RIFLE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name brifle_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 136 40 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + } + + itemDef + { + name brifle_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 134 31 80 80 + background "gfx/hud/w_icon_blaster_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name brifle_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 134 31 80 80 + background "gfx/hud/w_icon_blaster" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name brifle_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 137 40 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide brifle_icon + show brifle_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_blaster_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_BLASTER_RIFLE_DESC + } + mouseExit + { + show brifle_icon + hide brifle_icon_lit + } + + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "3" "2" "300" "brifle_icon" "brifle_icon_lit" "brifle_hex_background" "sound/weapons/blaster/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// DISRUPTOR RIFLE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name disruptor_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 136 109 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + } + + itemDef + { + name disruptor_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 134 99 80 80 + background "gfx/hud/w_icon_disruptor_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name disruptor_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 134 99 80 80 + background "gfx/hud/w_icon_disruptor" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name disruptor_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 137 109 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide disruptor_icon + show disruptor_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_disruptor_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_DISRUPTOR_RIFLE_DESC + } + mouseExit + { + show disruptor_icon + hide disruptor_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "4" "3" "300" "disruptor_icon" "disruptor_icon_lit" "disruptor_hex_background" "sound/weapons/disruptor/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// BOWCASTER BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name bowcaster_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 40 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + } + + itemDef + { + name bowcaster_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 31 80 80 + background "gfx/hud/w_icon_bowcaster_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name bowcaster_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 31 80 80 + background "gfx/hud/w_icon_bowcaster" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name bowcaster_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 224 40 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide bowcaster_icon + show bowcaster_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_bowcaster_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_BOWCASTER_DESC + } + mouseExit + { + show bowcaster_icon + hide bowcaster_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "5" "3" "300" "bowcaster_icon" "bowcaster_icon_lit" "bowcaster_hex_background" "sound/weapons/bowcaster/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// DEMP BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name demp_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 109 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + } + + itemDef + { + name demp_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 99 80 80 + background "gfx/hud/w_icon_demp2_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name demp_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 99 80 80 + background "gfx/hud/w_icon_demp2" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name demp_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 225 109 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide demp_icon + show demp_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_demp2_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_DEMP2_DESC + } + mouseExit + { + show demp_icon + hide demp_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "7" "3" "300" "demp_icon" "demp_icon_lit" "demp_hex_background" "sound/weapons/demp2/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// REPEATER BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name repeater_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 311 40 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + cvartest "ui_demo_level" + showcvar + { + "t3_rift"; + } + } + + itemDef + { + name repeater_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 309 35 80 80 + background "gfx/hud/w_icon_repeater_na" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "ui_demo_level" + showcvar + { + "t3_rift"; + } + } + + itemDef + { + name repeater_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 309 35 80 80 + background "gfx/hud/w_icon_repeater" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name repeater_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 312 40 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + cvartest "ui_demo_level" + showcvar + { + "t3_rift"; + } + mouseEnter + { + hide repeater_icon + show repeater_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_repeater_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_HEAVYREPEATER_DESC + } + mouseExit + { + show repeater_icon + hide repeater_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "6" "4" "400" "repeater_icon" "repeater_icon_lit" "repeater_hex_background" "sound/weapons/repeater/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// FLECHETTE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name flechette_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 311 109 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + cvartest "ui_demo_level" + showcvar + { + "t3_rift"; + } + } + + itemDef + { + name flechette_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 309 100 80 80 + background "gfx/hud/w_icon_flechette_na" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "ui_demo_level" + showcvar + { + "t3_rift"; + } + } + + itemDef + { + name flechette_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 309 100 80 80 + background "gfx/hud/w_icon_flechette" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name flechette_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 312 109 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + cvartest "ui_demo_level" + showcvar + { + "t3_rift"; + } + mouseEnter + { + hide flechette_icon + show flechette_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_flechette_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_FLECHETTE_DESC + } + mouseExit + { + show flechette_icon + hide flechette_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "8" "4" "400" "flechette_icon" "flechette_icon_lit" "flechette_hex_background" "sound/weapons/flechette/select.wav" + } + } + + + +//---------------------------------------------------------------------------------------------- +// THERMAL DETONATOR BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name thermal_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 506 40 60 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 0 .5 1 + visible 1 + decoration + } + + itemDef + { + name thermal_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 494 32 80 80 + background "gfx/hud/w_icon_thermal_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name thermal_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 494 32 80 80 + background "gfx/hud/w_icon_thermal" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name thermal_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 507 40 52 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide thermal_icon + show thermal_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_thermal_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_THERMAL_DETONATOR_DESC + } + mouseExit + { + show thermal_icon + hide thermal_icon_lit + } + action + { // Add/remove this weapon from player selection (weapon #, menu item with graphic) + uiScript "addthrowweaponselection" "10" "7" "10" "thermal_icon" "thermal_icon_lit" "thermal_hex_background" "sound/weapons/thermal/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// DETPACK BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name detpack_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 564 40 60 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 0 .5 1 + visible 1 + decoration + } + + itemDef + { + name detpack_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 553 36 80 80 + background "gfx/hud/w_icon_detpack_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name detpack_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 553 36 80 80 + background "gfx/hud/w_icon_detpack" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name detpack_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 565 40 52 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide detpack_icon + show detpack_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_detpack_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_DET_PACK_DESC + } + mouseExit + { + show detpack_icon + hide detpack_icon_lit + } + action + { // Add/remove this weapon from player selection (weapon #, menu item with graphic) + uiScript "addthrowweaponselection" "12" "9" "5" "detpack_icon" "detpack_icon_lit" "detpack_hex_background" "sound/weapons/detpack/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// TRIP MINE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name tripmine_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 538 109 60 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 0 .5 1 + visible 1 + decoration + } + + itemDef + { + name tripmine_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 526 100 80 80 + background "gfx/hud/w_icon_tripmine_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name tripmine_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 526 100 80 80 + background "gfx/hud/w_icon_tripmine" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tripmine_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 539 109 52 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide tripmine_icon + show tripmine_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_tripmine_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_TRIP_MINE_DESC + } + mouseExit + { + show tripmine_icon + hide tripmine_icon_lit + } + action + { // Add/remove this weapon from player selection (weapon #, menu item with graphic) + uiScript "addthrowweaponselection" "11" "8" "5" "tripmine_icon" "tripmine_icon_lit" "tripmine_hex_background" "sound/weapons/detpack/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// CHOSEN WEAPONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name chosensaber_hex_background + style WINDOW_STYLE_SHADER + rect 65 358 70 107 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 1 1 + visible 1 + decoration + } + + itemDef + { + name chosen_sabericon + group chosenicons + style WINDOW_STYLE_SHADER + rect 67 358 60 60 + background "gfx/hud/w_icon_lightsaber_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name chosenblaster_hex_background + style WINDOW_STYLE_SHADER + rect 152 358 70 107 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 1 1 + visible 1 + decoration + } + + itemDef + { + name chosen_blastericon + group chosenicons + style WINDOW_STYLE_SHADER + rect 154 358 60 60 + background "gfx/hud/w_icon_blaster_pistol_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + itemDef + { + name chosenweapon1_hex_background + style WINDOW_STYLE_SHADER + rect 280 358 70 107 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 0 1 + visible 1 + decoration + } + + itemDef + { + name chosenweapon1_icon + group chosenicons + style WINDOW_STYLE_SHADER + rect 282 358 60 60 + background "gfx/hud/w_icon_blaster_pistol_na" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name chosenweapon1_button + type ITEM_TYPE_BUTTON + rect 282 358 52 64 + forecolor 0 1 0 1 + visible 0 + desctext @MENUS_CLICKREMOVE + mouseEnter + { + uiScript highlightweaponselection "1" + } + mouseExit + { + uiScript normalweaponselection "1" + } + action + { + uiScript "removeweaponselection" "1" + } + } + + itemDef + { + name chosenweapon2_hex_background + style WINDOW_STYLE_SHADER + rect 367 358 70 107 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 0 1 + visible 1 + decoration + } + + itemDef + { + name chosenweapon2_icon + group chosenicons + style WINDOW_STYLE_SHADER + rect 369 358 60 60 + background "gfx/hud/w_icon_blaster_pistol_na" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name chosenweapon2_button + type ITEM_TYPE_BUTTON + rect 369 358 52 64 + forecolor 1 1 1 1 + visible 0 + desctext @MENUS_CLICKREMOVE + mouseEnter + { + uiScript highlightweaponselection "2" + } + mouseExit + { + uiScript normalweaponselection "2" + } + action + { + uiScript "removeweaponselection" "2" + } + } + + itemDef + { + name chosenthrowweapon_hex_background + style WINDOW_STYLE_SHADER + rect 500 358 70 107 + background "gfx/menus/hex_pattern_gray" + forecolor 0 0 1 1 + visible 1 + decoration + } + + itemDef + { + name chosenthrowweapon_icon + group chosenicons + style WINDOW_STYLE_SHADER + rect 500 358 60 60 + background "gfx/hud/w_icon_blaster_pistol_na" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name chosenthrowweapon_button + type ITEM_TYPE_BUTTON + rect 500 358 52 64 + forecolor 1 1 1 1 + visible 0 + desctext @MENUS_CLICKREMOVE + mouseEnter + { + uiScript highlightthrowselection + } + mouseExit + { + uiScript normalthrowselection + } + action + { + uiScript "removethrowweaponselection" + } + } + + + +//---------------------------------------------------------------------------------------------- +// BIG WEAPON ICON +//---------------------------------------------------------------------------------------------- + itemDef + { + name weapon_icon + group bigicons + style WINDOW_STYLE_SHADER + rect 20 200 128 128 + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// WEAPON DESCRIPTION TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name weapon_desc + group none + type ITEM_TYPE_TEXTSCROLL + rect 130 187 490 175 + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + lineHeight 16 + visible 0 + autowrapped + decoration + } + +//---------------------------------------------------------------------------------------------- +// BOTTOM ROW OF BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name backbut + type ITEM_TYPE_BUTTON + rect 30 447 140 24 + text @MENUS_HELP + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_HELP_DESC1 + visible 1 + mouseEnter + { + } + mouseExit + { + } + // If we're backing up then we have to reset levels to what they were + action + { + play "sound/interface/menuroam.wav" + + open ingameWpnSelectHelp + } + } + + + // Don't change the name of this, code looks for this name to turn it on and off + itemDef + { + name beginmission + type ITEM_TYPE_BUTTON + rect 400 447 220 24 + text @MENUS_BEGIN_MISSION + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_BEGIN_MISSION_DESC + visible 1 + cvartest "weapon_menu" + hidecvar + { + "1" ; "2" ; "3" + } + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + uiScript recordweapons + exec "vstr tier_mapname" + } + } + + // Don't change the name of this, code looks for this name to turn it on and off + // This is a big hack so that from Academy2 it will only bring up the weapon select menu + // and then go right to HOTH2. + itemDef + { + name beginmission + type ITEM_TYPE_BUTTON + rect 400 447 220 24 + text @MENUS_BEGIN_MISSION + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_BEGIN_MISSION_DESC + visible 1 + cvartest "weapon_menu" + showcvar + { + "1" + } + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + uiScript recordweapons + exec "maptransition hoth2" + } + } + + // Don't change the name of this, code looks for this name to turn it on and off + // This is a big hack so that from Academy4 it will only bring up the weapon select menu + // and then go right to VJUN1. + itemDef + { + name beginmission + type ITEM_TYPE_BUTTON + rect 400 447 220 24 + text @MENUS_BEGIN_MISSION + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_BEGIN_MISSION_DESC + visible 1 + cvartest "weapon_menu" + showcvar + { + "2" + } + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + uiScript recordweapons + exec "maptransition vjun1" + } + } + + // Don't change the name of this, code looks for this name to turn it on and off + // This is a big hack so that from Academy6 it will only bring up the weapon select menu + // and then go right to TASPIN1. + itemDef + { + name beginmission + type ITEM_TYPE_BUTTON + rect 400 447 220 24 + text @MENUS_BEGIN_MISSION + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_BEGIN_MISSION_DESC + visible 1 + cvartest "weapon_menu" + showcvar + { + "3" + } + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + uiScript recordweapons + exec "maptransition taspir1" + } + } + +//---------------------------------------------------------------------------------------------- +// +// SCANLINES +// +//---------------------------------------------------------------------------------------------- + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + diff --git a/base/ui/demo_ingame.txt b/base/ui/demo_ingame.txt new file mode 100644 index 0000000..74dd7d0 --- /dev/null +++ b/base/ui/demo_ingame.txt @@ -0,0 +1,34 @@ +// menu defs +// +{ + loadMenu { "ui/ingame.menu" } + loadMenu { "ui/ingamesave.menu" } + loadMenu { "ui/ingameload.menu" } + loadMenu { "ui/ingamecontrols.menu" } + loadMenu { "ui/ingamesetup.menu" } + loadMenu { "ui/ingamequit.menu" } + loadMenu { "ui/demo_MissionSelect.menu" } + loadMenu { "ui/demo_ingameMissionSelect.menu" } + loadMenu { "ui/ingameGotoTier.menu" } + loadMenu { "ui/demo_ForceSelect.menu" } + loadMenu { "ui/demo_WpnSelect.menu" } + loadMenu { "ui/ingameForceStatus.menu" } + loadMenu { "ui/ingameForceHelp.menu" } + loadMenu { "ui/ingameWpnSelectHelp.menu" } + + loadMenu { "ui/datapadforcepowers.menu" } + loadMenu { "ui/datapadmission.menu" } + loadMenu { "ui/datapadweapons.menu" } + loadMenu { "ui/datapadmoves.menu" } + + loadMenu { "ui/error.menu" } + loadMenu { "ui/ingamevid_warning.menu" } + loadMenu { "ui/videodriver.menu" } + loadMenu { "ui/saber.menu" } + loadMenu { "ui/saberstyle.menu" } + + loadMenu { "ui/missionfailed.menu" } + loadMenu { "ui/loadscreen.menu" } + + loadMenu { "ui/demo_sellscreen1.menu" } +} diff --git a/base/ui/demo_ingameMissionSelect.menu b/base/ui/demo_ingameMissionSelect.menu new file mode 100644 index 0000000..75e3fab --- /dev/null +++ b/base/ui/demo_ingameMissionSelect.menu @@ -0,0 +1,847 @@ +//---------------------------------------------------------------------------------------------- +// Mission Select Menu +// +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingameMissionSelect" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 20 // how often fade happens in milliseconds + fadeAmount 0.1 // amount to adjust alpha per cycle + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/mission_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name eyecandy1 + group none + style WINDOW_STYLE_SHADER + rect 17 309 357 153 + background "gfx/menus/mission_bottomleft_grid" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name botlf + group none + style WINDOW_STYLE_SHADER + rect 0 300 80 180 + background "gfx/menus/mission_bottom_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name complete + type ITEM_TYPE_TEXT + rect 170 16 300 32 + text @MENUS_MISSION_COMPLETE + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 150 + textaligny -1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Killed, Secrets +//---------------------------------------------------------------------------------------------- + itemDef + { + name killed + group stats + rect 0 52 320 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_ENEMIESKILLED + cvar "ui_stats_enemieskilled" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_CENTER + textalignx 160 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name secretsfound + group stats + rect 320 52 320 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_SECRETAREAS + cvar "ui_stats_secretsfound" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_CENTER + textalignx 160 + textaligny -1 + visible 1 + decoration + cvartest "ui_stats_secretsfound" + hidecvar + { + "0" + } + } + + // Lines above and below lightsaber info + itemDef + { + name border + group none + type ITEM_TYPE_TEXT + rect 70 80 500 2 + forecolor 1 1 1 1 + border 1 + bordercolor .403 .584 .741 1 + visible 1 + decoration + } + + itemDef + { + name border + group none + type ITEM_TYPE_TEXT + rect 70 165 500 2 + forecolor 1 1 1 1 + border 1 + bordercolor .403 .584 .741 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Saber use data +//---------------------------------------------------------------------------------------------- + itemDef + { + name saberuse + type ITEM_TYPE_TEXT + rect 0 87 640 32 + text @SP_INGAME_LIGHTSABERUSE + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 320 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name saber_thrown + rect 20 115 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_THROWN + cvar "ui_stats_thrown" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name saber_blocks + rect 20 140 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_BLOCKS + cvar "ui_stats_blocks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name leg_attacks + rect 220 115 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_LEGATTACKS + cvar "ui_stats_legattacks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name arm_attacks + rect 220 140 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_ARMATTACKS + cvar "ui_stats_armattacks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name body_attacks + rect 420 115 300 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_BODYATTACKS + cvar "ui_stats_bodyattacks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + + itemDef + { + name forceuse + type ITEM_TYPE_TEXT + rect 250 173 150 32 + text @SP_INGAME_FORCEUSE + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 75 + textaligny -1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// Lower 1st column of force stats (light side powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name absorb + rect -20 195 150 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_ABSORB + cvar "ui_stats_absorb" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name heal + rect -20 215 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_HEAL + cvar "ui_stats_heal" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name mindtrick + rect -20 235 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_MINDTRICK + cvar "ui_stats_mindtrick" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name protection + rect -20 255 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_PROTECTION + cvar "ui_stats_protect" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Lower 2nd column of force stats (neutral powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name jump + rect 130 195 160 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_JUMP + cvar "ui_stats_jump" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name pull + rect 130 215 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_PULL + cvar "ui_stats_pull" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name push + rect 130 235 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_PUSH + cvar "ui_stats_push" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name sense + rect 130 255 160 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_SENSE + cvar "ui_stats_sense" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Lower 3rd column of force stats (neutral powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name speed + rect 280 195 150 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_SPEED + cvar "ui_stats_speed" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name defense + rect 280 215 300 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_DEFENSE + cvar "ui_stats_defense" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name offense + rect 280 235 300 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_OFFENSE + cvar "ui_stats_offense" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name throw + rect 280 255 300 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_THROW + cvar "ui_stats_throw" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Lower 4th column of force stats (dark powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name grip + rect 430 195 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_DRAIN + cvar "ui_stats_drain" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name grip + rect 430 215 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_GRIP + cvar "ui_stats_grip" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name lightning + rect 430 235 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_LIGHTNING + cvar "ui_stats_lightning" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name rage + rect 430 255 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_RAGE + cvar "ui_stats_rage" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MODELS FOR V-O +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +// +// THESE HEADS APPEAR WHEN YOU SUCCESSFULLY COMPLETE A TIER LEVEL. +// +// THE SOUNDS ARE IN sounds/tiervictory AND ARE THE NAME OF THE LEVEL. +// IT IS HARDCODED TO LOOK FOR AND PLAY A SOUND MATCHING THE MAP NAME IN THIS DIR. +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name luke + group models + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + cvartest "storyhead" + showcvar + { + "luke" + "t1_surprise" + "t1_sour" + "t3_hevil" + "t1_test" + "t1_rail" + } + } + +//t1_rail has conversation; luke, kyle, luke, kyle, luke. + + itemDef + { + name kyle + group models + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + cvartest "storyhead" + showcvar + { + "t1_fatal" + "t1_danger" + "t2_rogue" + "t2_dpred" + "t2_rancor" + "t2_trip" + "t3_rift" + "t3_byss" + "t3_stamp" + "t3_bounty" + "kyle" + } + } + + +//---------------------------------------------------------------------------------------------- +// SUBTITLES FOR TIER VICTORY MESSAGES. +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +// 1ST TIER +//---------------------------------------------------------------------------------------------- + + itemDef + { + name vic_text_t1_sour + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T1_SOUR + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t1_sour" + } + } + + + +//---------------------------------------------------------------------------------------------- +// 3RD TIER +//---------------------------------------------------------------------------------------------- + itemDef + { + name vic_text_t2_rift + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T2_TRIP + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t3_rift" + } + } + + + +//---------------------------------------------------------------------------------------------- +// BUTTONS +// +// AFTER THE VICTORY MESSAGE ABOVE, YOU GET A STORY MESSAGE BEFORE CHOOSING NEXT MISSION. +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//TIER ONE - INITIATE TIER +//---------------------------------------------------------------------------------------------- + + +//---------------------------------------------------------------------------------------------- +//THIS IS THE DEMO!!!! +//SO THERE IS NO STORY INFO. +//---------------------------------------------------------------------------------------------- + itemDef + { + name victory2 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "0" "1" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory2 + hide victorytext + hide models + close all + open demo_MissionSelect + } + } + + + itemDef + { + name victory2 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "2" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory2 + hide victorytext + stopVoice + hide models + close all + open demo_sellscreen1 + + } + } + + itemDef + { + name kyle2 + group models2 + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + + +//---------------------------------------------------------------------------------------------- +// +// SCANLINES OVER WHOLE MENU +// +//---------------------------------------------------------------------------------------------- + + itemDef + { + name static + group none + style WINDOW_STYLE_SHADER + rect 396 314 175 120 + background "gfx/menus/static" + backcolor 1 0 0 .2 + forecolor 1 0 0 .2 + visible 0 + decoration + cvartest "storyhead" + showcvar + { + "t2_trip" + } + } + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} diff --git a/base/ui/demo_menus.txt b/base/ui/demo_menus.txt new file mode 100644 index 0000000..f7b3dad --- /dev/null +++ b/base/ui/demo_menus.txt @@ -0,0 +1,28 @@ +// menu defs +// +{ + loadMenu { "ui/main.menu" } + loadMenu { "ui/newgame.menu" } + loadMenu { "ui/newgame_first.menu" } + loadMenu { "ui/character.menu" } + loadMenu { "ui/demo_saber.menu" } + loadMenu { "ui/loadgame.menu" } + loadMenu { "ui/controls.menu" } + loadMenu { "ui/setup.menu" } + loadMenu { "ui/quit.menu" } + + loadMenu { "ui/error.menu" } + loadMenu { "ui/vid_warning.menu" } + loadMenu { "ui/videodriver.menu" } + loadMenu { "ui/demo_GotoTier.menu" } + loadMenu { "ui/demo_MissionSelect.menu" } + loadMenu { "ui/demo_ForceSelect.menu" } + loadMenu { "ui/ingameForcehelp.menu" } + loadMenu { "ui/demo_WpnSelect.menu" } + loadMenu { "ui/ingameWpnSelectHelp.menu" } + + loadMenu { "ui/demo_sellscreen1.menu" } + loadMenu { "ui/loadscreen.menu" } + + +} diff --git a/base/ui/demo_saber.menu b/base/ui/demo_saber.menu new file mode 100644 index 0000000..76fd90d --- /dev/null +++ b/base/ui/demo_saber.menu @@ -0,0 +1,1512 @@ +//---------------------------------------------------------------------------------------------- +// +// SABER CREATION MENU - called from main menu at the the start of a new game, +// and also when player is allowed to upgrade the fighting style and choose a new saber +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "saberMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + setcvar "playersave" "(NULL)" + setcvar "tiers_complete" "(NULL)" + uiScript "resetsabercvardefaults" + uiScript "getsabercvars" + uiScript "updatefightingstylechoices" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 + } + + onESC + { + play "sound/interface/esc.wav" + uiScript "updatesabercvars" + uiScript closesabermenu + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/sabermenu_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box1 + group none + style WINDOW_STYLE_SHADER + rect 4 66 219 165 + background "gfx/menus/sabermenu_box" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box2top + group none + style WINDOW_STYLE_SHADER + rect 212 66 219 60 + background "gfx/menus/sabermenu_box_top" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box2middle + group none + style WINDOW_STYLE_SHADER + rect 212 126 219 0 + background "gfx/menus/sabermenu_box_middle" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box2bottom + group none + style WINDOW_STYLE_SHADER + rect 212 126 219 60 + background "gfx/menus/sabermenu_box_bottom" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box3top + group none + style WINDOW_STYLE_SHADER + rect 418 66 219 60 + background "gfx/menus/sabermenu_box_top" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box3middle + group none + style WINDOW_STYLE_SHADER + rect 418 126 219 0 + background "gfx/menus/sabermenu_box_middle" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box3bottom + group none + style WINDOW_STYLE_SHADER + rect 418 126 219 60 + background "gfx/menus/sabermenu_box_bottom" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name styleboxleft + group none + style WINDOW_STYLE_SHADER + rect 227 183 198 40 + background "gfx/menus/sabermenu_stylebox_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name styleboxright + group none + style WINDOW_STYLE_SHADER + rect 425 183 198 40 + background "gfx/menus/sabermenu_stylebox_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + // Big button "NEW" + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 16 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + + cvarTest saber_menu + showCvar { "0" } + + mouseEnter + { + show button_glow + setitemrect button_glow 0 14 200 30 + } + mouseExit + { + hide button_glow + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 16 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 130 14 200 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "updatesabercvars" + close all + open loadgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 16 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 16 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 310 14 200 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "updatesabercvars" + close all + open controlsMenu + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 16 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 16 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 472 14 200 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "updatesabercvars" + close all + open setupMenu + } + } + + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 -60 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 -60 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// SABER MENU specific stuff +//---------------------------------------------------------------------------------------------- + itemDef + { + name title_glow + group none + style WINDOW_STYLE_SHADER + rect 100 48 440 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // CREATION title + itemDef + { + name creation_title + group title + style WINDOW_STYLE_EMPTY + text @MENUS_LIGHTSABER_CREATION + rect 100 50 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// SABER TYPE BUTTONS (standard, dual, two handed) +//---------------------------------------------------------------------------------------------- + itemDef + { + name typebut + group none + text @MENUS_SABER_TYPE + descText @MENUS_SABER_TYPE_DESC + style WINDOW_STYLE_EMPTY + rect 32 96 160 24 + font 3 + textscale 1 + textstyle 1 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name typebut_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + + itemDef + { + name typebut_single + group none + text @MENUS_SINGLESABER + descText @MENUS_SINGLESABER_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 32 132 160 16 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show typebut_glow + setitemrect typebut_glow 5 130 210 20 + } + mouseExit + { + hide typebut_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "single" + uiScript "saber_type" + setcvar ui_saber "single_1" + setcvar ui_saber2 "" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor 1 1 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 + transition2 box2middle 212 126 219 0 20 5 + transition2 box2bottom 212 126 219 60 20 5 + transition2 box3middle 418 126 219 0 20 5 + transition2 box3bottom 418 126 219 60 20 5 + transition2 styleboxleft 227 183 198 40 20 5 + transition2 styleboxright 425 183 198 40 20 5 + transition2 saber 12 -80 615 615 20 10 + uiScript "updatefightingstylechoices" + } + } + + itemDef + { + name typebut_dual + group none + text @MENUS_DUALSABERS + descText @MENUS_DUALSABERS_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 32 152 160 16 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show typebut_glow + setitemrect typebut_glow 5 150 210 20 + } + mouseExit + { + hide typebut_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "dual" + uiScript "saber_type" + setcvar ui_saber "single_1" + setcvar ui_saber2 "single_1" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor 1 1 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 + transition2 box2middle 212 126 219 44 20 5 + transition2 box2bottom 212 170 219 60 20 5 + transition2 box3middle 418 126 219 44 20 5 + transition2 box3bottom 418 170 219 60 20 5 + transition2 styleboxleft 425 183 0 40 20 5 + transition2 styleboxright 425 183 0 40 20 5 + transition2 saber 12 -130 615 615 20 5 + uiScript "updatefightingstylechoices" + } + } + + itemDef + { + name typebut_staff + group none + text @MENUS_SABERSTAFF + descText @MENUS_SABERSTAFF_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 32 172 160 16 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show typebut_glow + setitemrect typebut_glow 5 170 210 20 + } + mouseExit + { + hide typebut_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "staff" + uiScript "saber_type" + setcvar ui_saber "dual_1" + setcvar ui_saber2 "" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor 1 1 1 1 + transition2 box2middle 212 126 219 0 20 5 + transition2 box2bottom 212 126 219 60 20 5 + transition2 box3middle 418 126 219 0 20 5 + transition2 box3bottom 418 126 219 60 20 5 + transition2 styleboxleft 425 183 0 40 20 5 + transition2 styleboxright 425 183 0 40 20 5 + transition2 saber 12 -80 615 615 20 10 + uiScript "updatefightingstylechoices" + } + } + +//---------------------------------------------------------------------------------------------- +//HILTS +//---------------------------------------------------------------------------------------------- + itemDef + { + name hilttype + group none + text @MENUS_SABER_HILTS + descText @MENUS_SABER_HILTS_DESC + style WINDOW_STYLE_EMPTY + rect 240 96 160 24 + font 3 + textscale 1 + textstyle 1 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // HILT BUTTON 1 - SINGLE or DUAL + itemDef + { + name hiltbut_glow + group none + style WINDOW_STYLE_SHADER + rect 210 130 210 20 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name hiltbut + group none + text @MENUS_HILT1 + descText @MENUS_HILT1_DESC + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 132 160 16 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_saber_type + hideCvar { "staff" } + + cvar "ui_saber" + //FIXME: read these from sabers.cfg + *.sab? + cvarStrList + { + @MENUS_SINGLE_HILT1 "single_1" + @MENUS_SINGLE_HILT2 "single_2" + @MENUS_SINGLE_HILT3 "single_3" + @MENUS_SINGLE_HILT4 "single_4" + @MENUS_SINGLE_HILT5 "single_5" + @MENUS_SINGLE_HILT6 "single_6" + @MENUS_SINGLE_HILT7 "single_7" + @MENUS_SINGLE_HILT8 "single_8" + @MENUS_SINGLE_HILT9 "single_9" + } + + visible 1 + + mouseEnter + { + show hiltbut_glow + } + mouseExit + { + hide hiltbut_glow + } + action + { + play "sound/interface/choose_hilt.wav" + uiScript "saber_hilt" + } + } + + // HILT BUTTON 1 - STAVES + itemDef + { + name hiltbut_staves + group none + text @MENUS_HILT1 + descText @MENUS_HILT1_DESC + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 132 160 16 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_saber_type + hideCvar { "single"; "dual" } + + cvar "ui_saber" + //FIXME: read these from sabers.cfg + *.sab? + cvarStrList + { + @MENUS_STAFF_HILT1 "dual_1" + @MENUS_STAFF_HILT2 "dual_2" + @MENUS_STAFF_HILT3 "dual_3" + @MENUS_STAFF_HILT4 "dual_4" + @MENUS_STAFF_HILT5 "dual_5" + } + visible 1 + mouseEnter + { + show hiltbut_glow + } + mouseExit + { + hide hiltbut_glow + } + action + { + play "sound/interface/choose_hilt.wav" + uiScript "saber_hilt" + } + } + + // HILT BUTTON 2 - DUAL + itemDef + { + name hiltbut2_glow + group none + style WINDOW_STYLE_SHADER + rect 210 150 210 20 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + decoration + } + + itemDef + { + name hiltbut2 + group none + text @MENUS_HILT2 + descText @MENUS_HILT2_DESC + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 152 160 16 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + + cvar "ui_saber2" + //FIXME: read these from sabers.cfg + *.sab? + cvarStrList + { + @MENUS_SINGLE_HILT1 "single_1" + @MENUS_SINGLE_HILT2 "single_2" + @MENUS_SINGLE_HILT3 "single_3" + @MENUS_SINGLE_HILT4 "single_4" + @MENUS_SINGLE_HILT5 "single_5" + @MENUS_SINGLE_HILT6 "single_6" + @MENUS_SINGLE_HILT7 "single_7" + @MENUS_SINGLE_HILT8 "single_8" + @MENUS_SINGLE_HILT9 "single_9" + } + visible 1 + mouseEnter + { + show hiltbut2_glow + } + mouseExit + { + hide hiltbut2_glow + } + action + { + play "sound/interface/choose_hilt.wav" + uiScript "saber2_hilt" + } + } + +//---------------------------------------------------------------------------------------------- +//BLADE COLORS +//---------------------------------------------------------------------------------------------- + itemDef + { + name bladecolor_title + group none + text @MENUS_BLADE_COLOR + descText @MENUS_BLADE_COLOR_DESC + style WINDOW_STYLE_EMPTY + rect 446 96 160 24 + font 3 + textscale 1 + textstyle 1 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // COLOR 1 BUTTON + /*itemDef + { + name colorbut_glow + group none + style WINDOW_STYLE_SHADER + rect 446 136 160 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name colorbut + group none + text @MENUS_COLOR1 + descText @MENUS_COLOR1_DESC + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 446 136 160 16 + font 2 + textscale .8 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -4 + forecolor 1 1 1 1 + visible 1 + + cvar "ui_saber_color" + cvarStrList + { + "red" "red" + "orange" "orange" + "yellow" "yellow" + "green" "green" + "blue" "blue" + "purple" "purple" + } + + mouseEnter + { + show colorbut_glow + } + mouseExit + { + hide colorbut_glow + } + action + { + play "sound/interface/choose_blade.wav" + uiScript "saber_color" + } + }*/ + + itemDef + { + name blueicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 446 124 24 24 + background "gfx/menus/saber_icon_blue" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor blueicon forecolor 1 1 1 1 + setitemcolor blueicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor blueicon forecolor .75 .75 .75 1 + setitemcolor blueicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber_color" "blue" + } + } + + itemDef + { + name greenicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 480 124 24 24 + background "gfx/menus/saber_icon_green" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor greenicon forecolor 1 1 1 1 + setitemcolor greenicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor greenicon forecolor .75 .75 .75 1 + setitemcolor greenicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber_color" "green" + } + } + + itemDef + { + name orangeicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 514 124 24 24 + background "gfx/menus/saber_icon_orange" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor orangeicon forecolor 1 1 1 1 + setitemcolor orangeicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor orangeicon forecolor .75 .75 .75 1 + setitemcolor orangeicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber_color" "orange" + } + } + + itemDef + { + name purpleicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 548 124 24 24 + background "gfx/menus/saber_icon_purple" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor purpleicon forecolor 1 1 1 1 + setitemcolor purpleicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor purpleicon forecolor .75 .75 .75 1 + setitemcolor purpleicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber_color" "purple" + } + } + + itemDef + { + name yellowicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 582 124 24 24 + background "gfx/menus/saber_icon_yellow" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor yellowicon forecolor 1 1 1 1 + setitemcolor yellowicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor yellowicon forecolor .75 .75 .75 1 + setitemcolor yellowicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber_color" "yellow" + } + } + +// COLOR 2 BUTTON + itemDef + { + name colorbut2 + group none + text @MENUS_COLOR2 + descText @MENUS_COLOR2_DESC + //type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 446 152 160 16 + font 2 + textscale .8 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -4 + forecolor .79 .64 .22 1 + visible 1 + decoration + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + + /*cvar "ui_saber2_color" + cvarStrList + { + "red" "red" + "orange" "orange" + "yellow" "yellow" + "green" "green" + "blue" "blue" + "purple" "purple" + } + + mouseEnter + { + show colorbut2_glow + } + mouseExit + { + hide colorbut2_glow + } + action + { + play "sound/interface/choose_blade.wav" + uiScript "saber2_color" + }*/ + } + + itemDef + { + name blueicon2 + group sabericons2 + descText @MENUS_COLOR2_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 446 170 24 24 + background "gfx/menus/saber_icon_blue" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor blueicon2 forecolor 1 1 1 1 + setitemcolor blueicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor blueicon2 forecolor .75 .75 .75 1 + setitemcolor blueicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber2_color" "blue" + } + } + + itemDef + { + name greenicon2 + group sabericons2 + descText @MENUS_COLOR2_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 480 170 24 24 + background "gfx/menus/saber_icon_green" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor greenicon2 forecolor 1 1 1 1 + setitemcolor greenicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor greenicon2 forecolor .75 .75 .75 1 + setitemcolor greenicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber2_color" "green" + } + } + + itemDef + { + name orangeicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + descText @MENUS_COLOR2_DESC + type ITEM_TYPE_BUTTON + rect 514 170 24 24 + background "gfx/menus/saber_icon_orange" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor orangeicon2 forecolor 1 1 1 1 + setitemcolor orangeicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor orangeicon2 forecolor .75 .75 .75 1 + setitemcolor orangeicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber2_color" "orange" + } + } + + itemDef + { + name purpleicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + descText @MENUS_COLOR2_DESC + rect 548 170 24 24 + background "gfx/menus/saber_icon_purple" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor purpleicon2 forecolor 1 1 1 1 + setitemcolor purpleicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor purpleicon2 forecolor .75 .75 .75 1 + setitemcolor purpleicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber2_color" "purple" + } + } + + itemDef + { + name yellowicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 582 170 24 24 + descText @MENUS_COLOR2_DESC + background "gfx/menus/saber_icon_yellow" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor yellowicon2 forecolor 1 1 1 1 + setitemcolor yellowicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor yellowicon2 forecolor .75 .75 .75 1 + setitemcolor yellowicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber2_color" "yellow" + } + } + +//---------------------------------------------------------------------------------------------- +// SABER COMBAT STYLES +//---------------------------------------------------------------------------------------------- + itemDef + { + name fightingstylebutton0 + group stylebuttons + text @MENUS_SABERSTYLE + descText @MENUS_FIGHTINGSTYLE + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 190 380 24 + font 3 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + cvarTest ui_saber_type + showCvar { "single" } + + cvar "ui_newfightingstyle" + cvarStrList + { + @MENUS_COMBATSTYLEFAST "0" + @MENUS_COMBATSTYLEMEDIUM "1" + @MENUS_COMBATSTYLEHEAVY "2" + } + + visible 1 + + } + + itemDef + { + name fightingstyle_glow + group none + style WINDOW_STYLE_SHADER + rect 240 190 380 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + +//---------------------------------------------------------------------------------------------- +//SABER MODELS +//---------------------------------------------------------------------------------------------- + //FIRST SABER + itemDef + { + name saber + group models + type ITEM_TYPE_MODEL + //rect 12 -130 615 615 + rect 12 -80 615 615 + asset_model "models/weapons2/saber_1/saber_1.glm" + isSaber 1 + model_angle 180 + model_rotation 20 + model_g2mins 0 0 0 + model_g2maxs 20 20 20 + model_fovx 75 + model_fovy 75 + visible 1 + decoration + } + + //SECOND SABER + itemDef + { + name saber2 + group models + type ITEM_TYPE_MODEL + rect 12 -50 615 615 + asset_model "models/weapons2/saber_1/saber_1.glm" + isSaber2 1 + model_angle 180 + model_rotation 20 + model_g2mins 0 0 0 + model_g2maxs 20 20 20 + model_fovx 75 + model_fovy 75 + visible 1 + + cvarTest ui_saber_type + hideCvar { "single" "staff" } + + decoration + } + +//---------------------------------------------------------------------------------------------- +// OTHER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name backbutton + group exit + + text @MENUS_BACK + descText @MENUS_BACKUP_ONE_MENU + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + cvarTest saber_menu + showCvar { "0" } + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open characterMenu + } + } + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + cvarTest saber_menu + showCvar { "0" } + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3" + uiScript "updatesabercvars" + close all + open quitMenu + } + } + + //BEGIN GAME BUTTON + itemDef + { + name begingamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgame_begin + group none + text @MENUS_BEGIN_GAME + descText @MENUS_START_JEDI_KNIGHT_III + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 455 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + + cvarTest saber_menu + showCvar { "0" } + + action + { + uiScript "updatesabercvars" + uiScript "updatefightingstyle" + open demo_GotoTier + } + mouseEnter + { + show begingamebutton_glow + } + mouseExit + { + hide begingamebutton_glow + } + } + } +} diff --git a/base/ui/demo_sellscreen1.menu b/base/ui/demo_sellscreen1.menu new file mode 100644 index 0000000..0cc12e5 --- /dev/null +++ b/base/ui/demo_sellscreen1.menu @@ -0,0 +1,98 @@ +//---------------------------------------------------------------------------------------------- +// SELL SCREEN +// +// HEY BUY THIS GAME DUDE +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "demo_sellscreen1" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + + onOpen + { + setfocus next_button + } + + onEsc + { + play "sound/interface/button1.wav" + uiScript Quit + } + + + itemDef + { + name screen1 + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "menu/sellscreen1" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name screen2 + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "menu/sellscreen2" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name next_button + type ITEM_TYPE_BUTTON + rect 0 0 640 480 + forecolor 1 1 1 1 + + action + { + play "sound/interface/transition" + hide screen1 + show screen2 + show quit_button + } + } + + itemDef + { + name quit_button + type ITEM_TYPE_BUTTON + rect 0 0 640 480 + forecolor 1 1 1 1 + visible 0 + + action + { + play "sound/interface/button1" + uiScript Quit + } + } + + + } + +} + + + + + + + + + + + diff --git a/base/ui/error.menu b/base/ui/error.menu new file mode 100644 index 0000000..6b95c05 --- /dev/null +++ b/base/ui/error.menu @@ -0,0 +1,128 @@ +//-------------------------------------------------------------- +// +// ERROR MENU +// +// Displays error messages +// +//-------------------------------------------------------------- +{ + menuDef + { + name "error_popmenu" + visible 0 + fullscreen 0 + rect 158 80 320 320 + focusColor 1 1 1 1 + style WINDOW_STYLE_FILLED + border 1 + popup + outOfBoundsClick + + onClose + { + uiScript clearError + } + + onOpen + { + } + + onESC + { + close error_popmenu ; + open mainMenu + } + + itemDef + { + name window + rect 10 15 300 320 + style WINDOW_STYLE_FILLED + backcolor .015 .015 .229 1 + forecolor 0 0 0 1 + border 1 + bordercolor .988 .984 .121 1 + bordersize 5 + visible 1 + decoration + } + + itemDef + { + name errorinfo + rect 0 50 320 20 + text @MENUS_ERROR + textalign ITEM_ALIGN_CENTER + textstyle 6 + textscale 1 + textalignx 160 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name errorinfo + rect 60 80 200 270 + type ITEM_TYPE_TEXT + style WINDOW_STYLE_FILLED + textstyle 3 + autowrapped + cvar "com_errorMessage" + textalign ITEM_ALIGN_CENTER + textalignx 90 + textscale 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + +//-------------------------------------------------------------------------------- +// BUTTON +//-------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 118 295 85 26 + background "gfx/menus/menu_buttonback" forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exit + group grpControlbutton + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 138 295 45 26 + text @MENUS_OKAY + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 22 + forecolor 1 .682 0 1 + visible 1 + + action + { + play "sound/misc/nomenu.wav" + close error_popmenu + open mainMenu + } + + mouseEnter + { + show button_glow + } + mouseExit + { + hide button_glow + } + } + } +} + + diff --git a/base/ui/hud.menu b/base/ui/hud.menu new file mode 100644 index 0000000..7ebc74e --- /dev/null +++ b/base/ui/hud.menu @@ -0,0 +1,1069 @@ +// In Game HUD +// +// defines from ui_shared.h + +assetGlobalDef +{ + bigFont "fonts/reallybigfont" 20 // font + small2Font "arialnb" 14 +} + +{ + menuDef + { + name "mainhud" + fullScreen 0 // MENU_FALSE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 434 + descColor .96 .933 .40 1 // Focus color for text and items + } + + // LEFT SIDE HUD - contains the armor and health + // + menuDef + { + name "lefthud" + fullScreen 0 // MENU_FALSE + rect 0 368 112 112 // Size and position of the menu + visible 1 // Visible on open + + // Metal Frame + itemDef + { + name "frame" + forecolor 1 1 1 1 + background "gfx/hud/hudleft" + rect 0 0 112 112 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + } + + + // Health tics + itemDef + { + name health_tic1 + background "gfx/hud/health_tic_1" + forecolor 1 1 1 1 + rect 20 24 28 28 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name health_tic2 + background "gfx/hud/health_tic_2" + forecolor 1 1 1 1 + rect 38 33 28 28 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name health_tic3 + background "gfx/hud/health_tic_3" + forecolor 1 1 1 1 + rect 52 47 28 28 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name health_tic4 + background "gfx/hud/health_tic_4" + forecolor 1 1 1 1 + rect 60 65 28 28 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + // Armor tics + itemDef + { + name armor_tic1 + background "gfx/hud/armor_tic_1" + forecolor 1 1 1 1 + rect 9 -6 56 56 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name armor_tic2 + background "gfx/hud/armor_tic_2" + forecolor 1 1 1 1 + rect 34 3 56 56 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name armor_tic3 + background "gfx/hud/armor_tic_3" + forecolor 1 1 1 1 + rect 54 24 56 56 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name armor_tic4 + background "gfx/hud/armor_tic_4" + forecolor 1 1 1 1 + rect 63 48 56 56 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + // Scan line +// itemDef +// { +// name scanline +// group none +// background "gfx/hud/hudleft_scanline" +// forecolor 1 1 1 1 +// rect 23 8 80 80 // (these positions are relative to the initial position of the menu) +// // X pos, Y pos, char size, char height +// visible 1 +// } + + // Numeric counter for armor + itemDef + { + name armoramount + group none + forecolor 0.0 .613 .097 1 + rect 85 98 6 12 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + visible 1 + } + + // Numeric counter for health + itemDef + { + name healthamount + group none + forecolor .835 .015 .015 1 + rect 59 98 6 12 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + } + + // Vehicle HUD (Swoop bike) + itemDef + { + name vehicleHUD + group none + rect 0 -145 60 120 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + } + + // Vehicle HUD (Swoop bike) + itemDef + { + name vehicleHUDbackground + group none + rect 20 -135 30 90 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + } + + // Vehicle HUD (Swoop bike) + itemDef + { + name vehicleHUDhealthbar + group none + rect 20 15 30 74 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + } + + } + + // RIGHT SIDE HUD - contains force and ammo amounts + // + menuDef + { + name "righthud" + fullScreen 0 // MENU_FALSE + rect 640 368 -112 112 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + appearanceIncrement 75 // In miliseconds + descX 320 + descY 434 + descColor .96 .933 .40 1 // Focus color for text and items + + // Scan line +// itemDef +// { +// name scanline +// group none +// background "gfx/hud/hudleft_scanline" +// forecolor 1 1 1 1 +// rect -23 8 -80 80 // (these positions are relative to the initial position of the menu) +// // X pos, Y pos, char size, char height +// } + + // Metal Frame + itemDef + { + name "frame" + forecolor 1 1 1 1 + background "gfx/hud/hudleft" + rect 0 0 -112 112 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + } + + + // Ammo tics + itemDef + { + name ammo_tic1 + background "gfx/hud/ammo_tic_1" + forecolor 1 1 1 1 + rect -48 25 28 28 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name ammo_tic2 + background "gfx/hud/ammo_tic_2" + forecolor 1 1 1 1 + rect -66 33 28 28 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name ammo_tic3 + background "gfx/hud/ammo_tic_3" + forecolor 1 1 1 1 + rect -80 47 28 28 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name ammo_tic4 + background "gfx/hud/ammo_tic_4" + forecolor 1 1 1 1 + rect -88 65 28 28 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + // Force tics + itemDef + { + name force_tic1 + background "gfx/hud/force_tic_1" + forecolor 1 1 1 1 + rect -65 -5 56 56 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name force_tic2 + background "gfx/hud/force_tic_2" + forecolor 1 1 1 1 + rect -89 4 56 56 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name force_tic3 + background "gfx/hud/force_tic_3" + forecolor 1 1 1 1 + rect -109 24 56 56 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + itemDef + { + name force_tic4 + background "gfx/hud/force_tic_4" + forecolor 1 1 1 1 + rect -119 48 56 56 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, graphic size, graphic height + } + + + // Numeric counter for force + itemDef + { + name forceamount + group none + rect -109 98 6 12 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + forecolor .359 .524 .722 1 + } + + // Numeric counter for ammo + itemDef + { + name ammoamount + group none + rect -83 98 6 12 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + forecolor 1.0 .658 .062 1 + } + + // When ammo is infinite (used in MP) + itemDef + { + name ammoinfinite + group none + rect -75 87 6 12 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + forecolor 1.0 .658 .062 1 + } + + + // Saber style graphics + itemDef + { + name saberstyle_strong + background "gfx/hud/saber_strong" + forecolor 1 1 1 1 + rect -49 24 26 26 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + } + + itemDef + { + name saberstyle_medium + background "gfx/hud/saber_med" + forecolor 1 1 1 1 + rect -70 43 26 26 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + } + + itemDef + { + name saberstyle_fast + background "gfx/hud/saber_fast" + forecolor 1 1 1 1 + rect -85 69 24 24 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + } + + // Tells current score of game + itemDef + { + name score_line + forecolor 1 1 1 1 + rect -150 92 6 12 // (these positions are relative to the initial position of the menu) + // X pos, Y pos, char size, char height + } + + } + + menuDef + { + name "weaponselecthud" + fullScreen 0 // MENU_FALSE + rect 30 410 0 0 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + } + + menuDef + { + name "forceselecthud" + fullScreen 0 // MENU_FALSE + rect 30 410 0 0 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + } + + menuDef + { + name "inventoryselecthud" + fullScreen 0 // MENU_FALSE + rect 30 410 0 0 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + } + + + // Used as background for the Weapon, Force Powers, and Inventory HUDs + menuDef + { + name "iconbackground" + fullScreen 0 // MENU_FALSE + rect 90 440 460 60 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + } + + ////////////////////////////////////////// + // Taun Taun + ////////////////////////////////////////// + menuDef + { + name "tauntaunhud" + rect 70 400 640 80 + focusColor 1 1 1 1 + + ////////////////////////////////////////// + // Vehicle Speed meter + ////////////////////////////////////////// + itemDef + { + name "speedbackground" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_grid2" + rect 215 55 115 16 + } + + itemDef + { + name "speed_tic1" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_turbo_tick" + rect 216 55 14 16 + } + + itemDef + { + name "speed_tic2" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_turbo_tick" + rect 228 55 14 16 + } + + itemDef + { + name "speed_tic3" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_turbo_tick" + rect 240 55 14 16 + } + + itemDef + { + name "speed_tic4" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_turbo_tick" + rect 252 55 14 16 + } + + itemDef + { + name "speed_tic5" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_turbo_tick" + rect 264 55 14 16 + } + } + + ////////////////////////////////////////// + // ATST Specific + ////////////////////////////////////////// + menuDef + { + name "atsthud" + fullScreen 0 // MENU_FALSE + rect 0 368 112 112 // Size and position of the menu + + ////////////////////////////////////////// + // Vehicle Speed meter + ////////////////////////////////////////// + itemDef + { + name "background" + forecolor 1 1 1 1 + background "gfx/menus/radar/circle_base" + rect 5 10 100 100 + } + + itemDef + { + name "outer_frame" + forecolor 1 1 1 1 + background "gfx/menus/radar/circle_base_frame" + rect 5 10 100 100 + } + + itemDef + { + name "left_pic" + forecolor 1 1 1 1 + background "gfx/menus/radar/atst_dam" + rect 10 20 90 80 + } + } + + menuDef + { + name "swoopvehiclehud" + rect 0 400 640 80 + focusColor 1 1 1 1 + + itemDef + { + name "leftframe" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_frame" + rect 200 30 16 64 + } + + itemDef + { + name "rightframe" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_frame" + rect 436 30 -16 64 + } + + ////////////////////////////////////////// + // Vehicle Shield meter + ////////////////////////////////////////// + itemDef + { + name "shieldbackground" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_grid" + rect 190 32 256 16 + } + + itemDef + { + name "shield_tic1" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 203 32 31 16 + } + + itemDef + { + name "shield_tic2" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 221 32 31 16 + } + + itemDef + { + name "shield_tic3" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 239 32 31 16 + } + + itemDef + { + name "shield_tic4" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 257 32 31 16 + } + + itemDef + { + name "shield_tic5" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 275 32 31 16 + } + + itemDef + { + name "shield_tic6" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 293 32 31 16 + } + + itemDef + { + name "shield_tic7" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 311 32 31 16 + } + + itemDef + { + name "shield_tic8" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 329 32 31 16 + } + + itemDef + { + name "shield_tic9" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 347 32 31 16 + } + + itemDef + { + name "shield_tic10" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 365 32 31 16 + } + + itemDef + { + name "shield_tic11" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 383 32 31 16 + } + + itemDef + { + name "shield_tic12" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_health_tick" + rect 401 32 31 16 + } + + + ////////////////////////////////////////// + // Vehicle Linked Weapons Indicator + ////////////////////////////////////////// + itemDef + { + name "weaponslinked" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_grid2" + rect 426 56 3 14 + } + + ////////////////////////////////////////// + // Vehicle Turbo Recharge meter + ////////////////////////////////////////// + itemDef + { + name "turborecharge" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_grid2" + rect 207 70 3 -14 + } + + ////////////////////////////////////////// + // Vehicle Speed meter + ////////////////////////////////////////// + itemDef + { + name "speedbackground" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_grid2" + rect 215 55 115 16 + } + + itemDef + { + name "speed_tic1" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_turbo_tick" + rect 216 55 14 16 + } + + itemDef + { + name "speed_tic2" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_turbo_tick" + rect 228 55 14 16 + } + + itemDef + { + name "speed_tic3" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_turbo_tick" + rect 240 55 14 16 + } + + itemDef + { + name "speed_tic4" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_turbo_tick" + rect 252 55 14 16 + } + + itemDef + { + name "speed_tic5" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_turbo_tick" + rect 264 55 14 16 + } + + ////////////////////////////////////////// + // Vehicle Armor meter + ////////////////////////////////////////// + itemDef + { + name "armorbackground" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_grid2" + rect 286 55 115 16 + } + + itemDef + { + name "armor_tic1" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_armor_tick" + rect 287 55 14 16 + } + + itemDef + { + name "armor_tic2" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_armor_tick" + rect 299 55 14 16 + } + + itemDef + { + name "armor_tic3" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_armor_tick" + rect 311 55 14 16 + } + + itemDef + { + name "armor_tic4" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_armor_tick" + rect 323 55 14 16 + } + + itemDef + { + name "armor_tic5" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_armor_tick" + rect 335 55 14 16 + } + + ////////////////////////////////////////// + // Vehicle Ammo meter (normal) + ////////////////////////////////////////// + itemDef + { + name "ammobackground" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_grid2" + rect 357 55 115 16 + } + + itemDef + { + name "ammo_tic1" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 358 55 14 16 + } + + itemDef + { + name "ammo_tic2" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 370 55 14 16 + } + + itemDef + { + name "ammo_tic3" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 382 55 14 16 + } + + itemDef + { + name "ammo_tic4" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 394 55 14 16 + } + + itemDef + { + name "ammo_tic5" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 406 55 14 16 + } + + ////////////////////////////////////////// + // Vehicle Ammo Upper meter (for vehicles with two type of ammo) + ////////////////////////////////////////// + itemDef + { + name "ammoupperbackground" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_grid2" + rect 357 55 115 10 + } + + itemDef + { + name "ammoupper_tic1" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 358 55 14 10 + } + + itemDef + { + name "ammoupper_tic2" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 370 55 14 10 + } + + itemDef + { + name "ammoupper_tic3" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 382 55 14 10 + } + + itemDef + { + name "ammoupper_tic4" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 394 55 14 10 + } + + itemDef + { + name "ammoupper_tic5" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 406 55 14 10 + } + + ////////////////////////////////////////// + // Vehicle Ammo Lower meter (for vehicles with two type of ammo) + ////////////////////////////////////////// + itemDef + { + name "ammolowerbackground" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_grid2" + rect 357 67 115 10 + } + + itemDef + { + name "ammolower_tic1" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 358 67 14 10 + } + + itemDef + { + name "ammolower_tic2" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 370 67 14 10 + } + + itemDef + { + name "ammolower_tic3" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 382 67 14 10 + } + + itemDef + { + name "ammolower_tic4" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 394 67 14 10 + } + + itemDef + { + name "ammolower_tic5" + forecolor 1 1 1 1 + background "gfx/hud/vehicle_ammo_tick" + rect 406 67 14 10 + } + } + + menuDef + { + name "mp_timer" + rect 560 160 60 30 + focusColor 1 1 1 1 + + itemDef + { + name "frame" + forecolor 1 1 1 1 + background "gfx/mp/count" + rect 0 15 80 40 + } + + itemDef + { + name "deathtimer" + forecolor 0 .6 0 1 + rect 15 22 64 32 + } + + itemDef + { + name "timer" + forecolor 0 .6 0 1 + rect 15 22 64 32 + } + } + + menuDef + { + name "vehicledamagehud" + fullScreen 0 // MENU_FALSE + rect 0 368 112 112 // Size and position of the menu + visible 1 // Visible on open + + itemDef + { + name "background" + forecolor 1 1 1 1 + background "gfx/menus/radar/circle_base" + rect 5 10 100 100 + } + + itemDef + { + name "outer_frame" + forecolor 1 1 1 1 + background "gfx/menus/radar/circle_base_frame" + rect 5 10 100 100 + } + + itemDef + { + name "shields" + forecolor 1 1 1 1 + background "gfx/menus/radar/circle_base_shield" + rect 5 10 100 100 + } + + itemDef + { + name "vehicle_front" + forecolor 1 1 1 1 + // background is determined by vehicle + background "gfx/menus/radar/circle_base_shield" + rect 5 10 100 100 + } + + itemDef + { + name "vehicle_back" + forecolor 1 1 1 1 + // background is determined by vehicle + background "gfx/menus/radar/circle_base_shield" + rect 5 10 100 100 + } + + itemDef + { + name "vehicle_left" + forecolor 1 1 1 1 + // background is determined by vehicle + background "gfx/menus/radar/circle_base_shield" + rect 5 10 100 100 + } + + itemDef + { + name "vehicle_right" + forecolor 1 1 1 1 + // background is determined by vehicle + background "gfx/menus/radar/circle_base_shield" + rect 5 10 100 100 + } + } + + menuDef + { + name "enemyvehicledamagehud" + fullScreen 0 // MENU_FALSE + rect 530 368 112 112 // Size and position of the menu + visible 1 // Visible on open + + itemDef + { + name "background" + forecolor 1 1 1 1 + background "gfx/menus/radar/circle_base" + rect 5 10 100 100 + } + + itemDef + { + name "outer_frame" + forecolor 1 1 1 1 + background "gfx/menus/radar/circle_base_frame" + rect 5 10 100 100 + } + + itemDef + { + name "shields" + forecolor 1 1 1 1 + background "gfx/menus/radar/circle_base_shield" + rect 5 10 100 100 + } + + itemDef + { + name "vehicle_front" + forecolor 1 1 1 1 + // background is determined by vehicle + background "gfx/menus/radar/circle_base_shield" + rect 5 10 100 100 + } + + itemDef + { + name "vehicle_back" + forecolor 1 1 1 1 + // background is determined by vehicle + background "gfx/menus/radar/circle_base_shield" + rect 5 10 100 100 + } + + itemDef + { + name "vehicle_left" + forecolor 1 1 1 1 + // background is determined by vehicle + background "gfx/menus/radar/circle_base_shield" + rect 5 10 100 100 + } + + itemDef + { + name "vehicle_right" + forecolor 1 1 1 1 + // background is determined by vehicle + background "gfx/menus/radar/circle_base_shield" + rect 5 10 100 100 + } + } +} \ No newline at end of file diff --git a/base/ui/ingame.menu b/base/ui/ingame.menu new file mode 100644 index 0000000..812cdac --- /dev/null +++ b/base/ui/ingame.menu @@ -0,0 +1,473 @@ +//---------------------------------------------------------------------------------------------- +// INGAME MAIN MENU +// +// Ingame main menu +// +//---------------------------------------------------------------------------------------------- +{ + assetGlobalDef + { + smallFont "aurabesh" 18 + mediumFont "ergoec" 18 + bigFont "anewhope" 20 + smallFont "arialnb" 18 + + cursor "cursor" + itemFocusSound "sound/interface/menuroam.wav" + itemFocusForce "fffx/interface/menuroam" + + forceChosenSound "sound/effects/hologram_on" + forceUnchosenSound "sound/effects/hologram_off" + + datapadmoveRollSound "sound/player/roll1" + datapadmoveJumpSound "sound/weapons/force/jump" + + datapadmoveSaberSound1 "sound/weapons/saber/saberhup1" + datapadmoveSaberSound2 "sound/weapons/saber/saberhup2" + datapadmoveSaberSound3 "sound/weapons/saber/saberhup3" + datapadmoveSaberSound4 "sound/weapons/saber/saberhup4" + datapadmoveSaberSound5 "sound/weapons/saber/saberhup5" + datapadmoveSaberSound6 "sound/weapons/saber/saberhup6" + + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 1 // how often fade happens in milliseconds + fadeAmount 0.1 // amount to adjust alpha per cycle + shadowColor 0.1 0.1 0.1 0.0 // shadow color + focuscolor 0 0 1 1 + precacheSound + { + "sound/interface/choose_color.wav" + "sound/interface/choose_head.wav" + "sound/interface/choose_torso.wav" + "sound/interface/choose_saber.wav" + "sound/interface/choose_hilt.wav" + "sound/interface/choose_blade.wav" + "sound/interface/transition.wav" + "sound/interface/esc.wav" + "sound/interface/sub_select.wav" + } + } + + + menuDef + { + name "ingameMainMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + setfocus resume ; + } + + onESC + { + play "sound/interface/esc.wav" + uiScript closeingame // Close menu + } + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + + // Big button "SAVE" + itemDef + { + name savegamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name savegamebutton + group nbut + text @MENUS_SAVE + descText @MENUS_SAVE_CURRENT_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show savegamebutton_glow + } + mouseExit + { + hide savegamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamesaveMenu + } + } + + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingameloadMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamecontrolsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamesetupMenu ; + } + } + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open ingamequitMenu + } + } + + // RESUME button in the lower right corner + itemDef + { + name resumebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name resume + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 455 444 130 24 + text @MENUS_RESUME + descText @MENUS_RESUME_CURRENT_GAME + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show resumebutton_glow + } + mouseExit + { + hide resumebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + uiScript closeingame // Close menu + } + } + + + } +} + + + + + + + + + + + diff --git a/base/ui/ingame.txt b/base/ui/ingame.txt new file mode 100644 index 0000000..e143dec --- /dev/null +++ b/base/ui/ingame.txt @@ -0,0 +1,32 @@ +// menu defs +// +{ + loadMenu { "ui/ingame.menu" } + loadMenu { "ui/ingamesave.menu" } + loadMenu { "ui/ingameload.menu" } + loadMenu { "ui/ingamecontrols.menu" } + loadMenu { "ui/ingamesetup.menu" } + loadMenu { "ui/ingamequit.menu" } + loadMenu { "ui/ingameMissionSelect.menu" } + loadMenu { "ui/ingameGotoTier.menu" } + loadMenu { "ui/ingameForceSelect.menu" } + loadMenu { "ui/ingameWpnSelect.menu" } + loadMenu { "ui/ingameForceStatus.menu" } + loadMenu { "ui/ingameForceHelp.menu" } + loadMenu { "ui/ingameWpnSelectHelp.menu" } + + loadMenu { "ui/datapadforcepowers.menu" } + loadMenu { "ui/datapadmission.menu" } + loadMenu { "ui/datapadweapons.menu" } + loadMenu { "ui/datapadmoves.menu" } + + loadMenu { "ui/error.menu" } + loadMenu { "ui/ingamevid_warning.menu" } + loadMenu { "ui/videodriver.menu" } + loadMenu { "ui/saber.menu" } + loadMenu { "ui/saberstyle.menu" } + + loadMenu { "ui/missionfailed.menu" } + loadMenu { "ui/loadscreen.menu" } + +} diff --git a/base/ui/ingameForceHelp.menu b/base/ui/ingameForceHelp.menu new file mode 100644 index 0000000..f72b1ce --- /dev/null +++ b/base/ui/ingameForceHelp.menu @@ -0,0 +1,127 @@ +//-------------------------------------------------------------- +// +// ERROR MENU +// +// Displays error messages +// +//-------------------------------------------------------------- +{ + menuDef + { + name "ingameForceHelp" + visible 0 + fullscreen 0 + rect 140 70 350 375 + focusColor 1 1 1 1 + style WINDOW_STYLE_FILLED + border 1 + popup + outOfBoundsClick + descX 240 + descY 425 + descScale 1 + descColor 1 .682 0 .8 + + itemDef + { + name window + rect 0 0 350 375 + style WINDOW_STYLE_FILLED + backcolor .015 .015 .229 1 + forecolor 0 0 0 1 + border 1 + bordercolor .388 .396 .925 1 + bordersize 5 + visible 1 + decoration + } + + itemDef + { + name instruction_title + group inst_stuff + type ITEM_TYPE_TEXT + rect 0 25 350 18 + text @MENUS_HELP + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 175 + visible 1 + decoration + } + + itemDef + { + name instruction_text + group inst_stuff + type ITEM_TYPE_TEXTSCROLL + rect 25 80 300 250 + text @MENUS_FORCE_UPGRADE_HELP + font 4 + forecolor 1 1 1 1 + textscale 1.0 + textalign ITEM_ALIGN_LEFT + visible 1 + decoration autowrapped + textaligny 0 + lineHeight 13 + + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//-------------------------------------------------------------------------------- +// BUTTON +//-------------------------------------------------------------------------------- + itemDef + { + name inst_done_button + group inst_stuff + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 0 315 350 18 + text @MENUS_OKAY + descText @MENUS_HELP_DESC + font 3 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 175 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 230 384 170 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/misc/nomenu.wav" + close ingameForceHelp +// open ingameForceSelect + } + } + + + } +} + + diff --git a/base/ui/ingameForceSelect.menu b/base/ui/ingameForceSelect.menu new file mode 100644 index 0000000..f4f1a46 --- /dev/null +++ b/base/ui/ingameForceSelect.menu @@ -0,0 +1,2571 @@ +//---------------------------------------------------------------------------------------------- +// +// Force Select Menu +// +// The player is allowed to allocate one point to any of the given force powers +// +// +// Be sure not to change the names of the fields that end with +// _hexpic +// _fbutton +// _level1desc +// _level2desc +// _level3desc +// +// Also don't change +// allocate_text +// allocated_text +// +// +// The uiScript initallocforcepower sets the rank background for a force power +// +// The uiScript showforceleveldesc show the appropriate level description for that force power. +// +// The uiScript affectforcepowerlevel upgrades a force power rank. It +// also greys out or ungreys the icons. +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "ingameForceSelect" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + descTextStyle 2 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 25 // how often fade happens in milliseconds + fadeAmount 0.05 // amount to adjust alpha per cycle + onOpen + { + stopVoice + + // Update force power pics to current force power levels + + // Light powers + uiScript "initallocforcepower" "absorb" + uiScript "initallocforcepower" "heal" + uiScript "initallocforcepower" "mindtrick" + uiScript "initallocforcepower" "protect" + + // Core powers + uiScript "initallocforcepower" "jump" + uiScript "initallocforcepower" "pull" + uiScript "initallocforcepower" "push" + uiScript "initallocforcepower" "sense" + uiScript "initallocforcepower" "speed" + uiScript "initallocforcepower" "sabdef" + uiScript "initallocforcepower" "saboff" + uiScript "initallocforcepower" "sabthrow" + + // Dark powers + uiScript "initallocforcepower" "drain" + uiScript "initallocforcepower" "grip" + uiScript "initallocforcepower" "lightning" + uiScript "initallocforcepower" "rage" + + uiScript "forcehelpactive" + + } + onESC + { + play "sound/interface/menuroam.wav" + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/forcemenu_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TEXT - Allocate Force power point +//---------------------------------------------------------------------------------------------- + // Allocate force point + itemDef + { + name allocate_text + type ITEM_TYPE_TEXT + rect 0 5 640 18 + text @MENUS_ALLOCATE_FP + font 2 + forecolor 1 1 1 1 + textscale .80 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 1 + decoration + } + + // Force point allocated + itemDef + { + name allocated_text + type ITEM_TYPE_TEXT + rect 0 5 640 18 + text @MENUS_ALLOCATED_FP + font 2 + forecolor 1 1 1 1 + textscale .80 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 0 + decoration + } + + // Core Power message + itemDef + { + name allocated_text_core + type ITEM_TYPE_TEXT + rect 0 5 640 18 + text @MENUS_COREPOWER_DESC + font 2 + forecolor 1 1 1 1 + textscale .80 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// So these will precache +//---------------------------------------------------------------------------------------------- + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_1" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_2" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_3" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_1_gold" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_2_gold" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + itemDef + { + name precache + group none + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_3_gold" + forecolor .65 .65 .65 1 + visible 0 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// DEALLOCATION BUTTON - this button is hidden until a power is chosen then this +// becomes active so the power can be deallocated +//---------------------------------------------------------------------------------------------- + itemDef + { + name deallocate_fbutton + group none + type ITEM_TYPE_BUTTON + rect 0 0 0 0 + forecolor 1 1 1 1 + visible 0 + // Decrement force power + action + { + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + + uiScript "decrementcurrentforcepower" + + hide deallocate_fbutton + + show force_button + show force_icon + show force_desc + +// setfocus absorb_fbutton + + } + } + +//---------------------------------------------------------------------------------------------- +// ABSORB BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name absorb_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 76 41 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name absorb_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 33 30 64 64 + background "gfx/mp/NEW_f_icon_lt_absorb" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name absorb_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 33 41 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { + setitemcolor absorb_iconpic forecolor 1 1 1 1 + setitemcolor absorb_hexpic forecolor 1 1 1 1 + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + } + + // Because this gets focus when the menu first opens. + onFocus + { + setitemcolor absorb_iconpic forecolor 1 1 1 1 + setitemcolor absorb_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "absorb" + + setitembackground force_icon "gfx/mp/NEW_f_icon_lt_absorb" + setitemtext force_desc @SP_INGAME_FORCE_ABSORB_DESC + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "absorb" + hide force_button + + setitemrect deallocate_fbutton 33 41 115 46 + show deallocate_fbutton + + setitemcolor absorb_iconpic forecolor 1 1 1 1 + setitemcolor absorb_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// HEAL BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name heal_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 76 95 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name heal_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 33 85 64 64 + background "gfx/mp/NEW_f_icon_lt_heal" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name heal_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 33 95 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor heal_iconpic forecolor 1 1 1 1 + setitemcolor heal_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "heal" + + setitembackground force_icon "gfx/mp/NEW_f_icon_lt_heal" + setitemtext force_desc @SP_INGAME_FORCE_HEAL_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor heal_iconpic forecolor .65 .65 .65 1 + setitemcolor heal_hexpic forecolor .65 .65 .65 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "heal" + hide force_button + + setitemrect deallocate_fbutton 33 95 115 46 + show deallocate_fbutton + + setitemcolor heal_iconpic forecolor 1 1 1 1 + setitemcolor heal_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// MINDTRICK BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name mindtrick_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 76 149 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name mindtrick_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 33 141 64 64 + background "gfx/mp/NEW_f_icon_lt_mind_trick" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name mindtrick_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 33 149 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor mindtrick_iconpic forecolor 1 1 1 1 + setitemcolor mindtrick_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "mindtrick" + + setitembackground force_icon "gfx/mp/NEW_f_icon_lt_mind_trick" + setitemtext force_desc @SP_INGAME_FORCE_MIND_TRICK_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor mindtrick_iconpic forecolor .65 .65 .65 1 + setitemcolor mindtrick_hexpic forecolor .65 .65 .65 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "mindtrick" + + hide force_button + + setitemrect deallocate_fbutton 33 149 115 46 + show deallocate_fbutton + + setitemcolor mindtrick_iconpic forecolor 1 1 1 1 + setitemcolor mindtrick_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// PROTECT BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name protect_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 76 205 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name protect_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 33 196 64 64 + background "gfx/mp/NEW_f_icon_lt_protect" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name protect_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 33 205 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor protect_iconpic forecolor 1 1 1 1 + setitemcolor protect_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "protect" + + setitembackground force_icon "gfx/mp/NEW_f_icon_lt_protect" + setitemtext force_desc @SP_INGAME_FORCE_PROTECT_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor protect_iconpic forecolor .65 .65 .65 1 + setitemcolor protect_hexpic forecolor .65 .65 .65 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "protect" + + hide force_button + + setitemrect deallocate_fbutton 33 205 115 46 + show deallocate_fbutton + + setitemcolor protect_iconpic forecolor 1 1 1 1 + setitemcolor protect_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// JUMP BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name jump_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 225 41 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name jump_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 184 30 64 64 + background "gfx/mp/NEW_f_icon_jump" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name jump_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 184 41 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor jump_iconpic forecolor 1 1 1 1 + setitemcolor jump_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "jump" + + setitembackground force_icon "gfx/mp/NEW_f_icon_jump" + setitemtext force_desc @SP_INGAME_FORCE_JUMP_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor jump_iconpic forecolor .65 .65 .65 1 + setitemcolor jump_hexpic forecolor .65 .65 .65 1 + + } + } + +//---------------------------------------------------------------------------------------------- +// PULL BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name pull_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 225 95 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name pull_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 184 85 64 64 + background "gfx/mp/NEW_f_icon_pull" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name pull_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 184 95 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor pull_iconpic forecolor 1 1 1 1 + setitemcolor pull_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "pull" + + setitembackground force_icon "gfx/mp/NEW_f_icon_pull" + setitemtext force_desc @SP_INGAME_FORCE_PULL_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor pull_iconpic forecolor .65 .65 .65 1 + setitemcolor pull_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// PUSH BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name push_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 225 149 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name push_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 184 141 64 64 + background "gfx/mp/NEW_f_icon_push" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name push_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 184 149 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor push_iconpic forecolor 1 1 1 1 + setitemcolor push_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "push" + + setitembackground force_icon "gfx/mp/NEW_f_icon_push" + setitemtext force_desc @SP_INGAME_FORCE_PUSH_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor push_iconpic forecolor .65 .65 .65 1 + setitemcolor push_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// SENSE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name sense_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 225 205 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name sense_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 184 196 64 64 + background "gfx/mp/NEW_f_icon_sight" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name sense_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 184 205 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor sense_iconpic forecolor 1 1 1 1 + setitemcolor sense_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "sense" + + setitembackground force_icon "gfx/mp/NEW_f_icon_sight" + setitemtext force_desc @SP_INGAME_FORCE_SENSE_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor sense_iconpic forecolor .65 .65 .65 1 + setitemcolor sense_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// SPEED BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name speed_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 380 41 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name speed_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 336 30 64 64 + background "gfx/mp/NEW_f_icon_speed" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name speed_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 336 41 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor speed_iconpic forecolor 1 1 1 1 + setitemcolor speed_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "speed" + + setitembackground force_icon "gfx/mp/NEW_f_icon_speed" + setitemtext force_desc @SP_INGAME_FORCE_SPEED_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor speed_iconpic forecolor .65 .65 .65 1 + setitemcolor speed_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// SABER DEFENSIVE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name sabdef_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 380 95 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name sabdef_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 336 85 64 64 + background "gfx/mp/NEW_f_icon_saber_defend" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name sabdef_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 336 95 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor sabdef_iconpic forecolor 1 1 1 1 + setitemcolor sabdef_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "sabdef" + + setitembackground force_icon "gfx/mp/NEW_f_icon_saber_defend" + setitemtext force_desc @SP_INGAME_FORCE_SABER_DEFENSE_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor sabdef_iconpic forecolor .65 .65 .65 1 + setitemcolor sabdef_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// SABER OFFENSIVE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name saboff_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 380 149 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name saboff_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 336 141 64 64 + background "gfx/mp/NEW_f_icon_saber_attack" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name saboff_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 336 149 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor saboff_iconpic forecolor 1 1 1 1 + setitemcolor saboff_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "saboff" + + setitembackground force_icon "gfx/mp/NEW_f_icon_saber_attack" + setitemtext force_desc @SP_INGAME_FORCE_SABER_OFFENSE_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor saboff_iconpic forecolor .65 .65 .65 1 + setitemcolor saboff_hexpic forecolor .65 .65 .65 1 + } + } + +//---------------------------------------------------------------------------------------------- +// SABER THROW BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name sabthrow_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 380 205 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name sabthrow_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 336 196 64 64 + background "gfx/mp/NEW_f_icon_saber_throw" + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name sabthrow_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 336 205 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_COREPOWER_DESC + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor sabthrow_iconpic forecolor 1 1 1 1 + setitemcolor sabthrow_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "sabthrow" + + setitembackground force_icon "gfx/mp/NEW_f_icon_saber_throw" + setitemtext force_desc @SP_INGAME_FORCE_SABER_THROW_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor sabthrow_iconpic forecolor .5 .5 .5 1 + setitemcolor sabthrow_hexpic forecolor .5 .5 .5 1 + } + } + +//---------------------------------------------------------------------------------------------- +// DRAIN BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name drain_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 527 41 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name drain_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 484 30 64 64 + background "gfx/mp/NEW_f_icon_dk_drain" + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name drain_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 484 41 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor drain_iconpic forecolor 1 1 1 1 + setitemcolor drain_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "drain" + + setitembackground force_icon "gfx/mp/NEW_f_icon_dk_drain" + setitemtext force_desc @SP_INGAME_FORCE_DRAIN_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor drain_iconpic forecolor .5 .5 .5 1 + setitemcolor drain_hexpic forecolor .5 .5 .5 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "drain" + + hide force_button + + setitemrect deallocate_fbutton 484 41 115 46 + show deallocate_fbutton + + setitemcolor drain_iconpic forecolor 1 1 1 1 + setitemcolor drain_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// GRIP BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name grip_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 527 95 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name grip_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 484 85 64 64 + background "gfx/mp/NEW_f_icon_dk_grip" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name grip_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 484 95 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor grip_iconpic forecolor 1 1 1 1 + setitemcolor grip_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "grip" + + setitembackground force_icon "gfx/mp/NEW_f_icon_dk_grip" + setitemtext force_desc @SP_INGAME_FORCE_GRIP_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor grip_iconpic forecolor .65 .65 .65 1 + setitemcolor grip_hexpic forecolor .65 .65 .65 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "grip" + + hide force_button + + setitemrect deallocate_fbutton 484 95 115 46 + show deallocate_fbutton + + setitemcolor grip_iconpic forecolor 1 1 1 1 + setitemcolor grip_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// LIGHTNING BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name lightning_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 527 149 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name lightning_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 484 141 64 64 + background "gfx/mp/NEW_f_icon_dk_l1" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name lightning_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 484 149 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor lightning_iconpic forecolor 1 1 1 1 + setitemcolor lightning_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "lightning" + + setitembackground force_icon "gfx/mp/NEW_f_icon_dk_l1" + setitemtext force_desc @SP_INGAME_FORCE_LIGHTNING_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor lightning_iconpic forecolor .65 .65 .65 1 + setitemcolor lightning_hexpic forecolor .65 .65 .65 1 + } + + // Increment force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "lightning" + + hide force_button + + setitemrect deallocate_fbutton 484 149 115 46 + show deallocate_fbutton + + setitemcolor lightning_iconpic forecolor 1 1 1 1 + setitemcolor lightning_hexpic forecolor 1 1 1 1 + } + } + +//---------------------------------------------------------------------------------------------- +// RAGE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name rage_hexpic + group hexpic + style WINDOW_STYLE_SHADER + rect 527 205 70 49 + background "gfx/menus/hex_pattern_0" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name rage_iconpic + group iconpic + style WINDOW_STYLE_SHADER + rect 484 196 64 64 + background "gfx/mp/NEW_f_icon_dk_rage" + forecolor .65 .65 .65 1 + visible 1 + decoration + } + + itemDef + { + name rage_fbutton + group force_button + type ITEM_TYPE_BUTTON + rect 484 205 115 46 + forecolor 1 1 1 1 + visible 1 + desctext @MENUS_ADDFP + // Show force power description + mouseEnter + { // Because on opening, absorb is given focus + setitemcolor absorb_iconpic forecolor .65 .65 .65 1 + setitemcolor absorb_hexpic forecolor .65 .65 .65 1 + + setitemcolor rage_iconpic forecolor 1 1 1 1 + setitemcolor rage_hexpic forecolor 1 1 1 1 + + // Hide all force level descrptions and have script determine the level of this one + hide leveltext + uiScript "showforceleveldesc" "rage" + + setitembackground force_icon "gfx/mp/NEW_f_icon_dk_rage" + setitemtext force_desc @SP_INGAME_FORCE_RAGE_DESC + + show force_icon + show force_desc + } + mouseExit + { + setitemcolor rage_iconpic forecolor .65 .65 .65 1 + setitemcolor rage_hexpic forecolor .65 .65 .65 1 + } + + // Increment/decrement force power + action + { // hide descriptions and let affectforcepowerlevel determine the corect one + hide leveltext + uiScript "affectforcepowerlevel" "rage" + + hide force_button + + setitemrect deallocate_fbutton 484 205 115 46 + show deallocate_fbutton + + setitemcolor rage_iconpic forecolor 1 1 1 1 + setitemcolor rage_hexpic forecolor 1 1 1 1 + + } + } +//---------------------------------------------------------------------------------------------- +// BIG FORCE ICON AND FORCE DESCRIPTION +//---------------------------------------------------------------------------------------------- + + itemDef + { + name force_icon + group bigicons + style WINDOW_STYLE_SHADER + rect 30 265 128 128 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name force_desc + rect 157 264 460 100 + font 4 + type ITEM_TYPE_TEXTSCROLL + text "This is the text" + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + lineHeight 14 + decoration + } + +//---------------------------------------------------------------------------------------------- +// ABSORB LEVEL DESCRIPTION +//---------------------------------------------------------------------------------------------- + // Level 1 description + itemDef + { + name absorb_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_ABSORB_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + // Level 2 description + itemDef + { + name absorb_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_ABSORB_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + // Level 3 description + itemDef + { + name absorb_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_ABSORB_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// HEAL DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name heal_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_HEAL_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name heal_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_HEAL_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name heal_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_HEAL_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// MIND TRICK DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name mindtrick_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_MIND_TRICK_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name mindtrick_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_MIND_TRICK_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name mindtrick_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_MIND_TRICK_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// PROTECT DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name protect_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PROTECT_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name protect_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PROTECT_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name protect_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PROTECT_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// +// JUMP DESCRIPTION +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name jump_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_JUMP_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name jump_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_JUMP_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name jump_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_JUMP_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// PULL DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name pull_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PULL_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name pull_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PULL_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name pull_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PULL_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// PUSH DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name push_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PUSH_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name push_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PUSH_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name push_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_PUSH_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// SENSE DESCRIPTION +//---------------------------------------------------------------------------------------------- + + itemDef + { + name big_sense + group bigicons + style WINDOW_STYLE_SHADER + rect 30 265 128 128 + background "gfx/mp/NEW_f_icon_sight" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name sense_text + group mainpowertext + rect 157 264 460 120 + text @SP_INGAME_FORCE_SENSE_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name sense_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SENSE_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sense_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SENSE_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sense_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SENSE_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// SPEED DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name speed_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SPEED_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name speed_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SPEED_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name speed_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SPEED_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// SABER DEFENSE DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name sabdef_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_DEFENSE_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sabdef_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_DEFENSE_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sabdef_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_DEFENSE_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// SABER OFFENSE DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name saboff_text + group mainpowertext + rect 157 264 460 120 + text @SP_INGAME_FORCE_SABER_OFFENSE_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name saboff_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_OFFENSE_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name saboff_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_OFFENSE_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name saboff_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_OFFENSE_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// SABER THROW DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name sabthrow_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_THROW_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sabthrow_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_THROW_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name sabthrow_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_SABER_THROW_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// DRAIN DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name drain_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_DRAIN_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name drain_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_DRAIN_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name drain_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_DRAIN_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// GRIP DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name grip_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_GRIP_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name grip_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_GRIP_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name grip_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_GRIP_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// LIGHTNING DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name lightning_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_LIGHTNING_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name lightning_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_LIGHTNING_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name lightning_level3desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_LIGHTNING_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + +//---------------------------------------------------------------------------------------------- +// RAGE DESCRIPTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name rage_level1desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_RAGE_LVL1_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name rage_level2desc + group leveltext + rect 157 364 460 60 + text @SP_INGAME_FORCE_RAGE_LVL2_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + itemDef + { + name rage_level3desc + group leveltext + rect 157 384 460 40 + text @SP_INGAME_FORCE_RAGE_LVL3_DESC + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 0 + autowrapped + type ITEM_TYPE_TEXTSCROLL + lineHeight 13 + } + + +//---------------------------------------------------------------------------------------------- +// BOTTOM ROW OF BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name backbut + type ITEM_TYPE_BUTTON + rect 30 447 140 24 + text @MENUS_HELP + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_HELP_DESC1 + visible 1 + mouseEnter + { + } + mouseExit + { + } + + // If we're backing up then we have to reset levels to what they were + action + { + play "sound/interface/menuroam.wav" + open ingameForceHelp + } + } + +/* Commented out until I can get the screens all linked up + itemDef + { + name backbut + type ITEM_TYPE_BUTTON + rect 30 447 140 24 + text "BACK" + font 2 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext "Choose a different mission." + visible 1 + mouseEnter + { + } + mouseExit + { + } + + // If we're backing up then we have to reset levels to what they were + action + { + uiScript "resetforcelevels" + play "sound/interface/menuroam.wav" + close all + open ingameMissionSelect1 + } + } +*/ + +/* Commented out until I can get the screens all linked up + itemDef + { + name forcebut + type ITEM_TYPE_BUTTON + rect 183 447 0 0 + text "Force Powers" + font 2 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + visible 1 + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + close all + } + } +*/ + + // do not change the name of this because it's made active/inactive in code + itemDef + { + name weaponbutton + type ITEM_TYPE_BUTTON + rect 336 447 140 24 + text @MENUS_WEAPONS + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_CHOOSEWEAPONS + visible 1 + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + close all + open ingameWpnSelect + } + } + +/* Commented out until I can get the screens all linked up + itemDef + { + name nextbut + type ITEM_TYPE_BUTTON + rect 489 447 120 24 + text "Begin" + font 2 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext "Start mission." + visible 1 + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + close all + open ingameWpnSelect + } + } +*/ + +//---------------------------------------------------------------------------------------------- +// +// SCANLINES +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + diff --git a/base/ui/ingameForceStatus.menu b/base/ui/ingameForceStatus.menu new file mode 100644 index 0000000..14ede30 --- /dev/null +++ b/base/ui/ingameForceStatus.menu @@ -0,0 +1,654 @@ +//---------------------------------------------------------------------------------------------- +// Force Status Menu +// +// The screen where after calculating the amount of light side force powers you allocated, +// Luke or Kyle will congratulate or warn you, depending on which way you're going. +// +// 90 % - 100 : very light +// 60 % - 89 : semi-light +// 41 % - 59 : neutral +// 11 % - 40 : semi-dark +// 0% - 10 : very dark +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "ingameForceStatus" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 25 // how often fade happens in milliseconds + fadeAmount 0.05 // amount to adjust alpha per cycle + onOpen + { + // Compare light/dark force powers allocated + uiScript "calcforcestatus" + + uiScript "initallocforcepower" "absorb" + uiScript "initallocforcepower" "heal" + uiScript "initallocforcepower" "mindtrick" + uiScript "initallocforcepower" "protect" + + uiScript "initallocforcepower" "drain" + uiScript "initallocforcepower" "grip" + uiScript "initallocforcepower" "lightning" + uiScript "initallocforcepower" "rage" + + } + onESC + { + } + + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/mission_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Allocate force point + itemDef + { + name allocate_text + type ITEM_TYPE_TEXT + rect 0 30 640 18 + text @MENUS_CUR_FORCE_ALLOC + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// Light Force Icons +//---------------------------------------------------------------------------------------------- + //---------------------------------- + // Absorb Icon + //---------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive) + itemDef + { + name absorb_hexpic + group lighthexes + style WINDOW_STYLE_SHADER + rect 96 91 70 49 + background "gfx/menus/hex_pattern_0" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name absorb_iconpic + style WINDOW_STYLE_SHADER + rect 53 80 64 64 + background "gfx/mp/NEW_f_icon_lt_absorb" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------- + // Heal Icon + //---------------------------------- + itemDef + { + name heal_hexpic + group lighthexes + style WINDOW_STYLE_SHADER + rect 236 91 70 49 + background "gfx/menus/hex_pattern_0" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name heal_iconpic + style WINDOW_STYLE_SHADER + rect 193 80 64 64 + background "gfx/mp/NEW_f_icon_lt_heal" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------- + // Mindtrick Icon + //---------------------------------- + itemDef + { + name mindtrick_hexpic + group lighthexes + style WINDOW_STYLE_SHADER + rect 376 91 70 49 + background "gfx/menus/hex_pattern_0" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name mindtrick_iconpic + style WINDOW_STYLE_SHADER + rect 333 80 64 64 + background "gfx/mp/NEW_f_icon_lt_mind_trick" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------- + // Protect Icon + //---------------------------------- + itemDef + { + name protect_hexpic + group lighthexes + style WINDOW_STYLE_SHADER + rect 516 91 70 49 + background "gfx/menus/hex_pattern_0" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name protect_iconpic + style WINDOW_STYLE_SHADER + rect 473 80 64 64 + background "gfx/mp/NEW_f_icon_lt_protect" + forecolor 1 1 1 1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// Dark Force Icons +//---------------------------------------------------------------------------------------------- + + //---------------------------------- + // Drain Icon + //---------------------------------- + // The hex powerlevel graphic (this has to print first because it's additive + itemDef + { + name drain_hexpic + group darkhexes + style WINDOW_STYLE_SHADER + rect 96 191 70 49 + background "gfx/menus/hex_pattern_0" + forecolor 1 .7 .7 1 + visible 1 + decoration + } + + itemDef + { + name drain_iconpic + style WINDOW_STYLE_SHADER + rect 53 182 64 64 + background "gfx/mp/NEW_f_icon_dk_drain" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + //---------------------------------- + // Grip Icon + //---------------------------------- + itemDef + { + name grip_hexpic + group darkhexes + style WINDOW_STYLE_SHADER + rect 236 191 70 49 + background "gfx/menus/hex_pattern_0" + forecolor 1 .7 .7 1 + visible 1 + decoration + } + + itemDef + { + name grip_iconpic + style WINDOW_STYLE_SHADER + rect 193 182 64 64 + background "gfx/mp/NEW_f_icon_dk_grip" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------- + // Lighting Icon + //---------------------------------- + itemDef + { + name lightning_hexpic + group darkhexes + style WINDOW_STYLE_SHADER + rect 376 191 70 49 + background "gfx/menus/hex_pattern_0" + forecolor 1 .7 .7 1 + visible 1 + decoration + } + + itemDef + { + name lightning_iconpic + style WINDOW_STYLE_SHADER + rect 333 182 64 64 + background "gfx/mp/NEW_f_icon_dk_l1" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------- + // Rage Icon + //---------------------------------- + itemDef + { + name rage_hexpic + group darkhexes + style WINDOW_STYLE_SHADER + rect 516 191 70 49 + background "gfx/menus/hex_pattern_0" + forecolor 1 .7 .7 1 + visible 1 + decoration + } + + itemDef + { + name rage_iconpic + style WINDOW_STYLE_SHADER + rect 473 182 64 64 + background "gfx/mp/NEW_f_icon_dk_rage" + forecolor .75 .75 .75 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MODELS FOR V-O +//---------------------------------------------------------------------------------------------- + itemDef + { + name luke_face + group models + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + cvarTest ui_forcestatus + showCvar { "vll"; "sll"; "ntl"; "sdl"; "vdl" } + + } + + itemDef + { + name kyle_face + group models + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + cvarTest ui_forcestatus + showCvar { "vlk"; "slk"; "ntk"; "sdk"; "vdk" } + + decoration + } + +//---------------------------------------------------- +// LUKE SUBTITLES +//---------------------------------------------------- + itemDef + { + name luke_very_light + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @MISC_MLUK_03 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + autowrapped + cvarTest ui_forcestatus + showCvar { "vll" } + } + + itemDef + { + name luke_semi_light + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @MISC_MLUK_04 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + autowrapped + cvarTest ui_forcestatus + showCvar { "sll" } + } + + itemDef + { + name luke_neutral + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @MISC_MLUK_05 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + autowrapped + cvarTest ui_forcestatus + showCvar { "ntl" } + } + + itemDef + { + name luke_semi_dark + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @MISC_MLUK_01 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + autowrapped + cvarTest ui_forcestatus + showCvar { "sdl" } + } + + itemDef + { + name luke_very_dark + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @MISC_MLUK_02 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + autowrapped + cvarTest ui_forcestatus + showCvar { "vdl" } + } + + +//---------------------------------------------------- +// KYLE SUBTITLES +//---------------------------------------------------- + itemDef + { + name kyle_very_light + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @MISC_MKYK_05 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + autowrapped + cvarTest ui_forcestatus + showCvar { "vlk" } + } + + itemDef + { + name kyle_semi_light + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @MISC_MKYK_04 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + autowrapped + cvarTest ui_forcestatus + showCvar { "slk" } + } + + itemDef + { + name kyle_neutral + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @MISC_MKYK_03 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + autowrapped + cvarTest ui_forcestatus + showCvar { "ntk" } + } + + itemDef + { + name kyle_semi_dark + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @MISC_MKYK_01 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + autowrapped + cvarTest ui_forcestatus + showCvar { "sdk" } + } + + itemDef + { + name kyle_very_dark + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @MISC_MKYK_02 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + autowrapped + cvarTest ui_forcestatus + showCvar { "vdk" } + } + +//----------------------------------------- +// Go to Academy (taken from ingameMissionSelect) +//---------------------------------------- + itemDef + { + name story5_continue + type ITEM_TYPE_BUTTON + rect 430 440 100 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showCvar { "5"; "6" } + mouseEnter + { + } + mouseExit + { + } + action + { + close all + uiScript startmap academy2 + } + } + + itemDef + { + name story11_continue + type ITEM_TYPE_BUTTON + rect 430 440 100 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showCvar { "11"; "12" } + mouseEnter + { + } + mouseExit + { + } + action + { + close all + uiScript startmap academy4 + } + } + + itemDef + { + name story17_continue + type ITEM_TYPE_BUTTON + rect 430 440 100 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showCvar { "17"; "18" } + mouseEnter + { + } + mouseExit + { + } + action + { + close all + uiScript startmap academy6 + } + } + +//---------------------------------------------------------------------------------------------- +// SCANLINES OVER WHOLE MENU +//---------------------------------------------------------------------------------------------- + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + + diff --git a/base/ui/ingameGotoTier.menu b/base/ui/ingameGotoTier.menu new file mode 100644 index 0000000..2999755 --- /dev/null +++ b/base/ui/ingameGotoTier.menu @@ -0,0 +1,854 @@ +//---------------------------------------------------------------------------------------------- +// Mission Select Menu +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "ingameGotoTier" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 20 // how often fade happens in milliseconds + fadeAmount 0.1 // amount to adjust alpha per cycle + onOpen + { + uiScript loadmissionselectmenu tier_storyinfo + fadein background + } + onESC + { + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/mission_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name eyecandy1 + group none + style WINDOW_STYLE_SHADER + rect 17 309 357 153 + background "gfx/menus/mission_bottomleft_grid" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name botlf + group none + style WINDOW_STYLE_SHADER + rect 0 300 80 180 + background "gfx/menus/mission_bottom_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + itemDef + { + name complete + type ITEM_TYPE_TEXT + rect 170 16 300 32 + text @MENUS_MISSION_COMPLETE + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 150 + textaligny -1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Killed, Secrets +//---------------------------------------------------------------------------------------------- + itemDef + { + name killed + group stats + rect 0 52 320 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_ENEMIESKILLED + cvar "ui_stats_enemieskilled" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_CENTER + textalignx 160 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name secretsfound + group stats + rect 320 52 320 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_SECRETAREAS + cvar "ui_stats_secretsfound" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_CENTER + textalignx 160 + textaligny -1 + visible 1 + decoration + cvartest "ui_stats_secretsfound" + hidecvar + { + "0" + } + } + +// Lines above and below lightsaber info + itemDef + { + name border + group none + type ITEM_TYPE_TEXT + rect 70 80 500 2 + forecolor 1 1 1 1 + border 1 + bordercolor .403 .584 .741 1 + visible 1 + decoration + } + + itemDef + { + name border + group none + type ITEM_TYPE_TEXT + rect 70 165 500 2 + forecolor 1 1 1 1 + border 1 + bordercolor .403 .584 .741 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Saber use data +//---------------------------------------------------------------------------------------------- + itemDef + { + name saberuse + type ITEM_TYPE_TEXT + rect 0 87 640 32 + text @SP_INGAME_LIGHTSABERUSE + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 320 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name saber_thrown + rect 20 115 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_THROWN + cvar "ui_stats_thrown" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name saber_blocks + rect 20 140 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_BLOCKS + cvar "ui_stats_blocks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name leg_attacks + rect 220 115 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_LEGATTACKS + cvar "ui_stats_legattacks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name arm_attacks + rect 220 140 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_ARMATTACKS + cvar "ui_stats_armattacks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name body_attacks + rect 420 115 300 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_BODYATTACKS + cvar "ui_stats_bodyattacks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + + itemDef + { + name forceuse + type ITEM_TYPE_TEXT + rect 250 173 150 32 + text @SP_INGAME_FORCEUSE + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 75 + textaligny -1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// Lower 1st column of force stats (light side powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name absorb + rect -20 195 150 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_ABSORB + cvar "ui_stats_absorb" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name heal + rect -20 215 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_HEAL + cvar "ui_stats_heal" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name mindtrick + rect -20 235 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_MINDTRICK + cvar "ui_stats_mindtrick" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name protection + rect -20 255 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_PROTECTION + cvar "ui_stats_protect" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Lower 2nd column of force stats (neutral powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name jump + rect 130 195 160 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_JUMP + cvar "ui_stats_jump" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name pull + rect 130 215 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_PULL + cvar "ui_stats_pull" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name push + rect 130 235 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_PUSH + cvar "ui_stats_push" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name sense + rect 130 255 160 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_SENSE + cvar "ui_stats_sense" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Lower 3rd column of force stats (neutral powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name speed + rect 280 195 150 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_SPEED + cvar "ui_stats_speed" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name defense + rect 280 215 300 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_DEFENSE + cvar "ui_stats_defense" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name offense + rect 280 235 300 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_OFFENSE + cvar "ui_stats_offense" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name throw + rect 280 255 300 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_THROW + cvar "ui_stats_throw" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Lower 4th column of force stats (dark powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name grip + rect 430 195 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_DRAIN + cvar "ui_stats_drain" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name grip + rect 430 215 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_GRIP + cvar "ui_stats_grip" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name lightning + rect 430 235 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_LIGHTNING + cvar "ui_stats_lightning" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name rage + rect 430 255 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_RAGE + cvar "ui_stats_rage" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MODELS FOR V-O +//---------------------------------------------------------------------------------------------- + itemDef + { + name luke + group storyheads + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + cvartest "storyhead" + showcvar + { + "luke" + "yavin2" + } + } + + itemDef + { + name kyle + group storyheads + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + cvartest "storyhead" + showcvar + { + "kyle" + } + } + + itemDef + { + name protocol + group storyheads + type ITEM_TYPE_MODEL + rect 426 317 116 116 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/protocol/model_menu.skin" + asset_model "models/players/protocol/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 17 + model_g2maxs 60 20 50 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + cvartest "storyhead" + showcvar + { + "protocol" + "academy3" + } + } + +//---------------------------------------------------------------------------------------------- +// +// BUTTONS +// +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//Sound is automatic +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//coming from Yavin2 +//---------------------------------------------------------------------------------------------- + itemDef + { + name story1 + type ITEM_TYPE_BUTTON + rect 550 440 172 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "1" + } + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect1 + } + } + + itemDef + { + name story1text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER1_1 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 1 + autowrapped + cvartest "tier_storyinfo" + showcvar + { + "1" + } + } + + +//---------------------------------------------------------------------------------------------- +//coming from Hoth3 +//---------------------------------------------------------------------------------------------- + itemDef + { + name story6 + type ITEM_TYPE_BUTTON + rect 550 440 172 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "7" + } + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect2 + } + } + + itemDef + { + name story2text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER2_7 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 1 + autowrapped + cvartest "tier_storyinfo" + showcvar + { + "7" + } + } + +//---------------------------------------------------------------------------------------------- +//coming from Vjun3 +//---------------------------------------------------------------------------------------------- + itemDef + { + name story11 + type ITEM_TYPE_BUTTON + rect 550 440 172 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "13" + } + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect3 + } + } + + itemDef + { + name story3text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER3_13 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 1 + autowrapped + cvartest "tier_storyinfo" + showcvar + { + "13" + } + } + +//---------------------------------------------------------------------------------------------- +// SCANLINES OVER WHOLE MENU +//---------------------------------------------------------------------------------------------- + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + } +} + + + + + + + + + + + diff --git a/base/ui/ingameMissionSelect.menu b/base/ui/ingameMissionSelect.menu new file mode 100644 index 0000000..8b25677 --- /dev/null +++ b/base/ui/ingameMissionSelect.menu @@ -0,0 +1,2849 @@ +//---------------------------------------------------------------------------------------------- +// Mission Select Menu +// +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingameMissionSelect" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 20 // how often fade happens in milliseconds + fadeAmount 0.1 // amount to adjust alpha per cycle + onOpen + { + uiScript loadmissionselectmenu tier_storyinfo + } + onESC + { + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/mission_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name eyecandy1 + group none + style WINDOW_STYLE_SHADER + rect 17 309 357 153 + background "gfx/menus/mission_bottomleft_grid" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name botlf + group none + style WINDOW_STYLE_SHADER + rect 0 300 80 180 + background "gfx/menus/mission_bottom_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name complete + type ITEM_TYPE_TEXT + rect 170 16 300 32 + text @MENUS_MISSION_COMPLETE + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 150 + textaligny -1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Killed, Secrets +//---------------------------------------------------------------------------------------------- + itemDef + { + name killed + group stats + rect 0 52 320 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_ENEMIESKILLED + cvar "ui_stats_enemieskilled" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_CENTER + textalignx 160 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name secretsfound + group stats + rect 320 52 320 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_SECRETAREAS + cvar "ui_stats_secretsfound" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_CENTER + textalignx 160 + textaligny -1 + visible 1 + decoration + cvartest "ui_stats_secretsfound" + hidecvar + { + "0" + } + } + + // Lines above and below lightsaber info + itemDef + { + name border + group none + type ITEM_TYPE_TEXT + rect 70 80 500 2 + forecolor 1 1 1 1 + border 1 + bordercolor .403 .584 .741 1 + visible 1 + decoration + } + + itemDef + { + name border + group none + type ITEM_TYPE_TEXT + rect 70 165 500 2 + forecolor 1 1 1 1 + border 1 + bordercolor .403 .584 .741 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Saber use data +//---------------------------------------------------------------------------------------------- + itemDef + { + name saberuse + type ITEM_TYPE_TEXT + rect 0 87 640 32 + text @SP_INGAME_LIGHTSABERUSE + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 320 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name saber_thrown + rect 20 115 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_THROWN + cvar "ui_stats_thrown" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name saber_blocks + rect 20 140 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_BLOCKS + cvar "ui_stats_blocks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name leg_attacks + rect 220 115 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_LEGATTACKS + cvar "ui_stats_legattacks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name arm_attacks + rect 220 140 200 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_ARMATTACKS + cvar "ui_stats_armattacks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name body_attacks + rect 420 115 300 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_BODYATTACKS + cvar "ui_stats_bodyattacks" + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 1 + decoration + } + + + itemDef + { + name forceuse + type ITEM_TYPE_TEXT + rect 250 173 150 32 + text @SP_INGAME_FORCEUSE + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 75 + textaligny -1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// Lower 1st column of force stats (light side powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name absorb + rect -20 195 150 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_ABSORB + cvar "ui_stats_absorb" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name heal + rect -20 215 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_HEAL + cvar "ui_stats_heal" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name mindtrick + rect -20 235 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_MINDTRICK + cvar "ui_stats_mindtrick" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name protection + rect -20 255 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_PROTECTION + cvar "ui_stats_protect" + font 2 + forecolor 0 0 1 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Lower 2nd column of force stats (neutral powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name jump + rect 130 195 160 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_JUMP + cvar "ui_stats_jump" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name pull + rect 130 215 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_PULL + cvar "ui_stats_pull" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name push + rect 130 235 160 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_PUSH + cvar "ui_stats_push" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name sense + rect 130 255 160 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_SENSE + cvar "ui_stats_sense" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Lower 3rd column of force stats (neutral powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name speed + rect 280 195 150 32 + type ITEM_TYPE_EDITFIELD + text @SP_INGAME_SPEED + cvar "ui_stats_speed" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name defense + rect 280 215 300 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_DEFENSE + cvar "ui_stats_defense" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name offense + rect 280 235 300 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_OFFENSE + cvar "ui_stats_offense" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -1 + visible 1 + decoration + } + + itemDef + { + name throw + rect 280 255 300 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_THROW + cvar "ui_stats_throw" + font 2 + forecolor .631 .631 .815 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Lower 4th column of force stats (dark powers) +//---------------------------------------------------------------------------------------------- + itemDef + { + name grip + rect 430 195 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_DRAIN + cvar "ui_stats_drain" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name grip + rect 430 215 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_GRIP + cvar "ui_stats_grip" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name lightning + rect 430 235 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_FORCE_LIGHTNING + cvar "ui_stats_lightning" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + + itemDef + { + name rage + rect 430 255 150 32 + type ITEM_TYPE_EDITFIELD + text @MENUS_RAGE + cvar "ui_stats_rage" + font 2 + forecolor 1 0 0 1 + textscale .8 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MODELS FOR V-O +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +// +// THESE HEADS APPEAR WHEN YOU SUCCESSFULLY COMPLETE A TIER LEVEL. +// +// THE SOUNDS ARE IN sounds/tiervictory AND ARE THE NAME OF THE LEVEL. +// IT IS HARDCODED TO LOOK FOR AND PLAY A SOUND MATCHING THE MAP NAME IN THIS DIR. +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name luke + group models + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + cvartest "storyhead" + showcvar + { + "luke" + "t1_surprise" + "t1_sour" + "t3_hevil" + "t1_test" + "t1_rail" + } + } + +//t1_rail has conversation; luke, kyle, luke, kyle, luke. + + itemDef + { + name kyle + group models + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + cvartest "storyhead" + showcvar + { + "t1_fatal" + "t1_danger" + "t2_rogue" + "t2_dpred" + "t2_rancor" + "t2_trip" + "t3_rift" + "t3_byss" + "t3_stamp" + "t3_bounty" + "kyle" + } + } + + itemDef + { + name prot + group models + type ITEM_TYPE_MODEL + rect 426 317 116 116 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/protocol/model_menu.skin" + asset_model "models/players/protocol/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 17 + model_g2maxs 60 20 50 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + cvartest "storyhead" + showcvar + { + "prot" + } + } + + itemDef + { + name wedge + group models + type ITEM_TYPE_MODEL + rect 426 317 116 116 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/rebel_pilot/model_default.skin" + asset_model "models/players/rebel_pilot/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 50 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 1 + decoration + cvartest "storyhead" + showcvar + { + "t2_wedge" + "wedge" + } + } +//---------------------------------------------------------------------------------------------- +// SUBTITLES FOR TIER VICTORY MESSAGES. +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +// 1ST TIER +//---------------------------------------------------------------------------------------------- + itemDef + { + name vic_text_t1_test + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T1_SURPRISE + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t1_test" + } + } + + itemDef + { + name vic_text_t1_sour + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T1_SOUR + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t1_sour" + } + } + + itemDef + { + name vic_text_t1_surprise + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T1_SURPRISE + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t1_surprise" + } + } + + itemDef + { + name vic_text_t1_fatal + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T1_FATAL + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t1_fatal" + } + } + + itemDef + { + name vic_text_t1_danger + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T1_DANGER + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t1_danger" + } + } + + itemDef + { + name vic_text_t1_rail + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T1_RAIL_01 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t1_rail" + } + } + +//---------------------------------------------------------------------------------------------- +// 2ND TIER +//---------------------------------------------------------------------------------------------- + itemDef + { + name vic_text_t2_rancor + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T2_RANCOR + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t2_rancor" + } + } + + itemDef + { + name vic_text_t2_trip + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T2_TRIP + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t2_trip" + } + } + + itemDef + { + name vic_text_t2_wedge + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T2_WEDGE + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t2_wedge" + } + } + + itemDef + { + name vic_text_t2_rogue + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T2_ROGUE + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t2_rogue" + } + } + + itemDef + { + name vic_text_t2_dpred + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T2_DPRED + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t2_dpred" + } + } + +//---------------------------------------------------------------------------------------------- +// 3RD TIER +//---------------------------------------------------------------------------------------------- + itemDef + { + name vic_text_t2_rift + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T3_RIFT + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t3_rift" + } + } + + itemDef + { + name vic_text_t3_stampede + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T3_STAMP + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t3_stamp" + } + } + + itemDef + { + name vic_text_t3_hevil + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T3_HEVIL + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t3_hevil" + } + } + + itemDef + { + name vic_text_t3_byss + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T3_BYSS + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t3_byss" + } + } + + itemDef + { + name vic_text_t3_bounty + group victorytext + type ITEM_TYPE_TEXT + rect 44 313 304 145 + text @TIERVICTORY_T3_BOUNTY + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + visible 1 + autowrapped + decoration + cvartest "storyhead" + showcvar + { + "t3_bounty" + } + } + + +//---------------------------------------------------------------------------------------------- +// BUTTONS +// +// AFTER THE VICTORY MESSAGE ABOVE, YOU GET A STORY MESSAGE BEFORE CHOOSING NEXT MISSION. +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//TIER ONE - INITIATE TIER +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//The first story message is in ingameGotoTier and not here. +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//completed 1st tier level on Tier 1 +//2nd visit to tier +//---------------------------------------------------------------------------------------------- + itemDef + { + name victory2 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "1" "2" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory2 + hide victorytext + hide models + show story2 + show story2text + playvoice "sound/chars/storyinfo/2.mp3" + show kyle2 + } + } + + itemDef + { + name kyle2 + group models2 + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story2 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect1 + } + } + + itemDef + { + name story2text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER1_2 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +//completed 2nd tier level on Tier 1 +//3rd visit to tier +//---------------------------------------------------------------------------------------------- + itemDef + { + name victory3 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "3" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory3 + hide victorytext + hide models + show story3 + show story3text + playvoice "sound/chars/storyinfo/3.mp3" + show luke3 + } + } + + + itemDef + { + name luke3 + group models2 + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story3 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + uiScript startmap t1_inter + //open ingameMissionSelect1 + } + } + + itemDef + { + name story3text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER1_3 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +//completed 3rd tier level on Tier 1 +//4th visit to tier +//---------------------------------------------------------------------------------------------- + itemDef + { + name victory4 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "4" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory4 + hide victorytext + hide models + show story4 + show story4text + playvoice "sound/chars/storyinfo/4.mp3" + show luke4 + } + } + + + itemDef + { + name luke4 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story4 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect1 + } + } + + itemDef + { + name story4text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER1_4 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +//completed 4th tier level on Tier 1 +//5th visit to tier +//---------------------------------------------------------------------------------------------- + + itemDef + { + name victory5 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "5" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory5 + hide victorytext + hide models + show story5 + show story5text + playvoice "sound/chars/storyinfo/5.mp3" + show kyle5 + show story5_continue + } + } + + + itemDef + { + name kyle5 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story5 + type ITEM_TYPE_BUTTON + rect 494 440 100 24 + text @MENUS_NEXT_MISSION + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect1 + } + } + + itemDef + { + name story5text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER1_5 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + + itemDef + { + name story5_continue + type ITEM_TYPE_BUTTON + rect 394 440 100 24 + text @MENUS_RETURN_TO_ACADEMY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 5 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameForceStatus +// uiScript startmap academy2 + } + } + +//---------------------------------------------------------------------------------------------- +//completed 5th tier level on Tier 1 +//6th visit to tier +//---------------------------------------------------------------------------------------------- + itemDef + { + name victory6 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "6" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory6 + hide victorytext + hide models + show story6_continue + show story6text + playvoice "sound/chars/storyinfo/6.mp3" + show kyle6 + } + } + + + itemDef + { + name kyle6 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story6_continue + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_RETURN_TO_ACADEMY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameForceStatus +// uiScript startmap academy2 + } + } + + itemDef + { + name story6text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER1_6 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + + +//------------------------------------- +// +//TIER TWO - APPRENTICE TIER +// +//------------------------------------- + +//----------------------------------------------------------- +// +//The first story message is in ingameGotoTier and not here. +// +//----------------------------------------------------------- + +//------------------------------------- +// +//completed 1st tier level on Tier 2 +//2nd visit to tier +// +//------------------------------------- + + itemDef + { + name victory8 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "8" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory8 + hide victorytext + hide models + show story8 + show story8text + playvoice "sound/chars/storyinfo/8.mp3" + show prot8 + } + } + + itemDef + { + name prot8 + group models2 + type ITEM_TYPE_MODEL + rect 426 317 116 116 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/protocol/model_menu.skin" + asset_model "models/players/protocol/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 17 + model_g2maxs 60 20 50 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story8 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect2 + } + } + + itemDef + { + name story8text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER2_8 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//------------------------------------- +// +//completed 2nd tier level on Tier 2 +//3rd visit to tier +//------------------------------------- + + itemDef + { + name victory9 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "9" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory9 + hide victorytext + hide models + show story9 + show story9text + playvoice "sound/chars/storyinfo/9.mp3" + show kyle9 + } + } + + + itemDef + { + name kyle9 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story9 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect2 + } + } + + itemDef + { + name story9text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER2_9 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//------------------------------------- +// +//completed 3rd tier level on Tier 2 +//4th visit to tier +//------------------------------------- + + itemDef + { + name victory10 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "10" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory10 + hide victorytext + hide models + show story10 + show story10text + playvoice "sound/chars/storyinfo/10.mp3" + show luke10 + } + } + + + itemDef + { + name luke10 + group models2 + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story10 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect2 + } + } + + itemDef + { + name story10text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER2_10 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//------------------------------------- +// +//completed 4th tier level on Tier 2 +//5th visit to tier +//------------------------------------- + + itemDef + { + name victory11 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "11" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory11 + hide victorytext + hide models + show story11 + show story11text + playvoice "sound/chars/storyinfo/11.mp3" + show kyle11 + show story11_continue + } + } + + + itemDef + { + name kyle11 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story11 + type ITEM_TYPE_BUTTON + rect 494 440 100 24 + text @MENUS_NEXT_MISSION + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect2 + } + } + + itemDef + { + name story11text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER2_11 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + + itemDef + { + name story11_continue + type ITEM_TYPE_BUTTON + rect 394 440 100 24 + text @MENUS_RETURN_TO_ACADEMY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 5 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameForceStatus +// uiScript startmap academy4 + } + } + +//------------------------------------- +// +//completed 5th tier level on Tier 2 +//6th visit to tier +//------------------------------------- + + itemDef + { + name victory12 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "12" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory12 + hide victorytext + hide models + show story12_continue + show story12text + playvoice "sound/chars/storyinfo/12.mp3" + show kyle12 + } + } + + + itemDef + { + name kyle12 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story12_continue + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_RETURN_TO_ACADEMY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameForceStatus +// uiScript startmap academy4 + } + } + + itemDef + { + name story12text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER2_12 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//------------------------------------- +// +//TIER THREE - JEDI KNIGHT TIER +// +//------------------------------------- + +//----------------------------------------------------------- +// +//The first story message is in ingameGotoTier and not here. +// +//----------------------------------------------------------- + +//------------------------------------- +// +//completed 1st tier level on Tier 3 +//2nd visit to tier +// +//------------------------------------- + + itemDef + { + name victory14 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "14" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory14 + hide victorytext + hide models + show story14 + show story14text + playvoice "sound/chars/storyinfo/14.mp3" + show kyle14 + } + } + + itemDef + { + name kyle14 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story14 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect3 + } + } + + itemDef + { + name story14text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER3_14 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//------------------------------------- +// +//completed 2nd tier level on Tier 3 +//3rd visit to tier +//------------------------------------- + + itemDef + { + name victory15 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "15" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory15 + hide victorytext + hide models + show story15 + show story15text + playvoice "sound/chars/storyinfo/15.mp3" + show kyle15 + } + } + + + itemDef + { + name kyle15 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story15 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect3 + } + } + + itemDef + { + name story15text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER3_15 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//------------------------------------- +// +//completed 3rd tier level on Tier 3 +//4th visit to tier +//------------------------------------- + + itemDef + { + name victory16 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "16" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory16 + hide victorytext + hide models + show story16 + show story16text + playvoice "sound/chars/storyinfo/16.mp3" + show kyle16 + } + } + + + itemDef + { + name kyle16 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story16 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_OKAY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect3 + } + } + + itemDef + { + name story16text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER3_16 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//------------------------------------- +// +//completed 4th tier level on Tier 3 +//5th visit to tier +//------------------------------------- + + itemDef + { + name victory17 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "17" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory17 + hide victorytext + hide models + show story17 + show story17text + playvoice "sound/chars/storyinfo/17.mp3" + show kyle17 + show story17_continue + } + } + + + itemDef + { + name kyle17 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story17 + type ITEM_TYPE_BUTTON + rect 494 440 100 24 + text @MENUS_NEXT_MISSION + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 100 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameMissionSelect3 + } + } + + itemDef + { + name story17text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER3_17 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + + itemDef + { + name story17_continue + type ITEM_TYPE_BUTTON + rect 394 440 100 24 + text @MENUS_RETURN_TO_ACADEMY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 5 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameForceStatus +// uiScript startmap academy6 + } + } + +//------------------------------------- +// +//completed 5th tier level on Tier 3 +//6th visit to tier +//------------------------------------- + + itemDef + { + name victory18 + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_CONTINUE + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "18" + } + mouseEnter + { + } + mouseExit + { + } + action + { + hide victory18 + hide victorytext + hide models + show story18_continue + show story18text + playvoice "sound/chars/storyinfo/18.mp3" + show kyle18 + } + } + + + itemDef + { + name kyle18 + group models2 + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name story18_continue + type ITEM_TYPE_BUTTON + rect 394 440 200 24 + text @MENUS_RETURN_TO_ACADEMY + font 2 + forecolor 1 1 1 1 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 200 + textaligny -1 + visible 0 + mouseEnter + { + } + mouseExit + { + } + action + { + close all + open ingameForceStatus +// uiScript startmap academy6 + } + } + + itemDef + { + name story18text + type ITEM_TYPE_TEXTSCROLL + rect 44 313 304 145 + text @TIER3_18 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 2 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +// +// SCANLINES OVER WHOLE MENU +// +//---------------------------------------------------------------------------------------------- + + itemDef + { + name static + group none + style WINDOW_STYLE_SHADER + rect 396 314 175 120 + background "gfx/menus/static" + backcolor 1 0 0 .2 + forecolor 1 0 0 .2 + visible 0 + decoration + cvartest "storyhead" + showcvar + { + "t2_trip" + } + } + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + + diff --git a/base/ui/ingameMissionSelect1.menu b/base/ui/ingameMissionSelect1.menu new file mode 100644 index 0000000..8e2ec51 --- /dev/null +++ b/base/ui/ingameMissionSelect1.menu @@ -0,0 +1,1276 @@ +//------------------------------------------------------------------------------------------------ +// Mission Select 1 Menu +// +// Select missions from the 1st tier. +// +//------------------------------------------------------------------------------------------------ +{ + menuDef + { + name "ingameMissionSelect1" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 200 + descY 444 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 25 // how often fade happens in milliseconds + fadeAmount 0.05 // amount to adjust alpha per cycle + onOpen + { + show mission_button + stopVoice + } + onESC + { + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/mission_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name eyecandy1 + group none + style WINDOW_STYLE_SHADER + rect 17 309 357 153 + background "gfx/menus/mission_bottomleft_grid" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name botlf + group none + style WINDOW_STYLE_SHADER + rect 0 300 80 180 + background "gfx/menus/mission_bottom_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starfield + group none + style WINDOW_STYLE_FILLED + rect 20 20 600 256 + background "gfx/menus/star_field" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name stars_close + group none + style WINDOW_STYLE_FILLED + rect 20 20 600 256 + background "gfx/menus/star_field_zoom" + backcolor 1 1 1 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// BIG PLANET GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name tatooine + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/tatooine" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name bakura + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/bakura" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name blenjeel + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/blenjeel" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name corellia + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/core" + backcolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// GALAXY MAP +//---------------------------------------------------------------------------------------------- + itemDef + { + name galaxy + group none + style WINDOW_STYLE_FILLED + rect 64 20 512 256 + background "gfx/menus/mission_galaxy" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name map_title + type ITEM_TYPE_TEXT + rect 40 14 600 18 + text @MENUS_GALACTIC_MAP + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 300 + textaligny -1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TIER 1 LOCATION MARKERS ON GALAXY MAP +//---------------------------------------------------------------------------------------------- + itemDef + { + name loc_but01_off + group location_marker_off + style WINDOW_STYLE_SHADER + rect 239 188 32 32 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_sour" + } + cvarSubString + } + + itemDef + { + name loc_but01_on + group location_marker_on + style WINDOW_STYLE_SHADER + rect 239 188 32 32 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_sour" + } + cvarSubString + } + + itemDef + { + name loc_but02_off + group location_marker_off + style WINDOW_STYLE_SHADER + rect 239 188 32 32 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_surprise" + } + cvarSubString + } + + itemDef + { + name loc_but02_on + group location_marker_on + style WINDOW_STYLE_SHADER + rect 239 188 32 32 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_surprise" + } + cvarSubString + } + + itemDef + { + name loc_but03_off + group location_marker_off + style WINDOW_STYLE_SHADER + rect 216 122 20 20 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_fatal" + } + cvarSubString + } + + itemDef + { + name loc_but03_on + group location_marker_on + style WINDOW_STYLE_SHADER + rect 216 122 20 20 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_fatal" + } + cvarSubString + } + + itemDef + { + name loc_but04_off + group location_marker_off + style WINDOW_STYLE_SHADER + rect 301 161 32 32 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_danger" + } + cvarSubString + } + + itemDef + { + name loc_but04_on + group location_marker_on + style WINDOW_STYLE_SHADER + rect 301 161 32 32 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_danger" + } + cvarSubString + } + + itemDef + { + name loc_but05_off + group location_marker_off + style WINDOW_STYLE_SHADER + rect 320 128 28 28 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_rail" + } + cvarSubString + } + + itemDef + { + name loc_but05_on + group location_marker_on + style WINDOW_STYLE_SHADER + rect 320 128 28 28 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t1_rail" + } + cvarSubString + } + + itemDef + { + name planet_name + type ITEM_TYPE_TEXT + rect 52 316 288 24 + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + autowrapped + decoration + } + +//---------------------------------------------------------------------------------------------- +// TIER 1 MISSION BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name map_title + type ITEM_TYPE_TEXT + rect 10 305 400 20 + text @MENUS_MISSIONS + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny -1 + visible 1 + } + + itemDef + { + name miss1 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 330 224 20 + text @MENUS_T1_SOUR_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t1_sour" + } + cvarSubString + + mouseEnter + { + hide loc_but01_off + show loc_but01_on + + show planet_name + setitemtext planet_name @MENUS_TATOOINE + setitemrect planet_name 150 230 120 26 + + show button_glow + setitemrect button_glow 20 328 380 22 + + } + + mouseExit + { + hide loc_but01_on + show loc_but01_off + + hide planet_name + hide button_glow + } + + action + { + play "sound/weapons/force/protect.wav" + + fadeout starfield + fadeout galaxy + + fadein stars_close + + transition2 loc_but01_off 255 204 0 0 20 25 + transition2 loc_but02_off 255 204 0 0 20 25 + transition2 loc_but03_off 226 132 0 0 20 25 + transition2 loc_but04_off 317 177 0 0 20 25 + transition2 loc_but05_off 334 142 0 0 20 25 + + transition2 planet_name 270 250 256 20 20 15 + + transition2 tatooine 192 20 256 256 10 15 + + hide mission_button + hide location_marker_on + + show backbut + show accbut1 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T1_SOUR + + hide button_glow + hide map_title + } + } + + itemDef + { + name miss2 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 353 224 20 + text @MENUS_T1_SURPRISE_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t1_surprise" + } + cvarSubString + + mouseEnter + { + hide loc_but02_off + show loc_but02_on + + show planet_name + setitemtext planet_name @MENUS_TATOOINE + setitemrect planet_name 150 230 120 26 + + show button_glow + setitemrect button_glow 20 351 380 22 + } + + mouseExit + { + hide loc_but02_on + show loc_but02_off + + hide planet_name + hide button_glow + } + + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + + transition2 loc_but01_off 255 204 0 0 20 25 + transition2 loc_but02_off 255 204 0 0 20 25 + transition2 loc_but03_off 226 132 0 0 20 25 + transition2 loc_but04_off 317 177 0 0 20 25 + transition2 loc_but05_off 334 142 0 0 20 25 + + transition2 tatooine 192 20 256 256 10 15 + + transition2 planet_name 270 250 256 20 20 15 + hide mission_button + + hide location_marker_on + + show backbut + show accbut2 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T1_SURPRISE + + hide button_glow + hide map_title + } + } + + itemDef + { + name miss3 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 376 224 20 + text @MENUS_T1_FATAL_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t1_fatal" + } + cvarSubString + + mouseEnter + { + hide loc_but03_off + show loc_but03_on + + show planet_name + setitemtext planet_name @MENUS_BAKURA + setitemrect planet_name 120 120 120 26 + + show button_glow + setitemrect button_glow 20 374 380 22 + } + mouseExit + { + hide loc_but03_on + show loc_but03_off + + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 255 204 0 0 20 25 + transition2 loc_but02_off 255 204 0 0 20 25 + transition2 loc_but03_off 226 132 0 0 20 25 + transition2 loc_but04_off 317 177 0 0 20 25 + transition2 loc_but05_off 334 142 0 0 20 25 + transition2 bakura 192 20 256 256 10 15 + + transition2 planet_name 270 250 256 20 20 15 + + hide mission_button + hide location_marker_on + show backbut + show accbut3 + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T1_FATAL + hide button_glow + hide map_title + } + } + + itemDef + { + name miss4 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 399 224 20 + text @MENUS_T1_DANGER_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t1_danger" + } + cvarSubString + + mouseEnter + { + hide loc_but04_off + show loc_but04_on + + show planet_name + setitemtext planet_name @MENUS_BLENJEEL + setitemrect planet_name 300 200 120 26 + + show button_glow + setitemrect button_glow 20 397 380 22 + } + mouseExit + { + hide loc_but04_on + show loc_but04_off + + hide planet_name + hide button_glow + } + + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 255 204 0 0 20 25 + transition2 loc_but02_off 255 204 0 0 20 25 + transition2 loc_but03_off 226 132 0 0 20 25 + transition2 loc_but04_off 317 177 0 0 20 25 + transition2 loc_but05_off 334 142 0 0 20 25 + transition2 blenjeel 192 20 256 256 10 15 + + transition2 planet_name 270 250 256 20 20 15 + + hide mission_button + + hide location_marker_on + show backbut + show accbut4 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T1_DANGER + + hide button_glow + hide map_title + } + } + + itemDef + { + name miss5 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 422 224 20 + text @MENUS_T1_RAIL_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t1_rail" + } + cvarSubString + + mouseEnter + { + hide loc_but05_off + show loc_but05_on + + show planet_name + setitemtext planet_name @MENUS_CORELLIA + setitemrect planet_name 380 120 120 26 + + show button_glow + setitemrect button_glow 20 420 380 22 + } + mouseExit + { + hide loc_but05_on + show loc_but05_off + + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 255 204 0 0 20 25 + transition2 loc_but02_off 255 204 0 0 20 25 + transition2 loc_but03_off 226 132 0 0 20 25 + transition2 loc_but04_off 317 177 0 0 20 25 + transition2 loc_but05_off 334 142 0 0 20 25 + transition2 corellia 192 20 256 256 10 15 + + transition2 planet_name 270 250 256 20 20 15 + hide mission_button + + hide location_marker_on + show backbut + show accbut5 + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T1_RAIL + hide button_glow + hide map_title + } + } + +//---------------------------------------------------------------------------------------------- +// MISSION BRIEF TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name briefing_background + group none + style WINDOW_STYLE_FILLED + rect 42 314 320 114 + backcolor 0 0 .35 .7 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 0 + decoration + } + + itemDef + { + name briefing_text + type ITEM_TYPE_TEXTSCROLL + rect 42 314 320 114 + text @BRIEFINGS_T1_SOUR + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +// GENERAL BACK BUTTON - if you don't want this mission +//---------------------------------------------------------------------------------------------- + itemDef + { + name backbut + type ITEM_TYPE_BUTTON + rect 52 430 172 24 + text @MENUS_BACK_CAPS + desctext @MENUS_DIFF_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 15 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/esc.wav" + fadein starfield + fadein galaxy + fadeout stars_close + + transition2 loc_but01_off 239 188 32 32 20 25 + transition2 loc_but02_off 239 188 32 32 20 25 + transition2 loc_but03_off 216 122 20 20 20 25 + transition2 loc_but04_off 301 161 32 32 20 25 + transition2 loc_but05_off 320 128 28 28 20 25 + + hide backbut + + hide planet_name + + hide accbuttons + + hide briefing_text + hide tatooine + hide bakura + hide blenjeel + hide corellia + hide briefing_background + + hide button_glow + show mission_button + show map_title + } + } + +//---------------------------------------------------------------------------------------------- +// ACCEPT BUTTONS - IF YOU CHOOSE A PARTICULAR MISSION +//---------------------------------------------------------------------------------------------- + itemDef + { + name accbut1 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + + action + { + hide backbut + hide briefing_text + show kyle + show nextscreen_button + show accept1text + playVoice "sound/chars/kyle/07kyk001.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t1_sour" + hide button_glow + hide briefing_background + } + } + + itemDef + { + name accbut2 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide backbut + hide briefing_text + show kyle + show nextscreen_button + show accept2text + playVoice "sound/chars/kyle/04kyk001.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t1_surprise" + hide button_glow + hide briefing_background + } + } + + itemDef + { + name accbut3 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide backbut + hide briefing_text + show luke + show nextscreen_button + show accept3text + playVoice "sound/chars/luke/08luk001.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t1_fatal" + hide button_glow + hide briefing_background + } + } + + itemDef + { + name accbut4 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide backbut + hide briefing_text + show luke + show nextscreen_button + show accept4text + playVoice "sound/chars/luke/06luk001.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t1_danger" + hide button_glow + hide briefing_background + } + } + + itemDef + { + name accbut5 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide backbut + hide briefing_text + show kyle + show nextscreen_button + show accept5text + playVoice "sound/chars/kyle/05kyk001.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t1_rail" + hide button_glow + hide briefing_background + } + } + +//---------------------------------------------------------------------------------------------- +// MODELS FOR V-O +//---------------------------------------------------------------------------------------------- + itemDef + { + name luke + group models + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name kyle + group models + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name prot + group models + type ITEM_TYPE_MODEL + rect 426 317 116 116 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/protocol/model_menu.skin" + asset_model "models/players/protocol/model.glm" + model_angle 180 + model_g2mins 15 -20 17 + model_g2maxs 60 20 50 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// BRIEFING SUBTITLES +//---------------------------------------------------------------------------------------------- + itemDef + { + name accept1text + type ITEM_TYPE_TEXTSCROLL + rect 30 316 320 130 + text @T1_SOUR_07KYK001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + + itemDef + { + name accept2text + type ITEM_TYPE_TEXT + rect 30 316 320 130 + text @T1_SURPRISE_04KYK001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + autowrapped + } + + itemDef + { + name accept3text + type ITEM_TYPE_TEXT + rect 30 316 320 130 + text @T1_FATAL_08LUK001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + autowrapped + } + + itemDef + { + name accept4text + type ITEM_TYPE_TEXT + rect 30 316 320 130 + text @T1_DANGER_06LUK001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + autowrapped + } + + itemDef + { + name accept5text + type ITEM_TYPE_TEXT + rect 30 316 320 130 + text @T1_RAIL_05KYK001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +// NEXT BUTTONS - to go to next screen +//---------------------------------------------------------------------------------------------- + itemDef + { + name nextscreen_button + type ITEM_TYPE_BUTTON + rect 530 440 172 24 + text @MENUS_NEXT + desctext @MENUS_ADVANCE_NEXT + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + close all + open ingameForceSelect + } + } + +//---------------------------------------------------------------------------------------------- +// SCANLINES OVER WHOLE MENU +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + + diff --git a/base/ui/ingameMissionSelect2.menu b/base/ui/ingameMissionSelect2.menu new file mode 100644 index 0000000..d40633c --- /dev/null +++ b/base/ui/ingameMissionSelect2.menu @@ -0,0 +1,1334 @@ +//------------------------------------------------------------------------------------------------ +// Mission Select 2 Menu +// +// Select missions from the 2nd tier. +// +//------------------------------------------------------------------------------------------------ +{ + menuDef + { + name "ingameMissionSelect2" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 200 + descY 444 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 25 // how often fade happens in milliseconds + fadeAmount 0.05 // amount to adjust alpha per cycle + onOpen + { + show mission_button + stopVoice + } + onESC + { + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/mission_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name eyecandy1 + group none + style WINDOW_STYLE_SHADER + rect 17 309 357 153 + background "gfx/menus/mission_bottomleft_grid" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name botlf + group none + style WINDOW_STYLE_SHADER + rect 0 300 80 180 + background "gfx/menus/mission_bottom_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starfield + group none + style WINDOW_STYLE_FILLED + rect 20 20 600 256 + background "gfx/menus/star_field" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name stars_close + group none + style WINDOW_STYLE_FILLED + //rect 320 148 0 0 + rect 20 20 600 256 + background "gfx/menus/star_field_zoom" + backcolor 1 1 1 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// BIG PLANET GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name krildor + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/krildor" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name narkreeta + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/narkreeta" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name dosuun + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/dosuun" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name zonju + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/zonju" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name coruscant + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/coruscant" + backcolor 1 1 1 1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// GALAXY MAP +//---------------------------------------------------------------------------------------------- + itemDef + { + name galaxy + group none + style WINDOW_STYLE_FILLED + rect 64 20 512 256 + background "gfx/menus/mission_galaxy" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name map_title + type ITEM_TYPE_TEXT + rect 40 14 600 18 + text @MENUS_GALACTIC_MAP + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 300 + textaligny -1 + visible 1 + } + +//---------------------------------------------------------------------------------------------- +// TIER 2 LOCATION MARKERS ON GALAXY MAP +//---------------------------------------------------------------------------------------------- + itemDef + { + name loc_but01_off + group none + style WINDOW_STYLE_SHADER + rect 324 158 32 32 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t2_rancor" + } + cvarSubString + } + + itemDef + { + name loc_but01_on + group none + style WINDOW_STYLE_SHADER + rect 324 158 32 32 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t2_rancor" + } + cvarSubString + } + + itemDef + { + name loc_but02_off + group none + style WINDOW_STYLE_SHADER + rect 154 208 32 32 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t2_trip" + } + cvarSubString + } + + itemDef + { + name loc_but02_on + group none + style WINDOW_STYLE_SHADER + rect 154 208 32 32 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t2_trip" + } + cvarSubString + } + + itemDef + { + name loc_but03_off + group none + style WINDOW_STYLE_SHADER + rect 260 104 20 20 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t2_wedge" + } + cvarSubString + } + + itemDef + { + name loc_but03_on + group none + style WINDOW_STYLE_SHADER + rect 260 104 20 20 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t2_wedge" + } + cvarSubString + } + + itemDef + { + name loc_but04_off + group none + style WINDOW_STYLE_SHADER + rect 335 91 26 26 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t2_rogue" + } + cvarSubString + } + + itemDef + { + name loc_but04_on + group none + style WINDOW_STYLE_SHADER + rect 335 91 26 26 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t2_rogue" + } + cvarSubString + } + + itemDef + { + name loc_but05_off + group none + style WINDOW_STYLE_SHADER + rect 150 150 28 28 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t2_dpred" + } + cvarSubString + } + + itemDef + { + name loc_but05_on + group none + style WINDOW_STYLE_SHADER + rect 150 150 28 28 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t2_dpred" + } + cvarSubString + } + + itemDef + { + name planet_name + type ITEM_TYPE_TEXT + rect 52 316 288 24 + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + autowrapped + decoration + } + +//---------------------------------------------------------------------------------------------- +// MODELS FOR V-O +//---------------------------------------------------------------------------------------------- + itemDef + { + name luke + group models + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name kyle + group models + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name prot + group models + type ITEM_TYPE_MODEL + rect 426 317 116 116 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/protocol/model_menu.skin" + asset_model "models/players/protocol/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 17 + model_g2maxs 60 20 50 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name wedge + group models + type ITEM_TYPE_MODEL + rect 426 317 116 116 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/rebel_pilot/model_default.skin" + asset_model "models/players/rebel_pilot/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 50 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } +//---------------------------------------------------------------------------------------------- +// BUTTONS +//---------------------------------------------------------------------------------------------- + // GENERAL BACK BUTTON - if you don't want mission + itemDef + { + name backbut + type ITEM_TYPE_BUTTON + rect 52 430 172 24 + text @MENUS_BACK_CAPS + desctext @MENUS_DIFF_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 15 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/esc.wav" + fadein starfield + fadein galaxy + fadeout stars_close + transition2 loc_but01_off 324 158 32 32 20 25 + transition2 loc_but02_off 154 208 32 32 20 25 + transition2 loc_but03_off 260 104 20 20 20 25 + transition2 loc_but04_off 335 91 26 26 20 25 + transition2 loc_but05_off 150 150 28 28 20 25 + hide backbut + show mission_button + hide accbut1 + hide accbut2 + hide accbut3 + hide accbut4 + hide accbut5 + + hide krildor + hide narkreeta + hide dosuun + hide zonju + hide coruscant + + hide planet_name + show map_title + + hide briefing_background + hide briefing_text + + hide button_glow + } + } + +//---------------------------------------------------------------------------------------------- +// TIER 1 MISSION BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name map_title + type ITEM_TYPE_TEXT + rect 10 305 400 20 + text @MENUS_MISSIONS + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny -1 + visible 1 + } + + itemDef + { + name miss1 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 330 224 20 + text @MENUS_T2_RANCOR_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t2_rancor" + } + cvarSubString + + mouseEnter + { + hide loc_but01_off + show loc_but01_on + + show planet_name + setitemtext planet_name @MENUS_NAR_KREETA + setitemrect planet_name 300 190 120 26 + + show button_glow + setitemrect button_glow 20 328 380 22 + } + mouseExit + { + hide loc_but01_on + show loc_but01_off + + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 340 174 0 0 20 25 + transition2 loc_but02_off 170 224 0 0 20 25 + transition2 loc_but03_off 270 114 0 0 20 25 + transition2 loc_but04_off 348 104 0 0 20 25 + transition2 loc_but05_off 164 164 0 0 20 25 + transition2 narkreeta 192 20 256 256 10 15 + hide mission_button + hide loc_but01_on + hide loc_but02_on + hide loc_but03_on + hide loc_but04_on + hide loc_but05_on + show backbut + show accbut1 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T2_RANCOR + + transition2 planet_name 270 250 256 20 20 15 + hide map_title + hide button_glow + } + } + + itemDef + { + name miss2 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 353 224 20 + text @MENUS_T2_TRIP_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t2_trip" + } + cvarSubString + + mouseEnter + { + hide loc_but02_off + show loc_but02_on + + show planet_name + setitemtext planet_name @MENUS_ZONJU_V + setitemrect planet_name 70 210 120 26 + + show button_glow + setitemrect button_glow 20 351 380 22 + } + mouseExit + { + hide loc_but02_on + show loc_but02_off + + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 340 174 0 0 20 25 + transition2 loc_but02_off 170 224 0 0 20 25 + transition2 loc_but03_off 270 114 0 0 20 25 + transition2 loc_but04_off 348 104 0 0 20 25 + transition2 loc_but05_off 164 164 0 0 20 25 + transition2 zonju 192 20 256 256 10 15 + hide mission_button + hide loc_but01_on + hide loc_but02_on + hide loc_but03_on + hide loc_but04_on + hide loc_but05_on + show backbut + show accbut2 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T2_TRIP + + transition2 planet_name 270 250 256 20 20 15 + hide map_title + hide button_glow + } + } + + itemDef + { + name miss3 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 376 224 20 + text @MENUS_T2_WEDGE_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t2_wedge" + } + cvarSubString + + mouseEnter + { + hide loc_but03_off + show loc_but03_on + + show planet_name + setitemtext planet_name @MENUS_KRIL_DOR + setitemrect planet_name 190 80 120 26 + + show button_glow + setitemrect button_glow 20 374 380 22 + } + mouseExit + { + hide loc_but03_on + show loc_but03_off + + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 340 174 0 0 20 25 + transition2 loc_but02_off 170 224 0 0 20 25 + transition2 loc_but03_off 270 114 0 0 20 25 + transition2 loc_but04_off 348 104 0 0 20 25 + transition2 loc_but05_off 164 164 0 0 20 25 + transition2 krildor 192 20 256 256 10 15 + hide mission_button + hide loc_but01_on + hide loc_but02_on + hide loc_but03_on + hide loc_but04_on + hide loc_but05_on + show backbut + show accbut3 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T2_WEDGE + + transition2 planet_name 270 250 256 20 20 15 + hide map_title + hide button_glow + } + } + + itemDef + { + name miss4 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 399 224 20 + text @MENUS_T2_ROGUE_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t2_rogue" + } + cvarSubString + + mouseEnter + { + hide loc_but04_off + show loc_but04_on + + show planet_name + setitemtext planet_name @MENUS_CORUSCANT + setitemrect planet_name 380 90 120 26 + + show button_glow + setitemrect button_glow 20 397 380 22 + } + mouseExit + { + hide loc_but04_on + show loc_but04_off + + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 340 174 0 0 20 25 + transition2 loc_but02_off 170 224 0 0 20 25 + transition2 loc_but03_off 270 114 0 0 20 25 + transition2 loc_but04_off 348 104 0 0 20 25 + transition2 loc_but05_off 164 164 0 0 20 25 + transition2 coruscant 192 20 256 256 10 15 + hide mission_button + hide loc_but01_on + hide loc_but02_on + hide loc_but03_on + hide loc_but04_on + hide loc_but05_on + show backbut + show accbut4 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T2_ROGUE + + transition2 planet_name 270 250 256 20 20 15 + hide map_title + hide button_glow + } + } + + itemDef + { + name miss5 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 422 224 20 + text @MENUS_T2_DPRED_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t2_dpred" + } + cvarSubString + + mouseEnter + { + hide loc_but05_off + show loc_but05_on + + show planet_name + setitemtext planet_name @MENUS_DOSUUN + setitemrect planet_name 60 150 120 26 + + show button_glow + setitemrect button_glow 20 420 380 22 + } + mouseExit + { + hide loc_but05_on + show loc_but05_off + + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 340 174 0 0 20 25 + transition2 loc_but02_off 170 224 0 0 20 25 + transition2 loc_but03_off 270 114 0 0 20 25 + transition2 loc_but04_off 348 104 0 0 20 25 + transition2 loc_but05_off 164 164 0 0 20 25 + transition2 dosuun 192 20 256 256 10 15 + hide mission_button + hide loc_but01_on + hide loc_but02_on + hide loc_but03_on + hide loc_but04_on + hide loc_but05_on + show backbut + show accbut5 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T2_DPRED + + transition2 planet_name 270 250 256 20 20 15 + hide map_title + hide button_glow + } + } + + +//---------------------------------------------------------------------------------------------- +// MISSION BRIEF TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name briefing_background + group none + style WINDOW_STYLE_FILLED + rect 42 314 320 114 + backcolor 0 0 .35 .7 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 0 + decoration + } + + itemDef + { + name briefing_text + type ITEM_TYPE_TEXTSCROLL + rect 42 314 320 114 + text @BRIEFINGS_T1_SOUR + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +// ACCEPT BUTTONS - IF YOU CHOOSE A PARTICULAR MISSION +//---------------------------------------------------------------------------------------------- + itemDef + { + name accbut1 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide backbut + show prot + show nextscreen_button + show accept1text + playVoice "sound/chars/protocol/12pro001.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t2_rancor" + hide briefing_background + hide briefing_text + hide button_glow + } + } + + itemDef + { + name accbut2 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide backbut + show prot + show nextscreen_button + show accept2text + playVoice "sound/chars/protocol/15pro001.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t2_trip" + hide briefing_background + hide briefing_text + hide button_glow + } + } + + itemDef + { + name accbut3 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide backbut + show wedge + show nextscreen_button + show accept3text + playVoice "sound/chars/wedge/13wea001.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t2_wedge" + hide briefing_background + hide briefing_text + hide button_glow + } + } + + itemDef + { + name accbut4 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide backbut + + show nextscreen_button + show accept4text + + show kyle + playVoice "sound/chars/kyle/14kyk001.mp3" + + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t2_rogue" + hide briefing_background + hide briefing_text + hide button_glow + } + } + + itemDef + { + name accbut5 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide backbut + show prot + show nextscreen_button + show accept5text + playVoice "sound/chars/protocol/16pro001.mp3" + hide accbuttons + setfocus nextscreen_button + setcvar tier_mapname "maptransition t2_dpred" + hide briefing_background + hide briefing_text + hide button_glow + } + } + +//---------------------------------------------------------------------------------------------- +// BRIEFING SUBTITLES +//---------------------------------------------------------------------------------------------- + itemDef + { + name accept1text + type ITEM_TYPE_TEXTSCROLL + rect 30 316 320 130 + text @T2_RANCOR_12PRO001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + + itemDef + { + name accept2text + type ITEM_TYPE_TEXTSCROLL + rect 30 316 320 130 + text @T2_TRIP_15PRO001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + + itemDef + { + name accept3text + type ITEM_TYPE_TEXTSCROLL + rect 30 316 320 130 + text @T2_WEDGE_13WEA001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + + itemDef + { + name accept4text + type ITEM_TYPE_TEXTSCROLL + rect 30 316 320 130 + text @T2_ROGUE_14KYK001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + + itemDef + { + name accept5text + type ITEM_TYPE_TEXTSCROLL + rect 30 316 320 130 + text @T2_DPRED_16PRO001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +// NEXT BUTTONS - to go to next screen +//---------------------------------------------------------------------------------------------- + itemDef + { + name nextscreen_button + type ITEM_TYPE_BUTTON + rect 530 440 172 24 + text @MENUS_NEXT + desctext @MENUS_ADVANCE_NEXT + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + close all + open ingameForceSelect + } + } + +//---------------------------------------------------------------------------------------------- +// SCANLINES OVER WHOLE MENU +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name static + group none + style WINDOW_STYLE_SHADER + rect 396 314 175 120 + background "gfx/menus/static" + backcolor 1 0 0 .2 + forecolor 1 0 0 .2 + visible 0 + decoration + } + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + + diff --git a/base/ui/ingameMissionSelect3.menu b/base/ui/ingameMissionSelect3.menu new file mode 100644 index 0000000..3d9e21e --- /dev/null +++ b/base/ui/ingameMissionSelect3.menu @@ -0,0 +1,1311 @@ +//------------------------------------------------------------------------------------------------ +// Mission Select 3 Menu +// +// Select missions from the 3rd tier. +// +//------------------------------------------------------------------------------------------------ +{ + menuDef + { + name "ingameMissionSelect3" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 200 + descY 444 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 25 // how often fade happens in milliseconds + fadeAmount 0.05 // amount to adjust alpha per cycle + onOpen + { + show mission_button + stopVoice + } + onESC + { + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/mission_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name eyecandy1 + group none + style WINDOW_STYLE_SHADER + rect 17 309 357 153 + background "gfx/menus/mission_bottomleft_grid" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name botlf + group none + style WINDOW_STYLE_SHADER + rect 0 300 80 180 + background "gfx/menus/mission_bottom_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starfield + group none + style WINDOW_STYLE_FILLED + rect 20 20 600 256 + background "gfx/menus/star_field" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name stars_close + group none + style WINDOW_STYLE_FILLED + rect 20 20 600 256 + background "gfx/menus/star_field_zoom" + backcolor 1 1 1 0 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// BIG PLANET GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name big_planet + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/yalara" + backcolor 1 1 1 1 + visible 1 + decoration + } + + + itemDef + { + name yalara + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/yalara" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name chandrila + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/chandrila" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name ordmantell + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/ordman" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name tanaab + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/tanaab" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name byss + group none + style WINDOW_STYLE_FILLED + rect 320 148 0 0 + background "gfx/menus/planets/byss" + backcolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// GALAXY MAP +//---------------------------------------------------------------------------------------------- + itemDef + { + name galaxy + group none + style WINDOW_STYLE_FILLED + rect 64 20 512 256 + background "gfx/menus/mission_galaxy" + backcolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name map_title + type ITEM_TYPE_TEXT + rect 40 14 600 18 + text @MENUS_GALACTIC_MAP + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 300 + textaligny -1 + visible 1 + } + +//---------------------------------------------------------------------------------------------- +// TIER 3 LOCATION MARKERS ON GALAXY MAP +//---------------------------------------------------------------------------------------------- + itemDef + { + name loc_but01_off + group location_marker + style WINDOW_STYLE_SHADER + rect 270 109 20 20 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_rift" + } + cvarSubString + } + + itemDef + { + name loc_but01_on + group location_marker + style WINDOW_STYLE_SHADER + rect 270 109 20 20 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_rift" + } + cvarSubString + } + + itemDef + { + name loc_but02_off + group location_marker + style WINDOW_STYLE_SHADER + rect 380 122 30 30 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_stamp" + } + cvarSubString + } + + itemDef + { + name loc_but02_on + group location_marker + style WINDOW_STYLE_SHADER + rect 380 122 30 30 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_stamp" + } + cvarSubString + } + + itemDef + { + name loc_but03_off + group location_marker + style WINDOW_STYLE_SHADER + rect 170 152 23 23 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_hevil" + } + cvarSubString + } + + itemDef + { + name loc_but03_on + group location_marker + style WINDOW_STYLE_SHADER + rect 170 152 23 23 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_hevil" + } + cvarSubString + } + + itemDef + { + name loc_but04_off + group location_marker + style WINDOW_STYLE_SHADER + rect 295 104 20 20 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_byss" + } + cvarSubString + } + + itemDef + { + name loc_but04_on + group location_marker + style WINDOW_STYLE_SHADER + rect 295 104 20 20 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_byss" + } + cvarSubString + } + + itemDef + { + name loc_but05_off + group location_marker + style WINDOW_STYLE_SHADER + rect 380 70 18 18 + background "gfx/menus/mission_loc_but_off" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_bounty" + } + cvarSubString + } + + itemDef + { + name loc_but05_on + group location_marker + style WINDOW_STYLE_SHADER + rect 380 70 18 18 + background "gfx/menus/mission_loc_but_on" + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "tiers_complete" + disablecvar + { + "t3_bounty" + } + cvarSubString + } + + itemDef + { + name planet_name + type ITEM_TYPE_TEXT + rect 52 316 288 24 + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + autowrapped + decoration + } + +//---------------------------------------------------------------------------------------------- +// MODELS FOR V-O +//---------------------------------------------------------------------------------------------- + itemDef + { + name luke + group models + type ITEM_TYPE_MODEL + rect 415 285 149 149 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/luke/model_menu.skin" + asset_model "models/players/luke/model.glm" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name kyle + group models + type ITEM_TYPE_MODEL + rect 415 288 145 145 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/kyle/model_menu.skin" + asset_model "models/players/kyle/model.glm" + model_angle 180 + model_g2mins 15 -20 18 + model_g2maxs 60 20 55 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + + itemDef + { + name prot + group models + type ITEM_TYPE_MODEL + rect 426 317 116 116 + model_g2anim "BOTH_STAND1_TALK2" + model_g2skin "models/players/protocol/model_menu.skin" + asset_model "models/players/protocol/model.glm" + model_angle 180 + model_g2mins 15 -20 17 + model_g2maxs 60 20 50 + model_rotation 0 + model_fovx 10 + model_fovy 10 + isCharacter 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// BUTTONS +//---------------------------------------------------------------------------------------------- + //GENERAL BACK BUTTON - if you don't want mission + itemDef + { + name backbut + type ITEM_TYPE_BUTTON + rect 52 430 172 24 + text @MENUS_BACK_CAPS + desctext @MENUS_DIFF_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 15 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/esc.wav" + fadein starfield + fadein galaxy + fadeout stars_close + transition2 loc_but01_off 270 109 20 20 20 25 + transition2 loc_but02_off 380 122 30 30 20 25 + transition2 loc_but03_off 170 152 23 23 20 25 + transition2 loc_but04_off 295 104 20 20 20 25 + transition2 loc_but05_off 380 70 18 18 20 25 + hide backbut + + hide planet_name + + show mission_button + + hide accbuttons + + hide briefing_text + + hide big_planet + + hide planet_name + show map_title + + hide briefing_background + hide briefing_text + + hide button_glow + } + } + +//---------------------------------------------------------------------------------------------- +// TIER 3 MISSION BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name map_title + type ITEM_TYPE_TEXT + rect 10 305 400 20 + text @MENUS_MISSIONS + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny -1 + visible 1 + } + + itemDef + { + name miss1 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 330 224 20 + text @MENUS_T3_RIFT_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t3_rift" + } + cvarSubString + mouseEnter + { + hide loc_but01_off + show loc_but01_on + + show planet_name + setitemtext planet_name @MENUS_CHANDRILA + setitemrect planet_name 150 90 120 26 + + show button_glow + setitemrect button_glow 20 328 380 22 + } + mouseExit + { + hide loc_but01_on + show loc_but01_off + + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + + fadeout starfield + fadeout galaxy + + fadein stars_close + + transition2 loc_but01_off 280 119 0 0 20 25 + transition2 loc_but02_off 395 137 0 0 20 25 + transition2 loc_but03_off 181 163 0 0 20 25 + transition2 loc_but04_off 305 114 0 0 20 25 + transition2 loc_but05_off 389 79 0 0 20 25 + + hide loc_but01_on + hide loc_but02_on + hide loc_but03_on + hide loc_but04_on + hide loc_but05_on + + setitembackground big_planet "gfx/menus/planets/chandrila" + transition2 big_planet 192 20 256 256 10 15 + + transition2 planet_name 270 250 256 256 20 15 + + hide mission_button + + show backbut + show accbut1 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T3_RIFT + + hide map_title + hide button_glow + + } + } + + itemDef + { + name miss2 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 353 224 20 + text @MENUS_T3_STAMPEDE_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t3_stamp" + } + cvarSubString + mouseEnter + { + hide loc_but02_off + show loc_but02_on + + show planet_name + setitemtext planet_name @MENUS_TANAAB + setitemrect planet_name 420 180 120 26 + + show button_glow + setitemrect button_glow 20 351 380 22 + } + mouseExit + { + hide loc_but02_on + show loc_but02_off + + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 280 119 0 0 20 25 + transition2 loc_but02_off 395 137 0 0 20 25 + transition2 loc_but03_off 181 163 0 0 20 25 + transition2 loc_but04_off 305 114 0 0 20 25 + transition2 loc_but05_off 389 79 0 0 20 25 + + hide loc_but01_on + hide loc_but02_on + hide loc_but03_on + hide loc_but04_on + hide loc_but05_on + + setitembackground big_planet "gfx/menus/planets/tanaab" + transition2 big_planet 192 20 256 256 10 15 + + transition2 planet_name 270 250 256 256 20 15 + + hide mission_button + + show backbut + show accbut2 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T3_STAMP + + hide map_title + hide button_glow + } + } + + itemDef + { + name miss3 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 376 224 20 + text @MENUS_T3_HEVIL_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t3_hevil" + } + cvarSubString + mouseEnter + { + hide loc_but03_off + show loc_but03_on + + show planet_name + setitemtext planet_name @MENUS_YALARA + setitemrect planet_name 60 180 120 26 + + show button_glow + setitemrect button_glow 20 374 380 22 + } + mouseExit + { + hide loc_but03_on + show loc_but03_off + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 280 119 0 0 20 25 + transition2 loc_but02_off 395 137 0 0 20 25 + transition2 loc_but03_off 181 163 0 0 20 25 + transition2 loc_but04_off 305 114 0 0 20 25 + transition2 loc_but05_off 389 79 0 0 20 25 + + hide loc_but01_on + hide loc_but02_on + hide loc_but03_on + hide loc_but04_on + hide loc_but05_on + + setitembackground big_planet "gfx/menus/planets/yalara" + transition2 big_planet 192 20 256 256 10 15 + + transition2 planet_name 270 250 256 256 20 15 + + hide mission_button + + show backbut + show accbut3 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T3_HEVIL + hide map_title + hide button_glow + } + } + + itemDef + { + name miss4 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 399 224 20 + text @MENUS_T3_BYSS_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t3_byss" + } + cvarSubString + mouseEnter + { + hide loc_but04_off + show loc_but04_on + + show planet_name + setitemtext planet_name @MENUS_BYSS + setitemrect planet_name 240 40 120 26 + + show button_glow + setitemrect button_glow 20 397 380 22 + } + mouseExit + { + hide loc_but04_on + show loc_but04_off + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 280 119 0 0 20 25 + transition2 loc_but02_off 395 137 0 0 20 25 + transition2 loc_but03_off 181 163 0 0 20 25 + transition2 loc_but04_off 305 114 0 0 20 25 + transition2 loc_but05_off 389 79 0 0 20 25 + + hide loc_but01_on + hide loc_but02_on + hide loc_but03_on + hide loc_but04_on + hide loc_but05_on + + setitembackground big_planet "gfx/menus/planets/byss" + transition2 big_planet 192 20 256 256 10 15 + + transition2 planet_name 270 250 256 256 20 15 + + hide mission_button + + show backbut + show accbut4 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T3_BYSS + + hide map_title + hide button_glow + } + } + + itemDef + { + name miss5 + group mission_button + type ITEM_TYPE_BUTTON + rect 52 422 224 20 + text @MENUS_T3_BOUNTY_TITLE + desctext @MENUS_CLICK_BRIEFING + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 1 + cvartest "tiers_complete" + disablecvar + { + "t3_bounty" + } + cvarSubString + mouseEnter + { + hide loc_but05_off + show loc_but05_on + + show planet_name + setitemtext planet_name @MENUS_ORD_MANTELL + setitemrect planet_name 410 40 120 26 + + show button_glow + setitemrect button_glow 20 420 380 22 + } + mouseExit + { + hide loc_but05_on + show loc_but05_off + hide planet_name + hide button_glow + } + action + { + play "sound/weapons/force/protect.wav" + fadeout starfield + fadeout galaxy + fadein stars_close + transition2 loc_but01_off 280 119 0 0 20 25 + transition2 loc_but02_off 395 137 0 0 20 25 + transition2 loc_but03_off 181 163 0 0 20 25 + transition2 loc_but04_off 305 114 0 0 20 25 + transition2 loc_but05_off 389 79 0 0 20 25 + + hide loc_but01_on + hide loc_but02_on + hide loc_but03_on + hide loc_but04_on + hide loc_but05_on + + setitembackground big_planet "gfx/menus/planets/ordman" + transition2 big_planet 192 20 256 256 10 15 + + transition2 planet_name 270 250 256 256 20 15 + + hide mission_button + hide location_marker + + show backbut + show accbut5 + + show briefing_background + show briefing_text + setitemtext briefing_text @BRIEFINGS_T3_BOUNTY + + hide map_title + hide button_glow + + } + } + +//---------------------------------------------------------------------------------------------- +// MISSION BRIEFING TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name briefing_background + group none + style WINDOW_STYLE_FILLED + rect 42 314 320 114 + backcolor 0 0 .35 .7 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 0 + decoration + } + + itemDef + { + name briefing_text + type ITEM_TYPE_TEXTSCROLL + rect 42 314 320 114 + text @BRIEFINGS_T3_RIFT + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + lineHeight 18 + visible 0 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +// ACCEPT BUTTONS - IF YOU CHOOSE A PARTICULAR MISSION +//---------------------------------------------------------------------------------------------- + itemDef + { + name accbut1 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide accbuttons + setcvar tier_mapname "maptransition t3_rift" + + hide backbut + + show nextscreen_button + setfocus nextscreen_button + + show accept_text + setitemtext accept_text @T3_RIFT_23LUK001 + + show luke + playVoice "sound/chars/luke/23luk001.mp3" + + hide briefing_background + hide briefing_text + hide button_glow + } + } + + itemDef + { + name accbut2 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide accbuttons + setcvar tier_mapname "maptransition t3_stamp" + + hide backbut + hide briefing_text + + show nextscreen_button + setfocus nextscreen_button + + show accept_text + setitemtext accept_text @T3_STAMP_25KYK001 + + hide briefing_background + hide briefing_text + hide button_glow + + show kyle + playVoice "sound/chars/kyle/25kyk001.mp3" + } + } + + itemDef + { + name accbut3 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide accbuttons + setcvar tier_mapname "maptransition t3_hevil" + + hide backbut + hide briefing_text + + show nextscreen_button + setfocus nextscreen_button + + show accept_text + setitemtext accept_text @T3_HEVIL_22LUK001 + + show luke + playVoice "sound/chars/luke/22luk001.mp3" + + hide briefing_background + hide briefing_text + hide button_glow + + } + } + + itemDef + { + name accbut4 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide accbuttons + setcvar tier_mapname "maptransition t3_byss" + + hide backbut + hide briefing_text + + show nextscreen_button + setfocus nextscreen_button + + show accept_text + setitemtext accept_text @T3_BYSS_21KYK001 + + show kyle + playVoice "sound/chars/kyle/21kyk001.mp3" + + hide briefing_background + hide briefing_text + hide button_glow + } + } + + itemDef + { + name accbut5 + group accbuttons + type ITEM_TYPE_BUTTON + rect 268 430 172 24 + text @MENUS_ACCEPT + desctext @MENUS_ACCEPT_MISSION + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + mouseEnter + { + show button_glow + setitemrect button_glow 200 430 200 20 + } + mouseExit + { + hide button_glow + } + action + { + hide accbuttons + setcvar tier_mapname "maptransition t3_bounty" + + hide backbut + hide briefing_text + + show nextscreen_button + setfocus nextscreen_button + + show accept_text + setitemtext accept_text @T3_BOUNTY_24KYK001 + + show kyle + playVoice "sound/chars/kyle/24kyk001.mp3" + + hide briefing_background + hide briefing_text + hide button_glow + } + } + +//---------------------------------------------------------------------------------------------- +// ACCEPT TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name accept_text + type ITEM_TYPE_TEXT + rect 52 316 288 124 + text @T3_RIFT_23LUK001 + font 2 + forecolor 1 1 1 1 + textscale .8 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + autowrapped + decoration + } + +//---------------------------------------------------------------------------------------------- +// NEXT BUTTONS - to go to next screen +//---------------------------------------------------------------------------------------------- + itemDef + { + name nextscreen_button + type ITEM_TYPE_BUTTON + rect 530 440 172 24 + text @MENUS_NEXT + desctext @MENUS_ADVANCE_NEXT + font 2 + forecolor 1 .682 0 1 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + close all + open ingameForceSelect + } + } + +//---------------------------------------------------------------------------------------------- +// SCANLINES OVER WHOLE MENU +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name static + group none + style WINDOW_STYLE_SHADER + rect 396 314 175 120 + background "gfx/menus/static" + backcolor 1 0 0 .2 + forecolor 1 0 0 .2 + visible 0 + decoration + } + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + + diff --git a/base/ui/ingameWpnSelect.menu b/base/ui/ingameWpnSelect.menu new file mode 100644 index 0000000..a80ae74 --- /dev/null +++ b/base/ui/ingameWpnSelect.menu @@ -0,0 +1,1638 @@ +//---------------------------------------------------------------------------------------------- +// +// Weapon Select Menu +// +// The player is allowed to choose from a list of weapons. The weapons in the list are +// based on the value in tier_storyinfo +// +// Weapon index values +// SABER - 1 +// BLASTER PISTOL - 2 +// BLASTER - 3 +// DISRUPTOR - 4 +// BOWCASTER - 5 +// REPEATER - 6 +// DEMP - 7 +// FLECHETTE - 8 +// ROCKET LAUNCHER - 9 +// THERMAL DET. - 10 +// TRIP MINE - 11 +// DET PACK - 12 +// CONCUSSION - 13 +// +// Ammo index values amounts to give +// SABER - FORCE - 1 - 100 +// BLASTER PISTOL - BLASTER - 2 - 300 +// BLASTER - BLASTER - 2 - 300 +// DISRUPTOR - POWER CELL - 3 - 300 +// BOWCASTER - POWER CELL - 3 - 300 +// REPEATER - METAL BOLTS - 4 - 400 +// DEMP - POWER CELL - 3 - 300 +// FLECHETTE - METAL BOLTS - 4 - 400 +// ROCKET LAUNCHER - ROCKETS - 5 - 10 +// THERMAL DET. - THERMAL - 7 - 10 +// TRIP MINE - TRIP MINE - 8 - 5 +// DET. PACK - DETPACK - 9 - 5 +// CONCUSSION - METAL BOLTS - 4 - 400 +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "ingameWpnSelect" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 25 // how often fade happens in milliseconds + fadeAmount 0.05 // amount to adjust alpha per cycle + + onOpen + { + uiScript "initweaponselect" // Perform screen init + uiScript "clearweapons" // Get rid of any weapons + uiScript "clearinventory" // Get rid of any inventory + uiScript "giveweapon" "1" // Give saber + uiScript "giveweapon" "2" // Give blaster + uiScript "equipweapon" "1" // start with saber + + } + + onESC + { + play "sound/interface/menuroam.wav" + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/weaponmenu_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TEXT - Choose weapons/Weapons chosen. +//---------------------------------------------------------------------------------------------- + itemDef + { + name choose_weapons_text + type ITEM_TYPE_TEXT + rect 0 15 640 18 + text @MENUS_CHOOSEWEAPONS + font 2 + forecolor 1 1 1 1 + textscale .80 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 1 + decoration + } + + itemDef + { + name weapons_chosen_text + type ITEM_TYPE_TEXT + rect 0 20 640 18 + text @MENUS_WEAPONSCHOSEN + font 2 + forecolor .308 .980 .277 1 + textscale .80 + textalign ITEM_ALIGN_CENTER + textalignx 320 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// INSTRUCTIONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name setup_background + group inst_stuff + style WINDOW_STYLE_FILLED + rect 40 195 455 145 + backcolor 0 0 .35 .9 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 0 + decoration + } + + itemDef + { + name instruction_title + group inst_stuff + type ITEM_TYPE_TEXT + rect 50 195 470 18 + text @MENUS_INSTRUCTIONS + font 2 + forecolor .549 .854 1 1 + textscale .8 + textalign ITEM_ALIGN_CENTER + textalignx 230 + visible 0 + decoration + } + + itemDef + { + name instruction_text + group inst_stuff + type ITEM_TYPE_TEXTSCROLL + rect 45 220 475 130 + text @MENUS_WEAPON_SELECTION_HELP + font 4 + forecolor 1 1 1 1 + textscale 1.0 + textalign ITEM_ALIGN_LEFT + visible 0 + decoration autowrapped + textaligny 0 + lineHeight 13 + + } + + itemDef + { + name inst_done_button + group inst_stuff + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 495 255 130 64 + text @MENUS_DONE_CAPS + descText @MENUS_HELP_DESC + font 3 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor .79 .64 .22 1 + visible 0 + + mouseEnter + { + show button_glow + setitemrect button_glow 490 255 140 25 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + hide button_glow + hide inst_stuff + + show weapon_button + } + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// SABER BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name saber_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 28 40 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 1 1 + visible 1 + decoration + } + + itemDef + { + name saber_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 24 30 80 80 + background "gfx/hud/w_icon_lightsaber_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name saber_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 24 30 80 80 + background "gfx/hud/w_icon_lightsaber" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name saber_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 28 40 79 64 + forecolor 1 1 1 1 + visible 1 + mouseEnter + { + hide saber_icon + show saber_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_lightsaber_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_SABER_DESC + } + mouseExit + { + show saber_icon + hide saber_icon_lit + } + action + { + } + } + +//---------------------------------------------------------------------------------------------- +// BLASTER PISTOL BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name bpistol_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 28 109 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 1 1 + visible 1 + decoration + } + + itemDef + { + name bpistol_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 24 96 80 80 + background "gfx/hud/w_icon_blaster_pistol_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name bpistol_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 24 96 80 80 + background "gfx/hud/w_icon_blaster_pistol" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name bpistol_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 28 109 79 64 + forecolor 1 1 1 1 +// desctext @MENU_CLICKDESCRIPTION + visible 1 + mouseEnter + { + hide bpistol_icon + show bpistol_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_blaster_pistol_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_NEW_BLASTER_PISTOL_DESC + } + mouseExit + { + show bpistol_icon + hide bpistol_icon_lit + } + action + { + } + } + +//---------------------------------------------------------------------------------------------- +// BLASTER RIFLE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name brifle_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 136 40 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + } + + itemDef + { + name brifle_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 134 31 80 80 + background "gfx/hud/w_icon_blaster_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name brifle_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 134 31 80 80 + background "gfx/hud/w_icon_blaster" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name brifle_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 137 40 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide brifle_icon + show brifle_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_blaster_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_BLASTER_RIFLE_DESC + } + mouseExit + { + show brifle_icon + hide brifle_icon_lit + } + + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "3" "2" "300" "brifle_icon" "brifle_icon_lit" "brifle_hex_background" "sound/weapons/blaster/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// DISRUPTOR RIFLE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name disruptor_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 136 109 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + } + + itemDef + { + name disruptor_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 134 99 80 80 + background "gfx/hud/w_icon_disruptor_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name disruptor_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 134 99 80 80 + background "gfx/hud/w_icon_disruptor" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name disruptor_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 137 109 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide disruptor_icon + show disruptor_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_disruptor_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_DISRUPTOR_RIFLE_DESC + } + mouseExit + { + show disruptor_icon + hide disruptor_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "4" "3" "300" "disruptor_icon" "disruptor_icon_lit" "disruptor_hex_background" "sound/weapons/disruptor/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// BOWCASTER BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name bowcaster_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 40 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + } + + itemDef + { + name bowcaster_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 31 80 80 + background "gfx/hud/w_icon_bowcaster_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name bowcaster_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 31 80 80 + background "gfx/hud/w_icon_bowcaster" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name bowcaster_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 224 40 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide bowcaster_icon + show bowcaster_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_bowcaster_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_BOWCASTER_DESC + } + mouseExit + { + show bowcaster_icon + hide bowcaster_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "5" "3" "300" "bowcaster_icon" "bowcaster_icon_lit" "bowcaster_hex_background" "sound/weapons/bowcaster/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// DEMP BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name demp_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 109 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + } + + itemDef + { + name demp_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 99 80 80 + background "gfx/hud/w_icon_demp2_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name demp_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 222 99 80 80 + background "gfx/hud/w_icon_demp2" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name demp_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 225 109 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide demp_icon + show demp_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_demp2_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_DEMP2_DESC + } + mouseExit + { + show demp_icon + hide demp_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "7" "3" "300" "demp_icon" "demp_icon_lit" "demp_hex_background" "sound/weapons/demp2/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// REPEATER BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name repeater_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 311 40 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + cvartest "tier_storyinfo" + showcvar + { + "7"; "8"; "9"; "10"; "11"; "12"; "13"; "14"; "15"; "16"; "17"; "18"; "19"; + } + } + + itemDef + { + name repeater_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 309 35 80 80 + background "gfx/hud/w_icon_repeater_na" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tier_storyinfo" + showcvar + { + "7"; "8"; "9"; "10"; "11"; "12"; "13"; "14"; "15"; "16"; "17"; "18"; "19"; + } + } + + itemDef + { + name repeater_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 309 35 80 80 + background "gfx/hud/w_icon_repeater" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name repeater_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 312 40 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "7"; "8"; "9"; "10"; "11"; "12"; "13"; "14"; "15"; "16"; "17"; "18"; "19"; + } + mouseEnter + { + hide repeater_icon + show repeater_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_repeater_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_HEAVYREPEATER_DESC + } + mouseExit + { + show repeater_icon + hide repeater_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "6" "4" "400" "repeater_icon" "repeater_icon_lit" "repeater_hex_background" "sound/weapons/repeater/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// FLECHETTE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name flechette_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 311 109 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + cvartest "tier_storyinfo" + showcvar + { + "7"; "8"; "9"; "10"; "11"; "12"; "13"; "14"; "15"; "16"; "17"; "18"; "19"; + } + } + + itemDef + { + name flechette_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 309 100 80 80 + background "gfx/hud/w_icon_flechette_na" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tier_storyinfo" + showcvar + { + "7"; "8"; "9"; "10"; "11"; "12"; "13"; "14"; "15"; "16"; "17"; "18"; "19"; + } + } + + itemDef + { + name flechette_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 309 100 80 80 + background "gfx/hud/w_icon_flechette" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name flechette_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 312 109 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "7"; "8"; "9"; "10"; "11"; "12"; "13"; "14"; "15"; "16"; "17"; "18"; "19"; + } + mouseEnter + { + hide flechette_icon + show flechette_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_flechette_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_FLECHETTE_DESC + } + mouseExit + { + show flechette_icon + hide flechette_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "8" "4" "400" "flechette_icon" "flechette_icon_lit" "flechette_hex_background" "sound/weapons/flechette/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// CONCUSSION BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name concussion_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 399 40 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + cvartest "tier_storyinfo" + showcvar + { + "13"; "14"; "15"; "16"; "17"; "18"; "19" + } + } + + itemDef + { + name concussion_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 395 30 80 80 + background "gfx/hud/w_icon_c_rifle_na" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tier_storyinfo" + showcvar + { + "13"; "14"; "15"; "16"; "17"; "18"; "19" + } + } + + itemDef + { + name concussion_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 395 30 80 80 + background "gfx/hud/w_icon_c_rifle" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name concussion_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 400 40 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "13"; "14"; "15"; "16"; "17"; "18"; "19" + } + mouseEnter + { + hide concussion_icon + show concussion_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_c_rifle_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_CONCUSSION_DESC + } + mouseExit + { + show concussion_icon + hide concussion_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "13" "4" "400" "concussion_icon" "concussion_icon_lit" "concussion_hex_background" "sound/weapons/concussion/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// ROCKET BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name rocket_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 399 109 88 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 .5 0 1 + visible 1 + decoration + cvartest "tier_storyinfo" + showcvar + { + "13"; "14"; "15"; "16"; "17"; "18"; "19" + } + } + + itemDef + { + name rocket_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 395 99 80 80 + background "gfx/hud/w_icon_merrsonn_na" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "tier_storyinfo" + showcvar + { + "13"; "14"; "15"; "16"; "17"; "18"; "19" + } + } + + itemDef + { + name rocket_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 395 99 80 80 + background "gfx/hud/w_icon_merrsonn" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name rocket_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 400 109 79 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "13"; "14"; "15"; "16"; "17"; "18"; "19" + } + mouseEnter + { + hide rocket_icon + show rocket_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_merrsonn_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_MERR_SONN_DESC + } + mouseExit + { + show rocket_icon + hide rocket_icon_lit + } + action + { // Add this weapon from player selection (weapon index, ammo index, ammo amount, menu item with graphic) + uiScript "addweaponselection" "9" "5" "10" "rocket_icon" "rocket_icon_lit" "rocket_hex_background" "sound/weapons/rocket/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// THERMAL DETONATOR BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name thermal_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 506 40 60 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 0 .5 1 + visible 1 + decoration + } + + itemDef + { + name thermal_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 494 32 80 80 + background "gfx/hud/w_icon_thermal_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name thermal_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 494 32 80 80 + background "gfx/hud/w_icon_thermal" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name thermal_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 507 40 52 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide thermal_icon + show thermal_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_thermal_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_THERMAL_DETONATOR_DESC + } + mouseExit + { + show thermal_icon + hide thermal_icon_lit + } + action + { // Add/remove this weapon from player selection (weapon #, menu item with graphic) + uiScript "addthrowweaponselection" "10" "7" "10" "thermal_icon" "thermal_icon_lit" "thermal_hex_background" "sound/weapons/thermal/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// DETPACK BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name detpack_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 564 40 60 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 0 .5 1 + visible 1 + decoration + } + + itemDef + { + name detpack_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 553 36 80 80 + background "gfx/hud/w_icon_detpack_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name detpack_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 553 36 80 80 + background "gfx/hud/w_icon_detpack" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name detpack_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 565 40 52 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide detpack_icon + show detpack_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_detpack_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_DET_PACK_DESC + } + mouseExit + { + show detpack_icon + hide detpack_icon_lit + } + action + { // Add/remove this weapon from player selection (weapon #, menu item with graphic) + uiScript "addthrowweaponselection" "12" "9" "5" "detpack_icon" "detpack_icon_lit" "detpack_hex_background" "sound/weapons/detpack/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// TRIP MINE BUTTONS AND BUTTON GRAPHICS +//---------------------------------------------------------------------------------------------- + itemDef + { + name tripmine_hex_background + group normal_icon + style WINDOW_STYLE_SHADER + rect 538 109 60 120 + background "gfx/menus/hex_pattern_gray" + forecolor 0 0 .5 1 + visible 1 + decoration + } + + itemDef + { + name tripmine_icon + group normal_icon + style WINDOW_STYLE_SHADER + rect 526 100 80 80 + background "gfx/hud/w_icon_tripmine_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name tripmine_icon_lit + group normal_icon + style WINDOW_STYLE_SHADER + rect 526 100 80 80 + background "gfx/hud/w_icon_tripmine" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tripmine_button + group weapon_button + type ITEM_TYPE_BUTTON + rect 539 109 52 64 + forecolor 1 1 1 1 + desctext @MENUS_CLICKSELECT + visible 1 + mouseEnter + { + hide tripmine_icon + show tripmine_icon_lit + + // Show big icon + show weapon_icon + setitembackground weapon_icon "gfx/hud/w_icon_tripmine_na" + + // Show weapon description + show weapon_desc + setitemtext weapon_desc @SP_INGAME_TRIP_MINE_DESC + } + mouseExit + { + show tripmine_icon + hide tripmine_icon_lit + } + action + { // Add/remove this weapon from player selection (weapon #, menu item with graphic) + uiScript "addthrowweaponselection" "11" "8" "5" "tripmine_icon" "tripmine_icon_lit" "tripmine_hex_background" "sound/weapons/detpack/select.wav" + } + } + +//---------------------------------------------------------------------------------------------- +// CHOSEN WEAPONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name chosensaber_hex_background + style WINDOW_STYLE_SHADER + rect 65 358 70 107 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 1 1 + visible 1 + decoration + } + + itemDef + { + name chosen_sabericon + group chosenicons + style WINDOW_STYLE_SHADER + rect 67 358 60 60 + background "gfx/hud/w_icon_lightsaber_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name chosenblaster_hex_background + style WINDOW_STYLE_SHADER + rect 152 358 70 107 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 1 1 + visible 1 + decoration + } + + itemDef + { + name chosen_blastericon + group chosenicons + style WINDOW_STYLE_SHADER + rect 154 358 60 60 + background "gfx/hud/w_icon_blaster_pistol_na" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + itemDef + { + name chosenweapon1_hex_background + style WINDOW_STYLE_SHADER + rect 280 358 70 107 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 0 1 + visible 1 + decoration + } + + itemDef + { + name chosenweapon1_icon + group chosenicons + style WINDOW_STYLE_SHADER + rect 282 358 60 60 + background "gfx/hud/w_icon_blaster_pistol_na" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name chosenweapon1_button + type ITEM_TYPE_BUTTON + rect 282 358 52 64 + forecolor 0 1 0 1 + visible 0 + desctext @MENUS_CLICKREMOVE + mouseEnter + { + uiScript highlightweaponselection "1" + } + mouseExit + { + uiScript normalweaponselection "1" + } + action + { + uiScript "removeweaponselection" "1" + } + } + + itemDef + { + name chosenweapon2_hex_background + style WINDOW_STYLE_SHADER + rect 367 358 70 107 + background "gfx/menus/hex_pattern_gray" + forecolor 0 1 0 1 + visible 1 + decoration + } + + itemDef + { + name chosenweapon2_icon + group chosenicons + style WINDOW_STYLE_SHADER + rect 369 358 60 60 + background "gfx/hud/w_icon_blaster_pistol_na" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name chosenweapon2_button + type ITEM_TYPE_BUTTON + rect 369 358 52 64 + forecolor 1 1 1 1 + visible 0 + desctext @MENUS_CLICKREMOVE + mouseEnter + { + uiScript highlightweaponselection "2" + } + mouseExit + { + uiScript normalweaponselection "2" + } + action + { + uiScript "removeweaponselection" "2" + } + } + + itemDef + { + name chosenthrowweapon_hex_background + style WINDOW_STYLE_SHADER + rect 500 358 70 107 + background "gfx/menus/hex_pattern_gray" + forecolor 0 0 1 1 + visible 1 + decoration + } + + itemDef + { + name chosenthrowweapon_icon + group chosenicons + style WINDOW_STYLE_SHADER + rect 500 358 60 60 + background "gfx/hud/w_icon_blaster_pistol_na" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name chosenthrowweapon_button + type ITEM_TYPE_BUTTON + rect 500 358 52 64 + forecolor 1 1 1 1 + visible 0 + desctext @MENUS_CLICKREMOVE + mouseEnter + { + uiScript highlightthrowselection + } + mouseExit + { + uiScript normalthrowselection + } + action + { + uiScript "removethrowweaponselection" + } + } + + + +//---------------------------------------------------------------------------------------------- +// BIG WEAPON ICON +//---------------------------------------------------------------------------------------------- + itemDef + { + name weapon_icon + group bigicons + style WINDOW_STYLE_SHADER + rect 20 200 128 128 + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// WEAPON DESCRIPTION TEXT +//---------------------------------------------------------------------------------------------- + itemDef + { + name weapon_desc + group none + type ITEM_TYPE_TEXTSCROLL + rect 130 187 490 175 + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 8 + textaligny -1 + lineHeight 16 + visible 0 + autowrapped + decoration + } + +//---------------------------------------------------------------------------------------------- +// BOTTOM ROW OF BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name backbut + type ITEM_TYPE_BUTTON + rect 30 447 140 24 + text @MENUS_HELP + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_HELP_DESC1 + visible 1 + mouseEnter + { + } + mouseExit + { + } + // If we're backing up then we have to reset levels to what they were + action + { + play "sound/interface/menuroam.wav" + + open ingameWpnSelectHelp + } + } + + + // Don't change the name of this, code looks for this name to turn it on and off + itemDef + { + name beginmission + type ITEM_TYPE_BUTTON + rect 400 447 220 24 + text @MENUS_BEGIN_MISSION + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_BEGIN_MISSION_DESC + visible 1 + cvartest "weapon_menu" + hidecvar + { + "1" ; "2" ; "3" + } + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + exec "vstr tier_mapname" + } + } + + // Don't change the name of this, code looks for this name to turn it on and off + // This is a big hack so that from Academy2 it will only bring up the weapon select menu + // and then go right to HOTH2. + itemDef + { + name beginmission + type ITEM_TYPE_BUTTON + rect 400 447 220 24 + text @MENUS_BEGIN_MISSION + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_BEGIN_MISSION_DESC + visible 1 + cvartest "weapon_menu" + showcvar + { + "1" + } + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + exec "maptransition hoth2" + } + } + + // Don't change the name of this, code looks for this name to turn it on and off + // This is a big hack so that from Academy4 it will only bring up the weapon select menu + // and then go right to VJUN1. + itemDef + { + name beginmission + type ITEM_TYPE_BUTTON + rect 400 447 220 24 + text @MENUS_BEGIN_MISSION + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_BEGIN_MISSION_DESC + visible 1 + cvartest "weapon_menu" + showcvar + { + "2" + } + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + exec "maptransition vjun1" + } + } + + // Don't change the name of this, code looks for this name to turn it on and off + // This is a big hack so that from Academy6 it will only bring up the weapon select menu + // and then go right to TASPIN1. + itemDef + { + name beginmission + type ITEM_TYPE_BUTTON + rect 400 447 220 24 + text @MENUS_BEGIN_MISSION + font 2 + forecolor 1 .682 0 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + desctext @MENUS_BEGIN_MISSION_DESC + visible 1 + cvartest "weapon_menu" + showcvar + { + "3" + } + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/menuroam.wav" + exec "maptransition taspir1" + } + } + +//---------------------------------------------------------------------------------------------- +// +// SCANLINES +// +//---------------------------------------------------------------------------------------------- + + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + diff --git a/base/ui/ingameWpnSelectHelp.menu b/base/ui/ingameWpnSelectHelp.menu new file mode 100644 index 0000000..8a956c7 --- /dev/null +++ b/base/ui/ingameWpnSelectHelp.menu @@ -0,0 +1,126 @@ +//-------------------------------------------------------------- +// +// ERROR MENU +// +// Displays error messages +// +//-------------------------------------------------------------- +{ + menuDef + { + name "ingameWpnSelectHelp" + visible 0 + fullscreen 0 + rect 140 70 350 375 + focusColor 1 1 1 1 + style WINDOW_STYLE_FILLED + border 1 + popup + outOfBoundsClick + descX 240 + descY 425 + descScale 1 + descColor 1 .682 0 .8 + + itemDef + { + name window + rect 0 0 350 375 + style WINDOW_STYLE_FILLED + backcolor .015 .015 .229 1 + forecolor 0 0 0 1 + border 1 + bordercolor .388 .396 .925 1 + bordersize 5 + visible 1 + decoration + } + + itemDef + { + name instruction_title + group inst_stuff + type ITEM_TYPE_TEXT + rect 0 25 350 18 + text @MENUS_HELP + font 2 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 175 + visible 1 + decoration + } + + itemDef + { + name instruction_text + group inst_stuff + type ITEM_TYPE_TEXTSCROLL + rect 25 80 300 250 + text @MENUS_WEAPON_SELECTION_HELP + font 4 + forecolor 1 1 1 1 + textscale 1.0 + textalign ITEM_ALIGN_LEFT + visible 1 + decoration autowrapped + textaligny 0 + lineHeight 13 + + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//-------------------------------------------------------------------------------- +// BUTTON +//-------------------------------------------------------------------------------- + itemDef + { + name inst_done_button + group inst_stuff + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 0 315 350 18 + text @MENUS_OKAY + descText @MENUS_HELP_DESC + font 3 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 175 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 230 384 170 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/misc/nomenu.wav" + close ingameWpnSelectHelp + } + } + + + } +} + + diff --git a/base/ui/ingamecontrols.menu b/base/ui/ingamecontrols.menu new file mode 100644 index 0000000..b046c55 --- /dev/null +++ b/base/ui/ingamecontrols.menu @@ -0,0 +1,3247 @@ +//---------------------------------------------------------------------------------------------- +// +// INGAME CONTROLS MENU +// +// Player can change key bindings from ingame +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingameControlsMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript loadControls + + + // fade in movement controls + show movecontrols + setitemcolor movecontrols forecolor .615 .615 .956 0.0 + fadein movecontrols + + + hide attackcontrols + hide weaponcontrols + hide forcecontrols + hide quickcontrols + hide joycontrols + hide othercontrols + show setup_background + hide ffwarning + + setitemcolor side_buttons forecolor 1 .682 0 1 + setitemcolor movementcontrolbutton forecolor 1 1 1 1 + } + + onClose + { + uiScript saveControls + hide ffwarning + } + + onESC + { + play "sound/interface/esc.wav" + hide highlights + close all + open ingameMainMenu + hide ffwarning + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MENU BUTTONS +//---------------------------------------------------------------------------------------------- + + + // Big button "SAVE" + itemDef + { + name savegamebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name savegamebutton + group nbut + text @MENUS_SAVE + descText @MENUS_SAVE_CURRENT_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show savegamebutton_glow + } + mouseExit + { + hide savegamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamesaveMenu + } + } + + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingameloadMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamesetupMenu ; + } + } + +//---------------------------------------------------------------------------------------------- +// OTHER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name backbutton + group exit + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open ingamemainMenu + } + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group exit_glow + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open ingamequitMenu + } + } + + // RESUME button in the lower right corner + itemDef + { + name resumebutton_glow + group resume_glow + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name resume + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 455 444 130 24 + text @MENUS_RESUME + descText @MENUS_RESUME_CURRENT_GAME + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show resumebutton_glow + } + mouseExit + { + hide resumebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + uiScript closeingame // Close menu + } + } + +//---------------------------------------------------------------------------------------------- +// SECOND ROW MENU BUTTONS +//---------------------------------------------------------------------------------------------- +// Configure Controls title + itemDef + { + name control_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CONFIGURE_CONTROLS + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// GLOW ON SIDE BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name sidebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 60 185 200 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MOVEMENT button +//---------------------------------------------------------------------------------------------- + itemDef + { + name movementcontrolbutton + group side_buttons + text @MENUS_MOVEMENT + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 185 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_MOVEMENT_KEYS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 185 170 24 + } + mouseExit + { + hide sidebutton_glow + } + + action + { + play sound/interface/sub_select + show setup_background + + show movecontrols + fadein movecontrols + + hide attackcontrols + hide weaponcontrols + hide forcecontrols + hide quickcontrols + hide joycontrols + hide othercontrols + setitemcolor movementcontrolbutton forecolor 1 1 1 1 + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 + setitemcolor quickcontrolbutton forecolor 1 .682 0 1 + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 + setitemcolor othercontrolbutton forecolor 1 .682 0 1 + } + } + +//---------------------------------------------------------------------------------------------- +// INTERACTION button +//---------------------------------------------------------------------------------------------- + itemDef + { + name attackcontrolbutton + group side_buttons + text @MENUS_INTERACTION + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 209 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_INTERACTION_DESC + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 209 170 24 + } + mouseExit + { + hide sidebutton_glow + } + + action + { + play sound/interface/sub_select + show setup_background + hide movecontrols + + show attackcontrols + + hide weaponcontrols + hide forcecontrols + hide quickcontrols + hide joycontrols + hide othercontrols + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 + setitemcolor attackcontrolbutton forecolor 1 1 1 1 + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 + setitemcolor quickcontrolbutton forecolor 1 .682 0 1 + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 + setitemcolor othercontrolbutton forecolor 1 .682 0 1 + } + } + + +//---------------------------------------------------------------------------------------------- +// WEAPONS button +//---------------------------------------------------------------------------------------------- + itemDef + { + name weaponscontrolbutton + group side_buttons + text @MENUS_WEAPONS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 233 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_WEAPON_CONTROLS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 233 170 24 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + hide movecontrols + hide attackcontrols + show weaponcontrols + hide forcecontrols + hide quickcontrols + hide joycontrols + hide othercontrols + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 + setitemcolor weaponscontrolbutton forecolor 1 1 1 1 + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 + setitemcolor quickcontrolbutton forecolor 1 .682 0 1 + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 + setitemcolor othercontrolbutton forecolor 1 .682 0 1 + } + } + +//---------------------------------------------------------------------------------------------- +// FORCE POWERS button +//---------------------------------------------------------------------------------------------- + itemDef + { + name forcecontrolbutton + group side_buttons + text @MENUS_FORCE_POWERS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 257 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_FORCE_POWER + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 257 170 24 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + hide movecontrols + hide attackcontrols + hide weaponcontrols + show forcecontrols + hide quickcontrols + hide joycontrols + hide othercontrols + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 + setitemcolor forcecontrolbutton forecolor 1 1 1 1 + setitemcolor quickcontrolbutton forecolor 1 .682 0 1 + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 + setitemcolor othercontrolbutton forecolor 1 .682 0 1 + } + } + +//---------------------------------------------------------------------------------------------- +// QUICK KEYS button +//---------------------------------------------------------------------------------------------- + itemDef + { + name quickcontrolbutton + group side_buttons + text @MENUS_QUICK_KEYS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 281 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_QUICK_KEYS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 281 170 24 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + hide movecontrols + hide attackcontrols + hide weaponcontrols + hide forcecontrols + show quickcontrols + hide joycontrols + hide othercontrols + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 + setitemcolor quickcontrolbutton forecolor 1 1 1 1 + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 + setitemcolor othercontrolbutton forecolor 1 .682 0 1 + } + } + +//---------------------------------------------------------------------------------------------- +// MOUSE/JOYSTICK button +//---------------------------------------------------------------------------------------------- + itemDef + { + name mousejoystickcontrolbutton + group side_buttons + text @MENUS_MOUSE_JOYSTICK + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 305 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_MOUSE_AND_JOYSTICK + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 305 170 24 + } + mouseExit + { + hide sidebutton_glow + } + + action + { + play sound/interface/sub_select + show setup_background + hide movecontrols + hide attackcontrols + hide weaponcontrols + hide forcecontrols + hide quickcontrols + show joycontrols + hide othercontrols + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 + setitemcolor quickcontrolbutton forecolor 1 .682 0 1 + setitemcolor mousejoystickcontrolbutton forecolor 1 1 1 1 + setitemcolor othercontrolbutton forecolor 1 .682 0 1 + } + } + +//---------------------------------------------------------------------------------------------- +// OTHER button +//---------------------------------------------------------------------------------------------- + itemDef + { + name othercontrolbutton + group side_buttons + text @MENUS_OTHER + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 329 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_ADDITIONAL + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 329 170 24 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + show setup_background + hide movecontrols + hide attackcontrols + hide weaponcontrols + hide forcecontrols + hide quickcontrols + hide joycontrols + show othercontrols + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 + setitemcolor quickcontrolbutton forecolor 1 .682 0 1 + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 + setitemcolor othercontrolbutton forecolor 1 1 1 1 + } + } + + itemDef + { + name setup_background + group side_buttons + style WINDOW_STYLE_FILLED + rect 260 185 340 225 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// HIGHLIGHT BARS +//---------------------------------------------------------------------------------------------- + itemDef + { + name highlight1 + group highlights + style WINDOW_STYLE_SHADER + rect 260 190 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight2 + group highlights + style WINDOW_STYLE_SHADER + rect 260 204 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight3 + group highlights + style WINDOW_STYLE_SHADER + rect 260 218 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight4 + group highlights + style WINDOW_STYLE_SHADER + rect 260 232 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight5 + group highlights + style WINDOW_STYLE_SHADER + rect 260 246 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight6 + group highlights + style WINDOW_STYLE_SHADER + rect 260 260 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight7 + group highlights + style WINDOW_STYLE_SHADER + rect 260 274 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight8 + group highlights + style WINDOW_STYLE_SHADER + rect 260 288 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight9 + group highlights + style WINDOW_STYLE_SHADER + rect 260 302 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight10 + group highlights + style WINDOW_STYLE_SHADER + rect 260 316 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight11 + group highlights + style WINDOW_STYLE_SHADER + rect 260 330 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight12 + group highlights + style WINDOW_STYLE_SHADER + rect 260 344 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight13 + group highlights + style WINDOW_STYLE_SHADER + rect 260 358 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight14 + group highlights + style WINDOW_STYLE_SHADER + rect 260 372 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight15 + group highlights + style WINDOW_STYLE_SHADER + rect 260 386 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MOVEMENT BINDING +//---------------------------------------------------------------------------------------------- + itemDef + { + name movement1 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_WALK_FORWARD + cvar "+forward" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_MOVES_PLAYER_FORWARD + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + } + + + itemDef + { + name movement2 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_BACKPEDAL + cvar "+back" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_MOVES_PLAYER_BACKWARD + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + itemDef + { + name movement3 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_TURN_LEFT + cvar "+left" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_ROTATES_PLAYER_LEFT + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + name movement4 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_TURN_RIGHT + cvar "+right" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_ROTATES_PLAYER_RIGHT + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + name movement5 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_RUN_WALK + cvar "+speed" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_IF_HELD_TOGGLES_BETWEEN + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + itemDef + { + name movement6 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_STEP_LEFT + cvar "+moveleft" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_STEPS_PLAYER_TO_THE_LEFT + + action + { + play sound/interface/button1 + } + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + } + + itemDef + { + name movement7 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_STEP_RIGHT + cvar "+moveright" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_STEPS_PLAYER_TO_THE_RIGHT + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + + itemDef + { + name movement8 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_SIDESTEP_TURN + cvar "+strafe" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_HELD_ALLOWS_PLAYER_TO + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + } + + itemDef + { + name movement12 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_UP_JUMP + cvar "+moveup" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_MAKES_PLAYER_JUMP_IF + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + } + + itemDef + { + name movement13 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_DOWN_CROUCH + cvar "+movedown" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 0.0 + visible 0 + descText @MENUS_MAKES_PLAYER_CROUCH_TO + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight13 + show keybindstatus + } + mouseexit + { + hide highlight13 + hide keybindstatus + } + } + + itemDef + { + name movement11 + group movecontrols + text @MENUS_HOLD_USE_PLUS_STRAFE + rect 260 384 340 14 + textalign ITEM_ALIGN_CENTER + textalignx 170 + textaligny 0 + font 4 + textscale 1 + forecolor .549 .854 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// INTERACTION BINDING +//---------------------------------------------------------------------------------------------- + itemDef + { + name attacklook1 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_ATTACK + cvar "+attack" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_ATTACKS_WITH_READIED + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + } + + itemDef + { + name attacklook2 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_ALT_ATTACK + cvar "+altattack" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_ATTACKS_WITH_ALTERNATE + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + itemDef + { + name attacksaber + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LIGHTSABER_STYLE + cvar "saberAttackCycle" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_CYCLES_BETWEEN_AVAILABLE + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + name attacklook3 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_USE + cvar "+use" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_ACTIVATES_WORLD_DEVICES + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + + itemDef + { + name attacklook4 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LOOK_UP + cvar "+lookup" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_TILTS_VIEW_UPWARDS + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide highlight9 + hide keybindstatus + } + } + + + itemDef + { + name attacklook5 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LOOK_DOWN + cvar "+lookdown" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_TILTS_VIEW_DOWNWARDS + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_MOUSE_LOOK + cvar "+mlook" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_IF_HELD_ALLOWS_PLAYER + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_CENTERVIEW + cvar "centerview" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_RETURNS_VIEW_TO_HORIZONTAL + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// WEAPON BINDING +//---------------------------------------------------------------------------------------------- + itemDef + { + name weapon1 + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_MELEE_LIGHTSABER + cvar "weapon 1" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_READIES_LIGHTSABER + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + } + + itemDef + { + name weapon3 + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_PISTOL + cvar "weapon 2" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_READIES_THE_BLASTER_PISTOL + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_RIFLE + cvar "weapon 3" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_READIES_THE_E_11_BLASTER + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_DISRUPTOR_RIFLE + cvar "weapon 4" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_READIES_THE_TENLOSS_DXR_6 + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_BOWCASTER + cvar "weapon 5" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_READIES_THE_WOOKIEE_BOWCASTER + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_HEAVY_REPEATER + cvar "weapon 6" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_READIES_THE_IMPERIAL + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_DEMP_2 + cvar "weapon 7" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_READIES_THE_DEMP2_GUN + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_FLECHETTE + cvar "weapon 8" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_READIES_THE_GOLAN_ARMS + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide keybindstatus + hide highlight8 + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_CONC_RIFLE_SETUP + cvar "weapon 13" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_READIES_THE_CONC_RIFLE + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide keybindstatus + hide highlight9 + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_MERR_SONN + cvar "weapon 9" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_READIES_THE_MERR_SONN + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_THROWABLE_WEAPONS + cvar "weapon 10" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_TOGGLES_BETWEEN_DETONATORS + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_NEXT_WEAPON + cvar "weapnext" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_SELECTS_THE_NEXT_WEAPON + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_PREVIOUS_WEAPON + cvar "weapprev" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_SELECTS_THE_PREVIOUS + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight13 + show keybindstatus + } + mouseexit + { + hide highlight13 + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// FORCE BINDING +//---------------------------------------------------------------------------------------------- + itemDef + { + name force1 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PUSH + cvar "force_throw" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_FORCE_PUSH_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + } + + itemDef + { + name force2 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PULL + cvar "force_pull" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_FORCE_PULL_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + itemDef + { + name force3 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_SPEED + cvar "force_speed" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_FORCE_SPEED_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + name force4 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_SENSE + cvar "force_sight" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_FORCE_SENSE_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + name force5 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_ABSORB + cvar "force_absorb" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_FORCE_ABSORB_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + itemDef + { + name force6 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_HEAL + cvar "force_heal" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_FORCE_HEAL_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + } + + itemDef + { + name force7 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_MINDTRICK + cvar "force_distract" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_JEDI_MIND_TRICK + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + + itemDef + { + name force8 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PROTECT + cvar "force_protect" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_FORCE_PROTECT_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + } + + itemDef + { + name force9 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_DRAIN + cvar "+force_drain" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_DRAIN_FORCE_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide highlight9 + hide keybindstatus + } + } + + itemDef + { + name force10 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_GRIP + cvar "+force_grip" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_FORCE_GRIP_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + } + + + itemDef + { + name force11 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_LIGHTNING + cvar "+force_lightning" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_FORCE_LIGHTNING + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + } + + itemDef + { + name force12 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_DARK_RAGE + cvar "force_rage" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_RAGE_FORCE_ABILITY + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_USE_FORCE_POWER + cvar "+useforce" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_USES_CURRENTLY_SELECTED + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight13 + show keybindstatus + } + mouseexit + { + hide highlight13 + hide keybindstatus + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_NEXT + cvar "forcenext" + rect 260 370 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_SELECTS_NEXT_AVAILABLE + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight14 + show keybindstatus + } + mouseexit + { + hide highlight14 + hide keybindstatus + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PREVIOUS + cvar "forceprev" + rect 260 384 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_SELECTS_PREVIOUS_AVAILABLE + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight15 + show keybindstatus + } + mouseexit + { + hide highlight15 + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// QUICK KEY BINDING +//---------------------------------------------------------------------------------------------- + itemDef + { + name quickkeys1 + group quickcontrols + type ITEM_TYPE_BIND + text @MENUS_DATAPAD + cvar "datapad" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_CHECK_DATAPAD_FOR_MISSION + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + } + + itemDef + { + name quickkeys + group quickcontrols + type ITEM_TYPE_BIND + text @MENUS_SAVE_MENU + cvar "uimenu ingamesavemenu" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_BRINGS_UP_SAVE_GAME_MENU + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + name quickkeys + group quickcontrols + type ITEM_TYPE_BIND + text @MENUS_LOAD_MENU + cvar "uimenu ingameloadmenu" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_BRINGS_UP_LOAD_GAME_MENU + + action + { + play sound/interface/button1 + } + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + name quickkeys + group quickcontrols + type ITEM_TYPE_BIND + text @MENUS_INSTANT_SAVE + cvar "save quick" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_AUTOMATICALLY_SAVES_GAME + + action + { + play sound/interface/button1 + } + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + itemDef + { + name quickkeys + group quickcontrols + type ITEM_TYPE_BIND + text @MENUS_INSTANT_LOAD + cvar "load quick" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_AUTOMATICALLY_LOADS_GAME + + action + { + play sound/interface/button1 + } + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + } + + +//---------------------------------------------------------------------------------------------- +// MOUSE/JOYSTICK KEY BINDING +//---------------------------------------------------------------------------------------------- + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_INVERT_MOUSE + cvar "ui_mousePitch" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_TOGGLE_TO_TILT_VIEW_IN + + action { + uiScript update ui_mousePitch + play sound/interface/button1 + } + + mouseenter + { + show highlight2 + } + + mouseexit + { + hide highlight2 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_SMOOTH_MOUSE + cvar "m_filter" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_WHEN_TURNED_ON_MOUSE + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight3 + } + + mouseexit + { + hide highlight3 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_SLIDER + text @MENUS_SENSITIVITY + cvarfloat "sensitivity" 5 2 30 + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_ADJUSTS_CHARACTER_REACTION + + action + { + play sound/interface/button1 + } + mouseenter + { + show highlight4 + } + mouseexit + { + hide highlight4 + } + } + + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_ENABLE_JOYSTICK + cvar "in_joystick" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_TURNED_ON_GAME_SEARCHES + action + { + play sound/interface/button1 + exec in_restart + } + + mouseenter + { + show highlight6 + } + + mouseexit + { + hide highlight6 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_X_AXIS_AS_BUTTONS + cvar "joy_xbutton" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_WHEN_OFF_HORIZONTAL + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight7 + } + + mouseexit + { + hide highlight7 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_Y_AXIS_AS_BUTTONS + cvar "joy_ybutton" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_WHEN_OFF_VERTICAL_STICK + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight8 + } + + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_SLIDER + text @MENUS_JOYSTICK_THRESHOLD + cvarfloat "joy_threshold" .15 .05 .75 + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_ADJUSTS_THE_SIZE_OF_THE + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + } + +// +// Not shown +// + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_FORCE_FEEDBACK + cvar "use_ff" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_WHEN_TURNED_ON_GAME + action + { + play sound/interface/button1 + uiScript update ff + } + + mouseenter + { + show highlight11 + show ffwarning + } + + mouseexit + { + hide highlight11 + hide ffwarning + } + } + + itemDef + { + name ffwarning + type ITEM_TYPE_TEXT + text @MENUS_APPLY_FORCE_FEEDBACK + text2 @MENUS_AND_RETURNTO_GAME + rect 260 360 340 14 + textalign ITEM_ALIGN_CENTER + textalignx 170 + text2aligny 18 + font 4 + textscale 1 + // foreColor 1 .682 0 .8 + forecolor 1 0 0 1 + decoration + visible 0 + } + +//---------------------------------------------------------------------------------------------- +// OTHER +//---------------------------------------------------------------------------------------------- + itemDef + { + name other1 + group othercontrols + type ITEM_TYPE_YESNO + text @MENUS_ALWAYS_RUN + cvar "cl_run" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_WHEN_ON_PLAYER_ALWAYS + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight1 + } + + mouseexit + { + hide highlight1 + } + } + + itemDef + { + name other2 + group othercontrols + type ITEM_TYPE_MULTI + text @MENUS_AUTO_SWITCH + cvar "cg_autoswitch" + cvarFloatList + { + @MENUS_DON_T_SWITCH 0 + @MENUS_BEST_SAFE_WEAPON 1 + @MENUS_ALWAYS_BEST_WEAPON 2 + } + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_CHOOSE_WHETHER_TO_SWITCH + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight2 + } + + mouseexit + { + hide highlight2 + } + + } + + itemDef + { + name other3 + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_3RD_PERSON + cvar "cg_thirdperson !" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1.0 + visible 0 + descText @MENUS_CHANGES_VIEW_BETWEEN + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// Text +//---------------------------------------------------------------------------------------------- + itemDef + { + name keyBindStatus + group none + ownerdraw 250 // UI_KEYBINDSTATUS + text @MENUS_BLANK_1 + rect 375 425 0 0 + textaligny 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + forecolor 1 1 0 1 + visible 0 + decoration + } + + itemDef + { + name slider_message + group none + text @MENUS_MOVE_THE_SLIDER_TO_INCREASE + rect 375 425 0 0 + textaligny 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + visible 0 + decoration + } + + itemDef + { + name yesno_message + group none + text @MENUS_CLICK_ON_FIELD_TO_TOGGLE + rect 375 425 0 0 + textaligny 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + visible 0 + decoration + } + + itemDef + { + name multi_message + group none + text @MENUS_CLICK_ON_FIELD_TO_CHANGE + rect 375 425 0 0 + textaligny 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + visible 0 + decoration + } + + } +} diff --git a/base/ui/ingameload.menu b/base/ui/ingameload.menu new file mode 100644 index 0000000..260cd0a --- /dev/null +++ b/base/ui/ingameload.menu @@ -0,0 +1,726 @@ +//---------------------------------------------------------------------------------------------- +// INGAME MAIN MENU +// +// Allows user to load from saved games. Called from in game main menu. +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + // ***IMPORTANT*** This name can not change, code looks for it. + // If it must change, change it in UI_AdjustSaveGameListBox() also + // + name "ingameloadMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + + onOpen + { + uiScript ReadSaveDirectory + } + + onESC + { + play "sound/interface/esc.wav" + uiScript closeingame // Close menu + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Big button "SAVE" + itemDef + { + name savegamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name savegamebutton + group nbut + text @MENUS_SAVE + descText @MENUS_SAVE_CURRENT_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show savegamebutton_glow + } + mouseExit + { + hide savegamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamesaveMenu + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + decoration + + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamecontrolsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamesetupMenu ; + } + } + +//---------------------------------------------------------------------------------------------- +// OTHER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name backbutton + group exit + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + cvartest ui_missionfailed + hidecvar + { + "1" + } + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open ingamemainMenu + } + } + + // Only displayed when the mission failure screen is up + itemDef + { + name backbutton + group exit + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + cvartest ui_missionfailed + showcvar + { + "1" + } + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open missionfailed_menu + } + } + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open ingamequitMenu + } + } + + // RESUME button in the lower right corner + itemDef + { + name resumebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name resume + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 455 444 130 24 + text @MENUS_RESUME + descText @MENUS_RESUME_CURRENT_GAME + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show resumebutton_glow + } + mouseExit + { + hide resumebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + uiScript closeingame // Close menu + } + } + +//---------------------------------------------------------------------------------------------- +// LOAD GAME MENU specific stuff +//---------------------------------------------------------------------------------------------- + // Load Game title + itemDef + { + name loadgame_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_LOAD_GAME + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + // ***IMPORTANT*** This name can not change, code looks for it. + // If it must change, change it in UI_AdjustSaveGameListBox() also + // + name loadgamelist + group loadscreen + rect 40 200 360 190 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 16 + font 2 + textaligny 8 + textscale 0.7 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + + + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SAVEGAMES + notselectable + visible 1 + columns 2 2 55 150 155 0 200 //#of col., x loc. of 1st column, 2 number doesn't seem to do anything, third is width of column + mouseEnter + { + setitemcolor loadgamelist bordercolor 1 1 1 1 + } + mouseExit + { + setitemcolor loadgamelist bordercolor 0 0 .8 1 + } + doubleclick + { + play sound/interface/button1.wav + uiScript loadgame + } + action + { + play sound/interface/sub_select + } + } + + itemDef + { + name loadgamepic + group loadscreen + style WINDOW_STYLE_EMPTY + ownerdraw 236 //UI_ALLMAPS_SELECTION + font 2 + textscale .8 +// forecolor 1 1 1 1 + forecolor .549 .854 1 1 + rect 435 200 180 135 + border 1 + bordercolor 0 0 .8 1 + visible 1 + } + + // loadgame button + itemDef + { + name loadgameaction_glow + group glow + style WINDOW_STYLE_SHADER + rect 440 360 190 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgameaction + group actionbutton + text @MENUS_LOAD_GAME + descText @MENUS_LOAD_CHOSEN_GAME + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 440 360 175 25 + font 3 + textscale 0.8 + textalignx 175 + textaligny 1 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + cvarTest ui_SelectionOK + enableCvar { "1" } + + mouseEnter + { + show loadgameaction_glow + } + mouseExit + { + hide loadgameaction_glow + } + action + { + play sound/interface/button1.wav ; + hide glow ; + uiScript loadgame + } + } + + // deletegame button + itemDef + { + name deletegameaction_glow + group glow + style WINDOW_STYLE_SHADER + rect 440 385 190 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name deletegamebutton + group actionbutton + text @MENUS_DELETE_GAME + descText @MENUS_DELETE_CHOSEN_GAME + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 440 385 175 25 + font 3 + textscale 0.8 + textalignx 175 + textaligny 1 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + cvarTest ui_SelectionOK + enableCvar { "1" } + + mouseEnter + { + show deletegameaction_glow + } + mouseExit + { + hide deletegameaction_glow + } + action + { + play sound/interface/button1.wav ; + hide glow ; + uiScript deletegame + } + } + + + // resumegame button + itemDef + { + name resumegameaction_glow + group glow + style WINDOW_STYLE_SHADER + rect 440 410 190 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name resumeautobutton + group actionbutton + text @MENUS_RESUME_MISSION + descText @MENUS_RESUME_LAST_MISSION_ENTERED + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 440 410 175 25 + font 3 + textscale 0.8 + textalignx 175 + textaligny 1 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 // This button isn't used when not in game. + cvarTest ui_ResumeOK + enableCvar { "1" } + + mouseEnter + { + show resumegameaction_glow + } + mouseExit + { + hide resumegameaction_glow + } + action + { + play "sound/interface/button1.wav" ; + hide glow ; + uiScript loadAuto + } + } + } +} + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/ingamequit.menu b/base/ui/ingamequit.menu new file mode 100644 index 0000000..367fca8 --- /dev/null +++ b/base/ui/ingamequit.menu @@ -0,0 +1,784 @@ + //---------------------------------------------------------------------------------------------- + // + // QUIT MENU + // + //---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingamequitMenu" + visible 0 + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + hide abandongamegroup ; + hide quitprogramgroup ; + hide highlights ; + show quitchoicegroup ; + } + onESC + { + play "sound/interface/esc.wav" + hide abandongamegroup ; + hide quitprogramgroup ; + hide highlights ; + show quitchoicegroup ; + close all ; + open ingameMainMenu ; + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + + // Big button "SAVE" + itemDef + { + name savegamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name savegamebutton + group nbut + text @MENUS_SAVE + descText @MENUS_SAVE_CURRENT_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show savegamebutton_glow + } + mouseExit + { + hide savegamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamesaveMenu + } + } + + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingameloadMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamecontrolsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamesetupMenu ; + } + } + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // RESUME button in the lower right corner + itemDef + { + name resumebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name resume + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 455 444 130 24 + text @MENUS_RESUME + descText @MENUS_RESUME_CURRENT_GAME + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show resumebutton_glow + } + mouseExit + { + hide resumebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + uiScript closeingame // Close menu + } + } + + //---------------------------------------------------------------------------------------------- + // + // QUIT GAME MENU specific stuff + // + //---------------------------------------------------------------------------------------------- + // Quitting title + itemDef + { + name quit_title + group none + text @MENUS_LEAVING_JEDI_KNIGHT_2 + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name quitgame_current_glow + group highlights + style WINDOW_STYLE_SHADER + rect 220 210 200 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // Quit current button + itemDef + { + name quitgame_current + group quitchoicegroup + text @MENUS_ABANDON_GAME + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 220 210 200 30 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 100 + textaligny -2 + descText @MENUS_QUIT_CURRENT_GAME_AND + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + show abandongamegroup ; + hide quitchoicegroup ; + hide quitprogramgroup ; + hide highlights ; + } + mouseEnter + { + show quitgame_current_glow + } + mouseExit + { + hide quitgame_current_glow + } + } + + itemDef + { + name quitgame_program_glow + group highlights + style WINDOW_STYLE_SHADER + rect 220 280 200 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // Quit program button + itemDef + { + name quitgame_program + group quitchoicegroup + text @MENUS_QUIT_PROGRAM + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 220 280 200 30 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 100 + textaligny -2 + descText @MENUS_LEAVE_THE_PROGRAM_ENTIRELY + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + show quitprogramgroup ; + hide abandongamegroup ; + hide quitchoicegroup ; + hide highlights ; + } + mouseEnter + { + show quitgame_program_glow + } + mouseExit + { + hide quitgame_program_glow + } + } + + +//---------------------------------------------------------------------------------------------- +// +// Abandon game Confirmation +// +//---------------------------------------------------------------------------------------------- +// Quit current button + itemDef + { + name fakequitgame_current + group abandongamegroup + text @MENUS_ABANDON_GAME + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 220 210 200 30 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 100 + textaligny -2 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name abandongame_cancel_glow + group highlights + style WINDOW_STYLE_SHADER + rect 78 384 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // CANCEL button + itemDef + { + name abandongame_cancel + group abandongamegroup + text @MENUS_NO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 78 384 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + descText @MENUS_DO_NOT_ABANDON + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide abandongamegroup ; + hide quitprogramgroup ; + hide highlights ; + show quitchoicegroup ; + } + mouseEnter + { + show abandongame_cancel_glow + } + mouseExit + { + hide abandongame_cancel_glow + } + } + + itemDef + { + name abandongame_yes_glow + group highlights + style WINDOW_STYLE_SHADER + rect 434 384 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // YES button + itemDef + { + name abandongame_yes + group abandongamegroup + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 434 384 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + descText @MENUS_ABANDON + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide abandongamegroup ; + hide quitprogramgroup ; + hide highlights ; + show quitchoicegroup ; + uiScript Leave //disconnect and start over + } + mouseEnter + { + show abandongame_yes_glow + } + mouseExit + { + hide abandongame_yes_glow + } + } + + +//---------------------------------------------------------------------------------------------- +// +// Leave game Confirmation +// +//---------------------------------------------------------------------------------------------- +// Quit program button + itemDef + { + name fakequitgame_program + group quitprogramgroup + text @MENUS_QUIT_PROGRAM + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 220 280 200 30 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 100 + textaligny -2 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name quitprogram_cancel_glow + group highlights + style WINDOW_STYLE_SHADER + rect 78 384 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // CANCEL button + itemDef + { + name quitprogram_cancel + group quitprogramgroup + text @MENUS_NO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 78 384 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + descText @MENUS_DO_NOT_LEAVE_JEDI_KNIGHT + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide quitprogramgroup ; + hide abandongamegroup ; + hide highlights ; + show quitchoicegroup ; + } + mouseEnter + { + show quitprogram_cancel_glow + } + mouseExit + { + hide quitprogram_cancel_glow + } + } + + itemDef + { + name quitprogram_yes_glow + group highlights + style WINDOW_STYLE_SHADER + rect 434 384 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // YES button + itemDef + { + name quitprogram_yes + group quitprogramgroup + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 434 384 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + descText @MENUS_JEDI_KNIGHT_II + forecolor 1 .682 0 1 + visible 1 + cvartest "com_demo" + hidecvar + { + "1" + } + action + { + play "sound/interface/button1.wav" ; + uiScript Quit // Quit the game + } + mouseEnter + { + show quitprogram_yes_glow + } + mouseExit + { + hide quitprogram_yes_glow + } + } + + // YES button + itemDef + { + name quitprogram_yes + group quitprogramgroup + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 434 384 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + descText @MENUS_JEDI_KNIGHT_II + forecolor 1 .682 0 1 + visible 1 + cvartest "com_demo" + hidecvar + { + "0" + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open demo_sellscreen1 ; + + } + mouseEnter + { + show quitprogram_yes_glow + } + mouseExit + { + hide quitprogram_yes_glow + } + } + + + } +} \ No newline at end of file diff --git a/base/ui/ingamesave.menu b/base/ui/ingamesave.menu new file mode 100644 index 0000000..c30d8a7 --- /dev/null +++ b/base/ui/ingamesave.menu @@ -0,0 +1,663 @@ +//------------------------------------------------------------------------------------------------ +// INGAME MAIN MENU +// +// Allows player to save current game +// +//------------------------------------------------------------------------------------------------ +{ + menuDef + { + name "ingamesaveMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + + onOpen + { + uiScript ReadSaveDirectory + setfocus savegamedesc + } + + onESC + { + play "sound/interface/esc.wav" + uiScript closeingame // Close menu + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Big button "SAVE" + itemDef + { + name savegamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name savegamebutton + group nbut + text @MENUS_SAVE + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + decoration + } + + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingameloadMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamecontrolsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamesetupMenu ; + } + } + +//---------------------------------------------------------------------------------------------- +// OTHER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name backbutton + group exit + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open ingamemainMenu + } + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open ingamequitMenu + } + } + + // RESUME button in the lower right corner + itemDef + { + name resumebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name resume + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 455 444 130 24 + text @MENUS_RESUME + descText @MENUS_RESUME_CURRENT_GAME + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show resumebutton_glow + } + mouseExit + { + hide resumebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + uiScript closeingame // Close menu + } + } + +//---------------------------------------------------------------------------------------------- +// SAVE GAME FIELDS +//---------------------------------------------------------------------------------------------- + // Save Game title + itemDef + { + name savegame_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_SAVE_GAME + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name savegamedesc + group savegame + type ITEM_TYPE_EDITFIELD + style WINDOW_STYLE_EMPTY + text @MENUS_DESC + cvar ui_gameDesc + maxChars 60 + rect 40 185 450 16 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -4 + font 2 + textscale .8 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor .615 .615 .956 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name savegamelist + group loadscreen + rect 40 200 360 190 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 16 + font 2 + textaligny 8 + textscale 0.7 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SAVEGAMES + notselectable + visible 1 + columns 2 2 55 150 155 0 200 + mouseEnter + { + setitemcolor savegamelist bordercolor 1 1 1 1 + } + mouseExit + { + setitemcolor savegamelist bordercolor 0 0 .8 1 + } + } + + itemDef + { + name savegame_desc + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_TEXT + rect 40 392 360 16 + text @MENUS_SAVEGAME_DESC + font 4 + textscale 1 + textalignx 0 + textaligny 1 + textalign ITEM_ALIGN_LEFT + forecolor 1 .682 0 1 + visible 1 + } + + itemDef + { + name savegamepic + group loadscreen + style WINDOW_STYLE_EMPTY + ownerdraw 236 //UI_ALLMAPS_SELECTION + font 2 + textscale .8 + forecolor .549 .854 1 1 + rect 435 200 180 135 + border 1 + bordercolor 0 0 .8 1 + visible 1 + decoration + } + + itemDef + { + name savegameaction_glow + group glow + style WINDOW_STYLE_SHADER + rect 440 360 190 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name savegamebutton + group toprow + text @MENUS_SAVE_GAME + descText @MENUS_SAVE_GAME + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 440 360 175 25 + font 3 + textscale 0.8 + textalignx 175 + textaligny 1 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show savegameaction_glow + } + mouseExit + { + hide savegameaction_glow + } + action + { + play sound/interface/button1.wav ; + uiScript savegame + } + } + + // deletegame button + itemDef + { + name deletegameaction_glow + group glow + style WINDOW_STYLE_SHADER + rect 440 385 190 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name deletegamebutton + group toprow + text @MENUS_DELETE_GAME + descText @MENUS_DELETE_CHOSEN_GAME + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 440 385 175 25 + font 3 + textscale 0.8 + textalignx 175 + textaligny 1 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + cvarTest "ui_SelectionOK" + enableCvar { "1" } + mouseEnter + { + show deletegameaction_glow + } + mouseExit + { + hide deletegameaction_glow + } + action + { + play sound/interface/button1.wav ; + hide glow ; + uiScript deletegame + } + } + } +} + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/ingamesetup.menu b/base/ui/ingamesetup.menu new file mode 100644 index 0000000..04260e6 --- /dev/null +++ b/base/ui/ingamesetup.menu @@ -0,0 +1,2740 @@ +//---------------------------------------------------------------------------------------------- +// INGAME SETUP MENU +// +// Allow user to setup video and stuff +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingameSetupMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + setfocus graphics ; + uiScript getvideosetup ; // Get video settings + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide defaults + show setup_background ; + show video ; + setitemcolor video1menubutton forecolor 1 1 1 1 ; + setitemcolor video2menubutton forecolor 1 .682 0 1 ; + setitemcolor soundmenubutton forecolor 1 .682 0 1; + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1; + setitemcolor modsmenubutton forecolor 1 .682 0 1; + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1; + } + + onESC + { + play "sound/interface/esc.wav" + defer VideoSetup ingamevideowarningMenu + hide highlights + close all + open ingameMainMenu + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group highlights + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// GLOW ON SIDE BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name sidebutton_glow + group highlights + style WINDOW_STYLE_SHADER + rect 60 185 200 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Big button "SAVE" + itemDef + { + name savegamebutton + group nbut + text @MENUS_SAVE + descText @MENUS_SAVE_CURRENT_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 7 126 130 26 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamesaveMenu + } + + } + + + // Big button "LOAD" + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 170 126 130 26 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingameloadMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 340 126 130 26 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/button1.wav" ; + close all ; + open ingamecontrolsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 502 126 130 26 + } + mouseExit + { + hide button_glow + } + } +//---------------------------------------------------------------------------------------------- +// OTHER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton + group exit + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 59 444 130 24 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open ingamemainMenu + } + } + +// EXIT button in lower left corner + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 255 444 130 24 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open ingamequitMenu + } + } + + // RESUME button in the lower right corner + itemDef + { + name resume + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 455 444 130 24 + text @MENUS_RESUME + descText @MENUS_RESUME_CURRENT_GAME + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 455 444 130 24 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + uiScript closeingame // Close menu + } + } + +//---------------------------------------------------------------------------------------------- +// SECOND ROW MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Setup Options title + itemDef + { + name setup_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_SETUP_OPTIONS + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + + // video1 button + itemDef + { + name video1menubutton + group none + text @MENUS_VIDEO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 185 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textstyle 1 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_VIDEO_SETTINGS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 185 180 24 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select ; + + defer VideoSetup ingamevideowarningMenu ; + + uiScript getvideosetup ; // Get video settings + + show setup_background ; + show video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide mods ; + hide defaults ; + setfocus graphics ; + setitemcolor video1menubutton forecolor 1 1 1 1 ; + setitemcolor video2menubutton forecolor 1 .682 0 1 + setitemcolor soundmenubutton forecolor 1 .682 0 1 + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1 + setitemcolor modsmenubutton forecolor 1 .682 0 1 + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1 + } + } + + // video2 button + itemDef + { + name video2menubutton + group none + text @MENUS_MORE_VIDEO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 209 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGUE_MORE_VIDEO_SETTINGS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 209 180 24 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select ; + + defer VideoSetup ingamevideowarningMenu ; + + show setup_background ; + hide video ; + hide applyChanges ; + show video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide mods ; + hide defaults ; + setitemcolor video1menubutton forecolor 1 .682 0 1 + setitemcolor video2menubutton forecolor 1 1 1 1 ; + setitemcolor soundmenubutton forecolor 1 .682 0 1 + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1 + setitemcolor modsmenubutton forecolor 1 .682 0 1 + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1 + } + } + + // sound button + itemDef + { + name soundmenubutton + group none + text @MENUS_SOUND + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 233 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_SOUND_SETTINGS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 233 180 24 + } + mouseExit + { + hide sidebutton_glow + } + + action + { + play sound/interface/sub_select ; + + defer VideoSetup ingamevideowarningMenu ; + + show setup_background ; + hide video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + show sound ; + hide options ; + hide mods ; + hide defaults ; + setitemcolor video1menubutton forecolor 1 .682 0 1 + setitemcolor video2menubutton forecolor 1 .682 0 1 + setitemcolor soundmenubutton forecolor 1 1 1 1 ; + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1 + setitemcolor modsmenubutton forecolor 1 .682 0 1 + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1 + } + } + + // gameoptions button + itemDef + { + name gameoptionmenubutton + group none + text @MENUS_GAME_OPTIONS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 257 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_GAME_OPTIONS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 257 180 24 + } + mouseExit + { + hide sidebutton_glow + } + + action + { + defer VideoSetup ingamevideowarningMenu + + play sound/interface/sub_select + show setup_background + hide video + hide applyChanges + hide video2 + hide vidrestart + hide sound + show options + hide langapply + hide mods + hide defaults + setitemcolor video1menubutton forecolor 1 .682 0 1 + setitemcolor video2menubutton forecolor 1 .682 0 1 + setitemcolor soundmenubutton forecolor 1 .682 0 1 + setitemcolor gameoptionmenubutton forecolor 1 1 1 1 + setitemcolor modsmenubutton forecolor 1 .682 0 1 + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1 + } + } + + // mods button + itemDef + { + name modsmenubutton + group none + text @MENUS_MODS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 281 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_GAME_OPTIONS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 281 180 24 + } + mouseExit + { + hide sidebutton_glow + } + + action + { + play sound/interface/sub_select ; + + defer VideoSetup ingamevideowarningMenu ; + uiScript loadMods ; + + show setup_background ; + hide video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + show mods ; + hide defaults ; + setitemcolor video1menubutton forecolor 1 .682 0 1 + setitemcolor video2menubutton forecolor 1 .682 0 1 + setitemcolor soundmenubutton forecolor 1 .682 0 1 + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1 + setitemcolor modsmenubutton forecolor 1 1 1 1 ; + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1 + } + } + + // gamedefaults button + itemDef + { + name gamedefaultsmenubutton + group none + text @MENUS_DEFAULTS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 305 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_RESTORE_DEFAULT_SETTINGS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 80 305 180 24 + } + mouseExit + { + hide sidebutton_glow + } + + action + { + play sound/interface/sub_select ; + + defer VideoSetup ingamevideowarningMenu ; + + show setup_background ; + hide video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide mods ; + show defaults ; + + setitemcolor video1menubutton forecolor 1 .682 0 1 + setitemcolor video2menubutton forecolor 1 .682 0 1 + setitemcolor soundmenubutton forecolor 1 .682 0 1 + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1 + setitemcolor modsmenubutton forecolor 1 .682 0 1 + setitemcolor gamedefaultsmenubutton forecolor 1 1 1 1 ; + } + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 260 185 340 225 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// HIGHLIGHT BARS +//---------------------------------------------------------------------------------------------- + itemDef + { + name highlight1 + group highlights + style WINDOW_STYLE_SHADER + rect 260 190 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight2 + group highlights + style WINDOW_STYLE_SHADER + rect 260 204 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight3 + group highlights + style WINDOW_STYLE_SHADER + rect 260 218 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight4 + group highlights + style WINDOW_STYLE_SHADER + rect 260 232 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight5 + group highlights + style WINDOW_STYLE_SHADER + rect 260 246 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight6 + group highlights + style WINDOW_STYLE_SHADER + rect 260 260 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight7 + group highlights + style WINDOW_STYLE_SHADER + rect 260 274 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight8 + group highlights + style WINDOW_STYLE_SHADER + rect 260 288 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight9 + group highlights + style WINDOW_STYLE_SHADER + rect 260 302 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight10 + group highlights + style WINDOW_STYLE_SHADER + rect 260 316 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight11 + group highlights + style WINDOW_STYLE_SHADER + rect 260 330 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight12 + group highlights + style WINDOW_STYLE_SHADER + rect 260 344 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight13 + group highlights + style WINDOW_STYLE_SHADER + rect 260 358 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight14 + group highlights + style WINDOW_STYLE_SHADER + rect 260 372 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight15 + group highlights + style WINDOW_STYLE_SHADER + rect 260 386 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// VIDEO 1 MENU BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name graphics + group video + text @MENUS_VIDEO_QUALITY + type ITEM_TYPE_MULTI + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + style WINDOW_STYLE_FILLED + forecolor .615 .615 .956 1 + descText @MENUS_SELECT_PRESET_GRAPHIC + + visible 0 + + cvar "ui_r_glCustom" + cvarFloatList + { + @MENUS_HIGH_QUALITY 0 + @MENUS_NORMAL 1 + @MENUS_FAST 2 + @MENUS_FASTEST 3 + @MENUS_CUSTOM 4 + } + + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + action + { + play "sound/interface/button1.wav" ; + uiScript update "ui_r_glCustom" ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + + itemDef + { + name video_mode + group video + type ITEM_TYPE_MULTI + text @MENUS_VIDEO_MODE + cvarFloatList { @MENUS_640_X_480 3 @MENUS_800_X_600 4 @MENUS_1024_X_768 6 @MENUS_1152_X_864 7 @MENUS_1280_X_1024 8 @MENUS_1600_X_1200 9 @MENUS_2048_X_1536 10 @MENUS_2400_X_600 12 } + cvar "ui_r_mode" + + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + descText @MENUS_CHANGE_THE_DISPLAY_RESOLUTION + + visible 0 + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name color_depth + group video + type ITEM_TYPE_MULTI + text @MENUS_COLOR_DEPTH + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + + cvarFloatList { @MENUS_DEFAULT 0 @MENUS_16_BIT 16 @MENUS_32_BIT 32 } + descText @MENUS_CHANGE_THE_NUMBER_OF + cvar "ui_r_colorbits" + + visible 0 + + mouseenter + { + show highlight4 + } + mouseexit + { + hide highlight4 + } + action + { + play "sound/interface/button1.wav" + uiScript glCustom + uiScript update "ui_r_colorbits" + setcvar ui_r_modified 1 + show applyChanges + } + } + + itemDef + { + name fullscreen + group video + type ITEM_TYPE_MULTI + text @MENUS_FULL_SCREEN + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_TOGGLE_BETWEEN_FULL_SCREEN + cvar "ui_r_fullscreen" + + visible 0 + + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name geometric_detail + group video + type ITEM_TYPE_MULTI + text @MENUS_GEOMETRIC_DETAIL + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_LOW 2 @MENUS_MEDIUM 1 @MENUS_HIGH 0 } + descText @MENUS_ADJUST_THE_NUMBER_OF + cvar "ui_r_lodbias" + + visible 0 + + mouseenter + { + show highlight6 + } + mouseexit + { + hide highlight6 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + uiScript update "ui_r_lodbias" ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name texture_detail + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_DETAIL + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_LOW 3 @MENUS_MEDIUM 2 @MENUS_HIGH 1 @MENUS_VERY_HIGH 0 } + descText @MENUS_SELECT_THE_RESOLUTION + cvar "ui_r_picmip" + + visible 0 + + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name texture_quality + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_QUALITY + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_DEFAULT 0 @MENUS_16_BIT 16 @MENUS_32_BIT 32 } + descText @MENUS_SELECT_THE_NUMBER_OF + cvar "ui_r_texturebits" + + visible 0 + + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name texture_filter + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_FILTER + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarStrList { @MENUS_BILINEAR , "GL_LINEAR_MIPMAP_NEAREST" , @MENUS_TRILINEAR , "GL_LINEAR_MIPMAP_LINEAR" } + descText @MENUS_ADJUST_HOW_WELL_THE_TEXTURES + cvar "ui_r_texturemode" + + visible 0 + + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name simple_shaders + group video + type ITEM_TYPE_MULTI + text @MENUS_DETAILED_SHADERS + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_HIDE_OR_UNHIDE_TEXTURES + cvar "ui_r_detailtextures" + + visible 0 + + mouseenter + { + show highlight10 + } + mouseexit + { + hide highlight10 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + + + itemDef + { + name video_sync + group video + type ITEM_TYPE_MULTI + text @MENUS_VIDEO_SYNC + cvar "r_swapInterval" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_VIDEO_SYNC_DESC + + mouseenter + { + show button_glow + setitemrect button_glow 260 328 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + + } + } + + itemDef + { + name compress_textures + group video_obsolete + type ITEM_TYPE_MULTI + text @MENUS_COMPRESSED_TEXTURES + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_TAKE_ADVANTAGE_OF_3D + cvar "ui_r_ext_compress_textures" + + visible 0 + + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + action + { + play "sound/interface/button1.wav" + uiScript glCustom + setcvar ui_r_modified 1 + show applyChanges + } + } + + + + // APPLY CHANGES BUTTON + itemDef + { + name applybutton_glow + group none + style WINDOW_STYLE_SHADER + rect 260 384 340 14 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 0.5 0.5 1 + visible 0 + decoration + } + + itemDef + { + name applyChanges + group none + text @MENUS_APPLY_CHANGES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 260 384 340 14 + textaligny 0 + font 4 + textscale 1 + textalignx 170 + textalign ITEM_ALIGN_CENTER + forecolor 1 0 0 1 + backcolor 0 0 1 0 + visible 0 + + mouseEnter + { + show applybutton_glow + } + mouseExit + { + hide applybutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + show vidrestart ; + setfocus vidrestart_no ; + hide video ; + hide video2 ; + hide applybutton_glow ; + hide applyChanges ; + } + } + +//---------------------------------------------------------------------------------------------- +// VIDEO RESTART +//---------------------------------------------------------------------------------------------- +// Faint red box + itemDef + { + name vidrestart_background + group vidrestart + style WINDOW_STYLE_FILLED + rect 260 185 340 225 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } + + itemDef + { + name vidrestart_text1 + group vidrestart + text @MENUS_THIS_WILL_APPLY_VIDEO + text2 @MENUS_AND_RETURNTO_GAME + rect 305 230 290 20 + textalign ITEM_ALIGN_CENTER + text2aligny 18 + textalignx 145 + font 2 + textscale 1 + forecolor 1 1 0 1 + visible 0 + decoration + } + + itemDef + { + name vidrestart_text2 + group vidrestart + text @MENUS_VID_RESTART3 + rect 305 300 290 20 + textalign ITEM_ALIGN_CENTER + textalignx 145 + font 2 + textscale 1 + forecolor 1 1 0 1 + visible 0 + } + + + + itemDef + { + name vidrestart_yes_button + group none + style WINDOW_STYLE_SHADER + rect 467 386 120 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // YES button + itemDef + { + name vidrestart_yes + group vidrestart + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 467 386 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + descText @MENUS_APPLY_CHANGES_AND_RETURN + forecolor 1 .682 0 1 + visible -1 + + action + { + play "sound/interface/button1.wav" ; + close all ; + uiScript updatevideosetup ; + } + mouseEnter + { + show vidrestart_yes_button + } + mouseExit + { + hide vidrestart_yes_button + } + + } + + itemDef + { + name vidrestart_no_button + group vidrestart + style WINDOW_STYLE_SHADER + rect 305 386 120 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // CANCEL button + itemDef + { + name vidrestart_no + group vidrestart + text @MENUS_NO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 305 386 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -1 + descText @MENUS_DO_NOT_APPLY_CHANGES + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/esc.wav" + setfocus video1menubutton ; + show setup_background ; + hide vidrestart ; + show video ; + hide video2 ; + show applyChanges ; + hide vidrestart_yes_button ; + hide vidrestart_no_button ; + } + mouseEnter + { + show vidrestart_no_button + } + mouseExit + { + hide vidrestart_no_button + } + + } + +//---------------------------------------------------------------------------------------------- +// VIDEO 2 +//---------------------------------------------------------------------------------------------- + itemDef + { + name gamma_text + group video2 + style WINDOW_STYLE_SHADER + rect 290 198 280 36 + background "gfx/menus/greyscale" // greyscale + forecolor 1 1 1 1 + visible 0 + decoration + + } + + itemDef + { + name bright_text + group video2 + text @MENUS_ADJUST_BRIGHTNESS_SLIDER + text2 @MENUS_THE_NUMBER_6_CAN_BARELY + text2aligny 14 + textalignx 170 + textaligny 0 + font 4 + textscale 1 + rect 260 234 340 20 + textalign ITEM_ALIGN_CENTER + forecolor .549 .854 1 1 + visible 0 + decoration + } + + itemDef + { + name brightness + group video2 + type ITEM_TYPE_SLIDER + text @MENUS_VIDEO_BRIGHTNESS + cvarfloat "r_gamma" 1 .5 3 + rect 260 267 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ADJUST_THE_BRIGHTNESS + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 265 340 20 + } + mouseexit + { + hide button_glow + } + } + + itemDef + { + name shadows + group video2 + type ITEM_TYPE_MULTI + text @MENUS_SHADOWS + descText @MENUS_SHADOWS_DESC + cvar "cg_shadows" + cvarFloatList + { + @MENUS_NONE 0 + @MENUS_SHADOWS_SIMPLE 1 + @MENUS_SHADOWS_VOLUMETRIC 2 +// "Projected" 3 + } + rect 260 287 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show button_glow + setitemrect button_glow 260 285 340 20 + } + mouseexit + { + hide button_glow + } + + } + + + itemDef + { + name dynamic_light + group video2 + type ITEM_TYPE_MULTI + text @MENUS_DYNAMIC_LIGHTS + cvar "r_dynamiclight" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 301 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_TO_TURN_ON_MOVING + + mouseenter + { + show button_glow + setitemrect button_glow 260 301 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + + } + } + + itemDef + { + name dynamic_glow + group video2 + type ITEM_TYPE_MULTI + text @MENUS_DYNAMIC_GLOW + cvar "r_dynamicglow" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 315 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_DYNAMIC_GLOW_DESC + + mouseenter + { + show button_glow + setitemrect button_glow 260 315 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + + } + } + + itemDef + { + name light_flares + group video2 + type ITEM_TYPE_MULTI + text @MENUS_LIGHT_FLARES + cvar "r_flares" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 329 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_TO_SHOW_HALOS + + mouseenter + { + show button_glow + setitemrect button_glow 260 329 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + } + } + + itemDef + { + name wall_marks + group video2 + type ITEM_TYPE_MULTI + text @MENUS_WALL_MARKS + cvar "cg_marks" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 343 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_TO_DISPLAY_SCORCH + + mouseenter + { + show button_glow + setitemrect button_glow 260 343 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + } + } + + itemDef + { + name video_mode + group video2 + type ITEM_TYPE_SLIDER + text @MENUS_ANISOTROPIC_FILTERING + cvarTest r_ext_texture_filter_anisotropic_avail + hideCvar { 0 } + cvarfloat "r_ext_texture_filter_anisotropic" 2 0 16 + rect 260 359 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + + descText @MENUS_TOGGLE_ADVANCED_TEXTURE + + mouseenter + { + show button_glow + setitemrect button_glow 260 359 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + } + } + + itemDef + { + name advancedvideo + group video2 + type ITEM_TYPE_BUTTON + text @MENUS_SHOW_DRIVER_INFO + rect 260 393 340 14 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 170 + textaligny 0 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + descText @MENUS_SHOW_ADVANCED_INFORMATION + + mouseenter + { + show button_glow + setitemrect button_glow 260 392 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + setfocus video2menubutton + open videodriverMenu + } + } + +//---------------------------------------------------------------------------------------------- +// SOUND FIELDS +//---------------------------------------------------------------------------------------------- + itemDef + { + name effects_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_EFFECTS_VOLUME + cvarfloat "s_volume" 0 0 1 + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ADJUST_VOLUME_FOR_SOUND + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + } + + itemDef + { + name music_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_MUSIC_VOLUME + cvarfloat "s_musicvolume" 0 0 1 + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ADJUST_VOLUME_FOR_MUSIC + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + } + + itemDef + { + name voice_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_VOICE_VOLUME + cvarfloat "s_volumevoice" 0 0 1 + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ADJUST_VOLUME_FOR_SPEECH + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + } + + itemDef + { + name sound_quality + group sound + type ITEM_TYPE_MULTI + text @MENUS_SOUND_QUALITY + cvar "s_khz" + cvarFloatList { @MENUS_LOW 11 @MENUS_HIGH 22 } + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TRADE_CLARITY_OF_SOUND + cvarTest "s_UseOpenAL" + hideCvar { 1 } + + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + action + { + play "sound/interface/button1.wav" + uiScript update s_khz + } + } + + itemDef + { + name eax + group sound + type ITEM_TYPE_MULTI + cvar "s_UseOpenAL" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + text @MENUS_EAX + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_EAX_DESC + action + { + play "sound/interface/button1.wav" + uiScript update s_khz + } + + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + } + + itemDef + { + name eax_icon + group sound + style WINDOW_STYLE_SHADER + rect 366 316 128 64 + background "gfx/menus/eax" + forecolor 1 1 1 1 + visible 1 + decoration + cvarTest "s_UseOpenAL" + hideCvar { 0 } + } + +//-------------------------------------------------------------------------------------------- +// OPTION FIELDS +//---------------------------------------------------------------------------------------------- + itemDef + { + name draw_crosshair + group options + type ITEM_TYPE_MULTI + text @MENUS_DRAW_CROSSHAIR + cvar "cg_drawcrosshair" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_TO_SHOW_OR_HIDE + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + } + + + itemDef + { + name identifytarget + group options + type ITEM_TYPE_MULTI + text @MENUS_IDENTIFY_TARGET + cvar "cg_crosshairIdentifyTarget" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_TO_HAVE_THE_CROSSHAIR + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight2 + } + mouseexit + { + hide highlight2 + } + } + + + itemDef + { + name slowmo + group options + type ITEM_TYPE_MULTI + text @MENUS_SLOW_MOTION_DEATH + cvar "d_slowmodeath" + cvarFloatList + { + @MENUS_NEVER 0 + @MENUS_ON_DEATH 1 + @MENUS_RARELY 2 + @MENUS_SLOW_MO_NORMAL 3 + @MENUS_OFTEN 4 + @MENUS_FREQUENTLY 5 + @MENUS_EXCESSIVELY 6 + } + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_SELECT_THE_FREQUENCY + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + } + + + itemDef + { + name force1st + group options + type ITEM_TYPE_MULTI + text @MENUS_1ST_PERSON_GUNS + cvar "cg_gunAutoFirst" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_WHEN_PUTTING_AWAY_SABER + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + } + + itemDef + { + name footsteps + group options + type ITEM_TYPE_MULTI + text @MENUS_FOOTSTEPS + desctext @MENUS_FOOTSTEPS_DESC + cvar "cg_footsteps" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_SOUNDS 1 + @MENUS_SOUNDS_AND_EFFECTS 2 + @MENUS_SOUNDS_EFFECTS_GRAPHICS 3 + } + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight6 + } + mouseexit + { + hide highlight6 + } + } + + + itemDef + { + name dismemberment + group options + type ITEM_TYPE_MULTI + text @MENUS_DISMEMBERMENT + cvar "g_dismemberment" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 3 + } + cvarTest ui_iscensored + hideCvar { 1 } + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_SELECT_WHAT_LIGHTSABER + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + } + +// Weapon Sway. Yes, this is nutty. Two cvars here, one removes weapon sway, the other adds it. + itemDef + { + name weaponswayon + group options + type ITEM_TYPE_MULTI + text @MENUS_VIEW_SWAYING + descText @MENUS_VIEW_SWAYING_DESC + cvar "ui_disableWeaponSway" + cvarFloatList + { + @MENUS_ON 0 + @MENUS_OFF 1 + } + cvarTest "ui_disableWeaponSway" + showCvar + { + "0" + } + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + + action + { + play "sound/interface/button1.wav" + exec "exec noMotion.cfg" ; + show weaponswayoff ; + setfocus weaponswayoff + } + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name weaponswayoff + group options + type ITEM_TYPE_MULTI + text @MENUS_VIEW_SWAYING + descText @MENUS_VIEW_SWAYING_DESC + cvar "ui_disableWeaponSway" + cvarFloatList + { + @MENUS_ON 0 + @MENUS_OFF 1 + } + cvarTest "ui_disableWeaponSway" + hideCvar + { + "0" + } + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + + action + { + play "sound/interface/button1.wav" + exec "exec restoreMotion.cfg" ; + show weaponswayon ; + setfocus weaponswayon + } + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name text + group options + type ITEM_TYPE_MULTI + text @MENUS_TEXT + cvar "se_language" + feeder 23 //FEEDER_PLAYER_SPECIES + cvarStrList feeder + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_CHOOSE_THE_LANGUAGE_FOR + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight10 + } + mouseexit + { + hide highlight10 + } + } + + itemDef + { + name voice + group options + type ITEM_TYPE_MULTI + text @MENUS_VOICE + cvarTest com_demo + hideCvar { 1 } + cvar "s_language" + cvarStrList + { + "English" "english" + @MENUS_LANG_FRENCH "francais" + "Deutsch" "deutsch" + "Español" "espanol" + } + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_CHOOSE_THE_LANGUAGE_TO + + action + { + play "sound/interface/button1.wav" + show langapply + } + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + } + + itemDef + { + name voice + group options + type ITEM_TYPE_MULTI + text @MENUS_SUBTITLES + cvar "g_subtitles" + cvarFloatList + { + @MENUS_NONE 0 + @MENUS_IN_CINEMATICS 2 +// @MENUS_ALL_VOICEOVERS 1 + } + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_WHETHER_SUBTITLES + + action + { + play "sound/interface/button1" + } + mouseenter + { + show highlight12 + } + mouseexit + { + hide highlight12 + } + } + + // APPLY CHANGES button + itemDef + { + name langapply + group options + text @MENUS_SET_LANGUAGE + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 260 356 340 14 + textalign ITEM_ALIGN_CENTER + textalignx 174 + textaligny -5 + font 2 + textscale 1 + forecolor 1 .682 0 1 + descText @MENUS_SET_LANGUAGE_INFO + visible 0 + + action + { + play "sound/interface/button1" + exec "snd_restart" + hide langapply + hide highlight13 + } + mouseEnter + { + show highlight13 + } + mouseExit + { + hide highlight13 + } + + } + +//---------------------------------------------------------------------------------------------- +// MOD GAME MENU specific stuff +//---------------------------------------------------------------------------------------------- + itemDef + { + name serverinfo + group mods + rect 270 196 320 176 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 16 + font 2 + textscale 1 + border 1 + bordersize 1 + bordercolor .5 .5 .5 .5 + forecolor 1 .682 0 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder 9 + notselectable + visible 0 + columns 1 2 40 280 + } + + itemDef + { + name loadmod_button + group none + style WINDOW_STYLE_SHADER + rect 260 384 340 14 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadmod + group mods + text @MENUS_LOAD_MOD + descText @MENUS_LOAD_CHOSEN_MOD + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 260 384 340 14 + textalign ITEM_ALIGN_CENTER + textalignx 170 + textaligny 0 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/button1.wav" + uiScript RunMod ; + } + + mouseEnter + { + show loadmod_button + } + mouseExit + { + hide loadmod_button + } + } + +//---------------------------------------------------------------------------------------------- +// RESET DEFAULTS +//---------------------------------------------------------------------------------------------- +// Faint red box + itemDef + { + name vidrestart_background + group defaults + style WINDOW_STYLE_FILLED + rect 260 185 340 225 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } + + itemDef + { + name button_glow2 + group highlights + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_WARNING + rect 260 202 340 20 + textalign ITEM_ALIGN_CENTER + textalignx 170 + font 2 + textscale 1 + forecolor 1 0 0 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_THIS_WILL_SET_ALL_GAME + text2 @MENUS_TO_THEIR_FACTORY_SETTINGS + rect 260 230 340 20 + textalign ITEM_ALIGN_CENTER + textalignx 174 + text2aligny 20 + font 2 + textscale 1 + forecolor 1 0 0 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_VID_RESTART3 + rect 260 286 340 20 + textalign ITEM_ALIGN_CENTER + textalignx 174 + font 2 + textscale 1 + forecolor 1 0 0 1 + visible 0 + decoration + } + + // NO button - return to Main Menu + itemDef + { + name default_no_button + group defaults + text @MENUS_NO + descText @MENUS_DO_NOT_RESET_DEFAULT + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 276 356 120 32 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/esc.wav" + hide highlights ; + uiscript clearmouseover default_no_button ; + close all ; + open ingamemainMenu ; + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 276 356 120 32 + } + mouseExit + { + hide button_glow2 + } + } + + // YES button - lose reset defaults + itemDef + { + name default_yes + group defaults + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 466 356 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + descText @MENUS_USE_DEFAULT_SETTINGS + forecolor 1 .682 0 1 + visible 0 + + action + { + play "sound/interface/button1.wav" ; + hide highlights ; + close all ; + uiscript resetdefaults + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 466 356 120 32 + } + mouseExit + { + hide button_glow2 + } + + } + } +} + diff --git a/base/ui/ingamevid_warning.menu b/base/ui/ingamevid_warning.menu new file mode 100644 index 0000000..8f6c1f4 --- /dev/null +++ b/base/ui/ingamevid_warning.menu @@ -0,0 +1,197 @@ +//------------------------------------------------------------------------------------------------ +// INGAME VIDEO WARNING +//------------------------------------------------------------------------------------------------ +{ + menuDef + { + name "ingamevideowarningMenu" + visible 0 + fullScreen 1 // MENU_TRUE + rect 100 120 440 320 + focusColor 1 1 1 1 // Focus color for text and items + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + descX 320 + descY 350 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + popup + + onESC + { + play "sound/interface/esc.wav" + close all ; + open ingameSetupMenu ; + } + + onClose + { + play "sound/interface/button1.wav" ; + } + + itemDef + { + name warn_background + group none + style WINDOW_STYLE_SHADER + rect 100 120 440 320 + background "gfx/menus/menu_boxred" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// VIDEO WARNING +// +//---------------------------------------------------------------------------------------------- + // Video Warning title + itemDef + { + name vidwarn_title + group vidwarn + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_UNAPPLIED_VIDEO_CHANGES + rect 120 130 400 20 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny -3 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name vidwarn_text1 + group vidwarn + text @MENUS_YOU_HAVE_MADE_CHANGES + rect 210 240 220 20 + textalign ITEM_ALIGN_CENTER + text2aligny 18 + textalignx 110 + font 2 + textscale 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name vidwarn_text2 + group vidwarn + text @MENUS_APPLY_THESE_CHANGES_OR + rect 210 260 220 20 + textalign ITEM_ALIGN_CENTER + textalignx 110 + font 2 + textscale 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name vidwarn_no_button + group vidwarn + style WINDOW_STYLE_SHADER + rect 180 380 120 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // DISCARD button - return to Video Data screen + itemDef + { + name vidwarn_no + group vidwarn + text @MENUS_DISCARD + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 180 380 120 24 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -3 + descText @MENUS_DO_NOT_APPLY_CHANGES + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/esc.wav" + close all ; + uiScript getvid1data ; // Get video settings + open ingameSetupMenu ; + activate ingameSetupMenu video1menubutton + } + mouseEnter + { + show vidwarn_no_button + } + mouseExit + { + hide vidwarn_no_button + } + } + + itemDef + { + name vidwarn_yes_button + group vidwarn + style WINDOW_STYLE_SHADER + rect 340 380 120 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // APPLY button, use settings + itemDef + { + name vidwarn_yes + group vidwarn + text @MENUS_APPLY + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 380 120 24 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -3 + descText @MENUS_APPLY_CHANGES_AND_THEN + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + uiScript setvid1data ingameSetupMenu ; // Set video settings + close all + } + mouseEnter + { + show vidwarn_yes_button + } + mouseExit + { + hide vidwarn_yes_button + } + } + } +} + + + + + + diff --git a/base/ui/jahud.txt b/base/ui/jahud.txt new file mode 100644 index 0000000..9f7797f --- /dev/null +++ b/base/ui/jahud.txt @@ -0,0 +1,5 @@ +// hud menu defs +// +{ + loadMenu { "ui/hud.menu" } +} diff --git a/base/ui/jamp/advancedcreateserver.menu b/base/ui/jamp/advancedcreateserver.menu new file mode 100644 index 0000000..233be9f --- /dev/null +++ b/base/ui/jamp/advancedcreateserver.menu @@ -0,0 +1,1174 @@ +//---------------------------------------------------------------------------------------------- +// MAIN MENU +//---------------------------------------------------------------------------------------------- +//ui_net_gametype +// 0 = FFA +// 1 = DUEL +// 2 = POWER DUEL +// 3 = TEAM FFA +// 4 = SIEGE +// 5 = CTF + +{ + menuDef + { + name "advancedcreateserver" + fullScreen MENU_FALSE + rect 40 50 560 380 // Size and position of the menu + visible MENU_FALSE // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + popup + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + + onOpen + { + uiScript setui_dualforcepower + } + + onESC + { + play "sound/interface/button1.wav" ; + close advancedcreateserver ; + open createserver ; + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 0 0 560 420 + backcolor 0 0 .4 .95 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + /*itemDef + { + name frame_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 560 380 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + decoration + visible 1 + appearance_slot 1 + }*/ + +//------------------------------------------------ +// ADVANCED OPTIONS +//------------------------------------------------ + // Advanced Server title + itemDef + { + name advanced_title + group none + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_SERVER_RULES + rect 115 5 330 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 165 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//--------------------------------- +// +// COMBAT RULES +// +//--------------------------------- + + itemDef + { + name rules_title + group none + text @MENUS_COMBAT_RULES + rect 5 35 280 20 + font 3 + textscale 0.8 + textalign ITEM_ALIGN_CENTER + textalignx 140 + textaligny -2 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//--------------------------------- +// FRIENDLY FIRE +//--------------------------------- + + itemDef + { + name settingsButton1 + style WINDOW_STYLE_SHADER + rect 5 55 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_YESNO + text @MENUS_FRIENDLY_FIRE + descText @MENUS_WHEN_ON_IT_MEANS_TEAMMATES + cvar "g_friendlyfire" + cvarTest "ui_netGameType" + showCvar + { + "2" ; + "3" ; + "4" ; + "5" + } + rect 5 55 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton1 + } + mouseExit + { + hide settingsButton1 + } + } + +//------------------------ +// LIGHTSABER ONLY +//------------------------ + + itemDef + { + name settingsButton2 + style WINDOW_STYLE_SHADER + rect 5 75 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name options + group grpOptions + type ITEM_TYPE_YESNO + text @MENUS_LIGHTSABER_ONLY + descText @MENUS_SABER_ONLY_INFO + cvar "g_weaponDisable" + rect 5 75 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + cvarTest "ui_netGameType" + //Hide in Duel/Power Duel/Siege + hideCvar + { + "1" ; + "2" ; + "4" ; + } + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton2 + } + mouseExit + { + hide settingsButton2 + } + } + + itemDef + { + name options + group grpOptions + type ITEM_TYPE_YESNO + text @MENUS_LIGHTSABER_ONLY + descText @MENUS_SABER_ONLY_INFO + cvar "g_duelWeaponDisable" + rect 5 75 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + cvarTest "ui_netGameType" + //hide in everthing but Duel/Power Duel + showCvar + { + "1" ; + "2" + } + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton2 + } + mouseExit + { + hide settingsButton2 + } + } + +//-------------------------- +// ALLOW PRIVATE DUEL +//-------------------------- + itemDef + { + name settingsButton3 + style WINDOW_STYLE_SHADER + rect 5 95 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name options + group grpOptions + type ITEM_TYPE_YESNO + text @MENUS_ALLOW_SABER_CHALLENGE + descText @MENUS_SABER_CHALLENGE_INFO + cvar "g_privateDuel" + cvarTest "ui_netGameType" + showCvar + { + "0" ; + "3" + } + rect 5 95 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton3 + } + mouseExit + { + hide settingsButton3 + } + } + +//-------------------------------- +// FORCE MASTERY LEVEL +//-------------------------------- + + itemDef + { + name settingsButton4 + style WINDOW_STYLE_SHADER + rect 5 115 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name normal + group grpsettings + text @MENUS_FORCE_MASTERY + descText @MENUS_FORCE_MASTERY_INFO + ownerdraw UI_FORCE_MASTERY_SET + cvarTest "ui_netGameType" + hideCvar + { + "4" + } + rect 5 115 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton4 + } + mouseExit + { + hide settingsButton4 + } + } + +//-------------------------------- +// FORCE POWER DISABLE +//-------------------------------- + itemDef + { + name settingsButton5 + style WINDOW_STYLE_SHADER + rect 5 135 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name options + group grpOptions + type ITEM_TYPE_YESNO + text @MENUS_DISABLE_FORCE + descText @MENUS_DISABLE_FORCE_INFO + cvar "g_forcePowerDisable" + cvarTest "ui_netGameType" + hideCvar + { + "1" ; + "2" ; + "4" + } + rect 5 135 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + uiScript forcePowersDisable ; + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton5 + } + mouseExit + { + hide settingsButton5 + } + } + +// FORCE POWERS setting in Duel/Power Duel + itemDef + { + name options + group grpOptions + type ITEM_TYPE_MULTI + text @MENUS_FORCE_POWERS_TITLE + descText @MENUS_FORCE_POWERS_TITLE_DESC + cvar "g_forcePowerDisable" + cvarTest "ui_netGameType" + showCvar + { + "1" ; + "2" + } + cvar "ui_dualforcepower" + cvarFloatList + { + @MENUS_YES 0 + @MENUS_NO 1 + @MENUS_LIMITED 2 + } + rect 5 135 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + uiScript dualForcePowers ; + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton5 + } + mouseExit + { + hide settingsButton5 + } + } + +//-------------------------------- +// SABERLOCKING +//-------------------------------- + itemDef + { + name settingsButton7 + style WINDOW_STYLE_SHADER + rect 5 175 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_YESNO + text @MENUS_SABERLOCKING + descText @MENUS_SABERLOCKING_DESC + cvar "g_saberLocking" + rect 5 175 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 2 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton7 + } + mouseExit + { + hide settingsButton7 + } + } + +//--------------------------------- +// +// ADVANCED GAME RULES +// +//--------------------------------- + + itemDef + { + name rules_title + group none + text @MENUS_ADV_GAME_RULES + rect 5 205 280 20 + font 3 + textscale 0.8 + textalign ITEM_ALIGN_CENTER + textalignx 140 + textaligny -2 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//-------------------------------- +// AUTOJOIN +//-------------------------------- + itemDef + { + name settingsButton8 + style WINDOW_STYLE_SHADER + rect 5 225 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_YESNO + text @MENUS_AUTO_JOIN + descText @MENUS_SET_TO_ON_TO_HAVE_PLAYERS + cvar "g_teamautojoin" + rect 5 225 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 2 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton8 + } + mouseExit + { + hide settingsButton8 + } + } + + +//-------------------------------- +// FORCE BASED TEAMS +//-------------------------------- + + itemDef + { + name settingsButton11 + style WINDOW_STYLE_SHADER + rect 5 285 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name options + group grpOptions + type ITEM_TYPE_YESNO + text @MENUS_LIGHT_DARK_TEAMS + descText @MENUS_LIGHT_DARK_INFO + cvar "g_forceBasedTeams" + cvarTest "ui_netGameType" + showCvar + { + "3" ; + "5" + } + rect 5 285 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton11 + } + mouseExit + { + hide settingsButton11 + } + } + +//--------------------------------- +// +// SERVER RULES +// +//--------------------------------- + + itemDef + { + name rules_title + group none + text @MENUS_SERVER_RULES + rect 285 35 280 20 + font 3 + textscale 0.8 + textalign ITEM_ALIGN_CENTER + textalignx 140 + textaligny -2 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//------------------------ +// AUTO MAP CYCLE +//------------------------ + itemDef + { + name settingsButton12 + style WINDOW_STYLE_SHADER + rect 285 55 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name auto_map_cycle + group grpsettings + type ITEM_TYPE_YESNO + text @MENUS_AUTO_MAP_CYCLE + cvar "g_autoMapCycle" + rect 285 55 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + descText @MENUS_AUTO_MAP_CYCLE_DESC + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton12 + } + mouseExit + { + hide settingsButton12 + } + } + +//--------------------- +// ALLOW VOTE +//--------------------- + itemDef + { + name settingsButton13 + style WINDOW_STYLE_SHADER + rect 285 75 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_YESNO + text @MENUS_ENABLE_VOTING + cvar "g_allowvote" + rect 285 75 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + descText @MENUS_THIS_ALLOWS_PLAYERS_TO + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton13 + } + mouseExit + { + hide settingsButton13 + } + } + +//---------------------------- +// DEDICATED +//--------------------------- + itemDef + { + name settingsButton14 + style WINDOW_STYLE_SHADER + rect 285 95 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_MULTI + text @MENUS_DEDICATED_SERVER + // syntax for this is cvar name followed by a semicolan separated list of choices first choice equals 0 + // dedicated is a special cvar in that as soon as it is set, the game goes to console only so the ui catches this one specifically + cvar "ui_dedicated" + cvarFloatList + { + @MENUS_NO 0 @MENUS_LAN 1 @MENUS_INTERNET 2 + } + rect 285 95 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + descText @MENUS_A_DEDICATED_SERVER_IS + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton14 + } + mouseExit + { + hide settingsButton14 + } + } + +//------------------------- +// PASSWORD +//------------------------- + itemDef + { + name settingsButton15 + style WINDOW_STYLE_SHADER + rect 285 115 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name expert + group grpsettings + type 4 + text @MENUS_PASSWORD_1 + cvar "g_password" + rect 285 115 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 130 + textaligny 0 + font 4 + textscale 1 + maxchars 8 + forecolor 1 1 1 1 + visible 1 + descText @MENUS_SETS_PASSWORD_FOR_YOUR + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton15 + } + mouseExit + { + hide settingsButton15 + } + } + +//--------------------------------- +// +// CONNECTION RULES +// +//--------------------------------- + + itemDef + { + name rules_title + group none + text @MENUS_CONNECTION_RULES + rect 285 205 280 20 + font 3 + textscale 0.8 + textalign ITEM_ALIGN_CENTER + textalignx 140 + textaligny -2 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//------------------------- +// PURE SERVER +//------------------------- + + itemDef + { + name settingsButton16 + style WINDOW_STYLE_SHADER + rect 285 225 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_YESNO + text @MENUS_PURE_SERVER + descText @MENUS_CLIENT_MUST_HAVE_EXACT + cvar "sv_pure" + rect 285 225 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton16 + } + mouseExit + { + hide settingsButton16 + } + } + +//------------------------- +// AUTO DOWNLOAD +//------------------------- + + itemDef + { + name settingsButton17 + style WINDOW_STYLE_SHADER + rect 285 245 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_YESNO + text @MENUS_AUTO_DOWNLOAD + descText @MENUS_CLIENTS_CAN_DOWNLOAD + cvar "sv_allowdownload" + rect 285 245 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton17 + } + mouseExit + { + hide settingsButton17 + } + } + +//------------------------- +// MIN PING +//------------------------- + itemDef + { + name settingsButton18 + style WINDOW_STYLE_SHADER + rect 285 265 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_NUMERICFIELD + maxchars 4 + text @MENUS_MINIMUM_PING + descText @MENUS_FASTEST_CLIENT_ALLOWED + cvar "sv_minping" + rect 285 265 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton18 + } + mouseExit + { + hide settingsButton18 + } + } + +//------------------------- +// MAX PING +//------------------------- + itemDef + { + name settingsButton19 + style WINDOW_STYLE_SHADER + rect 285 285 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_NUMERICFIELD + maxchars 4 + text @MENUS_MAXIMUM_PING + descText @MENUS_SLOWEST_CLIENT_ALLOWED + cvar "sv_maxping" + rect 285 285 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton19 + } + mouseExit + { + hide settingsButton19 + } + } + +//------------------------- +// MAX RATE +//------------------------- + itemDef + { + name settingsButton20 + style WINDOW_STYLE_SHADER + rect 285 305 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_MAX_RATE + descText @MENUS_DATA_TRANSFER_RATE_TO + cvar "sv_maxrate" + rect 285 305 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton20 + } + mouseExit + { + hide settingsButton20 + } + } + +//------------------------- +// RECONNECT LIMIT +//------------------------- + itemDef + { + name settingsButton21 + style WINDOW_STYLE_SHADER + rect 285 325 280 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_RECONNECT_LIMIT + descText @MENUS_MINIMUM_TIME_ALLOWED + cvar "sv_reconnectlimit" + maxchars 4 + rect 285 325 280 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton21 + } + mouseExit + { + hide settingsButton21 + } + } + +//--------------------------------- +// +// DONE BUTTON +// +//--------------------------------- + + itemDef + { + name doneButton + group none + style WINDOW_STYLE_SHADER + rect 200 352 160 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name done + text @MENUS_DONE + descText @MENUS_GO_BACK_TO_STANDARD_SETTINGS + type ITEM_TYPE_BUTTON + font 3 + textscale 1 + style WINDOW_STYLE_EMPTY + rect 200 352 160 30 + textalignx 80 // Center + textaligny 0 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + close advancedcreateserver ; + open createserver ; + } + mouseEnter + { + show doneButton + } + mouseExit + { + hide doneButton + } + } + + } +} + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/connect.menu b/base/ui/jamp/connect.menu new file mode 100644 index 0000000..fdb4fdb --- /dev/null +++ b/base/ui/jamp/connect.menu @@ -0,0 +1,21 @@ +{ + +menuDef { + name "Connect" + rect 0 0 640 480 + fullScreen MENU_FALSE + visible MENU_FALSE + style WINDOW_STYLE_SHADER + + itemDef + { + name miniFrame + rect 0 0 640 480 + style WINDOW_STYLE_SHADER + background "menu/art/unknownmap_mp" + visible 1 + decoration + } + } + +} \ No newline at end of file diff --git a/base/ui/jamp/controls.menu b/base/ui/jamp/controls.menu new file mode 100644 index 0000000..62a4450 --- /dev/null +++ b/base/ui/jamp/controls.menu @@ -0,0 +1,3765 @@ +//-------------------------------------------------------------- +// +// MULTIPLAYER CONTROLS MENU +// +//-------------------------------------------------------------- +{ + menuDef + { + name "controlsMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 0 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript loadControls; + + show setup_background ; + show movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + hide quickcontrols ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 1 1 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor quickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + + onClose + { + uiScript saveControls + } + + onESC + { + play "sound/interface/menuroam.wav" ; + hide highlights ; + close controlsMenu ; + open main ; + } + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "PLAYER PROFILE" + itemDef + { + name profilebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show profilebutton_glow + } + mouseExit + { + hide profilebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open playerMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Credits hidden button + itemDef + { + name creditsbutton + group othermain +// text @CREDITS + descText @MENUS_SHOW_GAME_CREDITS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 200 144 256 256 + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textalignx 46 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open creditsMenu + } + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // SECOND ROW MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Configure Controls title + itemDef + { + name control_title + group none + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CONFIGURE_CONTROLS + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // movement button + itemDef + { + name movementcontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 80 185 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name movementcontrolbutton + group none + text @MENUS_MOVEMENT + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 185 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_MOVEMENT_KEYS + + mouseEnter + { + show movementcontrolbutton_glow + } + mouseExit + { + hide movementcontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + show movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 1 1 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + + // attack button + itemDef + { + name attackcontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 80 209 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name attackcontrolbutton + group none + text @MENUS_INTERACTION + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 209 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_INTERACTION_DESC + + mouseEnter + { + show attackcontrolbutton_glow + } + mouseExit + { + hide attackcontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + show attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 1 1 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + // Weapons button + itemDef + { + name weaponscontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 80 233 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name weaponscontrolbutton + group none + text @MENUS_WEAPONS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 233 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_WEAPON_CONTROLS + + mouseEnter + { + show weaponscontrolbutton_glow + } + mouseExit + { + hide weaponscontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + hide attackcontrols ; + show weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 1 1 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + // Force Powers button + itemDef + { + name forcecontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 80 257 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name forcecontrolbutton + group none + text @MENUS_FORCE_POWERS_1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 257 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_FORCE_POWER + + mouseEnter + { + show forcecontrolbutton_glow + } + mouseExit + { + hide forcecontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + show forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 1 1 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + // Force Powers button 2 + itemDef + { + name forcecontrolbutton2_glow + group mods + style WINDOW_STYLE_SHADER + rect 80 281 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name forcecontrolbutton2 + text @MENUS_FORCE_POWERS_2 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 281 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_FORCE_POWER + + mouseEnter + { + show forcecontrolbutton2_glow + } + mouseExit + { + hide forcecontrolbutton2_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + show forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 1 1 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + // mousejoystick button + itemDef + { + name mousejoystickcontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 80 305 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name mousejoystickcontrolbutton + group none + text @MENUS_MOUSE_JOYSTICK + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 305 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_MOUSE_AND_JOYSTICK + + mouseEnter + { + show mousejoystickcontrolbutton_glow + } + mouseExit + { + hide mousejoystickcontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + show joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 1 1 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + // other button + itemDef + { + name othercontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 80 329 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name othercontrolbutton + group none + text @MENUS_OTHER + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 329 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_ADDITIONAL + + mouseEnter + { + show othercontrolbutton_glow + } + mouseExit + { + hide othercontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + show othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 1 1 1 ; + } + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 260 185 340 235 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // HIGHLIGHT BARS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name highlight1 + group highlights + style WINDOW_STYLE_SHADER + rect 260 190 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight2 + group highlights + style WINDOW_STYLE_SHADER + rect 260 204 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight3 + group highlights + style WINDOW_STYLE_SHADER + rect 260 218 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight4 + group highlights + style WINDOW_STYLE_SHADER + rect 260 232 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight5 + group highlights + style WINDOW_STYLE_SHADER + rect 260 246 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight6 + group highlights + style WINDOW_STYLE_SHADER + rect 260 260 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight7 + group highlights + style WINDOW_STYLE_SHADER + rect 260 274 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight8 + group highlights + style WINDOW_STYLE_SHADER + rect 260 288 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight9 + group highlights + style WINDOW_STYLE_SHADER + rect 260 302 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight10 + group highlights + style WINDOW_STYLE_SHADER + rect 260 316 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight11 + group highlights + style WINDOW_STYLE_SHADER + rect 260 330 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight12 + group highlights + style WINDOW_STYLE_SHADER + rect 260 344 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight13 + group highlights + style WINDOW_STYLE_SHADER + rect 260 358 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight14 + group highlights + style WINDOW_STYLE_SHADER + rect 260 372 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight15 + group highlights + style WINDOW_STYLE_SHADER + rect 260 386 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight16 + group highlights + style WINDOW_STYLE_SHADER + rect 260 400 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// MOVEMENT BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name movement1 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_WALK_FORWARD + cvar "+forward" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_MOVES_PLAYER_FORWARD + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + } + + + itemDef + { + name movement2 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_BACKPEDAL + cvar "+back" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_MOVES_PLAYER_BACKWARD + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + itemDef + { + name movement3 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_TURN_LEFT + cvar "+left" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ROTATES_PLAYER_LEFT + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + name movement4 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_TURN_RIGHT + cvar "+right" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ROTATES_PLAYER_RIGHT + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + name movement5 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_RUN_WALK + cvar "+speed" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_IF_HELD_TOGGLES_BETWEEN + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + itemDef + { + name movement6 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_STEP_LEFT + cvar "+moveleft" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_STEPS_PLAYER_TO_THE_LEFT + + action + { + play sound/interface/button1 + } + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + } + + itemDef + { + name movement7 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_STEP_RIGHT + cvar "+moveright" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_STEPS_PLAYER_TO_THE_RIGHT + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + + itemDef + { + name movement8 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_SIDESTEP_TURN + cvar "+strafe" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_HELD_ALLOWS_PLAYER_TO + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + } + + itemDef + { + name movement12 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_UP_JUMP + cvar "+moveup" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_MAKES_PLAYER_JUMP_IF + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + } + + itemDef + { + name movement13 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_DOWN_CROUCH + cvar "+movedown" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_MAKES_PLAYER_CROUCH_TO + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight13 + show keybindstatus + } + mouseexit + { + hide highlight13 + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// +// INTERACTION BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name attacklook1 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_ATTACK + cvar "+attack" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ATTACKS_WITH_READIED + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + } + + itemDef + { + name attacklook2 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_ALT_ATTACK + cvar "+altattack" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ATTACKS_WITH_ALTERNATE + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + itemDef + { + name attacksaber + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LIGHTSABER_STYLE + cvar "saberAttackCycle" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_CYCLES_BETWEEN_AVAILABLE + action + { + play sound/interface/button1 + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + group attackcontrols + name attacklook + type ITEM_TYPE_BIND + text @MENUS_USE + cvar "+use" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 3 + descText @MENUS_ACTIVATES_WORLD_DEVICES + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_USE_HELD_ITEM + cvar "+button2" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_ACTIVATES_CURRENTLY_SELECTED + + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_NEXT_INVENTORY + cvar "invnext" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_SELECTS_NEXT_USABLE_ITEM + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_PREV_INVENTORY + cvar "invprev" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_SELECTS_PREVIOUS_USABLE + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name attacklook4 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LOOK_UP + cvar "+lookup" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 4 + descText @MENUS_TILTS_VIEW_UPWARDS + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + + itemDef + { + name attacklook5 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LOOK_DOWN + cvar "+lookdown" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + descText @MENUS_TILTS_VIEW_DOWNWARDS + forecolor .615 .615 .956 1 + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_MOUSE_LOOK + cvar "+mlook" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 0 + // appearance_slot 6 + descText @MENUS_IF_HELD_ALLOWS_PLAYER + forecolor .615 .615 .956 1 + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_CENTER_VIEW + cvar "centerview" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 0 + // appearance_slot 6 + descText @MENUS_RETURNS_VIEW_TO_HORIZONTAL + forecolor .615 .615 .956 1 + + mouseenter + { + show highlight13 + show keybindstatus + } + mouseexit + { + hide highlight13 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + +//---------------------------------------------------------------------------------------------- +// +// WEAPON BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name weapon1 + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_MELEE_LIGHTSABER + cvar "weapon 1" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 0 + descText @MENUS_READIES_LIGHTSABER + forecolor .615 .615 .956 1 + + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name weapon3 + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_PISTOL + cvar "weapon 2" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 0 + descText @MENUS_READIES_THE_BLASTER_PISTOL + forecolor .615 .615 .956 1 + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_RIFLE + cvar "weapon 3" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 0 + descText @MENUS_READIES_THE_E_11_BLASTER + forecolor .615 .615 .956 1 + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_DISRUPTOR_RIFLE + cvar "weapon 4" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_READIES_THE_TENLOSS_DXR_6 + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_BOWCASTER + cvar "weapon 5" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_READIES_THE_WOOKIEE_BOWCASTER + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_HEAVY_REPEATER + cvar "weapon 6" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_READIES_THE_IMPERIAL + + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_DEMP_2 + cvar "weapon 7" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 7 + descText @MENUS_READIES_THE_DEMP2_GUN + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_FLECHETTE + cvar "weapon 8" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 8 + descText @MENUS_READIES_THE_GOLAN_ARMS + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide keybindstatus + hide highlight8 + } + action + { + play "sound/interface/button1.wav" ; + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_CONC_RIFLE_SETUP + cvar "weapon 13" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 8 + descText @MENUS_READIES_THE_CONC_RIFLE + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide keybindstatus + hide highlight9 + } + action + { + play "sound/interface/button1.wav" ; + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_MERR_SONN + cvar "weapon 9" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 9 + descText @MENUS_READIES_THE_MERR_SONN + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_THROWABLE_WEAPONS + cvar "weapon 10" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 10 + descText @MENUS_TOGGLES_BETWEEN_DETONATORS + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_NEXT_WEAPON + cvar "weapnext" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 10 + descText @MENUS_SELECTS_THE_NEXT_WEAPON + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_PREVIOUS_WEAPON + cvar "weapprev" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 11 + descText @MENUS_SELECTS_THE_PREVIOUS + + mouseenter + { + show highlight13 + show keybindstatus + } + mouseexit + { + hide highlight13 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + +//---------------------------------------------------------------------------------------------- +// +// FORCE BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name force2 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PUSH + cvar "force_throw" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_USES_FORCE_PUSH_ABILITY + + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name force3 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PULL + cvar "force_pull" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 2 + descText @MENUS_USES_FORCE_PULL_ABILITY + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name force4 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_SPEED + cvar "force_speed" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 3 + descText @MENUS_USES_FORCE_SPEED_ABILITY + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name force4 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_SIGHT + cvar "force_seeing" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_FORCE_SIGHT_ABILITY + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_USE_FORCE_POWER + cvar "+useforce" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 8 + descText @MENUS_USES_CURRENTLY_SELECTED + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide highlight9 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_NEXT + cvar "forcenext" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 9 + descText @MENUS_SELECTS_NEXT_AVAILABLE + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PREVIOUS + cvar "forceprev" + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 10 + descText @MENUS_SELECTS_PREVIOUS_AVAILABLE + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + +//---------------------------------------------------------------------------------------------- +// +// FORCE BINDING 2 (light / dark) +// +//---------------------------------------------------------------------------------------------- + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_PROTECT + cvar "force_protect" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_FORCE_PROTECT_ABILITY + + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_ABSORB + cvar "force_absorb" + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USE_ABSORB + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_HEAL + cvar "force_heal" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_FORCE_HEAL_ABILITY + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_HEAL_OTHER + cvar "force_healother" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USE_HEAL_OTHER + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_MINDTRICK + cvar "force_distract" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_MIND_TRICK_ABILITY + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_GRIP + cvar "+force_grip" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_GRIP_FORCE_ABILITY + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_DRAIN + cvar "+force_drain" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_DRAIN_FORCE_ABILITY + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_LIGHTNING + cvar "+force_lightning" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_LIGHTNING_FORCE + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide highlight9 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_RAGE + cvar "force_rage" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_RAGE_FORCE_ABILITY + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_TEAM_POWER + cvar "force_forcepowerother" + rect 260 330 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_TEAM_POWER_FORCE + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + +//---------------------------------------------------------------------------------------------- +// +// MOUSE/JOYSTICK KEY BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name mousejoystick1 + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_FREE_LOOK + cvar "cl_freelook" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_TOGGLE_TO_ALLOW_PLAYER + + mouseenter + { + show highlight1 + } + + mouseexit + { + hide highlight1 + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_SLIDER + text @MENUS_SENSITIVITY + cvarfloat "sensitivity" 5 2 30 + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 2 + descText @MENUS_ADJUSTS_CHARACTER_REACTION + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_INVERT_MOUSE + cvar "ui_mousePitch" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 3 + descText @MENUS_TOGGLE_TO_TILT_VIEW_IN + + action + { + play "sound/interface/button1.wav" ; + uiScript update ui_mousePitch + } + mouseenter + { + show highlight5 + } + + mouseexit + { + hide highlight5 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_SMOOTH_MOUSE + cvar "m_filter" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 4 + descText @MENUS_WHEN_TURNED_ON_MOUSE + + mouseenter + { + show highlight6 + } + + mouseexit + { + hide highlight6 + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_ENABLE_JOYSTICK + cvar "in_joystick" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_TURNED_ON_GAME_SEARCHES + + mouseenter + { + show highlight9 + } + + mouseexit + { + hide highlight9 + } + action + { + play "sound/interface/button1.wav" ; + exec in_restart + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_SLIDER + text @MENUS_JOYSTICK_THRESHOLD + cvarfloat "joy_threshold" .15 .05 .75 + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 7 + descText @MENUS_ADJUSTS_THE_SIZE_OF_THE + + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_X_AXIS_AS_BUTTONS + cvar "joy_xbutton" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 8 + descText @MENUS_WHEN_OFF_HORIZONTAL + + mouseenter + { + show highlight13 + } + + mouseexit + { + hide highlight13 + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_Y_AXIS_AS_BUTTONS + cvar "joy_ybutton" + rect 260 370 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 9 + descText @MENUS_WHEN_OFF_VERTICAL_STICK + + mouseenter + { + show highlight14 + } + + mouseexit + { + hide highlight14 + } + action + { + play "sound/interface/button1.wav" ; + } + } + +//---------------------------------------------------------------------------------------------- +// +// OTHER +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name other1 + group othercontrols + type ITEM_TYPE_YESNO + text @MENUS_ALWAYS_RUN + cvar "cl_run" + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_WHEN_ON_PLAYER_ALWAYS + + mouseenter + { + show highlight1 + } + + mouseexit + { + hide highlight1 + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_MULTI + text @MENUS_AUTO_SWITCH + cvar "cg_autoswitch" + cvarFloatList + { + @MENUS_DON_T_SWITCH 0 + @MENUS_BEST_SAFE_WEAPON 1 + @MENUS_ALWAYS_BEST_WEAPON 2 + } + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_CHOOSE_WHETHER_TO_SWITCH + mouseenter + { + show highlight2 + } + mouseexit + { + hide highlight2 + } + action + { + play "sound/interface/button1.wav" + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_CHAT + cvar "messagemode" + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_SENDS_A_CHAT_MESSAGE + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_TEAM_CHAT + cvar "messagemode2" + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_A_CHAT_MESSAGE_TO_EVERYONE + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_VC_CONTROLS + cvar "voicechat" + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_VC_CONTROLS_DESC + + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_AUTOMAP + cvar "automap_toggle" + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_AUTOMAP_DESC + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_SHOW_SCORES + cvar "+scores" + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_SHOWS_THE_CURRENT_SCORES + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_SABER_CHALLENGE + cvar "engage_duel" + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_INFO_SABER_CHALLENGE + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide highlight9 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_3RD_PERSON + cvar "cg_thirdperson !" + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_CHANGES_VIEW_BETWEEN + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_TAUNT + cvar "taunt" + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TAUNT_DESC + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_BOW + cvar "bow" + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_BOW_DESC + + mouseenter + { + show highlight13 + show keybindstatus + } + mouseexit + { + hide highlight13 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_MEDITATE + cvar "meditate" + rect 260 372 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_MEDITATE_DESC + + mouseenter + { + show highlight14 + show keybindstatus + } + mouseexit + { + hide highlight14 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_FLOURISH + cvar "flourish" + rect 260 386 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_FLOURISH_DESC + + mouseenter + { + show highlight15 + show keybindstatus + } + mouseexit + { + hide highlight15 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_GLOAT + cvar "gloat" + rect 260 400 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_GLOAT_DESC + + mouseenter + { + show highlight16 + show keybindstatus + } + mouseexit + { + hide highlight16 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + //---------------------------------------------------------------------------------------------- + // + // Text + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name keyBindStatus + group none + ownerdraw 250 // UI_KEYBINDSTATUS + text @MENUS_BLANK_1 + rect 375 434 0 0 + textStyle 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 0 + decoration + } + + itemDef + { + name slider_message + group none + text @MENUS_MOVE_THE_SLIDER_TO_INCREASE + rect 375 434 0 0 + textStyle 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 0 + decoration + } + + itemDef + { + name yesno_message + group none + text @MENUS_CLICK_ON_FIELD_TO_TOGGLE + rect 375 434 0 0 + textStyle 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 0 + decoration + } + + itemDef + { + name multi_message + group none + text @MENUS_CLICK_ON_FIELD_TO_CHANGE + rect 375 434 0 0 + textStyle 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 0 + decoration + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/createfavorite.menu b/base/ui/jamp/createfavorite.menu new file mode 100644 index 0000000..872aaae --- /dev/null +++ b/base/ui/jamp/createfavorite.menu @@ -0,0 +1,222 @@ +//---------------------------------------------------------------------------------------------- +// +// PASSWORD POPUP MENU +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "createfavorite_popmenu" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 200 150 240 180 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 320 + descY 314 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + popup + + onOpen + { + setfocus addressEntry + } + onESC + { + play "sound/interface/esc.wav" ; + close createfavorite_popmenu + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 0 0 240 180 + backcolor 0 0 .35 .9 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 1 + decoration + } + + itemDef + { + name title_glow + group none + style WINDOW_STYLE_SHADER + rect 0 4 270 22 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // SCREEN TITLE + itemDef + { + name title + text @MENUS_FAVORITE_SERVER_INFO + style WINDOW_STYLE_EMPTY + rect 5 5 220 20 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 110 + textaligny 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + //--------------------------------------------- + // ENTER NAME AND ADDRESS + //--------------------------------------------- +/* + itemDef + { + name nameTitle + text @MENUS_FAVE_NAME + style 0 + decoration + font 1 + textscale .8 + rect 177 126 110 20 + textalign ITEM_ALIGN_LEFT + textalignx 1 + textaligny 3 + forecolor 1 1 1 1 + visible 1 + } + + itemDef + { + name nameEntry + style 1 + maxchars 15 + text @MENUS_BLANK_1 + descText @MENUS_FAVE_NAME_DESC + font 1 + textscale .8 + TYPE 4 + cvar "ui_favoriteName" + rect 265 126 150 20 + textalign 0 + textalignx -16 + textaligny 3 + forecolor 1 1 1 1 + backcolor .25 .25 .25 .5 + visible 1 + mouseenter + { + setitemcolor nameEntry backcolor .25 .25 .25 .75 + } + mouseexit + { + setitemcolor nameEntry backcolor .25 .25 .25 .5 + } + } +*/ + + + itemDef + { + name addressTitle + text @MENUS_ENTER_IP_ADDRESS + style 0 + decoration + font 2 + textscale .8 + rect 10 35 220 20 + textalign ITEM_ALIGN_CENTER + textalignx 110 + textaligny 3 + forecolor .549 .854 1 1 + visible 1 + } + + itemDef + { + name addressEntry + style 1 + maxPaintChars 21 + text " " + descText @MENUS_INPUT_IP_ADDRESS_OF_SERVER + font 2 + textscale .8 + TYPE 4 + maxchars 21 + cvar "ui_favoriteAddress" + rect 10 59 220 20 + textalign ITEM_ALIGN_LEFT + textalignx 42 + textaligny -2 + forecolor 1 1 1 1 + border 1 + bordercolor .79 .64 .22 1 + backcolor .25 .25 .25 .5 + visible 1 + mouseenter + { + setitemcolor addressEntry backcolor .25 .25 .25 .75 + } + + mouseexit + { + setitemcolor addressEntry backcolor .25 .25 .25 .5 + } + } + + itemDef + { + name doneText + text @MENUS_DONE + descText @MENUS_FINISHED_INPUTTING_INFO + type 1 + font 3 + textscale .8 + style WINDOW_STYLE_FILLED + rect 20 110 200 30 + textalign ITEM_ALIGN_CENTER + textalignx 100 + textaligny 5 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript CreateFavorite + close createfavorite_popmenu + } + mouseEnter + { + show button_glow + setitemrect button_glow 10 113 240 26 + } + mouseExit + { + hide button_glow + } + } + } +} diff --git a/base/ui/jamp/createserver.menu b/base/ui/jamp/createserver.menu new file mode 100644 index 0000000..0b9c4e6 --- /dev/null +++ b/base/ui/jamp/createserver.menu @@ -0,0 +1,2011 @@ +//---------------------------------------------------------------------------------------------- +// +// CREATE SERVER MENU +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "createserver" + fullScreen MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onEsc + { + setcvartocvar password g_password + close createserver + open multiplayermenu + uiscript checkservername + } + + onOpen + { + setcvartocvar password g_password + uiScript loadArenas + hide accept_alt + show accept + hide back_alt + show back + hide grpmessage + uiScript setBotButtons + uiscript clampmaxplayers + uiscript checkservername + } + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + +///////////////////////////////////// +// Create Server title +///////////////////////////////////// + + itemDef + { + name create_title + group none + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CREATE_A_NEW_MULTIPLAYER + rect 50 4 540 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 270 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +///////////////////////////////////// +// Blue backgrounds +///////////////////////////////////// + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 2 50 200 192 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 204 50 429 192 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 204 244 429 178 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + +//--------------------------------------------- +// GAME TYPE SELECTION FIELD +//--------------------------------------------- + itemDef + { + name gametypefieldButton + group none + style WINDOW_STYLE_SHADER + rect 5 25 255 26 + background "gfx/menus/menu_blendbox3" + forecolor .615 .615 .956 1 + visible 0 + decoration + + } + + itemDef + { + name gametypefield + style 0 + ownerdraw UI_NETGAMETYPE + text @MENUS_GAME_TYPE + descText @MENUS_ALLOWS_YOU_TO_SELECT + textstyle 0 + rect 5 25 255 26 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -3 + font 2 + textscale .9 + forecolor .615 .615 .956 1 + visible 1 + border 0 + bordercolor 1 1 1 1 + action + { + play "sound/interface/button1" + uiScript setBotButtons + } + mouseEnter + { + show gametypefieldButton + } + mouseExit + { + hide gametypefieldButton + } + } + +//--------------------------------------------- +// SERVER NAME ENTRY FIELD +//--------------------------------------------- + + itemDef + { + name hostnameButton + group none + style WINDOW_STYLE_SHADER + rect 220 22 470 26 + background "gfx/menus/menu_blendbox" + forecolor .615 .615 .956 1 + decoration + visible 0 + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_EDITFIELD + text @MENUS_SERVER_NAME_1 //@HOST_NAME + cvar "sv_hostname" + maxChars 32 + maxPaintChars 18 + rect 260 25 310 26 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -3 + font 2 + textscale .9 + forecolor .615 .615 .956 1 + visible 1 + border 0 + bordercolor .79 .64 .22 1 + descText @MENUS_ENTER_THE_NAME_FOR_YOUR + mouseEnter + { + show hostnameButton + } + mouseExit + { + hide hostnameButton + uiscript checkservername + } + action + { + play "sound/interface/button1" + uiscript checkservername + } + } +//------------------------------------------------ +// +// GAME SETTINGS +// +//------------------------------------------------ + +// +// GENERAL RULES TITLE +// + + itemDef + { + name rules_title + group none + text @MENUS_GENERAL_RULES + rect 10 55 180 20 + font 3 + textscale 0.8 + textalign ITEM_ALIGN_CENTER + textalignx 90 + textaligny -2 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +// +// MINIMUM BOTS/PLAYERS +// + itemDef + { + name settingsButton11 + style WINDOW_STYLE_SHADER + rect 10 75 180 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_NUMERICFIELD + maxchars 4 + text @MENUS_PLAYERS + descText @MENUS_PLAYERS_INFO + cvar "bot_minplayers" + rect 10 75 180 20 + textalign ITEM_ALIGN_RIGHT + textalignx 145 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarTest "ui_netGameType" + hideCvar + { + "4" + } + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show settingsButton11 + } + mouseExit + { + hide settingsButton11 + } + } + +// TEAM SWITCHING + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_YESNO + text @MENUS_TEAM_SWITCHING + cvar "g_siegeTeamSwitch" + cvarTest "ui_netGameType" + showCvar + { + "4" + } + rect 10 75 180 20 + textalign ITEM_ALIGN_RIGHT + textalignx 145 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 1 + descText @MENUS_TEAM_SWITCHING_DESC + action + { + play "sound/interface/button1" + } + mouseEnter + { + show settingsButton11 + } + mouseExit + { + hide settingsButton11 + } + } + +// +// MAX PLAYERS +// + itemDef + { + name settingsButton1 + style WINDOW_STYLE_SHADER + rect 10 95 180 20 + background "gfx/menus/menu_blendbox" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_MAXIMUM_PLAYERS + cvar "sv_maxclients" + rect 10 95 180 20 + textalign ITEM_ALIGN_RIGHT + textalignx 145 + textaligny 0 + font 4 + textscale 1 + maxchars 6 + forecolor .615 .615 .956 1 + visible 1 + descText @MENUS_SETS_THE_MAXIMUM_NUMBER + action + { + play "sound/interface/button1" + } + mouseEnter + { + show settingsButton1 + } + mouseExit + { + hide settingsButton1 + uiscript clampmaxplayers + } + } + +// +// EVEN TEAMS +// + itemDef + { + name settingsButton10 + style WINDOW_STYLE_SHADER + rect 10 115 180 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name expert + group grpsettings + type ITEM_TYPE_YESNO + text @MENUS_EVEN_TEAMS + cvar "g_teamforcebalance" + cvarTest "ui_netGameType" + showCvar + { + "3" ; + "4" ; + "5" + } + rect 10 115 180 20 + textalign ITEM_ALIGN_RIGHT + textalignx 145 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 1 + descText @MENUS_THIS_OPTION_RE_BALANCES + action + { + play "sound/interface/button1" + } + mouseEnter + { + show settingsButton10 + } + mouseExit + { + hide settingsButton10 + } + } + +// +// END GAME RULES TITLE +// + + itemDef + { + name rules_title + group none + text @MENUS_ENDGAME_RULES + rect 10 155 180 20 + font 3 + textscale 0.8 + textalign ITEM_ALIGN_CENTER + textalignx 90 + textaligny -2 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + +// +// TIME LIMIT +// + itemDef + { + name settingsButton2 + style WINDOW_STYLE_SHADER + rect 10 175 180 20 + background "gfx/menus/menu_blendbox" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_TIME_LIMIT + cvar "timelimit" + rect 10 175 180 20 + textalign ITEM_ALIGN_RIGHT + textalignx 145 + textaligny 0 + font 4 + textscale 1 + maxchars 4 + forecolor .615 .615 .956 1 + visible 1 + descText @MENUS_THIS_VALUE_ADJUSTS_THE + cvarTest "ui_netGameType" + hideCvar + { + "1" ; + "2" ; + "4" + } + action + { + play "sound/interface/button1" + } + mouseEnter + { + show settingsButton2 + } + mouseExit + { + hide settingsButton2 + } + } + + +// +// KILL LIMIT / CAPTURE LIMIT /DUEL LIMIT +// + itemDef + { + name settingsButton3 + style WINDOW_STYLE_SHADER + rect 10 195 180 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_KILL_LIMIT + cvar "fraglimit" + cvarTest "ui_netGameType" + showCvar + { + "0" ; + "3" + } + rect 10 195 180 20 + textalign ITEM_ALIGN_RIGHT + textalignx 145 + textaligny 0 + font 4 + textscale 1 + maxchars 4 + forecolor .615 .615 .956 1 + visible 1 + descText @MENUS_ESTABLISH_THE_NUMBER + action + { + play "sound/interface/button1" + } + mouseEnter + { + show settingsButton3 + } + mouseExit + { + hide settingsButton3 + } + } + + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_CAPTURE_LIMIT + cvar "capturelimit" + cvarTest "ui_netGameType" + showCvar + { + "5" + } + rect 10 195 180 20 + textalign ITEM_ALIGN_RIGHT + textalignx 145 + textaligny 0 + font 4 + textscale 1 + maxchars 6 + forecolor .615 .615 .956 1 + visible 1 + descText @MENUS_THIS_IS_THE_NUMBER_OF + action + { + play "sound/interface/button1" + } + mouseEnter + { + show settingsButton3 + } + mouseExit + { + hide settingsButton3 + } + } + + itemDef + { + name settingsButton3b + style WINDOW_STYLE_SHADER + rect 10 215 180 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_DUEL_LIMIT + descText @MENUS_VALUE_ADJUSTS_THE_TOTAL + cvar "duel_fraglimit" + cvarTest "ui_netGameType" + hideCvar + { + "0" ; + "3" ; + "4" ; + "5" + } + rect 10 215 180 20 + textalign ITEM_ALIGN_RIGHT + textalignx 145 + textaligny 0 + font 4 + textscale 1 + maxchars 6 + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show settingsButton3b + } + mouseExit + { + hide settingsButton3b + } + } + +//--------------------------------------------- +// MAP LISTING +//--------------------------------------------- + + itemDef + { + name maplist + rect 2 244 200 178 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 20 + font 2 + textscale .75 + elementtype LISTBOX_TEXT + feeder FEEDER_ALLMAPS + textstyle 6 + textalign 3 + textaligny 2 + border 1 + bordercolor .79 .64 .22 .5 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .75 + outlinecolor .25 .464 .578 .5 + descText @MENUS_CHOOSE_YOUR_GAME + visible 1 + columns 1 2 196 172 + mouseEnter + { + setitemcolor maplist bordercolor .79 .64 .22 1 + } + mouseExit + { + setitemcolor maplist bordercolor .79 .64 .22 .5 + } + } + +//------------------------------------- +// BOT LIST FOR NON-TEAMGAMES +//------------------------------------- + itemDef + { + name bot1Button + style WINDOW_STYLE_SHADER + rect 207 55 196 20 + background "gfx/menus/menu_blendbox" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_1_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM1 + rect 207 55 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot1Button + } + mouseExit + { + hide bot1Button + } + } + + itemDef + { + name bot2Button + style WINDOW_STYLE_SHADER + rect 207 75 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_2_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM1 + rect 207 75 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot2Button + } + mouseExit + { + hide bot2Button + } + } + + itemDef + { + name bot3Button + style WINDOW_STYLE_SHADER + rect 207 95 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_3_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM2 + rect 207 95 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot3Button + } + mouseExit + { + hide bot3Button + } + } + + itemDef + { + name bot4Button + style WINDOW_STYLE_SHADER + rect 207 115 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_4_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM2 + rect 207 115 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot4Button + } + mouseExit + { + hide bot4Button + } + } + + itemDef + { + name bot5Button + style WINDOW_STYLE_SHADER + rect 207 135 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_5_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM3 + rect 207 135 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot5Button + } + mouseExit + { + hide bot5Button + } + } + + itemDef + { + name bot6Button + style WINDOW_STYLE_SHADER + rect 207 155 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_6_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM3 + rect 207 155 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot6Button + } + mouseExit + { + hide bot6Button + } + } + + itemDef + { + name bot7Button + style WINDOW_STYLE_SHADER + rect 207 175 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_7_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM4 + rect 207 175 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot7Button + } + mouseExit + { + hide bot7Button + } + } + + itemDef + { + name bot8Button + style WINDOW_STYLE_SHADER + rect 207 195 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_8_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM4 + rect 207 195 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot8Button + } + mouseExit + { + hide bot8Button + } + } + + itemDef + { + name bot9Button + style WINDOW_STYLE_SHADER + rect 435 55 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_9_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM5 + rect 435 55 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot9Button + } + mouseExit + { + hide bot9Button + } + } + + itemDef + { + name bot10Button + style WINDOW_STYLE_SHADER + rect 435 75 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_10 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM5 + rect 435 75 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot10Button + } + mouseExit + { + hide bot10Button + } + } + + itemDef + { + name bot11Button + style WINDOW_STYLE_SHADER + rect 435 95 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_11 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM6 + rect 435 95 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot11Button + } + mouseExit + { + hide bot11Button + } + } + + itemDef + { + name bot12Button + style WINDOW_STYLE_SHADER + rect 435 115 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_12 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM6 + rect 435 115 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot12Button + } + mouseExit + { + hide bot12Button + } + } + + itemDef + { + name bot13Button + style WINDOW_STYLE_SHADER + rect 435 135 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_13 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM7 + rect 435 135 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot13Button + } + mouseExit + { + hide bot13Button + } + } + + itemDef + { + name bot14Button + style WINDOW_STYLE_SHADER + rect 435 155 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_14 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM7 + rect 435 155 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot14Button + } + mouseExit + { + hide bot14Button + } + } + + itemDef + { + name bot15Button + style WINDOW_STYLE_SHADER + rect 435 175 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_15 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM8 + rect 435 175 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot15Button + } + mouseExit + { + hide bot15Button + } + } + + itemDef + { + name bot16Button + style WINDOW_STYLE_SHADER + rect 435 195 196 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name humanbotfield + style 0 + text @MENUS_16 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM8 + rect 435 195 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + mouseEnter + { + show bot16Button + } + mouseExit + { + hide bot16Button + } + } + + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_1_1 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_BLUETEAM1 + rect 207 55 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_2_1 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_REDTEAM1 + rect 207 75 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_3_1 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_BLUETEAM2 + rect 207 95 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_4_1 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_REDTEAM2 + rect 207 115 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_5_1 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_BLUETEAM3 + rect 207 135 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_6_1 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_REDTEAM3 + rect 207 155 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_7_1 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_BLUETEAM4 + rect 207 175 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_8_1 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_REDTEAM4 + rect 207 195 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_9_1 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_BLUETEAM5 + rect 435 55 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_10 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_REDTEAM5 + rect 435 75 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_11 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_BLUETEAM6 + rect 435 95 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_12 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_REDTEAM6 + rect 435 115 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_13 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_BLUETEAM7 + rect 435 135 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_14 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_REDTEAM7 + rect 435 155 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_15 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_BLUETEAM8 + rect 435 175 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + itemDef + { + name humanbotnonfield + style 0 + text @MENUS_16 + descText @MENUS_ONLY_HUMANS + ownerdraw UI_REDTEAM8 + rect 435 195 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + +//--------------------------------------------- +// SKILL BUTTON +//--------------------------------------------- + + itemDef + { + name difficultyfieldButton + group none + style WINDOW_STYLE_SHADER + rect 410 215 175 24 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name difficultyfield + style 0 + ownerdraw UI_SKILL + text @MENUS_SKILL + descText @MENUS_ADJUSTS_THE_DIFFICULTY + rect 435 215 175 24 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny 2 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 1 + cvarTest "ui_netGameType" + hideCvar + { + "4" + } + action + { + play "sound/interface/button1" + } + mouseEnter + { + show difficultyfieldButton + } + mouseExit + { + hide difficultyfieldButton + } + } + +//--------------------------------------------- +// MAP SCREENSHOT +//--------------------------------------------- + + itemDef + { + name mappreview + style 0 + ownerdraw UI_STARTMAPCINEMATIC + //rect 7 57 190 141 + rect 314 254 210 158 + border 1 + bordercolor .265 .824 .886 .25 + visible 1 + } + + itemDef + { + name mappreview + style WINDOW_STYLE_FILLED + //rect 6 56 192 143 + rect 313 253 212 160 + border 1 + bordercolor .265 .824 .886 .25 + visible 1 + } + +//--------------------------------------------- +// BOTTOM BUTTONS OPTIONS +//--------------------------------------------- + itemDef + { + name backButton + group none + style WINDOW_STYLE_SHADER + rect 10 444 200 32 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name back + text @MENUS_BACK + descText @MENUS_EXIT_TO_MAIN_MENU + type ITEM_TYPE_BUTTON + font 3 + textscale 1.1 + textstyle 0 + style WINDOW_STYLE_FILLED + rect 10 444 200 32 + textalign 1 + textalignx 100 + textaligny 2 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1" + close createserver + open multiplayermenu + } + mouseenter + { + show backButton + } + mouseexit + { + hide backButton + } + } + + itemDef + { + name acceptButton + group none + style WINDOW_STYLE_SHADER + rect 430 444 200 32 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name accept + text @MENUS_BEGIN + descText @MENUS_START_SERVER + type ITEM_TYPE_BUTTON + font 3 + textscale 1.1 + textstyle 0 + style WINDOW_STYLE_FILLED + rect 430 444 200 32 + textalign 1 + textalignx 100 + textaligny 2 + forecolor 1 .682 0 1 + visible 1 + + action + { + play "sound/interface/button1" + uiScript setSiegeNoBots + uiScript weaponDisable //update the weapondisable cvar for duel or non-duel + uiScript StartServer + setcvartocvar password g_password + close createserver + } + + mouseenter + { + show acceptButton + } + mouseexit + { + hide acceptButton + } + } + + itemDef + { + name advancedButton + group none + style WINDOW_STYLE_SHADER + rect 220 444 200 32 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name advanced + text @MENUS_ADVANCED + descText @MENUS_UPDATE_ADVANCED_SERVER + type ITEM_TYPE_BUTTON + font 3 + textscale 1.1 + textstyle 0 + style WINDOW_STYLE_FILLED + rect 220 444 200 32 + textalign 1 + textalignx 100 + textaligny 2 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + open advancedcreateserver + } + mouseenter + { + show advancedButton + } + mouseexit + { + hide advancedButton + } + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/credits.menu b/base/ui/jamp/credits.menu new file mode 100644 index 0000000..734cac5 --- /dev/null +++ b/base/ui/jamp/credits.menu @@ -0,0 +1,441 @@ +// CREDITS MENU + +{ + menuDef + { + name "creditsMenu" + fullScreen MENU_TRUE // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible MENU_TRUE // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + appearanceIncrement 75 // In miliseconds + descX 375 + descY 434 + descScale 1 + descColor .235 .882 .847 1 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + exec "music music/cairn_bay/impbasee_action" ; + setfocus newgamebutton ; + } + + onClose + { +// exec "music music/cairn_bay/impbasee_explore" ; + } + + onESC + { + play "sound/interface/button1.wav" ; + close all ; + open mainMenu ; + } + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name frame_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/menu1" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + + // The saber glow on the left + itemDef + { + name saberglow + group none + style WINDOW_STYLE_SHADER + rect 15 0 60 480 + background "gfx/menus/menu3" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + + + // The red saber glow on the left + itemDef + { + name saberglowr + group none + style WINDOW_STYLE_SHADER + rect 75 0 60 480 + background "gfx/menus/menu3r" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + + + // The starwars logo on the top + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 143 12 470 93 + background "gfx/menus/menu4" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + + // The saber halo on the left + itemDef + { + name saberhalo + group none + style WINDOW_STYLE_SHADER + rect -255 -50 600 600 + background "gfx/menus/menu2" // Frame + forecolor 0.7 0.7 0.7 1 + visible 1 + decoration + } + + // The red saber halo on the left + itemDef + { + name saberhalor + group none + style WINDOW_STYLE_SHADER + rect -195 35 600 600 + background "gfx/menus/menu2r" // Frame + forecolor 0.7 0.7 0.7 1 + visible 1 + decoration + } + + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 115 115 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 115 115 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 0.9 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + forecolor 0.65 0.65 1 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 245 115 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name loadgamebutton + group toprow + text @MENUS_RULES + descText @MENUS_GAME_DESCRIPTIONS + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 245 115 130 24 + textaligny 0 + font 3 + textscale 0.9 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + forecolor 0.65 0.65 1 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open rulesgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 375 115 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 375 115 130 24 + font 3 + textscale 0.9 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + backcolor 0 0 0 0 + forecolor 0.65 0.65 1 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open weaponSetupMenu + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 505 115 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 505 115 130 24 + font 3 + textscale 0.9 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + backcolor 0 0 0 0 + forecolor 0.65 0.65 1 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open weaponSetupMenu + } + } + + itemDef + { + name header_line + group toprow + style WINDOW_STYLE_SHADER + rect 125 136 500 4 + background "gfx/menus/menu_line" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 115 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 115 444 130 24 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 0.65 0.65 1 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // CREDITS FIELDS + // + //---------------------------------------------------------------------------------------------- + // Credits title + itemDef + { + name credits_title + group none + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CREDITS + rect 150 145 450 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor 1 1 1 1 + visible 1 + // appearance_slot 2 + decoration + } + + + itemDef + { + name confirm + group none + text @MENUS_TO_BE_DETERMINED + font 2 + textscale 1 + textstyle 3 + rect 320 220 110 20 + textalign ITEM_ALIGN_CENTER + textalignx 0 + textaligny 0 + decoration + forecolor .433 .703 .722 1 + visible 1 + // appearance_slot 2 + } + + } +} + + + + + + + + + + + diff --git a/base/ui/jamp/demo.menu b/base/ui/jamp/demo.menu new file mode 100644 index 0000000..05afe2c --- /dev/null +++ b/base/ui/jamp/demo.menu @@ -0,0 +1,553 @@ +//---------------------------------------------------------------------------------------------- +// MAIN MENU +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "demo" + fullScreen MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible MENU_TRUE // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript LoadDemos ; + } + + onESC + { + play "sound/interface/menuroam.wav" ; + close all ; + open main ; + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "PLAYER PROFILE" + itemDef + { + name profilebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show profilebutton_glow + } + mouseExit + { + hide profilebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open playerMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Credits hidden button + itemDef + { + name creditsbutton + group othermain +// text @CREDITS + descText @MENUS_SHOW_GAME_CREDITS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 200 144 256 256 + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textalignx 46 + backcolor 0 0 0 0 + forecolor 0.65 0.65 1 1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open creditsMenu + } + } + + itemDef + { + name backButton + group none + style WINDOW_STYLE_SHADER + rect 10 444 200 32 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name back + text @MENUS_BACK + descText @MENUS_EXIT_TO_MAIN_MENU + type ITEM_TYPE_BUTTON + font 3 + textscale 1.1 + textstyle 0 + style WINDOW_STYLE_FILLED + rect 59 444 130 24 + textalign 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + mouseenter + { + show backButton + } + mouseexit + { + hide backButton + } + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + +/////////////////////////////////// +// TITLE BAR +/////////////////////////////////// + + itemDef + { + name default_rulesheader + group rulesheader + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_DEMOS + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -2 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name demolist + rect 40 183 560 200 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 20 + //background "gfx/menus/menu_box1" + font 3 + textscale .8 + elementtype LISTBOX_TEXT + feeder FEEDER_DEMOS + textalign 3 + textaligny 22 + border 1 + bordercolor 0 0 .6 1 + outlinecolor .5 .5 .5 .5 + forecolor .615 .615 .956 1 + backcolor 0 0 .6 .5 + visible 1 + doubleclick + { + play "sound/interface/button1.wav" ; + close demo ; + uiScript RunDemo + } + } + + itemDef + { + name beginButton + group none + style WINDOW_STYLE_SHADER + rect 245 390 150 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name begin + text @MENUS_PLAY_DEMO + descText @MENUS_BEGIN_PLAYING_THE_DEMO + type ITEM_TYPE_BUTTON + font 3 + textscale 1 + style WINDOW_STYLE_EMPTY + rect 245 390 150 30 + textalignx 75 // Center + textaligny 1 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + close demo ; + uiScript RunDemo + } + mouseEnter + { + show beginButton + } + mouseExit + { + hide beginButton + } + + } + } +} + + + + + + + + + + + + + diff --git a/base/ui/jamp/error.menu b/base/ui/jamp/error.menu new file mode 100644 index 0000000..00cb45c --- /dev/null +++ b/base/ui/jamp/error.menu @@ -0,0 +1,127 @@ +{ +\\ END OF GAME \\ + menuDef + { + name "error_popmenu" + visible 0 + fullscreen 0 + rect 158 80 320 320 + focusColor 1 1 1 1 + style WINDOW_STYLE_FILLED + border 1 + popup + outOfBoundsClick + + onClose + { + uiScript clearError + } + + onOpen + { + } + + onESC + { + close error_popmenu ; + open main + } + + + + itemDef + { + name window + rect 10 15 300 320 + style WINDOW_STYLE_FILLED + backcolor .015 .015 .229 1 + forecolor 0 0 0 1 + border 1 + bordercolor .988 .984 .121 1 + bordersize 5 + visible 1 + decoration + } + + itemDef + { + name errorinfo + rect 0 50 320 20 + text @MENUS_ERROR + textalign ITEM_ALIGN_CENTER + textstyle 6 + textscale 1 + textalignx 160 + forecolor 1 1 1 1 + visible 1 + decoration + } + itemDef + { + name errorinfo + rect 60 80 200 270 + type ITEM_TYPE_TEXT + style WINDOW_STYLE_FILLED + textstyle 3 + autowrapped + cvar "com_errorMessage" + textalign ITEM_ALIGN_CENTER + textalignx 90 + textscale 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + +// BUTTON // +//-------------------------------------------------------------------------------- +// BUTTON +//-------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 118 295 85 26 + background "gfx/menus/menu_buttonback" forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exit + group grpControlbutton + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 138 295 45 26 + text @MENUS_OKAY + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 22 + forecolor 1 .682 0 1 + + visible 1 + + action + { + play "sound/misc/nomenu.wav" + close error_popmenu + open main + } + + mouseEnter + { + show button_glow + } + mouseExit + { + hide button_glow + } + } + } +} + + + + diff --git a/base/ui/jamp/findplayer.menu b/base/ui/jamp/findplayer.menu new file mode 100644 index 0000000..cbc965e --- /dev/null +++ b/base/ui/jamp/findplayer.menu @@ -0,0 +1,351 @@ +//---------------------------------------------------------------------------------------------- +// +// FIND PLAYER POPUP MENU +// +// Screen for finding which server has a particular player +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "findplayer_popmenu" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 125 60 390 360 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 320 + descY 400 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + popup + + onOpen + { + uiScript FindPlayer + setfocus nameField + } + onESC + { + play "sound/interface/esc.wav" ; + close findplayer_popmenu + } + + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 0 0 390 380 + backcolor 0 0 .35 .9 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 1 + decoration + } + + //--------------------------------------------- + // TITLE + //--------------------------------------------- + // title + itemDef + { + name screenTitle + text @MENUS_FIND_PLAYER + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + rect 25 5 340 20 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 170 + textaligny 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + //--------------------------------------------- + // PLAYER NAME + //--------------------------------------------- + itemDef + { + name nameTitle + text @MENUS_NAME1 + style 0 + decoration + font 2 + textscale .9 + rect 25 30 90 25 + textalign ITEM_ALIGN_RIGHT + textalignx 85 + textaligny 0 + forecolor .549 .854 1 1 + visible 1 + } + + itemDef + { + name nameEntry + style 1 + descText @MENUS_INPUT_PLAYER_NAME + text @MENUS_BLANK_1 + maxchars 15 + font 2 + textscale .9 + type ITEM_TYPE_EDITFIELD + cvar "ui_findplayer" + rect 115 30 250 25 + textalign ITEM_ALIGN_LEFT + textalignx 5 + textaligny 0 + forecolor 1 1 1 1 + backcolor .25 .25 .25 .5 + border 1 + bordercolor .79 .64 .22 1 + visible 1 + action + { + ui_script FindPlayer + } + mouseenter + { + setitemcolor nameEntry backcolor .25 .25 .25 .75 + } + mouseexit + { + setitemcolor nameEntry backcolor .25 .25 .25 .5 + } + } + + + //--------------------------------------------- + // LIST OF SERVER NAMES + //--------------------------------------------- + itemDef + { + name serverNameList + rect 25 60 340 90 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 16 + font 2 + textscale .8 + backcolor 0 0 0 0.5 + outlinecolor .1 .1 .7 .5 + border 1 + bordersize 1 + bordercolor .5 .5 .5 1 + elementtype LISTBOX_TEXT + feeder FEEDER_FINDPLAYER + visible 1 + textaligny 12 + mouseenter + { + setitemcolor serverNameWindow bordercolor .7 0 0 1 + } + mouseexit + { + setitemcolor serverNameWindow bordercolor .5 .5 .5 1 + } + } + + itemDef + { + name serverNameWindow + rect 25 60 340 90 + style 1 + backcolor 0 0 0 0 + forecolor 0 0 0 0 + border 1 + bordersize 1 + bordercolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name serversFoundMsg + cvar "ui_playerServersFound" + type 0 + style 0 + background "gfx/menus/menu_blendbox" + rect 5 360 390 30 + font 3 + textscale .6 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + //--------------------------------------------- + // LIST OF SERVER STATUS INFORMATION + //--------------------------------------------- + itemDef + { + name serverInfoList + rect 25 160 340 150 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 16 + font 2 + textscale .7 + backcolor 0 0 0 1 + border 1 + bordersize 1 + bordercolor .5 .5 .5 1 + elementtype LISTBOX_TEXT + feeder FEEDER_SERVERSTATUS + notselectable + visible 1 + columns 4 2 40 120 + 40 40 180 + 90 40 180 + 135 40 180 + mouseenter + { + setitemcolor serverInfowindow bordercolor .7 0 0 1 + } + mouseexit + { + setitemcolor serverInfowindow bordercolor .5 .5 .5 1 + } + } + + itemDef + { + name serverInfowindow + rect 25 160 340 150 + style 1 + backcolor 0 0 0 0 + forecolor 0 0 0 0 + border 1 + bordersize 1 + bordercolor .5 .5 .5 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name search + text @MENUS_SEARCH_CAPS + descText @MENUS_SEARCH_DESC + type 1 + font 3 + textscale .8 + style WINDOW_STYLE_FILLED + rect 15 310 120 30 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 5 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + ui_script FindPlayer + } + mouseEnter + { + show button_glow + setitemrect button_glow 15 312 150 26 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name doneText + text @MENUS_DONE_CAPS + descText @MENUS_DONE_DESC + type 1 + font 3 + textscale .8 + style WINDOW_STYLE_FILLED + rect 135 310 120 30 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 5 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + close findplayer_popmenu + } + mouseEnter + { + show button_glow + setitemrect button_glow 115 312 160 26 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name join + text @MENUS_JOIN_CAPS + descText @MENUS_JOIN_PLAYER_DESC + type 1 + font 3 + textscale .8 + style WINDOW_STYLE_FILLED + rect 255 310 120 30 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 5 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + ui_script FoundPlayerJoinServer + } + mouseEnter + { + show button_glow + setitemrect button_glow 235 312 160 26 + } + mouseExit + { + hide button_glow + } + } + } +} diff --git a/base/ui/jamp/gameinfo.txt b/base/ui/jamp/gameinfo.txt new file mode 100644 index 0000000..da21e40 --- /dev/null +++ b/base/ui/jamp/gameinfo.txt @@ -0,0 +1,35 @@ +// Game type info +// +// description, g_gametype enum +// +// 0 - GT_FFA, // free for all +// 1 - GT_HOLOCRON, // ffa with holocrons +// 2 - GT_JEDIMASTER, // jedi master +// 3 - GT_DUEL, // one on one tournament +// 4 - GT_POWERDUEL, +// 5 - GT_SINGLE_PLAYER, // single player ffa +// 6 - GT_TEAM, // team ffa +// 7 - GT_SIEGE, // siege +// 8 - GT_CTF, // capture the flag +// 9 - GT_CTY, // capture the ysalimari + +gametypes { + { "Free For All" 0 } + { "Duel" 3 } + { "Power Duel" 4 } + { "Team FFA" 6 } + { "Siege" 7 } + { "Capture the Flag" 8 } +} + + +joingametypes { + { "All" -1 } + { "Free For All" 0 } + { "Duel" 3 } + { "Power Duel" 4 } + { "Team FFA" 6 } + { "Siege" 7 } + { "Capture the Flag" 8 } +} + diff --git a/base/ui/jamp/ingame.menu b/base/ui/jamp/ingame.menu new file mode 100644 index 0000000..1d240a3 --- /dev/null +++ b/base/ui/jamp/ingame.menu @@ -0,0 +1,604 @@ +//------------------------------------------- +// INGAME MENU +// +// This is the main menu of the ingame menus. +// +//------------------------------------------- +{ + assetGlobalDef + { + font "ergoec" 18 // font + smallFont "ocr_a" 18 // font + bigFont "anewhope" 20 // font + small2Font "arialnb" 14 + cursor "cursor" // cursor + gradientBar "ui/assets/gradientbar2.tga" // gradient bar + itemFocusSound "sound/interface/menuroam.wav" // sound for item getting focus (via keyboard or mouse ) + + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 1 // how often fade happens in milliseconds + fadeAmount 0.1 // amount to adjust alpha per cycle + + shadowColor 0.1 0.1 0.1 0.25 // shadow color + precacheSound + { + "sound/interface/choose_color.wav" ; + "sound/interface/choose_head.wav" ; + "sound/interface/choose_torso.wav" ; + "sound/interface/choose_saber.wav" ; + "sound/interface/choose_hilt.wav" ; + "sound/interface/choose_blade.wav" ; + "sound/interface/transition.wav" ; + "sound/interface/esc.wav" ; + "sound/interface/sub_select.wav" ; + } + } + + menuDef + { + name "ingame" + visible 0 + fullScreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 0 0 640 480 + focusColor 1 1 1 1 + disableColor 0.5 0.5 0.5 1 + + onOpen + { + uiScript setBotButton + } + + + + + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 32 + background "gfx/menus/menu_top_mp" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name aboutButton + group none + style WINDOW_STYLE_SHADER + rect 5 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemdef + { + name about + text @MENUS_ABOUT + rect 5 0 70 32 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open ingame_about + } + mouseenter + { + show aboutButton + } + mouseexit + { + hide aboutButton + } + } + + itemDef + { + name joinButton + group none + style WINDOW_STYLE_SHADER + rect 75 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name join + text @MENUS_JOIN + cvarTest "ui_singleplayeractive" + disableCvar + { + "1" + } + rect 75 0 70 32 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + cvartest "g_gametype" + showcvar { "0", "1", "2", "3", "4", "5", "6", "8" } + action + { + play "sound/interface/button1.wav" ; + open ingame_join + } + mouseenter + { + show joinButton + } + mouseexit + { + hide joinButton + } + } + + itemDef + { + name class + text @MENUS_JOIN + type 1 + style WINDOW_STYLE_FILLED + rect 75 0 70 32 + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + cvartest "g_gametype" + showcvar { "7" } + action + { + play "sound/interface/button1.wav" ; + open ingame_siegeclass + } + mouseenter + { + show joinButton + } + mouseexit + { + hide joinButton + } + } + + itemDef + { + name playerButton + group none + style WINDOW_STYLE_SHADER + rect 145 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name player + text @MENUS_PROFILE_LOWER + type 1 + style WINDOW_STYLE_FILLED + rect 145 0 70 32 + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + cvartest "g_gametype" + showcvar { "0", "1", "2", "3", "4", "5", "6", "8" } + action + { + play "sound/interface/button1.wav" ; + open ingame_player + } + mouseenter + { + show playerButton + } + mouseexit + { + hide playerButton + } + } + + itemDef + { + name objectivesButton + group none + style WINDOW_STYLE_SHADER + rect 145 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name objectives + text @MENUS_OBJECTIVES + type 1 + style WINDOW_STYLE_FILLED + rect 145 0 70 32 + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + cvartest "g_gametype" + showcvar { "7" } + action + { + play "sound/interface/button1.wav" ; + open ingame_objectives + } + mouseenter + { + show objectivesButton + } + mouseexit + { + hide objectivesButton + } + } + + itemDef + { + name chatButton + group none + style WINDOW_STYLE_SHADER + rect 215 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name chat + text @MENUS_VOICE_CHAT + type 1 + style WINDOW_STYLE_FILLED + rect 215 0 70 32 + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + cvartest "g_gametype" + showcvar { "7" } + action + { + play "sound/interface/button1.wav" ; + open ingame_voicechat + } + mouseenter + { + show chatButton + } + mouseexit + { + hide chatButton + } + } + + itemDef + { + name addBotButton + group none + style WINDOW_STYLE_SHADER + rect 215 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // do not change the name of this, the uiScript setBotButton looks + // for this item and turns it off if the gametype is siege + itemDef + { + name addBot + text @MENUS_ADD_BOT + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + rect 215 0 70 32 + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + cvarTest "sv_running" + disableCvar + { + "0" + } + action + { + play "sound/interface/button1.wav" ; + open ingame_addbot + } + mouseenter + { + show addBotButton + } + mouseexit + { + hide addBotButton + } + } + + itemDef + { + name controlsButton + group none + style WINDOW_STYLE_SHADER + rect 285 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name controls + text @MENUS_CONTROLS2 + type 1 + style WINDOW_STYLE_FILLED + rect 285 0 70 32 + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open ingame_controls + } + mouseenter + { + show controlsButton + } + mouseexit + { + hide controlsButton + } + } + + itemDef + { + name setupButton + group none + style WINDOW_STYLE_SHADER + rect 355 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name setup + text @MENUS_SETUP_INGAME + type 1 + style WINDOW_STYLE_FILLED + rect 355 0 70 32 + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open ingame_setup + } + mouseenter + { + show setupButton + } + mouseexit + { + hide setupButton + } + } + + itemDef + { + name voteButton + group none + style WINDOW_STYLE_SHADER + rect 425 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name vote + text @MENUS_VOTE + type 1 + style WINDOW_STYLE_FILLED + cvarTest "ui_singleplayeractive" + disableCvar + { + "1" + } + rect 425 0 70 32 + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open ingame_vote + } + mouseenter + { + show voteButton + } + mouseexit + { + hide voteButton + } + } + + itemDef + { + name callvoteButton + group none + style WINDOW_STYLE_SHADER + rect 495 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name callvote + text @MENUS_CALL_VOTE + type 1 + style WINDOW_STYLE_FILLED + cvarTest "ui_singleplayeractive" + disableCvar + { + "1" + } + + rect 495 0 70 32 + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open ingame_callvote + } + mouseenter + { + show callvoteButton + } + mouseexit + { + hide callvoteButton + } + } + + itemDef + { + name leaveButton + group none + style WINDOW_STYLE_SHADER + rect 565 0 70 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name leave + text @MENUS_EXIT_INGAME + type 1 + style WINDOW_STYLE_FILLED + rect 565 0 70 32 + font 2 + textscale .8 + textstyle 3 + textalign ITEM_ALIGN_CENTER + textalignx 35 + textaligny 2 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open ingame_leave + } + mouseenter + { + show leaveButton + } + mouseexit + { + hide leaveButton + } + } + } +} diff --git a/base/ui/jamp/ingame_about.menu b/base/ui/jamp/ingame_about.menu new file mode 100644 index 0000000..23ce3cd --- /dev/null +++ b/base/ui/jamp/ingame_about.menu @@ -0,0 +1,300 @@ +//---------------------------------------------------------------------------------------------- +// INGAME ABOUT BOX +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingame_about" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 10 40 380 280 + focusColor 1 .75 0 1 + style 1 + border 1 + + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 500 240 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name about + rect 10 5 360 20 + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + type 4 + text @MENUS_HOST_NAME + cvar "ui_about_hostname" + maxPaintChars 40 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -2 + font 2 + textscale 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name about + rect 10 40 360 20 + style 0 + type 4 + text @MENUS_ADDRESS + cvar "cl_currentServerAddress" + maxPaintChars 24 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -3 + forecolor 1 .682 0 1 + font 2 + textscale 1 + visible 1 + decoration + } + + itemDef + { + name about + rect 10 60 360 20 + type 4 + style 0 + text @MENUS_MAP_NAME + cvar "ui_about_mapname" + maxPaintChars 20 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -3 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + decoration + } + + itemDef + { + name about + rect 10 80 360 20 + style 0 + //type 4 + textalign ITEM_ALIGN_RIGHT + text @MENUS_TYPE + type ITEM_TYPE_MULTI + cvar "ui_about_gametype" + cvarFloatList + { + @MENUS_FREE_FOR_ALL 0 + @MENUS_HOLOCRON_FFA 1 + @MENUS_JEDI_MASTER 2 + @MENUS_DUEL 3 + @MENUS_POWERDUEL 4 + @MENUS_TEAM_FFA 6 + @MENUS_SIEGE 7 + @MENUS_CAPTURE_THE_FLAG 8 + @MENUS_CAPTURE_THE_YSALIMARI 9 + } + + textalignx 150 + textaligny -3 + font 2 + textscale 1 + forecolor 1 .682 0 1 + backcolor 0 0 .75 .25 + visible 1 + decoration + } + + + + itemDef + { + name about + rect 10 100 360 20 + type 4 + style 0 + text @MENUS_MAXIMUM_PLAYERS + cvar "ui_about_maxclients" + maxPaintChars 12 + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -3 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + decoration + } + + itemDef + { + name about + rect 10 120 360 20 + type 4 + style 0 + text @MENUS_TIME_LIMIT + maxPaintChars 12 + cvar "ui_about_timelimit" + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -3 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + decoration + } + + itemDef + { + name about + rect 10 140 360 20 + type 4 + style 0 + text @MENUS_FRAG_LIMIT + cvarTest "ui_about_gametype" + hideCvar + { + "1" ; + "2" ; + "3" ; + "4" ; + "7" ; + "8" ; + "9" ; + } + maxPaintChars 12 + cvar "ui_about_fraglimit" + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -3 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + decoration + } + + itemDef + { + name about + rect 10 140 360 20 + type 4 + style 0 + text @MENUS_CAPTURE_LIMIT + cvarTest "ui_about_gametype" + showCvar + { + "8" ; + "9" ; + } + maxPaintChars 12 + cvar "ui_about_capturelimit" + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -3 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + decoration + } + + itemDef + { + name about + rect 10 160 360 20 + type 4 + style 0 + text @MENUS_DUEL_LIMIT + cvarTest "ui_about_gametype" + showCvar + { + "3" ; + "4" ; + } + maxPaintChars 12 + cvar "ui_about_duellimit" + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -3 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + decoration + } + + itemDef + { + name about + rect 10 180 360 20 + type 4 + maxPaintChars 4 + style 0 + text @MENUS_MINIMUM_PLAYERS + cvar "ui_about_botminplayers" + textalign ITEM_ALIGN_RIGHT + textalignx 150 + textaligny -3 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + decoration + } + + itemDef + { + name about + rect 10 210 360 20 + type 4 + style 0 + text @MENUS_VERSION + cvar version + maxPaintChars 40 + textalign ITEM_ALIGN_CENTER + textalignx 180 + textaligny 14 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + decoration + } + + } +} + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/ingame_addbot.menu b/base/ui/jamp/ingame_addbot.menu new file mode 100644 index 0000000..ef917b5 --- /dev/null +++ b/base/ui/jamp/ingame_addbot.menu @@ -0,0 +1,153 @@ +//---------------------------------------------------------------------------------------------- +// ADDBOT MENU +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingame_addbot" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 105 40 240 128 + disableColor .5 .5 .5 1 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 240 128 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name gametypefield + style 0 + text @MENUS_NAME1 + ownerdraw UI_BOTNAME + rect 2 4 236 30 + textalign ITEM_ALIGN_RIGHT + textalignx 80 + textaligny 0 + font 2 + textscale .9 + forecolor 1 .682 0 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + } + + itemDef + { + name gametypefield + style 0 + text @MENUS_TEAM + ownerdraw UI_REDBLUE + rect 2 34 236 30 + textalign ITEM_ALIGN_RIGHT + textalignx 80 + textaligny 0 + font 2 + textscale .9 + forecolor 1 .682 0 1 + cvarTest "g_gametype" + disableCvar + { + "0" ; + "1" ; + "2" ; + "3" ; + "4" ; + "5" + } + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name gametypefield + style 0 + text @MENUS_SKILL1 + ownerdraw UI_BOTSKILL + rect 2 64 236 30 + textalign ITEM_ALIGN_RIGHT + textalignx 80 + textaligny 0 + font 2 + textscale .9 + forecolor 1 .682 0 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name okButton + group none + style WINDOW_STYLE_SHADER + rect 20 94 200 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name ok + text @MENUS_ADD_BOT + type 1 + style WINDOW_STYLE_EMPTY + rect 20 94 200 30 + textalignx 80 + textaligny 2 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + uiScript addBot ; + uiScript closeingame + } + mouseEnter + { + show okButton + } + mouseExit + { + hide okButton + } + } + } +} + + + + diff --git a/base/ui/jamp/ingame_callvote.menu b/base/ui/jamp/ingame_callvote.menu new file mode 100644 index 0000000..33a4821 --- /dev/null +++ b/base/ui/jamp/ingame_callvote.menu @@ -0,0 +1,647 @@ +\\ CALL VOTE MENU \\ +{ + menuDef + { + name "ingame_callvote" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 270 40 360 216 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 450 + descY 210 + descScale 1 + descColor 1 .682 0 .8 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + onopen + { + uiscript clearmouseover map ; + uiscript clearmouseover type ; + uiscript clearmouseover kick ; + hide doneButton ; + hide grpcallVote ; + show misc ; + uiScript loadArenas ; + setitemcolor grpbutton forecolor 1 .682 0 1 ; + setitemcolor miscbutton forecolor 1 1 1 1 ; + uiscript resetmaplist ; + } + + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 360 210 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name playerconfigtitle + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CALL_FOR_VOTES + rect 10 5 340 15 + textalign ITEM_ALIGN_CENTER + textalignx 170 + textaligny -2 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 3 + textscale 0.8 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + +/* itemDef + { + name setup_background + style WINDOW_STYLE_SHADER + rect 112 35 245 135 + background "gfx/menus/menu_box1" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + */ + + + itemDef + { + name setup_background2 + group none + style WINDOW_STYLE_FILLED + rect 120 35 235 135 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + + // DONE GLOW, used for several buttons + + itemDef + { + name doneButton + group none + style WINDOW_STYLE_SHADER + rect 140 186 80 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // BUTTONS // + itemDef + { + name misc_glow + group glows + style WINDOW_STYLE_SHADER + rect 0 35 120 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name miscbutton + group grpbutton + text @MENUS_GENERAL + descText @MENUS_ADJUST_GAME_OPTIONS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 10 35 103 30 + font 3 + textscale 0.9 + textalignx 100 + textaligny 5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide grpCallVote ; + show misc ; + setitemcolor grpbutton forecolor 1 .682 0 1 ; + setitemcolor miscbutton forecolor 1 1 1 1 ; + } + mouseEnter + { + show misc_glow + } + mouseExit + { + hide misc_glow + } + } + + + itemDef + { + name gametype_glow + group glows + style WINDOW_STYLE_SHADER + rect 0 65 120 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name gametypebutton + group grpbutton + text @MENUS_GAMETYPE + descText @MENUS_START_A_DIFFERENT_TYPE + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 10 65 103 30 + font 3 + textscale 0.9 + textalignx 100 + textaligny 5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide grpCallVote ; + show type ; + setitemcolor grpbutton forecolor 1 .682 0 1 ; + setitemcolor gametypebutton forecolor 1 1 1 1 ; + } + + forecolor .615 .615 .956 1 + + mouseEnter + { + show gametype_glow + } + mouseExit + { + hide gametype_glow + } + } + + itemDef + { + name changemap_glow + group glows + style WINDOW_STYLE_SHADER + rect 0 95 120 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name changemapbutton + text @MENUS_MAP + descText @MENUS_CHANGE_TO_A_NEW_MAP + group grpbutton + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 10 95 103 30 + font 3 + textscale 0.9 + textalignx 100 + textaligny 5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide grpCallVote ; + show maplist ; + show map ; + setitemcolor grpbutton forecolor 1 .682 0 1 ; + setitemcolor changemapbutton forecolor 1 1 1 1 ; + } + mouseEnter + { + show changemap_glow + } + mouseExit + { + hide changemap_glow + } + } + + itemDef + { + name kick_glow + group glows + style WINDOW_STYLE_SHADER + rect 0 125 120 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name kickbutton + group grpbutton + text @MENUS_KICK + descText @MENUS_BANISH_A_PLAYER_FROM + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 10 125 103 30 + font 3 + textscale 0.9 + textalignx 100 + textaligny 5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide grpCallVote ; + show kick ; + setitemcolor grpbutton forecolor 1 .682 0 1 ; + setitemcolor kickbutton forecolor 1 1 1 1 ; + } + mouseEnter + { + show kick_glow + } + mouseExit + { + hide kick_glow + } + } + + //---------------------------------- + // MISC VOTE PANEL + //---------------------------------- + itemDef + { + name misc + text @MENUS_RESTART_MAP + descText @MENUS_BEGIN_VOTE_TO_RESTART + group grpCallVote + rect 112 35 245 30 + type 1 + textalign ITEM_ALIGN_CENTER + textalignx 122 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/button1.wav" ; + exec "cmd callvote map_restart" ; + uiScript closeingame + } + } + + itemDef + { + name misc + group grpCallVote + text @MENUS_NEXT_MAP + descText @MENUS_BEGIN_VOTE_TO_CYCLE_TO + rect 112 65 245 30 + type 1 + textalign ITEM_ALIGN_CENTER + textalignx 122 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/button1.wav" ; + exec "cmd callvote nextmap" ; + uiScript closeingame + } + } + + itemDef + { + name misc + group grpCallVote + text @MENUS_WARMUP + descText @MENUS_BEGIN_VOTE_TO_DO_A_WARMUP + rect 112 95 245 30 + type 1 + textalign ITEM_ALIGN_CENTER + textalignx 122 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/button1.wav" ; + exec "cmd callvote g_doWarmup 1" ; + uiScript closeingame + } + } + + //---------------------------------- + // GAMETYPE VOTE PANEL + //---------------------------------- + itemDef + { + name type + group grpCallVote + text @MENUS_GAME_TYPE + descText @MENUS_SELECT_A_NEW_GAME_TYPE + style 0 + ownerdraw UI_NETGAMETYPE + rect 120 65 230 30 + textalign ITEM_ALIGN_RIGHT + textalignx 50 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + } + + + itemDef + { + name type + group grpCallVote + text @MENUS_OKAY + descText @MENUS_BEGIN_VOTE_FOR_THIS_GAME + type 1 + style WINDOW_STYLE_FILLED + rect 140 186 80 30 + textalign ITEM_ALIGN_CENTER + textalignx 40 + textaligny 0 + font 3 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + uiScript voteGame ; + uiScript closeingame + } + mouseEnter + { + show doneButton + } + mouseExit + { + hide doneButton + } + } + + + //---------------------------------- + // MAP VOTE PANEL + //---------------------------------- + itemDef + { + name map + group grpCallVote + text @MENUS_NEW_MAP + ownerdraw UI_ALLMAPS_SELECTION + rect 120 35 230 20 + textalign 0 + textalignx 5 + textaligny -2 + font 2 + textscale .8 + forecolor .549 .854 1 1 + decoration + visible 0 + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name maplist + group grpCallVote + descText @MENUS_SELECT_DESIRED_MAP + rect 120 55 230 108 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 15 + elementtype LISTBOX_TEXT + feeder FEEDER_ALLMAPS + border 1 + bordercolor 1 .682 0 .8 + backcolor 0 0 .5 .25 + outlinecolor .1 .1 .7 .5 + visible 0 + font 2 + textaligny 12 + textscale .8 + forecolor .615 .615 .956 1 + mouseEnter + { + fadein message_arena ; + setitemcolor map bordercolor .7 0 0 1 + } + mouseExit + { + fadeout message_arena ; + setitemcolor map bordercolor .5 .5 .5 .5 + } + action + { + play "sound/interface/button1.wav" ; + + } + } + + + + itemDef + { + name map + text @MENUS_OKAY + descText @MENUS_BEGIN_VOTE_TO_SWITCH + type 1 + group grpCallVote + style WINDOW_STYLE_FILLED + rect 140 186 80 30 + textalign ITEM_ALIGN_CENTER + textalignx 40 + textaligny 0 + font 3 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + uiScript voteMap ; + uiScript closeingame + } + mouseEnter + { + show doneButton + } + mouseExit + { + hide doneButton + } + } + + + //---------------------------------- + // KICK VOTE PANEL + //---------------------------------- + itemDef + { + name kick + group grpCallVote + text @MENUS_KICK_PLAYER + rect 120 35 230 20 + textalign 0 + textalignx 5 + textaligny -2 + font 2 + textscale .8 + forecolor .549 .854 1 1 + visible 0 + decoration + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + name kick + group grpCallVote + descText @MENUS_SELECT_PLAYER_TO_KICK + rect 120 55 230 108 + style WINDOW_STYLE_FILLED + type ITEM_TYPE_LISTBOX + elementwidth 120 + elementheight 15 + elementtype LISTBOX_TEXT + feeder FEEDER_PLAYER_LIST + border 1 + bordercolor 1 .682 0 .8 + backcolor 0 0 .5 .25 + outlinecolor .1 .1 .7 .5 + font 2 + textaligny 12 + textscale .8 + forecolor .615 .615 .956 1 + visible 0 + mouseEnter + { + fadein message_arena ; + setitemcolor kick bordercolor .7 0 0 1 + } + mouseExit + { + fadeout message_arena ; + setitemcolor kick bordercolor .5 .5 .5 1 + } + action + { + play "sound/interface/button1.wav" ; + } + + } + + itemDef + { + name kick + text @MENUS_OKAY + descText @MENUS_BEGIN_VOTE_TO_BANISH + type 1 + group grpCallVote + style WINDOW_STYLE_FILLED + rect 140 186 80 30 + textalign ITEM_ALIGN_CENTER + textalignx 40 + textaligny 0 + font 3 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + uiScript voteKick ; + uiScript closeingame + } + mouseEnter + { + show doneButton + } + mouseExit + { + hide doneButton + } + } + + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/ingame_controls.menu b/base/ui/jamp/ingame_controls.menu new file mode 100644 index 0000000..e655529 --- /dev/null +++ b/base/ui/jamp/ingame_controls.menu @@ -0,0 +1,3305 @@ +//----------------------------------------- +// SETUP MENU +//----------------------------------------- +{ + menuDef + { + name "ingame_controls" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 45 35 550 335 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 305 + descY 360 + descScale 1 + descColor 1 .682 0 .8 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + + onClose + { + hide highlights ; + uiScript saveControls + } + + onESC + { + play "sound/interface/button1.wav" ; + close ingame_controls + } + + onopen + { + hide grpControls ; + show look ; + uiScript loadControls + + show setup_background ; + show movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 1 1 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 550 345 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name playerconfigtitle + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CONFIGURE_CONTROLS + rect 20 5 510 28 + textalign ITEM_ALIGN_CENTER + textalignx 255 + textaligny 2 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 3 + textscale 0.9 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + +// movement button + itemDef + { + name movementcontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 20 43 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name movementcontrolbutton + group none + text @MENUS_MOVEMENT + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 43 170 30 + font 3 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 0.65 0.65 1 1 + visible 1 + descText @MENUS_CONFIGURE_MOVEMENT_KEYS + + mouseEnter + { + show movementcontrolbutton_glow + } + mouseExit + { + hide movementcontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + show movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 1 1 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + +// interaction button + itemDef + { + name attackcontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 20 73 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name attackcontrolbutton + group none + text @MENUS_INTERACTION + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 73 170 30 + font 3 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 0.65 0.65 1 1 + visible 1 + descText @MENUS_INTERACTION_DESC + + mouseEnter + { + show attackcontrolbutton_glow + } + mouseExit + { + hide attackcontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + show attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 1 1 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + // Weapons button + itemDef + { + name weaponscontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 20 103 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name weaponscontrolbutton + group none + text @MENUS_WEAPONS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 103 170 30 + font 3 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_WEAPON_CONTROLS + + mouseEnter + { + show weaponscontrolbutton_glow + } + mouseExit + { + hide weaponscontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + hide attackcontrols ; + show weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 1 1 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + // Force Powers button + itemDef + { + name forcecontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 20 133 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name forcecontrolbutton + group none + text @MENUS_FORCE_POWERS_1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 133 170 30 + font 3 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 0.65 0.65 1 1 + visible 1 + descText @MENUS_CONFIGURE_FORCE_POWER + + mouseEnter + { + show forcecontrolbutton_glow + } + mouseExit + { + hide forcecontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + show forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 1 1 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + // Force Powers button 2 + itemDef + { + name forcecontrolbutton2_glow + group mods + style WINDOW_STYLE_SHADER + rect 20 163 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name forcecontrolbutton2 + text @MENUS_FORCE_POWERS_2 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 163 170 30 + font 3 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 0.65 0.65 1 1 + visible 1 + descText @MENUS_CONFIGURE_FORCE_POWER + + mouseEnter + { + show forcecontrolbutton2_glow + } + mouseExit + { + hide forcecontrolbutton2_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + show forcecontrols2 ; + hide joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 1 1 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + // mousejoystick button + itemDef + { + name mousejoystickcontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 20 193 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name mousejoystickcontrolbutton + group none + text @MENUS_MOUSE_JOYSTICK + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 193 170 30 + font 3 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 0.65 0.65 1 1 + visible 1 + descText @MENUS_CONFIGURE_MOUSE_AND_JOYSTICK + + mouseEnter + { + show mousejoystickcontrolbutton_glow + } + mouseExit + { + hide mousejoystickcontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + show joycontrols ; + hide othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 1 1 1 ; + setitemcolor othercontrolbutton forecolor 1 .682 0 1 ; + } + } + + // other button + itemDef + { + name othercontrolbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 20 223 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name othercontrolbutton + group none + text @MENUS_OTHER + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 223 170 30 + font 3 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 0.65 0.65 1 1 + visible 1 + descText @MENUS_CONFIGURE_ADDITIONAL + + mouseEnter + { + show othercontrolbutton_glow + } + mouseExit + { + hide othercontrolbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide movecontrols ; + hide attackcontrols ; + hide weaponcontrols ; + hide forcecontrols ; + hide forcecontrols2 ; + hide joycontrols ; + show othercontrols ; + setitemcolor movementcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor attackcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor weaponscontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton forecolor 1 .682 0 1 ; + setitemcolor forcecontrolbutton2 forecolor 1 .682 0 1 ; + setitemcolor mousejoystickcontrolbutton forecolor 1 .682 0 1 ; + setitemcolor othercontrolbutton forecolor 1 1 1 1 ; + } + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 210 41 320 280 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } + + + //---------------------------------------------------------------------------------------------- + // + // HIGHLIGHT BARS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name highlight1 + group highlights + style WINDOW_STYLE_SHADER + rect 220 40 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight2 + group highlights + style WINDOW_STYLE_SHADER + rect 220 60 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight3 + group highlights + style WINDOW_STYLE_SHADER + rect 220 80 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight4 + group highlights + style WINDOW_STYLE_SHADER + rect 220 100 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight5 + group highlights + style WINDOW_STYLE_SHADER + rect 220 120 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight6 + group highlights + style WINDOW_STYLE_SHADER + rect 220 140 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight7 + group highlights + style WINDOW_STYLE_SHADER + rect 220 160 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight8 + group highlights + style WINDOW_STYLE_SHADER + rect 220 180 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight9 + group highlights + style WINDOW_STYLE_SHADER + rect 220 200 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight10 + group highlights + style WINDOW_STYLE_SHADER + rect 220 220 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight11 + group highlights + style WINDOW_STYLE_SHADER + rect 220 240 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight12 + group highlights + style WINDOW_STYLE_SHADER + rect 220 260 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight13 + group highlights + style WINDOW_STYLE_SHADER + rect 220 280 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight14 + group highlights + style WINDOW_STYLE_SHADER + rect 220 300 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// MOVEMENT BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name movement1 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_WALK_FORWARD + cvar "+forward" + rect 220 61 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_MOVES_PLAYER_FORWARD + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + + itemDef + { + name movement2 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_BACKPEDAL + cvar "+back" + rect 220 81 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 2 + descText @MENUS_MOVES_PLAYER_BACKWARD + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + name movement3 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_TURN_LEFT + cvar "+left" + rect 220 101 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 3 + descText @MENUS_ROTATES_PLAYER_LEFT + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + name movement4 + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_TURN_RIGHT + cvar "+right" + rect 220 121 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 4 + descText @MENUS_ROTATES_PLAYER_RIGHT + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + itemDef + { + name movement + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_RUN_WALK + cvar "+speed" + rect 220 141 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 5 + descText @MENUS_IF_HELD_TOGGLES_BETWEEN + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + } + + itemDef + { + name movement + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_STEP_LEFT + cvar "+moveleft" + rect 220 161 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_STEPS_PLAYER_TO_THE_LEFT + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + + itemDef + { + name movement + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_STEP_RIGHT + cvar "+moveright" + rect 220 181 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 7 + descText @MENUS_STEPS_PLAYER_TO_THE_RIGHT + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + } + + itemDef + { + name movement + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_SIDESTEP + cvar "+strafe" + rect 220 201 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 8 + descText @MENUS_HELD_ALLOWS_PLAYER_TO + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide highlight9 + hide keybindstatus + } + } + + itemDef + { + name movement + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_UP_JUMP + cvar "+moveup" + rect 220 221 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 9 + descText @MENUS_MAKES_PLAYER_JUMP_IF + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + } + + itemDef + { + name movement + group movecontrols + type ITEM_TYPE_BIND + text @MENUS_DOWN_CROUCH + cvar "+movedown" + rect 220 241 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 10 + descText @MENUS_MAKES_PLAYER_CROUCH_TO + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// +// INTERACTION BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name attacklook1 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_ATTACK + cvar "+attack" + rect 220 61 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_ATTACKS_WITH_READIED + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + itemDef + { + name attacklook2 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_ALT_ATTACK + cvar "+altattack" + rect 220 81 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 2 + descText @MENUS_ATTACKS_WITH_ALTERNATE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + name attacksaber + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LIGHTSABER_STYLE + cvar "saberAttackCycle" + rect 220 101 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 2 + descText @MENUS_CYCLES_BETWEEN_AVAILABLE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + name attacksaber + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_USE + cvar "+use" + rect 220 141 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 3 + descText @MENUS_ACTIVATES_WORLD_DEVICES + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + } + + itemDef + { + name attacksaber + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_USE_HELD_ITEM + cvar "+button2" + rect 220 161 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_ACTIVATES_CURRENTLY_SELECTED + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + + itemDef + { + name attacksaber + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_INV_NEXT + cvar "invnext" + rect 220 181 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_SELECTS_NEXT_USABLE_ITEM + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + } + + itemDef + { + name attacksaber + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_INV_PREV + cvar "invprev" + rect 220 201 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_SELECTS_PREVIOUS_USABLE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide highlight9 + hide keybindstatus + } + } + + itemDef + { + name attacklook4 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LOOK_UP + cvar "+lookup" + rect 220 221 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 4 + descText @MENUS_TILTS_VIEW_UPWARDS + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + } + + + itemDef + { + name attacklook5 + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_LOOK_DOWN + cvar "+lookdown" + rect 220 241 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 5 + descText @MENUS_TILTS_VIEW_DOWNWARDS + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_MOUSE_LOOK + cvar "+mlook" + rect 220 261 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_IF_HELD_ALLOWS_PLAYER + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + } + + itemDef + { + name attacklook + group attackcontrols + type ITEM_TYPE_BIND + text @MENUS_CENTER_VIEW + cvar "centerview" + rect 220 281 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_RETURNS_VIEW_TO_HORIZONTAL + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight13 + show keybindstatus + } + mouseexit + { + hide highlight13 + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// +// WEAPON BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name weapon1 + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_MELEE_LIGHTSABER + cvar "weapon 1" + rect 220 41 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_READIES_LIGHTSABER + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight1 + show keybindstatus + } + mouseexit + { + hide highlight1 + hide keybindstatus + } + } + + itemDef + { + name weapon3 + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_PISTOL + cvar "weapon 2" + rect 220 61 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_READIES_THE_BLASTER_PISTOL + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_RIFLE + cvar "weapon 3" + rect 220 81 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_READIES_THE_E_11_BLASTER + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_DISRUPTOR_RIFLE + cvar "weapon 4" + rect 220 101 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_READIES_THE_TENLOSS_DXR_6 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_BOWCASTER + cvar "weapon 5" + rect 220 121 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_READIES_THE_WOOKIEE_BOWCASTER + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_HEAVY_REPEATER + cvar "weapon 6" + rect 220 141 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_READIES_THE_IMPERIAL + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_DEMP_2 + cvar "weapon 7" + rect 220 161 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 7 + descText @MENUS_READIES_THE_DEMP2_GUN + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_FLECHETTE + cvar "weapon 8" + rect 220 181 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 8 + descText @MENUS_READIES_THE_GOLAN_ARMS + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide keybindstatus + hide highlight8 + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_CONC_RIFLE_SETUP + cvar "weapon 13" + rect 220 201 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 8 + descText @MENUS_READIES_THE_CONC_RIFLE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide keybindstatus + hide highlight9 + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_MERR_SONN + cvar "weapon 9" + rect 220 221 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 9 + descText @MENUS_READIES_THE_MERR_SONN + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + } + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_THROWABLE_WEAPONS + cvar "weapon 10" + rect 220 241 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 10 + descText @MENUS_TOGGLES_BETWEEN_DETONATORS + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_NEXT_WEAPON + cvar "weapnext" + rect 220 261 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 10 + descText @MENUS_SELECTS_THE_NEXT_WEAPON + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + } + + itemDef + { + name none + group weaponcontrols + type ITEM_TYPE_BIND + text @MENUS_PREVIOUS_WEAPON + cvar "weapprev" + rect 220 281 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 11 + descText @MENUS_SELECTS_THE_PREVIOUS + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight13 + show keybindstatus + } + mouseexit + { + hide highlight13 + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// +// FORCE BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name force2 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PUSH + cvar "force_throw" + rect 220 61 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_USES_FORCE_PUSH_ABILITY + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + itemDef + { + name force3 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PULL + cvar "force_pull" + rect 220 81 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 2 + descText @MENUS_USES_FORCE_PULL_ABILITY + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + name force4 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_SPEED + cvar "force_speed" + rect 220 101 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 3 + descText @MENUS_USES_FORCE_SPEED_ABILITY + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + name force4 + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_SIGHT + cvar "force_seeing" + rect 220 121 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_FORCE_SIGHT_ABILITY + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_USE_FORCE_POWER + cvar "+useforce" + rect 220 161 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 8 + descText @MENUS_USES_CURRENTLY_SELECTED + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_NEXT + cvar "forcenext" + rect 220 181 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 9 + descText @MENUS_SELECTS_NEXT_AVAILABLE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + } + + itemDef + { + name forcekeys + group forcecontrols + type ITEM_TYPE_BIND + text @MENUS_FORCE_PREVIOUS + cvar "forceprev" + rect 220 201 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 10 + descText @MENUS_SELECTS_PREVIOUS_AVAILABLE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide highlight9 + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// +// FORCE BINDING 2 (light / dark) +// +//---------------------------------------------------------------------------------------------- + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_PROTECT + cvar "force_protect" + rect 220 61 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_FORCE_PROTECT_ABILITY + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight2 + show keybindstatus + } + mouseexit + { + hide highlight2 + hide keybindstatus + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_ABSORB + cvar "force_absorb" + rect 220 81 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USE_ABSORB + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_HEAL + cvar "force_heal" + rect 220 101 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_FORCE_HEAL_ABILITY + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_HEAL_OTHER + cvar "force_healother" + rect 220 121 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USE_HEAL_OTHER + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_MINDTRICK + cvar "force_distract" + rect 220 141 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_MIND_TRICK_ABILITY + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_GRIP + cvar "+force_grip" + rect 220 161 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_GRIP_FORCE_ABILITY + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_DRAIN + cvar "+force_drain" + rect 220 181 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_DRAIN_FORCE_ABILITY + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_FORCE_LIGHTNING + cvar "+force_lightning" + rect 220 201 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_LIGHTNING_FORCE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide highlight9 + hide keybindstatus + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_RAGE + cvar "force_rage" + rect 220 221 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_RAGE_FORCE_ABILITY + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + } + + itemDef + { + group forcecontrols2 + type ITEM_TYPE_BIND + text @MENUS_TEAM_POWER + cvar "force_forcepowerother" + rect 220 241 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_USES_TEAM_POWER_FORCE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + } + +//---------------------------------------------------------------------------------------------- +// +// MOUSE/JOYSTICK KEY BINDING +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name mousejoystick1 + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_FREE_LOOK + cvar "cl_freelook" + rect 220 61 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_TOGGLE_TO_ALLOW_PLAYER + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight2 + } + + mouseexit + { + hide highlight2 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_SLIDER + text @MENUS_SENSITIVITY + cvarfloat "sensitivity" 5 2 30 + rect 220 81 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 2 + descText @MENUS_ADJUSTS_CHARACTER_REACTION + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_INVERT_PITCH + cvar "ui_mousePitch" + rect 220 101 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 3 + descText @MENUS_TOGGLE_TO_TILT_VIEW_IN + + action { uiScript update ui_mousePitch ; + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight4 + } + + mouseexit + { + hide highlight4 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_SMOOTH_MOUSE + cvar "m_filter" + rect 220 121 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 4 + descText @MENUS_WHEN_TURNED_ON_MOUSE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight5 + } + + mouseexit + { + hide highlight5 + } + } + +// +// Not shown +// + itemDef + { + name mousejoystick + group joycontrols_obsolete + type ITEM_TYPE_YESNO + text @MENUS_FORCE_FEEDBACK + cvar "use_ff" + rect 220 141 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 5 + descText @MENUS_WHEN_TURNED_ON_GAME + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight6 + } + + mouseexit + { + hide highlight6 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_ENABLE_JOYSTICK + cvar "in_joystick" + rect 220 181 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 6 + descText @MENUS_TURNED_ON_GAME_SEARCHES + action + { + play "sound/interface/button1.wav" ; + exec in_restart + } + + mouseenter + { + show highlight8 + } + + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_SLIDER + text @MENUS_JOYSTICK_THRESHOLD + cvarfloat "joy_threshold" .15 .05 .75 + rect 220 201 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 7 + descText @MENUS_ADJUSTS_THE_SIZE_OF_THE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_X_AXIS_AS_BUTTONS + cvar "joy_xbutton" + rect 220 221 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 8 + descText @MENUS_WHEN_OFF_HORIZONTAL + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight10 + } + + mouseexit + { + hide highlight10 + } + } + + itemDef + { + name mousejoystick + group joycontrols + type ITEM_TYPE_YESNO + text @MENUS_Y_AXIS_AS_BUTTONS + cvar "joy_ybutton" + rect 220 241 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 9 + descText @MENUS_WHEN_OFF_VERTICAL_STICK + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight11 + } + + mouseexit + { + hide highlight11 + } + } + +//---------------------------------------------------------------------------------------------- +// +// OTHER +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name other1 + group othercontrols + type ITEM_TYPE_YESNO + text @MENUS_ALWAYS_RUN + cvar "cl_run" + rect 220 41 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_WHEN_ON_PLAYER_ALWAYS + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight1 + } + + mouseexit + { + hide highlight1 + } + } + + itemDef + { + name other2 + group othercontrols + type ITEM_TYPE_MULTI + text @MENUS_AUTO_SWITCH + cvar "cg_autoswitch" + cvarFloatList + { + @MENUS_DON_T_SWITCH 0 + @MENUS_BEST_SAFE_WEAPON 1 + @MENUS_ALWAYS_BEST_WEAPON 2 + } + rect 220 61 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_CHOOSE_WHETHER_TO_SWITCH + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight2 + } + + mouseexit + { + hide highlight2 + } + + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_CHAT + cvar "messagemode" + rect 220 81 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_SENDS_A_CHAT_MESSAGE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight3 + show keybindstatus + } + mouseexit + { + hide highlight3 + hide keybindstatus + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_TEAM_CHAT + cvar "messagemode2" + rect 220 101 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_A_CHAT_MESSAGE_TO_EVERYONE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight4 + show keybindstatus + } + mouseexit + { + hide highlight4 + hide keybindstatus + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_VC_CONTROLS + cvar "voicechat" + rect 220 121 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_VC_CONTROLS_DESC + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight5 + show keybindstatus + } + mouseexit + { + hide highlight5 + hide keybindstatus + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_AUTOMAP + cvar "automap_toggle" + rect 220 141 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_AUTOMAP_DESC + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight6 + show keybindstatus + } + mouseexit + { + hide highlight6 + hide keybindstatus + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_SHOW_SCORES + cvar "+scores" + rect 220 161 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_SHOWS_THE_CURRENT_SCORES + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight7 + show keybindstatus + } + mouseexit + { + hide highlight7 + hide keybindstatus + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_SABER_CHALLENGE + cvar "engage_duel" + rect 220 181 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_INFO_SABER_CHALLENGE + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight8 + show keybindstatus + } + mouseexit + { + hide highlight8 + hide keybindstatus + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_3RD_PERSON + cvar "cg_thirdperson !" + rect 220 201 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_CHANGES_VIEW_BETWEEN + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight9 + show keybindstatus + } + mouseexit + { + hide highlight9 + hide keybindstatus + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_TAUNT + cvar "taunt" + rect 220 221 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TAUNT_DESC + + mouseenter + { + show highlight10 + show keybindstatus + } + mouseexit + { + hide highlight10 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_BOW + cvar "bow" + rect 220 241 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_BOW_DESC + + mouseenter + { + show highlight11 + show keybindstatus + } + mouseexit + { + hide highlight11 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_MEDITATE + cvar "meditate" + rect 220 261 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_MEDITATE_DESC + + mouseenter + { + show highlight12 + show keybindstatus + } + mouseexit + { + hide highlight12 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_FLOURISH + cvar "flourish" + rect 220 281 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_FLOURISH_DESC + + mouseenter + { + show highlight13 + show keybindstatus + } + mouseexit + { + hide highlight13 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + itemDef + { + group othercontrols + type ITEM_TYPE_BIND + text @MENUS_GLOAT + cvar "gloat" + rect 220 301 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 151 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_GLOAT_DESC + + mouseenter + { + show highlight14 + show keybindstatus + } + mouseexit + { + hide highlight14 + hide keybindstatus + } + action + { + play "sound/interface/button1.wav" ; + } + } + + //---------------------------------------------------------------------------------------------- + // + // Text + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name keyBindStatus + group none + ownerdraw 250 // UI_KEYBINDSTATUS + text "" + rect 275 322 0 0 + textStyle 0 + font 2 + textscale .7 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 .9 + visible 0 + decoration + } + + itemDef + { + name slider_message + group none + text @MENUS_MOVE_THE_SLIDER_TO_INCREASE + rect 275 310 0 0 + textStyle 0 + font 2 + textscale .7 + textalign ITEM_ALIGN_CENTER + visible 0 + decoration + } + + itemDef + { + name yesno_message + group none + text @MENUS_CLICK_ON_FIELD_TO_TOGGLE + rect 275 310 0 0 + textStyle 0 + font 2 + textscale .7 + textalign ITEM_ALIGN_CENTER + visible 0 + decoration + } + + itemDef + { + name multi_message + group none + text @MENUS_CLICK_ON_FIELD_TO_CHANGE + rect 275 310 0 0 + textStyle 0 + font 2 + textscale .7 + textalign ITEM_ALIGN_CENTER + visible 0 + decoration + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/ingame_join.menu b/base/ui/jamp/ingame_join.menu new file mode 100644 index 0000000..bb87ff8 --- /dev/null +++ b/base/ui/jamp/ingame_join.menu @@ -0,0 +1,393 @@ +//---------------------------------------------------------------------------------------------- +// INGAME_JOIN MENU +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingame_join" + visible 1 + fullScreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 55 40 128 128 + focusColor 1 1 1 1 + + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 128 128 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //--------------------------------- + // BUTTONS + //--------------------------------- + itemDef + { + name button1 + group buttons + style WINDOW_STYLE_SHADER + rect 2 4 124 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name button2 + group buttons + style WINDOW_STYLE_SHADER + rect 2 34 124 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name button3 + group buttons + style WINDOW_STYLE_SHADER + rect 2 64 124 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name button4 + group buttons + style WINDOW_STYLE_SHADER + rect 2 94 124 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + //--------------------------------- + // TEAM JOIN + //--------------------------------- + itemDef + { + name team + text @MENUS_AUTO_TEAM + type 1 + style 2 + rect 2 4 124 30 + textalign ITEM_ALIGN_CENTER + textalignx 62 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + cvarTest "ui_about_gametype" + showCvar + { + "6" ; + "7" ; + "8" ; + "9" + } + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "cmd team free" ; + uiScript closeingame + } + mouseEnter + { + show button1 + } + mouseExit + { + hide button1 + } + } + + itemDef + { + name team + text @MENUS_TEAM_RED + type 1 + style 2 + rect 2 34 124 30 + textalign ITEM_ALIGN_CENTER + textalignx 62 + textaligny 0 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + cvarTest "ui_about_gametype" + showCvar + { + "6" ; + "7" ; + "8" ; + "9" + } + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "cmd team red" ; + uiScript closeingame + } + mouseEnter + { + show button2 + } + mouseExit + { + hide button2 + } + } + + itemDef + { + name team + text @MENUS_TEAM_BLUE + type 1 + style 2 + rect 2 64 124 30 + textalign ITEM_ALIGN_CENTER + textalignx 62 + textaligny 0 + font 2 + textscale 1 + forecolor 0.2 0.2 1 1 + cvarTest "ui_about_gametype" + showCvar + { + "6" ; + "7" ; + "8" ; + "9" + } + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "cmd team blue" ; + uiScript closeingame + } + mouseEnter + { + show button3 + } + mouseExit + { + hide button3 + } + } + + + itemDef + { + name team + text @MP_INGAME_JOIN_SINGLE + type 1 + style 2 + rect 2 34 124 30 + textalign ITEM_ALIGN_CENTER + textalignx 62 + textaligny 0 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + cvarTest "ui_about_gametype" + showCvar + { + "4" + } + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "cmd duelteam single" ; + uiScript closeingame + } + mouseEnter + { + show button2 + } + mouseExit + { + hide button2 + } + } + + itemDef + { + name team + text @MP_INGAME_JOIN_DOUBLE + type 1 + style 2 + rect 2 64 124 30 + textalign ITEM_ALIGN_CENTER + textalignx 62 + textaligny 0 + font 2 + textscale 1 + forecolor 0.2 0.2 1 1 + cvarTest "ui_about_gametype" + showCvar + { + "4" + } + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "cmd duelteam double" ; + uiScript closeingame + } + mouseEnter + { + show button3 + } + mouseExit + { + hide button3 + } + } + + + itemDef + { + name team + text @MENUS_SPECTATE + type 1 + style 2 + rect 2 94 124 30 + textalign ITEM_ALIGN_CENTER + textalignx 62 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + cvarTest "ui_about_gametype" + showCvar + { + "6" ; + "7" ; + "8" ; + "9" + } + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "cmd team s" ; + uiScript closeingame + } + mouseEnter + { + show button4 + } + mouseExit + { + hide button4 + } + } + + //--------------------------------- + // NON-TEAM JOIN + //--------------------------------- + itemDef + { + name team + text @MENUS_JOIN_GAME + type 1 + style 2 + rect 2 34 124 30 + textalign ITEM_ALIGN_CENTER + textalignx 62 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + cvarTest "ui_about_gametype" + hideCvar + { + "4" ; + "6" ; + "7" ; + "8" ; + "9" + } + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "cmd team free" ; + uiScript closeingame + } + mouseEnter + { + show button2 + } + mouseExit + { + hide button2 + } + } + + itemDef + { + name team + text @MENUS_SPECTATE + type 1 + style 2 + rect 2 64 124 30 + textalign ITEM_ALIGN_CENTER + textalignx 62 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + cvarTest "ui_about_gametype" + hideCvar + { + "4" ; + "6" ; + "7" ; + "8" ; + "9" + } + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "cmd team s" ; + uiScript closeingame + } + mouseEnter + { + show button3 + } + mouseExit + { + hide button3 + } + } + + } +} + + + + + diff --git a/base/ui/jamp/ingame_leave.menu b/base/ui/jamp/ingame_leave.menu new file mode 100644 index 0000000..ec49a31 --- /dev/null +++ b/base/ui/jamp/ingame_leave.menu @@ -0,0 +1,423 @@ +\\ INGAME_LEAVE MENU \\ +{ + menuDef + { + name "ingame_leave" + visible 1 + fullScreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 474 40 156 100 + focusColor 1 1 1 1 + onOpen + { + show grpMenu ; + hide grpConfirm + } + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 156 100 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + +// +// BUTTONS +// + itemDef + { + name button1 + group buttons + style WINDOW_STYLE_SHADER + rect 2 5 152 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name button2 + group buttons + style WINDOW_STYLE_SHADER + rect 2 35 152 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name button3 + group buttons + style WINDOW_STYLE_SHADER + rect 2 65 152 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + +// +// LEAVE MAIN MENU +// + + itemDef + { + name leave + text @MENUS_MAIN_MENU + group grpMenu + style 2 + type 1 + rect 2 5 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide grpMenu ; + hide buttons ; + show leaveConfirm + } + mouseEnter + { + show button1 + } + mouseExit + { + hide button1 + } + } + + itemDef + { + name leave + group grpMenu + text @MENUS_RESTART_MATCH + style 2 + type 1 + rect 2 35 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + cvarTest "cl_currentServerAddress" + enableCvar + { + "Localhost" + } + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide grpMenu ; + hide buttons ; + show restartConfirm + } + mouseEnter + { + show button2 + } + mouseExit + { + hide button2 + } + } + + itemDef + { + name leave + group grpMenu + type 1 + text @MENUS_QUIT_PROGRAM + style 2 + rect 2 65 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide grpMenu ; + hide buttons ; + show quitConfirm + } + mouseEnter + { + show button3 + } + mouseExit + { + hide button3 + } + } + + + + //--------------------------------- + // MAIN MENU CONFIRM + //--------------------------------- + itemDef + { + name leaveConfirm + text @MENUS_GO_TO_MAIN_MENU + group grpConfirm + style 2 + rect 2 5 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor .549 .854 1 1 + decoration + visible 1 + } + + + itemDef + { + name leaveConfirm + text @MENUS_YES + group grpConfirm + type 1 + style 0 + rect 2 35 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + uiScript leave + } + mouseEnter + { + show button2 + } + mouseExit + { + hide button2 + } + } + + itemDef + { + name leaveConfirm + text @MENUS_NO + group grpConfirm + type 1 + style 0 + rect 2 65 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide grpConfirm ; + show grpMenu + } + mouseEnter + { + show button3 + } + mouseExit + { + hide button3 + } + } + + + + //--------------------------------- + // RESTART MATCH CONFIRM + //--------------------------------- + itemDef + { + name restartConfirm + text @MENUS_MATCH + group grpConfirm + style 2 + rect 2 5 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor .549 .854 1 1 + decoration + visible 1 + } + + itemDef + { + name restartConfirm + text @MENUS_YES + group grpConfirm + type 1 + rect 2 35 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "map_restart" ; + close ingame_leave ; + close ingame + } + mouseEnter + { + show button2 + } + mouseExit + { + hide button2 + } + } + + itemDef + { + name restartConfirm + text @MENUS_NO + group grpConfirm + type 1 + rect 2 65 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide grpConfirm ; + show grpMenu + } + mouseEnter + { + show button3 + } + mouseExit + { + hide button3 + } + } + + + + //--------------------------------- + // RESTART MATCH CONFIRM + //--------------------------------- + itemDef + { + name quitConfirm + text @MENUS_PROGRAM + group grpConfirm + style 2 + rect 2 5 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor .549 .854 1 1 + decoration + visible 1 + } + + itemDef + { + name quitConfirm + text @MENUS_YES + group grpConfirm + type 1 + rect 2 35 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + uiScript quit + } + mouseEnter + { + show button2 + } + mouseExit + { + hide button2 + } + } + + itemDef + { + name quitConfirm + text @MENUS_NO + group grpConfirm + type 1 + rect 2 65 152 30 + textalign ITEM_ALIGN_CENTER + textalignx 76 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + hide grpConfirm ; + show grpMenu + } + mouseEnter + { + show button3 + } + mouseExit + { + hide button3 + } + } + } +} \ No newline at end of file diff --git a/base/ui/jamp/ingame_objectives.menu b/base/ui/jamp/ingame_objectives.menu new file mode 100644 index 0000000..27578cc --- /dev/null +++ b/base/ui/jamp/ingame_objectives.menu @@ -0,0 +1,2957 @@ +//-------------------------------------------------------------------------------------------- +// uberScreen +// +// cvars used (at least some of them +// siege_mapgraphic - the name of the graphic of the map of the level. +// currentObjButton - the menu item name of currently active objective button +// +// +//-------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingame_objectives" + fullScreen 0 + visible 0 + outOfBoundsClick + rect 0 0 322 480 + focusColor 1 1 1 1 + style 1 + descX 480 + descY 434 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + backColor .05 .07 .09 1 + disablecolor 1 1 1 1 + onClose + { + hide minidesc + hide obj_longdesc + hide objective_pic + } + + onOpen + { + setcvar currentObjButton "null" + + exec "siegeCompleteCvarUpdate" + + setitembackground mappic "*siege_mapgraphic" + + // set tm_icon fields to the proper mapicon graphic + uiscript updatesiegeobjgraphics + + setitembackground team1_symbol "*team1_icon" + setitembackground team2_symbol "*team2_icon" + setitembackground team1_button "*team1_icon" + setitembackground team2_button "*team2_icon" + + // Enable team buttons, in case they'd been shut off + disable team_button 0 + disable autoteam 0 + disable team1_button 0 + disable team2_button 0 + + // and reset colors + setitemcolor team1_button backcolor .8 .48 0 1 + setitemcolor team2_button backcolor .8 .48 0 1 + setitemcolor team1name forecolor .8 .48 0 1 + setitemcolor team2name forecolor .8 .48 0 1 + setitemcolor team1_count forecolor .8 .48 0 1 + setitemcolor team2_count forecolor .8 .48 0 1 + setitemcolor siegeclassconfigtitle forecolor 1 .68 0 1 + + // + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + setitemcolor team1obj_buttons forecolor 1 1 1 1 + setitemcolor team2obj_buttons forecolor 1 1 1 1 + + disable team1obj_buttons 0 + disable team2obj_buttons 0 + + hide grey_button + hide class_portrait + + // Show first objective + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_1 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective1_longdesc" + + // change objective pic + setitembackground objective_pic "*team1_objective1_gfx" + + // Show new map icon + setitembackground tm1_icon1 "*team1_objective1_mapicon" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + + setitemcolor mapicons bordercolor 0 0 0 0 + + // Disable current button and give it a border + setitemcolor team1obj1_button bordercolor 1 1 1 1 + setitemcolor team1obj1_button backcolor 1 0 0 1 + + // Set the map icon picture to the updated graphic + uiscript setsiegeobjbuttons tm1_icon1 team1_objective1_mapicon team1_objective1_litmapicon + } + + onESC + { + hide minidesc + hide fulldesc + hide objective_pic + close all; + } + + //------------------------------------------------------------ + // Window backdrop + //------------------------------------------------------------ + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 0 + decoration + } + + // Title of objective section + itemDef + { + name objectivetitle + style WINDOW_STYLE_FILLED + backcolor .05 .13 .25 1 + text @MENUS_MISSION_OBJECTIVES + rect 7 7 308 20 + textalign ITEM_ALIGN_CENTER + textalignx 160 + textaligny -1 + font 3 + textscale 0.9 + forecolor 1 .68 0 1 + visible 1 + decoration + } + + // Border around the objective half of the screen + itemDef + { + name border + group none + type ITEM_TYPE_TEXT + rect 5 5 310 370 +// forecolor 1 1 1 .6 + visible 1 + border 1 + bordercolor 1 1 1 1 + decoration + } + + + itemDef + { + name mapname + group time + style WINDOW_STYLE_EMPTY +// text @MENUS_MISSION + cvar siege_missionname + maxChars 0 + rect 70 28 180 18 + textalign ITEM_ALIGN_CENTER + textalignx 90 + font 3 + textscale .7 + forecolor 1 1 1 1 + visible 1 + decoration + } + + //------------------------------------------------------------ + // Map picture + //------------------------------------------------------------ + itemDef + { + name mappic + group none + style WINDOW_STYLE_SHADER + rect 70 46 180 320 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .38 .51 .59 1 + decoration + background "*siege_mapgraphic" + } + + +//---------------------------------------------------------------------------------------------- +// TEAM SYMBOLS +//---------------------------------------------------------------------------------------------- + itemDef + { + name team1_symbol + style WINDOW_STYLE_SHADER + rect 12 60 45 45 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name team2_symbol + style WINDOW_STYLE_SHADER + rect 265 60 45 45 + forecolor 1 1 1 1 + visible 1 + decoration + } + +//------------------------------------------- +// TEAM1 - OBJECTIVE BUTTON 1 +//------------------------------------------- + itemDef + { + name team1obj1_button + group team1obj_buttons + rect 22 120 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_1 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 1 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + mouseEnter + { + setitemcolor team1obj1_button forecolor 1 1 1 1 + setitemcolor team1obj1_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj1_button forecolor .75 .75 .75 1 + setitemcolor team1obj1_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_1 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective1_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective1_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button and give it a border + setcvar currentObjButton "team1obj1_button" + setitemcolor team1obj1_button bordercolor 1 1 1 1 + setitemcolor team1obj1_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj1 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon1 team1_objective1_mapicon team1_objective1_litmapicon + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM1 - OBJECTIVE BUTTON 2 +//---------------------------------------------------------------------------------------------- + itemDef + { + name team1obj2_button + group team1obj_buttons + rect 22 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_2 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective2_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj2_button forecolor 1 1 1 1 + setitemcolor team1obj2_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj2_button forecolor .75 .75 .75 1 + setitemcolor team1obj2_button backcolor .7 0 0 1 + } + + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_2 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective2_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective2_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj2_button" + setitemcolor team1obj2_button bordercolor 1 1 1 1 + setitemcolor team1obj2_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj2 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon2 team1_objective2_mapicon team1_objective2_litmapicon + + } + } + + itemDef + { + name team1obj3_button + group team1obj_buttons + rect 22 180 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_3 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective3_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj3_button forecolor 1 1 1 1 + setitemcolor team1obj3_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj3_button forecolor .75 .75 .75 1 + setitemcolor team1obj3_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_3 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective3_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective3_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj3_button" + setitemcolor team1obj3_button bordercolor 1 1 1 1 + setitemcolor team1obj3_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj3 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon3 team1_objective3_mapicon team1_objective3_litmapicon + } + } + + itemDef + { + name team1obj4_button + group team1obj_buttons + rect 22 210 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_4 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective4_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj4_button forecolor 1 1 1 1 + setitemcolor team1obj4_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj4_button forecolor .75 .75 .75 1 + setitemcolor team1obj4_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_4 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective4_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective4_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj4_button" + setitemcolor team1obj4_button bordercolor 1 1 1 1 + setitemcolor team1obj4_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj4 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon4 team1_objective4_mapicon team1_objective4_litmapicon + } + } + + itemDef + { + name team1obj5_button + group team1obj_buttons + rect 22 240 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_5 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective5_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj5_button forecolor 1 1 1 1 + setitemcolor team1obj5_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj5_button forecolor .75 .75 .75 1 + setitemcolor team1obj5_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_5 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective5_longdesc" + + + // change pic to match objective + setitembackground objective_pic "*team1_objective5_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj5_button" + setitemcolor team1obj5_button bordercolor 1 1 1 1 + setitemcolor team1obj5_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj5 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon5 team1_objective5_mapicon team1_objective5_litmapicon + } + } + + itemDef + { + name team1obj6_button + group team1obj_buttons + rect 22 270 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_6 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective6_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj6_button forecolor 1 1 1 1 + setitemcolor team1obj6_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj6_button forecolor .75 .75 .75 1 + setitemcolor team1obj6_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_6 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective6_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective6_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj6_button" + setitemcolor team1obj6_button bordercolor 1 1 1 1 + setitemcolor team1obj6_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj6 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon6 team1_objective6_mapicon team1_objective6_litmapicon + } + } + + itemDef + { + name team1obj7_button + group team1obj_buttons + rect 22 300 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_7 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective7_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj7_button forecolor 1 1 1 1 + setitemcolor team1obj7_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj7_button forecolor .75 .75 .75 1 + setitemcolor team1obj7_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_7 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective7_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective7_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj7_button" + setitemcolor team1obj7_button bordercolor 1 1 1 1 + setitemcolor team1obj7_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj7 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon7 team1_objective7_mapicon team1_objective7_litmapicon + } + } + + itemDef + { + name team1obj8_button + group team1obj_buttons + rect 22 330 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_8 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective8_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj8_button forecolor 1 1 1 1 + setitemcolor team1obj8_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj8_button forecolor .75 .75 .75 1 + setitemcolor team1obj8_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_8 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective8_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective8_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj8_button" + setitemcolor team1obj8_button bordercolor 1 1 1 1 + setitemcolor team1obj8_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj8 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon8 team1_objective8_mapicon team1_objective8_litmapicon + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM1 - MET OBJECTIVES BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name team1_met_obj1 + group none + style WINDOW_STYLE_SHADER + rect 22 120 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective1" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj2 + group none + style WINDOW_STYLE_SHADER + rect 22 150 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective2" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj3 + group none + style WINDOW_STYLE_SHADER + rect 22 180 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective3" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj4 + group none + style WINDOW_STYLE_SHADER + rect 22 210 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective4" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj5 + group none + style WINDOW_STYLE_SHADER + rect 22 240 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective5" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj6 + group none + style WINDOW_STYLE_SHADER + rect 22 270 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective6" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj7 + group none + style WINDOW_STYLE_SHADER + rect 22 300 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective7" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj8 + group none + style WINDOW_STYLE_SHADER + rect 22 330 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective8" + decoration + showcvar + { + "1" + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM2 - PRIMARY OBJECTIVE BUTTON +//---------------------------------------------------------------------------------------------- + itemDef + { + name team2obj1_button + group team2obj_buttons + rect 273 120 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_1 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + mouseEnter + { + setitemcolor team2obj1_button forecolor 1 1 1 1 + setitemcolor team2obj1_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj1_button forecolor .75 .75 .75 1 + setitemcolor team2obj1_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_1 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective1_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective1_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj1_button" + setitemcolor team2obj1_button bordercolor 1 1 1 1 + setitemcolor team2obj1_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj1 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon1 team2_objective1_mapicon team2_objective1_litmapicon + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM2 - SECONDARY OBJECTIVE BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name team2obj2_button + group team2obj_buttons + rect 273 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_2 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective2_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj2_button forecolor 1 1 1 1 + setitemcolor team2obj2_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj2_button forecolor .75 .75 .75 1 + setitemcolor team2obj2_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_2 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective2_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective2_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj2_button" + setitemcolor team2obj2_button bordercolor 1 1 1 1 + setitemcolor team2obj2_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj2 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon2 team2_objective2_mapicon team2_objective2_litmapicon + } + } + + itemDef + { + name team2obj3_button + group team2obj_buttons + rect 273 180 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_3 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective3_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj3_button forecolor 1 1 1 1 + setitemcolor team2obj3_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj3_button forecolor .75 .75 .75 1 + setitemcolor team2obj3_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_3 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective3_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective3_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj3_button" + setitemcolor team2obj3_button bordercolor 1 1 1 1 + setitemcolor team2obj3_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj3 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon3 team2_objective3_mapicon team2_objective3_litmapicon + } + } + + itemDef + { + name team2obj4_button + group team2obj_buttons + rect 273 210 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_4 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective4_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj4_button forecolor 1 1 1 1 + setitemcolor team2obj4_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj4_button forecolor .75 .75 .75 1 + setitemcolor team2obj4_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_4 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective4_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective4_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj4_button" + setitemcolor team2obj4_button bordercolor 1 1 1 1 + setitemcolor team2obj4_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj4 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon4 team2_objective4_mapicon team2_objective4_litmapicon + } + } + + itemDef + { + name team2obj5_button + group team2obj_buttons + rect 273 240 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_5 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective5_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj5_button forecolor 1 1 1 1 + setitemcolor team2obj5_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj5_button forecolor .75 .75 .75 1 + setitemcolor team2obj5_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_5 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective5_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective5_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj5_button" + setitemcolor team2obj5_button bordercolor 1 1 1 1 + setitemcolor team2obj5_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj5 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon5 team2_objective5_mapicon team2_objective5_litmapicon + } + } + + itemDef + { + name team2obj6_button + group team2obj_buttons + rect 273 270 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_6 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective6_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj6_button forecolor 1 1 1 1 + setitemcolor team2obj6_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj6_button forecolor .75 .75 .75 1 + setitemcolor team2obj6_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_6 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective6_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective6_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj6_button" + setitemcolor team2obj6_button bordercolor 1 1 1 1 + setitemcolor team2obj6_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj6 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon6 team2_objective6_mapicon team2_objective6_litmapicon + } + } + + itemDef + { + name team2obj7_button + group team2obj_buttons + rect 273 300 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_7 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective7_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj7_button forecolor 1 1 1 1 + setitemcolor team2obj7_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj7_button forecolor .75 .75 .75 1 + setitemcolor team2obj7_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_7 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective7_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective7_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj7_button" + setitemcolor team2obj7_button bordercolor 1 1 1 1 + setitemcolor team2obj7_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj7 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon7 team2_objective7_mapicon team2_objective7_litmapicon + } + } + + itemDef + { + name team2obj8_button + group team2obj_buttons + rect 273 330 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_8 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective8_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj8_button forecolor 1 1 1 1 + setitemcolor team2obj8_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj8_button forecolor .75 .75 .75 1 + setitemcolor team2obj8_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_8 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective8_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective8_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj8_button" + setitemcolor team2obj8_button bordercolor 1 1 1 1 + setitemcolor team2obj8_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj8 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon8 team2_objective8_mapicon team2_objective8_litmapicon + } + } + + +//---------------------------------------------------------------------------------------------- +// TEAM2 - MET OBJECTIVES +//---------------------------------------------------------------------------------------------- + itemDef + { + name team2_met_obj1 + group none + style WINDOW_STYLE_SHADER + rect 273 120 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective1" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj2 + group none + style WINDOW_STYLE_SHADER + rect 273 150 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective2" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj3 + group none + style WINDOW_STYLE_SHADER + rect 273 180 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective3" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj4 + group none + style WINDOW_STYLE_SHADER + rect 273 210 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective4" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj5 + group none + style WINDOW_STYLE_SHADER + rect 273 240 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective5" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj6 + group none + style WINDOW_STYLE_SHADER + rect 273 270 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective6" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj7 + group none + style WINDOW_STYLE_SHADER + rect 273 300 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective7" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj8 + group none + style WINDOW_STYLE_SHADER + rect 273 330 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective8" + decoration + showcvar + { + "1" + } + } + + +//---------------------------------------------------------------------------------------------- +// TEAM1 MAP ICONS - position for these is set in the on open of the menu +//---------------------------------------------------------------------------------------------- + itemDef + { + name tm1_icon1 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective1_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon2 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective2_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon3 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective3_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon4 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective4_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon5 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective5_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon6 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective6_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon7 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective7_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon8 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective8_inuse" + hidecvar + { + "0" + } + } + + +//---------------------------------------------------------------------------------------------- +// TEAM2 MAP ICONS - position for these is set in the on open of the menu +//---------------------------------------------------------------------------------------------- + itemDef + { + name tm2_icon1 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective1_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon2 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective2_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon3 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective3_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon4 + group mapicons + //rectcvar "siege_objective3_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective4_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon5 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective5_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon6 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective6_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon7 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective7_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon8 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective8_inuse" + hidecvar + { + "0" + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM1 - MET OBJECTIVES MAP ICONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name team1_met_map1 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective1_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective1_donemapicon" + visible 1 + cvartest "team1_objective1" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map2 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective2_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective2_donemapicon" + visible 1 + cvartest "team1_objective2" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map3 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective3_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective3_donemapicon" + visible 1 + cvartest "team1_objective3" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map4 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective4_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective4_donemapicon" + visible 1 + cvartest "team1_objective4" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map5 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective5_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective5_donemapicon" + visible 1 + cvartest "team1_objective5" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map6 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective6_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective6_donemapicon" + visible 1 + cvartest "team1_objective6" + decoration + showcvar + { + "1" + } + } + + + itemDef + { + name team1_met_map7 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective7_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective7_donemapicon" + visible 1 + cvartest "team1_objective7" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map8 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective8_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective8_donemapicon" + visible 1 + cvartest "team1_objective8" + decoration + showcvar + { + "1" + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM2 - MET OBJECTIVES MAP ICONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name team2_met_map1 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective1_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective1_donemapicon" + visible 1 + cvartest "team1_objective1" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map2 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective2_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective2_donemapicon" + visible 1 + cvartest "team1_objective2" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map3 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective3_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective3_donemapicon" + visible 1 + cvartest "team1_objective3" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map4 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective4_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective4_donemapicon" + visible 1 + cvartest "team1_objective4" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map5 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective5_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective5_donemapicon" + visible 1 + cvartest "team1_objective5" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map6 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective6_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective6_donemapicon" + visible 1 + cvartest "team1_objective6" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map7 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective7_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective7_donemapicon" + visible 1 + cvartest "team1_objective7" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map8 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective8_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective8_donemapicon" + visible 1 + cvartest "team1_objective8" + decoration + showcvar + { + "1" + } + } + + //------------------------------------------- + // OBJECTIVE MINI-DESCRIPTIONS + //------------------------------------------- + itemDef + { + name obj_minidesc + rect 7 378 308 20 + style WINDOW_STYLE_FILLED + backcolor .05 .13 .25 1 + text @MENUS_PRIMARY_OBJECTIVES + font 2 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 300 + forecolor 1 .68 0 1 + visible 0 + decoration + } + + //------------------------------------------- + // OBJECTIVE LONG DESCRIPTION + //------------------------------------------- + itemDef + { + name obj_longdesc + rect 16 403 270 76 + type ITEM_TYPE_TEXT + font 4 + textscale .8 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 1 + autowrapped + decoration + } + + + //------------------------------------------- + // OBJECTIVE PICTURE + //------------------------------------------- + itemDef + { + name objective_pic + rect 208 398 107 78 + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //------------------------------------------- + // COMPLETED TEXT + //------------------------------------------- + itemDef + { + name text_tm1_obj1 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective1" + showcvar + { + "1" + } + + } + + itemDef + { + name text_tm1_obj2 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective2" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm1_obj3 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective3" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm1_obj4 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective4" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm1_obj5 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective5" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm1_obj6 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective6" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm1_obj7 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective7" + showcvar + { + "1" + } + } + + itemDef + { + name text_tm1_obj8 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective8" + showcvar + { + "1" + } + } + + itemDef + { + name text_tm2_obj1 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective1" + showcvar + { + "1" + } + } + + itemDef + { + name text_tm2_obj2 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective2" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm2_obj3 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective3" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm2_obj4 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective4" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm2_obj5 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective5" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm2_obj6 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective6" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm2_obj7 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective7" + showcvar + { + "1" + } + } + + itemDef + { + name text_tm2_obj8 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective8" + showcvar + { + "1" + } + } + + + // Border around the objective text and pic (lower left section of screen) + itemDef + { + name objtext_border + group none + type ITEM_TYPE_TEXT + rect 5 378 310 98 + visible 1 + border 1 + bordercolor 1 1 1 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// TEAM1 LARGE MAP ICONS - position for these is set in the on open of the menu +//---------------------------------------------------------------------------------------------- + itemDef + { + name tm1_lp_icon + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm1_l_icon1 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm1_l_icon2 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + border 1 + bordercolor 1 1 1 1 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm1_l_icon3 + group largemapicons + //rectcvar "siege_objective3_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm1_l_icon4 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm1_l_icon5 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm1_l_icon6 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm1_l_icon7 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TEAM2 LARGE MAP ICONS - position for these is set in the on open of the menu +//---------------------------------------------------------------------------------------------- + itemDef + { + name tm2_lp_icon + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm2_l_icon1 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm2_l_icon2 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + border 1 + bordercolor 1 1 1 1 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm2_l_icon3 + group largemapicons + //rectcvar "siege_objective3_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm2_l_icon4 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm2_l_icon5 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm2_l_icon6 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name tm2_l_icon7 + group largemapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor 1 1 1 1 + visible 0 + decoration + } + + //----------------------------------------------- + // OKAY BUTTON + //----------------------------------------------- + itemDef + { + name okay + text @MENUS_DONE + type 1 + textscale .8 + group grpControlbutton + type ITEM_TYPE_BUTTON + rect 105 456 150 26 + textalign ITEM_ALIGN_CENTER + textalignx 50 + forecolor 1 .682 0 1 + backcolor .37 .1 .1 1 + visible 1 + action + { + play "sound/interface/button1.wav" + close all + } + } + } +} \ No newline at end of file diff --git a/base/ui/jamp/ingame_orders.menu b/base/ui/jamp/ingame_orders.menu new file mode 100644 index 0000000..63376bb --- /dev/null +++ b/base/ui/jamp/ingame_orders.menu @@ -0,0 +1,386 @@ +{ +\\ SETUP MENU \\ +{ + menuDef + { + name "ingame_orders" + visible 0 + fullscreen 0 + rect 45 30 200 240 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + focusColor 1 .75 0 1 + style 1 + border 1 + disableColor .5 .5 .5 1 + onopen + { + hide grpicon ; + show attack + } + + itemDef + { + name window + rect 10 15 180 225 + style 1 + backcolor 0 .1 0 1 + visible 1 + decoration + } + + itemDef + { + name orders + group grporders + text @MENUS_NAME + style 0 + ownerdraw UI_SELECTEDPLAYER + // As cycle through playerlist selected player in HUD cycles // + rect 10 20 200 20 + textalign 0 + textalignx 10 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + } + + + // GIVE ORDERS TO OTHERS // + itemDef + { + name orders + group grporders + text @MENUS_YOU_DECIDE + rect 0 80 200 20 + type 1 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 0" + } + ownerdrawflag UI_SHOW_LEADER + } + + itemDef + { + name orders + group grporders + type 1 + text @MENUS_ATTACK_THE_ENEMY_S_BASE + rect 0 100 200 20 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + cvarTest "g_gametype" + hideCvar + { + "5" ; + "6" + } + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 1" + } + ownerdrawflag UI_SHOW_LEADER + } + + itemDef + { + name orders + group grporders + type 1 + text @MENUS_DEFEND_OUR_BASE + rect 0 120 200 20 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + cvarTest "g_gametype" + hideCvar + { + "5" ; + "6" + } + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 2" + } + ownerdrawflag UI_SHOW_LEADER + } + + itemDef + { + name orders + group grporders + type 1 + text @MENUS_GET_OUR_FLAG_BACK + rect 0 140 200 20 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + cvarTest "g_gametype" + hideCvar + { + "5" ; + "6" + } + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 3" + } + ownerdrawflag UI_SHOW_LEADER + } + + itemDef + { + name orders + group grporders + text @MENUS_GUARD_OUR_FLAG_CARRIER + type 1 + rect 0 160 200 20 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + cvarTest "g_gametype" + hideCvar + { + "5" ; + "6" + } + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 4" + } + ownerdrawflag UI_SHOW_LEADER + } + + itemDef + { + name orders + group grporders + type 1 + text @MENUS_GET_ENEMY_FLAG_BACK_TO + rect 0 180 200 20 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + cvarTest "g_gametype" + hideCvar + { + "5" ; + "6" + } + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 5" + } + ownerdrawflag UI_SHOW_LEADER + } + + itemDef + { + name orders + group grporders + type 1 + text @MENUS_COMPLETE_THE_CURRENT + rect 0 100 200 20 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + cvarTest "g_gametype" + hideCvar + { + "5" ; + "7" + } + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 1" + } + ownerdrawflag UI_SHOW_LEADER + } + + itemDef + { + name orders + group grporders + type 1 + text @MENUS_PREVENT_THE_ENEMY_FROM + rect 0 120 200 20 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + cvarTest "g_gametype" + hideCvar + { + "5" ; + "7" + } + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 2" + } + ownerdrawflag UI_SHOW_LEADER + } + + itemDef + { + name orders + group grporders + type 1 + text @MENUS_FOLLOW_ME + rect 0 100 200 20 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + cvarTest "g_gametype" + hideCvar + { + "6" ; + "7" + } + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 1" + } + ownerdrawflag UI_SHOW_LEADER + } + + itemDef + { + name orders + group grporders + type 1 + text @MENUS_ASSIST_ME + rect 0 120 200 20 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + cvarTest "g_gametype" + hideCvar + { + "6" ; + "7" + } + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 2" + } + ownerdrawflag UI_SHOW_LEADER + } + + itemDef + { + name orders + group grporders + type 1 + text @MENUS_REGROUP + rect 0 140 200 20 + textalign 1 + textalignx 100 + textaligny 2 + textscale .25 + forecolor 1 1 1 1 + visible 1 + cvarTest "g_gametype" + hideCvar + { + "6" ; + "7" + } + mouseenter + { + hide grpicon + } + action + { + uiScript voiceOrders "cmd bot_order %d 3" + } + ownerdrawflag UI_SHOW_LEADER + } + } +} + + + + + + + + + + + + diff --git a/base/ui/jamp/ingame_player.menu b/base/ui/jamp/ingame_player.menu new file mode 100644 index 0000000..d6a758a --- /dev/null +++ b/base/ui/jamp/ingame_player.menu @@ -0,0 +1,1173 @@ +//------------------------------------------------------------------------------------------------ +// PLAYER SETUP MENU - ingame_player +// +// Choose player skin, name +// Choose light saber +// Allocate force powers +// +// g_gametypes +// "Free For All" 0 +// "Duel" 3 +// "Power Duel" 4 +// "Team FFA" 6 +// "Siege" 7 +// "Capture the Flag" 8 +// +// ui_net_gametype +// 0 = FFA +// 1 = DUEL +// 2 = POWER DUEL +// 3 = TEAM FFA +// 4 = SIEGE +// 5 = CTF +//------------------------------------------------------------------------------------------------ +{ + menuDef + { + name "ingame_player" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 20 25 600 440 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 320 + descY 428 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + onOpen + { + uiScript update "ui_GetName" + uiScript updateForceStatus + hide highlights + } + onClose + { + uiScript update "ui_SetName" + uiScript updateForceStatus + hide highlights + } + +//------------------------------------------------------------------------------------------------ +// Overall window backdrop +//------------------------------------------------------------------------------------------------ + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 600 440 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + // Button background at bottom of screen + itemDef + { + name background + group none + style WINDOW_STYLE_FILLED + rect 0 420 600 20 + backcolor .298 .305 .690 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + +//------------------------------------------------------------------------------------------------ +// Player Configuration +//------------------------------------------------------------------------------------------------ +// Player Configuration title + itemDef + { + name playerconfigtitle_glow + group none + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_buttonback" // Frame around button + rect 20 3 560 30 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name playerconfigtitle + style WINDOW_STYLE_EMPTY + text @MENUS_PLAYER_CONFIGURATION + rect 20 5 560 28 + textalign ITEM_ALIGN_CENTER + textalignx 280 + textaligny 2 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 3 + textscale 1 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + +//------------------------------------------------------------------------------------------------ +// Name entry field +//------------------------------------------------------------------------------------------------ + itemDef + { + name nameglow + group mods + style WINDOW_STYLE_SHADER + rect 25 31 300 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name namefield + type ITEM_TYPE_EDITFIELD + style 0 + text @MENUS_NAME1 + cvar "ui_Name" + maxchars 26 + rect 20 33 300 20 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -5 + font 2 + textscale 1 + forecolor 1 .682 0 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_ENTER_YOUR_NAME_HERE + visible 1 + action + { + play "sound/interface/button1" + } + mouseenter + { + show nameglow + } + mouseexit + { + hide nameglow + } + } + +//------------------------------------------------------------------------------------------------ +// Player Model label +//------------------------------------------------------------------------------------------------ + // Box around character models + itemDef + { + name background + group none + style WINDOW_STYLE_EMPTY + rect 15 65 570 230 + border 1 + bordercolor .298 .305 .690 1 + bordersize 2 + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Title box for character models + itemDef + { + name background + group none + style WINDOW_STYLE_FILLED + rect 17 57 570 20 + backcolor .298 .305 .690 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name modeltitle + style 0 + text @MENUS_CHARACTER_MODEL + rect 3 52 570 0 + textalign ITEM_ALIGN_CENTER + textalignx 285 +// textaligny 12 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 +// font 4 +// textscale 1 + font 2 + textscale 1 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1" + } + } + +//------------------------------------------------------------------------------------------------ +// Skin/Team Color Chooser +//------------------------------------------------------------------------------------------------ + itemDef + { + name setcolor + style 0 + text @MENUS_TEAM_COLOR + ownerdraw UI_SKIN_COLOR + rect 50 78 160 20 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -5 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale .8 + forecolor 1 .682 0 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_CHOOSE_THE_COLOR_FOR + cvarTest "ui_about_gametype" + hideCvar + { + "6" ; + "7" ; + "8" ; + "9" + } + visible 1 + action + { + play "sound/interface/button1" + } + } + +// Skin/Team Color Chooser + itemDef + { + name setcolor + style 0 + text @MENUS_TEAM_COLOR + ownerdraw UI_SKIN_COLOR + rect 50 78 160 20 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -5 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale .8 + forecolor 0.7 0.7 0.7 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_CHOOSE_THE_COLOR_FOR + cvarTest "ui_about_gametype" + showCvar + { + "6" ; + "7" ; + "8" ; + "9" + } + visible 1 + decoration + action + { + play "sound/interface/button1" + } + } + +//------------------------------------------------------------------------------------------------ +// Scroll box with portraits. +//------------------------------------------------------------------------------------------------ + itemDef + { + name headlist +// rect 20 90 388 82 + rect 20 90 404 194 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 64 + elementheight 64 + elementtype LISTBOX_IMAGE + feeder FEEDER_Q3HEADS +// horizontalscroll + backcolor 0 0 0 1 + border 1 + bordercolor 1 .682 0 1 + forecolor 1 1 1 1 + descText @MENUS_CHOOSE_THE_MODEL_FOR + visible 1 + textscale 0.7 + action + { + play "sound/interface/button1" + } + mouseenter + { + setitemcolor headlist bordercolor 1 0 0 1 + } + mouseexit + { + setitemcolor headlist bordercolor 1 .682 0 .7 + } + } + +//------------------------------------------------------------------------------------------------ +// Custom skin +//------------------------------------------------------------------------------------------------ + itemDef + { + name customtitle + style 0 + text @MENUS_CUSTOM + rect 425 130 150 26 + textalign ITEM_ALIGN_CENTER + textalignx 75 + textaligny -3 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name custom + group none + background "gfx/mp/custom_mp_default" + descText @MENUS_CUSTOMPLAYER_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_SHADER + rect 465 160 75 75 + font 3 + textscale 1 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor .5 .5 .5 1 + visible 1 + mouseEnter + { + setitemcolor custom forecolor 1 1 1 1 + } + mouseExit + { + setitemcolor custom forecolor .5 .5 .5 1 + } + action + { + play "sound/interface/button1.wav" + uiScript updateForceStatus + close ingame_player + open ingame_player2 + } + } + +//------------------------------------------------------------------------------------------------ +// Saber Setup +//------------------------------------------------------------------------------------------------ + // Box for force powers + itemDef + { + name background + group none + style WINDOW_STYLE_EMPTY + rect 15 299 410 100 + border 1 + bordercolor .298 .305 .690 1 + bordersize 2 + forecolor 1 1 1 1 + visible 1 + decoration + } + // Title box for force powers + itemDef + { + name background + group none + style WINDOW_STYLE_FILLED + rect 17 300 410 18 + backcolor .298 .305 .690 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Box for saber setup + itemDef + { + name background + group none + style WINDOW_STYLE_EMPTY + rect 430 299 155 100 + border 1 + bordercolor .298 .305 .690 1 + bordersize 2 + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Title box for saber setup + itemDef + { + name background + group none + style WINDOW_STYLE_FILLED + rect 432 300 155 18 + backcolor .298 .305 .690 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name sabertitle + style 0 + text @MENUS_SABER_TITLE + rect 430 297 150 26 + textalign ITEM_ALIGN_CENTER + textalignx 75 + textaligny -3 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name saber + group none + background "gfx/menus/saberonly" + descText @MENUS_SABER_TITLE_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_SHADER + rect 465 322 75 75 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor .5 .5 .5 1 + visible 1 + mouseEnter + { + setitemcolor saber forecolor 1 1 1 1 + } + mouseExit + { + setitemcolor saber forecolor .5 .5 .5 1 + } + action + { + play "sound/interface/button1.wav" + close ingame_player + open ingame_saber + } + } + + +//------------------------------------------------------------------------------------------------ +// FORCE POWER INFO (If normal server) +//------------------------------------------------------------------------------------------------ +// The Force title + itemDef + { + name forcetitle + group yesforce + style 0 + text @MENUS_THE_FORCE + rect 220 298 0 0 + textalign ITEM_ALIGN_CENTER + textalignx 0 + textaligny -4 + outlinecolor 1 .5 .5 .5 + font 2 + textscale 1 + forecolor .549 .854 1 1 + visible 1 + cvarTest "ui_about_gametype" + hideCvar + { + "1" ; + "2" + } + decoration + } + + + +// Force Rank display + itemDef + { + name rankdisplay + group "yesforce" + style 0 + text @MENUS_FORCE_MASTERY + ownerdraw UI_FORCE_RANK + rect 20 320 175 16 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + font 4 + textscale 1 + forecolor 1 .682 0 .8 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + visible 1 + decoration + cvarTest "ui_about_gametype" + hideCvar + { + "1" ; + "2" + } + } + +// Force Side Chooser + itemDef + { + name setside + group "yesforce" + style 0 + text @MENUS_FORCE_SIDE + ownerdraw UI_FORCE_SIDE + rect 20 340 175 16 + textalign ITEM_ALIGN_LEFT + textalignx 0 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + textaligny 0 + font 4 + textscale 1 + forecolor 1 .682 0 .8 + visible 1 + cvarTest "ui_about_gametype" + hideCvar + { + "1" ; + "2" + } + decoration + action + { + play "sound/interface/button1" + } + + } + +// Force pointsdisplay + itemDef + { + name setpoints + group "yesforce" + style 0 + text @MENUS_POINTS_REMAINING + ownerdraw UI_FORCE_POINTS + rect 20 360 175 16 + textalign ITEM_ALIGN_LEFT + textalignx 0 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + textaligny 0 + font 4 + textscale 1 + forecolor 1 .682 0 .8 + visible 1 + cvarTest "ui_about_gametype" + hideCvar + { + "1" ; + "2" + } + decoration + } + +// Jedi/Non-Jedi Chooser + itemDef + { + name jedinonjedi + group jedinonjedi + style 0 + text @MENUS_JEDI_NONJEDI + ownerdraw UI_JEDI_NONJEDI + rect 183 370 105 32 + textalign ITEM_ALIGN_LEFT + textalignx 0 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + textaligny 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_CHOOSE_JEDI_NONJEDI + visible 1 + action + { + uiScript updateForceStatus + } + } + +//------------------------------------------------------------------------------------------------ +// Configure force button +//------------------------------------------------------------------------------------------------ + itemDef + { + name configforcebutton + group "yesforce" + type 1 + style WINDOW_STYLE_SHADER + background "gfx/menus/configforce" + rect 275 320 75 75 + textalign ITEM_ALIGN_CENTER + textalignx 97 + textaligny 2 + font 2 + textscale 1 + forecolor .5 .5 .5 1 + descText @MENUS_SET_UP_THE_FORCE_ABILITIES + visible 1 + cvarTest "ui_about_gametype" + hideCvar + { + "1" ; + "2" + } + action + { + play "sound/interface/button1" + close ingame_player + open ingame_playerforce + hide highlights + } + mouseEnter + { + setitemcolor configforcebutton forecolor 1 1 1 1 + } + mouseExit + { + setitemcolor configforcebutton forecolor .5 .5 .5 1 + } + } + + +//---------------------------------------- +// DISABLED FORCE POWER (If g_forcepowerdisable) +//---------------------------------------- +// The Force title + itemDef + { + name forcetitle + group noforce + style 0 + text @MENUS_NOFORCE_DISABLED_0 + rect 220 298 0 0 + textalign ITEM_ALIGN_CENTER + textalignx 0 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + textaligny -4 + font 2 + textscale 1 + forecolor 1 1 1 1 + visible 1 + cvarTest "ui_about_gametype" + hideCvar + { + "1" ; + "2" + } + decoration + } + + + itemDef + { + name text2 + group "noforce" + style 0 + text @MENUS_NOFORCE_DISABLED_1 + rect 20 320 400 20 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny 0 + font 4 + textscale 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 1 .682 0 .8 + visible 1 + cvarTest "ui_about_gametype" + hideCvar + { + "1" ; + "2" + } + decoration + } + +//------------------------------------------------------------------------------------------------ +// HOLOCRON INFO +//------------------------------------------------------------------------------------------------ +// The Force title + itemDef + { + name forcetitle + group none + style 0 + text @MENUS_NOFORCE_HOLO_0 + rect 220 298 0 0 + textalign ITEM_ALIGN_CENTER + textalignx 0 + backcolor 0 0 0 0 + textaligny -4 + font 2 + textscale 1 + forecolor 1 1 1 1 + cvarTest "ui_about_gametype" + showCvar + { + "1" + } + visible 1 + decoration + } + + itemDef + { + name text1 + group none + style 0 + text @MENUS_NOFORCE_HOLO_1 + rect 20 330 400 20 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny 0 + font 4 + textscale 1 + forecolor 1 .682 0 .8 + outlinecolor 1 .5 .5 .5 + cvarTest "ui_about_gametype" + showCvar + { + "1" + } + visible 1 + decoration + } + + itemDef + { + name text2 + group none + style 0 + text @MENUS_NOFORCE_HOLO_2 + rect 20 350 400 20 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny 0 + font 4 + textscale 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 1 .682 0 .8 + cvarTest "ui_about_gametype" + showCvar + { + "1" + } + visible 1 + decoration + } + + itemDef + { + name text3 + group none + style 0 + text @MENUS_NOFORCE_HOLO_3 + rect 20 370 400 20 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny 0 + font 4 + textscale 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 1 .682 0 .8 + cvarTest "ui_about_gametype" + showCvar + { + "1" + } + visible 1 + decoration + } + +//------------------------------------------------------------------------------------------------ +// JEDI MASTER INFO +//------------------------------------------------------------------------------------------------ + // The Force title + itemDef + { + name forcetitle + group none + style 0 + text @MENUS_NOFORCE_JEDI_0 + rect 220 298 0 0 + textalign ITEM_ALIGN_CENTER + textalignx 0 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + textaligny -4 + font 2 + textscale 1 + forecolor 1 1 1 1 + cvarTest "ui_about_gametype" + showCvar + { + "2" + } + visible 1 + decoration + } + + itemDef + { + name text1 + group none + style 0 + text @MENUS_NOFORCE_JEDI_1 + rect 20 330 400 20 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny 0 + font 4 + textscale 1 + forecolor 1 .682 0 .8 + outlinecolor 1 .5 .5 .5 + cvarTest "ui_about_gametype" + showCvar + { + "2" + } + visible 1 + decoration + } + + itemDef + { + name text2 + group none + style 0 + text @MENUS_NOFORCE_JEDI_2 + rect 20 350 400 20 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny 0 + font 4 + textscale 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 1 .682 0 .8 + cvarTest "ui_about_gametype" + showCvar + { + "2" + } + visible 1 + decoration + } + + itemDef + { + name text3 + group none + style 0 + text @MENUS_NOFORCE_JEDI_3 + rect 20 370 400 20 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny 0 + font 4 + textscale 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 1 .682 0 .8 + cvarTest "ui_about_gametype" + showCvar + { + "2" + } + visible 1 + decoration + } + +//--------------------------------------------- +// APPLY BUTTON +//--------------------------------------------- +// APPLY, already on a team + itemDef + { + name applyjoinButton + group highlights + style WINDOW_STYLE_SHADER + rect 5 412 105 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + appearance_slot 3 + } + + itemDef + { + name applycurrent + group "playerapply" + text @MENUS_APPLY + type 1 + style WINDOW_STYLE_EMPTY + rect 5 412 105 32 + textalign ITEM_ALIGN_CENTER + textalignx 52 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + descText @MENUS_APPLY_CHANGES_AND_JOIN + visible 1 + action + { + play "sound/interface/button1" + uiScript setForce "none" + hide highlights + uiScript closeingame + } + mouseEnter + { + show applyjoinButton + } + mouseExit + { + hide applyjoinButton + } + } + + // APPLY Free + itemDef + { + name applyjoin + group "playerforcejoin" + text @MENUS_JOIN_GAME + type 1 + style WINDOW_STYLE_EMPTY + rect 5 412 105 32 + textalign ITEM_ALIGN_CENTER + textalignx 52 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + descText @MENUS_APPLY_CHANGES_AND_JOIN + visible 1 + action + { + play "sound/interface/button1" + uiScript setForce "free" + hide highlights + uiScript closeingame + } + mouseEnter + { + show applyjoinButton + } + mouseExit + { + hide applyjoinButton + } + } + +// APPLY RED + itemDef + { + name applyredButton + group highlights + style WINDOW_STYLE_SHADER + rect 110 412 105 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name applyred + group "playerforcered" + text @MENUS_JOIN_RED + type 1 + style WINDOW_STYLE_EMPTY + rect 110 412 105 32 + textalign ITEM_ALIGN_CENTER + textalignx 52 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + cvarTest "ui_about_gametype" + hideCvar + { + "0" ; + "1" ; + "2" ; + "3" ; + "4" + } + descText @MENUS_CHANGES_AND_JOIN_THE + visible 1 + action + { + play "sound/interface/button1" + uiScript setForce "red" + hide highlights + uiScript closeingame + } + mouseEnter + { + show applyredButton + } + mouseExit + { + hide applyredButton + } + } + + // APPLY BLUE + itemDef + { + name applyblueButton + group highlights + style WINDOW_STYLE_SHADER + rect 215 412 105 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name applyblue + group "playerforceblue" + text @MENUS_JOIN_BLUE + type 1 + style WINDOW_STYLE_EMPTY + rect 215 412 105 32 + textalign ITEM_ALIGN_CENTER + textalignx 52 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + cvarTest "ui_about_gametype" + hideCvar + { + "0" ; + "1" ; + "2" ; + "3" ; + "4" + } + descText @MENUS_AND_JOIN_THE_BLUE_TEAM + visible 1 + action + { + play "sound/interface/button1" + uiScript setForce "blue" + hide highlights + uiScript closeingame + } + mouseEnter + { + show applyblueButton + } + mouseExit + { + hide applyblueButton + } + } + +// APPLY SPECTATOR + itemDef + { + name applyspectateButton + group highlights + style WINDOW_STYLE_SHADER + rect 320 412 105 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name applyspectate + group "playerforcespectate" + text @MENUS_SPECTATE + type 1 + style WINDOW_STYLE_EMPTY + rect 320 412 105 32 + textalign ITEM_ALIGN_CENTER + textalignx 52 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + descText @MENUS_APPLY_CHANGES_BUT_REMAIN + visible 1 + action + { + play "sound/interface/button1" + uiScript setForce "s" + hide highlights + uiScript closeingame + } + mouseEnter + { + show applyspectateButton + } + mouseExit + { + hide applyspectateButton + } + } + } +} diff --git a/base/ui/jamp/ingame_player2.menu b/base/ui/jamp/ingame_player2.menu new file mode 100644 index 0000000..dab2e10 --- /dev/null +++ b/base/ui/jamp/ingame_player2.menu @@ -0,0 +1,558 @@ +//----------------------------------- +// Custom Player +//----------------------------------- +{ + menuDef + { + name "ingame_player2" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 105 40 430 425 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 320 + descY 450 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + onOpen + { + uiScript "getcharcvars" + uiScript "character" + uiScript updateForceStatus + hide highlights + } + onClose + { + uiScript updateForceStatus + hide highlights + } + +// Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 430 425 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------- +// Player Configuration +//---------------------------------------- +// Player Configuration title + itemDef + { + name playerconfigtitle + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CUSTOM_CHARACTER + rect 35 5 360 28 + textalign ITEM_ALIGN_CENTER + textalignx 180 + textaligny 2 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 3 + textscale 0.9 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + +// Character model + itemDef + { + name character + group models + type ITEM_TYPE_MODEL + rect 270 84 200 225 + model_g2anim "BOTH_WALK1" + asset_model "ui_char_model" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + model_g2mins -30 -15 -14 + model_g2maxs 20 15 30 + model_rotation 50 + model_fovx 50 + model_fovy 50 + isCharacter 1 + visible 1 + decoration + } + +////////////////// +// SPECIES BUTTON +////////////////// + + itemDef + { + name species + group none + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 15 48 140 24 + forecolor .549 .854 1 1 + text @MENUS_SPECIES + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + font 3 + textscale 1 + visible 1 + decoration + } + + itemDef + { + name speciesbut_glow + group none + style WINDOW_STYLE_SHADER + rect 161 52 150 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name speciesbut + group none + text " " + descText @MENUS_CHOOSE_SPECIES + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 161 52 150 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 0 + textalignx 0 + backcolor 0 0 0 0 + forecolor .615 .615 .956 1 + feeder 19 //FEEDER_PLAYER_SPECIES + cvar "ui_char_model" + cvarStrList feeder + + visible 1 + + mouseEnter + { + show speciesbut_glow + } + mouseExit + { + hide speciesbut_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "characterchanged" + uiScript "resetcharacterlistboxes" + } + } + +//////////////////// +// COLOR TINT AREA +//////////////////// + + + itemDef + { + name color + group none + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 15 80 160 24 + forecolor .549 .854 1 1 + text @MENUS_COLOR + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + font 3 + textscale 1 + visible 1 + decoration + } + + itemDef + { + name colorbox + group tints + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 32 + elementheight 32 + elementtype 1 //LISTBOX_IMAGE + feeder 23 //FEEDER_COLORCHOICES + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + rect 15 104 292 48 + visible 1 + action + { + play "sound/interface/choose_color.wav" + } + } + +/////////////////////// +//APPEARANCE +////////////////////// + + itemDef + { + name appear + group none + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 15 160 180 24 + forecolor .549 .854 1 1 + text @MENUS_APPEARANCE + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + font 3 + textscale 1 + visible 1 + decoration + } + + // HEAD BUTTON + itemDef + { + name headbut_glow + group none + style WINDOW_STYLE_SHADER + rect 15 184 90 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name headbut + group none + text @MENUS_HEAD + descText @MENUS_SELECT_HEAD + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 15 184 90 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 0 + textalignx 0 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show headbut_glow + } + mouseExit + { + hide headbut_glow + } + action + { + play "sound/interface/button1.wav" + show heads + hide torso + hide lower + } + } + + + // TORSO BUTTON + itemDef + { + name torsobut_glow + group none + style WINDOW_STYLE_SHADER + rect 111 184 90 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name torsobut + group none + text @MENUS_TORSO + descText @MENUS_SELECT_TORSO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 111 184 90 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 0 + textalignx 0 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show torsobut_glow + } + mouseExit + { + hide torsobut_glow + } + action + { + play "sound/interface/button1.wav" + show torso + hide heads + hide lower + } + } + + // LEGS BUTTON + itemDef + { + name legsbut_glow + group none + style WINDOW_STYLE_SHADER + rect 209 184 90 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name legsbut + group none + text @MENUS_LEGS + descText @MENUS_SELECT_LEGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 209 184 90 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 0 + textalignx 0 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show legsbut_glow + } + mouseExit + { + hide legsbut_glow + } + action + { + play "sound/interface/button1.wav" + show lower + hide heads + hide torso + } + } + +////////////////////// +//LISTBOXES +////////////////////// + + itemDef + { + name headlistbox + group heads + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 72 + elementheight 72 + elementtype 1 //LISTBOX_IMAGE + feeder 20 //FEEDER_PLAYER_SKIN_HEAD + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + forecolor -1 //use playercolor + rect 15 206 292 93 + visible 1 + action + { + play "sound/interface/choose_head.wav" + uiScript "char_skin" + } + } + + itemDef + { + name torsolistbox + group torso + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 72 + elementheight 72 + elementtype 1 //LISTBOX_IMAGE + feeder 21 //FEEDER_PLAYER_SKIN_TORSO + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + forecolor -1 //use playercolor + rect 15 206 292 93 + visible 0 + action + { + play "sound/interface/choose_torso.wav" + uiScript "char_skin" + } + } + + itemDef + { + name lowerlistbox + group lower + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 72 + elementheight 72 + elementtype 1 //LISTBOX_IMAGE + feeder 22 //FEEDER_PLAYER_SKIN_LEGS + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + forecolor -1 //use playercolor + rect 15 206 292 93 + visible 0 + action + { + play "sound/interface/choose_head.wav" + uiScript "char_skin" + } + } + + itemDef + { + name backButton + group highlights + style WINDOW_STYLE_SHADER + rect 30 370 110 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // BACK button + itemDef + { + name backmenu_button + text @MENUS_BACK + descText @MENUS_RETURN_PREVIOUS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 30 370 110 32 + font 2 + textscale .9 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 55 + textaligny 2 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show backButton + } + mouseExit + { + hide backButton + } + + action + { + play "sound/interface/esc.wav" ; + close ingame_player2 ; + open ingame_player ; + } + + } + +//--------------------------------------------- +// APPLY BUTTON +//--------------------------------------------- +// APPLY, already on a team + itemDef + { + name applyjoinButton + group highlights + style WINDOW_STYLE_SHADER + rect 290 370 110 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name applycurrent + group "playerapply" + text @MENUS_APPLY + type 1 + style WINDOW_STYLE_EMPTY + rect 290 370 110 32 + textalign ITEM_ALIGN_CENTER + textalignx 55 + textaligny 2 + font 2 + textscale .9 + forecolor 1 .682 0 1 + descText @MENUS_APPLY_CHANGES_AND_JOIN + visible 1 + action + { + play "sound/interface/button1" + uiScript "updatecharmodel" + hide highlights + close ingame_player2 + open ingame_player + } + mouseEnter + { + show applyjoinButton + } + mouseExit + { + hide applyjoinButton + } + } + + } +} diff --git a/base/ui/jamp/ingame_playerforce.menu b/base/ui/jamp/ingame_playerforce.menu new file mode 100644 index 0000000..0728519 --- /dev/null +++ b/base/ui/jamp/ingame_playerforce.menu @@ -0,0 +1,1575 @@ +//----------------------------------- +// SETUP MENU +//----------------------------------- +{ + menuDef + { + name "ingame_playerforce" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 105 40 430 425 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 320 + descY 450 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + onOpen + { + uiScript setForce "none" + } + onClose + { + uiScript setForce "none" + // open ingame_player + } + onEsc + { + close ingame_playerforce ; + } + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 430 425 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------------- + // Force Configuration + //---------------------------------------- + // Player Configuration title + itemDef + { + name playerconfigtitle + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CHOOSE_YOUR_FORCE_TRAINING + rect 20 5 390 28 + textalign ITEM_ALIGN_CENTER + textalignx 195 + textaligny 2 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 3 + textscale 0.9 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Rank display + itemDef + { + name siderank + group "playersettingforcegroup" + style 0 + text @MENUS_FORCE_MASTERY + ownerdraw UI_FORCE_RANK + rect 220 40 0 0 + textalign ITEM_ALIGN_CENTER + textalignx 0 + textaligny -6 + font 2 + textscale 1 + forecolor 1 .682 0 1 + backcolor 0 0 0 0 + visible 1 + decoration + } + + // Force Configuration title + itemDef + { + name playerconfigtitle + style 0 + text @MENUS_SPEND_YOUR_POINTS_BY + rect 220 60 0 0 + textalign ITEM_ALIGN_CENTER + textalignx 0 + textaligny 0 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 4 + textscale 1 + forecolor 1 .682 0 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force background light + itemDef + { + name forcedivlight + group lightpowers + style WINDOW_STYLE_SHADER + rect 0 85 225 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Force background dark + itemDef + { + name forcedivdark + group darkpowers + style WINDOW_STYLE_SHADER + rect 00 85 225 20 + background "gfx/menus/menu_blendboxr" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + // Force Side Chooser + itemDef + { + name setside + group "playersettingforcegroup" + style 0 + text @MENUS_FORCE_SIDE + ownerdraw UI_FORCE_SIDE + rect 26 85 175 16 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -5 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale 1 + forecolor .615 .615 .956 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_CHOOSE_THE_PATH_OF_THE + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + } + + // Force points remaining + itemDef + { + name siderank + group "playersettingforcegroup" + style 0 + text @MENUS_POINTS_REMAINING + ownerdraw UI_FORCE_POINTS + rect 250 85 0 0 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -5 + font 2 + textscale 1 + forecolor .549 .854 1 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + + + //---------------------------------------- + // FORCE TEMPLATES + //---------------------------------------- + // Force Template title + itemDef + { + name fcflist + group "playersettingforcegroup" + style 0 + text @MENUS_FORCE_TEMPLATES + rect 16 113 0 0 + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 0 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 4 + textscale 1 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Template picker + itemDef + { + name fcflist + rect 16 130 185 110 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 155 + elementheight 17 + font 2 + textscale .75 + elementtype LISTBOX_TEXT + feeder FEEDER_FORCECFG + textstyle 6 + textalign 3 + textaligny 2 + border 1 + bordercolor .5 .5 .5 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .25 .464 .578 .5 + descText @MENUS_CHOOSE_A_PRE_MADE_ALLOCATION + visible 1 + columns 1 2 190 250 + mouseenter + { + setitemcolor fcflist bordercolor 1 0 0 1 + } + mouseexit + { + setitemcolor fcflist bordercolor .5 .5 .5 1 + } + action + { + play "sound/interface/button1.wav" ; + } + } + + + //---------------------------------------- + // SAVE TEMPLATE + //---------------------------------------- + + //Save template title entry field + itemDef + { + name namefield + type ITEM_TYPE_EDITFIELD + style 0 + text @MENUS_FILENAME + cvar "ui_SaveFCF" + maxchars 12 + rect 5 250 185 32 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_ENTER_THE_TITLE_FOR_YOUR + visible 1 + } + + //Save template button + itemDef + { + name templatesavebutton + group "playerforcetemplatesave" + style WINDOW_STYLE_SHADER + rect 16 280 185 32 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + decoration + visible 0 + } + + //Save template button + itemDef + { + name templatesavetext + group "playerforcetemplatesave" + style WINDOW_STYLE_EMPTY + textalign ITEM_ALIGN_LEFT + rect 16 320 185 32 + forecolor 1 .682 0 .8 + font 4 + textscale 1 + text @MENUS_MATCHING_NAMES + decoration + visible 0 + } + + itemDef + { + name templatesave + group "playerforcetemplatesave" + text @MENUS_SAVE_FILE + type 1 + style WINDOW_STYLE_EMPTY + rect 16 280 185 32 + textalign 1 + textalignx 92 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + descText @MENUS_SAVE_CURRENT_FORCE_SETUP + visible 1 + action + { + play "sound/interface/button1.wav" ; + uiScript saveTemplate; + } + mouseEnter + { + show templatesavetext + show templatesavebutton + } + mouseExit + { + hide templatesavetext + hide templatesavebutton + } + } + + //---------------------------------------- + // FORCE POWER HEADERS + //---------------------------------------- + + //---------------------------------------- + // NEUTRAL POWERS + //---------------------------------------- + + // Force Ranks title + itemDef + { + name forceranktitle + group "playersettingforcegroup" + style 0 + text @MENUS_POWER + rect 230 112 0 0 + textalign ITEM_ALIGN_RIGHT + textalignx 104 + textaligny 0 + outlinecolor 1 .5 .5 .5 + backcolor 0.3 0.3 0.3 1 + font 4 + textscale 1 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + itemDef + { + name forceranktitle2 + group "playersettingforcegroup" + style 0 + text @MENUS_1_2_3 + rect 230 112 0 0 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny 0 + outlinecolor 1 .5 .5 .5 + backcolor 0.3 0.3 0.3 1 + font 4 + textscale 1 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Jump Title + itemDef + { + name setfp_jump + group "playersettingforcegroup" + style 0 + text @MENUS_FORCE_JUMP + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 130 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.8 0.8 0.8 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Jump Dots + itemDef + { + name setfp_jump + group "playersettingforcegroup" + style 0 + ownerdraw UI_FORCE_RANK_LEVITATION + rect 230 130 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.8 0.8 0.8 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_LEAP_TO_AMAZING_HEIGHTS + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_jump forecolor 1 1 1 1 + } + mouseexit + { + setitemcolor setfp_jump forecolor 0.8 0.8 0.8 1 + } + } + + // Force Push title + itemDef + { + name setfp_push + group "playersettingforcegroup" + style 0 + text @MENUS_FORCE_PUSH + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 147 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.8 0.8 0.8 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Push Assign + itemDef + { + name setfp_push + group "playersettingforcegroup" + style 0 + ownerdraw UI_FORCE_RANK_PUSH + rect 230 147 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.8 0.8 0.8 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_PUSH_YOUR_FOES_AWAY_AND + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_push forecolor 1 1 1 1 + } + mouseexit + { + setitemcolor setfp_push forecolor 0.8 0.8 0.8 1 + } + } + + // Force Pull title + itemDef + { + name setfp_pull + group "playersettingforcegroup" + style 0 + text @MENUS_FORCE_PULL + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 164 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.8 0.8 0.8 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Pull assign + itemDef + { + name setfp_pull + group "playersettingforcegroup" + style 0 + ownerdraw UI_FORCE_RANK_PULL + rect 230 164 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.8 0.8 0.8 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_PULL_YOUR_FOES_TO_YOU + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_pull forecolor 1 1 1 1 + } + mouseexit + { + setitemcolor setfp_pull forecolor 0.8 0.8 0.8 1 + } + } + + // Force Speed Title + itemDef + { + name setfp_speed + group "playersettingforcegroup" + style 0 + text @MENUS_FORCE_SPEED + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 181 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.8 0.8 0.8 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Speed Assign + itemDef + { + name setfp_speed + group "playersettingforcegroup" + style 0 + ownerdraw UI_FORCE_RANK_SPEED + rect 230 181 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.8 0.8 0.8 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_MOVE_AT_AN_ACCELERATED + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_speed forecolor 1 1 1 1 + } + mouseexit + { + setitemcolor setfp_speed forecolor 0.8 0.8 0.8 1 + } + } + + // Force Sight title + itemDef + { + name setfp_see + group "playersettingforcegroup" + style 0 + text @MENUS_FORCE_SIGHT + rect 230 198 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.8 0.8 0.8 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Sight assign + itemDef + { + name setfp_see + group "playersettingforcegroup" + style 0 + ownerdraw UI_FORCE_RANK_SEE + rect 230 198 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.8 0.8 0.8 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_SEE_ENEMIES_AT_ALL_TIMES + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_see forecolor 1 1 1 1 + } + mouseexit + { + setitemcolor setfp_see forecolor 0.8 0.8 0.8 1 + } + } + + //---------------------------------------- + // LIGHTSIDE POWERS + //---------------------------------------- + + // Force Absorb title + itemDef + { + name setfp_absorb + group lightpowers + style 0 + text @MENUS_FORCE_ABSORB + rect 230 223 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.5 0.5 1 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Absorb assign + itemDef + { + name setfp_absorb + group lightpowers + style 0 + ownerdraw UI_FORCE_RANK_ABSORB + rect 230 223 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.5 0.5 1 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_PROTECT_AGAINST_FORCE + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_absorb forecolor 0.75 0.75 1 1 + } + mouseexit + { + setitemcolor setfp_absorb forecolor 0.5 0.5 1 1 + } + } + + // Force Heal title + itemDef + { + name setfp_healself + group lightpowers + style 0 + text @MENUS_FORCE_HEAL + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 240 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.5 0.5 1 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Heal title + itemDef + { + name setfp_healself + group lightpowers + style 0 + ownerdraw UI_FORCE_RANK_HEAL + rect 230 240 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .9 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.5 0.5 1 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_HEAL_YOUR_BODY_WITH_THE + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_healself forecolor 0.75 0.75 1 1 + } + mouseexit + { + setitemcolor setfp_healself forecolor 0.5 0.5 1 1 + } + } + + // Force Protection title + itemDef + { + name setfp_protect + group lightpowers + style 0 + text @MENUS_FORCE_PROTECT + rect 230 257 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + textaligny 0 + font 4 + textscale 1 + forecolor 0.5 0.5 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Protection assign + itemDef + { + name setfp_protect + group lightpowers + style 0 + ownerdraw UI_FORCE_RANK_PROTECT + rect 230 257 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.5 0.5 1 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_CREATE_AN_AURA_THAT_PROTECTS + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_protect forecolor 0.75 0.75 1 1 + } + mouseexit + { + setitemcolor setfp_protect forecolor 0.5 0.5 1 1 + } + } + + // Force Mind Trick title + itemDef + { + name setfp_mindtrick + group lightpowers + style 0 + text @MENUS_FORCE_MINDTRICK + rect 230 274 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.5 0.5 1 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Mind Trick assign + itemDef + { + name setfp_mindtrick + group lightpowers + style 0 + ownerdraw UI_FORCE_RANK_TELEPATHY + rect 230 274 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .9 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.5 0.5 1 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_RENDER_YOURSELF_INVISIBLE + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_mindtrick forecolor 0.75 0.75 1 1 + } + mouseexit + { + setitemcolor setfp_mindtrick forecolor 0.5 0.5 1 1 + } + } + + // Force Team Heal title + itemDef + { + name setfp_teamheal + group lightpowers_team + style 0 + text @MENUS_FORCE_TEAM_HEAL + rect 230 291 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + textaligny 0 + font 4 + textscale 1 + forecolor 0.5 0.5 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + cvarTest "ui_about_gametype" + hideCvar + { + "0" ; + "1" ; + "2" ; + "3" ; + "4" + } + } + + // Force Team Heal assign + itemDef + { + name setfp_teamheal + group lightpowers_team + style 0 + ownerdraw UI_FORCE_RANK_TEAM_HEAL + rect 230 291 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.5 0.5 1 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_CHANNEL_THE_FORCE_TO + visible 1 + cvarTest "ui_about_gametype" + hideCvar + { + "0" ; + "1" ; + "2" ; + "3" ; + "4" + } + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_teamheal forecolor 0.75 0.75 1 1 + } + mouseexit + { + setitemcolor setfp_teamheal forecolor 0.5 0.5 1 1 + } + } + + //---------------------------------------- + // DARKSIDE POWERS + //---------------------------------------- + + // Force Grip title + itemDef + { + name setfp_grip + group darkpowers + style 0 + text @MENUS_FORCE_GRIP + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 223 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 1 0.2 0.2 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Grip assign + itemDef + { + name setfp_grip + group darkpowers + style 0 + ownerdraw UI_FORCE_RANK_GRIP + rect 230 223 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_IMMOBILIZE_OPPONENTS + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_grip forecolor 1 0.7 0.7 1 + } + mouseexit + { + setitemcolor setfp_grip forecolor 1 0.2 0.2 1 + } + } + + // Force Drain title + itemDef + { + name setfp_drain + group darkpowers + style 0 + text @MENUS_FORCE_DRAIN + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 240 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 1 0.2 0.2 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Drain assign + itemDef + { + name setfp_drain + group darkpowers + style 0 + ownerdraw UI_FORCE_RANK_DRAIN + rect 230 240 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_DRAIN_THE_FORCE_POWER + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_drain forecolor 1 0.7 0.7 1 + } + mouseexit + { + setitemcolor setfp_drain forecolor 1 0.2 0.2 1 + } + } + + // Force Lightning title + itemDef + { + name setfp_lightning + group darkpowers + style 0 + text @MENUS_FORCE_LIGHTNING + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 257 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 1 0.2 0.2 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Lightning assign + itemDef + { + name setfp_lightning + group darkpowers + style 0 + ownerdraw UI_FORCE_RANK_LIGHTNING + rect 230 257 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_UNLEASH_DEADLY_ELECTRICAL + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_lightning forecolor 1 0.7 0.7 1 + } + mouseexit + { + setitemcolor setfp_lightning forecolor 1 0.2 0.2 1 + } + } + + // Force Rage title + itemDef + { + name setfp_rage + group darkpowers + style 0 + text @MENUS_DARK_RAGE + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 274 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 1 0.2 0.2 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Force Rage assign + itemDef + { + name setfp_rage + group darkpowers + style 0 + ownerdraw UI_FORCE_RANK_RAGE + rect 230 274 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_BECOME_A_NEARLY_UNSTOPPABLE + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_rage forecolor 1 0.7 0.7 1 + } + mouseexit + { + setitemcolor setfp_rage forecolor 1 0.2 0.2 1 + } + } + + // Force Team Energize title + itemDef + { + name setfp_powerother + group darkpowers_team + style 0 + text @MENUS_FORCE_TEAM_REPLENISH + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 291 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 1 0.2 0.2 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + cvarTest "ui_about_gametype" + hideCvar + { + "0" ; + "1" ; + "2" ; + "3" ; + "4" + } + } + + // Force Team Energize assign + itemDef + { + name setfp_powerother + group darkpowers_team + style 0 + ownerdraw UI_FORCE_RANK_TEAM_FORCE + rect 230 291 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_THE_FORCE_TO_BOOST_THE + visible 1 + cvarTest "ui_about_gametype" + hideCvar + { + "0" ; + "1" ; + "2" ; + "3" ; + "4" + } + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_powerother forecolor 1 0.7 0.7 1 + } + mouseexit + { + setitemcolor setfp_powerother forecolor 1 0.2 0.2 1 + } + } + + //---------------------------------------- + // SABER POWERS + //---------------------------------------- + + // Saber attack title + itemDef + { + name setfp_saberattack + group "playersettingforcegroup" + style 0 + text @MENUS_SABER_ATTACK + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 316 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.8 0.8 0.8 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Saber attack assign + itemDef + { + name setfp_saberattack + group "playersettingforcegroup" + style 0 + ownerdraw UI_FORCE_RANK_SABERATTACK + rect 230 316 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.8 0.8 0.8 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_USE_MORE_POWERFUL_LIGHTSABER + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_saberattack forecolor 1 1 1 1 + } + mouseexit + { + setitemcolor setfp_saberattack forecolor 0.8 0.8 0.8 1 + } + } + + // Saber Defend title + itemDef + { + name setfp_saberdefend + group "playersettingforcegroup" + style 0 + text @MENUS_SABER_DEFEND + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 333 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.8 0.8 0.8 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Saber Defend assign + itemDef + { + name setfp_saberdefend + group "playersettingforcegroup" + style 0 + ownerdraw UI_FORCE_RANK_SABERDEFEND + rect 230 333 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + forecolor 0.8 0.8 0.8 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_USE_THE_LIGHTSABER_TO + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_saberdefend forecolor 1 1 1 1 + } + mouseexit + { + setitemcolor setfp_saberdefend forecolor 0.8 0.8 0.8 1 + } + } + + // Saber Throw title + itemDef + { + name setfp_saberthrow + group "playersettingforcegroup" + style 0 + text @MENUS_SABER_THROW + //descText @MENUS_DESCRIPTION_OF_A_FORCE + rect 230 350 110 15 + textalign ITEM_ALIGN_RIGHT + textalignx 105 + textaligny 0 + font 4 + textscale 1 + forecolor 0.8 0.8 0.8 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + visible 1 + decoration + } + + // Saber Throw assign + itemDef + { + name setfp_saberthrow + group "playersettingforcegroup" + style 0 + ownerdraw UI_FORCE_RANK_SABERTHROW + rect 230 350 175 15 + textalign ITEM_ALIGN_LEFT + textalignx 115 + textaligny -6 + textscale .25 + outlinecolor 1 .5 .5 .5 + background "forcecirclegray" + backcolor 0 0 0 0 + forecolor 0.8 0.8 0.8 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_THROW_YOUR_LIGHTSABER + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + setitemcolor setfp_saberthrow forecolor 1 1 1 1 + } + mouseexit + { + setitemcolor setfp_saberthrow forecolor 0.8 0.8 0.8 1 + } + } + + itemDef + { + name nosaber + group "playersettinggroup" + text @MENUS_NO_LIGHTSABER + type 1 + style 0 + rect 253 338 0 0 + textalign ITEM_ALIGN_CENTER + textalignx 65 + textaligny 0 + font 4 + textscale 1 + forecolor 0.5 0.5 0.5 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_MUST_ACQUIRE_A_MINIMUM + visible 0 + decoration + } + + // Add Saber Attack for saber + itemDef + { + name nosaber + group "playersettinggroup" + text @MENUS__REQUIRES_SABER_ATTACK + type 1 + style 0 + rect 253 355 0 0 + textalign ITEM_ALIGN_CENTER + textalignx 65 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + textaligny 0 + font 4 + textscale 1 + forecolor 0.5 0.5 0.5 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_MUST_ACQUIRE_A_MINIMUM + visible 0 + decoration + } + + // Invisible description box + itemDef + { + name nosaber + group "playersettinggroup" + style 0 + rect 230 338 175 34 + forecolor .7 .7 .7 1 + decoration + descText @MENUS_MUST_ACQUIRE_A_MINIMUM + visible 0 + } + + //--------------------------------------------- + // APPLY BUTTON + //--------------------------------------------- + itemDef + { + name applyjoinbutton + group "playerforcejoin" + style WINDOW_STYLE_SHADER + rect 140 370 150 32 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name applyjoin + group "playerforcejoin" + text @MENUS_APPLY_POWERS + type 1 + style WINDOW_STYLE_EMPTY + rect 140 370 150 32 + textalign 1 + textalignx 75 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + descText @MENUS_MAKE_THESE_CHANGES_TO + visible 1 + action + { + play "sound/interface/button1.wav" ; + uiScript setForce "same" + close ingame_playerforce ; +// uiScript updateForceStatus ; + open ingame_player + } + mouseEnter + { + show applyjoinbutton + } + mouseExit + { + hide applyjoinbutton + } + } + } +} + \ No newline at end of file diff --git a/base/ui/jamp/ingame_saber.menu b/base/ui/jamp/ingame_saber.menu new file mode 100644 index 0000000..4e336ee --- /dev/null +++ b/base/ui/jamp/ingame_saber.menu @@ -0,0 +1,1040 @@ +{ + menuDef + { + name "ingame_saber" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 105 40 430 425 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 320 + descY 440 + descScale 1 + descColor 1 .682 0 1 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + hide highlights + uiScript "getsabercvars" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 + uiScript getsaberhiltinfo + } + onClose + { + hide highlights + } + +// Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 430 425 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + +// Screen title + itemDef + { + name playerconfigtitle + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_LIGHTSABER_CREATION + rect 20 5 390 28 + textalign ITEM_ALIGN_CENTER + textalignx 195 + textaligny 2 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 3 + textscale 0.9 + forecolor 1 1 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + +//---------------------------------------------------------------------------------------------- +// SABER TYPE BUTTONS (standard, dual, two handed) +//---------------------------------------------------------------------------------------------- + itemDef + { + name typebut + group none + text @MENUS_SABER_TYPE + descText @MENUS_SABER_TYPE_DESC + style WINDOW_STYLE_EMPTY + rect 15 38 160 24 + font 3 + textscale .9 + textstyle 0 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name typebut_single_glow + group none + style WINDOW_STYLE_SHADER + rect 15 72 180 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + + //cvarTest ui_saber_type + //showCvar { "single" } + } + + itemDef + { + name typebut_dual_glow + group none + style WINDOW_STYLE_SHADER + rect 15 88 180 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name typebut_staff_glow + group none + style WINDOW_STYLE_SHADER + rect 15 104 180 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name typebut_single + group none + text @MENUS_SINGLESABER + descText @MENUS_SINGLESABER_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 15 72 160 16 + font 4 + textscale 1 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + mouseEnter + { + show typebut_single_glow + } + mouseExit + { + hide typebut_single_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "single" + uiScript "saber_type" + setcvar ui_saber "single_1" + setcvar ui_saber2 "none" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor 1 1 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 + show sabstyle + transition2 saber 0 "0" 430 430 20 10 + } + } + + itemDef + { + name typebut_dual + group none + text @MENUS_DUALSABERS + descText @MENUS_DUALSABERS_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 15 88 160 16 + font 4 + textscale 1 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + mouseEnter + { + show typebut_dual_glow + } + mouseExit + { + hide typebut_dual_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "dual" + uiScript "saber_type" + setcvar ui_saber "single_1" + setcvar ui_saber2 "single_1" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor 1 1 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 + hide sabstyle + transition2 saber 0 "-20" 430 430 20 5 + } + } + + itemDef + { + name typebut_staff + group none + text @MENUS_SABERSTAFF + descText @MENUS_SABERSTAFF_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 15 104 160 16 + font 4 + textscale 1 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + mouseEnter + { + show typebut_staff_glow + } + mouseExit + { + hide typebut_staff_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "staff" + uiScript "saber_type" + setcvar ui_saber "dual_1" + setcvar ui_saber2 "none" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor 1 1 1 1 + hide sabstyle + transition2 saber 0 "0" 430 430 20 10 + } + } + +//---------------------------------------------------------------------------------------------- +//HILTS +//---------------------------------------------------------------------------------------------- + itemDef + { + name hilttype + group none + text @MENUS_HILT1 + descText @MENUS_SABER_HILTS_DESC + style WINDOW_STYLE_EMPTY + rect 200 34 160 24 + font 3 + textscale .9 + textstyle 0 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "dual" } + decoration + } + + itemDef + { + name hilttype + group none + text @MENUS_HILT1 + descText @MENUS_SABER_HILTS_DESC + style WINDOW_STYLE_EMPTY + rect 200 34 160 24 + font 3 + textscale .7 + textstyle 0 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "staff" ; "single" } + decoration + } + + itemDef + { + name hilttype + group none + text @MENUS_HILT2 + descText @MENUS_SABER_HILTS_DESC + style WINDOW_STYLE_EMPTY + rect 200 105 160 24 + font 3 + textscale .7 + textstyle 0 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "staff" ; "single" } + decoration + } + + // HILT BUTTON 1 - SINGLE or DUAL + itemDef + { + name hiltbut_glow + group none + style WINDOW_STYLE_SHADER + rect 200 72 190 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + + + + // HILT BUTTON 1 - SINGLE + itemDef + { + name hiltbut + group none + rect 200 56 160 120 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_EMPTY + elementwidth 120 + elementheight 16 + font 4 + textaligny 16 + textscale 1 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SABER_SINGLE_INFO + + descText @MENUS_HILT1_DESC + elementtype LISTBOX_TEXT + textalign ITEM_ALIGN_LEFT + + cvarTest ui_saber_type + hideCvar { "staff" ; "dual" } + + visible 1 + action + { + play "sound/interface/choose_hilt.wav" + uiScript "setscreensaberhilt" + uiScript "saber_hilt" + } + } + + // HILT BUTTON 1 - STAVES + itemDef + { + name hiltbut_staves + group none + rect 200 56 160 120 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_EMPTY + elementwidth 120 + elementheight 16 + font 4 + textaligny 16 + textscale 1 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SABER_STAFF_INFO + + descText @MENUS_HILT1_DESC + + cvarTest ui_saber_type + hideCvar { "single"; "dual" } + + visible 1 + action + { + play "sound/interface/choose_hilt.wav" + uiScript "setscreensaberstaff" + uiScript "saber_hilt" + } + } + + // HILT BUTTON 1 - DUAL + itemDef + { + name hiltbut1 + group none + rect 200 50 160 55 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_EMPTY + elementwidth 120 + elementheight 16 + font 4 + textaligny 16 + textscale 1 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SABER_SINGLE_INFO + +// text @MENUS_HILT1 + descText @MENUS_HILT1_DESC + elementtype LISTBOX_TEXT + textalign ITEM_ALIGN_LEFT + + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + + visible 1 + action + { + play "sound/interface/choose_hilt.wav" + uiScript "setscreensaberhilt1" + uiScript "saber_hilt" + } + } + + // HILT BUTTON 2 - DUAL + itemDef + { + name hiltbut2 + group none + rect 200 120 160 55 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_EMPTY + elementwidth 120 + elementheight 16 + font 4 + textaligny 16 + textscale 1 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SABER_SINGLE_INFO + + +// text @MENUS_HILT2 + descText @MENUS_HILT2_DESC + forecolor .615 .615 .956 1 + + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + + visible 1 + action + { + play "sound/interface/choose_hilt.wav" + uiScript "setscreensaberhilt2" + uiScript "saber2_hilt" + } + } + + +//---------------------------------------------------------------------------------------------- +//BLADE COLORS +//---------------------------------------------------------------------------------------------- + itemDef + { + name bladecolortitle + group none + text @MENUS_BLADE_COLOR + descText @MENUS_BLADE_COLOR_DESC + style WINDOW_STYLE_EMPTY + rect 15 181 160 24 + font 2 + textscale .8 + textstyle 0 + textalignx 0 + textaligny -4 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name blueicon + group sabericons + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 15 197 24 24 + background "gfx/menus/saber_icon_blue" + forecolor .75 .75 .75 1 + descText @MENUS_BLADE_COLOR_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor blueicon forecolor 1 1 1 1 + setitemcolor blueicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor blueicon forecolor .75 .75 .75 1 + setitemcolor blueicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "blue" + } + } + + itemDef + { + name greenicon + group sabericons + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 40 197 24 24 + background "gfx/menus/saber_icon_green" + forecolor .75 .75 .75 1 + descText @MENUS_BLADE_COLOR_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor greenicon forecolor 1 1 1 1 + setitemcolor greenicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor greenicon forecolor .75 .75 .75 1 + setitemcolor greenicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "green" + } + } + + itemDef + { + name orangeicon + group sabericons + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 65 197 24 24 + background "gfx/menus/saber_icon_orange" + forecolor .75 .75 .75 1 + descText @MENUS_BLADE_COLOR_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor orangeicon forecolor 1 1 1 1 + setitemcolor orangeicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor orangeicon forecolor .75 .75 .75 1 + setitemcolor orangeicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "orange" + } + } + + itemDef + { + name purpleicon + group sabericons + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 90 197 24 24 + background "gfx/menus/saber_icon_purple" + forecolor .75 .75 .75 1 + descText @MENUS_BLADE_COLOR_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor purpleicon forecolor 1 1 1 1 + setitemcolor purpleicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor purpleicon forecolor .75 .75 .75 1 + setitemcolor purpleicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "purple" + } + } + + itemDef + { + name yellowicon + group sabericons + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 115 197 24 24 + background "gfx/menus/saber_icon_yellow" + forecolor .75 .75 .75 1 + descText @MENUS_BLADE_COLOR_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor yellowicon forecolor 1 1 1 1 + setitemcolor yellowicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor yellowicon forecolor .75 .75 .75 1 + setitemcolor yellowicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "yellow" + } + } + + itemDef + { + name redicon + group sabericons + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 140 197 24 24 + background "gfx/menus/saber_icon_red" + descText @MENUS_BLADE_COLOR_DESC + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor redicon forecolor 1 1 1 1 + setitemcolor redicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor redicon forecolor .75 .75 .75 1 + setitemcolor redicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "red" + } + } + + // COLOR 2 BUTTON + itemDef + { + name colorbut2 + group none + text @MENUS_COLOR2 + descText @MENUS_COLOR2_DESC + //type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 270 181 160 16 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -4 + forecolor .549 .854 1 1 + visible 1 + decoration + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + } + + itemDef + { + name blueicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 270 197 24 24 + background "gfx/menus/saber_icon_blue" + forecolor .75 .75 .75 1 + descText @MENUS_COLOR2_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor blueicon2 forecolor 1 1 1 1 + setitemcolor blueicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor blueicon2 forecolor .75 .75 .75 1 + setitemcolor blueicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "blue" + } + } + + itemDef + { + name greenicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 295 197 24 24 + background "gfx/menus/saber_icon_green" + forecolor .75 .75 .75 1 + descText @MENUS_COLOR2_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor greenicon2 forecolor 1 1 1 1 + setitemcolor greenicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor greenicon2 forecolor .75 .75 .75 1 + setitemcolor greenicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "green" + } + } + + itemDef + { + name orangeicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 320 197 24 24 + background "gfx/menus/saber_icon_orange" + forecolor .75 .75 .75 1 + descText @MENUS_COLOR2_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor orangeicon2 forecolor 1 1 1 1 + setitemcolor orangeicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor orangeicon2 forecolor .75 .75 .75 1 + setitemcolor orangeicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "orange" + } + } + + itemDef + { + name purpleicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 345 197 24 24 + background "gfx/menus/saber_icon_purple" + forecolor .75 .75 .75 1 + descText @MENUS_COLOR2_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor purpleicon2 forecolor 1 1 1 1 + setitemcolor purpleicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor purpleicon2 forecolor .75 .75 .75 1 + setitemcolor purpleicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "purple" + } + } + + itemDef + { + name yellowicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 370 197 24 24 + background "gfx/menus/saber_icon_yellow" + forecolor .75 .75 .75 1 + descText @MENUS_COLOR2_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor yellowicon2 forecolor 1 1 1 1 + setitemcolor yellowicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor yellowicon2 forecolor .75 .75 .75 1 + setitemcolor yellowicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "yellow" + } + } + + itemDef + { + name redicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 395 197 24 24 + background "gfx/menus/saber_icon_red" + forecolor .75 .75 .75 1 + descText @MENUS_COLOR2_DESC + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor redicon2 forecolor 1 1 1 1 + setitemcolor redicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor redicon2 forecolor .75 .75 .75 1 + setitemcolor redicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "red" + } + } + +//////////////////////// +//SABER MODELS +//////////////////////// + + //FIRST SABER + itemDef + { + name saber + group models + type ITEM_TYPE_MODEL + rect 0 -20 430 430 + asset_model "models/weapons2/saber_reborn/saber_w.glm" + isSaber 1 + model_angle 180 + model_rotation 20 + model_g2mins 0 0 0 + model_g2maxs 20 20 20 + model_fovx 75 + model_fovy 75 + visible 1 + decoration + } + + //SECOND SABER + itemDef + { + name saber2 + group models + type ITEM_TYPE_MODEL + rect 0 40 430 430 + asset_model "models/weapons2/saber_reborn/saber_w.glm" + isSaber2 1 + model_angle 180 + model_rotation 20 + model_g2mins 0 0 0 + model_g2maxs 20 20 20 + model_fovx 75 + model_fovy 75 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + + decoration + } + +//--------------------------------------------- +// APPLY BUTTON +//--------------------------------------------- + itemDef + { + name applyjoinButton + group highlights + style WINDOW_STYLE_SHADER + rect 160 360 110 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name applyjoinBorder + group none + style WINDOW_STYLE_EMPTY + rect 140 360 150 32 + forecolor 1 1 1 1 + decoration + border 1 + bordercolor 1 .682 0 1 + visible 1 + } + + + itemDef + { + name applycurrent + group "playerapply" + text @MENUS_APPLY_CHANGES + type 1 + style WINDOW_STYLE_EMPTY + rect 160 360 110 32 + textalign ITEM_ALIGN_CENTER + textalignx 55 + textaligny 2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + descText @MENUS_APPLY_CHANGES_AND_JOIN + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript "updatesabercvars" + hide highlights + close ingame_saber + } + mouseEnter + { + show applyjoinButton + } + mouseExit + { + hide applyjoinButton + } + } + } +} + diff --git a/base/ui/jamp/ingame_setup.menu b/base/ui/jamp/ingame_setup.menu new file mode 100644 index 0000000..e568962 --- /dev/null +++ b/base/ui/jamp/ingame_setup.menu @@ -0,0 +1,2271 @@ +//-------------------------------------------------------------- +// +// SETUP MENU +// +//-------------------------------------------------------------- +{ + menuDef + { + name "ingame_setup" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 45 35 550 335 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 305 + descY 347 + descScale 1 + descColor 1 .682 0 .8 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript getvideosetup // Get video settings + + show setup_background + show video + hide applyChanges + hide video2 + hide vidrestart + hide sound + hide options + hide mods + hide defaults + hide default_glow + hide highlight12 + + setitemcolor setup_submenu forecolor 1 .682 0 1 + setitemcolor videomenubutton forecolor 1 1 1 1 + } + + onESC + { + play "sound/interface/button1" + + defer VideoSetup videowarningMenu + hide highlights + close ingame_setup + } + + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 570 335 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + itemDef + { + name playerconfigtitle + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_SETUP + rect 20 5 510 28 + textalign ITEM_ALIGN_CENTER + textalignx 255 + textaligny 2 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 3 + textscale 0.9 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + + // video1 button + itemDef + { + name video1button_glow + style WINDOW_STYLE_SHADER + rect 20 43 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name video1menubutton + group setup_submenu + text @MENUS_VIDEO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 43 170 30 + font 3 + textscale 0.9 + textalignx 170 + textaligny 5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_VIDEO_SETTINGS + + mouseEnter + { + show video1button_glow + } + mouseExit + { + hide video1button_glow + } + action + { + play "sound/interface/button1" + + defer VideoSetup videowarningMenu + + uiScript getvideosetup // Get video settings + + show setup_background + show video + hide applyChanges + hide video2 + hide vidrestart + hide sound + hide options + hide mods + hide defaults + + setitemcolor setup_submenu forecolor 1 .682 0 1 + setitemcolor video1menubutton forecolor 1 1 1 1 + } + } + + // video2 button + itemDef + { + name video2button_glow + style WINDOW_STYLE_SHADER + rect 20 73 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name video2menubutton + group setup_submenu + text @MENUS_MORE_VIDEO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 73 170 30 + font 3 + textscale 0.9 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGUE_MORE_VIDEO_SETTINGS + + mouseEnter + { + show video2button_glow + } + mouseExit + { + hide video2button_glow + } + action + { + play "sound/interface/button1" + + defer VideoSetup videowarningMenu + + show setup_background + hide video + hide applyChanges + show video2 + hide vidrestart + hide sound + hide options + hide mods + hide defaults + + setitemcolor setup_submenu forecolor 1 .682 0 1 + setitemcolor video2menubutton forecolor 1 1 1 1 + } + } + + // sound button + itemDef + { + name soundbutton_glow + style WINDOW_STYLE_SHADER + rect 20 103 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name soundmenubutton + group setup_submenu + text @MENUS_SOUND + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 103 170 30 + font 3 + textscale 0.9 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_SOUND_SETTINGS + + mouseEnter + { + show soundbutton_glow + } + mouseExit + { + hide soundbutton_glow + } + action + { + play "sound/interface/button1" + + defer VideoSetup videowarningMenu + + show setup_background + hide video + hide applyChanges + hide video2 + hide vidrestart + show sound + hide options + hide mods + hide defaults + + setitemcolor setup_submenu forecolor 1 .682 0 1 + setitemcolor soundmenubutton forecolor 1 1 1 1 + } + } + + // gameoptions button + itemDef + { + name gameoptionsbutton_glow + style WINDOW_STYLE_SHADER + rect 20 133 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name gameoptionmenubutton + group setup_submenu + text @MENUS_OPTIONS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 133 170 30 + font 3 + textscale 0.9 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_GAME_OPTIONS + + mouseEnter + { + show gameoptionsbutton_glow + } + mouseExit + { + hide gameoptionsbutton_glow + } + action + { + play "sound/interface/button1" + + defer VideoSetup videowarningMenu + + show setup_background + hide video + hide applyChanges + hide video2 + hide vidrestart + hide sound + show options + hide langapply + hide mods + hide defaults + + setitemcolor setup_submenu forecolor 1 .682 0 1 + setitemcolor gameoptionmenubutton forecolor 1 1 1 1 + } + } + + // mods button + itemDef + { + name modsbutton_glow + style WINDOW_STYLE_SHADER + rect 20 163 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + + // gamedefaults button + itemDef + { + name gamedefaultsbutton_glow + style WINDOW_STYLE_SHADER + rect 20 163 185 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name gamedefaultsmenubutton + group setup_submenu + text @MENUS_DEFAULTS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 20 163 170 30 + font 3 + textscale 0.9 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_RESTORE_DEFAULT_SETTINGS + + mouseEnter + { + show gamedefaultsbutton_glow + } + mouseExit + { + hide gamedefaultsbutton_glow + } + action + { + play "sound/interface/button1" + + defer VideoSetup videowarningMenu + + show setup_background + hide video + hide applyChanges + hide video2 + hide vidrestart + hide sound + hide options + hide mods + show defaults + + setitemcolor setup_submenu forecolor 1 .682 0 1 + setitemcolor gamedefaultsmenubutton forecolor 1 1 1 1 + } + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 210 41 350 250 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } + + + //---------------------------------------------------------------------------------------------- + // + // HIGHLIGHT BARS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name highlight1 + group highlights + style WINDOW_STYLE_SHADER + rect 220 43 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight2 + group highlights + style WINDOW_STYLE_SHADER + rect 220 63 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight3 + group highlights + style WINDOW_STYLE_SHADER + rect 220 83 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight4 + group highlights + style WINDOW_STYLE_SHADER + rect 220 103 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight5 + group highlights + style WINDOW_STYLE_SHADER + rect 220 123 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight6 + group highlights + style WINDOW_STYLE_SHADER + rect 220 143 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight7 + group highlights + style WINDOW_STYLE_SHADER + rect 220 163 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight8 + group highlights + style WINDOW_STYLE_SHADER + rect 220 183 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight9 + group highlights + style WINDOW_STYLE_SHADER + rect 220 203 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight10 + group highlights + style WINDOW_STYLE_SHADER + rect 220 223 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight11 + group highlights + style WINDOW_STYLE_SHADER + rect 220 243 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight12 + group highlights + style WINDOW_STYLE_SHADER + rect 220 263 300 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight13 + group highlights + style WINDOW_STYLE_SHADER + rect 365 283 120 32 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + //---------------------------------------------------------------------------------------------- + // + // VIDEO 1 MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name graphics + group video + text @MENUS_VIDEO_QUALITY + type ITEM_TYPE_MULTI + rect 220 41 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + style WINDOW_STYLE_FILLED + forecolor .615 .615 .956 1 + descText @MENUS_SELECT_PRESET_GRAPHIC + + visible 0 + + cvar "ui_r_glCustom" + cvarFloatList + { + @MENUS_HIGH_QUALITY 0 + @MENUS_NORMAL 1 + @MENUS_FAST 2 + @MENUS_FASTEST 3 + @MENUS_CUSTOM 4 + } + + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + action + { + play "sound/interface/button1" + uiScript update "ui_r_glCustom" + setcvar ui_r_modified 1 + show applyChanges + } + } + + itemDef + { + name video_mode + group video + type ITEM_TYPE_MULTI + text @MENUS_VIDEO_MODE + cvarFloatList { @MENUS_640_X_480 3 @MENUS_800_X_600 4 @MENUS_1024_X_768 6 @MENUS_1152_X_864 7 @MENUS_1280_X_1024 8 @MENUS_1600_X_1200 9 @MENUS_2048_X_1536 10 @MENUS_2400_X_600 12 } + cvar "ui_r_mode" + + rect 220 83 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + descText @MENUS_CHANGE_THE_DISPLAY_RESOLUTION + + visible 0 + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + action + { + play "sound/interface/button1" + uiScript glCustom + setcvar ui_r_modified 1 + show applyChanges + } + } + + itemDef + { + name color_depth + group video + type ITEM_TYPE_MULTI + text @MENUS_COLOR_DEPTH + rect 220 103 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + + cvarFloatList { @MENUS_DEFAULT 0 @MENUS_16_BIT 16 @MENUS_32_BIT 32 } + descText @MENUS_CHANGE_THE_NUMBER_OF + cvar "ui_r_colorbits" + + visible 0 + + mouseenter + { + show highlight4 + } + mouseexit + { + hide highlight4 + } + action + { + play "sound/interface/button1" + uiScript glCustom + uiScript update "ui_r_colorbits" + setcvar ui_r_modified 1 + show applyChanges + } + } + + itemDef + { + name fullscreen + group video + type ITEM_TYPE_MULTI + text @MENUS_FULL_SCREEN + rect 220 123 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_TOGGLE_BETWEEN_FULL_SCREEN + cvar "ui_r_fullscreen" + + visible 0 + + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + action + { + play "sound/interface/button1" + uiScript glCustom + setcvar ui_r_modified 1 + show applyChanges + } + } + + itemDef + { + name geometric_detail + group video + type ITEM_TYPE_MULTI + text @MENUS_GEOMETRIC_DETAIL + rect 220 143 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_LOW 2 @MENUS_MEDIUM 1 @MENUS_HIGH 0 } + descText @MENUS_ADJUST_THE_NUMBER_OF + cvar "ui_r_lodbias" + + visible 0 + + mouseenter + { + show highlight6 + } + mouseexit + { + hide highlight6 + } + action + { + play "sound/interface/button1" + uiScript glCustom + uiScript update "ui_r_lodbias" + setcvar ui_r_modified 1 + show applyChanges + } + } + + itemDef + { + name texture_detail + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_DETAIL + rect 220 163 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_LOW 3 @MENUS_MEDIUM 2 @MENUS_HIGH 1 @MENUS_VERY_HIGH 0 } + descText @MENUS_SELECT_THE_RESOLUTION + cvar "ui_r_picmip" + + visible 0 + + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + action + { + play "sound/interface/button1" + uiScript glCustom + setcvar ui_r_modified 1 + show applyChanges + } + } + + itemDef + { + name texture_quality + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_QUALITY + rect 220 183 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_DEFAULT 0 @MENUS_16_BIT 16 @MENUS_32_BIT 32 } + descText @MENUS_SELECT_THE_NUMBER_OF + cvar "ui_r_texturebits" + + visible 0 + + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + action + { + play "sound/interface/button1.wav" + uiScript glCustom + setcvar ui_r_modified 1 + show applyChanges + } + } + + itemDef + { + name texture_filter + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_FILTER + rect 220 203 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarStrList { @MENUS_BILINEAR , "GL_LINEAR_MIPMAP_NEAREST" , @MENUS_TRILINEAR , "GL_LINEAR_MIPMAP_LINEAR" } + descText @MENUS_ADJUST_HOW_WELL_THE_TEXTURES + cvar "ui_r_texturemode" + + visible 0 + + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + action + { + play "sound/interface/button1.wav" + uiScript glCustom + setcvar ui_r_modified 1 + show applyChanges + } + } + + itemDef + { + name simple_shaders + group video + type ITEM_TYPE_MULTI + text @MENUS_DETAILED_SHADERS + rect 220 223 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_HIDE_OR_UNHIDE_TEXTURES + cvar "ui_r_detailtextures" + + visible 0 + + mouseenter + { + show highlight10 + } + mouseexit + { + hide highlight10 + } + action + { + play "sound/interface/button1" + uiScript glCustom + setcvar ui_r_modified 1 + show applyChanges + } + } + + itemDef + { + name VIDEO_SYNC + group video + type ITEM_TYPE_MULTI + text @MENUS_VIDEO_SYNC + cvar "r_swapInterval" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 220 243 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_VIDEO_SYNC_DESC + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + } + + itemDef + { + name compress_textures + group video_obsolete + type ITEM_TYPE_MULTI + text @MENUS_COMPRESSED_TEXTURES + rect 220 243 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_TAKE_ADVANTAGE_OF_3D + cvar "ui_r_ext_compress_textures" + + visible 0 + + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + action + { + play "sound/interface/button1" + uiScript glCustom + setcvar ui_r_modified 1 + show applyChanges + } + } + + // APPLY CHANGES BUTTON + itemDef + { + name applybutton_glow + style WINDOW_STYLE_SHADER + rect 260 265 200 18 + background "gfx/menus/menu_blendbox" + forecolor 1 0.5 0.5 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name applyChanges + text @MENUS_APPLY_CHANGES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 260 260 200 30 + font 4 + textscale 1 + textalignx 100 + textaligny 5 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 0 0 1 + backcolor 0 0 1 0 + visible 0 + + mouseEnter + { + show applybutton_glow + } + mouseExit + { + hide applybutton_glow + } + action + { + play "sound/interface/button1" + show setup_background + show vidrestart + hide video + hide video2 + hide applybutton_glow + } + } + //---------------------------------------------------------------------------------------------- + // + // VIDEO RESTART + // + //---------------------------------------------------------------------------------------------- + // Faint red box + itemDef + { + name vidrestart_background + group vidrestart + style WINDOW_STYLE_SHADER + rect 210 41 302 252 + background "gfx/menus/menu_boxred" // Frame + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name vidrestart_text1 + group vidrestart + text @MENUS_THIS_WILL_APPLY_VIDEO + text2 @MENUS_AND_RETURNTO_GAME + rect 210 100 300 20 + textalign ITEM_ALIGN_CENTER + text2aligny 18 + textalignx 150 + font 2 + textscale 1 + forecolor 1 1 0 1 + visible 0 + decoration + } + + itemDef + { + name vidrestart_text2 + group vidrestart + text @MENUS_VID_RESTART3 + rect 210 160 256 20 + textalign ITEM_ALIGN_CENTER + textalignx 150 + font 2 + textscale 1 + forecolor 1 1 0 1 + visible 0 + } + + + + itemDef + { + name vidrestart_yes_button + style WINDOW_STYLE_SHADER + rect 367 241 120 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + // appearance_slot 4 + } + + // YES button + itemDef + { + name vidrestart_yes + group vidrestart + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 367 241 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + descText @MENUS_APPLY_CHANGES_AND_THEN + forecolor 1 .682 0 1 + visible -1 + // appearance_slot 5 + + action + { + play "sound/interface/button1.wav" + close all + uiScript updatevideosetup + } + mouseEnter + { + show vidrestart_yes_button + } + mouseExit + { + hide vidrestart_yes_button + } + + } + + itemDef + { + name vidrestart_no_button + style WINDOW_STYLE_SHADER + rect 240 241 120 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + // appearance_slot 3 + } + + // CANCEL button + itemDef + { + name vidrestart_no + group vidrestart + text @MENUS_NO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 240 241 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -1 + descText @MENUS_DO_NOT_APPLY_CHANGES + forecolor 1 .682 0 1 + visible 0 + // appearance_slot 6 + action + { + play "sound/interface/button1" + show setup_background + hide vidrestart + show video + hide video2 + show applyChanges + hide vidrestart_yes_button + hide vidrestart_no_button + } + mouseEnter + { + show vidrestart_no_button + } + mouseExit + { + hide vidrestart_no_button + } + + } + + //---------------------------------------------------------------------------------------------- + // + // VIDEO 2 + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name gamma_text + group video2 + style WINDOW_STYLE_SHADER + rect 220 63 260 36 + background "gfx/menus/greyscale" // greyscale + forecolor 1 1 1 1 + visible 0 + decoration + // appearance_slot 1 + } + + + + itemDef + { + name bright_text + group video2 + text @MENUS_ADJUST_BRIGHTNESS_SLIDER + text2 @MENUS_THE_NUMBER_6_CAN_BARELY + text2aligny 14 + textalignx 128 + textaligny 0 + font 4 + textscale 1 + rect 220 103 256 20 + textalign ITEM_ALIGN_CENTER + forecolor 0.7 0.7 0.7 1 + visible 0 + // appearance_slot 2 + decoration + } + + itemDef + { + name brightness + group video2 + type ITEM_TYPE_SLIDER + text @MENUS_VIDEO_BRIGHTNESS + cvarfloat "r_gamma" 1 .5 3 + rect 220 141 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 120 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 3 + descText @MENUS_ADJUST_THE_BRIGHTNESS + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight6 + } + mouseexit + { + hide highlight6 + } + } + + + itemDef + { + name dynamic_light + group video2 + type ITEM_TYPE_MULTI + text @MENUS_DYNAMIC_LIGHTS + cvar "r_dynamiclight" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 220 183 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_TOGGLE_TO_TURN_ON_MOVING + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name wall_marks + group video2 + type ITEM_TYPE_MULTI + text @MENUS_WALL_MARKS + cvar "cg_marks" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 220 203 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_TOGGLE_TO_DISPLAY_SCORCH + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + } + + itemDef + { + name video_mode + group video2 + type ITEM_TYPE_SLIDER + text @MENUS_ANISOTROPIC_FILTERING + cvarTest r_ext_texture_filter_anisotropic_avail + disableCvar { 0 } + cvarfloat r_ext_texture_filter_anisotropic 1 .5 16 + rect 220 223 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 5 + + descText @MENUS_TOGGLE_ADVANCED_TEXTURE + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight10 + } + mouseexit + { + hide highlight10 + } + } + + itemDef + { + name light_flares + group video2_obsolete + type ITEM_TYPE_MULTI + text @MENUS_LIGHT_FLARES + cvar "r_flares" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 220 263 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_TOGGLE_TO_SHOW_HALOS + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight12 + } + mouseexit + { + hide highlight12 + } + } + + //---------------------------------------------------------------------------------------------- + // + // SOUND FIELDS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name effects_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_EFFECTS_VOLUME + cvarfloat "s_volume" 0 0 1 + rect 220 61 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 120 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_ADJUST_VOLUME_FOR_SOUND + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight2 + } + mouseexit + { + hide highlight2 + } + } + + itemDef + { + name music_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_MUSIC_VOLUME + cvarfloat "s_musicvolume" 0 0 1 + rect 220 81 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 120 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 2 + descText @MENUS_ADJUST_VOLUME_FOR_MUSIC + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + } + + itemDef + { + name voice_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_VOICE_VOLUME + cvarfloat "s_volumeVoice" 0 0 1 + rect 220 101 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 120 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_VOICE_VOLUME_DESC + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight4 + } + mouseexit + { + hide highlight4 + } + } + + + itemDef + { + name sound_quality + group sound + type ITEM_TYPE_MULTI + text @MENUS_SOUND_QUALITY + cvar "s_khz" + cvarFloatList { @MENUS_LOW 11 @MENUS_HIGH 22 } + rect 220 141 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TRADE_CLARITY_OF_SOUND + cvarTest "s_UseOpenAL" + hideCvar { 1 } + + mouseenter + { + show highlight6 + } + mouseexit + { + hide highlight6 + } + action + { + play "sound/interface/button1" + uiScript update "s_khz" + } + } + itemDef + { + name eax + group sound + type ITEM_TYPE_MULTI + cvar "s_UseOpenAL" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + text @MENUS_EAX + rect 220 181 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 120 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_EAX_DESC + action + { + play "sound/interface/button1.wav" + uiScript update s_khz + } + + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name eax_icon + group sound + style WINDOW_STYLE_SHADER + rect 297 201 128 64 + background "gfx/menus/eax" + forecolor 1 1 1 1 + visible 1 + decoration + cvarTest "s_UseOpenAL" + hideCvar { 0 } + } + + //---------------------------------------------------------------------------------------------- + // + // OPTION FIELDS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name draw_crosshair + group options + type ITEM_TYPE_MULTI + text @MENUS_DRAW_CROSSHAIR + cvar "cg_drawcrosshair" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 220 41 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_TOGGLE_TO_SHOW_OR_HIDE + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + } + + + itemDef + { + name identifytarget + group options + type ITEM_TYPE_MULTI + text @MENUS_IDENTIFY_TARGET + // cvar "cg_crosshairIdentifyTarget" + cvar "cg_drawCrosshairNames" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 220 61 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + // appearance_slot 1 + descText @MENUS_TOGGLE_TO_HAVE_THE_CROSSHAIR + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight2 + } + mouseexit + { + hide highlight2 + } + } + + itemDef + { + name forcemodels + group options + type ITEM_TYPE_MULTI + text @MENUS_FORCE_PLAYER_MODELS + cvar "cg_forceModel" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 220 81 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_FORCE_ALL_PLAYER_MODELS + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + } + + itemDef + { + name defermodels + group options + type ITEM_TYPE_MULTI + text @MENUS_DEFER_PLAYER_MODELS + cvar "cg_deferPlayers" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 220 101 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_DEFERS_LOADING_OF_NEW + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight4 + } + mouseexit + { + hide highlight4 + } + } + + itemDef + { + name teamchatsonly + group options + type ITEM_TYPE_MULTI + text @MENUS_TEAM_CHATS_ONLY + cvar "cg_teamChatsOnly" + cvarFloatList + { + @MENUS_NO 0 + @MENUS_YES 1 + } + rect 220 121 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_IGNORE_NON_TEAM_CHATS + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + } + + itemDef + { + name simpleitems + group options + type ITEM_TYPE_MULTI + text @MENUS_SIMPLE_ITEMS + cvar "cg_simpleItems" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 220 141 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_RENDER_ICONS_FOR_ITEMS + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight6 + } + mouseexit + { + hide highlight6 + } + } + + itemDef + { + name teamoverlay + group options + type ITEM_TYPE_MULTI + text @MENUS_TEAM_OVERLAY + cvar "cg_drawTeamOverlay" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 220 161 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_DRAW_OVERLAY_SHOWING + action + { + play "sound/interface/button1" + } + + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + } + +// Weapon Sway. Yes, this is nutty. Two cvars here, one removes weapon sway, the other adds it. + itemDef + { + name weaponswayon + group options + type ITEM_TYPE_MULTI + text @MENUS_VIEW_SWAYING + descText @MENUS_VIEW_SWAYING_DESC + cvar "ui_disableWeaponSway" + cvarFloatList + { + @MENUS_ON 0 + @MENUS_OFF 1 + } + cvarTest "ui_disableWeaponSway" + showCvar + { + "0" + } + rect 220 181 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + + action + { + play "sound/interface/button1" + exec "exec noMotion.cfg" + show weaponswayoff + setfocus weaponswayoff + } + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name weaponswayoff + group options + type ITEM_TYPE_MULTI + text @MENUS_VIEW_SWAYING + descText @MENUS_VIEW_SWAYING_DESC + cvar "ui_disableWeaponSway" + cvarFloatList + { + @MENUS_ON 0 + @MENUS_OFF 1 + } + cvarTest "ui_disableWeaponSway" + hideCvar + { + "0" + } + rect 220 181 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + + action + { + play "sound/interface/button1" + exec "exec restoreMotion.cfg" + show weaponswayon + setfocus weaponswayon + } + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name footsteps + group options + type ITEM_TYPE_MULTI + text @MENUS_FOOTSTEPS + desctext @MENUS_FOOTSTEPS_DESC + cvar "cg_footsteps" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_SOUNDS 1 + @MENUS_SOUNDS_AND_EFFECTS 2 + @MENUS_SOUNDS_EFFECTS_GRAPHICS 3 + } + rect 220 201 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + } + + itemDef + { + name text + group options + type ITEM_TYPE_MULTI + text @MENUS_TEXT + cvar "se_language" + feeder 40 + cvarStrList feeder + rect 220 221 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_CHOOSE_THE_LANGUAGE_FOR + action + { + play "sound/interface/button1.wav" + show langapply + } + + mouseenter + { + show highlight10 + } + mouseexit + { + hide highlight10 + } + } + + itemDef + { + name voice + group options + type ITEM_TYPE_MULTI + text @MENUS_VOICE + cvar "s_language" + cvarStrList + { + English english + @MENUS_LANG_FRENCH francais + Deutsch deutsch + "Español" espanol + } + rect 220 241 300 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_CHOOSE_THE_LANGUAGE_TO + + action + { + play "sound/interface/button1.wav" + show langapply + } + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + } + + + // APPLY CHANGES button + itemDef + { + name langapply + group options + text @MENUS_SET_LANGUAGE + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 365 261 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + descText @MENUS_SET_LANGUAGE_INFO + forecolor 1 .682 0 1 + visible 0 + // appearance_slot 5 + + action + { + play "sound/interface/button1" + exec "ui_load ; snd_restart" + hide langapply + } + mouseEnter + { + show highlight12 + } + mouseExit + { + hide highlight12 + } + + } + + //---------------------------------------------------------------------------------------------- + // + // RESET DEFAULTS + // + //---------------------------------------------------------------------------------------------- + // Faint red box + itemDef + { + name vidrestart_background + group defaults + style WINDOW_STYLE_SHADER + rect 210 41 352 252 + background "gfx/menus/menu_boxred" // Frame + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_WARNING + rect 210 61 352 20 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 176 + forecolor 1 .682 0 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_THIS_WILL_SET_ALL_GAME + rect 210 101 352 20 + text2aligny 20 + font 2 + textscale .9 + textalign ITEM_ALIGN_CENTER + textalignx 176 + forecolor 1 .682 0 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_TO_THEIR_FACTORY_SETTINGS + rect 210 131 352 20 + text2aligny 20 + font 2 + textscale .9 + textalign ITEM_ALIGN_CENTER + textalignx 176 + forecolor 1 .682 0 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_VID_RESTART3 + rect 220 191 352 20 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 176 + forecolor 1 .682 0 1 + visible 0 + decoration + } + + + itemDef + { + name default_yes_button + group default_glow + style WINDOW_STYLE_SHADER + rect 430 241 120 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + // appearance_slot 4 + } + + // YES button - lose reset defaults + itemDef + { + name default_yes + group defaults + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 430 241 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 60 + textaligny 0 + descText @MENUS_USE_DEFAULT_SETTINGS + forecolor 1 .682 0 1 + visible 0 + // appearance_slot 5 + + action + { + play "sound/interface/button1" + close all + uiscript resetdefaults + } + mouseEnter + { + show default_yes_button + } + mouseExit + { + hide default_yes_button + } + + } + + itemDef + { + name default_no_button + group default_glow + style WINDOW_STYLE_SHADER + rect 220 241 120 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + // appearance_slot 3 + } + + // NO button - return to Main Menu + itemDef + { + name default_no + group defaults + text @MENUS_NO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 220 241 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 60 + textaligny 0 + descText @MENUS_DO_NOT_RESET_DEFAULT + forecolor 1 .682 0 1 + visible 0 + // appearance_slot 6 + action + { + play "sound/interface/button1" + uiscript clearmouseover default_no + hide defaults + hide default_glow + close all + open ingame_setup + } + mouseEnter + { + show default_no_button + } + mouseExit + { + hide default_no_button + } + } + } +} diff --git a/base/ui/jamp/ingame_siegeobjectives.menu b/base/ui/jamp/ingame_siegeobjectives.menu new file mode 100644 index 0000000..8a1655d --- /dev/null +++ b/base/ui/jamp/ingame_siegeobjectives.menu @@ -0,0 +1,987 @@ +{ + menuDef + { + name "ingame_siegeobjectives" + visible 0 + fullscreen 0 + outOfBoundsClick + //rect 105 40 430 425 + rect 15 40 610 425 + focusColor 1 1 1 1 + style 1 + border 1 + descX 320 + descY 438 + descScale 0.7 + descColor .79 .64 .22 .7 + descAlignment ITEM_ALIGN_CENTER + onClose + { + hide seconddescA + hide seconddescB + hide seconddescC + hide seconddescD + hide seconddescE + hide seconddescF + hide seconddescG + hide secondpicA + hide secondpicB + hide secondpicC + hide secondpicD + hide secondpicE + hide secondpicF + hide secondpicG + hide secondpic_text + hide missionpics + } + onOpen + { + show primeicon + hide seconddescA + hide seconddescB + hide seconddescC + hide seconddescD + hide seconddescE + hide seconddescF + hide seconddescG + hide secondpicA + hide secondpicB + hide secondpicC + hide secondpicD + hide secondpicE + hide secondpicF + hide secondpicG + exec "siegeCvarUpdate" + setitembackground mappic "*siege_mapgraphic" + setitembackground primeicon "*siege_primobj_mapicon" + setitembackground primepic "*siege_primobj_gfx" + setitembackground secondpicA "*siege_objective1_gfx" + setitembackground secondpicB "*siege_objective2_gfx" + setitembackground secondpicC "*siege_objective3_gfx" + setitembackground secondpicD "*siege_objective4_gfx" + setitembackground secondpicE "*siege_objective5_gfx" + setitembackground secondpicF "*siege_objective6_gfx" + setitembackground secondpicG "*siege_objective7_gfx" + setitembackground secondiconA "*siege_objective1_mapicon" + setitembackground secondiconB "*siege_objective2_mapicon" + setitembackground secondiconC "*siege_objective3_mapicon" + setitembackground secondiconD "*siege_objective4_mapicon" + setitembackground secondiconE "*siege_objective5_mapicon" + setitembackground secondiconF "*siege_objective6_mapicon" + setitembackground secondiconG "*siege_objective7_mapicon" + setitemrectcvar primeicon "siege_primobj_mappos" + setitemrectcvar secondiconA "siege_objective1_mappos" + setitemrectcvar secondiconB "siege_objective2_mappos" + setitemrectcvar secondiconC "siege_objective3_mappos" + setitemrectcvar secondiconD "siege_objective4_mappos" + setitemrectcvar secondiconE "siege_objective5_mappos" + setitemrectcvar secondiconF "siege_objective6_mappos" + setitemrectcvar secondiconG "siege_objective7_mappos" + } + onESC + { + hide seconddescA + hide seconddescB + hide seconddescC + hide seconddescD + hide seconddescE + hide seconddescF + hide seconddescG + hide secondpicA + hide secondpicB + hide secondpicC + hide secondpicD + hide secondpicE + hide secondpicF + hide secondpicG + hide secondpic_text + hide missionpics + close all; + } + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 610 425 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name mappic + group none + style WINDOW_STYLE_SHADER + rect 420 42 180 348 + forecolor 1 1 1 .6 + visible 1 + border 1 + bordercolor .2 .3 .7 1 + decoration + background "*siege_mapgraphic" + } + + itemDef + { + name objectivetitle + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_MISSION_OBJECTIVES + rect 120 5 390 28 + textalign ITEM_ALIGN_CENTER + textalignx 195 + textaligny 2 + outlinecolor 1 .5 .5 .5 + font 3 + textscale 0.9 + forecolor 1 1 1 1 + visible 1 + decoration + } + +//------------------------------------------- +// +// PRIMARY OBJECTIVE +// +//------------------------------------------- + + itemDef + { + name primaryobj + rect 10 42 200 24 + type ITEM_TYPE_TEXT + style WINDOW_STYLE_FILLED + text @MENUS_PRIMARY_OBJECTIVES + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 5 + textaligny -2 + backcolor .2 .3 .7 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + decoration + } + + itemDef + { + name primedesc + rect 10 68 402 76 + type ITEM_TYPE_TEXT + cvar "siege_primobj_desc" + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 6 + forecolor 1 1 1 1 + visible 1 + autowrapped + decoration + } + +//------------------------------------------- +// +// SECONDARY OBJECTIVES +// +//------------------------------------------- + + //------------------------------------------- + // TITLE + //------------------------------------------- + + itemDef + { + name secondobj + rect 10 150 200 24 + type ITEM_TYPE_TEXT + style WINDOW_STYLE_FILLED + text @MENUS_SECONDARY_OBJECTIVES + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 5 + textaligny -2 + backcolor .2 .3 .7 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + decoration + } + + //------------------------------------------- + // OBJECTIVES + //------------------------------------------- + + itemDef + { + name secondobjA + rect 215 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_1 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .2 .3 .7 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + cvartest "siege_objective1_inuse" + showcvar + { + "2" + } + action + { + show secondpic_text + show seconddescA + hide seconddescB + hide seconddescC + hide seconddescD + hide seconddescE + hide seconddescF + hide seconddescG + show secondpicA + hide secondpicB + hide secondpicC + hide secondpicD + hide secondpicE + hide secondpicF + hide secondpicG + show secondiconA + hide secondiconB + hide secondiconC + hide secondiconD + hide secondiconE + hide secondiconF + hide secondiconG + } + } + + itemDef + { + name secondobjB + rect 244 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_2 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .2 .3 .7 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + cvartest "siege_objective2_inuse" + showcvar + { + "2" + } + action + { + show secondpic_text + hide seconddescA + show seconddescB + hide seconddescC + hide seconddescD + hide seconddescE + hide seconddescF + hide seconddescG + hide secondpicA + show secondpicB + hide secondpicC + hide secondpicD + hide secondpicE + hide secondpicF + hide secondpicG + hide secondiconA + show secondiconB + hide secondiconC + hide secondiconD + hide secondiconE + hide secondiconF + hide secondiconG + } + } + + itemDef + { + name secondobjC + rect 273 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_3 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .2 .3 .7 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + cvartest "siege_objective3_inuse" + showcvar + { + "2" + } + action + { + show secondpic_text + hide seconddescA + hide seconddescB + show seconddescC + hide seconddescD + hide seconddescE + hide seconddescF + hide seconddescG + hide secondpicA + hide secondpicB + show secondpicC + hide secondpicD + hide secondpicE + hide secondpicF + hide secondpicG + hide secondiconA + hide secondiconB + show secondiconC + hide secondiconD + hide secondiconE + hide secondiconF + hide secondiconG + } + } + + itemDef + { + name secondobjD + rect 302 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_4 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .2 .3 .7 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + cvartest "siege_objective4_inuse" + showcvar + { + "2" + } + action + { + show secondpic_text + hide seconddescA + hide seconddescB + hide seconddescC + show seconddescD + hide seconddescE + hide seconddescF + hide seconddescG + hide secondpicA + hide secondpicB + hide secondpicC + show secondpicD + hide secondpicE + hide secondpicF + hide secondpicG + hide secondiconA + hide secondiconB + hide secondiconC + show secondiconD + hide secondiconE + hide secondiconF + hide secondiconG + } + } + + itemDef + { + name secondobjE + rect 331 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_5 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .2 .3 .7 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + cvartest "siege_objective5_inuse" + showcvar + { + "2" + } + action + { + show secondpic_text + hide seconddescA + hide seconddescB + hide seconddescC + hide seconddescD + show seconddescE + hide seconddescF + hide seconddescG + hide secondpicA + hide secondpicB + hide secondpicC + hide secondpicD + show secondpicE + hide secondpicF + hide secondpicG + hide secondiconA + hide secondiconB + hide secondiconC + hide secondiconD + show secondiconE + hide secondiconF + hide secondiconG + } + } + + itemDef + { + name secondobjF + rect 360 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_6 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .2 .3 .7 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + cvartest "siege_objective6_inuse" + showcvar + { + "2" + } + action + { + show secondpic_text + hide seconddescA + hide seconddescB + hide seconddescC + hide seconddescD + hide seconddescE + show seconddescF + hide seconddescG + hide secondpicA + hide secondpicB + hide secondpicC + hide secondpicD + hide secondpicE + show secondpicF + hide secondpicG + hide secondiconA + hide secondiconB + hide secondiconC + hide secondiconD + hide secondiconE + show secondiconF + hide secondiconG + } + } + + itemDef + { + name secondobjG + rect 389 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_7 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .2 .3 .7 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + cvartest "siege_objective7_inuse" + showcvar + { + "2" + } + action + { + show secondpic_text + hide seconddescA + hide seconddescB + hide seconddescC + hide seconddescD + hide seconddescE + hide seconddescF + show seconddescG + hide secondpicA + hide secondpicB + hide secondpicC + hide secondpicD + hide secondpicE + hide secondpicF + show secondpicG + hide secondiconA + hide secondiconB + hide secondiconC + hide secondiconD + hide secondiconE + hide secondiconF + show secondiconG + } + } + +//------------------------------------------- +// +// SECONDARY OBJECTIVES DESCRIPTIONS +// +//------------------------------------------- + + itemDef + { + name seconddescA + rect 10 176 402 76 + type ITEM_TYPE_TEXT + cvar "siege_objective1_desc" + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 6 + forecolor 1 1 1 1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name seconddescB + rect 10 176 402 76 + type ITEM_TYPE_TEXT + cvar "siege_objective2_desc" + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 6 + forecolor 1 1 1 1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name seconddescC + rect 10 176 402 76 + type ITEM_TYPE_TEXT + cvar "siege_objective3_desc" + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 6 + forecolor 1 1 1 1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name seconddescD + rect 10 176 402 76 + type ITEM_TYPE_TEXT + cvar "siege_objective4_desc" + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 6 + forecolor 1 1 1 1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name seconddescE + rect 10 176 402 76 + type ITEM_TYPE_TEXT + cvar "siege_objective5_desc" + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 6 + forecolor 1 1 1 1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name seconddescF + rect 10 176 402 76 + type ITEM_TYPE_TEXT + cvar "siege_objective6_desc" + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 6 + forecolor 1 1 1 1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name seconddescG + rect 10 176 402 76 + type ITEM_TYPE_TEXT + cvar "siege_objective7_desc" + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 6 + forecolor 1 1 1 1 + visible 0 + autowrapped + decoration + } + +//------------------------------------------- +// +// MAP ICONS +// +//------------------------------------------- + + itemDef + { + name primeicon + group missionpics + //rectcvar "siege_primobj_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name secondiconA + group missionpics + //rectcvar "siege_objective1_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name secondiconB + group missionpics + //rectcvar "siege_objective2_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name secondiconC + group missionpics + //rectcvar "siege_objective3_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name secondiconD + group missionpics + //rectcvar "siege_objective4_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name secondiconE + group missionpics + //rectcvar "siege_objective5_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name secondiconF + group missionpics + //rectcvar "siege_objective6_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name secondiconG + group missionpics + //rectcvar "siege_objective7_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//------------------------------------------- +// +// OBJECTIVE PICTURES +// +//------------------------------------------- + + itemDef + { + name primepic_text + rect 10 382 192 14 + type ITEM_TYPE_TEXT + text @MENUS_PRIMARY_OBJECTIVES + font 2 + textscale .6 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 2 + forecolor .79 .64 .22 1 + visible 1 + autowrapped + decoration + } + + itemDef + { + name secondpic_text + rect 212 382 192 14 + type ITEM_TYPE_TEXT + text @MENUS_SECONDARY_OBJECTIVES + font 2 + textscale .6 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 2 + forecolor .79 .64 .22 1 + visible 0 + autowrapped + decoration + } + + itemDef + { + name primepic + group primepic + rect 10 254 192 128 + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor .79 .64 .22 1 + visible 1 + decoration + } + + itemDef + { + name secondpicA + group missionpics + rect 212 254 192 128 + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor .79 .64 .22 1 + visible 0 + decoration + } + + itemDef + { + name secondpicB + group missionpics + rect 212 254 192 128 + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor .79 .64 .22 1 + visible 0 + decoration + } + + itemDef + { + name secondpicC + group missionpics + rect 212 254 192 128 + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor .79 .64 .22 1 + visible 0 + decoration + } + + itemDef + { + name secondpicD + group missionpics + rect 212 254 192 128 + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor .79 .64 .22 1 + visible 0 + decoration + } + + itemDef + { + name secondpicE + group missionpics + rect 212 254 192 128 + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor .79 .64 .22 1 + visible 0 + decoration + } + + itemDef + { + name secondpicF + group missionpics + rect 212 254 192 128 + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor .79 .64 .22 1 + visible 0 + decoration + } + + itemDef + { + name secondpicG + group missionpics + rect 212 254 192 128 + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + border 1 + bordercolor .79 .64 .22 1 + visible 0 + decoration + } + + + itemDef + { + name exit + text @MENUS_OKAY + type 1 + textscale 1 + group grpControlbutton + type ITEM_TYPE_BUTTON + rect 160 392 300 26 + textalign ITEM_ALIGN_CENTER + textalignx 150 + forecolor .79 .64 .22 1 + backcolor .37 .1 .1 1 + visible 1 + action + { + play "sound/misc/nomenu.wav" ; + hide missionpics + close ingame_siegeobjectives ; + } + mouseEnter + { + setitemcolor 1 1 1 1 + } + mouseExit + { + setitemcolor .79 .64 .22 1 + } + } + + } +} \ No newline at end of file diff --git a/base/ui/jamp/ingame_voicechat.menu b/base/ui/jamp/ingame_voicechat.menu new file mode 100644 index 0000000..9fd238b --- /dev/null +++ b/base/ui/jamp/ingame_voicechat.menu @@ -0,0 +1,1178 @@ +//---------------------------------------------------------------------------------------------- +// VOICECHAT BOX +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingame_voicechat" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 10 100 180 260 + focusColor 1 1 1 1 + style 1 + border 1 + onESC + { + hide att_grp + hide def_grp + hide req_grp + hide rep_grp + hide spot_grp + hide tac_grp + hide main + close all + } + onClose + { + hide att_grp + hide def_grp + hide req_grp + hide rep_grp + hide spot_grp + hide tac_grp + hide main + } + + onOpen + { + disable main 0 + disable att_grp 0 + disable def_grp 0 + disable req_grp 0 + disable rep_grp 0 + disable spot_grp 0 + disable tac_grp 0 + setFocus attack + show main + } + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 180 260 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name title_small_background + rect 0 5 180 26 + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + visible 1 + decoration + } + + itemDef + { + name title_big_background + rect 0 5 180 48 + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + visible 0 + decoration + } + + itemDef + { + name title + rect 0 5 180 26 + style WINDOW_STYLE_EMPTY + type 4 + text @MENUS_VC_TITLE + textalign ITEM_ALIGN_CENTER + textalignx 90 + textaligny 2 + font 3 + textscale .9 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//----------------------------------- +// +// MAIN VOICE GROUPS +// +//----------------------------------- + itemDef + { + name attack + group main + rect 10 45 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_ATT + textalign ITEM_ALIGN_LEFT + ownerdraw UI_CHAT_MAIN + textalignx 4 + textaligny -2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + hide title_small_background + show title_big_background + hide def_grp + hide req_grp + hide rep_grp + hide spot_grp + hide tac_grp + hide main + show att_grp + setFocus att_01 + } + + } + + + itemDef + { + name defend + group main + rect 10 75 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_DEF + textalign ITEM_ALIGN_LEFT + ownerdraw UI_CHAT_MAIN + textalignx 4 + textaligny -2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + hide title_small_background + show title_big_background + disable main 1 + hide att_grp + hide req_grp + hide rep_grp + hide spot_grp + hide tac_grp + hide main + show def_grp + setFocus def_01 + } + } + + itemDef + { + name request + group main + rect 10 105 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REQ + textalign ITEM_ALIGN_LEFT + ownerdraw UI_CHAT_MAIN + textalignx 4 + textaligny -2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + hide title_small_background + show title_big_background + disable main 1 + hide att_grp + hide def_grp + hide req_grp + hide spot_grp + hide tac_grp + hide main + show req_grp + setFocus req_01 + } + } + + itemDef + { + name reply + group main + rect 10 135 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REPLY + textalign ITEM_ALIGN_LEFT + ownerdraw UI_CHAT_MAIN + textalignx 4 + textaligny -2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + hide title_small_background + show title_big_background + disable main 1 + hide att_grp + hide def_grp + hide req_grp + hide spot_grp + hide tac_grp + hide main + show rep_grp + setFocus rep_01 + } + } + + itemDef + { + name spot + group main + rect 10 165 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_SPOT + textalign ITEM_ALIGN_LEFT + ownerdraw UI_CHAT_MAIN + textalignx 4 + textaligny -2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + hide title_small_background + show title_big_background + disable main 1 + hide att_grp + hide def_grp + hide req_grp + hide rep_grp + hide tac_grp + hide main + show spot_grp + setFocus spot_01 + } + } + + itemDef + { + name tactics + group main + rect 10 195 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_TACTIC + textalign ITEM_ALIGN_LEFT + ownerdraw UI_CHAT_MAIN + textalignx 4 + textaligny -2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + hide title_small_background + show title_big_background + disable main 1 + hide att_grp + hide def_grp + hide req_grp + hide rep_grp + hide spot_grp + hide main + show tac_grp + setFocus tac_01 + } + } + + itemDef + { + name cancel + group none + rect 10 235 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_CANCEL + textalign ITEM_ALIGN_CENTER + textalignx 80 + textaligny -2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + hide att_grp + hide def_grp + hide req_grp + hide rep_grp + hide spot_grp + hide tac_grp + show main + close all + } + } + +//----------------------------------- +// +// ATTACK COMMANDS +// +//----------------------------------- + itemDef + { + name title + group att_grp + rect 0 25 180 26 + style WINDOW_STYLE_EMPTY + type 4 + text @MENUS_ATTACK_TITLE + textalign ITEM_ALIGN_CENTER + textalignx 90 + textaligny 2 + font 3 + textscale .9 + forecolor .549 .854 1 1 + visible 0 + decoration + } + + itemDef + { + name att_01 + group att_grp + rect 10 45 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_ATT_PRIMARY + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_ATTACK + action + { + disable att_grp 1 + exec "voice_cmd att_primary" + hide att_grp + show main + close all + } + } + + itemDef + { + name att_02 + group att_grp + rect 10 75 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_ATT_SECONDARY + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_ATTACK + action + { + disable att_grp 1 + exec "voice_cmd att_second" + hide att_grp + show main + close all + } + } + + itemDef + { + name att_03 + group att_grp + rect 10 105 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_ATT_POSITION + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_ATTACK + action + { + disable att_grp 1 + exec "voice_cmd att_attack" + hide att_grp + show main + close all + } + } + +//----------------------------------- +// +// DEFEND COMMANDS +// +//----------------------------------- + itemDef + { + name title + group def_grp + rect 0 25 180 26 + style WINDOW_STYLE_EMPTY + type 4 + text @MENUS_DEFEND_TITLE + textalign ITEM_ALIGN_CENTER + textalignx 90 + textaligny 2 + font 3 + textscale .9 + forecolor .549 .854 1 1 + visible 0 + decoration + } + + itemDef + { + name def_01 + group def_grp + rect 10 45 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_DEF_PRIMARY + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_DEFEND + action + { + disable def_grp 1 + exec "voice_cmd def_primary" + hide def_grp + show main + close all + } + } + + itemDef + { + name def_02 + group def_grp + rect 10 75 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_DEF_SECONDARY + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_DEFEND + action + { + disable def_grp 1 + exec "voice_cmd def_second" + hide def_grp + show main + close all + } + } + + itemDef + { + name def_03 + group def_grp + rect 10 105 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_DEF_POSITION + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_DEFEND + action + { + disable def_grp 1 + exec "voice_cmd def_position" + hide def_grp + show main + close all + } + } + + itemDef + { + name def_04 + group def_grp + rect 10 135 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_DEF_GUNS + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_DEFEND + action + { + disable def_grp 1 + exec "voice_cmd def_guns" + hide def_grp + show main + close all + } + } + +//----------------------------------- +// +// REQUEST COMMANDS +// +//----------------------------------- + itemDef + { + name title + group req_grp + rect 0 25 180 26 + style WINDOW_STYLE_EMPTY + type 4 + text @MENUS_REQUEST_TITLE + textalign ITEM_ALIGN_CENTER + textalignx 90 + textaligny 2 + font 3 + textscale .9 + forecolor .549 .854 1 1 + visible 0 + decoration + } + + itemDef + { + name req_01 + group req_grp + rect 10 45 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REQ_ASSIST + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REQUEST + action + { + disable req_grp 1 + exec "voice_cmd req_assist" + hide req_grp + show main + close all + } + } + + itemDef + { + name req_02 + group req_grp + rect 10 75 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REQ_MEDIC + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REQUEST + action + { + disable req_grp 1 + exec "voice_cmd req_medic" + hide req_grp + show main + close all + } + } + + itemDef + { + name req_03 + group req_grp + rect 10 105 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REQ_SUPPLY + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REQUEST + action + { + disable req_grp 1 + exec "voice_cmd req_sup" + hide req_grp + show main + close all + } + } + + itemDef + { + name req_04 + group req_grp + rect 10 135 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REQ_TECH + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REQUEST + action + { + disable req_grp 1 + exec "voice_cmd req_tech" + hide req_grp + show main + close all + } + } + + itemDef + { + name req_05 + group req_grp + rect 10 165 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REQ_HVY + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REQUEST + action + { + disable req_grp 1 + exec "voice_cmd req_hvy" + hide req_grp + show main + close all + } + } + + itemDef + { + name req_06 + group req_grp + rect 10 195 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REQ_DEMO + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REQUEST + action + { + disable req_grp 1 + exec "voice_cmd req_demo" + hide req_grp + show main + close all + } + } + +//----------------------------------- +// +// REPLY COMMANDS +// +//----------------------------------- + itemDef + { + name title + group rep_grp + rect 0 25 180 26 + style WINDOW_STYLE_EMPTY + type 4 + text @MENUS_REPLY_TITLE + textalign ITEM_ALIGN_CENTER + textalignx 90 + textaligny 2 + font 3 + textscale .9 + forecolor .549 .854 1 1 + visible 0 + decoration + } + + itemDef + { + name rep_01 + group rep_grp + rect 10 45 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REPLY_YES + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REPLY + action + { + disable rep_grp 1 + exec "voice_cmd reply_yes" + hide rep_grp + show main + close all + } + } + + itemDef + { + name rep_02 + group rep_grp + rect 10 75 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REPLY_NO + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REPLY + action + { + disable rep_grp 1 + exec "voice_cmd reply_no" + hide rep_grp + show main + close all + } + } + + itemDef + { + name rep_03 + group rep_grp + rect 10 105 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REPLY_COMING + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REPLY + action + { + disable rep_grp 1 + exec "voice_cmd reply_coming" + hide rep_grp + show main + close all + } + } + + itemDef + { + name rep_04 + group rep_grp + rect 10 135 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REPLY_GO + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REPLY + action + { + disable rep_grp 1 + exec "voice_cmd reply_go" + hide rep_grp + show main + close all + } + } + + itemDef + { + name rep_05 + group rep_grp + rect 10 165 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_REPLY_STAY + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_REPLY + action + { + disable rep_grp 1 + exec "voice_cmd reply_stay" + hide rep_grp + show main + close all + } + } + +//----------------------------------- +// +// SPOT COMMANDS +// +//----------------------------------- + itemDef + { + name title + group spot_grp + rect 0 25 180 26 + style WINDOW_STYLE_EMPTY + type 4 + text @MENUS_SPOTTED_TITLE + textalign ITEM_ALIGN_CENTER + textalignx 90 + textaligny 2 + font 3 + textscale .9 + forecolor .549 .854 1 1 + visible 0 + decoration + } + + itemDef + { + name spot_01 + group spot_grp + rect 10 45 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_SPOT_DEF + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_SPOT + action + { + disable spot_grp 1 + exec "voice_cmd spot_defenses" + hide spot_grp + show main + close all + } + } + + itemDef + { + name spot_02 + group spot_grp + rect 10 75 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_SPOT_TROOP + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_SPOT + action + { + disable spot_grp 1 + exec "voice_cmd spot_troops" + hide spot_grp + show main + close all + } + } + + itemDef + { + name spot_03 + group spot_grp + rect 10 105 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_SPOT_SNIPER + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_SPOT + action + { + disable spot_grp 1 + exec "voice_cmd spot_sniper" + hide spot_grp + show main + close all + } + } + + itemDef + { + name spot_04 + group spot_grp + rect 10 135 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_SPOT_EMPLACED + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_SPOT + action + { + disable spot_grp 1 + exec "voice_cmd spot_emplaced" + hide spot_grp + show main + close all + } + } + +//----------------------------------- +// +// TACTICS COMMANDS +// +//----------------------------------- + itemDef + { + name title + group tac_grp + rect 0 25 180 26 + style WINDOW_STYLE_EMPTY + type 4 + text @MENUS_TACTICS_TITLE + textalign ITEM_ALIGN_CENTER + textalignx 90 + textaligny 2 + font 3 + textscale .9 + forecolor .549 .854 1 1 + visible 0 + decoration + } + + itemDef + { + name tac_01 + group tac_grp + rect 10 45 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_TAC_COVER + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_TACTICAL + action + { + disable tac_grp 1 + exec "voice_cmd tac_cover" + hide tac_grp + show main + close all + } + } + + itemDef + { + name tac_02 + group tac_grp + rect 10 75 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_TAC_HOLD + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_TACTICAL + action + { + disable tac_grp 1 + exec "voice_cmd tac_hold" + hide tac_grp + show main + close all + } + } + + itemDef + { + name tac_03 + group tac_grp + rect 10 105 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_TAC_FOLLOW + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_TACTICAL + action + { + disable tac_grp 1 + exec "voice_cmd tac_follow" + hide tac_grp + show main + close all + } + } + + itemDef + { + name tac_04 + group tac_grp + rect 10 135 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_TAC_FALLBACK + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_TACTICAL + action + { + disable tac_grp 1 + exec "voice_cmd tac_fallback" + hide tac_grp + show main + close all + } + } + + itemDef + { + name tac_05 + group tac_grp + rect 10 165 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_TAC_TOGETHER + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_TACTICAL + action + { + disable tac_grp 1 + exec "voice_cmd tac_together" + hide tac_grp + show main + close all + } + } + + itemDef + { + name tac_06 + group tac_grp + rect 10 195 160 20 + type ITEM_TYPE_BUTTON + text @MENUS_VC_TAC_SPLIT + textalign ITEM_ALIGN_LEFT + textalignx 4 + textaligny 12 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + ownerdraw UI_CHAT_TACTICAL + action + { + disable tac_grp 1 + exec "voice_cmd tac_split" + hide tac_grp + show main + close all + } + } + + } +} + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/ingame_vote.menu b/base/ui/jamp/ingame_vote.menu new file mode 100644 index 0000000..83e2efa --- /dev/null +++ b/base/ui/jamp/ingame_vote.menu @@ -0,0 +1,112 @@ +// SERVER INFO MENU +{ + menuDef + { + name "ingame_vote" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 430 40 80 68 + focusColor 1 1 1 1 + style 1 + border 1 + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 80 68 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + itemDef + { + name button1 + group buttons + style WINDOW_STYLE_SHADER + rect 2 4 76 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name yes + text @MENUS_YES + type 1 + style 2 + rect 2 4 76 30 + textalign ITEM_ALIGN_CENTER + textalignx 38 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "vote yes" ; + uiScript closeingame + } + mouseEnter + { + show button1 + } + mouseExit + { + hide button1 + } + } + + itemDef + { + name button2 + group buttons + style WINDOW_STYLE_SHADER + rect 2 34 76 30 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name no + text @MENUS_NO + type 1 + style 2 + rect 2 34 76 30 + textalign ITEM_ALIGN_CENTER + textalignx 38 + textaligny 0 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + exec "vote no" ; + uiScript closeingame + } + mouseEnter + { + show button2 + } + mouseExit + { + hide button2 + } + } + } +} diff --git a/base/ui/jamp/joinserver.menu b/base/ui/jamp/joinserver.menu new file mode 100644 index 0000000..d77b69d --- /dev/null +++ b/base/ui/jamp/joinserver.menu @@ -0,0 +1,1224 @@ +//---------------------------------------------------------------------------------------------- +// Join a Server +// Allows user to view a list of available servers and choose which one to join. +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "joinserver" + visible 0 + fullscreen 1 + rect 0 0 640 480 + outOfBoundsClick + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript UpdateFilter + } + + onEsc + { + play "sound/interface/esc.wav" ; + uiScript closeJoin + open multiplayermenu + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Setup background box (upper right) + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 240 24 384 60 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + // Secondard menus background box (lower) + itemDef + { + name secondary_background + group none + style WINDOW_STYLE_FILLED + rect 5 398 630 22 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Join Server title +//---------------------------------------------------------------------------------------------- + itemDef + { + name join_title + group none + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_JOIN_A_GAME_IN_PROGRESS + rect 50 4 540 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 270 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name button_glow2 + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_blendbox_extended" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Top buttons +//---------------------------------------------------------------------------------------------- + // GET NEW LIST + itemDef + { + name getnewlist_button + group none + text @MENUS_GET_NEW_LIST + descText @MENUS_GET_UPDATED_SERVER_LIST + type ITEM_TYPE_BUTTON + textscale 1 + style WINDOW_STYLE_FILLED + rect 15 26 180 26 + textalign ITEM_ALIGN_LEFT + textalignx 0 // center + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript RefreshServers + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 10 27 220 26 + } + mouseExit + { + hide button_glow2 + } + } + + // REFRESH LIST + itemDef + { + name refreshFilter_button + text @MENUS_REFRESH_LIST + descText @MENUS_REFRESH_SERVER_LIST + textscale 1 + style WINDOW_STYLE_FILLED + type ITEM_TYPE_BUTTON + rect 15 54 180 26 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript RefreshFilter + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 10 55 220 26 + } + mouseExit + { + hide button_glow2 + } + } + +//---------------------------------------------------------------------------------------------- +// Selectors (top right box - left column) +//---------------------------------------------------------------------------------------------- + // Source selector + itemDef + { + name netsource + style 0 + //text @MENUS_SOURCE + descText @MENUS_CHOOSE_SOURCE_OF_SERVERS + ownerdraw UI_NETSOURCE + rect 250 26 180 18 + font 4 + textscale 1 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + textstyle 3 + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 242 24 200 20 + } + mouseExit + { + hide button_glow2 + } + } + + // Filter selector + itemDef + { + name netfilter + style 0 + //text @MENUS_GAME + descText @MENUS_SET_FILTER_FOR_SPECIFIC + ownerdraw UI_NETFILTER + rect 250 44 180 18 + font 4 + textscale 1 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + textstyle 3 + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 243 42 200 20 + } + mouseExit + { + hide button_glow2 + } + } + + // Game type selector + itemDef + { + name gametypefilter + style 0 + text @MENUS_GAME_TYPE + descText @MENUS_FILTER_FOR_SPECIFIC_GAME + ownerdraw UI_JOINGAMETYPE + rect 250 62 180 18 + font 4 + textscale 1 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + textstyle 3 + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 242 60 200 20 + } + mouseExit + { + hide button_glow2 + } + } + +//---------------------------------------------------------------------------------------------- +// Selectors (top right box - rightt column) +//---------------------------------------------------------------------------------------------- + // view empty selector + itemDef + { + name viewEmpty + type ITEM_TYPE_YESNO + text @MENUS_VIEW_EMPTY + descText @MENUS_INCLUDE_EMPTY_SERVERS + cvar "ui_browserShowEmpty" + font 4 + textscale 1 + rect 430 26 180 18 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript RefreshFilter + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 422 24 200 20 + } + mouseExit + { + hide button_glow2 + } + } + + // View Full selector + itemDef + { + name viewFull + type 11 + text @MENUS_VIEW_FULL + descText @MENUS_INCLUDE_FULL_SERVERS + cvar "ui_browserShowFull" + font 4 + textscale 1 + rect 430 44 180 18 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript RefreshFilter + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 422 42 200 20 + } + mouseExit + { + hide button_glow2 + } + } + + // Data Rate selector + itemDef + { + name datarate + type ITEM_TYPE_MULTI + text @MENUS_DATA_RATE + descText @MENUS_DATA_RATE_DESC + cvar "rate" + cvarFloatList + { + "@MENUS_56K" 4000 + "@MENUS_ISDN" 5000 + "@MENUS_LAN/CABLE" 25000 + } + textscale 1 + rect 430 62 180 18 + font 4 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript update ui_setRate + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 422 60 200 20 + } + mouseExit + { + hide button_glow2 + } + } + +//---------------------------------------------------------------------------------------------- +// COLUMN HEADINGS/SORT TABS +//---------------------------------------------------------------------------------------------- + itemDef + { + name server + group grpTabs + text @MENUS_SERVER_NAME + descText @MENUS_SORT_BY_SERVER_NAME + type ITEM_TYPE_BUTTON + textscale 1 + style WINDOW_STYLE_EMPTY + rect 10 88 265 26 + textalign ITEM_ALIGN_LEFT + textalignx 4 // center + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript ServerSort 0 + setitemcolor grpTabs backcolor 0.6 0.6 0.6 1 + setitemcolor server backcolor 1 1 1 1 + setitemcolor grpTabs forecolor .79 .64 .22 1 + setitemcolor server forecolor 1 1 1 1 + setitemcolor grpColumn backcolor 0 0 0 0 + setitemcolor serverColumn backcolor 0.1 0.1 0.5 0.5 + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 7 90 265 24 + setitemcolor serverColumn bordercolor .79 .64 .22 1 + } + + mouseExit + { + hide button_glow2 + setitemcolor serverColumn bordercolor 0.2 0.2 0.5 0.5 + } + } + + itemDef + { + name map + group grpTabs + type ITEM_TYPE_BUTTON + text @MENUS_MAP_NAME_1 + descText @MENUS_SORT_BY_MAP_NAME + textscale 1 + style WINDOW_STYLE_EMPTY + rect 275 88 125 26 + textalign ITEM_ALIGN_LEFT + textalignx 4 // center + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript ServerSort 1 + setitemcolor grpTabs backcolor 0.6 0.6 0.6 1 + setitemcolor map backcolor 1 1 1 1 + setitemcolor grpTabs forecolor .79 .64 .22 1 + setitemcolor map forecolor 1 1 1 1 + setitemcolor grpColumn backcolor 0 0 0 0 + setitemcolor mapColumn backcolor 0.1 0.1 0.5 0.5 + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 272 90 134 24 + setitemcolor mapColumn bordercolor .79 .64 .22 1 + } + mouseExit + { + setitemcolor mapColumn bordercolor 0.2 0.2 0.5 0.5 + } + } + + itemDef + { + name Players + group grpTabs + text @MENUS_PLYRS + descText @MENUS_SORT_BY_NUMBER_OF_PLAYERS + type ITEM_TYPE_BUTTON + textscale 1 + style WINDOW_STYLE_EMPTY + background "gfx/menus/menu_blendbox3" // Frame around button + rect 400 88 60 26 + textalign ITEM_ALIGN_LEFT + textalignx 4 // center + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript ServerSort 2 + setitemcolor grpTabs backcolor 0.6 0.6 0.6 1 + setitemcolor Players backcolor 1 1 1 1 + setitemcolor grpTabs forecolor .79 .64 .22 1 + setitemcolor Players forecolor 1 1 1 1 + setitemcolor grpColumn backcolor 0 0 0 0 + setitemcolor playerColumn backcolor 0.1 0.1 0.5 0.5 + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 397 90 68 24 + setitemcolor playerColumn bordercolor .79 .64 .22 1 + } + mouseExit + { + setitemcolor playerColumn bordercolor 0.2 0.2 0.5 0.5 + } + } + + itemDef + { + name Type + group grpTabs + text @MENUS_SORT_TYPE + descText @MENUS_SORT_BY_GAME_TYPE + textscale 1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + background "gfx/menus/menu_blendbox3" // Frame around button + rect 460 88 60 26 + textalign ITEM_ALIGN_LEFT + textalignx 4 // center + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript ServerSort 3 + setitemcolor grpTabs backcolor 0.6 0.6 0.6 1 + setitemcolor Type backcolor 1 1 1 1 + setitemcolor grpTabs forecolor .79 .64 .22 1 + setitemcolor Type forecolor 1 1 1 1 + setitemcolor grpColumn backcolor 0 0 0 0 + setitemcolor typeColumn backcolor 0.1 0.1 0.5 0.5 + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 457 90 110 24 + setitemcolor typeColumn bordercolor .79 .64 .22 1 + } + mouseExit + { + hide button_glow2 + setitemcolor typeColumn bordercolor 0.2 0.2 0.5 0.5 + } + } + + + itemDef + { + name Ping + group grpTabs + text @MENUS_PING + descText @MENUS_SORT_BY_PING_TIME + type ITEM_TYPE_BUTTON + textscale 1 + style WINDOW_STYLE_EMPTY + background "gfx/menus/menu_blendbox3" // Frame around button + rect 560 88 52 26 + textalign ITEM_ALIGN_LEFT + textalignx 4 // center + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript ServerSort 4 + setitemcolor grpTabs backcolor 0.6 0.6 0.6 1 + setitemcolor Ping backcolor 1 1 1 1 + setitemcolor grpTabs forecolor .79 .64 .22 1 + setitemcolor Ping forecolor 1 1 1 1 + setitemcolor grpColumn backcolor 0 0 0 0 ; + setitemcolor pingColumn backcolor 0.1 0.1 0.5 0.5 + } + mouseEnter + { + show button_glow2 + setitemrect button_glow2 557 90 60 24 + setitemcolor pingColumn bordercolor .79 .64 .22 1 + } + mouseExit + { + hide button_glow2 + setitemcolor pingColumn bordercolor 0.2 0.2 0.5 0.5 + } + } + +//---------------------------------------------------------------------------------------------- +// HORIZONTAL SEPARATORS +//---------------------------------------------------------------------------------------------- + itemDef + { + name horizontalseparators + rect 10 116 604 26 + style WINDOW_STYLE_FILLED + border 0 + backcolor 0.1 0.1 0.3 0.5 + visible 1 + decoration + } + + itemDef + { + name horizontalseparators + rect 10 142 604 26 + style WINDOW_STYLE_FILLED + border 0 + backcolor 0 0 0.2 0.5 + visible 1 + decoration + } + + itemDef + { + name horizontalseparators + rect 10 168 604 26 + style WINDOW_STYLE_FILLED + border 0 + backcolor 0.1 0.1 0.3 0.5 + visible 1 + decoration + } + + itemDef + { + name horizontalseparators + rect 10 194 604 26 + style WINDOW_STYLE_FILLED + border 0 + backcolor 0 0 0.2 0.5 + visible 1 + decoration + } + + itemDef + { + name horizontalseparators + rect 10 220 604 26 + style WINDOW_STYLE_FILLED + border 0 + backcolor 0.1 0.1 0.3 0.5 + visible 1 + decoration + } + + itemDef + { + name horizontalseparators + rect 10 246 604 26 + style WINDOW_STYLE_FILLED + border 0 + backcolor 0 0 0.2 0.5 + visible 1 + decoration + } + + itemDef + { + name horizontalseparators + rect 10 272 604 26 + style WINDOW_STYLE_FILLED + border 0 + backcolor 0.1 0.1 0.3 0.5 + visible 1 + decoration + } + + itemDef + { + name horizontalseparators + rect 10 298 604 26 + style WINDOW_STYLE_FILLED + border 0 + backcolor 0 0 0.2 0.5 + visible 1 + decoration + } + + itemDef + { + name horizontalseparators + rect 10 324 604 26 + style WINDOW_STYLE_FILLED + border 0 + backcolor 0.1 0.1 0.3 0.5 + visible 1 + decoration + } + + itemDef + { + name horizontalseparators + rect 10 350 604 26 + style WINDOW_STYLE_FILLED + border 0 + backcolor 0 0 0.2 0.5 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// COLUMN LINES +//---------------------------------------------------------------------------------------------- + itemDef + { + name serverColumn + group grpColumn + rect 10 112 265 264 + style WINDOW_STYLE_FILLED + border 1 + backcolor 0 0 0 0 + bordersize 1 + bordercolor 0.2 0.2 0.5 0.5 + visible 1 + decoration + } + + itemDef + { + name mapColumn + group grpColumn + rect 275 112 125 264 + style WINDOW_STYLE_FILLED + border 1 + backcolor 0 0 0 0 + bordersize 1 + bordercolor 0.2 0.2 0.5 0.5 + visible 1 + decoration + } + + itemDef + { + name playerColumn + group grpColumn + rect 400 112 60 264 + style WINDOW_STYLE_FILLED + border 1 + backcolor 0 0 0 0 + bordersize 1 + bordercolor 0.2 0.2 0.5 0.5 + visible 1 + decoration + } + + itemDef + { + name typeColumn + group grpColumn + rect 460 112 100 264 + style WINDOW_STYLE_FILLED + border 1 + backcolor 0 0 0 0 + bordersize 1 + bordercolor 0.2 0.2 0.5 0.5 + visible 1 + decoration + } + + itemDef + { + name pingColumn + group grpColumn + rect 560 112 52 264 + style WINDOW_STYLE_FILLED + border 1 + backcolor 0 0 0 0 + bordersize 1 + bordercolor 0.2 0.2 0.5 0.5 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// SERVER LIST WINDOW +//---------------------------------------------------------------------------------------------- + itemDef + { + name serverlist + rect 10 112 620 264 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 26 + font 4 + textscale 1 + textaligny 6 + elementtype LISTBOX_TEXT + feeder FEEDER_SERVERS + border 1 + bordercolor 0.2 0.2 0.5 0.5 //Color of border + forecolor 1 1 1 1 //Color of text + backcolor 0.25 0.25 0.8 .25 //Background color of listbox + outlinecolor 1 1 1 .25 //Highlight when item is selected. + visible 1 + columns 5 2 40 258 270 40 104 388 5 64 450 20 100 560 20 47 + mouseenter + { + setitemcolor serverlist bordercolor .79 .64 .22 1 + } + mouseexit + { + setitemcolor serverlist bordercolor 0.2 0.2 0.5 0.5 + } + doubleClick + { + + uiScript checkpassword + // uiScript JoinServer + // close joinserver + //open forcealloc + } + } + + // DATE AND TIME + itemDef + { + name refreshdate + ownerdraw UI_SERVERREFRESHDATE + font 4 + textscale 1 + rect 10 376 285 20 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor .79 .64 .22 .7 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// ADDITIONAL SCREENS BUTTONS +//---------------------------------------------------------------------------------------------- + + // PASSWORD + itemDef + { + name passwordText + text @MENUS_PASSWORD + descText @MENUS_INPUT_PASSWORD + type ITEM_TYPE_BUTTON + font 4 + textscale 1 + style WINDOW_STYLE_FILLED + rect 10 402 120 20 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open password_popmenu + } + mouseenter + { + show button_glow + setitemrect button_glow 7 400 150 20 + } + mouseexit + { + hide button_glow + } + } + + // NEW FAVORITE + itemDef + { + name createFavoriteText + text @MENUS_NEW_FAVORITE + descText @MENUS_ENTER_IP_ADDRESS_OF_FAVORITE + type ITEM_TYPE_BUTTON + font 4 + textscale 1 + style WINDOW_STYLE_FILLED + rect 135 402 120 20 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open createfavorite_popmenu + } + mouseenter + { + show button_glow + setitemrect button_glow 132 400 150 20 + } + mouseexit + { + hide button_glow + } + } + + // ADD/DELETE FAVORITE + itemDef + { + name addFavorite + text @MENUS_ADD_FAVORITE + descText @MENUS_ADD_SELECTED_SERVER_TO + type ITEM_TYPE_BUTTON + font 4 + textscale 1 + style WINDOW_STYLE_FILLED + ownerdrawFlag UI_SHOW_NOTFAVORITESERVERS + rect 260 402 120 20 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript addFavorite + } + mouseenter + { + show button_glow + setitemrect button_glow 257 400 150 20 + } + mouseexit + { + hide button_glow + } + } + + itemDef + { + name delfavorite + text @MENUS_DEL_FAVORITE + descText @MENUS_DELETE_SELECTED_SERVER + type ITEM_TYPE_BUTTON + font 4 + textscale 1 + style WINDOW_STYLE_FILLED + ownerdrawFlag UI_SHOW_FAVORITESERVERS + rect 260 402 120 20 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript DeleteFavorite + uiScript UpdateFilter + } + mouseenter + { + show button_glow + setitemrect button_glow 257 400 150 20 + } + mouseexit + { + hide button_glow + } + } + + // SERVER INFO + itemDef + { + name serverinfoText + text @MENUS_GET_SERVER_INFO + descText @MENUS_DISPLAY_SERVER_INFORMATION + type ITEM_TYPE_BUTTON + font 4 + textscale 1 + style WINDOW_STYLE_FILLED + rect 385 402 120 20 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open serverinfo_popmenu + } + mouseenter + { + show button_glow + setitemrect button_glow 382 400 150 20 + } + mouseexit + { + hide button_glow + } + } + + // FIND PLAYER + itemDef + { + name findplayerText + text @MENUS_FIND_PLAYER + descText @MENUS_SEARCH_CURRENT_SERVER + type ITEM_TYPE_BUTTON + font 4 + textscale 1 + style WINDOW_STYLE_FILLED + rect 510 402 120 20 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open findplayer_popmenu + hide findPlayerButton + } + mouseenter + { + show button_glow + setitemrect button_glow 507 400 150 20 + } + mouseexit + { + hide button_glow + } + } + +//---------------------------------------------------------------------------------------------- +// VERY BOTTOM ROW BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK BUTTON + itemDef + { + name back + text @MENUS_BACK + descText @MENUS_BACKUP_ONE_MENU + type ITEM_TYPE_BUTTON + font 3 + textscale 1.1 + textstyle 0 + style WINDOW_STYLE_FILLED + rect 59 444 130 24 + textalign 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + visible 1 + forecolor 1 .682 0 1 + action + { + play "sound/interface/esc.wav" ; + close joinserver ; + open multiplayermenu + } + mouseEnter + { + show button_glow + setitemrect button_glow 30 441 190 30 + } + mouseExit + { + hide button_glow + } + } + + // EXIT button + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 235 441 190 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + // JOIN BUTTON + itemDef + { + name join_button + text @MENUS_JOIN + descText @MENUS_JOIN_CHOSEN_SERVER + type ITEM_TYPE_BUTTON + font 3 + textscale 1 + textstyle 0 + style WINDOW_STYLE_FILLED + rect 455 444 200 32 + textalign 1 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + action + { + play "sound/interface/button1.wav" + uiScript checkpassword + // uiScript JoinServer + // close joinserver + } + + mouseEnter + { + show button_glow + setitemrect button_glow 425 441 190 30 + } + mouseExit + { + hide button_glow + } + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/main.menu b/base/ui/jamp/main.menu new file mode 100644 index 0000000..91989c2 --- /dev/null +++ b/base/ui/jamp/main.menu @@ -0,0 +1,523 @@ +//---------------------------------------------------------------------------------------------- +// +// MULTIPLAYER - MAIN MENU +// +// Opening menu. Called when game is first started. +// +//---------------------------------------------------------------------------------------------- +{ + assetGlobalDef + { + font "ergoec" 18 // font + smallFont "aurabesh" 18 // font + bigFont "anewhope" 20 // font + small2Font "arialnb" 14 + cursor "cursor" // cursor + gradientBar "ui/assets/gradientbar2.tga" // gradient bar + itemFocusSound "sound/interface/menuroam.wav" // sound for item getting focus (via keyboard or mouse ) + + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 1 // how often fade happens in milliseconds + fadeAmount 0.1 // amount to adjust alpha per cycle + + moveRollSound "sound/player/roll1" + moveJumpSound "sound/weapons/force/jump" + + shadowColor 0.1 0.1 0.1 0.25 // shadow color + focuscolor 0 0 1 1 + precacheSound + { + "sound/interface/choose_color.wav" ; + "sound/interface/choose_head.wav" ; + "sound/interface/choose_torso.wav" ; + "sound/interface/choose_saber.wav" ; + "sound/interface/choose_hilt.wav" ; + "sound/interface/choose_blade.wav" ; + "sound/interface/transition.wav" ; + "sound/interface/esc.wav" ; + "sound/interface/sub_select.wav" ; + } + } + + menuDef + { + name "main" + fullScreen 1 + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + exec "music music/t2_dpred/ImpBaseB_Action" ; + } + + onESC + { + play "sound/interface/esc.wav" + close all + open quitMenu + } + onClose + { + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background_video + group none + style WINDOW_STYLE_SHADER + rect 200 144 256 256 + background "gfx/menus/videologo" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name ring + group none + style WINDOW_STYLE_SHADER + rect 193 145 256 256 + background "gfx/menus/main_ring" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name centerwindow + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerwindow" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name leftwindow + group none + style WINDOW_STYLE_SHADER + rect 0 150 320 240 + background "gfx/menus/main_leftwindow" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name rightwindow + group none + style WINDOW_STYLE_SHADER + rect 320 150 320 240 + background "gfx/menus/main_rightwindow" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name multiplayer_title + style WINDOW_STYLE_EMPTY + rect 200 120 240 24 + text @MENUS_MULTIPLAYER + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 120 + forecolor .695 .760 .861 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MENU BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name playbutton_undertext + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 194 130 24 + text @MENUS_NEW + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor .02 0 .33 1 + visible 0 + decoration + } + + itemDef + { + name playbutton + group main_button + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 212 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 16 210 210 32 + show playbutton_undertext + } + mouseExit + { + hide button_glow + hide playbutton_undertext + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "PLAYER PROFILE" + itemDef + { + name profilebutton_undertext + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 293 130 24 + text @MENUS_LOAD + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor .02 0 .33 1 + visible 0 + decoration + } + + itemDef + { + name profilebutton + group main_button + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 310 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 16 308 210 32 + show profilebutton_undertext + } + mouseExit + { + hide button_glow + hide profilebutton_undertext + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open playerMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_undertext + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 448 194 146 24 + text @MENUS_CONTROLS2 + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor .02 0 .33 1 + visible 0 + decoration + } + + itemDef + { + name controlsbutton + group main_button + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 441 212 160 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 416 210 210 32 + show controlsbutton_undertext + } + mouseExit + { + hide button_glow + hide controlsbutton_undertext + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_undertext + group none + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 456 292 130 24 + text @MENUS_SETUP + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor .02 0 .33 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group main_button + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 456 310 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 416 308 210 32 + show setupbutton_undertext + } + mouseExit + { + hide button_glow + hide setupbutton_undertext + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + +//---------------------------------------------------------------------------------------------- +// OTHER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Credits hidden button + itemDef + { + name creditsbutton + group othermain +// text @CREDITS + descText @MENUS_SHOW_GAME_CREDITS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 200 144 256 256 + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textalignx 46 + backcolor 0 0 0 0 + forecolor 0.65 0.65 1 1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open creditsMenu + } + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 215 440 210 32 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + } +} + + + + + + + + + + diff --git a/base/ui/jamp/menudef.h b/base/ui/jamp/menudef.h new file mode 100644 index 0000000..54c1721 --- /dev/null +++ b/base/ui/jamp/menudef.h @@ -0,0 +1,388 @@ + + +#define CT_LTBLUE1 0.367 0.261 0.722 +#define CT_DKBLUE1 0.199 0.0 0.398 + +#define CT_LTCYAN 0 0.5 0.5 +#define CT_DKCYAN 0 0.25 0.25 + +#define ITEM_TYPE_TEXT 0 // simple text +#define ITEM_TYPE_BUTTON 1 // button, basically text with a border +#define ITEM_TYPE_RADIOBUTTON 2 // toggle button, may be grouped +#define ITEM_TYPE_CHECKBOX 3 // check box +#define ITEM_TYPE_EDITFIELD 4 // editable text, associated with a cvar +#define ITEM_TYPE_COMBO 5 // drop down list +#define ITEM_TYPE_LISTBOX 6 // scrollable list +#define ITEM_TYPE_MODEL 7 // model +#define ITEM_TYPE_OWNERDRAW 8 // owner draw, name specs what it is +#define ITEM_TYPE_NUMERICFIELD 9 // editable text, associated with a cvar +#define ITEM_TYPE_SLIDER 10 // mouse speed, volume, etc. +#define ITEM_TYPE_YESNO 11 // yes no cvar setting +#define ITEM_TYPE_MULTI 12 // multiple list setting, enumerated +#define ITEM_TYPE_BIND 13 // multiple list setting, enumerated +#define ITEM_TYPE_TEXTSCROLL 14 // scrolls text + +#define ITEM_ALIGN_LEFT 0 // left alignment +#define ITEM_ALIGN_CENTER 1 // center alignment +#define ITEM_ALIGN_RIGHT 2 // right alignment + +#define ITEM_TEXTSTYLE_NORMAL 0 // normal text +#define ITEM_TEXTSTYLE_BLINK 1 // fast blinking +#define ITEM_TEXTSTYLE_PULSE 2 // slow pulsing +#define ITEM_TEXTSTYLE_SHADOWED 3 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINED 4 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINESHADOWED 5 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_SHADOWEDMORE 6 // drop shadow ( need a color for this ) + +#define WINDOW_BORDER_NONE 0 // no border +#define WINDOW_BORDER_FULL 1 // full border based on border color ( single pixel ) +#define WINDOW_BORDER_HORZ 2 // horizontal borders only +#define WINDOW_BORDER_VERT 3 // vertical borders only +#define WINDOW_BORDER_KCGRADIENT 4 // horizontal border using the gradient bars + +#define WINDOW_STYLE_EMPTY 0 // no background +#define WINDOW_STYLE_FILLED 1 // filled with background color +#define WINDOW_STYLE_GRADIENT 2 // gradient bar based on background color +#define WINDOW_STYLE_SHADER 3 // gradient bar based on background color +#define WINDOW_STYLE_TEAMCOLOR 4 // team color +#define WINDOW_STYLE_CINEMATIC 5 // cinematic + +#define MENU_TRUE 1 // uh.. true +#define MENU_FALSE 0 // and false + +#define HUD_VERTICAL 0x00 +#define HUD_HORIZONTAL 0x01 + +// list box element types +#define LISTBOX_TEXT 0x00 +#define LISTBOX_IMAGE 0x01 + +// list feeders +//#define FEEDER_HEADS 0x00 // model heads +#define FEEDER_MAPS 0x01 // text maps based on game type +#define FEEDER_SERVERS 0x02 // servers +//#define FEEDER_CLANS 0x03 // clan names +#define FEEDER_ALLMAPS 0x04 // all maps available, in graphic format +#define FEEDER_REDTEAM_LIST 0x05 // red team members +#define FEEDER_BLUETEAM_LIST 0x06 // blue team members +#define FEEDER_PLAYER_LIST 0x07 // players +#define FEEDER_TEAM_LIST 0x08 // team members for team voting +#define FEEDER_MODS 0x09 // team members for team voting +#define FEEDER_DEMOS 0x0a // team members for team voting +#define FEEDER_SCOREBOARD 0x0b // team members for team voting +#define FEEDER_Q3HEADS 0x0c // model heads +#define FEEDER_SERVERSTATUS 0x0d // server status +#define FEEDER_FINDPLAYER 0x0e // find player +#define FEEDER_CINEMATICS 0x0f // cinematics + +#define FEEDER_FORCECFG 0x10 // force config list + +#define FEEDER_SIEGE_TEAM1 0x11 // siege class list for team1 +#define FEEDER_SIEGE_TEAM2 0x12 // siege class list for team2 + +#define FEEDER_PLAYER_SPECIES 0x13 // models/player/* +#define FEEDER_PLAYER_SKIN_HEAD 0x14 // head*.skin files in species folder +#define FEEDER_PLAYER_SKIN_TORSO 0x15 // torso*.skin files in species folder +#define FEEDER_PLAYER_SKIN_LEGS 0x16 // lower*.skin files in species folder +#define FEEDER_COLORCHOICES 0x17 // special hack to feed text/actions from playerchoice.txt in species folder + +#define FEEDER_TEAM1_INFANTRY 0x18 // for siege team choice +#define FEEDER_TEAM1_VANGUARD 0x19 // for siege team choice +#define FEEDER_TEAM1_SUPPORT 0x1a // for siege team choice +#define FEEDER_TEAM1_JEDI 0x1b // for siege team choice +#define FEEDER_TEAM1_DEMO 0x1c // for siege team choice +#define FEEDER_TEAM1_HEAVY 0x1d // for siege team choice + +#define FEEDER_TEAM2_INFANTRY 0x1e // for siege team choice +#define FEEDER_TEAM2_VANGUARD 0x1f // for siege team choice +#define FEEDER_TEAM2_SUPPORT 0x20 // for siege team choice +#define FEEDER_TEAM2_JEDI 0x21 // for siege team choice +#define FEEDER_TEAM2_DEMO 0x22 // for siege team choice +#define FEEDER_TEAM2_HEAVY 0x23 // for siege team choice + +#define FEEDER_SIEGE_BASE_CLASS 0x24 // for siege team choice +#define FEEDER_SIEGE_CLASS_WEAPONS 0x25 // for siege team choice +#define FEEDER_SIEGE_CLASS_INVENTORY 0x26 // for siege team choice +#define FEEDER_SIEGE_CLASS_FORCE 0x27 // for siege team choice +#define FEEDER_LANGUAGES 0x28 // for language choice +#define FEEDER_MOVES 0x29 // moves for the data pad moves screen +#define FEEDER_MOVES_TITLES 0x2a // move titles for the data pad moves screen +#define FEEDER_SABER_SINGLE_INFO 0x2b // saber single +#define FEEDER_SABER_STAFF_INFO 0x2c // saber staff + + +// Xbox specific, hope no one minds +#define FEEDER_XBL_ACCOUNTS 0xA0 // list of available XBL accounts +#define FEEDER_XBL_PLAYERS 0xA1 // players (current and recent) +#define FEEDER_XBL_FRIENDS 0xA2 // friends +#define FEEDER_XBL_SERVERS 0xA3 // results of an optimatch query + +// display flags +#define CG_SHOW_BLUE_TEAM_HAS_REDFLAG 0x00000001 +#define CG_SHOW_RED_TEAM_HAS_BLUEFLAG 0x00000002 +#define CG_SHOW_ANYTEAMGAME 0x00000004 +#define CG_SHOW_HARVESTER 0x00000008 +#define CG_SHOW_ONEFLAG 0x00000010 +#define CG_SHOW_CTF 0x00000020 +#define CG_SHOW_OBELISK 0x00000040 +#define CG_SHOW_HEALTHCRITICAL 0x00000080 +#define CG_SHOW_SINGLEPLAYER 0x00000100 +#define CG_SHOW_TOURNAMENT 0x00000200 +#define CG_SHOW_DURINGINCOMINGVOICE 0x00000400 +#define CG_SHOW_IF_PLAYER_HAS_FLAG 0x00000800 +#define CG_SHOW_LANPLAYONLY 0x00001000 +#define CG_SHOW_MINED 0x00002000 +#define CG_SHOW_HEALTHOK 0x00004000 +#define CG_SHOW_TEAMINFO 0x00008000 +#define CG_SHOW_NOTEAMINFO 0x00010000 +#define CG_SHOW_OTHERTEAMHASFLAG 0x00020000 +#define CG_SHOW_YOURTEAMHASENEMYFLAG 0x00040000 +#define CG_SHOW_ANYNONTEAMGAME 0x00080000 +#define CG_SHOW_2DONLY 0x10000000 + + +#define UI_SHOW_LEADER 0x00000001 +#define UI_SHOW_NOTLEADER 0x00000002 +#define UI_SHOW_FAVORITESERVERS 0x00000004 +#define UI_SHOW_ANYNONTEAMGAME 0x00000008 +#define UI_SHOW_ANYTEAMGAME 0x00000010 +#define UI_SHOW_NEWHIGHSCORE 0x00000020 +#define UI_SHOW_DEMOAVAILABLE 0x00000040 +#define UI_SHOW_NEWBESTTIME 0x00000080 +#define UI_SHOW_FFA 0x00000100 +#define UI_SHOW_NOTFFA 0x00000200 +#define UI_SHOW_NETANYNONTEAMGAME 0x00000400 +#define UI_SHOW_NETANYTEAMGAME 0x00000800 +#define UI_SHOW_NOTFAVORITESERVERS 0x00001000 + + + + +// owner draw types +// ideally these should be done outside of this file but +// this makes it much easier for the macro expansion to +// convert them for the designers ( from the .menu files ) +#define CG_OWNERDRAW_BASE 1 +#define CG_PLAYER_ARMOR_ICON 1 +#define CG_PLAYER_ARMOR_VALUE 2 +#define CG_PLAYER_HEAD 3 +#define CG_PLAYER_HEALTH 4 +#define CG_PLAYER_AMMO_ICON 5 +#define CG_PLAYER_AMMO_VALUE 6 +#define CG_SELECTEDPLAYER_HEAD 7 +#define CG_SELECTEDPLAYER_NAME 8 +#define CG_SELECTEDPLAYER_LOCATION 9 +#define CG_SELECTEDPLAYER_STATUS 10 +#define CG_SELECTEDPLAYER_WEAPON 11 +#define CG_SELECTEDPLAYER_POWERUP 12 + +#define CG_FLAGCARRIER_HEAD 13 +#define CG_FLAGCARRIER_NAME 14 +#define CG_FLAGCARRIER_LOCATION 15 +#define CG_FLAGCARRIER_STATUS 16 +#define CG_FLAGCARRIER_WEAPON 17 +#define CG_FLAGCARRIER_POWERUP 18 + +#define CG_PLAYER_ITEM 19 +#define CG_PLAYER_SCORE 20 + +#define CG_BLUE_FLAGHEAD 21 +#define CG_BLUE_FLAGSTATUS 22 +#define CG_BLUE_FLAGNAME 23 +#define CG_RED_FLAGHEAD 24 +#define CG_RED_FLAGSTATUS 25 +#define CG_RED_FLAGNAME 26 + +#define CG_BLUE_SCORE 27 +#define CG_RED_SCORE 28 +#define CG_RED_NAME 29 +#define CG_BLUE_NAME 30 +#define CG_HARVESTER_SKULLS 31 // only shows in harvester +#define CG_ONEFLAG_STATUS 32 // only shows in one flag +#define CG_PLAYER_LOCATION 33 +#define CG_TEAM_COLOR 34 +#define CG_CTF_POWERUP 35 + +#define CG_AREA_POWERUP 36 +#define CG_AREA_LAGOMETER 37 // painted with old system +#define CG_PLAYER_HASFLAG 38 +#define CG_GAME_TYPE 39 // not done + +#define CG_SELECTEDPLAYER_ARMOR 40 +#define CG_SELECTEDPLAYER_HEALTH 41 +#define CG_PLAYER_STATUS 42 +#define CG_FRAGGED_MSG 43 // painted with old system +#define CG_PROXMINED_MSG 44 // painted with old system +#define CG_AREA_FPSINFO 45 // painted with old system +#define CG_AREA_SYSTEMCHAT 46 // painted with old system +#define CG_AREA_TEAMCHAT 47 // painted with old system +#define CG_AREA_CHAT 48 // painted with old system +#define CG_GAME_STATUS 49 +#define CG_KILLER 50 +#define CG_PLAYER_ARMOR_ICON2D 51 +#define CG_PLAYER_AMMO_ICON2D 52 +#define CG_ACCURACY 53 +#define CG_ASSISTS 54 +#define CG_DEFEND 55 +#define CG_EXCELLENT 56 +#define CG_IMPRESSIVE 57 +#define CG_PERFECT 58 +#define CG_GAUNTLET 59 +#define CG_SPECTATORS 60 +#define CG_TEAMINFO 61 +#define CG_VOICE_HEAD 62 +#define CG_VOICE_NAME 63 +#define CG_PLAYER_HASFLAG2D 64 +#define CG_HARVESTER_SKULLS2D 65 // only shows in harvester +#define CG_CAPFRAGLIMIT 66 +#define CG_1STPLACE 67 +#define CG_2NDPLACE 68 +#define CG_CAPTURES 69 +#define CG_PLAYER_FORCE_VALUE 70 + + + + +#define UI_OWNERDRAW_BASE 200 +#define UI_HANDICAP 200 +#define UI_EFFECTS 201 +#define UI_PLAYERMODEL 202 +#define UI_CLANNAME 203 +#define UI_CLANLOGO 204 +#define UI_GAMETYPE 205 +#define UI_MAPPREVIEW 206 +#define UI_SKILL 207 +#define UI_BLUETEAMNAME 208 +#define UI_REDTEAMNAME 209 +#define UI_BLUETEAM1 210 +#define UI_BLUETEAM2 211 +#define UI_BLUETEAM3 212 +#define UI_BLUETEAM4 213 +#define UI_BLUETEAM5 214 +#define UI_REDTEAM1 215 +#define UI_REDTEAM2 216 +#define UI_REDTEAM3 217 +#define UI_REDTEAM4 218 +#define UI_REDTEAM5 219 +#define UI_NETSOURCE 220 +#define UI_NETMAPPREVIEW 221 +#define UI_NETFILTER 222 +#define UI_TIER 223 +#define UI_OPPONENTMODEL 224 +#define UI_TIERMAP1 225 +#define UI_TIERMAP2 226 +#define UI_TIERMAP3 227 +#define UI_PLAYERLOGO 228 +#define UI_OPPONENTLOGO 229 +#define UI_PLAYERLOGO_METAL 230 +#define UI_OPPONENTLOGO_METAL 231 +#define UI_PLAYERLOGO_NAME 232 +#define UI_OPPONENTLOGO_NAME 233 +#define UI_TIER_MAPNAME 234 +#define UI_TIER_GAMETYPE 235 +#define UI_ALLMAPS_SELECTION 236 +#define UI_OPPONENT_NAME 237 +#define UI_VOTE_KICK 238 +#define UI_BOTNAME 239 +#define UI_BOTSKILL 240 +#define UI_REDBLUE 241 +#define UI_CROSSHAIR 242 +#define UI_SELECTEDPLAYER 243 +#define UI_MAPCINEMATIC 244 +#define UI_NETGAMETYPE 245 +#define UI_NETMAPCINEMATIC 246 +#define UI_SERVERREFRESHDATE 247 +#define UI_SERVERMOTD 248 +#define UI_GLINFO 249 +#define UI_KEYBINDSTATUS 250 +#define UI_CLANCINEMATIC 251 +#define UI_MAP_TIMETOBEAT 252 +#define UI_JOINGAMETYPE 253 +#define UI_PREVIEWCINEMATIC 254 +#define UI_STARTMAPCINEMATIC 255 +#define UI_MAPS_SELECTION 256 +#define UI_FORCE_SIDE 257 +#define UI_FORCE_RANK 258 +#define UI_FORCE_RANK_HEAL 259 +#define UI_FORCE_RANK_LEVITATION 260 +#define UI_FORCE_RANK_SPEED 261 +#define UI_FORCE_RANK_PUSH 262 +#define UI_FORCE_RANK_PULL 263 +#define UI_FORCE_RANK_TELEPATHY 264 +#define UI_FORCE_RANK_GRIP 265 +#define UI_FORCE_RANK_LIGHTNING 266 +#define UI_FORCE_RANK_RAGE 267 +#define UI_FORCE_RANK_PROTECT 268 +#define UI_FORCE_RANK_ABSORB 269 +#define UI_FORCE_RANK_TEAM_HEAL 270 +#define UI_FORCE_RANK_TEAM_FORCE 271 +#define UI_FORCE_RANK_DRAIN 272 +#define UI_FORCE_RANK_SEE 273 +#define UI_FORCE_RANK_SABERATTACK 274 +#define UI_FORCE_RANK_SABERDEFEND 275 +#define UI_FORCE_RANK_SABERTHROW 276 +#define UI_VERSION 277 +#define UI_TOTALFORCESTARS 278 +#define UI_AUTOSWITCHLIST 279 + +//How handy it would be if this were an enum. +#define UI_BLUETEAM6 280 +#define UI_BLUETEAM7 281 +#define UI_BLUETEAM8 282 +#define UI_REDTEAM6 283 +#define UI_REDTEAM7 284 +#define UI_REDTEAM8 285 + +// Yes it would be handy +#define UI_FORCE_MASTERY_SET 286 +#define UI_SKIN_COLOR 287 +#define UI_FORCE_POINTS 288 + +//extra, for patch +#define UI_JEDI_NONJEDI 289 + +// Xbox-only, for complicated passcode entry screen. Sorry. +#define UI_XBOX_PASSCODE 290 + +#define UI_CHAT_MAIN 291 +#define UI_CHAT_ATTACK 292 +#define UI_CHAT_DEFEND 293 +#define UI_CHAT_REQUEST 294 +#define UI_CHAT_REPLY 295 +#define UI_CHAT_SPOT 296 +#define UI_CHAT_TACTICAL 297 + +#define VOICECHAT_GETFLAG "getflag" // command someone to get the flag +#define VOICECHAT_OFFENSE "offense" // command someone to go on offense +#define VOICECHAT_DEFEND "defend" // command someone to go on defense +#define VOICECHAT_DEFENDFLAG "defendflag" // command someone to defend the flag +#define VOICECHAT_PATROL "patrol" // command someone to go on patrol (roam) +#define VOICECHAT_CAMP "camp" // command someone to camp (we don't have sounds for this one) +#define VOICECHAT_FOLLOWME "followme" // command someone to follow you +#define VOICECHAT_RETURNFLAG "returnflag" // command someone to return our flag +#define VOICECHAT_FOLLOWFLAGCARRIER "followflagcarrier" // command someone to follow the flag carrier +#define VOICECHAT_YES "yes" // yes, affirmative, etc. +#define VOICECHAT_NO "no" // no, negative, etc. +#define VOICECHAT_ONGETFLAG "ongetflag" // I'm getting the flag +#define VOICECHAT_ONOFFENSE "onoffense" // I'm on offense +#define VOICECHAT_ONDEFENSE "ondefense" // I'm on defense +#define VOICECHAT_ONPATROL "onpatrol" // I'm on patrol (roaming) +#define VOICECHAT_ONCAMPING "oncamp" // I'm camping somewhere +#define VOICECHAT_ONFOLLOW "onfollow" // I'm following +#define VOICECHAT_ONFOLLOWCARRIER "onfollowcarrier" // I'm following the flag carrier +#define VOICECHAT_ONRETURNFLAG "onreturnflag" // I'm returning our flag +#define VOICECHAT_INPOSITION "inposition" // I'm in position +#define VOICECHAT_IHAVEFLAG "ihaveflag" // I have the flag +#define VOICECHAT_BASEATTACK "baseattack" // the base is under attack +#define VOICECHAT_ENEMYHASFLAG "enemyhasflag" // the enemy has our flag (CTF) +#define VOICECHAT_STARTLEADER "startleader" // I'm the leader +#define VOICECHAT_STOPLEADER "stopleader" // I resign leadership +#define VOICECHAT_TRASH "trash" // lots of trash talk +#define VOICECHAT_WHOISLEADER "whoisleader" // who is the team leader +#define VOICECHAT_WANTONDEFENSE "wantondefense" // I want to be on defense +#define VOICECHAT_WANTONOFFENSE "wantonoffense" // I want to be on offense +#define VOICECHAT_KILLINSULT "kill_insult" // I just killed you +#define VOICECHAT_TAUNT "taunt" // I want to taunt you +#define VOICECHAT_DEATHINSULT "death_insult" // you just killed me +#define VOICECHAT_KILLGAUNTLET "kill_gauntlet" // I just killed you with the gauntlet +#define VOICECHAT_PRAISE "praise" // you did something good diff --git a/base/ui/jamp/multiplayer.menu b/base/ui/jamp/multiplayer.menu new file mode 100644 index 0000000..8e67bc0 --- /dev/null +++ b/base/ui/jamp/multiplayer.menu @@ -0,0 +1,571 @@ +//---------------------------------------------------------------------------------------------- +// +// MULTIPLAYER MENU +// +// Allows player to start a game or join one in progress +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "multiplayermenu" + fullScreen 1 + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onESC + { + play "sound/interface/esc.wav" ; + close all; + open mainMenu + } + + onOpen + { + setfocus quickserver_button + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MENU BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + // Big button "PLAY" + itemDef + { + name playbutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + + action + { + } + } + + // Big button "PLAYER PROFILE" + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 120 124 230 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open playerMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 290 124 230 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 452 124 230 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + +//---------------------------------------------------------------------------------------------- +// MULTIPLAYER MENU SPECIFIC +//---------------------------------------------------------------------------------------------- + itemDef + { + name title_glow + group none + style WINDOW_STYLE_SHADER + rect 150 162 340 20 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name title + group none + text @MENUS_START_PLAYING + style WINDOW_STYLE_EMPTY + rect 225 164 190 16 + font 3 + textscale .7 + textalign ITEM_ALIGN_CENTER + textalignx 95 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // QUICK START + itemDef + { + name quickserver_button + group none + text @MENUS_SOLO_GAME + descText @MENUS_QUICKSTART_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 191 190 36 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 175 197 300 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close multiplayermenu ; + open quickgame + } + } + + // JOIN SERVER + itemDef + { + name joinserver_button + group none + text @MENUS_JOIN_SERVER_CAPS + descText @MENUS_SEARCH_FOR_SERVERS_TO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 226 190 36 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 175 232 300 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close multiplayermenu ; + open joinserver + } + } + + // CREATE SERVER + itemDef + { + name startserver_button + group none + text @MENUS_CREATE_SERVER_CAPS + descText @MENUS_CREATE_YOUR_OWN_SERVER + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 261 190 36 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 175 267 300 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close multiplayermenu ; + open createserver + } + } + + // PLAY DEMO FILE + itemDef + { + name playdemo_button + group none + text @MENUS_PLAY_DEMO_CAPS + descText @MENUS_PLAY_BACK_A_RECORDED + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 296 190 36 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 175 302 300 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close multiplayermenu ; + open demo + } + } + + // GAME RULES + itemDef + { + name rules_button + group none + text @MENUS_RULES + descText @MENUS_RULES_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 331 190 36 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 175 337 300 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close multiplayermenu ; + open rulesMenu + } + } + +//---------------------------------------------------------------------------------------------- +// BOTTOM BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button + itemDef + { + name backbutton + group fade_buttons + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 30 441 190 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/esc.wav" ; + close all ; + open mainMenu + } + + } + + // EXIT button + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 225 441 190 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + } +} + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/password.menu b/base/ui/jamp/password.menu new file mode 100644 index 0000000..e141dca --- /dev/null +++ b/base/ui/jamp/password.menu @@ -0,0 +1,140 @@ +//---------------------------------------------------------------------------------------------- +// +// PASSWORD POPUP MENU +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "password_popmenu" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 200 150 240 180 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 320 + descY 312 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + popup + + onESC + { + play "sound/interface/esc.wav" ; + close password_popmenu + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 0 0 240 180 + backcolor 0 0 .35 .9 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// PASSWORD +//---------------------------------------------------------------------------------------------- + itemDef + { + name passwordTitle + text @MENUS_ENTER_PASSWORD + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + rect 10 5 220 20 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 110 + textaligny 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name passwordEntry + style 1 + descText @MENUS_INPUT_PASSWORD_1 + text @MENUS_BLANK_1 + maxchars 15 + font 2 + textscale .8 + TYPE 4 + cvar "password" + rect 20 50 200 30 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny 3 + forecolor 1 1 1 1 + backcolor .25 .25 .25 .5 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + mouseenter + { + setitemcolor passwordEntry backcolor .25 .25 .25 .75 + } + mouseexit + { + setitemcolor passwordEntry backcolor .25 .25 .25 .5 + } + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name doneText + text @MENUS_DONE + descText @MENUS_FINISHED_INPUTTING_INFO + type 1 + font 3 + textscale .8 + style WINDOW_STYLE_FILLED + rect 20 110 200 30 + textalign ITEM_ALIGN_CENTER + textalignx 100 + textaligny 5 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + close password_popmenu + } + mouseEnter + { + show button_glow + setitemrect button_glow 10 113 240 26 + } + mouseExit + { + hide button_glow + } + } + } +} diff --git a/base/ui/jamp/password_request.menu b/base/ui/jamp/password_request.menu new file mode 100644 index 0000000..cc9eba3 --- /dev/null +++ b/base/ui/jamp/password_request.menu @@ -0,0 +1,173 @@ +//---------------------------------------------------------------------------------------------- +// +// PASSWORD POPUP MENU +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "password_request" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 200 150 240 180 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 320 + descY 312 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + popup + + onESC + { + play "sound/interface/esc.wav" ; + close password_request + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 0 0 240 180 + backcolor 0 0 .35 .9 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// PASSWORD +//---------------------------------------------------------------------------------------------- + itemDef + { + name passwordTitle + text @MENUS_ENTER_PASSWORD + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + rect 10 5 220 20 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 110 + textaligny 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name passwordEntry + style 1 + descText @MENUS_INPUT_PASSWORD_1 + text @MENUS_BLANK_1 + maxchars 15 + font 2 + textscale .8 + TYPE 4 + cvar "password" + rect 20 50 200 30 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny 3 + forecolor 1 1 1 1 + backcolor .25 .25 .25 .5 + visible 1 + border 1 + bordercolor .79 .64 .22 1 + mouseenter + { + setitemcolor passwordEntry backcolor .25 .25 .25 .75 + } + mouseexit + { + setitemcolor passwordEntry backcolor .25 .25 .25 .5 + } + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name doneText + text @MENUS_DONE + descText @MENUS_FINISHED_INPUTTING_INFO + type 1 + font 3 + textscale .8 + style WINDOW_STYLE_FILLED + rect 20 90 200 30 + textalign ITEM_ALIGN_CENTER + textalignx 100 + textaligny 5 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiscript JoinServer + close password_request + } + mouseEnter + { + show button_glow + setitemrect button_glow 10 93 240 26 + } + mouseExit + { + hide button_glow + } + } + + + itemDef + { + name cancel + text @MENUS_VC_CANCEL + descText @MP_SVGAME_CANCEL_PASSWORD + type 1 + font 3 + textscale .8 + style WINDOW_STYLE_FILLED + rect 20 125 200 30 + textalign ITEM_ALIGN_CENTER + textalignx 100 + textaligny 5 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + close password_request + } + mouseEnter + { + show button_glow + setitemrect button_glow 10 128 240 26 + } + mouseExit + { + hide button_glow + } + } + } +} diff --git a/base/ui/jamp/player.menu b/base/ui/jamp/player.menu new file mode 100644 index 0000000..7c95b1d --- /dev/null +++ b/base/ui/jamp/player.menu @@ -0,0 +1,638 @@ +// CHARACTER CREATION MENU +{ + + menuDef + { + name "playerMenu" + fullScreen 1 + rect 0 0 640 480 + focusColor 1 1 1 1 + descX 320 + descY 430 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript update "ui_GetName" + } + + onESC + { + play "sound/interface/menuroam.wav" + close playerMenu + open mainMenu + } + + onClose + { + uiScript update "ui_SetName" + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + //background "gfx/menus/charmenu" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Box around character models + itemDef + { + name background + group none + style WINDOW_STYLE_EMPTY + rect 13 186 610 245 + border 1 + bordercolor .298 .305 .690 1 + bordersize 2 + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Title box for character models + itemDef + { + name background + group none + style WINDOW_STYLE_FILLED + rect 15 186 610 20 + backcolor .298 .305 .690 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "PLAYER PROFILE" + itemDef + { + name profilebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + //show profilebutton_glow + } + mouseExit + { + //hide profilebutton_glow + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsmenu + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + +//---------------------------------------------------------------------------------------------- +// +// OTHER MAIN MENU BUTTONS +// +//---------------------------------------------------------------------------------------------- + +// EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3" + close all + open quitMenu + } + } + + + +//------------------------------- +// +// PLAYER MENU SPECIFIC STUFF +// +//------------------------------- + +// Name entry field + itemDef + { + name nameglow + group mods + style WINDOW_STYLE_SHADER + rect 20 156 300 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name namefield + type ITEM_TYPE_EDITFIELD + style 0 + text @MENUS_NAME1 + cvar "ui_Name" + maxchars 26 + rect 15 163 300 28 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -5 + font 2 + textscale 1 + forecolor .615 .615 .956 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_ENTER_YOUR_NAME_HERE + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show nameglow + } + mouseexit + { + hide nameglow + } + } + +// Player Model label + itemDef + { + name modeltitle + style 0 + text @MENUS_CHARACTER_MODEL + rect 320 184 0 0 + textalign ITEM_ALIGN_CENTER + textalignx 0 + textaligny -3 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale 1 + forecolor .549 .854 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + } + +// Skin/Team Color Chooser + itemDef + { + name setcolor + style 0 + text @MENUS_TEAM_COLOR + ownerdraw UI_SKIN_COLOR + rect 50 209 160 20 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -5 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale .9 + forecolor .615 .615 .956 1 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_CHOOSE_THE_COLOR_FOR + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + } + +// Scroll box with portraits. + itemDef + { + name headlist + rect 30 224 404 194 +// rect 126 215 80 200 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 64 + elementheight 64 + elementtype LISTBOX_IMAGE + feeder FEEDER_Q3HEADS +// horizontalscroll + backcolor 0 0 0 1 + border 1 + bordercolor .5 .5 .5 1 + forecolor 1 1 1 1 + descText @MENUS_CHOOSE_THE_MODEL_FOR + visible 1 + textscale 0.7 + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + setitemcolor headlist bordercolor 1 0 0 1 + } + mouseexit + { + setitemcolor headlist bordercolor .5 .5 .5 1 + } + } + +// Custom skin + + itemDef + { + name customtitle + style 0 + text @MENUS_CUSTOM + rect 425 250 200 26 + textalign ITEM_ALIGN_CENTER + textalignx 100 + textaligny -3 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name custom + group none + background "gfx/mp/custom_mp_default" + descText @MENUS_CUSTOMPLAYER_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_SHADER + rect 480 280 96 96 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor .5 .5 .5 1 + visible 1 + mouseenter + { + setitemcolor custom forecolor 1 1 1 1 + } + mouseexit + { + setitemcolor custom forecolor .5 .5 .5 1 + } + action + { + play "sound/interface/button1.wav" + close playerMenu + open playerMenu2 + } + } + + + itemDef + { + name next_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name next + group none + text @MENUS_APPLY_CAPS + descText @MENUS_APPLY_PLAYER_SABER + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 455 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + close all +// uiScript "updatecharmodel" + +// uiScript "updatecharcvars" + open saberMenu +// open mainMenu + } + mouseEnter + { + show next_glow + } + mouseExit + { + hide next_glow + } + } + } +} \ No newline at end of file diff --git a/base/ui/jamp/player2.menu b/base/ui/jamp/player2.menu new file mode 100644 index 0000000..4389dde --- /dev/null +++ b/base/ui/jamp/player2.menu @@ -0,0 +1,821 @@ +// CHARACTER CREATION MENU +{ + + menuDef + { + name "playerMenu2" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 430 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + + onOpen + { + uiScript "getcharcvars" + uiScript "character" + } + + onESC + { + play "sound/interface/menuroam.wav" + close playerMenu2 + open playerMenu + } + + +//---------------------------------------------------------------------------------------------- +// +// MENU BACKGROUND +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 +// background "gfx/menus/main_background" + background "gfx/menus/charmenu" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 -60 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 -60 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + // CREATION title + itemDef + { + name creation_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_CHARACTER_CREATION + rect 100 54 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name character + group models + type ITEM_TYPE_MODEL + //rect 95 84 820 1000 + rect 52 84 900 1000 + model_g2anim "BOTH_WALK1" + asset_model "ui_char_model" + model_angle 180 + //mins maxs format is apparently z x y (hmmm... y x z?) + //model_g2mins -20 -15 -10 + //model_g2maxs 10 15 20 + model_g2mins -10 -15 -10 + model_g2maxs 20 15 30 + model_rotation 50 + //model_fovx 75 + model_fovx 50 + //model_fovy 75 + model_fovy 50 + isCharacter 1 + visible 1 + decoration + } + + itemDef + { + name background2 + group none + style WINDOW_STYLE_SHADER + rect 320 360 320 120 + background "gfx/menus/charmenu_bottom" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// TOP MAIN MENU BUTTONS +// +//---------------------------------------------------------------------------------------------- + +// Big button "NEW" + itemDef + { + name newbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 7 16 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 16 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + close all ; + open multiplayermenu + } + } + + +// Big button "PROFILE" + itemDef + { + name profilebutton + group lbut + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 16 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + } + mouseExit + { + } + action + { + } + } + +// Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 16 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 16 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + close all ; + open controlsMenu ; + } + } + +// Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 16 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 16 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + +//---------------------------------------------------------------------------------------------- +// +// CHARACTER MENU specific stuff +// +//---------------------------------------------------------------------------------------------- + + + +////////////////// +// SPECIES BUTTON +////////////////// + + itemDef + { + name species + group none + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 30 88 140 24 + forecolor .549 .854 1 1 + text @MENUS_SPECIES + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + font 3 + textscale 1 + visible 1 + decoration + } + + itemDef + { + name speciesbut_glow + group none + style WINDOW_STYLE_SHADER + rect 176 92 150 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name speciesbut + group none + text " " + descText @MENUS_CHOOSE_SPECIES + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 176 92 150 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 0 + textalignx 0 + backcolor 0 0 0 0 + forecolor .615 .615 .956 1 + feeder 19 //FEEDER_PLAYER_SPECIES + cvar "ui_char_model" + cvarStrList feeder + + visible 1 + + mouseEnter + { + show speciesbut_glow + } + mouseExit + { + hide speciesbut_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "characterchanged" + uiScript "resetcharacterlistboxes" + } + } + +//////////////////// +// COLOR TINT AREA +//////////////////// + + + itemDef + { + name color + group none + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 30 144 160 24 + text @MENUS_COLOR + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + font 3 + textscale 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name colorbox + group tints + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 32 + elementheight 32 + elementtype 1 //LISTBOX_IMAGE + feeder 23 //FEEDER_COLORCHOICES + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + rect 30 168 292 48 + visible 1 + action + { + play "sound/interface/choose_color.wav" + } + } + +/////////////////////// +//APPEARANCE +////////////////////// + + itemDef + { + name appear + group none + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 30 252 180 24 + forecolor .549 .854 1 1 + text @MENUS_APPEARANCE + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -1 + font 3 + textscale 1 + visible 1 + decoration + } + + // HEAD BUTTON + itemDef + { + name headbut_glow + group none + style WINDOW_STYLE_SHADER + rect 30 280 90 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name headbut + group none + text @MENUS_HEAD + descText @MENUS_SELECT_HEAD + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 30 280 90 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 0 + textalignx 0 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show headbut_glow + } + mouseExit + { + hide headbut_glow + } + action + { + play "sound/interface/button1.wav" + show heads + hide torso + hide lower + transition2 character 52 84 900 1000 20 25 + } + } + + + // TORSO BUTTON + itemDef + { + name torsobut_glow + group none + style WINDOW_STYLE_SHADER + rect 126 280 90 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name torsobut + group none + text @MENUS_TORSO + descText @MENUS_SELECT_TORSO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 126 280 90 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 0 + textalignx 0 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show torsobut_glow + } + mouseExit + { + hide torsobut_glow + } + action + { + play "sound/interface/button1.wav" + show torso + hide heads + hide lower + transition2 character 355 84 300 340 20 25 + } + } + + // LEGS BUTTON + itemDef + { + name legsbut_glow + group none + style WINDOW_STYLE_SHADER + rect 224 280 90 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name legsbut + group none + text @MENUS_LEGS + descText @MENUS_SELECT_LEGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 224 280 90 16 + font 2 + textscale .9 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle 0 + textalignx 0 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show legsbut_glow + } + mouseExit + { + hide legsbut_glow + } + action + { + play "sound/interface/button1.wav" + show lower + hide heads + hide torso + transition2 character 355 84 300 340 20 25 + } + } + +////////////////////// +//LISTBOXES +////////////////////// + + itemDef + { + name headlistbox + group heads + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 72 + elementheight 72 + elementtype 1 //LISTBOX_IMAGE + feeder 20 //FEEDER_PLAYER_SKIN_HEAD + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + forecolor -1 //use playercolor + rect 30 306 292 93 + visible 1 + action + { + play "sound/interface/choose_head.wav" + uiScript "char_skin" + } + } + + itemDef + { + name torsolistbox + group torso + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 72 + elementheight 72 + elementtype 1 //LISTBOX_IMAGE + feeder 21 //FEEDER_PLAYER_SKIN_TORSO + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + forecolor -1 //use playercolor + rect 30 306 292 93 + visible 0 + action + { + play "sound/interface/choose_torso.wav" + uiScript "char_skin" + } + } + + itemDef + { + name lowerlistbox + group lower + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 72 + elementheight 72 + elementtype 1 //LISTBOX_IMAGE + feeder 22 //FEEDER_PLAYER_SKIN_LEGS + horizontalscroll + border 1 + bordersize 1 + backcolor .66 .66 1 .25 + bordercolor .66 .66 1 1 + forecolor -1 //use playercolor + rect 30 306 292 93 + visible 0 + action + { + play "sound/interface/choose_head.wav" + uiScript "char_skin" + } + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_BACK + descText @MENUS_BACK_2_PROFILE + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3" + close playerMenu2 + open playerMenu + } + } + + itemDef + { + name next_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name next + group none + text @MENUS_APPLY_CAPS + descText @MENUS_APPLY_PLAYER_SABER + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 455 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + close all + uiScript "updatecharmodel" + +// uiScript "updatecharcvars" + open saberMenu +// open mainMenu + } + mouseEnter + { + show next_glow + } + mouseExit + { + hide next_glow + } + } + } +} \ No newline at end of file diff --git a/base/ui/jamp/quickbots.menu b/base/ui/jamp/quickbots.menu new file mode 100644 index 0000000..76111ad --- /dev/null +++ b/base/ui/jamp/quickbots.menu @@ -0,0 +1,652 @@ +//---------------------------------------------------------------------------------------------- +// QUICK GAME BOT MENU - +// Set up the roster of bots and humans for a game. Called from Quick Game2 menu. +// +//ui_net_gametype +// 0 = FFA +// 1 = DUEL +// 2 = POWER DUEL +// 3 = TEAM FFA +// 4 = SIEGE +// 5 = CTF +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "quickbots" + fullScreen MENU_FALSE + rect 100 50 440 380 // Size and position of the menu + visible MENU_FALSE // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 380 + descScale 1 + descColor 1 .682 0 1 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + popup + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + + onESC + { + play "sound/interface/esc.wav" ; + close quickbots ; + open quickgame2 ; + } + + itemDef + { + name frame_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 440 380 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + decoration + border 1 + bordercolor .265 .824 .886 .25 + bordersize 3 + visible 1 + } + +//---------------------------------------------------------------------------------------------- +// TITLE +//---------------------------------------------------------------------------------------------- + // Bots title + itemDef + { + name title + group none + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_BOTS + rect 55 5 330 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 165 + textaligny -1 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// BOT LIST FOR NON-TEAMGAMES +//---------------------------------------------------------------------------------------------- + itemDef + { + name botlist_slot + style 0 + text @MENUS_1_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM1 + rect 20 55 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 51 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_2_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM1 + rect 20 75 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 71 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_3_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM2 + rect 20 95 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 91 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_4_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM2 + rect 20 115 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 111 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_5_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM3 + rect 20 135 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 131 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_6_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM3 + rect 20 155 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 151 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_7_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM4 + rect 20 175 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 171 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_8_1 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM4 + rect 20 195 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 191 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_9 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM5 + rect 220 55 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 20 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 200 51 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_10 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM5 + rect 220 75 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 200 71 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_11 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM6 + rect 220 95 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 200 91 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_12 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM6 + rect 220 115 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 200 111 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_13 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM7 + rect 220 135 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 200 131 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_14 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM7 + rect 220 155 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 200 151 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_15 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_BLUETEAM8 + rect 220 175 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 0.65 0.65 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 200 171 250 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name botlist_slot + style 0 + text @MENUS_16 + descText @MENUS_CLICK_FOR_HUMAN_BOTS + ownerdraw UI_REDTEAM8 + rect 220 195 196 20 + textalign ITEM_ALIGN_LEFT + textalignx 10 + textaligny -4 + font 2 + textscale 1 + forecolor 1 0.2 0.2 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 200 191 250 28 + } + mouseExit + { + hide button_glow + } + } + +//---------------------------------------------------------------------------------------------- +// DONE +//---------------------------------------------------------------------------------------------- + itemDef + { + name done + text @MENUS_DONE + descText @MENUS_RETURN_PREVIOUS + type ITEM_TYPE_BUTTON + font 3 + textscale 1 + style WINDOW_STYLE_EMPTY + rect 140 352 160 30 + textalignx 80 // Center + textaligny 0 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + close quickbots ; + } + mouseEnter + { + show button_glow + setitemrect button_glow 130 350 200 34 + } + mouseExit + { + hide button_glow + } + } + + } +} + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/quickgame.menu b/base/ui/jamp/quickgame.menu new file mode 100644 index 0000000..1aa88b4 --- /dev/null +++ b/base/ui/jamp/quickgame.menu @@ -0,0 +1,431 @@ +//---------------------------------------------------------------------------------------------- +// +// MULTIPLAYER QUICK SERVER MENU +// +// Allows player to quickly create a server +// This is the first screen which allows you to choose a map. +// The next screen lets you set up the rules. +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "quickgame" + fullScreen MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onEsc + { + play "sound/interface/esc.wav" ; + close quickgame ; + open multiplayermenu + } + + onOpen + { + uiScript loadArenas ; + uiScript checkforsiege ; + hide accept_alt ; + show accept ; + hide back_alt ; + show back ; + hide grpmessage + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Quick Game title + itemDef + { + name title_glow + group none + style WINDOW_STYLE_SHADER + rect 150 11 340 30 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name title + group none + style WINDOW_STYLE_EMPTY + background "gfx/menus/menu_blendbox" + text @MENUS_SOLO_GAME + rect 50 14 540 16 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 270 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// GAME TYPE SELECTION +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + // GAME TYPE SELECTION FIELD + // Do NOT change the name of this item - it's been hacked in code + // so that if you hit siege it hops over it to the next game type. + itemDef + { + name solo_gametypefield + style WINDOW_STYLE_EMPTY + ownerdraw UI_NETGAMETYPE + text @MENUS_GAME_TYPE + descText @MENUS_ALLOWS_YOU_TO_SELECT + textstyle 0 + rect 170 60 300 32 + textalign ITEM_ALIGN_RIGHT + textalignx 85 + textaligny -1 + font 2 + textscale 1 + forecolor .615 .615 .956 1 + visible 1 + border 0 + bordercolor 1 1 1 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 125 57 400 38 + } + mouseExit + { + hide button_glow + } + } + +//---------------------------------------------------------------------------------------------- +// MAP LISTING BOX +//---------------------------------------------------------------------------------------------- + itemDef + { + name create_title + group none + style WINDOW_STYLE_EMPTY + text @MENUS_MAP_LISTING + rect 30 105 200 24 + font 3 + textscale .9 + textalign ITEM_ALIGN_CENTER + textalignx 100 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name maplist + rect 35 125 194 250 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 20 + font 4 + textscale 1 + elementtype LISTBOX_TEXT + feeder FEEDER_ALLMAPS + textstyle 6 + textalign 3 + textaligny 2 + border 1 + bordercolor .79 .64 .22 .5 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .75 + outlinecolor .25 .464 .578 .5 + descText @MENUS_CHOOSE_YOUR_GAME + visible 1 + columns 1 2 190 172 + action + { + play "sound/interface/button1.wav" ; + } + mouseEnter + { + setitemcolor maplist bordercolor .79 .64 .22 1 + } + mouseExit + { + setitemcolor maplist bordercolor .79 .64 .22 .5 + } + } + +//---------------------------------------------------------------------------------------------- +// MAP SCREENSHOT +//---------------------------------------------------------------------------------------------- + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 280 104 340 280 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + itemDef + { + name mappreview + style 0 + ownerdraw UI_STARTMAPCINEMATIC + rect 290 124 320 240 + border 1 + bordercolor .265 .824 .886 .25 + visible 1 + } + + itemDef + { + name mappreview + style WINDOW_STYLE_FILLED + rect 289 123 322 242 + border 1 + bordercolor .265 .824 .886 .25 + visible 1 + } + +//---------------------------------------------------------------------------------------------- +// BOTTOM BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button + itemDef + { + name backbutton + group fade_buttons + text @MENUS_BACK + descText @MENUS_BACKUP_ONE_MENU + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 30 441 190 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/esc.wav" ; + close all ; + open multiplayermenu + } + + } + + // EXIT button + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 225 441 190 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + // NEXT button + itemDef + { + name next_button + text @MENUS_NEXT + descText @MENUS_CHOOSE_RULES + type ITEM_TYPE_BUTTON + font 3 + textscale 1.1 + textstyle 0 + style WINDOW_STYLE_FILLED + rect 455 444 200 32 + textalign 1 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + close quickgame ; + open quickgame2 + } + + mouseEnter + { + show button_glow + setitemrect button_glow 425 441 190 30 + } + mouseExit + { + hide button_glow + } + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/quickgame2.menu b/base/ui/jamp/quickgame2.menu new file mode 100644 index 0000000..689d3fc --- /dev/null +++ b/base/ui/jamp/quickgame2.menu @@ -0,0 +1,865 @@ +//---------------------------------------------------------------------------------------------- +// +// MULTIPLAYER QUICK SERVER MENU +// +// Allows player to choose quickly game server rules +// This is the second screen in the quick game series. +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "quickgame2" + fullScreen MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onEsc + { + play "sound/interface/esc.wav" ; + close quickgame2 ; + open quickgame + } + + onOpen + { + hide accept_alt ; + show accept ; + hide back_alt ; + show back ; + hide grpmessage + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Create Server title +//---------------------------------------------------------------------------------------------- + // Quick Game title + itemDef + { + name title_glow + group none + style WINDOW_STYLE_SHADER + rect 150 11 340 30 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name title + group none + style WINDOW_STYLE_EMPTY + background "gfx/menus/menu_blendbox" + text @MENUS_SOLO_GAME + rect 50 14 540 16 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 270 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// GENERAL RULES +//---------------------------------------------------------------------------------------------- + itemDef + { + name rules_title + group none + text @MENUS_GENERAL_RULES + rect 5 110 240 20 + font 3 + textscale 0.8 + textalign ITEM_ALIGN_CENTER + textalignx 120 + textaligny -2 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // LIGHTSABER ONLY + itemDef + { + name options + group grpOptions + type ITEM_TYPE_YESNO + text @MENUS_LIGHTSABER_ONLY + descText @MENUS_SABER_ONLY_INFO + cvar "g_weaponDisable" + rect 5 130 240 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny -2 + font 2 + textscale .9 + forecolor .615 .615 .956 1 + visible 1 + cvarTest "ui_netGameType" + //Hide in Duel/Power Duel + hideCvar + { + "1" ; + "2" + } + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 127 260 30 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name options + group grpOptions + type ITEM_TYPE_YESNO + text @MENUS_LIGHTSABER_ONLY + descText @MENUS_SABER_ONLY_INFO + cvar "g_duelWeaponDisable" + rect 5 130 240 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny -2 + font 2 + textscale .9 + forecolor .615 .615 .956 1 + visible 1 + cvarTest "ui_netGameType" + //hide in everthing but Duel/Power Duel + showCvar + { + "1" ; + "2" + } + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 127 260 30 + } + mouseExit + { + hide button_glow + } + } + + // FORCE POWER DISABLE + itemDef + { + name options + group grpOptions + type ITEM_TYPE_YESNO + text @MENUS_DISABLE_FORCE + descText @MENUS_DISABLE_FORCE_INFO + cvar "g_forcePowerDisable" + cvarTest "ui_netGameType" + hideCvar + { + "4" + } + rect 5 150 240 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny -2 + font 2 + textscale .9 + forecolor .615 .615 .956 1 + visible 1 + action + { + uiScript forcePowersDisable ; + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 147 260 30 + } + mouseExit + { + hide button_glow + } + } + + + itemDef + { + name normal + group grpsettings + text @MENUS_FORCE_MASTERY + descText @MENUS_FORCE_MASTERY_INFO + ownerdraw UI_FORCE_MASTERY_SET + cvarTest "ui_netGameType" + hideCvar + { + "4" + } + rect 5 170 240 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny 0 + font 2 + textscale .9 + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 167 260 30 + } + mouseExit + { + hide button_glow + } + } + + +//---------------------------------------------------------------------------------------------- +// END GAME RULES TITLE +//---------------------------------------------------------------------------------------------- + itemDef + { + name end_game_title + group none + text @MENUS_ENDGAME_RULES + rect 5 200 240 20 + font 3 + textscale 0.8 + textalign ITEM_ALIGN_CENTER + textalignx 120 + textaligny -2 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // TIME LIMIT + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_TIME_LIMIT + cvar "timelimit" + rect 5 220 240 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny -2 + font 2 + textscale .9 + maxchars 4 + forecolor .615 .615 .956 1 + visible 1 + descText @MENUS_THIS_VALUE_ADJUSTS_THE + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 217 260 30 + } + mouseExit + { + hide button_glow + } + } + + // KILL LIMIT / CAPTURE LIMIT /DUEL LIMIT + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_KILL_LIMIT + cvar "fraglimit" + cvarTest "ui_netGameType" + showCvar + { + "0" ; + "3" ; + } + rect 5 240 240 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny -2 + font 2 + textscale .9 + maxchars 4 + forecolor .615 .615 .956 1 + visible 1 + descText @MENUS_ESTABLISH_THE_NUMBER + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 237 260 30 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_CAPTURE_LIMIT + cvar "capturelimit" + cvarTest "ui_netGameType" + showCvar + { + "5" ; + } + rect 5 240 240 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny -2 + font 2 + textscale .9 + maxchars 8 + forecolor .615 .615 .956 1 + visible 1 + descText @MENUS_THIS_IS_THE_NUMBER_OF + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 237 260 30 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name normal + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_DUEL_LIMIT + descText @MENUS_VALUE_ADJUSTS_THE_TOTAL + cvar "duel_fraglimit" + cvarTest "ui_netGameType" + hideCvar + { + "0" ; + "3" ; + "4" ; + "5" ; + } + rect 5 240 240 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny -2 + font 2 + textscale .9 + maxchars 8 + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 237 260 30 + } + mouseExit + { + hide button_glow + } + } + +//---------------------------------------------------------------------------------------------- +// BOTS TITLE +//---------------------------------------------------------------------------------------------- + itemDef + { + name bots_title + group none + text @MENUS_BOTS + rect 5 280 240 20 + font 3 + textscale 0.8 + textalign ITEM_ALIGN_CENTER + textalignx 120 + textaligny -2 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // MINIMUM BOTS/PLAYERS + itemDef + { + name expert_button + group grpsettings + type ITEM_TYPE_NUMERICFIELD + maxchars 4 + text @MENUS_PLAYERS + descText @MENUS_PLAYERS_INFO + cvar "bot_minplayers" + rect 5 300 240 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny -2 + font 2 + textscale .9 + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 297 260 30 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name expert_button + group grpsettings + type ITEM_TYPE_NUMERICFIELD + text @MENUS_MAXIMUM_PLAYERS + cvar "sv_maxclients" + rect 5 320 240 20 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny -2 + font 2 + textscale .9 + forecolor .615 .615 .956 1 + maxchars 6 + visible 1 + descText @MENUS_SETS_THE_MAXIMUM_NUMBER + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show settingsButton1 + } + mouseExit + { + hide settingsButton1 + uiscript clampmaxplayers + } + } + + itemDef + { + name difficulty_button + style 0 + ownerdraw UI_SKILL + text @MENUS_SKILL + descText @MENUS_ADJUSTS_THE_DIFFICULTY + rect 5 340 240 20 + textstyle 0 + textalign ITEM_ALIGN_RIGHT + textalignx 165 + textaligny -2 + font 2 + textscale .9 + forecolor .615 .615 .956 1 + visible 1 + action + { + play "sound/interface/button1.wav" + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 337 260 30 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name custombots_button + text @MENUS_SELECT_BOTS + descText @MENUS_SELECT_BOTS_DESC + type ITEM_TYPE_BUTTON + font 2 + textscale .9 + textstyle 0 + style WINDOW_STYLE_FILLED + rect 5 360 240 20 + textalignx 120 + textaligny -2 + textalign ITEM_ALIGN_CENTER + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + open quickbots + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 357 260 30 + } + mouseExit + { + hide button_glow + } + } + +//---------------------------------------------------------------------------------------------- +// MAP SCREENSHOT +//---------------------------------------------------------------------------------------------- + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 280 104 340 280 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + itemDef + { + name mappreview + style 0 + ownerdraw UI_STARTMAPCINEMATIC + rect 290 124 320 240 + border 1 + bordercolor .265 .824 .886 .25 + visible 1 + } + + itemDef + { + name mappreview + style WINDOW_STYLE_FILLED + rect 289 123 322 242 + border 1 + bordercolor .265 .824 .886 .25 + visible 1 + } + + +//---------------------------------------------------------------------------------------------- +// BOTTOM BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button + itemDef + { + name back_button + group fade_buttons + text @MENUS_BACK + descText @MENUS_BACKUP_ONE_MENU + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 30 441 190 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/esc.wav" ; + close quickgame2 ; + open quickgame + } + + } + + // EXIT button + itemDef + { + name exitgame_button + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 225 441 190 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + // NEXT button + itemDef + { + name next_button + text @MENUS_NEXT + descText @MENUS_START_SERVER + type ITEM_TYPE_BUTTON + font 3 + textscale 1.1 + textstyle 0 + style WINDOW_STYLE_FILLED + rect 455 444 200 32 + textalign 1 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 425 441 190 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + uiScript weaponDisable ; //update the weapondisable cvar for duel or non-duel + setcvar ui_singlePlayerActive "1" + uiScript StartServer ; + close quickgame2 ; + } + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/quit.menu b/base/ui/jamp/quit.menu new file mode 100644 index 0000000..d8d0a5b --- /dev/null +++ b/base/ui/jamp/quit.menu @@ -0,0 +1,505 @@ + //---------------------------------------------------------------------------------------------- + // + // QUIT MENU + // + //---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "quitMenu" + visible 0 + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + focusColor 1 1 1 1 // Focus color for text and items + appearanceIncrement 100 // In miliseconds + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + appearanceIncrement 100 // In miliseconds + + onESC + { + play "sound/interface/button1.wav" ; + close quitMenu ; + open mainMenu ; + } + + onOpen + { + setfocus quitgame_cancel + } + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "PLAYER PROFILE" + itemDef + { + name profilebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show profilebutton_glow + } + mouseExit + { + hide profilebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open playerMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Credits hidden button + itemDef + { + name creditsbutton + group othermain +// text @CREDITS + descText @MENUS_SHOW_GAME_CREDITS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 200 144 256 256 + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textalignx 46 + backcolor 0 0 0 0 + forecolor 0.65 0.65 1 1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open creditsMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // QUIT MENU specific stuff + // + //---------------------------------------------------------------------------------------------- + // Quitting title + itemDef + { + name quit_title + group none + text @MENUS_LEAVING_JEDI_KNIGHT_2 + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name confirm + group none + text @MENUS_QUIT_JEDI_KNIGHT_II + font 2 + textscale 1 + textstyle 0 + rect 95 250 450 20 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny 0 + decoration + forecolor 1 .682 0 1 + visible 1 + } + + + itemDef + { + name quitgame_cancel_button + group none + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // CANCEL button + itemDef + { + name quitgame_cancel + group none + text @MENUS_NO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + descText @MENUS_DO_NOT_LEAVE_JEDI_KNIGHT + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + close quitMenu ; + open mainMenu ; + } + mouseEnter + { + show quitgame_cancel_button + } + mouseExit + { + hide quitgame_cancel_button + } + } + + itemDef + { + name quitgame_yes_button + group none + style WINDOW_STYLE_SHADER + rect 454 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // YES button + itemDef + { + name quitgame_yes + group none + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 454 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + descText @MENUS_JEDI_KNIGHT_II + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + uiScript Quit // Quit the game + } + mouseEnter + { + show quitgame_yes_button + } + mouseExit + { + hide quitgame_yes_button + } + } + + } +} + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/rules.menu b/base/ui/jamp/rules.menu new file mode 100644 index 0000000..1c961ee --- /dev/null +++ b/base/ui/jamp/rules.menu @@ -0,0 +1,595 @@ +//---------------------------------------------------------------------------------------------- +// MULTIPLAYER RULES +// +// Rules main menu.From here you can get at any of the rules menus. +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "rulesMenu" + fullScreen 1 + rect 0 0 640 480 // Size and position of the menu + visible 0 + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + setcvar "ui_rules_backout" 0 ; + } + + onESC + { + play "sound/interface/esc.wav" ; + close rulesMenu ; + open multiplayerMenu ; + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MENU BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + // Big button "PLAY" + itemDef + { + name playbutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 0 124 180 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "PLAYER PROFILE" + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 120 124 230 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open playerMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 290 124 230 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 452 124 230 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + +//---------------------------------------------------------------------------------------------- +// SECOND ROW MENU BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name title_glow + group none + style WINDOW_STYLE_SHADER + rect 150 162 340 20 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name default_rulesheader + group rulesheader + style WINDOW_STYLE_EMPTY + text @MENUS_RULES + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // GAMES + itemDef + { + name gamesmenubutton + group default_submenu + text @MENUS_GAMES + descText @MENUS_GAME_RULES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 190 190 40 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 175 196 300 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + + close "rulesMenu" ; + open "rulesMenu_games" ; + } + } + + // Games + itemDef + { + name forcemenubutton + group default_submenu + text @MENUS_FORCE_POWERS + descText @MENUS_FORCE_RULES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 230 190 40 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show button_glow + setitemrect button_glow 175 236 300 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + close "rulesMenu" ; + open "rulesMenu_force" ; + } + } + + // Weapons + itemDef + { + name weaponmenubutton + group default_submenu + text @MENUS_WEAPONS + descText @MENUS_WEAPON_RULES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 270 190 40 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + + close "rulesMenu" ; + open "rulesMenu_weapons" ; + } + mouseEnter + { + show button_glow + setitemrect button_glow 175 276 300 30 + } + mouseExit + { + hide button_glow + } + } + + // Items + itemDef + { + name itemmenubutton + group default_submenu + text @MENUS_ITEMS + descText @MENUS_ITEM_RULES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 310 190 40 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + + close "rulesMenu" ; + open "rulesMenu_items" ; + } + mouseEnter + { + show button_glow + setitemrect button_glow 175 316 300 30 + } + mouseExit + { + hide button_glow + } + } + + // Move + itemDef + { + name itemmenubutton + group default_submenu + text @MENUS_MOVES + descText @MENUS_MOVE_RULES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 350 190 40 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + + close "rulesMenu" ; + open "rulesMenu_moves" ; + } + mouseEnter + { + show button_glow + setitemrect button_glow 175 356 300 30 + } + mouseExit + { + hide button_glow + } + } + +//---------------------------------------------------------------------------------------------- +// BOTTOM BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button + itemDef + { + name backbutton + group fade_buttons + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 30 441 190 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/esc.wav" ; + close all ; + open mainMenu + } + + } + + // EXIT button + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 225 441 190 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + } +} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/rules_force.menu b/base/ui/jamp/rules_force.menu new file mode 100644 index 0000000..48358bd --- /dev/null +++ b/base/ui/jamp/rules_force.menu @@ -0,0 +1,1830 @@ +//---------------------------------------------------------------------------------------------- +// RULES +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "rulesMenu_force" + fullScreen MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 0 + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + hide gamedesc ; + show jump_desc ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor jumpmenubutton forecolor 1 1 1 1 ; + } + + onESC + { + play "sound/interface/button1.wav" ; + + close "rulesMenu_force" ; + open "rulesMenu" ; + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "LOAD" + itemDef + { + name profilebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show profilebutton_glow + } + mouseExit + { + hide profilebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open playerMenu + } + } + + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Credits hidden button + itemDef + { + name creditsbutton + group othermain +// text @CREDITS + descText @MENUS_SHOW_GAME_CREDITS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 200 144 256 256 + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textalignx 46 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open creditsMenu + } + } + + // BACK button + itemDef + { + name backbutton + group fade_buttons + text @MENUS_BACK + descText @MENUS_BACKTORULES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 30 441 190 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/esc.wav" ; + close all ; + open rulesMenu + } + + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // SECOND ROW MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name force_rulesheader + group rulesheader + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_FORCE_RULES_TITLE + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 225 183 370 230 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + // Main small highlights + itemDef + { + group smallhighlight + name smallhighlight1 + style WINDOW_STYLE_SHADER + rect 40 185 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight2 + style WINDOW_STYLE_SHADER + rect 40 200 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight3 + style WINDOW_STYLE_SHADER + rect 40 215 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight4 + style WINDOW_STYLE_SHADER + rect 40 230 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight5 + style WINDOW_STYLE_SHADER + rect 40 245 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight6 + style WINDOW_STYLE_SHADER + rect 40 260 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight7 + style WINDOW_STYLE_SHADER + rect 40 275 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight8 + style WINDOW_STYLE_SHADER + rect 40 290 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight9 + style WINDOW_STYLE_SHADER + rect 40 305 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight10 + style WINDOW_STYLE_SHADER + rect 40 320 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight11 + style WINDOW_STYLE_SHADER + rect 40 335 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight12 + style WINDOW_STYLE_SHADER + rect 40 350 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight13 + style WINDOW_STYLE_SHADER + rect 40 365 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight14 + style WINDOW_STYLE_SHADER + rect 40 380 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight15 + style WINDOW_STYLE_SHADER + rect 40 395 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // FORCE RULES + // + //---------------------------------------------------------------------------------------------- + + // JUMP FORCE DESC + + itemDef + { + name jumpmenubutton + group force_submenu + text @MENUS_JUMP + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 185 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight1 + } + mouseExit + { + hide smallhighlight1 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor jumpmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show jump_desc ; + } + } + + itemDef + { + name jump_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_jump" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name jump_desc + text @MENUS_DURATION_IMMEDIATE_NAREA + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + lineHeight 15 + maxLineChars 45 + } + + // SPEED FORCE DESC + + itemDef + { + name speedmenubutton + group force_submenu + text @MENUS_SPEED + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 200 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight2 + } + mouseExit + { + hide smallhighlight2 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor speedmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show speed_desc ; + } + } + + itemDef + { + name speed_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_speed" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name speed_desc + text @MENUS_DURATION_5_SECONDS_NAREA + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // PUSH FORCE DESC + + itemDef + { + name pushmenubutton + group force_submenu + text @MENUS_PUSH + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 215 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight3 + } + mouseExit + { + hide smallhighlight3 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor pushmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show push_desc ; + } + } + + itemDef + { + name push_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_push" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name push_desc + text @MENUS_DURATION_INSTANTANEOUS + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // PULL FORCE DESC + + itemDef + { + name pullmenubutton + group force_submenu + text @MENUS_PULL + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 230 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight4 + } + mouseExit + { + hide smallhighlight4 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor pullmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show pull_desc ; + } + } + + itemDef + { + name pull_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_pull" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name pull_desc + text @MENUS_INSTANTANEOUS_EFFECT_NAREA + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // SIGHT FORCE DESC + + itemDef + { + name sightmenubutton + group force_submenu + text @MENUS_SIGHT + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 245 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight5 + } + mouseExit + { + hide smallhighlight5 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor sightmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show sight_desc ; + } + } + + itemDef + { + name sight_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_sight" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name sight_desc + text @MENUS_EFFECT_NAREA_OF_EFFECT + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // PROTECT FORCE DESC + + itemDef + { + name protectmenubutton + group force_submenu + text @MENUS_PROTECT + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 260 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight6 + } + mouseExit + { + hide smallhighlight6 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor protectmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show protect_desc ; + } + } + + itemDef + { + name protect_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_lt_protect" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name protect_desc + text @MENUS_DURATION_VARIABLE_NAREA + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // ABSORB FORCE DESC + + itemDef + { + name absorbmenubutton + group force_submenu + text @MENUS_ABSORB + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 275 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight7 + } + mouseExit + { + hide smallhighlight7 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor absorbmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show absorb_desc ; + } + } + + itemDef + { + name absorb_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_lt_absorb" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name absorb_desc + text @MENUS_DURATION_CONTINUOUS_NAREA + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 1 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // HEAL FORCE DESC + + itemDef + { + name healmenubutton + group force_submenu + text @MENUS_HEAL + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 290 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight8 + } + mouseExit + { + hide smallhighlight8 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor healmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show heal_desc ; + } + } + + itemDef + { + name heal_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_lt_heal" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name heal_desc + text @MENUS_OF_EFFECT_JEDI_ONLY_NEFFECT + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 1 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // MIND TRICK FORCE DESC + + itemDef + { + name trickmenubutton + group force_submenu + text @MENUS_MIND_TRICK + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 305 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight9 + } + mouseExit + { + hide smallhighlight9 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor trickmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show mindtrick_desc ; + } + } + + itemDef + { + name mindtrick_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_lt_mind_trick" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name mindtrick_desc + text @MENUS_DURATION_VARIABLE_20 + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 1 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // TEAM HEAL FORCE DESC + + itemDef + { + name thealmenubutton + group force_submenu + text @MENUS_TEAM_HEAL + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 320 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight10 + } + mouseExit + { + hide smallhighlight10 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor thealmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show theal_desc ; + } + } + + itemDef + { + name theal_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_lt_healother" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name theal_desc + text @MENUS_OF_EFFECT_JEDI_ALLIES_NEFFECT + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // GRIP FORCE DESC + + itemDef + { + name gripmenubutton + group force_submenu + text @MENUS_GRIP + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 335 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight11 + } + mouseExit + { + hide smallhighlight11 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor gripmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show grip_desc ; + } + } + + itemDef + { + name grip_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_dk_grip" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name grip_desc + text @MENUS_DURATION_INSTANTANEOUS_NAREA + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // DRAIN FORCE DESC + + itemDef + { + name drainmenubutton + group force_submenu + text @MENUS_DRAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 350 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight12 + } + mouseExit + { + hide smallhighlight12 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor drainmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show drain_desc ; + } + } + + itemDef + { + name drain_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_dk_drain" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name drain_desc + text @MENUS_VARIABLE_NAREA_OF_EFFECT + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // LIGHTNING FORCE DESC + + itemDef + { + name lightmenubutton + group force_submenu + text @MENUS_LIGHTNING + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 365 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight13 + } + mouseExit + { + hide smallhighlight13 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor lightmenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show light_desc ; + } + } + + itemDef + { + name light_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_dk_l1" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group gamedesc + name light_desc + text @MENUS_OF_EFFECT_LIVING_PERSONS + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // RAGE FORCE DESC + + itemDef + { + name ragemenubutton + group force_submenu + text @MENUS_RAGE_1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 380 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight14 + } + mouseExit + { + hide smallhighlight14 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor ragemenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show rage_desc ; + } + } + + itemDef + { + name rage_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_dk_rage" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group gamedesc + name rage_desc + text @MENUS_DURATION_VARIABLE_10 + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // TEAM POWER FORCE DESC + + itemDef + { + name powermenubutton + group force_submenu + text @MENUS_TEAM_POWER_1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 395 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_FORCE + + mouseEnter + { + show smallhighlight15 + } + mouseExit + { + hide smallhighlight15 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor force_submenu forecolor 1 .682 0 1 ; + setitemcolor powermenubutton forecolor 1 1 1 1 ; + + show setup_background; + + hide gamedesc ; + show power_desc ; + } + } + + itemDef + { + name power_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/mp/NEW_f_icon_dk_forceother" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group gamedesc + name power_desc + text @MENUS_EFFECT_JEDI_ALLIES_NEFFECT + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/rules_games.menu b/base/ui/jamp/rules_games.menu new file mode 100644 index 0000000..8fe735c --- /dev/null +++ b/base/ui/jamp/rules_games.menu @@ -0,0 +1,963 @@ +//---------------------------------------------------------------------------------------------- +// RULES +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "rulesMenu_games" + fullScreen MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 0 + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + hide gamedesc ; + show ffa_desc ; + + setitemcolor game_submenu forecolor 1 .682 0 1; + setitemcolor ffamenubutton forecolor 1 1 1 1 ; + } + + onESC + { + play "sound/interface/button1.wav" ; + + close "rulesMenu_games" ; + open "rulesMenu" ; + } + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "PLAYER PROFILE" + itemDef + { + name profilebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show profilebutton_glow + } + mouseExit + { + hide profilebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open playerMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Credits hidden button + itemDef + { + name creditsbutton + group othermain +// text @CREDITS + descText @MENUS_SHOW_GAME_CREDITS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 200 144 256 256 + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textalignx 46 + backcolor 0 0 0 0 + forecolor 0.65 0.65 1 1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open creditsMenu + } + } + + // BACK button + itemDef + { + name backbutton + group fade_buttons + text @MENUS_BACK + descText @MENUS_BACKTORULES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 30 441 190 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/esc.wav" ; + close all ; + open rulesMenu + } + + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // SECOND ROW MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name game_rulesheader + group rulesheader + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_GAME_RULES_TITLE + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // Main highlights + itemDef + { + group mainhighlight + name mainhighlight1 + style WINDOW_STYLE_SHADER + rect 40 185 170 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight2 + style WINDOW_STYLE_SHADER + rect 40 215 170 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight3 + style WINDOW_STYLE_SHADER + rect 40 245 170 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight4 + style WINDOW_STYLE_SHADER + rect 40 275 170 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight5 + style WINDOW_STYLE_SHADER + rect 40 305 170 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight6 + style WINDOW_STYLE_SHADER + rect 40 335 170 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight7 + style WINDOW_STYLE_SHADER + rect 40 365 170 30 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // GAME RULES + // + //---------------------------------------------------------------------------------------------- + // ffa button + itemDef + { + name ffamenubutton + group game_submenu + text @MENUS_FFA_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 185 170 30 + font 2 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_FREE_FOR_ALL_GAME_RULES + + mouseEnter + { + show mainhighlight1 + } + mouseExit + { + hide mainhighlight1 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1; + setitemcolor ffamenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show ffa_desc ; + } + } + + + // ffa button + itemDef + { + name duelmenubutton + group game_submenu + text @MENUS_DUEL_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 215 170 30 + font 2 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DUEL_GAMETYPE + + mouseEnter + { + show mainhighlight2 + } + mouseExit + { + hide mainhighlight2 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1; + setitemcolor duelmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show duel_desc ; + } + } + + itemDef + { + name pduelmenubutton + group game_submenu + text @MENUS_POWERDUEL_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 245 170 30 + font 2 + textscale 0.8 + textalignx 170 + textaligny 5 + textstyle 0 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_POWERDUEL_GAME_RULES + + mouseEnter + { + show mainhighlight3 + } + mouseExit + { + hide mainhighlight3 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1; + setitemcolor pduelmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show pduel_desc ; + } + } + + itemDef + { + name teamffamenubutton + group game_submenu + text @MENUS_TEAM_FFA_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 275 170 30 + font 2 + textscale 0.8 + textalignx 170 + textaligny 5 + textstyle 0 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_TEAM_FFA_GAMETYPE + + mouseEnter + { + show mainhighlight4 + } + mouseExit + { + hide mainhighlight4 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1; + setitemcolor teamffamenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show teamffa_desc ; + } + } + + // force button + itemDef + { + name ctfmenubutton + group game_submenu + text @MENUS_CTF_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 305 170 30 + font 2 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CAPTURE_THE_FLAG_RULES + + mouseEnter + { + show mainhighlight5 + } + mouseExit + { + hide mainhighlight5 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1; + setitemcolor ctfmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show ctf_desc ; + } + } + + // force button + itemDef + { + name siegemenubutton + group game_submenu + text @MENUS_SIEGE_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 335 170 30 + font 2 + textscale 0.8 + textalignx 170 + textaligny 5 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_SIEGE_GAME_RULES + + mouseEnter + { + show mainhighlight6 + } + mouseExit + { + hide mainhighlight6 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor siegemenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show siege_desc ; + } + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 225 183 370 230 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + itemDef + { + group gamedesc + name duel_desc + text @MENUS_THE_GAME_OF_DUEL_IS_ALSO + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 187 360 225 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + group gamedesc + name teamffa_desc + text @MENUS_THIS_GAME_PITS_TWO_JEDI + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 187 360 225 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + group gamedesc + name ctf_desc + text @MENUS_IN_CAPTURE_THE_FLAG_CTF + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 187 360 225 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + group gamedesc + name siege_desc + text @MENUS_SIEGE_GAME_RULES_DESC + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 187 360 225 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + group gamedesc + name ffa_desc + text @MENUS_THIS_GAME_IS_BEST_SUMMED + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 187 360 225 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + group gamedesc + name pduel_desc + text @MENUS_POWERDUEL_GAME_RULES_DESC + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 187 360 225 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/rules_items.menu b/base/ui/jamp/rules_items.menu new file mode 100644 index 0000000..151a25a --- /dev/null +++ b/base/ui/jamp/rules_items.menu @@ -0,0 +1,1276 @@ +//---------------------------------------------------------------------------------------------- +// RULES +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "rulesMenu_items" + fullScreen MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 0 + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + hide gamedesc ; + show medpac_desc ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor medpacmenubutton forecolor 1 1 1 1 ; + } + + onESC + { + play "sound/interface/button1.wav" ; + + close "rulesMenu_items" ; + open "rulesMenu" ; + } + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "PLAYER PROFILE" + itemDef + { + name profilebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show profilebutton_glow + } + mouseExit + { + hide profilebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open playerMenu + } + } + + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Credits hidden button + itemDef + { + name creditsbutton + group othermain +// text @CREDITS + descText @MENUS_SHOW_GAME_CREDITS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 200 144 256 256 + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textalignx 46 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open creditsMenu + } + } + + // BACK button + itemDef + { + name backbutton + group fade_buttons + text @MENUS_BACK + descText @MENUS_BACKTORULES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 30 441 190 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/esc.wav" ; + close all ; + open rulesMenu + } + + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // SECOND ROW MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name game_rulesheader + group rulesheader + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_ITEM_RULES_TITLE + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // Main highlights + itemDef + { + group mainhighlight + name mainhighlight1 + style WINDOW_STYLE_SHADER + rect 40 185 170 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight2 + style WINDOW_STYLE_SHADER + rect 40 205 170 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight3 + style WINDOW_STYLE_SHADER + rect 40 225 170 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight4 + style WINDOW_STYLE_SHADER + rect 40 245 170 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight5 + style WINDOW_STYLE_SHADER + rect 40 265 170 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight6 + style WINDOW_STYLE_SHADER + rect 40 285 170 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight7 + style WINDOW_STYLE_SHADER + rect 40 305 170 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight8 + style WINDOW_STYLE_SHADER + rect 40 325 170 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group mainhighlight + name mainhighlight9 + style WINDOW_STYLE_SHADER + rect 40 345 170 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // ITEM RULES + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name medpacmenubutton + group game_submenu + text @MENUS_MEDPAC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 185 170 20 + font 2 + textscale 0.8 + textalignx 170 + textaligny -1 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_ITEM_TYPE + + mouseEnter + { + show mainhighlight1 + } + mouseExit + { + hide mainhighlight1 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor medpacmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show medpac_desc ; + } + } + + itemDef + { + name bactamenubutton + group game_submenu + text @MENUS_BACTA_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 205 170 20 + font 2 + textscale 0.8 + textalignx 170 + textaligny -1 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_ITEM_TYPE + + mouseEnter + { + show mainhighlight2 + } + mouseExit + { + hide mainhighlight2 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor bactamenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show bacta_desc ; + } + } + + itemDef + { + name seekermenubutton + group game_submenu + text @MENUS_SEEKER_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 225 170 20 + font 2 + textscale 0.8 + textalignx 170 + textaligny -1 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_ITEM_TYPE + + mouseEnter + { + show mainhighlight3 + } + mouseExit + { + hide mainhighlight3 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor seekermenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show seeker_desc ; + } + } + + itemDef + { + name sentrymenubutton + group game_submenu + text @MENUS_SENTRY_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 245 170 20 + font 2 + textscale 0.8 + textalignx 170 + textaligny -1 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_ITEM_TYPE + + mouseEnter + { + show mainhighlight4 + } + mouseExit + { + hide mainhighlight4 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor sentrymenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show sentry_desc ; + } + } + + itemDef + { + name ewebmenubutton + group game_submenu + text @MENUS_EWEB_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 265 170 20 + font 2 + textscale 0.8 + textalignx 170 + textaligny -1 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_ITEM_TYPE + + mouseEnter + { + show mainhighlight5 + } + mouseExit + { + hide mainhighlight5 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor ewebmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show eweb_desc ; + } + } + + itemDef + { + name stationarymenubutton + group game_submenu + text @MENUS_STATIONARY_SHIELD_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 285 170 20 + font 2 + textscale 0.8 + textalignx 170 + textaligny -1 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_ITEM_TYPE + + mouseEnter + { + show mainhighlight6 + } + mouseExit + { + hide mainhighlight6 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor stationarymenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show stationary_desc ; + } + } + + itemDef + { + name personalmenubutton + group game_submenu + text @MENUS_PERSONAL_SHIELD_ARMOR + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 305 170 20 + font 2 + textscale 0.8 + textalignx 170 + textaligny -1 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_ITEM_TYPE + + mouseEnter + { + show mainhighlight7 + } + mouseExit + { + hide mainhighlight7 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor personalmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show personal_desc ; + } + } + + itemDef + { + name cloakmenubutton + group game_submenu + text @MENUS_CLOAK_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 325 170 20 + font 2 + textscale 0.8 + textalignx 170 + textaligny -1 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_ITEM_TYPE + + mouseEnter + { + show mainhighlight8 + } + mouseExit + { + hide mainhighlight8 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor cloakmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show cloak_desc ; + } + } + + itemDef + { + name jetpackmenubutton + group game_submenu + text @MENUS_JETPACK_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 345 170 20 + font 2 + textscale 0.8 + textalignx 170 + textaligny -1 + textalign ITEM_ALIGN_RIGHT + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_ITEM_TYPE + + mouseEnter + { + show mainhighlight9 + } + mouseExit + { + hide mainhighlight9 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor game_submenu forecolor 1 .682 0 1 ; + setitemcolor jetpackmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show jetpack_desc ; + } + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 225 183 370 230 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + itemDef + { + name medpac_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/i_icon_medkit" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name medpac_desc + text @MENUS_THE_MEDPAC_IS_A_SMALL + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + name bacta_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 354 185 56 56 + background "gfx/hud/i_icon_bacta" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name bacta_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 410 185 56 56 + background "gfx/hud/i_icon_big_bacta" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name bacta_desc + text @MENUS_BACTA_CANISTERS_ARE_PORTABLE + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + name seeker_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/i_icon_seeker" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name seeker_desc + text @MENUS_AN_ATTACK_DRONE_SIMILAR + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + name sentry_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/i_icon_sentrygun" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name sentry_desc + text @MENUS_THIS_DEADLY_WEAPON_IS + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + name eweb_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/i_icon_eweb" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name eweb_desc + text @MENUS_EWEB_DESC + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + name stationary_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/i_icon_shieldwall" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name stationary_desc + text @MENUS_THIS_STATIONARY_ENERGY + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + name personal_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 360 185 120 56 + background "gfx/hud/i_icon_psd" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name personal_desc + text @MENUS_THE_PERSONAL_SHIELD_IS + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + name cloak_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/i_icon_cloak" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name cloak_desc + text @MENUS_CLOAK_DESC + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + itemDef + { + name jetpack_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/i_icon_jetpack" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name jetpack_desc + text @MENUS_JETPACK_DESC + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/rules_moves.menu b/base/ui/jamp/rules_moves.menu new file mode 100644 index 0000000..9c36e8f --- /dev/null +++ b/base/ui/jamp/rules_moves.menu @@ -0,0 +1,461 @@ +//---------------------------------------------------------------------------------------------- +// +// MP - Rules Moves +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "rulesMenu_moves" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onESC + { + play "sound/interface/esc.wav" + close "rulesMenu_moves" ; + open "rulesMenu" ; + } + + onOpen + { + setcvar "ui_movesflag" 0 + setcvar "ui_move_title" 2 + uiScript setMoveCharacter + uiScript setMovesListDefault + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// MOVES BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name game_rulesheader + group rulesheader + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_MOVES + rect 100 5 440 24 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// DATA +//---------------------------------------------------------------------------------------------- + itemDef + { + name move_titles_box + group none + style WINDOW_STYLE_FILLED + rect 25 47 255 23 + backcolor 0 0 .6 1 + visible 1 + decoration + } + itemDef + { + name move_titles_outline + group none + style WINDOW_STYLE_EMPTY + rect 24 68 255 30 + border 1 + bordersize 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + itemDef + { + name move_titles_title + type ITEM_TYPE_TEXT + rect 25 47 255 23 + text @MENUS_MOVE_TYPES + font 3 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 125 + visible 1 + decoration + } + + itemDef + { + name move_titles + group none + text " " + descText @MENUS_CHOOSE_MOVE_DESC + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 25 77 340 14 + font 2 + textscale .8 + textaligny -5 + textalign ITEM_ALIGN_LEFT + textstyle .8 + textalignx 0 + backcolor 0 0 0 0 + forecolor .615 .615 .956 1 + feeder 42 //FEEDER_MOVES_TITLES + cvar "ui_move_title" + cvarFloatList + { + "@MENUS_ACROBATICS" 0 + "@MENUS_SINGLE_FAST" 1 + "@MENUS_SINGLE_MEDIUM" 2 + "@MENUS_SINGLE_STRONG" 3 + "@MENUS_DUAL_SABERS" 4 + "@MENUS_SABER_STAFF" 5 + } + + + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 0 73 320 24 + } + mouseExit + { + hide button_glow + } + action + { + uiScript resetMovesList + play "sound/interface/button1.wav" + } + } + + itemDef + { + name moveslist_title_box + group none + style WINDOW_STYLE_FILLED + rect 26 110 253 23 + backcolor 0 0 .6 1 + visible 1 + decoration + } + + itemDef + { + name moveslist_title + type ITEM_TYPE_TEXT + rect 35 108 253 26 + text @MENUS_MOVES + font 3 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 125 + visible 1 + decoration + } + + itemDef + { + name moveslist + group none + rect 25 130 253 250 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 16 + font 2 + textscale 0.7 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder 41 + desctext @MENUS_MOVE_DESC2 + notselectable + visible 1 + columns 1 2 55 225 + mouseEnter + { + setitemcolor moveslist bordercolor 1 1 1 1 + } + mouseExit + { + setitemcolor moveslist bordercolor 0 0 .8 1 + } + doubleclick + { + play sound/interface/button1.wav + } + } + + itemDef + { + name character_box + group none + style WINDOW_STYLE_EMPTY + rect 285 38 330 370 + border 1 + bordersize 2 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + itemDef + { + name character + group models + type ITEM_TYPE_MODEL + rect 295 -30 300 340 + + model_g2anim "BOTH_ROLL_F" + asset_model "ui_char_model" + model_angle 180 + model_g2mins -20 -15 -10 + model_g2maxs 20 15 45 + model_rotation 50 + model_fovx 50 + model_fovy 50 + isCharacter 1 + isSaber 1 + visible 1 + decoration + + } + + itemDef + { + name item_desc + type ITEM_TYPE_TEXTSCROLL + rect 295 300 320 90 + text " " + cvar ui_move_desc + font 4 + forecolor .549 .854 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 6 + visible 1 + lineHeight 13 + + } + + +//---------------------------------------------------------------------------------------------- +// OTHER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Credits hidden button + itemDef + { + name creditsbutton + group othermain +// text @CREDITS + descText @MENUS_SHOW_GAME_CREDITS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 200 144 256 256 + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textalignx 46 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open creditsMenu + } + } + + // BACK button + itemDef + { + name backbutton + group fade_buttons + text @MENUS_BACK + descText @MENUS_BACKTORULES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 30 441 190 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/esc.wav" ; + close all ; + open rulesMenu + } + + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + } +} diff --git a/base/ui/jamp/rules_weapons.menu b/base/ui/jamp/rules_weapons.menu new file mode 100644 index 0000000..a864aa7 --- /dev/null +++ b/base/ui/jamp/rules_weapons.menu @@ -0,0 +1,1612 @@ +//---------------------------------------------------------------------------------------------- +// RULES +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "rulesMenu_weapons" + fullScreen MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 0 + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + hide gamedesc ; + show stun_desc ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor stunmenubutton forecolor 1 1 1 1 ; + } + + onESC + { + play "sound/interface/button1.wav" ; + + close "rulesMenu_weapons" ; + open "rulesMenu" ; + } + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open multiplayermenu + } + } + + // Big button "PLAYER PROFILE" + itemDef + { + name profilebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show profilebutton_glow + } + mouseExit + { + hide profilebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open playerMenu + } + } + + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setup_menu + } + } + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Credits hidden button + itemDef + { + name creditsbutton + group othermain +// text @CREDITS + descText @MENUS_SHOW_GAME_CREDITS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 200 144 256 256 + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textalignx 46 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open creditsMenu + } + } + + // BACK button + itemDef + { + name backbutton + group fade_buttons + text @MENUS_BACK + descText @MENUS_BACKTORULES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 30 441 190 30 + } + mouseExit + { + hide button_glow + } + + action + { + play "sound/interface/esc.wav" ; + close all ; + open rulesMenu + } + + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // SECOND ROW MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name rules_title + group none + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_WEAPON_RULES_TITLE + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 225 183 370 230 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 1 + decoration + } + + // Main small highlights + itemDef + { + group smallhighlight + name smallhighlight1 + style WINDOW_STYLE_SHADER + rect 40 185 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight2 + style WINDOW_STYLE_SHADER + rect 40 200 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight3 + style WINDOW_STYLE_SHADER + rect 40 215 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight4 + style WINDOW_STYLE_SHADER + rect 40 230 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight5 + style WINDOW_STYLE_SHADER + rect 40 245 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight6 + style WINDOW_STYLE_SHADER + rect 40 260 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight7 + style WINDOW_STYLE_SHADER + rect 40 275 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight8 + style WINDOW_STYLE_SHADER + rect 40 290 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight9 + style WINDOW_STYLE_SHADER + rect 40 305 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight10 + style WINDOW_STYLE_SHADER + rect 40 320 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight11 + style WINDOW_STYLE_SHADER + rect 40 335 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight12 + style WINDOW_STYLE_SHADER + rect 40 350 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight13 + style WINDOW_STYLE_SHADER + rect 40 365 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight14 + style WINDOW_STYLE_SHADER + rect 40 380 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group smallhighlight + name smallhighlight15 + style WINDOW_STYLE_SHADER + rect 40 395 170 15 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// WEAPON RULES +// +//---------------------------------------------------------------------------------------------- + + // LIGHT SABER FORCE DESC + + itemDef + { + name sabermenubutton + group weapon_submenu + text @MENUS_LIGHTSABER_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 185 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + + mouseEnter + { + show smallhighlight1 + } + mouseExit + { + hide smallhighlight1 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor sabermenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show saber_desc ; + } + } + + itemDef + { + name saber_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_lightsaber" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name saber_desc + text @MENUS_AN_ELEGANT_WEAPON_FOR + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + lineHeight 15 + maxLineChars 45 + } + + // BLASTER PISTOL + + itemDef + { + name pistolmenubutton + group weapon_submenu + text @MENUS_BLASTER_PISTOL + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 200 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + + mouseEnter + { + show smallhighlight2 + } + mouseExit + { + hide smallhighlight2 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor pistolmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show pistol_desc ; + } + } + + itemDef + { + name pistol_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_blaster_pistol" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name pistol_desc + text @MENUS_BLASTER_PISTOL_DESC + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + lineHeight 15 + maxLineChars 45 + } + + // BLASTER RIFLE DESC + + itemDef + { + name briflemenubutton + group weapon_submenu + text @MENUS_BLASTER_RIFLE + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 215 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + + mouseEnter + { + show smallhighlight3 + } + mouseExit + { + hide smallhighlight3 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor briflemenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show brifle_desc ; + } + } + + itemDef + { + name brifle_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_blaster" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name brifle_desc + text @MENUS_THE_PRIMARY_WEAPON_OF + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // DISTRUPTER RIFLE DESC + + itemDef + { + name driflemenubutton + group weapon_submenu + text @MENUS_DISRUPTOR + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 230 170 15 + font 2 + textscale .8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + visible 1 + + mouseEnter + { + show smallhighlight4 + } + mouseExit + { + hide smallhighlight4 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor driflemenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show drifle_desc ; + } + } + + itemDef + { + name drifle_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_disruptor" + forecolor 1 1 1 1 + decoration + } + + itemDef + { + name drifle_desc + group gamedesc + text @MENUS_THIS_NEFARIOUS_WEAPON + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // BOW CASTER DESC + + itemDef + { + name bowcastmenubutton + group weapon_submenu + text @MENUS_BOWCASTER_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 245 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + visible 1 + + mouseEnter + { + show smallhighlight5 + } + mouseExit + { + hide smallhighlight5 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor bowcastmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show bowcast_desc ; + } + } + + itemDef + { + name bowcast_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_bowcaster" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name bowcast_desc + text @MENUS_THIS_ARCHAIC_LOOKING + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // REPEATER + + itemDef + { + name repeatermenubutton + group weapon_submenu + text @MENUS_REPEATER + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 260 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 0 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + visible 1 + + mouseEnter + { + show smallhighlight6 + } + mouseExit + { + hide smallhighlight6 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor repeatermenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show repeater_desc ; + } + } + + itemDef + { + name repeater_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_repeater" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name repeater_desc + text @MENUS_THIS_DESTRUCTIVE_PROJECTILE + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // DEMP 2 + + itemDef + { + name demp2menubutton + group weapon_submenu + text @MENUS_DEMP_2_1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 275 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + + mouseEnter + { + show smallhighlight7 + } + mouseExit + { + hide smallhighlight7 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor demp2menubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show demp2_desc ; + } + } + + itemDef + { + name demp2_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_demp2" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name demp2_desc + text @MENUS_COMMONLY_REFERRED_TO + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // Flechette + + itemDef + { + name flechmenubutton + group weapon_submenu + text @MENUS_FLECHETTE_1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 290 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + + mouseEnter + { + show smallhighlight8 + } + mouseExit + { + hide smallhighlight8 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor flechmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show flechette_desc ; + } + } + + itemDef + { + name flechette_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_flechette" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name flechette_desc + text @MENUS_WIDELY_USED_BY_THE_CORPORATE + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // Concussion Rifle + + itemDef + { + name concmenubutton + group weapon_submenu + text @MENUS_CONC_RIFLE + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 305 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + + mouseEnter + { + show smallhighlight9 + } + mouseExit + { + hide smallhighlight9 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor concmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show conc_desc ; + } + } + + itemDef + { + name conc_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_c_rifle" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name conc_desc + text @MENUS_CONC_RIFLE_DESC + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // MERRSONN + + itemDef + { + name merrmenubutton + group weapon_submenu + text @MENUS_MERRSONN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 320 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + + mouseEnter + { + show smallhighlight10 + } + mouseExit + { + hide smallhighlight10 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor merrmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show merr_desc ; + } + } + + itemDef + { + name merr_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_merrsonn" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name merr_desc + text @MENUS_THE_PLX_2M_IS_AN_EXTREMELY + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // THERMAL + + itemDef + { + name thermmenubutton + group weapon_submenu + text @MENUS_THERMAL_DETONATOR + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 335 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + + mouseEnter + { + show smallhighlight11 + } + mouseExit + { + hide smallhighlight11 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor thermmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show therm_desc ; + } + } + + itemDef + { + name therm_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_thermal" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name therm_desc + text @MENUS_THE_THERMAL_DETONATOR + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // TRIP MINE + + itemDef + { + name tripmenubutton + group weapon_submenu + text @MENUS_TRIP_MINE + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 350 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + + mouseEnter + { + show smallhighlight12 + } + mouseExit + { + hide smallhighlight12 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor tripmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show trip_desc ; + } + } + + itemDef + { + name trip_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_tripmine" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + group gamedesc + name trip_desc + text @MENUS_TRIP_MINES_CONSIST_OF + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + + // DETPACK + + itemDef + { + name detpackmenubutton + group weapon_submenu + text @MENUS_DETPACK + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 40 365 170 15 + font 2 + textscale 0.8 + textalignx 170 + textaligny -5 + textstyle 3 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DESCRIPTION_OF_A_WEAPON + + mouseEnter + { + show smallhighlight13 + } + mouseExit + { + hide smallhighlight13 + } + action + { + play "sound/interface/button1.wav" ; + + setitemcolor weapon_submenu forecolor 1 .682 0 1 ; + setitemcolor detpackmenubutton forecolor 1 1 1 1 ; + + hide gamedesc ; + show detpack_desc ; + } + } + + itemDef + { + name detpack_desc + group gamedesc + style WINDOW_STYLE_SHADER + rect 382 185 56 56 + background "gfx/hud/w_icon_detpack" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + group gamedesc + name detpack_desc + text @MENUS_A_DETONATION_PACK_IS + type ITEM_TYPE_TEXTSCROLL + style WINDOW_STYLE_EMPTY + visible 0 + rect 235 254 360 148 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textaligny 12 + backcolor 0 0 1 0 + forecolor .549 .854 1 1 + + lineHeight 15 + maxLineChars 45 + } + } +} + diff --git a/base/ui/jamp/saber.menu b/base/ui/jamp/saber.menu new file mode 100644 index 0000000..9c6271b --- /dev/null +++ b/base/ui/jamp/saber.menu @@ -0,0 +1,1381 @@ +//---------------------------------------------------------------------------------------------- +// +// MP SABER CREATION MENU - called from main menu at the the start of a new game, +// and also when player is allowed to upgrade is style and choose a new saber +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "saberMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript setsaberboxesandhilts + uiScript "getsabercvars" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 + uiScript getsaberhiltinfo + } + + onESC + { + play "sound/interface/menuroam.wav" + uiScript "updatesabercvars" + close saberMenu + open characterMenu + } + + +//---------------------------------------------------------------------------------------------- +// +// MENU BACKGROUND +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/sabermenu_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box1 + group none + style WINDOW_STYLE_SHADER + rect 4 66 219 165 + background "gfx/menus/sabermenu_box" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box2top + group none + style WINDOW_STYLE_SHADER + rect 212 66 219 60 + background "gfx/menus/sabermenu_box_top" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name box2middle + group none + style WINDOW_STYLE_SHADER + rect 212 126 219 0 + background "gfx/menus/sabermenu_box_middle" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name box2bottom + group none + style WINDOW_STYLE_SHADER + rect 212 126 219 60 + background "gfx/menus/sabermenu_box_bottom" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name box3top + group none + style WINDOW_STYLE_SHADER + rect 418 66 219 60 + background "gfx/menus/sabermenu_box_top" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box3middle + group none + style WINDOW_STYLE_SHADER + rect 418 126 219 0 + background "gfx/menus/sabermenu_box_middle" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box3bottom + group none + style WINDOW_STYLE_SHADER + rect 418 126 219 60 + background "gfx/menus/sabermenu_box_bottom" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// TOP MAIN MENU BUTTONS +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 16 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 7 14 130 30 + } + mouseExit + { + hide button_glow + } + action + { + close all ; + open multiplayermenu + } + } + +// Big button "PROFILE" + itemDef + { + name profilebutton + group lbut + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 16 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + } + mouseExit + { + } + action + { + } + } + + // Big button "CONTROLS" + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 16 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 340 14 130 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "updatesabercvars" + close all + open controlsMenu + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 16 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 502 14 130 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "updatesabercvars" + close all + open setupMenu + } + } + + +//---------------------------------------------------------------------------------------------- +// +// OTHER MAIN MENU BUTTONS +// +//---------------------------------------------------------------------------------------------- + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3" + uiScript "updatesabercvars" + close all + open quitMenu + } + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 -60 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 -60 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //---------------------------------------------------------------------------------------------- + // + // SABER MENU specific stuff + // + //---------------------------------------------------------------------------------------------- + + // CREATION title + itemDef + { + name creation_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_LIGHTSABER_CREATION + rect 100 54 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// SABER TYPE BUTTONS (standard, dual, two handed) +//---------------------------------------------------------------------------------------------- + itemDef + { + name typebut + group none + text @MENUS_SABER_TYPE + descText @MENUS_SABER_TYPE_DESC + style WINDOW_STYLE_EMPTY + rect 32 96 160 24 + font 3 + textscale 1 + textstyle 0 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name typebut_single_glow + group none + style WINDOW_STYLE_SHADER + rect 32 132 180 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + + //cvarTest ui_saber_type + //showCvar { "single" } + } + + itemDef + { + name typebut_dual_glow + group none + style WINDOW_STYLE_SHADER + rect 32 152 180 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + + //cvarTest ui_saber_type + //showCvar { "dual" } + } + + itemDef + { + name typebut_staff_glow + group none + style WINDOW_STYLE_SHADER + rect 32 172 180 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + + //cvarTest ui_saber_type + //showCvar { "staff" } + } + + itemDef + { + name typebut_single + group none + text @MENUS_SINGLESABER + descText @MENUS_SINGLESABER_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 32 132 160 16 + font 4 + textscale 1 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + mouseEnter + { + show typebut_single_glow + } + mouseExit + { + hide typebut_single_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "single" + uiScript "saber_type" + setcvar ui_saber "single_1" + setcvar ui_saber2 "none" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor 1 1 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 +// transition2 box2middle 212 126 219 0 20 5 +// transition2 box2bottom 212 126 219 60 20 5 + transition2 box3middle 418 126 219 0 20 5 + transition2 box3bottom 418 126 219 60 20 5 + show sabstyle + transition2 saber 12 "-80" 615 615 20 10 + } + } + + itemDef + { + name typebut_dual + group none + text @MENUS_DUALSABERS + descText @MENUS_DUALSABERS_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 32 152 160 16 + font 4 + textscale 1 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + mouseEnter + { + show typebut_dual_glow + } + mouseExit + { + hide typebut_dual_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "dual" + uiScript "saber_type" + setcvar ui_saber "single_1" + setcvar ui_saber2 "single_1" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor 1 1 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 +// transition2 box2middle 212 126 219 44 20 5 +// transition2 box2bottom 212 170 219 60 20 5 + transition2 box3middle 418 126 219 44 20 5 + transition2 box3bottom 418 170 219 60 20 5 + hide sabstyle + transition2 saber 12 "-130" 615 615 20 5 + } + } + + itemDef + { + name typebut_staff + group none + text @MENUS_SABERSTAFF + descText @MENUS_SABERSTAFF_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 32 172 160 16 + font 4 + textscale 1 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + mouseEnter + { + show typebut_staff_glow + } + mouseExit + { + hide typebut_staff_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "staff" + uiScript "saber_type" + setcvar ui_saber "dual_1" + setcvar ui_saber2 "none" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor 1 1 1 1 +// transition2 box2middle 212 126 219 44 20 5 +// transition2 box2bottom 212 170 219 60 20 5 + transition2 box3middle 418 126 219 44 20 5 + transition2 box3bottom 418 170 219 60 20 5 + hide sabstyle + transition2 saber 12 "-80" 615 615 20 10 + } + } + +//---------------------------------------------------------------------------------------------- +//HILTS +//---------------------------------------------------------------------------------------------- + itemDef + { + name hilttype + group none + text @MENUS_HILT1 + descText @MENUS_HILT1_DESC + style WINDOW_STYLE_EMPTY + rect 240 80 160 24 + font 3 + textscale .7 + textstyle 0 + textalignx 80 + textaligny 0 + textalign ITEM_ALIGN_CENTER + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // HILT BUTTON 1 - SINGLE + itemDef + { + name hiltbut + group none + rect 240 95 160 120 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_EMPTY + elementwidth 120 + elementheight 16 + font 4 + textaligny 16 + textscale 1 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SABER_SINGLE_INFO + +// text @MENUS_HILT1 + descText @MENUS_HILT1_DESC + elementtype LISTBOX_TEXT + textalign ITEM_ALIGN_LEFT + + cvarTest ui_saber_type + hideCvar { "staff" ; "dual" } + + visible 1 + action + { + play "sound/interface/choose_hilt.wav" + uiScript "setscreensaberhilt" + uiScript "saber_hilt" + } + } + + // HILT BUTTON 1 - STAVES + itemDef + { + name hiltbut_staves + group none + rect 240 95 160 120 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_EMPTY + elementwidth 120 + elementheight 16 + font 4 + textaligny 16 + textscale 1 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SABER_STAFF_INFO + + descText @MENUS_HILT1_DESC + + cvarTest ui_saber_type + hideCvar { "single"; "dual" } + + visible 1 + action + { + play "sound/interface/choose_hilt.wav" + uiScript "setscreensaberstaff" + uiScript "saber_hilt" + } + } + + itemDef + { + name hilttype + group none + text @MENUS_HILT2 + descText @MENUS_HILT2_DESC + style WINDOW_STYLE_EMPTY + rect 240 150 160 24 + font 3 + textscale .7 + textstyle 0 + textalignx 80 + textaligny 0 + textalign ITEM_ALIGN_CENTER + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + visible 1 + decoration + } + + // HILT BUTTON 1 - DUAL + itemDef + { + name hiltbut1 + group none + rect 240 95 160 54 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_EMPTY + elementwidth 120 + elementheight 16 + font 4 + textaligny 16 + textscale 1 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SABER_SINGLE_INFO + +// text @MENUS_HILT1 + descText @MENUS_HILT1_DESC + elementtype LISTBOX_TEXT + textalign ITEM_ALIGN_LEFT + + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + + visible 1 + action + { + play "sound/interface/choose_hilt.wav" + uiScript "setscreensaberhilt1" + uiScript "saber_hilt" + } + } + + // HILT BUTTON 2 - DUAL + itemDef + { + name hiltbut2 + group none + + rect 240 165 160 54 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_EMPTY + elementwidth 120 + elementheight 16 + font 4 + textaligny 16 + textscale 1 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SABER_SINGLE_INFO + + +// text @MENUS_HILT2 + descText @MENUS_HILT2_DESC + forecolor .615 .615 .956 1 + + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + + visible 1 + action + { + play "sound/interface/choose_hilt.wav" + uiScript "setscreensaberhilt2" + uiScript "saber2_hilt" + } + } + +//---------------------------------------------------------------------------------------------- +//BLADE COLORS +//---------------------------------------------------------------------------------------------- + itemDef + { + name color_title + group none + text @MENUS_BLADE_COLOR + descText @MENUS_BLADE_COLOR_DESC + style WINDOW_STYLE_EMPTY + rect 446 96 160 24 + font 3 + textscale 1 + textstyle 0 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name blueicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 446 124 24 24 + background "gfx/menus/saber_icon_blue" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor blueicon forecolor 1 1 1 1 + setitemcolor blueicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor blueicon forecolor .75 .75 .75 1 + setitemcolor blueicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "blue" + } + } + + itemDef + { + name greenicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 473 124 24 24 + background "gfx/menus/saber_icon_green" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor greenicon forecolor 1 1 1 1 + setitemcolor greenicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor greenicon forecolor .75 .75 .75 1 + setitemcolor greenicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "green" + } + } + + itemDef + { + name orangeicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 500 124 24 24 + background "gfx/menus/saber_icon_orange" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor orangeicon forecolor 1 1 1 1 + setitemcolor orangeicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor orangeicon forecolor .75 .75 .75 1 + setitemcolor orangeicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "orange" + } + } + + itemDef + { + name purpleicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 527 124 24 24 + background "gfx/menus/saber_icon_purple" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor purpleicon forecolor 1 1 1 1 + setitemcolor purpleicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor purpleicon forecolor .75 .75 .75 1 + setitemcolor purpleicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "purple" + } + } + + itemDef + { + name yellowicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 554 124 24 24 + background "gfx/menus/saber_icon_yellow" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor yellowicon forecolor 1 1 1 1 + setitemcolor yellowicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor yellowicon forecolor .75 .75 .75 1 + setitemcolor yellowicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "yellow" + } + } + + itemDef + { + name redicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 581 124 24 24 + background "gfx/menus/saber_icon_red" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor redicon forecolor 1 1 1 1 + setitemcolor redicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor redicon forecolor .75 .75 .75 1 + setitemcolor redicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber_color "red" + } + } + + // COLOR 2 BUTTON + itemDef + { + name colorbut2 + group none + text @MENUS_COLOR2 + descText @MENUS_COLOR2_DESC + //type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 446 152 160 16 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -4 + forecolor .549 .854 1 1 + visible 1 + decoration + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + } + + itemDef + { + name blueicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 446 170 24 24 + background "gfx/menus/saber_icon_blue" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor blueicon2 forecolor 1 1 1 1 + setitemcolor blueicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor blueicon2 forecolor .75 .75 .75 1 + setitemcolor blueicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "blue" + } + } + + itemDef + { + name greenicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 473 170 24 24 + background "gfx/menus/saber_icon_green" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor greenicon2 forecolor 1 1 1 1 + setitemcolor greenicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor greenicon2 forecolor .75 .75 .75 1 + setitemcolor greenicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "green" + } + } + + itemDef + { + name orangeicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 500 170 24 24 + background "gfx/menus/saber_icon_orange" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor orangeicon2 forecolor 1 1 1 1 + setitemcolor orangeicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor orangeicon2 forecolor .75 .75 .75 1 + setitemcolor orangeicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "orange" + } + } + + itemDef + { + name purpleicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 527 170 24 24 + background "gfx/menus/saber_icon_purple" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor purpleicon2 forecolor 1 1 1 1 + setitemcolor purpleicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor purpleicon2 forecolor .75 .75 .75 1 + setitemcolor purpleicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "purple" + } + } + + itemDef + { + name yellowicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 554 170 24 24 + background "gfx/menus/saber_icon_yellow" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor yellowicon2 forecolor 1 1 1 1 + setitemcolor yellowicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor yellowicon2 forecolor .75 .75 .75 1 + setitemcolor yellowicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "yellow" + } + } + + itemDef + { + name redicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 581 170 24 24 + background "gfx/menus/saber_icon_red" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor redicon2 forecolor 1 1 1 1 + setitemcolor redicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor redicon2 forecolor .75 .75 .75 1 + setitemcolor redicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + setcvar ui_saber2_color "red" + } + } + + + +//////////////////////// +//SABER MODELS +//////////////////////// + + //FIRST SABER + itemDef + { + name saber + group models + type ITEM_TYPE_MODEL + rect 12 -80 615 615 + asset_model "models/weapons2/saber_reborn/saber_w.glm" + isSaber 1 + model_angle 180 + model_rotation 20 + model_g2mins 0 0 0 + model_g2maxs 20 20 20 + model_fovx 75 + model_fovy 75 + visible 1 + decoration + } + + //SECOND SABER + itemDef + { + name saber2 + group models + type ITEM_TYPE_MODEL + rect 12 -50 615 615 + asset_model "models/weapons2/saber_reborn/saber_w.glm" + isSaber2 1 + model_angle 180 + model_rotation 20 + model_g2mins 0 0 0 + model_g2maxs 20 20 20 + model_fovx 75 + model_fovy 75 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + + decoration + } + + //APPLY BUTTON + /*itemDef + { + name applybutton_glow + group none + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name apply + group none + text @MENUS_APPLY + descText @MENUS_APPLY + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + action + { + uiScript "updatesabercvars" + play "sound/interface/button1.wav" + close all + open mainMenu + } + mouseEnter + { + show applybutton_glow + } + mouseExit + { + hide applybutton_glow + } + }*/ + + //BEGIN GAME BUTTON + itemDef + { + name begingamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgame_begin + group none + text @MENUS_APPLY + descText @MENUS_APPLY_SABER_CHANGES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 455 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + action + { + uiScript "updatesabercvars" + close sabermenu + open main + } + mouseEnter + { + show begingamebutton_glow + } + mouseExit + { + hide begingamebutton_glow + } + } + } +} diff --git a/base/ui/jamp/serverinfo.menu b/base/ui/jamp/serverinfo.menu new file mode 100644 index 0000000..1ddeafb --- /dev/null +++ b/base/ui/jamp/serverinfo.menu @@ -0,0 +1,179 @@ +//---------------------------------------------------------------------------------------------- +// +// SERVER INFORMATION POPUP MENU +// +// List information pertinant to one server +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "serverinfo_popmenu" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 140 60 360 360 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 320 + descY 400 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + popup + + onOpen + { + uiScript ServerStatus + } + + onESC + { + play "sound/interface/esc.wav" ; + close serverinfo_popmenu + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 0 0 360 360 + backcolor 0 0 .35 .9 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 1 + decoration + } + + //--------------------------------------------- + // TITLE + //--------------------------------------------- + // title + itemDef + { + name screenTitle + text @MENUS_SERVER_INFORMATION + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + rect 10 5 340 20 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 170 + textaligny 1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + //--------------------------------------------- + // SERVER INFO + //--------------------------------------------- + itemDef + { + name serverinfoList + rect 10 27 340 280 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + feeder FEEDER_SERVERSTATUS + elementtype LISTBOX_TEXT + elementwidth 120 + elementheight 16 + font 4 + textscale 1 + backcolor 0 0 0 0.5 + border 1 + bordersize 1 + bordercolor .5 .5 .5 1 + notselectable + visible 1 + columns 4 2 40 148 + 50 40 50 + 100 40 45 + 150 40 165 + } + +//---------------------------------------------------------------------------------------------- +// BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name refresh + text @MENUS_REFRESH_LIST + descText @MENUS_REFRESH_SERVER_LIST + type 1 + font 3 + textscale .8 + style WINDOW_STYLE_FILLED + rect 10 310 170 30 + textalign ITEM_ALIGN_CENTER + textalignx 85 + textaligny 5 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + uiScript ServerStatus + } + mouseEnter + { + show button_glow + setitemrect button_glow 0 310 200 28 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name doneText + text @MENUS_DONE_CAPS + descText @MENUS_DONE_DESC + type 1 + font 3 + textscale .8 + style WINDOW_STYLE_FILLED + rect 180 310 170 30 + textalign ITEM_ALIGN_CENTER + textalignx 85 + textaligny 5 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + close serverinfo_popmenu + } + mouseEnter + { + show button_glow + setitemrect button_glow 170 310 200 28 + } + mouseExit + { + hide button_glow + } + } + } +} diff --git a/base/ui/jamp/setup.menu b/base/ui/jamp/setup.menu new file mode 100644 index 0000000..0dfae73 --- /dev/null +++ b/base/ui/jamp/setup.menu @@ -0,0 +1,2959 @@ +//-------------------------------------------------------------- +// +// MULTIPLAYER SETUP MENU +// +//-------------------------------------------------------------- +{ + menuDef + { + name "setup_menu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 0 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + appearanceIncrement 75 // In miliseconds + descX 320 + descY 424 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript getvideosetup // Get video settings + + show setup_background + show video + hide applyChanges + hide video2 + hide vidrestart + hide sound + hide options + hide mods + hide defaults + hide highlights + + setitemcolor setup_submenu forecolor 1 .682 0 1 + setitemcolor video1menubutton forecolor 1 1 1 1 + } + + onESC + { + play "sound/interface/button1.wav" + + defer VideoSetup videowarningMenu + + close all + open mainMenu + } + +//---------------------------------------------------------------------------------------------- +// +// MENU BACKGROUND +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// TOP MENU BUTTONS +// +//---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newgamebutton_glow + group topbut + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_PLAY + descText @MENUS_START_PLAYING_NOW + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show newgamebutton_glow + } + mouseExit + { + hide newgamebutton_glow + } + action + { + play "sound/interface/button1.wav" + + defer VideoSetup videowarningMenu + + close all ; + open multiplayermenu + } + } + + itemDef + { + name profilebutton_glow + group topbut + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name profilebutton + group toprow + text @MENUS_PROFILE + descText @MENUS_PROFILE_DESC + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show profilebutton_glow + } + mouseExit + { + hide profilebutton_glow + } + action + { + play "sound/interface/button1" + + defer VideoSetup videowarningMenu + + close all + open playerMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group topbut + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS2 + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1" + + defer VideoSetup videowarningMenu + + close all + open controlsMenu + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group topbut + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 1 1 1 + visible 1 + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + } + } + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Credits hidden button + itemDef + { + name creditsbutton + group othermain +// text @CREDITS + descText @MENUS_SHOW_GAME_CREDITS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 200 144 256 256 + font 2 + textscale 1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textalignx 46 + backcolor 0 0 0 0 + forecolor 0.65 0.65 1 1 + visible 0 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open creditsMenu + } + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_LEAVE_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff" + + defer VideoSetup videowarningMenu + + close all + open quitMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // SECOND ROW MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // Setup Options title + itemDef + { + name setup_title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_SETUP_OPTIONS + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // video1 button + itemDef + { + name video1button_glow + group highlights + style WINDOW_STYLE_SHADER + rect 80 185 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name video1menubutton + group setup_submenu + text @MENUS_VIDEO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 185 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textstyle 0 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_VIDEO_SETTINGS + + mouseEnter + { + show video1button_glow + } + mouseExit + { + hide video1button_glow + } + action + { + play "sound/interface/button1.wav" ; + + defer VideoSetup videowarningMenu ; + + uiScript getvideosetup ; // Get video settings + + show setup_background ; + show video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide mods ; + hide defaults ; + + setitemcolor setup_submenu forecolor 1 .682 0 1; + setitemcolor video1menubutton forecolor 1 1 1 1 ; + } + } + + // video2 button + itemDef + { + name video2button_glow + group highlights + style WINDOW_STYLE_SHADER + rect 80 209 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name video2menubutton + group setup_submenu + text @MENUS_MORE_VIDEO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 209 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGUE_MORE_VIDEO_SETTINGS + + mouseEnter + { + show video2button_glow + } + mouseExit + { + hide video2button_glow + } + action + { + play "sound/interface/button1.wav" ; + + defer VideoSetup videowarningMenu ; + + show setup_background ; + hide video ; + hide applyChanges ; + show video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide mods ; + hide defaults ; + + setitemcolor setup_submenu forecolor 1 .682 0 1 ; + setitemcolor video2menubutton forecolor 1 1 1 1 ; + } + } + + // sound button + itemDef + { + name soundbutton_glow + group highlights + style WINDOW_STYLE_SHADER + rect 80 233 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name soundmenubutton + group setup_submenu + text @MENUS_SOUND + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 233 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_SOUND_SETTINGS + + mouseEnter + { + show soundbutton_glow + } + mouseExit + { + hide soundbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + + defer VideoSetup videowarningMenu ; + + show setup_background ; + hide video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + show sound ; + hide options ; + hide mods ; + hide defaults ; + + setitemcolor setup_submenu forecolor 1 .682 0 1 ; + setitemcolor soundmenubutton forecolor 1 1 1 1 ; + } + } + + // gameoptions button + itemDef + { + name gameoptionsbutton_glow + group highlights + style WINDOW_STYLE_SHADER + rect 80 257 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name gameoptionmenubutton + group setup_submenu + text @MENUS_OPTIONS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 257 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_GAME_OPTIONS + + mouseEnter + { + show gameoptionsbutton_glow + } + mouseExit + { + hide gameoptionsbutton_glow + } + action + { + play "sound/interface/button1.wav" + + defer VideoSetup videowarningMenu + show setup_background + hide video + hide applyChanges + hide video2 + hide vidrestart + hide sound + show options + hide langapply + hide mods + hide defaults + + setitemcolor setup_submenu forecolor 1 .682 0 1 ; + setitemcolor gameoptionmenubutton forecolor 1 1 1 1 ; + } + } + + // mods button + itemDef + { + name modsbutton_glow + group highlights + style WINDOW_STYLE_SHADER + rect 80 281 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name modsmenubutton + group setup_submenu + text @MENUS_MODS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 281 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_GAME_OPTIONS + + mouseEnter + { + show modsbutton_glow + } + mouseExit + { + hide modsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; // This sound may be cut out by the loadmods, so we play it later too. + + defer VideoSetup videowarningMenu ; + uiScript loadMods ; + + show setup_background ; + hide video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + show mods ; + hide defaults ; + + setitemcolor setup_submenu forecolor 1 .682 0 1 ; + setitemcolor modsmenubutton forecolor 1 1 1 1 ; + } + } + + // gamedefaults button + itemDef + { + name gamedefaultsbutton_glow + group highlights + style WINDOW_STYLE_SHADER + rect 80 305 180 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + // appearance_slot 1 + decoration + } + + itemDef + { + name gamedefaultsmenubutton + group setup_submenu + text @MENUS_DEFAULTS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 305 170 24 + font 3 + textscale 0.9 + textalignx 160 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 0 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_RESTORE_DEFAULT_SETTINGS + + mouseEnter + { + show gamedefaultsbutton_glow + } + mouseExit + { + hide gamedefaultsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + + defer VideoSetup videowarningMenu ; + + show setup_background ; + hide video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide mods ; + show defaults ; + + setitemcolor setup_submenu forecolor 1 .682 0 1 ; + setitemcolor gamedefaultsmenubutton forecolor 1 1 1 1 ; + } + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 260 185 340 225 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } +//---------------------------------------------------------------------------------------------- +// +// HIGHLIGHT BARS +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name highlight1 + group highlights + style WINDOW_STYLE_SHADER + rect 260 190 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight2 + group highlights + style WINDOW_STYLE_SHADER + rect 260 204 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight3 + group highlights + style WINDOW_STYLE_SHADER + rect 260 218 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight4 + group highlights + style WINDOW_STYLE_SHADER + rect 260 232 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight5 + group highlights + style WINDOW_STYLE_SHADER + rect 260 246 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight6 + group highlights + style WINDOW_STYLE_SHADER + rect 260 260 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight7 + group highlights + style WINDOW_STYLE_SHADER + rect 260 274 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight8 + group highlights + style WINDOW_STYLE_SHADER + rect 260 288 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight9 + group highlights + style WINDOW_STYLE_SHADER + rect 260 302 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight10 + group highlights + style WINDOW_STYLE_SHADER + rect 260 316 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight11 + group highlights + style WINDOW_STYLE_SHADER + rect 260 330 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight12 + group highlights + style WINDOW_STYLE_SHADER + rect 260 344 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight13 + group highlights + style WINDOW_STYLE_SHADER + rect 260 358 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight14 + group highlights + style WINDOW_STYLE_SHADER + rect 260 372 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight15 + group highlights + style WINDOW_STYLE_SHADER + rect 260 386 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// VIDEO 1 MENU BUTTONS +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name graphics + group video + text @MENUS_VIDEO_QUALITY + type ITEM_TYPE_MULTI + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + style WINDOW_STYLE_FILLED + forecolor .615 .615 .956 1 + descText @MENUS_SELECT_PRESET_GRAPHIC + + visible 1 + + cvar "ui_r_glCustom" + cvarFloatList + { + @MENUS_HIGH_QUALITY 0 + @MENUS_NORMAL 1 + @MENUS_FAST 2 + @MENUS_FASTEST 3 + @MENUS_CUSTOM 4 + } + + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + action + { + play "sound/interface/button1.wav" ; + uiScript update "ui_r_glCustom" ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + + itemDef + { + name video_mode + group video + type ITEM_TYPE_MULTI + text @MENUS_VIDEO_MODE + cvarFloatList { @MENUS_640_X_480 3 @MENUS_800_X_600 4 @MENUS_1024_X_768 6 @MENUS_1152_X_864 7 @MENUS_1280_X_1024 8 @MENUS_1600_X_1200 9 @MENUS_2048_X_1536 10 @MENUS_2400_X_600 12 } + cvar "ui_r_mode" + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + descText @MENUS_CHANGE_THE_DISPLAY_RESOLUTION + visible 1 + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name color_depth + group video + type ITEM_TYPE_MULTI + text @MENUS_COLOR_DEPTH + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_DEFAULT 0 @MENUS_16_BIT 16 @MENUS_32_BIT 32 } + descText @MENUS_CHANGE_THE_NUMBER_OF + cvar "ui_r_colorbits" + visible 1 + + mouseenter + { + show highlight4 + } + mouseexit + { + hide highlight4 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + uiScript update "ui_r_colorbits" ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name fullscreen + group video + type ITEM_TYPE_MULTI + text @MENUS_FULL_SCREEN + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_TOGGLE_BETWEEN_FULL_SCREEN + cvar "ui_r_fullscreen" + visible 1 + + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name geometric_detail + group video + type ITEM_TYPE_MULTI + text @MENUS_GEOMETRIC_DETAIL + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_LOW 2 @MENUS_MEDIUM 1 @MENUS_HIGH 0 } + descText @MENUS_ADJUST_THE_NUMBER_OF + cvar "ui_r_lodbias" + visible 1 + + mouseenter + { + show highlight6 + } + mouseexit + { + hide highlight6 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + uiScript update "ui_r_lodbias" ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name texture_detail + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_DETAIL + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_LOW 3 @MENUS_MEDIUM 2 @MENUS_HIGH 1 @MENUS_VERY_HIGH 0 } + descText @MENUS_SELECT_THE_RESOLUTION + cvar "ui_r_picmip" + visible 1 + + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name texture_quality + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_QUALITY + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_DEFAULT 0 @MENUS_16_BIT 16 @MENUS_32_BIT 32 } + descText @MENUS_SELECT_THE_NUMBER_OF + cvar "ui_r_texturebits" + visible 1 + + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name texture_filter + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_FILTER + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarStrList { @MENUS_BILINEAR , "GL_LINEAR_MIPMAP_NEAREST" , @MENUS_TRILINEAR , "GL_LINEAR_MIPMAP_LINEAR" } + descText @MENUS_ADJUST_HOW_WELL_THE_TEXTURES + cvar "ui_r_texturemode" + visible 1 + + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name simple_shaders + group video + type ITEM_TYPE_MULTI + text @MENUS_DETAILED_SHADERS + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_HIDE_OR_UNHIDE_TEXTURES + cvar "ui_r_detailtextures" + visible 1 + + mouseenter + { + show highlight10 + } + mouseexit + { + hide highlight10 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + + itemDef + { + name video_sync + group video + type ITEM_TYPE_MULTI + text @MENUS_VIDEO_SYNC + cvar "r_swapInterval" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_VIDEO_SYNC_DESC + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + } + + // APPLY CHANGES BUTTON + itemDef + { + name applybutton_glow + group none + style WINDOW_STYLE_SHADER + rect 260 384 340 14 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 0.5 0.5 1 + visible 0 + decoration + } + + itemDef + { + name applyChanges + group none + text @MENUS_APPLY_CHANGES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 260 384 340 14 + textaligny 0 + font 4 + textscale 1 + textalignx 170 + textalign ITEM_ALIGN_CENTER + textstyle 0 + forecolor 1 0 0 1 + backcolor 0 0 1 0 + visible 0 + + mouseEnter + { + show applybutton_glow + } + mouseExit + { + hide applybutton_glow + } + action + { + play "sound/interface/button1.wav" + show setup_background + show vidrestart + hide video + hide video2 + hide applybutton_glow + } + } + + + //---------------------------------------------------------------------------------------------- + // + // VIDEO RESTART + // + //---------------------------------------------------------------------------------------------- + // Faint red box + itemDef + { + name vidrestart_background + group vidrestart + style WINDOW_STYLE_SHADER + rect 260 185 342 227 + background "gfx/menus/menu_boxred" // Frame + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name vidrestart_text1 + group vidrestart + text @MENUS_THIS_WILL_APPLY_VIDEO + text2 @MENUS_AND_RETURN_TO_THE_MAIN + rect 290 230 290 20 + textalign ITEM_ALIGN_CENTER + text2aligny 18 + textalignx 145 + font 2 + textscale 1 + forecolor 1 1 0 1 + visible 0 + decoration + } + + itemDef + { + name vidrestart_text2 + group vidrestart + text @MENUS_VID_RESTART3 + rect 290 300 290 20 + textalign ITEM_ALIGN_CENTER + textalignx 145 + font 2 + textscale 1 + forecolor 1 1 0 1 + visible 0 + } + + + + itemDef + { + name vidrestart_yes_button + style WINDOW_STYLE_SHADER + rect 467 384 120 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // YES button + itemDef + { + name vidrestart_yes + group vidrestart + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 467 384 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + descText @MENUS_APPLY_CHANGES_AND_THEN + forecolor 1 .682 0 1 + visible -1 + + action + { + play "sound/interface/button1.wav" ; + close all ; + uiScript updatevideosetup ; + } + mouseEnter + { + show vidrestart_yes_button + } + mouseExit + { + hide vidrestart_yes_button + } + + } + + itemDef + { + name vidrestart_no_button + style WINDOW_STYLE_SHADER + rect 275 384 120 32 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // CANCEL button + itemDef + { + name vidrestart_no + group vidrestart + text @MENUS_NO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 275 384 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -1 + descText @MENUS_DO_NOT_APPLY_CHANGES + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + hide vidrestart ; + show video ; + hide video2 ; + show applyChanges ; + hide player ; + hide vidrestart_yes_button ; + hide vidrestart_no_button ; + } + mouseEnter + { + show vidrestart_no_button + } + mouseExit + { + hide vidrestart_no_button + } + + } + + //---------------------------------------------------------------------------------------------- + // + // VIDEO 2 + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name gamma_text + group video2 + style WINDOW_STYLE_SHADER + rect 280 198 280 36 + background "gfx/menus/greyscale" // greyscale + forecolor 1 1 1 1 + visible 0 + decoration + } + + + + itemDef + { + name bright_text + group video2 + text @MENUS_ADJUST_BRIGHTNESS_SLIDER + text2 @MENUS_THE_NUMBER_6_CAN_BARELY + text2aligny 14 + textalignx 170 + textaligny 0 + font 4 + textscale 1 + rect 260 234 340 20 + textalign ITEM_ALIGN_CENTER + forecolor .549 .854 1 1 + visible 0 + decoration + } + + itemDef + { + name brightness + group video2 + type ITEM_TYPE_SLIDER + text @MENUS_VIDEO_BRIGHTNESS + cvarfloat "r_gamma" 1 .5 3 + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_ADJUST_THE_BRIGHTNESS + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + } + + + itemDef + { + name dynamic_light + group video2 + type ITEM_TYPE_MULTI + text @MENUS_DYNAMIC_LIGHTS + cvar "r_dynamiclight" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_TOGGLE_TO_TURN_ON_MOVING + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + } + + itemDef + { + name dynamic_glow + group video2 + type ITEM_TYPE_MULTI + text @MENUS_DYNAMIC_GLOW + cvar r_dynamicglow + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_DYNAMIC_GLOW_DESC + action + { + play "sound/interface/button1" + } + mouseenter + { + show highlight10 + } + mouseexit + { + hide highlight10 + } + } + + itemDef + { + name light_flares + group video2 + type ITEM_TYPE_MULTI + text @MENUS_LIGHT_FLARES + cvar r_flares + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_TOGGLE_TO_SHOW_HALOS + action + { + play "sound/interface/button1" + } + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + } + + itemDef + { + name wall_marks + group video2 + type ITEM_TYPE_MULTI + text @MENUS_WALL_MARKS + cvar cg_marks + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_TOGGLE_TO_DISPLAY_SCORCH + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight12 + } + mouseexit + { + hide highlight12 + } + } + + itemDef + { + name video_mode + group video2 + type ITEM_TYPE_SLIDER + text @MENUS_ANISOTROPIC_FILTERING + cvarTest r_ext_texture_filter_anisotropic_avail + disableCvar { 0 } + cvarfloat r_ext_texture_filter_anisotropic 1 .5 16 + rect 260 356 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_ADVANCED_TEXTURE + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight13 + } + mouseexit + { + hide highlight13 + } + } + + itemDef + { + name advancedvideobutton_glow + group highlights + style WINDOW_STYLE_SHADER + rect 260 384 340 14 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name advancedvideo + group video2 + type ITEM_TYPE_BUTTON + text @MENUS_SHOW_DRIVER_INFO + rect 260 384 340 14 + textalign ITEM_ALIGN_CENTER + textalignx 170 + textaligny 0 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + descText @MENUS_SHOW_ADVANCED_INFORMATION + + mouseenter + { + show advancedvideobutton_glow + } + mouseexit + { + hide advancedvideobutton_glow + } + action + { + play sound/interface/button1.wav + hide advancedvideobutton_glow + open videodriverMenu + } + } + +//---------------------------------------------------------------------------------------------- +// +// SOUND FIELDS +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name effects_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_EFFECTS_VOLUME + cvarfloat "s_volume" 0 0 1 + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_ADJUST_VOLUME_FOR_SOUND + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + } + + itemDef + { + name music_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_MUSIC_VOLUME + cvarfloat "s_musicvolume" 0 0 1 + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_ADJUST_VOLUME_FOR_MUSIC + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + } + + itemDef + { + name voice_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_VOICE_VOLUME + cvarfloat "s_volumeVoice" 0 0 1 + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_VOICE_VOLUME_DESC + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + } + + itemDef + { + name sound_quality + group sound + type ITEM_TYPE_MULTI + text @MENUS_SOUND_QUALITY + cvar "s_khz" + cvarFloatList + { + @MENUS_LOW 11 + @MENUS_HIGH 22 + } + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_TRADE_CLARITY_OF_SOUND + cvarTest "s_UseOpenAL" + hideCvar { 1 } + + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + action + { + play "sound/interface/button1.wav" + uiScript update s_khz + } + } + + itemDef + { + name eax + group sound + type ITEM_TYPE_MULTI + cvar "s_UseOpenAL" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + text @MENUS_EAX + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_EAX_DESC + action + { + play "sound/interface/button1.wav" + uiScript update s_khz + } + + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + } + + itemDef + { + name eax_icon + group sound + style WINDOW_STYLE_SHADER + rect 380 320 128 64 + background "gfx/menus/eax" + forecolor 1 1 1 1 + visible 1 + decoration + cvarTest "s_UseOpenAL" + hideCvar { 0 } + } + +//---------------------------------------------------------------------------------------------- +// +// OPTION FIELDS +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name draw_crosshair + group options + type ITEM_TYPE_MULTI + text @MENUS_DRAW_CROSSHAIR + cvar "cg_drawcrosshair" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_TOGGLE_TO_SHOW_OR_HIDE + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + } + + + itemDef + { + name identifytarget + group options + type ITEM_TYPE_MULTI + text @MENUS_IDENTIFY_TARGET + cvar cg_drawCrosshairNames + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_TOGGLE_TO_HAVE_THE_CROSSHAIR + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight2 + } + mouseexit + { + hide highlight2 + } + } + + itemDef + { + name forcemodels + group options + type ITEM_TYPE_MULTI + text @MENUS_FORCE_PLAYER_MODELS + cvar "cg_forceModel" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_FORCE_ALL_PLAYER_MODELS + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + } + + itemDef + { + name defermodels + group options + type ITEM_TYPE_MULTI + text @MENUS_DEFER_PLAYER_MODELS + cvar "cg_deferPlayers" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_DEFERS_LOADING_OF_NEW + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight4 + } + mouseexit + { + hide highlight4 + } + } + + itemDef + { + name teamchatsonly + group options + type ITEM_TYPE_MULTI + text @MENUS_TEAM_CHATS_ONLY + cvar "cg_teamChatsOnly" + cvarFloatList + { + @MENUS_NO 0 + @MENUS_YES 1 + } + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_IGNORE_NON_TEAM_CHATS + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + } + + itemDef + { + name simpleitems + group options + type ITEM_TYPE_MULTI + text @MENUS_SIMPLE_ITEMS + cvar "cg_simpleItems" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_RENDER_ICONS_FOR_ITEMS + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight6 + } + mouseexit + { + hide highlight6 + } + } + + itemDef + { + name teamoverlay + group options + type ITEM_TYPE_MULTI + text @MENUS_TEAM_OVERLAY + cvar "cg_drawTeamOverlay" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_DRAW_OVERLAY_SHOWING + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + } + +// Weapon Sway. Yes, this is nutty. Two cvars here, one removes weapon sway, the other adds it. + itemDef + { + name weaponswayon + group options + type ITEM_TYPE_MULTI + text @MENUS_VIEW_SWAYING + descText @MENUS_VIEW_SWAYING_DESC + cvar "ui_disableWeaponSway" + cvarFloatList + { + @MENUS_ON 0 + @MENUS_OFF 1 + } + cvarTest "ui_disableWeaponSway" + showCvar + { + "0" + } + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + + action + { + play "sound/interface/button1.wav" ; + exec "exec noMotion.cfg" + show weaponswayoff + setfocus weaponswayoff + } + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name weaponswayoff + group options + type ITEM_TYPE_MULTI + text @MENUS_VIEW_SWAYING + descText @MENUS_VIEW_SWAYING_DESC + cvar "ui_disableWeaponSway" + cvarFloatList + { + @MENUS_ON 0 + @MENUS_OFF 1 + } + cvarTest "ui_disableWeaponSway" + hideCvar + { + "0" + } + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + + action + { + play "sound/interface/button1.wav" ; + exec "exec restoreMotion.cfg" ; + show weaponswayon ; + setfocus weaponswayon + } + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name allowdownload + group options + type ITEM_TYPE_MULTI + text @MENUS_ALLOW_DOWNLOADS + descText @MENUS_ALLOW_DOWNLOADS_INFO + cvar "cl_allowDownload" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 1 + } + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + } + + itemDef + { + name footsteps + group options + type ITEM_TYPE_MULTI + text @MENUS_FOOTSTEPS + desctext @MENUS_FOOTSTEPS_DESC + cvar "cg_footsteps" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_SOUNDS 1 + @MENUS_SOUNDS_AND_EFFECTS 2 + @MENUS_SOUNDS_EFFECTS_GRAPHICS 3 + } + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight10 + } + mouseexit + { + hide highlight10 + } + } + + itemDef + { + name text + group options + type ITEM_TYPE_MULTI + text @MENUS_TEXT + cvar "se_language" + feeder 40 + cvarStrList feeder + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_CHOOSE_THE_LANGUAGE_FOR + action + { + play "sound/interface/button1" + show langapply + } + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + } + itemDef + { + name voice + group options + type ITEM_TYPE_MULTI + text @MENUS_VOICE + cvar "s_language" + cvarStrList + { + English english + @MENUS_LANG_FRENCH francais + Deutsch deutsch + "Español" espanol + } + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor 0.65 0.65 1 1 + visible 0 + descText @MENUS_CHOOSE_THE_LANGUAGE_TO + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight12 + } + mouseexit + { + hide highlight12 + } + } + + // APPLY CHANGES button + itemDef + { + name langapply + group options + text @MENUS_SET_LANGUAGE + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 260 356 340 14 + textaligny 0 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 160 + descText @MENUS_SET_LANGUAGE_INFO + forecolor 1 .682 0 1 + visible 0 + + action + { + play "sound/interface/button1" + exec "ui_load ; snd_restart" + hide langapply + } + mouseEnter + { + show highlight13 + } + mouseExit + { + hide highlight13 + } + + } + +//---------------------------------------------------------------------------------------------- +// +// MOD GAME MENU specific stuff +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name serverinfo + group mods + rect 270 196 320 176 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 16 + font 2 + textscale 1 + textaligny -2 + border 1 + bordersize 1 + bordercolor .5 .5 .5 .5 + forecolor 1 .682 0 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder 9 + notselectable + visible 0 + columns 1 2 40 280 + } + + itemDef + { + name loadmod_button + style WINDOW_STYLE_SHADER + rect 260 384 340 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadmod + group mods + text @MENUS_LOAD_MOD + descText @MENUS_LOAD_CHOSEN_MOD + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 260 384 340 14 + textalign ITEM_ALIGN_CENTER + textalignx 170 + textaligny -2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/button1.wav" ; + uiScript RunMod ; + } + + mouseEnter + { + show loadmod_button + } + mouseExit + { + hide loadmod_button + } + } + +//---------------------------------------------------------------------------------------------- +// +// RESET DEFAULTS +// +//---------------------------------------------------------------------------------------------- +// Faint red box + itemDef + { + name vidrestart_background + group defaults + style WINDOW_STYLE_SHADER + rect 260 185 342 227 + background "gfx/menus/menu_boxred" // Frame + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_WARNING + rect 260 202 340 20 + textalign ITEM_ALIGN_CENTER + textalignx 170 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_THIS_WILL_SET_ALL_GAME + rect 260 230 340 20 + textalign ITEM_ALIGN_CENTER + textalignx 170 + text2aligny 20 + font 2 + textscale .9 + forecolor 1 .682 0 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_TO_THEIR_FACTORY_SETTINGS + rect 260 260 340 20 + textalign ITEM_ALIGN_CENTER + textalignx 170 + text2aligny 20 + font 2 + textscale .9 + forecolor 1 .682 0 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_VID_RESTART3 + rect 260 320 340 20 + textalign ITEM_ALIGN_CENTER + textalignx 170 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + decoration + } + + + itemDef + { + name default_yes_button + group highlights + style WINDOW_STYLE_SHADER + rect 443 364 120 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // YES button - lose reset defaults + itemDef + { + name default_yes + group defaults + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 443 364 120 24 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -2 + descText @MENUS_USE_DEFAULT_SETTINGS + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/button1.wav" ; + hide highlights ; + close all ; + uiscript resetdefaults + } + mouseEnter + { + show default_yes_button + } + mouseExit + { + hide default_yes_button + } + + } + + itemDef + { + name default_no_button + group highlights + style WINDOW_STYLE_SHADER + rect 297 364 120 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // NO button - return to Main Menu + itemDef + { + name default_no + group defaults + text @MENUS_NO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 297 364 120 24 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -2 + descText @MENUS_DO_NOT_RESET_DEFAULT + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/button1.wav" + hide highlights ; + hide default_no_button + uiscript clearmouseover default_no ; + close all ; + open mainMenu ; + } + mouseEnter + { + show default_no_button + } + mouseExit + { + hide default_no_button + } + } + + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/jamp/siege_class.menu b/base/ui/jamp/siege_class.menu new file mode 100644 index 0000000..1e2c7ec --- /dev/null +++ b/base/ui/jamp/siege_class.menu @@ -0,0 +1,4417 @@ +//-------------------------------------------------------------------------------------------- +// uberScreen +// +// cvars used (at least some of them +// siege_mapgraphic - the name of the graphic of the map of the level. +// currentObjButton - the menu item name of currently active objective button +// +// team1_icon - read in from siege file, the graphic of team1 +// team2_icon - read in from siege file, the graphic of team2 +// +//-------------------------------------------------------------------------------------------- +{ + menuDef + { + name "ingame_siegeclass" + fullScreen 1 + visible 0 + outOfBoundsClick + rect 0 0 640 480 + focusColor 1 1 1 1 + style 1 + descX 480 + descY 444 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + backColor .05 .07 .09 1 + disablecolor 1 1 1 1 + onClose + { + hide minidesc + hide obj_longdesc + hide objective_pic + hide siegeclassconfigtitle + } + + onOpen + { + setcvar currentObjButton "null" + + exec "siegeCompleteCvarUpdate" + + setitembackground mappic "*siege_mapgraphic" + + // set tm_icon fields to the proper mapicon graphic + uiscript updatesiegeobjgraphics + + setitembackground team1_symbol "*team1_icon" + setitembackground team2_symbol "*team2_icon" + setitembackground team1_button "*team1_icon" + setitembackground team2_button "*team2_icon" + + // Enable team buttons, in case they'd been shut off + disable team_button 0 + disable autoteam 0 + disable team1_button 0 + disable team2_button 0 + + // and reset colors + setitemcolor team1_button backcolor .8 .48 0 1 + setitemcolor team2_button backcolor .8 .48 0 1 + setitemcolor team1name forecolor .8 .48 0 1 + setitemcolor team2name forecolor .8 .48 0 1 + setitemcolor team1_count forecolor .8 .48 0 1 + setitemcolor team2_count forecolor .8 .48 0 1 + setitemcolor siegeclassconfigtitle forecolor 1 .68 0 1 + + // + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + setitemcolor team1obj_buttons forecolor 1 1 1 1 + setitemcolor team2obj_buttons forecolor 1 1 1 1 + + disable team1obj_buttons 0 + disable team2obj_buttons 0 + + hide grey_button + hide class_portrait + hide itemdescription + + // Show first objective + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_1 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective1_longdesc" + + // change objective pic + setitembackground objective_pic "*team1_objective1_gfx" + + // Show new map icon + setitembackground tm1_icon1 "*team1_objective1_mapicon" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + + setitemcolor mapicons bordercolor 0 0 0 0 + + // Disable current button and give it a border + setitemcolor team1obj1_button bordercolor 1 1 1 1 + setitemcolor team1obj1_button backcolor 1 0 0 1 + + // Set the map icon picture to the updated graphic + uiscript setsiegeobjbuttons tm1_icon1 team1_objective1_mapicon team1_objective1_litmapicon + uiscript setteamclassicons + } + + onESC + { + hide stats + hide forcepowerlevel + hide minidesc + hide fulldesc + hide objective_pic + disable team_button 0 + close all; + } + + //------------------------------------------------------------ + // Window backdrop + //------------------------------------------------------------ + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 0 + decoration + } + + // Title of objective section + itemDef + { + name objectivetitle + style WINDOW_STYLE_FILLED + backcolor .05 .13 .25 1 + text @MENUS_MISSION_OBJECTIVES + rect 7 7 308 20 + textalign ITEM_ALIGN_CENTER + textalignx 160 + textaligny -1 + font 3 + textscale 0.9 + forecolor 1 .68 0 1 + visible 1 + decoration + } + + // Border around the objective half of the screen + itemDef + { + name border + group none + type ITEM_TYPE_TEXT + rect 5 5 310 370 +// forecolor 1 1 1 .6 + visible 1 + border 1 + bordercolor 1 1 1 1 + decoration + } + + + itemDef + { + name mapname + group time + style WINDOW_STYLE_EMPTY +// text @MENUS_MISSION + cvar siege_missionname + maxChars 0 + rect 70 28 180 18 + textalign ITEM_ALIGN_CENTER + textalignx 90 + font 3 + textscale .7 + forecolor 1 1 1 1 + visible 1 + decoration + } + + //------------------------------------------------------------ + // Map picture + //------------------------------------------------------------ + itemDef + { + name mappic + group none + style WINDOW_STYLE_SHADER + rect 70 46 180 320 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor .38 .51 .59 1 + decoration + background "*siege_mapgraphic" + } + + +//---------------------------------------------------------------------------------------------- +// TEAM SYMBOLS +//---------------------------------------------------------------------------------------------- + itemDef + { + name team1_symbol + style WINDOW_STYLE_SHADER + rect 12 60 45 45 + forecolor 1 0 0 1 + visible 1 + decoration + } + + itemDef + { + name team2_symbol + style WINDOW_STYLE_SHADER + rect 265 60 45 45 + forecolor 0 0 1 1 + visible 1 + decoration + } + +//------------------------------------------- +// TEAM1 - OBJECTIVE BUTTON 1 +//------------------------------------------- + itemDef + { + name team1obj1_button + group team1obj_buttons + rect 22 120 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_1 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 1 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + mouseEnter + { + setitemcolor team1obj1_button forecolor 1 1 1 1 + setitemcolor team1obj1_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj1_button forecolor .75 .75 .75 1 + setitemcolor team1obj1_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_1 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective1_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective1_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button and give it a border + setcvar currentObjButton "team1obj1_button" + setitemcolor team1obj1_button bordercolor 1 1 1 1 + setitemcolor team1obj1_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj1 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon1 team1_objective1_mapicon team1_objective1_litmapicon + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM1 - OBJECTIVE BUTTON 2 +//---------------------------------------------------------------------------------------------- + itemDef + { + name team1obj2_button + group team1obj_buttons + rect 22 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_2 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective2_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj2_button forecolor 1 1 1 1 + setitemcolor team1obj2_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj2_button forecolor .75 .75 .75 1 + setitemcolor team1obj2_button backcolor .7 0 0 1 + } + + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_2 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective2_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective2_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj2_button" + setitemcolor team1obj2_button bordercolor 1 1 1 1 + setitemcolor team1obj2_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj2 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon2 team1_objective2_mapicon team1_objective2_litmapicon + + } + } + + itemDef + { + name team1obj3_button + group team1obj_buttons + rect 22 180 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_3 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective3_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj3_button forecolor 1 1 1 1 + setitemcolor team1obj3_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj3_button forecolor .75 .75 .75 1 + setitemcolor team1obj3_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_3 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective3_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective3_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj3_button" + setitemcolor team1obj3_button bordercolor 1 1 1 1 + setitemcolor team1obj3_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj3 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon3 team1_objective3_mapicon team1_objective3_litmapicon + } + } + + itemDef + { + name team1obj4_button + group team1obj_buttons + rect 22 210 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_4 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective4_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj4_button forecolor 1 1 1 1 + setitemcolor team1obj4_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj4_button forecolor .75 .75 .75 1 + setitemcolor team1obj4_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_4 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective4_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective4_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj4_button" + setitemcolor team1obj4_button bordercolor 1 1 1 1 + setitemcolor team1obj4_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj4 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon4 team1_objective4_mapicon team1_objective4_litmapicon + } + } + + itemDef + { + name team1obj5_button + group team1obj_buttons + rect 22 240 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_5 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective5_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj5_button forecolor 1 1 1 1 + setitemcolor team1obj5_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj5_button forecolor .75 .75 .75 1 + setitemcolor team1obj5_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_5 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective5_longdesc" + + + // change pic to match objective + setitembackground objective_pic "*team1_objective5_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj5_button" + setitemcolor team1obj5_button bordercolor 1 1 1 1 + setitemcolor team1obj5_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj5 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon5 team1_objective5_mapicon team1_objective5_litmapicon + } + } + + itemDef + { + name team1obj6_button + group team1obj_buttons + rect 22 270 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_6 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective6_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj6_button forecolor 1 1 1 1 + setitemcolor team1obj6_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj6_button forecolor .75 .75 .75 1 + setitemcolor team1obj6_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_6 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective6_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective6_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj6_button" + setitemcolor team1obj6_button bordercolor 1 1 1 1 + setitemcolor team1obj6_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj6 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon6 team1_objective6_mapicon team1_objective6_litmapicon + } + } + + itemDef + { + name team1obj7_button + group team1obj_buttons + rect 22 300 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_7 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective7_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj7_button forecolor 1 1 1 1 + setitemcolor team1obj7_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj7_button forecolor .75 .75 .75 1 + setitemcolor team1obj7_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_7 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective7_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective7_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj7_button" + setitemcolor team1obj7_button bordercolor 1 1 1 1 + setitemcolor team1obj7_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj7 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon7 team1_objective7_mapicon team1_objective7_litmapicon + } + } + + itemDef + { + name team1obj8_button + group team1obj_buttons + rect 22 330 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_8 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor .7 0 0 1 + forecolor .75 .75 .75 1 + visible 1 + border 1 + bordercolor 0 0 0 1 + cvartest "team1_objective8_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team1obj8_button forecolor 1 1 1 1 + setitemcolor team1obj8_button backcolor 1 0 0 1 + } + mouseExit + { + setitemcolor team1obj8_button forecolor .75 .75 .75 1 + setitemcolor team1obj8_button backcolor .7 0 0 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_8 + + //change text of long desc + setitemtext obj_longdesc "*team1_objective8_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team1_objective8_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team1obj8_button" + setitemcolor team1obj8_button bordercolor 1 1 1 1 + setitemcolor team1obj8_button backcolor 1 0 0 1 + + // Completed text + hide completed_text + show text_tm1_obj8 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm1_icon8 team1_objective8_mapicon team1_objective8_litmapicon + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM1 - MET OBJECTIVES BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name team1_met_obj1 + group none + style WINDOW_STYLE_SHADER + rect 22 120 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective1" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj2 + group none + style WINDOW_STYLE_SHADER + rect 22 150 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective2" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj3 + group none + style WINDOW_STYLE_SHADER + rect 22 180 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective3" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj4 + group none + style WINDOW_STYLE_SHADER + rect 22 210 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective4" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj5 + group none + style WINDOW_STYLE_SHADER + rect 22 240 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective5" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj6 + group none + style WINDOW_STYLE_SHADER + rect 22 270 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective6" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj7 + group none + style WINDOW_STYLE_SHADER + rect 22 300 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective7" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_obj8 + group none + style WINDOW_STYLE_SHADER + rect 22 330 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective8" + decoration + showcvar + { + "1" + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM2 - PRIMARY OBJECTIVE BUTTON +//---------------------------------------------------------------------------------------------- + itemDef + { + name team2obj1_button + group team2obj_buttons + rect 273 120 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_1 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + mouseEnter + { + setitemcolor team2obj1_button forecolor 1 1 1 1 + setitemcolor team2obj1_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj1_button forecolor .75 .75 .75 1 + setitemcolor team2obj1_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_1 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective1_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective1_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj1_button" + setitemcolor team2obj1_button bordercolor 1 1 1 1 + setitemcolor team2obj1_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj1 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon1 team2_objective1_mapicon team2_objective1_litmapicon + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM2 - SECONDARY OBJECTIVE BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name team2obj2_button + group team2obj_buttons + rect 273 150 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_2 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective2_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj2_button forecolor 1 1 1 1 + setitemcolor team2obj2_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj2_button forecolor .75 .75 .75 1 + setitemcolor team2obj2_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_2 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective2_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective2_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj2_button" + setitemcolor team2obj2_button bordercolor 1 1 1 1 + setitemcolor team2obj2_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj2 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon2 team2_objective2_mapicon team2_objective2_litmapicon + } + } + + itemDef + { + name team2obj3_button + group team2obj_buttons + rect 273 180 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_3 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective3_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj3_button forecolor 1 1 1 1 + setitemcolor team2obj3_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj3_button forecolor .75 .75 .75 1 + setitemcolor team2obj3_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and stext of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_3 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective3_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective3_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj3_button" + setitemcolor team2obj3_button bordercolor 1 1 1 1 + setitemcolor team2obj3_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj3 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon3 team2_objective3_mapicon team2_objective3_litmapicon + } + } + + itemDef + { + name team2obj4_button + group team2obj_buttons + rect 273 210 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_4 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective4_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj4_button forecolor 1 1 1 1 + setitemcolor team2obj4_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj4_button forecolor .75 .75 .75 1 + setitemcolor team2obj4_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_4 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective4_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective4_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj4_button" + setitemcolor team2obj4_button bordercolor 1 1 1 1 + setitemcolor team2obj4_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj4 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon4 team2_objective4_mapicon team2_objective4_litmapicon + } + } + + itemDef + { + name team2obj5_button + group team2obj_buttons + rect 273 240 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_5 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective5_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj5_button forecolor 1 1 1 1 + setitemcolor team2obj5_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj5_button forecolor .75 .75 .75 1 + setitemcolor team2obj5_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_5 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective5_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective5_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj5_button" + setitemcolor team2obj5_button bordercolor 1 1 1 1 + setitemcolor team2obj5_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj5 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon5 team2_objective5_mapicon team2_objective5_litmapicon + } + } + + itemDef + { + name team2obj6_button + group team2obj_buttons + rect 273 270 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_6 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective6_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj6_button forecolor 1 1 1 1 + setitemcolor team2obj6_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj6_button forecolor .75 .75 .75 1 + setitemcolor team2obj6_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_6 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective6_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective6_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj6_button" + setitemcolor team2obj6_button bordercolor 1 1 1 1 + setitemcolor team2obj6_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj6 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon6 team2_objective6_mapicon team2_objective6_litmapicon + } + } + + itemDef + { + name team2obj7_button + group team2obj_buttons + rect 273 300 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_7 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective7_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj7_button forecolor 1 1 1 1 + setitemcolor team2obj7_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj7_button forecolor .75 .75 .75 1 + setitemcolor team2obj7_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_7 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective7_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective7_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj7_button" + setitemcolor team2obj7_button bordercolor 1 1 1 1 + setitemcolor team2obj7_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj7 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon7 team2_objective7_mapicon team2_objective7_litmapicon + } + } + + itemDef + { + name team2obj8_button + group team2obj_buttons + rect 273 330 24 24 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + text @MENUS_8 + font 2 + textscale .8 + textstyle 0 + textalign ITEM_ALIGN_CENTER + textalignx 12 + textaligny -1 + backcolor 0 0 1 1 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 0 0 0 0 + cvartest "team2_objective8_inuse" + showcvar + { + "1"; "2" + } + mouseEnter + { + setitemcolor team2obj8_button forecolor 1 1 1 1 + setitemcolor team2obj8_button backcolor 0 0 1 1 + } + mouseExit + { + setitemcolor team2obj8_button forecolor .75 .75 .75 1 + setitemcolor team2obj8_button backcolor 0 0 .7 1 + } + action + { + // Because open and close menu hide these + show obj_minidesc + show obj_longdesc + show objective_pic + + // change color and text of mini desc + setitemtext obj_minidesc @MENUS_OBJECTIVE_8 + + //change text of long desc + setitemtext obj_longdesc "*team2_objective8_longdesc" + + // change pic to match objective + setitembackground objective_pic "*team2_objective8_gfx" + + // Reset all buttons to their normal colors + setitemcolor team1obj_buttons forecolor .75 .75 .75 1 + setitemcolor team1obj_buttons backcolor .7 0 0 1 + setitemcolor team2obj_buttons forecolor .75 .75 .75 1 + setitemcolor team2obj_buttons backcolor 0 0 .7 1 + setitemcolor team1obj_buttons bordercolor 0 0 0 0 + setitemcolor team2obj_buttons bordercolor 0 0 0 0 + + // Disable current button + setcvar currentObjButton "team2obj8_button" + setitemcolor team2obj8_button bordercolor 1 1 1 1 + setitemcolor team2obj8_button backcolor 0 0 1 1 + + // Completed text + hide completed_text + show text_tm2_obj8 + + // Reset old map icon to old background and enable old obj button + // Set new map icon to hilite background and disable new obj button + uiscript setsiegeobjbuttons tm2_icon8 team2_objective8_mapicon team2_objective8_litmapicon + } + } + + +//---------------------------------------------------------------------------------------------- +// TEAM2 - MET OBJECTIVES +//---------------------------------------------------------------------------------------------- + itemDef + { + name team2_met_obj1 + group none + style WINDOW_STYLE_SHADER + rect 273 120 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective1" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj2 + group none + style WINDOW_STYLE_SHADER + rect 273 150 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective2" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj3 + group none + style WINDOW_STYLE_SHADER + rect 273 180 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective3" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj4 + group none + style WINDOW_STYLE_SHADER + rect 273 210 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective4" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj5 + group none + style WINDOW_STYLE_SHADER + rect 273 240 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective5" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj6 + group none + style WINDOW_STYLE_SHADER + rect 273 270 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective6" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj7 + group none + style WINDOW_STYLE_SHADER + rect 273 300 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective7" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_obj8 + group none + style WINDOW_STYLE_SHADER + rect 273 330 24 24 + forecolor 1 1 1 1 + visible 1 + border 1 + bordercolor 1 1 1 1 + background "gfx/menus/x" + visible 1 + cvartest "team1_objective8" + decoration + showcvar + { + "1" + } + } + + +//---------------------------------------------------------------------------------------------- +// TEAM1 MAP ICONS - position for these is set in the on open of the menu +//---------------------------------------------------------------------------------------------- + itemDef + { + name tm1_icon1 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective1_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon2 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective2_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon3 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective3_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon4 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective4_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon5 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective5_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon6 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective6_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon7 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective7_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm1_icon8 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team1_objective8_inuse" + hidecvar + { + "0" + } + } + + +//---------------------------------------------------------------------------------------------- +// TEAM2 MAP ICONS - position for these is set in the on open of the menu +//---------------------------------------------------------------------------------------------- + itemDef + { + name tm2_icon1 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective1_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon2 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective2_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon3 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective3_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon4 + group mapicons + //rectcvar "siege_objective3_mappos" + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective4_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon5 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective5_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon6 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective6_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon7 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective7_inuse" + hidecvar + { + "0" + } + } + + itemDef + { + name tm2_icon8 + group mapicons + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + cvartest "team2_objective8_inuse" + hidecvar + { + "0" + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM1 - MET OBJECTIVES MAP ICONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name team1_met_map1 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective1_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective1_donemapicon" + visible 1 + cvartest "team1_objective1" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map2 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective2_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective2_donemapicon" + visible 1 + cvartest "team1_objective2" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map3 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective3_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective3_donemapicon" + visible 1 + cvartest "team1_objective3" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map4 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective4_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective4_donemapicon" + visible 1 + cvartest "team1_objective4" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map5 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective5_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective5_donemapicon" + visible 1 + cvartest "team1_objective5" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map6 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective6_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective6_donemapicon" + visible 1 + cvartest "team1_objective6" + decoration + showcvar + { + "1" + } + } + + + itemDef + { + name team1_met_map7 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective7_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective7_donemapicon" + visible 1 + cvartest "team1_objective7" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team1_met_map8 + group none + style WINDOW_STYLE_SHADER + rectcvar team1_objective8_mappos + forecolor 1 1 1 1 + visible 1 + background "*team1_objective8_donemapicon" + visible 1 + cvartest "team1_objective8" + decoration + showcvar + { + "1" + } + } + +//---------------------------------------------------------------------------------------------- +// TEAM2 - MET OBJECTIVES MAP ICONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name team2_met_map1 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective1_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective1_donemapicon" + visible 1 + decoration + cvartest "team1_objective1" + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map2 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective2_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective2_donemapicon" + visible 1 + cvartest "team1_objective2" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map3 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective3_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective3_donemapicon" + visible 1 + cvartest "team1_objective3" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map4 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective4_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective4_donemapicon" + visible 1 + cvartest "team1_objective4" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map5 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective5_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective5_donemapicon" + visible 1 + cvartest "team1_objective5" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map6 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective6_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective6_donemapicon" + visible 1 + cvartest "team1_objective6" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map7 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective7_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective7_donemapicon" + visible 1 + cvartest "team1_objective7" + decoration + showcvar + { + "1" + } + } + + itemDef + { + name team2_met_map8 + group none + style WINDOW_STYLE_SHADER + rectcvar team2_objective8_mappos + forecolor 1 1 1 1 + visible 1 + background "*team2_objective8_donemapicon" + visible 1 + cvartest "team1_objective8" + decoration + showcvar + { + "1" + } + } + + //------------------------------------------- + // OBJECTIVE MINI-DESCRIPTIONS + //------------------------------------------- + itemDef + { + name obj_minidesc + rect 7 378 308 20 + style WINDOW_STYLE_FILLED + backcolor .05 .13 .25 1 + text @MENUS_PRIMARY_OBJECTIVES + font 2 + textscale .7 + textalign ITEM_ALIGN_RIGHT + textalignx 300 + forecolor 1 .68 0 1 + visible 0 + decoration + } + + //------------------------------------------- + // OBJECTIVE LONG DESCRIPTION + //------------------------------------------- + itemDef + { + name obj_longdesc + rect 16 403 270 76 + type ITEM_TYPE_TEXT + font 4 + textscale .8 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 1 + autowrapped + decoration + } + + + //------------------------------------------- + // OBJECTIVE PICTURE + //------------------------------------------- + itemDef + { + name objective_pic + rect 208 398 107 78 + style WINDOW_STYLE_SHADER + background "gfx/2d/select" + forecolor 1 1 1 1 + visible 1 + decoration + } + + //------------------------------------------- + // COMPLETED TEXT + //------------------------------------------- + itemDef + { + name text_tm1_obj1 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective1" + showcvar + { + "1" + } + + } + + itemDef + { + name text_tm1_obj2 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective2" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm1_obj3 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective3" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm1_obj4 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective4" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm1_obj5 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective5" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm1_obj6 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective6" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm1_obj7 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective7" + showcvar + { + "1" + } + } + + itemDef + { + name text_tm1_obj8 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team1_objective8" + showcvar + { + "1" + } + } + + itemDef + { + name text_tm2_obj1 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective1" + showcvar + { + "1" + } + } + + itemDef + { + name text_tm2_obj2 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective2" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm2_obj3 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective3" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm2_obj4 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective4" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm2_obj5 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective5" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm2_obj6 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective6" + showcvar + { + "1" + } + } + + + itemDef + { + name text_tm2_obj7 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective7" + showcvar + { + "1" + } + } + + itemDef + { + name text_tm2_obj8 + group completed_text + rect 210 427 107 78 + type ITEM_TYPE_TEXT + text @MENUS_OBJECTIVE_COMPLETED + textalign ITEM_ALIGN_CENTER + font 2 + textscale .7 + textalignx 53 + textaligny 0 + forecolor 1 1 1 1 + visible 0 + decoration + cvartest "team2_objective8" + showcvar + { + "1" + } + } + + + // Border around the objective text and pic (lower left section of screen) + itemDef + { + name objtext_border + group none + type ITEM_TYPE_TEXT + rect 5 378 310 98 + visible 1 + border 1 + bordercolor 1 1 1 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// MAP TIME SECTION +//---------------------------------------------------------------------------------------------- + + // Border + itemDef + { + name border + group none + type ITEM_TYPE_TEXT + rect 317 5 317 20 + forecolor 1 1 1 .6 + visible 1 + border 1 + bordercolor 1 1 1 1 + decoration + } + + itemDef + { + name playername + group time + type ITEM_TYPE_EDITFIELD + rect 325 6 120 18 + text @MENUS_NAME_TITLE + cvar name + textalign ITEM_ALIGN_LEFT + textalignx 0 + font 4 + textscale 1 + forecolor 1 .68 0 1 + visible 1 + } + + itemDef + { + name playerteam + group time + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 500 6 120 18 + text @MENUS_TEAM + cvar ui_team + cvarStrList + { + + @MENUS_SPECTATOR 0 + "*cg_siegeTeam1Name" 1 + "*cg_siegeTeam2Name" 2 + @MENUS_SPECTATOR 3 + } + + textalign ITEM_ALIGN_LEFT + textalignx 0 + font 4 + textscale 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TEAM SELECTION +//---------------------------------------------------------------------------------------------- + + // join a team text + itemDef + { + name joinateam + rect 318 27 316 20 + style WINDOW_STYLE_FILLED + backcolor .05 .13 .25 1 + text @MENUS_JOIN_A_TEAM + font 3 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 6 + textaligny 2 + forecolor 1 .68 0 1 + visible 1 + decoration + } + + // Border + itemDef + { + name teamborder + group none + type ITEM_TYPE_TEXT + rect 317 26 317 103 + forecolor 1 1 1 .6 + visible 1 + border 1 + bordercolor 1 1 1 1 + decoration + } + + //---------------------------------------- + // TEAM SYMBOLS AND COUNTS PER TEAM + //---------------------------------------- + + itemDef + { + name team1name + group none + rect 355 47 45 45 + cvar cg_siegeTeam1Name + textalign ITEM_ALIGN_CENTER + textalignx 23 + font 4 + textscale 1 + forecolor .8 .48 0 1 + visible 1 + decoration + } + + itemDef + { + + name team1_button + group team_button + rect 355 66 45 45 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + descText @MENUS_JOIN_TEAM_DESC + backcolor .8 .48 0 1 + visible 1 + + mouseEnter + { + setitemcolor team1_button backcolor 1 .68 0 1 + setitemcolor team1_count forecolor 1 .68 0 1 + setitemcolor team1name forecolor 1 .68 0 1 + } + mouseExit + { + setitemcolor team1_button backcolor .8 .48 0 1 + setitemcolor team1_count forecolor .8 .48 0 1 + setitemcolor team1name forecolor .8 .48 0 1 + } + + // If you change this script, be sure to change onteam1 + action + { + // show the class stuff, cause this uses class + show class_button + show class_count + show classdescription + + play "sound/interface/button1.wav" + setcvar ui_holdteam 1 + uiscript updatesiegeclasscnt 1 + uiscript updatesiegecvars + + // Darken the other team buttons + setitemcolor team_button backcolor .8 .48 0 1 + setitemcolor team_count forecolor .8 .48 0 1 + setitemcolor team2name forecolor .8 .48 0 1 + setitemcolor autoteam bordercolor 0 0 0 0 + + // turn on the other team buttons for input + disable team_button 0 + disable autoteam 0 + + // turn on class buttons + disable class_button 0 + + // highlight this team button + setitemcolor team1_button backcolor 1 1 1 1 + setitemcolor team1_count forecolor 1 1 1 1 + setitemcolor team1name forecolor 1 1 1 1 + + // no more input for this guy + disable team1_button 1 + + show grey_button + setitemcolor class_button forecolor .5 .5 .5 1 + + setitemtext class1_count "*ui_tm1_c0_cnt" + setitemtext class2_count "*ui_tm1_c1_cnt" + setitemtext class3_count "*ui_tm1_c2_cnt" + setitemtext class4_count "*ui_tm1_c3_cnt" + setitemtext class5_count "*ui_tm1_c4_cnt" + setitemtext class6_count "*ui_tm1_c5_cnt" + hide class_portrait + show siegeclassconfigtitle + uiscript resetsiegelistboxes + uiscript resetitemdescription + hide feeders + hide forcepowerlevel + hide stats + hide classdescription + setitemcolor class_count forecolor .5 .5 .5 1 + hide itemdescription + } + } + + itemDef + { + name team1_count + group team_count + type ITEM_TYPE_TEXT + rect 355 111 60 18 + cvar ui_tm1_cnt + textalign ITEM_ALIGN_CENTER + textalignx 24 + font 4 + textscale 1 + forecolor .8 .48 0 1 + visible 1 + decoration + } + + // This item is here just to have a script for C to run if the menu is opened by + // a player who is already on a team + itemDef + { + name onteam1 + group none + rect 455 47 45 45 + textalign ITEM_ALIGN_CENTER + textalignx 23 + forecolor .8 .48 0 1 + visible 0 + decoration + action + { + // show the class stuff, cause this uses class + show class_button + show class_count + show classdescription + +// setitemcolorcvar feeders bordercolor "team1_coloron" +// setitemcolorcvar siegeclassconfigtitle forecolor "team1_coloron" + + play "sound/interface/button1.wav" + setcvar ui_holdteam 1 + uiscript updatesiegeclasscnt 1 + uiscript updatesiegecvars + + // Darken the other team buttons + setitemcolor team_button backcolor .8 .48 0 1 + setitemcolor team_count forecolor .8 .48 0 1 + setitemcolor team2name forecolor .8 .48 0 1 + setitemcolor autoteam bordercolor 0 0 0 0 + + // turn on the other team buttons for input + disable team_button 0 + disable autoteam 0 + + // turn on class buttons + disable class_button 0 + + // highlight this team button + setitemcolor team1_button backcolor 1 1 1 1 + setitemcolor team1_count forecolor 1 1 1 1 + setitemcolor team1name forecolor 1 1 1 1 + + // no more input for this guy + disable team1_button 1 + + show grey_button + setitemcolor class_button forecolor .5 .5 .5 1 + + setitemtext class1_count "*ui_tm1_c0_cnt" + setitemtext class2_count "*ui_tm1_c1_cnt" + setitemtext class3_count "*ui_tm1_c2_cnt" + setitemtext class4_count "*ui_tm1_c3_cnt" + setitemtext class5_count "*ui_tm1_c4_cnt" + setitemtext class6_count "*ui_tm1_c5_cnt" + hide class_portrait + show siegeclassconfigtitle + uiscript resetsiegelistboxes + uiscript resetitemdescription + hide feeders + hide forcepowerlevel + hide stats + hide classdescription + setitemcolor class_count forecolor .5 .5 .5 1 + hide itemdescription + } + } + itemDef + { + name team2name + group none + rect 455 47 45 45 + cvar cg_siegeTeam2Name + textalign ITEM_ALIGN_CENTER + textalignx 23 + font 4 + textscale 1 + forecolor .8 .48 0 1 + visible 1 + decoration + } + itemDef + { + name team2_button + group team_button + rect 455 67 45 45 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + descText @MENUS_JOIN_TEAM_DESC + backcolor .8 .48 0 1 + visible 1 + + mouseEnter + { + setitemcolor team2_button backcolor 1 .68 0 1 + setitemcolor team2_count forecolor 1 .68 0 1 + setitemcolor team2name forecolor 1 .68 0 1 + } + mouseExit + { + setitemcolor team2_button backcolor .8 .48 0 1 + setitemcolor team2_count forecolor .8 .48 0 1 + setitemcolor team2name forecolor .8 .48 0 1 + } + + // If you change this script, be sure to change onteam2 + action + { + // show the class stuff, cause this uses class + show class_button + show class_count + show classdescription + + play "sound/interface/button1.wav" + setcvar ui_holdteam 2 + uiscript updatesiegeclasscnt 2 + uiscript updatesiegecvars + + // Darken the other team buttons + setitemcolor team_button backcolor .8 .48 0 1 + setitemcolor team_count forecolor .8 .48 0 1 + setitemcolor team1name forecolor .8 .48 0 1 + setitemcolor autoteam bordercolor 0 0 0 0 + // turn on the other team buttons for input + disable team_button 0 + disable autoteam 0 + + // turn on class buttons + disable class_button 0 + + // highlight this team button + setitemcolor team2_button backcolor 1 1 1 1 + setitemcolor team2_count forecolor 1 1 1 1 + setitemcolor team2name forecolor 1 1 1 1 + // no more input for this guy + disable team2_button 1 + + show grey_button + setitemcolor class_button forecolor .5 .5 .5 1 + setitemtext class1_count "*ui_tm2_c0_cnt" + setitemtext class2_count "*ui_tm2_c1_cnt" + setitemtext class3_count "*ui_tm2_c2_cnt" + setitemtext class4_count "*ui_tm2_c3_cnt" + setitemtext class5_count "*ui_tm2_c4_cnt" + setitemtext class6_count "*ui_tm2_c5_cnt" + + hide class_portrait + show siegeclassconfigtitle + uiscript resetsiegelistboxes + uiscript resetitemdescription + hide feeders + hide forcepowerlevel + hide stats + hide classdescription + setitemcolor class_count forecolor .5 .5 .5 1 + hide itemdescription + } + } + + itemDef + { + name team2_count + group team_count + type ITEM_TYPE_TEXT + rect 455 111 60 18 + cvar ui_tm2_cnt + textalign ITEM_ALIGN_CENTER + textalignx 24 + font 4 + textscale 1 + forecolor .8 .48 0 1 + visible 1 + decoration + } + + // This item is here just to have a script for C to run if the menu is opened by + // a player who is already on a team + itemDef + { + name onteam2 + group none + rect 455 47 45 45 + textalign ITEM_ALIGN_CENTER + textalignx 23 + forecolor .8 .48 0 1 + visible 0 + decoration + action + { + // show the class stuff, cause this uses class + show class_button + show class_count + show classdescription + + play "sound/interface/button1.wav" + setcvar ui_holdteam 2 + uiscript updatesiegeclasscnt 2 + uiscript updatesiegecvars + + // Darken the other team buttons + setitemcolor team_button backcolor .8 .48 0 1 + setitemcolor team_count forecolor .8 .48 0 1 + setitemcolor team1name forecolor .8 .48 0 1 + setitemcolor autoteam bordercolor 0 0 0 0 + // turn on the other team buttons for input + disable team_button 0 + disable autoteam 0 + + // turn on class buttons + disable class_button 0 + + // highlight this team button + setitemcolor team2_button backcolor 1 1 1 1 + setitemcolor team2_count forecolor 1 1 1 1 + setitemcolor team2name forecolor 1 1 1 1 + // no more input for this guy + disable team2_button 1 + + show grey_button + setitemcolor class_button forecolor .5 .5 .5 1 + setitemtext class1_count "*ui_tm2_c0_cnt" + setitemtext class2_count "*ui_tm2_c1_cnt" + setitemtext class3_count "*ui_tm2_c2_cnt" + setitemtext class4_count "*ui_tm2_c3_cnt" + setitemtext class5_count "*ui_tm2_c4_cnt" + setitemtext class6_count "*ui_tm2_c5_cnt" + + hide class_portrait + show siegeclassconfigtitle + uiscript resetsiegelistboxes + uiscript resetitemdescription + hide feeders + hide forcepowerlevel + hide stats + hide classdescription + setitemcolor class_count forecolor .5 .5 .5 1 + hide itemdescription + } + } + + itemDef + { + name team3name + group none + rect 556 69 32 20 + text @MENUS_SPECTATOR + textalign ITEM_ALIGN_CENTER + textalignx 16 + font 4 + textscale 1 + forecolor .8 .48 0 1 + visible 1 + decoration + } + + itemDef + { + name spectator_symbol + group none + rect 556 45 32 32 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + descText @MENUS_WATCH_DESC + backcolor .5 .5 .5 1 + visible 1 + background "gfx/2d/mp_spec_symbol" + + mouseEnter + { + setitemcolor spectator_symbol backcolor 1 1 1 1 + setitemcolor team3name forecolor 1 .68 0 1 + } + mouseExit + { + setitemcolor spectator_symbol backcolor .5 .5 .5 1 + setitemcolor team3name forecolor .8 .48 0 1 + } + action + { + + play "sound/interface/button1.wav" + setcvar ui_holdteam 3 + exec "cmd team s" + uiScript closeingame + } + } + + + + //---------------------------------------- + // AUTOTEAM BUTTON + //---------------------------------------- + itemDef + { + name autoteam_symbol + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_FILLED + descText @MENUS_AUTO_JOIN_DESC + rect 556 85 32 32 + backcolor .5 .5 .5 1 + visible 1 + background "gfx/2d/mp_autoteam_symbol" + + mouseEnter + { + setitemcolor autoteam_symbol backcolor 1 1 1 1 + setitemcolor autoteamname forecolor 1 .68 0 1 + } + mouseExit + { + setitemcolor autoteam_symbol backcolor .5 .5 .5 1 + setitemcolor autoteamname forecolor .8 .48 0 1 + } + action + { + // hide the class stuff, cause this doesn't use class + hide class_button + hide class_count + hide feeders + hide stats + hide classdescription + setitemcolor siegeclassconfigtitle forecolor 1 1 1 1 + + play "sound/interface/button1.wav" + exec "cmd team free" + + uiScript closeingame + } + } + + + itemDef + { + name autoteamname + group none + rect 556 109 32 20 + text @MENUS_CAP_AUTO_JOIN + textalign ITEM_ALIGN_CENTER + textalignx 16 + font 4 + textscale 1 + forecolor .8 .48 0 1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// CLASS SELECTION +//---------------------------------------------------------------------------------------------- + // join a team text + itemDef + { + name siegeclassconfigtitle + rect 318 130 316 20 + style WINDOW_STYLE_FILLED + backcolor .05 .13 .25 1 + text @MENUS_CHOOSE_CLASS + font 3 + textscale .7 + textalign ITEM_ALIGN_LEFT + textalignx 6 + textaligny 2 + forecolor 1 .68 0 1 + visible 0 + decoration + } + + // Border around the objective half of the screen + itemDef + { + name border + group none + type ITEM_TYPE_TEXT + rect 317 130 317 331 + forecolor 1 1 1 .6 + visible 1 + border 1 + bordercolor 1 1 1 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// CLASS 1 - INFANTRY +//---------------------------------------------------------------------------------------------- + itemDef + { + name class1_desc + group class_count + type ITEM_TYPE_TEXT + rect 328 154 45 18 + text @MENUS_SIEGE_ASSAULT + textalign ITEM_ALIGN_CENTER + textalignx 22 + font 4 + textscale .5 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name class1_button + group class_button + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_SHADER + background "gfx/mp/c_icon_infantry" + rect 328 165 45 45 + forecolor .5 .5 .5 1 + visible 1 + descText @MENUS_CLASS_CHOICE_DESC + cvartest "ui_infantry_cnt" + disablecvar { "0" } + + mouseEnter + { + setitemcolor class1_button forecolor 1 1 1 1 + setitemcolor class1_count forecolor 1 1 1 1 + setitemcolor class1_desc forecolor 1 1 1 1 + } + mouseExit + { + setitemcolor class1_button forecolor .5 .5 .5 1 + setitemcolor class1_count forecolor .5 .5 .5 1 + setitemcolor class1_desc forecolor .5 .5 .5 1 + } + + action + { + play "sound/interface/button1.wav" + + setcvar ui_siege_class 0 + uiscript updatesiegecvars + + // Turn other buttons on + setitemcolor class_button forecolor .5 .5 .5 1 + disable class_button 0 + setitemcolor class_count forecolor .5 .5 .5 1 + + // High light this button and disable it + setitemcolor class1_button forecolor 1 1 1 1 + setitemcolor class1_count forecolor 1 1 1 1 + setitemcolor class1_desc forecolor 1 1 1 1 + disable class1_button 1 + +// setitemtext description "*ui_classdesc" + show class_portrait + setitembackground class_portrait "*ui_classportrait" + setitemrect class_portrait 328 162 45 45 + + uiscript resetsiegelistboxes + uiscript resetitemdescription + show description + hide itemdescription + setitemcolor stats forecolor 1 1 1 1 + show feeders + show stats + show forcepowerlevel + } + } + + itemDef + { + name class1_count + group class_count + type ITEM_TYPE_TEXT + rect 328 207 45 18 + textalign ITEM_ALIGN_CENTER + textalignx 23 + font 4 + textscale 1 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name class1_greybutton + group grey_button + style WINDOW_STYLE_FILLED + rect 328 165 45 45 + forecolor .5 .5 .5 .6 + backcolor .5 .5 .5 .6 + visible 1 + cvartest "ui_infantry_cnt" + showcvar { "0" } + decoration + } + +//---------------------------------------------------------------------------------------------- +// CLASS 2 +//---------------------------------------------------------------------------------------------- + itemDef + { + name class2_desc + group class_count + type ITEM_TYPE_TEXT + rect 379 154 45 18 + text @MENUS_SIEGE_HEAVY_WEAPS + textalign ITEM_ALIGN_CENTER + textalignx 20 + font 4 + textscale .5 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name class2_button + group class_button + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_SHADER + background "gfx/mp/c_icon_heavy_weapons" + rect 379 165 45 45 + forecolor .5 .5 .5 1 + visible 1 + descText @MENUS_CLASS_CHOICE_DESC + cvartest "ui_heavy_cnt" + disablecvar { "0" } + + mouseEnter + { + setitemcolor class2_button forecolor 1 1 1 1 + setitemcolor class2_count forecolor 1 1 1 1 + setitemcolor class2_desc forecolor 1 1 1 1 + } + mouseExit + { + setitemcolor class2_button forecolor .5 .5 .5 1 + setitemcolor class2_count forecolor .5 .5 .5 1 + setitemcolor class2_desc forecolor .5 .5 .5 1 + } + + action + { + play "sound/interface/button1.wav" + + setcvar ui_siege_class 5 + uiscript updatesiegecvars + + // Turn other buttons on + setitemcolor class_button forecolor .5 .5 .5 1 + disable class_button 0 + setitemcolor class_count forecolor .5 .5 .5 1 + + // High light this button and disable it + setitemcolor class2_button forecolor 1 1 1 1 + setitemcolor class2_count forecolor 1 1 1 1 + setitemcolor class2_desc forecolor 1 1 1 1 + disable class2_button 1 + +// setitemtext description "*ui_classdesc" + show class_portrait + setitembackground class_portrait "*ui_classportrait" + setitemrect class_portrait 379 162 45 45 + + uiscript resetsiegelistboxes + uiscript resetitemdescription + show description + hide itemdescription + setitemcolor stats forecolor 1 1 1 1 + show feeders + show stats + show forcepowerlevel + } + } + + itemDef + { + name class2_count + group class_count + type ITEM_TYPE_TEXT + rect 379 207 45 18 + textalign ITEM_ALIGN_CENTER + textalignx 23 + font 4 + textscale 1 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name class2_greybutton + group grey_button + style WINDOW_STYLE_FILLED + rect 379 165 45 45 + forecolor .5 .5 .5 .6 + backcolor .5 .5 .5 .6 + visible 1 + cvartest "ui_heavy_cnt" + showcvar { "0" } + decoration + } + +//---------------------------------------------------------------------------------------------- +// CLASS 3 +//---------------------------------------------------------------------------------------------- + itemDef + { + name class3_desc + group class_count + type ITEM_TYPE_TEXT + rect 430 154 45 18 + text @MENUS_SIEGE_DEMOLITIONS + textalign ITEM_ALIGN_CENTER + textalignx 19 + font 4 + textscale .5 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + // The actual button + itemDef + { + name class3_button + group class_button + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_SHADER + background "gfx/mp/c_icon_demolitionist" + rect 430 165 45 45 + forecolor .5 .5 .5 1 + visible 1 + descText @MENUS_CLASS_CHOICE_DESC + cvartest "ui_demo_cnt" + disablecvar { "0" } + + mouseEnter + { + setitemcolor class3_button forecolor 1 1 1 1 + setitemcolor class3_count forecolor 1 1 1 1 + setitemcolor class3_desc forecolor 1 1 1 1 + } + mouseExit + { + setitemcolor class3_button forecolor .5 .5 .5 1 + setitemcolor class3_count forecolor .5 .5 .5 1 + setitemcolor class3_desc forecolor .5 .5 .5 1 + } + + action + { + play "sound/interface/button1.wav" + + setcvar ui_siege_class 4 + uiscript updatesiegecvars + + // Turn other buttons on + setitemcolor class_button forecolor .5 .5 .5 1 + disable class_button 0 + setitemcolor class_count forecolor .5 .5 .5 1 + + // High light this button and disable it + setitemcolor class3_button forecolor 1 1 1 1 + setitemcolor class3_count forecolor 1 1 1 1 + setitemcolor class3_desc forecolor 1 1 1 1 + disable class3_button 1 + +// setitemtext description "*ui_classdesc" + show class_portrait + setitembackground class_portrait "*ui_classportrait" + setitemrect class_portrait 430 162 45 45 + + uiscript resetsiegelistboxes + uiscript resetitemdescription + + show description + hide itemdescription + setitemcolor stats forecolor 1 1 1 1 + show feeders + show stats + show forcepowerlevel + } + } + + itemDef + { + name class3_count + group class_count + type ITEM_TYPE_TEXT + rect 430 207 45 18 + textalign ITEM_ALIGN_CENTER + textalignx 23 + font 4 + textscale 1 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name class3_greybutton + group grey_button + style WINDOW_STYLE_FILLED + rect 430 165 45 45 + forecolor .5 .5 .5 .6 + backcolor .5 .5 .5 .6 + visible 1 + cvartest "ui_demo_cnt" + showcvar { "0" } + decoration + } + +//---------------------------------------------------------------------------------------------- +// CLASS 4 +//---------------------------------------------------------------------------------------------- + itemDef + { + name class4_desc + group class_count + type ITEM_TYPE_TEXT + rect 481 154 45 18 + text @MENUS_SIEGE_SCOUT + textalign ITEM_ALIGN_CENTER + textalignx 21 + font 4 + textscale .5 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + // The actual button + itemDef + { + name class4_button + group class_button + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_SHADER + background "gfx/mp/c_icon_vanguard" + rect 481 165 45 45 + forecolor .5 .5 .5 1 + visible 1 + descText @MENUS_CLASS_CHOICE_DESC + cvartest "ui_vanguard_cnt" + disablecvar { "0" } + + mouseEnter + { + setitemcolor class4_button forecolor 1 1 1 1 + setitemcolor class4_count forecolor 1 1 1 1 + setitemcolor class4_desc forecolor 1 1 1 1 + } + mouseExit + { + setitemcolor class4_button forecolor .5 .5 .5 1 + setitemcolor class4_count forecolor .5 .5 .5 1 + setitemcolor class4_desc forecolor .5 .5 .5 1 + } + action + { + play "sound/interface/button1.wav" + + setcvar ui_siege_class 1 + uiscript updatesiegecvars + + // Turn other buttons on + setitemcolor class_button forecolor .5 .5 .5 1 + disable class_button 0 + setitemcolor class_count forecolor .5 .5 .5 1 + + // High light this button and disable it + setitemcolor class4_button forecolor 1 1 1 1 + setitemcolor class4_count forecolor 1 1 1 1 + setitemcolor class4_desc forecolor 1 1 1 1 + disable class4_button 1 + +// setitemtext description "*ui_classdesc" + show class_portrait + setitembackground class_portrait "*ui_classportrait" + setitemrect class_portrait 481 162 45 45 + + uiscript resetsiegelistboxes + uiscript resetitemdescription + + show description + hide itemdescription + setitemcolor stats forecolor 1 1 1 1 + show feeders + show stats + show forcepowerlevel + } + } + + itemDef + { + name class4_count + group class_count + type ITEM_TYPE_TEXT + rect 481 207 60 18 + textalign ITEM_ALIGN_CENTER + textalignx 24 + font 4 + textscale 1 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name class4_greybutton + group grey_button + style WINDOW_STYLE_FILLED + rect 481 165 45 45 + forecolor .5 .5 .5 .6 + backcolor .5 .5 .5 .6 + visible 1 + cvartest "ui_vanguard_cnt" + showcvar { "0" } + decoration + } + +//---------------------------------------------------------------------------------------------- +// CLASS 5 +//---------------------------------------------------------------------------------------------- + itemDef + { + name class5_desc + group class_count + type ITEM_TYPE_TEXT + rect 532 154 45 18 + text @MENUS_SIEGE_TECH + textalign ITEM_ALIGN_CENTER + textalignx 21 + font 4 + textscale .5 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + // The actual button + itemDef + { + name class5_button + group class_button + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_SHADER + background "gfx/mp/c_icon_support" + rect 532 165 45 45 + forecolor .5 .5 .5 1 + visible 1 + descText @MENUS_CLASS_CHOICE_DESC + cvartest "ui_support_cnt" + disablecvar { "0" } + + mouseEnter + { + setitemcolor class5_button forecolor 1 1 1 1 + setitemcolor class5_count forecolor 1 1 1 1 + setitemcolor class5_desc forecolor 1 1 1 1 + } + mouseExit + { + setitemcolor class5_button forecolor .5 .5 .5 1 + setitemcolor class5_count forecolor .5 .5 .5 1 + setitemcolor class5_desc forecolor .5 .5 .5 1 + } + action + { + play "sound/interface/button1.wav" + + setcvar ui_siege_class 2 + uiscript updatesiegecvars + + // Turn other buttons on + setitemcolor class_button forecolor .5 .5 .5 1 + disable class_button 0 + setitemcolor class_count forecolor .5 .5 .5 1 + + // High light this button and disable it + setitemcolor class5_button forecolor 1 1 1 1 + setitemcolor class5_count forecolor 1 1 1 1 + setitemcolor class5_desc forecolor 1 1 1 1 + disable class5_button 1 + +// setitemtext description "*ui_classdesc" + show class_portrait + setitembackground class_portrait "*ui_classportrait" + setitemrect class_portrait 532 162 45 45 + + uiscript resetsiegelistboxes + uiscript resetitemdescription + + show description + hide itemdescription + setitemcolor stats forecolor 1 1 1 1 + show feeders + show stats + show forcepowerlevel + } + } + + itemDef + { + name class5_count + group class_count + type ITEM_TYPE_TEXT + rect 532 207 60 18 + textalign ITEM_ALIGN_CENTER + textalignx 23 + font 4 + textscale 1 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name class5_greybutton + group grey_button + style WINDOW_STYLE_FILLED + rect 532 165 45 45 + forecolor .5 .5 .5 .6 + backcolor .5 .5 .5 .6 + visible 1 + cvartest "ui_support_cnt" + showcvar { "0" } + decoration + } + +//-------------------------------------------------- +// CLASS 6 +//-------------------------------------------------- + itemDef + { + name class6_desc + group class_count + type ITEM_TYPE_TEXT + rect 583 154 45 18 + text @MENUS_SIEGE_JEDI + textalign ITEM_ALIGN_CENTER + textalignx 21 + font 4 + textscale .5 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + // The actual button + itemDef + { + name class6_button + group class_button + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_SHADER + background "gfx/mp/c_icon_jedi_general" + rect 583 165 45 45 + forecolor .5 .5 .5 1 + visible 1 + descText @MENUS_CLASS_CHOICE_DESC + cvartest "ui_jedi_cnt" + disablecvar { "0" } + mouseEnter + { + setitemcolor class6_button forecolor 1 1 1 1 + setitemcolor class6_count forecolor 1 1 1 1 + setitemcolor class6_desc forecolor 1 1 1 1 + } + mouseExit + { + setitemcolor class6_button forecolor .5 .5 .5 1 + setitemcolor class6_count forecolor .5 .5 .5 1 + setitemcolor class6_desc forecolor .5 .5 .5 1 + } + + action + { + play "sound/interface/button1.wav" + + setcvar ui_siege_class 3 + uiscript updatesiegecvars + + // Turn other buttons on + setitemcolor class_button forecolor .5 .5 .5 1 + disable class_button 0 + setitemcolor class_count forecolor .5 .5 .5 1 + + // High light this button and disable it + setitemcolor class6_button forecolor 1 1 1 1 + setitemcolor class6_count forecolor 1 1 1 1 + setitemcolor class6_desc forecolor 1 1 1 1 + disable class6_button 1 + +// setitemtext description "*ui_classdesc" + show class_portrait + setitembackground class_portrait "*ui_classportrait" + setitemrect class_portrait 583 162 45 45 + + uiscript resetsiegelistboxes + uiscript resetitemdescription + + show description + hide itemdescription + setitemcolor stats forecolor 1 1 1 1 + show feeders + show stats + show forcepowerlevel + } + } + + itemDef + { + name class6_count + group class_count + type ITEM_TYPE_TEXT + rect 583 207 60 18 + textalign ITEM_ALIGN_CENTER + textalignx 23 + font 4 + textscale 1 + forecolor .5 .5 .5 1 + visible 1 + decoration + } + + itemDef + { + name class6_greybutton + group grey_button + style WINDOW_STYLE_FILLED + rect 583 165 45 45 + forecolor .5 .5 .5 .6 + backcolor .5 .5 .5 .6 + visible 1 + cvartest "ui_jedi_cnt" + showcvar { "0" } + decoration + } + +//-------------------------------------------------- +// CLASS PORTRAIT - this gets its background value from ui_classportrait +//-------------------------------------------------- + itemDef + { + name class_portrait + style WINDOW_STYLE_FILLED + rect 526 176 45 45 + backcolor 1 1 1 1 + visible 0 + decoration + } + +//----------------------------------------------- +// +// DISPLAY HEALTH, SHIELD, SPEED +// +//----------------------------------------------- + + itemDef + { + name health_disp + group stats + rect 328 230 110 20 + type ITEM_TYPE_EDITFIELD + text @SIEGE_HEALTH + cvar "ui_class_health" + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + font 4 + textscale .7 + visible 0 + forecolor .5 .5 .5 1 + decoration + } + + itemDef + { + name armor_disp + group stats + rect 438 230 100 20 + type ITEM_TYPE_EDITFIELD + text @SIEGE_SHIELDS + cvar "ui_class_armor" + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + font 4 + textscale .7 + forecolor .5 .5 .5 1 + visible 0 + decoration + } + + itemDef + { + name speed_disp + group stats + rect 548 230 100 20 + type ITEM_TYPE_EDITFIELD + text @SIEGE_SPEED + cvar "ui_class_speed" + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + font 4 + textscale .7 + visible 0 + forecolor .5 .5 .5 1 + decoration + } + +//-------------------------------------------------- +// BASE CLASS WEAPONS FEEDER +//-------------------------------------------------- + itemDef + { + name base_class_weapons_feed + group feeders + type ITEM_TYPE_LISTBOX + desctext @MENUS_CLASS_WEAPONS + style WINDOW_STYLE_FILLED + elementwidth 34 + elementheight 34 + elementtype LISTBOX_IMAGE + feeder FEEDER_SIEGE_CLASS_WEAPONS + rect 326 246 148 52 + horizontalscroll + backcolor 0 0 0 0 + border 1 + bordercolor 1 1 1 1 + forecolor 1 1 1 1 + visible 1 + textscale 0.7 + action + { + hide description + show itemdescription + uiscript updatesiegeweapondesc + uiscript resetitemdescription + } + + } + + +//-------------------------------------------------- +// BASE CLASS INVENTORY FEEDER +//-------------------------------------------------- + itemDef + { + name base_class_inventory_feed + group feeders + desctext @MENUS_CLASS_INVENTORY + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 34 + elementheight 34 + elementtype LISTBOX_IMAGE + feeder FEEDER_SIEGE_CLASS_INVENTORY + rect 479 246 148 52 + horizontalscroll + backcolor 0 0 0 0 + border 1 + bordercolor 1 1 1 1 + forecolor 1 1 1 1 + visible 1 + textscale 0.7 + action + { + hide description + show itemdescription + uiscript updatesiegeinventorydesc + uiscript resetitemdescription + } + + } + +//-------------------------------------------------- +// BASE CLASS FORCE POWER FEEDER +//-------------------------------------------------- + itemDef + { + name base_class_force_feed + group feeders + type ITEM_TYPE_LISTBOX + desctext @MENUS_CLASS_FORCEPOWERS + style WINDOW_STYLE_FILLED + elementwidth 34 + elementheight 34 + elementtype LISTBOX_IMAGE + feeder FEEDER_SIEGE_CLASS_FORCE + rect 320 299 311 52 + horizontalscroll + backcolor 0 0 0 0 + border 1 + bordercolor 1 1 1 1 + forecolor 1 1 1 1 + visible 1 + textscale 0.7 + action + { + hide description + show itemdescription + uiscript updatesiegeforcedesc + uiscript resetitemdescription + } + + } + + itemDef + { + name description + group classdescription + rect 327 356 300 85 + type ITEM_TYPE_TEXTSCROLL + text " " + cvar ui_classdesc + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 0 + lineHeight 13 + } + + itemDef + { + name slot0 + group forcepowerlevel + rect 350 322 300 80 + type ITEM_TYPE_TEXT + font 4 + textscale .5 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 0 + cvar ui_class_powerlevelslot0 + cvartest ui_class_powerlevelslot0 + hidecvar + { + "0" + } + } + + itemDef + { + name slot1 + group forcepowerlevel + rect 384 322 300 80 + type ITEM_TYPE_TEXT + cvar ui_itemforceinvdesc + font 4 + textscale .5 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 0 + cvar ui_class_powerlevelslot1 + cvartest ui_class_powerlevelslot1 + hidecvar + { + "0" + } + } + + itemDef + { + name slot2 + group forcepowerlevel + rect 418 322 300 80 + type ITEM_TYPE_TEXT + cvar ui_itemforceinvdesc + font 4 + textscale .5 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 0 + cvar ui_class_powerlevelslot2 + cvartest ui_class_powerlevelslot2 + hidecvar + { + "0" + } + } + + itemDef + { + name slot3 + group forcepowerlevel + rect 452 322 300 80 + type ITEM_TYPE_TEXT + cvar ui_itemforceinvdesc + font 4 + textscale .5 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 0 + cvar ui_class_powerlevelslot3 + cvartest ui_class_powerlevelslot3 + hidecvar + { + "0" + } + } + + itemDef + { + name slot4 + group forcepowerlevel + rect 486 322 300 80 + type ITEM_TYPE_TEXT + cvar ui_itemforceinvdesc + font 4 + textscale .5 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 0 + cvar ui_class_powerlevelslot4 + cvartest ui_class_powerlevelslot4 + hidecvar + { + "0" + } + } + + itemDef + { + name slot5 + group forcepowerlevel + rect 520 322 300 80 + type ITEM_TYPE_TEXT + cvar ui_itemforceinvdesc + font 4 + textscale .5 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 0 + cvar ui_class_powerlevelslot5 + cvartest ui_class_powerlevelslot5 + hidecvar + { + "0" + } + } + + itemDef + { + name slot6 + group forcepowerlevel + rect 554 322 300 80 + type ITEM_TYPE_TEXT + cvar ui_itemforceinvdesc + font 4 + textscale .5 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 1 + cvar ui_class_powerlevelslot6 + cvartest ui_class_powerlevelslot6 + hidecvar + { + "0" + } + } + + itemDef + { + name slot7 + group forcepowerlevel + rect 588 322 300 80 + type ITEM_TYPE_TEXT + cvar ui_itemforceinvdesc + font 4 + textscale .5 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 1 + cvar ui_class_powerlevelslot7 + cvartest ui_class_powerlevelslot7 + hidecvar + { + "0" + } + } + + itemDef + { + name slot8 + group forcepowerlevel + rect 620 322 300 80 + type ITEM_TYPE_TEXT + cvar ui_itemforceinvdesc + font 4 + textscale .5 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 1 + cvar ui_class_powerlevelslot8 + cvartest ui_class_powerlevelslot8 + hidecvar + { + "0" + } + } + + itemDef + { + name itemdescription + group itemdescription + rect 327 356 300 85 + type ITEM_TYPE_TEXTSCROLL + text " " + cvar ui_itemforceinvdesc + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + forecolor 1 1 1 1 + visible 0 + lineHeight 13 + } + + //----------------------------------------------- + // CANCEL BUTTON + //----------------------------------------------- + itemDef + { + name cancel + text @MENUS_CANCEL + type 1 + textscale .8 + group grpControlbutton + type ITEM_TYPE_BUTTON + rect 325 460 150 26 + textalign ITEM_ALIGN_CENTER + textalignx 75 + forecolor 1 .682 0 1 + backcolor .37 .1 .1 1 + visible 1 + descText @MENUS_CANCEL_DESC + action + { + play "sound/interface/button1.wav" + hide stats + hide forcepowerlevel + close all + } + } + //----------------------------------------------- + // OKAY BUTTON + //----------------------------------------------- + itemDef + { + name okay + text @MENUS_OKAY + type 1 + textscale .8 + group grpControlbutton + type ITEM_TYPE_BUTTON + rect 480 460 150 26 + textalign ITEM_ALIGN_CENTER + textalignx 75 + forecolor 1 .682 0 1 + backcolor .37 .1 .1 1 + visible 1 + descText @MENUS_JOIN_GAME + action + { + play "sound/interface/button1.wav" + uiScript setsiegeclassandteam + hide stats + hide forcepowerlevel + uiScript closeingame + } + } + + } +} \ No newline at end of file diff --git a/base/ui/jamp/siege_msg.menu b/base/ui/jamp/siege_msg.menu new file mode 100644 index 0000000..a1cf8f6 --- /dev/null +++ b/base/ui/jamp/siege_msg.menu @@ -0,0 +1,123 @@ +//----------------------------------------------------------------- +// seige_msg.menu +// +// Popup menu that displays messages at the end of a siege game +// +//----------------------------------------------------------------- +{ +\\ END OF GAME \\ + menuDef + { + name "siege_popmenu" + visible 0 + fullscreen 0 + rect 158 80 320 320 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + popup + onClose + { + } + onOpen + { + } + onESC + { + close siege_popmenu + open main + } + + itemDef + { + name window + rect 10 15 300 320 + style 1 + backcolor .015 .015 .229 0.25 + forecolor 0 0 0 1 + border 1 + bordercolor .388 .396 .925 1 + bordersize 5 + visible 1 + decoration + } + + + itemDef + { + name siegeinfo + rect 0 50 320 20 + text @MENUS_DATAPAD + textalign 1 + textstyle 6 + textscale 1 + textalignx 160 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name siegeinfo + rect 60 80 200 270 + type ITEM_TYPE_TEXT + style 1 + textstyle 3 + autowrapped + cvar "cg_siegeMessage" + textalign ITEM_ALIGN_CENTER + textalignx 100 + textscale 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + +// BUTTON // + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 118 295 85 26 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exit + group grpControlbutton + text @MENUS_OKAY + type 1 + textscale 1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 138 295 45 26 + textalign 1 + textalignx 22 + forecolor .79 .64 .22 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + close siege_popmenu + } + mouseEnter + { + show button_glow + } + mouseExit + { + hide button_glow + } + } + } +} + + + + diff --git a/base/ui/jamp/siege_team.menu b/base/ui/jamp/siege_team.menu new file mode 100644 index 0000000..75de0ca --- /dev/null +++ b/base/ui/jamp/siege_team.menu @@ -0,0 +1,464 @@ +//------------------------------------------------------------------------------------------------ +// SIEGE_TEAM - player chooses team and then from here they choose base class. +// +// ui_team 1 = RED +// ui_team 2 = BLUE +// ui_team 3 = SPECTATOR +// +//------------------------------------------------------------------------------------------------ +{ + menuDef + { + name "ingame_siegeteam" + visible 0 + fullscreen 0 + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + rect 95 34 460 435 + focusColor 1 1 1 1 // Focus color for text and items + style 1 + border 1 + descX 320 + descY 440 + descScale 0.7 + descColor .79 .64 .22 .7 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + onOpen + { + uiScript update ui_GetName + uiScript updateForceStatus + hide nextbutton + hide glow + setitemcolor team1_button forecolor .670 .670 .929 1 + setitemcolor team2_button forecolor .670 .670 .929 1 + } + onClose + { +// uiScript update ui_SetName +// uiScript updateForceStatus + } + + // Overall window backdrop + itemDef + { + name background_pic + group none + style WINDOW_STYLE_SHADER + rect 0 0 460 435 + background "gfx/menus/menu_box_ingame" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------- +// +// TITLE BAR +// +//---------------------------------------- + itemDef + { + name siegeclassconfigtitle + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text "TEAM CHOICE" + rect 20 5 420 28 + textalign ITEM_ALIGN_CENTER + textalignx 210 + textaligny 2 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 3 + textscale 0.9 + forecolor 1 1 1 1 + border 0 + bordercolor 0 0 0 0 + visible 1 + } + +//---------------------------------------- +// +// TEAM SYMBOLS +// +//---------------------------------------- + + itemDef + { + name team1_impsymbol + group team1_symbol + style WINDOW_STYLE_SHADER + background "gfx/2d/mp_imp_symbol" + cvarTest cg_siegeTeam1Name + showCVar { "Imperial" } + rect 102 87 256 256 + forecolor 1 1 1 .3 + visible 1 + decoration + } + + itemDef + { + name team1_rebelsymbol + group team1_symbol + style WINDOW_STYLE_SHADER + background "gfx/2d/mp_rebel_symbol" + cvarTest cg_siegeTeam1Name + showCVar { "Rebel" } + rect 102 87 256 256 + forecolor 1 1 1 .3 + visible 1 + decoration + } + + itemDef + { + name team2_impsymbol + group team2_symbol + style WINDOW_STYLE_SHADER + background "gfx/2d/mp_imp_symbol" + cvarTest cg_siegeTeam2Name + showCVar { "Imperial" } + rect 102 87 256 256 + forecolor 1 1 1 .3 + visible 0 + decoration + } + + itemDef + { + name team2_rebelsymbol + group team2_symbol + style WINDOW_STYLE_SHADER + background "gfx/2d/mp_rebel_symbol" + cvarTest cg_siegeTeam2Name + showCVar { "Rebel" } + rect 102 87 256 256 + forecolor 1 1 1 .3 + visible 0 + decoration + } + +//---------------------------------------- +// +// NAME ENTRY FIELD +// +//---------------------------------------- + itemDef + { + name namefield + type ITEM_TYPE_EDITFIELD + style 0 + text @MENUS_NAME + cvar ui_Name + maxchars 26 + rect 80 80 300 20 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -4 + font 0 + textscale 1 + forecolor .670 .670 .929 1 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + border 0 + bordercolor 0 0 0 0 + descText @MENUS_ENTER_YOUR_NAME_HERE + visible 1 + mouseEnter + { + show namefield_glow + } + mouseExit + { + hide namefield_glow + } + } + + // The high light + itemDef + { + name namefield_glow + group glow + style WINDOW_STYLE_SHADER + rect 10 80 400 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------- +// TEAM1 BUTTON +//---------------------------------------- + itemDef + { + name team1_button + type ITEM_TYPE_BUTTON + cvar cg_siegeTeam1Name + rect 165 150 130 16 + textalign ITEM_ALIGN_CENTER + textalignx 65 + textaligny -4 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale 0.9 + forecolor .670 .670 .929 1 + border 0 + bordercolor 0 0 0 0 + descText "Join this team." + + visible 1 + mouseEnter + { + show team1_glow + } + mouseExit + { + hide team1_glow + } + action + { + play "sound/interface/button1.wav" + hide team2_symbol + show team1_symbol + exec "cmd team r" + setcvar ui_team 1 + uiscript updatesiegeclasscnt 1 + uiscript updatesiegecvars +// show nextbutton + +// setitemcolor team1_button forecolor 1 1 .807 1 +// setitemcolor team2_button forecolor .670 .670 .929 1 + + uiScript setsiegeclass + close all + open ingame_siegeclass + + } + } + + // The high light + itemDef + { + name team1_glow + group glow + style WINDOW_STYLE_SHADER + rect 165 145 130 26 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------- +// TEAM2 BUTTON +//---------------------------------------- + itemDef + { + name team2_button + type ITEM_TYPE_BUTTON + cvar cg_siegeTeam2Name + rect 165 200 130 16 + textalign ITEM_ALIGN_CENTER + textalignx 65 + textaligny -4 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale 0.9 + forecolor .670 .670 .929 1 + border 0 + bordercolor 0 0 0 0 + descText "Join this team." + + visible 1 + action + { + play "sound/interface/button1.wav" + hide team1_symbol + show team2_symbol + exec "cmd team b" + setcvar ui_team 2 + uiscript updatesiegeclasscnt 2 + uiscript updatesiegecvars +// show nextbutton + +// setitemcolor team1_button forecolor .670 .670 .929 1 +// setitemcolor team2_button forecolor 1 1 .807 1 + + uiScript setsiegeclass + close all + open ingame_siegeclass + + } + mouseEnter + { + show team2_glow + } + mouseExit + { + hide team2_glow + } + } + + // The high light + itemDef + { + name team2_glow + group glow + style WINDOW_STYLE_SHADER + rect 165 195 130 26 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------- +// AUTOTEAM BUTTON +//---------------------------------------- + itemDef + { + name autoteam + text @MENUS_AUTO_TEAM + type 1 + style 2 + rect 165 250 130 16 + textalign ITEM_ALIGN_CENTER + textalignx 65 + textaligny -4 + outlinecolor 1 .5 .5 .5 + backcolor 0 0 0 0 + font 2 + textscale 0.9 + forecolor .670 .670 .929 1 + descText "Let server automatically decide the team." + visible 1 + action + { + play "sound/interface/button1.wav" + exec "cmd team free" + uiScript closeingame + } + mouseEnter + { + show autoteam_glow + } + mouseExit + { + hide autoteam_glow + } + } + + // The high light + itemDef + { + name autoteam_glow + group glow + style WINDOW_STYLE_SHADER + rect 165 245 130 26 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------- +// SPECTATOR BUTTON +//---------------------------------------- + itemDef + { + name spectate + text @MENUS_SPECTATE + type 1 + style 2 + rect 165 300 130 16 + textalign ITEM_ALIGN_CENTER + textalignx 65 + textaligny -4 + font 2 + textscale .9 + forecolor .670 .670 .929 1 + visible 1 + descText "Watch the game." + action + { + play "sound/interface/button1.wav" + exec "cmd team s" + uiScript closeingame + } + mouseEnter + { + show spectate_glow + } + mouseExit + { + hide spectate_glow + } + } + + // The high light + itemDef + { + name spectate_glow + group glow + style WINDOW_STYLE_SHADER + rect 165 295 130 26 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------- +// NEXT BUTTON +//---------------------------------------- +/* itemDef + { + name nextbutton + group playerapply + text @MENUS_NEXT + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 10 370 440 16 + textalign ITEM_ALIGN_CENTER + textalignx 220 + textaligny -4 + font 2 + textscale .9 + forecolor .79 .64 .22 1 + descText "Select a class on the chosen team." + visible 0 + action + { + play "sound/interface/button1.wav" + uiScript setsiegeclass + close all + open ingame_siegeclass + } + mouseEnter + { + show next_glow + } + mouseExit + { + hide next_glow + } + } + + // The high light + itemDef + { + name next_glow + group glow + style WINDOW_STYLE_SHADER + rect 10 365 440 26 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + } */ +} + diff --git a/base/ui/jamp/vid_warning.menu b/base/ui/jamp/vid_warning.menu new file mode 100644 index 0000000..f9cfb51 --- /dev/null +++ b/base/ui/jamp/vid_warning.menu @@ -0,0 +1,205 @@ +// VIDEO WARNING +{ + menuDef + { + name "videowarningMenu" + visible 0 + fullScreen 0 // MENU_TRUE + rect 0 0 640 480 + focusColor 1 1 1 1 // Focus color for text and items + outOfBoundsClick // this closes the window if it gets a click out of the rectangle // In miliseconds + descX 320 + descY 434 + descScale 1 + descColor .79 .64 .22 .7 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + popup + + onESC + { + play "sound/interface/button1.wav" ; + close "videowarningMenu" ; + setcvar "ui_r_modified" 0 ; + rundeferred ; + } + + onClose + { + play "sound/interface/button1.wav" ; + } + + itemDef + { + name warn_background + group none + style WINDOW_STYLE_SHADER + rect 100 100 440 320 + background "gfx/menus/menu_boxred" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// +// VIDEO WARNING +// +//---------------------------------------------------------------------------------------------- + // Video Warning title + itemDef + { + name vidwarn_title + group vidwarn + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_UNAPPLIED_VIDEO_CHANGES + rect 120 129 400 20 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny -3 + forecolor 1 1 1 1 + visible 1 + decoration + } + + + itemDef + { + name vidwarn_text1 + group vidwarn + text @MENUS_YOU_HAVE_MADE_CHANGES + rect 210 230 220 20 + textalign ITEM_ALIGN_CENTER + text2aligny 18 + textalignx 110 + font 2 + textscale 1 + forecolor .79 .64 .22 1 + visible 1 + decoration + } + + itemDef + { + name vidwarn_text2 + group vidwarn + text @MENUS_APPLY_THESE_CHANGES_OR + rect 210 250 220 20 + textalign ITEM_ALIGN_CENTER + textalignx 110 + font 2 + textscale 1 + forecolor .79 .64 .22 1 + visible 1 + } + +// DISCARD button - return to Video Data screen + itemDef + { + name vidwarn_no_button + group vidwarn + style WINDOW_STYLE_SHADER + rect 180 386 120 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name vidwarn_no + group vidwarn + text @MENUS_DISCARD + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 180 386 120 24 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -3 + descText @MENUS_DO_NOT_APPLY_CHANGES + forecolor .79 .64 .22 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + close "videowarningMenu" ; + + setcvar "ui_r_modified" 0 ; + + rundeferred ; + } + + mouseEnter + { + show vidwarn_no_button + } + + mouseExit + { + hide vidwarn_no_button + } + } + +// APPLY button, use settings + itemDef + { + name vidwarn_yes_button + group vidwarn + style WINDOW_STYLE_SHADER + rect 340 386 120 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name vidwarn_yes + group vidwarn + text @MENUS_APPLY + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 386 120 24 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -3 + descText @MENUS_APPLY_CHANGES_AND_THEN + forecolor .79 .64 .22 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + + uiScript updatevideosetup ; + +// uiScript setvid1data setupMenu ; // Set video settings + close all + } + mouseEnter + { + show vidwarn_yes_button + } + mouseExit + { + hide vidwarn_yes_button + } + } + + } +} + + + + + + diff --git a/base/ui/jamp/videodriver.menu b/base/ui/jamp/videodriver.menu new file mode 100644 index 0000000..18b5cdb --- /dev/null +++ b/base/ui/jamp/videodriver.menu @@ -0,0 +1,114 @@ +// VIDEODRIVER MENU +{ + menuDef + { + name "videodriverMenu" + visible 0 + fullScreen 0 // MENU_TRUE + rect 100 120 440 320 + focusColor 1 1 1 1 // Focus color for text and items + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + descX 320 + descY 450 + descScale 1 + descColor .79 .64 .22 .7 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + popup + + onESC + { + play "sound/interface/button1.wav" + close all + open setupMenu + } + + onClose + { + play "sound/interface/button1.wav" ; + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name driver_background + group none + style WINDOW_STYLE_SHADER + rect 0 0 440 320 + background "gfx/menus/menu_boxred" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name driver_done_button + group none + style WINDOW_STYLE_SHADER + rect 150 293 140 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name driver_done + group none + text @MENUS_DONE_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 150 293 140 24 + forecolor 1 1 1 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 70 + textaligny 0 + font 3 + textscale 1 + forecolor .79 .64 .22 1 + visible 1 + + mouseEnter + { + show driver_done_button + } + mouseExit + { + hide driver_done_button + } + action + { + close videodriverMenu + } + } + + + //---------------------------------------------------------------------------------------------- + // + // VIDEO DRIVER INFO + // + //---------------------------------------------------------------------------------------------- + itemDef // Drivers + { + name game_version + group none + ownerdraw UI_GLINFO // UI_GLINFO + font 4 + textscale 1 + rect 10 3 430 280 + forecolor .79 .64 .22 1 + style WINDOW_STYLE_EMPTY + textalign ITEM_ALIGN_LEFT + textaligny 14 + visible 1 + decoration + } + } +} diff --git a/base/ui/jamp/vssver.scc b/base/ui/jamp/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..5df1befb0e0a21ee27023a48aa55c15963d6822d GIT binary patch literal 880 zcmW;KdoWZ{90%}S%A>5uTCZN3ty$|4k`OJ0RyJbtDC{)RYiLq-#*VzoWJE?YjYdK? zL+O~o&NjP54~*A{K}u3Isc0L?nAoJ!chCLf-ut=d{LVSQ-|xD!nG9(c`*h)Sb70NH zcJY>Y%|n)3c%+>vgI;_*OEcom`Ex%JV|=lPxmSt0^2$e|13SPmU2d17yJm?A>aIj3Br54*!ox7wcT zbQV$XhWllrl;g42X+9pX=_)6_#VeoM6AoA|pRtsc(EYsN#tRyaB&(R7cMlv=_%r2Zbg%OGVYh2=NoJ*kv$f{E0bX!sC|s=i7xm2bGM* bufnw)*3k4)7IlpkgYmaM)sF2gqWS&@I6Ve- literal 0 HcmV?d00001 diff --git a/base/ui/jampingame.txt b/base/ui/jampingame.txt new file mode 100644 index 0000000..0fdbaa7 --- /dev/null +++ b/base/ui/jampingame.txt @@ -0,0 +1,35 @@ +//--------------------------------------------------------- +// jk2mpingame.txt +// +// .menu files to load for the JAMP ingame menu system +// +//--------------------------------------------------------- +{ + loadMenu { "ui/jamp/connect.menu" } + + loadMenu { "ui/jamp/ingame_about.menu" } + loadMenu { "ui/jamp/ingame_addbot.menu" } + loadMenu { "ui/jamp/ingame_callvote.menu" } + loadMenu { "ui/jamp/ingame_controls.menu" } + loadMenu { "ui/jamp/ingame_join.menu" } + + loadMenu { "ui/jamp/ingame_leave.menu" } + loadMenu { "ui/jamp/ingame.menu" } + loadMenu { "ui/jamp/ingame_player.menu" } + loadMenu { "ui/jamp/ingame_player2.menu" } + loadMenu { "ui/jamp/ingame_playerforce.menu" } + + loadMenu { "ui/jamp/ingame_saber.menu" } + loadMenu { "ui/jamp/ingame_setup.menu" } + loadMenu { "ui/jamp/ingame_siegeobjectives.menu" } + loadMenu { "ui/jamp/ingame_vote.menu" } + loadMenu { "ui/jamp/ingame_orders.menu" } + + loadMenu { "ui/jamp/ingame_voicechat.menu" } + loadMenu { "ui/jamp/siege_msg.menu" } + loadMenu { "ui/jamp/siege_class.menu" } + loadMenu { "ui/jamp/vid_warning.menu" } + + loadMenu { "ui/jamp/ingame_objectives.menu" } +} + diff --git a/base/ui/jampmenus.txt b/base/ui/jampmenus.txt new file mode 100644 index 0000000..658b19d --- /dev/null +++ b/base/ui/jampmenus.txt @@ -0,0 +1,37 @@ +// menu defs +// +{ + loadMenu { "ui/jamp/main.menu" } + loadMenu { "ui/jamp/joinserver.menu" } + loadMenu { "ui/jamp/player.menu" } + loadMenu { "ui/jamp/player2.menu" } + loadMenu { "ui/jamp/createserver.menu" } + loadMenu { "ui/jamp/connect.menu" } + loadMenu { "ui/jamp/rules.menu" } + loadMenu { "ui/jamp/rules_games.menu" } + loadMenu { "ui/jamp/rules_force.menu" } + loadMenu { "ui/jamp/rules_weapons.menu" } + loadMenu { "ui/jamp/rules_items.menu" } + loadMenu { "ui/jamp/rules_moves.menu" } + loadMenu { "ui/jamp/controls.menu" } + loadMenu { "ui/jamp/setup.menu" } + loadMenu { "ui/jamp/demo.menu" } + loadMenu { "ui/jamp/password.menu" } + loadMenu { "ui/jamp/quit.menu" } + loadMenu { "ui/jamp/error.menu" } + loadMenu { "ui/jamp/serverinfo.menu" } + loadMenu { "ui/jamp/findplayer.menu" } + loadMenu { "ui/jamp/createfavorite.menu" } + loadMenu { "ui/jamp/multiplayer.menu" } + loadMenu { "ui/jamp/advancedcreateserver.menu" } + loadMenu { "ui/jamp/saber.menu" } + loadMenu { "ui/jamp/quickgame.menu" } + loadMenu { "ui/jamp/quickgame2.menu" } + loadMenu { "ui/jamp/quickbots.menu" } + + + loadMenu { "ui/jamp/vid_warning.menu" } + loadMenu { "ui/jamp/videodriver.menu" } + loadMenu { "ui/jamp/password_request.menu" } + +} diff --git a/base/ui/loadgame.menu b/base/ui/loadgame.menu new file mode 100644 index 0000000..541b9ca --- /dev/null +++ b/base/ui/loadgame.menu @@ -0,0 +1,666 @@ +//------------------------------------------------------------------------------------------------ +// LOADGAME MENU +// +// Load save games screen called from main menu. +// +//------------------------------------------------------------------------------------------------ +{ + menuDef + { + // ***IMPORTANT*** This name can not change, code looks for it. + // If it must change, change it in UI_AdjustSaveGameListBox() also + // + name "loadgameMenu" + visible 0 + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + + onESC + { + play "sound/interface/esc.wav" + close all + open mainMenu + } + onOpen + { + uiScript ReadSaveDirectory ; + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Big button "NEW" + itemDef + { + name newbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newbutton_glow + } + mouseExit + { + hide newbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open newgameMenu + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setupMenu ; + } + } + + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name backbutton + group exit + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open mainMenu + } + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // LOAD GAME MENU specific stuff + // + //---------------------------------------------------------------------------------------------- + // Load Game title + itemDef + { + name loadgame_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_LOAD_GAME + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + // ***IMPORTANT*** This name can not change, code looks for it. + // If it must change, change it in UI_AdjustSaveGameListBox() also + // + name loadgamelist + group loadscreen + rect 40 200 360 190 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 16 + font 2 + textaligny 8 + textscale 0.7 + border 1 + bordersize 1 + bordercolor 0 0 .8 1 + forecolor .615 .615 .956 1 + +// forecolor 1 .682 0 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder FEEDER_SAVEGAMES + notselectable + visible 1 + columns 2 2 55 150 155 0 200 //#of col., x loc. of 1st column, 2 number doesn't seem to do anything, third is width of column + mouseEnter + { + setitemcolor loadgamelist bordercolor 1 1 1 1 + } + mouseExit + { + setitemcolor loadgamelist bordercolor 0 0 .8 1 + } + doubleclick + { + play sound/interface/button1.wav + uiScript loadgame + } + action + { + play sound/interface/sub_select + } + } + + itemDef + { + name loadgamepic + group loadscreen + style WINDOW_STYLE_EMPTY + ownerdraw 236 //UI_ALLMAPS_SELECTION + font 2 + textscale .8 + forecolor .549 .854 1 1 + rect 435 200 180 135 + border 1 + bordercolor 0 0 .8 1 + visible 1 + decoration + } + + // loadgame button + itemDef + { + name loadgameaction_glow + group glow + style WINDOW_STYLE_SHADER + rect 440 360 190 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgameaction + group actionbutton + text @MENUS_LOAD_GAME + descText @MENUS_LOAD_CHOSEN_GAME + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 440 360 190 20 + font 3 + textscale 0.8 + textalignx 175 + textaligny 1 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + cvarTest ui_SelectionOK + enableCvar { "1" } + + mouseEnter + { + show loadgameaction_glow + } + mouseExit + { + hide loadgameaction_glow + } + action + { + play sound/interface/button1.wav ; + hide glow ; + uiScript loadgame + } + } + + // deletegame button + itemDef + { + name deletegameaction_glow + group glow + style WINDOW_STYLE_SHADER + rect 440 385 190 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name deletegamebutton + group actionbutton + text @MENUS_DELETE_GAME + descText @MENUS_DELETE_CHOSEN_GAME + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 440 385 175 25 + font 3 + textscale 0.8 + textalignx 175 + textaligny 1 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + cvarTest ui_SelectionOK + enableCvar { "1" } + + mouseEnter + { + show deletegameaction_glow + } + mouseExit + { + hide deletegameaction_glow + } + action + { + play sound/interface/button1.wav ; + hide glow ; + uiScript deletegame + } + } + + + // resumegame button + itemDef + { + name resumegameaction_glow + group glow + style WINDOW_STYLE_SHADER + rect 440 410 190 20 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name resumeautobutton + group actionbutton + text @MENUS_RESUME_MISSION + descText @MENUS_RESUME_LAST_MISSION_ENTERED + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 440 410 175 25 + font 3 + textscale 0.8 + textalignx 175 + textaligny 1 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 // This button isn't used when not in game. + cvarTest ui_ResumeOK + enableCvar { "1" } + + mouseEnter + { + show resumegameaction_glow + } + mouseExit + { + hide resumegameaction_glow + } + action + { + play "sound/interface/button1.wav" ; + hide glow ; + uiScript loadAuto + } + } + + } +} + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/loadscreen.menu b/base/ui/loadscreen.menu new file mode 100644 index 0000000..2332cd5 --- /dev/null +++ b/base/ui/loadscreen.menu @@ -0,0 +1,179 @@ +//---------------------------------------------------------------------------------------------- +// Load screen +// +// Data displayed while level is loading. Run only during map transitions. +// +//---------------------------------------------------------------------------------------------- +{ + + menuDef + { + name "loadscreen" + fullScreen 0 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 426 + descScale 1 + descColor .79 .64 .22 .7 + descAlignment ITEM_ALIGN_CENTER + disablecolor .5 .5 .5 1 + onClose + { + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/load_back" + forecolor 1 1 1 1 + visible 0 + decoration + } + + // Picture that goes along with map + itemDef + { + name mappic + group none + style WINDOW_STYLE_SHADER + rect 25 31 266 200 + background "gfx/menus/mission_back" + forecolor 1 1 1 1 + visible 0 + decoration + } + + // Used only if there are enough icons to fill one row + itemDef + { + name weaponicons_singlerow + group none + style WINDOW_STYLE_SHADER + rect 20 280 540 30 + forecolor 1 1 1 1 + visible 0 + decoration + } + + // Used only if there are enough icons to fill two rows + itemDef + { + name weaponicons_row1 + group none + style WINDOW_STYLE_SHADER + rect 20 260 540 30 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name weaponicons_row2 + group none + style WINDOW_STYLE_SHADER + rect 20 305 540 30 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name forceicons_singlerow + group none + style WINDOW_STYLE_SHADER + rect 50 385 540 30 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name forceicons_row1 + group none + style WINDOW_STYLE_SHADER + rect 50 360 540 30 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name forceicons_row2 + group none + style WINDOW_STYLE_SHADER + rect 50 400 540 30 + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name briefingtext + group none + type ITEM_TYPE_TEXTSCROLL + rect 295 20 330 370 + cvar ui_missionbriefing + font 4 + forecolor 1 1 1 1 + textscale 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 6 + lineHeight 18 + visible 1 + autowrapped + } + // Frame around map pic + itemDef + { + name background + group none + style WINDOW_STYLE_EMPTY + rect 21 29 270 203 + border 1 + bordercolor .298 .305 .690 1 + bordersize 2 + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// Scan line to go over top of everything else +//---------------------------------------------------------------------------------------------- + itemDef + { + name scan + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 640 + background "gfx/menus/scanlines_noscroll" + forecolor 1 1 1 1 + visible 1 + decoration + } + + } + +} + + + + + + + + + + + diff --git a/base/ui/main.menu b/base/ui/main.menu new file mode 100644 index 0000000..866fdcb --- /dev/null +++ b/base/ui/main.menu @@ -0,0 +1,549 @@ +//---------------------------------------------------------------------------------------------- +// +// MAIN MENU - called when game is first started +// +//---------------------------------------------------------------------------------------------- +{ + assetGlobalDef + { + smallFont "aurabesh" 18 + mediumFont "ergoec" 18 + bigFont "anewhope" 20 + smallFont "arialnb" 14 + + cursor "cursor" + itemFocusSound "sound/interface/menuroam.wav" + itemFocusForce "fffx/interface/menuroam" + + fadeClamp 1.0 // sets the fadeup alpha + fadeCycle 1 // how often fade happens in milliseconds + fadeAmount 0.1 // amount to adjust alpha per cycle + precacheSound + { + "sound/interface/choose_color.wav" + "sound/interface/choose_head.wav" + "sound/interface/choose_torso.wav" + "sound/interface/choose_saber.wav" + "sound/interface/choose_hilt.wav" + "sound/interface/choose_blade.wav" + "sound/interface/transition.wav" + "sound/interface/esc.wav" + "sound/interface/sub_select.wav" + } + focuscolor 0 0 1 1 + shadowColor 0.1 0.1 0.1 0.0 // shadow color + } + + + menuDef + { + name "mainMenu" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + onOpen + { + setfocus newgamebutton + } + onESC + { + play "sound/interface/menuroam.wav" ; + close mainMenu ; + open quitMenu ; + } + +//---------------------------------------------------------------------------------------------- +// +// MENU BACKGROUND +// +//---------------------------------------------------------------------------------------------- + itemDef + { + name background_video + group none + style WINDOW_STYLE_SHADER + rect 200 144 256 256 + background "gfx/menus/videologo" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name ring + group none + style WINDOW_STYLE_SHADER + rect 193 145 256 256 + background "gfx/menus/main_ring" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name centerwindow + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerwindow" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name leftwindow + group none + style WINDOW_STYLE_SHADER + rect 0 150 320 240 + background "gfx/menus/main_leftwindow" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name rightwindow + group none + style WINDOW_STYLE_SHADER + rect 320 150 320 240 + background "gfx/menus/main_rightwindow" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// TOP MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group mods + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + + // Big button "NEW" + itemDef + { + name newgamebutton_undertext + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 194 130 24 + text @MENUS_NEW + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor .02 0 .33 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 212 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 16 210 210 32 + show newgamebutton_undertext + } + mouseExit + { + hide button_glow + hide newgamebutton_undertext + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open newgamefirstMenu + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton_undertext + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 293 130 24 + text @MENUS_LOAD + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor .02 0 .33 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group toprow + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 310 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 16 308 210 32 + show loadgamebutton_undertext + } + mouseExit + { + hide button_glow + hide loadgamebutton_undertext + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open loadgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_undertext + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 448 194 146 24 + text @MENUS_CONTROLS2 + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor .02 0 .33 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group toprow + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 456 212 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 416 210 210 32 + show controlsbutton_undertext + } + mouseExit + { + hide button_glow + hide controlsbutton_undertext + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_undertext + group toprow + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 456 292 130 24 + text @MENUS_SETUP + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor .02 0 .33 1 + visible 0 + decoration + } + + itemDef { + name setupbutton + group toprow + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 456 310 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 416 308 210 32 + show setupbutton_undertext + } + mouseExit + { + hide button_glow + hide setupbutton_undertext + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setupMenu ; + } + } + + +//---------------------------------------------------------------------------------------------- +// OTHER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton + group othermain + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 215 440 210 32 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + /*itemDef + { + name temp_mission_glow + group mods + style WINDOW_STYLE_SHADER + rect 65 414 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name temp_mission + group mission + text "TEMP MISSION SELECT" + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 65 414 130 24 + font 2 + textscale .7 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + textaligny -1 + forecolor .79 .64 .22 1 + visible 1 + + mouseEnter + { + show temp_mission_glow + } + mouseExit + { + hide temp_mission_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open ingameMissionSelect2 + } + } + + itemDef + { + name temp_story + group mission + text "AbCdEfGhIjKlMnOpQrStUvWxYz1234567890" + desctext "Start this map and walk forward." + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 65 444 130 24 + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + textaligny -1 + forecolor .79 .64 .22 1 + visible 1 + + mouseEnter + { + } + mouseExit + { + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + exec "map mission" + } + }*/ + + } +} + + + + + + + + + + + diff --git a/base/ui/menus.txt b/base/ui/menus.txt new file mode 100644 index 0000000..42435b1 --- /dev/null +++ b/base/ui/menus.txt @@ -0,0 +1,18 @@ +// menu defs +// +{ + loadMenu { "ui/main.menu" } + loadMenu { "ui/newgame.menu" } + loadMenu { "ui/newgame_first.menu" } + loadMenu { "ui/character.menu" } + loadMenu { "ui/saber.menu" } + loadMenu { "ui/loadgame.menu" } + loadMenu { "ui/controls.menu" } + loadMenu { "ui/setup.menu" } + loadMenu { "ui/quit.menu" } + + loadMenu { "ui/error.menu" } + loadMenu { "ui/vid_warning.menu" } + loadMenu { "ui/videodriver.menu" } + +} diff --git a/base/ui/missionfailed.menu b/base/ui/missionfailed.menu new file mode 100644 index 0000000..aadc7e6 --- /dev/null +++ b/base/ui/missionfailed.menu @@ -0,0 +1,347 @@ +//-------------------------------------------------------------- +// +// MISSION FAILED MENU +// +// Displays choices after player death +// +//-------------------------------------------------------------- +{ + menuDef + { + name "missionfailed_menu" + visible 0 + // Not full screen so we can see the game behind it. + fullscreen 0 + rect 0 0 640 480 + focusColor 1 1 1 1 + style WINDOW_STYLE_FILLED + border 1 + descX 320 + descY 458 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + ignoreescape 1 + + onClose + { + } + + onOpen + { + setcvar ui_missionfailed "0" + } + + onESC + { + } + + + // Top Background + itemDef + { + name top_background + rect 50 10 540 80 + style WINDOW_STYLE_FILLED + backcolor .015 .015 .229 .5 + forecolor 0 0 0 .5 + visible 1 + decoration + } + + // Bottom Background + itemDef + { + name bottom_background + rect 170 380 300 95 + style WINDOW_STYLE_FILLED + backcolor .015 .015 .229 .5 + forecolor 0 0 0 .5 + visible 1 + decoration + } + + // Title + itemDef + { + name failed_title + group title + style WINDOW_STYLE_EMPTY + background "gfx/menus/menu_blendbox" + text @SP_INGAME_MISSIONFAILED + rect 0 20 640 16 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 320 + textaligny -1 + forecolor 1 0 0 1 + visible 1 + decoration + } + + // Text saying why you failed + itemDef + { + name why_text + group none + background "gfx/menus/menu_blendbox" + cvar ui_missionfailed_text + rect 0 60 640 0 + font 3 + textscale .7 + textalign ITEM_ALIGN_CENTER + textalignx 320 + textaligny -1 + forecolor 1 0 0 1 + visible 1 + decoration + } + + +//-------------------------------------------------------------------------------- +// BUTTON +//-------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 118 295 85 26 + background "gfx/menus/menu_buttonback" forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name restartauto + group grpControlbutton + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 380 300 20 + text @MENUS_LOAD_AUTOSAVE + desctext @MENUS_LOAD_AUTOSAVE_DESC + textscale 1 + font 4 + textalign ITEM_ALIGN_CENTER + textalignx 150 + forecolor 1 .682 0 1 + visible 1 + + action + { + play "sound/misc/nomenu.wav" + close all + uiScript "load_auto" // Perform screen init + } + + mouseEnter + { + show button_glow + setitemrect button_glow 170 378 300 24 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name loadsave + group grpControlbutton + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 400 300 20 + text @MENUS_LOAD_SAVE_GAME + desctext @MENUS_LOAD_SAVE_GAME_DESC + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 150 + forecolor 1 .682 0 1 + visible 1 + font 4 + + action + { + play "sound/misc/nomenu.wav" + close all + setcvar cl_paused "1" + setcvar ui_missionfailed "1" + open ingameloadMenu + } + + mouseEnter + { + show button_glow + setitemrect button_glow 170 398 300 24 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name instantload + group grpControlbutton + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 420 300 20 + text @MENUS_INSTANT_LOAD_CAPS + desctext @MENUS_INSTANT_LOAD_CAPS_DESC + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 150 + forecolor 1 .682 0 1 + visible 1 + font 4 + + action + { + play "sound/misc/nomenu.wav" + close all + uiScript "load_quick" + } + + mouseEnter + { + show button_glow + setitemrect button_glow 170 418 300 24 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name missionselect + group grpControlbutton + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 440 300 20 + text @MENUS_NEW_MISSION + desctext @MENUS_NEW_MISSION_DESC + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 150 + forecolor 1 .682 0 1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "1" "2" "3" "4" "5" "6" + } + + action + { + play "sound/misc/nomenu.wav" + setcvar cl_paused "1" + uiScript decrementforcepowerlevel + close all + open ingameMissionSelect1 + + } + + mouseEnter + { + show button_glow + setitemrect button_glow 170 438 300 24 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name missionselect + group grpControlbutton + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 440 300 20 + text @MENUS_NEW_MISSION + desctext @MENUS_NEW_MISSION_DESC + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 150 + forecolor 1 .682 0 1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "7" "8" "9" "10" "11" "12" + } + + action + { + play "sound/misc/nomenu.wav" + setcvar cl_paused "1" + uiScript decrementforcepowerlevel + close all + open ingameMissionSelect2 + + } + + mouseEnter + { + show button_glow + setitemrect button_glow 170 438 300 24 + } + mouseExit + { + hide button_glow + } + } + itemDef + { + name missionselect + group grpControlbutton + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 440 300 20 + text @MENUS_NEW_MISSION + desctext @MENUS_NEW_MISSION_DESC + font 4 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 150 + forecolor 1 .682 0 1 + visible 1 + cvartest "tier_storyinfo" + showcvar + { + "13" "14" "15" "16" "17" "18" + } + + action + { + play "sound/misc/nomenu.wav" + setcvar cl_paused "1" + uiScript decrementforcepowerlevel + close all + open ingameMissionSelect3 + } + + mouseEnter + { + show button_glow + setitemrect button_glow 170 438 300 24 + } + mouseExit + { + hide button_glow + } + } + + } +} + + diff --git a/base/ui/newgame.menu b/base/ui/newgame.menu new file mode 100644 index 0000000..69e9461 --- /dev/null +++ b/base/ui/newgame.menu @@ -0,0 +1,735 @@ +//----------------------------------------------------------------------- +// NEWGAME MENU +// +// Called from all other menus other than main menu. This has no transition in it. +// +//----------------------------------------------------------------------- +{ + menuDef + { + name "newgameMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + setcvar g_spskill 1 + setcvar cg_crosshairForceHint 1 + setcvar handicap 100 + setfocus medbut + hide diff1 + show diff2 + hide diff3 + hide diff4 + setitemcolor easybut forecolor 1 .682 0 1 + setitemcolor medbut forecolor 1 1 1 1 + setitemcolor hardbut forecolor 1 .682 0 1 + setitemcolor vhardbut forecolor 1 .682 0 1 + } + + onESC + { + play "sound/interface/esc.wav" + close newgameMenu ; + open mainMenu ; + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + //---------------------------------------------------------------------------------------------- + // + // TOP MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show newbutton_glow + } + mouseExit + { + hide newbutton_glow + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open loadgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setupMenu ; + } + } + + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name backbutton + group exit + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open mainMenu + } + } + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + close all ; + open quitMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // NEW GAME MENU specific stuff + // + //---------------------------------------------------------------------------------------------- + + + + // New Game title + itemDef + { + name newgame_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_SELECT_SKILL + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name diff1 + group none + style WINDOW_STYLE_SHADER + rect 225 196 190 40 + background "gfx/menus/menu_buttonback" // Box around difficulty 1 + forecolor 1 1 1 1 + visible 0 + decoration + } + + // EASY difficulty button + itemDef + { + name easybut + group easy + text @MENUS_APPRENTICE + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + rect 225 196 190 40 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_EASY_GAME_SETTINGS + + action + { + play "sound/interface/button1.wav" + setcvar g_spskill 0 + setcvar cg_crosshairForceHint 1 + setcvar handicap 100 + show diff1 + hide diff2 + hide diff3 + hide diff4 + setitemcolor easybut forecolor 1 1 1 1 + setitemcolor medbut forecolor 1 .682 0 1 + setitemcolor hardbut forecolor 1 .682 0 1 + setitemcolor vhardbut forecolor 1 .682 0 1 + } + + } + + itemDef + { + name diff2 + group med + style WINDOW_STYLE_SHADER + rect 225 243 190 40 + background "gfx/menus/menu_buttonback" // Box around difficulty 2 + forecolor 1 1 1 1 + visible 1 + decoration + } + + // MEDIUM difficulty button + itemDef + { + name medbut + group med + text @MENUS_JEDI + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + font 3 + textscale 1 + rect 225 243 190 40 + textalignx 95 + textaligny 8 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_NORMAL_GAME_SETTINGS + + action + { + play "sound/interface/button1.wav" + setcvar g_spskill 1 + setcvar cg_crosshairForceHint 1 + setcvar handicap 100 + hide diff1 ; + show diff2 ; + hide diff3 ; + hide diff4 ; + setitemcolor easybut forecolor 1 .682 0 1 + setitemcolor medbut forecolor 1 1 1 1 + setitemcolor hardbut forecolor 1 .682 0 1 + setitemcolor vhardbut forecolor 1 .682 0 1 + } + + } + + itemDef + { + name diff3 + group none + style WINDOW_STYLE_SHADER + rect 225 289 190 40 + background "gfx/menus/menu_buttonback" // Box around difficulty 3 + forecolor 1 1 1 1 + visible 0 + decoration + } + + // HARD difficulty button + itemDef + { + name hardbut + group hard + text @MENUS_JEDI_KNIGHT + font 3 + textscale 1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 289 190 40 + textalignx 95 + textaligny 8 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DIFFICULT_GAME_SETTINGS + + action + { + play "sound/interface/button1.wav" ; + setcvar g_spskill 2 ; + setcvar cg_crosshairForceHint 0 ; + setcvar handicap 100 + hide diff1 ; + hide diff2 ; + show diff3 ; + hide diff4 ; + setitemcolor easybut forecolor 1 .682 0 1 + setitemcolor medbut forecolor 1 .682 0 1 + setitemcolor hardbut forecolor 1 1 1 1 + setitemcolor vhardbut forecolor 1 .682 0 1 + } + + } + + itemDef + { + name diff4 + group none + style WINDOW_STYLE_SHADER + rect 225 335 190 40 + background "gfx/menus/menu_buttonback" // Box around difficulty 4 + forecolor 1 1 1 1 + visible 0 + decoration + } + + // VERY HARD difficulty button + itemDef + { + name vhardbut + group vhard + text @MENUS_JEDI_MASTER + font 3 + textscale 1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 335 190 40 + textalignx 95 + textaligny 8 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_EXTREMELY_DIFFICULT_GAME + + action + { + play "sound/interface/button1.wav" + setcvar g_spskill 2 + setcvar cg_crosshairForceHint 0 + setcvar handicap 50 + hide diff1 ; + hide diff2 ; + hide diff3 ; + show diff4 ; + setitemcolor easybut forecolor 1 .682 0 1 + setitemcolor medbut forecolor 1 .682 0 1 + setitemcolor hardbut forecolor 1 .682 0 1 + setitemcolor vhardbut forecolor 1 1 1 1 + } + + } + + // BEGIN button + itemDef + { + name next_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name next + group none + text @MENUS_NEXT + descText @MENUS_NEXT_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 455 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" ; + close all ; + open characterMenu ; + } + mouseEnter + { + show next_glow + } + mouseExit + { + hide next_glow + } + } + + + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/newgame_first.menu b/base/ui/newgame_first.menu new file mode 100644 index 0000000..8b00fdc --- /dev/null +++ b/base/ui/newgame_first.menu @@ -0,0 +1,926 @@ +//----------------------------------------------------------------------- +// NEWGAME MENU +// +// Called only from main menu. Has the transition of fields from main menu +// to new game menu +// +//----------------------------------------------------------------------- +{ + + menuDef + { + name "newgamefirstMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + play "sound/interface/transition.wav" + setcvar g_spskill 1 + setcvar cg_crosshairForceHint 1 + setcvar handicap 100 + setfocus medbut + hide diff1 + show diff2 + hide diff3 + hide diff4 + setitemcolor easybut forecolor 1 .682 0 1 + setitemcolor medbut forecolor 1 1 1 1 + setitemcolor hardbut forecolor 1 .682 0 1 + setitemcolor vhardbut forecolor 1 .682 0 1 + transition ring 161 114 320 320 321 273 0 0 20 25 + transition vid 200 144 320 320 321 273 0 0 10 15 + transition cwin 156 154 320 240 321 273 0 0 20 25 + transition lwin 0 150 320 240 -320 150 320 240 20 25 + transition lbut 36 310 130 24 170 126 130 24 20 25 + transition lbut_u 36 293 130 24 -284 293 130 24 20 25 + transition nbut 36 212 130 24 7 126 130 24 20 25 + transition nbut_u 36 194 130 24 -284 194 130 24 20 25 + transition rwin 320 150 320 240 640 150 320 240 20 25 + transition cbut 456 212 130 24 340 126 130 24 20 25 + transition cbut_u 448 194 146 24 768 194 146 24 20 25 + transition sbut 456 310 130 24 502 126 130 24 20 25 + transition sbut_u 456 292 130 24 776 310 130 24 20 25 + //newgame stuff + //transition but_fr 160 480 320 320 160 160 320 320 15 25 + transition easy 225 526 190 40 225 196 190 40 15 25 + transition med 225 578 190 40 225 243 190 40 15 25 + transition hard 225 629 190 40 225 289 190 40 15 25 + transition vhard 225 680 190 40 225 335 190 40 15 25 + //menu bar +// transition lf_fr -320 50 320 160 0 50 320 160 20 25 + transition lf_fr -320 50 320 160 0 50 320 160 2 25 + transition rt_fr 640 50 320 160 320 50 320 160 20 25 + //transition exit 255 444 130 24 59 444 130 24 20 25 + + transition backbutton -320 444 130 24 59 444 130 24 20 25 + transition nextbutton 834 444 130 24 455 444 130 24 20 25 + + fadein newgame_title + fadein difficulty_background + + } + + onESC + { + setitemcolor newgame_title forecolor .549 .854 1 0 + setitemcolor difficulty_background backcolor 0 0 .6 0.5 + play "sound/interface/esc.wav" + close newgamefirstMenu + open mainMenu + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name vid + group center + style WINDOW_STYLE_SHADER + rect 200 144 256 256 + background "gfx/menus/videologo" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name ring + group none + style WINDOW_STYLE_SHADER + rect 161 114 320 320 + background "gfx/menus/menu_rotate_ring_b" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name cwin + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerwindow" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name lwin + group none + style WINDOW_STYLE_SHADER + rect 0 150 320 240 + background "gfx/menus/main_leftwindow" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name rwin + group none + style WINDOW_STYLE_SHADER + rect 320 150 320 240 + background "gfx/menus/main_rightwindow" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // The starwars logo on the top + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + + //---------------------------------------------------------------------------------------------- + // + // TOP MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + + // Big button "NEW" + itemDef + { + name newbutton_glow + group none + style WINDOW_STYLE_SHADER + //rect 36 212 130 24 + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton_undertext + group nbut_u + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 194 130 24 + text @MENUS_NEW + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + forecolor .79 .64 .22 .2 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + //forecolor .79 .64 .22 1 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show newbutton_glow + } + mouseExit + { + hide newbutton_glow + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group none + style WINDOW_STYLE_SHADER + //rect 36 310 130 24 + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton_undertext + group lbut_u + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 293 130 24 + text @MENUS_LOAD + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + forecolor .79 .64 .22 .2 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 36 310 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + show loadgamebutton_undertext + } + mouseExit + { + hide loadgamebutton_glow + hide loadgamebutton_undertext + } + action + { + play "sound/interface/button1.wav" + close all + open loadgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + //rect 441 212 160 24 + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name controlsbutton_undertext + group cbut_u + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 448 194 146 24 + text @MENUS_CONTROLS2 + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + forecolor .79 .64 .22 .2 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 456 212 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + show controlsbutton_undertext + } + mouseExit + { + hide controlsbutton_glow + hide controlsbutton_undertext + } + action + { + play "sound/interface/button1.wav" + close all + open controlsMenu + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + //rect 456 310 130 24 + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton_undertext + group sbut_u + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 456 292 130 24 + text @MENUS_SETUP + font 1 + textscale .7 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + forecolor .79 .64 .22 .2 + visible 0 + decoration + } + + itemDef { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 456 310 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + show setupbutton_undertext + } + mouseExit + { + hide setupbutton_glow + hide setupbutton_undertext + } + action + { + play "sound/interface/button1.wav" + close all + open setupMenu + } + } + + //---------------------------------------------------------------------------------------------- + // + // NEW GAME MENU specific stuff + // + //---------------------------------------------------------------------------------------------- + /*itemDef + { + name button_frame + group but_fr + style WINDOW_STYLE_SHADER + rect 160 160 320 320 + background "gfx/menus/newgame_boxes" + forecolor 1 1 1 1 + visible 1 + decoration + }*/ + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Select a skill level title + itemDef + { + name newgame_title + group none + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_SELECT_SKILL + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 0 + visible 1 + decoration + } + + itemDef + { + name diff1 + group none + style WINDOW_STYLE_SHADER + rect 225 196 190 40 + background "gfx/menus/menu_buttonback" // Box around difficulty 1 + forecolor 1 1 1 1 + visible 0 + decoration + } + + // EASY difficulty button + itemDef + { + name easybut + group easy + text @MENUS_APPRENTICE + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + rect 225 196 190 40 + textalignx 95 + textaligny 8 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_EASY_GAME_SETTINGS + + action + { + play "sound/interface/button1.wav" + setcvar g_spskill 0 + setcvar cg_crosshairForceHint 1 + setcvar handicap 100 + show diff1 + hide diff2 + hide diff3 + hide diff4 + setitemcolor easybut forecolor 1 1 1 1 + setitemcolor medbut forecolor 1 .682 0 1 + setitemcolor hardbut forecolor 1 .682 0 1 + setitemcolor vhardbut forecolor 1 .682 0 1 + } + + } + + itemDef + { + name diff2 + group med + style WINDOW_STYLE_SHADER + rect 225 243 190 40 + background "gfx/menus/menu_buttonback" // Box around difficulty 2 + forecolor 1 1 1 1 + visible 1 + decoration + } + + // MEDIUM difficulty button + itemDef + { + name medbut + group med + text @MENUS_JEDI + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + font 3 + textscale 1 + rect 225 243 190 40 + textalignx 95 + textaligny 8 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_NORMAL_GAME_SETTINGS + + action + { + play "sound/interface/button1.wav" + setcvar g_spskill 1 + setcvar cg_crosshairForceHint 1 + setcvar handicap 100 + hide diff1 + show diff2 + hide diff3 + hide diff4 + setitemcolor easybut forecolor 1 .682 0 1 + setitemcolor medbut forecolor 1 1 1 1 + setitemcolor hardbut forecolor 1 .682 0 1 + setitemcolor vhardbut forecolor 1 .682 0 1 + } + + } + + itemDef + { + name diff3 + group none + style WINDOW_STYLE_SHADER + rect 225 289 190 40 + background "gfx/menus/menu_buttonback" // Box around difficulty 3 + forecolor 1 1 1 1 + visible 0 + decoration + } + + // HARD difficulty button + itemDef + { + name hardbut + group hard + text @MENUS_JEDI_KNIGHT + font 3 + textscale 1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 289 190 40 + textalignx 95 + textaligny 8 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_DIFFICULT_GAME_SETTINGS + + action + { + play "sound/interface/button1.wav" + setcvar g_spskill 2 + setcvar cg_crosshairForceHint 0 + setcvar handicap 100 + hide diff1 + hide diff2 + show diff3 + hide diff4 + setitemcolor easybut forecolor 1 .682 0 1 + setitemcolor medbut forecolor 1 .682 0 1 + setitemcolor hardbut forecolor 1 1 1 1 + setitemcolor vhardbut forecolor 1 .682 0 1 + } + + } + + itemDef + { + name diff4 + group none + style WINDOW_STYLE_SHADER + rect 225 335 190 40 + background "gfx/menus/menu_buttonback" // Box around difficulty 4 + forecolor 1 1 1 1 + visible 0 + decoration + } + + // VERY HARD difficulty button + itemDef + { + name vhardbut + group vhard + text @MENUS_JEDI_MASTER + font 3 + textscale 1 + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 225 335 190 40 + textalignx 95 + textaligny 8 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_EXTREMELY_DIFFICULT_GAME + + action + { + play "sound/interface/button1.wav" + setcvar g_spskill 2 + setcvar cg_crosshairForceHint 0 + setcvar handicap 50 + hide diff1 + hide diff2 + hide diff3 + show diff4 + setitemcolor easybut forecolor 1 .682 0 1 + setitemcolor medbut forecolor 1 .682 0 1 + setitemcolor hardbut forecolor 1 .682 0 1 + setitemcolor vhardbut forecolor 1 1 1 1 + } + + } + + //---------------------------------------------------------------------------------------------- + // + // OTHER MAIN MENU BUTTONS + // + //---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name backbutton + group fade_buttons + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open mainMenu + } + } + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3" + close all + open quitMenu + } + } + + // NEXT button + itemDef + { + name next_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name nextbutton + group fade_buttons + text @MENUS_NEXT + descText @MENUS_NEXT_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 455 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/button1.wav" + close all + open characterMenu + } + mouseEnter + { + show next_glow + } + mouseExit + { + hide next_glow + } + } + + + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/base/ui/quit.menu b/base/ui/quit.menu new file mode 100644 index 0000000..51ab790 --- /dev/null +++ b/base/ui/quit.menu @@ -0,0 +1,506 @@ +//---------------------------------------------------------------------------------------------- +// +// QUIT MENU +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "quitMenu" + visible 0 + fullScreen 1 + rect 0 0 640 480 + focusColor 1 1 1 1 + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onESC + { + play "sound/interface/esc.wav" + close quitMenu ; + open mainMenu ; + } + + onOpen + { + setfocus quitgame_cancel + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Big button "NEW" + itemDef + { + name newbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newbutton_glow + } + mouseExit + { + hide newbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open newgameMenu + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open loadgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open controlsMenu ; + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + close all ; + open setupMenu ; + } + } + +//---------------------------------------------------------------------------------------------- +// QUIT MENU specific stuff +//---------------------------------------------------------------------------------------------- + itemDef + { + name quit_title + group none + text @MENUS_LEAVING_JEDI_KNIGHT_2 + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name confirm + group none + text @MENUS_QUIT_JEDI_KNIGHT_II + rect 0 250 640 20 + font 2 + textstyle 1 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 320 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// CANCEL button +//---------------------------------------------------------------------------------------------- + itemDef + { + name quitgame_no_button + group none + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name quitgame_no + group none + text @MENUS_NO + descText @MENUS_DO_NOT_LEAVE_JEDI_KNIGHT + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + action + { + play "sound/interface/esc.wav" + close quitMenu + open mainMenu + } + mouseEnter + { + show quitgame_no_button + } + mouseExit + { + hide quitgame_no_button + } + } + +//---------------------------------------------------------------------------------------------- +// CONFIRM button +//---------------------------------------------------------------------------------------------- + itemDef + { + name quitgame_yes_button + group none + style WINDOW_STYLE_SHADER + rect 454 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name quitgame_yes + group none + text @MENUS_YES + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 454 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + cvartest "com_demo" + hidecvar + { + "1" + } + action + { + play "sound/interface/button1.wav" + uiScript Quit + } + mouseEnter + { + show quitgame_yes_button + } + mouseExit + { + hide quitgame_yes_button + } + } + + + itemDef + { + name quitgame_yes + group none + text @MENUS_YES + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 454 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + cvartest "com_demo" + hidecvar + { + "0" + } + action + { + play "sound/interface/button1.wav" + close all + open demo_sellscreen1 + } + mouseEnter + { + show quitgame_yes_button + } + mouseExit + { + hide quitgame_yes_button + } + } + + } +} + + + + + + + + + + + + + + + + + diff --git a/base/ui/saber.menu b/base/ui/saber.menu new file mode 100644 index 0000000..33428f2 --- /dev/null +++ b/base/ui/saber.menu @@ -0,0 +1,1820 @@ +//---------------------------------------------------------------------------------------------- +// +// SABER CREATION MENU - called from main menu at the the start of a new game, +// and also when player is allowed to upgrade the fighting style and choose a new saber +// +// values allowed for "ui_fightingstylesallowed" for styles player can choose from +// 0 - none +// 1 - Medium and Heavy +// 2 - Fast and Heavy +// 3 - Fast and Medium +// 4 - Forced to take Fast +// 5 - Forced to take Medium +// 6 - Forced to take Strong +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "saberMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript "resetsabercvardefaults" + uiScript "getsabercvars" + uiScript "updatefightingstylechoices" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 + } + + onESC + { + play "sound/interface/esc.wav" + uiScript "updatesabercvars" + uiScript closesabermenu + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/sabermenu_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box1 + group none + style WINDOW_STYLE_SHADER + rect 4 66 219 165 + background "gfx/menus/sabermenu_box" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box2top + group none + style WINDOW_STYLE_SHADER + rect 212 66 219 60 + background "gfx/menus/sabermenu_box_top" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box2middle + group none + style WINDOW_STYLE_SHADER + rect 212 126 219 0 + background "gfx/menus/sabermenu_box_middle" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box2bottom + group none + style WINDOW_STYLE_SHADER + rect 212 126 219 60 + background "gfx/menus/sabermenu_box_bottom" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box3top + group none + style WINDOW_STYLE_SHADER + rect 418 66 219 60 + background "gfx/menus/sabermenu_box_top" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box3middle + group none + style WINDOW_STYLE_SHADER + rect 418 126 219 0 + background "gfx/menus/sabermenu_box_middle" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name box3bottom + group none + style WINDOW_STYLE_SHADER + rect 418 126 219 60 + background "gfx/menus/sabermenu_box_bottom" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name styleboxleft + group none + style WINDOW_STYLE_SHADER + rect 227 183 198 40 + background "gfx/menus/sabermenu_stylebox_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name styleboxright + group none + style WINDOW_STYLE_SHADER + rect 425 183 198 40 + background "gfx/menus/sabermenu_stylebox_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + // Big button "NEW" + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 16 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 1 1 1 + visible 1 + + cvarTest saber_menu + showCvar { "0" } + + mouseEnter + { + show button_glow + setitemrect button_glow 0 14 200 30 + } + mouseExit + { + hide button_glow + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 16 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + cvarTest saber_menu + showCvar { "0" } + + mouseEnter + { + show button_glow + setitemrect button_glow 130 14 200 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "updatesabercvars" + close all + open loadgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 16 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 16 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + cvarTest saber_menu + showCvar { "0" } + + mouseEnter + { + show button_glow + setitemrect button_glow 310 14 200 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "updatesabercvars" + close all + open controlsMenu + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 16 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 16 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + cvarTest saber_menu + showCvar { "0" } + + mouseEnter + { + show button_glow + setitemrect button_glow 472 14 200 30 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + uiScript "updatesabercvars" + close all + open setupMenu + } + } + + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 -60 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 -60 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// SABER MENU specific stuff +//---------------------------------------------------------------------------------------------- + itemDef + { + name title_glow + group none + style WINDOW_STYLE_SHADER + rect 100 48 440 20 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // CREATION title + itemDef + { + name creation_title + group title + style WINDOW_STYLE_EMPTY + text @MENUS_LIGHTSABER_CREATION + rect 100 50 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + cvarTest saber_menu + hideCvar { "1" "2" } + + visible 1 + decoration + } + + itemDef + { + name creation_title2 + group title + style WINDOW_STYLE_EMPTY + text @MENUS_NEW_SABER + rect 100 50 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + cvarTest saber_menu + showCvar { "1" "2" } + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// SABER TYPE BUTTONS (standard, dual, two handed) +//---------------------------------------------------------------------------------------------- + itemDef + { + name typebut + group none + text @MENUS_SABER_TYPE + descText @MENUS_SABER_TYPE_DESC + style WINDOW_STYLE_EMPTY + rect 32 96 160 24 + font 3 + textscale 1 + textstyle 1 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name typebut_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + // This prints the first time in, so you don't get a highlite on the single + itemDef + { + name type_single_title + group none + text @MENUS_SINGLESABER + descText @MENUS_SINGLESABER_DESC + style WINDOW_STYLE_EMPTY + rect 32 132 0 0 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + + cvarTest ui_sabermenu + showCvar { "0" } + + } + + itemDef + { + name typebut_single + group none + text @MENUS_SINGLESABER + descText @MENUS_SINGLESABER_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 32 132 160 16 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + + cvarTest ui_sabermenu + hideCvar { "0" } + + mouseEnter + { + show typebut_glow + setitemrect typebut_glow 5 130 210 20 + } + mouseExit + { + hide typebut_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "single" + uiScript "saber_type" + setcvar ui_saber "single_1" + setcvar ui_saber2 "" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor 1 1 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 + transition2 box2middle 212 126 219 0 20 5 + transition2 box2bottom 212 126 219 60 20 5 + transition2 box3middle 418 126 219 0 20 5 + transition2 box3bottom 418 126 219 60 20 5 + transition2 styleboxleft 227 183 198 40 20 5 + transition2 styleboxright 425 183 198 40 20 5 + transition2 saber 12 -80 615 615 20 10 + uiScript "updatefightingstylechoices" + } + } + + itemDef + { + name typebut_dual + group none + text @MENUS_DUALSABERS + descText @MENUS_DUALSABERS_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 32 152 160 16 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + + cvarTest saber_menu + showCvar { "2" } + + mouseEnter + { + show typebut_glow + setitemrect typebut_glow 5 150 210 20 + } + mouseExit + { + hide typebut_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "dual" + uiScript "saber_type" + setcvar ui_saber "single_1" + setcvar ui_saber2 "single_1" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor 1 1 1 1 + setitemcolor typebut_staff forecolor .65 .65 1 1 + transition2 box2middle 212 126 219 44 20 5 + transition2 box2bottom 212 170 219 60 20 5 + transition2 box3middle 418 126 219 44 20 5 + transition2 box3bottom 418 170 219 60 20 5 + transition2 styleboxleft 425 183 0 40 20 5 + transition2 styleboxright 425 183 0 40 20 5 + transition2 saber 12 -130 615 615 20 5 + uiScript "updatefightingstylechoices" + } + } + + itemDef + { + name typebut_staff + group none + text @MENUS_SABERSTAFF + descText @MENUS_SABERSTAFF_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 32 172 160 16 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + visible 1 + + cvarTest saber_menu + showCvar { "2" } + + mouseEnter + { + show typebut_glow + setitemrect typebut_glow 5 170 210 20 + } + mouseExit + { + hide typebut_glow + } + action + { + play "sound/interface/choose_saber.wav" + setcvar ui_saber_type "staff" + uiScript "saber_type" + setcvar ui_saber "dual_1" + setcvar ui_saber2 "" + uiScript "saber_hilt" + uiScript "saber_color" + uiScript "saber2_hilt" + uiScript "saber2_color" + setitemcolor typebut_single forecolor .65 .65 1 1 + setitemcolor typebut_dual forecolor .65 .65 1 1 + setitemcolor typebut_staff forecolor 1 1 1 1 + transition2 box2middle 212 126 219 44 20 5 + transition2 box2bottom 212 170 219 60 20 5 + transition2 box3middle 418 126 219 44 20 5 + transition2 box3bottom 418 170 219 60 20 5 + transition2 styleboxleft 425 183 0 40 20 5 + transition2 styleboxright 425 183 0 40 20 5 + transition2 saber 12 -80 615 615 20 10 + uiScript "updatefightingstylechoices" + } + } + +//---------------------------------------------------------------------------------------------- +//HILTS +//---------------------------------------------------------------------------------------------- + itemDef + { + name hilttype + group none + text @MENUS_SABER_HILTS + descText @MENUS_SABER_HILTS_DESC + style WINDOW_STYLE_EMPTY + rect 240 96 160 24 + font 3 + textscale 1 + textstyle 1 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // HILT BUTTON 1 - SINGLE or DUAL + itemDef + { + name hiltbut_glow + group none + style WINDOW_STYLE_SHADER + rect 210 130 210 20 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name hiltbut + group none + text @MENUS_HILT1 + descText @MENUS_HILT1_DESC + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 132 160 16 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_saber_type + hideCvar { "staff" } + + cvar "ui_saber" + //FIXME: read these from sabers.cfg + *.sab? + cvarStrList + { + @MENUS_SINGLE_HILT1 "single_1" + @MENUS_SINGLE_HILT2 "single_2" + @MENUS_SINGLE_HILT3 "single_3" + @MENUS_SINGLE_HILT4 "single_4" + @MENUS_SINGLE_HILT5 "single_5" + @MENUS_SINGLE_HILT6 "single_6" + @MENUS_SINGLE_HILT7 "single_7" + @MENUS_SINGLE_HILT8 "single_8" + @MENUS_SINGLE_HILT9 "single_9" + } + + visible 1 + + mouseEnter + { + show hiltbut_glow + } + mouseExit + { + hide hiltbut_glow + } + action + { + play "sound/interface/choose_hilt.wav" + uiScript "saber_hilt" + } + } + + // HILT BUTTON 1 - STAVES + itemDef + { + name hiltbut_staves + group none + text @MENUS_HILT1 + descText @MENUS_HILT1_DESC + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 132 160 16 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_saber_type + hideCvar { "single"; "dual" } + + cvar "ui_saber" + //FIXME: read these from sabers.cfg + *.sab? + cvarStrList + { + @MENUS_STAFF_HILT1 "dual_1" + @MENUS_STAFF_HILT2 "dual_2" + @MENUS_STAFF_HILT3 "dual_3" + @MENUS_STAFF_HILT4 "dual_4" + @MENUS_STAFF_HILT5 "dual_5" + } + visible 1 + mouseEnter + { + show hiltbut_glow + } + mouseExit + { + hide hiltbut_glow + } + action + { + play "sound/interface/choose_hilt.wav" + uiScript "saber_hilt" + } + } + + // HILT BUTTON 2 - DUAL + itemDef + { + name hiltbut2_glow + group none + style WINDOW_STYLE_SHADER + rect 210 150 210 20 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + decoration + } + + itemDef + { + name hiltbut2 + group none + text @MENUS_HILT2 + descText @MENUS_HILT2_DESC + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 152 160 16 + font 4 + textscale 1 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + + cvar "ui_saber2" + //FIXME: read these from sabers.cfg + *.sab? + cvarStrList + { + @MENUS_SINGLE_HILT1 "single_1" + @MENUS_SINGLE_HILT2 "single_2" + @MENUS_SINGLE_HILT3 "single_3" + @MENUS_SINGLE_HILT4 "single_4" + @MENUS_SINGLE_HILT5 "single_5" + @MENUS_SINGLE_HILT6 "single_6" + @MENUS_SINGLE_HILT7 "single_7" + @MENUS_SINGLE_HILT8 "single_8" + @MENUS_SINGLE_HILT9 "single_9" + } + visible 1 + mouseEnter + { + show hiltbut2_glow + } + mouseExit + { + hide hiltbut2_glow + } + action + { + play "sound/interface/choose_hilt.wav" + uiScript "saber2_hilt" + } + } + +//---------------------------------------------------------------------------------------------- +//BLADE COLORS +//---------------------------------------------------------------------------------------------- + itemDef + { + name bladecolor_title + group none + text @MENUS_BLADE_COLOR + descText @MENUS_BLADE_COLOR_DESC + style WINDOW_STYLE_EMPTY + rect 446 96 160 24 + font 3 + textscale 1 + textstyle 1 + textalignx 0 + textaligny 0 + textalign ITEM_ALIGN_LEFT + backcolor 0 0 0 0 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + // COLOR 1 BUTTON + /*itemDef + { + name colorbut_glow + group none + style WINDOW_STYLE_SHADER + rect 446 136 160 16 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name colorbut + group none + text @MENUS_COLOR1 + descText @MENUS_COLOR1_DESC + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 446 136 160 16 + font 2 + textscale .8 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -4 + forecolor 1 1 1 1 + visible 1 + + cvar "ui_saber_color" + cvarStrList + { + "red" "red" + "orange" "orange" + "yellow" "yellow" + "green" "green" + "blue" "blue" + "purple" "purple" + } + + mouseEnter + { + show colorbut_glow + } + mouseExit + { + hide colorbut_glow + } + action + { + play "sound/interface/choose_blade.wav" + uiScript "saber_color" + } + }*/ + + itemDef + { + name blueicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 446 124 24 24 + background "gfx/menus/saber_icon_blue" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor blueicon forecolor 1 1 1 1 + setitemcolor blueicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor blueicon forecolor .75 .75 .75 1 + setitemcolor blueicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber_color" "blue" + } + } + + itemDef + { + name greenicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 480 124 24 24 + background "gfx/menus/saber_icon_green" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor greenicon forecolor 1 1 1 1 + setitemcolor greenicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor greenicon forecolor .75 .75 .75 1 + setitemcolor greenicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber_color" "green" + } + } + + itemDef + { + name orangeicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 514 124 24 24 + background "gfx/menus/saber_icon_orange" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor orangeicon forecolor 1 1 1 1 + setitemcolor orangeicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor orangeicon forecolor .75 .75 .75 1 + setitemcolor orangeicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber_color" "orange" + } + } + + itemDef + { + name purpleicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 548 124 24 24 + background "gfx/menus/saber_icon_purple" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor purpleicon forecolor 1 1 1 1 + setitemcolor purpleicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor purpleicon forecolor .75 .75 .75 1 + setitemcolor purpleicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber_color" "purple" + } + } + + itemDef + { + name yellowicon + group sabericons + descText @MENUS_COLOR1_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 582 124 24 24 + background "gfx/menus/saber_icon_yellow" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + mouseEnter + { + setitemcolor yellowicon forecolor 1 1 1 1 + setitemcolor yellowicon bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor yellowicon forecolor .75 .75 .75 1 + setitemcolor yellowicon bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber_color" "yellow" + } + } + +// COLOR 2 BUTTON + itemDef + { + name colorbut2 + group none + text @MENUS_COLOR2 + descText @MENUS_COLOR2_DESC + //type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 446 152 160 16 + font 2 + textscale .8 + textstyle 1 + textalign ITEM_ALIGN_LEFT + textalignx 0 + textaligny -4 + forecolor .79 .64 .22 1 + visible 1 + decoration + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + + /*cvar "ui_saber2_color" + cvarStrList + { + "red" "red" + "orange" "orange" + "yellow" "yellow" + "green" "green" + "blue" "blue" + "purple" "purple" + } + + mouseEnter + { + show colorbut2_glow + } + mouseExit + { + hide colorbut2_glow + } + action + { + play "sound/interface/choose_blade.wav" + uiScript "saber2_color" + }*/ + } + + itemDef + { + name blueicon2 + group sabericons2 + descText @MENUS_COLOR2_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 446 170 24 24 + background "gfx/menus/saber_icon_blue" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor blueicon2 forecolor 1 1 1 1 + setitemcolor blueicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor blueicon2 forecolor .75 .75 .75 1 + setitemcolor blueicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber2_color" "blue" + } + } + + itemDef + { + name greenicon2 + group sabericons2 + descText @MENUS_COLOR2_DESC + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 480 170 24 24 + background "gfx/menus/saber_icon_green" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor greenicon2 forecolor 1 1 1 1 + setitemcolor greenicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor greenicon2 forecolor .75 .75 .75 1 + setitemcolor greenicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber2_color" "green" + } + } + + itemDef + { + name orangeicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + descText @MENUS_COLOR2_DESC + type ITEM_TYPE_BUTTON + rect 514 170 24 24 + background "gfx/menus/saber_icon_orange" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor orangeicon2 forecolor 1 1 1 1 + setitemcolor orangeicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor orangeicon2 forecolor .75 .75 .75 1 + setitemcolor orangeicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber2_color" "orange" + } + } + + itemDef + { + name purpleicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + descText @MENUS_COLOR2_DESC + rect 548 170 24 24 + background "gfx/menus/saber_icon_purple" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor purpleicon2 forecolor 1 1 1 1 + setitemcolor purpleicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor purpleicon2 forecolor .75 .75 .75 1 + setitemcolor purpleicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber2_color" "purple" + } + } + + itemDef + { + name yellowicon2 + group sabericons2 + style WINDOW_STYLE_SHADER + type ITEM_TYPE_BUTTON + rect 582 170 24 24 + descText @MENUS_COLOR2_DESC + background "gfx/menus/saber_icon_yellow" + forecolor .75 .75 .75 1 + border 1 + bordersize 1 + bordercolor .33 .33 .5 1 + visible 1 + cvarTest ui_saber_type + hideCvar { "single"; "staff" } + mouseEnter + { + setitemcolor yellowicon2 forecolor 1 1 1 1 + setitemcolor yellowicon2 bordercolor .66 .66 1 1 + } + mouseExit + { + setitemcolor yellowicon2 forecolor .75 .75 .75 1 + setitemcolor yellowicon2 bordercolor .33 .33 .5 1 + } + action + { + play "sound/interface/choose_blade.wav" + //uiScript "saber_color" + setcvar "ui_saber2_color" "yellow" + } + } + +//---------------------------------------------------------------------------------------------- +// SABER COMBAT STYLES +//---------------------------------------------------------------------------------------------- + itemDef + { + name fightingstylebutton0 + group stylebuttons + text @MENUS_SABERSTYLE + descText @MENUS_FIGHTINGSTYLE + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 190 0 0 + font 3 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + //cvarTest ui_fightingstylesallowed + cvarTest saber_menu + showCvar { "0" } + + cvar "ui_newfightingstyle" + cvarStrList + { + @MENUS_COMBATSTYLEMEDIUM "1" + } + + visible 1 + + } + + /* + itemDef + { + name fightingstylebutton1 + group stylebuttons + text @MENUS_SABERSTYLE + // descText @MENUS_ADDFIGHTINGSTYLE + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 190 380 24 + font 3 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_fightingstylesallowed + showCvar { "1" } + + cvar "ui_newfightingstyle" + cvarStrList + { + @MENUS_COMBATSTYLEMEDIUM "1" + @MENUS_COMBATSTYLEHEAVY "2" + } + + visible 1 + + mouseEnter + { + // show fightingstyle_glow + } + mouseExit + { + // hide fightingstyle_glow + } + action + { + play "sound/interface/button1.wav" + } + } + + itemDef + { + name fightingstylebutton2 + group none + text @MENUS_SABERSTYLE + // descText @MENUS_ADDFIGHTINGSTYLE + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 190 380 24 + font 3 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_fightingstylesallowed + showCvar { "2" } + + cvar "ui_newfightingstyle" + cvarStrList + { + @MENUS_COMBATSTYLEFAST "0" + @MENUS_COMBATSTYLEHEAVY "2" + } + + visible 1 + decoration + + mouseEnter + { + // show fightingstyle_glow + } + mouseExit + { + // hide fightingstyle_glow + } + action + { + play "sound/interface/button1.wav" + } + } + + itemDef + { + name fightingstylebutton3 + group stylebuttons + text @MENUS_SABERSTYLE + // descText @MENUS_ADDFIGHTINGSTYLE + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 240 190 380 24 + font 3 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_fightingstylesallowed + showCvar { "3" } + + cvar "ui_newfightingstyle" + cvarStrList + { + @MENUS_COMBATSTYLEFAST "0" + @MENUS_COMBATSTYLEMEDIUM "1" + } + + visible 1 + + mouseEnter + { + // show fightingstyle_glow + } + mouseExit + { + // hide fightingstyle_glow + } + action + { + play "sound/interface/button1.wav" + } + } + */ + itemDef + { + name fightingstylebutton4 + group stylebuttons + text @MENUS_FIGHTINGSTYLE_FAST + type ITEM_TYPE_TEXT + style WINDOW_STYLE_EMPTY + rect 240 190 0 0 + font 3 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_fightingstylesallowed + showCvar { "4" } + + visible 1 + } + + itemDef + { + name fightingstylebutton5 + group stylebuttons + text @MENUS_FIGHTINGSTYLE_MED + type ITEM_TYPE_TEXT + style WINDOW_STYLE_EMPTY + rect 240 190 0 0 + font 3 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_fightingstylesallowed + showCvar { "5" } + + visible 1 + } + + itemDef + { + name fightingstylebutton6 + group stylebuttons + text @MENUS_FIGHTINGSTYLE_STRONG + type ITEM_TYPE_TEXT + style WINDOW_STYLE_EMPTY + rect 240 190 0 0 + font 3 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .65 .65 1 1 + + cvarTest ui_fightingstylesallowed + showCvar { "6" } + + visible 1 + } + + itemDef + { + name fightingstyle_glow + group none + style WINDOW_STYLE_SHADER + rect 240 190 380 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + + +//---------------------------------------------------------------------------------------------- +//SABER MODELS +//---------------------------------------------------------------------------------------------- + //FIRST SABER + itemDef + { + name saber + group models + type ITEM_TYPE_MODEL + //rect 12 -130 615 615 + rect 12 -80 615 615 + asset_model "models/weapons2/saber_1/saber_1.glm" + isSaber 1 + model_angle 180 + model_rotation 20 + model_g2mins 0 0 0 + model_g2maxs 20 20 20 + model_fovx 75 + model_fovy 75 + visible 1 + decoration + } + + //SECOND SABER + itemDef + { + name saber2 + group models + type ITEM_TYPE_MODEL + rect 12 -50 615 615 + asset_model "models/weapons2/saber_1/saber_1.glm" + isSaber2 1 + model_angle 180 + model_rotation 20 + model_g2mins 0 0 0 + model_g2maxs 20 20 20 + model_fovx 75 + model_fovy 75 + visible 1 + + cvarTest ui_saber_type + hideCvar { "single" "staff" } + + decoration + } + +//---------------------------------------------------------------------------------------------- +// OTHER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name backbutton + group exit + + text @MENUS_BACK + descText @MENUS_BACKUP_ONE_MENU + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + cvarTest saber_menu + showCvar { "0" } + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open characterMenu + } + } + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + cvarTest saber_menu + showCvar { "0" } + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3" + uiScript "updatesabercvars" + close all + open quitMenu + } + } + + //BEGIN GAME BUTTON + itemDef + { + name begingamebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgame_begin + group none + text @MENUS_BEGIN_GAME + descText @MENUS_START_JEDI_KNIGHT_III + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 455 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + + cvarTest saber_menu + showCvar { "0" } + + action + { + uiScript "updatesabercvars" + uiScript "updatefightingstyle" + uiScript startgame + } + mouseEnter + { + show begingamebutton_glow + } + mouseExit + { + hide begingamebutton_glow + } + } + + itemDef + { + name continue + group none + text @MENUS_CONTINUE + descText @MENUS_NEW_MISSION_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 455 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + + cvarTest saber_menu + hideCvar { "0" } + + action + { + uiScript "updatesabercvars" + uiScript "updatefightingstyle" + close all + setcvar tier_storyinfo "13" + open ingameGotoTier + setcvar storyhead "kyle" + // HACK We're expecting to go to a mission screen from here + playvoice "sound/chars/storyinfo/13.mp3" + + } + mouseEnter + { + show begingamebutton_glow + } + mouseExit + { + hide begingamebutton_glow + } + } + } +} diff --git a/base/ui/saberstyle.menu b/base/ui/saberstyle.menu new file mode 100644 index 0000000..e7972b8 --- /dev/null +++ b/base/ui/saberstyle.menu @@ -0,0 +1,347 @@ +//---------------------------------------------------------------------------------------------- +// +// SABER STYLE MENU - called from in game - allows player to change saber fighting style +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "saberstyleMenu" + fullScreen 1 // MENU_TRUE + rect 0 0 640 480 // Size and position of the menu + visible 1 // Visible on open + focusColor 1 1 1 1 // Focus color for text and items + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript "getsabercvars" + uiScript "updatefightingstylechoices" + setfocus new_fightingstyle_button + uiScript "saber_hilt" + uiScript "saber_color" + } + + onESC + { + play "sound/interface/esc.wav" + uiScript "updatesabercvars" + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/sabermenu_back" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name styleboxleft + group none + style WINDOW_STYLE_SHADER + rect 127 53 198 40 + background "gfx/menus/sabermenu_stylebox_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name styleboxright + group none + style WINDOW_STYLE_SHADER + rect 325 53 198 40 + background "gfx/menus/sabermenu_stylebox_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name styleboxleft + group none + style WINDOW_STYLE_SHADER + rect 127 118 198 40 + background "gfx/menus/sabermenu_stylebox_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name styleboxright + group none + style WINDOW_STYLE_SHADER + rect 325 118 198 40 + background "gfx/menus/sabermenu_stylebox_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + +//---------------------------------------------------------------------------------------------- +// SABER MENU specific stuff +//---------------------------------------------------------------------------------------------- + itemDef + { + name title_glow + group none + style WINDOW_STYLE_SHADER + rect 100 26 440 24 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 1 + decoration + } + + // Screen title + itemDef + { + name title + group title + style WINDOW_STYLE_EMPTY + text @MENUS_CHOOSE_SABER_STYLE + rect 100 30 440 16 + font 3 + textscale 0.8 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// SABER COMBAT STYLES +//---------------------------------------------------------------------------------------------- + // Current fighting style + itemDef + { + name fightingstylebutton1 + group none + text @MENUS_CURRENT_STYLE + descText @MENUS_ADDFIGHTINGSTYLE + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 140 60 0 0 + font 3 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .549 .854 1 1 + + cvar "ui_currentfightingstyle" + cvarStrList + { + @MENUS_COMBATSTYLEFAST "0" + @MENUS_COMBATSTYLEMEDIUM "1" + @MENUS_COMBATSTYLEHEAVY "2" + } + + visible 1 + } + + itemDef + { + name new_fightingstyle_button + group none + text @MENUS_NEW_STYLE + descText @MENUS_ADDFIGHTINGSTYLE + type ITEM_TYPE_MULTI + style WINDOW_STYLE_EMPTY + rect 140 125 380 24 + font 3 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor .615 .615 .956 1 + + cvar "ui_newfightingstyle" + cvarStrList + { + @MENUS_COMBATSTYLEFAST "0" + @MENUS_COMBATSTYLEHEAVY "2" + } + + visible 1 + + mouseEnter + { + show button_glow + setitemrect button_glow 180 124 380 26 + } + mouseExit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + } + } + + itemDef + { + name briefing_background + group none + style WINDOW_STYLE_FILLED + rect 127 160 395 64 + backcolor 0 0 .35 .7 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .8 1 + visible 1 + decoration + } + + itemDef + { + name new_fightingstyle_desc + group none + type ITEM_TYPE_TEXTSCROLL + text @SP_INGAME_FAST_STYLE + style WINDOW_STYLE_EMPTY + rect 130 162 392 60 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + cvartest "ui_newfightingstyle" + showcvar + { + "0" + } + lineHeight 14 + visible 1 + autowrapped + } + + itemDef + { + name new_fightingstyle_desc + group none + type ITEM_TYPE_TEXTSCROLL + text @SP_INGAME_STRONG_STYLE + style WINDOW_STYLE_EMPTY + rect 130 162 392 60 + font 4 + textscale 1 + textalign ITEM_ALIGN_LEFT + textstyle 1 + textalignx 0 + textaligny 0 + forecolor 1 1 1 1 + cvartest "ui_newfightingstyle" + showcvar + { + "2" + } + + lineHeight 14 + visible 1 + autowrapped + } + +//---------------------------------------------------------------------------------------------- +//SABER MODELS +//---------------------------------------------------------------------------------------------- + itemDef + { + name saber + group models + type ITEM_TYPE_MODEL + rect 12 -110 615 615 + asset_model "models/weapons2/saber_reborn/saber_w.glm" + isSaber 1 + model_angle 180 + model_rotation 20 + model_g2mins 0 0 0 + model_g2maxs 20 20 20 + model_fovx 75 + model_fovy 75 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// CONTINUE BUTTON +//---------------------------------------------------------------------------------------------- + itemDef + { + name continue + group none + text @MENUS_CONTINUE + descText @MENUS_NEW_MISSION_DESC + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 455 444 130 24 + font 3 + textscale 1 + textalignx 65 + textaligny -1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + forecolor 1 .682 0 1 + visible 1 + + action + { + uiScript "updatesabercvars" + uiScript "updatefightingstyle" + close all + setcvar tier_storyinfo "7" + setcvar storyhead "protocol" + open ingameGotoTier + // HACK We're expecting to go to a mission screen from here + playvoice "sound/chars/storyinfo/7.mp3" + } + mouseEnter + { + show button_glow + setitemrect button_glow 455 444 130 24 + } + mouseExit + { + hide button_glow + } + } + + itemDef + { + name button_glow + group mods + style WINDOW_STYLE_SHADER + rect 455 444 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + } +} diff --git a/base/ui/setup.menu b/base/ui/setup.menu new file mode 100644 index 0000000..5432d93 --- /dev/null +++ b/base/ui/setup.menu @@ -0,0 +1,2750 @@ +//---------------------------------------------------------------------------------------------- +// +// SETUP MENU +// +//---------------------------------------------------------------------------------------------- +{ + menuDef + { + name "setupMenu" + fullScreen 1 + rect 0 0 640 480 + visible 1 + focusColor 1 1 1 1 + descX 320 + descY 426 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + + onOpen + { + uiScript getvideosetup ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide mods ; + hide defaults ; + show video ; + show setup_background ; + hide highlights ; + setitemcolor video1menubutton forecolor 1 1 1 1 ; + setitemcolor video2menubutton forecolor 1 .682 0 1 ; + setitemcolor soundmenubutton forecolor 1 .682 0 1; + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1; + setitemcolor modsmenubutton forecolor 1 .682 0 1; + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1; + } + + onESC + { + play "sound/interface/esc.wav" + + defer VideoSetup videowarningMenu ; + + close all ; + open mainMenu + } + +//---------------------------------------------------------------------------------------------- +// MENU BACKGROUND +//---------------------------------------------------------------------------------------------- + itemDef + { + name really_background + group none + style WINDOW_STYLE_SHADER + rect 156 154 320 240 + background "gfx/menus/main_centerblue" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text + group none + style WINDOW_STYLE_SHADER + rect 0 0 160 480 + background "gfx/menus/menu_side_text" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background_text_b + group none + style WINDOW_STYLE_SHADER + rect 480 0 160 480 + background "gfx/menus/menu_side_text_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name background + group none + style WINDOW_STYLE_SHADER + rect 0 0 640 480 + background "gfx/menus/main_background" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name starwars + group none + style WINDOW_STYLE_SHADER + rect 107 8 428 112 + background "gfx/menus/jediacademy" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name left_frame + group lf_fr + style WINDOW_STYLE_SHADER + rect 0 50 320 160 + background "gfx/menus/menu_boxes_left" + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name right_frame + group rt_fr + style WINDOW_STYLE_SHADER + rect 320 50 320 160 + background "gfx/menus/menu_boxes_right" + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// TOP MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Big button "NEW" + itemDef + { + name newbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 7 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name newgamebutton + group nbut + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 7 126 130 24 + text @MENUS_NEW + descText @MENUS_START_A_NEW_GAME + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show newbutton_glow + } + mouseExit + { + hide newbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + defer VideoSetup videowarningMenu ; + close all ; + open newgameMenu + } + } + + // Big button "LOAD" + itemDef + { + name loadgamebutton_glow + group none + style WINDOW_STYLE_SHADER + rect 170 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadgamebutton + group lbut + text @MENUS_LOAD + descText @MENUS_LOAD_A_SAVED_GAME + style WINDOW_STYLE_EMPTY + type ITEM_TYPE_BUTTON + rect 170 126 130 24 + textaligny 0 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show loadgamebutton_glow + } + mouseExit + { + hide loadgamebutton_glow + } + action + { + play "sound/interface/button1.wav" ; + defer VideoSetup videowarningMenu ; + close all ; + open loadgameMenu + } + } + + // Big button "CONTROLS" + itemDef + { + name controlsbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 340 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef { + name controlsbutton + group cbut + text @MENUS_CONTROLS + descText @MENUS_CONFIGURE_GAME_CONTROLS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show controlsbutton_glow + } + mouseExit + { + hide controlsbutton_glow + } + action + { + play "sound/interface/button1.wav" ; + defer VideoSetup videowarningMenu ; + close all ; + open controlsMenu + } + } + + // Big button "SETUP" + itemDef + { + name setupbutton_glow + group none + style WINDOW_STYLE_SHADER + rect 502 126 130 24 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name setupbutton + group sbut + text @MENUS_SETUP + descText @MENUS_CONFIGURE_GAME_SETTINGS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 502 126 130 24 + font 3 + textscale 1.1 + textaligny 0 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 65 + backcolor 0 0 0 0 + forecolor 1 1 1 1 + visible 1 + + mouseEnter + { + show setupbutton_glow + } + mouseExit + { + hide setupbutton_glow + } + + } + +//---------------------------------------------------------------------------------------------- +// OTHER MAIN MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // BACK button in lower left corner + itemDef + { + name backbutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 59 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name backbutton + group exit + text @MENUS_BACK + descText @MENUS_BACKTOMAIN + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 59 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show backbutton_glow + } + mouseExit + { + hide backbutton_glow + } + action + { + play "sound/interface/esc.wav" + close all ; + open mainMenu + } + } + + // EXIT button in lower left corner + itemDef + { + name exitgamebutton_glow + group exit + style WINDOW_STYLE_SHADER + rect 255 444 130 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name exitgamebutton + group exit + text @MENUS_EXIT + descText @MENUS_JEDI_KNIGHT_II + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 255 444 130 24 + font 3 + textscale 1.1 + textalign ITEM_ALIGN_CENTER + textstyle 3 + textalignx 65 + textaligny -1 + forecolor 1 .682 0 1 + visible 1 + + mouseEnter + { + show exitgamebutton_glow + } + mouseExit + { + hide exitgamebutton_glow + } + action + { + play "sound/weapons/saber/saberoff.mp3"; + defer VideoSetup videowarningMenu ; + close all ; + open quitMenu + } + } + +//---------------------------------------------------------------------------------------------- +// SECOND ROW MENU BUTTONS +//---------------------------------------------------------------------------------------------- + // Setup Options title + itemDef + { + name setup_title + group title + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_SETUP_OPTIONS + rect 100 164 440 16 + font 3 + textscale 0.7 + textalign ITEM_ALIGN_CENTER + textalignx 225 + textaligny -1 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + itemDef + { + name sidebutton_glow + group mods + style WINDOW_STYLE_SHADER + rect 60 185 200 24 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name video1menubutton + group none + text @MENUS_VIDEO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 185 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textstyle 1 + textalign ITEM_ALIGN_RIGHT + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_VIDEO_SETTINGS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 184 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + + defer VideoSetup videowarningMenu ; + + uiScript getvideosetup ; // Get video settings + + show setup_background ; + show video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide mods ; + hide defaults ; + setfocus graphics ; + setitemcolor video1menubutton forecolor 1 1 1 1 ; + setitemcolor video2menubutton forecolor 1 .682 0 1 ; + setitemcolor soundmenubutton forecolor 1 .682 0 1; + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1; + setitemcolor modsmenubutton forecolor 1 .682 0 1; + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1; + } + } + + itemDef + { + name video2menubutton + group none + text @MENUS_MORE_VIDEO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 209 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGUE_MORE_VIDEO_SETTINGS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 208 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + + defer VideoSetup videowarningMenu ; + + show setup_background ; + hide video ; + hide applyChanges ; + show video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide mods ; + hide defaults ; + setitemcolor video1menubutton forecolor 1 .682 0 1; + setitemcolor video2menubutton forecolor 1 1 1 1 ; + setitemcolor soundmenubutton forecolor 1 .682 0 1 ; + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1; + setitemcolor modsmenubutton forecolor 1 .682 0 1; + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1; + } + } + + itemDef + { + name soundmenubutton + group none + text @MENUS_SOUND + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 233 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_SOUND_SETTINGS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 232 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + + defer VideoSetup videowarningMenu ; + + show setup_background ; + hide video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + show sound ; + hide options ; + hide mods ; + hide defaults ; + setitemcolor video1menubutton forecolor 1 .682 0 1; + setitemcolor video2menubutton forecolor 1 .682 0 1; + setitemcolor soundmenubutton forecolor 1 1 1 1 ; + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1; + setitemcolor modsmenubutton forecolor 1 .682 0 1; + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1; + } + } + + // gameoptions button + itemDef + { + name gameoptionmenubutton + group none + text @MENUS_GAME_OPTIONS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 257 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_GAME_OPTIONS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 256 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + defer VideoSetup videowarningMenu ; + + play sound/interface/sub_select + show setup_background ; + hide video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + show options ; + hide mods ; + hide defaults ; + setitemcolor video1menubutton forecolor 1 .682 0 1; + setitemcolor video2menubutton forecolor 1 .682 0 1; + setitemcolor soundmenubutton forecolor 1 .682 0 1; + setitemcolor gameoptionmenubutton forecolor 1 1 1 1 ; + setitemcolor modsmenubutton forecolor 1 .682 0 1; + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1; + } + } + + // mods button + itemDef + { + name modsmenubutton + group none + text @MENUS_MODS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 281 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_CONFIGURE_GAME_OPTIONS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 280 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + + defer VideoSetup videowarningMenu ; + uiScript loadMods ; + + show setup_background ; + hide video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + show mods ; + hide defaults ; + setitemcolor video1menubutton forecolor 1 .682 0 1; + setitemcolor video2menubutton forecolor 1 .682 0 1; + setitemcolor soundmenubutton forecolor 1 .682 0 1; + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1; + setitemcolor modsmenubutton forecolor 1 1 1 1 ; + setitemcolor gamedefaultsmenubutton forecolor 1 .682 0 1; + } + } + + // gamedefaults button + itemDef + { + name gamedefaultsmenubutton + group none + text @MENUS_DEFAULTS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 80 305 170 24 + font 3 + textscale 0.9 + textalignx 170 + textaligny 2 + textalign ITEM_ALIGN_RIGHT + textstyle 1 + forecolor 1 .682 0 1 + visible 1 + descText @MENUS_RESTORE_DEFAULT_SETTINGS + + mouseEnter + { + show sidebutton_glow + setitemrect sidebutton_glow 40 304 220 26 + } + mouseExit + { + hide sidebutton_glow + } + action + { + play sound/interface/sub_select + + defer VideoSetup videowarningMenu ; + + show setup_background ; + hide video ; + hide applyChanges ; + hide video2 ; + hide vidrestart ; + hide sound ; + hide options ; + hide mods ; + show defaults ; + setitemcolor video1menubutton forecolor 1 .682 0 1; + setitemcolor video2menubutton forecolor 1 .682 0 1; + setitemcolor soundmenubutton forecolor 1 .682 0 1; + setitemcolor gameoptionmenubutton forecolor 1 .682 0 1; + setitemcolor modsmenubutton forecolor 1 .682 0 1; + setitemcolor gamedefaultsmenubutton forecolor 1 1 1 1 ; + } + } + + itemDef + { + name setup_background + group none + style WINDOW_STYLE_FILLED + rect 260 185 340 225 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// HIGHLIGHT BARS +//---------------------------------------------------------------------------------------------- + itemDef + { + name highlight1 + group highlights + style WINDOW_STYLE_SHADER + rect 260 190 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight2 + group highlights + style WINDOW_STYLE_SHADER + rect 260 204 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight3 + group highlights + style WINDOW_STYLE_SHADER + rect 260 218 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight4 + group highlights + style WINDOW_STYLE_SHADER + rect 260 232 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight5 + group highlights + style WINDOW_STYLE_SHADER + rect 260 246 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight6 + group highlights + style WINDOW_STYLE_SHADER + rect 260 260 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight7 + group highlights + style WINDOW_STYLE_SHADER + rect 260 274 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight8 + group highlights + style WINDOW_STYLE_SHADER + rect 260 288 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight9 + group highlights + style WINDOW_STYLE_SHADER + rect 260 302 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight10 + group highlights + style WINDOW_STYLE_SHADER + rect 260 316 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight11 + group highlights + style WINDOW_STYLE_SHADER + rect 260 330 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight12 + group highlights + style WINDOW_STYLE_SHADER + rect 260 344 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight13 + group highlights + style WINDOW_STYLE_SHADER + rect 260 358 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight14 + group highlights + style WINDOW_STYLE_SHADER + rect 260 372 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name highlight15 + group highlights + style WINDOW_STYLE_SHADER + rect 260 386 340 14 + background "gfx/menus/menu_blendbox" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// VIDEO 1 MENU BUTTONS +//---------------------------------------------------------------------------------------------- + itemDef + { + name graphics + group video + text @MENUS_VIDEO_QUALITY + type ITEM_TYPE_MULTI + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + style WINDOW_STYLE_FILLED + forecolor .615 .615 .956 1 + descText @MENUS_SELECT_PRESET_GRAPHIC + + visible 0 + + cvar "ui_r_glCustom" + cvarFloatList + { + @MENUS_HIGH_QUALITY 0 + @MENUS_NORMAL 1 + @MENUS_FAST 2 + @MENUS_FASTEST 3 + @MENUS_CUSTOM 4 + } + + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + action + { + play "sound/interface/button1.wav" ; + uiScript update "ui_r_glCustom" ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + + + itemDef + { + name video_mode + group video + type ITEM_TYPE_MULTI + text @MENUS_VIDEO_MODE + cvarFloatList { @MENUS_640_X_480 3 @MENUS_800_X_600 4 @MENUS_1024_X_768 6 @MENUS_1152_X_864 7 @MENUS_1280_X_1024 8 @MENUS_1600_X_1200 9 @MENUS_2048_X_1536 10 @MENUS_2400_X_600 12 } + cvar "ui_r_mode" + + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + descText @MENUS_CHANGE_THE_DISPLAY_RESOLUTION + + visible 0 + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name color_depth + group video + type ITEM_TYPE_MULTI + text @MENUS_COLOR_DEPTH + rect 260 230 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + + cvarFloatList { @MENUS_DEFAULT 0 @MENUS_16_BIT 16 @MENUS_32_BIT 32 } + descText @MENUS_CHANGE_THE_NUMBER_OF + cvar "ui_r_colorbits" + + visible 0 + + mouseenter + { + show highlight4 + } + mouseexit + { + hide highlight4 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + uiScript update "ui_r_colorbits" ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name fullscreen + group video + type ITEM_TYPE_MULTI + text @MENUS_FULL_SCREEN + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_TOGGLE_BETWEEN_FULL_SCREEN + cvar "ui_r_fullscreen" + + visible 0 + + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name geometric_detail + group video + type ITEM_TYPE_MULTI + text @MENUS_GEOMETRIC_DETAIL + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_LOW 2 @MENUS_MEDIUM 1 @MENUS_HIGH 0 } + descText @MENUS_ADJUST_THE_NUMBER_OF + cvar "ui_r_lodbias" + + visible 0 + + mouseenter + { + show highlight6 + } + mouseexit + { + hide highlight6 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + uiScript update "ui_r_lodbias" ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name texture_detail + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_DETAIL + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_LOW 3 @MENUS_MEDIUM 2 @MENUS_HIGH 1 @MENUS_VERY_HIGH 0 } + descText @MENUS_SELECT_THE_RESOLUTION + cvar "ui_r_picmip" + + visible 0 + + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name texture_quality + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_QUALITY + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_DEFAULT 0 @MENUS_16_BIT 16 @MENUS_32_BIT 32 } + descText @MENUS_SELECT_THE_NUMBER_OF + cvar "ui_r_texturebits" + + visible 0 + + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name texture_filter + group video + type ITEM_TYPE_MULTI + text @MENUS_TEXTURE_FILTER + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarStrList { @MENUS_BILINEAR , "GL_LINEAR_MIPMAP_NEAREST" , @MENUS_TRILINEAR , "GL_LINEAR_MIPMAP_LINEAR" } + descText @MENUS_ADJUST_HOW_WELL_THE_TEXTURES + cvar "ui_r_texturemode" + + visible 0 + + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name simple_shaders + group video + type ITEM_TYPE_MULTI + text @MENUS_DETAILED_SHADERS + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_HIDE_OR_UNHIDE_TEXTURES + cvar "ui_r_detailtextures" + + visible 0 + + mouseenter + { + show highlight10 + } + mouseexit + { + hide highlight10 + } + action + { + play "sound/interface/button1.wav" ; + uiScript glCustom ; + setcvar ui_r_modified 1 ; + show applyChanges + } + } + + itemDef + { + name video_sync + group video + type ITEM_TYPE_MULTI + text @MENUS_VIDEO_SYNC + cvar "r_swapInterval" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_VIDEO_SYNC_DESC + + mouseenter + { + show button_glow + setitemrect button_glow 260 328 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + + } + } + + itemDef + { + name compress_textures + group video_obsolete + type ITEM_TYPE_MULTI + text @MENUS_COMPRESSED_TEXTURES + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + descText @MENUS_TAKE_ADVANTAGE_OF_3D + cvar "ui_r_ext_compress_textures" + + visible 0 + + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + action + { + play "sound/interface/button1.wav" + uiScript glCustom + setcvar ui_r_modified 1 + show applyChanges + } + } + + + + // APPLY CHANGES BUTTON + itemDef + { + name applybutton_glow + group none + style WINDOW_STYLE_SHADER + rect 260 384 340 14 + background "gfx/menus/menu_blendbox2" // Frame around button + forecolor 1 0.5 0.5 1 + visible 0 + decoration + } + + itemDef + { + name applyChanges + group none + text @MENUS_APPLY_CHANGES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 260 384 340 14 + textaligny 0 + font 4 + textscale 1 + textalignx 170 + textalign ITEM_ALIGN_CENTER + forecolor 1 0 0 1 + backcolor 0 0 1 0 + visible 0 + + mouseEnter + { + show applybutton_glow + } + mouseExit + { + hide applybutton_glow + } + action + { + play "sound/interface/button1.wav" ; + show setup_background ; + show vidrestart ; + setfocus vidrestart_no ; + hide video ; + hide video2 ; + hide applybutton_glow ; + hide applyChanges ; + } + } + +//---------------------------------------------------------------------------------------------- +// VIDEO RESTART +//---------------------------------------------------------------------------------------------- + // Faint red box +/* itemDef + { + name vidrestart_background + group vidrestart + style WINDOW_STYLE_SHADER + rect 300 171 300 250 + background "gfx/menus/menu_boxred" // Frame + forecolor 1 1 1 0.5 + visible 0 + decoration + } */ + itemDef + { + name vidrestart_background + group vidrestart + style WINDOW_STYLE_FILLED + rect 260 185 340 225 + backcolor 0 0 .6 .5 + forecolor 1 1 1 1 + border 1 + bordercolor 0 0 .6 1 + visible 0 + decoration + } + + + itemDef + { + name vidrestart_text1 + group vidrestart + text @MENUS_THIS_WILL_APPLY_VIDEO + text2 @MENUS_AND_RETURN_TO_THE_MAIN + rect 305 230 290 20 + textalign ITEM_ALIGN_CENTER + text2aligny 18 + textalignx 145 + font 2 + textscale 1 + forecolor 1 1 0 1 + visible 0 + decoration + } + + itemDef + { + name vidrestart_text2 + group vidrestart + text @MENUS_VID_RESTART3 + rect 305 300 290 20 + textalign ITEM_ALIGN_CENTER + textalignx 145 + font 2 + textscale 1 + forecolor 1 1 0 1 + visible 0 + } + + + + itemDef + { + name vidrestart_yes_button + group none + style WINDOW_STYLE_SHADER + rect 467 389 120 22 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // YES button + itemDef + { + name vidrestart_yes + group vidrestart + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 467 386 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny 0 + descText @MENUS_APPLY_CHANGES_AND_THEN + forecolor 1 .682 0 1 + visible -1 + + action + { + play "sound/interface/button1.wav" ; + close all ; + uiScript updatevideosetup ; + } + mouseEnter + { + show vidrestart_yes_button + } + mouseExit + { + hide vidrestart_yes_button + } + + } + + itemDef + { + name vidrestart_no_button + group vidrestart + style WINDOW_STYLE_SHADER + rect 305 389 120 22 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // CANCEL button + itemDef + { + name vidrestart_no + group vidrestart + text @MENUS_NO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 305 386 120 32 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -1 + descText @MENUS_DO_NOT_APPLY_CHANGES + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/esc.wav" + setfocus video1menubutton ; + show setup_background ; + hide vidrestart ; + show video ; + hide video2 ; + show applyChanges ; + hide vidrestart_yes_button ; + hide vidrestart_no_button ; + } + mouseEnter + { + show vidrestart_no_button + } + mouseExit + { + hide vidrestart_no_button + } + + } + +//---------------------------------------------------------------------------------------------- +// VIDEO 2 +//---------------------------------------------------------------------------------------------- + itemDef + { + name gamma_text + group video2 + style WINDOW_STYLE_SHADER + rect 290 198 280 36 + background "gfx/menus/greyscale" // greyscale + forecolor 1 1 1 1 + visible 0 + decoration + + } + + itemDef + { + name bright_text + group video2 + text @MENUS_ADJUST_BRIGHTNESS_SLIDER + text2 @MENUS_THE_NUMBER_6_CAN_BARELY + text2aligny 14 + textalignx 170 + textaligny 0 + font 4 + textscale 1 + rect 260 234 340 20 + textalign ITEM_ALIGN_CENTER + forecolor .549 .854 1 1 + visible 0 + decoration + } + + itemDef + { + name brightness + group video2 + type ITEM_TYPE_SLIDER + text @MENUS_VIDEO_BRIGHTNESS + cvarfloat "r_gamma" 1 .5 3 + rect 260 267 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ADJUST_THE_BRIGHTNESS + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show button_glow + setitemrect button_glow 260 267 340 20 + } + mouseexit + { + hide button_glow + } + } + + itemDef + { + name shadows + group video2 + type ITEM_TYPE_MULTI + text @MENUS_SHADOWS + descText @MENUS_SHADOWS_DESC + cvar "cg_shadows" + cvarFloatList + { + @MENUS_NONE 0 + @MENUS_SHADOWS_SIMPLE 1 + @MENUS_SHADOWS_VOLUMETRIC 2 +// "Projected" 3 + } + rect 260 287 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + action + { + play "sound/interface/button1.wav" ; + } + mouseenter + { + show button_glow + setitemrect button_glow 260 287 340 20 + } + mouseexit + { + hide button_glow + } + + } + + + itemDef + { + name dynamic_light + group video2 + type ITEM_TYPE_MULTI + text @MENUS_DYNAMIC_LIGHTS + cvar "r_dynamiclight" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 301 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_TO_TURN_ON_MOVING + + mouseenter + { + show button_glow + setitemrect button_glow 260 301 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + + } + } + + itemDef + { + name dynamic_glow + group video2 + type ITEM_TYPE_MULTI + text @MENUS_DYNAMIC_GLOW + cvar "r_dynamicglow" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 315 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_DYNAMIC_GLOW_DESC + + mouseenter + { + show button_glow + setitemrect button_glow 260 315 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" ; + + } + } + + itemDef + { + name light_flares + group video2 + type ITEM_TYPE_MULTI + text @MENUS_LIGHT_FLARES + cvar "r_flares" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 329 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_TO_SHOW_HALOS + + mouseenter + { + show button_glow + setitemrect button_glow 260 329 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + } + } + + itemDef + { + name wall_marks + group video2 + type ITEM_TYPE_MULTI + text @MENUS_WALL_MARKS + cvar "cg_marks" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 343 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_TO_DISPLAY_SCORCH + + mouseenter + { + show button_glow + setitemrect button_glow 260 343 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + } + } + + itemDef + { + name video_mode + group video2 + type ITEM_TYPE_SLIDER + text @MENUS_ANISOTROPIC_FILTERING + cvarTest r_ext_texture_filter_anisotropic_avail + hideCvar { 0 } + cvarfloat "r_ext_texture_filter_anisotropic" 2 0 16 + rect 260 359 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + + descText @MENUS_TOGGLE_ADVANCED_TEXTURE + + mouseenter + { + show button_glow + setitemrect button_glow 260 359 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + } + } + + itemDef + { + name advancedvideo + group video2 + type ITEM_TYPE_BUTTON + text @MENUS_SHOW_DRIVER_INFO + rect 260 393 340 14 + textalign ITEM_ALIGN_CENTER + textstyle 1 + textalignx 170 + textaligny 0 + font 4 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + descText @MENUS_SHOW_ADVANCED_INFORMATION + + mouseenter + { + show button_glow + setitemrect button_glow 260 392 340 20 + } + mouseexit + { + hide button_glow + } + action + { + play "sound/interface/button1.wav" + setfocus video2menubutton + hide button_glow + open videodriverMenu + } + } + +//---------------------------------------------------------------------------------------------- +// SOUND FIELDS +//---------------------------------------------------------------------------------------------- + itemDef + { + name effects_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_EFFECTS_VOLUME + cvarfloat "s_volume" 0 0 1 + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ADJUST_VOLUME_FOR_SOUND + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + } + + itemDef + { + name music_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_MUSIC_VOLUME + cvarfloat "s_musicvolume" 0 0 1 + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ADJUST_VOLUME_FOR_MUSIC + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + } + + itemDef + { + name voice_volume + group sound + type ITEM_TYPE_SLIDER + text @MENUS_VOICE_VOLUME + cvarfloat "s_volumevoice" 0 0 1 + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_ADJUST_VOLUME_FOR_SPEECH + action + { + play "sound/interface/button1.wav" ; + } + + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + } + + itemDef + { + name sound_quality + group sound + type ITEM_TYPE_MULTI + text @MENUS_SOUND_QUALITY + cvar "s_khz" + cvarFloatList { @MENUS_LOW 11 @MENUS_HIGH 22 } + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TRADE_CLARITY_OF_SOUND + cvarTest "s_UseOpenAL" + hideCvar { 1 } + + + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + action + { + play "sound/interface/button1.wav" + uiScript update s_khz + } + } + + itemDef + { + name eax + group sound + type ITEM_TYPE_MULTI + cvar "s_UseOpenAL" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + text @MENUS_EAX + rect 260 300 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_EAX_DESC + action + { + play "sound/interface/button1.wav" + uiScript update s_khz + } + + mouseenter + { + show highlight9 + } + mouseexit + { + hide highlight9 + } + } + + itemDef + { + name eax_icon + group sound + style WINDOW_STYLE_SHADER + rect 366 316 128 64 + background "gfx/menus/eax" + forecolor 1 1 1 1 + visible 1 + decoration + cvarTest "s_UseOpenAL" + hideCvar { 0 } + } + +//---------------------------------------------------------------------------------------------- +// OPTION FIELDS +//---------------------------------------------------------------------------------------------- + itemDef + { + name draw_crosshair + group options + type ITEM_TYPE_MULTI + text @MENUS_DRAW_CROSSHAIR + cvar "cg_drawcrosshair" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 188 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_TO_SHOW_OR_HIDE + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight1 + } + mouseexit + { + hide highlight1 + } + } + + + itemDef + { + name identifytarget + group options + type ITEM_TYPE_MULTI + text @MENUS_IDENTIFY_TARGET + cvar "cg_crosshairIdentifyTarget" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 202 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_TO_HAVE_THE_CROSSHAIR + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight2 + } + mouseexit + { + hide highlight2 + } + } + + + itemDef + { + name slowmo + group options + type ITEM_TYPE_MULTI + text @MENUS_SLOW_MOTION_DEATH + cvar "d_slowmodeath" + cvarFloatList + { + @MENUS_NEVER 0 + @MENUS_ON_DEATH 1 + @MENUS_RARELY 2 + @MENUS_SLOW_MO_NORMAL 3 + @MENUS_OFTEN 4 + @MENUS_FREQUENTLY 5 + @MENUS_EXCESSIVELY 6 + } + rect 260 216 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_SELECT_THE_FREQUENCY + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight3 + } + mouseexit + { + hide highlight3 + } + } + + + itemDef + { + name force1st + group options + type ITEM_TYPE_MULTI + text @MENUS_1ST_PERSON_GUNS + cvar "cg_gunAutoFirst" + cvarFloatList { @MENUS_OFF 0 @MENUS_ON 1 } + rect 260 244 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_WHEN_PUTTING_AWAY_SABER + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight5 + } + mouseexit + { + hide highlight5 + } + } + + itemDef + { + name footsteps + group options + type ITEM_TYPE_MULTI + text @MENUS_FOOTSTEPS + desctext @MENUS_FOOTSTEPS_DESC + cvar "cg_footsteps" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_SOUNDS 1 + @MENUS_SOUNDS_AND_EFFECTS 2 + @MENUS_SOUNDS_EFFECTS_GRAPHICS 3 + } + rect 260 258 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight6 + } + mouseexit + { + hide highlight6 + } + } + + itemDef + { + name dismemberment + group options + type ITEM_TYPE_MULTI + text @MENUS_DISMEMBERMENT + cvar "g_dismemberment" + cvarFloatList + { + @MENUS_OFF 0 + @MENUS_ON 3 + } + cvarTest ui_iscensored + hideCvar { 1 } + rect 260 272 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_SELECT_WHAT_LIGHTSABER + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight7 + } + mouseexit + { + hide highlight7 + } + } + +// Weapon Sway. Yes, this is nutty. Two cvars here, one removes weapon sway, the other adds it. + itemDef + { + name weaponswayon + group options + type ITEM_TYPE_MULTI + text @MENUS_VIEW_SWAYING + descText @MENUS_VIEW_SWAYING_DESC + cvar "ui_disableWeaponSway" + cvarFloatList + { + @MENUS_ON 0 + @MENUS_OFF 1 + } + cvarTest "ui_disableWeaponSway" + showCvar + { + "0" + } + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + + action + { + play "sound/interface/button1.wav" + exec "exec noMotion.cfg" ; + show weaponswayoff ; + setfocus weaponswayoff + } + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name weaponswayoff + group options + type ITEM_TYPE_MULTI + text @MENUS_VIEW_SWAYING + descText @MENUS_VIEW_SWAYING_DESC + cvar "ui_disableWeaponSway" + cvarFloatList + { + @MENUS_ON 0 + @MENUS_OFF 1 + } + cvarTest "ui_disableWeaponSway" + hideCvar + { + "0" + } + rect 260 286 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + + action + { + play "sound/interface/button1.wav" + exec "exec restoreMotion.cfg" ; + show weaponswayon ; + setfocus weaponswayon + } + mouseenter + { + show highlight8 + } + mouseexit + { + hide highlight8 + } + } + + itemDef + { + name text + group options + type ITEM_TYPE_MULTI + text @MENUS_TEXT + cvar "se_language" + feeder 23 //FEEDER_PLAYER_SPECIES + cvarStrList feeder + rect 260 314 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_CHOOSE_THE_LANGUAGE_FOR + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight10 + } + mouseexit + { + hide highlight10 + } + } + + itemDef + { + name voice + group options + type ITEM_TYPE_MULTI + text @MENUS_VOICE + cvarTest com_demo + hideCvar { 1 } + cvar "s_language" + cvarStrList + { + "English", "english" + @MENUS_LANG_FRENCH "francais" + "Deutsch" "deutsch" + "Español" "espanol" + } + rect 260 328 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_CHOOSE_THE_LANGUAGE_TO + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight11 + } + mouseexit + { + hide highlight11 + } + } + + itemDef + { + name voice + group options + type ITEM_TYPE_MULTI + text @MENUS_SUBTITLES + cvar "g_subtitles" + cvarFloatList + { + @MENUS_NONE 0 + @MENUS_IN_CINEMATICS 2 +// @MENUS_ALL_VOICEOVERS 1 + } + rect 260 342 340 14 + textalign ITEM_ALIGN_RIGHT + textalignx 174 + textaligny 0 + font 4 + textscale 1 + forecolor .615 .615 .956 1 + visible 0 + descText @MENUS_TOGGLE_WHETHER_SUBTITLES + + action + { + play "sound/interface/button1.wav" + } + mouseenter + { + show highlight12 + } + mouseexit + { + hide highlight12 + } + } + +//---------------------------------------------------------------------------------------------- +// MOD GAME MENU specific stuff +//---------------------------------------------------------------------------------------------- + itemDef + { + name serverinfo + group mods + rect 270 196 320 176 + type ITEM_TYPE_LISTBOX + style WINDOW_STYLE_FILLED + elementwidth 120 + elementheight 16 + font 2 + textscale 1 + textaligny -2 + border 1 + bordersize 1 + bordercolor .5 .5 .5 .5 + forecolor .79 .64 .22 1 + backcolor 0 0 .5 .25 + outlinecolor .5 .5 .5 .5 + elementtype LISTBOX_TEXT + feeder 9 + notselectable + visible 0 + columns 1 2 40 280 + } + + itemDef + { + name loadmod_button + group none + style WINDOW_STYLE_SHADER + rect 310 384 240 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + visible 0 + decoration + } + + itemDef + { + name loadmod + group mods + text @MENUS_LOAD_MOD + descText @MENUS_LOAD_CHOSEN_MOD + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 260 384 340 24 + textalign ITEM_ALIGN_CENTER + textalignx 170 + textaligny -2 + font 2 + textscale 1 + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/button1.wav" + uiScript RunMod ; + } + + mouseEnter + { + show loadmod_button + } + mouseExit + { + hide loadmod_button + } + } + + itemDef + { + name button_glow + group none + style WINDOW_STYLE_SHADER + rect 0 0 0 0 + background "gfx/menus/menu_buttonback" + forecolor 1 1 1 1 + visible 0 + decoration + } + +//---------------------------------------------------------------------------------------------- +// RESET DEFAULTS +//---------------------------------------------------------------------------------------------- + // Faint red box + itemDef + { + name vidrestart_background + group defaults + style WINDOW_STYLE_FILLED + rect 257 182 343 228 + backcolor 0 0 .6 .5 + forecolor .5 0 0 1 + border 1 + bordersize 3 + bordercolor 1 0 0 1 + visible 0 + decoration + } + + + itemDef + { + name options + group defaults + text @MENUS_WARNING + rect 260 202 340 20 + textalign ITEM_ALIGN_CENTER + textalignx 170 + font 2 + textscale 1 + forecolor 1 0 0 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_THIS_WILL_SET_ALL_GAME + text2 @MENUS_TO_THEIR_FACTORY_SETTINGS + rect 260 230 340 20 + textalign ITEM_ALIGN_CENTER + textalignx 170 + text2aligny 20 + font 2 + textscale 1 + forecolor 1 0 0 1 + visible 0 + decoration + } + + itemDef + { + name options + group defaults + text @MENUS_VID_RESTART3 + rect 260 286 340 20 + textalign ITEM_ALIGN_CENTER + textalignx 170 + font 2 + textscale 1 + forecolor 1 0 0 1 + visible 0 + decoration + } + + + itemDef + { + name default_yes_button + group highlights + style WINDOW_STYLE_SHADER + rect 443 364 120 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // YES button - lose reset defaults + itemDef + { + name default_yes + group defaults + text @MENUS_YES + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 443 364 120 24 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -2 + descText @MENUS_USE_DEFAULT_SETTINGS + forecolor 1 .682 0 1 + visible 0 + + action + { + play "sound/interface/button1.wav" ; + hide highlights ; + close all ; + uiscript resetdefaults + } + mouseEnter + { + show default_yes_button + } + mouseExit + { + hide default_yes_button + } + + } + + itemDef + { + name default_no_button + group highlights + style WINDOW_STYLE_SHADER + rect 297 364 120 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // NO button - return to Main Menu + itemDef + { + name default_no + group defaults + text @MENUS_NO + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 297 364 120 24 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -2 + descText @MENUS_DO_NOT_RESET_DEFAULT + forecolor 1 .682 0 1 + visible 0 + action + { + play "sound/interface/esc.wav" + hide highlights ; + hide default_no_button + uiscript clearmouseover default_no ; + close all ; + open mainMenu ; + } + mouseEnter + { + show default_no_button + } + mouseExit + { + hide default_no_button + } + } + } +} diff --git a/base/ui/tier1.txt b/base/ui/tier1.txt new file mode 100644 index 0000000..ff432f4 --- /dev/null +++ b/base/ui/tier1.txt @@ -0,0 +1,5 @@ +// menu defs +// +{ + loadMenu { "ui/ingameMissionSelect1.menu" } +} diff --git a/base/ui/tier2.txt b/base/ui/tier2.txt new file mode 100644 index 0000000..2407b6e --- /dev/null +++ b/base/ui/tier2.txt @@ -0,0 +1,5 @@ +// menu defs +// +{ + loadMenu { "ui/ingameMissionSelect2.menu" } +} diff --git a/base/ui/tier3.txt b/base/ui/tier3.txt new file mode 100644 index 0000000..9f144db --- /dev/null +++ b/base/ui/tier3.txt @@ -0,0 +1,5 @@ +// menu defs +// +{ + loadMenu { "ui/ingameMissionSelect3.menu" } +} diff --git a/base/ui/vid_warning.menu b/base/ui/vid_warning.menu new file mode 100644 index 0000000..25b4e0d --- /dev/null +++ b/base/ui/vid_warning.menu @@ -0,0 +1,204 @@ +//------------------------------------------------------------------------------------------------ +// VIDEO WARNING +// +// +//------------------------------------------------------------------------------------------------ +{ + menuDef + { + name "videowarningMenu" + visible 0 + fullScreen 1 // MENU_TRUE + rect 100 120 440 320 + focusColor 1 1 1 1 // Focus color for text and items + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + descX 320 + descY 360 + descScale 1 + descColor 1 .682 0 .8 + descAlignment ITEM_ALIGN_CENTER + popup + + onESC + { + play "sound/interface/esc.wav" + close all + open setupMenu + } + + onClose + { + } + + itemDef + { + name warn_background + group none + style WINDOW_STYLE_SHADER + rect 100 120 440 300 + background "gfx/menus/menu_boxred" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + +//---------------------------------------------------------------------------------------------- +// VIDEO WARNING +//---------------------------------------------------------------------------------------------- + // Video Warning title + itemDef + { + name vidwarn_title + group vidwarn + style WINDOW_STYLE_SHADER + background "gfx/menus/menu_blendbox" + text @MENUS_UNAPPLIED_VIDEO_CHANGES + rect 120 130 400 20 + font 3 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 200 + textaligny -3 + forecolor .549 .854 1 1 + visible 1 + decoration + } + + + itemDef + { + name vidwarn_text1 + group vidwarn + text @MENUS_YOU_HAVE_MADE_CHANGES + rect 210 240 220 20 + textalign ITEM_ALIGN_CENTER + text2aligny 18 + textalignx 110 + font 2 + textscale 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name vidwarn_text2 + group vidwarn + text @MENUS_APPLY_THESE_CHANGES_OR + rect 210 260 220 20 + textalign ITEM_ALIGN_CENTER + textalignx 110 + font 2 + textscale 1 + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name vidwarn_no_button + group vidwarn + style WINDOW_STYLE_SHADER + rect 180 380 120 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // DISCARD button - return to Video Data screen + itemDef + { + name vidwarn_no + group vidwarn + text @MENUS_DISCARD + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 180 380 120 24 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -3 + descText @MENUS_DO_NOT_APPLY_CHANGES + forecolor .79 .64 .22 1 + visible 1 + + action + { + play "sound/interface/esc.wav" + close "videowarningMenu" ; + + setcvar "ui_r_modified" 0 ; + + rundeferred ; + } + + mouseEnter + { + show vidwarn_no_button + } + + mouseExit + { + hide vidwarn_no_button + } + } + + itemDef + { + name vidwarn_yes_button + group vidwarn + style WINDOW_STYLE_SHADER + rect 340 380 120 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + // APPLY button, use settings + itemDef + { + name vidwarn_yes + group vidwarn + text @MENUS_APPLY + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 340 380 120 24 + font 2 + textscale 1 + textalign ITEM_ALIGN_CENTER + textalignx 60 + textaligny -3 + descText @MENUS_APPLY_CHANGES_AND_THEN + forecolor .79 .64 .22 1 + visible 1 + + action + { + play "sound/interface/button1.wav" ; + + uiScript updatevideosetup ; + close all + } + mouseEnter + { + show vidwarn_yes_button + } + mouseExit + { + hide vidwarn_yes_button + } + } + + } +} + + + + + + diff --git a/base/ui/videodriver.menu b/base/ui/videodriver.menu new file mode 100644 index 0000000..0129a49 --- /dev/null +++ b/base/ui/videodriver.menu @@ -0,0 +1,113 @@ +// VIDEODRIVER MENU +{ + menuDef + { + name "videodriverMenu" + visible 0 + fullScreen 0 // MENU_TRUE + rect 100 120 440 320 + focusColor 1 1 1 1 // Focus color for text and items + outOfBoundsClick // this closes the window if it gets a click out of the rectangle + descX 320 + descY 450 + descScale 1 + descColor .79 .64 .22 .7 // Focus color for text and items + descAlignment ITEM_ALIGN_CENTER + popup + + onESC + { + play "sound/interface/esc.wav" + close all + open setupMenu + } + + onClose + { + } + + + //---------------------------------------------------------------------------------------------- + // + // MENU BACKGROUND + // + //---------------------------------------------------------------------------------------------- + itemDef + { + name driver_background + group none + style WINDOW_STYLE_SHADER + rect 0 0 440 320 + background "gfx/menus/menu_boxred" // Frame + forecolor 1 1 1 1 + visible 1 + decoration + } + + itemDef + { + name driver_done_button + group none + style WINDOW_STYLE_SHADER + rect 150 293 140 24 + background "gfx/menus/menu_buttonback" // Frame around button + forecolor 1 1 1 1 + decoration + visible 0 + } + + itemDef + { + name driver_done + group none + text @MENUS_DONE_CAPS + type ITEM_TYPE_BUTTON + style WINDOW_STYLE_EMPTY + rect 150 293 140 24 + forecolor 1 1 1 1 + textalign ITEM_ALIGN_CENTER + textstyle 0 + textalignx 70 + textaligny 0 + font 3 + textscale 1 + forecolor .79 .64 .22 1 + visible 1 + + mouseEnter + { + show driver_done_button + } + mouseExit + { + hide driver_done_button + } + action + { + close videodriverMenu + } + } + + + //---------------------------------------------------------------------------------------------- + // + // VIDEO DRIVER INFO + // + //---------------------------------------------------------------------------------------------- + itemDef // Drivers + { + name game_version + group none + ownerdraw 249 // UI_GLINFO + font 4 + textscale 1 + rect 10 3 430 280 + forecolor .79 .64 .22 1 + style WINDOW_STYLE_EMPTY + textalign ITEM_ALIGN_LEFT + textaligny 14 + visible 1 + decoration + } + } +} \ No newline at end of file diff --git a/base/ui/vssver.scc b/base/ui/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..5cbde0b44b2dd3c1c6da0e651e1c0455be983fc1 GIT binary patch literal 928 zcmXxidq`7J90&00+m&Wcn{(QHY|WCeY?T&bvQ?zy0&~uAmToF!yK>Ub7iy`a?;T?syyV#vdVk(8y11&1@ldus2+FJl8DhZ6Y&a z5xnjD!+nNp4a6Syfd$GJRei2&XxtY*yy#a}+1e;F1M#`=$9T*9B9{!}0E=N&T%y%F zkVf2LKUkwwJ^gQgcvf}j#qmSii4gGs*f869D(_(-L6-?cV;3H}Bkm`UxWE#)UeQsT zQNbs!uoN!2op7e$vJaUD&w~rI?cFUrA#s9baPigt$r1kmT3;;uHCn7$Te^tuUj>_@ z%Z0V+3#m82k7Mh|orq;*7Upk+JLNrsuf_LRE`L1SuKl2HJ=f1lU^Q&^HKmk&e#vt6 zCcsJ8b!S6nT3IfCHi|Z`w9As!M-X2)2bSzAy0G7Q1>wQDaLnk(iIuUPESE12wrOij<5pa040dxOoJ%|yCQ}|XXq%i* z1n?2KWk6rCWK2f<;bC~)X3x3j#F-xJ z?kf4>K+t8z(R|+Zb}~}N(Dke5Fidm!)-X}dbJhlNGrUUwEzMwgOI-sOUQ!SC-Fi*^ z1b(N@Vwx&*i8ICrW0QWveMbHaP5lo#oe5w7 literal 0 HcmV?d00001 diff --git a/base/vssver.scc b/base/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..c135a0dbc27eede2e3c880289c869c508a5468b7 GIT binary patch literal 160 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiWUJqpV(VPXJ-4nYQnbJ{7>+Rg+n;s=Us5oKWD z+qdI<+ULAQ>_GlAaR!E)d$vd0F~%%n1oEE)`9D5PxBKRr4(7iA@~bzWD&vw(0P|l0 p`LYQ=r^=aRg88q2{E#K4Ue_{8z +{ +#include "g_items.h" item_e +} + +<%s="MISSIONFAILED"> +{ +#include "objectives.h" MissionFailed_e +} + +<%s="OBJECTIVES"> +{ +#include "objectives.h" Objective_e +} + +<%s="ANIM_NAMES"> +{ +#include "anims.h" animNumber_e +} + +<%s="BSTATE_STRINGS"> +{ +#include "bstate.h" bState_e +} + +<%s="STATUSTEXT"> +{ +#include "objectives.h" StatusText_e +} + +<%s="WEAPON_NAMES"> +{ + "drop" +#include "weapons.h" weapon_e +} + +// TREKSPECIFIC: this typeset now MUST be present for BehavEd to present GET-helper combos! +<%s="SET_TYPES"> +{ +#include "q3_interface.h" setType_e +} + +<%i="CHANNELS"> +{ +#include "channels.h" soundChannel_e +} + +<%s="EVENT_NAMES"> +{ +#include "events.h" eventType_e +} + +<%s="MUSIC_STATES"> +{ +#include "dmstates.h" dynamicMusic_e +} + +<%s="LEAN_TYPES"> +{ + "none" + "left" + "right" +} + +<%s="BOOL_TYPES"> +{ + "false" + "true" +} + +<%s="MENUSCREENS"> +{ + "mainMenu" + "ingameMainMenu" + "ingameloadMenu" +} + +<%s="TEAM_NAMES"> +{ +#include "teams.h" team_e +} + +<%i="GET_TYPE"> +{ + FIELD + VAR +} + +<%i="AFFECT_TYPE"> +{ + FLUSH + INSERT +} + +// TREKSPECIFIC: this typeset now MUST be present for BehavEd to present GET-helper combos! +<%i="TAG_TYPE"> +{ + ORIGIN + ANGLES +} + +<%i="CAMERA_COMMANDS"> +{ + ENABLE//## %% # Puts game in camera mode // no more parms + DISABLE//## %% # Takes game out of camera mode //no more parms + ZOOM//## %f="0.0" %d="0" # Normal is 80, 10 is zoomed in, max is 120. Second value is time in ms to take to zoom to the new FOV + MOVE//## %v="0.0 0.0 0.0" %d="0" # Move to a absolute vector origin or TAG("targetname", ORIGIN) over time over number of milliseconds + PAN//## %v="0.0 0.0 0.0" %v="0.0 0.0 0.0" %d="0" # Pan to absolute angle from current angle in dir (no dir will use shortest) over number of milliseconds + ROLL//## %f="0.0" %d="0" # Roll to relative angle offsets of current angle over number of milliseconds + TRACK//## %s="trackName" %f="0.0" %d="0" # Get on track and move at speed, last number is whether or not to lerp to the start pos + FOLLOW//## %s="cameraGroup" %f="0.0" %d="0" # Follow ents with matching cameraGroup at angleSpeed, last number is whether or not to lerp to the start angle + DISTANCE//## %f="0.0" %d="0" # Keep this distance from cameraGroup (if any), last number is whether or not to lerp to the start angle + FADE//## %v="0.0 0.0 0.0" %f="0.0" %v="0.0 0.0 0.0" %f="0.0" %d="0" # Fade from [start Red Green Blue], [Opacity] to [end Red Green Blue], [Opacity] (all fields valid ranges are 0 to 1) over [number of milliseconds] + SHAKE//## %f="0.0" %d="0" # Intensity (0-16) and duration, in milliseconds + PATH //## %s="filename" !!"w:\game\base\scripts\!!#*.rof" # Path to ROF file +} + +// TREKSPECIFIC: this typeset now MUST be present for BehavEd to present GET-helper combos! +<%i="DECLARE_TYPE"> +{ + FLOAT//## %s="" # A number + STRING//## %s="" # A string + VECTOR//## %s="" # A vector +} + +<%s="PLAY_TYPES"> +{ +#include "q3_interface.h" playType_e +} + +// these must be left in this order, since they mirror the order of icons in res/bitmap1.bmp +// +<%i="ICON_OVERRIDES"> +{ + I_BRACE + I_EVENT + I_MACRO + I_SPACE + // + I_SOUND + I_CAMERA + I_ROTATE + I_REMOVE + I_SET + I_MOVE + I_IF + I_LOOP + I_DO + I_WAIT + I_DOWAIT + I_SIGNAL + I_WAITSIGNAL + I_FLUSH + I_WAITCLOCK +} + +<%d="FORCE_LEVELS"> +{ + 0 + 1 + 2 + 3 +} + +<%d="SABER_STYLES"> +{ + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 +} + +<%s="SABER_COLORS"> +{ + "red" + "orange" + "yellow" + "green" + "blue" + "purple" + "random" +} + +//#=== Flow control commands ============================================ + +[I_FLUSH] flush();//# clear all previous script commands on ent +//[I_IF] if ( $test expression =,<,>,! xxx$ ) {} //# if condition true, execute block of commands +[I_IF] if ( $exp1$, $ < > ! = $, $exp2$ ) {} //# if condition true, execute block of commands + +[I_IF] else () {} //# must immediately follow and else, will execute if the if condition is false + +[I_LOOP] loop ( %d=-1 ) {} //# execute block of commands any number of times (-1 = forever) + +affect( %s="DEFAULT", %t="AFFECT_TYPE" ) {}//# switch script affect to ent with specified name, flush old commands or insert the new block of commands into current commands + +run ( %s="DEFAULT" ); //# ent runs specified script + +//#=== Standard commands ================================================ + +[I_WAITCLOCK] wait( %f=1000.0 );//# script will wait specified number of milliseconds +[I_WAITSIGNAL] waitsignal( %s="signalname" );//# wait until a signal() command is given with the name name - only one ent can wait for a particular signal +[I_SIGNAL] signal( %s="signalname" );//# The ent waiting for this signal will continue with it's script + +//action( %s="DEFAULT", %s="DEFAULT" );//# no longer valid, but I'll leave it in for the moment for testing +[I_SOUND] sound( %t="CHANNELS", %s="FILENAME" );//# play sound on specified channel of ent + +[I_MOVE] move ( %v=<0.0 0.0 0.0>, %v=<0.0 0.0 0.0>, %f=1000.0 );//move ent from point to point at speed +//move ( $default$, $default$, %f=1000.0 );//move ent from point to point at speed +//[I_MOVE] move ( $default$, %f=1000.0 );//move ent from point to point at speed +[I_MOVE] move ( $default$, $default$ );//move ent from point to point at speed +[I_ROTATE] rotate( %v=<0.0 0.0 0.0>, %f=1000.0 );//# rotate ent to target angles at speed + +use ( %s="DEFAULT" );//# uses specified ent +use ( $get(STRING,"targetname")$ );//# uses specified ent from a get(STRING) command +kill ( %s="DEFAULT" );//# kills ent with specified name +[I_REMOVE] remove ( %s="DEFAULT" ); //# removes ent with specified name from game + +print( %s="DEFAULT" );//# Prints text to center of screen +rem(%s="comment");//# Just a comment for script, no actual effect in-game + +//#=== Variable Handling =============================================== +declare( %t="DECLARE_TYPE", %s="variablename" ); //# declare a global variable here, limit of 16 per map + +free( %s="variablename" ); //# free a global variable so you can make more + +//get( %t="DECLARE_TYPE", %s="variablename" ); //# OF NO USE BY ITSELF - this will be removed soon, but is still usable inside other commands + +//random( %f=0.0, %f=0.0 );//# use a random float between 2 specified values. OF NO USE BY ITSELF - this will be removed soon, but is still usable inside other commands + +//#=== Set commands ===================================================== + +//# standard strings +[I_SET] set( %t="SET_TYPES", %s="DEFAULT" );//# standard set commands +[I_SET] set( %s="variablename", %s="value" );//# set for variables +//set( %s="variablename", $value$ );//# set a variable to a get +//set( $value$, $value$ );//# set a get to a get + +//#Camera functions +[I_CAMERA] camera( %t="CAMERA_COMMANDS" ); + +//#Task functions + +task( %s="DEFAULT" ) {} +[I_DO] do( %s="DEFAULT" ) +[I_WAIT] wait( %s="DEFAULT" ) //# wait until task "taskname" is complete +//wait( $random( 0, 1 )$ ) //# wait a specified amount of time + +[I_DOWAIT] dowait( %s="DEFAULT" ); //# shorthand form of: do("taskname"); wait("taskname") + +play (%t="PLAY_TYPES", %s="default"); + +//#=== Macros =========================================================== +"standOnly"//# simply stands, ignores enemies & alerts +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_IGNOREALERTS", %r%s="true" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="false" ); +} + +"walkOnly"//# simply walks, ignores enemies & alerts +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_WALKING", %r%s="true" ); + set( %r%s="SET_RUNNING", %r%s="false" ); + set( %r%s="SET_IGNOREALERTS", %r%s="true" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="false" ); +} + +"runOnly"//# simply runs, ignores enemies & alerts +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_WALKING", %r%s="false" ); + set( %r%s="SET_RUNNING", %r%s="true" ); + set( %r%s="SET_IGNOREALERTS", %r%s="true" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="false" ); +} + +"standNoAlerts"//# simply stands, ignores alerts +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_IGNOREALERTS", %r%s="true" ); +} + +"walkNoAlerts"//# simply walks, ignores alerts +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_WALKING", %r%s="true" ); + set( %r%s="SET_RUNNING", %r%s="false" ); + set( %r%s="SET_IGNOREALERTS", %r%s="true" ); +} + +"runNoAlerts"//# simply runs, ignores alerts +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_WALKING", %r%s="false" ); + set( %r%s="SET_RUNNING", %r%s="true" ); + set( %r%s="SET_IGNOREALERTS", %r%s="true" ); +} + +"standNoEnemies"//# simply stands, ignores enemies +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="false" ); +} + +"walkNoEnemies"//# simply walks, ignores enemies +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_WALKING", %r%s="true" ); + set( %r%s="SET_RUNNING", %r%s="false" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="false" ); +} + +"runNoEnemies"//# simply runs, ignores enemies +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_WALKING", %r%s="false" ); + set( %r%s="SET_RUNNING", %r%s="true" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="false" ); +} + +"standGuardNoChase"//# looks for enemies, shoots at but doesn't chase them +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="true" ); + set( %r%s="SET_CHASE_ENEMIES", %r%s="false" ); +} + +"patrolRun"//# patrols in a run, chases enemies +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_WALKING", %r%s="false" ); + set( %r%s="SET_RUNNING", %r%s="true" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="true" ); + set( %r%s="SET_CHASE_ENEMIES", %r%s="true" ); +} + +"patrolNoChase"//# patrols, sticks to patrol route even when shooting enemy +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="true" ); + set( %r%s="SET_CHASE_ENEMIES", %r%s="false" ); +} + +"patrolWalkNoChase"//# patrols in a walk, sticks to walking patrol route even when shooting enemy +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_WALKING", %r%s="true" ); + set( %r%s="SET_RUNNING", %r%s="false" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="true" ); + set( %r%s="SET_CHASE_ENEMIES", %r%s="false" ); +} + +"patrolRunNoChase"//# patrols in a run, sticks to running patrol route even when shooting enemy +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_WALKING", %r%s="false" ); + set( %r%s="SET_RUNNING", %r%s="true" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="true" ); + set( %r%s="SET_CHASE_ENEMIES", %r%s="false" ); +} + +"default"//# resets to default behavior +{ + set( %r%s="SET_BEHAVIOR_STATE", %r%s="BS_DEFAULT" ); + set( %r%s="SET_CHASE_ENEMIES", %r%s="true" ); + set( %r%s="SET_LOOK_FOR_ENEMIES", %r%s="true" ); + set( %r%s="SET_IGNOREALERTS", %r%s="false" ); + set( %r%s="SET_WALKING", %r%s="false" ); + set( %r%s="SET_RUNNING", %r%s="false" ); +} + +// Old 1st parm locked set commands, not needed any more: +/* +//# vectors +set( %r%s="SET_ORIGIN", %v=<0.0 0.0 0.0>); +set( %r%s="SET_ANGLES", %v=<0.0 0.0 0.0>); +set( %r%s="SET_COPY_ORIGIN", %v=<0.0 0.0 0.0>); + +//# floats +set( %r%s="SET_VELOCITY",%f=0.0); +set( %r%s="SET_XVELOCITY",%f=0.0); +set( %r%s="SET_YVELOCITY",%f=0.0); +set( %r%s="SET_ZVELOCITY",%f=0.0); +set( %r%s="SET_AVELOCITY",%f=0.0); +set( %r%s="SET_DPITCH",%f=0.0); +set( %r%s="SET_DYAW",%f=0.0); +set( %r%s="SET_TIMESCALE",%f=0.0); +set( %r%s="SET_VISRANGE",%f=0.0); +set( %r%s="SET_EARSHOT",%f=0.0); +set( %r%s="SET_VIGILANCE",%f=0.0); + +//# ints +set( %r%s="SET_ANIM_HOLDTIME_LOWER",%d=0); +set( %r%s="SET_ANIM_HOLDTIME_UPPER",%d=0); +set( %r%s="SET_HEALTH",%d=0); +set( %r%s="SET_WALKSPEED",%d=0); +set( %r%s="SET_RUNSPEED",%d=0); +set( %r%s="SET_YAWSPEED",%d=0); +set( %r%s="SET_FRICTION",%d=0); +set( %r%s="SET_SHOOTDIST",%d=0); +set( %r%s="SET_HFOV",%d=0); +set( %r%s="SET_VFOV",%d=0); +set( %r%s="SET_DELAYSCRIPTTIME",%d=0); +set( %r%s="SET_FORWARDMOVE",%d=0); +set( %r%s="SET_RIGHTMOVE",%d=0); +set( %r%s="SET_AGGRESSION",%d=0);//# 1 - 5 +set( %r%s="SET_AIM",%d=0);//# 1 - 5 + +//# booleans and simple calls +set( %r%s="SET_SCRIPTED",%t="BOOL_TYPES"); +set( %r%s="SET_HIDING",%t="BOOL_TYPES"); +set( %r%s="SET_IGNOREPAIN",%t="BOOL_TYPES"); +set( %r%s="SET_IGNOREENEMIES",%t="BOOL_TYPES"); +set( %r%s="SET_STRAIGHTTOGOAL",%t="BOOL_TYPES"); +set( %r%s="SET_DONTSHOOT",%t="BOOL_TYPES"); +set( %r%s="SET_NOTARGET",%t="BOOL_TYPES"); +set( %r%s="SET_CROUCHED",%t="BOOL_TYPES"); +set( %r%s="SET_WALKING",%t="BOOL_TYPES"); +set( %r%s="SET_CAREFUL",%t="BOOL_TYPES"); +set( %r%s="SET_UNDYING",%t="BOOL_TYPES"); +set( %r%s="SET_NOAVOID",%t="BOOL_TYPES"); +set( %r%s="SET_BEAM",%t="BOOL_TYPES"); +set( %r%s="SET_CREATEFORMATION",%t="BOOL_TYPES"); + +//# Behavior state settings +set( %r%s="BSTATE", %t="BSTATE_STRINGS" ); +set( %r%s="defaultBState", %t="BSTATE_STRINGS" ); +set( %r%s="tempBehavior", %t="BSTATE_STRINGS" ); + +//# Animation settings +set( %r%s="anim_upper", %t="ANIM_NAMES" ); +set( %r%s="anim_lower", %t="ANIM_NAMES" ); +set( %r%s="anim_both", %t="ANIM_NAMES" ); + +//#Enemy team table +set( %r%s="playerTeam", %t="TEAM_NAMES" ); +set( %r%s="enemyTeam", %t="TEAM_NAMES" ); + +//#Weapon table +set( %r%s="weapon", %t="WEAPON_NAMES" ); + +//#Lean side table +set( %r%s="LEAN", %t="LEAN_TYPES" );//# left, right, or none + +//#Event/effect table +set( %r%s="event", %s="default" );//# not implemented + +//Old hardcoded camera commands +camera( %r%s="ORIGIN", %v=<0.0 0.0 0.0> ); +camera( %r%s="ANGLES", %v=<0.0 0.0 0.0> ); +camera( %r%s="FOV", %f=0.0 ); +camera( %r%s="MOVE", %v=<0.0 0.0 0.0>, %f=0.0 ); +camera( %r%s="PAN", %v=<0.0 0.0 0.0>, %f=0.0 ); +camera( %r%s="ZOOM", %f=0.0, %f=0.0 ); +camera( %r%s="FADE", %v=<0.0 0.0 0.0>, %f=0.0, %v=<0.0 0.0 0.0>, %f=0.0, %f=0.0 ); +camera( %r%s="ENABLE" ); +camera( %r%s="DISABLE" ); +*/ diff --git a/bin/StarWars.qe4 b/bin/StarWars.qe4 new file mode 100644 index 0000000..a08b772 --- /dev/null +++ b/bin/StarWars.qe4 @@ -0,0 +1,34 @@ +{ +"bsp [LOCAL] Entities" "s:\bin\sof2map -bsp -onlyents $" +"bsp [LOCAL] Relight(1/2)" "s:\bin\sof2map -bsp -onlyents $ && s:\bin\sof2map -light -extra -samplesize 32 $" +"bsp [LOCAL] No Vis" "w:\bin\bats\novisnolight.bat $" +"bsp [LOCAL] Fast Vis No Light" "w:\bin\bats\fastvisnolight.bat $" +"bsp [LOCAL] Fast Vis" "w:\bin\bats\fastvis.bat $" +"bsp [LOCAL] Full Vis No Light" "w:\bin\bats\fullvisnolight.bat $" +"bsp [LOCAL] Full Vis" "s:\bin\sof2map -all $" +"bsp [LOCAL] Full Vis(1/2 LMs)" "s:\bin\sof2map -all -samplesize 32 $" +"bsp [LOCAL] Info" "s:\bin\sof2map -info $" +"bsp Relight (extra wide)" "s:\bin\sof2map -bsp -onlyents $ && !sof2map -light -extrawide $" +"bsp Relight (extra)" "s:\bin\sof2map -bsp -onlyents $ && !sof2map -light -extra $" +"bsp Relight (1/2 LM)" "s:\bin\sof2map -bsp -onlyents $ && !sof2map -light -extra -samplesize 32 $" +"bsp Relight" "s:\bin\sof2map -bsp -onlyents $ && !sof2map -light $" +"bsp Novis (nolight)" "!sof2map -bsp $ -class 4" +"bsp FastVis (nolight)" "!sof2map -bsp $ -class 4 && !sof2map -vis -fast $ -class 3" +"bsp FastVis(1/2)" "!sof2map -bsp -samplesize 32 $ -class 4 && !sof2map -vis -fast $ -class 3 && !sof2map -light -extra -samplesize 32 $" +"bsp FullVis (lowmem)" "!sof2map -bsp $ -class 4 && !sof2map -vis -nopassage $ -class 2 && !sof2map -light $" +"bsp FullVis (lesmem)" "!sof2map -bsp $ -class 4 && !sof2map -vis -passageOnly $ -class 2 && !sof2map -light $" +"bsp FullVis (nolight)" "!sof2map -bsp $ -class 4 && !sof2map -vis $ -class 2" +"bsp FullVis (extra)" "!sof2map -bsp $ -class 4 && !sof2map -vis $ -class 2 && !sof2map -light -extra $" +"bsp FullVis (1/2 LMs)" "!sof2map -bsp -samplesize 32 $ -class 4 && !sof2map -vis $ -class 2 && !sof2map -light -extra -samplesize 32 $" +"bsp FullVis" "!sof2map -bsp $ -class 4 && !sof2map -vis $ -class 2 && !sof2map -light $" +"bsp2 FullVis (1/2 LMs)" "!q3map2\q3map2 -custinfoparms -game jk2 -fs_basepath w:\game -samplesize 32 $ -class 4 && !q3map2\q3map2 -game jk2 +-fs_basepath w:\game -vis $ -class 2 && !q3map2\q3map2 -game jk2 -light -fs_basepath w:\game -extra -samplesize 32 $" +"bsp2 Novis (nolight)" "!q3map2\q3map2 -custinfoparms -game jk2 -fs_basepath w:\game $ -class 4" +"autosave" "c:\ravenlocal\jk2.5\autosave.map" +"rshcmd" "ts w:\bin\" +"mapspath" "w:\game\base\maps\" +"entitypath" "w:\bin\gamesource\*.cpp" +"texturepath" "w:\game\base\textures" +"remotebasepath" "w:\game\base" +"basepath" "w:\game\base" +} diff --git a/bin/StarWarsMP.qe4 b/bin/StarWarsMP.qe4 new file mode 100644 index 0000000..4cae305 --- /dev/null +++ b/bin/StarWarsMP.qe4 @@ -0,0 +1,31 @@ +{ +"bsp [LOCAL] Entities" "s:\bin\sof2map -bsp -onlyents $" +"bsp [LOCAL] Relight" "s:\bin\sof2map -bsp -onlyents $ && s:\bin\sof2map -light $" +"bsp [LOCAL] No Vis" "w:\bin\bats\novisnolight.bat $" +"bsp [LOCAL] Fast Vis No Light" "w:\bin\bats\fastvisnolight.bat $" +"bsp [LOCAL] Fast Vis" "w:\bin\bats\fastvis.bat $" +"bsp [LOCAL] Full Vis No Light" "w:\bin\bats\fullvisnolight.bat $" +"bsp [LOCAL] Full Vis" "s:\bin\sof2map -all $" +"bsp [LOCAL] Full Vis(1/2 LMs)" "s:\bin\sof2map -all -samplesize 32 $" +"bsp [LOCAL] Info" "s:\bin\sof2map -info $" +"bsp Relight (extra wide)" "s:\bin\sof2map -bsp -onlyents $ && !sof2map -light -extrawide $" +"bsp Relight (extra)" "s:\bin\sof2map -bsp -onlyents $ && !sof2map -light -extra $" +"bsp Relight (1/2 LM)" "s:\bin\sof2map -bsp -onlyents $ && !sof2map -light -extra -samplesize 32 $" +"bsp Relight" "s:\bin\sof2map -bsp -onlyents $ && !sof2map -light $" +"bsp Novis (nolight)" "!sof2map -bsp $ -class 4" +"bsp FastVis (nolight)" "!sof2map -bsp $ -class 4 && !sof2map -vis -fast $ -class 3" +"bsp FastVis(1/2)" "!sof2map -bsp -samplesize 32 $ -class 4 && !sof2map -vis -fast $ -class 3 && !sof2map -light -extra -samplesize 32 $" +"bsp FullVis (lowmem)" "!sof2map -bsp $ -class 4 && !sof2map -vis -nopassage $ -class 2 && !sof2map -light $" +"bsp FullVis (lesmem)" "!sof2map -bsp $ -class 4 && !sof2map -vis -passageOnly $ -class 2 && !sof2map -light $" +"bsp FullVis (nolight)" "!sof2map -bsp $ -class 4 && !sof2map -vis $ -class 2" +"bsp FullVis (extra)" "!sof2map -bsp $ -class 4 && !sof2map -vis $ -class 2 && !sof2map -light -extra $" +"bsp FullVis (1/2 LMs)" "!sof2map -bsp -samplesize 32 $ -class 4 && !sof2map -vis $ -class 2 && !sof2map -light -extra -samplesize 32 $" +"bsp FullVis" "!sof2map -bsp $ -class 4 && !sof2map -vis $ -class 2 && !sof2map -light $" +"autosave" "c:\ravenlocal\jk2.5\autosave.map" +"rshcmd" "ts s:\bin\" +"mapspath" "w:\game\base\maps\" +"entitypath" "w:\bin\mpsource\*.c" +"texturepath" "w:\game\base\textures" +"remotebasepath" "w:\game\base" +"basepath" "w:\game\base" +} diff --git a/bin/stringed.cfg b/bin/stringed.cfg new file mode 100644 index 0000000..9d667e2 --- /dev/null +++ b/bin/stringed.cfg @@ -0,0 +1,25 @@ +// Use lower case for most stuff except where I've done otherwise -Ste + +#SETTINGS +dir_inhouse "w:\bin\strings" +dir_ingame "w:\game\base\strings" +ss_ini "\\Ravendata1\VSS\central_assets\SRCSAFE.INI" +ss_prj "$/jedi/bin/Strings/" +contact_info "Any questions, email me at "scork@ravensoft.com"" + +#LANGUAGES +english +german +french +spanish + +#FLAGS +caption +typematic +centered +flag4 +flag5 +flag6 +flag7 +flag_last + diff --git a/bin/vssver.scc b/bin/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..e539eefe0d36370ecd64d17a63f98fb27522e518 GIT binary patch literal 96 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiWgDmQ)K$ix5!y#fpj9w&u4?`@v4i32E7F37+j qeyKY*&#HG5NJ#~dfApTAqgs8(CPpCt4v=5bB5$(oVACd6ARhqvXC12m literal 0 HcmV?d00001 diff --git a/code/0_compiled_first/0_SH_Leak.cpp b/code/0_compiled_first/0_SH_Leak.cpp new file mode 100644 index 0000000..a924712 --- /dev/null +++ b/code/0_compiled_first/0_SH_Leak.cpp @@ -0,0 +1,774 @@ +// leave this as first line for PCH reasons... +// +#pragma warning( disable : 4786) +#pragma warning( disable : 4100) +#pragma warning( disable : 4663) +#include + +#include "..\smartheap\smrtheap.h" +#include "../game/q_shared.h" +#include "..\qcommon\qcommon.h" + +#include +#include + +using namespace std; + +#if MEM_DEBUG +#include "..\smartheap\heapagnt.h" + +static const int maxStack=2048; +static int TotalMem; +static int TotalBlocks; +static int nStack; +static char StackNames[maxStack][256]; +static int StackSize[maxStack]; +static int StackCount[maxStack]; +static int StackCache[48]; +static int StackCacheAt=0; +static int CheckpointSize[3000]; +static int CheckpointCount[3000]; + +//#define _FASTRPT_ + + +#ifdef _FASTRPT_ +class CMyStrComparator +{ +public: + bool operator()(const char *s1, const char *s2) const { return(strcmp(s1, s2) < 0); } +}; + +hmap Lookup; +#endif + +cvar_t *mem_leakfile; +cvar_t *mem_leakreport; + +MEM_BOOL MEM_CALLBACK MyMemReporter2(MEM_ERROR_INFO *info) +{ + static char buffer[10000]; + if (!info->objectCreationInfo) + return 1; + info=info->objectCreationInfo; + int idx=info->checkpoint; + if (idx<0||idx>=1000) + { + idx=0; + } + CheckpointCount[idx]++; + CheckpointSize[idx]+=info->argSize; + dbgMemFormatCall(info,buffer,9999); + if (strstr(buffer,"ntdll")) + return 1; + if (strstr(buffer,"CLBCATQ")) + return 1; + int i; + TotalBlocks++; + if (TotalBlocks%1000==0) + { + char mess[1000]; + sprintf(mess,"%d blocks processed\n",TotalBlocks); + OutputDebugString(mess); + } + for (i=strlen(buffer);i>0;i--) + { + if (buffer[i]=='\n') + break; + } + if (!i) + return 1; + buffer[i]=0; + char *buf=buffer; + while (*buf) + { + if (*buf=='\n') + { + buf++; + break; + } + buf++; + } + char *start=0; + char *altName=0; + while (*buf) + { + while (*buf==' ') + buf++; + start=buf; + while (*buf!=0&&*buf!='\n') + buf++; + if (*start) + { + if (*buf) + { + *buf=0; + buf++; + } + if (strlen(start)>255) + start[255]=0; + if (strstr(start,"std::")) + { + altName="std::??"; +// start=0; + continue; + } + if (strstr(start,"Malloc")) + { + altName="Malloc??"; + start=0; + continue; + } + if (strstr(start,"G_Alloc")) + { + altName="G_Alloc"; + start=0; + continue; + } + if (strstr(start,"Hunk_Alloc")) + { + altName="Hunk_Alloc"; + start=0; + continue; + } + if (strstr(start,"FS_LoadFile")) + { + altName="FS_LoadFile"; + start=0; + continue; + } + if (strstr(start,"CopyString")) + { + altName="CopyString"; + start=0; + continue; + } + break; + } + } + if (!start||!*start) + { + start=altName; + if (!start||!*start) + { + start="UNKNOWN"; + } + } +#ifdef _FASTRPT_ + hmap::iterator f=Lookup.find(start); + if(f==Lookup.end()) + { + strcpy(StackNames[nStack++],start); + Lookup[(const char *)&StackNames[nStack-1]]=nStack-1; + StackSize[nStack-1]=info->argSize; + StackCount[nStack-1]=1; + } + else + { + StackSize[(*f).second]+=info->argSize; + StackCount[(*f).second]++; + } +#else + for (i=0;i<48;i++) + { + if (StackCache[i]<0||StackCache[i]>=nStack) + continue; + if (!strcmpi(start,StackNames[StackCache[i]])) + break; + } + if (i<48) + { + StackSize[StackCache[i]]+=info->argSize; + StackCount[StackCache[i]]++; + } + else + { + for (i=0;iargSize; + StackCount[i]++; + StackCache[StackCacheAt]=i; + StackCacheAt++; + if (StackCacheAt>=48) + StackCacheAt=0; + } + else if (iargSize; + StackCount[i]=1; + nStack++; + } + else if (nStackargSize; + StackCount[maxStack-1]=1; + } + else + { + StackSize[maxStack-1]+=info->argSize; + StackCount[maxStack-1]++; + } + } +#endif + TotalMem+=info->argSize; + return 1; +} + +MEM_BOOL MEM_CALLBACK MyMemReporter3(MEM_ERROR_INFO *info) +{ + static char buffer[10000]; + if (!info->objectCreationInfo) + return 1; + info=info->objectCreationInfo; + int idx=info->checkpoint; + if (idx<0||idx>=3000) + { + idx=0; + } + CheckpointCount[idx]++; + CheckpointSize[idx]+=info->argSize; + dbgMemFormatCall(info,buffer,9999); + int i; + TotalBlocks++; +// if (TotalBlocks%1000==0) +// { +// char mess[1000]; +// sprintf(mess,"%d blocks processed\n",TotalBlocks); +// OutputDebugString(mess); +// } + for (i=strlen(buffer);i>0;i--) + { + if (buffer[i]=='\n') + break; + } + if (!i) + return 1; + buffer[i]=0; + char *buf=buffer; + while (*buf) + { + if (*buf=='\n') + { + buf++; + break; + } + buf++; + } + char *start=0; + char *altName=0; + while (*buf) + { + while (*buf==' ') + buf++; + start=buf; + while (*buf!=0&&*buf!='\n') + buf++; + if (*start) + { + if (*buf) + { + *buf=0; + buf++; + } + if (strlen(start)>255) + start[255]=0; + if (strstr(start,"SV_AreaEntities")) + { + altName="SV_AreaEntities??"; + start=0; + continue; + } + if (strstr(start,"SV_Trace")) + { + altName="SV_Trace??"; + start=0; + continue; + } + if (strstr(start,"SV_PointContents")) + { + altName="SV_PointContents??"; + start=0; + continue; + } + if (strstr(start,"CG_Trace")) + { + altName="??"; + start=0; + continue; + } + if (strstr(start,"CG_PointContents")) + { + altName="??"; + start=0; + continue; + } +/* + if (strstr(start,"")) + { + altName="??"; + start=0; + continue; + } + if (strstr(start,"")) + { + altName="??"; + start=0; + continue; + } +*/ + break; + } + } + if (!start||!*start) + { + start=altName; + if (!start||!*start) + { + start="UNKNOWN"; + } + } +#ifdef _FASTRPT_ + hmap::iterator f=Lookup.find(start); + if(f==Lookup.end()) + { + strcpy(StackNames[nStack++],start); + Lookup[(const char *)&StackNames[nStack-1]]=nStack-1; + StackSize[nStack-1]=info->argSize; + StackCount[nStack-1]=1; + } + else + { + StackSize[(*f).second]+=info->argSize; + StackCount[(*f).second]++; + } +#else + for (i=0;i<48;i++) + { + if (StackCache[i]<0||StackCache[i]>=nStack) + continue; + if (!strcmpi(start,StackNames[StackCache[i]])) + break; + } + if (i<48) + { + StackSize[StackCache[i]]+=info->argSize; + StackCount[StackCache[i]]++; + } + else + { + for (i=0;iargSize; + StackCount[i]++; + StackCache[StackCacheAt]=i; + StackCacheAt++; + if (StackCacheAt>=48) + StackCacheAt=0; + } + else if (iargSize; + StackCount[i]=1; + nStack++; + } + else if (nStackargSize; + StackCount[maxStack-1]=1; + } + else + { + StackSize[maxStack-1]+=info->argSize; + StackCount[maxStack-1]++; + } + } +#endif + TotalMem+=info->argSize; + return 1; +} + +void SH_Checking_f(void); +#endif + +class Leakage +{ + MEM_POOL MyPool; + +public: + Leakage() + { + MyPool = MemInitDefaultPool(); +// MemPoolSetSmallBlockSize(MyPool, 16); + MemPoolSetSmallBlockAllocator(MyPool,MEM_SMALL_BLOCK_SH3); +#if MEM_DEBUG + dbgMemSetGuardSize(2); + EnableChecking(100000); +#endif + } + + void LeakReport(void) + { +#if MEM_DEBUG + + // This just makes sure we have map nodes available without allocation + // during the heap walk (which could be bad). + int i; +#ifdef _FASTRPT_ + hlist makeSureWeHaveNodes; + for(i=0;i<5000;i++) + { + makeSureWeHaveNodes.push_back(0); + } + makeSureWeHaveNodes.clear(); + Lookup.clear(); +#endif + char mess[1000]; + int blocks=dbgMemTotalCount(); + int mem=dbgMemTotalSize()/1024; + sprintf(mess,"Final Memory Summary %d blocks %d K\n",blocks,mem); + OutputDebugString(mess); + + for (i=0;i<3000;i++) + { + CheckpointSize[i]=0; + CheckpointCount[i]=0; + } + + TotalMem=0; + TotalBlocks=0; + nStack=0; + MemSetErrorHandler(MyMemReporter2); + dbgMemReportLeakage(NULL,1,1000); + MemSetErrorHandler(MemDefaultErrorHandler); + multimap > sortit; + multimap >::iterator j; + if (TotalBlocks) + { + // Sort by size. + Sleep(100); + OutputDebugString("**************************************\n"); + OutputDebugString("**********Memory Leak Report**********\n"); + OutputDebugString("*************** By Size **************\n"); + OutputDebugString("**************************************\n"); + sprintf(mess,"Actual leakage %d blocks %d K\n",TotalBlocks,TotalMem/1024); + OutputDebugString(mess); + sortit.clear(); + for (i=0;i >(-StackSize[i],pair(StackCount[i],StackNames[i]))); + Sleep(5); + for (j=sortit.begin();j!=sortit.end();j++) + { + sprintf(mess,"%5d KB %6d cnt %s\n",-(*j).first/1024,(*j).second.first,(*j).second.second); + // if (!(-(*j).first/1024)) + // break; + Sleep(5); + OutputDebugString(mess); + } + + // Sort by count. + Sleep(100); + OutputDebugString("**************************************\n"); + OutputDebugString("**********Memory Leak Report**********\n"); + OutputDebugString("************** By Count **************\n"); + OutputDebugString("**************************************\n"); + sprintf(mess,"Actual leakage %d blocks %d K\n",TotalBlocks,TotalMem/1024); + OutputDebugString(mess); + sortit.clear(); + for (i=0;i >(-StackCount[i],pair(StackSize[i],StackNames[i]))); + Sleep(5); + for (j=sortit.begin();j!=sortit.end();j++) + { + sprintf(mess,"%5d KB %6d cnt %s\n",(*j).second.first/1024,-(*j).first,(*j).second.second); + // if (!(-(*j).first/1024)) + // break; + Sleep(5); + OutputDebugString(mess); + } + } + else + { + OutputDebugString("No Memory Leaks\n"); + } + + TotalMem=0; + TotalBlocks=0; + nStack=0; + MemSetErrorHandler(MyMemReporter3); + dbgMemReportLeakage(NULL,2001,2001); + MemSetErrorHandler(MemDefaultErrorHandler); + if (TotalBlocks) + { + // Sort by count. + Sleep(100); + OutputDebugString("**************************************\n"); + OutputDebugString("SV_PointContents "); + sprintf(mess,"%d Calls.\n",TotalBlocks); + OutputDebugString(mess); + OutputDebugString("**************************************\n"); + sortit.clear(); + for (i=0;i >(-StackCount[i],pair(StackSize[i],StackNames[i]))); + Sleep(5); + for (j=sortit.begin();j!=sortit.end();j++) + { + sprintf(mess,"%7d cnt %s\n",-(*j).first,(*j).second.second); + Sleep(5); + OutputDebugString(mess); + } + } + TotalMem=0; + TotalBlocks=0; + nStack=0; + MemSetErrorHandler(MyMemReporter3); + dbgMemReportLeakage(NULL,2002,2002); + MemSetErrorHandler(MemDefaultErrorHandler); + if (TotalBlocks) + { + // Sort by count. + Sleep(100); + OutputDebugString("**************************************\n"); + OutputDebugString("SV_Trace "); + sprintf(mess,"%d Calls.\n",TotalBlocks); + OutputDebugString(mess); + OutputDebugString("**************************************\n"); + sortit.clear(); + for (i=0;i >(-StackCount[i],pair(StackSize[i],StackNames[i]))); + Sleep(5); + for (j=sortit.begin();j!=sortit.end();j++) + { + sprintf(mess,"%7d cnt %s\n",-(*j).first,(*j).second.second); + Sleep(5); + OutputDebugString(mess); + } + } + TotalMem=0; + TotalBlocks=0; + nStack=0; + MemSetErrorHandler(MyMemReporter3); + dbgMemReportLeakage(NULL,2003,2003); + MemSetErrorHandler(MemDefaultErrorHandler); + if (TotalBlocks) + { + // Sort by count. + Sleep(100); + OutputDebugString("**************************************\n"); + OutputDebugString("SV_AreaEntities "); + sprintf(mess,"%d Calls.\n",TotalBlocks); + OutputDebugString(mess); + OutputDebugString("**************************************\n"); + sortit.clear(); + for (i=0;i >(-StackCount[i],pair(StackSize[i],StackNames[i]))); + Sleep(5); + for (j=sortit.begin();j!=sortit.end();j++) + { + sprintf(mess,"%7d cnt %s\n",-(*j).first,(*j).second.second); + Sleep(5); + OutputDebugString(mess); + } + } + TotalMem=0; + TotalBlocks=0; + nStack=0; + MemSetErrorHandler(MyMemReporter3); + dbgMemReportLeakage(NULL,2004,2004); + MemSetErrorHandler(MemDefaultErrorHandler); + if (TotalBlocks) + { + // Sort by count. + Sleep(100); + OutputDebugString("**************************************\n"); + OutputDebugString("CG_Trace "); + sprintf(mess,"%d Calls.\n",TotalBlocks); + OutputDebugString(mess); + OutputDebugString("**************************************\n"); + sortit.clear(); + for (i=0;i >(-StackCount[i],pair(StackSize[i],StackNames[i]))); + Sleep(5); + for (j=sortit.begin();j!=sortit.end();j++) + { + sprintf(mess,"%7d cnt %s\n",-(*j).first,(*j).second.second); + Sleep(5); + OutputDebugString(mess); + } + } + TotalMem=0; + TotalBlocks=0; + nStack=0; + MemSetErrorHandler(MyMemReporter3); + dbgMemReportLeakage(NULL,2005,2005); + MemSetErrorHandler(MemDefaultErrorHandler); + if (TotalBlocks) + { + // Sort by count. + Sleep(100); + OutputDebugString("**************************************\n"); + OutputDebugString("CG_PointContents "); + sprintf(mess,"%d Calls.\n",TotalBlocks); + OutputDebugString(mess); + OutputDebugString("**************************************\n"); + sortit.clear(); + for (i=0;i >(-StackCount[i],pair(StackSize[i],StackNames[i]))); + Sleep(5); + for (j=sortit.begin();j!=sortit.end();j++) + { + sprintf(mess,"%7d cnt %s\n",-(*j).first,(*j).second.second); + Sleep(5); + OutputDebugString(mess); + } + } +#if 0 //sw doesn't have the tag stuff + // Sort by size. + Sleep(5); + OutputDebugString("***************************************\n"); + OutputDebugString("By Tag, sort: size ********************\n"); + OutputDebugString("size(K) count name \n"); + OutputDebugString("-----------------------\n"); + Sleep(5); + multimap sorted; + for (i=0;i<1000;i++) + { + if (CheckpointCount[i]) + { + sorted.insert(pair(-CheckpointSize[i],i)); + } + } + multimap::iterator k; + for (k=sorted.begin();k!=sorted.end();k++) + { + sprintf(mess,"%8d %8d %s\n",CheckpointSize[(*k).second]/1024,CheckpointCount[(*k).second],(*k).second>=2?tagDefs[(*k).second-2]:"unknown"); + Sleep(5); + OutputDebugString(mess); + } + + // Sort by count. + Sleep(5); + OutputDebugString("By Tag, sort: count *******************\n"); + OutputDebugString("size(K) count name \n"); + OutputDebugString("-----------------------\n"); + Sleep(5); + sorted.clear(); + for (i=0;i<1000;i++) + { + if (CheckpointCount[i]) + { + sorted.insert(pair(-CheckpointCount[i],i)); + } + } + for (k=sorted.begin();k!=sorted.end();k++) + { + sprintf(mess,"%8d %8d %s\n",CheckpointSize[(*k).second]/1024,CheckpointCount[(*k).second],(*k).second>=2?tagDefs[(*k).second-2]:"unknown"); + Sleep(5); + OutputDebugString(mess); + } +#endif +#endif + } + + ~Leakage() + { +#if MEM_DEBUG + if (mem_leakfile && mem_leakfile->integer) + { + dbgMemSetDefaultErrorOutput(DBGMEM_OUTPUT_FILE,"leakage.out"); + dbgMemReportLeakage(NULL,1,1); + dbgMemSetDefaultErrorOutput(DBGMEM_OUTPUT_PROMPT,NULL); + } + if (mem_leakreport && mem_leakreport->integer) + { + LeakReport(); + } +#endif + } +#if MEM_DEBUG + + void EnableChecking(int x) + { + if (x) + { + dbgMemSetSafetyLevel(MEM_SAFETY_DEBUG); + dbgMemPoolSetCheckFrequency(MyPool, x); + dbgMemSetCheckFrequency(x); + dbgMemDeferFreeing(TRUE); + dbgMemSetDeferQueueLen(50000); + } + else + { + dbgMemSetSafetyLevel(MEM_SAFETY_SOME); + dbgMemDeferFreeing(FALSE); + } + + } +#endif + +}; + +static Leakage TheLeakage; + +#if MEM_DEBUG + +void MEM_Checking_f(void) +{ + if (Cmd_Argc() != 2) + { + Com_Printf ("mem_checking \n"); + return; + } + + if (atol(Cmd_Argv(1)) > 0 && atol(Cmd_Argv(1)) < 100) + { + Com_Printf ("mem_checking frequency is too low ( < 100 )\n"); + return; + } + + TheLeakage.EnableChecking(atol(Cmd_Argv(1))); +} + +void MEM_Report_f(void) +{ + if (0) + { + dbgMemSetDefaultErrorOutput(DBGMEM_OUTPUT_FILE,"leakage.out"); + dbgMemReportLeakage(NULL,1,1); + dbgMemSetDefaultErrorOutput(DBGMEM_OUTPUT_PROMPT,NULL); + } + TheLeakage.LeakReport(); +} + +/* +void myexit(void) +{ + TheLeakage.LeakReport(); +} +*/ +void SH_Register(void) +{ + Cmd_AddCommand ("mem_checking", MEM_Checking_f); + Cmd_AddCommand ("mem_report", MEM_Report_f); + + mem_leakfile = Cvar_Get( "mem_leakfile", "0", 0 ); + mem_leakreport = Cvar_Get( "mem_leakreport", "1", 0 ); +// atexit(myexit); +} + +#endif diff --git a/code/ALut.lib b/code/ALut.lib new file mode 100644 index 0000000000000000000000000000000000000000..67cde9f676b7dbead6771de243a29cdf1b73f6be GIT binary patch literal 4702 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 V0|5*KFc82%00RLG{6B?({{dt;YKZ^< literal 0 HcmV?d00001 diff --git a/code/IFC22.dll b/code/IFC22.dll new file mode 100644 index 0000000000000000000000000000000000000000..44331c8fe623321de7131d4300f53790a8dbae8e GIT binary patch literal 200704 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 z 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ð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
!L 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%0DFTYu4;{ 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 z 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

mT@_7~a9)MmQvFGKm=QG6)*T4?*f-MDXiQ}q& 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

m*Gq^aBh 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
9^}L+9%6VbTlQWS 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 xxtebW{Bs^*9<5iq@$(0^ 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
6>R-^1ozN~jI;G*tPqiwK0cf0xj7V{Nd; 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

G|?iuM*c>|}< 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

T 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

B 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

6S_^tICFFv>&UR3 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�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 H(D!V96FPqgUz4<-5OiT|I$W9yo-qF(gbj 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

c%_Hs#4oo9BFlgWr9@ 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%0DnN%4Cez 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 z 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 mAn=IN_E~z8s-?19GF}U^Dd>k zVLHt7cp0bjJP(sJn(CQ7<97f+R?lTFWBCQ#wy_n*lKnL)s9&g_i&gb3MmBOBHvBD+ zs&Q>99`w{JSz*yg!=bcu98pn8sje|KA|BAxp+rR`U4|xKJU7umotmPlNsR0(9)_t< zar0aB7R0kP^`Gec|C{NkFI`P>try91tY(d()vV>bnzdX#4ih)kfI>%AFK$6pYahQB z<1W>lYc$7vL}eGFObm;?G`>j(TtZZL3CRg1SHu0dd?c`wHJD$4IUWg%3jh}XiyECi z!%=DWA)+ML<({5e?tY?nmdn%6jiAXwFLM=o1paqwc*QX+nx2m9bQJnZRp>V=*+KuB zVkO(|LSCVvP8v+xgizubRq6%xNh>sI6p^S;Dtm@*TyL3>FE(jvkTKCM+0?LDrKv|z zp14NV)QFg`sm(;CYxCgSD0XA`Ydg_)j6LsrtZ(Z!BvPxwhfq4V1HRr#rivoFA5(QQ zbhOJKQ7-@ZQdL?@HSuypsaZ-}!(uIsV$uzhiPYKuT@?_UPo`_IN4f^%nvto%Xq%7%ySX#i2Q)ZIGZ%FY#@8kv zb+@Zoy{nqFqA=Q5vp?hMT2!+Qs+#$fY*e!ZCHv*C3V6q4i5C1X+Gm4~Wl+mYAVtJ% zP5LX5P|faNb~E2|%G9LoM4F_BzLPZRb_Q%W^qrtlNZNT5QUAYJFnVPal{_7le+MI4 zOa{h#Dd+dnM1zQAV*D12N|f_c997N_5M?9dng5ZifUjucenjnFiKZ*(Xg$73~XU>rk4FaI%7Gw7|B+5Gx^Ix5}mXTFuz>XfgE%;NNw}g>{ZsdARox#Yo`1Um+)ApRlG<75+ z)8ZRO3yHizQv-~g=0?6yQ$NSzqBSW*puF*CQdrE@)V~qcU927E0>h}E`<^}!U7Otp z($+Vs0d!OqZ51J7qUG}B7K~GIKT3sIFVn2E6)QX{izHt>Nu!&rEawyTd*)<2ac6BL`uw@#B@;+cnr)8_X`F zusDptNf|-oKo?+Kqu(1lvWyF;l?9{Hoctz$j?3N+06F4d}57P!3zMOID zR#bz%N>q0ZkY@6V%wW`e4w$NnVbMc_Rdo%A(m*yS25s=!=kuBPEl7JOFM zVC!hQ>jjO>3~I0*3d~i}Z_r?^-Y(6oC$JmwC%c^Ot}bU+`!NTXTX}da7+uc0s>|5~ zNOCbQx9SMpzTBeyx5|W4N^gI;^=nNnA}UR7)a6!#CXHc0J?1>1NkbX4-I!COsY#5< zms{C%xwTZ21X^CZhV=jLa_dgjfa$1e*m^?d1r&MR_!SLCvJh|VC{$ii(JZ7T0qe2q zg`CyYztduDJWIh}tl`f?lrFQf$;B9!Hfi`E!FR5`)D&9pk!9N8D${8DGWGatVxM!F z8f)bxH0{|Vqp-+Cmzc75cxEz5Jxxo!n@E_hCekG0-!$?_W2-5>&`at0;#Hk~J*B(5 zuy&qD_x=4By4H)X%Z{mZGcTS3G^+b_R0Yr*QJ%Ts#aM-qy+X_W9OG#_*|GrmqHz^& zP7)Gr6H-Wx#11#Z1UwICZuvzCP&kHSc@U*Mn$3Ud%YqeuB!^bCp!I zpP5SPV3K+#9HJb00j0yyDJ3EzF4yUUDIJamf4-p0E5)D8I6mSJi}Q8*Cse&~vy2OD zv`&AWt6k7(ftP5~(?t5-GQ(BG_m`P_apF3_kc;W4T)Y_3lEEk^QvMMdxn%9h>nPDx zQYi{1q+w1Xc7VXvd@)JG&^DoVio4b@UQy%L+c+w1Xqynhuq%gXn5QY-ZNF}~@wEzO zqlU2*3~Tcgjby6*cMZd%=tKoGTf^ik7;ZuJ8it;%=|USGKfh4}3vKsNMtzGU80D1f z)f(x2B6YTl2hS3ED^Z~_sv-UuxMY=n*HxwI0~)SbC_X@V!KYlMGgXzYLN-zXuSs4- za?p@`C0~TagEUM@hg?DFsM4cKUW$nII-TE5-|k`oJ^BB>8g*7>fQkG4@e@#y>Kh%E zGY*s1C}C)lFH*I{W=iku3GNwa<(*)j&@jJKFi<>Sybk4*LBHr4hI@v~L6dGg>~iBG z%AtD(^unLw$z1ddxyp@~fPs`pFPuTVYVtyz!)7fwg-Enagez9+i0PtM>GUl%PrD28yMct;#xjw?=b$yQ(}&u&Dtn5OLUN=2-+iOX+hy{HC2>wnQ;M&%#7`ujT3 zPjR7w{SA{DdO6=^j4#s`;b-mfJSUpiw zm|q~i1_if&MqnM|4D52C?O=aEj&^P-Sw^jWgO}<89`7s=lXxZ-h*uFO_Is!eO0iHT ze;CU0@^*;)==bg7n@Rcb=|$SbhoTSWt3{WRWfE!lYqaj-t=n+16bY}DBoLY}-jA}j z{pu_u@a0e=fcMk~-a$A$WCThOHk@e$9>wuq$3 zZzH^Qff0Be_(eGOxzGq4LRfr}5!ivyJIe@Mh43_jf3y*}72$6PxfdIOClEr}M&M@% zafD08zz&2VV~xOigzpf-ml%O_bBw_ClZ?Pac}AdN8f4`gfzxI}?kpoPa5i`lwjz9q zFg0uhsuA8q2u2_q;ckSl5vC*GMueX)Gy)$MApd1XV8A>h@Kc0!Q6unIgg}uIz$;q= zzPU!=T7>-weJ(Wu#R#t;oP<0#BJ4$;Q|Ci>gf|e<7Z`z;5zYiQUMv|{i}>ipM&PDJ zM&L>upF>Ey+z6~hp!DNc!2T!D85gP;xDJ5WYjmD=`AU zM0f{b#C4F1upa^6uL>+ecm&}n!la*o58*9@v#y722+tsRZ-8D1_aKm*lx0R>&`*uP z`3Tjw7=c+UQLhkg!8y^NS`J^`h%!V-j=??zN`C~$jipB5afELXrmZjncOVe0|4pb{ z2tnZBLsAYV(SkoS0(Y%40)Ij1_j4mK7GW*In+T!R&=X-7La&=8Rt=6_>Gx0QN_g{b zH3Hqsj6ek8?JAkpg!mKXM&Ja(gf&LML^y~rs6x{3!ZB2dI*dRV@8Nh0^8OluFyz++ z>+8!uu16PEv_Ci0i#{>cGeNBS2{scRvue}>;~sG~CJEGQ(6jltjSQVQ|#eWo>q9!bxNwoFNDdA9{SZ>`xOdGGzoO&#=0KEzO$k0Wn11WNew@Wy&zC$QJ=?x;2G%uV$7QEmML_86)Uj%~lOP z+Kyd#8J}RMU-b~`D|17>ma=B((>yyJGE>l*rR0k!Hlfo?R)b-2ou20Z6>QkH=Y!(& zb0|J54=OCSqB9_L2_9=@S;f}mm5{#Fnzb6#Wi8cZU}Z|oiY_)cgy`Iwyu_@8|3R5t z4C3ZcEycTu=l+;Lz46MH$tmK`GQQecp8~xjqG2;=l_4q+=oScD^<)naH&aTjfd9>P zp-nhhTbqQLE8fAH8(M;GZny8nbCKZN29kJQcHOP?t-5|R`uQeV<(pw8#QLn;2r@hF zU-K{82g!w;aa2?AwN|_r;H%N(M(hM7_jP_R0DZ01M#9%x7cRh?boD1)hoaB-&^rPW z&gQ}pEooFQ0OjF#1oTP(`waUFu(1jB!OcL4CtQ+TLe(mbb|i{Pn6^Om6kw%AN&uHE zUZqtK`jF;~Px^2kiU6;2v5G8VIX7a0F}UBo*2KX z19yFWRDHLM-eXsXx<(Io#>UAxlYu!Cr{VT*Y`9Dd*lEN!NT#LPX~dVbXkp_b)_Eam z+$FyHFlIY^C9@sAlGzSl$!u`z+d-z=rCrLurVIO&PVM9?o!ZG)I<=Fpbb8m;sS7{Y zI-$LL3$PQ}TUa-tN3>{n4*AaBKaufCoyTI`nML?7xi4j_g3unU}lum zm~mi{xt%6o=Js{Ol*vqZ9T!f9f8JC^vtPqFoU`X&o~E*uI7z`4(WWxGUfGPPZWHa( zE4yUj0<)GbJetaAxh;Rku5nFev{v@ssyT?&BlZT~mDg0Z0kH{-|Cw#pZbHngMZbXm zE%Rl@~!o|EP^ zm5~bwLptFyj*+Kcd|@4oB45x3An6NoLQ@&JfVKxH35&G^>|;_A_AZ3q7c^C-*zmSK z%QW}s&id@UZExw=d9qyael!SbDkEj+R;g5m?p`UuWg2RN7QE9iE}z7Oj&BZ2^Im*m z2Gl0BW-cE>q{{%amdrD2shAaJy~fPxLOpUtJM|#VfSU*b&e@IExG^H(D#R@s@#^-K zbCIf==61?%Zl|h5Rg7F_SIImSEI~@+p)}5Q6{H&*c=;34>+z3Et4X$_w`;jG+FOEh zH@A^D7Pw2Fr2KU3S~!Z72b5!$vD+^5q--0#L8G6geNTl>jcq|YA4v<8I}YlM6WXgq z^)cf>yPUG{F>s)ebWurd-dpv=IEDGH#yq{FGVLtIpo@lKzdGqKvQhtCdahDr`17=w z?m0=i#W3EQ=oCY)RD~Qvk7&rj1)bE^CHN{X-mH@}s-dxX9X^~JZX4j)o@ggwX!1nb z4q`CIjAjGIq~Zqfv<>hw%YBqNc2e>E?tHNxZ3i*JCT&l^sMo0QW8H!4LncSC>L9l) z^7B=}YC}5r=!w{$v6Kx8}oAmQhR}9~YGlU*G-7#9&AyEchV@ zDLs#dX7uRviDi@su5#%cW)GHnw~!JT6?fD3(%Mr`d$BkZ7JtS+E9K;$h0!roAd=E)(u6r*Y%D5W8ph33Ow4>VOHQTt zYNX4c8WsX{KmHg}+w`5yS8Ryyd z(6_Zyl_0h!MHzw*EG+nmG6mD*0(@`zznLQCD|_a!^y^8w%bwF9CTQEkPr(Gk`C{d& zE=}oRPa-3xcCu%>wCBo^wmlcS?I}r6CB#Joi{|~UokjD}V7|9-pn+XJl~|<1epCGa zZ+mvU^+W6##fUQqks5(h-;_PSI`@a!GaX+l=E^2ZXdSwUA7alF3(20B@c+Nj8P7QMq#Cw1MZ~-EYIJ)7n#I~i{E@%W9)UQ3 z5ziw;)k7?E9CS4n&Vof^`3T!Vg~d52Me)Rac9(IQHDD>a3|tBMWYe@ZQk#kt<)ah1 zYzn@0j83Gg!K~SU%Ramq4(*s$CFxQfzT2L_Ep!p}EUHzpsr(l$f^=Vz_+q#%J!Cya zFz9nmwq6n=W+WIUsGY%D* z^FsjyU&c`}`ZClJ+|9r$Nih3_zmh)ER)}W>v9MKftcSmb_I1&1(MOCxc5AGg^<);N zmDUtbUd6E#dpcoCUsLBkn%BHMMm>$ z!L^j`TFUc^V=p;XOYv$c+Z4wZ;?PohXerf-;}+u3QhI7BOBBa^;?PohX(yV^4pu_v15XRq$LtE-m z9Oxh#6vqzY&{C4Ml)DwjM&i&?x@jq^6-SIXw3O~z%4LcppE$G>ua-jlx=7P>;?Poh zXek2~M<3$QQhI7BUtraSFh0i7C8d{^@~Yx^o;b7=pO&&+acm~5F@4mVR3y2Mw;h)9Y%WOe?)vVi}GeS#(b7H`>z=A z{>TH~Y|qq+;{*IPCxPA;s{2@ZrYEuYsc7(&;*9JTI9lPLm6L+^ z9^lQ(7}!E3Ug|m%B{K%zTxzDfRfeZ1Ltd&CPQwiwd^d*fI*q~PIkO}XDF8yQ6&_}F zqcM(UA!R-duw^RyN|ram%1LK=BOq^tl;?5e%^-QRRfZ{)flc6%Hd&i+uHra@I0jZU zc;ztKO>v-bdnt}2h2fJN-8DuW*8=2*_i!W(x_1KGy&9uQaqK1z=n<40Jv7DxisLs5 zBSmub)EE_tgZ6q6MyllKr7SjMG7M%IeKdh+Sf(43{V)uBu5{O zaRS$S#Bl^i(qn|=@N0|~#qkPpz#r+7BcL&!QXGF!7@3lzug17tanvb{EXmPNW8A1X zXkQoUF-CIq*BBAS!LNUU9yyXDs4>n{9A_zvT*)y&WAs)W$;1H+%*d#02WpH~T*IMT z=%O`M98d7yb`Bb>ztX^e`5xThyS3stCY(upj1rBhdD+40mqDo|A1x;K}>2*9d9zK)VgcZ@@R?ZX=L` z@Oy-p5&nbF{~jZ70YVYN&k%l%@GQbR2nk!T*9u`0!Zipc!ovtJB7BC>7qT)C3K4EX zcnYBf;RM3qdyT+2gv${s5FS8iLWm>uf^O#`%trVr!mkisL--P55cD05@Ckzd0qBcx zE5de!R}sEI82F$O$U?XbVKu_t2n`7DBY1v`y+{ao2qg&X5FSN1i10Z=D||z~>i0YC zv)L+b1&+T#*oUwT^bH6*5Z*#C9>N|Pggk_$2=xe0AT%RvKo(exY4i=*Xf?)pVer$dyD|9dA^tQ|G&q?B%QasQ4O77zCH-!1YAs@tTYk6(`2A z6nlLi6E+VmDK_j5)7_5G`KB$tU-2M5hY7j6=@t!A(Zw4wmG&_1&gqz1MyYgz#>E@E zemC8Uai`3ecUwM}cTONBc8R>p;z}98_g0XL@5m6($=2t)mp0>miN#W{IYo>fQj))bhJ^Gk4!6RY1i_ zBw#C^)MWuXoOBPRn=)P2sHj+l^<$~;8?5gta4UV!^^m?xDnz2K^h_IbiH#{W;8TRCL+7~3Iics=%Lu%n&Uhx=<{)nwcL z8!-mq9GOT6IIHkRF;K}JUiO8*@@x1A{_t1c$&ntmV=^UnR7|&J;_;V-Dbk=xE#p1S z3sdDuR?B#A^TITFGQ4HHuX$leo(yRj59bY&Cw*JS!*?V2#BaR_Y4}{@prdes#uv(?KKcOUWPGUH zYtF)#Kam}`GL#>4@I5WAST|v5RMyahW>rQKRuz$iRV8!=Ul^!*{VugJ!ZJ1*7Ep*b^hV#0Tf3lJ< zCmKa)@1z9U09CM8V+&B%g19wW2)*#uzZl&y@s)qZE$^&~&k|$Z+2GcTty_Fi^J2Wl z5Fc2b6@*gnjbrd(yD495gOoWS+@D75^kOHS!ACpb@L!}L<4_?M z*}dX4k|VNP{gwa55`VO)P^6JG+z*3>nB-OOLvo=wh1H=X%xv#R5}b=|QP0B7MmW+o zz}QZkL;O{1h;%#c=CrC)6Y%+}H?t4=YnD>#leBdg*{hN<>=$oAl9*HhQ3nb2HS=~N zWBCXx3_Iys5N|#B`pCm1#;PKcnP4%?{xu1h*e1wCt9ArV%3jRDhei>RiA~VyiHTMn zHp#)jBkD(=r{e zT}SaJNr4?S)ibN>odoml6jwae6;E@;L#{a8ppszb-6LG_bRFNAN%3b%Aa+VNj5D9g zauUqj#_0GxIXZq@u8!Z6=Zep8#lx=n938)Hu8!Bt*YSInQEg;J%zI+4_)1rNwJToc zidVYg{4~GBxnrG9f2v-`Z`+{b_iWPf+cxX?J$zS5;@oz>E1m8-*?KQ`>W=5x0R`a->Pf<{oBqkC5ExMc8V5mb&8DVsmqr%dpK^WZCL}*xIsf zn-t;?vu(TL{$aK~r?`KZZSVe{ZL3EPRha9tHsGX#Z7B-zhuM~;xPO>!a~1awvu&l~ z{sHBdHnxnqj?GzKwM7R;!e|sCZQ(KTIOio7Om^rxaYv zqo*|JE*?EqI**r#F>3qo>N#pyhAk(Nm@K=qa%_6_W#1I**22iu(r+po;qk4xozr2M?fZ zMf(9%8s-e3Zre7Yul?_n1GjDedjNIY_P+;Ew{1UU0Bvs@51`UEX8?8EmhykHElY8K z{{ZT?ZLZ?}{sGi&+e*d#-v&@+Tl)c2TBin3K544t0o2tX)F8Uc;sL=ui2k?1Q!)Rr z!Bg5oW7Q72a4UbA2T+d7%f|`hO}>o89gKj~1D|jy6a}>G^(X2R)mTawl&|8_mPX6i zDaFZKuvgv3m)Wh!a=9fZ&R5%oqUdb360raqOeHNOX_VkvZL6;@5Ir*>sF}XELHCug z(pw<9BR{03;Ci*H&0mdewMKzR1Ro_d_$yCGLiRH>Z24Sed`WdJNugWK{>?EEX3# z!}4N1CaG;Ib`bc&=0>832l2Rzi>dTb6pzg}Rg;U2G|+=s&9Z()Dqy$SBb znob|-XFS-2^H!f4?|wyBQ*wOfHS6`njYq)nZ%;kGiynDc3@6djJCm|M^RF3>2h0k@ z2e_*SeR)~C7~V8=z#(pvB=_?L{QSaA=R*RPtYfpWJhckhu)mmf zBMl)W<3QD`D?-pAkllpU{W3CReD?Fp{}UDSrI59aSlMrUJOS&U?gUvEQlwbkk69#o zlCWbaol3g5iu)-&NMI}!h>9Cv8Fp_{L0(8#`?TMBEq1K5RUALfUz3WYjE1%4We0Fu zCsef*hZb63cCVQGt3E?1%j7YbPSWz}c>mhJ+lgu^-EVz>6x0ALnxuur0j)&W>2x%XFKej3a0cjnUraJaMe^wXm9$@ zX_;v&dIjZUNf1owds8>kYz$A7)NOF$JIk8N-lRDPV+QT^*^!LR&yH;Ui(N5osY z)m2XY_T{#==G3OTU=vr3Rl{2*<9V~G&6}G(zV*Adx>S`MY?A#@uzkM$M?I8bQbZ|Vlzb?36dhlGNV|f)W+|Mo?qtfX)?zHB6 zTt2KJ0RU;|#W&C=r7AaKf3@RBZjI26-!bQU z&AC34(arTNX-aBN)SQxPZKQI;W}UK^$|8kkHAAi^Ste6N17->p`vO?dMp~h{s{xpz zbx1o?r8EBY59PlX+f(0eE)-9i+(<>Bd zZU`0oHy~kz3X9tZ!z{FSGK)S_h({=^4#lp3r6HJk89h`L74PEVcQD{_o(bSh8A}7z z>h!TRM%{XL-8ydPR`4bC0;y(S*lMkNgZos^8)h$yM;LQZ>nUB?+f7;pR9{$}1YNlD zIu{Qgy~Tp*(xE{7{Yv*iJ#eKWT^3SAYE>YLU3^?f7fA|r#s%UWM_*jku)f3G z`uZ!6!3>&97l~W(!clgxzY>pF^0aX*^)k1vj$Osw?5z{VCgA+$!Q2g(#{9c#sN*Xz zyNM4@agyCOVd~=}qou*ke4V>6bY|x44(#Jevqbo#m z?9@E__VaY>Hfn_hW*>2_X0`6122G%IT!}T*&5;)4bYi>e-W{*}g53pTh>L+e`N`jz zqK7qnfwYBYQ_UG|+3(;JjZimQAl^8cYXIu#m?M;d$FK^}WEac5&)EJjqlvrReu&ks zPlQ&r>ka(JGG%XP?(*U+yUE<;!_nMD7Mi<;5l25OU~VMm&@Jj5Uv^XJQD^Gw#V&F= z2_JGU6@sro6ha|bv~bb9M%g2Uq#*?&TT>Rz!&142?2P|Ew7m;pRK?joz6l#xaB)|R z7&K~ZV~quC6l$>vvOvHnw?H7la;@T5NVP@2jYS1XNH%g97SSToRw!+?rPg{y1+U>E z0U=zh)uebqtL=$+!D_jw@PD3n=IlAUxj_5-f9Pz^dFP#H-kEvldS>3qfVb4qG3E9V zjE(vpM+Nm4G{Dp`lxBPx?pN!DBx0-rRFY-SBbwm82Ma#+OnnEyyeT+1pnL?v4lDRK zroLba)jf&V(&qriX;ilIKcN<~SQf5eHdG&B<}6&n%+Tw$%pzpVLiQ8ZI?0w@);h_S zb&@UXOtze6Ev*NrbuD&x1PgW{Tb8mJFR9uYD7EB~Z3WH?ViFqeEGOB4fh z$!}r{r6@*pVD*bkIa649npOy=eoGxw@8hJx%;T|L%^ra<6T~jJYk)*SrE*o?5Q)_O~gI%m@Aqc$p7eNU0|225WEh4$cK+38RutK7a8A<+CHk6+@{oRu&7Q=P{$rYTf) zY!!4)bFgF8i3ZhCgLK6+d5^9qv>t;xQ^~Mf@1UOYl}VDy^|a4kC(BDHlTUjlm&r@7 zOCrkc*ApIYnfw->OP6B2iy@URkH0(RaUa+d#F>m{MFm z-adpg@sOr7mIww!EM@HsA^pbYiI zmG-l&Y6mpeS<%>ofr?bUfKH6tn7;9vDd$fzjRH*EE+i)h+wB{RtKMAv`$=VP!(q`1qEyRq)9%_I=+oSyE2jF4yMA!i6yRBd&7O-=$OdFxCk8Jhtf;VpA>|;1$z`jxye2%-wf_=9k zNW)-GTw$ayi0x#&!1o&L5Pq&;L$nj(Lg<`MdoV)AUkyi1xFvX1!Qt7Ds@;e_YpRm@ z6DsRz)*~{L8fx@-|LJN_5Y1RuwN)M}4_PyV>Jgdd$kdU8<4zx1%X3irh$&cBwW|tu znOnkQEs;5op1;1zVGbfgNtf9fp0w($_{$#2(=z)*tYbUbpeHlyw3nDhsQD-`9($)P z>}<4GV|0c2|EGHXk6ps(jnEz_!xh$ zDDj74?=vGK!9fMvXaBR{om*z2_;YnAcb*xx#=W3tn?2N+>}#ws*)sLaz2Z2*(sS{$>g~?#Rp@5fV4|!{VcFmwEP5dJDHEqo=DLGqE!Nbff|c!- z%pY&x)CQB?U;FBCPD5n=mf2@H zfgr=5gZ-`{C;Vox>34*u5hJY%e+C(eaIG52`u=?eLbvH<_I-e1W`Xl!A95~wb#e5{ zi;rUiKR)!x(#8!Mr_7!URp%brZLr+NIE>&C%vf9K}968v@6 ztWTBcG58juqVCH-!kR}ijx~lc!I!ZzrpbnvLIA@H(tx^H%=A#+1_dS ze^?wHtmQvX%m3g`Q(K+~>s!D9>l7C2-a4kC>@DulT%F>l)1duoTlq0v+HXdRpH9$a zh|(LAPSlu0YV6Yzsjws!rvWCZ6ecE0;T)ZccN$G94q{f&Y*KNplLnYK4cKoc`6wxc zGR!3c_QqtNB_coC%z(Wn$wx~cMO1DKZh`P1hF#7PpX7gyNL~(rB+17WM=wv(p9>tx z3&D!Kmvb;m^ry_u^U!pj+rOCvGc)S2-95>RBHMMw>t8_^Ny}WnL$XJ(9f`~Ahd8(s zVP$Y*KZJ0cd_W5B=V#tS_JCHL$;HttwBY7c#*G@Y%&yVVP1)qp`pIQm($a7rfo>=^ zY1zHHUMLL3E<GCRPt!fSv$-Gs6^_+BmEo0B5`}>V zaR!o#z1g}}UmfjR-6GZ6q;825(}M`aD&gqTTUqu!4#}woS@wLFq_scGzR@9R(FZty z;}Ucc{Q^gHw0|k`o&`>$JSUX*?N?0B{JUGSyvI--|gWjCL)qELaz4rr%-l%jR>& z@zIF&aHpT0hcB$pzmKzVQSn?TzsuxzHGg#jbtOb$WkS~Ad)iX)FNeDy?zeEPlPG@< z9Fd>VpP;`(QqannQ%(rw9CZ3V+n%OX%ncp$% z$#vu@aOkc)fh2j3ay-WTnOw;(G4^^!``mF&z^tE?%8Ne?0jw}tly}=~(J@A5W z%BBKQy69OZx7fcyx(KDaLl&QqTTeO6t`rLZv~(%68T0OL_LG;u4D*YBK7M>%8bWRi znk8iT`%A~Gma=jy_85BQb+jUD#cI1?kQJ-PFDFC;@ael3A%rag ziPq8jEw}Ftv|`T-b)ozg(~q`qEZivyo+!_pWt(8kD{>o+YyFz~Nm*3U$`v86yu6&r!ll|7pIe2J@RpEE_SuYK2 z=uGp!*2*FPH;7#g-|BC?+z_KWi-q5q>@&oLat*NwplwB-Su3YejM&2>BkglovHRtB znfxx6-|6JJcLshNVmIPftog=f;fXeTRipjzyRc6DLIi@W$EFP5Izm6o{QUeuD>jMZ z#{A%3&+L~hvf{-=j=#@D$7V<@0dQFNPT=#sysa$$)DHa09#s_Cfe3OB2NmKUF-ZJ1 z{5Hgz@GCZtV{hV#)R94i5=&W;11>Fk(B8nOe0eeYa}HY&e2 z%I|dfog}~Go zcN>bJKc1L<*2=>oF)x_0R{B7-RtDuajNkYRQT(pY7RF3GB1b3teiOm=_jtSt)c8|8 zHE*T;rieS@2l;T z*tstorf&^eD<75skI65$E5&21X>zbv9fqF2zzqcY^?X^-6ruKwe>C z4ZoPSf-4Wd)IH=lp2SS`%6#$fhhK_vrM0p@3GqT9N94Iko{RC@5F3GCt=}blqP6PS zIHAE>^~wo&ioY-ozfvUXKtl`zLn%k*#%|YxuMrff;fu$zpbwuvC?}0iPA=vGQ|xM6a;E-WVfVj)&Xl)7$$43u6*=2 zJlhbz_c6pbb&8d)y2}xOYy7{FlN*Brsyk0^sF$(i&=9K!7X%pC5Zf$Iu*@CX$|ts| zE2npch2@DG{qDy*_Mhby0reTi;J+Uu%{|-dsq0Slb*Y*HPM(DeaLJq>T8F{rCY!TZ z{jFVW9#qAx4mf_E42Xl7eP1m@=kk550R*m?<;P*^-3nnUKPM*+B%ZDUB~~}PNXG~J zh>Ry(VvX_J`CgjtQA<@bOi0$SdE_az(*m$H5w?$@1R++YB_0s*=VU-%!q3&#V80Fk z(?K*>@(PLvA55#h^o3Q#HBrG+2~3@$0@fndzE+VW zaj@34=7<pvJ`Fuba4v zpuId1(i@5bO~tqUDfoPKnkw<5)Knyp$TAOv&YzODQFt~`@<+-~6&uq{IY9zebaQlTjzcn{^h!(S)(|LGBbk&nnSgvxtjqbJQ9qc%CYc)**H+j?^ zcpG`<6)n5txJ@3m2cFw;=r;1$8+V_ML!The`*j?;f5+a19eYPQ_Act!ySTNt)M%Lh z>wz%=H+tv~KQDqh(MgZC=2%d2`yX(YiWrVBoeIx|He&+fVZc zMtgc}KJjKcRLuy4FjcdFGA;;V{;cOw<(_&4JZI{r7V66rm?ereFt<(CGNauEIf?{xq-x$XA!Nn+@v&rA)R=-?&k z+A!m17+;|Y`G~pQs&`Kj$eZ|U{K$PzZYTM%WZLr!cHkH8z%R1{zpM`YvODn0>A)|y z1HZfu{Q7j@m*0V3zYhHRci>mpfnTHpzoHKOic|cY&W{rqYW0Y(MXQVO2c2Kq&^EN1 z>k>)^vMEZWb+Zw3z?6ENTrJb)7bbOeSL+UI5~6Igt4oQ+>F0XrV6_b9t=Ux+uU#cw zB%NIQ-qORh?=9V1``*&GweKw*Tl?P9tF`YfU0VCz(x0{Oy}ILUk)Evm(9(^y?=5{; zvbX8LzIhZKSOq(<8_>o-J{Y=DDs4Qb(rBxax~?PutxQ!6klZO4Wg1RPM{RUhDcZ98 zW897Ti7&k23+;aUnJ;`8?t6HzhCB8V?%@Q_M{qshz6D;h8&@{prl+OhfAbG}cGtjv z2i#G(VW0ZKQMjMOJps1|?f_iq0M4PnjfA@qPQg6{w++sQ>w@^d1veCKI$RChi*StJ z8#iKII`3abcIPi-&iGu@F2vtQ;r76h@7Lsk`#ao^K_g!r&dYZ--am&s1326Mjr&Ls z!fqbse{{b-jC_DQd=&Ql@SX+#@o*o&T?p3`yk7ZI^QO*&MZ7=YeG}X}a9_fmc?h-# z;YP##817EE-@&~Ew-@dZ-06t_61d@TKZ2`)dk&8Ad*f!_+;Bw8{X6rQalUJweMrMx zxL?7MuNIDc8{w`5jeL{w*UNVv-k*hQhD&^3C|5_+GqN0V5GM(-MopPmyP?IjU{iPBlj-3mG0-*Wb~gZcf>viP}qn{i-MwLC{*>@XI|h#)I#IG|B8gQKP>X3MWvzX!kh)mVhWZXE3M z2-S?n=pvr^GFlawBg6t}@7?jW_g3Kz1_E)@Va3;2%>MQHKHqa`5nq+UaY8JGWy%P# z=18xfwP0Y<-=7x7ATOI=asLf8+@zc{E41K4JdPb8Y0XDL^;(H_nYvkQ%FV`-PylgEy%+&Ec>=zTXOjcJ;94 zx~#oO*7~q2A8hYoY1_$}ukmaW_%D)O#M3VY>oXqKS6$Wyk~QMPSdP~m%?RDO1qTB$xmt}TqVL`oZv1dCu<1)MvO^2%@hFwz z4crddqC8B$H%#Hsf?tu05)L~GXU+Djn*-RZYt61Lo`F0(;xd=JP+BAf{}665+{3lD~y3AYsP8MwFMG7tyjxf<>#a1X(~47U%?>A|qy zT^)H*x`)rO7J)s5VYLaX>o84zxO3=E9$3Q8f-f>qP6zk%2tT^7=w(pptnAsnOVlp= zPyLZ>d=8&`PHZpoVV8YYsUdKQAY0uh`4Hw+P!rLi8H58w2+wX&VV2yE#M(Co`jH?E z5m|$RfWigszWu*7L0(`G|Lmt#U<|x)SG#ZjoyP08+Ur*;tagpHafLhTg6ZoZZLRo* z7TijbPwgtPyA*=sssu-kjoTYba-zuF^{pma0lbtuX+4%#K!z`Auu~jQxlSdQ6ipqunmt^f<5^@1EwD+J7*~_)Ne@?}%e9eYiSa{n#XVeqaJlX$Sz`1L z2G@@~T-7euEhI~fSCVUxhijtCRYJ1FIEP$kd$_*qa($a*iSY??{Tudsoty}{Tz+y% zj62A+%fr=zXOk0elPocI9s;gEdbl>ZT+4qI#l*z)j}+LjFHoE9DgR|ofNxOH%Q;68=x-UUqr+_iA?;U0wB4)+<{ znSr$M0J!VnEVxJE{tEXc-1%M8!dJoF1h)+CIk@-Wx}wP$gXRMlZa2#uV0_>DvVuM3 zw2L3Ac?Zk1T;5clRDXbqHoy97P6k3XQA`rdrMr3}ura>2rV72TT#eon6M^ma@-z53 z-E?r*qKB*bx>xAVixKj8?w_AOP38f4C_P&B=HLdm^pXp^^m1reWFn%5&gNovP=ZGj zn=G&P-iRxQ<7=}HAPfQU;c`T7p1{mXz~GDs&CX;R7!2RK5=P&KEtmePWxa^d!bt&O zgq>@~j1}yEV;Ym$Ij(6b^{_wfvj3?y`(d)@df4xD+2^)q|BCGY?&JzH!DS!WnjHn< zYuVyqzsO}jw>3KlUoH1}*pEUmlhjY~n3B{0*=Kmzx4Z21t=YNZq~!_^JNI1~^t)TL zhsoZ}!+w*?eqC#JZeMBHo9^b!l`ebV*6iHl((*9bF|6ZEGi2B?`1g}N`NHz}vMla} znJAx92IEVFAKH+m#^NJ&ph2I}!@+COO`|0*L2{G0wRlX)x;#dIu}9jaE_YI) z)6{q-ry0K9dyu`k8{YFP|5;XNzz5mtKp9M{Mb%`I)>R2bM|=d zQ@K5KL)vyDif#0g988=}@kWaVKQF#kRUszXe)jo;44a6cv1c_udS?7OZ!ri>Qg9W$rg z*>mHC0@wSfTK3#hP^aQVKg@1SnQV2_U4ev%juY-tUhZY>bAL;?bG+PKB~Qh!IEF6j zb9~ld{&K3LV?2K0DdNm z#a01IHWMYg2ia4-L3Y2XOJ#jVPnR)Qp28_J=F14wkj{eiAp8=qL_o#A3y;d;&bX;o zH!x>TY2Zqi%N?C1oU>;tiyxiZ z7Y^O|GSD$mTYDDDp(r<()s_F$%dD(YAlV$e#J~nAbBb|po{w9yogG$C{Hf|?wmle2 zvh9)SZ#IW29Of`kAhcj89#vVNsb~~n+iV<+6v2W4L-B^eHO z&Ri>?Qzylrl)Bk2|Br+}r(^!XF2CGGH!8Pd{vIxW7s1c#nEyQ}D03U~zkx^GY0xqM z6E6QBgrDWoL3$Us{Ib-HYhOC#AM5g8CHVb2=KqGvf2QykcFfNeX_MZ6;!%x?bj-ie z<>y^=(29=v?{WF>6#n9l`MK0=*)IQ&gnwE){4i9+CS)*~&<)Fc_Pcml_os&*dg#!*8$R^c z9QEIl@hl1*W5cl<_I0Y+GQH(++C+ev+3KY?`EYAad`_1Q|IAQ@-?MJb zjTc7L!^b#)ix(cVX6C6!IY6^!_EGbr)~(_A+(;;*3XkDL`mLFv1ua}lL4(gWKAt%i z!$vDC4O>zx|7%3U7P96r776ab%LY3GU%hY1SjV#2urH%}%Vs?6{{i}GlxcLfDbLw6 z69v+$EUUM0V7LE56J(&q56`}QgN@x`>A*(e0^47`SRxbA0QJOpSxc0q$eCbIwc)kA#~C_h-1b;JRlaJlq_(eZ4T2LeE(Q_Y&U!4%Zd5 zOW@e=I{!+c$N{WDV{oB9Q&(k{l-k>n)>!@AprCw8MrP!yBTaT~}9oHyfUx6N{ z?6mi3#%xuN;~8U1?P(|q1PpSKbSHq=0dmzj;Mt+i@3q0c^@p~g)Q=~=(grwI3Y3<+ zBgy;M%Sf`|^NG=YSoy`Jf4m3n*w%whXV*t~Ubls1!|$umouJx4Tv*5a7(`!r@5XrM zLbVuHB~WV3HGfY#w*BDVv-b|6L3`RkbSz`JRkV$+dTLSPu;j6a=E_R-kV^qOUk^j|7km}Cy@8mu)r@|%m_hAOTXYz1zET-_{(Q`T^bZ>{Mkzy0Nx z6K`t`C&&#|ocGEpn<_SFJP>8AVRP0{#hE0JMaBJ++IXR!{BG)f#QM&LeP~-* zZ@bH>D-4Re%c(0IJLoQ_u5d*auKBF&s}@i})gmgZwScOqB(CnzQeQ(t8fy_XSc_q_ zG}Bk{m3qZ)LTrgP64UUC;~;KIAsT(AJ_f75l!~+=w742vvAy%>vH2)VtRTeuvGy~M z3Yw!ouvSTK1qxoN95ueYdP^1PuIBdr`13zZom|;35g0nFmZeeoI{i&YWUXM1dh2kF z)Y`xlUDYtZ-D;nX0OkXux4mQiGZQ5_s3h{Ez3s&LWC`Xv_P3F3m>MkRvQ&kyT{SpINr;vcNEY3fv1rpt?GYR)$f)-Gp0rA5!sB3X58eAruxP>8t_eK@KtEY1ee zQ^bSy@yuf7|7$+Z#Fv!E`^W=wZI#j5D#W5jYE9-Onk9B4Y2&pf*BROb(uRf;rSplQ zM>-$J}j5JUaE;kyUCE>FZ;a@a#pM=jLT`=m5mfO@)esV|dF=sKNQ?Hd< zXJM?v9Wy)ai;bGo{*oG>pHPPn4E+zJ5ABzT@Xtp5?;;&bCAzqB)sAwj7tWS(=lxE5 zg3<8`GL0M|60vP$u9Qhc6A}@9Wq7$LrG}0ZU0P@A!nY;-v_z_S8wxRc2*xDInXe4( z7|K%(Es^jGjn02a+Q^{9KTh;Hw3w@q>fWXNWyXGOSYIMR4&@3$oz#ZV;x!=aHfya3 zznqLCiv{yBJX4+pq>Y~;fQxm#n5~9F@6ldm>w0jV8aYeCJg?u!s_{|bp9HN&y&0^A z&Jq5v^!sHhUJFzS@(XCqh#AZ4AbAxQE%r|PhuAus=ytRs zFI_5j70#j6^)mGtO&Mf_)PCfrwtC>lhC#C#5EjWn9?y|;IPTXKrvY8jeQT z*J9OL+WlpysX8KmBBFIpK#6FL6A{mon1}+=;x%cy391@c8PHGl15LzDPIyMViJ5{` zYQ&28x5CSQ6P8XS^7~Mwb>!hh8-xDo~ zbUSZJ$JSJpRxCtdbWupcIA zc+d6kK=n}$r{>-idv*Ryj#tXcG^H`@o|6AEyX$|KkTYrA>@xD;c16+GtgC za_r#zYavjeJTY zQ?g^KBLItC4=`nZdZPF_3qkz+bF%R-Wc)cP@xKEb%qdESe>0t3>?8mFtHE8p#!;FWJi zTlF*S%Ew17Uw_FDEnlD3^|O~-KdW}6S$8jMEnws4DFV9nGZbHm!ecShdhw%pewMWa z%OU{~gJre#tSZq9aH&8rQGc87uU?3J=<}UBSzp8qV1>YB>KC9Gx1? zH&r|@0d%ldYB-04(xg~-uYmMbJJQ`84*RN)+8~CO+=#UuEoijcvm~{Yfr7=cPhfso z=RZCT$-iWM|K%@!aV7O#IyXCKw@2BZbb|7;Qv1rjrnR!KYpv{@g&1Y$qqp?5viE7N z?7g(I-;d;IWv|-dPt|s<@7Ugh6A3(zfG>{vJVIq3=oR)_3iaUxwI`?;BMNo7Lp7^n zvRe~{ivt8kmDVF_U+;}^Y#GErqwmM@TG>pY4&XpQG3tJnY#+jq-wIf>{MMYn6{lC` zd(^N-w~6Pd8g>)$pRWcUEXrElEFs`aq;(rk^~)MTqI?ql!+)!PTT}I~sSW+h)B49h zC#!!Ci}ao*o%**M9eRrXk>u##PY!gfe{5!5{aX#Ae2K=kuM@Om{j*!^-{IE!*T>aA zKK>{A_n1fja!#awgJCkUhUa0D^si5X>eauV4)s6LzkEplU+dpmDZxm``j^rlV%?Da zAsPzl55I=pyhIT#g%N7gop#hjaDbT7A94hlsPVc#l!3DD5Bd0?6j8mWh^kVGNOv#p zcyOL3c*lbkZS^-?k~IAxAGJ8&_w+a4@l$^Y%kO~loC<&gSga1wwzr*02o#5Q7 z3eH{|KIu~Pk2JL%ruz4Sq(4HUvB*sQ9Lf2w-yLD`GpO0|9AF|D>lU|I-MtTT)x46n|694o3}4^Yd^v{iy<^U3n!(U|RT-_HicbrZIVn9! z`SyJ5l`#DzBus~dU!R18U!O=qQxVOdlazPE$LU^y{U;%?9|C`Q5(0nu9|?TY@^dpPq!gpZ?#-o8gr==OpCKfxPdWguL(k-^kn9 zD{tf^V)!Ey;22v=bupCs`uvqNAl|Z1ZB-ba1SjA;mOq$Zj3TKJv2q^jtWlY1a5}e&do4w?uBWz$02s~JXfmHFb=lb8M+_4 zL=DMQjV1Q~=+_)IJZlV&s&+wR&#z}2{akErwBNwxl+xiEqNhp4r{WD-8m0w}Vt z8#RsTa%h!Zo<)!4+doEo$!;*i4lfAc+i%j&lzKuz7~lT0-6T6+p%@Pn)Vy-MG`@m6 zmd6VVRv$8vP55eVZf3)DOg+X01zci>H3)0CAAho~;k2`vQ@s=5ehU^xjvj{%QQ=c> z>)kizi@QG5^a{S&3qEd9$<*KI8AoLWw)bQn6hp9S(a9!=*@N>+>peL7%hd_@;P7e- zU)p4A1?8S7E7I5-yioQrEag6i>g}?5A+c%4S|~&1r7|v+P?MctD8a4v8MxYVYGUr` zDL;(={8;}_j8BU+p^s<$cd3wmQIBZ#Fptw3Y#Jaz*DVYTe2IlNQ5yS!cbs8dvi(yH z&zhz7n2J@0&_)cXkokNS{a+#dl1bLFPL^2pUXJvd!^fl8@F!mZN_%fDE7x11{EMWF z`tFTCRs~e-oR~7Pva8vsQmN(k?f~)mFw2n5AlP6e%KnP|;oDxxwP38=ZOH4X-;TU2 zoFsWkQ2$TzvP{j66(2Tjr^@^NBzdt%>LM?J!{-2Pn4GG2dW9B`0$lYGSqQyrFrkO} zWoJ%kaX-9aD128Rp#}`~T)Yj??5B%I(0F7R_OF@3VPQl46mJCiC;m1MOWfuyO(<`L?%>QD%9L0&Vdds ziSr^*)v!Dx6VDGZO{WVr-$}or(uPCxuzpFXEeaPz#ed z?{#QNoII}8JS>vL`D2Hc#5o?+L&Nyt@OUW3MFXCbpQHPHgn z+<@z8Lz2^5WjK02*8aO8VO5T$&wybxRt)$hs-7B^$$$e4$tI~RivfRWNIdVOhT-^n ziQQmG8zo>417@0?8T^i*%5oX-hlX^&1k7W=M-0i5fPENnuaM^6rpodes1Fu5(eM3O z4eQ4MllAK)HL5?EU(~N7RaqgK&%?5hbdtj&WTr)Vyk3I+?qq&fzvigX0d9`O{CYH2 zqtR^OBtNF5Py8*>NrL7yu~Pq3PxA?LB0gc3Mk0M|xz>zQNS+)wcUW`7zk9#n`7z)>=EG!bM$1gZBX z`g_eL>rJAa%*Ws4{2z<_AC;2-SGLOkJ_TcnO#T;ZX)>)bMW`Ltpkm0Og-Dj*DSYWN z-K$&n6oZ|Xv=?)~A&uz#vkL}gM^Ce=BP`58*}%hxXM!b)aHfOB5_H3#7prIB90o3u zz&Q*IOZDEseY}B_;y+KvpH(m@H+rfyC^so3iO4^C!`u6L<&826}Y{J$O|?1DpsO6Ca1>G z4!*aV8GK!<9DG(FS8)^gU*lHEq!gSyzNx52{z%*SD^u}XJK75r(ithvL<4DREC&rUc z--M+2=DP7sKz#W4kQ86#|9gCslH#j!xi5D7O%ONY7_S5`VX zQ7~v)6d!bJfo%<%R(&=0dWErX&#nD9FDl1*N)JuX4a+S%SS03Y(aJN`NOsS0pe6S7 zU5f?OzI%D}>J|>7U}!*3a%Fws0H>GEYJjW{Xo^`4ko5sgF{=TxJ|L9JiK;m44EGnX z$KN*p4pp3~NpmOQb=;7e`mBP#&0d1(YnT#n?*%2mb`4q(N|pp?Oe!bzeYHi_&hfz$ zQ_A8jdjqbiOiBwT)bzSK%l>^UFPs{N*ELzTO7+rdHYsFGW}P5kdzgf34`jS=|C!pu zq?aBU&tfp@Q?tVH;8-i#7*(?}<0Hm~@QA&hSp}z6cBvkTvWjqzV-^yAHLlIe3dOQe z%t!z>$+IN1hI`AeY{Q2c@P<7i-9E2*2;M#NM1QP?d||@<9Q;g)612m59P~eE zS9$4LcFm^z2+r8yDYW2G9Jitx$~I!I(ik<256X^D@aD6u?6}I9(71lMell4Z6B^g6 zl~+QAkKvBTq)?vn(=oN7jE;v^M)kR;G(F15uU2z#dIU!lJMFaAzs4-+Z>?|Q-#LD3 z{hRU-h+oqM99H3_YEBT^8xMk)ANX|3~y7Eo|^X9-h`glbw}bv&r34ngFn(K^fXKVrJnC%ng=_2&K#h2 zFl(CAJxUc)zc(b0Ug^6+Q|B!FswcE0?d3+a1Yn5 zIounn!?l&VIh=H&<}jgwPvEA>Bwa!?YTCT}T6rZjaBixXM*|P=aNqw+bEvhi(Ry68 zZ&s%DxTxQ(EbDPmzggKv{rX+hRy&zv)QaZTQq;;B4kYz=XfOTJUZS^?zVp}E{gRX= zeLrR;x0t!eSdUui)o`=^D}8~cczZid@%DCl?fa){h_m=~9}1o-br%(YOZfS+qR5HE zbb0qERCMgnnoU^+ABGl0QSg-OGlZhjtG~7C0lCzh=m8T&{t|YZB&E+S@&{UZC5qfi z^-3yo#?(fmqdRu9PM+KYGKA>?rO{z8=xIkXO*@j*ZlDt!9*WU+UP{5Dy=Z1XK7K}B%)X7DLe;~QDXGbej?fg(5XQ78?hwZ5H}~;V ziN@)vL&--Ep@d@Bv&PaS8N?h`Qy(t)46F0#P3C}(D^;*1xaI-qngmowHBP9Os~4(U z;$+t~0T{;Ee|^o7^va*M!C$G(A;)hE$DdpaFd3E&!x8I^VOU7XhXq^gWrRUdYnB{5 z#-YP*@ftE(iofRgm_&jF3|ik9VkTlk%w9%!@PS%21{)qedk*VizC*HO84u~vZ#CD| zqd{&O)jwXh5PlWDlG2I*?o1uE?D+BO-}55@Qv7uyL|U{+)jcMRH~^U$m*Y`5Xu?AX z_E`wC|19j93dWa5F>V@DF>CA$NLq#M+i&A^i^fB|#}0}FY0<0N21^H6-K#(+EdMe%=2tnaG0dJlMkMuFX!yn=ecJaQ}c(tpT^5`&+c&Eqf zc&q?$r9ivHgGMsZ5Q1o5;GB<<4tJ9iM@tt-m99#|Rb&9~oqOEWl1@A>BPf|B=N?%c%ORla#HaY8oVyR8|c9!op@NZB%a^Jd&i3xNyS@i z@NikH{psCqdPyf9h8@J?c`B2CFKN6*l8ZRXlAe`|^s z)f-5d+_pP=q$HhELVJj~9T}h@s@^w>>9n(HzUYwT0@`dadjRu^C2nStPE1rnV*U)6 zPP2XNxC58o3T~@`I~{P>d*DbX99l%e<$2(~PQdwF!R;_`IKE(Kdf-SWT&9L=h88)} zeVKp@w1V4h;LZTt%W*fcq!SLu9x2@-58R;yT(}k7YX+_-;BN82kxn=qktAG!2kz4Z zTuv*vCIfdS;4bvQkxn?6=qB7psD@5r-$}qlTEV?(;BXS%Zd&XnmUO~pX}FahxR(-e zRjuIm8Mt16vpjI56Rwwr8|8tUfBcG7?9-!}JjI700M6CoKbAkZ93}Gkqu(o6clfar z@EmoOAJ>BMTNOTmGUp7{c1EYC#%GnqV;o*#cV+xpZot3*3lkP$jb5n^sO>Lt_$K>YE{VrvG}H!X zW-@2S!lB{){w+hpX^V!+Ng}a;#SiGA+CoTSKKl3Gda!npoEHhzUO=)xqDH(NRYP+- zZIE?_`5Eiv_))BwlBL(wXTUy?D&mSzUNVas+0TlN5H5^&b6~3{wyp9iwK$KnCMzyy zzSBVkh0`Z1K1p~rh4UyYKF!3@g~VW<6`u}53(u~rT7kg{hi|E&g$8Y8e=Eiy25Mx) z1b7OxWZ7_7DhIREOe?0vzHBqd<|LT2ftYzRY!2oLnd=?FWq*2Qpm`M1cf*a%p!1&^ zS)|fq+faS7Q4uT8Rf}I9$0<;plL6HOX0uf7U%8sq@;td~#TRt~2hN3KWU&>ey@i(F z!8^GI;7)VFKrP4t&feO;5;Y z&FlK^R>+7Yd9TJ7X-!r`iyNfnkTyiBGp=n1h!rE$)CM+%Ild{vx?*x>Wwwfc z#HQ%p2w28oMP)1|Txs?n>(E_fn?^exr)Nd+7(L}`w0mP{DQK913=ISvq1@ol(EN|l(V(x88Azl39)`VYXaIY9hX#NYc93EsQ=FfSODI%DXCR%S zk#Kmf!Z$V^qi1P}o_jRfD-6=78Y#y?GN~&Dvrb(XAZ?F{Ou9>o$LU!*M$bAL?NJ8l z9gP%mklfU<-9_pGK&sYAPU`4`)X99K(e@jpmw>crXaLg;ljSCP{GzOr*A-BgYbYmq z^hEMxrqO7x$6sdDd_dtZCm2JMijUupbppEq>%-cl1kw`;gg8~$8F`b&;snEhr8{75 z8rgIsji&%?r3Q1-NFSt8W*3cijsy0!1Lk+YOe$I5bSi@YE74$1D(Q(-VlY6-{(_7{ z>Z;!V(g6!NV5S_|V(Vmf2Uw@%WYQDK#9)hHbq*MnOY$u2fVs(Jr-Edj3b3bZk`$Fb zNTy6B8tp3_FzT0p3B>4{{@B%;y&2=iM>CRI$pA`X~IChMzC zW*A`E8q85wdLo%Jd%%5F4j8ozV3;%*&2Ur6Pr*p#X~5dOASspfK`Le5&}f%BSX47< zCOBQtdS;TzZ@xO2J%F}AqdCc>Cz2^MhDJLBXw^3aj$DrLF={l^l6U}3JnW{eXBjpe3tEn8L|fJ^mPK% zi&X;Uq>r8z;gT5`)HAap28z|YcCu;e&vrTu>b0b<6hM_4$VnXiDgtBl;#H%4hz(0#xL7X$plaMG5Oz9~sX zu{3BAiy&DH~DzIj1$9Jmrw4gF=g^~Hux$%e2ZPaYLZQv z{PkS$jr8zMcloX*Igtxzp9juwdN{xDa$Z8RiMf<~UwSTf>EZHqAs=Ff%{ruICOLV% zjFT1{&nC@pk&L*R=HHO-j~>2ym+vW(P0Z`bSMA|j;qt{vHfc`F0pGPAzF98c4J4a* z|46<|J$%J3-vE+Lyc@{Z6@(YlB0iXp<$YIjSJ}oJ)52TevVIEN_;54e0ym|FFMt~i zw*c;8xYyvmg8SA_(!wQhi{RG6y$!b%VV;G14=#vSrypD`+{19!A#?$OdM&CH9? zOg*CuH~Lo=YcqG ze0xm;fJSAvX#m)4Eg_$@Plk`TZJBheA)iszE#$kI1jDCXzABPUdOsoG)gHd*SO6$?)xWEBihMH}M`LUq|<7 znRqY%4)~;fGI+mo@s<+L#5;w2(mol!|8@Chc;dZ3kC3mchmQ@N zDUU3YO}zgg-@BeBfeoGEJK$*&&brVijQ`H>0j zYR&u5Q^VbiM`Z;%;(pq2L#{vJv3f(0+$Sg3ZEj3Im6)m6h8O_ePid8R5?BhBX818Ryfm?C+E8KLrd*If=y$zSrUiL(L`5m-SXfJo)(QbR0 zZkjV(S#t;p((UDD6t20s2JK}rGT7T*?gAfk0T;)}N6^P#13T~Iv6}E;UE>D`uL8on z-nj~h4d)6I3^&%q8Pnr$nqZIbV=!FR`#5U}t{+1CAwax*3OCE7-BkKr?jzts7t#^;>+m#^Y!_}8T)Wt7T<(>^&C+d`yH&GA z6=;{cRkNj4pqo(`=(Ja1S~XkLlXkINHCtbF#Jv$wnLN?W*5Qu0TQyr>wa@KowtOh2 zcI1t^%@zx@JubJ|3be)5Rj+!y$ts`-1&3U!lU8l!TkxY z8LrE`wD5P~u7|rD?j^X7;m$@2bT!;h;qHaw{I2GseAo`hroPBJEL`B*^113r&6j84 zBW4yBd}u~I8_O#`ehtENE|v*M;|>V^?SoZe{>{fCEdSE#Isf*@(kH_d&hy9i&P(sR zw|UqE=Ofp_&GM@6XjDS@wZfIokU z!1#`e6Y>0#8xZWb_NTwqgh75$1eePIqG;~YmhFsqSV>ZPsCLc6MkLa+uvL0~4*WN} z_m}jPG~F=Hq-U-~+d4fXke)nGdOU!h^jw($)#)i|y3PUq<_Xi&S*J(cxeQgcg4Tx8 ze5IDWsm^Mbbti}5_Fk-nV~g)%sw~$#G0V(Csgv|?)EhO*uUAw4jxRJMvN=*^*o4LT z=-YT9QCg~Dn6zF|~rY|J`0EcbltrP%Q zcjszOjUi4Wu`I{LZ-~PMK;KFL-Y!Tm0s09*p0hB?xeCeWfL!5=m&vUJ-PC5;y2|?q zaOcG?UP{1A%gFczN?i@pml6OrAM4E|zeVyWeJ26PzcA!2AQM(DC1?xQhXr|t=XQEE z3{ykqmO4cf7Ye9cRKVoEFXXwEs;opOwJ(OoI6OUF4IAeygiX_=F=`a-$1~8Qnh;TC zlT7|-!X;|hG#y)aRBWJ}t42-NLG*nJbGxgu8+DMgp(ogujHT{;p2I@j(C2OE^@9=C z0IUrc%07@QsyV}mo`r+A$Sh0VR5djX_Oj%!Trm@?zvHha9HyE3x9O8#&fy@v{?raW zCtAcW5`4ZHd|1X{JpUp05AW`lq}OP7OM98epr<(v&$;;HH+&tO?rr^&TUEzvQ) zpf^6T4;@a}hyEB_vtggni84q_xj_^XFZ&FrI!>Dr8>+ZfHv+%Sb(~_l0(}z`e6TTU zwWoWa3VcgOP`2gdb7cx9&flk|-$#3qt~`^Iqk<4 zr^NU1&yv!w@0CI!F6VTYdFkqJnaP^zy8NNni;zdPuSR=f%Y$FtEqQhKIOftXO7MF{ zH7CK;z;P5+-i!w3Bs4InO?lk0)_`|eXQRaT|3)hhvMss%jJ;!{FZs#Bw_SJ>r0^z5 z;c=er%}P(<1tZpy2~u)oatJAxZdz_s8545|!00Kks;{{03Ay zm!`*ep;#O0I9NCfeU$fF4Sf&HBHW@KDM(5}O!cKcMR$irpCmBOejD~K+0av+# zClAKJcH*X(3S5jAu=)LnoKFfsz5{S7)_h66pXBm>B#*UkHN-nX1S0Rm0ODpV%bJ0! zka$7NKMltX5)5Miiar~qNvskQuR;&cuV2_dZOB)UJQ`yG_B=h2%jC@!0QB|z%DK}X zXvlK5>J z$N@uNB^238+h}kVS<8!c%2wb7gOJ2}fib^AA5w!{k=whl%Imx+g}fe79AxP9BrEaQ3Ioar3}84oaApYq2 zIy`%pM6~Q|h%*c&ucLjN*{Y+(QJ!`#e}VLDw-aJC{1RfAyI&q*TW-Jms21hp1aJ(% zT9mLO3LDvkC~S8rN&)IRMVVW!xg!pD)XP1~aQ~U&lKUc^)!5f|o=)dnFZ&$B&P5Az zKZ#KQxBPYSR}FN>cU3=?l6MAMEcn-S!JF~j`Of(6MwYI4>cI`I?zcU_OCBFU%V_UB zoUnJ^Jvqv@yC^2!s$+hzM?(L*gv)SKHnQ*UC_Rt;Bqco*pMxi^Pn&^gEBW+z&#@1F zQ!+%`@-erqIu?K9{<=AqJVWKG|FQ%d@p>+%rN+k>0& zIA?~Icsk{^yPfiK?7tcN$vx0XhS^Cz2*s=Z$8oCP7PX5ei)|YBVxvq=S3CE#&}cCx zX}kptP1bCfJl?h8!*rTFwmDS>Z3@F~uscccbf^|+*U;dx--PA26wof$VKXG`FE#BO zI&5bN%boJzJJr}l-c@G%k#HE-3M6iAH)HG+{fOK@%w*whu+g>Cj^CJ)g^r5wQ-B#? zyZMitzpcoGUpV>(DR8TiJ(dSF_Th#?*KZY9U?by5s?>mY_C{0*O^w-JFK zmop_t@MTg2cxyj`6#PzY;M3q$GX9|?e7v1BzP}CpFl=Haf#`mX!p>EFHlm`PCoH2-|hi^C6ltVir0p$4Nm z4N^bz#|}onFEvos(GG5oM7z<c#q*&HMM%<*|d6Thx8a zsH}G_#&6X>2J-%2p;$thI%ShNuZ-%86G-GGMs%>}R9ugET?BNVJ@(tH%CSGv9Ni;z zOhy01+2e0uEw}pjf>D3$xNg@DZca}<3CICDkCUDPxX~Us(#KTz6Gt`wg5_h=IP$0_ zaSzIG@_A+)7^WaVECt{(+yj&>9{`cKDl`oX5`d`0f>gVTsHvwPx&A`|I|1(z4<6|v zAj*$;m$`UJ^0hih%EcD~`T;oE14z06;x7T;#lpBwF*b&J5{ygmJmrDY;9M~wUICUH1K0(C z6&^s+1rU8R0YB-PNHR{4x`2_?By*{O5(WU+%L7Qd07Cf*xY7m0@lgUUl3$F=QZkW~ zd?I!QV#7E$&7=z=HuDp4xQmG6qeOI6J1teUry0m@fQ)(|Nf$^tbb#?OHcUIJZB)_0 z^rvES+)gorz|8Srk}jB7aiy3KcrcCjIGC-DA#=1y%N{Olh#4fb~<%A~8e7f8d=9UhhFkxtbpHPh3vlrNlmly3HY!tnWLk1Xlow#LF>g z(yIi(p$sI7Mb)bSz=_XGh8QI0XiVohBspq`htRgFax5Njru#_!iojqMnlMp~#`*zc{G%pZqssg1AWfQZg}SOx2e}Zn4H$W9bVLWaMH9|c`KjcvQ58{WUsfM|U_xvMZf z)z7(c)xZj0DJ1;SbuvmkWvY9Ph#IKs_zfL5pN5+2_E@no_QA$}Jnql_43mdUIf`07FI>AVR9gdS8af#n>mlQ|=>IlA zVNh5{;zRt^|CG`A&T=DY2|kDA+RtMbC}!`$gyj_siOb_Z4uC0VP=#+iQuB*8S%w4+ z3(5#cdG_o!*wopoj$ij-CFFACYi4uZKBgQ;H}O^Uwr^tjov)L%MeEy6M~bqrKu%gO zojEsPB%Eoj;8si zlVk-?iUw5Y?!J21KwQGOtj>D*11U%T{{K?`(^{2(a~;3`n=-#GsOq?RK!pl6U}ztL z1}u^H)6ud)tmWjTSo(f-XY9ql$VKyg=t4}j`2Fd-=KIrTgo$>ys`*!?(T-%Ae9jTG ztarR)xXpt6Zs%)GYrIPg-d2Ohzg|3i!g|ku@e|hT4w9DA#kg z%{P^Xk-ruhkdC~B+>?N0Jl^k9-u04_DJhdj(-0)ShtV>kd=F#Yx1^O&oiQ80Hw3f% z;3@x9d09Isuhf-lzEyXPRHY@p>HX*OuAbaV-hF^|*9*>e9WE1)Ynt8bnAl2QX>=0up04FhYGhI*cICa;h~Ts@Ur^rDt!nuaTFp1M3h8czc+(@Zw|z;H z*PFkQRC$@1TyNucCR0)!>5nfpd~@Ab2zA=7dy4sd`^}ntU9PAO zfS0Ak@4lA2v9z4#E74qGKU75Q$im#l!3B#k6ika=poaS6Pkn@uL;Qs=5PbcW=r})r zP>sYnaxnOOr);%G`cx%`tJUxNs?gsCvHIsrgZa`DYDH`xf?`Mzj>jIuQ*-Qn{K7Wn z+}sE)Q_jsT;`yaa-Bd=#wc;z0=_=k#u-(4y6l6L}v-Jn&S53$f*>pU?Nu5D@yPnCJqB z0)uHlJF5vqc;u=DToA=mj*2n2@zh@}pvEJT!sZ214%i~LXaQGAxTL=(N3NUBY3e?S zW-$g#;D)D3u+4yR3x_Znw`+R}xWk1-0$XbkB%edY{ofXmVMs&FAcSI=HXdhMT1MeP zb7Azt#23Q+UF|WXs5vGLkhF;A`2x0%PMaA1TbvC)0_TC-w?7|vd3$4|HA5f>oYxd* z8*U64JjiEI#fQ8i(sY=GK%`CEkS*cn4e;;7r?-zZf7HBx<-u&iQw$u&odmr5Ahpv?+od4I|Bh+&d) z0c?Mh{m==dni-baW)X-F`H?UAaE0~@_cB*FTw+FUmW*UJ`mGpWbT;;H+5qN0_EIw6 zyBo|ZzSU$TavfR8H)dr3Iaqyzk4L;^lb@7D{3(`*%p&Fm8am75p}QV2c5dj*>ZznG zO5-b8Bo3pVWpqPF)!EE2M5Oo&6wG1L)1?Gj7mY@7G)J&OWOFAh`klFWXefTtebFmw z=4GZ=_Ed5h8nz`$%CO9rVLvkj^RS*=SjY6PqF>nzB%24I&kI2J8I-mz(2{-M1b2>Q zk1fN2>Xu>m*Wt0p1Cz-L zf9VWl6T`1I;c@=jw`izuB0fqw5+o8(j%BtC``}=c08GrWQc%v-5+Y172sn662?Eh! zl7ejIq#!O?hJ)o9KzWQPH2lHoz4ocVJQNdhV)ZasFsVSSa01KNE#SCTpLXv8tm#Qu zae7wx6LvPXV3y9Dfkg#k+Xlo56xX1!6@K zSmwk#$FKUtdskrX7$j-L$7AGmJVsBZQ5L@%?e7>Y*i-OT&GY$gb@I!kk>gUG#%{o> z(pVOI50_RV_nY3~YqSM+LaLY8DlU8~%t;jEy z!9nO7P{eqLhQr6~R*AI{2#M_uxS<-(Ni2PsSd1hI#}je_M_m)?V3yLrnZ$A&q7!>6 z;0_Kv}G}s(ox+6E+;u<5fUi4OHD!#N$*t9UnEa zj`8(}6$7nBTDwam9K>hCgvO*zZQa2pkg`x+MV5%ZYEKQ#jK`^tHHXtHOLVjnq39;! z>_KVV`ax|QTO!H^&wpPDPg8Kz99?i!tG)2qKipJm7MW4?Ar#U{fL2|Nh6G&bO@L9v zic{cmsv|us2y#y^I3l4XX2G%1-jCSjw$+-=5!iCTm?`=EbB2BGXvoTq=Z^h`Q0UGZ z5DJH%4?LE1_*v{X^w;(q{udi|lk7L7<2Q9ff)1%b0Z|n2@ zw~Rp4UKKwzbSDorLB&@;nWW-!zQ5n>?=Tbh@e3PGlsM=6cb#hpAs7unOxf!0lhX40 zP&M{HrL!cOYNg~udaf{@sa8rZ#umgD>@ha^8BMiPaufY0B>da7QbG-XkbL+`-aPRCdsCp&o~==`#kz^)YXSiNjBv?l6>nte6PBE4J4a*W8_=v z;d{vC`!&fX-WSL>!^5}0<@*`QCf*BZ*Ms&)biRgaf8g?sCfUS0nS5CuzRO&`3rRNd z-b=m%ZRqO_kEexa!aV}F9nOXeJdqa8gDZiX1GgG(E8GFNET~%qZU!9JDAXP-iua{lig2A}f(k@xoTQB~Le z@FX*V0Vd2S2}X??b+FiA8%?yK0}>>}AOR%H`ISu9d_W#|#;0&pf`~U7=c&b#1#W9a< z&c?J7TLbxTwe8t7j~jnC;%_nM#NK=S&Bx#U_-n)8C-}<+Ki`1Ah4_02e;hAT`~|M}`ppY4;o~|!J>IGEV>!3p z+PHX(ynW+HYPh@1FEAW1od_QsX-yx1_mO~Mdzg@04Y(RlS6BveL#ymlpoY53oCx>~ z{uP5|rvd_LhGXDr0f1SD_Shc{T3~g4x$IQX*~dWP|&<8x2 zOU&I&vy{4*!=CS8V6@tmzk`>RRPumix?w*H1vT>XWt0l{(rga@a+=rw$P9*rME{+! zmN#|tkrltUKsySL#D3l9EYC(u`KQ}Y9cqjdf#u^Zy>HSM!AB!^wrZ(hF`J)3s~IiN zawuQLA=me1AlMi$=t0Iy!uRe*cr3R$zG8<3#8DWJy_6`~g@wV!FD0$Bv0c)p7k1^h zSHk9*^*Mr%`D82km$N(EoEyJigIE<6}=%+mwKvUEJ@jy2Ul?} z-mNPI9KFzgt`cy1ISu;(7^6$*zd!}}E(`u+vehuT_3ty7C{l`W(fsd1Q(9a9<`dvU ztwOQ;@YD!Una~J~j<)avhaGbV7d8R1zG_dyYkp%@175LhFo{z1m?ny@JP}N8D@&>W z30w1~^~X<>VBZDvU`uDMjfbd`2`+a!VNG^Ev2gh~jU*d8K_x^O()t=fq9>RpsD$z@ z|A6bP`uk-)8?DMCDxB~ow)Xc4L4NT7N%70oc={Tt{b0Y6{K9o0v6qkRLqb}v1JCKj zBdf-YllJJpmG39xzC~j^j8btG2Qd(CpgyJRIhajgtYSbv>L%n{qJOxmk%1kVm4&y3RJP>_7PU^yv@%V#VpmDau6jvgH{z*UsI2*V0@uEqv5@Wa zcK&YM80R9aeVKI#F`sSd-!qKO%t8@xVz=%TiFyCTvA2GOZ*V<35 z;6E|Awtzr1m^Lfoc>yE4(Fn%CJdR-gUBFamOdgOy%6;ET0piyqB6t2_B)J11Ig;jq zocOF)*O%+T@o6MRz)W>x9c%NLj)$Y#0`A1(tO>C`b^StkBAWlCKS!w>15DhZKQBUOPdAk=O8JE6&)p#vTaJAJ#myb#`r-Cd8uz6Gh8 zX>n<2n+MszUb&HV^MQnPSGTG{dmU+^mY*iuw{j!TQ^tbuY}Uvsid~K;*IJvGaJ&Lr zSaLksw|xuQD32r?lmGlD+`dKOgyGfvuRCGV1*OgfljzM@9UzS^R&U=zeoB*Mu@o?b zQ=8233ldWo>kQ14?ORY6WW^Mw#Dn946uo%s85eD#oFMDGI}3whDb_fjr~E*T2=eg! zczhogJ%QN11>RiKV<9)oDr(txHDY=*7Z78&Od`gfN+?6Y*s3Qv^yN~1JVd%`DWW>( z&e0T4Y6xsEYy3-fE&5`#G*ik}ERm>ni6**>r_^AKVL)Q25SR&gCu|k^)v4iewwD-* zQpRh)@epJBk3<^fzWqUMkad9}TEqmFggzRV+!(YJ4FF!Bow6zDFFNRk)J;KGf@P5G zn$%4}VI6i!>ZYJ>9fr+5++Crt+2?xn+f!}!0g5%Gi#k+`4zh~;i_>8KJI(iY;06qo z-wSnNi>asuhD2&5^p2xwH0f(|8(#P5UPlK~?q1L^Br+M7P&GBZf5B2LsvIk9+w&>Q zI~pj*Mv75>C1Y=BCytHw85v!EQgdVV7r)A+Id$gRgZ89f>#{)vq+x?+|NP zt80f^e%bzMdcP_3sa3p;g`TpCUwuOrPgR~>msbIj7KFN-D34=hJvNAw7MiHu z)0TMjj3u6`JV(F!c~Bk)zSPnlI&Ep6p0PB@%SPc6y+2EHnD4DQfs0T#hbu3Z?#1pL zNzuqAqk1MUvZ00yfbBDAgc}{rUAR|HcDXcn-7)6KE1nwkjX#-VHQ%4Bch;OOBLH^H z(0^HW%zUEr%ptGoJu)5GfSz&&cO?lTUGvDcyue8FC3J>x3x@}XUi9*O2ZFv$I_g&BcjfdSn<4$<+N>B#X)_r67j8#QN1=yFl6kpN! zzAG3SS;chG7Agw;W8V96UrKI$EKtf-C00Q87+wVOP!oWJSMh1N$pi<(n-{3rN@BzM zP9(~>bAUF1LONrCot0d3XsJH24s(fU8hIw50(tkqf-2aag7a2{fI==dtmz>}Xo#f-FR9`T(XdOE9w3_M!=86 z1`nhp(XiOsy0t6XFO{Z0VV2sYrnzobintMMvekh4Jj^6nhNgL0hvit!7c>!7oZrgtoanD z;!eycZj|}d*tY%gxY0;EA8vx;hp&+)!$7tnzpC*=Lf>vqS&{Jhdn)+aE9mY1*zf3% z{h9EmA(HUw#UAHhFWf4lnB_|s&13jEWJhZyt!4u2X{i9dh;$MFwhb^H|cr&;#Y_%r4- z_@AnMvri45hTK!bXUwVKi*V1p50WzakN3KwZP8x1`@J7M7&YNa`t`ffl%bT}@eKWu z=3&y4zX^Ie;2xdm?f;+ndBpclNlzM^PhDTeoT|R$=l_7VL)9C+4>e^3{DXasG)NPB z{wA=;Sdhd%(XXr3fzXFNp#!6EZ!^Y%HB5kmRgF*Sc{L&DNCQW#v_O9N4+kJ_!!I5N zyoAHBG7;Aq;*@*a!t5)WL+LHyfC*W2)%FbjomZw9AQWOF(T&f4N-Pt z{LjM|ZJhjd?F~^P;VmyVXha&4LW!$2q#RuuJ;Bb$9o6u@;pha{J5Cb4tde=f`SJKs zN$bMchN}Yc42Oy^RS&CYfSB6dcYv~WuCpMF8vri?$fzxHvoq9=tCpiBj>yf9=nO}+ z#EG!z3@2U?7^Q=27p~2F75v!|d5sRw@9yPwbdfdtQ9yK!1BH<%GVb&!33@kTNMP*z z$Ri&jNkS!Yp*r|9Ld+4BXJkELpdG{hF`z(Jw4oH=kr1ULzCF?Oq)RnCl00{%tF21f z5Zx#%;ym(7n!EW+&>^zMDOgyZ%&#LT8e&i+b0fQ2`+G?@=cX=1!XsOVc4V6)aE($2 z(59qrB}g)@KCgaIlD@B>3-#z#^O|JSb41l`BY1#=;28~~A8oGy5O~qxJsKLrqE!TS ziH3^h{m*y@?WAcUYPWnv{vedtFRsy#WLY&}J2vOw`ECaF-tKD&F@x2B`|u;Wo}jjS z=~s+69eR4m%-P+b^JFE!3&KHQ_asWk{eM#L=o+Tg zxFi90PqeZ|9?3y0*={_mCG!9pNwOmANyli2(fa+pFOR(92o^-vsO! zaj1jgJ6DA`Xh+A7iA*X^WqibCsWlXV?{yv7nkF2t6>CL#3YvN#C32%7j<$MU^A6}7 z4RQ3c<~g_`Qp=S{2lxweB`_3p$ccoA8NUeue_1L-ktD9qD0fsu5h%Rt6@lS;L6KXL z5UUpn2?s!AxmO*LzQi<<5C=8&S?Hs5)EvDP{@r7R=8ud*rbIU%tU~-`-N+dGG9M%B z{*IbI7~K;wWMT%v7o*cUY+Yjg^6(cn2s8(CC%E9qRtJL$Qi3AwFo@woOoRd*J(r7g zA*=vW2{Lsb-b5ZjV&vWBz_UP$Soo>CBJKNZk@>((K!7ZxA|dh_;yn1vii~)eJ%HE^ zrI3^BMP0U%-0h)1r5n=9dJIxKL+_^trV6P+;C&gZnGbJ?sidCrA$X@2q(IP$j~M!M!UG>&71HRS}c-3ga@1j{;Ki%3LFRN+xN zHK&ejT|dZd%Yra`h$T%ki)e&uA<`t4-}Lf(D$J42m5ske3TzA=z_l#&FIocI-}_QM zBtTmZ0@yTvGzZ3jf#%Nu67x-DvudTr=Ny$WU54K)Rfz*rp6_Dhzb!}k9qRe}09(C4 zk~DtABtwoH@}*F#;5&11c|nezUCL0Jm|XUe48*D}B&KHi2w=R5kf5Wu8eY#cggC0* zkih1)JI)HNfXC_(%n_Hcc0IB4)hek2mSRYkX$ivoW*DN&xO>v0TAv`qPMk?CYCYPA z=RF@%5A8q)XgtQP0`AZUXk4|5q9weJPv``Cai`DJao0I}TPS1_LQ+KjeCJlDQi^pms&$(RDJSEC5qW7(*x(O_}`)?C@rm5M9R}=X)iP zrH0sda7uhTii!L>wI4>;u?wnml-;*`S-Sda??5G!m$|SQ?;E=VMQY_Qx{g9rj;nQc z=p=<7(~W}itwA?(bLpL#dXo$J1g1x%@rV)p zUr0tFf?7rLf(+ch2V#IIP7sybyj+p4cgTX82<=*6U@uWz4;x_3om!N>*S;?^!d{%~ zH75dcXq&?-kce6Tz|D?G=6)voR{DFTu>S=Ikpa@q7XN7P_Y%U%Cc?>yq1VPuO(v4e z>_C;xI@)A_@_)!3fkUwtiQ6zM!LFaiV*|ltSgeA7gx|~qk`xibcdN*EWV+;1uwYF6 zyQ(JCGq6@g-S<8NlEbHT;rnnz3V5?X=;(lVQQd$0F{=yfg1h-uazvRUyn@ZZxD|bI zPfHxuYb4?)eGv~yMB=>6ioS^D5|KEvw6HH?zC;iMmT_KR#AHOk6DlXo-6RWQWktq> zVw~=5OvW+F#o@ACqiRfea-QNzPF3#UEe$0|=D~@P;i@c^mP{LZl$}=Jd$LAEjCueejXR$NHVv=0(GM%#kQhT^ z49O9@O08MVoa$jdLyw&z591AqHzZ!w6d6?kRW`<$uuM%Y4%g%wlb41k%u<8p*lV15 z9iCjr!>|3$aM{Svc28eTbv4yF;mM^&RVmgojqwf`yuuUwMx|2?MgAIx0U_6K+Q`q{Y&CN5yJ%pG@{c70v!i8+mQ{(sK;ID(F5N1DtJQW8R5^{o7DbiNtHcS* zI_RoGBwWL5ZH9y!@)^5ODWOw{bV5Rj7s^P88g5moOsZ6a+x?u~f}j>!9fy8~SPN`*m*3b>6(V;07Q6?9tNg|UKMs!~ z50$Z^-P~b_-jx&TNyAT8V2s*LzP!q~QY~^Aw>j8`mCkUviwt@tZVI{0!TUZmlL>Je zx>_}rVs1?z36KJJgsWzS+FU2)1K-ve;Ex0|6A>Q~p;|a0U>#fGR^LHblz8Pts{+6< z@I#ft*Gjlob9eNn;?pyD_g}&Iny*tiU3Uk~&O)$cN8#L0+EqB-mDVlpae~~^BywES zP9V3G$X%*56A80KZyM9^I`#_p=b)KNbBgd0B9X+p*^K-a2TCc-8NtyMT6EoSk8_c)FPm9W4mxk|hZo+;C z?oEDC3k|aOke(rXloEk*)R3fV4FyAC8+M?=1|dl`Um1U3HeC-xvRE?WRrAkF;F*Ed zau@8u8`DbYo$9iMC)qabP4ldKH_bEWy)@5p{N4Lk>_6C_=2?TkkMW1fi#tzMwTg1! zs44uo)|imhDRb88P?(Gjw1*fQsKyx^sK6PwI*fedR+n+BbNd8OnlU-YSaSfe=6)C# z053QEhr{TLMu@uOMk+8yNJiTb)sKzn?c>5P()uEH7h?nM4#tLO<+)LwFUhk_p4;Tf z39zw&lVT&pX|l0F)F7G10#}5Ly!D@_0$03!{;TjGTtFcF;!AjnPpq*)EGgeJHgHa3 zY@h|j*g&g+v4K-9V}nqrwe437>16eBPS-`LB#y^Nd@)#;qfbTSpnU z78th{bxtUj(k6_Nq9&9Wx0V_c%8gqqAgqjAryIAcHD?}gzdYHjJchVTsWNh8OO^e|!77K> zl(KQlT`E(mxmAOgiW0n3l;EYJ1V4-EH%wzH@`ssR6G!Gg>uOG+RvO8`6tyyt2n=M>az!V$?i&D2@V^Rr zWr#!HNvfq@bg+lI94VDB?6LEbNcTm_*gV0Z287o<3FM6spZ(O%C>lEz{l8Y90{xxI zz!dbKO9ZB%uK-QZAF197D~{9f+)Go}i5{-)+|!&xI-$sFt9NX`pcjdw8X69f*$l;J zzj+78R5j5n>tCS$L8# zsg}e*Bff@lN%~xg!{B65%TW7CoFR=nDg7I}gx__{5YjM$61zFPs5Js^vZH@W1{#wc z$)vwy(qu7S=)sC=Rf0$s^_hGhA>YH~LN73*p?i}R7yD@+obUCaOVy0%vUi@0?tTD` z6{O&rnk)rZNheN^X(iapKaq9`7kGyXIRyX|dMM<4^B!+LqRG1~>&djB2R71i0a21+ z6NSu_o!t5rdh##-JI(XFiG8_f#9+7n9mv-jp zZ;Va<#uWd%3G5j2RQBpDyXW!y@F?u6X?|nLFc947+m4mahIwew@xGml4qeqYr{kG4 zoPNA|_2Um9WoHJT;1F8}<^zuP{IbT&y0%?IJ@M?4XVP_u^-FaNoNA2dv7{o32i6B6A!7zM&9l@I8bWE*@LxGb?YQqb2&50=iV+Wv+ zU}5d%CREduo5Yg^{FVjeq$Iu~9e@MZmj|va51e0TXDLyvQK9@=^PqmQaNEbM@AeCN zjX^v61--g}>>JxQJ(vmN=WYy*$YY@~!7!ng^3#5B923G+zu=DBcxZEt)ND&;WW=n#6nwf za)E+Kz!x$$GkmJv~Kot{aI_y zO@>QD6(qv1Plh`lLw4R2E)DqoWVkefE%6EYvgOk>&%5~h3V)Y^kcmctMuj6hVRkFr z>-*f@^ka0X>e?FfFL1Qp#HE!N#sNWNYOyhO*(Muiid*m)XRIROj8)t4e0j#`qA?q7 zw#V8w*-9RrXw)n)9-`PcRumW)8gm@R9H%kIWz6vybFz#%ImVpa&aylJ*<#G`8Y@N` zb4D3+W_6a$gEx7Jv0{E_*_cPcuAOD2(>87%*I8En{+-kDQ}Nap4D??&@9)0Ovgy?6 zjDMbCl+_t?N{l%RJIew(|I*Hu2E-ABv91Ykoh_|+3al1(Pl7Wxua+2gOM%E^c|Nos@5WPnsw-pyQ}0k$$ONX|sjiR-Ov_=W*?f@CdY7@Phlw*wI1y^;W#B44 zRdy*tUIt6rXcx>Z$5&Mir!d(Scr~8#Aj(+h;?wUkp31_D%Jd5quGiz54~Aqu~zR&HH4 z2G8v+WARK=<=7%sf;R<#vSCh@c&Y%%5SR5%*(R2brZYDCk*ZU?yl?sSh6$ZzMJqsL zbF~YCV_QLI#iAh&hWjwnVg9wC&=W)i*_3fCwWT0wa&kuh*+lJ|R zjT!z@y6PNUkEmA=E30uul&jjz(hmI;a2!TO0X8x~^i)4T5Q3Pyy5~adF`}pS@YRtctA}6+dTO(tM`h{Tj zg>NfXwQ!+yJvyc8R?ig?)6WC{l!75KO2yE^QL zGf>vtIinvY^9Ba_&CJIH`)XsYu*X_qk7i*H^AHwIk?E${GdpHuzT~6~#dr56geSzM zK2MsO$lh9a)8D}Z>T2pHxSU~L-Fy^g=1)La5Arp~jj@ahjNcSRy27^4H;5WzJWQli zCgZj@YR=5&$29X_u-)fz>N`qu0!Ybv`NY1Ko3K1dg$HVdOC3zS2M_B~pS!NF)Xhv5 zxGq_$PFrImtgeKI|*c^E-=(8x|M~-TuKTzq~?j*pk}Lxq|wS}tyKeZmmpSC zF4Qo~1%3ck24v__bI@82wpRC0#3Y+aUWDYJS=P!{wslh%6m?M?p=BLFCG^gc8r;iu zKSmhT4plf0;~zehY*DN+qY~ld=AsU*-TX!;m5~0$@jPry2>y^(V_uhl7aR^hk+XHq zpXop-tT%OFd}oiqZBv2awdRizQ@dR@P}|H~B$>1M$lQQWOEy&D${O=pgzIlD+1MJb zqLg&8Z~n{ogkx*W%XBd-JNg>R!V~v7%XQABHRhqi1SWm-DM}9j10hF(SHWcNmX?7K zL$xcL7oc-dELGtf!UkV(Ju8}q$0+5T2B86U+C|%AnD@!5-TAzIc?_YT+ZSVDyEw9T z7Ge_qHaruhXk#m^aYlGL!ei&Eu%9_mwr|IhLHX?&jEtq{x2xwW7$M2*2LUe6kPzcR zDe4p%cBp0^0XKG-2gTzYPIuD+tQd^V!l%3GX1uDG>B6Jl^1Z!iSm@=|z%ng}Bcu!8 zIC?aeuHIH_J%}zmqFya*^X&@csw>fU8ET0$mR@+ozTLNV@jx}n1;3Y%kfxd0)mgsn zaB%Rtn;+yCa8y`Bv)Qo!U4HKvU_8ff^`JaE@{bk1fgI1}AS{6CDN4z}A{*d$+4uO~ zzWsctl<9=DmMJC}@}o*zzU{#`Mhm3i_cyXW#&ab|U^F}A$y#gTY;P{bJ3eOMr+Y2x zjHdF`u)*?HN}Rk(15U$Ie91KRAlsp-O}FkezjDGS3?Sl_qtr5QuJ3rD0&SKBIlrg9 zJN@$w^d+)?{-2cd>SdOq0oZ#C{e`slJ-DV;z>Z~OE*g@t^f$-jMl%Zn%p41*=M*qK zN-(!z9@O>VuzE(Ah}Y;>$`Is{A3jD_Vr;2EJx3Z3lFhm)22iun0eFiw{S(Fo_25Wi z70$xbw`b7+;f6!%IdVet8!%kvEg7tqxNZm@<9p#7YJfN-c_FQqa)BO_#2IrH!Y?Q` zAitT7LTagK{yV;o+iyYdzO1=z$t6mfOYN{9B~4Y-evGul3Jz)c1cV0ohIrU_kM9&9 zXaMvOL2Zc(rUax2HYHegvPO@A-MAtvZb8T5SN=}>(eRjb)vm$s(BK84#Tmp7soD(* zjE&L3X$H1)`r13p_RimG2X{-$v!>F{XKWp`IPM^9`d3g} zvmm5wJ_aA+gWc zMCc&81Lw^l)#q$QUfM4DQqBNpWD0g4*qp3zIG?4r%5i z2LyHKPCWS2qpmLkj6t<#$8bTl*1R*BfGSGk0S>D02KcS_F}xJ+t*LE%nRwaq5|%X0 zM!pVrQwxHv*-djX(qOienBA=9w`wj*gz6c^T7K*NdUm6lyqx75Yss0amGm3^OPMU3 zHF~X{@>H9}4-1XV)gzdh$c%{p6;n)kbvJ#Ood|vbRJY+yH+R#Gc-sW4A=LixGVikR zbA{`)@+t^T_l5YIDp(q^&bHSPc*)oW_L-5^w1v zabs43#9JtMj%RIk?zLRlb(%knd0{QhIH6&4-e7oIE zGx1@FstAAUaz7rUjJ2Xqtu04XLWf!_W1Q5No+=|D1j~I}mpJmb#s=nZ6+IQrn^UFB zs>OSex{^O5`S8~Ir&Y#lDA=-mU%{mbljBn;JUFd-I?A-~KBLMs%7Tz{3J5vh0s=H4 zOTKGu8)q?4Jt|P5DtY;yr&Xn953o@WX5k5g!h<>ZbvKQ~@w5re6v}8v@U!l0o>(g) zoqcg2zl6w}sC@hOxSI~+czP*{^)Yd6H zOAWsJO~kI0P)@KYFZA@KaPL^M`4)n1Or#d zdp|bjAm?s+3E)Pr_2NA^(r9M8CP3o=OvG*CyaNuMRvF#*F}=)@g6AbtrXpd@P5P0;S5gXsVf>C0aaq*w#0Rv?{hjd;&itKv z!bbHzFjDQJhd!oeC7hbIDVcduCna6gpisL@r9s@iqwHgCXOXz~qp`-O$?Ls%Rn23N zkRtY6$ZRDd5uOm+lLORqDFYNk^#Ju8C93XJdd!evsyrbaQ->6c8JLP`(F(C#gumj> z0jLhO2PpNFdSEu9K|V4bkP$}ZU`XMhLk*5Y3XHt`xhB2e0OLpw zFo+xD`5j^jPTj+S24|z3IMTrCGJt3PaF|g83gbSAY}z^d4LQckejsbSOljC}_&LgR zg0D#oKSTXB@yanuTaIK%9KDfaio5A4JgZFu=iVZ1_ha}9y)5;2H?P4*DqYdG=Ub`+ z1|AwZHSH|Ar4YLCjnj?WeZzCH@gU2=$UKI`PiPeww`H{6gu+1}8R{!A!i$~(!hPEz zd^#xGNXkw-nXn*dA7T5A$S{}UK#brpjL2a!TcsY62iC?tx(R|8oVG{(0A_=4*ETo7 zCFq;mCNO;T%c4$kcR)d9wOP7K#(g!x1v*fZ!QFHPpk1C4RS9OJJaw&X+v3(Kgq!bO ziQ|lF;GD!VaubO24CSVuY?9Rio@KQ zhI1&hqC1>+Jcr?fOO-z)PxX_2N#jA7Vi=7UquOKH&;OF{ll zOD*a%9Z;jO%m>!I6qbck&r5SpnwKu~j-pXuF{s$jC~&fd16>8CeAW?03;uRrcY4zI z*=_m_JU}%^+iuQ4er>a<$1L=_u-RPJ&t@~~6r|6=B+8>+*5toFh5Ql(qmkHc`jKDI z<2a?sudPB*KTk*e9E<3xJ%6hnwCuYL8N6a8zC9N47herT0OC}nC-Ee&5Dj9d>;$K}%K_d_ zn82iBn>dQFX1?~@tEmR79Vt|V#>>g5*uR8d`v_QOXa+99ECo`*6HFRwaSImv76VmI ztd;z0m2w)Zmlb;=?-n&4BXWyHJKrpLer7+WcAcnRYb%sRXXyXv3e7gW_J5T?V>HmfmMjh9 zh@j%c#6K}jzBSt1071)EtYe+JlMRuy?+9&nqbCk*q*6n^zk%9O4TjZ*LQ zYFrd#{#^A#<7fhn-$PAVDW&5c=RmUf`slJtrItP7Ziu=>=|w1AQ>N)dxA71vtTpt&?9pP{3RY`T<$HC>5A?(e zvdz`Fl{}(t+B#J0+xdG8nI-C&L}z_4mz_u!0PG39GAv;aQvbH^84(9chI|-n8DXr= z}OM z4jpcBsEq#AmF9MwA&FUkfVT|W5*1|QWs#} z@SZ|h_}%4}vM_q>7*OF3xCe-=(M$QMbXW>HppV$hyi}Az`#((}G$93{w7^n$AfN=S z>-{sXLpJaI4pq+_)nhKln;NbnxRbiJCO)mm(&DwYt{OST!1#8FYNjHKHy&`3S3RxX ziQ{uefe#DJe{bnW6F8nFP-sKu1Wg?7rr*gY#}Up6_2ey=69R{qyO4(;KJEqof^xphcJ?tt3&yp`8svwU}fusm90~(Y`*O)2HSV(33_Za z)Qyh(UGOUr<5b}C&1hho%CT2DA*s(d7CQ9eDM}%-6Hv0Oq6YV=W)e%5B$r=o@01s< z@bp`uOpNv-jCtvoyx`XBn;N&VQ4-D%uvdo;6mi9WOzn}fB5VN;6A`_pzL{(4)rB8J zk*u#$_p-6UG*hDY(#WbssO*08n>o-LR7591Csa<@Dloy7u~pq;@m-d>*W$auwN++w zlNO-5s4ZMNnCLSXJ$#cYE1DhJf7~c5g3XRStgI+K@PDvX7FXHA%px$MJGo?p%|ie3 zsC(!@fN>Uw_2}_6hnh!D${?#+1&OYJ8)C;*S)XQJjWbawz{`q4`@fdLUIY*n<_Oe9 z%d%9BOO<)lD)v~2nH(uC59Q{nMkx)(MwHfPdi1i3mz+>#H_Wm*eyB5@clVX{m6SF` zmj*NNDo`_8mWNF#swP*Jc~K>=nmQ7tj!f3JK*}E#EgK~-#Yj_>tadSXr%(5e>3e7@ zSOI8?A}xCQQ31NI-TZzEUPlG5=YS2U;0a97g@?tJ3{}-cK8K6zSR^?C;E>w-0pDbBn;&jR&3~3paV6#ZML3_o}@$zE^6umwL~(;W5k>$lp@Y4a5FH zxf}<^kzeCR_JdnW5Vi^M=;!q7;23b|t?nhXlsQ~3dIE3zzHm3~hGwyeMi?CCJz$(l(PZBlS(L`&x2 zFBgA#G_ZJY0qb!YWjOdx%yEICV+n!jFLBy}1LXx5_uvy6?QrNk5Ma122==3=+kYAQ zV4!_#dyk#gtUPc<9yU)mexPG_H|<4e4uI$0g2OAk?xr80Zq@PZEO@_!*X7}(s%{|` zJ?hh{>l*MI&(1{*ZGeY30@h^vv;T}r`IX%tugzi9D~J;F6IKw4 zKi>eni)kOh@0efU7u$|Dds!}I^BCo~vSI>T%D{2qRb-XQx_MlY zq5X$Zvsq3KAflXkQqH`28a@<#J?WElS0mk#Qt$jccQcQkRKup3{|Z|_8Z*@6ao;x_ zVMa?X;HykB22MO6v!r2nUA7BrMh>R70S)XcBfJ>NF%oObQ8MuTwaL1hPj+_;Z3B2dR_guT|&7rwIw~ z><Ds`u8uXu5K#rg& ztxv0|E4Tyf=Y(iAEn~o6- zqz*q3$C(-vJ;p?wk5ivl4aekzC=1dC%4$4&Ki;8-oTu#g#+t~7jGJPsG`;6ar;ZJX zXZ#jO0qV%mD|zJ*c_ZD;w`2bNxD7Sq`5$GuD$~LCzQOUB6ThuEMg#j*B81Oi0)Gw3)Z1}(+Bs7^Yzs6jPN}(1!JSqv$@?8}FfMs6i%NPlRb5thL zeC%O7CN;Gg@)e}>F?JD?*0_V9jww<9-GB|ZiN~1%edSU?1w^1n^$gZOn;B@*sXrSe zvXV=w@R!7SNVVp(e~HKaq=osyt6+q z>lvg|!hkm2TTp+tPMoDOB?>8h?ThLANI4?DkLcxvZ|IS!-gfj51-p7t82_T3o0?goCf@#;}yAF?fN4gOf;A!Cu> z!U_Hxlvj|01(bd;>H%ubsoDT;m z;9BI$1-)T+D5n+!*p<_;`2Q(RqX=#DKstF~a-9zCoUstF{>s{FbB~UV6(b(k^5D29 zns85aLezP=clu-pMZ_^&JtAadsrbxO_S$KcRdIiX%4R*P?Qd!14f!Ta=Rkii=xa+;%$JIa$2Ctw+JdN z%2MO4oCCBVi)ZIRaCJWqstOCMvGY(=H5k1Wvv~jZ40eXJ_}eOz9nK1_#P>rKao}tx zva2iD<-?mPV_u|y!5#p-*5_eI!C!DMiFrOOIvm|Q^Cn{snaIlism8K97w*L_0pxg` zvdbTTTTn4B;FqssHaqj%#s%!#S`KtrAw=&liD1!pzg1H|DabOps3T!dg+7O7Az@}Q|LSYf8WfqshHMg!x? zKQ-pr_^weA^H?XsR|%)pI@CZgVvTtVw#C<&2N|PSwa~U)(4#W`7cC1}Hai4n3JDB? z_fbAc4bff+K~isvXV;NLZGeE2zpx)MfNE2M6yn5I%nBC3>i)$_&&mr|?)>4K!3&$Z z0>fAO(^k$&lkcISw)D{Uba<)ngEwi$`E8*Hv$>Sx>t%p*J%m@ga(zvh6p+nbvu|=g z*p@OKUHv%I{ZEwFlq}Ejbpzg6T@-|#?DU(%#p2|^xAs3%_ZFe&hX2SXaGZ@MbK&%S zb1GK(>WX(`i{KO5Gy+euA-I#?yjm~+VT6_GWZ7Em;2D4kB`TYz-Pk(s<@}ldI`5@P zjQ`@T)HC5q);A!84!CN}b9U>dh7Sc&;d?taivdyQQL?QE%mvW1)k6f;{6@<0^^?#k z5eECj8%r+2LZc(nfU(~;9#bq#mzi8OaC(h>rtj;SJs@+U!zBnO&uxn=!vXnBZB5RS z6Emltn4LJ2=d{*=8|lr13uVSvW8VIXcJB*qbEQ;GDk&IUP=J1$@Sh(RJ-|R7!NxNj zySJn7$dTldwebM=vkLb6u!i({Sbu;&N_5$q>Zhmx_p!EGmtxrBH?*9=sUSn2MtEO6^z%5yNF{S7^Iq zV&icek}uj0XX_)rPnY0G_Gim|oq@rYp117Ta@F)KAtEr-(j;G!fHL0~i}uV_uZU7X z5?O@inG*wpliC!zf8uyY$e&ZhkL8fMV$eqZFzr}d1Fn$E*vOxhylg-W zI8K1%U1F~2cxR+s4lJJJc&|Hp9*-^_gd>e7Iq~%)99E3^1Cvb2sI1J4KKAVoq@Y-NB zxER}tAs`mQx{EyKR(z^ew8pC%7*LoUoCL#9IT)}=cU3T34o>+*r$rRZKuZx-r)nw? zUZTqU2&_<3xr#MX)ht8cDAm$HIQMy5@uODJFpgNis+o`2=?eQxp@z9PZ>CKexD+J3 zXoee2-qp-fmQz_b*rRhHOQ{)ypbI}B=96i4-2QD2g0KuXd3PYWzm9vg&7&|>hwt;E zV_{P6X2{rh z=6LW5)I2Nlg!fUxoFymr=HT-&{mB!RbMg6v{>+UwiO&;;?geOoCWKA{*&kr+{^+C9 zfc{EzCiXfb$?TM#{=*X0Z;Q?>ATB4HYGPp#CI(nxq-QCd-%$ z{Lz5WO0Wkx%ACy%4`@~?^)5hOMk5v37ZJn-Y4`D~)VmN7Y&k@*XhcYxm10r&K51y= zXDyKIppHL`pYU{VG2+>-h-a~=Ea6Y{BbWly9L^fRVt}M2sL*e4=~na@P$wB-_iJtg zEnR^{W4Ze^SO6-zlINJ;N%7?YYrCxA8Hs_2J$nW!&InJ+)hc)Lpk5~++hU^-MMmXu z+agCJ^X0uDf-LWSDTnO}BU4ZyZI;+)%kid7#ua$W1=4yk+CR1=kwz zTxEm-qjOMkB-5vElb@_akqnAbsfI>%4EXFx6bx*}fT^CvtG^P}C`F!f#4!M$tV;zx zH|oy{Qvpka&C&#e#Fi6d?_xGr3K8!rM1U9NO-J>!SjaR#hNogPhHaXN!Xdf2sA2MQ zAlM3H-(i+Savm@}&2PShVmvFSC^r3FzRkO#aK8f;f&XKLsShR$%H^kTlH zpy8Jo=SeYs7tkTFRCEwTNJ*^l&HRaPOT&4k!i_)yJz)O@-;-klF8PM}}x?~%i+dUG=MVZATbYOu@8J0qUJML~e zgtdTL^Z&d6DcR(003fIWToCO1CHqZ)e zv5Um9n4jqIwwOmgV;Qo93OS{>c^<;7@o8%OFyW6J+wdY@z|V_#q)!&VAnmfYY&_`n zKtnAGXT1VQT&UQoqIxf!?AQBG1y`#r8!;`?f!2mdU4_xlaMs{%`7NF>6y>RAI${*Y z#i!fd{4)gmFkf|tc-RI+7}t?2D#H*P)3Df8ZAP()S&2hNXj_(V7cd{=ZsLXOJOCE9 zBXw5eF(E=`lywf3ka@xD?NHI^Xb)gFH1iZ7bPWzk_?^y3%fS3UOym;r@ zUJ3r@7jh`!ETIm63jt<6{ubbUX(V$L`ZB%(@3p_z4QI_9a|lp|M;Iuc-|5@2C|&Jv zKOKOKfcTGOjznsO>hk35aM2=j8n!EgI{>PHJDYflED#<=gjI)nv)vpDGY)1vlk_U= zB%B+E+Pb$?v^d;bMzy$~-ua@f1kN(=xSx*MUvKXo>fXZLH^n%en}b0qZmV#7&4!QW z$`7hv!4NbuL-0db0|3XpWqM1x`{_6BJK4MfsU2t~Ae9TGmoy~)#Qw>>#Z~6sQd*Y& zGEs0py*2+I?x$b3@AzgcfZ0j#5LThF?ZXY&`e9rc35;LTq5L8JK;BXIcdjqDT4|KK zxeJa72=#(=W7M0Ce?R}{4t>D1E4J6}z61n8>2p$j@=YJ$};&`tI+XUiB z@|bX1iSZnn&uC^)?2>A;YaOJI>|7FI5pY+VEjZSp+AP31AQ;qA94VUCXoanTFiSB@ zur}Gr$rKa-ayCVg`<@w0r?thxHwwFEi+GFD8%*wE%tbKoaw1K=5Y0#|XHD5Rc(9cR%;CqYVn94J&y1FT;`yjoAuWy31 zun&@>0kB#fR}$2Y?q4#q#;y+{6~(CIb+SPOKj6bk{0eUP5$^Pw7xaNlL2gIvh7-Uo z8U2l*-Z+@7XDW7`=H&sj7aVyi&7VIjU6^al3#|Ef{OUiyh6A}kQ4=!AHghHqadPax z{SpK@%v=ODx6KR=9}S@%U^0hUjcqavd#|vq(}Jp6K+4C-%b3_(l!~IG8vBXZhnnXFS+z4wqFISInJK&O(;cB?X0+J8d{f zGdMIJbeX%+Z)bKla3l!AvzUQS^Jh>9W_JG+feB>X&73Y4#zV*VyYGDy6lAhq?8Q=% znaz9MO^+bRqEv-i?yP}3$$vv-7Q}+r)R-e+?NU86VR64g&338@z2&~`kj>luRkdaq zEmCDZ4PXGOB30&}@EY2N-QB?nXCj-Na&fVm;#A|4;3{j)YXJ^`s?1galhwR8cyWI) zi0x~D00=`!!3#oT$u_nCcCb!_Pb(VE-8>kS=$?nFtIqD5@B`wzo6qLU@%^{En~yAU z>K2vOx5)Ds&>}q1CGA5p6`VsdkrW^lXGfY#7SB+}+dp;GR-qZ#YJ1LOtzn492BGE7 z*j2UW8fMf)b3boy-w!QDXa=CQX6^Z)nw50~5(>fG&C`K!O0$!_giaGqhLD{C;cvqQ zLM*H1l*Dm2_kvF>0=b($#Yc^q2cK6Vk^^PNu7!OXG`aK}NDZso@C2bVYfRY4q=#&k zi(R@pOsqNBOdDJ~*1v?tFn^7mAbUUP=-4=6E_C*cKcja=Zo-DE-FWHDh~RncEJp0Z z+X_T4nGoIe*BSJ)WqWrufdaJVsG?ky?7lgZLK7;Yo2@`{D6=7 z7lVZ}+7MHpHUq~x7Pe7V1wR`tVJj|*XEJVdMiBr8Gt9+EZtlGlk2`@_49ASbcB0-@ z=xGotu3EL6X9*1ojb$!d?6T1Dv_MAaczW=X$7cZ#aNMBUnyUD2ytwdUI?J`Qb5@kG}>)c<6m5atsZP-Hp#QRAdndBDDX1@KQ%o#fMRX z%1WLB+&&V5w0ve@Sb4xz9>_qJ*&X%iu`lZ#u`f2-AoON;jIcGp2;KaK+7Z0)=tLJ5 z!EM0-l$$srvi)e}{5J3E45UCxK;3si0hQ4G>r{Jih(zuWwUx~7kXPJQ25B+7oR$OGP-^Tly;X{RS2Ya_#cUm@!rxn#(Q^qAj5Pctnq!ELUuQj zI2*s@o8E|TdJ&~P*5K96P}=fVR(RbF?>D%cZ${zKL*@%t!6f%(S};AjKQwkDxxn3A zij3jRR*?4vbTV7_-S|#Mey4w=c`g9_i1<0pKj0OvB*UFS`}kPld+lPdkG+DRq0Znz zd_#8HV$T(J#fBGl!4=AF8-jUoP-k9(%&fB7J%ZRmyGHz2z6J*j0rcQToP+aX=s-Zd zRtYkZNHC2&^6qU;^B&^2%`@}eo1O2bg|-E_V%Gq0QhxV!;8_Ar6zFs}9|cF^S~AQ8 zgffA#=2DVof!E#k@j$T8ISu#>4BvajV?*qorq=@?&PKGpt@{WvMn7qK-QAQ2_|g5{ zto^%@1ZYoiVASl+VALnw$FIasy9t4XgiT#=c#%@yjqJU`!XIyE=zCEKOF!OD3p)6OCsJp5%`iY)tl=c}9A+^>rjaoMKTER4;Q~0H zf`H=|UlBDU?fClu#;0Sr%I@=IYtBTz7szDc%+)Biy9VF47fU8YxI-YX7j%QJ$C+oNC)@~9Sz=Yv3_++mhlBrrj%hG|2{e9@d%g=a>dPVO$jqI zkm#eLV%6J1m!Xq4KqlVOSUlxN*qK~D2KRDj+S^x_pVvBcWy$%iWjKWu9eU%avq6ux zYfy{kI<$6`ANynQ`v8!RXWs`1DCfM;%S#cK9y}}b@&deMEOMfMcT;%@H~$&uS=6n@ z%+_Bw13fmL-71;jJqQqmd&6NSC|=8FVA#sEF*h%Bj=A%0$Edv4wDHB~20zvi>WBO5 zN|4?B;%YJ^C8ATu#3qHC=cB3X94O9T70+%0{Nl9?3WxbT4OB+?H3+|X5unbTiG9r1 z){pX=l|}gSb<7MDl&BrWxYw>>v@hsrJvSy8eO5oX%5Rbdfd^ERzy;771H6qTFpS!v zi9;sh4a<86%*3j1oBgIP$i{jb$C5f5$l!jU4XfV|2ocs(9SvM#u0aj}Sx+T6m|52Y z1=@TK8OvN+D9Do@Us_afEu&a?B5|@HkYtG1B8@#2eaPQ~~N&y7x>C`<}lssOJ zFE#dIe1sRe>TD(A66LYQuZh21M|lgK46pGexZm3X{+H19tO-5=vus!VY69qsn|)L4 zJ4ES{Esuex)loQSf&qqFpinWEq^|KD4gNh9S01=lg7!%Q5*56;Z9Sm}(|A&=BQ&A} zAGXj4Rsim6>&M{N$mB$zGouJk5YLACGCJ&PkeY>Et?;(V!tB_Q?{~pq0tzo07;4Lk znR-BiID@W=v)qC%HQrOJj)%7Q=HKCJ|J1Gi^wTb$&lSEetrVCUQx2-3la92f8B==Q z%^x8`WnCH0z0#;YtZI6V>K;{n7&j#vvt7Xz?Z+H9gpQ3{Vy_?fBQ@FKo9ql$gpTDc zz7SR?93{Q;%~-f1-Es8Ii)U>tJw7Nn5*S>3ZtUXl4f`cQGWyYt+mHb%1~A1;hnmu> zX1ii`{^F*in;iBjhm9XOa1=O@Qh32(j|kt>ul(6Pp}zsG!}@UI{9uoo-P8V+qkfzb zbWNV)dv8g)de1(kN9_R&X7s&g6)p|A25=WZ74Uw!kFqiRT+Ov$4eFq_8`AgKCPT3p3oDmY+Vf( z=aU{UpP&}Y^- zLhX=*`8&cDXC0^vmuFS_%}J0+xUT_XDW1t*;>%#2mN-H`b7o0*kuHT4##mNwJ!7F9 z$^AJf9*%iJ z?fXMJu#*3#iBDr<&*SX6D&qUAG26NLTyzoTyL3v=rN@W-DL5q5k>#7+i-<-4jo;uM z>K$K%$+G(;%RsW&N4`jKCK4?^D|8peKqtbGGWId^U6#m~DRGki6Qmz%(kDPgn)f1U zAgjuJ2cI+5WDhPEnBWN&$Abe4+q&HdG%gS9vK+ONYFrf`^0Ws9!W8eSBgsB17#Cb^OxNL*5Q_tLk3ZmE$J} zn8T_9aCZeWtzzo)P{`cou4Nf}-oHL}R82Xorhn;BgY(fn4jEH=+K)S?hK`S00WmYj zchudK2{EN^aQJR;25%0<^Ooo94)CWpV-OhOWyO#<#_R)^&T1_+@ORveBYZIHpK{>h zA+amNHzs6{l{S2zuqTAnxyRkehTA4};>ix_vFZk=y3-L_-eWWFaG+Fmr_;E@>2Bh{ zPfV8`LjOHv+=+Y1aN8JD;Hm}-yZI|C1~@{;(SL_5zx;T6!}1}vR)hqHY(eG*wd>7K z2loNO>2rtcnq(lea6GlYv8wb==qqRKph z@76o&YbIdp_oa=Y*y@TbY=8_hwIbkA{p;Zyuk&365J&in!m2p zX1uhHoo{8?gq1svx8vl8iO$xk6N2_ZOC2I()vIs*%SbE z*1`+V$;WL^<2|tVwg1DdcAnG)Z|t4qL+A3SS)wBwR!2H;r8iIevPW@# z`p+lg4XmLB-Z=EeB?Icm&BU*o>+sEW2FoD^7hhzJ=U~2YMVgHGt2yH511pU9UdY9h zQamd1BxTUQ(Fu?SM*kZf>TY&2-1CvYD{V?|XnC*Az3M+f6Sce-qiTEHabvjR%HB|1 zRFAo8_Tg~uSH^4)GzI3L?J;KJ(m98k>EN}KGabH}&fu(-6$2fR7Ohi;aV#C^SbTBA z@?pubRD=h{(s1eOQcdMQ#W2_+fhSdD$T-?0m?JnbRSkabk4<{X*Wi>7F1 zy@S1$5R?c|$akYNcs)c*Lf6^_WhyWrLHVJ5y4KVL<-?FXka8!HeYQ)1^0&){iJ<)L z&_bZDcbi-aYqCxhTlOZ5YVEomp($^fA-wUrbR=NvS;kHWi1VI%+@ z5>k$u${midMd7I_6o&i1-6+lJg{m)H9~%1%6z@3w0)qoLQwfhM~X!m9y~UO@|O# z_(te!k9+Ob8!p{*!$bpHiYSvBzqUDSwq<{VDpYH}lrIx3$U1w6uV?Z34L|QezIgb0 zr!lbeddES0G%W%Si; zuwB9yUglWOHwy&zjXBg%7P4wj-Y zZ1&_~c^CEqTjMxl9vrPYM47agd4%%T%n5U;i$d;rJaZpr@T!ap3=VA3wHqs3P%`3E zoPo^77%Lt6)Y-gu`R^$raOletM_^Rwj+}V#vih{}_`Dxc&2e2viXIb+eyV?i11n7% z=I1kgf@EMYjce|o(-T2>3$_Wz3nW~P+UXnbS(4!!kM(pl$hR}_gV-lex`S7$gD=ps z*7y+&NH*VRi}FB8moXGikJ@nyOc@9B+l`_5Tj#X*;Qn)`6I0(ga&rqNc+UKCPy3gy zaOn>E_`BTCpOxlmxY*WMOoCuRk!%q>C!WEohsN@mhb03z9Ak1kQ`S&~ylk6VRYRT4 zx~O-BGrTGX^i`et+e2m!HvP5lciFf4zJ$$r@eh?7ijD8pW#^5a>sXSF9q1m zabmBuPp8wjyl2rH8p<_3VXi=S_XN<|kdMnRGFCXdM5H9*`_FSfgzHv%Aff3T;p4fX1Zf-w4Rb#FuKVI+x%aaEPS_XRc# z`sP`kyR_Z79;Rn!D{icv z>%824Iqr3?7~l-Ikb8`wu_ChwMt^Yk<*MA%IuqN|&vi<#GtQDy^WTI{>3;sr%l$dw zM&{wsZHZBz3XiJBn2*_Otr{KvB?W`;Gt5&|C+8IxhaKm%p5yCSJTUF7%PSqRL7|%+ zw(xkTkp|(>y$gx$M;orS1+WqpF2Xr>izlqiY1$hsK;M~>HmAX73pys0#g6(uTRhb_ zu{W53>1hnZmTPpi%YDx;k)6YFM=Znr{O5Lo5OgkjIeeSbcXaWJuw%GyqI2<*l>@MK zp#%`;q|G425fe&cN5i=uA7(m(@;@`qB~XpnZBFCLRu5V*b`4G?A9ndf7PP2HeI9$t z{d}AKVC*;H+~GRkR^%I>$j1t`4iA?(Z|!npnJT}%KaD^j7!zvT&ku%bS#5vg&s@L}24^r-D%3HtEVYB8&LAMeWe|$X2*^cKBpCuiAb7@0 zM|5bQ!!VL|J>5=eW!d$3P93wGf*9hZymbmv49jvjV}@yf*ZF_fer8}mJEza@*Zcl| z@B1=*7VFt-@4fcgYwu@Y*WS-2N8pqGS?F|je@LF@4r!SjUviZ+<5Pq|ndl}1>n6f` zJ@8v_uGivU5j_CLAFB1N8B!Z7s+l9IjTM7!%^V!rkeGKf=KFh@*Tr+aVOb~<4tAJ# zi%Eo6dqb@k!o(uP9P(M)>x1|T2yq!{o(G%;oCF*Ld;!=Gu*N)c(h{@oB(M$G27DU$ zG;k+yC-8aT^T0j8J-`@Xt-D+&twXjsNvEX8r3nXRI0z61&;#%mF3R51xSn$B{w(C& zNr8<0v;kGP!q)Lq*+AK;|G+-|`&>D<=`zyYG9Rfpx?7>;#driSS9x3z%3~6h$2Q1< z5EOWQGoEb#B^qcdXjPfYu3-9oN+3kT_uJ7-y) zk(hs?5>-4jA8oH&4DilH`b7X9unJHGSPOUp@EQPm4uGC<7V!~SLaoC!TaWkw`vJp{ zb{+Z4f#tw5VC*YBA_bQAFBd^S_2AbGXaSri-mY?ql_2+S<-pv?ty>u&ZwMpM`juNg z3Lir^w2-zNu@#9eJG@5b)BbH9E3jDJwy>woqm(^#w&^;L8GDW;_8eQ<*|yNurn4Oz zt$`OdMwZcT9IaCCY0Q5fBw=R&jq`|n6Py^DNHNlZ+XH>a!N!hfphq@@Qf&9s+vWt~ z#p^L-@snfC;kXS%DO#<7MUS_(WNi9mV!1c1yFe zFq^|*56c2^(mjoJI;GzBW1pAOB4g`irtp&(p}?erzYPI;VCXo&!%1BaEn01l#?v)xzVKn`s@)FOZYoB+K@Kv=mM-WNE^81X%6&2*&=a1vongQYZt02Cf)~H0ii0brm86tFB*zP5KPm zTJ#H9O+$jR=GeuO9w;Z0sPr|>4OD@$(BdtV&dI9os+i~$mzQ=Qw|jOU4?VsL>*BD3 ziAUhn!k`RW!dRA;aI%PABeteMyVpZ#|GEmIyiEDR=S1Uas_jGBWd*HI6^hz=Hr@8a ztyuA;U=W)^hOl6Y=NoE#PE%y96yM9e{4&K|H7>NVVl-;9SFL9(Mhh2Iu~*5qB*PX- zec3L#UFk6T2^1Nl&|>XEvXf9Yc~C)<`2?9#F|gf-WA>&AtX9&tHt6!cU~+QW;%H~e z6-3V3iVCHHpN0>y>M(<-Q05tLK$^_J#r3^CIZ`xYYT=@O3m$ggexx5^w1FfSHfG>} zrX1!*H&OF|<9sWiG_5{e(w%O5`zFeha#42U#$3!W(3*%H2lJH&allrFe{8!eUrJ7n zoSz68Zp492%B7r{s6Z#!@}e`GHp~-AMU5F)ObQ9tkV%Fw!pR&XHg91~)+<2RI+b&& zs9k=RaI)H{8689c!!`zC5e*XDf^SmON?+XrmqRo}%5mKmx;wB@CO1S%vU44Okb%WpE}NjdW{2QF9f+DKu{ zO@g6={oA^jKx(JAA?ZBiI*LLjx0XR^}m4e z7BI#<3R}LotlpIjj1!Q1%WT@U^fV<&wRQ#B`FY_{W|!i>DcXEVvg$bp6WlT4Eh;`7 zE;R3oX-wZvVzKh`(%bfbg=&5)mt}5}R$e8|t@?gZBinMF2nW_>g2)TnZn3b{#h zi!la=uC=C%xA3=D9p)z}wjS4lMKd zbC@LIo9K|UR!Lz!C>#bSXBoX2ba&yc^ZXp*-8y!{A^50CbB|G~#i~7tG#P8r#tFfW zkP3ecb5@9Msnd&$QtM8;#DbYog*^Ch$UJ|=6FV*Q{14f!NAAXlOozc}lb1Gm-V^H( z-EBNqtAw0)nJfG`RCI_E0a5r_%$Dj-1iaBLi=Kjh1l=gy6exk#93pK;CWd<~mr$5P z)G>;j4qjru4aMPy9?1bitGRPGYH=Goo#PL~`-8ocqrqL+KWQ`uW390hS3k;kdr$}} zMf*g@h-UVxhF(%s{RCaEu6;T#OR$C7`DycP_Z|^m8MKl7_|#O~m4c%V2t~KV z!1NaVr|m3)WWlbU#xKfr$B23ouhE`LjYdM~B}maLF(0NGYsE=rt;XMYiT-s>+O4(P z=GiW>YYMU;vlJ%x-t_U+i7?7WFNMbFV`A7O%+MDuz&O31HQpUAYrJv!myu1IKmKiu zfx2LZ%=$@c!fd1;Ho&hu{R;GGorhuHwoa_VgerMj0g4Pac}bM3&ZgsQPd`XPpCT_s zH60akC8nsdfd_K!<4jOkW2(GJsp1R|{?}FCgAQIWronKZwE&24gOgtKK6+Q161Hnu zSpq3TU-t<`u~{*I(q@@^!j|1G_~>mbKtgN5Sek92g%U|meib-m*pkqyVK`7%y%^Og zKc8{^Fo2RLLWbVT6AJz^tNX~$ycgG&Pe%TEL=V&9p+t=v+ZQA zUB@EOeG@Zn_Aw9|4~6H2o&p_)2BqS(&z?(P08bnS%R-N<64bQH6ABaNPgq#-Zh-OX z)ixarRxv8XO)fCBB-6Q}A@gL$k%c0Am_Ts|6e+A4?*ON5%xzUv#h|uvAiY0hYUbk)-7#LiZkscw5 zGCrd>%616`VTe{vlR2tqT{?6rHykp^yL8?rUTRL1iw+rNN%m8Dm4;zIVQUlZRwD99 zvl3>ZNd|aGjs~8$^-rox6@%a?~!403towS zxRMB~_*NLWcvOBnmb>aemdI;pCQCR@Wj^tp_W?^BT>fn+Ygt<+ty&Dt)KmO8J@d9! z1X$vTfN4^u)^-k3&@l*)4`O=wsVbBC zd}Eb;=s1UV^Mu6Q!SNH~`H}Jb(0Jn*Q<6w-_qPr+eSgXHnaqxIHGSW0`b>)DWbwF> zRv9f!oftxKZiwfH#2ZJB8)+YI9X5Wi=`%4d>SYfS%dj@Bwrwn%NU^07syL|`7&V?6 zTPWHrTfqBOEyTuAs%-&sl4U;C-LV%9D9UwYmky%}Y^2#D1)obDTi{cLFXkM^3kqLY zM^Gn^LC;tUv5m?Xd%5GA6;(B$!+@B|ITM%)dSYm^^Q%pX@-~sMLeb{29xXWD;^i=l zZCiN0O?Td2jGbSXKP}eC+B~S4w*yn)-f*$kh9iFpN8x+Jjd2Snc=b!K?MCyds~ulL z-px3VON-^m8E!<|g{So3E>vYPb|Q3g%-)%Ny-Op08TiLoHah>Cn=iIOBI11IfV4l}(b+AKw8zSKYOUr(LAAkRQc)rAvpJ z=!-n5c-*OGGYaINg6pgCeKxGX>|skrWXw5y^-Y*4tQoP}qZ7mDTM)2OcpOKMw1D(n zTg6qy4&7DZgby4?zwLA4^aISxs^5E_Q$19J8x*_ZdFR{JDr7{{+q^wL^Sw005;*rqcpblmB3%;zwe z^;xF#KFo@}k+$qOdk?dbx1UWXBmMLEFTdDg$&18GbL#HLeM-;Jy{oa{8 z2ye55zdv~8;HR~pBH`M0Hz^LDJb2P;*R`_P8lXEI`e>_Dhie+v0?YR9^6_1_3o7>*nNexBomA2MKGPJJz zmc|_By=&;cD9_fSK+i@T^}s0|Pj(2U5!X{}6ANcB8m&=McI1Q|HsK|0VxpK&d--52 zzXr23PdKe!idUMw8%}t`Ew#u4k4_FW@A3jm)T$^cHODeH9|Vxq z5I^HM7h`|M*nDv`Kg5QwQ-Hf|X5bQ`Fha?H%glAsuI%yTGuVjT3s%NOZ2jB44K?MCWIm z9x_j9#LS?lcZX-W!QEB1w9TUiLjqJB9FA@C@1RjjNTQ!{JVhMi9&~w>yQ_S+Js<>I z5mPaDQB*&MSZz9GZ|~hZGFk3UEx!*(S_~nHa-)IfON-$r3=T5gqtgxr4Q@sVBNDT4 zYN~A^rWZ<@jr65bK8idssM$b+8*%FyXCQH_jfScIxVP5Xxw1J7E#2qh9>}cC>$8mU zy`)*FQFuWpa|>lzSxA~4RXa9gGxQDl7Gf+xO{~oq0egec)K1*HSB4R;`|>*#EoQVd zFu+w@sb)hDM@EUV+Oc)ZMHb`bzRj_%jfJ>*?^)DMYzrxboiWAMMz%RdXPZ50$HdwX znl4R2EF6GK^b^}XOqV7ZJwXQvuQb+l*V5hxEtFK5*+ZaW9o=tS2)`_2py^T&KNM3f z9LDAB(`r4#R`5PynT$lQgncY5AVU(B#!(1p9M1fuVVOo5t}l$GG{~C!aJNypgD%2F zyW$ic77wfCh0=6e+Ylza_eNn?uaxcT@iN8seddlJ`!rWxy|D)JclV%pp-RW^Mjr1{ zY;V-tJ{=D>)@E~uIy6yPKGSOKL2hNC#`9%Qv7-<=2n@@=Op_Q;|<~Y;x zp*Z$7IYig9?6xkQpQvw~2P+&{w%dKd*>pnO)q3-u?p6s#Z#`h{fwV1RgcswI16@yd z3k+<4X@W)8T4IaFX{b^cI_evmA;_7k(VF!tui)E?S792}OGb-!eN$)rgCwWC_ zTKPlR2bwF8j6Zs}WLO&;zEh@qw^+S)IIdn@4ReU@;CP! zB@$H{18^~tsFFWI2=7Ma50pFOu0`1ka0td4by?HG&%}L=KWUEfw^g0wPc_(-AdLX& z7)YKVePMrUQMMp))K$c5tc$uLpv#1)V;~EzjMVg6U+nSF>S96d1EQ)fJ{f0#a5HP| zkXX$@#?l+RS$Ooqj@B=W7Gy&LB6J$vDOn76ql|4F+QivTJvC(8<=iISx1)t-I+z-YcAG#eC|!Tdl2RA<`XO|MNR2n0?l(nE#)9 zwKmofyl2)N$8sqzjgQBKzhMCxKDauyx#47&g#k~% z^@LT1O*%b0!~9>eN7?*G%y_S3k5d02vwzqIZ?Z=TBYY6F%K#M|y*4KQ&K`xB!)~EB ztkcF}fISL@y1qS%GyaPRuoh4SSOwq#MSxtuJpkA-oP-_2f72d?X~IDnt_SP~v=eWa z-NXMfdlYR2QW5M?I#>OwJxUzBP!1G$eI}k)05$+zb|lx>qfp4-W{<*ny7=^3ZTvfX z6esWVNdGcGilpQKKR^H=5TFL2Tz&_86iUqM(Bpq5ARn-bD6Vq(@7SZbUN&kk(s1Ay z$NJD_&Jo(a4C@8+MQPcT?pCbr@UlsVYKF{-H(soX0 zn7a@LO(z-xjN916JDn!iWP3RaEu9r(UwQYz@j$ZiI=Odey6qnzXW{UB7N*;5KtS$WuSuQt%NxK@c*dZ5pHL8F_U5z*s zj&Un2X5ZhJZauV}mUnir<+m?CVT*5L1uI+L4;Z~HFT<5M-ro51jrXT^iD|!R{DDQh zcj#wEIk>f%HKv*mKyj3_EA)`Ws+Zwe?U}OuERl2*+d%SGk(R%Qdei}>n=3Tf++$i4DsCZxJg@O}?ZRYUjvG2jI zN*N6S%sr8cwR8l++%sRXZX-R^tAuNE7Rq)|HVZ|e-z=(_5Hf>ok5+r*QUon4UAvca zaFRK`3F%0&onx?$AfeH>_CW0Bu9&+-CSGAOc;R!1H856PI5d;Q9?I3=C{z<{zUvY% z7fMKInI30yNHE)N*!nQ?K0^%E7sAg`-fF#jMn`MM$P`5qLx9X2B2$tpZl^alS=bgBNPj z&+~D7>)UE2KQ0=UDKeWM>6(U|!!pR3L{5yovcMa;q_BmOtZ`^udbFVpIH5C7h7*fn z58>M~Z(<V_fsdH5n zb#e>LBYUj-u(f=o9Ro__7N41_IA17UVC^oHWi|XD4*h180y)4(IGe0erA6wCWo1;+ zKoNI0Oix#~v+S_(Ken%_)D-W>d0AWWzL_wJhS74Irv-;e;>3#gVMDCMeO+s?ENrJ+ zyC4RdzsgIZdf11<=op(<=YzZSHcu!N>Qz)9LPkK-+iVyZWfh7uI~tsnI-L!scAz?z z%UFo9c*aZw6+jeiog=k2PfC`BwpPfQ=j#;R9v0fNCSp<}K0koSjJ^zXJf!Pw@3^u0 zg1+N1N3IpZJi!>Ng1b6aA+^x4t0uCj=`4br)?%5Proz_ceyE8Ob)l#u1z{a%KbHHM z4Bcxegyftp9$~Vwa4>#s)}&;)b&c}K57;5;#!#DDC(h)3)*ELn9a;#WI!YIzMxmglumF>!yd8c=E>lQg>>RlPb*1g6sA8!N5tlD+v0G#S4w9 z%OrmblA3n~vX%&wxN8I7=oa`%S74#jBikpLz0f)0x6(yf(v0o~Hpo}hiXt)44HcZm zvsTfEG^&>zq@nyy92|X}4d89KQwhPwV~l(g);}B^Ka7?^9K6{6S=~y9-KTElRlBTi zGI>zs}b?;;; z4JXW!+HDy{%~W<{p778#2fdWbk=^J|n^oM18Hbga^(;;N6X8g2@ivC0RAn;}LI7zf&axyqSl zPc?ctqNMiW88$jFrqyE#`2f2xP_TXziZLBcGB%#+eMpAbS#`$qS^05jpJ5#4OE+O( z-n1!ilPy#QqrFm)VUMEN?r|5MUe3qAY{d=_3jJ~s{_XbQ@jn5ujr^R;2bfRd1iy9n$d!&@#}n)o{i8t=o~fiCyM_^Tx)@{0*Vs;A*lDxG>DIC+?uv z+deM%D9j*+haNG~KBOg6YQdf9nKJv`bsvZj4CfE6dmB1NG_-qrHzR;IdXaCv>3pQJ z`g0>b3n}9f-!ENkCk5vDh)x%Gy zep*-c^MKUczpsPSPzM8rI(S209gJkPdQwAUUF22b&z`RIK2en<8b0*4?tQ%9Lc{D2 zJyPFLgW`m#Tj-2q(6Ysd-7-2k1MQqsRHf6?;cZXj#SIDK%gpsbQQO=MvV&%ODEWwaF5T8f8%qC%-S1A_)Sd|8+H6V*d} zQyvb*)<_3TTtas${zOMxO*PESM{7UR#}YT>PftOqH2ffaqwa;xkO=2Dto!)MwR1(N z&M+|&DYjk=-M!oo$7gCETaN&;^~6StgId2tMw&u6;}>)orxCXvT$YwKpip;voJO7@ zIj4V_a&K!Q!L~k+xI5b6-n1#}Ws-f9eI6LBbl{?(IJ7S4+64(^+He#(R|W%9wvWXE z_c675T1Ha>xXF~bp}VyEgp6K+M}C+M+vc3@@A!v<#1gEjxcXe&w?QE=u)1o*J3!AQ zpku;Brs^yGiIW-evQe?|Ed=Zf&c@_ZUO_9z-(t2&`B7$@4_)viLASh(HU=8E4QKzQ z7!fya^QGsELM>W;R(>cor_f9?aT@Ks7I(FsuhsqBR81}w+ER6DM%}z~I0S>EH28G&XpBBSW*KV23^4sTB;K%WlNL89gtn=*rq=Q-i9ehH)Be?#U^;!QAuU@?kD^ z&87~Oz1_tOthS>uG>4OYAZHw2c*Qmusb%f>IfH&Ne9t5wD!m{1x^?BlQ! zMRQJm0&2$Xg1^zu>VR>s`8x^kBLsl&A7DgBgLW(-H3}zR_~8^)QB@13&a*IQgs4UE zxZ8Mf!6KpCWc_YD|5u^w^>lT;BVnB>bo0m2j7&z?JI2}d%7w1i*kl=H_ovIb&?}6x z($|@Enh&((fe3YL|?7-3GM(M`4x^kdjc6dWHF6FvBfJ z92-&$<0Z#0G&a}*Oxre~nZ?;II1v)YvTN`v-^ad-!7pyb_jW)H@QHvbCpgYQoR~lr zwtQ5e`m?ZTl=)FDK3l+rZkQG@LIi+ei_d#bvVfgr#FctOs{V-@fBp@r@xFp&)4;<| zQeS=t%s|*v8TQz)saN0Pvkgfe#pQIA|JFUvQJNh+cx|1Dn@>+Rw@2VS(p7|OfuZW; zM!;vDEiim?Ju>QXMi9uy83kPBt@TI40`Z`-JrABp1L^ig6ujiNO2JF+=7k-=ez6xh zV^dsLw7WoKZiEEQHp|Jzop!5R80;|hN;3>}q(SoRb(cp-*Y*=y5aum+^KRYCWOC!u z>eCACu9I zOQ0)QuZ%);IivT=vX^A+L3_ng?30nv+Znw#?7lu3o5*J$lW~7P8JQfm*IeH6);{mn z{Y*wTE+$v7UKxexbi1XHIUxqgnC1%GC*wY%hxHGO{a8>DO=Y`Tl>6Q4-Dk;gezFDj6(EPjNU8b=OkmkD{P;PjIQqui{?ov zX1OgCL?#wU&K6;!lcYzZN88t?HR1G+52o*2XwY!`w8F!Nv9}|0rxd0Sm)0 zKs-7*klG|UdE(x78DZij2d_@z)#=Wwjd#?oW56%Zz%2` z2FL-TpY5i8agly+9Ngb7vt2?%<}N#kezqT|C3KVhKjeS!03Iy={}{ld1=IE4kbit~ z8S)Ise?dQ9$UozCP5z?>$iebIrC;1@@=p!E~G^LInc`=pchSz9WpL53%`wb);BjIAeH z4pD_gR*hmL%%Z^QLN54Wuuk)0k`leg zxf7h;wkj~JdnY)<7`Rt{TG)5Vf6V6?F$5z>8b$*iN~U$s1TGpgh|>YB(*rAWapUL5 z95)nWtkysn(gb8J$ZSl($4?=bKiY!e!-(riGy65Xm#qyI3*V`l--y};Uj9~Tpf_Uj zD}|09RJ0d+#gK-KW&&N>q|8upsn`%Od^^UB%l*xJMaO);fPj>e^C>LQuIi2K%nFO# z*3ugSywD4}1Zl;v3-bIwQ*Rsp$JX0l{zJX}<$r6vr8}pnjq0tsU%P6||KC-2u1gSS z)n(rnYh=4njjiVzTYnao!7^dD!|owfypNlNGU8x?Lm$5hjN?bg@gw8-v2k(ykT_#; zq0&BbwUP(629HeV`*$8?``fG z4a*fn`&F2|C$_iYlDHG!wJkho-XpOjw$~-e@V#)HW>3Yx&3aqH=fuPxcln|zFe+{F zI00pz4O=MQ&m6uUZ!u@|PPc8s0Vdkrk>UKzNt|!WVyj13mtp2;t(OHGs{T87ShOUm zaujR-icDaEL9y;tda$WFUZ65uD670g?PtYAmzj{fP@HP3{f>47>@Jg{Omn-~xovm< z^|tM>+31DsMcT9zHXGTl9ouZgmLrydo%?pqg_h^BSYW#!shZi|-Rop1e7(&(;rwBt zBOeyG%KXmPVH2?tLUa0D3pzq-WobFnVz6yo3yy4QcXplbB$k90rRR4d4%R9Iu>zBrOpEOef5(VGqy_6x+-bZ7tC@9SxRe98 z5eHtjojZmtCWFm--eNokgC1LIJF8B1Zz!+PYOvAKJ#*q^2M+Ds?l89F+XTcPJBco? zUc*$2O($N)i+IOZ6>9@#d=W?;K=K`PTC zThzm{@d!Ubmom#}3S++|BO_JNCj9)J+PnqDy4!>NsVxnu@Wb6(bX;9&djURK*pKwv zNWP@$(GFvP>+CvZi)DcrbsR@rrs5-v3GSY9s#ukQL(Q1dQ}?ii`leuGQD4od~2SQnlQsW`^xp#+Z!DUgz#d{KcCky+*? zNJ*-&c3O-rq22tnE}f5**M?LCD5_{}H`ExgW{i-vfl**9SiKHGsDzpal#uf}o;vAF zLFSgP1(}ao7Pd2)hxf`He3S@Qr2Y;69_q63A^HsFa zNx7mknm7uIi%cKD6u{clK?dez35Ql4RwC(jcR6s{RRg;Tt@Ug?&Uu`JW0BEg61*F& zJDKelu)pLZA9Nq!V=x56vx2x3zs z)^_p|N34vx{A(Cn^HH~$TB?DR*3&~x=wr- zLd@Lr@t>Mr!+D%W?%@>MVY%5U1UP|N1;&C79WL)u8n*@?&JIbH;hY&Ri)?^3pe0&n zl~_9)&fqMV()%p!MC&ehqGzy_ODz)nk?l12t}cgpWvcBwSizV2jV!wzpZB=DD9;k@ zCA{zE)A!)5#@=?F?iH8~U=O=~zD3t!nP5*kbi~35<5IJ25M*lSqNx4?M0T{e5J#?CC^wn**UW|% z7=C2hUjRGHyv|iHvJF}Nj=)M$g+Vk2t6P353~~9%WKf%8x)iN=!~#~POS;v=O_!D^ z9{D4485HZv@KEU4(To-+xzM|#9+;#Y3Z9BbXuj;?n}ug^Q&j&0L99DFM#GKNLq;(B z6<-(vV-LOpZFCk)JLcPmx!VVmB?~_W+e$Z~OE|bX~R!g?-=ZWVC;(uz`~vDpP>BTBpARF^mR`VOltfoxeuTsMHPLp2WnDO ziSW?zPb4dFN2;rxIeHV#;jiRKQAGwm=q)qqlCM%(Br1inh;f!9k8;dq<*0zp3T1YK zWnMdF#)CxYLKMYXnnaSW*LC2*?nSyEMm1z;;n2+Fj%^sDQG^(KgSkS*@ppi8U9tm} zgoa^$adC?3_YlvVXmb<}$?UKoIN?gdUdfr}9xp}JlL*jJ0t%{kM-3dib?p!b-&b>u zV{m%Dk?uX(foAkt({3rsE&vwNb<(YMeB{B(t9n0Eu zGLE=iK*M3K=;SOKMO7>0DfG__(Oq6P!c_5*$12HH-A8!!P&qVXioc zZ^O8enHY&OZ(U2aEEQ_Bza(XM9s$^`49--O2q;y6*u$08RmHfDXXF z0CvD>z>k14mzs1xMYyiNJIH%BMT@7{rT7nfB{@ENV;qC8Vcir`Av2E1Gmq!UqbXV< zYKoQybzDWh@?cE(AkKMkro7!S$`9>veHy2GH;7u}kbDgWTJ~6o#gC{&o)JdpPXwVZ8Z~uJ8)_=j@n8mE9mPl}U*+<=!|CanWj~>Kf2Z{TdWa7H zf_h~l7Ld-!^2~-#Sl`vjlkzO{F5bT<(NfV>_nt`peBg(@+`r;ueHJtv2dG{V~woiESeG!%#IPb z1${)e;(g;qoIHOPJuO5wHIhj|vmGbzh> zt}a=GY*+8G=mJ*jEw~2J*jZO0+q8)SZL-~jP%OAFdyom@y^5tYM!kfGTHPgUVt~nT z30d)zqUjBAB>L5ej9W2)uZ?rW#hEV5sJmObDk!v}OhI-np0I0)v!BKFIZ|z&Lvoe7 zqj9pNCR&2SYNlL&Dd#eRLwH-P)Ur!=}FrJl$=J3)w#Ct+Qc#z+?NHrf$wq#hbuoBG15`TjJ) zanQCP=bYfcM`g{FSxe>Ss}BB7++Y;QPpq51%qo8uYj61eJHGt%F24LUJYR&$VvTuM zX44|JBkf(8y)kzk3YitNurZr!WwU|Gl~PIwjRWrHPLYSql^H#R(~YTfKdSM?IUX

KGI4%KQ|CKc5WAxU-$12yh4 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 T(F#N>5UoJ80?`Wmhbr(t)^$2H literal 0 HcmV?d00001 diff --git a/code/OpenAL32.lib b/code/OpenAL32.lib new file mode 100644 index 0000000000000000000000000000000000000000..86de42002459c4b3e4f37d9bfd092afa9f491dbb GIT binary patch literal 16866 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 literal 0 HcmV?d00001 diff --git a/code/RMG/RM_Area.cpp b/code/RMG/RM_Area.cpp new file mode 100644 index 0000000..cb8b44e --- /dev/null +++ b/code/RMG/RM_Area.cpp @@ -0,0 +1,480 @@ +/************************************************************************************************ + * + * Copyright (C) 2001-2002 Raven Software + * + * RM_Area.cpp + * + ************************************************************************************************/ + +#include "../server/exe_headers.h" + +#include "rm_headers.h" + +#ifdef _WIN32 +#pragma optimize("p", on) +#endif + +/************************************************************************************************ + * CRMArea::CRMArea + * constructor + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMArea::CRMArea ( + float spacingRadius, + float paddingSize, + float confineRadius, + vec3_t confineOrigin, + vec3_t lookAtOrigin, + bool flatten, + int symmetric + ) +{ + mMoveCount = 0; + mAngle = 0; + mCollision = true; + mConfineRadius = confineRadius; + mPaddingSize = paddingSize; + mSpacingRadius = spacingRadius; + mFlatten = flatten; + mLookAt = true; + mLockOrigin = false; + mSymmetric = symmetric; + mRadius = spacingRadius; + + VectorCopy ( confineOrigin, mConfineOrigin ); + VectorCopy ( lookAtOrigin, mLookAtOrigin ); +} + +/************************************************************************************************ + * CRMArea::LookAt + * Angle the area towards the given point + * + * inputs: + * lookat - the origin to look at + * + * return: + * the angle in radians that was calculated + * + ************************************************************************************************/ +float CRMArea::LookAt ( vec3_t lookat ) +{ + if (mLookAt) + { // this area orients itself towards a point + vec3_t a; + + VectorCopy ( lookat, mLookAtOrigin ); + VectorSubtract ( lookat, mOrigin, a ); + + mAngle = atan2 ( a[1], a[0] ); + } + + return mAngle; +} + +/************************************************************************************************ + * CRMArea::Mirror + * Mirrors the area to the other side of the map. This includes mirroring the confine origin + * and lookat origin + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +void CRMArea::Mirror ( void ) +{ + mOrigin[0] = -mOrigin[0]; + mOrigin[1] = -mOrigin[1]; + + mConfineOrigin[0] = -mConfineOrigin[0]; + mConfineOrigin[1] = -mConfineOrigin[1]; + + mLookAtOrigin[0] = -mLookAtOrigin[0]; + mLookAtOrigin[1] = -mLookAtOrigin[1]; +} + +/************************************************************************************************ + * CRMAreaManager::CRMAreaManager + * constructor + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMAreaManager::CRMAreaManager ( const vec3_t mins, const vec3_t maxs) +{ + VectorCopy ( mins, mMins ); + VectorCopy ( maxs, mMaxs ); + + mWidth = mMaxs[0] - mMins[0]; + mHeight = mMaxs[1] - mMins[1]; +} + +/************************************************************************************************ + * CRMAreaManager::~CRMAreaManager + * Removes all managed areas + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMAreaManager::~CRMAreaManager ( ) +{ + int i; + + for ( i = mAreas.size() - 1; i >=0; i -- ) + { + delete mAreas[i]; + } + + mAreas.clear(); +} + +/************************************************************************************************ + * CRMAreaManager::MoveArea + * Moves an area within the area manager thus shifting any other areas as needed + * + * inputs: + * area - area to be moved + * origin - new origin to attempt to move to + * + * return: + * none + * + ************************************************************************************************/ +void CRMAreaManager::MoveArea ( CRMArea* movedArea, vec3_t origin) +{ + int index; + int size; + + // Increment the addcount (this is for infinite protection) + movedArea->AddMoveCount (); + + // Infinite recursion prevention + if ( movedArea->GetMoveCount() > 250 ) + { +// assert ( 0 ); + movedArea->EnableCollision ( false ); + return; + } + + // First set the area's origin, This may cause it to be in collision with + // another area but that will get fixed later + movedArea->SetOrigin ( origin ); + + // when symmetric we want to ensure that no instances end up on the "other" side of the imaginary diaganol that cuts the map in two + // mSymmetric tells us which side of the map is legal + if ( movedArea->GetSymmetric ( ) ) + { + const vec3pair_t& bounds = TheRandomMissionManager->GetLandScape()->GetBounds(); + + vec3_t point; + vec3_t dir; + vec3_t tang; + bool push; + float len; + + VectorSubtract( movedArea->GetOrigin(), bounds[0], point ); + VectorSubtract( bounds[1], bounds[0], dir ); + VectorNormalize(dir); + + dir[2] = 0; + point[2] = 0; + VectorMA( bounds[0], DotProduct(point, dir), dir, tang ); + VectorSubtract ( movedArea->GetOrigin(), tang, dir ); + + dir[2] = 0; + push = false; + len = VectorNormalize(dir); + + if ( len < movedArea->GetRadius ( ) ) + { + if ( movedArea->GetLockOrigin ( ) ) + { + movedArea->EnableCollision ( false ); + return; + } + + VectorMA ( point, (movedArea->GetSpacingRadius() - len) + TheRandomMissionManager->GetLandScape()->irand(10,movedArea->GetSpacingRadius()), dir, point ); + origin[0] = point[0] + bounds[0][0]; + origin[1] = point[1] + bounds[0][1]; + movedArea->SetOrigin ( origin ); + } + + switch ( movedArea->GetSymmetric ( ) ) + { + case SYMMETRY_TOPLEFT: + if ( origin[1] > origin[0] ) + { + movedArea->Mirror ( ); + } + break; + + case SYMMETRY_BOTTOMRIGHT: + if ( origin[1] < origin[0] ) + { + movedArea->Mirror ( ); + } + + break; + + default: + // unknown symmetry type + assert ( 0 ); + break; + } + } + + // Confine to area unless we are being pushed back by the same guy who pushed us last time (infinite loop) + if ( movedArea->GetConfineRadius() ) + { + if ( movedArea->GetMoveCount() < 25 ) + { + vec3_t cdiff; + float cdist; + + VectorSubtract ( movedArea->GetOrigin(), movedArea->GetConfineOrigin(), cdiff ); + cdiff[2] = 0; + cdist = VectorLength ( cdiff ); + + if ( cdist + movedArea->GetSpacingRadius() > movedArea->GetConfineRadius() ) + { + cdist = movedArea->GetConfineRadius() - movedArea->GetSpacingRadius(); + VectorNormalize ( cdiff ); + + VectorMA ( movedArea->GetConfineOrigin(), cdist, cdiff, movedArea->GetOrigin()); + } + } + else + { + index = 0; + } + } + + // See if it fell off the world in the x direction + if ( movedArea->GetOrigin()[0] + movedArea->GetSpacingRadius() > mMaxs[0] ) + movedArea->GetOrigin()[0] = mMaxs[0] - movedArea->GetSpacingRadius() - (TheRandomMissionManager->GetLandScape()->irand(10,200)); + else if ( movedArea->GetOrigin()[0] - movedArea->GetSpacingRadius() < mMins[0] ) + movedArea->GetOrigin()[0] = mMins[0] + movedArea->GetSpacingRadius() + (TheRandomMissionManager->GetLandScape()->irand(10,200)); + + // See if it fell off the world in the y direction + if ( movedArea->GetOrigin()[1] + movedArea->GetSpacingRadius() > mMaxs[1] ) + movedArea->GetOrigin()[1] = mMaxs[1] - movedArea->GetSpacingRadius() - (TheRandomMissionManager->GetLandScape()->irand(10,200)); + else if ( movedArea->GetOrigin()[1] - movedArea->GetSpacingRadius() < mMins[1] ) + movedArea->GetOrigin()[1] = mMins[1] + movedArea->GetSpacingRadius() + (TheRandomMissionManager->GetLandScape()->irand(10,200)); + + // Look at what we need to look at + movedArea->LookAt ( movedArea->GetLookAtOrigin() ); + + // Dont collide against things that have no collision +// if ( !movedArea->IsCollisionEnabled ( ) ) +// { +// return; +// } + + // See if its colliding + for(index = 0, size = mAreas.size(); index < size; index ++ ) + { + CRMArea *area = mAreas[index]; + vec3_t diff; + vec3_t newOrigin; + float dist; + float targetdist; + + // Skip the one that was moved in the first place + if ( area == movedArea ) + { + continue; + } + + if ( area->GetLockOrigin ( ) && movedArea->GetLockOrigin( ) ) + { + continue; + } + + // Dont collide against things that have no collision + if ( !area->IsCollisionEnabled ( ) ) + { + continue; + } + + // Grab the distance between the two + // only want the horizontal distance -- dmv + //dist = Distance ( movedArea->GetOrigin ( ), area->GetOrigin ( )); + vec3_t maOrigin; + vec3_t aOrigin; + VectorCopy(movedArea->GetOrigin(), maOrigin); + VectorCopy(area->GetOrigin(), aOrigin); + maOrigin[2] = aOrigin[2] = 0; + dist = Distance ( maOrigin, aOrigin ); + targetdist = movedArea->GetSpacingRadius() + area->GetSpacingRadius() + maximum(movedArea->GetPaddingSize(),area->GetPaddingSize()); + + if ( dist == 0 ) + { + area->GetOrigin()[0] += (50 * (float)(TheRandomMissionManager->GetLandScape()->irand(0,99))/100.0f); + area->GetOrigin()[1] += (50 * (float)(TheRandomMissionManager->GetLandScape()->irand(0,99))/100.0f); + + VectorCopy(area->GetOrigin(), aOrigin); + aOrigin[2] = 0; + + dist = Distance ( maOrigin, aOrigin ); + } + + // Are they are enough apart? + if ( dist >= targetdist ) + { + continue; + } + + // Dont move a step if locked + if ( area->GetLockOrigin ( ) ) + { + MoveArea ( area, area->GetOrigin ( ) ); + continue; + } + + // we got a collision, move the guy we hit + VectorSubtract ( area->GetOrigin(), movedArea->GetOrigin(), diff ); + diff[2] = 0; + VectorNormalize ( diff ); + + // Push by the difference in the distance and no-collide radius + VectorMA ( area->GetOrigin(), targetdist - dist + 1 , diff, newOrigin ); + + // Move the area now + MoveArea ( area, newOrigin ); + } +} + +/************************************************************************************************ + * CRMAreaManager::CreateArea + * Creates an area and adds it to the list of managed areas + * + * inputs: + * none + * + * return: + * a pointer to the newly added area class + * + ************************************************************************************************/ +CRMArea* CRMAreaManager::CreateArea ( + vec3_t origin, + float spacingRadius, + int spacingLine, + float paddingSize, + float confineRadius, + vec3_t confineOrigin, + vec3_t lookAtOrigin, + bool flatten, + bool collide, + bool lockorigin, + int symmetric + ) +{ + CRMArea* area = new CRMArea ( spacingRadius, paddingSize, confineRadius, confineOrigin, lookAtOrigin, flatten, symmetric ); + + if ( lockorigin || spacingLine ) + { + area->LockOrigin ( ); + } + + if (origin[0] != lookAtOrigin[0] || origin[1] != lookAtOrigin[1]) + area->EnableLookAt(true); + + // First add the area to the list + mAreas.push_back ( area ); + + area->EnableCollision(collide); + + // Set the real radius which is used for center line detection + if ( spacingLine ) + { + area->SetRadius ( spacingRadius + (spacingLine - 1) * spacingRadius ); + } + + // Now move the area around + MoveArea ( area, origin ); + + if ( (origin[0] != lookAtOrigin[0] || origin[1] != lookAtOrigin[1]) ) + { + int i; + vec3_t linedir; + vec3_t dir; + vec3_t up = {0,0,1}; + vec3_t zerodvec; + + VectorClear(zerodvec); + + VectorSubtract ( lookAtOrigin, origin, dir ); + VectorNormalize ( dir ); + dir[2] = 0; + CrossProduct ( dir, up, linedir ); + + for ( i = 0; i < spacingLine - 1; i ++ ) + { + CRMArea* linearea; + vec3_t lineorigin; + + linearea = new CRMArea ( spacingRadius, paddingSize, 0, zerodvec, zerodvec, false, symmetric ); + linearea->LockOrigin ( ); + linearea->EnableCollision(collide); + + VectorMA ( origin, spacingRadius + (spacingRadius * 2 * i), linedir, lineorigin ); + mAreas.push_back ( linearea ); + MoveArea ( linearea, lineorigin ); + + linearea = new CRMArea ( spacingRadius, paddingSize, 0, zerodvec, zerodvec, false, symmetric ); + linearea->LockOrigin ( ); + linearea->EnableCollision(collide); + + VectorMA ( origin, -spacingRadius - (spacingRadius * 2 * i), linedir, lineorigin ); + mAreas.push_back ( linearea ); + MoveArea ( linearea, lineorigin ); + } + } + + // Return it for convienience + return area; +} + +/************************************************************************************************ + * CRMAreaManager::EnumArea + * Allows for enumeration through the area list. If an invalid index is given then NULL will + * be returned; + * + * inputs: + * index - current enumeration index + * + * return: + * requested area class pointer or NULL if the index was invalid + * + ************************************************************************************************/ +CRMArea* CRMAreaManager::EnumArea ( const int index ) +{ + // This isnt an assertion case because there is no size method for + // the area manager so the areas are enumerated until NULL is returned. + if ( index < 0 || index >= mAreas.size ( ) ) + { + return NULL; + } + + return mAreas[index]; +} + +#ifdef _WIN32 +#pragma optimize("p", off) +#endif diff --git a/code/RMG/RM_Area.h b/code/RMG/RM_Area.h new file mode 100644 index 0000000..c7391a4 --- /dev/null +++ b/code/RMG/RM_Area.h @@ -0,0 +1,99 @@ +/************************************************************************************************ + * + * Copyright (C) 2001-2002 Raven Software + * + * RM_Area.h + * + ************************************************************************************************/ + +#pragma once +#if !defined(RM_AREA_H_INC) +#define RM_AREA_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Area.h") +#endif + +class CRMArea +{ +private: + + float mPaddingSize; + float mSpacingRadius; + float mConfineRadius; + float mRadius; + float mAngle; + int mMoveCount; + vec3_t mOrigin; + vec3_t mConfineOrigin; + vec3_t mLookAtOrigin; + bool mCollision; + bool mFlatten; + bool mLookAt; + bool mLockOrigin; + int mSymmetric; + +public: + + CRMArea ( float spacing, float padding, float confine, vec3_t confineOrigin, vec3_t lookAtOrigin, bool flatten = true, int symmetric = 0 ); + + void Mirror ( void ); + + void SetOrigin(vec3_t origin) { VectorCopy ( origin, mOrigin ); } + void SetAngle(float angle) { mAngle = angle; } + void SetSymmetric(int sym) { mSymmetric = sym; } + + void EnableCollision(bool e) { mCollision = e; } + void EnableLookAt(bool la) {mLookAt = la; } + + float LookAt(vec3_t lookat); + void LockOrigin( void ) { mLockOrigin = true; } + + void AddMoveCount() { mMoveCount++; } + void ClearMoveCount() { mMoveCount=0; } + + float GetPaddingSize() { return mPaddingSize; } + float GetSpacingRadius() { return mSpacingRadius; } + float GetRadius() { return mRadius; } + float GetConfineRadius() { return mConfineRadius; } + float GetAngle() { return mAngle; } + int GetMoveCount() { return mMoveCount; } + vec_t* GetOrigin() { return mOrigin; } + vec_t* GetConfineOrigin() { return mConfineOrigin; } + vec_t* GetLookAtOrigin() { return mLookAtOrigin; } + bool GetLookAt() { return mLookAt;} + bool GetLockOrigin() { return mLockOrigin; } + int GetSymmetric() { return mSymmetric; } + + void SetRadius(float r) { mRadius = r; } + + bool IsCollisionEnabled(){ return mCollision; } + bool IsFlattened (){ return mFlatten; } +}; + +typedef vector rmAreaVector_t; + +class CRMAreaManager +{ +private: + + rmAreaVector_t mAreas; + vec3_t mMins; + vec3_t mMaxs; + float mWidth; + float mHeight; + +public: + + CRMAreaManager ( const vec3_t mins, const vec3_t maxs ); + ~CRMAreaManager ( ); + + CRMArea* CreateArea ( vec3_t origin, float spacing, int spacingline, float padding, float confine, vec3_t confineOrigin, vec3_t lookAtOrigin, bool flatten=true, bool collide=true, bool lockorigin=false, int symmetric=0); + void MoveArea ( CRMArea* area, vec3_t origin); + CRMArea* EnumArea ( const int index ); + +// void CreateMap ( void ); +}; + +#endif + diff --git a/code/RMG/RM_Headers.h b/code/RMG/RM_Headers.h new file mode 100644 index 0000000..c93b1f7 --- /dev/null +++ b/code/RMG/RM_Headers.h @@ -0,0 +1,71 @@ +#pragma once +#if !defined(RM_HEADERS_H_INC) +#define RM_HEADERS_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Headers.h") +#endif + +#pragma warning (push, 3) +#include +#include +#pragma warning (pop) + +using namespace std; + +#if !defined(GENERICPARSER2_H_INC) +#include "../game/genericparser2.h" +#endif + +#if !defined(CM_LOCAL_H_INC) +#include "../qcommon/cm_local.h" +#endif + +#define MAX_INSTANCE_TRIES 5 + +// on a symmetric map which corner is the first node +typedef enum +{ + SYMMETRY_NONE, + SYMMETRY_TOPLEFT, + SYMMETRY_BOTTOMRIGHT + +} symmetry_t; + +#if !defined(CM_TERRAINMAP_H_INC) + #include "../qcommon/cm_terrainmap.h" +#endif + +#if !defined(RM_AREA_H_INC) + #include "RM_Area.h" +#endif + +#if !defined(RM_PATH_H_INC) + #include "RM_Path.h" +#endif + +#if !defined(RM_OBJECTIVE_H_INC) + #include "RM_Objective.h" +#endif + +#if !defined(RM_INSTANCEFILE_H_INC) + #include "RM_InstanceFile.h" +#endif + +#if !defined(RM_INSTANCE_H_INC) + #include "RM_Instance.h" +#endif + +#if !defined(RM_MISSION_H_INC) + #include "RM_Mission.h" +#endif + +#if !defined(RM_MANAGER_H_INC) + #include "RM_Manager.h" +#endif + +#if !defined(RM_TERRAIN_H_INC) + #include "RM_Terrain.h" +#endif + +#endif diff --git a/code/RMG/RM_Instance.cpp b/code/RMG/RM_Instance.cpp new file mode 100644 index 0000000..000303a --- /dev/null +++ b/code/RMG/RM_Instance.cpp @@ -0,0 +1,191 @@ +#include "../server/exe_headers.h" + +#include "rm_headers.h" +#include "../qcommon/cm_terrainmap.h" + +/************************************************************************************************ + * CRMInstance::CRMInstance + * constructs a instnace object using the given parser group + * + * inputs: + * instance: parser group containing information about the instance + * + * return: + * none + * + ************************************************************************************************/ +CRMInstance::CRMInstance ( CGPGroup *instGroup, CRMInstanceFile& instFile ) +{ + mObjective = NULL; + mSpacingRadius = 0; + mFlattenRadius = 0; + mFilter[0] = mTeamFilter[0] = 0; + mArea = NULL; + mAutomapSymbol = 0; + mEntityID = 0; + mSide = 0; + mMirror = 0; + mFlattenHeight = 66; + mSpacingLine = 0; + mSurfaceSprites = true; + mLockOrigin = false; +} + +/************************************************************************************************ + * CRMInstance::PreSpawn + * Prepares the instance for spawning by flattening the ground under it + * + * inputs: + * landscape: landscape the instance will be spawned on + * + * return: + * true: spawn preparation successful + * false: spawn preparation failed + * + ************************************************************************************************/ +bool CRMInstance::PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + vec3_t origin; + CArea area; + + VectorCopy(GetOrigin(), origin); + + if (mMirror) + { + origin[0] = TheRandomMissionManager->GetLandScape()->GetBounds()[0][0] + TheRandomMissionManager->GetLandScape()->GetBounds()[1][0] - origin[0]; + origin[1] = TheRandomMissionManager->GetLandScape()->GetBounds()[0][1] + TheRandomMissionManager->GetLandScape()->GetBounds()[1][1] - origin[1]; + } + + const vec3_t& terxelSize = terrain->GetLandScape()->GetTerxelSize ( ); + const vec3pair_t& bounds = terrain->GetLandScape()->GetBounds(); + + // Align the instance to the center of a terxel + origin[0] = bounds[0][0] + (int)((origin[0] - bounds[0][0] + terxelSize[0] / 2) / terxelSize[0]) * terxelSize[0]; + origin[1] = bounds[0][1] + (int)((origin[1] - bounds[0][1] + terxelSize[1] / 2) / terxelSize[1]) * terxelSize[1]; + + + // This is BAD - By copying the mirrored origin back into the instance, you've now mirrored the original instance + // so when anything from this point on looks at the instance they'll be looking at a mirrored version but will be expecting the original + // so later in the spawn functions the instance will be re-mirrored, because it thinks the mInstances have not been changed +// VectorCopy(origin, GetOrigin()); + + // Flatten the area below the instance + if ( GetFlattenRadius() ) + { + area.Init( origin, GetFlattenRadius(), 0.0f, AT_NONE, 0, 0 ); + terrain->GetLandScape()->FlattenArea( &area, mFlattenHeight | (mSurfaceSprites?0:0x80), false, true, true ); + } + + return true; +} + +/************************************************************************************************ + * CRMInstance::PostSpawn + * Finishes the spawn by linking any objectives into the world that are associated with it + * + * inputs: + * landscape: landscape the instance was spawned on + * + * return: + * true: post spawn successfull + * false: post spawn failed + * + ************************************************************************************************/ +bool CRMInstance::PostSpawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + if ( mObjective ) + { + return mObjective->Link ( ); + } + + return true; +} +#ifndef DEDICATED +void CRMInstance::DrawAutomapSymbol() +{ + // draw proper symbol on map for instance + switch (GetAutomapSymbol()) + { + default: + case AUTOMAP_NONE: + if (HasObjective()) + CM_TM_AddObjective(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + case AUTOMAP_BLD: + CM_TM_AddBuilding(GetOrigin()[0], GetOrigin()[1], GetSide()); + if (HasObjective()) + CM_TM_AddObjective(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + case AUTOMAP_OBJ: + CM_TM_AddObjective(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + case AUTOMAP_START: + CM_TM_AddStart(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + case AUTOMAP_END: + CM_TM_AddEnd(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + case AUTOMAP_ENEMY: + if (HasObjective()) + CM_TM_AddObjective(GetOrigin()[0], GetOrigin()[1]); + if (1 == Cvar_VariableIntegerValue("rmg_automapshowall")) + CM_TM_AddNPC(GetOrigin()[0], GetOrigin()[1], false); + break; + case AUTOMAP_FRIEND: + if (HasObjective()) + CM_TM_AddObjective(GetOrigin()[0], GetOrigin()[1]); + if (1 == Cvar_VariableIntegerValue("rmg_automapshowall")) + CM_TM_AddNPC(GetOrigin()[0], GetOrigin()[1], true); + break; + case AUTOMAP_WALL: + CM_TM_AddWallRect(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + } +} +#endif // !DEDICATED +/************************************************************************************************ + * CRMInstance::Preview + * Renderings debug information about the instance + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +void CRMInstance::Preview ( const vec3_t from ) +{ +/* CEntity *tent; + + // Add a cylindar for the whole settlement + tent = G_TempEntity( GetOrigin(), EV_DEBUG_CYLINDER ); + VectorCopy( GetOrigin(), tent->s.origin2 ); + tent->s.pos.trBase[2] += 40; + tent->s.origin2[2] += 50; + tent->s.time = 1050 + ((int)(GetSpacingRadius())<<16); + tent->s.time2 = GetPreviewColor ( ); + G_AddTempEntity(tent); + + // Origin line + tent = G_TempEntity( GetOrigin ( ), EV_DEBUG_LINE ); + VectorCopy( GetOrigin(), tent->s.origin2 ); + tent->s.origin2[2] += 400; + tent->s.time = 1050; + tent->s.weapon = 10; + tent->s.time2 = (255<<24) + (255<<16) + (255<<8) + 255; + G_AddTempEntity(tent); + + if ( GetFlattenRadius ( ) ) + { + // Add a cylindar for the whole settlement + tent = G_TempEntity( GetOrigin(), EV_DEBUG_CYLINDER ); + VectorCopy( GetOrigin(), tent->s.origin2 ); + tent->s.pos.trBase[2] += 40; + tent->s.origin2[2] += 50; + tent->s.time = 1050 + ((int)(GetFlattenRadius ( ))<<16); + tent->s.time2 = (255<<24) + (80<<16) +(80<<8) + 80; + G_AddTempEntity(tent); + } +*/ +} diff --git a/code/RMG/RM_Instance.h b/code/RMG/RM_Instance.h new file mode 100644 index 0000000..6caba5f --- /dev/null +++ b/code/RMG/RM_Instance.h @@ -0,0 +1,122 @@ +#pragma once +#if !defined(RM_INSTANCE_H_INC) +#define RM_INSTANCE_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Instance.h") +#endif + +#if !defined(CM_LANDSCAPE_H_INC) +#include "../qcommon/cm_landscape.h" +#endif + +enum CRMAutomapSymbol +{ + AUTOMAP_NONE = 0, + AUTOMAP_BLD = 1, + AUTOMAP_OBJ = 2, + AUTOMAP_START= 3, + AUTOMAP_END = 4, + AUTOMAP_ENEMY= 5, + AUTOMAP_FRIEND=6, + AUTOMAP_WALL=7 +}; + +class CRMInstance +{ +protected: + char mFilter[MAX_QPATH]; // filter of entities inside of this + char mTeamFilter[MAX_QPATH]; // team specific filter + + vec3pair_t mBounds; // Bounding box for instance itself + + CRMArea* mArea; // Position of the instance + + CRMObjective* mObjective; // Objective associated with this instance + + // optional instance specific strings for objective + string mMessage; // message outputed when objective is completed + string mDescription; // description of objective + string mInfo; // more info for objective + + float mSpacingRadius; // Radius to space instances with + float mFlattenRadius; // Radius to flatten under instances + + int mSpacingLine; // Line of spacing radius's, forces locket + bool mLockOrigin; // Origin cant move + + bool mSurfaceSprites; // allow surface sprites under instance? + + int mAutomapSymbol; // show which symbol on automap 0=none + + int mEntityID; // id of entity spawned + int mSide; // blue or red side + int mMirror; // mirror origin, angle + + int mFlattenHeight; // height to flatten land + +public: + + CRMInstance ( CGPGroup* instance, CRMInstanceFile& instFile); + + virtual ~CRMInstance ( ) { } + + virtual bool IsValid ( ) { return true; } + + virtual bool PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ); + virtual bool Spawn ( CRandomTerrain* terrain, qboolean IsServer ) { return false; } + virtual bool PostSpawn ( CRandomTerrain* terrain, qboolean IsServer ); + + virtual void Preview ( const vec3_t from ); + + virtual void SetArea ( CRMAreaManager* amanager, CRMArea* area ) { mArea = area; } + virtual void SetFilter ( const char *filter ) { strcpy(mFilter, filter); } + virtual void SetTeamFilter ( const char *teamFilter ) { strcpy(mTeamFilter, teamFilter); } + void SetObjective ( CRMObjective* obj ) { mObjective = obj; } + CRMObjective* GetObjective (void) {return mObjective;} + bool HasObjective () {return mObjective != NULL;} + int GetAutomapSymbol () {return mAutomapSymbol;} + void DrawAutomapSymbol (); + const char* GetMessage(void) { return mMessage.c_str(); } + const char* GetDescription(void){ return mDescription.c_str(); } + const char* GetInfo(void) { return mInfo.c_str(); } + void SetMessage(const char* msg) { mMessage = msg; } + void SetDescription(const char* desc) { mDescription = desc; } + void SetInfo(const char* info) { mInfo = info; } + void SetSide(int side) {mSide = side;} + int GetSide ( ) {return mSide;} + + // NOTE: should consider making SetMirror also set all other variables that need flipping + // like the origin and Side, etc... Otherwise an Instance may have had it's origin flipped + // but then later will have mMirror set to false, but the origin is still flipped. So any functions + // that look at the instance later will see mMirror set to false, but not realize the origin has ALREADY been flipped + virtual void SetMirror(int mirror) { mMirror = mirror;} + int GetMirror ( ) { return mMirror;} + + virtual bool GetSurfaceSprites ( ) { return mSurfaceSprites; } + + virtual bool GetLockOrigin ( ) { return mLockOrigin; } + virtual int GetSpacingLine ( ) { return mSpacingLine; } + + virtual int GetPreviewColor ( ) { return 0; } + virtual float GetSpacingRadius ( ) { return mSpacingRadius; } + virtual float GetFlattenRadius ( ) { return mFlattenRadius; } + const char *GetFilter ( ) { return mFilter; } + const char *GetTeamFilter ( ) { return mTeamFilter; } + + CRMArea& GetArea ( ) { return *mArea; } + vec_t* GetOrigin ( ) {return mArea->GetOrigin(); } + float GetAngle ( ) {return mArea->GetAngle();} + void SetAngle(float ang ) { mArea->SetAngle(ang);} + const vec3pair_t& GetBounds(void) const { return(mBounds); } + + void SetFlattenHeight ( int height ) { mFlattenHeight = height; } + int GetFlattenHeight ( void ) { return mFlattenHeight; } + + void SetSpacingRadius (float spacing) { mSpacingRadius = spacing; } +}; + +typedef list::iterator rmInstanceIter_t; +typedef list rmInstanceList_t; + +#endif diff --git a/code/RMG/RM_InstanceFile.cpp b/code/RMG/RM_InstanceFile.cpp new file mode 100644 index 0000000..bfeb1a8 --- /dev/null +++ b/code/RMG/RM_InstanceFile.cpp @@ -0,0 +1,200 @@ +/************************************************************************************************ + * + * RM_InstanceFile.cpp + * + * implements the CRMInstanceFile class. This class provides functionality to load + * and create instances from an instance file. First call Open to open the instance file and + * then use CreateInstance to create new instances. When finished call Close to cleanup. + * + ************************************************************************************************/ + +#include "../server/exe_headers.h" + +#include "rm_headers.h" + +//#include "rm_instance_npc.h" +#include "rm_instance_bsp.h" +#include "rm_instance_random.h" +#include "rm_instance_group.h" +#include "rm_instance_void.h" + +/************************************************************************************************ + * CRMInstanceFile::CRMInstanceFile + * constructor + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMInstanceFile::CRMInstanceFile ( ) +{ + mInstances = NULL; +} + +/************************************************************************************************ + * CRMInstanceFile::~CRMInstanceFile + * Destroys the instance file by freeing the parser + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMInstanceFile::~CRMInstanceFile ( ) +{ + Close ( ); +} + +/************************************************************************************************ + * CRMInstanceFile::Open + * Opens the given instance file and prepares it for use in instance creation + * + * inputs: + * instance: Name of instance to open. Note that the root path will be automatically + * added and shouldnt be included in the given name + * + * return: + * true: instance file successfully loaded + * false: instance file could not be loaded for some reason + * + ************************************************************************************************/ +bool CRMInstanceFile::Open ( const char* instance ) +{ + char instanceDef[MAX_QPATH]; + CGPGroup *basegroup; + + // Build the filename + Com_sprintf(instanceDef, MAX_QPATH, "ext_data/rmg/%s.instance", instance ); + +#ifndef FINAL_BUILD + // Debug message + Com_Printf("CM_Terrain: Loading and parsing instanceDef %s.....\n", instance); +#endif + + // Parse the text file using the generic parser + if(!Com_ParseTextFile(instanceDef, mParser )) + { + Com_sprintf(instanceDef, MAX_QPATH, "ext_data/arioche/%s.instance", instance ); + if(!Com_ParseTextFile(instanceDef, mParser )) + { + Com_Printf(va("CM_Terrain: Could not open instance file '%s'\n", instanceDef)); + return false; + } + } + + // The whole file.... + basegroup = mParser.GetBaseParseGroup(); + + // The root { } struct + mInstances = basegroup->GetSubGroups(); + + // The "instances" { } structure + mInstances = mInstances->GetSubGroups ( ); + + return true; +} + +/************************************************************************************************ + * CRMInstanceFile::Close + * Closes an open instance file + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +void CRMInstanceFile::Close ( void ) +{ + // If not open then dont close it + if ( NULL == mInstances ) + { + return; + } + mParser.Clean(); + + mInstances = NULL; +} + +/************************************************************************************************ + * CRMInstanceFile::CreateInstance + * Creates an instance (to be freed by caller) using the given instance name. + * + * inputs: + * name: Name of the instance to read from the instance file + * + * return: + * NULL: instance could not be read from the instance file + * NON-NULL: instance created and returned for further use + * + ************************************************************************************************/ +CRMInstance* CRMInstanceFile::CreateInstance ( const char* name ) +{ + static int instanceID = 0; + + CGPGroup* group; + CRMInstance* instance; + + // Make sure we were loaded + assert ( mInstances ); + + // Search through the instances for the one with the given name + for ( group = mInstances; group; group = group->GetNext ( ) ) + { + // Skip it if the name doesnt match + if ( stricmp ( name, group->FindPairValue ( "name", "" ) ) ) + { + continue; + } + + // Handle the various forms of instance types + if ( !stricmp ( group->GetName ( ), "bsp" ) ) + { + instance = new CRMBSPInstance ( group, *this ); + } + else if ( !stricmp ( group->GetName ( ), "npc" ) ) + { +// instance = new CRMNPCInstance ( group, *this ); + continue; + } + else if ( !stricmp ( group->GetName ( ), "group" ) ) + { + instance = new CRMGroupInstance ( group, *this ); + } + else if ( !stricmp ( group->GetName ( ), "random" ) ) + { + instance = new CRMRandomInstance ( group, *this ); + } + else if ( !stricmp ( group->GetName ( ), "void" ) ) + { + instance = new CRMVoidInstance ( group, *this ); + } + else + { + continue; + } + + // If the instance isnt valid after being created then delete it + if ( !instance->IsValid ( ) ) + { + delete instance; + return NULL; + } + + // The instance was successfully created so return it + return instance; + } + +#ifndef FINAL_BUILD + // The instance wasnt found in the file so report it + Com_Printf(va("WARNING: Instance '%s' was not found in the active instance file\n", name )); +#endif + + return NULL; +} diff --git a/code/RMG/RM_InstanceFile.h b/code/RMG/RM_InstanceFile.h new file mode 100644 index 0000000..92f8ebc --- /dev/null +++ b/code/RMG/RM_InstanceFile.h @@ -0,0 +1,28 @@ +#pragma once +#if !defined(RM_INSTANCEFILE_H_INC) +#define RM_INSTANCEFILE_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_InstanceFile.h") +#endif + +class CRMInstance; + +class CRMInstanceFile +{ +public: + + CRMInstanceFile ( ); + ~CRMInstanceFile ( ); + + bool Open ( const char* instance ); + void Close ( void ); + CRMInstance* CreateInstance ( const char* name ); + +protected: + + CGenericParser2 mParser; + CGPGroup* mInstances; +}; + +#endif \ No newline at end of file diff --git a/code/RMG/RM_Instance_BSP.cpp b/code/RMG/RM_Instance_BSP.cpp new file mode 100644 index 0000000..919191d --- /dev/null +++ b/code/RMG/RM_Instance_BSP.cpp @@ -0,0 +1,294 @@ +/************************************************************************************************ + * + * RM_Instance_BSP.cpp + * + * Implements the CRMBSPInstance class. This class is reponsible for parsing a + * bsp instance as well as spawning it into a landscape. + * + ************************************************************************************************/ + +#include "../server/exe_headers.h" + +#include "../qcommon/cm_local.h" +#include "../server/server.h" +#include "rm_headers.h" + +#include "rm_instance_bsp.h" + +#include "../client/vmachine.h" + +/************************************************************************************************ + * CRMBSPInstance::CRMBSPInstance + * constructs a building instance object using the given parser group + * + * inputs: + * instance: parser group containing information about the building instance + * + * return: + * none + * + ************************************************************************************************/ +CRMBSPInstance::CRMBSPInstance(CGPGroup *instGroup, CRMInstanceFile& instFile) : CRMInstance ( instGroup, instFile ) +{ + strcpy(mBsp, instGroup->FindPairValue("file", "")); + + mAngleVariance = DEG2RAD(atof(instGroup->FindPairValue("anglevariance", "0"))); + mBaseAngle = DEG2RAD(atof(instGroup->FindPairValue("baseangle", "0"))); + mAngleDiff = DEG2RAD(atof(instGroup->FindPairValue("anglediff", "0"))); + mSpacingRadius = atof( instGroup->FindPairValue ( "spacing", "100" ) ); + mSpacingLine = atoi( instGroup->FindPairValue ( "spacingline", "0" ) ); + mSurfaceSprites = (!Q_stricmp ( instGroup->FindPairValue ( "surfacesprites", "no" ), "yes")) ? true : false; + mLockOrigin = (!Q_stricmp ( instGroup->FindPairValue ( "lockorigin", "no" ), "yes")) ? true : false; + mFlattenRadius = atof( instGroup->FindPairValue ( "flatten", "0" ) ); + mHoleRadius = atof( instGroup->FindPairValue ( "hole", "0" ) ); + + const char * automapSymName = instGroup->FindPairValue ( "automap_symbol", "building" ); + if (0 == strcmpi(automapSymName, "none")) mAutomapSymbol = AUTOMAP_NONE ; + else if (0 == strcmpi(automapSymName, "building")) mAutomapSymbol = AUTOMAP_BLD ; + else if (0 == strcmpi(automapSymName, "objective")) mAutomapSymbol = AUTOMAP_OBJ ; + else if (0 == strcmpi(automapSymName, "start")) mAutomapSymbol = AUTOMAP_START; + else if (0 == strcmpi(automapSymName, "end")) mAutomapSymbol = AUTOMAP_END ; + else if (0 == strcmpi(automapSymName, "enemy")) mAutomapSymbol = AUTOMAP_ENEMY; + else if (0 == strcmpi(automapSymName, "friend")) mAutomapSymbol = AUTOMAP_FRIEND; + else if (0 == strcmpi(automapSymName, "wall")) mAutomapSymbol = AUTOMAP_WALL; + else mAutomapSymbol = atoi( automapSymName ); + + // optional instance objective strings + SetMessage(instGroup->FindPairValue("objective_message","")); + SetDescription(instGroup->FindPairValue("objective_description","")); + SetInfo(instGroup->FindPairValue("objective_info","")); + + mBounds[0][0] = 0; + mBounds[0][1] = 0; + mBounds[1][0] = 0; + mBounds[1][1] = 0; + + mBaseAngle += (TheRandomMissionManager->GetLandScape()->irand(0,mAngleVariance) - mAngleVariance/2); +} + +/************************************************************************************************ + * CRMBSPInstance::Spawn + * spawns a bsp into the world using the previously aquired origin + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +bool CRMBSPInstance::Spawn ( CRandomTerrain* terrain, qboolean IsServer) +{ +#ifndef PRE_RELEASE_DEMO +// TEntity* ent; + float yaw; + char temp[10000]; + char *savePtr; + vec3_t origin; + vec3_t notmirrored; + float water_level = terrain->GetLandScape()->GetWaterHeight(); + + const vec3_t& terxelSize = terrain->GetLandScape()->GetTerxelSize ( ); + const vec3pair_t& bounds = terrain->GetLandScape()->GetBounds(); + + // If this entity somehow lost its collision flag then boot it + if ( !GetArea().IsCollisionEnabled ( ) ) + { + return false; + } + + // copy out the unmirrored version + VectorCopy(GetOrigin(), notmirrored); + + // we want to mirror it before determining the Z value just in case the landscape isn't perfectly mirrored + if (mMirror) + { + GetOrigin()[0] = TheRandomMissionManager->GetLandScape()->GetBounds()[0][0] + TheRandomMissionManager->GetLandScape()->GetBounds()[1][0] - GetOrigin()[0]; + GetOrigin()[1] = TheRandomMissionManager->GetLandScape()->GetBounds()[0][1] + TheRandomMissionManager->GetLandScape()->GetBounds()[1][1] - GetOrigin()[1]; + } + + // Align the instance to the center of a terxel + GetOrigin ( )[0] = bounds[0][0] + (int)((GetOrigin ( )[0] - bounds[0][0] + terxelSize[0] / 2) / terxelSize[0]) * terxelSize[0]; + GetOrigin ( )[1] = bounds[0][1] + (int)((GetOrigin ( )[1] - bounds[0][1] + terxelSize[1] / 2) / terxelSize[1]) * terxelSize[1]; + + // Make sure the bsp is resting on the ground, not below or above it + // NOTE: This check is basically saying "is this instance not a bridge", because when instances are created they are all + // placed above the world's Z boundary, EXCEPT FOR BRIDGES. So this call to GetWorldHeight will move all other instances down to + // ground level except bridges + if ( GetOrigin()[2] > terrain->GetBounds()[1][2] ) + { + if( GetFlattenRadius() ) + { + terrain->GetLandScape()->GetWorldHeight ( GetOrigin(), GetBounds ( ), false ); + GetOrigin()[2] += 5; + } + else if (IsServer) + { // if this instance does not flatten the ground around it, do a trace to more accurately determine its Z value + trace_t tr; + vec3_t end; + vec3_t start; + + VectorCopy(GetOrigin(), end); + VectorCopy(GetOrigin(), start); + // start the trace below the top height of the landscape + start[2] = TheRandomMissionManager->GetLandScape()->GetBounds()[1][2] - 1; + // end the trace at the bottom of the world + end[2] = MIN_WORLD_COORD; + + memset ( &tr, 0, sizeof ( tr ) ); + SV_Trace( &tr, start, vec3_origin, vec3_origin, end, ENTITYNUM_NONE, CONTENTS_TERRAIN|CONTENTS_SOLID, G2_NOCOLLIDE, 0); //qfalse, 0, 10 ); + + if( !(tr.contents & CONTENTS_TERRAIN) || (tr.fraction == 1.0) ) + { + if ( 0 ) + assert(0); // this should never happen + + // restore the unmirrored origin + VectorCopy( notmirrored, GetOrigin() ); + // don't spawn + return false; + } + // assign the Z-value to wherever it hit the terrain + GetOrigin()[2] = tr.endpos[2]; + // lower it a little, otherwise the bottom of the instance might be exposed if on some weird sloped terrain + GetOrigin()[2] -= 16; // FIXME: would it be better to use a number related to the instance itself like 1/5 it's height or something... + } + + } + else + { + terrain->GetLandScape()->GetWorldHeight ( GetOrigin(), GetBounds ( ), true ); + } + + // save away the origin + VectorCopy(GetOrigin(), origin); + // make sure not to spawn if in water + if (!HasObjective() && GetOrigin()[2] < water_level) + return false; + // restore the origin + VectorCopy(origin, GetOrigin()); + + if (mMirror) + { // change blue things to red for symmetric maps + if (strlen(mFilter) > 0) + { + char * blue = strstr(mFilter,"blue"); + if (blue) + { + blue[0] = (char) 0; + strcat(mFilter, "red"); + SetSide(SIDE_RED); + } + } + if (strlen(mTeamFilter) > 0) + { + char * blue = strstr(mTeamFilter,"blue"); + if (blue) + { + strcpy(mTeamFilter, "red"); + SetSide(SIDE_RED); + } + } + yaw = RAD2DEG(mArea->GetAngle() + mBaseAngle) + 180; + } + else + { + yaw = RAD2DEG(mArea->GetAngle() + mBaseAngle); + } + +/* + if( TheRandomMissionManager->GetMission()->GetSymmetric() ) + { + vec3_t diagonal; + vec3_t lineToPoint; + vec3_t mins; + vec3_t maxs; + vec3_t point; + vec3_t vProj; + vec3_t vec; + float distance; + + VectorCopy( TheRandomMissionManager->GetLandScape()->GetBounds()[1], maxs ); + VectorCopy( TheRandomMissionManager->GetLandScape()->GetBounds()[0], mins ); + VectorCopy( GetOrigin(), point ); + mins[2] = maxs[2] = point[2] = 0; + VectorSubtract( point, mins, lineToPoint ); + VectorSubtract( maxs, mins, diagonal); + + + VectorNormalize(diagonal); + VectorMA( mins, DotProduct(lineToPoint, diagonal), diagonal, vProj); + VectorSubtract(point, vProj, vec ); + distance = VectorLength(vec); + + // if an instance is too close to the imaginary diagonal that cuts the world in half, don't spawn it + // otherwise you can get overlapping instances + if( distance < GetSpacingRadius() ) + { +#ifdef _DEBUG + mAutomapSymbol = AUTOMAP_END; +#endif + if( !HasObjective() ) + { + return false; + } + } + } +*/ + + // Spawn in the bsp model + sprintf(temp, + "{\n" + "\"classname\" \"misc_bsp\"\n" + "\"bspmodel\" \"%s\"\n" + "\"origin\" \"%f %f %f\"\n" + "\"angles\" \"0 %f 0\"\n" + "\"filter\" \"%s\"\n" + "\"teamfilter\" \"%s\"\n" + "\"spacing\" \"%d\"\n" + "\"flatten\" \"%d\"\n" + "}\n", + mBsp, + GetOrigin()[0], GetOrigin()[1], GetOrigin()[2], + AngleNormalize360(yaw), + mFilter, + mTeamFilter, + (int)GetSpacingRadius(), + (int)GetFlattenRadius() + ); + + if (IsServer) + { // only allow for true spawning on the server + savePtr = sv.entityParsePoint; + sv.entityParsePoint = temp; +// VM_Call( cgvm, GAME_SPAWN_RMG_ENTITY ); + // char *s; + int bufferSize = 1024; + char buffer[1024]; + + // s = COM_Parse( (const char **)&sv.entityParsePoint ); + Q_strncpyz( buffer, sv.entityParsePoint, bufferSize ); + if ( sv.entityParsePoint && sv.entityParsePoint[0] ) + { + ge->GameSpawnRMGEntity(sv.entityParsePoint); + } + sv.entityParsePoint = savePtr; + } + + +#ifndef DEDICATED + DrawAutomapSymbol(); +#endif + Com_DPrintf( "RMG: Building '%s' spawned at (%f %f %f)\n", mBsp, GetOrigin()[0], GetOrigin()[1], GetOrigin()[2] ); + // now restore the instances un-mirrored origin + // NOTE: all this origin flipping, setting the side etc... should be done when mMirror is set + // because right after this function is called, mMirror is set to 0 but all the instance data is STILL MIRRORED -- not good + VectorCopy(notmirrored, GetOrigin()); + +#endif // PRE_RELEASE_DEMO + + return true; +} + + + diff --git a/code/RMG/RM_Instance_BSP.h b/code/RMG/RM_Instance_BSP.h new file mode 100644 index 0000000..055c3c1 --- /dev/null +++ b/code/RMG/RM_Instance_BSP.h @@ -0,0 +1,35 @@ +#pragma once +#if !defined(RM_INSTANCE_BSP_H_INC) +#define RM_INSTANCE_BSP_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Instance_BSP.h") +#endif + +class CRMBSPInstance : public CRMInstance +{ +private: + + char mBsp[MAX_QPATH]; + float mAngleVariance; + float mBaseAngle; + float mAngleDiff; + + float mHoleRadius; + +public: + + CRMBSPInstance ( CGPGroup *instance, CRMInstanceFile& instFile ); + + virtual int GetPreviewColor ( ) { return (255<<24)+255; } + + virtual float GetHoleRadius ( ) { return mHoleRadius; } + + virtual bool Spawn ( CRandomTerrain* terrain, qboolean IsServer ); + + const char* GetModelName (void) const { return(mBsp); } + float GetAngleDiff (void) const { return(mAngleDiff); } + bool GetAngularType (void) const { return(mAngleDiff != 0.0f); } +}; + +#endif \ No newline at end of file diff --git a/code/RMG/RM_Instance_Group.cpp b/code/RMG/RM_Instance_Group.cpp new file mode 100644 index 0000000..4ebefad --- /dev/null +++ b/code/RMG/RM_Instance_Group.cpp @@ -0,0 +1,343 @@ +/************************************************************************************************ + * + * RM_Instance_Group.cpp + * + * Implements the CRMGroupInstance class. This class is reponsible for parsing a + * group instance as well as spawning it into a landscape. + * + ************************************************************************************************/ + +#include "../server/exe_headers.h" + +#include "rm_headers.h" + +#include "rm_instance_group.h" + +/************************************************************************************************ + * CRMGroupInstance::CRMGroupInstance + * constructur + * + * inputs: + * settlementID: ID of the settlement being created + * + * return: + * none + * + ************************************************************************************************/ +CRMGroupInstance::CRMGroupInstance ( CGPGroup *instGroup, CRMInstanceFile& instFile ) + : CRMInstance ( instGroup, instFile ) +{ + // Grab the padding and confine radius + mPaddingSize = atof ( instGroup->FindPairValue ( "padding", va("%i", TheRandomMissionManager->GetMission()->GetDefaultPadding() ) ) ); + mConfineRadius = atof ( instGroup->FindPairValue ( "confine", "0" ) ); + + const char * automapSymName = instGroup->FindPairValue ( "automap_symbol", "none" ); + if (0 == strcmpi(automapSymName, "none")) mAutomapSymbol = AUTOMAP_NONE ; + else if (0 == strcmpi(automapSymName, "building")) mAutomapSymbol = AUTOMAP_BLD ; + else if (0 == strcmpi(automapSymName, "objective")) mAutomapSymbol = AUTOMAP_OBJ ; + else if (0 == strcmpi(automapSymName, "start")) mAutomapSymbol = AUTOMAP_START; + else if (0 == strcmpi(automapSymName, "end")) mAutomapSymbol = AUTOMAP_END ; + else if (0 == strcmpi(automapSymName, "enemy")) mAutomapSymbol = AUTOMAP_ENEMY; + else if (0 == strcmpi(automapSymName, "friend")) mAutomapSymbol = AUTOMAP_FRIEND; + else mAutomapSymbol = atoi( automapSymName ); + + // optional instance objective strings + SetMessage(instGroup->FindPairValue("objective_message","")); + SetDescription(instGroup->FindPairValue("objective_description","")); + SetInfo(instGroup->FindPairValue("objective_info","")); + + // Iterate through the sub groups to determine the instances which make up the group + instGroup = instGroup->GetSubGroups ( ); + + while ( instGroup ) + { + CRMInstance* instance; + const char* name; + int mincount; + int maxcount; + int count; + float minrange; + float maxrange; + + // Make sure only instances are specified as sub groups + assert ( 0 == stricmp ( instGroup->GetName ( ), "instance" ) ); + + // Grab the name + name = instGroup->FindPairValue ( "name", "" ); + + // Grab the range information + minrange = atof(instGroup->FindPairValue ( "minrange", "0" ) ); + maxrange = atof(instGroup->FindPairValue ( "maxrange", "0" ) ); + + // Grab the count information and randomly generate a count value + mincount = atoi(instGroup->FindPairValue ( "mincount", "1" ) ); + maxcount = atoi(instGroup->FindPairValue ( "maxcount", "1" ) ); + count = mincount; + + if ( maxcount > mincount ) + { + count += (TheRandomMissionManager->GetLandScape()->irand(0, maxcount-mincount)); + } + + // For each count create and add the instance + for ( ; count ; count -- ) + { + // Create the instance + instance = instFile.CreateInstance ( name ); + + // Skip this instance if it couldnt be created for some reason. The CreateInstance + // method will report an error so no need to do so here. + if ( NULL == instance ) + { + continue; + } + + // Set the min and max range for the instance + instance->SetFilter(mFilter); + instance->SetTeamFilter(mTeamFilter); + + // Add the instance to the list + mInstances.push_back ( instance ); + } + + // Next sub group + instGroup = instGroup->GetNext ( ); + } +} + +/************************************************************************************************ + * CRMGroupInstance::~CRMGroupInstance + * Removes all buildings and inhabitants + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMGroupInstance::~CRMGroupInstance(void) +{ + // Cleanup + RemoveInstances ( ); +} + +/************************************************************************************************ + * CRMGroupInstance::SetFilter + * Sets a filter used to exclude instances + * + * inputs: + * filter: filter name + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::SetFilter( const char *filter ) +{ + rmInstanceIter_t it; + + CRMInstance::SetFilter(filter); + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + (*it)->SetFilter(filter); + } +} + +/************************************************************************************************ + * CRMGroupInstance::SetTeamFilter + * Sets the filter used to exclude team based instances + * + * inputs: + * teamFilter: filter name + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::SetTeamFilter( const char *teamFilter ) +{ + rmInstanceIter_t it; + + CRMInstance::SetTeamFilter(teamFilter); + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + (*it)->SetTeamFilter(teamFilter); + } +} + +/************************************************************************************************ + * CRMGroupInstance::SetMirror + * Sets the flag to mirror an instance on map + * + * inputs: + * mirror + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::SetMirror(int mirror) +{ + rmInstanceIter_t it; + + CRMInstance::SetMirror(mirror); + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + (*it)->SetMirror(mirror); + } +} + + +/************************************************************************************************ + * CRMGroupInstance::RemoveInstances + * Removes all instances associated with the group + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::RemoveInstances ( ) +{ + rmInstanceIter_t it; + + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + delete *it; + } + + mInstances.clear(); +} + +/************************************************************************************************ + * CRMGroupInstance::PreSpawn + * Prepares the group for spawning by + * + * inputs: + * landscape: landscape to calculate the position within + * instance: instance to calculate the position for + * + * return: + * none + * + ************************************************************************************************/ +bool CRMGroupInstance::PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + rmInstanceIter_t it; + + for(it = mInstances.begin(); it != mInstances.end(); it++ ) + { + CRMInstance* instance = *it; + + instance->SetFlattenHeight ( mFlattenHeight ); + + // Add the instance to the landscape now + instance->PreSpawn ( terrain, IsServer ); + } + + return CRMInstance::PreSpawn ( terrain, IsServer ); +} + +/************************************************************************************************ + * CRMGroupInstance::Spawn + * Adds the group instance to the given landscape using the specified origin. All sub instances + * will be added to the landscape within their min and max range from the origin. + * + * inputs: + * landscape: landscape to add the instance group to + * origin: origin of the instance group + * + * return: + * none + * + ************************************************************************************************/ +bool CRMGroupInstance::Spawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + rmInstanceIter_t it; + + // Spawn all the instances associated with this group + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + CRMInstance* instance = *it; + instance->SetSide(GetSide()); // which side owns it? + + // Add the instance to the landscape now + instance->Spawn ( terrain, IsServer ); + } +#ifndef DEDICATED + DrawAutomapSymbol(); +#endif + return true; +} + +/************************************************************************************************ + * CRMGroupInstance::Preview + * Renders debug information for the instance + * + * inputs: + * from: point to render the preview from + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::Preview ( const vec3_t from ) +{ + rmInstanceIter_t it; + + CRMInstance::Preview ( from ); + + // Render all the instances + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + CRMInstance* instance = *it; + + instance->Preview ( from ); + } +} + +/************************************************************************************************ + * CRMGroupInstance::SetArea + * Overidden to make sure the groups area doesnt eat up any room. The collision on the + * groups area will be turned off + * + * inputs: + * area: area to set + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::SetArea ( CRMAreaManager* amanager, CRMArea* area ) +{ + rmInstanceIter_t it; + + bool collide = area->IsCollisionEnabled ( ); + + // Disable collision + area->EnableCollision ( false ); + + // Do what really needs to get done + CRMInstance::SetArea ( amanager, area ); + + // Prepare for spawn by calculating all the positions of the sub instances + // and flattening the ground below them. + for(it = mInstances.begin(); it != mInstances.end(); it++ ) + { + CRMInstance *instance = *it; + CRMArea *newarea; + vec3_t origin; + + // Drop it in the center of the group for now + origin[0] = GetOrigin()[0]; + origin[1] = GetOrigin()[1]; + origin[2] = 2500; + + // Set the area of position + newarea = amanager->CreateArea ( origin, instance->GetSpacingRadius(), instance->GetSpacingLine(), mPaddingSize, mConfineRadius, GetOrigin(), GetOrigin(), instance->GetFlattenRadius()?true:false, collide, instance->GetLockOrigin(), area->GetSymmetric ( ) ); + instance->SetArea ( amanager, newarea ); + } +} diff --git a/code/RMG/RM_Instance_Group.h b/code/RMG/RM_Instance_Group.h new file mode 100644 index 0000000..0620ae4 --- /dev/null +++ b/code/RMG/RM_Instance_Group.h @@ -0,0 +1,41 @@ +#pragma once +#if !defined(RM_INSTANCE_GROUP_H_INC) +#define RM_INSTANCE_GROUP_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Instance_Group.h") +#endif + +class CRMGroupInstance : public CRMInstance +{ +protected: + + rmInstanceList_t mInstances; + float mConfineRadius; + float mPaddingSize; + +public: + + CRMGroupInstance( CGPGroup* instGroup, CRMInstanceFile& instFile); + ~CRMGroupInstance(); + + virtual bool PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ); + virtual bool Spawn ( CRandomTerrain* terrain, qboolean IsServer ); + + virtual void Preview ( const vec3_t from ); + + virtual void SetFilter ( const char *filter ); + virtual void SetTeamFilter ( const char *teamFilter ); + virtual void SetArea ( CRMAreaManager* amanager, CRMArea* area ); + + virtual int GetPreviewColor ( ) { return (255<<24)+(255<<8); } + virtual float GetSpacingRadius ( ) { return 0; } + virtual float GetFlattenRadius ( ) { return 0; } + virtual void SetMirror(int mirror); + +protected: + + void RemoveInstances ( ); +}; + +#endif \ No newline at end of file diff --git a/code/RMG/RM_Instance_Random.cpp b/code/RMG/RM_Instance_Random.cpp new file mode 100644 index 0000000..003eb35 --- /dev/null +++ b/code/RMG/RM_Instance_Random.cpp @@ -0,0 +1,187 @@ +/************************************************************************************************ + * + * RM_Instance_Random.cpp + * + * Implements the CRMRandomInstance class. This class is reponsible for parsing a + * random instance as well as spawning it into a landscape. + * + ************************************************************************************************/ + +#include "../server/exe_headers.h" + +#include "rm_headers.h" + +#include "rm_instance_random.h" + +/************************************************************************************************ + * CRMRandomInstance::CRMRandomInstance + * constructs a random instance by choosing one of the sub instances and creating it + * + * inputs: + * instGroup: parser group containing infromation about this instance + * instFile: reference to an open instance file for creating sub instances + * + * return: + * none + * + ************************************************************************************************/ +CRMRandomInstance::CRMRandomInstance ( CGPGroup *instGroup, CRMInstanceFile& instFile ) + : CRMInstance ( instGroup, instFile ) +{ + CGPGroup* group; + CGPGroup* groups[MAX_RANDOM_INSTANCES]; + int numGroups; + + // Build a list of the groups one can be chosen + for ( numGroups = 0, group = instGroup->GetSubGroups ( ); + group; + group = group->GetNext ( ) ) + { + // If this isnt an instance group then skip it + if ( stricmp ( group->GetName ( ), "instance" ) ) + { + continue; + } + + int multiplier = atoi(group->FindPairValue ( "multiplier", "1" )); + for ( ; multiplier > 0 && numGroups < MAX_RANDOM_INSTANCES; multiplier -- ) + { + groups[numGroups++] = group; + } + } + + // No groups, no instance + if ( !numGroups ) + { + // Initialize this now + mInstance = NULL; + + Com_Printf ( "WARNING: No sub instances specified for random instance '%s'\n", group->FindPairValue ( "name", "unknown" ) ); + return; + } + + // Now choose a group to parse + instGroup = groups[TheRandomMissionManager->GetLandScape()->irand(0,numGroups-1)]; + + // Create the child instance now. If the instance create fails then the + // IsValid routine will return false and this instance wont be added + mInstance = instFile.CreateInstance ( instGroup->FindPairValue ( "name", "" ) ); + mInstance->SetFilter(mFilter); + mInstance->SetTeamFilter(mTeamFilter); + + mAutomapSymbol = mInstance->GetAutomapSymbol(); + + SetMessage(mInstance->GetMessage()); + SetDescription(mInstance->GetDescription()); + SetInfo(mInstance->GetInfo()); +} + +/************************************************************************************************ + * CRMRandomInstance::~CRMRandomInstance + * Deletes the sub instance + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMRandomInstance::~CRMRandomInstance(void) +{ + if ( mInstance ) + { + delete mInstance; + } +} + +void CRMRandomInstance::SetMirror(int mirror) +{ + CRMInstance::SetMirror(mirror); + if (mInstance) + { + mInstance->SetMirror(mirror); + } +} + +void CRMRandomInstance::SetFilter( const char *filter ) +{ + CRMInstance::SetFilter(filter); + if (mInstance) + { + mInstance->SetFilter(filter); + } +} + +void CRMRandomInstance::SetTeamFilter( const char *teamFilter ) +{ + CRMInstance::SetTeamFilter(teamFilter); + if (mInstance) + { + mInstance->SetTeamFilter(teamFilter); + } +} + +/************************************************************************************************ + * CRMRandomInstance::PreSpawn + * Prepares for the spawn of the random instance + * + * inputs: + * landscape: landscape object this instance will be spawned on + * + * return: + * true: preparation successful + * false: preparation failed + * + ************************************************************************************************/ +bool CRMRandomInstance::PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + assert ( mInstance ); + + mInstance->SetFlattenHeight ( GetFlattenHeight( ) ); + + return mInstance->PreSpawn ( terrain, IsServer ); +} + +/************************************************************************************************ + * CRMRandomInstance::Spawn + * Spawns the instance onto the landscape + * + * inputs: + * landscape: landscape object this instance will be spawned on + * + * return: + * true: spawn successful + * false: spawn failed + * + ************************************************************************************************/ +bool CRMRandomInstance::Spawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + mInstance->SetObjective(GetObjective()); + mInstance->SetSide(GetSide()); + + if ( !mInstance->Spawn ( terrain, IsServer ) ) + { + return false; + } + + return true; +} + +/************************************************************************************************ + * CRMRandomInstance::SetArea + * Forwards the given area off to the internal instance + * + * inputs: + * area: area to be set + * + * return: + * none + * + ************************************************************************************************/ +void CRMRandomInstance::SetArea ( CRMAreaManager* amanager, CRMArea* area ) +{ + CRMInstance::SetArea ( amanager, area ); + + mInstance->SetArea ( amanager, mArea ); +} diff --git a/code/RMG/RM_Instance_Random.h b/code/RMG/RM_Instance_Random.h new file mode 100644 index 0000000..474dfb9 --- /dev/null +++ b/code/RMG/RM_Instance_Random.h @@ -0,0 +1,40 @@ +#pragma once +#if !defined(RM_INSTANCE_RANDOM_H_INC) +#define RM_INSTANCE_RANDOM_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Instance_Random.h") +#endif + +#define MAX_RANDOM_INSTANCES 64 + +class CRMRandomInstance : public CRMInstance +{ +protected: + + CRMInstance* mInstance; + +public: + + CRMRandomInstance ( CGPGroup* instGroup, CRMInstanceFile& instFile ); + ~CRMRandomInstance ( ); + + virtual bool IsValid ( ) { return mInstance==NULL?false:true; } + + virtual int GetPreviewColor ( ) { return mInstance->GetPreviewColor ( ); } + + virtual float GetSpacingRadius ( ) { return mInstance->GetSpacingRadius ( ); } + virtual int GetSpacingLine ( ) { return mInstance->GetSpacingLine ( ); } + virtual float GetFlattenRadius ( ) { return mInstance->GetFlattenRadius ( ); } + virtual bool GetLockOrigin ( ) { return mInstance->GetLockOrigin ( ); } + + virtual void SetFilter ( const char *filter ); + virtual void SetTeamFilter ( const char *teamFilter ); + virtual void SetArea ( CRMAreaManager* amanager, CRMArea* area ); + virtual void SetMirror (int mirror); + + virtual bool PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ); + virtual bool Spawn ( CRandomTerrain* terrain, qboolean IsServer ); +}; + +#endif \ No newline at end of file diff --git a/code/RMG/RM_Instance_Void.cpp b/code/RMG/RM_Instance_Void.cpp new file mode 100644 index 0000000..b092c9d --- /dev/null +++ b/code/RMG/RM_Instance_Void.cpp @@ -0,0 +1,53 @@ +/************************************************************************************************ + * + * RM_Instance_Void.cpp + * + * Implements the CRMVoidInstance class. This class just adds a void into the + * area manager to help space things out. + * + ************************************************************************************************/ + +#include "../server/exe_headers.h" + +#include "rm_headers.h" + +#include "rm_instance_void.h" + +/************************************************************************************************ + * CRMVoidInstance::CRMVoidInstance + * constructs a void instance + * + * inputs: + * instGroup: parser group containing infromation about this instance + * instFile: reference to an open instance file for creating sub instances + * + * return: + * none + * + ************************************************************************************************/ +CRMVoidInstance::CRMVoidInstance ( CGPGroup *instGroup, CRMInstanceFile& instFile ) + : CRMInstance ( instGroup, instFile ) +{ + mSpacingRadius = atof( instGroup->FindPairValue ( "spacing", "0" ) ); + mFlattenRadius = atof( instGroup->FindPairValue ( "flatten", "0" ) ); +} + +/************************************************************************************************ + * CRMVoidInstance::SetArea + * Overidden to make sure the void area doesnt continually. + * + * inputs: + * area: area to set + * + * return: + * none + * + ************************************************************************************************/ +void CRMVoidInstance::SetArea ( CRMAreaManager* amanager, CRMArea* area ) +{ + // Disable collision + area->EnableCollision ( false ); + + // Do what really needs to get done + CRMInstance::SetArea ( amanager, area ); +} diff --git a/code/RMG/RM_Instance_Void.h b/code/RMG/RM_Instance_Void.h new file mode 100644 index 0000000..dac0eca --- /dev/null +++ b/code/RMG/RM_Instance_Void.h @@ -0,0 +1,18 @@ +#pragma once +#if !defined(RM_INSTANCE_VOID_H_INC) +#define RM_INSTANCE_VOID_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Instance_Void.h") +#endif + +class CRMVoidInstance : public CRMInstance +{ +public: + + CRMVoidInstance ( CGPGroup* instGroup, CRMInstanceFile& instFile ); + + virtual void SetArea ( CRMAreaManager* amanager, CRMArea* area ); +}; + +#endif \ No newline at end of file diff --git a/code/RMG/RM_Manager.cpp b/code/RMG/RM_Manager.cpp new file mode 100644 index 0000000..83e1b76 --- /dev/null +++ b/code/RMG/RM_Manager.cpp @@ -0,0 +1,402 @@ +/************************************************************************************************ + * + * RM_Manager.cpp + * + * Implements the CRMManager class. The CRMManager class manages the arioche system. + * + ************************************************************************************************/ + +#include "../server/exe_headers.h" + +#include "rm_headers.h" +#include "../server/server.h" + +CRMObjective *CRMManager::mCurObjective=0; + +/************************************************************************************************ + * TheRandomMissionManager + * Pointer to only active CRMManager class + * + ************************************************************************************************/ +CRMManager *TheRandomMissionManager; + +/************************************************************************************************ + * CRMManager::CRMManager + * constructor + * + * inputs: + * + * return: + * + ************************************************************************************************/ +CRMManager::CRMManager(void) +{ + mLandScape = NULL; + mTerrain = NULL; + mMission = NULL; + mCurPriority = 1; + mUseTimeLimit = false; +} + +/************************************************************************************************ + * CRMManager::~CRMManager + * destructor + * + * inputs: + * + * return: + * + ************************************************************************************************/ +CRMManager::~CRMManager(void) +{ +#ifndef FINAL_BUILD + Com_Printf ("... Shutting down TheRandomMissionManager\n"); +#endif +#ifndef DEDICATED + CM_TM_Free(); +#endif + if (mMission) + { + delete mMission; + mMission = NULL; + } +} + +/************************************************************************************************ + * CRMManager::SetLandscape + * Sets the landscape and terrain object used to load a mission + * + * inputs: + * landscape - landscape object + * + * return: + * none + * + ************************************************************************************************/ +void CRMManager::SetLandScape(CCMLandScape *landscape) +{ + mLandScape = landscape; + mTerrain = landscape->GetRandomTerrain(); +} + +/************************************************************************************************ + * CRMManager::LoadMission + * Loads the mission using the mission name stored in the ar_mission cvar + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +bool CRMManager::LoadMission ( qboolean IsServer ) +{ +#ifndef PRE_RELEASE_DEMO + char instances[MAX_QPATH]; + char mission[MAX_QPATH]; + char course[MAX_QPATH]; + char map[MAX_QPATH]; + char temp[MAX_QPATH]; + +#ifndef FINAL_BUILD + Com_Printf ("--------- Random Mission Manager ---------\n\n"); + Com_Printf ("RMG version : 0.01\n\n"); +#endif + + if (!mTerrain) + { + return false; + } + + // Grab the arioche variables + Cvar_VariableStringBuffer("rmg_usetimelimit", temp, MAX_QPATH); + if (strcmpi(temp, "yes") == 0) + { + mUseTimeLimit = true; + } + Cvar_VariableStringBuffer("rmg_instances", instances, MAX_QPATH); + Cvar_VariableStringBuffer("RMG_mission", temp, MAX_QPATH); + Cvar_VariableStringBuffer("rmg_map", map, MAX_QPATH); + sprintf(mission, "%s_%s", temp, map); + Cvar_VariableStringBuffer("rmg_course", course, MAX_QPATH); + + // dump existing mission, if any + if (mMission) + { + delete mMission; + mMission = NULL; + } + + // Create a new mission file + mMission = new CRMMission ( mTerrain ); + + // Load the mission using the arioche variables + if ( !mMission->Load ( mission, instances, course ) ) + { + return false; + } + + if (mUseTimeLimit) + { + Cvar_Set("rmg_timelimit", va("%d", mMission->GetTimeLimit())); + } + else + { + Cvar_Set("rmg_timelimit", "0"); + } + + if (IsServer) + { // set the names of the teams + CGenericParser2 parser; + //CGPGroup* root; + + Cvar_VariableStringBuffer("RMG_terrain", temp, MAX_QPATH); + + /* + // Create the parser for the mission file + if(Com_ParseTextFile(va("ext_data/rmg/%s.teams", temp), parser)) + { + root = parser.GetBaseParseGroup()->GetSubGroups(); + if (0 == stricmp(root->GetName(), "teams")) + { + SV_SetConfigstring( CS_GAMETYPE_REDTEAM, root->FindPairValue ( "red", "marine" )); + SV_SetConfigstring( CS_GAMETYPE_BLUETEAM, root->FindPairValue ( "blue", "thug" )); + } + parser.Clean(); + } + */ + //rww - This is single player, no such thing. + } + + // Must have a valid landscape before we can spawn the mission + assert ( mLandScape ); + +#ifndef FINAL_BUILD + Com_Printf ("------------------------------------------\n"); +#endif + + return true; +#else + return false; +#endif // PRE_RELEASE_DEMO +} + +/************************************************************************************************ + * CRMManager::IsMissionComplete + * Determines whether or not all the arioche objectives have been met + * + * inputs: + * none + * + * return: + * true: all objectives have been completed + * false: one or more of the objectives has not been met + * + ************************************************************************************************/ +bool CRMManager::IsMissionComplete(void) +{ + if ( NULL == mMission->GetCurrentObjective ( ) ) + { + return true; + } + + return false; +} + +/************************************************************************************************ + * CRMManager::HasTimeExpired + * Determines whether or not the time limit (if one) has expired + * + * inputs: + * none + * + * return: + * true: time limit has expired + * false: time limit has not expired + * + ************************************************************************************************/ +bool CRMManager::HasTimeExpired(void) +{ +/* if (mMission->GetTimeLimit() == 0 || !mUseTimeLimit) + { // no time limit set + return false; + } + + if (mMission->GetTimeLimit() * 1000 * 60 > level.time - level.startTime) + { // we are still under our time limit + return false; + } + + // over our time limit! + return true;*/ + + return false; +} + +/************************************************************************************************ + * CRMManager::UpdateStatisticCvars + * Updates the statistic cvars with data from the game + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +void CRMManager::UpdateStatisticCvars ( void ) +{ +/* // No player set then nothing more to do + if ( mPlayer ) + { + float accuracy; + + // Calculate the accuracy + accuracy = (float)mPlayer->client->ps.persistant[PERS_SHOTS_HIT]; + accuracy /= (float)mPlayer->client->ps.persistant[PERS_SHOTS]; + accuracy *= 100.0f; + + // set the accuracy cvar + gi.Cvar_Set ( "ar_pl_accuracy", va("%d%%",(int)accuracy) ); + + // Set the # of kills cvar + gi.Cvar_Set ( "ar_kills", va("%d", mPlayer->client->ps.persistant[PERS_SCORE] ) ); + + int hours; + int mins; + int seconds; + int tens; + int millisec = (level.time - level.startTime); + + seconds = millisec / 1000; + hours = seconds / (60 * 60); + seconds -= (hours * 60 * 60); + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + gi.Cvar_Set ( "ar_duration", va("%dhr %dmin %dsec", hours, mins, seconds ) ); + + WpnID wpnID = TheWpnSysMgr().GetFavoriteWeapon ( ); + gi.Cvar_Set ( "ar_fav_wp", CWeaponSystem::GetWpnName ( wpnID ) ); + + // show difficulty + char difficulty[MAX_QPATH]; + gi.Cvar_VariableStringBuffer("g_skill", difficulty, MAX_QPATH); + strupr(difficulty); + gi.Cvar_Set ( "ar_diff", va("&GENERIC_%s&",difficulty) ); + + // compute rank + float compositeRank = 1; + int rankMax = 3; // max rank less 1 + float timeRank = mUseTimeLimit ? (1.0f - (mins / (float)mMission->GetTimeLimit())) : 0; + float killRank = mPlayer->client->ps.persistant[PERS_SCORE] / (float)GetCharacterManager().GetAllSize(); + killRank = (killRank > 0) ? killRank : 1.0f; + float accuRank = (accuracy > 0) ? accuracy*0.01f : 1.0f; + float weapRank = 1.0f - CWeaponSystem::GetRank(wpnID); + + compositeRank = ((timeRank + killRank + accuRank + weapRank) / 3.0f) * rankMax + 1; + + if (compositeRank > 4) + compositeRank = 4; + + gi.Cvar_Set ( "ar_rank", va("&RMG_RANK%d&",((int)compositeRank)) ); + }*/ +} + +/************************************************************************************************ + * CRMManager::CompleteMission + * Does end-of-mission stuff (pause game, end screen, return to menu) + * * + * Input * + * : * + * Output / Return * + * : * + ************************************************************************************************/ +void CRMManager::CompleteMission(void) +{ + UpdateStatisticCvars ( ); + + mMission->CompleteMission(); +} + +/************************************************************************************************ + * CRMManager::FailedMission + * Does end-of-mission stuff (pause game, end screen, return to menu) + * * + * Input * + * TimeExpired: indicates if the reason failed was because of time + * Output / Return * + * : * + ************************************************************************************************/ +void CRMManager::FailedMission(bool TimeExpired) +{ + UpdateStatisticCvars ( ); + + mMission->FailedMission(TimeExpired); +} + +/************************************************************************************************ + * CRMManager::CompleteObjective + * Marks the given objective as completed + * + * inputs: + * obj: objective to set as completed + * + * return: + * none + * + ************************************************************************************************/ +void CRMManager::CompleteObjective ( CRMObjective *obj ) +{ + assert ( obj ); + + mMission->CompleteObjective ( obj ); +} + +/************************************************************************************************ + * CRMManager::Preview + * previews the random mission genration information + * + * inputs: + * from: origin being previed from + * + * return: + * none + * + ************************************************************************************************/ +void CRMManager::Preview ( const vec3_t from ) +{ + // Dont bother if we havent reached our timer yet +/* if ( level.time < mPreviewTimer ) + { + return; + } + + // Let the mission do all the previewing + mMission->Preview ( from ); + + // Another second + mPreviewTimer = level.time + 1000;*/ +} + +/************************************************************************************************ + * CRMManager::Preview + * previews the random mission genration information + * + * inputs: + * from: origin being previed from + * + * return: + * none + * + ************************************************************************************************/ +bool CRMManager::SpawnMission ( qboolean IsServer ) +{ + // Spawn the mission + mMission->Spawn ( mTerrain, IsServer ); + + return true; +} diff --git a/code/RMG/RM_Manager.h b/code/RMG/RM_Manager.h new file mode 100644 index 0000000..3b95fb6 --- /dev/null +++ b/code/RMG/RM_Manager.h @@ -0,0 +1,55 @@ +#pragma once +#if !defined(RM_MANAGER_H_INC) +#define RM_MANAGER_H_INC + +#if !defined(CM_LANDSCAPE_H_INC) +#include "../qcommon/cm_landscape.h" +#endif + +class CRMManager +{ +private: + + CRMMission* mMission; + CCMLandScape* mLandScape; + CRandomTerrain* mTerrain; + int mPreviewTimer; + int mCurPriority; + bool mUseTimeLimit; + + void UpdateStatisticCvars ( void ); + +public: + + // Constructors + CRMManager (void); + ~CRMManager (void); + + bool LoadMission ( qboolean IsServer ); + bool SpawnMission ( qboolean IsServer ); + + // Accessors + void SetLandScape (CCMLandScape *landscape); + void SetCurPriority (int priority) { mCurPriority = priority; } + + CRandomTerrain* GetTerrain (void) { return mTerrain; } + CCMLandScape* GetLandScape (void) { return mLandScape; } + CRMMission* GetMission (void) { return mMission; } + int GetCurPriority (void) { return mCurPriority; } + + void Preview ( const vec3_t from ); + + bool IsMissionComplete (void); + bool HasTimeExpired (void); + void CompleteObjective ( CRMObjective *obj ); + void CompleteMission (void); + void FailedMission (bool TimeExpired); + + // eek + static CRMObjective *mCurObjective; +}; + +extern CRMManager* TheRandomMissionManager; + + +#endif // RANDOMMISSION_H_INC \ No newline at end of file diff --git a/code/RMG/RM_Mission.cpp b/code/RMG/RM_Mission.cpp new file mode 100644 index 0000000..8f8cc3e --- /dev/null +++ b/code/RMG/RM_Mission.cpp @@ -0,0 +1,1930 @@ +/************************************************************************************************ + * + * RM_Mission.cpp + * + * implements the CRMMission class. The CRMMission class loads and manages an arioche mission + * + ************************************************************************************************/ + +#include "../server/exe_headers.h" + +#include "rm_headers.h" + +#define ARIOCHE_CLIPBRUSH_SIZE 300 +#define CVAR_OBJECTIVE 0 + +/************************************************************************************************ + * CRMMission::CRMMission + * constructor + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMMission::CRMMission ( CRandomTerrain* landscape ) +{ + mCurrentObjective = NULL; + mValidPaths = false; + mValidRivers = false; + mValidNodes = false; + mValidWeapons = false; + mValidAmmo = false; + mValidObjectives = false; + mValidInstances = false; + mTimeLimit = 0; + mMaxInstancePosition = 1; + mAccuracyMultiplier = 1.0f; + mHealthMultiplier = 1.0f; + mPickupHealth = 1.0f; + mPickupArmor = 1.0f; + mPickupAmmo = 1.0f; + mPickupWeapon = 1.0f; + mPickupEquipment = 1.0f; + + mDefaultPadding = 0; + mSymmetric = SYMMETRY_NONE; + +// mCheckedEnts.clear(); + + mLandScape = landscape; + + // cut down the possible area that is 'legal' for area manager to use by 20% + vec3_t land_min, land_max; + + land_min[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * 0.1f; + land_min[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * 0.1f; + land_min[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * 0.1f; + + land_max[0] = mLandScape->GetBounds ( )[1][0] - (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * 0.1f; + land_max[1] = mLandScape->GetBounds ( )[1][1] - (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * 0.1f; + land_max[2] = mLandScape->GetBounds ( )[1][2] - (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * 0.1f; + + // Create a new area manager for the landscape + mAreaManager = new CRMAreaManager ( land_min, + land_max ); + + // Create a new path manager + mPathManager = new CRMPathManager ( mLandScape ); +} + +/************************************************************************************************ + * CRMMission::~CRMMission + * destructor + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMMission::~CRMMission ( ) +{ + rmObjectiveIter_t oit; + rmInstanceIter_t iit; + +// mCheckedEnts.clear(); + + // Cleanup the objectives + for (oit = mObjectives.begin(); oit != mObjectives.end(); oit++) + { + delete (*oit); + } + + // Cleanup the instances + for (iit = mInstances.begin(); iit != mInstances.end(); iit++) + { + delete (*iit); + } + + if (mPathManager) + { + delete mPathManager; + mPathManager = 0; + } + + if (mAreaManager) + { + delete mAreaManager; + mAreaManager = 0; + } +} + +/************************************************************************************************ + * CRMMission::FindObjective + * searches through the missions objectives for the one with the given name + * + * inputs: + * name: name of objective to find + * + * return: + * objective: objective matching the given name or NULL if it couldnt be found + * + ************************************************************************************************/ +CRMObjective* CRMMission::FindObjective ( const char* name ) +{ + rmObjectiveIter_t it; + + for (it = mObjectives.begin(); it != mObjectives.end(); it++) + { + // Does it match? + if (!stricmp ((*it)->GetName(), name )) + { + return (*it); + } + } + + // Not found + return NULL; +} + +void CRMMission::MirrorPos(vec3_t pos) +{ + pos[0] = 1.0f - pos[0]; + pos[1] = 1.0f - pos[1]; +} + +/************************************************************************************************ + * CRMMission::ParseOrigin + * parses an origin block which includes linking to a node and absolute origins + * + * inputs: + * group: parser group containing the node or origin + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseOrigin ( CGPGroup* originGroup, vec3_t origin, vec3_t lookat, int* flattenHeight ) +{ + const char* szNodeName; + vec3_t mins; + vec3_t maxs; + + if ( flattenHeight ) + { + *flattenHeight = 66; + } + + // If no group was given then use 0,0,0 + if ( NULL == originGroup ) + { + VectorCopy ( vec3_origin, origin ); + return false; + } + + // See if attaching to a named node + szNodeName = originGroup->FindPairValue ( "node", "" ); + if ( *szNodeName ) + { + CRMNode* node; + // Find the node being attached to + node = mPathManager->FindNodeByName ( szNodeName ); + if ( node ) + { + if ( flattenHeight ) + { + if ( node->GetFlattenHeight ( ) == -1 ) + { + node->SetFlattenHeight ( 40 + mLandScape->irand(0,40) ); + } + + *flattenHeight = node->GetFlattenHeight ( ); + } + + VectorCopy(node->GetPos(), origin); + + VectorCopy ( origin, lookat ); + + int dir; + int rnd_offset = mLandScape->irand(0, DIR_MAX-1); + for (dir=0; dirPathExist(d)) + { + vec4_t tmp_pt, tmp_dir; + int pathID = node->GetPath(d); + mLandScape->GetPathInfo(pathID, 0.1f, tmp_pt, tmp_dir ); + lookat[0] = tmp_pt[0]; + lookat[1] = tmp_pt[1]; + lookat[2] = 0; + return true; + } + } + return true; + } + } + + mins[0] = atof( originGroup->FindPairValue ( "left", ".1" ) ); + mins[1] = atof( originGroup->FindPairValue ( "top", ".1" ) ); + maxs[0] = atof( originGroup->FindPairValue ( "right", ".9" ) ); + maxs[1] = atof( originGroup->FindPairValue ( "bottom", ".9" ) ); + + lookat[0] = origin[0] = mLandScape->flrand(mins[0],maxs[0]); + lookat[1] = origin[1] = mLandScape->flrand(mins[1],maxs[1]); + lookat[2] = origin[2] = 0; + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseNodes + * parses all the named nodes in the file + * + * inputs: + * group: parser group containing the named nodes + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseNodes ( CGPGroup* group ) +{ + // If NULL that means this particular difficulty level has no named nodes + if ( NULL == group || mValidNodes) + { + return true; + } + + // how many nodes spaced over map? + int x_cells; + int y_cells; + + x_cells = atoi ( group->FindPairValue ( "x_cells", "3" ) ); + y_cells = atoi ( group->FindPairValue ( "y_cells", "3" ) ); + + mPathManager->CreateArray(x_cells, y_cells); + + // Loop through all the nodes and generate each as specified + for ( group = group->GetSubGroups(); + group; + group=group->GetNext() ) + { + int min_depth = atof( group->FindPairValue ( "min_depth", "0" ) ); + int max_depth = atof( group->FindPairValue ( "max_depth", "5" ) ); + int min_paths = atoi( group->FindPairValue ( "min_paths", "1" ) ); + int max_paths = atoi( group->FindPairValue ( "max_paths", "1" ) ); + + mPathManager->CreateLocation( group->GetName(), min_depth, max_depth, min_paths, max_paths ); + } + + mValidNodes = true; + return true; +} + +/************************************************************************************************ + * CRMMission::ParsePaths + * parses all path styles in the file and then generates paths + * + * inputs: + * group: parser group containing the list of path styles + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParsePaths ( CGPGroup* group ) +{ + // If NULL that means this particular difficulty level has no paths + if ( NULL == group || mValidPaths) + { + return true; + } + + // path style info + float depth; + float deviation; + float breadth; + float minwidth; + float maxwidth; + int points; + + points = atoi ( group->FindPairValue ( "points", "10" ) ); + depth = atof ( group->FindPairValue ( "depth", ".31" ) ); + deviation = atof ( group->FindPairValue ( "deviation", ".025" ) ); + breadth = atof ( group->FindPairValue ( "breadth", "5" ) ); + minwidth = atof ( group->FindPairValue ( "minwidth", ".03" ) ); + maxwidth = atof ( group->FindPairValue ( "maxwidth", ".05" ) ); + + mPathManager->SetPathStyle( points, minwidth, maxwidth, depth, deviation, breadth); + + if (!mValidPaths) + { // we must create paths + mPathManager->GeneratePaths( mSymmetric ); + mValidPaths = true; + } + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseRivers + * parses all river styles in the file and then generates rivers + * + * inputs: + * group: parser group containing the list of path styles + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseRivers ( CGPGroup* group ) +{ + // If NULL that means this particular difficulty level has no rivers + if ( NULL == group || mValidRivers) + { + return true; + } + + // river style info + int maxdepth; + float beddepth; + float deviation; + float breadth; + float minwidth; + float maxwidth; + int points; + string bridge_name; + + maxdepth = atoi ( group->FindPairValue ( "maxpathdepth", "5" ) ); + points = atoi ( group->FindPairValue ( "points", "10" ) ); + beddepth = atof ( group->FindPairValue ( "depth", "1" ) ); + deviation = atof ( group->FindPairValue ( "deviation", ".03" ) ); + breadth = atof ( group->FindPairValue ( "breadth", "7" ) ); + minwidth = atof ( group->FindPairValue ( "minwidth", ".01" ) ); + maxwidth = atof ( group->FindPairValue ( "maxwidth", ".03" ) ); + bridge_name= group->FindPairValue ( "bridge", "" ) ; + + mPathManager->SetRiverStyle( maxdepth, points, minwidth, maxwidth, beddepth, deviation, breadth, bridge_name); + + if (!mValidRivers && + beddepth < 1) // use a depth of 1 if we don't want any rivers + { // we must create rivers + mPathManager->GenerateRivers(); + mValidRivers = true; + } + + return true; +} + +void CRMMission::PlaceBridges() +{ + if (!mValidRivers || strlen(mPathManager->GetBridgeName()) < 1) + return; + + int max_bridges = 0; + int path; + float t; + float river_depth = mLandScape->GetLandScape()->GetWaterHeight(); + vec3_t pos, lastpos; + vec3pair_t bounds; + VectorSet(bounds[0], 0,0,0); + VectorSet(bounds[1], 0,0,0); + + // walk along paths looking for dips + for (path = 0; path < mPathManager->GetPathCount(); path++) + { + vec4_t tmp_pt, tmp_dir; + bool new_water = true; + + mLandScape->GetPathInfo(path, 0, tmp_pt, tmp_dir ); + lastpos[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * tmp_pt[0]; + lastpos[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * tmp_pt[1]; + lastpos[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * tmp_pt[2]; + mLandScape->GetLandScape()->GetWorldHeight ( lastpos, bounds, true ); + + const float delta = 0.05f; + for (t= delta; t < 1.0f; t += delta) + { + mLandScape->GetPathInfo(path, t, tmp_pt, tmp_dir ); + pos[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * tmp_pt[0]; + pos[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * tmp_pt[1]; + pos[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * tmp_pt[2]; + mLandScape->GetLandScape()->GetWorldHeight ( pos, bounds, true ); + + if (new_water && + lastpos[2] < river_depth && + pos[2] < river_depth && + pos[2] > lastpos[2]) + { // add a bridge + if (max_bridges < 3) + { + CRMArea* area; + CRMInstance* instance; + + max_bridges++; + + // create a single bridge + lastpos[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * mPathManager->GetPathDepth(); + instance = mInstanceFile.CreateInstance ( mPathManager->GetBridgeName() ); + + if ( NULL != instance ) + { // Set the area + vec3_t zerodvec; + VectorClear(zerodvec); + area = mAreaManager->CreateArea ( lastpos, instance->GetSpacingRadius(), instance->GetSpacingLine(), GetDefaultPadding(), 0, zerodvec, pos, instance->GetFlattenRadius()?true:false, false, instance->GetLockOrigin() ); + area->EnableLookAt(false); + + instance->SetArea ( mAreaManager, area ); + mInstances.push_back ( instance ); + new_water = false; + } + } + } + else if (pos[2] > river_depth) + { // hit land again + new_water = true; + } + VectorCopy ( pos, lastpos ); + } + } +} + +void CRMMission::PlaceWallInstance(CRMInstance* instance, float xpos, float ypos, float zpos, int x, int y, float angle) +{ + if (NULL == instance) + return; + + float spacing = instance->GetSpacingRadius(); + CRMArea* area; + vec3_t origin; + vec3_t zerodvec; + VectorClear(zerodvec); + + origin[0] = xpos + spacing * x; + origin[1] = ypos + spacing * y; + origin[2] = zpos; + + // Set the area of position + area = mAreaManager->CreateArea ( origin, (spacing / 2.1f), 0, GetDefaultPadding(), 0, zerodvec, origin, instance->GetFlattenRadius()?true:false, false, instance->GetLockOrigin() ); + area->EnableLookAt(false); + area->SetAngle(angle); + instance->SetArea ( mAreaManager, area ); + + mInstances.push_back ( instance ); +} + + +/************************************************************************************************ + * CRMMission::ParseWallRect + * creates instances for walled rectangle at this node (fence) + * + * inputs: + * group: parser group containing the wall rect info + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseWallRect(CGPGroup* group , int side) +{ +#ifndef PRE_RELEASE_DEMO + CGPGroup* wallGroup = group->FindSubGroup ( "wallrect" ) ; + + // If NULL that means this particular instance has no wall rect + if ( NULL == group || NULL == wallGroup) + { + return true; + } + + const char* wallName = wallGroup->FindPairValue ( "wall_instance", "" ); + const char* cornerName = wallGroup->FindPairValue ( "corner_instance", "" ); + const char* towerName = wallGroup->FindPairValue ( "tower_instance", "" ); + const char* gateName = wallGroup->FindPairValue ( "gate_instance", "" ); + const char* ripName = wallGroup->FindPairValue ( "rip_instance", "" ); + + if ( NULL != wallName ) + { + int xcount = atoi( wallGroup->FindPairValue ( "xcount", "0" ) ); + int ycount = atoi( wallGroup->FindPairValue ( "ycount", "0" ) ); + + int gateCount = atoi( wallGroup->FindPairValue ( "gate_count", "1" ) ); + int gateMin = atoi( wallGroup->FindPairValue ( "gate_min", "0" ) ); + int gateMax = atoi( wallGroup->FindPairValue ( "gate_max", "0" ) ); + + int ripCount = atoi( wallGroup->FindPairValue ( "rip_count", "0" ) ); + int ripMin = atoi( wallGroup->FindPairValue ( "rip_min", "0" ) ); + int ripMax = atoi( wallGroup->FindPairValue ( "rip_max", "0" ) ); + + int towerCount = atoi( wallGroup->FindPairValue ( "tower_count", "0" ) ); + int towerMin = atoi( wallGroup->FindPairValue ( "tower_min", "0" ) ); + int towerMax = atoi( wallGroup->FindPairValue ( "tower_max", "0" ) ); + + if (gateMin != gateMax) + gateCount = mLandScape->irand(gateMin,gateMax); + + if (ripMin != ripMax) + ripCount = mLandScape->irand(ripMin,ripMax); + + if (towerMin != towerMax) + towerCount = mLandScape->irand(towerMin,towerMax); + + if (NULL == gateName) + gateCount = 0; + + if (NULL == towerName) + towerCount = 0; + + if (NULL == ripName) + ripCount = 0; + + const char* nodename; + CGPGroup* originGroup = group->FindSubGroup ( "origin" ); + if (originGroup) + { + nodename = originGroup->FindPairValue ( "node", "" ); + if (*nodename) + { + CRMNode* node; + // Find the node being attached to + node = mPathManager->FindNodeByName ( nodename ); + if ( node ) + { + CRMInstance* instance; + int x,y; + int halfx = xcount/2; + int halfy = ycount/2; + float xpos = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * node->GetPos()[0]; + float ypos = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * node->GetPos()[1]; + float zpos = mLandScape->GetBounds ( )[1][2] + 100; + float angle = 0; + int lastGate = 0; + int lastRip = 0; + + // corners + x = -halfx; + y = -halfy; + if (towerCount > 3 || + (towerCount > 0 && mLandScape->irand(1,2) == 1) ) + { + towerCount--; + instance = mInstanceFile.CreateInstance ( towerName ); + } + else + instance = mInstanceFile.CreateInstance ( cornerName ); + angle = (float)DEG2RAD(90); + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, y, angle); + + x = halfx; + y = -halfy; + if (towerCount > 3 || + (towerCount > 0 && mLandScape->irand(1,2) == 1) ) + { + towerCount--; + instance = mInstanceFile.CreateInstance ( towerName ); + } + else + instance = mInstanceFile.CreateInstance ( cornerName ); + angle = (float)DEG2RAD(180); + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, y, angle); + + x = halfx; + y = halfy; + if (towerCount > 3 || + (towerCount > 0 && mLandScape->irand(1,2) == 1) ) + { + towerCount--; + instance = mInstanceFile.CreateInstance ( towerName ); + } + else + instance = mInstanceFile.CreateInstance ( cornerName ); + angle = (float)DEG2RAD(270); + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, y, angle); + + x = -halfx; + y = halfy; + if (towerCount > 3 || + (towerCount > 0 && mLandScape->irand(1,2) == 1) ) + { + towerCount--; + instance = mInstanceFile.CreateInstance ( towerName ); + } + else + instance = mInstanceFile.CreateInstance ( cornerName ); + angle = DEG2RAD(0); + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, y, angle); + + // walls + angle = DEG2RAD(0); + for (x = -halfx+1; x <= halfx-1; x++) + { + if (lastGate<1 && gateCount > 0 && mLandScape->irand(1,(halfx+halfy)/gateCount) == 1) + { // gate + gateCount--; + lastGate = 3; + instance = mInstanceFile.CreateInstance ( gateName ); + } + else if (lastRip<1 && ripCount > 0 && mLandScape->irand(1,(halfx+halfy)/ripCount) == 1) + { // damaged fence + ripCount--; + lastRip = 3; + instance = mInstanceFile.CreateInstance ( ripName ); + } + else + { // just a wall + instance = mInstanceFile.CreateInstance ( wallName ); + lastRip--; + lastGate--; + } + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, -halfy, angle); + } + for (x = -halfx+1; x <= halfx-1; x++) + { + if (lastGate<1 && gateCount > 0 && mLandScape->irand(1,(halfx+halfy)/gateCount) == 1) + { // gate + gateCount--; + lastGate = 3; + instance = mInstanceFile.CreateInstance ( gateName ); + } + else if (lastRip<1 && ripCount > 0 && mLandScape->irand(1,(halfx+halfy)/ripCount) == 1) + { // damaged fence + ripCount--; + lastRip = 3; + instance = mInstanceFile.CreateInstance ( ripName ); + } + else + { // just a wall + instance = mInstanceFile.CreateInstance ( wallName ); + lastRip--; + lastGate--; + } + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, halfy, angle); + } + + angle = (float)DEG2RAD(90); + for (y = -halfy+1; y <= halfy-1; y++) + { + if (lastGate<1 && gateCount > 0 && mLandScape->irand(1,(halfx+halfy)/gateCount) == 1) + { // gate + gateCount--; + lastGate = 3; + instance = mInstanceFile.CreateInstance ( gateName ); + } + else if (lastRip<1 && ripCount > 0 && mLandScape->irand(1,(halfx+halfy)/ripCount) == 1) + { // damaged fence + ripCount--; + lastRip = 3; + instance = mInstanceFile.CreateInstance ( ripName ); + } + else + { // just a wall + instance = mInstanceFile.CreateInstance ( wallName ); + lastRip--; + lastGate--; + } + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, -halfx, y, angle); + } + for (y = -halfy+1; y <= halfy-1; y++) + { + if (lastGate<1 && gateCount > 0 && mLandScape->irand(1,(halfx+halfy)/gateCount) == 1) + { // gate + gateCount--; + lastGate = 3; + instance = mInstanceFile.CreateInstance ( gateName ); + } + else if (lastRip<1 && ripCount > 0 && mLandScape->irand(1,(halfx+halfy)/ripCount) == 1) + { // damaged fence + ripCount--; + lastRip = 3; + instance = mInstanceFile.CreateInstance ( ripName ); + } + else + { // just a wall + instance = mInstanceFile.CreateInstance ( wallName ); + lastRip--; + lastGate--; + } + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, halfx, y, angle); + } + } + } + } + } + else + return false; +#endif // #ifndef PRE_RELEASE_DEMO + + return true; +} + + +/************************************************************************************************ + * CRMMission::ParseInstancesOnPath + * creates instances on path between nodes + * + * inputs: + * group: parser group containing the defenses, other instances on the path between nodes + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseInstancesOnPath ( CGPGroup* group ) +{ +#ifndef PRE_RELEASE_DEMO + CGPGroup* defenseGroup; + for ( defenseGroup = group->GetSubGroups(); + defenseGroup; + defenseGroup=defenseGroup->GetNext() ) + if (stricmp ( defenseGroup->GetName ( ), "defenses" )==0 || + stricmp ( defenseGroup->GetName(), "instanceonpath")==0) + { + const char* defName = defenseGroup->FindPairValue ( "instance", "" ); + if ( *defName ) + { + float minpos; + float maxpos; + int mincount; + int maxcount; + + // how far along path does this get placed? + minpos = atof( defenseGroup->FindPairValue ( "minposition", "0.5" ) ); + maxpos = atof( defenseGroup->FindPairValue ( "maxposition", "0.5" ) ); + mincount = atoi( defenseGroup->FindPairValue ( "mincount", "1" ) ); + maxcount = atoi( defenseGroup->FindPairValue ( "maxcount", "1" ) ); + + const char* nodename; + CGPGroup* originGroup = group->FindSubGroup ( "origin" ); + if (originGroup) + { + nodename = originGroup->FindPairValue ( "node", "" ); + if (*nodename) + { + CRMNode* node; + // Find the node being attached to + node = mPathManager->FindNodeByName ( nodename ); + if ( node ) + { + int dir; + // look at each connection from this node to others, if there is a path, create a defense + for (dir=0; dirPathExist(dir)) + { // path leads out of this node + CRMArea* area; + CRMInstance* instance; + float spacing; + vec3_t origin; + vec3_t lookat; + vec4_t tmp_pt, tmp_dir; + int n,num_insts = mLandScape->irand(mincount,maxcount); + int pathID = node->GetPath(dir); + + if (0 == num_insts) + continue; + + float posdelta = (maxpos - minpos) / num_insts; + + for (n=0; nFindPairValue ( "spacing", "0" ) ); + if ( spacing ) + { + instance->SetSpacingRadius ( spacing ); + } + + instance->SetFilter(group->FindPairValue("filter", "")); + instance->SetTeamFilter(group->FindPairValue("teamfilter", "")); + + if (strstr(instance->GetTeamFilter(),"red")) + instance->SetSide(SIDE_RED); + else if (strstr(instance->GetTeamFilter(),"blue")) + instance->SetSide(SIDE_BLUE); + + float pos_along_path = mLandScape->flrand(minpos + posdelta*n, minpos + posdelta*(n+1)); + float look_along_path = atof( defenseGroup->FindPairValue ( "pathalign", "1" ) ) ; + mLandScape->GetPathInfo (pathID, pos_along_path, tmp_pt, tmp_dir ); + origin[0] = tmp_pt[0]; + origin[1] = tmp_pt[1]; + + mLandScape->GetPathInfo (pathID, look_along_path, tmp_dir, tmp_pt ); + lookat[0] = tmp_pt[0]; + lookat[1] = tmp_pt[1]; + + origin[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * origin[0]; + origin[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * origin[1]; + origin[2] = mLandScape->GetBounds ( )[0][2] ; + + // look at a point along the path at this location + lookat[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * lookat[0]; + lookat[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * lookat[1]; + lookat[2] = 0; + + // Fixed height? (used for bridges) + if ( !atoi(group->FindPairValue ( "nodrop", "0" )) ) + { + origin[2] = mLandScape->GetBounds ( )[1][2] + 100; + } + + // Set the area of position + area = mAreaManager->CreateArea ( origin, instance->GetSpacingRadius(), instance->GetSpacingLine(), GetDefaultPadding(), 0, origin, lookat, instance->GetFlattenRadius()?true:false, true, instance->GetLockOrigin(), mSymmetric ); + area->EnableLookAt(false); + + if ( node->GetFlattenHeight ( ) == -1 ) + { + node->SetFlattenHeight ( 66 + mLandScape->irand(0,40) ); + } + instance->SetFlattenHeight ( node->GetFlattenHeight ( ) ); + + instance->SetArea ( mAreaManager, area ); + + mInstances.push_back ( instance ); + } + } + } + } + } + } + else + return false; + } + else + return false; + + } +#endif // #ifndef PRE_RELEASE_DEMO + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseInstance + * Parses an individual instance + * + * inputs: + * group: parser group containing the list of instances + * + * return: + * true: instances parsed successfully + * false: instances failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseInstance ( CGPGroup* group ) +{ + CRMArea* area; + CRMInstance* instance; + float spacing; + vec3_t origin; + vec3_t lookat; + int flattenHeight; + vec3_t zerodvec; + + VectorClear(zerodvec); + + // create fences / walls + + // Create the instance using the instance file helper class + instance = mInstanceFile.CreateInstance ( group->GetName ( ) ); + + // Failed to create, not good + if ( NULL == instance ) + { + return false; + } + + // If a spacing radius was specified then override the one thats + // in the instance + spacing = atof( group->FindPairValue ( "spacing", "0" ) ); + if ( spacing ) + { + instance->SetSpacingRadius ( spacing ); + } + + instance->SetFilter(group->FindPairValue("filter", "")); + instance->SetTeamFilter(group->FindPairValue("teamfilter", "")); + + if (strstr(instance->GetTeamFilter(),"red")) + instance->SetSide( SIDE_RED); + else if (strstr(instance->GetTeamFilter(),"blue")) + instance->SetSide( SIDE_BLUE ); + +// ParseWallRect(group, instance->GetSide()); + + // Get its origin now + ParseOrigin ( group->FindSubGroup ( "origin" ), origin, lookat, &flattenHeight ); + origin[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * origin[0]; + origin[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * origin[1]; + origin[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * origin[2]; + + lookat[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * lookat[0]; + lookat[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * lookat[1]; + lookat[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * lookat[2]; + + // Fixed height? (used for bridges) + if ( !atoi(group->FindPairValue ( "nodrop", "0" )) ) + { + origin[2] = mLandScape->GetBounds ( )[1][2] + 100; + } + + // Set the area of position + area = mAreaManager->CreateArea ( origin, instance->GetSpacingRadius(), instance->GetSpacingLine(), GetDefaultPadding(), 0, zerodvec, lookat, instance->GetFlattenRadius()?true:false, true, instance->GetLockOrigin(), mSymmetric ); + instance->SetArea ( mAreaManager, area ); + instance->SetFlattenHeight ( flattenHeight ); + + mInstances.push_back ( instance ); + + // create defenses? + ParseInstancesOnPath(group ); + + return true; +} + + +/************************************************************************************************ + * CRMMission::ParseInstances + * parses all instances within the mission and populates the instance list + * + * inputs: + * group: parser group containing the list of instances + * + * return: + * true: instances parsed successfully + * false: instances failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseInstances ( CGPGroup* group ) +{ +#ifndef PRE_RELEASE_DEMO + // If NULL that means this particular difficulty level has no instances + if ( NULL == group ) + { + return true; + } + + // Loop through all the instances in the mission and add each + // to the master list of instances + for ( group = group->GetSubGroups(); + group; + group=group->GetNext() ) + { + ParseInstance ( group ); + } +#endif // #ifndef PRE_RELEASE_DEMO + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseObjectives + * parses all objectives within the mission and populates the objective list + * + * inputs: + * group: parser group containing the list of objectives + * + * return: + * true: objectives parsed successfully + * false: objectives failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseObjectives ( CGPGroup* group ) +{ + // If NULL that means this particular difficulty level has no objectives + if ( NULL == group ) + { + return true; + } + + // Loop through all the objectives in the mission and add each + // to the master list of objectives + for ( group = group->GetSubGroups(); + group; + group=group->GetNext() ) + { + CRMObjective* objective; + + // Create the new objective + objective = new CRMObjective ( group ); + + mObjectives.push_back ( objective ); + } + + mValidObjectives = true; + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseAmmo + * parses the given ammo list and sets the necessary ammo cvars to grant those + * weapons to the players + * + * inputs: + * ammos: parser group containing the ammo list + * + * return: + * true: ammo parsed successfully + * false: ammo failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseAmmo ( CGPGroup* ammos ) +{ +/* CGPValue* ammo; + + // No weapons, no success + if ( NULL == ammos ) + { + return false; + } + + if (0 == gi.Cvar_VariableIntegerValue("ar_wpnselect")) + { + // Make sure the ammo cvars are all reset so ammo from the last map or + // another difficulty level wont carry over + CWeaponSystem::ClearAmmoCvars (TheWpnSysHelper()); + + ammo = ammos->GetPairs ( ); + + // Loop through the weapons listed and grant them to the player + while ( ammo ) + { + // Grab the weapons ID + AmmoID id = CWeaponSystem::GetAmmoID ( ammo->GetName ( ) ); + + // Now set the weapon cvar with the given data + TheWpnSysHelper().CvarSet ( CWeaponSystem::GetAmmoCvar ( id ), ammo->GetTopValue ( ), CVAR_AMMO ); + + // Move on to the next weapon + ammo = (CGPValue*)ammo->GetNext(); + } + } +*/ + mValidAmmo = true; + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseWeapons + * parses the given weapon list and sets the necessary weapon cvars to grant those + * weapons to the players + * + * inputs: + * weapons: parser group containing the weapons list + * + * return: + * true: weapons parsed successfully + * false: weapons failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseWeapons ( CGPGroup* weapons ) +{ +/* CGPValue* weapon; + WpnID id; + + // No weapons, no success + if ( NULL == weapons ) + { + return false; + } + + if (0 == gi.Cvar_VariableIntegerValue("ar_wpnselect")) + { + // Make sure the weapon cvars are all reset so weapons from the last map or + // another difficulty level wont carry over + CWeaponSystem::ClearWpnCvars (TheWpnSysHelper()); + + id = NULL_WpnID; + weapon = weapons->GetPairs ( ); + + // Loop through the weapons listed and grant them to the player + while ( weapon ) + { + // Grab the weapons ID + id = CWeaponSystem::GetWpnID ( weapon->GetName ( ) ); + + // Now set the weapon cvar with the given data + TheWpnSysHelper().CvarSet ( CWeaponSystem::GetWpnCvar ( id ), weapon->GetTopValue ( ) ); + + // Move on to the next weapon + weapon = (CGPValue*)weapon->GetNext(); + } + + // If we found at least one weapon then ready the last one in the list + if ( NULL_WpnID != id ) + { + TheWpnSysHelper().CvarSet("wp_righthand", va("%i/%i/0/0",id,CWeaponSystem::GetClipSize ( id )), CVAR_MISC ); + } + } +*/ + mValidWeapons = true; + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseOutfit + * parses the outfit (weapons and ammo) + * + * inputs: + * outfit: parser group containing the outfit + * + * return: + * true: weapons and ammo parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseOutfit ( CGPGroup* outfit ) +{ + if ( NULL == outfit ) + { + return false; + } + +/* // Its ok to fail parsing weapons as long as weapons have + // already been parsed at some point + if ( !ParseWeapons ( ParseRandom ( outfit->FindSubGroup ( "weapons" ) ) ) ) + { + if ( !mValidWeapons ) + { + return false; + } + } + + // Its ok to fail parsing ammo as long as ammo have + // already been parsed at some point + if ( !ParseAmmo ( ParseRandom ( outfit->FindSubGroup ( "ammo" ) ) ) ) + { + if ( !mValidAmmo) + { + return false; + } + } +*/ + return true; +} + +/************************************************************************************************ + * CRMMission::ParseRandom + * selects a random sub group with from all within this one + * + * inputs: + * random: parser group containing the various subgroups + * + * return: + * true: parsed successfuly + * false: failed to parse + * + ************************************************************************************************/ +CGPGroup* CRMMission::ParseRandom ( CGPGroup* randomGroup ) +{ + if (NULL == randomGroup) + return NULL; + + CGPGroup* group; + CGPGroup* groups[MAX_RANDOM_CHOICES]; + int numGroups; + + // Build a list of the groups one can be chosen + for ( numGroups = 0, group = randomGroup->GetSubGroups ( ); + group; + group = group->GetNext ( ) ) + { + if ( stricmp ( group->GetName ( ), "random_choice" ) ) + { + continue; + } + + int weight = atoi ( group->FindPairValue ( "random_weight", "1" ) ); + while (weight-- > 0) + groups[numGroups++] = group; + assert (numGroups <= MAX_RANDOM_CHOICES); + } + + // No groups! + if ( !numGroups ) + { + return randomGroup; + } + + // Now choose a group to parse + return groups[mLandScape->irand(0,numGroups-1)]; +} + +/************************************************************************************************ + * CRMMission::ParseDifficulty + * parses the given difficulty and populates the mission with its data + * + * inputs: + * difficulty: parser group containing the difficulties info + * + * return: + * true: difficulty parsed successfully + * false: difficulty failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseDifficulty ( CGPGroup* difficulty, CGPGroup *parent ) +{ + // If a null difficulty then stop the recursion. Make sure to + // return true here so the parsing doesnt fail + if ( NULL == difficulty ) + { + return true; + } + + if (difficulty->GetParent()) + { + parent = difficulty->GetParent(); + } + + // is map supposed to be symmetric? + mSymmetric = (symmetry_t)atoi(parent->FindPairValue ( "symmetric", "0" )); + mBackUpPath = atoi(parent->FindPairValue ( "backuppath", "0" )); + if( mSymmetric ) + {// pick between the 2 starting corners -- yes this is a hack + mSymmetric = SYMMETRY_TOPLEFT; + if( TheRandomMissionManager->GetLandScape()->irand(0, 1) ) + { + mSymmetric = SYMMETRY_BOTTOMRIGHT; + } + } + + mDefaultPadding = atoi(parent->FindPairValue ( "padding", "0" )); + + // Parse the nodes + if ( !ParseNodes ( ParseRandom ( difficulty->FindSubGroup ( "nodes" ) ) ) ) + { + return false; + } + + // Parse the paths + if ( !ParsePaths ( ParseRandom ( difficulty->FindSubGroup ( "paths" ) ) ) ) + { + return false; + } + + // Parse the rivers + if ( !ParseRivers ( ParseRandom ( difficulty->FindSubGroup ( "rivers" ) ) ) ) + { + return false; + } + + // Handle inherited properties + if ( !ParseDifficulty ( parent->FindSubGroup ( difficulty->FindPairValue ( "inherit", "" ) ), parent ) ) + { + return false; + } + + // parse the player's outfit (weapons and ammo) + if ( !ParseOutfit( ParseRandom ( difficulty->FindSubGroup ( "outfit" ) ) ) ) + { + // Its ok to fail parsing weapons as long as weapons have + // already been parsed at some point + if ( !ParseWeapons ( ParseRandom ( difficulty->FindSubGroup ( "weapons" ) ) ) ) + { + if ( !mValidWeapons ) + { + return false; + } + } + + // Its ok to fail parsing ammo as long as ammo have + // already been parsed at some point + if ( !ParseAmmo ( ParseRandom ( difficulty->FindSubGroup ( "ammo" ) ) ) ) + { + if ( !mValidAmmo) + { + return false; + } + } + } + + // Its ok to fail parsing objectives as long as objectives have + // already been parsed at some point + if ( !ParseObjectives ( ParseRandom ( difficulty->FindSubGroup ( "objectives" ) ) ) ) + { + if ( !mValidObjectives ) + { + return false; + } + } + + // Set the cvars with the available values + Cvar_Set ( "mi_health", difficulty->FindPairValue ( "health", "100" ) ); + Cvar_Set ( "mi_armor", difficulty->FindPairValue ( "armor", "0" ) ); + + // Parse out the timelimit + mTimeLimit = atol(difficulty->FindPairValue("timelimit", "0")); + + // NPC multipliers + mAccuracyMultiplier = atof(difficulty->FindPairValue("npcaccuracy", "1")); + mHealthMultiplier = atof(difficulty->FindPairValue("npchealth", "1")); + + // keep only some of RMG pickups 1 = 100% + mPickupHealth = atof(difficulty->FindPairValue("pickup_health", "1")); + mPickupArmor = atof(difficulty->FindPairValue("pickup_armor", "1")); + mPickupAmmo = atof(difficulty->FindPairValue("pickup_ammo", "1")); + mPickupWeapon = atof(difficulty->FindPairValue("pickup_weapon", "1")); + mPickupEquipment = atof(difficulty->FindPairValue("pickup_equipment", "1")); + + // Its ok to fail parsing instances as long as instances have + // already been parsed at some point + if ( !ParseInstances ( ParseRandom ( difficulty->FindSubGroup ( "instances" ) ) ) ) + { + if ( !mValidInstances ) + { + return false; + } + } + + return true; +} + +/************************************************************************************************ + * CRMMission::Load + * Loads the given mission using the given difficulty level + * + * inputs: + * name: Name of the mission to load (should only be the name rather than the full path) + * difficulty: difficulty level to load + * + * return: + * true: mission successfully loaded + * false: mission failed to load + * + ************************************************************************************************/ +bool CRMMission::Load ( const char* mission, const char* instances, const char* difficulty ) +{ + CGenericParser2 parser; + CGPGroup* root; + + // Create the parser for the mission file + if(!Com_ParseTextFile(va("ext_data/rmg/%s.mission", mission), parser)) + { + if(!Com_ParseTextFile(va("ext_data/arioche/%s.mission", mission), parser)) + { + Com_Printf("ERROR: Failed to open mission file '%s'\n", mission); + return false; + } + } + + // Grab the root parser groop and make sure its mission, otherwise this + // isnt a valid mission file + root = parser.GetBaseParseGroup()->GetSubGroups(); + if(stricmp(root->GetName(), "mission")) + { + Com_Printf("ERROR: '%s' is not a valid mission file\n", mission ); + parser.Clean(); + return false; + } + + // Grab the mission description and set the cvar for it + mDescription = root->FindPairValue ( "description", "" ); +// Cvar_Set("ar_obj_main0",mDescription.c_str(), CVAR_OBJECTIVE); +// Cvar_Set("ar_obj_maincom0", "&OBJECTIVES_INPROGRESS&", CVAR_OBJECTIVE); +// Cvar_SetValue ("ar_cur_objective", 0, CVAR_OBJECTIVE); + + string mInfo = root->FindPairValue ( "info", "" ); +// Cvar_Set("ar_obj_info0",mInfo.c_str(), CVAR_OBJECTIVE); + + mExitScreen = root->FindPairValue ( "exitScreen", "" ); + mTimeExpiredScreen = root->FindPairValue ( "TimeExpiredScreen", "

9q^0& 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

+

Build Log

+

+--------------------Configuration: starwars - Win32 Debug-------------------- +

+

Command Lines

+ + + +

Results

+jk2sp.exe - 0 error(s), 0 warning(s) +
+ + diff --git a/code/starwars.vcproj b/code/starwars.vcproj new file mode 100644 index 0000000..9acf017 --- /dev/null +++ b/code/starwars.vcproj @@ -0,0 +1,6784 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/tonet.bat b/code/tonet.bat new file mode 100644 index 0000000..0a0bc46 --- /dev/null +++ b/code/tonet.bat @@ -0,0 +1,10 @@ +del /q release\jk3sp.exe +del /q release\jk3sp.map +del /q release\jk3gamex86.dll +del /q release\jk3gamex86.map + +call vu.bat + +xcopy/d/y release\jasp.exe w:\game +xcopy/d/y release\jagamex86.dll w:\game +xcopy/d/y release\*.map w:\game \ No newline at end of file diff --git a/code/tosend.bat b/code/tosend.bat new file mode 100644 index 0000000..a47ffb2 --- /dev/null +++ b/code/tosend.bat @@ -0,0 +1,6 @@ +xcopy/y release\jasp.exe c:\send\game\jasp_nocd.exe +xcopy/y finalbuild\jasp.exe c:\send\game +xcopy/y finalbuild\jagamex86.dll c:\send\game +xcopy/y finalbuild\*.map c:\send\game + +rd c:\send\game\base\saves diff --git a/code/ui/gameinfo.cpp b/code/ui/gameinfo.cpp new file mode 100644 index 0000000..4c243e8 --- /dev/null +++ b/code/ui/gameinfo.cpp @@ -0,0 +1,35 @@ +// +// gameinfo.c +// + +// *** This file is used by both the game and the user interface *** + + +// ... and for that reason is excluded from PCH usage for the moment =Ste. + + +#include "gameinfo.h" +#include "..\game\weapons.h" + + +gameinfo_import_t gi; + +weaponData_t weaponData[WP_NUM_WEAPONS]; +ammoData_t ammoData[AMMO_MAX]; + +extern void WP_LoadWeaponParms (void); + +// +// Initialization - Read in files and parse into infos +// + +/* +=============== +GI_Init +=============== +*/ +void GI_Init( gameinfo_import_t *import ) { + gi = *import; + + WP_LoadWeaponParms (); +} diff --git a/code/ui/gameinfo.h b/code/ui/gameinfo.h new file mode 100644 index 0000000..4f16b26 --- /dev/null +++ b/code/ui/gameinfo.h @@ -0,0 +1,24 @@ +#ifndef __GAMEINFO_H__ +#define __GAMEINFO_H__ + + +#include "../game/q_shared.h" +#include + + +typedef struct { + int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); + int (*FS_Read)( void *buffer, int len, fileHandle_t f ); + void (*FS_FCloseFile)( fileHandle_t f ); + void (*Cvar_Set)( const char *name, const char *value ); + void (*Cvar_VariableStringBuffer)( const char *var_name, char *buffer, int bufsize ); + void (*Cvar_Create)( const char *var_name, const char *var_value, int flags ); + int (*FS_ReadFile)( const char *name, void **buf ); + void (*FS_FreeFile)( void *buf ); + void (*Printf)( const char *fmt, ... ); +} gameinfo_import_t; + + +void GI_Init( gameinfo_import_t *import ); + +#endif diff --git a/code/ui/menudef.h b/code/ui/menudef.h new file mode 100644 index 0000000..424942a --- /dev/null +++ b/code/ui/menudef.h @@ -0,0 +1,143 @@ + +#define ITEM_TYPE_TEXT 0 // simple text +#define ITEM_TYPE_BUTTON 1 // button, basically text with a border +#define ITEM_TYPE_RADIOBUTTON 2 // toggle button, may be grouped +#define ITEM_TYPE_CHECKBOX 3 // check box +#define ITEM_TYPE_EDITFIELD 4 // editable text, associated with a cvar +#define ITEM_TYPE_COMBO 5 // drop down list +#define ITEM_TYPE_LISTBOX 6 // scrollable list +#define ITEM_TYPE_MODEL 7 // model +#define ITEM_TYPE_OWNERDRAW 8 // owner draw, name specs what it is +#define ITEM_TYPE_NUMERICFIELD 9 // editable text, associated with a cvar +#define ITEM_TYPE_SLIDER 10 // mouse speed, volume, etc. +#define ITEM_TYPE_YESNO 11 // yes no cvar setting +#define ITEM_TYPE_MULTI 12 // multiple list setting, enumerated +#define ITEM_TYPE_BIND 13 // multiple list setting, enumerated +#define ITEM_TYPE_TEXTSCROLL 14 // scrolling text + + +#define ITEM_ALIGN_LEFT 0 // left alignment +#define ITEM_ALIGN_CENTER 1 // center alignment +#define ITEM_ALIGN_RIGHT 2 // right alignment + +#define ITEM_TEXTSTYLE_NORMAL 0 // normal text +#define ITEM_TEXTSTYLE_BLINK 1 // fast blinking +#define ITEM_TEXTSTYLE_PULSE 2 // slow pulsing +#define ITEM_TEXTSTYLE_SHADOWED 3 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINED 4 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINESHADOWED 5 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_SHADOWEDMORE 6 // drop shadow ( need a color for this ) + +#define WINDOW_BORDER_NONE 0 // no border +#define WINDOW_BORDER_FULL 1 // full border based on border color ( single pixel ) +#define WINDOW_BORDER_HORZ 2 // horizontal borders only +#define WINDOW_BORDER_VERT 3 // vertical borders only +#define WINDOW_BORDER_KCGRADIENT 4 // horizontal border using the gradient bars + +#define WINDOW_STYLE_EMPTY 0 // no background +#define WINDOW_STYLE_FILLED 1 // filled with background color +#define WINDOW_STYLE_GRADIENT 2 // gradient bar based on background color +#define WINDOW_STYLE_SHADER 3 // gradient bar based on background color +#define WINDOW_STYLE_TEAMCOLOR 4 // team color +#define WINDOW_STYLE_CINEMATIC 5 // cinematic + +#define MENU_TRUE 1 // uh.. true +#define MENU_FALSE 0 // and false + +#define HUD_VERTICAL 0x00 +#define HUD_HORIZONTAL 0x01 + +// list box element types +#define LISTBOX_TEXT 0x00 +#define LISTBOX_IMAGE 0x01 + +// list feeders +#define FEEDER_SAVEGAMES 0x00 // save games +#define FEEDER_MAPS 0x01 // text maps based on game type +#define FEEDER_SERVERS 0x02 // servers +#define FEEDER_CLANS 0x03 // clan names +#define FEEDER_ALLMAPS 0x04 // all maps available, in graphic format +#define FEEDER_REDTEAM_LIST 0x05 // red team members +#define FEEDER_BLUETEAM_LIST 0x06 // blue team members +#define FEEDER_PLAYER_LIST 0x07 // players +#define FEEDER_TEAM_LIST 0x08 // team members for team voting +#define FEEDER_MODS 0x09 // +#define FEEDER_DEMOS 0x0a // +#define FEEDER_SCOREBOARD 0x0b // +#define FEEDER_Q3HEADS 0x0c // model heads +#define FEEDER_SERVERSTATUS 0x0d // server status +#define FEEDER_FINDPLAYER 0x0e // find player +#define FEEDER_CINEMATICS 0x0f // cinematics +#define FEEDER_PLAYER_SPECIES 0x10 // models/player/*w +#define FEEDER_PLAYER_SKIN_HEAD 0x11 // head*.skin files in species folder +#define FEEDER_PLAYER_SKIN_TORSO 0x12 // torso*.skin files in species folder +#define FEEDER_PLAYER_SKIN_LEGS 0x13 // lower*.skin files in species folder +#define FEEDER_COLORCHOICES 0x14 // special hack to feed text/actions from playerchoice.txt in species folder +#define FEEDER_MOVES 0x15 // moves for the data pad moves screen +#define FEEDER_MOVES_TITLES 0x16 // move titles for the data pad moves screen +#define FEEDER_LANGUAGES 0x17 // the list of languages +#define FEEDER_PROFILES 0x18 //list of saved profiles +#define FEEDER_LEVELSELECT 0x19 // list of levels for level select cheat screen + +#define UI_SOFT_KEYBOARD_ACCEPT 197 +#define UI_SOFT_KEYBOARD_DELETE 198 +#define UI_SOFT_KEYBOARD 199 +#define UI_VERSION 200 +#define UI_HANDICAP 200 +#define UI_EFFECTS 201 +#define UI_PLAYERMODEL 202 +#define UI_DATAPAD_MISSION 203 +#define UI_DATAPAD_WEAPONS 204 +#define UI_DATAPAD_INVENTORY 205 +#define UI_DATAPAD_FORCEPOWERS 206 +#define UI_SKILL 207 +#define UI_BLUETEAMNAME 208 +#define UI_REDTEAMNAME 209 +#define UI_BLUETEAM1 210 +#define UI_BLUETEAM2 211 +#define UI_BLUETEAM3 212 +#define UI_BLUETEAM4 213 +#define UI_BLUETEAM5 214 +#define UI_REDTEAM1 215 +#define UI_REDTEAM2 216 +#define UI_REDTEAM3 217 +#define UI_REDTEAM4 218 +#define UI_REDTEAM5 219 +#define UI_NETSOURCE 220 +#define UI_NETMAPPREVIEW 221 +#define UI_NETFILTER 222 +#define UI_TIER 223 +#define UI_OPPONENTMODEL 224 +#define UI_TIERMAP1 225 +#define UI_TIERMAP2 226 +#define UI_TIERMAP3 227 +#define UI_PLAYERLOGO 228 +#define UI_OPPONENTLOGO 229 +#define UI_PLAYERLOGO_METAL 230 +#define UI_OPPONENTLOGO_METAL 231 +#define UI_PLAYERLOGO_NAME 232 +#define UI_OPPONENTLOGO_NAME 233 +#define UI_TIER_MAPNAME 234 +#define UI_TIER_GAMETYPE 235 +#define UI_ALLMAPS_SELECTION 236 +#define UI_OPPONENT_NAME 237 +#define UI_VOTE_KICK 238 +#define UI_BOTNAME 239 +#define UI_BOTSKILL 240 +#define UI_REDBLUE 241 +#define UI_CROSSHAIR 242 +#define UI_SELECTEDPLAYER 243 +#define UI_MAPCINEMATIC 244 +#define UI_NETGAMETYPE 245 +#define UI_NETMAPCINEMATIC 246 +#define UI_SERVERREFRESHDATE 247 +#define UI_SERVERMOTD 248 +#define UI_GLINFO 249 +#define UI_KEYBINDSTATUS 250 +#define UI_CLANCINEMATIC 251 +#define UI_MAP_TIMETOBEAT 252 +#define UI_JOINGAMETYPE 253 +#define UI_PREVIEWCINEMATIC 254 +#define UI_STARTMAPCINEMATIC 255 +#define UI_MAPS_SELECTION 256 + diff --git a/code/ui/ui.def b/code/ui/ui.def new file mode 100644 index 0000000..68c7fd2 --- /dev/null +++ b/code/ui/ui.def @@ -0,0 +1,4 @@ +EXPORTS + GetUIAPI + vmMain + dllEntry \ No newline at end of file diff --git a/code/ui/ui_atoms.cpp b/code/ui/ui_atoms.cpp new file mode 100644 index 0000000..568a9e2 --- /dev/null +++ b/code/ui/ui_atoms.cpp @@ -0,0 +1,504 @@ +/********************************************************************** + UI_ATOMS.C + + User interface building blocks and support functions. +**********************************************************************/ + +// leave this at the top of all UI_xxxx files for PCH reasons... +// + +#include "../server/exe_headers.h" + +#include "ui_local.h" +#include "gameinfo.h" +#include "../qcommon/stv_version.h" + +uiimport_t ui; +uiStatic_t uis; + +//externs +static void UI_LoadMenu_f( void ); +static void UI_SaveMenu_f( void ); + +//locals + + +/* +================= +UI_ForceMenuOff +================= +*/ +void UI_ForceMenuOff (void) +{ + ui.Key_SetCatcher( ui.Key_GetCatcher() & ~KEYCATCH_UI ); + ui.Key_ClearStates(); + ui.Cvar_Set( "cl_paused", "0" ); +} + + +/* +================= +UI_SetActiveMenu - + this should be the ONLY way the menu system is brought up + +================= +*/ +extern void S_StopAllSoundsExceptMusic( void ); +void UI_SetActiveMenu( const char* menuname,const char *menuID ) +{ + // Sooper-hack. After we play the ja08 cutscene, the game renders for a couple frames. + // So the cinematic code turns off Present(), and we have to turn it back on here: + extern bool connectSwapOverride; + connectSwapOverride = false; + + // this should be the ONLY way the menu system is brought up (besides the UI_ConsoleCommand below) + + if (cls.state != CA_DISCONNECTED && !ui.SG_GameAllowedToSaveHere(qtrue)) //don't check full sytem, only if incamera + { + return; + } + + if ( !menuname ) { + UI_ForceMenuOff(); + return; + } + + //make sure force-speed and slowmodeath doesn't slow down menus - NOTE: they should reset the timescale when the game un-pauses + Cvar_SetValue( "timescale", 1.0f ); + + UI_Cursor_Show(qtrue); + + // enusure minumum menu data is cached + Menu_Cache(); + + if ( Q_stricmp (menuname, "mainMenu") == 0 ) + { + UI_MainMenu(); + return; + } + + if ( Q_stricmp (menuname, "ingame") == 0 ) + { + ui.Cvar_Set( "cl_paused", "1" ); + // S_StopAllSounds(); + + //NOT USED + //JLF usually called with menuID == NULL but if 'noController' is the menuID + // this forces the pause menu open first and then opens the 'noController' menu + //basically forces the 'ingameMainMenu' open first + // if (menuID) + // UI_InGameMenu(NULL); + //END NOT USED + UI_InGameMenu(menuID); + return; + } + + if ( Q_stricmp (menuname, "datapad") == 0 ) + { + ui.Cvar_Set( "cl_paused", "1" ); + S_StopAllSoundsExceptMusic(); + UI_DataPadMenu(); + return; + } + + if ( Q_stricmp (menuname, "missionfailed_menu") == 0 ) + { + Menus_CloseAll(); + Menus_ActivateByName("missionfailed_menu"); + ui.Key_SetCatcher( KEYCATCH_UI ); + return; + } +//allows the 'noController' menu and similar menus to 'popup' over existing menu + if ( Q_stricmp (menuname, "ui_popup") == 0 ) + { + + Menus_ActivateByName(menuID); + return; + } + +//JLF SPLASHMAIN MPSKIPPED +#ifdef _XBOX + { + Menus_CloseAll(); + if (Menus_ActivateByName(menuname)) + ui.Key_SetCatcher( KEYCATCH_UI ); + else + UI_MainMenu(); + } +#endif + +} + + +/* +================= +UI_Argv +================= +*/ +static char *UI_Argv( int arg ) +{ + static char buffer[MAX_STRING_CHARS]; + + ui.Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +/* +================= +UI_Cvar_VariableString +================= +*/ +char *UI_Cvar_VariableString( const char *var_name ) +{ + static char buffer[MAX_STRING_CHARS]; + + ui.Cvar_VariableStringBuffer( var_name, buffer, sizeof( buffer ) ); + + return buffer; +} + +/* +================= +UI_Cache +================= +*/ +static void UI_Cache_f( void ) +{ + int index; + Menu_Cache(); + +extern const char *lukeForceStatusSounds[]; +extern const char *kyleForceStatusSounds[]; + + for (index = 0; index < 5; index++) + { + DC->registerSound(lukeForceStatusSounds[index], qfalse); + DC->registerSound(kyleForceStatusSounds[index], qfalse); + } + for (index = 1; index <= 18; index++) + { + DC->registerSound(va("sound/chars/storyinfo/%d",index), qfalse); + } + trap_S_RegisterSound("sound/chars/kyle/04kyk001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/kyle/05kyk001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/kyle/07kyk001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/kyle/14kyk001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/kyle/21kyk001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/kyle/24kyk001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/kyle/25kyk001.mp3", qfalse); + + trap_S_RegisterSound("sound/chars/luke/06luk001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/luke/08luk001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/luke/22luk001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/luke/23luk001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/protocol/12pro001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/protocol/15pro001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/protocol/16pro001.mp3", qfalse); + trap_S_RegisterSound("sound/chars/wedge/13wea001.mp3", qfalse); + + + Menus_ActivateByName("ingameMissionSelect1"); + Menus_ActivateByName("ingameMissionSelect2"); + Menus_ActivateByName("ingameMissionSelect3"); +} + + +/* +================= +UI_ConsoleCommand +================= +*/ +void UI_Load(void); //in UI_main.cpp + +qboolean UI_ConsoleCommand( void ) +{ + char *cmd; + + if (!ui.SG_GameAllowedToSaveHere(qtrue)) //only check if incamera + { + return qfalse; + } + + cmd = UI_Argv( 0 ); + + // ensure minimum menu data is available + Menu_Cache(); + + if ( Q_stricmp (cmd, "ui_cache") == 0 ) + { + UI_Cache_f(); + return qtrue; + } + + if ( Q_stricmp (cmd, "levelselect") == 0 ) + { + UI_LoadMenu_f(); + return qtrue; + } + + if ( Q_stricmp (cmd, "ui_teamOrders") == 0 ) + { + UI_SaveMenu_f(); + return qtrue; + } + + if ( Q_stricmp (cmd, "ui_report") == 0 ) + { + UI_Report(); + return qtrue; + } + + if ( Q_stricmp (cmd, "ui_load") == 0 ) + { + UI_Load(); + return qtrue; + } + + return qfalse; +} + + +/* +================= +UI_Init +================= +*/ +void UI_Init( int apiVersion, uiimport_t *uiimport, qboolean inGameLoad ) +{ + ui = *uiimport; + + if ( apiVersion != UI_API_VERSION ) { + ui.Error( ERR_FATAL, "Bad UI_API_VERSION: expected %i, got %i\n", UI_API_VERSION, apiVersion ); + } + + // get static data (glconfig, media) + ui.GetGlconfig( &uis.glconfig ); + + uis.scaley = uis.glconfig.vidHeight * (1.0/480.0); + uis.scalex = uis.glconfig.vidWidth * (1.0/640.0); + + Menu_Cache( ); + + ui.Cvar_Create( "cg_drawCrosshair", "1", CVAR_ARCHIVE ); + ui.Cvar_Create( "cg_marks", "1", CVAR_ARCHIVE ); +// ui.Cvar_Create ("s_language", "english", CVAR_ARCHIVE | CVAR_NORESTART); + ui.Cvar_Create( "g_char_model", "jedi_tf", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_char_skin_head", "head_a1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_char_skin_torso", "torso_a1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_char_skin_legs", "lower_a1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_char_color_red", "255", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_char_color_green", "255", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_char_color_blue", "255", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_saber_type", "single", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_saber", "single_1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_saber2", "", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_saber_color", "yellow", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + ui.Cvar_Create( "g_saber2_color", "yellow", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + + ui.Cvar_Create( "ui_forcepower_inc", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART); + ui.Cvar_Create( "tier_storyinfo", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART); + ui.Cvar_Create( "tiers_complete", "", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART); + ui.Cvar_Create( "ui_prisonerobj_currtotal", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART); + ui.Cvar_Create( "ui_prisonerobj_mintotal", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART); + + ui.Cvar_Create( "g_dismemberment", "3", CVAR_ARCHIVE );//0 = none, 1 = arms and hands, 2 = legs, 3 = waist and head, 4 = mega dismemberment + ui.Cvar_Create( "cg_gunAutoFirst", "1", CVAR_ARCHIVE ); + ui.Cvar_Create( "cg_crosshairIdentifyTarget", "1", CVAR_ARCHIVE ); + ui.Cvar_Create( "g_subtitles", "0", CVAR_ARCHIVE ); + ui.Cvar_Create( "cg_marks", "1", CVAR_ARCHIVE ); + ui.Cvar_Create( "d_slowmodeath", "3", CVAR_ARCHIVE ); + ui.Cvar_Create( "cg_shadows", "1", CVAR_ARCHIVE ); + + ui.Cvar_Create( "cg_runpitch", "0.002", CVAR_ARCHIVE ); + ui.Cvar_Create( "cg_runroll", "0.005", CVAR_ARCHIVE ); + ui.Cvar_Create( "cg_bobup", "0.005", CVAR_ARCHIVE ); + ui.Cvar_Create( "cg_bobpitch", "0.002", CVAR_ARCHIVE ); + ui.Cvar_Create( "cg_bobroll", "0.002", CVAR_ARCHIVE ); + + ui.Cvar_Create( "ui_disableWeaponSway", "0", CVAR_ARCHIVE ); + + + + _UI_Init(inGameLoad); +} + +// these are only here so the functions in q_shared.c can link + +#ifndef UI_HARD_LINKED + +/* +================ +Com_Error +================= +*/ +/* +void Com_Error( int level, const char *error, ... ) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + ui.Error( level, "%s", text); +} +*/ +/* +================ +Com_Printf +================= +*/ +/* +void Com_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + ui.Printf( "%s", text); +} +*/ +#endif + + +/* +================ +UI_DrawNamedPic +================= +*/ +void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ) +{ + qhandle_t hShader; + + hShader = ui.R_RegisterShaderNoMip( picname ); + ui.R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + +/* +================ +UI_DrawHandlePic +================= +*/ +void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ) +{ + float s0; + float s1; + float t0; + float t1; + + if( w < 0 ) { // flip about horizontal + w = -w; + s0 = 1; + s1 = 0; + } + else { + s0 = 0; + s1 = 1; + } + + if( h < 0 ) { // flip about vertical + h = -h; + t0 = 1; + t1 = 0; + } + else { + t0 = 0; + t1 = 1; + } + + ui.R_DrawStretchPic( x, y, w, h, s0, t0, s1, t1, hShader ); +} + +/* +================ +UI_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void UI_FillRect( float x, float y, float width, float height, const float *color ) +{ + ui.R_SetColor( color ); + + ui.R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, uis.whiteShader ); + + ui.R_SetColor( NULL ); +} + +/* +================= +UI_UpdateScreen +================= +*/ +void UI_UpdateScreen( void ) +{ + ui.UpdateScreen(); +} + + +/* +=============== +UI_LoadMenu_f +=============== +*/ +static void UI_LoadMenu_f( void ) +{ + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_ActivateByName("ingameloadMenu"); +} + +/* +=============== +UI_SaveMenu_f +=============== +*/ +static void UI_SaveMenu_f( void ) +{ +// ui.PrecacheScreenshot(); + + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_ActivateByName("ingamesaveMenu"); +} + + +//-------------------------------------------- + +/* +================= +UI_SetColor +================= +*/ +void UI_SetColor( const float *rgba ) +{ + trap_R_SetColor( rgba ); +} + +/*int registeredFontCount = 0; +#define MAX_FONTS 6 +static fontInfo_t registeredFont[MAX_FONTS]; +*/ + +/* +================= +UI_RegisterFont +================= +*/ + +int UI_RegisterFont(const char *fontName) +{ + int iFontIndex = ui.R_RegisterFont(fontName); + if (iFontIndex == 0) + { + iFontIndex = ui.R_RegisterFont("ergoec"); // fall back + } + + return iFontIndex; +} + diff --git a/code/ui/ui_connect.cpp b/code/ui/ui_connect.cpp new file mode 100644 index 0000000..347c044 --- /dev/null +++ b/code/ui/ui_connect.cpp @@ -0,0 +1,127 @@ +// leave this at the top of all UI_xxxx files for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "ui_local.h" + +/* +=============================================================================== + +CONNECTION SCREEN + +=============================================================================== +*/ + +char connectionDialogString[1024]; +char connectionMessageString[1024]; + + +/* +======================== +UI_DrawConnect + +======================== +*/ +void UI_DrawConnect( const char *servername, const char *updateInfoString ) +{ + // We need to use this special hack variable, nothing else is set up yet: + const char *s = Cvar_VariableString( "ui_mapname" ); + + // Special case for first map: + extern SavedGameJustLoaded_e g_eSavedGameJustLoaded; + if ( g_eSavedGameJustLoaded != eFULL && (!strcmp(s,"yavin1") || !strcmp(s,"demo")) ) + { + ui.R_SetColor( colorTable[CT_BLACK] ); + uis.whiteShader = ui.R_RegisterShaderNoMip( "*white" ); + ui.R_DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, uis.whiteShader ); + + const char *t = SE_GetString( "SP_INGAME_ALONGTIME" ); + int w = ui.R_Font_StrLenPixels( t, uiInfo.uiDC.Assets.qhMediumFont, 1.0f ); + ui.R_Font_DrawString( (320)-(w/2), 140, t, colorTable[CT_ICON_BLUE], uiInfo.uiDC.Assets.qhMediumFont, -1, 1.0f ); + return; + } + + // Grab the loading menu: + extern menuDef_t *Menus_FindByName( const char *p ); + menuDef_t *menu = Menus_FindByName( "loadingMenu" ); + if( !menu ) + return; + + // Find the levelshot item, so we can fix up it's shader: + itemDef_t *item = Menu_FindItemByName( menu, "mappic" ); + if( !item ) + return; + + item->window.background = ui.R_RegisterShaderNoMip( va( "levelshots/%s", s ) ); + if (!item->window.background) { + item->window.background = ui.R_RegisterShaderNoMip( "menu/art/unknownmap" ); + } + + // Do the second levelshot as well: + qhandle_t firstShot = item->window.background; + item = Menu_FindItemByName( menu, "mappic2" ); + if( !item ) + return; + + item->window.background = ui.R_RegisterShaderNoMip( va( "levelshots/%s2", s ) ); + if (!item->window.background) { + item->window.background = firstShot; + } + + const char *b = SE_GetString( "BRIEFINGS", s ); + if( b && b[0] ) + Cvar_Set( "ui_missionbriefing", va("@BRIEFINGS_%s", s) ); + else + Cvar_Set( "ui_missionbriefing", "@BRIEFINGS_NONE" ); + + // Now, force it to draw: + extern void Menu_Paint( menuDef_t *menu, qboolean forcePaint ); + Menu_Paint( menu, qtrue ); +} + + +/* +======================== +UI_UpdateConnectionString + +======================== +*/ +void UI_UpdateConnectionString( char *string ) { + Q_strncpyz( connectionDialogString, string, sizeof( connectionDialogString ) ); + UI_UpdateScreen(); +} + +/* +======================== +UI_UpdateConnectionMessageString + +======================== +*/ +void UI_UpdateConnectionMessageString( char *string ) { + char *s; + + Q_strncpyz( connectionMessageString, string, sizeof( connectionMessageString ) ); + + // strip \n + s = strstr( connectionMessageString, "\n" ); + if ( s ) { + *s = 0; + } + UI_UpdateScreen(); +} + +/* +=================== +UI_KeyConnect +=================== +*/ +void UI_KeyConnect( int key ) +{ + if ( key == A_ESCAPE ) + { + ui.Cmd_ExecuteText( EXEC_APPEND, "disconnect\n" ); + return; + } +} + + diff --git a/code/ui/ui_debug.cpp b/code/ui/ui_debug.cpp new file mode 100644 index 0000000..4c86b7c --- /dev/null +++ b/code/ui/ui_debug.cpp @@ -0,0 +1,747 @@ +// Filename:- ui_debug.cpp +// +// an entire temp module just for doing some evil menu hackery during development... +// + + +#include "../server/exe_headers.h" + + +#if 0 // this entire module was special code to StripEd-ify *menu, it isn't needed now, but I'll keep the source around for a while -ste + + +#ifdef _DEBUG +#include +#include "../qcommon/sstring.h" +typedef sstring<4096> sstringBIG_t; +typedef set StringSet_t; + StringSet_t MenusUsed; +typedef map ReferencesAndPackages_t; + ReferencesAndPackages_t ReferencesAndPackage; + + +struct Reference_t +{ + sstringBIG_t sString; + sstring_t sReference; + sstring_t sMenu; + + Reference_t() + { + sString = ""; + sReference = ""; + sMenu = ""; + } + + // sort by menu entry, then by reference within menu... + // + bool operator < (const Reference_t& _X) const + { + int i = stricmp(sMenu.c_str(),_X.sMenu.c_str()); + if (i) + return i<0; + + return !!(stricmp(sReference.c_str(),_X.sReference.c_str()) < 0); + } +}; +#include +typedef list References_t; + References_t BadReferences; + +sstring_t sCurrentMenu; +void UI_Debug_AddMenuFilePath(LPCSTR psMenuFile) // eg "ui/blah.menu" +{ + sCurrentMenu = psMenuFile; + + OutputDebugString(va("Current menu: \"%s\"\n",psMenuFile)); +} + + +typedef struct +{ + // to correct... + // + sstring_t sMenuFile; + sstringBIG_t sTextToFind; // will be either @REFERENCE or "text" + sstringBIG_t sTextToReplaceWith; // will be @NEWREF + // + // to generate new data... + // + sstring_t sStripEdReference; // when NZ, this will create a new StripEd entry... + sstringBIG_t sStripEdText; + sstring_t sStripEdFileRef; // ... in this file reference (eg "SPMENUS%d"), where 0.255 of each all have this in them (for ease of coding) + +} CorrectionDataItem_t; +typedef list CorrectionData_t; + CorrectionData_t CorrectionData; + + +static LPCSTR CreateUniqueReference(LPCSTR psText) +{ + static set ReferencesSoFar; + + static sstringBIG_t NewReference; + + LPCSTR psTextScanPos = psText; + + while(*psTextScanPos) + { + while (isspace(*psTextScanPos)) psTextScanPos++; + + NewReference = psTextScanPos; + + // cap off text at an approx length... + // + const int iApproxReferenceLength = 20; + char *p; + if (iApproxReferenceLength TextConsolidationTable_t; // string and ref + static TextConsolidationTable_t TextConsolidationTable, RefrConsolidationTable; + static int iIndex = 0; // INC'd every time a new StripEd entry is synthesised + + TextConsolidationTable_t::iterator it = TextConsolidationTable.find(psText); + if (it == TextConsolidationTable.end()) + { + // new entry... + // + LPCSTR psNewReference = CreateUniqueReference( (strlen(psReference) > strlen(psText))?psReference:psText ); + + CorrectionDataItem_t CorrectionDataItem; + CorrectionDataItem.sMenuFile = psMenuFile; + CorrectionDataItem.sTextToFind = strlen(psReference) ? ( /* !stricmp(psReference,psNewReference) ? "" :*/ va("@%s",psReference) ) + : va("\"%s\"",psText); + CorrectionDataItem.sTextToReplaceWith = /* !stricmp(psReference,psNewReference) ? "" : */va("@%s",psNewReference); + // + CorrectionDataItem.sStripEdReference = psNewReference; + CorrectionDataItem.sStripEdText = psText; + +// qboolean bIsMulti = !!strstr(psMenuFile,"jk2mp"); +// CorrectionDataItem.sStripEdFileRef = va("%sMENUS%d",bIsMulti?"MP":"SP",iIndex/256); + CorrectionDataItem.sStripEdFileRef = va( "MENUS%d",iIndex/256); + iIndex++; + + CorrectionData.push_back( CorrectionDataItem ); + + TextConsolidationTable[ psText ] = psNewReference; + RefrConsolidationTable[ psText ] = CorrectionDataItem.sStripEdFileRef.c_str(); + } + else + { + // text already entered, so do a little duplicate-resolving... + // + // need to find the reference for the existing version... + // + LPCSTR psNewReference = (*it).second.c_str(); + LPCSTR psPackageRef = (*RefrConsolidationTable.find(psText)).second.c_str(); // yeuch, hack-city + + // only enter correction data if references are different... + // +// if (stricmp(psReference,psNewReference)) + { + CorrectionDataItem_t CorrectionDataItem; + CorrectionDataItem.sMenuFile = psMenuFile; + CorrectionDataItem.sTextToFind = strlen(psReference) ? va("@%s",psReference) : va("\"%s\"",psText); + CorrectionDataItem.sTextToReplaceWith = va("@%s",psNewReference); + // + CorrectionDataItem.sStripEdReference = ""; + CorrectionDataItem.sStripEdText = ""; + CorrectionDataItem.sStripEdFileRef = psPackageRef; + + + CorrectionData.push_back( CorrectionDataItem ); + } + } +} + +static void EnterGoodRef(LPCSTR ps4LetterType, LPCSTR psReference, LPCSTR psPackageReference, LPCSTR psText) +{ + EnterRef(psReference, psText, sCurrentMenu.c_str()); + + ReferencesAndPackage[psReference].insert(psPackageReference); + MenusUsed.insert(psPackageReference); +} + +static bool SendFileToNotepad(LPCSTR psFilename) +{ + bool bReturn = false; + + char sExecString[MAX_PATH]; + + sprintf(sExecString,"notepad %s",psFilename); + + if (WinExec(sExecString, // LPCSTR lpCmdLine, // address of command line + SW_SHOWNORMAL // UINT uCmdShow // window style for new application + ) + >31 // don't ask me, Windoze just uses >31 as OK in this call. + ) + { + // ok... + // + bReturn = true; + } + else + { + assert(0);//ErrorBox("Unable to locate/run NOTEPAD on this machine!\n\n(let me know about this -Ste)"); + } + + return bReturn; +} + +// creates as temp file, then spawns notepad with it... +// +static bool SendStringToNotepad(LPCSTR psWhatever, LPCSTR psLocalFileName) +{ + bool bReturn = false; + + LPCSTR psOutputFileName = va("c:\\%s",psLocalFileName); + + FILE *handle = fopen(psOutputFileName,"wt"); + if (handle) + { + fprintf(handle,"%s",psWhatever); // NOT fprintf(handle,psWhatever), which will try and process built-in %s stuff + fclose(handle); + + bReturn = SendFileToNotepad(psOutputFileName); + } + else + { + assert(0);//ErrorBox(va("Unable to create file \"%s\" for notepad to use!",psOutputFileName)); + } + + return bReturn; +} + + +static qboolean DoFileFindReplace( LPCSTR psMenuFile, LPCSTR psFind, LPCSTR psReplace ) +{ + char *buffer; + + OutputDebugString(va("Loading: \"%s\"\n",psMenuFile)); + + int iLen = FS_ReadFile( psMenuFile,(void **) &buffer); + if (iLen<1) + { + OutputDebugString("Failed!\n"); + assert(0); + return qfalse; + } + + + // find/rep... + // + string str(buffer); + str += "\r\n"; // safety for *(char+1) stuff + + + FS_FreeFile( buffer ); // let go of the buffer + + // originally this kept looping for replacements, but now it only does one (since the find/replace args are repeated + // and this is called again if there are >1 replacements of the same strings to be made... + // +// int iReplacedCount = 0; + char *pFound; + int iSearchPos = 0; + while ( (pFound = strstr(str.c_str()+iSearchPos,psFind)) != NULL) + { + // special check, the next char must be whitespace or carriage return etc, or we're not on a whole-word position... + // + int iFoundLoc = pFound - str.c_str(); + char cAfterFind = pFound[strlen(psFind)]; + if (cAfterFind > 32) + { + // ... then this string was part of a larger one, so ignore it... + // + iSearchPos = iFoundLoc+1; + continue; + } + + str.replace(iFoundLoc, strlen(psFind), psReplace); +// iSearchPos = iFoundLoc+1; +// iReplacedCount++; + break; + } + +// assert(iReplacedCount); +// if (iReplacedCount>1) +// { +// int z=1; +// } + FS_WriteFile( psMenuFile, str.c_str(), strlen(str.c_str())); + + OutputDebugString("Ok\n"); + + return qtrue; +} + +void UI_Dump_f(void) +{ + string sFinalOutput; + vector vStripEdFiles; + +#define OUTPUT sFinalOutput+= +#define OUTPUTSTRIP vStripEdFiles[vStripEdFiles.size()-1] += + + OUTPUT("### UI_Dump(): Top\n"); + + for (ReferencesAndPackages_t::iterator it = ReferencesAndPackage.begin(); it!=ReferencesAndPackage.end(); ++it) + { + if ( (*it).second.size()>1) + { + OUTPUT(va("!!!DUP: Ref \"%s\" exists in:\n",(*it).first.c_str())); + StringSet_t &Set = (*it).second; + for (StringSet_t::iterator itS = Set.begin(); itS!=Set.end(); ++itS) + { + OUTPUT(va("%s\n",(*itS).c_str())); + } + } + } + + OUTPUT("\nSP Package Reference list:\n"); + + for (StringSet_t::iterator itS = MenusUsed.begin(); itS!=MenusUsed.end(); ++itS) + { + OUTPUT(va("%s\n",(*itS).c_str())); + } + + OUTPUT("\nBad Text list:\n"); + + for (References_t::iterator itBad=BadReferences.begin(); itBad!=BadReferences.end();++itBad) + { + Reference_t &BadReference = (*itBad); + + OUTPUT(va("File: %30s \"%s\"\n",BadReference.sMenu.c_str(), BadReference.sString.c_str())); + } + + OUTPUT("\nAdding bad references to final correction list...\n"); + + for (itBad=BadReferences.begin(); itBad!=BadReferences.end();++itBad) + { + Reference_t &BadReference = (*itBad); + + EnterRef("", BadReference.sString.c_str(), BadReference.sMenu.c_str() ); + } + + + OUTPUT("\nFinal correction list:\n"); + +// qboolean bIsMulti = !!strstr((*CorrectionData.begin()).sMenuFile.c_str(),"jk2mp"); + + // actually do the find/replace... + // + for (CorrectionData_t::iterator itCorrectionData = CorrectionData.begin(); itCorrectionData != CorrectionData.end(); ++itCorrectionData) + { + CorrectionDataItem_t &CorrectionDataItem = (*itCorrectionData); + + if (CorrectionDataItem.sTextToFind.c_str()[0] && CorrectionDataItem.sTextToReplaceWith.c_str()[0]) + { + OUTPUT( va("Load File: \"%s\", find \"%s\", replace with \"%s\"\n", + CorrectionDataItem.sMenuFile.c_str(), + CorrectionDataItem.sTextToFind.c_str(), + CorrectionDataItem.sTextToReplaceWith.c_str() + ) + ); + + +// if (strstr(CorrectionDataItem.sTextToReplaceWith.c_str(),"START_A_NEW_GAME")) +// { +// int z=1; +// } + assert( CorrectionDataItem.sTextToReplaceWith.c_str()[0] ); + string sReplace( CorrectionDataItem.sTextToReplaceWith.c_str() ); + sReplace.insert(1,"_"); + sReplace.insert(1,CorrectionDataItem.sStripEdFileRef.c_str()); + + DoFileFindReplace( CorrectionDataItem.sMenuFile.c_str(), + CorrectionDataItem.sTextToFind.c_str(), + sReplace.c_str()//CorrectionDataItem.sTextToReplaceWith.c_str() + ); + } + } + + + // scan in all SP files into one huge string, so I can pick out any foreign translations to add in when generating + // new StripEd files... + // + char **ppsFiles; + char *buffers[1000]; // max # SP files, well-OTT. + int iNumFiles; + int i; + string sStripFiles; + + // scan for shader files + ppsFiles = FS_ListFiles( "strip", ".sp", &iNumFiles ); + if ( !ppsFiles || !iNumFiles ) + { + assert(0); + } + else + { + // load files... + // + for (i=0; i=0; i-- ) + { + sStripFiles += buffers[i]; + sStripFiles += "\r\n"; + + FS_FreeFile( buffers[i] ); + } + } + + int iIndex=0; + for (itCorrectionData = CorrectionData.begin(); itCorrectionData != CorrectionData.end(); ++itCorrectionData) + { + CorrectionDataItem_t &CorrectionDataItem = (*itCorrectionData); + + if (CorrectionDataItem.sStripEdReference.c_str()[0] // skip over duplicate-resolving entries +// && CorrectionDataItem.sStripEdText.c_str()[0] // + ) + { + string strAnyForeignStringsFound; // will be entire line plus CR + string strNotes; // will be just the bit within quotes + + LPCSTR psFoundExisting; + int iInitialSearchPos = 0; + while (iInitialSearchPos < sStripFiles.size() && + (strAnyForeignStringsFound.empty() || strNotes.empty()) + ) + { + if ( (psFoundExisting = strstr( sStripFiles.c_str()+iInitialSearchPos, va("\"%s\"",CorrectionDataItem.sStripEdText.c_str()))) != NULL ) + { + // see if we can find any NOTES entry above this... + // + LPCSTR p; + + if (strNotes.empty()) + { + p = psFoundExisting; + while (p > sStripFiles.c_str() && *p!='{') + { + if (!strnicmp(p,"NOTES",5) && isspace(p[-1]) && isspace(p[5])) + { + p = strchr(p,'"'); + if (!p++) + break; + while (*p != '"') + strNotes += *p++; + break; + } + p--; + } + } + + // now search for any foreign versions we already have translated... + // + if (strAnyForeignStringsFound.empty()) + { + p = psFoundExisting; + LPCSTR psNextBrace = strchr(p,'}'); + assert(psNextBrace); + if (psNextBrace) + { + for (int i=2; i<10; i++) + { + LPCSTR psForeign = strstr(p,va("TEXT_LANGUAGE%d",i)); + if (psForeign && psForeign < psNextBrace) + { + strAnyForeignStringsFound += " "; + while (*psForeign != '\n' && *psForeign != '\0') + { + strAnyForeignStringsFound += *psForeign++; + } + strAnyForeignStringsFound += "\n"; + } + } + } + } + + iInitialSearchPos = psFoundExisting - sStripFiles.c_str(); + iInitialSearchPos++; // one past, so we don't re-find ourselves + } + else + { + break; + } + } + + if (!strNotes.empty()) + { + strNotes = va(" NOTES \"%s\"\n",strNotes.c_str()); + } + + // now do output... + // + if (!(iIndex%256)) + { + string s; + vStripEdFiles.push_back(s); + + OUTPUTSTRIP( va( "VERSION 1\n" + "CONFIG W:\\bin\\striped.cfg\n" + "ID %d\n" + "REFERENCE MENUS%d\n" + "DESCRIPTION \"menu text\"\n" + "COUNT 256\n", // count will need correcting for last one + 250 + (iIndex/256), // 250 range seems to be unused + iIndex/256 + ) + ); + +// OUTPUTSTRIP( va("REFERENCE %s\n", va("%sMENUS%d",bIsMulti?"MP":"SP",iIndex/256)) ); +// OUTPUTSTRIP( va("REFERENCE %s\n", va( "MENUS%d",iIndex/256)) ); + } + + OUTPUTSTRIP( va( "INDEX %d\n" + "{\n" + " REFERENCE %s\n" + "%s" + " TEXT_LANGUAGE1 \"%s\"\n" + "%s" + "}\n", + iIndex%256, + CorrectionDataItem.sStripEdReference.c_str(), + (strNotes.empty()?"":strNotes.c_str()), + CorrectionDataItem.sStripEdText.c_str(), + strAnyForeignStringsFound.c_str() + ) + ); + + iIndex++; + } + } + + OUTPUT("### UI_Dump(): Bottom\n"); + + SendStringToNotepad(sFinalOutput.c_str(), "temp.txt"); + + // output the SP files... + // + for (i=0; i +#include + +#include "../game/q_shared.h" +#include "../renderer/tr_types.h" +#include "../qcommon/qcommon.h" +#include "ui_public.h" +#include "ui_shared.h" + +#define MAX_PLAYERMODELS 32 +#define MAX_DEFERRED_SCRIPT 1024 + +// +// ui_qmenu.c +// +#define MAX_EDIT_LINE 256 + +typedef struct { + int cursor; + int scroll; + int widthInChars; + char buffer[MAX_EDIT_LINE]; + int maxchars; + int style; + int textEnum; // Label + int textcolor; // Normal color + int textcolor2; // Highlight color +} uifield_t; + +extern void Menu_Cache( void ); + +// +// ui_field.c +// +extern void Field_Clear( uifield_t *edit ); +extern void Field_CharEvent( uifield_t *edit, int ch ); +extern void Field_Draw( uifield_t *edit, int x, int y, int width, int size,int color,int color2, qboolean showCursor ); + + +// +// ui_menu.c +// +extern void UI_MainMenu(void); +extern void UI_InGameMenu(const char*holoFlag); +extern void AssetCache(void); +extern void UI_DataPadMenu(void); + +// +// ui_connect.c +// +extern void UI_DrawConnect( const char *servername, const char * updateInfoString ); +extern void UI_UpdateConnectionString( char *string ); +extern void UI_UpdateConnectionMessageString( char *string ); + + +// +// ui_atoms.c +// + +#define UI_FADEOUT 0 +#define UI_FADEIN 1 + +typedef struct { + int frametime; + int realtime; + int cursorx; + int cursory; + + glconfig_t glconfig; + qboolean debugMode; + qhandle_t whiteShader; + qhandle_t menuBackShader; + qhandle_t cursor; + float scalex; + float scaley; + //float bias; + qboolean firstdraw; +} uiStatic_t; + +extern void UI_FillRect( float x, float y, float width, float height, const float *color ); +extern void UI_DrawString( int x, int y, const char* str, int style, vec4_t color ); +extern void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ); +extern void UI_UpdateScreen( void ); +extern int UI_RegisterFont(const char *fontName); +extern void UI_SetColor( const float *rgba ); +extern char *UI_Cvar_VariableString( const char *var_name ); + +extern uiStatic_t uis; +extern uiimport_t ui; + + +#define MAX_MOVIES 256 +#define MAX_MODS 64 + +typedef struct { + const char *modName; + const char *modDescr; +} modInfo_t; + +typedef struct { + char Name[64]; + int SkinHeadCount; +// qhandle_t SkinHeadIcons[MAX_PLAYERMODELS]; + char SkinHeadNames[MAX_PLAYERMODELS][16]; + int SkinTorsoCount; +// qhandle_t SkinTorsoIcons[MAX_PLAYERMODELS]; + char SkinTorsoNames[MAX_PLAYERMODELS][16]; + int SkinLegCount; +// qhandle_t SkinLegIcons[MAX_PLAYERMODELS]; + char SkinLegNames[MAX_PLAYERMODELS][16]; + char ColorShader[MAX_PLAYERMODELS][64]; + int ColorCount; + char ColorActionText[MAX_PLAYERMODELS][128]; +} playerSpeciesInfo_t; + +typedef struct { + displayContextDef_t uiDC; + + int effectsColor; + int currentCrosshair; + + modInfo_t modList[MAX_MODS]; + int modIndex; + int modCount; + + int playerSpeciesCount; + playerSpeciesInfo_t playerSpecies[MAX_PLAYERMODELS]; + int playerSpeciesIndex; + + + char deferredScript [ MAX_DEFERRED_SCRIPT ]; + itemDef_t* deferredScriptItem; + + itemDef_t* runScriptItem; + + qboolean inGameLoad; + // Used by Force Power allocation screen + short forcePowerUpdated; // Enum of which power had the point allocated + // Used by Weapon allocation screen + short selectedWeapon1; // 1st weapon chosen +// char selectedWeapon1ItemName[64]; // Item name of weapon chosen +// int selectedWeapon1AmmoIndex; // Holds index to ammo + short selectedWeapon2; // 2nd weapon chosen +// char selectedWeapon2ItemName[64]; // Item name of weapon chosen +// int selectedWeapon2AmmoIndex; // Holds index to ammo + short selectedThrowWeapon; // throwable weapon chosen +// char selectedThrowWeaponItemName[64]; // Item name of weapon chosen +// int selectedThrowWeaponAmmoIndex; // Holds index to ammo + + itemDef_t *weapon1ItemButton; + qhandle_t litWeapon1Icon; + qhandle_t unlitWeapon1Icon; + itemDef_t *weapon2ItemButton; + qhandle_t litWeapon2Icon; + qhandle_t unlitWeapon2Icon; + + itemDef_t *weaponThrowButton; + qhandle_t litThrowableIcon; + qhandle_t unlitThrowableIcon; + short movesTitleIndex; + char *movesBaseAnim; + int moveAnimTime; +// int languageCount; + int languageCountIndex; +} uiInfo_t; + +extern uiInfo_t uiInfo; + +// +// ui_main.c +// +void _UI_Init( qboolean inGameLoad ); +void _UI_DrawRect( float x, float y, float width, float height, float size, const float *color ); +void _UI_MouseEvent( int dx, int dy ); +void _UI_KeyEvent( int key, qboolean down ); +void UI_Report(void); + +extern char GoToMenu[]; + + +// +// ui_syscalls.c +// +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits, const char *psAudioFile /* = NULL */); +int trap_CIN_StopCinematic(int handle); +void trap_Cvar_Set( const char *var_name, const char *value ); +float trap_Cvar_VariableValue( const char *var_name ); +void trap_GetGlconfig( glconfig_t *glconfig ); +void trap_Key_ClearStates( void ); +int trap_Key_GetCatcher( void ); +qboolean trap_Key_GetOverstrikeMode( void ); +void trap_Key_SetBinding( int keynum, const char *binding ); +void trap_Key_SetCatcher( int catcher ); +void trap_Key_SetOverstrikeMode( qboolean state ); +void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); +void trap_R_SetColor( const float *rgba ); +void trap_R_ClearScene( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_S_StopSounds( void ); +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +#ifdef _IMMERSION +ffHandle_t trap_FF_Register( const char *name, int channel = FF_CHANNEL_MENU ); +void trap_FF_Start( ffHandle_t ff ); +#endif // _IMMERSION +#ifndef _XBOX +int PASSFLOAT( float x ); +#endif + + + +void _UI_Refresh( int realtime ); + +//from xboxcommon.h (from MP) +// +// Xbox Error Popup Constants +// +// The error popup system needs a context when it returns a value to know +// what to do. One of these is saved off when we create the popup, and then it's +// queried when we get a response so we know what we're doing. +enum xbErrorPopupType +{ + XB_POPUP_NONE, + + XB_POPUP_DELETE_CONFIRM, + XB_POPUP_DISKFULL, // Only at startup + XB_POPUP_DISKFULL_DURING_SAVE, // When trying to save a new game + XB_POPUP_SAVE_COMPLETE, + XB_POPUP_OVERWRITE_CONFIRM, + XB_POPUP_LOAD_FAILED, + XB_POPUP_LOAD_CHECKPOINT_FAILED, + XB_POPUP_QUIT_CONFIRM, + XB_POPUP_SAVING, + XB_POPUP_LOAD_CONFIRM, + XB_POPUP_TOO_MANY_SAVES, + XB_POPUP_LOAD_CONFIRM_CHECKPOINT, + XB_POPUP_CONFIRM_INVITE, + XB_POPUP_CORRUPT_SETTINGS, + XB_POPUP_DISKFULL_BOTH, + XB_POPUP_YOU_ARE_DEAD, + XB_POPUP_TESTING_SAVE, + XB_POPUP_CORRUPT_SCREENSHOT, + XB_POPUP_CONFIRM_NEW_1, + XB_POPUP_CONFIRM_NEW_2, + XB_POPUP_CONFIRM_NEW_3, +}; +void UI_xboxErrorPopup(xbErrorPopupType popup); +void UI_xboxPopupResponse(void); +void UI_CheckForInvite( void ); + + + + +#endif diff --git a/code/ui/ui_main.cpp b/code/ui/ui_main.cpp new file mode 100644 index 0000000..20c10d7 --- /dev/null +++ b/code/ui/ui_main.cpp @@ -0,0 +1,8580 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/* +======================================================================= + +USER INTERFACE MAIN + +======================================================================= +*/ + +// leave this at the top of all UI_xxxx files for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "ui_local.h" + +#include "menudef.h" + +#include "ui_shared.h" + +#include "../ghoul2/G2.h" + +#include "../game/bg_public.h" +#include "../game/anims.h" +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +#include "../qcommon/stv_version.h" + +#ifdef _XBOX +#include +#define filepathlength 120 +#endif + +#include "../qcommon/xb_settings.h" + +//JLF +int gScrollAccum = 0; +int gScrollDelta = 0; + +#define TEXTSCROLLDESCRETESTEP 50 +#define SCROLL_SENSITIVITY 2 + +#define ARROW_SPACE 8 + + +extern qboolean ItemParse_model_g2anim_go( itemDef_t *item, const char *animName ); +extern qboolean ItemParse_asset_model_go( itemDef_t *item, const char *name ); +extern qboolean ItemParse_model_g2skin_go( itemDef_t *item, const char *skinName ); +extern qboolean UI_SaberModelForSaber( const char *saberName, char *saberModel ); +extern qboolean UI_SaberSkinForSaber( const char *saberName, char *saberSkin ); +extern void UI_SaberAttachToChar( itemDef_t *item ); + +extern unsigned long SG_SaveGameSize(); + + +// VIRTUAL KEYBOARD DEFINES ETC +// +// Warning: These next values must work out so that there are at least 2 columns and/or +// 2 rows. Otherwise you will not be able to compile because of divide by zero errors. +// Not to mention the ugly keyboard you'd be designing + +#define SKB_NUM_LETTERS (36) +#define SKB_NUM_COLS (10) // must be > 1 and < SKB_NUM_LETTERS-1 +#define SKB_NUM_ROWS ((SKB_NUM_LETTERS%SKB_NUM_COLS)?(SKB_NUM_LETTERS/SKB_NUM_COLS+1):(SKB_NUM_LETTERS/SKB_NUM_COLS)) +#define SKB_TOP (225) +#define SKB_BOT (350) +#define SKB_LEFT (100) +#define SKB_RIGHT (540) +#define SKB_STRING_LENGTH (10) +#define SKB_STRING_TOP (150) +#define SKB_STRING_LEFT (200) +#define SKB_SPACE_H ((SKB_RIGHT-SKB_LEFT)/(SKB_NUM_COLS-1)) +#define SKB_SPACE_V ((SKB_BOT-SKB_TOP)/(SKB_NUM_ROWS-1)) +#define SKB_ACCEPT_NAME ("skb_accept") +#define SKB_DELETE_NAME ("skb_delete") +#define SKB_KEYBOARD_NAME ("skb_keyboard") +#define SKB_OK_X (390) +#define SKB_OK_Y (400) +#define SKB_BACKSPACE_X (250) +#define SKB_BACKSPACE_Y (400) + + +char *letters[SKB_NUM_LETTERS] = { + "0", "1", "2", "3", + "4", "5", "6", "7", + "8", "9", + "A", "B", "C", "D", + "E", "F", "G", "H", + "I", "J", "K", "L", + "M", "N", "O", "P", + "Q", "R", "S", "T", + "U", "V", "W", "X", + "Y", "Z", }; + +typedef struct +{ + short activeKey; + short curStringPos; + short curCol; + short curRow; +} softkeyboardDef_t; + +softkeyboardDef_t skb; + + +extern qboolean PC_Script_Parse(const char **out); + +#define LISTBUFSIZE 10240 + +static struct +{ + char listBuf[LISTBUFSIZE]; // The list of file names read in + + // For scrolling through file names + int currentLine; // Index to currentSaveFileComments[] currently highlighted + int saveFileCnt; // Number of save files read in + + int awaitingSave; // Flag to see if user wants to overwrite a game. + + char *savegameMap; + int savegameFromFlag; +} s_savegame; + +//JLF MPMOVED +#ifdef _XBOX + +struct playerProfile_t +{ + + // For scrolling through file names + int currentLine; // Index to currentSaveFileComments[] currently highlighted + int fileCnt; // Number of save files read in + char *modelName; +}; + +playerProfile_t s_playerProfile; + +void resetProfileFileCount() +{ + s_playerProfile.fileCnt = -1; + +}; +#endif + + + +#ifdef _XBOX +#define MAX_SAVELOADFILES 100 +//JLF MPMOVED +#define MAX_PROFILEFILES 8 +#else +#define MAX_SAVELOADFILES 100 +#endif +#define MAX_SAVELOADNAME 32 + +//byte screenShotBuf[SG_SCR_WIDTH * SG_SCR_HEIGHT * 4]; + +typedef struct +{ + char *currentSaveFileName; // file name of savegame + char currentSaveFileComments[iSG_COMMENT_SIZE]; // file comment + char currentSaveFileDateTimeString[iSG_COMMENT_SIZE]; // file time and date + time_t currentSaveFileDateTime; +#ifdef _XBOX + char currentSaveFileMap[32]; // map save game is from +#else + char currentSaveFileMap[MAX_TOKEN_CHARS]; // map save game is from +#endif + char corrupt; + char screenshotNotify; +} savedata_t; + +static savedata_t s_savedata[MAX_SAVELOADFILES]; + +//JLF +char g_loadsaveGameName[MAX_SAVELOADNAME]; +qboolean g_loadsaveGameNameInitialized = qfalse; + +extern unsigned long SG_BlocksLeft(); + +//JLF used to tell if delete button should be shown +static qboolean ui_ShowDeleteActive =0 ; + +void storeSGDataDatetoCvar(); +void storeSGDataTimetoCvar(); +void storeSGDataDiffLeveltoCvar(); + + + +//JLF MPMOVED +#ifdef _XBOX +typedef struct +{ + char currentProfileName[iSG_COMMENT_SIZE]; // file name of savegame + char currentProfileComments[iSG_COMMENT_SIZE]; // file comment + char currentProfileDateTimeString[iSG_COMMENT_SIZE]; // file time and date + time_t currentProfileDateTime; +} profileData_t; + +static profileData_t s_ProfileData[MAX_PROFILEFILES]; +#endif + + +void UI_SetActiveMenu( const char* menuname,const char *menuID ); +void ReadSaveDirectory (void); +//JLF MPMOVED +#ifdef _XBOX + +//void ReadSaveDirectoryProfiles(void); +void UI_UpdateSettingsCvars(void); +static void UI_UpdateVolume(const char* action, const char* type, const char* itemName, int width, int value ); +static void UI_UpdateMoves(void); +static void UI_UpdateMoveTitles(void); + +#endif +void Item_RunScript(itemDef_t *item, const char *s); +qboolean Item_SetFocus(itemDef_t *item, float x, float y); + +qboolean Asset_Parse(char **buffer); +menuDef_t *Menus_FindByName(const char *p); +void Menus_HideItems(const char *menuName); +int Text_Height(const char *text, float scale, int iFontIndex ); +int Text_Width(const char *text, float scale, int iFontIndex ); +void _UI_DrawTopBottom(float x, float y, float w, float h, float size); +void _UI_DrawSides(float x, float y, float w, float h, float size); +void UI_CheckVid1Data(const char *menuTo,const char *warningMenuName); +void UI_GetVideoSetup ( void ); +void UI_UpdateVideoSetup ( void ); +static void UI_UpdateCharacterCvars ( void ); +static void UI_GetCharacterCvars ( void ); +static void UI_UpdateSaberCvars ( void ); +static void UI_GetSaberCvars ( void ); +static void UI_ResetSaberCvars ( void ); +static void UI_InitAllocForcePowers ( const char *forceName ); +static void UI_AffectForcePowerLevel ( const char *forceName ); +static void UI_SetUpForceSelect( void ); +static void UI_ShowForceLevelDesc ( const char *forceName ); +static void UI_ResetForceLevels ( void ); +static void UI_ClearWeapons ( void ); +static void UI_GiveWeapon ( const int weaponIndex ); +static void UI_EquipWeapon ( const int weaponIndex ); +static void UI_LoadMissionSelectMenu( const char *cvarName ); +static void UI_AddWeaponSelection ( const int weaponIndex, const int ammoIndex, const int ammoAmount, const char *iconItemName,const char *litIconItemName, const char *hexBackground, const char *soundfile ); +static void UI_AddThrowWeaponSelection ( const int weaponIndex, const int ammoIndex, const int ammoAmount, const char *iconItemName,const char *litIconItemName, const char *hexBackground, const char *soundfile ); +static void UI_RemoveWeaponSelection ( const int weaponIndex ); +static void UI_RemoveThrowWeaponSelection ( void ); +static void UI_HighLightWeaponSelection ( const int selectionslot ); +static void UI_NormalWeaponSelection ( const int selectionslot ); +static void UI_NormalThrowSelection ( void ); +static void UI_HighLightThrowSelection( void ); +static void UI_ClearInventory ( void ); +static void UI_GiveInventory ( const int itemIndex, const int amount ); +static void UI_ForcePowerWeaponsButton(qboolean activeFlag); +static void UI_UpdateCharacterSkin( void ); +static void UI_UpdateCharacter( qboolean changedModel ); +static void UI_UpdateSaberType( void ); +static void UI_UpdateSaberHilt( qboolean secondSaber ); +//static void UI_UpdateSaberColor( qboolean secondSaber ); +static void UI_InitWeaponSelect( void ); +static void UI_DisableWeapon( void ); +static void UI_WeaponHelpActive( void ); +static void UI_UpdateFightingStyle ( void ); +static void UI_UpdateFightingStyleChoices ( void ); +static void UI_CalcForceStatus(void); +static void UI_DecrementForcePowerLevel( void ); +static void UI_DecrementCurrentForcePower ( void ); +static void UI_ShutdownForceHelp( void ); +static void UI_ForceHelpActive( void ); +static void UI_ResetCharacterListBoxes( void ); +static void UI_CheckForForceCheat( void ); + +void UI_LoadMenus(const char *menuFile, qboolean reset); +static void UI_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle, int iFontIndex); +static qboolean UI_OwnerDrawVisible(int flags); +int UI_OwnerDrawWidth(int ownerDraw, float scale); +static void UI_Update(const char *name); +void UI_UpdateCvars( void ); +void UI_ResetDefaults( void ); +void UI_AdjustSaveGameListBox( int currentLine ); +static void UI_FeederSelection(float feederID, int index, itemDef_t *item); + +void Menus_CloseByName(const char *p); + +static qboolean UI_SoftKeyboard_HandleKey(int flags, float *special, int key); +static qboolean UI_SoftKeyboardDelete_HandleKey(int flags, float *special, int key); +static qboolean UI_SoftKeyboardAccept_HandleKey(int flags, float *special, int key); +static void UI_SoftKeyboardInit(); +static void UI_SoftKeyboardDelete(); +static void UI_SoftKeyboardAccept(); +static void UI_SoftKeyboard_Draw(); +static void UI_SoftKeyboardDelete_Draw(); +static void UI_SoftKeyboardAccept_Draw(); + +// Level select screen data +struct levelSelect_t +{ + char mapname[MAX_QPATH]; // Map name to use when loading + char displayName[32]; // Text to show in the listbox + int forceLevel; // Level of neutral force powers +}; + +const levelSelect_t levelSelectData[] = { + { "yavin1b", "@MENUS_YAVIN1B", 0 }, + { "yavin2", "@MENUS_YAVIN2", 1 }, + + { "t1_danger", "@MENUS_T1_DANGER", 1 }, + { "t1_fatal", "@MENUS_T1_FATAL", 1 }, + { "t1_rail", "@MENUS_T1_RAIL", 1 }, + { "t1_sour", "@MENUS_T1_SOUR", 1 }, + { "t1_surprise", "@MENUS_T1_SURPRISE", 1 }, + + { "hoth2", "@MENUS_HOTH2", 1 }, + { "hoth3", "@MENUS_HOTH3", 1 }, + + { "t2_dpred", "@MENUS_T2_DPRED", 2 }, + { "t2_rancor", "@MENUS_T2_RANCOR", 2 }, + { "t2_rogue", "@MENUS_T2_ROGUE", 2 }, + { "t2_trip", "@MENUS_T2_TRIP", 2 }, + { "t2_wedge", "@MENUS_T2_WEDGE", 2 }, + + { "vjun1", "@MENUS_VJUN1", 2 }, + { "vjun2", "@MENUS_VJUN2", 2 }, + { "vjun3", "@MENUS_VJUN3", 2 }, + + { "t3_bounty", "@MENUS_T3_BOUNTY", 3 }, + { "t3_byss", "@MENUS_T3_BYSS", 3 }, + { "t3_hevil", "@MENUS_T3_HEVIL", 3 }, + { "t3_rift", "@MENUS_T3_RIFT", 3 }, + { "t3_stamp", "@MENUS_T3_STAMP", 3 }, + + { "taspir1", "@MENUS_TASPIR1", 3 }, + { "taspir2", "@MENUS_TASPIR2", 3 }, + { "kor1", "@MENUS_KOR1", 3 }, + { "kor2", "@MENUS_KOR2", 3 }, +}; + +const int levelSelectSize = sizeof(levelSelectData) / sizeof(levelSelectData[0]); + +// Currently selected map in level select cheat screen +int levelSelectChoice = 0; + +// Movedata Sounds +typedef enum +{ + MDS_NONE = 0, + MDS_FORCE_JUMP, + MDS_ROLL, + MDS_SABER, + MDS_MOVE_SOUNDS_MAX +}; + +typedef enum +{ + MD_ACROBATICS = 0, + MD_SINGLE_FAST, + MD_SINGLE_MEDIUM, + MD_SINGLE_STRONG, + MD_DUAL_SABERS, + MD_SABER_STAFF, + MD_MOVE_TITLE_MAX +}; + +// Some hard coded badness +// At some point maybe this should be externalized to a .dat file +char *datapadMoveTitleData[MD_MOVE_TITLE_MAX] = +{ +"@MENUS_ACROBATICS", +"@MENUS_SINGLE_FAST", +"@MENUS_SINGLE_MEDIUM", +"@MENUS_SINGLE_STRONG", +"@MENUS_DUAL_SABERS", +"@MENUS_SABER_STAFF", +}; + +char *datapadMoveTitleBaseAnims[MD_MOVE_TITLE_MAX] = +{ +"BOTH_RUN1", +"BOTH_SABERFAST_STANCE", +"BOTH_STAND2", +"BOTH_SABERSLOW_STANCE", +"BOTH_SABERDUAL_STANCE", +"BOTH_SABERSTAFF_STANCE", +}; + +#define MAX_MOVES 16 + +typedef struct +{ + char *title; + char *desc; + char *anim; + short sound; +} datpadmovedata_t; + +static datpadmovedata_t datapadMoveData[MD_MOVE_TITLE_MAX][MAX_MOVES] = +{ +// Acrobatics +"@MENUS_FORCE_JUMP1", "@MENUS_FORCE_JUMP1_DESC", "BOTH_FORCEJUMP1", MDS_FORCE_JUMP, +"@MENUS_FORCE_FLIP", "@MENUS_FORCE_FLIP_DESC", "BOTH_FLIP_F", MDS_FORCE_JUMP, +"@MENUS_ROLL", "@MENUS_ROLL_DESC", "BOTH_ROLL_F", MDS_ROLL, +"@MENUS_BACKFLIP_OFF_WALL", "@MENUS_BACKFLIP_OFF_WALL_DESC", "BOTH_WALL_FLIP_BACK1", MDS_FORCE_JUMP, +"@MENUS_SIDEFLIP_OFF_WALL", "@MENUS_SIDEFLIP_OFF_WALL_DESC", "BOTH_WALL_FLIP_RIGHT", MDS_FORCE_JUMP, +"@MENUS_WALL_RUN", "@MENUS_WALL_RUN_DESC", "BOTH_WALL_RUN_RIGHT", MDS_FORCE_JUMP, +"@MENUS_LONG_JUMP", "@MENUS_LONG_JUMP_DESC", "BOTH_FORCELONGLEAP_START", MDS_FORCE_JUMP, +"@MENUS_WALL_GRAB_JUMP", "@MENUS_WALL_GRAB_JUMP_DESC", "BOTH_FORCEWALLREBOUND_FORWARD",MDS_FORCE_JUMP, +"@MENUS_RUN_UP_WALL_BACKFLIP", "@MENUS_RUN_UP_WALL_BACKFLIP_DESC", "BOTH_FORCEWALLRUNFLIP_START", MDS_FORCE_JUMP, +"@MENUS_JUMPUP_FROM_KNOCKDOWN", "@MENUS_JUMPUP_FROM_KNOCKDOWN_DESC","BOTH_KNOCKDOWN3", MDS_NONE, +"@MENUS_JUMPKICK_FROM_KNOCKDOWN", "@MENUS_JUMPKICK_FROM_KNOCKDOWN_DESC","BOTH_KNOCKDOWN2", MDS_NONE, +"@MENUS_ROLL_FROM_KNOCKDOWN", "@MENUS_ROLL_FROM_KNOCKDOWN_DESC", "BOTH_KNOCKDOWN1", MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, + +//Single Saber, Fast Style +"@MENUS_STAB_BACK", "@MENUS_STAB_BACK_DESC", "BOTH_A2_STABBACK1", MDS_SABER, +"@MENUS_LUNGE_ATTACK", "@MENUS_LUNGE_ATTACK_DESC", "BOTH_LUNGE2_B__T_", MDS_SABER, +"@MENUS_FORCE_PULL_IMPALE", "@MENUS_FORCE_PULL_IMPALE_DESC", "BOTH_PULL_IMPALE_STAB", MDS_SABER, +"@MENUS_FAST_ATTACK_KATA", "@MENUS_FAST_ATTACK_KATA_DESC", "BOTH_A1_SPECIAL", MDS_SABER, +"@MENUS_ATTACK_ENEMYONGROUND", "@MENUS_ATTACK_ENEMYONGROUND_DESC", "BOTH_STABDOWN", MDS_FORCE_JUMP, +"@MENUS_CARTWHEEL", "@MENUS_CARTWHEEL_DESC", "BOTH_ARIAL_RIGHT", MDS_FORCE_JUMP, +"@MENUS_BOTH_ROLL_STAB", "@MENUS_BOTH_ROLL_STAB2_DESC", "BOTH_ROLL_STAB", MDS_SABER, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, + +//Single Saber, Medium Style +"@MENUS_SLASH_BACK", "@MENUS_SLASH_BACK_DESC", "BOTH_ATTACK_BACK", MDS_SABER, +"@MENUS_FLIP_ATTACK", "@MENUS_FLIP_ATTACK_DESC", "BOTH_JUMPFLIPSLASHDOWN1", MDS_FORCE_JUMP, +"@MENUS_FORCE_PULL_SLASH", "@MENUS_FORCE_PULL_SLASH_DESC", "BOTH_PULL_IMPALE_SWING", MDS_SABER, +"@MENUS_MEDIUM_ATTACK_KATA", "@MENUS_MEDIUM_ATTACK_KATA_DESC", "BOTH_A2_SPECIAL", MDS_SABER, +"@MENUS_ATTACK_ENEMYONGROUND", "@MENUS_ATTACK_ENEMYONGROUND_DESC", "BOTH_STABDOWN", MDS_FORCE_JUMP, +"@MENUS_CARTWHEEL", "@MENUS_CARTWHEEL_DESC", "BOTH_ARIAL_RIGHT", MDS_FORCE_JUMP, +"@MENUS_BOTH_ROLL_STAB", "@MENUS_BOTH_ROLL_STAB2_DESC", "BOTH_ROLL_STAB", MDS_SABER, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, + +//Single Saber, Strong Style +"@MENUS_SLASH_BACK", "@MENUS_SLASH_BACK_DESC", "BOTH_ATTACK_BACK", MDS_SABER, +"@MENUS_JUMP_ATTACK", "@MENUS_JUMP_ATTACK_DESC", "BOTH_FORCELEAP2_T__B_", MDS_FORCE_JUMP, +"@MENUS_FORCE_PULL_SLASH", "@MENUS_FORCE_PULL_SLASH_DESC", "BOTH_PULL_IMPALE_SWING", MDS_SABER, +"@MENUS_STRONG_ATTACK_KATA", "@MENUS_STRONG_ATTACK_KATA_DESC", "BOTH_A3_SPECIAL", MDS_SABER, +"@MENUS_ATTACK_ENEMYONGROUND", "@MENUS_ATTACK_ENEMYONGROUND_DESC", "BOTH_STABDOWN", MDS_FORCE_JUMP, +"@MENUS_CARTWHEEL", "@MENUS_CARTWHEEL_DESC", "BOTH_ARIAL_RIGHT", MDS_FORCE_JUMP, +"@MENUS_BOTH_ROLL_STAB", "@MENUS_BOTH_ROLL_STAB2_DESC", "BOTH_ROLL_STAB", MDS_SABER, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, + +//Dual Sabers +"@MENUS_SLASH_BACK", "@MENUS_SLASH_BACK_DESC", "BOTH_ATTACK_BACK", MDS_SABER, +"@MENUS_FLIP_FORWARD_ATTACK", "@MENUS_FLIP_FORWARD_ATTACK_DESC", "BOTH_JUMPATTACK6", MDS_FORCE_JUMP, +"@MENUS_DUAL_SABERS_TWIRL", "@MENUS_DUAL_SABERS_TWIRL_DESC", "BOTH_SPINATTACK6", MDS_SABER, +"@MENUS_ATTACK_ENEMYONGROUND", "@MENUS_ATTACK_ENEMYONGROUND_DESC", "BOTH_STABDOWN_DUAL", MDS_FORCE_JUMP, +"@MENUS_DUAL_SABER_BARRIER", "@MENUS_DUAL_SABER_BARRIER_DESC", "BOTH_A6_SABERPROTECT", MDS_SABER, +"@MENUS_DUAL_STAB_FRONT_BACK", "@MENUS_DUAL_STAB_FRONT_BACK_DESC", "BOTH_A6_FB", MDS_SABER, +"@MENUS_DUAL_STAB_LEFT_RIGHT", "@MENUS_DUAL_STAB_LEFT_RIGHT_DESC", "BOTH_A6_LR", MDS_SABER, +"@MENUS_CARTWHEEL", "@MENUS_CARTWHEEL_DESC", "BOTH_ARIAL_RIGHT", MDS_FORCE_JUMP, +"@MENUS_BOTH_ROLL_STAB", "@MENUS_BOTH_ROLL_STAB_DESC", "BOTH_ROLL_STAB", MDS_SABER, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, + +// Saber Staff +"@MENUS_STAB_BACK", "@MENUS_STAB_BACK_DESC", "BOTH_A2_STABBACK1", MDS_SABER, +"@MENUS_BACK_FLIP_ATTACK", "@MENUS_BACK_FLIP_ATTACK_DESC", "BOTH_JUMPATTACK7", MDS_FORCE_JUMP, +"@MENUS_SABER_STAFF_TWIRL", "@MENUS_SABER_STAFF_TWIRL_DESC", "BOTH_SPINATTACK7", MDS_SABER, +"@MENUS_ATTACK_ENEMYONGROUND", "@MENUS_ATTACK_ENEMYONGROUND_DESC", "BOTH_STABDOWN_STAFF", MDS_FORCE_JUMP, +"@MENUS_SPINNING_KATA", "@MENUS_SPINNING_KATA_DESC", "BOTH_A7_SOULCAL", MDS_SABER, +"@MENUS_KICK1", "@MENUS_KICK1_DESC", "BOTH_A7_KICK_F", MDS_FORCE_JUMP, +"@MENUS_JUMP_KICK", "@MENUS_JUMP_KICK_DESC", "BOTH_A7_KICK_F_AIR", MDS_FORCE_JUMP, +"@MENUS_SPLIT_KICK", "@MENUS_SPLIT_KICK_DESC", "BOTH_A7_KICK_RL", MDS_FORCE_JUMP, +"@MENUS_SPIN_KICK", "@MENUS_SPIN_KICK_DESC", "BOTH_A7_KICK_S", MDS_FORCE_JUMP, +"@MENUS_FLIP_KICK", "@MENUS_FLIP_KICK_DESC", "BOTH_A7_KICK_BF", MDS_FORCE_JUMP, +"@MENUS_BUTTERFLY_ATTACK", "@MENUS_BUTTERFLY_ATTACK_DESC", "BOTH_BUTTERFLY_FR1", MDS_SABER, +"@MENUS_BOTH_ROLL_STAB", "@MENUS_BOTH_ROLL_STAB2_DESC", "BOTH_ROLL_STAB", MDS_SABER, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +}; + + +static int gamecodetoui[] = {4,2,3,0,5,1,6}; + +uiInfo_t uiInfo; + +static void UI_RegisterCvars( void ); +void UI_Load(void); + + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + + +vmCvar_t ui_menuFiles; +vmCvar_t ui_hudFiles; + +vmCvar_t ui_char_anim; +vmCvar_t ui_char_model; +vmCvar_t ui_char_skin_head; +vmCvar_t ui_char_skin_torso; +vmCvar_t ui_char_skin_legs; +vmCvar_t ui_saber_type; +vmCvar_t ui_saber; +vmCvar_t ui_saber2; +vmCvar_t ui_saber_color; +vmCvar_t ui_saber2_color; +vmCvar_t ui_char_color_red; +vmCvar_t ui_char_color_green; +vmCvar_t ui_char_color_blue; +vmCvar_t ui_PrecacheModels; + +//JLFCALLOUT MPMOVED +vmCvar_t ui_hideAcallout; +vmCvar_t ui_hideBcallout; +vmCvar_t ui_hideXcallout; + +//END JLFCALLOUT +vmCvar_t saveGameCount; +vmCvar_t overwriteAvailable; +vmCvar_t ui_newGameActive; +vmCvar_t noNewSaveGameAvailable; +vmCvar_t ui_BlocksAvailable; +vmCvar_t ui_BlocksNeeded; + +vmCvar_t ui_ShowDelete ; +vmCvar_t ui_cancelYScript ; + +//controller menu +vmCvar_t ControllerOutNum ; + + + +// Version of startup state machine function used when we came from MP XBE: +void XB_FastStartup( XBStartupState startupState ) +{ + if( startupState <= STARTUP_LOAD_SETTINGS ) + { + bool bSuccess = Settings.Load(); + if( !bSuccess ) + { + // Odd. If saving was disabled, then Load will appear to work. + UI_xboxErrorPopup( XB_POPUP_CORRUPT_SETTINGS ); + return; + } + } + + if( startupState <= STARTUP_FINISH ) + { + // Restore settings from stored (or default) settings: + Settings.SetAll(); + // Save them out, in case user just deleted (and is now restoring) them: + Settings.Save(); + + // mainMenu has already been opened + } +} + +/* + MASTER Startup function for saved games, invite checks, etc... + Modeled after XBL_Login +*/ +int blocksNeeded = 0; // 40/44 - Blocks free +extern bool Sys_QuickStart( void ); +void XB_Startup( XBStartupState startupState ) +{ + // If we came from MP - use the express version + if( Sys_QuickStart() && Cvar_Get("inSplashMenu", "0", 0)->integer == 0 ) + { + XB_FastStartup( startupState ); + return; + } + + if( startupState <= STARTUP_LOAD_SETTINGS ) + { + bool bSuccess = Settings.Load(); + if( !bSuccess ) + { + if( Settings.Corrupt() ) + { + UI_xboxErrorPopup( XB_POPUP_CORRUPT_SETTINGS ); + return; + } + + // Otherwise, file doesn't exist - continue to space checking below + } + else + { + // Skip checking space for settings + startupState = STARTUP_GAME_SPACE_CHECK; + } + } + + if( startupState <= STARTUP_COMBINED_SPACE_CHECK ) + { + // Is there enough room for both settings and a savegame? + if ( SG_BlocksLeft() < SG_SaveGameSize() + SETTINGS_NUM_BLOCKS ) + { + blocksNeeded = (SG_SaveGameSize() + SETTINGS_NUM_BLOCKS) - SG_BlocksLeft(); + UI_xboxErrorPopup( XB_POPUP_DISKFULL_BOTH ); + return; + } + + // OK. There's enough room for settings - make a file: + Settings.Save(); + } + + if( startupState <= STARTUP_GAME_SPACE_CHECK ) + { +#ifndef XBOX_DEMO // No space checks in demo + // Is there enough room for another savegame? + if( SG_BlocksLeft() < SG_SaveGameSize() ) + { + blocksNeeded = SG_SaveGameSize() - SG_BlocksLeft(); + UI_xboxErrorPopup( XB_POPUP_DISKFULL ); + return; + } +#endif + } + + if( startupState <= STARTUP_INVITE_CHECK ) + { + // Do we have a pending invitation? This can only return true ONCE! + extern bool Sys_InviteExists(); + if( Sys_InviteExists() ) + { + UI_xboxErrorPopup( XB_POPUP_CONFIRM_INVITE ); + return; + } + } + + if( startupState <= STARTUP_FINISH ) + { + // Restore settings from stored (or default) settings: + Settings.SetAll(); + + // All done! Open the menu! + Menus_CloseAll(); + Menus_ActivateByName( Cvar_VariableString( "returnMenu" ) ); + } +} + + + + + +static cvarTable_t cvarTable[] = +{ + { &ui_menuFiles, "ui_menuFiles", "ui/menus.txt", CVAR_ARCHIVE }, + { &ui_hudFiles, "cg_hudFiles", "ui/jahud.txt",CVAR_ARCHIVE}, + + { &ui_char_anim, "ui_char_anim", "BOTH_WALK1",0}, + + { &ui_char_model, "ui_char_model", "",0}, //these are filled in by the "g_*" versions on load + { &ui_char_skin_head, "ui_char_skin_head", "",0}, //the "g_*" versions are initialized in UI_Init, ui_atoms.cpp + { &ui_char_skin_torso, "ui_char_skin_torso", "",0}, + { &ui_char_skin_legs, "ui_char_skin_legs", "",0}, + + { &ui_saber_type, "ui_saber_type", "",0}, + { &ui_saber, "ui_saber", "",0}, + { &ui_saber2, "ui_saber2", "",0}, + { &ui_saber_color, "ui_saber_color", "",0}, + { &ui_saber2_color, "ui_saber2_color", "",0}, + + { &ui_char_color_red, "ui_char_color_red", "", 0}, + { &ui_char_color_green, "ui_char_color_green", "", 0}, + { &ui_char_color_blue, "ui_char_color_blue", "", 0}, + + { &ui_PrecacheModels, "ui_PrecacheModels", "1", CVAR_ARCHIVE}, +//JLFCALLOUT MPMOVED + { &ui_hideAcallout, "ui_hideAcallout", "", 0}, + { &ui_hideBcallout, "ui_hideBcallout", "", 0}, + { &ui_hideXcallout, "ui_hideXcallout", "", 0}, + +//END JLFCALLOUT + { &saveGameCount, "saveGameCount", "", 0}, + { &overwriteAvailable, "overwriteAvailable", "0", 0}, + { &ui_newGameActive, "ui_newGameActive", "", 0}, + { &noNewSaveGameAvailable, "noNewSaveGameAvailable", "", 0}, + { &ui_BlocksAvailable, "ui_BlocksAvailable", "0", 0}, + { &ui_BlocksNeeded, "ui_BlocksNeeded", "0", 0}, + { &ui_ShowDelete, "ui_ShowDelete", "0", 0}, + { &ui_cancelYScript, "ui_cancelYScript", "0", 0}, + { &ControllerOutNum, "ControllerOutNum", "-1", 0}, + + + + + +}; + + +#define FP_UPDATED_NONE -1 +#define NOWEAPON -1 + +static int cvarTableSize = sizeof(cvarTable) / sizeof(cvarTable[0]); + +void Text_Paint(float x, float y, float scale, vec4_t color, const char *text, int iMaxPixelWidth, int style, int iFontIndex); +int Key_GetCatcher( void ); + +#define UI_FPS_FRAMES 4 +void _UI_Refresh( int realtime ) +{ + static int index; + static int previousTimes[UI_FPS_FRAMES]; + + if ( !( Key_GetCatcher() & KEYCATCH_UI ) ) + { + return; + } + + extern void SE_CheckForLanguageUpdates(void); + SE_CheckForLanguageUpdates(); + + if ( Menus_AnyFullScreenVisible() ) + {//if not in full screen, don't mess with ghoul2 + //rww - ghoul2 needs to know what time it is even if the client/server are not running + //FIXME: this screws up the game when you go back to the game... + G2API_SetTime(realtime, 0); + G2API_SetTime(realtime, 1); + } + + uiInfo.uiDC.frameTime = realtime - uiInfo.uiDC.realTime; + uiInfo.uiDC.realTime = realtime; + + previousTimes[index % UI_FPS_FRAMES] = uiInfo.uiDC.frameTime; + index++; + if ( index > UI_FPS_FRAMES ) + { + int i, total; + // average multiple frames together to smooth changes out a bit + total = 0; + for ( i = 0 ; i < UI_FPS_FRAMES ; i++ ) + { + total += previousTimes[i]; + } + if ( !total ) + { + total = 1; + } + uiInfo.uiDC.FPS = 1000 * UI_FPS_FRAMES / total; + } + + + + UI_UpdateCvars(); + + if (Menu_Count() > 0) + { + // paint all the menus + Menu_PaintAll(); + // refresh server browser list +// UI_DoServerRefresh(); + // refresh server status +// UI_BuildServerStatus(qfalse); + // refresh find player list +// UI_BuildFindPlayerList(qfalse); + } + +#ifdef _XBOX + // display current map name + if (Cvar_VariableIntegerValue( "cl_maphack" )) + { + float rgba[4] = { 1.0f, 1.0f, 0.0f, 1.0f }; + extern cvar_t *cl_mapname; + Text_Paint(130, 100, /* UI_FONT_DEFAULT, */ 0.9f, rgba, cl_mapname->string, 0, ITEM_TEXTSTYLE_NORMAL, 3); + } +#endif + +#ifndef _XBOX + // draw cursor + UI_SetColor( NULL ); + if (Menu_Count() > 0) + { + if (uiInfo.uiDC.cursorShow == qtrue) + { + UI_DrawHandlePic( uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory, 48, 48, uiInfo.uiDC.Assets.cursor); + } + } +#endif +} + +#ifdef _XBOX +static void UI_SetVis(menuDef_t* menu, const char* name, bool activeFlag) +{ + itemDef_t* item = Menu_FindItemByName(menu, name); + if (item) + { + // Make it active + if (activeFlag) + { + item->window.flags |= WINDOW_VISIBLE; + } + else + { + item->window.flags &= ~WINDOW_VISIBLE; + } + } +} +#endif + +/* +=============== +UI_LoadMods +=============== +*/ +static void UI_LoadMods() { + int numdirs; + char dirlist[2048]; + char *dirptr; + char *descptr; + int i; + int dirlen; + + uiInfo.modCount = 0; + numdirs = FS_GetFileList( "$modlist", "", dirlist, sizeof(dirlist) ); + dirptr = dirlist; + for( i = 0; i < numdirs; i++ ) { + dirlen = strlen( dirptr ) + 1; + descptr = dirptr + dirlen; + uiInfo.modList[uiInfo.modCount].modName = String_Alloc(dirptr); + uiInfo.modList[uiInfo.modCount].modDescr = String_Alloc(descptr); + dirptr += dirlen + strlen(descptr) + 1; + uiInfo.modCount++; + if (uiInfo.modCount >= MAX_MODS) { + break; + } + } + +} + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .qvm file +================ +*/ +int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) +{ + return 0; +} + + + +/* +================ +Text_PaintChar +================ +*/ +/* +static void Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) +{ + float w, h; + + w = width * scale; + h = height * scale; + ui.R_DrawStretchPic((int)x, (int)y, w, h, s, t, s2, t2, hShader ); //make the coords (int) or else the chars bleed +} +*/ + +/* +================ +Text_Paint +================ +*/ +// iMaxPixelWidth is 0 here for no limit (but gets converted to -1), else max printable pixel width relative to start pos +// +void Text_Paint(float x, float y, float scale, vec4_t color, const char *text, int iMaxPixelWidth, int style, int iFontIndex) +{ + if (iFontIndex == 0) + { + iFontIndex = uiInfo.uiDC.Assets.qhMediumFont; + } + // kludge.. convert JK2 menu styles to SOF2 printstring ctrl codes... + // + int iStyleOR = 0; + switch (style) + { +// case ITEM_TEXTSTYLE_NORMAL: iStyleOR = 0;break; // JK2 normal text +// case ITEM_TEXTSTYLE_BLINK: iStyleOR = STYLE_BLINK;break; // JK2 fast blinking + case ITEM_TEXTSTYLE_PULSE: iStyleOR = STYLE_BLINK;break; // JK2 slow pulsing + case ITEM_TEXTSTYLE_SHADOWED: iStyleOR = STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + case ITEM_TEXTSTYLE_OUTLINED: iStyleOR = STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + case ITEM_TEXTSTYLE_OUTLINESHADOWED: iStyleOR = STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + case ITEM_TEXTSTYLE_SHADOWEDMORE: iStyleOR = STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + } + + ui.R_Font_DrawString( x, // int ox + y, // int oy + text, // const char *text + color, // paletteRGBA_c c + iStyleOR | iFontIndex, // const int iFontHandle + !iMaxPixelWidth?-1:iMaxPixelWidth, // iMaxPixelWidth (-1 = none) + scale // const float scale = 1.0f + ); +} + + +/* +================ +Text_PaintWithCursor +================ +*/ +// iMaxPixelWidth is 0 here for no-limit +void Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int iMaxPixelWidth, int style, int iFontIndex) +{ + Text_Paint(x, y, scale, color, text, iMaxPixelWidth, style, iFontIndex); + + // now print the cursor as well... + // + char sTemp[1024]; + int iCopyCount = min(strlen(text), cursorPos); + iCopyCount = min(iCopyCount,sizeof(sTemp)); + + // copy text into temp buffer for pixel measure... + // + strncpy(sTemp,text,iCopyCount); + sTemp[iCopyCount] = '\0'; + + int iNextXpos = ui.R_Font_StrLenPixels(sTemp, iFontIndex, scale ); + + Text_Paint(x+iNextXpos, y, scale, color, va("%c",cursor), iMaxPixelWidth, style|ITEM_TEXTSTYLE_BLINK, iFontIndex); +} + + +const char *UI_FeederItemText(float feederID, int index, int column, qhandle_t *handle) +{ + *handle = -1; + + if (feederID == FEEDER_SAVEGAMES) + { + if (column==0) + { + return s_savedata[index].currentSaveFileName;//currentSaveFileComments; + } + else + { + return s_savedata[index].currentSaveFileDateTimeString; + } + } +//JLF MPMOVED +#ifdef _XBOX + else if (feederID == FEEDER_PROFILES) + { + if (column == 0) + { + return s_ProfileData[index].currentProfileName; + } + } +#endif + else if (feederID == FEEDER_MOVES) + { + return datapadMoveData[uiInfo.movesTitleIndex][index].title; + } + else if (feederID == FEEDER_MOVES_TITLES) + { + return datapadMoveTitleData[index]; + } + else if (feederID == FEEDER_PLAYER_SPECIES) + { + return uiInfo.playerSpecies[index].Name; + } + else if (feederID == FEEDER_LANGUAGES) + { + assert( 0 ); + return NULL; +// return SE_GetLanguageName( index ); + } + else if (feederID == FEEDER_PLAYER_SKIN_HEAD) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadCount) + { + *handle = ui.R_RegisterShaderNoMip(va("models/players/%s/icon_%s.jpg", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].Name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadNames[index])); + return uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadNames[index]; + } + } + else if (feederID == FEEDER_PLAYER_SKIN_TORSO) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinTorsoCount) + { + *handle = ui.R_RegisterShaderNoMip(va("models/players/%s/icon_%s.jpg", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].Name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinTorsoNames[index])); + return uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinTorsoNames[index]; + } + } + else if (feederID == FEEDER_PLAYER_SKIN_LEGS) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegCount) + { + *handle = ui.R_RegisterShaderNoMip(va("models/players/%s/icon_%s.jpg", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].Name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegNames[index])); + return uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegNames[index]; + } + } + else if (feederID == FEEDER_COLORCHOICES) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorCount) + { + *handle = ui.R_RegisterShaderNoMip( uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorShader[index]); + return uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorShader[index]; + } + } + else if (feederID == FEEDER_MODS) + { + if (index >= 0 && index < uiInfo.modCount) + { + if (uiInfo.modList[index].modDescr && *uiInfo.modList[index].modDescr) + { + return uiInfo.modList[index].modDescr; + } + else + { + return uiInfo.modList[index].modName; + } + } + } + else if (feederID == FEEDER_LEVELSELECT) + { + if (index >= 0 && index < levelSelectSize) + return levelSelectData[index].displayName; + } + + return ""; +} + +qhandle_t UI_FeederItemImage(float feederID, int index) +{ + if (feederID == FEEDER_PLAYER_SKIN_HEAD) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadCount) + { + //return uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadIcons[index]; + return ui.R_RegisterShaderNoMip(va("models/players/%s/icon_%s.jpg", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].Name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadNames[index])); + } + } + else if (feederID == FEEDER_PLAYER_SKIN_TORSO) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinTorsoCount) + { + //return uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinTorsoIcons[index]; + return ui.R_RegisterShaderNoMip(va("models/players/%s/icon_%s.jpg", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].Name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinTorsoNames[index])); + } + } + else if (feederID == FEEDER_PLAYER_SKIN_LEGS) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegCount) + { + //return uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegIcons[index]; + return ui.R_RegisterShaderNoMip(va("models/players/%s/icon_%s.jpg", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].Name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegNames[index])); + } + } + else if (feederID == FEEDER_COLORCHOICES) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorCount) + { + return ui.R_RegisterShaderNoMip( uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorShader[index]); + } + } +/* else if (feederID == FEEDER_ALLMAPS || feederID == FEEDER_MAPS) + { + int actual; + UI_SelectedMap(index, &actual); + index = actual; + if (index >= 0 && index < uiInfo.mapCount) + { + if (uiInfo.mapList[index].levelShot == -1) + { + uiInfo.mapList[index].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[index].imageName); + } + return uiInfo.mapList[index].levelShot; + } + } +*/ + return 0; +} + + +void setArrowX(itemDef_t * arrowcontrol, int xloc) +{ + + arrowcontrol->window.rect.x = xloc; + +} + +/* +================= +CreateNextSaveName +================= +*/ +static int CreateNextSaveName(char *fileName) +{ + int i; + + // Loop through all the save games and look for the first open name + for (i=0;i0) && ((s_savegame.currentLine+1) == s_savegame.saveFileCnt) ) + { + s_savegame.currentLine--; + // yeah this is a pretty bad hack + // adjust cursor position of listbox so correct item is highlighted + UI_AdjustSaveGameListBox( s_savegame.currentLine ); + } + +// ReadSaveDirectory(); //refresh + s_savegame.saveFileCnt = -1; //force a refresh at drawtime + + } + +} +void ui_SaveGame() +{ + char fileName[MAX_SAVELOADNAME]; + char description[64]; + // Create a new save game +// if ( !s_savedata[s_savegame.currentLine].currentSaveFileName) // No line was chosen + { +//JLF MPNOTUSED +#ifdef _XBOX + strcpy(fileName, "JKSG3"); +#else + CreateNextSaveName(fileName); + +#endif + } +// else // Overwrite a current save game? Ask first. + { +// s_savegame.yes.generic.flags = QMF_HIGHLIGHT_IF_FOCUS; +// s_savegame.no.generic.flags = QMF_HIGHLIGHT_IF_FOCUS; + +// strcpy(fileName,s_savedata[s_savegame.currentLine].currentSaveFileName); +// s_savegame.awaitingSave = qtrue; +// s_savegame.deletegame.generic.flags = QMF_GRAYED; // Turn off delete button +// break; + } + + // Save description line + ui.Cvar_VariableStringBuffer("ui_gameDesc",description,sizeof(description)); + ui.SG_StoreSaveGameComment(description); + + ui.Cmd_ExecuteText( EXEC_APPEND, va("save %s\n", fileName)); + // s_savegame.saveFileCnt = -1; //force a refresh the next time around +} + + +void openDashBoardMemory() +{ +// TCR C4-3 Cleanup Support + // Launch to the Dash memory area to clean up + // Reboot to Dash + LAUNCH_DATA ld; + memset(&ld, 0, sizeof(ld)); + PLD_LAUNCH_DASHBOARD pDash = (PLD_LAUNCH_DASHBOARD) &ld; + pDash->dwReason = XLD_LAUNCH_DASHBOARD_MEMORY; + char * path = NULL; + pDash->dwContext = 0; + pDash->dwParameter1 = 'U'; + pDash->dwParameter2 = SG_SaveGameSize() + SETTINGS_NUM_BLOCKS; + + S_Shutdown(); + // Similarly, kill off the streaming thread + extern void Sys_StreamShutdown(void); + Sys_StreamShutdown(); + + XLaunchNewImage(path, &ld); + + +// LD_LAUNCH_DASHBOARD LaunchDash; +// LaunchDash.dwReason = XLD_LAUNCH_DASHBOARD_MEMORY; +// LaunchDash.dwContext = 0; +// LaunchDash.dwParameter1 = DWORD( 'U'); +// LaunchDash.dwParameter2 = SG_SaveGameSize(); + +// XLaunchNewImage( NULL, (PLAUNCH_DATA)(&LaunchDash) ); + + // We never get here +} + +#ifdef XBOX_DEMO + +// Bleh. +int demoForcePowerLevel[NUM_FORCE_POWERS]; + +void UI_DemoSetForceLevels( void ) +{ + if( Cvar_VariableIntegerValue( "t1_mission" ) == 0 ) // Sour + {// NOTE : always set the uiInfo powers + // level 1 in all core powers + demoForcePowerLevel[FP_LEVITATION]=1; + demoForcePowerLevel[FP_SPEED]=1; + demoForcePowerLevel[FP_PUSH]=1; + demoForcePowerLevel[FP_PULL]=1; + demoForcePowerLevel[FP_SEE]=1; + demoForcePowerLevel[FP_SABER_OFFENSE]=1; + demoForcePowerLevel[FP_SABER_DEFENSE]=1; + demoForcePowerLevel[FP_SABERTHROW]=1; + // plus these extras + demoForcePowerLevel[FP_HEAL]=1; + demoForcePowerLevel[FP_TELEPATHY]=1; + demoForcePowerLevel[FP_GRIP]=1; + + // and set the rest to zero + demoForcePowerLevel[FP_ABSORB]=0; + demoForcePowerLevel[FP_PROTECT]=0; + demoForcePowerLevel[FP_DRAIN]=0; + demoForcePowerLevel[FP_LIGHTNING]=0; + demoForcePowerLevel[FP_RAGE]=0; + } + else // Rift + { + // level 3 in all core powers + demoForcePowerLevel[FP_LEVITATION]=3; + demoForcePowerLevel[FP_SPEED]=3; + demoForcePowerLevel[FP_PUSH]=3; + demoForcePowerLevel[FP_PULL]=3; + demoForcePowerLevel[FP_SEE]=3; + demoForcePowerLevel[FP_SABER_OFFENSE]=3; + demoForcePowerLevel[FP_SABER_DEFENSE]=3; + demoForcePowerLevel[FP_SABERTHROW]=3; + + // plus these extras + demoForcePowerLevel[FP_HEAL]=1; + demoForcePowerLevel[FP_TELEPATHY]=1; + demoForcePowerLevel[FP_GRIP]=2; + demoForcePowerLevel[FP_LIGHTNING]=1; + demoForcePowerLevel[FP_PROTECT]=1; + + // and set the rest to zero + + demoForcePowerLevel[FP_ABSORB]=0; + demoForcePowerLevel[FP_DRAIN]=0; + demoForcePowerLevel[FP_RAGE]=0; + } +} + +#endif + +/* +=============== +UI_RunMenuScript +=============== +*/ +static qboolean UI_RunMenuScript ( const char **args ) +{ + const char *name, *name2,*mapName,*menuName,*warningMenuName; + + if (String_Parse(args, &name)) + { + if (Q_stricmp(name, "resetdefaults") == 0) + { + UI_ResetDefaults(); + } + else if (Q_stricmp(name, "saveControls") == 0) + { + Controls_SetConfig(qtrue); + } + else if (Q_stricmp(name, "loadControls") == 0) + { + Controls_GetConfig(); + } + else if (Q_stricmp(name, "clearError") == 0) + { + Cvar_Set("com_errorMessage", ""); + } + //possibly make a separate readsavedirectory call that does it immediately + else if (Q_stricmp(name, "ReadSaveDirectory") == 0) + { + s_savegame.saveFileCnt = -1; //force a refresh at drawtime + } + else if (Q_stricmp(name, "loadAuto") == 0) + { +// Menus_CloseAll(); + UI_xboxErrorPopup( XB_POPUP_TESTING_SAVE ); + ui.Cmd_ExecuteText( EXEC_APPEND, "wait ; wait ; wait ; wait ; load auto\n"); //load game menu + } + else if (Q_stricmp(name, "loadgame") == 0) + { + if (s_savedata[s_savegame.currentLine].currentSaveFileName) + { +// Menus_CloseAll(); + UI_xboxErrorPopup( XB_POPUP_TESTING_SAVE ); + // the 'load' call is broken for levelnames that have spaces + // the global variable 'g_loadGameName' will carry the level name through + ui.Cmd_ExecuteText( EXEC_APPEND, va("wait ; wait ; wait ; wait ; load level\n")); + strcpy(g_loadsaveGameName,s_savedata[s_savegame.currentLine].currentSaveFileName); + g_loadsaveGameNameInitialized = qtrue; + } + } + else if (Q_stricmp(name, "deletegame") == 0) + { + ui_DeleteGame(); + } + else if (Q_stricmp(name, "savegame") == 0) + { + ui_SaveGame(); + } + else if (Q_stricmp(name, "checkforoverwrite") == 0) + { + if(s_savegame.saveFileCnt == MAX_SAVELOADFILES && !s_savegame.currentLine) // check to see if there are enough slots + { + // No free slots + Cvar_Set( "ui_overwriting", "3"); + } + else + { + char fileName[MAX_SAVELOADNAME]; + char description[64]; + if (svs.clients[0].frames[svs.clients[0].netchan.outgoingSequence & PACKET_MASK].ps.stats[STAT_HEALTH] <= 0) + { + Cvar_Set( "ui_overwriting", "666"); + } + else if ( s_savegame.currentLine || (1== Cvar_VariableIntegerValue("noNewSaveGameAvailable")&& s_savegame.saveFileCnt>0)) + { + // On an existing savegame + Cvar_Set( "ui_overwriting", "1"); + } + else if ( SG_BlocksLeft() < SG_SaveGameSize()) + { + // Insufficient space + blocksNeeded = SG_SaveGameSize() - SG_BlocksLeft(); + Cvar_Set( "ui_overwriting", "2"); + } + else + { + // Everything ok + Cvar_Set( "ui_overwriting", "0"); + } + } + } + else if (Q_stricmp(name, "loadgameselect") == 0) + { + if ( trap_Cvar_VariableValue("cl_paused")>0 ) + { //popup the confirmation + UI_xboxErrorPopup( XB_POPUP_LOAD_CONFIRM ); + } + else + { + itemDef_t item; + item.parent = Menu_GetFocused(); + item.window.flags = 0; + Item_RunScript(&item, "uiScript loadgame"); + } + } + + + + + + else if (Q_stricmp(name, "LoadMods") == 0) + { + UI_LoadMods(); + } + else if (Q_stricmp(name, "RunMod") == 0) + { + if (uiInfo.modList[uiInfo.modIndex].modName) + { + Cvar_Set( "fs_game", uiInfo.modList[uiInfo.modIndex].modName); + extern void FS_Restart( void ); + FS_Restart(); + Cbuf_ExecuteText( EXEC_APPEND, "vid_restart;" ); + } + } + else if (Q_stricmp(name, "Quit") == 0) + { + Cbuf_ExecuteText( EXEC_NOW, "quit"); + } + else if (Q_stricmp(name, "Controls") == 0) + { + Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_ActivateByName("setup_menu2"); + } + else if (Q_stricmp(name, "Leave") == 0) + { + Cbuf_ExecuteText( EXEC_APPEND, "disconnect\n" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + //Menus_ActivateByName("mainMenu"); + } + else if (Q_stricmp(name, "getvideosetup") == 0) + { + UI_GetVideoSetup ( ); + } + else if (Q_stricmp(name, "updatevideosetup") == 0) + { + UI_UpdateVideoSetup ( ); + } + else if (Q_stricmp(name, "nextDataPadForcePower") == 0) + { + ui.Cmd_ExecuteText( EXEC_NOW, "dpforcenext\n"); + extern void CG_SetDataPadForceText( void ); + CG_SetDataPadForceText(); + } + else if (Q_stricmp(name, "prevDataPadForcePower") == 0) + { + ui.Cmd_ExecuteText( EXEC_NOW, "dpforceprev\n"); + extern void CG_SetDataPadForceText( void ); + CG_SetDataPadForceText(); + } + else if (Q_stricmp(name, "nextDataPadWeapon") == 0) + { + ui.Cmd_ExecuteText( EXEC_NOW, "dpweapnext\n"); + extern void CG_SetDataPadWeaponText( void ); + CG_SetDataPadWeaponText(); + } + else if (Q_stricmp(name, "prevDataPadWeapon") == 0) + { + ui.Cmd_ExecuteText( EXEC_NOW, "dpweapprev\n"); + extern void CG_SetDataPadWeaponText( void ); + CG_SetDataPadWeaponText(); + } + else if (Q_stricmp(name, "nextDataPadInventory") == 0) + { + ui.Cmd_ExecuteText( EXEC_APPEND, "dpinvnext\n"); + } + else if (Q_stricmp(name, "prevDataPadInventory") == 0) + { + ui.Cmd_ExecuteText( EXEC_APPEND, "dpinvprev\n"); + } + else if (Q_stricmp(name, "checkvid1data") == 0) // Warn user data has changed before leaving screen? + { + String_Parse(args, &menuName); + + String_Parse(args, &warningMenuName); + + UI_CheckVid1Data(menuName,warningMenuName); + } + else if (Q_stricmp(name, "startgame") == 0) + { + Menus_CloseAll(); + if ( Cvar_VariableIntegerValue("com_demo") ) + { + ui.Cmd_ExecuteText( EXEC_APPEND, "map demo\n"); + } + else + { + ui.Cmd_ExecuteText( EXEC_APPEND, "map yavin1\n"); + } + } + else if (Q_stricmp(name, "startmap") == 0) + { + Menus_CloseAll(); + + String_Parse(args, &mapName); + + ui.Cmd_ExecuteText( EXEC_APPEND, va("maptransition %s\n",mapName)); + } + else if (Q_stricmp(name, "closeingame") == 0) + { + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + + if (1 == Cvar_VariableIntegerValue("ui_missionfailed")) + { + Menus_ActivateByName("missionfailed_menu"); + ui.Key_SetCatcher( KEYCATCH_UI ); + } + else + { + Menus_ActivateByName("mainhud"); + } + } + else if (Q_stricmp(name, "closedatapad") == 0) + { + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + Menus_ActivateByName("mainhud"); + + Cvar_Set( "cg_updatedDataPadForcePower1", "0" ); + Cvar_Set( "cg_updatedDataPadForcePower2", "0" ); + Cvar_Set( "cg_updatedDataPadForcePower3", "0" ); + Cvar_Set( "cg_updatedDataPadObjective", "0" ); + } + else if (Q_stricmp(name, "closesabermenu") == 0) + { + // if we're in the saber menu when creating a character, close this down + if( !Cvar_VariableIntegerValue( "saber_menu" ) ) + { + Menus_CloseByName( "saberMenu" ); + Menus_OpenByName( "characterMenu" ); + } + } + else if (Q_stricmp(name, "clearmouseover") == 0) + { + itemDef_t *item; + menuDef_t *menu = Menu_GetFocused(); + + if (menu) + { + const char *itemName; + String_Parse(args, &itemName); + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, itemName); + if (item) + { + item->window.flags &= ~WINDOW_MOUSEOVER; + } + } + } + else if (Q_stricmp(name, "setMovesListDefault") == 0) + { + uiInfo.movesTitleIndex = 2; + } + else if (Q_stricmp(name, "resetMovesDesc") == 0) + { + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item; + + if (menu) + { + item = (itemDef_s *) Menu_FindItemByName(menu, "item_desc"); + if (item) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if( listPtr ) + { + listPtr->cursorPos = 0; + listPtr->startPos = 0; + } + item->cursorPos = 0; + } + } + + } + else if (Q_stricmp(name, "resetMovesList") == 0) + { + menuDef_t *menu; + menu = Menus_FindByName("datapadMovesMenu"); + //update saber models + if (menu) + { + itemDef_t *item; + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, "character"); + if (item) + { + UI_SaberAttachToChar( item ); + } + } + + Cvar_Set( "ui_move_desc", " " ); + } +// else if (Q_stricmp(name, "setanisotropicmax") == 0) +// { +// r_ext_texture_filter_anisotropic->value; +// } + else if (Q_stricmp(name, "setMoveCharacter") == 0) + { + itemDef_t *item; + menuDef_t *menu; + modelDef_t *modelPtr; + char skin[MAX_QPATH]; + + UI_GetCharacterCvars(); + UI_GetSaberCvars(); + + uiInfo.movesTitleIndex = 0; + + menu = Menus_FindByName("datapadMovesMenu"); + + if (menu) + { + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, "character"); + if (item) + { + modelPtr = (modelDef_t*)item->typeData; + if (modelPtr) + { + uiInfo.movesBaseAnim = datapadMoveTitleBaseAnims[uiInfo.movesTitleIndex]; + ItemParse_model_g2anim_go( item, uiInfo.movesBaseAnim ); + + uiInfo.moveAnimTime = 0 ; + DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + Com_sprintf( skin, sizeof( skin ), "models/players/%s/|%s|%s|%s", + Cvar_VariableString ( "g_char_model"), + Cvar_VariableString ( "g_char_skin_head"), + Cvar_VariableString ( "g_char_skin_torso"), + Cvar_VariableString ( "g_char_skin_legs") + ); + + ItemParse_model_g2skin_go( item, skin ); + UI_SaberAttachToChar( item ); + } + } + } + } + else if (Q_stricmp(name, "glCustom") == 0) + { + Cvar_Set("ui_r_glCustom", "4"); + } + else if (Q_stricmp(name, "character") == 0) + { + UI_UpdateCharacter( qfalse ); + } + else if (Q_stricmp(name, "characterchanged") == 0) + { + UI_UpdateCharacter( qtrue ); + } + else if (Q_stricmp(name, "char_skin") == 0) + { + UI_UpdateCharacterSkin(); + } + else if (Q_stricmp(name, "saber_type") == 0) + { + UI_UpdateSaberType(); + } + else if (Q_stricmp(name, "saber_hilt") == 0) + { + UI_UpdateSaberHilt( qfalse ); + } + else if (Q_stricmp(name, "saber_color") == 0) + { +// UI_UpdateSaberColor( qfalse ); + } + else if (Q_stricmp(name, "saber2_hilt") == 0) + { + UI_UpdateSaberHilt( qtrue ); + } + else if (Q_stricmp(name, "saber2_color") == 0) + { +// UI_UpdateSaberColor( qtrue ); + } + else if (Q_stricmp(name, "updatecharcvars") == 0) + { + UI_UpdateCharacterCvars(); + } + else if (Q_stricmp(name, "getcharcvars") == 0) + { + UI_GetCharacterCvars(); + } + else if (Q_stricmp(name, "updatesabercvars") == 0) + { + UI_UpdateSaberCvars(); + } + else if (Q_stricmp(name, "getsabercvars") == 0) + { + UI_GetSaberCvars(); + } + else if (Q_stricmp(name, "resetsabercvardefaults") == 0) + { + // NOTE : ONLY do this if saber menu is set properly (ie. first time we enter this menu) + if( !Cvar_VariableIntegerValue( "saber_menu" ) ) + { + UI_ResetSaberCvars(); + } + } + else if( Q_stricmp(name, "fixforsabercheat") == 0) + { + if(strstr(Cvar_Get("g_saber2"," ", 0)->string, "single")) + { + extern bool Cheat_ChangeSaber( void ); + Cheat_ChangeSaber(); + Cheat_ChangeSaber(); + } + } + else if (Q_stricmp(name, "updatefightingstylechoices") == 0) + { + UI_UpdateFightingStyleChoices(); + } + else if (Q_stricmp(name, "initallocforcepower") == 0) + { + const char *forceName; + String_Parse(args, &forceName); + + UI_InitAllocForcePowers(forceName); + } + else if (Q_stricmp(name, "affectforcepowerlevel") == 0) + { + const char *forceName; + String_Parse(args, &forceName); + + UI_AffectForcePowerLevel(forceName); + } + else if (Q_stricmp(name, "decrementcurrentforcepower") == 0) + { + UI_DecrementCurrentForcePower(); + } + else if (Q_stricmp(name, "shutdownforcehelp") == 0) + { + UI_ShutdownForceHelp(); + } + else if (Q_stricmp(name, "forcehelpactive") == 0) + { + UI_ForceHelpActive(); + } + else if (Q_stricmp(name, "showforceleveldesc") == 0) + { + const char *forceName; + String_Parse(args, &forceName); + + UI_ShowForceLevelDesc(forceName); + } + else if (Q_stricmp(name, "resetforcelevels") == 0) + { + UI_ResetForceLevels(); + } + else if (Q_stricmp(name, "checkforforcecheat") == 0) + { + UI_CheckForForceCheat(); + } + else if (Q_stricmp(name, "weaponhelpactive") == 0) + { + UI_WeaponHelpActive(); + } + // initialize weapon selection screen + else if (Q_stricmp(name, "initweaponselect") == 0) + { + UI_InitWeaponSelect(); + } + else if(Q_stricmp(name, "setupForceSelect") == 0) + { + UI_SetUpForceSelect(); + } + else if (Q_stricmp(name, "clearweapons") == 0) + { + UI_ClearWeapons(); + } + else if (Q_stricmp(name, "stopgamesounds") == 0) + { + trap_S_StopSounds(); + } + else if (Q_stricmp(name, "loadmissionselectmenu") == 0) + { + const char *cvarName; + String_Parse(args, &cvarName); + + if (cvarName) + { + UI_LoadMissionSelectMenu(cvarName); + } + } + else if (Q_stricmp(name, "calcforcestatus") == 0) + { + UI_CalcForceStatus(); + } + else if (Q_stricmp(name, "giveweapon") == 0) + { + const char *weaponIndex; + String_Parse(args, &weaponIndex); + UI_GiveWeapon(atoi(weaponIndex)); + } + else if (Q_stricmp(name, "equipweapon") == 0) + { + const char *weaponIndex; + String_Parse(args, &weaponIndex); + UI_EquipWeapon(atoi(weaponIndex)); + } + else if (Q_stricmp(name, "addweaponselection") == 0) + { + const char *weaponIndex; + String_Parse(args, &weaponIndex); + if (!weaponIndex) + { + return qfalse; + } + + const char *ammoIndex; + String_Parse(args, &ammoIndex); + if (!ammoIndex) + { + return qfalse; + } + + const char *ammoAmount; + String_Parse(args, &ammoAmount); + if (!ammoAmount) + { + return qfalse; + } + + const char *itemName; + String_Parse(args, &itemName); + if (!itemName) + { + return qfalse; + } + + const char *litItemName; + String_Parse(args, &litItemName); + if (!litItemName) + { + return qfalse; + } + + const char *backgroundName; + String_Parse(args, &backgroundName); + if (!backgroundName) + { + return qfalse; + } + + const char *soundfile = NULL; + String_Parse(args, &soundfile); + + UI_AddWeaponSelection(atoi(weaponIndex),atoi(ammoIndex),atoi(ammoAmount),itemName,litItemName, backgroundName, soundfile); + } + else if (Q_stricmp(name, "addthrowweaponselection") == 0) + { + const char *weaponIndex; + String_Parse(args, &weaponIndex); + if (!weaponIndex) + { + return qfalse; + } + + const char *ammoIndex; + String_Parse(args, &ammoIndex); + if (!ammoIndex) + { + return qfalse; + } + + const char *ammoAmount; + String_Parse(args, &ammoAmount); + if (!ammoAmount) + { + return qfalse; + } + + const char *itemName; + String_Parse(args, &itemName); + if (!itemName) + { + return qfalse; + } + + const char *litItemName; + String_Parse(args, &litItemName); + if (!litItemName) + { + return qfalse; + } + + const char *backgroundName; + String_Parse(args, &backgroundName); + if (!backgroundName) + { + return qfalse; + } + + const char *soundfile; + String_Parse(args, &soundfile); + + UI_AddThrowWeaponSelection(atoi(weaponIndex),atoi(ammoIndex),atoi(ammoAmount),itemName,litItemName,backgroundName, soundfile); + } + else if (Q_stricmp(name, "removeweaponselection") == 0) + { + const char *weaponIndex; + String_Parse(args, &weaponIndex); + if (weaponIndex) + { + UI_RemoveWeaponSelection(atoi(weaponIndex)); + } + } + else if (Q_stricmp(name, "removethrowweaponselection") == 0) + { + UI_RemoveThrowWeaponSelection(); + } + else if (Q_stricmp(name, "normalthrowselection") == 0) + { + UI_NormalThrowSelection(); + } + else if (Q_stricmp(name, "highlightthrowselection") == 0) + { + UI_HighLightThrowSelection(); + } + else if (Q_stricmp(name, "normalweaponselection") == 0) + { + const char *slotIndex; + String_Parse(args, &slotIndex); + if (!slotIndex) + { + return qfalse; + } + + UI_NormalWeaponSelection(atoi(slotIndex)); + } + else if (Q_stricmp(name, "highlightweaponselection") == 0) + { + const char *slotIndex; + String_Parse(args, &slotIndex); + if (!slotIndex) + { + return qfalse; + } + + UI_HighLightWeaponSelection(atoi(slotIndex)); + } + else if (Q_stricmp(name, "clearinventory") == 0) + { + UI_ClearInventory(); + } + else if (Q_stricmp(name, "giveinventory") == 0) + { + const char *inventoryIndex,*amount; + String_Parse(args, &inventoryIndex); + String_Parse(args, &amount); + UI_GiveInventory(atoi(inventoryIndex),atoi(amount)); + } + else if (Q_stricmp(name, "updatefightingstyle") == 0) + { + UI_UpdateFightingStyle(); + } + else if (Q_stricmp(name, "update") == 0) + { + if (String_Parse(args, &name2)) + { + UI_Update(name2); + } + else + { + Com_Printf("update missing cmd\n"); + } + } + else if (Q_stricmp(name, "load_quick") == 0) + { + ui.Cmd_ExecuteText(EXEC_APPEND,"load quick\n"); + } + else if (Q_stricmp(name, "load_auto") == 0) + { + if ( trap_Cvar_VariableValue("cl_paused")>0 ) + { //popup the confirmation + UI_xboxErrorPopup( XB_POPUP_LOAD_CONFIRM_CHECKPOINT ); + } + else + { + ui.Cmd_ExecuteText(EXEC_APPEND,"load *respawn\n"); //death menu, might load a saved game instead if they just loaded on this map + } + } + else if (Q_stricmp(name, "load_auto_failed") == 0) + { + // Crazy case, we put up the Load Game screen here, then the popup over it. + Menus_CloseAll(); + + Cvar_Set( "returnmenu", "missionfailed_menu" ); + Cvar_Set( "cl_paused", "1" ); + Cvar_Set( "ui_missionfailed", "1" ); + Cvar_Set( "ui_frontEnd", "1" ); +#ifdef XBOX_DEMO + // For the demo (which should never show the load game screen) + // we show the pause menu instead: + Menus_ActivateByName( "ingameMainMenu" ); +#else + Menus_ActivateByName( "loadMenu" ); +#endif + UI_xboxErrorPopup( XB_POPUP_TESTING_SAVE ); + ui.Cmd_ExecuteText(EXEC_APPEND,"wait ; wait ; wait ; wait ; load *respawn\n"); //death menu, might load a saved game instead if they just loaded on this map + } + else if (Q_stricmp(name, "decrementforcepowerlevel") == 0) + { + UI_DecrementForcePowerLevel(); + } + else if (Q_stricmp(name, "resetcharacterlistboxes") == 0) + { + UI_ResetCharacterListBoxes(); + } +#ifdef _XBOX + else if (Q_stricmp(name, "multiplayer") == 0) + { + extern void Sys_Reboot( const char *reason, const void *pData ); + Sys_Reboot("multiplayer", NULL); + } +//JLF MPMOVED +#if 0 + else if (Q_stricmp(name, "loadprofile") == 0) + { + if (s_ProfileData[s_playerProfile.currentLine].currentProfileName)// && (*s_file_desc_field.field.buffer)) + { + Menus_CloseAll(); + ui.Cmd_ExecuteText( EXEC_APPEND, va("loadprofile %s\n", s_ProfileData[s_playerProfile.currentLine].currentProfileName)); + } + } + else if (Q_stricmp(name, "saveprofile") == 0) + { + //if (s_ProfileData[s_playerProfile.currentLine].currentProfileName)// && (*s_file_desc_field.field.buffer)) + { + Menus_CloseAll(); + // ui.Cmd_ExecuteText( EXEC_APPEND, va("saveprofile %s\n", "test"/*s_ProfileData[s_playerProfile.currentLine].currentProfileName*/)); + } + } + else if (Q_stricmp(name, "initprofile") == 0) + { + //if (s_ProfileData[s_playerProfile.currentLine].currentProfileName)// && (*s_file_desc_field.field.buffer)) + { + Menus_CloseAll(); + ui.Cmd_ExecuteText( EXEC_APPEND, va("initprofile \n" )); + } + } + else if (Q_stricmp(name, "deleteprofile") == 0) + { + //if (s_ProfileData[s_playerProfile.currentLine].currentProfileName)// && (*s_file_desc_field.field.buffer)) + { + Menus_CloseAll(); + ui.Cmd_ExecuteText( EXEC_APPEND, va("deleteprofile %s\n","test" )); + } + } + else if (Q_stricmp(name, "testandsaveprofile") == 0) + { + if (Cvar_VariableIntegerValue("ui_profileSaveNeeded"))// && (*s_file_desc_field.field.buffer)) + { + ui.Cmd_ExecuteText( EXEC_APPEND, va("saveprofile %s\n",s_ProfileData[s_playerProfile.currentLine].currentProfileName )); + Cvar_Set("ui_profileSaveNeeded","0"); + } + } +#endif +//JLF + else if (Q_stricmp(name, "processForDiskSpace") == 0) + { + // Kick off the crazy sequence! + XB_Startup( STARTUP_LOAD_SETTINGS ); + } + else if (Q_stricmp(name, "initListBoxes") == 0) + { + ui_ShowDeleteActive = qfalse; + + //find the listboxes for this level + menuDef_t * menu = Menu_GetFocused(); // Get current menu + + if (menu) + { + int i; + + for (i = 0; i < menu->itemCount; i++) + { + listBoxDef_t *listPtr; + //Item_ValidateTypeData(item); + + if (menu->items[i]->type == ITEM_TYPE_LISTBOX) + { + listPtr = (listBoxDef_t*)menu->items[i]->typeData; + if (listPtr) + { + listPtr->cursorPos = 0; + listPtr->startPos = 0; + menu->items[i]->cursorPos =0; + if (menu->items[i]->special== FEEDER_SAVEGAMES) + { + UI_FeederSelection( FEEDER_SAVEGAMES , 0, NULL ); + } + } + } + } + } + } + else if (Q_stricmp(name, "confirmdelete") == 0) + { + // User is already logged on - is trying to back out. Get confirmation + UI_xboxErrorPopup( XB_POPUP_DELETE_CONFIRM ); + } + else if (Q_stricmp(name, "xboxErrorResponse") == 0) + { + // User closed the Xbox Error Popup in some way. Do TheRightThing(TM) + UI_xboxPopupResponse(); + } + else if (Q_stricmp(name, "genericpopup") == 0) + { + const char *menuid; + String_Parse(args, &menuid); + if(Q_stricmp(menuid, "savecomplete") == 0) + { + UI_xboxErrorPopup( XB_POPUP_SAVE_COMPLETE ); + } + else if(Q_stricmp(menuid, "overwriteconfirm") == 0) + { + int confirmType = Cvar_VariableIntegerValue( "ui_overwriting" ); + + if( confirmType == 3 ) + { + UI_xboxErrorPopup( XB_POPUP_TOO_MANY_SAVES ); + } + else if( confirmType == 1 ) + { + UI_xboxErrorPopup( XB_POPUP_OVERWRITE_CONFIRM ); + } + else if( confirmType == 2 ) + { + UI_xboxErrorPopup( XB_POPUP_DISKFULL_DURING_SAVE ); + } + else if( confirmType == 666) + { + UI_xboxErrorPopup( XB_POPUP_YOU_ARE_DEAD ); + } + } + else if(Q_stricmp(menuid, "quitconfirm") == 0) + { + UI_xboxErrorPopup( XB_POPUP_QUIT_CONFIRM ); + } + else if(Q_stricmp(menuid, "saving") == 0) + { + UI_xboxErrorPopup( XB_POPUP_SAVING ); + } + else if(Q_stricmp(menuid, "confirmNewMission1") == 0) + { + UI_xboxErrorPopup( XB_POPUP_CONFIRM_NEW_1 ); + } + else if(Q_stricmp(menuid, "confirmNewMission2") == 0) + { + UI_xboxErrorPopup( XB_POPUP_CONFIRM_NEW_2 ); + } + else if(Q_stricmp(menuid, "confirmNewMission3") == 0) + { + UI_xboxErrorPopup( XB_POPUP_CONFIRM_NEW_3 ); + } + } + + else if (Q_stricmp(name, "setarrow")==0) + { + const char *controlName ; + const char *arrowControlName ; + const char * controlText; + int textwidth; + int startx; + itemDef_t * item; + itemDef_t * arrowControl; + menuDef_t *menu; + menu = Menu_GetFocused(); + + String_Parse(args, &controlName); + String_Parse(args, &arrowControlName); + //get the textwidth from control + if (menu) + { + itemDef_t *item; + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, controlName); + if (*(item->text) == '@') // string reference + { + controlText = SE_GetString( &(item->text[1]) ); + } + else + controlText = item->text; + textwidth = DC->textWidth( controlText, item->textscale, item->font ); + startx = item->window.rect.x; + arrowControl = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, arrowControlName); + setArrowX(arrowControl, textwidth + startx+ ARROW_SPACE); + } + + + } + else if (Q_stricmp(name, "getControls") == 0) + { + // Fetches all controls on the basic controls screen into ui cvars: + + // Inverted aim: + Cvar_SetValue("ui_mousePitch", (Cvar_VariableValue("m_pitch") >= 0) ? 0 : 1); + + // Thumbstick, buttons, triggers are automatic + } + else if (Q_stricmp(name, "setControls") == 0) + { + // Assigns all changes made on the basic controls screen: + char token[MAX_QPATH]; + + // Update inverted aim: + Settings.invertAim[0] = Cvar_VariableIntegerValue( "ui_mousePitch" ) ? false : true; + Cvar_SetValue( "m_pitch", Settings.invertAim[0] ? 0.022f : -0.022f ); + + // update thumbsticks in settings file: + Settings.thumbstickMode[0] = DC->getCVarValue( "ui_thumbStickMode" ); + + // update triggers + DC->getCVarString( "ui_triggerconfig", token, sizeof(token) ); + if( !Q_stricmp(token, "default") ) + { + Cbuf_ExecuteText( EXEC_NOW, "exec cfg/triggersConfig0.cfg" ); + Settings.triggerMode[0] = 0; + } + else if( !Q_stricmp(token, "southpaw") ) + { + Cbuf_ExecuteText( EXEC_NOW, "exec cfg/triggersConfig1.cfg" ); + Settings.triggerMode[0] = 1; + } + + // update buttons + DC->getCVarString( "ui_buttonconfig", token, sizeof(token) ); + if( !Q_stricmp(token, "weaponsbias") ) + { + Cbuf_ExecuteText( EXEC_NOW, "exec cfg/spbuttonConfig0.cfg" ); + Settings.buttonMode[0] = 0; + } + else if( !Q_stricmp(token, "forcebias") ) + { + Cbuf_ExecuteText( EXEC_NOW, "exec cfg/spbuttonConfig1.cfg" ); + Settings.buttonMode[0] = 1; + } + else if( !Q_stricmp(token, "southpaw") ) + { + Cbuf_ExecuteText( EXEC_NOW, "exec cfg/spbuttonConfig2.cfg" ); + Settings.buttonMode[0] = 2; + } + + Settings.Save(); + } + else if (Q_stricmp(name, "getsettingscvars") == 0) + { + // Fetches everything on the advanced controls screen: + Cvar_SetValue( "ui_useRumble", Cvar_VariableIntegerValue( "in_useRumble" ) ); + Cvar_SetValue( "ui_autolevel", Cvar_VariableIntegerValue( "cl_autolevel" ) ); + Cvar_SetValue( "ui_autoswitch", Cvar_VariableIntegerValue( "cg_autoswitch" ) ); + + // Horizontal/vertical + Cvar_SetValue( "ui_sensitivity", Cvar_VariableValue( "sensitivity" ) ); + Cvar_SetValue( "ui_sensitivityY", Cvar_VariableValue( "sensitivityY" ) ); + } + else if (Q_stricmp(name, "updatesettingscvars") == 0) + { + // Rumble + Settings.rumble[0] = Cvar_VariableIntegerValue( "ui_useRumble" ); + Cvar_SetValue( "in_useRumble", Settings.rumble[0] ); + + // Auto-level + Settings.autolevel[0] = Cvar_VariableIntegerValue( "ui_autolevel" ); + Cvar_SetValue( "cl_autolevel", Settings.autolevel[0] ); + + // Weapon switch + Settings.autoswitch[0] = Cvar_VariableIntegerValue( "ui_autoswitch" ); + Cvar_SetValue( "cg_autoswitch", Settings.autoswitch[0] ); + + // Turn/look + Settings.sensitivityX[0] = Cvar_VariableValue( "ui_sensitivity" ); + Settings.sensitivityY[0] = Cvar_VariableValue( "ui_sensitivityY" ); + Cvar_SetValue( "sensitivity", Settings.sensitivityX[0] ); + Cvar_SetValue( "sensitivityY", Settings.sensitivityY[0] ); + + Settings.Save(); + } + else if (Q_stricmp(name, "softkeyboardinit") == 0) + { + UI_SoftKeyboardInit(); + } + + else if (Q_stricmp(name, "updatevolume") == 0) + { + // Store all settings from the audio page: + Settings.effectsVolume = Cvar_VariableValue( "s_effects_volume" ); + Settings.musicVolume = Cvar_VariableValue( "s_music_volume" ); + Settings.voiceVolume = Cvar_VariableValue( "s_voice_volume" ); + + Settings.subtitles = Cvar_VariableIntegerValue( "g_subtitles" ); + Settings.brightness = Cvar_VariableValue( "s_brightness_volume" ); + + extern void GLimp_SetGamma(float); + GLimp_SetGamma(Cvar_VariableValue( "s_brightness_volume" ) / 5.0f); + + Settings.Save(); + } + else if (Q_stricmp(name, "brightnessChanged") == 0) + { + extern void GLimp_SetGamma(float); + GLimp_SetGamma(Cvar_VariableValue( "s_brightness_volume" ) / 5.0f); + } + else if(Q_stricmp(name,"updatemoves") == 0) + { + menuDef_t *menu; + menu = Menus_FindByName("datapadMovesMenu"); + //update saber models + if (menu) + { + itemDef_t *item; + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, "character"); + if (item) + { + UI_SaberAttachToChar( item ); + } + } + + UI_UpdateMoves(); + } + else if(Q_stricmp(name,"updatemovetitles") == 0) + { + UI_UpdateMoveTitles(); + } + else if(Q_stricmp(name, "resetscroll") == 0) + { + menuDef_t *menu = Menu_GetFocused(); + if(!menu) + return qfalse; + for( int i = 0; i < menu->itemCount; ++i ) + { + if( menu->items[i]->type == ITEM_TYPE_TEXTSCROLL ) + { + textScrollDef_t *scrollPtr = (textScrollDef_t*)menu->items[i]->typeData; + scrollPtr->startPos = 0; + } + } + } + else if(Q_stricmp(name, "levelselect") == 0) + { + const char *action; + String_Parse(args, &action); + if (!action) + return qfalse; + + if (Q_stricmp(action, "init") == 0) + { + UI_FeederSelection( FEEDER_LEVELSELECT, 0, NULL ); + } + else if (Q_stricmp(action, "load") == 0) + { + int levelSelectCheat = levelSelectData[levelSelectChoice].forceLevel; + Cvar_Set("levelSelectCheat", va("%d", levelSelectCheat)); + Cbuf_ExecuteText( EXEC_APPEND, va("map %s\n", levelSelectData[levelSelectChoice].mapname) ); + } + } + else if(Q_stricmp(name,"simulateuppress") == 0) + { + extern itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu); + menuDef_t *menu = Menu_GetFocused(); + Menu_SetNextCursorItem(menu); + } + else if(Q_stricmp(name,"simulatedownpress") == 0) + { + extern itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu); + menuDef_t *menu = Menu_GetFocused(); + Menu_SetPrevCursorItem(menu); + } + else if(Q_stricmp(name,"setMainController") == 0) + { + extern char lastControllerUsed; + extern void IN_SetMainController(int id); + + IN_SetMainController(lastControllerUsed); + } +#endif +#ifdef XBOX_DEMO + else if(Q_stricmp(name, "initdemoforce") == 0) + { + UI_DemoSetForceLevels(); + } + else if(Q_stricmp(name, "leaveDemo") == 0) + { + extern void Sys_Reboot( const char *reason, const void *pData ); + Sys_Reboot( "demo", NULL ); + } + else if(Q_stricmp(name, "kioskCheck") == 0) + { + // Only do this check once + static bool firstTime = true; + + // If we were started in kiosk mode (this is a filthy way to determine that) + // then we should start attract mode immediately: + extern bool demoTimerAlways; + extern void PlayDemo( void ); + + if( firstTime && demoTimerAlways ) + { + firstTime = false; // Bad fix for an awful recursion bug + PlayDemo(); + } + + firstTime = false; + } + else if(Q_stricmp(name, "clearplayersave") == 0) + { + Cvar_Set( "playersave", "" ); + } +#endif + else + { + Com_Printf("unknown UI script %s\n", name); + } + } + + return qtrue; +} + +/* +================= +UI_GetValue +================= +*/ +static float UI_GetValue(int ownerDraw) +{ + return 0; +} + +//Force Warnings +typedef enum +{ + FW_VERY_LIGHT = 0, + FW_SEMI_LIGHT, + FW_NEUTRAL, + FW_SEMI_DARK, + FW_VERY_DARK +}; + +const char *lukeForceStatusSounds[] = +{ +"sound/chars/luke/misc/MLUK_03.mp3", // Very Light +"sound/chars/luke/misc/MLUK_04.mp3", // Semi Light +"sound/chars/luke/misc/MLUK_05.mp3", // Neutral +"sound/chars/luke/misc/MLUK_01.mp3", // Semi dark +"sound/chars/luke/misc/MLUK_02.mp3", // Very dark +}; + +const char *kyleForceStatusSounds[] = +{ +"sound/chars/kyle/misc/MKYK_05.mp3", // Very Light +"sound/chars/kyle/misc/MKYK_04.mp3", // Semi Light +"sound/chars/kyle/misc/MKYK_03.mp3", // Neutral +"sound/chars/kyle/misc/MKYK_01.mp3", // Semi dark +"sound/chars/kyle/misc/MKYK_02.mp3", // Very dark +}; + + +static void UI_CalcForceStatus(void) +{ + float lightSide,darkSide,total; + short who, index=FW_VERY_LIGHT; + qboolean lukeFlag=qtrue; + float percent; + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + char value[256]; + + if (!cl) + { + return; + } + playerState_t* pState = cl->gentity->client; + + if (!cl->gentity || !cl->gentity->client) + { + return; + } + + memset(value, 0, sizeof(value)); + + lightSide = pState->forcePowerLevel[FP_HEAL] + + pState->forcePowerLevel[FP_TELEPATHY] + + pState->forcePowerLevel[FP_PROTECT] + + pState->forcePowerLevel[FP_ABSORB]; + + darkSide = pState->forcePowerLevel[FP_GRIP] + + pState->forcePowerLevel[FP_LIGHTNING] + + pState->forcePowerLevel[FP_RAGE] + + pState->forcePowerLevel[FP_DRAIN]; + + total = lightSide + darkSide; + + percent = lightSide / total; + + who = Q_irand( 0, 100 ); + if (percent >= 0.90f) // 90 - 100% + { + index = FW_VERY_LIGHT; + if (who <50) + { + strcpy(value,"vlk"); // Very light Kyle + lukeFlag = qfalse; + } + else + { + strcpy(value,"vll"); // Very light Luke + } + + } + else if (percent > 0.60f ) + { + index = FW_SEMI_LIGHT; + if ( who<50 ) + { + strcpy(value,"slk"); // Semi-light Kyle + lukeFlag = qfalse; + } + else + { + strcpy(value,"sll"); // Semi-light light Luke + } + } + else if (percent > 0.40f ) + { + index = FW_NEUTRAL; + if ( who<50 ) + { + strcpy(value,"ntk"); // Neutral Kyle + lukeFlag = qfalse; + } + else + { + strcpy(value,"ntl"); // Netural Luke + } + } + else if (percent > 0.10f ) + { + index = FW_SEMI_DARK; + if ( who<50 ) + { + strcpy(value,"sdk"); // Semi-dark Kyle + lukeFlag = qfalse; + } + else + { + strcpy(value,"sdl"); // Semi-Dark Luke + } + } + else + { + index = FW_VERY_DARK; + if ( who<50 ) + { + strcpy(value,"vdk"); // Very dark Kyle + lukeFlag = qfalse; + } + else + { + strcpy(value,"vdl"); // Very Dark Luke + } + } + + Cvar_Set("ui_forcestatus", value ); + + if (lukeFlag) + { + DC->startLocalSound(DC->registerSound(lukeForceStatusSounds[index], qfalse), CHAN_VOICE ); + } + else + { + DC->startLocalSound(DC->registerSound(kyleForceStatusSounds[index], qfalse), CHAN_VOICE ); + } +} + +/* +================= +UI_StopCinematic +================= +*/ +static void UI_StopCinematic(int handle) +{ + if (handle >= 0) + { + trap_CIN_StopCinematic(handle); + } + else + { + handle = abs(handle); + if (handle == UI_MAPCINEMATIC) + { + // FIXME - BOB do we need this? +// if (uiInfo.mapList[ui_currentMap.integer].cinematic >= 0) +// { +// trap_CIN_StopCinematic(uiInfo.mapList[ui_currentMap.integer].cinematic); +// uiInfo.mapList[ui_currentMap.integer].cinematic = -1; +// } + } + else if (handle == UI_NETMAPCINEMATIC) + { + // FIXME - BOB do we need this? +// if (uiInfo.serverStatus.currentServerCinematic >= 0) +// { +// trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic); +// uiInfo.serverStatus.currentServerCinematic = -1; +// } + } + else if (handle == UI_CLANCINEMATIC) + { + // FIXME - BOB do we need this? +// int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); +// if (i >= 0 && i < uiInfo.teamCount) +// { +// if (uiInfo.teamList[i].cinematic >= 0) +// { +// trap_CIN_StopCinematic(uiInfo.teamList[i].cinematic); +// uiInfo.teamList[i].cinematic = -1; +// } +// } + } + } +} + +static void UI_HandleLoadSelection() +{ + Cvar_Set("ui_SelectionOK", va("%d",(s_savegame.currentLine < s_savegame.saveFileCnt)) ); + if (s_savegame.currentLine >= s_savegame.saveFileCnt) + return; +// Cvar_Set("ui_gameDesc", s_savedata[s_savegame.currentLine].currentSaveFileComments ); // set comment + + + bool R_UpdateSaveGameImage(const char *filename); + //create the correctfilename + unsigned short saveGameName[filepathlength]; + char directoryInfo[filepathlength]; + char psLocalFilename[filepathlength]; + + mbstowcs(saveGameName, s_savedata[s_savegame.currentLine].currentSaveFileName, filepathlength); + + if (ERROR_SUCCESS ==XCreateSaveGame("U:\\", saveGameName, OPEN_EXISTING, 0,directoryInfo, filepathlength)) + { + strcpy (psLocalFilename , directoryInfo); + strcat (psLocalFilename , "screenshot.xbx"); + if( !R_UpdateSaveGameImage(psLocalFilename) && + !s_savedata[s_savegame.currentLine].screenshotNotify ) + { + s_savedata[s_savegame.currentLine].screenshotNotify = 1; + UI_xboxErrorPopup( XB_POPUP_CORRUPT_SCREENSHOT ); + } + } + else + R_UpdateSaveGameImage( "z:\\screenshot.xbx" ); +} + +/* +================= +UI_FeederCount +================= +*/ +static int UI_FeederCount(float feederID) +{ +#ifdef _XBOX +//JLF MPNOTNEEDED + static bool firstSaveRequest = true; +//JLF MPMOVED + static bool firstProfileListRequest = true; +#endif + + if (feederID == FEEDER_SAVEGAMES ) + { +//JLF MPNOTNEEDED +#ifdef _XBOX + if (s_savegame.saveFileCnt == -1 || firstSaveRequest) + { + firstSaveRequest = false; +#else + if (s_savegame.saveFileCnt == -1) + { +#endif + + ReadSaveDirectory(); //refresh + UI_HandleLoadSelection(); + UI_AdjustSaveGameListBox(s_savegame.currentLine); + } + return s_savegame.saveFileCnt; + } +//JLF MPMOVED +#ifdef _XBOX + else if (feederID == FEEDER_PROFILES ) + { + // if (s_playerProfile.fileCnt == -1 || firstProfileListRequest) + // { + firstProfileListRequest = false; + // ReadSaveDirectoryProfiles(); //refresh + // UI_HandleLoadSelection(); + // } + return s_playerProfile.fileCnt; + } +#endif + // count number of moves for the current title + else if (feederID == FEEDER_MOVES) + { + int count=0,i; + + for (i=0;itypeData; + if (modelPtr) + { + if (datapadMoveData[uiInfo.movesTitleIndex][index].anim) + { + ItemParse_model_g2anim_go( item, datapadMoveData[uiInfo.movesTitleIndex][index].anim ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + + // Play sound for anim + if (datapadMoveData[uiInfo.movesTitleIndex][index].sound == MDS_FORCE_JUMP) + { + DC->startLocalSound(uiInfo.uiDC.Assets.datapadmoveJumpSound, CHAN_LOCAL ); + } + else if (datapadMoveData[uiInfo.movesTitleIndex][index].sound == MDS_ROLL) + { + DC->startLocalSound(uiInfo.uiDC.Assets.datapadmoveRollSound, CHAN_LOCAL ); + } + else if (datapadMoveData[uiInfo.movesTitleIndex][index].sound == MDS_SABER) + { + // Randomly choose one sound + int soundI = Q_irand( 1, 6 ); + sfxHandle_t *soundPtr; + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound1; + if (soundI == 2) + { + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound2; + } + else if (soundI == 3) + { + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound3; + } + else if (soundI == 4) + { + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound4; + } + else if (soundI == 5) + { + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound5; + } + else if (soundI == 6) + { + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound6; + } + + DC->startLocalSound(*soundPtr, CHAN_LOCAL ); + } + + if (datapadMoveData[uiInfo.movesTitleIndex][index].desc) + { + Cvar_Set( "ui_move_desc", datapadMoveData[uiInfo.movesTitleIndex][index].desc); + } + + Com_sprintf( skin, sizeof( skin ), "models/players/%s/|%s|%s|%s", + Cvar_VariableString ( "g_char_model"), + Cvar_VariableString ( "g_char_skin_head"), + Cvar_VariableString ( "g_char_skin_torso"), + Cvar_VariableString ( "g_char_skin_legs") + ); + + ItemParse_model_g2skin_go( item, skin ); + + } + } + } + } + } + else if (feederID == FEEDER_MOVES_TITLES) + { + itemDef_t *item; + menuDef_t *menu; + modelDef_t *modelPtr; + + uiInfo.movesTitleIndex = index; + uiInfo.movesBaseAnim = datapadMoveTitleBaseAnims[uiInfo.movesTitleIndex]; + menu = Menus_FindByName("datapadMovesMenu"); + + if (menu) + { + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, "character"); + if (item) + { + modelPtr = (modelDef_t*)item->typeData; + if (modelPtr) + { + ItemParse_model_g2anim_go( item, uiInfo.movesBaseAnim ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + } + } + } + } + else if (feederID == FEEDER_MODS) + { + uiInfo.modIndex = index; + } + else if (feederID == FEEDER_PLAYER_SPECIES) + { + uiInfo.playerSpeciesIndex = index; + } + else if (feederID == FEEDER_LANGUAGES) + { + uiInfo.languageCountIndex = index; + } + else if (feederID == FEEDER_PLAYER_SKIN_HEAD) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadCount) + { + Cvar_Set("ui_char_skin_head", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadNames[index]); + } + } + else if (feederID == FEEDER_PLAYER_SKIN_TORSO) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinTorsoCount) + { + Cvar_Set("ui_char_skin_torso", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinTorsoNames[index]); + } + } + else if (feederID == FEEDER_PLAYER_SKIN_LEGS) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegCount) + { + Cvar_Set("ui_char_skin_legs", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegNames[index]); + } + } + else if (feederID == FEEDER_COLORCHOICES) + { +extern void Item_RunScript(itemDef_t *item, const char *s); //from ui_shared; + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorCount) + { + Item_RunScript(item, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorActionText[index]); + } + } + else if (feederID == FEEDER_LEVELSELECT) + { + if (index >= 0 && index < levelSelectSize) + levelSelectChoice = index; + } +/* else if (feederID == FEEDER_CINEMATICS) + { + uiInfo.movieIndex = index; + if (uiInfo.previewMovie >= 0) + { + trap_CIN_StopCinematic(uiInfo.previewMovie); + } + uiInfo.previewMovie = -1; + } + else if (feederID == FEEDER_DEMOS) + { + uiInfo.demoIndex = index; + } +*/ +} + +void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); +void Key_GetBindingBuf( int keynum, char *buf, int buflen ); + +static qboolean UI_Crosshair_HandleKey(int flags, float *special, int key) +{ + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) + { + if (key == A_MOUSE2) + { + uiInfo.currentCrosshair--; + } else { + uiInfo.currentCrosshair++; + } + + if (uiInfo.currentCrosshair >= NUM_CROSSHAIRS) { + uiInfo.currentCrosshair = 0; + } else if (uiInfo.currentCrosshair < 0) { + uiInfo.currentCrosshair = NUM_CROSSHAIRS - 1; + } + Cvar_Set("cg_drawCrosshair", va("%d", uiInfo.currentCrosshair)); + return qtrue; + } + return qfalse; +} + + +static qboolean UI_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) +{ + + switch (ownerDraw) + { + case UI_CROSSHAIR: + UI_Crosshair_HandleKey(flags, special, key); + break; + case UI_SOFT_KEYBOARD: + return UI_SoftKeyboard_HandleKey(flags, special, key); + break; + case UI_SOFT_KEYBOARD_DELETE: + return UI_SoftKeyboardDelete_HandleKey(flags, special, key); + break; + case UI_SOFT_KEYBOARD_ACCEPT: + return UI_SoftKeyboardAccept_HandleKey(flags, special, key); + break; + default: + break; + } + + return qfalse; +} + +//unfortunately we cannot rely on any game/cgame module code to do our animation stuff, +//because the ui can be loaded while the game/cgame are not loaded. So we're going to recreate what we need here. +// On Xbox, we need all the RAM we can get, and this is huge. So we just borrow the one from level. I hope +// this doesn't cause some apocalypse. Getting access to level in here is nigh impossible, as we'd have to +// include g_local.h, and the consequences of that are bad. So: This pointer is initialized in a global constructor +// in class UIAnimFileSetInitializer in g_main.cpp! Look there! Don't forget! +//#ifdef _XBOX +//animFileSet_t *ui_knownAnimFileSets = NULL; +//#else +#undef MAX_ANIM_FILES +#define MAX_ANIM_FILES 4 +typedef struct +{ + char filename[MAX_QPATH]; + animation_t animations[MAX_ANIMATIONS]; +} animFileSet_t; +static animFileSet_t ui_knownAnimFileSets[MAX_ANIM_FILES]; +//#endif + +int ui_numKnownAnimFileSets; + +qboolean UI_ParseAnimationFile( const char *af_filename ) +{ + const char *text_p; + int len; + int i; + const char *token; + float fps; + int skip; + char text[80000]; + int animNum; + animation_t *animations = ui_knownAnimFileSets[ui_numKnownAnimFileSets].animations; + + len = re.GetAnimationCFG(af_filename, text, sizeof(text)); + if ( len <= 0 ) + { + return qfalse; + } + if ( len >= sizeof( text ) - 1 ) + { + Com_Error( ERR_FATAL, "UI_ParseAnimationFile: File %s too long\n (%d > %d)", af_filename, len, sizeof( text ) - 1); + return qfalse; + } + + // parse the text + text_p = text; + skip = 0; // quiet the compiler warning + + //FIXME: have some way of playing anims backwards... negative numFrames? + + //initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100 + for(i = 0; i < MAX_ANIMATIONS; i++) + { + animations[i].firstFrame = 0; + animations[i].numFrames = 0; + animations[i].loopFrames = -1; + animations[i].frameLerp = 100; +// animations[i].initialLerp = 100; + } + + // read information for each frame + while(1) + { + token = COM_Parse( &text_p ); + + if ( !token || !token[0]) + { + break; + } + + animNum = GetIDForString(animTable, token); + if(animNum == -1) + { +//#ifndef FINAL_BUILD +#ifdef _DEBUG + if (strcmp(token,"ROOT")) + { + Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, af_filename); + } +#endif + while (token[0]) + { + token = COM_ParseExt( &text_p, qfalse ); //returns empty string when next token is EOL + } + continue; + } + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + animations[animNum].firstFrame = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + animations[animNum].numFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + animations[animNum].loopFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + fps = atof( token ); + if ( fps == 0 ) + { + fps = 1;//Don't allow divide by zero error + } + if ( fps < 0 ) + {//backwards + animations[animNum].frameLerp = floor(1000.0f / fps); + } + else + { + animations[animNum].frameLerp = ceil(1000.0f / fps); + } + +// animations[animNum].initialLerp = ceil(1000.0f / fabs(fps)); + } + + return qtrue; +} + +qboolean UI_ParseAnimFileSet( const char *animCFG, int *animFileIndex ) +{ //Not going to bother parsing the sound config here. + char afilename[MAX_QPATH]; + char strippedName[MAX_QPATH]; + int i; + char *slash; + + Q_strncpyz( strippedName, animCFG, sizeof(strippedName), qtrue); + slash = strrchr( strippedName, '/' ); + if ( slash ) + { + // truncate modelName to find just the dir not the extension + *slash = 0; + } + + //if this anims file was loaded before, don't parse it again, just point to the correct table of info + for ( i = 0; i < ui_numKnownAnimFileSets; i++ ) + { + if ( Q_stricmp(ui_knownAnimFileSets[i].filename, strippedName ) == 0 ) + { + *animFileIndex = i; + return qtrue; + } + } + + if ( ui_numKnownAnimFileSets == MAX_ANIM_FILES ) + {//TOO MANY! + for (i = 0; i < MAX_ANIM_FILES; i++) + { + Com_Printf("animfile[%d]: %s\n", i, ui_knownAnimFileSets[i].filename); + } + Com_Error( ERR_FATAL, "UI_ParseAnimFileSet: %d == MAX_ANIM_FILES == %d", ui_numKnownAnimFileSets, MAX_ANIM_FILES); + } + + //Okay, time to parse in a new one + Q_strncpyz( ui_knownAnimFileSets[ui_numKnownAnimFileSets].filename, strippedName, sizeof( ui_knownAnimFileSets[ui_numKnownAnimFileSets].filename ) ); + + // Load and parse animations.cfg file + Com_sprintf( afilename, sizeof( afilename ), "%s/animation.cfg", strippedName ); + if ( !UI_ParseAnimationFile( afilename ) ) + { + *animFileIndex = -1; + return qfalse; + } + + //set index and increment + *animFileIndex = ui_numKnownAnimFileSets++; + + return qtrue; +} + +int UI_G2SetAnim(CGhoul2Info *ghlInfo, const char *boneName, int animNum, const qboolean freeze) +{ + int animIndex,blendTime; + char *GLAName; + + GLAName = G2API_GetGLAName(ghlInfo); + + if (!GLAName || !GLAName[0]) + { + return 0; + } + + UI_ParseAnimFileSet(GLAName, &animIndex); + + if (animIndex != -1) + { + animation_t *anim = &ui_knownAnimFileSets[animIndex].animations[animNum]; + if (anim->numFrames <= 0) + { + return 0; + } + int sFrame = anim->firstFrame; + int eFrame = anim->firstFrame + anim->numFrames; + int flags = BONE_ANIM_OVERRIDE; + int time = uiInfo.uiDC.realTime; + float animSpeed = (50.0f / anim->frameLerp); + + blendTime = 150; + + // Freeze anim if it's not looping, special hack for datapad moves menu + if (freeze) + { + if (anim->loopFrames == -1) + { + flags = BONE_ANIM_OVERRIDE_FREEZE; + } + else + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + } + else if (anim->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + flags |= BONE_ANIM_BLEND; + blendTime = 150; + + + G2API_SetBoneAnim(ghlInfo, boneName, sFrame, eFrame, flags, animSpeed, time, -1, blendTime); + + return ((anim->frameLerp * (anim->numFrames-2))); + } + + return 0; +} + +static qboolean UI_ParseColorData(char* buf, playerSpeciesInfo_t &species) +{ + const char *token; + const char *p; + + p = buf; + COM_BeginParseSession(); + species.ColorCount = 0; + + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); //looking for the shader + if ( token[0] == 0 ) + { + return species.ColorCount; + } + Q_strncpyz( species.ColorShader[species.ColorCount], token, sizeof(species.ColorShader[0]), qtrue ); + + token = COM_ParseExt( &p, qtrue ); //looking for action block { + if ( token[0] != '{' ) + { + return qfalse; + } + + assert(!species.ColorActionText[species.ColorCount][0]); + token = COM_ParseExt( &p, qtrue ); //looking for action commands + while (token[0] != '}') + { + if ( token[0] == 0) + { //EOF + return qfalse; + } + assert(species.ColorCount < sizeof(species.ColorActionText)/sizeof(species.ColorActionText[0]) ); + Q_strcat(species.ColorActionText[species.ColorCount], sizeof(species.ColorActionText[0]), token); + Q_strcat(species.ColorActionText[species.ColorCount], sizeof(species.ColorActionText[0]), " "); + token = COM_ParseExt( &p, qtrue ); //looking for action commands or final } + } + species.ColorCount++; //next color please + } + return qtrue;//never get here +} + +/* +================= +bIsImageFile +builds path and scans for valid image extentions +================= +*/ +static bool bIsImageFile(const char* dirptr, const char* skinname, qboolean building) +{ + char fpath[MAX_QPATH]; + int f; + +#ifdef _XBOX + Com_sprintf(fpath, MAX_QPATH, "models/players/%s/icon_%s.dds", dirptr, skinname); +#else + Com_sprintf(fpath, MAX_QPATH, "models/players/%s/icon_%s.jpg", dirptr, skinname); +#endif + ui.FS_FOpenFile(fpath, &f, FS_READ); +#if !defined(_XBOX) || defined(_DEBUG) + if (!f) + { //not there, try png + Com_sprintf(fpath, MAX_QPATH, "models/players/%s/icon_%s.png", dirptr, skinname); + ui.FS_FOpenFile(fpath, &f, FS_READ); + } + if (!f) + { //not there, try tga + Com_sprintf(fpath, MAX_QPATH, "models/players/%s/icon_%s.tga", dirptr, skinname); + ui.FS_FOpenFile(fpath, &f, FS_READ); + } +#endif + if (f) + { + ui.FS_FCloseFile(f); + if ( building ) ui.R_RegisterShaderNoMip(fpath); + return true; + } + + return false; +} + +/* +================= +PlayerModel_BuildList +================= +*/ +static void UI_BuildPlayerModel_List( qboolean inGameLoad ) +{ + int numdirs; + char dirlist[2048]; + char* dirptr; + int dirlen; + int i; + const int building = Cvar_VariableIntegerValue("com_buildscript"); + + uiInfo.playerSpeciesCount = 0; + uiInfo.playerSpeciesIndex = 0; + memset(uiInfo.playerSpecies, 0, sizeof (uiInfo.playerSpecies) ); + + // iterate directory of all player models + numdirs = ui.FS_GetFileList("models/players", "/", dirlist, 2048 ); + dirptr = dirlist; + for (i=0; ig2_InitGhoul2Model(ghoul2, fpath, 0, 0, 0, 0, 0); + if (g2Model >= 0) + { + DC->g2_RemoveGhoul2Model( ghoul2, 0 ); + } + } + if (uiInfo.playerSpeciesCount >= MAX_PLAYERMODELS) + { + return; + } + } + } + +} + +/* +================= +UI_Init +================= +*/ +void _UI_Init( qboolean inGameLoad ) +{ + uiInfo.inGameLoad = inGameLoad; + + UI_RegisterCvars(); + + UI_InitMemory(); + + // cache redundant calulations + trap_GetGlconfig( &uiInfo.uiDC.glconfig ); + + // for 640x480 virtualized screen + uiInfo.uiDC.yscale = uiInfo.uiDC.glconfig.vidHeight * (1.0/480.0); + uiInfo.uiDC.xscale = uiInfo.uiDC.glconfig.vidWidth * (1.0/640.0); + if ( uiInfo.uiDC.glconfig.vidWidth * 480 > uiInfo.uiDC.glconfig.vidHeight * 640 ) + { + // wide screen + uiInfo.uiDC.bias = 0.5 * ( uiInfo.uiDC.glconfig.vidWidth - ( uiInfo.uiDC.glconfig.vidHeight * (640.0/480.0) ) ); + } + else + { + // no wide screen + uiInfo.uiDC.bias = 0; + } + + Init_Display(&uiInfo.uiDC); + + uiInfo.uiDC.drawText = &Text_Paint; + uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic; + uiInfo.uiDC.drawRect = &_UI_DrawRect; + uiInfo.uiDC.drawSides = &_UI_DrawSides; + uiInfo.uiDC.drawTextWithCursor = &Text_PaintWithCursor; + uiInfo.uiDC.executeText = &Cbuf_ExecuteText; + uiInfo.uiDC.drawTopBottom = &_UI_DrawTopBottom; + uiInfo.uiDC.feederCount = &UI_FeederCount; + uiInfo.uiDC.feederSelection = &UI_FeederSelection; + uiInfo.uiDC.fillRect = &UI_FillRect; + uiInfo.uiDC.getBindingBuf = &Key_GetBindingBuf; + uiInfo.uiDC.getCVarString = Cvar_VariableStringBuffer; + uiInfo.uiDC.getCVarValue = trap_Cvar_VariableValue; + uiInfo.uiDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + uiInfo.uiDC.getValue = &UI_GetValue; + uiInfo.uiDC.keynumToStringBuf = &Key_KeynumToStringBuf; + uiInfo.uiDC.modelBounds = &trap_R_ModelBounds; + uiInfo.uiDC.ownerDrawVisible = &UI_OwnerDrawVisible; + uiInfo.uiDC.ownerDrawWidth = &UI_OwnerDrawWidth; + uiInfo.uiDC.ownerDrawItem = &UI_OwnerDraw; + uiInfo.uiDC.Print = &Com_Printf; + uiInfo.uiDC.registerSound = &trap_S_RegisterSound; + uiInfo.uiDC.registerModel = ui.R_RegisterModel; + uiInfo.uiDC.clearScene = &trap_R_ClearScene; + uiInfo.uiDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + uiInfo.uiDC.renderScene = &trap_R_RenderScene; + uiInfo.uiDC.runScript = &UI_RunMenuScript; + uiInfo.uiDC.deferScript = &UI_DeferMenuScript; + uiInfo.uiDC.setBinding = &trap_Key_SetBinding; + uiInfo.uiDC.setColor = &UI_SetColor; + uiInfo.uiDC.setCVar = Cvar_Set; + uiInfo.uiDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + uiInfo.uiDC.startLocalSound = &trap_S_StartLocalSound; + uiInfo.uiDC.stopCinematic = &UI_StopCinematic; + uiInfo.uiDC.textHeight = &Text_Height; + uiInfo.uiDC.textWidth = &Text_Width; + uiInfo.uiDC.feederItemImage = &UI_FeederItemImage; + uiInfo.uiDC.feederItemText = &UI_FeederItemText; +#ifdef _IMMERSION + uiInfo.uiDC.registerForce = &trap_FF_Register; + uiInfo.uiDC.startForce = &trap_FF_Start; +#endif // _IMMERSION + uiInfo.uiDC.ownerDrawHandleKey = &UI_OwnerDrawHandleKey; + + uiInfo.uiDC.registerSkin = re.RegisterSkin; + +#ifndef _XBOX + uiInfo.uiDC.g2_SetSkin = G2API_SetSkin; + uiInfo.uiDC.g2_SetBoneAnim = G2API_SetBoneAnim; +#endif + uiInfo.uiDC.g2_RemoveGhoul2Model = G2API_RemoveGhoul2Model; + uiInfo.uiDC.g2_InitGhoul2Model = G2API_InitGhoul2Model; + uiInfo.uiDC.g2_CleanGhoul2Models = G2API_CleanGhoul2Models; + uiInfo.uiDC.g2_AddBolt = G2API_AddBolt; + uiInfo.uiDC.g2_GetBoltMatrix = G2API_GetBoltMatrix; + uiInfo.uiDC.g2_GiveMeVectorFromMatrix = G2API_GiveMeVectorFromMatrix; + + uiInfo.uiDC.g2hilev_SetAnim = UI_G2SetAnim; + + UI_BuildPlayerModel_List(inGameLoad); + + String_Init(); + + char *menuSet = UI_Cvar_VariableString("ui_menuFiles"); + + if (menuSet == NULL || menuSet[0] == '\0') + { + menuSet = "ui/menus.txt"; + } + if (inGameLoad) + { + UI_LoadMenus("ui/ingame.txt", qtrue); + } + else + { + UI_LoadMenus(menuSet, qtrue); + } + + Menus_CloseAll(); + + uiInfo.uiDC.whiteShader = ui.R_RegisterShaderNoMip( "white" ); + + AssetCache(); + + uis.debugMode = qfalse; + + // sets defaults for ui temp cvars + uiInfo.effectsColor = (int)trap_Cvar_VariableValue("color")-1; + if (uiInfo.effectsColor < 0) + { + uiInfo.effectsColor = 0; + } + uiInfo.effectsColor = gamecodetoui[uiInfo.effectsColor]; + uiInfo.currentCrosshair = (int)trap_Cvar_VariableValue("cg_drawCrosshair"); + + Cvar_Set("cg_endcredits", "0"); // Reset value + Cvar_Set("ui_missionfailed","0"); // reset + + uiInfo.forcePowerUpdated = FP_UPDATED_NONE; + uiInfo.selectedWeapon1 = NOWEAPON; + uiInfo.selectedWeapon2 = NOWEAPON; + uiInfo.selectedThrowWeapon = NOWEAPON; + + uiInfo.uiDC.Assets.nullSound = trap_S_RegisterSound("sound/null", qfalse); + + trap_S_RegisterSound("sound/interface/weapon_deselect", qfalse); + + +} + + +/* +================= +UI_RegisterCvars +================= +*/ +static void UI_RegisterCvars( void ) +{ + int i; + cvarTable_t *cv; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) + { + Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags ); + } +} + +#ifdef _XBOX +//JLFCALLOUT MPNOTNEEDED->(INCLUDE WORKS) +qboolean Menu_Parse(char *buffer, menuDef_t *menu); + +char * UI_ParseInclude(const char *menuFile, menuDef_t * menu) +{ + char * buffer; + int len; +// pc_token_t token; + + //Com_DPrintf("Parsing menu file:%s\n", menuFile); + + len = PC_StartParseSession(menuFile,&buffer, true); + + if (len<=0) + { + Com_Printf("UI_ParseMenu: Unable to load menu %s\n", menuFile); + return NULL; + } + + // PC_EndParseSession(buffer); + return buffer; +} +#endif + +/* +================= +UI_ParseMenu +================= +*/ +void UI_ParseMenu(const char *menuFile) +{ + char *buffer,*holdBuffer,*token2; + int len; +// pc_token_t token; + + //Com_DPrintf("Parsing menu file:%s\n", menuFile); + + len = PC_StartParseSession(menuFile,&buffer); + + holdBuffer = buffer; + + if (len<=0) + { + Com_Printf("UI_ParseMenu: Unable to load menu %s\n", menuFile); + return; + } + + while ( 1 ) + { + + token2 = PC_ParseExt(); + + if (!*token2) + { + break; + } +/* + if ( menuCount == MAX_MENUS ) + { + PC_ParseWarning("Too many menus!"); + break; + } +*/ + if ( *token2 == '{') + { + continue; + } + else if ( *token2 == '}' ) + { + break; + } + else if (Q_stricmp(token2, "assetGlobalDef") == 0) + { + if (Asset_Parse(&holdBuffer)) + { + continue; + } + else + { + break; + } + } + else if (Q_stricmp(token2, "menudef") == 0) + { + // start a new menu + Menu_New(holdBuffer); + continue; + } + + PC_ParseWarning(va("Invalid keyword '%s'",token2)); + } + + PC_EndParseSession(buffer); + +} + +/* +================= +Load_Menu + Load current menu file +================= +*/ +qboolean Load_Menu(const char **holdBuffer) +{ + const char *token2; + + token2 = COM_ParseExt( holdBuffer, qtrue ); + + if (!token2[0]) + { + return qfalse; + } + + if (*token2 != '{') + { + return qfalse; + } + + while ( 1 ) + { + token2 = COM_ParseExt( holdBuffer, qtrue ); + + if ((!token2) || (token2 == 0)) + { + return qfalse; + } + + if ( *token2 == '}' ) + { + return qtrue; + } + +//#ifdef _DEBUG +// extern void UI_Debug_AddMenuFilePath(const char *); +// UI_Debug_AddMenuFilePath(token2); +//#endif + UI_ParseMenu(token2); + + } + return qfalse; +} + +/* +================= +UI_LoadMenus + Load all menus based on the files listed in the data file in menuFile (default "ui/menus.txt") +================= +*/ +void UI_LoadMenus(const char *menuFile, qboolean reset) +{ +// pc_token_t token; +// int handle; + int start; + + char *buffer; + const char *holdBuffer; + int len; + + start = Sys_Milliseconds(); + + len = ui.FS_ReadFile(menuFile,(void **) &buffer); + + if (len<1) + { + Com_Printf( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); + len = ui.FS_ReadFile("ui/menus.txt",(void **) &buffer); + + if (len<1) + { + Com_Error( ERR_FATAL, "%s", va("default menu file not found: ui/menus.txt, unable to continue!\n", menuFile )); + return; + } + } + + if (reset) + { + Menu_Reset(); + } + + const char *token2; + holdBuffer = buffer; + while ( 1 ) + { + token2 = COM_ParseExt( &holdBuffer, qtrue ); + if (!*token2) + { + break; + } + + if( *token2 == 0 || *token2 == '}') // End of the menus file + { + break; + } + + if (*token2 == '{') + { + continue; + } + else if (Q_stricmp(token2, "loadmenu") == 0) + { + if (Load_Menu(&holdBuffer)) + { + continue; + } + else + { + break; + } + } + else + { + Com_Printf("Unknown keyword '%s' in menus file %s\n", token2, menuFile); + } + } + + //Com_Printf("UI menu load time = %d milli seconds\n", Sys_Milliseconds() - start); + + ui.FS_FreeFile( buffer ); //let go of the buffer +} + +/* +================= +UI_Load +================= +*/ +void UI_Load(void) +{ + char *menuSet; + char lastName[1024]; + menuDef_t *menu = Menu_GetFocused(); + + if (menu && menu->window.name) + { + strcpy(lastName, menu->window.name); + } + else + { + lastName[0] = 0; + } + + if (uiInfo.inGameLoad) + { + menuSet= "ui/ingame.txt"; + } + else + { + menuSet= UI_Cvar_VariableString("ui_menuFiles"); + } + if (menuSet == NULL || menuSet[0] == '\0') + { + menuSet = "ui/menus.txt"; + } + + String_Init(); + + + UI_LoadMenus(menuSet, qtrue); + Menus_CloseAll(); + Menus_ActivateByName(lastName); +} + +/* +================= +Asset_Parse +================= +*/ +qboolean Asset_Parse(char **buffer) +{ + char *token; + const char *tempStr; + int pointSize; + + token = PC_ParseExt(); + + if (!token) + { + return qfalse; + } + + if (*token != '{') + { + return qfalse; + } + + while ( 1 ) + { + + token = PC_ParseExt(); + + if (!token) + { + return qfalse; + } + + if (*token == '}') + { + return qtrue; + } + + // fonts + if (Q_stricmp(token, "smallFont") == 0) //legacy, really it only matters which order they are registered + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'smallFont'"); + return qfalse; + } + + UI_RegisterFont(tempStr); + + //not used anymore + if (PC_ParseInt(&pointSize)) + { +// PC_ParseWarning("Bad 2nd parameter for keyword 'smallFont'"); + } + + continue; + } + + if (Q_stricmp(token, "mediumFont") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'font'"); + return qfalse; + } + + uiInfo.uiDC.Assets.qhMediumFont = UI_RegisterFont(tempStr); + uiInfo.uiDC.Assets.fontRegistered = qtrue; + + //not used + if (PC_ParseInt(&pointSize)) + { +// PC_ParseWarning("Bad 2nd parameter for keyword 'font'"); + } + continue; + } + + if (Q_stricmp(token, "bigFont") == 0) //legacy + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'bigFont'"); + return qfalse; + } + + UI_RegisterFont(tempStr); + + if (PC_ParseInt(&pointSize)) + { +// PC_ParseWarning("Bad 2nd parameter for keyword 'bigFont'"); + } + + continue; + } + + // gradientbar + if (Q_stricmp(token, "gradientbar") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'gradientbar'"); + return qfalse; + } + uiInfo.uiDC.Assets.gradientBar = ui.R_RegisterShaderNoMip(tempStr); + continue; + } + + // enterMenuSound + if (Q_stricmp(token, "menuEnterSound") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'menuEnterSound'"); + return qfalse; + } + + uiInfo.uiDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // exitMenuSound + if (Q_stricmp(token, "menuExitSound") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'menuExitSound'"); + return qfalse; + } + uiInfo.uiDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // itemFocusSound + if (Q_stricmp(token, "itemFocusSound") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'itemFocusSound'"); + return qfalse; + } + uiInfo.uiDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // menuBuzzSound + if (Q_stricmp(token, "menuBuzzSound") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'menuBuzzSound'"); + return qfalse; + } + uiInfo.uiDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // Chose a force power from the ingame force allocation screen (the one where you get to allocate a force power point) + if (Q_stricmp(token, "forceChosenSound") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'forceChosenSound'"); + return qfalse; + } + + uiInfo.uiDC.Assets.forceChosenSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + + // Unchose a force power from the ingame force allocation screen (the one where you get to allocate a force power point) + if (Q_stricmp(token, "forceUnchosenSound") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'forceUnchosenSound'"); + return qfalse; + } + + uiInfo.uiDC.Assets.forceUnchosenSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token, "datapadmoveRollSound") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'datapadmoveRollSound'"); + return qfalse; + } + + uiInfo.uiDC.Assets.datapadmoveRollSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token, "datapadmoveJumpSound") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'datapadmoveRoll'"); + return qfalse; + } + + uiInfo.uiDC.Assets.datapadmoveJumpSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token, "datapadmoveSaberSound1") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'datapadmoveSaberSound1'"); + return qfalse; + } + + uiInfo.uiDC.Assets.datapadmoveSaberSound1 = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token, "datapadmoveSaberSound2") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'datapadmoveSaberSound2'"); + return qfalse; + } + + uiInfo.uiDC.Assets.datapadmoveSaberSound2 = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token, "datapadmoveSaberSound3") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'datapadmoveSaberSound3'"); + return qfalse; + } + + uiInfo.uiDC.Assets.datapadmoveSaberSound3 = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token, "datapadmoveSaberSound4") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'datapadmoveSaberSound4'"); + return qfalse; + } + + uiInfo.uiDC.Assets.datapadmoveSaberSound4 = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token, "datapadmoveSaberSound5") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'datapadmoveSaberSound5'"); + return qfalse; + } + + uiInfo.uiDC.Assets.datapadmoveSaberSound5 = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token, "datapadmoveSaberSound6") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'datapadmoveSaberSound6'"); + return qfalse; + } + + uiInfo.uiDC.Assets.datapadmoveSaberSound6 = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + +#ifdef _IMMERSION + + if (Q_stricmp(token, "menuEnterForce") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'menuEnterForce'"); + return qfalse; + } + uiInfo.uiDC.Assets.menuEnterForce = trap_FF_Register( tempStr ); + continue; + } + + if (Q_stricmp(token, "menuExitForce") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'menuExitForce'"); + return qfalse; + } + uiInfo.uiDC.Assets.menuExitForce = trap_FF_Register( tempStr ); + continue; + } + + if (Q_stricmp(token, "itemFocusForce") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'itemFocusForce'"); + return qfalse; + } + uiInfo.uiDC.Assets.itemFocusForce = trap_FF_Register( tempStr ); + continue; + } + + if (Q_stricmp(token, "menuBuzzForce") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'menuBuzzForce'"); + return qfalse; + } + uiInfo.uiDC.Assets.menuBuzzForce = trap_FF_Register( tempStr ); + continue; + } + +#endif // _IMMERSION + if (Q_stricmp(token, "cursor") == 0) + { + if (PC_ParseString(&tempStr)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'cursor'"); + return qfalse; + } +// uiInfo.uiDC.Assets.cursor = ui.R_RegisterShaderNoMip( tempStr); + continue; + } + + if (Q_stricmp(token, "fadeClamp") == 0) + { + if (PC_ParseFloat(&uiInfo.uiDC.Assets.fadeClamp)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'fadeClamp'"); + return qfalse; + } + continue; + } + + if (Q_stricmp(token, "fadeCycle") == 0) + { + if (PC_ParseInt(&uiInfo.uiDC.Assets.fadeCycle)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'fadeCycle'"); + return qfalse; + } + continue; + } + + if (Q_stricmp(token, "fadeAmount") == 0) + { + if (PC_ParseFloat(&uiInfo.uiDC.Assets.fadeAmount)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'fadeAmount'"); + return qfalse; + } + continue; + } + + if (Q_stricmp(token, "shadowX") == 0) + { + if (PC_ParseFloat(&uiInfo.uiDC.Assets.shadowX)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'shadowX'"); + return qfalse; + } + continue; + } + + if (Q_stricmp(token, "shadowY") == 0) + { + if (PC_ParseFloat(&uiInfo.uiDC.Assets.shadowY)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'shadowY'"); + return qfalse; + } + continue; + } + + if (Q_stricmp(token, "shadowColor") == 0) + { + if (PC_ParseColor(&uiInfo.uiDC.Assets.shadowColor)) + { + PC_ParseWarning("Bad 1st parameter for keyword 'shadowColor'"); + return qfalse; + } + uiInfo.uiDC.Assets.shadowFadeClamp = uiInfo.uiDC.Assets.shadowColor[3]; + continue; + } + + // precaching various sound files used in the menus + if (Q_stricmp(token, "precacheSound") == 0) + { + if (PC_Script_Parse(&tempStr)) + { + char *soundFile; + do + { + soundFile = COM_ParseExt(&tempStr, qfalse); + if (soundFile[0] != 0 && soundFile[0] != ';') { + if (!trap_S_RegisterSound( soundFile, qfalse )) + { + PC_ParseWarning("Can't locate precache sound"); + } + } + } while (soundFile[0]); + } + continue; + } + } + + PC_ParseWarning(va("Invalid keyword '%s'",token)); + return qfalse; +} + +/* +================= +UI_Update +================= +*/ +static void UI_Update(const char *name) +{ + int val = trap_Cvar_VariableValue(name); + + + if (Q_stricmp(name, "s_khz") == 0) + { + ui.Cmd_ExecuteText( EXEC_APPEND, "snd_restart\n" ); + return; + } +#ifdef _IMMERSION + if (Q_stricmp(name, "ff") == 0) + { + ui.Cmd_ExecuteText( EXEC_APPEND, "ff_restart\n"); + return; + } +#endif // _IMMERSION + + if (Q_stricmp(name, "ui_SetName") == 0) + { + Cvar_Set( "name", UI_Cvar_VariableString("ui_Name")); + } + else if (Q_stricmp(name, "ui_setRate") == 0) + { + float rate = trap_Cvar_VariableValue("rate"); + if (rate >= 5000) + { + Cvar_Set("cl_maxpackets", "30"); + Cvar_Set("cl_packetdup", "1"); + } + else if (rate >= 4000) + { + Cvar_Set("cl_maxpackets", "15"); + Cvar_Set("cl_packetdup", "2"); // favor less prediction errors when there's packet loss + } + else + { + Cvar_Set("cl_maxpackets", "15"); + Cvar_Set("cl_packetdup", "1"); // favor lower bandwidth + } + } + else if (Q_stricmp(name, "ui_GetName") == 0) + { + Cvar_Set( "ui_Name", UI_Cvar_VariableString("name")); + } + else if (Q_stricmp(name, "ui_r_colorbits") == 0) + { + switch (val) + { + case 0: + Cvar_SetValue( "ui_r_depthbits", 0 ); + break; + + case 16: + Cvar_SetValue( "ui_r_depthbits", 16 ); + break; + + case 32: + Cvar_SetValue( "ui_r_depthbits", 24 ); + break; + } + } + else if (Q_stricmp(name, "ui_r_lodbias") == 0) + { + switch (val) + { + case 0: + Cvar_SetValue( "ui_r_subdivisions", 4 ); + break; + case 1: + Cvar_SetValue( "ui_r_subdivisions", 12 ); + break; + + case 2: + Cvar_SetValue( "ui_r_subdivisions", 20 ); + break; + } + } + else if (Q_stricmp(name, "ui_r_glCustom") == 0) + { + switch (val) + { + case 0: // high quality + + Cvar_SetValue( "ui_r_fullScreen", 1 ); + Cvar_SetValue( "ui_r_subdivisions", 4 ); + Cvar_SetValue( "ui_r_lodbias", 0 ); + Cvar_SetValue( "ui_r_colorbits", 32 ); + Cvar_SetValue( "ui_r_depthbits", 24 ); + Cvar_SetValue( "ui_r_picmip", 0 ); + Cvar_SetValue( "ui_r_mode", 4 ); + Cvar_SetValue( "ui_r_texturebits", 32 ); + Cvar_SetValue( "ui_r_fastSky", 0 ); + Cvar_SetValue( "ui_r_inGameVideo", 1 ); + //Cvar_SetValue( "ui_cg_shadows", 2 );//stencil + Cvar_Set( "ui_r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + break; + + case 1: // normal + Cvar_SetValue( "ui_r_fullScreen", 1 ); + Cvar_SetValue( "ui_r_subdivisions", 4 ); + Cvar_SetValue( "ui_r_lodbias", 0 ); + Cvar_SetValue( "ui_r_colorbits", 0 ); + Cvar_SetValue( "ui_r_depthbits", 24 ); + Cvar_SetValue( "ui_r_picmip", 1 ); + Cvar_SetValue( "ui_r_mode", 3 ); + Cvar_SetValue( "ui_r_texturebits", 0 ); + Cvar_SetValue( "ui_r_fastSky", 0 ); + Cvar_SetValue( "ui_r_inGameVideo", 1 ); + //Cvar_SetValue( "ui_cg_shadows", 2 ); + Cvar_Set( "ui_r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + break; + + case 2: // fast + + Cvar_SetValue( "ui_r_fullScreen", 1 ); + Cvar_SetValue( "ui_r_subdivisions", 12 ); + Cvar_SetValue( "ui_r_lodbias", 1 ); + Cvar_SetValue( "ui_r_colorbits", 0 ); + Cvar_SetValue( "ui_r_depthbits", 0 ); + Cvar_SetValue( "ui_r_picmip", 2 ); + Cvar_SetValue( "ui_r_mode", 3 ); + Cvar_SetValue( "ui_r_texturebits", 0 ); + Cvar_SetValue( "ui_r_fastSky", 1 ); + Cvar_SetValue( "ui_r_inGameVideo", 0 ); + //Cvar_SetValue( "ui_cg_shadows", 1 ); + Cvar_Set( "ui_r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); + break; + + case 3: // fastest + + Cvar_SetValue( "ui_r_fullScreen", 1 ); + Cvar_SetValue( "ui_r_subdivisions", 20 ); + Cvar_SetValue( "ui_r_lodbias", 2 ); + Cvar_SetValue( "ui_r_colorbits", 16 ); + Cvar_SetValue( "ui_r_depthbits", 16 ); + Cvar_SetValue( "ui_r_mode", 3 ); + Cvar_SetValue( "ui_r_picmip", 3 ); + Cvar_SetValue( "ui_r_texturebits", 16 ); + Cvar_SetValue( "ui_r_fastSky", 1 ); + Cvar_SetValue( "ui_r_inGameVideo", 0 ); + //Cvar_SetValue( "ui_cg_shadows", 0 ); + Cvar_Set( "ui_r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); + break; + } + } + else + {//failure!! + Com_Printf("unknown UI script UPDATE %s\n", name); + } +} + +#define ASSET_SCROLLBAR "gfx/menus/scrollbar.tga" +#define ASSET_SCROLLBAR_ARROWDOWN "gfx/menus/scrollbar_arrow_dwn_a.tga" +#define ASSET_SCROLLBAR_ARROWUP "gfx/menus/scrollbar_arrow_up_a.tga" +#define ASSET_SCROLLBAR_ARROWLEFT "gfx/menus/scrollbar_arrow_left.tga" +#define ASSET_SCROLLBAR_ARROWRIGHT "gfx/menus/scrollbar_arrow_right.tga" +#define ASSET_SCROLL_THUMB "gfx/menus/scrollbar_thumb.tga" + + +/* +================= +AssetCache +================= +*/ +void AssetCache(void) +{ +// int n; + uiInfo.uiDC.Assets.scrollBar = ui.R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + uiInfo.uiDC.Assets.scrollBarArrowDown = ui.R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + uiInfo.uiDC.Assets.scrollBarArrowUp = ui.R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + uiInfo.uiDC.Assets.scrollBarArrowLeft = ui.R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + uiInfo.uiDC.Assets.scrollBarArrowRight = ui.R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + uiInfo.uiDC.Assets.scrollBarThumb = ui.R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + + uiInfo.uiDC.Assets.sliderBar = ui.R_RegisterShaderNoMip( "gfx/menus/newFront/slider" ); +// uiInfo.uiDC.Assets.sliderThumb = ui.R_RegisterShaderNoMip( "menu/new/sliderthumb"); + + + /* + for( n = 0; n < NUM_CROSSHAIRS; n++ ) + { + uiInfo.uiDC.Assets.crosshairShader[n] = ui.R_RegisterShaderNoMip( va("gfx/2d/crosshair%c", 'a' + n ) ); + } + */ +} + +/* +================ +_UI_DrawSides +================= +*/ +void _UI_DrawSides(float x, float y, float w, float h, float size) +{ + size *= uiInfo.uiDC.xscale; + trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} + +/* +================ +_UI_DrawTopBottom +================= +*/ +void _UI_DrawTopBottom(float x, float y, float w, float h, float size) +{ + size *= uiInfo.uiDC.yscale; + trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void _UI_DrawRect( float x, float y, float width, float height, float size, const float *color ) +{ + trap_R_SetColor( color ); + + _UI_DrawTopBottom(x, y, width, height, size); + _UI_DrawSides(x, y, width, height, size); + + trap_R_SetColor( NULL ); +} + +/* +================= +UI_UpdateCvars +================= +*/ +void UI_UpdateCvars( void ) +{ + int i; + cvarTable_t *cv; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) + { + Cvar_Update( cv->vmCvar ); + } +} + +/* +================= +UI_DrawEffects +================= +*/ +static void UI_DrawEffects(rectDef_t *rect, float scale, vec4_t color) +{ + UI_DrawHandlePic( rect->x, rect->y - 14, 128, 8, 0/*uiInfo.uiDC.Assets.fxBasePic*/ ); + UI_DrawHandlePic( rect->x + uiInfo.effectsColor * 16 + 8, rect->y - 16, 16, 12, 0/*uiInfo.uiDC.Assets.fxPic[uiInfo.effectsColor]*/ ); +} + +/* +================= +UI_Version +================= +*/ +static void UI_Version(rectDef_t *rect, float scale, vec4_t color, int iFontIndex) +{ + int width; + + width = DC->textWidth(Q3_VERSION, scale, 0); + + DC->drawText(rect->x - width, rect->y, scale, color, Q3_VERSION, 0, ITEM_TEXTSTYLE_SHADOWED, iFontIndex); +} + +/* +================= +UI_DrawKeyBindStatus +================= +*/ +static void UI_DrawKeyBindStatus(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iFontIndex) +{ + if (Display_KeyBindPending()) + { + Text_Paint(rect->x, rect->y, scale, color, SE_GetString("MENUS_WAITINGFORKEY"), 0, textStyle, iFontIndex); + } + else + { +// Text_Paint(rect->x, rect->y, scale, color, ui.SP_GetStringTextString("MENUS_ENTERTOCHANGE"), 0, textStyle, iFontIndex); + } +} + +/* +================= +UI_DrawKeyBindStatus +================= +*/ +static void UI_DrawGLInfo(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iFontIndex) +{ +#define MAX_LINES 64 + char buff[4096]; + char * eptr = buff; + const char *lines[MAX_LINES]; + int y, numLines=0, i=0; + + y = rect->y; + Text_Paint(rect->x, y, scale, color, va("GL_VENDOR: %s",uiInfo.uiDC.glconfig.vendor_string), rect->w, textStyle, iFontIndex); + y += 15; + Text_Paint(rect->x, y, scale, color, va("GL_VERSION: %s: %s", uiInfo.uiDC.glconfig.version_string,uiInfo.uiDC.glconfig.renderer_string), rect->w, textStyle, iFontIndex); + y += 15; + Text_Paint(rect->x, y, scale, color, "GL_PIXELFORMAT:", rect->w, textStyle, iFontIndex); + y += 15; + Text_Paint(rect->x, y, scale, color, va ("Color(%d-bits) Z(%d-bits) stencil(%d-bits)",uiInfo.uiDC.glconfig.colorBits, uiInfo.uiDC.glconfig.depthBits, uiInfo.uiDC.glconfig.stencilBits), rect->w, textStyle, iFontIndex); + y += 15; + // build null terminated extension strings + Q_strncpyz(buff, uiInfo.uiDC.glconfig.extensions_string, sizeof(buff)); + int testy=y-16; + while ( testy <= rect->y + rect->h && *eptr && (numLines < MAX_LINES) ) + { + while ( *eptr && *eptr == ' ' ) + *eptr++ = '\0'; + + // track start of valid string + if (*eptr && *eptr != ' ') + { + lines[numLines++] = eptr; + testy+=16; + } + + while ( *eptr && *eptr != ' ' ) + eptr++; + } + + numLines--; + while (i < numLines) + { + Text_Paint(rect->x, y, scale, color, lines[i++], rect->w, textStyle, iFontIndex); + y += 16; + } +} + +/* +================= +UI_DataPad_Inventory +================= +*/ +/* +static void UI_DataPad_Inventory(rectDef_t *rect, float scale, vec4_t color, int iFontIndex) +{ + Text_Paint(rect->x, rect->y, scale, color, "INVENTORY", 0, 1, iFontIndex); +} +*/ +/* +================= +UI_DataPad_ForcePowers +================= +*/ +/* +static void UI_DataPad_ForcePowers(rectDef_t *rect, float scale, vec4_t color, int iFontIndex) +{ + Text_Paint(rect->x, rect->y, scale, color, "FORCE POWERS", 0, 1, iFontIndex); +} +*/ + +static void UI_DrawCrosshair(rectDef_t *rect, float scale, vec4_t color) { + trap_R_SetColor( color ); + if (uiInfo.currentCrosshair < 0 || uiInfo.currentCrosshair >= NUM_CROSSHAIRS) { + uiInfo.currentCrosshair = 0; + } + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.uiDC.Assets.crosshairShader[uiInfo.currentCrosshair]); + trap_R_SetColor( NULL ); +} + +/* +================= +UI_SoftKeyboard +================= +*/ +static void UI_SoftKeyboardInit() +{ + char strtmp[] = ""; + DC->setCVar("keyboardinput", strtmp); + + skb.activeKey=0; + skb.curCol=0; + skb.curRow=0; + skb.curStringPos=0; +} + +static void UI_SoftKeyboardDelete() +{ + char strtmp[SKB_STRING_LENGTH+1]; + DC->getCVarString("keyboardinput", strtmp, SKB_STRING_LENGTH+1); + if(skb.curStringPos > 0) + { + skb.curStringPos--; + strtmp[skb.curStringPos] = 0; + } + DC->setCVar("keyboardinput", strtmp); +} + +static void UI_SoftKeyboardAccept() +{ + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item = Menu_FindItemByName(menu, SKB_ACCEPT_NAME); + if (menu->onAccept) + { + Item_RunScript(item, menu->onAccept); + } +} + +static qboolean UI_SoftKeyboardDelete_HandleKey(int flags, float *special, int key) +{ + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item; + switch(key) + { + case A_CURSOR_UP: + skb.curRow = SKB_NUM_ROWS - 1; + if((skb.curRow * SKB_NUM_COLS + skb.curCol) >= SKB_NUM_LETTERS) + skb.curRow--; + item = Menu_FindItemByName(menu, SKB_KEYBOARD_NAME); + Item_SetFocus(item, 0, 0); + break; + case A_CURSOR_DOWN: + skb.curRow = 0; + item = Menu_FindItemByName(menu, SKB_KEYBOARD_NAME); + Item_SetFocus(item, 0, 0); + break; + case A_CURSOR_LEFT: + skb.curCol = SKB_NUM_COLS/2; + item = Menu_FindItemByName(menu, SKB_ACCEPT_NAME); + Item_SetFocus(item, 0, 0); + break; + case A_CURSOR_RIGHT: + skb.curCol = SKB_NUM_COLS/2; + item = Menu_FindItemByName(menu, SKB_ACCEPT_NAME); + Item_SetFocus(item, 0, 0); + break; + case A_MOUSE1: + UI_SoftKeyboardDelete(); + break; + default: + break; + } + skb.activeKey = skb.curRow * SKB_NUM_COLS + skb.curCol; + return qtrue; +} + +static qboolean UI_SoftKeyboardAccept_HandleKey(int flags, float *special, int key) +{ + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item; + switch(key) + { + case A_CURSOR_UP: + skb.curRow = SKB_NUM_ROWS - 1; + if((skb.curRow * SKB_NUM_COLS + skb.curCol) >= SKB_NUM_LETTERS) + skb.curRow--; + item = Menu_FindItemByName(menu, SKB_KEYBOARD_NAME); + Item_SetFocus(item, 0, 0); + break; + case A_CURSOR_DOWN: + skb.curRow = 0; + item = Menu_FindItemByName(menu, SKB_KEYBOARD_NAME); + Item_SetFocus(item, 0, 0); + break; + case A_CURSOR_LEFT: + skb.curCol = SKB_NUM_COLS/2-1; + item = Menu_FindItemByName(menu, SKB_DELETE_NAME); + Item_SetFocus(item, 0, 0); + break; + case A_CURSOR_RIGHT: + skb.curCol = SKB_NUM_COLS/2-1; + item = Menu_FindItemByName(menu, SKB_DELETE_NAME); + Item_SetFocus(item, 0, 0); + break; + case A_MOUSE1: + UI_SoftKeyboardAccept(); + break; + default: + break; + } + skb.activeKey = skb.curRow * SKB_NUM_COLS + skb.curCol; + return qtrue; +} + + +static qboolean UI_SoftKeyboard_HandleKey(int flags, float *special, int key) +{ + char strtmp[SKB_STRING_LENGTH+1]; + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item; + +// If the user pressed A (mouse 1), just add a letter to our string and return + if(key == A_MOUSE1) + { + DC->getCVarString("keyboardinput", strtmp, SKB_STRING_LENGTH+1); + if(skb.curStringPos < SKB_STRING_LENGTH) + { + strtmp[skb.curStringPos] = letters[skb.activeKey][0]; + skb.curStringPos++; + } + DC->setCVar("keyboardinput", strtmp); + return qtrue; + } + +// Assuming the user pressed the D-pad, adjust the current row and column, +// and the associated active key position. + switch(key) + { + case A_CURSOR_UP: + skb.curRow-=1; + break; + case A_CURSOR_DOWN: + skb.curRow+=1; + break; + case A_CURSOR_LEFT: + skb.curCol-=1; + break; + case A_CURSOR_RIGHT: + skb.curCol+=1; + break; + default: + // We didn't handle this keypress. + return qfalse; + break; + } + skb.activeKey = skb.curRow * SKB_NUM_COLS + skb.curCol; + +// Now make sure that the new active key is actually on the keyboard +// This means that the row and columns must be within bounds, and we +// must be on a letter (not on an empty space) + if(skb.activeKey < 0 || skb.activeKey >=SKB_NUM_LETTERS || skb.curCol >= SKB_NUM_COLS || skb.curCol < 0) + { + switch(key) + { + case A_CURSOR_UP: + // Wrap to bottom of KB + skb.curRow = SKB_NUM_ROWS - 1; + if(skb.curCol < SKB_NUM_COLS/2) + item = Menu_FindItemByName(menu, SKB_DELETE_NAME); + else + item = Menu_FindItemByName(menu, SKB_ACCEPT_NAME); + Item_SetFocus(item, 0, 0); + break; + + case A_CURSOR_DOWN: + // Move to the next item below this + skb.curRow--; + if(skb.curCol < SKB_NUM_COLS/2) + item = Menu_FindItemByName(menu, SKB_DELETE_NAME); + else + item = Menu_FindItemByName(menu, SKB_ACCEPT_NAME); + Item_SetFocus(item, 0, 0); + break; + + case A_CURSOR_LEFT: + // Wrap to the right side of the KB + if(skb.curRow == SKB_NUM_ROWS-1) + skb.curCol = SKB_NUM_LETTERS % SKB_NUM_COLS - 1; + else + skb.curCol = SKB_NUM_COLS - 1; + break; + + case A_CURSOR_RIGHT: + // Wrap to the left side of the KB + skb.curCol=0; + break; + default: + break; + } + skb.activeKey = skb.curRow * SKB_NUM_COLS + skb.curCol; + } + + return qtrue; +} + + + +vec4_t color_unfocus = {0.7f, 0.7f, 0.8f, 1.0f}; +vec4_t color_focus = {0.78f, 0.471f, 0.161f, 1.0f}; + +static void UI_SoftKeyboardAccept_Draw() +{ + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item = Menu_FindItemByName(menu, SKB_ACCEPT_NAME); + int x = SKB_OK_X; + int y = SKB_OK_Y; + x -= DC->textWidth("OK", 1.0f, 2) / 2; + y -= DC->textHeight("OK", 1.0f, 2) / 2; + if(item->window.flags & WINDOW_HASFOCUS) + DC->drawText(x, y, 1.0f, color_focus, "OK", 1000, 0, 2); + else + DC->drawText(x, y, 1.0f, color_unfocus, "OK", 1000, 0, 2); +} + +static void UI_SoftKeyboardDelete_Draw() +{ + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item = Menu_FindItemByName(menu, SKB_DELETE_NAME); + int x = SKB_BACKSPACE_X; + int y = SKB_BACKSPACE_Y; + x -= DC->textWidth("Backspace", 1.0f, 2) / 2; + y -= DC->textHeight("Backspace", 1.0f, 2) / 2; + if(item->window.flags & WINDOW_HASFOCUS) + DC->drawText(x, y, 1.0f, color_focus, "Backspace", 1000, 0, 2); + else + DC->drawText(x, y, 1.0f, color_unfocus, "Backspace", 1000, 0, 2); +} + +static void UI_SoftKeyboard_Draw() +{ + menuDef_t *menu = Menu_GetFocused(); + itemDef_t *item = Menu_FindItemByName(menu, SKB_KEYBOARD_NAME); + +//draw each letter on the screen at the appropriate coordinates + int x,y; + float size; + vec4_t *color; + for(int cl=0; clwindow.flags & WINDOW_HASFOCUS)) + { + color = &color_focus; + size = 3.0f; + } + else + { + color = &color_unfocus; + size = 1.0f; + } + + x = (cl%SKB_NUM_COLS) * SKB_SPACE_H + SKB_LEFT; + x -= (DC->textWidth(letters[cl], size, 2)) / 2; + y = (cl/SKB_NUM_COLS) * SKB_SPACE_V + SKB_TOP; + y -= ((DC->textHeight(letters[cl], size, 2)) / 2) * 1.5; + + DC->drawText(x, y, size, *color, letters[cl], 1000, 0, 2); + } + + char *strtmp = new char[SKB_STRING_LENGTH]; + DC->getCVarString("keyboardinput", strtmp, SKB_STRING_LENGTH+1); + DC->drawText(SKB_STRING_LEFT, SKB_STRING_TOP, 1.5f, color_unfocus, strtmp, 1000, 0, 2); +} + + + +/* +================= +UI_OwnerDraw +================= +*/ +static void UI_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle, int iFontIndex) +{ + rectDef_t rect; + + rect.x = x + text_x; + rect.y = y + text_y; + rect.w = w; + rect.h = h; + + switch (ownerDraw) + { + case UI_SOFT_KEYBOARD: + UI_SoftKeyboard_Draw(); + break; + case UI_SOFT_KEYBOARD_ACCEPT: + UI_SoftKeyboardAccept_Draw(); + break; + case UI_SOFT_KEYBOARD_DELETE: + UI_SoftKeyboardDelete_Draw(); + break; + case UI_EFFECTS: + UI_DrawEffects(&rect, scale, color); + break; + case UI_VERSION: + UI_Version(&rect, scale, color, iFontIndex); + break; + + case UI_DATAPAD_MISSION: + ui.Draw_DataPad(DP_OBJECTIVES); + break; + + case UI_DATAPAD_WEAPONS: + ui.Draw_DataPad(DP_WEAPONS); + break; + +/* + case UI_DATAPAD_INVENTORY: + ui.Draw_DataPad(DP_HUD); + ui.Draw_DataPad(DP_INVENTORY); + break; +*/ + + case UI_DATAPAD_FORCEPOWERS: + ui.Draw_DataPad(DP_FORCEPOWERS); + break; + + case UI_ALLMAPS_SELECTION://saved game thumbnail + +//JLF MAPIMAGE MPNOTUSED +#ifdef _XBOX + //create a shader + UI_DrawHandlePic(x, y, w, h, shader); + +#else + int levelshot; + levelshot = ui.R_RegisterShaderNoMip( va( "levelshots/%s", s_savedata[s_savegame.currentLine].currentSaveFileMap ) ); + if (levelshot) + { + ui.R_DrawStretchPic( x, y, w, h, 0, 0, 1, 1, levelshot ); + } + else + { + UI_DrawHandlePic(x, y, w, h, uis.menuBackShader); + } +#endif +/* + ui.R_Font_DrawString( x, // int ox + y+h, // int oy + s_savedata[s_savegame.currentLine].currentSaveFileMap, // const char *text + color, // paletteRGBA_c c + iFontIndex, // const int iFontHandle + w,//-1, // iMaxPixelWidth (-1 = none) + scale // const float scale = 1.0f + ); +*/ + break; + case UI_PREVIEWCINEMATIC: + // FIXME BOB - make this work? +// UI_DrawPreviewCinematic(&rect, scale, color); + break; + case UI_CROSSHAIR: + UI_DrawCrosshair(&rect, scale, color); + break; + case UI_GLINFO: + UI_DrawGLInfo(&rect,scale, color, textStyle, iFontIndex); + break; + case UI_KEYBINDSTATUS: + UI_DrawKeyBindStatus(&rect,scale, color, textStyle, iFontIndex); + break; + default: + break; + } + +} + +/* +================= +UI_OwnerDrawVisible +================= +*/ +static qboolean UI_OwnerDrawVisible(int flags) +{ + qboolean vis = qtrue; + + while (flags) + { +/* if (flags & UI_SHOW_DEMOAVAILABLE) + { + if (!uiInfo.demoAvailable) + { + vis = qfalse; + } + flags &= ~UI_SHOW_DEMOAVAILABLE; + } + else +*/ { + flags = 0; + } + } + return vis; +} + +/* +================= +Text_Width +================= +*/ +int Text_Width(const char *text, float scale, int iFontIndex) +{ + // temp code until Bob retro-fits all menus to have font specifiers... + // + if ( iFontIndex == 0 ) + { + iFontIndex = uiInfo.uiDC.Assets.qhMediumFont; + } + return ui.R_Font_StrLenPixels(text, iFontIndex, scale); +} + +/* +================= +UI_OwnerDrawWidth +================= +*/ +int UI_OwnerDrawWidth(int ownerDraw, float scale) +{ +// int i, h, value; +// const char *text; + const char *s = NULL; + + + switch (ownerDraw) + { + case UI_KEYBINDSTATUS: + if (Display_KeyBindPending()) + { + s = SE_GetString("MENUS_WAITINGFORKEY"); + } + else + { +// s = ui.SP_GetStringTextString("MENUS_ENTERTOCHANGE"); + } + break; + + // FIXME BOB +// case UI_SERVERREFRESHDATE: +// s = UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer)); +// break; + default: + break; + } + + if (s) + { + return Text_Width(s, scale, 0); + } + return 0; +} + +/* +================= +Text_Height +================= +*/ +int Text_Height(const char *text, float scale, int iFontIndex) +{ + // temp until Bob retro-fits all menu files with font specifiers... + // + if ( iFontIndex == 0 ) + { + iFontIndex = uiInfo.uiDC.Assets.qhMediumFont; + } + return ui.R_Font_HeightPixels(iFontIndex, scale); +} + + +/* +================= +UI_MouseEvent +================= +*/ +//JLFMOUSE CALLED EACH FRAME IN UI +void _UI_MouseEvent( int dx, int dy ) +{ + // update mouse screen position + uiInfo.uiDC.cursorx += dx; + if (uiInfo.uiDC.cursorx < 0) + { + uiInfo.uiDC.cursorx = 0; + } + else if (uiInfo.uiDC.cursorx > SCREEN_WIDTH) + { + uiInfo.uiDC.cursorx = SCREEN_WIDTH; + } + + uiInfo.uiDC.cursory += dy; + if (uiInfo.uiDC.cursory < 0) + { + uiInfo.uiDC.cursory = 0; + } + else if (uiInfo.uiDC.cursory > SCREEN_HEIGHT) + { + uiInfo.uiDC.cursory = SCREEN_HEIGHT; + } + + if ( dy > SCROLL_SENSITIVITY || dy < -SCROLL_SENSITIVITY) + gScrollAccum += dy; + gScrollDelta =0; + + if (gScrollAccum > TEXTSCROLLDESCRETESTEP) + { + gScrollDelta =1; + gScrollAccum =0; + } + else if (gScrollAccum <0) + { + gScrollDelta = -1; + gScrollAccum = TEXTSCROLLDESCRETESTEP; + } + + + if (Menu_Count() > 0) + { + //menuDef_t *menu = Menu_GetFocused(); + //Menu_HandleMouseMove(menu, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory); + Display_MouseMove(NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory); + } + +} + +/* +================= +UI_KeyEvent +================= +*/ +void _UI_KeyEvent( int key, qboolean down ) +{ +/* extern qboolean SwallowBadNumLockedKPKey( int iKey ); + if (SwallowBadNumLockedKPKey(key)){ + return; + } +*/ + + if (Menu_Count() > 0) + { + menuDef_t *menu = Menu_GetFocused(); + if (menu) + { + //DemoEnd(); +//JLF MPMOVED +#ifdef _XBOX +// extern void G_DemoKeypress();//JLF new +// G_DemoKeypress(); //JLF new + extern void UpdateDemoTimer(); + UpdateDemoTimer(); + +#endif + if (key == A_ESCAPE && down && !Menus_AnyFullScreenVisible() && !(menu->window.flags & WINDOW_IGNORE_ESCAPE)) + { + Menus_CloseAll(); + } + else + { + Menu_HandleKey(menu, key, down ); + } + } + else + { + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + Cvar_Set( "cl_paused", "0" ); + } + } +} + +/* +================= +UI_Report +================= +*/ +void UI_Report(void) +{ + String_Report(); +} + + + +/* +================= +UI_DataPadMenu +================= +*/ +void UI_DataPadMenu(void) +{ + int newForcePower,newObjective; + + Menus_CloseByName("mainhud"); + + newForcePower = (int)trap_Cvar_VariableValue("cg_updatedDataPadForcePower1"); + newObjective = (int)trap_Cvar_VariableValue("cg_updatedDataPadObjective"); + + if (newForcePower) + { + Menus_ActivateByName("datapadForcePowersMenu"); + } + else if (newObjective) + { + Menus_ActivateByName("datapadMissionMenu"); + } + else + { + Menus_ActivateByName("datapadMissionMenu"); + } + ui.Key_SetCatcher( KEYCATCH_UI ); + +} + +/* +================= +UI_InGameMenu +================= +*/ +extern void S_StopAllSoundsExceptMusic(void); +void UI_InGameMenu(const char*menuID) +{ + // don't allow this if you are dead + if( menuID && !strcmp(menuID, "noController")) + { + // do nothing + } + else if ( svs.clients[0].frames[svs.clients[0].netchan.outgoingSequence & PACKET_MASK].ps.stats[STAT_HEALTH] <= 0) + { + ui.Cvar_Set( "cl_paused", "0" ); + return; + } + +#ifdef _XBOX + ui.PrecacheScreenshot(); +#endif + + Menus_CloseByName("mainhud"); + + if (menuID) + { + + Menus_ActivateByName(menuID); + } + else + { + S_StopAllSoundsExceptMusic(); + Menus_ActivateByName("ingameMainMenu"); + } + ui.Key_SetCatcher( KEYCATCH_UI ); +} + +qboolean _UI_IsFullscreen( void ) +{ + return Menus_AnyFullScreenVisible(); +} + +/* +======================================================================= + +MAIN MENU + +======================================================================= +*/ + + +/* +=============== +UI_MainMenu + +The main menu only comes up when not in a game, +so make sure that the attract loop server is down +and that local cinematics are killed +=============== +*/ +void UI_MainMenu(void) +{ + char buf[256]; + ui.Cvar_Set("sv_killserver", "1"); // let the demo server know it should shut down + + ui.Key_SetCatcher( KEYCATCH_UI ); + + menuDef_t *m = Menus_ActivateByName("mainMenu"); + if (!m) + { //wha? try again + UI_LoadMenus("ui/menus.txt",qfalse); + } + ui.Cvar_VariableStringBuffer("com_errorMessage", buf, sizeof(buf)); + if (strlen(buf)) { + Menus_ActivateByName("error_popmenu"); + } +} + + +/* +================= +Menu_Cache +================= +*/ +void Menu_Cache( void ) +{ +// uis.cursor = ui.R_RegisterShaderNoMip( "menu/new/crosshairb"); + // Common menu graphics + uis.whiteShader = ui.R_RegisterShader( "white" ); +// uis.menuBackShader = ui.R_RegisterShaderNoMip( "menu/art/unknownmap" ); +} + +/* +================= +UI_UpdateVideoSetup + +Copies the temporary user interface version of the video cvars into +their real counterparts. This is to create a interface which allows +you to discard your changes if you did something you didnt want +================= +*/ +void UI_UpdateVideoSetup ( void ) +{ + Cvar_Set ( "r_mode", Cvar_VariableString ( "ui_r_mode" ) ); + Cvar_Set ( "r_fullscreen", Cvar_VariableString ( "ui_r_fullscreen" ) ); + Cvar_Set ( "r_colorbits", Cvar_VariableString ( "ui_r_colorbits" ) ); + Cvar_Set ( "r_lodbias", Cvar_VariableString ( "ui_r_lodbias" ) ); + Cvar_Set ( "r_picmip", Cvar_VariableString ( "ui_r_picmip" ) ); + Cvar_Set ( "r_texturebits", Cvar_VariableString ( "ui_r_texturebits" ) ); + Cvar_Set ( "r_texturemode", Cvar_VariableString ( "ui_r_texturemode" ) ); + Cvar_Set ( "r_detailtextures", Cvar_VariableString ( "ui_r_detailtextures" ) ); + Cvar_Set ( "r_ext_compress_textures", Cvar_VariableString ( "ui_r_ext_compress_textures" ) ); + Cvar_Set ( "r_depthbits", Cvar_VariableString ( "ui_r_depthbits" ) ); + Cvar_Set ( "r_subdivisions", Cvar_VariableString ( "ui_r_subdivisions" ) ); + Cvar_Set ( "r_fastSky", Cvar_VariableString ( "ui_r_fastSky" ) ); + Cvar_Set ( "r_inGameVideo", Cvar_VariableString ( "ui_r_inGameVideo" ) ); + Cvar_Set ( "r_allowExtensions", Cvar_VariableString ( "ui_r_allowExtensions" ) ); +// Cvar_Set ( "cg_shadows", Cvar_VariableString ( "ui_cg_shadows" ) ); + Cvar_Set ( "ui_r_modified", "0" ); + + Cbuf_ExecuteText( EXEC_APPEND, "vid_restart;" ); +} + +/* +================= +UI_GetVideoSetup + +Retrieves the current actual video settings into the temporary user +interface versions of the cvars. +================= +*/ +void UI_GetVideoSetup ( void ) +{ + // Make sure the cvars are registered as read only. + Cvar_Register ( NULL, "ui_r_glCustom", "4", CVAR_ROM|CVAR_ARCHIVE ); + + Cvar_Register ( NULL, "ui_r_mode", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_fullscreen", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_colorbits", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_lodbias", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_picmip", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_texturebits", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_texturemode", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_detailtextures", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_ext_compress_textures", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_depthbits", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_subdivisions", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_fastSky", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_inGameVideo", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_allowExtensions", "0", CVAR_ROM ); +// Cvar_Register ( NULL, "ui_cg_shadows", "0", CVAR_ROM ); + Cvar_Register ( NULL, "ui_r_modified", "0", CVAR_ROM ); + + // Copy over the real video cvars into their temporary counterparts + Cvar_Set ( "ui_r_mode", Cvar_VariableString ( "r_mode" ) ); + Cvar_Set ( "ui_r_colorbits", Cvar_VariableString ( "r_colorbits" ) ); + Cvar_Set ( "ui_r_fullscreen", Cvar_VariableString ( "r_fullscreen" ) ); + Cvar_Set ( "ui_r_lodbias", Cvar_VariableString ( "r_lodbias" ) ); + Cvar_Set ( "ui_r_picmip", Cvar_VariableString ( "r_picmip" ) ); + Cvar_Set ( "ui_r_texturebits", Cvar_VariableString ( "r_texturebits" ) ); + Cvar_Set ( "ui_r_texturemode", Cvar_VariableString ( "r_texturemode" ) ); + Cvar_Set ( "ui_r_detailtextures", Cvar_VariableString ( "r_detailtextures" ) ); + Cvar_Set ( "ui_r_ext_compress_textures", Cvar_VariableString ( "r_ext_compress_textures" ) ); + Cvar_Set ( "ui_r_depthbits", Cvar_VariableString ( "r_depthbits" ) ); + Cvar_Set ( "ui_r_subdivisions", Cvar_VariableString ( "r_subdivisions" ) ); + Cvar_Set ( "ui_r_fastSky", Cvar_VariableString ( "r_fastSky" ) ); + Cvar_Set ( "ui_r_inGameVideo", Cvar_VariableString ( "r_inGameVideo" ) ); + Cvar_Set ( "ui_r_allowExtensions", Cvar_VariableString ( "r_allowExtensions" ) ); +// Cvar_Set ( "ui_cg_shadows", Cvar_VariableString ( "cg_shadows" ) ); + Cvar_Set ( "ui_r_modified", "0" ); +} + +static void UI_SetSexandSoundForModel(const char* char_model) +{ + int f,i; + char soundpath[MAX_QPATH]; + qboolean isFemale = qfalse; + + i = ui.FS_FOpenFile(va("models/players/%s/sounds.cfg", char_model), &f, FS_READ); + if ( !f ) + {//no? oh bother. + Cvar_Reset("snd"); + Cvar_Reset("sex"); + return; + } + + soundpath[0] = 0; + + ui.FS_Read(&soundpath, i, f); + + while (i >= 0 && soundpath[i] != '\n') + { + if (soundpath[i] == 'f') + { + isFemale = qtrue; + soundpath[i] = 0; + } + + i--; + } + + i = 0; + + while (soundpath[i] && soundpath[i] != '\r' && soundpath[i] != '\n') + { + i++; + } + soundpath[i] = 0; + + ui.FS_FCloseFile(f); + + Cvar_Set ( "snd", soundpath); + if (isFemale) + { + Cvar_Set ( "sex", "f"); + } + else + { + Cvar_Set ( "sex", "m"); + } +} +static void UI_UpdateCharacterCvars ( void ) +{ + const char *char_model = Cvar_VariableString ( "ui_char_model" ); + UI_SetSexandSoundForModel(char_model); + Cvar_Set ( "g_char_model", char_model ); + Cvar_Set ( "g_char_skin_head", Cvar_VariableString ( "ui_char_skin_head" ) ); + Cvar_Set ( "g_char_skin_torso", Cvar_VariableString ( "ui_char_skin_torso" ) ); + Cvar_Set ( "g_char_skin_legs", Cvar_VariableString ( "ui_char_skin_legs" ) ); + Cvar_Set ( "g_char_color_red", Cvar_VariableString ( "ui_char_color_red" ) ); + Cvar_Set ( "g_char_color_green", Cvar_VariableString ( "ui_char_color_green" ) ); + Cvar_Set ( "g_char_color_blue", Cvar_VariableString ( "ui_char_color_blue" ) ); +} + +static void UI_GetCharacterCvars ( void ) +{ + Cvar_Set ( "ui_char_skin_head", Cvar_VariableString ( "g_char_skin_head" ) ); + Cvar_Set ( "ui_char_skin_torso", Cvar_VariableString ( "g_char_skin_torso" ) ); + Cvar_Set ( "ui_char_skin_legs", Cvar_VariableString ( "g_char_skin_legs" ) ); + Cvar_Set ( "ui_char_color_red", Cvar_VariableString ( "g_char_color_red" ) ); + Cvar_Set ( "ui_char_color_green", Cvar_VariableString ( "g_char_color_green" ) ); + Cvar_Set ( "ui_char_color_blue", Cvar_VariableString ( "g_char_color_blue" ) ); + + char* model = Cvar_VariableString ( "g_char_model" ); + Cvar_Set ( "ui_char_model", model ); + + for (int i = 0; i < uiInfo.playerSpeciesCount; i++) + { + if ( !stricmp(model, uiInfo.playerSpecies[i].Name) ) + { + uiInfo.playerSpeciesIndex = i; + } + } +} + +static void UI_UpdateSaberCvars ( void ) +{ + Cvar_Set ( "g_saber_type", Cvar_VariableString ( "ui_saber_type" ) ); + Cvar_Set ( "g_saber", Cvar_VariableString ( "ui_saber" ) ); + Cvar_Set ( "g_saber2", Cvar_VariableString ( "ui_saber2" ) ); + Cvar_Set ( "g_saber_color", Cvar_VariableString ( "ui_saber_color" ) ); + Cvar_Set ( "g_saber2_color", Cvar_VariableString ( "ui_saber2_color" ) ); +} + +static void UI_UpdateFightingStyleChoices ( void ) +{ + // + if (!strcmpi("staff",Cvar_VariableString ( "ui_saber_type" ))) + { + Cvar_Set ( "ui_fightingstylesallowed", "0" ); + Cvar_Set ( "ui_newfightingstyle", "1" ); // Default, MEDIUM + } + else if (!strcmpi("dual",Cvar_VariableString ( "ui_saber_type" ))) + { + Cvar_Set ( "ui_fightingstylesallowed", "0" ); + Cvar_Set ( "ui_newfightingstyle", "1" ); // Default, MEDIUM + } + else + { + // Get player state + client_t *cl = &svs.clients[0]; // 0 because only ever us as a player + playerState_t *pState; + + if (cl && cl->gentity && cl->gentity->client) + { + pState = cl->gentity->client; + + + // Knows Fast style? + if (pState->saberStylesKnown & (1<saberStylesKnown & (1<saberStylesKnown & (1<saberStylesKnown & (1<saberStylesKnown & (1<saberStylesKnown & (1<saberAnimLevel == SS_FAST) + { + Cvar_Set ( "ui_currentfightingstyle", "0" ); // FAST + } + else if (pState->saberAnimLevel == SS_STRONG) + { + Cvar_Set ( "ui_currentfightingstyle", "2" ); // STRONG + } + else + { + Cvar_Set ( "ui_currentfightingstyle", "1" ); // default MEDIUM + } + } + else // No client so this must be first time + { + Cvar_Set ( "ui_currentfightingstyle", "1" ); // Default to MEDIUM + Cvar_Set ( "ui_fightingstylesallowed", "0" ); // Default to no new styles allowed + Cvar_Set ( "ui_newfightingstyle", "1" ); // MEDIUM + } + } +} + +#define MAX_POWER_ENUMS 16 + +typedef struct { + char *title; + short powerEnum; +} powerEnum_t; + +static powerEnum_t powerEnums[MAX_POWER_ENUMS] = +{ +"absorb", FP_ABSORB, +"heal", FP_HEAL, +"mindtrick", FP_TELEPATHY, +"protect", FP_PROTECT, + + // Core powers +"jump", FP_LEVITATION, +"pull", FP_PULL, +"push", FP_PUSH, +"sense", FP_SEE, +"speed", FP_SPEED, +"sabdef", FP_SABER_DEFENSE, +"saboff", FP_SABER_OFFENSE, +"sabthrow", FP_SABERTHROW, + + // Dark powers +"drain", FP_DRAIN, +"grip", FP_GRIP, +"lightning", FP_LIGHTNING, +"rage", FP_RAGE +}; + + +// Find the index to the Force Power in powerEnum array +static qboolean UI_GetForcePowerIndex ( const char *forceName, short *forcePowerI ) +{ + int i; + + // Find a match for the forceName passed in + for (i=0;igentity->client; +#endif + + char itemName[128]; + Com_sprintf (itemName, sizeof(itemName), "%s_hexpic", powerEnums[forcePowerI].title); + item = (itemDef_s *) Menu_FindItemByName(menu, itemName); + + if (item) + { + char itemGraphic[128]; + Com_sprintf (itemGraphic, sizeof(itemGraphic), "gfx/menus/hex_pattern_%d", +#ifdef XBOX_DEMO + demoForcePowerLevel[powerEnums[forcePowerI].powerEnum]); +#else + pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum]); +#endif + item->window.background = ui.R_RegisterShaderNoMip(itemGraphic); + + // If maxed out on power - don't allow update +/* if (pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum]>=3) + { + Com_sprintf (itemName, sizeof(itemName), "%s_fbutton", powerEnums[forcePowerI].title); + item = (itemDef_s *) Menu_FindItemByName(menu, itemName); + if (item) // This is okay, because core powers don't have a hex button + { + item->window.flags &= ~WINDOW_VISIBLE; + } + } */ + } + + // Set weapons button to inactive + if( Q_stricmp(menu->window.name, "ingameForceStatus") != 0 ) + UI_ForcePowerWeaponsButton(qfalse); +} + +// Flip flop between being able to see the text showing the Force Point has or hasn't been allocated (Used by Force Power Allocation screen) +static void UI_SetPowerTitleText ( qboolean showAllocated ) +{ + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + if (showAllocated) + { + // Show the text saying the force point has been allocated + item = (itemDef_s *) Menu_FindItemByName(menu, "allocated_text"); + if (item) + { + item->window.flags |= WINDOW_VISIBLE; + } + + // Hide text saying the force point needs to be allocated + item = (itemDef_s *) Menu_FindItemByName(menu, "allocate_text"); + if (item) + { + item->window.flags &= ~WINDOW_VISIBLE; + } + } + else + { + // Hide the text saying the force point has been allocated + item = (itemDef_s *) Menu_FindItemByName(menu, "allocated_text"); + if (item) + { + item->window.flags &= ~WINDOW_VISIBLE; + } + + // Show text saying the force point needs to be allocated + item = (itemDef_s *) Menu_FindItemByName(menu, "allocate_text"); + if (item) + { + item->window.flags |= WINDOW_VISIBLE; + } + } +} + +//. Find weapons button and make active/inactive (Used by Force Power Allocation screen) +static void UI_ForcePowerWeaponsButton(qboolean activeFlag) +{ + menuDef_t *menu; + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + // Find weaponsbutton + itemDef_t *item; + item = (itemDef_s *) Menu_FindItemByName(menu, "weaponbutton"); + + if (item) + { + // Make it active + if (activeFlag) + { + item->window.flags |= WINDOW_VISIBLE; + Item_SetFocus(item, 0, 0); + } + else + { + item->window.flags &= ~WINDOW_VISIBLE; + } + } + + //UI_SetVis(menu, "tab_Force", !activeFlag); + + if(activeFlag) + DC->setCVar("ui_hideAcallout", "0"); + else + DC->setCVar("ui_hideAcallout", "1"); +} + +void UI_SetItemColor(itemDef_t *item,const char *itemname,const char *name,vec4_t color); + +static void UI_SetHexPicLevel( const menuDef_t *menu,const int forcePowerI,const int powerLevel, const qboolean goldFlag ) +{ + char itemName[128]; + itemDef_t *item; + + // Find proper hex picture on menu + Com_sprintf (itemName, sizeof(itemName), "%s_hexpic", powerEnums[forcePowerI].title); + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, itemName); + + // Now give it the proper hex graphic + if (item) + { + char itemGraphic[128]; + if (goldFlag) + { + Com_sprintf (itemGraphic, sizeof(itemGraphic), "gfx/menus/hex_pattern_%d_gold",powerLevel); + } + else + { + Com_sprintf (itemGraphic, sizeof(itemGraphic), "gfx/menus/hex_pattern_%d",powerLevel); + } + + item->window.background = ui.R_RegisterShaderNoMip(itemGraphic); +/* + Com_sprintf (itemName, sizeof(itemName), "%s_fbutton", powerEnums[forcePowerI].title); + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *)menu, itemName); + if (item) + { + if (goldFlag) + { + // Change description text to tell player they can decrement the force point + item->descText = "@MENUS_REMOVEFP"; + } + else + { + // Change description text to tell player they can increment the force point + item->descText = "@MENUS_ADDFP"; + } + } +*/ + } +} + +void UI_SetItemVisible(menuDef_t *menu,const char *itemname,qboolean visible); + +// if this is the first time into the force power allocation screen, show the INSTRUCTION screen +static void UI_ForceHelpActive( void ) +{ + int tier_storyinfo = Cvar_VariableIntegerValue( "tier_storyinfo" ); + + // First time, show instructions + if (tier_storyinfo==1) + { +// Menus_OpenByName("ingameForceHelp"); + Menus_ActivateByName("ingameForceHelp"); + } +} + + +// Shut down the help screen in the force power allocation screen +static void UI_ShutdownForceHelp( void ) +{ + int i; + char itemName[128]; + menuDef_t *menu; + itemDef_t *item; + vec4_t color = { 0.65f, 0.65f, 0.65f, 1.0f}; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + // Not in upgrade mode so turn on all the force buttons, the big forceicon and description text + if (uiInfo.forcePowerUpdated == FP_UPDATED_NONE) + { + // We just decremented a field so turn all buttons back on + // Make it so all buttons can be clicked + for (i=0;iwindow.flags |= WINDOW_HASFOCUS; + + if (item->onFocus) + { + Item_RunScript(item, item->onFocus); + } + + } + // In upgrade mode so just turn the deallocate button on + else + { + UI_SetItemVisible(menu,"force_icon",qtrue); + UI_SetItemVisible(menu,"force_desc",qtrue); + UI_SetItemVisible(menu,"deallocate_fbutton",qtrue); + + item = (itemDef_s *) Menu_FindItemByName(menu, va("%s_fbutton",powerEnums[uiInfo.forcePowerUpdated].title)); + if (item) + { + #ifdef _XBOX + Item_SetFocus(item, 0,0); + #else + item->window.flags |= WINDOW_HASFOCUS; + #endif + } + + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + playerState_t* pState = cl->gentity->client; + + + if (uiInfo.forcePowerUpdated == FP_UPDATED_NONE) + { + return; + } + + // Update level description + Com_sprintf ( + itemName, + sizeof(itemName), + "%s_level%ddesc", + powerEnums[uiInfo.forcePowerUpdated].title, + pState->forcePowerLevel[powerEnums[uiInfo.forcePowerUpdated].powerEnum] + ); + + item = (itemDef_s *) Menu_FindItemByName(menu, itemName); + if (item) + { + item->window.flags |= WINDOW_VISIBLE; + } + } + + // If one was a chosen force power, high light it again. + if (uiInfo.forcePowerUpdated>FP_UPDATED_NONE) + { + char itemhexName[128]; + char itemiconName[128]; + vec4_t color2 = { 1.0f, 1.0f, 1.0f, 1.0f}; + + Com_sprintf (itemhexName, sizeof(itemhexName), "%s_hexpic", powerEnums[uiInfo.forcePowerUpdated].title); + Com_sprintf (itemiconName, sizeof(itemiconName), "%s_iconpic", powerEnums[uiInfo.forcePowerUpdated].title); + + UI_SetItemColor(item,itemhexName,"forecolor",color2); + UI_SetItemColor(item,itemiconName,"forecolor",color2); + } + else + { + // Un-grey-out all icons + UI_SetItemColor(item,"hexpic","forecolor",color); + UI_SetItemColor(item,"iconpic","forecolor",color); + } +} + +// Decrement force power level (Used by Force Power Allocation screen) +static void UI_DecrementCurrentForcePower ( void ) +{ + menuDef_t *menu; + itemDef_t *item; + short i; + vec4_t color = { 0.65f, 0.65f, 0.65f, 1.0f}; + char itemName[128]; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + +#ifndef XBOX_DEMO + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + playerState_t* pState = cl->gentity->client; +#endif + + if (uiInfo.forcePowerUpdated == FP_UPDATED_NONE) + { + return; + } + + DC->startLocalSound(uiInfo.uiDC.Assets.forceUnchosenSound, CHAN_AUTO ); + +#ifdef XBOX_DEMO + if (demoForcePowerLevel[powerEnums[uiInfo.forcePowerUpdated].powerEnum]>0) + { + demoForcePowerLevel[powerEnums[uiInfo.forcePowerUpdated].powerEnum]--; // Decrement it + } + + UI_SetHexPicLevel( menu,uiInfo.forcePowerUpdated,demoForcePowerLevel[powerEnums[uiInfo.forcePowerUpdated].powerEnum],qfalse ); +#else + if (pState->forcePowerLevel[powerEnums[uiInfo.forcePowerUpdated].powerEnum]>0) + { + pState->forcePowerLevel[powerEnums[uiInfo.forcePowerUpdated].powerEnum]--; // Decrement it + // Turn off power if level is 0 + if (pState->forcePowerLevel[powerEnums[uiInfo.forcePowerUpdated].powerEnum]<1) + { + pState->forcePowersKnown &= ~( 1 << powerEnums[uiInfo.forcePowerUpdated].powerEnum ); + } + } + + UI_SetHexPicLevel( menu,uiInfo.forcePowerUpdated,pState->forcePowerLevel[powerEnums[uiInfo.forcePowerUpdated].powerEnum],qfalse ); +#endif + + UI_ShowForceLevelDesc ( powerEnums[uiInfo.forcePowerUpdated].title ); + + // We just decremented a field so turn all buttons back on + // Make it so all buttons can be clicked + for (i=0;iwindow.flags |= WINDOW_VISIBLE; + } + } + + // Show point has not been allocated + UI_SetPowerTitleText( qfalse); + + // Make weapons button active + UI_ForcePowerWeaponsButton(qfalse); + + // Hide the deallocate button + item = (itemDef_s *) Menu_FindItemByName(menu, "deallocate_fbutton"); + if (item) + { + item->window.flags &= ~WINDOW_VISIBLE; // + + // Un-grey-out all icons + UI_SetItemColor(item,"hexpic","forecolor",color); + UI_SetItemColor(item,"iconpic","forecolor",color); + } + + item = (itemDef_s *) Menu_FindItemByName(menu, va("%s_fbutton",powerEnums[uiInfo.forcePowerUpdated].title)); + if (item) + { +#ifdef _XBOX + Item_SetFocus(item, 0,0); +#else + item->window.flags |= WINDOW_HASFOCUS; +#endif + } + + uiInfo.forcePowerUpdated = FP_UPDATED_NONE; // It's as if nothing happened. +} + +void Item_MouseEnter(itemDef_t *item, float x, float y); + +static void UI_SetUpForceSelect( void ) +{ + menuDef_t* menu; + itemDef_t* item; + client_t* client; + playerState_t* player; + + // get a ptr to the force select menu + menu = Menu_GetFocused(); + if(!menu) + { + return; + } + +#ifdef XBOX_DEMO + Item_SetFocus(Menu_FindItemByName(menu, "absorb_fbutton"), 0, 0); + return; +#else + // get a ptr to the player + client = &svs.clients[0]; + if(!client) + { + return; + } + player = client->gentity->client; + + // look for the first force power in the force powers screen + // that doesn't have all three points filled + if(player->forcePowerLevel[FP_ABSORB] < 3) + { + item = Menu_FindItemByName(menu, "absorb_fbutton"); + if(item) + { + Item_SetFocus(item, 0, 0); + } + return; + } + + if(player->forcePowerLevel[FP_HEAL] < 3) + { + item = Menu_FindItemByName(menu, "heal_fbutton"); + if(item) + { + Item_SetFocus(item, 0, 0); + } + return; + } + + if(player->forcePowerLevel[FP_TELEPATHY] < 3) + { + item = Menu_FindItemByName(menu, "mindtrick_fbutton"); + if(item) + { + Item_SetFocus(item, 0, 0); + } + return; + } + + if(player->forcePowerLevel[FP_PROTECT] < 3) + { + item = Menu_FindItemByName(menu, "protect_fbutton"); + if(item) + { + Item_SetFocus(item, 0, 0); + } + return; + } + + if(player->forcePowerLevel[FP_DRAIN] < 3) + { + item = Menu_FindItemByName(menu, "drain_fbutton"); + if(item) + { + Item_SetFocus(item, 0, 0); + } + return; + } + + if(player->forcePowerLevel[FP_GRIP] < 3) + { + item = Menu_FindItemByName(menu, "grip_fbutton"); + if(item) + { + Item_SetFocus(item, 0, 0); + } + return; + } + + if(player->forcePowerLevel[FP_LIGHTNING] < 3) + { + item = Menu_FindItemByName(menu, "lightning_fbutton"); + if(item) + { + Item_SetFocus(item, 0, 0); + } + return; + } + + if(player->forcePowerLevel[FP_RAGE] < 3) + { + item = Menu_FindItemByName(menu, "rage_fbutton"); + if(item) + { + Item_SetFocus(item, 0, 0); + } + return; + } +#endif +} +// Try to increment force power level (Used by Force Power Allocation screen) +static void UI_AffectForcePowerLevel ( const char *forceName ) +{ + short forcePowerI=0,i; + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + if (!UI_GetForcePowerIndex ( forceName, &forcePowerI )) + { + return; + } + +#ifndef XBOX_DEMO + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + playerState_t* pState = cl->gentity->client; +#endif + +#ifdef XBOX_DEMO + if (demoForcePowerLevel[powerEnums[forcePowerI].powerEnum]>2) + { // Too big, can't be incremented + Cvar_Set("ui_deallocate_button","0"); + item = Menu_FindItemByName(menu, Cvar_VariableString("ui_forceButton")); + + if(!item) + return; + item->window.flags &= ~WINDOW_DECORATION; + return; + } +#else + if (pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum]>2) + { // Too big, can't be incremented + Cvar_Set("ui_deallocate_button","0"); + item = Menu_FindItemByName(menu, Cvar_VariableString("ui_forceButton")); + + if(!item) + return; + item->window.flags &= ~WINDOW_DECORATION; + return; + } +#endif + + Cvar_Set("ui_deallocate_button","1"); + + // Increment power level. + DC->startLocalSound(uiInfo.uiDC.Assets.forceChosenSound, CHAN_AUTO ); + + uiInfo.forcePowerUpdated = forcePowerI; // Remember which power was updated + +#ifdef XBOX_DEMO + demoForcePowerLevel[powerEnums[forcePowerI].powerEnum]++; // Increment it + + UI_SetHexPicLevel( menu,uiInfo.forcePowerUpdated,demoForcePowerLevel[powerEnums[forcePowerI].powerEnum],qtrue ); +#else + pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum]++; // Increment it + pState->forcePowersKnown |= ( 1 << powerEnums[forcePowerI].powerEnum ); + + UI_SetHexPicLevel( menu,uiInfo.forcePowerUpdated,pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum],qtrue ); +#endif + + UI_ShowForceLevelDesc ( forceName ); + + // A field was updated, so make it so others can't be + if (uiInfo.forcePowerUpdated>FP_UPDATED_NONE) + { + vec4_t color = { 0.25f, 0.25f, 0.25f, 1.0f}; + char itemName[128]; + + // Make it so none of the other buttons can be clicked + for (i=0;iwindow.flags &= ~WINDOW_VISIBLE; + } + } + + // Show point has been allocated + UI_SetPowerTitleText ( qtrue ); + + // Make weapons button active + UI_ForcePowerWeaponsButton(qtrue); + + // Make user_info + Cvar_Set ( "ui_forcepower_inc", va("%d",uiInfo.forcePowerUpdated) ); + + // Just grab an item to hand it to the function. + item = (itemDef_s *) Menu_FindItemByName(menu, "deallocate_fbutton"); + + if (item) + { + // Show all icons as greyed-out + UI_SetItemColor(item,"hexpic","forecolor",color); + UI_SetItemColor(item,"iconpic","forecolor",color); + +#ifdef _XBOX + Item_SetFocus(item, 0,0); +#else + item->window.flags |= WINDOW_HASFOCUS; +#endif + } + } + +} + +static void UI_DecrementForcePowerLevel( void ) +{ + int forcePowerI = Cvar_VariableIntegerValue( "ui_forcepower_inc" ); + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + playerState_t* pState = cl->gentity->client; + + pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum]--; // Decrement it + +} + +// Show force level description that matches current player level (Used by Force Power Allocation screen) +static void UI_ShowForceLevelDesc ( const char *forceName ) +{ + short forcePowerI=0; + menuDef_t *menu; + itemDef_t *item; + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + + if (!UI_GetForcePowerIndex ( forceName, &forcePowerI )) + { + return; + } + + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + playerState_t* pState = cl->gentity->client; + + char itemName[128]; + + // Update level description + Com_sprintf ( + itemName, + sizeof(itemName), + "%s_level%ddesc", + powerEnums[forcePowerI].title, + pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum] + ); + + item = (itemDef_s *) Menu_FindItemByName(menu, itemName); + if (item) + { + item->window.flags |= WINDOW_VISIBLE; + } + +} + +// Reset force level powers screen to what it was before player upgraded them (Used by Force Power Allocation screen) +static void UI_ResetForceLevels ( void ) +{ + + // What force ppower had the point added to it? + if (uiInfo.forcePowerUpdated!=FP_UPDATED_NONE) + { + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + playerState_t* pState = cl->gentity->client; + + // Decrement that power + pState->forcePowerLevel[powerEnums[uiInfo.forcePowerUpdated].powerEnum]--; + + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + int i; + char itemName[128]; + + // Make it so all buttons can be clicked + for (i=0;iwindow.flags |= WINDOW_VISIBLE; + } + } + + UI_SetPowerTitleText( qfalse ); +/* + Com_sprintf (itemName, sizeof(itemName), "%s_fbutton", powerEnums[uiInfo.forcePowerUpdated].title); + item = (itemDef_s *) Menu_FindItemByName(menu, itemName); + if (item) + { + // Change description text to tell player they can increment the force point + item->descText = "@MENUS_ADDFP"; + } +*/ + uiInfo.forcePowerUpdated = FP_UPDATED_NONE; + } + + UI_ForcePowerWeaponsButton(qfalse); +} + +// If the user has entered the force cheat (and thus has every force power) +// make sure that they don't get stuck on the force config screen +static void UI_CheckForForceCheat( void ) +{ + // Get player state + client_t* cl = &svs.clients[0]; + if (!cl) + return; + + playerState_t *pState = cl->gentity->client; + + // If there is ANY light/dark power that they don't have at max rank, we don't do anything + if( (pState->forcePowerLevel[FP_HEAL] < 3) || + (pState->forcePowerLevel[FP_TELEPATHY] < 3) || + (pState->forcePowerLevel[FP_GRIP] < 3) || + (pState->forcePowerLevel[FP_LIGHTNING] < 3) || + (pState->forcePowerLevel[FP_RAGE] < 3) || + (pState->forcePowerLevel[FP_PROTECT] < 3) || + (pState->forcePowerLevel[FP_ABSORB] < 3) || + (pState->forcePowerLevel[FP_DRAIN] < 3) ) + return; + + // Twiddling controls was a mess. Instead, just skip this menu and go to weapons: + Menus_CloseAll(); + Menus_OpenByName( "ingameWpnSelect" ); +} + +// Used by the cheat system to prevent the give all force powers cheat while +// the config screen is active +bool UI_ForceConfigUIActive( void ) +{ + menuDef_t *menu = Menu_GetFocused(); + if( menu && (Q_stricmp(menu->window.name, "progress_FConfig") == 0) ) + return true; + return false; +} + +// Set the Players known saber style +static void UI_UpdateFightingStyle ( void ) +{ + playerState_t *pState; + int fightingStyle,saberStyle; + + + fightingStyle = Cvar_VariableIntegerValue( "ui_newfightingstyle" ); + + if (fightingStyle == 1) + { + saberStyle = SS_MEDIUM; + } + else if (fightingStyle == 2) + { + saberStyle = SS_STRONG; + } + else // 0 is Fast + { + saberStyle = SS_FAST; + } + + // Get player state + client_t *cl = &svs.clients[0]; // 0 because only ever us as a player + + // No client, get out + if (cl && cl->gentity && cl->gentity->client) + { + pState = cl->gentity->client; + pState->saberStylesKnown |= (1<typeData; + if( listPtr ) + { + listPtr->cursorPos = 0; + } + item->cursorPos = 0; + } + + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, "torsolistbox"); + if (item) + { + listPtr = (listBoxDef_t*)item->typeData; + if( listPtr ) + { + listPtr->cursorPos = 0; + } + item->cursorPos = 0; + } + + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, "lowerlistbox"); + if (item) + { + listPtr = (listBoxDef_t*)item->typeData; + if( listPtr ) + { + listPtr->cursorPos = 0; + } + item->cursorPos = 0; + } + + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "colorbox"); + if (item) + { + listPtr = (listBoxDef_t*)item->typeData; + if( listPtr ) + { + listPtr->cursorPos = 0; + } + item->cursorPos = 0; + } + } +} + +static void UI_ClearInventory ( void ) +{ + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + if (cl->gentity && cl->gentity->client) + { + playerState_t* pState = cl->gentity->client; + + // Clear out inventory for the player + int i; + for (i=0;iinventory[i] = 0; + } + } +} + +static void UI_GiveInventory ( const int itemIndex, const int amount ) +{ + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + if (cl->gentity && cl->gentity->client) + { + playerState_t* pState = cl->gentity->client; + + if (itemIndex < MAX_INVENTORY) + { + pState->inventory[itemIndex]=amount; + } + } + +} + +//. Find weapons allocation screen BEGIN button and make active/inactive +/* +static void UI_WeaponAllocBeginButton(qboolean activeFlag) +{ + menuDef_t *menu; + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + int weap = Cvar_VariableIntegerValue( "weapon_menu" ); + + // Find begin button + itemDef_t *item; + item = Menu_GetMatchingItemByNumber(menu, weap, "beginmission"); + +#ifndef _XBOX + + if (item) + { + // Make it active + if (activeFlag) + { + item->window.flags &= ~WINDOW_INACTIVE; + } + else + { + item->window.flags |= WINDOW_INACTIVE; + } + } + +#else + if (item) + { + // Make it active + if (activeFlag) + { + item->window.flags |= WINDOW_VISIBLE; + Item_SetFocus(item, 0, 0); + } + else + { + item->window.flags &= ~WINDOW_VISIBLE; + } + } + + UI_SetVis(menu, "tab_Weapon", !activeFlag); + UI_SetVis(menu, "tab_WeaponShadow", !activeFlag); + UI_SetVis(menu, "beginmissionShadow", activeFlag); + + if(activeFlag) + DC->setCVar("ui_hideAcallout", "0"); + else + DC->setCVar("ui_hideAcallout", "1"); +#endif + +} +*/ + +// If we have both weapons and the throwable weapon, turn on the begin mission button, +// otherwise, turn it off +/* +static void UI_WeaponsSelectionsComplete( void ) +{ + // We need two weapons and one throwable + if (( uiInfo.selectedWeapon1 != NOWEAPON ) && + ( uiInfo.selectedWeapon2 != NOWEAPON ) && + ( uiInfo.selectedThrowWeapon != NOWEAPON )) + { + UI_WeaponAllocBeginButton(qtrue); // Turn it on + } + else + { + UI_WeaponAllocBeginButton(qfalse); // Turn it off + } +} +*/ + +// if this is the first time into the weapon allocation screen, show the INSTRUCTION screen +static void UI_WeaponHelpActive( void ) +{ + int tier_storyinfo = Cvar_VariableIntegerValue( "tier_storyinfo" ); + menuDef_t *menu; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + // First time, show instructions + if (tier_storyinfo==1) + { + UI_SetItemVisible(menu,"weapon_button",qfalse); + + UI_SetItemVisible(menu,"inst_stuff",qtrue); + + } + // just act like normal + else + { + UI_SetItemVisible(menu,"weapon_button",qtrue); + + UI_SetItemVisible(menu,"inst_stuff",qfalse); + } +} + +static void UI_InitWeaponSelect( void ) +{ + menuDef_t *menu = Menu_GetFocused(); + if( !menu || !menu->window.name ) + return; + + if (Q_stricmp(menu->window.name, "ingameWpnSelect") == 0) + { + // First screen - reset all weapon selections + uiInfo.selectedWeapon1 = NOWEAPON; + uiInfo.selectedWeapon2 = NOWEAPON; + uiInfo.selectedThrowWeapon = NOWEAPON; + } + else if(Q_stricmp(menu->window.name, "ingameWpnSelect2") == 0) + { + // Second screen. Disable whatever needs to be disabled, re-enable the rest: + UI_DisableWeapon(); + + // Now put focus on the first item that isn't disabled: + itemDef_t *item; + if( uiInfo.selectedWeapon1 == WP_BLASTER ) + item = Menu_FindItemByName( menu, "R1_C2_button" ); + else + item = Menu_FindItemByName( menu, "R1_C1_button" ); + + if( !item ) + return; + + Item_SetFocus( item, 0, 0 ); + } + else if(Q_stricmp(menu->window.name, "ingameWpnSelect3") == 0) + { + // Third screen, nothing to do. + } +} + +// Called by the second weapon select screen to disable and gray out the +// weapon that was chosen on the first screen +static void UI_DisableWeapon( void ) +{ + // Get weapon chosen on first screen - adjust by one, as itemDefs use 1-based index + int firstWeapon = Cvar_VariableIntegerValue( "weaponSelect" ) + 1; + assert( firstWeapon >= 1 && firstWeapon <= 8 ); + + menuDef_t *menu = Menu_GetFocused(); + if( !menu ) + return; + + itemDef_t *greyItem = Menu_FindItemByName( menu, "selectionSlotFill" ); + if( !greyItem ) + return; + + for( int i = 1; i <= 8; ++i ) + { + itemDef_t *butItem = Menu_FindItemByName( menu, va("R1_C%d_button", i) ); + if( !butItem ) + continue; + + if( i == firstWeapon ) + { + // Copy rect so that disabled weapon has a gray background + greyItem->window.rect = butItem->window.rect; + greyItem->window.rect.x--; + greyItem->window.rect.y--; + greyItem->window.rect.w += 2; + greyItem->window.rect.h += 2; + + // Disable button for already selected weapon + butItem->window.flags |= WINDOW_DECORATION; + } + else + { + // Make sure all other weapons ARE selectable + butItem->window.flags &= ~WINDOW_DECORATION; + } + } +} + +static void UI_ClearWeapons ( void ) +{ + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + if (cl->gentity && cl->gentity->client) + { + playerState_t* pState = cl->gentity->client; + + // Clear out any weapons for the player + pState->stats[ STAT_WEAPONS ] = 0; + + pState->weapon = WP_NONE; + + } + +} + +static void UI_GiveWeapon ( const int weaponIndex ) +{ + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + if (cl->gentity && cl->gentity->client) + { + playerState_t* pState = cl->gentity->client; + + if (weaponIndexstats[ STAT_WEAPONS ] |= ( 1 << weaponIndex ); + } + } +} + +static void UI_EquipWeapon ( const int weaponIndex ) +{ + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + if (cl->gentity && cl->gentity->client) + { + playerState_t* pState = cl->gentity->client; + + if (weaponIndexweapon = weaponIndex; + //force it to change + //CG_ChangeWeapon( wp ); + } + } +} + +static void UI_LoadMissionSelectMenu( const char *cvarName ) +{ + int holdLevel = (int)trap_Cvar_VariableValue(cvarName); + + // Figure out which tier menu to load + if ((holdLevel > 0) && (holdLevel < 5)) + { + UI_LoadMenus("ui/tier1.txt",qfalse); + + Menus_CloseByName("ingameMissionSelect1"); + } + else if ((holdLevel > 6) && (holdLevel < 10)) + { + UI_LoadMenus("ui/tier2.txt",qfalse); + + Menus_CloseByName("ingameMissionSelect2"); + } + else if ((holdLevel > 11) && (holdLevel < 15)) + { + UI_LoadMenus("ui/tier3.txt",qfalse); + + Menus_CloseByName("ingameMissionSelect3"); + } + +} + +#ifdef XBOX_DEMO +int demoWeapon1; +int demoWeapon2; +int demoThrowable; +#endif + +// Update the player weapons with the chosen weapon +static void UI_AddWeaponSelection ( const int weaponIndex, const int ammoIndex, const int ammoAmount, const char *iconItemName,const char *litIconItemName, const char *hexBackground, const char *soundfile ) +{ + if( uiInfo.selectedWeapon1 == NOWEAPON ) + { + uiInfo.selectedWeapon1 = weaponIndex; +#ifdef XBOX_DEMO + demoWeapon1 = weaponIndex; +#endif + } + else + { + uiInfo.selectedWeapon2 = weaponIndex; +#ifdef XBOX_DEMO + demoWeapon2 = weaponIndex; +#endif + } + + if( soundfile ) + { + DC->startLocalSound(DC->registerSound(soundfile, qfalse), CHAN_LOCAL ); + } + + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + // Add weapon + if (cl->gentity && cl->gentity->client) + { + playerState_t* pState = cl->gentity->client; + + if ((weaponIndex>0) && (weaponIndexstats[ STAT_WEAPONS ] |= ( 1 << weaponIndex ); + } + + // Give them ammo too + if ((ammoIndex>0) && (ammoIndexammo[ ammoIndex ] = ammoAmount; + } + } + +// UI_WeaponsSelectionsComplete(); // Test to see if the mission begin button should turn on or off + + +} + + +// Update the player weapons with the chosen weapon +static void UI_RemoveWeaponSelection ( const int weaponSelectionIndex ) +{ + +} + +static void UI_NormalWeaponSelection ( const int selectionslot ) +{ + itemDef_s *item; + menuDef_t *menu; + + menu = Menu_GetFocused(); // Get current menu + if (!menu) + { + return; + } + + if (selectionslot == 1) + { + item = (itemDef_s *) Menu_FindItemByName( menu, "chosenweapon1_icon" ); + if (item) + { + item->window.background = uiInfo.unlitWeapon1Icon; + } + } + + if (selectionslot == 2) + { + item = (itemDef_s *) Menu_FindItemByName( menu, "chosenweapon2_icon" ); + if (item) + { + item->window.background = uiInfo.unlitWeapon2Icon; + } + } +} + +static void UI_HighLightWeaponSelection ( const int selectionslot ) +{ + itemDef_s *item; + menuDef_t *menu; + + menu = Menu_GetFocused(); // Get current menu + if (!menu) + { + return; + } + + if (selectionslot == 1) + { + item = (itemDef_s *) Menu_FindItemByName( menu, "chosenweapon1_icon" ); + if (item) + { + item->window.background = uiInfo.litWeapon1Icon; + } + } + + if (selectionslot == 2) + { + item = (itemDef_s *) Menu_FindItemByName( menu, "chosenweapon2_icon" ); + if (item) + { + item->window.background = uiInfo.litWeapon2Icon; + } + } +} + +// Update the player throwable weapons (okay it's a bad description) with the chosen weapon +static void UI_AddThrowWeaponSelection ( const int weaponIndex, const int ammoIndex, const int ammoAmount, const char *iconItemName,const char *litIconItemName, const char *hexBackground, const char *soundfile ) +{ + uiInfo.selectedThrowWeapon = weaponIndex; + +#ifdef XBOX_DEMO + demoThrowable = weaponIndex; +#endif + + if( soundfile ) + { + DC->startLocalSound(DC->registerSound(soundfile, qfalse), CHAN_LOCAL ); + } + + // Get player state + + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + // Add weapon + if (cl->gentity && cl->gentity->client) + { + playerState_t* pState = cl->gentity->client; + + if ((weaponIndex>0) && (weaponIndexstats[ STAT_WEAPONS ] |= ( 1 << weaponIndex ); + } + + // Give them ammo too + if ((ammoIndex>0) && (ammoIndexammo[ ammoIndex ] = ammoAmount; + } + } + +// UI_WeaponsSelectionsComplete(); // Test to see if the mission begin button should turn on or off + +} + + +// Update the player weapons with the chosen throw weapon +static void UI_RemoveThrowWeaponSelection ( void ) +{ + +} + +static void UI_NormalThrowSelection ( void ) +{ + itemDef_s *item; + menuDef_t *menu; + + menu = Menu_GetFocused(); // Get current menu + if (!menu) + { + return; + } + + item = (itemDef_s *) Menu_FindItemByName( menu, "chosenthrowweapon_icon" ); + item->window.background = uiInfo.unlitThrowableIcon; +} + +static void UI_HighLightThrowSelection ( void ) +{ + itemDef_s *item; + menuDef_t *menu; + + menu = Menu_GetFocused(); // Get current menu + if (!menu) + { + return; + } + + item = (itemDef_s *) Menu_FindItemByName( menu, "chosenthrowweapon_icon" ); + item->window.background = uiInfo.litThrowableIcon; +} + +static void UI_GetSaberCvars ( void ) +{ + Cvar_Set ( "ui_saber_type", Cvar_VariableString ( "g_saber_type" ) ); + Cvar_Set ( "ui_saber", Cvar_VariableString ( "g_saber" ) ); + Cvar_Set ( "ui_saber2", Cvar_VariableString ( "g_saber2" ) ); + Cvar_Set ( "ui_saber_color", Cvar_VariableString ( "g_saber_color" ) ); + Cvar_Set ( "ui_saber2_color", Cvar_VariableString ( "g_saber2_color" ) ); + + Cvar_Set ( "ui_newfightingstyle", "0"); + +} + +static void UI_ResetSaberCvars ( void ) +{ + Cvar_Set ( "g_saber_type", "single" ); + Cvar_Set ( "g_saber", "single_1" ); + Cvar_Set ( "g_saber2", "" ); + + Cvar_Set ( "ui_saber_type", "single" ); + Cvar_Set ( "ui_saber", "single_1" ); + Cvar_Set ( "ui_saber2", "" ); +} + +extern qboolean ItemParse_asset_model_go( itemDef_t *item, const char *name ); +extern qboolean ItemParse_model_g2skin_go( itemDef_t *item, const char *skinName ); +static void UI_UpdateCharacterSkin( void ) +{ + menuDef_t *menu; + itemDef_t *item; + char skin[MAX_QPATH]; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + item = (itemDef_s *) Menu_FindItemByName(menu, "character"); + + if (!item) + { + Com_Error( ERR_FATAL, "UI_UpdateCharacterSkin: Could not find item (character) in menu (%s)", menu->window.name); + } + + Com_sprintf( skin, sizeof( skin ), "models/players/%s/|%s|%s|%s", + Cvar_VariableString ( "ui_char_model"), + Cvar_VariableString ( "ui_char_skin_head"), + Cvar_VariableString ( "ui_char_skin_torso"), + Cvar_VariableString ( "ui_char_skin_legs") + ); + + ItemParse_model_g2skin_go( item, skin ); +} + +static void UI_UpdateCharacter( qboolean changedModel ) +{ + menuDef_t *menu; + itemDef_t *item; + char modelPath[MAX_QPATH]; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + item = (itemDef_s *) Menu_FindItemByName(menu, "character"); + + if (!item) + { + Com_Error( ERR_FATAL, "UI_UpdateCharacter: Could not find item (character) in menu (%s)", menu->window.name); + } + + ItemParse_model_g2anim_go( item, ui_char_anim.string ); + + Com_sprintf( modelPath, sizeof( modelPath ), "models/players/%s/model.glm", Cvar_VariableString ( "ui_char_model" ) ); + ItemParse_asset_model_go( item, modelPath ); + + if ( changedModel ) + {//set all skins to first skin since we don't know you always have all skins + //FIXME: could try to keep the same spot in each list as you swtich models + UI_FeederSelection(FEEDER_PLAYER_SKIN_HEAD, 0, item); //fixme, this is not really the right item!! + UI_FeederSelection(FEEDER_PLAYER_SKIN_TORSO, 0, item); + UI_FeederSelection(FEEDER_PLAYER_SKIN_LEGS, 0, item); + UI_FeederSelection(FEEDER_COLORCHOICES, 0, item); + } + UI_UpdateCharacterSkin(); +} + +void UI_UpdateSaberType( void ) +{ + char sType[MAX_QPATH]; + DC->getCVarString( "ui_saber_type", sType, sizeof(sType) ); + if ( Q_stricmp( "single", sType ) == 0 || + Q_stricmp( "staff", sType ) == 0 ) + { + DC->setCVar( "ui_saber2", "" ); + } +} + +static void UI_UpdateSaberHilt( qboolean secondSaber ) +{ + menuDef_t *menu; + itemDef_t *item; + char model[MAX_QPATH]; + char modelPath[MAX_QPATH]; + char skinPath[MAX_QPATH]; + menu = Menu_GetFocused(); // Get current menu (either video or ingame video, I would assume) + + if (!menu) + { + return; + } + + char *itemName; + char *saberCvarName; + if ( secondSaber ) + { + itemName = "saber2"; + saberCvarName = "ui_saber2"; + } + else + { + itemName = "saber"; + saberCvarName = "ui_saber"; + } + + item = (itemDef_s *) Menu_FindItemByName(menu, itemName ); + + if(!item) + { + Com_Error( ERR_FATAL, "UI_UpdateSaberHilt: Could not find item (%s) in menu (%s)", itemName, menu->window.name); + } + DC->getCVarString( saberCvarName, model, sizeof(model) ); + //read this from the sabers.cfg + if ( UI_SaberModelForSaber( model, modelPath ) ) + {//successfully found a model + ItemParse_asset_model_go( item, modelPath );//set the model + //get the customSkin, if any + //COM_StripExtension( modelPath, skinPath ); + //COM_DefaultExtension( skinPath, sizeof( skinPath ), ".skin" ); + if ( UI_SaberSkinForSaber( model, skinPath ) ) + { + ItemParse_model_g2skin_go( item, skinPath );//apply the skin + } + else + { + ItemParse_model_g2skin_go( item, NULL );//apply the skin + } + } +} + +/* +static void UI_UpdateSaberColor( qboolean secondSaber ) +{ + int sabernumber; + if (secondSaber) + sabernumber = 2; + else + sabernumber = 1; + + ui.Cmd_ExecuteText( EXEC_APPEND, va("sabercolor %i %s\n",sabernumber, Cvar_VariableString("g_saber_color"))); +} +*/ +char GoToMenu[1024]; + +/* +================= +Menus_SaveGoToMenu +================= +*/ +void Menus_SaveGoToMenu(const char *menuTo) +{ + memcpy(GoToMenu, menuTo, sizeof(GoToMenu)); +} + +/* +================= +UI_CheckVid1Data +================= +*/ +void UI_CheckVid1Data(const char *menuTo,const char *warningMenuName) +{ + menuDef_t *menu; + itemDef_t *applyChanges; + + menu = Menu_GetFocused(); // Get current menu (either video or ingame video, I would assume) + + if (!menu) + { + Com_Printf(S_COLOR_YELLOW"WARNING: No videoMenu was found. Video data could not be checked\n"); + return; + } + + applyChanges = (itemDef_s *) Menu_FindItemByName(menu, "applyChanges"); + + if (!applyChanges) + { +// Menus_CloseAll(); + Menus_OpenByName(menuTo); + return; + } + + if ((applyChanges->window.flags & WINDOW_VISIBLE)) // Is the APPLY CHANGES button active? + { +// Menus_SaveGoToMenu(menuTo); // Save menu you're going to +// Menus_HideItems(menu->window.name); // HIDE videMenu in case you have to come back + Menus_OpenByName(warningMenuName); // Give warning + } + else + { +// Menus_CloseAll(); +// Menus_OpenByName(menuTo); + } +} + +/* +================= +UI_ResetDefaults +================= +*/ +void UI_ResetDefaults( void ) +{ + ui.Cmd_ExecuteText( EXEC_APPEND, "cvar_restart\n"); + ui.Cmd_ExecuteText( EXEC_APPEND, "exec default.cfg\n"); + ui.Cmd_ExecuteText( EXEC_APPEND, "vid_restart\n" ); +} + +/* +======================= +UI_SortSaveGames +======================= +*/ +static int UI_SortSaveGames( const void *A, const void *B ) +{ + + const int &a = ((savedata_t*)A)->currentSaveFileDateTime; + const int &b = ((savedata_t*)B)->currentSaveFileDateTime; + + if (a > b) + { + return -1; + } + else + { + return (a < b); + } +} + +/* +======================= +UI_AdjustSaveGameListBox +======================= +*/ +// Yeah I could get fired for this... in a world of good and bad, this is bad +// I wish we passed in the menu item to RunScript(), oh well... +void UI_AdjustSaveGameListBox( int currentLine ) +{ + menuDef_t *menu; + itemDef_t *item; + + // could be in either the ingame or shell load menu (I know, I know it's bad) + menu = Menus_FindByName("loadgameMenu"); + if( !menu ) + { + menu = Menus_FindByName("ingameloadMenu"); + } + + if (menu) + { + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, "loadgamelist"); + if (item) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if( listPtr ) + { + listPtr->cursorPos = currentLine; + } + + item->cursorPos = currentLine; + } + } + +} + + +void setBlockDisplayCvar() +{ + unsigned long blocks; + blocks = SG_BlocksLeft(); + if (blocks > 50000) + Cvar_Set("ui_BlocksAvailable","50000+"); + else + Cvar_SetValue("ui_BlocksAvailable",(int)blocks); + + Cvar_SetValue("ui_BlocksNeeded", SG_SaveGameSize()); + +} + + + +void storeSGDataDatetoCvar() +{ + char timeBuffer[128]; + char * timeBufferPtr; + char filetime[64],*tokentimeptr; + + strcpy (filetime,s_savedata[s_savegame.currentLine].currentSaveFileDateTimeString); + if(!filetime[0]) + { + Cvar_Set("ui_DateString", ""); + return; + } + timeBuffer[0] = 0; + timeBufferPtr = &(timeBuffer[0]); + + //Wed + tokentimeptr = strtok(filetime," "); + + //Jan + tokentimeptr = strtok(NULL," "); + strcpy(timeBufferPtr,tokentimeptr); + timeBufferPtr+=strlen(tokentimeptr); + + *timeBufferPtr = ' '; + timeBufferPtr ++; + + //02 + tokentimeptr = strtok(NULL," "); + strcpy(timeBufferPtr,tokentimeptr); + timeBufferPtr+=strlen(tokentimeptr); + + *timeBufferPtr = ' '; + timeBufferPtr ++; + + //02: + tokentimeptr = strtok(NULL,":"); + //strcpy(timeBufferPtr,tokentimeptr); + //timeBufferPtr+=strlen(tokentimeptr); + + //*timeBufferPtr = ':'; + //timeBufferPtr ++; + + //03: + tokentimeptr = strtok(NULL,":"); + //strcpy(timeBufferPtr,tokentimeptr); + //timeBufferPtr+=strlen(tokentimeptr); + + //*timeBufferPtr = ' '; + //timeBufferPtr ++; + + //55 + tokentimeptr = strtok(NULL," "); + //strcpy(timeBufferPtr,tokentimeptr); + //timeBufferPtr+=strlen(tokentimeptr); + + //*timeBufferPtr = '_'; + //timeBufferPtr ++; + + //1980 + tokentimeptr = strtok(NULL,"\n"); + strcpy(timeBufferPtr,tokentimeptr); + timeBufferPtr+=strlen(tokentimeptr); + + *timeBufferPtr = 0; + Cvar_Set("ui_DateString", timeBuffer); + + +} + +void storeSGDataTimetoCvar() +{ + char timeBuffer[128]; + char * timeBufferPtr; + char filetime[64],*tokentimeptr; + + strcpy (filetime,s_savedata[s_savegame.currentLine].currentSaveFileDateTimeString); + if(!filetime[0]) + { + Cvar_Set("ui_TimeString", ""); + return; + } + + + timeBuffer[0] = 0; + timeBufferPtr = &(timeBuffer[0]); + + //Wed + tokentimeptr = strtok(filetime," "); + + //Jan + tokentimeptr = strtok(NULL," "); + + //02 + tokentimeptr = strtok(NULL," "); + + //02: + tokentimeptr = strtok(NULL,":"); + strcpy(timeBufferPtr,tokentimeptr); + timeBufferPtr+=strlen(tokentimeptr); + + *timeBufferPtr = ':'; + timeBufferPtr ++; + + //03: + tokentimeptr = strtok(NULL,":"); + strcpy(timeBufferPtr,tokentimeptr); + timeBufferPtr+=strlen(tokentimeptr); + + *timeBufferPtr = ':'; + timeBufferPtr ++; + + //55 + tokentimeptr = strtok(NULL," "); + strcpy(timeBufferPtr,tokentimeptr); + timeBufferPtr+=strlen(tokentimeptr); + + //*timeBufferPtr = '_'; + //timeBufferPtr ++; + + //1980 + tokentimeptr = strtok(NULL,"\n"); + //strcpy(timeBufferPtr,tokentimeptr); + //timeBufferPtr+=strlen(tokentimeptr); + + *timeBufferPtr = 0; + Cvar_Set("ui_TimeString", timeBuffer); + + + + +} + +void storeSGDataDiffLeveltoCvar() +{ + char * difflevel; + difflevel = s_savedata[s_savegame.currentLine].currentSaveFileComments; + if (!difflevel[0]) + { + Cvar_Set("ui_DiffLevel", ""); + return; + } + if ( difflevel[0] == '@') + Cvar_Set("ui_DiffLevel", SE_GetString(&difflevel[1])); + else + Cvar_Set("ui_DiffLevel", ""); + + +} + + + +void ui_resetSaveGameList() +{ + s_savegame.saveFileCnt = -1; +} +/* +================= +ReadSaveDirectory +================= +*/ +//for the xbox reading the save directory will consist of +//iterating through the save game folders + +void ReadSaveDirectory (void) +{ + + char *holdChar; + char *filetime,*tokentimeptr; + const char * newgame; + qboolean newGameActive,newGameAvailable; + char saveGameName[filepathlength]; + //char timeBuffer[128]; + //char * timeBufferPtr; + + + // Clear out save data + memset(s_savedata,0,sizeof(s_savedata)); + s_savegame.saveFileCnt = 0; + Cvar_Set("ui_gameDesc", "" ); // Blank out comment + Cvar_Set("ui_SelectionOK", "0" ); + + + //memset( screenShotBuf,0,(SG_SCR_WIDTH * SG_SCR_HEIGHT * 4)); //blank out sshot + + // Get everything in saves directory +// fileCnt = ui.FS_GetFileList("saves", ".sav", s_savegame.listBuf, LISTBUFSIZE ); + + Cvar_Set("ui_ResumeOK", "0" ); + holdChar = s_savegame.listBuf; + XGAME_FIND_DATA SaveGameData; + HANDLE searchhandle; + BOOL retval; + + newGameActive = (Cvar_VariableIntegerValue( "ui_newGameActive" )) ? qtrue : qfalse; + newGameAvailable = qfalse; + extern unsigned long SG_BlocksLeft(); + if (newGameActive) + { +// if (SG_BlocksLeft() < SG_SaveGameSize()) +// { +// Cvar_SetValue("noNewSaveGameAvailable", 1); +// newGameAvailable = qfalse; +// } +// else +// { + Cvar_SetValue("noNewSaveGameAvailable", 0); + newGameAvailable = qtrue; +// } + } + else + Cvar_SetValue("noNewSaveGameAvailable", 1); + + if ( newGameAvailable) + { + strcpy(s_savedata[s_savegame.saveFileCnt].currentSaveFileComments,"New Game"); + strcpy(s_savedata[s_savegame.saveFileCnt].currentSaveFileMap, "New Game"); + newgame = SE_GetString( "MENUS", "newSaveGame" ); + //JLF debug code + if ( !newgame) + newgame = "New Game"; + //END JLF + strcpy (holdChar, newgame); + s_savedata[s_savegame.saveFileCnt].currentSaveFileName = holdChar; + holdChar += strlen(holdChar)+1; + s_savedata[s_savegame.saveFileCnt].currentSaveFileDateTime = 0; + s_savedata[s_savegame.saveFileCnt].currentSaveFileDateTimeString[0] = 0; + + s_savegame.saveFileCnt++; + } + + // Any saves? + searchhandle = XFindFirstSaveGame( "U:\\", &SaveGameData ); + if ( searchhandle != INVALID_HANDLE_VALUE ) + { + do + { + // At least one; count up the rest + DWORD dwCount = 1; + //get the name of the file + + wcstombs(saveGameName, SaveGameData.szSaveGameName, filepathlength); + + // Skip over Settings + if( Q_stricmp( saveGameName, "Settings" ) == 0 ) + { + retval = XFindNextSaveGame( searchhandle, &SaveGameData ); + continue; + } + + strcpy( holdChar, saveGameName); + + + // Is this a valid file??? & Get comment of file + //create full path name + time_t result; + result = ui.SG_GetSaveGameComment(saveGameName, s_savedata[s_savegame.saveFileCnt].currentSaveFileComments, s_savedata[s_savegame.saveFileCnt].currentSaveFileMap); + if (result != 0) // ignore Bad save game + { + s_savedata[s_savegame.saveFileCnt].corrupt = false; + } + else + { + s_savedata[s_savegame.saveFileCnt].corrupt = true; + } + { + s_savedata[s_savegame.saveFileCnt].currentSaveFileName = holdChar; + s_savedata[s_savegame.saveFileCnt].currentSaveFileDateTime = result; + holdChar += strlen(holdChar)+1; + + struct tm *localTime; + localTime = localtime( &result ); + + // Wed Jan 02 02:03:55 1980\n\0 + filetime = asctime (localTime); + + + + strcpy(s_savedata[s_savegame.saveFileCnt].currentSaveFileDateTimeString, filetime ); + s_savegame.saveFileCnt++; + if (s_savegame.saveFileCnt == MAX_SAVELOADFILES) + { + break; + } + + } + + retval =XFindNextSaveGame( searchhandle, &SaveGameData ); + }while(retval); + } + + if ( newGameAvailable) + qsort( &(s_savedata[1]), s_savegame.saveFileCnt-1, sizeof(savedata_t), UI_SortSaveGames ); + else + qsort( s_savedata, s_savegame.saveFileCnt, sizeof(savedata_t), UI_SortSaveGames ); + + Cvar_SetValue("saveGameCount", s_savegame.saveFileCnt); + + + if ((s_savegame.saveFileCnt>1 && newGameAvailable) || + (s_savegame.saveFileCnt>0 && !newGameAvailable)) + Cvar_SetValue("overwriteAvailable", 1); + else + Cvar_SetValue("overwriteAvailable", 0); + + setBlockDisplayCvar(); + + if (s_savegame.currentLine == 0) + { + if (ui_ShowDeleteActive) + { + Cvar_SetValue("ui_showYdel",trap_Cvar_VariableValue("ui_ShowDelete")); + ui_ShowDeleteActive = qfalse; + Cvar_SetValue( "ui_cancelYScript",0); + } + if ( newGameAvailable) + { + //there is a 'new save game' index that is highlighted + Cvar_SetValue("ui_ShowDelete",trap_Cvar_VariableValue("ui_showYdel")); + Cvar_SetValue("ui_showYdel",0); + Cvar_SetValue( "ui_cancelYScript",1); + ui_ShowDeleteActive =qtrue; + + } + } + storeSGDataDatetoCvar(); + storeSGDataTimetoCvar(); + storeSGDataDiffLeveltoCvar(); +} + + + +#ifdef _XBOX +void UI_UpdateSettingsCvars( void ) +{ +} + +static void _UI_SetVol(const char* type, float value) +{ + if(!Q_stricmp(type, "effects") ) + { + if(value < 0.1f) + value = 0.0f; + DC->setCVar("s_effects_volume", va("%f",value) ); + } + else if(!Q_stricmp(type, "voice") ) + { + if(value < 0.1f) + value = 0.0f; + DC->setCVar("s_voice_volume", va("%f",value) ); + } + else if(!Q_stricmp(type, "music") ) + { + if(value < 0.1f) + value = 0.0f; + DC->setCVar("s_music_volume", va("%f",value) ); + } + else if(!Q_stricmp(type, "horizontal") ) + { + DC->setCVar("sensitivity", va("%f",value*10.0f) ); + } + else if(!Q_stricmp(type, "vertical") ) + { + DC->setCVar("sensitivityY", va("%f",value*10.0f) ); + } +} + +static float _UI_GetVol(const char* type) +{ + if(!Q_stricmp(type, "effects") ) + { + return DC->getCVarValue("s_effects_volume"); + } + else if(!Q_stricmp(type, "voice") ) + { + return DC->getCVarValue("s_voice_volume"); + } + else if(!Q_stricmp(type, "music") ) + { + return DC->getCVarValue("s_music_volume"); + } + else if(!Q_stricmp(type, "horizontal") ) + { + return DC->getCVarValue("sensitivity") / 10.0f; + } + else if(!Q_stricmp(type, "vertical") ) + { + return DC->getCVarValue("sensitivityY") / 10.0f; + } + return 1.0f; +} + +static void UI_UpdateVolume(const char* action, const char* type, const char* itemName, int width, int value ) +{ + itemDef_s* mask; + menuDef_t* menu; + int amount; + + // get the current menu + menu = Menu_GetFocused(); + if (!menu) + { + return; + } + + // get the masking item to change + mask = (itemDef_s*) Menu_FindItemByName(menu, itemName ); + + // figure out what to do + if(!Q_stricmp(action, "set") ) // set the volume level + { + amount = value; + } + else if(!Q_stricmp(action, "actual") ) + { + float mult; + + // get the requested volume + mult = _UI_GetVol(type); + + // set the mask + mask->window.rect.w = (int)(mult * width); + + return; + + } + else if(!Q_stricmp(action, "increment") ) // increment the volume level + { + amount = mask->window.rect.w + value; + } + else if(!Q_stricmp(action, "decrement") ) // decrement the volume level + { + amount = mask->window.rect.w - value; + } + + // make sure we're in bounds + if(amount <= width && amount >= 0) + { + float volume; + + // compute the volume + volume = (float)amount / (float)width; + + // alter the mask + mask->window.rect.w = amount; + + // update the volume + _UI_SetVol(type, volume); + } +} + +/* +========== +UI_UpdateMoveTitles() +========== +*/ +static void UI_UpdateMoveTitles(void) +{ + itemDef_t *item; + menuDef_t *menu; + modelDef_t *modelPtr; + + uiInfo.movesTitleIndex = (short)DC->getCVarValue("ui_move_title"); + if(uiInfo.movesTitleIndex > 5 || uiInfo.movesTitleIndex < 0) + uiInfo.movesTitleIndex = 0; + + uiInfo.movesBaseAnim = datapadMoveTitleBaseAnims[uiInfo.movesTitleIndex]; + menu = Menus_FindByName("datapadMovesMenu"); + + if (menu) + { + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, "character"); + if (item) + { + modelPtr = (modelDef_t*)item->typeData; + if (modelPtr) + { + ItemParse_model_g2anim_go( item, uiInfo.movesBaseAnim ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + } + } + } +} +/* +========== +UI_UpdateMoves() +========== +*/ +static void UI_UpdateMoves( void ) +{ + itemDef_t *item; + menuDef_t *menu; + modelDef_t *modelPtr; + char skin[MAX_QPATH]; + + uiInfo.movesTitleIndex = (short)DC->getCVarValue("ui_move_title"); + if(uiInfo.movesTitleIndex > 5 || uiInfo.movesTitleIndex < 0) + uiInfo.movesTitleIndex = 0; + + short index = (short)DC->getCVarValue("ui_moves"); + if(index > 15 || index < 0) + index = 0; + + menu = Menus_FindByName("datapadMovesMenu"); + + if (menu) + { + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, "character"); + if (item) + { + modelPtr = (modelDef_t*)item->typeData; + if (modelPtr) + { + if (datapadMoveData[uiInfo.movesTitleIndex][index].anim) + { + ItemParse_model_g2anim_go( item, datapadMoveData[uiInfo.movesTitleIndex][index].anim ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + + // Play sound for anim + if (datapadMoveData[uiInfo.movesTitleIndex][index].sound == MDS_FORCE_JUMP) + { + DC->startLocalSound(uiInfo.uiDC.Assets.datapadmoveJumpSound, CHAN_LOCAL ); + } + else if (datapadMoveData[uiInfo.movesTitleIndex][index].sound == MDS_ROLL) + { + DC->startLocalSound(uiInfo.uiDC.Assets.datapadmoveRollSound, CHAN_LOCAL ); + } + else if (datapadMoveData[uiInfo.movesTitleIndex][index].sound == MDS_SABER) + { + // Randomly choose one sound + int soundI = Q_irand( 1, 6 ); + sfxHandle_t *soundPtr; + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound1; + if (soundI == 2) + { + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound2; + } + else if (soundI == 3) + { + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound3; + } + else if (soundI == 4) + { + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound4; + } + else if (soundI == 5) + { + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound5; + } + else if (soundI == 6) + { + soundPtr = &uiInfo.uiDC.Assets.datapadmoveSaberSound6; + } + + DC->startLocalSound(*soundPtr, CHAN_LOCAL ); + } + + if (datapadMoveData[uiInfo.movesTitleIndex][index].desc) + { + Cvar_Set( "ui_move_desc", datapadMoveData[uiInfo.movesTitleIndex][index].desc); + } + + Com_sprintf( skin, sizeof( skin ), "models/players/%s/|%s|%s|%s", + Cvar_VariableString ( "g_char_model"), + Cvar_VariableString ( "g_char_skin_head"), + Cvar_VariableString ( "g_char_skin_torso"), + Cvar_VariableString ( "g_char_skin_legs") + ); + + ItemParse_model_g2skin_go( item, skin ); + + } + } + } + } +} +#endif + +//JLF error popup +// What popup is active at the moment? +static xbErrorPopupType sPopup = XB_POPUP_NONE; + +// Creates the requested popup, then displays it. +// Establishes context so proper action will be taken on a selection. +void UI_xboxErrorPopup(xbErrorPopupType popup) +{ + // Set our context + + //store all these values +/* + DC->setCVar("ui_showWbtsel", "0"); + DC->setCVar("ui_showWmove", "0"); + DC->setCVar("ui_showXadv", "0"); + DC->setCVar("ui_showXNew", "0"); + DC->setCVar("ui_showXchk", "0"); + DC->setCVar("ui_showXforce", "0"); + DC->setCVar("ui_showXcstm", "0"); + DC->setCVar("ui_showYref", "0"); + DC->setCVar("ui_showYdel", "0"); + DC->setCVar("ui_showYbtrule", "0"); + DC->setCVar("ui_showYsaber", "0"); + DC->setCVar("ui_showYwpn", "0"); + DC->setCVar("ui_showAcallout", "0"); + DC->setCVar("ui_showBcallout", "0"); + + +*/ + sPopup = popup; + Cvar_Set( "xb_errMessage", "" ); + Cvar_Set( "xb_PopupTitle", "" ); +// Cvar_Set( "xb_PopupStringX", "" ); + + // Set the menu cvars + switch( popup ) + { + case XB_POPUP_DELETE_CONFIRM: + Cvar_Set( "xb_errMessage", "@MENUS_DELETE_SAVE_PROMPT" ); + Cvar_Set( "xb_PopupTitle", "" ); + break; + + case XB_POPUP_DISKFULL: + Cvar_Set( "xb_errMessage", va( SE_GetString("MENUS_NO_SPACE_PLEASE_FREE"), blocksNeeded ) ); + break; + + case XB_POPUP_DISKFULL_DURING_SAVE: + Cvar_Set( "xb_errMessage", "@MENUS_NO_SPACE" ); + break; + + case XB_POPUP_YOU_ARE_DEAD: + Cvar_Set( "xb_errMessage", "@MENUS_SAVE_WHEN_DEAD"); + break; + + case XB_POPUP_SAVE_COMPLETE: + Cvar_Set( "xb_errMessage", "@MENUS_SAVE_GAME_COMPLETE" ); + Cvar_Set( "xb_PopupTitle", "" ); + break; + + case XB_POPUP_OVERWRITE_CONFIRM: + Cvar_Set( "xb_errMessage", "@MENUS_OVERWRITE_PROMPT" ); + Cvar_Set( "xb_PopupTitle", "" ); + break; + + case XB_POPUP_LOAD_FAILED: + { + Cvar_Set( "xb_errMessage", "@MENUS_DAMAGED_SAVE" ); + } + break; + + case XB_POPUP_LOAD_CHECKPOINT_FAILED: + { char messagebuffer[128]; + + strcpy (messagebuffer,SE_GetString("MENUS_NO_LOAD_1")); + strcat (messagebuffer, " "); + strcat (messagebuffer, SE_GetString("MENUS_CHECKPOINT_LOWER")); + strcat (messagebuffer, " "); + strcat (messagebuffer,SE_GetString("MENUS_NO_LOAD_2")); + Cvar_Set( "xb_errMessage", messagebuffer ); + Cvar_Set( "xb_PopupTitle", "" ); + } + break; + case XB_POPUP_QUIT_CONFIRM: + { + Cvar_Set( "xb_errMessage", "@MENUS_QUIT_CURRENT_GAME_AND"); + Cvar_Set( "xb_PopupTitle", "@MENUS_QUIT" ); + } + break; + + case XB_POPUP_SAVING: + { + Cvar_Set( "xb_errMessage", "@MENUS_SAVING"); + } + break; + + + case XB_POPUP_LOAD_CONFIRM: + { + if ( svs.clients[0].frames[svs.clients[0].netchan.outgoingSequence & PACKET_MASK].ps.stats[STAT_HEALTH] <= 0) + { + // player dead + Cvar_Set( "xb_errMessage", "@MENUS_LOAD_CONFIRM2"); + } + else + { + Cvar_Set( "xb_errMessage", "@MENUS_LOAD_CONFIRM"); + } + } + break; + case XB_POPUP_LOAD_CONFIRM_CHECKPOINT: + { + if ( svs.clients[0].frames[svs.clients[0].netchan.outgoingSequence & PACKET_MASK].ps.stats[STAT_HEALTH] <= 0) + { + // player dead + Cvar_Set( "xb_errMessage", "@MENUS_LOAD_CONFIRM2"); + } + else + { + Cvar_Set( "xb_errMessage", "@MENUS_LOAD_CONFIRM"); + } + break; + } + case XB_POPUP_TOO_MANY_SAVES: + { + Cvar_Set( "xb_errMessage", "@MENUS_TOO_MANY_SAVES"); + } + break; + case XB_POPUP_CONFIRM_INVITE: + { + Cvar_Set( "xb_errMessage", "@MENUS_CONFIRM_JOIN" ); + break; + } + case XB_POPUP_CORRUPT_SETTINGS: + { + Cvar_Set( "xb_errMessage", "@MENUS_CORRUPT_SETTINGS" ); + break; + } + case XB_POPUP_DISKFULL_BOTH: + { + Cvar_Set( "xb_errMessage", va( SE_GetString("MENUS_NO_SPACE_PLEASE_FREE"), blocksNeeded ) ); + break; + } + + case XB_POPUP_TESTING_SAVE: + { + Cvar_Set( "xb_errMessage", "@MENUS_LOADING_SAVEGAME" ); + break; + } + + case XB_POPUP_CORRUPT_SCREENSHOT: + { + Cvar_Set( "xb_errMessage", "@MENUS_CORRUPT_SCREENSHOT" ); + break; + } + + case XB_POPUP_CONFIRM_NEW_1: + case XB_POPUP_CONFIRM_NEW_2: + case XB_POPUP_CONFIRM_NEW_3: + { + Cvar_Set( "xb_errMessage", "@MENUS_CONFIRM_NEW_MISSION" ); + break; + } + + default: + Com_Error( ERR_FATAL, "ERROR: Invalid popup type %i\n", popup ); + } + + // Display the menu - but first make sure to close it, in case it's open. + // Fixes a bug where openMenuCount gets screwed up. + Menus_CloseByName( "xbox_error_popup" ); + Menus_ActivateByName( "xbox_error_popup" ); +} + +extern unsigned long getGameBlocks(char * filename); +// Accepts a response to the currently dislpayed popup. +// Does whatever is necessary based on which popup was visible, and what the response was. +void UI_xboxPopupResponse( void ) +{ + if( sPopup == XB_POPUP_NONE ) + Com_Error( ERR_FATAL, "ERROR: Got a popup response with no valid context\n" ); + + int response = Cvar_VariableIntegerValue( "xb_errResponse" ); + + if(response == 2) + return; + + + switch( sPopup ) + { + + case XB_POPUP_DELETE_CONFIRM: + if( response == 0 ) // A + { + Menus_CloseByName( "xbox_error_popup" ); + ui_DeleteGame(); + } + else if( response == 1 ) // B + { + Menus_CloseByName( "xbox_error_popup" ); + } + break; + + case XB_POPUP_DISKFULL: + if( response == 0 ) // A continue + { + // Continue without saving + Menus_CloseByName( "xbox_error_popup" ); + XB_Startup( STARTUP_INVITE_CHECK ); + return; + } + else if( response == 1 ) // B + { + openDashBoardMemory(); + } + break; + + case XB_POPUP_DISKFULL_DURING_SAVE: + if( response == 0 ) // A continue + { + // Continue (without saving) + Menus_CloseByName( "xbox_error_popup" ); + } + else if( response == 1 ) // B + { + return; + } + break; + case XB_POPUP_YOU_ARE_DEAD: + if( response == 0) + { + // Continue without saving + Menus_CloseByName( "xbox_error_popup" ); + } + else + { + return; + } + break; + case XB_POPUP_SAVE_COMPLETE : + if( response == 0 ) // A continue + { + Menus_CloseByName( "xbox_error_popup" ); + } + else + { + return; + } + break; + case XB_POPUP_OVERWRITE_CONFIRM: + if( response == 0 ) // A + { + unsigned long blocks; + unsigned long gameblocks; + blocks = SG_BlocksLeft(); + if ( blocks < SG_SaveGameSize()) + { + //read the blocks out of the game? + //gameblocks = getGameBlocks(s_savedata[s_savegame.currentLine].currentSaveFileName); + // if ( blocks + gameblocks >= SG_SaveGameSize()) + { + itemDef_t item; + item.parent = Menu_GetFocused(); + item.window.flags = 0; //err, item is fake here, but we want a valid flag before calling runscript + Item_RunScript( &item, "uiscript genericpopup saving ; delay 1 ; uiScript deletegame ; uiScript savegame ; defer always ; close xbox_error_popup ; uiScript genericpopup savecomplete" ); + return; + // UI_xboxErrorPopup(XB_POPUP_DISKFULL); + } + // else + // { + // Menus_CloseByName( "xbox_error_popup" ); + // UI_xboxErrorPopup(XB_POPUP_DISKFULL); + // return; + // } + } + else + { + itemDef_t item; + item.parent = Menu_GetFocused(); + item.window.flags = 0; //err, item is fake here, but we want a valid flag before calling runscript + Item_RunScript( &item, "uiscript genericpopup saving ; delay 1 ; uiScript deletegame ; uiScript savegame ; defer always ; close xbox_error_popup ; uiScript genericpopup savecomplete" ); + return; + //DELETE + // ui_DeleteGame(); + //SAVE + // ui_SaveGame(); + // //call to show save success + + } + // Menus_CloseByName( "xbox_error_popup" ); + + } + else if( response == 1 ) // B + { + Menus_CloseByName( "xbox_error_popup" ); + } + break; + + case XB_POPUP_LOAD_FAILED: + if( response == 0 ) // A continue + { + Menus_CloseByName( "xbox_error_popup" ); + } + else + { + return; + } + break; + + case XB_POPUP_LOAD_CHECKPOINT_FAILED: + if( response == 0 ) // A continue + { + + if ( cls.state == CA_ACTIVE ) + { + Menus_CloseAll(); + UI_SetActiveMenu( "ingame",NULL ); + } + else + Menus_CloseByName( "xbox_error_popup" ); + + } + else + { + return; + } + break; + case XB_POPUP_QUIT_CONFIRM: + if( response == 0 ) // A continue + { + Cbuf_ExecuteText( EXEC_APPEND, "disconnect\n" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + } + else + { + Menus_CloseByName( "xbox_error_popup" ); + } + break; + + case XB_POPUP_SAVING: + return; + case XB_POPUP_LOAD_CONFIRM: + if( response == 0 ) // A continue + { + itemDef_t item; + item.parent = Menu_GetFocused(); + item.window.flags = 0; //err, item is fake here, but we want a valid flag before calling runscript + Item_RunScript( &item, "uiscript loadgame" ); + return; + } + else + { + Menus_CloseByName( "xbox_error_popup" ); + } + break; + case XB_POPUP_LOAD_CONFIRM_CHECKPOINT: + if( response == 0 ) + { + // Hackery + Menus_CloseByName( "xbox_error_popup" ); + UI_xboxErrorPopup( XB_POPUP_TESTING_SAVE ); + ui.Cmd_ExecuteText(EXEC_APPEND,"wait ; wait ; wait ; wait ; load *respawn\n"); + return; + } + else + { + Menus_CloseByName( "xbox_error_popup" ); + + } + break; + case XB_POPUP_TOO_MANY_SAVES: + if(response == 0) + { + Menus_CloseByName( "xbox_error_popup" ); + } + else + { + return; + } + break; + + case XB_POPUP_CONFIRM_INVITE: + if( response == 0 ) // A - join game + { + // Never returns + extern void Sys_JoinInvite( void ); + Sys_JoinInvite(); + } + else + { + Menus_CloseByName( "xbox_error_popup" ); + XB_Startup( STARTUP_FINISH ); + return; + } + + case XB_POPUP_CORRUPT_SETTINGS: + if( response == 0 ) // A - accept + { + Settings.Delete(); + Menus_CloseByName( "xbox_error_popup" ); + XB_Startup( STARTUP_COMBINED_SPACE_CHECK ); + return; + } + else + { + return; + } + + case XB_POPUP_DISKFULL_BOTH: + if( response == 0 ) + { + // Continue without saving + Settings.Disable(); + Menus_CloseByName( "xbox_error_popup" ); + XB_Startup( STARTUP_INVITE_CHECK ); + return; + } + else + { + // Go to dashboard to free up memory + openDashBoardMemory(); + } + + case XB_POPUP_TESTING_SAVE: + // No response is valid, this is just informational while the save is read/checked + return; + + case XB_POPUP_CORRUPT_SCREENSHOT: + if( response == 0 ) // A - continue + { + Menus_CloseByName( "xbox_error_popup" ); + break; + } + else + { + // Invalid response + return; + } + + case XB_POPUP_CONFIRM_NEW_1: + case XB_POPUP_CONFIRM_NEW_2: + case XB_POPUP_CONFIRM_NEW_3: + if( response == 0 ) // A - continue + { + Cvar_SetValue( "cl_paused", 1 ); + UI_DecrementForcePowerLevel(); + Menus_CloseAll(); + Menus_OpenByName( va("ingameMissionSelect%d", (sPopup - XB_POPUP_CONFIRM_NEW_1) + 1) ); + } + else + { + Menus_CloseByName( "xbox_error_popup" ); + } + break; + + default: + Com_Error( ERR_FATAL, "ERROR: Invalid popup type %i\n", sPopup ); + } + + // If we get here, the user gave a valid response to our popup. Clear the context: + sPopup = XB_POPUP_NONE; +} + + diff --git a/code/ui/ui_playerinfo.h b/code/ui/ui_playerinfo.h new file mode 100644 index 0000000..9508d17 --- /dev/null +++ b/code/ui/ui_playerinfo.h @@ -0,0 +1,74 @@ +#ifndef __UI_PLAYERINFO_H__ +#define __UI_PLAYERINFO_H__ + +#include "../game/bg_public.h" +#include "../game/anims.h" + +//FIXME ripped from cg_local.h +typedef struct { + int oldFrame; + int oldFrameTime; // time when ->oldFrame was exactly on + + int frame; + int frameTime; // time when ->frame will be exactly on + + float backlerp; + + float yawAngle; + qboolean yawing; + float pitchAngle; + qboolean pitching; + + int animationNumber; // + animation_t *animation; + int animationTime; // time when the first frame of the animation will be exact +} lerpFrame_t; + +typedef struct { + // model info + qhandle_t legsModel; + qhandle_t legsSkin; + lerpFrame_t legs; + + qhandle_t torsoModel; + qhandle_t torsoSkin; + lerpFrame_t torso; + + qhandle_t headModel; + qhandle_t headSkin; + + animation_t animations[MAX_ANIMATIONS]; + + qhandle_t weaponModel; + vec3_t flashDlightColor; + int muzzleFlashTime; + + // currently in use drawing parms + vec3_t viewAngles; + vec3_t moveAngles; + weapon_t currentWeapon; + int legsAnim; + int torsoAnim; + + // animation vars + weapon_t weapon; + weapon_t lastWeapon; + weapon_t pendingWeapon; + int weaponTimer; + int pendingLegsAnim; + int torsoAnimationTimer; + + int pendingTorsoAnim; + int legsAnimationTimer; + + qboolean chat; + qboolean looking; +} playerInfo_t; + + +void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ); +void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char* headmodel ); +void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNum, qboolean chat ); +qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName ); + +#endif //__UI_PLAYERINFO_H__ \ No newline at end of file diff --git a/code/ui/ui_public.h b/code/ui/ui_public.h new file mode 100644 index 0000000..b8b0fc7 --- /dev/null +++ b/code/ui/ui_public.h @@ -0,0 +1,250 @@ +#ifndef __UI_PUBLIC_H__ +#define __UI_PUBLIC_H__ + + +#include "../client/keycodes.h" + + +#define UI_API_VERSION 3 + + +typedef struct { + //============== general Quake services ================== + + // print message on the local console + void (*Printf)( const char *fmt, ... ); + + // abort the game + void (*Error)( int level, const char *fmt, ... ); + + // console variable interaction + void (*Cvar_Set)( const char *name, const char *value ); + float (*Cvar_VariableValue)( const char *var_name ); + void (*Cvar_VariableStringBuffer)( const char *var_name, char *buffer, int bufsize ); + void (*Cvar_SetValue)( const char *var_name, float value ); + void (*Cvar_Reset)( const char *name ); + void (*Cvar_Create)( const char *var_name, const char *var_value, int flags ); + void (*Cvar_InfoStringBuffer)( int bit, char *buffer, int bufsize ); + + // console command interaction + int (*Argc)( void ); + void (*Argv)( int n, char *buffer, int bufferLength ); + void (*Cmd_ExecuteText)( int exec_when, const char *text ); + void (*Cmd_TokenizeString)( const char *text ); + + // filesystem access + int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); + int (*FS_Read)( void *buffer, int len, fileHandle_t f ); + int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); + void (*FS_FCloseFile)( fileHandle_t f ); + int (*FS_GetFileList)( const char *path, const char *extension, char *listbuf, int bufsize ); + int (*FS_ReadFile)( const char *name, void **buf ); + void (*FS_FreeFile)( void *buf ); + + // =========== renderer function calls ================ + + qhandle_t (*R_RegisterModel)( const char *name ); // returns rgb axis if not found + qhandle_t (*R_RegisterSkin)( const char *name ); // returns all white if not found + qhandle_t (*R_RegisterShader)( const char *name ); // returns white if not found + qhandle_t (*R_RegisterShaderNoMip)( const char *name ); // returns white if not found + qhandle_t (*R_RegisterFont)( const char *name ); // returns 0 for bad font +#ifdef _XBOX // No default arguments on function pointers + int R_Font_StrLenPixels(const char *text, const int setIndex, const float scale = 1.0f) + { + return RE_Font_StrLenPixels(text, setIndex, scale); + } + int R_Font_HeightPixels(const int setIndex, const float scale = 1.0f) + { + return RE_Font_HeightPixels(setIndex, scale); + } + void R_Font_DrawString(int ox, int oy, const char *text, const float *rgba, const int setIndex, int iMaxPixelWidth, const float scale = 1.0f) + { + RE_Font_DrawString(ox, oy, text, rgba, setIndex, iMaxPixelWidth, scale); + } +#else + int (*R_Font_StrLenPixels)(const char *text, const int setIndex, const float scale = 1.0f); + int (*R_Font_HeightPixels)(const int setIndex, const float scale = 1.0f); + void (*R_Font_DrawString)(int ox, int oy, const char *text, const float *rgba, const int setIndex, int iMaxPixelWidth, const float scale = 1.0f); +#endif + int (*R_Font_StrLenChars)(const char *text); + qboolean (*Language_IsAsian) (void); + qboolean (*Language_UsesSpaces) (void); + unsigned int (*AnyLanguage_ReadCharFromString)( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation /* = NULL */); + + // a scene is built up by calls to R_ClearScene and the various R_Add functions. + // Nothing is drawn until R_RenderScene is called. + void (*R_ClearScene)( void ); + void (*R_AddRefEntityToScene)( const refEntity_t *re ); + void (*R_AddPolyToScene)( qhandle_t hShader , int numVerts, const polyVert_t *verts ); + void (*R_AddLightToScene)( const vec3_t org, float intensity, float r, float g, float b ); + void (*R_RenderScene)( const refdef_t *fd ); + + void (*R_ModelBounds)( qhandle_t handle, vec3_t mins, vec3_t maxs ); + + void (*R_SetColor)( const float *rgba ); // NULL = 1,1,1,1 + void (*R_DrawStretchPic) ( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); // 0 = white + void (*R_ScissorPic) ( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); // 0 = white + + // force a screen update, only used during gamestate load + void (*UpdateScreen)( void ); + + // stuff for savegame screenshots... +#ifdef _XBOX + void (*PrecacheScreenshot)( void ); +#endif + + //========= model collision =============== + + // R_LerpTag is only valid for md3 models + void (*R_LerpTag)( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ); + + // =========== sound function calls =============== + + void (*S_StartLocalSound)( sfxHandle_t sfxHandle, int channelNum ); + sfxHandle_t (*S_RegisterSound)( const char* name); + void (*S_StartLocalLoopingSound)( sfxHandle_t sfxHandle); + void (*S_StopSounds)( void ); + + + // =========== getting save game picture =============== + void (*DrawStretchRaw) (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty); + //qboolean(*SG_GetSaveImage)( const char *psPathlessBaseName, void *pvAddress ); + int (*SG_GetSaveGameComment)(const char *psPathlessBaseName, char *sComment, char *sMapName); + qboolean (*SG_GameAllowedToSaveHere)(qboolean inCamera); + void (*SG_StoreSaveGameComment)(const char *sComment); + //byte *(*SCR_GetScreenshot)(qboolean *); + + // =========== data shared with the client system ============= + + // keyboard and key binding interaction + void (*Key_KeynumToStringBuf)( int keynum, char *buf, int buflen ); + void (*Key_GetBindingBuf)( int keynum, char *buf, int buflen ); + void (*Key_SetBinding)( int keynum, const char *binding ); + qboolean (*Key_IsDown)( int keynum ); + qboolean (*Key_GetOverstrikeMode)( void ); + void (*Key_SetOverstrikeMode)( qboolean state ); + void (*Key_ClearStates)( void ); + int (*Key_GetCatcher)( void ); + void (*Key_SetCatcher)( int catcher ); + + void (*GetClipboardData)( char *buf, int bufsize ); + + void (*GetGlconfig)( glconfig_t *config ); + + connstate_t (*GetClientState)( void ); + + void (*GetConfigString)( int index, char* buff, int buffsize ); + + int (*Milliseconds)( void ); + void (*Draw_DataPad)(int HUDType); +} uiimport_t; + +typedef enum { + DP_HUD=0, + DP_OBJECTIVES, + DP_WEAPONS, + DP_INVENTORY, + DP_FORCEPOWERS +}dpTypes_t; + +typedef enum { + UI_ERROR, + UI_PRINT, + UI_MILLISECONDS, + UI_CVAR_SET, + UI_CVAR_VARIABLEVALUE, + UI_CVAR_VARIABLESTRINGBUFFER, + UI_CVAR_SETVALUE, + UI_CVAR_RESET, + UI_CVAR_CREATE, + UI_CVAR_INFOSTRINGBUFFER, + UI_ARGC, // 10 + UI_ARGV, + UI_CMD_EXECUTETEXT, + UI_FS_FOPENFILE, + UI_FS_READ, + UI_FS_WRITE, + UI_FS_FCLOSEFILE, + UI_FS_GETFILELIST, + UI_R_REGISTERMODEL, + UI_R_REGISTERSKIN, + UI_R_REGISTERSHADERNOMIP, // 20 + UI_R_CLEARSCENE, + UI_R_ADDREFENTITYTOSCENE, + UI_R_ADDPOLYTOSCENE, + UI_R_ADDLIGHTTOSCENE, + UI_R_RENDERSCENE, + UI_R_SETCOLOR, + UI_R_DRAWSTRETCHPIC, + UI_UPDATESCREEN, + UI_CM_LERPTAG, + UI_CM_LOADMODEL, // 30 + UI_S_REGISTERSOUND, + UI_S_STARTLOCALSOUND, + UI_KEY_KEYNUMTOSTRINGBUF, + UI_KEY_GETBINDINGBUF, + UI_KEY_SETBINDING, + UI_KEY_ISDOWN, + UI_KEY_GETOVERSTRIKEMODE, + UI_KEY_SETOVERSTRIKEMODE, + UI_KEY_CLEARSTATES, + UI_KEY_GETCATCHER, // 40 + UI_KEY_SETCATCHER, + UI_GETCLIPBOARDDATA, + UI_GETGLCONFIG, + UI_GETCLIENTSTATE, + UI_GETCONFIGSTRING, + UI_LAN_GETPINGQUEUECOUNT, + UI_LAN_CLEARPING, + UI_LAN_GETPING, + UI_LAN_GETPINGINFO, + UI_CVAR_REGISTER, // 50 + UI_CVAR_UPDATE, + UI_MEMORY_REMAINING, + UI_GET_CDKEY, + UI_SET_CDKEY, + UI_R_REGISTERFONT, + UI_R_MODELBOUNDS, + UI_PC_ADD_GLOBAL_DEFINE, + UI_PC_LOAD_SOURCE, + UI_PC_FREE_SOURCE, + UI_PC_READ_TOKEN, // 60 + UI_PC_SOURCE_FILE_AND_LINE, + UI_S_STOPBACKGROUNDTRACK, + UI_S_STARTBACKGROUNDTRACK, + UI_REAL_TIME, + UI_LAN_GETSERVERCOUNT, + UI_LAN_GETSERVERADDRESSSTRING, + UI_LAN_GETSERVERINFO, + UI_LAN_MARKSERVERVISIBLE, + UI_LAN_UPDATEVISIBLEPINGS, + UI_LAN_RESETPINGS, // 70 + UI_LAN_LOADCACHEDSERVERS, + UI_LAN_SAVECACHEDSERVERS, + UI_LAN_ADDSERVER, + UI_LAN_REMOVESERVER, + UI_CIN_PLAYCINEMATIC, + UI_CIN_STOPCINEMATIC, + UI_CIN_RUNCINEMATIC, + UI_CIN_DRAWCINEMATIC, + UI_CIN_SETEXTENTS, + UI_R_REMAP_SHADER, // 80 + UI_VERIFY_CDKEY, + UI_LAN_SERVERSTATUS, + UI_LAN_GETSERVERPING, + UI_LAN_SERVERISVISIBLE, + UI_LAN_COMPARESERVERS, + + UI_MEMSET = 100, + UI_MEMCPY, + UI_STRNCPY, + UI_SIN, + UI_COS, + UI_ATAN2, + UI_SQRT, + UI_FLOOR, + UI_CEIL +} uiImport_t; + +#endif diff --git a/code/ui/ui_saber.cpp b/code/ui/ui_saber.cpp new file mode 100644 index 0000000..28ec08f --- /dev/null +++ b/code/ui/ui_saber.cpp @@ -0,0 +1,866 @@ +// +/* +======================================================================= + +USER INTERFACE SABER LOADING & DISPLAY CODE + +======================================================================= +*/ + +// leave this at the top of all UI_xxxx files for PCH reasons... +// +#include "../server/exe_headers.h" +#include "ui_local.h" +#include "ui_shared.h" +#include "../ghoul2/G2.h" + +#define MAX_SABER_DATA_SIZE 0x8000 +// On Xbox, static linking lets us steal the buffer from wp_saberLoad +// Just make sure that the saber data size is the same +#ifdef _XBOX +extern char SaberParms[MAX_SABER_DATA_SIZE]; +#else +char SaberParms[MAX_SABER_DATA_SIZE]; +#endif +qboolean ui_saber_parms_parsed = qfalse; + +static qhandle_t redSaberGlowShader; +static qhandle_t redSaberCoreShader; +static qhandle_t orangeSaberGlowShader; +static qhandle_t orangeSaberCoreShader; +static qhandle_t yellowSaberGlowShader; +static qhandle_t yellowSaberCoreShader; +static qhandle_t greenSaberGlowShader; +static qhandle_t greenSaberCoreShader; +static qhandle_t blueSaberGlowShader; +static qhandle_t blueSaberCoreShader; +static qhandle_t purpleSaberGlowShader; +static qhandle_t purpleSaberCoreShader; +void UI_CacheSaberGlowGraphics( void ) +{//FIXME: these get fucked by vid_restarts + redSaberGlowShader = re.RegisterShader( "gfx/effects/sabers/red_glow" ); + redSaberCoreShader = re.RegisterShader( "gfx/effects/sabers/red_line" ); + orangeSaberGlowShader = re.RegisterShader( "gfx/effects/sabers/orange_glow" ); + orangeSaberCoreShader = re.RegisterShader( "gfx/effects/sabers/orange_line" ); + yellowSaberGlowShader = re.RegisterShader( "gfx/effects/sabers/yellow_glow" ); + yellowSaberCoreShader = re.RegisterShader( "gfx/effects/sabers/yellow_line" ); + greenSaberGlowShader = re.RegisterShader( "gfx/effects/sabers/green_glow" ); + greenSaberCoreShader = re.RegisterShader( "gfx/effects/sabers/green_line" ); + blueSaberGlowShader = re.RegisterShader( "gfx/effects/sabers/blue_glow" ); + blueSaberCoreShader = re.RegisterShader( "gfx/effects/sabers/blue_line" ); + purpleSaberGlowShader = re.RegisterShader( "gfx/effects/sabers/purple_glow" ); + purpleSaberCoreShader = re.RegisterShader( "gfx/effects/sabers/purple_line" ); +} + +qboolean UI_ParseLiteral( const char **data, const char *string ) +{ + const char *token; + + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) + { + ui.Printf( "unexpected EOF\n" ); + return qtrue; + } + + if ( Q_stricmp( token, string ) ) + { + ui.Printf( "required string '%s' missing\n", string ); + return qtrue; + } + + return qfalse; +} + +qboolean UI_SaberParseParm( const char *saberName, const char *parmname, char *saberData ) +{ + const char *token; + const char *value; + const char *p; + + if ( !saberName || !saberName[0] ) + { + return qfalse; + } + + //try to parse it out + p = SaberParms; + COM_BeginParseSession(); + + // look for the right saber + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return qfalse; + } + + if ( !Q_stricmp( token, saberName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { + return qfalse; + } + + if ( UI_ParseLiteral( &p, "{" ) ) + { + return qfalse; + } + + // parse the saber info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + ui.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", saberName ); + return qfalse; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + if ( !Q_stricmp( token, parmname ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + strcpy( saberData, value ); + return qtrue; + } + + SkipRestOfLine( &p ); + continue; + } + + return qfalse; +} + +qboolean UI_SaberProperNameForSaber( const char *saberName, char *saberProperName ) +{ + return UI_SaberParseParm( saberName, "name", saberProperName ); +} + +qboolean UI_SaberModelForSaber( const char *saberName, char *saberModel ) +{ + return UI_SaberParseParm( saberName, "saberModel", saberModel ); +} + +qboolean UI_SaberSkinForSaber( const char *saberName, char *saberSkin ) +{ + return UI_SaberParseParm( saberName, "customSkin", saberSkin ); +} + +qboolean UI_SaberTypeForSaber( const char *saberName, char *saberType ) +{ + return UI_SaberParseParm( saberName, "saberType", saberType ); +} + +int UI_SaberNumBladesForSaber( const char *saberName ) +{ + char numBladesString[8]={0}; + UI_SaberParseParm( saberName, "numBlades", numBladesString ); + int numBlades = atoi( numBladesString ); + if ( numBlades < 1 ) + { + numBlades = 1; + } + else if ( numBlades > 8 ) + { + numBlades = 8; + } + return numBlades; +} + +float UI_SaberBladeLengthForSaber( const char *saberName, int bladeNum ) +{ + char lengthString[8]={0}; + float length = 40.0f; + UI_SaberParseParm( saberName, "saberLength", lengthString ); + if ( lengthString[0] ) + { + length = atof( lengthString ); + if ( length < 0.0f ) + { + length = 0.0f; + } + } + + UI_SaberParseParm( saberName, va("saberLength%d", bladeNum+1), lengthString ); + if ( lengthString[0] ) + { + length = atof( lengthString ); + if ( length < 0.0f ) + { + length = 0.0f; + } + } + + return length; +} + +float UI_SaberBladeRadiusForSaber( const char *saberName, int bladeNum ) +{ + char radiusString[8]={0}; + float radius = 3.0f; + UI_SaberParseParm( saberName, "saberRadius", radiusString ); + if ( radiusString[0] ) + { + radius = atof( radiusString ); + if ( radius < 0.0f ) + { + radius = 0.0f; + } + } + + UI_SaberParseParm( saberName, va("saberRadius%d", bladeNum+1), radiusString ); + if ( radiusString[0] ) + { + radius = atof( radiusString ); + if ( radius < 0.0f ) + { + radius = 0.0f; + } + } + + return radius; +} + +void UI_SaberLoadParms( void ) +{ + int len, totallen, saberExtFNLen, fileCnt, i; + char *buffer, *holdChar, *marker; + char saberExtensionListBuf[2048]; // The list of file names read in + + //ui.Printf( "UI Parsing *.sab saber definitions\n" ); + + ui_saber_parms_parsed = qtrue; + UI_CacheSaberGlowGraphics(); + + //set where to store the first one + totallen = 0; + marker = SaberParms; + marker[0] = '\0'; + + //now load in the sabers + fileCnt = ui.FS_GetFileList("ext_data/sabers", ".sab", saberExtensionListBuf, sizeof(saberExtensionListBuf) ); + + holdChar = saberExtensionListBuf; + for ( i = 0; i < fileCnt; i++, holdChar += saberExtFNLen + 1 ) + { + saberExtFNLen = strlen( holdChar ); + + len = ui.FS_ReadFile( va( "ext_data/sabers/%s", holdChar), (void **) &buffer ); + + if ( len == -1 ) + { + ui.Printf( "UI_SaberLoadParms: error reading %s\n", holdChar ); + } + else + { + if ( totallen && *(marker-1) == '}' ) + {//don't let it end on a } because that should be a stand-alone token + strcat( marker, " " ); + totallen++; + marker++; + } + len = COM_Compress( buffer ); + + if ( totallen + len >= MAX_SABER_DATA_SIZE ) { + Com_Error( ERR_FATAL, "UI_SaberLoadParms: ran out of space before reading %s\n(you must make the .npc files smaller)", holdChar ); + } + strcat( marker, buffer ); + ui.FS_FreeFile( buffer ); + + totallen += len; + marker += len; + } + } +} + +void UI_DoSaber( vec3_t origin, vec3_t dir, float length, float lengthMax, float radius, saber_colors_t color ) +{ + vec3_t mid, rgb={1,1,1}; + qhandle_t blade = 0, glow = 0; + refEntity_t saber; + float radiusmult; + + if ( length < 0.5f ) + { + // if the thing is so short, just forget even adding me. + return; + } + + // Find the midpoint of the saber for lighting purposes + VectorMA( origin, length * 0.5f, dir, mid ); + + switch( color ) + { + case SABER_RED: + glow = redSaberGlowShader; + blade = redSaberCoreShader; + VectorSet( rgb, 1.0f, 0.2f, 0.2f ); + break; + case SABER_ORANGE: + glow = orangeSaberGlowShader; + blade = orangeSaberCoreShader; + VectorSet( rgb, 1.0f, 0.5f, 0.1f ); + break; + case SABER_YELLOW: + glow = yellowSaberGlowShader; + blade = yellowSaberCoreShader; + VectorSet( rgb, 1.0f, 1.0f, 0.2f ); + break; + case SABER_GREEN: + glow = greenSaberGlowShader; + blade = greenSaberCoreShader; + VectorSet( rgb, 0.2f, 1.0f, 0.2f ); + break; + case SABER_BLUE: + glow = blueSaberGlowShader; + blade = blueSaberCoreShader; + VectorSet( rgb, 0.2f, 0.4f, 1.0f ); + break; + case SABER_PURPLE: + glow = purpleSaberGlowShader; + blade = purpleSaberCoreShader; + VectorSet( rgb, 0.9f, 0.2f, 1.0f ); + break; + } + + // always add a light because sabers cast a nice glow before they slice you in half!! or something... + /* + if ( doLight ) + {//FIXME: RGB combine all the colors of the sabers you're using into one averaged color! + cgi_R_AddLightToScene( mid, (length*2.0f) + (random()*8.0f), rgb[0], rgb[1], rgb[2] ); + } + */ + + memset( &saber, 0, sizeof( refEntity_t )); + + // Saber glow is it's own ref type because it uses a ton of sprites, otherwise it would eat up too many + // refEnts to do each glow blob individually + saber.saberLength = length; + + // Jeff, I did this because I foolishly wished to have a bright halo as the saber is unleashed. + // It's not quite what I'd hoped tho. If you have any ideas, go for it! --Pat + if (length < lengthMax ) + { + radiusmult = 1.0 + (2.0 / length); // Note this creates a curve, and length cannot be < 0.5. + } + else + { + radiusmult = 1.0; + } + + float radiusRange = radius * 0.075f; + float radiusStart = radius-radiusRange; + + saber.radius = (radiusStart + crandom() * radiusRange)*radiusmult; + //saber.radius = (2.8f + crandom() * 0.2f)*radiusmult; + + + VectorCopy( origin, saber.origin ); + VectorCopy( dir, saber.axis[0] ); + saber.reType = RT_SABER_GLOW; + saber.customShader = glow; + saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff; + //saber.renderfx = rfx; + + DC->addRefEntityToScene( &saber ); + + // Do the hot core + VectorMA( origin, length, dir, saber.origin ); + VectorMA( origin, -1, dir, saber.oldorigin ); + saber.customShader = blade; + saber.reType = RT_LINE; + radiusStart = radius/3.0f; + saber.radius = (radiusStart + crandom() * radiusRange)*radiusmult; +// saber.radius = (1.0 + crandom() * 0.2f)*radiusmult; + + DC->addRefEntityToScene( &saber ); +} + +saber_colors_t TranslateSaberColor( const char *name ) +{ + if ( !Q_stricmp( name, "red" ) ) + { + return SABER_RED; + } + if ( !Q_stricmp( name, "orange" ) ) + { + return SABER_ORANGE; + } + if ( !Q_stricmp( name, "yellow" ) ) + { + return SABER_YELLOW; + } + if ( !Q_stricmp( name, "green" ) ) + { + return SABER_GREEN; + } + if ( !Q_stricmp( name, "blue" ) ) + { + return SABER_BLUE; + } + if ( !Q_stricmp( name, "purple" ) ) + { + return SABER_PURPLE; + } + if ( !Q_stricmp( name, "random" ) ) + { + return ((saber_colors_t)(Q_irand( SABER_ORANGE, SABER_PURPLE ))); + } + return SABER_BLUE; +} + +saberType_t TranslateSaberType( const char *name ) +{ + if ( !Q_stricmp( name, "SABER_SINGLE" ) ) + { + return SABER_SINGLE; + } + if ( !Q_stricmp( name, "SABER_STAFF" ) ) + { + return SABER_STAFF; + } + if ( !Q_stricmp( name, "SABER_BROAD" ) ) + { + return SABER_BROAD; + } + if ( !Q_stricmp( name, "SABER_PRONG" ) ) + { + return SABER_PRONG; + } + if ( !Q_stricmp( name, "SABER_DAGGER" ) ) + { + return SABER_DAGGER; + } + if ( !Q_stricmp( name, "SABER_ARC" ) ) + { + return SABER_ARC; + } + if ( !Q_stricmp( name, "SABER_SAI" ) ) + { + return SABER_SAI; + } + if ( !Q_stricmp( name, "SABER_CLAW" ) ) + { + return SABER_CLAW; + } + if ( !Q_stricmp( name, "SABER_LANCE" ) ) + { + return SABER_LANCE; + } + if ( !Q_stricmp( name, "SABER_STAR" ) ) + { + return SABER_STAR; + } + if ( !Q_stricmp( name, "SABER_TRIDENT" ) ) + { + return SABER_TRIDENT; + } + if ( !Q_stricmp( name, "SABER_SITH_SWORD" ) ) + { + return SABER_SITH_SWORD; + } + return SABER_SINGLE; +} + +void UI_SaberDrawBlade( itemDef_t *item, char *saberName, int saberModel, saberType_t saberType, vec3_t origin, float curYaw, int bladeNum ) +{ + char bladeColorString[MAX_QPATH]; + vec3_t angles={0}; + +// if ( item->flags&(ITF_ISANYSABER) && item->flags&(ITF_ISCHARACTER) ) + { //it's bolted to a dude! + angles[YAW] = curYaw; + } +// else +// { +// angles[PITCH] = curYaw; +// angles[ROLL] = 90; +// } + + if ( saberModel >= item->ghoul2.size() ) + {//uhh... invalid index! + return; + } + + if ( (item->flags&ITF_ISSABER) && saberModel < 2 ) + { + DC->getCVarString( "ui_saber_color", bladeColorString, sizeof(bladeColorString) ); + } + else//if ( item->flags&ITF_ISSABER2 ) - presumed + { + DC->getCVarString( "ui_saber2_color", bladeColorString, sizeof(bladeColorString) ); + } + saber_colors_t bladeColor = TranslateSaberColor( bladeColorString ); + + float bladeLength = UI_SaberBladeLengthForSaber( saberName, bladeNum ); + float bladeRadius = UI_SaberBladeRadiusForSaber( saberName, bladeNum ); + vec3_t bladeOrigin={0}; + vec3_t axis[3]={0}; + mdxaBone_t boltMatrix; + qboolean tagHack = qfalse; + + char *tagName = va( "*blade%d", bladeNum+1 ); + int bolt = DC->g2_AddBolt( &item->ghoul2[saberModel], tagName ); + + if ( bolt == -1 ) + { + tagHack = qtrue; + //hmm, just fall back to the most basic tag (this will also make it work with pre-JKA saber models + bolt = DC->g2_AddBolt( &item->ghoul2[saberModel], "*flash" ); + if ( bolt == -1 ) + {//no tag_flash either?!! + bolt = 0; + } + } + + DC->g2_GetBoltMatrix( item->ghoul2, saberModel, bolt, &boltMatrix, angles, origin, uiInfo.uiDC.realTime, NULL, vec3_origin );//NULL was cgs.model_draw + + // work the matrix axis stuff into the original axis and origins used. + DC->g2_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, bladeOrigin); + DC->g2_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_X, axis[0]);//front (was NEGATIVE_Y, but the md3->glm exporter screws up this tag somethin' awful) + DC->g2_GiveMeVectorFromMatrix(boltMatrix, NEGATIVE_Y, axis[1]);//right + DC->g2_GiveMeVectorFromMatrix(boltMatrix, POSITIVE_Z, axis[2]);//up + + float scale = DC->xscale; + + if ( tagHack ) + { + switch ( saberType ) + { + case SABER_SINGLE: + case SABER_DAGGER: + case SABER_LANCE: + break; + case SABER_STAFF: + if ( bladeNum == 1 ) + { + VectorScale( axis[0], -1, axis[0] ); + VectorMA( bladeOrigin, 16*scale, axis[0], bladeOrigin ); + } + break; + case SABER_BROAD: + if ( bladeNum == 0 ) + { + VectorMA( bladeOrigin, -1*scale, axis[1], bladeOrigin ); + } + else if ( bladeNum == 1 ) + { + VectorMA( bladeOrigin, 1*scale, axis[1], bladeOrigin ); + } + break; + case SABER_PRONG: + if ( bladeNum == 0 ) + { + VectorMA( bladeOrigin, -3*scale, axis[1], bladeOrigin ); + } + else if ( bladeNum == 1 ) + { + VectorMA( bladeOrigin, 3*scale, axis[1], bladeOrigin ); + } + break; + case SABER_ARC: + VectorSubtract( axis[1], axis[2], axis[1] ); + VectorNormalize( axis[1] ); + switch ( bladeNum ) + { + case 0: + VectorMA( bladeOrigin, 8*scale, axis[0], bladeOrigin ); + VectorScale( axis[0], 0.75f, axis[0] ); + VectorScale( axis[1], 0.25f, axis[1] ); + VectorAdd( axis[0], axis[1], axis[0] ); + break; + case 1: + VectorScale( axis[0], 0.25f, axis[0] ); + VectorScale( axis[1], 0.75f, axis[1] ); + VectorAdd( axis[0], axis[1], axis[0] ); + break; + case 2: + VectorMA( bladeOrigin, -8*scale, axis[0], bladeOrigin ); + VectorScale( axis[0], -0.25f, axis[0] ); + VectorScale( axis[1], 0.75f, axis[1] ); + VectorAdd( axis[0], axis[1], axis[0] ); + break; + case 3: + VectorMA( bladeOrigin, -16*scale, axis[0], bladeOrigin ); + VectorScale( axis[0], -0.75f, axis[0] ); + VectorScale( axis[1], 0.25f, axis[1] ); + VectorAdd( axis[0], axis[1], axis[0] ); + break; + } + break; + case SABER_SAI: + if ( bladeNum == 1 ) + { + VectorMA( bladeOrigin, -3*scale, axis[1], bladeOrigin ); + } + else if ( bladeNum == 2 ) + { + VectorMA( bladeOrigin, 3*scale, axis[1], bladeOrigin ); + } + break; + case SABER_CLAW: + switch ( bladeNum ) + { + case 0: + VectorMA( bladeOrigin, 2*scale, axis[0], bladeOrigin ); + VectorMA( bladeOrigin, 2*scale, axis[2], bladeOrigin ); + break; + case 1: + VectorMA( bladeOrigin, 2*scale, axis[0], bladeOrigin ); + VectorMA( bladeOrigin, 2*scale, axis[2], bladeOrigin ); + VectorMA( bladeOrigin, 2*scale, axis[1], bladeOrigin ); + break; + case 2: + VectorMA( bladeOrigin, 2*scale, axis[0], bladeOrigin ); + VectorMA( bladeOrigin, 2*scale, axis[2], bladeOrigin ); + VectorMA( bladeOrigin, -2*scale, axis[1], bladeOrigin ); + break; + } + break; + case SABER_STAR: + switch ( bladeNum ) + { + case 0: + VectorMA( bladeOrigin, 8*scale, axis[0], bladeOrigin ); + break; + case 1: + VectorScale( axis[0], 0.33f, axis[0] ); + VectorScale( axis[2], 0.67f, axis[2] ); + VectorAdd( axis[0], axis[2], axis[0] ); + VectorMA( bladeOrigin, 8*scale, axis[0], bladeOrigin ); + break; + case 2: + VectorScale( axis[0], -0.33f, axis[0] ); + VectorScale( axis[2], 0.67f, axis[2] ); + VectorAdd( axis[0], axis[2], axis[0] ); + VectorMA( bladeOrigin, 8*scale, axis[0], bladeOrigin ); + break; + case 3: + VectorScale( axis[0], -1, axis[0] ); + VectorMA( bladeOrigin, 8*scale, axis[0], bladeOrigin ); + break; + case 4: + VectorScale( axis[0], -0.33f, axis[0] ); + VectorScale( axis[2], -0.67f, axis[2] ); + VectorAdd( axis[0], axis[2], axis[0] ); + VectorMA( bladeOrigin, 8*scale, axis[0], bladeOrigin ); + break; + case 5: + VectorScale( axis[0], 0.33f, axis[0] ); + VectorScale( axis[2], -0.67f, axis[2] ); + VectorAdd( axis[0], axis[2], axis[0] ); + VectorMA( bladeOrigin, 8*scale, axis[0], bladeOrigin ); + break; + } + break; + case SABER_TRIDENT: + switch ( bladeNum ) + { + case 0: + VectorMA( bladeOrigin, 24*scale, axis[0], bladeOrigin ); + break; + case 1: + VectorMA( bladeOrigin, -6*scale, axis[1], bladeOrigin ); + VectorMA( bladeOrigin, 24*scale, axis[0], bladeOrigin ); + break; + case 2: + VectorMA( bladeOrigin, 6*scale, axis[1], bladeOrigin ); + VectorMA( bladeOrigin, 24*scale, axis[0], bladeOrigin ); + break; + case 3: + VectorMA( bladeOrigin, -32*scale, axis[0], bladeOrigin ); + VectorScale( axis[0], -1, axis[0] ); + break; + } + break; + case SABER_SITH_SWORD: + //no blade + break; + } + } + if ( saberType == SABER_SITH_SWORD ) + {//draw no blade + return; + } + + UI_DoSaber( bladeOrigin, axis[0], bladeLength, bladeLength, bladeRadius, bladeColor ); +} + +extern qboolean ItemParse_asset_model_go( itemDef_t *item, const char *name ); +extern qboolean ItemParse_model_g2skin_go( itemDef_t *item, const char *skinName ); +void UI_GetSaberForMenu( char *saber, int saberNum ) +{ + char saberTypeString[MAX_QPATH]={0}; + saberType_t saberType = SABER_NONE; + + if ( saberNum == 0 ) + { + DC->getCVarString( "g_saber", saber, MAX_QPATH ); + } + else + { + DC->getCVarString( "g_saber2", saber, MAX_QPATH ); + } + //read this from the sabers.cfg + UI_SaberTypeForSaber( saber, saberTypeString ); + if ( saberTypeString[0] ) + { + saberType = TranslateSaberType( saberTypeString ); + } + + switch ( uiInfo.movesTitleIndex ) + { + case 0://MD_ACROBATICS: + break; + case 1://MD_SINGLE_FAST: + case 2://MD_SINGLE_MEDIUM: + case 3://MD_SINGLE_STRONG: + if ( saberType != SABER_SINGLE ) + { + Q_strncpyz(saber,"single_1",MAX_QPATH,qtrue); + } + break; + case 4://MD_DUAL_SABERS: + if ( saberType != SABER_SINGLE ) + { + Q_strncpyz(saber,"single_1",MAX_QPATH,qtrue); + } + break; + case 5://MD_SABER_STAFF: + if ( saberType == SABER_SINGLE || saberType == SABER_NONE ) + { + Q_strncpyz(saber,"dual_1",MAX_QPATH,qtrue); + } + break; + } + +} + +void UI_SaberDrawBlades( itemDef_t *item, vec3_t origin, float curYaw ) +{ + //NOTE: only allows one saber type in view at a time + char saber[MAX_QPATH]; + int saberNum = 0; + int saberModel = 0; + int numSabers = 1; + + if ( (item->flags&ITF_ISCHARACTER)//hacked sabermoves sabers in character's hand + && uiInfo.movesTitleIndex == 4 /*MD_DUAL_SABERS*/ ) + { + numSabers = 2; + } + + for ( saberNum = 0; saberNum < numSabers; saberNum++ ) + { + if ( (item->flags&ITF_ISCHARACTER) )//hacked sabermoves sabers in character's hand + { + UI_GetSaberForMenu( saber, saberNum ); + saberModel = saberNum + 1; + } + else if ( (item->flags&ITF_ISSABER) ) + { + DC->getCVarString( "ui_saber", saber, sizeof(saber) ); + saberModel = 0; + } + else if ( (item->flags&ITF_ISSABER2) ) + { + DC->getCVarString( "ui_saber2", saber, sizeof(saber) ); + saberModel = 0; + } + else + { + return; + } + if ( saber[0] ) + { + int numBlades = UI_SaberNumBladesForSaber( saber ); + if ( numBlades ) + {//okay, here we go, time to draw each blade... + char saberTypeString[MAX_QPATH]={0}; + UI_SaberTypeForSaber( saber, saberTypeString ); + saberType_t saberType = TranslateSaberType( saberTypeString ); + for ( int curBlade = 0; curBlade < numBlades; curBlade++ ) + { + UI_SaberDrawBlade( item, saber, saberModel, saberType, origin, curYaw, curBlade ); + } + } + } + } +} + +void UI_SaberAttachToChar( itemDef_t *item ) +{ + int numSabers = 1; + int saberNum = 0; + + if ( item->ghoul2.size() > 2 && item->ghoul2[2].mModelindex >=0 ) + {//remove any extra models + DC->g2_RemoveGhoul2Model(item->ghoul2, 2); + } + if ( item->ghoul2.size() > 1 && item->ghoul2[1].mModelindex >=0) + {//remove any extra models + DC->g2_RemoveGhoul2Model(item->ghoul2, 1); + } + + if ( uiInfo.movesTitleIndex == 4 /*MD_DUAL_SABERS*/ ) + { + numSabers = 2; + } + + if ( Cvar_Get("ui_move_title", "4", 0)->integer == 4) + { + numSabers = 2; + } + + for ( saberNum = 0; saberNum < numSabers; saberNum++ ) + { + //bolt sabers + char modelPath[MAX_QPATH]; + char skinPath[MAX_QPATH]; + char saber[MAX_QPATH]; + + UI_GetSaberForMenu( saber, saberNum ); + + if ( UI_SaberModelForSaber( saber, modelPath ) ) + {//successfully found a model + int g2Saber = DC->g2_InitGhoul2Model(item->ghoul2, modelPath, 0, 0, 0, 0, 0); //add the model + if (g2Saber) + { + //get the customSkin, if any + if ( UI_SaberSkinForSaber( saber, skinPath ) ) + { + int g2skin = DC->registerSkin(skinPath); + DC->g2_SetSkin( &item->ghoul2[g2Saber], 0, g2skin );//this is going to set the surfs on/off matching the skin file + } + else + { + DC->g2_SetSkin( &item->ghoul2[g2Saber], -1, 0 );//turn off custom skin + } + int boltNum; + if ( saberNum == 0 ) + { + boltNum = G2API_AddBolt(&item->ghoul2[0], "*r_hand"); + } + else + { + boltNum = G2API_AddBolt(&item->ghoul2[0], "*l_hand"); + } + G2API_AttachG2Model(&item->ghoul2[g2Saber], &item->ghoul2[0], boltNum, 0); + } + } + } +} diff --git a/code/ui/ui_shared.cpp b/code/ui/ui_shared.cpp new file mode 100644 index 0000000..496376f --- /dev/null +++ b/code/ui/ui_shared.cpp @@ -0,0 +1,12322 @@ +// +// string allocation/managment + +// leave this at the top of all UI_xxxx files for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "ui_local.h" + +//rww - added for ui ghoul2 models +#define UI_SHARED_CPP + +#include "../game/anims.h" +#include "../cgame/animtable.h" + +#include "ui_shared.h" +#include "menudef.h" + +void UI_LoadMenus(const char *menuFile, qboolean reset); + +#ifdef _XBOX +#include "../win32/glw_win_dx8.h" +#include "../renderer/tr_lightmanager.h" + +//MAP HACK! +extern cvar_t *cl_mapname; +void Menu_MapHack(int key); + +//JLF DEMOCODE MPMOVED + +//support for attract mode demo timer +#define DEMO_TIME_MAX 45000 //g_demoTimeBeforeStart +int g_demoLastKeypress = 0; //milliseconds +bool g_ReturnToSplash = false; +bool g_runningDemo = false; + +void G_DemoStart(); +void G_DemoEnd(); +void G_DemoKeypress(); + +void PlayDemo(); +void UpdateDemoTimer(); +bool TestDemoTimer(); + +//END DEMOCODE +//JLF used by sliders MPMOVED +#define TICK_COUNT 16 + +//JLF MORE PROTOTYPES MPMOVED +qboolean Item_SetFocus(itemDef_t *item, float x, float y); +qboolean Item_HandleAction(itemDef_t * item); +qboolean Item_HandleSelectionNext(itemDef_t * item); +qboolean Item_HandleSelectionPrev(itemDef_t * item); + + +#endif + +extern vmCvar_t ui_char_color_red; +extern vmCvar_t ui_char_color_green; +extern vmCvar_t ui_char_color_blue; + +void *UI_Alloc( int size ); + +void Controls_GetConfig( void ); +void Fade(int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount); +void Item_Init(itemDef_t *item); +void Item_InitControls(itemDef_t *item); +qboolean Item_Parse(itemDef_t *item); +void Item_RunScript(itemDef_t *item, const char *s); +void Item_SetupKeywordHash(void); +void Item_Text_AutoWrapped_Paint(itemDef_t *item); +void Item_UpdatePosition(itemDef_t *item); +void Item_ValidateTypeData(itemDef_t *item); +void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t); +itemDef_t *Menu_FindItemByName(menuDef_t *menu, const char *p); +itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name); +void Menu_Paint(menuDef_t *menu, qboolean forcePaint); +void Menu_SetupKeywordHash(void); +void Menus_ShowItems(const char *menuName); +qboolean ParseRect(const char **p, rectDef_t *r); +const char *String_Alloc(const char *p); +void ToWindowCoords(float *x, float *y, windowDef_t *window); +void Window_Paint(Window *w, float fadeAmount, float fadeClamp, float fadeCycle); +int Item_ListBox_ThumbDrawPosition(itemDef_t *item); +int Item_ListBox_ThumbPosition(itemDef_t *item); +int Item_ListBox_MaxScroll(itemDef_t *item); +static qboolean Rect_ContainsPoint(rectDef_t *rect, float x, float y) ; +static qboolean Item_Paint(itemDef_t *item, qboolean bDraw); +int Item_TextScroll_ThumbDrawPosition ( itemDef_t *item ); +static void Item_TextScroll_BuildLines ( itemDef_t* item ); + +qboolean Item_EnableShowViaCvar(itemDef_t *item, int flag) ; + +//static qboolean debugMode = qfalse; +static qboolean g_waitingForKey = qfalse; +static qboolean g_editingField = qfalse; + +static itemDef_t *g_bindItem = NULL; +static itemDef_t *g_editItem = NULL; +static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any ) + +#define DOUBLE_CLICK_DELAY 300 +static int lastListBoxClickTime = 0; + +static void (*captureFunc) (void *p) = NULL; +static void *captureData = NULL; + +//const char defaultString[10] = {"default"}; +#ifndef _XBOX +#ifdef CGAME +#define MEM_POOL_SIZE 128 * 1024 +#else +#define MEM_POOL_SIZE 1024 * 1024 +#endif +#endif // _XBOX + +#define SCROLL_TIME_START 500 +#define SCROLL_TIME_ADJUST 150 +#define SCROLL_TIME_ADJUSTOFFSET 40 +#define SCROLL_TIME_FLOOR 20 + +typedef struct scrollInfo_s { + int nextScrollTime; + int nextAdjustTime; + int adjustValue; + int scrollKey; + float xStart; + float yStart; + itemDef_t *item; + qboolean scrollDir; +} scrollInfo_t; + +static scrollInfo_t scrollInfo; + +#ifndef _XBOX +static char memoryPool[MEM_POOL_SIZE]; +#endif +static int allocPoint, outOfMemory; + +displayContextDef_t *DC = NULL; + +menuDef_t Menus[MAX_MENUS]; // defined menus +int menuCount = 0; // how many + +menuDef_t *menuStack[MAX_OPEN_MENUS]; +int openMenuCount = 0; + +static int strPoolIndex = 0; +static char strPool[STRING_POOL_SIZE]; + +typedef struct stringDef_s { + struct stringDef_s *next; + const char *str; +} stringDef_t; + +#define HASH_TABLE_SIZE 2048 + +static int strHandleCount = 0; +static stringDef_t *strHandle[HASH_TABLE_SIZE]; + +typedef struct itemFlagsDef_s { + char *string; + int value; +} itemFlagsDef_t; + +itemFlagsDef_t itemFlags [] = { +"WINDOW_INACTIVE", WINDOW_INACTIVE, +NULL, NULL +}; + +char *styles [] = { +"WINDOW_STYLE_EMPTY", +"WINDOW_STYLE_FILLED", +"WINDOW_STYLE_GRADIENT", +"WINDOW_STYLE_SHADER", +"WINDOW_STYLE_TEAMCOLOR", +"WINDOW_STYLE_CINEMATIC", +NULL +}; + +char *types [] = { +"ITEM_TYPE_TEXT", +"ITEM_TYPE_BUTTON", +"ITEM_TYPE_RADIOBUTTON", +"ITEM_TYPE_CHECKBOX", +"ITEM_TYPE_EDITFIELD", +"ITEM_TYPE_COMBO", +"ITEM_TYPE_LISTBOX", +"ITEM_TYPE_MODEL", +"ITEM_TYPE_OWNERDRAW", +"ITEM_TYPE_NUMERICFIELD", +"ITEM_TYPE_SLIDER", +"ITEM_TYPE_YESNO", +"ITEM_TYPE_MULTI", +"ITEM_TYPE_BIND", +"ITEM_TYPE_TEXTSCROLL", +NULL +}; + +char *alignment [] = { +"ITEM_ALIGN_LEFT", +"ITEM_ALIGN_CENTER", +"ITEM_ALIGN_RIGHT", +NULL +}; + +/* +================== +Init_Display + +Initializes the display with a structure to all the drawing routines + ================== +*/ +void Init_Display(displayContextDef_t *dc) +{ + DC = dc; +} + +/* +================== +Window_Init + +Initializes a window structure ( windowDef_t ) with defaults + +================== +*/ +void Window_Init(Window *w) +{ + memset(w, 0, sizeof(windowDef_t)); + w->borderSize = 1; + w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0; +// w->cinematic = -1; +} + +/* +================= +PC_SourceError +================= +*/ +#ifndef _XBOX +void PC_SourceError(int handle, char *format, ...) +{ + int line; + char filename[128]; + va_list argptr; + static char string[4096]; + + va_start (argptr, format); + vsprintf (string, format, argptr); + va_end (argptr); + + filename[0] = '\0'; + line = 0; + + Com_Printf(S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string); +} +#endif + + +/* +================= +PC_ParseStringMem +================= +*/ +//static vector RetryPool; +//void AddMenuPackageRetryKey(const char *psSPPackage) +//{ +// RetryPool.push_back(psSPPackage); +//} +qboolean PC_ParseStringMem(const char **out) +{ + const char *temp; + + if (PC_ParseString(&temp)) + { + return qfalse; + } + + *(out) = String_Alloc(temp); + + return qtrue; +} + +/* +================= +PC_ParseRect +================= +*/ +qboolean PC_ParseRect(rectDef_t *r) +{ + if (!PC_ParseFloat(&r->x)) + { + if (!PC_ParseFloat(&r->y)) + { + if (!PC_ParseFloat(&r->w)) + { + if (!PC_ParseFloat(&r->h)) + { + return qtrue; + } + } + } + } + return qfalse; +} + + +/* +================= +PC_Script_Parse +================= +*/ +qboolean PC_Script_Parse(const char **out) +{ + char script[4096]; +// pc_token_t token; + char *token2; + + script[0]=0; + // scripts start with { and have ; separated command lists.. commands are command, arg.. + // basically we want everything between the { } as it will be interpreted at run time + + token2 = PC_ParseExt(); + if (!token2) + { + return qfalse; + } + + if (*token2 !='{') + { + return qfalse; + } + + while ( 1 ) + { + token2 = PC_ParseExt(); + if (!token2) + { + return qfalse; + } + + if (*token2 =='}') // End of the script? + { + *out = String_Alloc(script); + return qtrue; + } + + if (*(token2 +1) != '\0') + { + Q_strcat(script, sizeof(script), va("\"%s\"", token2)); + } + else + { + Q_strcat(script, sizeof(script), token2); + } + Q_strcat(script, sizeof(script), " "); + } +} + +//-------------------------------------------------------------------------------------------- +// Menu Keyword Parse functions +//-------------------------------------------------------------------------------------------- + +/* +================= +MenuParse_font +================= +*/ +qboolean MenuParse_font( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_ParseStringMem(&menu->font)) + { + return qfalse; + } + + if (!DC->Assets.fontRegistered) + { + DC->Assets.qhMediumFont = DC->registerFont(menu->font); + DC->Assets.fontRegistered = qtrue; + } + return qtrue; +} + + +/* +================= +MenuParse_name +================= +*/ +qboolean MenuParse_name(itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_ParseStringMem((const char **) &menu->window.name)) + { + return qfalse; + } + +// if (Q_stricmp(menu->window.name, "main") == 0) +// { + // default main as having focus +// menu->window.flags |= WINDOW_HASFOCUS; +// } + return qtrue; +} + +/* +================= +MenuParse_fullscreen +================= +*/ + +qboolean MenuParse_fullscreen( itemDef_t *item ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseInt((int *) &menu->fullScreen)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_rect +================= +*/ + +qboolean MenuParse_rect( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_ParseRect(&menu->window.rect)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_style +================= +*/ +qboolean MenuParse_style( itemDef_t *item) +{ + int i; + const char *tempStr; + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseString(&tempStr)) + { + return qfalse; + } + + i=0; + while (styles[i]) + { + if (Q_stricmp(tempStr,styles[i])==0) + { + menu->window.style = i; + break; + } + i++; + } + + if (styles[i] == NULL) + { + PC_ParseWarning(va("Unknown menu style value '%s'",tempStr)); + } + return qtrue; +} + + +/* +================= +MenuParse_visible +================= +*/ +qboolean MenuParse_visible( itemDef_t *item ) +{ + int i; + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseInt(&i)) + { + return qfalse; + } + + if (i) + { + menu->window.flags |= WINDOW_VISIBLE; + } + return qtrue; +} + +/* +================= +MenuParse_ignoreescape +================= +*/ +qboolean MenuParse_ignoreescape( itemDef_t *item ) +{ + int i; + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseInt(&i)) + { + return qfalse; + } + + if (i) + { + menu->window.flags |= WINDOW_IGNORE_ESCAPE; + } + return qtrue; +} + +/* +================= +MenuParse_onOpen +================= +*/ +qboolean MenuParse_onOpen( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Script_Parse(&menu->onOpen)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_onClose +================= +*/ +qboolean MenuParse_onClose( itemDef_t *item ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Script_Parse(&menu->onClose)) + { + return qfalse; + } + return qtrue; +} + +//JLFACCEPT MPMOVED +/* +================= +MenuParse_onAccept +================= +*/ +qboolean MenuParse_onAccept( itemDef_t *item ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Script_Parse(&menu->onAccept)) + { + return qfalse; + } + return qtrue; +} + + +/* +================= +MenuParse_onESC +================= +*/ +qboolean MenuParse_onESC( itemDef_t *item ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Script_Parse(&menu->onESC)) + { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_xScript( itemDef_t *item ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Script_Parse(&menu->xScript)) + { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_yScript( itemDef_t *item ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Script_Parse(&menu->yScript)) + { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_whiteScript( itemDef_t *item ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Script_Parse(&menu->whiteScript)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_border +================= +*/ +qboolean MenuParse_border( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseInt(&menu->window.border)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_borderSize +================= +*/ +qboolean MenuParse_borderSize( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseFloat(&menu->window.borderSize)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_backcolor +================= +*/ +qboolean MenuParse_backcolor( itemDef_t *item ) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) + { + if (PC_ParseFloat(&f)) + { + return qfalse; + } + menu->window.backColor[i] = f; + } + return qtrue; +} + +/* +================= +MenuParse_forecolor +================= +*/ +qboolean MenuParse_forecolor( itemDef_t *item) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) + { + if (PC_ParseFloat(&f)) + { + return qfalse; + } + if (f < 0) + { //special case for player color + menu->window.flags |= WINDOW_PLAYERCOLOR; + return qtrue; + } + menu->window.foreColor[i] = f; + menu->window.flags |= WINDOW_FORECOLORSET; + } + return qtrue; +} + +/* +================= +MenuParse_bordercolor +================= +*/ +qboolean MenuParse_bordercolor( itemDef_t *item ) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) + { + if (PC_ParseFloat(&f)) + { + return qfalse; + } + menu->window.borderColor[i] = f; + } + return qtrue; +} + +/* +================= +MenuParse_focuscolor +================= +*/ +qboolean MenuParse_focuscolor( itemDef_t *item) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) + { + if (PC_ParseFloat(&f)) + { + return qfalse; + } + menu->focusColor[i] = f; + } + return qtrue; +} + + +/* +================= +MenuParse_focuscolor +================= +*/ +qboolean MenuParse_appearanceIncrement( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseFloat(&menu->appearanceIncrement)) + { + return qfalse; + } + return qtrue; +} + + + +/* +================= +MenuParse_descAlignment +================= +*/ +qboolean MenuParse_descAlignment( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + const char *tempStr; + int i; + + if (PC_ParseString(&tempStr)) + { + return qfalse; + } + + i=0; + while (alignment[i]) + { + if (Q_stricmp(tempStr,alignment[i])==0) + { + menu->descAlignment = i; + break; + } + i++; + } + + if (alignment[i] == NULL) + { + PC_ParseWarning(va("Unknown desc alignment value '%s'",tempStr)); + } + + return qtrue; +} + +/* +================= +MenuParse_descTextStyle +================= +*/ +qboolean MenuParse_descTextStyle( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseInt(&menu->descTextStyle)) + { + return qfalse; + } + return qtrue; +} +/* +================= +MenuParse_descX +================= +*/ +qboolean MenuParse_descX( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseInt(&menu->descX)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_descY +================= +*/ +qboolean MenuParse_descY( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseInt(&menu->descY)) + { + return qfalse; + } + return qtrue; +} + + +/* +================= +MenuParse_descScale +================= +*/ +qboolean MenuParse_descScale( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseFloat(&menu->descScale)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_descColor +================= +*/ +qboolean MenuParse_descColor( itemDef_t *item) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) + { + if (PC_ParseFloat(&f)) + { + return qfalse; + } + menu->descColor[i] = f; + } + return qtrue; +} + +/* +================= +MenuParse_disablecolor +================= +*/ +qboolean MenuParse_disablecolor( itemDef_t *item) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) + { + if (PC_ParseFloat(&f)) + { + return qfalse; + } + menu->disableColor[i] = f; + } + return qtrue; +} + + +/* +================= +MenuParse_outlinecolor +================= +*/ +/* +qboolean MenuParse_outlinecolor( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseColor(&menu->window.outlineColor)) + { + return qfalse; + } + return qtrue; +} +*/ + +/* +================= +MenuParse_background +================= +*/ +qboolean MenuParse_background( itemDef_t *item) +{ + const char *buff; + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseString(&buff)) + { + return qfalse; + } + + menu->window.background = ui.R_RegisterShaderNoMip(buff); + return qtrue; +} + +/* +================= +MenuParse_cinematic +================= +*/ +/* +qboolean MenuParse_cinematic( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_ParseStringMem((const char **) &menu->window.cinematicName)) + { + return qfalse; + } + return qtrue; +} +*/ + +/* +================= +MenuParse_ownerdrawFlag +================= +*/ +qboolean MenuParse_ownerdrawFlag( itemDef_t *item) +{ + int i; + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseInt(&i)) + { + return qfalse; + } + menu->window.ownerDrawFlags |= i; + return qtrue; +} + +/* +================= +MenuParse_ownerdraw +================= +*/ +qboolean MenuParse_ownerdraw( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseInt(&menu->window.ownerDraw)) + { + return qfalse; + } + return qtrue; +} + + +/* +================= +MenuParse_popup +================= +*/ +qboolean MenuParse_popup( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + menu->window.flags |= WINDOW_POPUP; + return qtrue; +} + + +/* +================= +MenuParse_outOfBounds +================= +*/ +qboolean MenuParse_outOfBounds( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + menu->window.flags |= WINDOW_OOB_CLICK; + return qtrue; +} + +/* +================= +MenuParse_soundLoop +================= +*/ +qboolean MenuParse_soundLoop( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_ParseStringMem((const char **) &menu->soundName)) + { + return qfalse; + } + return qtrue; +} + +/* +================ +MenuParse_fadeClamp +================ +*/ +qboolean MenuParse_fadeClamp( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseFloat(&menu->fadeClamp)) + { + return qfalse; + } + return qtrue; +} + +/* +================ +MenuParse_fadeAmount +================ +*/ +qboolean MenuParse_fadeAmount( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseFloat(&menu->fadeAmount)) + { + return qfalse; + } + return qtrue; +} + + +/* +================ +MenuParse_fadeCycle +================ +*/ +qboolean MenuParse_fadeCycle( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (PC_ParseInt(&menu->fadeCycle)) + { + return qfalse; + } + return qtrue; +} + + +/* +================ +MenuParse_itemDef +================ +*/ +qboolean MenuParse_itemDef( itemDef_t *item) +{ + menuDef_t *menu = (menuDef_t*)item; + if (menu->itemCount < MAX_MENUITEMS) + { + // Hack. Check for a levelname after "itemDef" - if so, only parse it + // if we're on that level. Avoids loading wedge all over the place. + const char *token; + if (PC_ParseString(&token)) + return qfalse; + + // Check for non-brace could be "protocol" or "wedge": + if (*token != '{') + { + const char *mapname = Cvar_VariableString( "mapname" ); + + // Wedge should only be loaded in t2_wedge: + if( (Q_stricmp(token, "wedge") == 0) && // itemDef is wedge + (Q_stricmp(mapname, "t2_wedge") != 0) ) // not t2_wedge + { + // Skip this itemDef entirely, but don't fail: + PC_SkipBracedSection(); + return qtrue; + } + + // C3PO should only be loaded in tier2, and academy3: + if( (Q_stricmp(token, "protocol") == 0) && // itemDef is c3po + (!strstr(mapname, "t2_")) && // not a t2 level + (Q_stricmp(mapname, "academy3") != 0) ) // not academy3 + { + // Skip this itemDef entirely, but don't fail: + PC_SkipBracedSection(); + return qtrue; + } + + // OK. We're on the right level. Eat the real '{': + PC_ParseString(&token); + if (*token != '{') + return qfalse; + } + + menu->items[menu->itemCount] = (struct itemDef_s *) UI_Alloc(sizeof(itemDef_t)); + Item_Init(menu->items[menu->itemCount]); + if (!Item_Parse(menu->items[menu->itemCount])) + { + return qfalse; + } + Item_InitControls(menu->items[menu->itemCount]); + menu->items[menu->itemCount++]->parent = menu; + } + else + { + PC_ParseWarning(va("Exceeded item/menu max of %d", MAX_MENUITEMS)); + } + return qtrue; +} + +#define KEYWORDHASH_SIZE 512 + +typedef struct keywordHash_s +{ + char *keyword; + qboolean (*func)(itemDef_t *item); + struct keywordHash_s *next; +} keywordHash_t; + +keywordHash_t menuParseKeywords[] = { + {"appearanceIncrement", MenuParse_appearanceIncrement}, + {"backcolor", MenuParse_backcolor, }, + {"background", MenuParse_background, }, + {"border", MenuParse_border, }, + {"bordercolor", MenuParse_bordercolor, }, + {"borderSize", MenuParse_borderSize, }, +// {"cinematic", MenuParse_cinematic, }, + {"descAlignment", MenuParse_descAlignment }, + {"descTextStyle", MenuParse_descTextStyle }, + {"desccolor", MenuParse_descColor }, + {"descX", MenuParse_descX }, + {"descY", MenuParse_descY }, + {"descScale", MenuParse_descScale }, + {"disablecolor", MenuParse_disablecolor, }, + {"fadeClamp", MenuParse_fadeClamp, }, + {"fadeCycle", MenuParse_fadeCycle, }, + {"fadeAmount", MenuParse_fadeAmount, }, + {"focuscolor", MenuParse_focuscolor, }, + {"font", MenuParse_font, }, + {"forecolor", MenuParse_forecolor, }, + {"fullscreen", MenuParse_fullscreen, }, + {"itemDef", MenuParse_itemDef, }, + {"name", MenuParse_name, }, + {"onClose", MenuParse_onClose, }, +//JLFACCEPT MPMOVED + {"onAccept", MenuParse_onAccept, }, + {"onESC", MenuParse_onESC, }, + {"xScript", MenuParse_xScript, }, + {"yScript", MenuParse_yScript, }, + {"whiteScript", MenuParse_whiteScript, }, + {"onOpen", MenuParse_onOpen, }, +// {"outlinecolor", MenuParse_outlinecolor, }, + {"outOfBoundsClick", MenuParse_outOfBounds, }, + {"ownerdraw", MenuParse_ownerdraw, }, + {"ownerdrawFlag", MenuParse_ownerdrawFlag,}, + {"popup", MenuParse_popup, }, + {"rect", MenuParse_rect, }, + {"soundLoop", MenuParse_soundLoop, }, + {"style", MenuParse_style, }, + {"visible", MenuParse_visible, }, + {"ignoreescape", MenuParse_ignoreescape, }, + {NULL, NULL, } +}; + +keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE]; + +/* +================ +KeywordHash_Key +================ +*/ +int KeywordHash_Key(const char *keyword) +{ + int register hash, i; + + hash = 0; + for (i = 0; keyword[i] != '\0'; i++) { + if (keyword[i] >= 'A' && keyword[i] <= 'Z') + hash += (keyword[i] + ('a' - 'A')) * (119 + i); + else + hash += keyword[i] * (119 + i); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (KEYWORDHASH_SIZE-1); + return hash; +} + +/* +================ +KeywordHash_Add +================ +*/ +void KeywordHash_Add(keywordHash_t *table[], keywordHash_t *key) +{ + int hash; + + hash = KeywordHash_Key(key->keyword); + key->next = table[hash]; + table[hash] = key; +} + +/* +=============== +KeywordHash_Find +=============== +*/ +keywordHash_t *KeywordHash_Find(keywordHash_t *table[], const char *keyword) +{ + keywordHash_t *key; + int hash; + + hash = KeywordHash_Key(keyword); + for (key = table[hash]; key; key = key->next) + { + if (!Q_stricmp(key->keyword, keyword)) + return key; + } + return NULL; +} + +/* +================ +hashForString + +return a hash value for the string +================ +*/ +static long hashForString(const char *str) +{ + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (str[i] != '\0') + { + letter = tolower((unsigned char)str[i]); + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (HASH_TABLE_SIZE-1); + return hash; +} + +/* +================= +String_Alloc +================= +*/ +const char *String_Alloc(const char *p) +{ + int len; + long hash; + stringDef_t *str, *last; + static const char *staticNULL = ""; + + if (p == NULL) + { + return NULL; + } + + if (*p == 0) + { + return staticNULL; + } + + hash = hashForString(p); + + str = strHandle[hash]; + while (str) + { + if (strcmp(p, str->str) == 0) + { + return str->str; + } + str = str->next; + } + + len = strlen(p); + if (len + strPoolIndex + 1 < STRING_POOL_SIZE) + { + int ph = strPoolIndex; + strcpy(&strPool[strPoolIndex], p); + strPoolIndex += len + 1; + + str = strHandle[hash]; + last = str; + while (last && last->next) + { + last = last->next; + } + + str = (stringDef_s *) UI_Alloc( sizeof(stringDef_t)); + str->next = NULL; + str->str = &strPool[ph]; + if (last) + { + last->next = str; + } + else + { + strHandle[hash] = str; + } + + return &strPool[ph]; + } + else + { + Com_Printf("WARNING: Ran out of strPool space\n"); + } + + return NULL; +} + +/* +================= +String_Report +================= +*/ +void String_Report(void) +{ + float f; + Com_Printf("Memory/String Pool Info\n"); + Com_Printf("----------------\n"); + f = strPoolIndex; + f /= STRING_POOL_SIZE; + f *= 100; + Com_Printf("String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE); +#ifdef _XBOX + Com_Printf("Memory Pool is using %i bytes.\n", allocPoint); +#else + f = allocPoint; + f /= MEM_POOL_SIZE; + f *= 100; + Com_Printf("Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE); +#endif +} + +/* +================= +String_Init +================= +*/ +void String_Init(void) +{ + int i; + + for (i = 0; i < HASH_TABLE_SIZE; i++) + { + strHandle[i] = 0; + } + + strHandleCount = 0; + strPoolIndex = 0; + UI_InitMemory(); + Item_SetupKeywordHash(); + Menu_SetupKeywordHash(); + + if (DC && DC->getBindingBuf) + { + Controls_GetConfig(); + } +} + + + +//--------------------------------------------------------------------------------------------------------- +// Memory +//--------------------------------------------------------------------------------------------------------- + +/* +=============== +UI_Alloc +=============== +*/ +void *UI_Alloc( int size ) +{ +#ifdef _XBOX + allocPoint += size; + + return Z_Malloc(size, TAG_UI_ALLOC, qfalse, 4); +#else + char *p; + + if ( allocPoint + size > MEM_POOL_SIZE ) + { + outOfMemory = qtrue; + if (DC->Print) + { + DC->Print("UI_Alloc: Failure. Out of memory!\n"); + } + return NULL; + } + + p = &memoryPool[allocPoint]; + + allocPoint += ( size + 15 ) & ~15; + + return p; +#endif +} + +/* +=============== +UI_InitMemory +=============== +*/ +void UI_InitMemory( void ) +{ + allocPoint = 0; + outOfMemory = qfalse; +#ifdef _XBOX + Z_TagFree(TAG_UI_ALLOC); +#endif +} + + +/* +=============== +Menu_ItemsMatchingGroup +=============== +*/ +int Menu_ItemsMatchingGroup(menuDef_t *menu, const char *name) +{ + int i; + int count = 0; + + for (i = 0; i < menu->itemCount; i++) + { + if ((!menu->items[i]->window.name) && (!menu->items[i]->window.group)) + { + Com_Printf(S_COLOR_YELLOW"WARNING: item has neither name or group\n"); + continue; + } + + if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) + { + count++; + } + } + + return count; +} + +/* +=============== +Menu_GetMatchingItemByNumber +=============== +*/ +itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name) +{ + int i; + int count = 0; + for (i = 0; i < menu->itemCount; i++) + { + if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) + { + if (count == index) + { + return menu->items[i]; + } + count++; + } + } + return NULL; +} + +/* +=============== +Menu_FadeItemByName +=============== +*/ +void Menu_FadeItemByName(menuDef_t *menu, const char *p, qboolean fadeOut) +{ + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup(menu, p); + for (i = 0; i < count; i++) + { + item = Menu_GetMatchingItemByNumber(menu, i, p); + if (item != NULL) + { + if (fadeOut) + { + item->window.flags |= (WINDOW_FADINGOUT | WINDOW_VISIBLE); + item->window.flags &= ~WINDOW_FADINGIN; + } + else + { + item->window.flags |= (WINDOW_VISIBLE | WINDOW_FADINGIN); + item->window.flags &= ~WINDOW_FADINGOUT; + } + } + } +} + +/* +========== +Menu_SetItemDecorationByName +========== +*/ +void Menu_SetItemDecorationByName(menuDef_t* menu, const char *p, qboolean bShow) +{ + itemDef_t *item; + int i; + int count; + + count = Menu_ItemsMatchingGroup(menu, p); + + if (!count) + { + Com_Printf(S_COLOR_YELLOW"WARNING: Menu_SetItemDecorationByName - unable to locate any items named :%s\n",p); + } + + for (i = 0; i < count; i++) + { + item = Menu_GetMatchingItemByNumber(menu, i, p); + if (item != NULL) + { + if (bShow) + { + item->window.flags |= WINDOW_DECORATION; + } + else + { + item->window.flags &= ~WINDOW_DECORATION; + } + } + } +} + +/* +=============== +Menu_ShowItemByName +=============== +*/ +void Menu_ShowItemByName(menuDef_t *menu, const char *p, qboolean bShow) +{ + itemDef_t *item; + int i; + int count; + + count = Menu_ItemsMatchingGroup(menu, p); + + if (!count) + { + Com_Printf(S_COLOR_YELLOW"WARNING: Menu_ShowItemByName - unable to locate any items named :%s\n",p); + } + + for (i = 0; i < count; i++) + { + item = Menu_GetMatchingItemByNumber(menu, i, p); + if (item != NULL) + { + if (bShow) + { + item->window.flags |= WINDOW_VISIBLE; + } + else + { + item->window.flags &= ~(WINDOW_VISIBLE | WINDOW_HASFOCUS); + // stop cinematics playing in the window +/* + if (item->window.cinematic >= 0) + { + DC->stopCinematic(item->window.cinematic); + item->window.cinematic = -1; + } +*/ + } + } + } +} + +/* +=============== +Menu_GetFocused +=============== +*/ +menuDef_t *Menu_GetFocused(void) +{ + int i; + + for (i = 0; i < menuCount; i++) + { + if ((Menus[i].window.flags & WINDOW_HASFOCUS) && (Menus[i].window.flags & WINDOW_VISIBLE)) + { + return &Menus[i]; + } + } + + return NULL; +} + +/* +=============== +Menus_OpenByName +=============== +*/ +void Menus_OpenByName(const char *p) +{ + Menus_ActivateByName(p); +} + +/* +=============== +Menus_FindByName +=============== +*/ +menuDef_t *Menus_FindByName(const char *p) +{ + int i; + for (i = 0; i < menuCount; i++) + { + if (Q_stricmp(Menus[i].window.name, p) == 0) + { + return &Menus[i]; + } + } + return NULL; +} + +/* +=============== +Menu_RunCloseScript +=============== +*/ +static void Menu_RunCloseScript(menuDef_t *menu) +{ + if (menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose) + { + itemDef_t item; + item.parent = menu; + Item_RunScript(&item, menu->onClose); + } +} + +/* +=============== +Item_ActivateByName +=============== +*/ +void Item_ActivateByName(const char *menuName,const char *itemName) +{ + itemDef_t *item; + menuDef_t *menu; + + menu = Menus_FindByName(menuName); + + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) menu, itemName); + + if (item != NULL) + { + item->window.flags &= ~WINDOW_INACTIVE; + } +} + +/* +=============== +Menus_CloseByName +=============== +*/ +void Menus_CloseByName(const char *p) +{ + menuDef_t *menu = Menus_FindByName(p); + + // If the menu wasnt found just exit + if (menu == NULL) + { + return; + } + + // Run the close script for the menu + Menu_RunCloseScript(menu); + + // If this window had the focus then take it away + if ( menu->window.flags & WINDOW_HASFOCUS ) + { + // If there is something still in the open menu list then + // set it to have focus now + if ( openMenuCount ) + { + // Subtract one from the open menu count to prepare to + // remove the top menu from the list + openMenuCount -= 1; + + // Set the top menu to have focus now + menuStack[openMenuCount]->window.flags |= WINDOW_HASFOCUS; + + // Remove the top menu from the list + menuStack[openMenuCount] = NULL; + } + } + + // Window is now invisible and doenst have focus + menu->window.flags &= ~(WINDOW_VISIBLE | WINDOW_HASFOCUS); +} + +/* +=============== +Menu_FindItemByName +=============== +*/ +itemDef_t *Menu_FindItemByName(menuDef_t *menu, const char *p) +{ + int i; + if (menu == NULL || p == NULL) + { + return NULL; + } + + for (i = 0; i < menu->itemCount; i++) + { + if (Q_stricmp(p, menu->items[i]->window.name) == 0) + { + return menu->items[i]; + } + } + + return NULL; +} + +/* +=============== +Menu_FindVisibleItemByName +=============== +*/ +itemDef_t *Menu_FindVisibleItemByName(menuDef_t *menu, const char *p) +{ + int i; + if (menu == NULL || p == NULL) + { + return NULL; + } + + for (i = 0; i < menu->itemCount; i++) + { + if (Q_stricmp(p, menu->items[i]->window.name) == 0) + { + if (menu->items[i]->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(menu->items[i], CVAR_ENABLE)) + { + continue; + } + + if (menu->items[i]->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(menu->items[i], CVAR_SHOW)) + { + continue; + } + + if ( !(menu->items[i]->window.flags & WINDOW_VISIBLE) ) + { + continue; + } + + return menu->items[i]; + } + } + + return NULL; +} + +/* +================= +Menu_ClearFocus +================= +*/ +itemDef_t *Menu_ClearFocus(menuDef_t *menu) +{ + int i; + itemDef_t *ret = NULL; + + if (menu == NULL) + { + return NULL; + } + for (i = 0; i < menu->itemCount; i++) + { + if (menu->items[i]->window.flags & WINDOW_HASFOCUS) + { + ret = menu->items[i]; + menu->items[i]->window.flags &= ~WINDOW_HASFOCUS; + if (menu->items[i]->leaveFocus) + { + Item_RunScript(menu->items[i], menu->items[i]->leaveFocus); + } + } + } + return ret; +} + +// Set all the items within a given menu, with the given itemName, to the given shader +void Menu_SetItemBackground(const menuDef_t *menu,const char *itemName, const char *background) +{ + itemDef_t *item; + int j, count; + + if (!menu) // No menu??? + { + return; + } + + count = Menu_ItemsMatchingGroup( (menuDef_t *) menu, itemName); + + for (j = 0; j < count; j++) + { + item = Menu_GetMatchingItemByNumber( (menuDef_t *) menu, j, itemName); + if (item != NULL) + { +// item->window.background = DC->registerShaderNoMip(background); + item->window.background = ui.R_RegisterShaderNoMip(background); + } + } +} + +// Set all the items within a given menu, with the given itemName, to the given text +void Menu_SetItemText(const menuDef_t *menu,const char *itemName, const char *text) +{ + itemDef_t *item; + int j, count; + + if (!menu) // No menu??? + { + return; + } + + count = Menu_ItemsMatchingGroup( (menuDef_t *) menu, itemName); + + for (j = 0; j < count; j++) + { + item = Menu_GetMatchingItemByNumber( (menuDef_t *) menu, j, itemName); + if (item != NULL) + { + if (text[0] == '*') + { + item->cvar = text+1; + // Just copying what was in ItemParse_cvar() + if ( item->typeData) + { + editFieldDef_t *editPtr; + editPtr = (editFieldDef_t*)item->typeData; + editPtr->minVal = -1; + editPtr->maxVal = -1; + editPtr->defVal = -1; + } + } + else + { + if (item->type == ITEM_TYPE_TEXTSCROLL ) + { + char cvartext[1024]; + textScrollDef_t *scrollPtr = (textScrollDef_t*)item->typeData; + if ( scrollPtr ) + { + scrollPtr->startPos = 0; + scrollPtr->endPos = 0; + } + + if (item->cvar) + { + DC->getCVarString(item->cvar, cvartext, sizeof(cvartext)); + item->text = cvartext; + } + else + { + item->text = (char *) text; + } + + Item_TextScroll_BuildLines ( item ); + } + else + { + item->text = (char *) text; + } + } + } + } +} + +/* +================= +Menu_TransitionItemByName +================= +*/ +void Menu_TransitionItemByName(menuDef_t *menu, const char *p, const rectDef_t *rectFrom, const rectDef_t *rectTo, int time, float amt) +{ + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup(menu, p); + for (i = 0; i < count; i++) + { + item = Menu_GetMatchingItemByNumber(menu, i, p); + if (item != NULL) + { + if (!rectFrom) + { + rectFrom = &item->window.rect; //if there are more than one of these with the same name, they'll all use the FIRST one's FROM. + } + item->window.flags |= (WINDOW_INTRANSITION | WINDOW_VISIBLE); + item->window.offsetTime = time; + memcpy(&item->window.rectClient, rectFrom, sizeof(rectDef_t)); + memcpy(&item->window.rectEffects, rectTo, sizeof(rectDef_t)); + item->window.rectEffects2.x = abs(rectTo->x - rectFrom->x) / amt; + item->window.rectEffects2.y = abs(rectTo->y - rectFrom->y) / amt; + item->window.rectEffects2.w = abs(rectTo->w - rectFrom->w) / amt; + item->window.rectEffects2.h = abs(rectTo->h - rectFrom->h) / amt; + Item_UpdatePosition(item); + } + } +} + +/* +================= +Menu_TransitionItemByName +================= +*/ +//JLF MOVED +#define _TRANS3 +#ifdef _TRANS3 +void Menu_Transition3ItemByName(menuDef_t *menu, const char *p, const float minx, const float miny, const float minz, + const float maxx, const float maxy, const float maxz, const float fovtx, const float fovty, + const int time, const float amt) +{ + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup(menu, p); + modelDef_t * modelptr; + for (i = 0; i < count; i++) + { + item = Menu_GetMatchingItemByNumber(menu, i, p); + if (item != NULL) + { + if ( item->type == ITEM_TYPE_MODEL) + { + modelptr = (modelDef_t*)item->typeData; + + item->window.flags |= (WINDOW_INTRANSITIONMODEL | WINDOW_VISIBLE); + item->window.offsetTime = time; + modelptr->fov_x2 = fovtx; + modelptr->fov_y2 = fovty; + VectorSet(modelptr->g2maxs2, maxx, maxy, maxz); + VectorSet(modelptr->g2mins2, minx, miny, minz); + +// //modelptr->g2maxs2.x= maxx; +// modelptr->g2maxs2.y= maxy; +// modelptr->g2maxs2.z= maxz; +// modelptr->g2mins2.x= minx; +// modelptr->g2mins2.y= miny; +// modelptr->g2mins2.z= minz; + +// VectorSet(modelptr->g2maxs2, maxx, maxy, maxz); + + modelptr->g2maxsEffect[0] = abs(modelptr->g2maxs2[0] - modelptr->g2maxs[0]) / amt; + modelptr->g2maxsEffect[1] = abs(modelptr->g2maxs2[1] - modelptr->g2maxs[1]) / amt; + modelptr->g2maxsEffect[2] = abs(modelptr->g2maxs2[2] - modelptr->g2maxs[2]) / amt; + + modelptr->g2minsEffect[0] = abs(modelptr->g2mins2[0] - modelptr->g2mins[0]) / amt; + modelptr->g2minsEffect[1] = abs(modelptr->g2mins2[1] - modelptr->g2mins[1]) / amt; + modelptr->g2minsEffect[2] = abs(modelptr->g2mins2[2] - modelptr->g2mins[2]) / amt; + + + modelptr->fov_Effectx = abs(modelptr->fov_x2 - modelptr->fov_x) / amt; + modelptr->fov_Effecty = abs(modelptr->fov_y2 - modelptr->fov_y) / amt; + } + + } + } +} + +#endif + + + + +/* +================= +Menu_OrbitItemByName +================= +*/ +void Menu_OrbitItemByName(menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time) +{ + itemDef_t *item; + int i; + int count = Menu_ItemsMatchingGroup(menu, p); + for (i = 0; i < count; i++) + { + item = Menu_GetMatchingItemByNumber(menu, i, p); + if (item != NULL) + { + item->window.flags |= (WINDOW_ORBITING | WINDOW_VISIBLE); + item->window.offsetTime = time; + item->window.rectEffects.x = cx; + item->window.rectEffects.y = cy; + item->window.rectClient.x = x; + item->window.rectClient.y = y; + Item_UpdatePosition(item); + } + } +} + +/* +================= +Rect_Parse +================= +*/ +qboolean Rect_Parse(const char **p, rectDef_t *r) +{ + if (!COM_ParseFloat(p, &r->x)) + { + if (!COM_ParseFloat(p, &r->y)) + { + if (!COM_ParseFloat(p, &r->w)) + { + if (!COM_ParseFloat(p, &r->h)) + { + return qtrue; + } + } + } + } + return qfalse; +} + +qboolean Script_SetItemRect(itemDef_t *item, const char **args) +{ + const char *itemname; + rectDef_t *out; + rectDef_t rect; + + // expecting type of color to set and 4 args for the color + if (String_Parse(args, &itemname)) + { + itemDef_t *item2; + int j; + int count = Menu_ItemsMatchingGroup((menuDef_t *) item->parent, itemname); + + if (!Rect_Parse(args, &rect)) + { + return qtrue; + } + + for (j = 0; j < count; j++) + { + item2 = Menu_GetMatchingItemByNumber((menuDef_t *) item->parent, j, itemname); + if (item2 != NULL) + { + out = &item2->window.rect; + + if (out) + { + item2->window.rect.x = rect.x; + item2->window.rect.y = rect.y; + item2->window.rect.w = rect.w; + item2->window.rect.h = rect.h; + } + } + } + } + return qtrue; +} + +/* +================= +Script_SetItemBackground +================= +*/ +qboolean Script_SetItemBackground(itemDef_t *item, const char **args) +{ + const char *itemName; + const char *name; + + // expecting name of shader + if (String_Parse(args, &itemName) && String_Parse(args, &name)) + { + Menu_SetItemBackground((menuDef_t *) item->parent, itemName, name); + } + return qtrue; +} + +/* +================= +Script_SetItemText +================= +*/ +qboolean Script_SetItemText(itemDef_t *item, const char **args) +{ + const char *itemName; + const char *text; + + // expecting text + if (String_Parse(args, &itemName) && String_Parse(args, &text)) + { + Menu_SetItemText((menuDef_t *) item->parent, itemName, text); + } + return qtrue; +} + +/* +================= +Script_FadeIn +================= +*/ +qboolean Script_FadeIn(itemDef_t *item, const char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + Menu_FadeItemByName((menuDef_t *) item->parent, name, qfalse); + } + + return qtrue; +} + +/* +================= +Script_FadeOut +================= +*/ +qboolean Script_FadeOut(itemDef_t *item, const char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + Menu_FadeItemByName((menuDef_t *) item->parent, name, qtrue); + } + + return qtrue; +} + +/* +================= +Script_Show +================= +*/ +qboolean Script_Show(itemDef_t *item, const char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + Menu_ShowItemByName((menuDef_t *) item->parent, name, qtrue); + } + + return qtrue; +} + +/* +========== +Script_SetDecoration +========== +*/ +qboolean Script_SetDecoration(itemDef_t* item, const char **args) +{ + const char *name; + int val; + if (String_Parse(args, &name)) + { + if(!strcmp("vstr", name)) + { + if(String_Parse(args, &name)) + { + if(Int_Parse( args, &val)) + { + Menu_SetItemDecorationByName((menuDef_t*) item->parent, Cvar_VariableString(name), val ); + return qtrue; + } + } + } + else + { + if(Int_Parse( args, &val)) + { + Menu_SetItemDecorationByName((menuDef_t*) item->parent, name, val ); + return qtrue; + } + } + } + return qfalse; +} + +/* +================= +Script_ShowMenu +================= +*/ +qboolean Script_ShowMenu(itemDef_t *item, const char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + Menus_ShowItems(name); + } + + return qtrue; +} + + +/* +================= +Script_Hide +================= +*/ +qboolean Script_Hide(itemDef_t *item, const char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + Menu_ShowItemByName((menuDef_t *) item->parent, name, qfalse); + } + + return qtrue; +} + +/* +================= +Script_SetColor +================= +*/ +qboolean Script_SetColor(itemDef_t *item, const char **args) +{ + const char *name; + int i; + float f; + vec4_t *out; + + // expecting type of color to set and 4 args for the color + if (String_Parse(args, &name)) + { + out = NULL; + if (Q_stricmp(name, "backcolor") == 0) + { + out = &item->window.backColor; + item->window.flags |= WINDOW_BACKCOLORSET; + } + else if (Q_stricmp(name, "forecolor") == 0) + { + out = &item->window.foreColor; + item->window.flags |= WINDOW_FORECOLORSET; + } + else if (Q_stricmp(name, "bordercolor") == 0) + { + out = &item->window.borderColor; + } + + if (out) + { + for (i = 0; i < 4; i++) + { +// if (!Float_Parse(args, &f)) + if (COM_ParseFloat( args, &f)) + { + return qtrue; + } + (*out)[i] = f; + } + } + } + + return qtrue; +} + +/* +================= +Script_Open +================= +*/ +qboolean Script_Open(itemDef_t *item, const char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + if(!strcmp("vstr",name)) + { + if(String_Parse(args,&name)) + { + Menus_OpenByName(Cvar_VariableString(name)); + } + } + else + { + Menus_OpenByName(name); + } + } + + return qtrue; +} + +qboolean Script_OpenGoToMenu(itemDef_t *item, const char **args) +{ + Menus_OpenByName(GoToMenu); // Give warning + return qtrue; +} + + +/* +================= +Script_Close +================= +*/ +qboolean Script_Close(itemDef_t *item, const char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + if (Q_stricmp(name, "all") == 0) + { + Menus_CloseAll(); + } + else + { + Menus_CloseByName(name); + } + } + + return qtrue; +} + +/* +================= +Script_Activate +================= +*/ +qboolean Script_Activate(itemDef_t *item, const char **args) +{ + const char *name, *menu; + + if (String_Parse(args, &menu)) + { + if (String_Parse(args, &name)) + { + Item_ActivateByName(menu,name); + } + } + + return qtrue; +} + +/* +================= +Script_SetBackground +================= +*/ +qboolean Script_SetBackground(itemDef_t *item, const char **args) +{ + const char *name; + // expecting name to set asset to + if (String_Parse(args, &name)) + { + item->window.background = DC->registerShaderNoMip(name); + } + + return qtrue; +} + +/* +================= +Script_SetAsset +================= +*/ +qboolean Script_SetAsset(itemDef_t *item, const char **args) +{ + const char *name; + // expecting name to set asset to + if (String_Parse(args, &name)) + { + // check for a model + if (item->type == ITEM_TYPE_MODEL) + { + } + } + + return qtrue; +} + +/* +================= +Script_SetFocus +================= +*/ +qboolean Script_SetFocus(itemDef_t *item, const char **args) +{ + const char *name; + itemDef_t *focusItem; + + if (String_Parse(args, &name)) + { + if(!strcmp("vstr", name)) + { + if(String_Parse(args, &name)) + { + +#ifdef _XBOX + focusItem = (itemDef_s *) Menu_FindVisibleItemByName((menuDef_t *) item->parent, Cvar_VariableString(name)); +#else + focusItem = (itemDef_s *) Menu_FindItemByName((menuDef_t *) item->parent, Cvar_VariableString(name)); +#endif + } + else + { + return qfalse; + } + } + else + { +#ifdef _XBOX + focusItem = (itemDef_s *) Menu_FindVisibleItemByName((menuDef_t *) item->parent, name); +#else + focusItem = (itemDef_s *) Menu_FindItemByName((menuDef_t *) item->parent, name); +#endif + } + + if (focusItem && !(focusItem->window.flags & WINDOW_DECORATION) && !(focusItem->window.flags & WINDOW_HASFOCUS)) + { + Menu_ClearFocus((menuDef_t *) item->parent); +//JLF +#ifdef _XBOX + Item_SetFocus(focusItem, 0,0); +#else + focusItem->window.flags |= WINDOW_HASFOCUS; +#endif +//END JLF + + if (focusItem->onFocus) + { + Item_RunScript(focusItem, focusItem->onFocus); + } + if (DC->Assets.itemFocusSound) + { + DC->startLocalSound( DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND ); + } +#ifdef _IMMERSION + if (DC->Assets.itemFocusForce) + { + DC->startForce( DC->Assets.itemFocusForce ); + } +#endif // _IMMERSION + } + } + + return qtrue; +} + + +/* +================= +Script_SetItemFlag +================= +*/ +qboolean Script_SetItemFlag(itemDef_t *item, const char **args) +{ + const char *itemName,*number; + + if (String_Parse(args, &itemName)) + { + item = (itemDef_s *) Menu_FindItemByName((menuDef_t *) item->parent, itemName); + + if (String_Parse(args, &number)) + { + int amount = atoi(number); + item->window.flags |= amount; + } + } + + return qtrue; +} + +void UI_SetItemVisible(menuDef_t *menu,const char *itemname,qboolean visible) +{ + itemDef_t *item; + int j; + int count = Menu_ItemsMatchingGroup(menu, itemname); + + for (j = 0; j < count; j++) + { + item = Menu_GetMatchingItemByNumber(menu, j, itemname); + if (item != NULL) + { + if (visible==qtrue) + { + item->window.flags |= WINDOW_VISIBLE; + } + else + { + item->window.flags &= ~WINDOW_VISIBLE; + } + } + } +} + +void UI_SetItemColor(itemDef_t *item,const char *itemname,const char *name,vec4_t color) +{ + itemDef_t *item2; + int i,j; + vec4_t *out; + int count = Menu_ItemsMatchingGroup((menuDef_t *) item->parent, itemname); + + for (j = 0; j < count; j++) + { + item2 = Menu_GetMatchingItemByNumber((menuDef_t *) item->parent, j, itemname); + if (item2 != NULL) + { + out = NULL; + if (Q_stricmp(name, "backcolor") == 0) + { + out = &item2->window.backColor; + } + else if (Q_stricmp(name, "forecolor") == 0) + { + out = &item2->window.foreColor; + item2->window.flags |= WINDOW_FORECOLORSET; + } + else if (Q_stricmp(name, "bordercolor") == 0) + { + out = &item2->window.borderColor; + } + + if (out) + { + for (i = 0; i < 4; i++) + { + (*out)[i] = color[i]; + } + } + } + } +} + +/* +================= +Script_SetItemColor +================= +*/ +qboolean Script_SetItemColor(itemDef_t *item, const char **args) +{ + const char *itemname; + const char *name; + vec4_t color; + + // expecting type of color to set and 4 args for the color + if (String_Parse(args, &itemname) && String_Parse(args, &name)) + { + if (COM_ParseVec4(args, &color)) + { + return qtrue; + } + + UI_SetItemColor(item,itemname,name,color); + + } + + return qtrue; +} + +/* +================= +Script_SetFocusColor +================= +*/ +qboolean Script_SetFocusColor(itemDef_t *item, const char **args) +{ + menuDef_t *menu = (menuDef_t *) item->parent; + + // expecting type of color to set and 4 args for the color + if (COM_ParseVec4(args, &menu->focusColor)) + { + return qtrue; + } + + return qtrue; +} + +/* +================= +Script_CvarIfEqual + +Egads. OK. Script command should be: +cvarIfEqual + +If the contents of cvarname are equal to value, then the script in +cvarTrueScript will be executed, otherwise the script in cvarFalseScript +================= +*/ +qboolean Script_CvarIfEqual ( itemDef_t* item, const char **args ) +{ + const char *cvarName; + const char *testVal; + const char *cvarTrueScript; + const char *cvarFalseScript; + + if (!String_Parse(args, &cvarName) || !String_Parse(args, &testVal) || + !String_Parse(args, &cvarTrueScript) || !String_Parse(args, &cvarFalseScript)) + { + Com_Printf("Error parsing cvarIfEqual\n"); + return qfalse; + } + + bool testResult = (Q_stricmp(Cvar_VariableString(cvarName), testVal) == 0); + + if( testResult ) + Item_RunScript( item, Cvar_VariableString( cvarTrueScript ) ); + else + Item_RunScript( item, Cvar_VariableString( cvarFalseScript ) ); + + return qtrue; +} + +/* +================= +Script_Defer + +Defers the rest of the script based on the defer condition. The deferred +portion of the script can later be run with the "rundeferred" +================= +*/ +qboolean Script_Defer ( itemDef_t* item, const char **args ) +{ + // Should the script be deferred? + if ( DC->deferScript ( args ) ) + { + // Need the item the script was being run on + uiInfo.deferredScriptItem = item; + + // Save the rest of the script + Q_strncpyz ( uiInfo.deferredScript, *args, MAX_DEFERRED_SCRIPT, qfalse ); + + // No more running + return qfalse; + } + + // Keep running the script, its ok + return qtrue; +} + +/* +================= +Script_RunDeferred + +Runs the last deferred script, there can only be one script deferred at a +time so be careful of recursion +================= +*/ +qboolean Script_RunDeferred ( itemDef_t* item, const char **args ) +{ + // Make sure there is something to run. + if ( !uiInfo.deferredScript[0] || !uiInfo.deferredScriptItem ) + { + return qtrue; + } + + // Run the deferred script now + Item_RunScript ( uiInfo.deferredScriptItem, uiInfo.deferredScript ); + uiInfo.deferredScriptItem = NULL; + uiInfo.deferredScript[0] = 0; + + return qtrue; +} + +/* +================= +Script_Delay + +Delays the rest of the script for the specified amount of time +================= +*/ +qboolean Script_Delay ( itemDef_t* item, const char **args ) +{ + int time; + + if (Int_Parse(args, &time)) + { + // item->window.flags |= WINDOW_SCRIPTWAITING; + // item->window.delayTime = DC->realTime + time; // Flag to set delay time on next paint + // item->window.delayedScript = (char *)*args; // Copy current location, we'll resume executing here later + ((menuDef_t*)(item->parent))->window.flags |= WINDOW_SCRIPTWAITING; + ((menuDef_t*)(item->parent))->window.delayTime = DC->realTime + time; // Flag to set delay time on next paint + ((menuDef_t*)(item->parent))->window.delayedScript = (char *)*args; // Copy current location, we'll resume executing here later + + } + else + { + Com_Printf(S_COLOR_YELLOW"WARNING: Script_Delay: error parsing\n" ); + } + + // Stop running + return qfalse; +} + +/* +================= +Script_Transition + +transition rtvscr 321 0 202 264 415 0 202 264 20 25 +================= +*/ +qboolean Script_Transition(itemDef_t *item, const char **args) +{ + const char *name; + rectDef_t rectFrom, rectTo; + int time; + float amt; + + if (String_Parse(args, &name)) + { + if ( ParseRect(args, &rectFrom) && ParseRect(args, &rectTo) && Int_Parse(args, &time) && !COM_ParseFloat(args, &amt)) + { + Menu_TransitionItemByName((menuDef_t *) item->parent, name, &rectFrom, &rectTo, time, amt); + } + else + { + Com_Printf(S_COLOR_YELLOW"WARNING: Script_Transition: error parsing '%s'\n", name ); + } + } + + return qtrue; +} + + +/* +================= +Script_Transition2 +uses current origin instead of specifing a starting origin + +transition2 lfvscr 25 0 202 264 20 25 +================= +*/ +qboolean Script_Transition2(itemDef_t *item, const char **args) +{ + const char *name; + rectDef_t rectTo; + int time; + float amt; + + if (String_Parse(args, &name)) + { + if ( ParseRect(args, &rectTo) && Int_Parse(args, &time) && !COM_ParseFloat(args, &amt)) + { + Menu_TransitionItemByName((menuDef_t *) item->parent, name, 0, &rectTo, time, amt); + } + else + { + Com_Printf(S_COLOR_YELLOW"WARNING: Script_Transition2: error parsing '%s'\n", name ); + } + } + + return qtrue; +} + +#ifdef _TRANS3 +/* +JLF MPMOVED +================= +Script_Transition3 + +used exclusively with model views +uses current origin instead of specifing a starting origin + + +transition3 lfvscr (min extent) (max extent) (fovx,y) 20 25 +================= +*/ +qboolean Script_Transition3(itemDef_t *item, const char **args) +{ + const char *name; + const char *value; + float minx, miny, minz, maxx, maxy, maxz, fovtx, fovty; + int time; + float amt; + + if (String_Parse(args, &name)) + { + if (String_Parse( args, &value)) + { + minx = atof(value); + if (String_Parse( args, &value)) + { + miny = atof(value); + if (String_Parse( args, &value)) + { + minz = atof(value); + if (String_Parse( args, &value)) + { + maxx = atof(value); + if (String_Parse( args, &value)) + { + maxy = atof(value); + if (String_Parse( args, &value)) + { + maxz = atof(value); + if (String_Parse( args, &value)) + { + fovtx = atof(value); + if (String_Parse( args, &value)) + { + fovty = atof(value); + + if (String_Parse( args, &value)) + { + time = atoi(value); + if (String_Parse( args, &value)) + { + amt = atof(value); + //set up the variables + Menu_Transition3ItemByName((menuDef_t *) item->parent, + name, + minx, miny, minz, + maxx, maxy, maxz, + fovtx, fovty, + time, amt); + + return qtrue; + } + } + } + } + } + } + } + } + } + } + } + Com_Printf(S_COLOR_YELLOW"WARNING: Script_Transition2: error parsing '%s'\n", name ); + return qtrue; +} +#endif + + +//only works on some feeders +int GetCurrentFeederIndex(itemDef_t * item) +{ + float feederID = item->special; + char * name; + int i, max; + + if (feederID == FEEDER_PLAYER_SPECIES) + { + return uiInfo.playerSpeciesIndex; + } + if (feederID == FEEDER_PLAYER_SKIN_HEAD) + { + name = Cvar_VariableString("ui_char_skin_head"); + max = uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadCount; + for ( i = 0; i < max ; i++) + { + if (!Q_stricmp(name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadNames[i])) + { + return i; + } + + // Cvar_Set("ui_char_skin_head", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadNames[index]); + } + return -1; + } + else if (feederID == FEEDER_PLAYER_SKIN_TORSO) + { + name = Cvar_VariableString("ui_char_skin_torso"); + max = uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinTorsoCount; + for ( i = 0; i < max ; i++) + { + if (!Q_stricmp(name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinTorsoNames[i])) + { + return i; + } + // Cvar_Set("ui_char_skin_head", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadNames[index]); + } + return -1; + + } + else if (feederID == FEEDER_PLAYER_SKIN_LEGS) + { + name = Cvar_VariableString("ui_char_skin_legs"); + max = uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegCount; + for ( i = 0; i < max ; i++) + { + if (!Q_stricmp(name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegNames[i])) + { + return i; + } + // Cvar_Set("ui_char_skin_head", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadNames[index]); + } + return -1; + + + // if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegCount) + // { + // Cvar_Set("ui_char_skin_legs", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegNames[index]); + // } + } + + + else if (feederID == FEEDER_COLORCHOICES) + { + extern void Item_RunScript(itemDef_t *item, const char *s); //from ui_shared; + int currR, currG, currB, newR, newG, newB; + currR = Cvar_VariableIntegerValue( "ui_char_color_red"); + currG = Cvar_VariableIntegerValue( "ui_char_color_green"); + currB = Cvar_VariableIntegerValue( "ui_char_color_blue"); + + max = uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorCount; + for ( i = 0; i < max ; i++) + { + Item_RunScript(item, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorActionText[i]); + newR = Cvar_VariableIntegerValue( "ui_char_color_red"); + newG = Cvar_VariableIntegerValue( "ui_char_color_green"); + newB = Cvar_VariableIntegerValue( "ui_char_color_blue"); + if ( currR == newR && currG == newG && currB == newB) + return i; + } + return -1; + + + +//JLF junk copied code + /* +extern void Item_RunScript(itemDef_t *item, const char *s); //from ui_shared; + name = Cvar_VariableString("ui_char_skin_legs"); + max = uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorCount; + for ( i = 0; i < max ; i++) + if (!qstrcmp(name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegNames[i])) + { + return i; + // Cvar_Set("ui_char_skin_head", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadNames[index]); + } + return -1; + + + + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorCount) + { + Item_RunScript(item, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorActionText[index]); + } + */ + } + return -1; + +} + + + + + + + + + +qboolean Script_IncrementFeeder(itemDef_t * item, const char ** args) +{ + + int feedercount = uiInfo.uiDC.feederCount(item->special); + int value = GetCurrentFeederIndex(item); + value++; + if ( value >= feedercount) + value = 0; + DC->feederSelection(item->special, value, item); + return qtrue; +} + +qboolean Script_DecrementFeeder(itemDef_t * item, const char ** args) +{ + + int feedercount = uiInfo.uiDC.feederCount(item->special); + int value = GetCurrentFeederIndex(item); + value--; + if ( value < 0) + value = feedercount-1; + DC->feederSelection(item->special, value, item); + return qtrue; +} + + +/* +================= +Script_SetCvar +================= +*/ +qboolean Script_SetCvar(itemDef_t *item, const char **args) +{ + const char *cvar, *val; + if (String_Parse(args, &cvar) && String_Parse(args, &val)) + { + DC->setCVar(cvar, val); + } + + return qtrue; +} + +/* +================= +Script_Exec +================= +*/ +qboolean Script_Exec ( itemDef_t *item, const char **args) +{ + const char *val; + if (String_Parse(args, &val)) + { + DC->executeText(EXEC_APPEND, va("%s ; ", val)); + } + + return qtrue; +} + +/* +================= +Script_Play +================= +*/ +static qboolean Script_Play(itemDef_t *item, const char **args) +{ + const char *val; + if (String_Parse(args, &val)) + { + DC->startLocalSound(DC->registerSound(val, qfalse), CHAN_AUTO ); + } + + return qtrue; +} + +/* +================= +Script_PlayVoice +================= +*/ +static qboolean Script_PlayVoice(itemDef_t *item, const char **args) +{ + const char *val; + if (String_Parse(args, &val)) + { + DC->startLocalSound(DC->registerSound(val, qfalse), CHAN_VOICE ); + } + + return qtrue; +} + +/* +================= +Script_StopVoice +================= +*/ +static qboolean Script_StopVoice(itemDef_t *item, const char **args) +{ +#ifdef _XBOX + extern void S_KillEntityChannel(int entnum, int chan); + S_KillEntityChannel( 0, CHAN_VOICE ); +#else + DC->startLocalSound(uiInfo.uiDC.Assets.nullSound, CHAN_VOICE ); +#endif + + return qtrue; +} + +/* +================= +Script_playLooped +================= +*/ +/* +qboolean Script_playLooped(itemDef_t *item, const char **args) +{ + const char *val; + if (String_Parse(args, &val)) + { + // FIXME BOB - is this needed? + DC->stopBackgroundTrack(); + DC->startBackgroundTrack(val, val); + } + + return qtrue; +} +*/ +#ifdef _IMMERSION +/* +================= +Script_FFPlay +================= +*/ +qboolean Script_FFPlay(itemDef_t *item, const char **args) +{ + const char *val; + if (String_Parse(args, &val)) + { + DC->startForce(DC->registerForce(val)); + } + return qtrue; +} +#endif // _IMMERSION +/* +================= +Script_Orbit +================= +*/ +qboolean Script_Orbit(itemDef_t *item, const char **args) +{ + const char *name; + float cx, cy, x, y; + int time; + + if (String_Parse(args, &name)) + { +// if ( Float_Parse(args, &x) && Float_Parse(args, &y) && Float_Parse(args, &cx) && Float_Parse(args, &cy) && Int_Parse(args, &time) ) + if ( !COM_ParseFloat(args, &x) && !COM_ParseFloat(args, &y) && !COM_ParseFloat(args, &cx) && !COM_ParseFloat(args, &cy) && Int_Parse(args, &time) ) + { + Menu_OrbitItemByName((menuDef_t *) item->parent, name, x, y, cx, cy, time); + } + } + + return qtrue; +} + + +commandDef_t commandList[] = +{ + {"activate", &Script_Activate}, // menu + {"close", &Script_Close}, // menu + {"exec", &Script_Exec}, // group/name + {"fadein", &Script_FadeIn}, // group/name + {"fadeout", &Script_FadeOut}, // group/name + {"hide", &Script_Hide}, // group/name + {"open", &Script_Open}, // menu + {"openGoToMenu", &Script_OpenGoToMenu}, // + {"orbit", &Script_Orbit}, // group/name + {"play", &Script_Play}, // group/name + {"playVoice", &Script_PlayVoice}, // group/name + {"stopVoice", &Script_StopVoice}, // group/name +// {"playlooped", &Script_playLooped}, // group/name +#ifdef _IMMERSION + {"ffplay", &Script_FFPlay}, +#endif // _IMMERSION + {"setasset", &Script_SetAsset}, // works on this + {"setbackground", &Script_SetBackground}, // works on this + {"setcolor", &Script_SetColor}, // works on this + {"setcvar", &Script_SetCvar}, // group/name + {"setfocus", &Script_SetFocus}, // sets this background color to team color + {"setitemcolor", &Script_SetItemColor}, // group/name + {"setfocuscolor", &Script_SetFocusColor}, // affects whole menu + {"setitemflag", &Script_SetItemFlag}, // name + {"show", &Script_Show}, // group/name + {"showMenu", &Script_ShowMenu}, // menu + {"transition", &Script_Transition}, // group/name + {"transition2", &Script_Transition2}, // group/name + {"setitembackground", &Script_SetItemBackground}, // group/name + {"setitemtext", &Script_SetItemText}, // group/name + {"setitemrect", &Script_SetItemRect}, // group/name + {"defer", &Script_Defer}, // + {"rundeferred", &Script_RunDeferred}, // + {"delay", &Script_Delay}, // works on this (script) + {"transition3", &Script_Transition3}, // model exclusive transition + {"incrementfeeder", &Script_IncrementFeeder}, + {"decrementfeeder", &Script_DecrementFeeder}, + {"setdecoration", &Script_SetDecoration}, // changes the decoration flag of an object + {"cvarifequal", &Script_CvarIfEqual} +}; + +int scriptCommandCount = sizeof(commandList) / sizeof(commandDef_t); + + +/* +=============== +Item_Init +=============== +*/ +void Item_Init(itemDef_t *item) +{ + memset(item, 0, sizeof(itemDef_t)); + item->textscale = 0.55f; + Window_Init(&item->window); +} + +/* +=============== +Item_Multi_Setting +=============== +*/ +const char *Item_Multi_Setting(itemDef_t *item) +{ + char buff[1024]; + float value = 0; + int i; + multiDef_t *multiPtr = (multiDef_t*)item->typeData; + if (multiPtr) + { + if (multiPtr->strDef) + { + if (item->cvar) + { + DC->getCVarString(item->cvar, buff, sizeof(buff)); + } + else + { + + } + } + else + { + if (item->cvar) // Was a cvar given? + { + value = DC->getCVarValue(item->cvar); + } + else + { + value = item->value; + } + } + + for (i = 0; i < multiPtr->count; i++) + { + if (multiPtr->strDef) + { + if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) + { + return multiPtr->cvarList[i]; + } + } + else + { + if (multiPtr->cvarValue[i] == value) + { + return multiPtr->cvarList[i]; + } + } + } + } + + return ""; +} + +//--------------------------------------------------------------------------------------------------------- +// Item Keyword Parse functions +//--------------------------------------------------------------------------------------------------------- + +/* +=============== +ItemParse_name + name +=============== +*/ +qboolean ItemParse_name( itemDef_t *item) +{ + if (!PC_ParseStringMem((const char **)&item->window.name)) + { + return qfalse; + } + + return qtrue; +} + + + +qboolean ItemParse_font( itemDef_t *item ) +{ + if (PC_ParseInt(&item->font)) + { + return qfalse; + } + return qtrue; +} + + +/* +=============== +ItemParse_focusSound + name +=============== +*/ +qboolean ItemParse_focusSound( itemDef_t *item) +{ + const char *temp; + + if (PC_ParseString(&temp)) + { + return qfalse; + } + item->focusSound = DC->registerSound(temp, qfalse); + return qtrue; +} + + + +#ifdef _IMMERSION +/* +=============== +ItemParse_focusForce + name +=============== +*/ +qboolean ItemParse_focusForce( itemDef_t *item) +{ + const char *temp; + + if (PC_ParseString(&temp)) + { +//#ifdef _DEBUG +//extern void UI_Debug_EnterReference(LPCSTR ps4LetterType, LPCSTR psItemString); +//#endif + return qfalse; + } + item->focusForce = DC->registerForce(temp); + return qtrue; +} +#endif // _IMMERSION + +/* +=============== +ItemParse_text + text +=============== +*/ +qboolean ItemParse_text( itemDef_t *item) +{ + if (!PC_ParseStringMem((const char **) &item->text)) + { + return qfalse; + } + +//#ifdef _DEBUG +// UI_Debug_EnterReference("TEXT", item->text); +//#endif + + return qtrue; +} + +/* +=============== +ItemParse_descText + text +=============== +*/ +/* +qboolean ItemParse_descText( itemDef_t *item) +{ + if (!PC_ParseStringMem((const char **) &item->descText)) + { + return qfalse; + } + +//#ifdef _DEBUG +// UI_Debug_EnterReference("DESC", item->descText); +//#endif + + return qtrue; +} +*/ + +/* +=============== +ItemParse_text + text +=============== +*/ +/* +qboolean ItemParse_text2( itemDef_t *item) +{ + if (!PC_ParseStringMem((const char **) &item->text2)) + { + return qfalse; + } + +//#ifdef _DEBUG +// UI_Debug_EnterReference("TXT2", item->text2); +//#endif + + return qtrue; +} +*/ + +/* +=============== +ItemParse_group + group +=============== +*/ +qboolean ItemParse_group( itemDef_t *item) +{ + if (!PC_ParseStringMem((const char **)&item->window.group)) + { + return qfalse; + } + + return qtrue; +} + +/* +=============== +ItemParse_asset_model + asset_model +=============== +*/ +qboolean ItemParse_asset_model_go( itemDef_t *item, const char *name ) +{ + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!Q_stricmp(&name[strlen(name) - 4], ".glm")) + { //it's a ghoul2 model then + if ( item->ghoul2.size() && item->ghoul2[0].mModelindex >= 0) + { + DC->g2_RemoveGhoul2Model( item->ghoul2, 0 ); + item->flags &= ~ITF_G2VALID; + } + int g2Model = DC->g2_InitGhoul2Model(item->ghoul2, name, 0, 0, 0, 0, 0); + if (g2Model >= 0) + { + item->flags |= ITF_G2VALID; + + if (modelPtr->g2anim) + { //does the menu request this model be playing an animation? + DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qfalse); + } + if ( modelPtr->g2skin ) + { + DC->g2_SetSkin( &item->ghoul2[0], 0, modelPtr->g2skin );//this is going to set the surfs on/off matching the skin file + } + } + } + else if(!(item->asset)) + { //guess it's just an md3 + item->asset = DC->registerModel(name); + item->flags &= ~ITF_G2VALID; + } + return qtrue; +} + +qboolean ItemParse_asset_model( itemDef_t *item ) +{ + const char *temp; + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (PC_ParseString(&temp)) + { + return qfalse; + } + char modelPath[MAX_QPATH]; + + if (!stricmp(temp,"ui_char_model") ) + { + Com_sprintf( modelPath, sizeof( modelPath ), "models/players/%s/model.glm", Cvar_VariableString ( "g_char_model" ) ); + } + else + { + Com_sprintf( modelPath, sizeof( modelPath ), temp); + } + return (ItemParse_asset_model_go( item, modelPath )); +} + +/* +=============== +ItemParse_asset_model + asset_shader +=============== +*/ +qboolean ItemParse_asset_shader( itemDef_t *item) +{ + const char *temp; + + if (PC_ParseString(&temp)) + { + return qfalse; + } + item->asset = DC->registerShaderNoMip(temp); + return qtrue; +} + +/* +=============== +ItemParse_asset_model + model_origin +=============== +*/ +qboolean ItemParse_model_origin( itemDef_t *item) +{ + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!PC_ParseFloat(&modelPtr->origin[0])) + { + if (!PC_ParseFloat(&modelPtr->origin[1])) + { + if (!PC_ParseFloat(&modelPtr->origin[2])) + { + return qtrue; + } + } + } + return qfalse; +} + +/* +=============== +ItemParse_model_fovx + model_fovx +=============== +*/ +qboolean ItemParse_model_fovx( itemDef_t *item) +{ + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (PC_ParseFloat(&modelPtr->fov_x)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_model_fovy + model_fovy +=============== +*/ +qboolean ItemParse_model_fovy( itemDef_t *item) +{ + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (PC_ParseFloat(&modelPtr->fov_y)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_model_rotation + model_rotation +=============== +*/ +qboolean ItemParse_model_rotation( itemDef_t *item) +{ + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (PC_ParseInt(&modelPtr->rotationSpeed)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_model_angle + model_angle +=============== +*/ +qboolean ItemParse_model_angle( itemDef_t *item) +{ + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (PC_ParseInt(&modelPtr->angle)) + { + return qfalse; + } + return qtrue; +} + +// model_g2mins +qboolean ItemParse_model_g2mins( itemDef_t *item ) { + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!PC_ParseFloat(&modelPtr->g2mins[0])) { + if (!PC_ParseFloat(&modelPtr->g2mins[1])) { + if (!PC_ParseFloat(&modelPtr->g2mins[2])) { + return qtrue; + } + } + } + return qfalse; +} + +// model_g2maxs +qboolean ItemParse_model_g2maxs( itemDef_t *item ) { + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!PC_ParseFloat(&modelPtr->g2maxs[0])) { + if (!PC_ParseFloat(&modelPtr->g2maxs[1])) { + if (!PC_ParseFloat(&modelPtr->g2maxs[2])) { + return qtrue; + } + } + } + return qfalse; +} + +// model_g2skin +qboolean ItemParse_model_g2skin_go( itemDef_t *item, const char *skinName ) +{ + modelDef_t *modelPtr; + + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!skinName || !skinName[0]) + { //it was parsed cor~rectly so still return true. + modelPtr->g2skin = 0; + DC->g2_SetSkin( &item->ghoul2[0], -1, 0 );//turn off custom skin + return qtrue; + } + + modelPtr->g2skin = DC->registerSkin(skinName); + if ( item->ghoul2.IsValid() ) + { + DC->g2_SetSkin( &item->ghoul2[0], 0, modelPtr->g2skin );//this is going to set the surfs on/off matching the skin file + } + + return qtrue; +} + +qboolean ItemParse_model_g2skin( itemDef_t *item ) +{ + const char *skinName; + + if (PC_ParseString(&skinName)) { + return qfalse; + } + + return (ItemParse_model_g2skin_go( item, skinName )); +} + +// model_g2anim +qboolean ItemParse_model_g2anim_go( itemDef_t *item, const char *animName ) +{ + modelDef_t *modelPtr; + int i = 0; + + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!animName || !animName[0]) + { //it was parsed correctly so still return true. + return qtrue; + } + + while (i < MAX_ANIMATIONS) + { + if (!Q_stricmp(animName, animTable[i].name)) + { //found it + modelPtr->g2anim = animTable[i].id; + return qtrue; + } + i++; + } + + Com_Printf("Could not find '%s' in the anim table\n", animName); + return qtrue; +} + +qboolean ItemParse_model_g2anim( itemDef_t *item ) { + const char *animName; + + if (PC_ParseString(&animName)) { + return qfalse; + } + + return ItemParse_model_g2anim_go( item, animName ); +} + +/* +=============== +ItemParse_rect + rect +=============== +*/ +qboolean ItemParse_rect( itemDef_t *item) +{ + if (!PC_ParseRect(&item->window.rectClient)) + { + return qfalse; + } + + return qtrue; +} + +/* +=============== +ItemParse_flag + flag +=============== +*/ +qboolean ItemParse_flag( itemDef_t *item) +{ + int i; + const char *tempStr; + + if (PC_ParseString(&tempStr)) + { + return qfalse; + } + + i=0; + while (itemFlags[i].string) + { + if (Q_stricmp(tempStr,itemFlags[i].string)==0) + { + item->window.flags |= itemFlags[i].value; + break; + } + i++; + } + + if (itemFlags[i].string == NULL) + { + PC_ParseWarning(va("Unknown item flag value '%s'",tempStr)); + } + + return qtrue; +} + + +/* +=============== +ItemParse_style + style +=============== +*/ +qboolean ItemParse_style( itemDef_t *item) +{ + int i; + const char *tempStr; + + if (PC_ParseString(&tempStr)) + { + return qfalse; + } + + i=0; + while (styles[i]) + { + if (Q_stricmp(tempStr,styles[i])==0) + { + item->window.style = i; + break; + } + i++; + } + + if (styles[i] == NULL) + { + PC_ParseWarning(va("Unknown item style value '%s'",tempStr)); + } + + return qtrue; +} + +/* +=============== +ItemParse_decoration + decoration +=============== +*/ +qboolean ItemParse_decoration( itemDef_t *item ) +{ + item->window.flags |= WINDOW_DECORATION; + return qtrue; +} + +/* +=============== +ItemParse_notselectable + notselectable +=============== +*/ +qboolean ItemParse_notselectable( itemDef_t *item ) +{ + listBoxDef_t *listPtr; + Item_ValidateTypeData(item); + listPtr = (listBoxDef_t*)item->typeData; + + if (item->type == ITEM_TYPE_LISTBOX && listPtr) + { + listPtr->notselectable = qtrue; + } + return qtrue; +} + + +//JLF +#ifdef _XBOX +/* +=============== +ItemParse_scrollhidden + scrollhidden +=============== +*/ +qboolean ItemParse_scrollhidden( itemDef_t *item ) +{ + listBoxDef_t *listPtr; + Item_ValidateTypeData(item); + listPtr = (listBoxDef_t*)item->typeData; + + if (item->type == ITEM_TYPE_LISTBOX && listPtr) + { + listPtr->scrollhidden = qtrue; + } + return qtrue; +} + +/* +=============== +ItemParse_selectionShader + selectionShader +=============== +*/ +qboolean ItemParse_selectionShader( itemDef_t *item ) +{ + const char *temp; + if (PC_ParseString(&temp)) + { + return qfalse; + } + + listBoxDef_t *listPtr; + Item_ValidateTypeData( item ); + listPtr = (listBoxDef_t *)item->typeData; + if (item->type == ITEM_TYPE_LISTBOX && listPtr) + { + listPtr->selectionShader = ui.R_RegisterShaderNoMip(temp); + } + return qtrue; +} + +#endif + +/* +=============== +ItemParse_wrapped + manually wrapped +=============== +*/ +qboolean ItemParse_wrapped( itemDef_t *item ) +{ + item->window.flags |= WINDOW_WRAPPED; + return qtrue; +} + + +/* +=============== +ItemParse_autowrapped + auto wrapped +=============== +*/ +qboolean ItemParse_autowrapped( itemDef_t *item) +{ + item->window.flags |= WINDOW_AUTOWRAPPED; + return qtrue; +} + + +/* +=============== +ItemParse_horizontalscroll + horizontalscroll +=============== +*/ +qboolean ItemParse_horizontalscroll( itemDef_t *item ) +{ + item->window.flags |= WINDOW_HORIZONTAL; + return qtrue; +} + + +/* +=============== +ItemParse_type + type +=============== +*/ +qboolean ItemParse_type( itemDef_t *item ) +{ + int i; + const char *tempStr; + + if (PC_ParseString(&tempStr)) + { + return qfalse; + } + + i=0; + while (types[i]) + { + if (Q_stricmp(tempStr,types[i])==0) + { + item->type = i; + break; + } + i++; + } + + if (types[i] == NULL) + { + PC_ParseWarning(va("Unknown item type value '%s'",tempStr)); + } + else + { + Item_ValidateTypeData(item); + } + return qtrue; +} + +/* +=============== +ItemParse_elementwidth + elementwidth, used for listbox image elements + uses textalignx for storage +=============== +*/ +qboolean ItemParse_elementwidth( itemDef_t *item ) +{ + listBoxDef_t *listPtr; + + Item_ValidateTypeData(item); + listPtr = (listBoxDef_t*)item->typeData; + if (PC_ParseFloat(&listPtr->elementWidth)) + { + return qfalse; + } + return qtrue; + +} + +/* +=============== +ItemParse_elementheight + elementheight, used for listbox image elements + uses textaligny for storage +=============== +*/ +qboolean ItemParse_elementheight( itemDef_t *item ) +{ + listBoxDef_t *listPtr; + + Item_ValidateTypeData(item); + listPtr = (listBoxDef_t*)item->typeData; + if (PC_ParseFloat(&listPtr->elementHeight)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_feeder + feeder +=============== +*/ +qboolean ItemParse_feeder( itemDef_t *item ) +{ + if (PC_ParseFloat( &item->special)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_elementtype + elementtype, used to specify what type of elements a listbox contains + uses textstyle for storage +=============== +*/ +qboolean ItemParse_elementtype( itemDef_t *item ) +{ + listBoxDef_t *listPtr; + + Item_ValidateTypeData(item); + if (!item->typeData) + { + return qfalse; + } + + listPtr = (listBoxDef_t*)item->typeData; + if (PC_ParseInt(&listPtr->elementStyle)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_columns + columns sets a number of columns and an x pos and width per.. +=============== +*/ +qboolean ItemParse_columns( itemDef_t *item) +{ + int num, i; + listBoxDef_t *listPtr; + + Item_ValidateTypeData(item); + if (!item->typeData) + { + return qfalse; + } + + listPtr = (listBoxDef_t*)item->typeData; + if (!PC_ParseInt(&num)) + { + if (num > MAX_LB_COLUMNS) + { + num = MAX_LB_COLUMNS; + } + listPtr->numColumns = num; + for (i = 0; i < num; i++) + { + int pos, width, maxChars; + + if (!PC_ParseInt(&pos) && !PC_ParseInt(&width) && !PC_ParseInt(&maxChars)) + { + listPtr->columnInfo[i].pos = pos; + listPtr->columnInfo[i].width = width; + listPtr->columnInfo[i].maxChars = maxChars; + } + else + { + return qfalse; + } + } + } + else + { + return qfalse; + } + + return qtrue; +} + +/* +=============== +ItemParse_border +=============== +*/ +qboolean ItemParse_border( itemDef_t *item) +{ + if (PC_ParseInt(&item->window.border)) + { + return qfalse; + } + + return qtrue; +} + +/* +=============== +ItemParse_bordersize +=============== +*/ +qboolean ItemParse_bordersize( itemDef_t *item ) +{ + if (PC_ParseFloat(&item->window.borderSize)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_visible +=============== +*/ +qboolean ItemParse_visible( itemDef_t *item) +{ + int i; + + if (PC_ParseInt(&i)) + { + return qfalse; + } + if (i) + { + item->window.flags |= WINDOW_VISIBLE; + } + return qtrue; +} + +/* +=============== +ItemParse_ownerdraw +=============== +*/ +qboolean ItemParse_ownerdraw( itemDef_t *item) +{ + if (PC_ParseInt(&item->window.ownerDraw)) + { + return qfalse; + } + item->type = ITEM_TYPE_OWNERDRAW; + return qtrue; +} + +/* +=============== +ItemParse_align +=============== +*/ +qboolean ItemParse_align( itemDef_t *item) +{ + if (PC_ParseInt(&item->alignment)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_align +=============== +*/ +/* +qboolean ItemParse_Appearance_slot( itemDef_t *item) +{ + if (PC_ParseInt(&item->appearanceSlot)) + { + return qfalse; + } + return qtrue; +} +*/ + + +/* +=============== +ItemParse_textalign +=============== +*/ +qboolean ItemParse_textalign( itemDef_t *item ) +{ + const char *tempStr; + int i; + + if (PC_ParseString(&tempStr)) + { + return qfalse; + } + + i=0; + while (alignment[i]) + { + if (Q_stricmp(tempStr,alignment[i])==0) + { + item->textalignment = i; + break; + } + i++; + } + + if (alignment[i] == NULL) + { + PC_ParseWarning(va("Unknown text alignment value '%s'",tempStr)); + } + + return qtrue; + +} + +/* +=============== +ItemParse_text2alignx +=============== +*/ +/* +qboolean ItemParse_text2alignx( itemDef_t *item) +{ + if (PC_ParseFloat(&item->text2alignx)) + { + return qfalse; + } + return qtrue; +} +*/ + +/* +=============== +ItemParse_text2aligny +=============== +*/ +/* +qboolean ItemParse_text2aligny( itemDef_t *item) +{ + if (PC_ParseFloat(&item->text2aligny)) + { + return qfalse; + } + return qtrue; +} +*/ + +/* +=============== +ItemParse_textalignx +=============== +*/ +qboolean ItemParse_textalignx( itemDef_t *item) +{ + if (PC_ParseFloat(&item->textalignx)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_textaligny +=============== +*/ +qboolean ItemParse_textaligny( itemDef_t *item) +{ + if (PC_ParseFloat(&item->textaligny)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_textscale +=============== +*/ +qboolean ItemParse_textscale( itemDef_t *item ) +{ + if (PC_ParseFloat(&item->textscale)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_textstyle +=============== +*/ +qboolean ItemParse_textstyle( itemDef_t *item) +{ + if (PC_ParseInt(&item->textStyle)) + { + return qfalse; + } + return qtrue; +} + + +/* +=============== +ItemParse_invertyesno +=============== +*/ +/* +qboolean ItemParse_invertyesno( itemDef_t *item) +{ + if (PC_ParseInt(&item->invertYesNo)) + { + return qfalse; + } + return qtrue; +} +*/ + +/* +=============== +ItemParse_xoffset (used for yes/no and multi) +=============== +*/ +qboolean ItemParse_xoffset( itemDef_t *item) +{ + if (PC_ParseInt(&item->xoffset)) + { + return qfalse; + } + return qtrue; +} + + +/* +=============== +ItemParse_backcolor +=============== +*/ +qboolean ItemParse_backcolor( itemDef_t *item) +{ + int i; + float f; + + for (i = 0; i < 4; i++) + { + if (PC_ParseFloat(&f)) + { + return qfalse; + } + item->window.backColor[i] = f; + } + return qtrue; +} + +/* +=============== +ItemParse_forecolor +=============== +*/ +qboolean ItemParse_forecolor( itemDef_t *item) +{ + int i; + float f; + + for (i = 0; i < 4; i++) + { + if (PC_ParseFloat(&f)) + { + return qfalse; + } + if (f < 0) + { //special case for player color + item->window.flags |= WINDOW_PLAYERCOLOR; + return qtrue; + } + item->window.foreColor[i] = f; + item->window.flags |= WINDOW_FORECOLORSET; + } + return qtrue; +} + +/* +=============== +ItemParse_bordercolor +=============== +*/ +qboolean ItemParse_bordercolor( itemDef_t *item) +{ + int i; + float f; + + for (i = 0; i < 4; i++) + { + if (PC_ParseFloat(&f)) + { + return qfalse; + } + item->window.borderColor[i] = f; + } + return qtrue; +} + +/* +=============== +ItemParse_outlinecolor +=============== +*/ +/* +qboolean ItemParse_outlinecolor( itemDef_t *item) +{ + if (PC_ParseColor(&item->window.outlineColor)) + { + return qfalse; + } + return qtrue; +} +*/ + +/* +=============== +ItemParse_background +=============== +*/ +qboolean ItemParse_background( itemDef_t *item) +{ + const char *temp; + + if (PC_ParseString(&temp)) + { + return qfalse; + } + item->window.background = ui.R_RegisterShaderNoMip(temp); + return qtrue; +} + +/* +=============== +ItemParse_cinematic +=============== +*/ +/* +qboolean ItemParse_cinematic( itemDef_t *item) +{ + if (!PC_ParseStringMem((const char **) &item->window.cinematicName)) + { + return qfalse; + } + return qtrue; +} +*/ + +/* +=============== +ItemParse_doubleClick +=============== +*/ +qboolean ItemParse_doubleClick( itemDef_t *item) +{ + listBoxDef_t *listPtr; + + Item_ValidateTypeData(item); + if (!item->typeData) + { + return qfalse; + } + + listPtr = (listBoxDef_t*)item->typeData; + + if (!PC_Script_Parse(&listPtr->doubleClick)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_onFocus +=============== +*/ +qboolean ItemParse_onFocus( itemDef_t *item) +{ + if (!PC_Script_Parse(&item->onFocus)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_leaveFocus +=============== +*/ +qboolean ItemParse_leaveFocus( itemDef_t *item ) +{ + if (!PC_Script_Parse(&item->leaveFocus)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_mouseEnter +=============== +*/ +/* +qboolean ItemParse_mouseEnter( itemDef_t *item) +{ + if (!PC_Script_Parse(&item->mouseEnter)) + { + return qfalse; + } + return qtrue; +} +*/ + +/* +=============== +ItemParse_mouseExit +=============== +*/ +/* +qboolean ItemParse_mouseExit( itemDef_t *item) +{ + if (!PC_Script_Parse(&item->mouseExit)) + { + return qfalse; + } + return qtrue; +} +*/ + +/* +=============== +ItemParse_mouseEnterText +=============== +*/ +/* +qboolean ItemParse_mouseEnterText( itemDef_t *item) +{ + if (!PC_Script_Parse(&item->mouseEnterText)) + { + return qfalse; + } + return qtrue; +} +*/ + +/* +=============== +ItemParse_mouseExitText +=============== +*/ +/* +qboolean ItemParse_mouseExitText( itemDef_t *item) +{ + if (!PC_Script_Parse(&item->mouseExitText)) + { + return qfalse; + } + return qtrue; +} +*/ + + +/* +=============== +ItemParse_accept +=============== +*/ +/* +qboolean ItemParse_accept( itemDef_t *item) +{ + if (!PC_Script_Parse(&item->accept)) + { + return qfalse; + } + return qtrue; +} +*/ + + +//JLFDPADSCRIPT +/* +=============== +ItemParse_selectionNext +=============== +*/ +qboolean ItemParse_selectionNext( itemDef_t *item) +{ + if (!PC_Script_Parse(&item->selectionNext)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_selectionPrev +=============== +*/ +qboolean ItemParse_selectionPrev( itemDef_t *item) +{ + if (!PC_Script_Parse(&item->selectionPrev)) + { + return qfalse; + } + return qtrue; +} +// END JLFDPADSCRIPT + + + + + + +/* +=============== +ItemParse_action +=============== +*/ +qboolean ItemParse_action( itemDef_t *item) +{ + if (!PC_Script_Parse(&item->action)) + { + return qfalse; + } + return qtrue; +} + + +/* +=============== +ItemParse_special +=============== +*/ +qboolean ItemParse_special( itemDef_t *item) +{ + if (PC_ParseFloat(&item->special)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_cvarTest +=============== +*/ +qboolean ItemParse_cvarTest( itemDef_t *item) +{ + if (!PC_ParseStringMem((const char **) &item->cvarTest)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_cvar +=============== +*/ +qboolean ItemParse_cvar( itemDef_t *item) +{ + editFieldDef_t *editPtr; + + Item_ValidateTypeData(item); + if (!PC_ParseStringMem(&item->cvar)) + { + return qfalse; + } + + if ( item->typeData) + { + switch ( item->type ) + { + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + case ITEM_TYPE_YESNO: + case ITEM_TYPE_BIND: + case ITEM_TYPE_SLIDER: + case ITEM_TYPE_TEXT: + case ITEM_TYPE_TEXTSCROLL: + editPtr = (editFieldDef_t*)item->typeData; + editPtr->minVal = -1; + editPtr->maxVal = -1; + editPtr->defVal = -1; + break; + } + } + return qtrue; +} + +/* +=============== +ItemParse_maxChars +=============== +*/ +qboolean ItemParse_maxChars( itemDef_t *item) +{ + editFieldDef_t *editPtr; + int maxChars; + + Item_ValidateTypeData(item); + if (!item->typeData) + { + return qfalse; + } + + if (PC_ParseInt(&maxChars)) + { + return qfalse; + } + editPtr = (editFieldDef_t*)item->typeData; + editPtr->maxChars = maxChars; + return qtrue; +} + +/* +=============== +ItemParse_maxPaintChars +=============== +*/ +qboolean ItemParse_maxPaintChars( itemDef_t *item) +{ + editFieldDef_t *editPtr; + int maxChars; + + Item_ValidateTypeData(item); + if (!item->typeData) + { + return qfalse; + } + + if (PC_ParseInt(&maxChars)) + { + return qfalse; + } + editPtr = (editFieldDef_t*)item->typeData; + editPtr->maxPaintChars = maxChars; + return qtrue; +} + + +qboolean ItemParse_lineHeight( itemDef_t *item) +{ + textScrollDef_t *scrollPtr; + int height; + + Item_ValidateTypeData(item); + if (!item->typeData) + { + return qfalse; + } + + if (PC_ParseInt(&height)) + { + return qfalse; + } + + scrollPtr = (textScrollDef_t*)item->typeData; + scrollPtr->lineHeight = height; + + return qtrue; +} + +/* +=============== +ItemParse_cvarFloat +=============== +*/ +qboolean ItemParse_cvarFloat( itemDef_t *item) +{ + editFieldDef_t *editPtr; + + Item_ValidateTypeData(item); + if (!item->typeData) + { + return qfalse; + } + editPtr = (editFieldDef_t*)item->typeData; + if (PC_ParseStringMem((const char **) &item->cvar) && + !PC_ParseFloat(&editPtr->defVal) && + !PC_ParseFloat(&editPtr->minVal) && + !PC_ParseFloat(&editPtr->maxVal)) + { + if (!stricmp(item->cvar,"r_ext_texture_filter_anisotropic")) + {//hehe, hook up the correct max value here. + editPtr->maxVal=glConfig.maxTextureFilterAnisotropy; + } + return qtrue; + } + + return qfalse; +} + +/* +=============== +ItemParse_cvarStrList +=============== +*/ +qboolean ItemParse_cvarStrList( itemDef_t *item) +{ + const char *token; + multiDef_t *multiPtr; + int pass; + + Item_ValidateTypeData(item); + if (!item->typeData) + { + return qfalse; + } + multiPtr = (multiDef_t*)item->typeData; + multiPtr->count = 0; + multiPtr->strDef = qtrue; + + if (PC_ParseString(&token)) + { + return qfalse; + } + + if (!stricmp(token,"feeder") && item->special == FEEDER_PLAYER_SPECIES) + { + for (; multiPtr->count < uiInfo.playerSpeciesCount; multiPtr->count++) + { + multiPtr->cvarList[multiPtr->count] = String_Alloc(strupr(va("@MENUS_%s",uiInfo.playerSpecies[multiPtr->count].Name ))); //look up translation + multiPtr->cvarStr[multiPtr->count] = uiInfo.playerSpecies[multiPtr->count].Name; //value + } + return qtrue; + } + // languages + if (!stricmp(token,"feeder") && item->special == FEEDER_LANGUAGES) + { + assert( 0 ); +/* + for (; multiPtr->count < uiInfo.languageCount; multiPtr->count++) + { + // The displayed text + multiPtr->cvarList[multiPtr->count] = "@MENUS_MYLANGUAGE"; + // The cvar value that goes into se_language + multiPtr->cvarStr[multiPtr->count] = SE_GetLanguageName( multiPtr->count ); + } +*/ + return qtrue; + } + if (*token != '{') + { + return qfalse; + } + + pass = 0; + while ( 1 ) + { + if (!PC_ParseStringMem(&token)) + { + PC_ParseWarning("end of file inside menu item\n"); + return qfalse; + } + if (*token == '}') + { + return qtrue; + } + + if (*token == ',' || *token == ';') + { + continue; + } + + if (pass == 0) + { + multiPtr->cvarList[multiPtr->count] = token; + pass = 1; + } + else + { + multiPtr->cvarStr[multiPtr->count] = token; + pass = 0; + multiPtr->count++; + if (multiPtr->count >= MAX_MULTI_CVARS) + { + return qfalse; + } + } + } + + return qfalse; +} + +/* +=============== +ItemParse_cvarFloatList +=============== +*/ +qboolean ItemParse_cvarFloatList( itemDef_t *item) +{ + const char *token; + multiDef_t *multiPtr; + + Item_ValidateTypeData(item); + if (!item->typeData) + { + return qfalse; + } + multiPtr = (multiDef_t*)item->typeData; + multiPtr->count = 0; + multiPtr->strDef = qfalse; + + if (PC_ParseString(&token)) + { + return qfalse; + } + + if (*token != '{') + { + return qfalse; + } + + while ( 1 ) + { + if (!PC_ParseStringMem(&token)) + { + PC_ParseWarning("end of file inside menu item\n"); + return qfalse; + } + if (*token == '}') + { + return qtrue; + } + + if (*token == ',' || *token == ';') + { + continue; + } + + multiPtr->cvarList[multiPtr->count] = token; //a StringAlloc ptr + if (PC_ParseFloat(&multiPtr->cvarValue[multiPtr->count])) + { + return qfalse; + } + + multiPtr->count++; + if (multiPtr->count >= MAX_MULTI_CVARS) + { + return qfalse; + } + + } + + return qfalse; +} + + +/* +=============== +ItemParse_addColorRange +=============== +*/ +/* +qboolean ItemParse_addColorRange( itemDef_t *item) +{ + colorRangeDef_t color; + + if (PC_ParseFloat(&color.low) && + PC_ParseFloat(&color.high) && + PC_ParseColor(&color.color) ) + { + + if (item->numColors < MAX_COLOR_RANGES) + { + memcpy(&item->colorRanges[item->numColors], &color, sizeof(color)); + item->numColors++; + } + return qtrue; + } + return qfalse; +} +*/ + +/* +=============== +ItemParse_ownerdrawFlag +=============== +*/ +qboolean ItemParse_ownerdrawFlag( itemDef_t *item ) +{ + int i; + if (PC_ParseInt(&i)) + { + return qfalse; + } + item->window.ownerDrawFlags |= i; + return qtrue; +} + +/* +=============== +ItemParse_enableCvar +=============== +*/ +qboolean ItemParse_enableCvar( itemDef_t *item) +{ + if (PC_Script_Parse(&item->enableCvar)) + { + item->cvarFlags = CVAR_ENABLE; + return qtrue; + } + return qfalse; +} + +/* +=============== +ItemParse_disableCvar +=============== +*/ +qboolean ItemParse_disableCvar( itemDef_t *item ) +{ + if (PC_Script_Parse(&item->enableCvar)) + { + item->cvarFlags = CVAR_DISABLE; + return qtrue; + } + return qfalse; +} + +/* +=============== +ItemParse_showCvar +=============== +*/ +qboolean ItemParse_showCvar( itemDef_t *item ) +{ + if (PC_Script_Parse(&item->enableCvar)) + { + item->cvarFlags = CVAR_SHOW; + return qtrue; + } + return qfalse; +} + +/* +=============== +ItemParse_hideCvar +=============== +*/ +qboolean ItemParse_hideCvar( itemDef_t *item) +{ + if (PC_Script_Parse(&item->enableCvar)) + { + item->cvarFlags = CVAR_HIDE; + return qtrue; + } + return qfalse; +} + +/* +=============== +ItemParse_cvarsubstring +=============== +*/ +qboolean ItemParse_cvarsubstring( itemDef_t *item) +{ + assert(item->cvarFlags); //need something set first, then we or in our flag. + item->cvarFlags |= CVAR_SUBSTRING; + return qtrue; +} + +/* +=============== +Item_ValidateTypeData +=============== +*/ +void Item_ValidateTypeData(itemDef_t *item) +{ + if (item->typeData) + { + return; + } + + if (item->type == ITEM_TYPE_LISTBOX) + { + item->typeData = UI_Alloc(sizeof(listBoxDef_t)); + memset(item->typeData, 0, sizeof(listBoxDef_t)); + } + else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD || item->type == ITEM_TYPE_YESNO || item->type == ITEM_TYPE_BIND || item->type == ITEM_TYPE_SLIDER || item->type == ITEM_TYPE_TEXT) + { + item->typeData = UI_Alloc(sizeof(editFieldDef_t)); + memset(item->typeData, 0, sizeof(editFieldDef_t)); + if (item->type == ITEM_TYPE_EDITFIELD) + { + if (!((editFieldDef_t *) item->typeData)->maxPaintChars) + { + ((editFieldDef_t *) item->typeData)->maxPaintChars = MAX_EDITFIELD; + } + } + } + else if (item->type == ITEM_TYPE_MULTI) + { + item->typeData = UI_Alloc(sizeof(multiDef_t)); + } + else if (item->type == ITEM_TYPE_MODEL) + { + item->typeData = UI_Alloc(sizeof(modelDef_t)); + memset(item->typeData, 0, sizeof(modelDef_t)); + } + else if (item->type == ITEM_TYPE_TEXTSCROLL ) + { + item->typeData = UI_Alloc(sizeof(textScrollDef_t)); + } +} + +qboolean ItemParse_isCharacter( itemDef_t *item ) +{ + int i; + if ( !PC_ParseInt(&i) ) + { + if ( i ) + { + item->flags |= ITF_ISCHARACTER; + } + else + { + item->flags &= ~ITF_ISCHARACTER; + } + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_isSaber( itemDef_t *item ) +{ +extern void UI_SaberLoadParms( void ); +extern qboolean ui_saber_parms_parsed; +extern void UI_CacheSaberGlowGraphics( void ); + int i; + if ( !PC_ParseInt(&i) ) + { + if ( i ) + { + item->flags |= ITF_ISSABER; + UI_CacheSaberGlowGraphics(); + if ( !ui_saber_parms_parsed ) + { + UI_SaberLoadParms(); + } + } + else + { + item->flags &= ~ITF_ISSABER; + } + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_isSaber2( itemDef_t *item ) +{ +extern void UI_SaberLoadParms( void ); +extern qboolean ui_saber_parms_parsed; +extern void UI_CacheSaberGlowGraphics( void ); + int i; + if ( !PC_ParseInt(&i) ) + { + if ( i ) + { + item->flags |= ITF_ISSABER2; + UI_CacheSaberGlowGraphics(); + if ( !ui_saber_parms_parsed ) + { + UI_SaberLoadParms(); + } + } + else + { + item->flags &= ~ITF_ISSABER2; + } + return qtrue; + } + return qfalse; +} + +keywordHash_t itemParseKeywords[] = { +// {"accept", ItemParse_accept, }, + {"selectNext", ItemParse_selectionNext, }, + {"selectPrev", ItemParse_selectionPrev, }, + {"action", ItemParse_action, }, +// {"addColorRange", ItemParse_addColorRange, }, + {"align", ItemParse_align, }, +// {"appearance_slot", ItemParse_Appearance_slot, }, + {"asset_model", ItemParse_asset_model, }, + {"asset_shader", ItemParse_asset_shader, }, + {"isCharacter", ItemParse_isCharacter, }, + {"isSaber", ItemParse_isSaber, }, + {"isSaber2", ItemParse_isSaber2, }, + {"autowrapped", ItemParse_autowrapped, }, + {"backcolor", ItemParse_backcolor, }, + {"background", ItemParse_background, }, + {"border", ItemParse_border, }, + {"bordercolor", ItemParse_bordercolor, }, + {"bordersize", ItemParse_bordersize, }, +// {"cinematic", ItemParse_cinematic, }, + {"columns", ItemParse_columns, }, + {"cvar", ItemParse_cvar, }, + {"cvarFloat", ItemParse_cvarFloat, }, + {"cvarFloatList", ItemParse_cvarFloatList, }, + {"cvarSubString", ItemParse_cvarsubstring }, + {"cvarStrList", ItemParse_cvarStrList, }, + {"cvarTest", ItemParse_cvarTest, }, + {"decoration", ItemParse_decoration, }, +// {"desctext", ItemParse_descText }, + {"disableCvar", ItemParse_disableCvar, }, + {"doubleclick", ItemParse_doubleClick, }, + {"elementheight", ItemParse_elementheight, }, + {"elementtype", ItemParse_elementtype, }, + {"elementwidth", ItemParse_elementwidth, }, + {"enableCvar", ItemParse_enableCvar, }, + {"feeder", ItemParse_feeder, }, + {"flag", ItemParse_flag, }, + {"focusSound", ItemParse_focusSound, }, +#ifdef _IMMERSION + {"focusForce", ItemParse_focusForce, }, +#endif // _IMMERSION + {"font", ItemParse_font, }, + {"forecolor", ItemParse_forecolor, }, + {"group", ItemParse_group, }, + {"hideCvar", ItemParse_hideCvar, }, + {"horizontalscroll",ItemParse_horizontalscroll, }, + {"leaveFocus", ItemParse_leaveFocus, }, + {"maxChars", ItemParse_maxChars, }, + {"maxPaintChars", ItemParse_maxPaintChars, }, + {"model_angle", ItemParse_model_angle, }, + {"model_fovx", ItemParse_model_fovx, }, + {"model_fovy", ItemParse_model_fovy, }, + {"model_origin", ItemParse_model_origin, }, + {"model_rotation", ItemParse_model_rotation, }, + //rww - g2 begin + {"model_g2mins", ItemParse_model_g2mins, }, + {"model_g2maxs", ItemParse_model_g2maxs, }, + {"model_g2skin", ItemParse_model_g2skin, }, + {"model_g2anim", ItemParse_model_g2anim, }, + //rww - g2 end +// {"mouseEnter", ItemParse_mouseEnter, }, +// {"mouseEnterText", ItemParse_mouseEnterText, }, +// {"mouseExit", ItemParse_mouseExit, }, +// {"mouseExitText", ItemParse_mouseExitText, }, + {"name", ItemParse_name }, + {"notselectable", ItemParse_notselectable, }, +//JLF +#ifdef _XBOX + {"scrollhidden", ItemParse_scrollhidden, }, +#endif +//JLF END + {"onFocus", ItemParse_onFocus, }, +// {"outlinecolor", ItemParse_outlinecolor, }, + {"ownerdraw", ItemParse_ownerdraw, }, + {"ownerdrawFlag", ItemParse_ownerdrawFlag, }, + {"rect", ItemParse_rect, }, + {"showCvar", ItemParse_showCvar, }, + {"special", ItemParse_special, }, + {"style", ItemParse_style, }, + {"text", ItemParse_text }, +// {"text2", ItemParse_text2 }, +// {"text2alignx", ItemParse_text2alignx, }, +// {"text2aligny", ItemParse_text2aligny, }, + {"textalign", ItemParse_textalign, }, + {"textalignx", ItemParse_textalignx, }, + {"textaligny", ItemParse_textaligny, }, + {"textscale", ItemParse_textscale, }, + {"textstyle", ItemParse_textstyle, }, + {"type", ItemParse_type, }, + {"visible", ItemParse_visible, }, + {"wrapped", ItemParse_wrapped, }, +// {"invertyesno", ItemParse_invertyesno }, + {"xoffset", ItemParse_xoffset },//for yes/no and multi + {"selectionshader", ItemParse_selectionShader }, + + + + // Text scroll specific + {"lineHeight", ItemParse_lineHeight, NULL }, + + {NULL, NULL, } +}; + +keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE]; + +/* +=============== +Item_SetupKeywordHash +=============== +*/ +void Item_SetupKeywordHash(void) +{ + int i; + + memset(itemParseKeywordHash, 0, sizeof(itemParseKeywordHash)); + for (i = 0; itemParseKeywords[i].keyword; i++) + { + KeywordHash_Add(itemParseKeywordHash, &itemParseKeywords[i]); + } +} + + +/* +=============== +Item_Parse +=============== +*/ +qboolean Item_Parse(itemDef_t *item) +{ + keywordHash_t *key; + const char *token; + + // Brace is handled by MenuParse_ItemDef now, to handle wedge hackery +/* + if (PC_ParseString(&token)) + { + return qfalse; + } + + if (*token != '{') + { + return qfalse; + } +*/ + + while ( 1 ) + { + if (PC_ParseString(&token)) + { + PC_ParseWarning("End of file inside menu item"); + return qfalse; + } + + if (*token == '}') + { +/* if (!item->window.name) + { + item->window.name = defaultString; + Com_Printf(S_COLOR_YELLOW"WARNING: Menu item has no name\n"); + } + + if (!item->window.group) + { + item->window.group = defaultString; + Com_Printf(S_COLOR_YELLOW"WARNING: Menu item has no group\n"); + } +*/ + return qtrue; + } + + key = (keywordHash_s *) KeywordHash_Find(itemParseKeywordHash, token); + if (!key) + { + PC_ParseWarning(va("Unknown item keyword '%s'", token)); + continue; + } + + if ( !key->func(item) ) + { + PC_ParseWarning(va("Couldn't parse item keyword '%s'", token)); + return qfalse; + } + } +} + +static void Item_TextScroll_BuildLines ( itemDef_t* item ) +{ + // new asian-aware line breaker... (pasted from elsewhere late @ night, hence aliasing-vars ;-) + // + textScrollDef_t* scrollPtr = (textScrollDef_t*) item->typeData; + const char *psText = item->text; // for copy/paste ease + int iBoxWidth = item->window.rect.w - SCROLLBAR_SIZE - 10; + + // this could probably be simplified now, but it was converted from something else I didn't originally write, + // and it works anyway so wtf... + // + const char *psCurrentTextReadPos; + const char *psReadPosAtLineStart; + const char *psBestLineBreakSrcPos; + const char *psLastGood_s; // needed if we get a full screen of chars with no punctuation or space (see usage notes) + qboolean bIsTrailingPunctuation; + unsigned int uiLetter; + + if (!psText) + { + return; + } + + if (*psText == '@') // string reference + { +// trap_SP_GetStringTextString( &psText[1], text, sizeof(text)); + psText = SE_GetString( &psText[1] ); + } + + psCurrentTextReadPos = psText; + psReadPosAtLineStart = psCurrentTextReadPos; + psBestLineBreakSrcPos = psCurrentTextReadPos; + + scrollPtr->iLineCount = 0; + memset((char*)scrollPtr->pLines,0,sizeof(scrollPtr->pLines)); + + while (*psCurrentTextReadPos && (scrollPtr->iLineCount < MAX_TEXTSCROLL_LINES) ) + { + char sLineForDisplay[2048]; // ott + + // construct a line... + // + psCurrentTextReadPos = psReadPosAtLineStart; + sLineForDisplay[0] = '\0'; + while ( *psCurrentTextReadPos ) + { + int iAdvanceCount; + psLastGood_s = psCurrentTextReadPos; + + // read letter... + // + uiLetter = ui.AnyLanguage_ReadCharFromString(psCurrentTextReadPos, &iAdvanceCount, &bIsTrailingPunctuation); + psCurrentTextReadPos += iAdvanceCount; + + // concat onto string so far... + // + if (uiLetter == 32 && sLineForDisplay[0] == '\0') + { + psReadPosAtLineStart++; + continue; // unless it's a space at the start of a line, in which case ignore it. + } + + if (uiLetter > 255) + { + Q_strcat(sLineForDisplay, sizeof(sLineForDisplay),va("%c%c",uiLetter >> 8, uiLetter & 0xFF)); + } + else + { + Q_strcat(sLineForDisplay, sizeof(sLineForDisplay),va("%c",uiLetter & 0xFF)); + } + + if (uiLetter == '\n') + { + // explicit new line... + // + sLineForDisplay[ strlen(sLineForDisplay)-1 ] = '\0'; // kill the CR + psReadPosAtLineStart = psCurrentTextReadPos; + psBestLineBreakSrcPos = psCurrentTextReadPos; + + // hack it to fit in with this code... + // + scrollPtr->pLines[ scrollPtr->iLineCount ] = String_Alloc ( sLineForDisplay ); + break; // print this line + } + else + if ( DC->textWidth( sLineForDisplay, item->textscale, item->font ) >= iBoxWidth ) + { + // reached screen edge, so cap off string at bytepos after last good position... + // + if (uiLetter > 255 && bIsTrailingPunctuation && !ui.Language_UsesSpaces()) + { + // Special case, don't consider line breaking if you're on an asian punctuation char of + // a language that doesn't use spaces... + // + uiLetter = uiLetter; // breakpoint line only + } + else + { + if (psBestLineBreakSrcPos == psReadPosAtLineStart) + { + // aarrrggh!!!!! we'll only get here is someone has fed in a (probably) garbage string, + // since it doesn't have a single space or punctuation mark right the way across one line + // of the screen. So far, this has only happened in testing when I hardwired a taiwanese + // string into this function while the game was running in english (which should NEVER happen + // normally). On the other hand I suppose it's entirely possible that some taiwanese string + // might have no punctuation at all, so... + // + psBestLineBreakSrcPos = psLastGood_s; // force a break after last good letter + } + + sLineForDisplay[ psBestLineBreakSrcPos - psReadPosAtLineStart ] = '\0'; + psReadPosAtLineStart = psCurrentTextReadPos = psBestLineBreakSrcPos; + + // hack it to fit in with this code... + // + scrollPtr->pLines[ scrollPtr->iLineCount ] = String_Alloc( sLineForDisplay ); + break; // print this line + } + } + + // record last-good linebreak pos... (ie if we've just concat'd a punctuation point (western or asian) or space) + // + if (bIsTrailingPunctuation || uiLetter == ' ' || (uiLetter > 255 && !ui.Language_UsesSpaces())) + { + psBestLineBreakSrcPos = psCurrentTextReadPos; + } + } + + /// arrgghh, this is gettng horrible now... + // + if (scrollPtr->pLines[ scrollPtr->iLineCount ] == NULL && strlen(sLineForDisplay)) + { + // then this is the last line and we've just run out of text, no CR, no overflow etc... + // + scrollPtr->pLines[ scrollPtr->iLineCount ] = String_Alloc( sLineForDisplay ); + } + + scrollPtr->iLineCount++; + } +} + +/* +=============== +Item_InitControls + init's special control types +=============== +*/ +void Item_InitControls(itemDef_t *item) +{ + if (item == NULL) + { + return; + } + if (item->type == ITEM_TYPE_LISTBOX) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + item->cursorPos = 0; + if (listPtr) + { + listPtr->cursorPos = 0; + listPtr->startPos = 0; + listPtr->endPos = 0; + listPtr->cursorPos = 0; + } + } +} + +/* +================= +Int_Parse +================= +*/ +qboolean Int_Parse(const char **p, int *i) +{ + char *token; + token = COM_ParseExt(p, qfalse); + + if (token && token[0] != 0) + { + *i = atoi(token); + return qtrue; + } + else + { + return qfalse; + } +} + +/* +================= +String_Parse +================= +*/ +qboolean String_Parse(const char **p, const char **out) +{ + char *token; + + token = COM_ParseExt(p, qfalse); + if (token && token[0] != 0) + { + *(out) = String_Alloc(token); + return *(out)!=NULL; + } + return qfalse; +} + +/* +=============== +Item_RunScript +=============== +*/ +void Item_RunScript(itemDef_t *item, const char *s) +{ + const char *p; + int i; + qboolean bRan; + + uiInfo.runScriptItem = item; + + if (item && s && s[0]) + { + p = s; + while (1) + { + const char *command; + // expect command then arguments, ; ends command, NULL ends script + if (!String_Parse(&p, &command)) + { + return; + } + + if (command[0] == ';' && command[1] == '\0') + { + continue; + } + + bRan = qfalse; + for (i = 0; i < scriptCommandCount; i++) + { + if (Q_stricmp(command, commandList[i].name) == 0) + { + if ( !(commandList[i].handler(item, &p)) ) + { + return; + } + + bRan = qtrue; + break; + } + } + // not in our auto list, pass to handler + if (!bRan) + { + // Allow any script command to fail + if ( !DC->runScript(&p) ) + { + break; + } + } + } + } +} + + +/* +=============== +Menu_SetupKeywordHash +=============== +*/ +void Menu_SetupKeywordHash(void) +{ + int i; + + memset(menuParseKeywordHash, 0, sizeof(menuParseKeywordHash)); + for (i = 0; menuParseKeywords[i].keyword; i++) + { + KeywordHash_Add(menuParseKeywordHash, &menuParseKeywords[i]); + } +} + + +/* +=============== +Menus_ActivateByName +=============== +*/ + +void Menu_HandleMouseMove(menuDef_t *menu, float x, float y); +menuDef_t *Menus_ActivateByName(const char *p) +{ + int i; + menuDef_t *m = NULL; + menuDef_t *focus = Menu_GetFocused(); + + for (i = 0; i < menuCount; i++) + { + // Look for the name in the current list of windows + if (Q_stricmp(Menus[i].window.name, p) == 0) + { + + + m = &Menus[i]; + Menus_Activate(m); + if (openMenuCount < MAX_OPEN_MENUS && focus != NULL) + { + menuStack[openMenuCount++] = focus; + } + } + else + { + Menus[i].window.flags &= ~WINDOW_HASFOCUS; + } + } + + + if (!m) + { // A hack so we don't have to load all three mission menus before we know what tier we're on + if (!Q_stricmp( p, "ingameMissionSelect1" ) ) + { + UI_LoadMenus("ui/tier1.txt",qfalse); + Menus_CloseAll(); + Menus_OpenByName("ingameMissionSelect1"); + } + else if (!Q_stricmp( p, "ingameMissionSelect2" ) ) + { + UI_LoadMenus("ui/tier2.txt",qfalse); + Menus_CloseAll(); + Menus_OpenByName("ingameMissionSelect2"); + } + else if (!Q_stricmp( p, "ingameMissionSelect3" ) ) + { + UI_LoadMenus("ui/tier3.txt",qfalse); + Menus_CloseAll(); + Menus_OpenByName("ingameMissionSelect3"); + } + else + { + Com_Printf(S_COLOR_YELLOW"WARNING: Menus_ActivateByName: Unable to find menu '%s'\n",p); + } + } + + // First time, show force select instructions + if (!Q_stricmp( p, "ingameForceSelect" ) ) + { + int tier_storyinfo = Cvar_VariableIntegerValue( "tier_storyinfo" ); + + if (tier_storyinfo==1) + { +#ifndef _XBOX + Menus_OpenByName("ingameForceHelp"); +#endif + } + } + + // First time, show weapons select instructions + if (!Q_stricmp( p, "ingameWpnSelect" ) ) + { + int tier_storyinfo = Cvar_VariableIntegerValue( "tier_storyinfo" ); + + if (tier_storyinfo==1) + { +#ifndef _XBOX + Menus_OpenByName("ingameWpnSelectHelp"); +#endif + } + } + + // Want to handle a mouse move on the new menu in case your already over an item + Menu_HandleMouseMove ( m, DC->cursorx, DC->cursory ); + + + + + return m; +} + +/* +=============== +Menus_Activate +=============== +*/ +void Menus_Activate(menuDef_t *menu) +{ + menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE); + +//JLFCALLOUT MPMOVED +#ifdef _XBOX + + if (!(menu->window.flags & WINDOW_POPUP)&& menu->fullScreen ) + { + DC->setCVar("ui_showWmove", "0"); + + DC->setCVar("ui_showXrls", "0"); + DC->setCVar("ui_showXctrl", "0"); + DC->setCVar("ui_showXNew", "0"); + DC->setCVar("ui_showXforce", "0"); + DC->setCVar("ui_showXdfrc", "0"); + DC->setCVar("ui_showXcstm", "0"); + DC->setCVar("ui_showXhost", "0"); + DC->setCVar("ui_showXchk", "0"); + + DC->setCVar("ui_showYref", "0"); + DC->setCVar("ui_showYdel", "0"); + DC->setCVar("ui_showYsaber", "0"); + DC->setCVar("ui_showYwpn", "0"); + + DC->setCVar("ui_showAcallout", "0"); + DC->setCVar("ui_showBcallout", "0"); + DC->setCVar("ui_cancelYScript", "0"); + } +#endif +//JLF END + if (menu->onOpen) + { + itemDef_t item; + item.parent = menu; + item.window.flags = 0; //err, item is fake here, but we want a valid flag before calling runscript + Item_RunScript(&item, menu->onOpen); + + if (item.window.flags & WINDOW_SCRIPTWAITING) //in case runscript set waiting, copy it up to the menu + { + menu->window.flags |= WINDOW_SCRIPTWAITING; + menu->window.delayedScript = item.window.delayedScript; + menu->window.delayTime = item.window.delayTime; + } + } + +// menu->appearanceTime = DC->realTime + 1000; + menu->appearanceTime = 0; + menu->appearanceCnt = 0; + +} + +typedef struct { + char *command; + int id; + int defaultbind1; + int defaultbind2; + int bind1; + int bind2; +} bind_t; + +static bind_t g_bindings[] = +{ + {"invuse", A_ENTER, -1, -1, -1}, + {"force_throw", A_F1, -1, -1, -1}, + {"force_pull", A_F2, -1, -1, -1}, + {"force_speed", A_F3, -1, -1, -1}, + {"force_distract", A_F4, -1, -1, -1}, + {"force_heal", A_F5, -1, -1, -1}, + {"+force_grip", A_F6, -1, -1, -1}, + {"+force_lightning",A_F7, -1, -1, -1}, + //new powers + {"+force_drain", -1, -1, -1, -1}, + {"force_rage", -1, -1, -1, -1}, + {"force_protect", -1, -1, -1, -1}, + {"force_absorb", -1, -1, -1, -1}, + {"force_sight", -1, -1, -1, -1}, + + {"taunt", -1, -1, -1, -1}, + + {"+useforce", 'f', -1, -1, -1}, + {"forceprev", 'z', -1, -1, -1}, + {"forcenext", 'x', -1, -1, -1}, + {"use_bacta", -1, -1, -1, -1}, + {"use_seeker", -1, -1, -1, -1}, + {"use_sentry", -1, -1, -1, -1}, + {"use_lightamp_goggles",-1, -1, -1, -1}, + {"use_electrobinoculars",-1, -1, -1, -1}, + {"invnext", -1, -1, -1, -1}, + {"invprev", -1, -1, -1, -1}, + {"invuse", -1, -1, -1, -1}, + {"+speed", A_SHIFT, -1, -1, -1}, + {"+forward", A_CURSOR_UP, -1, -1, -1}, + {"+back", A_CURSOR_DOWN, -1, -1, -1}, + {"+moveleft", ',', -1, -1, -1}, + {"+moveright", '.', -1, -1, -1}, + {"+moveup", 'v', -1, -1, -1}, + {"+movedown", 'c', -1, -1, -1}, + {"+left", A_CURSOR_LEFT, -1, -1, -1}, + {"+right", A_CURSOR_RIGHT, -1, -1, -1}, + {"+strafe", -1, -1, -1, -1}, + {"+lookup", A_PAGE_DOWN, -1, -1, -1}, + {"+lookdown", A_DELETE, -1, -1, -1}, + {"+mlook", '/', -1, -1, -1}, + {"centerview", A_END, -1, -1, -1}, + {"zoom", -1, -1, -1, -1}, + {"weapon 0", -1, -1, -1, -1}, + {"weapon 1", '1', -1, -1, -1}, + {"weapon 2", '2', -1, -1, -1}, + {"weapon 3", '3', -1, -1, -1}, + {"weapon 4", '4', -1, -1, -1}, + {"weapon 5", '5', -1, -1, -1}, + {"weapon 6", '6', -1, -1, -1}, + {"weapon 7", '7', -1, -1, -1}, + {"weapon 8", '8', -1, -1, -1}, + {"weapon 9", '9', -1, -1, -1}, + {"weapon 10", '0', -1, -1, -1}, + {"weapon 11", -1, -1, -1, -1}, + {"weapon 12", -1, -1, -1, -1}, + {"weapon 13", -1, -1, -1, -1}, + {"+attack", A_CTRL, -1, -1, -1}, + {"+altattack", A_ALT, -1, -1, -1}, + {"weapprev", '[', -1, -1, -1}, + {"weapnext", ']', -1, -1, -1}, + {"+use", A_SPACE, -1, -1, -1}, + {"datapad", A_TAB, -1, -1, -1}, + {"save quick", A_F9, -1, -1, -1}, + {"load quick", -1, -1, -1, -1}, + {"load auto", -1, -1, -1, -1}, + {"cg_thirdperson !",'p', -1, -1, -1}, + {"exitview", -1, -1, -1, -1}, + {"uimenu ingameloadmenu", A_F10, -1, -1, -1}, + {"uimenu ingamesavemenu", A_F11, -1, -1, -1}, + {"saberAttackCycle",-1, -1, -1, -1}, +}; + + +static const int g_bindCount = sizeof(g_bindings) / sizeof(bind_t); + +/* +================= +Controls_GetKeyAssignment +================= +*/ +static void Controls_GetKeyAssignment (char *command, int *twokeys) +{ + int count; + int j; + char b[256]; + + twokeys[0] = twokeys[1] = -1; + count = 0; + + for ( j = 0; j < MAX_KEYS; j++ ) + { + DC->getBindingBuf( j, b, 256 ); + if ( *b == 0 ) + { + continue; + } + if ( !Q_stricmp( b, command ) ) + { + twokeys[count] = j; + count++; + if (count == 2) + { + break; + } + } + } +} + +/* +================= +Controls_GetConfig +================= +*/ +void Controls_GetConfig( void ) +{ + int i; + int twokeys[2]; + + // iterate each command, get its numeric binding + for (i=0; i < g_bindCount; i++) + { + Controls_GetKeyAssignment(g_bindings[i].command, twokeys); + + g_bindings[i].bind1 = twokeys[0]; + g_bindings[i].bind2 = twokeys[1]; + } +} + + +/* +=============== +Item_SetScreenCoords +=============== +*/ +void Item_SetScreenCoords(itemDef_t *item, float x, float y) +{ + + if (item == NULL) + { + return; + } + + if (item->window.border != 0) + { + x += item->window.borderSize; + y += item->window.borderSize; + } + + item->window.rect.x = x + item->window.rectClient.x; + item->window.rect.y = y + item->window.rectClient.y; + item->window.rect.w = item->window.rectClient.w; + item->window.rect.h = item->window.rectClient.h; + + // force the text rects to recompute + item->textRect.w = 0; + item->textRect.h = 0; + + switch ( item->type) + { + case ITEM_TYPE_TEXTSCROLL: + { + textScrollDef_t *scrollPtr = (textScrollDef_t*)item->typeData; + if ( scrollPtr ) + { + scrollPtr->startPos = 0; + scrollPtr->endPos = 0; + } + + Item_TextScroll_BuildLines ( item ); + + break; + } + } +} + + +static void Menu_FreeGhoulItems(menuDef_t *menu) +{ + int i,j; + for (i = 0; i < menu->itemCount; i++) + { + for (j=0; j < menu->items[i]->ghoul2.size(); j++) + { + if ( menu->items[i]->ghoul2[j].mModelindex >= 0) + { + DC->g2_RemoveGhoul2Model( menu->items[i]->ghoul2, j ); + } + } + (menu->items[i]->ghoul2).clear(); //clear is the public Free so i can actually remove this slot + } +} +/* +=============== +Menu_Reset +=============== +*/ +void Menu_Reset(void) +{ + //FIXME iterate menus to destoy G2 assets. + int i; + + for (i = 0; i < menuCount; i++) + { + Menu_FreeGhoulItems( &Menus[i] ); + } + + menuCount = 0; +} + +/* +=============== +Menu_UpdatePosition +=============== +*/ +void Menu_UpdatePosition(menuDef_t *menu) +{ + int i; + float x, y; + + if (menu == NULL) + { + return; + } + + x = menu->window.rect.x; + y = menu->window.rect.y; + if (menu->window.border != 0) + { + x += menu->window.borderSize; + y += menu->window.borderSize; + } + + for (i = 0; i < menu->itemCount; i++) + { + Item_SetScreenCoords(menu->items[i], x, y); + } +} + +/* +=============== +Menu_PostParse +=============== +*/ +void Menu_PostParse(menuDef_t *menu) +{ + if (menu == NULL) + { + return; + } + + if (menu->fullScreen) + { + menu->window.rect.x = 0; + menu->window.rect.y = 0; + menu->window.rect.w = 640; + menu->window.rect.h = 480; + } + Menu_UpdatePosition(menu); +} + +/* +=============== +Menu_Init +=============== +*/ +void Menu_Init(menuDef_t *menu) +{ + memset(menu, 0, sizeof(menuDef_t)); + menu->cursorItem = -1; + + UI_Cursor_Show(qtrue); + + if (DC) + { + menu->fadeAmount = DC->Assets.fadeAmount; + menu->fadeClamp = DC->Assets.fadeClamp; + menu->fadeCycle = DC->Assets.fadeCycle; + } + + Window_Init(&menu->window); +} + +/* +=============== +Menu_Parse +=============== +*/ +qboolean Menu_Parse(char *inbuffer, menuDef_t *menu) +{ +// pc_token_t token; + keywordHash_t *key; + char *token2; + char * buffer; + bool nest= false; + buffer = inbuffer; +#ifdef _XBOX + char * includeBuffer; +#endif + + token2 = PC_ParseExt(); + + if (!token2) + { + return qfalse; + } + + if (*token2 != '{') + { + PC_ParseWarning("Misplaced {"); + return qfalse; + } + while ( 1 ) + { + + token2 = PC_ParseExt(); + if (!token2) + { + PC_ParseWarning("End of file inside menu."); + return qfalse; + } + + if (*token2 == '}') + { + return qtrue; + } +#ifdef _XBOX +//JLFCALLOUT +char * UI_ParseInclude(const char *menuFile, menuDef_t * menu); + + if (!strcmp (token2, "#include")) + { + token2 = PC_ParseExt(); + includeBuffer = UI_ParseInclude(token2, menu ); + //bufferize thetoken2 + nest = true; + buffer = includeBuffer; + continue; + } +#endif + + if (nest && (*token2 == 0)) + { + PC_EndParseSession(buffer); + + nest = false; + continue; + } + key = KeywordHash_Find(menuParseKeywordHash, token2); + + if (!key) + { + PC_ParseWarning(va("Unknown menu keyword %s",token2)); + continue; + } + + if ( !key->func((itemDef_t*)menu) ) + { + PC_ParseWarning(va("Couldn't parse menu keyword %s as %s",token2, key->keyword)); + return qfalse; + } + } +} + +/* +=============== +Menu_New +=============== +*/ +void Menu_New(char *buffer) +{ + menuDef_t *menu = &Menus[menuCount]; + + if (menuCount < MAX_MENUS) + { + Menu_Init(menu); + if (Menu_Parse(buffer, menu)) + { + Menu_PostParse(menu); + menuCount++; + } + } +} + +/* +=============== +Menus_CloseAll +=============== +*/ +void Menus_CloseAll(void) +{ + int i; + + for (i = 0; i < menuCount; i++) + { + Menu_RunCloseScript ( &Menus[i] ); + Menus[i].window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); + } + + // Clear the menu stack + openMenuCount = 0; +} + +/* +=============== +PC_StartParseSession +=============== +*/ +#ifdef _XBOX +int PC_StartParseSession(const char *fileName,char **buffer, bool nested) +#else +int PC_StartParseSession(const char *fileName,char **buffer) +#endif +{ + int len; + + // Try to open file and read it in. + len = ui.FS_ReadFile( fileName,(void **) buffer ); + + // Not there? + if ( len>0 ) + { +#ifdef _XBOX + if (nested) + parseDataCount = 1; + else +#endif + parseDataCount = 0; + + strncpy(parseData[parseDataCount].fileName, fileName, MAX_QPATH); + parseData[parseDataCount].bufferStart = *buffer; + parseData[parseDataCount].bufferCurrent = *buffer; + +#ifdef _XBOX + COM_BeginParseSession(nested); +#else + COM_BeginParseSession(); +#endif + } + + return len; +} + +/* +=============== +PC_EndParseSession +=============== +*/ +void PC_EndParseSession(char *buffer) +{ + parseDataCount--; + ui.FS_FreeFile( buffer ); //let go of the buffer +} + +/* +=============== +PC_ParseWarning +=============== +*/ +void PC_ParseWarning(const char *message) +{ + ui.Printf(S_COLOR_YELLOW "WARNING: %s Line #%d of File '%s'\n", message,parseData[parseDataCount].com_lines,parseData[parseDataCount].fileName); +} + +char *PC_ParseExt(void) +{ + return (COM_ParseExt(&parseData[parseDataCount].bufferCurrent, qtrue)); +} + +qboolean PC_ParseString(const char **string) +{ + int hold; + + hold = COM_ParseString(&parseData[parseDataCount].bufferCurrent,string); + + while (hold==0 && **string == 0) + { + hold = COM_ParseString(&parseData[parseDataCount].bufferCurrent,string); + } + + return(hold); +} + +qboolean PC_ParseInt(int *number) +{ + return(COM_ParseInt(&parseData[parseDataCount].bufferCurrent,number)); +} + +qboolean PC_ParseFloat(float *number) +{ + return(COM_ParseFloat(&parseData[parseDataCount].bufferCurrent,number)); +} + +qboolean PC_ParseColor(vec4_t *color) +{ + return(COM_ParseVec4(&parseData[parseDataCount].bufferCurrent, color)); +} + +void PC_SkipBracedSection( void ) +{ + SkipBracedSection( &parseData[parseDataCount].bufferCurrent ); +} + +/* +================= +Menu_Count +================= +*/ +int Menu_Count(void) +{ + return menuCount; +} + +/* +================= +Menu_PaintAll +================= +*/ +void Menu_PaintAll(void) +{ + int i; + if (captureFunc) + { + captureFunc(captureData); + } + + + for (i = 0; i < menuCount; i++) + { + if (!(Menus[i].window.flags & WINDOW_POPUP || !Menus[i].fullScreen)) + Menu_Paint(&Menus[i], qfalse); + } + + for (i = 0; i < menuCount; i++) + { + if ((Menus[i].window.flags & WINDOW_POPUP || !Menus[i].fullScreen)) + Menu_Paint(&Menus[i], qfalse); + } + + + if (uis.debugMode) + { + vec4_t v = {1, 1, 1, 1}; + DC->drawText(5, 25, .75, v, va("(%d,%d)",DC->cursorx,DC->cursory), 0, 0, DC->Assets.qhMediumFont); + DC->drawText(5, 10, .75, v, va("fps: %f", DC->FPS), 0, 0, DC->Assets.qhMediumFont); + } +} + +/* +================= +Menu_Paint +================= +*/ +void Menu_Paint(menuDef_t *menu, qboolean forcePaint) +{ + int i; + + if (menu == NULL) + { + return; + } + + if (menu->window.flags & WINDOW_SCRIPTWAITING) + { + if (DC->realTime > menu->window.delayTime) + { // Time has elapsed, resume running whatever script we saved + itemDef_t item; + item.parent = menu; + item.window.flags = 0; //clear before calling RunScript + menu->window.flags &= ~WINDOW_SCRIPTWAITING; + Item_RunScript(&item, menu->window.delayedScript); + + // Could have hit another delay. Need to hoist from fake item + if (item.window.flags & WINDOW_SCRIPTWAITING) + { + menu->window.flags |= WINDOW_SCRIPTWAITING; + menu->window.delayedScript = item.window.delayedScript; + menu->window.delayTime = item.window.delayTime; + } + } + } + + if (!(menu->window.flags & WINDOW_VISIBLE) && !forcePaint) + { + return; + } + +// if (menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible(menu->window.ownerDrawFlags)) +// { +// return; +// } + + if (forcePaint) + { + menu->window.flags |= WINDOW_FORCED; + } + + // draw the background if necessary + if (menu->fullScreen) + { + + vec4_t color; + color[0] = menu->window.backColor[0]; + color[1] = menu->window.backColor[1]; + color[2] = menu->window.backColor[2]; + color[3] = menu->window.backColor[3]; + + ui.R_SetColor( color); + + if (menu->window.background==0) // No background shader given? Make it blank + { + menu->window.background = uis.whiteShader; + } + + DC->drawHandlePic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background ); + } + else if (menu->window.background) + { + // this allows a background shader without being full screen + //UI_DrawHandlePic(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, menu->backgroundShader); + } + + // paint the background and or border + Window_Paint(&menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle ); + + // Loop through all items for the menu and paint them + int iSlotsVisible = 0; + for (i = 0; i < menu->itemCount; i++) + { +// if (!menu->items[i]->appearanceSlot) +// { + Item_Paint(menu->items[i], qtrue); +// } +// else // Timed order of appearance +// { +// if (Item_Paint(menu->items[i], qfalse) ) +// { +// iSlotsVisible++; //It would paint +// } +// if (menu->items[i]->appearanceSlot<=menu->appearanceCnt) +// { +// Item_Paint(menu->items[i], qtrue); +// } +// } +#ifdef _XBOX +//JLFCALLOUT +// if ( menu->items[i]->window.flags & WINDOW_HASFOCUS) +// { +// if ( menu->items[i]->type == ITEM_TYPE_BUTTON || menu->onAccept) +// { + +// } +// } +#endif + } + if (iSlotsVisible && menu->appearanceTime < DC->realTime && menu->appearanceCnt < menu->itemCount) // Time to show another item + { + menu->appearanceTime = DC->realTime + menu->appearanceIncrement; + menu->appearanceCnt++; + } + + + if (uis.debugMode) + { + vec4_t color; + color[0] = color[2] = color[3] = 1; + color[1] = 0; + DC->drawRect(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color); + } +} + +/* +================= +Item_EnableShowViaCvar +================= +*/ +qboolean Item_EnableShowViaCvar(itemDef_t *item, int flag) +{ + if (item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest) + { + char script[1024]; + const char *p; + char buff[1024]; + if (item->cvarFlags & CVAR_SUBSTRING) + { + const char *val; + p = item->enableCvar; + if (!String_Parse(&p, &val)) + {//strip the quotes off + return (item->cvarFlags & flag) ? qfalse : qtrue; + } + Q_strncpyz(buff, val, sizeof(buff), qtrue); + DC->getCVarString(item->cvarTest, script, sizeof(script)); + p = script; + } + else + { + DC->getCVarString(item->cvarTest, buff, sizeof(buff)); + Q_strncpyz(script, item->enableCvar, sizeof(script), qtrue); + p = script; + } + while (1) + { + const char *val; + // expect value then ; or NULL, NULL ends list + if (!String_Parse(&p, &val)) + { + return (item->cvarFlags & flag) ? qfalse : qtrue; + } + + if (val[0] == ';' && val[1] == '\0') + { + continue; + } + + // enable it if any of the values are true + if (item->cvarFlags & flag) + { + if (Q_stricmp(buff, val) == 0) + { + return qtrue; + } + } + else + { + // disable it if any of the values are true + if (Q_stricmp(buff, val) == 0) + { + return qfalse; + } + } + } + return (item->cvarFlags & flag) ? qfalse : qtrue; + } + return qtrue; +} + +/* +================= +Item_SetTextExtents +================= +*/ +void Item_SetTextExtents(itemDef_t *item, int *width, int *height, const char *text) +{ + const char *textPtr = (text) ? text : item->text; + + if (textPtr == NULL ) + { + return; + } + + *width = item->textRect.w; + *height = item->textRect.h; + + // keeps us from computing the widths and heights more than once + if (*width == 0 || (item->type == ITEM_TYPE_OWNERDRAW && item->textalignment == ITEM_ALIGN_CENTER) + || (item->text && item->text[0]=='@' && item->asset != se_language->modificationCount ) //string package language changed + ) + { + int originalWidth; + + originalWidth = DC->textWidth(textPtr, item->textscale, item->font); + + if (item->type == ITEM_TYPE_OWNERDRAW && (item->textalignment == ITEM_ALIGN_CENTER || item->textalignment == ITEM_ALIGN_RIGHT)) + { + originalWidth += DC->ownerDrawWidth(item->window.ownerDraw, item->textscale); + } + else if (item->type == ITEM_TYPE_EDITFIELD && item->textalignment == ITEM_ALIGN_CENTER && item->cvar) + { + char buff[256]; + DC->getCVarString(item->cvar, buff, 256); + originalWidth += DC->textWidth(buff, item->textscale, item->font); + } + + *width = DC->textWidth(textPtr, item->textscale, item->font); + *height = DC->textHeight(textPtr, item->textscale, item->font); + + item->textRect.w = *width; + item->textRect.h = *height; + item->textRect.x = item->textalignx; + item->textRect.y = item->textaligny; + if (item->textalignment == ITEM_ALIGN_RIGHT) + { + item->textRect.x = item->textalignx - originalWidth; + } + else if (item->textalignment == ITEM_ALIGN_CENTER) + { + item->textRect.x = item->textalignx - originalWidth / 2; + } + + ToWindowCoords(&item->textRect.x, &item->textRect.y, &item->window); + if (item->text && item->text[0]=='@' )//string package + {//mark language + item->asset = se_language->modificationCount; + } + + } +} + +/* +================= +Item_TextColor +================= +*/ +void Item_TextColor(itemDef_t *item, vec4_t *newColor) +{ + vec4_t lowLight; + const vec4_t greyColor = { .5, .5, .5, 1}; + menuDef_t *parent = (menuDef_t*)item->parent; + + Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount); + + + if ( !(item->type == ITEM_TYPE_TEXT && item->window.flags & WINDOW_AUTOWRAPPED) && item->window.flags & WINDOW_HASFOCUS) + { + memcpy(newColor, &parent->focusColor, sizeof(vec4_t)); +/* + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; + LerpColor(parent->focusColor,lowLight,*newColor,0.5+0.5*sin((float)(DC->realTime / PULSE_DIVISOR))); +*/ + } +#ifdef _XBOX + else if (item->textStyle == ITEM_TEXTSTYLE_BLINK ) + { + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; + LerpColor(parent->focusColor,lowLight,*newColor,0.5+0.5*sin((float)(DC->realTime / PULSE_DIVISOR))); + } +#endif//_XBOX + else + { + memcpy(newColor, &item->window.foreColor, sizeof(vec4_t)); + } + + // items can be enabled and disabled based on cvars + if (item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest) + { + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) + { + memcpy(newColor, &parent->disableColor, sizeof(vec4_t)); + } + } + + if (item->window.flags & WINDOW_INACTIVE) + { + memcpy(newColor, &greyColor, sizeof(vec4_t)); + } +} + +/* +================= +Item_Text_Wrapped_Paint +================= +*/ +void Item_Text_Wrapped_Paint(itemDef_t *item) +{ + char text[1024]; + const char *p, *start, *textPtr; + char buff[1024]; + int width, height; + float x, y; + vec4_t color; + + // now paint the text and/or any optional images + // default to left + + if (item->text == NULL) + { + if (item->cvar == NULL) + { + return; + } + else + { + DC->getCVarString(item->cvar, text, sizeof(text)); + textPtr = text; + } + } + else + { + textPtr = item->text; + } + if (*textPtr == '@') // string reference + { + textPtr = SE_GetString( &textPtr[1] ); + } + + if (*textPtr == '\0') + { + return; + } + + Item_TextColor(item, &color); + Item_SetTextExtents(item, &width, &height, textPtr); + + x = item->textRect.x; + y = item->textRect.y; + start = textPtr; + p = strchr(textPtr, '\r'); + while (p && *p) + { + strncpy(buff, start, p-start+1); + buff[p-start] = '\0'; + DC->drawText(x, y, item->textscale, color, buff, 0, item->textStyle, item->font); + y += height + 5; + start += p - start + 1; + p = strchr(p+1, '\r'); + } + DC->drawText(x, y, item->textscale, color, start, 0, item->textStyle, item->font); +} + + +/* +================= +Menu_Paint +================= +*/ +void Item_Text_Paint(itemDef_t *item) +{ + char text[1024]; + const char *textPtr; + int height, width; + vec4_t color; + + if (item->window.flags & WINDOW_WRAPPED) + { + Item_Text_Wrapped_Paint(item); + return; + } + + if (item->window.flags & WINDOW_AUTOWRAPPED) + { + Item_Text_AutoWrapped_Paint(item); + return; + } + + if (item->text == NULL) + { + if (item->cvar == NULL) + { + return; + } + else + { + DC->getCVarString(item->cvar, text, sizeof(text)); + textPtr = text; + } + } + else + { + textPtr = item->text; + } + if (*textPtr == '@') // string reference + { + textPtr = SE_GetString( &textPtr[1] ); + } + + // this needs to go here as it sets extents for cvar types as well + Item_SetTextExtents(item, &width, &height, textPtr); + + if (*textPtr == '\0') + { + return; + } + + Item_TextColor(item, &color); + DC->drawText(item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, item->textStyle, item->font); + +/* + if (item->text2) // Is there a second line of text? + { + textPtr = item->text2; + if (*textPtr == '@') // string reference + { + textPtr = SE_GetString( &textPtr[1] ); + } + + Item_TextColor(item, &color); + DC->drawText(item->textRect.x + item->text2alignx, item->textRect.y + item->text2aligny, item->textscale, color, textPtr, 0, item->textStyle, item->font); + } +*/ +} + +/* +================= +Item_UpdatePosition +================= +*/ +// FIXME: consolidate this with nearby stuff +void Item_UpdatePosition(itemDef_t *item) +{ + float x, y; + menuDef_t *menu; + + if (item == NULL || item->parent == NULL) + { + return; + } + + menu = (menuDef_t *) item->parent; + + x = menu->window.rect.x; + y = menu->window.rect.y; + + if (menu->window.border != 0) + { + x += menu->window.borderSize; + y += menu->window.borderSize; + } + + Item_SetScreenCoords(item, x, y); + +} + +/* +================= +Item_TextField_Paint +================= +*/ +void Item_TextField_Paint(itemDef_t *item) +{ + char buff[1024]; + vec4_t newColor, lowLight; + int offset; + menuDef_t *parent = (menuDef_t*)item->parent; + editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + + Item_Text_Paint(item); + + buff[0] = '\0'; + + if (item->cvar) + { + DC->getCVarString(item->cvar, buff, sizeof(buff)); + } + + parent = (menuDef_t*)item->parent; + + if (item->window.flags & WINDOW_HASFOCUS) + { + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; + LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin((float)(DC->realTime / PULSE_DIVISOR))); + } + else + { + memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); + } + + offset = 8;//(item->text && *item->text) ? 8 : 0; + if (item->window.flags & WINDOW_HASFOCUS && g_editingField) + { + char cursor = DC->getOverstrikeMode() ? '_' : '|'; + DC->drawTextWithCursor(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, item->cursorPos - editPtr->paintOffset , cursor, /*editPtr->maxPaintChars*/ item->window.rect.w, item->textStyle, item->font); + } + else + { + DC->drawText(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, /*editPtr->maxPaintChars*/ item->window.rect.w, item->textStyle, item->font); + } +} + +void Item_TextScroll_Paint(itemDef_t *item) +{ + char cvartext[1024]; + float x, y, size, count; + int i; + textScrollDef_t *scrollPtr = (textScrollDef_t*)item->typeData; + + size = item->window.rect.h - 2; + + // Re-arranged this function. Previous version had a plethora of bugs. + // Still a little iffy - BTO (VV) + if (item->cvar) + { + DC->getCVarString(item->cvar, cvartext, sizeof(cvartext)); + item->text = cvartext; + } + + Item_TextScroll_BuildLines ( item ); + count = scrollPtr->iLineCount; + + // Just fix position - no scrolbar + scrollPtr->endPos = scrollPtr->startPos; + + // adjust size for item painting + size = item->window.rect.h - 2; + x = item->window.rect.x + item->textalignx + 1; + y = item->window.rect.y + item->textaligny + 1; + + + for (i = scrollPtr->startPos; i < count; i++) + { + const char *text; + + text = scrollPtr->pLines[i]; + if (!text) + { + continue; + } + + DC->drawText(x + 4, y, item->textscale, item->window.foreColor, text, 0, item->textStyle, item->font); + + size -= scrollPtr->lineHeight; + if (size < scrollPtr->lineHeight) + { + scrollPtr->drawPadding = scrollPtr->lineHeight - size; + break; + } + + scrollPtr->endPos++; + y += scrollPtr->lineHeight; + } +} + +/* +================= +Item_ListBox_Paint +================= +*/ +#define COLOR_MAX 255.0f + +void Item_ListBox_Paint(itemDef_t *item) +{ + float x, y, size; + int count, i, thumb; + qhandle_t image; + qhandle_t optionalImage; + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; +//JLF MPMOVED + int numlines; + int startPos; + int i2; + float sizeWidth, sizeHeight; + + // the listbox is horizontal or vertical and has a fixed size scroll bar going either direction + // elements are enumerated from the DC and either text or image handles are acquired from the DC as well + // textscale is used to size the text, textalignx and textaligny are used to size image elements + // there is no clipping available so only the last completely visible item is painted + count = DC->feederCount(item->special); + if (count ==0) + return; + +//JLFLISTBOX MPMOVED +#ifdef _XBOX + if (listPtr->notselectable) + listPtr->startPos = listPtr->cursorPos;//item->cursorPos; +// item->cursorPos = listPtr->startPos; +#endif +//JLFLISTBOX + + if (listPtr->startPos > (count?count-1:count)) + {//probably changed feeders, so reset + listPtr->startPos = 0; + } + + if (item->cursorPos > (count?count-1:count)) + {//probably changed feeders, so reset + item->cursorPos = 0; + } + // default is vertical if horizontal flag is not here + if (item->window.flags & WINDOW_HORIZONTAL) + { +//JLF new variable (code just indented) + if (!listPtr->scrollhidden) + { + // draw scrollbar in bottom of the window + // bar + if (Item_ListBox_MaxScroll(item) > 0) + { + x = item->window.rect.x + 1; + y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE - 1; + DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowLeft); + x += SCROLLBAR_SIZE - 1; + size = item->window.rect.w - (SCROLLBAR_SIZE * 2); + DC->drawHandlePic(x, y, size+1, SCROLLBAR_SIZE, DC->Assets.scrollBar); + x += size - 1; + DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowRight); + // thumb + thumb = Item_ListBox_ThumbDrawPosition(item);//Item_ListBox_ThumbPosition(item); + if (thumb > x - SCROLLBAR_SIZE - 1) + { + thumb = x - SCROLLBAR_SIZE - 1; + } + DC->drawHandlePic(thumb, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb); + } + else if (listPtr->startPos > 0) + { + listPtr->startPos = 0; + } + } +//JLF end + // + listPtr->endPos = listPtr->startPos; + size = item->window.rect.w - 2; + // items + // size contains max available space + if (listPtr->elementStyle == LISTBOX_IMAGE) + { + // fit = 0; + x = item->window.rect.x + 1; + y = item->window.rect.y + 1; + for (i = listPtr->startPos; i < count; i++) + { + // always draw at least one + // which may overdraw the box if it is too small for the element + image = DC->feederItemImage(item->special, i); + if (image) + { + if (item->window.flags & WINDOW_PLAYERCOLOR) + { + vec4_t color; + color[0] = ui_char_color_red.integer/255.0f; + color[1] = ui_char_color_green.integer/255.0f; + color[2] = ui_char_color_blue.integer/255.0f; + color[3] = 1; + ui.R_SetColor(color); + } + DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image); + } + + if (i == item->cursorPos) + { + DC->drawRect(x, y, listPtr->elementWidth-1, listPtr->elementHeight-1, item->window.borderSize, item->window.borderColor); + } + + size -= listPtr->elementWidth; + if (size < listPtr->elementWidth) + { + listPtr->drawPadding = size; //listPtr->elementWidth - size; + break; + } + x += listPtr->elementWidth; + listPtr->endPos++; + // fit++; + } + } + else + { + // + } + } + else //VERTICAL + { +//JLF MPMOVED + numlines = item->window.rect.h / listPtr->elementHeight; + if (listPtr->endPos +1 >= count) + { + //Cvar_Set("ui_downArrow","0"); + } + else + { + //Cvar_Set("ui_downArrow","1"); + } + if (listPtr->startPos <= 0) + { + //Cvar_Set("ui_upArrow","0"); + } + else + { + //Cvar_Set("ui_upArrow","1"); + } +//JLFEND +//JLF new variable (code idented with if) + if (!listPtr->scrollhidden) + { + // draw scrollbar to right side of the window + x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE - 1; + y = item->window.rect.y + 1; + DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowUp); + y += SCROLLBAR_SIZE - 1; + + listPtr->endPos = listPtr->startPos; + size = item->window.rect.h - (SCROLLBAR_SIZE * 2); + DC->drawHandlePic(x, y, SCROLLBAR_SIZE, size+1, DC->Assets.scrollBar); + y += size - 1; + DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowDown); + // thumb + thumb = Item_ListBox_ThumbDrawPosition(item);//Item_ListBox_ThumbPosition(item); + if (thumb > y - SCROLLBAR_SIZE - 1) + { + thumb = y - SCROLLBAR_SIZE - 1; + } + DC->drawHandlePic(x, thumb, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb); + } + listPtr->endPos = listPtr->startPos; +//JLF end + // adjust size for item painting + size = item->window.rect.h - 2; + + sizeWidth = item->window.rect.w - 2; + sizeHeight = item->window.rect.h - 2; + + if (listPtr->elementStyle == LISTBOX_IMAGE) + { + // Multiple rows and columns (since it's more than twice as wide as an element) + if ( item->window.rect.w > (listPtr->elementWidth*2) ) + { + startPos = listPtr->startPos; + x = item->window.rect.x + 1; + y = item->window.rect.y + 1; + // Next row + for (i2 = startPos; i2 < count; i2++) + { + x = item->window.rect.x + 1; + sizeWidth = item->window.rect.w - 2; + // print a row + for (i = startPos; i < count; i++) + { + // always draw at least one + // which may overdraw the box if it is too small for the element + image = DC->feederItemImage(item->special, i); + if (image) + { + #ifndef CGAME + if (item->window.flags & WINDOW_PLAYERCOLOR) + { + vec4_t color; + + color[0] = ui_char_color_red.integer/COLOR_MAX; + color[1] = ui_char_color_green.integer/COLOR_MAX; + color[2] = ui_char_color_blue.integer/COLOR_MAX; + color[3] = 1.0f; + DC->setColor(color); + } + #endif + DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image); + } + + if (i == item->cursorPos) + { + DC->drawRect(x, y, listPtr->elementWidth-1, listPtr->elementHeight-1, item->window.borderSize, item->window.borderColor); + } + + sizeWidth -= listPtr->elementWidth; + if (sizeWidth < listPtr->elementWidth) + { + listPtr->drawPadding = sizeWidth; //listPtr->elementWidth - size; + break; + } + x += listPtr->elementWidth; + listPtr->endPos++; + } + + sizeHeight -= listPtr->elementHeight; + if (sizeHeight < listPtr->elementHeight) + { + listPtr->drawPadding = sizeHeight; //listPtr->elementWidth - size; + break; + } + listPtr->endPos++; + startPos = listPtr->endPos; + y += listPtr->elementHeight; + + } + } + // single column + + else + { + // fit = 0; + x = item->window.rect.x + 1; + y = item->window.rect.y + 1; + + for (i = listPtr->startPos; i < count; i++) + { + // always draw at least one + // which may overdraw the box if it is too small for the element + image = DC->feederItemImage(item->special, i); + if (image) + { + DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image); + } + + if (i == item->cursorPos) + { + DC->drawRect(x, y, listPtr->elementWidth - 1, listPtr->elementHeight - 1, item->window.borderSize, item->window.borderColor); + } + + listPtr->endPos++; + size -= listPtr->elementHeight; + if (size < listPtr->elementHeight) + { + listPtr->drawPadding = listPtr->elementHeight - size; + break; + } + y += listPtr->elementHeight; + // fit++; + } + } + } + else + { + x = item->window.rect.x + 1; + y = item->window.rect.y + 1 - listPtr->elementHeight; +//JLF MPMOVED +#ifdef _XBOX + if ( listPtr->notselectable) + i = listPtr->startPos - (numlines/2); + else + i = listPtr->startPos; +#else + i = listPtr->startPos; +#endif + int enddisplaynum = i + numlines; + if (enddisplaynum > count) + enddisplaynum = count; + for (; icursorPos && listPtr->selectionShader) + { + int barWidth = item->window.rect.w - 4 - (listPtr->scrollhidden ? 0 : SCROLLBAR_SIZE); + // Crazy math to match the text offset below. Ugh. + int yOff = DC->textHeight("", item->textscale, item->font); + yOff = (-yOff) / 2 + (listPtr->elementHeight / 2); + DC->drawHandlePic(x + 2, y + listPtr->elementHeight + 2 + yOff, barWidth, listPtr->elementHeight, listPtr->selectionShader); + } +#endif + + const char *text; + // always draw at least one + // which may overdraw the box if it is too small for the element + if ((!listPtr->notselectable) || i>=0) + { + if (listPtr->numColumns > 0) + { + int j; + for (j = 0; j < listPtr->numColumns; j++) + { + text = DC->feederItemText(item->special, i, j, &optionalImage); + if (text[0]=='@') + { + text = SE_GetString( &text[1] ); + } + + if (optionalImage >= 0) + { + DC->drawHandlePic(x + 4 + listPtr->columnInfo[j].pos, y - 1 + listPtr->elementHeight / 2, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage); + } + else if (text) + { + vec4_t *color; + vec4_t newColor; + menuDef_t *parent = (menuDef_t*)item->parent; + + // Use focus color is it has focus. + + + if (i == item->cursorPos) + { + if (item->window.flags & WINDOW_HASFOCUS) + { + Item_TextColor(item,&newColor); + color = &newColor; + } + else + color = &parent->focusColor; + } + else + { + color = &item->window.foreColor; + } + + + int textyOffset = 0; + int textxOffset = 0; + + //JLF MPMOVED + #ifdef _XBOX + float fScaleA = item->textscale; + textyOffset = DC->textHeight (text, fScaleA, item->font); + textyOffset *= -1; + textyOffset /=2; + textyOffset += listPtr->elementHeight/2; + + // First column, always left justified: + if( j == 0 ) + { + textxOffset = 0; + } + else if( (j > 0) && (j < listPtr->numColumns - 1) ) + { // Middle columns in those with three or more - centered + // Half the column width minus half the text width -> centered + textxOffset = (listPtr->columnInfo[j].width / 2) - + (DC->textWidth (text, fScaleA, item->font) / 2); + textxOffset -= 4; // Cancel out the +4 from below + } + else if( j == listPtr->numColumns - 1 ) + { // Right most column, right justified + // Colum width, minus text width -> right aligned + textxOffset = listPtr->columnInfo[j].width - + DC->textWidth (text, fScaleA, item->font); + textxOffset -= 8; // Go 4 pixels from the other border (see below) + } + #endif + + + DC->drawText(x + 4 + listPtr->columnInfo[j].pos + textxOffset, y + listPtr->elementHeight+ textyOffset, item->textscale, *color, text, listPtr->columnInfo[j].maxChars, item->textStyle, item->font); + } + } + } + else + { + //JLF MPMOVED + #ifdef _XBOX + if (i >= 0) + { + #endif + text = DC->feederItemText(item->special, i, 0, &optionalImage); + + int textyOffset = 0; + float fScaleA = item->textscale; + textyOffset = DC->textHeight (text, fScaleA, item->font); + textyOffset *= -1; + textyOffset /=2; + textyOffset += listPtr->elementHeight/2; + + + if (optionalImage >= 0) + { + //DC->drawHandlePic(x + 4 + listPtr->elementHeight, y, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage); + } + else if (text) + { + DC->drawText(x + 4, y + listPtr->elementHeight + textyOffset, item->textscale, item->window.foreColor, text, 0, item->textStyle, item->font); + } + //JLF MPMOVED + #ifdef _XBOX + } + #endif + } + } + + // The chosen text +#ifndef _XBOX + if (i == item->cursorPos) + { + DC->fillRect(x + 2, y + listPtr->elementHeight + 2, item->window.rect.w - SCROLLBAR_SIZE - 4, listPtr->elementHeight+2, item->window.outlineColor); + } +#endif + + size -= listPtr->elementHeight; + if (size < listPtr->elementHeight) + { + listPtr->drawPadding = listPtr->elementHeight - size; + break; + } + listPtr->endPos++; + y += listPtr->elementHeight; + // fit++; + } + } + } +} + +char g_nameBind1[32]; +char g_nameBind2[32]; + +typedef struct +{ + char* name; + float defaultvalue; + float value; +} configcvar_t; + + + +/* +================= +BindingFromName +================= +*/ +void BindingFromName(const char *cvar) +{ + int i, b1, b2; + + // iterate each command, set its default binding + for (i=0; i < g_bindCount; i++) + { + if (Q_stricmp(cvar, g_bindings[i].command) == 0) { + b1 = g_bindings[i].bind1; + if (b1 == -1) + { + break; + } + DC->keynumToStringBuf( b1, g_nameBind1, sizeof(g_nameBind1) ); +// do NOT do this or it corrupts asian text!!! Q_strupr(g_nameBind1); + + b2 = g_bindings[i].bind2; + if (b2 != -1) + { + DC->keynumToStringBuf( b2, g_nameBind2, sizeof(g_nameBind2) ); +// do NOT do this or it corrupts asian text!!!// Q_strupr(g_nameBind2); + + strcat( g_nameBind1, va(" %s ",SE_GetString("MENUS_KEYBIND_OR" )) ); + strcat( g_nameBind1, g_nameBind2 ); + } + return; + } + } + + strcpy(g_nameBind1, "???"); +} + +/* +================= +Item_Bind_Paint +================= +*/ +void Item_Bind_Paint(itemDef_t *item) +{ + vec4_t newColor, lowLight; + float value,textScale,textWidth; + int maxChars = 0, textHeight,yAdj,startingXPos; + + menuDef_t *parent = (menuDef_t*)item->parent; + editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + + if (editPtr) + { + maxChars = editPtr->maxPaintChars; + } + + value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + + if (item->window.flags & WINDOW_HASFOCUS) + { + if (g_bindItem == item) + { + lowLight[0] = 0.8f * 1.0f; + lowLight[1] = 0.8f * 0.0f; + lowLight[2] = 0.8f * 0.0f; + lowLight[3] = 0.8f * 1.0f; + } + else + { + lowLight[0] = 0.8f * parent->focusColor[0]; + lowLight[1] = 0.8f * parent->focusColor[1]; + lowLight[2] = 0.8f * parent->focusColor[2]; + lowLight[3] = 0.8f * parent->focusColor[3]; + } + LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin((float)(DC->realTime / PULSE_DIVISOR))); + } + else + { + Item_TextColor( item,&newColor); + } + + if (item->text) + { + Item_Text_Paint(item); + BindingFromName(item->cvar); + + // If the text runs past the limit bring the scale down until it fits. + textScale = item->textscale; + textWidth = DC->textWidth(g_nameBind1,(float) textScale, uiInfo.uiDC.Assets.qhMediumFont); + + startingXPos = (item->textRect.x + item->textRect.w + 8); + + while ((startingXPos + textWidth) >= SCREEN_WIDTH) + { + textScale -= .05f; + textWidth = DC->textWidth(g_nameBind1,(float) textScale, uiInfo.uiDC.Assets.qhMediumFont); + } + + // Try to adjust it's y placement if the scale has changed. + yAdj = 0; + if (textScale != item->textscale) + { + textHeight = DC->textHeight(g_nameBind1, item->textscale, uiInfo.uiDC.Assets.qhMediumFont); + yAdj = textHeight - DC->textHeight(g_nameBind1, textScale, uiInfo.uiDC.Assets.qhMediumFont); + } + + DC->drawText(startingXPos, item->textRect.y + yAdj, textScale, newColor, g_nameBind1, maxChars/*item->textRect.w*/, item->textStyle, item->font); + } + else + { + DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "FIXME 1" : "FIXME 0", maxChars/*item->textRect.w*/, item->textStyle, item->font); + } +} + +void UI_ScaleModelAxis(refEntity_t *ent) + +{ // scale the model should we need to + if (ent->modelScale[0] && ent->modelScale[0] != 1.0f) + { + VectorScale( ent->axis[0], ent->modelScale[0] , ent->axis[0] ); + ent->nonNormalizedAxes = qtrue; + } + if (ent->modelScale[1] && ent->modelScale[1] != 1.0f) + { + VectorScale( ent->axis[1], ent->modelScale[1] , ent->axis[1] ); + ent->nonNormalizedAxes = qtrue; + } + if (ent->modelScale[2] && ent->modelScale[2] != 1.0f) + { + VectorScale( ent->axis[2], ent->modelScale[2] , ent->axis[2] ); + ent->nonNormalizedAxes = qtrue; + } +} + +/* +================= +Item_Model_Paint +================= +*/ +#ifdef _XBOX +extern int *s_entityWavVol; +#else +extern int s_entityWavVol[MAX_GENTITIES]; //from snd_dma.cpp +#endif +void UI_TalkingHead(itemDef_t *item) +{ +// static int facial_blink = DC->realTime + Q_flrand(4000.0, 8000.0); + static int facial_timer = DC->realTime + Q_flrand(10000.0, 30000.0); +// static animNumber_t facial_anim = FACE_ALERT; + int anim = -1; + + //are we blinking? +/* if (facial_blink < 0) + { // yes, check if we are we done blinking ? + if (-(facial_blink) < DC->realTime) + { // yes, so reset blink timer + facial_blink = DC->realTime + Q_flrand(4000.0, 8000.0); + CG_G2SetHeadBlink( cent, qfalse ); //stop the blink + } + } + else // no we aren't blinking + { + if (facial_blink < DC->realTime)// but should we start ? + { + CG_G2SetHeadBlink( cent, qtrue ); + if (facial_blink == 1) + {//requested to stay shut by SET_FACEEYESCLOSED + facial_blink = -(DC->realTime + 99999999.0f);// set blink timer + } + else + { + facial_blink = -(DC->realTime + 300.0f);// set blink timer + } + } + } +*/ + + if (s_entityWavVol[0] > 0) // if we aren't talking, then it will be 0, -1 for talking but paused + { + anim = FACE_TALK1 + s_entityWavVol[0]-1; + if( anim > FACE_TALK4 ) + { + anim = FACE_TALK4; + } + // reset timers so we don't start right away after talking + facial_timer = DC->realTime + Q_flrand(2000.0, 7000.0); + } + else if (s_entityWavVol[0] == -1) + {// talking, but silent + anim = FACE_TALK0; + // reset timers so we don't start right away after talking + facial_timer = DC->realTime + Q_flrand(2000.0, 7000.0); + } +/* else if (s_entityWavVol[0] == 0) //don't anim if in a slient part of speech + {//not talking + if (facial_timer < 0) // are animating ? + { //yes + if (-(facial_timer) < DC->realTime)// are we done animating ? + { // yes, reset timer + facial_timer = DC->realTime + Q_flrand(10000.0, 30000.0); + } + else + { // not yet, so choose anim + anim = facial_anim; + } + } + else // no we aren't animating + { // but should we start ? + if (facial_timer < DC->realTime) + {//yes + facial_anim = FACE_ALERT + Q_irand(0,2); //alert, smile, frown + // set aux timer + facial_timer = -(DC->realTime + 2000.0); + anim = facial_anim; + } + } + }//talking +*/ + if (facial_timer < DC->realTime) + {//restart the base anim +// modelDef_t *modelPtr = (modelDef_t*)item->typeData; + //ItemParse_model_g2anim_go( item, "BOTH_STAND5IDLE1" ); +// facial_timer = DC->realTime + Q_flrand(2000.0, 7000.0) + DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + } + if (anim != -1) + { + DC->g2hilev_SetAnim(&item->ghoul2[0], "face", anim, qfalse); + } +} + +/* +================= +Item_Model_Paint +================= +*/ +extern void UI_SaberDrawBlades( itemDef_t *item, vec3_t origin, float curYaw ); + +void Item_Model_Paint(itemDef_t *item) +{ + float x, y, w, h; + refdef_t refdef; + refEntity_t ent; + vec3_t mins, maxs, origin; + vec3_t angles; + const modelDef_t *modelPtr = (modelDef_t*)item->typeData; + + if (modelPtr == NULL) + { + return; + } + + // a moves datapad anim is playing + if (uiInfo.moveAnimTime && (uiInfo.moveAnimTime < uiInfo.uiDC.realTime)) + { + modelDef_t *modelPtr; + modelPtr = (modelDef_t*)item->typeData; + if (modelPtr) + { + //HACKHACKHACK: check for any multi-part anim sequences, and play the next anim, if needbe + switch( modelPtr->g2anim ) + { + case BOTH_FORCEWALLREBOUND_FORWARD: + case BOTH_FORCEJUMP1: + ItemParse_model_g2anim_go( item, animTable[BOTH_FORCEINAIR1].name ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + if ( !uiInfo.moveAnimTime ) + { + uiInfo.moveAnimTime = 500; + } + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_FORCEINAIR1: + ItemParse_model_g2anim_go( item, animTable[BOTH_FORCELAND1].name ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_FORCEWALLRUNFLIP_START: + ItemParse_model_g2anim_go( item, animTable[BOTH_FORCEWALLRUNFLIP_END].name ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_FORCELONGLEAP_START: + ItemParse_model_g2anim_go( item, animTable[BOTH_FORCELONGLEAP_LAND].name ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_KNOCKDOWN3://on front - into force getup + DC->startLocalSound(uiInfo.uiDC.Assets.datapadmoveJumpSound, CHAN_LOCAL ); + ItemParse_model_g2anim_go( item, animTable[BOTH_FORCE_GETUP_F1].name ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_KNOCKDOWN2://on back - kick forward getup + DC->startLocalSound(uiInfo.uiDC.Assets.datapadmoveJumpSound, CHAN_LOCAL ); + ItemParse_model_g2anim_go( item, animTable[BOTH_GETUP_BROLL_F].name ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_KNOCKDOWN1://on back - roll-away + DC->startLocalSound(uiInfo.uiDC.Assets.datapadmoveRollSound, CHAN_LOCAL ); + ItemParse_model_g2anim_go( item, animTable[BOTH_GETUP_BROLL_R].name ); + uiInfo.moveAnimTime = DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + default: + ItemParse_model_g2anim_go( item, uiInfo.movesBaseAnim ); + DC->g2hilev_SetAnim(&item->ghoul2[0], "model_root", modelPtr->g2anim, qtrue); + uiInfo.moveAnimTime = 0; + break; + } + } + } + + // setup the refdef + memset( &refdef, 0, sizeof( refdef ) ); + refdef.rdflags = RDF_NOWORLDMODEL; + AxisClear( refdef.viewaxis ); + x = item->window.rect.x+1; + y = item->window.rect.y+1; + w = item->window.rect.w-2; + h = item->window.rect.h-2; + + refdef.x = x * DC->xscale; + refdef.y = y * DC->yscale; + refdef.width = w * DC->xscale; + refdef.height = h * DC->yscale; + + if (item->flags&ITF_G2VALID) + { //ghoul2 models don't have bounds, so we have to parse them. + VectorCopy(modelPtr->g2mins, mins); + VectorCopy(modelPtr->g2maxs, maxs); + + if (!mins[0] && !mins[1] && !mins[2] && + !maxs[0] && !maxs[1] && !maxs[2]) + { //we'll use defaults then I suppose. + VectorSet(mins, -16, -16, -24); + VectorSet(maxs, 16, 16, 32); + } + } + else + { + DC->modelBounds( item->asset, mins, maxs ); + } + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + refdef.fov_x = (modelPtr->fov_x) ? modelPtr->fov_x : (int)((float)refdef.width / 640.0f * 90.0f); + refdef.fov_y = (modelPtr->fov_y) ? modelPtr->fov_y : atan2( refdef.height, refdef.width / tan( refdef.fov_x / 360 * M_PI ) ) * ( 360 / M_PI ); + +// refdef.fov_x = (modelPtr->fov_x) ? modelPtr->fov_x : refdef.width; +// refdef.fov_y = (modelPtr->fov_y) ? modelPtr->fov_y : refdef.height; + + // calculate distance so the model nearly fills the box + float len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; + + DC->clearScene(); + + refdef.time = DC->realTime; + + // add the model + + memset( &ent, 0, sizeof(ent) ); + + // use item storage to track + float curYaw = modelPtr->angle; + if (modelPtr->rotationSpeed) + { + curYaw += (float)refdef.time/modelPtr->rotationSpeed; + } +// if ( item->flags&ITF_ISANYSABER && !(item->flags&ITF_ISCHARACTER) ) +// {//hack to put saber on it's side +// VectorSet( angles, curYaw, 0, 90 ); +// } +// else + if (item->flags&ITF_G2VALID) + { + VectorSet( angles, 0, curYaw, 0 ); + } + else + { + // Hack to put the spinning saber logo thing on its side + VectorSet( angles, curYaw, 0, 90 ); + } + + + AnglesToAxis( angles, ent.axis ); + + if (item->flags&ITF_G2VALID) + { + ent.ghoul2 = &item->ghoul2; + ent.radius = 1000; + ent.customSkin = modelPtr->g2skin; + + if ( (item->flags&ITF_ISCHARACTER) ) + { + ent.shaderRGBA[0] = ui_char_color_red.integer; + ent.shaderRGBA[1] = ui_char_color_green.integer; + ent.shaderRGBA[2] = ui_char_color_blue.integer; + ent.shaderRGBA[3] = 255; + UI_TalkingHead(item); + } + if ( item->flags&ITF_ISANYSABER ) + {//UGH, draw the saber blade! + UI_SaberDrawBlades( item, origin, curYaw ); + } + } + else + { + ent.hModel = item->asset; + } + VectorCopy( origin, ent.origin ); + VectorCopy( ent.origin, ent.oldorigin ); + + // Set up lighting + //VectorCopy( refdef.vieworg, ent.lightingOrigin ); + //ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; + ent.renderfx = RF_NOSHADOW ; +#ifndef _XBOX + ui.R_AddLightToScene(refdef.vieworg, 500, 1, 1, 1); //fixme: specify in menu file! +#endif + + float oldCull = tr.distanceCull; + tr.distanceCull = 12000; + + DC->addRefEntityToScene( &ent ); + DC->renderScene( &refdef ); + + tr.distanceCull = oldCull; +} + +/* +================= +Item_OwnerDraw_Paint +================= +*/ +void Item_OwnerDraw_Paint(itemDef_t *item) +{ + menuDef_t *parent; + + if (item == NULL) + { + return; + } + + parent = (menuDef_t*)item->parent; + + if (DC->ownerDrawItem) + { + vec4_t color, lowLight; + menuDef_t *parent = (menuDef_t*)item->parent; + Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount); + memcpy(&color, &item->window.foreColor, sizeof(color)); +/* + if (item->numColors > 0 && DC->getValue) + { + // if the value is within one of the ranges then set color to that, otherwise leave at default + int i; + float f = DC->getValue(item->window.ownerDraw); + for (i = 0; i < item->numColors; i++) + { + if (f >= item->colorRanges[i].low && f <= item->colorRanges[i].high) + { + memcpy(&color, &item->colorRanges[i].color, sizeof(color)); + break; + } + } + } +*/ + + if (item->window.flags & WINDOW_HASFOCUS) + { + memcpy(color, &parent->focusColor, sizeof(vec4_t)); +/* + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; + LerpColor(parent->focusColor,lowLight,color,0.5+0.5*sin((float)(DC->realTime / PULSE_DIVISOR))); +*/ + } + else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) + { + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; + LerpColor(item->window.foreColor,lowLight,color,0.5+0.5*sin((float)(DC->realTime / PULSE_DIVISOR))); + } + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) + { + memcpy(color, parent->disableColor, sizeof(vec4_t)); + } + + if (item->text) + { + Item_Text_Paint(item); + + // +8 is an offset kludge to properly align owner draw items that have text combined with them + DC->ownerDrawItem(item->textRect.x + item->textRect.w + 8, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle, item->font ); + } + else + { + DC->ownerDrawItem(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, item->textalignx, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle, item->font ); + } + } +} + +void Item_YesNo_Paint(itemDef_t *item) +{ + vec4_t newColor; + float value; + menuDef_t *parent = (menuDef_t*)item->parent; + + value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + + if (item->window.flags & WINDOW_HASFOCUS) + { + memcpy(&newColor, &parent->focusColor, sizeof(vec4_t)); + } + else + { + memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); + } + + const char *psYes = SE_GetString( "MENUS_YES" ); + const char *psNo = SE_GetString( "MENUS_NO" ); + const char *yesnovalue; + +// if (item->invertYesNo) +// yesnovalue = (value == 0) ? psYes : psNo; +// else + yesnovalue = (value != 0) ? psYes : psNo; + + if (item->text) + { + Item_Text_Paint(item); +//JLF +#ifdef _XBOX + if (item->xoffset == 0) + DC->drawText(item->textRect.x + item->textRect.w + item->xoffset + 8, item->textRect.y, item->textscale, newColor, yesnovalue, 0, item->textStyle, item->font); + else +#endif + DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, yesnovalue, 0, item->textStyle, item->font); + + } + else + { +//JLF +#ifdef _XBOX + DC->drawText(item->textRect.x + item->xoffset, item->textRect.y, item->textscale, newColor, yesnovalue , 0, item->textStyle, item->font); +#else + DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, yesnovalue , 0, item->textStyle, item->font); +#endif + } + +} + +/* +================= +Item_Multi_Paint +================= +*/ +void Item_Multi_Paint(itemDef_t *item) +{ + vec4_t newColor; + const char *text = ""; + menuDef_t *parent = (menuDef_t*)item->parent; + + if (item->window.flags & WINDOW_HASFOCUS) + { + memcpy(&newColor, &parent->focusColor, sizeof(vec4_t)); + } + else + { + memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); + } + + text = Item_Multi_Setting(item); + if (*text == '@') // string reference + { + text = SE_GetString( &text[1] ); + } + + // How big is the string: + int textWidth = DC->textWidth( text, item->textscale, item->font ); + + if (item->text) + { + // Draw the item's label: + Item_Text_Paint(item); + + int x = item->textRect.x; // Start at left edge of rectangle + x += item->xoffset; // Add xoffset + x -= textWidth; // Minus width (to get right-justified) + x -= 20; // Leave 16 pixels (and slack) for arrow + + // Draw the text: + DC->drawText( x, item->textRect.y, item->textscale, newColor, text, 0, item->textStyle, item->font ); + + // If this item has focus, draw the change arrows two pixels out on either end: + if (item->window.flags & WINDOW_HASFOCUS) + { + qhandle_t arrowShader = ui.R_RegisterShaderNoMip( "gfx/menus/newFront/left_arrow" ); + ui.R_SetColor( NULL ); + int textHeight = DC->textHeight( "", item->textscale, item->font ); + ui.R_DrawStretchPic( x - 20, item->textRect.y + (textHeight/2) - 3, 16, 16, 0, 0, 1, 1, arrowShader ); + ui.R_DrawStretchPic( x + textWidth + 4, item->textRect.y + (textHeight/2) - 3, 16, 16, 1, 1, 0, 0, arrowShader ); + } + } + else + { + int x = item->window.rect.x; // Start at left edge of window + x += item->xoffset; // Add xoffset + + if( item->textalignment == ITEM_ALIGN_RIGHT ) + { + x -= 20; // Leave 16 pixels (and slack) for arrow + x -= textWidth; // Minus width (for right justified) + } + else if( item->textalignment == ITEM_ALIGN_CENTER ) + { + x -= textWidth / 2; // Minus half-width (for centered) + } + + // Draw the text: + DC->drawText( x, item->window.rect.y, item->textscale, newColor, text, 0, item->textStyle, item->font ); + + // If this item has focus, draw the change arrows two pixels out on either end: + if (item->window.flags & WINDOW_HASFOCUS) + { + qhandle_t arrowShader = ui.R_RegisterShaderNoMip( "gfx/menus/newFront/left_arrow" ); + ui.R_SetColor( NULL ); + int textHeight = DC->textHeight( "", item->textscale, item->font ); + ui.R_DrawStretchPic( x - 20, item->window.rect.y + (textHeight/2) - 3, 16, 16, 0, 0, 1, 1, arrowShader ); + ui.R_DrawStretchPic( x + textWidth + 4, item->window.rect.y + (textHeight/2) - 3, 16, 16, 1, 1, 0, 0, arrowShader ); + } + } +} + +int Item_TextScroll_MaxScroll ( itemDef_t *item ) +{ + textScrollDef_t *scrollPtr = (textScrollDef_t*)item->typeData; + + int count = scrollPtr->iLineCount; + int max = count - (int)(item->window.rect.h / scrollPtr->lineHeight) + 1; + + if (max < 0) + { + return 0; + } + + return max; +} + +int Item_TextScroll_ThumbPosition ( itemDef_t *item ) +{ + float max, pos, size; + textScrollDef_t *scrollPtr = (textScrollDef_t*)item->typeData; + + max = Item_TextScroll_MaxScroll ( item ); + size = item->window.rect.h - (SCROLLBAR_SIZE * 2) - 2; + + if (max > 0) + { + pos = (size-SCROLLBAR_SIZE) / (float) max; + } + else + { + pos = 0; + } + + pos *= scrollPtr->startPos; + + return item->window.rect.y + 1 + SCROLLBAR_SIZE + pos; +} + +int Item_TextScroll_ThumbDrawPosition ( itemDef_t *item ) +{ + int min, max; + + if (itemCapture == item) + { + min = item->window.rect.y + SCROLLBAR_SIZE + 1; + max = item->window.rect.y + item->window.rect.h - 2*SCROLLBAR_SIZE - 1; + + if (DC->cursory >= min + SCROLLBAR_SIZE/2 && DC->cursory <= max + SCROLLBAR_SIZE/2) + { + return DC->cursory - SCROLLBAR_SIZE/2; + } + + return Item_TextScroll_ThumbPosition(item); + } + + return Item_TextScroll_ThumbPosition(item); +} + +int Item_TextScroll_OverLB ( itemDef_t *item, float x, float y ) +{ + rectDef_t r; + textScrollDef_t *scrollPtr; + int thumbstart; + int count; + + scrollPtr = (textScrollDef_t*)item->typeData; + count = scrollPtr->iLineCount; + + // Scroll bar isn't drawing so ignore this input + if ((( scrollPtr->iLineCount * scrollPtr->lineHeight ) <= (item->window.rect.h - 2)) && (item->type == ITEM_TYPE_TEXTSCROLL)) + { + return 0; + } + + r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; + r.y = item->window.rect.y; + r.h = r.w = SCROLLBAR_SIZE; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_LEFTARROW; + } + + r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_RIGHTARROW; + } + + thumbstart = Item_TextScroll_ThumbPosition(item); + r.y = thumbstart; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_THUMB; + } + + r.y = item->window.rect.y + SCROLLBAR_SIZE; + r.h = thumbstart - r.y; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_PGUP; + } + + r.y = thumbstart + SCROLLBAR_SIZE; + r.h = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_PGDN; + } + + return 0; +} + +void Item_TextScroll_MouseEnter (itemDef_t *item, float x, float y) +{ + item->window.flags &= ~(WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN); + item->window.flags |= Item_TextScroll_OverLB(item, x, y); +} + +/* +================= +Item_Slider_ThumbPosition +================= +*/ +int Item_ListBox_ThumbDrawPosition(itemDef_t *item) +{ + int min, max; + + if (itemCapture == item) + { + if (item->window.flags & WINDOW_HORIZONTAL) + { + min = item->window.rect.x + SCROLLBAR_SIZE + 1; + max = item->window.rect.x + item->window.rect.w - 2*SCROLLBAR_SIZE - 1; + if (DC->cursorx >= min + SCROLLBAR_SIZE/2 && DC->cursorx <= max + SCROLLBAR_SIZE/2) + { + return DC->cursorx - SCROLLBAR_SIZE/2; + } + else + { + return Item_ListBox_ThumbPosition(item); + } + } + else + { + min = item->window.rect.y + SCROLLBAR_SIZE + 1; + max = item->window.rect.y + item->window.rect.h - 2*SCROLLBAR_SIZE - 1; + if (DC->cursory >= min + SCROLLBAR_SIZE/2 && DC->cursory <= max + SCROLLBAR_SIZE/2) + { + return DC->cursory - SCROLLBAR_SIZE/2; + } + else + { + return Item_ListBox_ThumbPosition(item); + } + } + } + else + { + return Item_ListBox_ThumbPosition(item); + } +} + +/* +================= +Item_Slider_ThumbPosition +================= +*/ +float Item_Slider_ThumbPosition(itemDef_t *item) +{ + float value, range; + editFieldDef_t *editDef = (editFieldDef_t *) item->typeData; + + if (!editDef || !item->cvar) + return 0.0f; + + value = DC->getCVarValue(item->cvar); + + if (value < editDef->minVal) { + value = editDef->minVal; + } else if (value > editDef->maxVal) { + value = editDef->maxVal; + } + + range = editDef->maxVal - editDef->minVal; + value -= editDef->minVal; + return value / range; +} + + +/* +================= +Item_Slider_Paint +================= +*/ +void Item_Slider_Paint(itemDef_t *item) +{ + float x, y, value; + + if (item->text) { + Item_Text_Paint(item); + } + + // Offset is to right of the (full) slider: + x = (item->window.rect.x + item->xoffset) - (SLIDER_WIDTH); + // And if we drew text, we need to shift vertically to be centered as well: + y = item->window.rect.y; + if (item->text) + y += (DC->textHeight("", item->textscale, item->font) / 2) - 2; + + value = Item_Slider_ThumbPosition(item); + + ui.R_SetColor( NULL ); + ui.R_DrawStretchPic( x, y, SLIDER_WIDTH * value, SLIDER_HEIGHT, 0.0f, 0.0f, value, 1.0f, DC->Assets.sliderBar ); +} + +/* +================= +Item_Paint +================= +*/ +static qboolean Item_Paint(itemDef_t *item, qboolean bDraw) +{ + int xPos,textWidth; + vec4_t red; + menuDef_t *parent = (menuDef_t*)item->parent; + red[0] = red[3] = 1; + red[1] = red[2] = 0; + + if (item == NULL) + { + return qfalse; + } + + if (item->window.flags & WINDOW_SCRIPTWAITING) + { + if (DC->realTime > item->window.delayTime) + { // Time has elapsed, resume running whatever script we saved + item->window.flags &= ~WINDOW_SCRIPTWAITING; + Item_RunScript(item, item->window.delayedScript); + } + } + + if (item->window.flags & WINDOW_ORBITING) + { + if (DC->realTime > item->window.nextTime) + { + float rx, ry, a, c, s, w, h; + item->window.nextTime = DC->realTime + item->window.offsetTime; + // translate + w = item->window.rectClient.w / 2; + h = item->window.rectClient.h / 2; + rx = item->window.rectClient.x + w - item->window.rectEffects.x; + ry = item->window.rectClient.y + h - item->window.rectEffects.y; + a = (float) (3 * M_PI / 180); + c = cos(a); + s = sin(a); + item->window.rectClient.x = (rx * c - ry * s) + item->window.rectEffects.x - w; + item->window.rectClient.y = (rx * s + ry * c) + item->window.rectEffects.y - h; + Item_UpdatePosition(item); + + } + } + + + if (item->window.flags & WINDOW_INTRANSITION) + { + if (DC->realTime > item->window.nextTime) + { + int done = 0; + item->window.nextTime = DC->realTime + item->window.offsetTime; + + // transition the x,y + if (item->window.rectClient.x == item->window.rectEffects.x) + { + done++; + } + else + { + if (item->window.rectClient.x < item->window.rectEffects.x) + { + item->window.rectClient.x += item->window.rectEffects2.x; + if (item->window.rectClient.x > item->window.rectEffects.x) + { + item->window.rectClient.x = item->window.rectEffects.x; + done++; + } + } + else + { + item->window.rectClient.x -= item->window.rectEffects2.x; + if (item->window.rectClient.x < item->window.rectEffects.x) + { + item->window.rectClient.x = item->window.rectEffects.x; + done++; + } + } + } + + if (item->window.rectClient.y == item->window.rectEffects.y) + { + done++; + } + else + { + if (item->window.rectClient.y < item->window.rectEffects.y) + { + item->window.rectClient.y += item->window.rectEffects2.y; + if (item->window.rectClient.y > item->window.rectEffects.y) + { + item->window.rectClient.y = item->window.rectEffects.y; + done++; + } + } + else + { + item->window.rectClient.y -= item->window.rectEffects2.y; + if (item->window.rectClient.y < item->window.rectEffects.y) + { + item->window.rectClient.y = item->window.rectEffects.y; + done++; + } + } + } + + if (item->window.rectClient.w == item->window.rectEffects.w) + { + done++; + } + else + { + if (item->window.rectClient.w < item->window.rectEffects.w) + { + item->window.rectClient.w += item->window.rectEffects2.w; + if (item->window.rectClient.w > item->window.rectEffects.w) + { + item->window.rectClient.w = item->window.rectEffects.w; + done++; + } + } + else + { + item->window.rectClient.w -= item->window.rectEffects2.w; + if (item->window.rectClient.w < item->window.rectEffects.w) + { + item->window.rectClient.w = item->window.rectEffects.w; + done++; + } + } + } + + if (item->window.rectClient.h == item->window.rectEffects.h) + { + done++; + } + else + { + if (item->window.rectClient.h < item->window.rectEffects.h) + { + item->window.rectClient.h += item->window.rectEffects2.h; + if (item->window.rectClient.h > item->window.rectEffects.h) + { + item->window.rectClient.h = item->window.rectEffects.h; + done++; + } + } + else + { + item->window.rectClient.h -= item->window.rectEffects2.h; + if (item->window.rectClient.h < item->window.rectEffects.h) + { + item->window.rectClient.h = item->window.rectEffects.h; + done++; + } + } + } + + Item_UpdatePosition(item); + + if (done == 4) + { + item->window.flags &= ~WINDOW_INTRANSITION; + } + + } + } + +#ifdef _TRANS3 + +//JLF begin model transition stuff + if (item->window.flags & WINDOW_INTRANSITIONMODEL) + { + if ( item->type == ITEM_TYPE_MODEL) + { +//fields ing modelptr +// vec3_t g2mins2, g2maxs2, g2minsEffect, g2maxsEffect; +// float fov_x2, fov_y2, fov_Effectx, fov_Effecty; + + modelDef_t * modelptr = (modelDef_t *)item->typeData; + + if (DC->realTime > item->window.nextTime) + { + int done = 0; + item->window.nextTime = DC->realTime + item->window.offsetTime; + + +// transition the x,y,z max + if (modelptr->g2maxs[0] == modelptr->g2maxs2[0]) + { + done++; + } + else + { + if (modelptr->g2maxs[0] < modelptr->g2maxs2[0]) + { + modelptr->g2maxs[0] += modelptr->g2maxsEffect[0]; + if (modelptr->g2maxs[0] > modelptr->g2maxs2[0]) + { + modelptr->g2maxs[0] = modelptr->g2maxs2[0]; + done++; + } + } + else + { + modelptr->g2maxs[0] -= modelptr->g2maxsEffect[0]; + if (modelptr->g2maxs[0] < modelptr->g2maxs2[0]) + { + modelptr->g2maxs[0] = modelptr->g2maxs2[0]; + done++; + } + } + } +//y + if (modelptr->g2maxs[1] == modelptr->g2maxs2[1]) + { + done++; + } + else + { + if (modelptr->g2maxs[1] < modelptr->g2maxs2[1]) + { + modelptr->g2maxs[1] += modelptr->g2maxsEffect[1]; + if (modelptr->g2maxs[1] > modelptr->g2maxs2[1]) + { + modelptr->g2maxs[1] = modelptr->g2maxs2[1]; + done++; + } + } + else + { + modelptr->g2maxs[1] -= modelptr->g2maxsEffect[1]; + if (modelptr->g2maxs[1] < modelptr->g2maxs2[1]) + { + modelptr->g2maxs[1] = modelptr->g2maxs2[1]; + done++; + } + } + } + + +//z + + if (modelptr->g2maxs[2] == modelptr->g2maxs2[2]) + { + done++; + } + else + { + if (modelptr->g2maxs[2] < modelptr->g2maxs2[2]) + { + modelptr->g2maxs[2] += modelptr->g2maxsEffect[2]; + if (modelptr->g2maxs[2] > modelptr->g2maxs2[2]) + { + modelptr->g2maxs[2] = modelptr->g2maxs2[2]; + done++; + } + } + else + { + modelptr->g2maxs[2] -= modelptr->g2maxsEffect[2]; + if (modelptr->g2maxs[2] < modelptr->g2maxs2[2]) + { + modelptr->g2maxs[2] = modelptr->g2maxs2[2]; + done++; + } + } + } + +// transition the x,y,z min + if (modelptr->g2mins[0] == modelptr->g2mins2[0]) + { + done++; + } + else + { + if (modelptr->g2mins[0] < modelptr->g2mins2[0]) + { + modelptr->g2mins[0] += modelptr->g2minsEffect[0]; + if (modelptr->g2mins[0] > modelptr->g2mins2[0]) + { + modelptr->g2mins[0] = modelptr->g2mins2[0]; + done++; + } + } + else + { + modelptr->g2mins[0] -= modelptr->g2minsEffect[0]; + if (modelptr->g2mins[0] < modelptr->g2mins2[0]) + { + modelptr->g2mins[0] = modelptr->g2mins2[0]; + done++; + } + } + } +//y + if (modelptr->g2mins[1] == modelptr->g2mins2[1]) + { + done++; + } + else + { + if (modelptr->g2mins[1] < modelptr->g2mins2[1]) + { + modelptr->g2mins[1] += modelptr->g2minsEffect[1]; + if (modelptr->g2mins[1] > modelptr->g2mins2[1]) + { + modelptr->g2mins[1] = modelptr->g2mins2[1]; + done++; + } + } + else + { + modelptr->g2mins[1] -= modelptr->g2minsEffect[1]; + if (modelptr->g2mins[1] < modelptr->g2mins2[1]) + { + modelptr->g2mins[1] = modelptr->g2mins2[1]; + done++; + } + } + } + + +//z + + if (modelptr->g2mins[2] == modelptr->g2mins2[2]) + { + done++; + } + else + { + if (modelptr->g2mins[2] < modelptr->g2mins2[2]) + { + modelptr->g2mins[2] += modelptr->g2minsEffect[2]; + if (modelptr->g2mins[2] > modelptr->g2mins2[2]) + { + modelptr->g2mins[2] = modelptr->g2mins2[2]; + done++; + } + } + else + { + modelptr->g2mins[2] -= modelptr->g2minsEffect[2]; + if (modelptr->g2mins[2] < modelptr->g2mins2[2]) + { + modelptr->g2mins[2] = modelptr->g2mins2[2]; + done++; + } + } + } + + + +//fovx + if (modelptr->fov_x == modelptr->fov_x2) + { + done++; + } + else + { + if (modelptr->fov_x < modelptr->fov_x2) + { + modelptr->fov_x += modelptr->fov_Effectx; + if (modelptr->fov_x > modelptr->fov_x2) + { + modelptr->fov_x = modelptr->fov_x2; + done++; + } + } + else + { + modelptr->fov_x -= modelptr->fov_Effectx; + if (modelptr->fov_x < modelptr->fov_x2) + { + modelptr->fov_x = modelptr->fov_x2; + done++; + } + } + } + +//fovy + if (modelptr->fov_y == modelptr->fov_y2) + { + done++; + } + else + { + if (modelptr->fov_y < modelptr->fov_y2) + { + modelptr->fov_y += modelptr->fov_Effecty; + if (modelptr->fov_y > modelptr->fov_y2) + { + modelptr->fov_y = modelptr->fov_y2; + done++; + } + } + else + { + modelptr->fov_y -= modelptr->fov_Effecty; + if (modelptr->fov_y < modelptr->fov_y2) + { + modelptr->fov_y = modelptr->fov_y2; + done++; + } + } + } + + if (done == 5) + { + item->window.flags &= ~WINDOW_INTRANSITIONMODEL; + } + + } + } + } +#endif +//JLF end transition stuff for models + + if (item->window.ownerDrawFlags && DC->ownerDrawVisible) + { + if (!DC->ownerDrawVisible(item->window.ownerDrawFlags)) + { + item->window.flags &= ~WINDOW_VISIBLE; + } + else + { + item->window.flags |= WINDOW_VISIBLE; + } + } + + if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE)) + { + if (!Item_EnableShowViaCvar(item, CVAR_SHOW)) + { + return qfalse; + } + } + + + if (item->window.flags & WINDOW_TIMEDVISIBLE) + { + + } + + if (!(item->window.flags & WINDOW_VISIBLE)) + { + return qfalse; + } + + if (!bDraw) + { + return qtrue; + } + //okay to paint +/* + //JLFMOUSE +#ifndef _XBOX + if (item->window.flags & WINDOW_MOUSEOVER) +#else + if (item->window.flags & WINDOW_HASFOCUS) +#endif + { + if (item->descText && !Display_KeyBindPending()) + { + // Make DOUBLY sure that this item should have desctext. +#ifndef _XBOX + // NOTE : we can't just check the mouse position on this, what if we TABBED + // to the current menu item -- in that case our mouse isn't over the item. + // Removing the WINDOW_MOUSEOVER flag just prevents the item's OnExit script from running + // if (!Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) + // { // It isn't something that should, because it isn't live anymore. + // item->window.flags &= ~WINDOW_MOUSEOVER; + // } + // else +#endif + //END JLFMOUSE + + // items can be enabled and disabled based on cvars + if( !(item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) ) + { // Draw the desctext + const char *textPtr = item->descText; + if (*textPtr == '@') // string reference + { + textPtr = SE_GetString( &textPtr[1] ); + } + + vec4_t color = {1, 1, 1, 1}; + Item_TextColor(item, &color); + + float fDescScale = parent->descScale ? parent->descScale : 1; + float fDescScaleCopy = fDescScale; + while (1) + { + // FIXME - add some type of parameter in the menu file like descfont to specify the font for the descriptions for this menu. + textWidth = DC->textWidth(textPtr, fDescScale, 4); // item->font); + + if (parent->descAlignment == ITEM_ALIGN_RIGHT) + { + xPos = parent->descX - textWidth; // Right justify + } + else if (parent->descAlignment == ITEM_ALIGN_CENTER) + { + xPos = parent->descX - (textWidth/2); // Center justify + } + else // Left justify + { + xPos = parent->descX; + } + + if (parent->descAlignment == ITEM_ALIGN_CENTER) + { + // only this one will auto-shrink the scale until we eventually fit... + // + if (xPos + textWidth > (SCREEN_WIDTH-4)) { + fDescScale -= 0.001f; + continue; + } + } + + + // Try to adjust it's y placement if the scale has changed... + // + int iYadj = 0; + if (fDescScale != fDescScaleCopy) + { + int iOriginalTextHeight = DC->textHeight(textPtr, fDescScaleCopy, uiInfo.uiDC.Assets.qhMediumFont); + iYadj = iOriginalTextHeight - DC->textHeight(textPtr, fDescScale, uiInfo.uiDC.Assets.qhMediumFont); + } + + // FIXME - add some type of parameter in the menu file like descfont to specify the font for the descriptions for this menu. + DC->drawText(xPos, parent->descY + iYadj, fDescScale, parent->descColor, textPtr, 0, parent->descTextStyle, 4); //item->font); + break; + } + } + } + } +*/ + + // paint the rect first.. + Window_Paint(&item->window, parent->fadeAmount , parent->fadeClamp, parent->fadeCycle); + + // Print a box showing the extents of the rectangle, when in debug mode + if (uis.debugMode) + { + vec4_t color; + color[1] = color[3] = 1; + color[0] = color[2] = 0; + DC->drawRect( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + 1, + color); + } + + //DC->drawRect(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, 1, red); + + switch (item->type) + { + case ITEM_TYPE_OWNERDRAW: + Item_OwnerDraw_Paint(item); + break; + + case ITEM_TYPE_TEXT: + case ITEM_TYPE_BUTTON: + Item_Text_Paint(item); + break; + case ITEM_TYPE_RADIOBUTTON: + break; + case ITEM_TYPE_CHECKBOX: + break; + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + Item_TextField_Paint(item); + break; + case ITEM_TYPE_COMBO: + break; + case ITEM_TYPE_LISTBOX: + Item_ListBox_Paint(item); + break; + case ITEM_TYPE_TEXTSCROLL: + Item_TextScroll_Paint ( item ); + break; + case ITEM_TYPE_MODEL: + Item_Model_Paint(item); + break; + case ITEM_TYPE_YESNO: + Item_YesNo_Paint(item); + break; + case ITEM_TYPE_MULTI: + Item_Multi_Paint(item); + break; + case ITEM_TYPE_BIND: + Item_Bind_Paint(item); + break; + case ITEM_TYPE_SLIDER: + Item_Slider_Paint(item); + break; + default: + break; + } + return qtrue; +} + +/* +================= +LerpColor +================= +*/ +void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t) +{ + int i; + + // lerp and clamp each component + for (i=0; i<4; i++) + { + c[i] = a[i] + t*(b[i]-a[i]); + if (c[i] < 0) + { + c[i] = 0; + } + else if (c[i] > 1.0) + { + c[i] = 1.0; + } + } +} + +/* +================= +Fade +================= +*/ +void Fade(int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount) +{ + if (*flags & (WINDOW_FADINGOUT | WINDOW_FADINGIN)) + { + if (DC->realTime > *nextTime) + { + *nextTime = DC->realTime + offsetTime; + if (*flags & WINDOW_FADINGOUT) + { + *f -= fadeAmount; + if (bFlags && *f <= 0.0) + { + *flags &= ~(WINDOW_FADINGOUT | WINDOW_VISIBLE); + } + } + else + { + *f += fadeAmount; + if (*f >= clamp) + { + *f = clamp; + if (bFlags) + { + *flags &= ~WINDOW_FADINGIN; + } + } + } + } + } +} + +/* +================= +GradientBar_Paint +================= +*/ +void GradientBar_Paint(rectDef_t *rect, vec4_t color) +{ + // gradient bar takes two paints + DC->setColor( color ); + DC->drawHandlePic(rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar); + DC->setColor( NULL ); +} + +/* +================= +Window_Paint +================= +*/ +void Window_Paint(Window *w, float fadeAmount, float fadeClamp, float fadeCycle) +{ + //float bordersize = 0; + vec4_t color; + rectDef_t fillRect = w->rect; + + + if (uis.debugMode) + { + color[0] = color[1] = color[2] = color[3] = 1; + DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color); + } + + if (w == NULL || (w->style == 0 && w->border == 0)) + { + return; + } + + if (w->border != 0) + { + fillRect.x += w->borderSize; + fillRect.y += w->borderSize; + fillRect.w -= w->borderSize + 1; + fillRect.h -= w->borderSize + 1; + } + + if (w->style == WINDOW_STYLE_FILLED) + { + // box, but possible a shader that needs filled + if (w->background) + { + Fade(&w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount); + DC->setColor(w->backColor); + DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background); + DC->setColor(NULL); + } + else + { + DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor); + } + } + else if (w->style == WINDOW_STYLE_GRADIENT) + { + GradientBar_Paint(&fillRect, w->backColor); + // gradient bar + } + else if (w->style == WINDOW_STYLE_SHADER) + { + if (w->flags & WINDOW_PLAYERCOLOR) + { + vec4_t color; + color[0] = ui_char_color_red.integer/255.0f; + color[1] = ui_char_color_green.integer/255.0f; + color[2] = ui_char_color_blue.integer/255.0f; + color[3] = 1; + ui.R_SetColor(color); + } + else if (w->flags & WINDOW_FORECOLORSET) + { + DC->setColor(w->foreColor); + } + + int rectw = fillRect.w; +#ifdef _XBOX + if(glw_state->isWidescreen && cls.state == CA_ACTIVE && !(Menus_AnyFullScreenVisible())) + { + if(strcmp(w->name, "screenOverlay") == 0) + rectw = 720; + + if(strcmp(w->name, "imageboxFill") == 0) + fillRect.x += 40; + + if(strcmp(w->name, "lineLeft") == 0) + fillRect.x += 40; + + if(strcmp(w->name, "lineRight") == 0) + fillRect.x += 40; + + if(strcmp(w->name, "lineTop") == 0) + fillRect.x += 40; + + if(strcmp(w->name, "lineTitle") == 0) + fillRect.x += 40; + + if(strcmp(w->name, "lineBottom") == 0) + fillRect.x += 40; + + if(strcmp(w->name, "acceptButton") == 0) + fillRect.x += 40; + } +#endif + DC->drawHandlePic(fillRect.x, fillRect.y, rectw, fillRect.h, w->background); + DC->setColor(NULL); + } + + if (w->border == WINDOW_BORDER_FULL) + { + // full + // HACK HACK HACK + if (w->style == WINDOW_STYLE_TEAMCOLOR) + { + if (color[0] > 0) + { + // red + color[0] = 1; + color[1] = color[2] = .5; + } + else + { + color[2] = 1; + color[0] = color[1] = .5; + } + color[3] = 1; + DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, color); + } + else + { + int rectw = w->rect.w; +#ifdef _XBOX + if(glw_state->isWidescreen) + { + if(strcmp(w->name, "missionfailed_menu") == 0) + rectw = 720; + } +#endif + DC->drawRect(w->rect.x, w->rect.y, rectw, w->rect.h, w->borderSize, w->borderColor); + } + } + else if (w->border == WINDOW_BORDER_HORZ) + { + // top/bottom + DC->setColor(w->borderColor); + DC->drawTopBottom(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize); + DC->setColor( NULL ); + } + else if (w->border == WINDOW_BORDER_VERT) + { + // left right + DC->setColor(w->borderColor); + DC->drawSides(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize); + DC->setColor( NULL ); + } + else if (w->border == WINDOW_BORDER_KCGRADIENT) + { + // this is just two gradient bars along each horz edge + rectDef_t r = w->rect; + r.h = w->borderSize; + GradientBar_Paint(&r, w->borderColor); + r.y = w->rect.y + w->rect.h - 1; + GradientBar_Paint(&r, w->borderColor); + } +} + +/* +================= +Display_KeyBindPending +================= +*/ +qboolean Display_KeyBindPending(void) +{ + return g_waitingForKey; +} + +/* +================= +ToWindowCoords +================= +*/ +void ToWindowCoords(float *x, float *y, windowDef_t *window) +{ + if (window->border != 0) + { + *x += window->borderSize; + *y += window->borderSize; + } + *x += window->rect.x; + *y += window->rect.y; +} + +/* +================= +Item_Text_AutoWrapped_Paint +================= +*/ +void Item_Text_AutoWrapped_Paint(itemDef_t *item) +{ + char text[1024]; + const char *p, *textPtr, *newLinePtr; + char buff[1024]; + int height, len, textWidth, newLine, newLineWidth; + float y; + vec4_t color; + + textWidth = 0; + newLinePtr = NULL; + + if (item->text == NULL) + { + if (item->cvar == NULL) + { + return; + } + else + { + DC->getCVarString(item->cvar, text, sizeof(text)); + textPtr = text; + } + } + else + { + textPtr = item->text; + } + if (*textPtr == '@') // string reference + { + textPtr = SE_GetString( &textPtr[1] ); + } + + if (*textPtr == '\0') + { + return; + } + Item_TextColor(item, &color); + //Item_SetTextExtents(item, &width, &height, textPtr); + if (item->value == 0) + { + item->value = (int)(0.5 + (float)DC->textWidth(textPtr, item->textscale, item->font) / item->window.rect.w); + } + height = DC->textHeight(textPtr, item->textscale, item->font); + item->special = 0; + + y = item->textaligny; + len = 0; + buff[0] = '\0'; + newLine = 0; + newLineWidth = 0; + p = textPtr; + int line = 1; + while (1) //findmeste (this will break widechar languages)! + { + if (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\0') + { + newLine = len; + newLinePtr = p+1; + newLineWidth = textWidth; + } + textWidth = DC->textWidth(buff, item->textscale, 0); + if ( (newLine && textWidth >= item->window.rect.w - item->textalignx) || *p == '\n' || *p == '\0') + { + if (line > item->cursorPos) //scroll + { + if (len) + { + if (item->textalignment == ITEM_ALIGN_LEFT) + { + item->textRect.x = item->textalignx; + } + else if (item->textalignment == ITEM_ALIGN_RIGHT) + { + item->textRect.x = item->textalignx - newLineWidth; + } + else if (item->textalignment == ITEM_ALIGN_CENTER) + { + item->textRect.x = item->textalignx - newLineWidth / 2; + } + item->textRect.y = y; + ToWindowCoords(&item->textRect.x, &item->textRect.y, &item->window); + // + buff[newLine] = '\0'; + + if ( *p && y + height + 4 > item->window.rect.h - height) + { + item->special = 1; + strcat(buff,"...");//uhh, let's render some ellipses + } + DC->drawText(item->textRect.x, item->textRect.y, item->textscale, color, buff, 0, item->textStyle, item->font); + } + y += height + 4; + if ( y > item->window.rect.h - height) + {//reached the bottom of the box, so stop + break; + } + len = 0; + } + else + { + strcpy(buff,"..."); + len = 3; + } + if (*p == '\0') + { //end of string + break; + } + p = newLinePtr; + newLine = 0; + newLineWidth = 0; + line++; + } + buff[len++] = *p++; + buff[len] = '\0'; + } + item->textRect = item->window.rect; +} + +/* +================= +Rect_ContainsPoint +================= +*/ +static qboolean Rect_ContainsPoint(rectDef_t *rect, float x, float y) +{ + //JLF ignore mouse pointer location +// return true; + // END JLF + if (rect) + { +// if ((x > rect->x) && (x < (rect->x + rect->w)) && (y > rect->y) && (y < (rect->y + rect->h))) + if ((x > rect->x) && (x < (rect->x + rect->w))) + { + if ((y > rect->y) && (y < (rect->y + rect->h))) + { + return qtrue; + } + } + } + return qfalse; +} + +qboolean Item_TextScroll_HandleKey ( itemDef_t *item, int key, qboolean down, qboolean force) +{ + textScrollDef_t *scrollPtr = (textScrollDef_t*)item->typeData; + int max; + int viewmax; + + if (force || (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS)) + { + max = Item_TextScroll_MaxScroll(item); + + viewmax = (item->window.rect.h / scrollPtr->lineHeight); + if ( key == A_CURSOR_UP || key == A_KP_8 ) + { + scrollPtr->startPos--; + if (scrollPtr->startPos < 0) + { + scrollPtr->startPos = 0; + } + return qtrue; + } + + if ( key == A_CURSOR_DOWN || key == A_KP_2 ) + { + scrollPtr->startPos++; + if (scrollPtr->startPos > max) + { + scrollPtr->startPos = max; + } + + return qtrue; + } + + // mouse hit + if (key == A_MOUSE1 || key == A_MOUSE2) + { + if (item->window.flags & WINDOW_LB_LEFTARROW) + { + scrollPtr->startPos--; + if (scrollPtr->startPos < 0) + { + scrollPtr->startPos = 0; + } + } + else if (item->window.flags & WINDOW_LB_RIGHTARROW) + { + // one down + scrollPtr->startPos++; + if (scrollPtr->startPos > max) + { + scrollPtr->startPos = max; + } + } + else if (item->window.flags & WINDOW_LB_PGUP) + { + // page up + scrollPtr->startPos -= viewmax; + if (scrollPtr->startPos < 0) + { + scrollPtr->startPos = 0; + } + } + else if (item->window.flags & WINDOW_LB_PGDN) + { + // page down + scrollPtr->startPos += viewmax; + if (scrollPtr->startPos > max) + { + scrollPtr->startPos = max; + } + } + else if (item->window.flags & WINDOW_LB_THUMB) + { + // Display_SetCaptureItem(item); + } + + return qtrue; + } + + if ( key == A_HOME || key == A_KP_7) + { + // home + scrollPtr->startPos = 0; + return qtrue; + } + if ( key == A_END || key == A_KP_1) + { + // end + scrollPtr->startPos = max; + return qtrue; + } + if (key == A_PAGE_UP || key == A_KP_9 ) + { + scrollPtr->startPos -= viewmax; + if (scrollPtr->startPos < 0) + { + scrollPtr->startPos = 0; + } + + return qtrue; + } + if ( key == A_PAGE_DOWN || key == A_KP_3 ) + { + scrollPtr->startPos += viewmax; + if (scrollPtr->startPos > max) + { + scrollPtr->startPos = max; + } + return qtrue; + } + } + + return qfalse; +} + +/* +================= +Item_ListBox_MaxScroll +================= +*/ +int Item_ListBox_MaxScroll(itemDef_t *item) +{ + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + int count = DC->feederCount(item->special); + int max; + + if (item->window.flags & WINDOW_HORIZONTAL) + { + max = count - (item->window.rect.w / listPtr->elementWidth) + 1; + } + else + { + max = count - (item->window.rect.h / listPtr->elementHeight) + 1; + } + + if (max < 0) + { + return 0; + } + return max; +} + +/* +================= +Item_ListBox_ThumbPosition +================= +*/ +int Item_ListBox_ThumbPosition(itemDef_t *item) +{ + float max, pos, size; + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + + max = Item_ListBox_MaxScroll(item); + if (item->window.flags & WINDOW_HORIZONTAL) { + + size = item->window.rect.w - (SCROLLBAR_SIZE * 2) - 2; + if (max > 0) + { + pos = (size-SCROLLBAR_SIZE) / (float) max; + } + else + { + pos = 0; + } + pos *= listPtr->startPos; + return item->window.rect.x + 1 + SCROLLBAR_SIZE + pos; + } + else + { + size = item->window.rect.h - (SCROLLBAR_SIZE * 2) - 2; + if (max > 0) + { + pos = (size-SCROLLBAR_SIZE) / (float) max; + } + else + { + pos = 0; + } + pos *= listPtr->startPos; + return item->window.rect.y + 1 + SCROLLBAR_SIZE + pos; + } +} + +/* +================= +Item_ListBox_OverLB +================= +*/ +int Item_ListBox_OverLB(itemDef_t *item, float x, float y) +{ + rectDef_t r; + listBoxDef_t *listPtr; + int thumbstart; + int count; + + count = DC->feederCount(item->special); + listPtr = (listBoxDef_t*)item->typeData; + if (item->window.flags & WINDOW_HORIZONTAL) + { + // check if on left arrow + r.x = item->window.rect.x; + r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; + r.h = r.w = SCROLLBAR_SIZE; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_LEFTARROW; + } + // check if on right arrow + r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_RIGHTARROW; + } + // check if on thumb + thumbstart = Item_ListBox_ThumbPosition(item); + r.x = thumbstart; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_THUMB; + } + r.x = item->window.rect.x + SCROLLBAR_SIZE; + r.w = thumbstart - r.x; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_PGUP; + } + r.x = thumbstart + SCROLLBAR_SIZE; + r.w = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_PGDN; + } + } + else + { + r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE; + r.y = item->window.rect.y; + r.h = r.w = SCROLLBAR_SIZE; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_LEFTARROW; + } + r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_RIGHTARROW; + } + thumbstart = Item_ListBox_ThumbPosition(item); + r.y = thumbstart; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_THUMB; + } + r.y = item->window.rect.y + SCROLLBAR_SIZE; + r.h = thumbstart - r.y; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_PGUP; + } + r.y = thumbstart + SCROLLBAR_SIZE; + r.h = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_PGDN; + } + } + return 0; +} + +/* +================= +Item_ListBox_MouseEnter +================= +*/ +void Item_ListBox_MouseEnter(itemDef_t *item, float x, float y) +{ + rectDef_t r; + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + + item->window.flags &= ~(WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN); + item->window.flags |= Item_ListBox_OverLB(item, x, y); + + if (item->window.flags & WINDOW_HORIZONTAL) + { + if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) + { + // check for selection hit as we have exausted buttons and thumb + if (listPtr->elementStyle == LISTBOX_IMAGE) + { + r.x = item->window.rect.x; + r.y = item->window.rect.y; + r.h = item->window.rect.h - SCROLLBAR_SIZE; + r.w = item->window.rect.w - listPtr->drawPadding; + if (Rect_ContainsPoint(&r, x, y)) + { + listPtr->cursorPos = (int)((x - r.x) / listPtr->elementWidth) + listPtr->startPos; + if (listPtr->cursorPos > listPtr->endPos) + { + listPtr->cursorPos = listPtr->endPos; + } + } + } + else + { + // text hit.. + } + } + } + else if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) + { + r.x = item->window.rect.x; + r.y = item->window.rect.y; + r.w = item->window.rect.w - SCROLLBAR_SIZE; + r.h = item->window.rect.h - listPtr->drawPadding; + if (Rect_ContainsPoint(&r, x, y)) + { + listPtr->cursorPos = (int)((y - 2 - r.y) / listPtr->elementHeight) + listPtr->startPos; + if (listPtr->cursorPos > listPtr->endPos) + { + listPtr->cursorPos = listPtr->endPos; + } + } + } +} + +/* +================= +Item_MouseEnter +================= +*/ +void Item_MouseEnter(itemDef_t *item, float x, float y) +{ + rectDef_t r; + //JLFMOUSE + if (item) + { + r = item->textRect; +// r.y -= r.h; // NOt sure why this is here, but I commented it out. + // in the text rect? + + // items can be enabled and disabled based on cvars + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) + { + return; + } + + if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) + { + return; + } + +//JLFMOUSE +#ifndef _XBOX + if (Rect_ContainsPoint(&r, x, y)) +#else + if (item->flags & WINDOW_HASFOCUS) +#endif + { + if (!(item->window.flags & WINDOW_MOUSEOVERTEXT)) + { +// Item_RunScript(item, item->mouseEnterText); + item->window.flags |= WINDOW_MOUSEOVERTEXT; + } + + if (!(item->window.flags & WINDOW_MOUSEOVER)) + { +// Item_RunScript(item, item->mouseEnter); + item->window.flags |= WINDOW_MOUSEOVER; + } + + } + else + { + // not in the text rect + if (item->window.flags & WINDOW_MOUSEOVERTEXT) + { + // if we were +// Item_RunScript(item, item->mouseExitText); + item->window.flags &= ~WINDOW_MOUSEOVERTEXT; + } + + if (!(item->window.flags & WINDOW_MOUSEOVER)) + { +// Item_RunScript(item, item->mouseEnter); + item->window.flags |= WINDOW_MOUSEOVER; + } + + if (item->type == ITEM_TYPE_LISTBOX) + { + Item_ListBox_MouseEnter(item, x, y); + } + else if ( item->type == ITEM_TYPE_TEXTSCROLL ) + { + Item_TextScroll_MouseEnter ( item, x, y ); + } + } + } +} + + + +/* +================= +Item_SetFocus +================= +*/ +// will optionaly set focus to this item +qboolean Item_SetFocus(itemDef_t *item, float x, float y) +{ + int i; + itemDef_t *oldFocus; + sfxHandle_t *sfx = &DC->Assets.itemFocusSound; + qboolean playSound = qfalse; +#ifdef _IMMERSION + ffHandle_t *ff = &DC->Assets.itemFocusForce; + qboolean playForce = qfalse; +#endif // _IMMERSION + // sanity check, non-null, not a decoration and does not already have the focus + if (item == NULL || item->window.flags & WINDOW_DECORATION || item->window.flags & WINDOW_HASFOCUS || !(item->window.flags & WINDOW_VISIBLE) || (item->window.flags & WINDOW_INACTIVE)) + { + return qfalse; + } + menuDef_t *parent = (menuDef_t*)item->parent; + + // items can be enabled and disabled based on cvars + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) + { + return qfalse; + } + + if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) + { + return qfalse; + } + + oldFocus = Menu_ClearFocus((menuDef_t *) item->parent); + + item->window.flags |= WINDOW_HASFOCUS; + if (item->onFocus) + { + Item_RunScript(item, item->onFocus); + } + if (item->focusSound) + { + sfx = &item->focusSound; + } + playSound = qtrue; + + if (playSound && sfx) + { + DC->startLocalSound( *sfx, CHAN_LOCAL_SOUND ); + } + + for (i = 0; i < parent->itemCount; i++) + { + if (parent->items[i] == item) + { + parent->cursorItem = i; + break; + } + } + + return qtrue; +} + + +/* +================= +IsVisible +================= +*/ +qboolean IsVisible(int flags) +{ + return (flags & WINDOW_VISIBLE && !(flags & WINDOW_FADINGOUT)); +} + +/* +================= +Item_MouseLeave +================= +*/ +void Item_MouseLeave(itemDef_t *item) +{ + if (item) + { + if (item->window.flags & WINDOW_MOUSEOVERTEXT) + { +// Item_RunScript(item, item->mouseExitText); + item->window.flags &= ~WINDOW_MOUSEOVERTEXT; + } +// Item_RunScript(item, item->mouseExit); + item->window.flags &= ~(WINDOW_LB_RIGHTARROW | WINDOW_LB_LEFTARROW); + } +} + +/* +================= +Item_SetMouseOver +================= +*/ +void Item_SetMouseOver(itemDef_t *item, qboolean focus) +{ + if (item) + { + if (focus) + { + item->window.flags |= WINDOW_MOUSEOVER; + } + else + { + item->window.flags &= ~WINDOW_MOUSEOVER; + } + } +} + + + + +extern int gScrollDelta; + +/* +================= +Menu_HandleMouseMove +================= +*/ +void Menu_HandleMouseMove(menuDef_t *menu, float x, float y) +{ +//JLF used exclusively for textscroll items +#ifdef _XBOX + if (!menu) + return; + + itemDef_t * item; + int i, itemCount; + itemCount = menu->itemCount; + textScrollDef_t *scrollPtr; + if (menu->window.flags && WINDOW_HASFOCUS) + { + for( i = 0; i < itemCount ;i++) + { + item = menu->items[i]; + + if (item->type != ITEM_TYPE_TEXTSCROLL) + continue; + + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) + continue; + + if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) + continue; + + if ( !(item->window.flags & WINDOW_VISIBLE) ) + continue; + + scrollPtr = (textScrollDef_t*)item->typeData; + if (gScrollDelta >0) + { + if (scrollPtr->endPos < scrollPtr->iLineCount-1) + { + scrollPtr->startPos+= gScrollDelta; + } + } + else if (gScrollDelta <0) + { + if(scrollPtr->startPos > 0) + { + scrollPtr->startPos+=gScrollDelta; + } + } + if ((scrollPtr->endPos +1 >= scrollPtr->iLineCount) || (scrollPtr->endPos == 0)) + { + Cvar_Set("ui_downArrow","0"); + } + else + { + Cvar_Set("ui_downArrow","1"); + } + if (scrollPtr->startPos <= 0) + { + Cvar_Set("ui_upArrow","0"); + } + else + { + Cvar_Set("ui_upArrow","1"); + } + } + } +#endif + return ; +} + +/* +================= +Display_MouseMove +================= +*/ +qboolean Display_MouseMove(void *p, int x, int y) +{ + //JLFMOUSE AGAIN I THINK THIS SHOULD BE MOOT + + int i; + menuDef_t *menu = (menuDef_t *) p; + + if (menu == NULL) + { + menu = Menu_GetFocused(); + if (menu) + { + if (menu->window.flags & WINDOW_POPUP) + { + Menu_HandleMouseMove(menu, x, y); + return qtrue; + } + } + + for (i = 0; i < menuCount; i++) + { + Menu_HandleMouseMove(&Menus[i], x, y); + } + } + else + { + menu->window.rect.x += x; + menu->window.rect.y += y; + Menu_UpdatePosition(menu); + } + return qtrue; +} + +/* +================= +Menus_AnyFullScreenVisible +================= +*/ +qboolean Menus_AnyFullScreenVisible(void) +{ + int i; + + for (i = 0; i < menuCount; i++) + { + if (Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen) + { + return qtrue; + } + + } + return qfalse; +} + +/* +================= +BindingIDFromName +================= +*/ +int BindingIDFromName(const char *name) +{ + int i; + for (i=0; i < g_bindCount; i++) + { + if (Q_stricmp(name, g_bindings[i].command) == 0) + { + return i; + } + } + return -1; +} + +/* +================= +Controls_SetConfig +================= +*/ +void Controls_SetConfig(qboolean restart) +{ + int i; + + // iterate each command, get its numeric binding + for (i=0; i < g_bindCount; i++) + { + if (g_bindings[i].bind1 != -1) + { + DC->setBinding( g_bindings[i].bind1, g_bindings[i].command ); + + if (g_bindings[i].bind2 != -1) + DC->setBinding( g_bindings[i].bind2, g_bindings[i].command ); + } + } + + //if ( s_controls.invertmouse.curvalue ) + // DC->setCVar("m_pitch", va("%f),-fabs( DC->getCVarValue( "m_pitch" ) ) ); + //else + // trap_Cvar_SetValue( "m_pitch", fabs( trap_Cvar_VariableValue( "m_pitch" ) ) ); + + //trap_Cvar_SetValue( "m_filter", s_controls.smoothmouse.curvalue ); + //trap_Cvar_SetValue( "cl_run", s_controls.alwaysrun.curvalue ); + //trap_Cvar_SetValue( "cg_autoswitch", s_controls.autoswitch.curvalue ); + //trap_Cvar_SetValue( "sensitivity", s_controls.sensitivity.curvalue ); + //trap_Cvar_SetValue( "in_joystick", s_controls.joyenable.curvalue ); + //trap_Cvar_SetValue( "joy_threshold", s_controls.joythreshold.curvalue ); + //trap_Cvar_SetValue( "cl_freelook", s_controls.freelook.curvalue ); +// +// DC->executeText(EXEC_APPEND, "in_restart\n"); +// ^--this is bad, it shows the cursor during map load, if you need to, add it as an exec cmd to use_joy or something. +} + +void Item_Bind_Ungrey(itemDef_t *item) +{ + menuDef_t *menu; + int i; + + menu = (menuDef_t *) item->parent; + for (i=0;iitemCount;++i) + { + if (menu->items[i] == item) + { + continue; + } + + menu->items[i]->window.flags &= ~WINDOW_INACTIVE; + } +} + +/* +================= +Item_Bind_HandleKey +================= +*/ +qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down) +{ + int id; + int i; + menuDef_t *menu; + + if (key == A_MOUSE1 && Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && !g_waitingForKey) + { + if (down) + { + g_waitingForKey = qtrue; + g_bindItem = item; + + // Set all others in the menu to grey + menu = (menuDef_t *) item->parent; + for (i=0;iitemCount;++i) + { + if (menu->items[i] == item) + { + continue; + } + menu->items[i]->window.flags |= WINDOW_INACTIVE; + } + + } + return qtrue; + } + else if (key == A_ENTER && !g_waitingForKey) + { + if (down) + { + g_waitingForKey = qtrue; + g_bindItem = item; + + // Set all others in the menu to grey + menu = (menuDef_t *) item->parent; + for (i=0;iitemCount;++i) + { + if (menu->items[i] == item) + { + continue; + } + menu->items[i]->window.flags |= WINDOW_INACTIVE; + } + + } + return qtrue; + } + else + { + if (!g_waitingForKey || g_bindItem == NULL) + { + return qfalse; + } + + if (key & K_CHAR_FLAG) + { + return qtrue; + } + + switch (key) + { + case A_ESCAPE: + g_waitingForKey = qfalse; + Item_Bind_Ungrey(item); + return qtrue; + + case A_BACKSPACE: + id = BindingIDFromName(item->cvar); + if (id != -1) + { + DC->setBinding( g_bindings[id].bind1, "" ); + DC->setBinding( g_bindings[id].bind2, "" ); + g_bindings[id].bind1 = -1; + g_bindings[id].bind2 = -1; + } + Controls_SetConfig(qtrue); + g_waitingForKey = qfalse; + g_bindItem = NULL; + Item_Bind_Ungrey(item); + return qtrue; + break; + case '`': + return qtrue; + } + } + + // Is the same key being bound to something else? + if (key != -1) + { + + for (i=0; i < g_bindCount; i++) + { + // The second binding matches the key + if (g_bindings[i].bind2 == key) + { + g_bindings[i].bind2 = -1; // NULL it out + } + + if (g_bindings[i].bind1 == key) + { + g_bindings[i].bind1 = g_bindings[i].bind2; + g_bindings[i].bind2 = -1; + } + } + } + + + id = BindingIDFromName(item->cvar); + + if (id != -1) + { + if (key == -1) + { + if( g_bindings[id].bind1 != -1 ) + { + DC->setBinding( g_bindings[id].bind1, "" ); + g_bindings[id].bind1 = -1; + } + if( g_bindings[id].bind2 != -1 ) + { + DC->setBinding( g_bindings[id].bind2, "" ); + g_bindings[id].bind2 = -1; + } + } + else if (g_bindings[id].bind1 == -1) + { + g_bindings[id].bind1 = key; + } + else if (g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1) + { + g_bindings[id].bind2 = key; + } + else + { + DC->setBinding( g_bindings[id].bind1, "" ); + DC->setBinding( g_bindings[id].bind2, "" ); + g_bindings[id].bind1 = key; + g_bindings[id].bind2 = -1; + } + } + + Controls_SetConfig(qtrue); + g_waitingForKey = qfalse; + Item_Bind_Ungrey(item); + + return qtrue; +} + +/* +================= +Menu_SetNextCursorItem +================= +*/ +itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu) +{ + + qboolean wrapped = qfalse; + int oldCursor = menu->cursorItem; + + + if (menu->cursorItem == -1) + { + menu->cursorItem = 0; + wrapped = qtrue; + } + + while (menu->cursorItem < menu->itemCount) + { + + menu->cursorItem++; + if (menu->cursorItem >= menu->itemCount && !wrapped) + { + wrapped = qtrue; + menu->cursorItem = 0; + } + + if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) + { + Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1); + return menu->items[menu->cursorItem]; + } + } + + menu->cursorItem = oldCursor; + return NULL; +} + +/* +================= +Menu_SetPrevCursorItem +================= +*/ +itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu) +{ + qboolean wrapped = qfalse; + int oldCursor = menu->cursorItem; + + if (menu->cursorItem < 0) + { + menu->cursorItem = menu->itemCount-1; + wrapped = qtrue; + } + + while (menu->cursorItem > -1) + { + menu->cursorItem--; + if (menu->cursorItem < 0 ) + { + if (wrapped) + { + break; + } + wrapped = qtrue; + menu->cursorItem = menu->itemCount -1; + } + + if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) + { + Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1); + return menu->items[menu->cursorItem]; + } + } + menu->cursorItem = oldCursor; + return NULL; + +} +/* +================= +Item_TextField_HandleKey +================= +*/ +qboolean Item_TextField_HandleKey(itemDef_t *item, int key) +{ + char buff[1024]; + int len; + itemDef_t *newItem = NULL; + editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + + if (item->cvar) + { + + memset(buff, 0, sizeof(buff)); + DC->getCVarString(item->cvar, buff, sizeof(buff)); + len = strlen(buff); + if (editPtr->maxChars && len > editPtr->maxChars) + { + len = editPtr->maxChars; + } + + if ( key & K_CHAR_FLAG ) + { + key &= ~K_CHAR_FLAG; + + + if (key == 'h' - 'a' + 1 ) + { // ctrl-h is backspace + if ( item->cursorPos > 0 ) + { + memmove( &buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos); + item->cursorPos--; + if (item->cursorPos < editPtr->paintOffset) + { + editPtr->paintOffset--; + } + } + DC->setCVar(item->cvar, buff); + return qtrue; + } + + // + // ignore any non printable chars + // + if ( key < 32 || !item->cvar) + { + return qtrue; + } + + if (item->type == ITEM_TYPE_NUMERICFIELD) + { + if (key < '0' || key > '9') + { + return qfalse; + } + } + + if (!DC->getOverstrikeMode()) + { + if (( len == MAX_EDITFIELD - 1 ) || (editPtr->maxChars && len >= editPtr->maxChars)) + { + return qtrue; + } + memmove( &buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos ); + } + else + { + if (editPtr->maxChars && item->cursorPos >= editPtr->maxChars) + { + return qtrue; + } + } + + buff[item->cursorPos] = key; + + DC->setCVar(item->cvar, buff); + + if (item->cursorPos < len + 1) + { + item->cursorPos++; + if (editPtr->maxPaintChars && item->cursorPos > editPtr->maxPaintChars) + { + editPtr->paintOffset++; + } + } + + } + else + { + + if ( key == A_DELETE || key == A_KP_PERIOD ) + { + if ( item->cursorPos < len ) + { + memmove( buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos); + DC->setCVar(item->cvar, buff); + } + return qtrue; + } + + if ( key == A_CURSOR_RIGHT || key == A_KP_6 ) + { + if (editPtr->maxPaintChars && item->cursorPos >= editPtr->maxPaintChars && item->cursorPos < len) + { + item->cursorPos++; + editPtr->paintOffset++; + return qtrue; + } + + if (item->cursorPos < len) + { + item->cursorPos++; + } + return qtrue; + } + + if ( key == A_CURSOR_LEFT|| key == A_KP_4 ) + { + if ( item->cursorPos > 0 ) + { + item->cursorPos--; + } + if (item->cursorPos < editPtr->paintOffset) + { + editPtr->paintOffset--; + } + return qtrue; + } + + if ( key == A_HOME || key == A_KP_7) + { + item->cursorPos = 0; + editPtr->paintOffset = 0; + return qtrue; + } + + if ( key == A_END || key == A_KP_1) + { + item->cursorPos = len; + if(item->cursorPos > editPtr->maxPaintChars) + { + editPtr->paintOffset = len - editPtr->maxPaintChars; + } + return qtrue; + } + + if ( key == A_INSERT || key == A_KP_0 ) + { + DC->setOverstrikeMode(!DC->getOverstrikeMode()); + return qtrue; + } + } + + if (key == A_TAB || key == A_CURSOR_DOWN || key == A_KP_2) + { + newItem = Menu_SetNextCursorItem((menuDef_t *) item->parent); + if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) + { + g_editItem = newItem; + } + } + + if (key == A_CURSOR_UP || key == A_KP_8) + { + newItem = Menu_SetPrevCursorItem((menuDef_t *) item->parent); + if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) + { + g_editItem = newItem; + } + } + + if ( key == A_ENTER || key == A_KP_ENTER || key == A_ESCAPE) + { + return qfalse; + } + + return qtrue; + } + return qfalse; + +} + +static void Scroll_TextScroll_AutoFunc (void *p) +{ + scrollInfo_t *si = (scrollInfo_t*)p; + + if (DC->realTime > si->nextScrollTime) + { + // need to scroll which is done by simulating a click to the item + // this is done a bit sideways as the autoscroll "knows" that the item is a listbox + // so it calls it directly + Item_TextScroll_HandleKey(si->item, si->scrollKey, qtrue, qfalse); + si->nextScrollTime = DC->realTime + si->adjustValue; + } + + if (DC->realTime > si->nextAdjustTime) + { + si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + if (si->adjustValue > SCROLL_TIME_FLOOR) + { + si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; + } + } +} + +static void Scroll_TextScroll_ThumbFunc(void *p) +{ + scrollInfo_t *si = (scrollInfo_t*)p; + rectDef_t r; + int pos; + int max; + + textScrollDef_t *scrollPtr = (textScrollDef_t*)si->item->typeData; + + if (DC->cursory != si->yStart) + { + r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_SIZE - 1; + r.y = si->item->window.rect.y + SCROLLBAR_SIZE + 1; + r.h = si->item->window.rect.h - (SCROLLBAR_SIZE*2) - 2; + r.w = SCROLLBAR_SIZE; + max = Item_TextScroll_MaxScroll(si->item); + // + pos = (DC->cursory - r.y - SCROLLBAR_SIZE/2) * max / (r.h - SCROLLBAR_SIZE); + if (pos < 0) + { + pos = 0; + } + else if (pos > max) + { + pos = max; + } + + scrollPtr->startPos = pos; + si->yStart = DC->cursory; + } + + if (DC->realTime > si->nextScrollTime) + { + // need to scroll which is done by simulating a click to the item + // this is done a bit sideways as the autoscroll "knows" that the item is a listbox + // so it calls it directly + Item_TextScroll_HandleKey(si->item, si->scrollKey, qtrue, qfalse); + si->nextScrollTime = DC->realTime + si->adjustValue; + } + + if (DC->realTime > si->nextAdjustTime) + { + si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + if (si->adjustValue > SCROLL_TIME_FLOOR) + { + si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; + } + } +} + +/* +================= +Menu_OverActiveItem +================= +*/ +static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y) +{ + if (menu && menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED)) + { +//JLFMOUSE +#ifdef _XBOX + return qtrue; +#endif + if (Rect_ContainsPoint(&menu->window.rect, x, y)) + { + int i; + for (i = 0; i < menu->itemCount; i++) + { + // turn off focus each item + // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; + + if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) + { + continue; + } + + if (menu->items[i]->window.flags & WINDOW_DECORATION) + { + continue; + } + + if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) + { + itemDef_t *overItem = menu->items[i]; + if (overItem->type == ITEM_TYPE_TEXT && overItem->text) + { + if (Rect_ContainsPoint(&overItem->window.rect, x, y)) + { + return qtrue; + } + else + { + continue; + } + } + else + { + return qtrue; + } + } + } + } + } + return qfalse; +} + +/* +================= +Display_VisibleMenuCount +================= +*/ +int Display_VisibleMenuCount(void) +{ + int i, count; + count = 0; + for (i = 0; i < menuCount; i++) + { + if (Menus[i].window.flags & (WINDOW_FORCED | WINDOW_VISIBLE)) + { + count++; + } + } + return count; +} + +/* +================= +Window_CloseCinematic +================= +*/ +static void Window_CloseCinematic(windowDef_t *window) +{ +/* + if (window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0) + { + DC->stopCinematic(window->cinematic); + window->cinematic = -1; + } +*/ +} +/* +================= +Menu_CloseCinematics +================= +*/ +static void Menu_CloseCinematics(menuDef_t *menu) +{ + if (menu) + { + int i; + Window_CloseCinematic(&menu->window); + for (i = 0; i < menu->itemCount; i++) + { + Window_CloseCinematic(&menu->items[i]->window); + if (menu->items[i]->type == ITEM_TYPE_OWNERDRAW) + { + DC->stopCinematic(0-menu->items[i]->window.ownerDraw); + } + } + } +} + +/* +================= +Display_CloseCinematics +================= +*/ +static void Display_CloseCinematics() +{ + int i; + for (i = 0; i < menuCount; i++) + { + Menu_CloseCinematics(&Menus[i]); + } +} + +/* +================= +Item_StopCapture +================= +*/ +void Item_StopCapture(itemDef_t *item) +{ + +} + +/* +================= +Item_ListBox_HandleKey +================= +*/ +qboolean Item_ListBox_HandleKey(itemDef_t *item, int key, qboolean down, qboolean force) +{ + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + int count = DC->feederCount(item->special); + int max, viewmaxw, viewmaxh,viewmax; + int gridcount; + + viewmaxw = (item->window.rect.w / listPtr->elementWidth); + viewmaxh = (item->window.rect.h / listPtr->elementHeight); + gridcount = viewmaxw * viewmaxh; + if (count <=0) + return qfalse; + +//JLFMOUSE +#ifndef _XBOX + if (force || (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS)) +#else + if (force || item->window.flags & WINDOW_HASFOCUS) +#endif + { + max = Item_ListBox_MaxScroll(item); + if (item->window.flags & WINDOW_HORIZONTAL) + { + viewmax = viewmaxw; + if ( key == A_CURSOR_LEFT || key == A_KP_4 ) + { + if (!listPtr->notselectable) + { + listPtr->cursorPos--; + if (listPtr->cursorPos < 0) + { + listPtr->cursorPos = 0; +#ifdef _XBOX + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + return qfalse; +#endif + } + if (listPtr->cursorPos < listPtr->startPos) + { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= listPtr->startPos + viewmaxw) + { + listPtr->startPos = listPtr->cursorPos - viewmaxw + 1; + } + item->cursorPos = listPtr->cursorPos; + + DC->feederSelection(item->special, item->cursorPos, item); + + } + else + { + listPtr->startPos--; +//JLF +#ifdef _XBOX // MPMOVED + listPtr->cursorPos--; + if (listPtr->cursorPos >= 0) + DC->feederSelection(item->special, listPtr->cursorPos, item); + else + listPtr->cursorPos =0; +#endif + if (listPtr->startPos < 0) + { + listPtr->startPos = 0; +//JLFMOUSE +#ifdef _XBOX // MPMOVED + return false; +#endif + + } + } + return qtrue; + } + if ( key == A_CURSOR_RIGHT || key == A_KP_6 ) + { + if (!listPtr->notselectable) + { + listPtr->cursorPos++; + + if (listPtr->cursorPos < listPtr->startPos) + { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= count) + { + listPtr->cursorPos = count-1; +#ifdef _XBOX + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + return qfalse; + +#endif + } + if (listPtr->cursorPos >= listPtr->startPos + viewmaxw) + { + listPtr->startPos = listPtr->cursorPos - viewmaxw + 1; + } + item->cursorPos = listPtr->cursorPos; + + DC->feederSelection(item->special, item->cursorPos, item); + } + else + { + listPtr->startPos++; +//JLF +#ifdef _XBOX // MPMOVED + listPtr->cursorPos++; + if (listPtr->cursorPos < count) + DC->feederSelection(item->special, listPtr->cursorPos, item); + else + listPtr->cursorPos =count -1; +#endif + if (listPtr->startPos >= count) + { + listPtr->startPos = count-1; +//JLFMOUSE +#ifdef _XBOX // MPMOVED + return false; +#endif + + } + } + return qtrue; + } + } + else //VERTICAL ALIGNMENT ************************* + { + viewmax = viewmaxh; + if ( key == A_CURSOR_UP || key == A_KP_8 ) + { + if (!listPtr->notselectable) + { + + if (listPtr->elementStyle == LISTBOX_IMAGE) + { listPtr->cursorPos-= viewmaxw; + if (listPtr->cursorPos < 0) + { + listPtr->cursorPos+=viewmaxw; + return qfalse; + } + } + else + listPtr->cursorPos--; + + + if (listPtr->cursorPos < 0) + { + listPtr->cursorPos = 0; +#ifdef _XBOX + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + return qfalse; + +#endif + } + if (listPtr->cursorPos < listPtr->startPos) + { + if (listPtr->elementStyle == LISTBOX_IMAGE) + listPtr->startPos -= viewmaxw; + else + listPtr->startPos = listPtr->cursorPos; + } + + if (listPtr->elementStyle == LISTBOX_IMAGE) + { + if (listPtr->cursorPos >= listPtr->startPos + gridcount) + listPtr->startPos += gridcount; + } + else + { + if (listPtr->cursorPos >= listPtr->startPos + viewmaxh) + listPtr->startPos= listPtr->cursorPos-viewmaxh + 1; + } + + // listPtr->startPos = listPtr->cursorPos - viewmaxh + 1; + + + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + } + else + { + listPtr->startPos--; + +//JLF +#ifdef _XBOX // MPMOVED + listPtr->cursorPos--; + if (listPtr->cursorPos >= 0) + { + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, listPtr->cursorPos, item); + } + else + listPtr->cursorPos = 0; +#endif + + if (listPtr->startPos < 0) + { + listPtr->startPos = 0; +//JLFMOUSE +#ifdef _XBOX // MPMOVED + return false; +#endif + + } + } + return qtrue; + } + if ( key == A_CURSOR_DOWN || key == A_KP_2 ) + { + if (!listPtr->notselectable) + { + if (listPtr->elementStyle == LISTBOX_IMAGE) + { + listPtr->cursorPos+= viewmaxw; + if (listPtr->cursorPos >=count) + { + listPtr->cursorPos-= viewmaxw; + if ( count-1 == listPtr->cursorPos) + listPtr->cursorPos++; + else + listPtr->cursorPos = count-1; + } + } + else + listPtr->cursorPos++; + + if (listPtr->cursorPos < listPtr->startPos) + { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= count) + { + listPtr->cursorPos = count-1; +#ifdef _XBOX + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + return qfalse; + +#endif + } + if (listPtr->elementStyle == LISTBOX_IMAGE) + { + if (listPtr->cursorPos >= listPtr->startPos + gridcount) { +#ifdef _XBOX + listPtr->startPos += viewmaxw; +#else + listPtr->startPos = listPtr->cursorPos - viewmax + 1; +#endif + } + } + else + { + if (listPtr->cursorPos >= listPtr->startPos + viewmaxh) + listPtr->startPos++; + } + + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + + } + else + { + listPtr->startPos++; +//JLF +#ifdef _XBOX // MPMOVED + listPtr->cursorPos++; + if (listPtr->cursorPos < count) + { + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, listPtr->cursorPos, item); + } + else + listPtr->cursorPos = count -1; + +#endif + + +//JLFMOUSE +#ifdef _XBOX // MPMOVED + if (listPtr->startPos > count-1) + { + listPtr->startPos = count-1; + return false; +#else + + if (listPtr->startPos > max) + { + listPtr->startPos = max; +#endif + + } + } + return qtrue; + } + +//JLF newstuff + if ( key == A_CURSOR_LEFT && listPtr->elementStyle == LISTBOX_IMAGE ) + { + if (viewmaxw <=1) + return false; + if (!listPtr->notselectable) + { + listPtr->cursorPos--; + + if (listPtr->cursorPos < 0) + { + listPtr->cursorPos = 0; +#ifdef _XBOX + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + return qfalse; + +#endif + } + if (listPtr->cursorPos < listPtr->startPos) + { + //JLF newgrid +#ifdef _XBOX + listPtr->startPos -= viewmaxw; +#else + listPtr->startPos = listPtr->cursorPos; +#endif + } + // if (listPtr->cursorPos >= listPtr->startPos + viewmaxh) + // { + // listPtr->startPos = listPtr->cursorPos - viewmaxh + 1; + // } + item->cursorPos = listPtr->cursorPos; + + DC->feederSelection(item->special, item->cursorPos, item); + } + else + { + listPtr->startPos--; +//JLF +#ifdef _XBOX // MPMOVED + listPtr->cursorPos--; + if (listPtr->cursorPos >= 0) + DC->feederSelection(item->special, listPtr->cursorPos, item); + else + listPtr->cursorPos = 0; +#endif + + if (listPtr->startPos < 0) + { + listPtr->startPos = 0; +//JLFMOUSE +#ifdef _XBOX // MPMOVED + return false; +#endif + + } + } + return qtrue; + } + + + if ( key == A_CURSOR_RIGHT && listPtr->elementStyle == LISTBOX_IMAGE ) + { + if (viewmaxw <=1) + return false; + if (!listPtr->notselectable) + { + listPtr->cursorPos++; + + if (listPtr->cursorPos >= count) + { + listPtr->cursorPos = count-1; +#ifdef _XBOX + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + return qfalse; + +#endif + } + if (listPtr->cursorPos < listPtr->startPos) + { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= listPtr->startPos + gridcount) + { + //listPtr->startPos = listPtr->cursorPos - viewmaxh + 1; + listPtr->startPos += viewmaxw; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + } + else + { + listPtr->startPos++; +//JLF +#ifdef _XBOX // MPMOVED + listPtr->cursorPos++; + if (listPtr->cursorPos < count) + DC->feederSelection(item->special, listPtr->cursorPos, item); + else + listPtr->cursorPos = count-1; +#endif + + if (listPtr->startPos startPos = count-1; +//JLFMOUSE +#ifdef _XBOX // MPMOVED + return false; +#endif + + } + } + return qtrue; + } + + } + + // mouse hit + if (key == A_MOUSE1 || key == A_MOUSE2) + { +#ifdef _XBOX + if (listPtr->doubleClick) + { + Item_RunScript(item, listPtr->doubleClick); + return qtrue; + } + else + { + return Item_HandleAction(item); + } +#else + if (item->window.flags & WINDOW_LB_LEFTARROW) + { + listPtr->startPos--; + if (listPtr->startPos < 0) + { + listPtr->startPos = 0; +//JLFMOUSE +#ifdef _XBOX // MPMOVED + return false; +#endif + + } + } + else if (item->window.flags & WINDOW_LB_RIGHTARROW) + { + // one down + listPtr->startPos++; + if (listPtr->startPos > max) + { + listPtr->startPos = max; +//JLFMOUSE +#ifdef _XBOX // MPMOVED + return false; +#endif + + } + } + else if (item->window.flags & WINDOW_LB_PGUP) + { + // page up + listPtr->startPos -= viewmax; + if (listPtr->startPos < 0) + { + listPtr->startPos = 0; +//JLFMOUSE +#ifdef _XBOX // MPMOVED + return false; +#endif + + } + } + else if (item->window.flags & WINDOW_LB_PGDN) + { + // page down + listPtr->startPos += viewmax; + if (listPtr->startPos > max) + { + listPtr->startPos = max; +//JLFMOUSE +#ifdef _XBOX // MPMOVED + return false; +#endif + + } + } + else if (item->window.flags & WINDOW_LB_THUMB) + { + // Display_SetCaptureItem(item); + } + else + { + // select an item + if (DC->realTime < lastListBoxClickTime && listPtr->doubleClick) + { + Item_RunScript(item, listPtr->doubleClick); + } + lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY; + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + } + return qtrue; +#endif + } + } + return qfalse; +} + +/* +================= +Scroll_ListBox_AutoFunc +================= +*/ +static void Scroll_ListBox_AutoFunc(void *p) +{ + scrollInfo_t *si = (scrollInfo_t*)p; + if (DC->realTime > si->nextScrollTime) + { + // need to scroll which is done by simulating a click to the item + // this is done a bit sideways as the autoscroll "knows" that the item is a listbox + // so it calls it directly + Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse); + si->nextScrollTime = DC->realTime + si->adjustValue; + } + + if (DC->realTime > si->nextAdjustTime) + { + si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + if (si->adjustValue > SCROLL_TIME_FLOOR) + { + si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; + } + } +} + +/* +================= +Scroll_ListBox_ThumbFunc +================= +*/ +static void Scroll_ListBox_ThumbFunc(void *p) +{ + scrollInfo_t *si = (scrollInfo_t*)p; + rectDef_t r; + int pos, max; + + listBoxDef_t *listPtr = (listBoxDef_t*)si->item->typeData; + if (si->item->window.flags & WINDOW_HORIZONTAL) + { + if (DC->cursorx == si->xStart) + { + return; + } + r.x = si->item->window.rect.x + SCROLLBAR_SIZE + 1; + r.y = si->item->window.rect.y + si->item->window.rect.h - SCROLLBAR_SIZE - 1; + r.h = SCROLLBAR_SIZE; + r.w = si->item->window.rect.w - (SCROLLBAR_SIZE*2) - 2; + max = Item_ListBox_MaxScroll(si->item); + // + pos = (DC->cursorx - r.x - SCROLLBAR_SIZE/2) * max / (r.w - SCROLLBAR_SIZE); + if (pos < 0) + { + pos = 0; + } + else if (pos > max) + { + pos = max; + } + listPtr->startPos = pos; + si->xStart = DC->cursorx; + } + else if (DC->cursory != si->yStart) + { + + r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_SIZE - 1; + r.y = si->item->window.rect.y + SCROLLBAR_SIZE + 1; + r.h = si->item->window.rect.h - (SCROLLBAR_SIZE*2) - 2; + r.w = SCROLLBAR_SIZE; + max = Item_ListBox_MaxScroll(si->item); + // + pos = (DC->cursory - r.y - SCROLLBAR_SIZE/2) * max / (r.h - SCROLLBAR_SIZE); + if (pos < 0) + { + pos = 0; + } + else if (pos > max) + { + pos = max; + } + listPtr->startPos = pos; + si->yStart = DC->cursory; + } + + if (DC->realTime > si->nextScrollTime) + { + // need to scroll which is done by simulating a click to the item + // this is done a bit sideways as the autoscroll "knows" that the item is a listbox + // so it calls it directly + Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse); + si->nextScrollTime = DC->realTime + si->adjustValue; + } + + if (DC->realTime > si->nextAdjustTime) + { + si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + if (si->adjustValue > SCROLL_TIME_FLOOR) + { + si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET; + } + } +} + +/* +================= +Item_Slider_OverSlider +================= +*/ +int Item_Slider_OverSlider(itemDef_t *item, float x, float y) +{ + rectDef_t r; + + r.x = Item_Slider_ThumbPosition(item) - (SLIDER_THUMB_WIDTH / 2); + r.y = item->window.rect.y - 2; + r.w = SLIDER_THUMB_WIDTH; + r.h = SLIDER_THUMB_HEIGHT; + + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_THUMB; + } + return 0; +} + +/* +================= +Scroll_Slider_ThumbFunc +================= +*/ +static void Scroll_Slider_ThumbFunc(void *p) +{ + float x, value, cursorx; + scrollInfo_t *si = (scrollInfo_t*)p; + editFieldDef_t *editDef = (struct editFieldDef_s *) si->item->typeData; + + if (si->item->text) + { + x = si->item->textRect.x + si->item->textRect.w + 8; + } + else + { + x = si->item->window.rect.x; + } + + cursorx = DC->cursorx; + + if (cursorx < x) + { + cursorx = x; + } + else if (cursorx > x + SLIDER_WIDTH) + { + cursorx = x + SLIDER_WIDTH; + } + value = cursorx - x; + value /= SLIDER_WIDTH; + value *= (editDef->maxVal - editDef->minVal); + value += editDef->minVal; + DC->setCVar(si->item->cvar, va("%f", value)); +} +/* +================= +Item_StartCapture +================= +*/ +void Item_StartCapture(itemDef_t *item, int key) +{ + int flags; + switch (item->type) + { + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + + case ITEM_TYPE_LISTBOX: + { + flags = Item_ListBox_OverLB(item, DC->cursorx, DC->cursory); + if (flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW)) + { + scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START; + scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + scrollInfo.adjustValue = SCROLL_TIME_START; + scrollInfo.scrollKey = key; + scrollInfo.scrollDir = (flags & WINDOW_LB_LEFTARROW) ? qtrue : qfalse; + scrollInfo.item = item; + captureData = &scrollInfo; + captureFunc = &Scroll_ListBox_AutoFunc; + itemCapture = item; + } + else if (flags & WINDOW_LB_THUMB) + { + scrollInfo.scrollKey = key; + scrollInfo.item = item; + scrollInfo.xStart = DC->cursorx; + scrollInfo.yStart = DC->cursory; + captureData = &scrollInfo; + captureFunc = &Scroll_ListBox_ThumbFunc; + itemCapture = item; + } + break; + } + + case ITEM_TYPE_TEXTSCROLL: + flags = Item_TextScroll_OverLB (item, DC->cursorx, DC->cursory); + if (flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW)) + { + scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START; + scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST; + scrollInfo.adjustValue = SCROLL_TIME_START; + scrollInfo.scrollKey = key; + scrollInfo.scrollDir = (flags & WINDOW_LB_LEFTARROW) ? qtrue : qfalse; + scrollInfo.item = item; + captureData = &scrollInfo; + captureFunc = &Scroll_TextScroll_AutoFunc; + itemCapture = item; + } + else if (flags & WINDOW_LB_THUMB) + { + scrollInfo.scrollKey = key; + scrollInfo.item = item; + scrollInfo.xStart = DC->cursorx; + scrollInfo.yStart = DC->cursory; + captureData = &scrollInfo; + captureFunc = &Scroll_TextScroll_ThumbFunc; + itemCapture = item; + } + break; + + case ITEM_TYPE_SLIDER: + { + flags = Item_Slider_OverSlider(item, DC->cursorx, DC->cursory); + if (flags & WINDOW_LB_THUMB) + { + scrollInfo.scrollKey = key; + scrollInfo.item = item; + scrollInfo.xStart = DC->cursorx; + scrollInfo.yStart = DC->cursory; + captureData = &scrollInfo; + captureFunc = &Scroll_Slider_ThumbFunc; + itemCapture = item; + } + break; + } + } +} + +/* +================= +Item_YesNo_HandleKey +================= +*/ +qboolean Item_YesNo_HandleKey(itemDef_t *item, int key) +{ +//JLFMOUSE MPMOVED +#ifndef _XBOX + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS && item->cvar) +#else + if (item->window.flags & WINDOW_HASFOCUS && item->cvar) +#endif + { +//JLFDPAD MPMOVED +#ifndef _XBOX + if (key == A_MOUSE1 || key == A_ENTER || key == A_MOUSE2 || key == A_MOUSE3) +#else + if ( key == A_CURSOR_RIGHT || key == A_CURSOR_LEFT) +#endif +//end JLFDPAD + { + DC->setCVar(item->cvar, va("%i", !DC->getCVarValue(item->cvar))); + return qtrue; + } + } + + return qfalse; + +} + +/* +================= +Item_Multi_FindCvarByValue +================= +*/ +int Item_Multi_FindCvarByValue(itemDef_t *item) +{ + char buff[1024]; + float value = 0; + int i; + multiDef_t *multiPtr = (multiDef_t*)item->typeData; + if (multiPtr) + { + if (multiPtr->strDef) + { + DC->getCVarString(item->cvar, buff, sizeof(buff)); + } + else + { + value = DC->getCVarValue(item->cvar); + } + for (i = 0; i < multiPtr->count; i++) + { + if (multiPtr->strDef) + { + if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) + { + return i; + } + } + else + { + if (multiPtr->cvarValue[i] == value) + { + return i; + } + } + } + } + return 0; +} + +/* +================= +Item_Multi_CountSettings +================= +*/ +int Item_Multi_CountSettings(itemDef_t *item) +{ + multiDef_t *multiPtr = (multiDef_t*)item->typeData; + if (multiPtr == NULL) + { + return 0; + } + return multiPtr->count; +} + + + +/* +================= +Item_OwnerDraw_HandleKey +================= +*/ +qboolean Item_OwnerDraw_HandleKey(itemDef_t *item, int key) +{ + if (item && DC->ownerDrawHandleKey) + { + return DC->ownerDrawHandleKey(item->window.ownerDraw, item->window.ownerDrawFlags, &item->special, key); + } + return qfalse; +} + +//JLF new selection LEFT/RIGHT +qboolean Item_Button_HandleKey(itemDef_t *item, int key) +{ +#ifdef _XBOX + if (key == A_MOUSE1) + { + if (Item_HandleAction(item)) + { + return qtrue; + } + } + if ( key == A_CURSOR_RIGHT) + { + if (Item_HandleSelectionNext(item)) + { + //Item processed it + return qtrue; + } + } + else if ( key == A_CURSOR_LEFT) + { + if (Item_HandleSelectionPrev(item)) + { + //Item processed it + return qtrue; + } + } +#endif + return false; +} + + +/* +================= +Item_Text_HandleKey +================= +*/ +qboolean Item_Text_HandleKey(itemDef_t *item, int key) +{ + if (key == A_MOUSE1) + { + if (Item_HandleAction(item)) + { + return qtrue; + } + } + else if (key == A_CURSOR_RIGHT) + { + if (Item_HandleSelectionNext(item)) + { + //Item processed it + return qtrue; + } + } + else if (key == A_CURSOR_LEFT) + { + if (Item_HandleSelectionPrev(item)) + { + //Item processed it + return qtrue; + } + } + return qfalse; +} + + +/* +================= +Item_Multi_HandleKey +================= +*/ +qboolean Item_Multi_HandleKey(itemDef_t *item, int key) +{ + multiDef_t *multiPtr = (multiDef_t*)item->typeData; + if (multiPtr) + { +//JLF MPMOVED +#ifndef _XBOX + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS) +#else + if (item->window.flags & WINDOW_HASFOCUS)// JLF* && item->cvar) +#endif + { +#ifndef _XBOX + + if (key == A_MOUSE1 || key == A_ENTER || key == A_MOUSE2 || key == A_MOUSE3) +#else +//JLFDPAD MPMOVED + if ( key == A_CURSOR_RIGHT || key == A_CURSOR_LEFT) +//end JLFDPAD +#endif + { + if (item->cvar) + { + int current = Item_Multi_FindCvarByValue(item); + int max = Item_Multi_CountSettings(item); +#ifndef _XBOX + if (key == A_MOUSE2) +#else + if (key == A_CURSOR_LEFT) +#endif + { + current--; + if ( current < 0 ) + { + current = max-1; + } + } + else + { + current++; + if ( current >= max ) + { + current = 0; + } + } + + if (multiPtr->strDef) + { + DC->setCVar(item->cvar, multiPtr->cvarStr[current]); + } + else + { + float value = multiPtr->cvarValue[current]; + if (((float)((int) value)) == value) + { + DC->setCVar(item->cvar, va("%i", (int) value )); + } + else + { + DC->setCVar(item->cvar, va("%f", value )); + } + } + if (item->special) + {//its a feeder? + DC->feederSelection(item->special, current, item); + } + } + else + { + int max = Item_Multi_CountSettings(item); +#ifndef _XBOX + if (key == A_MOUSE2) +#else + if (key == A_CURSOR_LEFT) +#endif + { + item->value--; + if ( item->value < 0 ) + { + item->value = max; + } + } + else + { + item->value++; + if ( item->value >= max ) + { + item->value = 0; + } + } + if (item->special) + {//its a feeder? + DC->feederSelection(item->special, item->value, item); + } + } +//JLF +#ifdef _XBOX + if ( key == A_CURSOR_RIGHT) + { + if (Item_HandleSelectionNext(item)) + { + //Item processed it + + } + } + else if ( key == A_CURSOR_LEFT) + { + if (Item_HandleSelectionPrev(item)) + { + //Item processed it + + } + } + +#endif + + return qtrue; + } + } + } + return qfalse; +} + +/* +================= +Item_Slider_HandleKey +================= +*/ +qboolean Item_Slider_HandleKey(itemDef_t *item, int key, qboolean down) +{ + float value; + + if (item->window.flags & WINDOW_HASFOCUS && item->cvar) + { + if (key == A_CURSOR_LEFT) + { + editFieldDef_t *editDef = (editFieldDef_s *) item->typeData; + if (editDef) + { + value = DC->getCVarValue(item->cvar); + value -= (editDef->maxVal-editDef->minVal)/TICK_COUNT; + if ( value < editDef->minVal) + value = editDef->minVal; + DC->setCVar(item->cvar, va("%f", value)); + return qtrue; + } + } + if (key == A_CURSOR_RIGHT) + { + editFieldDef_t *editDef = (editFieldDef_s *) item->typeData; + if (editDef) + { + value = DC->getCVarValue(item->cvar); + value += (editDef->maxVal-editDef->minVal)/TICK_COUNT; + if ( value > editDef->maxVal) + value = editDef->maxVal; + DC->setCVar(item->cvar, va("%f", value)); + return qtrue; + } + } + } + + return qfalse; +} + +/* +================= +Item_HandleKey +================= +*/ +qboolean Item_HandleKey(itemDef_t *item, int key, qboolean down) +{ + + if (itemCapture) + { + Item_StopCapture(itemCapture); + itemCapture = NULL; + captureFunc = NULL; + captureData = NULL; + } + else + { + if (down && key == A_MOUSE1 || key == A_MOUSE2 || key == A_MOUSE3) + { + Item_StartCapture(item, key); + } + } + + if (!down) + { + return qfalse; + } + + switch (item->type) + { + case ITEM_TYPE_BUTTON: +#ifdef _XBOX + return Item_Button_HandleKey(item, key); +#else + return qfalse; +#endif + break; + case ITEM_TYPE_RADIOBUTTON: + return qfalse; + break; + case ITEM_TYPE_CHECKBOX: + return qfalse; + break; + case ITEM_TYPE_EDITFIELD: + case ITEM_TYPE_NUMERICFIELD: + //return Item_TextField_HandleKey(item, key); + return qfalse; + break; + case ITEM_TYPE_COMBO: + return qfalse; + break; + case ITEM_TYPE_LISTBOX: + return Item_ListBox_HandleKey(item, key, down, qfalse); + break; + case ITEM_TYPE_TEXTSCROLL: + return Item_TextScroll_HandleKey(item, key, down, qfalse); + break; + case ITEM_TYPE_YESNO: + return Item_YesNo_HandleKey(item, key); + break; + case ITEM_TYPE_MULTI: + return Item_Multi_HandleKey(item, key); + break; + case ITEM_TYPE_OWNERDRAW: + return Item_OwnerDraw_HandleKey(item, key); + break; + case ITEM_TYPE_BIND: + return Item_Bind_HandleKey(item, key, down); + break; + case ITEM_TYPE_SLIDER: + return Item_Slider_HandleKey(item, key, down); + break; +//JLF MPMOVED + case ITEM_TYPE_TEXT: + return Item_Text_HandleKey(item, key); + break; + default: + return qfalse; + break; + } + + //return qfalse; +} + + + +//JLFACCEPT MPMOVED +/* +----------------------------------------- +Item_HandleAction + If Item has an action script, run it. +------------------------------------------- +*/ +qboolean Item_HandleAction(itemDef_t * item) +{ + if (!item) + return qfalse; + + if (item->action) + //if (item->accept) + { + //Item_RunScript(item, item->accept); + Item_RunScript(item, item->action); + return true; + } + return false; +} + + +//JLFDPADSCRIPT MPMOVED +/* +----------------------------------------- +Item_HandleSelectionNext + If Item has an selectionNext script, run it. +------------------------------------------- +*/ +qboolean Item_HandleSelectionNext(itemDef_t * item) +{ + if (item->selectionNext) + { + Item_RunScript(item, item->selectionNext); + return true; + } + return false; +} + +//JLFDPADSCRIPT MPMOVED +/* +----------------------------------------- +Item_HandleSelectionPrev + If Item has an selectionPrev script, run it. +------------------------------------------- +*/ +qboolean Item_HandleSelectionPrev(itemDef_t * item) +{ + if (item->selectionPrev) + { + Item_RunScript(item, item->selectionPrev); + return true; + } + return false; +} + + +/* +================= +Item_Action +================= +*/ +/* +void Item_Action(itemDef_t *item) +{ + if (item) + { + Item_RunScript(item, item->action); + } +} +*/ +/* +================= +Menu_HandleKey +================= +*/ +void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) +{ + int i; + itemDef_t *item = NULL; + qboolean inHandler = qfalse; + + if (inHandler) + { + return; + } + + inHandler = qtrue; + if (g_waitingForKey && down) + { + Item_Bind_HandleKey(g_bindItem, key, down); + inHandler = qfalse; + return; + } + + if (g_editingField && down) + { + if (!Item_TextField_HandleKey(g_editItem, key)) + { + g_editingField = qfalse; + g_editItem = NULL; + inHandler = qfalse; + return; + } + else if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_MOUSE3) + { + g_editingField = qfalse; + g_editItem = NULL; + Display_MouseMove(NULL, DC->cursorx, DC->cursory); + } + else if (key == A_TAB || key == A_CURSOR_UP || key == A_CURSOR_DOWN) + { + return; + } + } + + if (menu == NULL) + { + inHandler = qfalse; + return; + } + +#ifdef _XBOX + Menu_MapHack(key); +#endif + + // get the item with focus + for (i = 0; i < menu->itemCount; i++) + { + if (menu->items[i]->window.flags & WINDOW_HASFOCUS) + { + item = menu->items[i]; + break; + } + } + + if (!down) + { + inHandler = qfalse; + return; + } + + + if ( item !=NULL) + { + //Handlekey implies that this control was able to interpret and use this input + if (Item_HandleKey(item, key, down)) + { + if (((item->type == ITEM_TYPE_MULTI) || (item->type == ITEM_TYPE_SLIDER) || (item->type == ITEM_TYPE_YESNO)) && + ((key == A_CURSOR_RIGHT) || (key == A_CURSOR_LEFT))) + { + Item_HandleAction(item); + } +// if ((item->type != ITEM_TYPE_LISTBOX) && ((key != A_CURSOR_RIGHT && key != A_CURSOR_LEFT) || (item->type == ITEM_TYPE_MULTI))) +// Item_HandleAction(item); + inHandler = qfalse; + return; + } + } + + // Special Data Pad key handling (gotta love the datapad) + if (!(key & K_CHAR_FLAG) ) + { //only check keys not chars + char b[256]; + DC->getBindingBuf( key, b, 256 ); + if (Q_stricmp(b,"datapad") == 0) // They hit the datapad key again. + { + if (( Q_stricmp(menu->window.name,"datapadMissionMenu") == 0) || + (Q_stricmp(menu->window.name,"datapadWeaponsMenu") == 0) || + (Q_stricmp(menu->window.name,"datapadForcePowersMenu") == 0) || + (Q_stricmp(menu->window.name,"datapadInventoryMenu") == 0)) + { + key = A_ESCAPE; //pop on outta here + } + } + } + // default handling + switch ( key ) + { + case A_CURSOR_UP: + Menu_SetPrevCursorItem(menu); + break; + + case A_ESCAPE: + if (menu->onESC) + { + itemDef_t it; + it.parent = menu; + Item_RunScript(&it, menu->onESC); + } + break; + + case A_DELETE: + if (menu->xScript) + { + itemDef_t it; + it.parent = menu; + Item_RunScript(&it, menu->xScript); + } + break; + + case A_BACKSPACE: + if (menu->yScript) + { + itemDef_t it; + it.parent = menu; + if (Cvar_VariableIntegerValue("ui_cancelYScript")==0) + Item_RunScript(&it, menu->yScript); + } + break; + + case A_HOME: + if (menu->whiteScript) + { + itemDef_t it; + it.parent = menu; + Item_RunScript(&it, menu->whiteScript); + } + break; + + case A_CURSOR_DOWN: + Menu_SetNextCursorItem(menu); + break; + + case A_MOUSE1: + if (menu->onAccept) + { + itemDef_t it; + it.parent = menu; + Item_RunScript(&it, menu->onAccept); + } + break; + + case A_JOY0: + case A_JOY1: + case A_JOY2: + case A_JOY3: + case A_JOY4: + case A_AUX0: + case A_AUX1: + case A_AUX2: + case A_AUX3: + case A_AUX4: + case A_AUX5: + case A_AUX6: + case A_AUX7: + case A_AUX8: + case A_AUX9: + case A_AUX10: + case A_AUX11: + case A_AUX12: + case A_AUX13: + case A_AUX14: + case A_AUX15: + case A_AUX16: + break; + case A_KP_ENTER: + case A_ENTER: + if (item) + { + Item_HandleAction(item); + } + break; + } + inHandler = qfalse; +} + +/* +================= +ParseRect +================= +*/ +qboolean ParseRect(const char **p, rectDef_t *r) +{ + if (!COM_ParseFloat(p, &r->x)) + { + if (!COM_ParseFloat(p, &r->y)) + { + if (!COM_ParseFloat(p, &r->w)) + { + if (!COM_ParseFloat(p, &r->h)) + { + return qtrue; + } + } + } + } + return qfalse; +} + + +/* +================= +Menus_HideItems +================= +*/ +void Menus_HideItems(const char *menuName) +{ + menuDef_t *menu; + int i; + + menu =Menus_FindByName(menuName); // Get menu + + if (!menu) + { + Com_Printf(S_COLOR_YELLOW"WARNING: No menu was found. Could not hide items.\n"); + return; + } + + menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); + + for (i = 0; i < menu->itemCount; i++) + { + menu->items[i]->cvarFlags = CVAR_HIDE; + } +} + +/* +================= +Menus_ShowItems +================= +*/ +void Menus_ShowItems(const char *menuName) +{ + menuDef_t *menu; + int i; + + menu =Menus_FindByName(menuName); // Get menu + + if (!menu) + { + Com_Printf(S_COLOR_YELLOW"WARNING: No menu was found. Could not show items.\n"); + return; + } + + menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE); + + for (i = 0; i < menu->itemCount; i++) + { + menu->items[i]->cvarFlags = CVAR_SHOW; + } +} + +/* +================= +UI_Cursor_Show +================= +*/ +void UI_Cursor_Show(qboolean flag) +{ + DC->cursorShow = flag; + + if ((DC->cursorShow != qtrue) && (DC->cursorShow != qfalse)) + { + DC->cursorShow = qtrue; + } +} + +#ifdef _XBOX +/* +================= +Menu_MapHack +================= +*/ +void Menu_MapHack(int key) +{ + if (key == A_F1 || key == A_F2) + { + const char *maps[] = + { + "academy1", + "academy2", + "academy3", + "academy4", + "academy5", + "academy6", + "hoth2", + "hoth3", + "kor1", + "kor2", + "t1_danger", + "t1_fatal", + "t1_inter", + "t1_rail", + "t1_sour", + "t1_surprise", + "t2_dpred", + "t2_rancor", + "t2_rogue", + "t2_trip", + "t2_wedge", + "t3_bounty", + "t3_byss", + "t3_hevil", + "t3_rift", + "t3_stamp", + "taspir1", + "taspir2", + "vjun1", + "vjun2", + "vjun3", + "yavin1", + "yavin1b", + "yavin2", + }; + + const int num_maps = sizeof(maps) / sizeof(char*); + + int current = 0; + while (current < num_maps) + { + if (!strcmp(cl_mapname->string, maps[current])) break; + ++current; + } + + if (key == A_F1) --current; + if (key == A_F2) ++current; + + if (current < 0) current = num_maps - 1; + if (current >= num_maps) current = 0; + + DC->setCVar( "cl_mapname", maps[current] ); + } + else if (key == A_F3) + { + char localMapname[32]; + DC->getCVarString("cl_mapname", localMapname, sizeof(localMapname)); + DC->executeText( EXEC_APPEND, va("devmapall %s\n", localMapname ) ); + } +} + + +//JLF DEMOCODE +// IS ALREADY IN #ifdef _XBOX + + +void G_DemoStart() +{ +// demoDelay = 0; +// lastChange = 0; + g_runningDemo = true; + +// g_demoLastChange = 0; + + + extern void Menus_CloseAll(); + + Menus_CloseAll(); + + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + Cvar_Set( "cl_paused", "0" ); + + //We're moving back out to the attract movie - re-enable all controllers to interrupt it! + Cvar_SetValue( "inSplashMenu", 1 ); + +// g_demoStartFade = 0; +// g_demoStartTransition = 0; +} + +void G_DemoKeypress() +{ + g_demoLastKeypress = Sys_Milliseconds(); + +//JLF moved +// g_demoLastKeypress = Sys_Milliseconds(); + +} + +extern void UI_SetActiveMenu( const char* menuname,const char *menuID ); +void G_DemoEnd() +{ + + if (!g_runningDemo) + return; + //CIN_StopCinematic(1); + CIN_CloseAllVideos(); +// Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + g_ReturnToSplash = true; + g_runningDemo = qfalse; + G_DemoKeypress(); + trap_Key_SetCatcher( trap_Key_GetCatcher() & KEYCATCH_UI ); + trap_Key_ClearStates(); + Cvar_Set( "cl_paused", "0" ); + + UI_SetActiveMenu("splashMenu", NULL); + +// g_demoStartFade = 0; +// g_demoStartTransition = 0; +// g_demoLastKeypress = 0; +} + +void PlayDemo() +{ +// bool keypressed = false; + G_DemoStart(); + CIN_PlayAllFrames( "attract", 0, 0, 640, 480, 0, true ); +// while (!keypressed) +// keypressed = CIN_PlayAllFrames( "atvi.bik", 0, 0, 640, 480, 0, true ); + G_DemoEnd(); +} + +void UpdateDemoTimer() +{ + g_demoLastKeypress = Sys_Milliseconds(); +} + +bool TestDemoTimer() +{ +//JLF TEMP DEBUG +// return false; + + + menuDef_t* curMenu = Menu_GetFocused(); + if (curMenu && curMenu->window.name && + (!Q_stricmp(curMenu->window.name , "mainMenu") || + !Q_stricmp(curMenu->window.name, "splashMenu"))) + { + if (!g_demoLastKeypress) + g_demoLastKeypress = Sys_Milliseconds(); + else if (g_demoLastKeypress + DEMO_TIME_MAX < Sys_Milliseconds()) + return true; + } + return false; +} + +//END DEMOCODE + + +#endif // _XBOX diff --git a/code/ui/ui_shared.h b/code/ui/ui_shared.h new file mode 100644 index 0000000..b7a4d59 --- /dev/null +++ b/code/ui/ui_shared.h @@ -0,0 +1,535 @@ +#ifndef __UI_SHARED_H +#define __UI_SHARED_H + +#define MAX_TOKENLENGTH 1024 +#define MAX_OPEN_MENUS 16 +#define MAX_TEXTSCROLL_LINES 256 + +#define MAX_EDITFIELD 256 + +#ifndef TT_STRING +//token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation +#endif + +#define SLIDER_WIDTH 128.0 +#define SLIDER_HEIGHT 16.0 +#define SLIDER_THUMB_WIDTH 12.0 +#define SLIDER_THUMB_HEIGHT 16.0 +#define SCROLLBAR_SIZE 16.0 + +typedef struct pc_token_s +{ + int type; + int subtype; + int intvalue; + float floatvalue; + char string[MAX_TOKENLENGTH]; +} pc_token_t; + + + +// FIXME: combine flags into bitfields to save space +// FIXME: consolidate all of the common stuff in one structure for menus and items +// THINKABOUTME: is there any compelling reason not to have items contain items +// and do away with a menu per say.. major issue is not being able to dynamically allocate +// and destroy stuff.. Another point to consider is adding an alloc free call for vm's and have +// the engine just allocate the pool for it based on a cvar +// many of the vars are re-used for different item types, as such they are not always named appropriately +// the benefits of c++ in DOOM will greatly help crap like this +// FIXME: need to put a type ptr that points to specific type info per type +// +#define MAX_LB_COLUMNS 16 + +typedef struct columnInfo_s { + int pos; + int width; + int maxChars; +} columnInfo_t; + +typedef struct listBoxDef_s { + int startPos; + int endPos; + int drawPadding; + int cursorPos; + float elementWidth; + float elementHeight; + int elementStyle; + int numColumns; + columnInfo_t columnInfo[MAX_LB_COLUMNS]; + const char *doubleClick; + qboolean notselectable; +//JLF MPMOVED + qboolean scrollhidden; + qhandle_t selectionShader; + +} listBoxDef_t; + + +typedef struct editFieldDef_s { + float minVal; // edit field limits + float maxVal; // + float defVal; // + float range; // + int maxChars; // for edit fields + int maxPaintChars; // for edit fields + int paintOffset; // +} editFieldDef_t; + +#define MAX_MULTI_CVARS 64//32 + +typedef struct multiDef_s { + const char *cvarList[MAX_MULTI_CVARS]; + const char *cvarStr[MAX_MULTI_CVARS]; + float cvarValue[MAX_MULTI_CVARS]; + int count; + qboolean strDef; +} multiDef_t; + +#define CVAR_ENABLE 0x00000001 +#define CVAR_DISABLE 0x00000002 +#define CVAR_SHOW 0x00000004 +#define CVAR_HIDE 0x00000008 +#define CVAR_SUBSTRING 0x00000010 //when using enable or disable, just check for strstr instead of == + + +#ifdef _XBOX +// Super small - doesn't need to be bigger yet, helps us get into 64 MB +//#define STRING_POOL_SIZE 16*1024 +#define STRING_POOL_SIZE 128*1024 + +#else +#ifdef CGAME +#define STRING_POOL_SIZE 128*1024 +#else +#define STRING_POOL_SIZE 384*1024 +#endif +#endif + +//#define NUM_CROSSHAIRS 9 +#define NUM_CROSSHAIRS 1 + +typedef struct { + qhandle_t qhMediumFont; + qhandle_t cursor; + qhandle_t gradientBar; + qhandle_t scrollBarArrowUp; + qhandle_t scrollBarArrowDown; + qhandle_t scrollBarArrowLeft; + qhandle_t scrollBarArrowRight; + qhandle_t scrollBar; + qhandle_t scrollBarThumb; + qhandle_t buttonMiddle; + qhandle_t buttonInside; + qhandle_t solidBox; + qhandle_t sliderBar; +// qhandle_t sliderThumb; + sfxHandle_t menuEnterSound; + sfxHandle_t menuExitSound; + sfxHandle_t menuBuzzSound; + sfxHandle_t itemFocusSound; + sfxHandle_t forceChosenSound; + sfxHandle_t forceUnchosenSound; + sfxHandle_t datapadmoveRollSound; + sfxHandle_t datapadmoveJumpSound; + sfxHandle_t datapadmoveSaberSound1; + sfxHandle_t datapadmoveSaberSound2; + sfxHandle_t datapadmoveSaberSound3; + sfxHandle_t datapadmoveSaberSound4; + sfxHandle_t datapadmoveSaberSound5; + sfxHandle_t datapadmoveSaberSound6; + + sfxHandle_t nullSound; + +#ifdef _IMMERSION + ffHandle_t menuEnterForce; + ffHandle_t menuExitForce; + ffHandle_t menuBuzzForce; + ffHandle_t itemFocusForce; +#endif // _IMMERSION + float fadeClamp; + int fadeCycle; + float fadeAmount; + float shadowX; + float shadowY; + vec4_t shadowColor; + float shadowFadeClamp; + qboolean fontRegistered; + + // player settings +// qhandle_t fxBasePic; +// qhandle_t fxPic[7]; + qhandle_t crosshairShader[NUM_CROSSHAIRS]; + +} cachedAssets_t; + +struct itemDef_s; + +typedef struct { + + void (*addRefEntityToScene) (const refEntity_t *re ); + void (*clearScene) (); + void (*drawHandlePic) (float x, float y, float w, float h, qhandle_t asset); + void (*drawRect) ( float x, float y, float w, float h, float size, const vec4_t color); + void (*drawSides) (float x, float y, float w, float h, float size); + void (*drawText) (float x, float y, float scale, vec4_t color, const char *text, int iMaxPixelWidth, int style, int iFontIndex ); + void (*drawTextWithCursor)(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int iMaxPixelWidth, int style, int iFontIndex); + void (*drawTopBottom) (float x, float y, float w, float h, float size); + void (*executeText)(int exec_when, const char *text ); + int (*feederCount)(float feederID); + void (*feederSelection)(float feederID, int index, struct itemDef_s *item); + void (*fillRect) ( float x, float y, float w, float h, const vec4_t color); + void (*getBindingBuf)( int keynum, char *buf, int buflen ); + void (*getCVarString)(const char *cvar, char *buffer, int bufsize); + float (*getCVarValue)(const char *cvar); + qboolean (*getOverstrikeMode)(); + float (*getValue) (int ownerDraw); + void (*keynumToStringBuf)( int keynum, char *buf, int buflen ); + void (*modelBounds) (qhandle_t model, vec3_t min, vec3_t max); + qboolean (*ownerDrawHandleKey)(int ownerDraw, int flags, float *special, int key); + void (*ownerDrawItem) (float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle, int iFontIndex); + qboolean (*ownerDrawVisible) (int flags); + int (*ownerDrawWidth)(int ownerDraw, float scale); + void (*Pause)(qboolean b); + void (*Print)(const char *msg, ...); + int (*registerFont) (const char *pFontname); + qhandle_t (*registerModel) (const char *p); + qhandle_t (*registerShaderNoMip) (const char *p); + sfxHandle_t (*registerSound)(const char *name, qboolean compressed); + void (*renderScene) ( const refdef_t *fd ); + qboolean (*runScript)(const char **p); + qboolean (*deferScript)(const char **p); + void (*setBinding)( int keynum, const char *binding ); + void (*setColor) (const vec4_t v); + void (*setCVar)(const char *cvar, const char *value); + void (*setOverstrikeMode)(qboolean b); + void (*startLocalSound)( sfxHandle_t sfx, int channelNum ); + void (*stopCinematic)(int handle); + int (*textHeight) (const char *text, float scale, int iFontIndex); + int (*textWidth) (const char *text, float scale, int iFontIndex); + qhandle_t (*feederItemImage) (float feederID, int index); + const char *(*feederItemText) (float feederID, int index, int column, qhandle_t *handle); + + qhandle_t (*registerSkin)( const char *name ); + + //rww - ghoul2 stuff. Add whatever you need here, remember to set it in _UI_Init or it will crash when you try to use it. +#ifdef _XBOX // No default arguments on function pointers + qboolean g2_SetSkin(CGhoul2Info *ghlInfo, qhandle_t customSkin, qhandle_t renderSkin = 0) + { + return G2API_SetSkin(ghlInfo, customSkin, renderSkin); + } + qboolean g2_SetBoneAnim(CGhoul2Info *ghlInfo, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame = -1, const int blendTime = -1) + { + return G2API_SetBoneAnim(ghlInfo, boneName, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime); + } +#else + qboolean (*g2_SetSkin)(CGhoul2Info *ghlInfo, qhandle_t customSkin, qhandle_t renderSkin = 0); + qboolean (*g2_SetBoneAnim)(CGhoul2Info *ghlInfo, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame = -1, const int blendTime = -1); +#endif + qboolean (*g2_RemoveGhoul2Model)(CGhoul2Info_v &ghlInfo, const int modelIndex); + int (*g2_InitGhoul2Model)(CGhoul2Info_v &ghoul2, const char *fileName, int, qhandle_t customSkin, qhandle_t customShader, int modelFlags, int lodBias); + void (*g2_CleanGhoul2Models)(CGhoul2Info_v &ghoul2); + int (*g2_AddBolt)(CGhoul2Info *ghlInfo, const char *boneName); + qboolean (*g2_GetBoltMatrix)(CGhoul2Info_v &ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, const vec3_t scale); + void (*g2_GiveMeVectorFromMatrix)(mdxaBone_t &boltMatrix, Eorientations flags, vec3_t &vec); + + //Utility functions that don't immediately redirect to ghoul2 functions + int (*g2hilev_SetAnim)(CGhoul2Info *ghlInfo, const char *boneName, int animNum, const qboolean freeze); + +#ifdef _IMMERSION + ffHandle_t (*registerForce)(const char *name, int channel=FF_CHANNEL_MENU); + void (*startForce)(ffHandle_t ff); +#endif // _IMMERSION + + float yscale; + float xscale; + float bias; + int realTime; + int frameTime; + qboolean cursorShow; + int cursorx; + int cursory; + qboolean debug; + + cachedAssets_t Assets; + + glconfig_t glconfig; + qhandle_t whiteShader; + qhandle_t gradientImage; + float FPS; + +} displayContextDef_t; + +void UI_InitMemory( void ); + + +#define MAX_COLOR_RANGES 10 +#define MAX_MENUITEMS 200 +#define MAX_MENUS 64 + + + +#define WINDOW_MOUSEOVER 0x00000001 // mouse is over it, non exclusive +#define WINDOW_HASFOCUS 0x00000002 // has cursor focus, exclusive +#define WINDOW_VISIBLE 0x00000004 // is visible +#define WINDOW_INACTIVE 0x00000008 // is visible but grey ( non-active ) +#define WINDOW_DECORATION 0x00000010 // for decoration only, no mouse, keyboard, etc.. +#define WINDOW_FADINGOUT 0x00000020 // fading out, non-active +#define WINDOW_FADINGIN 0x00000040 // fading in +#define WINDOW_MOUSEOVERTEXT 0x00000080 // mouse is over it, non exclusive +#define WINDOW_INTRANSITION 0x00000100 // window is in transition +#define WINDOW_FORECOLORSET 0x00000200 // forecolor was explicitly set ( used to color alpha images or not ) +#define WINDOW_HORIZONTAL 0x00000400 // for list boxes and sliders, vertical is default this is set of horizontal +#define WINDOW_LB_LEFTARROW 0x00000800 // mouse is over left/up arrow +#define WINDOW_LB_RIGHTARROW 0x00001000 // mouse is over right/down arrow +#define WINDOW_LB_THUMB 0x00002000 // mouse is over thumb +#define WINDOW_LB_PGUP 0x00004000 // mouse is over page up +#define WINDOW_LB_PGDN 0x00008000 // mouse is over page down +#define WINDOW_ORBITING 0x00010000 // item is in orbit +#define WINDOW_OOB_CLICK 0x00020000 // close on out of bounds click +#define WINDOW_WRAPPED 0x00040000 // manually wrap text +#define WINDOW_AUTOWRAPPED 0x00080000 // auto wrap text +#define WINDOW_FORCED 0x00100000 // forced open +#define WINDOW_POPUP 0x00200000 // popup +#define WINDOW_BACKCOLORSET 0x00400000 // backcolor was explicitly set +#define WINDOW_TIMEDVISIBLE 0x00800000 // visibility timing ( NOT implemented ) +#define WINDOW_PLAYERCOLOR 0x01000000 // hack the forecolor to match ui_char_color_* +#define WINDOW_SCRIPTWAITING 0x02000000 // delayed script waiting to run +//JLF MPMOVED +#define WINDOW_INTRANSITIONMODEL 0x04000000 // delayed script waiting to run +#define WINDOW_IGNORE_ESCAPE 0x08000000 // ignore normal closeall menus escape functionality + +typedef struct { + float x; // horiz position + float y; // vert position + float w; // width + float h; // height; +} rectDef_t; + +typedef rectDef_t UIRectangle; + +// FIXME: do something to separate text vs window stuff +typedef struct { + UIRectangle rect; // client coord rectangle + UIRectangle rectClient; // screen coord rectangle + char *name; // + char *group; // if it belongs to a group +// const char *cinematicName; // cinematic name +// int cinematic; // cinematic handle + int style; // + int border; // + int ownerDraw; // ownerDraw style + int ownerDrawFlags; // show flags for ownerdraw items + float borderSize; // + int flags; // visible, focus, mouseover, cursor + UIRectangle rectEffects; // for various effects + UIRectangle rectEffects2; // for various effects + int offsetTime; // time based value for various effects + int nextTime; // time next effect should cycle + int delayTime; // time when delay expires + char *delayedScript; // points into another script's text while delaying + vec4_t foreColor; // text color + vec4_t backColor; // border color + vec4_t borderColor; // border color +// vec4_t outlineColor; // border color + qhandle_t background; // background asset +} windowDef_t; + +typedef windowDef_t Window; + +typedef struct { + vec4_t color; // + float low; // + float high; // +} colorRangeDef_t; + +typedef struct modelDef_s { + int angle; + vec3_t origin; + float fov_x; + float fov_y; + int rotationSpeed; + + vec3_t g2mins; //required + vec3_t g2maxs; //required + int g2skin; //optional + int g2anim; //optional +//JLF MPMOVED +//Transition extras + vec3_t g2mins2, g2maxs2, g2minsEffect, g2maxsEffect; + float fov_x2, fov_y2, fov_Effectx, fov_Effecty; +} modelDef_t; + +#define ITF_G2VALID 0x0001 // indicates whether or not g2 instance is valid. +#define ITF_ISCHARACTER 0x0002 // a character item, uses customRGBA +#define ITF_ISSABER 0x0004 // first saber item, draws blade +#define ITF_ISSABER2 0x0008 // second saber item, draws blade + +#define ITF_ISANYSABER (ITF_ISSABER|ITF_ISSABER2) //either saber + +typedef struct itemDef_s { + Window window; // common positional, border, style, layout info + UIRectangle textRect; // rectangle the text ( if any ) consumes + int type; // text, button, radiobutton, checkbox, textfield, listbox, combo + int alignment; // left center right + int textalignment; // ( optional ) alignment for text within rect based on text width + float textalignx; // ( optional ) text alignment x coord + float textaligny; // ( optional ) text alignment y coord +// float text2alignx; // ( optional ) text2 alignment x coord +// float text2aligny; // ( optional ) text2 alignment y coord + float textscale; // scale percentage from 72pts + int textStyle; // ( optional ) style, normal and shadowed are it for now + char *text; // display text +// char *text2; // display text2 +// char *descText; // Description text + void *parent; // menu owner + qhandle_t asset; // handle to asset + CGhoul2Info_v ghoul2; // ghoul2 instance if available instead of a model. + int flags; // flags like g2valid, character, saber, saber2, etc. +// const char *mouseEnterText; // mouse enter script +// const char *mouseExitText; // mouse exit script +// const char *mouseEnter; // mouse enter script +// const char *mouseExit; // mouse exit script + const char *action; // select script +//JLFACCEPT MPMOVED +// const char *accept; +//JLFDPADSCRIPT MPMOVED + const char * selectionNext; + const char * selectionPrev; + + const char *onFocus; // select script + const char *leaveFocus; // select script + const char *cvar; // associated cvar + const char *cvarTest; // associated cvar for enable actions + const char *enableCvar; // enable, disable, show, or hide based on value, this can contain a list + int cvarFlags; // what type of action to take on cvarenables + sfxHandle_t focusSound; // +#ifdef _IMMERSION + ffHandle_t focusForce; +#endif // _IMMERSION +// int numColors; // number of color ranges +// colorRangeDef_t colorRanges[MAX_COLOR_RANGES]; + float special; // used for feeder id's etc.. diff per type + int cursorPos; // cursor position in characters + void *typeData; // type specific data ptr's +// int appearanceSlot; // order of appearance + int value; // used by ITEM_TYPE_MULTI that aren't linked to a particular cvar. + int font; // FONT_SMALL,FONT_MEDIUM,FONT_LARGE +// int invertYesNo; + int xoffset; + +} itemDef_t; + +typedef struct { + Window window; + const char *font; // font + qboolean fullScreen; // covers entire screen + int itemCount; // number of items; + int fontIndex; // + int cursorItem; // which item as the cursor + int fadeCycle; // + float fadeClamp; // + float fadeAmount; // + const char *onOpen; // run when the menu is first opened + const char *onClose; // run when the menu is closed +//JLFACCEPT MPMOVED + const char *onAccept; // run when menu is closed with acceptance + + const char *onESC; // run when the menu is closed + const char *xScript; // run when X button is pressed + const char *yScript; // run when Y button is pressed + const char *whiteScript; // run when White button is pressed + const char *soundName; // background loop sound for menu + + vec4_t focusColor; // focus color for items + vec4_t disableColor; // focus color for items + itemDef_t *items[MAX_MENUITEMS]; // items this menu contains + float appearanceTime; // when next item should appear + int appearanceCnt; // current item displayed + float appearanceIncrement; // + int descX; // X position of description + int descY; // X position of description + vec4_t descColor; // description text color for items + int descAlignment; // Description of alignment + float descScale; // Description scale + int descTextStyle; // ( optional ) style, normal and shadowed are it for now + + +} menuDef_t; + +typedef struct textScrollDef_s +{ + int startPos; + int endPos; + + float lineHeight; + int maxLineChars; + int drawPadding; + + // changed spelling to make them fall out during compile while I made them asian-aware -Ste + // + int iLineCount; + const char* pLines[MAX_TEXTSCROLL_LINES]; // can contain NULL ptrs that you should skip over during paint. + +} textScrollDef_t; + +typedef struct +{ + const char *name; + qboolean (*handler) (itemDef_t *item, const char** args); + +} commandDef_t; + +menuDef_t *Menu_GetFocused(void); + +void Controls_GetConfig( void ); +void Controls_SetConfig(qboolean restart); +qboolean Display_KeyBindPending(void); +qboolean Display_MouseMove(void *p, int x, int y); +int Display_VisibleMenuCount(void); +qboolean Int_Parse(const char **p, int *i); +void Init_Display(displayContextDef_t *dc); +void Menus_Activate(menuDef_t *menu); +menuDef_t *Menus_ActivateByName(const char *p); +qboolean Menus_AnyFullScreenVisible(void); +void Menus_CloseAll(void); +int Menu_Count(void); +itemDef_t *Menu_FindItemByName(menuDef_t *menu, const char *p); +void Menu_HandleKey(menuDef_t *menu, int key, qboolean down); +void Menu_New(char *buffer); +void Menus_OpenByName(const char *p); +void Menu_PaintAll(void); +void Menu_Reset(void); +void PC_EndParseSession(char *buffer); +qboolean PC_Float_Parse(int handle, float *f); +qboolean PC_ParseString(const char **tempStr); +qboolean PC_ParseStringMem(const char **out); +void PC_ParseWarning(const char *message); +qboolean PC_String_Parse(int handle, const char **out); +#ifdef _XBOX +int PC_StartParseSession(const char *fileName,char **buffer, bool nested = false); +#else +int PC_StartParseSession(const char *fileName,char **buffer); +#endif +char *PC_ParseExt(void); +qboolean PC_ParseInt(int *number); +qboolean PC_ParseFloat(float *number); +qboolean PC_ParseColor(vec4_t *c); +void PC_SkipBracedSection( void ); +const char *String_Alloc(const char *p); +void String_Init(void); +qboolean String_Parse(const char **p, const char **out); +void String_Report(void); +void UI_Cursor_Show(qboolean flag); +itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name); + +extern displayContextDef_t *DC; + +#endif diff --git a/code/ui/ui_splash.cpp b/code/ui/ui_splash.cpp new file mode 100644 index 0000000..41abe8b --- /dev/null +++ b/code/ui/ui_splash.cpp @@ -0,0 +1,372 @@ + +#include "../client/client.h" +#include "../renderer/tr_local.h" +#include "../win32/glw_win_dx8.h" +#include "../win32/win_local.h" +#include "../win32/win_file.h" +#include "../ui/ui_splash.h" + +extern bool Sys_QuickStart( void ); + +/********* +Globals +*********/ +static bool SP_LicenseDone = false; + +/********* +SP_DisplayIntros +Draws intro movies to the screen +*********/ +void SP_DisplayLogos(void) +{ + if( !Sys_QuickStart() ) + CIN_PlayAllFrames( "logos", 0, 0, 640, 480, 0, true ); +} + +/********* +SP_DrawTexture +*********/ +void SP_DrawTexture(void* pixels, float width, float height, float vShift) +{ + if (!pixels) + { + // Ug. We were not even able to load the error message texture. + return; + } + + // Create a texture from the buffered file + GLuint texid; + qglGenTextures(1, &texid); + qglBindTexture(GL_TEXTURE_2D, texid); + qglTexImage2D(GL_TEXTURE_2D, 0, GL_DDS1_EXT, width, height, 0, GL_DDS1_EXT, GL_UNSIGNED_BYTE, pixels); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + + // Reset every GL state we've got. Who knows what state + // the renderer could be in when this function gets called. + qglColor3f(1.f, 1.f, 1.f); +#ifdef _XBOX + if(glw_state->isWidescreen) + qglViewport(0, 0, 720, 480); + else +#endif + qglViewport(0, 0, 640, 480); + + GLboolean alpha = qglIsEnabled(GL_ALPHA_TEST); + qglDisable(GL_ALPHA_TEST); + + GLboolean blend = qglIsEnabled(GL_BLEND); + qglDisable(GL_BLEND); + + GLboolean cull = qglIsEnabled(GL_CULL_FACE); + qglDisable(GL_CULL_FACE); + + GLboolean depth = qglIsEnabled(GL_DEPTH_TEST); + qglDisable(GL_DEPTH_TEST); + + GLboolean fog = qglIsEnabled(GL_FOG); + qglDisable(GL_FOG); + + GLboolean lighting = qglIsEnabled(GL_LIGHTING); + qglDisable(GL_LIGHTING); + + GLboolean offset = qglIsEnabled(GL_POLYGON_OFFSET_FILL); + qglDisable(GL_POLYGON_OFFSET_FILL); + + GLboolean scissor = qglIsEnabled(GL_SCISSOR_TEST); + qglDisable(GL_SCISSOR_TEST); + + GLboolean stencil = qglIsEnabled(GL_STENCIL_TEST); + qglDisable(GL_STENCIL_TEST); + + GLboolean texture = qglIsEnabled(GL_TEXTURE_2D); + qglEnable(GL_TEXTURE_2D); + + qglMatrixMode(GL_MODELVIEW); + qglLoadIdentity(); + qglMatrixMode(GL_PROJECTION); + qglLoadIdentity(); +#ifdef _XBOX + if(glw_state->isWidescreen) + qglOrtho(0, 720, 0, 480, 0, 1); + else +#endif + qglOrtho(0, 640, 0, 480, 0, 1); + + qglMatrixMode(GL_TEXTURE0); + qglLoadIdentity(); + qglMatrixMode(GL_TEXTURE1); + qglLoadIdentity(); + + qglActiveTextureARB(GL_TEXTURE0_ARB); + qglClientActiveTextureARB(GL_TEXTURE0_ARB); + + memset(&tess, 0, sizeof(tess)); + + // Draw the error message + qglBeginFrame(); + + if (!SP_LicenseDone) + { + // clear the screen if we haven't done the + // license yet... + qglClearColor(0, 0, 0, 1); + qglClear(GL_COLOR_BUFFER_BIT); + } + + float x1, x2, y1, y2; +#ifdef _XBOX + if(glw_state->isWidescreen) + { + x1 = 0; + x2 = 720; + y1 = 0; + y2 = 480; + } + else { +#endif + x1 = 0; + x2 = 640; + y1 = 0; + y2 = 480; +#ifdef _XBOX + } +#endif + + y1 += vShift; + y2 += vShift; + + qglBeginEXT (GL_TRIANGLE_STRIP, 4, 0, 0, 4, 0); + qglTexCoord2f( 0, 0 ); + qglVertex2f(x1, y1); + qglTexCoord2f( 1 , 0 ); + qglVertex2f(x2, y1); + qglTexCoord2f( 0, 1 ); + qglVertex2f(x1, y2); + qglTexCoord2f( 1, 1 ); + qglVertex2f(x2, y2); + qglEnd(); + + qglEndFrame(); + qglFlush(); + + // Restore (most) of the render states we reset + if (alpha) qglEnable(GL_ALPHA_TEST); + else qglDisable(GL_ALPHA_TEST); + + if (blend) qglEnable(GL_BLEND); + else qglDisable(GL_BLEND); + + if (cull) qglEnable(GL_CULL_FACE); + else qglDisable(GL_CULL_FACE); + + if (depth) qglEnable(GL_DEPTH_TEST); + else qglDisable(GL_DEPTH_TEST); + + if (fog) qglEnable(GL_FOG); + else qglDisable(GL_FOG); + + if (lighting) qglEnable(GL_LIGHTING); + else qglDisable(GL_LIGHTING); + + if (offset) qglEnable(GL_POLYGON_OFFSET_FILL); + else qglDisable(GL_POLYGON_OFFSET_FILL); + + if (scissor) qglEnable(GL_SCISSOR_TEST); + else qglDisable(GL_SCISSOR_TEST); + + if (stencil) qglEnable(GL_STENCIL_TEST); + else qglDisable(GL_STENCIL_TEST); + + if (texture) qglEnable(GL_TEXTURE_2D); + else qglDisable(GL_TEXTURE_2D); + + // Kill the texture + qglDeleteTextures(1, &texid); +} + + +/********* +SP_GetLanguageExt + +Retuns the extension for the current language, or +english if the language is unknown. +*********/ +char* SP_GetLanguageExt() +{ + switch(XGetLanguage()) + { + case XC_LANGUAGE_ENGLISH: + return "EN"; +// case XC_LANGUAGE_JAPANESE: +// return "JA"; + case XC_LANGUAGE_GERMAN: + return "GE"; +// case XC_LANGUAGE_SPANISH: +// return "SP"; +// case XC_LANGUAGE_ITALIAN: +// return "IT"; +// case XC_LANGUAGE_KOREAN: +// return "KO"; +// case XC_LANGUAGE_TCHINESE: +// return "CH"; +// case XC_LANGUAGE_PORTUGUESE: +// return "PO"; + case XC_LANGUAGE_FRENCH: + return "FR"; + default: + return "EN"; + } +} + +/********* +SP_LoadFileWithLanguage + +Loads a screen with the appropriate language +*********/ +void *SP_LoadFileWithLanguage(const char *name) +{ + char fullname[MAX_QPATH]; + void *buffer = NULL; + char *ext; + + // get the language extension + ext = SP_GetLanguageExt(); + + // creat the fullpath name and try to load the texture + sprintf(fullname, "%s_%s.dds", name, ext); + buffer = SP_LoadFile(fullname); + + if (!buffer) + { + sprintf(fullname, "%s.dds", name); + buffer = SP_LoadFile(fullname); + } + + return buffer; +} + +/********* +SP_LoadFile +*********/ +void* SP_LoadFile(const char* name) +{ + wfhandle_t h = WF_Open(name, true, false); + if (h < 0) return NULL; + + if (WF_Seek(0, SEEK_END, h)) + { + WF_Close(h); + return NULL; + } + + int len = WF_Tell(h); + + if (WF_Seek(0, SEEK_SET, h)) + { + WF_Close(h); + return NULL; + } + + void *buf = Z_Malloc(len, TAG_TEMP_WORKSPACE, false, 32); + + if (WF_Read(buf, len, h) != len) + { + Z_Free(buf); + WF_Close(h); + return NULL; + } + + WF_Close(h); + + return buf; +} + +/******** +SP_DoLicense + +Draws the license splash to the screen +*********/ +void SP_DoLicense(void) +{ + if( Sys_QuickStart() ) + return; + + // Load the license screen + void *license; + extern const char *Sys_RemapPath( const char *filename ); + license = SP_LoadFileWithLanguage( Sys_RemapPath( "base\\media\\LicenseScreen" ) ); + + if (license) + { + SP_DrawTexture(license, 512, 512, 0); + Z_Free(license); + } + + SP_LicenseDone = true; +} + +/* +SP_DrawMPLoadScreen + +Draws the Multiplayer loading screen +*/ +void SP_DrawMPLoadScreen( void ) +{ + // Load the texture: + void *image = SP_LoadFileWithLanguage("d:\\base\\media\\LoadMP"); + + if( image ) + { + SP_DrawTexture(image, 512, 512, 0); + Z_Free(image); + } +} + +/* +SP_DrawSPLoadScreen + +Draws the single player loading screen - used when skipping the logo movies +*/ +void SP_DrawSPLoadScreen( void ) +{ + // Load the texture: + extern const char *Sys_RemapPath( const char *filename ); + void *image = SP_LoadFileWithLanguage( Sys_RemapPath("base\\media\\LoadSP") ); + + if( image ) + { + SP_DrawTexture(image, 512, 512, 0); + Z_Free(image); + } +} + +/* +ERR_DiscFail + +Draws the damaged/dirty disc message, looping forever +*/ +void ERR_DiscFail(bool poll) +{ + // Load the texture: + extern const char *Sys_RemapPath( const char *filename ); + void *image = SP_LoadFileWithLanguage( Sys_RemapPath("base\\media\\DiscErr") ); + + if( image ) + { + SP_DrawTexture(image, 512, 512, 0); + Z_Free(image); + } + + for (;;) + { + extern void MuteBinkSystem(void); + MuteBinkSystem(); + + extern void S_Update_(void); + S_Update_(); + } +} diff --git a/code/ui/ui_splash.h b/code/ui/ui_splash.h new file mode 100644 index 0000000..5b98d6b --- /dev/null +++ b/code/ui/ui_splash.h @@ -0,0 +1,11 @@ +#ifndef __UI_SPLASH_H +#define __UI_SPLASH_H + +void SP_DisplayLogos(void); +void SP_DoLicense(void); +void* SP_LoadFile(const char* name); +void* SP_LoadFileWithLanguage(const char *name); +char* SP_GetLanguageExt(); +void SP_DrawTexture(void* pixels, float width, float height, float vShift); + +#endif diff --git a/code/ui/ui_syscalls.cpp b/code/ui/ui_syscalls.cpp new file mode 100644 index 0000000..5853873 --- /dev/null +++ b/code/ui/ui_syscalls.cpp @@ -0,0 +1,177 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// leave this at the top of all UI_xxxx files for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "ui_local.h" + +#ifdef _IMMERSION +#include "../ff/ff.h" +#endif // _IMMERSION + +// this file is only included when building a dll +// syscalls.asm is included instead when building a qvm + +static int (*syscall)( int arg, ... ) = (int (*)( int, ...))-1; + +void dllEntry( int (*syscallptr)( int arg,... ) ) { + syscall = syscallptr; +// CG_PreInit(); +} + +inline int PASSFLOAT( float x ) +{ + float floatTemp; + floatTemp = x; + return *(int *)&floatTemp; +} + +int CL_UISystemCalls( int *args ); + + +int FloatAsInt( float f ); + +float trap_Cvar_VariableValue( const char *var_name ) +{ + int temp; +// temp = syscall( UI_CVAR_VARIABLEVALUE, var_name ); + temp = FloatAsInt( Cvar_VariableValue(var_name) ); + return (*(float*)&temp); +} + + +void trap_R_ClearScene( void ) +{ + ui.R_ClearScene(); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) +{ + ui.R_AddRefEntityToScene(re); +} + +void trap_R_RenderScene( const refdef_t *fd ) +{ +// syscall( UI_R_RENDERSCENE, fd ); + ui.R_RenderScene(fd); +} + +void trap_R_SetColor( const float *rgba ) +{ +// syscall( UI_R_SETCOLOR, rgba ); +// re.SetColor( rgba ); + ui.R_SetColor(rgba); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ) +{ +// syscall( UI_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); +// re.DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); + + ui.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); + +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) +{ +// syscall( UI_R_MODELBOUNDS, model, mins, maxs ); + ui.R_ModelBounds(model, mins, maxs); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) +{ +// syscall( UI_S_STARTLOCALSOUND, sfx, channelNum ); + S_StartLocalSound( sfx, channelNum ); +} + + +void trap_S_StopSounds( void ) +{ + S_StopSounds( ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) +{ + return S_RegisterSound(sample); +} + +#ifdef _IMMERSION + +void trap_FF_Start( ffHandle_t ff ) +{ + FF_AddForce( ff ); +} + +ffHandle_t trap_FF_Register( const char *name, int channel ) +{ + return FF_Register( name, channel, qtrue ); +} + +#endif // _IMMERSION + +void trap_Key_SetBinding( int keynum, const char *binding ) +{ + Key_SetBinding( keynum, binding); +} + +qboolean trap_Key_GetOverstrikeMode( void ) +{ + return Key_GetOverstrikeMode(); +} + +void trap_Key_SetOverstrikeMode( qboolean state ) +{ + Key_SetOverstrikeMode( state ); +} + +void trap_Key_ClearStates( void ) +{ + Key_ClearStates(); +} + +int Key_GetCatcher( void ); + +int trap_Key_GetCatcher( void ) +{ + return Key_GetCatcher(); +} + +void Key_SetCatcher( int catcher ); + +void trap_Key_SetCatcher( int catcher ) +{ + Key_SetCatcher( catcher ); +} +/* +void trap_GetClipboardData( char *buf, int bufsize ) { + syscall( UI_GETCLIPBOARDDATA, buf, bufsize ); +} + +void trap_GetClientState( uiClientState_t *state ) { + syscall( UI_GETCLIENTSTATE, state ); +} +*/ + +void CL_GetGlconfig( glconfig_t *glconfig ); + +void trap_GetGlconfig( glconfig_t *glconfig ) +{ +// syscall( UI_GETGLCONFIG, glconfig ); + CL_GetGlconfig( glconfig ); +} + +#ifndef _XBOX +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits, const char *psAudioFile) { + return syscall(UI_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits, psAudioFile); +} +#endif + +// stops playing the cinematic and ends it. should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +int trap_CIN_StopCinematic(int handle) +{ + return syscall(UI_CIN_STOPCINEMATIC, handle); +} + diff --git a/code/unix/Makefile b/code/unix/Makefile new file mode 100644 index 0000000..c42db1d --- /dev/null +++ b/code/unix/Makefile @@ -0,0 +1,988 @@ +# +# Quake3 Unix Makefile +# +# Currently build for the following: +# Linux i386 (full client) +# Linux Alpha (dedicated server only) +# SGI IRIX (full client) +# +# Nov '98 by Zoid +# +# GNU Make required +# + +PLATFORM=$(shell uname|tr '[:upper:]' '[:lower:]') +PLATFORM_RELEASE=$(shell uname -r) + +### +### These paths are where you probably want to change things +### + +# Where we are building to, libMesaVoodooGL.so.3.1 should be here, etc. +# the demo pk3 file should be here in demoq3/pak0.pk3 or baseq3/pak0.pk3 +BUILD_DIR=/home/zoid/Quake3 + +# Where we are building from (where the source code should be!) +MOUNT_DIR=/devel/Quake3/q3code + +############################################################################# +## +## You shouldn't have to touch anything below here +## +############################################################################# + +DEMO_PAK=$(BUILD_DIR)/demoq3/pak0.pk3 + +BUILD_DEBUG_DIR=debug$(ARCH)$(GLIBC) +BUILD_RELEASE_DIR=release$(ARCH)$(GLIBC) +CLIENT_DIR=$(MOUNT_DIR)/client +SERVER_DIR=$(MOUNT_DIR)/server +REF_DIR=$(MOUNT_DIR)/renderer +COMMON_DIR=$(MOUNT_DIR)/qcommon +UNIX_DIR=$(MOUNT_DIR)/unix +GAME_DIR=$(MOUNT_DIR)/game +CGAME_DIR=$(MOUNT_DIR)/cgame +NULL_DIR=$(MOUNT_DIR)/null + +#used for linux i386 builds +MESA_DIR=/usr/local/src/Mesa-3.0 + +VERSION=1.05 + +VERSION_FN=$(VERSION)$(GLIBC) +RPM_RELEASE=9 + +############################################################################# +# SETUP AND BUILD +############################################################################# + +ifeq ($(PLATFORM),irix) + +ARCH=mips #default to MIPS +VENDOR=sgi +GLIBC= #libc is irrelevant + +CC=cc +BASE_CFLAGS=-Dstricmp=strcasecmp -Xcpluscomm -woff 1185 -mips3 \ + -nostdinc -I. -I$(ROOT)/usr/include +RELEASE_CFLAGS=$(BASE_CFLAGS) -O3 +DEBUG_CFLAGS=$(BASE_CFLAGS) -g + +SHLIBEXT=so +SHLIBCFLAGS= +SHLIBLDFLAGS=-shared + +LDFLAGS=-ldl -lm +GLLDFLAGS=-L/usr/X11/lib -lGL -lX11 -lXext -lm + +TARGETS=$(BUILDDIR)/sgiquake3 \ + $(BUILDDIR)/qagame$(ARCH).$(SHLIBEXT) \ + $(BUILDDIR)/cgame$(ARCH).$(SHLIBEXT) + +else +ifeq ($(PLATFORM),linux) + +ifneq (,$(findstring libc6,$(shell if [ -e /lib/libc.so.6* ];then echo libc6;fi))) +GLIBC=-glibc +else +GLIBC= +endif #libc6 test + + +ifneq (,$(findstring alpha,$(shell uname -m))) +ARCH=axp +RPMARCH=alpha +VENDOR=dec +else #default to i386 +ARCH=i386 +RPMARCH=i386 +VENDOR=unknown +endif #alpha test + +BASE_CFLAGS=-Dstricmp=strcasecmp -I$(MESA_DIR)/include -I/usr/include/glide \ + -DREF_HARD_LINKED -pipe + +DEBUG_CFLAGS=$(BASE_CFLAGS) -g +ifeq ($(ARCH),axp) +RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O6 -ffast-math -funroll-loops -fomit-frame-pointer -fexpensive-optimizations +else +RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O3 -m486 -fomit-frame-pointer -pipe -ffast-math -fexpensive-optimizations -malign-loops=2 -malign-jumps=2 -malign-functions=2 +endif + +CC=gcc + +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +LDFLAGS=-ldl -lm +GLLDFLAGS=-L/usr/X11R6/lib -L$(MESA_DIR)/lib -lX11 -lXext -lXxf86dga -lXxf86vm +GL_SVGA_LDFLAGS=-L/usr/X11R6/lib -L$(MESA_DIR)/lib -lX11 -lXext -lvga + +ifeq ($(ARCH),axp) +TARGETS=$(BUILDDIR)/linuxq3ded $(BUILDDIR)/qagame$(ARCH).$(SHLIBEXT) +else +# $(BUILDDIR)/linuxquake3.vga +TARGETS=\ + $(BUILDDIR)/linuxquake3 \ + $(BUILDDIR)/qagame$(ARCH).$(SHLIBEXT) \ + $(BUILDDIR)/cgame$(ARCH).$(SHLIBEXT) +#$(BUILDDIR)/quake3 +endif + + +else #generic + +CC=cc +BASE_CFLAGS=-Dstricmp=strcasecmp +DEBUG_CFLAGS=$(BASE_CFLAGS) -g +RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O + +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +LDFLAGS=-ldl -lm + +TARGETS=$(BUILDDIR)/q3ded $(BUILDDIR)/qagame$(ARCH).$(SHLIBEXT) + +endif #Linux +endif #IRIX + +DO_CC=$(CC) $(CFLAGS) -o $@ -c $< +DO_DEBUG_CC=$(CC) $(DEBUG_CFLAGS) -o $@ -c $< +DO_SHLIB_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_SHLIB_DEBUG_CC=$(CC) $(DEBUG_CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< +DO_AS=$(CC) $(CFLAGS) -DELF -x assembler-with-cpp -o $@ -c $< + +#### DEFAULT TARGET +# default: build_release + +build_debug: + $(MAKE) targets BUILDDIR=$(BUILD_DEBUG_DIR) CFLAGS="$(DEBUG_CFLAGS)" + +build_release: + $(MAKE) targets BUILDDIR=$(BUILD_RELEASE_DIR) CFLAGS="$(RELEASE_CFLAGS)" + +#Build both debug and release builds +all: build_debug build_release + +targets: makedirs $(TARGETS) + +makedirs: + @if [ ! -d $(BUILDDIR) ];then mkdir $(BUILDDIR);fi + @if [ ! -d $(BUILDDIR)/client ];then mkdir $(BUILDDIR)/client;fi + @if [ ! -d $(BUILDDIR)/ded ];then mkdir $(BUILDDIR)/ded;fi + @if [ ! -d $(BUILDDIR)/ref ];then mkdir $(BUILDDIR)/ref;fi + @if [ ! -d $(BUILDDIR)/game ];then mkdir $(BUILDDIR)/game;fi + @if [ ! -d $(BUILDDIR)/cgame ];then mkdir $(BUILDDIR)/cgame;fi + +############################################################################# +# CLIENT/SERVER +############################################################################# + +QUAKE3_OBJS = \ + $(BUILDDIR)/client/cl_cgame.o \ + $(BUILDDIR)/client/cl_cin.o \ + $(BUILDDIR)/client/cl_console.o \ + $(BUILDDIR)/client/cl_input.o \ + $(BUILDDIR)/client/cl_keys.o \ + $(BUILDDIR)/client/cl_main.o \ + $(BUILDDIR)/client/cl_parse.o \ + $(BUILDDIR)/client/cl_scrn.o \ + $(BUILDDIR)/client/snd_dma.o \ + $(BUILDDIR)/client/snd_mem.o \ + $(BUILDDIR)/client/snd_mix.o \ + $(BUILDDIR)/client/sv_bot.o \ + $(BUILDDIR)/client/sv_client.o \ + $(BUILDDIR)/client/sv_ccmds.o \ + $(BUILDDIR)/client/sv_game.o \ + $(BUILDDIR)/client/sv_init.o \ + $(BUILDDIR)/client/sv_main.o \ + $(BUILDDIR)/client/sv_snapshot.o \ + $(BUILDDIR)/client/sv_world.o \ + $(BUILDDIR)/client/ui_arena.o \ + $(BUILDDIR)/client/ui_connect.o \ + $(BUILDDIR)/client/ui_controls.o \ + $(BUILDDIR)/client/ui_demo.o \ + $(BUILDDIR)/client/ui_maps.o \ + $(BUILDDIR)/client/ui_menu.o \ + $(BUILDDIR)/client/ui_network.o \ + $(BUILDDIR)/client/ui_preferences.o \ + $(BUILDDIR)/client/ui_qmenu.o \ + $(BUILDDIR)/client/ui_servers.o \ + $(BUILDDIR)/client/ui_startserver.o \ + $(BUILDDIR)/client/ui_video.o \ + \ + $(BUILDDIR)/client/cm_load.o \ + $(BUILDDIR)/client/cm_patch.o \ + $(BUILDDIR)/client/cm_polylib.o \ + $(BUILDDIR)/client/cm_tag.o \ + $(BUILDDIR)/client/cm_test.o \ + $(BUILDDIR)/client/cm_trace.o \ + $(BUILDDIR)/client/cmd.o \ + $(BUILDDIR)/client/common.o \ + $(BUILDDIR)/client/cvar.o \ + $(BUILDDIR)/client/files.o \ + $(BUILDDIR)/client/gameinfo.o \ + $(BUILDDIR)/client/md4.o \ + $(BUILDDIR)/client/msg.o \ + $(BUILDDIR)/client/net_chan.o \ + $(BUILDDIR)/client/vm.o \ + \ + $(BUILDDIR)/client/q_shared.o \ + $(BUILDDIR)/client/q_math.o \ + \ + $(BUILDDIR)/client/tr_backend.o \ + $(BUILDDIR)/client/tr_bsp.o \ + $(BUILDDIR)/client/tr_calc.o \ + $(BUILDDIR)/client/tr_calc_3dnow.o \ + $(BUILDDIR)/client/tr_calc_c.o \ + $(BUILDDIR)/client/tr_calc_kni.o \ + $(BUILDDIR)/client/tr_cmds.o \ + $(BUILDDIR)/client/tr_curve.o \ + $(BUILDDIR)/client/tr_draw.o \ + $(BUILDDIR)/client/tr_flares.o \ + $(BUILDDIR)/client/tr_image.o \ + $(BUILDDIR)/client/tr_init.o \ + $(BUILDDIR)/client/tr_light.o \ + $(BUILDDIR)/client/tr_main.o \ + $(BUILDDIR)/client/tr_mesh.o \ + $(BUILDDIR)/client/tr_misc.o \ + $(BUILDDIR)/client/tr_model.o \ + $(BUILDDIR)/client/tr_noise.o \ + $(BUILDDIR)/client/tr_scene.o \ + $(BUILDDIR)/client/tr_shade.o \ + $(BUILDDIR)/client/tr_shader.o \ + $(BUILDDIR)/client/tr_shade_calc.o \ + $(BUILDDIR)/client/tr_shadows.o \ + $(BUILDDIR)/client/tr_smp.o \ + $(BUILDDIR)/client/tr_sky.o \ + $(BUILDDIR)/client/tr_surf.o \ + $(BUILDDIR)/client/tr_world.o \ + \ + $(BUILDDIR)/client/unix_main.o \ + $(BUILDDIR)/client/unix_net.o \ + $(BUILDDIR)/client/unix_shared.o + +#platform specific objects +ifeq ($(PLATFORM),irix) + QUAKE3_PLATOBJ=\ + $(BUILDDIR)/client/irix_qgl.o \ + $(BUILDDIR)/client/irix_glimp.o \ + $(BUILDDIR)/client/irix_snd.o +else +ifeq ($(PLATFORM),linux) +ifeq ($(ARCH),axp) + QUAKE3_PLATOBJ= +else + QUAKE3_PLATOBJ=\ + $(BUILDDIR)/client/linux_qgl.o \ + $(BUILDDIR)/client/linux_glimp.o \ + $(BUILDDIR)/client/linux_snd.o \ + $(BUILDDIR)/client/snd_mixa.o \ + $(BUILDDIR)/client/matha.o \ + $(BUILDDIR)/client/sys_dosa.o + QUAKE3_VGA=\ + $(BUILDDIR)/client/linux_qgl.o \ + $(BUILDDIR)/client/linux_glimp_vga.o \ + $(BUILDDIR)/client/linux_input_vga.o \ + $(BUILDDIR)/client/linux_snd.o \ + $(BUILDDIR)/client/snd_mixa.o \ + $(BUILDDIR)/client/matha.o \ + $(BUILDDIR)/client/sys_dosa.o + +endif +endif #Linux +endif #IRIX + + +$(BUILDDIR)/linuxquake3 : $(QUAKE3_OBJS) $(QUAKE3_PLATOBJ) + $(CC) $(CFLAGS) -o $@ $(QUAKE3_OBJS) $(QUAKE3_PLATOBJ) $(GLLDFLAGS) $(LDFLAGS) + +ifeq ($(PLATFORM),linux) +ifeq ($(ARCH),i386) +$(BUILDDIR)/linuxquake3.vga : $(QUAKE3_OBJS) $(QUAKE3_VGA) + $(CC) $(CFLAGS) -o $@ $(QUAKE3_OBJS) $(QUAKE3_VGA) $(GL_SVGA_LDFLAGS) $(LDFLAGS) +endif +endif + +$(BUILDDIR)/client/cl_cgame.o : $(CLIENT_DIR)/cl_cgame.c + $(DO_CC) + +$(BUILDDIR)/client/cl_cin.o : $(CLIENT_DIR)/cl_cin.c + $(DO_CC) + +$(BUILDDIR)/client/cl_console.o : $(CLIENT_DIR)/cl_console.c + $(DO_CC) + +$(BUILDDIR)/client/cl_input.o : $(CLIENT_DIR)/cl_input.c + $(DO_CC) + +$(BUILDDIR)/client/cl_keys.o : $(CLIENT_DIR)/cl_keys.c + $(DO_CC) + +$(BUILDDIR)/client/cl_main.o : $(CLIENT_DIR)/cl_main.c + $(DO_CC) + +$(BUILDDIR)/client/cl_parse.o : $(CLIENT_DIR)/cl_parse.c + $(DO_CC) + +$(BUILDDIR)/client/cl_scrn.o : $(CLIENT_DIR)/cl_scrn.c + $(DO_CC) + +$(BUILDDIR)/client/snd_dma.o : $(CLIENT_DIR)/snd_dma.c + $(DO_CC) + +$(BUILDDIR)/client/snd_mem.o : $(CLIENT_DIR)/snd_mem.c + $(DO_CC) + +$(BUILDDIR)/client/snd_mix.o : $(CLIENT_DIR)/snd_mix.c + $(DO_CC) + +$(BUILDDIR)/client/sv_bot.o : $(SERVER_DIR)/sv_bot.c + $(DO_CC) + +$(BUILDDIR)/client/sv_client.o : $(SERVER_DIR)/sv_client.c + $(DO_CC) + +$(BUILDDIR)/client/sv_ccmds.o : $(SERVER_DIR)/sv_ccmds.c + $(DO_CC) + +$(BUILDDIR)/client/sv_game.o : $(SERVER_DIR)/sv_game.c + $(DO_CC) + +$(BUILDDIR)/client/sv_init.o : $(SERVER_DIR)/sv_init.c + $(DO_CC) + +$(BUILDDIR)/client/sv_main.o : $(SERVER_DIR)/sv_main.c + $(DO_CC) + +$(BUILDDIR)/client/sv_snapshot.o : $(SERVER_DIR)/sv_snapshot.c + $(DO_CC) + +$(BUILDDIR)/client/sv_world.o : $(SERVER_DIR)/sv_world.c + $(DO_CC) + +$(BUILDDIR)/client/ui_arena.o : $(CLIENT_DIR)/ui_arena.c + $(DO_CC) + +$(BUILDDIR)/client/ui_connect.o : $(CLIENT_DIR)/ui_connect.c + $(DO_CC) + +$(BUILDDIR)/client/ui_controls.o : $(CLIENT_DIR)/ui_controls.c + $(DO_CC) + +$(BUILDDIR)/client/ui_demo.o : $(CLIENT_DIR)/ui_demo.c + $(DO_CC) + +$(BUILDDIR)/client/ui_maps.o : $(CLIENT_DIR)/ui_maps.c + $(DO_CC) + +$(BUILDDIR)/client/ui_menu.o : $(CLIENT_DIR)/ui_menu.c + $(DO_CC) + +$(BUILDDIR)/client/ui_network.o : $(CLIENT_DIR)/ui_network.c + $(DO_CC) + +$(BUILDDIR)/client/ui_preferences.o : $(CLIENT_DIR)/ui_preferences.c + $(DO_CC) + +$(BUILDDIR)/client/ui_qmenu.o : $(CLIENT_DIR)/ui_qmenu.c + $(DO_CC) + +$(BUILDDIR)/client/ui_servers.o : $(CLIENT_DIR)/ui_servers.c + $(DO_CC) + +$(BUILDDIR)/client/ui_startserver.o : $(CLIENT_DIR)/ui_startserver.c + $(DO_CC) + +$(BUILDDIR)/client/ui_video.o : $(UNIX_DIR)/ui_video.c + $(DO_CC) + +$(BUILDDIR)/client/cm_trace.o : $(COMMON_DIR)/cm_trace.c + $(DO_CC) + +$(BUILDDIR)/client/cm_load.o : $(COMMON_DIR)/cm_load.c + $(DO_CC) + +$(BUILDDIR)/client/cm_test.o : $(COMMON_DIR)/cm_test.c + $(DO_CC) + +$(BUILDDIR)/client/cm_patch.o : $(COMMON_DIR)/cm_patch.c + $(DO_CC) + +$(BUILDDIR)/client/cm_polylib.o : $(COMMON_DIR)/cm_polylib.c + $(DO_CC) + +$(BUILDDIR)/client/cm_tag.o : $(COMMON_DIR)/cm_tag.c + $(DO_CC) + +$(BUILDDIR)/client/cmd.o : $(COMMON_DIR)/cmd.c + $(DO_CC) + +$(BUILDDIR)/client/common.o : $(COMMON_DIR)/common.c + $(DO_CC) + +$(BUILDDIR)/client/cvar.o : $(COMMON_DIR)/cvar.c + $(DO_CC) + +$(BUILDDIR)/client/files.o : $(COMMON_DIR)/files.c + $(DO_CC) + +$(BUILDDIR)/client/gameinfo.o : $(COMMON_DIR)/gameinfo.c + $(DO_CC) + +$(BUILDDIR)/client/md4.o : $(COMMON_DIR)/md4.c + $(DO_CC) + +$(BUILDDIR)/client/msg.o : $(COMMON_DIR)/msg.c + $(DO_CC) + +$(BUILDDIR)/client/net_chan.o : $(COMMON_DIR)/net_chan.c + $(DO_CC) + +$(BUILDDIR)/client/vm.o : $(COMMON_DIR)/vm.c + $(DO_CC) + +$(BUILDDIR)/client/q_shared.o : $(GAME_DIR)/q_shared.c + $(DO_DEBUG_CC) + +$(BUILDDIR)/client/q_math.o : $(GAME_DIR)/q_math.c + $(DO_CC) + +$(BUILDDIR)/client/tr_bsp.o : $(REF_DIR)/tr_bsp.c + $(DO_CC) + +$(BUILDDIR)/client/tr_backend.o : $(REF_DIR)/tr_backend.c + $(DO_CC) + +$(BUILDDIR)/client/tr_calc.o : $(REF_DIR)/tr_calc.c + $(DO_CC) + +$(BUILDDIR)/client/tr_calc_3dnow.o : $(REF_DIR)/tr_calc_3dnow.c + $(DO_CC) + +$(BUILDDIR)/client/tr_calc_c.o : $(REF_DIR)/tr_calc_c.c + $(DO_CC) + +$(BUILDDIR)/client/tr_calc_kni.o : $(REF_DIR)/tr_calc_kni.c + $(DO_CC) + +$(BUILDDIR)/client/tr_cmds.o : $(REF_DIR)/tr_cmds.c + $(DO_CC) + +$(BUILDDIR)/client/tr_curve.o : $(REF_DIR)/tr_curve.c + $(DO_CC) + +$(BUILDDIR)/client/tr_draw.o : $(REF_DIR)/tr_draw.c + $(DO_CC) + +$(BUILDDIR)/client/tr_flares.o : $(REF_DIR)/tr_flares.c + $(DO_CC) + +$(BUILDDIR)/client/tr_image.o : $(REF_DIR)/tr_image.c + $(DO_CC) + +$(BUILDDIR)/client/tr_init.o : $(REF_DIR)/tr_init.c + $(DO_CC) + +$(BUILDDIR)/client/tr_light.o : $(REF_DIR)/tr_light.c + $(DO_CC) + +$(BUILDDIR)/client/tr_main.o : $(REF_DIR)/tr_main.c + $(DO_CC) + +$(BUILDDIR)/client/tr_mesh.o : $(REF_DIR)/tr_mesh.c + $(DO_CC) + +$(BUILDDIR)/client/tr_misc.o : $(REF_DIR)/tr_misc.c + $(DO_CC) + +$(BUILDDIR)/client/tr_model.o : $(REF_DIR)/tr_model.c + $(DO_CC) + +$(BUILDDIR)/client/tr_noise.o : $(REF_DIR)/tr_noise.c + $(DO_CC) + +$(BUILDDIR)/client/tr_scene.o : $(REF_DIR)/tr_scene.c + $(DO_CC) + +$(BUILDDIR)/client/tr_shade.o : $(REF_DIR)/tr_shade.c + $(DO_CC) + +$(BUILDDIR)/client/tr_shader.o : $(REF_DIR)/tr_shader.c + $(DO_CC) + +$(BUILDDIR)/client/tr_shade_calc.o : $(REF_DIR)/tr_shade_calc.c + $(DO_CC) + +$(BUILDDIR)/client/tr_shadows.o : $(REF_DIR)/tr_shadows.c + $(DO_CC) + +$(BUILDDIR)/client/tr_sky.o : $(REF_DIR)/tr_sky.c + $(DO_CC) + +$(BUILDDIR)/client/tr_smp.o : $(REF_DIR)/tr_smp.c + $(DO_CC) + +$(BUILDDIR)/client/tr_stripify.o : $(REF_DIR)/tr_stripify.c + $(DO_CC) + +$(BUILDDIR)/client/tr_subdivide.o : $(REF_DIR)/tr_subdivide.c + $(DO_CC) + +$(BUILDDIR)/client/tr_surf.o : $(REF_DIR)/tr_surf.c + $(DO_CC) + +$(BUILDDIR)/client/tr_world.o : $(REF_DIR)/tr_world.c + $(DO_CC) + +$(BUILDDIR)/client/unix_qgl.o : $(UNIX_DIR)/unix_qgl.c + $(DO_CC) + +$(BUILDDIR)/client/unix_dedicated.o : $(UNIX_DIR)/unix_dedicated.c + $(DO_CC) + +$(BUILDDIR)/client/unix_earlycon.o : $(UNIX_DIR)/unix_earlycon.c + $(DO_CC) + +$(BUILDDIR)/client/unix_main.o : $(UNIX_DIR)/unix_main.c + $(DO_CC) + +$(BUILDDIR)/client/unix_net.o : $(UNIX_DIR)/unix_net.c + $(DO_CC) + +$(BUILDDIR)/client/unix_shared.o : $(UNIX_DIR)/unix_shared.c + $(DO_CC) + +$(BUILDDIR)/client/irix_glimp.o : $(UNIX_DIR)/irix_glimp.c + $(DO_CC) + +$(BUILDDIR)/client/irix_snd.o : $(UNIX_DIR)/irix_snd.c + $(DO_CC) + +$(BUILDDIR)/client/irix_input.o : $(UNIX_DIR)/irix_input.c + $(DO_CC) + +$(BUILDDIR)/client/linux_glimp.o : $(UNIX_DIR)/linux_glimp.c + $(DO_CC) + +$(BUILDDIR)/client/linux_qgl.o : $(UNIX_DIR)/linux_qgl.c + $(DO_CC) + +$(BUILDDIR)/client/linux_input.o : $(UNIX_DIR)/linux_input.c + $(DO_CC) + +$(BUILDDIR)/client/linux_glimp_vga.o : $(UNIX_DIR)/linux_glimp_vga.c + $(DO_CC) + +$(BUILDDIR)/client/linux_snd.o : $(UNIX_DIR)/linux_snd.c + $(DO_CC) + +$(BUILDDIR)/client/snd_mixa.o : $(UNIX_DIR)/snd_mixa.s + $(DO_AS) + +$(BUILDDIR)/client/matha.o : $(UNIX_DIR)/matha.s + $(DO_AS) + +$(BUILDDIR)/client/sys_dosa.o : $(UNIX_DIR)/sys_dosa.s + $(DO_AS) + +$(BUILDDIR)/client/linux_input_vga.o : $(UNIX_DIR)/linux_input_vga.c + $(DO_CC) + +############################################################################# +# DEDICATED SERVER +############################################################################# + +Q3DED_OBJS = \ + $(BUILDDIR)/ded/sv_bot.o \ + $(BUILDDIR)/ded/sv_client.o \ + $(BUILDDIR)/ded/sv_ccmds.o \ + $(BUILDDIR)/ded/sv_game.o \ + $(BUILDDIR)/ded/sv_init.o \ + $(BUILDDIR)/ded/sv_main.o \ + $(BUILDDIR)/ded/sv_snapshot.o \ + $(BUILDDIR)/ded/sv_world.o \ + \ + $(BUILDDIR)/ded/cm_trace.o \ + $(BUILDDIR)/ded/cm_load.o \ + $(BUILDDIR)/ded/cm_test.o \ + $(BUILDDIR)/ded/cm_patch.o \ + $(BUILDDIR)/ded/cm_tag.o \ + $(BUILDDIR)/ded/cmd.o \ + $(BUILDDIR)/ded/common.o \ + $(BUILDDIR)/ded/cvar.o \ + $(BUILDDIR)/ded/files.o \ + $(BUILDDIR)/ded/gameinfo.o \ + $(BUILDDIR)/ded/md4.o \ + $(BUILDDIR)/ded/msg.o \ + $(BUILDDIR)/ded/net_chan.o \ + \ + $(BUILDDIR)/ded/unix_dedicated.o \ + $(BUILDDIR)/ded/unix_main.o \ + $(BUILDDIR)/ded/unix_net.o \ + $(BUILDDIR)/ded/unix_shared.o \ + \ + $(BUILDDIR)/ded/cl_null.o + +$(BUILDDIR)/linuxq3ded : $(Q3DED_OBJS) + $(CC) $(CFLAGS) -o $@ $(Q3DED_OBJS) $(LDFLAGS) + +$(BUILDDIR)/ded/sv_bot.o : $(SERVER_DIR)/sv_bot.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/sv_client.o : $(SERVER_DIR)/sv_client.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/sv_ccmds.o : $(SERVER_DIR)/sv_ccmds.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/sv_game.o : $(SERVER_DIR)/sv_game.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/sv_init.o : $(SERVER_DIR)/sv_init.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/sv_main.o : $(SERVER_DIR)/sv_main.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/sv_snapshot.o : $(SERVER_DIR)/sv_snapshot.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/sv_world.o : $(SERVER_DIR)/sv_world.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/cm_trace.o : $(COMMON_DIR)/cm_trace.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/cm_load.o : $(COMMON_DIR)/cm_load.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/cm_test.o : $(COMMON_DIR)/cm_test.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/cm_patch.o : $(COMMON_DIR)/cm_patch.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/cm_tag.o : $(COMMON_DIR)/cm_tag.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/cmd.o : $(COMMON_DIR)/cmd.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/common.o : $(COMMON_DIR)/common.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/cvar.o : $(COMMON_DIR)/cvar.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/files.o : $(COMMON_DIR)/files.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/gameinfo.o : $(COMMON_DIR)/gameinfo.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/md4.o : $(COMMON_DIR)/md4.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/msg.o : $(COMMON_DIR)/msg.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/net_chan.o : $(COMMON_DIR)/net_chan.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/unix_dedicated.o : $(UNIX_DIR)/unix_dedicated.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/unix_main.o : $(UNIX_DIR)/unix_main.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/unix_net.o : $(UNIX_DIR)/unix_net.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/unix_shared.o : $(UNIX_DIR)/unix_shared.c + $(DO_DED_CC) + +$(BUILDDIR)/ded/cl_null.o : $(NULL_DIR)/cl_null.c + $(DO_DED_CC) + +############################################################################# +# GAME +############################################################################# + +GAME_OBJS = \ + $(BUILDDIR)/game/b_ai.o \ + $(BUILDDIR)/game/b_files.o \ + $(BUILDDIR)/game/b_items.o \ + $(BUILDDIR)/game/b_main.o \ + $(BUILDDIR)/game/b_nav.o \ + $(BUILDDIR)/game/b_navgen.o \ + $(BUILDDIR)/game/bg_misc.o \ + $(BUILDDIR)/game/bg_pmove.o \ + $(BUILDDIR)/game/g_active.o \ + $(BUILDDIR)/game/g_aim.o \ + $(BUILDDIR)/game/g_client.o \ + $(BUILDDIR)/game/g_cmds.o \ + $(BUILDDIR)/game/g_combat.o \ + $(BUILDDIR)/game/g_items.o \ + $(BUILDDIR)/game/g_main.o \ + $(BUILDDIR)/game/g_mem.o \ + $(BUILDDIR)/game/g_misc.o \ + $(BUILDDIR)/game/g_missile.o \ + $(BUILDDIR)/game/g_mover.o \ + $(BUILDDIR)/game/g_spawn.o \ + $(BUILDDIR)/game/g_svcmds.o \ + $(BUILDDIR)/game/g_target.o \ + $(BUILDDIR)/game/g_team.o \ + $(BUILDDIR)/game/g_trigger.o \ + $(BUILDDIR)/game/g_utils.o \ + $(BUILDDIR)/game/g_weapon.o \ + $(BUILDDIR)/game/q_shared.o \ + $(BUILDDIR)/game/q_math.o + +$(BUILDDIR)/qagame$(ARCH).$(SHLIBEXT) : $(GAME_OBJS) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GAME_OBJS) + +$(BUILDDIR)/game/b_ai.o : $(GAME_DIR)/b_ai.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/b_files.o : $(GAME_DIR)/b_files.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/b_items.o : $(GAME_DIR)/b_items.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/b_main.o : $(GAME_DIR)/b_main.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/b_nav.o : $(GAME_DIR)/b_nav.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/b_navgen.o : $(GAME_DIR)/b_navgen.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/bg_misc.o : $(GAME_DIR)/bg_misc.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/bg_pmove.o : $(GAME_DIR)/bg_pmove.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_active.o : $(GAME_DIR)/g_active.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_aim.o : $(GAME_DIR)/g_aim.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_client.o : $(GAME_DIR)/g_client.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_cmds.o : $(GAME_DIR)/g_cmds.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_combat.o : $(GAME_DIR)/g_combat.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_items.o : $(GAME_DIR)/g_items.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_main.o : $(GAME_DIR)/g_main.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_mem.o : $(GAME_DIR)/g_mem.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_misc.o : $(GAME_DIR)/g_misc.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_missile.o : $(GAME_DIR)/g_missile.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_mover.o : $(GAME_DIR)/g_mover.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_spawn.o : $(GAME_DIR)/g_spawn.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_svcmds.o : $(GAME_DIR)/g_svcmds.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_target.o : $(GAME_DIR)/g_target.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_team.o : $(GAME_DIR)/g_team.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_trigger.o : $(GAME_DIR)/g_trigger.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_utils.o : $(GAME_DIR)/g_utils.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/g_weapon.o : $(GAME_DIR)/g_weapon.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/game/q_shared.o : $(GAME_DIR)/q_shared.c + $(DO_SHLIB_DEBUG_CC) + +$(BUILDDIR)/game/q_math.o : $(GAME_DIR)/q_math.c + $(DO_SHLIB_CC) + +############################################################################# +# CGAME +############################################################################# + +CGAME_OBJS = \ + $(BUILDDIR)/cgame/bg_misc.o \ + $(BUILDDIR)/cgame/bg_pmove.o \ + $(BUILDDIR)/cgame/cg_draw.o \ + $(BUILDDIR)/cgame/cg_effects.o \ + $(BUILDDIR)/cgame/cg_ents.o \ + $(BUILDDIR)/cgame/cg_event.o \ + $(BUILDDIR)/cgame/cg_info.o \ + $(BUILDDIR)/cgame/cg_localents.o \ + $(BUILDDIR)/cgame/cg_main.o \ + $(BUILDDIR)/cgame/cg_marks.o \ + $(BUILDDIR)/cgame/cg_menu.o \ + $(BUILDDIR)/cgame/cg_players.o \ + $(BUILDDIR)/cgame/cg_predict.o \ + $(BUILDDIR)/cgame/cg_scoreboard.o \ + $(BUILDDIR)/cgame/cg_snapshot.o \ + $(BUILDDIR)/cgame/cg_view.o \ + $(BUILDDIR)/cgame/cg_weapons.o \ + $(BUILDDIR)/cgame/q_shared.o \ + $(BUILDDIR)/cgame/q_math.o + +$(BUILDDIR)/cgame$(ARCH).$(SHLIBEXT) : $(CGAME_OBJS) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(CGAME_OBJS) + +$(BUILDDIR)/cgame/bg_misc.o : $(GAME_DIR)/bg_misc.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/bg_pmove.o : $(GAME_DIR)/bg_pmove.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_draw.o : $(CGAME_DIR)/cg_draw.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_effects.o : $(CGAME_DIR)/cg_effects.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_ents.o : $(CGAME_DIR)/cg_ents.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_event.o : $(CGAME_DIR)/cg_event.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_info.o : $(CGAME_DIR)/cg_info.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_localents.o : $(CGAME_DIR)/cg_localents.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_main.o : $(CGAME_DIR)/cg_main.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_marks.o : $(CGAME_DIR)/cg_marks.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_menu.o : $(CGAME_DIR)/cg_menu.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_players.o : $(CGAME_DIR)/cg_players.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_predict.o : $(CGAME_DIR)/cg_predict.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_scoreboard.o : $(CGAME_DIR)/cg_scoreboard.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_snapshot.o : $(CGAME_DIR)/cg_snapshot.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_view.o : $(CGAME_DIR)/cg_view.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/cg_weapons.o : $(CGAME_DIR)/cg_weapons.c + $(DO_SHLIB_CC) + +$(BUILDDIR)/cgame/q_shared.o : $(GAME_DIR)/q_shared.c + $(DO_SHLIB_DEBUG_CC) + +$(BUILDDIR)/cgame/q_math.o : $(GAME_DIR)/q_math.c + $(DO_SHLIB_CC) + + +############################################################################# +# RPM +############################################################################# + +###### DISABLED + +TMPDIR=/var/tmp +TARDIR=$(TMPDIR)/q3test +TARFILE = q3test-$(VERSION_FN)-$(RPM_RELEASE).$(ARCH).tar + +tar: + if [ ! -d archives ];then mkdir archives;chmod 755 archives;fi + $(MAKE) copyfiles COPYDIR=$(TARDIR) + cd $(TARDIR)/..; tar cvf $(TARFILE) q3test && gzip -9 $(TARFILE) + mv $(TARDIR)/../$(TARFILE).gz archives/. + chmod 644 archives/$(TARFILE).gz + rm -rf $(TARDIR) + +# Make RPMs. You need to be root to make this work +RPMROOT=/usr/src/redhat +RPM = rpm +RPMFLAGS = -bb +INSTALLDIR = /usr/local/games/q3test +RPMDIR = $(TMPDIR)/q3test-$(VERSION_FN) +DESTDIR= $(RPMDIR)/$(INSTALLDIR) + +rpm: q3test.spec + touch $(RPMROOT)/SOURCES/q3test-$(VERSION_FN).tar.gz + if [ ! -d archives ];then mkdir archives;fi + $(MAKE) copyfiles COPYDIR=$(DESTDIR) + cp $(UNIX_DIR)/quake3.gif $(RPMROOT)/SOURCES/. + cp q3test.spec $(RPMROOT)/SPECS/. + cd $(RPMROOT)/SPECS; $(RPM) $(RPMFLAGS) q3test.spec + rm -rf $(RPMDIR) + mv $(RPMROOT)/RPMS/$(RPMARCH)/q3test-$(VERSION_FN)-$(RPM_RELEASE).$(RPMARCH).rpm archives/q3test-$(VERSION_FN)-$(RPM_RELEASE).$(RPMARCH).rpm + chmod 644 archives/q3test-$(VERSION_FN)-$(RPM_RELEASE).$(RPMARCH).rpm + +copyfiles: + -mkdirhier $(COPYDIR) + cp $(BUILD_RELEASE_DIR)/linuxquake3 $(COPYDIR) + strip $(COPYDIR)/linuxquake3 + chmod 755 $(COPYDIR)/linuxquake3 + cp $(BUILD_RELEASE_DIR)/qagame$(ARCH).$(SHLIBEXT) $(COPYDIR) + chmod 755 $(COPYDIR)/qagame$(ARCH).$(SHLIBEXT) + cp $(BUILD_RELEASE_DIR)/cgame$(ARCH).$(SHLIBEXT) $(COPYDIR) + chmod 755 $(COPYDIR)/cgame$(ARCH).$(SHLIBEXT) + cp $(BUILD_DIR)/libMesaVoodooGL.so.3.1 $(COPYDIR)/. + chmod 755 $(COPYDIR)/libMesaVoodooGL.so.3.1 + -mkdir $(COPYDIR)/demoq3 + chmod 1777 $(COPYDIR)/demoq3 + cp $(DEMO_PAK) $(COPYDIR)/demoq3/pak0.pk3 + cp $(UNIX_DIR)/README.EULA $(COPYDIR) + chmod 644 $(COPYDIR)/README.EULA + cp $(UNIX_DIR)/README.Q3Test $(COPYDIR) + chmod 644 $(COPYDIR)/README.Q3Test + +q3test.spec : $(UNIX_DIR)/q3test.spec.sh Makefile + sh $< $(VERSION_FN) $(RPM_RELEASE) $(ARCH) $(INSTALLDIR) > $@ + +############################################################################# +# MISC +############################################################################# + +clean: clean-debug clean-release + +clean-debug: + $(MAKE) clean2 BUILDDIR=$(BUILD_DEBUG_DIR) CFLAGS="$(DEBUG_CFLAGS)" + +clean-release: + $(MAKE) clean2 BUILDDIR=$(BUILD_RELEASE_DIR) CFLAGS="$(DEBUG_CFLAGS)" + diff --git a/code/unix/linux_glimp.c b/code/unix/linux_glimp.c new file mode 100644 index 0000000..63e5632 --- /dev/null +++ b/code/unix/linux_glimp.c @@ -0,0 +1,1387 @@ +/* +** GLW_IMP.C +** +** This file contains ALL Linux specific stuff having to do with the +** OpenGL refresh. When a port is being made the following functions +** must be implemented by the port: +** +** GLimp_EndFrame +** GLimp_Init +** GLimp_Shutdown +** GLimp_SwitchFullscreen +** +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#include "../qcommon/qcommon.h" +//#include "../client/keys.h" +#include "../renderer/tr_local.h" +#include "../client/client.h" + +#include "unix_glw.h" + +#include + +#include +#include + +#include +#include + +typedef enum { + RSERR_OK, + + RSERR_INVALID_FULLSCREEN, + RSERR_INVALID_MODE, + + RSERR_UNKNOWN +} rserr_t; + +glwstate_t glw_state; + +static Display *dpy = NULL; +static int scrnum; +static Window win = 0; +static GLXContext ctx = NULL; + +static qboolean autorepeaton = qtrue; + +#define KEY_MASK (KeyPressMask | KeyReleaseMask) +#define MOUSE_MASK (ButtonPressMask | ButtonReleaseMask | \ + PointerMotionMask | ButtonMotionMask ) +#define X_MASK (KEY_MASK | MOUSE_MASK | VisibilityChangeMask | StructureNotifyMask ) + +static qboolean mouse_avail; +static qboolean mouse_active; +static int mx, my; + +static cvar_t *in_mouse; +static cvar_t *in_dgamouse; + +static cvar_t *r_fakeFullscreen; + +qboolean dgamouse = qfalse; +qboolean vidmode_ext = qfalse; + +static int win_x, win_y; + +static XF86VidModeModeInfo **vidmodes; +static int default_dotclock_vidmode; +static int num_vidmodes; +static qboolean vidmode_active = qfalse; + +static int mouse_accel_numerator; +static int mouse_accel_denominator; +static int mouse_threshold; + +/*****************************************************************************/ +/* KEYBOARD */ +/*****************************************************************************/ + +static unsigned int keyshift[256]; // key to map to if shift held down in console +static qboolean shift_down=qfalse; + +static char *XLateKey(XKeyEvent *ev, int *key) +{ + static char buf[64]; + KeySym keysym; + static qboolean setup = qfalse; + int i; + + *key = 0; + + XLookupString(ev, buf, sizeof buf, &keysym, 0); + +// ri.Printf( PRINT_ALL, "keysym=%04X\n", (int)keysym); + switch(keysym) + { + case XK_KP_Page_Up: + case XK_KP_9: *key = K_KP_PGUP; break; + case XK_Page_Up: *key = K_PGUP; break; + + case XK_KP_Page_Down: + case XK_KP_3: *key = K_KP_PGDN; break; + case XK_Page_Down: *key = K_PGDN; break; + + case XK_KP_Home: *key = K_KP_HOME; break; + case XK_KP_7: *key = K_KP_HOME; break; + case XK_Home: *key = K_HOME; break; + + case XK_KP_End: + case XK_KP_1: *key = K_KP_END; break; + case XK_End: *key = K_END; break; + + case XK_KP_Left: *key = K_KP_LEFTARROW; break; + case XK_KP_4: *key = K_KP_LEFTARROW; break; + case XK_Left: *key = K_LEFTARROW; break; + + case XK_KP_Right: *key = K_KP_RIGHTARROW; break; + case XK_KP_6: *key = K_KP_RIGHTARROW; break; + case XK_Right: *key = K_RIGHTARROW; break; + + case XK_KP_Down: + case XK_KP_2: *key = K_KP_DOWNARROW; break; + case XK_Down: *key = K_DOWNARROW; break; + + case XK_KP_Up: + case XK_KP_8: *key = K_KP_UPARROW; break; + case XK_Up: *key = K_UPARROW; break; + + case XK_Escape: *key = K_ESCAPE; break; + + case XK_KP_Enter: *key = K_KP_ENTER; break; + case XK_Return: *key = K_ENTER; break; + + case XK_Tab: *key = K_TAB; break; + + case XK_F1: *key = K_F1; break; + + case XK_F2: *key = K_F2; break; + + case XK_F3: *key = K_F3; break; + + case XK_F4: *key = K_F4; break; + + case XK_F5: *key = K_F5; break; + + case XK_F6: *key = K_F6; break; + + case XK_F7: *key = K_F7; break; + + case XK_F8: *key = K_F8; break; + + case XK_F9: *key = K_F9; break; + + case XK_F10: *key = K_F10; break; + + case XK_F11: *key = K_F11; break; + + case XK_F12: *key = K_F12; break; + +// case XK_BackSpace: *key = K_BACKSPACE; break; + case XK_BackSpace: *key = 8; break; // ctrl-h + + case XK_KP_Delete: + case XK_KP_Decimal: *key = K_KP_DEL; break; + case XK_Delete: *key = K_DEL; break; + + case XK_Pause: *key = K_PAUSE; break; + + case XK_Shift_L: + case XK_Shift_R: *key = K_SHIFT; break; + + case XK_Execute: + case XK_Control_L: + case XK_Control_R: *key = K_CTRL; break; + + case XK_Alt_L: + case XK_Meta_L: + case XK_Alt_R: + case XK_Meta_R: *key = K_ALT; break; + + case XK_KP_Begin: *key = K_KP_5; break; + + case XK_Insert: *key = K_INS; break; + case XK_KP_Insert: + case XK_KP_0: *key = K_KP_INS; break; + + case XK_KP_Multiply: *key = '*'; break; + case XK_KP_Add: *key = K_KP_PLUS; break; + case XK_KP_Subtract: *key = K_KP_MINUS; break; + case XK_KP_Divide: *key = K_KP_SLASH; break; + + default: + *key = *(unsigned char *)buf; + if (*key >= 'A' && *key <= 'Z') + *key = *key - 'A' + 'a'; + break; + } + + return buf; +} + +// ======================================================================== +// makes a null cursor +// ======================================================================== + +static Cursor CreateNullCursor(Display *display, Window root) +{ + Pixmap cursormask; + XGCValues xgc; + GC gc; + XColor dummycolour; + Cursor cursor; + + cursormask = XCreatePixmap(display, root, 1, 1, 1/*depth*/); + xgc.function = GXclear; + gc = XCreateGC(display, cursormask, GCFunction, &xgc); + XFillRectangle(display, cursormask, gc, 0, 0, 1, 1); + dummycolour.pixel = 0; + dummycolour.red = 0; + dummycolour.flags = 04; + cursor = XCreatePixmapCursor(display, cursormask, cursormask, + &dummycolour,&dummycolour, 0,0); + XFreePixmap(display,cursormask); + XFreeGC(display,gc); + return cursor; +} + +static void install_grabs(void) +{ +// inviso cursor + XDefineCursor(dpy, win, CreateNullCursor(dpy, win)); + + XGrabPointer(dpy, win, + False, + MOUSE_MASK, + GrabModeAsync, GrabModeAsync, + win, + None, + CurrentTime); + + XGetPointerControl(dpy, &mouse_accel_numerator, &mouse_accel_denominator, + &mouse_threshold); + + XChangePointerControl(dpy, qtrue, qtrue, 2, 1, 0); + + if (in_dgamouse->value) { + int MajorVersion, MinorVersion; + + if (!XF86DGAQueryVersion(dpy, &MajorVersion, &MinorVersion)) { + // unable to query, probalby not supported + ri.Printf( PRINT_ALL, "Failed to detect XF86DGA Mouse\n" ); + ri.Cvar_Set( "in_dgamouse", "0" ); + } else { + dgamouse = qtrue; + XF86DGADirectVideo(dpy, DefaultScreen(dpy), XF86DGADirectMouse); + XWarpPointer(dpy, None, win, 0, 0, 0, 0, 0, 0); + } + } else { + XWarpPointer(dpy, None, win, + 0, 0, 0, 0, + glConfig.vidWidth / 2, glConfig.vidHeight / 2); + } + + XGrabKeyboard(dpy, win, + False, + GrabModeAsync, GrabModeAsync, + CurrentTime); + +// XSync(dpy, True); +} + +static void uninstall_grabs(void) +{ + if (dgamouse) { + dgamouse = qfalse; + XF86DGADirectVideo(dpy, DefaultScreen(dpy), 0); + } + + XChangePointerControl(dpy, qtrue, qtrue, mouse_accel_numerator, + mouse_accel_denominator, mouse_threshold); + + XUngrabPointer(dpy, CurrentTime); + XUngrabKeyboard(dpy, CurrentTime); + +// inviso cursor + XUndefineCursor(dpy, win); + +// XAutoRepeatOn(dpy); + +// XSync(dpy, True); +} + +static void HandleEvents(void) +{ + int b; + int key; + XEvent event; + qboolean dowarp = qfalse; + int mwx = glConfig.vidWidth/2; + int mwy = glConfig.vidHeight/2; + char *p; + + if (!dpy) + return; + + while (XPending(dpy)) { + XNextEvent(dpy, &event); + switch(event.type) { + case KeyPress: + p = XLateKey(&event.xkey, &key); + if (key) + Sys_QueEvent( 0, SE_KEY, key, qtrue, 0, NULL ); + while (*p) + Sys_QueEvent( 0, SE_CHAR, *p++, 0, 0, NULL ); + break; + case KeyRelease: + XLateKey(&event.xkey, &key); + + Sys_QueEvent( 0, SE_KEY, key, qfalse, 0, NULL ); + break; + +#if 0 + case KeyPress: + case KeyRelease: + key = XLateKey(&event.xkey); + + Sys_QueEvent( 0, SE_KEY, key, event.type == KeyPress, 0, NULL ); + if (key == K_SHIFT) + shift_down = (event.type == KeyPress); + if (key < 128 && (event.type == KeyPress)) { + if (shift_down) + key = keyshift[key]; + Sys_QueEvent( 0, SE_CHAR, key, 0, 0, NULL ); + } +#endif + break; + + case MotionNotify: + if (mouse_active) { + if (dgamouse) { + if (abs(event.xmotion.x_root) > 1) + mx += event.xmotion.x_root * 2; + else + mx += event.xmotion.x_root; + if (abs(event.xmotion.y_root) > 1) + my += event.xmotion.y_root * 2; + else + my += event.xmotion.y_root; +// ri.Printf(PRINT_ALL, "mouse (%d,%d) (root=%d,%d)\n", event.xmotion.x + win_x, event.xmotion.y + win_y, event.xmotion.x_root, event.xmotion.y_root); + } + else + { +// ri.Printf(PRINT_ALL, "mouse x=%d,y=%d\n", (int)event.xmotion.x - mwx, (int)event.xmotion.y - mwy); + mx += ((int)event.xmotion.x - mwx); + my += ((int)event.xmotion.y - mwy); + mwx = event.xmotion.x; + mwy = event.xmotion.y; + + if (mx || my) + dowarp = qtrue; + } + } + break; + + case ButtonPress: + b=-1; + if (event.xbutton.button == 1) + b = 0; + else if (event.xbutton.button == 2) + b = 2; + else if (event.xbutton.button == 3) + b = 1; + Sys_QueEvent( 0, SE_KEY, K_MOUSE1 + b, qtrue, 0, NULL ); + break; + + case ButtonRelease: + b=-1; + if (event.xbutton.button == 1) + b = 0; + else if (event.xbutton.button == 2) + b = 2; + else if (event.xbutton.button == 3) + b = 1; + Sys_QueEvent( 0, SE_KEY, K_MOUSE1 + b, qfalse, 0, NULL ); + break; + + case CreateNotify : + win_x = event.xcreatewindow.x; + win_y = event.xcreatewindow.y; + break; + + case ConfigureNotify : + win_x = event.xconfigure.x; + win_y = event.xconfigure.y; + break; + } + } + + if (dowarp) { + /* move the mouse to the window center again */ + XWarpPointer(dpy,None,win,0,0,0,0, + (glConfig.vidWidth/2),(glConfig.vidHeight/2)); + } + +} + +void KBD_Init(void) +{ +} + +void KBD_Close(void) +{ +} + +void IN_ActivateMouse( void ) +{ + if (!mouse_avail || !dpy || !win) + return; + + if (!mouse_active) { + mx = my = 0; // don't spazz + install_grabs(); + mouse_active = qtrue; + } +} + +void IN_DeactivateMouse( void ) +{ + if (!mouse_avail || !dpy || !win) + return; + + if (mouse_active) { + uninstall_grabs(); + mouse_active = qfalse; + } +} +/*****************************************************************************/ + +static qboolean signalcaught = qfalse;; + +static void signal_handler(int sig) +{ + if (signalcaught) { + printf("DOUBLE SIGNAL FAULT: Received signal %d, exiting...\n", sig); + _exit(1); + } + + signalcaught = qtrue; + printf("Received signal %d, exiting...\n", sig); + GLimp_Shutdown(); + _exit(1); +} + +static void InitSig(void) +{ + signal(SIGHUP, signal_handler); + signal(SIGQUIT, signal_handler); + signal(SIGILL, signal_handler); + signal(SIGTRAP, signal_handler); + signal(SIGIOT, signal_handler); + signal(SIGBUS, signal_handler); + signal(SIGFPE, signal_handler); + signal(SIGSEGV, signal_handler); + signal(SIGTERM, signal_handler); +} + +/* +** GLimp_SetGamma +** +** This routine should only be called if glConfig.deviceSupportsGamma is TRUE +*/ +void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] ) +{ +} + +/* +** GLimp_Shutdown +** +** This routine does all OS specific shutdown procedures for the OpenGL +** subsystem. Under OpenGL this means NULLing out the current DC and +** HGLRC, deleting the rendering context, and releasing the DC acquired +** for the window. The state structure is also nulled out. +** +*/ +void GLimp_Shutdown( void ) +{ + if (!ctx || !dpy) + return; + IN_DeactivateMouse(); + XAutoRepeatOn(dpy); + if (dpy) { + if (ctx) + qglXDestroyContext(dpy, ctx); + if (win) + XDestroyWindow(dpy, win); + if (vidmode_active) + XF86VidModeSwitchToMode(dpy, scrnum, vidmodes[0]); + XCloseDisplay(dpy); + } + vidmode_active = qfalse; + dpy = NULL; + win = 0; + ctx = NULL; + + memset( &glConfig, 0, sizeof( glConfig ) ); + memset( &glState, 0, sizeof( glState ) ); + + QGL_Shutdown(); +} + +/* +** GLimp_LogComment +*/ +void GLimp_LogComment( char *comment ) +{ + if ( glw_state.log_fp ) { + fprintf( glw_state.log_fp, "%s", comment ); + } +} + +/* +** GLW_StartDriverAndSetMode +*/ +static qboolean GLW_StartDriverAndSetMode( const char *drivername, + int mode, + qboolean fullscreen ) +{ + rserr_t err; + + // don't ever bother going into fullscreen with a voodoo card +#if 1 // JDC: I reenabled this + if ( strstr( drivername, "Voodoo" ) ) { + ri.Cvar_Set( "r_fullscreen", "0" ); + r_fullscreen->modified = qfalse; + fullscreen = qfalse; + } +#endif + + err = GLW_SetMode( drivername, mode, fullscreen ); + + switch ( err ) + { + case RSERR_INVALID_FULLSCREEN: + ri.Printf( PRINT_ALL, "...WARNING: fullscreen unavailable in this mode\n" ); + return qfalse; + case RSERR_INVALID_MODE: + ri.Printf( PRINT_ALL, "...WARNING: could not set the given mode (%d)\n", mode ); + return qfalse; + default: + break; + } + return qtrue; +} + +/* +** GLW_SetMode +*/ +int GLW_SetMode( const char *drivername, int mode, qboolean fullscreen ) +{ + int attrib[] = { + GLX_RGBA, // 0 + GLX_RED_SIZE, 4, // 1, 2 + GLX_GREEN_SIZE, 4, // 3, 4 + GLX_BLUE_SIZE, 4, // 5, 6 + GLX_DOUBLEBUFFER, // 7 + GLX_DEPTH_SIZE, 1, // 8, 9 + GLX_STENCIL_SIZE, 1, // 10, 11 + None + }; +// these match in the array +#define ATTR_RED_IDX 2 +#define ATTR_GREEN_IDX 4 +#define ATTR_BLUE_IDX 6 +#define ATTR_DEPTH_IDX 9 +#define ATTR_STENCIL_IDX 11 + Window root; + XVisualInfo *visinfo; + XSetWindowAttributes attr; + unsigned long mask; + int colorbits, depthbits, stencilbits; + int tcolorbits, tdepthbits, tstencilbits; + int MajorVersion, MinorVersion; + int actualWidth, actualHeight; + int i; + + r_fakeFullscreen = ri.Cvar_Get( "r_fakeFullscreen", "0", CVAR_ARCHIVE); + + ri.Printf( PRINT_ALL, "Initializing OpenGL display\n"); + + ri.Printf (PRINT_ALL, "...setting mode %d:", mode ); + + if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, &glConfig.windowAspect, mode ) ) + { + ri.Printf( PRINT_ALL, " invalid mode\n" ); + return RSERR_INVALID_MODE; + } + ri.Printf( PRINT_ALL, " %d %d\n", glConfig.vidWidth, glConfig.vidHeight); + + if (!(dpy = XOpenDisplay(NULL))) { + fprintf(stderr, "Error couldn't open the X display\n"); + return RSERR_INVALID_MODE; + } + + scrnum = DefaultScreen(dpy); + root = RootWindow(dpy, scrnum); + + actualWidth = glConfig.vidWidth; + actualHeight = glConfig.vidHeight; + + // Get video mode list + MajorVersion = MinorVersion = 0; + if (!XF86VidModeQueryVersion(dpy, &MajorVersion, &MinorVersion)) { + vidmode_ext = qfalse; + } else { + ri.Printf(PRINT_ALL, "Using XFree86-VidModeExtension Version %d.%d\n", + MajorVersion, MinorVersion); + vidmode_ext = qtrue; + } + + if (vidmode_ext) { + int best_fit, best_dist, dist, x, y; + + XF86VidModeGetAllModeLines(dpy, scrnum, &num_vidmodes, &vidmodes); + + // Are we going fullscreen? If so, let's change video mode + if (fullscreen && !r_fakeFullscreen->integer) { + best_dist = 9999999; + best_fit = -1; + + for (i = 0; i < num_vidmodes; i++) { + if (glConfig.vidWidth > vidmodes[i]->hdisplay || + glConfig.vidHeight > vidmodes[i]->vdisplay) + continue; + + x = glConfig.vidWidth - vidmodes[i]->hdisplay; + y = glConfig.vidHeight - vidmodes[i]->vdisplay; + dist = (x * x) + (y * y); + if (dist < best_dist) { + best_dist = dist; + best_fit = i; + } + } + + if (best_fit != -1) { + actualWidth = vidmodes[best_fit]->hdisplay; + actualHeight = vidmodes[best_fit]->vdisplay; + + // change to the mode + XF86VidModeSwitchToMode(dpy, scrnum, vidmodes[best_fit]); + vidmode_active = qtrue; + + // Move the viewport to top left + XF86VidModeSetViewPort(dpy, scrnum, 0, 0); + } else + fullscreen = 0; + } + } + + + if (!r_colorbits->value) + colorbits = 24; + else + colorbits = r_colorbits->value; + + if ( !Q_stricmp( r_glDriver->string, _3DFX_DRIVER_NAME ) ) + colorbits = 16; + + if (!r_depthbits->value) + depthbits = 24; + else + depthbits = r_depthbits->value; + stencilbits = r_stencilbits->value; + + for (i = 0; i < 16; i++) { + // 0 - default + // 1 - minus colorbits + // 2 - minus depthbits + // 3 - minus stencil + if ((i % 4) == 0 && i) { + // one pass, reduce + switch (i / 4) { + case 2 : + if (colorbits == 24) + colorbits = 16; + break; + case 1 : + if (depthbits == 24) + depthbits = 16; + else if (depthbits == 16) + depthbits = 8; + case 3 : + if (stencilbits == 24) + stencilbits = 16; + else if (stencilbits == 16) + stencilbits = 8; + } + } + + tcolorbits = colorbits; + tdepthbits = depthbits; + tstencilbits = stencilbits; + + if ((i % 4) == 3) { // reduce colorbits + if (tcolorbits == 24) + tcolorbits = 16; + } + + if ((i % 4) == 2) { // reduce depthbits + if (tdepthbits == 24) + tdepthbits = 16; + else if (tdepthbits == 16) + tdepthbits = 8; + } + + if ((i % 4) == 1) { // reduce stencilbits + if (tstencilbits == 24) + tstencilbits = 16; + else if (tstencilbits == 16) + tstencilbits = 8; + else + tstencilbits = 0; + } + + if (tcolorbits == 24) { + attrib[ATTR_RED_IDX] = 8; + attrib[ATTR_GREEN_IDX] = 8; + attrib[ATTR_BLUE_IDX] = 8; + } else { + // must be 16 bit + attrib[ATTR_RED_IDX] = 4; + attrib[ATTR_GREEN_IDX] = 4; + attrib[ATTR_BLUE_IDX] = 4; + } + + attrib[ATTR_DEPTH_IDX] = tdepthbits; // default to 24 depth + attrib[ATTR_STENCIL_IDX] = tstencilbits; + +#if 0 + ri.Printf( PRINT_DEVELOPER, "Attempting %d/%d/%d Color bits, %d depth, %d stencil display...", + attrib[ATTR_RED_IDX], attrib[ATTR_GREEN_IDX], attrib[ATTR_BLUE_IDX], + attrib[ATTR_DEPTH_IDX], attrib[ATTR_STENCIL_IDX]); +#endif + + visinfo = qglXChooseVisual(dpy, scrnum, attrib); + if (!visinfo) { +#if 0 + ri.Printf( PRINT_DEVELOPER, "failed\n"); +#endif + continue; + } + +#if 0 + ri.Printf( PRINT_DEVELOPER, "Successful\n"); +#endif + + ri.Printf( PRINT_ALL, "Using %d/%d/%d Color bits, %d depth, %d stencil display.\n", + attrib[ATTR_RED_IDX], attrib[ATTR_GREEN_IDX], attrib[ATTR_BLUE_IDX], + attrib[ATTR_DEPTH_IDX], attrib[ATTR_STENCIL_IDX]); + + glConfig.colorBits = tcolorbits; + glConfig.depthBits = tdepthbits; + glConfig.stencilBits = tstencilbits; + break; + } + + if (!visinfo) { + ri.Printf( PRINT_ALL, "Couldn't get a visual\n" ); + return RSERR_INVALID_MODE; + } + + /* window attributes */ + attr.background_pixel = BlackPixel(dpy, scrnum); + attr.border_pixel = 0; + attr.colormap = XCreateColormap(dpy, root, visinfo->visual, AllocNone); + attr.event_mask = X_MASK; + if (vidmode_active) { + mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | + CWEventMask | CWOverrideRedirect; + attr.override_redirect = True; + attr.backing_store = NotUseful; + attr.save_under = False; + } else + mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask; + + win = XCreateWindow(dpy, root, 0, 0, + actualWidth, actualHeight, + 0, visinfo->depth, InputOutput, + visinfo->visual, mask, &attr); + XMapWindow(dpy, win); + + if (vidmode_active) + XMoveWindow(dpy, win, 0, 0); + + // Check for DGA + if (in_dgamouse->value) { + if (!XF86DGAQueryVersion(dpy, &MajorVersion, &MinorVersion)) { + // unable to query, probalby not supported + ri.Printf( PRINT_ALL, "Failed to detect XF86DGA Mouse\n" ); + ri.Cvar_Set( "in_dgamouse", "0" ); + } else + ri.Printf( PRINT_ALL, "XF86DGA Mouse (Version %d.%d) initialized\n", + MajorVersion, MinorVersion); + } + + XFlush(dpy); + + ctx = qglXCreateContext(dpy, visinfo, NULL, True); + + qglXMakeCurrent(dpy, win, ctx); + + return RSERR_OK; +} + +/* +** GLW_InitExtensions +*/ +static void GLW_InitExtensions( void ) +{ + // Use modern texture compression extensions + if ( strstr( glConfig.extensions_string, "ARB_texture_compression" ) && strstr( glConfig.extensions_string, "EXT_texture_compression_s3tc" ) ) + { + if ( r_ext_compressed_textures->value ) + { + glConfig.textureCompression = TC_S3TC_DXT; + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_compression_s3tc\n" ); + } + else + { + glConfig.textureCompression = TC_NONE; + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_compression_s3tc\n" ); + } + } + // Or check for old ones + else if ( strstr( glConfig.extensions_string, "GL_S3_s3tc" ) ) + { + if ( r_ext_compressed_textures->value ) + { + glConfig.textureCompression = TC_S3TC; + ri.Printf( PRINT_ALL, "...using GL_S3_s3tc\n" ); + } + else + { + glConfig.textureCompression = TC_NONE; + ri.Printf( PRINT_ALL, "...ignoring GL_S3_s3tc\n" ); + } + } + else + { + glConfig.textureCompression = TC_NONE; + ri.Printf( PRINT_ALL, "...no texture compression found\n" ); + } + +#if 0 + // WGL_EXT_swap_control + if ( strstr( glConfig.extensions_string, "WGL_EXT_swap_control" ) ) + { + qwglSwapIntervalEXT = ( BOOL (WINAPI *)(int)) qwglGetProcAddress( "wglSwapIntervalEXT" ); + ri.Printf( PRINT_ALL, "...using WGL_EXT_swap_control\n" ); + } + else + { + ri.Printf( PRINT_ALL, "...WGL_EXT_swap_control not found\n" ); + } +#endif + + // GL_ARB_multitexture + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + if ( strstr( glConfig.extensions_string, "GL_ARB_multitexture" ) ) + { + if ( r_ext_multitexture->value ) + { + qglMultiTexCoord2fARB = ( PFNGLMULTITEXCOORD2FARBPROC ) dlsym( glw_state.OpenGLLib, "glMultiTexCoord2fARB" ); + qglActiveTextureARB = ( PFNGLACTIVETEXTUREARBPROC ) dlsym( glw_state.OpenGLLib, "glActiveTextureARB" ); + qglClientActiveTextureARB = ( PFNGLCLIENTACTIVETEXTUREARBPROC ) dlsym( glw_state.OpenGLLib, "glClientActiveTextureARB" ); + + if ( qglActiveTextureARB ) + { + ri.Printf( PRINT_ALL, "...using GL_ARB_multitexture\n" ); + } + else + { + ri.Printf( PRINT_ALL, "...blind search for ARB_multitexture failed\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_ARB_multitexture\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_ARB_multitexture not found\n" ); + } + + // GL_EXT_texture_filter_anisotropic + glConfig.textureFilterAnisotropicAvailable = qfalse; + if ( strstr( glConfig.extensions_string, "EXT_texture_filter_anisotropic" ) ) + { + glConfig.textureFilterAnisotropicAvailable = qtrue; + ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic available\n" ); + + if ( r_ext_texture_filter_anisotropic->integer ) + { + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_filter_anisotropic\n" ); + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_filter_anisotropic\n" ); + } + ri.Cvar_Set( "r_ext_texture_filter_anisotropic_avail", "1" ); + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic not found\n" ); + ri.Cvar_Set( "r_ext_texture_filter_anisotropic_avail", "0" ); + } + + // GL_EXT_compiled_vertex_array + if ( strstr( glConfig.extensions_string, "GL_EXT_compiled_vertex_array" ) ) + { + if ( r_ext_compiled_vertex_array->value ) + { + ri.Printf( PRINT_ALL, "...using GL_EXT_compiled_vertex_array\n" ); + qglLockArraysEXT = ( void ( APIENTRY * )( int, int ) ) dlsym( glw_state.OpenGLLib, "glLockArraysEXT" ); + qglUnlockArraysEXT = ( void ( APIENTRY * )( void ) ) dlsym( glw_state.OpenGLLib, "glUnlockArraysEXT" ); + if (!qglLockArraysEXT || !qglUnlockArraysEXT) { + ri.Error (ERR_FATAL, "bad getprocaddress"); + } + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_compiled_vertex_array\n" ); + } + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_compiled_vertex_array not found\n" ); + } + +} + +/* +** GLW_LoadOpenGL +** +** GLimp_win.c internal function that that attempts to load and use +** a specific OpenGL DLL. +*/ +static qboolean GLW_LoadOpenGL( const char *name ) +{ + qboolean fullscreen; + + ri.Printf( PRINT_ALL, "...loading %s: ", name ); + + // disable the 3Dfx splash screen and set gamma + // we do this all the time, but it shouldn't hurt anything + // on non-3Dfx stuff + putenv("FX_GLIDE_NO_SPLASH=0"); + + // Mesa VooDoo hacks + putenv("MESA_GLX_FX=fullscreen\n"); + + // load the QGL layer + if ( QGL_Init( name ) ) + { + fullscreen = r_fullscreen->integer; + + // create the window and set up the context + if ( !GLW_StartDriverAndSetMode( name, r_mode->integer, fullscreen ) ) + { + if (r_mode->integer != 3) { + if ( !GLW_StartDriverAndSetMode( name, 3, fullscreen ) ) { + goto fail; + } + } else + goto fail; + } + + return qtrue; + } + else + { + ri.Printf( PRINT_ALL, "failed\n" ); + } +fail: + + QGL_Shutdown(); + + return qfalse; +} + +/* +** GLimp_Init +** +** This routine is responsible for initializing the OS specific portions +** of OpenGL. +*/ +void GLimp_Init( void ) +{ + qboolean attemptedlibGL = qfalse; + qboolean attempted3Dfx = qfalse; + qboolean success = qfalse; + char buf[1024]; + cvar_t *lastValidRenderer = ri.Cvar_Get( "r_lastValidRenderer", "(uninitialized)", CVAR_ARCHIVE ); + cvar_t *cv; + + glConfig.deviceSupportsGamma = qfalse; + + InitSig(); + + // + // load and initialize the specific OpenGL driver + // + if ( !GLW_LoadOpenGL( r_glDriver->string ) ) + { + if ( !Q_stricmp( r_glDriver->string, OPENGL_DRIVER_NAME ) ) + { + attemptedlibGL = qtrue; + } + else if ( !Q_stricmp( r_glDriver->string, _3DFX_DRIVER_NAME ) ) + { + attempted3Dfx = qtrue; + } + + if ( !attempted3Dfx && !success ) + { + attempted3Dfx = qtrue; + if ( GLW_LoadOpenGL( _3DFX_DRIVER_NAME ) ) + { + ri.Cvar_Set( "r_glDriver", _3DFX_DRIVER_NAME ); + r_glDriver->modified = qfalse; + success = qtrue; + } + } + + // try ICD before trying 3Dfx standalone driver + if ( !attemptedlibGL && !success ) + { + attemptedlibGL = qtrue; + if ( GLW_LoadOpenGL( OPENGL_DRIVER_NAME ) ) + { + ri.Cvar_Set( "r_glDriver", OPENGL_DRIVER_NAME ); + r_glDriver->modified = qfalse; + success = qtrue; + } + } + + if (!success) + ri.Error( ERR_FATAL, "GLimp_Init() - could not load OpenGL subsystem\n" ); + + } + + // get our config strings + Q_strncpyz( glConfig.vendor_string, qglGetString (GL_VENDOR), sizeof( glConfig.vendor_string ) ); + Q_strncpyz( glConfig.renderer_string, qglGetString (GL_RENDERER), sizeof( glConfig.renderer_string ) ); + if (*glConfig.renderer_string && glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] == '\n') + glConfig.renderer_string[strlen(glConfig.renderer_string) - 1] = 0; + Q_strncpyz( glConfig.version_string, qglGetString (GL_VERSION), sizeof( glConfig.version_string ) ); + Q_strncpyz( glConfig.extensions_string, qglGetString (GL_EXTENSIONS), sizeof( glConfig.extensions_string ) ); + + // + // chipset specific configuration + // + strcpy( buf, glConfig.renderer_string ); + strlwr( buf ); + + if ( Q_stricmp( lastValidRenderer->string, glConfig.renderer_string ) ) + { + ri.Cvar_Set( "r_picmip", "1" ); + ri.Cvar_Set( "r_twopartfog", "0" ); + ri.Cvar_Set( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST" ); + + // + // voodoo issues + // + if ( strstr( buf, "voodoo" ) && !strstr( buf, "banshee" ) ) + { + ri.Cvar_Set( "r_fakeFullscreen", "1"); + } + + // + // Riva128 issues + // + if ( strstr( buf, "riva 128" ) ) + { + ri.Cvar_Set( "r_twopartfog", "1" ); + } + + // + // Rage Pro issues + // + if ( strstr( buf, "rage pro" ) ) + { + ri.Cvar_Set( "r_mode", "2" ); + ri.Cvar_Set( "r_twopartfog", "1" ); + } + + // + // Permedia2 issues + // + if ( strstr( buf, "permedia2" ) ) + { + ri.Cvar_Set( "r_vertexLight", "1" ); + } + + // + // Riva TNT issues + // + if ( strstr( buf, "riva tnt " ) ) + { + if ( r_texturebits->integer == 32 || + ( ( r_texturebits->integer == 0 ) && glConfig.colorBits > 16 ) ) + { + ri.Cvar_Set( "r_picmip", "1" ); + } + } + + ri.Cvar_Set( "r_lastValidRenderer", glConfig.renderer_string ); + } + + // initialize extensions + GLW_InitExtensions(); + + InitSig(); + + return; +} + + +/* +** GLimp_EndFrame +** +** Responsible for doing a swapbuffers and possibly for other stuff +** as yet to be determined. Probably better not to make this a GLimp +** function and instead do a call to GLimp_SwapBuffers. +*/ +void GLimp_EndFrame (void) +{ +#if 0 + int err; + + if ( !glState.finishCalled ) + qglFinish(); + + // check for errors + if ( !gl_ignore_errors->value ) { + if ( ( err = qglGetError() ) != GL_NO_ERROR ) + { + ri.Error( ERR_FATAL, "GLimp_EndFrame() - glGetError() failed (0x%x)!\n", err ); + } + } +#endif + + // don't flip if drawing to front buffer + if ( stricmp( r_drawBuffer->string, "GL_FRONT" ) != 0 ) + { + qglXSwapBuffers(dpy, win); + } + + // check logging + QGL_EnableLogging( r_logFile->value ); + +#if 0 + GLimp_LogComment( "*** RE_EndFrame ***\n" ); + + // decrement log + if ( gl_log->value ) + { + ri.Cvar_Set( "gl_log", va("%i",gl_log->value - 1 ) ); + } +#endif +} + +/* +=========================================================== + +SMP acceleration + +=========================================================== +*/ + +sem_t renderCommandsEvent; +sem_t renderCompletedEvent; +sem_t renderActiveEvent; + +void (*glimpRenderThread)( void ); + +void GLimp_RenderThreadWrapper( void *stub ) { + glimpRenderThread(); + +#if 0 + // unbind the context before we die + qglXMakeCurrent(dpy, None, NULL); +#endif +} + + +/* +======================= +GLimp_SpawnRenderThread +======================= +*/ +pthread_t renderThreadHandle; +qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) { + + sem_init( &renderCommandsEvent, 0, 0 ); + sem_init( &renderCompletedEvent, 0, 0 ); + sem_init( &renderActiveEvent, 0, 0 ); + + glimpRenderThread = function; + + if (pthread_create( &renderThreadHandle, NULL, + GLimp_RenderThreadWrapper, NULL)) { + return qfalse; + } + + return qtrue; +} + +static void *smpData; +static int glXErrors; + +void *GLimp_RendererSleep( void ) { + void *data; + +#if 0 + if ( !qglXMakeCurrent(dpy, None, NULL) ) { + glXErrors++; + } +#endif + +// ResetEvent( renderActiveEvent ); + + // after this, the front end can exit GLimp_FrontEndSleep + sem_post ( &renderCompletedEvent ); + + sem_wait ( &renderCommandsEvent ); + +#if 0 + if ( !qglXMakeCurrent(dpy, win, ctx) ) { + glXErrors++; + } +#endif + +// ResetEvent( renderCompletedEvent ); +// ResetEvent( renderCommandsEvent ); + + data = smpData; + + // after this, the main thread can exit GLimp_WakeRenderer + sem_post ( &renderActiveEvent ); + + return data; +} + + +void GLimp_FrontEndSleep( void ) { + sem_wait ( &renderCompletedEvent ); + +#if 0 + if ( !qglXMakeCurrent(dpy, win, ctx) ) { + glXErrors++; + } +#endif +} + + +void GLimp_WakeRenderer( void *data ) { + smpData = data; + +#if 0 + if ( !qglXMakeCurrent(dpy, None, NULL) ) { + glXErrors++; + } +#endif + + // after this, the renderer can continue through GLimp_RendererSleep + sem_post( &renderCommandsEvent ); + + sem_wait( &renderActiveEvent ); +} + +/*===========================================================*/ + +/*****************************************************************************/ +/* MOUSE */ +/*****************************************************************************/ + +void IN_Init(void) +{ + // mouse variables + in_mouse = Cvar_Get ("in_mouse", "1", CVAR_ARCHIVE); + in_dgamouse = Cvar_Get ("in_dgamouse", "1", CVAR_ARCHIVE); + + if (in_mouse->value) + mouse_avail = qtrue; + else + mouse_avail = qfalse; +} + +void IN_Shutdown(void) +{ + mouse_avail = qfalse; +} + +void IN_MouseMove(void) +{ + if (!mouse_avail || !dpy || !win) + return; + +#if 0 + if (!dgamouse) { + Window root, child; + int root_x, root_y; + int win_x, win_y; + unsigned int mask_return; + int mwx = glConfig.vidWidth/2; + int mwy = glConfig.vidHeight/2; + + XQueryPointer(dpy, win, &root, &child, + &root_x, &root_y, &win_x, &win_y, &mask_return); + + mx = win_x - mwx; + my = win_y - mwy; + + XWarpPointer(dpy,None,win,0,0,0,0, mwx, mwy); + } +#endif + + if (mx || my) + Sys_QueEvent( 0, SE_MOUSE, mx, my, 0, NULL ); + mx = my = 0; +} + +void IN_Frame (void) +{ + if ( cls.keyCatchers || cls.state != CA_ACTIVE ) { + // temporarily deactivate if not in the game and + // running on the desktop + // voodoo always counts as full screen + if (Cvar_VariableValue ("r_fullscreen") == 0 + && strcmp( Cvar_VariableString("r_glDriver"), _3DFX_DRIVER_NAME ) ) { + IN_DeactivateMouse (); + return; + } + if (dpy && !autorepeaton) { + XAutoRepeatOn(dpy); + autorepeaton = qtrue; + } + } else if (dpy && autorepeaton) { + XAutoRepeatOff(dpy); + autorepeaton = qfalse; + } + + IN_ActivateMouse(); + + // post events to the system que + IN_MouseMove(); +} + +void IN_Activate(void) +{ +} + +void Sys_SendKeyEvents (void) +{ + XEvent event; + + if (!dpy) + return; + + HandleEvents(); +// while (XCheckMaskEvent(dpy,KEY_MASK|MOUSE_MASK,&event)) +// HandleEvent(&event); +} + diff --git a/code/unix/linux_qgl.c b/code/unix/linux_qgl.c new file mode 100644 index 0000000..ca249c3 --- /dev/null +++ b/code/unix/linux_qgl.c @@ -0,0 +1,4111 @@ +/* +** LINUX_QGL.C +** +** This file implements the operating system binding of GL to QGL function +** pointers. When doing a port of Quake2 you must implement the following +** two functions: +** +** QGL_Init() - loads libraries, assigns function pointers, etc. +** QGL_Shutdown() - unloads libraries, NULLs function pointers +*/ +#include +#include "../renderer/tr_local.h" +#include "unix_glw.h" + +#include +#include + +#include + +//FX Mesa Functions +fxMesaContext (*qfxMesaCreateContext)(GLuint win, GrScreenResolution_t, GrScreenRefresh_t, const GLint attribList[]); +fxMesaContext (*qfxMesaCreateBestContext)(GLuint win, GLint width, GLint height, const GLint attribList[]); +void (*qfxMesaDestroyContext)(fxMesaContext ctx); +void (*qfxMesaMakeCurrent)(fxMesaContext ctx); +fxMesaContext (*qfxMesaGetCurrentContext)(void); +void (*qfxMesaSwapBuffers)(void); + +//GLX Functions +XVisualInfo * (*qglXChooseVisual)( Display *dpy, int screen, int *attribList ); +GLXContext (*qglXCreateContext)( Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct ); +void (*qglXDestroyContext)( Display *dpy, GLXContext ctx ); +Bool (*qglXMakeCurrent)( Display *dpy, GLXDrawable drawable, GLXContext ctx); +void (*qglXCopyContext)( Display *dpy, GLXContext src, GLXContext dst, GLuint mask ); +void (*qglXSwapBuffers)( Display *dpy, GLXDrawable drawable ); + +void ( APIENTRY * qglAccum )(GLenum op, GLfloat value); +void ( APIENTRY * qglAlphaFunc )(GLenum func, GLclampf ref); +GLboolean ( APIENTRY * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +void ( APIENTRY * qglArrayElement )(GLint i); +void ( APIENTRY * qglBegin )(GLenum mode); +void ( APIENTRY * qglBindTexture )(GLenum target, GLuint texture); +void ( APIENTRY * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +void ( APIENTRY * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +void ( APIENTRY * qglCallList )(GLuint list); +void ( APIENTRY * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +void ( APIENTRY * qglClear )(GLbitfield mask); +void ( APIENTRY * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( APIENTRY * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +void ( APIENTRY * qglClearDepth )(GLclampd depth); +void ( APIENTRY * qglClearIndex )(GLfloat c); +void ( APIENTRY * qglClearStencil )(GLint s); +void ( APIENTRY * qglClipPlane )(GLenum plane, const GLdouble *equation); +void ( APIENTRY * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +void ( APIENTRY * qglColor3bv )(const GLbyte *v); +void ( APIENTRY * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +void ( APIENTRY * qglColor3dv )(const GLdouble *v); +void ( APIENTRY * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +void ( APIENTRY * qglColor3fv )(const GLfloat *v); +void ( APIENTRY * qglColor3i )(GLint red, GLint green, GLint blue); +void ( APIENTRY * qglColor3iv )(const GLint *v); +void ( APIENTRY * qglColor3s )(GLshort red, GLshort green, GLshort blue); +void ( APIENTRY * qglColor3sv )(const GLshort *v); +void ( APIENTRY * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +void ( APIENTRY * qglColor3ubv )(const GLubyte *v); +void ( APIENTRY * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +void ( APIENTRY * qglColor3uiv )(const GLuint *v); +void ( APIENTRY * qglColor3us )(GLushort red, GLushort green, GLushort blue); +void ( APIENTRY * qglColor3usv )(const GLushort *v); +void ( APIENTRY * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +void ( APIENTRY * qglColor4bv )(const GLbyte *v); +void ( APIENTRY * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +void ( APIENTRY * qglColor4dv )(const GLdouble *v); +void ( APIENTRY * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( APIENTRY * qglColor4fv )(const GLfloat *v); +void ( APIENTRY * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +void ( APIENTRY * qglColor4iv )(const GLint *v); +void ( APIENTRY * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +void ( APIENTRY * qglColor4sv )(const GLshort *v); +void ( APIENTRY * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +void ( APIENTRY * qglColor4ubv )(const GLubyte *v); +void ( APIENTRY * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +void ( APIENTRY * qglColor4uiv )(const GLuint *v); +void ( APIENTRY * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +void ( APIENTRY * qglColor4usv )(const GLushort *v); +void ( APIENTRY * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +void ( APIENTRY * qglColorMaterial )(GLenum face, GLenum mode); +void ( APIENTRY * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +void ( APIENTRY * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +void ( APIENTRY * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +void ( APIENTRY * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +void ( APIENTRY * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY * qglCullFace )(GLenum mode); +void ( APIENTRY * qglDeleteLists )(GLuint list, GLsizei range); +void ( APIENTRY * qglDeleteTextures )(GLsizei n, const GLuint *textures); +void ( APIENTRY * qglDepthFunc )(GLenum func); +void ( APIENTRY * qglDepthMask )(GLboolean flag); +void ( APIENTRY * qglDepthRange )(GLclampd zNear, GLclampd zFar); +void ( APIENTRY * qglDisable )(GLenum cap); +void ( APIENTRY * qglDisableClientState )(GLenum array); +void ( APIENTRY * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +void ( APIENTRY * qglDrawBuffer )(GLenum mode); +void ( APIENTRY * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +void ( APIENTRY * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglEdgeFlag )(GLboolean flag); +void ( APIENTRY * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglEdgeFlagv )(const GLboolean *flag); +void ( APIENTRY * qglEnable )(GLenum cap); +void ( APIENTRY * qglEnableClientState )(GLenum array); +void ( APIENTRY * qglEnd )(void); +void ( APIENTRY * qglEndList )(void); +void ( APIENTRY * qglEvalCoord1d )(GLdouble u); +void ( APIENTRY * qglEvalCoord1dv )(const GLdouble *u); +void ( APIENTRY * qglEvalCoord1f )(GLfloat u); +void ( APIENTRY * qglEvalCoord1fv )(const GLfloat *u); +void ( APIENTRY * qglEvalCoord2d )(GLdouble u, GLdouble v); +void ( APIENTRY * qglEvalCoord2dv )(const GLdouble *u); +void ( APIENTRY * qglEvalCoord2f )(GLfloat u, GLfloat v); +void ( APIENTRY * qglEvalCoord2fv )(const GLfloat *u); +void ( APIENTRY * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +void ( APIENTRY * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +void ( APIENTRY * qglEvalPoint1 )(GLint i); +void ( APIENTRY * qglEvalPoint2 )(GLint i, GLint j); +void ( APIENTRY * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +void ( APIENTRY * qglFinish )(void); +void ( APIENTRY * qglFlush )(void); +void ( APIENTRY * qglFogf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglFogfv )(GLenum pname, const GLfloat *params); +void ( APIENTRY * qglFogi )(GLenum pname, GLint param); +void ( APIENTRY * qglFogiv )(GLenum pname, const GLint *params); +void ( APIENTRY * qglFrontFace )(GLenum mode); +void ( APIENTRY * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLuint ( APIENTRY * qglGenLists )(GLsizei range); +void ( APIENTRY * qglGenTextures )(GLsizei n, GLuint *textures); +void ( APIENTRY * qglGetBooleanv )(GLenum pname, GLboolean *params); +void ( APIENTRY * qglGetClipPlane )(GLenum plane, GLdouble *equation); +void ( APIENTRY * qglGetDoublev )(GLenum pname, GLdouble *params); +GLenum ( APIENTRY * qglGetError )(void); +void ( APIENTRY * qglGetFloatv )(GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetIntegerv )(GLenum pname, GLint *params); +void ( APIENTRY * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +void ( APIENTRY * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +void ( APIENTRY * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +void ( APIENTRY * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +void ( APIENTRY * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +void ( APIENTRY * qglGetPixelMapfv )(GLenum map, GLfloat *values); +void ( APIENTRY * qglGetPixelMapuiv )(GLenum map, GLuint *values); +void ( APIENTRY * qglGetPixelMapusv )(GLenum map, GLushort *values); +void ( APIENTRY * qglGetPointerv )(GLenum pname, GLvoid* *params); +void ( APIENTRY * qglGetPolygonStipple )(GLubyte *mask); +const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +void ( APIENTRY * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +void ( APIENTRY * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +void ( APIENTRY * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +void ( APIENTRY * qglHint )(GLenum target, GLenum mode); +void ( APIENTRY * qglIndexMask )(GLuint mask); +void ( APIENTRY * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglIndexd )(GLdouble c); +void ( APIENTRY * qglIndexdv )(const GLdouble *c); +void ( APIENTRY * qglIndexf )(GLfloat c); +void ( APIENTRY * qglIndexfv )(const GLfloat *c); +void ( APIENTRY * qglIndexi )(GLint c); +void ( APIENTRY * qglIndexiv )(const GLint *c); +void ( APIENTRY * qglIndexs )(GLshort c); +void ( APIENTRY * qglIndexsv )(const GLshort *c); +void ( APIENTRY * qglIndexub )(GLubyte c); +void ( APIENTRY * qglIndexubv )(const GLubyte *c); +void ( APIENTRY * qglInitNames )(void); +void ( APIENTRY * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +GLboolean ( APIENTRY * qglIsEnabled )(GLenum cap); +GLboolean ( APIENTRY * qglIsList )(GLuint list); +GLboolean ( APIENTRY * qglIsTexture )(GLuint texture); +void ( APIENTRY * qglLightModelf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglLightModelfv )(GLenum pname, const GLfloat *params); +void ( APIENTRY * qglLightModeli )(GLenum pname, GLint param); +void ( APIENTRY * qglLightModeliv )(GLenum pname, const GLint *params); +void ( APIENTRY * qglLightf )(GLenum light, GLenum pname, GLfloat param); +void ( APIENTRY * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglLighti )(GLenum light, GLenum pname, GLint param); +void ( APIENTRY * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +void ( APIENTRY * qglLineStipple )(GLint factor, GLushort pattern); +void ( APIENTRY * qglLineWidth )(GLfloat width); +void ( APIENTRY * qglListBase )(GLuint base); +void ( APIENTRY * qglLoadIdentity )(void); +void ( APIENTRY * qglLoadMatrixd )(const GLdouble *m); +void ( APIENTRY * qglLoadMatrixf )(const GLfloat *m); +void ( APIENTRY * qglLoadName )(GLuint name); +void ( APIENTRY * qglLogicOp )(GLenum opcode); +void ( APIENTRY * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +void ( APIENTRY * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +void ( APIENTRY * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +void ( APIENTRY * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +void ( APIENTRY * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +void ( APIENTRY * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +void ( APIENTRY * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +void ( APIENTRY * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +void ( APIENTRY * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +void ( APIENTRY * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglMateriali )(GLenum face, GLenum pname, GLint param); +void ( APIENTRY * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +void ( APIENTRY * qglMatrixMode )(GLenum mode); +void ( APIENTRY * qglMultMatrixd )(const GLdouble *m); +void ( APIENTRY * qglMultMatrixf )(const GLfloat *m); +void ( APIENTRY * qglNewList )(GLuint list, GLenum mode); +void ( APIENTRY * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +void ( APIENTRY * qglNormal3bv )(const GLbyte *v); +void ( APIENTRY * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +void ( APIENTRY * qglNormal3dv )(const GLdouble *v); +void ( APIENTRY * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +void ( APIENTRY * qglNormal3fv )(const GLfloat *v); +void ( APIENTRY * qglNormal3i )(GLint nx, GLint ny, GLint nz); +void ( APIENTRY * qglNormal3iv )(const GLint *v); +void ( APIENTRY * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +void ( APIENTRY * qglNormal3sv )(const GLshort *v); +void ( APIENTRY * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +void ( APIENTRY * qglPassThrough )(GLfloat token); +void ( APIENTRY * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +void ( APIENTRY * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +void ( APIENTRY * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +void ( APIENTRY * qglPixelStoref )(GLenum pname, GLfloat param); +void ( APIENTRY * qglPixelStorei )(GLenum pname, GLint param); +void ( APIENTRY * qglPixelTransferf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglPixelTransferi )(GLenum pname, GLint param); +void ( APIENTRY * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +void ( APIENTRY * qglPointSize )(GLfloat size); +void ( APIENTRY * qglPolygonMode )(GLenum face, GLenum mode); +void ( APIENTRY * qglPolygonOffset )(GLfloat factor, GLfloat units); +void ( APIENTRY * qglPolygonStipple )(const GLubyte *mask); +void ( APIENTRY * qglPopAttrib )(void); +void ( APIENTRY * qglPopClientAttrib )(void); +void ( APIENTRY * qglPopMatrix )(void); +void ( APIENTRY * qglPopName )(void); +void ( APIENTRY * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +void ( APIENTRY * qglPushAttrib )(GLbitfield mask); +void ( APIENTRY * qglPushClientAttrib )(GLbitfield mask); +void ( APIENTRY * qglPushMatrix )(void); +void ( APIENTRY * qglPushName )(GLuint name); +void ( APIENTRY * qglRasterPos2d )(GLdouble x, GLdouble y); +void ( APIENTRY * qglRasterPos2dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos2f )(GLfloat x, GLfloat y); +void ( APIENTRY * qglRasterPos2fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos2i )(GLint x, GLint y); +void ( APIENTRY * qglRasterPos2iv )(const GLint *v); +void ( APIENTRY * qglRasterPos2s )(GLshort x, GLshort y); +void ( APIENTRY * qglRasterPos2sv )(const GLshort *v); +void ( APIENTRY * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglRasterPos3dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglRasterPos3fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos3i )(GLint x, GLint y, GLint z); +void ( APIENTRY * qglRasterPos3iv )(const GLint *v); +void ( APIENTRY * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +void ( APIENTRY * qglRasterPos3sv )(const GLshort *v); +void ( APIENTRY * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( APIENTRY * qglRasterPos4dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY * qglRasterPos4fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +void ( APIENTRY * qglRasterPos4iv )(const GLint *v); +void ( APIENTRY * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( APIENTRY * qglRasterPos4sv )(const GLshort *v); +void ( APIENTRY * qglReadBuffer )(GLenum mode); +void ( APIENTRY * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +void ( APIENTRY * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +void ( APIENTRY * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +void ( APIENTRY * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +void ( APIENTRY * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +void ( APIENTRY * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +void ( APIENTRY * qglRectiv )(const GLint *v1, const GLint *v2); +void ( APIENTRY * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +void ( APIENTRY * qglRectsv )(const GLshort *v1, const GLshort *v2); +GLint ( APIENTRY * qglRenderMode )(GLenum mode); +void ( APIENTRY * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY * qglSelectBuffer )(GLsizei size, GLuint *buffer); +void ( APIENTRY * qglShadeModel )(GLenum mode); +void ( APIENTRY * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +void ( APIENTRY * qglStencilMask )(GLuint mask); +void ( APIENTRY * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +void ( APIENTRY * qglTexCoord1d )(GLdouble s); +void ( APIENTRY * qglTexCoord1dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord1f )(GLfloat s); +void ( APIENTRY * qglTexCoord1fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord1i )(GLint s); +void ( APIENTRY * qglTexCoord1iv )(const GLint *v); +void ( APIENTRY * qglTexCoord1s )(GLshort s); +void ( APIENTRY * qglTexCoord1sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord2d )(GLdouble s, GLdouble t); +void ( APIENTRY * qglTexCoord2dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord2f )(GLfloat s, GLfloat t); +void ( APIENTRY * qglTexCoord2fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord2i )(GLint s, GLint t); +void ( APIENTRY * qglTexCoord2iv )(const GLint *v); +void ( APIENTRY * qglTexCoord2s )(GLshort s, GLshort t); +void ( APIENTRY * qglTexCoord2sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +void ( APIENTRY * qglTexCoord3dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +void ( APIENTRY * qglTexCoord3fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord3i )(GLint s, GLint t, GLint r); +void ( APIENTRY * qglTexCoord3iv )(const GLint *v); +void ( APIENTRY * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +void ( APIENTRY * qglTexCoord3sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +void ( APIENTRY * qglTexCoord4dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +void ( APIENTRY * qglTexCoord4fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +void ( APIENTRY * qglTexCoord4iv )(const GLint *v); +void ( APIENTRY * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +void ( APIENTRY * qglTexCoord4sv )(const GLshort *v); +void ( APIENTRY * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +void ( APIENTRY * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +void ( APIENTRY * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +void ( APIENTRY * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +void ( APIENTRY * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +void ( APIENTRY * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglVertex2d )(GLdouble x, GLdouble y); +void ( APIENTRY * qglVertex2dv )(const GLdouble *v); +void ( APIENTRY * qglVertex2f )(GLfloat x, GLfloat y); +void ( APIENTRY * qglVertex2fv )(const GLfloat *v); +void ( APIENTRY * qglVertex2i )(GLint x, GLint y); +void ( APIENTRY * qglVertex2iv )(const GLint *v); +void ( APIENTRY * qglVertex2s )(GLshort x, GLshort y); +void ( APIENTRY * qglVertex2sv )(const GLshort *v); +void ( APIENTRY * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglVertex3dv )(const GLdouble *v); +void ( APIENTRY * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglVertex3fv )(const GLfloat *v); +void ( APIENTRY * qglVertex3i )(GLint x, GLint y, GLint z); +void ( APIENTRY * qglVertex3iv )(const GLint *v); +void ( APIENTRY * qglVertex3s )(GLshort x, GLshort y, GLshort z); +void ( APIENTRY * qglVertex3sv )(const GLshort *v); +void ( APIENTRY * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( APIENTRY * qglVertex4dv )(const GLdouble *v); +void ( APIENTRY * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY * qglVertex4fv )(const GLfloat *v); +void ( APIENTRY * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +void ( APIENTRY * qglVertex4iv )(const GLint *v); +void ( APIENTRY * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( APIENTRY * qglVertex4sv )(const GLshort *v); +void ( APIENTRY * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +void ( APIENTRY * qglLockArraysEXT)( int, int); +void ( APIENTRY * qglUnlockArraysEXT) ( void ); + +void ( APIENTRY * qglPointParameterfEXT)( GLenum param, GLfloat value ); +void ( APIENTRY * qglPointParameterfvEXT)( GLenum param, const GLfloat *value ); +void ( APIENTRY * qglColorTableEXT)( int, int, int, int, int, const void * ); +void ( APIENTRY * qgl3DfxSetPaletteEXT)( GLuint * ); +void ( APIENTRY * qglSelectTextureSGIS)( GLenum ); +void ( APIENTRY * qglMTexCoord2fSGIS)( GLenum, GLfloat, GLfloat ); + +static void ( APIENTRY * dllAccum )(GLenum op, GLfloat value); +static void ( APIENTRY * dllAlphaFunc )(GLenum func, GLclampf ref); +GLboolean ( APIENTRY * dllAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +static void ( APIENTRY * dllArrayElement )(GLint i); +static void ( APIENTRY * dllBegin )(GLenum mode); +static void ( APIENTRY * dllBindTexture )(GLenum target, GLuint texture); +static void ( APIENTRY * dllBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +static void ( APIENTRY * dllBlendFunc )(GLenum sfactor, GLenum dfactor); +static void ( APIENTRY * dllCallList )(GLuint list); +static void ( APIENTRY * dllCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +static void ( APIENTRY * dllClear )(GLbitfield mask); +static void ( APIENTRY * dllClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +static void ( APIENTRY * dllClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +static void ( APIENTRY * dllClearDepth )(GLclampd depth); +static void ( APIENTRY * dllClearIndex )(GLfloat c); +static void ( APIENTRY * dllClearStencil )(GLint s); +static void ( APIENTRY * dllClipPlane )(GLenum plane, const GLdouble *equation); +static void ( APIENTRY * dllColor3b )(GLbyte red, GLbyte green, GLbyte blue); +static void ( APIENTRY * dllColor3bv )(const GLbyte *v); +static void ( APIENTRY * dllColor3d )(GLdouble red, GLdouble green, GLdouble blue); +static void ( APIENTRY * dllColor3dv )(const GLdouble *v); +static void ( APIENTRY * dllColor3f )(GLfloat red, GLfloat green, GLfloat blue); +static void ( APIENTRY * dllColor3fv )(const GLfloat *v); +static void ( APIENTRY * dllColor3i )(GLint red, GLint green, GLint blue); +static void ( APIENTRY * dllColor3iv )(const GLint *v); +static void ( APIENTRY * dllColor3s )(GLshort red, GLshort green, GLshort blue); +static void ( APIENTRY * dllColor3sv )(const GLshort *v); +static void ( APIENTRY * dllColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +static void ( APIENTRY * dllColor3ubv )(const GLubyte *v); +static void ( APIENTRY * dllColor3ui )(GLuint red, GLuint green, GLuint blue); +static void ( APIENTRY * dllColor3uiv )(const GLuint *v); +static void ( APIENTRY * dllColor3us )(GLushort red, GLushort green, GLushort blue); +static void ( APIENTRY * dllColor3usv )(const GLushort *v); +static void ( APIENTRY * dllColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +static void ( APIENTRY * dllColor4bv )(const GLbyte *v); +static void ( APIENTRY * dllColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +static void ( APIENTRY * dllColor4dv )(const GLdouble *v); +static void ( APIENTRY * dllColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +static void ( APIENTRY * dllColor4fv )(const GLfloat *v); +static void ( APIENTRY * dllColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +static void ( APIENTRY * dllColor4iv )(const GLint *v); +static void ( APIENTRY * dllColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +static void ( APIENTRY * dllColor4sv )(const GLshort *v); +static void ( APIENTRY * dllColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +static void ( APIENTRY * dllColor4ubv )(const GLubyte *v); +static void ( APIENTRY * dllColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +static void ( APIENTRY * dllColor4uiv )(const GLuint *v); +static void ( APIENTRY * dllColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +static void ( APIENTRY * dllColor4usv )(const GLushort *v); +static void ( APIENTRY * dllColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +static void ( APIENTRY * dllColorMaterial )(GLenum face, GLenum mode); +static void ( APIENTRY * dllColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +static void ( APIENTRY * dllCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +static void ( APIENTRY * dllCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +static void ( APIENTRY * dllCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +static void ( APIENTRY * dllCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +static void ( APIENTRY * dllCullFace )(GLenum mode); +static void ( APIENTRY * dllDeleteLists )(GLuint list, GLsizei range); +static void ( APIENTRY * dllDeleteTextures )(GLsizei n, const GLuint *textures); +static void ( APIENTRY * dllDepthFunc )(GLenum func); +static void ( APIENTRY * dllDepthMask )(GLboolean flag); +static void ( APIENTRY * dllDepthRange )(GLclampd zNear, GLclampd zFar); +static void ( APIENTRY * dllDisable )(GLenum cap); +static void ( APIENTRY * dllDisableClientState )(GLenum array); +static void ( APIENTRY * dllDrawArrays )(GLenum mode, GLint first, GLsizei count); +static void ( APIENTRY * dllDrawBuffer )(GLenum mode); +static void ( APIENTRY * dllDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +static void ( APIENTRY * dllDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllEdgeFlag )(GLboolean flag); +static void ( APIENTRY * dllEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllEdgeFlagv )(const GLboolean *flag); +static void ( APIENTRY * dllEnable )(GLenum cap); +static void ( APIENTRY * dllEnableClientState )(GLenum array); +static void ( APIENTRY * dllEnd )(void); +static void ( APIENTRY * dllEndList )(void); +static void ( APIENTRY * dllEvalCoord1d )(GLdouble u); +static void ( APIENTRY * dllEvalCoord1dv )(const GLdouble *u); +static void ( APIENTRY * dllEvalCoord1f )(GLfloat u); +static void ( APIENTRY * dllEvalCoord1fv )(const GLfloat *u); +static void ( APIENTRY * dllEvalCoord2d )(GLdouble u, GLdouble v); +static void ( APIENTRY * dllEvalCoord2dv )(const GLdouble *u); +static void ( APIENTRY * dllEvalCoord2f )(GLfloat u, GLfloat v); +static void ( APIENTRY * dllEvalCoord2fv )(const GLfloat *u); +static void ( APIENTRY * dllEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +static void ( APIENTRY * dllEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +static void ( APIENTRY * dllEvalPoint1 )(GLint i); +static void ( APIENTRY * dllEvalPoint2 )(GLint i, GLint j); +static void ( APIENTRY * dllFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +static void ( APIENTRY * dllFinish )(void); +static void ( APIENTRY * dllFlush )(void); +static void ( APIENTRY * dllFogf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllFogfv )(GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllFogi )(GLenum pname, GLint param); +static void ( APIENTRY * dllFogiv )(GLenum pname, const GLint *params); +static void ( APIENTRY * dllFrontFace )(GLenum mode); +static void ( APIENTRY * dllFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLuint ( APIENTRY * dllGenLists )(GLsizei range); +static void ( APIENTRY * dllGenTextures )(GLsizei n, GLuint *textures); +static void ( APIENTRY * dllGetBooleanv )(GLenum pname, GLboolean *params); +static void ( APIENTRY * dllGetClipPlane )(GLenum plane, GLdouble *equation); +static void ( APIENTRY * dllGetDoublev )(GLenum pname, GLdouble *params); +GLenum ( APIENTRY * dllGetError )(void); +static void ( APIENTRY * dllGetFloatv )(GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetIntegerv )(GLenum pname, GLint *params); +static void ( APIENTRY * dllGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetLightiv )(GLenum light, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetMapdv )(GLenum target, GLenum query, GLdouble *v); +static void ( APIENTRY * dllGetMapfv )(GLenum target, GLenum query, GLfloat *v); +static void ( APIENTRY * dllGetMapiv )(GLenum target, GLenum query, GLint *v); +static void ( APIENTRY * dllGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetPixelMapfv )(GLenum map, GLfloat *values); +static void ( APIENTRY * dllGetPixelMapuiv )(GLenum map, GLuint *values); +static void ( APIENTRY * dllGetPixelMapusv )(GLenum map, GLushort *values); +static void ( APIENTRY * dllGetPointerv )(GLenum pname, GLvoid* *params); +static void ( APIENTRY * dllGetPolygonStipple )(GLubyte *mask); +const GLubyte * ( APIENTRY * dllGetString )(GLenum name); +static void ( APIENTRY * dllGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +static void ( APIENTRY * dllGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +static void ( APIENTRY * dllGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +static void ( APIENTRY * dllHint )(GLenum target, GLenum mode); +static void ( APIENTRY * dllIndexMask )(GLuint mask); +static void ( APIENTRY * dllIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllIndexd )(GLdouble c); +static void ( APIENTRY * dllIndexdv )(const GLdouble *c); +static void ( APIENTRY * dllIndexf )(GLfloat c); +static void ( APIENTRY * dllIndexfv )(const GLfloat *c); +static void ( APIENTRY * dllIndexi )(GLint c); +static void ( APIENTRY * dllIndexiv )(const GLint *c); +static void ( APIENTRY * dllIndexs )(GLshort c); +static void ( APIENTRY * dllIndexsv )(const GLshort *c); +static void ( APIENTRY * dllIndexub )(GLubyte c); +static void ( APIENTRY * dllIndexubv )(const GLubyte *c); +static void ( APIENTRY * dllInitNames )(void); +static void ( APIENTRY * dllInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +GLboolean ( APIENTRY * dllIsEnabled )(GLenum cap); +GLboolean ( APIENTRY * dllIsList )(GLuint list); +GLboolean ( APIENTRY * dllIsTexture )(GLuint texture); +static void ( APIENTRY * dllLightModelf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllLightModelfv )(GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllLightModeli )(GLenum pname, GLint param); +static void ( APIENTRY * dllLightModeliv )(GLenum pname, const GLint *params); +static void ( APIENTRY * dllLightf )(GLenum light, GLenum pname, GLfloat param); +static void ( APIENTRY * dllLightfv )(GLenum light, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllLighti )(GLenum light, GLenum pname, GLint param); +static void ( APIENTRY * dllLightiv )(GLenum light, GLenum pname, const GLint *params); +static void ( APIENTRY * dllLineStipple )(GLint factor, GLushort pattern); +static void ( APIENTRY * dllLineWidth )(GLfloat width); +static void ( APIENTRY * dllListBase )(GLuint base); +static void ( APIENTRY * dllLoadIdentity )(void); +static void ( APIENTRY * dllLoadMatrixd )(const GLdouble *m); +static void ( APIENTRY * dllLoadMatrixf )(const GLfloat *m); +static void ( APIENTRY * dllLoadName )(GLuint name); +static void ( APIENTRY * dllLogicOp )(GLenum opcode); +static void ( APIENTRY * dllMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +static void ( APIENTRY * dllMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +static void ( APIENTRY * dllMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +static void ( APIENTRY * dllMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +static void ( APIENTRY * dllMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +static void ( APIENTRY * dllMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +static void ( APIENTRY * dllMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +static void ( APIENTRY * dllMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +static void ( APIENTRY * dllMaterialf )(GLenum face, GLenum pname, GLfloat param); +static void ( APIENTRY * dllMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllMateriali )(GLenum face, GLenum pname, GLint param); +static void ( APIENTRY * dllMaterialiv )(GLenum face, GLenum pname, const GLint *params); +static void ( APIENTRY * dllMatrixMode )(GLenum mode); +static void ( APIENTRY * dllMultMatrixd )(const GLdouble *m); +static void ( APIENTRY * dllMultMatrixf )(const GLfloat *m); +static void ( APIENTRY * dllNewList )(GLuint list, GLenum mode); +static void ( APIENTRY * dllNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +static void ( APIENTRY * dllNormal3bv )(const GLbyte *v); +static void ( APIENTRY * dllNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +static void ( APIENTRY * dllNormal3dv )(const GLdouble *v); +static void ( APIENTRY * dllNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +static void ( APIENTRY * dllNormal3fv )(const GLfloat *v); +static void ( APIENTRY * dllNormal3i )(GLint nx, GLint ny, GLint nz); +static void ( APIENTRY * dllNormal3iv )(const GLint *v); +static void ( APIENTRY * dllNormal3s )(GLshort nx, GLshort ny, GLshort nz); +static void ( APIENTRY * dllNormal3sv )(const GLshort *v); +static void ( APIENTRY * dllNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +static void ( APIENTRY * dllPassThrough )(GLfloat token); +static void ( APIENTRY * dllPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +static void ( APIENTRY * dllPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +static void ( APIENTRY * dllPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +static void ( APIENTRY * dllPixelStoref )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllPixelStorei )(GLenum pname, GLint param); +static void ( APIENTRY * dllPixelTransferf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllPixelTransferi )(GLenum pname, GLint param); +static void ( APIENTRY * dllPixelZoom )(GLfloat xfactor, GLfloat yfactor); +static void ( APIENTRY * dllPointSize )(GLfloat size); +static void ( APIENTRY * dllPolygonMode )(GLenum face, GLenum mode); +static void ( APIENTRY * dllPolygonOffset )(GLfloat factor, GLfloat units); +static void ( APIENTRY * dllPolygonStipple )(const GLubyte *mask); +static void ( APIENTRY * dllPopAttrib )(void); +static void ( APIENTRY * dllPopClientAttrib )(void); +static void ( APIENTRY * dllPopMatrix )(void); +static void ( APIENTRY * dllPopName )(void); +static void ( APIENTRY * dllPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +static void ( APIENTRY * dllPushAttrib )(GLbitfield mask); +static void ( APIENTRY * dllPushClientAttrib )(GLbitfield mask); +static void ( APIENTRY * dllPushMatrix )(void); +static void ( APIENTRY * dllPushName )(GLuint name); +static void ( APIENTRY * dllRasterPos2d )(GLdouble x, GLdouble y); +static void ( APIENTRY * dllRasterPos2dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos2f )(GLfloat x, GLfloat y); +static void ( APIENTRY * dllRasterPos2fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos2i )(GLint x, GLint y); +static void ( APIENTRY * dllRasterPos2iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos2s )(GLshort x, GLshort y); +static void ( APIENTRY * dllRasterPos2sv )(const GLshort *v); +static void ( APIENTRY * dllRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllRasterPos3dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllRasterPos3fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos3i )(GLint x, GLint y, GLint z); +static void ( APIENTRY * dllRasterPos3iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos3s )(GLshort x, GLshort y, GLshort z); +static void ( APIENTRY * dllRasterPos3sv )(const GLshort *v); +static void ( APIENTRY * dllRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +static void ( APIENTRY * dllRasterPos4dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +static void ( APIENTRY * dllRasterPos4fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +static void ( APIENTRY * dllRasterPos4iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +static void ( APIENTRY * dllRasterPos4sv )(const GLshort *v); +static void ( APIENTRY * dllReadBuffer )(GLenum mode); +static void ( APIENTRY * dllReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +static void ( APIENTRY * dllRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +static void ( APIENTRY * dllRectdv )(const GLdouble *v1, const GLdouble *v2); +static void ( APIENTRY * dllRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +static void ( APIENTRY * dllRectfv )(const GLfloat *v1, const GLfloat *v2); +static void ( APIENTRY * dllRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +static void ( APIENTRY * dllRectiv )(const GLint *v1, const GLint *v2); +static void ( APIENTRY * dllRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +static void ( APIENTRY * dllRectsv )(const GLshort *v1, const GLshort *v2); +GLint ( APIENTRY * dllRenderMode )(GLenum mode); +static void ( APIENTRY * dllRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllScaled )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllScalef )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +static void ( APIENTRY * dllSelectBuffer )(GLsizei size, GLuint *buffer); +static void ( APIENTRY * dllShadeModel )(GLenum mode); +static void ( APIENTRY * dllStencilFunc )(GLenum func, GLint ref, GLuint mask); +static void ( APIENTRY * dllStencilMask )(GLuint mask); +static void ( APIENTRY * dllStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +static void ( APIENTRY * dllTexCoord1d )(GLdouble s); +static void ( APIENTRY * dllTexCoord1dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord1f )(GLfloat s); +static void ( APIENTRY * dllTexCoord1fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord1i )(GLint s); +static void ( APIENTRY * dllTexCoord1iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord1s )(GLshort s); +static void ( APIENTRY * dllTexCoord1sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord2d )(GLdouble s, GLdouble t); +static void ( APIENTRY * dllTexCoord2dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord2f )(GLfloat s, GLfloat t); +static void ( APIENTRY * dllTexCoord2fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord2i )(GLint s, GLint t); +static void ( APIENTRY * dllTexCoord2iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord2s )(GLshort s, GLshort t); +static void ( APIENTRY * dllTexCoord2sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +static void ( APIENTRY * dllTexCoord3dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +static void ( APIENTRY * dllTexCoord3fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord3i )(GLint s, GLint t, GLint r); +static void ( APIENTRY * dllTexCoord3iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord3s )(GLshort s, GLshort t, GLshort r); +static void ( APIENTRY * dllTexCoord3sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +static void ( APIENTRY * dllTexCoord4dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +static void ( APIENTRY * dllTexCoord4fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +static void ( APIENTRY * dllTexCoord4iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +static void ( APIENTRY * dllTexCoord4sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllTexEnvf )(GLenum target, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexEnvi )(GLenum target, GLenum pname, GLint param); +static void ( APIENTRY * dllTexEnviv )(GLenum target, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexGend )(GLenum coord, GLenum pname, GLdouble param); +static void ( APIENTRY * dllTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +static void ( APIENTRY * dllTexGenf )(GLenum coord, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexGeni )(GLenum coord, GLenum pname, GLint param); +static void ( APIENTRY * dllTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexParameterf )(GLenum target, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexParameteri )(GLenum target, GLenum pname, GLint param); +static void ( APIENTRY * dllTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTranslated )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllTranslatef )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllVertex2d )(GLdouble x, GLdouble y); +static void ( APIENTRY * dllVertex2dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex2f )(GLfloat x, GLfloat y); +static void ( APIENTRY * dllVertex2fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex2i )(GLint x, GLint y); +static void ( APIENTRY * dllVertex2iv )(const GLint *v); +static void ( APIENTRY * dllVertex2s )(GLshort x, GLshort y); +static void ( APIENTRY * dllVertex2sv )(const GLshort *v); +static void ( APIENTRY * dllVertex3d )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllVertex3dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex3f )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllVertex3fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex3i )(GLint x, GLint y, GLint z); +static void ( APIENTRY * dllVertex3iv )(const GLint *v); +static void ( APIENTRY * dllVertex3s )(GLshort x, GLshort y, GLshort z); +static void ( APIENTRY * dllVertex3sv )(const GLshort *v); +static void ( APIENTRY * dllVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +static void ( APIENTRY * dllVertex4dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +static void ( APIENTRY * dllVertex4fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex4i )(GLint x, GLint y, GLint z, GLint w); +static void ( APIENTRY * dllVertex4iv )(const GLint *v); +static void ( APIENTRY * dllVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +static void ( APIENTRY * dllVertex4sv )(const GLshort *v); +static void ( APIENTRY * dllVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +static void APIENTRY logAccum(GLenum op, GLfloat value) +{ + fprintf( glw_state.log_fp, "glAccum\n" ); + dllAccum( op, value ); +} + +static void APIENTRY logAlphaFunc(GLenum func, GLclampf ref) +{ + fprintf( glw_state.log_fp, "glAlphaFunc( 0x%x, %f )\n", func, ref ); + dllAlphaFunc( func, ref ); +} + +static GLboolean APIENTRY logAreTexturesResident(GLsizei n, const GLuint *textures, GLboolean *residences) +{ + fprintf( glw_state.log_fp, "glAreTexturesResident\n" ); + return dllAreTexturesResident( n, textures, residences ); +} + +static void APIENTRY logArrayElement(GLint i) +{ + fprintf( glw_state.log_fp, "glArrayElement\n" ); + dllArrayElement( i ); +} + +static void APIENTRY logBegin(GLenum mode) +{ + fprintf( glw_state.log_fp, "glBegin( 0x%x )\n", mode ); + dllBegin( mode ); +} + +static void APIENTRY logBindTexture(GLenum target, GLuint texture) +{ + fprintf( glw_state.log_fp, "glBindTexture( 0x%x, %u )\n", target, texture ); + dllBindTexture( target, texture ); +} + +static void APIENTRY logBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap) +{ + fprintf( glw_state.log_fp, "glBitmap\n" ); + dllBitmap( width, height, xorig, yorig, xmove, ymove, bitmap ); +} + +static void APIENTRY logBlendFunc(GLenum sfactor, GLenum dfactor) +{ + fprintf( glw_state.log_fp, "glBlendFunc( 0x%x, 0x%x )\n", sfactor, dfactor ); + dllBlendFunc( sfactor, dfactor ); +} + +static void APIENTRY logCallList(GLuint list) +{ + fprintf( glw_state.log_fp, "glCallList( %u )\n", list ); + dllCallList( list ); +} + +static void APIENTRY logCallLists(GLsizei n, GLenum type, const void *lists) +{ + fprintf( glw_state.log_fp, "glCallLists\n" ); + dllCallLists( n, type, lists ); +} + +static void APIENTRY logClear(GLbitfield mask) +{ + fprintf( glw_state.log_fp, "glClear\n" ); + dllClear( mask ); +} + +static void APIENTRY logClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + fprintf( glw_state.log_fp, "glClearAccum\n" ); + dllClearAccum( red, green, blue, alpha ); +} + +static void APIENTRY logClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) +{ + fprintf( glw_state.log_fp, "glClearColor\n" ); + dllClearColor( red, green, blue, alpha ); +} + +static void APIENTRY logClearDepth(GLclampd depth) +{ + fprintf( glw_state.log_fp, "glClearDepth\n" ); + dllClearDepth( depth ); +} + +static void APIENTRY logClearIndex(GLfloat c) +{ + fprintf( glw_state.log_fp, "glClearIndex\n" ); + dllClearIndex( c ); +} + +static void APIENTRY logClearStencil(GLint s) +{ + fprintf( glw_state.log_fp, "glClearStencil\n" ); + dllClearStencil( s ); +} + +static void APIENTRY logClipPlane(GLenum plane, const GLdouble *equation) +{ + fprintf( glw_state.log_fp, "glClipPlane\n" ); + dllClipPlane( plane, equation ); +} + +static void APIENTRY logColor3b(GLbyte red, GLbyte green, GLbyte blue) +{ + fprintf( glw_state.log_fp, "glColor3b\n" ); + dllColor3b( red, green, blue ); +} + +static void APIENTRY logColor3bv(const GLbyte *v) +{ + fprintf( glw_state.log_fp, "glColor3bv\n" ); + dllColor3bv( v ); +} + +static void APIENTRY logColor3d(GLdouble red, GLdouble green, GLdouble blue) +{ + fprintf( glw_state.log_fp, "glColor3d\n" ); + dllColor3d( red, green, blue ); +} + +static void APIENTRY logColor3dv(const GLdouble *v) +{ + fprintf( glw_state.log_fp, "glColor3dv\n" ); + dllColor3dv( v ); +} + +static void APIENTRY logColor3f(GLfloat red, GLfloat green, GLfloat blue) +{ + fprintf( glw_state.log_fp, "glColor3f\n" ); + dllColor3f( red, green, blue ); +} + +static void APIENTRY logColor3fv(const GLfloat *v) +{ + fprintf( glw_state.log_fp, "glColor3fv\n" ); + dllColor3fv( v ); +} + +static void APIENTRY logColor3i(GLint red, GLint green, GLint blue) +{ + fprintf( glw_state.log_fp, "glColor3i\n" ); + dllColor3i( red, green, blue ); +} + +static void APIENTRY logColor3iv(const GLint *v) +{ + fprintf( glw_state.log_fp, "glColor3iv\n" ); + dllColor3iv( v ); +} + +static void APIENTRY logColor3s(GLshort red, GLshort green, GLshort blue) +{ + fprintf( glw_state.log_fp, "glColor3s\n" ); + dllColor3s( red, green, blue ); +} + +static void APIENTRY logColor3sv(const GLshort *v) +{ + fprintf( glw_state.log_fp, "glColor3sv\n" ); + dllColor3sv( v ); +} + +static void APIENTRY logColor3ub(GLubyte red, GLubyte green, GLubyte blue) +{ + fprintf( glw_state.log_fp, "glColor3ub\n" ); + dllColor3ub( red, green, blue ); +} + +static void APIENTRY logColor3ubv(const GLubyte *v) +{ + fprintf( glw_state.log_fp, "glColor3ubv\n" ); + dllColor3ubv( v ); +} + +#define SIG( x ) fprintf( glw_state.log_fp, x "\n" ) + +static void APIENTRY logColor3ui(GLuint red, GLuint green, GLuint blue) +{ + SIG( "glColor3ui" ); + dllColor3ui( red, green, blue ); +} + +static void APIENTRY logColor3uiv(const GLuint *v) +{ + SIG( "glColor3uiv" ); + dllColor3uiv( v ); +} + +static void APIENTRY logColor3us(GLushort red, GLushort green, GLushort blue) +{ + SIG( "glColor3us" ); + dllColor3us( red, green, blue ); +} + +static void APIENTRY logColor3usv(const GLushort *v) +{ + SIG( "glColor3usv" ); + dllColor3usv( v ); +} + +static void APIENTRY logColor4b(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha) +{ + SIG( "glColor4b" ); + dllColor4b( red, green, blue, alpha ); +} + +static void APIENTRY logColor4bv(const GLbyte *v) +{ + SIG( "glColor4bv" ); + dllColor4bv( v ); +} + +static void APIENTRY logColor4d(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha) +{ + SIG( "glColor4d" ); + dllColor4d( red, green, blue, alpha ); +} +static void APIENTRY logColor4dv(const GLdouble *v) +{ + SIG( "glColor4dv" ); + dllColor4dv( v ); +} +static void APIENTRY logColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + fprintf( glw_state.log_fp, "glColor4f( %f,%f,%f,%f )\n", red, green, blue, alpha ); + dllColor4f( red, green, blue, alpha ); +} +static void APIENTRY logColor4fv(const GLfloat *v) +{ + fprintf( glw_state.log_fp, "glColor4fv( %f,%f,%f,%f )\n", v[0], v[1], v[2], v[3] ); + dllColor4fv( v ); +} +static void APIENTRY logColor4i(GLint red, GLint green, GLint blue, GLint alpha) +{ + SIG( "glColor4i" ); + dllColor4i( red, green, blue, alpha ); +} +static void APIENTRY logColor4iv(const GLint *v) +{ + SIG( "glColor4iv" ); + dllColor4iv( v ); +} +static void APIENTRY logColor4s(GLshort red, GLshort green, GLshort blue, GLshort alpha) +{ + SIG( "glColor4s" ); + dllColor4s( red, green, blue, alpha ); +} +static void APIENTRY logColor4sv(const GLshort *v) +{ + SIG( "glColor4sv" ); + dllColor4sv( v ); +} +static void APIENTRY logColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) +{ + SIG( "glColor4b" ); + dllColor4b( red, green, blue, alpha ); +} +static void APIENTRY logColor4ubv(const GLubyte *v) +{ + SIG( "glColor4ubv" ); + dllColor4ubv( v ); +} +static void APIENTRY logColor4ui(GLuint red, GLuint green, GLuint blue, GLuint alpha) +{ + SIG( "glColor4ui" ); + dllColor4ui( red, green, blue, alpha ); +} +static void APIENTRY logColor4uiv(const GLuint *v) +{ + SIG( "glColor4uiv" ); + dllColor4uiv( v ); +} +static void APIENTRY logColor4us(GLushort red, GLushort green, GLushort blue, GLushort alpha) +{ + SIG( "glColor4us" ); + dllColor4us( red, green, blue, alpha ); +} +static void APIENTRY logColor4usv(const GLushort *v) +{ + SIG( "glColor4usv" ); + dllColor4usv( v ); +} +static void APIENTRY logColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) +{ + SIG( "glColorMask" ); + dllColorMask( red, green, blue, alpha ); +} +static void APIENTRY logColorMaterial(GLenum face, GLenum mode) +{ + SIG( "glColorMaterial" ); + dllColorMaterial( face, mode ); +} + +static void APIENTRY logColorPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) +{ + SIG( "glColorPointer" ); + dllColorPointer( size, type, stride, pointer ); +} + +static void APIENTRY logCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type) +{ + SIG( "glCopyPixels" ); + dllCopyPixels( x, y, width, height, type ); +} + +static void APIENTRY logCopyTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border) +{ + SIG( "glCopyTexImage1D" ); + dllCopyTexImage1D( target, level, internalFormat, x, y, width, border ); +} + +static void APIENTRY logCopyTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) +{ + SIG( "glCopyTexImage2D" ); + dllCopyTexImage2D( target, level, internalFormat, x, y, width, height, border ); +} + +static void APIENTRY logCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) +{ + SIG( "glCopyTexSubImage1D" ); + dllCopyTexSubImage1D( target, level, xoffset, x, y, width ); +} + +static void APIENTRY logCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) +{ + SIG( "glCopyTexSubImage2D" ); + dllCopyTexSubImage2D( target, level, xoffset, yoffset, x, y, width, height ); +} + +static void APIENTRY logCullFace(GLenum mode) +{ + SIG( "glCullFace" ); + dllCullFace( mode ); +} + +static void APIENTRY logDeleteLists(GLuint list, GLsizei range) +{ + SIG( "glDeleteLists" ); + dllDeleteLists( list, range ); +} + +static void APIENTRY logDeleteTextures(GLsizei n, const GLuint *textures) +{ + SIG( "glDeleteTextures" ); + dllDeleteTextures( n, textures ); +} + +static void APIENTRY logDepthFunc(GLenum func) +{ + SIG( "glDepthFunc" ); + dllDepthFunc( func ); +} + +static void APIENTRY logDepthMask(GLboolean flag) +{ + SIG( "glDepthMask" ); + dllDepthMask( flag ); +} + +static void APIENTRY logDepthRange(GLclampd zNear, GLclampd zFar) +{ + SIG( "glDepthRange" ); + dllDepthRange( zNear, zFar ); +} + +static void APIENTRY logDisable(GLenum cap) +{ + fprintf( glw_state.log_fp, "glDisable( 0x%x )\n", cap ); + dllDisable( cap ); +} + +static void APIENTRY logDisableClientState(GLenum array) +{ + SIG( "glDisableClientState" ); + dllDisableClientState( array ); +} + +static void APIENTRY logDrawArrays(GLenum mode, GLint first, GLsizei count) +{ + SIG( "glDrawArrays" ); + dllDrawArrays( mode, first, count ); +} + +static void APIENTRY logDrawBuffer(GLenum mode) +{ + SIG( "glDrawBuffer" ); + dllDrawBuffer( mode ); +} + +static void APIENTRY logDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices) +{ + SIG( "glDrawElements" ); + dllDrawElements( mode, count, type, indices ); +} + +static void APIENTRY logDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glDrawPixels" ); + dllDrawPixels( width, height, format, type, pixels ); +} + +static void APIENTRY logEdgeFlag(GLboolean flag) +{ + SIG( "glEdgeFlag" ); + dllEdgeFlag( flag ); +} + +static void APIENTRY logEdgeFlagPointer(GLsizei stride, const void *pointer) +{ + SIG( "glEdgeFlagPointer" ); + dllEdgeFlagPointer( stride, pointer ); +} + +static void APIENTRY logEdgeFlagv(const GLboolean *flag) +{ + SIG( "glEdgeFlagv" ); + dllEdgeFlagv( flag ); +} + +static void APIENTRY logEnable(GLenum cap) +{ + fprintf( glw_state.log_fp, "glEnable( 0x%x )\n", cap ); + dllEnable( cap ); +} + +static void APIENTRY logEnableClientState(GLenum array) +{ + SIG( "glEnableClientState" ); + dllEnableClientState( array ); +} + +static void APIENTRY logEnd(void) +{ + SIG( "glEnd" ); + dllEnd(); +} + +static void APIENTRY logEndList(void) +{ + SIG( "glEndList" ); + dllEndList(); +} + +static void APIENTRY logEvalCoord1d(GLdouble u) +{ + SIG( "glEvalCoord1d" ); + dllEvalCoord1d( u ); +} + +static void APIENTRY logEvalCoord1dv(const GLdouble *u) +{ + SIG( "glEvalCoord1dv" ); + dllEvalCoord1dv( u ); +} + +static void APIENTRY logEvalCoord1f(GLfloat u) +{ + SIG( "glEvalCoord1f" ); + dllEvalCoord1f( u ); +} + +static void APIENTRY logEvalCoord1fv(const GLfloat *u) +{ + SIG( "glEvalCoord1fv" ); + dllEvalCoord1fv( u ); +} +static void APIENTRY logEvalCoord2d(GLdouble u, GLdouble v) +{ + SIG( "glEvalCoord2d" ); + dllEvalCoord2d( u, v ); +} +static void APIENTRY logEvalCoord2dv(const GLdouble *u) +{ + SIG( "glEvalCoord2dv" ); + dllEvalCoord2dv( u ); +} +static void APIENTRY logEvalCoord2f(GLfloat u, GLfloat v) +{ + SIG( "glEvalCoord2f" ); + dllEvalCoord2f( u, v ); +} +static void APIENTRY logEvalCoord2fv(const GLfloat *u) +{ + SIG( "glEvalCoord2fv" ); + dllEvalCoord2fv( u ); +} + +static void APIENTRY logEvalMesh1(GLenum mode, GLint i1, GLint i2) +{ + SIG( "glEvalMesh1" ); + dllEvalMesh1( mode, i1, i2 ); +} +static void APIENTRY logEvalMesh2(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2) +{ + SIG( "glEvalMesh2" ); + dllEvalMesh2( mode, i1, i2, j1, j2 ); +} +static void APIENTRY logEvalPoint1(GLint i) +{ + SIG( "glEvalPoint1" ); + dllEvalPoint1( i ); +} +static void APIENTRY logEvalPoint2(GLint i, GLint j) +{ + SIG( "glEvalPoint2" ); + dllEvalPoint2( i, j ); +} + +static void APIENTRY logFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer) +{ + SIG( "glFeedbackBuffer" ); + dllFeedbackBuffer( size, type, buffer ); +} + +static void APIENTRY logFinish(void) +{ + SIG( "glFinish" ); + dllFinish(); +} + +static void APIENTRY logFlush(void) +{ + SIG( "glFlush" ); + dllFlush(); +} + +static void APIENTRY logFogf(GLenum pname, GLfloat param) +{ + SIG( "glFogf" ); + dllFogf( pname, param ); +} + +static void APIENTRY logFogfv(GLenum pname, const GLfloat *params) +{ + SIG( "glFogfv" ); + dllFogfv( pname, params ); +} + +static void APIENTRY logFogi(GLenum pname, GLint param) +{ + SIG( "glFogi" ); + dllFogi( pname, param ); +} + +static void APIENTRY logFogiv(GLenum pname, const GLint *params) +{ + SIG( "glFogiv" ); + dllFogiv( pname, params ); +} + +static void APIENTRY logFrontFace(GLenum mode) +{ + SIG( "glFrontFace" ); + dllFrontFace( mode ); +} + +static void APIENTRY logFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + SIG( "glFrustum" ); + dllFrustum( left, right, bottom, top, zNear, zFar ); +} + +static GLuint APIENTRY logGenLists(GLsizei range) +{ + SIG( "glGenLists" ); + return dllGenLists( range ); +} + +static void APIENTRY logGenTextures(GLsizei n, GLuint *textures) +{ + SIG( "glGenTextures" ); + dllGenTextures( n, textures ); +} + +static void APIENTRY logGetBooleanv(GLenum pname, GLboolean *params) +{ + SIG( "glGetBooleanv" ); + dllGetBooleanv( pname, params ); +} + +static void APIENTRY logGetClipPlane(GLenum plane, GLdouble *equation) +{ + SIG( "glGetClipPlane" ); + dllGetClipPlane( plane, equation ); +} + +static void APIENTRY logGetDoublev(GLenum pname, GLdouble *params) +{ + SIG( "glGetDoublev" ); + dllGetDoublev( pname, params ); +} + +static GLenum APIENTRY logGetError(void) +{ + SIG( "glGetError" ); + return dllGetError(); +} + +static void APIENTRY logGetFloatv(GLenum pname, GLfloat *params) +{ + SIG( "glGetFloatv" ); + dllGetFloatv( pname, params ); +} + +static void APIENTRY logGetIntegerv(GLenum pname, GLint *params) +{ + SIG( "glGetIntegerv" ); + dllGetIntegerv( pname, params ); +} + +static void APIENTRY logGetLightfv(GLenum light, GLenum pname, GLfloat *params) +{ + SIG( "glGetLightfv" ); + dllGetLightfv( light, pname, params ); +} + +static void APIENTRY logGetLightiv(GLenum light, GLenum pname, GLint *params) +{ + SIG( "glGetLightiv" ); + dllGetLightiv( light, pname, params ); +} + +static void APIENTRY logGetMapdv(GLenum target, GLenum query, GLdouble *v) +{ + SIG( "glGetMapdv" ); + dllGetMapdv( target, query, v ); +} + +static void APIENTRY logGetMapfv(GLenum target, GLenum query, GLfloat *v) +{ + SIG( "glGetMapfv" ); + dllGetMapfv( target, query, v ); +} + +static void APIENTRY logGetMapiv(GLenum target, GLenum query, GLint *v) +{ + SIG( "glGetMapiv" ); + dllGetMapiv( target, query, v ); +} + +static void APIENTRY logGetMaterialfv(GLenum face, GLenum pname, GLfloat *params) +{ + SIG( "glGetMaterialfv" ); + dllGetMaterialfv( face, pname, params ); +} + +static void APIENTRY logGetMaterialiv(GLenum face, GLenum pname, GLint *params) +{ + SIG( "glGetMaterialiv" ); + dllGetMaterialiv( face, pname, params ); +} + +static void APIENTRY logGetPixelMapfv(GLenum map, GLfloat *values) +{ + SIG( "glGetPixelMapfv" ); + dllGetPixelMapfv( map, values ); +} + +static void APIENTRY logGetPixelMapuiv(GLenum map, GLuint *values) +{ + SIG( "glGetPixelMapuiv" ); + dllGetPixelMapuiv( map, values ); +} + +static void APIENTRY logGetPixelMapusv(GLenum map, GLushort *values) +{ + SIG( "glGetPixelMapusv" ); + dllGetPixelMapusv( map, values ); +} + +static void APIENTRY logGetPointerv(GLenum pname, GLvoid* *params) +{ + SIG( "glGetPointerv" ); + dllGetPointerv( pname, params ); +} + +static void APIENTRY logGetPolygonStipple(GLubyte *mask) +{ + SIG( "glGetPolygonStipple" ); + dllGetPolygonStipple( mask ); +} + +static const GLubyte * APIENTRY logGetString(GLenum name) +{ + SIG( "glGetString" ); + return dllGetString( name ); +} + +static void APIENTRY logGetTexEnvfv(GLenum target, GLenum pname, GLfloat *params) +{ + SIG( "glGetTexEnvfv" ); + dllGetTexEnvfv( target, pname, params ); +} + +static void APIENTRY logGetTexEnviv(GLenum target, GLenum pname, GLint *params) +{ + SIG( "glGetTexEnviv" ); + dllGetTexEnviv( target, pname, params ); +} + +static void APIENTRY logGetTexGendv(GLenum coord, GLenum pname, GLdouble *params) +{ + SIG( "glGetTexGendv" ); + dllGetTexGendv( coord, pname, params ); +} + +static void APIENTRY logGetTexGenfv(GLenum coord, GLenum pname, GLfloat *params) +{ + SIG( "glGetTexGenfv" ); + dllGetTexGenfv( coord, pname, params ); +} + +static void APIENTRY logGetTexGeniv(GLenum coord, GLenum pname, GLint *params) +{ + SIG( "glGetTexGeniv" ); + dllGetTexGeniv( coord, pname, params ); +} + +static void APIENTRY logGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void *pixels) +{ + SIG( "glGetTexImage" ); + dllGetTexImage( target, level, format, type, pixels ); +} +static void APIENTRY logGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params ) +{ + SIG( "glGetTexLevelParameterfv" ); + dllGetTexLevelParameterfv( target, level, pname, params ); +} + +static void APIENTRY logGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params) +{ + SIG( "glGetTexLevelParameteriv" ); + dllGetTexLevelParameteriv( target, level, pname, params ); +} + +static void APIENTRY logGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params) +{ + SIG( "glGetTexParameterfv" ); + dllGetTexParameterfv( target, pname, params ); +} + +static void APIENTRY logGetTexParameteriv(GLenum target, GLenum pname, GLint *params) +{ + SIG( "glGetTexParameteriv" ); + dllGetTexParameteriv( target, pname, params ); +} + +static void APIENTRY logHint(GLenum target, GLenum mode) +{ + fprintf( glw_state.log_fp, "glHint( 0x%x, 0x%x )\n", target, mode ); + dllHint( target, mode ); +} + +static void APIENTRY logIndexMask(GLuint mask) +{ + SIG( "glIndexMask" ); + dllIndexMask( mask ); +} + +static void APIENTRY logIndexPointer(GLenum type, GLsizei stride, const void *pointer) +{ + SIG( "glIndexPointer" ); + dllIndexPointer( type, stride, pointer ); +} + +static void APIENTRY logIndexd(GLdouble c) +{ + SIG( "glIndexd" ); + dllIndexd( c ); +} + +static void APIENTRY logIndexdv(const GLdouble *c) +{ + SIG( "glIndexdv" ); + dllIndexdv( c ); +} + +static void APIENTRY logIndexf(GLfloat c) +{ + SIG( "glIndexf" ); + dllIndexf( c ); +} + +static void APIENTRY logIndexfv(const GLfloat *c) +{ + SIG( "glIndexfv" ); + dllIndexfv( c ); +} + +static void APIENTRY logIndexi(GLint c) +{ + SIG( "glIndexi" ); + dllIndexi( c ); +} + +static void APIENTRY logIndexiv(const GLint *c) +{ + SIG( "glIndexiv" ); + dllIndexiv( c ); +} + +static void APIENTRY logIndexs(GLshort c) +{ + SIG( "glIndexs" ); + dllIndexs( c ); +} + +static void APIENTRY logIndexsv(const GLshort *c) +{ + SIG( "glIndexsv" ); + dllIndexsv( c ); +} + +static void APIENTRY logIndexub(GLubyte c) +{ + SIG( "glIndexub" ); + dllIndexub( c ); +} + +static void APIENTRY logIndexubv(const GLubyte *c) +{ + SIG( "glIndexubv" ); + dllIndexubv( c ); +} + +static void APIENTRY logInitNames(void) +{ + SIG( "glInitNames" ); + dllInitNames(); +} + +static void APIENTRY logInterleavedArrays(GLenum format, GLsizei stride, const void *pointer) +{ + SIG( "glInterleavedArrays" ); + dllInterleavedArrays( format, stride, pointer ); +} + +static GLboolean APIENTRY logIsEnabled(GLenum cap) +{ + SIG( "glIsEnabled" ); + return dllIsEnabled( cap ); +} +static GLboolean APIENTRY logIsList(GLuint list) +{ + SIG( "glIsList" ); + return dllIsList( list ); +} +static GLboolean APIENTRY logIsTexture(GLuint texture) +{ + SIG( "glIsTexture" ); + return dllIsTexture( texture ); +} + +static void APIENTRY logLightModelf(GLenum pname, GLfloat param) +{ + SIG( "glLightModelf" ); + dllLightModelf( pname, param ); +} + +static void APIENTRY logLightModelfv(GLenum pname, const GLfloat *params) +{ + SIG( "glLightModelfv" ); + dllLightModelfv( pname, params ); +} + +static void APIENTRY logLightModeli(GLenum pname, GLint param) +{ + SIG( "glLightModeli" ); + dllLightModeli( pname, param ); + +} + +static void APIENTRY logLightModeliv(GLenum pname, const GLint *params) +{ + SIG( "glLightModeliv" ); + dllLightModeliv( pname, params ); +} + +static void APIENTRY logLightf(GLenum light, GLenum pname, GLfloat param) +{ + SIG( "glLightf" ); + dllLightf( light, pname, param ); +} + +static void APIENTRY logLightfv(GLenum light, GLenum pname, const GLfloat *params) +{ + SIG( "glLightfv" ); + dllLightfv( light, pname, params ); +} + +static void APIENTRY logLighti(GLenum light, GLenum pname, GLint param) +{ + SIG( "glLighti" ); + dllLighti( light, pname, param ); +} + +static void APIENTRY logLightiv(GLenum light, GLenum pname, const GLint *params) +{ + SIG( "glLightiv" ); + dllLightiv( light, pname, params ); +} + +static void APIENTRY logLineStipple(GLint factor, GLushort pattern) +{ + SIG( "glLineStipple" ); + dllLineStipple( factor, pattern ); +} + +static void APIENTRY logLineWidth(GLfloat width) +{ + SIG( "glLineWidth" ); + dllLineWidth( width ); +} + +static void APIENTRY logListBase(GLuint base) +{ + SIG( "glListBase" ); + dllListBase( base ); +} + +static void APIENTRY logLoadIdentity(void) +{ + SIG( "glLoadIdentity" ); + dllLoadIdentity(); +} + +static void APIENTRY logLoadMatrixd(const GLdouble *m) +{ + SIG( "glLoadMatrixd" ); + dllLoadMatrixd( m ); +} + +static void APIENTRY logLoadMatrixf(const GLfloat *m) +{ + SIG( "glLoadMatrixf" ); + dllLoadMatrixf( m ); +} + +static void APIENTRY logLoadName(GLuint name) +{ + SIG( "glLoadName" ); + dllLoadName( name ); +} + +static void APIENTRY logLogicOp(GLenum opcode) +{ + SIG( "glLogicOp" ); + dllLogicOp( opcode ); +} + +static void APIENTRY logMap1d(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points) +{ + SIG( "glMap1d" ); + dllMap1d( target, u1, u2, stride, order, points ); +} + +static void APIENTRY logMap1f(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points) +{ + SIG( "glMap1f" ); + dllMap1f( target, u1, u2, stride, order, points ); +} + +static void APIENTRY logMap2d(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points) +{ + SIG( "glMap2d" ); + dllMap2d( target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points ); +} + +static void APIENTRY logMap2f(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points) +{ + SIG( "glMap2f" ); + dllMap2f( target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points ); +} + +static void APIENTRY logMapGrid1d(GLint un, GLdouble u1, GLdouble u2) +{ + SIG( "glMapGrid1d" ); + dllMapGrid1d( un, u1, u2 ); +} + +static void APIENTRY logMapGrid1f(GLint un, GLfloat u1, GLfloat u2) +{ + SIG( "glMapGrid1f" ); + dllMapGrid1f( un, u1, u2 ); +} + +static void APIENTRY logMapGrid2d(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2) +{ + SIG( "glMapGrid2d" ); + dllMapGrid2d( un, u1, u2, vn, v1, v2 ); +} +static void APIENTRY logMapGrid2f(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2) +{ + SIG( "glMapGrid2f" ); + dllMapGrid2f( un, u1, u2, vn, v1, v2 ); +} +static void APIENTRY logMaterialf(GLenum face, GLenum pname, GLfloat param) +{ + SIG( "glMaterialf" ); + dllMaterialf( face, pname, param ); +} +static void APIENTRY logMaterialfv(GLenum face, GLenum pname, const GLfloat *params) +{ + SIG( "glMaterialfv" ); + dllMaterialfv( face, pname, params ); +} + +static void APIENTRY logMateriali(GLenum face, GLenum pname, GLint param) +{ + SIG( "glMateriali" ); + dllMateriali( face, pname, param ); +} + +static void APIENTRY logMaterialiv(GLenum face, GLenum pname, const GLint *params) +{ + SIG( "glMaterialiv" ); + dllMaterialiv( face, pname, params ); +} + +static void APIENTRY logMatrixMode(GLenum mode) +{ + SIG( "glMatrixMode" ); + dllMatrixMode( mode ); +} + +static void APIENTRY logMultMatrixd(const GLdouble *m) +{ + SIG( "glMultMatrixd" ); + dllMultMatrixd( m ); +} + +static void APIENTRY logMultMatrixf(const GLfloat *m) +{ + SIG( "glMultMatrixf" ); + dllMultMatrixf( m ); +} + +static void APIENTRY logNewList(GLuint list, GLenum mode) +{ + SIG( "glNewList" ); + dllNewList( list, mode ); +} + +static void APIENTRY logNormal3b(GLbyte nx, GLbyte ny, GLbyte nz) +{ + SIG ("glNormal3b" ); + dllNormal3b( nx, ny, nz ); +} + +static void APIENTRY logNormal3bv(const GLbyte *v) +{ + SIG( "glNormal3bv" ); + dllNormal3bv( v ); +} + +static void APIENTRY logNormal3d(GLdouble nx, GLdouble ny, GLdouble nz) +{ + SIG( "glNormal3d" ); + dllNormal3d( nx, ny, nz ); +} + +static void APIENTRY logNormal3dv(const GLdouble *v) +{ + SIG( "glNormal3dv" ); + dllNormal3dv( v ); +} + +static void APIENTRY logNormal3f(GLfloat nx, GLfloat ny, GLfloat nz) +{ + SIG( "glNormal3f" ); + dllNormal3f( nx, ny, nz ); +} + +static void APIENTRY logNormal3fv(const GLfloat *v) +{ + SIG( "glNormal3fv" ); + dllNormal3fv( v ); +} +static void APIENTRY logNormal3i(GLint nx, GLint ny, GLint nz) +{ + SIG( "glNormal3i" ); + dllNormal3i( nx, ny, nz ); +} +static void APIENTRY logNormal3iv(const GLint *v) +{ + SIG( "glNormal3iv" ); + dllNormal3iv( v ); +} +static void APIENTRY logNormal3s(GLshort nx, GLshort ny, GLshort nz) +{ + SIG( "glNormal3s" ); + dllNormal3s( nx, ny, nz ); +} +static void APIENTRY logNormal3sv(const GLshort *v) +{ + SIG( "glNormal3sv" ); + dllNormal3sv( v ); +} +static void APIENTRY logNormalPointer(GLenum type, GLsizei stride, const void *pointer) +{ + SIG( "glNormalPointer" ); + dllNormalPointer( type, stride, pointer ); +} +static void APIENTRY logOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + SIG( "glOrtho" ); + dllOrtho( left, right, bottom, top, zNear, zFar ); +} + +static void APIENTRY logPassThrough(GLfloat token) +{ + SIG( "glPassThrough" ); + dllPassThrough( token ); +} + +static void APIENTRY logPixelMapfv(GLenum map, GLsizei mapsize, const GLfloat *values) +{ + SIG( "glPixelMapfv" ); + dllPixelMapfv( map, mapsize, values ); +} + +static void APIENTRY logPixelMapuiv(GLenum map, GLsizei mapsize, const GLuint *values) +{ + SIG( "glPixelMapuiv" ); + dllPixelMapuiv( map, mapsize, values ); +} + +static void APIENTRY logPixelMapusv(GLenum map, GLsizei mapsize, const GLushort *values) +{ + SIG( "glPixelMapusv" ); + dllPixelMapusv( map, mapsize, values ); +} +static void APIENTRY logPixelStoref(GLenum pname, GLfloat param) +{ + SIG( "glPixelStoref" ); + dllPixelStoref( pname, param ); +} +static void APIENTRY logPixelStorei(GLenum pname, GLint param) +{ + SIG( "glPixelStorei" ); + dllPixelStorei( pname, param ); +} +static void APIENTRY logPixelTransferf(GLenum pname, GLfloat param) +{ + SIG( "glPixelTransferf" ); + dllPixelTransferf( pname, param ); +} + +static void APIENTRY logPixelTransferi(GLenum pname, GLint param) +{ + SIG( "glPixelTransferi" ); + dllPixelTransferi( pname, param ); +} + +static void APIENTRY logPixelZoom(GLfloat xfactor, GLfloat yfactor) +{ + SIG( "glPixelZoom" ); + dllPixelZoom( xfactor, yfactor ); +} + +static void APIENTRY logPointSize(GLfloat size) +{ + SIG( "glPointSize" ); + dllPointSize( size ); +} + +static void APIENTRY logPolygonMode(GLenum face, GLenum mode) +{ + fprintf( glw_state.log_fp, "glPolygonMode( 0x%x, 0x%x )\n", face, mode ); + dllPolygonMode( face, mode ); +} + +static void APIENTRY logPolygonOffset(GLfloat factor, GLfloat units) +{ + SIG( "glPolygonOffset" ); + dllPolygonOffset( factor, units ); +} +static void APIENTRY logPolygonStipple(const GLubyte *mask ) +{ + SIG( "glPolygonStipple" ); + dllPolygonStipple( mask ); +} +static void APIENTRY logPopAttrib(void) +{ + SIG( "glPopAttrib" ); + dllPopAttrib(); +} + +static void APIENTRY logPopClientAttrib(void) +{ + SIG( "glPopClientAttrib" ); + dllPopClientAttrib(); +} + +static void APIENTRY logPopMatrix(void) +{ + SIG( "glPopMatrix" ); + dllPopMatrix(); +} + +static void APIENTRY logPopName(void) +{ + SIG( "glPopName" ); + dllPopName(); +} + +static void APIENTRY logPrioritizeTextures(GLsizei n, const GLuint *textures, const GLclampf *priorities) +{ + SIG( "glPrioritizeTextures" ); + dllPrioritizeTextures( n, textures, priorities ); +} + +static void APIENTRY logPushAttrib(GLbitfield mask) +{ + SIG( "glPushAttrib" ); + dllPushAttrib( mask ); +} + +static void APIENTRY logPushClientAttrib(GLbitfield mask) +{ + SIG( "glPushClientAttrib" ); + dllPushClientAttrib( mask ); +} + +static void APIENTRY logPushMatrix(void) +{ + SIG( "glPushMatrix" ); + dllPushMatrix(); +} + +static void APIENTRY logPushName(GLuint name) +{ + SIG( "glPushName" ); + dllPushName( name ); +} + +static void APIENTRY logRasterPos2d(GLdouble x, GLdouble y) +{ + SIG ("glRasterPot2d" ); + dllRasterPos2d( x, y ); +} + +static void APIENTRY logRasterPos2dv(const GLdouble *v) +{ + SIG( "glRasterPos2dv" ); + dllRasterPos2dv( v ); +} + +static void APIENTRY logRasterPos2f(GLfloat x, GLfloat y) +{ + SIG( "glRasterPos2f" ); + dllRasterPos2f( x, y ); +} +static void APIENTRY logRasterPos2fv(const GLfloat *v) +{ + SIG( "glRasterPos2dv" ); + dllRasterPos2fv( v ); +} +static void APIENTRY logRasterPos2i(GLint x, GLint y) +{ + SIG( "glRasterPos2if" ); + dllRasterPos2i( x, y ); +} +static void APIENTRY logRasterPos2iv(const GLint *v) +{ + SIG( "glRasterPos2iv" ); + dllRasterPos2iv( v ); +} +static void APIENTRY logRasterPos2s(GLshort x, GLshort y) +{ + SIG( "glRasterPos2s" ); + dllRasterPos2s( x, y ); +} +static void APIENTRY logRasterPos2sv(const GLshort *v) +{ + SIG( "glRasterPos2sv" ); + dllRasterPos2sv( v ); +} +static void APIENTRY logRasterPos3d(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glRasterPos3d" ); + dllRasterPos3d( x, y, z ); +} +static void APIENTRY logRasterPos3dv(const GLdouble *v) +{ + SIG( "glRasterPos3dv" ); + dllRasterPos3dv( v ); +} +static void APIENTRY logRasterPos3f(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glRasterPos3f" ); + dllRasterPos3f( x, y, z ); +} +static void APIENTRY logRasterPos3fv(const GLfloat *v) +{ + SIG( "glRasterPos3fv" ); + dllRasterPos3fv( v ); +} +static void APIENTRY logRasterPos3i(GLint x, GLint y, GLint z) +{ + SIG( "glRasterPos3i" ); + dllRasterPos3i( x, y, z ); +} +static void APIENTRY logRasterPos3iv(const GLint *v) +{ + SIG( "glRasterPos3iv" ); + dllRasterPos3iv( v ); +} +static void APIENTRY logRasterPos3s(GLshort x, GLshort y, GLshort z) +{ + SIG( "glRasterPos3s" ); + dllRasterPos3s( x, y, z ); +} +static void APIENTRY logRasterPos3sv(const GLshort *v) +{ + SIG( "glRasterPos3sv" ); + dllRasterPos3sv( v ); +} +static void APIENTRY logRasterPos4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + SIG( "glRasterPos4d" ); + dllRasterPos4d( x, y, z, w ); +} +static void APIENTRY logRasterPos4dv(const GLdouble *v) +{ + SIG( "glRasterPos4dv" ); + dllRasterPos4dv( v ); +} +static void APIENTRY logRasterPos4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + SIG( "glRasterPos4f" ); + dllRasterPos4f( x, y, z, w ); +} +static void APIENTRY logRasterPos4fv(const GLfloat *v) +{ + SIG( "glRasterPos4fv" ); + dllRasterPos4fv( v ); +} +static void APIENTRY logRasterPos4i(GLint x, GLint y, GLint z, GLint w) +{ + SIG( "glRasterPos4i" ); + dllRasterPos4i( x, y, z, w ); +} +static void APIENTRY logRasterPos4iv(const GLint *v) +{ + SIG( "glRasterPos4iv" ); + dllRasterPos4iv( v ); +} +static void APIENTRY logRasterPos4s(GLshort x, GLshort y, GLshort z, GLshort w) +{ + SIG( "glRasterPos4s" ); + dllRasterPos4s( x, y, z, w ); +} +static void APIENTRY logRasterPos4sv(const GLshort *v) +{ + SIG( "glRasterPos4sv" ); + dllRasterPos4sv( v ); +} +static void APIENTRY logReadBuffer(GLenum mode) +{ + SIG( "glReadBuffer" ); + dllReadBuffer( mode ); +} +static void APIENTRY logReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels) +{ + SIG( "glReadPixels" ); + dllReadPixels( x, y, width, height, format, type, pixels ); +} + +static void APIENTRY logRectd(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2) +{ + SIG( "glRectd" ); + dllRectd( x1, y1, x2, y2 ); +} + +static void APIENTRY logRectdv(const GLdouble *v1, const GLdouble *v2) +{ + SIG( "glRectdv" ); + dllRectdv( v1, v2 ); +} + +static void APIENTRY logRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) +{ + SIG( "glRectf" ); + dllRectf( x1, y1, x2, y2 ); +} + +static void APIENTRY logRectfv(const GLfloat *v1, const GLfloat *v2) +{ + SIG( "glRectfv" ); + dllRectfv( v1, v2 ); +} +static void APIENTRY logRecti(GLint x1, GLint y1, GLint x2, GLint y2) +{ + SIG( "glRecti" ); + dllRecti( x1, y1, x2, y2 ); +} +static void APIENTRY logRectiv(const GLint *v1, const GLint *v2) +{ + SIG( "glRectiv" ); + dllRectiv( v1, v2 ); +} +static void APIENTRY logRects(GLshort x1, GLshort y1, GLshort x2, GLshort y2) +{ + SIG( "glRects" ); + dllRects( x1, y1, x2, y2 ); +} +static void APIENTRY logRectsv(const GLshort *v1, const GLshort *v2) +{ + SIG( "glRectsv" ); + dllRectsv( v1, v2 ); +} +static GLint APIENTRY logRenderMode(GLenum mode) +{ + SIG( "glRenderMode" ); + return dllRenderMode( mode ); +} +static void APIENTRY logRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glRotated" ); + dllRotated( angle, x, y, z ); +} + +static void APIENTRY logRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glRotatef" ); + dllRotatef( angle, x, y, z ); +} + +static void APIENTRY logScaled(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glScaled" ); + dllScaled( x, y, z ); +} + +static void APIENTRY logScalef(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glScalef" ); + dllScalef( x, y, z ); +} + +static void APIENTRY logScissor(GLint x, GLint y, GLsizei width, GLsizei height) +{ + SIG( "glScissor" ); + dllScissor( x, y, width, height ); +} + +static void APIENTRY logSelectBuffer(GLsizei size, GLuint *buffer) +{ + SIG( "glSelectBuffer" ); + dllSelectBuffer( size, buffer ); +} + +static void APIENTRY logShadeModel(GLenum mode) +{ + SIG( "glShadeModel" ); + dllShadeModel( mode ); +} + +static void APIENTRY logStencilFunc(GLenum func, GLint ref, GLuint mask) +{ + SIG( "glStencilFunc" ); + dllStencilFunc( func, ref, mask ); +} + +static void APIENTRY logStencilMask(GLuint mask) +{ + SIG( "glStencilMask" ); + dllStencilMask( mask ); +} + +static void APIENTRY logStencilOp(GLenum fail, GLenum zfail, GLenum zpass) +{ + SIG( "glStencilOp" ); + dllStencilOp( fail, zfail, zpass ); +} + +static void APIENTRY logTexCoord1d(GLdouble s) +{ + SIG( "glTexCoord1d" ); + dllTexCoord1d( s ); +} + +static void APIENTRY logTexCoord1dv(const GLdouble *v) +{ + SIG( "glTexCoord1dv" ); + dllTexCoord1dv( v ); +} + +static void APIENTRY logTexCoord1f(GLfloat s) +{ + SIG( "glTexCoord1f" ); + dllTexCoord1f( s ); +} +static void APIENTRY logTexCoord1fv(const GLfloat *v) +{ + SIG( "glTexCoord1fv" ); + dllTexCoord1fv( v ); +} +static void APIENTRY logTexCoord1i(GLint s) +{ + SIG( "glTexCoord1i" ); + dllTexCoord1i( s ); +} +static void APIENTRY logTexCoord1iv(const GLint *v) +{ + SIG( "glTexCoord1iv" ); + dllTexCoord1iv( v ); +} +static void APIENTRY logTexCoord1s(GLshort s) +{ + SIG( "glTexCoord1s" ); + dllTexCoord1s( s ); +} +static void APIENTRY logTexCoord1sv(const GLshort *v) +{ + SIG( "glTexCoord1sv" ); + dllTexCoord1sv( v ); +} +static void APIENTRY logTexCoord2d(GLdouble s, GLdouble t) +{ + SIG( "glTexCoord2d" ); + dllTexCoord2d( s, t ); +} + +static void APIENTRY logTexCoord2dv(const GLdouble *v) +{ + SIG( "glTexCoord2dv" ); + dllTexCoord2dv( v ); +} +static void APIENTRY logTexCoord2f(GLfloat s, GLfloat t) +{ + SIG( "glTexCoord2f" ); + dllTexCoord2f( s, t ); +} +static void APIENTRY logTexCoord2fv(const GLfloat *v) +{ + SIG( "glTexCoord2fv" ); + dllTexCoord2fv( v ); +} +static void APIENTRY logTexCoord2i(GLint s, GLint t) +{ + SIG( "glTexCoord2i" ); + dllTexCoord2i( s, t ); +} +static void APIENTRY logTexCoord2iv(const GLint *v) +{ + SIG( "glTexCoord2iv" ); + dllTexCoord2iv( v ); +} +static void APIENTRY logTexCoord2s(GLshort s, GLshort t) +{ + SIG( "glTexCoord2s" ); + dllTexCoord2s( s, t ); +} +static void APIENTRY logTexCoord2sv(const GLshort *v) +{ + SIG( "glTexCoord2sv" ); + dllTexCoord2sv( v ); +} +static void APIENTRY logTexCoord3d(GLdouble s, GLdouble t, GLdouble r) +{ + SIG( "glTexCoord3d" ); + dllTexCoord3d( s, t, r ); +} +static void APIENTRY logTexCoord3dv(const GLdouble *v) +{ + SIG( "glTexCoord3dv" ); + dllTexCoord3dv( v ); +} +static void APIENTRY logTexCoord3f(GLfloat s, GLfloat t, GLfloat r) +{ + SIG( "glTexCoord3f" ); + dllTexCoord3f( s, t, r ); +} +static void APIENTRY logTexCoord3fv(const GLfloat *v) +{ + SIG( "glTexCoord3fv" ); + dllTexCoord3fv( v ); +} +static void APIENTRY logTexCoord3i(GLint s, GLint t, GLint r) +{ + SIG( "glTexCoord3i" ); + dllTexCoord3i( s, t, r ); +} +static void APIENTRY logTexCoord3iv(const GLint *v) +{ + SIG( "glTexCoord3iv" ); + dllTexCoord3iv( v ); +} +static void APIENTRY logTexCoord3s(GLshort s, GLshort t, GLshort r) +{ + SIG( "glTexCoord3s" ); + dllTexCoord3s( s, t, r ); +} +static void APIENTRY logTexCoord3sv(const GLshort *v) +{ + SIG( "glTexCoord3sv" ); + dllTexCoord3sv( v ); +} +static void APIENTRY logTexCoord4d(GLdouble s, GLdouble t, GLdouble r, GLdouble q) +{ + SIG( "glTexCoord4d" ); + dllTexCoord4d( s, t, r, q ); +} +static void APIENTRY logTexCoord4dv(const GLdouble *v) +{ + SIG( "glTexCoord4dv" ); + dllTexCoord4dv( v ); +} +static void APIENTRY logTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat q) +{ + SIG( "glTexCoord4f" ); + dllTexCoord4f( s, t, r, q ); +} +static void APIENTRY logTexCoord4fv(const GLfloat *v) +{ + SIG( "glTexCoord4fv" ); + dllTexCoord4fv( v ); +} +static void APIENTRY logTexCoord4i(GLint s, GLint t, GLint r, GLint q) +{ + SIG( "glTexCoord4i" ); + dllTexCoord4i( s, t, r, q ); +} +static void APIENTRY logTexCoord4iv(const GLint *v) +{ + SIG( "glTexCoord4iv" ); + dllTexCoord4iv( v ); +} +static void APIENTRY logTexCoord4s(GLshort s, GLshort t, GLshort r, GLshort q) +{ + SIG( "glTexCoord4s" ); + dllTexCoord4s( s, t, r, q ); +} +static void APIENTRY logTexCoord4sv(const GLshort *v) +{ + SIG( "glTexCoord4sv" ); + dllTexCoord4sv( v ); +} +static void APIENTRY logTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) +{ + SIG( "glTexCoordPointer" ); + dllTexCoordPointer( size, type, stride, pointer ); +} + +static void APIENTRY logTexEnvf(GLenum target, GLenum pname, GLfloat param) +{ + fprintf( glw_state.log_fp, "glTexEnvf( 0x%x, 0x%x, %f )\n", target, pname, param ); + dllTexEnvf( target, pname, param ); +} + +static void APIENTRY logTexEnvfv(GLenum target, GLenum pname, const GLfloat *params) +{ + SIG( "glTexEnvfv" ); + dllTexEnvfv( target, pname, params ); +} + +static void APIENTRY logTexEnvi(GLenum target, GLenum pname, GLint param) +{ + fprintf( glw_state.log_fp, "glTexEnvi( 0x%x, 0x%x, 0x%x )\n", target, pname, param ); + dllTexEnvi( target, pname, param ); +} +static void APIENTRY logTexEnviv(GLenum target, GLenum pname, const GLint *params) +{ + SIG( "glTexEnviv" ); + dllTexEnviv( target, pname, params ); +} + +static void APIENTRY logTexGend(GLenum coord, GLenum pname, GLdouble param) +{ + SIG( "glTexGend" ); + dllTexGend( coord, pname, param ); +} + +static void APIENTRY logTexGendv(GLenum coord, GLenum pname, const GLdouble *params) +{ + SIG( "glTexGendv" ); + dllTexGendv( coord, pname, params ); +} + +static void APIENTRY logTexGenf(GLenum coord, GLenum pname, GLfloat param) +{ + SIG( "glTexGenf" ); + dllTexGenf( coord, pname, param ); +} +static void APIENTRY logTexGenfv(GLenum coord, GLenum pname, const GLfloat *params) +{ + SIG( "glTexGenfv" ); + dllTexGenfv( coord, pname, params ); +} +static void APIENTRY logTexGeni(GLenum coord, GLenum pname, GLint param) +{ + SIG( "glTexGeni" ); + dllTexGeni( coord, pname, param ); +} +static void APIENTRY logTexGeniv(GLenum coord, GLenum pname, const GLint *params) +{ + SIG( "glTexGeniv" ); + dllTexGeniv( coord, pname, params ); +} +static void APIENTRY logTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexImage1D" ); + dllTexImage1D( target, level, internalformat, width, border, format, type, pixels ); +} +static void APIENTRY logTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexImage2D" ); + dllTexImage2D( target, level, internalformat, width, height, border, format, type, pixels ); +} + +static void APIENTRY logTexParameterf(GLenum target, GLenum pname, GLfloat param) +{ + fprintf( glw_state.log_fp, "glTexParameterf( 0x%x, 0x%x, %f )\n", target, pname, param ); + dllTexParameterf( target, pname, param ); +} + +static void APIENTRY logTexParameterfv(GLenum target, GLenum pname, const GLfloat *params) +{ + SIG( "glTexParameterfv" ); + dllTexParameterfv( target, pname, params ); +} +static void APIENTRY logTexParameteri(GLenum target, GLenum pname, GLint param) +{ + fprintf( glw_state.log_fp, "glTexParameteri( 0x%x, 0x%x, 0x%x )\n", target, pname, param ); + dllTexParameteri( target, pname, param ); +} +static void APIENTRY logTexParameteriv(GLenum target, GLenum pname, const GLint *params) +{ + SIG( "glTexParameteriv" ); + dllTexParameteriv( target, pname, params ); +} +static void APIENTRY logTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexSubImage1D" ); + dllTexSubImage1D( target, level, xoffset, width, format, type, pixels ); +} +static void APIENTRY logTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexSubImage2D" ); + dllTexSubImage2D( target, level, xoffset, yoffset, width, height, format, type, pixels ); +} +static void APIENTRY logTranslated(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glTranslated" ); + dllTranslated( x, y, z ); +} + +static void APIENTRY logTranslatef(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glTranslatef" ); + dllTranslatef( x, y, z ); +} + +static void APIENTRY logVertex2d(GLdouble x, GLdouble y) +{ + SIG( "glVertex2d" ); + dllVertex2d( x, y ); +} + +static void APIENTRY logVertex2dv(const GLdouble *v) +{ + SIG( "glVertex2dv" ); + dllVertex2dv( v ); +} +static void APIENTRY logVertex2f(GLfloat x, GLfloat y) +{ + SIG( "glVertex2f" ); + dllVertex2f( x, y ); +} +static void APIENTRY logVertex2fv(const GLfloat *v) +{ + SIG( "glVertex2fv" ); + dllVertex2fv( v ); +} +static void APIENTRY logVertex2i(GLint x, GLint y) +{ + SIG( "glVertex2i" ); + dllVertex2i( x, y ); +} +static void APIENTRY logVertex2iv(const GLint *v) +{ + SIG( "glVertex2iv" ); + dllVertex2iv( v ); +} +static void APIENTRY logVertex2s(GLshort x, GLshort y) +{ + SIG( "glVertex2s" ); + dllVertex2s( x, y ); +} +static void APIENTRY logVertex2sv(const GLshort *v) +{ + SIG( "glVertex2sv" ); + dllVertex2sv( v ); +} +static void APIENTRY logVertex3d(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glVertex3d" ); + dllVertex3d( x, y, z ); +} +static void APIENTRY logVertex3dv(const GLdouble *v) +{ + SIG( "glVertex3dv" ); + dllVertex3dv( v ); +} +static void APIENTRY logVertex3f(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glVertex3f" ); + dllVertex3f( x, y, z ); +} +static void APIENTRY logVertex3fv(const GLfloat *v) +{ + SIG( "glVertex3fv" ); + dllVertex3fv( v ); +} +static void APIENTRY logVertex3i(GLint x, GLint y, GLint z) +{ + SIG( "glVertex3i" ); + dllVertex3i( x, y, z ); +} +static void APIENTRY logVertex3iv(const GLint *v) +{ + SIG( "glVertex3iv" ); + dllVertex3iv( v ); +} +static void APIENTRY logVertex3s(GLshort x, GLshort y, GLshort z) +{ + SIG( "glVertex3s" ); + dllVertex3s( x, y, z ); +} +static void APIENTRY logVertex3sv(const GLshort *v) +{ + SIG( "glVertex3sv" ); + dllVertex3sv( v ); +} +static void APIENTRY logVertex4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + SIG( "glVertex4d" ); + dllVertex4d( x, y, z, w ); +} +static void APIENTRY logVertex4dv(const GLdouble *v) +{ + SIG( "glVertex4dv" ); + dllVertex4dv( v ); +} +static void APIENTRY logVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + SIG( "glVertex4f" ); + dllVertex4f( x, y, z, w ); +} +static void APIENTRY logVertex4fv(const GLfloat *v) +{ + SIG( "glVertex4fv" ); + dllVertex4fv( v ); +} +static void APIENTRY logVertex4i(GLint x, GLint y, GLint z, GLint w) +{ + SIG( "glVertex4i" ); + dllVertex4i( x, y, z, w ); +} +static void APIENTRY logVertex4iv(const GLint *v) +{ + SIG( "glVertex4iv" ); + dllVertex4iv( v ); +} +static void APIENTRY logVertex4s(GLshort x, GLshort y, GLshort z, GLshort w) +{ + SIG( "glVertex4s" ); + dllVertex4s( x, y, z, w ); +} +static void APIENTRY logVertex4sv(const GLshort *v) +{ + SIG( "glVertex4sv" ); + dllVertex4sv( v ); +} +static void APIENTRY logVertexPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) +{ + SIG( "glVertexPointer" ); + dllVertexPointer( size, type, stride, pointer ); +} +static void APIENTRY logViewport(GLint x, GLint y, GLsizei width, GLsizei height) +{ + SIG( "glViewport" ); + dllViewport( x, y, width, height ); +} + +/* +** QGL_Shutdown +** +** Unloads the specified DLL then nulls out all the proc pointers. +*/ +void QGL_Shutdown( void ) +{ + if ( glw_state.OpenGLLib ) + { + dlclose ( glw_state.OpenGLLib ); + glw_state.OpenGLLib = NULL; + } + + glw_state.OpenGLLib = NULL; + + qglAccum = NULL; + qglAlphaFunc = NULL; + qglAreTexturesResident = NULL; + qglArrayElement = NULL; + qglBegin = NULL; + qglBindTexture = NULL; + qglBitmap = NULL; + qglBlendFunc = NULL; + qglCallList = NULL; + qglCallLists = NULL; + qglClear = NULL; + qglClearAccum = NULL; + qglClearColor = NULL; + qglClearDepth = NULL; + qglClearIndex = NULL; + qglClearStencil = NULL; + qglClipPlane = NULL; + qglColor3b = NULL; + qglColor3bv = NULL; + qglColor3d = NULL; + qglColor3dv = NULL; + qglColor3f = NULL; + qglColor3fv = NULL; + qglColor3i = NULL; + qglColor3iv = NULL; + qglColor3s = NULL; + qglColor3sv = NULL; + qglColor3ub = NULL; + qglColor3ubv = NULL; + qglColor3ui = NULL; + qglColor3uiv = NULL; + qglColor3us = NULL; + qglColor3usv = NULL; + qglColor4b = NULL; + qglColor4bv = NULL; + qglColor4d = NULL; + qglColor4dv = NULL; + qglColor4f = NULL; + qglColor4fv = NULL; + qglColor4i = NULL; + qglColor4iv = NULL; + qglColor4s = NULL; + qglColor4sv = NULL; + qglColor4ub = NULL; + qglColor4ubv = NULL; + qglColor4ui = NULL; + qglColor4uiv = NULL; + qglColor4us = NULL; + qglColor4usv = NULL; + qglColorMask = NULL; + qglColorMaterial = NULL; + qglColorPointer = NULL; + qglCopyPixels = NULL; + qglCopyTexImage1D = NULL; + qglCopyTexImage2D = NULL; + qglCopyTexSubImage1D = NULL; + qglCopyTexSubImage2D = NULL; + qglCullFace = NULL; + qglDeleteLists = NULL; + qglDeleteTextures = NULL; + qglDepthFunc = NULL; + qglDepthMask = NULL; + qglDepthRange = NULL; + qglDisable = NULL; + qglDisableClientState = NULL; + qglDrawArrays = NULL; + qglDrawBuffer = NULL; + qglDrawElements = NULL; + qglDrawPixels = NULL; + qglEdgeFlag = NULL; + qglEdgeFlagPointer = NULL; + qglEdgeFlagv = NULL; + qglEnable = NULL; + qglEnableClientState = NULL; + qglEnd = NULL; + qglEndList = NULL; + qglEvalCoord1d = NULL; + qglEvalCoord1dv = NULL; + qglEvalCoord1f = NULL; + qglEvalCoord1fv = NULL; + qglEvalCoord2d = NULL; + qglEvalCoord2dv = NULL; + qglEvalCoord2f = NULL; + qglEvalCoord2fv = NULL; + qglEvalMesh1 = NULL; + qglEvalMesh2 = NULL; + qglEvalPoint1 = NULL; + qglEvalPoint2 = NULL; + qglFeedbackBuffer = NULL; + qglFinish = NULL; + qglFlush = NULL; + qglFogf = NULL; + qglFogfv = NULL; + qglFogi = NULL; + qglFogiv = NULL; + qglFrontFace = NULL; + qglFrustum = NULL; + qglGenLists = NULL; + qglGenTextures = NULL; + qglGetBooleanv = NULL; + qglGetClipPlane = NULL; + qglGetDoublev = NULL; + qglGetError = NULL; + qglGetFloatv = NULL; + qglGetIntegerv = NULL; + qglGetLightfv = NULL; + qglGetLightiv = NULL; + qglGetMapdv = NULL; + qglGetMapfv = NULL; + qglGetMapiv = NULL; + qglGetMaterialfv = NULL; + qglGetMaterialiv = NULL; + qglGetPixelMapfv = NULL; + qglGetPixelMapuiv = NULL; + qglGetPixelMapusv = NULL; + qglGetPointerv = NULL; + qglGetPolygonStipple = NULL; + qglGetString = NULL; + qglGetTexEnvfv = NULL; + qglGetTexEnviv = NULL; + qglGetTexGendv = NULL; + qglGetTexGenfv = NULL; + qglGetTexGeniv = NULL; + qglGetTexImage = NULL; + qglGetTexLevelParameterfv = NULL; + qglGetTexLevelParameteriv = NULL; + qglGetTexParameterfv = NULL; + qglGetTexParameteriv = NULL; + qglHint = NULL; + qglIndexMask = NULL; + qglIndexPointer = NULL; + qglIndexd = NULL; + qglIndexdv = NULL; + qglIndexf = NULL; + qglIndexfv = NULL; + qglIndexi = NULL; + qglIndexiv = NULL; + qglIndexs = NULL; + qglIndexsv = NULL; + qglIndexub = NULL; + qglIndexubv = NULL; + qglInitNames = NULL; + qglInterleavedArrays = NULL; + qglIsEnabled = NULL; + qglIsList = NULL; + qglIsTexture = NULL; + qglLightModelf = NULL; + qglLightModelfv = NULL; + qglLightModeli = NULL; + qglLightModeliv = NULL; + qglLightf = NULL; + qglLightfv = NULL; + qglLighti = NULL; + qglLightiv = NULL; + qglLineStipple = NULL; + qglLineWidth = NULL; + qglListBase = NULL; + qglLoadIdentity = NULL; + qglLoadMatrixd = NULL; + qglLoadMatrixf = NULL; + qglLoadName = NULL; + qglLogicOp = NULL; + qglMap1d = NULL; + qglMap1f = NULL; + qglMap2d = NULL; + qglMap2f = NULL; + qglMapGrid1d = NULL; + qglMapGrid1f = NULL; + qglMapGrid2d = NULL; + qglMapGrid2f = NULL; + qglMaterialf = NULL; + qglMaterialfv = NULL; + qglMateriali = NULL; + qglMaterialiv = NULL; + qglMatrixMode = NULL; + qglMultMatrixd = NULL; + qglMultMatrixf = NULL; + qglNewList = NULL; + qglNormal3b = NULL; + qglNormal3bv = NULL; + qglNormal3d = NULL; + qglNormal3dv = NULL; + qglNormal3f = NULL; + qglNormal3fv = NULL; + qglNormal3i = NULL; + qglNormal3iv = NULL; + qglNormal3s = NULL; + qglNormal3sv = NULL; + qglNormalPointer = NULL; + qglOrtho = NULL; + qglPassThrough = NULL; + qglPixelMapfv = NULL; + qglPixelMapuiv = NULL; + qglPixelMapusv = NULL; + qglPixelStoref = NULL; + qglPixelStorei = NULL; + qglPixelTransferf = NULL; + qglPixelTransferi = NULL; + qglPixelZoom = NULL; + qglPointSize = NULL; + qglPolygonMode = NULL; + qglPolygonOffset = NULL; + qglPolygonStipple = NULL; + qglPopAttrib = NULL; + qglPopClientAttrib = NULL; + qglPopMatrix = NULL; + qglPopName = NULL; + qglPrioritizeTextures = NULL; + qglPushAttrib = NULL; + qglPushClientAttrib = NULL; + qglPushMatrix = NULL; + qglPushName = NULL; + qglRasterPos2d = NULL; + qglRasterPos2dv = NULL; + qglRasterPos2f = NULL; + qglRasterPos2fv = NULL; + qglRasterPos2i = NULL; + qglRasterPos2iv = NULL; + qglRasterPos2s = NULL; + qglRasterPos2sv = NULL; + qglRasterPos3d = NULL; + qglRasterPos3dv = NULL; + qglRasterPos3f = NULL; + qglRasterPos3fv = NULL; + qglRasterPos3i = NULL; + qglRasterPos3iv = NULL; + qglRasterPos3s = NULL; + qglRasterPos3sv = NULL; + qglRasterPos4d = NULL; + qglRasterPos4dv = NULL; + qglRasterPos4f = NULL; + qglRasterPos4fv = NULL; + qglRasterPos4i = NULL; + qglRasterPos4iv = NULL; + qglRasterPos4s = NULL; + qglRasterPos4sv = NULL; + qglReadBuffer = NULL; + qglReadPixels = NULL; + qglRectd = NULL; + qglRectdv = NULL; + qglRectf = NULL; + qglRectfv = NULL; + qglRecti = NULL; + qglRectiv = NULL; + qglRects = NULL; + qglRectsv = NULL; + qglRenderMode = NULL; + qglRotated = NULL; + qglRotatef = NULL; + qglScaled = NULL; + qglScalef = NULL; + qglScissor = NULL; + qglSelectBuffer = NULL; + qglShadeModel = NULL; + qglStencilFunc = NULL; + qglStencilMask = NULL; + qglStencilOp = NULL; + qglTexCoord1d = NULL; + qglTexCoord1dv = NULL; + qglTexCoord1f = NULL; + qglTexCoord1fv = NULL; + qglTexCoord1i = NULL; + qglTexCoord1iv = NULL; + qglTexCoord1s = NULL; + qglTexCoord1sv = NULL; + qglTexCoord2d = NULL; + qglTexCoord2dv = NULL; + qglTexCoord2f = NULL; + qglTexCoord2fv = NULL; + qglTexCoord2i = NULL; + qglTexCoord2iv = NULL; + qglTexCoord2s = NULL; + qglTexCoord2sv = NULL; + qglTexCoord3d = NULL; + qglTexCoord3dv = NULL; + qglTexCoord3f = NULL; + qglTexCoord3fv = NULL; + qglTexCoord3i = NULL; + qglTexCoord3iv = NULL; + qglTexCoord3s = NULL; + qglTexCoord3sv = NULL; + qglTexCoord4d = NULL; + qglTexCoord4dv = NULL; + qglTexCoord4f = NULL; + qglTexCoord4fv = NULL; + qglTexCoord4i = NULL; + qglTexCoord4iv = NULL; + qglTexCoord4s = NULL; + qglTexCoord4sv = NULL; + qglTexCoordPointer = NULL; + qglTexEnvf = NULL; + qglTexEnvfv = NULL; + qglTexEnvi = NULL; + qglTexEnviv = NULL; + qglTexGend = NULL; + qglTexGendv = NULL; + qglTexGenf = NULL; + qglTexGenfv = NULL; + qglTexGeni = NULL; + qglTexGeniv = NULL; + qglTexImage1D = NULL; + qglTexImage2D = NULL; + qglTexParameterf = NULL; + qglTexParameterfv = NULL; + qglTexParameteri = NULL; + qglTexParameteriv = NULL; + qglTexSubImage1D = NULL; + qglTexSubImage2D = NULL; + qglTranslated = NULL; + qglTranslatef = NULL; + qglVertex2d = NULL; + qglVertex2dv = NULL; + qglVertex2f = NULL; + qglVertex2fv = NULL; + qglVertex2i = NULL; + qglVertex2iv = NULL; + qglVertex2s = NULL; + qglVertex2sv = NULL; + qglVertex3d = NULL; + qglVertex3dv = NULL; + qglVertex3f = NULL; + qglVertex3fv = NULL; + qglVertex3i = NULL; + qglVertex3iv = NULL; + qglVertex3s = NULL; + qglVertex3sv = NULL; + qglVertex4d = NULL; + qglVertex4dv = NULL; + qglVertex4f = NULL; + qglVertex4fv = NULL; + qglVertex4i = NULL; + qglVertex4iv = NULL; + qglVertex4s = NULL; + qglVertex4sv = NULL; + qglVertexPointer = NULL; + qglViewport = NULL; + + qfxMesaCreateContext = NULL; + qfxMesaCreateBestContext = NULL; + qfxMesaDestroyContext = NULL; + qfxMesaMakeCurrent = NULL; + qfxMesaGetCurrentContext = NULL; + qfxMesaSwapBuffers = NULL; + + qglXChooseVisual = NULL; + qglXCreateContext = NULL; + qglXDestroyContext = NULL; + qglXMakeCurrent = NULL; + qglXCopyContext = NULL; + qglXSwapBuffers = NULL; +} + +#define GPA( a ) dlsym( glw_state.OpenGLLib, a ) + +void *qwglGetProcAddress(char *symbol) +{ + if (glw_state.OpenGLLib) + return GPA ( symbol ); + return NULL; +} + +/* +** QGL_Init +** +** This is responsible for binding our qgl function pointers to +** the appropriate GL stuff. In Windows this means doing a +** LoadLibrary and a bunch of calls to GetProcAddress. On other +** operating systems we need to do the right thing, whatever that +** might be. +** +*/ + +qboolean QGL_Init( const char *dllname ) +{ +#if 0 //FIXME + // update 3Dfx gamma irrespective of underlying DLL + { + char envbuffer[1024]; + float g; + + g = 2.00 * ( 0.8 - ( vid_gamma->value - 0.5 ) ) + 1.0F; + Com_sprintf( envbuffer, sizeof(envbuffer), "SSTV2_GAMMA=%f", g ); + putenv( envbuffer ); + Com_sprintf( envbuffer, sizeof(envbuffer), "SST_GAMMA=%f", g ); + putenv( envbuffer ); + } +#endif + + if ( ( glw_state.OpenGLLib = dlopen( dllname, RTLD_LAZY ) ) == 0 ) + { + char fn[1024]; + FILE *fp; + extern uid_t saved_euid; // unix_main.c + +//fprintf(stdout, "uid=%d,euid=%d\n", getuid(), geteuid()); fflush(stdout); + + // if we are not setuid, try current directory + if (getuid() == saved_euid) { + getcwd(fn, sizeof(fn)); + Q_strcat(fn, sizeof(fn), "/"); + Q_strcat(fn, sizeof(fn), dllname); + + if ( ( glw_state.OpenGLLib = dlopen( fn, RTLD_LAZY ) ) == 0 ) { + ri.Printf(PRINT_ALL, "QGL_Init: Can't load %s from /etc/ld.so.conf or current dir: %s\n", dllname, dlerror()); + return qfalse; + } + } else { + ri.Printf(PRINT_ALL, "QGL_Init: Can't load %s from /etc/ld.so.conf: %s\n", dllname, dlerror()); + return qfalse; + } + } + + qglAccum = dllAccum = GPA( "glAccum" ); + qglAlphaFunc = dllAlphaFunc = GPA( "glAlphaFunc" ); + qglAreTexturesResident = dllAreTexturesResident = GPA( "glAreTexturesResident" ); + qglArrayElement = dllArrayElement = GPA( "glArrayElement" ); + qglBegin = dllBegin = GPA( "glBegin" ); + qglBindTexture = dllBindTexture = GPA( "glBindTexture" ); + qglBitmap = dllBitmap = GPA( "glBitmap" ); + qglBlendFunc = dllBlendFunc = GPA( "glBlendFunc" ); + qglCallList = dllCallList = GPA( "glCallList" ); + qglCallLists = dllCallLists = GPA( "glCallLists" ); + qglClear = dllClear = GPA( "glClear" ); + qglClearAccum = dllClearAccum = GPA( "glClearAccum" ); + qglClearColor = dllClearColor = GPA( "glClearColor" ); + qglClearDepth = dllClearDepth = GPA( "glClearDepth" ); + qglClearIndex = dllClearIndex = GPA( "glClearIndex" ); + qglClearStencil = dllClearStencil = GPA( "glClearStencil" ); + qglClipPlane = dllClipPlane = GPA( "glClipPlane" ); + qglColor3b = dllColor3b = GPA( "glColor3b" ); + qglColor3bv = dllColor3bv = GPA( "glColor3bv" ); + qglColor3d = dllColor3d = GPA( "glColor3d" ); + qglColor3dv = dllColor3dv = GPA( "glColor3dv" ); + qglColor3f = dllColor3f = GPA( "glColor3f" ); + qglColor3fv = dllColor3fv = GPA( "glColor3fv" ); + qglColor3i = dllColor3i = GPA( "glColor3i" ); + qglColor3iv = dllColor3iv = GPA( "glColor3iv" ); + qglColor3s = dllColor3s = GPA( "glColor3s" ); + qglColor3sv = dllColor3sv = GPA( "glColor3sv" ); + qglColor3ub = dllColor3ub = GPA( "glColor3ub" ); + qglColor3ubv = dllColor3ubv = GPA( "glColor3ubv" ); + qglColor3ui = dllColor3ui = GPA( "glColor3ui" ); + qglColor3uiv = dllColor3uiv = GPA( "glColor3uiv" ); + qglColor3us = dllColor3us = GPA( "glColor3us" ); + qglColor3usv = dllColor3usv = GPA( "glColor3usv" ); + qglColor4b = dllColor4b = GPA( "glColor4b" ); + qglColor4bv = dllColor4bv = GPA( "glColor4bv" ); + qglColor4d = dllColor4d = GPA( "glColor4d" ); + qglColor4dv = dllColor4dv = GPA( "glColor4dv" ); + qglColor4f = dllColor4f = GPA( "glColor4f" ); + qglColor4fv = dllColor4fv = GPA( "glColor4fv" ); + qglColor4i = dllColor4i = GPA( "glColor4i" ); + qglColor4iv = dllColor4iv = GPA( "glColor4iv" ); + qglColor4s = dllColor4s = GPA( "glColor4s" ); + qglColor4sv = dllColor4sv = GPA( "glColor4sv" ); + qglColor4ub = dllColor4ub = GPA( "glColor4ub" ); + qglColor4ubv = dllColor4ubv = GPA( "glColor4ubv" ); + qglColor4ui = dllColor4ui = GPA( "glColor4ui" ); + qglColor4uiv = dllColor4uiv = GPA( "glColor4uiv" ); + qglColor4us = dllColor4us = GPA( "glColor4us" ); + qglColor4usv = dllColor4usv = GPA( "glColor4usv" ); + qglColorMask = dllColorMask = GPA( "glColorMask" ); + qglColorMaterial = dllColorMaterial = GPA( "glColorMaterial" ); + qglColorPointer = dllColorPointer = GPA( "glColorPointer" ); + qglCopyPixels = dllCopyPixels = GPA( "glCopyPixels" ); + qglCopyTexImage1D = dllCopyTexImage1D = GPA( "glCopyTexImage1D" ); + qglCopyTexImage2D = dllCopyTexImage2D = GPA( "glCopyTexImage2D" ); + qglCopyTexSubImage1D = dllCopyTexSubImage1D = GPA( "glCopyTexSubImage1D" ); + qglCopyTexSubImage2D = dllCopyTexSubImage2D = GPA( "glCopyTexSubImage2D" ); + qglCullFace = dllCullFace = GPA( "glCullFace" ); + qglDeleteLists = dllDeleteLists = GPA( "glDeleteLists" ); + qglDeleteTextures = dllDeleteTextures = GPA( "glDeleteTextures" ); + qglDepthFunc = dllDepthFunc = GPA( "glDepthFunc" ); + qglDepthMask = dllDepthMask = GPA( "glDepthMask" ); + qglDepthRange = dllDepthRange = GPA( "glDepthRange" ); + qglDisable = dllDisable = GPA( "glDisable" ); + qglDisableClientState = dllDisableClientState = GPA( "glDisableClientState" ); + qglDrawArrays = dllDrawArrays = GPA( "glDrawArrays" ); + qglDrawBuffer = dllDrawBuffer = GPA( "glDrawBuffer" ); + qglDrawElements = dllDrawElements = GPA( "glDrawElements" ); + qglDrawPixels = dllDrawPixels = GPA( "glDrawPixels" ); + qglEdgeFlag = dllEdgeFlag = GPA( "glEdgeFlag" ); + qglEdgeFlagPointer = dllEdgeFlagPointer = GPA( "glEdgeFlagPointer" ); + qglEdgeFlagv = dllEdgeFlagv = GPA( "glEdgeFlagv" ); + qglEnable = dllEnable = GPA( "glEnable" ); + qglEnableClientState = dllEnableClientState = GPA( "glEnableClientState" ); + qglEnd = dllEnd = GPA( "glEnd" ); + qglEndList = dllEndList = GPA( "glEndList" ); + qglEvalCoord1d = dllEvalCoord1d = GPA( "glEvalCoord1d" ); + qglEvalCoord1dv = dllEvalCoord1dv = GPA( "glEvalCoord1dv" ); + qglEvalCoord1f = dllEvalCoord1f = GPA( "glEvalCoord1f" ); + qglEvalCoord1fv = dllEvalCoord1fv = GPA( "glEvalCoord1fv" ); + qglEvalCoord2d = dllEvalCoord2d = GPA( "glEvalCoord2d" ); + qglEvalCoord2dv = dllEvalCoord2dv = GPA( "glEvalCoord2dv" ); + qglEvalCoord2f = dllEvalCoord2f = GPA( "glEvalCoord2f" ); + qglEvalCoord2fv = dllEvalCoord2fv = GPA( "glEvalCoord2fv" ); + qglEvalMesh1 = dllEvalMesh1 = GPA( "glEvalMesh1" ); + qglEvalMesh2 = dllEvalMesh2 = GPA( "glEvalMesh2" ); + qglEvalPoint1 = dllEvalPoint1 = GPA( "glEvalPoint1" ); + qglEvalPoint2 = dllEvalPoint2 = GPA( "glEvalPoint2" ); + qglFeedbackBuffer = dllFeedbackBuffer = GPA( "glFeedbackBuffer" ); + qglFinish = dllFinish = GPA( "glFinish" ); + qglFlush = dllFlush = GPA( "glFlush" ); + qglFogf = dllFogf = GPA( "glFogf" ); + qglFogfv = dllFogfv = GPA( "glFogfv" ); + qglFogi = dllFogi = GPA( "glFogi" ); + qglFogiv = dllFogiv = GPA( "glFogiv" ); + qglFrontFace = dllFrontFace = GPA( "glFrontFace" ); + qglFrustum = dllFrustum = GPA( "glFrustum" ); + qglGenLists = dllGenLists = GPA( "glGenLists" ); + qglGenTextures = dllGenTextures = GPA( "glGenTextures" ); + qglGetBooleanv = dllGetBooleanv = GPA( "glGetBooleanv" ); + qglGetClipPlane = dllGetClipPlane = GPA( "glGetClipPlane" ); + qglGetDoublev = dllGetDoublev = GPA( "glGetDoublev" ); + qglGetError = dllGetError = GPA( "glGetError" ); + qglGetFloatv = dllGetFloatv = GPA( "glGetFloatv" ); + qglGetIntegerv = dllGetIntegerv = GPA( "glGetIntegerv" ); + qglGetLightfv = dllGetLightfv = GPA( "glGetLightfv" ); + qglGetLightiv = dllGetLightiv = GPA( "glGetLightiv" ); + qglGetMapdv = dllGetMapdv = GPA( "glGetMapdv" ); + qglGetMapfv = dllGetMapfv = GPA( "glGetMapfv" ); + qglGetMapiv = dllGetMapiv = GPA( "glGetMapiv" ); + qglGetMaterialfv = dllGetMaterialfv = GPA( "glGetMaterialfv" ); + qglGetMaterialiv = dllGetMaterialiv = GPA( "glGetMaterialiv" ); + qglGetPixelMapfv = dllGetPixelMapfv = GPA( "glGetPixelMapfv" ); + qglGetPixelMapuiv = dllGetPixelMapuiv = GPA( "glGetPixelMapuiv" ); + qglGetPixelMapusv = dllGetPixelMapusv = GPA( "glGetPixelMapusv" ); + qglGetPointerv = dllGetPointerv = GPA( "glGetPointerv" ); + qglGetPolygonStipple = dllGetPolygonStipple = GPA( "glGetPolygonStipple" ); + qglGetString = dllGetString = GPA( "glGetString" ); + qglGetTexEnvfv = dllGetTexEnvfv = GPA( "glGetTexEnvfv" ); + qglGetTexEnviv = dllGetTexEnviv = GPA( "glGetTexEnviv" ); + qglGetTexGendv = dllGetTexGendv = GPA( "glGetTexGendv" ); + qglGetTexGenfv = dllGetTexGenfv = GPA( "glGetTexGenfv" ); + qglGetTexGeniv = dllGetTexGeniv = GPA( "glGetTexGeniv" ); + qglGetTexImage = dllGetTexImage = GPA( "glGetTexImage" ); + qglGetTexLevelParameterfv = dllGetTexLevelParameterfv = GPA( "glGetLevelParameterfv" ); + qglGetTexLevelParameteriv = dllGetTexLevelParameteriv = GPA( "glGetLevelParameteriv" ); + qglGetTexParameterfv = dllGetTexParameterfv = GPA( "glGetTexParameterfv" ); + qglGetTexParameteriv = dllGetTexParameteriv = GPA( "glGetTexParameteriv" ); + qglHint = dllHint = GPA( "glHint" ); + qglIndexMask = dllIndexMask = GPA( "glIndexMask" ); + qglIndexPointer = dllIndexPointer = GPA( "glIndexPointer" ); + qglIndexd = dllIndexd = GPA( "glIndexd" ); + qglIndexdv = dllIndexdv = GPA( "glIndexdv" ); + qglIndexf = dllIndexf = GPA( "glIndexf" ); + qglIndexfv = dllIndexfv = GPA( "glIndexfv" ); + qglIndexi = dllIndexi = GPA( "glIndexi" ); + qglIndexiv = dllIndexiv = GPA( "glIndexiv" ); + qglIndexs = dllIndexs = GPA( "glIndexs" ); + qglIndexsv = dllIndexsv = GPA( "glIndexsv" ); + qglIndexub = dllIndexub = GPA( "glIndexub" ); + qglIndexubv = dllIndexubv = GPA( "glIndexubv" ); + qglInitNames = dllInitNames = GPA( "glInitNames" ); + qglInterleavedArrays = dllInterleavedArrays = GPA( "glInterleavedArrays" ); + qglIsEnabled = dllIsEnabled = GPA( "glIsEnabled" ); + qglIsList = dllIsList = GPA( "glIsList" ); + qglIsTexture = dllIsTexture = GPA( "glIsTexture" ); + qglLightModelf = dllLightModelf = GPA( "glLightModelf" ); + qglLightModelfv = dllLightModelfv = GPA( "glLightModelfv" ); + qglLightModeli = dllLightModeli = GPA( "glLightModeli" ); + qglLightModeliv = dllLightModeliv = GPA( "glLightModeliv" ); + qglLightf = dllLightf = GPA( "glLightf" ); + qglLightfv = dllLightfv = GPA( "glLightfv" ); + qglLighti = dllLighti = GPA( "glLighti" ); + qglLightiv = dllLightiv = GPA( "glLightiv" ); + qglLineStipple = dllLineStipple = GPA( "glLineStipple" ); + qglLineWidth = dllLineWidth = GPA( "glLineWidth" ); + qglListBase = dllListBase = GPA( "glListBase" ); + qglLoadIdentity = dllLoadIdentity = GPA( "glLoadIdentity" ); + qglLoadMatrixd = dllLoadMatrixd = GPA( "glLoadMatrixd" ); + qglLoadMatrixf = dllLoadMatrixf = GPA( "glLoadMatrixf" ); + qglLoadName = dllLoadName = GPA( "glLoadName" ); + qglLogicOp = dllLogicOp = GPA( "glLogicOp" ); + qglMap1d = dllMap1d = GPA( "glMap1d" ); + qglMap1f = dllMap1f = GPA( "glMap1f" ); + qglMap2d = dllMap2d = GPA( "glMap2d" ); + qglMap2f = dllMap2f = GPA( "glMap2f" ); + qglMapGrid1d = dllMapGrid1d = GPA( "glMapGrid1d" ); + qglMapGrid1f = dllMapGrid1f = GPA( "glMapGrid1f" ); + qglMapGrid2d = dllMapGrid2d = GPA( "glMapGrid2d" ); + qglMapGrid2f = dllMapGrid2f = GPA( "glMapGrid2f" ); + qglMaterialf = dllMaterialf = GPA( "glMaterialf" ); + qglMaterialfv = dllMaterialfv = GPA( "glMaterialfv" ); + qglMateriali = dllMateriali = GPA( "glMateriali" ); + qglMaterialiv = dllMaterialiv = GPA( "glMaterialiv" ); + qglMatrixMode = dllMatrixMode = GPA( "glMatrixMode" ); + qglMultMatrixd = dllMultMatrixd = GPA( "glMultMatrixd" ); + qglMultMatrixf = dllMultMatrixf = GPA( "glMultMatrixf" ); + qglNewList = dllNewList = GPA( "glNewList" ); + qglNormal3b = dllNormal3b = GPA( "glNormal3b" ); + qglNormal3bv = dllNormal3bv = GPA( "glNormal3bv" ); + qglNormal3d = dllNormal3d = GPA( "glNormal3d" ); + qglNormal3dv = dllNormal3dv = GPA( "glNormal3dv" ); + qglNormal3f = dllNormal3f = GPA( "glNormal3f" ); + qglNormal3fv = dllNormal3fv = GPA( "glNormal3fv" ); + qglNormal3i = dllNormal3i = GPA( "glNormal3i" ); + qglNormal3iv = dllNormal3iv = GPA( "glNormal3iv" ); + qglNormal3s = dllNormal3s = GPA( "glNormal3s" ); + qglNormal3sv = dllNormal3sv = GPA( "glNormal3sv" ); + qglNormalPointer = dllNormalPointer = GPA( "glNormalPointer" ); + qglOrtho = dllOrtho = GPA( "glOrtho" ); + qglPassThrough = dllPassThrough = GPA( "glPassThrough" ); + qglPixelMapfv = dllPixelMapfv = GPA( "glPixelMapfv" ); + qglPixelMapuiv = dllPixelMapuiv = GPA( "glPixelMapuiv" ); + qglPixelMapusv = dllPixelMapusv = GPA( "glPixelMapusv" ); + qglPixelStoref = dllPixelStoref = GPA( "glPixelStoref" ); + qglPixelStorei = dllPixelStorei = GPA( "glPixelStorei" ); + qglPixelTransferf = dllPixelTransferf = GPA( "glPixelTransferf" ); + qglPixelTransferi = dllPixelTransferi = GPA( "glPixelTransferi" ); + qglPixelZoom = dllPixelZoom = GPA( "glPixelZoom" ); + qglPointSize = dllPointSize = GPA( "glPointSize" ); + qglPolygonMode = dllPolygonMode = GPA( "glPolygonMode" ); + qglPolygonOffset = dllPolygonOffset = GPA( "glPolygonOffset" ); + qglPolygonStipple = dllPolygonStipple = GPA( "glPolygonStipple" ); + qglPopAttrib = dllPopAttrib = GPA( "glPopAttrib" ); + qglPopClientAttrib = dllPopClientAttrib = GPA( "glPopClientAttrib" ); + qglPopMatrix = dllPopMatrix = GPA( "glPopMatrix" ); + qglPopName = dllPopName = GPA( "glPopName" ); + qglPrioritizeTextures = dllPrioritizeTextures = GPA( "glPrioritizeTextures" ); + qglPushAttrib = dllPushAttrib = GPA( "glPushAttrib" ); + qglPushClientAttrib = dllPushClientAttrib = GPA( "glPushClientAttrib" ); + qglPushMatrix = dllPushMatrix = GPA( "glPushMatrix" ); + qglPushName = dllPushName = GPA( "glPushName" ); + qglRasterPos2d = dllRasterPos2d = GPA( "glRasterPos2d" ); + qglRasterPos2dv = dllRasterPos2dv = GPA( "glRasterPos2dv" ); + qglRasterPos2f = dllRasterPos2f = GPA( "glRasterPos2f" ); + qglRasterPos2fv = dllRasterPos2fv = GPA( "glRasterPos2fv" ); + qglRasterPos2i = dllRasterPos2i = GPA( "glRasterPos2i" ); + qglRasterPos2iv = dllRasterPos2iv = GPA( "glRasterPos2iv" ); + qglRasterPos2s = dllRasterPos2s = GPA( "glRasterPos2s" ); + qglRasterPos2sv = dllRasterPos2sv = GPA( "glRasterPos2sv" ); + qglRasterPos3d = dllRasterPos3d = GPA( "glRasterPos3d" ); + qglRasterPos3dv = dllRasterPos3dv = GPA( "glRasterPos3dv" ); + qglRasterPos3f = dllRasterPos3f = GPA( "glRasterPos3f" ); + qglRasterPos3fv = dllRasterPos3fv = GPA( "glRasterPos3fv" ); + qglRasterPos3i = dllRasterPos3i = GPA( "glRasterPos3i" ); + qglRasterPos3iv = dllRasterPos3iv = GPA( "glRasterPos3iv" ); + qglRasterPos3s = dllRasterPos3s = GPA( "glRasterPos3s" ); + qglRasterPos3sv = dllRasterPos3sv = GPA( "glRasterPos3sv" ); + qglRasterPos4d = dllRasterPos4d = GPA( "glRasterPos4d" ); + qglRasterPos4dv = dllRasterPos4dv = GPA( "glRasterPos4dv" ); + qglRasterPos4f = dllRasterPos4f = GPA( "glRasterPos4f" ); + qglRasterPos4fv = dllRasterPos4fv = GPA( "glRasterPos4fv" ); + qglRasterPos4i = dllRasterPos4i = GPA( "glRasterPos4i" ); + qglRasterPos4iv = dllRasterPos4iv = GPA( "glRasterPos4iv" ); + qglRasterPos4s = dllRasterPos4s = GPA( "glRasterPos4s" ); + qglRasterPos4sv = dllRasterPos4sv = GPA( "glRasterPos4sv" ); + qglReadBuffer = dllReadBuffer = GPA( "glReadBuffer" ); + qglReadPixels = dllReadPixels = GPA( "glReadPixels" ); + qglRectd = dllRectd = GPA( "glRectd" ); + qglRectdv = dllRectdv = GPA( "glRectdv" ); + qglRectf = dllRectf = GPA( "glRectf" ); + qglRectfv = dllRectfv = GPA( "glRectfv" ); + qglRecti = dllRecti = GPA( "glRecti" ); + qglRectiv = dllRectiv = GPA( "glRectiv" ); + qglRects = dllRects = GPA( "glRects" ); + qglRectsv = dllRectsv = GPA( "glRectsv" ); + qglRenderMode = dllRenderMode = GPA( "glRenderMode" ); + qglRotated = dllRotated = GPA( "glRotated" ); + qglRotatef = dllRotatef = GPA( "glRotatef" ); + qglScaled = dllScaled = GPA( "glScaled" ); + qglScalef = dllScalef = GPA( "glScalef" ); + qglScissor = dllScissor = GPA( "glScissor" ); + qglSelectBuffer = dllSelectBuffer = GPA( "glSelectBuffer" ); + qglShadeModel = dllShadeModel = GPA( "glShadeModel" ); + qglStencilFunc = dllStencilFunc = GPA( "glStencilFunc" ); + qglStencilMask = dllStencilMask = GPA( "glStencilMask" ); + qglStencilOp = dllStencilOp = GPA( "glStencilOp" ); + qglTexCoord1d = dllTexCoord1d = GPA( "glTexCoord1d" ); + qglTexCoord1dv = dllTexCoord1dv = GPA( "glTexCoord1dv" ); + qglTexCoord1f = dllTexCoord1f = GPA( "glTexCoord1f" ); + qglTexCoord1fv = dllTexCoord1fv = GPA( "glTexCoord1fv" ); + qglTexCoord1i = dllTexCoord1i = GPA( "glTexCoord1i" ); + qglTexCoord1iv = dllTexCoord1iv = GPA( "glTexCoord1iv" ); + qglTexCoord1s = dllTexCoord1s = GPA( "glTexCoord1s" ); + qglTexCoord1sv = dllTexCoord1sv = GPA( "glTexCoord1sv" ); + qglTexCoord2d = dllTexCoord2d = GPA( "glTexCoord2d" ); + qglTexCoord2dv = dllTexCoord2dv = GPA( "glTexCoord2dv" ); + qglTexCoord2f = dllTexCoord2f = GPA( "glTexCoord2f" ); + qglTexCoord2fv = dllTexCoord2fv = GPA( "glTexCoord2fv" ); + qglTexCoord2i = dllTexCoord2i = GPA( "glTexCoord2i" ); + qglTexCoord2iv = dllTexCoord2iv = GPA( "glTexCoord2iv" ); + qglTexCoord2s = dllTexCoord2s = GPA( "glTexCoord2s" ); + qglTexCoord2sv = dllTexCoord2sv = GPA( "glTexCoord2sv" ); + qglTexCoord3d = dllTexCoord3d = GPA( "glTexCoord3d" ); + qglTexCoord3dv = dllTexCoord3dv = GPA( "glTexCoord3dv" ); + qglTexCoord3f = dllTexCoord3f = GPA( "glTexCoord3f" ); + qglTexCoord3fv = dllTexCoord3fv = GPA( "glTexCoord3fv" ); + qglTexCoord3i = dllTexCoord3i = GPA( "glTexCoord3i" ); + qglTexCoord3iv = dllTexCoord3iv = GPA( "glTexCoord3iv" ); + qglTexCoord3s = dllTexCoord3s = GPA( "glTexCoord3s" ); + qglTexCoord3sv = dllTexCoord3sv = GPA( "glTexCoord3sv" ); + qglTexCoord4d = dllTexCoord4d = GPA( "glTexCoord4d" ); + qglTexCoord4dv = dllTexCoord4dv = GPA( "glTexCoord4dv" ); + qglTexCoord4f = dllTexCoord4f = GPA( "glTexCoord4f" ); + qglTexCoord4fv = dllTexCoord4fv = GPA( "glTexCoord4fv" ); + qglTexCoord4i = dllTexCoord4i = GPA( "glTexCoord4i" ); + qglTexCoord4iv = dllTexCoord4iv = GPA( "glTexCoord4iv" ); + qglTexCoord4s = dllTexCoord4s = GPA( "glTexCoord4s" ); + qglTexCoord4sv = dllTexCoord4sv = GPA( "glTexCoord4sv" ); + qglTexCoordPointer = dllTexCoordPointer = GPA( "glTexCoordPointer" ); + qglTexEnvf = dllTexEnvf = GPA( "glTexEnvf" ); + qglTexEnvfv = dllTexEnvfv = GPA( "glTexEnvfv" ); + qglTexEnvi = dllTexEnvi = GPA( "glTexEnvi" ); + qglTexEnviv = dllTexEnviv = GPA( "glTexEnviv" ); + qglTexGend = dllTexGend = GPA( "glTexGend" ); + qglTexGendv = dllTexGendv = GPA( "glTexGendv" ); + qglTexGenf = dllTexGenf = GPA( "glTexGenf" ); + qglTexGenfv = dllTexGenfv = GPA( "glTexGenfv" ); + qglTexGeni = dllTexGeni = GPA( "glTexGeni" ); + qglTexGeniv = dllTexGeniv = GPA( "glTexGeniv" ); + qglTexImage1D = dllTexImage1D = GPA( "glTexImage1D" ); + qglTexImage2D = dllTexImage2D = GPA( "glTexImage2D" ); + qglTexParameterf = dllTexParameterf = GPA( "glTexParameterf" ); + qglTexParameterfv = dllTexParameterfv = GPA( "glTexParameterfv" ); + qglTexParameteri = dllTexParameteri = GPA( "glTexParameteri" ); + qglTexParameteriv = dllTexParameteriv = GPA( "glTexParameteriv" ); + qglTexSubImage1D = dllTexSubImage1D = GPA( "glTexSubImage1D" ); + qglTexSubImage2D = dllTexSubImage2D = GPA( "glTexSubImage2D" ); + qglTranslated = dllTranslated = GPA( "glTranslated" ); + qglTranslatef = dllTranslatef = GPA( "glTranslatef" ); + qglVertex2d = dllVertex2d = GPA( "glVertex2d" ); + qglVertex2dv = dllVertex2dv = GPA( "glVertex2dv" ); + qglVertex2f = dllVertex2f = GPA( "glVertex2f" ); + qglVertex2fv = dllVertex2fv = GPA( "glVertex2fv" ); + qglVertex2i = dllVertex2i = GPA( "glVertex2i" ); + qglVertex2iv = dllVertex2iv = GPA( "glVertex2iv" ); + qglVertex2s = dllVertex2s = GPA( "glVertex2s" ); + qglVertex2sv = dllVertex2sv = GPA( "glVertex2sv" ); + qglVertex3d = dllVertex3d = GPA( "glVertex3d" ); + qglVertex3dv = dllVertex3dv = GPA( "glVertex3dv" ); + qglVertex3f = dllVertex3f = GPA( "glVertex3f" ); + qglVertex3fv = dllVertex3fv = GPA( "glVertex3fv" ); + qglVertex3i = dllVertex3i = GPA( "glVertex3i" ); + qglVertex3iv = dllVertex3iv = GPA( "glVertex3iv" ); + qglVertex3s = dllVertex3s = GPA( "glVertex3s" ); + qglVertex3sv = dllVertex3sv = GPA( "glVertex3sv" ); + qglVertex4d = dllVertex4d = GPA( "glVertex4d" ); + qglVertex4dv = dllVertex4dv = GPA( "glVertex4dv" ); + qglVertex4f = dllVertex4f = GPA( "glVertex4f" ); + qglVertex4fv = dllVertex4fv = GPA( "glVertex4fv" ); + qglVertex4i = dllVertex4i = GPA( "glVertex4i" ); + qglVertex4iv = dllVertex4iv = GPA( "glVertex4iv" ); + qglVertex4s = dllVertex4s = GPA( "glVertex4s" ); + qglVertex4sv = dllVertex4sv = GPA( "glVertex4sv" ); + qglVertexPointer = dllVertexPointer = GPA( "glVertexPointer" ); + qglViewport = dllViewport = GPA( "glViewport" ); + + qfxMesaCreateContext = GPA("fxMesaCreateContext"); + qfxMesaCreateBestContext = GPA("fxMesaCreateBestContext"); + qfxMesaDestroyContext = GPA("fxMesaDestroyContext"); + qfxMesaMakeCurrent = GPA("fxMesaMakeCurrent"); + qfxMesaGetCurrentContext = GPA("fxMesaGetCurrentContext"); + qfxMesaSwapBuffers = GPA("fxMesaSwapBuffers"); + + qglXChooseVisual = GPA("glXChooseVisual"); + qglXCreateContext = GPA("glXCreateContext"); + qglXDestroyContext = GPA("glXDestroyContext"); + qglXMakeCurrent = GPA("glXMakeCurrent"); + qglXCopyContext = GPA("glXCopyContext"); + qglXSwapBuffers = GPA("glXSwapBuffers"); + + qglLockArraysEXT = 0; + qglUnlockArraysEXT = 0; + qglPointParameterfEXT = 0; + qglPointParameterfvEXT = 0; + qglColorTableEXT = 0; + qgl3DfxSetPaletteEXT = 0; + qglSelectTextureSGIS = 0; + qglMTexCoord2fSGIS = 0; + qglActiveTextureARB = 0; + qglClientActiveTextureARB = 0; + qglMultiTexCoord2fARB = 0; + + return qtrue; +} + +void QGL_EnableLogging( qboolean enable ) +{ + if ( enable ) + { + if ( !glw_state.log_fp ) + { + struct tm *newtime; + time_t aclock; + char buffer[1024]; + cvar_t *basedir; + + time( &aclock ); + newtime = localtime( &aclock ); + + asctime( newtime ); + + basedir = ri.Cvar_Get( "basedir", "", 0 ); + Com_sprintf( buffer, sizeof(buffer), "%s/gl.log", basedir->string ); + glw_state.log_fp = fopen( buffer, "wt" ); + + fprintf( glw_state.log_fp, "%s\n", asctime( newtime ) ); + } + + qglAccum = logAccum; + qglAlphaFunc = logAlphaFunc; + qglAreTexturesResident = logAreTexturesResident; + qglArrayElement = logArrayElement; + qglBegin = logBegin; + qglBindTexture = logBindTexture; + qglBitmap = logBitmap; + qglBlendFunc = logBlendFunc; + qglCallList = logCallList; + qglCallLists = logCallLists; + qglClear = logClear; + qglClearAccum = logClearAccum; + qglClearColor = logClearColor; + qglClearDepth = logClearDepth; + qglClearIndex = logClearIndex; + qglClearStencil = logClearStencil; + qglClipPlane = logClipPlane; + qglColor3b = logColor3b; + qglColor3bv = logColor3bv; + qglColor3d = logColor3d; + qglColor3dv = logColor3dv; + qglColor3f = logColor3f; + qglColor3fv = logColor3fv; + qglColor3i = logColor3i; + qglColor3iv = logColor3iv; + qglColor3s = logColor3s; + qglColor3sv = logColor3sv; + qglColor3ub = logColor3ub; + qglColor3ubv = logColor3ubv; + qglColor3ui = logColor3ui; + qglColor3uiv = logColor3uiv; + qglColor3us = logColor3us; + qglColor3usv = logColor3usv; + qglColor4b = logColor4b; + qglColor4bv = logColor4bv; + qglColor4d = logColor4d; + qglColor4dv = logColor4dv; + qglColor4f = logColor4f; + qglColor4fv = logColor4fv; + qglColor4i = logColor4i; + qglColor4iv = logColor4iv; + qglColor4s = logColor4s; + qglColor4sv = logColor4sv; + qglColor4ub = logColor4ub; + qglColor4ubv = logColor4ubv; + qglColor4ui = logColor4ui; + qglColor4uiv = logColor4uiv; + qglColor4us = logColor4us; + qglColor4usv = logColor4usv; + qglColorMask = logColorMask; + qglColorMaterial = logColorMaterial; + qglColorPointer = logColorPointer; + qglCopyPixels = logCopyPixels; + qglCopyTexImage1D = logCopyTexImage1D; + qglCopyTexImage2D = logCopyTexImage2D; + qglCopyTexSubImage1D = logCopyTexSubImage1D; + qglCopyTexSubImage2D = logCopyTexSubImage2D; + qglCullFace = logCullFace; + qglDeleteLists = logDeleteLists ; + qglDeleteTextures = logDeleteTextures ; + qglDepthFunc = logDepthFunc ; + qglDepthMask = logDepthMask ; + qglDepthRange = logDepthRange ; + qglDisable = logDisable ; + qglDisableClientState = logDisableClientState ; + qglDrawArrays = logDrawArrays ; + qglDrawBuffer = logDrawBuffer ; + qglDrawElements = logDrawElements ; + qglDrawPixels = logDrawPixels ; + qglEdgeFlag = logEdgeFlag ; + qglEdgeFlagPointer = logEdgeFlagPointer ; + qglEdgeFlagv = logEdgeFlagv ; + qglEnable = logEnable ; + qglEnableClientState = logEnableClientState ; + qglEnd = logEnd ; + qglEndList = logEndList ; + qglEvalCoord1d = logEvalCoord1d ; + qglEvalCoord1dv = logEvalCoord1dv ; + qglEvalCoord1f = logEvalCoord1f ; + qglEvalCoord1fv = logEvalCoord1fv ; + qglEvalCoord2d = logEvalCoord2d ; + qglEvalCoord2dv = logEvalCoord2dv ; + qglEvalCoord2f = logEvalCoord2f ; + qglEvalCoord2fv = logEvalCoord2fv ; + qglEvalMesh1 = logEvalMesh1 ; + qglEvalMesh2 = logEvalMesh2 ; + qglEvalPoint1 = logEvalPoint1 ; + qglEvalPoint2 = logEvalPoint2 ; + qglFeedbackBuffer = logFeedbackBuffer ; + qglFinish = logFinish ; + qglFlush = logFlush ; + qglFogf = logFogf ; + qglFogfv = logFogfv ; + qglFogi = logFogi ; + qglFogiv = logFogiv ; + qglFrontFace = logFrontFace ; + qglFrustum = logFrustum ; + qglGenLists = logGenLists ; + qglGenTextures = logGenTextures ; + qglGetBooleanv = logGetBooleanv ; + qglGetClipPlane = logGetClipPlane ; + qglGetDoublev = logGetDoublev ; + qglGetError = logGetError ; + qglGetFloatv = logGetFloatv ; + qglGetIntegerv = logGetIntegerv ; + qglGetLightfv = logGetLightfv ; + qglGetLightiv = logGetLightiv ; + qglGetMapdv = logGetMapdv ; + qglGetMapfv = logGetMapfv ; + qglGetMapiv = logGetMapiv ; + qglGetMaterialfv = logGetMaterialfv ; + qglGetMaterialiv = logGetMaterialiv ; + qglGetPixelMapfv = logGetPixelMapfv ; + qglGetPixelMapuiv = logGetPixelMapuiv ; + qglGetPixelMapusv = logGetPixelMapusv ; + qglGetPointerv = logGetPointerv ; + qglGetPolygonStipple = logGetPolygonStipple ; + qglGetString = logGetString ; + qglGetTexEnvfv = logGetTexEnvfv ; + qglGetTexEnviv = logGetTexEnviv ; + qglGetTexGendv = logGetTexGendv ; + qglGetTexGenfv = logGetTexGenfv ; + qglGetTexGeniv = logGetTexGeniv ; + qglGetTexImage = logGetTexImage ; + qglGetTexLevelParameterfv = logGetTexLevelParameterfv ; + qglGetTexLevelParameteriv = logGetTexLevelParameteriv ; + qglGetTexParameterfv = logGetTexParameterfv ; + qglGetTexParameteriv = logGetTexParameteriv ; + qglHint = logHint ; + qglIndexMask = logIndexMask ; + qglIndexPointer = logIndexPointer ; + qglIndexd = logIndexd ; + qglIndexdv = logIndexdv ; + qglIndexf = logIndexf ; + qglIndexfv = logIndexfv ; + qglIndexi = logIndexi ; + qglIndexiv = logIndexiv ; + qglIndexs = logIndexs ; + qglIndexsv = logIndexsv ; + qglIndexub = logIndexub ; + qglIndexubv = logIndexubv ; + qglInitNames = logInitNames ; + qglInterleavedArrays = logInterleavedArrays ; + qglIsEnabled = logIsEnabled ; + qglIsList = logIsList ; + qglIsTexture = logIsTexture ; + qglLightModelf = logLightModelf ; + qglLightModelfv = logLightModelfv ; + qglLightModeli = logLightModeli ; + qglLightModeliv = logLightModeliv ; + qglLightf = logLightf ; + qglLightfv = logLightfv ; + qglLighti = logLighti ; + qglLightiv = logLightiv ; + qglLineStipple = logLineStipple ; + qglLineWidth = logLineWidth ; + qglListBase = logListBase ; + qglLoadIdentity = logLoadIdentity ; + qglLoadMatrixd = logLoadMatrixd ; + qglLoadMatrixf = logLoadMatrixf ; + qglLoadName = logLoadName ; + qglLogicOp = logLogicOp ; + qglMap1d = logMap1d ; + qglMap1f = logMap1f ; + qglMap2d = logMap2d ; + qglMap2f = logMap2f ; + qglMapGrid1d = logMapGrid1d ; + qglMapGrid1f = logMapGrid1f ; + qglMapGrid2d = logMapGrid2d ; + qglMapGrid2f = logMapGrid2f ; + qglMaterialf = logMaterialf ; + qglMaterialfv = logMaterialfv ; + qglMateriali = logMateriali ; + qglMaterialiv = logMaterialiv ; + qglMatrixMode = logMatrixMode ; + qglMultMatrixd = logMultMatrixd ; + qglMultMatrixf = logMultMatrixf ; + qglNewList = logNewList ; + qglNormal3b = logNormal3b ; + qglNormal3bv = logNormal3bv ; + qglNormal3d = logNormal3d ; + qglNormal3dv = logNormal3dv ; + qglNormal3f = logNormal3f ; + qglNormal3fv = logNormal3fv ; + qglNormal3i = logNormal3i ; + qglNormal3iv = logNormal3iv ; + qglNormal3s = logNormal3s ; + qglNormal3sv = logNormal3sv ; + qglNormalPointer = logNormalPointer ; + qglOrtho = logOrtho ; + qglPassThrough = logPassThrough ; + qglPixelMapfv = logPixelMapfv ; + qglPixelMapuiv = logPixelMapuiv ; + qglPixelMapusv = logPixelMapusv ; + qglPixelStoref = logPixelStoref ; + qglPixelStorei = logPixelStorei ; + qglPixelTransferf = logPixelTransferf ; + qglPixelTransferi = logPixelTransferi ; + qglPixelZoom = logPixelZoom ; + qglPointSize = logPointSize ; + qglPolygonMode = logPolygonMode ; + qglPolygonOffset = logPolygonOffset ; + qglPolygonStipple = logPolygonStipple ; + qglPopAttrib = logPopAttrib ; + qglPopClientAttrib = logPopClientAttrib ; + qglPopMatrix = logPopMatrix ; + qglPopName = logPopName ; + qglPrioritizeTextures = logPrioritizeTextures ; + qglPushAttrib = logPushAttrib ; + qglPushClientAttrib = logPushClientAttrib ; + qglPushMatrix = logPushMatrix ; + qglPushName = logPushName ; + qglRasterPos2d = logRasterPos2d ; + qglRasterPos2dv = logRasterPos2dv ; + qglRasterPos2f = logRasterPos2f ; + qglRasterPos2fv = logRasterPos2fv ; + qglRasterPos2i = logRasterPos2i ; + qglRasterPos2iv = logRasterPos2iv ; + qglRasterPos2s = logRasterPos2s ; + qglRasterPos2sv = logRasterPos2sv ; + qglRasterPos3d = logRasterPos3d ; + qglRasterPos3dv = logRasterPos3dv ; + qglRasterPos3f = logRasterPos3f ; + qglRasterPos3fv = logRasterPos3fv ; + qglRasterPos3i = logRasterPos3i ; + qglRasterPos3iv = logRasterPos3iv ; + qglRasterPos3s = logRasterPos3s ; + qglRasterPos3sv = logRasterPos3sv ; + qglRasterPos4d = logRasterPos4d ; + qglRasterPos4dv = logRasterPos4dv ; + qglRasterPos4f = logRasterPos4f ; + qglRasterPos4fv = logRasterPos4fv ; + qglRasterPos4i = logRasterPos4i ; + qglRasterPos4iv = logRasterPos4iv ; + qglRasterPos4s = logRasterPos4s ; + qglRasterPos4sv = logRasterPos4sv ; + qglReadBuffer = logReadBuffer ; + qglReadPixels = logReadPixels ; + qglRectd = logRectd ; + qglRectdv = logRectdv ; + qglRectf = logRectf ; + qglRectfv = logRectfv ; + qglRecti = logRecti ; + qglRectiv = logRectiv ; + qglRects = logRects ; + qglRectsv = logRectsv ; + qglRenderMode = logRenderMode ; + qglRotated = logRotated ; + qglRotatef = logRotatef ; + qglScaled = logScaled ; + qglScalef = logScalef ; + qglScissor = logScissor ; + qglSelectBuffer = logSelectBuffer ; + qglShadeModel = logShadeModel ; + qglStencilFunc = logStencilFunc ; + qglStencilMask = logStencilMask ; + qglStencilOp = logStencilOp ; + qglTexCoord1d = logTexCoord1d ; + qglTexCoord1dv = logTexCoord1dv ; + qglTexCoord1f = logTexCoord1f ; + qglTexCoord1fv = logTexCoord1fv ; + qglTexCoord1i = logTexCoord1i ; + qglTexCoord1iv = logTexCoord1iv ; + qglTexCoord1s = logTexCoord1s ; + qglTexCoord1sv = logTexCoord1sv ; + qglTexCoord2d = logTexCoord2d ; + qglTexCoord2dv = logTexCoord2dv ; + qglTexCoord2f = logTexCoord2f ; + qglTexCoord2fv = logTexCoord2fv ; + qglTexCoord2i = logTexCoord2i ; + qglTexCoord2iv = logTexCoord2iv ; + qglTexCoord2s = logTexCoord2s ; + qglTexCoord2sv = logTexCoord2sv ; + qglTexCoord3d = logTexCoord3d ; + qglTexCoord3dv = logTexCoord3dv ; + qglTexCoord3f = logTexCoord3f ; + qglTexCoord3fv = logTexCoord3fv ; + qglTexCoord3i = logTexCoord3i ; + qglTexCoord3iv = logTexCoord3iv ; + qglTexCoord3s = logTexCoord3s ; + qglTexCoord3sv = logTexCoord3sv ; + qglTexCoord4d = logTexCoord4d ; + qglTexCoord4dv = logTexCoord4dv ; + qglTexCoord4f = logTexCoord4f ; + qglTexCoord4fv = logTexCoord4fv ; + qglTexCoord4i = logTexCoord4i ; + qglTexCoord4iv = logTexCoord4iv ; + qglTexCoord4s = logTexCoord4s ; + qglTexCoord4sv = logTexCoord4sv ; + qglTexCoordPointer = logTexCoordPointer ; + qglTexEnvf = logTexEnvf ; + qglTexEnvfv = logTexEnvfv ; + qglTexEnvi = logTexEnvi ; + qglTexEnviv = logTexEnviv ; + qglTexGend = logTexGend ; + qglTexGendv = logTexGendv ; + qglTexGenf = logTexGenf ; + qglTexGenfv = logTexGenfv ; + qglTexGeni = logTexGeni ; + qglTexGeniv = logTexGeniv ; + qglTexImage1D = logTexImage1D ; + qglTexImage2D = logTexImage2D ; + qglTexParameterf = logTexParameterf ; + qglTexParameterfv = logTexParameterfv ; + qglTexParameteri = logTexParameteri ; + qglTexParameteriv = logTexParameteriv ; + qglTexSubImage1D = logTexSubImage1D ; + qglTexSubImage2D = logTexSubImage2D ; + qglTranslated = logTranslated ; + qglTranslatef = logTranslatef ; + qglVertex2d = logVertex2d ; + qglVertex2dv = logVertex2dv ; + qglVertex2f = logVertex2f ; + qglVertex2fv = logVertex2fv ; + qglVertex2i = logVertex2i ; + qglVertex2iv = logVertex2iv ; + qglVertex2s = logVertex2s ; + qglVertex2sv = logVertex2sv ; + qglVertex3d = logVertex3d ; + qglVertex3dv = logVertex3dv ; + qglVertex3f = logVertex3f ; + qglVertex3fv = logVertex3fv ; + qglVertex3i = logVertex3i ; + qglVertex3iv = logVertex3iv ; + qglVertex3s = logVertex3s ; + qglVertex3sv = logVertex3sv ; + qglVertex4d = logVertex4d ; + qglVertex4dv = logVertex4dv ; + qglVertex4f = logVertex4f ; + qglVertex4fv = logVertex4fv ; + qglVertex4i = logVertex4i ; + qglVertex4iv = logVertex4iv ; + qglVertex4s = logVertex4s ; + qglVertex4sv = logVertex4sv ; + qglVertexPointer = logVertexPointer ; + qglViewport = logViewport ; + } + else + { + qglAccum = dllAccum; + qglAlphaFunc = dllAlphaFunc; + qglAreTexturesResident = dllAreTexturesResident; + qglArrayElement = dllArrayElement; + qglBegin = dllBegin; + qglBindTexture = dllBindTexture; + qglBitmap = dllBitmap; + qglBlendFunc = dllBlendFunc; + qglCallList = dllCallList; + qglCallLists = dllCallLists; + qglClear = dllClear; + qglClearAccum = dllClearAccum; + qglClearColor = dllClearColor; + qglClearDepth = dllClearDepth; + qglClearIndex = dllClearIndex; + qglClearStencil = dllClearStencil; + qglClipPlane = dllClipPlane; + qglColor3b = dllColor3b; + qglColor3bv = dllColor3bv; + qglColor3d = dllColor3d; + qglColor3dv = dllColor3dv; + qglColor3f = dllColor3f; + qglColor3fv = dllColor3fv; + qglColor3i = dllColor3i; + qglColor3iv = dllColor3iv; + qglColor3s = dllColor3s; + qglColor3sv = dllColor3sv; + qglColor3ub = dllColor3ub; + qglColor3ubv = dllColor3ubv; + qglColor3ui = dllColor3ui; + qglColor3uiv = dllColor3uiv; + qglColor3us = dllColor3us; + qglColor3usv = dllColor3usv; + qglColor4b = dllColor4b; + qglColor4bv = dllColor4bv; + qglColor4d = dllColor4d; + qglColor4dv = dllColor4dv; + qglColor4f = dllColor4f; + qglColor4fv = dllColor4fv; + qglColor4i = dllColor4i; + qglColor4iv = dllColor4iv; + qglColor4s = dllColor4s; + qglColor4sv = dllColor4sv; + qglColor4ub = dllColor4ub; + qglColor4ubv = dllColor4ubv; + qglColor4ui = dllColor4ui; + qglColor4uiv = dllColor4uiv; + qglColor4us = dllColor4us; + qglColor4usv = dllColor4usv; + qglColorMask = dllColorMask; + qglColorMaterial = dllColorMaterial; + qglColorPointer = dllColorPointer; + qglCopyPixels = dllCopyPixels; + qglCopyTexImage1D = dllCopyTexImage1D; + qglCopyTexImage2D = dllCopyTexImage2D; + qglCopyTexSubImage1D = dllCopyTexSubImage1D; + qglCopyTexSubImage2D = dllCopyTexSubImage2D; + qglCullFace = dllCullFace; + qglDeleteLists = dllDeleteLists ; + qglDeleteTextures = dllDeleteTextures ; + qglDepthFunc = dllDepthFunc ; + qglDepthMask = dllDepthMask ; + qglDepthRange = dllDepthRange ; + qglDisable = dllDisable ; + qglDisableClientState = dllDisableClientState ; + qglDrawArrays = dllDrawArrays ; + qglDrawBuffer = dllDrawBuffer ; + qglDrawElements = dllDrawElements ; + qglDrawPixels = dllDrawPixels ; + qglEdgeFlag = dllEdgeFlag ; + qglEdgeFlagPointer = dllEdgeFlagPointer ; + qglEdgeFlagv = dllEdgeFlagv ; + qglEnable = dllEnable ; + qglEnableClientState = dllEnableClientState ; + qglEnd = dllEnd ; + qglEndList = dllEndList ; + qglEvalCoord1d = dllEvalCoord1d ; + qglEvalCoord1dv = dllEvalCoord1dv ; + qglEvalCoord1f = dllEvalCoord1f ; + qglEvalCoord1fv = dllEvalCoord1fv ; + qglEvalCoord2d = dllEvalCoord2d ; + qglEvalCoord2dv = dllEvalCoord2dv ; + qglEvalCoord2f = dllEvalCoord2f ; + qglEvalCoord2fv = dllEvalCoord2fv ; + qglEvalMesh1 = dllEvalMesh1 ; + qglEvalMesh2 = dllEvalMesh2 ; + qglEvalPoint1 = dllEvalPoint1 ; + qglEvalPoint2 = dllEvalPoint2 ; + qglFeedbackBuffer = dllFeedbackBuffer ; + qglFinish = dllFinish ; + qglFlush = dllFlush ; + qglFogf = dllFogf ; + qglFogfv = dllFogfv ; + qglFogi = dllFogi ; + qglFogiv = dllFogiv ; + qglFrontFace = dllFrontFace ; + qglFrustum = dllFrustum ; + qglGenLists = dllGenLists ; + qglGenTextures = dllGenTextures ; + qglGetBooleanv = dllGetBooleanv ; + qglGetClipPlane = dllGetClipPlane ; + qglGetDoublev = dllGetDoublev ; + qglGetError = dllGetError ; + qglGetFloatv = dllGetFloatv ; + qglGetIntegerv = dllGetIntegerv ; + qglGetLightfv = dllGetLightfv ; + qglGetLightiv = dllGetLightiv ; + qglGetMapdv = dllGetMapdv ; + qglGetMapfv = dllGetMapfv ; + qglGetMapiv = dllGetMapiv ; + qglGetMaterialfv = dllGetMaterialfv ; + qglGetMaterialiv = dllGetMaterialiv ; + qglGetPixelMapfv = dllGetPixelMapfv ; + qglGetPixelMapuiv = dllGetPixelMapuiv ; + qglGetPixelMapusv = dllGetPixelMapusv ; + qglGetPointerv = dllGetPointerv ; + qglGetPolygonStipple = dllGetPolygonStipple ; + qglGetString = dllGetString ; + qglGetTexEnvfv = dllGetTexEnvfv ; + qglGetTexEnviv = dllGetTexEnviv ; + qglGetTexGendv = dllGetTexGendv ; + qglGetTexGenfv = dllGetTexGenfv ; + qglGetTexGeniv = dllGetTexGeniv ; + qglGetTexImage = dllGetTexImage ; + qglGetTexLevelParameterfv = dllGetTexLevelParameterfv ; + qglGetTexLevelParameteriv = dllGetTexLevelParameteriv ; + qglGetTexParameterfv = dllGetTexParameterfv ; + qglGetTexParameteriv = dllGetTexParameteriv ; + qglHint = dllHint ; + qglIndexMask = dllIndexMask ; + qglIndexPointer = dllIndexPointer ; + qglIndexd = dllIndexd ; + qglIndexdv = dllIndexdv ; + qglIndexf = dllIndexf ; + qglIndexfv = dllIndexfv ; + qglIndexi = dllIndexi ; + qglIndexiv = dllIndexiv ; + qglIndexs = dllIndexs ; + qglIndexsv = dllIndexsv ; + qglIndexub = dllIndexub ; + qglIndexubv = dllIndexubv ; + qglInitNames = dllInitNames ; + qglInterleavedArrays = dllInterleavedArrays ; + qglIsEnabled = dllIsEnabled ; + qglIsList = dllIsList ; + qglIsTexture = dllIsTexture ; + qglLightModelf = dllLightModelf ; + qglLightModelfv = dllLightModelfv ; + qglLightModeli = dllLightModeli ; + qglLightModeliv = dllLightModeliv ; + qglLightf = dllLightf ; + qglLightfv = dllLightfv ; + qglLighti = dllLighti ; + qglLightiv = dllLightiv ; + qglLineStipple = dllLineStipple ; + qglLineWidth = dllLineWidth ; + qglListBase = dllListBase ; + qglLoadIdentity = dllLoadIdentity ; + qglLoadMatrixd = dllLoadMatrixd ; + qglLoadMatrixf = dllLoadMatrixf ; + qglLoadName = dllLoadName ; + qglLogicOp = dllLogicOp ; + qglMap1d = dllMap1d ; + qglMap1f = dllMap1f ; + qglMap2d = dllMap2d ; + qglMap2f = dllMap2f ; + qglMapGrid1d = dllMapGrid1d ; + qglMapGrid1f = dllMapGrid1f ; + qglMapGrid2d = dllMapGrid2d ; + qglMapGrid2f = dllMapGrid2f ; + qglMaterialf = dllMaterialf ; + qglMaterialfv = dllMaterialfv ; + qglMateriali = dllMateriali ; + qglMaterialiv = dllMaterialiv ; + qglMatrixMode = dllMatrixMode ; + qglMultMatrixd = dllMultMatrixd ; + qglMultMatrixf = dllMultMatrixf ; + qglNewList = dllNewList ; + qglNormal3b = dllNormal3b ; + qglNormal3bv = dllNormal3bv ; + qglNormal3d = dllNormal3d ; + qglNormal3dv = dllNormal3dv ; + qglNormal3f = dllNormal3f ; + qglNormal3fv = dllNormal3fv ; + qglNormal3i = dllNormal3i ; + qglNormal3iv = dllNormal3iv ; + qglNormal3s = dllNormal3s ; + qglNormal3sv = dllNormal3sv ; + qglNormalPointer = dllNormalPointer ; + qglOrtho = dllOrtho ; + qglPassThrough = dllPassThrough ; + qglPixelMapfv = dllPixelMapfv ; + qglPixelMapuiv = dllPixelMapuiv ; + qglPixelMapusv = dllPixelMapusv ; + qglPixelStoref = dllPixelStoref ; + qglPixelStorei = dllPixelStorei ; + qglPixelTransferf = dllPixelTransferf ; + qglPixelTransferi = dllPixelTransferi ; + qglPixelZoom = dllPixelZoom ; + qglPointSize = dllPointSize ; + qglPolygonMode = dllPolygonMode ; + qglPolygonOffset = dllPolygonOffset ; + qglPolygonStipple = dllPolygonStipple ; + qglPopAttrib = dllPopAttrib ; + qglPopClientAttrib = dllPopClientAttrib ; + qglPopMatrix = dllPopMatrix ; + qglPopName = dllPopName ; + qglPrioritizeTextures = dllPrioritizeTextures ; + qglPushAttrib = dllPushAttrib ; + qglPushClientAttrib = dllPushClientAttrib ; + qglPushMatrix = dllPushMatrix ; + qglPushName = dllPushName ; + qglRasterPos2d = dllRasterPos2d ; + qglRasterPos2dv = dllRasterPos2dv ; + qglRasterPos2f = dllRasterPos2f ; + qglRasterPos2fv = dllRasterPos2fv ; + qglRasterPos2i = dllRasterPos2i ; + qglRasterPos2iv = dllRasterPos2iv ; + qglRasterPos2s = dllRasterPos2s ; + qglRasterPos2sv = dllRasterPos2sv ; + qglRasterPos3d = dllRasterPos3d ; + qglRasterPos3dv = dllRasterPos3dv ; + qglRasterPos3f = dllRasterPos3f ; + qglRasterPos3fv = dllRasterPos3fv ; + qglRasterPos3i = dllRasterPos3i ; + qglRasterPos3iv = dllRasterPos3iv ; + qglRasterPos3s = dllRasterPos3s ; + qglRasterPos3sv = dllRasterPos3sv ; + qglRasterPos4d = dllRasterPos4d ; + qglRasterPos4dv = dllRasterPos4dv ; + qglRasterPos4f = dllRasterPos4f ; + qglRasterPos4fv = dllRasterPos4fv ; + qglRasterPos4i = dllRasterPos4i ; + qglRasterPos4iv = dllRasterPos4iv ; + qglRasterPos4s = dllRasterPos4s ; + qglRasterPos4sv = dllRasterPos4sv ; + qglReadBuffer = dllReadBuffer ; + qglReadPixels = dllReadPixels ; + qglRectd = dllRectd ; + qglRectdv = dllRectdv ; + qglRectf = dllRectf ; + qglRectfv = dllRectfv ; + qglRecti = dllRecti ; + qglRectiv = dllRectiv ; + qglRects = dllRects ; + qglRectsv = dllRectsv ; + qglRenderMode = dllRenderMode ; + qglRotated = dllRotated ; + qglRotatef = dllRotatef ; + qglScaled = dllScaled ; + qglScalef = dllScalef ; + qglScissor = dllScissor ; + qglSelectBuffer = dllSelectBuffer ; + qglShadeModel = dllShadeModel ; + qglStencilFunc = dllStencilFunc ; + qglStencilMask = dllStencilMask ; + qglStencilOp = dllStencilOp ; + qglTexCoord1d = dllTexCoord1d ; + qglTexCoord1dv = dllTexCoord1dv ; + qglTexCoord1f = dllTexCoord1f ; + qglTexCoord1fv = dllTexCoord1fv ; + qglTexCoord1i = dllTexCoord1i ; + qglTexCoord1iv = dllTexCoord1iv ; + qglTexCoord1s = dllTexCoord1s ; + qglTexCoord1sv = dllTexCoord1sv ; + qglTexCoord2d = dllTexCoord2d ; + qglTexCoord2dv = dllTexCoord2dv ; + qglTexCoord2f = dllTexCoord2f ; + qglTexCoord2fv = dllTexCoord2fv ; + qglTexCoord2i = dllTexCoord2i ; + qglTexCoord2iv = dllTexCoord2iv ; + qglTexCoord2s = dllTexCoord2s ; + qglTexCoord2sv = dllTexCoord2sv ; + qglTexCoord3d = dllTexCoord3d ; + qglTexCoord3dv = dllTexCoord3dv ; + qglTexCoord3f = dllTexCoord3f ; + qglTexCoord3fv = dllTexCoord3fv ; + qglTexCoord3i = dllTexCoord3i ; + qglTexCoord3iv = dllTexCoord3iv ; + qglTexCoord3s = dllTexCoord3s ; + qglTexCoord3sv = dllTexCoord3sv ; + qglTexCoord4d = dllTexCoord4d ; + qglTexCoord4dv = dllTexCoord4dv ; + qglTexCoord4f = dllTexCoord4f ; + qglTexCoord4fv = dllTexCoord4fv ; + qglTexCoord4i = dllTexCoord4i ; + qglTexCoord4iv = dllTexCoord4iv ; + qglTexCoord4s = dllTexCoord4s ; + qglTexCoord4sv = dllTexCoord4sv ; + qglTexCoordPointer = dllTexCoordPointer ; + qglTexEnvf = dllTexEnvf ; + qglTexEnvfv = dllTexEnvfv ; + qglTexEnvi = dllTexEnvi ; + qglTexEnviv = dllTexEnviv ; + qglTexGend = dllTexGend ; + qglTexGendv = dllTexGendv ; + qglTexGenf = dllTexGenf ; + qglTexGenfv = dllTexGenfv ; + qglTexGeni = dllTexGeni ; + qglTexGeniv = dllTexGeniv ; + qglTexImage1D = dllTexImage1D ; + qglTexImage2D = dllTexImage2D ; + qglTexParameterf = dllTexParameterf ; + qglTexParameterfv = dllTexParameterfv ; + qglTexParameteri = dllTexParameteri ; + qglTexParameteriv = dllTexParameteriv ; + qglTexSubImage1D = dllTexSubImage1D ; + qglTexSubImage2D = dllTexSubImage2D ; + qglTranslated = dllTranslated ; + qglTranslatef = dllTranslatef ; + qglVertex2d = dllVertex2d ; + qglVertex2dv = dllVertex2dv ; + qglVertex2f = dllVertex2f ; + qglVertex2fv = dllVertex2fv ; + qglVertex2i = dllVertex2i ; + qglVertex2iv = dllVertex2iv ; + qglVertex2s = dllVertex2s ; + qglVertex2sv = dllVertex2sv ; + qglVertex3d = dllVertex3d ; + qglVertex3dv = dllVertex3dv ; + qglVertex3f = dllVertex3f ; + qglVertex3fv = dllVertex3fv ; + qglVertex3i = dllVertex3i ; + qglVertex3iv = dllVertex3iv ; + qglVertex3s = dllVertex3s ; + qglVertex3sv = dllVertex3sv ; + qglVertex4d = dllVertex4d ; + qglVertex4dv = dllVertex4dv ; + qglVertex4f = dllVertex4f ; + qglVertex4fv = dllVertex4fv ; + qglVertex4i = dllVertex4i ; + qglVertex4iv = dllVertex4iv ; + qglVertex4s = dllVertex4s ; + qglVertex4sv = dllVertex4sv ; + qglVertexPointer = dllVertexPointer ; + qglViewport = dllViewport ; + } +} + + +void GLimp_LogNewFrame( void ) +{ + fprintf( glw_state.log_fp, "*** R_BeginFrame ***\n" ); +} + + diff --git a/code/unix/linux_snd.c b/code/unix/linux_snd.c new file mode 100644 index 0000000..112595b --- /dev/null +++ b/code/unix/linux_snd.c @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../client/snd_local.h" + +int audio_fd; +int snd_inited=0; + +cvar_t *sndbits; +cvar_t *sndspeed; +cvar_t *sndchannels; + +cvar_t *snddevice; + +/* Some devices may work only with 48000 */ +static int tryrates[] = { 22050, 11025, 44100, 48000, 8000 }; + +qboolean SNDDMA_Init(void) +{ + int rc; + int fmt; + int tmp; + int i; + char *s; + struct audio_buf_info info; + int caps; + extern uid_t saved_euid; + + if (snd_inited) + return; + + if (!snddevice) { + sndbits = Cvar_Get("sndbits", "16", CVAR_ARCHIVE); + sndspeed = Cvar_Get("sndspeed", "0", CVAR_ARCHIVE); + sndchannels = Cvar_Get("sndchannels", "2", CVAR_ARCHIVE); + snddevice = Cvar_Get("snddevice", "/dev/dsp", CVAR_ARCHIVE); + } + +// open /dev/dsp, confirm capability to mmap, and get size of dma buffer + if (!audio_fd) { + seteuid(saved_euid); + + audio_fd = open(snddevice->string, O_RDWR); + + seteuid(getuid()); + + if (audio_fd < 0) { + perror(snddevice->string); + Com_Printf("Could not open %s\n", snddevice->string); + return 0; + } + } + +#if 0 + /* Not applicable here */ + rc = ioctl(audio_fd, SNDCTL_DSP_RESET, 0); + if (rc < 0) { + perror(snddevice->string); + Com_Printf("Could not reset %s\n", snddevice->string); + close(audio_fd); + + return 0; + } +#endif + + if (ioctl(audio_fd, SNDCTL_DSP_GETCAPS, &caps) == -1) { + perror(snddevice->string); + Com_Printf("Sound driver too old\n"); + close(audio_fd); + return 0; + } + + if (!(caps & DSP_CAP_TRIGGER) || !(caps & DSP_CAP_MMAP)) { + Com_Printf("Sorry but your soundcard can't do this\n"); + close(audio_fd); + return 0; + } + + +/* SNDCTL_DSP_GETOSPACE moved to be called later */ + +// set sample bits & speed + dma.samplebits = (int)sndbits->value; + if (dma.samplebits != 16 && dma.samplebits != 8) { + ioctl(audio_fd, SNDCTL_DSP_GETFMTS, &fmt); + if (fmt & AFMT_S16_LE) + dma.samplebits = 16; + else if (fmt & AFMT_U8) + dma.samplebits = 8; + } + + dma.speed = (int)sndspeed->value; + if (!dma.speed) { + for (i=0 ; ivalue; + if (dma.channels < 1 || dma.channels > 2) + dma.channels = 2; + +/* mmap() call moved forward */ + + tmp = 0; + if (dma.channels == 2) + tmp = 1; + rc = ioctl(audio_fd, SNDCTL_DSP_STEREO, &tmp); + if (rc < 0) { + perror(snddevice->string); + Com_Printf("Could not set %s to stereo=%d", snddevice->string, dma.channels); + close(audio_fd); + return 0; + } + + if (tmp) + dma.channels = 2; + else + dma.channels = 1; + + rc = ioctl(audio_fd, SNDCTL_DSP_SPEED, &dma.speed); + if (rc < 0) { + perror(snddevice->string); + Com_Printf("Could not set %s speed to %d", snddevice->string, dma.speed); + close(audio_fd); + return 0; + } + + if (dma.samplebits == 16) { + rc = AFMT_S16_LE; + rc = ioctl(audio_fd, SNDCTL_DSP_SETFMT, &rc); + if (rc < 0) { + perror(snddevice->string); + Com_Printf("Could not support 16-bit data. Try 8-bit.\n"); + close(audio_fd); + return 0; + } + } else if (dma.samplebits == 8) { + rc = AFMT_U8; + rc = ioctl(audio_fd, SNDCTL_DSP_SETFMT, &rc); + if (rc < 0) { + perror(snddevice->string); + Com_Printf("Could not support 8-bit data.\n"); + close(audio_fd); + return 0; + } + } else { + perror(snddevice->string); + Com_Printf("%d-bit sound not supported.", dma.samplebits); + close(audio_fd); + return 0; + } + + if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info)==-1) { + perror("GETOSPACE"); + Com_Printf("Um, can't do GETOSPACE?\n"); + close(audio_fd); + return 0; + } + + dma.samples = info.fragstotal * info.fragsize / (dma.samplebits/8); + dma.submission_chunk = 1; + +// memory map the dma buffer + + if (!dma.buffer) + dma.buffer = (unsigned char *) mmap(NULL, info.fragstotal + * info.fragsize, PROT_WRITE, MAP_FILE|MAP_SHARED, audio_fd, 0); + + if (!dma.buffer) { + perror(snddevice->string); + Com_Printf("Could not mmap %s\n", snddevice->string); + close(audio_fd); + return 0; + } + +// toggle the trigger & start her up + + tmp = 0; + rc = ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &tmp); + if (rc < 0) { + perror(snddevice->string); + Com_Printf("Could not toggle.\n"); + close(audio_fd); + return 0; + } + + tmp = PCM_ENABLE_OUTPUT; + rc = ioctl(audio_fd, SNDCTL_DSP_SETTRIGGER, &tmp); + if (rc < 0) { + perror(snddevice->string); + Com_Printf("Could not toggle.\n"); + close(audio_fd); + + return 0; + } + + snd_inited = 1; + return 1; +} + +int SNDDMA_GetDMAPos(void) +{ + struct count_info count; + + if (!snd_inited) return 0; + + if (ioctl(audio_fd, SNDCTL_DSP_GETOPTR, &count) == -1) { + perror(snddevice->string); + Com_Printf("Uh, sound dead.\n"); + close(audio_fd); + snd_inited = 0; + return 0; + } + return count.ptr / (dma.samplebits / 8); +} + +void SNDDMA_Shutdown(void) +{ +} + +/* +============== +SNDDMA_Submit + +Send sound to device if buffer isn't really the dma buffer +=============== +*/ +void SNDDMA_Submit(void) +{ +} + +void SNDDMA_BeginPainting (void) +{ +} diff --git a/code/unix/matha.s b/code/unix/matha.s new file mode 100644 index 0000000..7f6ce3d --- /dev/null +++ b/code/unix/matha.s @@ -0,0 +1,402 @@ +// +// math.s +// x86 assembly-language math routines. + +#define GLQUAKE 1 // don't include unneeded defs +#include "qasm.h" + + +#if id386 + + .data + + .align 4 +Ljmptab: .long Lcase0, Lcase1, Lcase2, Lcase3 + .long Lcase4, Lcase5, Lcase6, Lcase7 + + .text + +// TODO: rounding needed? +// stack parameter offset +#define val 4 + +.globl C(Invert24To16) +C(Invert24To16): + + movl val(%esp),%ecx + movl $0x100,%edx // 0x10000000000 as dividend + cmpl %edx,%ecx + jle LOutOfRange + + subl %eax,%eax + divl %ecx + + ret + +LOutOfRange: + movl $0xFFFFFFFF,%eax + ret + +#if 0 + +#define in 4 +#define out 8 + + .align 2 +.globl C(TransformVector) +C(TransformVector): + movl in(%esp),%eax + movl out(%esp),%edx + + flds (%eax) // in[0] + fmuls C(vright) // in[0]*vright[0] + flds (%eax) // in[0] | in[0]*vright[0] + fmuls C(vup) // in[0]*vup[0] | in[0]*vright[0] + flds (%eax) // in[0] | in[0]*vup[0] | in[0]*vright[0] + fmuls C(vpn) // in[0]*vpn[0] | in[0]*vup[0] | in[0]*vright[0] + + flds 4(%eax) // in[1] | ... + fmuls C(vright)+4 // in[1]*vright[1] | ... + flds 4(%eax) // in[1] | in[1]*vright[1] | ... + fmuls C(vup)+4 // in[1]*vup[1] | in[1]*vright[1] | ... + flds 4(%eax) // in[1] | in[1]*vup[1] | in[1]*vright[1] | ... + fmuls C(vpn)+4 // in[1]*vpn[1] | in[1]*vup[1] | in[1]*vright[1] | ... + fxch %st(2) // in[1]*vright[1] | in[1]*vup[1] | in[1]*vpn[1] | ... + + faddp %st(0),%st(5) // in[1]*vup[1] | in[1]*vpn[1] | ... + faddp %st(0),%st(3) // in[1]*vpn[1] | ... + faddp %st(0),%st(1) // vpn_accum | vup_accum | vright_accum + + flds 8(%eax) // in[2] | ... + fmuls C(vright)+8 // in[2]*vright[2] | ... + flds 8(%eax) // in[2] | in[2]*vright[2] | ... + fmuls C(vup)+8 // in[2]*vup[2] | in[2]*vright[2] | ... + flds 8(%eax) // in[2] | in[2]*vup[2] | in[2]*vright[2] | ... + fmuls C(vpn)+8 // in[2]*vpn[2] | in[2]*vup[2] | in[2]*vright[2] | ... + fxch %st(2) // in[2]*vright[2] | in[2]*vup[2] | in[2]*vpn[2] | ... + + faddp %st(0),%st(5) // in[2]*vup[2] | in[2]*vpn[2] | ... + faddp %st(0),%st(3) // in[2]*vpn[2] | ... + faddp %st(0),%st(1) // vpn_accum | vup_accum | vright_accum + + fstps 8(%edx) // out[2] + fstps 4(%edx) // out[1] + fstps (%edx) // out[0] + + ret + +#endif + +#define EMINS 4+4 +#define EMAXS 4+8 +#define P 4+12 + + .align 2 +.globl C(BoxOnPlaneSide) +C(BoxOnPlaneSide): + pushl %ebx + + movl P(%esp),%edx + movl EMINS(%esp),%ecx + xorl %eax,%eax + movl EMAXS(%esp),%ebx + movb pl_signbits(%edx),%al + cmpb $8,%al + jge Lerror + flds pl_normal(%edx) // p->normal[0] + fld %st(0) // p->normal[0] | p->normal[0] + jmp Ljmptab(,%eax,4) + + +//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]; +Lcase0: + fmuls (%ebx) // p->normal[0]*emaxs[0] | p->normal[0] + flds pl_normal+4(%edx) // p->normal[1] | p->normal[0]*emaxs[0] | + // p->normal[0] + fxch %st(2) // p->normal[0] | p->normal[0]*emaxs[0] | + // p->normal[1] + fmuls (%ecx) // p->normal[0]*emins[0] | + // p->normal[0]*emaxs[0] | p->normal[1] + fxch %st(2) // p->normal[1] | p->normal[0]*emaxs[0] | + // p->normal[0]*emins[0] + fld %st(0) // p->normal[1] | p->normal[1] | + // p->normal[0]*emaxs[0] | + // p->normal[0]*emins[0] + fmuls 4(%ebx) // p->normal[1]*emaxs[1] | p->normal[1] | + // p->normal[0]*emaxs[0] | + // p->normal[0]*emins[0] + flds pl_normal+8(%edx) // p->normal[2] | p->normal[1]*emaxs[1] | + // p->normal[1] | p->normal[0]*emaxs[0] | + // p->normal[0]*emins[0] + fxch %st(2) // p->normal[1] | p->normal[1]*emaxs[1] | + // p->normal[2] | p->normal[0]*emaxs[0] | + // p->normal[0]*emins[0] + fmuls 4(%ecx) // p->normal[1]*emins[1] | + // p->normal[1]*emaxs[1] | + // p->normal[2] | p->normal[0]*emaxs[0] | + // p->normal[0]*emins[0] + fxch %st(2) // p->normal[2] | p->normal[1]*emaxs[1] | + // p->normal[1]*emins[1] | + // p->normal[0]*emaxs[0] | + // p->normal[0]*emins[0] + fld %st(0) // p->normal[2] | p->normal[2] | + // p->normal[1]*emaxs[1] | + // p->normal[1]*emins[1] | + // p->normal[0]*emaxs[0] | + // p->normal[0]*emins[0] + fmuls 8(%ebx) // p->normal[2]*emaxs[2] | + // p->normal[2] | + // p->normal[1]*emaxs[1] | + // p->normal[1]*emins[1] | + // p->normal[0]*emaxs[0] | + // p->normal[0]*emins[0] + fxch %st(5) // p->normal[0]*emins[0] | + // p->normal[2] | + // p->normal[1]*emaxs[1] | + // p->normal[1]*emins[1] | + // p->normal[0]*emaxs[0] | + // p->normal[2]*emaxs[2] + faddp %st(0),%st(3) //p->normal[2] | + // p->normal[1]*emaxs[1] | + // p->normal[1]*emins[1]+p->normal[0]*emins[0]| + // p->normal[0]*emaxs[0] | + // p->normal[2]*emaxs[2] + fmuls 8(%ecx) //p->normal[2]*emins[2] | + // p->normal[1]*emaxs[1] | + // p->normal[1]*emins[1]+p->normal[0]*emins[0]| + // p->normal[0]*emaxs[0] | + // p->normal[2]*emaxs[2] + fxch %st(1) //p->normal[1]*emaxs[1] | + // p->normal[2]*emins[2] | + // p->normal[1]*emins[1]+p->normal[0]*emins[0]| + // p->normal[0]*emaxs[0] | + // p->normal[2]*emaxs[2] + faddp %st(0),%st(3) //p->normal[2]*emins[2] | + // p->normal[1]*emins[1]+p->normal[0]*emins[0]| + // p->normal[0]*emaxs[0]+p->normal[1]*emaxs[1]| + // p->normal[2]*emaxs[2] + fxch %st(3) //p->normal[2]*emaxs[2] + + // p->normal[1]*emins[1]+p->normal[0]*emins[0]| + // p->normal[0]*emaxs[0]+p->normal[1]*emaxs[1]| + // p->normal[2]*emins[2] + faddp %st(0),%st(2) //p->normal[1]*emins[1]+p->normal[0]*emins[0]| + // dist1 | p->normal[2]*emins[2] + + jmp LSetSides + +//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]; +Lcase1: + fmuls (%ecx) // emins[0] + flds pl_normal+4(%edx) + fxch %st(2) + fmuls (%ebx) // emaxs[0] + fxch %st(2) + fld %st(0) + fmuls 4(%ebx) // emaxs[1] + flds pl_normal+8(%edx) + fxch %st(2) + fmuls 4(%ecx) // emins[1] + fxch %st(2) + fld %st(0) + fmuls 8(%ebx) // emaxs[2] + fxch %st(5) + faddp %st(0),%st(3) + fmuls 8(%ecx) // emins[2] + fxch %st(1) + faddp %st(0),%st(3) + fxch %st(3) + faddp %st(0),%st(2) + + jmp LSetSides + +//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]; +Lcase2: + fmuls (%ebx) // emaxs[0] + flds pl_normal+4(%edx) + fxch %st(2) + fmuls (%ecx) // emins[0] + fxch %st(2) + fld %st(0) + fmuls 4(%ecx) // emins[1] + flds pl_normal+8(%edx) + fxch %st(2) + fmuls 4(%ebx) // emaxs[1] + fxch %st(2) + fld %st(0) + fmuls 8(%ebx) // emaxs[2] + fxch %st(5) + faddp %st(0),%st(3) + fmuls 8(%ecx) // emins[2] + fxch %st(1) + faddp %st(0),%st(3) + fxch %st(3) + faddp %st(0),%st(2) + + jmp LSetSides + +//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]; +Lcase3: + fmuls (%ecx) // emins[0] + flds pl_normal+4(%edx) + fxch %st(2) + fmuls (%ebx) // emaxs[0] + fxch %st(2) + fld %st(0) + fmuls 4(%ecx) // emins[1] + flds pl_normal+8(%edx) + fxch %st(2) + fmuls 4(%ebx) // emaxs[1] + fxch %st(2) + fld %st(0) + fmuls 8(%ebx) // emaxs[2] + fxch %st(5) + faddp %st(0),%st(3) + fmuls 8(%ecx) // emins[2] + fxch %st(1) + faddp %st(0),%st(3) + fxch %st(3) + faddp %st(0),%st(2) + + jmp LSetSides + +//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]; +Lcase4: + fmuls (%ebx) // emaxs[0] + flds pl_normal+4(%edx) + fxch %st(2) + fmuls (%ecx) // emins[0] + fxch %st(2) + fld %st(0) + fmuls 4(%ebx) // emaxs[1] + flds pl_normal+8(%edx) + fxch %st(2) + fmuls 4(%ecx) // emins[1] + fxch %st(2) + fld %st(0) + fmuls 8(%ecx) // emins[2] + fxch %st(5) + faddp %st(0),%st(3) + fmuls 8(%ebx) // emaxs[2] + fxch %st(1) + faddp %st(0),%st(3) + fxch %st(3) + faddp %st(0),%st(2) + + jmp LSetSides + +//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]; +Lcase5: + fmuls (%ecx) // emins[0] + flds pl_normal+4(%edx) + fxch %st(2) + fmuls (%ebx) // emaxs[0] + fxch %st(2) + fld %st(0) + fmuls 4(%ebx) // emaxs[1] + flds pl_normal+8(%edx) + fxch %st(2) + fmuls 4(%ecx) // emins[1] + fxch %st(2) + fld %st(0) + fmuls 8(%ecx) // emins[2] + fxch %st(5) + faddp %st(0),%st(3) + fmuls 8(%ebx) // emaxs[2] + fxch %st(1) + faddp %st(0),%st(3) + fxch %st(3) + faddp %st(0),%st(2) + + jmp LSetSides + +//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]; +Lcase6: + fmuls (%ebx) // emaxs[0] + flds pl_normal+4(%edx) + fxch %st(2) + fmuls (%ecx) // emins[0] + fxch %st(2) + fld %st(0) + fmuls 4(%ecx) // emins[1] + flds pl_normal+8(%edx) + fxch %st(2) + fmuls 4(%ebx) // emaxs[1] + fxch %st(2) + fld %st(0) + fmuls 8(%ecx) // emins[2] + fxch %st(5) + faddp %st(0),%st(3) + fmuls 8(%ebx) // emaxs[2] + fxch %st(1) + faddp %st(0),%st(3) + fxch %st(3) + faddp %st(0),%st(2) + + jmp LSetSides + +//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]; +Lcase7: + fmuls (%ecx) // emins[0] + flds pl_normal+4(%edx) + fxch %st(2) + fmuls (%ebx) // emaxs[0] + fxch %st(2) + fld %st(0) + fmuls 4(%ecx) // emins[1] + flds pl_normal+8(%edx) + fxch %st(2) + fmuls 4(%ebx) // emaxs[1] + fxch %st(2) + fld %st(0) + fmuls 8(%ecx) // emins[2] + fxch %st(5) + faddp %st(0),%st(3) + fmuls 8(%ebx) // emaxs[2] + fxch %st(1) + faddp %st(0),%st(3) + fxch %st(3) + faddp %st(0),%st(2) + +LSetSides: + +// sides = 0; +// if (dist1 >= p->dist) +// sides = 1; +// if (dist2 < p->dist) +// sides |= 2; + + faddp %st(0),%st(2) // dist1 | dist2 + fcomps pl_dist(%edx) + xorl %ecx,%ecx + fnstsw %ax + fcomps pl_dist(%edx) + andb $1,%ah + xorb $1,%ah + addb %ah,%cl + + fnstsw %ax + andb $1,%ah + addb %ah,%ah + addb %ah,%cl + +// return sides; + + popl %ebx + movl %ecx,%eax // return status + + ret + + +Lerror: + movl 1, %eax + ret + +#endif // id386 diff --git a/code/unix/q3test.spec.sh b/code/unix/q3test.spec.sh new file mode 100644 index 0000000..7bee048 --- /dev/null +++ b/code/unix/q3test.spec.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# Generate Quake3 test +# $1 is version +# $2 is release +# $3 is arch +# $4 is install dir (assumed to be in /var/tmp) +cat < +URL: http://www.idsoftware.com/ +Source: q3test-%{version}.tar.gz +Group: Games +Copyright: Restricted +Icon: quake3.gif +BuildRoot: /var/tmp/%{name}-%{version} +Summary: Q3Test for Linux + +%description + +%install + +%files + +%attr(644,root,root) $4/README.EULA +%attr(644,root,root) $4/README.Q3Test +%attr(755,root,root) $4/linuxquake3 +%attr(755,root,root) $4/cgamei386.so +%attr(755,root,root) $4/qagamei386.so +%attr(755,root,root) $4/libMesaVoodooGL.so.3.1 +%attr(644,root,root) $4/demoq3/pak0.pk3 + +EOF + diff --git a/code/unix/qasm.h b/code/unix/qasm.h new file mode 100644 index 0000000..6931bd7 --- /dev/null +++ b/code/unix/qasm.h @@ -0,0 +1,459 @@ +#ifndef __ASM_I386__ +#define __ASM_I386__ + +#ifdef ELF +#define C(label) label +#else +#define C(label) _##label +#endif + + +//#define GLQUAKE 1 + +#if defined(_WIN32) && !defined(WINDED) + +#if defined(_M_IX86) +#define __i386__ 1 +#endif + +#endif + +#ifdef __i386__ +#define id386 1 +#else +#define id386 0 +#endif + +// !!! must be kept the same as in d_iface.h !!! +#define TRANSPARENT_COLOR 255 + +#ifndef GLQUAKE + .extern C(d_zistepu) + .extern C(d_pzbuffer) + .extern C(d_zistepv) + .extern C(d_zrowbytes) + .extern C(d_ziorigin) + .extern C(r_turb_s) + .extern C(r_turb_t) + .extern C(r_turb_pdest) + .extern C(r_turb_spancount) + .extern C(r_turb_turb) + .extern C(r_turb_pbase) + .extern C(r_turb_sstep) + .extern C(r_turb_tstep) + .extern C(r_bmodelactive) + .extern C(d_sdivzstepu) + .extern C(d_tdivzstepu) + .extern C(d_sdivzstepv) + .extern C(d_tdivzstepv) + .extern C(d_sdivzorigin) + .extern C(d_tdivzorigin) + .extern C(sadjust) + .extern C(tadjust) + .extern C(bbextents) + .extern C(bbextentt) + .extern C(cacheblock) + .extern C(d_viewbuffer) + .extern C(cachewidth) + .extern C(d_pzbuffer) + .extern C(d_zrowbytes) + .extern C(d_zwidth) + .extern C(d_scantable) + .extern C(r_lightptr) + .extern C(r_numvblocks) + .extern C(prowdestbase) + .extern C(pbasesource) + .extern C(r_lightwidth) + .extern C(lightright) + .extern C(lightrightstep) + .extern C(lightdeltastep) + .extern C(lightdelta) + .extern C(lightright) + .extern C(lightdelta) + .extern C(sourcetstep) + .extern C(surfrowbytes) + .extern C(lightrightstep) + .extern C(lightdeltastep) + .extern C(r_sourcemax) + .extern C(r_stepback) + .extern C(colormap) + .extern C(blocksize) + .extern C(sourcesstep) + .extern C(lightleft) + .extern C(blockdivshift) + .extern C(blockdivmask) + .extern C(lightleftstep) + .extern C(r_origin) + .extern C(r_ppn) + .extern C(r_pup) + .extern C(r_pright) + .extern C(ycenter) + .extern C(xcenter) + .extern C(d_vrectbottom_particle) + .extern C(d_vrectright_particle) + .extern C(d_vrecty) + .extern C(d_vrectx) + .extern C(d_pix_shift) + .extern C(d_pix_min) + .extern C(d_pix_max) + .extern C(d_y_aspect_shift) + .extern C(screenwidth) + .extern C(r_leftclipped) + .extern C(r_leftenter) + .extern C(r_rightclipped) + .extern C(r_rightenter) + .extern C(modelorg) + .extern C(xscale) + .extern C(r_refdef) + .extern C(yscale) + .extern C(r_leftexit) + .extern C(r_rightexit) + .extern C(r_lastvertvalid) + .extern C(cacheoffset) + .extern C(newedges) + .extern C(removeedges) + .extern C(r_pedge) + .extern C(r_framecount) + .extern C(r_u1) + .extern C(r_emitted) + .extern C(edge_p) + .extern C(surface_p) + .extern C(surfaces) + .extern C(r_lzi1) + .extern C(r_v1) + .extern C(r_ceilv1) + .extern C(r_nearzi) + .extern C(r_nearzionly) + .extern C(edge_aftertail) + .extern C(edge_tail) + .extern C(current_iv) + .extern C(edge_head_u_shift20) + .extern C(span_p) + .extern C(edge_head) + .extern C(fv) + .extern C(edge_tail_u_shift20) + .extern C(r_apverts) + .extern C(r_anumverts) + .extern C(aliastransform) + .extern C(r_avertexnormals) + .extern C(r_plightvec) + .extern C(r_ambientlight) + .extern C(r_shadelight) + .extern C(aliasxcenter) + .extern C(aliasycenter) + .extern C(a_sstepxfrac) + .extern C(r_affinetridesc) + .extern C(acolormap) + .extern C(d_pcolormap) + .extern C(r_affinetridesc) + .extern C(d_sfrac) + .extern C(d_ptex) + .extern C(d_pedgespanpackage) + .extern C(d_tfrac) + .extern C(d_light) + .extern C(d_zi) + .extern C(d_pdest) + .extern C(d_pz) + .extern C(d_aspancount) + .extern C(erroradjustup) + .extern C(errorterm) + .extern C(d_xdenom) + .extern C(r_p0) + .extern C(r_p1) + .extern C(r_p2) + .extern C(a_tstepxfrac) + .extern C(r_sstepx) + .extern C(r_tstepx) + .extern C(a_ststepxwhole) + .extern C(zspantable) + .extern C(skintable) + .extern C(r_zistepx) + .extern C(erroradjustdown) + .extern C(d_countextrastep) + .extern C(ubasestep) + .extern C(a_ststepxwhole) + .extern C(a_tstepxfrac) + .extern C(r_lstepx) + .extern C(a_spans) + .extern C(erroradjustdown) + .extern C(d_pdestextrastep) + .extern C(d_pzextrastep) + .extern C(d_sfracextrastep) + .extern C(d_ptexextrastep) + .extern C(d_countextrastep) + .extern C(d_tfracextrastep) + .extern C(d_lightextrastep) + .extern C(d_ziextrastep) + .extern C(d_pdestbasestep) + .extern C(d_pzbasestep) + .extern C(d_sfracbasestep) + .extern C(d_ptexbasestep) + .extern C(ubasestep) + .extern C(d_tfracbasestep) + .extern C(d_lightbasestep) + .extern C(d_zibasestep) + .extern C(zspantable) + .extern C(r_lstepy) + .extern C(r_sstepy) + .extern C(r_tstepy) + .extern C(r_zistepy) + .extern C(D_PolysetSetEdgeTable) + .extern C(D_RasterizeAliasPolySmooth) + + .extern float_point5 + .extern Float2ToThe31nd + .extern izistep + .extern izi + .extern FloatMinus2ToThe31nd + .extern float_1 + .extern float_particle_z_clip + .extern float_minus_1 + .extern float_0 + .extern fp_16 + .extern fp_64k + .extern fp_1m + .extern fp_1m_minus_1 + .extern fp_8 + .extern entryvec_table + .extern advancetable + .extern sstep + .extern tstep + .extern pspantemp + .extern counttemp + .extern jumptemp + .extern reciprocal_table + .extern DP_Count + .extern DP_u + .extern DP_v + .extern DP_32768 + .extern DP_Color + .extern DP_Pix + .extern DP_EntryTable + .extern pbase + .extern s + .extern t + .extern sfracf + .extern tfracf + .extern snext + .extern tnext + .extern spancountminus1 + .extern zi16stepu + .extern sdivz16stepu + .extern tdivz16stepu + .extern zi8stepu + .extern sdivz8stepu + .extern tdivz8stepu + .extern reciprocal_table_16 + .extern entryvec_table_16 + .extern ceil_cw + .extern single_cw + .extern fp_64kx64k + .extern pz + .extern spr8entryvec_table +#endif + + .extern C(snd_scaletable) + .extern C(paintbuffer) + .extern C(snd_linear_count) + .extern C(snd_p) + .extern C(snd_vol) + .extern C(snd_out) + .extern C(vright) + .extern C(vup) + .extern C(vpn) + .extern C(BOPS_Error) + +// +// !!! note that this file must match the corresponding C structures at all +// times !!! +// + +// plane_t structure +// !!! if this is changed, it must be changed in model.h too !!! +// !!! if the size of this is changed, the array lookup in SV_HullPointContents +// must be changed too !!! +#define pl_normal 0 +#define pl_dist 12 +#define pl_type 16 +#define pl_signbits 17 +#define pl_pad 18 +#define pl_size 20 + +// hull_t structure +// !!! if this is changed, it must be changed in model.h too !!! +#define hu_clipnodes 0 +#define hu_planes 4 +#define hu_firstclipnode 8 +#define hu_lastclipnode 12 +#define hu_clip_mins 16 +#define hu_clip_maxs 28 +#define hu_size 40 + +// dnode_t structure +// !!! if this is changed, it must be changed in bspfile.h too !!! +#define nd_planenum 0 +#define nd_children 4 +#define nd_mins 8 +#define nd_maxs 20 +#define nd_firstface 32 +#define nd_numfaces 36 +#define nd_size 40 + +// sfxcache_t structure +// !!! if this is changed, it much be changed in sound.h too !!! +#define sfxc_length 0 +#define sfxc_loopstart 4 +#define sfxc_speed 8 +#define sfxc_width 12 +#define sfxc_stereo 16 +#define sfxc_data 20 + +// channel_t structure +// !!! if this is changed, it much be changed in sound.h too !!! +#define ch_sfx 0 +#define ch_leftvol 4 +#define ch_rightvol 8 +#define ch_end 12 +#define ch_pos 16 +#define ch_looping 20 +#define ch_entnum 24 +#define ch_entchannel 28 +#define ch_origin 32 +#define ch_dist_mult 44 +#define ch_master_vol 48 +#define ch_size 52 + +// portable_samplepair_t structure +// !!! if this is changed, it much be changed in sound.h too !!! +#define psp_left 0 +#define psp_right 4 +#define psp_size 8 + + +// +// !!! note that this file must match the corresponding C structures at all +// times !!! +// + +// !!! if this is changed, it must be changed in r_local.h too !!! +#define NEAR_CLIP 0.01 + +// !!! if this is changed, it must be changed in r_local.h too !!! +#define CYCLE 128 + +// espan_t structure +// !!! if this is changed, it must be changed in r_shared.h too !!! +#define espan_t_u 0 +#define espan_t_v 4 +#define espan_t_count 8 +#define espan_t_pnext 12 +#define espan_t_size 16 + +// sspan_t structure +// !!! if this is changed, it must be changed in d_local.h too !!! +#define sspan_t_u 0 +#define sspan_t_v 4 +#define sspan_t_count 8 +#define sspan_t_size 12 + +// spanpackage_t structure +// !!! if this is changed, it must be changed in d_polyset.c too !!! +#define spanpackage_t_pdest 0 +#define spanpackage_t_pz 4 +#define spanpackage_t_count 8 +#define spanpackage_t_ptex 12 +#define spanpackage_t_sfrac 16 +#define spanpackage_t_tfrac 20 +#define spanpackage_t_light 24 +#define spanpackage_t_zi 28 +#define spanpackage_t_size 32 + +// edge_t structure +// !!! if this is changed, it must be changed in r_shared.h too !!! +#define et_u 0 +#define et_u_step 4 +#define et_prev 8 +#define et_next 12 +#define et_surfs 16 +#define et_nextremove 20 +#define et_nearzi 24 +#define et_owner 28 +#define et_size 32 + +// surf_t structure +// !!! if this is changed, it must be changed in r_shared.h too !!! +#define SURF_T_SHIFT 6 +#define st_next 0 +#define st_prev 4 +#define st_spans 8 +#define st_key 12 +#define st_last_u 16 +#define st_spanstate 20 +#define st_flags 24 +#define st_data 28 +#define st_entity 32 +#define st_nearzi 36 +#define st_insubmodel 40 +#define st_d_ziorigin 44 +#define st_d_zistepu 48 +#define st_d_zistepv 52 +#define st_pad 56 +#define st_size 64 + +// clipplane_t structure +// !!! if this is changed, it must be changed in r_local.h too !!! +#define cp_normal 0 +#define cp_dist 12 +#define cp_next 16 +#define cp_leftedge 20 +#define cp_rightedge 21 +#define cp_reserved 22 +#define cp_size 24 + +// medge_t structure +// !!! if this is changed, it must be changed in model.h too !!! +#define me_v 0 +#define me_cachededgeoffset 4 +#define me_size 8 + +// mvertex_t structure +// !!! if this is changed, it must be changed in model.h too !!! +#define mv_position 0 +#define mv_size 12 + +// refdef_t structure +// !!! if this is changed, it must be changed in render.h too !!! +#define rd_vrect 0 +#define rd_aliasvrect 20 +#define rd_vrectright 40 +#define rd_vrectbottom 44 +#define rd_aliasvrectright 48 +#define rd_aliasvrectbottom 52 +#define rd_vrectrightedge 56 +#define rd_fvrectx 60 +#define rd_fvrecty 64 +#define rd_fvrectx_adj 68 +#define rd_fvrecty_adj 72 +#define rd_vrect_x_adj_shift20 76 +#define rd_vrectright_adj_shift20 80 +#define rd_fvrectright_adj 84 +#define rd_fvrectbottom_adj 88 +#define rd_fvrectright 92 +#define rd_fvrectbottom 96 +#define rd_horizontalFieldOfView 100 +#define rd_xOrigin 104 +#define rd_yOrigin 108 +#define rd_vieworg 112 +#define rd_viewangles 124 +#define rd_ambientlight 136 +#define rd_size 140 + +// mtriangle_t structure +// !!! if this is changed, it must be changed in model.h too !!! +#define mtri_facesfront 0 +#define mtri_vertindex 4 +#define mtri_size 16 // !!! if this changes, array indexing in !!! + // !!! d_polysa.s must be changed to match !!! +#define mtri_shift 4 + +#endif diff --git a/code/unix/quake3.gif b/code/unix/quake3.gif new file mode 100644 index 0000000000000000000000000000000000000000..0e7a01cee0fdaab8ba941cec08f5806957c56f07 GIT binary patch literal 1378 zcmeIx`%fHI6bJBGb{BS`zz&Zxv^-{7i!7m*wOAM+b$5gO|u~lvh)W7HC0Gd>h*u{N5A>x z=A3iSPdB*->b&K2kiN1sEFm7=R{z#1B{&a2()yAPA6fKnfwClY%ZMq+O7vAwxqS1ARW|NBodw zA!-%0GhQwB*(bX6dTU|v~*O7H({W0U+E%u2Rn}L#;{@x<=;<&**39!x_hQ}Y_iXsu%oKIF*rSt zlv}}$_Xn9iM|MZfM&a;QCdu{I@r^>xThnyEy7-$f7rkP#J-c&ccC7ZJ)IHZ{77}ir zAFVigCa3$*Vou((3Hyq&11kpyI$sW%I)oT=t~&Qq)4Ub`;e)b-r+(hKe5>Y^b^6^2 zruMSib8PJi|BWYSN1q$K8eNLJRM#Tsq<_)G9BCcg-?hBdY*`@Brc`WJ&D>b|@FLzt z(71cwpDSOvy@QJx!({&)wauI+miw*Aa%J})YjqHslghxOg-av-H-~Y1 z+HB$bHNSmQkhRrj!xkq@w!9rzAIy7d?5PgNW@9SZV~Q_m_*iZVY@6R+T;?xtpKCR3 zo8$lf^f@|YQaNgn>5qI&Pz zI^bWp*HRQ%oJ!0n?3wO;uV;3)*?ztBbYA|oRwkiX`W(aimLA=0!+$l!CGB-jOxe>m ze!tT|rDcR{<@b(Z`2NN<%k$0n>xak^2W5$?t`GKR1%lR*nl1LHne^%{ZcAoiXp*>C zdO7n)`{Cmr3~#yD>h&~zJG1OLd1vlBBKbvAeEMrJLpj&JE9lM1XvMu*Z+=~Rp(U;J zp*Mf;iJ+ccL@NbK{ok$uvV;*I4v^z-cb)w#4rfBd8zobr!@+0bUy#hZZa+A9=6-X) T6LKh5hK6zz)zMQH3s(6LlNBB6 literal 0 HcmV?d00001 diff --git a/code/unix/snd_mixa.s b/code/unix/snd_mixa.s new file mode 100644 index 0000000..1bd516c --- /dev/null +++ b/code/unix/snd_mixa.s @@ -0,0 +1,197 @@ +// +// snd_mixa.s +// x86 assembly-language sound code +// + +#include "qasm.h" + +#if id386 + + .text + +#if 0 +//---------------------------------------------------------------------- +// 8-bit sound-mixing code +//---------------------------------------------------------------------- + +#define ch 4+16 +#define sc 8+16 +#define count 12+16 + +.globl C(S_PaintChannelFrom8) +C(S_PaintChannelFrom8): + pushl %esi // preserve register variables + pushl %edi + pushl %ebx + pushl %ebp + +// int data; +// short *lscale, *rscale; +// unsigned char *sfx; +// int i; + + movl ch(%esp),%ebx + movl sc(%esp),%esi + +// if (ch->leftvol > 255) +// ch->leftvol = 255; +// if (ch->rightvol > 255) +// ch->rightvol = 255; + movl ch_leftvol(%ebx),%eax + movl ch_rightvol(%ebx),%edx + cmpl $255,%eax + jna LLeftSet + movl $255,%eax +LLeftSet: + cmpl $255,%edx + jna LRightSet + movl $255,%edx +LRightSet: + +// lscale = snd_scaletable[ch->leftvol >> 3]; +// rscale = snd_scaletable[ch->rightvol >> 3]; +// sfx = (signed char *)sc->data + ch->pos; +// ch->pos += count; + andl $0xF8,%eax + addl $20,%esi + movl (%esi),%esi + andl $0xF8,%edx + movl ch_pos(%ebx),%edi + movl count(%esp),%ecx + addl %edi,%esi + shll $7,%eax + addl %ecx,%edi + shll $7,%edx + movl %edi,ch_pos(%ebx) + addl $(C(snd_scaletable)),%eax + addl $(C(snd_scaletable)),%edx + subl %ebx,%ebx + movb -1(%esi,%ecx,1),%bl + + testl $1,%ecx + jz LMix8Loop + + movl (%eax,%ebx,4),%edi + movl (%edx,%ebx,4),%ebp + addl C(paintbuffer)+psp_left-psp_size(,%ecx,psp_size),%edi + addl C(paintbuffer)+psp_right-psp_size(,%ecx,psp_size),%ebp + movl %edi,C(paintbuffer)+psp_left-psp_size(,%ecx,psp_size) + movl %ebp,C(paintbuffer)+psp_right-psp_size(,%ecx,psp_size) + movb -2(%esi,%ecx,1),%bl + + decl %ecx + jz LDone + +// for (i=0 ; i>8; +// if (val > 0x7fff) +// snd_out[i] = 0x7fff; +// else if (val < (short)0x8000) +// snd_out[i] = (short)0x8000; +// else +// snd_out[i] = val; + movl -8(%ebx,%ecx,4),%eax + sarl $8,%eax + cmpl $0x7FFF,%eax + jg LClampHigh + cmpl $0xFFFF8000,%eax + jnl LClampDone + movl $0xFFFF8000,%eax + jmp LClampDone +LClampHigh: + movl $0x7FFF,%eax +LClampDone: + +// val = (snd_p[i+1]*snd_vol)>>8; +// if (val > 0x7fff) +// snd_out[i+1] = 0x7fff; +// else if (val < (short)0x8000) +// snd_out[i+1] = (short)0x8000; +// else +// snd_out[i+1] = val; + movl -4(%ebx,%ecx,4),%edx + sarl $8,%edx + cmpl $0x7FFF,%edx + jg LClampHigh2 + cmpl $0xFFFF8000,%edx + jnl LClampDone2 + movl $0xFFFF8000,%edx + jmp LClampDone2 +LClampHigh2: + movl $0x7FFF,%edx +LClampDone2: + shll $16,%edx + andl $0xFFFF,%eax + orl %eax,%edx + movl %edx,-4(%edi,%ecx,2) + +// } + subl $2,%ecx + jnz LWLBLoopTop + +// snd_p += snd_linear_count; + + popl %ebx + popl %edi + + ret + + +#endif // id386 + diff --git a/code/unix/sys_dosa.s b/code/unix/sys_dosa.s new file mode 100644 index 0000000..276874e --- /dev/null +++ b/code/unix/sys_dosa.s @@ -0,0 +1,94 @@ +// +// sys_dosa.s +// x86 assembly-language DOS-dependent routines. + +#include "qasm.h" + + + .data + + .align 4 +fpenv: + .long 0, 0, 0, 0, 0, 0, 0, 0 + + .text + +.globl C(MaskExceptions) +C(MaskExceptions): + fnstenv fpenv + orl $0x3F,fpenv + fldenv fpenv + + ret + +#if 0 +.globl C(unmaskexceptions) +C(unmaskexceptions): + fnstenv fpenv + andl $0xFFFFFFE0,fpenv + fldenv fpenv + + ret +#endif + + .data + + .align 4 +.globl ceil_cw, single_cw, full_cw, cw, pushed_cw +ceil_cw: .long 0 +single_cw: .long 0 +full_cw: .long 0 +cw: .long 0 +pushed_cw: .long 0 + + .text + +.globl C(Sys_LowFPPrecision) +C(Sys_LowFPPrecision): + fldcw single_cw + + ret + +.globl C(Sys_HighFPPrecision) +C(Sys_HighFPPrecision): + fldcw full_cw + + ret + +.globl C(Sys_PushFPCW_SetHigh) +C(Sys_PushFPCW_SetHigh): + fnstcw pushed_cw + fldcw full_cw + + ret + +.globl C(Sys_PopFPCW) +C(Sys_PopFPCW): + fldcw pushed_cw + + ret + +.globl C(Sys_SetFPCW) +C(Sys_SetFPCW): + fnstcw cw + movl cw,%eax +#if id386 + andb $0xF0,%ah + orb $0x03,%ah // round mode, 64-bit precision +#endif + movl %eax,full_cw + +#if id386 + andb $0xF0,%ah + orb $0x0C,%ah // chop mode, single precision +#endif + movl %eax,single_cw + +#if id386 + andb $0xF0,%ah + orb $0x08,%ah // ceil mode, single precision +#endif + movl %eax,ceil_cw + + ret + diff --git a/code/unix/ui_video.c b/code/unix/ui_video.c new file mode 100644 index 0000000..8ca43da --- /dev/null +++ b/code/unix/ui_video.c @@ -0,0 +1,702 @@ +#include "../client/client.h" +#include "../client/ui_local.h" + +extern void UI_ForceMenuOff( void ); + +static const char *s_driver_names[] = +{ + "[default OpenGL]", + "[Voodoo OpenGL]", + "[Custom ]", + 0 +}; + +static const char *s_drivers[] = +{ + OPENGL_DRIVER_NAME, + _3DFX_DRIVER_NAME, + "", + 0 +}; + +/* +==================================================================== + +MENU INTERACTION + +==================================================================== +*/ +static menuframework_s s_menu; + +static menulist_s s_graphics_options_list; +static menulist_s s_mode_list; +static menulist_s s_driver_list; +static menuslider_s s_tq_slider; +static menulist_s s_fs_box; +static menulist_s s_lighting_box; +static menulist_s s_allow_extensions_box; +static menulist_s s_texturebits_box; +static menulist_s s_colordepth_list; +static menulist_s s_geometry_box; +static menulist_s s_filter_box; +static menuaction_s s_driverinfo_action; +static menuaction_s s_apply_action; +static menuaction_s s_defaults_action; + +typedef struct +{ + int mode; + qboolean fullscreen; + int tq; + int lighting; + int colordepth; + int texturebits; + int geometry; + int filter; + int driver; + qboolean extensions; +} InitialVideoOptions_s; + +static InitialVideoOptions_s s_ivo; + +static InitialVideoOptions_s s_ivo_templates[] = +{ + { + 4, qtrue, 2, 0, 2, 2, 1, 1, 0, qtrue // JDC: this was tq 3 + }, + { + 3, qtrue, 2, 0, 0, 0, 1, 0, 0, qtrue + }, + { + 2, qtrue, 1, 0, 1, 0, 0, 0, 0, qtrue + }, + { + 1, qtrue, 1, 1, 1, 0, 0, 0, 0, qtrue + }, + { + 3, qtrue, 1, 0, 0, 0, 1, 0, 0, qtrue + } +}; + +#define NUM_IVO_TEMPLATES ( sizeof( s_ivo_templates ) / sizeof( s_ivo_templates[0] ) ) + +static void DrvInfo_MenuDraw( void ); +static const char * DrvInfo_MenuKey( int key ); + +static void GetInitialVideoVars( void ) +{ + s_ivo.colordepth = s_colordepth_list.curvalue; + s_ivo.driver = s_driver_list.curvalue; + s_ivo.mode = s_mode_list.curvalue; + s_ivo.fullscreen = s_fs_box.curvalue; + s_ivo.extensions = s_allow_extensions_box.curvalue; + s_ivo.tq = s_tq_slider.curvalue; + s_ivo.lighting = s_lighting_box.curvalue; + s_ivo.geometry = s_geometry_box.curvalue; + s_ivo.filter = s_filter_box.curvalue; + s_ivo.texturebits = s_texturebits_box.curvalue; +} + +static void CheckConfigVsTemplates( void ) +{ + int i; + + for ( i = 0; i < NUM_IVO_TEMPLATES; i++ ) + { + if ( s_driver_list.curvalue != 1 ) + if ( s_ivo_templates[i].colordepth != s_colordepth_list.curvalue ) + continue; +#if 0 + if ( s_ivo_templates[i].driver != s_driver_list.curvalue ) + continue; +#endif + if ( s_ivo_templates[i].mode != s_mode_list.curvalue ) + continue; + if ( s_driver_list.curvalue != 1 ) + if ( s_ivo_templates[i].fullscreen != s_fs_box.curvalue ) + continue; + if ( s_ivo_templates[i].tq != s_tq_slider.curvalue ) + continue; + if ( s_ivo_templates[i].lighting != s_lighting_box.curvalue ) + continue; + if ( s_ivo_templates[i].geometry != s_geometry_box.curvalue ) + continue; + if ( s_ivo_templates[i].filter != s_filter_box.curvalue ) + continue; +// if ( s_ivo_templates[i].texturebits != s_texturebits_box.curvalue ) +// continue; + s_graphics_options_list.curvalue = i; + return; + } + s_graphics_options_list.curvalue = 4; +} + +static void UpdateMenuItemValues( void ) +{ + if ( s_driver_list.curvalue == 1 ) + { + s_fs_box.curvalue = 1; + s_fs_box.generic.flags = QMF_GRAYED; + s_colordepth_list.curvalue = 1; + } + else + { + s_fs_box.generic.flags = 0; + } + + if ( s_fs_box.curvalue == 0 || s_driver_list.curvalue == 1 ) + { + s_colordepth_list.curvalue = 0; + s_colordepth_list.generic.flags = QMF_GRAYED; + } + else + { + s_colordepth_list.generic.flags = 0; + } + + if ( s_allow_extensions_box.curvalue == 0 ) + { + if ( s_texturebits_box.curvalue == 0 ) + { + s_texturebits_box.curvalue = 1; + } + } + + s_apply_action.generic.flags = QMF_GRAYED; + + if ( s_ivo.mode != s_mode_list.curvalue ) + { + s_apply_action.generic.flags = QMF_BLINK; + } + if ( s_ivo.fullscreen != s_fs_box.curvalue ) + { + s_apply_action.generic.flags = QMF_BLINK; + } + if ( s_ivo.extensions != s_allow_extensions_box.curvalue ) + { + s_apply_action.generic.flags = QMF_BLINK; + } + if ( s_ivo.tq != s_tq_slider.curvalue ) + { + s_apply_action.generic.flags = QMF_BLINK; + } + if ( s_ivo.lighting != s_lighting_box.curvalue ) + { + s_apply_action.generic.flags = QMF_BLINK; + } + if ( s_ivo.colordepth != s_colordepth_list.curvalue ) + { + s_apply_action.generic.flags = QMF_BLINK; + } + if ( s_ivo.driver != s_driver_list.curvalue ) + { + s_apply_action.generic.flags = QMF_BLINK; + } + if ( s_ivo.texturebits != s_texturebits_box.curvalue ) + { + s_apply_action.generic.flags = QMF_BLINK; + } + if ( s_ivo.geometry != s_geometry_box.curvalue ) + { + s_apply_action.generic.flags = QMF_BLINK; + } + if ( s_ivo.filter != s_filter_box.curvalue ) + { + s_apply_action.generic.flags = QMF_BLINK; + } + + CheckConfigVsTemplates(); +} + +static void SetMenuItemValues( void ) +{ + s_mode_list.curvalue = Cvar_VariableValue( "r_mode" ); + s_fs_box.curvalue = Cvar_VariableValue("r_fullscreen"); + s_allow_extensions_box.curvalue = Cvar_VariableValue("r_allowExtensions"); + s_tq_slider.curvalue = 3-Cvar_VariableValue( "r_picmip"); + if ( s_tq_slider.curvalue < 0 ) + { + s_tq_slider.curvalue = 0; + } + else if ( s_tq_slider.curvalue > 3 ) + { + s_tq_slider.curvalue = 3; + } + + s_lighting_box.curvalue = Cvar_VariableValue( "r_vertexLight" ) != 0; + switch ( ( int ) Cvar_VariableValue( "r_texturebits" ) ) + { + case 0: + default: + s_texturebits_box.curvalue = 0; + break; + case 16: + s_texturebits_box.curvalue = 1; + break; + case 32: + s_texturebits_box.curvalue = 2; + break; + } + + if ( !Q_stricmp( Cvar_VariableString( "r_textureMode" ), "GL_LINEAR_MIPMAP_NEAREST" ) ) + { + s_filter_box.curvalue = 0; + } + else + { + s_filter_box.curvalue = 1; + } + + if ( Cvar_VariableValue( "r_subdivisions" ) == 999 || + Cvar_VariableValue( "r_lodBias" ) > 0 ) + { + s_geometry_box.curvalue = 0; + } + else + { + s_geometry_box.curvalue = 1; + } + + switch ( ( int ) Cvar_VariableValue( "r_colorbits" ) ) + { + default: + case 0: + s_colordepth_list.curvalue = 0; + break; + case 16: + s_colordepth_list.curvalue = 1; + break; + case 32: + s_colordepth_list.curvalue = 2; + break; + } + + if ( s_fs_box.curvalue == 0 ) + { + s_colordepth_list.curvalue = 0; + } + if ( s_driver_list.curvalue == 1 ) + { + s_colordepth_list.curvalue = 1; + } +} + +static void FullscreenCallback( void *s ) +{ +} + +static void ModeCallback( void *s ) +{ + // clamp 3dfx video modes + if ( s_driver_list.curvalue == 1 ) + { + if ( s_mode_list.curvalue < 2 ) + { + s_mode_list.curvalue = 2; + } + else if ( s_mode_list.curvalue > 6 ) + { + s_mode_list.curvalue = 6; + } + } +} + +static void GraphicsOptionsCallback( void *s ) +{ + InitialVideoOptions_s *ivo = &s_ivo_templates[s_graphics_options_list.curvalue]; + + s_mode_list.curvalue = ivo->mode; + s_tq_slider.curvalue = ivo->tq; + s_lighting_box.curvalue = ivo->lighting; + s_colordepth_list.curvalue = ivo->colordepth; + s_texturebits_box.curvalue = ivo->texturebits; + s_geometry_box.curvalue = ivo->geometry; + s_filter_box.curvalue = ivo->filter; + s_fs_box.curvalue = ivo->fullscreen; +} + +static void TextureDetailCallback( void *s ) +{ +} + +static void TextureQualityCallback( void *s ) +{ +} + +static void ExtensionsCallback( void *s ) +{ +} + +static void ColorDepthCallback( void *s ) +{ +} + +static void DriverInfoCallback( void *s ) +{ + UI_PushMenu( DrvInfo_MenuDraw, DrvInfo_MenuKey ); +} + +static void LightingCallback( void * s ) +{ +} + +static void ApplyChanges( void *unused ) +{ + switch ( s_texturebits_box.curvalue ) + { + case 0: + Cvar_SetValue( "r_texturebits", 0 ); + Cvar_SetValue( "r_ext_compress_textures", 1 ); + break; + case 1: + Cvar_SetValue( "r_texturebits", 16 ); + Cvar_SetValue( "r_ext_compress_textures", 0 ); + break; + case 2: + Cvar_SetValue( "r_texturebits", 32 ); + Cvar_SetValue( "r_ext_compress_textures", 0 ); + break; + } + Cvar_SetValue( "r_picmip", 3 - s_tq_slider.curvalue ); + Cvar_SetValue( "r_allowExtensions", s_allow_extensions_box.curvalue ); + Cvar_SetValue( "r_mode", s_mode_list.curvalue ); + Cvar_SetValue( "r_fullscreen", s_fs_box.curvalue ); + if (*s_drivers[s_driver_list.curvalue] ) + Cvar_Set( "r_glDriver", ( char * ) s_drivers[s_driver_list.curvalue] ); + switch ( s_colordepth_list.curvalue ) + { + case 0: + Cvar_SetValue( "r_colorbits", 0 ); + Cvar_SetValue( "r_depthbits", 0 ); + Cvar_SetValue( "r_stencilbits", 0 ); + break; + case 1: + Cvar_SetValue( "r_colorbits", 16 ); + Cvar_SetValue( "r_depthbits", 16 ); + Cvar_SetValue( "r_stencilbits", 0 ); + break; + case 2: + Cvar_SetValue( "r_colorbits", 32 ); + Cvar_SetValue( "r_depthbits", 24 ); + break; + } + Cvar_SetValue( "r_vertexLight", s_lighting_box.curvalue ); + + if ( s_geometry_box.curvalue ) + { + Cvar_SetValue( "r_lodBias", 0 ); + Cvar_SetValue( "r_subdivisions", 4 ); + } + else + { + Cvar_SetValue( "r_lodBias", 1 ); + Cvar_SetValue( "r_subdivisions", 999 ); + } + + if ( s_filter_box.curvalue ) + { + Cvar_Set( "r_textureMode", "GL_LINEAR_MIPMAP_LINEAR" ); + } + else + { + Cvar_Set( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST" ); + } + + UI_ForceMenuOff(); + + CL_Vid_Restart_f(); + + VID_MenuInit(); + +// s_fs_box.curvalue = Cvar_VariableValue( "r_fullscreen" ); +} + +/* +** VID_MenuInit +*/ +void VID_MenuInit( void ) +{ + static const char *tq_names[] = + { + "compressed", + "16-bit", + "32-bit", + 0 + }; + + static const char *s_graphics_options_names[] = + { + "high quality", + "normal", + "fast", + "fastest", + "custom", + 0 + }; + + static const char *lighting_names[] = + { + "lightmap", + "vertex", + 0 + }; + + static const char *colordepth_names[] = + { + "default", + "16-bit", + "32-bit", + 0 + }; + + static const char *resolutions[] = + { + "[320 240 ]", + "[400 300 ]", + "[512 384 ]", + "[640 480 ]", + "[800 600 ]", + "[960 720 ]", + "[1024 768 ]", + "[1152 864 ]", + "[1280 960 ]", + "[1600 1200]", + "[2048 1536]", + "[856 480 W]", + 0 + }; + static const char *filter_names[] = + { + "bilinear", + "trilinear", + 0 + }; + static const char *quality_names[] = + { + "low", + "high", + 0 + }; + static const char *enabled_names[] = + { + "disabled", + "enabled", + 0 + }; + int y = 0; + int i; + char *p; + + s_menu.x = SCREEN_WIDTH * 0.50; + s_menu.nitems = 0; + s_menu.wrapAround = qtrue; + + s_graphics_options_list.generic.type = MTYPE_SPINCONTROL; + s_graphics_options_list.generic.name = "graphics mode"; + s_graphics_options_list.generic.x = 0; + s_graphics_options_list.generic.y = y; + s_graphics_options_list.generic.callback = GraphicsOptionsCallback; + s_graphics_options_list.itemnames = s_graphics_options_names; + + s_driver_list.generic.type = MTYPE_SPINCONTROL; + s_driver_list.generic.name = "driver"; + s_driver_list.generic.x = 0; + s_driver_list.generic.y = y += 18; + + p = Cvar_VariableString( "r_glDriver" ); + for (i = 0; s_drivers[i]; i++) { + if (strcmp(s_drivers[i], p) == 0) + break; + } + if (!s_drivers[i]) + i--; // go back one, to default 'custom' + s_driver_list.curvalue = i; + + s_driver_list.itemnames = s_driver_names; + + // references/modifies "r_allowExtensions" + s_allow_extensions_box.generic.type = MTYPE_SPINCONTROL; + s_allow_extensions_box.generic.x = 0; + s_allow_extensions_box.generic.y = y += 18; + s_allow_extensions_box.generic.name = "OpenGL extensions"; + s_allow_extensions_box.generic.callback = ExtensionsCallback; + s_allow_extensions_box.itemnames = enabled_names; + + // references/modifies "r_mode" + s_mode_list.generic.type = MTYPE_SPINCONTROL; + s_mode_list.generic.name = "video mode"; + s_mode_list.generic.x = 0; + s_mode_list.generic.y = y += 36; + s_mode_list.itemnames = resolutions; + s_mode_list.generic.callback = ModeCallback; + + // references "r_colorbits" + s_colordepth_list.generic.type = MTYPE_SPINCONTROL; + s_colordepth_list.generic.name = "color depth"; + s_colordepth_list.generic.x = 0; + s_colordepth_list.generic.y = y += 18; + s_colordepth_list.itemnames = colordepth_names; + s_colordepth_list.generic.callback = ColorDepthCallback; + + // references/modifies "r_fullscreen" + s_fs_box.generic.type = MTYPE_RADIOBUTTON; + s_fs_box.generic.x = 0; + s_fs_box.generic.y = y += 18; + s_fs_box.generic.name = "fullscreen"; + s_fs_box.generic.callback = FullscreenCallback; + + // references/modifies "r_vertexLight" + s_lighting_box.generic.type = MTYPE_SPINCONTROL; + s_lighting_box.generic.x = 0; + s_lighting_box.generic.y = y += 18; + s_lighting_box.generic.name = "lighting"; + s_lighting_box.itemnames = lighting_names; + s_lighting_box.generic.callback = LightingCallback; + + // references/modifies "r_lodBias" & "subdivisions" + s_geometry_box.generic.type = MTYPE_SPINCONTROL; + s_geometry_box.generic.x = 0; + s_geometry_box.generic.y = y += 18; + s_geometry_box.generic.name = "geometric detail"; + s_geometry_box.itemnames = quality_names; + + // references/modifies "r_picmip" + s_tq_slider.generic.type = MTYPE_SLIDER; + s_tq_slider.generic.x = 0; + s_tq_slider.generic.y = y += 18; + s_tq_slider.generic.name = "texture detail"; + s_tq_slider.generic.callback = TextureDetailCallback; + s_tq_slider.minvalue = 0; + s_tq_slider.maxvalue = 3; + + // references/modifies "r_textureBits" + s_texturebits_box.generic.type = MTYPE_SPINCONTROL; + s_texturebits_box.generic.x = 0; + s_texturebits_box.generic.y = y += 18; + s_texturebits_box.generic.name = "texture quality"; + s_texturebits_box.generic.callback = TextureQualityCallback; + s_texturebits_box.itemnames = tq_names; + + // references/modifies "r_textureMode" + s_filter_box.generic.type = MTYPE_SPINCONTROL; + s_filter_box.generic.x = 0; + s_filter_box.generic.y = y += 18; + s_filter_box.generic.name = "texture filter"; + s_filter_box.itemnames = filter_names; + + s_driverinfo_action.generic.type = MTYPE_ACTION; + s_driverinfo_action.generic.name = "driver information"; + s_driverinfo_action.generic.x = 0; + s_driverinfo_action.generic.y = y += 36; + s_driverinfo_action.generic.callback = DriverInfoCallback; + + s_apply_action.generic.type = MTYPE_ACTION; + s_apply_action.generic.name = "apply"; + s_apply_action.generic.x = 0; + s_apply_action.generic.y = y += 36; + s_apply_action.generic.callback = ApplyChanges; + s_apply_action.generic.flags = QMF_GRAYED; + + SetMenuItemValues(); + GetInitialVideoVars(); + + Menu_AddItem( &s_menu, ( void * ) &s_graphics_options_list ); + Menu_AddItem( &s_menu, ( void * ) &s_driver_list ); + Menu_AddItem( &s_menu, ( void * ) &s_allow_extensions_box ); + Menu_AddItem( &s_menu, ( void * ) &s_mode_list ); + Menu_AddItem( &s_menu, ( void * ) &s_colordepth_list ); + Menu_AddItem( &s_menu, ( void * ) &s_fs_box ); + Menu_AddItem( &s_menu, ( void * ) &s_lighting_box ); + Menu_AddItem( &s_menu, ( void * ) &s_geometry_box ); + Menu_AddItem( &s_menu, ( void * ) &s_tq_slider ); + Menu_AddItem( &s_menu, ( void * ) &s_texturebits_box ); + Menu_AddItem( &s_menu, ( void * ) &s_filter_box ); + + Menu_AddItem( &s_menu, ( void * ) &s_driverinfo_action ); + Menu_AddItem( &s_menu, ( void * ) &s_apply_action ); + + Menu_Center( &s_menu ); + s_menu.y -= 6; +} + +/* +================ +VID_MenuDraw +================ +*/ +void VID_MenuDraw (void) +{ + UpdateMenuItemValues(); + Menu_AdjustCursor( &s_menu, 1 ); + Menu_Draw( &s_menu ); +} + +/* +================ +VID_MenuKey +================ +*/ +const char *VID_MenuKey( int key ) +{ + menuframework_s *m = &s_menu; + static const char *sound = "sound/misc/menu1.wav"; + + if ( key == K_ENTER ) + { + if ( !Menu_SelectItem( m ) ) + ApplyChanges( NULL ); + return NULL; + } + return Default_MenuKey( m, key ); + +} + +static void DrvInfo_MenuDraw( void ) +{ + float labelColor[] = { 0, 1.0, 0, 1.0 }; + float textColor[] = { 1, 1, 1, 1 }; + int i = 14; + char extensionsString[1024], *eptr = extensionsString; + + SCR_DrawBigStringColor( BIGCHAR_WIDTH * 4, BIGCHAR_HEIGHT * 3, "VENDOR:", labelColor ); + SCR_DrawBigStringColor( BIGCHAR_WIDTH * 4, BIGCHAR_HEIGHT * 4, Cvar_VariableString( "gl_vendor" ), textColor ); + SCR_DrawBigStringColor( BIGCHAR_WIDTH * 4, BIGCHAR_HEIGHT * 5.5, "VERSION:", labelColor ); + SCR_DrawBigStringColor( BIGCHAR_WIDTH * 4, BIGCHAR_HEIGHT * 6.5, Cvar_VariableString( "gl_version" ), textColor ); + SCR_DrawBigStringColor( BIGCHAR_WIDTH * 4, BIGCHAR_HEIGHT * 8, "RENDERER:", labelColor ); + SCR_DrawBigStringColor( BIGCHAR_WIDTH * 4, BIGCHAR_HEIGHT * 9, Cvar_VariableString( "gl_renderer" ), textColor ); + SCR_DrawBigStringColor( BIGCHAR_WIDTH * 4, BIGCHAR_HEIGHT * 10.5, "PIXELFORMAT:", labelColor ); + SCR_DrawBigStringColor( BIGCHAR_WIDTH * 4, BIGCHAR_HEIGHT * 11.5, Cvar_VariableString( "gl_pixelformat" ), textColor ); + + SCR_DrawBigStringColor( BIGCHAR_WIDTH * 4, BIGCHAR_HEIGHT * 13, "EXTENSIONS:", labelColor ); + strcpy( extensionsString, Cvar_VariableString( "gl_extensions" ) ); + while ( i < 25 && *eptr ) + { + while ( *eptr ) + { + char buf[2] = " "; + int j = BIGCHAR_WIDTH * 6; + + while ( *eptr && *eptr != ' ' ) + { + buf[0] = *eptr; + SCR_DrawBigStringColor( j, i * BIGCHAR_HEIGHT, buf, textColor ); + j += BIGCHAR_WIDTH; + eptr++; + } + + i++; + + while ( *eptr && *eptr == ' ' ) + eptr++; + } + } +} + +static const char * DrvInfo_MenuKey( int key ) +{ + if ( key == K_ESCAPE ) + UI_PopMenu(); + return NULL; +} + + diff --git a/code/unix/unix_glw.h b/code/unix/unix_glw.h new file mode 100644 index 0000000..dcfd36f --- /dev/null +++ b/code/unix/unix_glw.h @@ -0,0 +1,17 @@ +#ifndef __linux__ +#error You shouldnt be including this file on non-Linux platforms +#endif + +#ifndef __GLW_LINUX_H__ +#define __GLW_LINUX_H__ + +typedef struct +{ + void *OpenGLLib; // instance of OpenGL library + + FILE *log_fp; +} glwstate_t; + +extern glwstate_t glw_state; + +#endif diff --git a/code/unix/unix_main.c b/code/unix/unix_main.c new file mode 100644 index 0000000..c489a0b --- /dev/null +++ b/code/unix/unix_main.c @@ -0,0 +1,809 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" + +cvar_t *nostdout; + +// Structure containing functions exported from refresh DLL +refexport_t re; + +unsigned sys_frame_time; + +uid_t saved_euid; +qboolean stdin_active = qtrue; + +// ======================================================================= +// General routines +// ======================================================================= + +void Sys_BeginProfiling( void ) { +} + +/* +================= +Sys_In_Restart_f + +Restart the input subsystem +================= +*/ +void Sys_In_Restart_f( void ) +{ + IN_Shutdown(); + IN_Init(); +} + +void Sys_ConsoleOutput (char *string) +{ + if (nostdout && nostdout->value) + return; + + fputs(string, stdout); +} + +void Sys_Printf (char *fmt, ...) +{ + va_list argptr; + char text[1024]; + unsigned char *p; + + va_start (argptr,fmt); + vsprintf (text,fmt,argptr); + va_end (argptr); + + if (strlen(text) > sizeof(text)) + Sys_Error("memory overwrite in Sys_Printf"); + + if (nostdout && nostdout->value) + return; + + for (p = (unsigned char *)text; *p; p++) { + *p &= 0x7f; + if ((*p > 128 || *p < 32) && *p != 10 && *p != 13 && *p != 9) + printf("[%02x]", *p); + else + putc(*p, stdout); + } +} + +void Sys_Quit (void) +{ + CL_Shutdown (); + fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); + _exit(0); +} + +void Sys_Init(void) +{ + Cmd_AddCommand ("in_restart", Sys_In_Restart_f); + +#if id386 + Sys_SetFPCW(); +#endif + +#if defined __linux__ +#if defined __i386__ + Cvar_Set( "arch", "linux i386" ); +#elif defined __alpha__ + Cvar_Set( "arch", "linux alpha" ); +#elif defined __sparc__ + Cvar_Set( "arch", "linux sparc" ); +#else + Cvar_Set( "arch", "linux unknown" ); +#endif +#elif defined __sun__ +#if defined __i386__ + Cvar_Set( "arch", "solaris x86" ); +#elif defined __sparc__ + Cvar_Set( "arch", "solaris sparc" ); +#else + Cvar_Set( "arch", "solaris unknown" ); +#endif +#elif defined __sgi__ +#if defined __mips__ + Cvar_Set( "arch", "sgi mips" ); +#else + Cvar_Set( "arch", "sgi unknown" ); +#endif +#else + Cvar_Set( "arch", "unknown" ); +#endif + + IN_Init(); + +} + +void Sys_Error( const char *error, ...) +{ + va_list argptr; + char string[1024]; + +// change stdin to non blocking + fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); + + CL_Shutdown (); + + va_start (argptr,error); + vsprintf (string,error,argptr); + va_end (argptr); + fprintf(stderr, "Error: %s\n", string); + + _exit (1); + +} + +void Sys_Warn (char *warning, ...) +{ + va_list argptr; + char string[1024]; + + va_start (argptr,warning); + vsprintf (string,warning,argptr); + va_end (argptr); + fprintf(stderr, "Warning: %s", string); +} + +/* +============ +Sys_FileTime + +returns -1 if not present +============ +*/ +int Sys_FileTime (char *path) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return buf.st_mtime; +} + +void floating_point_exception_handler(int whatever) +{ +// Sys_Warn("floating point exception\n"); + signal(SIGFPE, floating_point_exception_handler); +} + +char *Sys_ConsoleInput(void) +{ + static char text[256]; + int len; + fd_set fdset; + struct timeval timeout; + + if (!com_dedicated || !com_dedicated->value) + return NULL; + + if (!stdin_active) + return NULL; + + FD_ZERO(&fdset); + FD_SET(0, &fdset); // stdin + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select (1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET(0, &fdset)) + return NULL; + + len = read (0, text, sizeof(text)); + if (len == 0) { // eof! + stdin_active = qfalse; + return NULL; + } + + if (len < 1) + return NULL; + text[len-1] = 0; // rip off the /n and terminate + + return text; +} + +/*****************************************************************************/ + +static void *game_library; + +#ifdef __i386__ + const char *gamename = "qagamei386.so"; +#elif defined __alpha__ + const char *gamename = "qagameaxp.so"; +#elif defined __mips__ + const char *gamename = "qagamemips.so"; +#else +#error Unknown arch +#endif + +/* +================= +Sys_UnloadGame +================= +*/ +void Sys_UnloadGame (void) +{ + Com_Printf("------ Unloading %s ------\n", gamename); + if (game_library) + dlclose (game_library); + game_library = NULL; +} + +/* +================= +Sys_GetGameAPI + +Loads the game dll +================= +*/ +void *Sys_GetGameAPI (void *parms) +{ + void *(*GetGameAPI) (void *); + + char name[MAX_OSPATH]; + char curpath[MAX_OSPATH]; + char *path; + if (game_library) + Com_Error (ERR_FATAL, "Sys_GetGameAPI without Sys_UnloadingGame"); + + // check the current debug directory first for development purposes + getcwd(curpath, sizeof(curpath)); + + Com_Printf("------- Loading %s -------\n", gamename); + Com_sprintf (name, sizeof(name), "%s/%s", curpath, gamename); + + game_library = dlopen (name, RTLD_LAZY ); + if (game_library) + Com_DPrintf ("LoadLibrary (%s)\n",name); + else { + Com_Printf( "LoadLibrary(\"%s\") failed\n", name); + Com_Printf( "...reason: '%s'\n", dlerror() ); + Com_Error( ERR_FATAL, "Couldn't load game" ); + } + + GetGameAPI = (void *)dlsym (game_library, "GetGameAPI"); + if (!GetGameAPI) + { + Sys_UnloadGame (); + return NULL; + } + + return GetGameAPI (parms); +} + +/*****************************************************************************/ + +static void *cgame_library; + +/* +================= +Sys_UnloadGame +================= +*/ +void Sys_UnloadCGame (void) +{ + if (cgame_library) + dlclose (cgame_library); + cgame_library = NULL; +} + +/* +================= +Sys_GetGameAPI + +Loads the game dll +================= +*/ +void *Sys_GetCGameAPI (void) +{ + void *(*api) (void); + + char name[MAX_OSPATH]; + char curpath[MAX_OSPATH]; +#ifdef __i386__ + const char *cgamename = "cgamei386.so"; +#elif defined __alpha__ + const char *cgamename = "cgameaxp.so"; +#elif defined __mips__ + const char *cgamename = "cgamemips.so"; +#else +#error Unknown arch +#endif + + Sys_UnloadCGame(); + + getcwd(curpath, sizeof(curpath)); + + Com_Printf("------- Loading %s -------\n", cgamename); + + sprintf (name, "%s/%s", curpath, cgamename); + cgame_library = dlopen (name, RTLD_LAZY ); + if (!cgame_library) + { + Com_Printf ("LoadLibrary (%s)\n",name); + Com_Error( ERR_FATAL, "Couldn't load cgame: %s", dlerror() ); + } + + api = (void *)dlsym (cgame_library, "GetCGameAPI"); + if (!api) + { + Com_Error( ERR_FATAL, "dlsym() failed on GetCGameAPI" ); + } + + return api(); +} + +/*****************************************************************************/ + +static void *ui_library; + +/* +================= +Sys_UnloadUI +================= +*/ +void Sys_UnloadUI(void) +{ + if (ui_library) + dlclose (ui_library); + ui_library = NULL; +} + +/* +================= +Sys_GetUIAPI + +Loads the ui dll +================= +*/ +void *Sys_GetUIAPI (void) +{ + void *api; + + char name[MAX_OSPATH]; + char curpath[MAX_OSPATH]; +#ifdef __i386__ + const char *uiname = "uii386.so"; +#elif defined __alpha__ + const char *uiname = "uiaxp.so"; +#elif defined __mips__ + const char *uiname = "uimips.so"; +#else +#error Unknown arch +#endif + + Sys_UnloadUI(); + + getcwd(curpath, sizeof(curpath)); + + Com_Printf("------- Loading %s -------\n", uiname); + + sprintf (name, "%s/%s", curpath, uiname); + ui_library = dlopen (name, RTLD_LAZY ); + if (!ui_library) + { + Com_Printf ("LoadLibrary (%s)\n",name); + Com_Error( ERR_FATAL, "Couldn't load ui: %s", dlerror() ); + } + + api = (void *)dlsym (ui_library, "GetUIAPI"); + if (!api) + { + Com_Error( ERR_FATAL, "dlsym() failed on GetUIAPI" ); + } + + return api; +} + +/*****************************************************************************/ + +void *Sys_GetRefAPI (void *parms) +{ + return (void *)GetRefAPI(REF_API_VERSION, parms); +} + +/* +======================================================================== + +BACKGROUND FILE STREAMING + +======================================================================== +*/ + +typedef struct { +#if 0 + HANDLE threadHandle; + int threadId; + CRITICAL_SECTION crit; +#endif + FILE *file; + byte *buffer; + qboolean eof; + int bufferSize; + int streamPosition; // next byte to be returned by Sys_StreamRead + int threadPosition; // next byte to be read from file +} streamState_t; + +streamState_t stream; + +/* +=============== +Sys_StreamThread + +A thread will be sitting in this loop forever +================ +*/ +void Sys_StreamThread( void ) +{ + int buffer; + int count; + int readCount; + int bufferPoint; + int r; + +//Loop here +// EnterCriticalSection (&stream.crit); + + // if there is any space left in the buffer, fill it up + if ( !stream.eof ) { + count = stream.bufferSize - (stream.threadPosition - stream.streamPosition); + if ( count ) { + bufferPoint = stream.threadPosition % stream.bufferSize; + buffer = stream.bufferSize - bufferPoint; + readCount = buffer < count ? buffer : count; + r = fread( stream.buffer + bufferPoint, 1, readCount, stream.file ); + stream.threadPosition += r; + + if ( r != readCount ) + stream.eof = qtrue; + } + } + +// LeaveCriticalSection (&stream.crit); +} + +/* +=============== +Sys_InitStreamThread + +================ +*/ +void Sys_InitStreamThread( void ) +{ +} + +/* +=============== +Sys_ShutdownStreamThread + +================ +*/ +void Sys_ShutdownStreamThread( void ) +{ +} + + +/* +=============== +Sys_BeginStreamedFile + +================ +*/ +void Sys_BeginStreamedFile( fileHandle_t f, int readAhead ) +{ + if ( stream.file ) { + Com_Error( ERR_FATAL, "Sys_BeginStreamedFile: unclosed stream"); + } + + stream.file = f; + stream.buffer = Z_Malloc( readAhead ); + stream.bufferSize = readAhead; + stream.streamPosition = 0; + stream.threadPosition = 0; + stream.eof = qfalse; + + // let the thread start running +// LeaveCriticalSection( &stream.crit ); +} + +/* +=============== +Sys_EndStreamedFile + +================ +*/ +void Sys_EndStreamedFile( FILE *f ) +{ + if ( f != stream.file ) { + Com_Error( ERR_FATAL, "Sys_EndStreamedFile: wrong file"); + } + // don't leave critical section until another stream is started +// EnterCriticalSection( &stream.crit ); + + stream.file = NULL; + Z_Free( stream.buffer ); +} + + +/* +=============== +Sys_StreamedRead + +================ +*/ +int Sys_StreamedRead( void *buffer, int size, int count, FILE *f ) +{ + int available; + int remaining; + int sleepCount; + int copy; + int bufferCount; + int bufferPoint; + byte *dest; + + dest = (byte *)buffer; + remaining = size * count; + + if ( remaining <= 0 ) { + Com_Error( ERR_FATAL, "Streamed read with non-positive size" ); + } + + sleepCount = 0; + while ( remaining > 0 ) { + available = stream.threadPosition - stream.streamPosition; + if ( !available ) { + if (stream.eof) + break; + Sys_StreamThread(); + continue; + } + + bufferPoint = stream.streamPosition % stream.bufferSize; + bufferCount = stream.bufferSize - bufferPoint; + + copy = available < bufferCount ? available : bufferCount; + if ( copy > remaining ) { + copy = remaining; + } + memcpy( dest, stream.buffer + bufferPoint, copy ); + stream.streamPosition += copy; + dest += copy; + remaining -= copy; + } + + return (count * size - remaining) / size; +} + +/* +=============== +Sys_StreamSeek + +================ +*/ +void Sys_StreamSeek( FILE *f, int offset, int origin ) { + + // halt the thread +// EnterCriticalSection( &stream.crit ); + + // clear to that point + fseek( f, offset, origin ); + stream.streamPosition = 0; + stream.threadPosition = 0; + stream.eof = qfalse; + + // let the thread start running at the new position +// LeaveCriticalSection( &stream.crit ); +} + + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +#define MAX_QUED_EVENTS 64 +#define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 ) + +sysEvent_t eventQue[MAX_QUED_EVENTS]; +int eventHead, eventTail; +byte sys_packetReceived[MAX_MSGLEN]; + +/* +================ +Sys_QueEvent + +A time of 0 will get the current time +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) { + sysEvent_t *ev; + + ev = &eventQue[ eventHead & MASK_QUED_EVENTS ]; + eventHead++; + + if ( time == 0 ) { + time = Sys_Milliseconds(); + } + + ev->evTime = time; + ev->evType = type; + ev->evValue = value; + ev->evValue2 = value2; + ev->evPtrLength = ptrLength; + ev->evPtr = ptr; +} + +/* +================ +Sys_GetEvent + +================ +*/ +sysEvent_t Sys_GetEvent( void ) { + sysEvent_t ev; + char *s; + msg_t netmsg; + netadr_t adr; + + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // pump the message loop + // in vga this calls KBD_Update, under X, it calls GetEvent + Sys_SendKeyEvents (); + + // check for console commands + s = Sys_ConsoleInput(); + if ( s ) { + char *b; + int len; + + len = strlen( s ) + 1; + b = malloc( len ); + strcpy( b, s ); + Sys_QueEvent( 0, SE_CONSOLE, 0, 0, len, b ); + } + + // check for other input devices + IN_Frame(); + + // check for network packets + MSG_Init( &netmsg, sys_packetReceived, sizeof( sys_packetReceived ) ); + if ( Sys_GetPacket ( &adr, &netmsg ) ) { + netadr_t *buf; + int len; + + // copy out to a seperate buffer for qeueing + len = sizeof( netadr_t ) + netmsg.cursize; + buf = malloc( len ); + *buf = adr; + memcpy( buf+1, netmsg.data, netmsg.cursize ); + Sys_QueEvent( 0, SE_PACKET, 0, 0, len, buf ); + } + + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // create an empty event to return + + memset( &ev, 0, sizeof( ev ) ); + ev.evTime = Sys_Milliseconds(); + + return ev; +} + +/*****************************************************************************/ + +void Sys_AppActivate (void) +{ +} + +char *Sys_GetClipboardData(void) +{ + return NULL; +} + +void Sys_Print( const char *msg ) +{ + fputs(msg, stderr); +} + +int main (int argc, char **argv) +{ + int oldtime, newtime; + int len, i; + char *cmdline; + void SetProgramPath(char *path); + + // go back to real user for config loads + saved_euid = geteuid(); + seteuid(getuid()); + + SetProgramPath(argv[0]); + + // merge the command line, this is kinda silly + for (len = 1, i = 1; i < argc; i++) + len += strlen(argv[i]) + 1; + cmdline = malloc(len); + *cmdline = 0; + for (i = 1; i < argc; i++) { + if (i > 1) + strcat(cmdline, " "); + strcat(cmdline, argv[i]); + } + Com_Init(cmdline); + NET_Init(); + + fcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | FNDELAY); + + nostdout = Cvar_Get("nostdout", "0", 0); + if (!nostdout->value) { + fcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | FNDELAY); +// printf ("Linux Quake -- Version %0.3f\n", LINUX_VERSION); + } + + while (1) + { + // set low precision every frame, because some system calls + // reset it arbitrarily + Sys_LowFPPrecision (); + + Com_Frame (); + } +} + +#if 0 +/* +================ +Sys_MakeCodeWriteable +================ +*/ +void Sys_MakeCodeWriteable (unsigned long startaddr, unsigned long length) +{ + + int r; + unsigned long addr; + int psize = getpagesize(); + + addr = (startaddr & ~(psize-1)) - psize; + +// fprintf(stderr, "writable code %lx(%lx)-%lx, length=%lx\n", startaddr, +// addr, startaddr+length, length); + + r = mprotect((char*)addr, length + startaddr - addr + psize, 7); + + if (r < 0) + Sys_Error("Protection change failed\n"); + +} + +#endif diff --git a/code/unix/unix_net.c b/code/unix/unix_net.c new file mode 100644 index 0000000..ec2bbf4 --- /dev/null +++ b/code/unix/unix_net.c @@ -0,0 +1,443 @@ +// unix_net.c + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef NeXT +#include +#endif + +static cvar_t *noudp; + +netadr_t net_local_adr; + +int ip_socket; +int ipx_socket; + +#define MAX_IPS 16 +static int numIP; +static byte localIP[MAX_IPS][4]; + +int NET_Socket (char *net_interface, int port); +char *NET_ErrorString (void); + +//============================================================================= + +void NetadrToSockadr (netadr_t *a, struct sockaddr_in *s) +{ + memset (s, 0, sizeof(*s)); + + if (a->type == NA_BROADCAST) + { + s->sin_family = AF_INET; + + s->sin_port = a->port; + *(int *)&s->sin_addr = -1; + } + else if (a->type == NA_IP) + { + s->sin_family = AF_INET; + + *(int *)&s->sin_addr = *(int *)&a->ip; + s->sin_port = a->port; + } +} + +void SockadrToNetadr (struct sockaddr_in *s, netadr_t *a) +{ + *(int *)&a->ip = *(int *)&s->sin_addr; + a->port = s->sin_port; + a->type = NA_IP; +} + +char *NET_BaseAdrToString (netadr_t a) +{ + static char s[64]; + + Com_sprintf (s, sizeof(s), "%i.%i.%i.%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3]); + + return s; +} + +/* +============= +Sys_StringToAdr + +idnewt +192.246.40.70 +============= +*/ +qboolean Sys_StringToSockaddr (const char *s, struct sockaddr *sadr) +{ + struct hostent *h; + char *colon; + + memset (sadr, 0, sizeof(*sadr)); + ((struct sockaddr_in *)sadr)->sin_family = AF_INET; + + ((struct sockaddr_in *)sadr)->sin_port = 0; + + if ( s[0] >= '0' && s[0] <= '9') + { + *(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr(s); + } + else + { + if (! (h = gethostbyname(s)) ) + return qfalse; + *(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0]; + } + + return qtrue; +} + +/* +============= +Sys_StringToAdr + +localhost +idnewt +idnewt:28000 +192.246.40.70 +192.246.40.70:28000 +============= +*/ +qboolean Sys_StringToAdr (const char *s, netadr_t *a) +{ + struct sockaddr_in sadr; + + if (!Sys_StringToSockaddr (s, (struct sockaddr *)&sadr)) + return qfalse; + + SockadrToNetadr (&sadr, a); + + return qtrue; +} + + +//============================================================================= + +qboolean Sys_GetPacket (netadr_t *net_from, msg_t *net_message) +{ + int ret; + struct sockaddr_in from; + int fromlen; + int net_socket; + int protocol; + int err; + + for (protocol = 0 ; protocol < 2 ; protocol++) + { + if (protocol == 0) + net_socket = ip_socket; + else + net_socket = ipx_socket; + + if (!net_socket) + continue; + + fromlen = sizeof(from); + ret = recvfrom (net_socket, net_message->data, net_message->maxsize + , 0, (struct sockaddr *)&from, &fromlen); + + SockadrToNetadr (&from, net_from); + + if (ret == -1) + { + err = errno; + + if (err == EWOULDBLOCK || err == ECONNREFUSED) + continue; + Com_Printf ("NET_GetPacket: %s from %s\n", NET_ErrorString(), + NET_AdrToString(*net_from)); + continue; + } + + if (ret == net_message->maxsize) + { + Com_Printf ("Oversize packet from %s\n", NET_AdrToString (*net_from)); + continue; + } + + net_message->cursize = ret; + return qtrue; + } + + return qfalse; +} + +//============================================================================= + +void Sys_SendPacket( int length, const void *data, netadr_t to ) +{ + int ret; + struct sockaddr_in addr; + int net_socket; + + if (to.type == NA_BROADCAST) + { + net_socket = ip_socket; + } + else if (to.type == NA_IP) + { + net_socket = ip_socket; + } + else if (to.type == NA_IPX) + { + net_socket = ipx_socket; + } + else if (to.type == NA_BROADCAST_IPX) + { + net_socket = ipx_socket; + } + else { + Com_Error (ERR_FATAL, "NET_SendPacket: bad address type"); + return; + } + + if (!net_socket) + return; + + NetadrToSockadr (&to, &addr); + + ret = sendto (net_socket, data, length, 0, (struct sockaddr *)&addr, sizeof(addr) ); + if (ret == -1) + { + Com_Printf ("NET_SendPacket ERROR: %s to %s\n", NET_ErrorString(), + NET_AdrToString (to)); + } +} + + +//============================================================================= + +/* +================== +Sys_IsLANAddress + +LAN clients will have their rate var ignored +================== +*/ +qboolean Sys_IsLANAddress (netadr_t adr) { + int i; + if ( adr.type == NA_LOOPBACK ) { + return qtrue; + } + + // FIXME: ipx? + + if ( adr.type == NA_IP ) { + for ( i = 0 ; i < numIP ; i++ ) { + // assuming a class C network, which may not be smart... + if ( adr.ip[0] == localIP[i][0] + && adr.ip[1] == localIP[i][1] + && adr.ip[2] == localIP[i][2] ) { + return qtrue; + } + } + } + + return qfalse; +} + +/* +===================== +NET_GetLocalAddress +===================== +*/ +void NET_GetLocalAddress( void ) { + char hostname[256]; + struct hostent *hostInfo; + int error; + char *p; + int ip; + int n; + + if ( gethostname( hostname, 256 ) == -1 ) { + return; + } + + hostInfo = gethostbyname( hostname ); + if ( !hostInfo ) { + return; + } + + Com_Printf( "Hostname: %s\n", hostInfo->h_name ); + n = 0; + while( ( p = hostInfo->h_aliases[n++] ) != NULL ) { + Com_Printf( "Alias: %s\n", p ); + } + + if ( hostInfo->h_addrtype != AF_INET ) { + return; + } + + numIP = 0; + while( ( p = hostInfo->h_addr_list[numIP++] ) != NULL && numIP < MAX_IPS ) { + ip = ntohl( *(int *)p ); + localIP[ numIP ][0] = p[0]; + localIP[ numIP ][1] = p[1]; + localIP[ numIP ][2] = p[2]; + localIP[ numIP ][3] = p[3]; + Com_Printf( "IP: %i.%i.%i.%i\n", ( ip >> 24 ) & 0xff, ( ip >> 16 ) & 0xff, ( ip >> 8 ) & 0xff, ip & 0xff ); + } +} + +/* +==================== +NET_OpenIP +==================== +*/ +void NET_OpenIP (void) +{ + cvar_t *ip; + int port; + int i; + + ip = Cvar_Get ("net_ip", "localhost", 0); + + port = Cvar_Get("net_port", va("%i", PORT_SERVER), 0)->value; + + for ( i = 0 ; i < 10 ; i++ ) { + ip_socket = NET_IPSocket (ip->string, port + i); + if ( ip_socket ) { + Cvar_SetValue( "net_port", port + i ); + NET_GetLocalAddress(); + return; + } + } + Com_Error (ERR_FATAL, "Couldn't allocate IP port"); +} + + +/* +==================== +NET_Init +==================== +*/ +void NET_Init (void) +{ + noudp = Cvar_Get ("net_noudp", "0", 0); + // open sockets + if (! noudp->value) { + NET_OpenIP (); + } +} + + +/* +==================== +NET_Socket +==================== +*/ +int NET_IPSocket (char *net_interface, int port) +{ + int newsocket; + struct sockaddr_in address; + qboolean _qtrue = qtrue; + int i = 1; + + if ( net_interface ) { + Com_Printf("Opening IP socket: %s:%i\n", net_interface, port ); + } else { + Com_Printf("Opening IP socket: localhost:%i\n", port ); + } + + if ((newsocket = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + { + Com_Printf ("ERROR: UDP_OpenSocket: socket: %s", NET_ErrorString()); + return 0; + } + + // make it non-blocking + if (ioctl (newsocket, FIONBIO, &_qtrue) == -1) + { + Com_Printf ("ERROR: UDP_OpenSocket: ioctl FIONBIO:%s\n", NET_ErrorString()); + return 0; + } + + // make it broadcast capable + if (setsockopt(newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)) == -1) + { + Com_Printf ("ERROR: UDP_OpenSocket: setsockopt SO_BROADCAST:%s\n", NET_ErrorString()); + return 0; + } + + if (!net_interface || !net_interface[0] || !stricmp(net_interface, "localhost")) + address.sin_addr.s_addr = INADDR_ANY; + else + Sys_StringToSockaddr (net_interface, (struct sockaddr *)&address); + + if (port == PORT_ANY) + address.sin_port = 0; + else + address.sin_port = htons((short)port); + + address.sin_family = AF_INET; + + if( bind (newsocket, (void *)&address, sizeof(address)) == -1) + { + Com_Printf ("ERROR: UDP_OpenSocket: bind: %s\n", NET_ErrorString()); + close (newsocket); + return 0; + } + + return newsocket; +} + +/* +==================== +NET_Shutdown +==================== +*/ +void NET_Shutdown (void) +{ + if (ip_socket) { + close(ip_socket); + ip_socket = 0; + } +} + + +/* +==================== +NET_ErrorString +==================== +*/ +char *NET_ErrorString (void) +{ + int code; + + code = errno; + return strerror (code); +} + +// sleeps msec or until net socket is ready +void NET_Sleep(int msec) +{ + struct timeval timeout; + fd_set fdset; + extern qboolean stdin_active; + + if (!ip_socket || !com_dedicated->integer) + return; // we're not a server, just run full speed + + FD_ZERO(&fdset); + if (stdin_active) + FD_SET(0, &fdset); // stdin is processed too + FD_SET(ip_socket, &fdset); // network socket + timeout.tv_sec = msec/1000; + timeout.tv_usec = (msec%1000)*1000; + select(ip_socket+1, &fdset, NULL, NULL, &timeout); +} + diff --git a/code/unix/unix_shared.c b/code/unix/unix_shared.c new file mode 100644 index 0000000..486cf40 --- /dev/null +++ b/code/unix/unix_shared.c @@ -0,0 +1,369 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" + +//=============================================================================== + +// Used to determine CD Path +static char programpath[MAX_OSPATH]; + +/* +================ +Sys_Milliseconds +================ +*/ +int curtime; +int sys_timeBase; +int Sys_Milliseconds (void) +{ + struct timeval tp; + struct timezone tzp; + + gettimeofday(&tp, &tzp); + + if (!sys_timeBase) + { + sys_timeBase = tp.tv_sec; + return tp.tv_usec/1000; + } + + curtime = (tp.tv_sec - sys_timeBase)*1000 + tp.tv_usec/1000; + + return curtime; +} + +void Sys_Mkdir( const char *path ) +{ + mkdir (path, 0777); +} + +char *strlwr (char *s) +{ + while (*s) { + *s = tolower(*s); + s++; + } +} + +//============================================ + +/* Like glob_match, but match PATTERN against any final segment of TEXT. */ +static int glob_match_after_star(char *pattern, char *text) +{ + register char *p = pattern, *t = text; + register char c, c1; + + while ((c = *p++) == '?' || c == '*') + if (c == '?' && *t++ == '\0') + return 0; + + if (c == '\0') + return 1; + + if (c == '\\') + c1 = *p; + else + c1 = c; + + while (1) { + if ((c == '[' || *t == c1) && glob_match(p - 1, t)) + return 1; + if (*t++ == '\0') + return 0; + } +} + +/* Return nonzero if PATTERN has any special globbing chars in it. */ +static int glob_pattern_p(char *pattern) +{ + register char *p = pattern; + register char c; + int open = 0; + + while ((c = *p++) != '\0') + switch (c) { + case '?': + case '*': + return 1; + + case '[': /* Only accept an open brace if there is a close */ + open++; /* brace to match it. Bracket expressions must be */ + continue; /* complete, according to Posix.2 */ + case ']': + if (open) + return 1; + continue; + + case '\\': + if (*p++ == '\0') + return 0; + } + + return 0; +} + +/* Match the pattern PATTERN against the string TEXT; + return 1 if it matches, 0 otherwise. + + A match means the entire string TEXT is used up in matching. + + In the pattern string, `*' matches any sequence of characters, + `?' matches any character, [SET] matches any character in the specified set, + [!SET] matches any character not in the specified set. + + A set is composed of characters or ranges; a range looks like + character hyphen character (as in 0-9 or A-Z). + [0-9a-zA-Z_] is the set of characters allowed in C identifiers. + Any other character in the pattern must be matched exactly. + + To suppress the special syntactic significance of any of `[]*?!-\', + and match the character exactly, precede it with a `\'. +*/ + +int glob_match(char *pattern, char *text) +{ + register char *p = pattern, *t = text; + register char c; + + while ((c = *p++) != '\0') + switch (c) { + case '?': + if (*t == '\0') + return 0; + else + ++t; + break; + + case '\\': + if (*p++ != *t++) + return 0; + break; + + case '*': + return glob_match_after_star(p, t); + + case '[': + { + register char c1 = *t++; + int invert; + + if (!c1) + return (0); + + invert = ((*p == '!') || (*p == '^')); + if (invert) + p++; + + c = *p++; + while (1) { + register char cstart = c, cend = c; + + if (c == '\\') { + cstart = *p++; + cend = cstart; + } + if (c == '\0') + return 0; + + c = *p++; + if (c == '-' && *p != ']') { + cend = *p++; + if (cend == '\\') + cend = *p++; + if (cend == '\0') + return 0; + c = *p++; + } + if (c1 >= cstart && c1 <= cend) + goto match; + if (c == ']') + break; + } + if (!invert) + return 0; + break; + + match: + /* Skip the rest of the [...] construct that already matched. */ + while (c != ']') { + if (c == '\0') + return 0; + c = *p++; + if (c == '\0') + return 0; + else if (c == '\\') + ++p; + } + if (invert) + return 0; + break; + } + + default: + if (c != *t++) + return 0; + } + + return *t == '\0'; +} + +//============================================ + +#define MAX_FOUND_FILES 0x1000 + +char **Sys_ListFiles( const char *directory, const char *extension, int *numfiles, qboolean wantsubs ) +{ + struct dirent *d; + char *p; + DIR *fdir; + qboolean dironly = wantsubs; + char search[MAX_OSPATH]; + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + int flag; + int i; + struct stat st; + + int extLen; + + if ( !extension) + extension = ""; + + if ( extension[0] == '/' && extension[1] == 0 ) { + extension = ""; + dironly = qtrue; + } + + extLen = strlen( extension ); + + // search + nfiles = 0; + + if ((fdir = opendir(directory)) == NULL) { + *numfiles = 0; + return NULL; + } + + while ((d = readdir(fdir)) != NULL) { + Com_sprintf(search, sizeof(search), "%s/%s", directory, d->d_name); + if (stat(search, &st) == -1) + continue; + if ((dironly && !(st.st_mode & S_IFDIR)) || + (!dironly && (st.st_mode & S_IFDIR))) + continue; + + if (*extension) { + if ( strlen( d->d_name ) < strlen( extension ) || + Q_stricmp( + d->d_name + strlen( d->d_name ) - strlen( extension ), + extension ) ) { + continue; // didn't match + } + } + + if ( nfiles == MAX_FOUND_FILES - 1 ) + break; + list[ nfiles ] = CopyString( d->d_name ); + nfiles++; + } + + list[ nfiles ] = 0; + + closedir(fdir); + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + +void Sys_FreeFileList( char **list ) { + int i; + + if ( !list ) { + return; + } + + for ( i = 0 ; list[i] ; i++ ) { + Z_Free( list[i] ); + } + + Z_Free( list ); +} + +char *Sys_Cwd( void ) +{ + static char cwd[MAX_OSPATH]; + + getcwd( cwd, sizeof( cwd ) - 1 ); + cwd[MAX_OSPATH-1] = 0; + + return cwd; +} + +void SetProgramPath(char *path) +{ + char *p; + + Q_strncpyz(programpath, path, sizeof(programpath)); + if ((p = strrchr(programpath, '/')) != NULL) + *p = 0; // remove program name, leave only path +} + +char *Sys_DefaultCDPath(void) +{ + if (*programpath) + return programpath; + else + return Sys_Cwd(); +} + +char *Sys_DefaultBasePath(void) +{ + char *p; + static char basepath[MAX_OSPATH]; + int e; + + if ((p = getenv("HOME")) != NULL) { + Q_strncpyz(basepath, p, sizeof(basepath)); + Q_strcat(basepath, sizeof(basepath), "/.q3a"); + if (mkdir(basepath, 0777)) { + if (errno != EEXIST) + Sys_Error("Unable to create directory \"%s\", error is %s(%d)\n", basepath, strerror(errno), errno); + } + return basepath; + } + return ""; // assume current dir +} + +//============================================ + +int Sys_GetProcessorId( void ) +{ + return CPUID_GENERIC; +} + +void Sys_ShowConsole( int visLevel, qboolean quitOnClose ) +{ +} + + diff --git a/code/win32/AutoVersion.h b/code/win32/AutoVersion.h new file mode 100644 index 0000000..f7032b7 --- /dev/null +++ b/code/win32/AutoVersion.h @@ -0,0 +1,79 @@ +#ifndef __AUTO_VERSION_HEADER +#define __AUTO_VERSION_HEADER + +#define VERSION_MAJOR_RELEASE 1 +#define VERSION_MINOR_RELEASE 0 +#define VERSION_EXTERNAL_BUILD 0 +#define VERSION_INTERNAL_BUILD 0 + +#define VERSION_STRING "1, 0, 0, 0" +#define VERSION_STRING_DOTTED "1.0.0.0" + +#define VERSION_BUILD_NUMBER 73 + +// BEGIN COMMENTS +// 1.0.0.0 07/21/2003 16:09:23 jmonroe going gold +// 0.0.12.0 07/20/2003 19:04:44 jmonroe build 0.12 for qa +// 0.0.11.0 07/19/2003 15:30:08 jmonroe stuff +// 0.0.11.0 07/17/2003 17:08:09 jmonroe build 0.11 for qa +// 0.0.10.1 07/16/2003 17:33:28 mgummelt General Update With Latest Code +// 0.0.10.0 07/16/2003 10:28:51 jmonroe new product id, remove outcast.roq at end, menu ignoreescape, eax looping sounds support chan_less_atten +// 0.0.10.0 07/14/2003 19:04:42 jmonroe build 0.10 for qa +// 0.0.9.0 07/12/2003 13:05:57 jmonroe increase numsnapshot ents +// 0.0.9.0 07/11/2003 17:48:40 jmonroe buil 0.09 for qa +// 0.0.8.4 07/11/2003 15:47:55 jmonroe weatherzone caching +// 0.0.8.3 07/11/2003 12:05:44 mgummelt Lava & Acid fixes +// 0.0.8.2 07/10/2003 17:26:42 mgummelt General Update with today's bug fixes +// 0.0.8.1 07/09/2003 19:24:19 jmonroe eax voice stomp fix, increased weather zones, ... +// 0.0.8.0 07/08/2003 17:28:27 jmonroe build 0.08 for qa +// 0.0.7.2 07/07/2003 16:28:40 mgummelt Blaster Pistol alt-fire returns, Rancor spawnflags corrected +// 0.0.7.1 07/07/2003 10:48:29 jmonroe load menu fixes, darkside autoload fix,... +// 0.0.7.0 07/02/2003 19:31:37 jmonroe buil 7 for qa +// 0.0.6.5 07/02/2003 18:17:17 mgummelt Boss balancing +// 0.0.6.3 07/02/2003 01:34:48 jmonroe saber in moves menu +// 0.0.6.2 06/30/2003 15:33:37 mgummelt Force Sight change (designers readme) +// 0.0.6.1 06/29/2003 19:00:17 jmonroe vv merged, aev_soundchan +// 0.0.6.0 06/28/2003 16:38:27 jmonroe eax update, force sight on key dudes +// 0.0.6.0 06/26/2003 16:45:26 jmonroe qa build +// 0.0.5.6 06/25/2003 21:22:38 mgummelt "redcrosshair" field on misc_model_breakables and func_breakables +// 0.0.5.5 06/25/2003 18:31:52 mgummelt Force Visible on all applicable ents +// 0.0.5.4 06/25/2003 10:45:12 jmonroe EAX 4.0 +// 0.0.5.3 06/24/2003 22:59:31 mgummelt Reborn tweaks +// 0.0.5.2 06/24/2003 18:53:55 mgummelt New Reborn Master +// 0.0.5.1 06/23/2003 20:47:44 jmonroe fix NULL NPC usage +// 0.0.5.0 06/21/2003 13:04:27 jmonroe fix eweb crash +// 0.0.4.2 06/18/2003 14:24:35 jmonroe script cmd SET_WEAPON now precaches weapon, gil's optimized dlights +// 0.0.4.1 06/17/2003 19:04:28 mgummelt Various Force Power, saber move & Enemy Jedi tweaks +// 0.0.4.0 06/16/2003 19:41:54 jmonroe bug stuff +// 0.0.4.0 06/13/2003 23:52:36 jmonroe inc version to match qa beta +// 0.0.2.6 06/12/2003 13:11:21 creed new z-far cull +// 0.0.2.5 06/10/2003 17:02:17 creed targetJump & other navigation +// 0.0.2.4 06/10/2003 11:18:58 jmonroe footstep sounds in per material +// 0.0.2.3 06/10/2003 01:37:10 mgummelt helping with entity limit at spawntime +// 0.0.2.2 06/09/2003 14:10:32 scork fix for asian languages being bust by bad font +// 0.0.2.1 06/08/2003 17:57:11 mgummelt Area portal fix +// 0.0.2.0 06/08/2003 14:02:03 mgummelt Force sight change +// 0.0.2.0 06/08/2003 00:03:58 jmonroe CGEN_LIGHTING_DIFFUSE_ENTITY merges lightingdiffuse and entity color +// 0.0.1.25 06/05/2003 20:51:30 mgummelt Saber pull-attacks done +// 0.0.1.24 06/05/2003 18:11:06 mgummelt Both saber control schemes implemented +// 0.0.1.23 05/30/2003 17:12:34 mgummelt Fixed Noghri and Sand Creature +// 0.0.1.22 05/30/2003 12:15:37 mgummelt Saboteur change, new cultist +// 0.0.1.21 05/29/2003 16:02:48 creed Fixes For Assassin, ST AI changes +// 0.0.1.20 05/29/2003 12:00:18 mgummelt Navigation changes +// 0.0.1.19 05/29/2003 11:08:06 mgummelt various tweaks +// 0.0.1.18 05/28/2003 22:08:16 creed Boba Fett ++ +// 0.0.1.17 05/28/2003 20:38:33 mgummelt force grip and force sense changes +// 0.0.1.15 05/28/2003 17:07:26 mgummelt tweaks of force drain, protect and absorb +// 0.0.1.14 05/28/2003 14:48:57 mgummelt various saber & AI fixes +// 0.0.1.13 05/27/2003 17:26:30 areis Ported Glow stuff from MP (with support for nVidia and ATI cards). Added solid flag for roffs (in behaved) and made tie-bombers explode with effect. +// 0.0.1.12 05/27/2003 13:06:03 mgummelt Noghri stick weapon shoots a projectile, E-Web uses proper sounds +// 0.0.1.7 05/19/2003 15:00:10 mgummelt adding random jedi, elder prisoners, jedi master +// 0.0.1.6 05/19/2003 09:22:18 creed Weather Effects & Haz Trooper +// 0.0.1.5 05/16/2003 18:55:10 creed New Wind Spawn Flags For Dusty Fog & 1/2 way through Haz Trooper Fixins +// 0.0.1.4 05/15/2003 14:29:35 jmonroe misc_model_static stay around after a vid_start +// 0.0.1.3 05/14/2003 17:53:44 mgummelt testing misc_model_breakable scaling +// 0.0.1.2 05/13/2003 20:48:35 jmonroe vv post merge +// 0.0.1.1 05/13/2003 14:17:20 scork ste test comment +// END COMMENTS + +#endif // __AUTO_VERSION_HEADER diff --git a/code/win32/FeelIt/FEELitIFR.h b/code/win32/FeelIt/FEELitIFR.h new file mode 100644 index 0000000..823ae79 --- /dev/null +++ b/code/win32/FeelIt/FEELitIFR.h @@ -0,0 +1,247 @@ +/********************************************************************** + Copyright (c) 1999 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FEELitIFR.h + + PURPOSE: Input/Output for IFR Files, FEELit version + + STARTED: + + NOTES/REVISIONS: + +**********************************************************************/ + +#if !defined( _FEELIT2_H_) +#define _FEELIT2_H_ + +#ifndef __FEELITAPI_INCLUDED__ + #error include 'dinput.h' before including this file for structures. +#endif /* !__DINPUT_INCLUDED__ */ + +#define IFRAPI __stdcall + +#if !defined(_FFCDLL_) +#define DLLAPI __declspec(dllimport) +#else +#define DLLAPI __declspec(dllexport) +#endif + +#if defined __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* +** CONSTANTS +*/ + +/* +** RT_FEELIT - Resource type for IFR projects stored as resources. +** This is the resource type looked for by IFLoadProjectResource(). +*/ +#define RT_FEELIT ((LPCSTR)"FEELIT") + + +/* +** TYPES/STRUCTURES +*/ + +/* +** HIFRPROJECT - used to identify a loaded project as a whole. +** individual objects within a project are uniquely referenced by name. +** Created by the IFLoadProject*() functions and released by IFReleaseProject(). +*/ +typedef LPVOID HIFRPROJECT; + +/* +** IFREffect - contains the information needed to create a DI effect +** using IDirectInputEffect::CreateEffect(). An array of pointers to these +** structures is allocated and returned by IFCreateEffectStructs(). +*/ +typedef struct { + GUID guid; + LPFEELIT_EFFECT lpDIEffect; +} IFREffect; + + +/* +** FUNCTION DECLARATIONS +*/ + +/* +** IFLoadProjectResource() - Load a project from a resource. +** hRsrcModule - handle of the module containing the project definition resource. +** pRsrcName - name or MAKEINTRESOURCE(id) identifier of resource to load. +** pDevice - device for which the project is being loaded. If NULL, +** effects will be created generically, and IFCreateEffects() will fail. +** Returns an identifier for the loaded project, or NULL if unsuccessful. +*/ +DLLAPI +HIFRPROJECT +IFRAPI +IFRLoadProjectResource( + HMODULE hRsrcModule, + LPCSTR pRsrcName, + LPIFEELIT_DEVICE pDevice ); + +/* +** IFLoadProjectPointer() - Load a project from a pointer. +** pProject - points to a project definition. +** pDevice - device for which the project is being loaded. If NULL, +** effects will be created generically, and IFCreateEffects() will fail. +** Returns an identifier for the loaded project, or NULL if unsuccessful. +*/ +DLLAPI +HIFRPROJECT +IFRAPI +IFRLoadProjectPointer( + LPVOID pProject, + LPIFEELIT_DEVICE pDevice ); + +/* +** IFLoadProjectFile() - Load a project from a file. +** pProjectFileName - points to a project file name. +** pDevice - device for which the project is being loaded. If NULL, +** effects will be created generically, and IFCreateEffects() will fail. +** Returns an identifier for the loaded project, or NULL if unsuccessful. +*/ +DLLAPI +HIFRPROJECT +IFRAPI +IFRLoadProjectFile( + LPCSTR pProjectFileName, + LPIFEELIT_DEVICE pDevice ); + +/* +** IFLoadProjectObjectPointer() - Load a project from a pointer to a single +** object definition (usually used only by the editor). +** pObject - points to an object definition. +** pDevice - device for which the project is being loaded. If NULL, +** effects will be created generically, and IFCreateEffects() will fail. +** Returns an identifier for the loaded project, or NULL if unsuccessful. +*/ +DLLAPI +HIFRPROJECT +IFRAPI +IFRLoadProjectObjectPointer( + LPVOID pObject, + LPIFEELIT_DEVICE pDevice ); + +/* +** IFReleaseProject() - Release a loaded project. +** hProject - identifies the project to be released. +** Returns TRUE if the project is released, FALSE if it is an invalid project. +*/ +DLLAPI +BOOL +IFRAPI +IFRReleaseProject( + HIFRPROJECT hProject ); + +/* +** IFCreateEffectStructs() - Create IFREffects for a named effect. +** hProject - identifies the project containing the object. +** pObjectName - name of the object for which to create structures. +** pNumEffects - if not NULL will be set to a count of the IFREffect +** structures in the array (not including the terminating NULL pointer.) +** Returns a pointer to the allocated array of pointers to IFREffect +** structures. The array is terminated with a NULL pointer. If the +** function fails, a NULL pointer is returned. +*/ +DLLAPI +IFREffect ** +IFRAPI +IFRCreateEffectStructs( + HIFRPROJECT hProject, + LPCSTR pObjectName, + int *pNumEffects ); + +DLLAPI +IFREffect ** +IFRAPI +IFRCreateEffectStructsByIndex( + HIFRPROJECT hProject, + int nObjectIndex, + int *pNumEffects ); + +DLLAPI +LPCSTR +IFRAPI +IFRGetObjectNameByIndex( + HIFRPROJECT hProject, + int nObjectIndex ); + +/* +** IFReleaseEffectStructs() - Release an array of IFREffects. +** hProject - identifies the project for which the effects were created. +** pEffects - points to the array of IFREffect pointers to be released. +** Returns TRUE if the array is released, FALSE if it is an invalid array. +*/ +DLLAPI +BOOL +IFRAPI +IFRReleaseEffectStructs( + HIFRPROJECT hProject, + IFREffect **pEffects ); + +/* +** IFCreateEffects() - Creates the DirectInput effects using +** IDirectInput::CreateEffect(). +** hProject - identifies the project containing the object. +** pObjectName - name of the object for which to create effects. +** pNumEffects - if not NULL will be set to a count of the IDirectInputEffect +** pointers in the array (not including the terminating NULL pointer.) +** Returns a pointer to the allocated array of pointers to IDirectInputEffects. +** The array is terminated with a NULL pointer. If the function fails, +** a NULL pointer is returned. +*/ +DLLAPI +LPIFEELIT_EFFECT * +IFRAPI +IFRCreateEffects( + HIFRPROJECT hProject, + LPCSTR pObjectName, + int *pNumEffects ); + +/* +** IFReleaseEffects() - Releases an array of IDirectInputEffect structures. +** hProject - identifies the project for which the effects were created. +** pEffects - points to the array if IDirectInputEffect pointers to be released. +** Returns TRUE if the array is released, FALSE if it is an invalid array. +*/ +DLLAPI +BOOL +IFRAPI +IFRReleaseEffects( + HIFRPROJECT hProject, + LPIFEELIT_EFFECT *pEffects ); + +DLLAPI +BOOL +IFRAPI +DllMain( + HINSTANCE hInstDLL, + DWORD fdwReason, + LPVOID lpvReserved ); + + +#if defined __cplusplus +} +#endif /* __cplusplus */ + +#endif /* !IForce2_h */ diff --git a/code/win32/FeelIt/FFC.h b/code/win32/FeelIt/FFC.h new file mode 100644 index 0000000..1d86d97 --- /dev/null +++ b/code/win32/FeelIt/FFC.h @@ -0,0 +1,78 @@ +/********************************************************************** + Copyright (c) 1997,8,9 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FFC.h + + PURPOSE: Class Types for the Force Foundation Classes + + STARTED: 10/29/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + +**********************************************************************/ + + +#if !defined(AFX_FFC_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FFC_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + + +#include "FeelBox.h" +#include "FeelCondition.h" +#include "FeelConstant.h" +#include "FeelDamper.h" +#include "FeelDevice.h" +#include "FeelDXDevice.h" +#include "FeelEffect.h" +#include "FeelEllipse.h" +#include "FeelEnclosure.h" +#include "FeelMouse.h" +#include "FeelFriction.h" +#include "FeelGrid.h" +#include "FeelInertia.h" +#include "FeelPeriodic.h" +#include "FeelProjects.h" +#include "FeelRamp.h" +#include "FeelSpring.h" +#include "FeelTexture.h" +#include "FFCErrors.h" + + +#endif // !defined(AFX_FFC_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) + + + + + + + + + + + + + + + + + diff --git a/code/win32/FeelIt/FFC10.dll b/code/win32/FeelIt/FFC10.dll new file mode 100644 index 0000000000000000000000000000000000000000..0536f23bd32a58b9a7eed9c85fc223026b60f629 GIT binary patch literal 126976 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 al|WPiQ3*sP5S2hw0#OM>CGa0g;C}%#h?dC! literal 0 HcmV?d00001 diff --git a/code/win32/FeelIt/FFC10.lib b/code/win32/FeelIt/FFC10.lib new file mode 100644 index 0000000000000000000000000000000000000000..54ca7f42b1b7ef4c754983a64208c51b6a21e0bc GIT binary patch literal 232332 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 literal 0 HcmV?d00001 diff --git a/code/win32/FeelIt/FFC10d.dll b/code/win32/FeelIt/FFC10d.dll new file mode 100644 index 0000000000000000000000000000000000000000..1f546c2e40fcf999183299c764142ae734442d7e GIT binary patch literal 405591 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 GlK%#D>d=b- literal 0 HcmV?d00001 diff --git a/code/win32/FeelIt/FFC10d.lib b/code/win32/FeelIt/FFC10d.lib new file mode 100644 index 0000000000000000000000000000000000000000..786d23d7632545867c573a8195ff70cda9fe5739 GIT binary patch literal 232598 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
dLm8Ij_W`}aFQ*0V97E{Uyb=(Ix3E#5Li84yc zOWB3k*^=@KVGVy8cA+9D=76rZ4p5=Je?Co-OO~l+I8bgVUsJp0>fHNBXaWV$%K=+& zj@w(IeV^}7!EJ_n+IU%c>(!El;wu|C0Q4%#^2=*vZWuk7s**A1KZ1e@TZ zPd~6@qwYffrzvR6n(AP1EaSYp9gVuSI%AXc1dVxUp;RU~37w8r@_QxKbB5Tu**ly*ndme)8Fta{R^P?T0@wbN&yf6vnFTx1H}d-x_? z7&^5yzqp3$?)Y|zv5{2xH4j@1rDYsF>Ek3hQ<}QkY27q(z|(&ewX-@W!TuHWPd085 zxMU{k-MD&L08?7@_YWW>yXpf22WI#~wrshKi0rdkm#)$7GCO``xo?3gg<^1rx!dkJ zZ8)>kgA{8L8nU0hGfSU^es%=v$oxXIBb3e)t&>M(L?EM;yp(-Yw=*$ck_ds`G$|B+ z6*|%GuT$_R&a)feK(m*29+-L_yA^>>syHC%JNoY3VXmz>s|`6|C-&H;?AFn@F9-Y_ z^zU!14K|coNVXwjiXj=bly!E8(Mev4`|Xgo@H+%HyB={rA9@stx(c16_%{aXv?Z^l z-Th;$hABDrxI(+TI~8dr4IJ?DA6xX9J1LymhFonM(x*u%#(w&8wa>fpwE=hi8q#!XI3VXOM=P`r;%`mF>C2x>%hNp9K_sN-u!RZ^i1~B13h8PO zWMNG2$)&WFtWQ@KxmHuh=~8nU0hDEvEt!j+Y!mBFPs#_UE0 zhuJ?gJ?$SjR9gR5`?&Nugjykt;6H*m7Y&L;S8sJB=@%8cMrRl27Uma^B@tF7Ny-rB zqC=4=tB~#E?>Wl$9zD9+SQGQ9+5u9!PF%_{g1_w}Cp8L1oH>)fr46MfvrRUM9AH0v zrRJA7;`X6`Ni=`2ZNEv5B`N9ry>7piR$YBgm7VgSq-Q=_IN;`Q2kF~ktOwbs*4=+d z;!U?P(aqROia0xm)8|mxDUMdeIjK=7<|?GSJ&2fFn_Y=G+~Csj<)ssgbGjX*Dprsj zba24T+ty~;KRIkeNqPzP$0i1P`AX9Nl|b3z93Bs_v?$I=;AXZmdLJ>$$V*w-E|<6P zJA`5Ul-!Ks%cOt~=SkUC8a^zy}4Q^S_zTx{2b^4f1Z2tgFGo~7O0 zh5t-s8TEI-%9~?V2vuL!Q6)8h4y&@t!*!r4S^pwYWm(ak;~YRF2ud#eK|kOK#TBE8yp(>gqm9j*$6QR}DpB`h@no)#+Nfj3uTeZD z1w%<~s~r+h_^mLddjheYpA1Y{>R&9b%pG(d!*aA6pSlps)Wkq9Uq1D93$DcKp3#zW zDn=D~DOBmcvjZ3*4OOc4g<`5g^WNV;Ox-fScI3wCrGdk%OA9B~8rKRRobHde2jU)3YvP;Y3JpKQD=;45! zFPH129`E)ih(92g>`xoe+UB*ok=4Kj4kD`yy%-Yh-!u)D96CNwYV*|s7 z z21+Z*8SUhy&^aM*;dcl}@l&F6oiuX5(_6ZCn`4;E+Yp_sr#CUs%NLywr=WA@@U1iR zi*t>WLbr6mbX0(}j2u+wm2!lmpR{ot$?L+XKcyT{^_TKp=K7h9s)_y|=x;2ZI6gDi z;G>?I<&~u)`p{=$_o!MBH#B^3Y;bsNa{u(ejgerr%{3E?A>AV&$HW5)^}s?Zj%1ae)`J# zZ$tl4w1u;A(a@OKQj>24EA>q_D$+LFEamDjMk#qIz4JxZaL{AI5@5|UOw*7Edy`-~$o|f|5v~a-9TT|@qHOJhiwIQdw6#Hfq1HF7X z-FK6CQ{xu}v{G_6Mjd%6?)F@H3%^4c#m|Vl=}5Ct!2vOEO|L@Z=I=rO{30&SnO&HF zWwa?y(&~-k6h|ZCKuS3`lQi*^)H3clZZ3#Y4ygKb!yV=f&PLVyzHefcsC))SONvRm z-glM884oDVn8aiTMT*2;PZ!`1b?oKR$z7;h_4aw#4ZiAHb3v!Nssx|%G>#r}sf6?ZQP@s6G<0A`(wa^^Lu@i?q5@)B{_ zzl-H9{0@Qba~{bnj6$dMIBJZTne#|4*uInIEHX+x{zS+qqk?b>(&}BMY;~ z*aQV>>WP7$a!a$C$xF4g?k~V*7vh%AQ6opg`%9Dx?RNSI^e?Y8mS`!(NM; zPf15|cKurSyc<&}%Rz-=F;<;1-2b&>Eb=9bAZFu9WIczFK1_m1nWKJfj+jXR9ZLFe zXG&B0s8mvBu{gTS67q;jIx#+@GK)Q}1$K<3j^S(=)2ZNqn6Lcm>+Cl6k?BC(pYHBu4Yfoqmr!8&6jm1sZI!{><7EGyNB^#Bt_Rlp8`?YTcH*8 z|BsmZ{ZGg7!cTokU@5sAqnf;weRo*i!tW4nz|Uxu!LZjFbsUiNA9t9uS{rpW z;HKP&#BJB1~&0M9;1_4?|eUi~nUJ9R6 z@)mxF@N)cg;M1E<4w!mtoPFC(`M#|UamuW(O$_w%#p&rOI8_@s8%X6)wSu*jXk}EC zmqIJ^yp>l7%lOlU)|RN{fULhpYR=Ba1)uY7)DFf+8Ua4mZF)VsP%If`afkeA|a=gM379YPm=3W-Z=>wt{^IAeQtJfzPI zW^nJtLB0*4U`+d?jT#X`rNoe*hqR;i4oG>+zY3l9^rSRI?Yec0Z>?BT-n3QYLUJxo z_vd1ISj3y`y^7Gl0WW`!)oprAKZ0dxHz{`>mZ^z>UcOT9ECXMwOx%@{S223XOUb*7 z#^VYMFeel renaming + 3/6/99 jrm: Added user error handling control + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(FFCERRORS_H__INCLUDED_) +#define FFCERRORS_H__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include +#include "FeelBaseTypes.h" + + +/**************************************************************************** + * + * Error Codes + * + ****************************************************************************/ + +typedef enum { + FFC_ERR_OK = 0, + + FFC_ERR_UNKNOWN_ERROR = 1, + + FFC_ERR_ALLOCATION_FAILED = 2, + FFC_ERR_INVALID_PARAMETER = 3, + FFC_ERR_NULL_PARAMETER = 4, + FFC_ERR_WRONG_FORM = 5, + + FFC_ERR_DEVICE_IS_NULL = 6, + FFC_ERR_INVALID_GUID = 7, + FFC_ERR_EFFECT_NOT_INITIALIZED = 8, + + FFC_ERR_CANT_INITIALIZE_DEVICE = 9, + + FFC_ERR_CANT_CREATE_EFFECT = 10, + FFC_ERR_CANT_CREATE_EFFECT_FROM_IFR = 11, + FFC_ERR_NO_EFFECTS_FOUND = 12, + FFC_ERR_EFFECT_IS_COMPOUND = 13, + + FFC_ERR_PROJECT_ALREADY_OPEN = 14, + FFC_ERR_PROJECT_NOT_OPEN = 15 +} FFC_ERROR_CODE; + +typedef enum { + FFC_OUTPUT_ERR_TO_DEBUG = 0x0001, + FFC_OUTPUT_ERR_TO_DIALOG = 0x0002 +} FFC_ERROR_HANDLING_FLAGS; + + +/**************************************************************************** + * + * Macros + * + ****************************************************************************/ + +// +// ------ PUBLIC MACROS ------ +// +#define FFC_GET_LAST_ERROR CFFCErrors::GetLastErrorCode() +#define FFC_SET_ERROR_HANDLING CFFCErrors::SetErrorHandling + + +// +// ------ PRIVATE MACROS ------ +// +#if (FFC_VERSION >= 0x0110) + #define FFC_SET_ERROR(err) CFFCErrors::SetErrorCode(err, __FILE__, __LINE__) +#else + #define FFC_SET_ERROR(err) CFFCErrors::SetErrorCode(err) +#endif +#define FFC_CLEAR_ERROR FFC_SET_ERROR(FFC_ERR_OK) + + + +/**************************************************************************** + * + * CFFCErrors + * + ****************************************************************************/ +// All members are static. Don't bother instantiating an object of this class. + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFFCErrors +{ + // + // ATTRIBUTES + // + + public: + + static HRESULT + GetLastErrorCode() + { return m_Err; } + + static void + SetErrorHandling(unsigned long dwFlags) + { m_dwErrHandlingFlags = dwFlags; } + + +// +// ------ PRIVATE INTERFACE ------ +// + + // Internally used by FFC classes + static void + SetErrorCode( + HRESULT err +#if (FFC_VERSION >= 0x0110) + , const char *sFile, int nLine +#endif + ); + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + private: + + static HRESULT m_Err; + static unsigned long m_dwErrHandlingFlags; +}; + + +#endif // FFCERRORS_H__INCLUDED_ diff --git a/code/win32/FeelIt/FeelBaseTypes.h b/code/win32/FeelIt/FeelBaseTypes.h new file mode 100644 index 0000000..3152707 --- /dev/null +++ b/code/win32/FeelIt/FeelBaseTypes.h @@ -0,0 +1,265 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelBaseTypes.h + + PURPOSE: Base Types for Feelit API Foundation Classes + + STARTED: 10/29/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + +**********************************************************************/ + + +#if !defined(AFX_FEELBASETYPES_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELBASETYPES_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +//#include +#include "FeelitApi.h" + +#ifndef FFC_VERSION + #define FFC_VERSION 0x0100 +#endif + +#if (FFC_VERSION >= 0x0110) + #define FFC_START_DELAY + #define FFC_EFFECT_CACHING +#endif + +// These are defined in FEELitAPI.h +// +// #define FEELIT_DEVICETYPE_DEVICE 1 +// #define FEELIT_DEVICETYPE_MOUSE 2 +// #define FEELIT_DEVICETYPE_HID 0x00010000 +// +// Add define for DirectInput Device emulating FEELit Device +#define FEELIT_DEVICETYPE_DIRECTINPUT 3 + + +//================================================================ +// TYPE WRAPPERS +//================================================================ + +// +// FEEL --> FEELIT Wrappers +// +#define FEEL_DEVICETYPE_DEVICE FEELIT_DEVICETYPE_DEVICE +#define FEEL_DEVICETYPE_MOUSE FEELIT_DEVICETYPE_MOUSE +#define FEEL_DEVICETYPE_DIRECTINPUT FEELIT_DEVICETYPE_DIRECTINPUT + +#define FEEL_EFFECT FEELIT_EFFECT +#define LPFEEL_EFFECT LPFEELIT_EFFECT +#define LPCFEEL_EFFECT LPCFEELIT_EFFECT + +#define FEEL_CONDITION FEELIT_CONDITION +#define LPFEEL_CONDITION LPFEELIT_CONDITION +#define LPCFEEL_CONDITION LPCFEELIT_CONDITION + +#define FEEL_TEXTURE FEELIT_TEXTURE +#define LPFEEL_TEXTURE LPFEELIT_TEXTURE +#define LPCFEEL_TEXTURE LPCFEELIT_TEXTURE + +#define FEEL_PERIODIC FEELIT_PERIODIC +#define LPFEEL_PERIODIC LPFEELIT_PERIODIC +#define LPCFEEL_PERIODIC LPCFEELIT_PERIODIC + +#define FEEL_CONSTANTFORCE FEELIT_CONSTANTFORCE +#define LPFEEL_CONSTANTFORCE LPFEELIT_CONSTANTFORCE +#define LPCFEEL_CONSTANTFORCE LPCFEELIT_CONSTANTFORCE + +#define FEEL_RAMPFORCE FEELIT_RAMPFORCE +#define LPFEEL_RAMPFORCE LPFEELIT_RAMPFORCE +#define LPCFEEL_RAMPFORCE LPCFEELIT_RAMPFORCE + +#define FEEL_ENVELOPE FEELIT_ENVELOPE +#define LPFEEL_ENVELOPE LPFEELIT_ENVELOPE +#define LPCFEEL_ENVELOPE LPCFEELIT_ENVELOPE + +#define LPIFEEL_API LPIFEELIT +#define LPIFEEL_EFFECT LPIFEELIT_EFFECT +#define LPIFEEL_DEVICE LPIFEELIT_DEVICE + +#define LPFEEL_DEVICEINSTANCE LPFEELIT_DEVICEINSTANCE +#define LPCFEEL_DEVICEOBJECTINSTANCE LPCFEELIT_DEVICEOBJECTINSTANCE +#define LPCFEEL_EFFECTINFO LPCFEELIT_EFFECTINFO + + +#define FEEL_FPARAM_DURATION FEELIT_FPARAM_DURATION +#define FEEL_FPARAM_SAMPLEPERIOD FEELIT_FPARAM_SAMPLEPERIOD +#define FEEL_FPARAM_GAIN FEELIT_FPARAM_GAIN +#define FEEL_FPARAM_TRIGGERBUTTON FEELIT_FPARAM_TRIGGERBUTTON +#define FEEL_FPARAM_TRIGGERREPEATINTERVAL FEELIT_FPARAM_TRIGGERREPEATINTERVAL +#define FEEL_FPARAM_AXES FEELIT_FPARAM_AXES +#define FEEL_FPARAM_DIRECTION FEELIT_FPARAM_DIRECTION +#define FEEL_FPARAM_ENVELOPE FEELIT_FPARAM_ENVELOPE +#define FEEL_FPARAM_TYPESPECIFICPARAMS FEELIT_FPARAM_TYPESPECIFICPARAMS +#define FEEL_FPARAM_ALLPARAMS FEELIT_FPARAM_ALLPARAMS +#define FEEL_FPARAM_START FEELIT_FPARAM_START +#define FEEL_FPARAM_NORESTART FEELIT_FPARAM_NORESTART +#define FEEL_FPARAM_NODOWNLOAD FEELIT_FPARAM_NODOWNLOAD + +#define FEEL_FEFFECT_OBJECTIDS FEELIT_FEFFECT_OBJECTIDS +#define FEEL_FEFFECT_OBJECTOFFSETS FEELIT_FEFFECT_OBJECTOFFSETS +#define FEEL_FEFFECT_CARTESIAN FEELIT_FEFFECT_CARTESIAN +#define FEEL_FEFFECT_POLAR FEELIT_FEFFECT_POLAR +#define FEEL_FEFFECT_SPHERICAL FEELIT_FEFFECT_SPHERICAL + +#define FEEL_PARAM_NOTRIGGER FEELIT_PARAM_NOTRIGGER + +#define FEEL_MOUSEOFFSET_XAXIS FEELIT_MOUSEOFFSET_XAXIS +#define FEEL_MOUSEOFFSET_YAXIS FEELIT_MOUSEOFFSET_YAXIS +#define FEEL_MOUSEOFFSET_ZAXIS FEELIT_MOUSEOFFSET_ZAXIS + +// +// FORCE --> FEELIT Wrappers +// +#define FORCE_EFFECT FEELIT_EFFECT +#define LPFORCE_EFFECT LPFEELIT_EFFECT +#define LPCFORCE_EFFECT LPCFEELIT_EFFECT + +#define FORCE_CONDITION FEELIT_CONDITION +#define LPFORCE_CONDITION LPFEELIT_CONDITION +#define LPCFORCE_CONDITION LPCFEELIT_CONDITION + +#define FORCE_TEXTURE FEELIT_TEXTURE +#define LPFORCE_TEXTURE LPFEELIT_TEXTURE +#define LPCFORCE_TEXTURE LPCFEELIT_TEXTURE + +#define FORCE_PERIODIC FEELIT_PERIODIC +#define LPFORCE_PERIODIC LPFEELIT_PERIODIC +#define LPCFORCE_PERIODIC LPCFEELIT_PERIODIC + +#define FORCE_CONSTANTFORCE FEELIT_CONSTANTFORCE +#define LPFORCE_CONSTANTFORCE LPFEELIT_CONSTANTFORCE +#define LPCFORCE_CONSTANTFORCE LPCFEELIT_CONSTANTFORCE + +#define FORCE_RAMPFORCE FEELIT_RAMPFORCE +#define LPFORCE_RAMPFORCE LPFEELIT_RAMPFORCE +#define LPCFORCE_RAMPFORCE LPCFEELIT_RAMPFORCE + +#define FORCE_ENVELOPE FEELIT_ENVELOPE +#define LPFORCE_ENVELOPE LPFEELIT_ENVELOPE +#define LPCFORCE_ENVELOPE LPCFEELIT_ENVELOPE + +#define LPIFORCE_API LPIFEELIT +#define LPIFORCE_EFFECT LPIFEELIT_EFFECT +#define LPIFORCE_DEVICE LPIFEELIT_DEVICE + +#define LPFORCE_DEVICEINSTANCE LPFEELIT_DEVICEINSTANCE +#define LPCFORCE_DEVICEOBJECTINSTANCE LPCFEELIT_DEVICEOBJECTINSTANCE +#define LPCFORCE_EFFECTINFO LPCFEELIT_EFFECTINFO + + +#define FORCE_FPARAM_DURATION FEELIT_FPARAM_DURATION +#define FORCE_FPARAM_SAMPLEPERIOD FEELIT_FPARAM_SAMPLEPERIOD +#define FORCE_FPARAM_GAIN FEELIT_FPARAM_GAIN +#define FORCE_FPARAM_TRIGGERBUTTON FEELIT_FPARAM_TRIGGERBUTTON +#define FORCE_FPARAM_TRIGGERREPEATINTERVAL FEELIT_FPARAM_TRIGGERREPEATINTERVAL +#define FORCE_FPARAM_AXES FEELIT_FPARAM_AXES +#define FORCE_FPARAM_DIRECTION FEELIT_FPARAM_DIRECTION +#define FORCE_FPARAM_ENVELOPE FEELIT_FPARAM_ENVELOPE +#define FORCE_FPARAM_TYPESPECIFICPARAMS FEELIT_FPARAM_TYPESPECIFICPARAMS +#define FORCE_FPARAM_ALLPARAMS FEELIT_FPARAM_ALLPARAMS +#define FORCE_FPARAM_START FEELIT_FPARAM_START +#define FORCE_FPARAM_NORESTART FEELIT_FPARAM_NORESTART +#define FORCE_FPARAM_NODOWNLOAD FEELIT_FPARAM_NODOWNLOAD + +#define FORCE_FEFFECT_OBJECTIDS FEELIT_FEFFECT_OBJECTIDS +#define FORCE_FEFFECT_OBJECTOFFSETS FEELIT_FEFFECT_OBJECTOFFSETS +#define FORCE_FEFFECT_CARTESIAN FEELIT_FEFFECT_CARTESIAN +#define FORCE_FEFFECT_POLAR FEELIT_FEFFECT_POLAR +#define FORCE_FEFFECT_SPHERICAL FEELIT_FEFFECT_SPHERICAL + +#define FORCE_PARAM_NOTRIGGER FEELIT_PARAM_NOTRIGGER + +#define FORCE_MOUSEOFFSET_XAXIS FEELIT_MOUSEOFFSET_XAXIS +#define FORCE_MOUSEOFFSET_YAXIS FEELIT_MOUSEOFFSET_YAXIS +#define FORCE_MOUSEOFFSET_ZAXIS FEELIT_MOUSEOFFSET_ZAXIS + + +//================================================================ +// GUID WRAPPERS +//================================================================ + +// +// Feel --> Feelit Wrappers +// +#define GUID_Feel_ConstantForce GUID_Feelit_ConstantForce +#define GUID_Feel_RampForce GUID_Feelit_RampForce +#define GUID_Feel_Square GUID_Feelit_Square +#define GUID_Feel_Sine GUID_Feelit_Sine +#define GUID_Feel_Triangle GUID_Feelit_Triangle +#define GUID_Feel_SawtoothUp GUID_Feelit_SawtoothUp +#define GUID_Feel_SawtoothDown GUID_Feelit_SawtoothDown +#define GUID_Feel_Spring GUID_Feelit_Spring +#define GUID_Feel_DeviceSpring GUID_Feelit_DeviceSpring +#define GUID_Feel_Damper GUID_Feelit_Damper +#define GUID_Feel_Inertia GUID_Feelit_Inertia +#define GUID_Feel_Friction GUID_Feelit_Friction +#define GUID_Feel_Texture GUID_Feelit_Texture +#define GUID_Feel_Grid GUID_Feelit_Grid +#define GUID_Feel_Enclosure GUID_Feelit_Enclosure +#define GUID_Feel_Ellipse GUID_Feelit_Ellipse +#define GUID_Feel_CustomForce GUID_Feelit_CustomForce + +// +// Force --> Feelit Wrappers +// +#define GUID_Force_ConstantForce GUID_Feelit_ConstantForce +#define GUID_Force_RampForce GUID_Feelit_RampForce +#define GUID_Force_Square GUID_Feelit_Square +#define GUID_Force_Sine GUID_Feelit_Sine +#define GUID_Force_Triangle GUID_Feelit_Triangle +#define GUID_Force_SawtoothUp GUID_Feelit_SawtoothUp +#define GUID_Force_SawtoothDown GUID_Feelit_SawtoothDown +#define GUID_Force_Spring GUID_Feelit_Spring +#define GUID_Force_Damper GUID_Feelit_Damper +#define GUID_Force_Inertia GUID_Feelit_Inertia +#define GUID_Force_Friction GUID_Feelit_Friction +#define GUID_Force_Texture GUID_Feelit_Texture +#define GUID_Force_Grid GUID_Feelit_Grid +#define GUID_Force_Enclosure GUID_Feelit_Enclosure +#define GUID_Force_Ellipse GUID_Feelit_Ellipse +#define GUID_Force_CustomForce GUID_Feelit_CustomForce + + +#endif // !defined(AFX_FEELBASETYPES_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) + + + + + + + + + + + + + + + + + diff --git a/code/win32/FeelIt/FeelBox.h b/code/win32/FeelIt/FeelBox.h new file mode 100644 index 0000000..0ac5582 --- /dev/null +++ b/code/win32/FeelIt/FeelBox.h @@ -0,0 +1,178 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelBox.h + + PURPOSE: Box Class for Feelit API Foundation Classes + + STARTED: 11/04/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELBOX_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELBOX_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelBaseTypes.h" +#include "FeelEffect.h" +#include "FeelEnclosure.h" + + + +//================================================================ +// Constants +//================================================================ + +const POINT FEEL_BOX_MOUSE_POS_AT_START = { MAXLONG, MAXLONG }; + +#define FEEL_BOX_DEFAULT_STIFFNESS 5000 +#define FEEL_BOX_DEFAULT_WIDTH 10 +#define FEEL_BOX_DEFAULT_HEIGHT FEEL_ENCLOSURE_HEIGHT_AUTO +#define FEEL_BOX_DEFAULT_WALL_WIDTH FEEL_ENCLOSURE_WALL_WIDTH_AUTO + +#define FEEL_BOX_DEFAULT_CENTER_POINT FEEL_BOX_MOUSE_POS_AT_START + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_BOX_MOUSE_POS_AT_START FEEL_BOX_DEFAULT_STIFFNESS + +#define FORCE_BOX_DEFAULT_STIFFNESS FEEL_BOX_DEFAULT_STIFFNESS +#define FORCE_BOX_DEFAULT_WIDTH FEEL_BOX_DEFAULT_WIDTH +#define FORCE_BOX_DEFAULT_HEIGHT FEEL_BOX_DEFAULT_HEIGHT +#define FORCE_BOX_DEFAULT_WALL_WIDTH FEEL_BOX_DEFAULT_WALL_WIDTH + +#define FORCE_BOX_DEFAULT_CENTER_POINT FEEL_BOX_DEFAULT_CENTER_POINT + + + +//================================================================ +// CFeelBox +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelBox : public CFeelEnclosure +{ + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CFeelBox(); + + // Destructor + virtual + ~CFeelBox(); + + + // + // ATTRIBUTES + // + + public: + + + BOOL + ChangeParameters( + POINT pntCenter, + LONG lStiffness = FEEL_EFFECT_DONT_CHANGE, + DWORD dwWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwHeight = FEEL_EFFECT_DONT_CHANGE, + DWORD dwWallWidth = FEEL_EFFECT_DONT_CHANGE, + CFeelEffect* pInsideEffect = (CFeelEffect*) FEEL_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParameters( + LPCRECT pRectOutside, + LONG lStiffness = FEEL_EFFECT_DONT_CHANGE, + DWORD dwWallWidth = FEEL_EFFECT_DONT_CHANGE, + CFeelEffect* pInsideEffect = (CFeelEffect*) FEEL_EFFECT_DONT_CHANGE + ); + + + // + // OPERATIONS + // + + public: + + + BOOL + Initialize( + CFeelDevice* pDevice, + DWORD dwWidth = FEEL_ENCLOSURE_DEFAULT_WIDTH, + DWORD dwHeight = FEEL_ENCLOSURE_DEFAULT_HEIGHT, + LONG lStiffness = FEEL_BOX_DEFAULT_STIFFNESS, + DWORD dwWallWidth = FEEL_BOX_DEFAULT_WALL_WIDTH, + POINT pntCenter = FEEL_BOX_DEFAULT_CENTER_POINT, + CFeelEffect* pInsideEffect = NULL + ); + + + BOOL + Initialize( + CFeelDevice* pDevice, + LPCRECT pRectOutside, + LONG lStiffness = FEEL_BOX_DEFAULT_STIFFNESS, + DWORD dwWallWidth = FEEL_BOX_DEFAULT_WALL_WIDTH, + CFeelEffect* pInsideEffect = NULL + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + + // + // INTERNAL DATA + // + + protected: + +}; + + +#endif // !defined(AFX_FEELBOX_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelCompoundEffect.h b/code/win32/FeelIt/FeelCompoundEffect.h new file mode 100644 index 0000000..319b3d6 --- /dev/null +++ b/code/win32/FeelIt/FeelCompoundEffect.h @@ -0,0 +1,184 @@ +/********************************************************************** + Copyright (c) 1999 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelCompoundEffect.h + + PURPOSE: Manages Compound Effects for Force Foundation Classes + + STARTED: 2/24/99 by Jeff Mallett + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(__FEELCOMPOUNDEFFECT_H) +#define __FEELCOMPOUNDEFFECT_H + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelBaseTypes.h" +#include "FeelEffect.h" + +#include "FeelitIFR.h" + + +//================================================================ +// CFeelCompoundEffect +//================================================================ +// Represents a compound effect, such as might be created in +// I-FORCE Studio. Contains an array of effect objects. +// Methods iterate over component effects, passing the message +// to each one. +// Also, has stuff for being used by CFeelProject: +// * next pointer so can be put on a linked list +// * force name + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelCompoundEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + protected: + // Constructs a CFeelCompoundEffect + // Don't try to construct a CFeelCompoundEffect yourself. + // Instead let CFeelProject construct it for you. + CFeelCompoundEffect( + IFREffect **hEffects, + long nEffects + ); + + public: + + ~CFeelCompoundEffect(); + + + // + // ATTRIBUTES + // + + public: + + long + GetNumberOfContainedEffects() const + { return m_nEffects; } + + const char * + GetName() const + { return m_lpszName; } + + GENERIC_EFFECT_PTR + GetContainedEffect( + long index + ); + + + // + // OPERATIONS + // + + public: + + // Start all the contained effects + BOOL Start( + DWORD dwIterations = 1, + DWORD dwFlags = 0 + ); + + // Stop all the contained effects + BOOL Stop(); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL initialize( + CFeelDevice* pDevice, + IFREffect **hEffects + ); + + BOOL + set_contained_effect( + GENERIC_EFFECT_PTR pObject, + int index = 0 + ); + + BOOL + set_name( + const char *lpszName + ); + + void + set_next( + CFeelCompoundEffect *pNext + ) + { m_pNext = pNext; } + + CFeelCompoundEffect * + get_next() const + { return m_pNext; } + + + // + // FRIENDS + // + + public: + + friend class CFeelProject; + + + // + // INTERNAL DATA + // + + protected: + + GENERIC_EFFECT_PTR *m_paEffects; // Array of force class object pointers + long m_nEffects; // Number of effects in m_paEffects + + private: + + char *m_lpszName; // Name of the compound effect + CFeelCompoundEffect *m_pNext; // Next compound effect in the project +}; + +#endif diff --git a/code/win32/FeelIt/FeelCondition.h b/code/win32/FeelIt/FeelCondition.h new file mode 100644 index 0000000..cbbb42c --- /dev/null +++ b/code/win32/FeelIt/FeelCondition.h @@ -0,0 +1,345 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelCondition.h + + PURPOSE: Base Condition Class for Feelit API Foundation Classes + + STARTED: 10/10/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELCONDITION_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELCONDITION_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelBaseTypes.h" +#include "FeelEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +const POINT FEEL_CONDITION_PT_NULL = { 0, 0 }; + +#define FEEL_CONDITION_DEFAULT_COEFFICIENT 2500 +#define FEEL_CONDITION_DEFAULT_SATURATION 10000 +#define FEEL_CONDITION_DEFAULT_DEADBAND 100 +#define FEEL_CONDITION_DEFAULT_CENTER_POINT FEEL_EFFECT_MOUSE_POS_AT_START +#define FEEL_CONDITION_DEFAULT_DURATION INFINITE + +typedef enum { + FC_NULL = 0, + FC_POSITIVE_COEFFICIENT, + FC_NEGATIVE_COEFFICIENT, + FC_POSITIVE_SATURATION, + FC_NEGATIVE_SATURATION, + FC_DEAD_BAND, + FC_AXIS, + FC_CENTER, + FC_DIRECTION_X, + FC_DIRECTION_Y, + FC_ANGLE, + FC_CONDITION_X, + FC_CONDITION_Y +} FC_ArgumentType; + +#define FC_CONDITION FC_CONDITION_X + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_CONDITION_PT_NULL FEEL_CONDITION_PT_NULL + +#define FORCE_CONDITION_DEFAULT_COEFFICIENT FEEL_CONDITION_DEFAULT_COEFFICIENT +#define FORCE_CONDITION_DEFAULT_SATURATION FEEL_CONDITION_DEFAULT_SATURATION +#define FORCE_CONDITION_DEFAULT_DEADBAND FEEL_CONDITION_DEFAULT_DEADBAND +#define FORCE_CONDITION_DEFAULT_CENTER_POINT FEEL_CONDITION_DEFAULT_CENTER_POINT +#define FORCE_CONDITION_DEFAULT_DURATION FEEL_CONDITION_DEFAULT_DURATION + + + +//================================================================ +// CFeelCondition +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelCondition : public CFeelEffect +{ + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CFeelCondition( + const GUID& rguidEffect + ); + + // Destructor + virtual + ~CFeelCondition(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + // Use this form for single-axis and dual-axis effects + BOOL + ChangeConditionParams( + LPCFEELIT_CONDITION pConditionX, + LPCFEELIT_CONDITION pConditionY + ); + + // Use this form for directional effects + BOOL + ChangeConditionParams( + LPCFEELIT_CONDITION pCondition, + LONG lDirectionX, + LONG lDirectionY + ); + + // Use this form for directional effects + BOOL + ChangeConditionParamsPolar( + LPCFEELIT_CONDITION pCondition, + LONG lAngle + ); + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + ChangeConditionParams( + LONG lPositiveCoefficient, + LONG lNegativeCoefficient = FEEL_EFFECT_DONT_CHANGE, + DWORD dwPositiveSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwNegativeSaturation = FEEL_EFFECT_DONT_CHANGE, + LONG lDeadBand = FEEL_EFFECT_DONT_CHANGE, + POINT pntCenter = FEEL_EFFECT_DONT_CHANGE_POINT, + LONG lDirectionX = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionY = FEEL_EFFECT_DONT_CHANGE + ); + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + ChangeConditionParamsPolar( + LONG lPositiveCoefficient, + LONG lNegativeCoefficient, + DWORD dwPositiveSaturation, + DWORD dwNegativeSaturation, + LONG lDeadBand, + POINT pntCenter, + LONG lAngle + ); + + + BOOL + SetCenter( + POINT pntCenter + ); + + BOOL + ChangeConditionParams2( + FC_ArgumentType type, + ... + ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CFeelDevice* pDevice, + const FEEL_EFFECT &effect + ); + + // Use this form for single-axis and dual-axis effects + BOOL + InitCondition( + CFeelDevice* pDevice, + LPCFEELIT_CONDITION pConditionX, + LPCFEELIT_CONDITION pConditionY, + BOOL bUseDeviceCoordinates = FALSE + ); + + + // Use this form for directional effects + BOOL + InitCondition( + CFeelDevice* pDevice, + LPCFEELIT_CONDITION pCondition, + LONG lDirectionX, + LONG lDirectionY, + BOOL bUseDeviceCoordinates = FALSE + ); + + + // Use this form for directional effects + BOOL + InitConditionPolar( + CFeelDevice* pDevice, + LPCFEELIT_CONDITION pCondition, + LONG lAngle, + BOOL bUseDeviceCoordinates = FALSE + ); + + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + InitCondition( + CFeelDevice* pDevice, + LONG lPositiveCoefficient = FEEL_CONDITION_DEFAULT_COEFFICIENT, + LONG lNegativeCoefficient = FEEL_CONDITION_DEFAULT_COEFFICIENT, + DWORD dwPositiveSaturation = FEEL_CONDITION_DEFAULT_SATURATION, + DWORD dwNegativeSaturation = FEEL_CONDITION_DEFAULT_SATURATION, + LONG lDeadBand = FEEL_CONDITION_DEFAULT_DEADBAND, + DWORD dwfAxis = FEEL_EFFECT_AXIS_BOTH, + POINT pntCenter = FEEL_CONDITION_DEFAULT_CENTER_POINT, + LONG lDirectionX = FEEL_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = FEEL_EFFECT_DEFAULT_DIRECTION_Y, + BOOL bUseDeviceCoordinates = FALSE + ); + + // Use this form for directional effects + BOOL + InitConditionPolar( + CFeelDevice* pDevice, + LONG lPositiveCoefficient = FEEL_CONDITION_DEFAULT_COEFFICIENT, + LONG lNegativeCoefficient = FEEL_CONDITION_DEFAULT_COEFFICIENT, + DWORD dwPositiveSaturation = FEEL_CONDITION_DEFAULT_SATURATION, + DWORD dwNegativeSaturation = FEEL_CONDITION_DEFAULT_SATURATION, + LONG lDeadBand = FEEL_CONDITION_DEFAULT_DEADBAND, + POINT pntCenter = FEEL_CONDITION_DEFAULT_CENTER_POINT, + LONG lAngle = FEEL_EFFECT_DEFAULT_ANGLE, + BOOL bUseDeviceCoordinates = FALSE + ); + + + virtual BOOL +#ifdef FFC_START_DELAY + StartNow( +#else + Start( +#endif + DWORD dwIterations = 1, + DWORD dwFlags = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + void + convert_line_point_to_offset( + POINT pntOnLine + ); + + BOOL + set_parameters( + DWORD dwfAxis, + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + LPCFEELIT_CONDITION pConditionX, + LPCFEELIT_CONDITION pConditionY + ); + + BOOL + set_parameters( + DWORD dwfAxis, + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + LONG lPositiveCoefficient, + LONG lNegativeCoefficient, + DWORD dwPositiveSaturation, + DWORD dwNegativeSaturation, + LONG lDeadBand, + POINT pntCenter + ); + + // + // INTERNAL DATA + // + + FEEL_CONDITION m_aCondition[2]; + DWORD m_dwfAxis; + BOOL m_bUseMousePosAtStart; + + protected: + BOOL m_bUseDeviceCoordinates; + +}; + + +// +// INLINES +// + +inline BOOL +CFeelCondition::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_Spring) || + IsEqualGUID(guid, GUID_Feel_DeviceSpring) || + IsEqualGUID(guid, GUID_Feel_Damper) || + IsEqualGUID(guid, GUID_Feel_Inertia) || + IsEqualGUID(guid, GUID_Feel_Friction) || + IsEqualGUID(guid, GUID_Feel_Texture) || + IsEqualGUID(guid, GUID_Feel_Grid); +} + +#endif // !defined(AFX_FEELCONDITION_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelConstant.h b/code/win32/FeelIt/FeelConstant.h new file mode 100644 index 0000000..6d5e846 --- /dev/null +++ b/code/win32/FeelIt/FeelConstant.h @@ -0,0 +1,193 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelConstant.h + + PURPOSE: Base Constant Class for Feelit API Foundation Classes + + STARTED: 11/03/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELCONSTANT_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELCONSTANT_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelBaseTypes.h" +#include "FeelEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +const POINT FEEL_CONSTANT_DEFAULT_DIRECTION = { 1, 0 }; +#define FEEL_CONSTANT_DEFAULT_DURATION 1000 // Milliseconds +#define FEEL_CONSTANT_DEFAULT_MAGNITUDE 5000 + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_CONSTANT_DEFAULT_DIRECTION FEEL_CONSTANT_DEFAULT_DIRECTION +#define FORCE_CONSTANT_DEFAULT_DURATION FEEL_CONSTANT_DEFAULT_DURATION +#define FORCE_CONSTANT_DEFAULT_MAGNITUDE FEEL_CONSTANT_DEFAULT_MAGNITUDE + + +//================================================================ +// CFeelConstant +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelConstant : public CFeelEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CFeelConstant(); + + // Destructor + virtual + ~CFeelConstant(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + LONG lDirectionX, + LONG lDirectionY, + DWORD dwDuration = FEEL_EFFECT_DONT_CHANGE, + LONG lMagnitude = FEEL_EFFECT_DONT_CHANGE, + LPFEEL_ENVELOPE pEnvelope = (LPFEEL_ENVELOPE) FEEL_EFFECT_DONT_CHANGE_PTR + ); + + BOOL + ChangeParametersPolar( + LONG lAngle, + DWORD dwDuration = FEEL_EFFECT_DONT_CHANGE, + LONG lMagnitude = FEEL_EFFECT_DONT_CHANGE, + LPFEEL_ENVELOPE pEnvelope = (LPFEEL_ENVELOPE) FEEL_EFFECT_DONT_CHANGE_PTR + ); + + + // + // OPERATIONS + // + + public: + + virtual + BOOL + Initialize( + CFeelDevice* pDevice, + LONG lDirectionX = FEEL_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = FEEL_EFFECT_DEFAULT_DIRECTION_Y, + DWORD dwDuration = FEEL_CONSTANT_DEFAULT_DURATION, + LONG lMagnitude = FEEL_CONSTANT_DEFAULT_MAGNITUDE, + LPFEEL_ENVELOPE pEnvelope = NULL + ); + + virtual + BOOL + InitializePolar( + CFeelDevice* pDevice, + LONG lArray = FEEL_EFFECT_DEFAULT_ANGLE, + DWORD dwDuration = FEEL_CONSTANT_DEFAULT_DURATION, + LONG lMagnitude = FEEL_CONSTANT_DEFAULT_MAGNITUDE, + LPFEEL_ENVELOPE pEnvelope = NULL + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + DWORD dwDuration, + LONG lMagnitude, + LPFEEL_ENVELOPE pEnvelope + ); + + + // + // INTERNAL DATA + // + + FEEL_CONSTANTFORCE m_ConstantForce; + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CFeelConstant::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_ConstantForce); +} + + +#endif // !defined(AFX_FEELCONSTANT_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelDXDevice.h b/code/win32/FeelIt/FeelDXDevice.h new file mode 100644 index 0000000..cc1e442 --- /dev/null +++ b/code/win32/FeelIt/FeelDXDevice.h @@ -0,0 +1,126 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelDXDevice.h + + PURPOSE: Abstraction of DirectX Force Feedback device + + STARTED: 10/10/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + +#ifndef FeelDXDevice_h +#define FeelDXDevice_h + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelDevice.h" + + +//================================================================ +// CFeelDXDevice +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelDXDevice : public CFeelDevice +{ + + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CFeelDXDevice(); + + // Destructor + virtual + ~CFeelDXDevice(); + + + // + // ATTRIBUTES + // + + public: + + virtual LPIFEEL_API + GetAPI() + { return (LPIFEEL_API) m_piApi; } // actually LPDIRECTINPUT + + virtual LPIFEEL_DEVICE + GetDevice() + { return (LPIFEEL_DEVICE) m_piDevice; } // actually LPDIRECTINPUTDEVICE2 + + + // + // OPERATIONS + // + + public: + + BOOL + Initialize( + HANDLE hinstApp, + HANDLE hwndApp, + LPDIRECTINPUT pDI = NULL, + LPDIRECTINPUTDEVICE2 piDevice = NULL + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + virtual void + reset(); + + + // + // INTERNAL DATA + // + + protected: + + BOOL m_bpDIPreExist; + BOOL m_bpDIDevicePreExist; + + LPDIRECTINPUT m_piApi; + LPDIRECTINPUTDEVICE2 m_piDevice; +}; + +#endif // ForceDXDevice_h diff --git a/code/win32/FeelIt/FeelDamper.h b/code/win32/FeelIt/FeelDamper.h new file mode 100644 index 0000000..7c454d8 --- /dev/null +++ b/code/win32/FeelIt/FeelDamper.h @@ -0,0 +1,177 @@ +/********************************************************************** + Copyright (c) 1997,8,9 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelDamper.h + + PURPOSE: Feelit API Damper Effect Class + + STARTED: 10/14/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELDamper_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELDamper_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include +#include "FeelCondition.h" + + +//================================================================ +// Constants +//================================================================ + +#define FEEL_DAMPER_DEFAULT_VISCOSITY 2500 +#define FEEL_DAMPER_DEFAULT_SATURATION 10000 +#define FEEL_DAMPER_DEFAULT_MIN_VELOCITY 0 + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_DAMPER_DEFAULT_VISCOSITY FEEL_DAMPER_DEFAULT_VISCOSITY +#define FORCE_DAMPER_DEFAULT_SATURATION FEEL_DAMPER_DEFAULT_SATURATION +#define FORCE_DAMPER_DEFAULT_MIN_VELOCITY FEEL_DAMPER_DEFAULT_MIN_VELOCITY + + +//================================================================ +// CFeelDamper +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelDamper : public CFeelCondition +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CFeelDamper(); + + // Destructor + virtual ~CFeelDamper(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + DWORD dwViscosity, + DWORD dwSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwMinVelocity = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionX = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionY = FEEL_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParametersPolar( + DWORD dwViscosity, + DWORD dwSaturation, + DWORD dwMinVelocity, + LONG lAngle + ); + + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CFeelDevice* pDevice, + DWORD dwViscosity = FEEL_DAMPER_DEFAULT_VISCOSITY, + DWORD dwSaturation = FEEL_DAMPER_DEFAULT_SATURATION, + DWORD dwMinVelocity = FEEL_DAMPER_DEFAULT_MIN_VELOCITY, + DWORD dwfAxis = FEEL_EFFECT_AXIS_BOTH, + LONG lDirectionX = FEEL_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = FEEL_EFFECT_DEFAULT_DIRECTION_Y + ); + + virtual BOOL + InitializePolar( + CFeelDevice* pDevice, + DWORD dwViscosity, + DWORD dwSaturation, + DWORD dwMinVelocity, + LONG lAngle + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CFeelDamper::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_Damper); +} + + +#endif // !defined(AFX_FEELDamper_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelDevice.h b/code/win32/FeelIt/FeelDevice.h new file mode 100644 index 0000000..4b8d551 --- /dev/null +++ b/code/win32/FeelIt/FeelDevice.h @@ -0,0 +1,196 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelDevice.h + + PURPOSE: Abstract Base Device Class for Force Foundation Classes + + STARTED: 10/10/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + 3/16/99 jrm: Made abstract. Moved functionality to CFeelMouse/CFeelDXDevice + +**********************************************************************/ + +#if !defined(AFX_FORCEDEVICE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FORCEDEVICE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#define DIRECTINPUT_VERSION 0x0800 //[ 0x0300 | 0x0500 | 0x0700 | 0x0800 ] +#include "dinput.h" +#include "FeelBaseTypes.h" + +#ifdef FFC_EFFECT_CACHING + #include "FeelEffectSuite.h" +#endif + + + +//================================================================ +// CFeelDevice +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelDevice +{ + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CFeelDevice(); + + // Destructor + virtual + ~CFeelDevice(); + + + // + // ATTRIBUTES + // + + public: + + virtual LPIFEEL_API + GetAPI() + = 0; // pure virtual function + + virtual LPIFEEL_DEVICE // Will actually return LPDIRECTINPUTDEVICE2 if non-FEELit + GetDevice() + = 0; // pure virtual function + + DWORD + GetDeviceType() const + { return m_dwDeviceType; } + + + // + // OPERATIONS + // + + public: + + static CFeelDevice * + CreateDevice(HINSTANCE hinstApp, HWND hwndApp); + + virtual BOOL + ChangeScreenResolution( + BOOL bAutoSet, + DWORD dwXScreenSize = 0, + DWORD dwYScreenSize = 0 + ); + + // The default state is using standard Win32 Mouse messages (e.g., WM_MOUSEMOVE) + // and functions (e.g, GetCursorPos). Call only to switch to relative mode + // if not using standard Win32 Mouse services (e.g., DirectInput) for mouse + // input. + BOOL + UsesWin32MouseServices( + BOOL bWin32MouseServ + ); + + // Another syntax for SwitchToAbsoluteMode. + // The default is Absolute mode. Call only to switch to Relative mode or + // to switch back to Absolute mode. + virtual BOOL + SwitchToAbsoluteMode( + BOOL bAbsMode + ); + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // CACHING + // + +#ifdef FFC_EFFECT_CACHING + public: + + void Cache_AddEffect(CFeelEffect *pFeelEffect); + void Cache_RemoveEffect(const CFeelEffect *pFeelEffect); + void Cache_SwapOutEffect(); + + protected: + + void Cache_LoadEffectSuite(CFeelEffectSuite *pSuite, BOOL bCreateOnDevice); + void Cache_UnloadEffectSuite(CFeelEffectSuite *pSuite, BOOL bUnloadFromDevice); + + CEffectList m_Cache; // List of all effects created on device +#endif + + // + // HELPERS + // + + protected: + + // Performs device preparation by setting the device's parameters + virtual BOOL + prepare_device(); + + virtual void + reset() + = 0; // pure virtual function + + static BOOL CALLBACK + enum_didevices_proc( + LPDIDEVICEINSTANCE pForceDevInst, + LPVOID pv + ); + + static BOOL CALLBACK + enum_devices_proc( + LPFORCE_DEVICEINSTANCE pForceDevInst, + LPVOID pv + ); + + + // + // INTERNAL DATA + // + + protected: + + BOOL m_bInitialized; + DWORD m_dwDeviceType; + GUID m_guidDevice; + BOOL m_bGuidValid; + +}; + + +#endif // !defined(AFX_FORCEDEVICE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelEffect.h b/code/win32/FeelIt/FeelEffect.h new file mode 100644 index 0000000..58ebf00 --- /dev/null +++ b/code/win32/FeelIt/FeelEffect.h @@ -0,0 +1,344 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelEffect.h + + PURPOSE: Base Effect Class for Feelit API Foundation Classes + + STARTED: 10/10/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID and feel_to_DI_GUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELEFFECT_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELEFFECT_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelBaseTypes.h" +#include "FeelDevice.h" +class CFeelProject; + + +//================================================================ +// Constants +//================================================================ + +#define FEEL_EFFECT_AXIS_X 1 +#define FEEL_EFFECT_AXIS_Y 2 +#define FEEL_EFFECT_AXIS_BOTH 3 +#define FEEL_EFFECT_AXIS_DIRECTIONAL 4 +#define FEEL_EFFECT_DONT_CHANGE MINLONG +#define FEEL_EFFECT_DONT_CHANGE_PTR MAXDWORD +const POINT FEEL_EFFECT_DONT_CHANGE_POINT = { 0xFFFFFFFF, 0xFFFFFFFF }; +const POINT FEEL_EFFECT_MOUSE_POS_AT_START = { MAXLONG, MAXLONG }; + +#define FEEL_EFFECT_DEFAULT_ENVELOPE NULL +#define FEEL_EFFECT_DEFAULT_DIRECTION_X 1 +#define FEEL_EFFECT_DEFAULT_DIRECTION_Y 1 +#define FEEL_EFFECT_DEFAULT_ANGLE 0 + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_EFFECT_AXIS_X FEEL_EFFECT_AXIS_X +#define FORCE_EFFECT_AXIS_Y FEEL_EFFECT_AXIS_Y +#define FORCE_EFFECT_AXIS_BOTH FEEL_EFFECT_AXIS_BOTH +#define FORCE_EFFECT_AXIS_DIRECTIONAL FEEL_EFFECT_AXIS_DIRECTIONAL +#define FORCE_EFFECT_DONT_CHANGE FEEL_EFFECT_DONT_CHANGE +#define FORCE_EFFECT_DONT_CHANGE_PTR FEEL_EFFECT_DONT_CHANGE_PTR +#define FORCE_EFFECT_DONT_CHANGE_POINT FEEL_EFFECT_DONT_CHANGE_POINT +#define FORCE_EFFECT_MOUSE_POS_AT_START FEEL_EFFECT_MOUSE_POS_AT_START + +#define FORCE_EFFECT_DEFAULT_ENVELOPE FEEL_EFFECT_DEFAULT_ENVELOPE +#define FORCE_EFFECT_DEFAULT_DIRECTION_X FEEL_EFFECT_DEFAULT_DIRECTION_X +#define FORCE_EFFECT_DEFAULT_DIRECTION_Y FEEL_EFFECT_DEFAULT_DIRECTION_Y +#define FORCE_EFFECT_DEFAULT_ANGLE FEEL_EFFECT_DEFAULT_ANGLE + + + +// GENERIC_EFFECT_PTR +// This is really a pointer to a child of CFeelEffect. +typedef class CFeelEffect * GENERIC_EFFECT_PTR; + + +//================================================================ +// CFeelEffect +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CFeelEffect( + const GUID& rguidEffect + ); + + // Destructor + virtual + ~CFeelEffect(); + + // + // ATTRIBUTES + // + + public: + + LPIFEEL_EFFECT + GetEffect() + { return m_piFeelitEffect; } + + BOOL + GetStatus( + DWORD* pdwStatus + ) +#if (FFC_VERSION >= 0x0110) || defined(FFC_EFFECT_CACHING) + const +#endif + ; + + GUID + GetGUID() + { return m_guidEffect; } + + virtual BOOL + GetIsCompatibleGUID( + GUID & /* guid */ + ) + { return true; } + + // Allocates an object of the correct FFC class from the given GUID + static GENERIC_EFFECT_PTR + NewObjectFromGUID( + GUID &guid + ); + + BOOL + ChangeBaseParams( + LONG lDirectionX, + LONG lDirectionY, + DWORD dwDuration = FEEL_EFFECT_DONT_CHANGE, + LPFEEL_ENVELOPE pEnvelope = (LPFEEL_ENVELOPE) FEEL_EFFECT_DONT_CHANGE_PTR, + DWORD dwSamplePeriod = FEEL_EFFECT_DONT_CHANGE, + DWORD dwGain = FEEL_EFFECT_DONT_CHANGE, + DWORD dwTriggerButton = FEEL_EFFECT_DONT_CHANGE, + DWORD dwTriggerRepeatInterval = FEEL_EFFECT_DONT_CHANGE +#ifdef FFC_START_DELAY + ,DWORD dwStartDelay = FEEL_EFFECT_DONT_CHANGE +#endif + ); + + BOOL + ChangeBaseParamsPolar( + LONG lAngle, + DWORD dwDuration = FEEL_EFFECT_DONT_CHANGE, + LPFEEL_ENVELOPE pEnvelope = (LPFEEL_ENVELOPE) FEEL_EFFECT_DONT_CHANGE_PTR, + DWORD dwSamplePeriod = FEEL_EFFECT_DONT_CHANGE, + DWORD dwGain = FEEL_EFFECT_DONT_CHANGE, + DWORD dwTriggerButton = FEEL_EFFECT_DONT_CHANGE, + DWORD dwTriggerRepeatInterval = FEEL_EFFECT_DONT_CHANGE +#ifdef FFC_START_DELAY + ,DWORD dwStartDelay = FEEL_EFFECT_DONT_CHANGE +#endif + ); + + BOOL + ChangeDirection( + LONG lDirectionX, + LONG lDirectionY + ); + + BOOL + ChangeDirection( + LONG lAngle + ); + + + BOOL + SetEnvelope( + DWORD dwAttackLevel, + DWORD dwAttackTime, + DWORD dwFadeLevel, + DWORD dwFadeTime + ); + + BOOL + SetEnvelope( + LPFEEL_ENVELOPE pEnvelope + ); + + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CFeelDevice* pDevice, + const FEEL_EFFECT &effect + ); + + virtual BOOL + InitializeFromProject( + CFeelProject &project, + LPCSTR lpszEffectName, + CFeelDevice* pDevice = NULL + ); + + virtual BOOL + Start( + DWORD dwIterations = 1, + DWORD dwFlags = 0 + ); + +#ifdef FFC_START_DELAY + virtual BOOL + StartNow( + DWORD dwIterations = 1, + DWORD dwFlags = 0 + ); +#endif + + virtual BOOL + Stop(); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // CACHING + // + +#ifdef FFC_EFFECT_CACHING + + public: + + BOOL GetIsPlaying() const; + BOOL GetIsTriggered() const; + short GetPriority() const { return m_Priority; } + void SetPriority(short priority) { m_Priority = priority; } + HRESULT Unload(); + void Reload(); + + public: + + ECacheState m_CacheState; // effect's status in the cache + BOOL m_bInCurrentSuite; // is the effect in the currently loaded suite? + short m_Priority; // Priority within suite: higher number is higher priority + DWORD m_dwLastStarted; // when last started (0 = never) or when param change made on device + DWORD m_dwLastStopped; // when last stopped (0 = not since last start) + DWORD m_dwLastLoaded; // when last loaded with CFeelEffectSuite::Load or Create + + protected: + + CFeelDevice *m_pFeelDevice; // ### Use instead of m_piFeelitDevice +#endif + + // + // HELPERS + // + protected: + +#ifdef FFC_EFFECT_CACHING + public: // initalize needs to be called by CFeelDevice +#endif + BOOL + initialize( + CFeelDevice* pDevice + ); +#ifdef FFC_EFFECT_CACHING + protected: +#endif + + HRESULT + set_parameters_on_device( + DWORD dwFlags + ); + + void + feel_to_DI_GUID( + GUID &guid + ); + + void + reset(); + + void + reset_effect_struct(); + + + // + // INTERNAL DATA + // + + protected: + + FEEL_EFFECT m_Effect; + DWORD m_dwaAxes[2]; + LONG m_laDirections[2]; + + GUID m_guidEffect; + BOOL m_bIsPlaying; + DWORD m_dwDeviceType; + LPIFEEL_DEVICE m_piFeelitDevice; // Might also be holding LPDIRECTINPUTDEVICE2 + LPIFEEL_EFFECT m_piFeelitEffect; + DWORD m_cAxes; // Number of axes + +#ifdef FFC_START_DELAY + public: + // Prevents access to dangling pointer when this is deleted + // All relevent code may be removed when all hardware and drivers support start delay + CFeelEffect **m_ppTimerRef; // pointer to pointer to this. +#endif +}; + + +#if (DIRECTINPUT_VERSION >= 0x0700) + #define DIRECT_INPUT_STARTDELAY_SUPPORTED TRUE +#else + #define DIRECT_INPUT_STARTDELAY_SUPPORTED FALSE +#endif /* DIRECTINPUT_VERSION >= 0x0700 */ + +#endif // !defined(AFX_FEELEFFECT_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelEllipse.h b/code/win32/FeelIt/FeelEllipse.h new file mode 100644 index 0000000..42c8f85 --- /dev/null +++ b/code/win32/FeelIt/FeelEllipse.h @@ -0,0 +1,253 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelEllipse.h + + PURPOSE: Base Ellipse Class for Feelit API Foundation Classes + + STARTED: 10/29/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELELLIPSE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELELLIPSE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelBaseTypes.h" +#include "FeelEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +#define FEEL_ELLIPSE_DEFAULT_STIFFNESS 5000 +#define FEEL_ELLIPSE_DEFAULT_SATURATION 10000 +#define FEEL_ELLIPSE_DEFAULT_WIDTH 10 +#define FEEL_ELLIPSE_HEIGHT_AUTO MAXDWORD +#define FEEL_ELLIPSE_DEFAULT_HEIGHT FEEL_ELLIPSE_HEIGHT_AUTO +#define FEEL_ELLIPSE_WALL_WIDTH_AUTO MAXDWORD +#define FEEL_ELLIPSE_DEFAULT_WALL_WIDTH FEEL_ELLIPSE_WALL_WIDTH_AUTO +#define FEEL_ELLIPSE_DEFAULT_STIFFNESS_MASK FEELIT_FSTIFF_ANYWALL +#define FEEL_ELLIPSE_DEFAULT_CLIPPING_MASK FEELIT_FCLIP_NONE + +#define FEEL_ELLIPSE_DEFAULT_CENTER_POINT FEEL_EFFECT_MOUSE_POS_AT_START + +// +// FEEL --> FORCE Wrappers +// +#define FORCE_ELLIPSE_DEFAULT_STIFFNESS FEEL_ELLIPSE_DEFAULT_STIFFNESS +#define FORCE_ELLIPSE_DEFAULT_SATURATION FEEL_ELLIPSE_DEFAULT_SATURATION +#define FORCE_ELLIPSE_DEFAULT_WIDTH FEEL_ELLIPSE_DEFAULT_WIDTH +#define FORCE_ELLIPSE_HEIGHT_AUTO FEEL_ELLIPSE_HEIGHT_AUTO +#define FORCE_ELLIPSE_DEFAULT_HEIGHT FEEL_ELLIPSE_DEFAULT_HEIGHT +#define FORCE_ELLIPSE_WALL_WIDTH_AUTO FEEL_ELLIPSE_WALL_WIDTH_AUTO +#define FORCE_ELLIPSE_DEFAULT_WALL_WIDTH FEEL_ELLIPSE_DEFAULT_WALL_WIDTH +#define FORCE_ELLIPSE_DEFAULT_STIFFNESS_MASK FEEL_ELLIPSE_DEFAULT_STIFFNESS_MASK +#define FORCE_ELLIPSE_DEFAULT_CLIPPING_MASK FEEL_ELLIPSE_DEFAULT_CLIPPING_MASK + +#define FORCE_ELLIPSE_DEFAULT_CENTER_POINT FEEL_ELLIPSE_DEFAULT_CENTER_POINT + + + + +//================================================================ +// CFeelEllipse +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelEllipse : public CFeelEffect +{ + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CFeelEllipse(); + + // Destructor + virtual + ~CFeelEllipse(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + POINT pntCenter, + DWORD dwWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwHeight = FEEL_EFFECT_DONT_CHANGE, + LONG lStiffness = FEEL_EFFECT_DONT_CHANGE, + DWORD dwWallWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwStiffnessMask = FEEL_EFFECT_DONT_CHANGE, + DWORD dwClippingMask = FEEL_EFFECT_DONT_CHANGE, + CFeelEffect* pInsideEffect = (CFeelEffect*) FEEL_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParameters( + LPCRECT pRectOutside, + LONG lStiffness = FEEL_EFFECT_DONT_CHANGE, + DWORD dwWallWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwStiffnessMask = FEEL_EFFECT_DONT_CHANGE, + DWORD dwClippingMask = FEEL_EFFECT_DONT_CHANGE, + CFeelEffect* pInsideEffect = (CFeelEffect*) FEEL_EFFECT_DONT_CHANGE + ); + + + BOOL + SetRect( + LPCRECT pRect + ); + + + BOOL + SetCenter( + POINT pntCenter + ); + + + BOOL + SetCenter( + LONG x, + LONG y + ); + + + // + // OPERATIONS + // + + public: + + BOOL + Initialize( + CFeelDevice* pDevice, + DWORD dwWidth = FEEL_ELLIPSE_DEFAULT_WIDTH, + DWORD dwHeight = FEEL_ELLIPSE_DEFAULT_HEIGHT, + LONG lStiffness = FEEL_ELLIPSE_DEFAULT_STIFFNESS, + DWORD dwWallWidth = FEEL_ELLIPSE_DEFAULT_WALL_WIDTH, + DWORD dwSaturation = FEEL_ELLIPSE_DEFAULT_SATURATION, + DWORD dwStiffnessMask = FEEL_ELLIPSE_DEFAULT_STIFFNESS_MASK, + DWORD dwClippingMask = FEEL_ELLIPSE_DEFAULT_CLIPPING_MASK, + POINT pntCenter = FEEL_ELLIPSE_DEFAULT_CENTER_POINT, + CFeelEffect* pInsideEffect = NULL + ); + + + BOOL + Initialize( + CFeelDevice* pDevice, + LPCRECT pRectOutside, + LONG lStiffness = FEEL_ELLIPSE_DEFAULT_STIFFNESS, + DWORD dwWallWidth = FEEL_ELLIPSE_DEFAULT_WALL_WIDTH, + DWORD dwSaturation = FEEL_ELLIPSE_DEFAULT_SATURATION, + DWORD dwStiffnessMask = FEEL_ELLIPSE_DEFAULT_STIFFNESS_MASK, + DWORD dwClippingMask = FEEL_ELLIPSE_DEFAULT_CLIPPING_MASK, + CFeelEffect* pInsideEffect = NULL + ); + + + virtual BOOL +#ifdef FFC_START_DELAY + StartNow( +#else + Start( +#endif + DWORD dwFlags = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + LPCRECT pRectOutside, + LONG lStiffness, + DWORD dwWallWidth, + DWORD dwSaturation, + DWORD dwStiffnessMask, + DWORD dwClippingMask, + CFeelEffect* pInsideEffect + ); + + // + // INTERNAL DATA + // + + protected: + + FEELIT_ELLIPSE m_ellipse; + BOOL m_bUseMousePosAtStart; + +}; + + + +// +// INLINES +// + +inline BOOL +CFeelEllipse::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_Ellipse); +} + +#endif // !defined(AFX_FEELELLIPSE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelEnclosure.h b/code/win32/FeelIt/FeelEnclosure.h new file mode 100644 index 0000000..ab666d3 --- /dev/null +++ b/code/win32/FeelIt/FeelEnclosure.h @@ -0,0 +1,268 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelEnclosure.h + + PURPOSE: Base Enclosure Class for Feelit API Foundation Classes + + STARTED: 10/29/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELENCLOSURE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELENCLOSURE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelBaseTypes.h" +#include "FeelEffect.h" + + + +//================================================================ +// Constants +//================================================================ + + +#define FEEL_ENCLOSURE_DEFAULT_STIFFNESS 5000 +#define FEEL_ENCLOSURE_DEFAULT_SATURATION 10000 +#define FEEL_ENCLOSURE_DEFAULT_WIDTH 10 +#define FEEL_ENCLOSURE_HEIGHT_AUTO MAXDWORD +#define FEEL_ENCLOSURE_DEFAULT_HEIGHT FEEL_ENCLOSURE_HEIGHT_AUTO +#define FEEL_ENCLOSURE_WALL_WIDTH_AUTO MAXDWORD +#define FEEL_ENCLOSURE_DEFAULT_WALL_WIDTH FEEL_ENCLOSURE_WALL_WIDTH_AUTO +#define FEEL_ENCLOSURE_DEFAULT_STIFFNESS_MASK FEELIT_FSTIFF_ANYWALL +#define FEEL_ENCLOSURE_DEFAULT_CLIPPING_MASK FEELIT_FCLIP_NONE + +#define FEEL_ENCLOSURE_DEFAULT_CENTER_POINT FEEL_EFFECT_MOUSE_POS_AT_START + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_ENCLOSURE_DEFAULT_STIFFNESS FEEL_ENCLOSURE_DEFAULT_STIFFNESS +#define FORCE_ENCLOSURE_DEFAULT_SATURATION FEEL_ENCLOSURE_DEFAULT_SATURATION +#define FORCE_ENCLOSURE_DEFAULT_WIDTH FEEL_ENCLOSURE_DEFAULT_WIDTH +#define FORCE_ENCLOSURE_HEIGHT_AUTO FEEL_ENCLOSURE_HEIGHT_AUTO +#define FORCE_ENCLOSURE_DEFAULT_HEIGHT FEEL_ENCLOSURE_DEFAULT_HEIGHT +#define FORCE_ENCLOSURE_WALL_WIDTH_AUTO FEEL_ENCLOSURE_WALL_WIDTH_AUTO +#define FORCE_ENCLOSURE_DEFAULT_WALL_WIDTH FEEL_ENCLOSURE_DEFAULT_WALL_WIDTH +#define FORCE_ENCLOSURE_DEFAULT_STIFFNESS_MASK FEEL_ENCLOSURE_DEFAULT_STIFFNESS_MASK +#define FORCE_ENCLOSURE_DEFAULT_CLIPPING_MASK FEEL_ENCLOSURE_DEFAULT_CLIPPING_MASK + +#define FORCE_ENCLOSURE_DEFAULT_CENTER_POINT FEEL_ENCLOSURE_DEFAULT_CENTER_POINT + + + + +//================================================================ +// CFeelEnclosure +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelEnclosure : public CFeelEffect +{ + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CFeelEnclosure(); + + // Destructor + virtual + ~CFeelEnclosure(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + POINT pntCenter, + DWORD dwWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwHeight = FEEL_EFFECT_DONT_CHANGE, + LONG lTopAndBottomWallStiffness = FEEL_EFFECT_DONT_CHANGE, + LONG lLeftAndRightWallStiffness = FEEL_EFFECT_DONT_CHANGE, + DWORD dwTopAndBottomWallWallWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwLeftAndRightWallWallWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwTopAndBottomWallSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwLeftAndRightWallSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwStiffnessMask = FEEL_EFFECT_DONT_CHANGE, + DWORD dwClippingMask = FEEL_EFFECT_DONT_CHANGE, + CFeelEffect* pInsideEffect = (CFeelEffect*) FEEL_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParameters( + LPCRECT pRectOutside, + LONG lTopAndBottomWallStiffness = FEEL_EFFECT_DONT_CHANGE, + LONG lLeftAndRightWallStiffness = FEEL_EFFECT_DONT_CHANGE, + DWORD dwTopAndBottomWallWallWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwLeftAndRightWallWallWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwTopAndBottomWallSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwLeftAndRightWallSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwStiffnessMask = FEEL_EFFECT_DONT_CHANGE, + DWORD dwClippingMask = FEEL_EFFECT_DONT_CHANGE, + CFeelEffect* pInsideEffect = (CFeelEffect*) FEEL_EFFECT_DONT_CHANGE + ); + + + BOOL + SetRect( + LPCRECT pRect + ); + + + BOOL + SetCenter( + POINT pntCenter + ); + + + BOOL + SetCenter( + LONG x, + LONG y + ); + + + // + // OPERATIONS + // + + public: + + BOOL + Initialize( + CFeelDevice* pDevice, + DWORD dwWidth = FEEL_ENCLOSURE_DEFAULT_WIDTH, + DWORD dwHeight = FEEL_ENCLOSURE_DEFAULT_HEIGHT, + LONG lTopAndBottomWallStiffness = FEEL_ENCLOSURE_DEFAULT_STIFFNESS, + LONG lLeftAndRightWallStiffness = FEEL_ENCLOSURE_DEFAULT_STIFFNESS, + DWORD dwTopAndBottomWallWallWidth = FEEL_ENCLOSURE_DEFAULT_WALL_WIDTH, + DWORD dwLeftAndRightWallWallWidth = FEEL_ENCLOSURE_DEFAULT_WALL_WIDTH, + DWORD dwTopAndBottomWallSaturation = FEEL_ENCLOSURE_DEFAULT_SATURATION, + DWORD dwLeftAndRightWallSaturation = FEEL_ENCLOSURE_DEFAULT_SATURATION, + DWORD dwStiffnessMask = FEEL_ENCLOSURE_DEFAULT_STIFFNESS_MASK, + DWORD dwClippingMask = FEEL_ENCLOSURE_DEFAULT_CLIPPING_MASK, + POINT pntCenter = FEEL_ENCLOSURE_DEFAULT_CENTER_POINT, + CFeelEffect* pInsideEffect = NULL + ); + + + BOOL + Initialize( + CFeelDevice* pDevice, + LPCRECT pRectOutside, + LONG lTopAndBottomWallStiffness = FEEL_ENCLOSURE_DEFAULT_STIFFNESS, + LONG lLeftAndRightWallStiffness = FEEL_ENCLOSURE_DEFAULT_STIFFNESS, + DWORD dwTopAndBottomWallWallWidth = FEEL_ENCLOSURE_DEFAULT_WALL_WIDTH, + DWORD dwLeftAndRightWallWallWidth = FEEL_ENCLOSURE_DEFAULT_WALL_WIDTH, + DWORD dwTopAndBottomWallSaturation = FEEL_ENCLOSURE_DEFAULT_SATURATION, + DWORD dwLeftAndRightWallSaturation = FEEL_ENCLOSURE_DEFAULT_SATURATION, + DWORD dwStiffnessMask = FEEL_ENCLOSURE_DEFAULT_STIFFNESS_MASK, + DWORD dwClippingMask = FEEL_ENCLOSURE_DEFAULT_CLIPPING_MASK, + CFeelEffect* pInsideEffect = NULL + ); + + + virtual BOOL +#ifdef FFC_START_DELAY + StartNow( +#else + Start( +#endif + DWORD dwFlags = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + LPCRECT pRectOutside, + LONG lTopAndBottomWallStiffness, + LONG lLeftAndRightWallStiffness, + DWORD dwTopAndBottomWallWallWidth, + DWORD dwLeftAndRightWallWallWidth, + DWORD dwTopAndBottomWallSaturation, + DWORD dwLeftAndRightWallSaturation, + DWORD dwStiffnessMask, + DWORD dwClippingMask, + CFeelEffect* pInsideEffect + ); + + // + // INTERNAL DATA + // + + protected: + + FEELIT_ENCLOSURE m_enclosure; + BOOL m_bUseMousePosAtStart; + +}; + + +// +// INLINES +// + +inline BOOL +CFeelEnclosure::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_Enclosure); +} + +#endif // !defined(AFX_FEELENCLOSURE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelFriction.h b/code/win32/FeelIt/FeelFriction.h new file mode 100644 index 0000000..e39af13 --- /dev/null +++ b/code/win32/FeelIt/FeelFriction.h @@ -0,0 +1,172 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelFriction.h + + PURPOSE: Feelit API Friction Effect Class + + STARTED: 12/29/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELFriction_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELFriction_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include +#include "FeelCondition.h" + + +//================================================================ +// Constants +//================================================================ + +#define FEEL_FRICTION_DEFAULT_COEFFICIENT 2500 +#define FEEL_FRICTION_DEFAULT_MIN_VELOCITY 0 + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_FRICTION_DEFAULT_COEFFICIENT FEEL_FRICTION_DEFAULT_COEFFICIENT +#define FORCE_FRICTION_DEFAULT_MIN_VELOCITY FEEL_FRICTION_DEFAULT_MIN_VELOCITY + + +//================================================================ +// CFeelFriction +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelFriction : public CFeelCondition +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CFeelFriction(); + + // Destructor + virtual + ~CFeelFriction(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + DWORD dwCoefficient, + DWORD dwMinVelocity = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionX = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionY = FEEL_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParametersPolar( + DWORD dwCoefficient, + DWORD dwMinVelocity, + LONG lAngle + ); + + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CFeelDevice* pDevice, + DWORD dwCoefficient = FEEL_FRICTION_DEFAULT_COEFFICIENT, + DWORD dwMinVelocity = FEEL_FRICTION_DEFAULT_MIN_VELOCITY, + DWORD dwfAxis = FEEL_EFFECT_AXIS_BOTH, + LONG lDirectionX = FEEL_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = FEEL_EFFECT_DEFAULT_DIRECTION_Y + ); + + virtual BOOL + InitializePolar( + CFeelDevice* pDevice, + DWORD dwCoefficient, + DWORD dwMinVelocity, + LONG lAngle + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CFeelFriction::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_Friction); +} + + +#endif // !defined(AFX_FEELFriction_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelGrid.h b/code/win32/FeelIt/FeelGrid.h new file mode 100644 index 0000000..3cffc44 --- /dev/null +++ b/code/win32/FeelIt/FeelGrid.h @@ -0,0 +1,171 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelGrid.h + + PURPOSE: Feelit API Grid Effect Class + + STARTED: 12/11/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELGrid_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELGrid_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include +#include "FeelCondition.h" + + +//================================================================ +// Constants +//================================================================ + +#define FEEL_GRID_DEFAULT_HORIZ_OFFSET 0 +#define FEEL_GRID_DEFAULT_VERT_OFFSET 0 +#define FEEL_GRID_DEFAULT_HORIZ_SPACING 100 +#define FEEL_GRID_DEFAULT_VERT_SPACING 100 +#define FEEL_GRID_DEFAULT_NODE_STRENGTH 5000 +#define FEEL_GRID_DEFAULT_NODE_SATURATION 10000 + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_GRID_DEFAULT_HORIZ_OFFSET FEEL_GRID_DEFAULT_HORIZ_OFFSET +#define FORCE_GRID_DEFAULT_VERT_OFFSET FEEL_GRID_DEFAULT_VERT_OFFSET +#define FORCE_GRID_DEFAULT_HORIZ_SPACING FEEL_GRID_DEFAULT_HORIZ_SPACING +#define FORCE_GRID_DEFAULT_VERT_SPACING FEEL_GRID_DEFAULT_VERT_SPACING +#define FORCE_GRID_DEFAULT_NODE_STRENGTH FEEL_GRID_DEFAULT_NODE_STRENGTH +#define FORCE_GRID_DEFAULT_NODE_SATURATION FEEL_GRID_DEFAULT_NODE_SATURATION + + +//================================================================ +// CFeelGrid +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelGrid : public CFeelCondition +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CFeelGrid(); + + // Destructor + virtual + ~CFeelGrid(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + DWORD dwHorizSpacing, + DWORD dwVertSpacing, + LONG lHorizNodeStrength = FEEL_EFFECT_DONT_CHANGE, + LONG lVertNodeStrength = FEEL_EFFECT_DONT_CHANGE, + DWORD dwHorizOffset = FEEL_EFFECT_DONT_CHANGE, + DWORD dwVertOffset = FEEL_EFFECT_DONT_CHANGE, + DWORD dwHorizNodeSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwVertNodeSaturation = FEEL_EFFECT_DONT_CHANGE + ); + + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CFeelDevice* pDevice, + DWORD dwHorizSpacing = FEEL_GRID_DEFAULT_HORIZ_SPACING, + DWORD dwVertSpacing = FEEL_GRID_DEFAULT_VERT_SPACING, + LONG lHorizNodeStrength = FEEL_GRID_DEFAULT_NODE_STRENGTH, + LONG lVertNodeStrength = FEEL_GRID_DEFAULT_NODE_STRENGTH, + DWORD dwHorizOffset = FEEL_GRID_DEFAULT_HORIZ_OFFSET, + DWORD dwVertOffset = FEEL_GRID_DEFAULT_VERT_OFFSET, + DWORD dwHorizNodeSaturation = FEEL_GRID_DEFAULT_NODE_SATURATION, + DWORD dwVertNodeSaturation = FEEL_GRID_DEFAULT_NODE_SATURATION + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CFeelGrid::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_Grid); +} + +#endif // !defined(AFX_FEELGrid_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelInertia.h b/code/win32/FeelIt/FeelInertia.h new file mode 100644 index 0000000..c75e04d --- /dev/null +++ b/code/win32/FeelIt/FeelInertia.h @@ -0,0 +1,178 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelInertia.h + + PURPOSE: Feelit API Inertia Effect Class + + STARTED: 12/29/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELInertia_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELInertia_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include +#include "FeelCondition.h" + + +//================================================================ +// Constants +//================================================================ + +#define FEEL_INERTIA_DEFAULT_COEFFICIENT 2500 +#define FEEL_INERTIA_DEFAULT_SATURATION 10000 +#define FEEL_INERTIA_DEFAULT_MIN_ACCELERATION 0 + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_INERTIA_DEFAULT_COEFFICIENT FEEL_INERTIA_DEFAULT_COEFFICIENT +#define FORCE_INERTIA_DEFAULT_SATURATION FEEL_INERTIA_DEFAULT_SATURATION +#define FORCE_INERTIA_DEFAULT_MIN_ACCELERATION FEEL_INERTIA_DEFAULT_MIN_ACCELERATION + + +//================================================================ +// CFeelInertia +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelInertia : public CFeelCondition +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CFeelInertia(); + + // Destructor + virtual + ~CFeelInertia(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + DWORD dwCoefficient, + DWORD dwSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwMinAcceleration = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionX = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionY = FEEL_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParametersPolar( + DWORD dwCoefficient, + DWORD dwSaturation, + DWORD dwMinAcceleration, + LONG lAngle + ); + + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CFeelDevice* pDevice, + DWORD dwCoefficient = FEEL_INERTIA_DEFAULT_COEFFICIENT, + DWORD dwSaturation = FEEL_INERTIA_DEFAULT_SATURATION, + DWORD dwMinAcceleration = FEEL_INERTIA_DEFAULT_MIN_ACCELERATION, + DWORD dwfAxis = FEEL_EFFECT_AXIS_BOTH, + LONG lDirectionX = FEEL_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = FEEL_EFFECT_DEFAULT_DIRECTION_Y + ); + + virtual BOOL + InitializePolar( + CFeelDevice* pDevice, + DWORD dwCoefficient, + DWORD dwSaturation, + DWORD dwMinAcceleration, + LONG lAngle + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CFeelInertia::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_Inertia); +} + + +#endif // !defined(AFX_FEELInertia_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelMouse.h b/code/win32/FeelIt/FeelMouse.h new file mode 100644 index 0000000..4d5cc3e --- /dev/null +++ b/code/win32/FeelIt/FeelMouse.h @@ -0,0 +1,143 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelMouse.h + + PURPOSE: Abstraction of Feelit mouse device + + STARTED: 10/10/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + +#ifndef FeelMouse_h +#define FeelMouse_h + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelDevice.h" + + +//================================================================ +// CFeelMouse +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelMouse : public CFeelDevice +{ + + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CFeelMouse(); + + // Destructor + virtual + ~CFeelMouse(); + + + // + // ATTRIBUTES + // + + public: + + virtual LPIFEEL_API + GetAPI() + { return m_piApi; } + + virtual LPIFEEL_DEVICE + GetDevice() + { return m_piDevice; } + + BOOL + HaveForceFeelitMouse() + { return m_piDevice != NULL; } + + + // + // OPERATIONS + // + + public: + + BOOL + Initialize( + HANDLE hinstApp, + HANDLE hwndApp, + DWORD dwCooperativeFlag = FEELIT_FCOOPLEVEL_FOREGROUND + ); + + virtual BOOL + ChangeScreenResolution( + BOOL bAutoSet, + DWORD dwXScreenSize = 0, + DWORD dwYScreenSize = 0 + ); + + // Another syntax for SwitchToAbsoluteMode. + // The default is Absolute mode. Call only to switch to Relative mode or + // to switch back to Absolute mode. + virtual BOOL + SwitchToAbsoluteMode( + BOOL bAbsMode + ); + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + virtual void + reset(); + + virtual BOOL + prepare_device(); + + + // + // INTERNAL DATA + // + + protected: + + LPIFEEL_API m_piApi; + LPIFEEL_DEVICE m_piDevice; +}; + +#endif // ForceFeelitMouse_h \ No newline at end of file diff --git a/code/win32/FeelIt/FeelPeriodic.h b/code/win32/FeelIt/FeelPeriodic.h new file mode 100644 index 0000000..369fbac --- /dev/null +++ b/code/win32/FeelIt/FeelPeriodic.h @@ -0,0 +1,227 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelPeriodic.h + + PURPOSE: Base Periodic Class for Feelit API Foundation Classes + + STARTED: 11/03/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELPERIODIC_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELPERIODIC_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelBaseTypes.h" +#include "FeelEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +#define FEEL_PERIODIC_DEFAULT_DURATION 1000 // Milliseconds +#define FEEL_PERIODIC_DEFAULT_MAGNITUDE 5000 +#define FEEL_PERIODIC_DEFAULT_PERIOD 100 // Milliseconds +#define FEEL_PERIODIC_DEFAULT_OFFSET 0 +#define FEEL_PERIODIC_DEFAULT_PHASE 0 // Degrees +#define FEEL_PERIODIC_DEFAULT_DIRECTION_X 1 // Pixels +#define FEEL_PERIODIC_DEFAULT_DIRECTION_Y 0 // Pixels +#define FEEL_PERIODIC_DEFAULT_ANGLE 9000 // 100ths of degrees + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_PERIODIC_DEFAULT_DURATION FEEL_PERIODIC_DEFAULT_DURATION +#define FORCE_PERIODIC_DEFAULT_MAGNITUDE FEEL_PERIODIC_DEFAULT_MAGNITUDE +#define FORCE_PERIODIC_DEFAULT_PERIOD FEEL_PERIODIC_DEFAULT_PERIOD +#define FORCE_PERIODIC_DEFAULT_OFFSET FEEL_PERIODIC_DEFAULT_OFFSET +#define FORCE_PERIODIC_DEFAULT_PHASE FEEL_PERIODIC_DEFAULT_PHASE +#define FORCE_PERIODIC_DEFAULT_DIRECTION_X FEEL_PERIODIC_DEFAULT_DIRECTION_X +#define FORCE_PERIODIC_DEFAULT_DIRECTION_Y FEEL_PERIODIC_DEFAULT_DIRECTION_Y +#define FORCE_PERIODIC_DEFAULT_ANGLE FEEL_PERIODIC_DEFAULT_ANGLE + + + +//================================================================ +// CFeelPeriodic +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelPeriodic : public CFeelEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructors + + // You may use this form if you will immediately initialize it + // from an IFR file... + CFeelPeriodic(); + + // Otherwise use this form... + CFeelPeriodic( + const GUID& rguidEffect + ); + + // Destructor + virtual + ~CFeelPeriodic(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + DWORD dwMagnitude, + DWORD dwPeriod = FEEL_EFFECT_DONT_CHANGE, + DWORD dwDuration = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionX = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionY = FEEL_EFFECT_DONT_CHANGE, + LONG lOffset = FEEL_EFFECT_DONT_CHANGE, + DWORD dwPhase = FEEL_EFFECT_DONT_CHANGE, + LPFEEL_ENVELOPE pEnvelope = (LPFEEL_ENVELOPE) FEEL_EFFECT_DONT_CHANGE_PTR + ); + + BOOL + ChangeParametersPolar( + DWORD dwMagnitude, + DWORD dwPeriod = FEEL_EFFECT_DONT_CHANGE, + DWORD dwDuration = FEEL_EFFECT_DONT_CHANGE, + LONG lAngle = FEEL_EFFECT_DONT_CHANGE, + LONG lOffset = FEEL_EFFECT_DONT_CHANGE, + DWORD dwPhase = FEEL_EFFECT_DONT_CHANGE, + LPFEEL_ENVELOPE pEnvelope = (LPFEEL_ENVELOPE) FEEL_EFFECT_DONT_CHANGE_PTR + ); + + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CFeelDevice* pDevice, + DWORD dwMagnitude = FEEL_PERIODIC_DEFAULT_MAGNITUDE, + DWORD dwPeriod = FEEL_PERIODIC_DEFAULT_PERIOD, + DWORD dwDuration = FEEL_PERIODIC_DEFAULT_DURATION, + LONG lDirectionX = FEEL_PERIODIC_DEFAULT_DIRECTION_X, + LONG lDirectionY = FEEL_PERIODIC_DEFAULT_DIRECTION_Y, + LONG lOffset = FEEL_PERIODIC_DEFAULT_OFFSET, + DWORD dwPhase = FEEL_PERIODIC_DEFAULT_PHASE, + LPFEEL_ENVELOPE pEnvelope = NULL + ); + + virtual BOOL + InitializePolar( + CFeelDevice* pDevice, + DWORD dwMagnitude = FEEL_PERIODIC_DEFAULT_MAGNITUDE, + DWORD dwPeriod = FEEL_PERIODIC_DEFAULT_PERIOD, + DWORD dwDuration = FEEL_PERIODIC_DEFAULT_DURATION, + LONG lAngle = FEEL_PERIODIC_DEFAULT_ANGLE, + LONG lOffset = FEEL_PERIODIC_DEFAULT_OFFSET, + DWORD dwPhase = FEEL_PERIODIC_DEFAULT_PHASE, + LPFEEL_ENVELOPE pEnvelope = NULL + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + DWORD dwDuration, + DWORD dwMagnitude, + DWORD dwPeriod, + LONG lOffset, + DWORD dwPhase, + LPFEEL_ENVELOPE pEnvelope + ); + + + // + // INTERNAL DATA + // + + protected: + + FEEL_PERIODIC m_Periodic; + +}; + + +// +// INLINES +// + +inline BOOL +CFeelPeriodic::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_Sine) || + IsEqualGUID(guid, GUID_Feel_Square) || + IsEqualGUID(guid, GUID_Feel_Triangle) || + IsEqualGUID(guid, GUID_Feel_SawtoothUp) || + IsEqualGUID(guid, GUID_Feel_SawtoothDown); +} + +#endif // !defined(AFX_FEELPERIODIC_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelProjects.h b/code/win32/FeelIt/FeelProjects.h new file mode 100644 index 0000000..3b5fad4 --- /dev/null +++ b/code/win32/FeelIt/FeelProjects.h @@ -0,0 +1,302 @@ +/********************************************************************** + Copyright (c) 1999 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelProjects.h + + PURPOSE: CFeelProject + Manages a set of forces in a project. + There will be a project for each opened IFR file. + CFeelProjects + Manages a set of projects + + STARTED: 2/22/99 by Jeff Mallett + + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + +#ifndef __FEEL_PROJECTS_H +#define __FEEL_PROJECTS_H + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + + +#include "FFCErrors.h" +#include "FeelBaseTypes.h" +#include "FeelDevice.h" +#include "FeelCompoundEffect.h" + +class CFeelProjects; + + +//================================================================ +// CFeelProject +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelProject +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + CFeelProject() : + m_hProj(NULL), m_pCreatedEffects(NULL), + m_pNext(NULL), m_pDevice(NULL) + { } + + ~CFeelProject(); + + void + Close(); + + + // + // ATTRIBUTES + // + + public: + + CFeelDevice* + GetDevice() const + { return m_pDevice; } + + BOOL + GetIsOpen() const + { return m_hProj != NULL; } + + CFeelCompoundEffect * + GetCreatedEffect( + LPCSTR lpszEffectName + ); + + + // + // OPERATIONS + // + + public: + + BOOL + Start( + LPCSTR lpszEffectName = NULL, + DWORD dwIterations = 1, + DWORD dwFlags = 0, + CFeelDevice* pDevice = NULL + ); + + BOOL + Stop( + LPCSTR lpszEffectName = NULL + ); + + BOOL + OpenFile( + LPCSTR lpszFilePath, + CFeelDevice *pDevice + ); + + LoadProjectObjectPointer( + BYTE *pMem, + CFeelDevice *pDevice + ); + + CFeelCompoundEffect * + CreateEffect( + LPCSTR lpszEffectName, + CFeelDevice* pDevice = NULL + ); + + CFeelCompoundEffect * + CreateEffectByIndex( + int nEffectIndex, + CFeelDevice* pDevice = NULL + ); + + CFeelCompoundEffect * + AddEffect( + LPCSTR lpszEffectName, + GENERIC_EFFECT_PTR pObject + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + void + set_next( + CFeelProject *pNext + ) + { m_pNext = pNext; } + + CFeelProject * + get_next() const + { return m_pNext; } + + void + append_effect_to_list( + CFeelCompoundEffect* pEffect + ); + + IFREffect ** + create_effect_structs( + LPCSTR lpszEffectName, + int &nEff + ); + + IFREffect ** + create_effect_structs_by_index( + int nEffectIndex, + int &nEff + ); + + BOOL + release_effect_structs( + IFREffect **hEffects + ); + + // + // FRIENDS + // + + public: + + friend BOOL + CFeelEffect::InitializeFromProject( + CFeelProject &project, + LPCSTR lpszEffectName, + CFeelDevice* pDevice /* = NULL */ + ); + + friend class CFeelProjects; + + // + // INTERNAL DATA + // + + protected: + + HIFRPROJECT m_hProj; + CFeelCompoundEffect* m_pCreatedEffects; + CFeelDevice* m_pDevice; + + private: + + CFeelProject* m_pNext; +}; + + + +//================================================================ +// CFeelProjects +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelProjects +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + CFeelProjects() : m_pProjects(NULL) { } + + ~CFeelProjects(); + + void + Close(); + + + // + // ATTRIBUTES + // + + public: + + CFeelProject * + GetProject( + int index = 0 + ); + + + // + // OPERATIONS + // + + public: + + BOOL + Stop(); + + long + OpenFile( + LPCSTR lpszFilePath, + CFeelDevice *pDevice + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + + // + // INTERNAL DATA + // + protected: + + CFeelProject *m_pProjects; +}; + + + +#endif // __FEEL_PROJECTS_H diff --git a/code/win32/FeelIt/FeelRamp.h b/code/win32/FeelIt/FeelRamp.h new file mode 100644 index 0000000..8331390 --- /dev/null +++ b/code/win32/FeelIt/FeelRamp.h @@ -0,0 +1,194 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelRamp.h + + PURPOSE: Base Ramp Force Class for Feelit API Foundation Classes + + STARTED: 12/11/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELRAMP_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELRAMP_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include "FeelBaseTypes.h" +#include "FeelEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +#define FEEL_RAMP_DEFAULT_DURATION 1000 // Milliseconds +#define FEEL_RAMP_DEFAULT_MAGNITUDE_START 0 +#define FEEL_RAMP_DEFAULT_MAGNITUDE_END 10000 + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_RAMP_DEFAULT_DURATION FEEL_RAMP_DEFAULT_DURATION +#define FORCE_RAMP_DEFAULT_MAGNITUDE_START FEEL_RAMP_DEFAULT_MAGNITUDE_START +#define FORCE_RAMP_DEFAULT_MAGNITUDE_END FEEL_RAMP_DEFAULT_MAGNITUDE_END + + +//================================================================ +// CFeelRamp +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelRamp : public CFeelEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CFeelRamp(); + + // Destructor + virtual + ~CFeelRamp(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + LONG lDirectionX, + LONG lDirectionY, + DWORD dwDuration = FEEL_EFFECT_DONT_CHANGE, + LONG lMagStart = FEEL_EFFECT_DONT_CHANGE, + LONG lMagEnd = FEEL_EFFECT_DONT_CHANGE, + LPFEEL_ENVELOPE pEnvelope = (LPFEEL_ENVELOPE) FEEL_EFFECT_DONT_CHANGE_PTR + ); + + BOOL + ChangeParametersPolar( + LONG lAngle, + DWORD dwDuration = FEEL_EFFECT_DONT_CHANGE, + LONG lMagStart = FEEL_EFFECT_DONT_CHANGE, + LONG lMagEnd = FEEL_EFFECT_DONT_CHANGE, + LPFEEL_ENVELOPE pEnvelope = (LPFEEL_ENVELOPE) FEEL_EFFECT_DONT_CHANGE_PTR + ); + + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CFeelDevice* pDevice, + LONG lDirectionX = FEEL_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = FEEL_EFFECT_DEFAULT_DIRECTION_Y, + DWORD dwDuration = FEEL_RAMP_DEFAULT_DURATION, + LONG lMagStart = FEEL_RAMP_DEFAULT_MAGNITUDE_START, + LONG lMagEnd = FEEL_RAMP_DEFAULT_MAGNITUDE_END, + LPFEEL_ENVELOPE pEnvelope = NULL + ); + + virtual BOOL + InitializePolar( + CFeelDevice* pDevice, + LONG lAngle = FEEL_EFFECT_DEFAULT_ANGLE, + DWORD dwDuration = FEEL_RAMP_DEFAULT_DURATION, + LONG lMagStart = FEEL_RAMP_DEFAULT_MAGNITUDE_START, + LONG lMagEnd = FEEL_RAMP_DEFAULT_MAGNITUDE_END, + LPFEEL_ENVELOPE pEnvelope = NULL + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + DWORD dwDuration, + LONG lMagStart, + LONG lMagEnd, + LPFEEL_ENVELOPE pEnvelope + ); + + + // + // INTERNAL DATA + // + + protected: + + FEEL_RAMPFORCE m_RampForce; + +}; + + +// +// INLINES +// + +inline BOOL +CFeelRamp::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_RampForce); +} + +#endif // !defined(AFX_FEELRAMP_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelSpring.h b/code/win32/FeelIt/FeelSpring.h new file mode 100644 index 0000000..db2f6ec --- /dev/null +++ b/code/win32/FeelIt/FeelSpring.h @@ -0,0 +1,191 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelSpring.h + + PURPOSE: Feelit API Spring Class + + STARTED: 10/10/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FEELSPRING_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELSPRING_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include +#include "FeelCondition.h" + + +//================================================================ +// Constants +//================================================================ + +#define FEEL_SPRING_DEFAULT_STIFFNESS 2500 +#define FEEL_SPRING_DEFAULT_SATURATION 10000 +#define FEEL_SPRING_DEFAULT_DEADBAND 100 +#define FEEL_SPRING_DEFAULT_CENTER_POINT FEEL_EFFECT_MOUSE_POS_AT_START +#define FEEL_SPRING_DEFAULT_DIRECTION_X 1 +#define FEEL_SPRING_DEFAULT_DIRECTION_Y 0 +#define FEEL_SPRING_DEFAULT_ANGLE 0 + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_SPRING_DEFAULT_STIFFNESS FEEL_SPRING_DEFAULT_STIFFNESS +#define FORCE_SPRING_DEFAULT_SATURATION FEEL_SPRING_DEFAULT_SATURATION +#define FORCE_SPRING_DEFAULT_DEADBAND FEEL_SPRING_DEFAULT_DEADBAND +#define FORCE_SPRING_DEFAULT_CENTER_POINT FEEL_SPRING_DEFAULT_CENTER_POINT +#define FORCE_SPRING_DEFAULT_DIRECTION_X FEEL_SPRING_DEFAULT_DIRECTION_X +#define FORCE_SPRING_DEFAULT_DIRECTION_Y FEEL_SPRING_DEFAULT_DIRECTION_Y +#define FORCE_SPRING_DEFAULT_ANGLE FEEL_SPRING_DEFAULT_ANGLE + + +//================================================================ +// CFeelSpring +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelSpring : public CFeelCondition +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CFeelSpring(); + + // Destructor + virtual + ~CFeelSpring(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + POINT pntCenter, + LONG lStiffness = FEEL_EFFECT_DONT_CHANGE, + DWORD dwSaturation = FEEL_EFFECT_DONT_CHANGE, + DWORD dwDeadband = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionX = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionY = FEEL_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParametersPolar( + POINT pntCenter, + LONG lStiffness, + DWORD dwSaturation, + DWORD dwDeadband, + LONG lAngle + ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CFeelDevice* pDevice, + LONG lStiffness = FEEL_SPRING_DEFAULT_STIFFNESS, + DWORD dwSaturation = FEEL_SPRING_DEFAULT_SATURATION, + DWORD dwDeadband = FEEL_SPRING_DEFAULT_DEADBAND, + DWORD dwfAxis = FEEL_EFFECT_AXIS_BOTH, + POINT pntCenter = FEEL_SPRING_DEFAULT_CENTER_POINT, + LONG lDirectionX = FEEL_SPRING_DEFAULT_DIRECTION_X, + LONG lDirectionY = FEEL_SPRING_DEFAULT_DIRECTION_Y, + BOOL bUseDeviceCoordinates = FALSE + ); + + virtual BOOL + InitializePolar( + CFeelDevice* pDevice, + LONG lStiffness, + DWORD dwSaturation, + DWORD dwDeadband, + POINT pntCenter, + LONG lAngle, + BOOL bUseDeviceCoordinates = FALSE + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CFeelSpring::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_Spring); +} + +#endif // !defined(AFX_FEELSPRING_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) + diff --git a/code/win32/FeelIt/FeelTexture.h b/code/win32/FeelIt/FeelTexture.h new file mode 100644 index 0000000..03eb467 --- /dev/null +++ b/code/win32/FeelIt/FeelTexture.h @@ -0,0 +1,287 @@ +/********************************************************************** + Copyright (c) 1997 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelTexture.h + + PURPOSE: Texture Class for Feelit API Foundation Classes + + STARTED: 2/27/98 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_FeelTexture_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FeelTexture_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _FFCDLL_ +#define DLLFFC __declspec(dllimport) +#else +#define DLLFFC __declspec(dllexport) +#endif + +#include +#include "FeelBaseTypes.h" +#include "FeelEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +const POINT FEEL_TEXTURE_PT_NULL = { 0, 0 }; +const POINT FEEL_TEXTURE_DEFAULT_OFFSET_POINT = { 0, 0}; + +#define FEEL_TEXTURE_DEFAULT_MAGNITUDE 5000 +#define FEEL_TEXTURE_DEFAULT_WIDTH 10 +#define FEEL_TEXTURE_DEFAULT_SPACING 20 + +// +// FORCE --> FEEL Wrappers +// +#define FORCE_TEXTURE_PT_NULL FEEL_TEXTURE_PT_NULL +#define FORCE_TEXTURE_DEFAULT_OFFSET_POINT FEEL_TEXTURE_DEFAULT_OFFSET_POINT + +#define FORCE_TEXTURE_DEFAULT_MAGNITUDE FEEL_TEXTURE_DEFAULT_MAGNITUDE +#define FORCE_TEXTURE_DEFAULT_WIDTH FEEL_TEXTURE_DEFAULT_WIDTH +#define FORCE_TEXTURE_DEFAULT_SPACING FEEL_TEXTURE_DEFAULT_SPACING + + +//================================================================ +// CFeelTexture +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLFFC CFeelTexture : public CFeelEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CFeelTexture(); + + // Destructor + virtual + ~CFeelTexture(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + // Use this form for single-axis and dual-axis effects + BOOL + ChangeTextureParams( + LPCFEELIT_TEXTURE pTextureX, + LPCFEELIT_TEXTURE pTextureY + ); + + // Use this form for directional effects + BOOL + ChangeTextureParams( + LPCFEELIT_TEXTURE pTexture, + LONG lDirectionX, + LONG lDirectionY + ); + + // Use this form for directional effects + BOOL + ChangeTextureParamsPolar( + LPCFEELIT_TEXTURE pTexture, + LONG lAngle + ); + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + ChangeTextureParams( + LONG lPosBumpMag = FEEL_EFFECT_DONT_CHANGE, + DWORD dwPosBumpWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwPosBumpSpacing = FEEL_EFFECT_DONT_CHANGE, + LONG lNegBumpMag = FEEL_EFFECT_DONT_CHANGE, + DWORD dwNegBumpWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwNegBumpSpacing = FEEL_EFFECT_DONT_CHANGE, + POINT pntOffset = FEEL_EFFECT_DONT_CHANGE_POINT, + LONG lDirectionX = FEEL_EFFECT_DONT_CHANGE, + LONG lDirectionY = FEEL_EFFECT_DONT_CHANGE + ); + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + ChangeTextureParamsPolar( + LONG lPosBumpMag = FEEL_EFFECT_DONT_CHANGE, + DWORD dwPosBumpWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwPosBumpSpacing = FEEL_EFFECT_DONT_CHANGE, + LONG lNegBumpMag = FEEL_EFFECT_DONT_CHANGE, + DWORD dwNegBumpWidth = FEEL_EFFECT_DONT_CHANGE, + DWORD dwNegBumpSpacing = FEEL_EFFECT_DONT_CHANGE, + POINT pntOffset = FEEL_EFFECT_DONT_CHANGE_POINT, + LONG lAngle = FEEL_EFFECT_DONT_CHANGE + ); + + BOOL + SetOffset( + POINT pntOffset + ); + + // + // OPERATIONS + // + + public: + + // Use this form for single-axis and dual-axis effects + BOOL + InitTexture( + CFeelDevice* pDevice, + LPCFEELIT_TEXTURE pTextureX, + LPCFEELIT_TEXTURE pTextureY + ); + + + // Use this form for directional effects + BOOL + InitTexture( + CFeelDevice* pDevice, + LPCFEELIT_TEXTURE pTexture, + LONG lDirectionX, + LONG lDirectionY + ); + + + // Use this form for directional effects + BOOL + InitTexturePolar( + CFeelDevice* pDevice, + LPCFEELIT_TEXTURE pTexture, + LONG lAngle + ); + + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + InitTexture( + CFeelDevice* pDevice, + LONG lPosBumpMag = FEEL_TEXTURE_DEFAULT_MAGNITUDE, + DWORD dwPosBumpWidth = FEEL_TEXTURE_DEFAULT_WIDTH, + DWORD dwPosBumpSpacing = FEEL_TEXTURE_DEFAULT_SPACING, + LONG lNegBumpMag = FEEL_TEXTURE_DEFAULT_MAGNITUDE, + DWORD dwNegBumpWidth = FEEL_TEXTURE_DEFAULT_WIDTH, + DWORD dwNegBumpSpacing = FEEL_TEXTURE_DEFAULT_SPACING, + DWORD dwfAxis = FEEL_EFFECT_AXIS_BOTH, + POINT pntOffset = FEEL_TEXTURE_DEFAULT_OFFSET_POINT, + LONG lDirectionX = FEEL_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = FEEL_EFFECT_DEFAULT_DIRECTION_Y + ); + + // Use this form for directional effects + BOOL + InitTexturePolar( + CFeelDevice* pDevice, + LONG lPosBumpMag = FEEL_TEXTURE_DEFAULT_MAGNITUDE, + DWORD dwPosBumpWidth = FEEL_TEXTURE_DEFAULT_WIDTH, + DWORD dwPosBumpSpacing = FEEL_TEXTURE_DEFAULT_SPACING, + LONG lNegBumpMag = FEEL_TEXTURE_DEFAULT_MAGNITUDE, + DWORD dwNegBumpWidth = FEEL_TEXTURE_DEFAULT_WIDTH, + DWORD dwNegBumpSpacing = FEEL_TEXTURE_DEFAULT_SPACING, + POINT pntOffset = FEEL_TEXTURE_DEFAULT_OFFSET_POINT, + LONG lAngle = FEEL_EFFECT_DEFAULT_ANGLE + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + DWORD dwfAxis, + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + LPCFEELIT_TEXTURE pTextureX, + LPCFEELIT_TEXTURE pTextureY + ); + + BOOL + set_parameters( + DWORD dwfAxis, + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + LONG lPosBumpMag, + DWORD dwPosBumpWidth, + DWORD dwPosBumpSpacing, + LONG lNegBumpMag, + DWORD dwNegBumpWidth, + DWORD dwNegBumpSpacing, + POINT pntOffset + ); + + // + // INTERNAL DATA + // + + FEEL_TEXTURE m_aTexture[2]; + DWORD m_dwfAxis; + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CFeelTexture::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Feel_Texture); +} + +#endif // !defined(AFX_FeelTexture_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/win32/FeelIt/FeelitAPI.h b/code/win32/FeelIt/FeelitAPI.h new file mode 100644 index 0000000..6b22a1a --- /dev/null +++ b/code/win32/FeelIt/FeelitAPI.h @@ -0,0 +1,1252 @@ +/********************************************************************** + Copyright (c) 1997, 1998, 1999 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 2158 Paragon Drive + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FeelitAPI.h + + PURPOSE: Feelit API + + STARTED: 09/08/97 + + NOTES/REVISIONS: + +**********************************************************************/ + +#ifndef __FEELITAPI_INCLUDED__ +#define __FEELITAPI_INCLUDED__ + +#ifndef FEELIT_VERSION +#define FEELIT_VERSION 0x0103 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if (FEELIT_VERSION >= 0x0101) +#undef IS_VXD +#endif // FEELIT_VERSION + +#ifndef IS_VXD + +#ifdef _WIN32 +#define COM_NO_WINDOWS_H +#include +#endif + +#endif // IS_VXD not defined + + +#ifndef IS_VXD + +/**************************************************************************** + * + * Class IDs + * + ****************************************************************************/ + +DEFINE_GUID(CLSID_Feelit, 0x5959df60,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(CLSID_FeelitDevice, 0x5959df61,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + + +/**************************************************************************** + * + * Interfaces + * + ****************************************************************************/ + +DEFINE_GUID(IID_IFeelit, 0x5959df62,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(IID_IFeelitDevice, 0x5959df63,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(IID_IFeelitEffect, 0x5959df64,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(IID_IFeelitConfig, 0x900c39e0,0xcc5c,0x11d2,0x8c,0x5d,0x00,0x10,0x5a,0x17,0x8a,0xd1); + + +/**************************************************************************** + * + * Predefined object types + * + ****************************************************************************/ + +DEFINE_GUID(GUID_Feelit_XAxis, 0x5959df65,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_YAxis, 0x5959df66,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_ZAxis, 0x5959df67,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_RxAxis, 0x5959df68,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_RyAxis, 0x5959df69,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_RzAxis, 0x5959df6a,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_Slider, 0x5959df6b,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + +DEFINE_GUID(GUID_Feelit_Button, 0x5959df6c,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_Key, 0x5959df6d,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + +DEFINE_GUID(GUID_Feelit_POV, 0x5959df6e,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + +DEFINE_GUID(GUID_Feelit_Unknown, 0x5959df6f,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + + +/**************************************************************************** + * + * Predefined Product GUIDs + * + ****************************************************************************/ + +DEFINE_GUID(GUID_Feelit_Mouse, 0x99bb5400,0x2b94,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + +/**************************************************************************** + * + * Force feedback effects + * + ****************************************************************************/ + + +/* Constant Force */ +DEFINE_GUID(GUID_Feelit_ConstantForce,0x5959df71,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + +/* Ramp Force */ +DEFINE_GUID(GUID_Feelit_RampForce, 0x5959df72,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + +/* Periodic Effects */ +DEFINE_GUID(GUID_Feelit_Square, 0x5959df73,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_Sine, 0x5959df74,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_Triangle, 0x5959df75,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_SawtoothUp, 0x5959df76,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_SawtoothDown,0x5959df77,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + + +/* Conditions */ +DEFINE_GUID(GUID_Feelit_Spring, 0x5959df78,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_DeviceSpring,0x5959df83,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_Damper, 0x5959df79,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_Inertia, 0x5959df7a,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_Friction, 0x5959df7b,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_Texture, 0x5959df7c,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_Grid, 0x5959df7d,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + +/* Enclosures */ +DEFINE_GUID(GUID_Feelit_Enclosure, 0x5959df7f,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); +DEFINE_GUID(GUID_Feelit_Ellipse, 0x5959df82,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + +/* Custom Force */ +DEFINE_GUID(GUID_Feelit_CustomForce, 0x5959df7e,0x2911,0x11d1,0xb0,0x49,0x00,0x20,0xaf,0x30,0x26,0x9a); + +#endif // IS_VXD not defined + +/**************************************************************************** + * + * Interfaces and Structures... + * + ****************************************************************************/ + + +/**************************************************************************** + * + * IFeelitEffect + * + ****************************************************************************/ + +#define FEELIT_FEFFECTTYPE_ALL 0x00000000 + +#define FEELIT_FEFFECTTYPE_CONSTANTFORCE 0x00000001 +#define FEELIT_FEFFECTTYPE_RAMPFORCE 0x00000002 +#define FEELIT_FEFFECTTYPE_PERIODIC 0x00000003 +#define FEELIT_FEFFECTTYPE_CONDITION 0x00000004 +#define FEELIT_FEFFECTTYPE_ENCLOSURE 0x00000005 +#define FEELIT_FEFFECTTYPE_ELLIPSE 0x00000006 +#define FEELIT_FEFFECTTYPE_TEXTURE 0x00000007 +#define FEELIT_FEFFECTTYPE_CUSTOMFORCE 0x000000F0 +#define FEELIT_FEFFECTTYPE_HARDWARE 0x000000FF + +#define FEELIT_FEFFECTTYPE_FFATTACK 0x00000200 +#define FEELIT_FEFFECTTYPE_FFFADE 0x00000400 +#define FEELIT_FEFFECTTYPE_SATURATION 0x00000800 +#define FEELIT_FEFFECTTYPE_POSNEGCOEFFICIENTS 0x00001000 +#define FEELIT_FEFFECTTYPE_POSNEGSATURATION 0x00002000 +#define FEELIT_FEFFECTTYPE_DEADBAND 0x00004000 + +#define FEELIT_EFFECTTYPE_GETTYPE(n) LOBYTE(n) +#define FEELIT_EFFECTTYPE_GETFLAGS(n) ( n & 0xFFFFFF00 ) + +#define FEELIT_DEGREES 100 +#define FEELIT_FFNOMINALMAX 10000 +#define FEELIT_SECONDS 1000000 + +typedef struct FEELIT_CONSTANTFORCE { + LONG lMagnitude; /* Magnitude of the effect, in the range -10000 to 10000 */ +} FEELIT_CONSTANTFORCE, *LPFEELIT_CONSTANTFORCE; +typedef const FEELIT_CONSTANTFORCE *LPCFEELIT_CONSTANTFORCE; + +typedef struct FEELIT_RAMPFORCE { + LONG lStart; /* Magnitude at start of effect. Range -10000 to 10000 */ + LONG lEnd; /* Magnitude at end of effect. Range -10000 to 10000 */ +} FEELIT_RAMPFORCE, *LPFEELIT_RAMPFORCE; +typedef const FEELIT_RAMPFORCE *LPCFEELIT_RAMPFORCE; + +typedef struct FEELIT_PERIODIC { + DWORD dwMagnitude; /* Magnitude of the effect, in the range 0 to 10000 */ + LONG lOffset; /* Force will be gen'd in range lOffset - dwMagnitude to lOffset + dwMagnitude */ + DWORD dwPhase; /* Position in cycle at wich playback begins. Range 0 - 35,999 */ + DWORD dwPeriod; /* Period (length of one cycle) of the effect in microseconds */ +} FEELIT_PERIODIC, *LPFEELIT_PERIODIC; +typedef const FEELIT_PERIODIC *LPCFEELIT_PERIODIC; + +typedef struct FEELIT_CONDITION { + LONG lCenter; /* Center-point in screen coords. Axis depends on that in FEELIT_EFFECT */ + LONG lPositiveCoefficient; /* Coef. on pos. side of the offset. Range -10000 to 10000 */ + LONG lNegativeCoefficient; /* Coef. on neg. side of the offset. Range -10000 to 10000 */ + DWORD dwPositiveSaturation; /* Max force output on pos. side of offset. Range 0 to 10000 */ + DWORD dwNegativeSaturation; /* Max force output on neg. side of offset. Range 0 to 10000 */ + LONG lDeadBand; /* Region around lOffset where condition is not active. Range 0 to 10000 */ +} FEELIT_CONDITION, *LPFEELIT_CONDITION; +typedef const FEELIT_CONDITION *LPCFEELIT_CONDITION; + +typedef struct FEELIT_TEXTURE { + DWORD dwSize; /* sizeof(FEELIT_TEXTURE) */ + LONG lOffset; /* Offset in screen coords of first texture from left or top edge */ + LONG lPosBumpMag; /* Magnitude of bumps felt when mouse travels in positive direction */ + DWORD dwPosBumpWidth; /* Width of bumps felt when mouse travels in positive direction */ + DWORD dwPosBumpSpacing; /* Center-to-Center spacing of bumps felt when mouse travels in positive direction */ + LONG lNegBumpMag; /* Magnitude of bumps felt when mouse travels in negative direction */ + DWORD dwNegBumpWidth; /* Width of bumps felt when mouse travels in negative direction */ + DWORD dwNegBumpSpacing; /* Center-to-Center spacing of bumps felt when mouse travels in negative direction */ +} FEELIT_TEXTURE, *LPFEELIT_TEXTURE; +typedef const FEELIT_TEXTURE *LPCFEELIT_TEXTURE; + +typedef struct FEELIT_CUSTOMFORCE { + DWORD cChannels; /* No. of channels (axes) affected by this force */ + DWORD dwSamplePeriod; /* Sample period in microseconds */ + DWORD cSamples; /* Total number of samples in the rglForceData */ + LPLONG rglForceData; /* long[cSamples]. Array of force values. Channels are interleaved */ +} FEELIT_CUSTOMFORCE, *LPFEELIT_CUSTOMFORCE; +typedef const FEELIT_CUSTOMFORCE *LPCFEELIT_CUSTOMFORCE; + +typedef struct FEELIT_ENVELOPE { + DWORD dwSize; /* sizeof(FEELIT_ENVELOPE) */ + DWORD dwAttackLevel; /* Ampl. for start of env., rel. to baseline. Range 0 to 10000 */ + DWORD dwAttackTime; /* Time, in microseconds, to reach sustain level */ + DWORD dwFadeLevel; /* Ampl. for end of env., rel. to baseline. Range 0 to 10000 */ + DWORD dwFadeTime; /* Time, in microseconds, to reach fade level */ +} FEELIT_ENVELOPE, *LPFEELIT_ENVELOPE; +typedef const FEELIT_ENVELOPE *LPCFEELIT_ENVELOPE; + +typedef struct FEELIT_EFFECT { + DWORD dwSize; /* sizeof(FEELIT_EFFECT) */ + GUID guidEffect; /* Effect Identifier */ + DWORD dwFlags; /* FEELIT_FEFFECT_* */ + DWORD dwDuration; /* Microseconds */ + DWORD dwSamplePeriod; /* RESERVED */ + DWORD dwGain; /* RESERVED */ + DWORD dwTriggerButton; /* RESERVED */ + DWORD dwTriggerRepeatInterval; /* RESERVED */ + DWORD cAxes; /* Number of axes */ + LPDWORD rgdwAxes; /* Array of axes */ + LPLONG rglDirection; /* Array of directions */ + LPFEELIT_ENVELOPE lpEnvelope; /* Optional */ + DWORD cbTypeSpecificParams; /* Size of params */ + LPVOID lpvTypeSpecificParams; /* Pointer to params */ + DWORD dwStartDelay; /* Microseconds delay */ +} FEELIT_EFFECT, *LPFEELIT_EFFECT; +typedef const FEELIT_EFFECT *LPCFEELIT_EFFECT; + + +/* Effect Flags */ +#define FEELIT_FEFFECT_OBJECTIDS 0x00000001 +#define FEELIT_FEFFECT_OBJECTOFFSETS 0x00000002 +#define FEELIT_FEFFECT_CARTESIAN 0x00000010 +#define FEELIT_FEFFECT_POLAR 0x00000020 +#define FEELIT_FEFFECT_SPHERICAL 0x00000040 + +/* Parameter Flags */ +#define FEELIT_FPARAM_DURATION 0x00000001 +#define FEELIT_FPARAM_SAMPLEPERIOD 0x00000002 +#define FEELIT_FPARAM_GAIN 0x00000004 +#define FEELIT_FPARAM_TRIGGERBUTTON 0x00000008 +#define FEELIT_FPARAM_TRIGGERREPEATINTERVAL 0x00000010 +#define FEELIT_FPARAM_AXES 0x00000020 +#define FEELIT_FPARAM_DIRECTION 0x00000040 +#define FEELIT_FPARAM_ENVELOPE 0x00000080 +#define FEELIT_FPARAM_TYPESPECIFICPARAMS 0x00000100 +#define FEELIT_FPARAM_STARTDELAY 0x00000200 +#define FEELIT_FPARAM_ALLPARAMS 0x000003FF +#define FEELIT_FPARAM_START 0x20000000 +#define FEELIT_FPARAM_NORESTART 0x40000000 +#define FEELIT_FPARAM_NODOWNLOAD 0x80000000 + +#define FEELIT_PARAM_NOTRIGGER 0xFFFFFFFF + +/* Start Flags */ +#define FEELIT_FSTART_SOLO 0x00000001 +#define FEELIT_FSTART_NODOWNLOAD 0x80000000 + +/* Status Flags */ +#define FEELIT_FSTATUS_PLAYING 0x00000001 +#define FEELIT_FSTATUS_EMULATED 0x00000002 + +/* Stiffness Mask Flags */ +#define FEELIT_FSTIFF_NONE 0x00000000 +#define FEELIT_FSTIFF_OUTERLEFTWALL 0x00000001 +#define FEELIT_FSTIFF_INNERLEFTWALL 0x00000002 +#define FEELIT_FSTIFF_INNERRIGHTWALL 0x00000004 +#define FEELIT_FSTIFF_OUTERRIGHTWALL 0x00000008 +#define FEELIT_FSTIFF_OUTERTOPWALL 0x00000010 +#define FEELIT_FSTIFF_INNERTOPWALL 0x00000020 +#define FEELIT_FSTIFF_INNERBOTTOMWALL 0x00000040 +#define FEELIT_FSTIFF_OUTERBOTTOMWALL 0x00000080 +#define FEELIT_FSTIFF_OUTERANYWALL ( FEELIT_FSTIFF_OUTERTOPWALL | FEELIT_FSTIFF_OUTERBOTTOMWALL | FEELIT_FSTIFF_OUTERLEFTWALL | FEELIT_FSTIFF_OUTERRIGHTWALL ) +#define FEELIT_FSTIFF_INBOUNDANYWALL FEELIT_FSTIFF_OUTERANYWALL +#define FEELIT_FSTIFF_INNERANYWALL ( FEELIT_FSTIFF_INNERTOPWALL | FEELIT_FSTIFF_INNERBOTTOMWALL | FEELIT_FSTIFF_INNERLEFTWALL | FEELIT_FSTIFF_INNERRIGHTWALL ) +#define FEELIT_FSTIFF_OUTBOUNDANYWALL FEELIT_FSTIFF_INNERANYWALL +#define FEELIT_FSTIFF_ANYWALL ( FEELIT_FSTIFF_OUTERANYWALL | FEELIT_FSTIFF_INNERANYWALL ) + +/* Clipping Mask Flags */ +#define FEELIT_FCLIP_NONE 0x00000000 +#define FEELIT_FCLIP_OUTERLEFTWALL 0x00000001 +#define FEELIT_FCLIP_INNERLEFTWALL 0x00000002 +#define FEELIT_FCLIP_INNERRIGHTWALL 0x00000004 +#define FEELIT_FCLIP_OUTERRIGHTWALL 0x00000008 +#define FEELIT_FCLIP_OUTERTOPWALL 0x00000010 +#define FEELIT_FCLIP_INNERTOPWALL 0x00000020 +#define FEELIT_FCLIP_INNERBOTTOMWALL 0x00000040 +#define FEELIT_FCLIP_OUTERBOTTOMWALL 0x00000080 +#define FEELIT_FCLIP_OUTERANYWALL ( FEELIT_FCLIP_OUTERTOPWALL | FEELIT_FCLIP_OUTERBOTTOMWALL | FEELIT_FCLIP_OUTERLEFTWALL | FEELIT_FCLIP_OUTERRIGHTWALL ) +#define FEELIT_FCLIP_INNERANYWALL ( FEELIT_FCLIP_INNERTOPWALL | FEELIT_FCLIP_INNERBOTTOMWALL | FEELIT_FCLIP_INNERLEFTWALL | FEELIT_FCLIP_INNERRIGHTWALL ) +#define FEELIT_FCLIP_ANYWALL ( FEELIT_FCLIP_OUTERANYWALL | FEELIT_FCLIP_INNERANYWALL ) + +typedef struct FEELIT_EFFESCAPE { + DWORD dwSize; /* sizeof( FEELIT_EFFESCAPE ) */ + DWORD dwCommand; /* Driver-specific command number */ + LPVOID lpvInBuffer; /* Buffer containing data required to perform the operation */ + DWORD cbInBuffer; /* Size, in bytes, of lpvInBuffer */ + LPVOID lpvOutBuffer; /* Buffer in which the operation's output data is returned */ + DWORD cbOutBuffer; /* Size, in bytes, of lpvOutBuffer */ +} FEELIT_EFFESCAPE, *LPFEELIT_EFFESCAPE; + + +#ifndef IS_VXD + +#undef INTERFACE +#define INTERFACE IFeelitEffect + +DECLARE_INTERFACE_(IFeelitEffect, IUnknown) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IFeelitEffect methods ***/ + STDMETHOD(GetEffectGuid)(THIS_ LPGUID) PURE; + STDMETHOD(GetParameters)(THIS_ LPFEELIT_EFFECT,DWORD) PURE; + STDMETHOD(SetParameters)(THIS_ LPCFEELIT_EFFECT,DWORD) PURE; + STDMETHOD(Start)(THIS_ DWORD,DWORD) PURE; + STDMETHOD(Stop)(THIS) PURE; + STDMETHOD(GetEffectStatus)(THIS_ LPDWORD) PURE; + STDMETHOD(Download)(THIS) PURE; + STDMETHOD(Unload)(THIS) PURE; + STDMETHOD(Escape)(THIS_ LPFEELIT_EFFESCAPE) PURE; +}; + +typedef struct IFeelitEffect *LPIFEELIT_EFFECT; + + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IFeelitEffect_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IFeelitEffect_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IFeelitEffect_Release(p) (p)->lpVtbl->Release(p) +#define IFeelitEffect_GetEffectGuid(p,a) (p)->lpVtbl->GetEffectGuid(p,a) +#define IFeelitEffect_GetParameters(p,a,b) (p)->lpVtbl->GetParameters(p,a,b) +#define IFeelitEffect_SetParameters(p,a,b) (p)->lpVtbl->SetParameters(p,a,b) +#define IFeelitEffect_Start(p,a,b) (p)->lpVtbl->Start(p,a,b) +#define IFeelitEffect_Stop(p) (p)->lpVtbl->Stop(p) +#define IFeelitEffect_GetEffectStatus(p,a) (p)->lpVtbl->GetEffectStatus(p,a) +#define IFeelitEffect_Download(p) (p)->lpVtbl->Download(p) +#define IFeelitEffect_Unload(p) (p)->lpVtbl->Unload(p) +#define IFeelitEffect_Escape(p,a) (p)->lpVtbl->Escape(p,a) +#else +#define IFeelitEffect_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IFeelitEffect_AddRef(p) (p)->AddRef() +#define IFeelitEffect_Release(p) (p)->Release() +#define IFeelitEffect_GetEffectGuid(p,a) (p)->GetEffectGuid(a) +#define IFeelitEffect_GetParameters(p,a,b) (p)->GetParameters(a,b) +#define IFeelitEffect_SetParameters(p,a,b) (p)->SetParameters(a,b) +#define IFeelitEffect_Start(p,a,b) (p)->Start(a,b) +#define IFeelitEffect_Stop(p) (p)->Stop() +#define IFeelitEffect_GetEffectStatus(p,a) (p)->GetEffectStatus(a) +#define IFeelitEffect_Download(p) (p)->Download() +#define IFeelitEffect_Unload(p) (p)->Unload() +#define IFeelitEffect_Escape(p,a) (p)->Escape(a) +#endif + + +typedef struct FEELIT_ENCLOSURE { + DWORD dwSize; /* sizeof(FEELIT_ENCLOSURE) */ + RECT rectBoundary; /* Rectangle defining the boundaries of the effect, in screen coords */ + DWORD dwTopAndBottomWallThickness; /* Thickness (pixels) of top/bottom walls. Must be < rectOutside.Height()/2 */ + DWORD dwLeftAndRightWallThickness; /* Thickness (pixels) of left/right walls. Must be < rectOutside.Width()/2 */ + LONG lTopAndBottomWallStiffness; /* Stiffness of horizontal borders */ + LONG lLeftAndRightWallStiffness; /* Stiffness of vertical borders */ + DWORD dwStiffnessMask; /* Borders where stiffness is turned on (FEELIT_FSTIFF*) */ + DWORD dwClippingMask; /* Borders where clipping is turned on (FEELIT_FCLIP*) */ + DWORD dwTopAndBottomWallSaturation; /* Saturation level of spring effect for top/bottom borders */ + DWORD dwLeftAndRightWallSaturation; /* Saturation level of spring effect for left/right borders */ + LPIFEELIT_EFFECT piInsideEffect; /* Interface pointer to effect active in inner area of the enclosure */ +} FEELIT_ENCLOSURE, *LPFEELIT_ENCLOSURE; +typedef const FEELIT_ENCLOSURE *LPCFEELIT_ENCLOSURE; + + +typedef struct FEELIT_ELLIPSE { + DWORD dwSize; /* sizeof(FEELIT_ELLIPSE) */ + RECT rectBoundary; /* Rectangle which circumscribes the ellipse (screen coords) */ + DWORD dwWallThickness; /* Thickness (pixels) of ellipse wall at its thickest point */ + LONG lStiffness; /* Stiffness of ellipse borders */ + DWORD dwStiffnessMask; /* Borders where stiffness is turned on (FEELIT_FSTIFF*) */ + DWORD dwClippingMask; /* Borders where clipping is turned on (FEELIT_FCLIP*) */ + DWORD dwSaturation; /* Saturation level of spring effect for ellipse borders */ + LPIFEELIT_EFFECT piInsideEffect; /* Interface pointer to effect active in inner area of the ellipse */ +} FEELIT_ELLIPSE, *LPFEELIT_ELLIPSE; +typedef const FEELIT_ELLIPSE *LPCFEELIT_ELLIPSE; + +#endif // IS_VXD + +/**************************************************************************** + * + * IFeelitDevice + * + ****************************************************************************/ + +/* Device types */ +#define FEELIT_DEVICETYPE_DEVICE 1 +#define FEELIT_DEVICETYPE_MOUSE 2 +#define FEELIT_DEVICETYPE_HID 0x00010000 + +/* Device subtypes */ +#define FEELIT_DEVICETYPEMOUSE_UNKNOWN 1 +#define FEELIT_DEVICETYPEMOUSE_TRADITIONAL_FF 2 + +/* Device type macros */ +#define GET_FEELIT_DEVICE_TYPE(dwDevType) LOBYTE(dwDevType) +#define GET_FEELIT_DEVICE_SUBTYPE(dwDevType) HIBYTE(dwDevType) + +typedef struct FEELIT_DEVCAPS { + DWORD dwSize; /* sizeof( FEELIT_DEVCAPS ) */ + DWORD dwFlags; /* FEELIT_FDEVCAPS_* */ + DWORD dwDevType; /* FEELIT_DEVICETYPE* */ + DWORD dwAxes; /* No. of axes available on the device */ + DWORD dwButtons; /* No. of buttons available on the device */ + DWORD dwPOVs; /* No. of point-of-view controllers on the device */ + DWORD dwFFSamplePeriod; /* Min. time btwn playback of consec. raw force commands */ + DWORD dwFFMinTimeResolution; /* Min. time, in microseconds, the device can resolve */ + DWORD dwFirmwareRevision; /* Firmware revision number of the device */ + DWORD dwHardwareRevision; /* Hardware revision number of the device */ + DWORD dwFFDriverVersion; /* Version number of the device driver */ +} FEELIT_DEVCAPS, *LPFEELIT_DEVCAPS; +typedef const FEELIT_DEVCAPS *LPCFEELIT_DEVCAPS; + +/* Device capabilities flags */ +#define FEELIT_FDEVCAPS_ATTACHED 0x00000001 +#define FEELIT_FDEVCAPS_POLLEDDEVICE 0x00000002 +#define FEELIT_FDEVCAPS_EMULATED 0x00000004 +#define FEELIT_FDEVCAPS_POLLEDDATAFORMAT 0x00000008 +#define FEELIT_FDEVCAPS_FORCEFEEDBACK 0x00000100 +#define FEELIT_FDEVCAPS_FFATTACK 0x00000200 +#define FEELIT_FDEVCAPS_FFFADE 0x00000400 +#define FEELIT_FDEVCAPS_SATURATION 0x00000800 +#define FEELIT_FDEVCAPS_POSNEGCOEFFICIENTS 0x00001000 +#define FEELIT_FDEVCAPS_POSNEGSATURATION 0x00002000 +#define FEELIT_FDEVCAPS_DEADBAND 0x00004000 + + +/* Data Format Type Flags */ + +#define FEELIT_FOBJDATAFMT_ALL 0x00000000 + +#define FEELIT_FOBJDATAFMT_RELAXIS 0x00000001 +#define FEELIT_FOBJDATAFMT_ABSAXIS 0x00000002 +#define FEELIT_FOBJDATAFMT_AXIS 0x00000003 + +#define FEELIT_FOBJDATAFMT_PSHBUTTON 0x00000004 +#define FEELIT_FOBJDATAFMT_TGLBUTTON 0x00000008 +#define FEELIT_FOBJDATAFMT_BUTTON 0x0000000C + +#define FEELIT_FOBJDATAFMT_POV 0x00000010 + +#define FEELIT_FOBJDATAFMT_COLLECTION 0x00000040 +#define FEELIT_FOBJDATAFMT_NODATA 0x00000080 +#define FEELIT_FOBJDATAFMT_FFACTUATOR 0x01000000 +#define FEELIT_FOBJDATAFMT_FFEFFECTTRIGGER 0x02000000 +#define FEELIT_FOBJDATAFMT_NOCOLLECTION 0x00FFFF00 + +#define FEELIT_FOBJDATAFMT_ANYINSTANCE 0x00FFFF00 +#define FEELIT_FOBJDATAFMT_INSTANCEMASK FEELIT_FOBJDATAFMT_ANYINSTANCE + + +/* Data Format Type Macros */ +#define FEELIT_OBJDATAFMT_MAKEINSTANCE(n) ((WORD)(n) << 8) +#define FEELIT_OBJDATAFMT_GETTYPE(n) LOBYTE(n) +#define FEELIT_OBJDATAFMT_GETINSTANCE(n) LOWORD((n) >> 8) +#define FEELIT_OBJDATAFMT_ENUMCOLLECTION(n) ((WORD)(n) << 8) + +#ifndef IS_VXD + +typedef struct _FEELIT_OBJECTDATAFORMAT { + const GUID *pguid; /* Unique ID for the axis, button, or other input source. */ + DWORD dwOfs; /* Offset in data packet where input source data is stored */ + DWORD dwType; /* Device type describing the object. (FEELIT_FOBJDATAFMT_*) */ + DWORD dwFlags; /* Aspect flags. Zero or more of FEELIT_FDEVOBJINST_ASPECT* */ +} FEELIT_OBJECTDATAFORMAT, *LPFEELIT_OBJECTDATAFORMAT; +typedef const FEELIT_OBJECTDATAFORMAT *LPCFEELIT_OBJECTDATAFORMAT; + +typedef struct _FEELIT_DATAFORMAT { + DWORD dwSize; /* sizeof( FEELIT_DATAFORMAT ) */ + DWORD dwObjSize; /* sizeof( FEELIT_OBJECTDATAFORMAT ) */ + DWORD dwFlags; /* One of FEELIT_FDATAFORMAT_* */ + DWORD dwDataSize; /* Size, in bytes, of data packet returned by the device */ + DWORD dwNumObjs; /* Number of object in the rgodf array */ + LPFEELIT_OBJECTDATAFORMAT rgodf; /* Ptr to array of FEELIT_OBJECTDATAFORMAT */ +} FEELIT_DATAFORMAT, *LPFEELIT_DATAFORMAT; +typedef const FEELIT_DATAFORMAT *LPCFEELIT_DATAFORMAT; + +/* Data Format Flags */ +#define FEELIT_FDATAFORMAT_ABSAXIS 0x00000001 +#define FEELIT_FDATAFORMAT_RELAXIS 0x00000002 + +/* Predefined Data Formats */ +extern const FEELIT_DATAFORMAT c_dfFeelitMouse; + +typedef struct FEELIT_DEVICEOBJECTINSTANCE { + DWORD dwSize; /* sizeof( FEELIT_DEVICEOBJECTINSTANCE ) */ + GUID guidType; /* Optional unique ID indicating object type */ + DWORD dwOfs; /* Offset within data format for data from this object */ + DWORD dwType; /* Device type describing the object. (FEELIT_FOBJDATAFMT_*) */ + DWORD dwFlags; /* Zero or more of FEELIT_FDEVOBJINST_* */ + CHAR tszName[MAX_PATH]; /* Name of object (e.g. "X-Axis") */ + DWORD dwFFMaxForce; /* Mag. of max force created by actuator for this object */ + DWORD dwFFForceResolution; /* Force resolution of the actuator for this object */ + WORD wCollectionNumber; /* RESERVED */ + WORD wDesignatorIndex; /* RESERVED */ + WORD wUsagePage; /* HID usage page associated with the object */ + WORD wUsage; /* HID usage associated with the object */ + DWORD dwDimension; /* Dimensional units in which object's value is reported */ + WORD wExponent; /* Exponent to associate with the demension */ + WORD wReserved; +} FEELIT_DEVICEOBJECTINSTANCE, *LPFEELIT_DEVICEOBJECTINSTANCE; +typedef const FEELIT_DEVICEOBJECTINSTANCE *LPCFEELIT_DEVICEOBJECTINSTANCE; + +typedef BOOL (FAR PASCAL * LPFEELIT_ENUMDEVICEOBJECTSCALLBACK)(LPCFEELIT_DEVICEOBJECTINSTANCE, LPVOID); + +/* Device Object Instance Flags */ +#define FEELIT_FDEVOBJINST_FFACTUATOR 0x00000001 +#define FEELIT_FDEVOBJINST_FFEFFECTTRIGGER 0x00000002 +#define FEELIT_FDEVOBJINST_POLLED 0x00008000 +#define FEELIT_FDEVOBJINST_ASPECTPOSITION 0x00000100 +#define FEELIT_FDEVOBJINST_ASPECTVELOCITY 0x00000200 +#define FEELIT_FDEVOBJINST_ASPECTACCEL 0x00000300 +#define FEELIT_FDEVOBJINST_ASPECTFORCE 0x00000400 +#define FEELIT_FDEVOBJINST_ASPECTMASK 0x00000F00 + +#endif // IS_VXD + +typedef struct FEELIT_PROPHEADER { + DWORD dwSize; /* Size of enclosing struct, to which this struct is header */ + DWORD dwHeaderSize; /* sizeof ( FEELIT_PROPHEADER ) */ + DWORD dwObj; /* Object for which the property is to be accessed */ + DWORD dwHow; /* Specifies how dwObj is interpreted. ( FEELIT_FPROPHEADER_* ) */ +} FEELIT_PROPHEADER, *LPFEELIT_PROPHEADER; +typedef const FEELIT_PROPHEADER *LPCFEELIT_PROPHEADER; + +/* Prop header flags */ +#define FEELIT_FPROPHEADER_DEVICE 0 +#define FEELIT_FPROPHEADER_BYOFFSET 1 +#define FEELIT_FPROPHEADER_BYID 2 + +typedef struct FEELIT_PROPDWORD { + FEELIT_PROPHEADER feelitph; /* Feelit property header struct */ + DWORD dwData; /* Property-specific value being retrieved */ +} FEELIT_PROPDWORD, *LPFEELIT_PROPDWORD; +typedef const FEELIT_PROPDWORD *LPCFEELIT_PROPDWORD; + +typedef struct FEELIT_PROPRANGE { + FEELIT_PROPHEADER feelitph; /* Feelit property header struct */ + LONG lMin; /* Lower limit of range, inclusive */ + LONG lMax; /* Upper limit of range, inclusive */ +} FEELIT_PROPRANGE, *LPFEELIT_PROPRANGE; +typedef const FEELIT_PROPRANGE *LPCFEELIT_PROPRANGE; + +#define FEELIT_PROPRANGE_NOMIN ((LONG)0x80000000) +#define FEELIT_PROPRANGE_NOMAX ((LONG)0x7FFFFFFF) + +#ifdef __cplusplus +#define MAKE_FEELIT_PROP(prop) (*(const GUID *)(prop)) +#else +#define MAKE_FEELIT_PROP(prop) ((REFGUID)(prop)) +#endif + +#define FEELIT_PROP_BUFFERSIZE MAKE_FEELIT_PROP(1) + +#define FEELIT_PROP_AXISMODE MAKE_FEELIT_PROP(2) + +#define FEELIT_PROPAXISMODE_ABS 0 +#define FEELIT_PROPAXISMODE_REL 1 + +#define FEELIT_PROP_GRANULARITY MAKE_FEELIT_PROP(3) + +#define FEELIT_PROP_RANGE MAKE_FEELIT_PROP(4) + +#define FEELIT_PROP_DEADZONE MAKE_FEELIT_PROP(5) + +#define FEELIT_PROP_SATURATION MAKE_FEELIT_PROP(6) + +#define FEELIT_PROP_FFGAIN MAKE_FEELIT_PROP(7) + +#define FEELIT_PROP_FFLOAD MAKE_FEELIT_PROP(8) + +#define FEELIT_PROP_AUTOCENTER MAKE_FEELIT_PROP(9) + +#define FEELIT_PROPAUTOCENTER_OFF 0 +#define FEELIT_PROPAUTOCENTER_ON 1 + +#define FEELIT_PROP_CALIBRATIONMODE MAKE_FEELIT_PROP(10) + +#define FEELIT_PROPCALIBRATIONMODE_COOKED 0 +#define FEELIT_PROPCALIBRATIONMODE_RAW 1 + +// Device configuration/control, for use by control panels + +#define FEELIT_PROP_DEVICEGAIN MAKE_FEELIT_PROP(11) +#define FEELIT_PROP_BALLISTICS MAKE_FEELIT_PROP(12) + +typedef struct FEELIT_PROPBALLISTICS { + FEELIT_PROPHEADER feelitph; /* Feelit property header struct */ + INT Sensitivity; /* Property-specific value */ + INT LowThreshhold; /* Property-specific value */ + INT HighThreshhold; /* Property-specific value */ +} FEELIT_PROPBALLISTICS, *LPFEELIT_PROPBALLISTICS; +typedef const FEELIT_PROPBALLISTICS *LPCFEELIT_PROPBALLISTICS; + +#define FEELIT_PROP_SCREENSIZE MAKE_FEELIT_PROP(13) + +typedef struct FEELIT_PROPSCREENSIZE { + FEELIT_PROPHEADER feelitph; /* Feelit property header struct */ + DWORD dwXScreenSize; /* Max X screen coord value */ + DWORD dwYScreenSize; /* Max Y screen coord value */ +} FEELIT_PROPSCREENSIZE, *LPFEELIT_PROPSCREENSIZE; +typedef const FEELIT_PROPSCREENSIZE *LPCFEELIT_PROPSCREENSIZE; + +#define FEELIT_PROP_ABSOLUTEMODE MAKE_FEELIT_PROP(14) + +typedef struct FEELIT_PROPABSOLUTEMODE { + FEELIT_PROPHEADER feelitph; /* Feelit property header struct */ + BOOL bAbsMode; /* TRUE for Absolute mode FALSE for Relative mode */ +} FEELIT_PROPABSOLUTEMODE, *LPFEELIT_PROPABSOLUTEMODE; +typedef const FEELIT_PROPABSOLUTEMODE *LPCFEELIT_PROPABSOLUTEMODE; + + +#define FEELIT_PROP_DEVICEMODE MAKE_FEELIT_PROP(15) +#define FEELIT_PROPDEVICEMODE_MOUSE 1 +#define FEELIT_PROPDEVICEMODE_JOYSTICK 2 + + +typedef struct FEELIT_DEVICEOBJECTDATA { + DWORD dwOfs; /* Offset into current data format of this data's object */ + DWORD dwData; /* Data obtained from the device */ + DWORD dwTimeStamp; /* Tick count, in milliseconds, at which event was generated */ + DWORD dwSequence; /* Sequence number for this event */ +} FEELIT_DEVICEOBJECTDATA, *LPFEELIT_DEVICEOBJECTDATA; +typedef const FEELIT_DEVICEOBJECTDATA *LPCFEELIT_DEVICEOBJECTDATA; + +#define FEELIT_SEQUENCE_COMPARE(dwSequence1, cmp, dwSequence2) \ + ((int)((dwSequence1) - (dwSequence2)) cmp 0) + +/* GetDeviceData Flags */ +#define FEELIT_FGETDEVDATA_PEEK 0x00000001 + +/* Cooperative Level Flags */ +#define FEELIT_FCOOPLEVEL_FOREGROUND 0x00000004 +#define FEELIT_FCOOPLEVEL_BACKGROUND 0x00000008 + +#ifndef IS_VXD + +typedef struct FEELIT_DEVICEINSTANCE { + DWORD dwSize; /* sizeof ( FEELIT_DEVICEINSTANCE ) */ + GUID guidInstance; /* Unique id for instance of device */ + GUID guidProduct; /* Unique id for the product */ + DWORD dwDevType; /* Device type (FEELIT_DEVICETYPE*) */ + CHAR tszInstanceName[MAX_PATH]; /* Friendly name for the instance (e.g. "Feelit Mouse 1") */ + CHAR tszProductName[MAX_PATH]; /* Friendly name for the product (e.g. "Feelit Mouse") */ + GUID guidFFDriver; /* Unique id for the driver being used for force feedback */ + WORD wUsagePage; /* HID usage page code (if the device driver is a HID device) */ + WORD wUsage; /* HID usage code (if the device driver is a HID device) */ +} FEELIT_DEVICEINSTANCE, *LPFEELIT_DEVICEINSTANCE; +typedef const FEELIT_DEVICEINSTANCE *LPCFEELIT_DEVICEINSTANCE; + +#endif // IS_VXD + +#define FEELIT_FCOMMAND_RESET 0x00000001 +#define FEELIT_FCOMMAND_STOPALL 0x00000002 +#define FEELIT_FCOMMAND_PAUSE 0x00000004 +#define FEELIT_FCOMMAND_CONTINUE 0x00000008 +#define FEELIT_FCOMMAND_SETACTUATORSON 0x00000010 +#define FEELIT_FCOMMAND_SETACTUATORSOFF 0x00000020 + +#define FEELIT_FDEVICESTATE_EMPTY 0x00000001 +#define FEELIT_FDEVICESTATE_STOPPED 0x00000002 +#define FEELIT_FDEVICESTATE_PAUSED 0x00000004 +#define FEELIT_FDEVICESTATE_ACTUATORSON 0x00000010 +#define FEELIT_FDEVICESTATE_ACTUATORSOFF 0x00000020 +#define FEELIT_FDEVICESTATE_POWERON 0x00000040 +#define FEELIT_FDEVICESTATE_POWEROFF 0x00000080 +#define FEELIT_FDEVICESTATE_SAFETYSWITCHON 0x00000100 +#define FEELIT_FDEVICESTATE_SAFETYSWITCHOFF 0x00000200 +#define FEELIT_FDEVICESTATE_USERFFSWITCHON 0x00000400 +#define FEELIT_FDEVICESTATE_USERFFSWITCHOFF 0x00000800 +#define FEELIT_FDEVICESTATE_DEVICELOST 0x80000000 + +#ifndef IS_VXD + +typedef struct FEELIT_EFFECTINFO { + DWORD dwSize; /* sizeof( FEELIT_EFFECTINFO ) */ + GUID guid; /* Unique ID of the effect */ + DWORD dwEffType; /* Zero or more of FEELIT_FEFFECTTYPE_* */ + DWORD dwStaticParams; /* All params supported. Zero or more of FEELIT_FPARAM_* */ + DWORD dwDynamicParams; /* Params modifiable while effect playing. (FEELIT_FPARAM_*) */ + CHAR tszName[MAX_PATH]; /* Name of effect (e.g. "Enclosure" ) */ +} FEELIT_EFFECTINFO, *LPFEELIT_EFFECTINFO; +typedef const FEELIT_EFFECTINFO *LPCFEELIT_EFFECTINFO; + +typedef BOOL (FAR PASCAL * LPFEELIT_ENUMEFFECTSCALLBACK)(LPCFEELIT_EFFECTINFO, LPVOID); +typedef BOOL (FAR PASCAL * LPFEELIT_ENUMCREATEDEFFECTOBJECTSCALLBACK)(LPFEELIT_EFFECT, LPVOID); + +#endif // IS_VXD + + +/* + Feelit Events + +Feelit events are defined using a FEELIT_EVENT struct. They are created by +passing the struct to CreateFeelitEvent, which returns an HFEELITEVENT handle. +Feelit notifies clients that Feelit Event has triggered by sending a message to +the window handle associated with the event. Window handles are associated with +events using the hWndEventHandler param. of the FEELIT_EVENT struct. The window +message that Feelit sends to notify of an event, contains information in the +WPARAM and LPARAM as described below. + +DURING INITIALIZATION: +const UINT g_wmFeelitEvent = RegisterWindowMessage( FEELIT_EVENT_MSG_STRING ); + +IN MESSAGE LOOP: +if ( msgID == g_wmFeelitEvent ) +{ + WORD wRef = LOWORD(wParam); // 16-bit app-defined event id + WORD wfTriggers = HIWORD(wParam); // Trigger Flags + short xForce = (short) LOWORD(lParam); // Force applied along X-axis + short yForce = (short) HIWORD(lParam); // Force applied along Y-axis +} + +*/ + +#define FEELIT_EVENT_MSG_STRING "FEELIT_EVENT_MSG" + +typedef HANDLE HFEELITEVENT, *LPHFEELITEVENT; /* Handle type used to manage Feelit Events */ + +typedef struct FEELIT_EVENT { + DWORD dwSize; /* sizeof(FEELIT_EVENT) */ + HWND hWndEventHandler; /* Handle of window to which event msgs are sent */ + WORD wRef; /* 16-bit app-defined value to identify the event to the app */ + DWORD dwEventTriggerMask; /* Mask specifying events which trigger the callback (FEELIT_FTRIG*) */ + LPIFEELIT_EFFECT piEffect; /* Effect, if any, that this event is associated with */ +} FEELIT_EVENT, *LPFEELIT_EVENT; + +typedef const FEELIT_EVENT *LPCFEELIT_EVENT; + + +/* Event Trigger Flags */ + +#define FEELIT_FTRIG_NONE 0x00000000 +#define FEELIT_FTRIG_ENTER 0x00000001 +#define FEELIT_FTRIG_EXIT 0x00000002 +#define FEELIT_FTRIG_OUTER 0x00000004 +#define FEELIT_FTRIG_INBOUND FEELIT_FTRIG_OUTER +#define FEELIT_FTRIG_INNER 0x00000008 +#define FEELIT_FTRIG_OUTBOUND FEELIT_FTRIG_INNER +#define FEELIT_FTRIG_TOPWALL 0x00000010 +#define FEELIT_FTRIG_BOTTOMWALL 0x00000020 +#define FEELIT_FTRIG_LEFTWALL 0x00000040 +#define FEELIT_FTRIG_RIGHTWALL 0x00000080 +#define FEELIT_FTRIG_ANYWALL ( FEELIT_FTRIG_TOPWALL | FEELIT_FTRIG_BOTTOMWALL | FEELIT_FTRIG_LEFTWALL | FEELIT_FTRIG_RIGHTWALL ) +#define FEELIT_FTRIG_ONENTERANY ( FEELIT_FTRIG_ENTER | FEELIT_FTRIG_ANYWALL ) +#define FEELIT_FTRIG_ONEXITANY ( FEELIT_FTRIG_EXIT | FEELIT_FTRIG_ANYWALL ) +#define FEELIT_FTRIG_ONOUTERANY ( FEELIT_FTRIG_OUTER | FEELIT_FTRIG_ANYWALL ) +#define FEELIT_FTRIG_ONINBOUNDANY FEELIT_FTRIG_ONOUTERANY +#define FEELIT_FTRIG_ONINNERANY ( FEELIT_FTRIG_INNER | FEELIT_FTRIG_ANYWALL ) +#define FEELIT_FTRIG_ONOUTBOUNDANY FEELIT_FTRIG_ONINNERANY +#define FEELIT_FTRIG_ONANYENCLOSURE ( FEELIT_FTRIG_ONENTERANY | FEELIT_FTRIG_ONEXITANY | FEELIT_FTRIG_ONOUTERANY | FEELIT_FTRIG_ONINNERANY ) + +#define FEELIT_FTRIG_ONSCROLL 0x0000100 +#define FEELIT_FTRIG_ONEFFECTCOMPLETION 0x0000200 + +#ifndef IS_VXD + +#undef INTERFACE +#define INTERFACE IFeelitDevice + +DECLARE_INTERFACE_(IFeelitDevice, IUnknown) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IFeelitDevice methods ***/ + STDMETHOD(GetCapabilities)(THIS_ LPFEELIT_DEVCAPS) PURE; + STDMETHOD(EnumObjects)(THIS_ LPFEELIT_ENUMDEVICEOBJECTSCALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(GetProperty)(THIS_ REFGUID,LPFEELIT_PROPHEADER) PURE; + STDMETHOD(SetProperty)(THIS_ REFGUID,LPCFEELIT_PROPHEADER) PURE; + STDMETHOD(Acquire)(THIS) PURE; + STDMETHOD(Unacquire)(THIS) PURE; + STDMETHOD(GetDeviceState)(THIS_ DWORD,LPVOID) PURE; + STDMETHOD(GetDeviceData)(THIS_ DWORD,LPFEELIT_DEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(SetDataFormat)(THIS_ LPCFEELIT_DATAFORMAT) PURE; + STDMETHOD(SetEventNotification)(THIS_ HANDLE) PURE; + STDMETHOD(SetCooperativeLevel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(GetObjectInfo)(THIS_ LPFEELIT_DEVICEOBJECTINSTANCE,DWORD,DWORD) PURE; + STDMETHOD(GetDeviceInfo)(THIS_ LPFEELIT_DEVICEINSTANCE) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(CreateEffect)(THIS_ LPCFEELIT_EFFECT,LPIFEELIT_EFFECT *,LPUNKNOWN) PURE; + STDMETHOD(EnumEffects)(THIS_ LPFEELIT_ENUMEFFECTSCALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(GetEffectInfo)(THIS_ LPFEELIT_EFFECTINFO,REFGUID) PURE; + STDMETHOD(GetForceFeedbackState)(THIS_ LPDWORD) PURE; + STDMETHOD(SendForceFeedbackCommand)(THIS_ DWORD) PURE; + STDMETHOD(EnumCreatedEffectObjects)(THIS_ LPFEELIT_ENUMCREATEDEFFECTOBJECTSCALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(Escape)(THIS_ LPFEELIT_EFFESCAPE) PURE; + STDMETHOD(Poll)(THIS) PURE; + STDMETHOD(SendDeviceData)(THIS_ DWORD,LPFEELIT_DEVICEOBJECTDATA,LPDWORD,DWORD) PURE; + STDMETHOD(CreateFeelitEvent)(THIS_ LPCFEELIT_EVENT,LPHFEELITEVENT) PURE; + STDMETHOD(DestroyFeelitEvent)(THIS_ HFEELITEVENT) PURE; + STDMETHOD(EnableFeelitEvent)(THIS_ HFEELITEVENT,BOOL) PURE; + STDMETHOD(SetEventNotificationPeriodicity)(THIS_ DWORD) PURE; +}; + +typedef struct IFeelitDevice *LPIFEELIT_DEVICE; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IFeelitDevice_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IFeelitDevice_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IFeelitDevice_Release(p) (p)->lpVtbl->Release(p) +#define IFeelitDevice_GetCapabilities(p,a) (p)->lpVtbl->GetCapabilities(p,a) +#define IFeelitDevice_EnumObjects(p,a,b,c) (p)->lpVtbl->EnumObjects(p,a,b,c) +#define IFeelitDevice_GetProperty(p,a,b) (p)->lpVtbl->GetProperty(p,a,b) +#define IFeelitDevice_SetProperty(p,a,b) (p)->lpVtbl->SetProperty(p,a,b) +#define IFeelitDevice_Acquire(p) (p)->lpVtbl->Acquire(p) +#define IFeelitDevice_Unacquire(p) (p)->lpVtbl->Unacquire(p) +#define IFeelitDevice_GetDeviceState(p,a,b) (p)->lpVtbl->GetDeviceState(p,a,b) +#define IFeelitDevice_GetDeviceData(p,a,b,c,d) (p)->lpVtbl->GetDeviceData(p,a,b,c,d) +#define IFeelitDevice_SetDataFormat(p,a) (p)->lpVtbl->SetDataFormat(p,a) +#define IFeelitDevice_SetEventNotification(p,a) (p)->lpVtbl->SetEventNotification(p,a) +#define IFeelitDevice_SetCooperativeLevel(p,a,b) (p)->lpVtbl->SetCooperativeLevel(p,a,b) +#define IFeelitDevice_GetObjectInfo(p,a,b,c) (p)->lpVtbl->GetObjectInfo(p,a,b,c) +#define IFeelitDevice_GetDeviceInfo(p,a) (p)->lpVtbl->GetDeviceInfo(p,a) +#define IFeelitDevice_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b) +#define IFeelitDevice_CreateEffect(p,a,b,c,d) (p)->lpVtbl->CreateEffect(p,a,b,c,d) +#define IFeelitDevice_EnumEffects(p,a,b,c) (p)->lpVtbl->EnumEffects(p,a,b,c) +#define IFeelitDevice_GetEffectInfo(p,a,b) (p)->lpVtbl->GetEffectInfo(p,a,b) +#define IFeelitDevice_GetForceFeedbackState(p,a) (p)->lpVtbl->GetForceFeedbackState(p,a) +#define IFeelitDevice_SendForceFeedbackCommand(p,a) (p)->lpVtbl->SendForceFeedbackCommand(p,a) +#define IFeelitDevice_EnumCreatedEffectObjects(p,a,b,c) (p)->lpVtbl->EnumCreatedEffectObjects(p,a,b,c) +#define IFeelitDevice_Escape(p,a) (p)->lpVtbl->Escape(p,a) +#define IFeelitDevice_Poll(p) (p)->lpVtbl->Poll(p) +#define IFeelitDevice_SendDeviceData(p,a,b,c,d) (p)->lpVtbl->SendDeviceData(p,a,b,c,d) +#define IFeelitDevice_CreateFeelitEvent(p,a,b) (p)->lpVtbl->CreateFeelitEvent(p,a,b) +#define IFeelitDevice_DestroyFeelitEvent(p,a) (p)->lpVtbl->DestroyFeelitEvent(p,a) +#define IFeelitDevice_EnableFeelitEvent(p,a,b) (p)->lpVtbl->EnableFeelitEvent(p,a,b) +#define IFeelitDevice_SetEventNotificationPeriodicity(p,a) (p)->lpVtbl->SetEventNotificationPeriodicity(p,a) +#else +#define IFeelitDevice_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IFeelitDevice_AddRef(p) (p)->AddRef() +#define IFeelitDevice_Release(p) (p)->Release() +#define IFeelitDevice_GetCapabilities(p,a) (p)->GetCapabilities(a) +#define IFeelitDevice_EnumObjects(p,a,b,c) (p)->EnumObjects(a,b,c) +#define IFeelitDevice_GetProperty(p,a,b) (p)->GetProperty(a,b) +#define IFeelitDevice_SetProperty(p,a,b) (p)->SetProperty(a,b) +#define IFeelitDevice_Acquire(p) (p)->Acquire() +#define IFeelitDevice_Unacquire(p) (p)->Unacquire() +#define IFeelitDevice_GetDeviceState(p,a,b) (p)->GetDeviceState(a,b) +#define IFeelitDevice_GetDeviceData(p,a,b,c,d) (p)->GetDeviceData(a,b,c,d) +#define IFeelitDevice_SetDataFormat(p,a) (p)->SetDataFormat(a) +#define IFeelitDevice_SetEventNotification(p,a) (p)->SetEventNotification(a) +#define IFeelitDevice_SetCooperativeLevel(p,a,b) (p)->SetCooperativeLevel(a,b) +#define IFeelitDevice_GetObjectInfo(p,a,b,c) (p)->GetObjectInfo(a,b,c) +#define IFeelitDevice_GetDeviceInfo(p,a) (p)->GetDeviceInfo(a) +#define IFeelitDevice_RunControlPanel(p,a,b) (p)->RunControlPanel(a,b) +#define IFeelitDevice_CreateEffect(p,a,b,c,d) (p)->CreateEffect(a,b,c,d) +#define IFeelitDevice_EnumEffects(p,a,b,c) (p)->EnumEffects(a,b,c) +#define IFeelitDevice_GetEffectInfo(p,a,b) (p)->GetEffectInfo(a,b) +#define IFeelitDevice_GetForceFeedbackState(p,a) (p)->GetForceFeedbackState(a) +#define IFeelitDevice_SendForceFeedbackCommand(p,a) (p)->SendForceFeedbackCommand(a) +#define IFeelitDevice_EnumCreatedEffectObjects(p,a,b,c) (p)->EnumCreatedEffectObjects(a,b,c) +#define IFeelitDevice_Escape(p,a) (p)->Escape(a) +#define IFeelitDevice_Poll(p) (p)->Poll() +#define IFeelitDevice_SendDeviceData(p,a,b,c,d) (p)->SendDeviceData(a,b,c,d) +#define IFeelitDevice_CreateFeelitEvent(p,a,b) (p)->CreateFeelitEvent(a,b) +#define IFeelitDevice_DestroyFeelitEvent(p,a) (p)->DestroyFeelitEvent(a) +#define IFeelitDevice_EnableFeelitEvent(p,a,b) (p)->EnableFeelitEvent(a,b) +#define IFeelitDevice_SetEventNotificationPeriodicity(p,a) (p)->SetEventNotificationPeriodicity(a) +#endif + +#endif // IS_VXD + +/**************************************************************************** + * + * Mouse State + * + ****************************************************************************/ + +typedef struct _FEELIT_MOUSESTATE { + LONG lXpos; + LONG lYpos; + LONG lZpos; + LONG lXforce; + LONG lYforce; + LONG lZforce; + BYTE rgbButtons[4]; +} FEELIT_MOUSESTATE, *LPFEELIT_MOUSESTATE; + +#define FEELIT_MOUSEOFFSET_XAXIS FIELD_OFFSET(FEELIT_MOUSESTATE, lXpos) +#define FEELIT_MOUSEOFFSET_YAXIS FIELD_OFFSET(FEELIT_MOUSESTATE, lYpos) +#define FEELIT_MOUSEOFFSET_ZAXIS FIELD_OFFSET(FEELIT_MOUSESTATE, lZpos) +#define FEELIT_MOUSEOFFSET_XFORCE FIELD_OFFSET(FEELIT_MOUSESTATE, lXforce) +#define FEELIT_MOUSEOFFSET_YFORCE FIELD_OFFSET(FEELIT_MOUSESTATE, lYforce) +#define FEELIT_MOUSEOFFSET_ZFORCE FIELD_OFFSET(FEELIT_MOUSESTATE, lZforce) +#define FEELIT_MOUSEOFFSET_BUTTON0 (FIELD_OFFSET(FEELIT_MOUSESTATE, rgbButtons) + 0) +#define FEELIT_MOUSEOFFSET_BUTTON1 (FIELD_OFFSET(FEELIT_MOUSESTATE, rgbButtons) + 1) +#define FEELIT_MOUSEOFFSET_BUTTON2 (FIELD_OFFSET(FEELIT_MOUSESTATE, rgbButtons) + 2) +#define FEELIT_MOUSEOFFSET_BUTTON3 (FIELD_OFFSET(FEELIT_MOUSESTATE, rgbButtons) + 3) + + +/**************************************************************************** + * + * IFeelit + * + ****************************************************************************/ + +#define FEELIT_ENUM_STOP 0 +#define FEELIT_ENUM_CONTINUE 1 + +#ifndef IS_VXD +typedef BOOL (FAR PASCAL * LPFEELIT_ENUMDEVICESCALLBACK)(LPCFEELIT_DEVICEINSTANCE, LPVOID); +#endif // IS_VXD + +#define FEELIT_FENUMDEV_ALLDEVICES 0x00000000 +#define FEELIT_FENUMDEV_ATTACHEDONLY 0x00000001 +#define FEELIT_FENUMDEV_FORCEFEEDBACK 0x00000100 + +#ifndef IS_VXD + +#undef INTERFACE +#define INTERFACE IFeelit + +DECLARE_INTERFACE_(IFeelit, IUnknown) +{ + /*** IUnknown methods ***/ + STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE; + STDMETHOD_(ULONG,AddRef)(THIS) PURE; + STDMETHOD_(ULONG,Release)(THIS) PURE; + + /*** IFeelit methods ***/ + STDMETHOD(CreateDevice)(THIS_ REFGUID,LPIFEELIT_DEVICE *,LPUNKNOWN) PURE; + STDMETHOD(EnumDevices)(THIS_ DWORD,LPFEELIT_ENUMDEVICESCALLBACK,LPVOID,DWORD) PURE; + STDMETHOD(GetDeviceStatus)(THIS_ REFGUID) PURE; + STDMETHOD(RunControlPanel)(THIS_ HWND,DWORD) PURE; + STDMETHOD(Initialize)(THIS_ HINSTANCE,DWORD) PURE; +}; + +typedef struct IFeelit *LPIFEELIT; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IFeelit_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IFeelit_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IFeelit_Release(p) (p)->lpVtbl->Release(p) +#define IFeelit_CreateDevice(p,a,b,c) (p)->lpVtbl->CreateDevice(p,a,b,c) +#define IFeelit_EnumDevices(p,a,b,c,d) (p)->lpVtbl->EnumDevices(p,a,b,c,d) +#define IFeelit_GetDeviceStatus(p,a) (p)->lpVtbl->GetDeviceStatus(p,a) +#define IFeelit_RunControlPanel(p,a,b) (p)->lpVtbl->RunControlPanel(p,a,b) +#define IFeelit_Initialize(p,a,b) (p)->lpVtbl->Initialize(p,a,b) +#define IFeelit_FindDevice(p,a,b,c) (p)->lpVtbl->FindDevice(p,a,b,c) +#endif + +extern HRESULT WINAPI FeelitCreateA(HINSTANCE hinst, DWORD dwVersion, LPIFEELIT *ppFeelit, LPUNKNOWN punkOuter); +#define FeelitCreate FeelitCreateA + +#endif // IS_VXD + + +/**************************************************************************** + * + * Return Codes + * + ****************************************************************************/ + +/* + * The operation completed successfully. + */ +#define FEELIT_RESULT_OK S_OK + +/* + * The device exists but is not currently attached. + */ +#define FEELIT_RESULT_NOTATTACHED S_FALSE + +/* + * The device buffer overflowed. Some input was lost. + */ +#define FEELIT_RESULT_BUFFEROVERFLOW S_FALSE + +/* + * The change in device properties had no effect. + */ +#define FEELIT_RESULT_PROPNOEFFECT S_FALSE + +/* + * The operation had no effect. + */ +#define FEELIT_RESULT_NOEFFECT S_FALSE + +/* + * The device is a polled device. As a result, device buffering + * will not collect any data and event notifications will not be + * signalled until GetDeviceState is called. + */ +#define FEELIT_RESULT_POLLEDDEVICE ((HRESULT)0x00000002L) + +/* + * The parameters of the effect were successfully updated by + * IFeelitEffect::SetParameters, but the effect was not + * downloaded because the device is not exclusively acquired + * or because the FEELIT_FPARAM_NODOWNLOAD flag was passed. + */ +#define FEELIT_RESULT_DOWNLOADSKIPPED ((HRESULT)0x00000003L) + +/* + * The parameters of the effect were successfully updated by + * IFeelitEffect::SetParameters, but in order to change + * the parameters, the effect needed to be restarted. + */ +#define FEELIT_RESULT_EFFECTRESTARTED ((HRESULT)0x00000004L) + +/* + * The parameters of the effect were successfully updated by + * IFeelitEffect::SetParameters, but some of them were + * beyond the capabilities of the device and were truncated. + */ +#define FEELIT_RESULT_TRUNCATED ((HRESULT)0x00000008L) + +/* + * Equal to FEELIT_RESULT_EFFECTRESTARTED | FEELIT_RESULT_TRUNCATED. + */ +#define FEELIT_RESULT_TRUNCATEDANDRESTARTED ((HRESULT)0x0000000CL) + +/* + * The application requires a newer version of Feelit. + */ +#define FEELIT_ERROR_OLDFEELITVERSION \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_OLD_WIN_VERSION) + +/* + * The application was written for an unsupported prerelease version + * of Feelit. + */ +#define FEELIT_ERROR_BETAFEELITVERSION \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_RMODE_APP) + +/* + * The object could not be created due to an incompatible driver version + * or mismatched or incomplete driver components. + */ +#define FEELIT_ERROR_BADDRIVERVER \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_BAD_DRIVER_LEVEL) + +/* + * The device or device instance or effect is not registered with Feelit. + */ +#define FEELIT_ERROR_DEVICENOTREG REGDB_E_CLASSNOTREG + +/* + * The requested object does not exist. + */ +#define FEELIT_ERROR_NOTFOUND \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_FILE_NOT_FOUND) + +/* + * The requested object does not exist. + */ +#define FEELIT_ERROR_OBJECTNOTFOUND \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_FILE_NOT_FOUND) + +/* + * An invalid parameter was passed to the returning function, + * or the object was not in a state that admitted the function + * to be called. + */ +#define FEELIT_ERROR_INVALIDPARAM E_INVALIDARG + +/* + * The specified interface is not supported by the object + */ +#define FEELIT_ERROR_NOINTERFACE E_NOINTERFACE + +/* + * An undetermined error occured inside the Feelit subsystem + */ +#define FEELIT_ERROR_GENERIC E_FAIL + +/* + * The Feelit subsystem couldn't allocate sufficient memory to complete the + * caller's request. + */ +#define FEELIT_ERROR_OUTOFMEMORY E_OUTOFMEMORY + +/* + * The function called is not supported at this time + */ +#define FEELIT_ERROR_UNSUPPORTED E_NOTIMPL + +/* + * This object has not been initialized + */ +#define FEELIT_ERROR_NOTINITIALIZED \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_NOT_READY) + +/* + * This object is already initialized + */ +#define FEELIT_ERROR_ALREADYINITIALIZED \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_ALREADY_INITIALIZED) + +/* + * This object does not support aggregation + */ +#define FEELIT_ERROR_NOAGGREGATION CLASS_E_NOAGGREGATION + +/* + * Another app has a higher priority level, preventing this call from + * succeeding. + */ +#define FEELIT_ERROR_OTHERAPPHASPRIO E_ACCESSDENIED + +/* + * Access to the device has been lost. It must be re-acquired. + */ +#define FEELIT_ERROR_INPUTLOST \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_READ_FAULT) + +/* + * The operation cannot be performed while the device is acquired. + */ +#define FEELIT_ERROR_ACQUIRED \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_BUSY) + +/* + * The operation cannot be performed unless the device is acquired. + */ +#define FEELIT_ERROR_NOTACQUIRED \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_INVALID_ACCESS) + +/* + * The specified property cannot be changed. + */ +#define FEELIT_ERROR_READONLY E_ACCESSDENIED + +/* + * The device already has an event notification associated with it. + */ +#define FEELIT_ERROR_HANDLEEXISTS E_ACCESSDENIED + +/* + * Data is not yet available. + */ +#ifndef E_PENDING +#define E_PENDING 0x80070007L +#endif + +/* + * Unable to perform the requested operation because the user + * does not have sufficient privileges. + */ +#define FEELIT_ERROR_INSUFFICIENTPRIVS 0x80040200L + +/* + * The device is full. + */ +#define FEELIT_ERROR_DEVICEFULL 0x80040201L + +/* + * Not all the requested information fit into the buffer. + */ +#define FEELIT_ERROR_MOREDATA 0x80040202L + +/* + * The effect is not downloaded. + */ +#define FEELIT_ERROR_NOTDOWNLOADED 0x80040203L + +/* + * The device cannot be reinitialized because there are still effects + * attached to it. + */ +#define FEELIT_ERROR_HASEFFECTS 0x80040204L + +/* + * The operation cannot be performed unless the device is acquired + * in FEELIT_FCOOPLEVEL_EXCLUSIVE mode. + */ +#define FEELIT_ERROR_NOTEXCLUSIVEACQUIRED 0x80040205L + +/* + * The effect could not be downloaded because essential information + * is missing. For example, no axes have been associated with the + * effect, or no type-specific information has been created. + */ +#define FEELIT_ERROR_INCOMPLETEEFFECT 0x80040206L + +/* + * Attempted to read buffered device data from a device that is + * not buffered. + */ +#define FEELIT_ERROR_NOTBUFFERED 0x80040207L + +/* + * An attempt was made to modify parameters of an effect while it is + * playing. Not all hardware devices support altering the parameters + * of an effect while it is playing. + */ +#define FEELIT_ERROR_EFFECTPLAYING 0x80040208L + +/* + * An internal error occurred (inside the API or the driver) + */ +#define FEELIT_ERROR_INTERNAL 0x80040209L + +/* + * Effect set referenced by a command is not the active set + */ +#define FEELIT_ERROR_INACTIVE 0x8004020AL + +#ifdef __cplusplus +}; +#endif + + +#endif /* __FEELITAPI_INCLUDED__ */ + diff --git a/code/win32/FeelIt/fffx.cpp b/code/win32/FeelIt/fffx.cpp new file mode 100644 index 0000000..4a926ff --- /dev/null +++ b/code/win32/FeelIt/fffx.cpp @@ -0,0 +1,680 @@ +// Filename:- fffx.cpp (Force-Feedback FX) +// +// (Function names with "_FF_" beginnings are my internal stuff only, "FF_" beginnings are for external stuff) +// +#define INITGUID // this will need removing if already defined in someone else's module. Only one must exist in whole game + +#include "../../client/client.h" +#include "../win_local.h" +#include "ffc.h" +#include "fffx_feel.h" + +// these now MUST default to NULL... +// +CFeelDevice *g_pFeelDevice=NULL; +CFeelProject *g_pFeelProject=NULL; +CFeelSpring *g_pFeelSpring=NULL; + + +ffFX_e ffFXLoaded[MAX_CONCURRENT_FFFXs]; + +//extern HINSTANCE global_hInstance; +//extern HWND cl_hwnd; +extern WinVars_t g_wv; + + +extern cvar_t *in_joystick; +cvar_t *use_ff; +cvar_t *ff_defaultTension; + + +void _FF_ClearUsageArray(void); +void _FF_ClearFXSlot(int i); +void _FF_ClearCreatePlayFXSlot(int iSlotNum, ffFX_e fffx); +void _FF_CreatePlayFXSlot(int iSlotNum, ffFX_e fffx); +void _FF_PlayFXSlot(int iSlotNum); + + + +// externally accessed +qboolean FF_IsAvailable(void) +{ + return g_pFeelDevice?TRUE:FALSE; +} + +qboolean FF_IsMouse(void) +{ + if (g_pFeelDevice && (g_pFeelDevice->GetDeviceType() == FEEL_DEVICETYPE_MOUSE)) + return TRUE; + + return FALSE; +} + + +// 4 semi-useful CMD functions... +void CMD_FF_UseMouse(void) +{ + FF_Init(TRUE); +} + +void CMD_FF_UseJoy(void) +{ + FF_Init(FALSE); +} + +// arg = 0..3 +// +void CMD_FF_Tension(void) +{ + if (Cmd_Argc() != 2) + { + Com_Printf ("ff_tension (default = 1)\n"); + return; + } + + int iTension = atoi(Cmd_Argv(1)); + if (iTension<0 || iTension>3) + { + Com_Printf ("ff_tension \n"); + return; + } + + if (g_pFeelSpring) + { + Com_Printf(va("Setting tension %d\n",iTension)); + Cvar_Set(ff_defaultTension->name,va("%d",iTension)); + FF_SetTension(iTension); + } + else + { + Com_Printf("No spring device\n"); + } +} + + +typedef struct +{ + char* psName; + ffFX_e eFXNum; +}FFFX_LOOKUP; + +#define FFFX_ENTRY(blah) {#blah,(ffFX_e)fffx_ ## blah} +FFFX_LOOKUP FFFX_Lookup[fffx_NUMBEROF]= +{ + FFFX_ENTRY( RandomNoise ), + FFFX_ENTRY( AircraftCarrierTakeOff ), // this one is pointless / dumb + FFFX_ENTRY( BasketballDribble ), + FFFX_ENTRY( CarEngineIdle ), + FFFX_ENTRY( ChainsawIdle ), + FFFX_ENTRY( ChainsawInAction ), + FFFX_ENTRY( DieselEngineIdle ), + FFFX_ENTRY( Jump ), + FFFX_ENTRY( Land ), + FFFX_ENTRY( MachineGun ), + FFFX_ENTRY( Punched ), + FFFX_ENTRY( RocketLaunch ), + FFFX_ENTRY( SecretDoor ), + FFFX_ENTRY( SwitchClick ), + FFFX_ENTRY( WindGust ), + FFFX_ENTRY( WindShear ), // also pretty crap + FFFX_ENTRY( Pistol ), + FFFX_ENTRY( Shotgun ), + FFFX_ENTRY( Laser1 ), + FFFX_ENTRY( Laser2 ), + FFFX_ENTRY( Laser3 ), + FFFX_ENTRY( Laser4 ), + FFFX_ENTRY( Laser5 ), + FFFX_ENTRY( Laser6 ), + FFFX_ENTRY( OutOfAmmo ), + FFFX_ENTRY( LightningGun ), + FFFX_ENTRY( Missile ), + FFFX_ENTRY( GatlingGun ), + FFFX_ENTRY( ShortPlasma ), + FFFX_ENTRY( PlasmaCannon1 ), + FFFX_ENTRY( PlasmaCannon2 ), + FFFX_ENTRY( Cannon ) +}; + +void CMD_FF_Play(void) +{ + if (Cmd_Argc() != 2 && Cmd_Argc() != 3) + { + Com_Printf ("ff_play (where n = 0..%d) || ff_play name \"fxname\"\n",fffx_NUMBEROF-1); + return; + } + + ffFX_e eFX = fffx_NULL; + + if (!Q_stricmp(Cmd_Argv(1),"name")) + { + if (Cmd_Argc() != 3) + { + Com_Printf ("ff_play (where n = 0..%d) || ff_play name \"fxname\"\n",0,fffx_NUMBEROF-1); + return; + } + + for (int i=0; i=0 && eFX\n"); + return; + } + + long lSpring = atoi(Cmd_Argv(1)); + if (lSpring<0 || lSpring>10000) + { + Com_Printf ("ff_spring <0..10000>\n"); + return; + } + + if (g_pFeelSpring) + { + Com_Printf(va("Setting spring to %d\n",lSpring)); + FF_SetSpring(lSpring); + } + else + { + Com_Printf("No spring device\n"); + } +} + + +// Called once only during .exe lifetime... +// +void FF_Init(qboolean bTryMouseFirst) +{ + FF_Shutdown(); + + Cmd_AddCommand ("ff_usemouse", CMD_FF_UseMouse); + Cmd_AddCommand ("ff_usejoy", CMD_FF_UseJoy); + Cmd_AddCommand ("ff_tension", CMD_FF_Tension); + Cmd_AddCommand ("ff_spring", CMD_FF_Spring); + Cmd_AddCommand ("ff_play", CMD_FF_Play); + + // ==================================== + + Com_Printf("\n" S_COLOR_CYAN "------- Force Feedback Initialization -------\n"); + + // for the moment default to OFF until usage tables are in... + use_ff = Cvar_Get ("use_ff", "0", CVAR_ARCHIVE); + ff_defaultTension = Cvar_Get ("ff_defaultTension", "1", CVAR_ARCHIVE); + + // don't bother initializing if user specifically turned off force feedback... + // + if (!use_ff->value) + { + Com_Printf("...inhibited, not initializing\n\n"); + return; + } + + Com_Printf("Creating feedback device:\n"); + + if ( bTryMouseFirst ) + { + CFeelMouse* m_pFeelMouse = new CFeelMouse; + if (m_pFeelMouse) + { + if (m_pFeelMouse->Initialize( g_wv.hInstance, g_wv.hWnd)) + { + g_pFeelDevice = m_pFeelMouse; + } + else + { + delete m_pFeelMouse; + m_pFeelMouse = NULL; + } + } + } + + if (!g_pFeelDevice) + { + // try a general DI FF device... + // + CFeelDXDevice* m_pFeelDXDevice = new CFeelDXDevice; + if (m_pFeelDXDevice) + { + if (m_pFeelDXDevice->Initialize( g_wv.hInstance, g_wv.hWnd)) + { + g_pFeelDevice = m_pFeelDXDevice; + } + else + { + delete m_pFeelDXDevice; + m_pFeelDXDevice = NULL; + } + } + } + + +// g_pFeelDevice = CFeelDevice::CreateDevice(g_wv.hInstance, g_wv.hWnd); + if (!g_pFeelDevice) + { + Com_Printf("...no feedback devices found\n"); + return; + } + else + { + _FeelInitEffects(); + + for (int _i=0; _iGetDeviceType() == FEEL_DEVICETYPE_MOUSE) + { + Com_Printf("...found FEELit Mouse\n"); + g_pFeelDevice->UsesWin32MouseServices(FALSE); + } + else if (g_pFeelDevice->GetDeviceType() == FEEL_DEVICETYPE_DIRECTINPUT) + { + Com_Printf("...found feedback device\n"); + g_pFeelSpring = new CFeelSpring; + if (!g_pFeelSpring->Initialize( g_pFeelDevice, + 2000, //10000, // LONG lStiffness = FEEL_SPRING_DEFAULT_STIFFNESS + 10000, //5000, // DWORD dwSaturation = FEEL_SPRING_DEFAULT_SATURATION + 1000, //0, // DWORD dwDeadband = FEEL_SPRING_DEFAULT_DEADBAND // must be 0..n..10000 + FEEL_EFFECT_AXIS_BOTH, // DWORD dwfAxis = FEEL_EFFECT_AXIS_BOTH + FEEL_SPRING_DEFAULT_CENTER_POINT, // POINT pntCenter = FEEL_SPRING_DEFAULT_CENTER_POINT + FEEL_EFFECT_DEFAULT_DIRECTION_X, // LONG lDirectionX = FEEL_EFFECT_DEFAULT_DIRECTION_X + FEEL_EFFECT_DEFAULT_DIRECTION_Y, // LONG lDirectionY = FEEL_EFFECT_DEFAULT_DIRECTION_Y + TRUE // TRUE = rel coords, else screen coords // BOOL bUseDeviceCoordinates = FALSE + ) + ) + { + Com_Printf("...(no device return spring)\n"); + delete g_pFeelSpring; + g_pFeelSpring = NULL; + } + else + { + Com_Printf("...device return spring ok\n"); + FF_SetTension(ff_defaultTension->integer); // 0..3 + } + }// if (g_pFeelDevice->GetDeviceType() == FEEL_DEVICETYPE_DIRECTINPUT) + } +} + + +// call this at app shutdown... (or when switching controllers) +// +// (also called by FF_Init in case you're switching controllers so do everything +// as if-protected) +// +void FF_Shutdown(void) +{ + // note the check first before print, since this is called from the init code + // as well, and it'd be weird to see the sutdown string first... + // + if (g_pFeelSpring || g_pFeelDevice) + { + Com_Printf("\n" S_COLOR_CYAN "------- Force Feedback Shutdown -------\n"); + } + + if (g_pFeelSpring) + { + Com_Printf("...closing return spring\n"); + delete g_pFeelSpring; + g_pFeelSpring = NULL; + } + + if (g_pFeelDevice) + { + Com_Printf("...closing feedback device\n"); + _FF_ClearUsageArray(); + delete g_pFeelDevice; + g_pFeelDevice = NULL; + } + + Cmd_RemoveCommand ("ff_usemouse"); + Cmd_RemoveCommand ("ff_usejoy"); + Cmd_RemoveCommand ("ff_tension"); + Cmd_RemoveCommand ("ff_spring"); + Cmd_RemoveCommand ("ff_play"); +} + + + + + + + + + +void FF_EnsurePlaying(ffFX_e fffx) +{ + if (fffx<0 || fffx>=fffx_NUMBEROF) + return; + + // if user has specifically turned off force feedback at command line, + // or is not using the joystick as current input method (though this can be ignored because stick has a hands-on sensor), + // then forget it... + // + if (!use_ff->value) + return; + + + if (FF_IsAvailable()) + { + // Have we already got this FF FX loaded? + // + for (int i=0; i=fffx_NUMBEROF) + return; + + // if user has specifically turned off force feedback at command line, + // or is not using the joystick as current input method (though this can be ignored because stick has a hands-on sensor), + // then forget it... + // + if (!use_ff->value) + return; + + if (FF_IsAvailable()) + { + // first, search for an instance of this FF FX that's already loaded, if found, start it off again... + // + for (i=0; iinteger); + } +} + + + + + +void _FF_ClearUsageArray(void) +{ + int i; + + for (i=0; i=fffx_NUMBEROF) + return; + + if (FF_IsAvailable()) + { + for (int i=0; iStart()) + { + return FALSE; + } + bFXPlaying = TRUE; + + static POINT p={0,0}; + g_pFeelSpring->ChangeParameters(p, lSpring); + } + else + { + if (bFXPlaying && !g_pFeelSpring->Stop()) + { + return FALSE; + } + bFXPlaying = FALSE; + } + + return TRUE; + } + } + + return FALSE; +} + +// tension is 0 (none) to 3 (max)... +// +qboolean FF_SetTension(int iTension) +{ + static long lSpringValues[4] = {0, 1000, 5000, 10000}; + + if (iTension>3) + iTension=3; + if (iTension<0) + iTension=0; + + return FF_SetSpring(lSpringValues[iTension]); +} + +///////////////////////// eof ////////////////////////////// + diff --git a/code/win32/FeelIt/fffx_feel.cpp b/code/win32/FeelIt/fffx_feel.cpp new file mode 100644 index 0000000..57e41fc --- /dev/null +++ b/code/win32/FeelIt/fffx_feel.cpp @@ -0,0 +1,689 @@ +// Filename:- fffx_Feel.cpp (Force-Feedback FX) + +#include "../../client/client.h" +//#include "stdafx.h" +//#include "resource.h" +#include "fffx_feel.h" + +extern cvar_t* js_ffmult; + + +#define MAX_EFFECTS_IN_COMPOUND 3 // This needs to be at least 3 for now. I can add array bounds checking later + +CFeelEffect* g_pEffects[MAX_CONCURRENT_FFFXs][MAX_EFFECTS_IN_COMPOUND]; + +void _FeelInitEffects() +{ + for (int i = 0; i < MAX_CONCURRENT_FFFXs; i++) + { + for (int j = 0; j < MAX_EFFECTS_IN_COMPOUND; j++) + { + g_pEffects[i][j] = NULL; + } + } +} + +BOOL _FeelCreateEffect(int iSlotNum, ffFX_e fffx, CFeelDevice* pFeelDevice) +{ + BOOL success = TRUE; + FEELIT_ENVELOPE envelope; + envelope.dwSize = sizeof(FEELIT_ENVELOPE); + + switch (fffx) + { + case fffx_RandomNoise: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 7500 * js_ffmult->value, // magnitude + 95, // period + 10000, // duration + 0, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + g_pEffects[iSlotNum][1] = new CFeelPeriodic(GUID_Feel_SawtoothDown); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 8300 * js_ffmult->value, // magnitude + 160, // period + 10000, // duration + 9000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + g_pEffects[iSlotNum][2] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][2]))->InitializePolar(pFeelDevice, + 8300 * js_ffmult->value, // magnitude + 34, // period + 10000, // duration + 31000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + + success = FALSE; + break; + case fffx_AircraftCarrierTakeOff: + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 600000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 750000; + + g_pEffects[iSlotNum][0] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 0, // angle + 2500, // duration + 10000 * js_ffmult->value, // magnitude + &envelope))// envelope + + success = FALSE; + break; + case fffx_BasketballDribble: + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 40000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 30000; + + g_pEffects[iSlotNum][0] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 18000, // angle + 150, // duration + 5000 * js_ffmult->value, // magnitude + &envelope))// envelope + + success = FALSE; + break; + case fffx_CarEngineIdle: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 2500 * js_ffmult->value, // magnitude + 50, // period + 10000, // duration + 0, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_ChainsawIdle: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 3600 * js_ffmult->value, // magnitude + 60, // period + 1000, // duration + 9000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + g_pEffects[iSlotNum][1] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 4000 * js_ffmult->value, // magnitude + 100, // period + 1000, // duration + 18000, // angle + 4000, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_ChainsawInAction: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 6700 * js_ffmult->value, // magnitude + 60, // period + 1000, // duration + 9000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + g_pEffects[iSlotNum][1] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 5000 * js_ffmult->value, // magnitude + 100, // period + 1000, // duration + 18000, // angle + 5000, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + g_pEffects[iSlotNum][2] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][2]))->InitializePolar(pFeelDevice, + 10000 * js_ffmult->value, // magnitude + 340, // period + 1000, // duration + 18000, // angle + 4000, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_DieselEngineIdle: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_SawtoothDown); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 2000 * js_ffmult->value, // magnitude + 250, // period + 10000, // duration + 18000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + g_pEffects[iSlotNum][1] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 4000 * js_ffmult->value, // magnitude + 125, // period + 10000, // duration + 18000, // angle + 1500, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_Jump: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 5000 * js_ffmult->value, // magnitude + 500, // period + 300, // duration + 18000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_Land: + envelope.dwAttackLevel = 6000; + envelope.dwAttackTime = 200000; + envelope.dwFadeLevel = 3000; + envelope.dwFadeTime = 50000; + + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 1000 * js_ffmult->value, // magnitude + 750, // period + 250, // duration + 0, // angle + 0, // offset + 0, // phase + &envelope)) // envelope + success = FALSE; + break; + case fffx_MachineGun: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 3500 * js_ffmult->value, // magnitude + 70, // period + 1000, // duration + 0, // angle + 2500, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_Punched: + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 0; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 50000; + + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 8000 * js_ffmult->value, // magnitude + 130, // period + 70, // duration + 0, // angle + 0, // offset + 0, // phase + &envelope)) // envelope + success = FALSE; + break; + case fffx_RocketLaunch: + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 200000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 0; + + g_pEffects[iSlotNum][1] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 18000, // angle + 400, // duration + 10000 * js_ffmult->value, // magnitude + &envelope))// envelope + success = FALSE; + + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 300000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 100000; + + g_pEffects[iSlotNum][0] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 0, // angle + 1000, // duration + 10000 * js_ffmult->value, // magnitude + &envelope))// envelope + success = FALSE; + break; + case fffx_SecretDoor: + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 400000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 0; + + g_pEffects[iSlotNum][0] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 0, // angle + 400, // duration + 10000 * js_ffmult->value, // magnitude + &envelope))// envelope + + success = FALSE; + break; + case fffx_SwitchClick: + g_pEffects[iSlotNum][0] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 18000, // angle + 50, // duration + 7000 * js_ffmult->value, // magnitude + NULL))// envelope + + success = FALSE; + break; + case fffx_WindGust: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 6000 * js_ffmult->value, // magnitude + 1000, // period + 500, // duration + 18000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_WindShear: + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 1500000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 500000; + + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_SawtoothDown); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 10000 * js_ffmult->value, // magnitude + 80, // period + 2000, // duration + 0, // angle + 0, // offset + 0, // phase + &envelope)) // envelope + success = FALSE; + break; + case fffx_Pistol: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 8500 * js_ffmult->value, // magnitude + 130, // period + 50, // duration + 0, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_Shotgun: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 6000 * js_ffmult->value, // magnitude + 100, // period + 100, // duration + 18000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 100000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 100000; + + g_pEffects[iSlotNum][1] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 0, // angle + 300, // duration + 7000 * js_ffmult->value, // magnitude + &envelope))// envelope + success = FALSE; + break; + case fffx_Laser1: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 6000 * js_ffmult->value, // magnitude + 25, // period + 1000, // duration + 18000, // angle + 2000, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_Laser2: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 5000 * js_ffmult->value, // magnitude + 25, // period + 1000, // duration + 0, // angle + 3000, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_Laser3: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 5000 * js_ffmult->value, // magnitude + 25, // period + 1000, // duration + 9000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_Laser4: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 7000 * js_ffmult->value, // magnitude + 25, // period + 1000, // duration + 9000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_Laser5: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 5000 * js_ffmult->value, // magnitude + 25, // period + 1000, // duration + 0, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_Laser6: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 5000 * js_ffmult->value, // magnitude + 25, // period + 1000, // duration + 0, // angle + 2000, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_OutOfAmmo: + g_pEffects[iSlotNum][0] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 18000, // angle + 10, // duration + 6000 * js_ffmult->value, // magnitude + NULL))// envelope + + success = FALSE; + break; + case fffx_LightningGun: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 1500 * js_ffmult->value, // magnitude + 250, // period + 1000, // duration + 18000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + + success = FALSE; + g_pEffects[iSlotNum][1] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 6000 * js_ffmult->value, // magnitude + 50, // period + 1000, // duration + 0, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_Missile: + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 500000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 200000; + + g_pEffects[iSlotNum][0] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 0, // angle + 250, // duration + 10000 * js_ffmult->value, // magnitude + &envelope))// envelope + + success = FALSE; + break; + case fffx_GatlingGun: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_SawtoothDown); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 6000 * js_ffmult->value, // magnitude + 100, // period + 1000, // duration + 0, // angle + 1000, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + break; + case fffx_ShortPlasma: + envelope.dwAttackLevel = 7000; + envelope.dwAttackTime = 250000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 0; + + g_pEffects[iSlotNum][0] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 0, // angle + 250, // duration + 0, // magnitude + &envelope))// envelope + + success = FALSE; + + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 0; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 250000; + + g_pEffects[iSlotNum][1] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 5000 * js_ffmult->value, // magnitude + 30, // period + 250, // duration + 0, // angle + 0, // offset + 0, // phase + &envelope)) // envelope + success = FALSE; + break; + case fffx_PlasmaCannon1: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 5000 * js_ffmult->value, // magnitude + 500, // period + 400, // duration + 18000, // angle + -5000, // offset + 0, // phase + NULL)) // envelope + + success = FALSE; + + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 250000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 0; + + g_pEffects[iSlotNum][1] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 6000 * js_ffmult->value, // magnitude + 30, // period + 250, // duration + 0, // angle + 0, // offset + 0, // phase + &envelope)) // envelope + success = FALSE; + break; + case fffx_PlasmaCannon2: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 4000 * js_ffmult->value, // magnitude + 1000, // period + 800, // duration + 18000, // angle + -4000, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 500000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 0; + + g_pEffects[iSlotNum][1] = new CFeelPeriodic(GUID_Feel_Sine); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 8000 * js_ffmult->value, // magnitude + 35, // period + 500, // duration + 0, // angle + 0, // offset + 0, // phase + &envelope)) // envelope + success = FALSE; + break; + case fffx_Cannon: + g_pEffects[iSlotNum][0] = new CFeelPeriodic(GUID_Feel_Square); + if (!((CFeelPeriodic*)(g_pEffects[iSlotNum][0]))->InitializePolar(pFeelDevice, + 8000 * js_ffmult->value, // magnitude + 100, // period + 100, // duration + 18000, // angle + 0, // offset + 0, // phase + NULL)) // envelope + success = FALSE; + + envelope.dwAttackLevel = 0; + envelope.dwAttackTime = 100000; + envelope.dwFadeLevel = 0; + envelope.dwFadeTime = 100000; + + g_pEffects[iSlotNum][1] = new CFeelConstant; + if (!((CFeelConstant*)(g_pEffects[iSlotNum][1]))->InitializePolar(pFeelDevice, + 0, // angle + 300, // duration + 10000 * js_ffmult->value, // magnitude + &envelope))// envelope + success = FALSE; + break; + }// switch (fffx) + + // if any effect in the compound failed to initialize, dump the lot + if (!success) + { + for (int i = 0; i < MAX_EFFECTS_IN_COMPOUND; i++) + { + if (g_pEffects[iSlotNum][i]) + { + delete g_pEffects[iSlotNum][i]; + g_pEffects[iSlotNum][i] = NULL; + } + } + } + + return success; +} + +BOOL _FeelStartEffect(int iSlotNum, DWORD dwIterations, DWORD dwFlags) +{ + BOOL success = TRUE; + + for (int i = 0; i < MAX_EFFECTS_IN_COMPOUND; i++) + { + if (g_pEffects[iSlotNum][i]) + { + if (!g_pEffects[iSlotNum][i]->Start(dwIterations, dwFlags)) + success = FALSE; + } + } + + return success; +} + +BOOL _FeelEffectPlaying(int iSlotNum) +{ + DWORD dwFlags; + dwFlags = 0; + + // check to see if any effect within the compound is still playing + for (int i = 0; i < MAX_EFFECTS_IN_COMPOUND; i++) + { + if (g_pEffects[iSlotNum][i]) + { + g_pEffects[iSlotNum][i]->GetStatus(&dwFlags); + if (dwFlags & FEELIT_FSTATUS_PLAYING) + return TRUE; + } + } + + return FALSE; +} + +BOOL _FeelStopEffect(int iSlotNum) +{ + BOOL success = TRUE; + + for (int i = 0; i < MAX_EFFECTS_IN_COMPOUND; i++) + { + if (g_pEffects[iSlotNum][i]) + { + if (!g_pEffects[iSlotNum][i]->Stop()) + success = FALSE; + } + } + + return success; +} + +BOOL _FeelClearEffect(int iSlotNum) +{ + for (int i = 0; i < MAX_EFFECTS_IN_COMPOUND; i++) + { + if (g_pEffects[iSlotNum][i]) + { + delete g_pEffects[iSlotNum][i]; + g_pEffects[iSlotNum][i] = NULL; + } + } + + return TRUE; +} + + diff --git a/code/win32/FeelIt/fffx_feel.h b/code/win32/FeelIt/fffx_feel.h new file mode 100644 index 0000000..61bd744 --- /dev/null +++ b/code/win32/FeelIt/fffx_feel.h @@ -0,0 +1,29 @@ +// Filename:- fffx_Feel.h (Force-Feedback FX) +// ADDED BY IMMRESION + +#ifndef FFFX_FEEL_H +#define FFFX_FEEL_H + +#include "../../client/fffx.h" +#include "ffc.h" + +///////////////////////////////////////////////////////////////////////// +/* These functions were created to make the code a little easier to read. + * _FeelCreateEffect is quite long since it needs to create different + * kinds of effects. When playing effects, the number of iterations + * may not act as expected. I can't use CFeelCompound effects since I + * don't have a Project (which requires an ifr file at this point). So, + * I simulate compound effects with arrays. If an effect has multiple + * CFeelEffect in it, each CFeelEffect will be started individually with + * that number of iterations. The only case where this will act strange + * is when the CFeelEffects have different durations. +*/ +///////////////////////////////////////////////////////////////////////// +void _FeelInitEffects(); +BOOL _FeelCreateEffect(int iSlotNum, ffFX_e fffx, CFeelDevice* pFeelDevice); +BOOL _FeelStartEffect(int iSlotNum, DWORD dwIterations, DWORD dwFlags); +BOOL _FeelEffectPlaying(int iSlotNum); +BOOL _FeelStopEffect(int iSlotNum); +BOOL _FeelClearEffect(int iSlotNum); + +#endif // #ifndef FFFX_FEEL_H diff --git a/code/win32/background.bmp b/code/win32/background.bmp new file mode 100644 index 0000000000000000000000000000000000000000..88b3dabf80d613a696eb13f67f029225ae91e187 GIT binary patch literal 197688 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

rbcCSs(?A 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

6w&Mpk?3u`kG3bIsSo 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 literal 0 HcmV?d00001 diff --git a/code/win32/bink.h b/code/win32/bink.h new file mode 100644 index 0000000..c355108 --- /dev/null +++ b/code/win32/bink.h @@ -0,0 +1,620 @@ +#ifndef BINKH +#define BINKH + +#define BINKVERSION "1.0p" +#define BINKDATE "2000-06-26" + +#ifndef __RADRES__ + +#include "rad.h" + +RADDEFSTART + +typedef struct BINK PTR4* HBINK; + +typedef s32 (RADLINK PTR4* BINKIOOPEN) (struct BINKIO PTR4* Bnkio, const char PTR4 *name, u32 flags); +typedef u32 (RADLINK PTR4* BINKIOREADHEADER) (struct BINKIO PTR4* Bnkio, s32 Offset, void PTR4* Dest,u32 Size); +typedef u32 (RADLINK PTR4* BINKIOREADFRAME) (struct BINKIO PTR4* Bnkio, u32 Framenum,s32 origofs,void PTR4* dest,u32 size); +typedef u32 (RADLINK PTR4* BINKIOGETBUFFERSIZE)(struct BINKIO PTR4* Bnkio, u32 Size); +typedef void (RADLINK PTR4* BINKIOSETINFO) (struct BINKIO PTR4* Bnkio, void PTR4* Buf,u32 Size,u32 FileSize,u32 simulate); +typedef u32 (RADLINK PTR4* BINKIOIDLE) (struct BINKIO PTR4* Bnkio); +typedef void (RADLINK PTR4* BINKIOCLOSE) (struct BINKIO PTR4* Bnkio); + +typedef struct BINKIO { + BINKIOREADHEADER ReadHeader; + BINKIOREADFRAME ReadFrame; + BINKIOGETBUFFERSIZE GetBufferSize; + BINKIOSETINFO SetInfo; + BINKIOIDLE Idle; + BINKIOCLOSE Close; + HBINK bink; + volatile u32 ReadError; + volatile u32 DoingARead; + volatile u32 BytesRead; + volatile u32 Working; + volatile u32 TotalTime; + volatile u32 ForegroundTime; + volatile u32 IdleTime; + volatile u32 ThreadTime; + volatile u32 BufSize; + volatile u32 BufHighUsed; + volatile u32 CurBufSize; + volatile u32 CurBufUsed; + volatile u8 iodata[128]; +} BINKIO; + +typedef s32 (RADLINK PTR4* BINKSNDOPEN) (struct BINKSND PTR4* BnkSnd, u32 freq, s32 bits, s32 chans, u32 flags, HBINK bink); +typedef s32 (RADLINK PTR4* BINKSNDREADY) (struct BINKSND PTR4* BnkSnd); +typedef s32 (RADLINK PTR4* BINKSNDLOCK) (struct BINKSND PTR4* BnkSnd, u8 PTR4* PTR4* addr, u32 PTR4* len); +typedef s32 (RADLINK PTR4* BINKSNDUNLOCK) (struct BINKSND PTR4* BnkSnd, u32 filled); +typedef void (RADLINK PTR4* BINKSNDVOLUME) (struct BINKSND PTR4* BnkSnd, s32 volume); +typedef void (RADLINK PTR4* BINKSNDPAN) (struct BINKSND PTR4* BnkSnd, s32 pan); +typedef s32 (RADLINK PTR4* BINKSNDONOFF) (struct BINKSND PTR4* BnkSnd, s32 status); +typedef s32 (RADLINK PTR4* BINKSNDPAUSE) (struct BINKSND PTR4* BnkSnd, s32 status); +typedef void (RADLINK PTR4* BINKSNDCLOSE) (struct BINKSND PTR4* BnkSnd); + +typedef BINKSNDOPEN (RADLINK PTR4* BINKSNDSYSOPEN) (u32 param); + +typedef struct BINKSND { + BINKSNDREADY Ready; + BINKSNDLOCK Lock; + BINKSNDUNLOCK Unlock; + BINKSNDVOLUME Volume; + BINKSNDPAN Pan; + BINKSNDPAUSE Pause; + BINKSNDONOFF SetOnOff; + BINKSNDCLOSE Close; + u32 BestSizeIn16; + u32 SoundDroppedOut; + s32 OnOff; + u32 Latency; + u32 freq; + s32 bits,chans; + u8 snddata[128]; +} BINKSND; + +typedef struct BINKRECT { + s32 Left,Top,Width,Height; +} BINKRECT; + +#define BINKMAXDIRTYRECTS 8 + +typedef struct BUNDLEPOINTERS { + void* typeptr; + void* type16ptr; + void* colorptr; + void* bits2ptr; + void* motionXptr; + void* motionYptr; + void* dctptr; + void* mdctptr; + void* patptr; +} BUNDLEPOINTERS; + + +typedef struct BINK { + u32 Width; // Width (1 based, 640 for example) + u32 Height; // Height (1 based, 480 for example) + u32 Frames; // Number of frames (1 based, 100 = 100 frames) + u32 FrameNum; // Frame to *be* displayed (1 based) + u32 LastFrameNum; // Last frame decompressed or skipped (1 based) + + u32 FrameRate; // Frame Rate Numerator + u32 FrameRateDiv; // Frame Rate Divisor (frame rate=numerator/divisor) + + u32 ReadError; // Non-zero if a read error has ocurred + u32 OpenFlags; // flags used on open + u32 BinkType; // Bink flags + + u32 Size; // size of file + u32 FrameSize; // The current frame's size in bytes + u32 SndSize; // The current frame sound tracks' size in bytes + + BINKRECT FrameRects[BINKMAXDIRTYRECTS];// Dirty rects from BinkGetRects + s32 NumRects; + + u32 PlaneNum; // which set of planes is current + void PTR4* YPlane[2]; // pointer to the uncompressed Y (Cr and Cr follow) + void PTR4* APlane[2]; // decompressed alpha plane (if present) + u32 YWidth; // widths and heights of the video planes + u32 YHeight; + u32 UVWidth; + u32 UVHeight; + + void PTR4* MaskPlane; // pointer to the mask plane (Ywidth/16*Yheight/16) + u32 MaskPitch; // Mask Pitch + u32 MaskLength; // total length of the mask plane + + u32 LargestFrameSize; // Largest frame size + u32 InternalFrames; // how many frames were potentially compressed + + s32 NumTracks; // how many tracks + + u32 Highest1SecRate; // Highest 1 sec data rate + u32 Highest1SecFrame; // Highest 1 sec data rate starting frame + + s32 Paused; // is the bink movie paused? + + u32 BackgroundThread; // handle to background thread + + // everything below is for internal Bink use + + void PTR4* compframe; // compressed frame data + void PTR4* preloadptr; // preloaded compressed frame data + u32* frameoffsets; // offsets of each of the frames + + BINKIO bio; // IO structure + u8 PTR4* ioptr; // io buffer ptr + u32 iosize; // io buffer size + u32 decompwidth; // width not include scaling + u32 decompheight; // height not include scaling + + s32 trackindex; // track index + u32 PTR4* tracksizes; // largest single frame of track + u32 PTR4* tracktypes; // type of each sound track + s32 PTR4* trackIDs; // external track numbers + + u32 numrects; // number of rects from BinkGetRects + + u32 playedframes; // how many frames have we played + u32 firstframetime; // very first frame start + u32 startframetime; // start frame start + u32 startblittime; // start of blit period + u32 startsynctime; // start of synched time + u32 startsyncframe; // frame of startsynctime + u32 twoframestime; // two frames worth of time + u32 entireframetime; // entire frame time + + u32 slowestframetime; // slowest frame in ms + u32 slowestframe; // slowest frame number + u32 slowest2frametime; // second slowest frame in ms + u32 slowest2frame; // second slowest frame + + u32 soundon; // sound turned on? + u32 videoon; // video turned on? + + u32 totalmem; // total memory used + u32 timevdecomp; // total time decompressing video + u32 timeadecomp; // total time decompressing audio + u32 timeblit; // total time blitting + u32 timeopen; // total open time + + u32 fileframerate; // frame rate originally in the file + u32 fileframeratediv; + + volatile u32 threadcontrol; // controls the background reading thread + + u32 runtimeframes; // max frames for runtime analysis + u32 runtimemoveamt; // bytes to move each frame + u32 PTR4* rtframetimes; // start times for runtime frames + u32 PTR4* rtadecomptimes; // decompress times for runtime frames + u32 PTR4* rtvdecomptimes; // decompress times for runtime frames + u32 PTR4* rtblittimes; // blit times for runtime frames + u32 PTR4* rtreadtimes; // read times for runtime frames + u32 PTR4* rtidlereadtimes; // idle read times for runtime frames + u32 PTR4* rtthreadreadtimes;// thread read times for runtime frames + + u32 lastblitflags; // flags used on last blit + u32 lastdecompframe; // last frame number decompressed + + u32 sndbufsize; // sound buffer size + u8 PTR4* sndbuf; // sound buffer + u8 PTR4* sndend; // end of the sound buffer + u8 PTR4* sndwritepos; // current write position + u8 PTR4* sndreadpos; // current read position + u32 sndcomp; // sound compression handle + u32 sndamt; // amount of sound currently in the buffer + volatile u32 sndreenter; // re-entrancy check on the sound + u32 sndconvert8; // convert back to 8-bit sound at runtime + BINKSND bsnd; // SND structure + u32 skippedlastblit; // skipped last frame? + u32 skippedblits; // how many blits were skipped + u32 soundskips; // number of sound stops + u32 sndendframe; // frame number that the sound ends on + u32 sndprime; // amount of data to prime the playahead + u32 sndpad; // padded this much audio + + BUNDLEPOINTERS bunp; // pointers to internal temporary memory +} BINK; + + +typedef struct BINKSUMMARY { + u32 Width; // Width of frames + u32 Height; // Height of frames + u32 TotalTime; // total time (ms) + u32 FileFrameRate; // frame rate + u32 FileFrameRateDiv; // frame rate divisor + u32 FrameRate; // frame rate + u32 FrameRateDiv; // frame rate divisor + u32 TotalOpenTime; // Time to open and prepare for decompression + u32 TotalFrames; // Total Frames + u32 TotalPlayedFrames; // Total Frames played + u32 SkippedFrames; // Total number of skipped frames + u32 SkippedBlits; // Total number of skipped blits + u32 SoundSkips; // Total number of sound skips + u32 TotalBlitTime; // Total time spent blitting + u32 TotalReadTime; // Total time spent reading + u32 TotalVideoDecompTime; // Total time spent decompressing video + u32 TotalAudioDecompTime; // Total time spent decompressing audio + u32 TotalIdleReadTime; // Total time spent reading while idle + u32 TotalBackReadTime; // Total time spent reading in background + u32 TotalReadSpeed; // Total io speed (bytes/second) + u32 SlowestFrameTime; // Slowest single frame time (ms) + u32 Slowest2FrameTime; // Second slowest single frame time (ms) + u32 SlowestFrameNum; // Slowest single frame number + u32 Slowest2FrameNum; // Second slowest single frame number + u32 AverageDataRate; // Average data rate of the movie + u32 AverageFrameSize; // Average size of the frame + u32 HighestMemAmount; // Highest amount of memory allocated + u32 TotalIOMemory; // Total extra memory allocated + u32 HighestIOUsed; // Highest extra memory actually used + u32 Highest1SecRate; // Highest 1 second rate + u32 Highest1SecFrame; // Highest 1 second start frame +} BINKSUMMARY; + + +typedef struct BINKREALTIME { + u32 FrameNum; // Current frame number + u32 FrameRate; // frame rate + u32 FrameRateDiv; // frame rate divisor + u32 Frames; // frames in this sample period + u32 FramesTime; // time is ms for these frames + u32 FramesVideoDecompTime; // time decompressing these frames + u32 FramesAudioDecompTime; // time decompressing these frames + u32 FramesReadTime; // time reading these frames + u32 FramesIdleReadTime; // time reading these frames at idle + u32 FramesThreadReadTime; // time reading these frames in background + u32 FramesBlitTime; // time blitting these frames + u32 ReadBufferSize; // size of read buffer + u32 ReadBufferUsed; // amount of read buffer currently used + u32 FramesDataRate; // data rate for these frames +} BINKREALTIME; + +#define BINKMARKER1 'fKIB' +#define BINKMARKER2 'gKIB' // new Bink files use this tag +#define BINKMARKER3 'hKIB' // newer Bink files use this tag +#define BINKMARKER4 'iKIB' // even newer Bink files use this tag + +typedef struct BINKHDR { + u32 Marker; // Bink marker + u32 Size; // size of the file-8 + u32 Frames; // Number of frames (1 based, 100 = 100 frames) + u32 LargestFrameSize; // Size in bytes of largest frame + u32 InternalFrames; // Number of internal frames + + u32 Width; // Width (1 based, 640 for example) + u32 Height; // Height (1 based, 480 for example) + u32 FrameRate; // frame rate + u32 FrameRateDiv; // frame rate divisor (framerate/frameratediv=fps) + + u32 Flags; // height compression options + u32 NumTracks; // number of tracks +} BINKHDR; + + +//======================================================================= +#define BINKFRAMERATE 0x00001000L // Override fr (call BinkFrameRate first) +#define BINKPRELOADALL 0x00002000L // Preload the entire animation +#define BINKSNDTRACK 0x00004000L // Set the track number to play +#define BINKOLDFRAMEFORMAT 0x00008000L // using the old Bink frame format (internal use only) +#define BINKRBINVERT 0x00010000L // use reversed R and B planes (internal use only) +#define BINKGRAYSCALE 0x00020000L // Force Bink to use grayscale +#define BINKNOMMX 0x00040000L // Don't use MMX +#define BINKNOSKIP 0x00080000L // Don't skip frames if falling behind +#define BINKALPHA 0x00100000L // Decompress alpha plane (if present) +#define BINKNOFILLIOBUF 0x00200000L // Fill the IO buffer in SmackOpen +#define BINKSIMULATE 0x00400000L // Simulate the speed (call BinkSim first) +#define BINKFILEHANDLE 0x00800000L // Use when passing in a file handle +#define BINKIOSIZE 0x01000000L // Set an io size (call BinkIOSize first) +#define BINKIOPROCESSOR 0x02000000L // Set an io processor (call BinkIO first) +#define BINKFROMMEMORY 0x04000000L // Use when passing in a pointer to the file +#define BINKNOTHREADEDIO 0x08000000L // Don't use a background thread for IO + +#define BINKSURFACEFAST 0x00000000L +#define BINKSURFACESLOW 0x08000000L +#define BINKSURFACEDIRECT 0x04000000L + +#define BINKCOPYALL 0x80000000L // copy all pixels (not just changed) +#define BINKCOPY2XH 0x10000000L // Force doubling height scaling +#define BINKCOPY2XHI 0x20000000L // Force interleaving height scaling +#define BINKCOPY2XW 0x30000000L // copy the width zoomed by two +#define BINKCOPY2XWH 0x40000000L // copy the width and height zoomed by two +#define BINKCOPY2XWHI 0x50000000L // copy the width and height zoomed by two +#define BINKCOPY1XI 0x60000000L // copy the width and height zoomed by two +#define BINKCOPYNOSCALING 0x70000000L // Force scaling off + +//#define BINKALPHA 0x00100000L // Decompress alpha plane (if present) +//#define BINKNOSKIP 0x00080000L // don't skip the blit if behind in sound +//#define BINKNOMMX 0x00040000L // Don't skip frames if falling behind +//#define BINKGRAYSCALE 0x00020000L // force Bink to use grayscale +//#define BINKRBINVERT 0x00010000L // use reversed R and B planes + +#define BINKSURFACE8P 0 +#define BINKSURFACE24 1 +#define BINKSURFACE24R 2 +#define BINKSURFACE32 3 +#define BINKSURFACE32R 4 +#define BINKSURFACE32A 5 +#define BINKSURFACE32RA 6 +#define BINKSURFACE4444 7 +#define BINKSURFACE5551 8 +#define BINKSURFACE555 9 +#define BINKSURFACE565 10 +#define BINKSURFACE655 11 +#define BINKSURFACE664 12 +#define BINKSURFACEYUY2 13 +#define BINKSURFACEUYVY 14 +#define BINKSURFACEYV12 15 +#define BINKSURFACEMASK 15 + +#define BINKGOTOQUICK 1 + +#define BINKGETKEYPREVIOUS 0 +#define BINKGETKEYNEXT 1 +#define BINKGETKEYCLOSEST 2 +#define BINKGETKEYNOTEQUAL 128 + +//======================================================================= + +#ifdef __RADMAC__ + #include + + #pragma export on + + RADEXPFUNC HBINK RADEXPLINK BinkMacOpen(FSSpec* fsp,u32 flags); +#endif + +RADEXPFUNC void PTR4* RADEXPLINK BinkLogoAddress(void); + +RADEXPFUNC void RADEXPLINK BinkSetError(const char PTR4* err); +RADEXPFUNC char PTR4* RADEXPLINK BinkGetError(void); + +RADEXPFUNC HBINK RADEXPLINK BinkOpen(const char PTR4* name,u32 flags); + +RADEXPFUNC s32 RADEXPLINK BinkDoFrame(HBINK bnk); +RADEXPFUNC void RADEXPLINK BinkNextFrame(HBINK bnk); +RADEXPFUNC s32 RADEXPLINK BinkWait(HBINK bnk); +RADEXPFUNC void RADEXPLINK BinkClose(HBINK bnk); +RADEXPFUNC s32 RADEXPLINK BinkPause(HBINK bnk,s32 pause); +RADEXPFUNC s32 RADEXPLINK BinkCopyToBuffer(HBINK bnk,void* dest,s32 destpitch,u32 destheight,u32 destx,u32 desty,u32 flags); +RADEXPFUNC s32 RADEXPLINK BinkGetRects(HBINK bnk,u32 flags); +RADEXPFUNC void RADEXPLINK BinkGoto(HBINK bnk,u32 frame,s32 flags); // use 1 for the first frame +RADEXPFUNC u32 RADEXPLINK BinkGetKeyFrame(HBINK bnk,u32 frame,s32 flags); + +RADEXPFUNC s32 RADEXPLINK BinkSetVideoOnOff(HBINK bnk,s32 onoff); +RADEXPFUNC s32 RADEXPLINK BinkSetSoundOnOff(HBINK bnk,s32 onoff); +RADEXPFUNC void RADEXPLINK BinkSetVolume(HBINK bnk,s32 volume); +RADEXPFUNC void RADEXPLINK BinkSetPan(HBINK bnk,s32 pan); +RADEXPFUNC void RADEXPLINK BinkService(HBINK bink); + +typedef struct BINKTRACK PTR4* HBINKTRACK; + +typedef struct BINKTRACK +{ + u32 Frequency; + u32 Bits; + u32 Channels; + u32 MaxSize; + + HBINK bink; + u32 sndcomp; + s32 trackindex; +} BINKTRACK; + + +RADEXPFUNC HBINKTRACK RADEXPLINK BinkOpenTrack(HBINK bnk,u32 trackindex); +RADEXPFUNC void RADEXPLINK BinkCloseTrack(HBINKTRACK bnkt); +RADEXPFUNC u32 RADEXPLINK BinkGetTrackData(HBINKTRACK bnkt,void PTR4* dest); + +RADEXPFUNC u32 RADEXPLINK BinkGetTrackType(HBINK bnk,u32 trackindex); +RADEXPFUNC u32 RADEXPLINK BinkGetTrackMaxSize(HBINK bnk,u32 trackindex); +RADEXPFUNC u32 RADEXPLINK BinkGetTrackID(HBINK bnk,u32 trackindex); +RADEXPFUNC u32 RADEXPLINK BinkGetTrackLargest(HBINK bnk,u32 trackindex); + +RADEXPFUNC void RADEXPLINK BinkGetSummary(HBINK bnk,BINKSUMMARY PTR4* sum); +RADEXPFUNC void RADEXPLINK BinkGetRealtime(HBINK bink,BINKREALTIME PTR4* run,u32 frames); + +#define BINKNOSOUND 0xffffffff + +RADEXPFUNC void RADEXPLINK BinkSetSoundTrack(u32 track); +RADEXPFUNC void RADEXPLINK BinkSetIO(BINKIOOPEN io); +RADEXPFUNC void RADEXPLINK BinkSetFrameRate(u32 forcerate,u32 forceratediv); +RADEXPFUNC void RADEXPLINK BinkSetSimulate(u32 sim); +RADEXPFUNC void RADEXPLINK BinkSetIOSize(u32 iosize); + +RADEXPFUNC s32 RADEXPLINK BinkSetSoundSystem(BINKSNDSYSOPEN open, u32 param); + +#ifdef __RADWIN__ + + RADEXPFUNC BINKSNDOPEN RADEXPLINK BinkOpenDirectSound(u32 param); // don't call directly + #define BinkSoundUseDirectSound(lpDS) BinkSetSoundSystem(BinkOpenDirectSound,(u32)lpDS) + + RADEXPFUNC BINKSNDOPEN RADEXPLINK BinkOpenWaveOut(u32 param); // don't call directly + #define BinkSoundUseWaveOut() BinkSetSoundSystem(BinkOpenWaveOut,0) + + #define INCLUDE_MMSYSTEM_H + #include "windows.h" + #include "windowsx.h" + + #ifdef __RADNT__ // to combat WIN32_LEAN_AND_MEAN + #include "mmsystem.h" + #endif + +#endif + + +#ifndef __RADMAC__ + + RADEXPFUNC BINKSNDOPEN RADEXPLINK BinkOpenMiles(u32 param); // don't call directly + #define BinkSoundUseMiles(hdigdriver) BinkSetSoundSystem(BinkOpenMiles,(u32)hdigdriver) + +#endif + + +#ifdef __RADMAC__ + + RADEXPFUNC BINKSNDOPEN RADEXPLINK BinkOpenSoundManager(u32 param); // don't call directly + #define BinkSoundUseSoundManager() BinkSetSoundSystem(BinkOpenSoundManager,0) + +#endif + + +// The BinkBuffer API isn't currently implemented on DOS +#if !defined(__RADDOS__) + +//========================================================================= +typedef struct BINKBUFFER * HBINKBUFFER; + +#define BINKBUFFERSTRETCHXINT 0x80000000 +#define BINKBUFFERSTRETCHX 0x40000000 +#define BINKBUFFERSHRINKXINT 0x20000000 +#define BINKBUFFERSHRINKX 0x10000000 +#define BINKBUFFERSTRETCHYINT 0x08000000 +#define BINKBUFFERSTRETCHY 0x04000000 +#define BINKBUFFERSHRINKYINT 0x02000000 +#define BINKBUFFERSHRINKY 0x01000000 +#define BINKBUFFERSCALES 0xff000000 +#define BINKBUFFERRESOLUTION 0x00800000 + +#ifdef __RADMAC__ + +#include +#include +#include + +typedef struct BINKBUFFER { + u32 Width; + u32 Height; + u32 WindowWidth; + u32 WindowHeight; + u32 SurfaceType; + void* Buffer; + s32 BufferPitch; + u32 ScreenWidth; + u32 ScreenHeight; + u32 ScreenDepth; + u32 ScaleFlags; + + s32 destx,desty; + s32 wndx,wndy; + u32 wnd; + + s32 noclipping; + u32 type; + s32 issoftcur; + u32 cursorcount; + +} BINKBUFFER; + + +#define BINKBUFFERAUTO 0 +#define BINKBUFFERDIRECT 1 +#define BINKBUFFERGWORLD 2 +#define BINKBUFFERTYPEMASK 31 + +RADEXPFUNC HBINKBUFFER RADEXPLINK BinkBufferOpen( WindowPtr wnd, u32 width, u32 height, u32 bufferflags); +RADEXPFUNC s32 RADEXPLINK BinkGDSurfaceType( GDHandle gd ); +RADEXPFUNC s32 RADEXPLINK BinkIsSoftwareCursor(GDHandle gd); +RADEXPFUNC s32 RADEXPLINK BinkCheckCursor(WindowPtr wp,s32 x,s32 y,s32 w,s32 h); + +#else + +typedef struct BINKBUFFER { + u32 Width; + u32 Height; + u32 WindowWidth; + u32 WindowHeight; + u32 SurfaceType; + void* Buffer; + s32 BufferPitch; + s32 ClientOffsetX; + s32 ClientOffsetY; + u32 ScreenWidth; + u32 ScreenHeight; + u32 ScreenDepth; + u32 ExtraWindowWidth; + u32 ExtraWindowHeight; + u32 ScaleFlags; + u32 StretchWidth; + u32 StretchHeight; + + s32 surface; + void* ddsurface; + void* ddclipper; + s32 destx,desty; + s32 wndx,wndy; + u32 wnd; + s32 ddoverlay; + s32 ddoffscreen; + s32 lastovershow; + + s32 issoftcur; + u32 cursorcount; + void* buffertop; + u32 type; + s32 noclipping; + + s32 loadeddd; + s32 loadedwin; + + void* dibh; + void* dibbuffer; + s32 dibpitch; + void* dibinfo; + u32 dibdc; + u32 diboldbitmap; + +} BINKBUFFER; + + +#define BINKBUFFERAUTO 0 +#define BINKBUFFERPRIMARY 1 +#define BINKBUFFERDIBSECTION 2 +#define BINKBUFFERYV12OVERLAY 3 +#define BINKBUFFERYUY2OVERLAY 4 +#define BINKBUFFERUYVYOVERLAY 5 +#define BINKBUFFERYV12OFFSCREEN 6 +#define BINKBUFFERYUY2OFFSCREEN 7 +#define BINKBUFFERUYVYOFFSCREEN 8 +#define BINKBUFFERRGBOFFSCREENVIDEO 9 +#define BINKBUFFERRGBOFFSCREENSYSTEM 10 +#define BINKBUFFERLAST 10 +#define BINKBUFFERTYPEMASK 31 + +RADEXPFUNC HBINKBUFFER RADEXPLINK BinkBufferOpen( HWND wnd, u32 width, u32 height, u32 bufferflags); +RADEXPFUNC s32 RADEXPLINK BinkBufferSetHWND( HBINKBUFFER buf, HWND newwnd); +RADEXPFUNC s32 RADEXPLINK BinkDDSurfaceType(void PTR4* lpDDS); +RADEXPFUNC s32 RADEXPLINK BinkIsSoftwareCursor(void PTR4* lpDDSP,HCURSOR cur); +RADEXPFUNC s32 RADEXPLINK BinkCheckCursor(HWND wnd,s32 x,s32 y,s32 w,s32 h); +RADEXPFUNC s32 RADEXPLINK BinkBufferSetDirectDraw(void PTR4* lpDirectDraw, void PTR4* lpPrimary); + +#endif + +RADEXPFUNC void RADEXPLINK BinkBufferClose( HBINKBUFFER buf); +RADEXPFUNC s32 RADEXPLINK BinkBufferLock( HBINKBUFFER buf); +RADEXPFUNC s32 RADEXPLINK BinkBufferUnlock( HBINKBUFFER buf); +RADEXPFUNC void RADEXPLINK BinkBufferSetResolution( s32 w, s32 h, s32 bits); +RADEXPFUNC void RADEXPLINK BinkBufferCheckWinPos( HBINKBUFFER buf, s32 PTR4* NewWindowX, s32 PTR4* NewWindowY); +RADEXPFUNC s32 RADEXPLINK BinkBufferSetOffset( HBINKBUFFER buf, s32 destx, s32 desty); +RADEXPFUNC void RADEXPLINK BinkBufferBlit( HBINKBUFFER buf, BINKRECT PTR4* rects, u32 numrects ); +RADEXPFUNC s32 RADEXPLINK BinkBufferSetScale( HBINKBUFFER buf, u32 w, u32 h); +RADEXPFUNC char PTR4* RADEXPLINK BinkBufferGetDescription( HBINKBUFFER buf); +RADEXPFUNC char PTR4* RADEXPLINK BinkBufferGetError(); +RADEXPFUNC s32 RADEXPLINK BinkBufferClear(HBINKBUFFER buf, u32 RGB); + +RADEXPFUNC void RADEXPLINK BinkRestoreCursor(s32 checkcount); + +#endif + +#ifdef __RADMAC__ + +#pragma export off + +#endif + +RADDEFEND + +#endif + +#endif + diff --git a/code/win32/binkw32.lib b/code/win32/binkw32.lib new file mode 100644 index 0000000000000000000000000000000000000000..75369a38c03f8efaf9dec964c399e4ab189fd7a4 GIT binary patch literal 58414 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 Lff}iAB5wW*Z-u|# literal 0 HcmV?d00001 diff --git a/code/win32/clear.bmp b/code/win32/clear.bmp new file mode 100644 index 0000000000000000000000000000000000000000..14516380f383bc87c4b5940113cc160b5b0afed4 GIT binary patch literal 5174 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 literal 0 HcmV?d00001 diff --git a/code/win32/dbg_console_xbox.cpp b/code/win32/dbg_console_xbox.cpp new file mode 100644 index 0000000..95058c6 --- /dev/null +++ b/code/win32/dbg_console_xbox.cpp @@ -0,0 +1,172 @@ +//----------------------------------------------------------------------------- +// File: dbg_console_xbox.cpp +// +// Desc: Listens for string commands sent from a debug console on a +// remote dev machine, and forwards them to the Q3 engine. +// +// Commands are sent from the remote debug console through the debug +// channel to the debug monitor on the Xbox machine. The Xbox machine +// receives the commands on a separate thread through a +// registered command processor callback function. The callback +// function will store commands in a buffer, and the app should +// poll this buffer once per frame and then decipher and handle +// the commands. +// +// Hist: 02.05.01 - Initial creation for March XDK release +// 08.21.02 - Revision and code cleanup +// 04.10.02 - Buthcered by BTO for use in JK3:JA +// +// Copyright (c) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#include +#include +#include +#include "dbg_console_xbox.h" +#include "../client/client.h" +#include "../qcommon/qcommon.h" + + +// Command prefix for things sent across the dubg channel +static const CHAR g_strDebugConsoleCommandPrefix[] = "XCMD"; + + +// Global buffer to receive remote commands from the debug console. Note that +// since this data is accessed by the app's main thread, and the debug monitor +// thread, we need to protect access with a critical section +static CHAR g_strRemoteBuf[MAXRCMDLENGTH]; + + +// The critical section used to protect data that is shared between threads +static CRITICAL_SECTION g_CriticalSection; + + +// Temporary replacement for CRT string funcs, since +// we can't call CRT functions on the debug monitor +// thread right now. + + +//----------------------------------------------------------------------------- +// Name: dbgtolower() +// Desc: Returns lowercase of char +//----------------------------------------------------------------------------- +inline CHAR dbgtolower( CHAR ch ) +{ + if( ch >= 'A' && ch <= 'Z' ) + return ch - ( 'A' - 'a' ); + else + return ch; +} + + +//----------------------------------------------------------------------------- +// Name: dbgstrnicmp() +// Desc: Critical section safe string compare. +//----------------------------------------------------------------------------- +BOOL dbgstrnicmp( const CHAR* str1, const CHAR* str2, int n ) +{ + while( ( dbgtolower( *str1 ) == dbgtolower( *str2 ) ) && *str1 && n > 0 ) + { + --n; + ++str1; + ++str2; + } + + return( n == 0 || dbgtolower( *str1 ) == dbgtolower( *str2 ) ); +} + + +//----------------------------------------------------------------------------- +// Name: dbgstrcpy() +// Desc: Critical section safe string copy +//----------------------------------------------------------------------------- +VOID dbgstrcpy( CHAR* strDest, const CHAR* strSrc ) +{ + while( ( *strDest++ = *strSrc++ ) != 0 ); +} + + +//----------------------------------------------------------------------------- +// Name: DebugConsoleCmdProcessor() +// Desc: Command notification proc that is called by the Xbox debug monitor to +// have us process a command. What we'll actually attempt to do is tell +// it to make calls to us on a separate thread, so that we can just block +// until we're able to process a command. +// +// Note: Do NOT include newlines in the response string! To do so will confuse +// the internal WinSock networking code used by the debug monitor API. +//----------------------------------------------------------------------------- +HRESULT __stdcall DebugConsoleCmdProcessor( const CHAR* strCommand, + CHAR* strResponse, DWORD dwResponseLen, + PDM_CMDCONT pdmcc ) +{ + // Skip over the command prefix and the exclamation mark + strCommand += strlen(g_strDebugConsoleCommandPrefix) + 1; + + // Check if this is the initial connect signal + if( dbgstrnicmp( strCommand, "__connect__", 11 ) ) + { + // If so, respond that we're connected + lstrcpynA( strResponse, "Connected.", dwResponseLen ); + return XBDM_NOERR; + } + + // g_strRemoteBuf needs to be protected by the critical section + EnterCriticalSection( &g_CriticalSection ); + if( g_strRemoteBuf[0] ) + { + // This means the application has probably stopped polling for debug commands + dbgstrcpy( strResponse, "Cannot execute - previous command still pending" ); + } + else + { + dbgstrcpy( g_strRemoteBuf, strCommand ); + } + LeaveCriticalSection( &g_CriticalSection ); + + return XBDM_NOERR; +} + + +//----------------------------------------------------------------------------- +// Name: DebugConsoleHandleCommands() +// Desc: Poll routine called periodically (typically every frame) by the Xbox +// app to see if there is a command waiting to be executed, and if so, +// execute it. +//----------------------------------------------------------------------------- +BOOL DebugConsoleHandleCommands() +{ + static BOOL bInitialized = FALSE; + CHAR strLocalBuf[MAXRCMDLENGTH+1]; // local copy of command + + // Initialize ourselves when we're first called. + if( !bInitialized ) + { + // Register our command handler with the debug monitor + HRESULT hr = DmRegisterCommandProcessor( g_strDebugConsoleCommandPrefix, + DebugConsoleCmdProcessor ); + if( FAILED(hr) ) + return FALSE; + + // We'll also need a critical section to protect access to g_strRemoteBuf + InitializeCriticalSection( &g_CriticalSection ); + + bInitialized = TRUE; + } + + // If there's nothing waiting, return. + if( !g_strRemoteBuf[0] ) + return FALSE; + + // Grab a local copy of the command received in the remote buffer + EnterCriticalSection( &g_CriticalSection ); + + lstrcpyA( strLocalBuf, g_strRemoteBuf ); + g_strRemoteBuf[0] = 0; + + LeaveCriticalSection( &g_CriticalSection ); + + Cbuf_ExecuteText( EXEC_APPEND, va("%s\n", strLocalBuf) ); + + return TRUE; +} + diff --git a/code/win32/dbg_console_xbox.h b/code/win32/dbg_console_xbox.h new file mode 100644 index 0000000..1fc3eeb --- /dev/null +++ b/code/win32/dbg_console_xbox.h @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// File: dbg_console_xbox.h +// +// Desc: Header file for communicating with a remote debug console. Please read +// the comments in the dbg_console_xbox.cpp file for more info +// on the API. +// +// This header defines the following arrays: +// +// g_RemoteCommands - This is the list of commands your application provides. +// Note that "help" and "set" are provided automatically +// This is implemented in DebugCmd.cpp +// +// g_RemoteVariables - This is a list of variables that your application +// exposes. They can be examined and modified by the +// remote debug console with the "set" command. +// This is implemented in DebugChannel.cpp +// +// Hist: 02.05.01 - Initial creation for March XDK release +// 08.21.02 - Revision and code cleanup +// +// Copyright (c) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- +#ifndef DEBUGCMD_H +#define DEBUGCMD_H + +#define MAXRCMDLENGTH 256 // Size of the remote cmd buffer + +// Handle any remote commands that have been sent - this should be called +// periodically by the application +BOOL DebugConsoleHandleCommands(); + +#endif // DEBUGCMD_H + diff --git a/code/win32/game.rc b/code/win32/game.rc new file mode 100644 index 0000000..0039008 --- /dev/null +++ b/code/win32/game.rc @@ -0,0 +1,104 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "AutoVersion.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_MAJOR_RELEASE,VERSION_MINOR_RELEASE,VERSION_EXTERNAL_BUILD,VERSION_INTERNAL_BUILD + PRODUCTVERSION VERSION_MAJOR_RELEASE,VERSION_MINOR_RELEASE,VERSION_EXTERNAL_BUILD,VERSION_INTERNAL_BUILD + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "Raven Software" + VALUE "CompanyName", "Activision Inc" + VALUE "FileDescription", "Jedi Academy Game DLL" + VALUE "FileVersion", VERSION_STRING + VALUE "InternalName", "ja game.dll" + VALUE "LegalCopyright", "Copyright (C) 2003" + VALUE "OriginalFilename", "jagamex86.dll" + VALUE "ProductName", "Jedi Knight®: Jedi Academy" + VALUE "ProductVersion", VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/code/win32/glw_win.h b/code/win32/glw_win.h new file mode 100644 index 0000000..e57a098 --- /dev/null +++ b/code/win32/glw_win.h @@ -0,0 +1,30 @@ +#ifndef _WIN32 +# error You should not be including this file on this platform +#endif + +#ifndef __GLW_WIN_H__ +#define __GLW_WIN_H__ + +typedef struct +{ + WNDPROC wndproc; + + HDC hDC; // handle to device context + HGLRC hGLRC; // handle to GL rendering context + + HINSTANCE hinstOpenGL; // HINSTANCE for the OpenGL library + + qboolean allowdisplaydepthchange; + qboolean pixelFormatSet; + + int desktopBitsPixel; + int desktopWidth, desktopHeight; + + qboolean cdsFullscreen; + + FILE *log_fp; +} glwstate_t; + +extern glwstate_t glw_state; + +#endif diff --git a/code/win32/glw_win_dx8.h b/code/win32/glw_win_dx8.h new file mode 100644 index 0000000..524c947 --- /dev/null +++ b/code/win32/glw_win_dx8.h @@ -0,0 +1,184 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +#ifndef __GLW_WIN_H__ +#define __GLW_WIN_H__ + +#include + +#include +#ifdef _WIN32 +#include +#endif + +#include "../renderer/qgl_console.h" +#include "../game/q_shared.h" +#include "../qcommon/qfiles.h" + +#define GLW_MAX_TEXTURE_STAGES 2 +#define GLW_MAX_STRIPS 2048 + + +struct glwstate_t +{ + // Interface to DX + IDirect3DDevice9* device; + + // Matrix stuff + enum MatrixMode + { + MatrixMode_Model = 0, + MatrixMode_Projection = 1, + MatrixMode_Texture0 = 2, + MatrixMode_Texture1 = 3, + MatrixMode_Texture2 = 4, + MatrixMode_Texture3 = 5, + + Num_MatrixModes + }; + + ID3DXMatrixStack* matrixStack[Num_MatrixModes]; + MatrixMode matrixMode; + + // Current primitive mode (triangles/quads/strips) + D3DPRIMITIVETYPE primitiveMode; + + // Are we in a glBegin/glEnd block? (Used for sanity checks.) + bool inDrawBlock; + + // Texturing + bool textureStageDirty[GLW_MAX_TEXTURE_STAGES]; + bool textureStageEnable[GLW_MAX_TEXTURE_STAGES]; + GLuint currentTexture[GLW_MAX_TEXTURE_STAGES]; + D3DTEXTUREOP textureEnv[GLW_MAX_TEXTURE_STAGES]; + + struct TextureInfo + { + IDirect3DTexture9* mipmap; + D3DTEXTUREFILTERTYPE minFilter, mipFilter, magFilter; + D3DTEXTUREADDRESS wrapU, wrapV; + float anisotropy; + + // I only need this for ONE texture, but it's easier than adding more hacks: + void *data; + }; + + typedef std::map texturexlat_t; + texturexlat_t textureXlat; + + GLuint textureBindNum; + + GLuint serverTU, clientTU; + + // Pointers to various draw buffers + const void* vertexPointer; + const void* normalPointer; + const void* texCoordPointer[GLW_MAX_TEXTURE_STAGES]; + const void* colorPointer; + +#ifdef _WINDOWS + // Temporary storage used when rendering quads + const void* vertexPointerBack; + const void* normalPointerBack; + const void* texCoordPointerBack[GLW_MAX_TEXTURE_STAGES]; + const void* colorPointerBack; +#endif + + // State of draw buffers + bool colorArrayState; + bool texCoordArrayState[GLW_MAX_TEXTURE_STAGES]; + bool vertexArrayState; + bool normalArrayState; + + // Stride of various draw buffers + int vertexStride; + int texCoordStride[GLW_MAX_TEXTURE_STAGES]; + int colorStride; + int normalStride; + + // Current number of verts in this packet + int numVertices; + + // Max verts allowed in this packet + int maxVertices; + + // Total verts to draw (may take multiple packets) + int totalVertices; + + // Current number of indices in this packet + int numIndices; + + // Max indices allowed in this packet + int maxIndices; + + // Total indices to draw + int totalIndices; + + // Culling + bool cullEnable; + D3DCULL cullMode; + + // Viewport + D3DVIEWPORT9 viewport; + + // Clearing info + D3DCOLOR clearColor; + float clearDepth; + int clearStencil; + + // Widescreen mode + bool isWidescreen; + + // Global color + D3DCOLOR currentColor; + + // Scissoring + bool scissorEnable; + D3DRECT scissorBox; + + // Directional Light + D3DLIGHT9 dirLight; + D3DMATERIAL9 mtrl; + + // Description of current shader + DWORD shaderMask; + + // Should we reset matrices on next draw? + bool matricesDirty[Num_MatrixModes]; + + // Render commands go here + DWORD* drawArray; + DWORD drawStride; + + // This is designed to be an optimization for triangle strips + // as well as making life easier for the flare effect + GLushort strip_dest[SHADER_MAX_INDEXES]; + GLuint strip_lengths[GLW_MAX_STRIPS]; + GLsizei num_strip_lengths; + +#ifdef _XBOX +// class FlareEffect* flareEffect; + class LightEffects* lightEffects; +#endif +}; + +extern glwstate_t *glw_state; + +void renderObject_HACK(); +void renderObject_Light( int numIndexes, const unsigned short *indexes ); +void renderObject_Shadow( int primType, int numIndexes, const unsigned short *indexes ); +void renderObject_Env(); +void renderObject_Bump(); +bool CreateVertexShader( const CHAR* strFilename, const DWORD* pdwVertexDecl, DWORD* pdwVertexShader ); +bool CreatePixelShader( const CHAR* strFilename, DWORD* pdwPixelShader ); + +#endif diff --git a/code/win32/rad.h b/code/win32/rad.h new file mode 100644 index 0000000..b09aa92 --- /dev/null +++ b/code/win32/rad.h @@ -0,0 +1,962 @@ +#ifndef __RAD__ +#define __RAD__ + +#define RADCOPYRIGHT "Copyright (C) 1994-2000, RAD Game Tools, Inc." + +#ifndef __RADRES__ + +// __RAD16__ means 16 bit code (Win16) +// __RAD32__ means 32 bit code (DOS, Win386, Win32s, Mac) + +// __RADDOS__ means DOS code (16 or 32 bit) +// __RADWIN__ means Windows code (Win16, Win386, Win32s) +// __RADWINEXT__ means Windows 386 extender (Win386) +// __RADNT__ means Win32s code +// __RADMAC__ means Macintosh + +// __RADX86__ means Intel x86 +// __RADMMX__ means Intel x86 MMX instructions are allowed +// __RAD68K__ means 68K +// __RADPPC__ means PowerPC + +// __RADLITTLEENDIAN__ means processor is little-endian (x86) +// __RADBIGENDIAN__ means processor is big-endian (680x0, PPC) + +// __RADALLOWINLINES__ means this compiler allows inline function declarations +// use RADINLINE for the appropriate keyword + + +#if (defined(__MWERKS__) && !defined(__INTEL__)) || defined(__MRC__) || defined(THINK_C) || defined(powerc) || defined(macintosh) || defined(__powerc) + + #define __RADMAC__ + #if defined(powerc) || defined(__powerc) + #define __RADPPC__ + #else + #define __RAD68K__ + #endif + + #define __RAD32__ + + #define __RADBIGENDIAN__ + + #if defined(__MWERKS__) + #if (defined(__cplusplus) || ! __option(only_std_keywords)) + #define __RADALLOWINLINES__ + #define RADINLINE inline + #endif + #elif defined(__MRC__) + #if defined(__cplusplus) + #define __RADALLOWINLINES__ + #define RADINLINE inline + #endif + #endif + +#else + + #define __RADX86__ + #define __RADMMX__ + + #ifdef __MWERKS__ + #define _WIN32 + #endif + + #ifdef __DOS__ + #define __RADDOS__ + #endif + + #ifdef __386__ + #define __RAD32__ + #endif + + #ifdef _Windows //For Borland + #ifdef __WIN32__ + #define WIN32 + #else + #define __WINDOWS__ + #endif + #endif + + #ifdef _WINDOWS //For MS + #ifndef _WIN32 + #define __WINDOWS__ + #endif + #endif + + #ifdef _WIN32 + #define __RADWIN__ + #define __RADNT__ + #define __RAD32__ + #else + #ifdef __NT__ + #define __RADWIN__ + #define __RADNT__ + #define __RAD32__ + #else + #ifdef __WINDOWS_386__ + #define __RADWIN__ + #define __RADWINEXT__ + #define __RAD32__ + #else + #ifdef __WINDOWS__ + #define __RADWIN__ + #define __RAD16__ + #else + #ifdef WIN32 + #define __RADWIN__ + #define __RADNT__ + #define __RAD32__ + #endif + #endif + #endif + #endif + #endif + + #define __RADLITTLEENDIAN__ + + // TODO - make sure these are set correctly for non-Mac versions + #define __RADALLOWINLINES__ + #define RADINLINE __inline + +#endif + +#ifndef __RADALLOWINLINES__ + #define RADINLINE +#endif + +#if (!defined(__RADDOS__) && !defined(__RADWIN__) && !defined(__RADMAC__)) + #error RAD.H did not detect your platform. Define __DOS__, __WINDOWS__, WIN32, macintosh, or powerc. +#endif + +#ifdef __RADMAC__ + + // this define is for CodeWarrior 11's stupid new libs (even though + // we don't use longlong's). + + #define __MSL_LONGLONG_SUPPORT__ + + #define RADLINK + #define RADEXPLINK + + #ifdef __CFM68K__ + #ifdef __RADINDLL__ + #define RADEXPFUNC RADDEFFUNC __declspec(export) + #else + #define RADEXPFUNC RADDEFFUNC __declspec(import) + #endif + #else + #define RADEXPFUNC RADDEFFUNC + #endif + #define RADASMLINK + +#else + + #ifdef __RADNT__ + #ifndef _WIN32 + #define _WIN32 + #endif + #ifndef WIN32 + #define WIN32 + #endif + #endif + + #ifdef __RADWIN__ + #ifdef __RAD32__ + #ifdef __RADNT__ + + #define RADLINK __stdcall + #define RADEXPLINK __stdcall + + #ifdef __RADINEXE__ + #define RADEXPFUNC RADDEFFUNC + #else + #ifndef __RADINDLL__ + #define RADEXPFUNC RADDEFFUNC __declspec(dllimport) + #ifdef __BORLANDC__ + #if __BORLANDC__<=0x460 + #undef RADEXPFUNC + #define RADEXPFUNC RADDEFFUNC + #endif + #endif + #else + #define RADEXPFUNC RADDEFFUNC __declspec(dllexport) + #endif + #endif + #else + #define RADLINK __pascal + #define RADEXPLINK __far __pascal + #define RADEXPFUNC RADDEFFUNC + #endif + #else + #define RADLINK __pascal + #define RADEXPLINK __far __pascal __export + #define RADEXPFUNC RADDEFFUNC + #endif + #else + #define RADLINK __pascal + #define RADEXPLINK __pascal + #define RADEXPFUNC RADDEFFUNC + #endif + + #define RADASMLINK __cdecl + +#endif + +#ifdef __RADWIN__ + #ifndef _WINDOWS + #define _WINDOWS + #endif +#endif + +#ifdef __cplusplus + #define RADDEFFUNC extern "C" + #define RADDEFSTART extern "C" { + #define RADDEFEND } +#else + #define RADDEFFUNC + #define RADDEFSTART + #define RADDEFEND +#endif + + +RADDEFSTART + +#define s8 signed char +#define u8 unsigned char +#define u32 unsigned long +#define s32 signed long +#define f32 float +#define f64 double + +#if defined(__MWERKS__) || defined(__MRC__) +#define u64 unsigned long long +#define s64 signed long long +#else +#define u64 unsigned __int64 +#define s64 signed __int64 +#endif + +/* 32 bit implementations */ + +#ifdef __RAD32__ + #define PTR4 + + #define u16 unsigned short + #define s16 signed short + + #ifdef __RADMAC__ + + #include + #include + #include + #include + #ifdef __MRC__ + #include "intrinsics.h" + #endif + + void radconv32a(void* p, u32 n); + + u32 radloadu32(u32 a); + + u32 radloadu32ptr(u32* p); + + #define radstrcpy strcpy + + #define radstrcat strcat + + #define radmemcpy(dest,source,size) BlockMoveData((Ptr)(source),(Ptr)(dest),size) + + #define radmemcpydb(dest,source,size) BlockMoveData((Ptr)(source),(Ptr)(dest),size) + + #define radmemcmp memcmp + + #define radmemset memset + + #define radstrlen strlen + + #define radstrchr strchr + + #define radtoupper toupper + + #define radstru32(s) ((u32)atol(s)) + + //s8 radstricmp(const void* s1,const void* s2); + + #define radstrcmp strcmp + + //char* radstrupr(void* s1); + + //char* radstrlwr(void* s1); + + u32 radsqr(u32 a); + + u32 mult64anddiv(u32 m1,u32 m2,u32 d); + + s32 radabs(s32 ab); + + #define radabs32 radabs + + //char* radstpcpy(void* dest,const void* source); + + //char* radstpcpyrs(void* dest,const void* source); + + void radmemset16(void* dest,u16 value,u32 size); + + //void radmemset32(void* dest,u32 value,u32 size); + + #define BreakPoint() DebugStr("\pBreakPoint() was called") + + //u8 radinp(u16 p); + + //void radoutp(u16 p,u8 v); + + //u32 RADsqrt(u32 sq); + + u32 RADCycleTimerAvail(void); + + void RADCycleTimerStartAddr(u32* addr); + + u32 RADCycleTimerDeltaAddr(u32* addr); + + void RADCycleTimerStartAddr64(u64* addr); + + void RADCycleTimerDeltaAddr64(u64* addr); + + #define RADCycleTimerStart(var) RADCycleTimerStartAddr(&var) + + #define RADCycleTimerDelta(var) RADCycleTimerDeltaAddr(&var) + + #define RADCycleTimerStart64(var) RADCycleTimerStartAddr64(&var) + + #define RADCycleTimerDelta64(var) RADCycleTimerDeltaAddr64(&var) + + + #ifdef __RAD68K__ + #pragma parameter radconv32a(__A0,__D0) + void radconv32a(void* p,u32 n) ={0x4A80,0x600C,0x2210,0xE059,0x4841,0xE059,0x20C1,0x5380,0x6EF2}; + // tst.l d0 bra.s @loope @loop: move.l (a0),d1 ror.w #8,d1 swap d1 ror.w #8,d1 move.l d1,(a0)+ sub.l #1,d0 bgt.s @loop @loope: + #endif + + #ifdef __RADALLOWINLINES__ + #if defined __RADPPC__ && defined(__MWERKS__) && (__MWERKS__ >= 0x2301) && 0 + u32 RADINLINE radloadu32(register u32 x) { + register u32 t1, t2; + asm { // x = aa bb cc dd + rlwinm t1,x,24,0,23 // t1 = dd aa bb 00 + rlwinm t2,x,8,24,31 // t2 = 00 00 00 aa + rlwimi t1,x,8, 8,15 // t1 = dd cc bb 00 + or x,t1,t2 // x = dd cc bb aa + } + return x; + } + #else + u32 RADINLINE radloadu32(register u32 x) { + return (((x << 24) & 0xFF000000) | + ((x << 8) & 0x00FF0000) | + ((x >> 8) & 0x0000FF00) | + ((x >> 24) & 0x000000FF)); + } + #endif + #endif + + #if defined(__RADPPC__) && (defined(__MWERKS__) || defined(__MRC__)) + #define radloadu32ptr(p) (u32) __lwbrx((p),0) + #else + #define radloadu32ptr(p) radloadu32(*(u32*)(p)); + #endif + + #ifdef __RADALLOWINLINES__ + u32 RADINLINE radsqr(u32 a) { return(a*a); } + #endif + + #ifdef __RAD68K__ + #pragma parameter __D0 mult64anddiv(__D0,__D1,__D2) + u32 mult64anddiv(u32 m1,u32 m2,u32 d) ={0x4C01,0x0C01,0x4C42,0x0C01}; + // muls.l d1,d1:d0 divs.l d2,d1:d0 + #endif + + #if defined(__RADPPC__) && (defined(__MWERKS__) || defined(__MRC__)) + #define radabs(ab) __abs((s32)(ab)) + #elif defined(__RADALLOWINLINES__) + s32 RADINLINE radabs(s32 ab) { return (ab < 0) ? -ab : ab; } + #endif + + #else + + #define radconv32a(p,n) ((void)0) + + #define radloadu32(a) ((u32)(a)) + + #define radloadu32ptr(p) *((u32*)(p)) + + #ifdef __WATCOMC__ + + u32 radsqr(s32 a); + #pragma aux radsqr = "mul eax" parm [eax] modify [EDX eax]; + + u32 mult64anddiv(u32 m1,u32 m2,u32 d); + #pragma aux mult64anddiv = "mul ecx" "div ebx" parm [eax] [ecx] [ebx] modify [EDX eax]; + + s32 radabs(s32 ab); + #pragma aux radabs = "test eax,eax" "jge skip" "neg eax" "skip:" parm [eax]; + + #define radabs32 radabs + + u32 DOSOut(const char* str); + #pragma aux DOSOut = "cld" "mov ecx,0xffffffff" "xor eax,eax" "mov edx,edi" "repne scasb" "not ecx" "dec ecx" "mov ebx,1" "mov ah,0x40" "int 0x21" parm [EDI] modify [EAX EBX ECX EDX EDI] value [ecx]; + + void DOSOutNum(const char* str,u32 len); + #pragma aux DOSOutNum = "mov ah,0x40" "mov ebx,1" "int 0x21" parm [edx] [ecx] modify [eax ebx]; + + u32 ErrOut(const char* str); + #pragma aux ErrOut = "cld" "mov ecx,0xffffffff" "xor eax,eax" "mov edx,edi" "repne scasb" "not ecx" "dec ecx" "xor ebx,ebx" "mov ah,0x40" "int 0x21" parm [EDI] modify [EAX EBX ECX EDX EDI] value [ecx]; + + void ErrOutNum(const char* str,u32 len); + #pragma aux ErrOutNum = "mov ah,0x40" "xor ebx,ebx" "int 0x21" parm [edx] [ecx] modify [eax ebx]; + + void radmemset16(void* dest,u16 value,u32 size); + #pragma aux radmemset16 = "cld" "mov bx,ax" "shl eax,16" "mov ax,bx" "mov bl,cl" "shr ecx,1" "rep stosd" "mov cl,bl" "and cl,1" "rep stosw" parm [EDI] [EAX] [ECX] modify [EAX EDX EBX ECX EDI]; + + void radmemset(void* dest,u8 value,u32 size); + #pragma aux radmemset = "cld" "mov ah,al" "mov bx,ax" "shl eax,16" "mov ax,bx" "mov bl,cl" "shr ecx,2" "and bl,3" "rep stosd" "mov cl,bl" "rep stosb" parm [EDI] [AL] [ECX] modify [EAX EDX EBX ECX EDI]; + + void radmemset32(void* dest,u32 value,u32 size); + #pragma aux radmemset32 = "cld" "rep stosd" parm [EDI] [EAX] [ECX] modify [EAX EDX EBX ECX EDI]; + + void radmemcpy(void* dest,const void* source,u32 size); + #pragma aux radmemcpy = "cld" "mov bl,cl" "shr ecx,2" "rep movsd" "mov cl,bl" "and cl,3" "rep movsb" parm [EDI] [ESI] [ECX] modify [EBX ECX EDI ESI]; + + void __far *radfmemcpy(void __far* dest,const void __far* source,u32 size); + #pragma aux radfmemcpy = "cld" "push es" "push ds" "mov es,cx" "mov ds,dx" "mov ecx,eax" "shr ecx,2" "rep movsd" "mov cl,al" "and cl,3" "rep movsb" "pop ds" "pop es" parm [CX EDI] [DX ESI] [EAX] modify [ECX EDI ESI] value [CX EDI]; + + void radmemcpydb(void* dest,const void* source,u32 size); //Destination bigger + #pragma aux radmemcpydb = "std" "mov bl,cl" "lea esi,[esi+ecx-4]" "lea edi,[edi+ecx-4]" "shr ecx,2" "rep movsd" "and bl,3" "jz dne" "add esi,3" "add edi,3" "mov cl,bl" "rep movsb" "dne:" "cld" parm [EDI] [ESI] [ECX] modify [EBX ECX EDI ESI]; + + char* radstrcpy(void* dest,const void* source); + #pragma aux radstrcpy = "cld" "mov edx,edi" "lp:" "mov al,[esi]" "inc esi" "mov [edi],al" "inc edi" "cmp al,0" "jne lp" parm [EDI] [ESI] modify [EAX EDX EDI ESI] value [EDX]; + + char __far* radfstrcpy(void __far* dest,const void __far* source); + #pragma aux radfstrcpy = "cld" "push es" "push ds" "mov es,cx" "mov ds,dx" "mov edx,edi" "lp:" "lodsb" "stosb" "test al,0xff" "jnz lp" "pop ds" "pop es" parm [CX EDI] [DX ESI] modify [EAX EDX EDI ESI] value [CX EDX]; + + char* radstpcpy(void* dest,const void* source); + #pragma aux radstpcpy = "cld" "lp:" "mov al,[esi]" "inc esi" "mov [edi],al" "inc edi" "cmp al,0" "jne lp" "dec edi" parm [EDI] [ESI] modify [EAX EDI ESI] value [EDI]; + + char* radstpcpyrs(void* dest,const void* source); + #pragma aux radstpcpyrs = "cld" "lp:" "mov al,[esi]" "inc esi" "mov [edi],al" "inc edi" "cmp al,0" "jne lp" "dec esi" parm [EDI] [ESI] modify [EAX EDI ESI] value [ESI]; + + u32 radstrlen(const void* dest); + #pragma aux radstrlen = "cld" "mov ecx,0xffffffff" "xor eax,eax" "repne scasb" "not ecx" "dec ecx" parm [EDI] modify [EAX ECX EDI] value [ECX]; + + char* radstrcat(void* dest,const void* source); + #pragma aux radstrcat = "cld" "mov ecx,0xffffffff" "mov edx,edi" "xor eax,eax" "repne scasb" "dec edi" "lp:" "lodsb" "stosb" "test al,0xff" "jnz lp" \ + parm [EDI] [ESI] modify [EAX ECX EDI ESI] value [EDX]; + + char* radstrchr(const void* dest,char chr); + #pragma aux radstrchr = "cld" "lp:" "lodsb" "cmp al,dl" "je fnd" "cmp al,0" "jnz lp" "mov esi,1" "fnd:" "dec esi" parm [ESI] [DL] modify [EAX ESI] value [esi]; + + s8 radmemcmp(const void* s1,const void* s2,u32 len); + #pragma aux radmemcmp = "cld" "rep cmpsb" "setne al" "jbe end" "neg al" "end:" parm [EDI] [ESI] [ECX] modify [ECX EDI ESI]; + + s8 radstrcmp(const void* s1,const void* s2); + #pragma aux radstrcmp = "lp:" "mov al,[esi]" "mov ah,[edi]" "cmp al,ah" "jne set" "cmp al,0" "je set" "inc esi" "inc edi" "jmp lp" "set:" "setne al" "jbe end" "neg al" "end:" \ + parm [EDI] [ESI] modify [EAX EDI ESI]; + + s8 radstricmp(const void* s1,const void* s2); + #pragma aux radstricmp = "lp:" "mov al,[esi]" "mov ah,[edi]" "cmp al,'a'" "jb c1" "cmp al,'z'" "ja c1" "sub al,32" "c1:" "cmp ah,'a'" "jb c2" "cmp ah,'z'" "ja c2" "sub ah,32" "c2:" "cmp al,ah" "jne set" "cmp al,0" "je set" \ + "inc esi" "inc edi" "jmp lp" "set:" "setne al" "jbe end" "neg al" "end:" \ + parm [EDI] [ESI] modify [EAX EDI ESI]; + + s8 radstrnicmp(const void* s1,const void* s2,u32 len); + #pragma aux radstrnicmp = "lp:" "mov al,[esi]" "mov ah,[edi]" "cmp al,'a'" "jb c1" "cmp al,'z'" "ja c1" "sub al,32" "c1:" "cmp ah,'a'" "jb c2" "cmp ah,'z'" "ja c2" "sub ah,32" "c2:" "cmp al,ah" "jne set" "cmp al,0" "je set" \ + "dec ecx" "jz set" "inc esi" "inc edi" "jmp lp" "set:" "setne al" "jbe end" "neg al" "end:" \ + parm [EDI] [ESI] [ECX] modify [EAX ECX EDI ESI]; + + char* radstrupr(void* s1); + #pragma aux radstrupr = "mov ecx,edi" "lp:" "mov al,[edi]" "cmp al,'a'" "jb c1" "cmp al,'z'" "ja c1" "sub [edi],32" "c1:" "inc edi" "cmp al,0" "jne lp" parm [EDI] modify [EAX EDI] value [ecx]; + + char* radstrlwr(void* s1); + #pragma aux radstrlwr = "mov ecx,edi" "lp:" "mov al,[edi]" "cmp al,'A'" "jb c1" "cmp al,'Z'" "ja c1" "add [edi],32" "c1:" "inc edi" "cmp al,0" "jne lp" parm [EDI] modify [EAX EDI] value [ecx]; + + u32 radstru32(const void* dest); + #pragma aux radstru32 = "cld" "xor ecx,ecx" "xor ebx,ebx" "xor edi,edi" "lodsb" "cmp al,45" "jne skip2" "mov edi,1" "jmp skip" "lp:" "mov eax,10" "mul ecx" "lea ecx,[eax+ebx]" \ + "skip:" "lodsb" "skip2:" "cmp al,0x39" "ja dne" "cmp al,0x30" "jb dne" "mov bl,al" "sub bl,0x30" "jmp lp" "dne:" "test edi,1" "jz pos" "neg ecx" "pos:" \ + parm [ESI] modify [EAX EBX EDX EDI ESI] value [ecx]; + + u16 GetDS(); + #pragma aux GetDS = "mov ax,ds" value [ax]; + + #ifdef __RADWINEXT__ + + #define _16To32(ptr16) ((void*)(((GetSelectorBase((u16)(((u32)(ptr16))>>16))+((u16)(u32)(ptr16)))-GetSelectorBase(GetDS())))) + + #endif + + #ifndef __RADWIN__ + #define int86 int386 + #define int86x int386x + #endif + + #define u32regs x + #define u16regs w + + #else + + #define radstrcpy strcpy + #define radstrcat strcat + #define radmemcpy memcpy + #define radmemcpydb memmove + #define radmemcmp memcmp + #define radmemset memset + #define radstrlen strlen + #define radstrchr strchr + #define radtoupper toupper + #define radstru32(s) ((u32)atol(s)) + #define radstricmp _stricmp + #define radstrcmp strcmp + #define radstrupr _strupr + #define radstrlwr _strlwr + #define BreakPoint() __asm {int 3} + #define DOSOut(str) + + #ifdef _MSC_VER + + #pragma warning( disable : 4035) + + typedef char* RADPCHAR; + + u32 __inline radsqr(u32 m) { + __asm { + mov eax,[m] + mul eax + } + } + + u32 __inline mult64anddiv(u32 m1,u32 m2, u32 d) { + __asm { + mov eax,[m1] + mov ecx,[m2] + mul ecx + mov ecx,[d] + div ecx + } + } + + s32 __inline radabs(s32 ab) { + __asm { + mov eax,[ab] + test eax,eax + jge skip + neg eax + skip: + } + } + + u8 __inline radinp(u16 p) { + __asm { + mov dx,[p] + in al,dx + } + } + + void __inline radoutp(u16 p,u8 v) { + __asm { + mov dx,[p] + mov al,[v] + out dx,al + } + } + + RADPCHAR __inline radstpcpy(char* p1, char* p2) { + __asm { + mov edx,[p1] + mov ecx,[p2] + cld + lp: + mov al,[ecx] + inc ecx + mov [edx],al + inc edx + cmp al,0 + jne lp + dec edx + mov eax,edx + } + } + + RADPCHAR __inline radstpcpyrs(char* p1, char* p2) { + __asm { + mov edx,[p1] + mov ecx,[p2] + cld + lp: + mov al,[ecx] + inc ecx + mov [edx],al + inc edx + cmp al,0 + jne lp + dec ecx + mov eax,ecx + } + } + + void __inline radmemset16(void* dest,u16 value,u32 sizeb) { + __asm { + mov edi,[dest] + mov ax,[value] + mov ecx,[sizeb] + shl eax,16 + cld + mov ax,[value] + mov bl,cl + shr ecx,1 + rep stosd + mov cl,bl + and cl,1 + rep stosw + } + } + + void __inline radmemset32(void* dest,u32 value,u32 sizeb) { + __asm { + mov edi,[dest] + mov eax,[value] + mov ecx,[sizeb] + cld + rep stosd + } + } + + u32 __inline __stdcall RADsqrt(u32 sq) { + __asm { + fild dword ptr [sq] + fsqrt + fistp word ptr [sq] + movzx eax,word ptr [sq] + } + } + + u32 __inline RADCycleTimerAvail(void) + { + u32 rdtscavail=(u32)-1; + __try + { + __asm + { +#ifdef __MWERKS__ + rdtsc +#else +#if _MSC_VER<=1100 + __emit 0xf + __emit 0x31 +#else + rdtsc +#endif +#endif + } + rdtscavail=1; + } + __except (1) + { + rdtscavail=(u32)-1; + } + return rdtscavail; + } + + void __inline RADCycleTimerStartAddr(u32* addr) + { + __asm { + mov ecx,[addr] +#ifdef __MWERKS__ + rdtsc +#else +#if _MSC_VER<=1100 + __emit 0xf + __emit 0x31 +#else + rdtsc +#endif +#endif + mov [ecx],eax + } + } + + u32 __inline RADCycleTimerDeltaAddr(u32* addr) + { + __asm { +#ifdef __MWERKS__ + rdtsc +#else +#if _MSC_VER<=1100 + __emit 0xf + __emit 0x31 +#else + rdtsc +#endif +#endif + mov ecx,[addr] + mov edx,eax + sub eax,[ecx] + mov [ecx],eax + } + } + + void __inline RADCycleTimerStartAddr64(u64* addr) + { + __asm { + mov ecx,[addr] +#ifdef __MWERKS__ + rdtsc +#else +#if _MSC_VER<=1100 + __emit 0xf + __emit 0x31 +#else + rdtsc +#endif +#endif + mov [ecx],eax + mov [ecx+4],edx + } + } + + void __inline RADCycleTimerDeltaAddr64(u64* addr) + { + __asm { +#ifdef __MWERKS__ + rdtsc +#else +#if _MSC_VER<=1100 + __emit 0xf + __emit 0x31 +#else + rdtsc +#endif +#endif + mov ecx,[addr] + sub eax,[ecx] + sbb edx,[ecx+4] + mov [ecx],eax + mov [ecx+4],edx + } + } + + #define RADCycleTimerStart(var) RADCycleTimerStartAddr(&var) + #define RADCycleTimerDelta(var) RADCycleTimerDeltaAddr(&var) + + #define RADCycleTimerStart64(var) RADCycleTimerStartAddr64(&var) + #define RADCycleTimerDelta64(var) RADCycleTimerDeltaAddr64(&var) + + #pragma warning( default : 4035) + + #endif + + #endif + + #endif + +#else + + #define PTR4 __far + + #define u16 unsigned int + #define s16 signed int + + #ifdef __WATCOMC__ + + u32 radsqr(s32 a); + #pragma aux radsqr = "shl edx,16" "mov dx,ax" "mov eax,edx" "xor edx,edx" "mul eax" "shld edx,eax,16" parm [dx ax] modify [DX ax] value [dx ax]; + + s16 radabs(s16 ab); + #pragma aux radabs = "test ax,ax" "jge skip" "neg ax" "skip:" parm [ax] value [ax]; + + s32 radabs32(s32 ab); + #pragma aux radabs32 = "test dx,dx" "jge skip" "neg dx" "neg ax" "sbb dx,0" "skip:" parm [dx ax] value [dx ax]; + + u32 DOSOut(const char far* dest); + #pragma aux DOSOut = "cld" "and edi,0xffff" "mov dx,di" "mov ecx,0xffffffff" "xor eax,eax" 0x67 "repne scasb" "not ecx" "dec ecx" "mov bx,1" "push ds" "push es" "pop ds" "mov ah,0x40" "int 0x21" "pop ds" "movzx eax,cx" "shr ecx,16" \ + parm [ES DI] modify [AX BX CX DX DI ES] value [CX AX]; + + void DOSOutNum(const char far* str,u16 len); + #pragma aux DOSOutNum = "push ds" "mov ds,cx" "mov cx,bx" "mov ah,0x40" "mov bx,1" "int 0x21" "pop ds" parm [cx dx] [bx] modify [ax bx cx]; + + u32 ErrOut(const char far* dest); + #pragma aux ErrOut = "cld" "and edi,0xffff" "mov dx,di" "mov ecx,0xffffffff" "xor eax,eax" 0x67 "repne scasb" "not ecx" "dec ecx" "xor bx,bx" "push ds" "push es" "pop ds" "mov ah,0x40" "int 0x21" "pop ds" "movzx eax,cx" "shr ecx,16" \ + parm [ES DI] modify [AX BX CX DX DI ES] value [CX AX]; + + void ErrOutNum(const char far* str,u16 len); + #pragma aux ErrOutNum = "push ds" "mov ds,cx" "mov cx,bx" "mov ah,0x40" "xor bx,bx" "int 0x21" "pop ds" parm [cx dx] [bx] modify [ax bx cx]; + + void radmemset(void far *dest,u8 value,u32 size); + #pragma aux radmemset = "cld" "and edi,0ffffh" "shl ecx,16" "mov cx,bx" "mov ah,al" "mov bx,ax" "shl eax,16" "mov ax,bx" "mov bl,cl" "shr ecx,2" 0x67 "rep stosd" "mov cl,bl" "and cl,3" "rep stosb" parm [ES DI] [AL] [CX BX]; + + void radmemset16(void far* dest,u16 value,u32 size); + #pragma aux radmemset16 = "cld" "and edi,0ffffh" "shl ecx,16" "mov cx,bx" "mov bx,ax" "shl eax,16" "mov ax,bx" "mov bl,cl" "shr ecx,1" "rep stosd" "mov cl,bl" "and cl,1" "rep stosw" parm [ES DI] [AX] [CX BX]; + + void radmemcpy(void far* dest,const void far* source,u32 size); + #pragma aux radmemcpy = "cld" "push ds" "mov ds,dx" "and esi,0ffffh" "and edi,0ffffh" "shl ecx,16" "mov cx,bx" "shr ecx,2" 0x67 "rep movsd" "mov cl,bl" "and cl,3" "rep movsb" "pop ds" parm [ES DI] [DX SI] [CX BX] modify [CX SI DI ES]; + + s8 radmemcmp(const void far* s1,const void far* s2,u32 len); + #pragma aux radmemcmp = "cld" "push ds" "mov ds,dx" "shl ecx,16" "mov cx,bx" "rep cmpsb" "setne al" "jbe end" "neg al" "end:" "pop ds" parm [ES DI] [DX SI] [CX BX] modify [CX SI DI ES]; + + char far* radstrcpy(void far* dest,const void far* source); + #pragma aux radstrcpy = "cld" "push ds" "mov ds,dx" "and esi,0xffff" "and edi,0xffff" "mov dx,di" "lp:" "lodsb" "stosb" "test al,0xff" "jnz lp" "pop ds" parm [ES DI] [DX SI] modify [AX DX DI SI ES] value [es dx]; + + char far* radstpcpy(void far* dest,const void far* source); + #pragma aux radstpcpy = "cld" "push ds" "mov ds,dx" "and esi,0xffff" "and edi,0xffff" "lp:" "lodsb" "stosb" "test al,0xff" "jnz lp" "dec di" "pop ds" parm [ES DI] [DX SI] modify [DI SI ES] value [es di]; + + u32 radstrlen(const void far* dest); + #pragma aux radstrlen = "cld" "and edi,0xffff" "mov ecx,0xffffffff" "xor eax,eax" 0x67 "repne scasb" "not ecx" "dec ecx" "movzx eax,cx" "shr ecx,16" parm [ES DI] modify [AX CX DI ES] value [CX AX]; + + char far* radstrcat(void far* dest,const void far* source); + #pragma aux radstrcat = "cld" "and edi,0xffff" "mov ecx,0xffffffff" "and esi,0xffff" "push ds" "mov ds,dx" "mov dx,di" "xor eax,eax" 0x67 "repne scasb" "dec edi" "lp:" "lodsb" "stosb" "test al,0xff" "jnz lp" "pop ds" \ + parm [ES DI] [DX SI] modify [AX CX DI SI ES] value [es dx]; + + char far* radstrchr(const void far* dest,char chr); + #pragma aux radstrchr = "cld" "lp:" 0x26 "lodsb" "cmp al,dl" "je fnd" "cmp al,0" "jnz lp" "xor ax,ax" "mov es,ax" "mov si,1" "fnd:" "dec si" parm [ES SI] [DL] modify [AX SI ES] value [es si]; + + s8 radstricmp(const void far* s1,const void far* s2); + #pragma aux radstricmp = "and edi,0xffff" "push ds" "mov ds,dx" "and esi,0xffff" "lp:" "mov al,[esi]" "mov ah,[edi]" "cmp al,'a'" "jb c1" "cmp al,'z'" "ja c1" "sub al,32" "c1:" \ + "cmp ah,'a'" "jb c2" "cmp ah,'z'" "ja c2" "sub ah,32" "c2:" "cmp al,ah" "jne set" "cmp al,0" "je set" \ + "inc esi" "inc edi" "jmp lp" "set:" "setne al" "jbe end" "neg al" "end:" "pop ds" \ + parm [ES DI] [DX SI] modify [AX DI SI]; + + u32 radstru32(const void far* dest); + #pragma aux radstru32 = "cld" "xor ecx,ecx" "xor ebx,ebx" "xor edi,edi" 0x26 "lodsb" "cmp al,45" "jne skip2" "mov edi,1" "jmp skip" "lp:" "mov eax,10" "mul ecx" "lea ecx,[eax+ebx]" \ + "skip:" 0x26 "lodsb" "skip2:" "cmp al,0x39" "ja dne" "cmp al,0x30" "jb dne" "mov bl,al" "sub bl,0x30" "jmp lp" "dne:" "test edi,1" "jz pos" "neg ecx" "pos:" \ + "movzx eax,cx" "shr ecx,16" parm [ES SI] modify [AX BX DX DI SI] value [cx ax]; + + u32 mult64anddiv(u32 m1,u32 m2,u32 d); + #pragma aux mult64anddiv = "shl ecx,16" "mov cx,ax" "shrd eax,edx,16" "mov ax,si" "mul ecx" "shl edi,16" "mov di,bx" "div edi" "shld edx,eax,16" "and edx,0xffff" "and eax,0xffff" parm [cx ax] [dx si] [di bx] \ + modify [ax bx cx dx si di] value [dx ax]; + + #endif + +#endif + +RADDEFEND + +#define u32neg1 ((u32)(s32)-1) +#define RAD_align(var) var; u8 junk##var[4-(sizeof(var)&3)]; +#define RAD_align_after(var) u8 junk##var[4-(sizeof(var)&3)]={0}; +#define RAD_align_init(var,val) var=val; u8 junk##var[4-(sizeof(var)&3)]={0}; +#define RAD_align_array(var,num) var[num]; u8 junk##var[4-(sizeof(var)&3)]; +#define RAD_align_string(var,str) char var[]=str; u8 junk##var[4-(sizeof(var)&3)]={0}; + + +typedef void PTR4* (RADLINK PTR4* RADMEMALLOC) (u32 bytes); +typedef void (RADLINK PTR4* RADMEMFREE) (void PTR4* ptr); + +#ifdef __RADMAC__ + #pragma export on +#endif +RADEXPFUNC void RADEXPLINK RADSetMemory(RADMEMALLOC a,RADMEMFREE f); +#ifdef __RADMAC__ + #pragma export off +#endif + +RADEXPFUNC void PTR4* RADEXPLINK radmalloc(u32 numbytes); +RADEXPFUNC void RADEXPLINK radfree(void PTR4* ptr); + +#ifdef __RADDOS__ + + RADDEFSTART + extern void* RADTimerSetupAddr; + extern void* RADTimerReadAddr; + extern void* RADTimerDoneAddr; + RADDEFEND + + typedef void RADEXPLINK (*RADTimerSetupType)(void); + typedef u32 RADEXPLINK (*RADTimerReadType)(void); + typedef void RADEXPLINK (*RADTimerDoneType)(void); + + #define RADTimerSetup() ((RADTimerSetupType)(RADTimerSetupAddr))() + #define RADTimerRead() ((RADTimerReadType)(RADTimerReadAddr))() + #define RADTimerDone() ((RADTimerDoneType)(RADTimerDoneAddr))() + +#else + + #define RADTimerSetup() + #define RADTimerDone() + + #if (defined(__RAD16__) || defined(__RADWINEXT__)) + + #define RADTimerRead timeGetTime + + #else + + RADEXPFUNC u32 RADEXPLINK RADTimerRead(void); + + #endif + +#endif + + +#ifdef __WATCOMC__ + + char bkbhit(); + #pragma aux bkbhit = "mov ah,1" "int 0x16" "lahf" "shr eax,14" "and eax,1" "xor al,1" ; + + char bgetch(); + #pragma aux bgetch = "xor ah,ah" "int 0x16" "test al,0xff" "jnz done" "mov al,ah" "or al,0x80" "done:" modify [AX]; + + void BreakPoint(); + #pragma aux BreakPoint = "int 3"; + + u8 radinp(u16 p); + #pragma aux radinp = "in al,dx" parm [DX]; + + u8 radtoupper(u8 p); + #pragma aux radtoupper = "cmp al,'a'" "jb c1" "cmp al,'z'" "ja c1" "sub al,32" "c1:" parm [al] value [al]; + + void radoutp(u16 p,u8 v); + #pragma aux radoutp = "out dx,al" parm [DX] [AL]; + +#else + +// for multi-processor machines + +#ifdef __RADNT__ + #define LockedIncrement(var) __asm { lock inc [var] } + #define LockedDecrement(var) __asm { lock dec [var] } + void __inline LockedIncrementFunc(void PTR4* var) { + __asm { + mov eax,[var] + lock inc [eax] + } + } + + void __inline LockedDecrementFunc(void PTR4* var) { + __asm { + mov eax,[var] + lock dec [eax] + } + } + +#else + + #ifdef __RADMAC__ + + #define LockedIncrement(var) {++(var);} + #define LockedDecrement(var) {--(var);} + + #define LockedIncrementFunc(ptr) {++(*((u32*)(ptr)));} + #define LockedDecrementFunc(ptr) {--(*((u32*)(ptr)));} + + #else + + #define LockedIncrement(var) __asm { inc [var] } + #define LockedDecrement(var) __asm { dec [var] } + void __inline LockedIncrementFunc(void PTR4* var) { __asm { mov eax,[var] + inc [eax] } } + void __inline LockedDecrementFunc(void PTR4* var) { __asm { mov eax,[var] + dec [eax] } } + #endif + +#endif + +#endif + +#endif + +#endif + diff --git a/code/win32/resource.h b/code/win32/resource.h new file mode 100644 index 0000000..2a145bf --- /dev/null +++ b/code/win32/resource.h @@ -0,0 +1,21 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by winquake.rc +// +#define IDS_STRING1 1 +#define IDI_ICON1 1 +#define IDB_BITMAP1 1 +#define IDB_BITMAP2 128 +#define IDC_CURSOR1 129 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/code/win32/shader_constants.h b/code/win32/shader_constants.h new file mode 100644 index 0000000..44a6aaf --- /dev/null +++ b/code/win32/shader_constants.h @@ -0,0 +1,64 @@ +// +// +// ShaderConstants.h +// +// Definitions for various shader constants +// +// + +#ifndef _SHADERCONSTANTS_H_ +#define _SHADERCONSTANTS_H_ + +#define CV_ZERO 0 +#define CV_ONE 1 +#define CV_HALF 2 + +#define CV_WORLDVIEWPROJ_0 4 +#define CV_WORLDVIEWPROJ_1 5 +#define CV_WORLDVIEWPROJ_2 6 +#define CV_WORLDVIEWPROJ_3 7 + +#define CV_WORLDVIEW_0 8 +#define CV_WORLDVIEW_1 9 +#define CV_WORLDVIEW_2 10 +#define CV_WORLDVIEW_3 11 + +#define CV_WORLDVIEWIT_0 12 +#define CV_WORLDVIEWIT_1 13 +#define CV_WORLDVIEWIT_2 14 + +#define CV_WORLD_0 15 +#define CV_WORLD_1 16 +#define CV_WORLD_2 17 +#define CV_WORLD_3 18 + +#define CV_VIEWINV_0 19 +#define CV_VIEWINV_1 20 +#define CV_VIEWINV_2 21 +#define CV_VIEWINV_3 22 + +#define CV_VIEW_0 23 +#define CV_VIEW_1 24 +#define CV_VIEW_2 25 +#define CV_VIEW_3 26 + +#define CV_LIGHT_COLOR 50 +#define CV_ONE_OVER_LIGHT_RANGE 51 + +#define CV_LIGHT_DIRECTION 52 +#define CV_LIGHT_POSITION 53 + +#define CV_CAMERA_DIRECTION 54 + +#define CV_EXTRUSION_LENGTH 55 +#define CV_SHADOW_FACTORS 56 +#define CV_SHADOW_PLANE 57 + +#define CV_VIEWPORT_OFFSETS 95 + +#define CP_AMBIENT_COLOR 0 +#define CP_DIFFUSE_COLOR 1 +#define CP_EXTRACT_CUTOFF 2 +#define CP_EXTRACT_SCALE 3 + +#endif \ No newline at end of file diff --git a/code/win32/snd_fx_img.h b/code/win32/snd_fx_img.h new file mode 100644 index 0000000..134e457 --- /dev/null +++ b/code/win32/snd_fx_img.h @@ -0,0 +1,85 @@ + +#pragma once + +typedef enum _DSP_IMAGE_image_FX_INDICES { + GraphI3DL2_I3DL2Reverb = 0, + GraphXTalk_XTalk = 1, + GraphVoice_Voice_0 = 2, + GraphVoice_Voice_1 = 3, + GraphVoice_Voice_2 = 4, + GraphVoice_Voice_3 = 5 +} DSP_IMAGE_image_FX_INDICES; + +#define DSI3DL2_ENVIRONMENT_GraphI3DL2_I3DL2Reverb -1000, -100, 0.000000, 1.490000, 0.830000, -2602, 0.007000, 200, 0.011000, 100.000000, 100.000000, 5000.000000 + +typedef struct _GraphI3DL2_FX0_I3DL2Reverb_STATE { + DWORD dwScratchOffset; // Offset in bytes, of scratch area for this FX + DWORD dwScratchLength; // Length in DWORDS, of scratch area for this FX + DWORD dwYMemoryOffset; // Offset in DSP WORDS, of Y memory area for this FX + DWORD dwYMemoryLength; // Length in DSP WORDS, of Y memory area for this FX + DWORD dwFlags; // FX bitfield for various flags. See xgpimage documentation + DWORD dwInMixbinPtrs[2]; // XRAM offsets in DSP WORDS, of input mixbins + DWORD dwOutMixbinPtrs[35]; // XRAM offsets in DSP WORDS, of output mixbins +} GraphI3DL2_FX0_I3DL2Reverb_STATE, *LPGraphI3DL2_FX0_I3DL2Reverb_STATE; + +typedef const GraphI3DL2_FX0_I3DL2Reverb_STATE *LPCGraphI3DL2_FX0_I3DL2Reverb_STATE; + +typedef struct _GraphXTalk_FX0_XTalk_STATE { + DWORD dwScratchOffset; // Offset in bytes, of scratch area for this FX + DWORD dwScratchLength; // Length in DWORDS, of scratch area for this FX + DWORD dwYMemoryOffset; // Offset in DSP WORDS, of Y memory area for this FX + DWORD dwYMemoryLength; // Length in DSP WORDS, of Y memory area for this FX + DWORD dwFlags; // FX bitfield for various flags. See xgpimage documentation + DWORD dwInMixbinPtrs[4]; // XRAM offsets in DSP WORDS, of input mixbins + DWORD dwOutMixbinPtrs[4]; // XRAM offsets in DSP WORDS, of output mixbins +} GraphXTalk_FX0_XTalk_STATE, *LPGraphXTalk_FX0_XTalk_STATE; + +typedef const GraphXTalk_FX0_XTalk_STATE *LPCGraphXTalk_FX0_XTalk_STATE; + +typedef struct _GraphVoice_FX0_Voice_0_STATE { + DWORD dwScratchOffset; // Offset in bytes, of scratch area for this FX + DWORD dwScratchLength; // Length in DWORDS, of scratch area for this FX + DWORD dwYMemoryOffset; // Offset in DSP WORDS, of Y memory area for this FX + DWORD dwYMemoryLength; // Length in DSP WORDS, of Y memory area for this FX + DWORD dwFlags; // FX bitfield for various flags. See xgpimage documentation + DWORD dwInMixbinPtrs[1]; // XRAM offsets in DSP WORDS, of input mixbins + DWORD dwOutMixbinPtrs[1]; // XRAM offsets in DSP WORDS, of output mixbins +} GraphVoice_FX0_Voice_0_STATE, *LPGraphVoice_FX0_Voice_0_STATE; + +typedef const GraphVoice_FX0_Voice_0_STATE *LPCGraphVoice_FX0_Voice_0_STATE; + +typedef struct _GraphVoice_FX1_Voice_1_STATE { + DWORD dwScratchOffset; // Offset in bytes, of scratch area for this FX + DWORD dwScratchLength; // Length in DWORDS, of scratch area for this FX + DWORD dwYMemoryOffset; // Offset in DSP WORDS, of Y memory area for this FX + DWORD dwYMemoryLength; // Length in DSP WORDS, of Y memory area for this FX + DWORD dwFlags; // FX bitfield for various flags. See xgpimage documentation + DWORD dwInMixbinPtrs[1]; // XRAM offsets in DSP WORDS, of input mixbins + DWORD dwOutMixbinPtrs[1]; // XRAM offsets in DSP WORDS, of output mixbins +} GraphVoice_FX1_Voice_1_STATE, *LPGraphVoice_FX1_Voice_1_STATE; + +typedef const GraphVoice_FX1_Voice_1_STATE *LPCGraphVoice_FX1_Voice_1_STATE; + +typedef struct _GraphVoice_FX2_Voice_2_STATE { + DWORD dwScratchOffset; // Offset in bytes, of scratch area for this FX + DWORD dwScratchLength; // Length in DWORDS, of scratch area for this FX + DWORD dwYMemoryOffset; // Offset in DSP WORDS, of Y memory area for this FX + DWORD dwYMemoryLength; // Length in DSP WORDS, of Y memory area for this FX + DWORD dwFlags; // FX bitfield for various flags. See xgpimage documentation + DWORD dwInMixbinPtrs[1]; // XRAM offsets in DSP WORDS, of input mixbins + DWORD dwOutMixbinPtrs[1]; // XRAM offsets in DSP WORDS, of output mixbins +} GraphVoice_FX2_Voice_2_STATE, *LPGraphVoice_FX2_Voice_2_STATE; + +typedef const GraphVoice_FX2_Voice_2_STATE *LPCGraphVoice_FX2_Voice_2_STATE; + +typedef struct _GraphVoice_FX3_Voice_3_STATE { + DWORD dwScratchOffset; // Offset in bytes, of scratch area for this FX + DWORD dwScratchLength; // Length in DWORDS, of scratch area for this FX + DWORD dwYMemoryOffset; // Offset in DSP WORDS, of Y memory area for this FX + DWORD dwYMemoryLength; // Length in DSP WORDS, of Y memory area for this FX + DWORD dwFlags; // FX bitfield for various flags. See xgpimage documentation + DWORD dwInMixbinPtrs[1]; // XRAM offsets in DSP WORDS, of input mixbins + DWORD dwOutMixbinPtrs[1]; // XRAM offsets in DSP WORDS, of output mixbins +} GraphVoice_FX3_Voice_3_STATE, *LPGraphVoice_FX3_Voice_3_STATE; + +typedef const GraphVoice_FX3_Voice_3_STATE *LPCGraphVoice_FX3_Voice_3_STATE; diff --git a/code/win32/starwars.ico b/code/win32/starwars.ico new file mode 100644 index 0000000000000000000000000000000000000000..ceefbbd40af7a9fc723d8f506a1f18eac03fe95d GIT binary patch literal 3638 zcmeH|KWy7Z6o;RS7O0mXt21ShS;o>J5|E`qMiI$?t;rr7@0to2^&%uC89<``ID_Qc zfQF1lhN39s$xGmDMg|+`kV#YENd_`hmMqI6G872=9cc+kQB485Bu5?Z-u=FNcYLJo ziOk41a`&zT<)`mN=IPzPAKHI>FS5tj510y3!!yF5Dl=g-=}&A|grB%>*l?#SCnqOz za(*I%KYy3@z?Y+rFMDlYwoZLnIrb&e@_ z@4;`uufRv9L`THGfIovjgx`lRz;8yx-yoW+L^Dk^<3uw;H0R(4@E!Oz{3-k~d<(t> z--LJI^YA%%1Kx-C;eB`?-iPS*nbD8?GNBP z@NM{0_+xm2GbKQ6pYNHvN8O^XP$Sf70U8d6^6uR`dHeRQym|9RUcY`VFJHctR;wik z2M5w@Hs#r~XW}@HY;SK%KA)FIj~+=bmy@NXB{2*`=I7_--o1Nr=gu7&{O!wcFJACJ zcKN@suKFvne$a4NTk+LRMxGZNrG}%Ccq30Fsy|rJ5(zD#C!=~4dqGcXi`R+7vX;_E z@F^{6npeidbu+1_M7)%iF?!v&kukGcN<42l^RW9@I=$4rx>`(mkIQAZQFkoMsW(b) zdE}NFj&1$Yv8uDS&4}wZE^TYJW6xT))7W)K@F2BWc(+`B?0F*wiuO)X&GtMNPy>#Q zS;k}o43r%^RI$}adD}r$YFNDD_D2?D*kkSUT6NCer~XKtoU`9zjEg#Bzn-&S&)Ki% z>=%B{jt_*O^@cZ}*vjx?u^4A@EE8eAT(XJ~Hdr;@rJQ|{GDNnKNV!d$=Zn`ZV? z9U{df4u6h+w}<{~R@u7B0zvVV@2qJ)G_J0?hS7KF<4;fC%1X~>O{3TEci*F5WlVEf z?IE1)jgp#~$r#2Z=_DPGcP|YiyS|9e;Mn)P8;zw&ba4_MB_22WgKLvukQY?B8Lnsh z{odtexBLEmx7)q!l}he)cKIgM%cK^!R4Nwh^}5PO^JCl4 z-Uw1uD7w21MB64^RjSp>><(Sq>4i@4-00QOtl8?5jz#HIo>WO{0kKP-+buQFnOv=Q zEarBeRBM*f8>BsU0aChMV5d^EIyEFUS?xe=fTFe!Qg>>VN^P>eK;rRli3WsSt=dkp zQ4F6?rNW?uix2^)R&|P~p=+g)G{+uDw~keFg2`%SrSJu*d$p0-}Tt7QEM6=aWWlvt-+)@xCWQ f*a@E0{n;)t`Crn+^prkTWf$n~XXulK{=d#Y(n>05 literal 0 HcmV?d00001 diff --git a/code/win32/win_file.h b/code/win32/win_file.h new file mode 100644 index 0000000..d509f49 --- /dev/null +++ b/code/win32/win_file.h @@ -0,0 +1,33 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +#ifndef _WIN_FILE_ +#define _WIN_FILE_ + +typedef int wfhandle_t; + +extern void WF_Init(void); +extern void WF_Shutdown(void); +extern wfhandle_t WF_Open(const char* name, bool read, bool aligned); +extern void WF_Close(wfhandle_t handle); +extern int WF_Read(void* buffer, int len, wfhandle_t handle); +extern int WF_Write(const void* buffer, int len, wfhandle_t handle); +extern int WF_Seek(int offset, int origin, wfhandle_t handle); +extern int WF_Tell(wfhandle_t handle); +extern int WF_Resize(int size, wfhandle_t handle); + +int Sys_GetFileCode(const char *name); +void Sys_InitFileCodes(void); +void Sys_ShutdownFileCodes(void); + + +#endif _WIN_FILE_ diff --git a/code/win32/win_file_xbox.cpp b/code/win32/win_file_xbox.cpp new file mode 100644 index 0000000..98cd855 --- /dev/null +++ b/code/win32/win_file_xbox.cpp @@ -0,0 +1,171 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +#include "../game/q_shared.h" +#include "win_file.h" +#include "../qcommon/qcommon.h" + +#ifdef _XBOX +#include +#endif + +#ifdef _WINDOWS +#include +#endif + + +struct FileTable +{ + bool m_bUsed; + bool m_bErrorsFatal; + HANDLE m_Handle; +}; + +FileTable* s_FileTable = NULL; +const int WF_MAX_OPEN_FILES = 8; + +void WF_Init(void) +{ + assert(!s_FileTable); + + s_FileTable = new FileTable[WF_MAX_OPEN_FILES]; + + for (wfhandle_t i = 0; i < WF_MAX_OPEN_FILES; ++i) + { + s_FileTable[i].m_bUsed = false; + } +} + +void WF_Shutdown(void) +{ + assert(s_FileTable); + + for (wfhandle_t i = 0; i < WF_MAX_OPEN_FILES; ++i) + { + if (s_FileTable[i].m_bUsed) + { + WF_Close(i); + } + } + + delete [] s_FileTable; + s_FileTable = NULL; +} + +static wfhandle_t WF_GetFreeHandle(void) +{ + for (int i = 0; i < WF_MAX_OPEN_FILES; ++i) + { + if (!s_FileTable[i].m_bUsed) + { + return i; + } + } + + return -1; +} + +int WF_Open(const char* name, bool read, bool aligned) +{ + wfhandle_t handle = WF_GetFreeHandle(); + if (handle == -1) return -1; + + s_FileTable[handle].m_Handle = + CreateFile(name, read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, 0, + read ? OPEN_EXISTING : OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL |(aligned ? FILE_FLAG_NO_BUFFERING : 0) , 0); + + if (s_FileTable[handle].m_Handle != INVALID_HANDLE_VALUE) + { + s_FileTable[handle].m_bUsed = true; + + // errors are fatal on game partition + s_FileTable[handle].m_bErrorsFatal = (name[0] == 'D' || name[0] == 'd'); + + return handle; + } + + return -1; +} + +void WF_Close(wfhandle_t handle) +{ + assert(handle >= 0 && handle < WF_MAX_OPEN_FILES && + s_FileTable[handle].m_bUsed); + + CloseHandle(s_FileTable[handle].m_Handle); + s_FileTable[handle].m_bUsed = false; +} + +int WF_Read(void* buffer, int len, wfhandle_t handle) +{ + assert(handle >= 0 && handle < WF_MAX_OPEN_FILES && + s_FileTable[handle].m_bUsed); + + DWORD bytes; + if (!ReadFile(s_FileTable[handle].m_Handle, buffer, len, &bytes, 0) && + s_FileTable[handle].m_bErrorsFatal) + { +#if defined(FINAL_BUILD) + extern void ERR_DiscFail(bool); + ERR_DiscFail(false); +#else + assert(0); +#endif + } + + return bytes; +} + +int WF_Write(const void* buffer, int len, wfhandle_t handle) +{ + assert(handle >= 0 && handle < WF_MAX_OPEN_FILES && + s_FileTable[handle].m_bUsed); + + DWORD bytes; + WriteFile(s_FileTable[handle].m_Handle, buffer, len, &bytes, 0); + return bytes; +} + +int WF_Seek(int offset, int origin, wfhandle_t handle) +{ + assert(handle >= 0 && handle < WF_MAX_OPEN_FILES && + s_FileTable[handle].m_bUsed); + + switch (origin) + { + case SEEK_CUR: origin = FILE_CURRENT; break; + case SEEK_END: origin = FILE_END; break; + case SEEK_SET: origin = FILE_BEGIN; break; + default: assert(false); + } + + return SetFilePointer(s_FileTable[handle].m_Handle, offset, 0, origin) < 0; +} + +int WF_Tell(wfhandle_t handle) +{ + assert(handle >= 0 && handle < WF_MAX_OPEN_FILES && + s_FileTable[handle].m_bUsed); + + return SetFilePointer(s_FileTable[handle].m_Handle, 0, 0, FILE_CURRENT); +} + +int WF_Resize(int size, wfhandle_t handle) +{ + assert(handle >= 0 && handle < WF_MAX_OPEN_FILES && + s_FileTable[handle].m_bUsed); + + SetFilePointer(s_FileTable[handle].m_Handle, size, NULL, FILE_BEGIN); + return SetEndOfFile(s_FileTable[handle].m_Handle); +} diff --git a/code/win32/win_filecode.cpp b/code/win32/win_filecode.cpp new file mode 100644 index 0000000..0c370d6 --- /dev/null +++ b/code/win32/win_filecode.cpp @@ -0,0 +1,350 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +#include "../server/exe_headers.h" +#include "../client/client.h" +#include "../win32/win_local.h" +#include "../qcommon/qcommon.h" +#include "../qcommon/fixedmap.h" +#include "../zlib/zlib.h" +#include "../qcommon/files.h" + +/*********************************************** +* +* WINDOWS/XBOX VERSION +* +* Build a translation table, CRC -> file name. We have the memory. +* +************************************************/ + +#if defined(_WINDOWS) +#include +#elif defined(_XBOX) +#include +#endif + +struct FileInfo +{ + char* name; + int size; +}; +static VVFixedMap< FileInfo, unsigned int >* s_Files = NULL; +static byte* buffer; + +HANDLE s_Mutex = INVALID_HANDLE_VALUE; + +int _buildFileList(const char* path, bool insert, bool buildList) +{ + WIN32_FIND_DATA data; + char spec[MAX_OSPATH]; + int count = 0; + + // Look for all files + Com_sprintf(spec, sizeof(spec), "%s\\*.*", path); + + HANDLE h = FindFirstFile(spec, &data); + while (h != INVALID_HANDLE_VALUE) + { + char full[MAX_OSPATH]; + Com_sprintf(full, sizeof(full), "%s\\%s", path, data.cFileName); + + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + // Directory -- lets go recursive + if (data.cFileName[0] != '.') { + count += _buildFileList(full, insert, buildList); + } + } + else + { + + if(insert || buildList) + { + // Regular file -- add it to the table + strlwr(full); + unsigned int code = crc32(0, (const byte *)full, strlen(full)); + + FileInfo info; + info.name = CopyString(full); + info.size = data.nFileSizeLow; + + if(insert) + { + s_Files->Insert(info, code); + } + + if(buildList) + { + // get the length of the filename + int len; + len = strlen(info.name) + 1; + + // save the file code + *(int*)buffer = code; + buffer += sizeof(code); + + // save the name of the file + strcpy((char*)buffer,info.name); + buffer += len; + + // save the size of the file + *(int*)buffer = info.size; + buffer += sizeof(info.size); + } + } + + count++; + } + + // Continue the loop + if (!FindNextFile(h, &data)) + { + FindClose(h); + return count; + } + } + return count; +} + +bool _buildFileListFromSavedList(void) +{ + // open the file up for reading + FILE* in; + extern const char *Sys_RemapPath( const char *filename ); + in = fopen( Sys_RemapPath( "xbx_filelist" ), "rb" ); + if(!in) + { + return false; + } + + // read in the number of files + int count; + if(!(fread(&count,sizeof(count),1,in))) + { + fclose(in); + return false; + } + + // allocate memory for a temp buffer + byte* baseAddr; + int bufferSize; + bufferSize = count * ( 2 * sizeof(int) + MAX_OSPATH ); + buffer = (byte*)Z_Malloc(bufferSize,TAG_TEMP_WORKSPACE,qtrue,32); + baseAddr = buffer; + + // read the rest of the file into a big buffer + if(!(fread(buffer,bufferSize,1,in))) + { + fclose(in); + Z_Free(baseAddr); + return false; + } + + // allocate some memory for s_Files + s_Files = new VVFixedMap(count); + + // loop through all the files write out the codes + int i; + for(i = 0; i < count; i++) + { + FileInfo info; + unsigned int code; + + // read the code for the file + code = *(int*)buffer; + buffer += sizeof(code); + + // read the filename + info.name = CopyString((char*)buffer); + buffer += (strlen(info.name) + 1); + + // read the size of the file + info.size = *(int*)buffer; + buffer += sizeof(info.size); + + // save the data - optimization: don't check for dupes! + s_Files->InsertUnsafe(info, code); + } + + fclose(in); + Z_Free(baseAddr); + return true; +} + +bool Sys_SaveFileCodes(void) +{ + bool ret; + + // get the number of files + int count; + count = _buildFileList(Sys_Cwd(), false, false); + + // open a file for writing + FILE* out; + out = fopen("d:\\xbx_filelist","wb"); + if(!out) + { + return false; + } + + // allocate a buffer for writing + byte* baseAddr; + int bufferSize; + + bufferSize = sizeof(int) + ( count * ( 2 * sizeof(int) + MAX_OSPATH ) ); + baseAddr = (byte*)Z_Malloc(bufferSize,TAG_TEMP_WORKSPACE,qtrue,32); + buffer = baseAddr; + + // write the number of files to the buffer + *(int*)buffer = count; + buffer += sizeof(count); + + // fill up the rest of the buffer + ret = _buildFileList(Sys_Cwd(), false, true); + + if(!ret) + { + // there was a problem + fclose(out); + Z_Free(baseAddr); + return false; + } + + // attempt to write out the data + if(!(fwrite(baseAddr,bufferSize,1,out))) + { + // there was a problem + fclose(out); + Z_Free(baseAddr); + return false; + } + + // everything went ok + fclose(out); + Z_Free(baseAddr); + return true; +} + +void Sys_InitFileCodes(void) +{ + bool ret; + int count = 0; + + Z_PushNewDeleteTag( TAG_FILELIST ); + + // First: try to load an existing filecode cache + ret = _buildFileListFromSavedList(); + + // if we had trouble building the list that way + // we need to do it by searching the files + if( !ret ) + { + // There was no filelist cache, make one + if( !Sys_SaveFileCodes() ) + Com_Error( ERR_DROP, "ERROR: Couldn't create filecode cache\n" ); + + // Now re-read it + if( !_buildFileListFromSavedList() ) + Com_Error( ERR_DROP, "ERROR: Couldn't re-read filecode cache\n" ); + } + s_Files->Sort(); + + Z_PopNewDeleteTag(); + + // make it thread safe + s_Mutex = CreateMutex(NULL, FALSE, NULL); +} + +void Sys_ShutdownFileCodes(void) +{ + FileInfo* info = NULL; + + info = s_Files->Pop(); + while(info) + { + Z_Free(info->name); + info->name = NULL; + info = s_Files->Pop(); + } + + delete s_Files; + s_Files = NULL; + + CloseHandle(s_Mutex); +} + +int Sys_GetFileCode(const char* name) +{ + WaitForSingleObject(s_Mutex, INFINITE); + + // Get system level path + char* osname = FS_BuildOSPathUnMapped(name); + + // Generate hash for file name + strlwr(osname); + unsigned int code = crc32(0, (const byte *)osname, strlen(osname)); + + // Check if the file exists + if (!s_Files->Find(code)) + { + ReleaseMutex(s_Mutex); + return -1; + } + + ReleaseMutex(s_Mutex); + return code; +} + +const char* Sys_GetFileCodeName(int code) +{ + WaitForSingleObject(s_Mutex, INFINITE); + + FileInfo *entry = s_Files->Find(code); + if (entry) + { + ReleaseMutex(s_Mutex); + return entry->name; + } + + ReleaseMutex(s_Mutex); + return NULL; +} + +int Sys_GetFileCodeSize(int code) +{ + WaitForSingleObject(s_Mutex, INFINITE); + + FileInfo *entry = s_Files->Find(code); + if (entry) + { + ReleaseMutex(s_Mutex); + return entry->size; + } + + ReleaseMutex(s_Mutex); + return -1; +} + +// Quick function to re-scan for new files, update the filecode +// table, and dump the new one to disk +void Sys_FilecodeScan_f( void ) +{ + // Make an updated filecode cache + if( !Sys_SaveFileCodes() ) + Com_Error( ERR_DROP, "ERROR: Couldn't create filecode cache\n" ); + + // Throw out our current list + Sys_ShutdownFileCodes(); + + // Re-init, which should use the new list we just made + Sys_InitFileCodes(); +} diff --git a/code/win32/win_gamma.cpp b/code/win32/win_gamma.cpp new file mode 100644 index 0000000..d990d32 --- /dev/null +++ b/code/win32/win_gamma.cpp @@ -0,0 +1,141 @@ +/* +** WIN_GAMMA.C +*/ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include +#include "../renderer/tr_local.h" +#include "../qcommon/qcommon.h" +#include "glw_win.h" +#include "win_local.h" + +static unsigned short s_oldHardwareGamma[3][256]; + +/* +** WG_CheckHardwareGamma +** +** Determines if the underlying hardware supports the Win32 gamma correction API. +*/ +void WG_CheckHardwareGamma( void ) +{ + HDC hDC; + + glConfig.deviceSupportsGamma = qfalse; + + if ( !r_ignorehwgamma->integer ) + { + hDC = GetDC( GetDesktopWindow() ); + glConfig.deviceSupportsGamma = GetDeviceGammaRamp( hDC, s_oldHardwareGamma ); + ReleaseDC( GetDesktopWindow(), hDC ); + + if ( glConfig.deviceSupportsGamma ) + { + // + // do a sanity check on the gamma values + // + if ( ( HIBYTE( s_oldHardwareGamma[0][255] ) <= HIBYTE( s_oldHardwareGamma[0][0] ) ) || + ( HIBYTE( s_oldHardwareGamma[1][255] ) <= HIBYTE( s_oldHardwareGamma[1][0] ) ) || + ( HIBYTE( s_oldHardwareGamma[2][255] ) <= HIBYTE( s_oldHardwareGamma[2][0] ) ) ) + { + glConfig.deviceSupportsGamma = qfalse; + VID_Printf( PRINT_WARNING, "WARNING: device has broken gamma support, generated gamma.dat\n" ); + } + + // + // make sure that we didn't have a prior crash in the game, and if so we need to + // restore the gamma values to at least a linear value + // + if ( ( HIBYTE( s_oldHardwareGamma[0][181] ) == 255 ) ) + { + int g; + + VID_Printf( PRINT_WARNING, "WARNING: suspicious gamma tables, using linear ramp for restoration\n" ); + + for ( g = 0; g < 255; g++ ) + { + s_oldHardwareGamma[0][g] = g << 8; + s_oldHardwareGamma[1][g] = g << 8; + s_oldHardwareGamma[2][g] = g << 8; + } + } + } + } +} + +/* +** GLimp_SetGamma +** +** This routine should only be called if glConfig.deviceSupportsGamma is TRUE +*/ +void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] ) { + unsigned short table[3][256]; + int i, j; + int ret; + OSVERSIONINFO vinfo; + + if ( !glConfig.deviceSupportsGamma || r_ignorehwgamma->integer || !glw_state.hDC ) { + return; + } + +//mapGammaMax(); + + for ( i = 0; i < 256; i++ ) { + table[0][i] = ( ( ( unsigned short ) red[i] ) << 8 ) | red[i]; + table[1][i] = ( ( ( unsigned short ) green[i] ) << 8 ) | green[i]; + table[2][i] = ( ( ( unsigned short ) blue[i] ) << 8 ) | blue[i]; + } + + // Win2K puts this odd restriction on gamma ramps... + vinfo.dwOSVersionInfoSize = sizeof(vinfo); + GetVersionEx( &vinfo ); + if ( vinfo.dwMajorVersion == 5 && vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) { + Com_DPrintf( "performing W2K gamma clamp.\n" ); + for ( j = 0 ; j < 3 ; j++ ) { + for ( i = 0 ; i < 128 ; i++ ) { + if ( table[j][i] > ( (128+i) << 8 ) ) { + table[j][i] = (128+i) << 8; + } + } + if ( table[j][127] > 254<<8 ) { + table[j][127] = 254<<8; + } + } + } else { + Com_DPrintf( "skipping W2K gamma clamp.\n" ); + } + + // enforce constantly increasing + for ( j = 0 ; j < 3 ; j++ ) { + for ( i = 1 ; i < 256 ; i++ ) { + if ( table[j][i] < table[j][i-1] ) { + table[j][i] = table[j][i-1]; + } + } + } + + + ret = SetDeviceGammaRamp( glw_state.hDC, table ); + if ( !ret ) { + Com_Printf( "SetDeviceGammaRamp failed.\n" ); + } +} + +/* +** WG_RestoreGamma +*/ +void WG_RestoreGamma( void ) +{ + if ( glConfig.deviceSupportsGamma ) + { + HDC hDC; + + hDC = GetDC( GetDesktopWindow() ); + SetDeviceGammaRamp( hDC, s_oldHardwareGamma ); + ReleaseDC( GetDesktopWindow(), hDC ); + } +} + diff --git a/code/win32/win_gamma_console.cpp b/code/win32/win_gamma_console.cpp new file mode 100644 index 0000000..9926c45 --- /dev/null +++ b/code/win32/win_gamma_console.cpp @@ -0,0 +1,73 @@ +/* +** WIN_GAMMA.C +*/ +// leave this as first line for PCH reasons... +// +//#include "../server/exe_headers.h" + + + +#include +#include "../game/q_shared.h" +#include "../renderer/tr_local.h" +#include "../qcommon/qcommon.h" +#include "win_local.h" + +#if defined(_XBOX) +#include "glw_win_dx8.h" +#endif + + +/* +** WG_CheckHardwareGamma +** +** Determines if the underlying hardware supports the Win32 gamma correction API. +*/ +void WG_CheckHardwareGamma( void ) +{ + glConfig.deviceSupportsGamma = qtrue; +} + +/* +** GLimp_SetGamma +** +** This routine should only be called if glConfig.deviceSupportsGamma is TRUE +*/ +void GLimp_SetGamma( float g ) { +#if defined(_GAMECUBE) + GXGamma gamma = GX_GM_1_0; + if (g >= 2.2f) + { + gamma = GX_GM_2_2; + } + else if (g >= 1.7f) + { + gamma = GX_GM_1_7; + } + GXSetDispCopyGamma(gamma); +#elif defined(_XBOX) + const int maxval = 255; + + D3DGAMMARAMP ramp; + for ( int i = 0; i < 256; i++ ) + { + int inf; + if ( g == 1 ) { + inf = maxval * i / 255.0f; + } else { + inf = maxval * pow ( i/255.0f, 1.0f / g ) + 0.5f; + } + if (inf < 0) { + inf = 0; + } + if (inf > maxval) { + inf = maxval; + } + ramp.red[i] = inf; + ramp.green[i] = inf; + ramp.blue[i] = inf; + } + glw_state->device->SetGammaRamp(D3DSGR_CALIBRATE, &ramp); +#endif +} + diff --git a/code/win32/win_glimp.cpp b/code/win32/win_glimp.cpp new file mode 100644 index 0000000..08f1588 --- /dev/null +++ b/code/win32/win_glimp.cpp @@ -0,0 +1,1815 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +/* +** WIN_GLIMP.C +** +** This file contains ALL Win32 specific stuff having to do with the +** OpenGL refresh. When a port is being made the following functions +** must be implemented by the port: +** +** GLimp_EndFrame +** GLimp_Init +** GLimp_LogComment +** GLimp_Shutdown +** +** Note that the GLW_xxx functions are Windows specific GL-subsystem +** related functions that are relevant ONLY to win_glimp.c +*/ +#include +#include "../renderer/tr_local.h" +#include "../qcommon/qcommon.h" +#include "glw_win.h" +#include "win_local.h" +#include "resource.h" //JFM: to get icon + +extern void WG_CheckHardwareGamma( void ); +extern void WG_RestoreGamma( void ); + +typedef enum { + RSERR_OK, + + RSERR_INVALID_FULLSCREEN, + RSERR_INVALID_MODE, + + RSERR_UNKNOWN +} rserr_t; + +#define TRY_PFD_SUCCESS 0 +#define TRY_PFD_FAIL_SOFT 1 +#define TRY_PFD_FAIL_HARD 2 + +#define WINDOW_CLASS_NAME "Jedi Knight®: Jedi Academy" + +static void GLW_InitExtensions( void ); +static rserr_t GLW_SetMode( int mode, + int colorbits, + qboolean cdsFullscreen ); + +static qboolean s_classRegistered = qfalse; + +// +// function declaration +// +void QGL_EnableLogging( qboolean enable ); +qboolean QGL_Init( const char *dllname ); +void QGL_Shutdown( void ); + +// +// variable declarations +// +glwstate_t glw_state; + +cvar_t *r_allowSoftwareGL; // don't abort out if the pixelformat claims software +cvar_t *r_maskMinidriver; // allow a different dll name to be treated as if it were opengl32.dll + +// Whether the current hardware supports dynamic glows/flares. +extern bool g_bDynamicGlowSupported; + +// Hack variable for deciding which kind of texture rectangle thing to do (for some +// reason it acts different on radeon! It's against the spec!). +bool g_bTextureRectangleHack = false; + + +/* +** GLW_StartDriverAndSetMode +*/ +static qboolean GLW_StartDriverAndSetMode( int mode, + int colorbits, + qboolean cdsFullscreen ) +{ + rserr_t err; + + err = GLW_SetMode( mode, colorbits, cdsFullscreen ); + + switch ( err ) + { + case RSERR_INVALID_FULLSCREEN: + VID_Printf( PRINT_ALL, "...WARNING: fullscreen unavailable in this mode\n" ); + return qfalse; + case RSERR_INVALID_MODE: + VID_Printf( PRINT_ALL, "...WARNING: could not set the given mode (%d)\n", mode ); + return qfalse; + default: + break; + } + return qtrue; +} + +/* +** ChoosePFD +** +** Helper function that replaces ChoosePixelFormat. +*/ +#define MAX_PFDS 256 + +static int GLW_ChoosePFD( HDC hDC, PIXELFORMATDESCRIPTOR *pPFD ) +{ + PIXELFORMATDESCRIPTOR pfds[MAX_PFDS+1]; + int maxPFD = 0; + int i; + int bestMatch = 0; + + VID_Printf( PRINT_ALL, "...GLW_ChoosePFD( %d, %d, %d )\n", ( int ) pPFD->cColorBits, ( int ) pPFD->cDepthBits, ( int ) pPFD->cStencilBits ); + + // count number of PFDs + maxPFD = DescribePixelFormat( hDC, 1, sizeof( PIXELFORMATDESCRIPTOR ), &pfds[0] ); + if ( maxPFD > MAX_PFDS ) + { + VID_Printf( PRINT_WARNING, "...numPFDs > MAX_PFDS (%d > %d)\n", maxPFD, MAX_PFDS ); + maxPFD = MAX_PFDS; + } + + VID_Printf( PRINT_ALL, "...%d PFDs found\n", maxPFD - 1 ); + + // grab information + for ( i = 1; i <= maxPFD; i++ ) + { + DescribePixelFormat( hDC, i, sizeof( PIXELFORMATDESCRIPTOR ), &pfds[i] ); + } + + // look for a best match + for ( i = 1; i <= maxPFD; i++ ) + { + // + // make sure this has hardware acceleration + // + if ( ( pfds[i].dwFlags & PFD_GENERIC_FORMAT ) != 0 ) + { + if ( !r_allowSoftwareGL->integer ) + { + if ( r_verbose->integer ) + { + VID_Printf( PRINT_ALL, "...PFD %d rejected, software acceleration\n", i ); + } + continue; + } + } + + // verify pixel type + if ( pfds[i].iPixelType != PFD_TYPE_RGBA ) + { + if ( r_verbose->integer ) + { + VID_Printf( PRINT_ALL, "...PFD %d rejected, not RGBA\n", i ); + } + continue; + } + + // verify proper flags + if ( ( ( pfds[i].dwFlags & pPFD->dwFlags ) & pPFD->dwFlags ) != pPFD->dwFlags ) + { + if ( r_verbose->integer ) + { + VID_Printf( PRINT_ALL, "...PFD %d rejected, improper flags (%x instead of %x)\n", i, pfds[i].dwFlags, pPFD->dwFlags ); + } + continue; + } + + // verify enough bits + if ( pfds[i].cDepthBits < 15 ) + { + continue; + } + if ( ( pfds[i].cStencilBits < 4 ) && ( pPFD->cStencilBits > 0 ) ) + { + continue; + } + + // + // selection criteria (in order of priority): + // + // PFD_STEREO + // colorBits + // depthBits + // stencilBits + // + if ( bestMatch ) + { + // check stereo + if ( ( pfds[i].dwFlags & PFD_STEREO ) && ( !( pfds[bestMatch].dwFlags & PFD_STEREO ) ) && ( pPFD->dwFlags & PFD_STEREO ) ) + { + bestMatch = i; + continue; + } + + if ( !( pfds[i].dwFlags & PFD_STEREO ) && ( pfds[bestMatch].dwFlags & PFD_STEREO ) && ( pPFD->dwFlags & PFD_STEREO ) ) + { + bestMatch = i; + continue; + } + + // check color + if ( pfds[bestMatch].cColorBits != pPFD->cColorBits ) + { + // prefer perfect match + if ( pfds[i].cColorBits == pPFD->cColorBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( pfds[i].cColorBits > pfds[bestMatch].cColorBits ) + { + bestMatch = i; + continue; + } + } + + // check depth + if ( pfds[bestMatch].cDepthBits != pPFD->cDepthBits ) + { + // prefer perfect match + if ( pfds[i].cDepthBits == pPFD->cDepthBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( pfds[i].cDepthBits > pfds[bestMatch].cDepthBits ) + { + bestMatch = i; + continue; + } + } + + // check stencil + if ( pfds[bestMatch].cStencilBits != pPFD->cStencilBits ) + { + // prefer perfect match + if ( pfds[i].cStencilBits == pPFD->cStencilBits ) + { + bestMatch = i; + continue; + } + // otherwise if this PFD has more bits than our best, use it + else if ( ( pfds[i].cStencilBits > pfds[bestMatch].cStencilBits ) && + ( pPFD->cStencilBits > 0 ) ) + { + bestMatch = i; + continue; + } + } + } + else + { + bestMatch = i; + } + } + + if ( !bestMatch ) + return 0; + + if ( ( pfds[bestMatch].dwFlags & PFD_GENERIC_FORMAT ) != 0 ) + { + if ( !r_allowSoftwareGL->integer ) + { + VID_Printf( PRINT_ALL, "...no hardware acceleration found\n" ); + return 0; + } + else + { + VID_Printf( PRINT_ALL, "...using software emulation\n" ); + } + } + else if ( pfds[bestMatch].dwFlags & PFD_GENERIC_ACCELERATED ) + { + VID_Printf( PRINT_ALL, "...MCD acceleration found\n" ); + } + else + { + VID_Printf( PRINT_ALL, "...hardware acceleration found\n" ); + } + + *pPFD = pfds[bestMatch]; + + return bestMatch; +} + +/* +** void GLW_CreatePFD +** +** Helper function zeros out then fills in a PFD +*/ +static void GLW_CreatePFD( PIXELFORMATDESCRIPTOR *pPFD, int colorbits, int depthbits, int stencilbits, qboolean stereo ) +{ + PIXELFORMATDESCRIPTOR src = + { + sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd + 1, // version number + PFD_DRAW_TO_WINDOW | // support window + PFD_SUPPORT_OPENGL | // support OpenGL + PFD_DOUBLEBUFFER, // double buffered + PFD_TYPE_RGBA, // RGBA type + 24, // 24-bit color depth + 0, 0, 0, 0, 0, 0, // color bits ignored + 0, // no alpha buffer + 0, // shift bit ignored + 0, // no accumulation buffer + 0, 0, 0, 0, // accum bits ignored + 24, // 24-bit z-buffer + 8, // 8-bit stencil buffer + 0, // no auxiliary buffer + PFD_MAIN_PLANE, // main layer + 0, // reserved + 0, 0, 0 // layer masks ignored + }; + + src.cColorBits = colorbits; + src.cDepthBits = depthbits; + src.cStencilBits = stencilbits; + + if ( stereo ) + { + VID_Printf( PRINT_ALL, "...attempting to use stereo\n" ); + src.dwFlags |= PFD_STEREO; + glConfig.stereoEnabled = qtrue; + } + else + { + glConfig.stereoEnabled = qfalse; + } + + *pPFD = src; +} + +/* +** GLW_MakeContext +*/ +static int GLW_MakeContext( PIXELFORMATDESCRIPTOR *pPFD ) +{ + int pixelformat; + + // + // don't putz around with pixelformat if it's already set (e.g. this is a soft + // reset of the graphics system) + // + if ( !glw_state.pixelFormatSet ) + { + // + // choose, set, and describe our desired pixel format. If we're + // using a minidriver then we need to bypass the GDI functions, + // otherwise use the GDI functions. + // + if ( ( pixelformat = GLW_ChoosePFD( glw_state.hDC, pPFD ) ) == 0 ) + { + VID_Printf( PRINT_ALL, "...GLW_ChoosePFD failed\n"); + return TRY_PFD_FAIL_SOFT; + } + VID_Printf( PRINT_ALL, "...PIXELFORMAT %d selected\n", pixelformat ); + + DescribePixelFormat( glw_state.hDC, pixelformat, sizeof( *pPFD ), pPFD ); + + if ( SetPixelFormat( glw_state.hDC, pixelformat, pPFD ) == FALSE ) + { + VID_Printf (PRINT_ALL, "...SetPixelFormat failed\n", glw_state.hDC ); + return TRY_PFD_FAIL_SOFT; + } + + glw_state.pixelFormatSet = qtrue; + } + + // + // startup the OpenGL subsystem by creating a context and making it current + // + if ( !glw_state.hGLRC ) + { + VID_Printf( PRINT_ALL, "...creating GL context: " ); + if ( ( glw_state.hGLRC = qwglCreateContext( glw_state.hDC ) ) == 0 ) + { + VID_Printf (PRINT_ALL, "failed\n"); + + return TRY_PFD_FAIL_HARD; + } + VID_Printf( PRINT_ALL, "succeeded\n" ); + + VID_Printf( PRINT_ALL, "...making context current: " ); + if ( !qwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) ) + { + qwglDeleteContext( glw_state.hGLRC ); + glw_state.hGLRC = NULL; + VID_Printf (PRINT_ALL, "failed\n"); + return TRY_PFD_FAIL_HARD; + } + VID_Printf( PRINT_ALL, "succeeded\n" ); + } + + return TRY_PFD_SUCCESS; +} + + +/* +** GLW_InitDriver +** +** - get a DC if one doesn't exist +** - create an HGLRC if one doesn't exist +*/ +static qboolean GLW_InitDriver( int colorbits ) +{ + int tpfd; + int depthbits, stencilbits; + static PIXELFORMATDESCRIPTOR pfd; // save between frames since 'tr' gets cleared + + VID_Printf( PRINT_ALL, "Initializing OpenGL driver\n" ); + + // + // get a DC for our window if we don't already have one allocated + // + if ( glw_state.hDC == NULL ) + { + VID_Printf( PRINT_ALL, "...getting DC: " ); + + if ( ( glw_state.hDC = GetDC( g_wv.hWnd ) ) == NULL ) + { + VID_Printf( PRINT_ALL, "failed\n" ); + return qfalse; + } + VID_Printf( PRINT_ALL, "succeeded\n" ); + } + + if ( colorbits == 0 ) + { + colorbits = glw_state.desktopBitsPixel; + } + + // + // implicitly assume Z-buffer depth == desktop color depth + // + if ( r_depthbits->integer == 0 ) { + if ( colorbits > 16 ) { + depthbits = 24; + } else { + depthbits = 16; + } + } else { + depthbits = r_depthbits->integer; + } + + // + // do not allow stencil if Z-buffer depth likely won't contain it + // + stencilbits = r_stencilbits->integer; + if ( depthbits < 24 ) + { + stencilbits = 0; + } + + // + // make two attempts to set the PIXELFORMAT + // + + // + // first attempt: r_colorbits, depthbits, and r_stencilbits + // + if ( !glw_state.pixelFormatSet ) + { + GLW_CreatePFD( &pfd, colorbits, depthbits, stencilbits, r_stereo->integer ); + if ( ( tpfd = GLW_MakeContext( &pfd ) ) != TRY_PFD_SUCCESS ) + { + if ( tpfd == TRY_PFD_FAIL_HARD ) + { + VID_Printf( PRINT_WARNING, "...failed hard\n" ); + return qfalse; + } + + // + // punt if we've already tried the desktop bit depth and no stencil bits + // + if ( ( r_colorbits->integer == glw_state.desktopBitsPixel ) && + ( stencilbits == 0 ) ) + { + ReleaseDC( g_wv.hWnd, glw_state.hDC ); + glw_state.hDC = NULL; + + VID_Printf( PRINT_ALL, "...failed to find an appropriate PIXELFORMAT\n" ); + + return qfalse; + } + + // + // second attempt: desktop's color bits and no stencil + // + if ( colorbits > glw_state.desktopBitsPixel ) + { + colorbits = glw_state.desktopBitsPixel; + } + GLW_CreatePFD( &pfd, colorbits, depthbits, 0, r_stereo->integer ); + if ( GLW_MakeContext( &pfd ) != TRY_PFD_SUCCESS ) + { + if ( glw_state.hDC ) + { + ReleaseDC( g_wv.hWnd, glw_state.hDC ); + glw_state.hDC = NULL; + } + + VID_Printf( PRINT_ALL, "...failed to find an appropriate PIXELFORMAT\n" ); + + return qfalse; + } + } + + /* + ** report if stereo is desired but unavailable + */ + if ( !( pfd.dwFlags & PFD_STEREO ) && ( r_stereo->integer != 0 ) ) + { + VID_Printf( PRINT_ALL, "...failed to select stereo pixel format\n" ); + glConfig.stereoEnabled = qfalse; + } + } + + /* + ** store PFD specifics + */ + glConfig.colorBits = ( int ) pfd.cColorBits; + glConfig.depthBits = ( int ) pfd.cDepthBits; + glConfig.stencilBits = ( int ) pfd.cStencilBits; + + return qtrue; +} + +/* +** GLW_CreateWindow +** +** Responsible for creating the Win32 window and initializing the OpenGL driver. +*/ +#define WINDOW_STYLE (WS_OVERLAPPED|WS_BORDER|WS_CAPTION|WS_VISIBLE) +static qboolean GLW_CreateWindow( int width, int height, int colorbits, qboolean cdsFullscreen ) +{ + RECT r; + cvar_t *vid_xpos, *vid_ypos; + int stylebits; + int x, y, w, h; + int exstyle; + + // + // register the window class if necessary + // + if ( !s_classRegistered ) + { + WNDCLASS wc; + + memset( &wc, 0, sizeof( wc ) ); + + wc.style = 0; + wc.lpfnWndProc = (WNDPROC) glw_state.wndproc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = g_wv.hInstance; + wc.hIcon = LoadIcon (g_wv.hInstance, MAKEINTRESOURCE(IDI_ICON1)); //jfm: to get icon + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = 0;//(struct HBRUSH__ *)COLOR_GRAYTEXT; + wc.lpszMenuName = 0; + wc.lpszClassName = WINDOW_CLASS_NAME; + + if ( !RegisterClass( &wc ) ) + { + Com_Error( ERR_FATAL, "GLW_CreateWindow: could not register window class" ); + } + s_classRegistered = qtrue; + VID_Printf( PRINT_ALL, "...registered window class\n" ); + } + + // + // create the HWND if one does not already exist + // + if ( !g_wv.hWnd ) + { + // + // compute width and height + // + r.left = 0; + r.top = 0; + r.right = width; + r.bottom = height; + + if ( cdsFullscreen ) + { + exstyle = WS_EX_TOPMOST; + stylebits = WS_SYSMENU|WS_POPUP|WS_VISIBLE; //sysmenu gives you the icon + } + else + { + exstyle = 0; + stylebits = WS_SYSMENU|WINDOW_STYLE|WS_MINIMIZEBOX; + AdjustWindowRect (&r, stylebits, FALSE); + } + + w = r.right - r.left; + h = r.bottom - r.top; + + if ( cdsFullscreen ) + { + x = 0; + y = 0; + } + else + { + vid_xpos = Cvar_Get ("vid_xpos", "", 0); + vid_ypos = Cvar_Get ("vid_ypos", "", 0); + x = vid_xpos->integer; + y = vid_ypos->integer; + + // adjust window coordinates if necessary + // so that the window is completely on screen + if ( x < 0 ) + x = 0; + if ( y < 0 ) + y = 0; + + if ( w < glw_state.desktopWidth && + h < glw_state.desktopHeight ) + { + if ( x + w > glw_state.desktopWidth ) + x = ( glw_state.desktopWidth - w ); + if ( y + h > glw_state.desktopHeight ) + y = ( glw_state.desktopHeight - h ); + } + } + + g_wv.hWnd = CreateWindowEx ( + exstyle, + WINDOW_CLASS_NAME, //class + WINDOW_CLASS_NAME, //window title + stylebits, + x, y, w, h, + NULL, + NULL, + g_wv.hInstance, + NULL); + + if ( !g_wv.hWnd ) + { + Com_Error (ERR_FATAL, "GLW_CreateWindow() - Couldn't create window"); + } + + ShowWindow( g_wv.hWnd, SW_SHOW ); + UpdateWindow( g_wv.hWnd ); + VID_Printf( PRINT_ALL, "...created window@%d,%d (%dx%d)\n", x, y, w, h ); + } + else + { + VID_Printf( PRINT_ALL, "...window already present, CreateWindowEx skipped\n" ); + } + + if ( !GLW_InitDriver( colorbits ) ) + { + ShowWindow( g_wv.hWnd, SW_HIDE ); + DestroyWindow( g_wv.hWnd ); + g_wv.hWnd = NULL; + + return qfalse; + } + + SetForegroundWindow( g_wv.hWnd ); + SetFocus( g_wv.hWnd ); + + return qtrue; +} + +static void PrintCDSError( int value ) +{ + switch ( value ) + { + case DISP_CHANGE_RESTART: + VID_Printf( PRINT_ALL, "restart required\n" ); + break; + case DISP_CHANGE_BADPARAM: + VID_Printf( PRINT_ALL, "bad param\n" ); + break; + case DISP_CHANGE_BADFLAGS: + VID_Printf( PRINT_ALL, "bad flags\n" ); + break; + case DISP_CHANGE_FAILED: + VID_Printf( PRINT_ALL, "DISP_CHANGE_FAILED\n" ); + break; + case DISP_CHANGE_BADMODE: + VID_Printf( PRINT_ALL, "bad mode\n" ); + break; + case DISP_CHANGE_NOTUPDATED: + VID_Printf( PRINT_ALL, "not updated\n" ); + break; + default: + VID_Printf( PRINT_ALL, "unknown error %d\n", value ); + break; + } +} + +/* +** GLW_SetMode +*/ +static rserr_t GLW_SetMode( int mode, + int colorbits, + qboolean cdsFullscreen ) +{ + HDC hDC; + const char *win_fs[] = { "W", "FS" }; + int cdsRet; + DEVMODE dm; + + // + // print out informational messages + // + VID_Printf( PRINT_ALL, "...setting mode %d:", mode ); + if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, mode ) ) + { + VID_Printf( PRINT_ALL, " invalid mode\n" ); + return RSERR_INVALID_MODE; + } + VID_Printf( PRINT_ALL, " %d %d %s\n", glConfig.vidWidth, glConfig.vidHeight, win_fs[cdsFullscreen] ); + + // + // check our desktop attributes + // + hDC = GetDC( GetDesktopWindow() ); + glw_state.desktopBitsPixel = GetDeviceCaps( hDC, BITSPIXEL ); + glw_state.desktopWidth = GetDeviceCaps( hDC, HORZRES ); + glw_state.desktopHeight = GetDeviceCaps( hDC, VERTRES ); + ReleaseDC( GetDesktopWindow(), hDC ); + + // + // verify desktop bit depth + // + if ( glw_state.desktopBitsPixel < 15 || glw_state.desktopBitsPixel == 24 ) + { + if ( !cdsFullscreen && (colorbits == 0 || colorbits >= 15 ) ) + { + // since I can't be bothered trying to mess around with asian codepages and MBCS stuff for a windows + // error box that'll only appear if something's seriously fucked then I'm going to fallback to + // english text when these would otherwise be used... + // + char sErrorHead[1024]; // ott + + extern qboolean Language_IsAsian(void); + Q_strncpyz(sErrorHead, Language_IsAsian() ? "Low Desktop Color Depth" : SE_GetString("CON_TEXT_LOW_DESKTOP_COLOUR_DEPTH"), sizeof(sErrorHead) ); + + const char *psErrorBody = Language_IsAsian() ? + "It is highly unlikely that a correct windowed\n" + "display can be initialized with the current\n" + "desktop display depth. Select 'OK' to try\n" + "anyway. Select 'Cancel' to try a fullscreen\n" + "mode instead." + : + SE_GetString("CON_TEXT_TRY_ANYWAY"); + + if ( MessageBox( NULL, + psErrorBody, + sErrorHead, + MB_OKCANCEL | MB_ICONEXCLAMATION ) != IDOK ) + { + return RSERR_INVALID_MODE; + } + } + } + + // do a CDS if needed + if ( cdsFullscreen ) + { + memset( &dm, 0, sizeof( dm ) ); + + dm.dmSize = sizeof( dm ); + + dm.dmPelsWidth = glConfig.vidWidth; + dm.dmPelsHeight = glConfig.vidHeight; + dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT; + + if ( r_displayRefresh->integer != 0 ) + { + dm.dmDisplayFrequency = r_displayRefresh->integer; + dm.dmFields |= DM_DISPLAYFREQUENCY; + } + + // try to change color depth if possible + if ( colorbits != 0 ) + { + if ( glw_state.allowdisplaydepthchange ) + { + dm.dmBitsPerPel = colorbits; + dm.dmFields |= DM_BITSPERPEL; + VID_Printf( PRINT_ALL, "...using colorsbits of %d\n", colorbits ); + } + else + { + VID_Printf( PRINT_ALL, "WARNING:...changing depth not supported on Win95 < pre-OSR 2.x\n" ); + } + } + else + { + VID_Printf( PRINT_ALL, "...using desktop display depth of %d\n", glw_state.desktopBitsPixel ); + } + + // + // if we're already in fullscreen then just create the window + // + if ( glw_state.cdsFullscreen ) + { + VID_Printf( PRINT_ALL, "...already fullscreen, avoiding redundant CDS\n" ); + + if ( !GLW_CreateWindow ( glConfig.vidWidth, glConfig.vidHeight, colorbits, qtrue ) ) + { + VID_Printf( PRINT_ALL, "...restoring display settings\n" ); + ChangeDisplaySettings( 0, 0 ); + return RSERR_INVALID_MODE; + } + } + // + // need to call CDS + // + else + { + VID_Printf( PRINT_ALL, "...calling CDS: " ); + + // try setting the exact mode requested, because some drivers don't report + // the low res modes in EnumDisplaySettings, but still work + if ( ( cdsRet = ChangeDisplaySettings( &dm, CDS_FULLSCREEN ) ) == DISP_CHANGE_SUCCESSFUL ) + { + VID_Printf( PRINT_ALL, "ok\n" ); + + if ( !GLW_CreateWindow ( glConfig.vidWidth, glConfig.vidHeight, colorbits, qtrue) ) + { + VID_Printf( PRINT_ALL, "...restoring display settings\n" ); + ChangeDisplaySettings( 0, 0 ); + return RSERR_INVALID_MODE; + } + + glw_state.cdsFullscreen = qtrue; + } + else + { + // + // the exact mode failed, so scan EnumDisplaySettings for the next largest mode + // + DEVMODE devmode; + int modeNum; + + VID_Printf( PRINT_ALL, "failed, " ); + + PrintCDSError( cdsRet ); + + VID_Printf( PRINT_ALL, "...trying next higher resolution:" ); + + // we could do a better matching job here... + for ( modeNum = 0 ; ; modeNum++ ) { + if ( !EnumDisplaySettings( NULL, modeNum, &devmode ) ) { + modeNum = -1; + break; + } + if ( devmode.dmPelsWidth >= glConfig.vidWidth + && devmode.dmPelsHeight >= glConfig.vidHeight + && devmode.dmBitsPerPel >= 15 ) { + break; + } + } + + if ( modeNum != -1 && ( cdsRet = ChangeDisplaySettings( &devmode, CDS_FULLSCREEN ) ) == DISP_CHANGE_SUCCESSFUL ) + { + VID_Printf( PRINT_ALL, " ok\n" ); + if ( !GLW_CreateWindow( glConfig.vidWidth, glConfig.vidHeight, colorbits, qtrue) ) + { + VID_Printf( PRINT_ALL, "...restoring display settings\n" ); + ChangeDisplaySettings( 0, 0 ); + return RSERR_INVALID_MODE; + } + + glw_state.cdsFullscreen = qtrue; + } + else + { + VID_Printf( PRINT_ALL, " failed, " ); + + PrintCDSError( cdsRet ); + + VID_Printf( PRINT_ALL, "...restoring display settings\n" ); + ChangeDisplaySettings( 0, 0 ); + +/* jfm: i took out the following code to allow fallback to mode 3, with this code it goes half windowed and just doesn't work. + glw_state.cdsFullscreen = qfalse; + glConfig.isFullscreen = qfalse; + if ( !GLW_CreateWindow( glConfig.vidWidth, glConfig.vidHeight, colorbits, qfalse) ) + { + return RSERR_INVALID_MODE; + } +*/ + return RSERR_INVALID_FULLSCREEN; + } + } + } + } + else + { + if ( glw_state.cdsFullscreen ) + { + ChangeDisplaySettings( 0, 0 ); + } + + glw_state.cdsFullscreen = qfalse; + if ( !GLW_CreateWindow( glConfig.vidWidth, glConfig.vidHeight, colorbits, qfalse ) ) + { + return RSERR_INVALID_MODE; + } + } + + // + // success, now check display frequency, although this won't be valid on Voodoo(2) + // + memset( &dm, 0, sizeof( dm ) ); + dm.dmSize = sizeof( dm ); + if ( EnumDisplaySettings( NULL, ENUM_CURRENT_SETTINGS, &dm ) ) + { + glConfig.displayFrequency = dm.dmDisplayFrequency; + } + + // NOTE: this is overridden later on standalone 3Dfx drivers + glConfig.isFullscreen = cdsFullscreen; + + return RSERR_OK; +} + +//-------------------------------------------- +static void GLW_InitTextureCompression( void ) +{ + qboolean newer_tc, old_tc; + + // Check for available tc methods. + newer_tc = ( strstr( glConfig.extensions_string, "ARB_texture_compression" ) + && strstr( glConfig.extensions_string, "EXT_texture_compression_s3tc" )) ? qtrue : qfalse; + old_tc = ( strstr( glConfig.extensions_string, "GL_S3_s3tc" )) ? qtrue : qfalse; + + if ( old_tc ) + { + VID_Printf( PRINT_ALL, "...GL_S3_s3tc available\n" ); + } + + if ( newer_tc ) + { + VID_Printf( PRINT_ALL, "...GL_EXT_texture_compression_s3tc available\n" ); + } + + if ( !r_ext_compressed_textures->value ) + { + // Compressed textures are off + glConfig.textureCompression = TC_NONE; + VID_Printf( PRINT_ALL, "...ignoring texture compression\n" ); + } + else if ( !old_tc && !newer_tc ) + { + // Requesting texture compression, but no method found + glConfig.textureCompression = TC_NONE; + VID_Printf( PRINT_ALL, "...no supported texture compression method found\n" ); + VID_Printf( PRINT_ALL, ".....ignoring texture compression\n" ); + } + else + { + // some form of supported texture compression is avaiable, so see if the user has a preference + if ( r_ext_preferred_tc_method->integer == TC_NONE ) + { + // No preference, so pick the best + if ( newer_tc ) + { + VID_Printf( PRINT_ALL, "...no tc preference specified\n" ); + VID_Printf( PRINT_ALL, ".....using GL_EXT_texture_compression_s3tc\n" ); + glConfig.textureCompression = TC_S3TC_DXT; + } + else + { + VID_Printf( PRINT_ALL, "...no tc preference specified\n" ); + VID_Printf( PRINT_ALL, ".....using GL_S3_s3tc\n" ); + glConfig.textureCompression = TC_S3TC; + } + } + else + { + // User has specified a preference, now see if this request can be honored + if ( old_tc && newer_tc ) + { + // both are avaiable, so we can use the desired tc method + if ( r_ext_preferred_tc_method->integer == TC_S3TC ) + { + VID_Printf( PRINT_ALL, "...using preferred tc method, GL_S3_s3tc\n" ); + glConfig.textureCompression = TC_S3TC; + } + else + { + VID_Printf( PRINT_ALL, "...using preferred tc method, GL_EXT_texture_compression_s3tc\n" ); + glConfig.textureCompression = TC_S3TC_DXT; + } + } + else + { + // Both methods are not available, so this gets trickier + if ( r_ext_preferred_tc_method->integer == TC_S3TC ) + { + // Preferring to user older compression + if ( old_tc ) + { + VID_Printf( PRINT_ALL, "...using GL_S3_s3tc\n" ); + glConfig.textureCompression = TC_S3TC; + } + else + { + // Drat, preference can't be honored + VID_Printf( PRINT_ALL, "...preferred tc method, GL_S3_s3tc not available\n" ); + VID_Printf( PRINT_ALL, ".....falling back to GL_EXT_texture_compression_s3tc\n" ); + glConfig.textureCompression = TC_S3TC_DXT; + } + } + else + { + // Preferring to user newer compression + if ( newer_tc ) + { + VID_Printf( PRINT_ALL, "...using GL_EXT_texture_compression_s3tc\n" ); + glConfig.textureCompression = TC_S3TC_DXT; + } + else + { + // Drat, preference can't be honored + VID_Printf( PRINT_ALL, "...preferred tc method, GL_EXT_texture_compression_s3tc not available\n" ); + VID_Printf( PRINT_ALL, ".....falling back to GL_S3_s3tc\n" ); + glConfig.textureCompression = TC_S3TC; + } + } + } + } + } +} + +/* +** GLW_InitExtensions +*/ +static void GLW_InitExtensions( void ) +{ + if ( !r_allowExtensions->integer ) + { + VID_Printf( PRINT_ALL, "*** IGNORING OPENGL EXTENSIONS ***\n" ); + g_bDynamicGlowSupported = false; + Cvar_Set( "r_DynamicGlow","0" ); + return; + } + + VID_Printf( PRINT_ALL, "Initializing OpenGL extensions\n" ); + + // Select our tc scheme + GLW_InitTextureCompression(); + + // GL_EXT_texture_env_add + glConfig.textureEnvAddAvailable = qfalse; + if ( strstr( glConfig.extensions_string, "EXT_texture_env_add" ) ) + { + if ( r_ext_texture_env_add->integer ) + { + glConfig.textureEnvAddAvailable = qtrue; + VID_Printf( PRINT_ALL, "...using GL_EXT_texture_env_add\n" ); + } + else + { + glConfig.textureEnvAddAvailable = qfalse; + VID_Printf( PRINT_ALL, "...ignoring GL_EXT_texture_env_add\n" ); + } + } + else + { + VID_Printf( PRINT_ALL, "...GL_EXT_texture_env_add not found\n" ); + } + + // GL_EXT_texture_filter_anisotropic + glConfig.maxTextureFilterAnisotropy = 0; + if ( strstr( glConfig.extensions_string, "EXT_texture_filter_anisotropic" ) ) + { +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF //can't include glext.h here ... sigh + qglGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glConfig.maxTextureFilterAnisotropy ); + Com_Printf ("...GL_EXT_texture_filter_anisotropic available\n" ); + + if ( r_ext_texture_filter_anisotropic->integer>1 ) + { + Com_Printf ("...using GL_EXT_texture_filter_anisotropic\n" ); + } + else + { + Com_Printf ("...ignoring GL_EXT_texture_filter_anisotropic\n" ); + } + Cvar_Set( "r_ext_texture_filter_anisotropic_avail", va("%f",glConfig.maxTextureFilterAnisotropy) ); + if ( r_ext_texture_filter_anisotropic->value > glConfig.maxTextureFilterAnisotropy ) + { + Cvar_Set( "r_ext_texture_filter_anisotropic", va("%f",glConfig.maxTextureFilterAnisotropy) ); + } + } + else + { + Com_Printf ("...GL_EXT_texture_filter_anisotropic not found\n" ); + Cvar_Set( "r_ext_texture_filter_anisotropic_avail", "0" ); + } + + // GL_EXT_clamp_to_edge + glConfig.clampToEdgeAvailable = qfalse; + if ( strstr( glConfig.extensions_string, "GL_EXT_texture_edge_clamp" ) ) + { + glConfig.clampToEdgeAvailable = qtrue; + VID_Printf( PRINT_ALL, "...Using GL_EXT_texture_edge_clamp\n" ); + } + + // WGL_EXT_swap_control + qwglSwapIntervalEXT = ( BOOL (WINAPI *)(int)) qwglGetProcAddress( "wglSwapIntervalEXT" ); + if ( qwglSwapIntervalEXT ) + { + VID_Printf( PRINT_ALL, "...using WGL_EXT_swap_control\n" ); + r_swapInterval->modified = qtrue; // force a set next frame + } + else + { + VID_Printf( PRINT_ALL, "...WGL_EXT_swap_control not found\n" ); + } + + // GL_ARB_multitexture + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + if ( strstr( glConfig.extensions_string, "GL_ARB_multitexture" ) ) + { + if ( r_ext_multitexture->integer ) + { + qglMultiTexCoord2fARB = ( PFNGLMULTITEXCOORD2FARBPROC ) qwglGetProcAddress( "glMultiTexCoord2fARB" ); + qglActiveTextureARB = ( PFNGLACTIVETEXTUREARBPROC ) qwglGetProcAddress( "glActiveTextureARB" ); + qglClientActiveTextureARB = ( PFNGLCLIENTACTIVETEXTUREARBPROC ) qwglGetProcAddress( "glClientActiveTextureARB" ); + + if ( qglActiveTextureARB ) + { + qglGetIntegerv( GL_MAX_ACTIVE_TEXTURES_ARB, &glConfig.maxActiveTextures ); + + if ( glConfig.maxActiveTextures > 1 ) + { + VID_Printf( PRINT_ALL, "...using GL_ARB_multitexture\n" ); + } + else + { + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + VID_Printf( PRINT_ALL, "...not using GL_ARB_multitexture, < 2 texture units\n" ); + } + } + } + else + { + VID_Printf( PRINT_ALL, "...ignoring GL_ARB_multitexture\n" ); + } + } + else + { + VID_Printf( PRINT_ALL, "...GL_ARB_multitexture not found\n" ); + } + + // GL_EXT_compiled_vertex_array + qglLockArraysEXT = NULL; + qglUnlockArraysEXT = NULL; + if ( strstr( glConfig.extensions_string, "GL_EXT_compiled_vertex_array" ) ) + { + if ( r_ext_compiled_vertex_array->integer ) + { + VID_Printf( PRINT_ALL, "...using GL_EXT_compiled_vertex_array\n" ); + qglLockArraysEXT = ( void ( APIENTRY * )( int, int ) ) qwglGetProcAddress( "glLockArraysEXT" ); + qglUnlockArraysEXT = ( void ( APIENTRY * )( void ) ) qwglGetProcAddress( "glUnlockArraysEXT" ); + if (!qglLockArraysEXT || !qglUnlockArraysEXT) { + Com_Error (ERR_FATAL, "bad getprocaddress"); + } + } + else + { + VID_Printf( PRINT_ALL, "...ignoring GL_EXT_compiled_vertex_array\n" ); + } + } + else + { + VID_Printf( PRINT_ALL, "...GL_EXT_compiled_vertex_array not found\n" ); + } + + // GL_EXT_point_parameters + qglPointParameterfEXT = NULL; + qglPointParameterfvEXT = NULL; + if ( strstr( glConfig.extensions_string, "GL_EXT_point_parameters" ) ) + { + if ( r_ext_point_parameters->integer ) + { + qglPointParameterfEXT = ( void ( APIENTRY * )( GLenum, GLfloat) ) qwglGetProcAddress( "glPointParameterfEXT" ); + qglPointParameterfvEXT = ( void ( APIENTRY * )( GLenum, GLfloat *) ) qwglGetProcAddress( "glPointParameterfvEXT" ); + if (!qglPointParameterfEXT || !qglPointParameterfvEXT) + { + VID_Printf( ERR_FATAL, "Bad GetProcAddress for GL_EXT_point_parameters"); + } + VID_Printf( PRINT_ALL, "...using GL_EXT_point_parameters\n" ); + } + else + { + VID_Printf( PRINT_ALL, "...ignoring GL_EXT_point_parameters\n" ); + } + } + else + { + VID_Printf( PRINT_ALL, "...GL_EXT_point_parameters not found\n" ); + } + + // GL_NV_point_sprite + qglPointParameteriNV = NULL; + qglPointParameterivNV = NULL; + if ( strstr( glConfig.extensions_string, "GL_NV_point_sprite" ) ) + { + if ( r_ext_nv_point_sprite->integer ) + { + qglPointParameteriNV = ( void ( APIENTRY * )( GLenum, GLint) ) qwglGetProcAddress( "glPointParameteriNV" ); + qglPointParameterivNV = ( void ( APIENTRY * )( GLenum, const GLint *) ) qwglGetProcAddress( "glPointParameterivNV" ); + if (!qglPointParameteriNV || !qglPointParameterivNV) + { + VID_Printf( ERR_FATAL, "Bad GetProcAddress for GL_NV_point_sprite"); + } + VID_Printf( PRINT_ALL, "...using GL_NV_point_sprite\n" ); + } + else + { + VID_Printf( PRINT_ALL, "...ignoring GL_NV_point_sprite\n" ); + } + } + else + { + VID_Printf( PRINT_ALL, "...GL_NV_point_sprite not found\n" ); + } + + bool bNVRegisterCombiners = false; + // Register Combiners. + if ( strstr( glConfig.extensions_string, "GL_NV_register_combiners" ) ) + { + // NOTE: This extension requires multitexture support (over 2 units). + if ( glConfig.maxActiveTextures >= 2 ) + { + bNVRegisterCombiners = true; + // Register Combiners function pointer address load. - AReis + // NOTE: VV guys will _definetly_ not be able to use regcoms. Pixel Shaders are just as good though :-) + // NOTE: Also, this is an nVidia specific extension (of course), so fragment shaders would serve the same purpose + // if we needed some kind of fragment/pixel manipulation support. + qglCombinerParameterfvNV = ( PFNGLCOMBINERPARAMETERFVNV ) qwglGetProcAddress( "glCombinerParameterfvNV" ); + qglCombinerParameterivNV = ( PFNGLCOMBINERPARAMETERIVNV ) qwglGetProcAddress( "glCombinerParameterivNV" ); + qglCombinerParameterfNV = ( PFNGLCOMBINERPARAMETERFNV ) qwglGetProcAddress( "glCombinerParameterfNV" ); + qglCombinerParameteriNV = ( PFNGLCOMBINERPARAMETERINV ) qwglGetProcAddress( "glCombinerParameteriNV" ); + qglCombinerInputNV = ( PFNGLCOMBINERINPUTNV ) qwglGetProcAddress( "glCombinerInputNV" ); + qglCombinerOutputNV = ( PFNGLCOMBINEROUTPUTNV ) qwglGetProcAddress( "glCombinerOutputNV" ); + qglFinalCombinerInputNV = ( PFNGLFINALCOMBINERINPUTNV ) qwglGetProcAddress( "glFinalCombinerInputNV" ); + qglGetCombinerInputParameterfvNV = ( PFNGLGETCOMBINERINPUTPARAMETERFVNV ) qwglGetProcAddress( "glGetCombinerInputParameterfvNV" ); + qglGetCombinerInputParameterivNV = ( PFNGLGETCOMBINERINPUTPARAMETERIVNV ) qwglGetProcAddress( "glGetCombinerInputParameterivNV" ); + qglGetCombinerOutputParameterfvNV = ( PFNGLGETCOMBINEROUTPUTPARAMETERFVNV ) qwglGetProcAddress( "glGetCombinerOutputParameterfvNV" ); + qglGetCombinerOutputParameterivNV = ( PFNGLGETCOMBINEROUTPUTPARAMETERIVNV ) qwglGetProcAddress( "glGetCombinerOutputParameterivNV" ); + qglGetFinalCombinerInputParameterfvNV = ( PFNGLGETFINALCOMBINERINPUTPARAMETERFVNV ) qwglGetProcAddress( "glGetFinalCombinerInputParameterfvNV" ); + qglGetFinalCombinerInputParameterivNV = ( PFNGLGETFINALCOMBINERINPUTPARAMETERIVNV ) qwglGetProcAddress( "glGetFinalCombinerInputParameterivNV" ); + + // Validate the functions we need. + if ( !qglCombinerParameterfvNV || !qglCombinerParameterivNV || !qglCombinerParameterfNV || !qglCombinerParameteriNV || !qglCombinerInputNV || + !qglCombinerOutputNV || !qglFinalCombinerInputNV || !qglGetCombinerInputParameterfvNV || !qglGetCombinerInputParameterivNV || + !qglGetCombinerOutputParameterfvNV || !qglGetCombinerOutputParameterivNV || !qglGetFinalCombinerInputParameterfvNV || !qglGetFinalCombinerInputParameterivNV ) + { + bNVRegisterCombiners = false; + qglCombinerParameterfvNV = NULL; + qglCombinerParameteriNV = NULL; + Com_Printf ("...GL_NV_register_combiners failed\n" ); + } + } + else + { + bNVRegisterCombiners = false; + Com_Printf ("...ignoring GL_NV_register_combiners\n" ); + } + } + else + { + bNVRegisterCombiners = false; + Com_Printf ("...GL_NV_register_combiners not found\n" ); + } + + // NOTE: Vertex and Fragment Programs are very dependant on each other - this is actually a + // good thing! So, just check to see which we support (one or the other) and load the shared + // function pointers. ARB rocks! + + // Vertex Programs. + bool bARBVertexProgram = false; + if ( strstr( glConfig.extensions_string, "GL_ARB_vertex_program" ) ) + { + bARBVertexProgram = true; + } + else + { + bARBVertexProgram = false; + Com_Printf ("...GL_ARB_vertex_program not found\n" ); + } + + bool bARBFragmentProgram = false; + // Fragment Programs. + if ( strstr( glConfig.extensions_string, "GL_ARB_fragment_program" ) ) + { + bARBFragmentProgram = true; + } + else + { + bARBFragmentProgram = false; + Com_Printf ("...GL_ARB_fragment_program not found\n" ); + } + + // If we support one or the other, load the shared function pointers. + if ( bARBVertexProgram || bARBFragmentProgram ) + { + qglProgramStringARB = (PFNGLPROGRAMSTRINGARBPROC) qwglGetProcAddress("glProgramStringARB"); + qglBindProgramARB = (PFNGLBINDPROGRAMARBPROC) qwglGetProcAddress("glBindProgramARB"); + qglDeleteProgramsARB = (PFNGLDELETEPROGRAMSARBPROC) qwglGetProcAddress("glDeleteProgramsARB"); + qglGenProgramsARB = (PFNGLGENPROGRAMSARBPROC) qwglGetProcAddress("glGenProgramsARB"); + qglProgramEnvParameter4dARB = (PFNGLPROGRAMENVPARAMETER4DARBPROC) qwglGetProcAddress("glProgramEnvParameter4dARB"); + qglProgramEnvParameter4dvARB = (PFNGLPROGRAMENVPARAMETER4DVARBPROC) qwglGetProcAddress("glProgramEnvParameter4dvARB"); + qglProgramEnvParameter4fARB = (PFNGLPROGRAMENVPARAMETER4FARBPROC) qwglGetProcAddress("glProgramEnvParameter4fARB"); + qglProgramEnvParameter4fvARB = (PFNGLPROGRAMENVPARAMETER4FVARBPROC) qwglGetProcAddress("glProgramEnvParameter4fvARB"); + qglProgramLocalParameter4dARB = (PFNGLPROGRAMLOCALPARAMETER4DARBPROC) qwglGetProcAddress("glProgramLocalParameter4dARB"); + qglProgramLocalParameter4dvARB = (PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) qwglGetProcAddress("glProgramLocalParameter4dvARB"); + qglProgramLocalParameter4fARB = (PFNGLPROGRAMLOCALPARAMETER4FARBPROC) qwglGetProcAddress("glProgramLocalParameter4fARB"); + qglProgramLocalParameter4fvARB = (PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) qwglGetProcAddress("glProgramLocalParameter4fvARB"); + qglGetProgramEnvParameterdvARB = (PFNGLGETPROGRAMENVPARAMETERDVARBPROC) qwglGetProcAddress("glGetProgramEnvParameterdvARB"); + qglGetProgramEnvParameterfvARB = (PFNGLGETPROGRAMENVPARAMETERFVARBPROC) qwglGetProcAddress("glGetProgramEnvParameterfvARB"); + qglGetProgramLocalParameterdvARB = (PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) qwglGetProcAddress("glGetProgramLocalParameterdvARB"); + qglGetProgramLocalParameterfvARB = (PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) qwglGetProcAddress("glGetProgramLocalParameterfvARB"); + qglGetProgramivARB = (PFNGLGETPROGRAMIVARBPROC) qwglGetProcAddress("glGetProgramivARB"); + qglGetProgramStringARB = (PFNGLGETPROGRAMSTRINGARBPROC) qwglGetProcAddress("glGetProgramStringARB"); + qglIsProgramARB = (PFNGLISPROGRAMARBPROC) qwglGetProcAddress("glIsProgramARB"); + + // Validate the functions we need. + if ( !qglProgramStringARB || !qglBindProgramARB || !qglDeleteProgramsARB || !qglGenProgramsARB || + !qglProgramEnvParameter4dARB || !qglProgramEnvParameter4dvARB || !qglProgramEnvParameter4fARB || + !qglProgramEnvParameter4fvARB || !qglProgramLocalParameter4dARB || !qglProgramLocalParameter4dvARB || + !qglProgramLocalParameter4fARB || !qglProgramLocalParameter4fvARB || !qglGetProgramEnvParameterdvARB || + !qglGetProgramEnvParameterfvARB || !qglGetProgramLocalParameterdvARB || !qglGetProgramLocalParameterfvARB || + !qglGetProgramivARB || !qglGetProgramStringARB || !qglIsProgramARB ) + { + bARBVertexProgram = false; + bARBFragmentProgram = false; + qglGenProgramsARB = NULL; //clear ptrs that get checked + qglProgramEnvParameter4fARB = NULL; + Com_Printf ("...ignoring GL_ARB_vertex_program\n" ); + Com_Printf ("...ignoring GL_ARB_fragment_program\n" ); + } + } + + // Figure out which texture rectangle extension to use. + // TOTAL HACK!!! This will need to be fixed. + // FIXMEFIXMEFIXME! + bool bTexRectSupported = false; + if ( strstr( glConfig.extensions_string, "GL_EXT_texture_rectangle" ) ) + { + g_bTextureRectangleHack = true; + bTexRectSupported = true; + } + else if ( strstr( glConfig.extensions_string, "GL_NV_texture_rectangle" ) ) + { + g_bTextureRectangleHack = false; + bTexRectSupported = true; + } + + // OK, so not so good to put this here, but no one else uses it!!! -AReis + typedef const char * (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC) (HDC hdc); + PFNWGLGETEXTENSIONSSTRINGARBPROC qwglGetExtensionsStringARB; + qwglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC) qwglGetProcAddress("wglGetExtensionsStringARB"); + + const char *wglExtensions = NULL; + bool bHasPixelFormat = false; + bool bHasRenderTexture = false; + + // Get the WGL extensions string. + if ( qwglGetExtensionsStringARB ) + { + wglExtensions = qwglGetExtensionsStringARB( glw_state.hDC ); + } + + // This externsion is used to get the wgl extension string. + if ( wglExtensions ) + { + // Pixel Format. + if ( strstr( wglExtensions, "WGL_ARB_pixel_format" ) ) + { + qwglGetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC) qwglGetProcAddress("wglGetPixelFormatAttribivARB"); + qwglGetPixelFormatAttribfvARB = (PFNWGLGETPIXELFORMATATTRIBFVARBPROC) qwglGetProcAddress("wglGetPixelFormatAttribfvARB"); + qwglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC) qwglGetProcAddress("wglChoosePixelFormatARB"); + + // Validate the functions we need. + if ( !qwglGetPixelFormatAttribivARB || !qwglGetPixelFormatAttribfvARB || !qwglChoosePixelFormatARB ) + { + Com_Printf ("...ignoring WGL_ARB_pixel_format\n" ); + } + else + { + bHasPixelFormat = true; + } + } + else + { + Com_Printf ("...ignoring WGL_ARB_pixel_format\n" ); + } + + // Offscreen pixel-buffer. + // NOTE: VV guys can use the equivelant SetRenderTarget() with the correct texture surfaces. + bool bWGLARBPbuffer = false; + if ( strstr( wglExtensions, "WGL_ARB_pbuffer" ) && bHasPixelFormat ) + { + bWGLARBPbuffer = true; + qwglCreatePbufferARB = (PFNWGLCREATEPBUFFERARBPROC) qwglGetProcAddress("wglCreatePbufferARB"); + qwglGetPbufferDCARB = (PFNWGLGETPBUFFERDCARBPROC) qwglGetProcAddress("wglGetPbufferDCARB"); + qwglReleasePbufferDCARB = (PFNWGLRELEASEPBUFFERDCARBPROC) qwglGetProcAddress("wglReleasePbufferDCARB"); + qwglDestroyPbufferARB = (PFNWGLDESTROYPBUFFERARBPROC) qwglGetProcAddress("wglDestroyPbufferARB"); + qwglQueryPbufferARB = (PFNWGLQUERYPBUFFERARBPROC) qwglGetProcAddress("wglQueryPbufferARB"); + + // Validate the functions we need. + if ( !qwglCreatePbufferARB || !qwglGetPbufferDCARB || !qwglReleasePbufferDCARB || !qwglDestroyPbufferARB || !qwglQueryPbufferARB ) + { + bWGLARBPbuffer = false; + Com_Printf ("...WGL_ARB_pbuffer failed\n" ); + } + } + else + { + bWGLARBPbuffer = false; + Com_Printf ("...WGL_ARB_pbuffer not found\n" ); + } + + // Render-Texture (requires pbuffer ext (and it's dependancies of course). + if ( strstr( wglExtensions, "WGL_ARB_render_texture" ) && bWGLARBPbuffer ) + { + qwglBindTexImageARB = (PFNWGLBINDTEXIMAGEARBPROC) qwglGetProcAddress("wglBindTexImageARB"); + qwglReleaseTexImageARB = (PFNWGLRELEASETEXIMAGEARBPROC) qwglGetProcAddress("wglReleaseTexImageARB"); + qwglSetPbufferAttribARB = (PFNWGLSETPBUFFERATTRIBARBPROC) qwglGetProcAddress("wglSetPbufferAttribARB"); + + // Validate the functions we need. + if ( !qwglCreatePbufferARB || !qwglGetPbufferDCARB || !qwglReleasePbufferDCARB || !qwglDestroyPbufferARB || !qwglQueryPbufferARB ) + { + Com_Printf ("...ignoring WGL_ARB_render_texture\n" ); + } + else + { + bHasRenderTexture = true; + } + } + else + { + Com_Printf ("...ignoring WGL_ARB_render_texture\n" ); + } + } + + // Find out how many general combiners they have. + #define GL_MAX_GENERAL_COMBINERS_NV 0x854D + GLint iNumGeneralCombiners = 0; + qglGetIntegerv( GL_MAX_GENERAL_COMBINERS_NV, &iNumGeneralCombiners ); + + // Only allow dynamic glows/flares if they have the hardware + if ( bTexRectSupported && bARBVertexProgram && bHasRenderTexture && qglActiveTextureARB && glConfig.maxActiveTextures >= 4 && + ( ( bNVRegisterCombiners && iNumGeneralCombiners >= 2 ) || bARBFragmentProgram ) ) + { + g_bDynamicGlowSupported = true; + // this would overwrite any achived setting gwg + // Cvar_Set( "r_DynamicGlow", "1" ); + } + else + { + g_bDynamicGlowSupported = false; + Cvar_Set( "r_DynamicGlow","0" ); + } +} + +/* +** GLW_CheckOSVersion +*/ +static qboolean GLW_CheckOSVersion( void ) +{ +#define OSR2_BUILD_NUMBER 1111 + + OSVERSIONINFO vinfo; + + vinfo.dwOSVersionInfoSize = sizeof(vinfo); + + glw_state.allowdisplaydepthchange = qfalse; + + if ( GetVersionEx( &vinfo) ) + { + if ( vinfo.dwMajorVersion > 4 ) + { + glw_state.allowdisplaydepthchange = qtrue; + } + else if ( vinfo.dwMajorVersion == 4 ) + { + if ( vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + glw_state.allowdisplaydepthchange = qtrue; + } + else if ( vinfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) + { + if ( LOWORD( vinfo.dwBuildNumber ) >= OSR2_BUILD_NUMBER ) + { + glw_state.allowdisplaydepthchange = qtrue; + } + } + } + } + else + { + VID_Printf( PRINT_ALL, "GLW_CheckOSVersion() - GetVersionEx failed\n" ); + return qfalse; + } + + return qtrue; +} + +/* +** GLW_LoadOpenGL +** +** GLimp_win.c internal function that attempts to load and use +** a specific OpenGL DLL. +*/ +static qboolean GLW_LoadOpenGL() +{ + char buffer[1024]; + qboolean cdsFullscreen; + + strlwr( strcpy( buffer, OPENGL_DRIVER_NAME ) ); + + // + // load the driver and bind our function pointers to it + // + if ( QGL_Init( buffer ) ) + { + cdsFullscreen = r_fullscreen->integer; + + // create the window and set up the context + if ( !GLW_StartDriverAndSetMode( r_mode->integer, r_colorbits->integer, cdsFullscreen ) ) + { + // if we're on a 24/32-bit desktop and we're going fullscreen + // try it again but with a 16-bit desktop + if ( r_colorbits->integer != 16 || + cdsFullscreen != (int)qtrue || + r_mode->integer != 3 ) + { + if ( !GLW_StartDriverAndSetMode( 3, 16, qtrue ) ) + { + goto fail; + } + } + } + return qtrue; + } +fail: + + QGL_Shutdown(); + + return qfalse; +} + +/* +** GLimp_EndFrame +*/ +void GLimp_EndFrame (void) +{ + // + // swapinterval stuff + // + if ( r_swapInterval->modified ) { + r_swapInterval->modified = qfalse; + + if ( !glConfig.stereoEnabled ) { // why? + if ( qwglSwapIntervalEXT ) { + qwglSwapIntervalEXT( r_swapInterval->integer ); + } + } + } + + + // don't flip if drawing to front buffer + //if ( stricmp( r_drawBuffer->string, "GL_FRONT" ) != 0 ) + { + SwapBuffers( glw_state.hDC ); + } + + // check logging + QGL_EnableLogging( r_logFile->integer ); +} + +static void GLW_StartOpenGL( void ) +{ + // + // load and initialize the specific OpenGL driver + // + if ( !GLW_LoadOpenGL() ) + { + Com_Error( ERR_FATAL, "GLW_StartOpenGL() - could not load OpenGL subsystem\n" ); + } +} + +/* +** GLimp_Init +** +** This is the platform specific OpenGL initialization function. It +** is responsible for loading OpenGL, initializing it, setting +** extensions, creating a window of the appropriate size, doing +** fullscreen manipulations, etc. Its overall responsibility is +** to make sure that a functional OpenGL subsystem is operating +** when it returns to the ref. +*/ +void GLimp_Init( void ) +{ + char buf[MAX_STRING_CHARS]; + cvar_t *lastValidRenderer = Cvar_Get( "r_lastValidRenderer", "(uninitialized)", CVAR_ARCHIVE ); + cvar_t *cv; + + VID_Printf( PRINT_ALL, "Initializing OpenGL subsystem\n" ); + + // + // check OS version to see if we can do fullscreen display changes + // + if ( !GLW_CheckOSVersion() ) + { + Com_Error( ERR_FATAL, "GLimp_Init() - incorrect operating system\n" ); + } + + // save off hInstance and wndproc + cv = Cvar_Get( "win_hinstance", "", 0 ); + sscanf( cv->string, "%i", (int *)&g_wv.hInstance ); + + cv = Cvar_Get( "win_wndproc", "", 0 ); + sscanf( cv->string, "%i", (int *)&glw_state.wndproc ); + + r_allowSoftwareGL = Cvar_Get( "r_allowSoftwareGL", "0", CVAR_LATCH ); + + // load appropriate DLL and initialize subsystem + GLW_StartOpenGL(); + + // get our config strings + glConfig.vendor_string = (const char *) qglGetString (GL_VENDOR); + glConfig.renderer_string = (const char *) qglGetString (GL_RENDERER); + glConfig.version_string = (const char *) qglGetString (GL_VERSION); + glConfig.extensions_string = (const char *) qglGetString (GL_EXTENSIONS); + + if (!glConfig.vendor_string || !glConfig.renderer_string || !glConfig.version_string || !glConfig.extensions_string) + { + Com_Error( ERR_FATAL, "GLimp_Init() - Invalid GL Driver\n" ); + } + + // OpenGL driver constants + qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &glConfig.maxTextureSize ); + // stubbed or broken drivers may have reported 0... + if ( glConfig.maxTextureSize <= 0 ) + { + glConfig.maxTextureSize = 0; + } + + // + // chipset specific configuration + // + strcpy( buf, glConfig.renderer_string ); + strlwr( buf ); + + // + // NOTE: if changing cvars, do it within this block. This allows them + // to be overridden when testing driver fixes, etc. but only sets + // them to their default state when the hardware is first installed/run. + // +extern qboolean Sys_LowPhysicalMemory(); + if ( Q_stricmp( lastValidRenderer->string, glConfig.renderer_string ) ) + { + if (Sys_LowPhysicalMemory()) + { + Cvar_Set("s_khz", "11");// this will get called before S_Init + Cvar_Set("cg_VariantSoundCap", "2"); + Cvar_Set("s_allowDynamicMusic","0"); + } + //reset to defaults + Cvar_Set( "r_picmip", "1" ); + + // Savage3D and Savage4 should always have trilinear enabled + if ( strstr( buf, "savage3d" ) || strstr( buf, "s3 savage4" ) || strstr( buf, "geforce" ) || strstr( buf, "quadro" ) ) + { + Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + } + else + { + Cvar_Set( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST" ); + } + + if ( strstr( buf, "kyro" ) ) + { + Cvar_Set( "r_ext_texture_filter_anisotropic", "0"); //KYROs have it avail, but suck at it! + Cvar_Set( "r_ext_preferred_tc_method", "1"); //(Use DXT1 instead of DXT5 - same quality but much better performance on KYRO) + } + + if ( strstr( buf, "geforce2" ) ) + { + Cvar_Set( "cg_renderToTextureFX", "0"); // slow to zero bug fix + } + + if ( strstr( buf, "radeon 9000" ) ) + { + Cvar_Set( "cg_renderToTextureFX", "0"); // white texture bug + } + + GLW_InitExtensions(); //get the values for test below + //this must be a really sucky card! + if ( (glConfig.textureCompression == TC_NONE) || (glConfig.maxActiveTextures < 2) || (glConfig.maxTextureSize <= 512) ) + { + Cvar_Set( "r_picmip", "2"); + Cvar_Set( "r_colorbits", "16"); + Cvar_Set( "r_texturebits", "16"); + Cvar_Set( "r_mode", "3"); //force 640 + Cmd_ExecuteString ("exec low.cfg\n"); //get the rest which can be pulled in after init + } + } + + Cvar_Set( "r_lastValidRenderer", glConfig.renderer_string ); + GLW_InitExtensions(); + + WG_CheckHardwareGamma(); +} + +/* +** GLimp_Shutdown +** +** This routine does all OS specific shutdown procedures for the OpenGL +** subsystem. +*/ +void GLimp_Shutdown( void ) +{ +// const char *strings[] = { "soft", "hard" }; + const char *success[] = { "failed", "success" }; + int retVal; + + // FIXME: Brian, we need better fallbacks from partially initialized failures + if ( !qwglMakeCurrent ) { + return; + } + + VID_Printf( PRINT_ALL, "Shutting down OpenGL subsystem\n" ); + + // restore gamma. We do this first because 3Dfx's extension needs a valid OGL subsystem + WG_RestoreGamma(); + + // set current context to NULL + if ( qwglMakeCurrent ) + { + retVal = qwglMakeCurrent( NULL, NULL ) != 0; + + VID_Printf( PRINT_ALL, "...wglMakeCurrent( NULL, NULL ): %s\n", success[retVal] ); + } + + // delete HGLRC + if ( glw_state.hGLRC ) + { + retVal = qwglDeleteContext( glw_state.hGLRC ) != 0; + VID_Printf( PRINT_ALL, "...deleting GL context: %s\n", success[retVal] ); + glw_state.hGLRC = NULL; + } + + // release DC + if ( glw_state.hDC ) + { + retVal = ReleaseDC( g_wv.hWnd, glw_state.hDC ) != 0; + VID_Printf( PRINT_ALL, "...releasing DC: %s\n", success[retVal] ); + glw_state.hDC = NULL; + } + + // destroy window + if ( g_wv.hWnd ) + { + VID_Printf( PRINT_ALL, "...destroying window\n" ); + ShowWindow( g_wv.hWnd, SW_HIDE ); + DestroyWindow( g_wv.hWnd ); + g_wv.hWnd = NULL; + glw_state.pixelFormatSet = qfalse; + } + + // close the r_logFile + if ( glw_state.log_fp ) + { + fclose( glw_state.log_fp ); + glw_state.log_fp = 0; + } + + // reset display settings + if ( glw_state.cdsFullscreen ) + { + VID_Printf( PRINT_ALL, "...resetting display\n" ); + ChangeDisplaySettings( 0, 0 ); + glw_state.cdsFullscreen = qfalse; + } + + // shutdown QGL subsystem + QGL_Shutdown(); + + memset( &glConfig, 0, sizeof( glConfig ) ); + memset( &glState, 0, sizeof( glState ) ); +} + +/* +** GLimp_LogComment +*/ +void GLimp_LogComment( char *comment ) +{ + if ( glw_state.log_fp ) { + fprintf( glw_state.log_fp, "%s", comment ); + } +} \ No newline at end of file diff --git a/code/win32/win_glimp_console.cpp b/code/win32/win_glimp_console.cpp new file mode 100644 index 0000000..0c3dc0f --- /dev/null +++ b/code/win32/win_glimp_console.cpp @@ -0,0 +1,261 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +/* +** WIN_GLIMP.C +** +** This file contains ALL Win32 specific stuff having to do with the +** OpenGL refresh. When a port is being made the following functions +** must be implemented by the port: +** +** GLimp_EndFrame +** GLimp_Init +** GLimp_LogComment +** GLimp_Shutdown +** +** Note that the GLW_xxx functions are Windows specific GL-subsystem +** related functions that are relevant ONLY to win_glimp.c +*/ +#include +#include "../renderer/tr_local.h" +#include "../qcommon/qcommon.h" +#include "win_local.h" + +#if defined(_WINDOWS) || defined(_XBOX) +#include "glw_win_dx8.h" +#elif defined(_GAMECUBE) +#include "glw_win_gc.h" +#endif + + +extern void WG_CheckHardwareGamma( void ); + +static void GLW_InitExtensions( void ); +static int GLW_CreateWindow( void ); + +// +// function declaration +// +void QGL_EnableLogging( qboolean enable ); +qboolean QGL_Init( const char *dllname ); +void QGL_Shutdown( void ); +void GLW_Init(int width, int height, int colorbits, qboolean cdsFullscreen); +void GLW_Shutdown(void); + + +// +// variable declarations +// +glwstate_t *glw_state = NULL; + + +/* +** GLW_CreateWindow +** +** Responsible for creating the Alchemy window and initializing the OpenGL driver. +*/ +static qboolean GLW_CreateWindow( int width, int height, int colorbits, qboolean cdsFullscreen ) +{ + GLW_Init(width, height, colorbits, cdsFullscreen); + IN_Init(); + + return qtrue; +} + +//-------------------------------------------- +static void GLW_InitTextureCompression( void ) +{ + glConfig.textureCompression = TC_NONE; +} + +/* +** GLW_InitExtensions +*/ +static void GLW_InitExtensions( void ) +{ + // Select our tc scheme + GLW_InitTextureCompression(); + + // GL_EXT_texture_env_add + glConfig.textureEnvAddAvailable = qfalse; + if ( strstr( glConfig.extensions_string, "EXT_texture_env_add" ) ) + { + glConfig.textureEnvAddAvailable = qtrue; + } + + // GL_EXT_texture_filter_anisotropic + glConfig.textureFilterAnisotropicAvailable = qfalse; + if ( strstr( glConfig.extensions_string, "EXT_texture_filter_anisotropic" ) ) + { + glConfig.textureFilterAnisotropicAvailable = qtrue; + } + + // GL_EXT_clamp_to_edge + glConfig.clampToEdgeAvailable = qfalse; + if ( strstr( glConfig.extensions_string, "GL_EXT_texture_edge_clamp" ) ) + { + glConfig.clampToEdgeAvailable = qtrue; + } + + // GL_ARB_multitexture + if ( strstr( glConfig.extensions_string, "GL_ARB_multitexture" ) ) + { + if ( qglActiveTextureARB ) + { + qglGetIntegerv( GL_MAX_ACTIVE_TEXTURES_ARB, &glConfig.maxActiveTextures ); + + if ( glConfig.maxActiveTextures < 2 ) + { + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + } + } + } +} + +/* +** GLW_LoadOpenGL +** +** GLimp_win.c internal function that attempts to load and use +** a specific OpenGL DLL. +*/ +static qboolean GLW_LoadOpenGL() +{ + char buffer[1024]; + + strlwr( strcpy( buffer, OPENGL_DRIVER_NAME ) ); + + // + // load the driver and bind our function pointers to it + // + if ( QGL_Init( buffer ) ) + { + GLW_CreateWindow(640, 480, 24, 1); + return qtrue; + } + + QGL_Shutdown(); + + return qfalse; +} + + +/* +** GLimp_EndFrame +*/ +void GLimp_EndFrame (void) +{ + // don't flip if drawing to front buffer +// if ( stricmp( r_drawBuffer->string, "GL_FRONT" ) != 0 ) + { + } +} + +static void GLW_StartOpenGL( void ) +{ + // + // load and initialize the specific OpenGL driver + // + if ( !GLW_LoadOpenGL() ) + { + Com_Error( ERR_FATAL, "GLW_StartOpenGL() - could not load OpenGL subsystem\n" ); + } +} + +/* +** GLimp_Init +** +** This is the platform specific OpenGL initialization function. It +** is responsible for loading OpenGL, initializing it, setting +** extensions, creating a window of the appropriate size, doing +** fullscreen manipulations, etc. Its overall responsibility is +** to make sure that a functional OpenGL subsystem is operating +** when it returns to the ref. +*/ +void GLimp_Init( void ) +{ + // load appropriate DLL and initialize subsystem + GLW_StartOpenGL(); + + // get our config strings + glConfig.vendor_string = (const char *) qglGetString (GL_VENDOR); + glConfig.renderer_string = (const char *) qglGetString (GL_RENDERER); + glConfig.version_string = (const char *) qglGetString (GL_VERSION); + glConfig.extensions_string = (const char *) qglGetString (GL_EXTENSIONS); + + if (!glConfig.vendor_string || !glConfig.renderer_string || !glConfig.version_string || !glConfig.extensions_string) + { + Com_Error( ERR_FATAL, "GLimp_Init() - Invalid GL Driver\n" ); + } + + // OpenGL driver constants + qglGetIntegerv( GL_MAX_TEXTURE_SIZE, &glConfig.maxTextureSize ); + // stubbed or broken drivers may have reported 0... + if ( glConfig.maxTextureSize <= 0 ) + { + glConfig.maxTextureSize = 0; + } + + GLW_InitExtensions(); + WG_CheckHardwareGamma(); +} + +/* +** GLimp_Shutdown +** +** This routine does all OS specific shutdown procedures for the OpenGL +** subsystem. +*/ +void GLimp_Shutdown( void ) +{ + // FIXME: Brian, we need better fallbacks from partially initialized failures + VID_Printf( PRINT_ALL, "Shutting down OpenGL subsystem\n" ); + + // Set the gamma back to normal +// GLimp_SetGamma(1.f); + + // kill input system (tied to window) + IN_Shutdown(); + + // shutdown QGL subsystem + GLW_Shutdown(); + QGL_Shutdown(); + + memset( &glConfig, 0, sizeof( glConfig ) ); + memset( &glState, 0, sizeof( glState ) ); +} + +/* +** GLimp_LogComment +*/ +void GLimp_LogComment( char *comment ) +{ +} + + +/* +=========================================================== + +SMP acceleration + +=========================================================== +*/ + +qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) { + return qfalse; +} + +void *GLimp_RendererSleep( void ) { + return NULL; +} + + +void GLimp_FrontEndSleep( void ) { +} + + +void GLimp_WakeRenderer( void *data ) { +} diff --git a/code/win32/win_highdynamicrange.cpp b/code/win32/win_highdynamicrange.cpp new file mode 100644 index 0000000..4b541d7 --- /dev/null +++ b/code/win32/win_highdynamicrange.cpp @@ -0,0 +1,673 @@ +// +// +// Win_HighDynamicRange.cpp +// +// High dynamic range effect +// +// + +#include "../server/exe_headers.h" + +#include "../renderer/tr_local.h" +#include "glw_win_dx8.h" +#include "win_local.h" +#include "win_highdynamicrange.h" +#include "shader_constants.h" + +#include +#include + +extern const char *Sys_RemapPath( const char *filename ); + +VVHighDynamicRange HDREffect; + +VVHighDynamicRange::VVHighDynamicRange() +{ + m_bInitialized = false; + + m_dwHotBlurPixelShader = 0; + m_dwExtractHotPixelShader = 0; +} + + +void VVHighDynamicRange::Initialize() +{ + // Create pixel shader + if(!(CreatePixelShader(Sys_RemapPath("base\\media\\hotblur.xpu"), &m_dwHotBlurPixelShader))) + return; + + if(!(CreatePixelShader(Sys_RemapPath("base\\media\\extracthot.xpu"), &m_dwExtractHotPixelShader))) + return; + + // Get size of render target + LPDIRECT3DSURFACE8 pRenderTarget; + glw_state->device->GetRenderTarget( &pRenderTarget ); + D3DSURFACE_DESC descRenderTarget; + pRenderTarget->GetDesc( &descRenderTarget ); + UINT Width = descRenderTarget.Width; + UINT Height = descRenderTarget.Height; + D3DFORMAT Format = descRenderTarget.Format; + pRenderTarget->Release(); + + // Create extract hot text + glw_state->device->CreateTexture( Width >> 1, Height >> 1, 1, + D3DUSAGE_RENDERTARGET, Format, + 0, &m_rpHotImage); + + // Make the size a factor of 2 smaller on each axis + glw_state->device->CreateTexture( Width >> 1, Height >> 2, 1, + D3DUSAGE_RENDERTARGET, Format, + 0, &m_rpBlur[0]); + + glw_state->device->CreateTexture( Width >> 2, Height >> 2, 1, + D3DUSAGE_RENDERTARGET, Format, + 0, &m_rpBlur[1]); + + // Set bloom scale + m_fBloomScale = 1.00f; + + m_bInitialized = true; +} + + +void VVHighDynamicRange::Render() +{ + if(!m_bInitialized) + return; + + DWORD lighting, fog, srcblend, destblend, alphablend, zwrite, zenable; + + glw_state->device->GetRenderState( D3DRS_LIGHTING, &lighting ); + glw_state->device->GetRenderState( D3DRS_FOGENABLE, &fog ); + glw_state->device->GetRenderState( D3DRS_SRCBLEND, &srcblend ); + glw_state->device->GetRenderState( D3DRS_DESTBLEND, &destblend ); + glw_state->device->GetRenderState( D3DRS_ALPHABLENDENABLE, &alphablend ); + glw_state->device->GetRenderState( D3DRS_ZWRITEENABLE, &zwrite ); + glw_state->device->GetRenderState( D3DRS_ZENABLE, &zenable ); + + HotBlur(); // Blur the hot values in the backbuffer + DrawHotBlur(); // Draw blurred hot values, add to scene + + glw_state->device->SetRenderState( D3DRS_LIGHTING, lighting ); + glw_state->device->SetRenderState( D3DRS_FOGENABLE, fog ); + glw_state->device->SetRenderState( D3DRS_SRCBLEND, srcblend ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, destblend ); + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, alphablend ); + glw_state->device->SetRenderState( D3DRS_ZWRITEENABLE, zwrite ); + glw_state->device->SetRenderState( D3DRS_ZENABLE, zenable ); +} + + +void VVHighDynamicRange::DrawHotBlur() +{ + if( !m_pBlur ) + return; + + LPDIRECT3DTEXTURE8 pTexture = m_pBlur; + + // Get size of backbuffer + LPDIRECT3DSURFACE8 pRenderTarget; + glw_state->device->GetRenderTarget( &pRenderTarget ); + D3DSURFACE_DESC descRenderTarget; + pRenderTarget->GetDesc( &descRenderTarget ); + UINT Width = descRenderTarget.Width; + UINT Height = descRenderTarget.Height; + pRenderTarget->Release(); + + // Texture coordinates in linear format textures go from 0 to n-1 rather + // than the 0 to 1 that is used for swizzled textures. + D3DSURFACE_DESC desc; + pTexture->GetLevelDesc( 0, &desc ); + struct BACKGROUNDVERTEX { D3DXVECTOR4 p; FLOAT tu, tv; } v[4]; + v[0].p = D3DXVECTOR4( -0.5f, -0.5f, 1.0f, 1.0f ); + v[0].tu = 0.0f; v[0].tv = 0.0f; + v[1].p = D3DXVECTOR4( Width - 0.5f, -0.5f, 1.0f, 1.0f ); + v[1].tu = (float)desc.Width; v[1].tv = 0.0f; + v[2].p = D3DXVECTOR4( -0.5f, Height - 0.5f, 1.0f, 1.0f ); + v[2].tu = 0.0f; v[2].tv = (float)desc.Height; + v[3].p = D3DXVECTOR4( Width - 0.5f, Height - 0.5f, 1.0f, 1.0f ); + v[3].tu = (float)desc.Width; v[3].tv = (float)desc.Height; + + // Set states + glw_state->device->SetPixelShader( 0 ); + glw_state->device->SetTexture( 0, pTexture ); + glw_state->device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE2X ); + glw_state->device->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); + glw_state->device->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_TFACTOR ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MAXMIPLEVEL, 0 ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_NONE ); + glw_state->device->SetRenderState( D3DRS_ZENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE ); + + XGCOLOR Blend(1.0f, 1.0f, 1.0f, 1.0f); + Blend*= r_hdrbloom->value;//m_fBloomScale; // adjust blend amount + glw_state->device->SetRenderState( D3DRS_TEXTUREFACTOR, Blend ); + + // add if requested + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, true ); + glw_state->device->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); + glw_state->device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); + + // Render the screen-aligned quadrilateral + glw_state->device->SetVertexShader( D3DFVF_XYZRHW|D3DFVF_TEX1 ); + glw_state->device->DrawVerticesUP( D3DPT_QUADSTRIP, 4, v, sizeof(BACKGROUNDVERTEX) ); + + glw_state->device->SetRenderState( D3DRS_ZENABLE, TRUE ); +} + + +void VVHighDynamicRange::FilterCopy( LPDIRECT3DTEXTURE8 pTextureDst, + LPDIRECT3DTEXTURE8 pTextureSrc, + UINT nSample, FilterSample rSample[], + UINT nSuperSampleX, UINT nSuperSampleY, + bool bCrap, + RECT* pRectDst, RECT* pRectSrc + ) +{ + // Texture space pixel center == screen space pixel center + glw_state->device->SetScreenSpaceOffset( -0.5f, -0.5f ); + + // Save current render target and depth buffer + LPDIRECT3DSURFACE8 pRenderTarget, pZBuffer; + glw_state->device->GetRenderTarget( &pRenderTarget ); + glw_state->device->GetDepthStencilSurface( &pZBuffer ); + + // Set destination as render target + LPDIRECT3DSURFACE8 pSurface = NULL; + pTextureDst->GetSurfaceLevel( 0, &pSurface ); + glw_state->device->SetRenderTarget( pSurface, NULL ); // no depth-buffering + pSurface->Release(); + + // Get descriptions of source and destination + D3DSURFACE_DESC descSrc; + pTextureSrc->GetLevelDesc( 0, &descSrc ); + D3DSURFACE_DESC descDst; + pTextureDst->GetLevelDesc( 0, &descDst ); + + // Setup rectangles if not specified on input + RECT rectSrc = { 0, 0, descSrc.Width, descSrc.Height }; + if( pRectSrc == NULL ) pRectSrc = &rectSrc; + RECT rectDst = { 0, 0, descDst.Width, descDst.Height }; + if( pRectDst == NULL ) + { + // If the destination rectangle is not specified, + // we change it to match the source rectangle + rectDst.right = (pRectSrc->right - pRectSrc->left) / nSuperSampleX; + rectDst.bottom = (pRectSrc->bottom - pRectSrc->top) / nSuperSampleY; + pRectDst = &rectDst; + } + assert( (pRectDst->right - pRectDst->left) == + (pRectSrc->right - pRectDst->left) / (INT)nSuperSampleX ); + assert( (pRectDst->bottom - pRectDst->top) == + (pRectSrc->bottom - pRectDst->top) / (INT)nSuperSampleY ); + + //Set render state for filtering + glw_state->device->SetRenderState( D3DRS_LIGHTING, FALSE ); + glw_state->device->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID ); + glw_state->device->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_ZENABLE, D3DZB_FALSE ); + glw_state->device->SetRenderState( D3DRS_STENCILENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_FOGENABLE, FALSE ); + // On first rendering, copy new value over current render target contents + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); + // Setup subsequent renderings to add to previous value + glw_state->device->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); + glw_state->device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); + + // Set texture state + for( UINT xx = 0; xx < 4; xx++) + { + // Use our source texture for all four stages + glw_state->device->SetTexture( xx, pTextureSrc); + glw_state->device->SetTextureStageState( xx, D3DTSS_COLOROP, D3DTOP_DISABLE ); + glw_state->device->SetTextureStageState( xx, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + + // Pass texture coords without transformation + glw_state->device->SetTextureStageState( xx, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE ); + + // Each texture has different tex coords + glw_state->device->SetTextureStageState( xx, D3DTSS_TEXCOORDINDEX, xx ); + glw_state->device->SetTextureStageState( xx, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( xx, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( xx, D3DTSS_MAXMIPLEVEL, 0 ); + glw_state->device->SetTextureStageState( xx, D3DTSS_MIPFILTER, D3DTEXF_NONE ); + glw_state->device->SetTextureStageState( xx, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( xx, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( xx, D3DTSS_COLORKEYOP, D3DTCOLORKEYOP_DISABLE ); + glw_state->device->SetTextureStageState( xx, D3DTSS_COLORSIGN, 0 ); + glw_state->device->SetTextureStageState( xx, D3DTSS_ALPHAKILL, D3DTALPHAKILL_DISABLE ); + } + + // Use hot blur pixel shader + if(bCrap) + glw_state->device->SetPixelShader( m_dwExtractHotPixelShader ); + else + glw_state->device->SetPixelShader( m_dwHotBlurPixelShader ); + + // For screen-space texture-mapped quadrilateral + glw_state->device->SetVertexShader( D3DFVF_XYZRHW|D3DFVF_TEX4 ); + + // Prepare quadrilateral vertices + float x0 = (float)pRectDst->left; + float y0 = (float)pRectDst->top; + float x1 = (float)pRectDst->right; + float y1 = (float)pRectDst->bottom; + struct Quad + { + float x, y, z, w1; + struct uv + { + float u, v; + } + tex[4]; // each texture has different offset + } + aQuad[4] = + { // X Y Z 1/W u0 v0 u1 v1 u2 v2 u3 v3 + {x0, y0, 1.0f, 1.0f, }, // texture coords are set below + {x1, y0, 1.0f, 1.0f, }, + {x0, y1, 1.0f, 1.0f, }, + {x1, y1, 1.0f, 1.0f, } + }; + + // Set rendering to just the destination rect + glw_state->device->SetScissors( 1, FALSE, (D3DRECT *)pRectDst ); + + // Draw a quad for each block of 4 filter coefficients + float fOffsetScaleU = (float)nSuperSampleX; // offset for supersample + float fOffsetScaleV = (float)nSuperSampleY; + float u0 = (float)pRectSrc->left; + float v0 = (float)pRectSrc->top; + float u1 = (float)pRectSrc->right; + float v1 = (float)pRectSrc->bottom; + + + if( XGIsSwizzledFormat( descSrc.Format ) ) + { + float fWidthScale = 1.f / (float)descSrc.Width; + float fHeightScale = 1.f / (float)descSrc.Height; + fOffsetScaleU *= fWidthScale; + fOffsetScaleV *= fHeightScale; + u0 *= fWidthScale; + v0 *= fHeightScale; + u1 *= fWidthScale; + v1 *= fHeightScale; + } + + xx = 0; // current texture stage + D3DCOLOR rColor[4]; + DWORD rPSInput[4]; + for( UINT iSample = 0; iSample < nSample; iSample++ ) + { + // Set filter coefficients + float fValue = rSample[iSample].fValue; + if( fValue < 0.f ) + { + rColor[xx] = D3DXCOLOR( -fValue, -fValue, -fValue, -fValue ); + rPSInput[xx] = PS_INPUTMAPPING_SIGNED_NEGATE | + ((xx % 2) ? PS_REGISTER_C1 : PS_REGISTER_C0); + } + else + { + rColor[xx] = D3DXCOLOR( fValue, fValue, fValue, fValue ); + rPSInput[xx] = PS_INPUTMAPPING_SIGNED_IDENTITY | + ((xx % 2) ? PS_REGISTER_C1 : PS_REGISTER_C0); + } + + // Align supersamples with center of destination pixels + float fOffsetX = rSample[iSample].fOffsetX;// * fOffsetScaleU; + float fOffsetY = rSample[iSample].fOffsetY;// * fOffsetScaleV; + aQuad[0].tex[xx].u = u0 + fOffsetX; + aQuad[0].tex[xx].v = v0 + fOffsetY; + aQuad[1].tex[xx].u = u1 + fOffsetX; + aQuad[1].tex[xx].v = v0 + fOffsetY; + aQuad[2].tex[xx].u = u0 + fOffsetX; + aQuad[2].tex[xx].v = v1 + fOffsetY; + aQuad[3].tex[xx].u = u1 + fOffsetX; + aQuad[3].tex[xx].v = v1 + fOffsetY; + + xx++; // Go to next stage + if( xx == 4 || iSample == nSample - 1 ) // max texture stages or last sample + { + // Zero out unused texture stage coefficients + // (Only for last filter sample, when number of samples is not divisible by 4) + for( ; xx < 4; xx++) + { + glw_state->device->SetTexture( xx, NULL ); + rColor[xx] = 0; + rPSInput[xx] = PS_INPUTMAPPING_UNSIGNED_IDENTITY | PS_REGISTER_ZERO; + } + + // Set coefficients + glw_state->device->SetRenderState( D3DRS_PSCONSTANT0_0, rColor[0] ); + glw_state->device->SetRenderState( D3DRS_PSCONSTANT1_0, rColor[1] ); + glw_state->device->SetRenderState( D3DRS_PSCONSTANT0_1, rColor[2] ); + glw_state->device->SetRenderState( D3DRS_PSCONSTANT1_1, rColor[3] ); + + if(bCrap) + { + } + else + { + + // Remap coefficients to proper sign + glw_state->device->SetRenderState( + D3DRS_PSRGBINPUTS0, + PS_COMBINERINPUTS( rPSInput[0] | PS_CHANNEL_RGB, + PS_REGISTER_T0 | PS_CHANNEL_RGB | + PS_INPUTMAPPING_SIGNED_IDENTITY, + rPSInput[1] | PS_CHANNEL_RGB, + PS_REGISTER_T1 | PS_CHANNEL_RGB | + PS_INPUTMAPPING_SIGNED_IDENTITY ) ); + glw_state->device->SetRenderState( + D3DRS_PSALPHAINPUTS0, + PS_COMBINERINPUTS( rPSInput[0] | PS_CHANNEL_ALPHA, + PS_REGISTER_T0 | PS_CHANNEL_ALPHA | + PS_INPUTMAPPING_SIGNED_IDENTITY, + rPSInput[1] | PS_CHANNEL_ALPHA, + PS_REGISTER_T1 | PS_CHANNEL_ALPHA | + PS_INPUTMAPPING_SIGNED_IDENTITY ) ); + glw_state->device->SetRenderState( + D3DRS_PSRGBINPUTS1, + PS_COMBINERINPUTS( rPSInput[2] | PS_CHANNEL_RGB, + PS_REGISTER_T2 | PS_CHANNEL_RGB | + PS_INPUTMAPPING_SIGNED_IDENTITY, + rPSInput[3] | PS_CHANNEL_RGB, + PS_REGISTER_T3 | PS_CHANNEL_RGB | + PS_INPUTMAPPING_SIGNED_IDENTITY ) ); + glw_state->device->SetRenderState( + D3DRS_PSALPHAINPUTS1, + PS_COMBINERINPUTS( rPSInput[2] | PS_CHANNEL_ALPHA, + PS_REGISTER_T2 | PS_CHANNEL_ALPHA | + PS_INPUTMAPPING_SIGNED_IDENTITY, + rPSInput[3] | PS_CHANNEL_ALPHA, + PS_REGISTER_T3 | PS_CHANNEL_ALPHA | + PS_INPUTMAPPING_SIGNED_IDENTITY ) ); + } + + // Draw the quad to filter the coefficients so far + // One quad blends 4 textures + glw_state->device->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, aQuad, sizeof(Quad) ); + + // On subsequent renderings, add to what's in the render target + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); + xx = 0; + } + } + + // Clear texture stages + for( xx=0; xx<4; xx++ ) + { + glw_state->device->SetTexture( xx, NULL ); + glw_state->device->SetTextureStageState( xx, D3DTSS_COLOROP, D3DTOP_DISABLE ); + glw_state->device->SetTextureStageState( xx, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + glw_state->device->SetTextureStageState( xx, D3DTSS_MIPMAPLODBIAS, 0 ); + } + + // Restore render target and zbuffer + glw_state->device->SetRenderTarget( pRenderTarget, pZBuffer ); + + if( pRenderTarget ) + pRenderTarget->Release(); + + if( pZBuffer ) + pZBuffer->Release(); + + glw_state->device->SetScreenSpaceOffset( 0.0f, 0.0f ); +} + + +void VVHighDynamicRange::ExtractHot( LPDIRECT3DTEXTURE8 pTextureDst, + LPDIRECT3DTEXTURE8 pTextureSrc, + UINT nSuperSampleX, UINT nSuperSampleY, + RECT* pRectDst, RECT* pRectSrc ) +{ + // Texture space pixel center == screen space pixel center + glw_state->device->SetScreenSpaceOffset( -0.5f, -0.5f ); + + // Save current render target and depth buffer + LPDIRECT3DSURFACE8 pRenderTarget, pZBuffer; + glw_state->device->GetRenderTarget( &pRenderTarget ); + glw_state->device->GetDepthStencilSurface( &pZBuffer ); + + // Surface that has the depth-buffer as data, used as an RGBA texture: + D3DSurface zBufferSurface; + D3DSURFACE_DESC descZ; + pZBuffer->GetDesc( &descZ ); + XGSetSurfaceHeader( descZ.Width, descZ.Height, D3DFMT_LIN_A8R8G8B8, &zBufferSurface, pZBuffer->Data, descZ.Width * 4 ); + + // Set destination as render target + LPDIRECT3DSURFACE8 pSurface = NULL; + pTextureDst->GetSurfaceLevel( 0, &pSurface ); + glw_state->device->SetRenderTarget( pSurface, NULL ); // no depth-buffering + pSurface->Release(); + + // Get descriptions of source and destination + D3DSURFACE_DESC descSrc; + pTextureSrc->GetLevelDesc( 0, &descSrc ); + D3DSURFACE_DESC descDst; + pTextureDst->GetLevelDesc( 0, &descDst ); + + // Setup rectangles if not specified on input + RECT rectSrc = { 0, 0, descSrc.Width, descSrc.Height }; + if( pRectSrc == NULL ) pRectSrc = &rectSrc; + RECT rectDst = { 0, 0, descDst.Width, descDst.Height }; + if( pRectDst == NULL ) + { + // If the destination rectangle is not specified, + // we change it to match the source rectangle + rectDst.right = (pRectSrc->right - pRectSrc->left) / nSuperSampleX; + rectDst.bottom = (pRectSrc->bottom - pRectSrc->top) / nSuperSampleY; + pRectDst = &rectDst; + } + assert( (pRectDst->right - pRectDst->left) == + (pRectSrc->right - pRectDst->left) / (INT)nSuperSampleX ); + assert( (pRectDst->bottom - pRectDst->top) == + (pRectSrc->bottom - pRectDst->top) / (INT)nSuperSampleY ); + + //Set render state for filtering + glw_state->device->SetRenderState( D3DRS_LIGHTING, FALSE ); + glw_state->device->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID ); + glw_state->device->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_ZENABLE, D3DZB_FALSE ); +// glw_state->device->SetRenderState( D3DRS_ZFUNC, D3DCMP_ALWAYS ); // New +// glw_state->device->SetRenderState( D3DRS_STENCILENABLE, FALSE ); // Stencil done in PS + + glw_state->device->SetRenderState( D3DRS_FOGENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); + glw_state->device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); + + // Put our original back buffer in texture 0, put the old Z/Stencil in texture 1: + glw_state->device->SetTexture( 0, pTextureSrc ); + glw_state->device->SetTexture( 1, (LPDIRECT3DTEXTURE8)&zBufferSurface ); + + glw_state->device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_DISABLE ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + glw_state->device->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE ); + glw_state->device->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE ); + + // Pass texture coords without transformation + glw_state->device->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE ); + glw_state->device->SetTextureStageState( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE ); + + // Each texture has different tex coords + glw_state->device->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0 ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ADDRESSU, + D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ADDRESSV, + D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MAXMIPLEVEL, 0 ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_NONE ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_GAUSSIANCUBIC ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_GAUSSIANCUBIC ); + glw_state->device->SetTextureStageState( 0, D3DTSS_COLORKEYOP, + D3DTCOLORKEYOP_DISABLE ); + glw_state->device->SetTextureStageState( 0, D3DTSS_COLORSIGN, 0 ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ALPHAKILL, + D3DTALPHAKILL_DISABLE ); + + // Z/Stencil is similar, but we don't want any filtering, just sampling: + glw_state->device->SetTextureStageState( 1, D3DTSS_TEXCOORDINDEX, 0 ); + glw_state->device->SetTextureStageState( 1, D3DTSS_ADDRESSU, + D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 1, D3DTSS_ADDRESSV, + D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 1, D3DTSS_MAXMIPLEVEL, 0 ); + glw_state->device->SetTextureStageState( 1, D3DTSS_MIPFILTER, D3DTEXF_NONE ); + glw_state->device->SetTextureStageState( 1, D3DTSS_MINFILTER, D3DTEXF_POINT ); + glw_state->device->SetTextureStageState( 1, D3DTSS_MAGFILTER, D3DTEXF_POINT ); + glw_state->device->SetTextureStageState( 1, D3DTSS_COLORKEYOP, + D3DTCOLORKEYOP_DISABLE ); + glw_state->device->SetTextureStageState( 1, D3DTSS_COLORSIGN, 0 ); + glw_state->device->SetTextureStageState( 1, D3DTSS_ALPHAKILL, + D3DTALPHAKILL_DISABLE ); + + // Use extract hot pixel shader + glw_state->device->SetPixelShader( m_dwExtractHotPixelShader ); + + float CutoffScale[8]; + CutoffScale[0] = CutoffScale[1] = CutoffScale[2] = CutoffScale[3] = r_hdrcutoff->value; + CutoffScale[4] = CutoffScale[5] = CutoffScale[6] = CutoffScale[7] = r_hdrcutoff->value / (1.0f - r_hdrcutoff->value); + glw_state->device->SetPixelShaderConstant( CP_EXTRACT_CUTOFF, CutoffScale, 2 ); + + // For screen-space texture-mapped quadrilateral + glw_state->device->SetVertexShader( D3DFVF_XYZRHW|D3DFVF_TEX1 ); + + // Prepare quadrilateral vertices + float x0 = (float)pRectDst->left; + float y0 = (float)pRectDst->top; + float x1 = (float)pRectDst->right; + float y1 = (float)pRectDst->bottom; + struct Quad + { + float x, y, z, w1; + struct uv + { + float u, v; + } + tex; + } + aQuad[4] = + { // X Y Z 1/W u0 v0 u1 v1 u2 v2 u3 v3 + {x0, y0, 1.0f, 1.0f, }, // texture coords are set below + {x1, y0, 1.0f, 1.0f, }, + {x0, y1, 1.0f, 1.0f, }, + {x1, y1, 1.0f, 1.0f, } + }; + + // Set rendering to just the destination rect + glw_state->device->SetScissors( 1, FALSE, (D3DRECT *)pRectDst ); + + + // Draw a quad for each block of 4 filter coefficients + float u0 = (float)pRectSrc->left; + float v0 = (float)pRectSrc->top; + float u1 = (float)pRectSrc->right; + float v1 = (float)pRectSrc->bottom; + + + if( XGIsSwizzledFormat( descSrc.Format ) ) + { + float fWidthScale = 1.f / (float)descSrc.Width; + float fHeightScale = 1.f / (float)descSrc.Height; + u0 *= fWidthScale; + v0 *= fHeightScale; + u1 *= fWidthScale; + v1 *= fHeightScale; + } + + aQuad[0].tex.u = u0; + aQuad[0].tex.v = v0; + aQuad[1].tex.u = u1; + aQuad[1].tex.v = v0; + aQuad[2].tex.u = u0; + aQuad[2].tex.v = v1; + aQuad[3].tex.u = u1; + aQuad[3].tex.v = v1; + + // Draw the quad + glw_state->device->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, + 2, aQuad, sizeof(Quad) ); + + glw_state->device->SetTexture( 0, NULL ); + glw_state->device->SetTexture( 1, NULL ); + + // Restore render target and zbuffer + glw_state->device->SetRenderTarget( pRenderTarget, pZBuffer ); + + if( pRenderTarget ) + pRenderTarget->Release(); + + if( pZBuffer ) + pZBuffer->Release(); + + glw_state->device->SetScreenSpaceOffset( 0.0f, 0.0f ); + + // Restore ztest? +// glw_state->device->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL ); +} + + +void VVHighDynamicRange::HotBlur() +{ + // Make D3DTexture wrapper around current render target + LPDIRECT3DSURFACE8 pRenderTarget; + glw_state->device->GetRenderTarget( &pRenderTarget ); + D3DSURFACE_DESC descRenderTarget; + pRenderTarget->GetDesc( &descRenderTarget ); + D3DTexture RenderTargetTexture; + ZeroMemory( &RenderTargetTexture, sizeof(RenderTargetTexture) ); + XGSetTextureHeader( descRenderTarget.Width, descRenderTarget.Height, + 1, 0, descRenderTarget.Format, 0, + &RenderTargetTexture, pRenderTarget->Data, + descRenderTarget.Width * 4 ); + pRenderTarget->Release(); + + // Filters align to blurriest point in supersamples, on the pixel centers + // This takes advantage of the bilinear filtering in the texture map lookup. + FilterSample YFilter[] = // 1221 4-tap filter in Y + { + { 2.0f/6.f, 0.0f, 1.0f }, + { 1.0f/6.f, 0.0f, 3.0f }, + { 2.0f/6.f, 0.0f, -1.0f }, + { 1.0f/6.f, 0.0f, -3.0f }, + }; + FilterSample XFilter[] = // 1221 4-tap filter in X + { + { 2.0f/6.f, 1.0f, 0.0f }, + { 1.0f/6.f, 3.0f, 0.0f }, + { 2.0f/6.f, -1.0f, 0.0f }, + { 1.0f/6.f, -3.0f, 0.0f }, + }; + + D3DTexture *pTextureSrc; + D3DTexture *pTextureDst; + + pTextureDst = &RenderTargetTexture; // source is backbuffer + + // extract "hot" portion of hte image with downsampling + pTextureSrc = pTextureDst; + pTextureDst = m_rpHotImage; // destination is blur texture + ExtractHot( pTextureDst, pTextureSrc, 2, 2 ); +// ExtractHot( pTextureDst, pTextureSrc, 1, 1 ); + + // 2 passes: Vertical gaussian (1221) followed by + // horizontal gaussian (1221), with 2x2 downsampling + pTextureSrc = pTextureDst; // destination is next blur texture + pTextureDst = m_rpBlur[0]; // destination is blur texture + FilterCopy(pTextureDst, pTextureSrc, 4, YFilter, 1, 2, false); + + pTextureSrc = pTextureDst; // source is previous blur texture + pTextureDst = m_rpBlur[1]; // destination is next blur texture + FilterCopy(pTextureDst, pTextureSrc, 4, XFilter, 2, 1, false); + + m_pBlur = pTextureDst; +} \ No newline at end of file diff --git a/code/win32/win_highdynamicrange.h b/code/win32/win_highdynamicrange.h new file mode 100644 index 0000000..5bcbb99 --- /dev/null +++ b/code/win32/win_highdynamicrange.h @@ -0,0 +1,77 @@ +// +// +// Win_HighDynamicRange.h +// +// Declaration of high dynamic range effect class +// +// + +#ifndef _WIN_HIGHDYNAMICRANGE_H_ +#define _WIN_HIGHDYNAMICRANGE_H_ + +struct FilterSample +{ + float fValue; // coefficient + float fOffsetX, fOffsetY; // subpixel offsets of supersamples in + // destination coordinates +}; + + +class VVHighDynamicRange +{ + // The blur filters are multipass and need temporary space. +#define BLUR_COUNT 2 + D3DTexture *m_rpHotImage; // hot image + D3DTexture *m_rpBlur[BLUR_COUNT]; // bluring textures of decreasing size + D3DTexture *m_pBlur; // current blur texture, set by Blur() + + // Light blend intensity scale factor + float m_fBloomScale; + + // Pixel shader handles + DWORD m_dwHotBlurPixelShader; // blur the hot image + DWORD m_dwExtractHotPixelShader; // extract hot image + + bool m_bInitialized; + + // Filtering routine that draws the source texture multiple + // times, with sub-pixel offsets and filter coefficients. + void FilterCopy( LPDIRECT3DTEXTURE9 pTextureDst, + LPDIRECT3DTEXTURE9 pTextureSrc, + UINT nSample, + FilterSample rSample[], + UINT nSuperSampleX, + UINT nSuperSampleY, + bool bCrap, + RECT *pRectDst = NULL, // The destination texture is + // written only within this + // region. + RECT *pRectSrc = NULL );// The source texture is read + // outside of this region by + // the halfwidth of the + // filter. + + // extract hot image with downsamping + void ExtractHot( LPDIRECT3DTEXTURE9 pTextureDst, + LPDIRECT3DTEXTURE9 pTextureSrc, + UINT nSuperSampleX, UINT nSuperSampleY, + RECT* pRectDst = NULL, + RECT* pRectSrc = NULL ); + + // Blur hot texture and set m_pBlur. Calls FilterCopy with + // different filter coefficients and offsets + void HotBlur(); + + // Demonstrate the inputs to the full high dynamic range effect. + void DrawHotBlur(); // draw blurred "hot" texture + +public: + virtual void Initialize(); + virtual void Render(); + + VVHighDynamicRange(); +}; + +extern VVHighDynamicRange HDREffect; + +#endif \ No newline at end of file diff --git a/code/win32/win_input.cpp b/code/win32/win_input.cpp new file mode 100644 index 0000000..3657e95 --- /dev/null +++ b/code/win32/win_input.cpp @@ -0,0 +1,1147 @@ +// win_input.c -- win32 mouse and joystick code +// 02/21/97 JCB Added extended DirectInput code to support external controllers. + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + + +#include "../client/client.h" +#ifndef _IMMERSION +#include "../client/fffx.h" +#endif // _IMMERSION +#include "win_local.h" + +typedef struct { + int oldButtonState; + + qboolean mouseActive; + qboolean mouseInitialized; +} WinMouseVars_t; + +static WinMouseVars_t s_wmv; + +static int window_center_x, window_center_y; + +// +// MIDI definitions +// +static void IN_StartupMIDI( void ); +static void IN_ShutdownMIDI( void ); + +#define MAX_MIDIIN_DEVICES 8 + +typedef struct { + int numDevices; + MIDIINCAPS caps[MAX_MIDIIN_DEVICES]; + + HMIDIIN hMidiIn; +} MidiInfo_t; + +static MidiInfo_t s_midiInfo; + +// +// Joystick definitions +// +#define JOY_MAX_AXES 6 // X, Y, Z, R, U, V + +typedef struct { + qboolean avail; + int id; // joystick number + JOYCAPS jc; + + int oldbuttonstate; + int oldpovstate; + + JOYINFOEX ji; +} joystickInfo_t; + +static joystickInfo_t joy; + + +cvar_t *in_midi; +cvar_t *in_midiport; +cvar_t *in_midichannel; +cvar_t *in_mididevice; + +cvar_t *in_mouse; +cvar_t *in_joystick; +cvar_t *in_joyBallScale; +cvar_t *in_debugJoystick; +cvar_t *joy_threshold; +cvar_t *js_ffmult; +cvar_t *joy_xbutton; +cvar_t *joy_ybutton; + +qboolean in_appactive; + +// forward-referenced functions +void IN_StartupJoystick (void); +void IN_JoyMove(void); + +static void MidiInfo_f( void ); + +/* +============================================================ + +WIN32 MOUSE CONTROL + +============================================================ +*/ + +/* +================ +IN_InitWin32Mouse +================ +*/ +void IN_InitWin32Mouse( void ) +{ +} + +/* +================ +IN_ShutdownWin32Mouse +================ +*/ +void IN_ShutdownWin32Mouse( void ) { +} + +/* +================ +IN_ActivateWin32Mouse +================ +*/ +void IN_ActivateWin32Mouse( void ) { + int width, height; + RECT window_rect; + + width = GetSystemMetrics (SM_CXSCREEN); + height = GetSystemMetrics (SM_CYSCREEN); + + GetWindowRect ( g_wv.hWnd, &window_rect); + if (window_rect.left < 0) + window_rect.left = 0; + if (window_rect.top < 0) + window_rect.top = 0; + if (window_rect.right >= width) + window_rect.right = width-1; + if (window_rect.bottom >= height-1) + window_rect.bottom = height-1; + window_center_x = (window_rect.right + window_rect.left)/2; + window_center_y = (window_rect.top + window_rect.bottom)/2; + + SetCursorPos (window_center_x, window_center_y); + + SetCapture ( g_wv.hWnd ); + ClipCursor (&window_rect); + while (ShowCursor (FALSE) >= 0) + ; +} + +/* +================ +IN_DeactivateWin32Mouse +================ +*/ +void IN_DeactivateWin32Mouse( void ) +{ + ClipCursor (NULL); + ReleaseCapture (); + while (ShowCursor (TRUE) < 0) + ; +} + +/* +================ +IN_Win32Mouse +================ +*/ +void IN_Win32Mouse( int *mx, int *my ) { + POINT current_pos; + + // find mouse movement + GetCursorPos (¤t_pos); + + // force the mouse to the center, so there's room to move + SetCursorPos (window_center_x, window_center_y); + + *mx = current_pos.x - window_center_x; + *my = current_pos.y - window_center_y; +} + + +/* +============================================================ + +DIRECT INPUT MOUSE CONTROL + +============================================================ +*/ + +#undef DEFINE_GUID + +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID name \ + = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } + +DEFINE_GUID(qGUID_SysMouse, 0x6F1D2B60,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(qGUID_XAxis, 0xA36D02E0,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(qGUID_YAxis, 0xA36D02E1,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUID(qGUID_ZAxis, 0xA36D02E2,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); + + +#define DINPUT_BUFFERSIZE 16 +#define iDirectInputCreate(a,b,c,d) pDirectInputCreate(a,b,c,d) + +HRESULT (WINAPI *pDirectInputCreate)(HINSTANCE hinst, DWORD dwVersion, + LPDIRECTINPUT * lplpDirectInput, LPUNKNOWN punkOuter); + +static HINSTANCE hInstDI; + +typedef struct MYDATA { + LONG lX; // X axis goes here + LONG lY; // Y axis goes here + LONG lZ; // Z axis goes here + BYTE bButtonA; // One button goes here + BYTE bButtonB; // Another button goes here + BYTE bButtonC; // Another button goes here + BYTE bButtonD; // Another button goes here +} MYDATA; + +static DIOBJECTDATAFORMAT rgodf[] = { + { &qGUID_XAxis, FIELD_OFFSET(MYDATA, lX), DIDFT_AXIS | DIDFT_ANYINSTANCE, 0,}, + { &qGUID_YAxis, FIELD_OFFSET(MYDATA, lY), DIDFT_AXIS | DIDFT_ANYINSTANCE, 0,}, + { &qGUID_ZAxis, FIELD_OFFSET(MYDATA, lZ), 0x80000000 | DIDFT_AXIS | DIDFT_ANYINSTANCE, 0,}, + { 0, FIELD_OFFSET(MYDATA, bButtonA), DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,}, + { 0, FIELD_OFFSET(MYDATA, bButtonB), DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,}, + { 0, FIELD_OFFSET(MYDATA, bButtonC), 0x80000000 | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,}, + { 0, FIELD_OFFSET(MYDATA, bButtonD), 0x80000000 | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,}, +}; + +#define NUM_OBJECTS (sizeof(rgodf) / sizeof(rgodf[0])) + +static DIDATAFORMAT df = { + sizeof(DIDATAFORMAT), // this structure + sizeof(DIOBJECTDATAFORMAT), // size of object data format + DIDF_RELAXIS, // absolute axis coordinates + sizeof(MYDATA), // device data size + NUM_OBJECTS, // number of objects + rgodf, // and here they are +}; + +static LPDIRECTINPUT g_pdi; +static LPDIRECTINPUTDEVICE g_pMouse; + +void IN_DIMouse( int *mx, int *my ); + +/* +======================== +IN_InitDIMouse +======================== +*/ +qboolean IN_InitDIMouse( void ) { + HRESULT hr; + int x, y; + DIPROPDWORD dipdw = { + { + sizeof(DIPROPDWORD), // diph.dwSize + sizeof(DIPROPHEADER), // diph.dwHeaderSize + 0, // diph.dwObj + DIPH_DEVICE, // diph.dwHow + }, + DINPUT_BUFFERSIZE, // dwData + }; + + Com_Printf( "Initializing DirectInput...\n"); + + if (!hInstDI) { + hInstDI = LoadLibrary("dinput.dll"); + + if (hInstDI == NULL) { + Com_Printf ("Couldn't load dinput.dll\n"); + return qfalse; + } + } + + if (!pDirectInputCreate) { + pDirectInputCreate = (long (__stdcall *)(struct HINSTANCE__ *,unsigned long,struct IDirectInputA ** ,struct IUnknown *)) + GetProcAddress(hInstDI,"DirectInputCreateA"); + + if (!pDirectInputCreate) { + Com_Printf ("Couldn't get DI proc addr\n"); + return qfalse; + } + } + + // register with DirectInput and get an IDirectInput to play with. + hr = iDirectInputCreate( g_wv.hInstance, DIRECTINPUT_VERSION, &g_pdi, NULL); + + if (FAILED(hr)) { + Com_Printf ("iDirectInputCreate failed\n"); + return qfalse; + } + + // obtain an interface to the system mouse device. + hr = g_pdi->CreateDevice( qGUID_SysMouse, &g_pMouse, NULL); + + if (FAILED(hr)) { + Com_Printf ("Couldn't open DI mouse device\n"); + return qfalse; + } + + // set the data format to "mouse format". + hr = IDirectInputDevice_SetDataFormat(g_pMouse, &df); + + if (FAILED(hr)) { + Com_Printf ("Couldn't set DI mouse format\n"); + return qfalse; + } + + // set the cooperativity level. + hr = IDirectInputDevice_SetCooperativeLevel(g_pMouse, g_wv.hWnd, + DISCL_EXCLUSIVE | DISCL_FOREGROUND); + + if (FAILED(hr)) { + Com_Printf ("Couldn't set DI coop level\n"); + return qfalse; + } + + + // set the buffer size to DINPUT_BUFFERSIZE elements. + // the buffer size is a DWORD property associated with the device + hr = IDirectInputDevice_SetProperty(g_pMouse, DIPROP_BUFFERSIZE, &dipdw.diph); + + if (FAILED(hr)) { + Com_Printf ("Couldn't set DI buffersize\n"); + return qfalse; + } + + // clear any pending samples + IN_DIMouse( &x, &y ); + IN_DIMouse( &x, &y ); + + Com_Printf( "DirectInput initialized.\n"); + return qtrue; +} + +/* +========================== +IN_ShutdownDIMouse +========================== +*/ +void IN_ShutdownDIMouse( void ) +{ + if (g_pMouse) + { + IDirectInputDevice_Release(g_pMouse); + g_pMouse = NULL; + } + if (g_pdi) + { + IDirectInput_Release(g_pdi); + g_pdi = NULL; + } + if(hInstDI) + { + FreeLibrary(hInstDI); + hInstDI = NULL; + } +} + +/* +========================== +IN_ActivateDIMouse +========================== +*/ +void IN_ActivateDIMouse( void ) { + HRESULT hr; + + if (!g_pMouse) { + return; + } + + // we may fail to reacquire if the window has been recreated + hr = IDirectInputDevice_Acquire( g_pMouse ); + if (FAILED(hr)) { + if ( !IN_InitDIMouse() ) { + Com_Printf ("Falling back to Win32 mouse support...\n"); + Cvar_Set( "in_mouse", "-1" ); + } + } +} + +/* +========================== +IN_DeactivateDIMouse +========================== +*/ +void IN_DeactivateDIMouse( void ) { + if (!g_pMouse) { + return; + } + IDirectInputDevice_Unacquire( g_pMouse ); +} + + +/* +=================== +IN_DIMouse +=================== +*/ +void IN_DIMouse( int *mx, int *my ) { + DIDEVICEOBJECTDATA od; + DIMOUSESTATE state; + DWORD dwElements; + HRESULT hr; + static float oldSysTime; + + if ( !g_pMouse ) { + return; + } + + // fetch new events + for (;;) + { + dwElements = 1; + + hr = IDirectInputDevice_GetDeviceData(g_pMouse, + sizeof(DIDEVICEOBJECTDATA), &od, &dwElements, 0); + if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED)) { + IDirectInputDevice_Acquire(g_pMouse); + return; + } + + /* Unable to read data or no data available */ + if ( FAILED(hr) ) { + break; + } + + if ( dwElements == 0 ) { + break; + } + + switch (od.dwOfs) { + case DIMOFS_BUTTON0: + if (od.dwData & 0x80) + Sys_QueEvent( od.dwTimeStamp, SE_KEY, A_MOUSE1, qtrue, 0, NULL ); + else + Sys_QueEvent( od.dwTimeStamp, SE_KEY, A_MOUSE1, qfalse, 0, NULL ); + break; + + case DIMOFS_BUTTON1: + if (od.dwData & 0x80) + Sys_QueEvent( od.dwTimeStamp, SE_KEY, A_MOUSE2, qtrue, 0, NULL ); + else + Sys_QueEvent( od.dwTimeStamp, SE_KEY, A_MOUSE2, qfalse, 0, NULL ); + break; + + case DIMOFS_BUTTON2: + if (od.dwData & 0x80) + Sys_QueEvent( od.dwTimeStamp, SE_KEY, A_MOUSE3, qtrue, 0, NULL ); + else + Sys_QueEvent( od.dwTimeStamp, SE_KEY, A_MOUSE3, qfalse, 0, NULL ); + break; + } + } + + // read the raw delta counter and ignore + // the individual sample time / values + hr = IDirectInputDevice_GetDeviceState(g_pMouse, + sizeof(DIDEVICEOBJECTDATA), &state); + if ( FAILED(hr) ) { + *mx = *my = 0; + return; + } + *mx = state.lX; + *my = state.lY; +} + +/* +============================================================ + + MOUSE CONTROL + +============================================================ +*/ + +/* +=========== +IN_ActivateMouse + +Called when the window gains focus or changes in some way +=========== +*/ +void IN_ActivateMouse( void ) +{ + if (!s_wmv.mouseInitialized ) { + return; + } + if ( !in_mouse->integer ) + { + s_wmv.mouseActive = qfalse; + return; + } + if ( s_wmv.mouseActive ) + { + return; + } + + s_wmv.mouseActive = qtrue; + + if ( in_mouse->integer != -1 ) { + IN_ActivateDIMouse(); + } + IN_ActivateWin32Mouse(); +} + + +/* +=========== +IN_DeactivateMouse + +Called when the window loses focus +=========== +*/ +void IN_DeactivateMouse( void ) { + if (!s_wmv.mouseInitialized ) { + return; + } + if (!s_wmv.mouseActive ) { + return; + } + s_wmv.mouseActive = qfalse; + + IN_DeactivateDIMouse(); + IN_DeactivateWin32Mouse(); +} + + + +/* +=========== +IN_StartupMouse +=========== +*/ +void IN_StartupMouse( void ) +{ + s_wmv.mouseInitialized = qfalse; + + if ( in_mouse->integer == 0 ) { + Com_Printf ("Mouse control not active.\n"); + return; + } + + // nt4.0 direct input is screwed up + if ( ( g_wv.osversion.dwPlatformId == VER_PLATFORM_WIN32_NT ) && + ( g_wv.osversion.dwMajorVersion == 4 ) ) + { + Com_Printf ("Disallowing DirectInput on NT 4.0\n"); + Cvar_Set( "in_mouse", "-1" ); + } + + s_wmv.mouseInitialized = qtrue; + + if ( in_mouse->integer == -1 ) { + Com_Printf ("Skipping check for DirectInput\n"); + } else { + if ( IN_InitDIMouse() ) { + return; + } + Com_Printf ("Falling back to Win32 mouse support...\n"); + } + IN_InitWin32Mouse(); +} + +/* +=========== +IN_MouseEvent +=========== +*/ +#define MAX_MOUSE_BUTTONS 5 + +static int mouseConvert[MAX_MOUSE_BUTTONS] = +{ + A_MOUSE1, + A_MOUSE2, + A_MOUSE3, + A_MOUSE4, + A_MOUSE5 +}; + +void IN_MouseEvent (int mstate) +{ + int i; + + if ( !s_wmv.mouseInitialized ) + { + return; + } + + // perform button actions + for (i = 0 ; i < MAX_MOUSE_BUTTONS ; i++ ) + { + if ( (mstate & (1 << i)) && !(s_wmv.oldButtonState & (1 << i)) ) + { + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, mouseConvert[i], true, 0, NULL ); + } + if ( !(mstate & (1 << i)) && (s_wmv.oldButtonState & (1 << i)) ) + { + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, mouseConvert[i], false, 0, NULL ); + } + } + s_wmv.oldButtonState = mstate; +} + + + +/* +=========== +IN_MouseMove +=========== +*/ +void IN_MouseMove ( void ) { + int mx, my; + + if ( g_pMouse ) { + IN_DIMouse( &mx, &my ); + } else { + IN_Win32Mouse( &mx, &my ); + } + + if ( !mx && !my ) { + return; + } + + Sys_QueEvent( 0, SE_MOUSE, mx, my, 0, NULL ); +} + + +/* +========================================================================= + +========================================================================= +*/ + +/* +=========== +IN_Startup +=========== +*/ +void IN_Startup( void ) { + Com_Printf ("\n------- Input Initialization -------\n"); + IN_StartupMouse (); + IN_StartupJoystick (); + IN_StartupMIDI(); + Com_Printf ("------------------------------------\n"); + + in_mouse->modified = qfalse; + in_joystick->modified = qfalse; +} + +/* +=========== +IN_Shutdown +=========== +*/ +void IN_Shutdown( void ) { + IN_DeactivateMouse(); + IN_ShutdownDIMouse(); + IN_ShutdownMIDI(); + Cmd_RemoveCommand("midiinfo" ); +#ifndef _IMMERSION + FF_Shutdown(); +#endif // _IMMERSION +} + + +/* +=========== +IN_Init +=========== +*/ +void IN_Init( void ) { + // MIDI input controler variables + in_midi = Cvar_Get ("in_midi", "0", CVAR_ARCHIVE); + in_midiport = Cvar_Get ("in_midiport", "1", CVAR_ARCHIVE); + in_midichannel = Cvar_Get ("in_midichannel", "1", CVAR_ARCHIVE); + in_mididevice = Cvar_Get ("in_mididevice", "0", CVAR_ARCHIVE); + + Cmd_AddCommand( "midiinfo", MidiInfo_f ); + + // mouse variables + in_mouse = Cvar_Get ("in_mouse", "-1", CVAR_ARCHIVE|CVAR_LATCH); + + // joystick variables + in_joystick = Cvar_Get ("in_joystick", "0", CVAR_ARCHIVE|CVAR_LATCH); + in_joyBallScale = Cvar_Get ("in_joyBallScale", "0.02", CVAR_ARCHIVE); + in_debugJoystick = Cvar_Get ("in_debugjoystick", "0", CVAR_TEMP); + + joy_threshold = Cvar_Get ("joy_threshold", "0.15", CVAR_ARCHIVE); + + js_ffmult = Cvar_Get ("js_ffmult", "3.0", CVAR_ARCHIVE); // force feedback + + joy_xbutton = Cvar_Get ("joy_xbutton", "1", CVAR_ARCHIVE); // treat axis as a button + joy_ybutton = Cvar_Get ("joy_ybutton", "0", CVAR_ARCHIVE); // treat axis as a button + + IN_Startup(); +#ifndef _IMMERSION + FF_Init(); +#endif // _IMMERSION +} + + +/* +=========== +IN_Activate + +Called when the main window gains or loses focus. +The window may have been destroyed and recreated +between a deactivate and an activate. +=========== +*/ +void IN_Activate (qboolean active) { + in_appactive = active; + + if ( !active ) + { + IN_DeactivateMouse(); + } +} + + +/* +================== +IN_Frame + +Called every frame, even if not generating commands +================== +*/ +void IN_Frame (void) { + // post joystick events + IN_JoyMove(); + + if ( !s_wmv.mouseInitialized ) { + return; + } + + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { + // temporarily deactivate if not in the game and + // running on the desktop + if (Cvar_VariableIntegerValue ("r_fullscreen") == 0 ) { + IN_DeactivateMouse (); + return; + } + } + + if ( !in_appactive ) { + IN_DeactivateMouse (); + return; + } + + IN_ActivateMouse(); + + // post events to the system que + IN_MouseMove(); + +} + + +/* +=================== +IN_ClearStates +=================== +*/ +void IN_ClearStates (void) +{ + s_wmv.oldButtonState = 0; +} + + +/* +========================================================================= + +JOYSTICK + +========================================================================= +*/ + +/* +=============== +IN_StartupJoystick +=============== +*/ +void IN_StartupJoystick (void) { + int numdevs; + MMRESULT mmr; + + // assume no joystick + joy.avail = qfalse; + + if (! in_joystick->integer ) { + Com_Printf ("Joystick is not active.\n"); + return; + } + + // verify joystick driver is present + if ((numdevs = joyGetNumDevs ()) == 0) + { + Com_Printf ("joystick not found -- driver not present\n"); + return; + } + + // cycle through the joystick ids for the first valid one + mmr = 0; + for (joy.id=0 ; joy.id 1 ) { + fValue = 1; + } + return fValue; +} + +int JoyToI( int value ) { + // move centerpoint to zero + value -= 32768; + + return value; +} + +int joyDirectionKeys[16] = { + A_CURSOR_LEFT, A_CURSOR_RIGHT, + A_CURSOR_UP, A_CURSOR_DOWN, + A_JOY16, A_JOY17, + A_JOY18, A_JOY19, + A_JOY20, A_JOY21, + A_JOY22, A_JOY23, + + A_JOY24, A_JOY25, + A_JOY26, A_JOY27 +}; + +/* +=========== +IN_JoyMove +=========== +*/ +void IN_JoyMove( void ) { + float fAxisValue; + int i; + DWORD buttonstate, povstate; + int x, y; + + // verify joystick is available and that the user wants to use it + if ( !joy.avail ) { + return; + } + + // collect the joystick data, if possible + memset (&joy.ji, 0, sizeof(joy.ji)); + joy.ji.dwSize = sizeof(joy.ji); + joy.ji.dwFlags = JOY_RETURNALL; + + if ( joyGetPosEx (joy.id, &joy.ji) != JOYERR_NOERROR ) { + // read error occurred + // turning off the joystick seems too harsh for 1 read error,\ + // but what should be done? + // Com_Printf ("IN_ReadJoystick: no response\n"); + // joy.avail = false; + return; + } + + if ( in_debugJoystick->integer ) { + Com_Printf( "%8x %5i %5.2f %5.2f %5.2f %5.2f %6i %6i\n", + joy.ji.dwButtons, + joy.ji.dwPOV, + JoyToF( joy.ji.dwXpos ), JoyToF( joy.ji.dwYpos ), + JoyToF( joy.ji.dwZpos ), JoyToF( joy.ji.dwRpos ), + JoyToI( joy.ji.dwUpos ), JoyToI( joy.ji.dwVpos ) ); + } + + // loop through the joystick buttons + // key a joystick event or auxillary event for higher number buttons for each state change + buttonstate = joy.ji.dwButtons; + for ( i=0 ; i < joy.jc.wNumButtons ; i++ ) { + if ( (buttonstate & (1<integer) { + if ( fAxisValue < -joy_threshold->value || fAxisValue > joy_threshold->value){ + Sys_QueEvent( g_wv.sysMsgTime, SE_JOYSTICK_AXIS, AXIS_SIDE, (int) -(fAxisValue*127.0), 0, NULL ); + }else{ + Sys_QueEvent( g_wv.sysMsgTime, SE_JOYSTICK_AXIS, AXIS_SIDE, 0, 0, NULL ); + } + continue; + } + + if (i == 1 && !joy_ybutton->integer) { + if ( fAxisValue < -joy_threshold->value || fAxisValue > joy_threshold->value){ + Sys_QueEvent( g_wv.sysMsgTime, SE_JOYSTICK_AXIS, AXIS_FORWARD, (int) -(fAxisValue*127.0), 0, NULL ); + }else{ + Sys_QueEvent( g_wv.sysMsgTime, SE_JOYSTICK_AXIS, AXIS_FORWARD, 0, 0, NULL ); + } + continue; + } + + if ( fAxisValue < -joy_threshold->value ) { + povstate |= (1<<(i*2)); + } else if ( fAxisValue > joy_threshold->value ) { + povstate |= (1<<(i*2+1)); + } + } + + // convert POV information from a direction into 4 button bits + if ( joy.jc.wCaps & JOYCAPS_HASPOV ) { + if ( joy.ji.dwPOV != JOY_POVCENTERED ) { + if (joy.ji.dwPOV == JOY_POVFORWARD) + povstate |= 1<<12; + if (joy.ji.dwPOV == JOY_POVBACKWARD) + povstate |= 1<<13; + if (joy.ji.dwPOV == JOY_POVRIGHT) + povstate |= 1<<14; + if (joy.ji.dwPOV == JOY_POVLEFT) + povstate |= 1<<15; + } + } + + // determine which bits have changed and key an auxillary event for each change + for (i=0 ; i < 16 ; i++) { + if ( (povstate & (1<= 6 ) { + x = JoyToI( joy.ji.dwUpos ) * in_joyBallScale->value; + y = JoyToI( joy.ji.dwVpos ) * in_joyBallScale->value; + if ( x || y ) { + Sys_QueEvent( g_wv.sysMsgTime, SE_MOUSE, x, y, 0, NULL ); + } + } +} + +/* +========================================================================= + +MIDI + +========================================================================= +*/ + +static void MIDI_NoteOff( int note ) +{ + int qkey; + + qkey = note - 60 + A_AUX0; + + if ( qkey < A_AUX0 ) + { + return; + } + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, qkey, qfalse, 0, NULL ); +} + +static void MIDI_NoteOn( int note, int velocity ) +{ + int qkey; + + if ( velocity == 0 ) + { + MIDI_NoteOff( note ); + } + + qkey = note - 60 + A_AUX0; + + if ( qkey < A_AUX0 ) + { + return; + } + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, qkey, qtrue, 0, NULL ); +} + +static void CALLBACK MidiInProc( HMIDIIN hMidiIn, UINT uMsg, DWORD dwInstance, + DWORD dwParam1, DWORD dwParam2 ) +{ + int message; + + switch ( uMsg ) + { + case MIM_OPEN: + break; + case MIM_CLOSE: + break; + case MIM_DATA: + message = dwParam1 & 0xff; + + // note on + if ( ( message & 0xf0 ) == 0x90 ) + { + if ( ( ( message & 0x0f ) + 1 ) == in_midichannel->integer ) + MIDI_NoteOn( ( dwParam1 & 0xff00 ) >> 8, ( dwParam1 & 0xff0000 ) >> 16 ); + } + else if ( ( message & 0xf0 ) == 0x80 ) + { + if ( ( ( message & 0x0f ) + 1 ) == in_midichannel->integer ) + MIDI_NoteOff( ( dwParam1 & 0xff00 ) >> 8 ); + } + break; + case MIM_LONGDATA: + break; + case MIM_ERROR: + break; + case MIM_LONGERROR: + break; + } + +// Sys_QueEvent( sys_msg_time, SE_KEY, wMsg, qtrue, 0, NULL ); +} + +static void MidiInfo_f( void ) +{ + int i; + + const char *enableStrings[] = { "disabled", "enabled" }; + + Com_Printf( "\nMIDI control: %s\n", enableStrings[in_midi->integer != 0] ); + Com_Printf( "port: %d\n", in_midiport->integer ); + Com_Printf( "channel: %d\n", in_midichannel->integer ); + Com_Printf( "current device: %d\n", in_mididevice->integer ); + Com_Printf( "number of devices: %d\n", s_midiInfo.numDevices ); + for ( i = 0; i < s_midiInfo.numDevices; i++ ) + { + if ( i == Cvar_VariableIntegerValue( "in_mididevice" ) ) + Com_Printf( "***" ); + else + Com_Printf( "..." ); + Com_Printf( "device %2d: %s\n", i, s_midiInfo.caps[i].szPname ); + Com_Printf( "...manufacturer ID: 0x%hx\n", s_midiInfo.caps[i].wMid ); + Com_Printf( "...product ID: 0x%hx\n", s_midiInfo.caps[i].wPid ); + + Com_Printf( "\n" ); + } +} + +static void IN_StartupMIDI( void ) +{ + int i; + + if ( !Cvar_VariableIntegerValue( "in_midi" ) ) + return; + + // + // enumerate MIDI IN devices + // + s_midiInfo.numDevices = midiInGetNumDevs(); + + for ( i = 0; i < s_midiInfo.numDevices; i++ ) + { + midiInGetDevCaps( i, &s_midiInfo.caps[i], sizeof( s_midiInfo.caps[i] ) ); + } + + // + // open the MIDI IN port + // + if ( midiInOpen( &s_midiInfo.hMidiIn, + in_mididevice->integer, + ( unsigned long ) MidiInProc, + ( unsigned long ) NULL, + CALLBACK_FUNCTION ) != MMSYSERR_NOERROR ) + { + Com_Printf( "WARNING: could not open MIDI device %d: '%s'\n", in_mididevice->integer , s_midiInfo.caps[( int ) in_mididevice->value] ); + return; + } + + midiInStart( s_midiInfo.hMidiIn ); +} + +static void IN_ShutdownMIDI( void ) +{ + if ( s_midiInfo.hMidiIn ) + { + midiInClose( s_midiInfo.hMidiIn ); + } + memset( &s_midiInfo, 0, sizeof( s_midiInfo ) ); +} diff --git a/code/win32/win_input.h b/code/win32/win_input.h new file mode 100644 index 0000000..9348f4b --- /dev/null +++ b/code/win32/win_input.h @@ -0,0 +1,101 @@ +#ifndef _WIN_INPUT_H_ +#define _WIN_INPUT_H_ + +bool IN_ControllersChanged(int inserted[], int removed[]); + +#if defined (_XBOX ) || defined (_GAMECUBE) +#define _USE_RUMBLE +#endif + +bool IN_AnyButtonPressed(void); + +void IN_enableRumble( void ); +void IN_disableRumble( void ); +bool IN_usingRumble( void ); + +int IN_CreateRumbleScript(int controller, int numStates, bool deleteWhenFinished); +void IN_DeleteRumbleScript(int whichScript); +void IN_KillRumbleScript(int whichScript); +void IN_ExecuteRumbleScript(int whichScript); + +bool IN_AdvanceToNextState(int whichScript); + +void IN_KillRumbleScripts(int controller); +void IN_KillRumbleScripts( void ); + +#define IN_CMD_GOTO_XTIMES -5 +#define IN_CMD_GOTO -6 + +#define IN_CMD_DEC_ARG2 -7 +#define IN_CMD_INC_ARG2 -8 +#define IN_CMD_DEC_ARG1 -9 +#define IN_CMD_INC_ARG1 -10 + +#ifdef _XBOX + #define IN_CMD_DEC_LEFT -70 + #define IN_CMD_DEC_RIGHT -71 + #define IN_CMD_INC_LEFT -72 + #define IN_CMD_INC_RIGHT -73 +#endif + + +#if defined (_XBOX) // ----- XBOX -------- + +int IN_AddRumbleState(int whichScript, int leftSpeed, int rightSpeed, int timeInMs); +int IN_AddEffectFade4(int whichScript, int startLeft, int startRight, int endLeft, int endRight, int timeInMs); +int IN_AddEffectFadeExp6(int whichScript, int startLeft, int startRight, int endLeft, int endRight, char factor, int timeInMs); + +#elif defined (_GAMECUBE) // ---- GAME CUBE ---- + +#define IN_GCACTION_START 1 +#define IN_GCACTION_STOP 2 +#define IN_GCACTION_STOPHARD 3 +int IN_AddRumbleState(int whichScript, int action, int timeInMs, int arg = 0); + +#endif // ------END IF------- + +int IN_AddRumbleStateSpecial(int whichScript, int action, int arg1, int arg2); +void IN_KillRumbleState(int whichScript, int index); + +void IN_PauseRumbling(int controller); +void IN_PauseRumbling( void ); + +void IN_UnPauseRumbling(int controller); +void IN_UnPauseRumbling( void ); + +void IN_TogglePauseRumbling(int controller); +void IN_TogglePauseRumbling( void ); +int IN_GetMainController(); +void IN_SetMainController(int id); + +void IN_PadUnplugged(int controller); +void IN_PadPlugged(int controller); + +void IN_CommonJoyPress(int controller, fakeAscii_t button, bool pressed); +void IN_CommonUpdate(void); + +#define IN_MAX_JOYSTICKS 2 +// Stores gamepad joystick info +struct JoystickInfo +{ + bool valid; + float x, y; +}; + +// Stores gamepad id and joysick info +struct PadInfo +{ + JoystickInfo joyInfo[2]; + int padId; +}; + +// Buffer for gamepad info +extern PadInfo _padInfo; + +bool IN_RumbleAdjust(int controller, int left, int right); +void IN_RumbleInit (void); +void IN_RumbleShutdown (void); +void IN_RumbleFrame (void); + + +#endif // END _WIN_INPUT_H_ diff --git a/code/win32/win_input_console.cpp b/code/win32/win_input_console.cpp new file mode 100644 index 0000000..42d2f24 --- /dev/null +++ b/code/win32/win_input_console.cpp @@ -0,0 +1,900 @@ +// #include "../server/exe_headers.h" + +#include "../client/client.h" +//JLF +#include "../client/client_ui.h" + + +#include "../qcommon/qcommon.h" +#ifdef _JK2MP +#include "../ui/keycodes.h" +#else +#include "../client/keycodes.h" +#endif + +#include "win_local.h" +#include "win_input.h" + +cvar_t *inSplashMenu = NULL; +cvar_t *controllerOut = NULL; + +static void HandleDebugJoystickPress(fakeAscii_t button); + +static bool _UIRunning = false; + +static bool IN_ControllerMustBePlugged(int controller); + +// By default, cheatpad is turned on except in final build. Just change +// here to modify that. +#ifndef FINAL_BUILD +//#define DEBUG_CONTROLLER +#endif + +// Controller connection globals +static signed char uiControllerNotification = -1; +bool noControllersConnected = false; +bool wasPlugged[4]; + +int mainControllerDelayedUnplug = 0; + +PadInfo _padInfo; // gamepad thumbstick buffer + +/********************************************************** +* +* CHEAT FUNCTIONS +* +**********************************************************/ + +bool Cheat_God( void ) +{ + Cbuf_ExecuteText( EXEC_APPEND, "god\n" ); + return true; +} + +void Cheat_GiveAll( void ) +{ + Cbuf_ExecuteText(EXEC_APPEND, "give all\n"); +} +extern bool Cheat_ChangeSaber( void ); // In wp_saber.cpp + +#include "../game/anims.h" +#include "../cgame/cg_local.h" +static bool isDeathAnimation( int anim ) +{ + switch( anim ) + { + case BOTH_DEATH1: //# First Death anim + case BOTH_DEATH2: //# Second Death anim + case BOTH_DEATH3: //# Third Death anim + case BOTH_DEATH4: //# Fourth Death anim + case BOTH_DEATH5: //# Fifth Death anim + case BOTH_DEATH6: //# Sixth Death anim + case BOTH_DEATH7: //# Seventh Death anim + case BOTH_DEATH8: //# + case BOTH_DEATH9: //# + case BOTH_DEATH10: //# + case BOTH_DEATH11: //# + case BOTH_DEATH12: //# + case BOTH_DEATH13: //# + case BOTH_DEATH14: //# + case BOTH_DEATH14_UNGRIP: //# Desann's end death (cin #35) + case BOTH_DEATH14_SITUP: //# Tavion sitting up after having been thrown (cin #23) + case BOTH_DEATH15: //# + case BOTH_DEATH16: //# + case BOTH_DEATH17: //# + case BOTH_DEATH18: //# + case BOTH_DEATH19: //# + case BOTH_DEATH20: //# + case BOTH_DEATH21: //# + case BOTH_DEATH22: //# + case BOTH_DEATH23: //# + case BOTH_DEATH24: //# + case BOTH_DEATH25: //# + case BOTH_DEATHFORWARD1: //# First Death in which they get thrown forward + case BOTH_DEATHFORWARD2: //# Second Death in which they get thrown forward + case BOTH_DEATHFORWARD3: //# Tavion's falling in cin# 23 + case BOTH_DEATHBACKWARD1: //# First Death in which they get thrown backward + case BOTH_DEATHBACKWARD2: //# Second Death in which they get thrown backward + case BOTH_DEATH1IDLE: //# Idle while close to death + case BOTH_LYINGDEATH1: //# Death to play when killed lying down + case BOTH_STUMBLEDEATH1: //# Stumble forward and fall face first death + case BOTH_FALLDEATH1: //# Fall forward off a high cliff and splat death - start + case BOTH_FALLDEATH1INAIR: //# Fall forward off a high cliff and splat death - loop + case BOTH_FALLDEATH1LAND: //# Fall forward off a high cliff and splat death - hit bottom + case BOTH_DEAD1: //# First Death finished pose + case BOTH_DEAD2: //# Second Death finished pose + case BOTH_DEAD3: //# Third Death finished pose + case BOTH_DEAD4: //# Fourth Death finished pose + case BOTH_DEAD5: //# Fifth Death finished pose + case BOTH_DEAD6: //# Sixth Death finished pose + case BOTH_DEAD7: //# Seventh Death finished pose + case BOTH_DEAD8: //# + case BOTH_DEAD9: //# + case BOTH_DEAD10: //# + case BOTH_DEAD11: //# + case BOTH_DEAD12: //# + case BOTH_DEAD13: //# + case BOTH_DEAD14: //# + case BOTH_DEAD15: //# + case BOTH_DEAD16: //# + case BOTH_DEAD17: //# + case BOTH_DEAD18: //# + case BOTH_DEAD19: //# + case BOTH_DEAD20: //# + case BOTH_DEAD21: //# + case BOTH_DEAD22: //# + case BOTH_DEAD23: //# + case BOTH_DEAD24: //# + case BOTH_DEAD25: //# + case BOTH_DEADFORWARD1: //# First thrown forward death finished pose + case BOTH_DEADFORWARD2: //# Second thrown forward death finished pose + case BOTH_DEADBACKWARD1: //# First thrown backward death finished pose + case BOTH_DEADBACKWARD2: //# Second thrown backward death finished pose + case BOTH_LYINGDEAD1: //# Killed lying down death finished pose + case BOTH_STUMBLEDEAD1: //# Stumble forward death finished pose + case BOTH_FALLDEAD1LAND: //# Fall forward and splat death finished pose + case BOTH_DEADFLOP1: //# React to being shot from First Death finished pose + case BOTH_DEADFLOP2: //# React to being shot from Second Death finished pose + case BOTH_DISMEMBER_HEAD1: //# + case BOTH_DISMEMBER_TORSO1: //# + case BOTH_DISMEMBER_LLEG: //# + case BOTH_DISMEMBER_RLEG: //# + case BOTH_DISMEMBER_RARM: //# + case BOTH_DISMEMBER_LARM: //# + case BOTH_DEATH_ROLL: //# Death anim from a roll + case BOTH_DEATH_FLIP: //# Death anim from a flip + case BOTH_DEATH_SPIN_90_R: //# Death anim when facing 90 degrees right + case BOTH_DEATH_SPIN_90_L: //# Death anim when facing 90 degrees left + case BOTH_DEATH_SPIN_180: //# Death anim when facing backwards + case BOTH_DEATH_LYING_UP: //# Death anim when lying on back + case BOTH_DEATH_LYING_DN: //# Death anim when lying on front + case BOTH_DEATH_FALLING_DN: //# Death anim when falling on face + case BOTH_DEATH_FALLING_UP: //# Death anim when falling on back + case BOTH_DEATH_CROUCHED: //# Death anim when crouched + return true; + break; + default: + return false; + break; + } +} + +bool Cheat_WinLevel( void ) +{ + // Do not do this while any UI is active. It's dangerous: + extern int Key_GetCatcher( void ); + if( Key_GetCatcher() == KEYCATCH_UI ) + return false; + + // Also, don't do it during cutscenes: + extern bool in_camera; + if( in_camera || ( g_entities[0].client && isDeathAnimation(g_entities[0].client->ps.legsAnim))) + return false; + + + // All maps (except kor2) have a trigger item named end_level + Cbuf_ExecuteText( EXEC_APPEND, "use end_level\n" ); + + return true; +} + +#ifndef XBOX_DEMO +bool Cheat_LevelSelect( void ) +{ + // Set cvar that enables the level select menu: + Cvar_SetValue( "ui_levelselect", 1 ); + + return true; +} +#endif + +extern bool Cheat_InfiniteForce( void ); // In wp_saber.cpp + +#if YELLOW_MODE +extern bool enableYellowMode; +char yellowModeLevel = 0; +bool enableYellowStage2 = false; + +bool Cheat_Yellow_Stage2( void ) +{ + if(!enableYellowStage2) + return false; + + if(IN_GetMainController() != 0) + return false; + + if( g_entities[0].client && isDeathAnimation(g_entities[0].client->ps.legsAnim)) + enableYellowMode = true; + else + enableYellowMode = false; + + return enableYellowMode; +} + +bool Cheat_Yellow_Stage1( void ) +{ + if(IN_GetMainController() != 1) + return false; + + if(yellowModeLevel == 0) // step one, in a cutscene + { + extern bool in_camera; + if(in_camera) + { + yellowModeLevel++; + return true; + } + } + else if( yellowModeLevel == 1 ) // step two, during the splash screen + { + if(inSplashMenu->integer) + { + yellowModeLevel++; + return true; + } + } + else if( yellowModeLevel == 2 ) // step three, while force hud is active + { + if(cg.forceHUDActive) + { + yellowModeLevel++; + return true; + } + } + + if(yellowModeLevel == 3) // eveything is ok, now reset and move to stage 2 + { + enableYellowStage2 = true; + yellowModeLevel = 4; + return true; + } + + return false; +} + +#endif // YELLOW_MODE + +bool Cheat_AllForce( void ) +{ + // Do not do this while the force config UI is running. That causes SERIOUS problems: + extern bool UI_ForceConfigUIActive( void ); + if( UI_ForceConfigUIActive() ) + return false; + + // Set all Light powers to level 3: + Cbuf_ExecuteText( EXEC_APPEND, "setForceHeal 3\nsetMindTrick 3\nsetForceProtect 3\nsetForceAbsorb 3\n" ); + // Set all Dark powers to level 3: + Cbuf_ExecuteText( EXEC_APPEND, "setForceGrip 3\nsetForceLightning 3\nsetForceRage 3\nsetForceDrain 3\n" ); + + return true; +} + +bool Cheat_KungFoo( void ) +{ + Cbuf_ExecuteText( EXEC_APPEND, "iknowkungfu\n"); + return qtrue; +} + +// +// CHEAT DATA +// +// Every cheat is a sequence of D-pad presses, performed while the right +// thumbstick is being pressed. The cheat sequences are all length 6, and +// each cheat is tied to a void (void) function. Mapping: +// 5 - Up, 7 - Down, 8 - Left, 6 - Right +typedef bool(*xcommandC_t)(void); +struct cheat_t +{ + fakeAscii_t buttons[6]; + xcommandC_t function; +}; + +cheat_t cheats[] = { + { {A_JOY7, A_JOY5, A_JOY8, A_JOY6, A_JOY7, A_JOY5}, Cheat_God }, + //{ {A_JOY6, A_JOY6, A_JOY8, A_JOY8, A_JOY7, A_JOY5}, Cheat_GiveAll }, + { {A_JOY7, A_JOY8, A_JOY5, A_JOY7, A_JOY6, A_JOY5}, Cheat_ChangeSaber }, + { {A_JOY5, A_JOY5, A_JOY7, A_JOY7, A_JOY8, A_JOY6}, Cheat_WinLevel }, +#ifndef XBOX_DEMO + { {A_JOY6, A_JOY8, A_JOY6, A_JOY7, A_JOY6, A_JOY5}, Cheat_LevelSelect }, +#endif + { {A_JOY5, A_JOY7, A_JOY5, A_JOY8, A_JOY5, A_JOY6}, Cheat_InfiniteForce }, + { {A_JOY8, A_JOY7, A_JOY6, A_JOY5, A_JOY7, A_JOY7}, Cheat_AllForce }, +// { {A_JOY8, A_JOY7, A_JOY6, A_JOY5, A_JOY7, A_JOY8}, Cheat_KungFoo }, +#if YELLOW_MODE + { {A_JOY5, A_JOY7, A_JOY5, A_JOY8, A_JOY6, A_JOY5}, Cheat_Yellow_Stage1 }, + { {A_JOY5, A_JOY6, A_JOY8, A_JOY5, A_JOY7, A_JOY7}, Cheat_Yellow_Stage2 }, +#endif // YELLOW_MODE +}; + +const int numCheats = sizeof(cheats) / sizeof(cheats[0]); + +bool enteringCheat = false; +int cheatLength = 0; +fakeAscii_t curCheat[6]; + + +//If the Xbox white or black button was held for less than this amount of +//time while a selection bar was up, the user wants to use the button rather +//than reassign it. +#define MAX_WB_HOLD_TIME 500 + +static fakeAscii_t UIJoy2Key(fakeAscii_t button) +{ + switch(button) { + // D-Pad + case A_JOY7: + return A_CURSOR_DOWN; + case A_JOY5: + return A_CURSOR_UP; + case A_JOY6: + return A_CURSOR_RIGHT; + case A_JOY8: + return A_CURSOR_LEFT; + + // A and B + case A_JOY15: + return A_MOUSE1; + case A_JOY14: + return A_ESCAPE; + + // X and Y + case A_JOY16: + return A_DELETE; + case A_JOY13: + return A_BACKSPACE; + + // L and R triggers + case A_JOY11: + return A_PAGE_UP; + case A_JOY12: + return A_PAGE_DOWN; + + // Start and Back + case A_JOY4: + return A_MOUSE1; + case A_JOY1: + return A_ESCAPE; + + // White and Black + case A_JOY9: + return A_HOME; + case A_JOY10: + return A_END; + + // Left and Right thumbstick - do nothing in the UI + case A_JOY2: + return A_SPACE; + case A_JOY3: + return A_SPACE; + } + + return A_SPACE; //Invalid button. +} + +struct +{ + int button; + bool pressed; +} uiKeyQueue[2][5] = {0}; +int uiQueueLen[2] = {0}; +int uiLastKeyUpDown[2] = {0}; +int uiLastKeyLeftRight[2] = {0}; + +void IN_UIEmptyQueue() +{ + /// If the ui is not running then this doesn't have any effect + if (!_UIRunning) + { + uiQueueLen[0] = uiQueueLen[1] = 0; + return; + } + +// BTO - No CM, bypass that logic. +// for (int i = 0; i < ClientManager::NumClients(); i++) + for (int i = 0; i < 1; ++i) + { +// ClientManager::ActivateClient(i); + int found = 0; + int bCancel = 0; + for (int j = 0; j < uiQueueLen[i]; j++) + { + switch (uiKeyQueue[i][j].button) + { + case A_CURSOR_DOWN: + case A_CURSOR_UP: + if ( found & 2 ) // Was a left/right key pressed already? + bCancel = 1; + found |= 1; + break; + case A_CURSOR_RIGHT: + case A_CURSOR_LEFT: + if ( found & 1 ) // Was an up/down key already pressed? + bCancel = 1; + found |= 2; + break; + } + } + + if (!bCancel) // was it cancelled? + { + for (int j = 0; j < uiQueueLen[i]; j++) + { + int time = Sys_Milliseconds(); + switch (uiKeyQueue[i][j].button) + { + case A_CURSOR_DOWN: + case A_CURSOR_UP: + if (uiLastKeyLeftRight[i]) + { + if (uiLastKeyLeftRight[i] > time) // don't allow up/down till left/right has enough leway time + { + continue; + } + } + uiLastKeyUpDown[i] = time + 150; /// 250 ms sound right? + break; + case A_CURSOR_LEFT: + case A_CURSOR_RIGHT: + if (uiLastKeyUpDown[i]) + { + if (uiLastKeyUpDown[i] > time) // don't allow up/down till left/right has enough leway time + { + continue; + } + } + uiLastKeyLeftRight[i] = time + 150; /// 250 ms sound right? + break; + } + Sys_QueEvent(0, SE_KEY, uiKeyQueue[i][j].button, uiKeyQueue[i][j].pressed, 0, NULL); + } + } + } + + // Reset the queue + uiQueueLen[0] = uiQueueLen[1] = 0; +} + +// extern void G_DemoKeypress(); +// extern void CG_SkipCredits(void); +char lastControllerUsed = 0; + +void IN_CommonJoyPress(int controller, fakeAscii_t button, bool pressed) +{ +#ifdef XBOX_DEMO + // Reset the demo timer so that we don't auto-reboot to CDX + extern void Demo_TimerKeypress( void ); + Demo_TimerKeypress(); +#endif + + lastControllerUsed = controller; + + // Cheat system hooks. The right thumbstick button has to be held for a cheat: + if (button == A_JOY3) + { + if (pressed) + { + // Just pressed the right thumstick in. Reset cheat detector + enteringCheat = true; + cheatLength = 0; + } + else + { + enteringCheat = false; + if (cheatLength == 6) + { + for( int i = 0; i < numCheats; ++i) + { + if( memcmp( &cheats[i].buttons[0], &curCheat[0], sizeof(curCheat) ) == 0 ) + { + if(cheats[i].function()) + S_StartLocalSound( S_RegisterSound( "sound/vehicles/x-wing/s-foil" ), CHAN_AUTO ); + } + } + } + } + } + else if (enteringCheat && pressed) + { + // Handle all other buttons while entering a cheat + if (cheatLength == 6 || (button != A_JOY5 && button != A_JOY6 && button != A_JOY7 && button != A_JOY8)) + { + // If we press too many buttons, or anything but the D-pad, cancel entry: + enteringCheat = false; + cheatLength = 0; + } + else + { + // We pressed a d-pad button, we're entering a cheat, and there's still room + curCheat[cheatLength++] = button; + } + } + + // Check for special cases for map hack +#ifndef FINAL_BUILD + if (Cvar_VariableIntegerValue("cl_maphack")) + { + if (_UIRunning && button == A_JOY11 && pressed) + { + // Left trigger -> F1 + Sys_QueEvent( 0, SE_KEY, A_F1, pressed, 0, NULL ); + return; + } + else if (_UIRunning && button == A_JOY12 && pressed) + { + // Right trigger -> F2 + Sys_QueEvent( 0, SE_KEY, A_F2, pressed, 0, NULL ); + return; + } + else if (_UIRunning && button == A_JOY4 && pressed) + { + // Start button -> F3 + //IN_SetMainController(controller); + Sys_QueEvent( 0, SE_KEY, A_F3, pressed, 0, NULL ); + return; + } + } +#endif + + if(inSplashMenu->integer) + { + // START always works, A only works if the popup isn't shown: + if(button == A_JOY4 || (button == A_JOY15 && controllerOut->integer < 0)) + { + Sys_QueEvent( 0, SE_KEY, _UIRunning ? UIJoy2Key(button) : button, pressed, 0, NULL ); + } + return; + } + + int controllerout = controllerOut->integer; + if(controllerout != -1) + { + if(controllerout == controller && (button == A_JOY4))// || button == A_JOY15)) + Sys_QueEvent( 0, SE_KEY, _UIRunning ? UIJoy2Key(button) : button, pressed, 0, NULL ); + return; + } + + if(IN_GetMainController() == controller ) + { + // Always map start button to ESCAPE + if (!_UIRunning && button == A_JOY4 && cls.state != CA_CINEMATIC) + Sys_QueEvent( 0, SE_KEY, A_ESCAPE, pressed, 0, NULL ); + +#ifdef DEBUG_CONTROLLER + if (controller != 3) +#endif + Sys_QueEvent( 0, SE_KEY, _UIRunning ? UIJoy2Key(button) : button, pressed, 0, NULL ); + } + +#ifdef DEBUG_CONTROLLER + if (controller == 3 && pressed) + { + HandleDebugJoystickPress(button); + return; + } +#endif +//JLF + +} + +qboolean g_noCheckAxis = qfalse; + +/********** +IN_CommonUpdate +Updates thumbstick events based on _padInfo and ui_thumbStickMode +**********/ +void IN_CommonUpdate() +{ + extern int Key_GetCatcher( void ); + _UIRunning = Key_GetCatcher() == KEYCATCH_UI; + + // Even in the UI, only the main controller should be able to scroll: + if( _UIRunning && _padInfo.padId == IN_GetMainController() ) + { + Sys_QueEvent( 0, + SE_MOUSE, + (_padInfo.joyInfo[0].x + _padInfo.joyInfo[1].x) * 4.0f, + (_padInfo.joyInfo[0].y + _padInfo.joyInfo[1].y) * -4.0f, + 0, + NULL ); + } + else if(_padInfo.padId == IN_GetMainController()) + { + // Find out how to configure the thumbsticks + //int thumbStickMode = Cvar_Get("ui_thumbStickMode", "0" , 0)->integer; + int thumbStickMode = cl_thumbStickMode->integer; + + switch(thumbStickMode) + { + case 0: + // Configure left thumbstick to move forward/back & strafe left/right + Sys_QueEvent( 0, SE_JOYSTICK_AXIS, AXIS_SIDE, _padInfo.joyInfo[0].x * 127.0f, 0, NULL ); + Sys_QueEvent( 0, SE_JOYSTICK_AXIS, AXIS_FORWARD, _padInfo.joyInfo[0].y * 127.0f, 0, NULL ); + + // Configure right thumbstick for freelook + Sys_QueEvent( 0, SE_MOUSE, _padInfo.joyInfo[1].x * 48.0f, _padInfo.joyInfo[1].y * 48.0f, 0, NULL ); + break; + case 1: + // Configure left thumbstick for freelook + Sys_QueEvent( 0, SE_MOUSE, _padInfo.joyInfo[0].x * 48.0f, _padInfo.joyInfo[0].y * 48.0f, 0, NULL ); + + // Configure right thumbstick to move forward/back & strafe left/right + Sys_QueEvent( 0, SE_JOYSTICK_AXIS, AXIS_SIDE, _padInfo.joyInfo[1].x * 127.0f, 0, NULL ); + Sys_QueEvent( 0, SE_JOYSTICK_AXIS, AXIS_FORWARD, _padInfo.joyInfo[1].y * 127.0f, 0, NULL ); + break; + case 2: + // Configure left thumbstick to move forward/back & turn left/right + Sys_QueEvent( 0, SE_JOYSTICK_AXIS, AXIS_FORWARD, _padInfo.joyInfo[0].y * 127.0f, 0, NULL ); + Sys_QueEvent( 0, SE_MOUSE, _padInfo.joyInfo[0].x * 48.0f, 0.0f, 0, NULL ); + + // Configure right thumbstick to look up/down & strafe left/right + Sys_QueEvent( 0, SE_JOYSTICK_AXIS, AXIS_SIDE, _padInfo.joyInfo[1].x * 127.f, 0, NULL ); + Sys_QueEvent( 0, SE_MOUSE, 0.0f, _padInfo.joyInfo[1].y * 48.0f, 0, NULL ); + break; + case 3: + // Configure left thumbstick to look up/down & strafe left/right + Sys_QueEvent( 0, SE_JOYSTICK_AXIS, AXIS_SIDE, _padInfo.joyInfo[0].x * 127.f, 0, NULL ); + Sys_QueEvent( 0, SE_MOUSE, 0.0f, _padInfo.joyInfo[0].y * 48.0f, 0, NULL ); + + // Configure right thumbstick to move forward/back & turn left/right + Sys_QueEvent( 0, SE_JOYSTICK_AXIS, AXIS_FORWARD, _padInfo.joyInfo[1].y * 127.0f, 0, NULL ); + Sys_QueEvent( 0, SE_MOUSE, _padInfo.joyInfo[1].x * 48.0f, 0.0f, 0, NULL ); + break; + default: + break; + } + } +} + +void startsetMainController(int controller) +{ + + IN_SetMainController(controller); + if ( !wasPlugged[controller]) + { + mainControllerDelayedUnplug = 1 << controller; + } +} + +/********* +IN_DisplayControllerUnplugged +*********/ + +static void IN_DisplayControllerUnplugged(int controller) +{ + uiControllerNotification = controller; + + bool noControllersConnected = !wasPlugged[0] && + !wasPlugged[1] && + !wasPlugged[2] && + !wasPlugged[3]; + + if ( !( cls.keyCatchers & KEYCATCH_UI ) ) + { + if ( cls.state == CA_ACTIVE ) + { + if (controller == IN_GetMainController()) + { + Cvar_SetValue("ControllerOutNum", controller); + UI_SetActiveMenu( "ingame","noController" ); + } + } + } + else // UI + { + if(inSplashMenu->integer && noControllersConnected) + { + Cvar_SetValue("ControllerOutNum", 4); + UI_SetActiveMenu("ui_popup", "noController"); + } + else if( controller == IN_GetMainController()) + { + Cvar_SetValue("ControllerOutNum", controller); + UI_SetActiveMenu("ui_popup", "noController"); + } + } +// END JLF + +} + +/********* +IN_ClearControllerUnplugged +*********/ +static void IN_ClearControllerUnplugged(void) +{ + uiControllerNotification = -1; + + //TODO Add a call to the UI that removes the controller disconnected + // message from the screen. +// VM_Call( uivm, UI_CONTROLLER_UNPLUGGED, false, 0); +} + +qboolean CurrentStateIsInteractive() +{ + if (cls.state == CA_UNINITIALIZED || + cls.state ==CA_CONNECTING|| + cls.state ==CA_CONNECTED|| + cls.state ==CA_CHALLENGING|| + cls.state ==CA_PRIMED|| + cls.state ==CA_CINEMATIC || + !SG_GameAllowedToSaveHere(qtrue)) + return qfalse; + return qtrue; +} + +// Magic flag used to avoid popping up the "no controllers" dialog if +// none were present when we booted (but not from MP) +bool hadAController = false; + +/********* +IN_ControllerMustBePlugged +*********/ +static bool IN_ControllerMustBePlugged(int controller) +{ + if( cls.state == CA_LOADING || + cls.state == CA_CONNECTING || + cls.state == CA_CONNECTED || + cls.state == CA_CHALLENGING || + cls.state == CA_PRIMED || + cls.state == CA_CINEMATIC) + { + return false; + } + + // If we're at the splash screen, have no controllers anymore, and there + // was a controller ever inserted into the machine: + extern bool Sys_QuickStart(); + if( inSplashMenu->integer && + !wasPlugged[0] && !wasPlugged[1] && + !wasPlugged[2] && !wasPlugged[3] && + hadAController ) + return true; + + // If we're at the splash screen, and anything else above is false + // (we have another controller, or there's never been a controller): + if( inSplashMenu->integer ) + return false; + + // OK. In all other cases, we need the main controller: + return (controller == IN_GetMainController()); +} + + +/********* +IN_PadUnplugged +*********/ +void IN_PadUnplugged(int controller) +{ + if(wasPlugged[controller]) + { + Com_Printf("\tController %d unplugged\n",controller); + } +//JLF moved + wasPlugged[controller] = false; + + //IN_CheckForNoControllers(); + + if(IN_ControllerMustBePlugged(controller)&& SG_GameAllowedToSaveHere(qtrue)) + { + //If UI isn't busy, inform it about controller loss. + if(uiControllerNotification == -1 && Cvar_VariableIntegerValue("ControllerOutNum")<0) + { + IN_DisplayControllerUnplugged(controller); + mainControllerDelayedUnplug &= ~( 1<< controller); + } +// else +// mainControllerDelayedUnplug = 1 << controller; + + } + else + { + if ( controller == IN_GetMainController()) + { + //store somehow for checking again later + mainControllerDelayedUnplug = 1 << controller; + } + } +// wasPlugged[controller] = false; + + + +} + +/********* +IN_PadPlugged +*********/ +void IN_PadPlugged(int controller) +{ + if(!wasPlugged[controller]) + { + Com_Printf("\tController %d plugged\n",controller); + } + + if(IN_ControllerMustBePlugged(controller)&& SG_GameAllowedToSaveHere(qtrue)) + { + //If UI is dealing with this controller, tell it to stop. + if(uiControllerNotification == controller || (_UIRunning && cls.state != CA_ACTIVE )) + { + IN_ClearControllerUnplugged(); + } + } + else + { + if (controller == IN_GetMainController()) + { + //store somehow for checking again later + mainControllerDelayedUnplug &= ~(1 << controller); + } + } + + wasPlugged[controller] = true; + noControllersConnected = false; + hadAController = true; +} + +/********* +IN_GetMainController +*********/ +int IN_GetMainController(void) +{ + return cls.mainGamepad; +} + +/********* +IN_SetMainController +*********/ +void IN_SetMainController(int id) +{ + cls.mainGamepad = id; +} + +/********************************************************** +* +* DEBUGGING CODE +* +**********************************************************/ + +#ifdef DEBUG_CONTROLLER +static void HandleDebugJoystickPress(fakeAscii_t button) +{ + switch(button) { + case A_JOY13: // Right pad up (yellow) + Cbuf_ExecuteText(EXEC_APPEND, "give all\n"); + break; + case A_JOY16: // Right pad left (blue) + Cbuf_ExecuteText(EXEC_APPEND, "viewpos\n"); + break; + case A_JOY14: // Right pad right (red) + Cbuf_ExecuteText(EXEC_APPEND, "noclip\n"); + break; + case A_JOY15: // Right pad down (green) + Cbuf_ExecuteText(EXEC_APPEND, "god\n"); + break; + case A_JOY4: // Start + break; + case A_JOY1: // back + break; + case A_JOY2: // Left thumbstick + extern void Z_CompactStats(void); + Z_CompactStats(); + break; + case A_JOY12: // Upper right trigger + break; + case A_JOY8: // Left pad left + break; + case A_JOY6: // Left pad right + break; + case A_JOY5: // Left pad up + break; + case A_JOY7: // Left pad down + break; + case A_JOY11: // Upper left trigger + break; + case A_JOY9: // White button + break; + case A_JOY10: // Black button + break; + } +} + +#endif + diff --git a/code/win32/win_input_rumble.cpp b/code/win32/win_input_rumble.cpp new file mode 100644 index 0000000..8d5e1f5 --- /dev/null +++ b/code/win32/win_input_rumble.cpp @@ -0,0 +1,686 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ +#include "../cgame/cg_local.h" +#include "../server/exe_headers.h" + +#include "win_local.h" +#include "win_input.h" + + +//MB #include "../client/cl_data.h" +#include "../game/q_shared.h" + +extern qboolean G_ActivePlayerNormal(void); +static int rumble_timer = 0; + +cvar_t* in_shaking_rumble; // 1 - shaking rumble on, 0 - shaking rumble off + +struct rumblestate_t +{ + int timeToStop; + + // Right motor speed on Xbox, action type on Gamecube + int arg1; + + // Left motor speed on Xbox, secondary action type on Gamecube + int arg2; +}; + +struct rumblestate_special_t +{ + int code; + int arg1; + int arg2; +}; + +struct rumblescript_t +{ + int nextStateAt; + + int controller; + + int currentState; + int usedStates; + int numStates; + + bool autoDelete; + rumblestate_t *states; +}; + +struct rumblestatus_t +{ + bool changed; + bool killed; + bool paused; + int timePaused; +}; + +#define MAX_RUMBLE_STATES 10 +#define MAX_RUMBLE_SCRIPTS 10 +#define MAX_RUMBLE_CONTROLLERS 4 + +// In rumblestate, highest speed for each side takes precidence +// Number of rumble states is fairly small, so a plain array will work fine +static rumblestatus_t rumbleStatus[MAX_RUMBLE_CONTROLLERS]; +static rumblescript_t rumbleScripts[MAX_RUMBLE_SCRIPTS]; + +cvar_t* in_useRumble = NULL; + +bool IN_usingRumble( void ) +{ + return in_useRumble->integer; +} + + +// Creates a rumble script with numStates +// Returns -1 on no more room, otherwise an identifier to use for scripts +int IN_CreateRumbleScript(int controller, int numStates, bool deleteWhenFinished) +{ + if (!IN_usingRumble()) return -1; + + if (controller <= -1 || controller >= MAX_RUMBLE_CONTROLLERS) return -1; + assert (numStates > 0 && numStates < MAX_RUMBLE_STATES); + + int i; + for (i = 0; i < MAX_RUMBLE_SCRIPTS; i++) + { + if (rumbleScripts[i].states == 0) + break; + } + + if (i == MAX_RUMBLE_SCRIPTS) + return -1; // Ran out of scripts + + + rumbleScripts[i].autoDelete = deleteWhenFinished; + rumbleScripts[i].controller = controller; + rumbleScripts[i].currentState = 0; + rumbleScripts[i].nextStateAt = 0; + rumbleScripts[i].numStates = numStates; + rumbleScripts[i].usedStates = 0; + rumbleScripts[i].states = new rumblestate_t[numStates]; + memset(rumbleScripts[i].states, 0, sizeof(rumblestate_t) * numStates); + return i; +} + +// A negative time will last until you kill it explicitly +// Returns index, used to kill or change a state in a script +int IN_AddRumbleStateFull(int whichScript, int arg1, int arg2, int timeInMs) +{ + if (!IN_usingRumble()) return -1; + + assert(whichScript >= 0 && whichScript < MAX_RUMBLE_SCRIPTS); + assert(rumbleScripts[whichScript].usedStates < rumbleScripts[whichScript].numStates); + + // Get the current state + rumblescript_t *curScript = &rumbleScripts[whichScript]; + rumblestate_t *curState = &curScript->states[curScript->usedStates]; + + curState->arg1 = arg1; + curState->arg2 = arg2; + + curState->timeToStop = timeInMs; + return curScript->usedStates++; +} + +int IN_AddRumbleState(int whichScript, int leftSpeed, int rightSpeed, int timeInMs) +{ + return IN_AddRumbleStateFull(whichScript, leftSpeed, rightSpeed, timeInMs); +} + +int IN_AddRumbleStateSpecial(int whichScript, int action, int arg1, int arg2) +{ + if (!IN_usingRumble()) return -1; + + assert(whichScript >= 0 && whichScript < MAX_RUMBLE_SCRIPTS); + assert(rumbleScripts[whichScript].usedStates < rumbleScripts[whichScript].numStates); + + // Get the current state + rumblescript_t *curScript = &rumbleScripts[whichScript]; + rumblestate_special_t *curState = (rumblestate_special_t*)&curScript->states[curScript->usedStates]; + + curState->code = action; + curState->arg1 = arg1; + curState->arg2 = arg2; + return curScript->usedStates++; +} + +int IN_AddEffectFade4(int whichScript, int startLeft, int startRight, + int endLeft, int endRight, int timeInMs) +{ + const int fadeSmoothness = 50; // number of ms between updates, smaller is smoother + + int e = IN_AddRumbleState(whichScript, startLeft, startRight, fadeSmoothness); // Lasts for fadeSmoothness ms + + if (startLeft < endLeft) // Fade increases + { + IN_AddRumbleStateSpecial(whichScript, IN_CMD_INC_LEFT, e, + (endLeft - startLeft) * fadeSmoothness / timeInMs); + } + else + { + IN_AddRumbleStateSpecial(whichScript, IN_CMD_DEC_LEFT, e, + (startLeft - endLeft) * fadeSmoothness / timeInMs); + } + + if (startRight < endRight) + { + IN_AddRumbleStateSpecial(whichScript, IN_CMD_INC_RIGHT, e, + (endRight - startRight) * fadeSmoothness / timeInMs); + } + else + { + IN_AddRumbleStateSpecial(whichScript, IN_CMD_DEC_RIGHT, e, + (startRight - endRight) * fadeSmoothness / timeInMs); + } + + return IN_AddRumbleStateSpecial(whichScript, IN_CMD_GOTO_XTIMES, + e, timeInMs / fadeSmoothness); +} + +int IN_AddEffectFadeExp6(int whichScript, int startLeft, int startRight, + int endLeft, int endRight, char factor, int timeInMs) +{ + const int fadeSmoothness = 10; // number of ms between updates, smaller is smoother + + int state = IN_AddRumbleState(whichScript, startLeft, startRight, fadeSmoothness); // Lasts for fadeSmoothness ms + + if (startLeft < endLeft) // Fade increases + { + IN_AddRumbleStateSpecial(whichScript, IN_CMD_INC_LEFT, state, + (endLeft - startLeft) * fadeSmoothness / timeInMs - + (factor / 2) * ( 1 - timeInMs / fadeSmoothness) + ); + } + else + { + IN_AddRumbleStateSpecial(whichScript, IN_CMD_DEC_LEFT, state, + (startLeft - endLeft) * fadeSmoothness / timeInMs - + (factor / 2) * ( 1 - timeInMs / fadeSmoothness) + ); + } + + if (startRight < endRight) + { + IN_AddRumbleStateSpecial(whichScript, IN_CMD_INC_RIGHT, state, + (endRight - startRight) * fadeSmoothness / timeInMs - + (factor / 2) * ( 1 - timeInMs / fadeSmoothness) + ); + } + else + { + IN_AddRumbleStateSpecial(whichScript, IN_CMD_DEC_RIGHT, state, + (startRight - endRight) * fadeSmoothness / timeInMs - + (factor / 2) * ( 1 - timeInMs / fadeSmoothness) + ); + } + + IN_AddRumbleStateSpecial(whichScript, IN_CMD_INC_ARG2, state + 1, factor); + IN_AddRumbleStateSpecial(whichScript, IN_CMD_INC_ARG2, state + 2, factor); + return IN_AddRumbleStateSpecial(whichScript, IN_CMD_GOTO_XTIMES, + state, timeInMs / fadeSmoothness); +} + +// Kills a rumble state based on index +void IN_KillRumbleState(int whichScript, int index) +{ + if (!IN_usingRumble()) return; + + assert( whichScript >= 0 && whichScript < MAX_RUMBLE_SCRIPTS); + assert( index < rumbleScripts[whichScript].numStates ); + + rumbleScripts[whichScript].states[index].timeToStop = 0; + rumbleStatus[rumbleScripts[whichScript].controller].changed = true; +} + +// Stops the script, if script has autodelete on then it will get deleted, otherwise it will only stop +void IN_KillRumbleScript(int whichScript) +{ + if (!IN_usingRumble()) return; + + assert (whichScript >= 0 && whichScript < MAX_RUMBLE_SCRIPTS); + + rumbleScripts[whichScript].nextStateAt = 0; + if (rumbleScripts[whichScript].autoDelete) + { + if (rumbleScripts[whichScript].states) + delete [] rumbleScripts[whichScript].states; + rumbleScripts[whichScript].states = 0; + } + + rumbleStatus[rumbleScripts[whichScript].controller].changed = true; +} + +// Stops Rumbling for specific controller +void IN_KillRumbleScripts(int controller) +{ + if (!IN_usingRumble()) return; + if (controller <= -1 || controller >= MAX_RUMBLE_CONTROLLERS) return; + if (rumbleStatus[controller].killed == true) return; + + for (int i = 0; i < MAX_RUMBLE_SCRIPTS; i++) + { + if (rumbleScripts[i].controller == controller) + IN_KillRumbleScript(i); + } + + rumbleStatus[controller].killed = IN_RumbleAdjust(controller, 0, 0); +} + +// Stops Rumbling on all controllers +void IN_KillRumbleScripts( void ) +{ + if (!IN_usingRumble()) return; + + for (int i = 0; i < MAX_RUMBLE_SCRIPTS; i++) + IN_KillRumbleScript(i); + + for (int j = 0; j < MAX_RUMBLE_CONTROLLERS; j++) + { + if (!rumbleStatus[j].killed) + { + rumbleStatus[j].killed = IN_RumbleAdjust(j, 0, 0); + } + } +} + +void IN_DeleteRumbleScript(int whichScript) +{ + if (!IN_usingRumble()) return; + + assert (whichScript >= 0 && whichScript < MAX_RUMBLE_SCRIPTS); + + if (rumbleScripts[whichScript].states) + delete [] rumbleScripts[whichScript].states; + rumbleScripts[whichScript].nextStateAt = 0; + rumbleScripts[whichScript].states = 0; + + rumbleStatus[rumbleScripts[whichScript].controller].changed = true; +} + +int IN_RunSpecialScript(int whichScript) +{ + rumblestate_special_t *sp = (rumblestate_special_t*)&rumbleScripts[whichScript].states[rumbleScripts[whichScript].currentState]; + switch (sp->code) + { + // updates the current state pointer + // uses arg1 + case IN_CMD_GOTO: + rumbleScripts[whichScript].currentState = sp->arg1; + return rumbleScripts[whichScript].states[sp->arg1].timeToStop; + break; + // does a goto, and decreases count of arg2, until 0 + case IN_CMD_GOTO_XTIMES: + if (--sp->arg2 >= 0) + { + rumbleScripts[whichScript].currentState = sp->arg1; + return rumbleScripts[whichScript].states[sp->arg1].timeToStop; + } + else // Go onto next cmd + { + if (!IN_AdvanceToNextState(whichScript)) + return -2; // Done + return -1; + } + break; + + // Decreasae Arg2 of a State, sp->arg1 = state, sp->arg2 = amount to decrease arg2 of state by + case IN_CMD_DEC_ARG2: + { + rumblestate_special_t *temp = (rumblestate_special_t*)&rumbleScripts[whichScript].states[sp->arg1]; + temp->arg2 -= sp->arg2; + } + break; + + // Increase Arg2 of a State, sp->arg1 = state, sp->arg2 = amount to increase arg2 of state by + case IN_CMD_INC_ARG2: + { + rumblestate_special_t *temp = (rumblestate_special_t*)&rumbleScripts[whichScript].states[sp->arg1]; + temp->arg2 += sp->arg2; + } + break; + + // Decreasae Arg1 of a State, sp->arg1 = state, sp->arg2 = amount to decrease arg1 of state by + case IN_CMD_DEC_ARG1: + { + rumblestate_special_t *temp = (rumblestate_special_t*)&rumbleScripts[whichScript].states[sp->arg1]; + temp->arg1 -= sp->arg2; + } + break; + + // Increase Arg2 of a State, sp->arg1 = state, sp->arg2 = amount to increase arg1 of state by + case IN_CMD_INC_ARG1: + { + rumblestate_special_t *temp = (rumblestate_special_t*)&rumbleScripts[whichScript].states[sp->arg1]; + temp->arg1 += sp->arg2; + } + break; + + case IN_CMD_DEC_LEFT: + rumbleScripts[whichScript].states[sp->arg1].arg2 -= sp->arg2; + if (rumbleScripts[whichScript].states[sp->arg1].arg2 < 0) + rumbleScripts[whichScript].states[sp->arg1].arg2 = 0; + if (rumbleScripts[whichScript].currentState >= rumbleScripts[whichScript].usedStates - 1) + return -2; // Done + return rumbleScripts[whichScript].states[++rumbleScripts[whichScript].currentState].timeToStop; + break; + + case IN_CMD_DEC_RIGHT: + rumbleScripts[whichScript].states[sp->arg1].arg1 -= sp->arg2; + if (rumbleScripts[whichScript].states[sp->arg1].arg1 < 0) + rumbleScripts[whichScript].states[sp->arg1].arg1 = 0; + if (rumbleScripts[whichScript].currentState >= rumbleScripts[whichScript].usedStates - 1) + return -2; // Done + return rumbleScripts[whichScript].states[++rumbleScripts[whichScript].currentState].timeToStop; + break; + + case IN_CMD_INC_LEFT: + rumbleScripts[whichScript].states[sp->arg1].arg2 += sp->arg2; + if (rumbleScripts[whichScript].states[sp->arg1].arg2 > 65534) + rumbleScripts[whichScript].states[sp->arg1].arg2 = 65534; + if (rumbleScripts[whichScript].currentState >= rumbleScripts[whichScript].usedStates - 1) + return -2; // Done + return rumbleScripts[whichScript].states[++rumbleScripts[whichScript].currentState].timeToStop; + break; + + case IN_CMD_INC_RIGHT: + rumbleScripts[whichScript].states[sp->arg1].arg1 += sp->arg2; + if (rumbleScripts[whichScript].states[sp->arg1].arg1 > 65534) + rumbleScripts[whichScript].states[sp->arg1].arg1 = 65534; + if (rumbleScripts[whichScript].currentState >= rumbleScripts[whichScript].usedStates - 1) + return -2; // Done + return rumbleScripts[whichScript].states[++rumbleScripts[whichScript].currentState].timeToStop; + break; + } + return 0; +} + +int IN_Time() +{ + //mb return ClientManager::ActiveClient().cg.time; + return cg.time; +} + +int testTime; + +void IN_ExecuteRumbleScript(int whichScript) +{ + if (!IN_usingRumble()) return; + + assert (whichScript >= 0 && whichScript < MAX_RUMBLE_SCRIPTS); + + // Can't execute an empty script??? + assert (rumbleScripts[whichScript].usedStates > 0); + + rumbleScripts[whichScript].currentState = 0; + int cmd = rumbleScripts[whichScript].states[rumbleScripts[whichScript].currentState].timeToStop; + if (cmd < 0) + { + cmd = IN_RunSpecialScript(whichScript); + } + + rumbleScripts[whichScript].nextStateAt = -1;//IN_Time() + cmd; + + rumbleStatus[rumbleScripts[whichScript].controller].changed = true; + rumbleStatus[rumbleScripts[whichScript].controller].killed = false; + + testTime = IN_Time(); +} + + + +void IN_PauseRumbling(int controller) +{ + if (!IN_usingRumble()) return; + if (controller <= -1 || controller >= MAX_RUMBLE_CONTROLLERS) return; + if (rumbleStatus[controller].paused == true) return; + + rumbleStatus[controller].timePaused = IN_Time(); + rumbleStatus[controller].paused = IN_RumbleAdjust(controller, 0, 0); + IN_KillRumbleScripts(); +} + +void IN_UnPauseRumbling(int controller) +{ + if (!IN_usingRumble()) return; + if (controller <= -1 || controller >= MAX_RUMBLE_CONTROLLERS) return; + + // can't unpause a control that wasn't paused + if (rumbleStatus[controller].paused == false) return; + + int cur_time = IN_Time(); + for (int i = 0; i < MAX_RUMBLE_SCRIPTS; i++) + { + if (rumbleScripts[i].controller == controller) + { + if (rumbleScripts[i].nextStateAt == 0) continue; + // update the time to stop based on how long it was paused + rumbleScripts[i].nextStateAt += (cur_time - rumbleStatus[controller].timePaused); + } + } + + rumbleStatus[controller].paused = false; + rumbleStatus[controller].changed = true; + rumbleStatus[controller].killed = false; + + IN_KillRumbleScripts(); +} + +void IN_TogglePauseRumbling(int controller) +{ + if (!IN_usingRumble()) return; + if (controller <= -1 || controller >= MAX_RUMBLE_CONTROLLERS) return; + if (rumbleStatus[controller].paused) + IN_UnPauseRumbling(controller); + else + IN_PauseRumbling(controller); +} + +// Pauses rumbling on all controllers +void IN_PauseRumbling( void ) +{ + if (!IN_usingRumble()) return; + for (int i = 0; i < MAX_RUMBLE_CONTROLLERS; i++) + IN_PauseRumbling(i); +} + +// UnPauses rumbling on all controllers +void IN_UnPauseRumbling( void ) +{ + if (!IN_usingRumble()) return; + for (int i = 0; i < MAX_RUMBLE_CONTROLLERS; i++) + IN_UnPauseRumbling(i); +} + +// Toggles Pausing on all controllers +void IN_TogglePauseRumbling( void ) +{ + if (!IN_usingRumble()) return; + for (int i = 0; i < MAX_RUMBLE_CONTROLLERS; i++) + IN_TogglePauseRumbling(i); +} + +// Returns false when the end of the script is reached +bool IN_AdvanceToNextState(int whichScript) +{ + assert( whichScript >= 0 && whichScript < MAX_RUMBLE_SCRIPTS ); + + if (rumbleScripts[whichScript].currentState >= rumbleScripts[whichScript].usedStates - 1) + { + // Script is at its end, so kill it( which deletes only if autodelete + IN_KillRumbleScript(whichScript); + return false; + } + + // Advance a state + rumbleScripts[whichScript].currentState++; + + int cmd = rumbleScripts[whichScript].states[rumbleScripts[whichScript].currentState].timeToStop; + while (cmd < 0) + { + cmd = IN_RunSpecialScript(whichScript); + if (cmd == -1) return true; + if (cmd == -2) return false; + } + + rumbleScripts[whichScript].nextStateAt = IN_Time() + cmd; + return true; +} + +// Max rumble takes precidence +// Other possibility is some kind of sum of all the speeds +// Call this once a frame, to update the controller based on the rumble states +extern qboolean _UI_IsFullscreen( void ); +void IN_UpdateRumbleFromStates() +{ + if(_UI_IsFullscreen()) + { + IN_KillRumbleScripts(); + return; + } + + + int i; + int value[MAX_RUMBLE_CONTROLLERS][2]; + int cur_time = IN_Time(); + bool canKillScripts = false; + + memset(value, 0, sizeof(int)*MAX_RUMBLE_CONTROLLERS*2); + for (i = 0; i < MAX_RUMBLE_SCRIPTS; i++) + { + // If rumble is paused on current controller than skip this rumble state + if ( rumbleStatus[rumbleScripts[i].controller].paused) continue; + +//*mb ClientManager::ActivateByControllerId(rumbleScripts[i].controller); + if ( !IN_usingRumble() ) + { + IN_KillRumbleScript(i); + continue; + } +/*mb + if (!ClientManager::ActiveGentity() || !G_ActivePlayerNormal()) + { + IN_KillRumbleScript(i); + continue; + } +*/ + // Unset state so skip + if ( rumbleScripts[i].nextStateAt == 0) continue; + + canKillScripts = true; + + if ( rumbleScripts[i].nextStateAt == -1) + { + int cmd = rumbleScripts[i].states[rumbleScripts[i].currentState].timeToStop; + rumbleScripts[i].nextStateAt = cur_time + cmd; + } + + // Time is up on this rumble state + if ( rumbleScripts[i].nextStateAt < cur_time) + { + // If timeToStop is < cur_time and > 0 then end this state otherwise (negative number) always rumble + if (rumbleScripts[i].nextStateAt > 0) + { + rumbleStatus[rumbleScripts[i].controller].changed = true; + rumbleStatus[rumbleScripts[i].controller].killed = false; + if (!IN_AdvanceToNextState(i)) // Returns false if reached the end of script + continue; + } + } + + rumblescript_t *curScript = &rumbleScripts[i]; + + if (value[curScript->controller][0] < curScript->states[curScript->currentState].arg2) + value[curScript->controller][0] = curScript->states[curScript->currentState].arg2; + if (value[curScript->controller][1] < curScript->states[curScript->currentState].arg1) + value[curScript->controller][1] = curScript->states[curScript->currentState].arg1; + } + + // Go through the 4 controller ports + for (i = 0; i < MAX_RUMBLE_CONTROLLERS; i++) + { + // paused, so do nothing for this controller + if ( rumbleStatus[i].paused) continue; + + // Only update the actual hardware if a state has changed + if (!rumbleStatus[i].changed) continue; + + IN_RumbleAdjust(i, value[i][0], value[i][1]); + + // State has changed + rumbleStatus[i].changed = false; + } + + if(canKillScripts) + { + if( (cur_time - rumble_timer) > 5000 ) + IN_KillRumbleScripts(); + } + else + { + rumble_timer = cur_time; + } + +} + + + +/* +================== +IN_RumbleInit +================== +*/ +void IN_RumbleInit (void) { + memset(&rumbleStatus, 0, sizeof(rumblestatus_t)*MAX_RUMBLE_CONTROLLERS); + memset(&rumbleScripts, 0, sizeof(rumblescript_t)*MAX_RUMBLE_SCRIPTS); + + in_useRumble = Cvar_Get( "in_useRumble", "1", 0 ); + in_shaking_rumble = Cvar_Get("in_shaking_rumble", "1", 0); +} + + +/* +================== +IN_RumbleShutdown +================== +*/ +void IN_RumbleShutdown (void) { + for (int i = 0; i < MAX_RUMBLE_SCRIPTS; i++) + { + if (rumbleScripts[i].states) + delete [] rumbleScripts[i].states; + rumbleScripts[i].states = 0; + rumbleScripts[i].nextStateAt = 0; + } +} + + +/* +================== +IN_RumbleFrame +================== +*/ +void IN_RumbleFrame (void) +{ + // Check to see if we need to pause rumbling + if(cl_paused->integer && !rumbleStatus[IN_GetMainController()].paused) + { + IN_PauseRumbling(IN_GetMainController()); + } + else if(!cl_paused->integer && rumbleStatus[IN_GetMainController()].paused) + { + IN_UnPauseRumbling(IN_GetMainController()); + } + + // Update the states + IN_UpdateRumbleFromStates(); +} diff --git a/code/win32/win_input_xbox.cpp b/code/win32/win_input_xbox.cpp new file mode 100644 index 0000000..88add33 --- /dev/null +++ b/code/win32/win_input_xbox.cpp @@ -0,0 +1,339 @@ +// win_input.c -- win32 mouse and joystick code +// 02/21/97 JCB Added extended DirectInput code to support external controllers. + +// leave this as first line for PCH reasons... +// +// #include "../server/exe_headers.h" + +#include +#include "glw_win_dx8.h" + +#include "../client/client.h" + +#include "../qcommon/qcommon.h" +#ifdef _JK2MP +#include "../ui/keycodes.h" +#else +#include "../client/keycodes.h" +#endif + +#include "win_local.h" +#include "win_input.h" + +#define IN_MAX_CONTROLLERS 4 + +void IN_UIEmptyQueue(); +void IN_CheckForNoControllers(); + +struct inputstate_t +{ + struct controller_t + { + HANDLE handle; + XINPUT_STATE state; + XINPUT_FEEDBACK feedback; + }; + controller_t controllers[IN_MAX_CONTROLLERS]; +}; + +inputstate_t *in_state = NULL; + + + +/* +========================================================================= + +JOYSTICK + +========================================================================= +*/ +//JLF moved here for multiple access (then not used multiple times. oh well.) +extern bool noControllersConnected; +// Process all the insertions and removals, updating handles and such +void IN_ProcessChanges(DWORD dwInsert, DWORD dwRemove) +{ + for(int port = 0; port < IN_MAX_CONTROLLERS; ++port) + { + // Close removals. + if((1 << port) & dwRemove) + { + if ( in_state->controllers[port].handle ) + { + XInputClose( in_state->controllers[port].handle ); + in_state->controllers[port].handle = 0; + } + IN_PadUnplugged(port); + + } + + // Open insertions. + if( (1 << port) & dwInsert ) + { + in_state->controllers[port].handle = XInputOpen( XDEVICE_TYPE_GAMEPAD, port, XDEVICE_NO_SLOT, NULL ); + IN_PadPlugged(port); + } + } + + return; +} + +/********* +IN_CheckForNoControllers() +If there are no controllers plugged in, the UI +is notified so it can display an appropriate +message. +*********/ +void IN_CheckForNoControllers() +{ + extern bool noControllersConnected; + if(!noControllersConnected) + { + extern bool wasPlugged[4]; + if( !wasPlugged[0] && + !wasPlugged[1] && + !wasPlugged[2] && + !wasPlugged[3] ) + { + // Tell the UI that there are no controllers connected + // VM_Call( uivm, UI_CONTROLLER_UNPLUGGED, true, -1); + noControllersConnected = true; + } + } +} + +/* +========================================================================= + + RUMBLE SUPPORT + +========================================================================= +*/ + +bool IN_RumbleAdjust(int controller, int left, int right) +{ + assert(controller >= 0 && controller < IN_MAX_CONTROLLERS); + + // Get a device handle for the controller. This may fail. + HANDLE handle = in_state->controllers[controller].handle; + + if (!handle) return false; + + XINPUT_FEEDBACK* fb = &in_state->controllers[controller].feedback; + + // If a prior rumble update is still pending, go away + if (fb->Header.dwStatus == ERROR_IO_PENDING) return false; + + fb->Rumble.wLeftMotorSpeed = left; + fb->Rumble.wRightMotorSpeed = right; + + return ERROR_IO_PENDING == XInputSetState(handle, fb); +} + + +/* +========================================================================= + +========================================================================= +*/ + +/* +igBool IN_WindowClose(igWindow *window) +{ + SV_Shutdown ("Server quit\n"); + CL_Shutdown (); + Com_Shutdown (); + Sys_Quit (); + return true; +} +*/ + +/* +=========== +IN_Shutdown +=========== +*/ +void IN_Shutdown( void ) { + IN_RumbleShutdown(); + + delete in_state; + in_state = NULL; +} + + +/* +=========== +IN_Init +=========== +*/ +void IN_Init( void ) +{ + in_state = new inputstate_t; + + // Initialize support for 4 gamepads + XDEVICE_PREALLOC_TYPE xdpt[] = { + {XDEVICE_TYPE_GAMEPAD, 4} + }; + + // Initialize the peripherals. We can only ever + // call XInitDevices once, no matter what. + static bool bInputInitialized = false; + if (!bInputInitialized) + XInitDevices( sizeof(xdpt) / sizeof(XDEVICE_PREALLOC_TYPE), xdpt ); + bInputInitialized = true; + + // Zero all of our data, including handles + memset(in_state->controllers, 0, sizeof(in_state->controllers)); + + // Find out the status of all gamepad ports, then open them + IN_ProcessChanges( XGetDevices( XDEVICE_TYPE_GAMEPAD ), 0 ); + + IN_RumbleInit(); + } + +static inline float _joyAxisConvert(SHORT x) +{ + // Change scale + float y = x / 32767.0; + + // Cheesy deadzone + if(fabs(y) < 0.25f) + { + y = 0.0f; + } + + return y; +} + +// How many controls on the xbox gamepad? +#define IN_NUM_DIGITAL_BUTTONS 8 +#define IN_NUM_ANALOG_BUTTONS 8 +// Cutoff where the analog buttons are considered to be "pressed" +// This should be smarter. +#define IN_ANALOG_BUTTON_THRESHOLD 64 + +void IN_UpdateGamepad(int port) +{ + // Lookup table to convert the digital buttons to fakeAscii_t, in mask order + const fakeAscii_t digitalXlat[IN_NUM_DIGITAL_BUTTONS] = { + A_JOY5, // DPAD_UP + A_JOY7, // DPAD_DOWN + A_JOY8, // DPAD_LEFT + A_JOY6, // DPAD_RIGHT + A_JOY4, // Start + A_JOY1, // Back + A_JOY2, // Left stick + A_JOY3 // Right stick + }; + + // Lookup table to convet the analog buttons to fakeAscii_t, in DX order + const fakeAscii_t analogXlat[IN_NUM_ANALOG_BUTTONS] = { + A_JOY15, // A + A_JOY14, // B + A_JOY16, // X + A_JOY13, // Y + A_JOY10, // Black + A_JOY9, // White + A_JOY11, // Left trigger + A_JOY12 // Right trigger + }; + + // Get new state + XINPUT_STATE newState; + XInputGetState( in_state->controllers[port].handle, &newState ); + + // Get old state + XINPUT_STATE &oldState(in_state->controllers[port].state); + + int buttonIdx; + bool oldPressed, newPressed; + + // Check all digital buttons first + for (buttonIdx = 0; buttonIdx < IN_NUM_DIGITAL_BUTTONS; ++buttonIdx) + { + oldPressed = oldState.Gamepad.wButtons & (1 << buttonIdx); + newPressed = newState.Gamepad.wButtons & (1 << buttonIdx); + + if (oldPressed != newPressed) + IN_CommonJoyPress(port, digitalXlat[buttonIdx], newPressed); + } + + // Now check all analog buttons + for (buttonIdx = 0; buttonIdx < IN_NUM_ANALOG_BUTTONS; ++buttonIdx) + { + oldPressed = oldState.Gamepad.bAnalogButtons[buttonIdx] > IN_ANALOG_BUTTON_THRESHOLD; + newPressed = newState.Gamepad.bAnalogButtons[buttonIdx] > IN_ANALOG_BUTTON_THRESHOLD; + + if (oldPressed != newPressed) + IN_CommonJoyPress(port, analogXlat[buttonIdx], newPressed); + } + + // Update joysticks + _padInfo.joyInfo[0].x = _joyAxisConvert(newState.Gamepad.sThumbLX); + _padInfo.joyInfo[0].y = _joyAxisConvert(newState.Gamepad.sThumbLY); + _padInfo.joyInfo[1].x = _joyAxisConvert(newState.Gamepad.sThumbRX); + _padInfo.joyInfo[1].y = _joyAxisConvert(newState.Gamepad.sThumbRY); + _padInfo.joyInfo[0].valid = _padInfo.joyInfo[1].valid = true; + _padInfo.padId = port; + + // Copy state back + oldState = newState; + + // Update game + IN_CommonUpdate(); +} + +extern qboolean CurrentStateIsInteractive(); +extern int mainControllerDelayedUnplug; + +extern void startsetMainController(int controller); +extern int gLaunchController; +/* +================== +IN_Frame + +Called every frame, even if not generating commands +================== +*/ +//extern int ignoreInputTime; +extern vmCvar_t ControllerOutNum; +void IN_Frame (void) +{ + static qboolean first = qtrue; + if (in_state) + { + // First, check for changes in device status (removed/inserted pads) + DWORD dwInsert, dwRemove; + if( XGetDeviceChanges( XDEVICE_TYPE_GAMEPAD, &dwInsert, &dwRemove ) ) + { + IN_ProcessChanges(dwInsert, dwRemove); + } + + if ( first ) + { + // We only force the controller to be locked when we came from MP: + extern bool Sys_QuickStart( void ); + if( Sys_QuickStart() ) + { + Com_Printf("\tController %d initialized\n", gLaunchController); + startsetMainController(gLaunchController); + + // We're bypassing splash menu! + Cvar_SetValue( "inSplashMenu", 0 ); + } + + // Only do this check once, no matter what: + first = qfalse; + } + + if ( mainControllerDelayedUnplug && CurrentStateIsInteractive() && ControllerOutNum.integer < 0) + IN_ProcessChanges(0, mainControllerDelayedUnplug); + + // Generate callbacks for each controller that's plugged in + for (int port = 0; port < IN_MAX_CONTROLLERS; ++port) + if (in_state->controllers[port].handle) + IN_UpdateGamepad(port); + + IN_UIEmptyQueue(); + IN_RumbleFrame(); + } +} diff --git a/code/win32/win_lighteffects.cpp b/code/win32/win_lighteffects.cpp new file mode 100644 index 0000000..367e6d2 --- /dev/null +++ b/code/win32/win_lighteffects.cpp @@ -0,0 +1,977 @@ +// +// +// win_lighteffects.cpp +// +// Various lighting effects w/ pixel shaders +// +// + +#ifdef VV_LIGHTING + +#include "../server/exe_headers.h" + +#include "../renderer/tr_local.h" +#include "glw_win_dx8.h" +#include "win_local.h" +#include "../renderer/tr_lightmanager.h" + +#include "win_lighteffects.h" + +#include +#include + +#include "shader_constants.h" + + +LightEffects::LightEffects() +{ + m_pCubeMap = NULL; + m_pBumpMap = NULL; + m_pSpecularMap = NULL; + m_pFalloffMap = NULL; + m_dwVertexShaderLight = 0L; + m_dwPixelShaderLight = 0L; + m_dwVertexShaderSpecular_Dynamic = 0L; + m_dwPixelShaderSpecular_Dynamic = 0L; + m_dwVertexShaderSpecular_Static = 0L; + m_dwPixelShaderSpecular_Static = 0L; + m_dwVertexShaderEnvironment = 0L; + m_dwVertexShaderBump = 0L; + m_dwPixelShaderBump = 0L; + m_bInLightPhase = false; + m_bInitialized = false; + + Initialize(); +} + + +LightEffects::~LightEffects() +{ + if( m_pCubeMap ) + m_pCubeMap->Release(); + m_pCubeMap = NULL; + + if(m_pBumpMap) + m_pBumpMap->Release(); + m_pBumpMap = NULL; + + if(m_pFalloffMap) + m_pFalloffMap->Release(); + m_pFalloffMap = NULL; + + if(m_pSpecularMap) + m_pSpecularMap->Release(); + m_pSpecularMap = NULL; + + if( glw_state->device ) + { + glw_state->device->DeleteVertexShader( m_dwVertexShaderLight ); + glw_state->device->DeletePixelShader( m_dwPixelShaderLight ); + glw_state->device->DeleteVertexShader( m_dwVertexShaderSpecular_Dynamic ); + glw_state->device->DeletePixelShader( m_dwPixelShaderSpecular_Dynamic ); + glw_state->device->DeleteVertexShader( m_dwVertexShaderSpecular_Static ); + glw_state->device->DeletePixelShader( m_dwPixelShaderSpecular_Static ); + glw_state->device->DeleteVertexShader( m_dwVertexShaderEnvironment ); + glw_state->device->DeletePixelShader( m_dwPixelShaderBump ); + glw_state->device->DeleteVertexShader( m_dwVertexShaderBump ); + } +} + + +void LightEffects::StartLightPhase() +{ + m_bInLightPhase = true; +} + + +void LightEffects::EndLightPhase() +{ + m_bInLightPhase = false; +} + +extern const char *Sys_RemapPath( const char *filename ); + +bool LightEffects::Initialize() +{ + HRESULT hr; + + // Create a vertex shader + DWORD dwVertexDecl[] = + { + D3DVSD_STREAM( 0 ), + D3DVSD_REG( 0, D3DVSDT_FLOAT3 ), // v0 = Position + D3DVSD_REG( 1, D3DVSDT_FLOAT3 ), // v1 = Normal + D3DVSD_REG( 2, D3DVSDT_FLOAT2 ), // v2 = Base tex coords + D3DVSD_REG( 3, D3DVSDT_FLOAT3 ), // v3 = Tangent space tangent + D3DVSD_END() + }; + + if(!( CreateVertexShader(Sys_RemapPath("base\\media\\dlight.xvu"), dwVertexDecl, &m_dwVertexShaderLight))) + return false; + + if(!( CreateVertexShader(Sys_RemapPath("base\\media\\specular_dynamic.xvu"), dwVertexDecl, &m_dwVertexShaderSpecular_Dynamic))) + return false; + + if(!( CreateVertexShader(Sys_RemapPath("base\\media\\specular_static.xvu"), dwVertexDecl, &m_dwVertexShaderSpecular_Static))) + return false; + + DWORD dwVertexDeclBump[] = + { + D3DVSD_STREAM( 0 ), + D3DVSD_REG( 0, D3DVSDT_FLOAT3 ), // v0 = Position + D3DVSD_REG( 1, D3DVSDT_FLOAT3 ), // v1 = Normal + D3DVSD_REG( 2, D3DVSDT_FLOAT2 ), // v2 = Base tex coords 0 + D3DVSD_REG( 3, D3DVSDT_FLOAT2 ), // v2 = Base tex coords 1 + D3DVSD_REG( 4, D3DVSDT_FLOAT3 ), // v4 = Tangent space tangent + D3DVSD_END() + }; + + if(!( CreateVertexShader(Sys_RemapPath("base\\media\\bump.xvu"), dwVertexDeclBump, &m_dwVertexShaderBump))) + return false; + + DWORD dwVertexDeclEnv[] = + { + D3DVSD_STREAM( 0 ), + D3DVSD_REG( 0, D3DVSDT_FLOAT3 ), // v0 = Position + D3DVSD_REG( 1, D3DVSDT_FLOAT3 ), // v1 = Normal + D3DVSD_REG( 2, D3DVSDT_D3DCOLOR ), // v2 = Color + D3DVSD_END() + }; + + if(!( CreateVertexShader(Sys_RemapPath("base\\media\\environment.xvu"), dwVertexDeclEnv, &m_dwVertexShaderEnvironment))) + return false; + + // Create the pixel shader + if(!(CreatePixelShader(Sys_RemapPath("base\\media\\dlight.xpu"), &m_dwPixelShaderLight))) + return false; + + if(!(CreatePixelShader(Sys_RemapPath("base\\media\\specular_dynamic.xpu"), &m_dwPixelShaderSpecular_Dynamic))) + return false; + + if(!(CreatePixelShader(Sys_RemapPath("base\\media\\specular_static.xpu"), &m_dwPixelShaderSpecular_Static))) + return false; + + if(!(CreatePixelShader(Sys_RemapPath("base\\media\\bump.xpu"), &m_dwPixelShaderBump))) + return false; + + hr = D3DXCreateTextureFromFileEx(glw_state->device, + Sys_RemapPath("base\\media\\defaultbump.dds"), + D3DX_DEFAULT, + D3DX_DEFAULT, + 0, + 0, + D3DFMT_A8R8G8B8, + 0, + D3DX_FILTER_LINEAR, + D3DX_FILTER_LINEAR, + 0, + NULL, + NULL, + &m_pBumpMap); + if (FAILED(hr)) + { + return false; + } + + hr = D3DXCreateTextureFromFileEx(glw_state->device, + Sys_RemapPath("base\\media\\diffspec.dds"), + D3DX_DEFAULT, + D3DX_DEFAULT, + 0, + 0, + D3DFMT_A8R8G8B8, + 0, + D3DX_FILTER_LINEAR, + D3DX_FILTER_LINEAR, + 0, + NULL, + NULL, + &m_pSpecularMap); + if (FAILED(hr)) + { + return false; + } + + // Create the volume falloff texture + UINT width = 32, + height = 32, + depth = 32, + levels = 1; + D3DFORMAT format = D3DFMT_A8; + if (FAILED (hr = glw_state->device->CreateVolumeTexture(width, height, depth, + 1, 0, format, D3DPOOL_DEFAULT, &m_pFalloffMap) ) ) + { + return false; + } + + // Fill the volume texture + D3DVOLUME_DESC desc; + D3DLOCKED_BOX lock; + m_pFalloffMap->GetLevelDesc( 0, &desc ); + m_pFalloffMap->LockBox( 0, &lock, 0, 0L ); + BYTE* pBits = (BYTE*)lock.pBits; + + for( UINT w=0; wUnlockBox( 0 ); + + // Create the normalization cube map + if(!CreateNormalizationCubeMap( 64, &m_pCubeMap )) + return false; + + m_bInitialized = true; + + return true; +} + + +bool LightEffects::RenderDynamicLights() +{ + VVdlight_t *dl; + shaderStage_t *dStage; + vec3_t origin; + byte clipBits[SHADER_MAX_VERTEXES]; + glIndex_t hitIndexes[SHADER_MAX_INDEXES]; + int numIndexes; + float radius; + int fogging; + vec3_t dist; + vec3_t e1; + vec3_t e2; + vec3_t normal; + float fac; + + if(!VVLightMan.num_dlights) + return true; + + glw_state->device->SetRenderState( D3DRS_LIGHTING, FALSE ); + + glw_state->device->SetTextureStageState( 1, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP ); + glw_state->device->SetTextureStageState( 1, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP ); + glw_state->device->SetTextureStageState( 1, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 1, D3DTSS_MIPFILTER, D3DTEXF_LINEAR ); + + glw_state->device->SetTextureStageState( 2, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 2, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 2, D3DTSS_ADDRESSW, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 2, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 2, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + + glw_state->device->SetTextureStageState( 3, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 3, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 3, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 3, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 3, D3DTSS_MIPFILTER, D3DTEXF_POINT ); + + glw_state->device->SetTextureStageState( 2, D3DTSS_ALPHAKILL, D3DTALPHAKILL_ENABLE); + +// GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + if(tess.shader->isBumpMap) + { + for ( int stage = 0; stage < tess.shader->numUnfoggedPasses; stage++ ) + { + shaderStage_t *pStage = &tess.xstages[stage]; + if(pStage->isBumpMap) + { + glwstate_t::texturexlat_t::iterator i = glw_state->textureXlat.find(pStage->bundle[1].image->texnum); + glw_state->device->SetTexture( 1, i->second.mipmap ); + } + } + } + else + glw_state->device->SetTexture(1, m_pBumpMap); + + glw_state->device->SetTexture(2, m_pFalloffMap); + glw_state->device->SetTexture(3, m_pCubeMap); + + glw_state->device->SetPixelShader(m_dwPixelShaderLight); + glw_state->device->SetVertexShader(m_dwVertexShaderLight); + + for(int l = 0; l < VVLightMan.num_dlights; l++) + { + if(!(tess.dlightBits & (1 << l))) + continue; + + dl = &VVLightMan.dlights[l]; + if(!dl) + continue; + + // Not going to bother testing all the polygons in a ghoul2 model + // they almost always all end up in range anyway + if(!backEnd.currentEntity->e.ghoul2) + { + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + + int clipall = 63; + for (int i = 0 ; i < tess.numVertexes ; i++) + { + int clip; + VectorSubtract( origin, tess.xyz[i], dist ); + + clip = 0; + if ( dist[0] < -radius ) + { + clip |= 1; + } + else if ( dist[0] > radius ) + { + clip |= 2; + } + if ( dist[1] < -radius ) + { + clip |= 4; + } + else if ( dist[1] > radius ) + { + clip |= 8; + } + if ( dist[2] < -radius ) + { + clip |= 16; + } + else if ( dist[2] > radius ) + { + clip |= 32; + } + + clipBits[i] = clip; + clipall &= clip; + } + if ( clipall ) + { + continue; // this surface doesn't have any of this light + } + + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) + { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i+1]; + c = tess.indexes[i+2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) + { + continue; // not lighted + } + + VectorSubtract( tess.xyz[a], tess.xyz[b], e1); + VectorSubtract( tess.xyz[c], tess.xyz[b], e2); + CrossProduct(e1,e2,normal); + + VectorNormalize(normal); + fac=DotProduct(normal,origin)-DotProduct(normal, tess.xyz[a]); + if (fac >= radius) // out of range + { + continue; + } + + // save the indexes + hitIndexes[numIndexes] = tess.indexes[i]; + hitIndexes[numIndexes + 1] = tess.indexes[i + 1]; + hitIndexes[numIndexes + 2] = tess.indexes[i + 2]; + + numIndexes += 3; + + if (numIndexes>=SHADER_MAX_VERTEXES-3) + { + break; // we are out of space, so we are done :) + } + } + + if ( !numIndexes ) { + continue; + } + } + + //don't have fog enabled when we redraw with alpha test, or it will double over + //and screw the tri up -rww + if (r_drawfog->value == 2 && + tr.world && + (tess.fogNum == tr.world->globalFog || tess.fogNum == tr.world->numfogs)) + { + fogging = qglIsEnabled(GL_FOG); + + if (fogging) + { + qglDisable(GL_FOG); + } + } + else + { + fogging = 0; + } + + dStage = NULL; + if (tess.shader && qglActiveTextureARB) + { + int i = 0; + while (i < tess.shader->numUnfoggedPasses) + { + const int blendBits = (GLS_SRCBLEND_BITS+GLS_DSTBLEND_BITS); + if (((tess.shader->stages[i].bundle[0].image && !tess.shader->stages[i].bundle[0].isLightmap && !tess.shader->stages[i].bundle[0].numTexMods && tess.shader->stages[i].bundle[0].tcGen != TCGEN_ENVIRONMENT_MAPPED && tess.shader->stages[i].bundle[0].tcGen != TCGEN_FOG) || + (tess.shader->stages[i].bundle[1].image && !tess.shader->stages[i].bundle[1].isLightmap && !tess.shader->stages[i].bundle[1].numTexMods && tess.shader->stages[i].bundle[1].tcGen != TCGEN_ENVIRONMENT_MAPPED && tess.shader->stages[i].bundle[1].tcGen != TCGEN_FOG)) && + (tess.shader->stages[i].stateBits & blendBits) == 0 ) + { //only use non-lightmap opaque stages + dStage = &tess.shader->stages[i]; + break; + } + i++; + } + } + + if (dStage) + { + GL_SelectTexture( 0 ); + GL_State(0); + // animMaps don't actually have a texture in image, it's an array of image pointers: + if (dStage->bundle[0].numImageAnimations > 1) + { + int index; + + if (backEnd.currentEntity->e.renderfx & RF_SETANIMINDEX ) + { + index = backEnd.currentEntity->e.skinNum; + } + else + { + // it is necessary to do this messy calc to make sure animations line up + // exactly with waveforms of the same frequency + index = myftol( backEnd.refdef.floatTime * dStage->bundle[0].imageAnimationSpeed * FUNCTABLE_SIZE ); + index >>= FUNCTABLE_SIZE2; + + if ( index < 0 ) { + index = 0; // may happen with shader time offsets + } + } + + if ( dStage->bundle[0].oneShotAnimMap ) + { + if ( index >= dStage->bundle[0].numImageAnimations ) + { + // stick on last frame + index = dStage->bundle[0].numImageAnimations - 1; + } + } + else + { + // loop + index %= dStage->bundle[0].numImageAnimations; + } + + GL_Bind( *((image_t**)dStage->bundle[0].image + index) ); + } + else if (dStage->bundle[0].image && !dStage->bundle[0].isLightmap && !dStage->bundle[0].numTexMods && dStage->bundle[0].tcGen != TCGEN_ENVIRONMENT_MAPPED && dStage->bundle[0].tcGen != TCGEN_FOG) + { + GL_Bind( dStage->bundle[0].image ); + } + else + { + GL_Bind( dStage->bundle[1].image ); + } + + GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE);// | GLS_DEPTHFUNC_EQUAL); + } + else + { + GL_Bind( tr.whiteImage ); + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE);// | GLS_DEPTHFUNC_EQUAL ); + } + + glwstate_t::texturexlat_t::iterator inf = glw_state->textureXlat.find(glw_state->currentTexture[0]); + + glw_state->device->SetTexture(0, inf->second.mipmap); + + D3DXVECTOR4 vecLightRange(1.0f / dl->radius, 0.0f, 0.0f, 0.0f); + glw_state->device->SetVertexShaderConstant(CV_ONE_OVER_LIGHT_RANGE, (void*)&vecLightRange.x, 1); + + glw_state->device->SetPixelShaderConstant(CP_DIFFUSE_COLOR, &dl->color[0], 1); + + ProcessVertices( (D3DXVECTOR3*)&dl->direction, (D3DXVECTOR3*)&dl->transformed ); + + if(backEnd.currentEntity->e.ghoul2) + renderObject_Light( tess.numIndexes, tess.indexes ); + else + renderObject_Light( numIndexes, hitIndexes ); + + if (fogging) + { + qglEnable(GL_FOG); + } + } + + glw_state->device->SetPixelShader( 0 ); + + // This is kinda a hack to get the quake renderer to discard + // these textures after they are used here, instead of applying + // them to more geometry + glState.currenttextures[0] = -2; + glState.currenttextures[1] = -2; + glw_state->currentTexture[0] = -2; + glw_state->currentTexture[1] = -2; + + glw_state->device->SetTextureStageState( 2, D3DTSS_ALPHAKILL, D3DTALPHAKILL_DISABLE); + + glw_state->device->SetTexture(2, NULL); + glw_state->device->SetTexture(3, NULL); + + return true; +} + + +void LightEffects::RenderSpecular() +{ + glw_state->device->SetRenderState( D3DRS_LIGHTING, FALSE ); + glw_state->device->SetRenderState( D3DRS_FOGENABLE, FALSE ); + + glwstate_t::texturexlat_t::iterator i = glw_state->textureXlat.find(glw_state->currentTexture[0]); + + glw_state->device->SetTexture( 0, i->second.mipmap ); + glw_state->device->SetTexture( 2, m_pSpecularMap ); + + glw_state->device->SetVertexShaderConstant(CV_CAMERA_DIRECTION, D3DXVECTOR4(tr.viewParms.or.axis[0][0], + tr.viewParms.or.axis[0][1], + tr.viewParms.or.axis[0][2], + 1.0f), 1 ); + + RenderSpecular_Static(); + + if(tess.dlightBits) + RenderSpecular_Dynamic(); + + glw_state->device->SetPixelShader(0); + + // This is kinda a hack to get the quake renderer to discard + // these textures after they are used here, instead of applying + // them to more geometry + glState.currenttextures[0] = -2; + glState.currenttextures[1] = -2; + glw_state->currentTexture[0] = -2; + glw_state->currentTexture[1] = -2; +} + + +bool LightEffects::RenderSpecular_Dynamic() +{ + VVdlight_t *dl; + + glw_state->device->SetTextureStageState( 0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 2, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 2, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 2, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 2, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + glw_state->device->SetTextureStageState( 2, D3DTSS_MIPFILTER, D3DTEXF_POINT ); + glw_state->device->SetTextureStageState( 3, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 3, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 3, D3DTSS_ADDRESSW, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 3, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 3, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + + glw_state->device->SetTexture( 3, m_pFalloffMap ); + + glw_state->device->SetPixelShader(m_dwPixelShaderSpecular_Dynamic); + glw_state->device->SetVertexShader(m_dwVertexShaderSpecular_Dynamic); + + for(int i = 0; i < VVLightMan.num_dlights; i++) + { + if(!(tess.dlightBits & (1 << i))) + continue; + + dl = &VVLightMan.dlights[i]; + + D3DXVECTOR4 vecLightRange(1.0f / dl->radius, 0.0f, 0.0f, 0.0f); + glw_state->device->SetVertexShaderConstant(CV_ONE_OVER_LIGHT_RANGE, (void*)&vecLightRange.x, 1); + + ProcessVertices( (D3DXVECTOR3*)&dl->direction, (D3DXVECTOR3*)&dl->transformed ); + + renderObject_Light( tess.numIndexes, tess.indexes); + } + + glw_state->device->SetTexture( 3, NULL ); + + return true; +} + + +bool LightEffects::RenderSpecular_Static() +{ + glw_state->device->SetTextureStageState( 0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 2, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 2, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 2, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 2, D3DTSS_MAGFILTER, D3DTEXF_LINEAR); + glw_state->device->SetTextureStageState( 2, D3DTSS_MIPFILTER, D3DTEXF_NONE ); + + glw_state->device->SetPixelShader(m_dwPixelShaderSpecular_Static); + glw_state->device->SetVertexShader(m_dwVertexShaderSpecular_Static); + + ProcessVertices( NULL, NULL ); + + D3DXVECTOR4 vLight; + if (backEnd.currentEntity && (backEnd.currentEntity->e.hModel||backEnd.currentEntity->e.ghoul2) ) + { + vLight.x = backEnd.currentEntity->lightDir[0]; + vLight.y = backEnd.currentEntity->lightDir[1]; + vLight.z = backEnd.currentEntity->lightDir[2]; + } + else + { + // These values were taken from the RB_CalcSpecularAlpha default case + vLight.x = -960.0f; + vLight.y = 1920.0f; + vLight.z = 96.0f; + } + + vLight.w = 1.0f; + + glw_state->device->SetVertexShaderConstant(CV_LIGHT_DIRECTION, vLight, 1); + + renderObject_Light( tess.numIndexes, tess.indexes ); + + return true; +} + +bool LightEffects::RenderEnvironment() +{ + glw_state->device->SetRenderState( D3DRS_LIGHTING, false ); + + glw_state->device->SetVertexShaderConstant(CV_CAMERA_DIRECTION, D3DXVECTOR4(backEnd.ori.viewOrigin[0], + backEnd.ori.viewOrigin[1], + backEnd.ori.viewOrigin[2], + 1.0f), 1 ); + + ProcessVertices(NULL, NULL); + + XGMATRIX *view, viewtran; + view = (XGMATRIX*)glw_state->matrixStack[glw_state->MatrixMode_Model]->GetTop(); + XGMatrixTranspose( &viewtran, view ); + glw_state->device->SetVertexShaderConstant(CV_VIEW_0, viewtran, 4); + + glw_state->device->SetVertexShader(m_dwVertexShaderEnvironment); + + renderObject_Env(); + + return true; +} + + +// Renders bump maps without the benefit of dynamic lights +void LightEffects::RenderBump() +{ + glw_state->device->SetRenderState( D3DRS_LIGHTING, false ); + glw_state->device->SetRenderState( D3DRS_FOGENABLE, false ); + + glwstate_t::texturexlat_t::iterator i = glw_state->textureXlat.find(glw_state->currentTexture[0]); + glw_state->device->SetTexture( 0, i->second.mipmap ); + + i = glw_state->textureXlat.find(glw_state->currentTexture[1]); + glw_state->device->SetTexture( 1, i->second.mipmap ); + + glw_state->device->SetTextureStageState(0, D3DTSS_MAXANISOTROPY, i->second.anisotropy); + glw_state->device->SetTextureStageState(0, D3DTSS_MINFILTER, i->second.minFilter); + glw_state->device->SetTextureStageState(0, D3DTSS_MIPFILTER, i->second.mipFilter); + glw_state->device->SetTextureStageState(0, D3DTSS_MAGFILTER, i->second.magFilter); + glw_state->device->SetTextureStageState(0, D3DTSS_ADDRESSU, i->second.wrapU); + glw_state->device->SetTextureStageState(0, D3DTSS_ADDRESSV, i->second.wrapV); + + glw_state->device->SetTextureStageState(1, D3DTSS_MAXANISOTROPY, i->second.anisotropy); + glw_state->device->SetTextureStageState(1, D3DTSS_MINFILTER, i->second.minFilter); + glw_state->device->SetTextureStageState(1, D3DTSS_MIPFILTER, i->second.mipFilter); + glw_state->device->SetTextureStageState(1, D3DTSS_MAGFILTER, i->second.magFilter); + glw_state->device->SetTextureStageState(1, D3DTSS_ADDRESSU, i->second.wrapU); + glw_state->device->SetTextureStageState(1, D3DTSS_ADDRESSV, i->second.wrapV); + + glw_state->device->SetRenderState(D3DRS_SPECULARENABLE, true); + + glw_state->device->SetPixelShader(m_dwPixelShaderBump); + glw_state->device->SetVertexShader(m_dwVertexShaderBump); + + ProcessVertices( NULL, NULL ); + + D3DXVECTOR4 vAmbient, vDiffuse, vLightDir; + + if (backEnd.currentEntity && (backEnd.currentEntity->e.hModel||backEnd.currentEntity->e.ghoul2) ) + { + if(tess.shader->stages[tess.currentPass].rgbGen == CGEN_LIGHTING_DIFFUSE_ENTITY) + { + vAmbient.x = (backEnd.currentEntity->ambientLight[0] / 255.f) * (backEnd.currentEntity->e.shaderRGBA[0] / 255.0); + vAmbient.y = (backEnd.currentEntity->ambientLight[1] / 255.f) * (backEnd.currentEntity->e.shaderRGBA[1] / 255.0); + vAmbient.z = (backEnd.currentEntity->ambientLight[2] / 255.f) * (backEnd.currentEntity->e.shaderRGBA[2] / 255.0); + } + else + { + vAmbient.x = backEnd.currentEntity->ambientLight[0] / 255.f; + vAmbient.y = backEnd.currentEntity->ambientLight[1] / 255.f; + vAmbient.z = backEnd.currentEntity->ambientLight[2] / 255.f; + } + + vDiffuse.x = backEnd.currentEntity->directedLight[0] / 255.f; + vDiffuse.y = backEnd.currentEntity->directedLight[1] / 255.f; + vDiffuse.z = backEnd.currentEntity->directedLight[2] / 255.f; + + vLightDir.x = DotProduct( backEnd.currentEntity->lightDir, backEnd.currentEntity->e.axis[0] ); + vLightDir.y = DotProduct( backEnd.currentEntity->lightDir, backEnd.currentEntity->e.axis[1] ); + vLightDir.z = DotProduct( backEnd.currentEntity->lightDir, backEnd.currentEntity->e.axis[2] ); + } + else + { + vec3_t sundir; + sundir[0] = r_sundir_x->value; + sundir[1] = r_sundir_y->value; + sundir[2] = r_sundir_z->value; + + VectorNormalize(sundir); + + vLightDir.x = sundir[0];//tr.sunDirection[0]; + vLightDir.y = sundir[1];//tr.sunDirection[1]; + vLightDir.z = sundir[2];//tr.sunDirection[2]; + vLightDir.w = 1.0f; + + vAmbient.x = tr.sunAmbient[0] / 1.5f; + vAmbient.y = tr.sunAmbient[1] / 1.5f; + vAmbient.z = tr.sunAmbient[2] / 1.5f; + + vDiffuse.x = 1.0f; + vDiffuse.y = 1.0f; + vDiffuse.z = 1.0f; + } + + glw_state->device->SetPixelShaderConstant(CP_AMBIENT_COLOR, vAmbient, 1); + glw_state->device->SetPixelShaderConstant(CP_DIFFUSE_COLOR, vDiffuse, 1); + + glw_state->device->SetVertexShaderConstant(CV_LIGHT_DIRECTION, vLightDir, 1); + + glw_state->device->SetVertexShaderConstant(CV_CAMERA_DIRECTION, D3DXVECTOR4(tr.viewParms.or.axis[0][0], + tr.viewParms.or.axis[0][1], + tr.viewParms.or.axis[0][2], + 1.0f), 1 ); + + renderObject_Bump(); + + glw_state->device->SetPixelShader( 0 ); + + glw_state->device->SetRenderState(D3DRS_SPECULARENABLE, false); +} + + +//bool LightEffects::RenderStaticLights() +//{ +// VVslight_t *sl; +// +// for(int i = 0; i < tess.numSlights; i++) +// { +// sl = &VVLightMan.slights[tess.slightBits[i]]; +// +// D3DXVECTOR4 vecLightRange(1.0f / (sl->radius * 2.0f), sl->radius, 1.0f, 1.0f); +// glw_state->device->SetVertexShaderConstant(CV_ONE_OVER_LIGHT_RANGE, (void*)&vecLightRange.x, 1); +// +// glw_state->device->SetVertexShaderConstant(CV_LIGHT_COLOR, &sl->color[0], 1); +// +// ProcessVertices( NULL, (D3DXVECTOR3*)&sl->origin ); +// +// renderObject_Light(); +// +// tess.currentPass++; +// } +// +// return true; +//} + + +void LightEffects::ProcessVertices( D3DXVECTOR3* pDirLightDir, D3DXVECTOR3* pPtLightPos ) +{ + // Just in case, this doesn't always get set + glw_state->device->SetTransform( D3DTS_PROJECTION, glw_state->matrixStack[glw_state->MatrixMode_Projection]->GetTop() ); + + // Compute the matrix set + XGMATRIX matComposite, matProjectionViewport, matWorld; + // Get the projection viewport matrix the fixed pipeline uses. + // The viewport matrix includes the viewport x,y scale and the + // appropriate z scale. + glw_state->device->GetProjectionViewportMatrix( &matProjectionViewport ); + + D3DVIEWPORT8 view; + glw_state->device->GetViewport(&view); + + // Gotta do this to fix an XDK bug + // GetProjectionViewportMatrix does not seem to reflect the viewport values + // when the viewport is offset + matProjectionViewport._31 += view.X; + matProjectionViewport._32 += view.Y; + + XGMatrixMultiply( &matComposite, (XGMATRIX*)glw_state->matrixStack[glwstate_t::MatrixMode_Model]->GetTop(), &matProjectionViewport ); + + // Transpose and set the composite matrix. + XGMatrixTranspose( &matComposite, &matComposite ); + glw_state->device->SetVertexShaderConstant( CV_WORLDVIEWPROJ_0, &matComposite, 4 ); + + if (pPtLightPos) + glw_state->device->SetVertexShaderConstant( CV_LIGHT_POSITION, pPtLightPos, 1 ); + + // Set viewport offsets. + float fViewportOffsets[4] = { 0.53125f, 0.53125f, 0.0f, 0.0f }; + glw_state->device->SetVertexShaderConstant( CV_VIEWPORT_OFFSETS, &fViewportOffsets, 1 ); + + // Set common constants + glw_state->device->SetVertexShaderConstant(CV_ONE, D3DXVECTOR4(1.0f, 1.0f, 1.0f, 1.0f), 1); + glw_state->device->SetVertexShaderConstant(CV_HALF, D3DXVECTOR4(0.5f, 0.5f, 0.5f, 0.5f), 1); +} + + +inline D3DCOLOR VectorToRGBA( const D3DXVECTOR3* v, FLOAT fHeight = 1.0f ) +{ + D3DCOLOR r = (D3DCOLOR)( ( v->x + 1.0f ) * 127.5f ); + D3DCOLOR g = (D3DCOLOR)( ( v->y + 1.0f ) * 127.5f ); + D3DCOLOR b = (D3DCOLOR)( ( v->z + 1.0f ) * 127.5f ); + D3DCOLOR a = (D3DCOLOR)( 255.0f * fHeight ); + return( (a<<24L) + (r<<16L) + (g<<8L) + (b<<0L) ); +} + + +bool LightEffects::CreateNormalizationCubeMap( DWORD dwSize, LPDIRECT3DCUBETEXTURE8* ppCubeMap ) +{ + HRESULT hr; + + // Create the cube map + if( FAILED( hr = glw_state->device->CreateCubeTexture( dwSize, 1, 0, D3DFMT_X8R8G8B8, + D3DPOOL_DEFAULT, ppCubeMap ) ) ) + return false; + + // Allocate temp space for swizzling the cubemap surfaces + DWORD* pSourceBits = new DWORD[ dwSize * dwSize ]; + + // Fill all six sides of the cubemap + for( DWORD i=0; i<6; i++ ) + { + // Lock the i'th cubemap surface + LPDIRECT3DSURFACE8 pCubeMapFace; + (*ppCubeMap)->GetCubeMapSurface( (D3DCUBEMAP_FACES)i, 0, &pCubeMapFace ); + + // Write the RGBA-encoded normals to the surface pixels + DWORD* pPixel = pSourceBits; + D3DXVECTOR3 n; + FLOAT w, h; + + for( DWORD y = 0; y < dwSize; y++ ) + { + h = (FLOAT)y / (FLOAT)(dwSize-1); // 0 to 1 + h = ( h * 2.0f ) - 1.0f; // -1 to 1 + + for( DWORD x = 0; x < dwSize; x++ ) + { + w = (FLOAT)x / (FLOAT)(dwSize-1); // 0 to 1 + w = ( w * 2.0f ) - 1.0f; // -1 to 1 + + // Calc the normal for this texel + switch( i ) + { + case D3DCUBEMAP_FACE_POSITIVE_X: // +x + n.x = +1.0; + n.y = -h; + n.z = -w; + break; + + case D3DCUBEMAP_FACE_NEGATIVE_X: // -x + n.x = -1.0; + n.y = -h; + n.z = +w; + break; + + case D3DCUBEMAP_FACE_POSITIVE_Y: // y + n.x = +w; + n.y = +1.0; + n.z = +h; + break; + + case D3DCUBEMAP_FACE_NEGATIVE_Y: // -y + n.x = +w; + n.y = -1.0; + n.z = -h; + break; + + case D3DCUBEMAP_FACE_POSITIVE_Z: // +z + n.x = +w; + n.y = -h; + n.z = +1.0; + break; + + case D3DCUBEMAP_FACE_NEGATIVE_Z: // -z + n.x = -w; + n.y = -h; + n.z = -1.0; + break; + } + + // Store the normal as an RGBA color + D3DXVec3Normalize( &n, &n ); + *pPixel++ = VectorToRGBA( &n ); + } + } + + // Swizzle the result into the cubemap face surface + D3DLOCKED_RECT lock; + pCubeMapFace->LockRect( &lock, 0, 0L ); + XGSwizzleRect( pSourceBits, 0, NULL, lock.pBits, dwSize, dwSize, + NULL, sizeof(DWORD) ); + pCubeMapFace->UnlockRect(); + + // Release the cubemap face + pCubeMapFace->Release(); + } + + // Free temp space + if( pSourceBits ) + delete [] pSourceBits; + pSourceBits = NULL; + + return true; +} + + +#endif // VV_LIGHTING \ No newline at end of file diff --git a/code/win32/win_lighteffects.h b/code/win32/win_lighteffects.h new file mode 100644 index 0000000..a276fd7 --- /dev/null +++ b/code/win32/win_lighteffects.h @@ -0,0 +1,52 @@ +// +// +// win_lightefects.h +// +// Declaration of class for pixel shader light effects +// +// + + +#ifndef _WIN_LIGHTEFFECTS_H_ +#define _WIN_LIGHTEFFECTS_H_ + + +class LightEffects +{ +public: + LPDIRECT3DCUBETEXTURE9 m_pCubeMap; // Normalization cubemap + LPDIRECT3DTEXTURE9 m_pBumpMap; + LPDIRECT3DTEXTURE9 m_pSpecularMap; + LPDIRECT3DVOLUMETEXTURE9 m_pFalloffMap; + DWORD m_dwVertexShaderLight; + DWORD m_dwPixelShaderLight; + DWORD m_dwVertexShaderSpecular_Dynamic; + DWORD m_dwPixelShaderSpecular_Dynamic; + DWORD m_dwVertexShaderSpecular_Static; + DWORD m_dwPixelShaderSpecular_Static; + DWORD m_dwVertexShaderEnvironment; + DWORD m_dwVertexShaderBump; + DWORD m_dwPixelShaderBump; + bool m_bInLightPhase; + bool m_bInitialized; + +public: + LightEffects(); + virtual ~LightEffects(); + + bool Initialize(); + void ProcessVertices(D3DXVECTOR3* pDirLightDir, D3DXVECTOR3* pPtLightPos); + bool RenderDynamicLights(); + bool RenderStaticLights(); + void RenderSpecular(); + bool RenderSpecular_Dynamic(); + bool RenderSpecular_Static(); + bool RenderEnvironment(); + void RenderBump(); + bool CreateNormalizationCubeMap( DWORD dwSize, LPDIRECT3DCUBETEXTURE9* ppCubeMap ); + void StartLightPhase(); + void EndLightPhase(); +}; + + +#endif \ No newline at end of file diff --git a/code/win32/win_local.h b/code/win32/win_local.h new file mode 100644 index 0000000..490d8fc --- /dev/null +++ b/code/win32/win_local.h @@ -0,0 +1,79 @@ +// win_local.h: Win32-specific Quake3 header file + +#if defined (_MSC_VER) && (_MSC_VER >= 1200) +#pragma warning(disable : 4201) +#pragma warning( push ) +#endif + +#if defined (_MSC_VER) && (_MSC_VER >= 1200) +#pragma warning( pop ) +#endif + +#ifndef _XBOX +#define DIRECTINPUT_VERSION 0x0800 //[ 0x0300 | 0x0500 | 0x0700 | 0x0800 ] +#include +#include +#else +#include "../qcommon/platform.h" +#endif + +void IN_MouseEvent (int mstate); + +void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ); + +void Sys_CreateConsole( void ); +void Sys_DestroyConsole( void ); + +char *Sys_ConsoleInput (void); + +// Input subsystem + +void IN_Init (void); +void IN_Shutdown (void); +void IN_JoystickCommands (void); + +void IN_Move (usercmd_t *cmd); +// add additional non keyboard / non mouse movement on top of the keyboard move cmd + +void IN_DeactivateWin32Mouse( void); + +void IN_Activate (qboolean active); +void IN_Frame (void); + +// window procedure +#ifndef _XBOX +LONG WINAPI MainWndProc ( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam); +#endif + +void Conbuf_AppendText( const char *msg ); + +void SNDDMA_Activate( qboolean bAppActive ); + +#ifndef _XBOX +typedef struct +{ + HWND hWnd; + HINSTANCE hInstance; + qboolean activeApp; + qboolean isMinimized; + OSVERSIONINFO osversion; + + // when we get a windows message, we store the time off so keyboard processing + // can know the exact time of an event + unsigned sysMsgTime; +} WinVars_t; + +extern WinVars_t g_wv; +#endif + + +#define MAX_QUED_EVENTS 256 +#define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 ) + +#define YELLOW_MODE 0 + + diff --git a/code/win32/win_main.cpp b/code/win32/win_main.cpp new file mode 100644 index 0000000..374e1bd --- /dev/null +++ b/code/win32/win_main.cpp @@ -0,0 +1,1241 @@ +// win_main.h + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "../client/client.h" +#include "win_local.h" +#include "resource.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// The following macros set and clear, respectively, given bits +// of the C runtime library debug flag, as specified by a bitmask. + +#ifdef _DEBUG +#define SET_CRT_DEBUG_FIELD(a) \ + _CrtSetDbgFlag((a) | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)) +#define CLEAR_CRT_DEBUG_FIELD(a) \ + _CrtSetDbgFlag(~(a) & _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)) +#else +#define SET_CRT_DEBUG_FIELD(a) ((void) 0) +#define CLEAR_CRT_DEBUG_FIELD(a) ((void) 0) +#endif + +#define CD_BASEDIR "gamedata\\gamedata" +#define CD_EXE "jasp.exe" +#define CD_VOLUME "JEDIACAD" + +#define MEM_THRESHOLD 128*1024*1024 + +static char sys_cmdline[MAX_STRING_CHARS]; + + +/* +================== +Sys_GetFileTime() +================== +*/ +bool Sys_GetFileTime(LPCSTR psFileName, FILETIME &ft) +{ + bool bSuccess = false; + HANDLE hFile = INVALID_HANDLE_VALUE; + + hFile = CreateFile( psFileName, // LPCTSTR lpFileName, // pointer to name of the file + GENERIC_READ, // DWORD dwDesiredAccess, // access (read-write) mode + FILE_SHARE_READ, // DWORD dwShareMode, // share mode + NULL, // LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes + OPEN_EXISTING, // DWORD dwCreationDisposition, // how to create + FILE_FLAG_NO_BUFFERING,// DWORD dwFlagsAndAttributes, // file attributes + NULL // HANDLE hTemplateFile // handle to file with attributes to + ); + + if (hFile != INVALID_HANDLE_VALUE) + { + if (GetFileTime(hFile, // handle to file + NULL, // LPFILETIME lpCreationTime + NULL, // LPFILETIME lpLastAccessTime + &ft // LPFILETIME lpLastWriteTime + ) + ) + { + bSuccess = true; + } + + CloseHandle(hFile); + } + + return bSuccess; +} + + +/* +================== +Sys_FileOutOfDate() +================== +*/ +qboolean Sys_FileOutOfDate( LPCSTR psFinalFileName /* dest */, LPCSTR psDataFileName /* src */ ) +{ + FILETIME ftFinalFile, ftDataFile; + + if (Sys_GetFileTime(psFinalFileName, ftFinalFile) && Sys_GetFileTime(psDataFileName, ftDataFile)) + { + // timer res only accurate to within 2 seconds on FAT, so can't do exact compare... + // + //LONG l = CompareFileTime( &ftFinalFile, &ftDataFile ); + if ( (abs(ftFinalFile.dwLowDateTime - ftDataFile.dwLowDateTime) <= 20000000 ) && + ftFinalFile.dwHighDateTime == ftDataFile.dwHighDateTime + ) + { + return false; // file not out of date, ie use it. + } + return true; // flag return code to copy over a replacement version of this file + } + + + // extra error check, report as suspicious if you find a file locally but not out on the net.,. + // + if (com_developer->integer) + { + if (!Sys_GetFileTime(psDataFileName, ftDataFile)) + { + Com_Printf( "Sys_FileOutOfDate: reading %s but it's not on the net!\n", psFinalFileName); + } + } + + return false; +} + +/* +================== +Sys_LowPhysicalMemory() +================== +*/ +qboolean Sys_CopyFile(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, qboolean bOverWrite) +{ + qboolean bOk = qtrue; + if (!CopyFile( lpExistingFileName, lpNewFileName, !bOverWrite ) && bOverWrite) + { + DWORD dwAttrs = GetFileAttributes(lpNewFileName); + SetFileAttributes(lpNewFileName, dwAttrs & ~FILE_ATTRIBUTE_READONLY); + bOk = CopyFile( lpExistingFileName, lpNewFileName, FALSE ); + } + return bOk; +} + +/* +================== +Sys_LowPhysicalMemory() +================== +*/ +qboolean Sys_LowPhysicalMemory() +{ + static MEMORYSTATUS stat; + static qboolean bAsked = qfalse; + static cvar_t* sys_lowmem = Cvar_Get( "sys_lowmem", "0", 0 ); + + if (!bAsked) // just in case it takes a little time for GlobalMemoryStatus() to gather stats on + { // stuff we don't care about such as virtual mem etc. + bAsked = qtrue; + GlobalMemoryStatus (&stat); + } + if (sys_lowmem->integer) + { + return qtrue; + } + return (stat.dwTotalPhys <= MEM_THRESHOLD) ? qtrue : qfalse; +} + + +/* +================== +Sys_BeginProfiling +================== +*/ +void Sys_BeginProfiling( void ) { + // this is just used on the mac build +} + +/* +============= +Sys_Error + +Show the early console as an error dialog +============= +*/ +void QDECL Sys_Error( const char *error, ... ) { + va_list argptr; + char text[4096]; + MSG msg; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + Conbuf_AppendText( text ); + Conbuf_AppendText( "\n" ); + + Sys_SetErrorText( text ); + Sys_ShowConsole( 1, qtrue ); + + timeEndPeriod( 1 ); + + IN_Shutdown(); + + // wait for the user to quit + while ( 1 ) { + if (!GetMessage (&msg, NULL, 0, 0)) + Com_Quit_f (); + TranslateMessage (&msg); + DispatchMessage (&msg); + } + + Sys_DestroyConsole(); + Com_ShutdownZoneMemory(); + Com_ShutdownHunkMemory(); + + exit (1); +} + +/* +============== +Sys_Quit +============== +*/ +void Sys_Quit( void ) { + timeEndPeriod( 1 ); + IN_Shutdown(); + Sys_DestroyConsole(); + Com_ShutdownZoneMemory(); + Com_ShutdownHunkMemory(); + + exit (0); +} + +/* +============== +Sys_Print +============== +*/ +void Sys_Print( const char *msg ) { + Conbuf_AppendText( msg ); +} + + +/* +============== +Sys_Mkdir +============== +*/ +void Sys_Mkdir( const char *path ) { + _mkdir (path); +} + +/* +============== +Sys_Cwd +============== +*/ +char *Sys_Cwd( void ) { + static char cwd[MAX_OSPATH]; + + _getcwd( cwd, sizeof( cwd ) - 1 ); + cwd[MAX_OSPATH-1] = 0; + + return cwd; +} + +/* +============== +Sys_DefaultCDPath +============== +*/ +char *Sys_DefaultCDPath( void ) { + return ""; +} + +/* +============== +Sys_DefaultBasePath +============== +*/ +char *Sys_DefaultBasePath( void ) { + return Sys_Cwd(); +} + +/* +============================================================== + +DIRECTORY SCANNING + +============================================================== +*/ + +#define MAX_FOUND_FILES 0x1000 + +char **Sys_ListFiles( const char *directory, const char *extension, int *numfiles, qboolean wantsubs ) { + char search[MAX_OSPATH]; + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + struct _finddata_t findinfo; + int findhandle; + int flag; + int i; + + if ( !extension) { + extension = ""; + } + + // passing a slash as extension will find directories + if ( extension[0] == '/' && extension[1] == 0 ) { + extension = ""; + flag = 0; + } else { + flag = _A_SUBDIR; + } + + Com_sprintf( search, sizeof(search), "%s\\*%s", directory, extension ); + + // search + nfiles = 0; + + findhandle = _findfirst (search, &findinfo); + if (findhandle == -1) { + *numfiles = 0; + return NULL; + } + + do { + if ( (!wantsubs && flag ^ ( findinfo.attrib & _A_SUBDIR )) || (wantsubs && findinfo.attrib & _A_SUBDIR) ) { + if ( nfiles == MAX_FOUND_FILES - 1 ) { + break; + } + list[ nfiles ] = CopyString( findinfo.name ); + nfiles++; + } + } while ( _findnext (findhandle, &findinfo) != -1 ); + + list[ nfiles ] = 0; + + _findclose (findhandle); + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = (char **) Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ), TAG_LISTFILES, qfalse); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + +void Sys_FreeFileList( char **filelist ) { + int i; + + if ( !filelist ) { + return; + } + + for ( i = 0 ; filelist[i] ; i++ ) { + Z_Free( filelist[i] ); + } + + Z_Free( filelist ); +} + +//======================================================== + + +/* +================ +Sys_ScanForCD + +Search all the drives to see if there is a valid CD to grab +the cddir from +================ +*/ +#ifdef FINAL_BUILD +static qboolean Sys_ScanForCD( void ) { + char drive[4]; + FILE *f; + char test[MAX_OSPATH]; + + drive[0] = 'c'; + drive[1] = ':'; + drive[2] = '\\'; + drive[3] = 0; + + // scan the drives + for ( drive[0] = 'c' ; drive[0] <= 'z' ; drive[0]++ ) { + if ( GetDriveType (drive) == DRIVE_CDROM ) { + BOOL Result; + char VolumeName[MAX_PATH],FileSystemName[MAX_PATH]; + DWORD VolumeSerialNumber,MaximumComponentLength,FileSystemFlags; + + Result = GetVolumeInformation(drive,VolumeName,sizeof(VolumeName),&VolumeSerialNumber, + &MaximumComponentLength,&FileSystemFlags,FileSystemName,sizeof(FileSystemName)); + + if (Result && (strnicmp(VolumeName,CD_VOLUME,8) == 0 ) ) + { + sprintf (test, "%s%s\\%s", drive, CD_BASEDIR, CD_EXE); + f = fopen( test, "r"); + if ( f ) { + fclose (f); + return Result; + } + } + } + } + + return qfalse; +} +#endif +/* +================ +Sys_CheckCD + +Return true if the proper CD is in the drive +================ +*/ +qboolean Sys_CheckCD( void ) { +#ifdef FINAL_BUILD + return Sys_ScanForCD(); +#else + return qtrue; +#endif +} + +/* +================ +Sys_GetClipboardData + +================ +*/ +char *Sys_GetClipboardData( void ) { + char *data = NULL; + char *cliptext; + + if ( OpenClipboard( NULL ) != 0 ) { + HANDLE hClipboardData; + + if ( ( hClipboardData = GetClipboardData( CF_TEXT ) ) != 0 ) { + if ( ( cliptext = (char *) GlobalLock( hClipboardData ) ) != 0 ) { + data = (char *) Z_Malloc( GlobalSize( hClipboardData ) + 1, TAG_CLIPBOARD, qfalse); + strcpy( data, cliptext ); + GlobalUnlock( hClipboardData ); + + strtok( data, "\n\r\b" ); + } + } + CloseClipboard(); + } + return data; +} + + +/* +======================================================================== + +GAME DLL + +======================================================================== +*/ +static HINSTANCE game_library; + +/* +================= +Sys_UnloadGame +================= +*/ +void Sys_UnloadGame( void ) { + if ( !game_library ) { + return; + } + if ( !FreeLibrary (game_library) ) { + Com_Error (ERR_FATAL, "FreeLibrary failed for game library"); + } + game_library = NULL; +} + +/* +================= +Sys_GetGameAPI + +Loads the game dll +================= +*/ +void *Sys_GetGameAPI (void *parms) +{ + void *(*GetGameAPI) (void *); + char name[MAX_OSPATH]; + char cwd[MAX_OSPATH]; +#if defined _M_IX86 + const char *gamename = "jagamex86.dll"; + +#ifdef NDEBUG + const char *debugdir = "release"; +#elif MEM_DEBUG + const char *debugdir = "shdebug"; +#else + const char *debugdir = "debug"; +#endif //NDEBUG + +#elif defined _M_ALPHA + const char *gamename = "jagameaxp.dll"; + +#ifdef NDEBUG + const char *debugdir = "releaseaxp"; +#else + const char *debugdir = "debugaxp"; +#endif //NDEBUG + +#endif //_M__IX86 + + if (game_library) + Com_Error (ERR_FATAL, "Sys_GetGameAPI without Sys_UnloadingGame"); + + // check the current debug directory first for development purposes + _getcwd (cwd, sizeof(cwd)); + Com_sprintf (name, sizeof(name), "%s/%s/%s", cwd, debugdir, gamename); + game_library = LoadLibrary ( name ); + if (game_library) + { + Com_DPrintf ("LoadLibrary (%s)\n", name); + } + else + { + // check the current directory for other development purposes + Com_sprintf (name, sizeof(name), "%s/%s", cwd, gamename); + game_library = LoadLibrary ( name ); + if (game_library) + { + Com_DPrintf ("LoadLibrary (%s)\n", name); + } else { + char *buf; + + FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &buf, 0, NULL ); + + Com_Printf( "LoadLibrary(\"%s\") failed\n", name); + Com_Printf( "...reason: '%s'\n", buf ); + Com_Error( ERR_FATAL, "Couldn't load game" ); + } + } + + GetGameAPI = (void *(*)(void *))GetProcAddress (game_library, "GetGameAPI"); + if (!GetGameAPI) + { + Sys_UnloadGame (); + return NULL; + } + return GetGameAPI (parms); +} + + +/* +================= +Sys_LoadCgame + +Used to hook up a development dll +================= +*/ +void * Sys_LoadCgame( int (**entryPoint)(int, ...), int (*systemcalls)(int, ...) ) +{ + void (*dllEntry)( int (*syscallptr)(int, ...) ); + + dllEntry = ( void (*)( int (*)( int, ... ) ) )GetProcAddress( game_library, "dllEntry" ); + *entryPoint = (int (*)(int,...))GetProcAddress( game_library, "vmMain" ); + if ( !*entryPoint || !dllEntry ) { + FreeLibrary( game_library ); + return NULL; + } + + dllEntry( systemcalls ); + return game_library; +} + +/* +======================================================================== + +BACKGROUND FILE STREAMING + +======================================================================== +*/ + +#if 1 + +void Sys_InitStreamThread( void ) { +} + +void Sys_ShutdownStreamThread( void ) { +} + +void Sys_BeginStreamedFile( fileHandle_t f, int readAhead ) { +} + +void Sys_EndStreamedFile( fileHandle_t f ) { +} + +int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ) { + return FS_Read( buffer, size * count, f ); +} + +void Sys_StreamSeek( fileHandle_t f, int offset, int origin ) { + FS_Seek( f, offset, origin ); +} + + +#else + +typedef struct { + HANDLE threadHandle; + int threadId; + CRITICAL_SECTION crit; + fileHandle_t file; + byte *buffer; + qboolean eof; + int bufferSize; + int streamPosition; // next byte to be returned by Sys_StreamRead + int threadPosition; // next byte to be read from file +} streamState_t; + +streamState_t stream; + +/* +=============== +Sys_StreamThread + +A thread will be sitting in this loop forever +================ +*/ +void Sys_StreamThread( void ) { + int buffer; + int count; + int readCount; + int bufferPoint; + int r; + + while (1) { + Sleep( 10 ); + EnterCriticalSection (&stream.crit); + + // if there is any space left in the buffer, fill it up + while ( !stream.eof ) { + count = stream.bufferSize - (stream.threadPosition - stream.streamPosition); + if ( !count ) { + break; + } + + bufferPoint = stream.threadPosition % stream.bufferSize; + buffer = stream.bufferSize - bufferPoint; + readCount = buffer < count ? buffer : count; + + r = FS_Read( stream.buffer + bufferPoint, readCount, stream.file ); + stream.threadPosition += r; + + if ( r != readCount ) { + stream.eof = qtrue; + break; + } + } + + LeaveCriticalSection (&stream.crit); + } +} + +/* +=============== +Sys_InitStreamThread + +================ +*/ +void Sys_InitStreamThread( void ) { + + InitializeCriticalSection ( &stream.crit ); + + // don't leave the critical section until there is a + // valid file to stream, which will cause the StreamThread + // to sleep without any overhead + EnterCriticalSection( &stream.crit ); + + stream.threadHandle = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + (LPTHREAD_START_ROUTINE)Sys_StreamThread, // LPTHREAD_START_ROUTINE lpStartAddr, + 0, // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + (unsigned long *) &stream.threadId); +} + +/* +=============== +Sys_ShutdownStreamThread + +================ +*/ +void Sys_ShutdownStreamThread( void ) { +} + + +/* +=============== +Sys_BeginStreamedFile + +================ +*/ +void Sys_BeginStreamedFile( fileHandle_t f, int readAhead ) { + if ( stream.file ) { + Sys_EndStreamedFile( stream.file ); + } + + stream.file = f; + stream.buffer = (unsigned char *) Z_Malloc( readAhead ); + stream.bufferSize = readAhead; + stream.streamPosition = 0; + stream.threadPosition = 0; + stream.eof = qfalse; + + // let the thread start running + LeaveCriticalSection( &stream.crit ); +} + +/* +=============== +Sys_EndStreamedFile + +================ +*/ +void Sys_EndStreamedFile( fileHandle_t f ) { + if ( f != stream.file ) { + Com_Error( ERR_FATAL, "Sys_EndStreamedFile: wrong file"); + } + // don't leave critical section until another stream is started + EnterCriticalSection( &stream.crit ); + + stream.file = 0; + Z_Free( stream.buffer ); + +} + + +/* +=============== +Sys_StreamedRead + +================ +*/ +int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ) { + int available; + int remaining; + int sleepCount; + int copy; + int bufferCount; + int bufferPoint; + byte *dest; + + dest = (byte *)buffer; + remaining = size * count; + + if ( remaining <= 0 ) { + Com_Error( ERR_FATAL, "Streamed read with non-positive size" ); + } + + sleepCount = 0; + while ( remaining > 0 ) { + available = stream.threadPosition - stream.streamPosition; + if ( !available ) { + if ( stream.eof ) { + break; + } + if ( sleepCount == 1 ) { + Com_DPrintf( "Sys_StreamedRead: waiting\n" ); + } + if ( ++sleepCount > 100 ) { + Com_Error( ERR_FATAL, "Sys_StreamedRead: thread has died"); + } + Sleep( 10 ); + continue; + } + + bufferPoint = stream.streamPosition % stream.bufferSize; + bufferCount = stream.bufferSize - bufferPoint; + + copy = available < bufferCount ? available : bufferCount; + if ( copy > remaining ) { + copy = remaining; + } + memcpy( dest, stream.buffer + bufferPoint, copy ); + stream.streamPosition += copy; + dest += copy; + remaining -= copy; + } + + return (count * size - remaining) / size; +} + +/* +=============== +Sys_StreamSeek + +================ +*/ +void Sys_StreamSeek( fileHandle_t f, int offset, int origin ) { + + // halt the thread + EnterCriticalSection( &stream.crit ); + + // clear to that point + FS_Seek( f, offset, origin ); + stream.streamPosition = 0; + stream.threadPosition = 0; + stream.eof = qfalse; + + // let the thread start running at the new position + LeaveCriticalSection( &stream.crit ); +} + +#endif + + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +#define MAX_QUED_EVENTS 256 +#define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 ) + +sysEvent_t eventQue[MAX_QUED_EVENTS]; +int eventHead, eventTail; +byte sys_packetReceived[MAX_MSGLEN]; + +/* +================ +Sys_QueEvent + +A time of 0 will get the current time +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) { + sysEvent_t *ev; + + ev = &eventQue[ eventHead & MASK_QUED_EVENTS ]; + if ( eventHead - eventTail >= MAX_QUED_EVENTS ) { + Com_Printf("Sys_QueEvent: overflow\n"); + // we are discarding an event, but don't leak memory + if ( ev->evPtr ) { + Z_Free( ev->evPtr ); + } + eventTail++; + } + + eventHead++; + + if ( time == 0 ) { + time = Sys_Milliseconds(); + } + + ev->evTime = time; + ev->evType = type; + ev->evValue = value; + ev->evValue2 = value2; + ev->evPtrLength = ptrLength; + ev->evPtr = ptr; +} + +/* +================ +Sys_GetEvent + +================ +*/ +sysEvent_t Sys_GetEvent( void ) { + MSG msg; + sysEvent_t ev; + char *s; + msg_t netmsg; + + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // pump the message loop + while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) { + if ( !GetMessage (&msg, NULL, 0, 0) ) { + Com_Quit_f(); + } + + // save the msg time, because wndprocs don't have access to the timestamp + g_wv.sysMsgTime = msg.time; + + TranslateMessage (&msg); + DispatchMessage (&msg); + } + + // check for console commands + s = Sys_ConsoleInput(); + if ( s ) { + char *b; + int len; + + len = strlen( s ) + 1; + b = (char *) Z_Malloc( len, TAG_EVENT, qfalse); + strcpy( b, s ); + Sys_QueEvent( 0, SE_CONSOLE, 0, 0, len, b ); + } + + // check for network packets + MSG_Init( &netmsg, sys_packetReceived, sizeof( sys_packetReceived ) ); + + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // create an empty event to return + + memset( &ev, 0, sizeof( ev ) ); + ev.evTime = timeGetTime(); + + return ev; +} + +//================================================================ + +/* +================= +Sys_In_Restart_f + +Restart the input subsystem +================= +*/ +void Sys_In_Restart_f( void ) { + IN_Shutdown(); + IN_Init(); +} + +static bool Sys_IsExpired() +{ +#if 0 +// sec min Hr Day Mon Yr + struct tm t_valid_start = { 0, 0, 8, 23, 6, 103 }; //zero based months! +// sec min Hr Day Mon Yr + struct tm t_valid_end = { 0, 0, 20, 30, 6, 103 }; +// struct tm t_valid_end = t_valid_start; +// t_valid_end.tm_mday += 8; + time_t startTime = mktime( &t_valid_start); + time_t expireTime = mktime( &t_valid_end); + time_t now; + time(&now); + if((now < startTime) || (now> expireTime)) + { + return true; + } +#endif + return false; +} + +/* +================ +Sys_Init + +Called after the common systems (cvars, files, etc) +are initialized +================ +*/ +#define OSR2_BUILD_NUMBER 1111 +#define WIN98_BUILD_NUMBER 1998 + +#if MEM_DEBUG +void SH_Register(void); +#endif + +void Sys_Init( void ) { + int cpuid; + + // make sure the timer is high precision, otherwise + // NT gets 18ms resolution + timeBeginPeriod( 1 ); + + Cmd_AddCommand ("in_restart", Sys_In_Restart_f); +#if MEM_DEBUG + SH_Register(); +#endif + + g_wv.osversion.dwOSVersionInfoSize = sizeof( g_wv.osversion ); + + if (!GetVersionEx (&g_wv.osversion)) + Sys_Error ("Couldn't get OS info"); + if (Sys_IsExpired()) { + g_wv.osversion.dwPlatformId = VER_PLATFORM_WIN32s; //sneaky: hide the expire with this error + } + + if (g_wv.osversion.dwMajorVersion < 4) + Sys_Error ("This game requires Windows version 4 or greater"); + if (g_wv.osversion.dwPlatformId == VER_PLATFORM_WIN32s) + Sys_Error ("This game doesn't run on Win32s"); + + if ( g_wv.osversion.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + Cvar_Set( "arch", "winnt" ); + } + else if ( g_wv.osversion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS ) + { + if ( LOWORD( g_wv.osversion.dwBuildNumber ) >= WIN98_BUILD_NUMBER ) + { + Cvar_Set( "arch", "win98" ); + } + else if ( LOWORD( g_wv.osversion.dwBuildNumber ) >= OSR2_BUILD_NUMBER ) + { + Cvar_Set( "arch", "win95 osr2.x" ); + } + else + { + Cvar_Set( "arch", "win95" ); + } + } + else + { + Cvar_Set( "arch", "unknown Windows variant" ); + } + + // save out a couple things in rom cvars for the renderer to access + Cvar_Get( "win_hinstance", va("%i", (int)g_wv.hInstance), CVAR_ROM ); + Cvar_Get( "win_wndproc", va("%i", (int)MainWndProc), CVAR_ROM ); + + // + // figure out our CPU + // + Cvar_Get( "sys_cpustring", "detect", CVAR_ROM ); + if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring"), "detect" ) ) + { + Com_Printf( "...detecting CPU, found " ); + + cpuid = Sys_GetProcessorId(); + + switch ( cpuid ) + { + case CPUID_GENERIC: + Cvar_Set( "sys_cpustring", "generic" ); + break; + case CPUID_INTEL_UNSUPPORTED: + Cvar_Set( "sys_cpustring", "x86 (pre-Pentium)" ); + break; + case CPUID_INTEL_PENTIUM: + Cvar_Set( "sys_cpustring", "x86 (P5/PPro, non-MMX)" ); + break; + case CPUID_INTEL_MMX: + Cvar_Set( "sys_cpustring", "x86 (P5/Pentium2, MMX)" ); + break; + case CPUID_INTEL_KATMAI: + Cvar_Set( "sys_cpustring", "Intel Pentium III" ); + break; + case CPUID_INTEL_WILLIAMETTE: + Cvar_Set( "sys_cpustring", "Intel Pentium IV" ); + break; + case CPUID_AMD_3DNOW: + Cvar_Set( "sys_cpustring", "AMD w/ 3DNow!" ); + break; + case CPUID_AXP: + Cvar_Set( "sys_cpustring", "Alpha AXP" ); + break; + default: + Com_Error( ERR_FATAL, "Unknown cpu type %d\n", cpuid ); + break; + } + } + else + { + Com_Printf( "...forcing CPU type to " ); + if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "generic" ) ) + { + cpuid = CPUID_GENERIC; + } + else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "x87" ) ) + { + cpuid = CPUID_INTEL_PENTIUM; + } + else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "mmx" ) ) + { + cpuid = CPUID_INTEL_MMX; + } + else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "3dnow" ) ) + { + cpuid = CPUID_AMD_3DNOW; + } + else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "PentiumIII" ) ) + { + cpuid = CPUID_INTEL_KATMAI; + } + else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "PentiumIV" ) ) + { + cpuid = CPUID_INTEL_WILLIAMETTE; + } + else if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "axp" ) ) + { + cpuid = CPUID_AXP; + } + else + { + Com_Printf( "WARNING: unknown sys_cpustring '%s'\n", Cvar_VariableString( "sys_cpustring" ) ); + cpuid = CPUID_GENERIC; + } + } + Cvar_SetValue( "sys_cpuid", cpuid ); + Com_Printf( "%s\n", Cvar_VariableString( "sys_cpustring" ) ); + + Cvar_Set( "username", Sys_GetCurrentUser() ); + + IN_Init(); // FIXME: not in dedicated? +} + + + +// do a quick mem test to check for any potential future mem problems... +// +static void QuickMemTest(void) +{ +// if (!Sys_LowPhysicalMemory()) + { + const int iMemTestMegs = 128; // useful search label + // special test, + void *pvData = malloc(iMemTestMegs * 1024 * 1024); + if (pvData) + { + free(pvData); + } + else + { + // err... + // + extern qboolean Language_IsAsian(void); + LPCSTR psContinue = Language_IsAsian() ? + "Your machine failed to allocate %dMB in a memory test, which may mean you'll have problems running this game all the way through.\n\nContinue anyway?" + : + SE_GetString("CON_TEXT_FAILED_MEMTEST"); + // ( since it's too much hassle doing MBCS code pages and decodings etc for MessageBox command ) + + #define GetYesNo(psQuery) (!!(MessageBox(NULL,psQuery,"Query",MB_YESNO|MB_ICONWARNING|MB_TASKMODAL)==IDYES)) + if (!GetYesNo(va(psContinue,iMemTestMegs))) + { + LPCSTR psNoMem = Language_IsAsian() ? + "Insufficient memory to run this game!\n" + : + SE_GetString("CON_TEXT_INSUFFICIENT_MEMORY"); + // ( since it's too much hassle doing MBCS code pages and decodings etc for MessageBox command ) + + Com_Error( ERR_FATAL, psNoMem ); + } + } + } +} + + +//======================================================================= +//int totalMsec, countMsec; + +/* +================== +WinMain + +================== +*/ +int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { + char cwd[MAX_OSPATH]; +// int startTime, endTime; + + SET_CRT_DEBUG_FIELD( _CRTDBG_LEAK_CHECK_DF ); +// _CrtSetBreakAlloc(34804); + + // should never get a previous instance in Win32 + if ( hPrevInstance ) { + return 0; + } + + g_wv.hInstance = hInstance; + Q_strncpyz( sys_cmdline, lpCmdLine, sizeof( sys_cmdline ) ); + + // done before Com/Sys_Init since we need this for error output + Sys_CreateConsole(); + + // no abort/retry/fail errors + SetErrorMode( SEM_FAILCRITICALERRORS ); + + // get the initial time base + Sys_Milliseconds(); + +#if 0 + // if we find the CD, add a +set cddir xxx command line + Sys_ScanForCD(); +#endif + + Sys_InitStreamThread(); + + Com_Init( sys_cmdline ); + + QuickMemTest(); + + _getcwd (cwd, sizeof(cwd)); + Com_Printf("Working directory: %s\n", cwd); + + // hide the early console since we've reached the point where we + // have a working graphics subsystems + if ( !com_viewlog->integer ) { + Sys_ShowConsole( 0, qfalse ); + } + + // main game loop + while( 1 ) { + // if not running as a game client, sleep a bit + if ( g_wv.isMinimized ) { + Sleep( 5 ); + } +#ifdef _DEBUG + if (!g_wv.activeApp) + { + Sleep(50); + } +#endif // _DEBUG + + // set low precision every frame, because some system calls + // reset it arbitrarily +// _controlfp( _PC_24, _MCW_PC ); + +// startTime = Sys_Milliseconds(); + + // make sure mouse and joystick are only called once a frame + IN_Frame(); + + // run the game + Com_Frame(); + +// endTime = Sys_Milliseconds(); +// totalMsec += endTime - startTime; +// countMsec++; + } + + // never gets here +} diff --git a/code/win32/win_main_common.cpp b/code/win32/win_main_common.cpp new file mode 100644 index 0000000..6c0967e --- /dev/null +++ b/code/win32/win_main_common.cpp @@ -0,0 +1,332 @@ +// win_main.h + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../client/client.h" +#include "win_local.h" +#include "resource.h" + +#ifndef _GAMECUBE +#include +#include +#include +#include +#include +#include +#include +#endif + + + +//#define SPANK_MONKEYS //----(SA) commented out for running net developer release builds +int sys_monkeySpank; + + +/* +================== +Sys_MonkeyShouldBeSpanked +================== +*/ +int Sys_MonkeyShouldBeSpanked( void ) { + return sys_monkeySpank; +} + + + + +/* +================== +Sys_FunctionCmp +================== +*/ +int Sys_FunctionCmp(void *f1, void *f2) { + + int i, j, l; + byte func_end[32] = {0xC3, 0x90, 0x90, 0x00}; + byte *ptr, *ptr2; + byte *f1_ptr, *f2_ptr; + + ptr = (byte *) f1; + if (*(byte *)ptr == 0xE9) { + //Com_Printf("f1 %p1 jmp %d\n", (int *) f1, *(int*)(ptr+1)); + f1_ptr = (byte*)(((byte*)f1) + (*(int *)(ptr+1)) + 5); + } + else { + f1_ptr = ptr; + } + //Com_Printf("f1 ptr %p\n", f1_ptr); + + ptr = (byte *) f2; + if (*(byte *)ptr == 0xE9) { + //Com_Printf("f2 %p jmp %d\n", (int *) f2, *(int*)(ptr+1)); + f2_ptr = (byte*)(((byte*)f2) + (*(int *)(ptr+1)) + 5); + } + else { + f2_ptr = ptr; + } + //Com_Printf("f2 ptr %p\n", f2_ptr); + +#ifdef _DEBUG + sprintf((char *)func_end, "%c%c%c%c%c%c%c", 0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3); +#endif + for (i = 0; i < 1024; i++) { + for (j = 0; func_end[j]; j++) { + if (f1_ptr[i+j] != func_end[j]) + break; + } + if (!func_end[j]) { + break; + } + } +#ifdef _DEBUG + l = i + 7; +#else + l = i + 2; +#endif + //Com_Printf("function length = %d\n", l); + + for (i = 0; i < l; i++) { + // check for a potential function call + if (*((byte *) &f1_ptr[i]) == 0xE8) { + // get the function pointers in case this really is a function call + ptr = (byte *) (((byte *) &f1_ptr[i]) + (*(int *) &f1_ptr[i+1])) + 5; + ptr2 = (byte *) (((byte *) &f2_ptr[i]) + (*(int *) &f2_ptr[i+1])) + 5; + // if it was a function call and both f1 and f2 call the same function + if (ptr == ptr2) { + i += 4; + continue; + } + } + if (f1_ptr[i] != f2_ptr[i]) + return qfalse; + } + return qtrue; +} + +/* +================== +Sys_FunctionCheckSum +================== +*/ +int Sys_FunctionCheckSum(void *f1) { + + int i, j, l; + unsigned shermcrap; + byte func_end[32] = {0xC3, 0x90, 0x90, 0x00}; + byte *ptr; + byte *f1_ptr; + + ptr = (byte *) f1; + if (*(byte *)ptr == 0xE9) { + //Com_Printf("f1 %p1 jmp %d\n", (int *) f1, *(int*)(ptr+1)); + f1_ptr = (byte*)(((byte*)f1) + (*(int *)(ptr+1)) + 5); + } + else { + f1_ptr = ptr; + } + //Com_Printf("f1 ptr %p\n", f1_ptr); + +#ifdef _DEBUG + sprintf((char *)func_end, "%c%c%c%c%c%c%c", 0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3); +#endif + for (i = 0; i < 1024; i++) { + for (j = 0; func_end[j]; j++) { + if (f1_ptr[i+j] != func_end[j]) + break; + } + if (!func_end[j]) { + break; + } + } +#ifdef _DEBUG + l = i + 7; +#else + l = i + 2; +#endif + //Com_Printf("function length = %d\n", l); + shermcrap = Com_BlockChecksum( f1_ptr, l ); + return (int)shermcrap; +} + + +//NOTE TTimo: heavily NON PORTABLE, PLZ DON'T USE +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=447 +#if 0 +//----(SA) added +/* +============== +Sys_ShellExecute + +- Windows only + + Performs an operation on a specified file. + + See info on ShellExecute() for details + +============== +*/ +int Sys_ShellExecute(char *op, char *file, qboolean doexit, char *params, char *dir ) { + unsigned int retval; + char *se_op; + + // set default operation to "open" + if(op) se_op = op; + else se_op = "open"; + + + // probably need to protect this some in the future so people have + // less chance of system invasion with this powerful interface + // (okay, not so invasive, but could be annoying/rude) + + + retval = (UINT)ShellExecute(NULL, se_op, file, params, dir, SW_NORMAL); // only option forced by game is 'sw_normal' + + if( retval <= 32) { // ERROR + Com_DPrintf("Sys_ShellExecuteERROR: %d\n", retval); + return retval; + } + + if ( doexit ) { + // (SA) this works better for exiting cleanly... + Cbuf_ExecuteText( EXEC_APPEND, "quit" ); + } + + return 999; // success +} +//----(SA) end +#endif + +/* +================== +Sys_BeginProfiling +================== +*/ +void Sys_BeginProfiling( void ) { + // this is just used on the mac build +} + + + + + +/* +============== +Sys_DefaultCDPath +============== +*/ +char *Sys_DefaultCDPath( void ) { + return ""; +} + +/* +============== +Sys_DefaultBasePath +============== +*/ +char *Sys_DefaultBasePath( void ) { + return Sys_Cwd(); +} + + + + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + + +sysEvent_t eventQue[MAX_QUED_EVENTS]; +int eventHead, eventTail; +byte sys_packetReceived[MAX_MSGLEN]; + +/* +================ +Sys_QueEvent + +A time of 0 will get the current time +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) { + sysEvent_t *ev; + + ev = &eventQue[ eventHead & MASK_QUED_EVENTS ]; + if ( eventHead - eventTail >= MAX_QUED_EVENTS ) { + Com_Printf("Sys_QueEvent: overflow\n"); + // we are discarding an event, but don't leak memory + if ( ev->evPtr ) { + Z_Free( ev->evPtr ); + } + eventTail++; + } + + eventHead++; + + if ( time == 0 ) { + time = Sys_Milliseconds(); + } + + ev->evTime = time; + ev->evType = type; + ev->evValue = value; + ev->evValue2 = value2; + ev->evPtrLength = ptrLength; + ev->evPtr = ptr; +} + + +//================================================================ + + +/* +================= +Sys_Net_Restart_f + +Restart the network subsystem +================= +*/ +void Sys_Net_Restart_f( void ) { +// NET_Restart(); +} + + + +//======================================================================= + + +void Sys_InitStreamThread( void ) { +} + +void Sys_ShutdownStreamThread( void ) { +} + +void Sys_BeginStreamedFile( fileHandle_t f, int readAhead ) { +} + +void Sys_EndStreamedFile( fileHandle_t f ) { +} + +int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ) { + return FS_Read( buffer, size * count, f ); +} + +void Sys_StreamSeek( fileHandle_t f, int offset, int origin ) { + FS_Seek( f, offset, origin ); +} + + +void *Sys_InitializeCriticalSection() { + return (void*)-1; +} + +void Sys_EnterCriticalSection(void *ptr) { +} + +void Sys_LeaveCriticalSection(void *ptr) { +} + diff --git a/code/win32/win_main_console.cpp b/code/win32/win_main_console.cpp new file mode 100644 index 0000000..7f79335 --- /dev/null +++ b/code/win32/win_main_console.cpp @@ -0,0 +1,821 @@ +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../client/client.h" +#include "win_local.h" +#include "resource.h" +#include +#include +#include "../game/g_public.h" +#include +#include "glw_win_dx8.h" +#include "../qcommon/xb_settings.h" + +#ifdef _XBOX +#include +#define NEWDECL __cdecl + +#ifndef FINAL_BUILD +#include "dbg_console_xbox.h" +#endif + +#endif + +int gLaunchController = 0; + +extern int eventHead, eventTail; +extern sysEvent_t eventQue[MAX_QUED_EVENTS]; +extern byte sys_packetReceived[MAX_MSGLEN]; + +void *NEWDECL operator new(size_t size) +{ + return Z_Malloc(size, TAG_NEWDEL, qfalse); +} + + +void *NEWDECL operator new[](size_t size) +{ + return Z_Malloc(size, TAG_NEWDEL, qfalse); +} + + +void NEWDECL operator delete[](void *ptr) +{ + if (ptr) + Z_Free(ptr); +} + + +void NEWDECL operator delete(void *ptr) +{ + if (ptr) + Z_Free(ptr); +} + +/* +================ +Sys_Init + +Called after the common systems (cvars, files, etc) +are initialized +================ +*/ +extern void Sys_In_Restart_f(void); +extern void Sys_Net_Restart_f(void); +void Sys_Init( void ) +{ + Cmd_AddCommand ("in_restart", Sys_In_Restart_f); + Cmd_AddCommand ("net_restart", Sys_Net_Restart_f); +} + +#ifdef XBOX_DEMO +// When we're a demo, we're not running from D:\, so we need some hacks: +char demoBasePath[64]; +#endif + +char *Sys_Cwd( void ) +{ + static char cwd[MAX_OSPATH]; + +#ifdef XBOX_DEMO + strcpy( cwd, demoBasePath ); +#else + strcpy(cwd, "d:"); +#endif + + return cwd; +} + +/* +================= +Sys_In_Restart_f + +Restart the input subsystem +================= +*/ +void Sys_In_Restart_f( void ) { +} + + + +/* +============= +Sys_Error + +Show the early console as an error dialog +============= +*/ +void Sys_Error( const char *error, ... ) { + va_list argptr; + char text[256]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + +#ifdef _GAMECUBE + printf(text); +#else + OutputDebugString(text); +#endif + +#if 0 // UN-PORT + Com_ShutdownZoneMemory(); + Com_ShutdownHunkMemory(); +#endif + + exit (1); +} + + +/* +================ +Sys_GetEvent + +================ +*/ +sysEvent_t Sys_GetEvent( void ) { + sysEvent_t ev; + + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // check for network packets + msg_t netmsg; + MSG_Init( &netmsg, sys_packetReceived, sizeof( sys_packetReceived ) ); + + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // create an empty event to return + memset( &ev, 0, sizeof( ev ) ); + ev.evTime = Sys_Milliseconds(); + + return ev; +} + + +void Sys_Print(const char *msg) +{ +#ifdef _GAMECUBE + printf(msg); +#else + OutputDebugString(msg); +#endif +} + +/* +============== +Sys_Log +============== +*/ +void Sys_Log( const char *file, const char *msg ) { + Sys_Log(file, msg, strlen(msg), strchr(msg, '\n') ? true : false); +} + +/* +============== +Sys_Log +============== +*/ +void Sys_Log( const char *file, const void *buffer, int size, bool flush ) { +#ifndef FINAL_BUILD + static bool unableToLog = false; + + // Once we've failed to write to the log files once, bail out. + // This lets us put release builds on DVD without recompiling. + if (unableToLog) + return; + + struct FileInfo + { + char name[MAX_QPATH]; + FILE *handle; + }; + + const int LOG_MAX_FILES = 4; + static FileInfo files[LOG_MAX_FILES]; + static int num_files = 0; + + FileInfo* cur = NULL; + for (int f = 0; f < num_files; ++f) + { + if (!stricmp(file, files[f].name)) + { + cur = &files[f]; + break; + } + } + + if (cur == NULL) + { + if (num_files >= LOG_MAX_FILES) + { + Sys_Print("Too many log files!\n"); + return; + } + + cur = &files[num_files++]; + strcpy(cur->name, file); + cur->handle = NULL; + } + + char fullname[MAX_QPATH]; + sprintf(fullname, "d:\\%s", cur->name); + if (!cur->handle) + { + cur->handle = fopen(fullname, "wb"); + if (cur->handle == NULL) + { + Sys_Print("Unable to open log file!\n"); + unableToLog = true; + return; + } + } + + if (size == 1) fputc(*(char*)buffer, cur->handle); + else fwrite(buffer, size, 1, cur->handle); + + if (flush) + { + fflush(cur->handle); + } +#endif +} + +#ifdef _XBOX +HANDLE Sys_FileStreamMutex = INVALID_HANDLE_VALUE; +#endif + +void Win_Init(void) +{ +#ifdef _XBOX + Sys_FileStreamMutex = CreateMutex(NULL, FALSE, NULL); +#endif +} + +/* +===================== + +XBE SWITCHING SUPPORT + +===================== +*/ + +#ifdef XBOX_DEMO +// Filled in when we're launched from CDX +LD_DEMO demoLaunchData; +bool demoLaunchDataValid = false; + +// If we were launched by the user, or someone has pressed a key +// then the timer should not count down during movies/loading: +bool demoTimerAlways = false; +int demoTimer = 0; +#endif + +// Takes a filename (relative to ".") and pre-pends the right path. This +// is needed for demos where the game won't be running from D: +const char *Sys_RemapPath( const char *filename ) +{ +#ifdef XBOX_DEMO + return va( "%s\\%s", demoBasePath, filename ); +#else + return va( "D:\\%s", filename ); +#endif +} + +// Despite what you may think, this function actually just returns +// a value telling you if you *should* quick-boot -- ie skip intro +// cinematics and such. Only supposed to XGetLaunchInfo once per +// boot, so we cache the results. +// +// This function always gets called at startup, so we also use it +// to retrieve the launch info for a demo +#define LAUNCH_MAGIC "J3D1" +bool Sys_QuickStart( void ) +{ + static bool retVal = false; + static bool initialized = false; + + if( initialized ) + return retVal; + + initialized = true; + +#ifdef XBOX_DEMO + // Default to D:\, this gets replaced below if CDX started us + strcpy( demoBasePath, "D:" ); + + gLaunchController = 0; // Irrelevant in demo + retVal = false; // We never come from MP (eg), so always false + DWORD launchType; + + DWORD result = XGetLaunchInfo( &launchType, (LAUNCH_DATA *) &demoLaunchData ); + if( result == ERROR_SUCCESS && launchType == LDT_TITLE ) + { + // We were launched by CDX: + demoLaunchDataValid = true; + + // How we were launched affects timer behavior: + demoTimerAlways = (demoLaunchData.dwRunmode == XLDEMO_RUNMODE_KIOSKMODE); + + // Need to re-map paths, as D:\\ doesn't work now: + Q_strncpyz( demoBasePath, demoLaunchData.szLaunchedXBE, sizeof(demoBasePath), qtrue ); + + // Find our executable name in the path, and truncate the string there: + char *pXBE = strstr( demoBasePath, "\\default.xbe" ); + if( !pXBE ) + Com_Error( ERR_FATAL, "Error re-mapping D drive\n" ); + *pXBE = 0; + + // Fix the video path: + extern char XBOX_VIDEO_PATH[64]; + strcpy( XBOX_VIDEO_PATH, demoBasePath ); + strcat( XBOX_VIDEO_PATH, "\\base\\video\\" ); + } + + return retVal; +#else + DWORD launchType; + LAUNCH_DATA ld; + + if( (XGetLaunchInfo( &launchType, &ld ) != ERROR_SUCCESS) || + (launchType != LDT_TITLE) || + strcmp((const char *)&ld.Data[1], LAUNCH_MAGIC) ) + return (retVal = false); + + gLaunchController = ld.Data[0]; + + // Magic number to disable settings/saving + if( ld.Data[5] == 0x42 ) + Settings.Disable(); + + return (retVal = true); +#endif +} + +extern int IN_GetMainController(void); +extern void SP_DrawMPLoadScreen(void); + +// Takes an extra parameter so that the accepted invite code can pass +// in the XONLINE_ACCEPTED_INVITE to be copied into launch data. +// +// For the demo, the only valid reason is "demo", and pData should be NULL +void Sys_Reboot( const char *reason, const void *pData ) +{ +#ifdef XBOX_DEMO + if( pData || !demoLaunchDataValid || Q_stricmp( reason, "demo" ) != 0 ) + Com_Error( ERR_DROP, "Invalid Sys_Reboot call\n" ); + + // Kill off the sound and stream threads: + S_Shutdown(); + extern void Sys_StreamShutdown(void); + Sys_StreamShutdown(); + + // Now return to CDX + XLaunchNewImage( demoLaunchData.szLauncherXBE, (LAUNCH_DATA *) &demoLaunchData ); + // Should never return! +#else + LAUNCH_DATA ld; + const char *path = NULL; + int controller; + + memset( &ld, 0, sizeof(ld) ); + controller = IN_GetMainController(); + ld.Data[0] = (byte) controller; + Com_Printf("\tController %d Passed\n",controller); + + if (!Q_stricmp(reason, "multiplayer")) + { + path = "d:\\jamp.xbe"; + SP_DrawMPLoadScreen(); + + // Set a magic number if saving is disabled + if( Settings.IsDisabled() ) + ld.Data[1] = 0x42; + + // Flag that there is no invite in the launch data: + ld.Data[2] = 0; + } + else if (!Q_stricmp(reason, "invite")) + { + path = "d:\\jamp.xbe"; + SP_DrawMPLoadScreen(); + + // Set a magic number if saving is disabled + if( Settings.IsDisabled() ) + ld.Data[1] = 0x42; + + // Flag that we're including an invite with the launch data: + ld.Data[2] = 1; + + memcpy( &ld.Data[3], pData, sizeof(XONLINE_ACCEPTED_GAMEINVITE) ); + } + else + { + Com_Error( ERR_FATAL, "Unknown reboot code %s\n", reason ); + } + + // Title should not be doing ANYTHING in the background. + // Shutting down sound ensures that the sound thread is gone + S_Shutdown(); + // Similarly, kill off the streaming thread + extern void Sys_StreamShutdown(void); + Sys_StreamShutdown(); + + // Keep the loading screen up while we reboot! + glw_state->device->PersistDisplay(); + + XLaunchNewImage(path, &ld); + + // This function should not return! + Com_Error( ERR_FATAL, "ERROR: XLaunchNewImage returned\n" ); +#endif +} + +static XONLINE_ACCEPTED_GAMEINVITE acceptedGameInvite; + +// Used to check for the presence of an accepted invite for our game on the HD. +// Can only return true on the FIRST call. +bool Sys_InviteExists( void ) +{ +#ifdef XBOX_DEMO + return false; +#else + static bool initialized = false; + if( initialized ) + return false; + initialized = true; + + // If we just came from the MP XBE, don't auto-reboot again. That's just silly. + if( Sys_QuickStart() ) + return false; + + // Try to retrieve an invitation from the HD (this requires that we start XOnline): + XOnlineStartup( NULL ); + HRESULT hr = XOnlineFriendsGetAcceptedGameInvite( &acceptedGameInvite ); + XOnlineCleanup(); + + return (hr == S_OK); +#endif +} + +// Reboot to MP to join the game that we have an invite for: +void Sys_JoinInvite( void ) +{ + // Aha. Well, XTL seems to blow this away now, so we need to copy it to launch_data. Bleh. + Sys_Reboot( "invite", &acceptedGameInvite ); + // Never returns! +} + +#ifdef XBOX_DEMO +// Timer code for the demo: +static int lastTime = 0; +static bool demoTimerPaused = false; + +// Notify the demo timer of a keypress, which resets the timer, and ensures +// that the timer no longer runs during FMV/loading (if it was before) +void Demo_TimerKeypress( void ) +{ + demoTimerAlways = false; + + // Reset the timer: + demoTimer = demoLaunchData.dwTimeout; + + // Stamp the diff-timer + lastTime = Sys_Milliseconds(); +} + +// Update the timer, and check to see if we should reboot: +void Demo_TimerUpdate( void ) +{ + // Handle first call correctly + if( !lastTime ) + { + demoTimer = demoLaunchData.dwTimeout; + lastTime = Sys_Milliseconds(); + } + + int newTime = Sys_Milliseconds(); + int diffTime = newTime - lastTime; + lastTime = newTime; + + // If the timer isn't supposed to run, and we're "paused", don't update: + extern bool in_camera; + if( !demoTimerAlways && (demoTimerPaused || in_camera) ) + return; + + // If we weren't even launched by CDX in the first place, or were given + // a zero timeout, do nothing: + if( !demoLaunchDataValid || !demoLaunchData.dwTimeout ) + return; + + // Time ran out? + if( demoTimer < diffTime ) + Sys_Reboot( "demo", NULL ); + + demoTimer -= diffTime; +} + +// Pause/unpause the demo timer when entering/exiting non-interactive state: +void Demo_TimerPause( bool bPaused ) +{ + // Always stamp the timer right before we change state: + Demo_TimerUpdate(); + + demoTimerPaused = bPaused; +} + +#endif + +/* +================== +WinMain + +================== +*/ +#if defined (_XBOX) +int __cdecl main() +#elif defined (_GAMECUBE) +int main(int argc, char* argv[]) +#endif +{ +// Z_SetFreeOSMem(); + + // I'm going to kill someone. This should not be necessary. No, really. + Direct3D_SetPushBufferSize(1024*1024, 128*1024); + + // get the initial time base + Sys_Milliseconds(); + + // Need to fetch this stuff REALLY early so that path re-mappnig works + // for renderer startup. Bleh. + Sys_QuickStart(); + + Win_Init(); + Com_Init( "" ); + + // Run one frame, to finish loading (calls CL_StartHunkUsers)... + IN_Frame(); + Com_Frame(); + + extern void G_AllocGentities( void ); + G_AllocGentities(); + + // And then quickly copy all the planet binks to the Z: drive + extern void Sys_BinkCopyInit(void); + Sys_BinkCopyInit(); + + // main game loop + while( 1 ) { + IN_Frame(); + Com_Frame(); + + // Poll debug console for new commands +#ifndef FINAL_BUILD + DebugConsoleHandleCommands(); +#endif + } + + return 0; +} + + +char *Sys_GetClipboardData(void) { return NULL; } + +void Sys_StartProcess(char *, qboolean) {} + +void Sys_OpenURL(char *, int) {} + +void Sys_Quit(void) {} + +void Sys_ShowConsole(int, int) {} + +void Sys_Mkdir(const char *) {} + +int Sys_LowPhysicalMemory(void) { return 0; } + +void Sys_FreeFileList(char **filelist) +{ + // All strings in a file list are allocated at once, so we just need to + // do two frees, one for strings, one for the pointers. + if ( filelist ) + { + if ( filelist[0] ) + Z_Free( filelist[0] ); + + Z_Free( filelist ); + } +} + +#ifdef _JK2MP +char** Sys_ListFiles(const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs) +#else +char** Sys_ListFiles(const char *directory, const char *extension, int *numfiles, qboolean wantsubs) +#endif +{ +#ifdef _JK2MP + // MP has extra filter paramter. We don't support that. + if (filter) + { + assert(!"Sys_ListFiles doesn't support filter on console!"); + return NULL; + } +#endif + + // Hax0red console version of Sys_ListFiles. We mangle our arguments to get a standard filename + // That file should exist, and contain the list of files that meet this search criteria. + char listFilename[MAX_OSPATH]; + char *listFile, *curFile, *end; + int nfiles; + char **retList; + + // S00per hack +#ifdef XBOX_DEMO + const char *basePath = Sys_RemapPath( "base\\" ); + if (strstr(directory, basePath)) + directory += strlen( basePath ); +#else + if (strstr(directory, "d:\\base\\")) + directory += 8; +#endif + + if (!extension) + { + extension = ""; + } + else if (extension[0] == '/' && extension[1] == 0) + { + // Passing a slash as extension will find directories + extension = "dir"; + } + else if (extension[0] == '.') + { + // Skip over leading . + extension++; + } + + // Build our filename + Com_sprintf(listFilename, sizeof(listFilename), "%s\\_console_%s_list_", directory, extension); + if (FS_ReadFile( listFilename, (void**)&listFile ) <= 0) + { + if(listFile) { + FS_FreeFile(listFile); + } + Com_Printf( "WARNING: List file %s not found\n", listFilename ); + if (numfiles) + *numfiles = 0; + return NULL; + } + + // Do a first pass to count number of files in the list + nfiles = 0; + curFile = listFile; + while (true) + { + // Find end of line + end = strchr(curFile, '\r'); + if (end) + { + // Should have a \n next -- skip them both + end += 2; + } + else + { + end = strchr(curFile, '\n'); + if (end) end++; + else end = curFile + strlen(curFile); + } + + // Is the line empty? If so, we're done. + if (!curFile || !curFile[0]) break; + ++nfiles; + + // Advance to next line + curFile = end; + } + + // Fill in caller's pointer for number of files found + if (numfiles) *numfiles = nfiles; + + // Did we find any files at all? + if (nfiles == 0) + { + FS_FreeFile(listFile); + return NULL; + } + + // Allocate a file list, and quick string pool, but use LISTFILES + retList = (char **) Z_Malloc( ( nfiles + 1 ) * sizeof( *retList ), TAG_LISTFILES, qfalse); + // Our string pool is actually slightly too large, but it's temporary, and that's better + // than slightly too small + char *stringPool = (char *) Z_Malloc( strlen(listFile) + 1, TAG_LISTFILES, qfalse ); + + // Now go through the list of files again, and fill in the list to be returned + nfiles = 0; + curFile = listFile; + while (true) + { + // Find end of line + end = strchr(curFile, '\r'); + if (end) + { + // Should have a \n next -- skip them both + *end++ = '\0'; + *end++ = '\0'; + } + else + { + end = strchr(curFile, '\n'); + if (end) *end++ = '\0'; + else end = curFile + strlen(curFile); + } + + // Is the line empty? If so, we're done. + int curStrSize = strlen(curFile); + if (curStrSize < 1) + { + retList[nfiles] = NULL; + break; + } + + // Alloc a small copy + //retList[nfiles++] = CopyString( curFile ); + retList[nfiles++] = stringPool; + strcpy(stringPool, curFile); + stringPool += (curStrSize + 1); + + // Advance to next line + curFile = end; + } + + // Free the special file's buffer + FS_FreeFile( listFile ); + + return retList; +} + +/* +================= +Sys_UnloadGame +================= +*/ +void Sys_UnloadGame( void ) { +} + +/* +================= +Sys_GetGameAPI + +Loads the game dll +================= +*/ +#ifndef _JK2MP +void *Sys_GetGameAPI (void *parms) +{ + extern game_export_t *GetGameAPI( game_import_t *import ); + return GetGameAPI((game_import_t *)parms); +} +#endif + +/* +================= +Sys_LoadCgame + +Used to hook up a development dll +================= +*/ +// void * Sys_LoadCgame( void ) +#ifndef _JK2MP +void * Sys_LoadCgame( int (**entryPoint)(int, ...), int (*systemcalls)(int, ...) ) +{ + extern void CG_PreInit(); + extern void cg_dllEntry( int (*syscallptr)( int arg,... ) ); + extern int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7 ); + cg_dllEntry(systemcalls); + *entryPoint = (int (*)(int,...))vmMain; +// CG_PreInit(); + return 0; +} +#endif + +/* VVFIXME: More stubs */ +qboolean Sys_FileOutOfDate( LPCSTR psFinalFileName /* dest */, LPCSTR psDataFileName /* src */ ) +{ + return qfalse; +} + +qboolean Sys_CopyFile(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, qboolean bOverwrite) +{ + return qfalse; +} + +qboolean Sys_CheckCD( void ) +{ + return qtrue; +} diff --git a/code/win32/win_qal_xbox.cpp b/code/win32/win_qal_xbox.cpp new file mode 100644 index 0000000..90ec6aa --- /dev/null +++ b/code/win32/win_qal_xbox.cpp @@ -0,0 +1,1342 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "win_local.h" + +#include "../client/openal/al.h" +#include "../client/openal/alc.h" + +#include +//#include +#include "snd_fx_img.h" + +#include +#include +#include + +#define QAL_STREAM_WAIT_TIME (500) +#define QAL_MAX_STREAM_PACKETS (2) + +// About 1 second of audio at 44100, stereo, ADPCM +#define QAL_STREAM_PACKET_SIZE (44136) + +// Un-comment to enable 5-channel 3-d sound mixing +//#define _FIVE_CHANNEL + +extern HANDLE Sys_FileStreamMutex; +extern const char* Sys_GetFileCodeName(int code); + +/*********************************************** +* +* OpenAL STATE - Main container for all AL objects +* +************************************************/ + +struct QALState +{ + IDirectSound8* m_SoundObject; + + ALuint m_MemoryUsed; + ALenum m_Error; + FLOAT m_Gain; + + struct ListenerInfo + { + D3DXVECTOR3 m_Position; + D3DXMATRIX m_LTM; + }; + typedef std::map listener_t; + listener_t m_Listeners; + ALuint m_NextListener; + + struct SourceInfo + { + typedef std::map voice_t; + voice_t m_Voices; + + ALuint m_Buffer; + + FLOAT m_Gain; + bool m_GainDirty; + + bool m_Loop; + + bool m_Is3d; + D3DXVECTOR3 m_Position; + }; + typedef std::map source_t; + source_t m_Sources; + ALuint m_NextSource; + + struct BufferInfo + { + void* m_Data; + DWORD m_DataOffset; + XBOXADPCMWAVEFORMAT m_WAVFormat; + + DWORD m_Freq; + DWORD m_Size; + + bool m_Valid; + }; + typedef std::map buffer_t; + buffer_t m_Buffers; + ALuint m_NextBuffer; + + struct StreamInfo + { + IDirectSoundStream* m_pVoice; + XFileMediaObject* m_pFile; + + unsigned int m_StartTime; + + bool m_Open; + bool m_Playing; + bool m_Valid; + + FLOAT m_Gain; + bool m_GainDirty; + + bool m_Looping; + + void* m_pPacketBuffer; + DWORD m_PacketStatus[QAL_MAX_STREAM_PACKETS]; + DWORD m_CurrentPacket; + + HANDLE m_Thread; + HANDLE m_Mutex; + HANDLE m_QueueLen; + + enum RequestType + { + REQ_NOP, + REQ_PLAY, + REQ_STOP, + REQ_SHUTDOWN, + }; + + struct Request + { + RequestType m_Type; + DWORD m_Data[3]; + }; + + typedef std::deque queue_t; + queue_t m_Queue; + }; + StreamInfo m_Stream; +}; + +static QALState* s_pState = NULL; + + +/*********************************************** +* +* DEVICES AND CONTEXTS +* +************************************************/ + +ALCdevice* alcOpenDevice(ALCubyte *deviceName) +{ + if (s_pState) return NULL; + s_pState = new QALState; + + s_pState->m_Gain = 1.f; + s_pState->m_Error = AL_NO_ERROR; + s_pState->m_MemoryUsed = 0; + s_pState->m_NextBuffer = 1; + s_pState->m_NextListener = 1; + s_pState->m_NextSource = 1; + s_pState->m_Stream.m_Valid = false; + + // init the sound hardware + if (DirectSoundCreate(NULL, &s_pState->m_SoundObject, NULL) != DS_OK) + { + delete s_pState; + return NULL; + } + + DirectSoundUseFullHRTF(); + + // download effects image to hardware + void* image; + int len = FS_ReadFile("sound/dsstdfx.bin", &image); + if (len <= 0) + { + delete s_pState; + return NULL; + } + + LPDSEFFECTIMAGEDESC desc; + DSEFFECTIMAGELOC effect; + effect.dwI3DL2ReverbIndex = GraphI3DL2_I3DL2Reverb; + effect.dwCrosstalkIndex = GraphXTalk_XTalk; + s_pState->m_SoundObject->DownloadEffectsImage(image, len, &effect, &desc); + + Z_Free(image); + + // setup default reverb + DSI3DL2LISTENER reverb = { DSI3DL2_ENVIRONMENT_PRESET_NOREVERB }; + s_pState->m_SoundObject->SetI3DL2Listener(&reverb, DS3D_DEFERRED); + + return (ALCdevice*)s_pState->m_SoundObject; +} + +ALCvoid alcCloseDevice(ALCdevice *device) +{ + // shutdown the sound hardware + s_pState->m_SoundObject->Release(); + + delete s_pState; + s_pState = NULL; +} + +ALCcontext* alcCreateContext(ALCdevice *device,ALCint *attrList) +{ + return (ALCcontext*)1; +} + +ALCboolean alcMakeContextCurrent(ALCcontext *context) +{ + return true; +} + +ALCcontext* alcGetCurrentContext(ALCvoid) +{ + return (ALCcontext*)1; +} + +ALCdevice* alcGetContextsDevice(ALCcontext *context) +{ + if (!s_pState) return NULL; + return (ALCdevice*)s_pState->m_SoundObject; +} + +ALCvoid alcDestroyContext(ALCcontext *context) +{ +} + +ALCenum alcGetError(ALCdevice *device) +{ + return ALC_NO_ERROR; +} + + + + +/*********************************************** +* +* LISTENERS +* +************************************************/ + +ALvoid alGenListeners( ALsizei n, ALuint* listeners ) +{ + while (n--) + { + QALState::ListenerInfo* info = new QALState::ListenerInfo; + + info->m_Position.x = 0.f; + info->m_Position.y = 0.f; + info->m_Position.z = 0.f; + + D3DXMatrixIdentity(&info->m_LTM); + + s_pState->m_Listeners[s_pState->m_NextListener] = info; + listeners[n] = s_pState->m_NextListener++; + } +} + +ALvoid alDeleteListeners( ALsizei n, ALuint* listeners ) +{ + while (n--) + { + QALState::listener_t::iterator i = + s_pState->m_Listeners.find(listeners[n]); + + if (i != s_pState->m_Listeners.end()) + { + delete i->second; + s_pState->m_Listeners.erase(i); + } + } +} + +ALvoid alListenerfv( ALuint listener, ALenum param, ALfloat* values ) +{ + assert(s_pState->m_Listeners.find(listener) != + s_pState->m_Listeners.end()); + + QALState::ListenerInfo* info = s_pState->m_Listeners[listener]; + D3DXVECTOR3 right; + D3DXMATRIX trans; + FLOAT det; + + switch (param) + { + case AL_POSITION: + info->m_Position.x = values[0]; + info->m_Position.y = values[1]; + info->m_Position.z = values[2]; + + // translation + D3DXMatrixTranslation(&trans, -values[0], -values[1], -values[2]); + D3DXMatrixMultiply(&info->m_LTM, &trans, &info->m_LTM); + break; + + case AL_ORIENTATION: + D3DXMatrixIdentity(&info->m_LTM); + + // at vector + info->m_LTM(2, 0) = values[0]; + info->m_LTM(2, 1) = values[1]; + info->m_LTM(2, 2) = values[2]; + + // up vector + info->m_LTM(1, 0) = values[3]; + info->m_LTM(1, 1) = values[4]; + info->m_LTM(1, 2) = values[5]; + + // Hack. We switched the sign on values[2] up above, need to do that here + D3DXVec3Cross(&right, (D3DXVECTOR3*)&values[0], (D3DXVECTOR3*)&values[3]); + + // right vector + info->m_LTM(0, 0) = right.x; + info->m_LTM(0, 1) = right.y; + info->m_LTM(0, 2) = right.z; + + // convert to local space transform + D3DXMatrixInverse(&info->m_LTM, &det, &info->m_LTM); + + // translation + D3DXMatrixTranslation(&trans, + -info->m_Position.x, -info->m_Position.y, -info->m_Position.z); + D3DXMatrixMultiply(&info->m_LTM, &trans, &info->m_LTM); + break; + } +} + + + + +/*********************************************** +* +* SOURCES +* +************************************************/ + +static void _wavSetFormat(XBOXADPCMWAVEFORMAT* wav, ALenum format, ALsizei freq) +{ + switch (format) + { + case AL_FORMAT_MONO4: + wav->wfx.wFormatTag = WAVE_FORMAT_XBOX_ADPCM; + wav->wfx.nChannels = 1; + wav->wfx.nSamplesPerSec = freq; + wav->wfx.nBlockAlign = 36 * wav->wfx.nChannels; + wav->wfx.nAvgBytesPerSec = wav->wfx.nSamplesPerSec * wav->wfx.nBlockAlign / 64; + wav->wfx.wBitsPerSample = 4; + wav->wfx.cbSize = sizeof(XBOXADPCMWAVEFORMAT) - sizeof(WAVEFORMATEX); + wav->wSamplesPerBlock = 64; + break; + + case AL_FORMAT_STEREO4: + wav->wfx.wFormatTag = WAVE_FORMAT_XBOX_ADPCM; + wav->wfx.nChannels = 2; + wav->wfx.nSamplesPerSec = freq; + wav->wfx.nBlockAlign = 36 * wav->wfx.nChannels; + wav->wfx.nAvgBytesPerSec = wav->wfx.nSamplesPerSec * wav->wfx.nBlockAlign / 64; + wav->wfx.wBitsPerSample = 4; + wav->wfx.cbSize = sizeof(XBOXADPCMWAVEFORMAT) - sizeof(WAVEFORMATEX); + wav->wSamplesPerBlock = 64; + break; + + case AL_FORMAT_MONO8: + case AL_FORMAT_STEREO8: + case AL_FORMAT_MONO16: + case AL_FORMAT_STEREO16: + default: + assert(0); + break; + } +} + +static int _genSource(bool is3d) +{ + // alloc a new source + QALState::SourceInfo* sinfo = new QALState::SourceInfo; + + // describe the voice + XBOXADPCMWAVEFORMAT wav; + _wavSetFormat(&wav, AL_FORMAT_MONO4, 22050); + + DSBUFFERDESC desc; + desc.dwSize = sizeof(desc); + if (is3d) desc.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_MUTE3DATMAXDISTANCE; + else desc.dwFlags = 0; + desc.dwBufferBytes = 0; + desc.lpwfxFormat = (WAVEFORMATEX*)&wav; + desc.lpMixBins = NULL; + desc.dwInputMixBin = 0; + + // create voice for all listeners + for (QALState::listener_t::iterator l = s_pState->m_Listeners.begin(); + l != s_pState->m_Listeners.end(); ++l) + { + // create the voice + IDirectSoundBuffer* voice; + if (s_pState->m_SoundObject->CreateSoundBuffer(&desc, &voice, NULL) != DS_OK) + { + s_pState->m_Error = AL_OUT_OF_MEMORY; + return false; + } + + sinfo->m_Voices[l->first] = voice; + + // only create a single voice for 2d sounds + if (!is3d) break; + } + + // setup some defaults + sinfo->m_Buffer = 0; + + sinfo->m_Gain = 1.f; + sinfo->m_GainDirty = true; + sinfo->m_Loop = false; + + sinfo->m_Is3d = is3d; + sinfo->m_Position.x = 0.f; + sinfo->m_Position.y = 0.f; + sinfo->m_Position.z = 0.f; + + s_pState->m_Sources[s_pState->m_NextSource] = sinfo; + + return true; +} + +static void _attachBuffer(ALuint source, ALuint buffer) +{ + assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end()); + assert(s_pState->m_Buffers.find(buffer) != s_pState->m_Buffers.end()); + + QALState::SourceInfo* sinfo = s_pState->m_Sources[source]; + QALState::BufferInfo* binfo = s_pState->m_Buffers[buffer]; + + // setup voices for all listeners + for (QALState::SourceInfo::voice_t::iterator v = sinfo->m_Voices.begin(); + v != sinfo->m_Voices.end(); ++v) + { + v->second->SetFormat((WAVEFORMATEX*)&binfo->m_WAVFormat); + +#ifdef _FIVE_CHANNEL + DSMIXBINVOLUMEPAIR dsmbvp[6] = { + DSMIXBINVOLUMEPAIRS_DEFAULT_5CHANNEL_3D, + }; + DSMIXBINS dsmb; + dsmb.dwMixBinCount = 6; + dsmb.lpMixBinVolumePairs = dsmbvp; + + v->second->SetMixBins(&dsmb); +#endif + + v->second->SetBufferData((char*)binfo->m_Data + binfo->m_DataOffset, binfo->m_Size); + } + + sinfo->m_Buffer = buffer; +} + +static void _dettachBuffer(ALuint source) +{ + assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end()); + + QALState::SourceInfo* info = s_pState->m_Sources[source]; + + // clear buffer on voices + for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); + v != info->m_Voices.end(); ++v) + { + v->second->Stop(); + v->second->SetBufferData(NULL, 0); + } + + info->m_Buffer = 0; +} + +static float rollOffPoint = 0; + +void SetHeadroom( int source, float value) +{ + QALState::SourceInfo* info = s_pState->m_Sources[source]; + for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); + v != info->m_Voices.end(); ++v) + { + DWORD dB = 100 * value; + v->second->SetHeadroom(dB); + } +} + +static void _sourceSetRefDist(QALState::SourceInfo* info, FLOAT value) +{ + for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); + v != info->m_Voices.end(); ++v) + { + // In order to prevent debug DX from complaining that + // the max dist is greater than the min dist, I clear + // the min dist _before_ setting the max. Ug. + v->second->SetMinDistance(1, DS3D_DEFERRED); + + // New algorithm - ref dist is supposed to be dist at which sound is 1/2 volume, + // which happens at double min distance in DS, thus: (reverted) + v->second->SetMaxDistance(value * 2.f, DS3D_DEFERRED); +// v->second->SetMinDistance(value, DS3D_DEFERRED); +// v->second->SetMinDistance(value / 2.f, DS3D_DEFERRED); + + v->second->SetRolloffCurve( + &rollOffPoint, + 1, + DS3D_IMMEDIATE ); + } +} + +ALvoid alGenSources2D( ALsizei n, ALuint* sources ) +{ + while (n--) + { + if (!_genSource(false)) break; + sources[n] = s_pState->m_NextSource++; + } +} + +ALvoid alGenSources3D( ALsizei n, ALuint* sources ) +{ + while (n--) + { + if (!_genSource(true)) break; + sources[n] = s_pState->m_NextSource++; + } +} + +ALvoid alDeleteSources( ALsizei n, ALuint* sources ) +{ + while (n--) + { + QALState::source_t::iterator i = + s_pState->m_Sources.find(sources[n]); + + if (i != s_pState->m_Sources.end()) + { + QALState::SourceInfo* info = i->second; + + // stop using any buffers + _dettachBuffer(sources[n]); + + // free associated voices + for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); + v != info->m_Voices.end(); ++v) + { + v->second->Release(); + } + + delete info; + s_pState->m_Sources.erase(i); + } + } +} + +ALvoid alSourcei( ALuint source, ALenum param, ALint value ) +{ + assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end()); + + switch (param) + { + case AL_LOOPING: + s_pState->m_Sources[source]->m_Loop = value; + break; + + case AL_BUFFER: + if (value) _attachBuffer(source, value); + break; + + default: + assert(0); + break; + } +} + +ALvoid alSourcef( ALuint source, ALenum param, ALfloat value ) +{ + assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end()); + + QALState::SourceInfo* info = s_pState->m_Sources[source]; + + switch (param) + { + case AL_REFERENCE_DISTANCE: + _sourceSetRefDist(info, value); + break; + case AL_GAIN: + info->m_Gain = value; + info->m_GainDirty = true; + break; + default: + assert(0); + break; + } +} + +ALvoid alSourcefv( ALuint source, ALenum param, ALfloat* values ) +{ + assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end()); + + QALState::SourceInfo* info = s_pState->m_Sources[source]; + + switch (param) + { + case AL_POSITION: + assert(info->m_Is3d); + info->m_Position.x = values[0]; + info->m_Position.y = values[1]; + info->m_Position.z = values[2]; + break; + default: + assert(0); + break; + } +} + +ALvoid alSourceStop( ALuint source ) +{ + assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end()); + + QALState::SourceInfo* info = s_pState->m_Sources[source]; + + // stop playing for all listeners + for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); + v != info->m_Voices.end(); ++v) + { + v->second->Stop(); + + DWORD status = 1; // Wait for voice to turn off + do { + v->second->GetStatus(&status); + } while (status != 0); + + } +} + +ALvoid alSourcePlay( ALuint source ) +{ + assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end()); + + QALState::SourceInfo* info = s_pState->m_Sources[source]; + + if (!info->m_Buffer) + { + return; + } + + // start playing for all listeners + for (QALState::SourceInfo::voice_t::iterator v = info->m_Voices.begin(); + v != info->m_Voices.end(); ++v) + { + v->second->SetCurrentPosition(0); + v->second->Play(0, 0, info->m_Loop ? DSBPLAY_LOOPING : 0); + } +} + +ALvoid alGetSourcei( ALuint source, ALenum param, ALint* value ) +{ + assert(s_pState->m_Sources.find(source) != s_pState->m_Sources.end()); + + QALState::SourceInfo* info = s_pState->m_Sources[source]; + + switch (param) + { + case AL_SOURCE_STATE: + { + DWORD status; + info->m_Voices.begin()->second->GetStatus(&status); + *value = (status & DSBSTATUS_PLAYING) ? AL_PLAYING : AL_STOPPED; + } + break; + default: + assert(0); + break; + } +} + + + + +/*********************************************** +* +* BUFFERS +* +************************************************/ + +ALvoid alGenBuffers( ALsizei n, ALuint* buffers ) +{ + while (n--) + { + QALState::BufferInfo* info = new QALState::BufferInfo; + + info->m_Valid = false; + + s_pState->m_Buffers[s_pState->m_NextBuffer] = info; + buffers[n] = s_pState->m_NextBuffer++; + } +} + +ALvoid alDeleteBuffers( ALsizei n, ALuint* buffers ) +{ + while (n--) + { + QALState::buffer_t::iterator b = + s_pState->m_Buffers.find(buffers[n]); + + // check if the buffer exists + if (b != s_pState->m_Buffers.end()) + { + QALState::BufferInfo* binfo = b->second; + + if (binfo->m_Valid) + { + // dettach buffer from any sources using it (may block) + for (QALState::source_t::iterator s = s_pState->m_Sources.begin(); + s != s_pState->m_Sources.end(); ++s) + { + QALState::SourceInfo* sinfo = s->second; + if (sinfo->m_Buffer == buffers[n]) + { + _dettachBuffer(s->first); + } + } + + // free the memory + Z_Free(binfo->m_Data); + s_pState->m_MemoryUsed -= binfo->m_Size; + } + + delete b->second; + s_pState->m_Buffers.erase(b); + } + } +} + +ALvoid alBufferData( ALuint buffer, ALenum format, ALvoid* data, ALsizei size, ALsizei freq ) +{ + assert(s_pState->m_Buffers.find(buffer) != s_pState->m_Buffers.end()); + + QALState::BufferInfo* info = s_pState->m_Buffers[buffer]; + + // if this buffer has been used before, clear the old data + if (info->m_Valid) + { + Z_Free(info->m_Data); + s_pState->m_MemoryUsed -= info->m_Size; + info->m_Valid = false; + } + + info->m_Data = data; + + // assume we have a wave file... + WAVEFORMATEX* wav = (WAVEFORMATEX*)((char*)data + 20); + info->m_DataOffset = 20 + sizeof(WAVEFORMATEX) + wav->cbSize + 8; + + info->m_Size = size; + s_pState->m_MemoryUsed += info->m_Size; + + _wavSetFormat(&info->m_WAVFormat, format, freq); + + info->m_Valid = true; +} + + +/*********************************************** +* +* STREAMS +* +************************************************/ + +static int _streamFromFile(void) +{ + DWORD total = 0; + DWORD used = 0; + + // setup a media packet for reading from the file + XMEDIAPACKET xmp; + ZeroMemory(&xmp, sizeof(xmp)); + xmp.pvBuffer = (BYTE *)s_pState->m_Stream.m_pPacketBuffer + + (QAL_STREAM_PACKET_SIZE * s_pState->m_Stream.m_CurrentPacket); + xmp.dwMaxSize = QAL_STREAM_PACKET_SIZE; + xmp.pdwCompletedSize = &used; + + WaitForSingleObject(Sys_FileStreamMutex, INFINITE); + + // loop until we have a full packet of data + while (total < QAL_STREAM_PACKET_SIZE) + { + if (DS_OK != s_pState->m_Stream.m_pFile->Process(NULL, &xmp)) + { + ReleaseMutex(Sys_FileStreamMutex); + return -1; + } + + total += used; + + // did we get enough data? + if (used < xmp.dwMaxSize) + { + if (s_pState->m_Stream.m_Looping) + { + // must have reached the end of the file, loop back + // around to the beginning and get more data + xmp.pvBuffer = (BYTE*)xmp.pvBuffer + used; + xmp.dwMaxSize = xmp.dwMaxSize - used; + + if (DS_OK != s_pState->m_Stream.m_pFile->Seek( + 0, FILE_BEGIN, NULL)) + { + ReleaseMutex(Sys_FileStreamMutex); + return -1; + } + } + else + { + // reached end, finish up + s_pState->m_Stream.m_Playing = false; + ReleaseMutex(Sys_FileStreamMutex); + return used; + } + } + } + + ReleaseMutex(Sys_FileStreamMutex); + + return QAL_STREAM_PACKET_SIZE; +} + +static void _streamToVoice(int size) +{ + // setup a packet with the current data + XMEDIAPACKET xmp; + ZeroMemory(&xmp, sizeof(xmp)); + xmp.pvBuffer = (BYTE *)s_pState->m_Stream.m_pPacketBuffer + + (QAL_STREAM_PACKET_SIZE * s_pState->m_Stream.m_CurrentPacket); + xmp.dwMaxSize = size; + xmp.pdwStatus = &s_pState->m_Stream.m_PacketStatus[ + s_pState->m_Stream.m_CurrentPacket]; + + // sent to the voice + s_pState->m_Stream.m_pVoice->Process(&xmp, NULL); + + // make sure we're playing + s_pState->m_Stream.m_pVoice->Pause(DSSTREAMPAUSE_RESUME); + if (s_pState->m_Stream.m_StartTime == 0) + { + s_pState->m_Stream.m_StartTime = Sys_Milliseconds(); + } +} + +static void _streamFill(void) +{ + // do we have any free packets? + if (XMEDIAPACKET_STATUS_PENDING != + s_pState->m_Stream.m_PacketStatus[s_pState->m_Stream.m_CurrentPacket]) + { + // get some data + int size = _streamFromFile(); + if (size > 0) + { + _streamToVoice(size); + + // next packet... + ++s_pState->m_Stream.m_CurrentPacket; + s_pState->m_Stream.m_CurrentPacket %= QAL_MAX_STREAM_PACKETS; + } + + if (!s_pState->m_Stream.m_Playing) + { + // Non-looping stream finished playback + s_pState->m_Stream.m_pVoice->Discontinuity(); + } + } +} + +static void _streamOpen(DWORD file, DWORD offset, bool loop) +{ + if (s_pState->m_Stream.m_Open) + { + // if a stream is current playing, interrupt it + s_pState->m_Stream.m_pVoice->Flush(); + s_pState->m_Stream.m_pFile->Release(); + s_pState->m_Stream.m_Playing = false; + s_pState->m_Stream.m_Open = false; + } + + // Get the original name, not re-mapped: + const char* name = Sys_GetFileCodeName(file); + +#ifdef XBOX_DEMO + // Skip over "D:" + name += 2; + + // Get the base path, then add the important part of the filename: + extern char demoBasePath[64]; + char mappedName[128]; + + strcpy( mappedName, demoBasePath ); + strcat( mappedName, name ); + name = mappedName; +#endif + + WaitForSingleObject(Sys_FileStreamMutex, INFINITE); + + // open the file for streaming + LPCWAVEFORMATEX fmt; + if (DS_OK == XWaveFileCreateMediaObject( + name, &fmt, &s_pState->m_Stream.m_pFile)) + { + // set the voice based on the file format + s_pState->m_Stream.m_pVoice->SetFormat(fmt); + +#ifdef _FIVE_CHANNEL + DSMIXBINVOLUMEPAIR dsmbvp[6] = { + DSMIXBINVOLUMEPAIRS_DEFAULT_5CHANNEL_3D, + }; + DSMIXBINS dsmb; + dsmb.dwMixBinCount = 6; + dsmb.lpMixBinVolumePairs = dsmbvp; + + s_pState->m_Stream.m_pVoice->SetMixBins(&dsmb); +#endif + + // seek the requested start position + s_pState->m_Stream.m_pFile->Seek(RoundDown(offset, 72), + FILE_BEGIN, NULL); + + s_pState->m_Stream.m_StartTime = 0; + s_pState->m_Stream.m_Looping = loop; + s_pState->m_Stream.m_Playing = true; + s_pState->m_Stream.m_Open = true; + } + + ReleaseMutex(Sys_FileStreamMutex); +} + +static void _streamClose(void) +{ + if (s_pState->m_Stream.m_Open) + { + // stop the stream + s_pState->m_Stream.m_pVoice->Flush(); + s_pState->m_Stream.m_pFile->Release(); + s_pState->m_Stream.m_Playing = false; + s_pState->m_Stream.m_Open = false; + } +} + +static DWORD WINAPI _streamThread(LPVOID lpParameter) +{ + for (;;) + { + QALState::StreamInfo* strm = &s_pState->m_Stream; + QALState::StreamInfo::Request req; + + // Wait for the queue to fill + WaitForSingleObject(strm->m_QueueLen, QAL_STREAM_WAIT_TIME); + + // Grab the next request + WaitForSingleObject(strm->m_Mutex, INFINITE); + if (!strm->m_Queue.empty()) + { + req = strm->m_Queue.front(); + strm->m_Queue.pop_front(); + } + else + { + req.m_Type = QALState::StreamInfo::REQ_NOP; + } + ReleaseMutex(strm->m_Mutex); + + // Process request + switch (req.m_Type) + { + case QALState::StreamInfo::REQ_PLAY: + _streamOpen(req.m_Data[0], req.m_Data[1], req.m_Data[2]); + break; + + case QALState::StreamInfo::REQ_STOP: + _streamClose(); + break; + + case QALState::StreamInfo::REQ_SHUTDOWN: + ExitThread(0); + break; + + case QALState::StreamInfo::REQ_NOP: + break; + } + + // fill the stream with data + if (strm->m_Open && strm->m_Playing) + { + _streamFill(); + } + } +} + +static void _postStreamRequest(const QALState::StreamInfo::Request& req) +{ + // Add request to queue + extern void Z_SetNewDeleteTemporary( bool bTemp ); + Z_SetNewDeleteTemporary( true ); + WaitForSingleObject(s_pState->m_Stream.m_Mutex, INFINITE); + s_pState->m_Stream.m_Queue.push_back(req); + ReleaseMutex(s_pState->m_Stream.m_Mutex); + Z_SetNewDeleteTemporary( false ); + + // Let thread know it has one more pending request + ReleaseSemaphore(s_pState->m_Stream.m_QueueLen, 1, NULL); + + // Give the stream thread some CPU + Sleep(0); +} + +ALvoid alGenStream( ALvoid ) +{ + assert(!s_pState->m_Stream.m_Valid); + + // describe the stream + XBOXADPCMWAVEFORMAT wav; + _wavSetFormat(&wav, AL_FORMAT_STEREO4, 44100); + + DSSTREAMDESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.dwMaxAttachedPackets = QAL_MAX_STREAM_PACKETS; + desc.lpwfxFormat = (WAVEFORMATEX*)&wav; + + // create a voice for the stream + if (s_pState->m_SoundObject->CreateSoundStream(&desc, + &s_pState->m_Stream.m_pVoice, NULL) != DS_OK) + { + s_pState->m_Error = AL_OUT_OF_MEMORY; + return; + } + + // get some memory to hold the stream data + s_pState->m_Stream.m_pPacketBuffer = + XPhysicalAlloc(QAL_MAX_STREAM_PACKETS * QAL_STREAM_PACKET_SIZE, + MAXULONG_PTR, 0, PAGE_READWRITE | PAGE_NOCACHE); + + // setup some defaults + s_pState->m_Stream.m_Gain = 1.f; + s_pState->m_Stream.m_GainDirty = true; + + s_pState->m_Stream.m_CurrentPacket = 0; + for (int p = 0; p < QAL_MAX_STREAM_PACKETS; ++p) + { + s_pState->m_Stream.m_PacketStatus[p] = XMEDIAPACKET_STATUS_SUCCESS; + } + + s_pState->m_Stream.m_Open = false; + s_pState->m_Stream.m_Playing = false; + s_pState->m_Stream.m_Valid = true; + + // setup a thread to service the stream (keep blocking IO out + // of the main thread) + s_pState->m_Stream.m_QueueLen = CreateSemaphore(NULL, 0, 256, NULL); + s_pState->m_Stream.m_Mutex = CreateMutex(NULL, FALSE, NULL); + s_pState->m_Stream.m_Thread = CreateThread(NULL, 64*1024, + _streamThread, NULL, 0, NULL ); +} + +ALvoid alDeleteStream( ALvoid ) +{ + assert(s_pState->m_Stream.m_Valid); + + // stop the audio + alStreamStop(); + + // kill the thread + QALState::StreamInfo::Request req; + req.m_Type = QALState::StreamInfo::REQ_SHUTDOWN; + _postStreamRequest(req); + + // Wait for thread to close + WaitForSingleObject(s_pState->m_Stream.m_Thread, INFINITE); + + // thread handles + CloseHandle(s_pState->m_Stream.m_Thread); + CloseHandle(s_pState->m_Stream.m_Mutex); + CloseHandle(s_pState->m_Stream.m_QueueLen); + + // release the stream + s_pState->m_Stream.m_pVoice->Release(); + XPhysicalFree(s_pState->m_Stream.m_pPacketBuffer); + + s_pState->m_Stream.m_Valid = false; +} + +ALvoid alStreamStop( ALvoid ) +{ + assert(s_pState->m_Stream.m_Valid); + + QALState::StreamInfo::Request req; + req.m_Type = QALState::StreamInfo::REQ_STOP; + _postStreamRequest(req); +} + +ALvoid alStreamPlay( ALsizei offset, ALint file, ALint loop ) +{ + assert(s_pState->m_Stream.m_Valid); + + QALState::StreamInfo::Request req; + req.m_Type = QALState::StreamInfo::REQ_PLAY; + req.m_Data[0] = file; + req.m_Data[1] = offset; + req.m_Data[2] = loop; + _postStreamRequest(req); + + s_pState->m_Stream.m_Playing = true; +} + +ALvoid alStreamf( ALenum param, ALfloat value ) +{ + assert(s_pState->m_Stream.m_Valid); + + switch (param) + { + case AL_GAIN: + s_pState->m_Stream.m_Gain = value; + s_pState->m_Stream.m_GainDirty = true; + break; + default: + assert(0); + break; + } +} + +ALvoid alGetStreamf( ALenum param, ALfloat* value ) +{ + assert(s_pState->m_Stream.m_Valid); + + switch (param) + { + case AL_TIME: + if (s_pState->m_Stream.m_Open && s_pState->m_Stream.m_StartTime) + { + *value = (float)(Sys_Milliseconds() - + s_pState->m_Stream.m_StartTime) / 1000.f; + } + else + { + *value = 0.f; + } + break; + default: + assert(0); + break; + } +} + +ALvoid alGetStreami( ALenum param, ALint* value ) +{ + assert(s_pState->m_Stream.m_Valid); + + switch (param) + { + case AL_SOURCE_STATE: + *value = s_pState->m_Stream.m_Playing ? AL_PLAYING : AL_STOPPED; + break; + default: + assert(0); + break; + } +} + + + +/*********************************************** +* +* ADDITIONAL FUNCTIONS +* +************************************************/ + +static void _updateVoiceGain(IDirectSoundBuffer* voice, FLOAT gain) +{ + // compute aggregate gain + FLOAT g = s_pState->m_Gain * gain; + + if (g <= 0.0f) + { + // mute the sound + voice->SetVolume(DSBVOLUME_MIN); + } + else + { + if( g >= 0.98) + g = 1.0f; + + // convert to dB + g = 20.f * log10(g) * 100.0f; + + if(g < DSBVOLUME_HW_MIN) { + g = DSBVOLUME_HW_MIN; + } + + // set the volume + voice->SetVolume(g); + } +} + +static void _updateVoicePos(IDirectSoundBuffer* voice, D3DXVECTOR3* pos, + QALState::ListenerInfo* listener) +{ + // get source pos in listener space + D3DXVECTOR4 lpos; + D3DXVec3Transform(&lpos, pos, &listener->m_LTM); + + voice->SetPosition(lpos.x, lpos.y, lpos.z, DS3D_DEFERRED); +} + +static void _updateSource(QALState::SourceInfo* source) +{ + // loop through all the voices at this source + for (QALState::SourceInfo::voice_t::iterator v = source->m_Voices.begin(); + v != source->m_Voices.end(); ++v) + { + // update the gain + if (source->m_GainDirty) + { + _updateVoiceGain(v->second, source->m_Gain); + } + + // update position + if (source->m_Is3d) + { + // get the listener for this voice + QALState::listener_t::iterator l = s_pState->m_Listeners.find(v->first); + + if (l != s_pState->m_Listeners.end()) + { + _updateVoicePos( + v->second, + &source->m_Position, + l->second); + } + } + } + + source->m_GainDirty = false; +} + +static void _updateStream(void) +{ + if (s_pState->m_Stream.m_Open && s_pState->m_Stream.m_GainDirty) + { + // compute aggregate gain + FLOAT g = s_pState->m_Gain * s_pState->m_Stream.m_Gain; + if (g <= 0.0f) + { + // mute the sound + s_pState->m_Stream.m_pVoice->SetVolume(DSBVOLUME_MIN); + } + else + { + if( g >= 0.98) + g = 1.0f; + + // convert to dB + g = 20.f * log10(g) * 100.0f; + + if(g < DSBVOLUME_HW_MIN) { + g = DSBVOLUME_HW_MIN; + } + + // set the volume + s_pState->m_Stream.m_pVoice->SetVolume(g); + } + + s_pState->m_Stream.m_GainDirty = false; + } +} + +ALenum alGetError( ALvoid ) +{ + ALenum error = s_pState->m_Error; + s_pState->m_Error = AL_NO_ERROR; + return error; +} + +ALvoid alUpdate( ALvoid ) +{ + DirectSoundDoWork(); + + // update sources + for (QALState::source_t::iterator i = s_pState->m_Sources.begin(); + i != s_pState->m_Sources.end(); ++i) + { + QALState::SourceInfo* info = i->second; + + // 3d sounds and dirty sources must be updated + if (info->m_Is3d || info->m_GainDirty) + { + // only playing sources should be updated + DWORD status; + info->m_Voices.begin()->second->GetStatus(&status); + + if (status & DSBSTATUS_PLAYING) + { + _updateSource(info); + } + } + } + + // update stream + _updateStream(); + + s_pState->m_SoundObject->CommitDeferredSettings(); +} + +ALvoid alGeti( ALenum param, ALint* value ) +{ + switch (param) + { + case AL_MEMORY_USED: + *value = s_pState->m_MemoryUsed; + break; + + default: + assert(0); + } +} + +ALvoid alGain( ALfloat value ) +{ + s_pState->m_Gain = value; + + // set gain dirty for all sources + for (QALState::source_t::iterator i = s_pState->m_Sources.begin(); + i != s_pState->m_Sources.end(); ++i) + { + i->second->m_GainDirty = true; + } + + // set gain dirty for stream + s_pState->m_Stream.m_GainDirty = true; +} diff --git a/code/win32/win_qgl.cpp b/code/win32/win_qgl.cpp new file mode 100644 index 0000000..44fd1aa --- /dev/null +++ b/code/win32/win_qgl.cpp @@ -0,0 +1,4276 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +/* +** QGL_WIN.C +** +** This file implements the operating system binding of GL to QGL function +** pointers. When doing a port of Quake3 you must implement the following +** two functions: +** +** QGL_Init() - loads libraries, assigns function pointers, etc. +** QGL_Shutdown() - unloads libraries, NULLs function pointers +*/ +#include +#include "../renderer/tr_local.h" +#include "glw_win.h" + +void QGL_EnableLogging( qboolean enable ); + +int ( WINAPI * qwglSwapIntervalEXT)( int interval ); + +BOOL ( WINAPI * qwglCopyContext)(HGLRC, HGLRC, UINT); +HGLRC ( WINAPI * qwglCreateContext)(HDC); +HGLRC ( WINAPI * qwglCreateLayerContext)(HDC, int); +BOOL ( WINAPI * qwglDeleteContext)(HGLRC); +HGLRC ( WINAPI * qwglGetCurrentContext)(VOID); +HDC ( WINAPI * qwglGetCurrentDC)(VOID); +PROC ( WINAPI * qwglGetProcAddress)(LPCSTR); +BOOL ( WINAPI * qwglMakeCurrent)(HDC, HGLRC); +BOOL ( WINAPI * qwglShareLists)(HGLRC, HGLRC); +BOOL ( WINAPI * qwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); + +BOOL ( WINAPI * qwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, + FLOAT, int, LPGLYPHMETRICSFLOAT); + +BOOL ( WINAPI * qwglDescribeLayerPlane)(HDC, int, int, UINT, + LPLAYERPLANEDESCRIPTOR); +int ( WINAPI * qwglSetLayerPaletteEntries)(HDC, int, int, int, + CONST COLORREF *); +int ( WINAPI * qwglGetLayerPaletteEntries)(HDC, int, int, int, + COLORREF *); +BOOL ( WINAPI * qwglRealizeLayerPalette)(HDC, int, BOOL); +BOOL ( WINAPI * qwglSwapLayerBuffers)(HDC, UINT); + +void ( APIENTRY * qglAccum )(GLenum op, GLfloat value); +void ( APIENTRY * qglAlphaFunc )(GLenum func, GLclampf ref); +GLboolean ( APIENTRY * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +void ( APIENTRY * qglArrayElement )(GLint i); +void ( APIENTRY * qglBegin )(GLenum mode); +void ( APIENTRY * qglBindTexture )(GLenum target, GLuint texture); +void ( APIENTRY * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +void ( APIENTRY * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +void ( APIENTRY * qglCallList )(GLuint list); +void ( APIENTRY * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +void ( APIENTRY * qglClear )(GLbitfield mask); +void ( APIENTRY * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( APIENTRY * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +void ( APIENTRY * qglClearDepth )(GLclampd depth); +void ( APIENTRY * qglClearIndex )(GLfloat c); +void ( APIENTRY * qglClearStencil )(GLint s); +void ( APIENTRY * qglClipPlane )(GLenum plane, const GLdouble *equation); +void ( APIENTRY * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +void ( APIENTRY * qglColor3bv )(const GLbyte *v); +void ( APIENTRY * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +void ( APIENTRY * qglColor3dv )(const GLdouble *v); +void ( APIENTRY * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +void ( APIENTRY * qglColor3fv )(const GLfloat *v); +void ( APIENTRY * qglColor3i )(GLint red, GLint green, GLint blue); +void ( APIENTRY * qglColor3iv )(const GLint *v); +void ( APIENTRY * qglColor3s )(GLshort red, GLshort green, GLshort blue); +void ( APIENTRY * qglColor3sv )(const GLshort *v); +void ( APIENTRY * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +void ( APIENTRY * qglColor3ubv )(const GLubyte *v); +void ( APIENTRY * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +void ( APIENTRY * qglColor3uiv )(const GLuint *v); +void ( APIENTRY * qglColor3us )(GLushort red, GLushort green, GLushort blue); +void ( APIENTRY * qglColor3usv )(const GLushort *v); +void ( APIENTRY * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +void ( APIENTRY * qglColor4bv )(const GLbyte *v); +void ( APIENTRY * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +void ( APIENTRY * qglColor4dv )(const GLdouble *v); +void ( APIENTRY * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( APIENTRY * qglColor4fv )(const GLfloat *v); +void ( APIENTRY * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +void ( APIENTRY * qglColor4iv )(const GLint *v); +void ( APIENTRY * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +void ( APIENTRY * qglColor4sv )(const GLshort *v); +void ( APIENTRY * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +void ( APIENTRY * qglColor4ubv )(const GLubyte *v); +void ( APIENTRY * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +void ( APIENTRY * qglColor4uiv )(const GLuint *v); +void ( APIENTRY * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +void ( APIENTRY * qglColor4usv )(const GLushort *v); +void ( APIENTRY * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +void ( APIENTRY * qglColorMaterial )(GLenum face, GLenum mode); +void ( APIENTRY * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +void ( APIENTRY * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +void ( APIENTRY * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +void ( APIENTRY * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +void ( APIENTRY * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY * qglCullFace )(GLenum mode); +void ( APIENTRY * qglDeleteLists )(GLuint list, GLsizei range); +void ( APIENTRY * qglDeleteTextures )(GLsizei n, const GLuint *textures); +void ( APIENTRY * qglDepthFunc )(GLenum func); +void ( APIENTRY * qglDepthMask )(GLboolean flag); +void ( APIENTRY * qglDepthRange )(GLclampd zNear, GLclampd zFar); +void ( APIENTRY * qglDisable )(GLenum cap); +void ( APIENTRY * qglDisableClientState )(GLenum array); +void ( APIENTRY * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +void ( APIENTRY * qglDrawBuffer )(GLenum mode); +void ( APIENTRY * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +void ( APIENTRY * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglEdgeFlag )(GLboolean flag); +void ( APIENTRY * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglEdgeFlagv )(const GLboolean *flag); +void ( APIENTRY * qglEnable )(GLenum cap); +void ( APIENTRY * qglEnableClientState )(GLenum array); +void ( APIENTRY * qglEnd )(void); +void ( APIENTRY * qglEndList )(void); +void ( APIENTRY * qglEvalCoord1d )(GLdouble u); +void ( APIENTRY * qglEvalCoord1dv )(const GLdouble *u); +void ( APIENTRY * qglEvalCoord1f )(GLfloat u); +void ( APIENTRY * qglEvalCoord1fv )(const GLfloat *u); +void ( APIENTRY * qglEvalCoord2d )(GLdouble u, GLdouble v); +void ( APIENTRY * qglEvalCoord2dv )(const GLdouble *u); +void ( APIENTRY * qglEvalCoord2f )(GLfloat u, GLfloat v); +void ( APIENTRY * qglEvalCoord2fv )(const GLfloat *u); +void ( APIENTRY * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +void ( APIENTRY * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +void ( APIENTRY * qglEvalPoint1 )(GLint i); +void ( APIENTRY * qglEvalPoint2 )(GLint i, GLint j); +void ( APIENTRY * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +void ( APIENTRY * qglFinish )(void); +void ( APIENTRY * qglFlush )(void); +void ( APIENTRY * qglFogf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglFogfv )(GLenum pname, const GLfloat *params); +void ( APIENTRY * qglFogi )(GLenum pname, GLint param); +void ( APIENTRY * qglFogiv )(GLenum pname, const GLint *params); +void ( APIENTRY * qglFrontFace )(GLenum mode); +void ( APIENTRY * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLuint ( APIENTRY * qglGenLists )(GLsizei range); +void ( APIENTRY * qglGenTextures )(GLsizei n, GLuint *textures); +void ( APIENTRY * qglGetBooleanv )(GLenum pname, GLboolean *params); +void ( APIENTRY * qglGetClipPlane )(GLenum plane, GLdouble *equation); +void ( APIENTRY * qglGetDoublev )(GLenum pname, GLdouble *params); +GLenum ( APIENTRY * qglGetError )(void); +void ( APIENTRY * qglGetFloatv )(GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetIntegerv )(GLenum pname, GLint *params); +void ( APIENTRY * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +void ( APIENTRY * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +void ( APIENTRY * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +void ( APIENTRY * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +void ( APIENTRY * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +void ( APIENTRY * qglGetPixelMapfv )(GLenum gmap, GLfloat *values); +void ( APIENTRY * qglGetPixelMapuiv )(GLenum gmap, GLuint *values); +void ( APIENTRY * qglGetPixelMapusv )(GLenum gmap, GLushort *values); +void ( APIENTRY * qglGetPointerv )(GLenum pname, GLvoid* *params); +void ( APIENTRY * qglGetPolygonStipple )(GLubyte *mask); +const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +void ( APIENTRY * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +void ( APIENTRY * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +void ( APIENTRY * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +void ( APIENTRY * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +void ( APIENTRY * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +void ( APIENTRY * qglHint )(GLenum target, GLenum mode); +void ( APIENTRY * qglIndexMask )(GLuint mask); +void ( APIENTRY * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglIndexd )(GLdouble c); +void ( APIENTRY * qglIndexdv )(const GLdouble *c); +void ( APIENTRY * qglIndexf )(GLfloat c); +void ( APIENTRY * qglIndexfv )(const GLfloat *c); +void ( APIENTRY * qglIndexi )(GLint c); +void ( APIENTRY * qglIndexiv )(const GLint *c); +void ( APIENTRY * qglIndexs )(GLshort c); +void ( APIENTRY * qglIndexsv )(const GLshort *c); +void ( APIENTRY * qglIndexub )(GLubyte c); +void ( APIENTRY * qglIndexubv )(const GLubyte *c); +void ( APIENTRY * qglInitNames )(void); +void ( APIENTRY * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +GLboolean ( APIENTRY * qglIsEnabled )(GLenum cap); +GLboolean ( APIENTRY * qglIsList )(GLuint list); +GLboolean ( APIENTRY * qglIsTexture )(GLuint texture); +void ( APIENTRY * qglLightModelf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglLightModelfv )(GLenum pname, const GLfloat *params); +void ( APIENTRY * qglLightModeli )(GLenum pname, GLint param); +void ( APIENTRY * qglLightModeliv )(GLenum pname, const GLint *params); +void ( APIENTRY * qglLightf )(GLenum light, GLenum pname, GLfloat param); +void ( APIENTRY * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglLighti )(GLenum light, GLenum pname, GLint param); +void ( APIENTRY * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +void ( APIENTRY * qglLineStipple )(GLint factor, GLushort pattern); +void ( APIENTRY * qglLineWidth )(GLfloat width); +void ( APIENTRY * qglListBase )(GLuint base); +void ( APIENTRY * qglLoadIdentity )(void); +void ( APIENTRY * qglLoadMatrixd )(const GLdouble *m); +void ( APIENTRY * qglLoadMatrixf )(const GLfloat *m); +void ( APIENTRY * qglLoadName )(GLuint name); +void ( APIENTRY * qglLogicOp )(GLenum opcode); +void ( APIENTRY * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +void ( APIENTRY * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +void ( APIENTRY * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +void ( APIENTRY * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +void ( APIENTRY * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +void ( APIENTRY * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +void ( APIENTRY * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +void ( APIENTRY * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +void ( APIENTRY * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +void ( APIENTRY * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglMateriali )(GLenum face, GLenum pname, GLint param); +void ( APIENTRY * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +void ( APIENTRY * qglMatrixMode )(GLenum mode); +void ( APIENTRY * qglMultMatrixd )(const GLdouble *m); +void ( APIENTRY * qglMultMatrixf )(const GLfloat *m); +void ( APIENTRY * qglNewList )(GLuint list, GLenum mode); +void ( APIENTRY * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +void ( APIENTRY * qglNormal3bv )(const GLbyte *v); +void ( APIENTRY * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +void ( APIENTRY * qglNormal3dv )(const GLdouble *v); +void ( APIENTRY * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +void ( APIENTRY * qglNormal3fv )(const GLfloat *v); +void ( APIENTRY * qglNormal3i )(GLint nx, GLint ny, GLint nz); +void ( APIENTRY * qglNormal3iv )(const GLint *v); +void ( APIENTRY * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +void ( APIENTRY * qglNormal3sv )(const GLshort *v); +void ( APIENTRY * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +void ( APIENTRY * qglPassThrough )(GLfloat token); +void ( APIENTRY * qglPixelMapfv )(GLenum gmap, GLsizei mapsize, const GLfloat *values); +void ( APIENTRY * qglPixelMapuiv )(GLenum gmap, GLsizei mapsize, const GLuint *values); +void ( APIENTRY * qglPixelMapusv )(GLenum gmap, GLsizei mapsize, const GLushort *values); +void ( APIENTRY * qglPixelStoref )(GLenum pname, GLfloat param); +void ( APIENTRY * qglPixelStorei )(GLenum pname, GLint param); +void ( APIENTRY * qglPixelTransferf )(GLenum pname, GLfloat param); +void ( APIENTRY * qglPixelTransferi )(GLenum pname, GLint param); +void ( APIENTRY * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +void ( APIENTRY * qglPointSize )(GLfloat size); +void ( APIENTRY * qglPolygonMode )(GLenum face, GLenum mode); +void ( APIENTRY * qglPolygonOffset )(GLfloat factor, GLfloat units); +void ( APIENTRY * qglPolygonStipple )(const GLubyte *mask); +void ( APIENTRY * qglPopAttrib )(void); +void ( APIENTRY * qglPopClientAttrib )(void); +void ( APIENTRY * qglPopMatrix )(void); +void ( APIENTRY * qglPopName )(void); +void ( APIENTRY * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +void ( APIENTRY * qglPushAttrib )(GLbitfield mask); +void ( APIENTRY * qglPushClientAttrib )(GLbitfield mask); +void ( APIENTRY * qglPushMatrix )(void); +void ( APIENTRY * qglPushName )(GLuint name); +void ( APIENTRY * qglRasterPos2d )(GLdouble x, GLdouble y); +void ( APIENTRY * qglRasterPos2dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos2f )(GLfloat x, GLfloat y); +void ( APIENTRY * qglRasterPos2fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos2i )(GLint x, GLint y); +void ( APIENTRY * qglRasterPos2iv )(const GLint *v); +void ( APIENTRY * qglRasterPos2s )(GLshort x, GLshort y); +void ( APIENTRY * qglRasterPos2sv )(const GLshort *v); +void ( APIENTRY * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglRasterPos3dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglRasterPos3fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos3i )(GLint x, GLint y, GLint z); +void ( APIENTRY * qglRasterPos3iv )(const GLint *v); +void ( APIENTRY * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +void ( APIENTRY * qglRasterPos3sv )(const GLshort *v); +void ( APIENTRY * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( APIENTRY * qglRasterPos4dv )(const GLdouble *v); +void ( APIENTRY * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY * qglRasterPos4fv )(const GLfloat *v); +void ( APIENTRY * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +void ( APIENTRY * qglRasterPos4iv )(const GLint *v); +void ( APIENTRY * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( APIENTRY * qglRasterPos4sv )(const GLshort *v); +void ( APIENTRY * qglReadBuffer )(GLenum mode); +void ( APIENTRY * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +void ( APIENTRY * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +void ( APIENTRY * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +void ( APIENTRY * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +void ( APIENTRY * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +void ( APIENTRY * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +void ( APIENTRY * qglRectiv )(const GLint *v1, const GLint *v2); +void ( APIENTRY * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +void ( APIENTRY * qglRectsv )(const GLshort *v1, const GLshort *v2); +GLint ( APIENTRY * qglRenderMode )(GLenum mode); +void ( APIENTRY * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY * qglSelectBuffer )(GLsizei size, GLuint *buffer); +void ( APIENTRY * qglShadeModel )(GLenum mode); +void ( APIENTRY * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +void ( APIENTRY * qglStencilMask )(GLuint mask); +void ( APIENTRY * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +void ( APIENTRY * qglTexCoord1d )(GLdouble s); +void ( APIENTRY * qglTexCoord1dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord1f )(GLfloat s); +void ( APIENTRY * qglTexCoord1fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord1i )(GLint s); +void ( APIENTRY * qglTexCoord1iv )(const GLint *v); +void ( APIENTRY * qglTexCoord1s )(GLshort s); +void ( APIENTRY * qglTexCoord1sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord2d )(GLdouble s, GLdouble t); +void ( APIENTRY * qglTexCoord2dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord2f )(GLfloat s, GLfloat t); +void ( APIENTRY * qglTexCoord2fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord2i )(GLint s, GLint t); +void ( APIENTRY * qglTexCoord2iv )(const GLint *v); +void ( APIENTRY * qglTexCoord2s )(GLshort s, GLshort t); +void ( APIENTRY * qglTexCoord2sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +void ( APIENTRY * qglTexCoord3dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +void ( APIENTRY * qglTexCoord3fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord3i )(GLint s, GLint t, GLint r); +void ( APIENTRY * qglTexCoord3iv )(const GLint *v); +void ( APIENTRY * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +void ( APIENTRY * qglTexCoord3sv )(const GLshort *v); +void ( APIENTRY * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +void ( APIENTRY * qglTexCoord4dv )(const GLdouble *v); +void ( APIENTRY * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +void ( APIENTRY * qglTexCoord4fv )(const GLfloat *v); +void ( APIENTRY * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +void ( APIENTRY * qglTexCoord4iv )(const GLint *v); +void ( APIENTRY * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +void ( APIENTRY * qglTexCoord4sv )(const GLshort *v); +void ( APIENTRY * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +void ( APIENTRY * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +void ( APIENTRY * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +void ( APIENTRY * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +void ( APIENTRY * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +void ( APIENTRY * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( APIENTRY * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +void ( APIENTRY * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +void ( APIENTRY * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglVertex2d )(GLdouble x, GLdouble y); +void ( APIENTRY * qglVertex2dv )(const GLdouble *v); +void ( APIENTRY * qglVertex2f )(GLfloat x, GLfloat y); +void ( APIENTRY * qglVertex2fv )(const GLfloat *v); +void ( APIENTRY * qglVertex2i )(GLint x, GLint y); +void ( APIENTRY * qglVertex2iv )(const GLint *v); +void ( APIENTRY * qglVertex2s )(GLshort x, GLshort y); +void ( APIENTRY * qglVertex2sv )(const GLshort *v); +void ( APIENTRY * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY * qglVertex3dv )(const GLdouble *v); +void ( APIENTRY * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY * qglVertex3fv )(const GLfloat *v); +void ( APIENTRY * qglVertex3i )(GLint x, GLint y, GLint z); +void ( APIENTRY * qglVertex3iv )(const GLint *v); +void ( APIENTRY * qglVertex3s )(GLshort x, GLshort y, GLshort z); +void ( APIENTRY * qglVertex3sv )(const GLshort *v); +void ( APIENTRY * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( APIENTRY * qglVertex4dv )(const GLdouble *v); +void ( APIENTRY * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY * qglVertex4fv )(const GLfloat *v); +void ( APIENTRY * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +void ( APIENTRY * qglVertex4iv )(const GLint *v); +void ( APIENTRY * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( APIENTRY * qglVertex4sv )(const GLshort *v); +void ( APIENTRY * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + + + +static void ( APIENTRY * dllAccum )(GLenum op, GLfloat value); +static void ( APIENTRY * dllAlphaFunc )(GLenum func, GLclampf ref); +GLboolean ( APIENTRY * dllAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +static void ( APIENTRY * dllArrayElement )(GLint i); +static void ( APIENTRY * dllBegin )(GLenum mode); +static void ( APIENTRY * dllBindTexture )(GLenum target, GLuint texture); +static void ( APIENTRY * dllBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +static void ( APIENTRY * dllBlendFunc )(GLenum sfactor, GLenum dfactor); +static void ( APIENTRY * dllCallList )(GLuint list); +static void ( APIENTRY * dllCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +static void ( APIENTRY * dllClear )(GLbitfield mask); +static void ( APIENTRY * dllClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +static void ( APIENTRY * dllClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +static void ( APIENTRY * dllClearDepth )(GLclampd depth); +static void ( APIENTRY * dllClearIndex )(GLfloat c); +static void ( APIENTRY * dllClearStencil )(GLint s); +static void ( APIENTRY * dllClipPlane )(GLenum plane, const GLdouble *equation); +static void ( APIENTRY * dllColor3b )(GLbyte red, GLbyte green, GLbyte blue); +static void ( APIENTRY * dllColor3bv )(const GLbyte *v); +static void ( APIENTRY * dllColor3d )(GLdouble red, GLdouble green, GLdouble blue); +static void ( APIENTRY * dllColor3dv )(const GLdouble *v); +static void ( APIENTRY * dllColor3f )(GLfloat red, GLfloat green, GLfloat blue); +static void ( APIENTRY * dllColor3fv )(const GLfloat *v); +static void ( APIENTRY * dllColor3i )(GLint red, GLint green, GLint blue); +static void ( APIENTRY * dllColor3iv )(const GLint *v); +static void ( APIENTRY * dllColor3s )(GLshort red, GLshort green, GLshort blue); +static void ( APIENTRY * dllColor3sv )(const GLshort *v); +static void ( APIENTRY * dllColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +static void ( APIENTRY * dllColor3ubv )(const GLubyte *v); +static void ( APIENTRY * dllColor3ui )(GLuint red, GLuint green, GLuint blue); +static void ( APIENTRY * dllColor3uiv )(const GLuint *v); +static void ( APIENTRY * dllColor3us )(GLushort red, GLushort green, GLushort blue); +static void ( APIENTRY * dllColor3usv )(const GLushort *v); +static void ( APIENTRY * dllColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +static void ( APIENTRY * dllColor4bv )(const GLbyte *v); +static void ( APIENTRY * dllColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +static void ( APIENTRY * dllColor4dv )(const GLdouble *v); +static void ( APIENTRY * dllColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +static void ( APIENTRY * dllColor4fv )(const GLfloat *v); +static void ( APIENTRY * dllColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +static void ( APIENTRY * dllColor4iv )(const GLint *v); +static void ( APIENTRY * dllColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +static void ( APIENTRY * dllColor4sv )(const GLshort *v); +static void ( APIENTRY * dllColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +static void ( APIENTRY * dllColor4ubv )(const GLubyte *v); +static void ( APIENTRY * dllColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +static void ( APIENTRY * dllColor4uiv )(const GLuint *v); +static void ( APIENTRY * dllColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +static void ( APIENTRY * dllColor4usv )(const GLushort *v); +static void ( APIENTRY * dllColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +static void ( APIENTRY * dllColorMaterial )(GLenum face, GLenum mode); +static void ( APIENTRY * dllColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +static void ( APIENTRY * dllCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +static void ( APIENTRY * dllCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +static void ( APIENTRY * dllCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +static void ( APIENTRY * dllCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +static void ( APIENTRY * dllCullFace )(GLenum mode); +static void ( APIENTRY * dllDeleteLists )(GLuint list, GLsizei range); +static void ( APIENTRY * dllDeleteTextures )(GLsizei n, const GLuint *textures); +static void ( APIENTRY * dllDepthFunc )(GLenum func); +static void ( APIENTRY * dllDepthMask )(GLboolean flag); +static void ( APIENTRY * dllDepthRange )(GLclampd zNear, GLclampd zFar); +static void ( APIENTRY * dllDisable )(GLenum cap); +static void ( APIENTRY * dllDisableClientState )(GLenum array); +static void ( APIENTRY * dllDrawArrays )(GLenum mode, GLint first, GLsizei count); +static void ( APIENTRY * dllDrawBuffer )(GLenum mode); +static void ( APIENTRY * dllDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +static void ( APIENTRY * dllDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllEdgeFlag )(GLboolean flag); +static void ( APIENTRY * dllEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllEdgeFlagv )(const GLboolean *flag); +static void ( APIENTRY * dllEnable )(GLenum cap); +static void ( APIENTRY * dllEnableClientState )(GLenum array); +static void ( APIENTRY * dllEnd )(void); +static void ( APIENTRY * dllEndList )(void); +static void ( APIENTRY * dllEvalCoord1d )(GLdouble u); +static void ( APIENTRY * dllEvalCoord1dv )(const GLdouble *u); +static void ( APIENTRY * dllEvalCoord1f )(GLfloat u); +static void ( APIENTRY * dllEvalCoord1fv )(const GLfloat *u); +static void ( APIENTRY * dllEvalCoord2d )(GLdouble u, GLdouble v); +static void ( APIENTRY * dllEvalCoord2dv )(const GLdouble *u); +static void ( APIENTRY * dllEvalCoord2f )(GLfloat u, GLfloat v); +static void ( APIENTRY * dllEvalCoord2fv )(const GLfloat *u); +static void ( APIENTRY * dllEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +static void ( APIENTRY * dllEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +static void ( APIENTRY * dllEvalPoint1 )(GLint i); +static void ( APIENTRY * dllEvalPoint2 )(GLint i, GLint j); +static void ( APIENTRY * dllFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +static void ( APIENTRY * dllFinish )(void); +static void ( APIENTRY * dllFlush )(void); +static void ( APIENTRY * dllFogf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllFogfv )(GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllFogi )(GLenum pname, GLint param); +static void ( APIENTRY * dllFogiv )(GLenum pname, const GLint *params); +static void ( APIENTRY * dllFrontFace )(GLenum mode); +static void ( APIENTRY * dllFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLuint ( APIENTRY * dllGenLists )(GLsizei range); +static void ( APIENTRY * dllGenTextures )(GLsizei n, GLuint *textures); +static void ( APIENTRY * dllGetBooleanv )(GLenum pname, GLboolean *params); +static void ( APIENTRY * dllGetClipPlane )(GLenum plane, GLdouble *equation); +static void ( APIENTRY * dllGetDoublev )(GLenum pname, GLdouble *params); +GLenum ( APIENTRY * dllGetError )(void); +static void ( APIENTRY * dllGetFloatv )(GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetIntegerv )(GLenum pname, GLint *params); +static void ( APIENTRY * dllGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetLightiv )(GLenum light, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetMapdv )(GLenum target, GLenum query, GLdouble *v); +static void ( APIENTRY * dllGetMapfv )(GLenum target, GLenum query, GLfloat *v); +static void ( APIENTRY * dllGetMapiv )(GLenum target, GLenum query, GLint *v); +static void ( APIENTRY * dllGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetPixelMapfv )(GLenum map, GLfloat *values); +static void ( APIENTRY * dllGetPixelMapuiv )(GLenum map, GLuint *values); +static void ( APIENTRY * dllGetPixelMapusv )(GLenum map, GLushort *values); +static void ( APIENTRY * dllGetPointerv )(GLenum pname, GLvoid* *params); +static void ( APIENTRY * dllGetPolygonStipple )(GLubyte *mask); +const GLubyte * ( APIENTRY * dllGetString )(GLenum name); +static void ( APIENTRY * dllGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +static void ( APIENTRY * dllGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +static void ( APIENTRY * dllGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +static void ( APIENTRY * dllGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +static void ( APIENTRY * dllGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +static void ( APIENTRY * dllHint )(GLenum target, GLenum mode); +static void ( APIENTRY * dllIndexMask )(GLuint mask); +static void ( APIENTRY * dllIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllIndexd )(GLdouble c); +static void ( APIENTRY * dllIndexdv )(const GLdouble *c); +static void ( APIENTRY * dllIndexf )(GLfloat c); +static void ( APIENTRY * dllIndexfv )(const GLfloat *c); +static void ( APIENTRY * dllIndexi )(GLint c); +static void ( APIENTRY * dllIndexiv )(const GLint *c); +static void ( APIENTRY * dllIndexs )(GLshort c); +static void ( APIENTRY * dllIndexsv )(const GLshort *c); +static void ( APIENTRY * dllIndexub )(GLubyte c); +static void ( APIENTRY * dllIndexubv )(const GLubyte *c); +static void ( APIENTRY * dllInitNames )(void); +static void ( APIENTRY * dllInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +GLboolean ( APIENTRY * dllIsEnabled )(GLenum cap); +GLboolean ( APIENTRY * dllIsList )(GLuint list); +GLboolean ( APIENTRY * dllIsTexture )(GLuint texture); +static void ( APIENTRY * dllLightModelf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllLightModelfv )(GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllLightModeli )(GLenum pname, GLint param); +static void ( APIENTRY * dllLightModeliv )(GLenum pname, const GLint *params); +static void ( APIENTRY * dllLightf )(GLenum light, GLenum pname, GLfloat param); +static void ( APIENTRY * dllLightfv )(GLenum light, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllLighti )(GLenum light, GLenum pname, GLint param); +static void ( APIENTRY * dllLightiv )(GLenum light, GLenum pname, const GLint *params); +static void ( APIENTRY * dllLineStipple )(GLint factor, GLushort pattern); +static void ( APIENTRY * dllLineWidth )(GLfloat width); +static void ( APIENTRY * dllListBase )(GLuint base); +static void ( APIENTRY * dllLoadIdentity )(void); +static void ( APIENTRY * dllLoadMatrixd )(const GLdouble *m); +static void ( APIENTRY * dllLoadMatrixf )(const GLfloat *m); +static void ( APIENTRY * dllLoadName )(GLuint name); +static void ( APIENTRY * dllLogicOp )(GLenum opcode); +static void ( APIENTRY * dllMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +static void ( APIENTRY * dllMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +static void ( APIENTRY * dllMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +static void ( APIENTRY * dllMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +static void ( APIENTRY * dllMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +static void ( APIENTRY * dllMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +static void ( APIENTRY * dllMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +static void ( APIENTRY * dllMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +static void ( APIENTRY * dllMaterialf )(GLenum face, GLenum pname, GLfloat param); +static void ( APIENTRY * dllMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllMateriali )(GLenum face, GLenum pname, GLint param); +static void ( APIENTRY * dllMaterialiv )(GLenum face, GLenum pname, const GLint *params); +static void ( APIENTRY * dllMatrixMode )(GLenum mode); +static void ( APIENTRY * dllMultMatrixd )(const GLdouble *m); +static void ( APIENTRY * dllMultMatrixf )(const GLfloat *m); +static void ( APIENTRY * dllNewList )(GLuint list, GLenum mode); +static void ( APIENTRY * dllNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +static void ( APIENTRY * dllNormal3bv )(const GLbyte *v); +static void ( APIENTRY * dllNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +static void ( APIENTRY * dllNormal3dv )(const GLdouble *v); +static void ( APIENTRY * dllNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +static void ( APIENTRY * dllNormal3fv )(const GLfloat *v); +static void ( APIENTRY * dllNormal3i )(GLint nx, GLint ny, GLint nz); +static void ( APIENTRY * dllNormal3iv )(const GLint *v); +static void ( APIENTRY * dllNormal3s )(GLshort nx, GLshort ny, GLshort nz); +static void ( APIENTRY * dllNormal3sv )(const GLshort *v); +static void ( APIENTRY * dllNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +static void ( APIENTRY * dllPassThrough )(GLfloat token); +static void ( APIENTRY * dllPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +static void ( APIENTRY * dllPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +static void ( APIENTRY * dllPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +static void ( APIENTRY * dllPixelStoref )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllPixelStorei )(GLenum pname, GLint param); +static void ( APIENTRY * dllPixelTransferf )(GLenum pname, GLfloat param); +static void ( APIENTRY * dllPixelTransferi )(GLenum pname, GLint param); +static void ( APIENTRY * dllPixelZoom )(GLfloat xfactor, GLfloat yfactor); +static void ( APIENTRY * dllPointSize )(GLfloat size); +static void ( APIENTRY * dllPolygonMode )(GLenum face, GLenum mode); +static void ( APIENTRY * dllPolygonOffset )(GLfloat factor, GLfloat units); +static void ( APIENTRY * dllPolygonStipple )(const GLubyte *mask); +static void ( APIENTRY * dllPopAttrib )(void); +static void ( APIENTRY * dllPopClientAttrib )(void); +static void ( APIENTRY * dllPopMatrix )(void); +static void ( APIENTRY * dllPopName )(void); +static void ( APIENTRY * dllPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +static void ( APIENTRY * dllPushAttrib )(GLbitfield mask); +static void ( APIENTRY * dllPushClientAttrib )(GLbitfield mask); +static void ( APIENTRY * dllPushMatrix )(void); +static void ( APIENTRY * dllPushName )(GLuint name); +static void ( APIENTRY * dllRasterPos2d )(GLdouble x, GLdouble y); +static void ( APIENTRY * dllRasterPos2dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos2f )(GLfloat x, GLfloat y); +static void ( APIENTRY * dllRasterPos2fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos2i )(GLint x, GLint y); +static void ( APIENTRY * dllRasterPos2iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos2s )(GLshort x, GLshort y); +static void ( APIENTRY * dllRasterPos2sv )(const GLshort *v); +static void ( APIENTRY * dllRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllRasterPos3dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllRasterPos3fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos3i )(GLint x, GLint y, GLint z); +static void ( APIENTRY * dllRasterPos3iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos3s )(GLshort x, GLshort y, GLshort z); +static void ( APIENTRY * dllRasterPos3sv )(const GLshort *v); +static void ( APIENTRY * dllRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +static void ( APIENTRY * dllRasterPos4dv )(const GLdouble *v); +static void ( APIENTRY * dllRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +static void ( APIENTRY * dllRasterPos4fv )(const GLfloat *v); +static void ( APIENTRY * dllRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +static void ( APIENTRY * dllRasterPos4iv )(const GLint *v); +static void ( APIENTRY * dllRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +static void ( APIENTRY * dllRasterPos4sv )(const GLshort *v); +static void ( APIENTRY * dllReadBuffer )(GLenum mode); +static void ( APIENTRY * dllReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +static void ( APIENTRY * dllRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +static void ( APIENTRY * dllRectdv )(const GLdouble *v1, const GLdouble *v2); +static void ( APIENTRY * dllRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +static void ( APIENTRY * dllRectfv )(const GLfloat *v1, const GLfloat *v2); +static void ( APIENTRY * dllRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +static void ( APIENTRY * dllRectiv )(const GLint *v1, const GLint *v2); +static void ( APIENTRY * dllRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +static void ( APIENTRY * dllRectsv )(const GLshort *v1, const GLshort *v2); +GLint ( APIENTRY * dllRenderMode )(GLenum mode); +static void ( APIENTRY * dllRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllScaled )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllScalef )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +static void ( APIENTRY * dllSelectBuffer )(GLsizei size, GLuint *buffer); +static void ( APIENTRY * dllShadeModel )(GLenum mode); +static void ( APIENTRY * dllStencilFunc )(GLenum func, GLint ref, GLuint mask); +static void ( APIENTRY * dllStencilMask )(GLuint mask); +static void ( APIENTRY * dllStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +static void ( APIENTRY * dllTexCoord1d )(GLdouble s); +static void ( APIENTRY * dllTexCoord1dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord1f )(GLfloat s); +static void ( APIENTRY * dllTexCoord1fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord1i )(GLint s); +static void ( APIENTRY * dllTexCoord1iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord1s )(GLshort s); +static void ( APIENTRY * dllTexCoord1sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord2d )(GLdouble s, GLdouble t); +static void ( APIENTRY * dllTexCoord2dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord2f )(GLfloat s, GLfloat t); +static void ( APIENTRY * dllTexCoord2fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord2i )(GLint s, GLint t); +static void ( APIENTRY * dllTexCoord2iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord2s )(GLshort s, GLshort t); +static void ( APIENTRY * dllTexCoord2sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +static void ( APIENTRY * dllTexCoord3dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +static void ( APIENTRY * dllTexCoord3fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord3i )(GLint s, GLint t, GLint r); +static void ( APIENTRY * dllTexCoord3iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord3s )(GLshort s, GLshort t, GLshort r); +static void ( APIENTRY * dllTexCoord3sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +static void ( APIENTRY * dllTexCoord4dv )(const GLdouble *v); +static void ( APIENTRY * dllTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +static void ( APIENTRY * dllTexCoord4fv )(const GLfloat *v); +static void ( APIENTRY * dllTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +static void ( APIENTRY * dllTexCoord4iv )(const GLint *v); +static void ( APIENTRY * dllTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +static void ( APIENTRY * dllTexCoord4sv )(const GLshort *v); +static void ( APIENTRY * dllTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllTexEnvf )(GLenum target, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexEnvi )(GLenum target, GLenum pname, GLint param); +static void ( APIENTRY * dllTexEnviv )(GLenum target, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexGend )(GLenum coord, GLenum pname, GLdouble param); +static void ( APIENTRY * dllTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +static void ( APIENTRY * dllTexGenf )(GLenum coord, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexGeni )(GLenum coord, GLenum pname, GLint param); +static void ( APIENTRY * dllTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexParameterf )(GLenum target, GLenum pname, GLfloat param); +static void ( APIENTRY * dllTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +static void ( APIENTRY * dllTexParameteri )(GLenum target, GLenum pname, GLint param); +static void ( APIENTRY * dllTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +static void ( APIENTRY * dllTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +static void ( APIENTRY * dllTranslated )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllTranslatef )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllVertex2d )(GLdouble x, GLdouble y); +static void ( APIENTRY * dllVertex2dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex2f )(GLfloat x, GLfloat y); +static void ( APIENTRY * dllVertex2fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex2i )(GLint x, GLint y); +static void ( APIENTRY * dllVertex2iv )(const GLint *v); +static void ( APIENTRY * dllVertex2s )(GLshort x, GLshort y); +static void ( APIENTRY * dllVertex2sv )(const GLshort *v); +static void ( APIENTRY * dllVertex3d )(GLdouble x, GLdouble y, GLdouble z); +static void ( APIENTRY * dllVertex3dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex3f )(GLfloat x, GLfloat y, GLfloat z); +static void ( APIENTRY * dllVertex3fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex3i )(GLint x, GLint y, GLint z); +static void ( APIENTRY * dllVertex3iv )(const GLint *v); +static void ( APIENTRY * dllVertex3s )(GLshort x, GLshort y, GLshort z); +static void ( APIENTRY * dllVertex3sv )(const GLshort *v); +static void ( APIENTRY * dllVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +static void ( APIENTRY * dllVertex4dv )(const GLdouble *v); +static void ( APIENTRY * dllVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +static void ( APIENTRY * dllVertex4fv )(const GLfloat *v); +static void ( APIENTRY * dllVertex4i )(GLint x, GLint y, GLint z, GLint w); +static void ( APIENTRY * dllVertex4iv )(const GLint *v); +static void ( APIENTRY * dllVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +static void ( APIENTRY * dllVertex4sv )(const GLshort *v); +static void ( APIENTRY * dllVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +static void ( APIENTRY * dllViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +static const char * BooleanToString( GLboolean b ) +{ + if ( b == GL_FALSE ) + return "GL_FALSE"; + else if ( b == GL_TRUE ) + return "GL_TRUE"; + else + return "OUT OF RANGE FOR BOOLEAN"; +} + +static const char * FuncToString( GLenum f ) +{ + switch ( f ) + { + case GL_ALWAYS: + return "GL_ALWAYS"; + case GL_NEVER: + return "GL_NEVER"; + case GL_LEQUAL: + return "GL_LEQUAL"; + case GL_LESS: + return "GL_LESS"; + case GL_EQUAL: + return "GL_EQUAL"; + case GL_GREATER: + return "GL_GREATER"; + case GL_GEQUAL: + return "GL_GEQUAL"; + case GL_NOTEQUAL: + return "GL_NOTEQUAL"; + default: + return "!!! UNKNOWN !!!"; + } +} + +static const char * PrimToString( GLenum mode ) +{ + static char prim[1024]; + + if ( mode == GL_TRIANGLES ) + strcpy( prim, "GL_TRIANGLES" ); + else if ( mode == GL_TRIANGLE_STRIP ) + strcpy( prim, "GL_TRIANGLE_STRIP" ); + else if ( mode == GL_TRIANGLE_FAN ) + strcpy( prim, "GL_TRIANGLE_FAN" ); + else if ( mode == GL_QUADS ) + strcpy( prim, "GL_QUADS" ); + else if ( mode == GL_QUAD_STRIP ) + strcpy( prim, "GL_QUAD_STRIP" ); + else if ( mode == GL_POLYGON ) + strcpy( prim, "GL_POLYGON" ); + else if ( mode == GL_POINTS ) + strcpy( prim, "GL_POINTS" ); + else if ( mode == GL_LINES ) + strcpy( prim, "GL_LINES" ); + else if ( mode == GL_LINE_STRIP ) + strcpy( prim, "GL_LINE_STRIP" ); + else if ( mode == GL_LINE_LOOP ) + strcpy( prim, "GL_LINE_LOOP" ); + else + sprintf( prim, "0x%x", mode ); + + return prim; +} + +static const char * CapToString( GLenum cap ) +{ + static char buffer[1024]; + + switch ( cap ) + { + case GL_TEXTURE_2D: + return "GL_TEXTURE_2D"; + case GL_BLEND: + return "GL_BLEND"; + case GL_DEPTH_TEST: + return "GL_DEPTH_TEST"; + case GL_CULL_FACE: + return "GL_CULL_FACE"; + case GL_CLIP_PLANE0: + return "GL_CLIP_PLANE0"; + case GL_COLOR_ARRAY: + return "GL_COLOR_ARRAY"; + case GL_TEXTURE_COORD_ARRAY: + return "GL_TEXTURE_COORD_ARRAY"; + case GL_VERTEX_ARRAY: + return "GL_VERTEX_ARRAY"; + case GL_ALPHA_TEST: + return "GL_ALPHA_TEST"; + case GL_STENCIL_TEST: + return "GL_STENCIL_TEST"; + default: + sprintf( buffer, "0x%x", cap ); + } + + return buffer; +} + +static const char * TypeToString( GLenum t ) +{ + switch ( t ) + { + case GL_BYTE: + return "GL_BYTE"; + case GL_UNSIGNED_BYTE: + return "GL_UNSIGNED_BYTE"; + case GL_SHORT: + return "GL_SHORT"; + case GL_UNSIGNED_SHORT: + return "GL_UNSIGNED_SHORT"; + case GL_INT: + return "GL_INT"; + case GL_UNSIGNED_INT: + return "GL_UNSIGNED_INT"; + case GL_FLOAT: + return "GL_FLOAT"; + case GL_DOUBLE: + return "GL_DOUBLE"; + default: + return "!!! UNKNOWN !!!"; + } +} + +static void APIENTRY logAccum(GLenum op, GLfloat value) +{ + fprintf( glw_state.log_fp, "glAccum\n" ); + dllAccum( op, value ); +} + +static void APIENTRY logAlphaFunc(GLenum func, GLclampf ref) +{ + fprintf( glw_state.log_fp, "glAlphaFunc( 0x%x, %f )\n", func, ref ); + dllAlphaFunc( func, ref ); +} + +static GLboolean APIENTRY logAreTexturesResident(GLsizei n, const GLuint *textures, GLboolean *residences) +{ + fprintf( glw_state.log_fp, "glAreTexturesResident\n" ); + return dllAreTexturesResident( n, textures, residences ); +} + +static void APIENTRY logArrayElement(GLint i) +{ + fprintf( glw_state.log_fp, "glArrayElement\n" ); + dllArrayElement( i ); +} + +static void APIENTRY logBegin(GLenum mode) +{ + fprintf( glw_state.log_fp, "glBegin( %s )\n", PrimToString( mode )); + dllBegin( mode ); +} + +static void APIENTRY logBindTexture(GLenum target, GLuint texture) +{ + fprintf( glw_state.log_fp, "glBindTexture( 0x%x, %u )\n", target, texture ); + dllBindTexture( target, texture ); +} + +static void APIENTRY logBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap) +{ + fprintf( glw_state.log_fp, "glBitmap\n" ); + dllBitmap( width, height, xorig, yorig, xmove, ymove, bitmap ); +} + +static void BlendToName( char *n, GLenum f ) +{ + switch ( f ) + { + case GL_ONE: + strcpy( n, "GL_ONE" ); + break; + case GL_ZERO: + strcpy( n, "GL_ZERO" ); + break; + case GL_SRC_ALPHA: + strcpy( n, "GL_SRC_ALPHA" ); + break; + case GL_ONE_MINUS_SRC_ALPHA: + strcpy( n, "GL_ONE_MINUS_SRC_ALPHA" ); + break; + case GL_DST_COLOR: + strcpy( n, "GL_DST_COLOR" ); + break; + case GL_ONE_MINUS_DST_COLOR: + strcpy( n, "GL_ONE_MINUS_DST_COLOR" ); + break; + case GL_DST_ALPHA: + strcpy( n, "GL_DST_ALPHA" ); + break; + default: + sprintf( n, "0x%x", f ); + } +} +static void APIENTRY logBlendFunc(GLenum sfactor, GLenum dfactor) +{ + char sf[128], df[128]; + + BlendToName( sf, sfactor ); + BlendToName( df, dfactor ); + + fprintf( glw_state.log_fp, "glBlendFunc( %s, %s )\n", sf, df ); + dllBlendFunc( sfactor, dfactor ); +} + +static void APIENTRY logCallList(GLuint list) +{ + fprintf( glw_state.log_fp, "glCallList( %u )\n", list ); + dllCallList( list ); +} + +static void APIENTRY logCallLists(GLsizei n, GLenum type, const void *lists) +{ + fprintf( glw_state.log_fp, "glCallLists\n" ); + dllCallLists( n, type, lists ); +} + +static void APIENTRY logClear(GLbitfield mask) +{ + fprintf( glw_state.log_fp, "glClear( 0x%x = ", mask ); + + if ( mask & GL_COLOR_BUFFER_BIT ) + fprintf( glw_state.log_fp, "GL_COLOR_BUFFER_BIT " ); + if ( mask & GL_DEPTH_BUFFER_BIT ) + fprintf( glw_state.log_fp, "GL_DEPTH_BUFFER_BIT " ); + if ( mask & GL_STENCIL_BUFFER_BIT ) + fprintf( glw_state.log_fp, "GL_STENCIL_BUFFER_BIT " ); + if ( mask & GL_ACCUM_BUFFER_BIT ) + fprintf( glw_state.log_fp, "GL_ACCUM_BUFFER_BIT " ); + + fprintf( glw_state.log_fp, ")\n" ); + dllClear( mask ); +} + +static void APIENTRY logClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + fprintf( glw_state.log_fp, "glClearAccum\n" ); + dllClearAccum( red, green, blue, alpha ); +} + +static void APIENTRY logClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) +{ + fprintf( glw_state.log_fp, "glClearColor\n" ); + dllClearColor( red, green, blue, alpha ); +} + +static void APIENTRY logClearDepth(GLclampd depth) +{ + fprintf( glw_state.log_fp, "glClearDepth( %f )\n", ( float ) depth ); + dllClearDepth( depth ); +} + +static void APIENTRY logClearIndex(GLfloat c) +{ + fprintf( glw_state.log_fp, "glClearIndex\n" ); + dllClearIndex( c ); +} + +static void APIENTRY logClearStencil(GLint s) +{ + fprintf( glw_state.log_fp, "glClearStencil( %d )\n", s ); + dllClearStencil( s ); +} + +static void APIENTRY logClipPlane(GLenum plane, const GLdouble *equation) +{ + fprintf( glw_state.log_fp, "glClipPlane\n" ); + dllClipPlane( plane, equation ); +} + +static void APIENTRY logColor3b(GLbyte red, GLbyte green, GLbyte blue) +{ + fprintf( glw_state.log_fp, "glColor3b\n" ); + dllColor3b( red, green, blue ); +} + +static void APIENTRY logColor3bv(const GLbyte *v) +{ + fprintf( glw_state.log_fp, "glColor3bv\n" ); + dllColor3bv( v ); +} + +static void APIENTRY logColor3d(GLdouble red, GLdouble green, GLdouble blue) +{ + fprintf( glw_state.log_fp, "glColor3d\n" ); + dllColor3d( red, green, blue ); +} + +static void APIENTRY logColor3dv(const GLdouble *v) +{ + fprintf( glw_state.log_fp, "glColor3dv\n" ); + dllColor3dv( v ); +} + +static void APIENTRY logColor3f(GLfloat red, GLfloat green, GLfloat blue) +{ + fprintf( glw_state.log_fp, "glColor3f\n" ); + dllColor3f( red, green, blue ); +} + +static void APIENTRY logColor3fv(const GLfloat *v) +{ + fprintf( glw_state.log_fp, "glColor3fv\n" ); + dllColor3fv( v ); +} + +static void APIENTRY logColor3i(GLint red, GLint green, GLint blue) +{ + fprintf( glw_state.log_fp, "glColor3i\n" ); + dllColor3i( red, green, blue ); +} + +static void APIENTRY logColor3iv(const GLint *v) +{ + fprintf( glw_state.log_fp, "glColor3iv\n" ); + dllColor3iv( v ); +} + +static void APIENTRY logColor3s(GLshort red, GLshort green, GLshort blue) +{ + fprintf( glw_state.log_fp, "glColor3s\n" ); + dllColor3s( red, green, blue ); +} + +static void APIENTRY logColor3sv(const GLshort *v) +{ + fprintf( glw_state.log_fp, "glColor3sv\n" ); + dllColor3sv( v ); +} + +static void APIENTRY logColor3ub(GLubyte red, GLubyte green, GLubyte blue) +{ + fprintf( glw_state.log_fp, "glColor3ub\n" ); + dllColor3ub( red, green, blue ); +} + +static void APIENTRY logColor3ubv(const GLubyte *v) +{ + fprintf( glw_state.log_fp, "glColor3ubv\n" ); + dllColor3ubv( v ); +} + +#define SIG( x ) fprintf( glw_state.log_fp, x "\n" ) + +static void APIENTRY logColor3ui(GLuint red, GLuint green, GLuint blue) +{ + SIG( "glColor3ui" ); + dllColor3ui( red, green, blue ); +} + +static void APIENTRY logColor3uiv(const GLuint *v) +{ + SIG( "glColor3uiv" ); + dllColor3uiv( v ); +} + +static void APIENTRY logColor3us(GLushort red, GLushort green, GLushort blue) +{ + SIG( "glColor3us" ); + dllColor3us( red, green, blue ); +} + +static void APIENTRY logColor3usv(const GLushort *v) +{ + SIG( "glColor3usv" ); + dllColor3usv( v ); +} + +static void APIENTRY logColor4b(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha) +{ + SIG( "glColor4b" ); + dllColor4b( red, green, blue, alpha ); +} + +static void APIENTRY logColor4bv(const GLbyte *v) +{ + SIG( "glColor4bv" ); + dllColor4bv( v ); +} + +static void APIENTRY logColor4d(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha) +{ + SIG( "glColor4d" ); + dllColor4d( red, green, blue, alpha ); +} +static void APIENTRY logColor4dv(const GLdouble *v) +{ + SIG( "glColor4dv" ); + dllColor4dv( v ); +} +static void APIENTRY logColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + fprintf( glw_state.log_fp, "glColor4f( %f,%f,%f,%f )\n", red, green, blue, alpha ); + dllColor4f( red, green, blue, alpha ); +} +static void APIENTRY logColor4fv(const GLfloat *v) +{ + fprintf( glw_state.log_fp, "glColor4fv( %f,%f,%f,%f )\n", v[0], v[1], v[2], v[3] ); + dllColor4fv( v ); +} +static void APIENTRY logColor4i(GLint red, GLint green, GLint blue, GLint alpha) +{ + SIG( "glColor4i" ); + dllColor4i( red, green, blue, alpha ); +} +static void APIENTRY logColor4iv(const GLint *v) +{ + SIG( "glColor4iv" ); + dllColor4iv( v ); +} +static void APIENTRY logColor4s(GLshort red, GLshort green, GLshort blue, GLshort alpha) +{ + SIG( "glColor4s" ); + dllColor4s( red, green, blue, alpha ); +} +static void APIENTRY logColor4sv(const GLshort *v) +{ + SIG( "glColor4sv" ); + dllColor4sv( v ); +} +static void APIENTRY logColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) +{ + SIG( "glColor4b" ); + dllColor4b( red, green, blue, alpha ); +} +static void APIENTRY logColor4ubv(const GLubyte *v) +{ + SIG( "glColor4ubv" ); + dllColor4ubv( v ); +} +static void APIENTRY logColor4ui(GLuint red, GLuint green, GLuint blue, GLuint alpha) +{ + SIG( "glColor4ui" ); + dllColor4ui( red, green, blue, alpha ); +} +static void APIENTRY logColor4uiv(const GLuint *v) +{ + SIG( "glColor4uiv" ); + dllColor4uiv( v ); +} +static void APIENTRY logColor4us(GLushort red, GLushort green, GLushort blue, GLushort alpha) +{ + SIG( "glColor4us" ); + dllColor4us( red, green, blue, alpha ); +} +static void APIENTRY logColor4usv(const GLushort *v) +{ + SIG( "glColor4usv" ); + dllColor4usv( v ); +} +static void APIENTRY logColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) +{ + SIG( "glColorMask" ); + dllColorMask( red, green, blue, alpha ); +} +static void APIENTRY logColorMaterial(GLenum face, GLenum mode) +{ + SIG( "glColorMaterial" ); + dllColorMaterial( face, mode ); +} + +static void APIENTRY logColorPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) +{ + fprintf( glw_state.log_fp, "glColorPointer( %d, %s, %d, MEM )\n", size, TypeToString( type ), stride ); + dllColorPointer( size, type, stride, pointer ); +} + +static void APIENTRY logCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type) +{ + SIG( "glCopyPixels" ); + dllCopyPixels( x, y, width, height, type ); +} + +static void APIENTRY logCopyTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border) +{ + SIG( "glCopyTexImage1D" ); + dllCopyTexImage1D( target, level, internalFormat, x, y, width, border ); +} + +static void APIENTRY logCopyTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) +{ + SIG( "glCopyTexImage2D" ); + dllCopyTexImage2D( target, level, internalFormat, x, y, width, height, border ); +} + +static void APIENTRY logCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) +{ + SIG( "glCopyTexSubImage1D" ); + dllCopyTexSubImage1D( target, level, xoffset, x, y, width ); +} + +static void APIENTRY logCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) +{ + SIG( "glCopyTexSubImage2D" ); + dllCopyTexSubImage2D( target, level, xoffset, yoffset, x, y, width, height ); +} + +static void APIENTRY logCullFace(GLenum mode) +{ + fprintf( glw_state.log_fp, "glCullFace( %s )\n", ( mode == GL_FRONT ) ? "GL_FRONT" : "GL_BACK" ); + dllCullFace( mode ); +} + +static void APIENTRY logDeleteLists(GLuint list, GLsizei range) +{ + SIG( "glDeleteLists" ); + dllDeleteLists( list, range ); +} + +static void APIENTRY logDeleteTextures(GLsizei n, const GLuint *textures) +{ + SIG( "glDeleteTextures" ); + dllDeleteTextures( n, textures ); +} + +static void APIENTRY logDepthFunc(GLenum func) +{ + fprintf( glw_state.log_fp, "glDepthFunc( %s )\n", FuncToString( func ) ); + dllDepthFunc( func ); +} + +static void APIENTRY logDepthMask(GLboolean flag) +{ + fprintf( glw_state.log_fp, "glDepthMask( %s )\n", BooleanToString( flag ) ); + dllDepthMask( flag ); +} + +static void APIENTRY logDepthRange(GLclampd zNear, GLclampd zFar) +{ + fprintf( glw_state.log_fp, "glDepthRange( %f, %f )\n", ( float ) zNear, ( float ) zFar ); + dllDepthRange( zNear, zFar ); +} + +static void APIENTRY logDisable(GLenum cap) +{ + fprintf( glw_state.log_fp, "glDisable( %s )\n", CapToString( cap ) ); + dllDisable( cap ); +} + +static void APIENTRY logDisableClientState(GLenum array) +{ + fprintf( glw_state.log_fp, "glDisableClientState( %s )\n", CapToString( array ) ); + dllDisableClientState( array ); +} + +static void APIENTRY logDrawArrays(GLenum mode, GLint first, GLsizei count) +{ + SIG( "glDrawArrays" ); + dllDrawArrays( mode, first, count ); +} + +static void APIENTRY logDrawBuffer(GLenum mode) +{ + SIG( "glDrawBuffer" ); + dllDrawBuffer( mode ); +} + +static void APIENTRY logDrawElements(GLenum mode, GLsizei count, GLenum type, const void *indices) +{ + fprintf( glw_state.log_fp, "glDrawElements( %s, %d, %s, MEM )\n", PrimToString( mode ), count, TypeToString( type ) ); + dllDrawElements( mode, count, type, indices ); +} + +static void APIENTRY logDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glDrawPixels" ); + dllDrawPixels( width, height, format, type, pixels ); +} + +static void APIENTRY logEdgeFlag(GLboolean flag) +{ + SIG( "glEdgeFlag" ); + dllEdgeFlag( flag ); +} + +static void APIENTRY logEdgeFlagPointer(GLsizei stride, const void *pointer) +{ + SIG( "glEdgeFlagPointer" ); + dllEdgeFlagPointer( stride, pointer ); +} + +static void APIENTRY logEdgeFlagv(const GLboolean *flag) +{ + SIG( "glEdgeFlagv" ); + dllEdgeFlagv( flag ); +} + +static void APIENTRY logEnable(GLenum cap) +{ + fprintf( glw_state.log_fp, "glEnable( %s )\n", CapToString( cap ) ); + dllEnable( cap ); +} + +static void APIENTRY logEnableClientState(GLenum array) +{ + fprintf( glw_state.log_fp, "glEnableClientState( %s )\n", CapToString( array ) ); + dllEnableClientState( array ); +} + +static void APIENTRY logEnd(void) +{ + SIG( "glEnd" ); + dllEnd(); +} + +static void APIENTRY logEndList(void) +{ + SIG( "glEndList" ); + dllEndList(); +} + +static void APIENTRY logEvalCoord1d(GLdouble u) +{ + SIG( "glEvalCoord1d" ); + dllEvalCoord1d( u ); +} + +static void APIENTRY logEvalCoord1dv(const GLdouble *u) +{ + SIG( "glEvalCoord1dv" ); + dllEvalCoord1dv( u ); +} + +static void APIENTRY logEvalCoord1f(GLfloat u) +{ + SIG( "glEvalCoord1f" ); + dllEvalCoord1f( u ); +} + +static void APIENTRY logEvalCoord1fv(const GLfloat *u) +{ + SIG( "glEvalCoord1fv" ); + dllEvalCoord1fv( u ); +} +static void APIENTRY logEvalCoord2d(GLdouble u, GLdouble v) +{ + SIG( "glEvalCoord2d" ); + dllEvalCoord2d( u, v ); +} +static void APIENTRY logEvalCoord2dv(const GLdouble *u) +{ + SIG( "glEvalCoord2dv" ); + dllEvalCoord2dv( u ); +} +static void APIENTRY logEvalCoord2f(GLfloat u, GLfloat v) +{ + SIG( "glEvalCoord2f" ); + dllEvalCoord2f( u, v ); +} +static void APIENTRY logEvalCoord2fv(const GLfloat *u) +{ + SIG( "glEvalCoord2fv" ); + dllEvalCoord2fv( u ); +} + +static void APIENTRY logEvalMesh1(GLenum mode, GLint i1, GLint i2) +{ + SIG( "glEvalMesh1" ); + dllEvalMesh1( mode, i1, i2 ); +} +static void APIENTRY logEvalMesh2(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2) +{ + SIG( "glEvalMesh2" ); + dllEvalMesh2( mode, i1, i2, j1, j2 ); +} +static void APIENTRY logEvalPoint1(GLint i) +{ + SIG( "glEvalPoint1" ); + dllEvalPoint1( i ); +} +static void APIENTRY logEvalPoint2(GLint i, GLint j) +{ + SIG( "glEvalPoint2" ); + dllEvalPoint2( i, j ); +} + +static void APIENTRY logFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer) +{ + SIG( "glFeedbackBuffer" ); + dllFeedbackBuffer( size, type, buffer ); +} + +static void APIENTRY logFinish(void) +{ + SIG( "glFinish" ); + dllFinish(); +} + +static void APIENTRY logFlush(void) +{ + SIG( "glFlush" ); + dllFlush(); +} + +static void APIENTRY logFogf(GLenum pname, GLfloat param) +{ + SIG( "glFogf" ); + dllFogf( pname, param ); +} + +static void APIENTRY logFogfv(GLenum pname, const GLfloat *params) +{ + SIG( "glFogfv" ); + dllFogfv( pname, params ); +} + +static void APIENTRY logFogi(GLenum pname, GLint param) +{ + SIG( "glFogi" ); + dllFogi( pname, param ); +} + +static void APIENTRY logFogiv(GLenum pname, const GLint *params) +{ + SIG( "glFogiv" ); + dllFogiv( pname, params ); +} + +static void APIENTRY logFrontFace(GLenum mode) +{ + SIG( "glFrontFace" ); + dllFrontFace( mode ); +} + +static void APIENTRY logFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + SIG( "glFrustum" ); + dllFrustum( left, right, bottom, top, zNear, zFar ); +} + +static GLuint APIENTRY logGenLists(GLsizei range) +{ + SIG( "glGenLists" ); + return dllGenLists( range ); +} + +static void APIENTRY logGenTextures(GLsizei n, GLuint *textures) +{ + SIG( "glGenTextures" ); + dllGenTextures( n, textures ); +} + +static void APIENTRY logGetBooleanv(GLenum pname, GLboolean *params) +{ + SIG( "glGetBooleanv" ); + dllGetBooleanv( pname, params ); +} + +static void APIENTRY logGetClipPlane(GLenum plane, GLdouble *equation) +{ + SIG( "glGetClipPlane" ); + dllGetClipPlane( plane, equation ); +} + +static void APIENTRY logGetDoublev(GLenum pname, GLdouble *params) +{ + SIG( "glGetDoublev" ); + dllGetDoublev( pname, params ); +} + +static GLenum APIENTRY logGetError(void) +{ + SIG( "glGetError" ); + return dllGetError(); +} + +static void APIENTRY logGetFloatv(GLenum pname, GLfloat *params) +{ + SIG( "glGetFloatv" ); + dllGetFloatv( pname, params ); +} + +static void APIENTRY logGetIntegerv(GLenum pname, GLint *params) +{ + SIG( "glGetIntegerv" ); + dllGetIntegerv( pname, params ); +} + +static void APIENTRY logGetLightfv(GLenum light, GLenum pname, GLfloat *params) +{ + SIG( "glGetLightfv" ); + dllGetLightfv( light, pname, params ); +} + +static void APIENTRY logGetLightiv(GLenum light, GLenum pname, GLint *params) +{ + SIG( "glGetLightiv" ); + dllGetLightiv( light, pname, params ); +} + +static void APIENTRY logGetMapdv(GLenum target, GLenum query, GLdouble *v) +{ + SIG( "glGetMapdv" ); + dllGetMapdv( target, query, v ); +} + +static void APIENTRY logGetMapfv(GLenum target, GLenum query, GLfloat *v) +{ + SIG( "glGetMapfv" ); + dllGetMapfv( target, query, v ); +} + +static void APIENTRY logGetMapiv(GLenum target, GLenum query, GLint *v) +{ + SIG( "glGetMapiv" ); + dllGetMapiv( target, query, v ); +} + +static void APIENTRY logGetMaterialfv(GLenum face, GLenum pname, GLfloat *params) +{ + SIG( "glGetMaterialfv" ); + dllGetMaterialfv( face, pname, params ); +} + +static void APIENTRY logGetMaterialiv(GLenum face, GLenum pname, GLint *params) +{ + SIG( "glGetMaterialiv" ); + dllGetMaterialiv( face, pname, params ); +} + +static void APIENTRY logGetPixelMapfv(GLenum map, GLfloat *values) +{ + SIG( "glGetPixelMapfv" ); + dllGetPixelMapfv( map, values ); +} + +static void APIENTRY logGetPixelMapuiv(GLenum map, GLuint *values) +{ + SIG( "glGetPixelMapuiv" ); + dllGetPixelMapuiv( map, values ); +} + +static void APIENTRY logGetPixelMapusv(GLenum map, GLushort *values) +{ + SIG( "glGetPixelMapusv" ); + dllGetPixelMapusv( map, values ); +} + +static void APIENTRY logGetPointerv(GLenum pname, GLvoid* *params) +{ + SIG( "glGetPointerv" ); + dllGetPointerv( pname, params ); +} + +static void APIENTRY logGetPolygonStipple(GLubyte *mask) +{ + SIG( "glGetPolygonStipple" ); + dllGetPolygonStipple( mask ); +} + +static const GLubyte * APIENTRY logGetString(GLenum name) +{ + SIG( "glGetString" ); + return dllGetString( name ); +} + +static void APIENTRY logGetTexEnvfv(GLenum target, GLenum pname, GLfloat *params) +{ + SIG( "glGetTexEnvfv" ); + dllGetTexEnvfv( target, pname, params ); +} + +static void APIENTRY logGetTexEnviv(GLenum target, GLenum pname, GLint *params) +{ + SIG( "glGetTexEnviv" ); + dllGetTexEnviv( target, pname, params ); +} + +static void APIENTRY logGetTexGendv(GLenum coord, GLenum pname, GLdouble *params) +{ + SIG( "glGetTexGendv" ); + dllGetTexGendv( coord, pname, params ); +} + +static void APIENTRY logGetTexGenfv(GLenum coord, GLenum pname, GLfloat *params) +{ + SIG( "glGetTexGenfv" ); + dllGetTexGenfv( coord, pname, params ); +} + +static void APIENTRY logGetTexGeniv(GLenum coord, GLenum pname, GLint *params) +{ + SIG( "glGetTexGeniv" ); + dllGetTexGeniv( coord, pname, params ); +} + +static void APIENTRY logGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void *pixels) +{ + SIG( "glGetTexImage" ); + dllGetTexImage( target, level, format, type, pixels ); +} +static void APIENTRY logGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params ) +{ + SIG( "glGetTexLevelParameterfv" ); + dllGetTexLevelParameterfv( target, level, pname, params ); +} + +static void APIENTRY logGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params) +{ + SIG( "glGetTexLevelParameteriv" ); + dllGetTexLevelParameteriv( target, level, pname, params ); +} + +static void APIENTRY logGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params) +{ + SIG( "glGetTexParameterfv" ); + dllGetTexParameterfv( target, pname, params ); +} + +static void APIENTRY logGetTexParameteriv(GLenum target, GLenum pname, GLint *params) +{ + SIG( "glGetTexParameteriv" ); + dllGetTexParameteriv( target, pname, params ); +} + +static void APIENTRY logHint(GLenum target, GLenum mode) +{ + fprintf( glw_state.log_fp, "glHint( 0x%x, 0x%x )\n", target, mode ); + dllHint( target, mode ); +} + +static void APIENTRY logIndexMask(GLuint mask) +{ + SIG( "glIndexMask" ); + dllIndexMask( mask ); +} + +static void APIENTRY logIndexPointer(GLenum type, GLsizei stride, const void *pointer) +{ + SIG( "glIndexPointer" ); + dllIndexPointer( type, stride, pointer ); +} + +static void APIENTRY logIndexd(GLdouble c) +{ + SIG( "glIndexd" ); + dllIndexd( c ); +} + +static void APIENTRY logIndexdv(const GLdouble *c) +{ + SIG( "glIndexdv" ); + dllIndexdv( c ); +} + +static void APIENTRY logIndexf(GLfloat c) +{ + SIG( "glIndexf" ); + dllIndexf( c ); +} + +static void APIENTRY logIndexfv(const GLfloat *c) +{ + SIG( "glIndexfv" ); + dllIndexfv( c ); +} + +static void APIENTRY logIndexi(GLint c) +{ + SIG( "glIndexi" ); + dllIndexi( c ); +} + +static void APIENTRY logIndexiv(const GLint *c) +{ + SIG( "glIndexiv" ); + dllIndexiv( c ); +} + +static void APIENTRY logIndexs(GLshort c) +{ + SIG( "glIndexs" ); + dllIndexs( c ); +} + +static void APIENTRY logIndexsv(const GLshort *c) +{ + SIG( "glIndexsv" ); + dllIndexsv( c ); +} + +static void APIENTRY logIndexub(GLubyte c) +{ + SIG( "glIndexub" ); + dllIndexub( c ); +} + +static void APIENTRY logIndexubv(const GLubyte *c) +{ + SIG( "glIndexubv" ); + dllIndexubv( c ); +} + +static void APIENTRY logInitNames(void) +{ + SIG( "glInitNames" ); + dllInitNames(); +} + +static void APIENTRY logInterleavedArrays(GLenum format, GLsizei stride, const void *pointer) +{ + SIG( "glInterleavedArrays" ); + dllInterleavedArrays( format, stride, pointer ); +} + +static GLboolean APIENTRY logIsEnabled(GLenum cap) +{ + SIG( "glIsEnabled" ); + return dllIsEnabled( cap ); +} +static GLboolean APIENTRY logIsList(GLuint list) +{ + SIG( "glIsList" ); + return dllIsList( list ); +} +static GLboolean APIENTRY logIsTexture(GLuint texture) +{ + SIG( "glIsTexture" ); + return dllIsTexture( texture ); +} + +static void APIENTRY logLightModelf(GLenum pname, GLfloat param) +{ + SIG( "glLightModelf" ); + dllLightModelf( pname, param ); +} + +static void APIENTRY logLightModelfv(GLenum pname, const GLfloat *params) +{ + SIG( "glLightModelfv" ); + dllLightModelfv( pname, params ); +} + +static void APIENTRY logLightModeli(GLenum pname, GLint param) +{ + SIG( "glLightModeli" ); + dllLightModeli( pname, param ); + +} + +static void APIENTRY logLightModeliv(GLenum pname, const GLint *params) +{ + SIG( "glLightModeliv" ); + dllLightModeliv( pname, params ); +} + +static void APIENTRY logLightf(GLenum light, GLenum pname, GLfloat param) +{ + SIG( "glLightf" ); + dllLightf( light, pname, param ); +} + +static void APIENTRY logLightfv(GLenum light, GLenum pname, const GLfloat *params) +{ + SIG( "glLightfv" ); + dllLightfv( light, pname, params ); +} + +static void APIENTRY logLighti(GLenum light, GLenum pname, GLint param) +{ + SIG( "glLighti" ); + dllLighti( light, pname, param ); +} + +static void APIENTRY logLightiv(GLenum light, GLenum pname, const GLint *params) +{ + SIG( "glLightiv" ); + dllLightiv( light, pname, params ); +} + +static void APIENTRY logLineStipple(GLint factor, GLushort pattern) +{ + SIG( "glLineStipple" ); + dllLineStipple( factor, pattern ); +} + +static void APIENTRY logLineWidth(GLfloat width) +{ + SIG( "glLineWidth" ); + dllLineWidth( width ); +} + +static void APIENTRY logListBase(GLuint base) +{ + SIG( "glListBase" ); + dllListBase( base ); +} + +static void APIENTRY logLoadIdentity(void) +{ + SIG( "glLoadIdentity" ); + dllLoadIdentity(); +} + +static void APIENTRY logLoadMatrixd(const GLdouble *m) +{ + SIG( "glLoadMatrixd" ); + dllLoadMatrixd( m ); +} + +static void APIENTRY logLoadMatrixf(const GLfloat *m) +{ + SIG( "glLoadMatrixf" ); + dllLoadMatrixf( m ); +} + +static void APIENTRY logLoadName(GLuint name) +{ + SIG( "glLoadName" ); + dllLoadName( name ); +} + +static void APIENTRY logLogicOp(GLenum opcode) +{ + SIG( "glLogicOp" ); + dllLogicOp( opcode ); +} + +static void APIENTRY logMap1d(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points) +{ + SIG( "glMap1d" ); + dllMap1d( target, u1, u2, stride, order, points ); +} + +static void APIENTRY logMap1f(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points) +{ + SIG( "glMap1f" ); + dllMap1f( target, u1, u2, stride, order, points ); +} + +static void APIENTRY logMap2d(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points) +{ + SIG( "glMap2d" ); + dllMap2d( target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points ); +} + +static void APIENTRY logMap2f(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points) +{ + SIG( "glMap2f" ); + dllMap2f( target, u1, u2, ustride, uorder, v1, v2, vstride, vorder, points ); +} + +static void APIENTRY logMapGrid1d(GLint un, GLdouble u1, GLdouble u2) +{ + SIG( "glMapGrid1d" ); + dllMapGrid1d( un, u1, u2 ); +} + +static void APIENTRY logMapGrid1f(GLint un, GLfloat u1, GLfloat u2) +{ + SIG( "glMapGrid1f" ); + dllMapGrid1f( un, u1, u2 ); +} + +static void APIENTRY logMapGrid2d(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2) +{ + SIG( "glMapGrid2d" ); + dllMapGrid2d( un, u1, u2, vn, v1, v2 ); +} +static void APIENTRY logMapGrid2f(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2) +{ + SIG( "glMapGrid2f" ); + dllMapGrid2f( un, u1, u2, vn, v1, v2 ); +} +static void APIENTRY logMaterialf(GLenum face, GLenum pname, GLfloat param) +{ + SIG( "glMaterialf" ); + dllMaterialf( face, pname, param ); +} +static void APIENTRY logMaterialfv(GLenum face, GLenum pname, const GLfloat *params) +{ + SIG( "glMaterialfv" ); + dllMaterialfv( face, pname, params ); +} + +static void APIENTRY logMateriali(GLenum face, GLenum pname, GLint param) +{ + SIG( "glMateriali" ); + dllMateriali( face, pname, param ); +} + +static void APIENTRY logMaterialiv(GLenum face, GLenum pname, const GLint *params) +{ + SIG( "glMaterialiv" ); + dllMaterialiv( face, pname, params ); +} + +static void APIENTRY logMatrixMode(GLenum mode) +{ + SIG( "glMatrixMode" ); + dllMatrixMode( mode ); +} + +static void APIENTRY logMultMatrixd(const GLdouble *m) +{ + SIG( "glMultMatrixd" ); + dllMultMatrixd( m ); +} + +static void APIENTRY logMultMatrixf(const GLfloat *m) +{ + SIG( "glMultMatrixf" ); + dllMultMatrixf( m ); +} + +static void APIENTRY logNewList(GLuint list, GLenum mode) +{ + SIG( "glNewList" ); + dllNewList( list, mode ); +} + +static void APIENTRY logNormal3b(GLbyte nx, GLbyte ny, GLbyte nz) +{ + SIG ("glNormal3b" ); + dllNormal3b( nx, ny, nz ); +} + +static void APIENTRY logNormal3bv(const GLbyte *v) +{ + SIG( "glNormal3bv" ); + dllNormal3bv( v ); +} + +static void APIENTRY logNormal3d(GLdouble nx, GLdouble ny, GLdouble nz) +{ + SIG( "glNormal3d" ); + dllNormal3d( nx, ny, nz ); +} + +static void APIENTRY logNormal3dv(const GLdouble *v) +{ + SIG( "glNormal3dv" ); + dllNormal3dv( v ); +} + +static void APIENTRY logNormal3f(GLfloat nx, GLfloat ny, GLfloat nz) +{ + SIG( "glNormal3f" ); + dllNormal3f( nx, ny, nz ); +} + +static void APIENTRY logNormal3fv(const GLfloat *v) +{ + SIG( "glNormal3fv" ); + dllNormal3fv( v ); +} +static void APIENTRY logNormal3i(GLint nx, GLint ny, GLint nz) +{ + SIG( "glNormal3i" ); + dllNormal3i( nx, ny, nz ); +} +static void APIENTRY logNormal3iv(const GLint *v) +{ + SIG( "glNormal3iv" ); + dllNormal3iv( v ); +} +static void APIENTRY logNormal3s(GLshort nx, GLshort ny, GLshort nz) +{ + SIG( "glNormal3s" ); + dllNormal3s( nx, ny, nz ); +} +static void APIENTRY logNormal3sv(const GLshort *v) +{ + SIG( "glNormal3sv" ); + dllNormal3sv( v ); +} +static void APIENTRY logNormalPointer(GLenum type, GLsizei stride, const void *pointer) +{ + SIG( "glNormalPointer" ); + dllNormalPointer( type, stride, pointer ); +} +static void APIENTRY logOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + SIG( "glOrtho" ); + dllOrtho( left, right, bottom, top, zNear, zFar ); +} + +static void APIENTRY logPassThrough(GLfloat token) +{ + SIG( "glPassThrough" ); + dllPassThrough( token ); +} + +static void APIENTRY logPixelMapfv(GLenum map, GLsizei mapsize, const GLfloat *values) +{ + SIG( "glPixelMapfv" ); + dllPixelMapfv( map, mapsize, values ); +} + +static void APIENTRY logPixelMapuiv(GLenum map, GLsizei mapsize, const GLuint *values) +{ + SIG( "glPixelMapuiv" ); + dllPixelMapuiv( map, mapsize, values ); +} + +static void APIENTRY logPixelMapusv(GLenum map, GLsizei mapsize, const GLushort *values) +{ + SIG( "glPixelMapusv" ); + dllPixelMapusv( map, mapsize, values ); +} +static void APIENTRY logPixelStoref(GLenum pname, GLfloat param) +{ + SIG( "glPixelStoref" ); + dllPixelStoref( pname, param ); +} +static void APIENTRY logPixelStorei(GLenum pname, GLint param) +{ + SIG( "glPixelStorei" ); + dllPixelStorei( pname, param ); +} +static void APIENTRY logPixelTransferf(GLenum pname, GLfloat param) +{ + SIG( "glPixelTransferf" ); + dllPixelTransferf( pname, param ); +} + +static void APIENTRY logPixelTransferi(GLenum pname, GLint param) +{ + SIG( "glPixelTransferi" ); + dllPixelTransferi( pname, param ); +} + +static void APIENTRY logPixelZoom(GLfloat xfactor, GLfloat yfactor) +{ + SIG( "glPixelZoom" ); + dllPixelZoom( xfactor, yfactor ); +} + +static void APIENTRY logPointSize(GLfloat size) +{ + SIG( "glPointSize" ); + dllPointSize( size ); +} + +static void APIENTRY logPolygonMode(GLenum face, GLenum mode) +{ + fprintf( glw_state.log_fp, "glPolygonMode( 0x%x, 0x%x )\n", face, mode ); + dllPolygonMode( face, mode ); +} + +static void APIENTRY logPolygonOffset(GLfloat factor, GLfloat units) +{ + SIG( "glPolygonOffset" ); + dllPolygonOffset( factor, units ); +} +static void APIENTRY logPolygonStipple(const GLubyte *mask ) +{ + SIG( "glPolygonStipple" ); + dllPolygonStipple( mask ); +} +static void APIENTRY logPopAttrib(void) +{ + SIG( "glPopAttrib" ); + dllPopAttrib(); +} + +static void APIENTRY logPopClientAttrib(void) +{ + SIG( "glPopClientAttrib" ); + dllPopClientAttrib(); +} + +static void APIENTRY logPopMatrix(void) +{ + SIG( "glPopMatrix" ); + dllPopMatrix(); +} + +static void APIENTRY logPopName(void) +{ + SIG( "glPopName" ); + dllPopName(); +} + +static void APIENTRY logPrioritizeTextures(GLsizei n, const GLuint *textures, const GLclampf *priorities) +{ + SIG( "glPrioritizeTextures" ); + dllPrioritizeTextures( n, textures, priorities ); +} + +static void APIENTRY logPushAttrib(GLbitfield mask) +{ + SIG( "glPushAttrib" ); + dllPushAttrib( mask ); +} + +static void APIENTRY logPushClientAttrib(GLbitfield mask) +{ + SIG( "glPushClientAttrib" ); + dllPushClientAttrib( mask ); +} + +static void APIENTRY logPushMatrix(void) +{ + SIG( "glPushMatrix" ); + dllPushMatrix(); +} + +static void APIENTRY logPushName(GLuint name) +{ + SIG( "glPushName" ); + dllPushName( name ); +} + +static void APIENTRY logRasterPos2d(GLdouble x, GLdouble y) +{ + SIG ("glRasterPot2d" ); + dllRasterPos2d( x, y ); +} + +static void APIENTRY logRasterPos2dv(const GLdouble *v) +{ + SIG( "glRasterPos2dv" ); + dllRasterPos2dv( v ); +} + +static void APIENTRY logRasterPos2f(GLfloat x, GLfloat y) +{ + SIG( "glRasterPos2f" ); + dllRasterPos2f( x, y ); +} +static void APIENTRY logRasterPos2fv(const GLfloat *v) +{ + SIG( "glRasterPos2dv" ); + dllRasterPos2fv( v ); +} +static void APIENTRY logRasterPos2i(GLint x, GLint y) +{ + SIG( "glRasterPos2if" ); + dllRasterPos2i( x, y ); +} +static void APIENTRY logRasterPos2iv(const GLint *v) +{ + SIG( "glRasterPos2iv" ); + dllRasterPos2iv( v ); +} +static void APIENTRY logRasterPos2s(GLshort x, GLshort y) +{ + SIG( "glRasterPos2s" ); + dllRasterPos2s( x, y ); +} +static void APIENTRY logRasterPos2sv(const GLshort *v) +{ + SIG( "glRasterPos2sv" ); + dllRasterPos2sv( v ); +} +static void APIENTRY logRasterPos3d(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glRasterPos3d" ); + dllRasterPos3d( x, y, z ); +} +static void APIENTRY logRasterPos3dv(const GLdouble *v) +{ + SIG( "glRasterPos3dv" ); + dllRasterPos3dv( v ); +} +static void APIENTRY logRasterPos3f(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glRasterPos3f" ); + dllRasterPos3f( x, y, z ); +} +static void APIENTRY logRasterPos3fv(const GLfloat *v) +{ + SIG( "glRasterPos3fv" ); + dllRasterPos3fv( v ); +} +static void APIENTRY logRasterPos3i(GLint x, GLint y, GLint z) +{ + SIG( "glRasterPos3i" ); + dllRasterPos3i( x, y, z ); +} +static void APIENTRY logRasterPos3iv(const GLint *v) +{ + SIG( "glRasterPos3iv" ); + dllRasterPos3iv( v ); +} +static void APIENTRY logRasterPos3s(GLshort x, GLshort y, GLshort z) +{ + SIG( "glRasterPos3s" ); + dllRasterPos3s( x, y, z ); +} +static void APIENTRY logRasterPos3sv(const GLshort *v) +{ + SIG( "glRasterPos3sv" ); + dllRasterPos3sv( v ); +} +static void APIENTRY logRasterPos4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + SIG( "glRasterPos4d" ); + dllRasterPos4d( x, y, z, w ); +} +static void APIENTRY logRasterPos4dv(const GLdouble *v) +{ + SIG( "glRasterPos4dv" ); + dllRasterPos4dv( v ); +} +static void APIENTRY logRasterPos4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + SIG( "glRasterPos4f" ); + dllRasterPos4f( x, y, z, w ); +} +static void APIENTRY logRasterPos4fv(const GLfloat *v) +{ + SIG( "glRasterPos4fv" ); + dllRasterPos4fv( v ); +} +static void APIENTRY logRasterPos4i(GLint x, GLint y, GLint z, GLint w) +{ + SIG( "glRasterPos4i" ); + dllRasterPos4i( x, y, z, w ); +} +static void APIENTRY logRasterPos4iv(const GLint *v) +{ + SIG( "glRasterPos4iv" ); + dllRasterPos4iv( v ); +} +static void APIENTRY logRasterPos4s(GLshort x, GLshort y, GLshort z, GLshort w) +{ + SIG( "glRasterPos4s" ); + dllRasterPos4s( x, y, z, w ); +} +static void APIENTRY logRasterPos4sv(const GLshort *v) +{ + SIG( "glRasterPos4sv" ); + dllRasterPos4sv( v ); +} +static void APIENTRY logReadBuffer(GLenum mode) +{ + SIG( "glReadBuffer" ); + dllReadBuffer( mode ); +} +static void APIENTRY logReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels) +{ + SIG( "glReadPixels" ); + dllReadPixels( x, y, width, height, format, type, pixels ); +} + +static void APIENTRY logRectd(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2) +{ + SIG( "glRectd" ); + dllRectd( x1, y1, x2, y2 ); +} + +static void APIENTRY logRectdv(const GLdouble *v1, const GLdouble *v2) +{ + SIG( "glRectdv" ); + dllRectdv( v1, v2 ); +} + +static void APIENTRY logRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) +{ + SIG( "glRectf" ); + dllRectf( x1, y1, x2, y2 ); +} + +static void APIENTRY logRectfv(const GLfloat *v1, const GLfloat *v2) +{ + SIG( "glRectfv" ); + dllRectfv( v1, v2 ); +} +static void APIENTRY logRecti(GLint x1, GLint y1, GLint x2, GLint y2) +{ + SIG( "glRecti" ); + dllRecti( x1, y1, x2, y2 ); +} +static void APIENTRY logRectiv(const GLint *v1, const GLint *v2) +{ + SIG( "glRectiv" ); + dllRectiv( v1, v2 ); +} +static void APIENTRY logRects(GLshort x1, GLshort y1, GLshort x2, GLshort y2) +{ + SIG( "glRects" ); + dllRects( x1, y1, x2, y2 ); +} +static void APIENTRY logRectsv(const GLshort *v1, const GLshort *v2) +{ + SIG( "glRectsv" ); + dllRectsv( v1, v2 ); +} +static GLint APIENTRY logRenderMode(GLenum mode) +{ + SIG( "glRenderMode" ); + return dllRenderMode( mode ); +} +static void APIENTRY logRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glRotated" ); + dllRotated( angle, x, y, z ); +} + +static void APIENTRY logRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glRotatef" ); + dllRotatef( angle, x, y, z ); +} + +static void APIENTRY logScaled(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glScaled" ); + dllScaled( x, y, z ); +} + +static void APIENTRY logScalef(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glScalef" ); + dllScalef( x, y, z ); +} + +static void APIENTRY logScissor(GLint x, GLint y, GLsizei width, GLsizei height) +{ + fprintf( glw_state.log_fp, "glScissor( %d, %d, %d, %d )\n", x, y, width, height ); + dllScissor( x, y, width, height ); +} + +static void APIENTRY logSelectBuffer(GLsizei size, GLuint *buffer) +{ + SIG( "glSelectBuffer" ); + dllSelectBuffer( size, buffer ); +} + +static void APIENTRY logShadeModel(GLenum mode) +{ + SIG( "glShadeModel" ); + dllShadeModel( mode ); +} + +static void APIENTRY logStencilFunc(GLenum func, GLint ref, GLuint mask) +{ + SIG( "glStencilFunc" ); + dllStencilFunc( func, ref, mask ); +} + +static void APIENTRY logStencilMask(GLuint mask) +{ + SIG( "glStencilMask" ); + dllStencilMask( mask ); +} + +static void APIENTRY logStencilOp(GLenum fail, GLenum zfail, GLenum zpass) +{ + SIG( "glStencilOp" ); + dllStencilOp( fail, zfail, zpass ); +} + +static void APIENTRY logTexCoord1d(GLdouble s) +{ + SIG( "glTexCoord1d" ); + dllTexCoord1d( s ); +} + +static void APIENTRY logTexCoord1dv(const GLdouble *v) +{ + SIG( "glTexCoord1dv" ); + dllTexCoord1dv( v ); +} + +static void APIENTRY logTexCoord1f(GLfloat s) +{ + SIG( "glTexCoord1f" ); + dllTexCoord1f( s ); +} +static void APIENTRY logTexCoord1fv(const GLfloat *v) +{ + SIG( "glTexCoord1fv" ); + dllTexCoord1fv( v ); +} +static void APIENTRY logTexCoord1i(GLint s) +{ + SIG( "glTexCoord1i" ); + dllTexCoord1i( s ); +} +static void APIENTRY logTexCoord1iv(const GLint *v) +{ + SIG( "glTexCoord1iv" ); + dllTexCoord1iv( v ); +} +static void APIENTRY logTexCoord1s(GLshort s) +{ + SIG( "glTexCoord1s" ); + dllTexCoord1s( s ); +} +static void APIENTRY logTexCoord1sv(const GLshort *v) +{ + SIG( "glTexCoord1sv" ); + dllTexCoord1sv( v ); +} +static void APIENTRY logTexCoord2d(GLdouble s, GLdouble t) +{ + SIG( "glTexCoord2d" ); + dllTexCoord2d( s, t ); +} + +static void APIENTRY logTexCoord2dv(const GLdouble *v) +{ + SIG( "glTexCoord2dv" ); + dllTexCoord2dv( v ); +} +static void APIENTRY logTexCoord2f(GLfloat s, GLfloat t) +{ + SIG( "glTexCoord2f" ); + dllTexCoord2f( s, t ); +} +static void APIENTRY logTexCoord2fv(const GLfloat *v) +{ + SIG( "glTexCoord2fv" ); + dllTexCoord2fv( v ); +} +static void APIENTRY logTexCoord2i(GLint s, GLint t) +{ + SIG( "glTexCoord2i" ); + dllTexCoord2i( s, t ); +} +static void APIENTRY logTexCoord2iv(const GLint *v) +{ + SIG( "glTexCoord2iv" ); + dllTexCoord2iv( v ); +} +static void APIENTRY logTexCoord2s(GLshort s, GLshort t) +{ + SIG( "glTexCoord2s" ); + dllTexCoord2s( s, t ); +} +static void APIENTRY logTexCoord2sv(const GLshort *v) +{ + SIG( "glTexCoord2sv" ); + dllTexCoord2sv( v ); +} +static void APIENTRY logTexCoord3d(GLdouble s, GLdouble t, GLdouble r) +{ + SIG( "glTexCoord3d" ); + dllTexCoord3d( s, t, r ); +} +static void APIENTRY logTexCoord3dv(const GLdouble *v) +{ + SIG( "glTexCoord3dv" ); + dllTexCoord3dv( v ); +} +static void APIENTRY logTexCoord3f(GLfloat s, GLfloat t, GLfloat r) +{ + SIG( "glTexCoord3f" ); + dllTexCoord3f( s, t, r ); +} +static void APIENTRY logTexCoord3fv(const GLfloat *v) +{ + SIG( "glTexCoord3fv" ); + dllTexCoord3fv( v ); +} +static void APIENTRY logTexCoord3i(GLint s, GLint t, GLint r) +{ + SIG( "glTexCoord3i" ); + dllTexCoord3i( s, t, r ); +} +static void APIENTRY logTexCoord3iv(const GLint *v) +{ + SIG( "glTexCoord3iv" ); + dllTexCoord3iv( v ); +} +static void APIENTRY logTexCoord3s(GLshort s, GLshort t, GLshort r) +{ + SIG( "glTexCoord3s" ); + dllTexCoord3s( s, t, r ); +} +static void APIENTRY logTexCoord3sv(const GLshort *v) +{ + SIG( "glTexCoord3sv" ); + dllTexCoord3sv( v ); +} +static void APIENTRY logTexCoord4d(GLdouble s, GLdouble t, GLdouble r, GLdouble q) +{ + SIG( "glTexCoord4d" ); + dllTexCoord4d( s, t, r, q ); +} +static void APIENTRY logTexCoord4dv(const GLdouble *v) +{ + SIG( "glTexCoord4dv" ); + dllTexCoord4dv( v ); +} +static void APIENTRY logTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat q) +{ + SIG( "glTexCoord4f" ); + dllTexCoord4f( s, t, r, q ); +} +static void APIENTRY logTexCoord4fv(const GLfloat *v) +{ + SIG( "glTexCoord4fv" ); + dllTexCoord4fv( v ); +} +static void APIENTRY logTexCoord4i(GLint s, GLint t, GLint r, GLint q) +{ + SIG( "glTexCoord4i" ); + dllTexCoord4i( s, t, r, q ); +} +static void APIENTRY logTexCoord4iv(const GLint *v) +{ + SIG( "glTexCoord4iv" ); + dllTexCoord4iv( v ); +} +static void APIENTRY logTexCoord4s(GLshort s, GLshort t, GLshort r, GLshort q) +{ + SIG( "glTexCoord4s" ); + dllTexCoord4s( s, t, r, q ); +} +static void APIENTRY logTexCoord4sv(const GLshort *v) +{ + SIG( "glTexCoord4sv" ); + dllTexCoord4sv( v ); +} +static void APIENTRY logTexCoordPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) +{ + fprintf( glw_state.log_fp, "glTexCoordPointer( %d, %s, %d, MEM )\n", size, TypeToString( type ), stride ); + dllTexCoordPointer( size, type, stride, pointer ); +} + +static void APIENTRY logTexEnvf(GLenum target, GLenum pname, GLfloat param) +{ + fprintf( glw_state.log_fp, "glTexEnvf( 0x%x, 0x%x, %f )\n", target, pname, param ); + dllTexEnvf( target, pname, param ); +} + +static void APIENTRY logTexEnvfv(GLenum target, GLenum pname, const GLfloat *params) +{ + SIG( "glTexEnvfv" ); + dllTexEnvfv( target, pname, params ); +} + +static void APIENTRY logTexEnvi(GLenum target, GLenum pname, GLint param) +{ + fprintf( glw_state.log_fp, "glTexEnvi( 0x%x, 0x%x, 0x%x )\n", target, pname, param ); + dllTexEnvi( target, pname, param ); +} +static void APIENTRY logTexEnviv(GLenum target, GLenum pname, const GLint *params) +{ + SIG( "glTexEnviv" ); + dllTexEnviv( target, pname, params ); +} + +static void APIENTRY logTexGend(GLenum coord, GLenum pname, GLdouble param) +{ + SIG( "glTexGend" ); + dllTexGend( coord, pname, param ); +} + +static void APIENTRY logTexGendv(GLenum coord, GLenum pname, const GLdouble *params) +{ + SIG( "glTexGendv" ); + dllTexGendv( coord, pname, params ); +} + +static void APIENTRY logTexGenf(GLenum coord, GLenum pname, GLfloat param) +{ + SIG( "glTexGenf" ); + dllTexGenf( coord, pname, param ); +} +static void APIENTRY logTexGenfv(GLenum coord, GLenum pname, const GLfloat *params) +{ + SIG( "glTexGenfv" ); + dllTexGenfv( coord, pname, params ); +} +static void APIENTRY logTexGeni(GLenum coord, GLenum pname, GLint param) +{ + SIG( "glTexGeni" ); + dllTexGeni( coord, pname, param ); +} +static void APIENTRY logTexGeniv(GLenum coord, GLenum pname, const GLint *params) +{ + SIG( "glTexGeniv" ); + dllTexGeniv( coord, pname, params ); +} +static void APIENTRY logTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexImage1D" ); + dllTexImage1D( target, level, internalformat, width, border, format, type, pixels ); +} +static void APIENTRY logTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexImage2D" ); + dllTexImage2D( target, level, internalformat, width, height, border, format, type, pixels ); +} + +static void APIENTRY logTexParameterf(GLenum target, GLenum pname, GLfloat param) +{ + fprintf( glw_state.log_fp, "glTexParameterf( 0x%x, 0x%x, %f )\n", target, pname, param ); + dllTexParameterf( target, pname, param ); +} + +static void APIENTRY logTexParameterfv(GLenum target, GLenum pname, const GLfloat *params) +{ + SIG( "glTexParameterfv" ); + dllTexParameterfv( target, pname, params ); +} +static void APIENTRY logTexParameteri(GLenum target, GLenum pname, GLint param) +{ + fprintf( glw_state.log_fp, "glTexParameteri( 0x%x, 0x%x, 0x%x )\n", target, pname, param ); + dllTexParameteri( target, pname, param ); +} +static void APIENTRY logTexParameteriv(GLenum target, GLenum pname, const GLint *params) +{ + SIG( "glTexParameteriv" ); + dllTexParameteriv( target, pname, params ); +} +static void APIENTRY logTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexSubImage1D" ); + dllTexSubImage1D( target, level, xoffset, width, format, type, pixels ); +} +static void APIENTRY logTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) +{ + SIG( "glTexSubImage2D" ); + dllTexSubImage2D( target, level, xoffset, yoffset, width, height, format, type, pixels ); +} +static void APIENTRY logTranslated(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glTranslated" ); + dllTranslated( x, y, z ); +} + +static void APIENTRY logTranslatef(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glTranslatef" ); + dllTranslatef( x, y, z ); +} + +static void APIENTRY logVertex2d(GLdouble x, GLdouble y) +{ + SIG( "glVertex2d" ); + dllVertex2d( x, y ); +} + +static void APIENTRY logVertex2dv(const GLdouble *v) +{ + SIG( "glVertex2dv" ); + dllVertex2dv( v ); +} +static void APIENTRY logVertex2f(GLfloat x, GLfloat y) +{ + SIG( "glVertex2f" ); + dllVertex2f( x, y ); +} +static void APIENTRY logVertex2fv(const GLfloat *v) +{ + SIG( "glVertex2fv" ); + dllVertex2fv( v ); +} +static void APIENTRY logVertex2i(GLint x, GLint y) +{ + SIG( "glVertex2i" ); + dllVertex2i( x, y ); +} +static void APIENTRY logVertex2iv(const GLint *v) +{ + SIG( "glVertex2iv" ); + dllVertex2iv( v ); +} +static void APIENTRY logVertex2s(GLshort x, GLshort y) +{ + SIG( "glVertex2s" ); + dllVertex2s( x, y ); +} +static void APIENTRY logVertex2sv(const GLshort *v) +{ + SIG( "glVertex2sv" ); + dllVertex2sv( v ); +} +static void APIENTRY logVertex3d(GLdouble x, GLdouble y, GLdouble z) +{ + SIG( "glVertex3d" ); + dllVertex3d( x, y, z ); +} +static void APIENTRY logVertex3dv(const GLdouble *v) +{ + SIG( "glVertex3dv" ); + dllVertex3dv( v ); +} +static void APIENTRY logVertex3f(GLfloat x, GLfloat y, GLfloat z) +{ + SIG( "glVertex3f" ); + dllVertex3f( x, y, z ); +} +static void APIENTRY logVertex3fv(const GLfloat *v) +{ + SIG( "glVertex3fv" ); + dllVertex3fv( v ); +} +static void APIENTRY logVertex3i(GLint x, GLint y, GLint z) +{ + SIG( "glVertex3i" ); + dllVertex3i( x, y, z ); +} +static void APIENTRY logVertex3iv(const GLint *v) +{ + SIG( "glVertex3iv" ); + dllVertex3iv( v ); +} +static void APIENTRY logVertex3s(GLshort x, GLshort y, GLshort z) +{ + SIG( "glVertex3s" ); + dllVertex3s( x, y, z ); +} +static void APIENTRY logVertex3sv(const GLshort *v) +{ + SIG( "glVertex3sv" ); + dllVertex3sv( v ); +} +static void APIENTRY logVertex4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + SIG( "glVertex4d" ); + dllVertex4d( x, y, z, w ); +} +static void APIENTRY logVertex4dv(const GLdouble *v) +{ + SIG( "glVertex4dv" ); + dllVertex4dv( v ); +} +static void APIENTRY logVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + SIG( "glVertex4f" ); + dllVertex4f( x, y, z, w ); +} +static void APIENTRY logVertex4fv(const GLfloat *v) +{ + SIG( "glVertex4fv" ); + dllVertex4fv( v ); +} +static void APIENTRY logVertex4i(GLint x, GLint y, GLint z, GLint w) +{ + SIG( "glVertex4i" ); + dllVertex4i( x, y, z, w ); +} +static void APIENTRY logVertex4iv(const GLint *v) +{ + SIG( "glVertex4iv" ); + dllVertex4iv( v ); +} +static void APIENTRY logVertex4s(GLshort x, GLshort y, GLshort z, GLshort w) +{ + SIG( "glVertex4s" ); + dllVertex4s( x, y, z, w ); +} +static void APIENTRY logVertex4sv(const GLshort *v) +{ + SIG( "glVertex4sv" ); + dllVertex4sv( v ); +} +static void APIENTRY logVertexPointer(GLint size, GLenum type, GLsizei stride, const void *pointer) +{ + fprintf( glw_state.log_fp, "glVertexPointer( %d, %s, %d, MEM )\n", size, TypeToString( type ), stride ); + dllVertexPointer( size, type, stride, pointer ); +} +static void APIENTRY logViewport(GLint x, GLint y, GLsizei width, GLsizei height) +{ + fprintf( glw_state.log_fp, "glViewport( %d, %d, %d, %d )\n", x, y, width, height ); + dllViewport( x, y, width, height ); +} + +/* +** QGL_Shutdown +** +** Unloads the specified DLL then nulls out all the proc pointers. This +** is only called during a hard shutdown of the OGL subsystem (e.g. vid_restart). +*/ +void QGL_Shutdown( void ) +{ + VID_Printf( PRINT_ALL, "...shutting down QGL\n" ); + + if ( glw_state.hinstOpenGL ) + { + VID_Printf( PRINT_ALL, "...unloading OpenGL DLL\n" ); + FreeLibrary( glw_state.hinstOpenGL ); + } + + glw_state.hinstOpenGL = NULL; + + qglAccum = NULL; + qglAlphaFunc = NULL; + qglAreTexturesResident = NULL; + qglArrayElement = NULL; + qglBegin = NULL; + qglBindTexture = NULL; + qglBitmap = NULL; + qglBlendFunc = NULL; + qglCallList = NULL; + qglCallLists = NULL; + qglClear = NULL; + qglClearAccum = NULL; + qglClearColor = NULL; + qglClearDepth = NULL; + qglClearIndex = NULL; + qglClearStencil = NULL; + qglClipPlane = NULL; + qglColor3b = NULL; + qglColor3bv = NULL; + qglColor3d = NULL; + qglColor3dv = NULL; + qglColor3f = NULL; + qglColor3fv = NULL; + qglColor3i = NULL; + qglColor3iv = NULL; + qglColor3s = NULL; + qglColor3sv = NULL; + qglColor3ub = NULL; + qglColor3ubv = NULL; + qglColor3ui = NULL; + qglColor3uiv = NULL; + qglColor3us = NULL; + qglColor3usv = NULL; + qglColor4b = NULL; + qglColor4bv = NULL; + qglColor4d = NULL; + qglColor4dv = NULL; + qglColor4f = NULL; + qglColor4fv = NULL; + qglColor4i = NULL; + qglColor4iv = NULL; + qglColor4s = NULL; + qglColor4sv = NULL; + qglColor4ub = NULL; + qglColor4ubv = NULL; + qglColor4ui = NULL; + qglColor4uiv = NULL; + qglColor4us = NULL; + qglColor4usv = NULL; + qglColorMask = NULL; + qglColorMaterial = NULL; + qglColorPointer = NULL; + qglCopyPixels = NULL; + qglCopyTexImage1D = NULL; + qglCopyTexImage2D = NULL; + qglCopyTexSubImage1D = NULL; + qglCopyTexSubImage2D = NULL; + qglCullFace = NULL; + qglDeleteLists = NULL; + qglDeleteTextures = NULL; + qglDepthFunc = NULL; + qglDepthMask = NULL; + qglDepthRange = NULL; + qglDisable = NULL; + qglDisableClientState = NULL; + qglDrawArrays = NULL; + qglDrawBuffer = NULL; + qglDrawElements = NULL; + qglDrawPixels = NULL; + qglEdgeFlag = NULL; + qglEdgeFlagPointer = NULL; + qglEdgeFlagv = NULL; + qglEnable = NULL; + qglEnableClientState = NULL; + qglEnd = NULL; + qglEndList = NULL; + qglEvalCoord1d = NULL; + qglEvalCoord1dv = NULL; + qglEvalCoord1f = NULL; + qglEvalCoord1fv = NULL; + qglEvalCoord2d = NULL; + qglEvalCoord2dv = NULL; + qglEvalCoord2f = NULL; + qglEvalCoord2fv = NULL; + qglEvalMesh1 = NULL; + qglEvalMesh2 = NULL; + qglEvalPoint1 = NULL; + qglEvalPoint2 = NULL; + qglFeedbackBuffer = NULL; + qglFinish = NULL; + qglFlush = NULL; + qglFogf = NULL; + qglFogfv = NULL; + qglFogi = NULL; + qglFogiv = NULL; + qglFrontFace = NULL; + qglFrustum = NULL; + qglGenLists = NULL; + qglGenTextures = NULL; + qglGetBooleanv = NULL; + qglGetClipPlane = NULL; + qglGetDoublev = NULL; + qglGetError = NULL; + qglGetFloatv = NULL; + qglGetIntegerv = NULL; + qglGetLightfv = NULL; + qglGetLightiv = NULL; + qglGetMapdv = NULL; + qglGetMapfv = NULL; + qglGetMapiv = NULL; + qglGetMaterialfv = NULL; + qglGetMaterialiv = NULL; + qglGetPixelMapfv = NULL; + qglGetPixelMapuiv = NULL; + qglGetPixelMapusv = NULL; + qglGetPointerv = NULL; + qglGetPolygonStipple = NULL; + qglGetString = NULL; + qglGetTexEnvfv = NULL; + qglGetTexEnviv = NULL; + qglGetTexGendv = NULL; + qglGetTexGenfv = NULL; + qglGetTexGeniv = NULL; + qglGetTexImage = NULL; + qglGetTexLevelParameterfv = NULL; + qglGetTexLevelParameteriv = NULL; + qglGetTexParameterfv = NULL; + qglGetTexParameteriv = NULL; + qglHint = NULL; + qglIndexMask = NULL; + qglIndexPointer = NULL; + qglIndexd = NULL; + qglIndexdv = NULL; + qglIndexf = NULL; + qglIndexfv = NULL; + qglIndexi = NULL; + qglIndexiv = NULL; + qglIndexs = NULL; + qglIndexsv = NULL; + qglIndexub = NULL; + qglIndexubv = NULL; + qglInitNames = NULL; + qglInterleavedArrays = NULL; + qglIsEnabled = NULL; + qglIsList = NULL; + qglIsTexture = NULL; + qglLightModelf = NULL; + qglLightModelfv = NULL; + qglLightModeli = NULL; + qglLightModeliv = NULL; + qglLightf = NULL; + qglLightfv = NULL; + qglLighti = NULL; + qglLightiv = NULL; + qglLineStipple = NULL; + qglLineWidth = NULL; + qglListBase = NULL; + qglLoadIdentity = NULL; + qglLoadMatrixd = NULL; + qglLoadMatrixf = NULL; + qglLoadName = NULL; + qglLogicOp = NULL; + qglMap1d = NULL; + qglMap1f = NULL; + qglMap2d = NULL; + qglMap2f = NULL; + qglMapGrid1d = NULL; + qglMapGrid1f = NULL; + qglMapGrid2d = NULL; + qglMapGrid2f = NULL; + qglMaterialf = NULL; + qglMaterialfv = NULL; + qglMateriali = NULL; + qglMaterialiv = NULL; + qglMatrixMode = NULL; + qglMultMatrixd = NULL; + qglMultMatrixf = NULL; + qglNewList = NULL; + qglNormal3b = NULL; + qglNormal3bv = NULL; + qglNormal3d = NULL; + qglNormal3dv = NULL; + qglNormal3f = NULL; + qglNormal3fv = NULL; + qglNormal3i = NULL; + qglNormal3iv = NULL; + qglNormal3s = NULL; + qglNormal3sv = NULL; + qglNormalPointer = NULL; + qglOrtho = NULL; + qglPassThrough = NULL; + qglPixelMapfv = NULL; + qglPixelMapuiv = NULL; + qglPixelMapusv = NULL; + qglPixelStoref = NULL; + qglPixelStorei = NULL; + qglPixelTransferf = NULL; + qglPixelTransferi = NULL; + qglPixelZoom = NULL; + qglPointSize = NULL; + qglPolygonMode = NULL; + qglPolygonOffset = NULL; + qglPolygonStipple = NULL; + qglPopAttrib = NULL; + qglPopClientAttrib = NULL; + qglPopMatrix = NULL; + qglPopName = NULL; + qglPrioritizeTextures = NULL; + qglPushAttrib = NULL; + qglPushClientAttrib = NULL; + qglPushMatrix = NULL; + qglPushName = NULL; + qglRasterPos2d = NULL; + qglRasterPos2dv = NULL; + qglRasterPos2f = NULL; + qglRasterPos2fv = NULL; + qglRasterPos2i = NULL; + qglRasterPos2iv = NULL; + qglRasterPos2s = NULL; + qglRasterPos2sv = NULL; + qglRasterPos3d = NULL; + qglRasterPos3dv = NULL; + qglRasterPos3f = NULL; + qglRasterPos3fv = NULL; + qglRasterPos3i = NULL; + qglRasterPos3iv = NULL; + qglRasterPos3s = NULL; + qglRasterPos3sv = NULL; + qglRasterPos4d = NULL; + qglRasterPos4dv = NULL; + qglRasterPos4f = NULL; + qglRasterPos4fv = NULL; + qglRasterPos4i = NULL; + qglRasterPos4iv = NULL; + qglRasterPos4s = NULL; + qglRasterPos4sv = NULL; + qglReadBuffer = NULL; + qglReadPixels = NULL; + qglRectd = NULL; + qglRectdv = NULL; + qglRectf = NULL; + qglRectfv = NULL; + qglRecti = NULL; + qglRectiv = NULL; + qglRects = NULL; + qglRectsv = NULL; + qglRenderMode = NULL; + qglRotated = NULL; + qglRotatef = NULL; + qglScaled = NULL; + qglScalef = NULL; + qglScissor = NULL; + qglSelectBuffer = NULL; + qglShadeModel = NULL; + qglStencilFunc = NULL; + qglStencilMask = NULL; + qglStencilOp = NULL; + qglTexCoord1d = NULL; + qglTexCoord1dv = NULL; + qglTexCoord1f = NULL; + qglTexCoord1fv = NULL; + qglTexCoord1i = NULL; + qglTexCoord1iv = NULL; + qglTexCoord1s = NULL; + qglTexCoord1sv = NULL; + qglTexCoord2d = NULL; + qglTexCoord2dv = NULL; + qglTexCoord2f = NULL; + qglTexCoord2fv = NULL; + qglTexCoord2i = NULL; + qglTexCoord2iv = NULL; + qglTexCoord2s = NULL; + qglTexCoord2sv = NULL; + qglTexCoord3d = NULL; + qglTexCoord3dv = NULL; + qglTexCoord3f = NULL; + qglTexCoord3fv = NULL; + qglTexCoord3i = NULL; + qglTexCoord3iv = NULL; + qglTexCoord3s = NULL; + qglTexCoord3sv = NULL; + qglTexCoord4d = NULL; + qglTexCoord4dv = NULL; + qglTexCoord4f = NULL; + qglTexCoord4fv = NULL; + qglTexCoord4i = NULL; + qglTexCoord4iv = NULL; + qglTexCoord4s = NULL; + qglTexCoord4sv = NULL; + qglTexCoordPointer = NULL; + qglTexEnvf = NULL; + qglTexEnvfv = NULL; + qglTexEnvi = NULL; + qglTexEnviv = NULL; + qglTexGend = NULL; + qglTexGendv = NULL; + qglTexGenf = NULL; + qglTexGenfv = NULL; + qglTexGeni = NULL; + qglTexGeniv = NULL; + qglTexImage1D = NULL; + qglTexImage2D = NULL; + qglTexParameterf = NULL; + qglTexParameterfv = NULL; + qglTexParameteri = NULL; + qglTexParameteriv = NULL; + qglTexSubImage1D = NULL; + qglTexSubImage2D = NULL; + qglTranslated = NULL; + qglTranslatef = NULL; + qglVertex2d = NULL; + qglVertex2dv = NULL; + qglVertex2f = NULL; + qglVertex2fv = NULL; + qglVertex2i = NULL; + qglVertex2iv = NULL; + qglVertex2s = NULL; + qglVertex2sv = NULL; + qglVertex3d = NULL; + qglVertex3dv = NULL; + qglVertex3f = NULL; + qglVertex3fv = NULL; + qglVertex3i = NULL; + qglVertex3iv = NULL; + qglVertex3s = NULL; + qglVertex3sv = NULL; + qglVertex4d = NULL; + qglVertex4dv = NULL; + qglVertex4f = NULL; + qglVertex4fv = NULL; + qglVertex4i = NULL; + qglVertex4iv = NULL; + qglVertex4s = NULL; + qglVertex4sv = NULL; + qglVertexPointer = NULL; + qglViewport = NULL; + + qwglCopyContext = NULL; + qwglCreateContext = NULL; + qwglCreateLayerContext = NULL; + qwglDeleteContext = NULL; + qwglDescribeLayerPlane = NULL; + qwglGetCurrentContext = NULL; + qwglGetCurrentDC = NULL; + qwglGetLayerPaletteEntries = NULL; + qwglGetProcAddress = NULL; + qwglMakeCurrent = NULL; + qwglRealizeLayerPalette = NULL; + qwglSetLayerPaletteEntries = NULL; + qwglShareLists = NULL; + qwglSwapLayerBuffers = NULL; + qwglUseFontBitmaps = NULL; + qwglUseFontOutlines = NULL; +} + +# pragma warning (disable : 4113 4133 4047 ) +# define GPA( a ) GetProcAddress( glw_state.hinstOpenGL, a ) + +/* +** QGL_Init +** +** This is responsible for binding our qgl function pointers to +** the appropriate GL stuff. In Windows this means doing a +** LoadLibrary and a bunch of calls to GetProcAddress. On other +** operating systems we need to do the right thing, whatever that +** might be. +*/ +qboolean QGL_Init( const char *dllname ) +{ + assert( glw_state.hinstOpenGL == 0 ); + + VID_Printf( PRINT_ALL, "...initializing QGL\n" ); + + //VID_Printf( PRINT_ALL, "...calling LoadLibrary( '%s.dll' ): ", dllname ); + + if ( ( glw_state.hinstOpenGL = LoadLibrary( dllname ) ) == 0 ) + { + VID_Printf( PRINT_ALL, "failed\n" ); + return qfalse; + } + VID_Printf( PRINT_ALL, "succeeded\n" ); + + qglAccum = dllAccum = (void (__stdcall *)(unsigned int,float))GPA( "glAccum" ); + qglAlphaFunc = dllAlphaFunc = (void (__stdcall *)(unsigned int,float))GPA( "glAlphaFunc" ); + qglAreTexturesResident = dllAreTexturesResident = (unsigned char (__stdcall *)(int,const unsigned int *,unsigned char *))GPA( "glAreTexturesResident" ); + qglArrayElement = dllArrayElement = (void (__stdcall *)(int))GPA( "glArrayElement" ); + qglBegin = dllBegin = (void (__stdcall *)(unsigned int))GPA( "glBegin" ); + qglBindTexture = dllBindTexture = (void (__stdcall *)(unsigned int,unsigned int))GPA( "glBindTexture" ); + qglBitmap = dllBitmap = (void (__stdcall *)(int,int,float,float,float,float,const unsigned char *))GPA( "glBitmap" ); + qglBlendFunc = dllBlendFunc = (void (__stdcall *)(unsigned int,unsigned int))GPA( "glBlendFunc" ); + qglCallList = dllCallList = (void (__stdcall *)(unsigned int))GPA( "glCallList" ); + qglCallLists = dllCallLists = (void (__stdcall *)(int,unsigned int,const void *))GPA( "glCallLists" ); + qglClear = dllClear = (void (__stdcall *)(unsigned int))GPA( "glClear" ); + qglClearAccum = dllClearAccum = (void (__stdcall *)(float,float,float,float))GPA( "glClearAccum" ); + qglClearColor = dllClearColor = (void (__stdcall *)(float,float,float,float))GPA( "glClearColor" ); + qglClearDepth = dllClearDepth = (void (__stdcall *)(double))GPA( "glClearDepth" ); + qglClearIndex = dllClearIndex = (void (__stdcall *)(float))GPA( "glClearIndex" ); + qglClearStencil = dllClearStencil = (void (__stdcall *)(int))GPA( "glClearStencil" ); + qglClipPlane = dllClipPlane = (void (__stdcall *)(unsigned int,const double *))GPA( "glClipPlane" ); + qglColor3b = dllColor3b = (void (__stdcall *)(signed char,signed char,signed char))GPA( "glColor3b" ); + qglColor3bv = dllColor3bv = (void (__stdcall *)(const signed char *))GPA( "glColor3bv" ); + qglColor3d = dllColor3d = (void (__stdcall *)(double,double,double))GPA( "glColor3d" ); + qglColor3dv = dllColor3dv = (void (__stdcall *)(const double *))GPA( "glColor3dv" ); + qglColor3f = dllColor3f = (void (__stdcall *)(float,float,float))GPA( "glColor3f" ); + qglColor3fv = dllColor3fv = (void (__stdcall *)(const float *))GPA( "glColor3fv" ); + qglColor3i = dllColor3i = (void (__stdcall *)(int,int,int))GPA( "glColor3i" ); + qglColor3iv = dllColor3iv = (void (__stdcall *)(const int *))GPA( "glColor3iv" ); + qglColor3s = dllColor3s =(void (__stdcall *)(short,short,short))GPA( "glColor3s" ); + qglColor3sv = dllColor3sv =(void (__stdcall *)(const short *))GPA( "glColor3sv" ); + qglColor3ub = dllColor3ub =(void (__stdcall *)(unsigned char,unsigned char,unsigned char))GPA( "glColor3ub" ); + qglColor3ubv = dllColor3ubv =(void (__stdcall *)(const unsigned char *))GPA( "glColor3ubv" ); + qglColor3ui = dllColor3ui =(void (__stdcall *)(unsigned int,unsigned int,unsigned int))GPA( "glColor3ui" ); + qglColor3uiv = dllColor3uiv =(void (__stdcall *)(const unsigned int *))GPA( "glColor3uiv" ); + qglColor3us = dllColor3us =(void (__stdcall *)(unsigned short,unsigned short,unsigned short))GPA( "glColor3us" ); + qglColor3usv = dllColor3usv =(void (__stdcall *)(const unsigned short *))GPA( "glColor3usv" ); + qglColor4b = dllColor4b =(void (__stdcall *)(signed char,signed char,signed char,signed char))GPA( "glColor4b" ); + qglColor4bv = dllColor4bv =(void (__stdcall *)(const signed char *))GPA( "glColor4bv" ); + qglColor4d = dllColor4d =(void (__stdcall *)(double,double,double,double))GPA( "glColor4d" ); + qglColor4dv = dllColor4dv =(void (__stdcall *)(const double *))GPA( "glColor4dv" ); + qglColor4f = dllColor4f =(void (__stdcall *)(float,float,float,float))GPA( "glColor4f" ); + qglColor4fv = dllColor4fv =(void (__stdcall *)(const float *))GPA( "glColor4fv" ); + qglColor4i = dllColor4i =(void (__stdcall *)(int,int,int,int))GPA( "glColor4i" ); + qglColor4iv = dllColor4iv =(void (__stdcall *)(const int *))GPA( "glColor4iv" ); + qglColor4s = dllColor4s =(void (__stdcall *)(short,short,short,short))GPA( "glColor4s" ); + qglColor4sv = dllColor4sv =(void (__stdcall *)(const short *))GPA( "glColor4sv" ); + qglColor4ub = dllColor4ub =(void (__stdcall *)(unsigned char,unsigned char,unsigned char,unsigned char))GPA( "glColor4ub" ); + qglColor4ubv = dllColor4ubv =(void (__stdcall *)(const unsigned char *))GPA( "glColor4ubv" ); + qglColor4ui = dllColor4ui =(void (__stdcall *)(unsigned int,unsigned int,unsigned int,unsigned int))GPA( "glColor4ui" ); + qglColor4uiv = dllColor4uiv =(void (__stdcall *)(const unsigned int *))GPA( "glColor4uiv" ); + qglColor4us = dllColor4us =(void (__stdcall *)(unsigned short,unsigned short,unsigned short,unsigned short))GPA( "glColor4us" ); + qglColor4usv = dllColor4usv =(void (__stdcall *)(const unsigned short *))GPA( "glColor4usv" ); + qglColorMask = dllColorMask =(void (__stdcall *)(unsigned char,unsigned char,unsigned char,unsigned char))GPA( "glColorMask" ); + qglColorMaterial = dllColorMaterial =(void (__stdcall *)(unsigned int,unsigned int))GPA( "glColorMaterial" ); + qglColorPointer = dllColorPointer =(void (__stdcall *)(int,unsigned int,int,const void *))GPA( "glColorPointer" ); + qglCopyPixels = dllCopyPixels =(void (__stdcall *)(int,int,int,int,unsigned int))GPA( "glCopyPixels" ); + qglCopyTexImage1D = dllCopyTexImage1D =(void (__stdcall *)(unsigned int,int,unsigned int,int,int,int,int))GPA( "glCopyTexImage1D" ); + qglCopyTexImage2D = dllCopyTexImage2D =(void (__stdcall *)(unsigned int,int,unsigned int,int,int,int,int,int))GPA( "glCopyTexImage2D" ); + qglCopyTexSubImage1D = dllCopyTexSubImage1D =(void (__stdcall *)(unsigned int,int,int,int,int,int))GPA( "glCopyTexSubImage1D" ); + qglCopyTexSubImage2D = dllCopyTexSubImage2D =(void (__stdcall *)(unsigned int,int,int,int,int,int,int,int))GPA( "glCopyTexSubImage2D" ); + qglCullFace = dllCullFace =(void (__stdcall *)(unsigned int))GPA( "glCullFace" ); + qglDeleteLists = dllDeleteLists =(void (__stdcall *)(unsigned int,int))GPA( "glDeleteLists" ); + qglDeleteTextures = dllDeleteTextures =(void (__stdcall *)(int,const unsigned int *))GPA( "glDeleteTextures" ); + qglDepthFunc = dllDepthFunc =(void (__stdcall *)(unsigned int))GPA( "glDepthFunc" ); + qglDepthMask = dllDepthMask =(void (__stdcall *)(unsigned char))GPA( "glDepthMask" ); + qglDepthRange = dllDepthRange =(void (__stdcall *)(double,double))GPA( "glDepthRange" ); + qglDisable = dllDisable =(void (__stdcall *)(unsigned int))GPA( "glDisable" ); + qglDisableClientState = dllDisableClientState =(void (__stdcall *)(unsigned int))GPA( "glDisableClientState" ); + qglDrawArrays = dllDrawArrays =(void (__stdcall *)(unsigned int,int,int))GPA( "glDrawArrays" ); + qglDrawBuffer = dllDrawBuffer =(void (__stdcall *)(unsigned int))GPA( "glDrawBuffer" ); + qglDrawElements = dllDrawElements =(void (__stdcall *)(unsigned int,int,unsigned int,const void *))GPA( "glDrawElements" ); + qglDrawPixels = dllDrawPixels =(void (__stdcall *)(int,int,unsigned int,unsigned int,const void *))GPA( "glDrawPixels" ); + qglEdgeFlag = dllEdgeFlag =(void (__stdcall *)(unsigned char))GPA( "glEdgeFlag" ); + qglEdgeFlagPointer = dllEdgeFlagPointer =(void (__stdcall *)(int,const void *))GPA( "glEdgeFlagPointer" ); + qglEdgeFlagv = dllEdgeFlagv =(void (__stdcall *)(const unsigned char *))GPA( "glEdgeFlagv" ); + qglEnable = dllEnable =(void (__stdcall *)(unsigned int))GPA( "glEnable" ); + qglEnableClientState = dllEnableClientState =(void (__stdcall *)(unsigned int))GPA( "glEnableClientState" ); + qglEnd = dllEnd =(void (__stdcall *)(void))GPA( "glEnd" ); + qglEndList = dllEndList =(void (__stdcall *)(void))GPA( "glEndList" ); + qglEvalCoord1d = dllEvalCoord1d =(void (__stdcall *)(double))GPA( "glEvalCoord1d" ); + qglEvalCoord1dv = dllEvalCoord1dv =(void (__stdcall *)(const double *))GPA( "glEvalCoord1dv" ); + qglEvalCoord1f = dllEvalCoord1f =(void (__stdcall *)(float))GPA( "glEvalCoord1f" ); + qglEvalCoord1fv = dllEvalCoord1fv =(void (__stdcall *)(const float *))GPA( "glEvalCoord1fv" ); + qglEvalCoord2d = dllEvalCoord2d =(void (__stdcall *)(double,double))GPA( "glEvalCoord2d" ); + qglEvalCoord2dv = dllEvalCoord2dv =(void (__stdcall *)(const double *))GPA( "glEvalCoord2dv" ); + qglEvalCoord2f = dllEvalCoord2f =(void (__stdcall *)(float,float))GPA( "glEvalCoord2f" ); + qglEvalCoord2fv = dllEvalCoord2fv =(void (__stdcall *)(const float *))GPA( "glEvalCoord2fv" ); + qglEvalMesh1 = dllEvalMesh1 =(void (__stdcall *)(unsigned int,int,int))GPA( "glEvalMesh1" ); + qglEvalMesh2 = dllEvalMesh2 =(void (__stdcall *)(unsigned int,int,int,int,int))GPA( "glEvalMesh2" ); + qglEvalPoint1 = dllEvalPoint1 =(void (__stdcall *)(int))GPA( "glEvalPoint1" ); + qglEvalPoint2 = dllEvalPoint2 =(void (__stdcall *)(int,int))GPA( "glEvalPoint2" ); + qglFeedbackBuffer = dllFeedbackBuffer =(void (__stdcall *)(int,unsigned int,float *))GPA( "glFeedbackBuffer" ); + qglFinish = dllFinish =(void (__stdcall *)(void))GPA( "glFinish" ); + qglFlush = dllFlush =(void (__stdcall *)(void))GPA( "glFlush" ); + qglFogf = dllFogf =(void (__stdcall *)(unsigned int,float))GPA( "glFogf" ); + qglFogfv = dllFogfv =(void (__stdcall *)(unsigned int,const float *))GPA( "glFogfv" ); + qglFogi = dllFogi =(void (__stdcall *)(unsigned int,int))GPA( "glFogi" ); + qglFogiv = dllFogiv =(void (__stdcall *)(unsigned int,const int *))GPA( "glFogiv" ); + qglFrontFace = dllFrontFace =(void (__stdcall *)(unsigned int))GPA( "glFrontFace" ); + qglFrustum = dllFrustum =(void (__stdcall *)(double,double,double,double,double,double))GPA( "glFrustum" ); + qglGenLists = dllGenLists =(unsigned int (__stdcall *)(int))GPA( "glGenLists" ); + qglGenTextures = dllGenTextures =(void (__stdcall *)(int,unsigned int *))GPA( "glGenTextures" ); + qglGetBooleanv = dllGetBooleanv =(void (__stdcall *)(unsigned int,unsigned char *))GPA( "glGetBooleanv" ); + qglGetClipPlane = dllGetClipPlane =(void (__stdcall *)(unsigned int,double *))GPA( "glGetClipPlane" ); + qglGetDoublev = dllGetDoublev =(void (__stdcall *)(unsigned int,double *))GPA( "glGetDoublev" ); + qglGetError = dllGetError =(unsigned int (__stdcall *)(void))GPA( "glGetError" ); + qglGetFloatv = dllGetFloatv =(void (__stdcall *)(unsigned int,float *))GPA( "glGetFloatv" ); + qglGetIntegerv = dllGetIntegerv =(void (__stdcall *)(unsigned int,int *))GPA( "glGetIntegerv" ); + qglGetLightfv = dllGetLightfv =(void (__stdcall *)(unsigned int,unsigned int,float *))GPA( "glGetLightfv" ); + qglGetLightiv = dllGetLightiv =(void (__stdcall *)(unsigned int,unsigned int,int *))GPA( "glGetLightiv" ); + qglGetMapdv = dllGetMapdv =(void (__stdcall *)(unsigned int,unsigned int,double *))GPA( "glGetMapdv" ); + qglGetMapfv = dllGetMapfv =(void (__stdcall *)(unsigned int,unsigned int,float *))GPA( "glGetMapfv" ); + qglGetMapiv = dllGetMapiv =(void (__stdcall *)(unsigned int,unsigned int,int *))GPA( "glGetMapiv" ); + qglGetMaterialfv = dllGetMaterialfv =(void (__stdcall *)(unsigned int,unsigned int,float *))GPA( "glGetMaterialfv" ); + qglGetMaterialiv = dllGetMaterialiv =(void (__stdcall *)(unsigned int,unsigned int,int *))GPA( "glGetMaterialiv" ); + qglGetPixelMapfv = dllGetPixelMapfv =(void (__stdcall *)(unsigned int,float *))GPA( "glGetPixelMapfv" ); + qglGetPixelMapuiv = dllGetPixelMapuiv =(void (__stdcall *)(unsigned int,unsigned int *))GPA( "glGetPixelMapuiv" ); + qglGetPixelMapusv = dllGetPixelMapusv =(void (__stdcall *)(unsigned int,unsigned short *))GPA( "glGetPixelMapusv" ); + qglGetPointerv = dllGetPointerv =(void (__stdcall *)(unsigned int,void ** ))GPA( "glGetPointerv" ); + qglGetPolygonStipple = dllGetPolygonStipple =(void (__stdcall *)(unsigned char *))GPA( "glGetPolygonStipple" ); + qglGetString = dllGetString =(const unsigned char *(__stdcall *)(unsigned int))GPA( "glGetString" ); + qglGetTexEnvfv = dllGetTexEnvfv =(void (__stdcall *)(unsigned int,unsigned int,float *))GPA( "glGetTexEnvfv" ); + qglGetTexEnviv = dllGetTexEnviv =(void (__stdcall *)(unsigned int,unsigned int,int *))GPA( "glGetTexEnviv" ); + qglGetTexGendv = dllGetTexGendv =(void (__stdcall *)(unsigned int,unsigned int,double *))GPA( "glGetTexGendv" ); + qglGetTexGenfv = dllGetTexGenfv =(void (__stdcall *)(unsigned int,unsigned int,float *))GPA( "glGetTexGenfv" ); + qglGetTexGeniv = dllGetTexGeniv =(void (__stdcall *)(unsigned int,unsigned int,int *))GPA( "glGetTexGeniv" ); + qglGetTexImage = dllGetTexImage =(void (__stdcall *)(unsigned int,int,unsigned int,unsigned int,void *))GPA( "glGetTexImage" ); +// qglGetTexLevelParameterfv = dllGetTexLevelParameterfv =(void (__stdcall *)(unsigned int,int,unsigned int,float *))GPA( "glGetTexLevelParameterfv" ); +// qglGetTexLevelParameteriv = dllGetTexLevelParameteriv =(void (__stdcall *)(unsigned int,int,unsigned int,int *))GPA( "glGetTexLevelParameteriv" ); + qglGetTexParameterfv = dllGetTexParameterfv =(void (__stdcall *)(unsigned int,unsigned int,float *))GPA( "glGetTexParameterfv" ); + qglGetTexParameteriv = dllGetTexParameteriv =(void (__stdcall *)(unsigned int,unsigned int,int *))GPA( "glGetTexParameteriv" ); + qglHint = dllHint =(void (__stdcall *)(unsigned int,unsigned int))GPA( "glHint" ); + qglIndexMask = dllIndexMask =(void (__stdcall *)(unsigned int))GPA( "glIndexMask" ); + qglIndexPointer = dllIndexPointer =(void (__stdcall *)(unsigned int,int,const void *))GPA( "glIndexPointer" ); + qglIndexd = dllIndexd =(void (__stdcall *)(double))GPA( "glIndexd" ); + qglIndexdv = dllIndexdv =(void (__stdcall *)(const double *))GPA( "glIndexdv" ); + qglIndexf = dllIndexf =(void (__stdcall *)(float))GPA( "glIndexf" ); + qglIndexfv = dllIndexfv =(void (__stdcall *)(const float *))GPA( "glIndexfv" ); + qglIndexi = dllIndexi =(void (__stdcall *)(int))GPA( "glIndexi" ); + qglIndexiv = dllIndexiv =(void (__stdcall *)(const int *))GPA( "glIndexiv" ); + qglIndexs = dllIndexs =(void (__stdcall *)(short))GPA( "glIndexs" ); + qglIndexsv = dllIndexsv =(void (__stdcall *)(const short *))GPA( "glIndexsv" ); + qglIndexub = dllIndexub =(void (__stdcall *)(unsigned char))GPA( "glIndexub" ); + qglIndexubv = dllIndexubv =(void (__stdcall *)(const unsigned char *))GPA( "glIndexubv" ); + qglInitNames = dllInitNames =(void (__stdcall *)(void))GPA( "glInitNames" ); + qglInterleavedArrays = dllInterleavedArrays =(void (__stdcall *)(unsigned int,int,const void *))GPA( "glInterleavedArrays" ); + qglIsEnabled = dllIsEnabled =(unsigned char (__stdcall *)(unsigned int))GPA( "glIsEnabled" ); + qglIsList = dllIsList =(unsigned char (__stdcall *)(unsigned int))GPA( "glIsList" ); + qglIsTexture = dllIsTexture =(unsigned char (__stdcall *)(unsigned int))GPA( "glIsTexture" ); + qglLightModelf = dllLightModelf =(void (__stdcall *)(unsigned int,float))GPA( "glLightModelf" ); + qglLightModelfv = dllLightModelfv =(void (__stdcall *)(unsigned int,const float *))GPA( "glLightModelfv" ); + qglLightModeli = dllLightModeli =(void (__stdcall *)(unsigned int,int))GPA( "glLightModeli" ); + qglLightModeliv = dllLightModeliv =(void (__stdcall *)(unsigned int,const int *))GPA( "glLightModeliv" ); + qglLightf = dllLightf =(void (__stdcall *)(unsigned int,unsigned int,float))GPA( "glLightf" ); + qglLightfv = dllLightfv =(void (__stdcall *)(unsigned int,unsigned int,const float *))GPA( "glLightfv" ); + qglLighti = dllLighti =(void (__stdcall *)(unsigned int,unsigned int,int))GPA( "glLighti" ); + qglLightiv = dllLightiv =(void (__stdcall *)(unsigned int,unsigned int,const int *))GPA( "glLightiv" ); + qglLineStipple = dllLineStipple =(void (__stdcall *)(int,unsigned short))GPA( "glLineStipple" ); + qglLineWidth = dllLineWidth =(void (__stdcall *)(float))GPA( "glLineWidth" ); + qglListBase = dllListBase =(void (__stdcall *)(unsigned int))GPA( "glListBase" ); + qglLoadIdentity = dllLoadIdentity =(void (__stdcall *)(void))GPA( "glLoadIdentity" ); + qglLoadMatrixd = dllLoadMatrixd =(void (__stdcall *)(const double *))GPA( "glLoadMatrixd" ); + qglLoadMatrixf = dllLoadMatrixf =(void (__stdcall *)(const float *))GPA( "glLoadMatrixf" ); + qglLoadName = dllLoadName =(void (__stdcall *)(unsigned int))GPA( "glLoadName" ); + qglLogicOp = dllLogicOp =(void (__stdcall *)(unsigned int))GPA( "glLogicOp" ); + qglMap1d = dllMap1d =(void (__stdcall *)(unsigned int,double,double,int,int,const double *))GPA( "glMap1d" ); + qglMap1f = dllMap1f =(void (__stdcall *)(unsigned int,float,float,int,int,const float *))GPA( "glMap1f" ); + qglMap2d = dllMap2d =(void (__stdcall *)(unsigned int,double,double,int,int,double,double,int,int,const double *))GPA( "glMap2d" ); + qglMap2f = dllMap2f =(void (__stdcall *)(unsigned int,float,float,int,int,float,float,int,int,const float *))GPA( "glMap2f" ); + qglMapGrid1d = dllMapGrid1d =(void (__stdcall *)(int,double,double))GPA( "glMapGrid1d" ); + qglMapGrid1f = dllMapGrid1f =(void (__stdcall *)(int,float,float))GPA( "glMapGrid1f" ); + qglMapGrid2d = dllMapGrid2d =(void (__stdcall *)(int,double,double,int,double,double))GPA( "glMapGrid2d" ); + qglMapGrid2f = dllMapGrid2f =(void (__stdcall *)(int,float,float,int,float,float))GPA( "glMapGrid2f" ); + qglMaterialf = dllMaterialf =(void (__stdcall *)(unsigned int,unsigned int,float))GPA( "glMaterialf" ); + qglMaterialfv = dllMaterialfv =(void (__stdcall *)(unsigned int,unsigned int,const float *))GPA( "glMaterialfv" ); + qglMateriali = dllMateriali =(void (__stdcall *)(unsigned int,unsigned int,int))GPA( "glMateriali" ); + qglMaterialiv = dllMaterialiv =(void (__stdcall *)(unsigned int,unsigned int,const int *))GPA( "glMaterialiv" ); + qglMatrixMode = dllMatrixMode =(void (__stdcall *)(unsigned int))GPA( "glMatrixMode" ); + qglMultMatrixd = dllMultMatrixd =(void (__stdcall *)(const double *))GPA( "glMultMatrixd" ); + qglMultMatrixf = dllMultMatrixf =(void (__stdcall *)(const float *))GPA( "glMultMatrixf" ); + qglNewList = dllNewList =(void (__stdcall *)(unsigned int,unsigned int))GPA( "glNewList" ); + qglNormal3b = dllNormal3b =(void (__stdcall *)(signed char,signed char,signed char))GPA( "glNormal3b" ); + qglNormal3bv = dllNormal3bv =(void (__stdcall *)(const signed char *))GPA( "glNormal3bv" ); + qglNormal3d = dllNormal3d =(void (__stdcall *)(double,double,double))GPA( "glNormal3d" ); + qglNormal3dv = dllNormal3dv =(void (__stdcall *)(const double *))GPA( "glNormal3dv" ); + qglNormal3f = dllNormal3f =(void (__stdcall *)(float,float,float))GPA( "glNormal3f" ); + qglNormal3fv = dllNormal3fv =(void (__stdcall *)(const float *))GPA( "glNormal3fv" ); + qglNormal3i = dllNormal3i =(void (__stdcall *)(int,int,int))GPA( "glNormal3i" ); + qglNormal3iv = dllNormal3iv =(void (__stdcall *)(const int *))GPA( "glNormal3iv" ); + qglNormal3s = dllNormal3s =(void (__stdcall *)(short,short,short))GPA( "glNormal3s" ); + qglNormal3sv = dllNormal3sv =(void (__stdcall *)(const short *))GPA( "glNormal3sv" ); + qglNormalPointer = dllNormalPointer =(void (__stdcall *)(unsigned int,int,const void *))GPA( "glNormalPointer" ); + qglOrtho = dllOrtho =(void (__stdcall *)(double,double,double,double,double,double))GPA( "glOrtho" ); + qglPassThrough = dllPassThrough =(void (__stdcall *)(float))GPA( "glPassThrough" ); + qglPixelMapfv = dllPixelMapfv =(void (__stdcall *)(unsigned int,int,const float *))GPA( "glPixelMapfv" ); + qglPixelMapuiv = dllPixelMapuiv =(void (__stdcall *)(unsigned int,int,const unsigned int *))GPA( "glPixelMapuiv" ); + qglPixelMapusv = dllPixelMapusv =(void (__stdcall *)(unsigned int,int,const unsigned short *))GPA( "glPixelMapusv" ); + qglPixelStoref = dllPixelStoref =(void (__stdcall *)(unsigned int,float))GPA( "glPixelStoref" ); + qglPixelStorei = dllPixelStorei =(void (__stdcall *)(unsigned int,int))GPA( "glPixelStorei" ); + qglPixelTransferf = dllPixelTransferf =(void (__stdcall *)(unsigned int,float))GPA( "glPixelTransferf" ); + qglPixelTransferi = dllPixelTransferi =(void (__stdcall *)(unsigned int,int))GPA( "glPixelTransferi" ); + qglPixelZoom = dllPixelZoom =(void (__stdcall *)(float,float))GPA( "glPixelZoom" ); + qglPointSize = dllPointSize =(void (__stdcall *)(float))GPA( "glPointSize" ); + qglPolygonMode = dllPolygonMode =(void (__stdcall *)(unsigned int,unsigned int))GPA( "glPolygonMode" ); + qglPolygonOffset = dllPolygonOffset =(void (__stdcall *)(float,float))GPA( "glPolygonOffset" ); + qglPolygonStipple = dllPolygonStipple =(void (__stdcall *)(const unsigned char *))GPA( "glPolygonStipple" ); + qglPopAttrib = dllPopAttrib =(void (__stdcall *)(void))GPA( "glPopAttrib" ); + qglPopClientAttrib = dllPopClientAttrib =(void (__stdcall *)(void))GPA( "glPopClientAttrib" ); + qglPopMatrix = dllPopMatrix =(void (__stdcall *)(void))GPA( "glPopMatrix" ); + qglPopName = dllPopName =(void (__stdcall *)(void))GPA( "glPopName" ); + qglPrioritizeTextures = dllPrioritizeTextures =(void (__stdcall *)(int,const unsigned int *,const float *))GPA( "glPrioritizeTextures" ); + qglPushAttrib = dllPushAttrib =(void (__stdcall *)(unsigned int))GPA( "glPushAttrib" ); + qglPushClientAttrib = dllPushClientAttrib =(void (__stdcall *)(unsigned int))GPA( "glPushClientAttrib" ); + qglPushMatrix = dllPushMatrix =(void (__stdcall *)(void))GPA( "glPushMatrix" ); + qglPushName = dllPushName =(void (__stdcall *)(unsigned int))GPA( "glPushName" ); + qglRasterPos2d = dllRasterPos2d =(void (__stdcall *)(double,double))GPA( "glRasterPos2d" ); + qglRasterPos2dv = dllRasterPos2dv =(void (__stdcall *)(const double *))GPA( "glRasterPos2dv" ); + qglRasterPos2f = dllRasterPos2f =(void (__stdcall *)(float,float))GPA( "glRasterPos2f" ); + qglRasterPos2fv = dllRasterPos2fv =(void (__stdcall *)(const float *))GPA( "glRasterPos2fv" ); + qglRasterPos2i = dllRasterPos2i =(void (__stdcall *)(int,int))GPA( "glRasterPos2i" ); + qglRasterPos2iv = dllRasterPos2iv =(void (__stdcall *)(const int *))GPA( "glRasterPos2iv" ); + qglRasterPos2s = dllRasterPos2s =(void (__stdcall *)(short,short))GPA( "glRasterPos2s" ); + qglRasterPos2sv = dllRasterPos2sv =(void (__stdcall *)(const short *))GPA( "glRasterPos2sv" ); + qglRasterPos3d = dllRasterPos3d =(void (__stdcall *)(double,double,double))GPA( "glRasterPos3d" ); + qglRasterPos3dv = dllRasterPos3dv =(void (__stdcall *)(const double *))GPA( "glRasterPos3dv" ); + qglRasterPos3f = dllRasterPos3f =(void (__stdcall *)(float,float,float))GPA( "glRasterPos3f" ); + qglRasterPos3fv = dllRasterPos3fv =(void (__stdcall *)(const float *))GPA( "glRasterPos3fv" ); + qglRasterPos3i = dllRasterPos3i =(void (__stdcall *)(int,int,int))GPA( "glRasterPos3i" ); + qglRasterPos3iv = dllRasterPos3iv =(void (__stdcall *)(const int *))GPA( "glRasterPos3iv" ); + qglRasterPos3s = dllRasterPos3s =(void (__stdcall *)(short,short,short))GPA( "glRasterPos3s" ); + qglRasterPos3sv = dllRasterPos3sv =(void (__stdcall *)(const short *))GPA( "glRasterPos3sv" ); + qglRasterPos4d = dllRasterPos4d =(void (__stdcall *)(double,double,double,double))GPA( "glRasterPos4d" ); + qglRasterPos4dv = dllRasterPos4dv =(void (__stdcall *)(const double *))GPA( "glRasterPos4dv" ); + qglRasterPos4f = dllRasterPos4f =(void (__stdcall *)(float,float,float,float))GPA( "glRasterPos4f" ); + qglRasterPos4fv = dllRasterPos4fv =(void (__stdcall *)(const float *))GPA( "glRasterPos4fv" ); + qglRasterPos4i = dllRasterPos4i =(void (__stdcall *)(int,int,int,int))GPA( "glRasterPos4i" ); + qglRasterPos4iv = dllRasterPos4iv =(void (__stdcall *)(const int *))GPA( "glRasterPos4iv" ); + qglRasterPos4s = dllRasterPos4s =(void (__stdcall *)(short,short,short,short))GPA( "glRasterPos4s" ); + qglRasterPos4sv = dllRasterPos4sv =(void (__stdcall *)(const short *))GPA( "glRasterPos4sv" ); + qglReadBuffer = dllReadBuffer =(void (__stdcall *)(unsigned int))GPA( "glReadBuffer" ); + qglReadPixels = dllReadPixels =(void (__stdcall *)(int,int,int,int,unsigned int,unsigned int,void *))GPA( "glReadPixels" ); + qglRectd = dllRectd =(void (__stdcall *)(double,double,double,double))GPA( "glRectd" ); + qglRectdv = dllRectdv =(void (__stdcall *)(const double *,const double *))GPA( "glRectdv" ); + qglRectf = dllRectf =(void (__stdcall *)(float,float,float,float))GPA( "glRectf" ); + qglRectfv = dllRectfv =(void (__stdcall *)(const float *,const float *))GPA( "glRectfv" ); + qglRecti = dllRecti =(void (__stdcall *)(int,int,int,int))GPA( "glRecti" ); + qglRectiv = dllRectiv =(void (__stdcall *)(const int *,const int *))GPA( "glRectiv" ); + qglRects = dllRects =(void (__stdcall *)(short,short,short,short))GPA( "glRects" ); + qglRectsv = dllRectsv =(void (__stdcall *)(const short *,const short *))GPA( "glRectsv" ); + qglRenderMode = dllRenderMode =(int (__stdcall *)(unsigned int))GPA( "glRenderMode" ); + qglRotated = dllRotated =(void (__stdcall *)(double,double,double,double))GPA( "glRotated" ); + qglRotatef = dllRotatef =(void (__stdcall *)(float,float,float,float))GPA( "glRotatef" ); + qglScaled = dllScaled =(void (__stdcall *)(double,double,double))GPA( "glScaled" ); + qglScalef = dllScalef =(void (__stdcall *)(float,float,float))GPA( "glScalef" ); + qglScissor = dllScissor =(void (__stdcall *)(int,int,int,int))GPA( "glScissor" ); + qglSelectBuffer = dllSelectBuffer =(void (__stdcall *)(int,unsigned int *))GPA( "glSelectBuffer" ); + qglShadeModel = dllShadeModel =(void (__stdcall *)(unsigned int))GPA( "glShadeModel" ); + qglStencilFunc = dllStencilFunc =(void (__stdcall *)(unsigned int,int,unsigned int))GPA( "glStencilFunc" ); + qglStencilMask = dllStencilMask =(void (__stdcall *)(unsigned int))GPA( "glStencilMask" ); + qglStencilOp = dllStencilOp =(void (__stdcall *)(unsigned int,unsigned int,unsigned int))GPA( "glStencilOp" ); + qglTexCoord1d = dllTexCoord1d =(void (__stdcall *)(double))GPA( "glTexCoord1d" ); + qglTexCoord1dv = dllTexCoord1dv =(void (__stdcall *)(const double *))GPA( "glTexCoord1dv" ); + qglTexCoord1f = dllTexCoord1f =(void (__stdcall *)(float))GPA( "glTexCoord1f" ); + qglTexCoord1fv = dllTexCoord1fv =(void (__stdcall *)(const float *))GPA( "glTexCoord1fv" ); + qglTexCoord1i = dllTexCoord1i =(void (__stdcall *)(int))GPA( "glTexCoord1i" ); + qglTexCoord1iv = dllTexCoord1iv =(void (__stdcall *)(const int *))GPA( "glTexCoord1iv" ); + qglTexCoord1s = dllTexCoord1s =(void (__stdcall *)(short))GPA( "glTexCoord1s" ); + qglTexCoord1sv = dllTexCoord1sv =(void (__stdcall *)(const short *))GPA( "glTexCoord1sv" ); + qglTexCoord2d = dllTexCoord2d =(void (__stdcall *)(double,double))GPA( "glTexCoord2d" ); + qglTexCoord2dv = dllTexCoord2dv =(void (__stdcall *)(const double *))GPA( "glTexCoord2dv" ); + qglTexCoord2f = dllTexCoord2f =(void (__stdcall *)(float,float))GPA( "glTexCoord2f" ); + qglTexCoord2fv = dllTexCoord2fv =(void (__stdcall *)(const float *))GPA( "glTexCoord2fv" ); + qglTexCoord2i = dllTexCoord2i =(void (__stdcall *)(int,int))GPA( "glTexCoord2i" ); + qglTexCoord2iv = dllTexCoord2iv =(void (__stdcall *)(const int *))GPA( "glTexCoord2iv" ); + qglTexCoord2s = dllTexCoord2s =(void (__stdcall *)(short,short))GPA( "glTexCoord2s" ); + qglTexCoord2sv = dllTexCoord2sv =(void (__stdcall *)(const short *))GPA( "glTexCoord2sv" ); + qglTexCoord3d = dllTexCoord3d =(void (__stdcall *)(double,double,double))GPA( "glTexCoord3d" ); + qglTexCoord3dv = dllTexCoord3dv =(void (__stdcall *)(const double *))GPA( "glTexCoord3dv" ); + qglTexCoord3f = dllTexCoord3f =(void (__stdcall *)(float,float,float))GPA( "glTexCoord3f" ); + qglTexCoord3fv = dllTexCoord3fv =(void (__stdcall *)(const float *))GPA( "glTexCoord3fv" ); + qglTexCoord3i = dllTexCoord3i =(void (__stdcall *)(int,int,int))GPA( "glTexCoord3i" ); + qglTexCoord3iv = dllTexCoord3iv =(void (__stdcall *)(const int *))GPA( "glTexCoord3iv" ); + qglTexCoord3s = dllTexCoord3s =(void (__stdcall *)(short,short,short))GPA( "glTexCoord3s" ); + qglTexCoord3sv = dllTexCoord3sv =(void (__stdcall *)(const short *))GPA( "glTexCoord3sv" ); + qglTexCoord4d = dllTexCoord4d =(void (__stdcall *)(double,double,double,double))GPA( "glTexCoord4d" ); + qglTexCoord4dv = dllTexCoord4dv =(void (__stdcall *)(const double *))GPA( "glTexCoord4dv" ); + qglTexCoord4f = dllTexCoord4f =(void (__stdcall *)(float,float,float,float))GPA( "glTexCoord4f" ); + qglTexCoord4fv = dllTexCoord4fv =(void (__stdcall *)(const float *))GPA( "glTexCoord4fv" ); + qglTexCoord4i = dllTexCoord4i =(void (__stdcall *)(int,int,int,int))GPA( "glTexCoord4i" ); + qglTexCoord4iv = dllTexCoord4iv =(void (__stdcall *)(const int *))GPA( "glTexCoord4iv" ); + qglTexCoord4s = dllTexCoord4s =(void (__stdcall *)(short,short,short,short))GPA( "glTexCoord4s" ); + qglTexCoord4sv = dllTexCoord4sv =(void (__stdcall *)(const short *))GPA( "glTexCoord4sv" ); + qglTexCoordPointer = dllTexCoordPointer =(void (__stdcall *)(int,unsigned int,int,const void *))GPA( "glTexCoordPointer" ); + qglTexEnvf = dllTexEnvf =(void (__stdcall *)(unsigned int,unsigned int,float))GPA( "glTexEnvf" ); + qglTexEnvfv = dllTexEnvfv =(void (__stdcall *)(unsigned int,unsigned int,const float *))GPA( "glTexEnvfv" ); + qglTexEnvi = dllTexEnvi =(void (__stdcall *)(unsigned int,unsigned int,int))GPA( "glTexEnvi" ); + qglTexEnviv = dllTexEnviv =(void (__stdcall *)(unsigned int,unsigned int,const int *))GPA( "glTexEnviv" ); + qglTexGend = dllTexGend =(void (__stdcall *)(unsigned int,unsigned int,double))GPA( "glTexGend" ); + qglTexGendv = dllTexGendv =(void (__stdcall *)(unsigned int,unsigned int,const double *))GPA( "glTexGendv" ); + qglTexGenf = dllTexGenf =(void (__stdcall *)(unsigned int,unsigned int,float))GPA( "glTexGenf" ); + qglTexGenfv = dllTexGenfv =(void (__stdcall *)(unsigned int,unsigned int,const float *))GPA( "glTexGenfv" ); + qglTexGeni = dllTexGeni =(void (__stdcall *)(unsigned int,unsigned int,int))GPA( "glTexGeni" ); + qglTexGeniv = dllTexGeniv =(void (__stdcall *)(unsigned int,unsigned int,const int *))GPA( "glTexGeniv" ); + qglTexImage1D = dllTexImage1D =(void (__stdcall *)(unsigned int,int,int,int,int,unsigned int,unsigned int,const void *))GPA( "glTexImage1D" ); + qglTexImage2D = dllTexImage2D =(void (__stdcall *)(unsigned int,int,int,int,int,int,unsigned int,unsigned int,const void *))GPA( "glTexImage2D" ); + qglTexParameterf = dllTexParameterf =(void (__stdcall *)(unsigned int,unsigned int,float))GPA( "glTexParameterf" ); + qglTexParameterfv = dllTexParameterfv =(void (__stdcall *)(unsigned int,unsigned int,const float *))GPA( "glTexParameterfv" ); + qglTexParameteri = dllTexParameteri =(void (__stdcall *)(unsigned int,unsigned int,int))GPA( "glTexParameteri" ); + qglTexParameteriv = dllTexParameteriv =(void (__stdcall *)(unsigned int,unsigned int,const int *))GPA( "glTexParameteriv" ); + qglTexSubImage1D = dllTexSubImage1D =(void (__stdcall *)(unsigned int,int,int,int,unsigned int,unsigned int,const void *))GPA( "glTexSubImage1D" ); + qglTexSubImage2D = dllTexSubImage2D =(void (__stdcall *)(unsigned int,int,int,int,int,int,unsigned int,unsigned int,const void *))GPA( "glTexSubImage2D" ); + qglTranslated = dllTranslated =(void (__stdcall *)(double,double,double))GPA( "glTranslated" ); + qglTranslatef = dllTranslatef =(void (__stdcall *)(float,float,float))GPA( "glTranslatef" ); + qglVertex2d = dllVertex2d =(void (__stdcall *)(double,double))GPA( "glVertex2d" ); + qglVertex2dv = dllVertex2dv =(void (__stdcall *)(const double *))GPA( "glVertex2dv" ); + qglVertex2f = dllVertex2f =(void (__stdcall *)(float,float))GPA( "glVertex2f" ); + qglVertex2fv = dllVertex2fv =(void (__stdcall *)(const float *))GPA( "glVertex2fv" ); + qglVertex2i = dllVertex2i =(void (__stdcall *)(int,int))GPA( "glVertex2i" ); + qglVertex2iv = dllVertex2iv =(void (__stdcall *)(const int *))GPA( "glVertex2iv" ); + qglVertex2s = dllVertex2s =(void (__stdcall *)(short,short))GPA( "glVertex2s" ); + qglVertex2sv = dllVertex2sv =(void (__stdcall *)(const short *))GPA( "glVertex2sv" ); + qglVertex3d = dllVertex3d =(void (__stdcall *)(double,double,double))GPA( "glVertex3d" ); + qglVertex3dv = dllVertex3dv =(void (__stdcall *)(const double *))GPA( "glVertex3dv" ); + qglVertex3f = dllVertex3f =(void (__stdcall *)(float,float,float))GPA( "glVertex3f" ); + qglVertex3fv = dllVertex3fv =(void (__stdcall *)(const float *))GPA( "glVertex3fv" ); + qglVertex3i = dllVertex3i =(void (__stdcall *)(int,int,int))GPA( "glVertex3i" ); + qglVertex3iv = dllVertex3iv =(void (__stdcall *)(const int *))GPA( "glVertex3iv" ); + qglVertex3s = dllVertex3s =(void (__stdcall *)(short,short,short))GPA( "glVertex3s" ); + qglVertex3sv = dllVertex3sv =(void (__stdcall *)(const short *))GPA( "glVertex3sv" ); + qglVertex4d = dllVertex4d =(void (__stdcall *)(double,double,double,double))GPA( "glVertex4d" ); + qglVertex4dv = dllVertex4dv =(void (__stdcall *)(const double *))GPA( "glVertex4dv" ); + qglVertex4f = dllVertex4f =(void (__stdcall *)(float,float,float,float))GPA( "glVertex4f" ); + qglVertex4fv = dllVertex4fv =(void (__stdcall *)(const float *))GPA( "glVertex4fv" ); + qglVertex4i = dllVertex4i =(void (__stdcall *)(int,int,int,int))GPA( "glVertex4i" ); + qglVertex4iv = dllVertex4iv =(void (__stdcall *)(const int *))GPA( "glVertex4iv" ); + qglVertex4s = dllVertex4s =(void (__stdcall *)(short,short,short,short))GPA( "glVertex4s" ); + qglVertex4sv = dllVertex4sv =(void (__stdcall *)(const short *))GPA( "glVertex4sv" ); + qglVertexPointer = dllVertexPointer =(void (__stdcall *)(int,unsigned int,int,const void *))GPA( "glVertexPointer" ); + qglViewport = dllViewport =(void (__stdcall *)(int,int,int,int))GPA( "glViewport" ); + + qwglCopyContext =(int (__stdcall *)(struct HGLRC__ *,struct HGLRC__ *,unsigned int))GPA( "wglCopyContext" ); + qwglCreateContext =(struct HGLRC__ *(__stdcall *)(struct HDC__ *))GPA( "wglCreateContext" ); + qwglCreateLayerContext =(struct HGLRC__ *(__stdcall *)(struct HDC__ *,int))GPA( "wglCreateLayerContext" ); + qwglDeleteContext =(int (__stdcall *)(struct HGLRC__ *))GPA( "wglDeleteContext" ); + qwglDescribeLayerPlane =(int (__stdcall *)(struct HDC__ *,int,int,unsigned int,struct tagLAYERPLANEDESCRIPTOR *))GPA( "wglDescribeLayerPlane" ); + qwglGetCurrentContext =(struct HGLRC__ *(__stdcall *)(void))GPA( "wglGetCurrentContext" ); + qwglGetCurrentDC =(struct HDC__ *(__stdcall *)(void))GPA( "wglGetCurrentDC" ); + qwglGetLayerPaletteEntries =(int (__stdcall *)(struct HDC__ *,int,int,int,unsigned long *))GPA( "wglGetLayerPaletteEntries" ); + qwglGetProcAddress =(int (__stdcall *(__stdcall *)(const char *))(void))GPA( "wglGetProcAddress" ); + qwglMakeCurrent =(int (__stdcall *)(struct HDC__ *,struct HGLRC__ *))GPA( "wglMakeCurrent" ); + qwglRealizeLayerPalette =(int (__stdcall *)(struct HDC__ *,int,int))GPA( "wglRealizeLayerPalette" ); + qwglSetLayerPaletteEntries =(int (__stdcall *)(struct HDC__ *,int,int,int,const unsigned long *))GPA( "wglSetLayerPaletteEntries" ); + qwglShareLists =(int (__stdcall *)(struct HGLRC__ *,struct HGLRC__ *))GPA( "wglShareLists" ); + qwglSwapLayerBuffers =(int (__stdcall *)(struct HDC__ *,unsigned int))GPA( "wglSwapLayerBuffers" ); + qwglUseFontBitmaps =(int (__stdcall *)(struct HDC__ *,unsigned long,unsigned long,unsigned long))GPA( "wglUseFontBitmapsA" ); + qwglUseFontOutlines =(int (__stdcall *)(struct HDC__ *,unsigned long,unsigned long,unsigned long,float,float,int,struct _GLYPHMETRICSFLOAT *))GPA( "wglUseFontOutlinesA" ); + + qwglSwapIntervalEXT = 0; + qglActiveTextureARB = 0; + qglClientActiveTextureARB = 0; + qglMultiTexCoord2fARB = 0; + qglLockArraysEXT = 0; + qglUnlockArraysEXT = 0; + qglPointParameterfEXT = NULL; + qglPointParameterfvEXT = NULL; + qglPointParameteriNV = NULL; + qglPointParameterivNV = NULL; + + // check logging + QGL_EnableLogging( r_logFile->integer ); + + return qtrue; +} + +void QGL_EnableLogging( qboolean enable ) +{ + static qboolean isEnabled; + + // return if we're already active + if ( isEnabled && enable ) { + // decrement log counter and stop if it has reached 0 + Cvar_Set( "r_logFile", va("%d", r_logFile->integer - 1 ) ); + if ( r_logFile->integer ) { + return; + } + enable = qfalse; + } + + // return if we're already disabled + if ( !enable && !isEnabled ) + return; + + isEnabled = enable; + + if ( enable ) + { + if ( !glw_state.log_fp ) + { + struct tm *newtime; + time_t aclock; + char buffer[1024]; + cvar_t *basedir; + + time( &aclock ); + newtime = localtime( &aclock ); + + asctime( newtime ); + + basedir = Cvar_Get( "fs_basepath", "", 0 ); + Com_sprintf( buffer, sizeof(buffer), "%s/gl.log", basedir->string ); + glw_state.log_fp = fopen( buffer, "wt" ); + + fprintf( glw_state.log_fp, "%s\n", asctime( newtime ) ); + } + + qglAccum = logAccum; + qglAlphaFunc = logAlphaFunc; + qglAreTexturesResident = logAreTexturesResident; + qglArrayElement = logArrayElement; + qglBegin = logBegin; + qglBindTexture = logBindTexture; + qglBitmap = logBitmap; + qglBlendFunc = logBlendFunc; + qglCallList = logCallList; + qglCallLists = logCallLists; + qglClear = logClear; + qglClearAccum = logClearAccum; + qglClearColor = logClearColor; + qglClearDepth = logClearDepth; + qglClearIndex = logClearIndex; + qglClearStencil = logClearStencil; + qglClipPlane = logClipPlane; + qglColor3b = logColor3b; + qglColor3bv = logColor3bv; + qglColor3d = logColor3d; + qglColor3dv = logColor3dv; + qglColor3f = logColor3f; + qglColor3fv = logColor3fv; + qglColor3i = logColor3i; + qglColor3iv = logColor3iv; + qglColor3s = logColor3s; + qglColor3sv = logColor3sv; + qglColor3ub = logColor3ub; + qglColor3ubv = logColor3ubv; + qglColor3ui = logColor3ui; + qglColor3uiv = logColor3uiv; + qglColor3us = logColor3us; + qglColor3usv = logColor3usv; + qglColor4b = logColor4b; + qglColor4bv = logColor4bv; + qglColor4d = logColor4d; + qglColor4dv = logColor4dv; + qglColor4f = logColor4f; + qglColor4fv = logColor4fv; + qglColor4i = logColor4i; + qglColor4iv = logColor4iv; + qglColor4s = logColor4s; + qglColor4sv = logColor4sv; + qglColor4ub = logColor4ub; + qglColor4ubv = logColor4ubv; + qglColor4ui = logColor4ui; + qglColor4uiv = logColor4uiv; + qglColor4us = logColor4us; + qglColor4usv = logColor4usv; + qglColorMask = logColorMask; + qglColorMaterial = logColorMaterial; + qglColorPointer = logColorPointer; + qglCopyPixels = logCopyPixels; + qglCopyTexImage1D = logCopyTexImage1D; + qglCopyTexImage2D = logCopyTexImage2D; + qglCopyTexSubImage1D = logCopyTexSubImage1D; + qglCopyTexSubImage2D = logCopyTexSubImage2D; + qglCullFace = logCullFace; + qglDeleteLists = logDeleteLists ; + qglDeleteTextures = logDeleteTextures ; + qglDepthFunc = logDepthFunc ; + qglDepthMask = logDepthMask ; + qglDepthRange = logDepthRange ; + qglDisable = logDisable ; + qglDisableClientState = logDisableClientState ; + qglDrawArrays = logDrawArrays ; + qglDrawBuffer = logDrawBuffer ; + qglDrawElements = logDrawElements ; + qglDrawPixels = logDrawPixels ; + qglEdgeFlag = logEdgeFlag ; + qglEdgeFlagPointer = logEdgeFlagPointer ; + qglEdgeFlagv = logEdgeFlagv ; + qglEnable = logEnable ; + qglEnableClientState = logEnableClientState ; + qglEnd = logEnd ; + qglEndList = logEndList ; + qglEvalCoord1d = logEvalCoord1d ; + qglEvalCoord1dv = logEvalCoord1dv ; + qglEvalCoord1f = logEvalCoord1f ; + qglEvalCoord1fv = logEvalCoord1fv ; + qglEvalCoord2d = logEvalCoord2d ; + qglEvalCoord2dv = logEvalCoord2dv ; + qglEvalCoord2f = logEvalCoord2f ; + qglEvalCoord2fv = logEvalCoord2fv ; + qglEvalMesh1 = logEvalMesh1 ; + qglEvalMesh2 = logEvalMesh2 ; + qglEvalPoint1 = logEvalPoint1 ; + qglEvalPoint2 = logEvalPoint2 ; + qglFeedbackBuffer = logFeedbackBuffer ; + qglFinish = logFinish ; + qglFlush = logFlush ; + qglFogf = logFogf ; + qglFogfv = logFogfv ; + qglFogi = logFogi ; + qglFogiv = logFogiv ; + qglFrontFace = logFrontFace ; + qglFrustum = logFrustum ; + qglGenLists = logGenLists ; + qglGenTextures = logGenTextures ; + qglGetBooleanv = logGetBooleanv ; + qglGetClipPlane = logGetClipPlane ; + qglGetDoublev = logGetDoublev ; + qglGetError = logGetError ; + qglGetFloatv = logGetFloatv ; + qglGetIntegerv = logGetIntegerv ; + qglGetLightfv = logGetLightfv ; + qglGetLightiv = logGetLightiv ; + qglGetMapdv = logGetMapdv ; + qglGetMapfv = logGetMapfv ; + qglGetMapiv = logGetMapiv ; + qglGetMaterialfv = logGetMaterialfv ; + qglGetMaterialiv = logGetMaterialiv ; + qglGetPixelMapfv = logGetPixelMapfv ; + qglGetPixelMapuiv = logGetPixelMapuiv ; + qglGetPixelMapusv = logGetPixelMapusv ; + qglGetPointerv = logGetPointerv ; + qglGetPolygonStipple = logGetPolygonStipple ; + qglGetString = logGetString ; + qglGetTexEnvfv = logGetTexEnvfv ; + qglGetTexEnviv = logGetTexEnviv ; + qglGetTexGendv = logGetTexGendv ; + qglGetTexGenfv = logGetTexGenfv ; + qglGetTexGeniv = logGetTexGeniv ; + qglGetTexImage = logGetTexImage ; + qglGetTexLevelParameterfv = logGetTexLevelParameterfv ; + qglGetTexLevelParameteriv = logGetTexLevelParameteriv ; + qglGetTexParameterfv = logGetTexParameterfv ; + qglGetTexParameteriv = logGetTexParameteriv ; + qglHint = logHint ; + qglIndexMask = logIndexMask ; + qglIndexPointer = logIndexPointer ; + qglIndexd = logIndexd ; + qglIndexdv = logIndexdv ; + qglIndexf = logIndexf ; + qglIndexfv = logIndexfv ; + qglIndexi = logIndexi ; + qglIndexiv = logIndexiv ; + qglIndexs = logIndexs ; + qglIndexsv = logIndexsv ; + qglIndexub = logIndexub ; + qglIndexubv = logIndexubv ; + qglInitNames = logInitNames ; + qglInterleavedArrays = logInterleavedArrays ; + qglIsEnabled = logIsEnabled ; + qglIsList = logIsList ; + qglIsTexture = logIsTexture ; + qglLightModelf = logLightModelf ; + qglLightModelfv = logLightModelfv ; + qglLightModeli = logLightModeli ; + qglLightModeliv = logLightModeliv ; + qglLightf = logLightf ; + qglLightfv = logLightfv ; + qglLighti = logLighti ; + qglLightiv = logLightiv ; + qglLineStipple = logLineStipple ; + qglLineWidth = logLineWidth ; + qglListBase = logListBase ; + qglLoadIdentity = logLoadIdentity ; + qglLoadMatrixd = logLoadMatrixd ; + qglLoadMatrixf = logLoadMatrixf ; + qglLoadName = logLoadName ; + qglLogicOp = logLogicOp ; + qglMap1d = logMap1d ; + qglMap1f = logMap1f ; + qglMap2d = logMap2d ; + qglMap2f = logMap2f ; + qglMapGrid1d = logMapGrid1d ; + qglMapGrid1f = logMapGrid1f ; + qglMapGrid2d = logMapGrid2d ; + qglMapGrid2f = logMapGrid2f ; + qglMaterialf = logMaterialf ; + qglMaterialfv = logMaterialfv ; + qglMateriali = logMateriali ; + qglMaterialiv = logMaterialiv ; + qglMatrixMode = logMatrixMode ; + qglMultMatrixd = logMultMatrixd ; + qglMultMatrixf = logMultMatrixf ; + qglNewList = logNewList ; + qglNormal3b = logNormal3b ; + qglNormal3bv = logNormal3bv ; + qglNormal3d = logNormal3d ; + qglNormal3dv = logNormal3dv ; + qglNormal3f = logNormal3f ; + qglNormal3fv = logNormal3fv ; + qglNormal3i = logNormal3i ; + qglNormal3iv = logNormal3iv ; + qglNormal3s = logNormal3s ; + qglNormal3sv = logNormal3sv ; + qglNormalPointer = logNormalPointer ; + qglOrtho = logOrtho ; + qglPassThrough = logPassThrough ; + qglPixelMapfv = logPixelMapfv ; + qglPixelMapuiv = logPixelMapuiv ; + qglPixelMapusv = logPixelMapusv ; + qglPixelStoref = logPixelStoref ; + qglPixelStorei = logPixelStorei ; + qglPixelTransferf = logPixelTransferf ; + qglPixelTransferi = logPixelTransferi ; + qglPixelZoom = logPixelZoom ; + qglPointSize = logPointSize ; + qglPolygonMode = logPolygonMode ; + qglPolygonOffset = logPolygonOffset ; + qglPolygonStipple = logPolygonStipple ; + qglPopAttrib = logPopAttrib ; + qglPopClientAttrib = logPopClientAttrib ; + qglPopMatrix = logPopMatrix ; + qglPopName = logPopName ; + qglPrioritizeTextures = logPrioritizeTextures ; + qglPushAttrib = logPushAttrib ; + qglPushClientAttrib = logPushClientAttrib ; + qglPushMatrix = logPushMatrix ; + qglPushName = logPushName ; + qglRasterPos2d = logRasterPos2d ; + qglRasterPos2dv = logRasterPos2dv ; + qglRasterPos2f = logRasterPos2f ; + qglRasterPos2fv = logRasterPos2fv ; + qglRasterPos2i = logRasterPos2i ; + qglRasterPos2iv = logRasterPos2iv ; + qglRasterPos2s = logRasterPos2s ; + qglRasterPos2sv = logRasterPos2sv ; + qglRasterPos3d = logRasterPos3d ; + qglRasterPos3dv = logRasterPos3dv ; + qglRasterPos3f = logRasterPos3f ; + qglRasterPos3fv = logRasterPos3fv ; + qglRasterPos3i = logRasterPos3i ; + qglRasterPos3iv = logRasterPos3iv ; + qglRasterPos3s = logRasterPos3s ; + qglRasterPos3sv = logRasterPos3sv ; + qglRasterPos4d = logRasterPos4d ; + qglRasterPos4dv = logRasterPos4dv ; + qglRasterPos4f = logRasterPos4f ; + qglRasterPos4fv = logRasterPos4fv ; + qglRasterPos4i = logRasterPos4i ; + qglRasterPos4iv = logRasterPos4iv ; + qglRasterPos4s = logRasterPos4s ; + qglRasterPos4sv = logRasterPos4sv ; + qglReadBuffer = logReadBuffer ; + qglReadPixels = logReadPixels ; + qglRectd = logRectd ; + qglRectdv = logRectdv ; + qglRectf = logRectf ; + qglRectfv = logRectfv ; + qglRecti = logRecti ; + qglRectiv = logRectiv ; + qglRects = logRects ; + qglRectsv = logRectsv ; + qglRenderMode = logRenderMode ; + qglRotated = logRotated ; + qglRotatef = logRotatef ; + qglScaled = logScaled ; + qglScalef = logScalef ; + qglScissor = logScissor ; + qglSelectBuffer = logSelectBuffer ; + qglShadeModel = logShadeModel ; + qglStencilFunc = logStencilFunc ; + qglStencilMask = logStencilMask ; + qglStencilOp = logStencilOp ; + qglTexCoord1d = logTexCoord1d ; + qglTexCoord1dv = logTexCoord1dv ; + qglTexCoord1f = logTexCoord1f ; + qglTexCoord1fv = logTexCoord1fv ; + qglTexCoord1i = logTexCoord1i ; + qglTexCoord1iv = logTexCoord1iv ; + qglTexCoord1s = logTexCoord1s ; + qglTexCoord1sv = logTexCoord1sv ; + qglTexCoord2d = logTexCoord2d ; + qglTexCoord2dv = logTexCoord2dv ; + qglTexCoord2f = logTexCoord2f ; + qglTexCoord2fv = logTexCoord2fv ; + qglTexCoord2i = logTexCoord2i ; + qglTexCoord2iv = logTexCoord2iv ; + qglTexCoord2s = logTexCoord2s ; + qglTexCoord2sv = logTexCoord2sv ; + qglTexCoord3d = logTexCoord3d ; + qglTexCoord3dv = logTexCoord3dv ; + qglTexCoord3f = logTexCoord3f ; + qglTexCoord3fv = logTexCoord3fv ; + qglTexCoord3i = logTexCoord3i ; + qglTexCoord3iv = logTexCoord3iv ; + qglTexCoord3s = logTexCoord3s ; + qglTexCoord3sv = logTexCoord3sv ; + qglTexCoord4d = logTexCoord4d ; + qglTexCoord4dv = logTexCoord4dv ; + qglTexCoord4f = logTexCoord4f ; + qglTexCoord4fv = logTexCoord4fv ; + qglTexCoord4i = logTexCoord4i ; + qglTexCoord4iv = logTexCoord4iv ; + qglTexCoord4s = logTexCoord4s ; + qglTexCoord4sv = logTexCoord4sv ; + qglTexCoordPointer = logTexCoordPointer ; + qglTexEnvf = logTexEnvf ; + qglTexEnvfv = logTexEnvfv ; + qglTexEnvi = logTexEnvi ; + qglTexEnviv = logTexEnviv ; + qglTexGend = logTexGend ; + qglTexGendv = logTexGendv ; + qglTexGenf = logTexGenf ; + qglTexGenfv = logTexGenfv ; + qglTexGeni = logTexGeni ; + qglTexGeniv = logTexGeniv ; + qglTexImage1D = logTexImage1D ; + qglTexImage2D = logTexImage2D ; + qglTexParameterf = logTexParameterf ; + qglTexParameterfv = logTexParameterfv ; + qglTexParameteri = logTexParameteri ; + qglTexParameteriv = logTexParameteriv ; + qglTexSubImage1D = logTexSubImage1D ; + qglTexSubImage2D = logTexSubImage2D ; + qglTranslated = logTranslated ; + qglTranslatef = logTranslatef ; + qglVertex2d = logVertex2d ; + qglVertex2dv = logVertex2dv ; + qglVertex2f = logVertex2f ; + qglVertex2fv = logVertex2fv ; + qglVertex2i = logVertex2i ; + qglVertex2iv = logVertex2iv ; + qglVertex2s = logVertex2s ; + qglVertex2sv = logVertex2sv ; + qglVertex3d = logVertex3d ; + qglVertex3dv = logVertex3dv ; + qglVertex3f = logVertex3f ; + qglVertex3fv = logVertex3fv ; + qglVertex3i = logVertex3i ; + qglVertex3iv = logVertex3iv ; + qglVertex3s = logVertex3s ; + qglVertex3sv = logVertex3sv ; + qglVertex4d = logVertex4d ; + qglVertex4dv = logVertex4dv ; + qglVertex4f = logVertex4f ; + qglVertex4fv = logVertex4fv ; + qglVertex4i = logVertex4i ; + qglVertex4iv = logVertex4iv ; + qglVertex4s = logVertex4s ; + qglVertex4sv = logVertex4sv ; + qglVertexPointer = logVertexPointer ; + qglViewport = logViewport ; + } + else + { + if ( glw_state.log_fp ) { + fprintf( glw_state.log_fp, "*** CLOSING LOG ***\n" ); + fclose( glw_state.log_fp ); + glw_state.log_fp = NULL; + } + qglAccum = dllAccum; + qglAlphaFunc = dllAlphaFunc; + qglAreTexturesResident = dllAreTexturesResident; + qglArrayElement = dllArrayElement; + qglBegin = dllBegin; + qglBindTexture = dllBindTexture; + qglBitmap = dllBitmap; + qglBlendFunc = dllBlendFunc; + qglCallList = dllCallList; + qglCallLists = dllCallLists; + qglClear = dllClear; + qglClearAccum = dllClearAccum; + qglClearColor = dllClearColor; + qglClearDepth = dllClearDepth; + qglClearIndex = dllClearIndex; + qglClearStencil = dllClearStencil; + qglClipPlane = dllClipPlane; + qglColor3b = dllColor3b; + qglColor3bv = dllColor3bv; + qglColor3d = dllColor3d; + qglColor3dv = dllColor3dv; + qglColor3f = dllColor3f; + qglColor3fv = dllColor3fv; + qglColor3i = dllColor3i; + qglColor3iv = dllColor3iv; + qglColor3s = dllColor3s; + qglColor3sv = dllColor3sv; + qglColor3ub = dllColor3ub; + qglColor3ubv = dllColor3ubv; + qglColor3ui = dllColor3ui; + qglColor3uiv = dllColor3uiv; + qglColor3us = dllColor3us; + qglColor3usv = dllColor3usv; + qglColor4b = dllColor4b; + qglColor4bv = dllColor4bv; + qglColor4d = dllColor4d; + qglColor4dv = dllColor4dv; + qglColor4f = dllColor4f; + qglColor4fv = dllColor4fv; + qglColor4i = dllColor4i; + qglColor4iv = dllColor4iv; + qglColor4s = dllColor4s; + qglColor4sv = dllColor4sv; + qglColor4ub = dllColor4ub; + qglColor4ubv = dllColor4ubv; + qglColor4ui = dllColor4ui; + qglColor4uiv = dllColor4uiv; + qglColor4us = dllColor4us; + qglColor4usv = dllColor4usv; + qglColorMask = dllColorMask; + qglColorMaterial = dllColorMaterial; + qglColorPointer = dllColorPointer; + qglCopyPixels = dllCopyPixels; + qglCopyTexImage1D = dllCopyTexImage1D; + qglCopyTexImage2D = dllCopyTexImage2D; + qglCopyTexSubImage1D = dllCopyTexSubImage1D; + qglCopyTexSubImage2D = dllCopyTexSubImage2D; + qglCullFace = dllCullFace; + qglDeleteLists = dllDeleteLists ; + qglDeleteTextures = dllDeleteTextures ; + qglDepthFunc = dllDepthFunc ; + qglDepthMask = dllDepthMask ; + qglDepthRange = dllDepthRange ; + qglDisable = dllDisable ; + qglDisableClientState = dllDisableClientState ; + qglDrawArrays = dllDrawArrays ; + qglDrawBuffer = dllDrawBuffer ; + qglDrawElements = dllDrawElements ; + qglDrawPixels = dllDrawPixels ; + qglEdgeFlag = dllEdgeFlag ; + qglEdgeFlagPointer = dllEdgeFlagPointer ; + qglEdgeFlagv = dllEdgeFlagv ; + qglEnable = dllEnable ; + qglEnableClientState = dllEnableClientState ; + qglEnd = dllEnd ; + qglEndList = dllEndList ; + qglEvalCoord1d = dllEvalCoord1d ; + qglEvalCoord1dv = dllEvalCoord1dv ; + qglEvalCoord1f = dllEvalCoord1f ; + qglEvalCoord1fv = dllEvalCoord1fv ; + qglEvalCoord2d = dllEvalCoord2d ; + qglEvalCoord2dv = dllEvalCoord2dv ; + qglEvalCoord2f = dllEvalCoord2f ; + qglEvalCoord2fv = dllEvalCoord2fv ; + qglEvalMesh1 = dllEvalMesh1 ; + qglEvalMesh2 = dllEvalMesh2 ; + qglEvalPoint1 = dllEvalPoint1 ; + qglEvalPoint2 = dllEvalPoint2 ; + qglFeedbackBuffer = dllFeedbackBuffer ; + qglFinish = dllFinish ; + qglFlush = dllFlush ; + qglFogf = dllFogf ; + qglFogfv = dllFogfv ; + qglFogi = dllFogi ; + qglFogiv = dllFogiv ; + qglFrontFace = dllFrontFace ; + qglFrustum = dllFrustum ; + qglGenLists = dllGenLists ; + qglGenTextures = dllGenTextures ; + qglGetBooleanv = dllGetBooleanv ; + qglGetClipPlane = dllGetClipPlane ; + qglGetDoublev = dllGetDoublev ; + qglGetError = dllGetError ; + qglGetFloatv = dllGetFloatv ; + qglGetIntegerv = dllGetIntegerv ; + qglGetLightfv = dllGetLightfv ; + qglGetLightiv = dllGetLightiv ; + qglGetMapdv = dllGetMapdv ; + qglGetMapfv = dllGetMapfv ; + qglGetMapiv = dllGetMapiv ; + qglGetMaterialfv = dllGetMaterialfv ; + qglGetMaterialiv = dllGetMaterialiv ; + qglGetPixelMapfv = dllGetPixelMapfv ; + qglGetPixelMapuiv = dllGetPixelMapuiv ; + qglGetPixelMapusv = dllGetPixelMapusv ; + qglGetPointerv = dllGetPointerv ; + qglGetPolygonStipple = dllGetPolygonStipple ; + qglGetString = dllGetString ; + qglGetTexEnvfv = dllGetTexEnvfv ; + qglGetTexEnviv = dllGetTexEnviv ; + qglGetTexGendv = dllGetTexGendv ; + qglGetTexGenfv = dllGetTexGenfv ; + qglGetTexGeniv = dllGetTexGeniv ; + qglGetTexImage = dllGetTexImage ; + qglGetTexLevelParameterfv = dllGetTexLevelParameterfv ; + qglGetTexLevelParameteriv = dllGetTexLevelParameteriv ; + qglGetTexParameterfv = dllGetTexParameterfv ; + qglGetTexParameteriv = dllGetTexParameteriv ; + qglHint = dllHint ; + qglIndexMask = dllIndexMask ; + qglIndexPointer = dllIndexPointer ; + qglIndexd = dllIndexd ; + qglIndexdv = dllIndexdv ; + qglIndexf = dllIndexf ; + qglIndexfv = dllIndexfv ; + qglIndexi = dllIndexi ; + qglIndexiv = dllIndexiv ; + qglIndexs = dllIndexs ; + qglIndexsv = dllIndexsv ; + qglIndexub = dllIndexub ; + qglIndexubv = dllIndexubv ; + qglInitNames = dllInitNames ; + qglInterleavedArrays = dllInterleavedArrays ; + qglIsEnabled = dllIsEnabled ; + qglIsList = dllIsList ; + qglIsTexture = dllIsTexture ; + qglLightModelf = dllLightModelf ; + qglLightModelfv = dllLightModelfv ; + qglLightModeli = dllLightModeli ; + qglLightModeliv = dllLightModeliv ; + qglLightf = dllLightf ; + qglLightfv = dllLightfv ; + qglLighti = dllLighti ; + qglLightiv = dllLightiv ; + qglLineStipple = dllLineStipple ; + qglLineWidth = dllLineWidth ; + qglListBase = dllListBase ; + qglLoadIdentity = dllLoadIdentity ; + qglLoadMatrixd = dllLoadMatrixd ; + qglLoadMatrixf = dllLoadMatrixf ; + qglLoadName = dllLoadName ; + qglLogicOp = dllLogicOp ; + qglMap1d = dllMap1d ; + qglMap1f = dllMap1f ; + qglMap2d = dllMap2d ; + qglMap2f = dllMap2f ; + qglMapGrid1d = dllMapGrid1d ; + qglMapGrid1f = dllMapGrid1f ; + qglMapGrid2d = dllMapGrid2d ; + qglMapGrid2f = dllMapGrid2f ; + qglMaterialf = dllMaterialf ; + qglMaterialfv = dllMaterialfv ; + qglMateriali = dllMateriali ; + qglMaterialiv = dllMaterialiv ; + qglMatrixMode = dllMatrixMode ; + qglMultMatrixd = dllMultMatrixd ; + qglMultMatrixf = dllMultMatrixf ; + qglNewList = dllNewList ; + qglNormal3b = dllNormal3b ; + qglNormal3bv = dllNormal3bv ; + qglNormal3d = dllNormal3d ; + qglNormal3dv = dllNormal3dv ; + qglNormal3f = dllNormal3f ; + qglNormal3fv = dllNormal3fv ; + qglNormal3i = dllNormal3i ; + qglNormal3iv = dllNormal3iv ; + qglNormal3s = dllNormal3s ; + qglNormal3sv = dllNormal3sv ; + qglNormalPointer = dllNormalPointer ; + qglOrtho = dllOrtho ; + qglPassThrough = dllPassThrough ; + qglPixelMapfv = dllPixelMapfv ; + qglPixelMapuiv = dllPixelMapuiv ; + qglPixelMapusv = dllPixelMapusv ; + qglPixelStoref = dllPixelStoref ; + qglPixelStorei = dllPixelStorei ; + qglPixelTransferf = dllPixelTransferf ; + qglPixelTransferi = dllPixelTransferi ; + qglPixelZoom = dllPixelZoom ; + qglPointSize = dllPointSize ; + qglPolygonMode = dllPolygonMode ; + qglPolygonOffset = dllPolygonOffset ; + qglPolygonStipple = dllPolygonStipple ; + qglPopAttrib = dllPopAttrib ; + qglPopClientAttrib = dllPopClientAttrib ; + qglPopMatrix = dllPopMatrix ; + qglPopName = dllPopName ; + qglPrioritizeTextures = dllPrioritizeTextures ; + qglPushAttrib = dllPushAttrib ; + qglPushClientAttrib = dllPushClientAttrib ; + qglPushMatrix = dllPushMatrix ; + qglPushName = dllPushName ; + qglRasterPos2d = dllRasterPos2d ; + qglRasterPos2dv = dllRasterPos2dv ; + qglRasterPos2f = dllRasterPos2f ; + qglRasterPos2fv = dllRasterPos2fv ; + qglRasterPos2i = dllRasterPos2i ; + qglRasterPos2iv = dllRasterPos2iv ; + qglRasterPos2s = dllRasterPos2s ; + qglRasterPos2sv = dllRasterPos2sv ; + qglRasterPos3d = dllRasterPos3d ; + qglRasterPos3dv = dllRasterPos3dv ; + qglRasterPos3f = dllRasterPos3f ; + qglRasterPos3fv = dllRasterPos3fv ; + qglRasterPos3i = dllRasterPos3i ; + qglRasterPos3iv = dllRasterPos3iv ; + qglRasterPos3s = dllRasterPos3s ; + qglRasterPos3sv = dllRasterPos3sv ; + qglRasterPos4d = dllRasterPos4d ; + qglRasterPos4dv = dllRasterPos4dv ; + qglRasterPos4f = dllRasterPos4f ; + qglRasterPos4fv = dllRasterPos4fv ; + qglRasterPos4i = dllRasterPos4i ; + qglRasterPos4iv = dllRasterPos4iv ; + qglRasterPos4s = dllRasterPos4s ; + qglRasterPos4sv = dllRasterPos4sv ; + qglReadBuffer = dllReadBuffer ; + qglReadPixels = dllReadPixels ; + qglRectd = dllRectd ; + qglRectdv = dllRectdv ; + qglRectf = dllRectf ; + qglRectfv = dllRectfv ; + qglRecti = dllRecti ; + qglRectiv = dllRectiv ; + qglRects = dllRects ; + qglRectsv = dllRectsv ; + qglRenderMode = dllRenderMode ; + qglRotated = dllRotated ; + qglRotatef = dllRotatef ; + qglScaled = dllScaled ; + qglScalef = dllScalef ; + qglScissor = dllScissor ; + qglSelectBuffer = dllSelectBuffer ; + qglShadeModel = dllShadeModel ; + qglStencilFunc = dllStencilFunc ; + qglStencilMask = dllStencilMask ; + qglStencilOp = dllStencilOp ; + qglTexCoord1d = dllTexCoord1d ; + qglTexCoord1dv = dllTexCoord1dv ; + qglTexCoord1f = dllTexCoord1f ; + qglTexCoord1fv = dllTexCoord1fv ; + qglTexCoord1i = dllTexCoord1i ; + qglTexCoord1iv = dllTexCoord1iv ; + qglTexCoord1s = dllTexCoord1s ; + qglTexCoord1sv = dllTexCoord1sv ; + qglTexCoord2d = dllTexCoord2d ; + qglTexCoord2dv = dllTexCoord2dv ; + qglTexCoord2f = dllTexCoord2f ; + qglTexCoord2fv = dllTexCoord2fv ; + qglTexCoord2i = dllTexCoord2i ; + qglTexCoord2iv = dllTexCoord2iv ; + qglTexCoord2s = dllTexCoord2s ; + qglTexCoord2sv = dllTexCoord2sv ; + qglTexCoord3d = dllTexCoord3d ; + qglTexCoord3dv = dllTexCoord3dv ; + qglTexCoord3f = dllTexCoord3f ; + qglTexCoord3fv = dllTexCoord3fv ; + qglTexCoord3i = dllTexCoord3i ; + qglTexCoord3iv = dllTexCoord3iv ; + qglTexCoord3s = dllTexCoord3s ; + qglTexCoord3sv = dllTexCoord3sv ; + qglTexCoord4d = dllTexCoord4d ; + qglTexCoord4dv = dllTexCoord4dv ; + qglTexCoord4f = dllTexCoord4f ; + qglTexCoord4fv = dllTexCoord4fv ; + qglTexCoord4i = dllTexCoord4i ; + qglTexCoord4iv = dllTexCoord4iv ; + qglTexCoord4s = dllTexCoord4s ; + qglTexCoord4sv = dllTexCoord4sv ; + qglTexCoordPointer = dllTexCoordPointer ; + qglTexEnvf = dllTexEnvf ; + qglTexEnvfv = dllTexEnvfv ; + qglTexEnvi = dllTexEnvi ; + qglTexEnviv = dllTexEnviv ; + qglTexGend = dllTexGend ; + qglTexGendv = dllTexGendv ; + qglTexGenf = dllTexGenf ; + qglTexGenfv = dllTexGenfv ; + qglTexGeni = dllTexGeni ; + qglTexGeniv = dllTexGeniv ; + qglTexImage1D = dllTexImage1D ; + qglTexImage2D = dllTexImage2D ; + qglTexParameterf = dllTexParameterf ; + qglTexParameterfv = dllTexParameterfv ; + qglTexParameteri = dllTexParameteri ; + qglTexParameteriv = dllTexParameteriv ; + qglTexSubImage1D = dllTexSubImage1D ; + qglTexSubImage2D = dllTexSubImage2D ; + qglTranslated = dllTranslated ; + qglTranslatef = dllTranslatef ; + qglVertex2d = dllVertex2d ; + qglVertex2dv = dllVertex2dv ; + qglVertex2f = dllVertex2f ; + qglVertex2fv = dllVertex2fv ; + qglVertex2i = dllVertex2i ; + qglVertex2iv = dllVertex2iv ; + qglVertex2s = dllVertex2s ; + qglVertex2sv = dllVertex2sv ; + qglVertex3d = dllVertex3d ; + qglVertex3dv = dllVertex3dv ; + qglVertex3f = dllVertex3f ; + qglVertex3fv = dllVertex3fv ; + qglVertex3i = dllVertex3i ; + qglVertex3iv = dllVertex3iv ; + qglVertex3s = dllVertex3s ; + qglVertex3sv = dllVertex3sv ; + qglVertex4d = dllVertex4d ; + qglVertex4dv = dllVertex4dv ; + qglVertex4f = dllVertex4f ; + qglVertex4fv = dllVertex4fv ; + qglVertex4i = dllVertex4i ; + qglVertex4iv = dllVertex4iv ; + qglVertex4s = dllVertex4s ; + qglVertex4sv = dllVertex4sv ; + qglVertexPointer = dllVertexPointer ; + qglViewport = dllViewport ; + } +} + +#pragma warning (default : 4113 4133 4047 ) + + + diff --git a/code/win32/win_qgl_dx8.cpp b/code/win32/win_qgl_dx8.cpp new file mode 100644 index 0000000..6f991c0 --- /dev/null +++ b/code/win32/win_qgl_dx8.cpp @@ -0,0 +1,6963 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +/* +** QGL_WIN.C +** +** This file implements the operating system binding of GL to QGL function +** pointers. When doing a port of Quake3 you must implement the following +** two functions: +** +** QGL_Init() - loads libraries, assigns function pointers, etc. +** QGL_Shutdown() - unloads libraries, NULLs function pointers +*/ +#include +#include "../renderer/tr_local.h" +#include "glw_win_dx8.h" +#include "win_local.h" + +#include "xbox_texture_man.h" + +#ifdef _XBOX +#include +//#include "win_flareeffect.h" +#include "win_lighteffects.h" +#include "win_highdynamicrange.h" +#include "win_stencilshadow.h" + +#ifndef FINAL_BUILD +#include +#endif + +#endif + +// Global texture allocator (we only use one in SP): +//SwappingTextureAllocator gTextures; +StaticTextureAllocator gTextures; + +#include + +extern void Z_SetNewDeleteTemporary(bool); + +#define GLW_USE_TRI_STRIPS 1 + +#ifdef _XBOX +#define GLW_MAX_DRAW_PACKET_SIZE 2040 +#else +#define GLW_MAX_DRAW_PACKET_SIZE (SHADER_MAX_VERTEXES*12) +#endif + +#define MEMORY_PROFILE 1 + +int texMemSize = 0; + +#if MEMORY_PROFILE + +static int getTexMemSize(IDirect3DTexture9* mipmap) +{ + int levels = mipmap->GetLevelCount(); + int size = 0; + while (levels--) + { + D3DSURFACE_DESC desc; + mipmap->GetLevelDesc(levels, &desc); + size += desc.Size; + } + return size; +} +#endif + +void QGL_EnableLogging( qboolean enable ); + +void ( * qglAccum )(GLenum op, GLfloat value); +void ( * qglAlphaFunc )(GLenum func, GLclampf ref); +GLboolean ( * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +void ( * qglArrayElement )(GLint i); +void ( * qglBegin )(GLenum mode); +void ( * qglBeginEXT )(GLenum mode, GLint verts, GLint colors, GLint normals, GLint tex0, GLint tex1);//, GLint tex2, GLint tex3); +GLboolean ( * qglBeginFrame )(void); +void ( * qglBeginShadow )(void); +void ( * qglBindTexture )(GLenum target, GLuint texture); +void ( * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +void ( * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +void ( * qglCallList )(GLuint lnum); +void ( * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +void ( * qglClear )(GLbitfield mask); +void ( * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +void ( * qglClearDepth )(GLclampd depth); +void ( * qglClearIndex )(GLfloat c); +void ( * qglClearStencil )(GLint s); +void ( * qglClipPlane )(GLenum plane, const GLdouble *equation); +void ( * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +void ( * qglColor3bv )(const GLbyte *v); +void ( * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +void ( * qglColor3dv )(const GLdouble *v); +void ( * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +void ( * qglColor3fv )(const GLfloat *v); +void ( * qglColor3i )(GLint red, GLint green, GLint blue); +void ( * qglColor3iv )(const GLint *v); +void ( * qglColor3s )(GLshort red, GLshort green, GLshort blue); +void ( * qglColor3sv )(const GLshort *v); +void ( * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +void ( * qglColor3ubv )(const GLubyte *v); +void ( * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +void ( * qglColor3uiv )(const GLuint *v); +void ( * qglColor3us )(GLushort red, GLushort green, GLushort blue); +void ( * qglColor3usv )(const GLushort *v); +void ( * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +void ( * qglColor4bv )(const GLbyte *v); +void ( * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +void ( * qglColor4dv )(const GLdouble *v); +void ( * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( * qglColor4fv )(const GLfloat *v); +void ( * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +void ( * qglColor4iv )(const GLint *v); +void ( * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +void ( * qglColor4sv )(const GLshort *v); +void ( * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +void ( * qglColor4ubv )(const GLubyte *v); +void ( * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +void ( * qglColor4uiv )(const GLuint *v); +void ( * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +void ( * qglColor4usv )(const GLushort *v); +void ( * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +void ( * qglColorMaterial )(GLenum face, GLenum mode); +void ( * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +void ( * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +void ( * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +void ( * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +void ( * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +void ( * qglCullFace )(GLenum mode); +void ( * qglDeleteLists )(GLuint lnum, GLsizei range); +void ( * qglDeleteTextures )(GLsizei n, const GLuint *textures); +void ( * qglDepthFunc )(GLenum func); +void ( * qglDepthMask )(GLboolean flag); +void ( * qglDepthRange )(GLclampd zNear, GLclampd zFar); +void ( * qglDisable )(GLenum cap); +void ( * qglDisableClientState )(GLenum array); +void ( * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +void ( * qglDrawBuffer )(GLenum mode); +void ( * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +void ( * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( * qglEdgeFlag )(GLboolean flag); +void ( * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +void ( * qglEdgeFlagv )(const GLboolean *flag); +void ( * qglEnable )(GLenum cap); +void ( * qglEnableClientState )(GLenum array); +void ( * qglEnd )(void); +void ( * qglEndFrame )(void); +void ( * qglEndShadow )(void); +void ( * qglEndList )(void); +void ( * qglEvalCoord1d )(GLdouble u); +void ( * qglEvalCoord1dv )(const GLdouble *u); +void ( * qglEvalCoord1f )(GLfloat u); +void ( * qglEvalCoord1fv )(const GLfloat *u); +void ( * qglEvalCoord2d )(GLdouble u, GLdouble v); +void ( * qglEvalCoord2dv )(const GLdouble *u); +void ( * qglEvalCoord2f )(GLfloat u, GLfloat v); +void ( * qglEvalCoord2fv )(const GLfloat *u); +void ( * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +void ( * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +void ( * qglEvalPoint1 )(GLint i); +void ( * qglEvalPoint2 )(GLint i, GLint j); +void ( * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +void ( * qglFinish )(void); +void ( * qglFlush )(void); +void ( * qglFlushShadow )(void); +void ( * qglFogf )(GLenum pname, GLfloat param); +void ( * qglFogfv )(GLenum pname, const GLfloat *params); +void ( * qglFogi )(GLenum pname, GLint param); +void ( * qglFogiv )(GLenum pname, const GLint *params); +void ( * qglFrontFace )(GLenum mode); +void ( * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +GLuint ( * qglGenLists )(GLsizei range); +void ( * qglGenTextures )(GLsizei n, GLuint *textures); +void ( * qglGetBooleanv )(GLenum pname, GLboolean *params); +void ( * qglGetClipPlane )(GLenum plane, GLdouble *equation); +void ( * qglGetDoublev )(GLenum pname, GLdouble *params); +GLenum ( * qglGetError )(void); +void ( * qglGetFloatv )(GLenum pname, GLfloat *params); +void ( * qglGetIntegerv )(GLenum pname, GLint *params); +void ( * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +void ( * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +void ( * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +void ( * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +void ( * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +void ( * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +void ( * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +void ( * qglGetPixelMapfv )(GLenum gmap, GLfloat *values); +void ( * qglGetPixelMapuiv )(GLenum gmap, GLuint *values); +void ( * qglGetPixelMapusv )(GLenum gmap, GLushort *values); +void ( * qglGetPointerv )(GLenum pname, GLvoid* *params); +void ( * qglGetPolygonStipple )(GLubyte *mask); +const GLubyte * ( * qglGetString )(GLenum name); +void ( * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +void ( * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +void ( * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +void ( * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +void ( * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +void ( * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +void ( * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +void ( * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +void ( * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +void ( * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +void ( * qglHint )(GLenum target, GLenum mode); +void ( * qglIndexedTriToStrip )(GLsizei count, const GLushort *indices); +void ( * qglIndexMask )(GLuint mask); +void ( * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( * qglIndexd )(GLdouble c); +void ( * qglIndexdv )(const GLdouble *c); +void ( * qglIndexf )(GLfloat c); +void ( * qglIndexfv )(const GLfloat *c); +void ( * qglIndexi )(GLint c); +void ( * qglIndexiv )(const GLint *c); +void ( * qglIndexs )(GLshort c); +void ( * qglIndexsv )(const GLshort *c); +void ( * qglIndexub )(GLubyte c); +void ( * qglIndexubv )(const GLubyte *c); +void ( * qglInitNames )(void); +void ( * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +GLboolean ( * qglIsEnabled )(GLenum cap); +GLboolean ( * qglIsList )(GLuint lnum); +GLboolean ( * qglIsTexture )(GLuint texture); +void ( * qglLightModelf )(GLenum pname, GLfloat param); +void ( * qglLightModelfv )(GLenum pname, const GLfloat *params); +void ( * qglLightModeli )(GLenum pname, GLint param); +void ( * qglLightModeliv )(GLenum pname, const GLint *params); +void ( * qglLightf )(GLenum light, GLenum pname, GLfloat param); +void ( * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +void ( * qglLighti )(GLenum light, GLenum pname, GLint param); +void ( * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +void ( * qglLineStipple )(GLint factor, GLushort pattern); +void ( * qglLineWidth )(GLfloat width); +void ( * qglListBase )(GLuint base); +void ( * qglLoadIdentity )(void); +void ( * qglLoadMatrixd )(const GLdouble *m); +void ( * qglLoadMatrixf )(const GLfloat *m); +void ( * qglLoadName )(GLuint name); +void ( * qglLogicOp )(GLenum opcode); +void ( * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +void ( * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +void ( * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +void ( * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +void ( * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +void ( * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +void ( * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +void ( * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +void ( * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +void ( * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +void ( * qglMateriali )(GLenum face, GLenum pname, GLint param); +void ( * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +void ( * qglMatrixMode )(GLenum mode); +void ( * qglMultMatrixd )(const GLdouble *m); +void ( * qglMultMatrixf )(const GLfloat *m); +void ( * qglNewList )(GLuint lnum, GLenum mode); +void ( * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +void ( * qglNormal3bv )(const GLbyte *v); +void ( * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +void ( * qglNormal3dv )(const GLdouble *v); +void ( * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +void ( * qglNormal3fv )(const GLfloat *v); +void ( * qglNormal3i )(GLint nx, GLint ny, GLint nz); +void ( * qglNormal3iv )(const GLint *v); +void ( * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +void ( * qglNormal3sv )(const GLshort *v); +void ( * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +void ( * qglPassThrough )(GLfloat token); +void ( * qglPixelMapfv )(GLenum gmap, GLsizei mapsize, const GLfloat *values); +void ( * qglPixelMapuiv )(GLenum gmap, GLsizei mapsize, const GLuint *values); +void ( * qglPixelMapusv )(GLenum gmap, GLsizei mapsize, const GLushort *values); +void ( * qglPixelStoref )(GLenum pname, GLfloat param); +void ( * qglPixelStorei )(GLenum pname, GLint param); +void ( * qglPixelTransferf )(GLenum pname, GLfloat param); +void ( * qglPixelTransferi )(GLenum pname, GLint param); +void ( * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +void ( * qglPointSize )(GLfloat size); +void ( * qglPolygonMode )(GLenum face, GLenum mode); +void ( * qglPolygonOffset )(GLfloat factor, GLfloat units); +void ( * qglPolygonStipple )(const GLubyte *mask); +void ( * qglPopAttrib )(void); +void ( * qglPopClientAttrib )(void); +void ( * qglPopMatrix )(void); +void ( * qglPopName )(void); +void ( * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +void ( * qglPushAttrib )(GLbitfield mask); +void ( * qglPushClientAttrib )(GLbitfield mask); +void ( * qglPushMatrix )(void); +void ( * qglPushName )(GLuint name); +void ( * qglRasterPos2d )(GLdouble x, GLdouble y); +void ( * qglRasterPos2dv )(const GLdouble *v); +void ( * qglRasterPos2f )(GLfloat x, GLfloat y); +void ( * qglRasterPos2fv )(const GLfloat *v); +void ( * qglRasterPos2i )(GLint x, GLint y); +void ( * qglRasterPos2iv )(const GLint *v); +void ( * qglRasterPos2s )(GLshort x, GLshort y); +void ( * qglRasterPos2sv )(const GLshort *v); +void ( * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +void ( * qglRasterPos3dv )(const GLdouble *v); +void ( * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +void ( * qglRasterPos3fv )(const GLfloat *v); +void ( * qglRasterPos3i )(GLint x, GLint y, GLint z); +void ( * qglRasterPos3iv )(const GLint *v); +void ( * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +void ( * qglRasterPos3sv )(const GLshort *v); +void ( * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( * qglRasterPos4dv )(const GLdouble *v); +void ( * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( * qglRasterPos4fv )(const GLfloat *v); +void ( * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +void ( * qglRasterPos4iv )(const GLint *v); +void ( * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( * qglRasterPos4sv )(const GLshort *v); +void ( * qglReadBuffer )(GLenum mode); +//void ( * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei twidth, GLsizei theight, GLvoid *pixels); +void ( * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +void ( * qglCopyBackBufferToTexEXT ) (float width, float height, float u1, float v1, float u2, float v2); +void ( * qglCopyBackBufferToTex ) (void); +void ( * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +void ( * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +void ( * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +void ( * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +void ( * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +void ( * qglRectiv )(const GLint *v1, const GLint *v2); +void ( * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +void ( * qglRectsv )(const GLshort *v1, const GLshort *v2); +GLint ( * qglRenderMode )(GLenum mode); +void ( * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +void ( * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +void ( * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +void ( * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +void ( * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +void ( * qglSelectBuffer )(GLsizei size, GLuint *buffer); +void ( * qglShadeModel )(GLenum mode); +void ( * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +void ( * qglStencilMask )(GLuint mask); +void ( * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +void ( * qglTexCoord1d )(GLdouble s); +void ( * qglTexCoord1dv )(const GLdouble *v); +void ( * qglTexCoord1f )(GLfloat s); +void ( * qglTexCoord1fv )(const GLfloat *v); +void ( * qglTexCoord1i )(GLint s); +void ( * qglTexCoord1iv )(const GLint *v); +void ( * qglTexCoord1s )(GLshort s); +void ( * qglTexCoord1sv )(const GLshort *v); +void ( * qglTexCoord2d )(GLdouble s, GLdouble t); +void ( * qglTexCoord2dv )(const GLdouble *v); +void ( * qglTexCoord2f )(GLfloat s, GLfloat t); +void ( * qglTexCoord2fv )(const GLfloat *v); +void ( * qglTexCoord2i )(GLint s, GLint t); +void ( * qglTexCoord2iv )(const GLint *v); +void ( * qglTexCoord2s )(GLshort s, GLshort t); +void ( * qglTexCoord2sv )(const GLshort *v); +void ( * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +void ( * qglTexCoord3dv )(const GLdouble *v); +void ( * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +void ( * qglTexCoord3fv )(const GLfloat *v); +void ( * qglTexCoord3i )(GLint s, GLint t, GLint r); +void ( * qglTexCoord3iv )(const GLint *v); +void ( * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +void ( * qglTexCoord3sv )(const GLshort *v); +void ( * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +void ( * qglTexCoord4dv )(const GLdouble *v); +void ( * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +void ( * qglTexCoord4fv )(const GLfloat *v); +void ( * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +void ( * qglTexCoord4iv )(const GLint *v); +void ( * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +void ( * qglTexCoord4sv )(const GLshort *v); +void ( * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +void ( * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +void ( * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +void ( * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +void ( * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +void ( * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +void ( * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +void ( * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +void ( * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +void ( * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( * qglTexImage2DEXT )(GLenum target, GLint level, GLint numlevels, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +void ( * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +void ( * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +void ( * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +void ( * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +void ( * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +void ( * qglVertex2d )(GLdouble x, GLdouble y); +void ( * qglVertex2dv )(const GLdouble *v); +void ( * qglVertex2f )(GLfloat x, GLfloat y); +void ( * qglVertex2fv )(const GLfloat *v); +void ( * qglVertex2i )(GLint x, GLint y); +void ( * qglVertex2iv )(const GLint *v); +void ( * qglVertex2s )(GLshort x, GLshort y); +void ( * qglVertex2sv )(const GLshort *v); +void ( * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +void ( * qglVertex3dv )(const GLdouble *v); +void ( * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +void ( * qglVertex3fv )(const GLfloat *v); +void ( * qglVertex3i )(GLint x, GLint y, GLint z); +void ( * qglVertex3iv )(const GLint *v); +void ( * qglVertex3s )(GLshort x, GLshort y, GLshort z); +void ( * qglVertex3sv )(const GLshort *v); +void ( * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( * qglVertex4dv )(const GLdouble *v); +void ( * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( * qglVertex4fv )(const GLfloat *v); +void ( * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +void ( * qglVertex4iv )(const GLint *v); +void ( * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( * qglVertex4sv )(const GLshort *v); +void ( * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +#if 0 +void ( * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +void ( * qglActiveTextureARB )( GLenum texture ); +void ( * qglClientActiveTextureARB )( GLenum texture ); +#endif + +static void _d3d_check(HRESULT err, const char* func) +{ + if (err != D3D_OK) + { + MEMORYSTATUS status; + GlobalMemoryStatus(&status); + Sys_Print(va("%s returned %d! Memfree=%d\n", func, err, status.dwAvailPhys)); + } +} + +#ifdef _WINDOWS +static bool surfaceToBMP(LPDIRECT3DDEVICE8 pd3dDevice, LPDIRECT3DSURFACE8 lpSurface, const char *fname) +{ + DWORD outpixel; + BITMAPFILEHEADER fh; + BITMAPINFOHEADER bi; + int outbyte, BufferIndex, width, height, pitch; + char *WriteBuffer; + FILE *file; + HRESULT Error; + IDirect3DSurface8 *pTempSurf = NULL; + + // Get the surface description first + D3DSURFACE_DESC ddsd; + D3DLOCKED_RECT lrSurf; + + Error = lpSurface->GetDesc(&ddsd); + // This writes out 32 bit values, so whatever surface format we were passed in, + // copy it into a 32 bit surface + Error = pd3dDevice->CreateImageSurface(ddsd.Width, ddsd.Height, D3DFMT_A8R8G8B8, &pTempSurf); + + Error = D3DXLoadSurfaceFromSurface(pTempSurf, NULL, NULL, lpSurface, NULL, NULL, D3DX_DEFAULT, 0); + + file = fopen(fname, "wb"); + if(!file) + return FALSE; + + Error = pTempSurf->LockRect(&lrSurf, NULL, 0); + + BufferIndex = 0; + width = ddsd.Width; + height = ddsd.Height; + pitch = lrSurf.Pitch; + WriteBuffer = new char[width * height * 3]; + + // Setup the file headers + ((char*)&(fh.bfType))[0] = 'B'; + ((char*)&(fh.bfType))[1] = 'M'; + fh.bfSize = (long)(sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) + width * height * 3); + fh.bfReserved1 = 0; + fh.bfReserved2 = 0; + fh.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER); + bi.biSize = sizeof(BITMAPINFOHEADER); + bi.biWidth = width; + bi.biHeight = height; + bi.biPlanes = 1; + bi.biBitCount = 24; + bi.biCompression = BI_RGB; + bi.biSizeImage = 0; + bi.biXPelsPerMeter = 10000; + bi.biYPelsPerMeter = 10000; + bi.biClrUsed = 0; + bi.biClrImportant = 0; + + fwrite(&fh, sizeof(BITMAPFILEHEADER), 1, file); + fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, file); + + char *Bitmap_in = (char*)lrSurf.pBits; + + for(int y = height - 1; y >= 0; y--) + { + for(int x = 0; x < width; x++) + { + outpixel = *((DWORD *)(Bitmap_in + x * 4 + y * pitch)); //Load a word + + //Load up the Blue component and output it + outbyte = (((outpixel)&0x000000ff));//blue + WriteBuffer [BufferIndex++] = outbyte; + + //Load up the green component and output it + outbyte = (((outpixel>>8)&0x000000ff)); + WriteBuffer [BufferIndex++] = outbyte; + + //Load up the red component and output it + outbyte = (((outpixel>>16)&0x000000ff)); + WriteBuffer [BufferIndex++] = outbyte; + } + } + + //At this point the buffer should be full, so just write it out + fwrite(WriteBuffer, BufferIndex, 1, file); + + //Now unlock the surface and we're done + pTempSurf->UnlockRect(); + pTempSurf->Release(); + + fclose(file); + + delete [] WriteBuffer; + return true; +} +#endif + +/* +================= +_fixupScreenCoords + +Clamp coords to screen dimensions and fix Y direction. +================= +*/ +static void _fixupScreenCoords(GLint& x, GLint& y, GLsizei& width, GLsizei& height) +{ + if (x < 0) x = 0; + else if (x > glConfig.vidWidth) x = glConfig.vidWidth; + if (y < 0) + { + +#ifdef _XBOX + height += y; +#endif + y = 0; + } + else if (y > glConfig.vidHeight) y = glConfig.vidHeight; + + if (width < 0) width = 0; +#ifdef _XBOX + else if (x + width > glConfig.vidWidth) width = glConfig.vidWidth - x; +#endif +// else if (x + width > glConfig.vidWidth) width = glConfig.vidWidth - x; + if (height < 0) height = 0; + else if (y + height > glConfig.vidHeight) height = glConfig.vidHeight - y; + + // GL and DX disagree on the direction of Y + y = glConfig.vidHeight - (y + height); +} + + +/* +================= +_convertCompare + +Convert GL compare function to DX function. +================= +*/ +static D3DCMPFUNC _convertCompare(GLenum func) +{ + switch (func) + { + case GL_NEVER: return D3DCMP_NEVER; + case GL_LESS: return D3DCMP_LESS; + case GL_EQUAL: return D3DCMP_EQUAL; + case GL_LEQUAL: return D3DCMP_LESSEQUAL; + case GL_GREATER: return D3DCMP_GREATER; + case GL_NOTEQUAL: return D3DCMP_NOTEQUAL; + case GL_GEQUAL: return D3DCMP_GREATEREQUAL; + default: case GL_ALWAYS: return D3DCMP_ALWAYS; + } +} + + +/* +================= +_convertBlendFactor + +Convert GL blend mode to DX blend mode. +================= +*/ +static D3DBLEND _convertBlendFactor(GLenum factor) +{ + switch (factor) + { + case GL_ZERO: return D3DBLEND_ZERO; + default: case GL_ONE: return D3DBLEND_ONE; + case GL_SRC_COLOR: return D3DBLEND_SRCCOLOR; + case GL_ONE_MINUS_SRC_COLOR: return D3DBLEND_INVSRCCOLOR; + case GL_SRC_ALPHA: return D3DBLEND_SRCALPHA; + case GL_ONE_MINUS_SRC_ALPHA: return D3DBLEND_INVSRCALPHA; + case GL_DST_COLOR: return D3DBLEND_DESTCOLOR; + case GL_ONE_MINUS_DST_COLOR: return D3DBLEND_INVDESTCOLOR; + case GL_DST_ALPHA: return D3DBLEND_DESTALPHA; + case GL_ONE_MINUS_DST_ALPHA: return D3DBLEND_INVDESTALPHA; + case GL_SRC_ALPHA_SATURATE: return D3DBLEND_SRCALPHASAT; + } +} + + +/* +================= +_convertPrimMode + +Convert GL primitive mode to DX primitive mode. +================= +*/ +static D3DPRIMITIVETYPE _convertPrimMode(GLenum mode) +{ + switch (mode) + { + case GL_POINTS: return D3DPT_POINTLIST; + case GL_LINES: return D3DPT_LINELIST; + case GL_LINE_STRIP: return D3DPT_LINESTRIP; + case GL_TRIANGLES: return D3DPT_TRIANGLELIST; + case GL_TRIANGLE_STRIP: return D3DPT_TRIANGLESTRIP; + case GL_TRIANGLE_FAN: return D3DPT_TRIANGLEFAN; +#ifdef _XBOX + case GL_QUADS: return D3DPT_QUADLIST; + case GL_QUAD_STRIP: return D3DPT_QUADSTRIP; +#else + case GL_QUADS: return D3DPT_TRIANGLELIST; + case GL_QUAD_STRIP: return D3DPT_TRIANGLESTRIP; +#endif + case GL_POLYGON: return D3DPT_TRIANGLEFAN; + default: assert(0); return D3DPT_TRIANGLEFAN; + } +} + + +/* +================= +_updateDrawStride + +Update the stride of the draw array based on +the number of vertex attributes. The stride +is in DWORDs. +================= +*/ +static void _updateDrawStride(GLint normal, GLint tex0, GLint tex1) +{ + glw_state->drawStride = 4; + if (normal) glw_state->drawStride += 3; + if (tex0) glw_state->drawStride += 2; + if (tex1) glw_state->drawStride += 2; +} + + +/* +================= +_updateShader + +Set the vertex shader based on the number +of texture coordinates. +================= +*/ +static void _updateShader(bool normal, bool tex0, bool tex1)//, bool tex2, bool tex3) +{ + DWORD mask = D3DFVF_XYZ; + if (normal) mask |= D3DFVF_NORMAL; + mask |= D3DFVF_DIFFUSE; + if (tex0 && !tex1) mask |= D3DFVF_TEX1; + else if (tex1) mask |= D3DFVF_TEX2; + +// if (mask != glw_state->shaderMask) +// { + glw_state->device->SetVertexShader(mask); + glw_state->shaderMask = mask; +// } +} + + +/* +================= +_getCurrentTexture + +Get the texture information for the currently +bound texture at a stage. +================= +*/ +static glwstate_t::TextureInfo* _getCurrentTexture(int stage) +{ + glwstate_t::texturexlat_t::iterator i = glw_state->textureXlat.find( + glw_state->currentTexture[stage]); + + if (i == glw_state->textureXlat.end()) return NULL; + + return &i->second; +} + + +/* +================= +_updateTextures + +Setup texture stages with color operations, filters +and wrapping modes as needed. +================= +*/ +static void _updateTextures(void) +{ + for (int t = 0; t < GLW_MAX_TEXTURE_STAGES; ++t) + { + if (glw_state->textureStageDirty[t]) + { + glw_state->textureStageDirty[t] = false; + + if (glw_state->textureStageEnable[t] && glw_state->currentTexture[t]) + { + glwstate_t::TextureInfo* info = _getCurrentTexture(t); + if (!info) continue; + + glw_state->device->SetTexture(t, info->mipmap); + glw_state->device->SetTextureStageState(t, D3DTSS_COLOROP, glw_state->textureEnv[t]); + + glw_state->device->SetTextureStageState(t, D3DTSS_COLORARG1, + D3DTA_TEXTURE); + glw_state->device->SetTextureStageState(t, D3DTSS_COLORARG2, + D3DTA_CURRENT); + glw_state->device->SetTextureStageState(t, D3DTSS_ALPHAOP, + glw_state->textureEnv[t]); + glw_state->device->SetTextureStageState(t, D3DTSS_ALPHAARG1, + D3DTA_TEXTURE); + glw_state->device->SetTextureStageState(t, D3DTSS_ALPHAARG2, + D3DTA_CURRENT); + + glw_state->device->SetTextureStageState(t, D3DTSS_MAXANISOTROPY, + info->anisotropy); + glw_state->device->SetTextureStageState(t, D3DTSS_MINFILTER, + info->minFilter); + glw_state->device->SetTextureStageState(t, D3DTSS_MIPFILTER, + info->mipFilter); + glw_state->device->SetTextureStageState(t, D3DTSS_MAGFILTER, + info->magFilter); + + glw_state->device->SetTextureStageState(t, D3DTSS_ADDRESSU, + info->wrapU); + glw_state->device->SetTextureStageState(t, D3DTSS_ADDRESSV, + info->wrapV); + + glw_state->device->SetTextureStageState(t, D3DTSS_TEXCOORDINDEX, t); + + glw_state->device->SetTextureStageState( t, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2 ); + + /*if(tess.shader) + { + if(tess.currentPass < tess.shader->numUnfoggedPasses) + { + if(tess.shader->stages[tess.currentPass].isEnvironment) + { + glw_state->device->SetTextureStageState(t, D3DTSS_TEXCOORDINDEX, t | D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR); + } + } + }*/ + } + else + { + glw_state->device->SetTexture(t, NULL); + glw_state->device->SetTextureStageState(t, D3DTSS_COLOROP, D3DTOP_DISABLE); + glw_state->device->SetTextureStageState(t, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + } + } + //else + //{ + // // Hard-wired check for turning on hardware environment mapping + // if( glw_state->textureStageEnable[t] && + // glw_state->currentTexture[t] && + // tess.shader && + // tess.currentPass < tess.shader->numUnfoggedPasses && + // tess.shader->stages[tess.currentPass].isEnvironment) + // { + // glw_state->device->SetTextureStageState(t, D3DTSS_TEXCOORDINDEX, t | D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR); + // } + //} + } +} + + +/* +================= +_updateMatrices + +Set the current projection and view transforms to +the matrices at the top of the relevant stacks. +================= +*/ +static void _updateMatrices(void) +{ + if(glw_state->matricesDirty[glwstate_t::MatrixMode_Projection]) + { + glw_state->device->SetTransform(D3DTS_PROJECTION, + glw_state->matrixStack[glwstate_t::MatrixMode_Projection]->GetTop()); + + glw_state->matricesDirty[glwstate_t::MatrixMode_Projection] = false; + } + + if(glw_state->matricesDirty[glwstate_t::MatrixMode_Model]) + { + glw_state->device->SetTransform(D3DTS_VIEW, + glw_state->matrixStack[glwstate_t::MatrixMode_Model]->GetTop()); + + glw_state->matricesDirty[glwstate_t::MatrixMode_Model] = false; + } + + if(glw_state->matricesDirty[glwstate_t::MatrixMode_Texture0]) + { + glw_state->device->SetTransform(D3DTS_TEXTURE0, + glw_state->matrixStack[glwstate_t::MatrixMode_Texture0]->GetTop()); + + glw_state->matricesDirty[glwstate_t::MatrixMode_Texture0] = false; + } + + if(glw_state->matricesDirty[glwstate_t::MatrixMode_Texture1]) + { + glw_state->device->SetTransform(D3DTS_TEXTURE1, + glw_state->matrixStack[glwstate_t::MatrixMode_Texture1]->GetTop()); + + glw_state->matricesDirty[glwstate_t::MatrixMode_Texture1] = false; + } +} + + +/* +================= +_getMaxVerts + +Calculate the maximum number of verts to draw +given a total number to draw, stride and max +packet size. +================= +*/ +static int _getMaxVerts(void) +{ + int max = glw_state->totalVertices; + if (max > GLW_MAX_DRAW_PACKET_SIZE / glw_state->drawStride) + { + max = GLW_MAX_DRAW_PACKET_SIZE / glw_state->drawStride; + } + return max; +} + +static int _getMaxIndices(void) +{ + int max = glw_state->totalIndices; + if(max > 1022) + max = 1022; + + return max; +} + +#ifdef _XBOX +/* +================= +_restartDrawPacket + +Encode a new draw packet header into the draw array. +================= +*/ +inline static DWORD* _restartDrawPacket(DWORD* packet, int verts) +{ + packet[0] = D3DPUSH_ENCODE(D3DPUSH_SET_BEGIN_END, 1); + packet[1] = glw_state->primitiveMode; + packet[2] = D3DPUSH_ENCODE( + D3DPUSH_NOINCREMENT_FLAG|D3DPUSH_INLINE_ARRAY, + glw_state->drawStride * verts); + return packet + 3; +} + +/* +================= +_terminateDrawPacket + +Finish up the last draw packet. +================= +*/ +inline static DWORD* _terminateDrawPacket(DWORD* packet) +{ + packet[0] = D3DPUSH_ENCODE(D3DPUSH_SET_BEGIN_END, 1); + packet[1] = 0; + return packet + 2; +} + +#define CMD_DRAW_INDEX_BATCH 0x1800 +inline static DWORD* _restartIndexPacket(DWORD* packet, int numIndices) +{ + packet[0] = D3DPUSH_ENCODE(D3DPUSH_SET_BEGIN_END, 1); + packet[1] = glw_state->primitiveMode; + packet[2] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | CMD_DRAW_INDEX_BATCH, numIndices / 2 ); + return packet + 3; +} + +inline static DWORD* _terminateIndexPacket(DWORD* packet) +{ + packet[0] = D3DPUSH_ENCODE(D3DPUSH_SET_BEGIN_END, 1); + packet[1] = 0; + return packet + 2; +} + + +/* +================= +_handleDrawOverflow + +Prevent a draw packet from getting too +big for the hardware by restarting it as needed. +================= +*/ +static void _handleDrawOverflow(void) +{ + if (glw_state->numVertices >= glw_state->maxVertices) + { + glw_state->drawArray += glw_state->numVertices * + glw_state->drawStride; + + glw_state->totalVertices -= glw_state->numVertices; + glw_state->maxVertices = _getMaxVerts(); + glw_state->numVertices = 0; + + glw_state->drawArray = _restartDrawPacket( + glw_state->drawArray, glw_state->maxVertices); + } +} +#else _XBOX +inline static DWORD* _restartDrawPacket(DWORD* packet, int verts) +{ + return packet; +} + +inline static DWORD* _terminateDrawPacket(DWORD* packet) +{ + return packet; +} + +static void _handleDrawOverflow(void) +{ +} +#endif _XBOX + + +/* +================= +_vertexElement + +Copy position information from the source vertex +array into a draw array. +================= +*/ +#define _vertexElement(push, i) \ +{ \ + DWORD* vert = (DWORD*)((BYTE*)glw_state->vertexPointer + \ + (i) * glw_state->vertexStride); \ + (push)[0] = vert[0]; \ + (push)[1] = vert[1]; \ + (push)[2] = vert[2]; \ +} + +/* +================= +_colorElement + +Copy color information from the source color +array into a draw array. +================= +*/ +//#define _colorElement(push, i) \ +//{ \ +// DWORD col = *(DWORD*)((BYTE*)glw_state->colorPointer + \ +// (i) * glw_state->colorStride); \ +// (push)[0] = \ +// ((col & 0xFF000000) >> 0) | \ +// ((col & 0x00FF0000) >> 16) | \ +// ((col & 0x0000FF00) << 0) | \ +// ((col & 0x000000FF) << 16); \ +//} +#define _colorElement(push, i) \ +{ \ + DWORD col = *(DWORD*)((BYTE*)glw_state->colorPointer + \ + (i) * glw_state->colorStride); \ + (push)[0] = col; \ +} + +/* +================= +_texCoordElement + +Copy tex coord information from the source tex coord +array into a draw array. +================= +*/ +#define _texCoordElement(push, i, t) \ +{ \ + DWORD* tc = (DWORD*)((BYTE*)glw_state->texCoordPointer[t] + \ + (i) * glw_state->texCoordStride[t]); \ + (push)[0] = tc[0]; \ + (push)[1] = tc[1]; \ +} + +/* +================= +_normalElement + + Copy normal information from the source normal + array into a draw array +================= +*/ +#define _normalElement(push, i) \ +{ \ + DWORD* norm = (DWORD*)((BYTE*)glw_state->normalPointer + \ + (i) * glw_state->normalStride); \ + (push)[0] = norm[0]; \ + (push)[1] = norm[1]; \ + (push)[2] = norm[2]; \ +} + + +#define _tangentElement(push, i) \ +{ \ + DWORD* tang = (DWORD*)((BYTE*)&tess.tangent[i]); \ + (push)[0] = tang[0]; \ + (push)[1] = tang[1]; \ + (push)[2] = tang[2]; \ +} + + + +/* +========================================================= +FAST INDEXED GEOMETRY DRAW LOOPS + +Used by core draw routines to quickly copy +geometry from various source arrays to main +draw array. +========================================================= +*/ +static void _drawElementsV(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + push[3] = glw_state->currentColor; + push += 4; + } +} + +static void _drawElementsVN(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _normalElement(&push[3], indices[i]); + push[6] = glw_state->currentColor; + push += 7; + } +} + +static void _drawElementsVC(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _colorElement(&push[3], indices[i]); + push += 4; + } +} + +static void _drawElementsVCN(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _normalElement(&push[3], indices[i]); + _colorElement(&push[6], indices[i]); + push += 7; + } +} + +static void _drawElementsVCT(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _colorElement(&push[3], indices[i]); + _texCoordElement(&push[4], indices[i], 0); + push += 6; + } +} + +static void _drawElementsVCNT(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _normalElement(&push[3], indices[i]); + _colorElement(&push[6], indices[i]); + _texCoordElement(&push[7], indices[i], 0); + push += 9; + } +} + +static void _drawElementsVCTT(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _colorElement(&push[3], indices[i]); + _texCoordElement(&push[4], indices[i], 0); + _texCoordElement(&push[6], indices[i], 1); + push += 8; + } +} + +static void _drawElementsVCNTT(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _normalElement(&push[3], indices[i]); + _colorElement(&push[6], indices[i]); + _texCoordElement(&push[7], indices[i], 0); + _texCoordElement(&push[9], indices[i], 1); + push += 11; + } +} + +static void _drawElementsVT(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + push[3] = glw_state->currentColor; + _texCoordElement(&push[4], indices[i], 0); + push += 6; + } +} + +static void _drawElementsVNT(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _normalElement(&push[3], indices[i]); + push[6] = glw_state->currentColor; + _texCoordElement(&push[7], indices[i], 0); + push += 9; + } +} + +static void _drawElementsVTT(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + push[3] = glw_state->currentColor; + _texCoordElement(&push[4], indices[i], 0); + _texCoordElement(&push[6], indices[i], 1); + push += 8; + } +} + +static void _drawElementsVNTT(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for (int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _normalElement(&push[3], indices[i]); + push[6] = glw_state->currentColor; + _texCoordElement(&push[7], indices[i], 0); + _texCoordElement(&push[9], indices[i], 1); + push += 11; + } +} + + +static void _drawElementsLightShader(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for(int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _normalElement(&push[3], indices[i]); + _texCoordElement(&push[6], indices[i], 0); + _tangentElement(&push[8], indices[i]); + push += 11; + } +} + +static void _drawElementsBumpShader(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for(int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _normalElement(&push[3], indices[i]); + _texCoordElement(&push[6], indices[i], 0); + _texCoordElement(&push[8], indices[i], 1); + _tangentElement(&push[10], indices[i]); + push += 13; + } +} + +static void _drawElementsEnvShader(GLsizei count, const GLushort* indices) +{ + DWORD* push = glw_state->drawArray; + for(int i = 0; i < count; ++i) + { + _vertexElement(&push[0], indices[i]); + _normalElement(&push[3], indices[i]); + push += 6; + } +} + +typedef void(*drawelemfunc_t)(GLsizei, const GLushort*); +static drawelemfunc_t _drawElementFuncTable[12] = +{ + _drawElementsV, + _drawElementsVN, + _drawElementsVT, + _drawElementsVNT, + _drawElementsVTT, + _drawElementsVNTT, + _drawElementsVC, + _drawElementsVCN, + _drawElementsVCT, + _drawElementsVCNT, + _drawElementsVCTT, + _drawElementsVCNTT, +}; + + + +/* +========================================================= +FAST GEOMETRY DRAW LOOPS + +Used by core draw routines to quickly copy +geometry from various source arrays to main +draw array. +========================================================= +*/ +static void _drawArraysV(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + push[3] = glw_state->currentColor; + push += 4; + } +} + +static void _drawArraysVN(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + _normalElement(&push[3], i); + push[6] = glw_state->currentColor; + push += 7; + } +} + +static void _drawArraysVC(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + _colorElement(&push[3], i); + push += 4; + } +} + +static void _drawArraysVCN(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + _normalElement(&push[3], i); + _colorElement(&push[6], i); + push += 7; + } +} + +static void _drawArraysVCT(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + _colorElement(&push[3], i); + _texCoordElement(&push[4], i, 0); + push += 6; + } +} + +static void _drawArraysVCNT(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + _normalElement(&push[3], i); + _colorElement(&push[6], i); + _texCoordElement(&push[7], i, 0); + push += 9; + } +} + +static void _drawArraysVCTT(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + _colorElement(&push[3], i); + _texCoordElement(&push[4], i, 0); + _texCoordElement(&push[6], i, 1); + push += 8; + } +} + +static void _drawArraysVCNTT(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + _normalElement(&push[3], i); + _colorElement(&push[6], i); + _texCoordElement(&push[7], i, 0); + _texCoordElement(&push[9], i, 1); + push += 11; + } +} + +static void _drawArraysVT(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + push[3] = glw_state->currentColor; + _texCoordElement(&push[4], i, 0); + push += 6; + } +} + +static void _drawArraysVNT(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + _normalElement(&push[3], i); + push[6] = glw_state->currentColor; + _texCoordElement(&push[7], i, 0); + push += 9; + } +} + +static void _drawArraysVTT(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + push[3] = glw_state->currentColor; + _texCoordElement(&push[4], i, 0); + _texCoordElement(&push[6], i, 1); + push += 8; + } +} + +static void _drawArraysVNTT(GLsizei first, GLsizei last) +{ + DWORD* push = glw_state->drawArray; + for (int i = first; i < last; ++i) + { + _vertexElement(&push[0], i); + _normalElement(&push[3], i); + push[6] = glw_state->currentColor; + _texCoordElement(&push[7], i, 0); + _texCoordElement(&push[9], i, 1); + push += 11; + } +} + +typedef void(*drawarrayfunc_t)(GLsizei, GLsizei); +static drawarrayfunc_t _drawArrayFuncTable[12] = +{ + _drawArraysV, + _drawArraysVN, + _drawArraysVT, + _drawArraysVNT, + _drawArraysVTT, + _drawArraysVNTT, + _drawArraysVC, + _drawArraysVCN, + _drawArraysVCT, + _drawArraysVCNT, + _drawArraysVCTT, + _drawArraysVCNTT, +}; + + +/* +================= +_getDrawFunc + +Figure which drawing function we need based on +what vertex components we have. Use the returned +integer to index the draw function tables. +================= +*/ +static int _getDrawFunc(void) +{ + int func = 0; + if (glw_state->colorArrayState) func += 6; + if (glw_state->texCoordArrayState[0]) func += 2; + if (glw_state->texCoordArrayState[1]) func += 2; + if (glw_state->normalArrayState) ++func; + return func; +} + + +static void dllAccum(GLenum op, GLfloat value) +{ + assert(false); +} + +static void dllAlphaFunc(GLenum func, GLclampf ref) +{ + D3DCMPFUNC f = _convertCompare(func); + glw_state->device->SetRenderState(D3DRS_ALPHAFUNC, f); + glw_state->device->SetRenderState(D3DRS_ALPHAREF, (DWORD)(ref * 255.)); +} + +GLboolean dllAreTexturesResident(GLsizei n, const GLuint *textures, GLboolean *residences) +{ + assert(false); + return 1; +} + +static void dllArrayElement(GLint i) +{ + assert(glw_state->inDrawBlock); + + _handleDrawOverflow(); + + DWORD* push = &glw_state->drawArray[glw_state->numVertices * + glw_state->drawStride]; + + _vertexElement(push, i); + push += 3; + + if (glw_state->colorArrayState) + { + _colorElement(push, i); + ++push; + } + else + { + *push++ = glw_state->currentColor; + } + + for (int t = 0; t < GLW_MAX_TEXTURE_STAGES; ++t) + { + if (glw_state->texCoordArrayState[t]) + { + _texCoordElement(push, i, t); + push += 2; + } + } + + ++glw_state->numVertices; +} + +// EXTENSION: Begin a drawing block with at verts vertices +static void dllBeginEXT(GLenum mode, GLint verts, GLint colors, GLint normals, GLint tex0, GLint tex1)//, GLint tex2, GLint tex3) +{ + assert(!glw_state->inDrawBlock); + + // start the draw block + glw_state->inDrawBlock = true; + glw_state->primitiveMode = _convertPrimMode(mode); + + // update DX with any pending state changes + _updateDrawStride(normals, tex0, tex1);//, tex2, tex3); + _updateShader(normals, tex0, tex1);//, tex2, tex3); + _updateTextures(); + _updateMatrices(); + + // set vertex counters + glw_state->numVertices = 0; + glw_state->totalVertices = verts; + glw_state->maxVertices = _getMaxVerts(); + +#ifdef _XBOX + // open a draw packet + //int num_packets = ((verts * glw_state->drawStride) / GLW_MAX_DRAW_PACKET_SIZE) + 1; + int num_packets; + if(glw_state->maxVertices == 0) { + num_packets = 1; + } else { + num_packets = (verts / glw_state->maxVertices) + (!!(verts % glw_state->maxVertices)); + } + int cmd_size = num_packets * 3; + int vert_size = glw_state->drawStride * verts; + + glw_state->device->BeginPush(vert_size + cmd_size + 2, + &glw_state->drawArray); + + glw_state->drawArray = _restartDrawPacket( + glw_state->drawArray, glw_state->maxVertices); +#endif +} + +static void dllBegin(GLenum mode) +{ + assert(0); +} + +// EXTENSION: Start a new drawing frame +GLboolean dllBeginFrame(void) +{ + GLboolean result = glw_state->device->BeginScene() == D3D_OK; + return result; +} + +// EXTENSION: Begin shadow draw mode +static void dllBeginShadow(void) +{ + //Intentionally left blank +} + +static void dllBindTexture(GLenum target, GLuint texture) +{ + assert(target == GL_TEXTURE_2D); + + if (glw_state->currentTexture[glw_state->serverTU] != texture) + { + glw_state->currentTexture[glw_state->serverTU] = texture; + glw_state->textureStageDirty[glw_state->serverTU] = true; + } +} + +static void dllBitmap(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap) +{ + assert(false); +} + +static void dllBlendFunc(GLenum sfactor, GLenum dfactor) +{ + D3DBLEND s = _convertBlendFactor(sfactor); + D3DBLEND d = _convertBlendFactor(dfactor); + + glw_state->device->SetRenderState(D3DRS_SRCBLEND, s); + glw_state->device->SetRenderState(D3DRS_DESTBLEND, d); +} + +static void dllCallList(GLuint lnum) +{ + assert(0); +} + +static void dllCallLists(GLsizei n, GLenum type, const GLvoid *lists) +{ + assert(0); +} + +static void dllClear(GLbitfield mask) +{ + DWORD m = 0; + + if (mask & GL_COLOR_BUFFER_BIT) m |= D3DCLEAR_TARGET; + if (mask & GL_STENCIL_BUFFER_BIT) m |= D3DCLEAR_STENCIL; + +#ifdef _XBOX + // Clearing stencil when clearing depth buffer + // is faster on Xbox than just clearing depth alone. + if (mask & GL_DEPTH_BUFFER_BIT) m |= D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL; +#else + if (mask & GL_DEPTH_BUFFER_BIT) m |= D3DCLEAR_ZBUFFER; +#endif + + glw_state->device->Clear(0, NULL, m, glw_state->clearColor, + glw_state->clearDepth, glw_state->clearStencil); +} + +static void dllClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + assert(0); +} + +static void dllClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) +{ + glw_state->clearColor = D3DCOLOR_COLORVALUE(red, green, blue, alpha); +} + +static void dllClearDepth(GLclampd depth) +{ + glw_state->clearDepth = depth; +} + +static void dllClearIndex(GLfloat c) +{ + assert(0); +} + +static void dllClearStencil(GLint s) +{ + glw_state->clearStencil = s; +} + +static void dllClipPlane(GLenum plane, const GLdouble *equation) +{ + //FIXME +} + +static void setIntColor(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha) +{ + glw_state->currentColor = D3DCOLOR_RGBA(red, green, blue, alpha); +} + +static void setFloatColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + glw_state->currentColor = D3DCOLOR_COLORVALUE(red, green, blue, alpha); +} + +static void dllColor3b(GLbyte red, GLbyte green, GLbyte blue) +{ + setIntColor(red, green, blue, 127); +} + +static void dllColor3bv(const GLbyte *v) +{ + setIntColor(v[0], v[1], v[2], 127); +} + +static void dllColor3d(GLdouble red, GLdouble green, GLdouble blue) +{ + setFloatColor(red, green, blue, 1.f); +} + +static void dllColor3dv(const GLdouble *v) +{ + setFloatColor(v[0], v[1], v[2], 1.f); +} + +static void dllColor3f(GLfloat red, GLfloat green, GLfloat blue) +{ + setFloatColor(red, green, blue, 1.f); +} + +static void dllColor3fv(const GLfloat *v) +{ + setFloatColor(v[0], v[1], v[2], 1.f); +} + +static void dllColor3i(GLint red, GLint green, GLint blue) +{ + setIntColor(red, green, blue, 127); +} + +static void dllColor3iv(const GLint *v) +{ + setIntColor(v[0], v[1], v[2], 127); +} + +static void dllColor3s(GLshort red, GLshort green, GLshort blue) +{ + setIntColor(red, green, blue, 127); +} + +static void dllColor3sv(const GLshort *v) +{ + setIntColor(v[0], v[1], v[2], 127); +} + +static void dllColor3ub(GLubyte red, GLubyte green, GLubyte blue) +{ + setIntColor(red, green, blue, 127); +} + +static void dllColor3ubv(const GLubyte *v) +{ + setIntColor(v[0], v[1], v[2], 127); +} + +static void dllColor3ui(GLuint red, GLuint green, GLuint blue) +{ + setIntColor(red, green, blue, 127); +} + +static void dllColor3uiv(const GLuint *v) +{ + setIntColor(v[0], v[1], v[2], 127); +} + +static void dllColor3us(GLushort red, GLushort green, GLushort blue) +{ + setIntColor(red, green, blue, 127); +} + +static void dllColor3usv(const GLushort *v) +{ + setIntColor(v[0], v[1], v[2], 127); +} + +static void dllColor4b(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha) +{ + setIntColor(red, green, blue, alpha); +} + +static void dllColor4bv(const GLbyte *v) +{ + setIntColor(v[0], v[1], v[2], v[3]); +} + +static void dllColor4d(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha) +{ + setFloatColor(red, green, blue, alpha); +} + +static void dllColor4dv(const GLdouble *v) +{ + setFloatColor(v[0], v[1], v[2], v[3]); +} + +static void dllColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) +{ + setFloatColor(red, green, blue, alpha); +} + +static void dllColor4fv(const GLfloat *v) +{ + setFloatColor(v[0], v[1], v[2], v[3]); +} + +static void dllColor4i(GLint red, GLint green, GLint blue, GLint alpha) +{ + setIntColor(red, green, blue, alpha); +} + +static void dllColor4iv(const GLint *v) +{ + setIntColor(v[0], v[1], v[2], v[3]); +} + +static void dllColor4s(GLshort red, GLshort green, GLshort blue, GLshort alpha) +{ + setIntColor(red, green, blue, alpha); +} + +static void dllColor4sv(const GLshort *v) +{ + setIntColor(v[0], v[1], v[2], v[3]); +} + +static void dllColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) +{ + setIntColor(red, green, blue, alpha); +} + +static void dllColor4ubv(const GLubyte *v) +{ + setIntColor(v[0], v[1], v[2], v[3]); +} + +static void dllColor4ui(GLuint red, GLuint green, GLuint blue, GLuint alpha) +{ + setIntColor(red, green, blue, alpha); +} + +static void dllColor4uiv(const GLuint *v) +{ + setIntColor(v[0], v[1], v[2], v[3]); +} + +static void dllColor4us(GLushort red, GLushort green, GLushort blue, GLushort alpha) +{ + setIntColor(red, green, blue, alpha); +} + +static void dllColor4usv(const GLushort *v) +{ + setIntColor(v[0], v[1], v[2], v[3]); +} + +static void dllColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) +{ + DWORD m = 0; + if (red) m |= D3DCOLORWRITEENABLE_RED; + if (green) m |= D3DCOLORWRITEENABLE_GREEN; + if (blue) m |= D3DCOLORWRITEENABLE_BLUE; + if (alpha) m |= D3DCOLORWRITEENABLE_ALPHA; + glw_state->device->SetRenderState(D3DRS_COLORWRITEENABLE, m); +} + +static void dllColorMaterial(GLenum face, GLenum mode) +{ + assert(0); +} + +static void dllColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) +{ + assert(!glw_state->inDrawBlock); + assert(size == 4 && type == GL_UNSIGNED_BYTE); + + stride = (stride == 0) ? sizeof(GLint) : stride; + + glw_state->colorPointer = pointer; + glw_state->colorStride = stride; +} + +static void dllCopyPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type) +{ + assert(0); +} + +static void dllCopyTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border) +{ + assert(0); +} + +/********** +copies a portion of the backbuffer to the current texture. +the current texture must be a linear format texture, if +a swizzled texture format is needed, use +dllCopyBackBufferToTexEXT +**********/ +static void dllCopyTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) +{ + // check to make sure everything passed in is supported + assert((target == GL_TEXTURE_2D) && (level == 0) && (border == 0)); + + // locals + RECT rSrc; + POINT ptUpperLeft; + LPDIRECT3DSURFACE8 tSurf; + LPDIRECT3DSURFACE8 backbuffer; + glwstate_t::TextureInfo* tex; + HRESULT res; + + // get the current texture + tex = _getCurrentTexture(glw_state->serverTU); + if (tex == NULL) + { + return; + } + + // set up the source rectangle + rSrc.left = x; + rSrc.right = x + width; + rSrc.top = (480 - y) - height; + rSrc.bottom = (480 - y); + + // set up the target point + ptUpperLeft.x = 0; + ptUpperLeft.y = 0; + + // attach the current texture to a surface + tex->mipmap->GetSurfaceLevel(0, &tSurf); + + // attach the back buffer to a surface + res = glw_state->device->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backbuffer); + + // copy the data + res = glw_state->device->CopyRects(backbuffer, &rSrc, 0, tSurf, &ptUpperLeft); + + // release surfaces + tSurf->Release(); + backbuffer->Release(); +} + +static void dllCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) +{ + assert(0); +} + +static void dllCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) +{ + assert(0); +} + +static void dllCullFace(GLenum mode) +{ + switch (mode) + { + default: case GL_BACK: glw_state->cullMode = D3DCULL_CW; break; + case GL_FRONT: glw_state->cullMode = D3DCULL_CCW; break; + } + + glw_state->device->SetRenderState(D3DRS_CULLMODE, glw_state->cullMode); +} + +static void dllDeleteLists(GLuint lnum, GLsizei range) +{ + assert(0); +} + +static void dllDeleteTextures(GLsizei n, const GLuint *textures) +{ + glw_state->textureStageDirty[0] = true; + glw_state->textureStageDirty[1] = true; + glw_state->device->SetTexture(0, NULL); + glw_state->device->SetTexture(1, NULL); +// glw_state->device->SetTexture(2, NULL); +// glw_state->device->SetTexture(3, NULL); + + for (int t = 0; t < n; ++t) + { + glwstate_t::texturexlat_t::iterator i = + glw_state->textureXlat.find(textures[t]); + + if (i != glw_state->textureXlat.end()) + { +#if MEMORY_PROFILE + texMemSize -= getTexMemSize(i->second.mipmap); +#endif + i->second.mipmap->BlockUntilNotBusy(); + delete i->second.mipmap; + glw_state->textureXlat.erase(i); + } + } +} + +static void dllDepthFunc(GLenum func) +{ + D3DCMPFUNC f = _convertCompare(func); + glw_state->device->SetRenderState(D3DRS_ZFUNC, f); +} + +static void dllDepthMask(GLboolean flag) +{ + glw_state->device->SetRenderState(D3DRS_ZWRITEENABLE, flag); +} + +static void dllDepthRange(GLclampd zNear, GLclampd zFar) +{ + glw_state->viewport.MinZ = zNear; + glw_state->viewport.MaxZ = zFar; + glw_state->device->SetViewport(&glw_state->viewport); +} + +#ifdef _XBOX +static void setPresent(bool vsync) +{ + //extern void ShowOSMemory(); + //ShowOSMemory(); + + D3DPRESENT_PARAMETERS pp; + pp.BackBufferWidth = glConfig.vidWidth; + pp.BackBufferHeight = glConfig.vidHeight; + pp.BackBufferFormat = D3DFMT_X8R8G8B8; + pp.BackBufferCount = 1; + pp.MultiSampleType = D3DMULTISAMPLE_NONE; //D3DMULTISAMPLE_4_SAMPLES_SUPERSAMPLE_LINEAR; + pp.SwapEffect = D3DSWAPEFFECT_DISCARD; + pp.hDeviceWindow = 0; + pp.Windowed = FALSE; + pp.EnableAutoDepthStencil = TRUE; + pp.AutoDepthStencilFormat = D3DFMT_D24S8; + pp.Flags = 0; + pp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; + pp.FullScreen_PresentationInterval = + vsync ? D3DPRESENT_INTERVAL_DEFAULT : D3DPRESENT_INTERVAL_IMMEDIATE; + pp.BufferSurfaces[0] = pp.BufferSurfaces[1] = pp.BufferSurfaces[2] = 0; + pp.DepthStencilSurface = 0; + glw_state->device->PersistDisplay(); + glw_state->device->Reset(&pp); + + //ShowOSMemory(); +} +#endif + +static void setCap(GLenum cap, bool flag) +{ + switch (cap) + { + case GL_ALPHA_TEST: glw_state->device->SetRenderState(D3DRS_ALPHATESTENABLE, flag); break; + case GL_BLEND: glw_state->device->SetRenderState(D3DRS_ALPHABLENDENABLE, flag); break; + case GL_CULL_FACE: + glw_state->cullEnable = flag; + glw_state->device->SetRenderState(D3DRS_CULLMODE, + flag ? glw_state->cullMode : D3DCULL_NONE); + break; + case GL_DEPTH_TEST: glw_state->device->SetRenderState(D3DRS_ZENABLE, flag); break; + case GL_LIGHTING: glw_state->device->SetRenderState(D3DRS_LIGHTING, flag); break; +#ifdef _XBOX + case GL_POLYGON_OFFSET_POINT: + glw_state->device->SetRenderState(D3DRS_POINTOFFSETENABLE, flag); + break; + case GL_POLYGON_OFFSET_LINE: + glw_state->device->SetRenderState(D3DRS_WIREFRAMEOFFSETENABLE, flag); + break; + case GL_POLYGON_OFFSET_FILL: + glw_state->device->SetRenderState(D3DRS_SOLIDOFFSETENABLE, flag); + break; + case GL_SCISSOR_TEST: + glw_state->scissorEnable = flag; + glw_state->device->SetScissors(flag ? 1 : 0, FALSE, &glw_state->scissorBox); + break; +#endif + case GL_STENCIL_TEST: glw_state->device->SetRenderState(D3DRS_STENCILENABLE, flag); break; + case GL_TEXTURE_2D: + glw_state->textureStageEnable[glw_state->serverTU] = flag; + glw_state->textureStageDirty[glw_state->serverTU] = true; + break; + case GL_FOG: + glw_state->device->SetRenderState(D3DRS_FOGENABLE, flag); + break; +#ifdef _XBOX + case GL_VSYNC: + setPresent(flag); + break; +#endif + default: break; + } +} + +static void dllDisable(GLenum cap) +{ + setCap(cap, false); +} + +static void setArrayState(GLenum cap, bool state) +{ + switch (cap) + { + case GL_COLOR_ARRAY: glw_state->colorArrayState = state; break; + case GL_TEXTURE_COORD_ARRAY: glw_state->texCoordArrayState[glw_state->clientTU] = state; break; + case GL_VERTEX_ARRAY: glw_state->vertexArrayState = state; break; + case GL_NORMAL_ARRAY: glw_state->normalArrayState = state; break; + } +} + +static void dllDisableClientState(GLenum array) +{ + assert(!glw_state->inDrawBlock); + setArrayState(array, false); +} + +#ifdef _WINDOWS +static void _convertQuadsToTris(GLint first, GLsizei count) +{ + glw_state->vertexPointerBack = glw_state->vertexPointer; + glw_state->normalPointerBack = glw_state->normalPointer; + glw_state->colorPointerBack = glw_state->colorPointer; + glw_state->texCoordPointerBack[0] = glw_state->texCoordPointer[0]; + glw_state->texCoordPointerBack[1] = glw_state->texCoordPointer[1]; + + { + glw_state->vertexPointer = + Z_Malloc(count * glw_state->vertexStride * 3 / 2, + TAG_TEMP_WORKSPACE, qfalse); + for (int i = 0; i < count; i += 4) + { + int stride = glw_state->vertexStride / sizeof(float); + float* dst = (float*)glw_state->vertexPointer + (i * 3 / 2) * stride; + const float* src = (const float*)glw_state->vertexPointerBack + + (first + i) * stride; + + for (int j = 0; j < 3; ++j) + { + dst[0 * stride + j] = src[0 * stride + j]; + dst[1 * stride + j] = src[1 * stride + j]; + dst[2 * stride + j] = src[2 * stride + j]; + dst[3 * stride + j] = src[0 * stride + j]; + dst[4 * stride + j] = src[2 * stride + j]; + dst[5 * stride + j] = src[3 * stride + j]; + } + } + } + + if (glw_state->normalArrayState) + { + glw_state->normalPointer = + Z_Malloc(count * glw_state->normalStride * 3 / 2, + TAG_TEMP_WORKSPACE, qfalse); + + for (int i = 0; i < count; i += 4) + { + int stride = glw_state->normalStride / sizeof(float); + float* dst = (float*)glw_state->normalPointer + (i * 3 / 2) * stride; + const float* src = (const float*)glw_state->normalPointerBack + + (first + i) * stride; + + for (int j = 0; j < 3; ++j) + { + dst[0 * stride + j] = src[0 * stride + j]; + dst[1 * stride + j] = src[1 * stride + j]; + dst[2 * stride + j] = src[2 * stride + j]; + dst[3 * stride + j] = src[0 * stride + j]; + dst[4 * stride + j] = src[2 * stride + j]; + dst[5 * stride + j] = src[3 * stride + j]; + } + } + } + + if (glw_state->colorArrayState) + { + glw_state->colorPointer = + Z_Malloc(count * glw_state->colorStride * 3 / 2, + TAG_TEMP_WORKSPACE, qfalse); + + for (int i = 0; i < count; i += 4) + { + int stride = glw_state->colorStride / sizeof(DWORD); + DWORD* dst = (DWORD*)glw_state->colorPointer + (i * 3 / 2) * stride; + const DWORD* src = (const DWORD*)glw_state->colorPointerBack + + (first + i) * stride; + + dst[0 * stride] = src[0 * stride]; + dst[1 * stride] = src[1 * stride]; + dst[2 * stride] = src[2 * stride]; + dst[3 * stride] = src[0 * stride]; + dst[4 * stride] = src[2 * stride]; + dst[5 * stride] = src[3 * stride]; + } + } + + for (int t = 0; t < GLW_MAX_TEXTURE_STAGES; ++t) + { + if (glw_state->texCoordArrayState[t]) + { + glw_state->texCoordPointer[t] = + Z_Malloc(count * glw_state->texCoordStride[t] * 3 / 2, + TAG_TEMP_WORKSPACE, qfalse); + + for (int i = 0; i < count; i += 4) + { + int stride = glw_state->texCoordStride[t] / sizeof(float); + float* dst = (float*)glw_state->texCoordPointer[t] + (i * 3 / 2) * stride; + const float* src = (const float*)glw_state->texCoordPointerBack[t] + + (first + i) * stride; + + for (int j = 0; j < 2; ++j) + { + dst[0 * stride + j] = src[0 * stride + j]; + dst[1 * stride + j] = src[1 * stride + j]; + dst[2 * stride + j] = src[2 * stride + j]; + dst[3 * stride + j] = src[0 * stride + j]; + dst[4 * stride + j] = src[2 * stride + j]; + dst[5 * stride + j] = src[3 * stride + j]; + } + } + } + } +} + +static void _cleanupQuadsToTris(void) +{ + Z_Free(const_cast(glw_state->vertexPointer)); + glw_state->vertexPointer = glw_state->vertexPointerBack; + + if (glw_state->normalArrayState) + { + Z_Free(const_cast(glw_state->normalPointer)); + glw_state->normalPointer = glw_state->normalPointerBack; + } + + if (glw_state->colorArrayState) + { + Z_Free(const_cast(glw_state->colorPointer)); + glw_state->colorPointer = glw_state->colorPointerBack; + } + + for (int t = 0; t < GLW_MAX_TEXTURE_STAGES; ++t) + { + if (glw_state->texCoordArrayState[t]) + { + Z_Free(const_cast(glw_state->texCoordPointer[t])); + glw_state->texCoordPointer[t] = glw_state->texCoordPointerBack[t]; + } + } +} +#endif + +// NOTE: This is a core draw routine. It should be fast. +static void dllDrawArrays(GLenum mode, GLint first, GLsizei count) +{ +#ifdef _WINDOWS + if (mode == GL_QUADS) + { + _convertQuadsToTris(first, count); + count = count * 3 / 2; + first = 0; + } +#endif + + // start the draw mode + qglBeginEXT(mode, count, glw_state->colorArrayState ? count : 0, + glw_state->normalArrayState ? count : 0, + glw_state->texCoordArrayState[0] ? count : 0, + glw_state->texCoordArrayState[1] ? count : 0); + + // get the draw function we need + drawarrayfunc_t func = _drawArrayFuncTable[_getDrawFunc()]; + +#ifndef _XBOX + DWORD* base = glw_state->drawArray; +#endif + + int inc = glw_state->maxVertices; + // loop taking care not to draw too much at a time + for (int start = first; ; start += inc)//glw_state->maxVertices) + { + // draw glw_state->maxVertices amount of geometry + func(start, start + glw_state->maxVertices); + + // are we done yet? + glw_state->totalVertices -= glw_state->maxVertices; + if (glw_state->totalVertices <= 0) + { + glw_state->numVertices = glw_state->maxVertices; + break; + } + + // ready for another cycle + glw_state->drawArray += glw_state->maxVertices * + glw_state->drawStride; + glw_state->maxVertices = _getMaxVerts(); + + glw_state->drawArray = _restartDrawPacket( + glw_state->drawArray, glw_state->maxVertices); + } + +#ifndef _XBOX + glw_state->drawArray = base; +#endif + +#ifdef _WINDOWS + if (mode == GL_QUADS) + { + _cleanupQuadsToTris(); + } +#endif + + // finish up the draw + qglEnd(); +} + +static void dllDrawBuffer(GLenum mode) +{ + //FIXME +} + + +static void PushIndices(GLsizei count, const GLushort *indices) +{ + // open the index packet + // can only send 2047 indices thru at a time + // BUT, Microsoft recommends 511 pairs at a time (?) + int num_packets, numpairs; + bool singleindex = false; + + numpairs = count / 2; + + if(numpairs <= 511) + { + num_packets = 1; + + if(glw_state->maxIndices % 2) + { + glw_state->maxIndices -= 1; + singleindex = true; + } + } else + { + num_packets = (count / glw_state->maxIndices) + (!!(count % glw_state->maxIndices)); + } + + glw_state->drawArray = _restartIndexPacket(glw_state->drawArray, glw_state->maxIndices); + + int inc = glw_state->maxIndices; + for (int start = 0; ; start += inc) + { + // memcpy is faster than looping copy: + memcpy( glw_state->drawArray, indices+start, glw_state->maxIndices * sizeof(WORD) ); + glw_state->drawArray += glw_state->maxIndices / 2; + + /* + for(int i = start; i < start + glw_state->maxIndices; i += 2) + { + *glw_state->drawArray++ = (DWORD)(((WORD)indices[i + 1] << 16) + (WORD)indices[i]); + } + */ + + // are we done yet? + glw_state->totalIndices -= glw_state->maxIndices; + if (glw_state->totalIndices <= 1) + { + glw_state->numIndices = glw_state->maxIndices; + break; + } + + // ready for another cycle + //glw_state->drawArray += glw_state->maxVertices * glw_state->drawStride; + glw_state->maxIndices = _getMaxIndices(); + + if(glw_state->maxIndices % 2) + { + glw_state->maxIndices -= 1; + singleindex = true; + } + + glw_state->drawArray = _restartIndexPacket(glw_state->drawArray, glw_state->maxIndices); + } + +#define CMD_DRAW_INDEX_LAST 0x1808 + if(singleindex) + { + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_DRAW_INDEX_LAST, 1); + *glw_state->drawArray++ = indices[count - 1]; + } +} + +// NOTE: This is a core draw routine. It should be fast. +static void dllDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices) +{ + int normals, tex0, tex1, num_streams = 2; + + assert(type == GL_UNSIGNED_SHORT); + + normals = glw_state->normalArrayState ? tess.numVertexes : 0; + tex0 = glw_state->texCoordArrayState[0] ? tess.numVertexes : 0; + tex1 = glw_state->texCoordArrayState[1] ? tess.numVertexes : 0; + + num_streams += ((normals > 0) + (tex0 > 0) + (tex1 > 0)); + + // start the draw block + glw_state->inDrawBlock = true; + glw_state->primitiveMode = _convertPrimMode(mode); + + // update DX with any pending state changes + if(tess.currentPass == 0) + { + _updateDrawStride(normals, tex0, tex1); + glw_state->drawStride += normals ? 2 : 1; + } + else + { + glw_state->drawStride = 1; + if(normals && !tess.pNormal) glw_state->drawStride += 4; + if(tex0) glw_state->drawStride += 2; + if(tex1) glw_state->drawStride += 2; + } + _updateShader(normals, tex0, tex1); + _updateTextures(); + _updateMatrices(); + + glw_state->numIndices = 0; + glw_state->totalIndices = count; + glw_state->maxIndices = _getMaxIndices(); + + glw_state->device->SetStreamSource(0, NULL, glw_state->drawStride * 4); + + int vert_size = glw_state->drawStride * tess.numVertexes; + int index_size = count / 2; + + glw_state->device->BeginPush(vert_size + index_size + 60, &glw_state->drawArray); + + glw_state->drawArray = (DWORD*)*((DWORD*)glw_state->device); + + DWORD *jumpaddress = 0, *stream = 0; + + // Only copy the geometry in on the first pass + // Multiple passes will reuse this geometry (except tex coords) + if(tess.currentPass == 0) + { + // Determine where the end of the vertex data is gonna be, + // that's where we're going to jump to + jumpaddress = (DWORD*)*((DWORD*)glw_state->device) + (vert_size + 1); + + // Write the jump address + *glw_state->drawArray++ = ((DWORD)jumpaddress & 0x7fffffff) | 1; + + // Set up our own fake vertex buffer + stream = glw_state->drawArray; + + memcpy(glw_state->drawArray, tess.xyz, sizeof(vec4_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 4; + + if(normals) + { + memcpy(glw_state->drawArray, tess.normal, sizeof(vec4_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 4; + } + + if(glw_state->colorArrayState) + { + memcpy(glw_state->drawArray, tess.svars.colors, sizeof(D3DCOLOR) * tess.numVertexes); + } + else + { + for( int v = 0; v < tess.numVertexes; ++v ) + glw_state->drawArray[v] = glw_state->currentColor; + } + glw_state->drawArray += tess.numVertexes; + + if(tex0) + { + memcpy(glw_state->drawArray, tess.svars.texcoords[0], sizeof(vec2_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 2; + } + + if(tex1) + { + memcpy(glw_state->drawArray, tess.svars.texcoords[1], sizeof(vec2_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 2; + } + } + else + { + // Determine where the end of the vertex data is gonna be, + // that's where we're going to jump to + jumpaddress = (DWORD*)*((DWORD*)glw_state->device) + (vert_size + 1); + + // Write the jump address + *glw_state->drawArray++ = ((DWORD)jumpaddress & 0x7fffffff) | 1; + + // Set up our own fake vertex buffer + stream = glw_state->drawArray; + + if(normals && !tess.pNormal) + { + memcpy(glw_state->drawArray, tess.normal, sizeof(vec4_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 4; + } + + if(glw_state->colorArrayState) + { + memcpy(glw_state->drawArray, tess.svars.colors, sizeof(D3DCOLOR) * tess.numVertexes); + } + else + { + for( int v = 0; v < tess.numVertexes; ++v ) + glw_state->drawArray[v] = glw_state->currentColor; + } + glw_state->drawArray += tess.numVertexes; + + if(tex0) + { + memcpy(glw_state->drawArray, tess.svars.texcoords[0], sizeof(vec2_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 2; + } + + if(tex1) + { + memcpy(glw_state->drawArray, tess.svars.texcoords[1], sizeof(vec2_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 2; + } + } + + // Write the vertex shader +#define CMD_STREAM_STRIDEANDTYPE0 0x1760 + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_STREAM_STRIDEANDTYPE0, 16); + *glw_state->drawArray++ = (16 << 8)|D3DVSDT_FLOAT3; + + if(1) + { + *glw_state->drawArray++ = ((glw_state->drawStride * 4) << 8) | D3DVSDT_NONE; + } + + if(normals) + { + *glw_state->drawArray++ = (16 << 8) | D3DVSDT_FLOAT3; + } + else + { + *glw_state->drawArray++ = ((glw_state->drawStride * 4) << 8) | D3DVSDT_NONE; + } + + *glw_state->drawArray++ = (4 << 8) | D3DVSDT_D3DCOLOR; + + for(int i = 0; i < 5; i++) + { + *glw_state->drawArray++ = ((glw_state->drawStride * 4) << 8) | D3DVSDT_NONE; + } + + if(tex0) + { + *glw_state->drawArray++ = (8 << 8) | D3DVSDT_FLOAT2; + } + else + { + *glw_state->drawArray++ = ((glw_state->drawStride * 4) << 8) | D3DVSDT_NONE; + } + + if(tex1) + { + *glw_state->drawArray++ = (8 << 8) | D3DVSDT_FLOAT2; + } + else + { + *glw_state->drawArray++ = ((glw_state->drawStride * 4) << 8) | D3DVSDT_NONE; + } + + for(i = 0; i < 5; i++) + { + *glw_state->drawArray++ = ((glw_state->drawStride * 4) << 8) | D3DVSDT_NONE; + } + +// Write the indicator to our vertex stream +#define CMD_VERTEXSTREAM_XYZ 0x1720 +#define CMD_VERTEXSTREAM_NORMAL 0x1728 +#define CMD_VERTEXSTREAM_COLOR 0x172c +#define CMD_VERTEXSTREAM_TEX0 0x1744 +#define CMD_VERTEXSTREAM_TEX1 0x1748 + + // On multiple passes, just write the address to the previous geometry + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_XYZ, 1); + if(tess.currentPass == 0) + { + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + tess.pXyz = stream; + stream += tess.numVertexes * 4; + } + else + { + *glw_state->drawArray++ = (DWORD)tess.pXyz & 0x7fffffff; + } + + if(normals) + { + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_NORMAL, 1); + if(!tess.pNormal) + { + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + tess.pNormal = stream; + stream += tess.numVertexes * 4; + } + else + { + *glw_state->drawArray++ = (DWORD)tess.pNormal & 0x7fffffff; + } + } + + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_COLOR, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes; + + if(tex0) + { + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_TEX0, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + tess.pTex1 = stream; + stream += tess.numVertexes * 2; + } + + if(tex1) + { + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_TEX1, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + tess.pTex2 = stream; + } + + // Send thru the index data + PushIndices(count, (GLushort*)indices); + + // finish up the draw + glw_state->inDrawBlock = false; + + DWORD* push = _terminateIndexPacket(glw_state->drawArray); + + glw_state->device->EndPush(push); +} + +static void dllDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) +{ + assert(0); +} + +static void dllEdgeFlag(GLboolean flag) +{ + assert(0); +} + +static void dllEdgeFlagPointer(GLsizei stride, const GLvoid *pointer) +{ + assert(0); +} + +static void dllEdgeFlagv(const GLboolean *flag) +{ + assert(0); +} + +static void dllEnable(GLenum cap) +{ + setCap(cap, true); +} + +static void dllEnableClientState(GLenum array) +{ + assert(!glw_state->inDrawBlock); + setArrayState(array, true); +} + +static void dllEnd(void) +{ + assert(glw_state->inDrawBlock); + glw_state->inDrawBlock = false; +#ifdef _XBOX + // on Xbox, just close the draw packet + DWORD* push = _terminateDrawPacket( + &glw_state->drawArray[glw_state->numVertices * + glw_state->drawStride]); + + glw_state->device->EndPush(push); +#else + // on the PC, use DrawPrimitiveUp (a little slow) + int num = 0; + switch (glw_state->primitiveMode) + { + case D3DPT_POINTLIST: num = glw_state->numVertices; break; + case D3DPT_LINELIST: num = glw_state->numVertices / 2; break; + case D3DPT_LINESTRIP: num = glw_state->numVertices - 1; break; + case D3DPT_TRIANGLELIST: num = glw_state->numVertices / 3; break; + case D3DPT_TRIANGLESTRIP: num = glw_state->numVertices - 2; break; + case D3DPT_TRIANGLEFAN: num = glw_state->numVertices - 2; break; + } + + glw_state->device->DrawPrimitiveUP( + glw_state->primitiveMode, num, + glw_state->drawArray, glw_state->drawStride * sizeof(DWORD)); +#endif +} + +#if YELLOW_MODE + +static DWORD YellowModePixelShader = -1; +bool enableYellowMode = false; +static void initYellowMode( void ) +{ + if(!(CreatePixelShader("D:\\base\\media\\yellow.xpu", &YellowModePixelShader))) + return; +} + +static void renderYellowMode( void ) +{ + if(!enableYellowMode || YellowModePixelShader == -1) + return; + + if(cls.state == CA_CINEMATIC) + return; + + if(!tr.screenImage) + return; + + // bind tr.screenImage + GL_Bind(tr.screenImage); + GL_State(0); + + // copy backbuffer + //qglCopyBackBufferToTexEXT(width, height, u1, v1, u2, v2); + qglCopyBackBufferToTexEXT(512.0f, 256.0f, 2.0f, 2.0f, 640.0f, 480.0f); + + // set up the yellow mode shader + DWORD oldShader; + glw_state->device->GetPixelShader(&oldShader); + glw_state->device->SetPixelShader(YellowModePixelShader); + + // draw our polygon + qglBeginEXT(GL_QUADS,4,0,0,4,0); + + qglTexCoord2f( 0,0 ); + qglVertex2f( 0, 0 ); + + qglTexCoord2f( 1,0 ); + qglVertex2f( 640, 0 ); + + qglTexCoord2f( 1,1 ); + qglVertex2f( 640, 480); + + qglTexCoord2f( 0,1 ); + qglVertex2f( 0, 480); + + qglEnd (); + + glw_state->device->SetPixelShader(oldShader); +} + +#endif // YELLOW_MODE + +// EXTENSION: End drawing for a frame +bool connectSwapOverride = false; +static void dllEndFrame(void) +{ + assert(!glw_state->inDrawBlock); + + // the blend state can get reset by Present()... + GLboolean blend = qglIsEnabled(GL_BLEND); + +#if YELLOW_MODE + if( !connectSwapOverride ) + renderYellowMode(); +#endif // YELLOW_MODE + + glw_state->device->EndScene(); + + qglViewport(0, 0, glConfig.vidWidth, glConfig.vidHeight); + if( !connectSwapOverride ) + glw_state->device->Present(NULL, NULL, NULL, NULL); + + // restore the pre-Present state + if (blend) qglEnable(GL_BLEND); + else qglDisable(GL_BLEND); +} + +// EXTENSION: End shadow draw mode +static void dllEndShadow(void) +{ + //Intentionally left blank +} + +static void dllEndList(void) +{ + assert(0); +} + +static void dllEvalCoord1d(GLdouble u) +{ + assert(0); +} + +static void dllEvalCoord1dv(const GLdouble *u) +{ + assert(0); +} + +static void dllEvalCoord1f(GLfloat u) +{ + assert(0); +} + +static void dllEvalCoord1fv(const GLfloat *u) +{ + assert(0); +} + +static void dllEvalCoord2d(GLdouble u, GLdouble v) +{ + assert(0); +} + +static void dllEvalCoord2dv(const GLdouble *u) +{ + assert(0); +} + +static void dllEvalCoord2f(GLfloat u, GLfloat v) +{ + assert(0); +} + +static void dllEvalCoord2fv(const GLfloat *u) +{ + assert(0); +} + +static void dllEvalMesh1(GLenum mode, GLint i1, GLint i2) +{ + assert(0); +} + +static void dllEvalMesh2(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2) +{ + assert(0); +} + +static void dllEvalPoint1(GLint i) +{ + assert(0); +} + +static void dllEvalPoint2(GLint i, GLint j) +{ + assert(0); +} + +static void dllFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer) +{ + assert(0); +} + +static void dllFinish(void) +{ +#ifdef _XBOX + glw_state->device->BlockUntilIdle(); +#endif +} + +static void dllFlush(void) +{ +#ifdef _XBOX + glw_state->device->BlockUntilIdle(); +#endif +} + +// EXTENSION: Draw the shadow +static void dllFlushShadow(void) +{ + //Intentionally left blank +} + +static D3DFOGMODE _convertFogMode(GLint param) +{ + switch(param) + { + case GL_LINEAR: return D3DFOG_LINEAR; break; + case GL_EXP: return D3DFOG_EXP; break; + case GL_EXP2: return D3DFOG_EXP2; break; + } + + return D3DFOG_NONE; +} + +static void dllFogf(GLenum pname, GLfloat param) +{ + assert(pname == GL_FOG_DENSITY || pname == GL_FOG_START || pname == GL_FOG_END); + + switch(pname) + { + case GL_FOG_DENSITY: glw_state->device->SetRenderState( D3DRS_FOGDENSITY, *(DWORD*)¶m ); break; + case GL_FOG_START: glw_state->device->SetRenderState( D3DRS_FOGSTART, *(DWORD*)¶m ); break; + case GL_FOG_END: glw_state->device->SetRenderState( D3DRS_FOGEND, *(DWORD*)¶m ); break; + } +} + +static void dllFogfv(GLenum pname, const GLfloat *params) +{ + assert(pname == GL_FOG_COLOR); + + D3DCOLOR color = D3DCOLOR_ARGB(0x00, + (int)(params[0] * 255.0f), + (int)(params[1] * 255.0f), + (int)(params[2] * 255.0f)); + + glw_state->device->SetRenderState( D3DRS_FOGCOLOR, color ); +} + +static void dllFogi(GLenum pname, GLint param) +{ + assert(pname == GL_FOG_MODE); + + glw_state->device->SetRenderState( D3DRS_FOGTABLEMODE, _convertFogMode(param) ); +} + +static void dllFogiv(GLenum pname, const GLint *params) +{ + assert(0); +} + +static void dllFrontFace(GLenum mode) +{ + assert(0); +} + +static void dllFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + D3DXMATRIX m; + D3DXMatrixPerspectiveOffCenterRH(&m, left, right, bottom, top, zNear, zFar); + glw_state->matrixStack[glw_state->matrixMode]->MultMatrix(&m); + glw_state->matricesDirty[glw_state->matrixMode] = true; +} + +GLuint dllGenLists(GLsizei range) +{ + assert(0); + return 0; +} + +static void dllGenTextures(GLsizei n, GLuint *textures) +{ + for (int i = 0; i < n; ++i) + { + textures[i] = glw_state->textureBindNum++; + } +} + +// Implemented only the states we use. +template +static void _getState(GLenum pname, T *params) +{ + switch (pname) + { + case GL_CULL_FACE: params[0] = (T)glw_state->cullEnable; break; + case GL_MAX_TEXTURE_SIZE: params[0] = (T)512; break; + case GL_MAX_ACTIVE_TEXTURES_ARB: params[0] = GLW_MAX_TEXTURE_STAGES; break; + case GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT: params[0] = 4; break; + default: + assert(0); + params[0] = (T)0; + break; + } +} + +static void dllGetBooleanv(GLenum pname, GLboolean *params) +{ + _getState(pname, params); +} + +static void dllGetClipPlane(GLenum plane, GLdouble *equation) +{ + assert(0); +} + +static void dllGetDoublev(GLenum pname, GLdouble *params) +{ + _getState(pname, params); +} + +GLenum dllGetError(void) +{ + return 0; +} + +static void dllGetFloatv(GLenum pname, GLfloat *params) +{ + _getState(pname, params); +} + +static void dllGetIntegerv(GLenum pname, GLint *params) +{ + _getState(pname, params); +} + +static void dllGetLightfv(GLenum light, GLenum pname, GLfloat *params) +{ + assert(0); +} + +static void dllGetLightiv(GLenum light, GLenum pname, GLint *params) +{ + assert(0); +} + +static void dllGetMapdv(GLenum target, GLenum query, GLdouble *v) +{ + assert(0); +} + +static void dllGetMapfv(GLenum target, GLenum query, GLfloat *v) +{ + assert(0); +} + +static void dllGetMapiv(GLenum target, GLenum query, GLint *v) +{ + assert(0); +} + +static void dllGetMaterialfv(GLenum face, GLenum pname, GLfloat *params) +{ + assert(0); +} + +static void dllGetMaterialiv(GLenum face, GLenum pname, GLint *params) +{ + assert(0); +} + +static void dllGetPixelMapfv(GLenum map, GLfloat *values) +{ + assert(0); +} + +static void dllGetPixelMapuiv(GLenum map, GLuint *values) +{ + assert(0); +} + +static void dllGetPixelMapusv(GLenum map, GLushort *values) +{ + assert(0); +} + +static void dllGetPointerv(GLenum pname, GLvoid* *params) +{ + assert(0); +} + +static void dllGetPolygonStipple(GLubyte *mask) +{ + assert(0); +} + +const GLubyte * dllGetString(GLenum name) +{ + switch (name) + { + case GL_VENDOR: return (const unsigned char*)"Vicarious Visions"; + case GL_RENDERER: return (const unsigned char*)"Optimized DX8/OpenGL Layer"; + case GL_VERSION: return (const unsigned char*)"0.1"; + case GL_EXTENSIONS: + return (const unsigned char*) + "EXT_texture_env_add GL_ARB_multitexture EXT_texture_filter_anisotropic"; + default: return (const unsigned char*)""; + } +} + +static void dllGetTexEnvfv(GLenum target, GLenum pname, GLfloat *params) +{ + assert(0); +} + +static void dllGetTexEnviv(GLenum target, GLenum pname, GLint *params) +{ + assert(0); +} + +static void dllGetTexGendv(GLenum coord, GLenum pname, GLdouble *params) +{ + assert(0); +} + +static void dllGetTexGenfv(GLenum coord, GLenum pname, GLfloat *params) +{ + assert(0); +} + +static void dllGetTexGeniv(GLenum coord, GLenum pname, GLint *params) +{ + assert(0); +} + +static void dllGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels) +{ + assert(0); +} + +static void dllGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params) +{ + assert(0); +} + +static void dllGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params) +{ + assert(0); +} + +static void dllGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params) +{ + assert(0); +} + +static void dllGetTexParameteriv(GLenum target, GLenum pname, GLint *params) +{ + assert(0); +} + +static void dllHint(GLenum target, GLenum mode) +{ + assert(0); +} + +// Convert an triangle index array (indices) to a +// triangle strip index array (dest) with primitive +// length array. +static void buildStrips(GLuint* len, GLsizei* num_lens, GLushort* dest, GLsizei* num_indices, const GLushort* src) +{ + GLushort last[3]; + + // prime the strip + GLsizei cur_index = 0; + dest[cur_index++] = src[0]; + dest[cur_index++] = src[1]; + dest[cur_index++] = src[2]; + GLuint cur_length = 3; + GLsizei num_strips = 0; + + GLuint max_length = GLW_MAX_DRAW_PACKET_SIZE / glw_state->drawStride; + + last[0] = src[0]; + last[1] = src[1]; + last[2] = src[2]; + + qboolean even = qfalse; + + for ( GLsizei i = 3; i < *num_indices; i += 3 ) + { + // odd numbered triangle in potential strip + if ( !even ) + { + // check previous triangle to see if we're continuing a strip + if ( ( src[i+0] == last[2] ) && ( src[i+1] == last[1] ) && + cur_length < max_length ) + { + ++cur_length; + dest[cur_index++] = src[i+2]; + even = qtrue; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + len[num_strips++] = cur_length; + cur_length = 3; + + dest[cur_index++] = src[i+0]; + dest[cur_index++] = src[i+1]; + dest[cur_index++] = src[i+2]; + + even = qfalse; + } + } + else + { + // check previous triangle to see if we're continuing a strip + if ( ( last[2] == src[i+1] ) && ( last[0] == src[i+0] ) && + cur_length < max_length ) + { + ++cur_length; + dest[cur_index++] = src[i+2]; + even = qfalse; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + len[num_strips++] = cur_length; + cur_length = 3; + + dest[cur_index++] = src[i+0]; + dest[cur_index++] = src[i+1]; + dest[cur_index++] = src[i+2]; + + even = qfalse; + } + } + + // cache the last three vertices + last[0] = src[i+0]; + last[1] = src[i+1]; + last[2] = src[i+2]; + } + + len[num_strips++] = cur_length; + *num_lens = num_strips; + *num_indices = cur_index; + + assert(num_strips <= GLW_MAX_STRIPS); +} + +#ifdef _XBOX +void renderObject_Light( int numIndexes, const glIndex_t *indexes ) +{ + int i; + + // start the draw mode + assert(!glw_state->inDrawBlock); + + glw_state->inDrawBlock = true; + glw_state->primitiveMode = D3DPT_TRIANGLELIST; + + glw_state->drawStride = 14; + + glw_state->numIndices = 0; + glw_state->totalIndices = numIndexes; + glw_state->maxIndices = _getMaxIndices(); + + glw_state->device->SetStreamSource(0, NULL, glw_state->drawStride * 4); + + int vert_size; + if(!tess.pNormal) + vert_size = 8 * tess.numVertexes; + else + vert_size = 4 * tess.numVertexes; + + int index_size = numIndexes / 2; + + glw_state->device->BeginPush(vert_size + index_size + 60, &glw_state->drawArray); + + glw_state->drawArray = (DWORD*)*((DWORD*)glw_state->device); + + DWORD *jumpaddress = 0, *stream = 0; + + // Determine where the end of the vertex data is gonna be, + // that's where we're going to jump to + jumpaddress = (DWORD*)*((DWORD*)glw_state->device) + (vert_size + 1); + + // Write the jump address + *glw_state->drawArray++ = ((DWORD)jumpaddress & 0x7fffffff) | 1; + + // Set up our own fake vertex buffer + stream = glw_state->drawArray; + + if(!tess.pNormal) + { + memcpy(glw_state->drawArray, tess.normal, sizeof(vec4_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 4; + } + + memcpy(glw_state->drawArray, tess.tangent, sizeof(vec4_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 4; + + // Write the vertex shader +#define CMD_STREAM_STRIDEANDTYPE0 0x1760 + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_STREAM_STRIDEANDTYPE0, 16); + + // Position + *glw_state->drawArray++ = (16 << 8)|D3DVSDT_FLOAT3; + + // Normal + *glw_state->drawArray++ = (16 << 8) | D3DVSDT_FLOAT3; + + // Tex Coord + *glw_state->drawArray++ = (8 << 8) | D3DVSDT_FLOAT2; + + // Tangent + *glw_state->drawArray++ = (16 << 8) | D3DVSDT_FLOAT3; + + for(i = 0; i < 12; i++) + { + *glw_state->drawArray++ = ((glw_state->drawStride * 4) << 8) | D3DVSDT_NONE; + } + + // Write the indicator to our vertex stream + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1720, 1); + *glw_state->drawArray++ = (DWORD)tess.pXyz & 0x7fffffff; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1724, 1); + if(!tess.pNormal) + { + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes * 4; + } + else + *glw_state->drawArray++ = (DWORD)tess.pNormal & 0x7fffffff; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1728, 1); + *glw_state->drawArray++ = (DWORD)tess.pTex1 & 0x7fffffff; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x172c, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + + // Send thru the index data + PushIndices(numIndexes, (GLushort*)indexes); + + // finish up the draw + glw_state->inDrawBlock = false; + + DWORD* push = _terminateIndexPacket(glw_state->drawArray); + + glw_state->device->EndPush(push); +} + + +void renderObject_Shadow( int primType, int numIndexes, const unsigned short *indexes ) +{ + int i; + + // start the draw mode + assert(!glw_state->inDrawBlock); + + glw_state->inDrawBlock = true; + glw_state->primitiveMode = (D3DPRIMITIVETYPE)primType; + + glw_state->drawStride = 5; + + glw_state->numIndices = 0; + glw_state->totalIndices = numIndexes; + glw_state->maxIndices = _getMaxIndices(); + + glw_state->device->SetStreamSource(0, NULL, glw_state->drawStride * 4); + + int vert_size = glw_state->drawStride * (tess.numVertexes * 2); + int index_size = numIndexes / 2; + + glw_state->device->BeginPush(vert_size + index_size + 60, &glw_state->drawArray); + + glw_state->drawArray = (DWORD*)*((DWORD*)glw_state->device); + + DWORD *jumpaddress = 0, *stream = 0; + + if(!StencilShadower.pVerts) + { + // Determine where the end of the vertex data is gonna be, + // that's where we're going to jump to + jumpaddress = (DWORD*)*((DWORD*)glw_state->device) + (vert_size + 1); + + // Write the jump address + *glw_state->drawArray++ = ((DWORD)jumpaddress & 0x7fffffff) | 1; + + // Set up our own fake vertex buffer + stream = glw_state->drawArray; + + memcpy(glw_state->drawArray, tess.xyz, sizeof(vec4_t) * (tess.numVertexes * 2)); + glw_state->drawArray += (tess.numVertexes * 2) * 4; + + // Write the extrusion indicators + memset(glw_state->drawArray, 0x0, sizeof(float) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes; + memcpy(glw_state->drawArray, StencilShadower.m_extrusionIndicators, sizeof(float) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes; + } + + // Write the vertex shader +#define CMD_STREAM_STRIDEANDTYPE0 0x1760 + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_STREAM_STRIDEANDTYPE0, 16); + + // Position + *glw_state->drawArray++ = (16 << 8)|D3DVSDT_FLOAT3; + + // Extrusion determinant + *glw_state->drawArray++ = (4 << 8)|D3DVSDT_FLOAT1; + + for(i = 0; i < 14; i++) + { + *glw_state->drawArray++ = ((glw_state->drawStride * 4) << 8) | D3DVSDT_NONE; + } + + // Write the indicator to our vertex stream + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1720, 1); + if(!StencilShadower.pVerts) + { + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + StencilShadower.pVerts = stream; + stream += (tess.numVertexes * 2) * 4; + } + else + { + *glw_state->drawArray++ = (DWORD)StencilShadower.pVerts & 0x7fffffff; + } + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1724, 1); + if(!StencilShadower.pExtrusions) + { + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + StencilShadower.pExtrusions = stream; + stream += (tess.numVertexes * 2); + } + else + { + *glw_state->drawArray++ = (DWORD)StencilShadower.pExtrusions & 0x7fffffff; + } + + // Send thru the index data + PushIndices(numIndexes, (GLushort*)indexes); + + // finish up the draw + glw_state->inDrawBlock = false; + + DWORD* push = _terminateIndexPacket(glw_state->drawArray); + + glw_state->device->EndPush(push); +} + + +void renderObject_Bump() +{ + // start the draw mode + assert(!glw_state->inDrawBlock); + + glw_state->inDrawBlock = true; + glw_state->primitiveMode = D3DPT_TRIANGLELIST; + + glw_state->drawStride = 16; + + glw_state->numIndices = 0; + glw_state->totalIndices = tess.numIndexes; + glw_state->maxIndices = _getMaxIndices(); + + glw_state->device->SetStreamSource(0, NULL, glw_state->drawStride * 4); + + int vert_size = glw_state->drawStride * tess.numVertexes; + int index_size = tess.numIndexes / 2; + + glw_state->device->BeginPush(vert_size + index_size + 60, &glw_state->drawArray); + + glw_state->drawArray = (DWORD*)*((DWORD*)glw_state->device); + + DWORD *jumpaddress = 0, *stream = 0; + + // Determine where the end of the vertex data is gonna be, + // that's where we're going to jump to + jumpaddress = (DWORD*)*((DWORD*)glw_state->device) + (vert_size + 1); + + // Write the jump address + *glw_state->drawArray++ = ((DWORD)jumpaddress & 0x7fffffff) | 1; + + // Set up our own fake vertex buffer + stream = glw_state->drawArray; + + memcpy(glw_state->drawArray, tess.xyz, sizeof(vec4_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 4; + + memcpy(glw_state->drawArray, tess.normal, sizeof(vec4_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 4; + + memcpy(glw_state->drawArray, tess.svars.texcoords[0], sizeof(vec2_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 2; + + memcpy(glw_state->drawArray, tess.svars.texcoords[1], sizeof(vec2_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 2; + + memcpy(glw_state->drawArray, tess.tangent, sizeof(vec4_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 4; + + // Write the vertex shader +#define CMD_STREAM_STRIDEANDTYPE0 0x1760 + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_STREAM_STRIDEANDTYPE0, 16); + + // Position + *glw_state->drawArray++ = (16 << 8)|D3DVSDT_FLOAT3; + + // Normal + *glw_state->drawArray++ = (16 << 8) | D3DVSDT_FLOAT3; + + // Tex Coord 0 + *glw_state->drawArray++ = (8 << 8) | D3DVSDT_FLOAT2; + + // Tex Coord 1 + *glw_state->drawArray++ = (8 << 8) | D3DVSDT_FLOAT2; + + // Tangent + *glw_state->drawArray++ = (16 << 8) | D3DVSDT_FLOAT3; + + for(int i = 0; i < 11; i++) + { + *glw_state->drawArray++ = ((glw_state->drawStride * 4) << 8) | D3DVSDT_NONE; + } + + // Write the indicator to our vertex stream + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1720, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + tess.pXyz = stream; + stream += tess.numVertexes * 4; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1724, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + tess.pNormal = stream; + stream += tess.numVertexes * 4; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1728, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes * 2; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x172c, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes * 2; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1730, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes * 4; + + // Send thru the index data + PushIndices(tess.numIndexes, (GLushort*)tess.indexes); + + // finish up the draw + glw_state->inDrawBlock = false; + + DWORD* push = _terminateIndexPacket(glw_state->drawArray); + + glw_state->device->EndPush(push); +} + +void renderObject_Env() +{ + // start the draw mode + assert(!glw_state->inDrawBlock); + + glw_state->inDrawBlock = true; + glw_state->primitiveMode = D3DPT_TRIANGLELIST; + + glw_state->drawStride = 9; + _updateTextures(); + + glw_state->numIndices = 0; + glw_state->totalIndices = tess.numIndexes; + glw_state->maxIndices = _getMaxIndices(); + + glw_state->device->SetStreamSource(0, NULL, glw_state->drawStride * 4); + + int vert_size = glw_state->drawStride * tess.numVertexes; + int index_size = tess.numIndexes / 2; + + glw_state->device->BeginPush(vert_size + index_size + 60, &glw_state->drawArray); + + glw_state->drawArray = (DWORD*)*((DWORD*)glw_state->device); + + DWORD *jumpaddress = 0, *stream = 0; + + // Determine where the end of the vertex data is gonna be, + // that's where we're going to jump to + jumpaddress = (DWORD*)*((DWORD*)glw_state->device) + (vert_size + 1); + + // Write the jump address + *glw_state->drawArray++ = ((DWORD)jumpaddress & 0x7fffffff) | 1; + + // Set up our own fake vertex buffer + stream = glw_state->drawArray; + + memcpy(glw_state->drawArray, tess.xyz, sizeof(vec4_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 4; + + memcpy(glw_state->drawArray, tess.normal, sizeof(vec4_t) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes * 4; + + memcpy(glw_state->drawArray, tess.svars.colors, sizeof(D3DCOLOR) * tess.numVertexes); + glw_state->drawArray += tess.numVertexes; + + // Write the vertex shader +#define CMD_STREAM_STRIDEANDTYPE0 0x1760 + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_STREAM_STRIDEANDTYPE0, 16); + + // Position + *glw_state->drawArray++ = (16 << 8)|D3DVSDT_FLOAT3; + + // Normal + *glw_state->drawArray++ = (16 << 8) | D3DVSDT_FLOAT3; + + // Color + *glw_state->drawArray++ = (4 << 8) | D3DVSDT_D3DCOLOR; + + for(int i = 0; i < 13; i++) + { + *glw_state->drawArray++ = ((glw_state->drawStride * 4) << 8) | D3DVSDT_NONE; + } + + // Write the indicator to our vertex stream + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1720, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + tess.pXyz = stream; + stream += tess.numVertexes * 4; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1724, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + tess.pNormal = stream; + stream += tess.numVertexes * 4; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1728, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes; + + // Send thru the index data + PushIndices(tess.numIndexes, (GLushort*)tess.indexes); + + // finish up the draw + glw_state->inDrawBlock = false; + + DWORD* push = _terminateIndexPacket(glw_state->drawArray); + + glw_state->device->EndPush(push); +} +#endif + +// EXTENSION: Take an array of triangle indices and draw +// the appropriate triangle strips. Virtually ALL geometry +// is drawn with this function so it better be fast. +static void dllIndexedTriToStrip(GLsizei count, const GLushort *indices) +{ +//#ifndef _XBOX +#ifdef GLW_USE_TRI_STRIPS + + // update the render state + _updateDrawStride(glw_state->normalArrayState, + glw_state->texCoordArrayState[0] ? count : 0, + glw_state->texCoordArrayState[1] ? count : 0); + _updateShader(glw_state->normalArrayState, + glw_state->texCoordArrayState[0], + glw_state->texCoordArrayState[1]); + _updateTextures(); + _updateMatrices(); + + // convert triangles to strips -- guarantees that + // no strip exceeds the max draw packet size + if(tess.currentPass == 0) + { + buildStrips(glw_state->strip_lengths, + &glw_state->num_strip_lengths, glw_state->strip_dest, &count, indices); + } + + // Yeah, its a hack, but I gotta do this so bumpmapping + // doesnt go all crazy on the 'force speed' effect and + // 'disintegration' effect + if(tess.shader && + tess.shader->isBumpMap && + (backEnd.currentEntity->e.renderfx & +// VVFIXME : This is probably wrong. It looks like RF_ALPHA_FADE is renamed +// RF_RGB_TINT in MP. Substitute? +#ifndef _JK2MP + (RF_ALPHA_FADE | RF_DISINTEGRATE1 | RF_DISINTEGRATE2))) +#else + (RF_DISINTEGRATE1 | RF_DISINTEGRATE2))) +#endif + { + if(tess.currentPass != 2) + return; + } + +#ifdef _XBOX + glw_state->primitiveMode = D3DPT_TRIANGLESTRIP; + + // get the necessary draw function + drawelemfunc_t func = _drawElementFuncTable[_getDrawFunc()]; + int stride = glw_state->drawStride; + + int index = 0; + for (int l = 0; l < glw_state->num_strip_lengths; ++l) + { + int cur_len = glw_state->strip_lengths[l]; + + // start a draw packet + DWORD* push; + glw_state->device->BeginPush(stride * cur_len + 5, &push); + push = _restartDrawPacket(push, cur_len); + + // draw the geometry + glw_state->drawArray = push; + func(cur_len, &glw_state->strip_dest[index]); + index += cur_len; + + // finish the draw packet + push = _terminateDrawPacket(&push[stride * cur_len]); + glw_state->device->EndPush(push); + } +#else _XBOX + // simplified render on the PC + int index = 0; + for (int l = 0; l < glw_state->num_strip_lengths; ++l) + { + dllDrawElements(GL_TRIANGLE_STRIP, glw_state->strip_lengths[l], + GL_UNSIGNED_SHORT, &glw_state->strip_dest[index]); + index += glw_state->strip_lengths[l]; + } +#endif _XBOX + +#else GLW_USE_TRI_STRIPS + // just render simple triangles + dllDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, indices); +#endif GLW_USE_TRI_STRIPS +//#endif +} + +static void dllIndexMask(GLuint mask) +{ + assert(0); +} + +static void dllIndexPointer(GLenum type, GLsizei stride, const GLvoid *pointer) +{ + assert(0); +} + +static void dllIndexd(GLdouble c) +{ + assert(0); +} + +static void dllIndexdv(const GLdouble *c) +{ + assert(0); +} + +static void dllIndexf(GLfloat c) +{ + assert(0); +} + +static void dllIndexfv(const GLfloat *c) +{ + assert(0); +} + +static void dllIndexi(GLint c) +{ + assert(0); +} + +static void dllIndexiv(const GLint *c) +{ + assert(0); +} + +static void dllIndexs(GLshort c) +{ + assert(0); +} + +static void dllIndexsv(const GLshort *c) +{ + assert(0); +} + +static void dllIndexub(GLubyte c) +{ + assert(0); +} + +static void dllIndexubv(const GLubyte *c) +{ + assert(0); +} + +static void dllInitNames(void) +{ + assert(0); +} + +static void dllInterleavedArrays(GLenum format, GLsizei stride, const GLvoid *pointer) +{ + assert(0); +} + +GLboolean dllIsEnabled(GLenum cap) +{ + DWORD flag; + switch (cap) + { + case GL_ALPHA_TEST: glw_state->device->GetRenderState(D3DRS_ALPHATESTENABLE, &flag); break; + case GL_BLEND: glw_state->device->GetRenderState(D3DRS_ALPHABLENDENABLE, &flag); break; + case GL_CULL_FACE: return glw_state->cullEnable; + case GL_DEPTH_TEST: glw_state->device->GetRenderState(D3DRS_ZENABLE, &flag); break; + case GL_FOG: glw_state->device->GetRenderState(D3DRS_FOGENABLE, &flag); break; + case GL_LIGHTING: glw_state->device->GetRenderState(D3DRS_LIGHTING, &flag); break; +#ifdef _XBOX + case GL_POLYGON_OFFSET_FILL: glw_state->device->GetRenderState(D3DRS_SOLIDOFFSETENABLE, &flag); break; +#else + case GL_POLYGON_OFFSET_FILL: return FALSE; +#endif + case GL_SCISSOR_TEST: return glw_state->scissorEnable; + case GL_STENCIL_TEST: glw_state->device->GetRenderState(D3DRS_STENCILENABLE, &flag); break; + case GL_TEXTURE_2D: return glw_state->textureStageEnable[glw_state->serverTU]; + default: return FALSE; + } + return flag; +} + +GLboolean dllIsList(GLuint lnum) +{ + assert(0); + return 1; +} + +GLboolean dllIsTexture(GLuint texture) +{ + assert(0); + return 1; +} + +static void dllLightModelf(GLenum pname, GLfloat param) +{ + assert(0); +} + +static void dllLightModelfv(GLenum pname, const GLfloat *params) +{ + assert(0); +} + +static void dllLightModeli(GLenum pname, GLint param) +{ + assert(0); +} + +static void dllLightModeliv(GLenum pname, const GLint *params) +{ + assert(0); +} + +static void dllLightf(GLenum light, GLenum pname, GLfloat param) +{ + assert(0); +} + +static void dllLightfv(GLenum light, GLenum pname, const GLfloat *params) +{ + switch(pname) + { + case GL_AMBIENT: + { + glw_state->dirLight.Ambient.r = params[0] / 255.0f; + glw_state->dirLight.Ambient.g = params[1] / 255.0f; + glw_state->dirLight.Ambient.b = params[2] / 255.0f; + } + break; + + case GL_DIFFUSE: + { + glw_state->dirLight.Diffuse.r = params[0] / 255.0f; + glw_state->dirLight.Diffuse.g = params[1] / 255.0f; + glw_state->dirLight.Diffuse.b = params[2] / 255.0f; + } + break; + + case GL_SPECULAR: + { + glw_state->dirLight.Specular.r = params[0] / 255.0f; + glw_state->dirLight.Specular.g = params[1] / 255.0f; + glw_state->dirLight.Specular.b = params[2] / 255.0f; + } + break; + case GL_POSITION: + { + glw_state->dirLight.Position.x = params[0]; + glw_state->dirLight.Position.y = params[1]; + glw_state->dirLight.Position.z = params[2]; + } + break; + + case GL_SPOT_DIRECTION: + { + glw_state->dirLight.Direction.x = -params[0]; + glw_state->dirLight.Direction.y = -params[1]; + glw_state->dirLight.Direction.z = -params[2]; + } + break; + + default: + assert(0); + break; + } + + glw_state->device->SetLight(light, &glw_state->dirLight); + glw_state->device->LightEnable(light, TRUE); +} + +static void dllLighti(GLenum light, GLenum pname, GLint param) +{ + assert(0); +} + +static void dllLightiv(GLenum light, GLenum pname, const GLint *params) +{ + assert(0); +} + +static void dllLineStipple(GLint factor, GLushort pattern) +{ + assert(0); +} + +static void dllLineWidth(GLfloat width) +{ +// assert(0); +} + +static void dllListBase(GLuint base) +{ + assert(0); +} + +static void dllLoadIdentity(void) +{ + glw_state->matrixStack[glw_state->matrixMode]->LoadIdentity(); + glw_state->matricesDirty[glw_state->matrixMode] = true; +} + +static void dllLoadMatrixd(const GLdouble *m) +{ + assert(0); +} + +static void dllLoadMatrixf(const GLfloat *m) +{ + glw_state->matrixStack[glw_state->matrixMode]->LoadMatrix((D3DXMATRIX*)m); + glw_state->matricesDirty[glw_state->matrixMode] = true; +} + +static void dllLoadName(GLuint name) +{ + assert(0); +} + +static void dllLogicOp(GLenum opcode) +{ + assert(0); +} + +static void dllMap1d(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points) +{ + assert(0); +} + +static void dllMap1f(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points) +{ + assert(0); +} + +static void dllMap2d(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points) +{ + assert(0); +} + +static void dllMap2f(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points) +{ + assert(0); +} + +static void dllMapGrid1d(GLint un, GLdouble u1, GLdouble u2) +{ + assert(0); +} + +static void dllMapGrid1f(GLint un, GLfloat u1, GLfloat u2) +{ + assert(0); +} + +static void dllMapGrid2d(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2) +{ + assert(0); +} + +static void dllMapGrid2f(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2) +{ + assert(0); +} + +static void dllMaterialf(GLenum face, GLenum pname, GLfloat param) +{ + assert(0); +} + +static void dllMaterialfv(GLenum face, GLenum pname, const GLfloat *params) +{ + switch(pname) + { + case GL_AMBIENT: + glw_state->mtrl.Ambient.r = params[0] / 255.0f; + glw_state->mtrl.Ambient.g = params[1] / 255.0f; + glw_state->mtrl.Ambient.b = params[2] / 255.0f; + glw_state->mtrl.Ambient.a = params[3] / 255.0f; + break; + + case GL_DIFFUSE: + glw_state->mtrl.Diffuse.r = params[0] / 255.0f; + glw_state->mtrl.Diffuse.g = params[1] / 255.0f; + glw_state->mtrl.Diffuse.b = params[2] / 255.0f; + glw_state->mtrl.Diffuse.a = params[3] / 255.0f; + break; + + case GL_SPECULAR: + glw_state->mtrl.Specular.r = params[0] / 255.0f; + glw_state->mtrl.Specular.g = params[1] / 255.0f; + glw_state->mtrl.Specular.b = params[2] / 255.0f; + glw_state->mtrl.Specular.a = params[3] / 255.0f; + break; + + case GL_EMISSION: + glw_state->mtrl.Emissive.r = params[0] / 255.0f; + glw_state->mtrl.Emissive.g = params[1] / 255.0f; + glw_state->mtrl.Emissive.b = params[2] / 255.0f; + glw_state->mtrl.Emissive.a = params[3] / 255.0f; + break; + + default: + assert(0); + break; + } + + glw_state->device->SetMaterial(&glw_state->mtrl); +} + +static void dllMateriali(GLenum face, GLenum pname, GLint param) +{ + assert(0); +} + +static void dllMaterialiv(GLenum face, GLenum pname, const GLint *params) +{ + assert(0); +} + +static void dllMatrixMode(GLenum mode) +{ + switch (mode) + { + case GL_MODELVIEW: glw_state->matrixMode = glwstate_t::MatrixMode_Model; break; + case GL_PROJECTION: glw_state->matrixMode = glwstate_t::MatrixMode_Projection; break; +#ifdef _XBOX + case GL_TEXTURE0: glw_state->matrixMode = glwstate_t::MatrixMode_Texture0; break; + case GL_TEXTURE1: glw_state->matrixMode = glwstate_t::MatrixMode_Texture1; break; +#endif + default: assert(false); break; + } +} + +static void dllMultMatrixd(const GLdouble *m) +{ + assert(0); +} + +static void dllMultMatrixf(const GLfloat *m) +{ + glw_state->matrixStack[glw_state->matrixMode]->MultMatrixLocal((D3DXMATRIX*)m); + glw_state->matricesDirty[glw_state->matrixMode] = true; +} + +static void dllNewList(GLuint lnum, GLenum mode) +{ + assert(0); +} + +static void setNormal(float x, float y, float z) +{ + assert(glw_state->inDrawBlock); + + _handleDrawOverflow(); + + DWORD* push = &glw_state->drawArray[glw_state->numVertices * glw_state->drawStride + 4]; + push[0] = *((DWORD*)&x); + push[1] = *((DWORD*)&y); + push[2] = *((DWORD*)&z); + push[3] = glw_state->currentColor; +} +static void dllNormal3b(GLbyte nx, GLbyte ny, GLbyte nz) +{ + assert(0); +} + +static void dllNormal3bv(const GLbyte *v) +{ + assert(0); +} + +static void dllNormal3d(GLdouble nx, GLdouble ny, GLdouble nz) +{ + assert(0); +} + +static void dllNormal3dv(const GLdouble *v) +{ + assert(0); +} + +static void dllNormal3f(GLfloat nx, GLfloat ny, GLfloat nz) +{ + setNormal(nx, ny, nz); +} + +static void dllNormal3fv(const GLfloat *v) +{ + setNormal(v[0], v[1], v[2]); +} + +static void dllNormal3i(GLint nx, GLint ny, GLint nz) +{ + assert(0); +} + +static void dllNormal3iv(const GLint *v) +{ + assert(0); +} + +static void dllNormal3s(GLshort nx, GLshort ny, GLshort nz) +{ + assert(0); +} + +static void dllNormal3sv(const GLshort *v) +{ + assert(0); +} + +static void dllNormalPointer(GLenum type, GLsizei stride, const GLvoid *pointer) +{ + assert(type == GL_FLOAT); + + stride = (stride == 0) ? (sizeof(GLfloat) * 3) : stride; + + glw_state->normalPointer = pointer; + glw_state->normalStride = stride; +} + +static void dllOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar) +{ + D3DXMATRIX m; + D3DXMatrixOrthoOffCenterRH(&m, left, right, top, bottom, zNear, zFar); + glw_state->matrixStack[glw_state->matrixMode]->MultMatrix(&m); + glw_state->matricesDirty[glw_state->matrixMode] = true; +} + +static void dllPassThrough(GLfloat token) +{ + assert(0); +} + +static void dllPixelMapfv(GLenum map, GLsizei mapsize, const GLfloat *values) +{ + assert(0); +} + +static void dllPixelMapuiv(GLenum map, GLsizei mapsize, const GLuint *values) +{ + assert(0); +} + +static void dllPixelMapusv(GLenum map, GLsizei mapsize, const GLushort *values) +{ + assert(0); +} + +static void dllPixelStoref(GLenum pname, GLfloat param) +{ + assert(0); +} + +static void dllPixelStorei(GLenum pname, GLint param) +{ + assert(0); +} + +static void dllPixelTransferf(GLenum pname, GLfloat param) +{ + assert(0); +} + +static void dllPixelTransferi(GLenum pname, GLint param) +{ + assert(0); +} + +static void dllPixelZoom(GLfloat xfactor, GLfloat yfactor) +{ + assert(0); +} + +static void dllPointSize(GLfloat size) +{ + glw_state->device->SetRenderState(D3DRS_POINTSCALEENABLE, TRUE); + glw_state->device->SetRenderState(D3DRS_POINTSIZE, *((DWORD*)&size)); +} + +static void dllPolygonMode(GLenum face, GLenum mode) +{ + D3DFILLMODE m; + switch (mode) + { + case GL_POINT: m = D3DFILL_POINT; break; + case GL_LINE: m = D3DFILL_WIREFRAME; break; + case GL_FILL: m = D3DFILL_SOLID; break; + default: assert(0); break; + } + + switch (face) + { + case GL_FRONT: + glw_state->device->SetRenderState(D3DRS_FILLMODE, m); + break; + case GL_BACK: +#ifdef _XBOX + glw_state->device->SetRenderState(D3DRS_BACKFILLMODE, m); +#endif + break; + case GL_FRONT_AND_BACK: + glw_state->device->SetRenderState(D3DRS_FILLMODE, m); +#ifdef _XBOX + glw_state->device->SetRenderState(D3DRS_BACKFILLMODE, m); +#endif + break; + } +} + +static void dllPolygonOffset(GLfloat factor, GLfloat units) +{ +#ifdef _XBOX + glw_state->device->SetRenderState(D3DRS_POLYGONOFFSETZOFFSET, *((DWORD*)&factor)); + glw_state->device->SetRenderState(D3DRS_POLYGONOFFSETZSLOPESCALE, *((DWORD*)&units)); +#endif +} + +static void dllPolygonStipple(const GLubyte *mask) +{ + assert(0); +} + +static void dllPopAttrib(void) +{ + assert(0); +} + +static void dllPopClientAttrib(void) +{ + assert(0); +} + +static void dllPopMatrix(void) +{ + glw_state->matrixStack[glw_state->matrixMode]->Pop(); + glw_state->matricesDirty[glw_state->matrixMode] = true; +} + +static void dllPopName(void) +{ + assert(0); +} + +static void dllPrioritizeTextures(GLsizei n, const GLuint *textures, const GLclampf *priorities) +{ + assert(0); +} + +static void dllPushAttrib(GLbitfield mask) +{ + assert(0); +} + +static void dllPushClientAttrib(GLbitfield mask) +{ + assert(0); +} + +static void dllPushMatrix(void) +{ + glw_state->matrixStack[glw_state->matrixMode]->Push(); + glw_state->matricesDirty[glw_state->matrixMode] = true; +} + +static void dllPushName(GLuint name) +{ + assert(0); +} + +static void dllRasterPos2d(GLdouble x, GLdouble y) +{ + assert(0); +} + +static void dllRasterPos2dv(const GLdouble *v) +{ + assert(0); +} + +static void dllRasterPos2f(GLfloat x, GLfloat y) +{ + assert(0); +} + +static void dllRasterPos2fv(const GLfloat *v) +{ + assert(0); +} + +static void dllRasterPos2i(GLint x, GLint y) +{ + assert(0); +} + +static void dllRasterPos2iv(const GLint *v) +{ + assert(0); +} + +static void dllRasterPos2s(GLshort x, GLshort y) +{ + assert(0); +} + +static void dllRasterPos2sv(const GLshort *v) +{ + assert(0); +} + +static void dllRasterPos3d(GLdouble x, GLdouble y, GLdouble z) +{ + assert(0); +} + +static void dllRasterPos3dv(const GLdouble *v) +{ + assert(0); +} + +static void dllRasterPos3f(GLfloat x, GLfloat y, GLfloat z) +{ + assert(0); +} + +static void dllRasterPos3fv(const GLfloat *v) +{ + assert(0); +} + +static void dllRasterPos3i(GLint x, GLint y, GLint z) +{ + assert(0); +} + +static void dllRasterPos3iv(const GLint *v) +{ + assert(0); +} + +static void dllRasterPos3s(GLshort x, GLshort y, GLshort z) +{ + assert(0); +} + +static void dllRasterPos3sv(const GLshort *v) +{ + assert(0); +} + +static void dllRasterPos4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + assert(0); +} + +static void dllRasterPos4dv(const GLdouble *v) +{ + assert(0); +} + +static void dllRasterPos4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + assert(0); +} + +static void dllRasterPos4fv(const GLfloat *v) +{ + assert(0); +} + +static void dllRasterPos4i(GLint x, GLint y, GLint z, GLint w) +{ + assert(0); +} + +static void dllRasterPos4iv(const GLint *v) +{ + assert(0); +} + +static void dllRasterPos4s(GLshort x, GLshort y, GLshort z, GLshort w) +{ + assert(0); +} + +static void dllRasterPos4sv(const GLshort *v) +{ + assert(0); +} + +static void dllReadBuffer(GLenum mode) +{ + assert(0); +} + +//static void dllReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei twidth, GLsizei theight, GLvoid *pixels) +static void dllReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) +{ + assert( 0 ); + return; + +} + +/********** +dllCopyBackBufferToTex +Does a direct copy of the backbuffer to the current texture. The current texture +must be linear, and it must be 640 x 480 in size. If a more complex copy is +needed, use dllCopyBackBufferToTexEXT. +**********/ +static void dllCopyBackBufferToTex() +{ + glwstate_t::TextureInfo* info = _getCurrentTexture(glw_state->serverTU); + if (info == NULL) return; + + LPDIRECT3DSURFACE8 surf; + LPDIRECT3DSURFACE8 backbuffer; + + info->mipmap->GetSurfaceLevel(0, &surf); + glw_state->device->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &backbuffer); + + glw_state->device->CopyRects(backbuffer, NULL, 0, surf, NULL); + + surf->Release(); + backbuffer->Release(); +} + +/********** +dllCopyBackBufferToTexEXT +Copies a portion of the backbuffer to a texture +If the destination is a DXT1 texture, then the buffer will be compressed +width - width of the backbuffer polygon rendered to the destination texture +height - height of the backbuffer polygon rendered to the destination texture +u,v - describes the potion of the backbuffer to be copied in screen coords + +The active texture (that we're replacing) NEEDS to already have enough space! +**********/ +static void dllCopyBackBufferToTexEXT(float width, float height, float u1, float v1, float u2, float v2) +{ + glwstate_t::TextureInfo* info = _getCurrentTexture(glw_state->serverTU); + if (info == NULL) return; + + struct QUAD { D3DXVECTOR4 p; FLOAT tu,tv;} q[4]; + q[0].p = D3DXVECTOR4( 0.0f, 0.0f, 1.0f, 1.0f ); + q[0].tu = u1; q[0].tv = v1; + q[1].p = D3DXVECTOR4( width, 0.0f, 1.0f, 1.0f ); + q[1].tu = u2; q[1].tv = v1; + q[2].p = D3DXVECTOR4( 0.0f, height , 1.0f, 1.0f ); + q[2].tu = u1; q[2].tv = v2; + q[3].p = D3DXVECTOR4( width, height, 1.0f, 1.0f ); + q[3].tu = u2; q[3].tv = v2; + + + LPDIRECT3DSURFACE8 pSurface; + LPDIRECT3DSURFACE8 pBackBuffer; + LPDIRECT3DSURFACE8 pStencilBuffer; + D3DSURFACE_DESC desc; + D3DTexture* pRenderTex; + int w = 0; + int h = 0; + + DWORD srcblend, destblend, alphablend, alphatest, zwrite, zenable, vShader, pShader; + DWORD colorop, colorarg1, addressu, addressv, minfilter, magfilter, colorwriteenable; + + // save the current state + glw_state->device->GetRenderState( D3DRS_SRCBLEND, &srcblend ); + glw_state->device->GetRenderState( D3DRS_DESTBLEND, &destblend ); + glw_state->device->GetRenderState( D3DRS_ALPHABLENDENABLE, &alphablend ); + glw_state->device->GetRenderState( D3DRS_ALPHATESTENABLE, &alphatest ); + glw_state->device->GetRenderState( D3DRS_ZWRITEENABLE, &zwrite ); + glw_state->device->GetRenderState( D3DRS_ZENABLE, &zenable ); + glw_state->device->GetRenderState( D3DRS_COLORWRITEENABLE, &colorwriteenable); + glw_state->device->GetVertexShader( &vShader ); + glw_state->device->GetPixelShader( &pShader ); + // This function no longer makes ANY attempt to restore texture stages + glw_state->device->SetTexture(0, NULL); + glw_state->device->SetTexture(1, NULL); + glw_state->device->SetTexture(2, NULL); + glw_state->device->SetTexture(3, NULL); + glw_state->device->GetTextureStageState(0, D3DTSS_COLOROP, &colorop); + glw_state->device->GetTextureStageState(0, D3DTSS_COLORARG1, &colorarg1); + glw_state->device->GetTextureStageState(0, D3DTSS_ADDRESSU, &addressu); + glw_state->device->GetTextureStageState(0, D3DTSS_ADDRESSV, &addressv); + glw_state->device->GetTextureStageState(0, D3DTSS_MINFILTER, &minfilter); + glw_state->device->GetTextureStageState(0, D3DTSS_MAGFILTER, &magfilter); + + // get the buffers + glw_state->device->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer); + glw_state->device->GetDepthStencilSurface(&pStencilBuffer); + + // get a surface desc + info->mipmap->GetLevelDesc(0, &desc); + + // Check to see if the texture needs to be resized + if( desc.Width != width || desc.Height != height) + { + // We don't actually destroy/create the texture anymore. + // We just adjust the texture header. Thus, make sure the texture + // is big enough when you make it the first time! + + // Replacing this with a while( IsBusy() ) loop makes it hang sometimes + // But I can't figure out who the fuck has a lock on the texture, or + // and it doesn't seem to cause any problems. (ie: using push on cloaked guys) + info->mipmap->BlockUntilNotBusy(); + + // Change the texture size + XGSetTextureHeader( width, + height, + 1, + 0, + desc.Format, + 0, + info->mipmap, + 0, + 0 ); + + // Re-register the data: + info->mipmap->Register( info->data ); + } + + // check to see if we want a compressed output texture + if( desc.Format == D3DFMT_DXT1) + { + + w = desc.Width; + h = desc.Height; + + // create a new texture to use as a render target + _d3d_check(glw_state->device->CreateTexture( w, + h, + 1, + 0, + D3DFMT_LIN_X8R8G8B8, + 0, + &pRenderTex ), "CreateTexture"); + } + else + { + pRenderTex = info->mipmap; + + } + + // make our current surface a render target + pRenderTex->GetSurfaceLevel(0, &pSurface); + glw_state->device->SetRenderTarget( pSurface, NULL ); + + // set texture 0 to the back buffer data + glw_state->device->SetTexture(0,(LPDIRECT3DTEXTURE8)pBackBuffer); + + // set the texture 0 state + glw_state->device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); + glw_state->device->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR ); + glw_state->device->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR ); + + // set the render state + glw_state->device->SetRenderState( D3DRS_ZENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE); + glw_state->device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_SRCALPHA | D3DBLEND_INVSRCALPHA ); + glw_state->device->SetRenderState( D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALL); + + // set our vertex shader and draw the backbuffer to the texture + glw_state->device->SetVertexShader( D3DFVF_XYZRHW|D3DFVF_TEX1 ); + glw_state->device->SetPixelShader( NULL ); + glw_state->device->Clear(NULL,NULL,D3DCLEAR_TARGET,D3DCOLOR_COLORVALUE(1.0f, 1.0f, 1.0f, 1.0f), 1.0f, 0); + glw_state->device->DrawPrimitiveUP( D3DPT_QUADSTRIP, 1, q, sizeof(QUAD) ); + + // now that everything is rendered, check again to see + // if we want a compressed texture + if( desc.Format == D3DFMT_DXT1) + { + LPDIRECT3DTEXTURE8 pSrcTex; + LPDIRECT3DTEXTURE8 pDstTex; + D3DLOCKED_RECT srcLock; + D3DLOCKED_RECT dstLock; + + pSrcTex = pRenderTex; + pDstTex = info->mipmap; + + // lock our textures + pSrcTex->LockRect(0, &srcLock, NULL, 0); + pDstTex->LockRect(0, &dstLock, NULL, 0); + + // compress the texture + XGCompressRect( dstLock.pBits, + D3DFMT_DXT1, + dstLock.Pitch, + w, + h, + srcLock.pBits, + D3DFMT_LIN_X8R8G8B8, + srcLock.Pitch, + 1, + 0 ); + + // unlock + pSrcTex->UnlockRect(0); + pDstTex->UnlockRect(0); + + // release the render texture + pRenderTex->Release(); + } + + // return our state + glw_state->device->SetRenderState( D3DRS_SRCBLEND, srcblend ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, destblend ); + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, alphablend ); + glw_state->device->SetRenderState( D3DRS_ALPHATESTENABLE, alphatest ); + glw_state->device->SetRenderState( D3DRS_ZWRITEENABLE, zwrite ); + glw_state->device->SetRenderState( D3DRS_ZENABLE, zenable ); + glw_state->device->SetRenderState( D3DRS_COLORWRITEENABLE, colorwriteenable); + + // Clear stage zero again. We're not being nice. + glw_state->device->SetTexture(0, NULL); + glw_state->textureStageDirty[0] = true; + glw_state->textureStageDirty[1] = true; + + glw_state->device->SetTextureStageState(0, D3DTSS_COLOROP, colorop); + glw_state->device->SetTextureStageState(0, D3DTSS_COLORARG1, colorarg1); + glw_state->device->SetTextureStageState(0, D3DTSS_ADDRESSU, addressu); + glw_state->device->SetTextureStageState(0, D3DTSS_ADDRESSV, addressv); + glw_state->device->SetTextureStageState(0, D3DTSS_MINFILTER, minfilter); + glw_state->device->SetTextureStageState(0, D3DTSS_MAGFILTER, magfilter); + + glw_state->device->SetVertexShader( vShader ); + glw_state->device->SetPixelShader( pShader ); + + glw_state->device->SetRenderTarget( pBackBuffer, pStencilBuffer ); + + pSurface->Release(); + pBackBuffer->Release(); + pStencilBuffer->Release(); +} + +static void dllRectd(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2) +{ + assert(0); +} + +static void dllRectdv(const GLdouble *v1, const GLdouble *v2) +{ + assert(0); +} + +static void dllRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) +{ + assert(0); +} + +static void dllRectfv(const GLfloat *v1, const GLfloat *v2) +{ + assert(0); +} + +static void dllRecti(GLint x1, GLint y1, GLint x2, GLint y2) +{ + assert(0); +} + +static void dllRectiv(const GLint *v1, const GLint *v2) +{ + assert(0); +} + +static void dllRects(GLshort x1, GLshort y1, GLshort x2, GLshort y2) +{ + assert(0); +} + +static void dllRectsv(const GLshort *v1, const GLshort *v2) +{ + assert(0); +} + +GLint dllRenderMode(GLenum mode) +{ + assert(0); + return 0; +} + +static void dllRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z) +{ + assert(0); +} + +static void dllRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) +{ + D3DXVECTOR3 v(x, y, z); + glw_state->matrixStack[glw_state->matrixMode]->RotateAxisLocal(&v, angle); + glw_state->matricesDirty[glw_state->matrixMode] = true; +} + +static void dllScaled(GLdouble x, GLdouble y, GLdouble z) +{ + assert(0); +} + +static void dllScalef(GLfloat x, GLfloat y, GLfloat z) +{ + glw_state->matrixStack[glw_state->matrixMode]->Scale(x, y, z); + glw_state->matricesDirty[glw_state->matrixMode] = true; +} + +static void dllScissor(GLint x, GLint y, GLsizei width, GLsizei height) +{ +#ifdef _XBOX + _fixupScreenCoords(x, y, width, height); + + glw_state->scissorBox.x1 = x; + glw_state->scissorBox.y1 = y; + glw_state->scissorBox.x2 = x + width; + glw_state->scissorBox.y2 = y + height; + + if (glw_state->scissorEnable) + { + glw_state->device->SetScissors(1, FALSE, &glw_state->scissorBox); + } +#endif +} + +static void dllSelectBuffer(GLsizei size, GLuint *buffer) +{ + assert(0); +} + +static void dllShadeModel(GLenum mode) +{ + D3DSHADEMODE m; + switch (mode) + { + case GL_FLAT: m = D3DSHADE_FLAT; break; + case GL_SMOOTH: default: m = D3DSHADE_GOURAUD; break; + } + + glw_state->device->SetRenderState(D3DRS_SHADEMODE, m); +} + +static void dllStencilFunc(GLenum func, GLint ref, GLuint mask) +{ + D3DCMPFUNC f = _convertCompare(func); + + glw_state->device->SetRenderState(D3DRS_STENCILFUNC, f); + glw_state->device->SetRenderState(D3DRS_STENCILREF, ref); + glw_state->device->SetRenderState(D3DRS_STENCILMASK, mask); +} + +static void dllStencilMask(GLuint mask) +{ + glw_state->device->SetRenderState(D3DRS_STENCILWRITEMASK, mask); +} + +static D3DSTENCILOP _convertStencilOp(GLenum op) +{ + switch (op) + { + default: case GL_KEEP: return D3DSTENCILOP_KEEP; + case GL_ZERO: return D3DSTENCILOP_ZERO; + case GL_REPLACE: return D3DSTENCILOP_REPLACE; + case GL_INCR: return D3DSTENCILOP_INCR; + case GL_DECR: return D3DSTENCILOP_DECR; + case GL_INVERT: return D3DSTENCILOP_INVERT; + } +} + +static void dllStencilOp(GLenum fail, GLenum zfail, GLenum zpass) +{ + D3DSTENCILOP f = _convertStencilOp(fail); + D3DSTENCILOP zf = _convertStencilOp(zfail); + D3DSTENCILOP zp = _convertStencilOp(zpass); + + glw_state->device->SetRenderState(D3DRS_STENCILFAIL, f); + glw_state->device->SetRenderState(D3DRS_STENCILZFAIL, zf); + glw_state->device->SetRenderState(D3DRS_STENCILPASS, zp); +} + +static void dllTexCoord1d(GLdouble s) +{ + assert(0); +} + +static void dllTexCoord1dv(const GLdouble *v) +{ + assert(0); +} + +static void dllTexCoord1f(GLfloat s) +{ + assert(0); +} + +static void dllTexCoord1fv(const GLfloat *v) +{ + assert(0); +} + +static void dllTexCoord1i(GLint s) +{ + assert(0); +} + +static void dllTexCoord1iv(const GLint *v) +{ + assert(0); +} + +static void dllTexCoord1s(GLshort s) +{ + assert(0); +} + +static void dllTexCoord1sv(const GLshort *v) +{ + assert(0); +} + +static void setTexCoord(float s, float t) +{ + assert(glw_state->inDrawBlock); + + _handleDrawOverflow(); + + int off = 0; + if(glw_state->normalArrayState) + off = 3; + + DWORD* push = &glw_state->drawArray[ + glw_state->numVertices * glw_state->drawStride + + 4 + off + glw_state->serverTU * 2]; + + *push++ = *((DWORD*)&s); + *push++ = *((DWORD*)&t); +} + +static void dllTexCoord2d(GLdouble s, GLdouble t) +{ + assert(0); +} + +static void dllTexCoord2dv(const GLdouble *v) +{ + assert(0); +} + +static void dllTexCoord2f(GLfloat s, GLfloat t) +{ + setTexCoord(s, t); +} + +static void dllTexCoord2fv(const GLfloat *v) +{ + setTexCoord(v[0], v[1]); +} + +static void dllTexCoord2i(GLint s, GLint t) +{ + assert(0); +} + +static void dllTexCoord2iv(const GLint *v) +{ + assert(0); +} + +static void dllTexCoord2s(GLshort s, GLshort t) +{ + assert(0); +} + +static void dllTexCoord2sv(const GLshort *v) +{ + assert(0); +} + +static void dllTexCoord3d(GLdouble s, GLdouble t, GLdouble r) +{ + assert(0); +} + +static void dllTexCoord3dv(const GLdouble *v) +{ + assert(0); +} + +static void dllTexCoord3f(GLfloat s, GLfloat t, GLfloat r) +{ + assert(0); +} + +static void dllTexCoord3fv(const GLfloat *v) +{ + assert(0); +} + +static void dllTexCoord3i(GLint s, GLint t, GLint r) +{ + assert(0); +} + +static void dllTexCoord3iv(const GLint *v) +{ + assert(0); +} + +static void dllTexCoord3s(GLshort s, GLshort t, GLshort r) +{ + assert(0); +} + +static void dllTexCoord3sv(const GLshort *v) +{ + assert(0); +} + +static void dllTexCoord4d(GLdouble s, GLdouble t, GLdouble r, GLdouble q) +{ + assert(0); +} + +static void dllTexCoord4dv(const GLdouble *v) +{ + assert(0); +} + +static void dllTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat q) +{ + assert(0); +} + +static void dllTexCoord4fv(const GLfloat *v) +{ + assert(0); +} + +static void dllTexCoord4i(GLint s, GLint t, GLint r, GLint q) +{ + assert(0); +} + +static void dllTexCoord4iv(const GLint *v) +{ + assert(0); +} + +static void dllTexCoord4s(GLshort s, GLshort t, GLshort r, GLshort q) +{ + assert(0); +} + +static void dllTexCoord4sv(const GLshort *v) +{ + assert(0); +} + +static void dllTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) +{ + assert(size == 2 && type == GL_FLOAT); + + stride = (stride == 0) ? (sizeof(GLfloat) * 2) : stride; + + glw_state->texCoordPointer[glw_state->clientTU] = pointer; + glw_state->texCoordStride[glw_state->clientTU] = stride; +} + +static void dllTexEnvf(GLenum target, GLenum pname, GLfloat param) +{ + qglTexEnvi(target, pname, (GLint)param); +} + +static void dllTexEnvfv(GLenum target, GLenum pname, const GLfloat *params) +{ + assert(0); +} + +static void dllTexEnvi(GLenum target, GLenum pname, GLint param) +{ + assert(target == GL_TEXTURE_ENV && pname == GL_TEXTURE_ENV_MODE); + + /*glwstate_t::TextureInfo* info = _getCurrentTexture(glw_state->serverTU); + if (!info) return;*/ + + D3DTEXTUREOP env; + switch (param) + { + case GL_MODULATE: default: env = D3DTOP_MODULATE; break; + case GL_REPLACE: env = D3DTOP_SELECTARG1; break; + // MATT! - I use GL_DECAL as the bumpmapping state + case GL_DECAL: env = D3DTOP_DOTPRODUCT3; break; + case GL_ADD: env = D3DTOP_ADD; break; + case GL_NONE: env = D3DTOP_DISABLE; break; + } + + if (glw_state->textureEnv[glw_state->serverTU] != env) + { + glw_state->textureEnv[glw_state->serverTU] = env; + glw_state->textureStageDirty[glw_state->serverTU] = true; + } +} + +static void dllTexEnviv(GLenum target, GLenum pname, const GLint *params) +{ + assert(0); +} + +static void dllTexGend(GLenum coord, GLenum pname, GLdouble param) +{ + assert(0); +} + +static void dllTexGendv(GLenum coord, GLenum pname, const GLdouble *params) +{ + assert(0); +} + +static void dllTexGenf(GLenum coord, GLenum pname, GLfloat param) +{ + assert(0); +} + +static void dllTexGenfv(GLenum coord, GLenum pname, const GLfloat *params) +{ + assert(0); +} + +static void dllTexGeni(GLenum coord, GLenum pname, GLint param) +{ + assert(0); +} + +static void dllTexGeniv(GLenum coord, GLenum pname, const GLint *params) +{ + assert(0); +} + +static void dllTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels) +{ + assert(0); +} + +static void _texImageDDS(glwstate_t::TextureInfo* info, GLint numlevels, GLsizei width, GLsizei height, GLenum format, const GLvoid *pixels) +{ + D3DFORMAT f = D3DFMT_UNKNOWN; + switch( format ) + { + case GL_DDS1_EXT: + f = D3DFMT_DXT1; + break; + case GL_DDS5_EXT: + f = D3DFMT_DXT5; + break; + case GL_DDS_RGB16_EXT: + f = D3DFMT_R5G6B5; + break; + case GL_DDS_RGBA32_EXT: + f = D3DFMT_A8R8G8B8; + break; + } + + if( numlevels == 0) + numlevels = 1; + + info->mipmap = new IDirect3DTexture9; + DWORD pixelSize = XGSetTextureHeader( width, + height, + numlevels, + 0, + f, + 0, + info->mipmap, + 0, + 0 ); + + DWORD fileSize = Z_Size(const_cast(pixels)); + info->data = gTextures.Allocate( pixelSize, glw_state->currentTexture[glw_state->serverTU] ); + // Lightmaps need to be swizzled, they're in 565: + if( f == D3DFMT_R5G6B5 ) + { + byte *pSrc = ((byte *)pixels)+(fileSize-pixelSize); + byte *pDst = (byte *)info->data; + DWORD level = numlevels; + DWORD curWidth = width; + DWORD curHeight = height; + while (level--) + { + XGSwizzleRect(pSrc, 0, NULL, pDst, curWidth, curHeight, NULL, 2); + pSrc += curWidth*curHeight*2; + pDst += curWidth*curHeight*2; + curWidth >>= 1; + curHeight >>= 1; + } + } + else + { + memcpy( info->data, ((byte *)pixels)+(fileSize-pixelSize), pixelSize ); + } + info->mipmap->Register( info->data ); +} + +static void _texImageRGBA(glwstate_t::TextureInfo* info, GLint numlevels, GLint internalformat, GLsizei width, GLsizei height, GLenum format, const GLvoid *pixels) +{ + // Fix number of levels: + if( numlevels == 0 ) + numlevels = 1; + + // What format should the resultant texture be: + D3DFORMAT dstFormat = D3DFMT_UNKNOWN; + switch(internalformat) + { + case GL_RGB5: + case GL_RGB4_S3TC: + dstFormat = D3DFMT_R5G6B5; + break; + + case GL_RGBA4: + dstFormat = D3DFMT_A4R4G4B4; + break; + + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + dstFormat = D3DFMT_DXT1; + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + dstFormat = D3DFMT_DXT5; + break; + + case GL_RGB8: + case 3: + dstFormat = D3DFMT_X8R8G8B8; + break; + + case GL_LIN_RGBA8: + dstFormat = D3DFMT_LIN_A8R8G8B8; + break; + case GL_LIN_RGB8: + dstFormat = D3DFMT_LIN_X8R8G8B8; + break; + + case GL_RGBA8: + case 4: + dstFormat = D3DFMT_A8R8G8B8; + break; + case GL_RGB: + dstFormat = D3DFMT_X8R8G8B8; + break; + + default: + assert(0); + } + + // What format is our source data in: + D3DFORMAT srcFormat = D3DFMT_UNKNOWN; + float bpp; + int pitch; + switch(format) + { + case GL_RGB: + srcFormat = D3DFMT_X8R8G8B8; + bpp = 3; + break; + + case GL_RGBA: + srcFormat = D3DFMT_A8R8G8B8; + bpp = 4; + break; + + case GL_LIN_RGBA: + srcFormat = D3DFMT_LIN_A8R8G8B8; + bpp = 4; + break; + + case GL_LIN_RGB: + srcFormat = D3DFMT_LIN_X8R8G8B8; + bpp = 4; + break; + + case GL_LIN_RGB8: + srcFormat = D3DFMT_LIN_X8R8G8B8; + bpp = 4; + break; + + case GL_RGB8: + srcFormat = D3DFMT_X8R8G8B8; + bpp = 4; + break; + + case GL_RGB_SWIZZLE_EXT: + srcFormat = D3DFMT_R5G6B5; + bpp = 2; + break; + + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + srcFormat = D3DFMT_DXT1; + bpp = 0.5; + break; + + default: + assert(0); + } + + pitch = (int)((float)width * bpp); + + RECT srcRect; + + srcRect.top = 0; + srcRect.left = 0; + srcRect.right = width; + srcRect.bottom = height; + + info->mipmap = new IDirect3DTexture9; + DWORD pixelSize = XGSetTextureHeader( width, + height, + numlevels, + 0, + dstFormat, + 0, + info->mipmap, + 0, + 0 ); + + info->data = gTextures.Allocate( pixelSize, glw_state->currentTexture[glw_state->serverTU] ); + info->mipmap->Register( info->data ); + + IDirect3DSurface8 *pSurf = NULL; + info->mipmap->GetSurfaceLevel( 0, &pSurf ); + + D3DXLoadSurfaceFromMemory( pSurf, + NULL, + NULL, + pixels, + srcFormat, + pitch, + NULL, + &srcRect, + D3DX_DEFAULT, + 0 ); + + pSurf->Release(); + + // Generate mipmaps + if( numlevels > 1) + { + D3DXFilterTexture( info->mipmap, + NULL, + D3DX_DEFAULT, + D3DX_DEFAULT ); + } +} + +// EXTENSION: glTexImage2D plus "numlevels" number of mipmaps +static void dllTexImage2DEXT(GLenum target, GLint level, GLint numlevels, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) +{ + assert(target == GL_TEXTURE_2D && border == 0 && type == GL_UNSIGNED_BYTE); + + // In Direct3D, setting 0 for number of mipmap + // levels means create the whole chain.... + /*if(numlevels == 0) + numlevels = 1;*/ + + glwstate_t::TextureInfo* info; + + glwstate_t::texturexlat_t::iterator current = + glw_state->textureXlat.find( + glw_state->currentTexture[glw_state->serverTU]); + + // If we already have a texture bound to this ID, remove it. + if (current != glw_state->textureXlat.end()) + { + info = ¤t->second; + + delete info->mipmap; + assert( 0 ); // Why is this happening? We're leaking texture memory! + } + // Otherwise, initialize it. + else + { + info = &glw_state->textureXlat[ + glw_state->currentTexture[glw_state->serverTU]]; + + info->minFilter = D3DTEXF_NONE; + info->mipFilter = D3DTEXF_NONE; + info->magFilter = D3DTEXF_NONE; + info->anisotropy = 1.f; + info->wrapU = D3DTADDRESS_CLAMP; + info->wrapV = D3DTADDRESS_CLAMP; + + glw_state->textureStageDirty[glw_state->serverTU] = true; + } + + // force any DX allocs to temp memory +// Z_SetNewDeleteTemporary(true); + + if (format == GL_DDS1_EXT || + format == GL_DDS5_EXT || + format == GL_DDS_RGB16_EXT || + format == GL_DDS_RGBA32_EXT) + { + _texImageDDS(info, numlevels, width, height, format, pixels); + } + else + { + _texImageRGBA(info, numlevels, + internalformat, width, height, + format, pixels); + } + + // Done DX calls to new and delete +// Z_SetNewDeleteTemporary(false); + +#if MEMORY_PROFILE + texMemSize += getTexMemSize(info->mipmap); +#endif +} + +static void dllTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) +{ + dllTexImage2DEXT(target, level, 1, internalformat, width, height, border, format, type, pixels); +} + +static void dllTexParameteri(GLenum target, GLenum pname, GLint param) +{ + assert(target == GL_TEXTURE_2D); + + if (glw_state->currentTexture[glw_state->serverTU] == 0) return; + + glwstate_t::TextureInfo* info = _getCurrentTexture(glw_state->serverTU); + if (!info) return; + + glw_state->textureStageDirty[glw_state->serverTU] = true; + + switch (pname) + { + case GL_TEXTURE_MIN_FILTER: + switch (param) + { + case GL_NEAREST: + info->minFilter = D3DTEXF_POINT; + info->mipFilter = D3DTEXF_NONE; + break; + case GL_LINEAR: + info->minFilter = D3DTEXF_LINEAR; + info->mipFilter = D3DTEXF_NONE; + break; + case GL_NEAREST_MIPMAP_NEAREST: + info->minFilter = D3DTEXF_POINT; + info->mipFilter = D3DTEXF_POINT; + break; + case GL_LINEAR_MIPMAP_NEAREST: + info->minFilter = D3DTEXF_LINEAR; + info->mipFilter = D3DTEXF_POINT; + break; + case GL_NEAREST_MIPMAP_LINEAR: + info->minFilter = D3DTEXF_POINT; + info->mipFilter = D3DTEXF_LINEAR; + break; + case GL_LINEAR_MIPMAP_LINEAR: + info->minFilter = D3DTEXF_LINEAR; + info->mipFilter = D3DTEXF_LINEAR; + break; + } + info->anisotropy = 1.f; + break; + case GL_TEXTURE_MAG_FILTER: + switch (param) + { + case GL_NEAREST: + info->magFilter = D3DTEXF_POINT; + break; + case GL_LINEAR: + info->magFilter = D3DTEXF_LINEAR; + break; + } + info->anisotropy = 1.f; + break; + case GL_TEXTURE_WRAP_S: + switch (param) + { + case GL_REPEAT: info->wrapU = D3DTADDRESS_WRAP; break; + case GL_CLAMP: info->wrapU = D3DTADDRESS_CLAMP; break; + } + break; + case GL_TEXTURE_WRAP_T: + switch (param) + { + case GL_REPEAT: info->wrapV = D3DTADDRESS_WRAP; break; + case GL_CLAMP: info->wrapV = D3DTADDRESS_CLAMP; break; + } + break; + case GL_TEXTURE_MAX_ANISOTROPY_EXT: + info->anisotropy = (float)param; + info->minFilter = D3DTEXF_ANISOTROPIC; + info->magFilter = D3DTEXF_ANISOTROPIC; + break; + } +} + +static void dllTexParameterf(GLenum target, GLenum pname, GLfloat param) +{ + dllTexParameteri(target, pname, param); +} + +static void dllTexParameterfv(GLenum target, GLenum pname, const GLfloat *params) +{ + // Intentionally left blank +} + +static void dllTexParameteriv(GLenum target, GLenum pname, const GLint *params) +{ + // Intentionally left blank +} + +static void dllTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels) +{ + assert(0); +} + +static void dllTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) +{ + assert(target == GL_TEXTURE_2D && level == 0 && type == GL_UNSIGNED_BYTE); + + glwstate_t::TextureInfo* info = _getCurrentTexture(glw_state->serverTU); + if (info == NULL) return; + + RECT sr; + sr.top = 0; + sr.left = 0; + sr.right = width; + sr.bottom = height; + + RECT dr; + dr.top = xoffset; + dr.left = yoffset; + dr.right = xoffset + width; + dr.bottom = yoffset + height; + + Z_SetNewDeleteTemporary(true); + + LPDIRECT3DSURFACE8 surf; + info->mipmap->GetSurfaceLevel(0, &surf); + + // We use the supplied format to handle pixel data correctly, the way OGL would + D3DFORMAT srcFormat; + switch(format) + { + case GL_RGB: + srcFormat = D3DFMT_LIN_X8R8G8B8; + break; + + case GL_RGBA: + srcFormat = D3DFMT_LIN_A8R8G8B8; + break; + + default: + assert(0 && "Unsupported format in dllTexSubImage2D"); + return; + } + + D3DXLoadSurfaceFromMemory(surf, NULL, &dr, pixels, + srcFormat, width * 4, NULL, &sr, D3DX_DEFAULT, 0); + + surf->Release(); + + Z_SetNewDeleteTemporary(false); +} + +static void dllTranslated(GLdouble x, GLdouble y, GLdouble z) +{ + assert(0); +} + +static void dllTranslatef(GLfloat x, GLfloat y, GLfloat z) +{ + glw_state->matrixStack[glw_state->matrixMode]->TranslateLocal(x, y, z); + glw_state->matricesDirty[glw_state->matrixMode] = true; +} + +static void setVertex(float x, float y, float z) +{ + assert(glw_state->inDrawBlock); + + _handleDrawOverflow(); + + DWORD* push = &glw_state->drawArray[glw_state->numVertices * glw_state->drawStride]; + push[0] = *((DWORD*)&x); + push[1] = *((DWORD*)&y); + push[2] = *((DWORD*)&z); + push[3] = glw_state->currentColor; + + ++glw_state->numVertices; +} + +static void dllVertex2d(GLdouble x, GLdouble y) +{ + assert(0); +} + +static void dllVertex2dv(const GLdouble *v) +{ + assert(0); +} + +static void dllVertex2f(GLfloat x, GLfloat y) +{ + setVertex(x, y, 0.f); +} + +static void dllVertex2fv(const GLfloat *v) +{ + setVertex(v[0], v[1], 0.f); +} + +static void dllVertex2i(GLint x, GLint y) +{ + assert(0); +} + +static void dllVertex2iv(const GLint *v) +{ + assert(0); +} + +static void dllVertex2s(GLshort x, GLshort y) +{ + assert(0); +} + +static void dllVertex2sv(const GLshort *v) +{ + assert(0); +} + +static void dllVertex3d(GLdouble x, GLdouble y, GLdouble z) +{ + assert(0); +} + +static void dllVertex3dv(const GLdouble *v) +{ + assert(0); +} + +static void dllVertex3f(GLfloat x, GLfloat y, GLfloat z) +{ + setVertex(x, y, z); +} + +static void dllVertex3fv(const GLfloat *v) +{ + setVertex(v[0], v[1], v[2]); +} + +static void dllVertex3i(GLint x, GLint y, GLint z) +{ + assert(0); +} + +static void dllVertex3iv(const GLint *v) +{ + assert(0); +} + +static void dllVertex3s(GLshort x, GLshort y, GLshort z) +{ + assert(0); +} + +static void dllVertex3sv(const GLshort *v) +{ + assert(0); +} + +static void dllVertex4d(GLdouble x, GLdouble y, GLdouble z, GLdouble w) +{ + assert(0); +} + +static void dllVertex4dv(const GLdouble *v) +{ + assert(0); +} + +static void dllVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) +{ + setVertex(x, y, z); +} + +static void dllVertex4fv(const GLfloat *v) +{ + setVertex(v[0], v[1], v[2]); +} + +static void dllVertex4i(GLint x, GLint y, GLint z, GLint w) +{ + assert(0); +} + +static void dllVertex4iv(const GLint *v) +{ + assert(0); +} + +static void dllVertex4s(GLshort x, GLshort y, GLshort z, GLshort w) +{ + assert(0); +} + +static void dllVertex4sv(const GLshort *v) +{ + assert(0); +} + +static void dllVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer) +{ + assert(size == 3 && type == GL_FLOAT); + + stride = (stride == 0) ? (sizeof(GLfloat) * 3) : stride; + + glw_state->vertexPointer = pointer; + glw_state->vertexStride = stride; +} + +static void dllViewport(GLint x, GLint y, GLsizei width, GLsizei height) +{ + _fixupScreenCoords(x, y, width, height); + + glw_state->viewport.X = x; + glw_state->viewport.Y = y; + glw_state->viewport.Width = width; + glw_state->viewport.Height = height; + glw_state->device->SetViewport(&glw_state->viewport); +} + + +static void dllMultiTexCoord2fARB(GLenum texture, GLfloat s, GLfloat t) +{ + assert(glw_state->inDrawBlock); + + _handleDrawOverflow(); + + DWORD* push = &glw_state->drawArray[ + glw_state->numVertices * glw_state->drawStride + + 4 + (texture - GL_TEXTURE0_ARB) * 2]; + + *push++ = *((DWORD*)&s); + *push++ = *((DWORD*)&t); +} + +static void dllActiveTextureARB(GLenum texture) +{ + assert(GLW_MAX_TEXTURE_STAGES > texture - GL_TEXTURE0_ARB); + glw_state->serverTU = texture - GL_TEXTURE0_ARB; +} + +static void dllClientActiveTextureARB(GLenum texture) +{ + assert(GLW_MAX_TEXTURE_STAGES > texture - GL_TEXTURE0_ARB); + glw_state->clientTU = texture - GL_TEXTURE0_ARB; +} + + +/* +** QGL_Shutdown +** +** Unloads the specified DLL then nulls out all the proc pointers. This +** is only called during a hard shutdown of the OGL subsystem (e.g. vid_restart). +*/ +void QGL_Shutdown( void ) +{ + VID_Printf( PRINT_ALL, "...shutting down QGL\n" ); + + qglAccum = NULL; + qglAlphaFunc = NULL; + qglAreTexturesResident = NULL; + qglArrayElement = NULL; + qglBegin = NULL; + qglBeginEXT = NULL; + qglBeginFrame = NULL; + qglBeginShadow = NULL; + qglBindTexture = NULL; + qglBitmap = NULL; + qglBlendFunc = NULL; + qglCallList = NULL; + qglCallLists = NULL; + qglClear = NULL; + qglClearAccum = NULL; + qglClearColor = NULL; + qglClearDepth = NULL; + qglClearIndex = NULL; + qglClearStencil = NULL; + qglClipPlane = NULL; + qglColor3b = NULL; + qglColor3bv = NULL; + qglColor3d = NULL; + qglColor3dv = NULL; + qglColor3f = NULL; + qglColor3fv = NULL; + qglColor3i = NULL; + qglColor3iv = NULL; + qglColor3s = NULL; + qglColor3sv = NULL; + qglColor3ub = NULL; + qglColor3ubv = NULL; + qglColor3ui = NULL; + qglColor3uiv = NULL; + qglColor3us = NULL; + qglColor3usv = NULL; + qglColor4b = NULL; + qglColor4bv = NULL; + qglColor4d = NULL; + qglColor4dv = NULL; + qglColor4f = NULL; + qglColor4fv = NULL; + qglColor4i = NULL; + qglColor4iv = NULL; + qglColor4s = NULL; + qglColor4sv = NULL; + qglColor4ub = NULL; + qglColor4ubv = NULL; + qglColor4ui = NULL; + qglColor4uiv = NULL; + qglColor4us = NULL; + qglColor4usv = NULL; + qglColorMask = NULL; + qglColorMaterial = NULL; + qglColorPointer = NULL; + qglCopyPixels = NULL; + qglCopyTexImage1D = NULL; + qglCopyTexImage2D = NULL; + qglCopyTexSubImage1D = NULL; + qglCopyTexSubImage2D = NULL; + qglCullFace = NULL; + qglDeleteLists = NULL; + qglDeleteTextures = NULL; + qglDepthFunc = NULL; + qglDepthMask = NULL; + qglDepthRange = NULL; + qglDisable = NULL; + qglDisableClientState = NULL; + qglDrawArrays = NULL; + qglDrawBuffer = NULL; + qglDrawElements = NULL; + qglDrawPixels = NULL; + qglEdgeFlag = NULL; + qglEdgeFlagPointer = NULL; + qglEdgeFlagv = NULL; + qglEnable = NULL; + qglEnableClientState = NULL; + qglEnd = NULL; + qglEndFrame = NULL; + qglEndShadow = NULL; + qglEndList = NULL; + qglEvalCoord1d = NULL; + qglEvalCoord1dv = NULL; + qglEvalCoord1f = NULL; + qglEvalCoord1fv = NULL; + qglEvalCoord2d = NULL; + qglEvalCoord2dv = NULL; + qglEvalCoord2f = NULL; + qglEvalCoord2fv = NULL; + qglEvalMesh1 = NULL; + qglEvalMesh2 = NULL; + qglEvalPoint1 = NULL; + qglEvalPoint2 = NULL; + qglFeedbackBuffer = NULL; + qglFinish = NULL; + qglFlush = NULL; + qglFlushShadow = NULL; + qglFogf = NULL; + qglFogfv = NULL; + qglFogi = NULL; + qglFogiv = NULL; + qglFrontFace = NULL; + qglFrustum = NULL; + qglGenLists = NULL; + qglGenTextures = NULL; + qglGetBooleanv = NULL; + qglGetClipPlane = NULL; + qglGetDoublev = NULL; + qglGetError = NULL; + qglGetFloatv = NULL; + qglGetIntegerv = NULL; + qglGetLightfv = NULL; + qglGetLightiv = NULL; + qglGetMapdv = NULL; + qglGetMapfv = NULL; + qglGetMapiv = NULL; + qglGetMaterialfv = NULL; + qglGetMaterialiv = NULL; + qglGetPixelMapfv = NULL; + qglGetPixelMapuiv = NULL; + qglGetPixelMapusv = NULL; + qglGetPointerv = NULL; + qglGetPolygonStipple = NULL; + qglGetString = NULL; + qglGetTexEnvfv = NULL; + qglGetTexEnviv = NULL; + qglGetTexGendv = NULL; + qglGetTexGenfv = NULL; + qglGetTexGeniv = NULL; + qglGetTexImage = NULL; + qglGetTexLevelParameterfv = NULL; + qglGetTexLevelParameteriv = NULL; + qglGetTexParameterfv = NULL; + qglGetTexParameteriv = NULL; + qglHint = NULL; + qglIndexedTriToStrip = NULL; + qglIndexMask = NULL; + qglIndexPointer = NULL; + qglIndexd = NULL; + qglIndexdv = NULL; + qglIndexf = NULL; + qglIndexfv = NULL; + qglIndexi = NULL; + qglIndexiv = NULL; + qglIndexs = NULL; + qglIndexsv = NULL; + qglIndexub = NULL; + qglIndexubv = NULL; + qglInitNames = NULL; + qglInterleavedArrays = NULL; + qglIsEnabled = NULL; + qglIsList = NULL; + qglIsTexture = NULL; + qglLightModelf = NULL; + qglLightModelfv = NULL; + qglLightModeli = NULL; + qglLightModeliv = NULL; + qglLightf = NULL; + qglLightfv = NULL; + qglLighti = NULL; + qglLightiv = NULL; + qglLineStipple = NULL; + qglLineWidth = NULL; + qglListBase = NULL; + qglLoadIdentity = NULL; + qglLoadMatrixd = NULL; + qglLoadMatrixf = NULL; + qglLoadName = NULL; + qglLogicOp = NULL; + qglMap1d = NULL; + qglMap1f = NULL; + qglMap2d = NULL; + qglMap2f = NULL; + qglMapGrid1d = NULL; + qglMapGrid1f = NULL; + qglMapGrid2d = NULL; + qglMapGrid2f = NULL; + qglMaterialf = NULL; + qglMaterialfv = NULL; + qglMateriali = NULL; + qglMaterialiv = NULL; + qglMatrixMode = NULL; + qglMultMatrixd = NULL; + qglMultMatrixf = NULL; + qglNewList = NULL; + qglNormal3b = NULL; + qglNormal3bv = NULL; + qglNormal3d = NULL; + qglNormal3dv = NULL; + qglNormal3f = NULL; + qglNormal3fv = NULL; + qglNormal3i = NULL; + qglNormal3iv = NULL; + qglNormal3s = NULL; + qglNormal3sv = NULL; + qglNormalPointer = NULL; + qglOrtho = NULL; + qglPassThrough = NULL; + qglPixelMapfv = NULL; + qglPixelMapuiv = NULL; + qglPixelMapusv = NULL; + qglPixelStoref = NULL; + qglPixelStorei = NULL; + qglPixelTransferf = NULL; + qglPixelTransferi = NULL; + qglPixelZoom = NULL; + qglPointSize = NULL; + qglPolygonMode = NULL; + qglPolygonOffset = NULL; + qglPolygonStipple = NULL; + qglPopAttrib = NULL; + qglPopClientAttrib = NULL; + qglPopMatrix = NULL; + qglPopName = NULL; + qglPrioritizeTextures = NULL; + qglPushAttrib = NULL; + qglPushClientAttrib = NULL; + qglPushMatrix = NULL; + qglPushName = NULL; + qglRasterPos2d = NULL; + qglRasterPos2dv = NULL; + qglRasterPos2f = NULL; + qglRasterPos2fv = NULL; + qglRasterPos2i = NULL; + qglRasterPos2iv = NULL; + qglRasterPos2s = NULL; + qglRasterPos2sv = NULL; + qglRasterPos3d = NULL; + qglRasterPos3dv = NULL; + qglRasterPos3f = NULL; + qglRasterPos3fv = NULL; + qglRasterPos3i = NULL; + qglRasterPos3iv = NULL; + qglRasterPos3s = NULL; + qglRasterPos3sv = NULL; + qglRasterPos4d = NULL; + qglRasterPos4dv = NULL; + qglRasterPos4f = NULL; + qglRasterPos4fv = NULL; + qglRasterPos4i = NULL; + qglRasterPos4iv = NULL; + qglRasterPos4s = NULL; + qglRasterPos4sv = NULL; + qglReadBuffer = NULL; + qglReadPixels = NULL; + qglRectd = NULL; + qglRectdv = NULL; + qglRectf = NULL; + qglRectfv = NULL; + qglRecti = NULL; + qglRectiv = NULL; + qglRects = NULL; + qglRectsv = NULL; + qglRenderMode = NULL; + qglRotated = NULL; + qglRotatef = NULL; + qglScaled = NULL; + qglScalef = NULL; + qglScissor = NULL; + qglSelectBuffer = NULL; + qglShadeModel = NULL; + qglStencilFunc = NULL; + qglStencilMask = NULL; + qglStencilOp = NULL; + qglTexCoord1d = NULL; + qglTexCoord1dv = NULL; + qglTexCoord1f = NULL; + qglTexCoord1fv = NULL; + qglTexCoord1i = NULL; + qglTexCoord1iv = NULL; + qglTexCoord1s = NULL; + qglTexCoord1sv = NULL; + qglTexCoord2d = NULL; + qglTexCoord2dv = NULL; + qglTexCoord2f = NULL; + qglTexCoord2fv = NULL; + qglTexCoord2i = NULL; + qglTexCoord2iv = NULL; + qglTexCoord2s = NULL; + qglTexCoord2sv = NULL; + qglTexCoord3d = NULL; + qglTexCoord3dv = NULL; + qglTexCoord3f = NULL; + qglTexCoord3fv = NULL; + qglTexCoord3i = NULL; + qglTexCoord3iv = NULL; + qglTexCoord3s = NULL; + qglTexCoord3sv = NULL; + qglTexCoord4d = NULL; + qglTexCoord4dv = NULL; + qglTexCoord4f = NULL; + qglTexCoord4fv = NULL; + qglTexCoord4i = NULL; + qglTexCoord4iv = NULL; + qglTexCoord4s = NULL; + qglTexCoord4sv = NULL; + qglTexCoordPointer = NULL; + qglTexEnvf = NULL; + qglTexEnvfv = NULL; + qglTexEnvi = NULL; + qglTexEnviv = NULL; + qglTexGend = NULL; + qglTexGendv = NULL; + qglTexGenf = NULL; + qglTexGenfv = NULL; + qglTexGeni = NULL; + qglTexGeniv = NULL; + qglTexImage1D = NULL; + qglTexImage2D = NULL; + qglTexImage2DEXT = NULL; + qglTexParameterf = NULL; + qglTexParameterfv = NULL; + qglTexParameteri = NULL; + qglTexParameteriv = NULL; + qglTexSubImage1D = NULL; + qglTexSubImage2D = NULL; + qglTranslated = NULL; + qglTranslatef = NULL; + qglVertex2d = NULL; + qglVertex2dv = NULL; + qglVertex2f = NULL; + qglVertex2fv = NULL; + qglVertex2i = NULL; + qglVertex2iv = NULL; + qglVertex2s = NULL; + qglVertex2sv = NULL; + qglVertex3d = NULL; + qglVertex3dv = NULL; + qglVertex3f = NULL; + qglVertex3fv = NULL; + qglVertex3i = NULL; + qglVertex3iv = NULL; + qglVertex3s = NULL; + qglVertex3sv = NULL; + qglVertex4d = NULL; + qglVertex4dv = NULL; + qglVertex4f = NULL; + qglVertex4fv = NULL; + qglVertex4i = NULL; + qglVertex4iv = NULL; + qglVertex4s = NULL; + qglVertex4sv = NULL; + qglVertexPointer = NULL; + qglViewport = NULL; + + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + qglMultiTexCoord2fARB = NULL; +} + +/* +** QGL_Init +** +** This is responsible for binding our qgl function pointers to +** the appropriate GL stuff. In Windows this means doing a +** LoadLibrary and a bunch of calls to GetProcAddress. On other +** operating systems we need to do the right thing, whatever that +** might be. +*/ +qboolean QGL_Init( const char *dllname ) +{ + qglAccum = dllAccum; + qglAlphaFunc = dllAlphaFunc; + qglAreTexturesResident = dllAreTexturesResident; + qglArrayElement = dllArrayElement; + qglBegin = dllBegin; + qglBeginEXT = dllBeginEXT; + qglBeginFrame = dllBeginFrame; + qglBeginShadow = dllBeginShadow; + qglBindTexture = dllBindTexture; + qglBitmap = dllBitmap; + qglBlendFunc = dllBlendFunc; + qglCallList = dllCallList; + qglCallLists = dllCallLists; + qglClear = dllClear; + qglClearAccum = dllClearAccum; + qglClearColor = dllClearColor; + qglClearDepth = dllClearDepth; + qglClearIndex = dllClearIndex; + qglClearStencil = dllClearStencil; + qglClipPlane = dllClipPlane; + qglColor3b = dllColor3b; + qglColor3bv = dllColor3bv; + qglColor3d = dllColor3d; + qglColor3dv = dllColor3dv; + qglColor3f = dllColor3f; + qglColor3fv = dllColor3fv; + qglColor3i = dllColor3i; + qglColor3iv = dllColor3iv; + qglColor3s = dllColor3s; + qglColor3sv = dllColor3sv; + qglColor3ub = dllColor3ub; + qglColor3ubv = dllColor3ubv; + qglColor3ui = dllColor3ui; + qglColor3uiv = dllColor3uiv; + qglColor3us = dllColor3us; + qglColor3usv = dllColor3usv; + qglColor4b = dllColor4b; + qglColor4bv = dllColor4bv; + qglColor4d = dllColor4d; + qglColor4dv = dllColor4dv; + qglColor4f = dllColor4f; + qglColor4fv = dllColor4fv; + qglColor4i = dllColor4i; + qglColor4iv = dllColor4iv; + qglColor4s = dllColor4s; + qglColor4sv = dllColor4sv; + qglColor4ub = dllColor4ub; + qglColor4ubv = dllColor4ubv; + qglColor4ui = dllColor4ui; + qglColor4uiv = dllColor4uiv; + qglColor4us = dllColor4us; + qglColor4usv = dllColor4usv; + qglColorMask = dllColorMask; + qglColorMaterial = dllColorMaterial; + qglColorPointer = dllColorPointer; + qglCopyPixels = dllCopyPixels; + qglCopyTexImage1D = dllCopyTexImage1D; + qglCopyTexImage2D = dllCopyTexImage2D; + qglCopyTexSubImage1D = dllCopyTexSubImage1D; + qglCopyTexSubImage2D = dllCopyTexSubImage2D; + qglCullFace = dllCullFace; + qglDeleteLists = dllDeleteLists; + qglDeleteTextures = dllDeleteTextures; + qglDepthFunc = dllDepthFunc; + qglDepthMask = dllDepthMask; + qglDepthRange = dllDepthRange; + qglDisable = dllDisable; + qglDisableClientState = dllDisableClientState; + qglDrawArrays = dllDrawArrays; + qglDrawBuffer = dllDrawBuffer; + qglDrawElements = dllDrawElements; + qglDrawPixels = dllDrawPixels; + qglEdgeFlag = dllEdgeFlag; + qglEdgeFlagPointer = dllEdgeFlagPointer; + qglEdgeFlagv = dllEdgeFlagv; + qglEnable = dllEnable ; + qglEnableClientState = dllEnableClientState ; + qglEnd = dllEnd ; + qglEndFrame = dllEndFrame ; + qglEndShadow = dllEndShadow ; + qglEndList = dllEndList ; + qglEvalCoord1d = dllEvalCoord1d ; + qglEvalCoord1dv = dllEvalCoord1dv ; + qglEvalCoord1f = dllEvalCoord1f ; + qglEvalCoord1fv = dllEvalCoord1fv ; + qglEvalCoord2d = dllEvalCoord2d ; + qglEvalCoord2dv = dllEvalCoord2dv ; + qglEvalCoord2f = dllEvalCoord2f ; + qglEvalCoord2fv = dllEvalCoord2fv ; + qglEvalMesh1 = dllEvalMesh1 ; + qglEvalMesh2 = dllEvalMesh2 ; + qglEvalPoint1 = dllEvalPoint1 ; + qglEvalPoint2 = dllEvalPoint2 ; + qglFeedbackBuffer = dllFeedbackBuffer ; + qglFinish = dllFinish ; + qglFlush = dllFlush ; + qglFlushShadow = dllFlushShadow ; + qglFogf = dllFogf ; + qglFogfv = dllFogfv ; + qglFogi = dllFogi ; + qglFogiv = dllFogiv ; + qglFrontFace = dllFrontFace ; + qglFrustum = dllFrustum ; + qglGenLists = dllGenLists ; + qglGenTextures = dllGenTextures ; + qglGetBooleanv = dllGetBooleanv ; + qglGetClipPlane = dllGetClipPlane ; + qglGetDoublev = dllGetDoublev ; + qglGetError = dllGetError ; + qglGetFloatv = dllGetFloatv ; + qglGetIntegerv = dllGetIntegerv ; + qglGetLightfv = dllGetLightfv ; + qglGetLightiv = dllGetLightiv ; + qglGetMapdv = dllGetMapdv ; + qglGetMapfv = dllGetMapfv ; + qglGetMapiv = dllGetMapiv ; + qglGetMaterialfv = dllGetMaterialfv ; + qglGetMaterialiv = dllGetMaterialiv ; + qglGetPixelMapfv = dllGetPixelMapfv ; + qglGetPixelMapuiv = dllGetPixelMapuiv ; + qglGetPixelMapusv = dllGetPixelMapusv ; + qglGetPointerv = dllGetPointerv ; + qglGetPolygonStipple = dllGetPolygonStipple ; + qglGetString = dllGetString ; + qglGetTexEnvfv = dllGetTexEnvfv ; + qglGetTexEnviv = dllGetTexEnviv ; + qglGetTexGendv = dllGetTexGendv ; + qglGetTexGenfv = dllGetTexGenfv ; + qglGetTexGeniv = dllGetTexGeniv ; + qglGetTexImage = dllGetTexImage ; +// qglGetTexLevelParameterfv = dllGetTexLevelParameterfv ; +// qglGetTexLevelParameteriv = dllGetTexLevelParameteriv ; + qglGetTexParameterfv = dllGetTexParameterfv ; + qglGetTexParameteriv = dllGetTexParameteriv ; + qglHint = dllHint ; + qglIndexedTriToStrip = dllIndexedTriToStrip ; + qglIndexMask = dllIndexMask ; + qglIndexPointer = dllIndexPointer ; + qglIndexd = dllIndexd ; + qglIndexdv = dllIndexdv ; + qglIndexf = dllIndexf ; + qglIndexfv = dllIndexfv ; + qglIndexi = dllIndexi ; + qglIndexiv = dllIndexiv ; + qglIndexs = dllIndexs ; + qglIndexsv = dllIndexsv ; + qglIndexub = dllIndexub ; + qglIndexubv = dllIndexubv ; + qglInitNames = dllInitNames ; + qglInterleavedArrays = dllInterleavedArrays ; + qglIsEnabled = dllIsEnabled ; + qglIsList = dllIsList ; + qglIsTexture = dllIsTexture ; + qglLightModelf = dllLightModelf ; + qglLightModelfv = dllLightModelfv ; + qglLightModeli = dllLightModeli ; + qglLightModeliv = dllLightModeliv ; + qglLightf = dllLightf ; + qglLightfv = dllLightfv ; + qglLighti = dllLighti ; + qglLightiv = dllLightiv ; + qglLineStipple = dllLineStipple ; + qglLineWidth = dllLineWidth ; + qglListBase = dllListBase ; + qglLoadIdentity = dllLoadIdentity ; + qglLoadMatrixd = dllLoadMatrixd ; + qglLoadMatrixf = dllLoadMatrixf ; + qglLoadName = dllLoadName ; + qglLogicOp = dllLogicOp ; + qglMap1d = dllMap1d ; + qglMap1f = dllMap1f ; + qglMap2d = dllMap2d ; + qglMap2f = dllMap2f ; + qglMapGrid1d = dllMapGrid1d ; + qglMapGrid1f = dllMapGrid1f ; + qglMapGrid2d = dllMapGrid2d ; + qglMapGrid2f = dllMapGrid2f ; + qglMaterialf = dllMaterialf ; + qglMaterialfv = dllMaterialfv ; + qglMateriali = dllMateriali ; + qglMaterialiv = dllMaterialiv ; + qglMatrixMode = dllMatrixMode ; + qglMultMatrixd = dllMultMatrixd ; + qglMultMatrixf = dllMultMatrixf ; + qglNewList = dllNewList ; + qglNormal3b = dllNormal3b ; + qglNormal3bv = dllNormal3bv ; + qglNormal3d = dllNormal3d ; + qglNormal3dv = dllNormal3dv ; + qglNormal3f = dllNormal3f ; + qglNormal3fv = dllNormal3fv ; + qglNormal3i = dllNormal3i ; + qglNormal3iv = dllNormal3iv ; + qglNormal3s = dllNormal3s ; + qglNormal3sv = dllNormal3sv ; + qglNormalPointer = dllNormalPointer ; + qglOrtho = dllOrtho ; + qglPassThrough = dllPassThrough ; + qglPixelMapfv = dllPixelMapfv ; + qglPixelMapuiv = dllPixelMapuiv ; + qglPixelMapusv = dllPixelMapusv ; + qglPixelStoref = dllPixelStoref ; + qglPixelStorei = dllPixelStorei ; + qglPixelTransferf = dllPixelTransferf ; + qglPixelTransferi = dllPixelTransferi ; + qglPixelZoom = dllPixelZoom ; + qglPointSize = dllPointSize ; + qglPolygonMode = dllPolygonMode ; + qglPolygonOffset = dllPolygonOffset ; + qglPolygonStipple = dllPolygonStipple ; + qglPopAttrib = dllPopAttrib ; + qglPopClientAttrib = dllPopClientAttrib ; + qglPopMatrix = dllPopMatrix ; + qglPopName = dllPopName ; + qglPrioritizeTextures = dllPrioritizeTextures ; + qglPushAttrib = dllPushAttrib ; + qglPushClientAttrib = dllPushClientAttrib ; + qglPushMatrix = dllPushMatrix ; + qglPushName = dllPushName ; + qglRasterPos2d = dllRasterPos2d ; + qglRasterPos2dv = dllRasterPos2dv ; + qglRasterPos2f = dllRasterPos2f ; + qglRasterPos2fv = dllRasterPos2fv ; + qglRasterPos2i = dllRasterPos2i ; + qglRasterPos2iv = dllRasterPos2iv ; + qglRasterPos2s = dllRasterPos2s ; + qglRasterPos2sv = dllRasterPos2sv ; + qglRasterPos3d = dllRasterPos3d ; + qglRasterPos3dv = dllRasterPos3dv ; + qglRasterPos3f = dllRasterPos3f ; + qglRasterPos3fv = dllRasterPos3fv ; + qglRasterPos3i = dllRasterPos3i ; + qglRasterPos3iv = dllRasterPos3iv ; + qglRasterPos3s = dllRasterPos3s ; + qglRasterPos3sv = dllRasterPos3sv ; + qglRasterPos4d = dllRasterPos4d ; + qglRasterPos4dv = dllRasterPos4dv ; + qglRasterPos4f = dllRasterPos4f ; + qglRasterPos4fv = dllRasterPos4fv ; + qglRasterPos4i = dllRasterPos4i ; + qglRasterPos4iv = dllRasterPos4iv ; + qglRasterPos4s = dllRasterPos4s ; + qglRasterPos4sv = dllRasterPos4sv ; + qglReadBuffer = dllReadBuffer ; + qglReadPixels = dllReadPixels ; + qglCopyBackBufferToTexEXT = dllCopyBackBufferToTexEXT ; + qglCopyBackBufferToTex = dllCopyBackBufferToTex ; + qglRectd = dllRectd ; + qglRectdv = dllRectdv ; + qglRectf = dllRectf ; + qglRectfv = dllRectfv ; + qglRecti = dllRecti ; + qglRectiv = dllRectiv ; + qglRects = dllRects ; + qglRectsv = dllRectsv ; + qglRenderMode = dllRenderMode ; + qglRotated = dllRotated ; + qglRotatef = dllRotatef ; + qglScaled = dllScaled ; + qglScalef = dllScalef ; + qglScissor = dllScissor ; + qglSelectBuffer = dllSelectBuffer ; + qglShadeModel = dllShadeModel ; + qglStencilFunc = dllStencilFunc ; + qglStencilMask = dllStencilMask ; + qglStencilOp = dllStencilOp ; + qglTexCoord1d = dllTexCoord1d ; + qglTexCoord1dv = dllTexCoord1dv ; + qglTexCoord1f = dllTexCoord1f ; + qglTexCoord1fv = dllTexCoord1fv ; + qglTexCoord1i = dllTexCoord1i ; + qglTexCoord1iv = dllTexCoord1iv ; + qglTexCoord1s = dllTexCoord1s ; + qglTexCoord1sv = dllTexCoord1sv ; + qglTexCoord2d = dllTexCoord2d ; + qglTexCoord2dv = dllTexCoord2dv ; + qglTexCoord2f = dllTexCoord2f ; + qglTexCoord2fv = dllTexCoord2fv ; + qglTexCoord2i = dllTexCoord2i ; + qglTexCoord2iv = dllTexCoord2iv ; + qglTexCoord2s = dllTexCoord2s ; + qglTexCoord2sv = dllTexCoord2sv ; + qglTexCoord3d = dllTexCoord3d ; + qglTexCoord3dv = dllTexCoord3dv ; + qglTexCoord3f = dllTexCoord3f ; + qglTexCoord3fv = dllTexCoord3fv ; + qglTexCoord3i = dllTexCoord3i ; + qglTexCoord3iv = dllTexCoord3iv ; + qglTexCoord3s = dllTexCoord3s ; + qglTexCoord3sv = dllTexCoord3sv ; + qglTexCoord4d = dllTexCoord4d ; + qglTexCoord4dv = dllTexCoord4dv ; + qglTexCoord4f = dllTexCoord4f ; + qglTexCoord4fv = dllTexCoord4fv ; + qglTexCoord4i = dllTexCoord4i ; + qglTexCoord4iv = dllTexCoord4iv ; + qglTexCoord4s = dllTexCoord4s ; + qglTexCoord4sv = dllTexCoord4sv ; + qglTexCoordPointer = dllTexCoordPointer ; + qglTexEnvf = dllTexEnvf ; + qglTexEnvfv = dllTexEnvfv ; + qglTexEnvi = dllTexEnvi ; + qglTexEnviv = dllTexEnviv ; + qglTexGend = dllTexGend ; + qglTexGendv = dllTexGendv ; + qglTexGenf = dllTexGenf ; + qglTexGenfv = dllTexGenfv ; + qglTexGeni = dllTexGeni ; + qglTexGeniv = dllTexGeniv ; + qglTexImage1D = dllTexImage1D ; + qglTexImage2D = dllTexImage2D ; + qglTexImage2DEXT = dllTexImage2DEXT ; + qglTexParameterf = dllTexParameterf ; + qglTexParameterfv = dllTexParameterfv ; + qglTexParameteri = dllTexParameteri ; + qglTexParameteriv = dllTexParameteriv ; + qglTexSubImage1D = dllTexSubImage1D ; + qglTexSubImage2D = dllTexSubImage2D ; + qglTranslated = dllTranslated ; + qglTranslatef = dllTranslatef ; + qglVertex2d = dllVertex2d ; + qglVertex2dv = dllVertex2dv ; + qglVertex2f = dllVertex2f ; + qglVertex2fv = dllVertex2fv ; + qglVertex2i = dllVertex2i ; + qglVertex2iv = dllVertex2iv ; + qglVertex2s = dllVertex2s ; + qglVertex2sv = dllVertex2sv ; + qglVertex3d = dllVertex3d ; + qglVertex3dv = dllVertex3dv ; + qglVertex3f = dllVertex3f ; + qglVertex3fv = dllVertex3fv ; + qglVertex3i = dllVertex3i ; + qglVertex3iv = dllVertex3iv ; + qglVertex3s = dllVertex3s ; + qglVertex3sv = dllVertex3sv ; + qglVertex4d = dllVertex4d ; + qglVertex4dv = dllVertex4dv ; + qglVertex4f = dllVertex4f ; + qglVertex4fv = dllVertex4fv ; + qglVertex4i = dllVertex4i ; + qglVertex4iv = dllVertex4iv ; + qglVertex4s = dllVertex4s ; + qglVertex4sv = dllVertex4sv ; + qglVertexPointer = dllVertexPointer ; + qglViewport = dllViewport ; + + qglActiveTextureARB = dllActiveTextureARB ; + qglClientActiveTextureARB = dllClientActiveTextureARB ; + qglMultiTexCoord2fARB = dllMultiTexCoord2fARB ; + + return qtrue; +} + +void QGL_EnableLogging( qboolean enable ) +{ +} + +// Extra functions bound to d3d_ commands for controlling crazy D3D performance things +#ifndef FINAL_BUILD + +// D3D_AutoPerfData controls automatic display of performance information: +// framerate, push buffer data, etc... Usage: +// d3d_autoperf - Toggle on and off +// d3d_autoperf n - Set display frequency in ms (default 5000) +static void D3D_AutoPerfData_f( void ) +{ + static DWORD sdwInterval = 5000; + static bool sbEnabled = false; + + int numArgs = Cmd_Argc(); + + if (numArgs > 2) + { + Com_Printf("D3D_AutoPerfData_f: Too many arguments.\n"); + } + else if (numArgs <= 1) + { + sbEnabled = !sbEnabled; + D3DPERF_SetShowFrameRateInterval(sbEnabled ? sdwInterval : 0); + } + else // numArgs == 2 -> Exactly one real argument + { + int new_interval = atoi(Cmd_Argv(1)); + + if (!new_interval) + { + // Fancy way to turn it off, don't change stored interval + sbEnabled = false; + } + else + { + // Force it on + sdwInterval = new_interval; + sbEnabled = true; + } + D3DPERF_SetShowFrameRateInterval(sbEnabled ? sdwInterval : 0); + } +} + +#endif + +extern void GLimp_SetGamma(float); + + +static void _createWindow(int width, int height, int colorbits, qboolean cdsFullscreen) +{ + glConfig.colorBits = colorbits; + + if ( r_depthbits->integer == 0 ) { + if ( colorbits > 16 ) { + glConfig.depthBits = 24; + } else { + glConfig.depthBits = 16; + } + } else { + glConfig.depthBits = r_depthbits->integer; + } + + glConfig.stencilBits = r_stencilbits->integer; + if ( glConfig.depthBits < 24 ) + { + glConfig.stencilBits = 0; + } + + glConfig.displayFrequency = 75; + glConfig.stereoEnabled = qfalse; + + // VVFIXME : This is surely wrong. + glConfig.vidHeight = height; + glConfig.vidWidth = width; + +} + +enum VideoModes +{ + VM_480i = 0, + VM_480p, + VM_720p, + VM_1080i +}; + +bool bHadPersistedSurface = false; + +void GLW_Init(int width, int height, int colorbits, qboolean cdsFullscreen) +{ + glw_state = new glwstate_t; + int mode = VM_480i; + + glw_state->isWidescreen = false; + if( XGetVideoFlags() & XC_VIDEO_FLAGS_WIDESCREEN ) + { + glw_state->isWidescreen = true; + width = 640; + + if( XGetVideoFlags() & XC_VIDEO_FLAGS_HDTV_480p ) + { + width = 640; + height = 480; + mode = VM_480p; + } + + /*if( XGetVideoFlags() & XC_VIDEO_FLAGS_HDTV_720p ) + { + width = 1280; + height = 720; + mode = VM_720p; + } + + if( XGetVideoFlags() & XC_VIDEO_FLAGS_HDTV_1080i ) + { + width = 1920; + height = 1080; + mode = VM_1080i; + }*/ + } + + _createWindow(width, height, colorbits, cdsFullscreen); + + glw_state->matrixMode = glwstate_t::MatrixMode_Model; + glw_state->inDrawBlock = false; + + glw_state->serverTU = 0; + glw_state->clientTU = 0; + + glw_state->colorArrayState = false; + glw_state->vertexArrayState = false; + glw_state->normalArrayState = false; + + glw_state->cullEnable = true; + glw_state->cullMode = D3DCULL_CCW; + + glw_state->scissorEnable = false; + glw_state->scissorBox.x1 = 0; + glw_state->scissorBox.y1 = 0; + glw_state->scissorBox.x2 = glConfig.vidWidth; + glw_state->scissorBox.y2 = glConfig.vidHeight; + + glw_state->shaderMask = 0; + + glw_state->clearColor = D3DCOLOR_RGBA(255, 255, 255, 255); + glw_state->clearDepth = 1.f; + glw_state->clearStencil = 0; + + glw_state->currentColor = D3DCOLOR_RGBA(255, 255, 255, 255); + + glw_state->viewport.MinZ = 0.f; + glw_state->viewport.MaxZ = 1.f; + + for (int t = 0; t < GLW_MAX_TEXTURE_STAGES; ++t) + { + glw_state->textureEnv[t] = D3DTOP_MODULATE; + glw_state->texCoordArrayState[t] = false; + glw_state->currentTexture[t] = 0; + glw_state->textureStageDirty[t] = false; + } + + glw_state->textureBindNum = 1; + +D3DPRESENT_PARAMETERS present; + present.BackBufferWidth = width; + present.BackBufferHeight = height; + present.BackBufferFormat = D3DFMT_A8R8G8B8; + present.BackBufferCount = 1; + present.MultiSampleType = D3DMULTISAMPLE_NONE; + present.SwapEffect = D3DSWAPEFFECT_DISCARD; + present.hDeviceWindow = 0; + present.Windowed = FALSE; + present.EnableAutoDepthStencil = TRUE; + present.AutoDepthStencilFormat = D3DFMT_LIN_D24S8; + present.Flags = 0; + if( glw_state->isWidescreen ) + { + present.Flags = D3DPRESENTFLAG_WIDESCREEN; + extern void CGCam_SetWidescreen(qboolean widescreen); + if(mode == VM_480p) + { + present.Flags |= D3DPRESENTFLAG_PROGRESSIVE; + } + //else if(mode == VM_720p) + //{ + // present.Flags |= D3DPRESENTFLAG_PROGRESSIVE; + //} + //else if(mode == VM_1080i) + //{ + // present.Flags |= D3DPRESENTFLAG_INTERLACED; // | D3DPRESENTFLAG_FIELD; + //} + + + present.Flags |= D3DPRESENTFLAG_WIDESCREEN; + CGCam_SetWidescreen(qtrue); + } + + present.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; + present.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + present.BufferSurfaces[0] = NULL; + present.BufferSurfaces[1] = NULL; + present.BufferSurfaces[2] = NULL; + present.DepthStencilSurface = NULL; + + if (IDirect3D8::CreateDevice(D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, + NULL, + D3DCREATE_HARDWARE_VERTEXPROCESSING, + &present, + &glw_state->device) != D3D_OK) + { + Com_Printf("Failed to create device. That's bad.\n"); + } +// qglEnable(GL_VSYNC); + + // Immediately check to see if there's 1.2MB being wasted on a persisted surface: + IDirect3DSurface8 *pPersistedSurf; + glw_state->device->GetPersistedSurface( &pPersistedSurf ); + bHadPersistedSurface = (pPersistedSurf != NULL); + + for (int m = 0; m < glwstate_t::Num_MatrixModes; ++m) + { + D3DXCreateMatrixStack(0, &glw_state->matrixStack[m]); + glw_state->matrixStack[m]->LoadIdentity(); + glw_state->matricesDirty[m] = false; + } + + // VVFIXME: Hack - turn off lighting + dllDisable(GL_LIGHTING); + + // Set a material (for lighting) + memset( &glw_state->mtrl, 0, sizeof(D3DMATERIAL8) ); + glw_state->mtrl.Diffuse.r = glw_state->mtrl.Ambient.r = 1.0f; + glw_state->mtrl.Diffuse.g = glw_state->mtrl.Ambient.g = 1.0f; + glw_state->mtrl.Diffuse.b = glw_state->mtrl.Ambient.b = 1.0f; + glw_state->mtrl.Diffuse.a = glw_state->mtrl.Ambient.a = 1.0f; + glw_state->device->SetMaterial( &glw_state->mtrl ); + // Gamma hack + GLimp_SetGamma(1.3f); + + // Set up our directional light (used for diffuse lighting) + memset(&glw_state->dirLight, 0, sizeof(D3DLIGHT8)); + + // Set up a white point light. + glw_state->dirLight.Type = D3DLIGHT_DIRECTIONAL; + glw_state->dirLight.Diffuse.r = 1.0f; + glw_state->dirLight.Diffuse.g = 1.0f; + glw_state->dirLight.Diffuse.b = 1.0f; + glw_state->dirLight.Direction.x = 1.0f; + glw_state->dirLight.Direction.y = 0.0f; + glw_state->dirLight.Direction.z = 0.0f; + + // Don't attenuate. + glw_state->dirLight.Attenuation0 = 1.0f; + glw_state->dirLight.Range = 1000.0f; + + //glw_state->drawArray = new DWORD[SHADER_MAX_VERTEXES * 12]; + glw_state->drawArray = NULL; + +#ifdef _XBOX +#ifdef VV_LIGHTING +// glw_state->flareEffect = new FlareEffect; +// glw_state->flareEffect->Initialize(); + glw_state->lightEffects = new LightEffects; + StencilShadower.Initialize(); +#endif // VV_LIGHTING +// HDREffect.Initialize(); +#endif + +#ifndef FINAL_BUILD + Cmd_AddCommand("d3d_autoperf", D3D_AutoPerfData_f); +#endif + +#if YELLOW_MODE + initYellowMode(); +#endif // YELLOW_MODE +} + +void GLW_Shutdown(void) +{ +#ifdef _XBOX +#ifdef VV_LIGHTING + delete glw_state->lightEffects; +#endif +#endif + + for (int m = 0; m < glwstate_t::Num_MatrixModes; ++m) + { + glw_state->matrixStack[m]->Release(); + } + + glw_state->device->Release(); + +#ifdef _XBOX +// delete glw_state->flareEffect; +#endif + + delete glw_state; +} + +//----------------------------------------------------------------------------- +// Compressed Screen Shot code for the save game system +//----------------------------------------------------------------------------- + +//#define DISABLE_SCREENSHOT +#define CSS_IMAGE_HDR_SIZE 2048 +#define CSS_IMAGE_DATA_SIZE ((SAVE_GAME_IMAGE_W * SAVE_GAME_IMAGE_H) / 2 ) + +struct XprImageHeader +{ + XPR_HEADER xpr; // Standard XPR struct + IDirect3DTexture9 txt; // Standard D3D texture struct + DWORD dwEndOfHeader; // 0xFFFFFFFF +}; + +struct XprImage +{ + XprImageHeader hdr; + CHAR strPad[ CSS_IMAGE_HDR_SIZE - sizeof( XprImageHeader ) ]; + BYTE pBits[ CSS_IMAGE_DATA_SIZE ]; // data bits +}; + +//----------------------------------------------------------------------------- +// SaveCompressedScreenshot +// Saves two screenshots - one is full-screen at 256x128, and placed in +// Z:\screenshot.xbx. The other is cropped, and 64x64 for the dashboard, +// and placed in Z:\saveimage.xbx +//----------------------------------------------------------------------------- +void SaveCompressedScreenshot( void ) +{ +#ifndef DISABLE_SCREENSHOT + LPDIRECT3DSURFACE8 screenShot = 0; + HRESULT res; + glw_state->device->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &screenShot); + + // Copy over the screen shot to the new image that is CSS_IMAGE_WH x CSS_IMAGE_WH + LPDIRECT3DSURFACE8 compressedSaveGameImage = 0; + // New: we only screenshot the title-safe area, to make the image clearer in the ui: + RECT srcRect; + srcRect.left = 48; + srcRect.top = 36; + srcRect.right = 592; + srcRect.bottom = 444; + glw_state->device->CreateImageSurface( SAVE_GAME_IMAGE_W, SAVE_GAME_IMAGE_H, D3DFMT_DXT1, &compressedSaveGameImage ); + D3DXLoadSurfaceFromSurface( compressedSaveGameImage, NULL, NULL, screenShot, NULL, &srcRect, D3DX_DEFAULT, D3DCOLOR( 0 ) ); + + // Write out the large screenshot (our 256x128 for display in the UI) + res = XGWriteSurfaceOrTextureToXPR( compressedSaveGameImage, "Z:\\screenshot.xbx", TRUE ); + + // Free the compressed 256x128 image + if ( compressedSaveGameImage ) + compressedSaveGameImage->Release(); + + // Re-load the file and build a signature. File should always be less than 32k + // Then append to the file. This is a poor example of how-not-to-write-code: + byte xbxFile[32*1024]; + DWORD dwSize; + + // Open and read: + HANDLE hFile = CreateFile( "Z:\\screenshot.xbx", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0 ); + ReadFile( hFile, xbxFile, sizeof(xbxFile), &dwSize, NULL ); + + // Build a signature: + XCALCSIG_SIGNATURE xbxSig; + HANDLE hSig = XCalculateSignatureBegin( XCALCSIG_FLAG_SAVE_GAME ); + XCalculateSignatureUpdate( hSig, xbxFile, dwSize ); + XCalculateSignatureEnd( hSig, &xbxSig ); + + // Append: + SetFilePointer( hFile, 0, NULL, FILE_END ); + WriteFile( hFile, &xbxSig, sizeof(xbxSig), &dwSize, NULL ); + CloseHandle( hFile ); + + // Make another surface, this one only 64x64 for the dashboard: + glw_state->device->CreateImageSurface( 64, 64, D3DFMT_DXT1, &compressedSaveGameImage ); + + // We take a 240x240 region from the center of the screen, offset if in third person: + srcRect; + srcRect.left = (glConfig.vidWidth / 2) - 120; + srcRect.right = srcRect.left + 240; + + srcRect.top = (glConfig.vidHeight / 2) - 120; + if( Cvar_VariableValue( "cg_thirdPerson" ) ) + srcRect.top += 60; + srcRect.bottom = srcRect.top + 240; + + D3DXLoadSurfaceFromSurface( compressedSaveGameImage, NULL, NULL, screenShot, NULL, &srcRect, D3DX_DEFAULT, D3DCOLOR( 0 ) ); + + // Write out the small screenshot (64x64) + res = XGWriteSurfaceOrTextureToXPR( compressedSaveGameImage, "Z:\\saveimage.xbx", TRUE ); + + // Free the compressed 64x64 image + if ( compressedSaveGameImage ) + compressedSaveGameImage->Release(); + + // Free the big screenshot 640x480x4? + if ( screenShot ) + screenShot->Release(); + +#endif +} + +//----------------------------------------------------------------------------- +// LoadCompressedScreenshot +// Loads a .xbx screenshot file and replaces the current texture +//----------------------------------------------------------------------------- +BOOL LoadCompressedScreenshot(const char* filename) +{ +#ifndef DISABLE_SCREENSHOT + // get the current texture + glwstate_t::TextureInfo* info = _getCurrentTexture(glw_state->serverTU); + if (info == NULL) return FALSE; + + // locals + DWORD dwBytesRead; + BOOL bSuccess; + + // See if the image file for this saved game exists + HANDLE hFile = CreateFile( filename, GENERIC_READ, 0, NULL, OPEN_EXISTING,0, NULL ); + + if( hFile == INVALID_HANDLE_VALUE ) + { + // Extreme failure case. Skip other work below, but still blank out the screenshot + // ONE: Take all textures out of stages, in case ours is locked: + glw_state->device->SetTexture( 0, NULL ); + glw_state->device->SetTexture( 1, NULL ); + glw_state->textureStageDirty[0] = true; + glw_state->textureStageDirty[1] = true; + + // TWO: Now wait to be sure that the texture we're about to replace isn't locked by GPU: + while( info->mipmap->IsBusy() ) + { + // Do nothing + } + + // THREE: Use LockRect to get a pointer to the texture's data: + D3DLOCKED_RECT lr; + info->mipmap->LockRect( 0, &lr, NULL, D3DLOCK_TILED ); + + // FOUR: Copy the texture data from the file: + memset( lr.pBits, 0, CSS_IMAGE_DATA_SIZE ); + info->mipmap->UnlockRect( 0 ); + + return false; + } + + // Non-signature portion of the file, which we read all at once: + XprImage image; + + // Read everything but the signature from disk: + bSuccess = ReadFile( hFile, &image, sizeof( XprImage ), &dwBytesRead, NULL ); + + // Validate that data: + bSuccess &= dwBytesRead == sizeof( XprImage ) && + image.hdr.xpr.dwMagic == XPR_MAGIC_VALUE && + image.hdr.xpr.dwTotalSize == CSS_IMAGE_HDR_SIZE + CSS_IMAGE_DATA_SIZE && + image.hdr.xpr.dwHeaderSize == CSS_IMAGE_HDR_SIZE && + image.hdr.dwEndOfHeader == 0xFFFFFFFF; + + // Now read the signature: + XCALCSIG_SIGNATURE xbxSig; + bSuccess &= ReadFile( hFile, &xbxSig, sizeof( xbxSig ), &dwBytesRead, NULL ); + + // Verify that we're at the end of the file (it's properly sized) + if( SetFilePointer( hFile, 0, NULL, FILE_END ) != (sizeof( XprImage ) + sizeof( xbxSig )) ) + bSuccess = false; + CloseHandle( hFile ); + + // Re-sign the data: + XCALCSIG_SIGNATURE datSig; + HANDLE hSig = XCalculateSignatureBegin( XCALCSIG_FLAG_SAVE_GAME ); + XCalculateSignatureUpdate( hSig, (const BYTE *) &image, sizeof( image ) ); + XCalculateSignatureEnd( hSig, &datSig ); + + // Compare the signatures: + if( memcmp( &xbxSig, &datSig, sizeof( xbxSig ) ) != 0 ) + bSuccess = false; + + // Now we update the texture. We do this even if the file was bad, using black instead: + + // ONE: Take all textures out of stages, in case ours is locked: + glw_state->device->SetTexture( 0, NULL ); + glw_state->device->SetTexture( 1, NULL ); + glw_state->textureStageDirty[0] = true; + glw_state->textureStageDirty[1] = true; + + // TWO: Now wait to be sure that the texture we're about to replace isn't locked by GPU: + while( info->mipmap->IsBusy() ) + { + // Do nothing + } + + // THREE: Use LockRect to get a pointer to the texture's data: + D3DLOCKED_RECT lr; + info->mipmap->LockRect( 0, &lr, NULL, D3DLOCK_TILED ); + + if( bSuccess ) + { + // FOUR: Copy the texture data from the file: + memcpy( lr.pBits, image.pBits, sizeof( image.pBits ) ); + } + else + { + memset( lr.pBits, 0, CSS_IMAGE_DATA_SIZE ); + } + + info->mipmap->UnlockRect( 0 ); + + return bSuccess; +#else + return FALSE; +#endif +} + +bool CreateVertexShader( const CHAR* strFilename, const DWORD* pdwVertexDecl, DWORD* pdwVertexShader ) +{ + HRESULT hr; + + // Open the vertex shader file + HANDLE hFile; + DWORD dwNumBytesRead; + hFile = CreateFile( strFilename, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL ); + if( hFile == INVALID_HANDLE_VALUE ) + return false; + + // Allocate memory to read the vertex shader file + DWORD dwSize = GetFileSize(hFile, NULL); + BYTE* pData = new BYTE[dwSize+4]; + if( NULL == pData ) + { + CloseHandle( hFile ); + return false; + } + ZeroMemory( pData, dwSize+4 ); + + // Read the pre-compiled vertex shader microcode + ReadFile(hFile, pData, dwSize, &dwNumBytesRead, 0); + + // Create the vertex shader + hr = glw_state->device->CreateVertexShader( pdwVertexDecl, (const DWORD*)pData, + pdwVertexShader, 0 ); + + // Cleanup and return + CloseHandle( hFile ); + delete [] pData; + + if(hr == S_OK) + return true; + + return false; +} + + +bool CreatePixelShader( const CHAR* strFilename, DWORD* pdwPixelShader ) +{ + HRESULT hr; + + // Open the pixel shader file + HANDLE hFile; + DWORD dwNumBytesRead; + hFile = CreateFile( strFilename, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL ); + if( hFile == INVALID_HANDLE_VALUE ) + return false; + + // Load the pre-compiled pixel shader microcode + D3DPIXELSHADERDEF_FILE psdf; + ReadFile( hFile, &psdf, sizeof(D3DPIXELSHADERDEF_FILE), &dwNumBytesRead, NULL ); + + // Make sure the pixel shader is valid + if( psdf.FileID != D3DPIXELSHADERDEF_FILE_ID ) + { + CloseHandle( hFile ); + return false; + } + + // Create the pixel shader + if( FAILED( hr = glw_state->device->CreatePixelShader( &(psdf.Psd), pdwPixelShader ) ) ) + { + CloseHandle( hFile ); + return false; + } + + // Cleanup + CloseHandle( hFile ); + + return true; +} diff --git a/code/win32/win_shared.cpp b/code/win32/win_shared.cpp new file mode 100644 index 0000000..3288f53 --- /dev/null +++ b/code/win32/win_shared.cpp @@ -0,0 +1,281 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "win_local.h" + +/* +================ +Sys_Milliseconds +================ +*/ +int Sys_Milliseconds (void) +{ + static int sys_timeBase = timeGetTime(); + int sys_curtime; + + sys_curtime = timeGetTime() - sys_timeBase; + + return sys_curtime; +} + +/* +** -------------------------------------------------------------------------------- +** +** PROCESSOR STUFF +** +** -------------------------------------------------------------------------------- +*/ +static inline void CPUID( int func, unsigned int regs[4] ) +{ + unsigned regEAX, regEBX, regECX, regEDX; + + __asm mov eax, func + __asm __emit 00fh + __asm __emit 0a2h + __asm mov regEAX, eax + __asm mov regEBX, ebx + __asm mov regECX, ecx + __asm mov regEDX, edx + + regs[0] = regEAX; + regs[1] = regEBX; + regs[2] = regECX; + regs[3] = regEDX; +} + +static int IsPentium( void ) +{ + __asm + { + pushfd // save eflags + pop eax + test eax, 0x00200000 // check ID bit + jz set21 // bit 21 is not set, so jump to set_21 + and eax, 0xffdfffff // clear bit 21 + push eax // save new value in register + popfd // store new value in flags + pushfd + pop eax + test eax, 0x00200000 // check ID bit + jz good + jmp err // cpuid not supported +set21: + or eax, 0x00200000 // set ID bit + push eax // store new value + popfd // store new value in EFLAGS + pushfd + pop eax + test eax, 0x00200000 // if bit 21 is on + jnz good + jmp err + } + +err: + return qfalse; +good: + return qtrue; +} + +static int Is3DNOW( void ) +{ + unsigned regs[4]; + char pstring[16]; + char processorString[13]; + + // get name of processor + CPUID( 0, ( unsigned int * ) pstring ); + processorString[0] = pstring[4]; + processorString[1] = pstring[5]; + processorString[2] = pstring[6]; + processorString[3] = pstring[7]; + processorString[4] = pstring[12]; + processorString[5] = pstring[13]; + processorString[6] = pstring[14]; + processorString[7] = pstring[15]; + processorString[8] = pstring[8]; + processorString[9] = pstring[9]; + processorString[10] = pstring[10]; + processorString[11] = pstring[11]; + processorString[12] = 0; + +// REMOVED because you can have 3DNow! on non-AMD systems +// if ( strcmp( processorString, "AuthenticAMD" ) ) +// return qfalse; + + // check AMD-specific functions + CPUID( 0x80000000, regs ); + if ( regs[0] < 0x80000000 ) + return qfalse; + + // bit 31 of EDX denotes 3DNOW! support + CPUID( 0x80000001, regs ); + if ( regs[3] & ( 1 << 31 ) ) + return qtrue; + + return qfalse; +} + +static int IsKNI( void ) +{ + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 25 of EDX denotes KNI existence + if ( regs[3] & ( 1 << 25 ) ) + { + // Ok, CPU supports this instruction, but does the OS? + // + // Test a KNI instruction and make sure you don't get an exception... + // + __try + { + __asm + { + pushad; + // orps xmm1,xmm1; // Below are the op codes for this instruction + // emits will compile w/ MSVC 5.0 compiler + // You can comment these out and uncomment the + // orps when using the Intel Compiler + __emit 0x0f + __emit 0x56 + __emit 0xc9 + popad; + } + }// If OS creates an exception, it doesn't support Pentium III Instructions + __except(EXCEPTION_EXECUTE_HANDLER) + { + return qfalse; + } + + return qtrue; + } + + return qfalse; +} + +static int IsWIL( void ) +{ + unsigned regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 26 of EDX denotes WIL existence + if ( regs[3] & ( 1 << 26 ) ) + { + // Ok, CPU supports this instruction, but does the OS? + // + // Test a WIL instruction and make sure you don't get an exception... + // + __try + { + __asm + { + pushad; + // xorpd xmm0,xmm0; // Willamette New Instructions + __emit 0x0f + __emit 0x56 + __emit 0xc9 + popad; + } + }// If OS creates an exception, it doesn't support PentiumIV Instructions + __except(EXCEPTION_EXECUTE_HANDLER) + { +// if(_exception_code()==STATUS_ILLEGAL_INSTRUCTION) // forget it, any exception should count as fail for safety + return qfalse; // Willamette New Instructions not supported + } + + return qtrue; // Williamette/P4 instructions available + } + + return qfalse; + +} + + +static int IsMMX( void ) +{ + unsigned int regs[4]; + + // get CPU feature bits + CPUID( 1, regs ); + + // bit 23 of EDX denotes MMX existence + if ( regs[3] & ( 1 << 23 ) ) + return qtrue; + return qfalse; +} + +int Sys_GetProcessorId( void ) +{ +#if defined _M_ALPHA + return CPUID_AXP; +#elif !defined _M_IX86 + return CPUID_GENERIC; +#else + + // verify we're at least a Pentium or 486 w/ CPUID support + if ( !IsPentium() ) + return CPUID_INTEL_UNSUPPORTED; + + // check for MMX + if ( !IsMMX() ) + { + // Pentium or PPro + return CPUID_INTEL_PENTIUM; + } + + // see if we're an AMD 3DNOW! processor + if ( Is3DNOW() ) + { + return CPUID_AMD_3DNOW; + } + + // see if we're an Intel Katmai + if ( IsKNI() ) + { + // if we are, see if we're a Williamette as well... + // + if ( IsWIL() ) + { + return CPUID_INTEL_WILLIAMETTE; + } + return CPUID_INTEL_KATMAI; + } + + // by default we're functionally a vanilla Pentium/MMX or P2/MMX + return CPUID_INTEL_MMX; + +#endif +} + +//============================================ + +char *Sys_GetCurrentUser( void ) +{ +#ifdef _XBOX + return NULL; +#else + static char s_userName[1024]; + unsigned long size = sizeof( s_userName ); + + + if ( !GetUserName( s_userName, &size ) ) + strcpy( s_userName, "player" ); + + if ( !s_userName[0] ) + { + strcpy( s_userName, "player" ); + } + + return s_userName; +#endif +} \ No newline at end of file diff --git a/code/win32/win_snd.cpp b/code/win32/win_snd.cpp new file mode 100644 index 0000000..3b446e3 --- /dev/null +++ b/code/win32/win_snd.cpp @@ -0,0 +1,414 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include + +#include "../client/snd_local.h" +#include "win_local.h" + +HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter); +#define iDirectSoundCreate(a,b,c) pDirectSoundCreate(a,b,c) + +#define SECONDARY_BUFFER_SIZE 0x10000 + + +extern int s_UseOpenAL; + +static qboolean dsound_init; +static int sample16; +static DWORD gSndBufSize; +static DWORD locksize; +static LPDIRECTSOUND pDS; +static LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf; +static HINSTANCE hInstDS; + +static int SNDDMA_InitDS (); + +static const char *DSoundError( int error ) { + switch ( error ) { + case DSERR_BUFFERLOST: + return "DSERR_BUFFERLOST"; + case DSERR_INVALIDCALL: + return "DSERR_INVALIDCALLS"; + case DSERR_INVALIDPARAM: + return "DSERR_INVALIDPARAM"; + case DSERR_PRIOLEVELNEEDED: + return "DSERR_PRIOLEVELNEEDED"; + case DSERR_ALLOCATED: + return "DSERR_ALLOCATED"; + case DSERR_UNINITIALIZED: + return "DSERR_UNINITIALIZED"; + case DSERR_UNSUPPORTED: + return "DSERR_UNSUPPORTED "; + } + + return "unknown"; +} + +/* +================== +SNDDMA_Shutdown +================== +*/ +void SNDDMA_Shutdown( void ) { + Com_DPrintf( "Shutting down sound system\n" ); + + if ( pDS ) { + Com_DPrintf( "Destroying DS buffers\n" ); + if ( pDS ) + { + Com_DPrintf( "...setting NORMAL coop level\n" ); + pDS->SetCooperativeLevel( g_wv.hWnd, DSSCL_NORMAL ); + } + + if ( pDSBuf ) + { + Com_DPrintf( "...stopping and releasing sound buffer\n" ); + pDSBuf->Stop( ); + pDSBuf->Release( ); + } + + // only release primary buffer if it's not also the mixing buffer we just released + if ( pDSPBuf && ( pDSBuf != pDSPBuf ) ) + { + Com_DPrintf( "...releasing primary buffer\n" ); + pDSPBuf->Release( ); + } + pDSBuf = NULL; + pDSPBuf = NULL; + + dma.buffer = NULL; + + Com_DPrintf( "...releasing DS object\n" ); + pDS->Release( ); + } + + if ( hInstDS ) { + Com_DPrintf( "...freeing DSOUND.DLL\n" ); + FreeLibrary( hInstDS ); + hInstDS = NULL; + } + + pDS = NULL; + pDSBuf = NULL; + pDSPBuf = NULL; + dsound_init = qfalse; + memset ((void *)&dma, 0, sizeof (dma)); +} + +/* +================== +SNDDMA_Init + +Initialize direct sound +Returns false if failed +================== +*/ +qboolean SNDDMA_Init(void) { + + memset ((void *)&dma, 0, sizeof (dma)); + dsound_init = qfalse; + + if ( !SNDDMA_InitDS () ) { + return qfalse; + } + + dsound_init = qtrue; + + Com_DPrintf("Completed successfully\n" ); + + return qtrue; +} + + +static int SNDDMA_InitDS () +{ + HRESULT hresult; + qboolean pauseTried; + DSBUFFERDESC dsbuf; + DSBCAPS dsbcaps; + WAVEFORMATEX format; + + Com_Printf( "Initializing DirectSound\n"); + + if ( !hInstDS ) { + Com_DPrintf( "...loading dsound.dll: " ); + + hInstDS = LoadLibrary("dsound.dll"); + + if ( hInstDS == NULL ) { + Com_Printf ("failed\n"); + return 0; + } + + Com_DPrintf ("ok\n"); + pDirectSoundCreate = (long (__stdcall *)(struct _GUID *,struct IDirectSound ** ,struct IUnknown *)) + GetProcAddress(hInstDS,"DirectSoundCreate"); + + if ( !pDirectSoundCreate ) { + Com_Printf ("*** couldn't get DS proc addr ***\n"); + return 0; + } + } + + Com_DPrintf( "...creating DS object: " ); + pauseTried = qfalse; + while ( ( hresult = iDirectSoundCreate( NULL, &pDS, NULL ) ) != DS_OK ) { + if ( hresult != DSERR_ALLOCATED ) { + Com_Printf( "failed\n" ); + return 0; + } + + if ( pauseTried ) { + Com_Printf ("failed, hardware already in use\n" ); + return 0; + } + // first try just waiting five seconds and trying again + // this will handle the case of a sysyem beep playing when the + // game starts + Com_DPrintf ("retrying...\n"); + Sleep( 3000 ); + pauseTried = qtrue; + } + Com_DPrintf( "ok\n" ); + + Com_DPrintf("...setting DSSCL_PRIORITY coop level: " ); + + if ( DS_OK != pDS->SetCooperativeLevel( g_wv.hWnd, DSSCL_PRIORITY ) ) { + Com_Printf ("failed\n"); + SNDDMA_Shutdown (); + return qfalse; + } + Com_DPrintf("ok\n" ); + + + // create the secondary buffer we'll actually work with + dma.channels = 2; + dma.samplebits = 16; + + if (s_khz->integer == 44) + dma.speed = 44100; + else if (s_khz->integer == 22) + dma.speed = 22050; + else + dma.speed = 11025; + + memset (&format, 0, sizeof(format)); + format.wFormatTag = WAVE_FORMAT_PCM; + format.nChannels = dma.channels; + format.wBitsPerSample = dma.samplebits; + format.nSamplesPerSec = dma.speed; + format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; + format.cbSize = 0; + format.nAvgBytesPerSec = format.nSamplesPerSec*format.nBlockAlign; + + memset (&dsbuf, 0, sizeof(dsbuf)); + dsbuf.dwSize = sizeof(DSBUFFERDESC); + +#define idDSBCAPS_GETCURRENTPOSITION2 0x00010000 + + dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCHARDWARE | idDSBCAPS_GETCURRENTPOSITION2; + dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE; + dsbuf.lpwfxFormat = &format; + + Com_DPrintf( "...creating secondary buffer: " ); + hresult = pDS->CreateSoundBuffer(&dsbuf, &pDSBuf, NULL); + if (hresult != DS_OK) + { + if ( hresult == DSERR_CONTROLUNAVAIL ) + { + Com_Printf( " - Ancient version of DirectX - this will slow FPS\n" ); + dsbuf.dwFlags &= ~idDSBCAPS_GETCURRENTPOSITION2; // lose this DX8 cursor-position feature, and try again + hresult = pDS->CreateSoundBuffer(&dsbuf, &pDSBuf, NULL); + } + + if (hresult != DS_OK) + { + // we can't even specify sounds should be in hardware?... + // + // ( this seems to happen on integrated sound devices (eg SoundMax), regardless of DX version ) + // + dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY; // note that DX docs say that this can still use hardware if it wants to, since neither DSBCAPS_LOCHARDWARE nor DSBCAPS_LOCSOFTWARE were specified + hresult = pDS->CreateSoundBuffer(&dsbuf, &pDSBuf, NULL); + if (hresult != DS_OK) { + Com_Printf( "failed to create secondary buffer - %s\n", DSoundError( hresult ) ); + SNDDMA_Shutdown (); + return qfalse; + } + } + } + Com_Printf( "locked hardware. ok\n" ); + + // Make sure mixer is active + if ( DS_OK != pDSBuf->Play(0, 0, DSBPLAY_LOOPING) ) { + Com_Printf ("*** Looped sound play failed ***\n"); + SNDDMA_Shutdown (); + return qfalse; + } + + memset(&dsbcaps, 0, sizeof(dsbcaps)); + dsbcaps.dwSize = sizeof(dsbcaps); + // get the returned buffer size + if ( DS_OK != pDSBuf->GetCaps (&dsbcaps) ) { + Com_Printf ("*** GetCaps failed ***\n"); + SNDDMA_Shutdown (); + return qfalse; + } + + gSndBufSize = dsbcaps.dwBufferBytes; + + dma.channels = format.nChannels; + dma.samplebits = format.wBitsPerSample; + dma.speed = format.nSamplesPerSec; + dma.samples = gSndBufSize/(dma.samplebits/8); + dma.submission_chunk = 1; + dma.buffer = NULL; // must be locked first + + sample16 = (dma.samplebits/8) - 1; + + SNDDMA_BeginPainting (); + if (dma.buffer) + memset(dma.buffer, 0, dma.samples * dma.samplebits/8); + SNDDMA_Submit (); + return 1; +} +/* +============== +SNDDMA_GetDMAPos + +return the current sample position (in mono samples read) +inside the recirculating dma buffer, so the mixing code will know +how many sample are required to fill it up. +=============== +*/ +int SNDDMA_GetDMAPos( void ) { + MMTIME mmtime; + int s; + DWORD dwWrite; + + if ( !dsound_init ) { + return 0; + } + + mmtime.wType = TIME_SAMPLES; + pDSBuf->GetCurrentPosition(&mmtime.u.sample, &dwWrite); + + s = mmtime.u.sample; + + s >>= sample16; + + s &= (dma.samples-1); + + return s; +} + +/* +============== +SNDDMA_BeginPainting + +Makes sure dma.buffer is valid +=============== +*/ +void SNDDMA_BeginPainting( void ) { + int reps; + DWORD dwSize2; + DWORD *pbuf, *pbuf2; + HRESULT hresult; + DWORD dwStatus; + + if ( !pDSBuf ) { + return; + } + + // if the buffer was lost or stopped, restore it and/or restart it + if ( pDSBuf->GetStatus (&dwStatus) != DS_OK ) { + Com_Printf ("Couldn't get sound buffer status\n"); + } + + if (dwStatus & DSBSTATUS_BUFFERLOST) + pDSBuf->Restore (); + + if (!(dwStatus & DSBSTATUS_PLAYING)) + pDSBuf->Play(0, 0, DSBPLAY_LOOPING); + + // lock the dsound buffer + + reps = 0; + dma.buffer = NULL; + + while ((hresult = pDSBuf->Lock(0, gSndBufSize, (void **)&pbuf, &locksize, + (void **)&pbuf2, &dwSize2, 0)) != DS_OK) + { + if (hresult != DSERR_BUFFERLOST) + { + Com_Printf( "SNDDMA_BeginPainting: Lock failed with error '%s'\n", DSoundError( hresult ) ); + S_Shutdown (); + return; + } + else + { + pDSBuf->Restore( ); + } + + if (++reps > 2) + return; + } + dma.buffer = (unsigned char *)pbuf; +} + +/* +============== +SNDDMA_Submit + +Send sound to device if buffer isn't really the dma buffer +Also unlocks the dsound buffer +=============== +*/ +void SNDDMA_Submit( void ) { + // unlock the dsound buffer + if ( pDSBuf ) { + pDSBuf->Unlock(dma.buffer, locksize, NULL, 0); + } +} + + +/* +================= +SNDDMA_Activate + +When we change windows we need to do this +================= +*/ +void SNDDMA_Activate( qboolean bAppActive ) +{ + if (s_UseOpenAL) + { + S_AL_MuteAllSounds(!bAppActive); + } + + if ( !pDS ) { + return; + } + + if ( DS_OK != pDS->SetCooperativeLevel( g_wv.hWnd, DSSCL_PRIORITY ) ) { + Com_Printf ("sound SetCooperativeLevel failed\n"); + SNDDMA_Shutdown (); + } +} + + + +// I know this is a bit horrible, but I need to pass our LPDIRECTSOUND ptr to Bink for video playback, +// and I don't want other modules to have to know about LPDIRECTSOUND handles, hence the int casting +// +// (I'd prefer to use DWORD, but not all modules understand those) +// +unsigned int SNDDMA_GetDSHandle(void) +{ + return (unsigned int) pDS; +} + + diff --git a/code/win32/win_stencilshadow.cpp b/code/win32/win_stencilshadow.cpp new file mode 100644 index 0000000..ed8a852 --- /dev/null +++ b/code/win32/win_stencilshadow.cpp @@ -0,0 +1,474 @@ +// +// +// win_stencilshadow.cpp +// +// Stencil shadow computation/rendering +// +// + +#include "../server/exe_headers.h" + +#include "../renderer/tr_local.h" +#include "../renderer/tr_lightmanager.h" +#include "glw_win_dx8.h" +#include "win_local.h" + +#include "win_stencilshadow.h" + +#include +#include + +#include "shader_constants.h" + + + +StencilShadow StencilShadower; + + +StencilShadow::StencilShadow() +{ + m_dwVertexShaderShadow = 0; + + for(int i = 0; i < SHADER_MAX_VERTEXES / 2; i++) + { + m_extrusionIndicators[i] = 1.0f; + } +} + + +StencilShadow::~StencilShadow() +{ + if(m_dwVertexShaderShadow) + glw_state->device->DeleteVertexShader(m_dwVertexShaderShadow); +} + +extern const char *Sys_RemapPath( const char *filename ); + +bool StencilShadow::Initialize() +{ + // Create a vertex shader + DWORD dwVertexDecl[] = + { + D3DVSD_STREAM( 0 ), + D3DVSD_REG( 0, D3DVSDT_FLOAT3 ), // v0 = Position + D3DVSD_REG( 1, D3DVSDT_FLOAT1 ), // v1 = Extrusion determinant + D3DVSD_END() + }; + + if(!( CreateVertexShader(Sys_RemapPath("base\\media\\shadow.xvu"), dwVertexDecl, &m_dwVertexShaderShadow))) + return false; + + return true; +} + + +void StencilShadow::AddEdge( unsigned short i1, unsigned short i2, byte facing ) +{ +#ifndef DISABLE_STENCILSHADOW + int c; + + c = m_numEdgeDefs[ i1 ]; + if ( c == MAX_EDGE_DEFS ) + { + Com_Printf("WARNING: MAX_EDGE_DEFS overflow!\n"); + return; // overflow + } + m_edgeDefs[ i1 ][ c ].i2 = i2; + m_edgeDefs[ i1 ][ c ].facing = facing; + + m_numEdgeDefs[ i1 ]++; +#endif +} + + +void StencilShadow::BuildEdges() +{ +#ifndef DISABLE_STENCILSHADOW + int i; + int c; + int j; + unsigned short i2; + int numTris; + unsigned short o1, o2, o3; + + int hit[2]; + int c2, k; + + // an edge is NOT a silhouette edge if its face doesn't face the light, + // or if it has a reverse paired edge that also faces the light. + // A well behaved polyhedron would have exactly two faces for each edge, + // but lots of models have dangling edges or overfanned edges + m_nIndexes = 0; + m_nIndexesCap = 0; + + for ( i = 0 ; i < tess.numVertexes ; i++ ) + { + c = m_numEdgeDefs[ i ]; + for ( j = 0 ; j < c ; j++ ) + { + if ( !m_edgeDefs[ i ][ j ].facing ) + { + continue; + } + + /*i2 = m_edgeDefs[ i ][ j ].i2; + + m_shadowIndexes[m_nIndexes++] = i; + m_shadowIndexes[m_nIndexes++] = i + tess.numVertexes; + m_shadowIndexes[m_nIndexes++] = i2 + tess.numVertexes; + m_shadowIndexes[m_nIndexes++] = i2;*/ + + hit[0] = 0; + hit[1] = 0; + + i2 = m_edgeDefs[ i ][ j ].i2; + c2 = m_numEdgeDefs[ i2 ]; + for ( k = 0 ; k < c2 ; k++ ) { + if ( m_edgeDefs[ i2 ][ k ].i2 == i ) { + hit[ m_edgeDefs[ i2 ][ k ].facing ]++; + } + } + + // if it doesn't share the edge with another front facing + // triangle, it is a sil edge + if ( hit[ 1 ] == 0 ) { + m_shadowIndexes[m_nIndexes++] = i; + m_shadowIndexes[m_nIndexes++] = i + tess.numVertexes; + m_shadowIndexes[m_nIndexes++] = i2 + tess.numVertexes; + m_shadowIndexes[m_nIndexes++] = i2; + } + } + } + + if(!m_nIndexes) + return; + +#ifdef _STENCIL_REVERSE + //Carmack Reverse method requires that volumes + //be capped properly -rww + numTris = tess.numIndexes / 3; + + for ( i = 0 ; i < numTris ; i++ ) + { + if ( !m_facing[i] ) + { + continue; + } + + o1 = tess.indexes[ i*3 + 0 ]; + o2 = tess.indexes[ i*3 + 1 ]; + o3 = tess.indexes[ i*3 + 2 ]; + + m_shadowIndexesCap[m_nIndexesCap++] = o1; + m_shadowIndexesCap[m_nIndexesCap++] = o2; + m_shadowIndexesCap[m_nIndexesCap++] = o3; + m_shadowIndexesCap[m_nIndexesCap++] = o3 + tess.numVertexes; + m_shadowIndexesCap[m_nIndexesCap++] = o2 + tess.numVertexes; + m_shadowIndexesCap[m_nIndexesCap++] = o1 + tess.numVertexes; + } +#endif // _STENCIL_REVERSE + +#endif +} + + +bool StencilShadow::BuildFromLight() +{ +#ifndef DISABLE_STENCILSHADOW + int i; + int numTris; + vec3_t lightDir, ground; + float d; + + // we can only do this if we have enough space in the vertex buffers + if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) { + return false; + } + + //controlled method - try to keep shadows in range so they don't show through so much -rww + //VectorCopy( backEnd.currentEntity->shadowDir, lightDir ); + // + //ground[0] = backEnd.ori.axis[0][2]; + //ground[1] = backEnd.ori.axis[1][2]; + //ground[2] = backEnd.ori.axis[2][2]; + + //d = DotProduct( lightDir, ground ); + //// don't let the shadows get too long or go negative + //if ( d < 0.8 ) { + // VectorMA( lightDir, (0.8 - d), ground, lightDir ); + // d = DotProduct( lightDir, ground ); + //} + //d = 1.0 / d; + + //lightDir[0] = lightDir[0] * d; + //lightDir[1] = lightDir[1] * d; + //lightDir[2] = lightDir[2] * d; + + //VectorNormalize(lightDir); + + vec3_t entLight; + VectorCopy( backEnd.currentEntity->lightDir, entLight ); + entLight[2] = 0.0f; + VectorNormalize(entLight); + + //Oh well, just cast them straight down no matter what onto the ground plane. + //This presets no chance of screwups and still looks better than a stupid + //shader blob. + VectorSet(lightDir, entLight[0]*0.3f, entLight[1]*0.3f, 1.0f); + + + // Set the vertex shader constants + D3DXVECTOR4 light = D3DXVECTOR4(lightDir[0], lightDir[1], lightDir[2], 1.0f); + glw_state->device->SetVertexShaderConstant( CV_LIGHT_DIRECTION, light, 1 ); + + glw_state->device->SetVertexShaderConstant( CV_SHADOW_FACTORS, D3DXVECTOR4(backEnd.ori.origin[0], + backEnd.ori.origin[1], + backEnd.ori.origin[2], + 1.0f), 1); + glw_state->device->SetVertexShaderConstant( CV_SHADOW_PLANE, D3DXVECTOR4( backEnd.currentEntity->e.shadowPlane - 16.0f, + backEnd.currentEntity->e.shadowPlane - 16.0f, + backEnd.currentEntity->e.shadowPlane - 16.0f, + 1.0f), 1); + + // Create a second set of vertices to be projected + memcpy(&tess.xyz[tess.numVertexes], &tess.xyz[0], sizeof(vec4_t) * tess.numVertexes); + + // decide which triangles face the light + memset( m_numEdgeDefs, 0, sizeof(short) * tess.numVertexes ); + + numTris = tess.numIndexes / 3; + for ( i = 0 ; i < numTris ; i++ ) + { + short i1, i2, i3; + vec3_t d1, d2, normal; + float *v1, *v2, *v3; + float d; + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + v1 = tess.xyz[ i1 ]; + v2 = tess.xyz[ i2 ]; + v3 = tess.xyz[ i3 ]; + + VectorSubtract( v2, v1, d1 ); + VectorSubtract( v3, v1, d2 ); + CrossProduct( d1, d2, normal ); + + d = DotProduct( normal, lightDir ); + if ( d > 0 ) { + m_facing[ i ] = 1; + } else { + m_facing[ i ] = 0; + } + + // create the edges + AddEdge( i1, i2, m_facing[ i ] ); + AddEdge( i2, i3, m_facing[ i ] ); + AddEdge( i3, i1, m_facing[ i ] ); + } + + return true; +#else + return false; +#endif +} + + +void StencilShadow::RenderShadow() +{ +#ifndef DISABLE_STENCILSHADOW + DWORD lighting, fog, srcblend, destblend, alphablend, zwrite, zfunc, cullmode; + + GL_State(GLS_DEFAULT); + + glw_state->device->GetRenderState( D3DRS_LIGHTING, &lighting ); + glw_state->device->GetRenderState( D3DRS_FOGENABLE, &fog ); + glw_state->device->GetRenderState( D3DRS_SRCBLEND, &srcblend ); + glw_state->device->GetRenderState( D3DRS_DESTBLEND, &destblend ); + glw_state->device->GetRenderState( D3DRS_ALPHABLENDENABLE, &alphablend ); + glw_state->device->GetRenderState( D3DRS_ZWRITEENABLE, &zwrite ); + glw_state->device->GetRenderState( D3DRS_ZFUNC, &zfunc ); + glw_state->device->GetRenderState( D3DRS_CULLMODE, &cullmode ); + + pVerts = NULL; + pExtrusions = NULL; + + GL_Bind( tr.whiteImage ); + + glw_state->device->SetRenderState( D3DRS_LIGHTING, FALSE ); + glw_state->device->SetRenderState( D3DRS_FOGENABLE, FALSE ); + + glw_state->device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ZERO ); + + // Disable z-buffer writes (note: z-testing still occurs), and enable the + // stencil-buffer + glw_state->device->SetRenderState( D3DRS_ZWRITEENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_STENCILENABLE, TRUE ); + + // Don't bother with interpolating color + glw_state->device->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_FLAT ); + + glw_state->device->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESS ); + + // Set up stencil compare function, reference value, and masks. + // Stencil test passes if ((ref & mask) cmpfn (stencil & mask)) is true. + // Note: since we set up the stencil-test to always pass, the STENCILFAIL + // renderstate is really not needed. + glw_state->device->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); +#ifdef _STENCIL_REVERSE + glw_state->device->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_INCR ); + glw_state->device->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); + glw_state->device->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_KEEP ); +#else + glw_state->device->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP ); + glw_state->device->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); + glw_state->device->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_INCR ); +#endif + + // If ztest passes, inc/decrement stencil buffer value + glw_state->device->SetRenderState( D3DRS_STENCILREF, 0x1 ); + glw_state->device->SetRenderState( D3DRS_STENCILMASK, 0x7f ); //0xffffffff ); + glw_state->device->SetRenderState( D3DRS_STENCILWRITEMASK, 0x7f ); //0xffffffff ); + + // Make sure that no pixels get drawn to the frame buffer + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_COLORWRITEENABLE, 0 ); + + glw_state->device->SetTexture(0, NULL); + glw_state->device->SetTexture(1, NULL); + + // Compute the matrix set + XGMATRIX matComposite, matProjectionViewport, matWorld; + glw_state->device->GetProjectionViewportMatrix( &matProjectionViewport ); + + XGMatrixMultiply( &matComposite, (XGMATRIX*)glw_state->matrixStack[glwstate_t::MatrixMode_Model]->GetTop(), &matProjectionViewport ); + + // Transpose and set the composite matrix. + XGMatrixTranspose( &matComposite, &matComposite ); + glw_state->device->SetVertexShaderConstant( CV_WORLDVIEWPROJ_0, &matComposite, 4 ); + + // Set viewport offsets. + float fViewportOffsets[4] = { 0.53125f, 0.53125f, 0.0f, 0.0f }; + glw_state->device->SetVertexShaderConstant( CV_VIEWPORT_OFFSETS, &fViewportOffsets, 1 ); + + glw_state->device->SetVertexShader(m_dwVertexShaderShadow); + +#ifdef _STENCIL_REVERSE + qglCullFace( GL_FRONT ); +#else + qglCullFace( GL_BACK ); +#endif + + BuildEdges(); + + // Draw front-side of shadow volume in stencil/z only + if(m_nIndexes) + renderObject_Shadow( D3DPT_QUADLIST, m_nIndexes, m_shadowIndexes ); +#ifdef _STENCIL_REVERSE + if(m_nIndexesCap) + renderObject_Shadow( D3DPT_TRIANGLELIST, m_nIndexesCap, m_shadowIndexesCap ); +#endif + + // Now reverse cull order so back sides of shadow volume are written. +#ifdef _STENCIL_REVERSE + qglCullFace( GL_BACK ); +#else + qglCullFace( GL_FRONT ); +#endif + + // Decrement stencil buffer value +#ifdef _STENCIL_REVERSE + glw_state->device->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_DECR ); +#else + glw_state->device->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_DECR ); +#endif + + // Draw back-side of shadow volume in stencil/z only + if(m_nIndexes) + renderObject_Shadow( D3DPT_QUADLIST, m_nIndexes, m_shadowIndexes ); +#ifdef _STENCIL_REVERSE + if(m_nIndexesCap) + renderObject_Shadow( D3DPT_TRIANGLELIST, m_nIndexesCap, m_shadowIndexesCap ); +#endif + + // Restore render states + glw_state->device->SetRenderState( D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALL ); + + glw_state->device->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_GOURAUD ); + glw_state->device->SetRenderState( D3DRS_STENCILENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_LIGHTING, lighting ); + glw_state->device->SetRenderState( D3DRS_FOGENABLE, fog ); + glw_state->device->SetRenderState( D3DRS_SRCBLEND, srcblend ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, destblend ); + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, alphablend ); + glw_state->device->SetRenderState( D3DRS_ZWRITEENABLE, zwrite ); + glw_state->device->SetRenderState( D3DRS_ZFUNC, zfunc ); + glw_state->device->SetRenderState( D3DRS_CULLMODE, cullmode ); +#endif +} + + +void StencilShadow::FinishShadows() +{ +#ifndef DISABLE_STENCILSHADOW + + DWORD lighting, fog, srcblend, destblend, alphablend; + + glw_state->device->GetRenderState( D3DRS_LIGHTING, &lighting ); + glw_state->device->GetRenderState( D3DRS_FOGENABLE, &fog ); + glw_state->device->GetRenderState( D3DRS_SRCBLEND, &srcblend ); + glw_state->device->GetRenderState( D3DRS_DESTBLEND, &destblend ); + glw_state->device->GetRenderState( D3DRS_ALPHABLENDENABLE, &alphablend ); + + // The stencilbuffer values indicates # of shadows that overlap each pixel. + // We only want to draw pixels that are in shadow, which was set up in + // RenderShadow() such that StencilBufferValue >= 1. In the Direct3D API, + // the stencil test is pseudo coded as: + // StencilRef CompFunc StencilBufferValue + // so we set our renderstates with StencilRef = 1 and CompFunc = LESSEQUAL. + glw_state->device->SetRenderState( D3DRS_STENCILENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_STENCILREF, 0);//0x1 ); + glw_state->device->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_NOTEQUAL);//D3DCMP_LESSEQUAL ); + glw_state->device->SetRenderState( D3DRS_STENCILMASK, 0x7f ); // New! + glw_state->device->SetRenderState( D3DRS_STENCILWRITEMASK, 0x7f ); //255 ); + + // Set renderstates (disable z-buffering and turn on alphablending) + glw_state->device->SetRenderState( D3DRS_ZENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA ); + + // Set the hardware to draw black, alpha-blending pixels + glw_state->device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 ); + glw_state->device->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TFACTOR ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); + glw_state->device->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TFACTOR ); + glw_state->device->SetRenderState( D3DRS_TEXTUREFACTOR, 0x50000000 ); + + glw_state->device->SetRenderState( D3DRS_FOGENABLE, FALSE ); + + // Draw the big, darkening square + static FLOAT v[4][4] = + { + { 0 - 0.5f, 0 - 0.5f, 0.0f, 1.0f }, + { 640 - 0.5f, 0 - 0.5f, 0.0f, 1.0f }, + { 640 - 0.5f, 480 - 0.5f, 0.0f, 1.0f }, + { 0 - 0.5f, 480 - 0.5f, 0.0f, 1.0f }, + }; + + glw_state->device->SetVertexShader( D3DFVF_XYZRHW ); + glw_state->device->DrawPrimitiveUP( D3DPT_QUADLIST, 1, v, sizeof(v[0]) ); + + // Restore render states + glw_state->device->SetRenderState( D3DRS_ZENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_STENCILENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_LIGHTING, lighting ); + glw_state->device->SetRenderState( D3DRS_FOGENABLE, fog ); + glw_state->device->SetRenderState( D3DRS_SRCBLEND, srcblend ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, destblend ); + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, alphablend ); +#endif +} diff --git a/code/win32/win_stencilshadow.h b/code/win32/win_stencilshadow.h new file mode 100644 index 0000000..bd258fc --- /dev/null +++ b/code/win32/win_stencilshadow.h @@ -0,0 +1,63 @@ +// +// +// win_stencilshadow.h +// +// Declaration for stencil shadowing class +// +// + +#ifndef _WIN_STENCILSHADOW_H_ +#define _WIN_STENCILSHADOW_H_ + +// Quick way turn off all stencilshadow code, and get back 170k of memory: +//#define DISABLE_STENCILSHADOW + +// Enable this to enable Carmack's stencil reverse method +#define _STENCIL_REVERSE + +typedef struct +{ + // facing is only one bit, but we can't do better than 4 bytes without + // packing all the data we need into a single unsigned short, which + // isn't really worth it. (unless we REALLY need 64k at some point). + unsigned short i2; + byte facing; +} edgeDef_t; + +#define MAX_EDGE_DEFS 16 + + +class StencilShadow +{ +public: + +#ifndef DISABLE_STENCILSHADOW + edgeDef_t m_edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS]; + short m_numEdgeDefs[SHADER_MAX_VERTEXES]; + byte m_facing[SHADER_MAX_INDEXES/3]; + unsigned short m_shadowIndexes[SHADER_MAX_INDEXES]; + unsigned short m_nIndexes; + float m_extrusionIndicators[SHADER_MAX_VERTEXES/2]; +#ifdef _STENCIL_REVERSE + unsigned short m_shadowIndexesCap[SHADER_MAX_INDEXES]; + unsigned short m_nIndexesCap; +#endif + +#endif + DWORD m_dwVertexShaderShadow; + DWORD *pVerts; // For reusing vertices in multiple shadow renderings + DWORD *pExtrusions; + + StencilShadow(); + ~StencilShadow(); + bool Initialize(); + void AddEdge( unsigned short i1, unsigned short i2, byte facing ); + void BuildEdges(); + bool BuildFromLight(); + void RenderShadow(); + void FinishShadows(); +}; + +extern StencilShadow StencilShadower; + +#endif \ No newline at end of file diff --git a/code/win32/win_stream_dx8.cpp b/code/win32/win_stream_dx8.cpp new file mode 100644 index 0000000..51765b2 --- /dev/null +++ b/code/win32/win_stream_dx8.cpp @@ -0,0 +1,511 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +#include "../server/exe_headers.h" + +#include "../client/client.h" +#include "win_local.h" +#include "../qcommon/qcommon.h" +#include "../zlib/zlib.h" + +#if defined(_WINDOWS) +#include +#elif defined(_XBOX) +#include +#endif + +extern void Z_SetNewDeleteTemporary(bool); + +#define STREAM_SLOW_READ 0 + +#include "../client/snd_local_console.h" + +#include + +extern HANDLE Sys_FileStreamMutex; +extern int Sys_GetFileCodeSize(int code); + +#define STREAM_MAX_OPEN 48 +struct StreamInfo +{ + unsigned int file; + volatile bool used; + volatile bool error; + volatile bool opening; + volatile bool reading; +}; +static StreamInfo* s_Streams = NULL; + +enum IORequestType +{ + IOREQ_OPEN, + IOREQ_READ, + IOREQ_SHUTDOWN, +}; + +struct IORequest +{ + IORequestType type; + streamHandle_t handle; + DWORD data[3]; +}; +typedef std::deque requestqueue_t; +requestqueue_t* s_IORequestQueue = NULL; + +HANDLE s_Thread = INVALID_HANDLE_VALUE; +HANDLE s_QueueMutex = INVALID_HANDLE_VALUE; +HANDLE s_QueueLen = INVALID_HANDLE_VALUE; + + +#include "../qcommon/fixedmap.h" + +#pragma pack(push, 1) +typedef struct +{ + unsigned char filenameFlags; + int offset; + int size; +} sound_file_t; +#pragma pack(pop) + +static HANDLE soundfile = INVALID_HANDLE_VALUE; +static VVFixedMap< sound_file_t, unsigned int >* soundLookup = NULL; + +void Sys_StreamInitialize( void ); + +static DWORD WINAPI _streamThread(LPVOID) +{ + for (;;) + { + IORequest req; + DWORD bytes; + StreamInfo* strm; + + // Wait for the IO queue to fill + WaitForSingleObject(s_QueueLen, INFINITE); + + // Grab the next IO request + WaitForSingleObject(s_QueueMutex, INFINITE); + assert(!s_IORequestQueue->empty()); + req = s_IORequestQueue->front(); + s_IORequestQueue->pop_front(); + ReleaseMutex(s_QueueMutex); + + int offset = 0; + sound_file_t* crap; + + // Process request + switch (req.type) + { + case IOREQ_OPEN: + + strm = &s_Streams[req.handle]; + assert(strm->used); + + strm->file = req.data[0]; + strm->error = (strm->file == -1); + strm->opening = false; + break; +/* + { + const char* name = Sys_GetFileCodeName(req.data[0]); + + WaitForSingleObject(Sys_FileStreamMutex, INFINITE); + + strm->file = + CreateFile(name, GENERIC_READ, + FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + + ReleaseMutex(Sys_FileStreamMutex); + } + + strm->error = (strm->file == INVALID_HANDLE_VALUE); + strm->opening = false; + break; +*/ + case IOREQ_READ: + { + strm = &s_Streams[req.handle]; + assert(strm->used); + + WaitForSingleObject(Sys_FileStreamMutex, INFINITE); + + crap = soundLookup->Find(strm->file); + + if(crap) + { + offset = crap->offset + req.data[2]; + strm->error = (SetFilePointer(soundfile, offset, 0, FILE_BEGIN) != offset) || + (ReadFile(soundfile, (void*)req.data[0], req.data[1], &bytes, NULL) == 0); + } + else + { + strm->error = true; + } + + + /* + strm->error = + (SetFilePointer(strm->file, req.data[2], 0, FILE_BEGIN) != req.data[2] || + ReadFile(strm->file, (void*)req.data[0], req.data[1], &bytes, NULL) == 0); + */ + + ReleaseMutex(Sys_FileStreamMutex); + + strm->reading = false; + } + break; + + case IOREQ_SHUTDOWN: + ExitThread(0); + break; + } + } + + return TRUE; +} + + +static void _sendIORequest(const IORequest& req) +{ + // Add request to queue + WaitForSingleObject(s_QueueMutex, INFINITE); + Z_SetNewDeleteTemporary(true); + s_IORequestQueue->push_back(req); + Z_SetNewDeleteTemporary(false); + ReleaseMutex(s_QueueMutex); + + // Let IO thread know it has one more pending request + ReleaseSemaphore(s_QueueLen, 1, NULL); +} + +void Sys_IORequestQueueClear(void) +{ + WaitForSingleObject(s_QueueMutex, INFINITE); + delete s_IORequestQueue; + s_IORequestQueue = new requestqueue_t; + ReleaseMutex(s_QueueMutex); +} + +void Sys_StreamInit(void) +{ + Sys_StreamInitialize(); + + // Create array for storing open streams + s_Streams = (StreamInfo*)Z_Malloc( + STREAM_MAX_OPEN * sizeof(StreamInfo), TAG_FILESYS, qfalse); + for (int i = 0; i < STREAM_MAX_OPEN; ++i) + { + s_Streams[i].used = false; + } + + // Create queue to hold requests for IO thread + s_IORequestQueue = new requestqueue_t; + + // Create a thread to service IO + s_QueueMutex = CreateMutex(NULL, FALSE, NULL); + s_QueueLen = CreateSemaphore(NULL, 0, STREAM_MAX_OPEN * 3, NULL); + s_Thread = CreateThread(NULL, 64*1024, _streamThread, 0, 0, NULL); +} + +void Sys_StreamShutdown(void) +{ + // Tell the IO thread to shutdown + IORequest req; + req.type = IOREQ_SHUTDOWN; + _sendIORequest(req); + + // Wait for thread to close + WaitForSingleObject(s_Thread, INFINITE); + + // Kill IO thread + CloseHandle(s_Thread); + CloseHandle(s_QueueLen); + CloseHandle(s_QueueMutex); + + // Remove queue of IO requests + delete s_IORequestQueue; + + // Remove streaming table + Z_Free(s_Streams); +} + +static streamHandle_t GetFreeHandle(void) +{ + for (streamHandle_t i = 1; i < STREAM_MAX_OPEN; ++i) + { + if (!s_Streams[i].used) return i; + } + + // handle 0 is invalid by convention + return 0; +} + +int Sys_StreamOpen(int code, streamHandle_t *handle) +{ + // Find a free handle + *handle = GetFreeHandle(); + if (*handle == 0) + { + return -1; + } + + // Find the file size + sound_file_t* crap = soundLookup->Find(code); + int size = -1; + if(crap) + { + size = crap->size; + } + + if (size < 0) + { + *handle = 0; + return -1; + } + + // Init stream data + s_Streams[*handle].used = true; + s_Streams[*handle].opening = true; + s_Streams[*handle].reading = false; + s_Streams[*handle].error = false; + + // Send an open request to the thread + IORequest req; + req.type = IOREQ_OPEN; + req.handle = *handle; + req.data[0] = code; + _sendIORequest(req); + + // Return file size + return size; +} + +bool Sys_StreamRead(void* buffer, int size, int pos, streamHandle_t handle) +{ + assert((unsigned int)buffer % 32 == 0); + + // Handle must be valid. Do not allow multiple reads. + if (!s_Streams[handle].used || s_Streams[handle].reading) return false; + + // Ready to read + s_Streams[handle].reading = true; + s_Streams[handle].error = false; + + // Request IO threading reading + IORequest req; + req.type = IOREQ_READ; + req.handle = handle; + req.data[0] = (DWORD)buffer; + req.data[1] = size; + req.data[2] = pos; + _sendIORequest(req); + + return true; +} + +bool Sys_StreamIsReading(streamHandle_t handle) +{ + return s_Streams[handle].used && s_Streams[handle].reading; +} + +bool Sys_StreamIsError(streamHandle_t handle) +{ + return s_Streams[handle].used && s_Streams[handle].error; +} + +void Sys_StreamClose(streamHandle_t handle) +{ + if (s_Streams[handle].used) + { + // Block until read is done + while (s_Streams[handle].opening || s_Streams[handle].reading); + + // Close the file +// CloseHandle(s_Streams[handle].file); + s_Streams[handle].used = false; + } +} + +extern char* FS_BuildOSPathUnMapped(const char* name); + +unsigned int Sys_GetSoundFileCode(const char* name) +{ + // Get system level path + char* osname = FS_BuildOSPathUnMapped(name); + + // Generate hash for file name + strlwr(osname); + unsigned int code = crc32(0, (const byte *)osname, strlen(osname)); + + return code; +} + +unsigned int Sys_GetSoundFileCodeFlags(unsigned int code) +{ + sound_file_t* sf; + sf = soundLookup->Find(code); + + if(!sf) + { + return 0; + } + else + { + return sf->filenameFlags; + } +} + +int Sys_GetSoundFileCodeSize(unsigned int code) +{ + sound_file_t* sf; + sf = soundLookup->Find(code); + + if(!sf) + { + return -1; + } + else + { + return sf->size; + } + +} + + +#if PROFILE_SOUND +VVFixedMap< char*, unsigned int>* soundCrc = NULL; +void Sys_LoadSoundCRCFile( void ) +{ + FILE* file; + file = fopen("d:\\base\\soundbank\\crclookup.txt", "rb"); + + if(!file) + return; + + int numberOfLines = 0; + unsigned int crc = 0; + char name[255]; + + // count the number of lines + while(1) + { + if(fscanf(file, "%d %s", &crc, name) == -1) + break; + numberOfLines++; + } + + // allocate memory for the crc lookup + soundCrc = new VVFixedMap(numberOfLines); + + // actually read and store the data + fseek(file, 0, SEEK_SET); + while(1) + { + if(fscanf(file, "%d %s", &crc, name) == -1) + break; + + char *temp = (char*)Z_Malloc(strlen(name) + 1, TAG_SND_RAWDATA, qtrue); + + strcpy(temp, name); + + soundCrc->Insert(temp, crc); + } + soundCrc->Sort(); + fclose(file); +} + +char* Sys_GetSoundName( unsigned int crc ) +{ + char* name = *soundCrc->Find(crc); + return name; +} + +#endif // PROFILE_SOUND + +extern const char *Sys_RemapPath( const char *filename ); + +void Sys_StreamInitialize( void ) +{ + // open the sound file + soundfile = CreateFile( + Sys_RemapPath("base\\soundbank\\sound.bnk"), + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_READONLY | FILE_FLAG_RANDOM_ACCESS, + NULL ); + + // fill in the lookup table + HANDLE table = INVALID_HANDLE_VALUE; + + table = CreateFile( + Sys_RemapPath("base\\soundbank\\sound.tbl"), + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL ); + + DWORD fileSize = 0; + fileSize = GetFileSize( + table, + NULL); + + int numberOfRecords = fileSize / ((sizeof(unsigned int) * 3) + 1); + + soundLookup = new VVFixedMap(numberOfRecords); + + byte* tempData = (byte*)Z_Malloc(fileSize, TAG_TEMP_WORKSPACE, true, 32); + byte* restore = tempData; + + DWORD bytesRead; + + ReadFile( + table, + tempData, + fileSize, + &bytesRead, + NULL ); + + if(bytesRead != fileSize) + Com_Error(0,"Could not read sound index file.\n"); + + CloseHandle(table); + + for(int i = 0; i < numberOfRecords; i++) + { + unsigned int filecode = *(unsigned int*)tempData; + tempData += sizeof(unsigned int); + unsigned int offset = *(unsigned int*)tempData; + tempData += sizeof(unsigned int); + int size = *(int*)tempData; + tempData += sizeof(int); + unsigned char filenameFlags = *(unsigned char*)tempData; + tempData++; + + sound_file_t sfile; + sfile.offset = offset; + sfile.size = size; + sfile.filenameFlags = filenameFlags; + + soundLookup->Insert(sfile, filecode); + } + + soundLookup->Sort(); + Z_Free(restore); +#if PROFILE_SOUND + Sys_LoadSoundCRCFile(); +#endif +} diff --git a/code/win32/win_syscon.cpp b/code/win32/win_syscon.cpp new file mode 100644 index 0000000..819a0ce --- /dev/null +++ b/code/win32/win_syscon.cpp @@ -0,0 +1,536 @@ +// win_syscon.h + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + + +#include "../client/client.h" +#include "win_local.h" +#include "resource.h" +#include +#include +#include +#include +#include +#include +#include + +#define COPY_ID 1 +#define QUIT_ID 2 +#define CLEAR_ID 3 + +#define ERRORBOX_ID 10 +#define ERRORTEXT_ID 11 + +#define EDIT_ID 100 +#define INPUT_ID 101 + +typedef struct +{ + HWND hWnd; + HWND hwndBuffer; + + HWND hwndButtonClear; + HWND hwndButtonCopy; + HWND hwndButtonQuit; + + HWND hwndErrorBox; + HWND hwndErrorText; + + HBITMAP hbmLogo; + HBITMAP hbmClearBitmap; + + HBRUSH hbrEditBackground; + HBRUSH hbrErrorBackground; + + HFONT hfBufferFont; + HFONT hfButtonFont; + + HWND hwndInputLine; + + char errorString[80]; + + char consoleText[512], returnedText[512]; + int visLevel; + qboolean quitOnClose; + int windowWidth, windowHeight; + + WNDPROC SysInputLineWndProc; + +} WinConData; + +static WinConData s_wcd; + +static LONG WINAPI ConWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + char *cmdString; + static qboolean s_timePolarity; + + switch (uMsg) + { + case WM_ACTIVATE: + if ( LOWORD( wParam ) != WA_INACTIVE ) + { + SetFocus( s_wcd.hwndInputLine ); + } + + if ( com_viewlog ) + { + // if the viewlog is open, check to see if it's being minimized + if ( com_viewlog->integer == 1 ) + { + if ( HIWORD( wParam ) ) // minimized flag + { + Cvar_Set( "viewlog", "2" ); + } + } + else if ( com_viewlog->integer == 2 ) + { + if ( !HIWORD( wParam ) ) // minimized flag + { + Cvar_Set( "viewlog", "1" ); + } + } + } + break; + + case WM_CLOSE: + //cmdString = CopyString( "quit" ); + //Sys_QueEvent( 0, SE_CONSOLE, 0, 0, strlen( cmdString ) + 1, cmdString ); + if ( s_wcd.quitOnClose ) + { + PostQuitMessage( 0 ); + } + else + { + Sys_ShowConsole( 0, qfalse ); + Cvar_Set( "viewlog", "0" ); + } + return 0; + case WM_CTLCOLORSTATIC: + if ( ( HWND ) lParam == s_wcd.hwndBuffer ) + { + SetBkColor( ( HDC ) wParam, RGB( 0, 0, 0 ) ); + SetTextColor( ( HDC ) wParam, RGB( 249, 249, 000 ) ); + return ( long ) s_wcd.hbrEditBackground; + } + else if ( ( HWND ) lParam == s_wcd.hwndErrorBox ) + { + if ( s_timePolarity & 1 ) + { + SetBkColor( ( HDC ) wParam, RGB( 0x80, 0x80, 0x80 ) ); + SetTextColor( ( HDC ) wParam, RGB( 0xff, 0x00, 0x00 ) ); + } + else + { + SetBkColor( ( HDC ) wParam, RGB( 0x80, 0x80, 0x80 ) ); + SetTextColor( ( HDC ) wParam, RGB( 0x00, 0x00, 0x00 ) ); + } + return ( long ) s_wcd.hbrErrorBackground; + } + return FALSE; + break; + + case WM_COMMAND: + if ( wParam == COPY_ID ) + { + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + SendMessage( s_wcd.hwndBuffer, WM_COPY, 0, 0 ); + } + else if ( wParam == QUIT_ID ) + { + if ( s_wcd.quitOnClose ) + { + PostQuitMessage( 0 ); + } + else + { + cmdString = CopyString( "quit" ); + Sys_QueEvent( 0, SE_CONSOLE, 0, 0, strlen( cmdString ) + 1, cmdString ); + } + } + else if ( wParam == CLEAR_ID ) + { + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, FALSE, ( LPARAM ) "" ); + UpdateWindow( s_wcd.hwndBuffer ); + } + break; + case WM_CREATE: + s_wcd.hbrEditBackground = CreateSolidBrush( RGB( 0x00, 0x00, 0x00 ) ); + s_wcd.hbrErrorBackground = CreateSolidBrush( RGB( 0x80, 0x80, 0x80 ) ); + SetTimer( hWnd, 1, 1000, NULL ); + break; + case WM_ERASEBKGND: + return DefWindowProc( hWnd, uMsg, wParam, lParam ); + case WM_TIMER: + if ( wParam == 1 ) + { + s_timePolarity = !s_timePolarity; + if ( s_wcd.hwndErrorBox ) + { + InvalidateRect( s_wcd.hwndErrorBox, NULL, FALSE ); + } + } + break; + } + + return DefWindowProc( hWnd, uMsg, wParam, lParam ); +} + +LONG WINAPI InputLineWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + char inputBuffer[1024]; + + switch ( uMsg ) + { + case WM_KILLFOCUS: + if ( ( HWND ) wParam == s_wcd.hWnd || + ( HWND ) wParam == s_wcd.hwndErrorBox ) + { + SetFocus( hWnd ); + return 0; + } + break; + + case WM_CHAR: + if ( wParam == 13 ) + { + GetWindowText( s_wcd.hwndInputLine, inputBuffer, sizeof( inputBuffer ) ); + strncat( s_wcd.consoleText, inputBuffer, sizeof( s_wcd.consoleText ) - strlen( s_wcd.consoleText ) - 5 ); + strcat( s_wcd.consoleText, "\n" ); + SetWindowText( s_wcd.hwndInputLine, "" ); + + Sys_Print( va( "]%s\n", inputBuffer ) ); + + return 0; + } + } + + return CallWindowProc( s_wcd.SysInputLineWndProc, hWnd, uMsg, wParam, lParam ); +} + +/* +** Sys_CreateConsole +*/ +void Sys_CreateConsole( void ) +{ + HDC hDC; + WNDCLASS wc; + RECT rect; + const char *DEDCLASS = "JK2MP WinConsole"; + int nHeight; + int swidth, sheight; + int DEDSTYLE = WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX; + + memset( &wc, 0, sizeof( wc ) ); + + wc.style = 0; + wc.lpfnWndProc = (WNDPROC) ConWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = g_wv.hInstance; + wc.hIcon = LoadIcon( g_wv.hInstance, MAKEINTRESOURCE(IDI_ICON1)); + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = (HBRUSH__ *)COLOR_INACTIVEBORDER;//(HBRUSH__ *)COLOR_WINDOW; + wc.lpszMenuName = 0; + wc.lpszClassName = DEDCLASS; + + if ( !RegisterClass (&wc) ) { + return; + } + + rect.left = 0; + rect.right = 600; + rect.top = 0; + rect.bottom = 450; + AdjustWindowRect( &rect, DEDSTYLE, FALSE ); + + hDC = GetDC( GetDesktopWindow() ); + swidth = GetDeviceCaps( hDC, HORZRES ); + sheight = GetDeviceCaps( hDC, VERTRES ); + ReleaseDC( GetDesktopWindow(), hDC ); + + s_wcd.windowWidth = rect.right - rect.left + 1; + s_wcd.windowHeight = rect.bottom - rect.top + 1; + + s_wcd.hWnd = CreateWindowEx( 0, + DEDCLASS, + "Jedi Knight®: Jedi Academy SP Console", + DEDSTYLE, + ( swidth - 600 ) / 2, ( sheight - 450 ) / 2 , rect.right - rect.left + 1, rect.bottom - rect.top + 1, + NULL, + NULL, + g_wv.hInstance, + NULL ); + + if ( s_wcd.hWnd == NULL ) + { + return; + } + + // + // create fonts + // + hDC = GetDC( s_wcd.hWnd ); + nHeight = -MulDiv( 8, GetDeviceCaps( hDC, LOGPIXELSY), 72); + + s_wcd.hfBufferFont = CreateFont( nHeight, + 0, + 0, + 0, + FW_LIGHT, + 0, + 0, + 0, + DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, + FF_MODERN | FIXED_PITCH, + "Courier New" ); + + ReleaseDC( s_wcd.hWnd, hDC ); + + // + // create the input line + // + s_wcd.hwndInputLine = CreateWindow( "edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | + ES_LEFT | ES_AUTOHSCROLL, + 6, 400, s_wcd.windowWidth-20, 20, + s_wcd.hWnd, + ( HMENU ) INPUT_ID, // child window ID + g_wv.hInstance, NULL ); + + // + // create the buttons + // + s_wcd.hwndButtonCopy = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 5, 425, 72, 24, + s_wcd.hWnd, + ( HMENU ) COPY_ID, // child window ID + g_wv.hInstance, NULL ); + SendMessage( s_wcd.hwndButtonCopy, WM_SETTEXT, 0, ( LPARAM ) "copy" ); + + s_wcd.hwndButtonClear = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + 82, 425, 72, 24, + s_wcd.hWnd, + ( HMENU ) CLEAR_ID, // child window ID + g_wv.hInstance, NULL ); + SendMessage( s_wcd.hwndButtonClear, WM_SETTEXT, 0, ( LPARAM ) "clear" ); + + s_wcd.hwndButtonQuit = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, + s_wcd.windowWidth-92, 425, 72, 24, + s_wcd.hWnd, + ( HMENU ) QUIT_ID, // child window ID + g_wv.hInstance, NULL ); + SendMessage( s_wcd.hwndButtonQuit, WM_SETTEXT, 0, ( LPARAM ) "quit" ); + + + // + // create the scrollbuffer + // + s_wcd.hwndBuffer = CreateWindow( "edit", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_BORDER | + ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY, + 6, 40, s_wcd.windowWidth-20, 354, + s_wcd.hWnd, + ( HMENU ) EDIT_ID, // child window ID + g_wv.hInstance, NULL ); + SendMessage( s_wcd.hwndBuffer, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 ); + + s_wcd.SysInputLineWndProc = ( WNDPROC ) SetWindowLong( s_wcd.hwndInputLine, GWL_WNDPROC, ( long ) InputLineWndProc ); + SendMessage( s_wcd.hwndInputLine, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 ); + SendMessage( s_wcd.hwndBuffer, EM_LIMITTEXT, ( WPARAM ) 0x7fff, 0 ); + + ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT); + UpdateWindow( s_wcd.hWnd ); + SetForegroundWindow( s_wcd.hWnd ); + SetFocus( s_wcd.hwndInputLine ); + + s_wcd.visLevel = 1; +} + +/* +** Sys_DestroyConsole +*/ +void Sys_DestroyConsole( void ) { + if ( s_wcd.hWnd ) { + DeleteObject(s_wcd.hbrEditBackground); + DeleteObject(s_wcd.hbrErrorBackground); + DeleteObject(s_wcd.hfBufferFont); + ShowWindow( s_wcd.hWnd, SW_HIDE ); + CloseWindow( s_wcd.hWnd ); + DestroyWindow( s_wcd.hWnd ); + s_wcd.hWnd = 0; + } +} + +/* +** Sys_ShowConsole +*/ +void Sys_ShowConsole( int visLevel, qboolean quitOnClose ) +{ + s_wcd.quitOnClose = quitOnClose; + + if ( visLevel == s_wcd.visLevel ) + { + if (quitOnClose) {//attempt to bring it to the front on error exit + SetForegroundWindow( s_wcd.hWnd ); + SetFocus( s_wcd.hwndInputLine ); + } + return; + } + + s_wcd.visLevel = visLevel; + + if ( !s_wcd.hWnd ){ + return; + } + + switch ( visLevel ) + { + case 0: + ShowWindow( s_wcd.hWnd, SW_HIDE ); + break; + case 1: + ShowWindow( s_wcd.hWnd, SW_SHOWNORMAL ); + SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); + if (quitOnClose) {//attempt to bring it to the front on error exit + SetForegroundWindow( s_wcd.hWnd ); + SetFocus( s_wcd.hwndInputLine ); + } + break; + case 2: + ShowWindow( s_wcd.hWnd, SW_MINIMIZE ); + break; + default: + Sys_Error( "Invalid visLevel %d sent to Sys_ShowConsole\n", visLevel ); + break; + } +} + +/* +** Sys_ConsoleInput +*/ +char *Sys_ConsoleInput( void ) +{ + if ( s_wcd.consoleText[0] == 0 ) + { + return NULL; + } + + strcpy( s_wcd.returnedText, s_wcd.consoleText ); + s_wcd.consoleText[0] = 0; + + return s_wcd.returnedText; +} + +/* +** Conbuf_AppendText +*/ +void Conbuf_AppendText( const char *pMsg ) +{ +#define CONSOLE_BUFFER_SIZE 16384 + if ( !s_wcd.hWnd ) { + return; + } + char buffer[CONSOLE_BUFFER_SIZE*4]; + char *b = buffer; + const char *msg; + int bufLen; + int i = 0; + static unsigned long s_totalChars; + + // + // if the message is REALLY long, use just the last portion of it + // + if ( strlen( pMsg ) > CONSOLE_BUFFER_SIZE - 1 ) + { + msg = pMsg + strlen( pMsg ) - CONSOLE_BUFFER_SIZE + 1; + } + else + { + msg = pMsg; + } + + // + // copy into an intermediate buffer + // + while ( msg[i] && ( ( b - buffer ) < sizeof( buffer ) - 1 ) ) + { + if ( msg[i] == '\n' && msg[i+1] == '\r' ) + { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + i++; + } + else if ( msg[i] == '\r' ) + { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + } + else if ( msg[i] == '\n' ) + { + b[0] = '\r'; + b[1] = '\n'; + b += 2; + } + else if ( Q_IsColorString( &msg[i] ) ) + { + i++; + } + else + { + *b= msg[i]; + b++; + } + i++; + } + *b = 0; + bufLen = b - buffer; + + s_totalChars += bufLen; + + // + // replace selection instead of appending if we're overflowing + // + if ( s_totalChars > 0x7fff ) + { + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + s_totalChars = bufLen; + } + + // + // put this text into the windows console + // + SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); + SendMessage( s_wcd.hwndBuffer, EM_SCROLLCARET, 0, 0 ); + SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, 0, (LPARAM) buffer ); +} + +/* +** Sys_SetErrorText +*/ +void Sys_SetErrorText( const char *buf ) +{ + Q_strncpyz( s_wcd.errorString, buf, sizeof( s_wcd.errorString ) ); + + if ( !s_wcd.hwndErrorBox ) + { + s_wcd.hwndErrorBox = CreateWindow( "static", NULL, WS_CHILD | WS_VISIBLE | SS_SUNKEN, + 6, 5, s_wcd.windowWidth-20, 30, + s_wcd.hWnd, + ( HMENU ) ERRORBOX_ID, // child window ID + g_wv.hInstance, NULL ); + SendMessage( s_wcd.hwndErrorBox, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 ); + SetWindowText( s_wcd.hwndErrorBox, s_wcd.errorString ); + + DestroyWindow( s_wcd.hwndInputLine ); + s_wcd.hwndInputLine = NULL; + } +} diff --git a/code/win32/win_video.cpp b/code/win32/win_video.cpp new file mode 100644 index 0000000..cfb6659 --- /dev/null +++ b/code/win32/win_video.cpp @@ -0,0 +1,325 @@ +// Filename:- win_video.cpp +// +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "../client/client.h" +#include "win_local.h" + +/* +#include "bink.h" + + +static HANDLE hBinkFile = NULL; +static HBINK hBink = NULL; +*/ + +extern unsigned int SNDDMA_GetDSHandle(void); +extern int s_soundStarted; // if 0, game is running in silent mode + +// ret qtrue if success... +// +qboolean VIDEO_GetDims(int *piWidth, int *piHeight) +{ +/* + if (hBink) + { + if (piWidth && piHeight) + { +// *piWidth = hBink->Width; // Width (1 based, 640 for example) + + if (hBink->decompwidth == hBink->Width/2) + { + *piWidth = hBink->decompwidth; + } + else + { + *piWidth = hBink->Width; + } + + if (hBink->decompheight == hBink->Height/2) + { + *piHeight = hBink->decompheight; + } + else + { + *piHeight = hBink->Height; + } + + return qtrue; + } + } +*/ + return qfalse; +} + + +// ret qtrue if no reason to delay... +// +qboolean VIDEO_NextFrameReady(void) +{ +/* if (hBink && BinkWait(hBink)) + { + return qfalse; + } +*/ + return qtrue; +} + +void VIDEO_Pause(qboolean bPaused) +{ +/* + if (hBink) + { + BinkPause(hBink, bPaused); + } +*/ +} + +void VIDEO_Mute(qboolean bMute) +{ +/* + if (!s_soundStarted) + bMute = qtrue; // + + if (hBink) + { +// BinkSetSoundOnOff(hBink, !bMute); // this has a bug, and freezes video playback + BinkSetVolume(hBink,bMute?0:32768); // effectively the same, and instant, not just to next audio packet + } +*/ +} + +// advance to the next Bink Frame, qtrue if not finished playback +// +//qboolean VIDEO_NextFrame( byte *pbDestBuffer ) // pbDestBuffer will be sizeof Vid dims *4 +qboolean VIDEO_NextFrame( byte *pbDestBuffer, int iBytesPerLine, qboolean &bFrameSkipped) +{ +/* + if (hBink) + { + bFrameSkipped = BinkDoFrame(hBink); + + if (!bFrameSkipped) + { + +//extern cvar_t *r_speeds; +//int start,end; +//if ( r_speeds->integer ) +//{ +// start = Sys_Milliseconds(); +//} + BinkCopyToBuffer( hBink, // HBINK bnk, + pbDestBuffer, // void* dest, + iBytesPerLine,//hBink->Width*4, // u32 destpitch (bytes, not pixels) + hBink->Height, // u32 destheight + 0, // u32 destx + 0, // u32 desty, + BINKSURFACE32R + //|BINKCOPYALL + |BINKCOPYNOSCALING // important, or you'll crash on half-height compressed videos now! + ); + +//if ( r_speeds->integer ) +//{ +// end = Sys_Milliseconds(); +// Com_Printf( "BinkCopyToBuffer(): %i msec\n", end - start ); +//} + } + + // finished? + // + qboolean bStillPlaying = qtrue; + + if (hBink->FrameNum == hBink->Frames) + { + bStillPlaying = qfalse; // finished + } + else + { + BinkNextFrame(hBink); + } + + return bStillPlaying; + } +*/ + return qfalse; // will only get here if some sort of error in Q3 logic (which does happen) so that this is called + // at the wrong time. +} + + + +// called even if not fully opened, so check everything... +// +void VIDEO_Close(void) +{ +/* + if (hBink) + { +#ifdef _DEBUG + BINKSUMMARY Summary; + BinkGetSummary(hBink, &Summary); + Com_DPrintf("\nBINK Playback Summary:\n\n"); + Com_DPrintf("TotalTime: %d\n",Summary.TotalTime); // + Com_DPrintf("FileFrameRate: %d\n",Summary.FileFrameRate); // frame rate + Com_DPrintf("FileFrameRateDiv: %d\n",Summary.FileFrameRateDiv); // frame rate divisor + Com_DPrintf("FrameRate: %d\n",Summary.FrameRate); // frame rate + Com_DPrintf("FrameRateDiv: %d\n",Summary.FrameRateDiv); // frame rate divisor + Com_DPrintf("TotalOpenTime: %d\n",Summary.TotalOpenTime); // Time to open and prepare for decompression + Com_DPrintf("TotalFrames: %d\n",Summary.TotalFrames); // Total Frames + Com_DPrintf("TotalPlayedFrames: %d\n",Summary.TotalPlayedFrames); // Total Frames played + Com_DPrintf("SkippedFrames: %d\n",Summary.SkippedFrames); // Total number of skipped frames + Com_DPrintf("SkippedBlits: %d\n",Summary.SkippedBlits); // Total number of skipped blits + Com_DPrintf("SoundSkips: %d\n",Summary.SoundSkips); // Total number of sound skips + Com_DPrintf("TotalBlitTime: %d\n",Summary.TotalBlitTime); // Total time spent blitting + Com_DPrintf("TotalReadTime: %d\n",Summary.TotalReadTime); // Total time spent reading + Com_DPrintf("TotalVideoDecompTime: %d\n",Summary.TotalVideoDecompTime); // Total time spent decompressing video + Com_DPrintf("TotalAudioDecompTime: %d\n",Summary.TotalAudioDecompTime); // Total time spent decompressing audio + Com_DPrintf("TotalBackReadTime: %d\n",Summary.TotalBackReadTime); // Total time spent reading in background + Com_DPrintf("TotalReadSpeed: %d\n",Summary.TotalReadSpeed); // Total io speed (bytes/second) + Com_DPrintf("SlowestFrameTime: %d\n",Summary.SlowestFrameTime); // Slowest single frame time (ms) + Com_DPrintf("Slowest2FrameTime: %d\n",Summary.Slowest2FrameTime); // Second slowest single frame time (ms) + Com_DPrintf("SlowestFrameNum: %d\n",Summary.SlowestFrameNum); // Slowest single frame number + Com_DPrintf("Slowest2FrameNum: %d\n",Summary.Slowest2FrameNum); // Second slowest single frame number + Com_DPrintf("AverageDataRate: %d\n",Summary.AverageDataRate); // Average data rate of the movie + Com_DPrintf("AverageFrameSize: %d\n",Summary.AverageFrameSize); // Average size of the frame + Com_DPrintf("HighestMemAmount: %d\n",Summary.HighestMemAmount); // Highest amount of memory allocated + Com_DPrintf("TotalIOMemory: %d\n",Summary.TotalIOMemory); // Total extra memory allocated + Com_DPrintf("HighestIOUsed: %d\n",Summary.HighestIOUsed); // Highest extra memory actually used + Com_DPrintf("Highest1SecRate: %d\n",Summary.Highest1SecRate); // Highest 1 second rate + Com_DPrintf("Highest1SecFrame: %d\n",Summary.Highest1SecFrame); // Highest 1 second start frame + Com_DPrintf("\n"); +#endif + BinkClose(hBink); + hBink = NULL; + SNDDMA_Activate(); + } + + if (hBinkFile) + { + CloseHandle(hBinkFile); + hBinkFile = NULL; + } +*/ +} +/* +static qboolean VIDEO_Open2(char *psPathlessBaseName, qboolean qbInGame, qboolean qbTestOpenOnly, int iLanguageNumber) +{ + char sLocalFilename[MAX_OSPATH]; + + // Get the Quake filesystem to see if it exists, and fill in some internal structs as to where it really is, + // and what offset (if PAK) etc... + // + Com_sprintf (sLocalFilename, sizeof(sLocalFilename), "video/%s.bik", psPathlessBaseName); + char *psActualFilename; + int iSeekOffset; + qboolean bResult = FS_GetExtendedInfo_FOpenFileRead(sLocalFilename, &psActualFilename, &iSeekOffset); + if (!bResult) + { + if (!qbTestOpenOnly) + { + Com_Printf(S_COLOR_RED"Couldn't open %s\n", sLocalFilename); + } + return qfalse; + } + + if (qbTestOpenOnly) + return qtrue; + + + // Now re-open as a Windoze HANDLE, because Bink doesn't use FILE * types... + // + hBinkFile = CreateFile( psActualFilename, // LPCTSTR lpFileName, // pointer to name of the file + GENERIC_READ, // DWORD dwDesiredAccess, // access (read-write) mode + FILE_SHARE_READ, // DWORD dwShareMode, // share mode + NULL, // LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes + OPEN_EXISTING, // DWORD dwCreationDisposition, // how to create + FILE_FLAG_SEQUENTIAL_SCAN,// DWORD dwFlagsAndAttributes, // file attributes + NULL // HANDLE hTemplateFile // handle to file with attributes to + ); + + if (hBinkFile == INVALID_HANDLE_VALUE) + { + Com_Printf(S_COLOR_RED"Error converting FILE* to HANDLE for Bink, file: %s\n", psActualFilename); + return qfalse; + } + + + // Opened ok, now seek the handle to the correct position for starting (ie if inside a PAK file etc) + // + DWORD dwFileOffset = SetFilePointer(hBinkFile, // HANDLE hFile, // handle of file + iSeekOffset,// LONG lDistanceToMove, // number of bytes to move file pointer + NULL, // PLONG lpDistanceToMoveHigh, // pointer to high-order DWORD of distance to move + FILE_BEGIN // DWORD dwMoveMethod // how to move + ); + + if (dwFileOffset != iSeekOffset) + { + VIDEO_Close(); + Com_Printf( S_COLOR_RED"Error seeking to Bink video start (offset %d), file: %s\n", iSeekOffset,psActualFilename); + return qfalse; + } + + + // ok, I think we're ready... + // + if (!cls.soundStarted || s_soundStarted) // sound system not started (occurs in very first video), or sound desired + { + BinkSoundUseDirectSound(SNDDMA_GetDSHandle()); + BinkSetSoundTrack(iLanguageNumber); // select the language anyway here, regardless of multi-lingual file or not + } + else + { + BinkSetSoundTrack(BINKNOSOUND); + } + +// hBink = BinkOpen("d:\\kiss.bik",0); + hBink = BinkOpen((char *)hBinkFile,BINKFILEHANDLE | BINKSNDTRACK); + if (!hBink) + { + VIDEO_Close(); + Com_Printf( S_COLOR_RED"Unrecognised video file: %s\n", psActualFilename); + return qfalse; + } + + if (!s_soundStarted) // game running in silent mode? + { + VIDEO_Mute(qtrue); + } + + return qtrue; +} +*/ + +// now modified to take different languages into account... +// +qboolean VIDEO_Open(char *psPathlessBaseName, qboolean qbInGame, qboolean qbTestOpenOnly, int iLanguageNumber) +{ +/* + qboolean qbReturn = VIDEO_Open2(psPathlessBaseName, qbInGame, qbTestOpenOnly, iLanguageNumber); + + if (qbReturn) + { + if (!qbTestOpenOnly && hBink) + { + // check we didn't try to (eg) select German audio on a file with 1 track (eg just music) + // + if (iLanguageNumber+1 > hBink->NumTracks) + { + VIDEO_Close(); + return VIDEO_Open2(psPathlessBaseName, qbInGame, qbTestOpenOnly, 0); + } + } + } + + return qbReturn; +*/ + return qfalse; +} + + +//////////////// eof ////////////////// + diff --git a/code/win32/win_wndproc.cpp b/code/win32/win_wndproc.cpp new file mode 100644 index 0000000..8cc24ff --- /dev/null +++ b/code/win32/win_wndproc.cpp @@ -0,0 +1,532 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + + +#include "../client/client.h" +#include "win_local.h" + +// The only directly referenced keycode - the console key (which gives different ascii codes depending on locale) +#define CONSOLE_SCAN_CODE 0x29 + + +WinVars_t g_wv; + +#ifndef WM_MOUSEWHEEL +#define WM_MOUSEWHEEL (WM_MOUSELAST+1) // message that will be supported by the OS +#endif + +static UINT MSH_MOUSEWHEEL; + +// Console variables that we need to access from this module +cvar_t *vid_xpos; // X coordinate of window position +cvar_t *vid_ypos; // Y coordinate of window position +cvar_t *sr_fullscreen; + +#define VID_NUM_MODES ( sizeof( vid_modes ) / sizeof( vid_modes[0] ) ) + +LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); + +static qboolean s_alttab_disabled; + +static void WIN_DisableAltTab( void ) +{ + if ( s_alttab_disabled ) + return; + + if ( !Q_stricmp( Cvar_VariableString( "arch" ), "winnt" ) ) + { + RegisterHotKey( 0, 0, MOD_ALT, VK_TAB ); + } + else + { + BOOL old; + + SystemParametersInfo( SPI_SCREENSAVERRUNNING, 1, &old, 0 ); + } + s_alttab_disabled = qtrue; +} + +static void WIN_EnableAltTab( void ) +{ + if ( s_alttab_disabled ) + { + if ( !Q_stricmp( Cvar_VariableString( "arch" ), "winnt" ) ) + { + UnregisterHotKey( 0, 0 ); + } + else + { + BOOL old; + + SystemParametersInfo( SPI_SCREENSAVERRUNNING, 0, &old, 0 ); + } + + s_alttab_disabled = qfalse; + } +} + +/* +================== +VID_AppActivate +================== +*/ +static void VID_AppActivate(BOOL fActive, BOOL minimize) +{ + g_wv.isMinimized = minimize; + + Key_ClearStates(); // FIXME!!! + + // we don't want to act like we're active if we're minimized + if (fActive && !g_wv.isMinimized ) + { + g_wv.activeApp = qtrue; + } + else + { + g_wv.activeApp = qfalse; + } + + // minimize/restore mouse-capture on demand + if (!g_wv.activeApp ) + { + IN_Activate (qfalse); + } + else + { + IN_Activate (qtrue); + } +} + +static byte virtualKeyConvert[0x92][2] = +{ + { 0, 0 }, + { A_MOUSE1, A_MOUSE1 }, // VK_LBUTTON 01 Left mouse button + { A_MOUSE2, A_MOUSE2 }, // VK_RBUTTON 02 Right mouse button + { 0, 0 }, // VK_CANCEL 03 Control-break processing + { A_MOUSE3, A_MOUSE3 }, // VK_MBUTTON 04 Middle mouse button (three-button mouse) + { A_MOUSE4, A_MOUSE4 }, // VK_XBUTTON1 05 Windows 2000/XP: X1 mouse button + { A_MOUSE5, A_MOUSE5 }, // VK_XBUTTON2 06 Windows 2000/XP: X2 mouse button + { 0, 0 }, // 07 Undefined + { A_BACKSPACE, A_BACKSPACE }, // VK_BACK 08 BACKSPACE key + { A_TAB, A_TAB }, // VK_TAB 09 TAB key + { 0, 0 }, // 0A Reserved + { 0, 0 }, // 0B Reserved + { A_KP_5, 0 }, // VK_CLEAR 0C CLEAR key + { A_ENTER, A_KP_ENTER }, // VK_RETURN 0D ENTER key + { 0, 0 }, // 0E Undefined + { 0, 0 }, // 0F Undefined + { A_SHIFT, A_SHIFT }, // VK_SHIFT 10 SHIFT key + { A_CTRL, A_CTRL }, // VK_CONTROL 11 CTRL key + { A_ALT, A_ALT }, // VK_MENU 12 ALT key + { A_PAUSE, A_PAUSE }, // VK_PAUSE 13 PAUSE key + { A_CAPSLOCK, A_CAPSLOCK }, // VK_CAPITAL 14 CAPS LOCK key + { 0, 0 }, // VK_KANA 15 IME Kana mode + { 0, 0 }, // 16 Undefined + { 0, 0 }, // VK_JUNJA 17 IME Junja mode + { 0, 0 }, // VK_FINAL 18 IME final mode + { 0, 0 }, // VK_KANJI 19 IME Kanji mode + { 0, 0 }, // 1A Undefined + { A_ESCAPE, A_ESCAPE }, // VK_ESCAPE 1B ESC key + { 0, 0 }, // VK_CONVERT 1C IME convert + { 0, 0 }, // VK_NONCONVERT 1D IME nonconvert + { 0, 0 }, // VK_ACCEPT 1E IME accept + { 0, 0 }, // VK_MODECHANGE 1F IME mode change request + { A_SPACE, A_SPACE }, // VK_SPACE 20 SPACEBAR + { A_KP_9, A_PAGE_UP }, // VK_PRIOR 21 PAGE UP key + { A_KP_3, A_PAGE_DOWN }, // VK_NEXT 22 PAGE DOWN key + { A_KP_1, A_END }, // VK_END 23 END key + { A_KP_7, A_HOME }, // VK_HOME 24 HOME key + { A_KP_4, A_CURSOR_LEFT }, // VK_LEFT 25 LEFT ARROW key + { A_KP_8, A_CURSOR_UP }, // VK_UP 26 UP ARROW key + { A_KP_6, A_CURSOR_RIGHT }, // VK_RIGHT 27 RIGHT ARROW key + { A_KP_2, A_CURSOR_DOWN }, // VK_DOWN 28 DOWN ARROW key + { 0, 0 }, // VK_SELECT 29 SELECT key + { 0, 0 }, // VK_PRINT 2A PRINT key + { 0, 0 }, // VK_EXECUTE 2B EXECUTE key + { A_PRINTSCREEN, A_PRINTSCREEN }, // VK_SNAPSHOT 2C PRINT SCREEN key + { A_KP_0, A_INSERT }, // VK_INSERT 2D INS key + { A_KP_PERIOD, A_DELETE }, // VK_DELETE 2E DEL key + { 0, 0 }, // VK_HELP 2F HELP key + { A_0, A_0 }, // 30 0 key + { A_1, A_1 }, // 31 1 key + { A_2, A_2 }, // 32 2 key + { A_3, A_3 }, // 33 3 key + { A_4, A_4 }, // 34 4 key + { A_5, A_5 }, // 35 5 key + { A_6, A_6 }, // 36 6 key + { A_7, A_7 }, // 37 7 key + { A_8, A_8 }, // 38 8 key + { A_9, A_9 }, // 39 9 key + { 0, 0 }, // 3A Undefined + { 0, 0 }, // 3B Undefined + { 0, 0 }, // 3C Undefined + { 0, 0 }, // 3D Undefined + { 0, 0 }, // 3E Undefined + { 0, 0 }, // 3F Undefined + { 0, 0 }, // 40 Undefined + { A_CAP_A, A_CAP_A }, // 41 A key + { A_CAP_B, A_CAP_B }, // 42 B key + { A_CAP_C, A_CAP_C }, // 43 C key + { A_CAP_D, A_CAP_D }, // 44 D key + { A_CAP_E, A_CAP_E }, // 45 E key + { A_CAP_F, A_CAP_F }, // 46 F key + { A_CAP_G, A_CAP_G }, // 47 G key + { A_CAP_H, A_CAP_H }, // 48 H key + { A_CAP_I, A_CAP_I }, // 49 I key + { A_CAP_J, A_CAP_J }, // 4A J key + { A_CAP_K, A_CAP_K }, // 4B K key + { A_CAP_L, A_CAP_L }, // 4C L key + { A_CAP_M, A_CAP_M }, // 4D M key + { A_CAP_N, A_CAP_N }, // 4E N key + { A_CAP_O, A_CAP_O }, // 4F O key + { A_CAP_P, A_CAP_P }, // 50 P key + { A_CAP_Q, A_CAP_Q }, // 51 Q key + { A_CAP_R, A_CAP_R }, // 52 R key + { A_CAP_S, A_CAP_S }, // 53 S key + { A_CAP_T, A_CAP_T }, // 54 T key + { A_CAP_U, A_CAP_U }, // 55 U key + { A_CAP_V, A_CAP_V }, // 56 V key + { A_CAP_W, A_CAP_W }, // 57 W key + { A_CAP_X, A_CAP_X }, // 58 X key + { A_CAP_Y, A_CAP_Y }, // 59 Y key + { A_CAP_Z, A_CAP_Z }, // 5A Z key + { 0, 0 }, // VK_LWIN 5B Left Windows key (Microsoft® Natural® keyboard) + { 0, 0 }, // VK_RWIN 5C Right Windows key (Natural keyboard) + { 0, 0 }, // VK_APPS 5D Applications key (Natural keyboard) + { 0, 0 }, // 5E Reserved + { 0, 0 }, // VK_SLEEP 5F Computer Sleep key + { A_KP_0, A_KP_0 }, // VK_NUMPAD0 60 Numeric keypad 0 key + { A_KP_1, A_KP_1 }, // VK_NUMPAD1 61 Numeric keypad 1 key + { A_KP_2, A_KP_2 }, // VK_NUMPAD2 62 Numeric keypad 2 key + { A_KP_3, A_KP_3 }, // VK_NUMPAD3 63 Numeric keypad 3 key + { A_KP_4, A_KP_4 }, // VK_NUMPAD4 64 Numeric keypad 4 key + { A_KP_5, A_KP_5 }, // VK_NUMPAD5 65 Numeric keypad 5 key + { A_KP_6, A_KP_6 }, // VK_NUMPAD6 66 Numeric keypad 6 key + { A_KP_7, A_KP_7 }, // VK_NUMPAD7 67 Numeric keypad 7 key + { A_KP_8, A_KP_8 }, // VK_NUMPAD8 68 Numeric keypad 8 key + { A_KP_9, A_KP_9 }, // VK_NUMPAD9 69 Numeric keypad 9 key + { A_MULTIPLY, A_MULTIPLY }, // VK_MULTIPLY 6A Multiply key + { A_KP_PLUS, A_KP_PLUS }, // VK_ADD 6B Add key + { 0, 0 }, // VK_SEPARATOR 6C Separator key + { A_KP_MINUS, A_KP_MINUS }, // VK_SUBTRACT 6D Subtract key + { A_KP_PERIOD, A_KP_PERIOD }, // VK_DECIMAL 6E Decimal key + { A_DIVIDE, A_DIVIDE }, // VK_DIVIDE 6F Divide key + { A_F1, A_F1 }, // VK_F1 70 F1 key + { A_F2, A_F2 }, // VK_F2 71 F2 key + { A_F3, A_F3 }, // VK_F3 72 F3 key + { A_F4, A_F4 }, // VK_F4 73 F4 key + { A_F5, A_F5 }, // VK_F5 74 F5 key + { A_F6, A_F6 }, // VK_F6 75 F6 key + { A_F7, A_F7 }, // VK_F7 76 F7 key + { A_F8, A_F8 }, // VK_F8 77 F8 key + { A_F9, A_F9 }, // VK_F9 78 F9 key + { A_F10, A_F10 }, // VK_F10 79 F10 key + { A_F11, A_F11 }, // VK_F11 7A F11 key + { A_F12, A_F12 }, // VK_F12 7B F12 key + { 0, 0 }, // VK_F13 7C F13 key + { 0, 0 }, // VK_F14 7D F14 key + { 0, 0 }, // VK_F15 7E F15 key + { 0, 0 }, // VK_F16 7F F16 key + { 0, 0 }, // VK_F17 80H F17 key + { 0, 0 }, // VK_F18 81H F18 key + { 0, 0 }, // VK_F19 82H F19 key + { 0, 0 }, // VK_F20 83H F20 key + { 0, 0 }, // VK_F21 84H F21 key + { 0, 0 }, // VK_F22 85H F22 key + { 0, 0 }, // VK_F23 86H F23 key + { 0, 0 }, // VK_F24 87H F24 key + { 0, 0 }, // 88 Unassigned + { 0, 0 }, // 89 Unassigned + { 0, 0 }, // 8A Unassigned + { 0, 0 }, // 8B Unassigned + { 0, 0 }, // 8C Unassigned + { 0, 0 }, // 8D Unassigned + { 0, 0 }, // 8E Unassigned + { 0, 0 }, // 8F Unassigned + { A_NUMLOCK, A_NUMLOCK }, // VK_NUMLOCK 90 NUM LOCK key + { A_SCROLLLOCK, A_SCROLLLOCK } // VK_SCROLL 91 +}; + +/* +======= +MapKey + +Map from windows to quake keynums +======= +*/ +static int MapKey (ulong key, word wParam) +{ + ulong result, scan, extended; + + // Check for the console key (hard code to the key you would expect) + scan = ( key >> 16 ) & 0xff; + if(scan == CONSOLE_SCAN_CODE) + { + return(A_CONSOLE); + } + + // Try to convert the virtual key directly + result = 0; + extended = (key >> 24) & 1; + if(wParam > 0 && wParam <= VK_SCROLL) + { + result = virtualKeyConvert[wParam][extended]; + } + // Get the unshifted ascii code (if any) + if(!result) + { + result = MapVirtualKey(wParam, 2) & 0xff; + } + // Output any debug prints +// if(in_debug && in_debug->integer & 1) +// { +// Com_Printf("WM_KEY: %x : %x : %x\n", key, wParam, result); +// } + return(result); +} + + +/* +==================== +MainWndProc + +main window procedure +==================== +*/ + +#define WM_BUTTON4DOWN (WM_MOUSELAST+2) +#define WM_BUTTON4UP (WM_MOUSELAST+3) +#define MK_BUTTON4L 0x0020 +#define MK_BUTTON4R 0x0040 + +LONG WINAPI MainWndProc ( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + byte code; + + if ( uMsg == MSH_MOUSEWHEEL ) + { + if ( ( ( int ) wParam ) > 0 ) + { + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, A_MWHEELUP, qtrue, 0, NULL ); + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, A_MWHEELUP, qfalse, 0, NULL ); + } + else + { + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, A_MWHEELDOWN, qtrue, 0, NULL ); + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, A_MWHEELDOWN, qfalse, 0, NULL ); + } + return DefWindowProc (hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) + { + case WM_MOUSEWHEEL: + // + // + // this chunk of code theoretically only works under NT4 and Win98 + // since this message doesn't exist under Win95 + // + if ( ( short ) HIWORD( wParam ) > 0 ) + { + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, A_MWHEELUP, qtrue, 0, NULL ); + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, A_MWHEELUP, qfalse, 0, NULL ); + } + else + { + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, A_MWHEELDOWN, qtrue, 0, NULL ); + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, A_MWHEELDOWN, qfalse, 0, NULL ); + } + break; + + case WM_CREATE: + + g_wv.hWnd = hWnd; + + vid_xpos = Cvar_Get ("vid_xpos", "3", CVAR_ARCHIVE); + vid_ypos = Cvar_Get ("vid_ypos", "22", CVAR_ARCHIVE); + sr_fullscreen = Cvar_Get ("r_fullscreen", "1", CVAR_ARCHIVE | CVAR_LATCH ); + + MSH_MOUSEWHEEL = RegisterWindowMessage("MSWHEEL_ROLLMSG"); + if ( sr_fullscreen->integer ) + { + WIN_DisableAltTab(); + } + else + { + WIN_EnableAltTab(); + } + + break; +#if 0 + case WM_DISPLAYCHANGE: + Com_DPrintf( "WM_DISPLAYCHANGE\n" ); + // we need to force a vid_restart if the user has changed + // their desktop resolution while the game is running, + // but don't do anything if the message is a result of + // our own calling of ChangeDisplaySettings + if ( com_insideVidInit ) { + break; // we did this on purpose + } + // something else forced a mode change, so restart all our gl stuff + Cbuf_AddText( "vid_restart\n" ); + break; +#endif + case WM_DESTROY: + // let sound and input know about this? + g_wv.hWnd = NULL; + if ( sr_fullscreen->integer ) + { + WIN_EnableAltTab(); + } + break; + + case WM_CLOSE: + Cbuf_ExecuteText( EXEC_APPEND, "quit" ); + break; + + case WM_ACTIVATE: + { + int fActive, fMinimized; + + fActive = LOWORD(wParam); + fMinimized = (BOOL) HIWORD(wParam); + + VID_AppActivate( fActive != WA_INACTIVE, fMinimized); + SNDDMA_Activate( fActive != WA_INACTIVE && !fMinimized ); + } + break; + + case WM_MOVE: + { + int xPos, yPos; + RECT r; + int style; + + if (!sr_fullscreen->integer ) + { + xPos = (short) LOWORD(lParam); // horizontal position + yPos = (short) HIWORD(lParam); // vertical position + + r.left = 0; + r.top = 0; + r.right = 1; + r.bottom = 1; + + style = GetWindowLong( hWnd, GWL_STYLE ); + AdjustWindowRect( &r, style, FALSE ); + + Cvar_SetValue( "vid_xpos", xPos + r.left); + Cvar_SetValue( "vid_ypos", yPos + r.top); + vid_xpos->modified = qfalse; + vid_ypos->modified = qfalse; + if ( g_wv.activeApp ) + { + IN_Activate (qtrue); + } + } + } + break; + +// this is complicated because Win32 seems to pack multiple mouse events into +// one update sometimes, so we always check all states and look for events + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_MOUSEMOVE: + case WM_BUTTON4DOWN: + case WM_BUTTON4UP: + { + int temp; + + temp = 0; + + if (wParam & MK_LBUTTON) + temp |= 1; + + if (wParam & MK_RBUTTON) + temp |= 2; + + if (wParam & MK_MBUTTON) + temp |= 4; + + if (wParam & MK_BUTTON4L) + temp |= 8; + + if (wParam & MK_BUTTON4R) + temp |= 16; + + IN_MouseEvent (temp); + } + break; + + case WM_SYSCOMMAND: + if ( (wParam&0xFFF0) == SC_SCREENSAVE || (wParam&0xFFF0) == SC_MONITORPOWER) + { + return 0; + } + break; + + case WM_SYSKEYDOWN: + if ( wParam == VK_RETURN ) //alt-enter + { + if ( sr_fullscreen && !ge || (ge->GameAllowedToSaveHere() && !(cls.keyCatchers&KEYCATCH_UI)) ) + {//okay, don't switch if the game is running while in a cinematic or in the menu + Cvar_SetValue( "r_fullscreen", !sr_fullscreen->integer ); + Cbuf_AddText( "vid_restart\n" ); + } + return 0; + } + // fall through + case WM_KEYDOWN: + code = MapKey( lParam, wParam ); + if(code) + { + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, code, qtrue, 0, NULL ); + } + break; + + case WM_SYSKEYUP: + case WM_KEYUP: + code = MapKey( lParam, wParam ); + if(code) + { + Sys_QueEvent( g_wv.sysMsgTime, SE_KEY, code, qfalse, 0, NULL ); + } + break; + + case WM_CHAR: + if(((lParam >> 16) & 0xff) != CONSOLE_SCAN_CODE) + { + Sys_QueEvent( g_wv.sysMsgTime, SE_CHAR, wParam, 0, 0, NULL ); + } + // Output any debug prints +// if(in_debug && in_debug->integer & 2) +// { +// Com_Printf("WM_CHAR: %x\n", wParam); +// } + break; + + case WM_POWERBROADCAST: + if (wParam == PBT_APMQUERYSUSPEND) + { +#ifndef FINAL_BUILD + Com_Printf("Cannot go into hibernate / standby mode while game is running!\n"); +#endif + return BROADCAST_QUERY_DENY; + } + break; + } + + return DefWindowProc( hWnd, uMsg, wParam, lParam ); +} + diff --git a/code/win32/winquake.rc b/code/win32/winquake.rc new file mode 100644 index 0000000..c70459b --- /dev/null +++ b/code/win32/winquake.rc @@ -0,0 +1,101 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "starwars.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "AutoVersion.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_MAJOR_RELEASE,VERSION_MINOR_RELEASE,VERSION_EXTERNAL_BUILD,VERSION_INTERNAL_BUILD + PRODUCTVERSION VERSION_MAJOR_RELEASE,VERSION_MINOR_RELEASE,VERSION_EXTERNAL_BUILD,VERSION_INTERNAL_BUILD + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "Raven Software" + VALUE "CompanyName", "Activision Inc" + VALUE "FileDescription", "Jedi Academy SP" + VALUE "FileVersion", VERSION_STRING + VALUE "InternalName", "JASP" + VALUE "LegalCopyright", "Copyright (C) 2003" + VALUE "OriginalFilename", "jasp.exe" + VALUE "ProductName", "Jedi Knight®: Jedi Academy" + VALUE "ProductVersion", VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + diff --git a/code/win32/xbox_texture_man.h b/code/win32/xbox_texture_man.h new file mode 100644 index 0000000..80f6fad --- /dev/null +++ b/code/win32/xbox_texture_man.h @@ -0,0 +1,126 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +#ifndef __XBOX_TEXTURE_MAN_H__ +#define __XBOX_TEXTURE_MAN_H__ + +#include "glw_win_dx8.h" +#include + + +// Texture allocator that never frees anything, just grows until it's totally reset: +class StaticTextureAllocator +{ +public: + StaticTextureAllocator( void ) { } + + void Initialize( unsigned long size ) + { + base = (unsigned char *) D3D_AllocContiguousMemory( size, 0 ); + allocPoint = 0; + poolSize = size; + maxAlloc = 0; + swappedSize = 0; + swappedPoint = 0; + } + + // No bookkeeping necessary, texNum is unused: + void *Allocate( unsigned long size, GLuint texNum ) + { +#ifndef FINAL_BUILD + if( allocPoint + size > poolSize ) + throw "Static texture pool full"; +#endif + + // Current location: + void *retVal = base + allocPoint; + + // Advance, then round up: + allocPoint += size; + allocPoint = (allocPoint + 127) & ~127; + +#ifndef FINAL_BUILD + if( allocPoint > maxAlloc ) + maxAlloc = allocPoint; +#endif + + return retVal; + } + + void Reset( void ) + { + // Just move our allocation marker back to the start: + allocPoint = 0; + } + + // This is used by the bink code to make room for a giant texture + // that doesn't need to live at the same time as any others + void SwapTextureMemory( unsigned long size ) + { + assert( !swappedPoint && !swappedSize && (size < poolSize) ); + + // Save off old texturePoint: + swappedPoint = allocPoint; + swappedSize = size; + + // Reset texture pool to the beginning of the block: + allocPoint = 0; + + // Save whatever's there now: + DWORD dwWritten = 0; + HANDLE h = CreateFile( "Z:\\texswap", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); + assert( h != INVALID_HANDLE_VALUE ); + if( !WriteFile( h, base, size, &dwWritten, NULL ) || (dwWritten != size) ) + assert( 0 ); + + CloseHandle( h ); + } + + void UnswapTextureMemory( void ) + { + assert( swappedSize ); + + // Read back the data we dumped out before: + DWORD dwRead = 0; + HANDLE h = CreateFile( "Z:\\texswap", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ); + assert( h != INVALID_HANDLE_VALUE ); + if( !ReadFile( h, base, swappedSize, &dwRead, NULL ) || (dwRead != swappedSize) ) + assert( 0 ); + + CloseHandle( h ); + + // Reset texture point + allocPoint = swappedPoint; + swappedPoint = 0; + swappedSize = 0; + } + + unsigned long Size( void ) + { + return allocPoint; + } + +private: + unsigned char *base; + unsigned long allocPoint; + unsigned long poolSize; + unsigned long maxAlloc; + + // Extra bookkeeping for Bink texture nastiness: + unsigned long swappedSize; + unsigned long swappedPoint; +}; + +// Global texture allocators: +extern StaticTextureAllocator gTextures; + +#endif diff --git a/code/x_exe/Copy of x_exe.vcproj b/code/x_exe/Copy of x_exe.vcproj new file mode 100644 index 0000000..231d00b --- /dev/null +++ b/code/x_exe/Copy of x_exe.vcproj @@ -0,0 +1,1518 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/x_exe/title.bmp b/code/x_exe/title.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8cabeddc239dde190957dc3a2749c9371aeb13ee GIT binary patch literal 49206 zcmbS!2YeLO-gYPnA$9wd-KpFACR>thdLxyP(0lI?dhb%Dh#*}NyI?_3QB+V-^xAvZ zYp++mcEmp4b7n%stKRp0zx|Et?Ae*!&2!HAm*-!o>YX^1qCOIM_Tax&9shMnp>c-C zDfRh(e(ILmwQFj(ZfV`R^}x}+dwOd3o~fxBsi~Q1X<2D$**$vX^yrbBo}Sw?-O#hA zF{7s`GXsYuE7OvhNo8l!Sy@a@CYzmY&CTI;If5=*%+1A#sLyp8^WtelZP_7 zOlBWt3DKqiO$8Y`!qOp@3ei-UF$XCM2S)O&bb_Ob1SZL|B_dlPv6Z4Q*y~(S)7e9OVldrz{ z=8-R-8FgTmZ)A*Wl(;sxeO6o;6%t1Wg;4>1u%8>?wzatE1{c%ILDxvO8i}p8Qxy_b z!kd!)6xLDu161jdQUyT z&+KW=$h7p#uw-YkS(!8eJj<4o#p$v+eXgj_k#suQkSpo+k|Eb+GP)^KkTwQrbC5Mf ztW*RbTg(xf&bKmoG#j%q364#0blgVeu~a_GCK*y}sBHmW!vX#566|%LL!&Wa_ ztL%=E5#NPX-gOnuDfvQwFNayE6wPH4T`tiTB2_6I{LHDW>XX3Ha%lg6`cjZ~*+&RI>8Z)5*&3*;ALE zZtR&(R(@87SzGxz*(_!uC(D|X&11FaVA<f$OTTcym^yTxX&Q16mP zMf?|4`PNrECg%(NJl1+ALB2$!%6aSvx=N<2Wh_FvPN3^0re30I1WbgbS}^zGs9HRW zrh3ubBwAWUOJ657(8-Pouw#A320>gJ=jY~I$9VbKk;+{IcE54m2fuv&%b6d6|7*%e zGY>BdOe&^Z9sGol>w;o&T-ZJ*Ax#boV?z8;pD@^G>+7}laa(&kY`xullhaz~psO7W zeoUpzRM@TME~ea0VSC1{{|)~hsok*nd!CO!wm?rJc?=nv zwgb+iz~5>utTiI7KzeHT{;HrLoEMXhaKrk9ZD;>Sp{Wa%SY`Z^$On6W;tJxn!;{G5Do zRe^P!kDrxSwRh0&HxGXN%NM_X_Y?4cOZjBxEh_?3l9pz>Fg?$?s!*OBkr&3Lsd<

u`IurlD!Lsb4X{@vB}-=jMbK0VV^ z;iIkp9zAsFIMVe!A@eg$8Cd*T5c^bCHlrwk$$aI5b zX;3Gk&Ted!sNTTeZXV#Ihqm`{oN2g)>aSO?BMHhK<;5B zxNN-~PzTm3nJEVTvbn_e*Z8La|5R1;0)HTnqeqYIv@})icHmEhPeuln*;8Bm*6bWe z9vc=vfn6u*as|D?X*Bw1g9i%N0^wr`S}YNUtm+tR$;U3RGKB(Dz;n>8I4Q=t$i@VK zZ_ZQ25jF^)TN!Oo*_3 z$zM%XOH>7Kt+m^lB(~W_H@lf;CyntYCuV`}Edo7blil1Xn42MdT=XCpJ={%=@o=*v z^q7EUgxk5R)HW+(n;)&aV)*`dZu(sL`q%FX{_iQD&A($^Xi5pyW|!9#2@9jrl7wSv zu{-n>mZ>t4t`_MU`ET)0@19C5 z9~J+eJyhX;4u1pWk81X)!Y7l;%%)YzW7XyI0AG*ApDP-2M4i!XHh9gy| zTQ)b5#cyh}Q~jJ~SUV$J)F?MQJ0Fst>hBPDl?lsYfVbi55tqMnyDFDP;>FSJjKK>l!}AcFkDAg{H*kL&9d`uc=cH{aVSHhP3+ zm$lK!)i{_+r>$1DHM`hm_ut~*qkA_<{`9movi{S4SH8sZ>A*iz)4WVhmMVOR@uMz& zBKZZ_JVuk-V(?Ph;y3tcOOS^AH-$`89z=>@gFRU!vW0?%Jme2ugo86BY{rM|hwVeJ#)1N>=C^Jc^BB*I5fH7~Y+L>9l%WrodZ^g;5|7B6KEn5mG( z5};sP(ox=8AXp*u6TB6=wSWi0SmdUd)eQTyz(yCq5ald!))Hf>gh&B=sEMRN7jrfh zf3DUk)Oo}vx6~Gp2LxS%1K1As$_4C<3~YXBwT>elDiJhaKQy zankBE!QgC>Os!5>J`niKU^g?`Z=D>l%?%413#{V<`~?Ze{&HbyqW4YXuKVz|uaqB@ zpUC?ESowO{$&2!5RoL1iuD#XriiGo$3VCUPFgYSX|BepA1cC`F4Gu_yy~02rkDu2M zn=~M{`fSZkzQHB7xXAeMurx0GH~4q$u>5bIzw+otA*OMq;&GMw+ZDpxurz-r3 z@FAKPW{*zP8DQ`e{LKV^EdBsRT&fUd@l%#C1rrFnfGAnmnQ;NTfGHF#30OopSgHWK zfW=0zBzQ}K%~D`B;V9%Gf~;juw$wqF!osl&)h?l)C}J`N{eAM_kkrR-9~aKQyMO3l zBMd^OubXMGv$e$RXPR8hAh=Y1dZ3#c;4wkww7RG^r=`E!GQeRO2K;^8mvGRay2Mc}m6 zc;E=geY{Rcp@BhRa8PV?IcCJ;4-W_)Y~&{g={6_TXs6+A^^)jTCp*+jkMLNAxXlAy zmVpj(402IJ-0Uz9JI>2Z3kr+k(#E8HOH!B-4&2e~+E&*0=#)dB-E~&^S^4=_;Qw#s z+f@&4kIk!bY%6u|uX61wckZsT!~SWHK<>#CLj0JpI5O-Q8WxB6#erUVU=RluztGzw zHoLijesN?_o|@<1Qsr1!@V~-8E3JEWx@P?JB*uSFxV`B__-ABl!e0|UF#APf_T&E3Jl-^^}yRi{ya{$v}GQ$zeXJPQG6Jf)-8KQ&yTL%yLN-*SB)QH{AXeP z_aOM|({X6{6Xi>1WYVhgwGy+R*!&PaJi*^!H|QNk97Y*dD7Jvf=){g7y4c{MpolHl z0)DU%EEpbhgfT@JV}vqw|VhLaRWU}on4%gAA4z7@Q&X6)Cgq0xzRz@%5YlQkpX^u$Tlv( zj`Bg~;9$r2tmFK)aY24=p0F(8+*0g4*dVPc^xf1LI^H_y_V!!8zV|2P7wiB$zEIAt z|HrPvRrP^8n%vja29GqmE~$_f7szwtU?D_ofysG}@lpHeur$IiLIe$jZQ$i#0zo5; z&$myDx-P2lUtS|mjBs_%KZB2vE?rW(fpb&w$3zhO=Rfc_sLHp)?d`DqiR4#>f39TA zap-h*1CCrtr+4U05J7H}3Fatq1Sl#%!7kPNEyjS&0znj_Od(beH!Wf@hG=7$Hs&)# z5f>24$5bGg3wbl_qGHifE>K_Z0_a1yXh4np{Ib0X5Jgkx)k_l5?~ z?n=)UHHl}24!URN(Xa3SQTbIN?yvH-^8JMm?<%>lG5A26`%r!4juz+kN@-a_oFA9r zh)vITOv;ldNBPkah#>pWpxEDIYjSh_-O`w_eQMaXsUm!Hi*sgN=;wn+@YndKbm_!j zTmK#S=fe0@nFsTE_*+4{5#tBKzY~AGhQDNhBc_)P2AA37HW^g>p^GsE;6K5F8hjRW zfHj0*m(uzmZ3t1uFg5~h%%hA^mP`SdiUON4ZZj2uKeWPOw3LesSS*!ipr8xNB@5=F z!Vb2Fsu69CcCN*3?c=ichotqTL&a$&B)fvm)ZGu-G03 z;lND`3+-XsjIb~#A1;-1TZQjnec;w+*Y3*XTVsZuntSUv5BvoDiTMNk&u)5TZ|UCt zktg~Gj`a@T+t<0hLRwKMFHXpFV~!ay$JB^CF;5HA$IVV?@KLmdn%%>p- zi{EL0{MV!q@He;(8vcMCS^%?P@LF^bM}ErSXW%BALIi(Pm@?)urhM9z&%z5(X94yn znF~v?)m&sHTcA<~qiC&{*;>095~&QdCpb4U7li5r|IiEz(`6grr3d*e^`bOCzf_qO zdt?wdC1f4sHurY2{axaeh`1;w&X3sU=JW8m=Hv?t;ux{7EpqLs^j%*cd7!^@PgT+T z<3~NS?8tZb6WK$=zj8+T8SB6L;PBv6gMtsXgip3Pca%zNieZ8}7R2S*Q4kT3@Bo|} z86tEv#QwODJUiyuR35#5kZWVP*w4#DBY-Atbp7S}@4z3-C!DWL&H3#?m`@0QV*LM+ zc_5sdE=TpeG`9}!)vpNM9Lx9l*DP0I6 z$fA$nFzfSZ9L9Xs5M}f+))=*d$v2mXmQo(vGF$It8|+lQ2tL_T&Qlez^JNxBPOWTd zuv?lO);2dY5QhgZ?aSj8%DmXi!}wYG+z21+QhJC-m>(6E$Au*whrBEytxVWAl)86U z`ERI?J~7C1eQn_zV~4-D?3D66fu5K@nraW8F_wi4f2Zv{L&z=2p_rO@9<9n@`QO# zCovb*`33Vv$hULGe+FpJ4%$sMesWaL3;63GbHMpK^)Nm)`^OB0>#^Wj?>4E1D2yM% z2$_8r9S%6iFiitAk^dn|AH<+J$7j?9jfM!V3)4Aavpzx@V~inATmTZtC>2eF;g{%M zGE*;7%`ycJzlIRYY?VOw0@W;o*koH|{P|3O7cX4pE^4YwC! zUs_!tuTIKq62dBgUns9Fk~b8%_f`dOZq9pVi2qa{-{Y<8l$&39^x1oFI|k&x`RdE@ z{rf-m%wvN;T;YFhVC=n7q2n!{JyrIL%A~bPd1*pkSYU6@lcvE6%CimjiX(#J?1byW zlEibvLbo*W!vp*Pk1)h94S`Lqy0OaFA{UnjDL-GgYwPc z3GD#<^$xQ5f5+c#hRtd6S>TA7A!mG)$qN&RGDF(H7rH>z`Fr|lSilYcyz-F!xEER~eKphKIFOhDxQ@t?@4hteIbs~iWJZGaF zoDb92$@KHk1Kb#qRunWS>kIxhj+q~{4)ZcY-Qv8M{i0%dbBVmEMBG>e&r9A~>;(Qt z8X`{(jJz=<@a&+6zI}21*x5&p+@P*~rKPEIcCW!#D)+lT9~S?7g7=PQ&*jz59p(0o zCDQ65c|pRCSj^NseoR0d6_OStoEMcAyf{4nRDXV4*ao+Eh#!YABq$E_!6W+{`IFMM zTi4W#G|l<#ARkENi-cX^{AOy=M&f+sb|_!s`I49yVlGB|huFs~80;qaUnY>xu=xXU zgkkwmCLcqHh)AF4JNb2K_S%AO9sPFHQ8ZsG9VlVdg#G^suyp+q-?Zu zpy1>2#)UrH2;vJk;BD=zaO^H~>?o0Ume}`|J1?v7-P)M<)S%cm6G}gydg}wnCb#sz z?Y5gV{1?q{pV2Vz@E4~%%D93rCisu{_FdD;J=EcOxdBO<4JU{N( zTpWLOWWk$b#Hsnha6i}~ZkQkJ1V0c{-~%Q5xA>?1j(=KK4?_MB{5!%f>BRX3^OKcj z{UiS1oU!=rdQJH2fWK2^Ayod^2$=)3Ct!w!X~|;{cEn)`VezxzrD;g}AO-yO0ZWe0 zkmWUIt4EecpBumwm~~-`0n}Up!n-_FfU(4CD6yI=II4y>*9s<3`hDzde>XeWNB4Kr z&62qnczzKi8{5y$^!L(3y|$5Fs*faWO|DdKt3EqNx7sU=4sf%=?n^6t*Y$!k<-DTW zb5)h|+G=9hJl#M1^SEMV{?*5>8(dX=?N$4L|F1v&v~a=nO_Sza`Ni>4Wl6yo<9sKY z-8b}d9jLNzFR@>k6jv09^Ao~2zc?@M-ceET%BbQqQ|(I%_z?kVT%K)oz&gyw4fd0b zFeqRf?E72%Q~whGj(|Iuw@iY6rndfx@uRJOcwUIGtDYB-H-Z0mQASJw5&jn7ZvqR1 zl^?c(f`jKpi^e%yjE>JVxN38#e1gW_`>GEnqI- z%*Cv}*k&x|EtQ-Z2V&?@yG=6F$H9R49O>gm`6&a{;5B!BO0dAaMKS1nV4n;Seg)%lJzJMZjO|LXKPzwTJA9NT=)-j;ZB z_s%WA|Lobbixp;Y z%o`E@c7RVAVCp!C*fF{-MDn;Gc1)`LH&YRske_)RRz4dgAxI|9gX*y$;2uU?o4>j11C_lGDX(9=Z);UNfBV+;!2i4NzFR!Mea*c2r(b^LCgqu+ zGTZV_BY#h|>tLPxvP#FrCGeWXIZ^wrvdHVhT9l2x>l%e}{jLNxw{mcN5b-33C zVtHCvoE^)U!WW$w@`0kXC`(g!O7gsd$jQF37l-lB)JdNV?)Uzx^(S^+we_+a*X&u+ zKBK<2_~I?=fd85AzFjzP`pRW@d01L+oHjl+mQvsUECo&)LM;t^{0nZ@KkJ=X(rp*@v z_q77mNy@I2eFA@Bocg@afS)hA>ENMaqPuk7XCRpD4(EYFHTNB=$k-TsKbn)aaLpKk8?NBnbh;D15>gY&nW zOfJgou}}d9l!7_H-~_V(!9pXYBxXZ~0~SA@5IF>25OW9q5vw^*fGtE6ae&J85^1C9 z-yAX#dbk4^(M8N~#EdaIH_GHf3jqH(l~aT?Dw`n*`C~O!+KknLp-!+g3Fh8*8c`L* z(?|P3@4(>aSH$EENqI@sIw@p@**_;LEiZDdFH2u57d<};z>ZY5_I$i#`HAgM?LB_r z#w%J+&&}EvO1s!mtjs$8otsPTD~s$43hgTk{a5x1A8B^)sdTJNim*{9 zM-Zw1FZg%Q&@vxL!drSyIx$UiBx@+Pn#x&YC3XRCY7mVrB&uj0;Gl=Q2)7AhZFZip zB5vPa=G;{dhK&c$ycj%liEl?u#&(DQjX}Wnx+B-!xMchAlS`;KTFf^TN!v=Li<5Of zEjs?fy~Ar8_UzuO;om-e?4~JmA5>oSe=*W|pxivglhVhSF*UP)7%DJ)FzO2A^X-)pa1H*Up@$4kvJLe{(sR)*Z*a3eXf5fC9|1d2htt01< zBpjxv=`%7q@dxv%8UJifZU_FD1p|aX1+n8(rA^oZmkQ}^kS$i^_C&GxZMGOMB<;YT zE0Tml;LkE3cEX%FXf*^uL0F7_O7Ej`RM_Ey5l1Xw&heY|VT%cvtN0rVSaSiZPcS(N zg1;fj>XKGNsj7~NAp~1gFhdIj_LhhNCeV6U}?XC}}E0?D9Nr~RsR{Zhg$bJ3nSCmzMHtYC{_m6GrbKtTaD*oTk znm&H(j3pvmE4-b5S8UI*`x8*a+S4Z~d=s1b^gwsyS~e{)jvx=a7VbvHnTUk7|6f zIXMJ>E!9eAvRf!010$aaFmxEQoko%yq`fU6SmQW2TMUw4;)^6MAzI@$Hs8j^F)7G1 zVJu-Az&8YqAc%B+v+4p{aH5OA`LY-y6i7Kkl+u9+inE3|2S!4bKdcTSsF*R7S@k70 zBo^xHcr(m?q&M_+Ajk!hpB?M9PD3V7R9G69Hx@egRfO*B9eJpK=ui{CDQbJ5@yZV~xK<_}r_9r(leRPiSXS1>+-KT zFW%B9n_C^00WNx&hk*#18bBtYuq1|fuDq?pdv#6dL|gQwVcfg@wjJ4h`oK|cLs3dw zO4$>m0dlVwrlsr@2P#{lKTS^l*na%Udu9w8e&rQ=we`PX?)1HLFSu8E*8Als=Vj&M z)M!edE|Zi!?<-2iR(DFj%&IE~U(&06-K$pzl$k~UBH(*3t(CVx5hTG5$unbiWXg>X z^P>U?%>AYO@7lF%cceQ18GmEX%yam67@usmO8WzReJ*c6N;4Cr=`h8Ftqj~N=-eoa zm2WEm_ym7!0RnhFB8)ut1=c?qED-2~07J+Ol84y;RBq5jwm=YK$7GB!x;)l^h(JC= zD9|Xhuq`)6>l0ReA*(OpjL2uMgd@gVYB*#s>#A*r2Eo*1H@3=V1b+uR!S=J`{X_&I zR|*u(MMd7rs{@A`tWVaBdS}b4`ycFe-_(?fo{r&pDT_@vyhM1mrhm@WJydeFa(akz z@sTHwwGSG3a1cSGh+>Ywq4 z|7Fb5)_({7TG)jr_~*!a+D}`t=7E1cYmIUcKC0y5xj1jb0h=cd&Vgm}vHp?UOf)hi z5{tQri-`Op7uNtUAc%+zLt+7tIav855X0yJKEWR+tRZ2A3kLb4OTY`W8B2LnIcKRN zM+g3Oz@Mblng%!-sDNQ^daRe78bV@~v^?(IRP5PXW52e{{p6q%2ac|~Z+q@t$>`|; z&I^);Y0lM0F1%&)Wx%uYv)MDh-uA95Mw7 za#)EKYNHbnJ_1q7aVv}S1djhm05`wTn-bIGu86?US$0vy#(?HHa|1Q&rb03(}NuFUz(6s7rM4p=&p%Q zI-?s}7)ZKdOwv}V2?cGS&hJMQ0*A?Z}=U*N@xV1z6{P@Gd zc~kc=@o4cUVOvI$CqaB z2>LFrbgoIt%ZnTf2>xK_?342m3;xUeD+J#O^Zb9rALOTAGMSx5i-)2EtPN%-p?6p{ ztC{3B6SKca5MX&0$<`tnCXkir0-KtMC~;&^vL=vfNEiv^9I6p{1R_Z+7;YC1GgnPu#V3J>o6*Dj&W4#B)RY zwOnyI*%UvVIXiFmluH+0c!Tnw^M^5?eErpq#jEB{8~@VLho4uz8m4R$k5&l_qDXP+ zGQ@J=z>cf#x?Xp6!m&B&TwCH;QDR?MWSu2AGl972g*EV1*&vQ15U!(PB1u@%ZfQbzu# z6`DFOSg^t)j2`PhN}B<=ioXfMpM+pYEZ9Fj<$;4MqJmNXpWZP8W-caJapaf~sjyZ8hd)z%O zYM&ezhWsc0(4`w9@7?}C@wdbOG8!EqAIxSC99xD3@-d{86Sj?u@wQ?~Ds##eF1gAj zl{>jY$%=fQ1n}qhq|C!F!=#WbT%KeBLF_~LBLy@Lw#`N)Pd>8XfH`L=63nHXDQPnn zvg-O*cR_{CP|BjD1+f4lsw%)gWBvDcfc9iZ2UyTP?O}0lR9aTxTwmIKvwO>P2mbNM z{V7!$q=sm0o^NKMcS#WnYCP9g+0G8QLwWhz)9GiMge zX}@sIil?4?dfW0<>lV#>_raH+R=x--6AjnJ;c5xfB38sV+Z-8jlc9}!=Ke>9o|u=r zDFFOktBT!=3*EC5#QvX}@0^^Fh6Mj({gcd}|6lwKAfG|_6a3)?(7>P6bAjZC{{_M= zE{J6gxtt&`RpM|H{E-5!krG8RW(W9p<&Hn+9P`p z?@tMLmD+sr$cSfR%rmplvoz`7Qt3QcWjWJ!r}D~Y_rAVy`HJ-$RswnL@%^{oE?qQd z{hAdURxaDN`hpK0ef<^XJBKogxh?5lQ!LKNw+#y*=wqoAbK_Ksr^oSIZd&_~-90w? zylczdt4mx_89M}TnD0gs45D^m5#qa{+0yF|iM@L39*#gKp6p(^WX|sTgTqJ@P za^yfdT#*RmvHr<{Kv+4zM>vwRR0vSM=0?ecl9m<;VJ~VR33Bn{k=PlQXXT3v6G#-X zT@Vwmt$5>=m)p0`O>uWc?i@1MNkYhoh+|sJy*TN=xXN>+m;P+)G3DiN9(ixWs^#m} zEIV`NJ4^wd*REQ!Zq4%j+qRy0<!GA$aiKUS#Po3^3~egI@<447mm+jI zaK+x+Up|qwJ>Xqm>RnytT#|InDM0d%b6Voh>mOy0q~I~Fo0|FmSN0D{emG};cKBaf z+K-;(e9C6C(`-g;*F&>GRL`N3iv{_h;&0;%1jzhO{Bc6S$NDco3N)%CXxt>EG&-X=q8b%E)w}u!kbEn3Ly9+1*KAe3udn1QBq_^g^|ktumh0Q z>E%GdjR|p(|1;zth~&>)yH&EL_lC=h~J9L z>fTOTP4}fy{ZTBZ2q_otS^w0V&l-2f+#8Br>&o3riahfXATDrCi#eyngu%b--!3U# zx}xl*qwMrt=KuNpgFXZNL#9E+-<*|2Y5p%EpL4-KsN_GA49&>!^wM;YVnd)H7!7|= z{jfaw5=kz1+bhT+SGxFOyRDEUpR)z9c_e6ONPZ-uV+0^G1puE%+BqsRNb)U|LZJ=& zLQTXKm=bIOD?|`7QcTbV{&b5>lFSEqS)_uG4RYg<+ZF=;(!w}ccYa+g ze7NPY6OWDFJTb-JQ)=*XO&+0-TN)UUhxx_P`ADO+FGzUSCi#PkW(21i)qA98yT z{s)&4a8kwXff`C7ae=-GF z|5TL?5s??f9ji+DRk7HS)`#zSV9e&pDZZXk zeL(E(LDn1+0>xo|WY0R<;U*M0FD~^SZ>(1qzVP8|S6;N|(824-`v38V%XaTPaQU9= zF4^)s8@_gvXE*1v{-YA60_x####1i#E#*}$L9$z}-u zM#yJQZ*rOm`HUnVCqiJP;DQb?OqfBP7xXUU+Uk>#mOnHs zj}O~t#GI>=APu~C_ij{{{PUAH5AD9<$St=3|Fhqnx%P@HZ@B)NBRAi8Z2xtyoO)q_ za-;w3n7reyt}SKq{6drf*oFs?eTwXtltlVfH}1dVl~blG6aGso{adR2>nlA=OS}sT z9kUDmulRTJ|0ui8BK$w_&(-kHv6AEy;ExPj6H-031G>ONYVJDhpBQID86A!S_+rE; zg_A{r9g0~|{0P9gq+Kd^iDgcqSSES_r55lAN+lNf!_`W_+d>r*(xIV(1v8Rz!TA@n z1{8~xl3WyHITBHLlo~N8DQ&ca|0J0weO=7p05>`azE^IKIgp>Zyx6{?fV;Hl@I$w) z-M2m^FP%f~H0tA8d?FIXhX&-CA3Muv~~@$9aa7RONq!wm@{C8>LZ>C}niJD$BJ z=kol(&Kk8;xx%}u+_SjI1rgMa9F@OX|J}8o10CzXhbn*4wX$DL{;2C;i~j5N_fr4AWUa%tV1Z9sX{zcgZiMf;b7bpnT7Lwrwh!rlr z%*mHJtR;2^y8ty8MMMN){R4kkK@dS8|B)Jv5vrJ2o^ZjC|7nKRQEf$aEsCruyXD3kQpQWMJpA0|mk#u} z-0xjqf@qq1OF6O~9HE4&w!`<5g z=~cuE#ev2SA;yNx2b4et8KnIfgS06OH;v&?g<5PEi=9HL1Ng&7MztyFmH-D0TL8kp zh&MtJ!~a6CnD9SV zj)iU;FoF0ISuBS9u|xkt3t;_21fk|#QvsGFwt%2=p{zkmjYbp(rD4zrrM$UPgb7Mk zKH^{XlDS!C$>R6YL%fK;2ov(8=`d*uT&t444dwoA)hHd3@2ER@`-x3AZ%+xNN(~+! zHF1M|((tf-Qp7#4(6PG2v#Zj7SMQ25v)}&u!$>F#3xwe|i>YyI15;9vVz zmAV%dfrW6+Nc8?0pnPG7l7 z3}8nQ|4I-F)QAvR0oS2_)erz#|D>OQgK4&d0bmFE7*wE)3p=LdBkJLV@wqJNSySTK zSmD38B6nZ@;*%SX9XM&Z!2M3NBh6GjHWOagz`niAKJ3ioaGRo~Bf|GST$I{Db+ zPdrZWKl{U-cir{k$tRvs-YolhPR^l-%InHE7;0iJU-t;lJ+t z=ftO0>n;lg_tyvZ)&{m$`?r*PR+YFH7CGl25D>GE&iilcAI(oR`J?3@s>P3}zaaTn z9r$;KAanF~3({^$wjseE@`oq_ix+YR$kPmIDFZ%-Rz(m3G-M-~eP)XfwRp(+C#fm$ zwycQJ6st}d(Z6tqC3pdJ68e`zj+P;xQS0((V~m6VOfe2UWb{eUo``uN5f@o-B9T1E z#ci>(sIDFAw~YwO<3rNq2q;H+MpT-ga4ahZw!Y2f?3(=O14B+;bJqnoUYJstin36$ z{zsxXE9#sb^;}Tu*;?VbwXyo=dGCJx;nVj$@zm2#sq6oT+m7A-;t9NXt>mZn_+10D z7u)%Dgc(q;;!YTt~JG;E#mrz>bCXkl9%ongo>@OePYBlv%s~i$b%}aW4-Hs zng7p!zx%}94?q3PGaCN4-hSH)_dNNW@>bE=sd=}xM)x-5j1Z7eeXsJb=9gy}`g18o zsU!DK{q(+fY9F0sx->tquO8V}?hDHx{lI^^=YxVPbj*s{+heHF_#OW)UC{Q2*#EG9 zi1Dw60R94hHUCgu{~ak1Z~;*EXgBFd+A{&)f|c)~knKsK+7!7ESl);QKnny&@iiSL z&nQ6x{+I<5DuM}32c`m?GnoQ#L8R0gWp{`Lz~({DGjcxTq&gK`1VR7^!RI1UhAjXJ zx)i~8q@&vi`0#w2?ba4I*Vhf#Qa~#h82`fq$a+PkGp2ylyv)i+=9P0zse4NWEOx`; z!kbRtddC$<%`-hc;(Gf?*#G%R;CC)fdM>UA-rd~r+me?*c;oJacRv2aqrm^?pMJvn zfAqEoUs2vo{xBnSw6}kIZD4tMcCCe)?Axt8L_JiSx3l)WL(gn};y~IykAG*4XIrIv zQ@Lwh35owMD6r3p%1}m=Ntqa$1$E+&la97mgaSGj0{9dDBn3s4KUrD|3Y(1*)Eo)Q z7vP&Ab}UFfM8YSg0Ls^cQagYT3x_CQbrvuYti_MTk92g>;R>0z(9W0wDuVbUdJdrT z20f-x* zI28C`3k>tyhQlTaNhnB}l!x>`lA^z&#JjF6aB-FGqQc;#1Ma;2&a1Au!Zg#JR;qK2 zjlz1gFD>+Juk;;nZ1`pIzrXqF)XjI_cj|r(|D(6v`o;q#~ldYFcIViiv7LcHMO==1v=`f}S zVn`McF+o9x!WV|+8L=Tiui|eZ74c{_hWfh@o>?q@tteYGfO+QV8s1uO#}u%Qb~N0u^#%Uu!{7rmMygKGA8dRW#i3Gr z1SMLIr9}vMdAC#qcGZ~oM5E7)IDPbiTdp}suZ$oNZ=aCoT2buYULH8s-1PIJ>y-Q6 zd;6VxkDmbk-+%Y*$y@Jv_q+Gjd~I4BYi2=>7jXrFJ1AEZ@>Az zkFtR}R2be{i<&R*>Qa1~ePN+*Dxyuw#0U;?d_F($59_~6cZeXh^G^rv2J8u8TPdSyxYNFT=q$&8Vsv)rM9U(|`{>euEVUM1< zr2Znqh~^tv=SpC*vw1xlj$!@hh&j0q9rFK?e2WSrLWDx*6H~`ZazTUOf>?$~d&Ue) zhHL?~5FUrjG(LTno-P%!_f z2xe;RbOm~>$<`(Zj~f3z9xLq00X}}HR~#DPhX?sF5n)nDhWwcxB@w_y3C9IxXxZey zqzWl;!JRewEfM;TvP&O3^w}M69)9F#!3yI?*{ForZG-%TSIeEVPCn5+eT#v5Z(+KjQ;ItH=9$7t7b9y8Rp6lhf-_k>j)Eq816yDB?hAd9j&X;Eq2C4Gw%z9;U;;#Pr z2bzN0YdmX`p5?{v1xd#&gkWO!>G^bT$A92|{!192sDeyG{b@$`^T#{SyLInQ+Pk^T z8FUUR#X86THz)AsU}vKKh03ROPSgM9mOozugE@=O1=G$oIf?M;C5f$GBrE>;%KsSY z(!J|{jA$=I*VV~yx23dp{GQuyy7R{uzk2`8ciw*MEvxk}dgW0m`j?ciQu_bxH~O!v z|I07Z28A)d^KXB?tZQl)k^-I813~#ezpIU)G_`BjzkT=b?$X9}l95LKZGwZ{``@qp zAK%UOTF`t3Ehy22=6}5Szg*CzTQ~Q%^7Aj4^~1jJm7n%*-+TVxAI`^6e|Y&fFR*pA zceE4j8P1wExapQj>+T%kA7-eZ=^H@cNvW zm8;)SuD$n*%?F;I?5#JS|LqxUj&pce?AM{urf(X)^45XwW>aXGamdc1MYlJvyr=)R zQ=^amYfibvQtrzf~tI@Jb3ku z8!kDp_?n}eNV7Dgv-T^)W$CFpoGZ%9o%cd;spUu=Jz(EgZpZiIiYaqOcV4q)|9m7p zDGKrZFF&+C67dCnzKWXIf|cXHR360Os;h@|UW}Ga_7UL>Tedc`Zr4;8jEVxk^~a}Q zKfLGal?QIw^xTKH;jYeG28?dP%lT`jbdG2z^RDUrv2t2F|3*>nJ9&?Gt|;%24t&bH zSdrR@b7v(px+9I>>2o4PKw4BPZ))#$K6id8PTqa@UG1gx$NZmL9|2;IX}7=@Mcdod zOgO>>|DHT%@CE!W(-;16<)@{*rCg)V%TG_gb3^CXb)F=v^A+V&?d9-EjQ~?S&!DnN z+F_K-6%5*lqCB7||9JGn>$I2YR0oXepYuX-ZA=^g<8wFsA&T-z=b*LTArJGR*Cj^M z45^rIMR~4qU`^)*I4Q4*3ijWHIf0@J*^kUPf4Y#PT?xp^xcXT0;$`o?|F(Ai6%yi< zw}wydg_l%=0|j@b%8Y{N2P4H=yFekG3SMYmLRN(?PWzXa;)MK0^`V`2K8r(t=YKHR zIj)_f1sNj3n(*Io|1?ZZCz9F)cs@gZ=OdWX-ovZ4!7gdtP!WSfP?N*h)0~OH{YRH~ zUXNeVaaotH*`zhGsjI47nrC2|@XJ4h*uw?s<_ypa}a&84XV55JMA1P1k%s|Q!U_Sm+UU;y>F(blO=@yZ=mJ7^dOQ&lsR&Ndafv64x2!B zbUNPw{=!?ZPF_=#Q`g_U3FB$DtQ&5=5hr*k$`g3YAD3}n_v{4E4YiTS`uUDFd9NTd zf(Mq6qC7f&c0ZgW+bX!eQXT&5C&$>|`A_W%Mfqjwya9i{T2UUt_g;0|R_!fKfFFK) zo%%bU+jnT4HV$gSgaRUm9wpdt1i&g_yjx1D-jJ(ZaL40&Ui#M&?fm@bkCZzU<;9PG zIjIfmP;=*3jgWNwMX##+oxB746UM(0Z7Pcesm_T^X$&$Vg=xLwt~J`d9C{9vk-oBB zvv4n9{5Z!%fJH+p=d@UwzK3>M8VdOZ@~b{q9ys~f<=UY3j9L3xdH9WAh{JXMxWR!9 z1=;edHdyuYByMHYd%QV#do!9CoV)i$tp8;j+woEu8+J^Mc^4%8^NWLXiaaw4!x!~R z+&eISqF-oRwR>6t+vGU^^KstLT>l?mia|?BItDw(;Uj2wVdNL06gqw+vewbw9PSwD z1p$v()*IcV5sG#(I=FV;rJel#YynW@QCnAzK_noNUI zv7{t6cW7JfcI_G?0-E(?eP-D->7?{XC$_PD^zX{9hoL9JRaqgT!B=gai`%t(@jPUG zW#^|YSvu#5XYTEMtM}-#&bI=+V%2X?W@m<6msR?XHKOcSyG*-a@db165(V!vX@r$0 z56`m?2}|e#*T*Z5jrcZK`ghbI7>5SMogdzLf=~F8sF*X_M}7F~%b)!8S?BlSGSCrt z&MWh3gZuXG(eBN)>PQ!KK9_jJ94W`s+u;uDN%1?lxG;_O>9|t8>*sG3=%8P4{+064 z7s{vSe|}F(cD763f&V$-8IMJAZ8a{gPZE?K61Al|B+ z)?D`PL}rRlURM-;sBd<}*m*s!S+Zm?UV;O+j}2qr$uP_i-02?>`gq`~$Yb*Y8!LQU zt6dZ0+Fj>A@7%dV`_lYCLf6C8iZX8XF=Etk?J|%*IGM9-k~Z#hkwpmB0$C8~g_y&k z|9ox+mz&99B672Z>})Y7M>e4s1cJi2a`v3*xY3+Vd*x7iSABjwrhU%gn{U!SU*|Fd z=MMb4bpVp1BkDNUsiO+4h()4bE?pubbp)O`#{YfhLwv|5A3}YkbjwU*ko$y*&~@N1 z@ov1-9<0~s^AOg{IjewbQMZ3r@)g>MrefptO4{Gn;_1GuTSI23esa*h$8YM6|-+0+VZM=I=qA^pm(~$@St0v2qP4H(? zotl+}Uff)EmXMu;Ufhxq+=BKY=YM`TVp)-g_EwrcJ=dYnmW;Vxbw&MA{$x9JsD+`> zjyNeH^3K<|Q7wWSKllw=4>73kao#PXJ360}Ps`}|kyn=4bwrb?lH3{;6Ri)ePl z%DHpyPv^ITV8i)`Uw0UR?QwUZBig{7ffav4!}3m)E*e0vPr9<*;rcF!gI8jme^ zOvVM8=+H*c7+*%WnSLG)?HT*|Nh91oXjtkK8azn4MlPIo*?&CaBSQHdHtl=L^)Pxj z?4F@rgFw8t{^xEUu3Dt8r)3fZQghPqk6fS&J!wRbDF7*|9=W0pCz*U^uEUh$H1sfJ z;NmAgxc0_-I#l^xA77#$-O-EBn z8&xc@Wg@vICFMKi!86L~pOmL?4vB=0WH{-Pi+f-HNI7~|dHf6I(Vvv(z9goa5-B1Z z*4pB5&P?D04-De+91l+Z34N^%I@Tn6Fl2K*^zwmu%SYklS7L;`J$7b4oNxQ=P~Pjq zg(dkYG}f+9G2-^ORTD1dhT~gyTuDSwCX?ly97SdwX$0MdEIYr{>X({5T(g&NbW4A- zKedke;*Yv)4SY!l ziblIP)0(ZbqY)*ImpwDm)IXWLqbN_qsEC!3NL-h0U9%mAEFm`=_nbcG#}uI`yH`>F|H_ z-jS!ry7nZh8P?96y=_NdUW4!YP`OpREdSAg((=v;`W1tp5fAZWtDP7IS1KpN&ka0Z z^1P^AjivIwa;J8C=O2iFfNCFIv(3i|kkW}ndF7n*!Rql@eH7y9@7+efdg0XRP^ zk86YI$c^|5_~&!iKySVpO%mwv$aNbtZ8^XhS|FPvO;PZyy`?=PMvPW_sI0_G?dKUn zm*?*kD)+wpE#&_@c&Sgxy;*r8^7@dRV)aMDp{O&dzu|Lowffk7 z<T z=#&=ig0$S8XaR>5AkCP3_Tz*2(i$ z6jJ@Bz9VaXR36-Y_YANWOE!1>nL4)_z0?k9*(HnTV(693Rb;JJv(@tTdK{CVe>pBISn7 zti@OSi})ZfYafCx0lw>M^NzK8r*#_kKfbFR)h;M>#cowz2t3-VU5w|m3Y>*E_uRA+ z=h(&Cc;^;C@Xk9fN?H&jFx1<}BiFqS|9*;xtsv{`kx3ffj z7N^gW5XaGDH<8PT{so4wrF12KRQK#2-7<;9=zKi!>m@%E$NXQ3(tK^u-rtXZX$a?C zvvDuHA>dDZkgn<7wF?m4#z}6BvW*I$i4e{Mg$`BAyvGVeHRtAAq-AmMwRNuTW&PeJ zmOLJJzi>Hds1H=}vTD)FtIx>#CFBpy?^eGj{mRTKNDw=MYiD{^hMN0RWV4^Hy*+iH0_C^ zrI@s~pzYra>qmF2bNro`A76?KlBFG+0_WX=_!c4|8wL@YQCC2VGvbBT}fj-e6sBF4WK!Q&r~Xz(y-T=4e~UrfgBo$o{PqYDAS-$1&MA@YTepxV2tid1fT zWvN)6lR}Ksl=M(a?HuQ$1fOru|B%j4!%3G^;-B&^vK^pZ@oyyIACAdFDvP9gzUrm@ zR^+LM<)=FikM9{uvDNg*8){i`)4)%an^pJidyqe7hDa(razhq#k}wb2qxJvwb{=3- z6kFd1gpHGPp4pk**&JBH0!t7iE1-gcVvg6W7f~%G^EieOF{P!z<7iXupssA5hC z2&f>LU6$RYzu&2v9aiM}yzldU&(}}W)O6SM^q#Idb?VeP|NpHOyn5t6d-KE|7X&YO zsCMGq^YCK*>)y+#kQFkAPc8DDm}-GMNlmTvAX7(A;GD8Nz*464j!o#&=aaLSD4owG z-sbI(UaoJhk6B$g`MdInrmUvu#nr)cDoh2U@!0Nu?QHIR>P>z1N0t{So`?b?x7+;qm= zwU7SPIQ#1zuWo3ZGxy8KAD?>T;NyCuYSXte7dS9G9py40GSxkO{IQR{e&eIBPPprl zORvBE^!0VKR{SvIUz5j8d+YW$=RHWB3`cfgOkwz}lm}z0u}t48JhqsW?I^gQcXy9o z-?`iJ(^ne5o!Dm>R|gzdd&4~!>S`QEUEY87zUQy|_ZipTK6dT48M!v;vrx$Z?wu8s z5g+9_Yt8*v-Zu8}*Z=wOJ6C2YL$Th6fQNuR`zjW?B#SKe^uug06{^SVInqB%~>2fo-w1V!3vJ*?UhKW5mn zKu7U_QBdh|5`^ei6sZKkIkw!WB|f4IpB0=1 z%-rFR@O6u&g>YB_3Oqp`mf)YPOyYk`3jZ1WbIcpEP(gunst^>n87Du0!)^(Fb6S0SrkU3+>8q#Nzc z9RzyRlG#4{C^c}mC+rQ#;`V?+Su;AwV8o^N?OOb?`fi-bK$|6Sn$~YI@!@*#vMoQ09<&H%PV6y)&$QiO-$_7 zZSkqk8}kk}G|ip;7Gkg4w|CAP%z}*@jc4;8?-RMMI&@7(=b(t|=&-Y2RFaNQD)OHg zhf_}&H~L3~{qPvD@ktMf`e^V_!VO4<1{6dFCZhxLiS9e%bGOgTkoa2(J|PQOx`0Z7 zOr-z_?W5C_?SOxTS0E~gP=9N}?Z|Tyf(!GGIpQ#dVS%&>7iY+9!6Otv_#ibAtYu|c zlONqLoq9`kw{ptd93D3c<=qPI|M0@Ww@_P%+iKD6|_H@9AQ^Ndqk zKk7}-oN?*X!z<4T4j5l`-sGd_?7i&^W9n+-v3d3PPW$S*qJFJLf%feJLeP-!Zy}qw z=#G)&pC42`CRBS~_?l;iT{HPCi&ch8OBWyE)80`9(W^Rw3k{4w!2>;O7L0kGh0%uQ z`R_4RG4|}?i(;#!s`y5kVAajyuPW z@Dpz0N$3T*ZlHi;T$0YNv>J$OS;p}3FjcOK* zp33C(F^@UmsTKu*NC{gHVoY!NXXzGct_;(Mo5A2!u8oN zMue)TSIpNt?(dx!IJyAllwhxzzb7mz$o~lT50<2in=SdLt>vGM!0E-HQ%24$hkQ0U<(TdZPk9dc;{uz{QyKgA z>|FN#2egj~$p6IwfeXtb*H(K@$R`Y*_K(>Ets$weBw+F`e3BKC2S~WPR}}IfUu~Yd zHsCH@fQ zljn5A93(;>l7EIZ-S|U2f3otdJ>Nj}vjgt`*mW+F0%(mCRP;)~ZaORIeaq*xM?- zu#wuTWwMsv)upD@DQ^#9>OqLW2FE?bPM;}A1)m zWaS!M3xE^^PQlY9O!}36&h(Ki7{VSj{*02p%6}L6%j8$&uToG`gD)ZIs{Kp+r;b{QWafyZXtLK(9MjwE}rgMprpJLS@M z6YjWxGQ49za1cRFS#N;;Ig7<<)^lVfF0XW?4Y9i*HG!8R$uiwSdS-Uj&0`VTEf0$# zLa&B~6`4xJtyI*F;l zhC>FXHuB}^1ND6H-4RX zMNq!^*yqGFY=)CzDDNh(5G&VM^UNn!d?Ax>J(?ttt|JO7>UNmzs zux8PVDnsW~AZ-N(uBeA$AAn`GKa@1UQ%9XW1g8tj+0m620-O{;Ize*T1j|mxc<6^y zN231H$uBA66Zu;*vzP^_{JSiBE*dy(eBuRyd>LZ*l>SHlahE-!(gBqGS&rB8*H)0y z{~~|==dWA$6J=(*P9eqc=vSjJEm^pfOSqe`^C{>JW--ttpxEGJ|Kt1Wnu)mhaBRd^ zck}_`3Pv|bq3&*}8K|x^eE6}{ktKj?c`tdzA$|Vzu`;G$n!=AP88oAcBOr60T@e^n zl=lyazhmwy{_>;M*@{o^PWyn2!=`Kat4!;Gk;bV@Yz@!N%Gpt<7{YUlEGW2*wr9)-qtPD5+ z?jxH9+}B<)PO|Ab4^Npw*;eKul$}C;c~GD4sXUp%p-Gr(37eB_8=sjn^HTE9mC2V$ z$=||}$Op15=mM!;z>6RqiIv}|%))RNSOqLd;iZIGrxzvPD5i@s?KKe#&OKOQJY{~= zi@NkZe!uyjPwRG>>-gKMju}__t?}0NH($ji+#ux9lj20V_eou~3@Nk0;Dhpksf0`c zMgg+M^m&#iFg3T-eZogve1Moy$nWOS-))~Iwv%sAp@$4^hhM*l;c3O_>P`tJGn z^y4&|k=Q@GcWwXd<3%)`@y(Bv%@`8CyvjGaD0mK-S9!E^An`rq!}g5G zv}-_~vv&kwYEClRlCwtS03Fi`gnot3S|ZTarUFr<>5cR+!C&g2k{=O%mSUd5^Puo& zB>6?lUz2~~e~|WXhJ}H3SaPnxJkec1W&y0ZNA1`o1VUhT((JK(FhR``OSaPgzl;6< z$rG>si(fw~qn%31^P%YYxXXBdukqfdrgvt%J?ZTQ56^mU(y#R^W-WY#OAOHdPGLt< z^iR*xKUdNIUl|ns zdnez>g3vi-zF`R&d~1ZW6=|dV1&kEqr)Ly8QBB+7=;*PQlRV-C(b`rf1QVv@ALOqG z-$S(5D)<@WGlf5F&CXHs&(#pD%J@+zG=fE7kJw#FpEKcRA}oZUwoD`4fBfS$*~c#@lrV8<)++DmHfR*oOY!kNg`SESWwyHoh9J2jm~X zchQ4E*2nv0oCO|`luUUe07lis?6iMUc&fm4cbh6)mQn%OHy67PYyYi`A3gca(8hme zi}9JA!hgs$xy%AVkMXHcXo#`^2!$cQRp7-075TH07!v)@F701vdQ?zE#G>WDO$mA8 zbK{Tz?Vr?a5+NDZ`KV%ySKj`JT*BScK0|r`57A~G)u(ndb?Nreq7b#Z=~9-`p!@KyMmfR9v`R?cyEC28Im6(Q*3;v z{Cj)#`Q)7YjMwWAHZ7jR;;*rN`_|7Ef7oCcS2R5cM`85ZYT7^eitw8VB%|1wvOJC2 z_x5A_jW~N?_5;|WckJ;mcpnPy5d|^5Ef8oU5eO8|}(RgjY(X{AY zq9csoxBl|UBDgq>D;ggxdvOR0WPX_C;S?TS=)(9RNwXWd$p8ZiDvP^sT&7*!1FlYC z!B6h!w^aIo7KdF9UJp~kMzB-rQPqF{d-+>b`xgoxGgwR^cuuALV}jZUK7<#KAs}SQ z@{7klN4bDV%F{vVTT}A?JHL#(BTyLzpxES*_OIl>>VYRk{9!-0u|%`4Rdj(i zm&VWJSCD&&*@HQY$*;B2FEhka4{LCyLYFx&?e&$XI12FFl=J7X@%O*!f7Vu7q(sTLjPcFEDzBB&DGtRtZ_{68L z`RbQ97k~Z2(r>5EUHp-z&-9paesD*gy&zJB4jDRrA5cp}U@FBK-C zAB_C>_F(=T0v{9RO**@*ITCj00QOLrN6&jNYO=8_|G+wG*G`8fS{v@_T?1;S-8Cit<;fC#UI@Q-a}0A+2_ z9#g|ytIL4cXSalDwK}cH%`*>$jMD#{{OY;_KF9 zLHbOpK#)eOMNapqg8Lg^2|5n>0P&Y(nXGLNjq~bz?b_rPIAoQl-=S5W!|zOVN{>iN z{-v~k=V9%i`q&Qw{saC-PKS&f8RoJwvpSZFg9SnK>lhu_Bd>3@^MnD(fL{dIVwh8u z&6JhZvy*Q`zXHD_^Qacld2N>L_H8=mTaKPTbdK)PPvX*CB^HVjY>XK>4FkkQb~haG!tr0H0kfD7_6G zP%^MjQBe#XD>v#|6)p&x2lXk+mPvM-_SP($PH~br9jP(2S9!!Q)+v8jm%>p09_3Q6 zWjl6Y$1x}Ms;LU8nASEyN9NdJJqPwIX364E3&=PsuZ7Z=KBB*>3;Kw43X3JwLNGch z`TxQGq&`hfnBS!f2=XdtXkUEyPa7`SL)!`ctMqT>sK9c4A<(j(^^Yr0Nmtd?(#Q1q z8PDFtNu80TPVUonDhnLxI;{&YPZe{B2)cIaMaZT7EJQZGHRCS*#ee*rP&cD_7blJ5 z`gh{wJ1^ZU)kxFWw_MlJN8W}y|1-z1ifdH~6dUmxef{xMy3YN#jlp1O+VzvM_dBER z8)HBFI%D5DW7m(yf$xka46OsxpTCpJrf^E6+)aO%LOS7^i8<}x&Mb@s!vfmBG@jO* zmcPc|)`8+&-MMk*j^FT>V0Oud_fKyP^?U2~|E|mFo3vas?5!di(qCG=;MK1_ee0JW z-u-CKb1N6VWE|MpSch?8loW_tzG**yh~BpG(c_Rb0e*GQzPgt4#_u~fZ(hIri;pIy zf02Sz+uywBAOoY}76Y@*?q7-g=Zm{JE}SEkGgaHDug7_2y#30f>%Li7zwMK{tt&Qu zzlbw>lW(fKH6D;(T=Jd_`r|Jf5OUb##eB zI^o*YJ%G>OOm-pEip+oitp8Q}S3`gx|8NI7mrU~;zcK1QH})~#(c5na8@C^9HX7^e z8XM&=a@nOaTT2DQ*~+$B^#0SFKd@J%(R$n8Sg${}`Yz5b{ADvy0{YVa0}ajTKYah^ zdpFKIuzjhqcb@(V$HDz;sM&O&xv_a4g)1*OKK&TY2lV}SwYtA?uu)!9>+Rrvk@o6E zFSk@n_3kS#97eEwOU>r(ztKmSdl`Fw`_eeb(m&fCqp8t2c;H~Oyqci$a-@HqGrF8U zy1ECgV);0833esZu#CRr)tT6*Iqn``_~UR8)s)VoY_n$4*C*OVTA^mmzk>ko`?;6db z5ah4LP5I4AT|po1cB@|V(jV8Yla;}`#)ka|ME=h`dDp$STp(xm?`vq_jPzGs^XiLF zq|7$=y_W~n7FQK|Y?*BiHlnML{JqW1 z(q`Dw=l@&5T@y5G`6ujTW3g^^SWPAW7858ZBc1AdST#PX_znHDM!=I=qO#jFQ>P3= zW$CrSSk>Q6pHJV4GrFCH-6@0PVB-c(Ha0aj0V9#x>UaIN!D!h07+eYeWuqd}K?~?_ z{P@F{tCzh)xvpMb2Z)^>#)IjClJ-1N|^Tqbx_tnpY`|mL5w1cV;c>xQ)(zyFNi?cH6hBfWXO7S8BRpJB67_5{?gGcXLjqF9P4(7ers+z@bg#ib?YqVM?7k3-0|gt_ntI%d^l=| z45Roc$V3o&rX10D5nr~xPg6tvswLv0)EgdS3ZJf+Z8VE+S^vWdDzl|78^*);U&qHy z>m*W$ZS~@p)O9(0^F`|9Z8xT3K8N%!r;dJ2MR|W{QB4BzVT>HoS%1B~Ez1(Ofo1Bf z2*IW9D)-=gZ~~YNerc5lYIVk6M;RQKE(q4aZA<^CF0}0c_?5_CK~Q!OD^xAMu=vQz zmku5Ia=|@cu9&{)gQvEyUv$^4XC8J3`D0{jJnVc$c|c#_h~SzVNEelD&P!r8=&f=4 zXTPrhe9O9J>pq*+a<0w6y7lk8eB+AQ6IU&Mk&{>mkej&5y{sni@uur>ILNkbBIX3-^3d2O| zygO@RtCPl_dKow#dtwSFZ@vC3%DdH>;r&ycD_xoRWM5~3;RA5_5##_>E6kxnCj_aJ z_#dDM_tqsAAob!%I27LV^P97$P55xe9n0UF{M-W< ze)!6*jawIO`fBENSDbwFgbR0W`~3IK->?7flN+x)?}s%n{l4-2O<%lWSAiuw@Q!Vp z_V3?z*}1h_e^|QVhfj^>?>nnJFa6|SEO_jzH8a0nHT{`MSJTr$;VXTyoY>sn)iw6HfC|k9JCby7;ZVJASCE-}L;H@mw^Te_H>;XY0OPvi0XL>V8?v zndJ*-Y~S)VX6L|TTGV^|gg>O$*jC$ao&pepIq|6<16MpwPVw= z4c{*R{%btIzj$4;u(fT|XKNNczT$(YpMLO)d9&}H|K_9}KmK7v`+D(H3*Wrs^=Bu1 zH0zE{YoFV)cG?#oO_}w?)eGLZeeaJc{lJ3(tFD(|xM$P$1*?}#tKTwL{|&1@oc!g- zFU)&q(obK`y6LJBoc!ClCv9E-!552P`EkXgx{k4T=_iY(tyuE>lT#*qy7<|j)=Zmx z=ehb4M^x{+HBYWx^1{a-z3}D7uk8C};m(ckZ~pSVMYA7j*fRI2`!3Tp*RP$)53PLn zu}|ke_xaM98^4;f;oA>3fA_)OE$>T(c5PW`bJ5P_6}Z`DVU%TGUPQ6m0i$|}i}p`A zhrV0-_?Pl0uv_$hR!;V&Paoa5>X8kfPv7+Atlz$V>8FpUe7o@OZ$G+s-O~GiUOi>Y z>L<4T`0A#wU)Z#K@()Yz!_(Wnb1`4M_2!%RZ=Ak$?bN2tbG}(L$=J7K)%y>of0_cL z9s)rhWpSN#gO1#v`_2+`=m#JV(KA3rj(Y*Yt`IC*` z-qiOwWkd}tl3TucgQbDfNA=}oUMPl-`sc5wGl3e^SH_gDR=&ISi)YrYdSvH!)4u=Y zC7Q?3W0dwz=PhpkXva6Nt(<$u&!4~0u=&FS+g8+XnP==?GVSpj^sne6U100i)4rH@ zf8FLeyEn{PxAfuPzvXx9^i?-JmE}H1naf!&Bnb8jubTM^m$9uF<--&V#9C+T8&Xb( zK&bYwLl6agQsU38*~EXQ>lehsU28g*mX`SaKDR5?kMJbWf5C9|2mC?5M0$W!6Apwr z9K|#uwYlw3_~gc7!LD7qbjTFL2@C=4(g#a`w1bmIo!F~S4_!ML@^|mvz2&QT4cf9& z>`Z#-n4Ij=xud?6YdZ8`9=9tR4px*E6GxE#qQ#P1URmY#httn_V)@BXD4IU==Yl^Fu-T<0Tg z_&5tZ9hBSQFRTBrFS1thKlu*LKH4++*ELE1!RHSl2pDUYLJxB;J8NE8o;tfHDFyJ6 zwDYRpPl1euR?0fMg_IlLpjCD0e7<&yLZb~o;}b~I$-M|p+%NLC}|)Kyw6 z13FdY94BRmW>_qLU!l)k5|Ep4N2#H2gQcme;*XTn<7{V%gB(do5X*NHf<=;@)N6;q ztiIR2ExvJisFnv{w8|h;#*)>Jm}jc!>KJ&;YlvA}nJlqEv{5AaVm7kI z)NZ4W>~`WzguA&)Y(IOYmxvT6gu-QB*~@*XVu=*3@q1VwJUUO{i+e{Tb^?B}VF~gd zA*Y5Zrs%$YRj&^l@cD)J8E+ou_nB?1ytr{<=eJJCzq32>&+sb|@a!8D_YEr~>|X*= zDuBOsqhahxyMq)kQah9KoVa~Z`=D4@3heeIfeJ^Izk+~nSp|^%BNc*+{Ie}8`A9 zk#j5aZs?Yr)Td_FsPVfduLkz8$!Iumps`*&BCi|E&RBN?tm2Vts!5aa!LkkeCkV;C zBVeOBi15VbfeN8eB=6wyXf{c=wb*Sfv6GlaN3ayQ%=|@vn*XZy-%9?%0(?mR#3xJq zb6WnyzbW~ny#aOr{G7-)h%j7*2pq`=(vpV`Udm;3kO1ccu*1_|APa*Xpx95~oGmI5 z)Dk$C=OTBHfNCHh>@lk~ZXuybz@;Pn74lM2DP}C?6W<#r2T`!ZPxPlTCBv}5``Xv z_QeBq39?PeyruDr{ELH1{=NW4Q%Qh2-Xfnn>36~%L&$`z+B=aU_aSgya6Hs*M);CY zU^7K5r0?;!NQxhQggUJC$HA+yup|HUD`2geSPKxJ1WJfzraB1iPeog7R@r6cQF9l7Wq5qK~M>(z;$G9mmn5ez9Lr&lh%P$iLbDtokaeeCs|&J zUF)CpD@eZ%eTF0bLe``6QNk`_U!9dM2?zw`9uRwi(-;nT7ev7gh^bLaFyxoP53CqXEBp3BA z8q%)iuety#$b<9ES}6X1u_gXA|CP}n7o5mHwF=gORba{g%~DaIt>teP`NIO>B?`;x zjJWMFNr6O}BWD!5r!3&B2)WBb?$Ustw*UnI1=Srm(*CJNz zY4#FFM@|P%i&7#}RREgQDWR6-0Lo&rIlpP6|1F@io$;B30 zdB3LyLV38(eZn$xOpI4R6ln0I!sz*x@d-7FJ8L`57=HP0_br!TIof}-mj8>!{IOq8 zD0}P}2yG=(?4%M%AK)VpN{F5zC&XACAwGkUh=foXo8$@UhUEAciWNlr7k)J4|Ht?f z-Tv`(Y8G16{trH(~NniesxXBBmLvoc7!&*X%F}RhzUvpx?;ijgZy(* zJ~9PsrGHxF??L`zh}r@+XVir-y-9BXZk=*?E<*GIFD6j2pJ5l03*y{7MILvNFDOGW zLx2aG3|9di0TYOKflG>Zj_3daB8gSUw@5)o+o(MUh{dSM6fvQTP(i3W-M~_949V-b z7nvpqju7dt(sAPcLLL4r~O0jK)5W15IhkOz0vpw#RJDBL!-;0f2)Ym z{_p7Cb><0|Zn=Aj1o|2W8dLh`1>=KLKEJl)(SiBM|J(`!w}NCak>Lm`U~SaJKv3n? zQ!R!bYuw7Ot09TO%g(Z+m1MU97th1v={Dc`hzX>!kaQMgOZ!Lu=_wcxSQwuw3YX+Z zlLr<~v&HYU`BB%HKwdY-d~pEsQ-m+_$H*!2(f&O!kHE-=_Tkk0t&;=I$C1JPOhqF+srrX1G`FUkyMO07k%p zBzRtI(FF3RV;({N%)t)HUj^9cDS*gdO~DB1!2}fwHqk#Ok-yhs37GA8L89b8x?ORf zCxOhd_I-@PYDOmWGRnLT84l3@H2xxHh{&IbfG8ikN}^Or^kmbEHHDrho0T9s31SD? zPa#{5Z^iS8YShFQ=^o;J9`WckXrbgrfk0eURB|oBd%TJdN z$zK-26a<8B|H$8xlafE-a4Gqd+h{TQY*wGe7Q$$uO)?62(K&wfe^n&dG0N_*46{oY zQ1b$x(3JBEWRB)3@}sr13Ca*)N7ajABQ`unrWMPdP~I@vG-C5h7Z8jlCc-o!YKo2L zh&!ApmLeqYh4;;w^wJBg`2g_J?A6>k=>(+3SpwamMlBVlY?%U} zb4Z+t+8yY5q|Hah)&e}_WK+_ul<^rw;mAO2&topk)C|?iyT;7pySRpMw&)hl1kmfk zyUR$&dns_bm<=)Lm$-zCiK#apiE1yQ;F6PA4Ly@2MKXGlF(D~<$K}J<6ThSyCf~%r zYP-xl{=%RCGcVQtMgFYyKG8J$gq4?p{2v?FDRfqe_jqXE$!$mML1q&Y^V}t1FUOE@ zq6x+H3Xy(-C17R$UcHdH92u=m!9!dVK??3_LpyA{xJ=$Rj&?rcjv3RQHDx*Xt^S`UfU5uV9B( z1>86sSdAhlPOZtpW<{*H;FAo929GNUoeE!YW&GNjeB}Si2^UNIUz@gnn1emq{O0k? zF2g$!`FrQk=_LSe_y)!a?{sz|_0sRGA=N_COPTrNFhR)^ge*|Nk`M$}7swbyBU^H# zCUPQLM1J`Hs33uWIFkHx)D&2}aL6AOB=V>6+sN>x3t0WaMb0STNqEH_R0i9oD4$Su zj2Gc64`Op7d_5On6!tSNBR2qtW&Y)KstkJ3@LK-R>fsuS%e+jKJQlT>oq})An5!*+ zr|1?KuaJC7{wxr<8H~5RuvQ!Er0xP%!2j! z%PN4H0_WHuf;5Z#Q9j7uW3l*bHm_MmfuO^Y=kYKX$V16{5jA2DYWXt?GbpQBn4ITD z;3s4X!2C;P4aoS*j#V#&h76-zQr5b80+-dU*3VR^Hc`WOqC0=PhLpbPYkc!tLPr<7p-#IFa7A3MxYu3a_ZEr$DXwXxQ5E)9MZ_<`?3l@KiQmmYP$2#XJqyO>R8SIb zNun=c7{!?JFk{t(@-%)%G>)irCRw~7Ftn}ei^$F=V1AS!x-aTd< z!ZOn_2)`%PZyu7MI|Yf%A&<%1CFBIPfUbeX*fZqn872$Ndu#&Y+Pq7u64!Ohzr9!W zjN#|4zg5e>sYxPG_&2%v)#Dallz5<5cwA@nKY$OO{-pYb?VWrM7>z4{J0rt_2u~cj zGW#RWBsZL$6Uvc1l%OStS3p`&)Jj@bOM5^3e^wMm-KL9a_7do@03&=twv)~+d zH4Dgbn$beJSfM_vHQ=EATZ0%vPEXA1j{8XsCNEO#Oz3Fo4vf5*{pbqBo5##oNt ziiS@ZpM{K%R{$9$Sz1iKD!leWe8nDj7r(QM-$9~c?+}Qf{*gs_=U2op>zFs8ru?yi zW0qe%eb?OW+jnf*v}yNmyEg6I`k&2D^?&nBFv>y~RR+ksL;3WE4>Z84x~0g$$P3Dc ksnC%tY;?@u?ZB_)1afnNx!Hkil0l$L<1J+K(U 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/x_exe/x_exe.vcproj.old b/code/x_exe/x_exe.vcproj.old new file mode 100644 index 0000000..c5ec9ac --- /dev/null +++ b/code/x_exe/x_exe.vcproj.old @@ -0,0 +1,1516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/x_game/Copy of x_game.vcproj b/code/x_game/Copy of x_game.vcproj new file mode 100644 index 0000000..945bdc7 --- /dev/null +++ b/code/x_game/Copy of x_game.vcproj @@ -0,0 +1,1264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/x_game/x_game.vcproj b/code/x_game/x_game.vcproj new file mode 100644 index 0000000..bed606e --- /dev/null +++ b/code/x_game/x_game.vcproj @@ -0,0 +1,1263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/x_game/x_game.vcproj.old b/code/x_game/x_game.vcproj.old new file mode 100644 index 0000000..9ce4ac3 --- /dev/null +++ b/code/x_game/x_game.vcproj.old @@ -0,0 +1,1262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/x_shaders/bump.psh b/code/x_shaders/bump.psh new file mode 100644 index 0000000..efac46a --- /dev/null +++ b/code/x_shaders/bump.psh @@ -0,0 +1,30 @@ +xps.1.1 + +tex t0 ; color map +tex t1 ; normal map + +#include "../win32/shader_constants.h" + +; Dot product the bump with the light and halfangle vectors +xdd r0, r1, t1_bx2, v0_bx2, t1_bx2, v1_bx2 + +; Factor in the light color and add ambient +mad r0, r0_sat, c[CP_DIFFUSE_COLOR], c[CP_AMBIENT_COLOR] + +; Raise N.H^2 +mul r1.a, r1_sat.a, r1_sat.a + +; Modulate against base texture +mul r0.rgb, r0.rgb, t0.rgb ++mov r0.a, t0.a + +; N.H^4,^16,^64 +mul r1.a, r1.a, r1.a +mul r1.a, r1.a, r1.a +mul r1.a, r1.a, r1.a + +; Modulate the specular highlight by the gloss map +mul r1.a, t1.a, r1.a + +; Add the specular to the color +xfc zero, zero, r0.rgb, prod, t1.a, r1.a, r0.a \ No newline at end of file diff --git a/code/x_shaders/bump.vsh b/code/x_shaders/bump.vsh new file mode 100644 index 0000000..83ddba5 --- /dev/null +++ b/code/x_shaders/bump.vsh @@ -0,0 +1,64 @@ +;------------------------------------------------------------------------------ +; Vertex components (as specified in the vertex DECL) +; v0 = pVertex[i].p +; v1 = pVertex[i].n +; v2 = pVertex[i].t0 +; v3 = pVertex[i].t1 +; v4 = pVertex[i].basis.vTangent; +;------------------------------------------------------------------------------ +xvs.1.1 + +#include "../win32/shader_constants.h" + +#pragma screenspace + +; Transform position for world, view, and projection matrices +m4x4 oPos, v0, c[CV_WORLDVIEWPROJ_0] + +; Multiply by 1/w and add viewport offset. +; r12 is a read-only alias for oPos. +rcc r1.x, r12.w +mad oPos.xyz, r12, r1.x, c[CV_VIEWPORT_OFFSETS] + +; Pass thru the base tex coords +mov oT0, v2 +mov oT1, v3 + +; Generate binormal vector +mov r7, v4 +mul r8, r7.yzxw, v1.zxyw +mad r8, -r7.zxyw, v1.yzxw, r8 + +; Get the light vector +mov r1, c[CV_LIGHT_DIRECTION] + +; Move the point light vector into tangent space +; Put in tex coord set 2 +dp3 r3.x, r1, v4 +dp3 r3.y, r1, r8 +dp3 r3.z, r1, v1 + +; Multiply with 0.5 and add 0.5 +; Put it in diffuse +mad oD0.xyz, r3.xyz, c[CV_HALF].yyyy, c[CV_HALF].yyyy + +; Get the vector toward the camera +mov r2, -c[CV_CAMERA_DIRECTION] + +; Get the half angle +add r2.xyz, r2.xyz, r1.xyz + +; Normalize half angle +dp3 r11.x, r2.xyz, r2.xyz +rsq r11.xyz, r11.x +mul r2.xyz, r2.xyz, r11.xyz + +; Move the half angle into tangent space +; Put in tex coord set 3 +dp3 r3.x, r2, v4 +dp3 r3.y, r2, r8 +dp3 r3.z, r2, v1 + +; Multiply with 0.5 and add 0.5 +; Put it in specular +mad oD1.xyz, r3.xyz, c[CV_HALF].yyyy, c[CV_HALF].yyyy diff --git a/code/x_shaders/bump.xpu b/code/x_shaders/bump.xpu new file mode 100644 index 0000000000000000000000000000000000000000..fd668ea3156135bc9fe6c7a134722a5b2694af97 GIT binary patch literal 244 zcmWFtb}|T!42isCa8X7;Kvq^jz~F{}z}>q@_$EIO14Dz1tRw?90MM)pKywZNF^ERS wuAVNQhZGJ1Re->WGY}d(dDZ}^465<~79|{lj0}oM;`0Ch|K|td|BMU_0N}46r2qf` literal 0 HcmV?d00001 diff --git a/code/x_shaders/bump.xvu b/code/x_shaders/bump.xvu new file mode 100644 index 0000000000000000000000000000000000000000..e22a16b3346ac91e2c4ea226870307d1acd8f1fa GIT binary patch literal 324 zcmXZWp=-lH6vy!&Hz>zc#6)OeTt`HpP(n#3M`T20&D?C)h*4UoObdmIiMdUlt{BId zh^*)zFgd0wBPu52*7x2m?!)(Yk1Wj<_-h{qst8bw8uKpJ1_+P?Pkr%fhDm@3Fn8<+ zl>y1+0w{fNSykj|G;Ul?8Jmj|kI8;c{hHl5p0sLiHNxskT%Le8^41x48(HhG$M++@ zx6~`@x$m7l`I{X7@3$YoOFE*Th~<4r)t)~9|9N|7yxjq7H0qe literal 0 HcmV?d00001 diff --git a/code/x_shaders/dlight.xvu b/code/x_shaders/dlight.xvu new file mode 100644 index 0000000000000000000000000000000000000000..1370d105ffa50d5c455ae4924057bb3aadfaeb98 GIT binary patch literal 244 zcmbim zhZzTh#0H>328LGV28Jwy10_ItIS^mE;SJmW*+BV@9Rgtf0-*XGK)%NU1u*{rkbeWn z*MRV)Qi7OrDj2r-{7_g2l9ysz0JPd<3j@Oh4KSZ&0aFf#>6VZm6PAGa52iC@YcL#P tc(LFEnD5ZT5E8jcCE~?`4PZXg0-$?Mwj^jk+y@k6$`WAUNIkJ3003*ZHeLV# literal 0 HcmV?d00001 diff --git a/code/x_shaders/environment.vsh b/code/x_shaders/environment.vsh new file mode 100644 index 0000000..d0f244f --- /dev/null +++ b/code/x_shaders/environment.vsh @@ -0,0 +1,53 @@ +;------------------------------------------------------------------------------ +; Vertex components (as specified in the vertex DECL) +; v0 = pVertex[i].p +; v1 = pVertex[i].n +;------------------------------------------------------------------------------ +xvs.1.1 + +#include "../win32/shader_constants.h" + +#pragma screenspace + +; Get the view vector +add r2, c[CV_CAMERA_DIRECTION], -v0 + +; Normalize the view vector +dp3 r11.x, r2.xyz, r2.xyz +rsq r11.xyz, r11.x +mul r2.xyz, r2.xyz, r11.xyz + +; Get the dot product of the view vector +; and the vertex normal +dp3 r3.x, r2, v1 + +; Add the offsets +mul r4.x, v1.x, r3.x +mul r4.y, c[CV_HALF].x, r2.x +sub oT0.x, r4.x, r4.y + +mul r4.x, v1.y, r3.x +mul r4.y, c[CV_HALF].x, r2.y +sub oT0.y, r4.x, r4.y + +mov oT0.z, c[CV_ONE].z + +; Transform position for world, view, and projection matrices +m4x4 oPos, v0, c[CV_WORLDVIEWPROJ_0] + +; Multiply by 1/w and add viewport offset. +; r12 is a read-only alias for oPos. +rcc r1.x, r12.w +mad oPos.xyz, r12, r1.x, c[CV_VIEWPORT_OFFSETS] + +; Set the color +mov oD0, v2 + +; Transform vertex to view space +m4x4 r0, v0, c[CV_VIEW_0] + +; Use distance from vertex to eye as fog factor +dp3 r0.w, r0, r0 +rsq r1.w, r0.w +mul oFog.x, r0.w, r1.w + diff --git a/code/x_shaders/environment.xvu b/code/x_shaders/environment.xvu new file mode 100644 index 0000000000000000000000000000000000000000..0bd950d8ff908727c0ca897399dca567a6436472 GIT binary patch literal 324 zcmYMwze~eF6u|K>p-RA|LdB3coRZ-Lkq$u}Pv{VA*6xl4J2`c9?0=wRMkyj09Xt3J zI5}o?5bM}Mt?%6lzUe-^pWM{+&`1~8jC)ML9ILbT16C)oJgqu457GTakyo~vsxy|5 zoN7MzHp@6VQ^zmf?R)4?8lz4-E}B^Ky_I?Nrrum{6&fek2}bU3JRLgB2! d0c^6!VqgV$%wiT~WKcxXp#L8NJ~J{f002b=7Cis} literal 0 HcmV?d00001 diff --git a/code/x_shaders/hotblur.psh b/code/x_shaders/hotblur.psh new file mode 100644 index 0000000..34199bf --- /dev/null +++ b/code/x_shaders/hotblur.psh @@ -0,0 +1,30 @@ +;------------------------------------------------------------------------------ +; Filter to blur and image. +; +; Copyright (C) 2002 Microsoft Corporation +; All rights reserved. +;------------------------------------------------------------------------------ +xps.1.1 + +; Default filter is box filter, but this is easily overwritten using SetPixelShaderConstant + +; When setting a pixel shader constant in the main application, we must check to see if the +; filter coefficient is negative, in which case we set the constant as a positive +; number and negate the constant in the expression below. + +def c0, 0.25f, 0.25f, 0.25f, 0.25f +def c1, 0.25f, 0.25f, 0.25f, 0.25f +def c2, 0.25f, 0.25f, 0.25f, 0.25f +def c3, 0.25f, 0.25f, 0.25f, 0.25f + +; source textures +tex t0 +tex t1 +tex t2 +tex t3 + +xmma discard, discard, r0, c0, t0, c1, t1 ;r0 = (c0 * t0 + c1 * t1) +xmma discard, discard, r1, c2, t2, c3, t3 ;r1 = (c2 * t2 + c3 * t3) +add r0, r0_sat, r1_sat ;r0 = r0 + r1 + +xfc zero, zero, zero, r0, zero, zero, r0.a diff --git a/code/x_shaders/hotblur.xpu b/code/x_shaders/hotblur.xpu new file mode 100644 index 0000000000000000000000000000000000000000..b0ea5ba750da7158682aa6066989d1bcc29e3cb4 GIT binary patch literal 244 zcmWFtb~3nm>Bhy|mu_7&kTsBDzyf%HVhunU2PnXz1S~{Io&jVKFA#%hEa2p!69-Qp kI(1NiR{@6#Buxa&Visg%P;5b{SNQ+`zabF+XJlXi00c)H*8l(j literal 0 HcmV?d00001 diff --git a/code/x_shaders/rain.psh b/code/x_shaders/rain.psh new file mode 100644 index 0000000..04f487d --- /dev/null +++ b/code/x_shaders/rain.psh @@ -0,0 +1,10 @@ +xps.1.1 + +tex t0 // normal texture lookup in t0 + +;mov r0.a, v0.a // Move alpha channel into r0 + +// blend modulated colors +;xfc r0.a, t0, zero, Zero, zero, zero, r0.a + +mul r0, v0, t0 diff --git a/code/x_shaders/rain.vsh b/code/x_shaders/rain.vsh new file mode 100644 index 0000000..dde5e8d --- /dev/null +++ b/code/x_shaders/rain.vsh @@ -0,0 +1,59 @@ +;------------------------------------------------------------------------------ +; Vertex components (as specified in the vertex DECL) +; v0 = vPosition +; v1 = vFields +; v2 = vTex +;------------------------------------------------------------------------------ +xvs.1.1 + +#define LEFT r6 +#define DOWN r7 +#define TEMPPOS r8 +#define TEMP r9 +#define TEMPALPHA r10 + +#define CV_ONE 1 +#define CV_WORLDVIEWPROJ_0 2 +#define CV_WORLDVIEWPROJ_1 3 +#define CV_WORLDVIEWPROJ_2 4 +#define CV_WORLDVIEWPROJ_3 5 +#define CV_ALPHA 20 +#define CV_FADEALPHA 21 +#define CV_LEFT 26 +#define CV_DOWN 27 + + +; get possible alpha source +; alpha = mAlpha * (pos.y / -item->pos.z); +;rcp r10.x, -v0.z +;mul r10.x, v2.z, r10.x +;mul r10.x, c[CV_ALPHA].w, r10.x + +; if (alpha > mAlpha) alpha = mAlpha +;min r10.x, r10.x, c[CV_ALPHA].w + +; set the constant color to add alpha to +mov oD0, c[CV_ONE] + +; set the alpha fade +;mov r11.w, c[CV_ALPHA].w +;mul oD0.w, c[CV_FADEALPHA].w, r10.x +mov oD0.w, v2.z + +; Pass thru the tex coords +mov oT0.xy, v2.xy + +; Add in other angles based on the field +mul LEFT, v1.y, c[CV_LEFT] +mul DOWN, v1.z, c[CV_DOWN] + +; Set the final position +add r4, v0, LEFT +add r4, DOWN, r4 + +; Transform position to clip space +dp4 oPos.x, r4, c[CV_WORLDVIEWPROJ_0] +dp4 oPos.y, r4, c[CV_WORLDVIEWPROJ_1] +dp4 oPos.z, r4, c[CV_WORLDVIEWPROJ_2] +dp4 oPos.w, r4, c[CV_WORLDVIEWPROJ_3] + diff --git a/code/x_shaders/shadow.vsh b/code/x_shaders/shadow.vsh new file mode 100644 index 0000000..daf4c6a --- /dev/null +++ b/code/x_shaders/shadow.vsh @@ -0,0 +1,30 @@ +;------------------------------------------------------------------------------ +; Vertex components (as specified in the vertex DECL) +; v0 = pVertex[i].p +; v1 = extrusion determinant +;------------------------------------------------------------------------------ +xvs.1.1 + +#include "../win32/shader_constants.h" + +#pragma screenspace + +; Determine the distance to the ground +add r4, v0, c[CV_SHADOW_FACTORS] +sub r5, r4, c[CV_SHADOW_PLANE] + +; Factor in the extrusion determinant +; r3 will either be the distance to the ground, or 0 +mul r3, v1.x, -r5 + +; Extrude the vertex if necessary +mad r0, r3.z, c[CV_LIGHT_DIRECTION].xyz, v0.xyz +mov r0.w, v0.w + +; transform to hclip space +m4x4 oPos, r0, c[CV_WORLDVIEWPROJ_0] + +; Multiply by 1/w and add viewport offset. +; r12 is a read-only alias for oPos. +rcc r1.x, r12.w +mad oPos.xyz, r12, r1.x, c[CV_VIEWPORT_OFFSETS] \ No newline at end of file diff --git a/code/x_shaders/shadow.xvu b/code/x_shaders/shadow.xvu new file mode 100644 index 0000000000000000000000000000000000000000..e7ecc276af73bef2711aa8e2e980021f072ad650 GIT binary patch literal 180 zcmbYs<3JeSaAU=a4h`&k!C@;p~!oUzv1mZI=IWRD+ z2$5!BFi-;W7*;h*V#t_bVgh99f%rfPh8zJip#BXCVE%(Q3=BWaSQsRr{01O@29VzY ni9B@DFg54AL<}2S+!3FPFFd_U8pJ04|J3xO6 zhs73{^ZtyqumS-#iGb1whs1ux-oEIT*WCBTY*uZb%CCW`ydS%Jkpc$1uN`k*achW=p>>FrYHpnm;4SoQl z(P%IjZF=u?xBWQhJl;Z@E&&rwU{cGxoMK=?0kXyJX+Gc-=Row! zkpO{9d1`~sx<7$;!$3KanWLwn?a1-z%3c>D44w0g_7%2(uYaoizRGMRv_DZlNG0di znS3MLKa0(gzUGGQ`0x7`BYS_rBYkmSM3fs|*#7UuitJE7-|GqQRVUx`{|!%d^1eoP IT-Bxi7bdAXI{*Lx literal 0 HcmV?d00001 diff --git a/code/zlib/adler32.c b/code/zlib/adler32.c new file mode 100644 index 0000000..afc81b6 --- /dev/null +++ b/code/zlib/adler32.c @@ -0,0 +1,52 @@ +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: adler32.c,v 1.1 2003/03/19 19:08:52 osman Exp $ */ + +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zlib.h" + +#define BASE 65521L /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#define DO1(buf,i) {s1 += buf[i]; s2 += s1;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* ========================================================================= */ +uLong ZEXPORT adler32(adler, buf, len) + uLong adler; + const Bytef *buf; + uInt len; +{ + unsigned long s1 = adler & 0xffff; + unsigned long s2 = (adler >> 16) & 0xffff; + int k; + + if (buf == Z_NULL) return 1L; + + while (len > 0) { + k = len < NMAX ? len : NMAX; + len -= k; + while (k >= 16) { + DO16(buf); + buf += 16; + k -= 16; + } + if (k != 0) do { + s1 += *buf++; + s2 += s1; + } while (--k); + s1 %= BASE; + s2 %= BASE; + } + return (s2 << 16) | s1; +} diff --git a/code/zlib/compress.c b/code/zlib/compress.c new file mode 100644 index 0000000..fa41210 --- /dev/null +++ b/code/zlib/compress.c @@ -0,0 +1,71 @@ +/* compress.c -- compress a memory buffer + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: compress.c,v 1.1 2003/03/19 19:08:52 osman Exp $ */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zlib.h" + +/* =========================================================================== + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least 0.1% larger than sourceLen plus + 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ +int ZEXPORT compress2 (dest, destLen, source, sourceLen, level) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; + int level; +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; +#ifdef MAXSEG_64K + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; +#endif + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = deflateInit(&stream, level); + if (err != Z_OK) return err; + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + +/* =========================================================================== + */ +int ZEXPORT compress (dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; +{ + return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION); +} diff --git a/code/zlib/crc32.c b/code/zlib/crc32.c new file mode 100644 index 0000000..0dce697 --- /dev/null +++ b/code/zlib/crc32.c @@ -0,0 +1,165 @@ +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: crc32.c,v 1.1 2003/03/19 19:08:52 osman Exp $ */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zlib.h" + +#define local static + +#ifdef DYNAMIC_CRC_TABLE + +local int crc_table_empty = 1; +local uLongf crc_table[256]; +local void make_crc_table OF((void)); + +/* + Generate a table for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The table is simply the CRC of all possible eight bit values. This is all + the information needed to generate CRC's on data a byte at a time for all + combinations of CRC register values and incoming bytes. +*/ +local void make_crc_table() +{ + uLong c; + int n, k; + uLong poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static const Byte p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* make exclusive-or pattern from polynomial (0xedb88320L) */ + poly = 0L; + for (n = 0; n < sizeof(p)/sizeof(Byte); n++) + poly |= 1L << (31 - p[n]); + + for (n = 0; n < 256; n++) + { + c = (uLong)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[n] = c; + } + crc_table_empty = 0; +} +#else +/* ======================================================================== + * Table of CRC-32's of all single-byte values (made by make_crc_table) + */ +local const uLongf crc_table[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; +#endif + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const uLongf * ZEXPORT get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) make_crc_table(); +#endif + return (const uLongf *)crc_table; +} + +/* ========================================================================= */ +#define DO1(buf) crc = crc_table[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8); +#define DO2(buf) DO1(buf); DO1(buf); +#define DO4(buf) DO2(buf); DO2(buf); +#define DO8(buf) DO4(buf); DO4(buf); + +/* ========================================================================= */ +uLong ZEXPORT crc32(crc, buf, len) + uLong crc; + const Bytef *buf; + uInt len; +{ + if (buf == Z_NULL) return 0L; +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif + crc = crc ^ 0xffffffffL; + while (len >= 8) + { + DO8(buf); + len -= 8; + } + if (len) do { + DO1(buf); + } while (--len); + return crc ^ 0xffffffffL; +} diff --git a/code/zlib/deflate.c b/code/zlib/deflate.c new file mode 100644 index 0000000..98d3c9b --- /dev/null +++ b/code/zlib/deflate.c @@ -0,0 +1,1355 @@ +/* deflate.c -- compress data using the deflation algorithm + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process depends on being able to identify portions + * of the input text which are identical to earlier input (within a + * sliding window trailing behind the input currently being processed). + * + * The most straightforward technique turns out to be the fastest for + * most input files: try all possible matches and select the longest. + * The key feature of this algorithm is that insertions into the string + * dictionary are very simple and thus fast, and deletions are avoided + * completely. Insertions are performed at each input character, whereas + * string matches are performed only when the previous match ends. So it + * is preferable to spend more time in matches to allow very fast string + * insertions and avoid deletions. The matching algorithm for small + * strings is inspired from that of Rabin & Karp. A brute force approach + * is used to find longer strings when a small match has been found. + * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze + * (by Leonid Broukhis). + * A previous version of this file used a more sophisticated algorithm + * (by Fiala and Greene) which is guaranteed to run in linear amortized + * time, but has a larger average cost, uses more memory and is patented. + * However the F&G algorithm may be faster for some highly redundant + * files if the parameter max_chain_length (described below) is too large. + * + * ACKNOWLEDGEMENTS + * + * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and + * I found it in 'freeze' written by Leonid Broukhis. + * Thanks to many people for bug reports and testing. + * + * REFERENCES + * + * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". + * Available in ftp://ds.internic.net/rfc/rfc1951.txt + * + * A description of the Rabin and Karp algorithm is given in the book + * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. + * + * Fiala,E.R., and Greene,D.H. + * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 + * + */ + +/* @(#) $Id: deflate.c,v 1.1 2003/03/19 19:08:52 osman Exp $ */ + +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "deflate.h" + + +const char deflate_copyright[] = + " deflate 1.1.3 Copyright 1995-1998 Jean-loup Gailly "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* =========================================================================== + * Function prototypes. + */ +typedef enum { + need_more, /* block not completed, need more input or more output */ + block_done, /* block flush performed */ + finish_started, /* finish started, need only more output at next deflate */ + finish_done /* finish done, accept no more input or output */ +} block_state; + +typedef block_state (*compress_func) OF((deflate_state *s, int flush)); +/* Compression function. Returns the block state after the call. */ + +local void fill_window OF((deflate_state *s)); +local block_state deflate_stored OF((deflate_state *s, int flush)); +local block_state deflate_fast OF((deflate_state *s, int flush)); +local block_state deflate_slow OF((deflate_state *s, int flush)); +local void lm_init OF((deflate_state *s)); +local void putShortMSB OF((deflate_state *s, uInt b)); +local void flush_pending OF((z_streamp strm)); +local int read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); +#ifdef ASMV + void match_init OF((void)); /* asm code initialization */ + uInt longest_match OF((deflate_state *s, IPos cur_match)); +#else +local uInt longest_match OF((deflate_state *s, IPos cur_match)); +#endif + +#ifdef DEBUG +local void check_match OF((deflate_state *s, IPos start, IPos match, + int length)); +#endif + +/* =========================================================================== + * Local data + */ + +#define NIL 0 +/* Tail of hash chains */ + +#ifndef TOO_FAR +# define TOO_FAR 4096 +#endif +/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */ + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +typedef struct config_s { + ush good_length; /* reduce lazy search above this match length */ + ush max_lazy; /* do not perform lazy search above this match length */ + ush nice_length; /* quit search above this match length */ + ush max_chain; + compress_func func; +} config; + +local const config configuration_table[10] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}, /* maximum speed, no lazy matches */ +/* 2 */ {4, 5, 16, 8, deflate_fast}, +/* 3 */ {4, 6, 32, 32, deflate_fast}, + +/* 4 */ {4, 4, 16, 16, deflate_slow}, /* lazy matches */ +/* 5 */ {8, 16, 32, 32, deflate_slow}, +/* 6 */ {8, 16, 128, 128, deflate_slow}, +/* 7 */ {8, 32, 128, 256, deflate_slow}, +/* 8 */ {32, 128, 258, 1024, deflate_slow}, +/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* maximum compression */ + +/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 + * For deflate_fast() (levels <= 3) good is ignored and lazy has a different + * meaning. + */ + +#define EQUAL 0 +/* result of memcmp for equal strings */ + +struct static_tree_desc_s {int dummy;}; /* for buggy compilers */ + +/* =========================================================================== + * Update a hash value with the given input byte + * IN assertion: all calls to to UPDATE_HASH are made with consecutive + * input characters, so that a running hash key can be computed from the + * previous key instead of complete recalculation each time. + */ +#define UPDATE_HASH(s,h,c) (h = (((h)<hash_shift) ^ (c)) & s->hash_mask) + + +/* =========================================================================== + * Insert string str in the dictionary and set match_head to the previous head + * of the hash chain (the most recent string with same hash key). Return + * the previous length of the hash chain. + * If this file is compiled with -DFASTEST, the compression level is forced + * to 1, and no hash chains are maintained. + * IN assertion: all calls to to INSERT_STRING are made with consecutive + * input characters and the first MIN_MATCH bytes of str are valid + * (except for the last MIN_MATCH-1 bytes of the input file). + */ +#ifdef FASTEST +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#else +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + s->prev[(str) & s->w_mask] = match_head = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#endif + +/* =========================================================================== + * Initialize the hash table (avoiding 64K overflow for 16 bit systems). + * prev[] will be initialized on the fly. + */ +#define CLEAR_HASH(s) \ + s->head[s->hash_size-1] = NIL; \ + zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head)); + +/* ========================================================================= */ +int ZEXPORT deflateInit_(strm, level, version, stream_size) + z_streamp strm; + int level; + const char *version; + int stream_size; +{ + return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, version, stream_size); + /* To do: ignore strm->next_in if we use it as window */ +} + +/* ========================================================================= */ +int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, + version, stream_size) + z_streamp strm; + int level; + int method; + int windowBits; + int memLevel; + int strategy; + const char *version; + int stream_size; +{ + deflate_state *s; + int noheader = 0; + static const char* my_version = ZLIB_VERSION; + + ushf *overlay; + /* We overlay pending_buf and d_buf+l_buf. This works since the average + * output size for (length,distance) codes is <= 24 bits. + */ + + if (version == Z_NULL || version[0] != my_version[0] || + stream_size != sizeof(z_stream)) { + return Z_VERSION_ERROR; + } + if (strm == Z_NULL) return Z_STREAM_ERROR; + + strm->msg = Z_NULL; + if (strm->zalloc == Z_NULL) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == Z_NULL) strm->zfree = zcfree; + + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#ifdef FASTEST + level = 1; +#endif + + if (windowBits < 0) { /* undocumented feature: suppress zlib header */ + noheader = 1; + windowBits = -windowBits; + } + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state)); + if (s == Z_NULL) return Z_MEM_ERROR; + strm->state = (struct internal_state FAR *)s; + s->strm = strm; + + s->noheader = noheader; + s->w_bits = windowBits; + s->w_size = 1 << s->w_bits; + s->w_mask = s->w_size - 1; + + s->hash_bits = memLevel + 7; + s->hash_size = 1 << s->hash_bits; + s->hash_mask = s->hash_size - 1; + s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH); + + s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte)); + s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos)); + s->head = (Posf *) ZALLOC(strm, s->hash_size, sizeof(Pos)); + + s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); + s->pending_buf = (uchf *) overlay; + s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L); + + if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || + s->pending_buf == Z_NULL) { + strm->msg = (char*)ERR_MSG(Z_MEM_ERROR); + deflateEnd (strm); + return Z_MEM_ERROR; + } + s->d_buf = overlay + s->lit_bufsize/sizeof(ush); + s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; + + s->level = level; + s->strategy = strategy; + s->method = (Byte)method; + + return deflateReset(strm); +} + +/* ========================================================================= */ +int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) + z_streamp strm; + const Bytef *dictionary; + uInt dictLength; +{ + deflate_state *s; + uInt length = dictLength; + uInt n; + IPos hash_head = 0; + + if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL || + strm->state->status != INIT_STATE) return Z_STREAM_ERROR; + + s = strm->state; + strm->adler = adler32(strm->adler, dictionary, dictLength); + + if (length < MIN_MATCH) return Z_OK; + if (length > MAX_DIST(s)) { + length = MAX_DIST(s); +#ifndef USE_DICT_HEAD + dictionary += dictLength - length; /* use the tail of the dictionary */ +#endif + } + zmemcpy(s->window, dictionary, length); + s->strstart = length; + s->block_start = (long)length; + + /* Insert all strings in the hash table (except for the last two bytes). + * s->lookahead stays null, so s->ins_h will be recomputed at the next + * call of fill_window. + */ + s->ins_h = s->window[0]; + UPDATE_HASH(s, s->ins_h, s->window[1]); + for (n = 0; n <= length - MIN_MATCH; n++) { + INSERT_STRING(s, n, hash_head); + } + if (hash_head) hash_head = 0; /* to make compiler happy */ + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateReset (strm) + z_streamp strm; +{ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + strm->zalloc == Z_NULL || strm->zfree == Z_NULL) return Z_STREAM_ERROR; + + strm->total_in = strm->total_out = 0; + strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */ + strm->data_type = Z_UNKNOWN; + + s = (deflate_state *)strm->state; + s->pending = 0; + s->pending_out = s->pending_buf; + + if (s->noheader < 0) { + s->noheader = 0; /* was set to -1 by deflate(..., Z_FINISH); */ + } + s->status = s->noheader ? BUSY_STATE : INIT_STATE; + strm->adler = 1; + s->last_flush = Z_NO_FLUSH; + + _tr_init(s); + lm_init(s); + + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateParams(strm, level, strategy) + z_streamp strm; + int level; + int strategy; +{ + deflate_state *s; + compress_func func; + int err = Z_OK; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + + if (level == Z_DEFAULT_COMPRESSION) { + level = 6; + } + if (level < 0 || level > 9 || strategy < 0 || strategy > Z_HUFFMAN_ONLY) { + return Z_STREAM_ERROR; + } + func = configuration_table[s->level].func; + + if (func != configuration_table[level].func && strm->total_in != 0) { + /* Flush the last buffer: */ + err = deflate(strm, Z_PARTIAL_FLUSH); + } + if (s->level != level) { + s->level = level; + s->max_lazy_match = configuration_table[level].max_lazy; + s->good_match = configuration_table[level].good_length; + s->nice_match = configuration_table[level].nice_length; + s->max_chain_length = configuration_table[level].max_chain; + } + s->strategy = strategy; + return err; +} + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +local void putShortMSB (s, b) + deflate_state *s; + uInt b; +{ + put_byte(s, (Byte)(b >> 8)); + put_byte(s, (Byte)(b & 0xff)); +} + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output goes + * through this function so some applications may wish to modify it + * to avoid allocating a large strm->next_out buffer and copying into it. + * (See also read_buf()). + */ +local void flush_pending(strm) + z_streamp strm; +{ + unsigned len = strm->state->pending; + + if (len > strm->avail_out) len = strm->avail_out; + if (len == 0) return; + + zmemcpy(strm->next_out, strm->state->pending_out, len); + strm->next_out += len; + strm->state->pending_out += len; + strm->total_out += len; + strm->avail_out -= len; + strm->state->pending -= len; + if (strm->state->pending == 0) { + strm->state->pending_out = strm->state->pending_buf; + } +} + +/* ========================================================================= */ +int ZEXPORT deflate (strm, flush) + z_streamp strm; + int flush; +{ + int old_flush; /* value of flush param for previous deflate call */ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + flush > Z_FINISH || flush < 0) { + return Z_STREAM_ERROR; + } + s = strm->state; + + if (strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0) || + (s->status == FINISH_STATE && flush != Z_FINISH)) { + ERR_RETURN(strm, Z_STREAM_ERROR); + } + if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR); + + s->strm = strm; /* just in case */ + old_flush = s->last_flush; + s->last_flush = flush; + + /* Write the zlib header */ + if (s->status == INIT_STATE) { + + uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; + uInt level_flags = (s->level-1) >> 1; + + if (level_flags > 3) level_flags = 3; + header |= (level_flags << 6); + if (s->strstart != 0) header |= PRESET_DICT; + header += 31 - (header % 31); + + s->status = BUSY_STATE; + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s->strstart != 0) { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + strm->adler = 1L; + } + + /* Flush as much pending output as possible */ + if (s->pending != 0) { + flush_pending(strm); + if (strm->avail_out == 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s->last_flush = -1; + return Z_OK; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUFF_ERROR. + */ + } else if (strm->avail_in == 0 && flush <= old_flush && + flush != Z_FINISH) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* User must not provide more input after the first FINISH: */ + if (s->status == FINISH_STATE && strm->avail_in != 0) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* Start a new block or continue the current one. + */ + if (strm->avail_in != 0 || s->lookahead != 0 || + (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) { + block_state bstate; + + bstate = (*(configuration_table[s->level].func))(s, flush); + + if (bstate == finish_started || bstate == finish_done) { + s->status = FINISH_STATE; + } + if (bstate == need_more || bstate == finish_started) { + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate == block_done) { + if (flush == Z_PARTIAL_FLUSH) { + _tr_align(s); + } else { /* FULL_FLUSH or SYNC_FLUSH */ + _tr_stored_block(s, (char*)0, 0L, 0); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush == Z_FULL_FLUSH) { + CLEAR_HASH(s); /* forget history */ + } + } + flush_pending(strm); + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } + } + } + Assert(strm->avail_out > 0, "bug2"); + + if (flush != Z_FINISH) return Z_OK; + if (s->noheader) return Z_STREAM_END; + + /* Write the zlib trailer (adler32) */ + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + s->noheader = -1; /* write the trailer only once! */ + return s->pending != 0 ? Z_OK : Z_STREAM_END; +} + +/* ========================================================================= */ +int ZEXPORT deflateEnd (strm) + z_streamp strm; +{ + int status; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + + status = strm->state->status; + if (status != INIT_STATE && status != BUSY_STATE && + status != FINISH_STATE) { + return Z_STREAM_ERROR; + } + + /* Deallocate in reverse order of allocations: */ + TRY_FREE(strm, strm->state->pending_buf); + TRY_FREE(strm, strm->state->head); + TRY_FREE(strm, strm->state->prev); + TRY_FREE(strm, strm->state->window); + + ZFREE(strm, strm->state); + strm->state = Z_NULL; + + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; +} + +/* ========================================================================= + * Copy the source state to the destination state. + * To simplify the source, this is not supported for 16-bit MSDOS (which + * doesn't have enough memory anyway to duplicate compression states). + */ +int ZEXPORT deflateCopy (dest, source) + z_streamp dest; + z_streamp source; +{ +#ifdef MAXSEG_64K + return Z_STREAM_ERROR; +#else + deflate_state *ds; + deflate_state *ss; + ushf *overlay; + + + if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) { + return Z_STREAM_ERROR; + } + + ss = source->state; + + *dest = *source; + + ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state)); + if (ds == Z_NULL) return Z_MEM_ERROR; + dest->state = (struct internal_state FAR *) ds; + *ds = *ss; + ds->strm = dest; + + ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); + ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); + ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); + overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2); + ds->pending_buf = (uchf *) overlay; + + if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL || + ds->pending_buf == Z_NULL) { + deflateEnd (dest); + return Z_MEM_ERROR; + } + /* following zmemcpy do not work for 16-bit MSDOS */ + zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); + zmemcpy(ds->prev, ss->prev, ds->w_size * sizeof(Pos)); + zmemcpy(ds->head, ss->head, ds->hash_size * sizeof(Pos)); + zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); + + ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); + ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush); + ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize; + + ds->l_desc.dyn_tree = ds->dyn_ltree; + ds->d_desc.dyn_tree = ds->dyn_dtree; + ds->bl_desc.dyn_tree = ds->bl_tree; + + return Z_OK; +#endif +} + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->next_in buffer and copying from it. + * (See also flush_pending()). + */ +local int read_buf(strm, buf, size) + z_streamp strm; + Bytef *buf; + unsigned size; +{ + unsigned len = strm->avail_in; + + if (len > size) len = size; + if (len == 0) return 0; + + strm->avail_in -= len; + + if (!strm->state->noheader) { + strm->adler = adler32(strm->adler, strm->next_in, len); + } + zmemcpy(buf, strm->next_in, len); + strm->next_in += len; + strm->total_in += len; + + return (int)len; +} + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +local void lm_init (s) + deflate_state *s; +{ + s->window_size = (ulg)2L*s->w_size; + + CLEAR_HASH(s); + + /* Set the default configuration parameters: + */ + s->max_lazy_match = configuration_table[s->level].max_lazy; + s->good_match = configuration_table[s->level].good_length; + s->nice_match = configuration_table[s->level].nice_length; + s->max_chain_length = configuration_table[s->level].max_chain; + + s->strstart = 0; + s->block_start = 0L; + s->lookahead = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + s->ins_h = 0; +#ifdef ASMV + match_init(); /* initialize the asm code */ +#endif +} + +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +#ifndef ASMV +/* For 80x86 and 680x0, an optimized version will be provided in match.asm or + * match.S. The code will be functionally equivalent. + */ +#ifndef FASTEST +local uInt longest_match(s, cur_match) + deflate_state *s; + IPos cur_match; /* current match */ +{ + unsigned chain_length = s->max_chain_length;/* max hash chain length */ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + int best_len = s->prev_length; /* best match length so far */ + int nice_match = s->nice_match; /* stop if match long enough */ + IPos limit = s->strstart > (IPos)MAX_DIST(s) ? + s->strstart - (IPos)MAX_DIST(s) : NIL; + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + Posf *prev = s->prev; + uInt wmask = s->w_mask; + +#ifdef UNALIGNED_OK + /* Compare two bytes at a time. Note: this is not always beneficial. + * Try with and without -DUNALIGNED_OK to check. + */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; + register ush scan_start = *(ushf*)scan; + register ush scan_end = *(ushf*)(scan+best_len-1); +#else + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + register Byte scan_end1 = scan[best_len-1]; + register Byte scan_end = scan[best_len]; +#endif + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s->prev_length >= s->good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + Assert(cur_match < s->strstart, "no future"); + match = s->window + cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2: + */ +#if (defined(UNALIGNED_OK) && MAX_MATCH == 258) + /* This code assumes sizeof(unsigned short) == 2. Do not use + * UNALIGNED_OK if your compiler uses a different size. + */ + if (*(ushf*)(match+best_len-1) != scan_end || + *(ushf*)match != scan_start) continue; + + /* It is not necessary to compare scan[2] and match[2] since they are + * always equal when the other bytes match, given that the hash keys + * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at + * strstart+3, +5, ... up to strstart+257. We check for insufficient + * lookahead only every 4th comparison; the 128th check will be made + * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is + * necessary to put more guard bytes at the end of the window, or + * to check more often for insufficient lookahead. + */ + Assert(scan[2] == match[2], "scan[2]?"); + scan++, match++; + do { + } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + scan < strend); + /* The funny "do {}" generates better code on most compilers */ + + /* Here, scan <= window+strstart+257 */ + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + if (*scan == *match) scan++; + + len = (MAX_MATCH - 1) - (int)(strend-scan); + scan = strend - (MAX_MATCH-1); + +#else /* UNALIGNED_OK */ + + if (match[best_len] != scan_end || + match[best_len-1] != scan_end1 || + *match != *scan || + *++match != scan[1]) continue; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match++; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + +#endif /* UNALIGNED_OK */ + + if (len > best_len) { + s->match_start = cur_match; + best_len = len; + if (len >= nice_match) break; +#ifdef UNALIGNED_OK + scan_end = *(ushf*)(scan+best_len-1); +#else + scan_end1 = scan[best_len-1]; + scan_end = scan[best_len]; +#endif + } + } while ((cur_match = prev[cur_match & wmask]) > limit + && --chain_length != 0); + + if ((uInt)best_len <= s->lookahead) return (uInt)best_len; + return s->lookahead; +} + +#else /* FASTEST */ +/* --------------------------------------------------------------------------- + * Optimized version for level == 1 only + */ +local uInt longest_match(s, cur_match) + deflate_state *s; + IPos cur_match; /* current match */ +{ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + Assert(cur_match < s->strstart, "no future"); + + match = s->window + cur_match; + + /* Return failure if the match length is less than 2: + */ + if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match += 2; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + + if (len < MIN_MATCH) return MIN_MATCH - 1; + + s->match_start = cur_match; + return len <= s->lookahead ? len : s->lookahead; +} +#endif /* FASTEST */ +#endif /* ASMV */ + +#ifdef DEBUG +/* =========================================================================== + * Check that the match at match_start is indeed a match. + */ +local void check_match(s, start, match, length) + deflate_state *s; + IPos start, match; + int length; +{ + /* check that the match is indeed a match */ + if (zmemcmp(s->window + match, + s->window + start, length) != EQUAL) { + fprintf(stderr, " start %u, match %u, length %d\n", + start, match, length); + do { + fprintf(stderr, "%c%c", s->window[match++], s->window[start++]); + } while (--length != 0); + z_error("invalid match"); + } + if (z_verbose > 1) { + fprintf(stderr,"\\[%d,%d]", start-match, length); + do { putc(s->window[start++], stderr); } while (--length != 0); + } +} +#else +# define check_match(s, start, match, length) +#endif + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +local void fill_window(s) + deflate_state *s; +{ + register unsigned n, m; + register Posf *p; + unsigned more; /* Amount of free space at the end of the window. */ + uInt wsize = s->w_size; + + do { + more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); + + /* Deal with !@#$% 64K limit: */ + if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + more = wsize; + + } else if (more == (unsigned)(-1)) { + /* Very unlikely, but possible on 16 bit machine if strstart == 0 + * and lookahead == 1 (input done one byte at time) + */ + more--; + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + } else if (s->strstart >= wsize+MAX_DIST(s)) { + + zmemcpy(s->window, s->window+wsize, (unsigned)wsize); + s->match_start -= wsize; + s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ + s->block_start -= (long) wsize; + + /* Slide the hash table (could be avoided with 32 bit values + at the expense of memory usage). We slide even when level == 0 + to keep the hash table consistent if we switch back to level > 0 + later. (Using level 0 permanently is not an optimal usage of + zlib, so we don't care about this pathological case.) + */ + n = s->hash_size; + p = &s->head[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + } while (--n); + + n = wsize; +#ifndef FASTEST + p = &s->prev[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); +#endif + more += wsize; + } + if (s->strm->avail_in == 0) return; + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + Assert(more >= 2, "more < 2"); + + n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); + s->lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s->lookahead >= MIN_MATCH) { + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); +} + +/* =========================================================================== + * Flush the current block, with given end-of-file flag. + * IN assertion: strstart is set to the end of the current match. + */ +#define FLUSH_BLOCK_ONLY(s, eof) { \ + _tr_flush_block(s, (s->block_start >= 0L ? \ + (charf *)&s->window[(unsigned)s->block_start] : \ + (charf *)Z_NULL), \ + (ulg)((long)s->strstart - s->block_start), \ + (eof)); \ + s->block_start = s->strstart; \ + flush_pending(s->strm); \ + Tracev((stderr,"[FLUSH]")); \ +} + +/* Same but force premature exit if necessary. */ +#define FLUSH_BLOCK(s, eof) { \ + FLUSH_BLOCK_ONLY(s, eof); \ + if (s->strm->avail_out == 0) return (eof) ? finish_started : need_more; \ +} + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * This function does not insert new strings in the dictionary since + * uncompressible data is probably not useful. This function is used + * only for the level=0 compression option. + * NOTE: this function should be optimized to avoid extra copying from + * window to pending_buf. + */ +local block_state deflate_stored(s, flush) + deflate_state *s; + int flush; +{ + /* Stored blocks are limited to 0xffff bytes, pending_buf is limited + * to pending_buf_size, and each stored block has a 5 byte header: + */ + ulg max_block_size = 0xffff; + ulg max_start; + + if (max_block_size > s->pending_buf_size - 5) { + max_block_size = s->pending_buf_size - 5; + } + + /* Copy as much as possible from input to output: */ + for (;;) { + /* Fill the window as much as possible: */ + if (s->lookahead <= 1) { + + Assert(s->strstart < s->w_size+MAX_DIST(s) || + s->block_start >= (long)s->w_size, "slide too late"); + + fill_window(s); + if (s->lookahead == 0 && flush == Z_NO_FLUSH) return need_more; + + if (s->lookahead == 0) break; /* flush the current block */ + } + Assert(s->block_start >= 0L, "block gone"); + + s->strstart += s->lookahead; + s->lookahead = 0; + + /* Emit a stored block if pending_buf will be full: */ + max_start = s->block_start + max_block_size; + if (s->strstart == 0 || (ulg)s->strstart >= max_start) { + /* strstart == 0 is possible when wraparound on 16-bit machine */ + s->lookahead = (uInt)(s->strstart - max_start); + s->strstart = (uInt)max_start; + FLUSH_BLOCK(s, 0); + } + /* Flush if we may have to slide, otherwise block_start may become + * negative and the data will be gone: + */ + if (s->strstart - (uInt)s->block_start >= MAX_DIST(s)) { + FLUSH_BLOCK(s, 0); + } + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +local block_state deflate_fast(s, flush) + deflate_state *s; + int flush; +{ + IPos hash_head = NIL; /* head of the hash chain */ + int bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + if (s->strategy != Z_HUFFMAN_ONLY) { + s->match_length = longest_match (s, hash_head); + } + /* longest_match() sets match_start */ + } + if (s->match_length >= MIN_MATCH) { + check_match(s, s->strstart, s->match_start, s->match_length); + + _tr_tally_dist(s, s->strstart - s->match_start, + s->match_length - MIN_MATCH, bflush); + + s->lookahead -= s->match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ +#ifndef FASTEST + if (s->match_length <= s->max_insert_length && + s->lookahead >= MIN_MATCH) { + s->match_length--; /* string at strstart already in hash table */ + do { + s->strstart++; + INSERT_STRING(s, s->strstart, hash_head); + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s->match_length != 0); + s->strstart++; + } else +#endif + { + s->strstart += s->match_length; + s->match_length = 0; + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} + +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +local block_state deflate_slow(s, flush) + deflate_state *s; + int flush; +{ + IPos hash_head = NIL; /* head of hash chain */ + int bflush; /* set if current block must be flushed */ + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + */ + s->prev_length = s->match_length, s->prev_match = s->match_start; + s->match_length = MIN_MATCH-1; + + if (hash_head != NIL && s->prev_length < s->max_lazy_match && + s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + if (s->strategy != Z_HUFFMAN_ONLY) { + s->match_length = longest_match (s, hash_head); + } + /* longest_match() sets match_start */ + + if (s->match_length <= 5 && (s->strategy == Z_FILTERED || + (s->match_length == MIN_MATCH && + s->strstart - s->match_start > TOO_FAR))) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s->match_length = MIN_MATCH-1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) { + uInt max_insert = s->strstart + s->lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + check_match(s, s->strstart-1, s->prev_match, s->prev_length); + + _tr_tally_dist(s, s->strstart -1 - s->prev_match, + s->prev_length - MIN_MATCH, bflush); + + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s->lookahead -= s->prev_length-1; + s->prev_length -= 2; + do { + if (++s->strstart <= max_insert) { + INSERT_STRING(s, s->strstart, hash_head); + } + } while (--s->prev_length != 0); + s->match_available = 0; + s->match_length = MIN_MATCH-1; + s->strstart++; + + if (bflush) FLUSH_BLOCK(s, 0); + + } else if (s->match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + if (bflush) { + FLUSH_BLOCK_ONLY(s, 0); + } + s->strstart++; + s->lookahead--; + if (s->strm->avail_out == 0) return need_more; + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s->match_available = 1; + s->strstart++; + s->lookahead--; + } + } + Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s->match_available) { + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + s->match_available = 0; + } + FLUSH_BLOCK(s, flush == Z_FINISH); + return flush == Z_FINISH ? finish_done : block_done; +} diff --git a/code/zlib/deflate.h b/code/zlib/deflate.h new file mode 100644 index 0000000..80df091 --- /dev/null +++ b/code/zlib/deflate.h @@ -0,0 +1,318 @@ +/* deflate.h -- internal compression state + * Copyright (C) 1995-1998 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id: deflate.h,v 1.1 2003/03/19 19:08:52 osman Exp $ */ + +#ifndef _DEFLATE_H +#define _DEFLATE_H + +#include "zutil.h" + +/* =========================================================================== + * Internal compression state. + */ + +#define LENGTH_CODES 29 +/* number of length codes, not counting the special END_BLOCK code */ + +#define LITERALS 256 +/* number of literal bytes 0..255 */ + +#define L_CODES (LITERALS+1+LENGTH_CODES) +/* number of Literal or Length codes, including the END_BLOCK code */ + +#define D_CODES 30 +/* number of distance codes */ + +#define BL_CODES 19 +/* number of codes used to transfer the bit lengths */ + +#define HEAP_SIZE (2*L_CODES+1) +/* maximum heap size */ + +#define MAX_BITS 15 +/* All codes must not exceed MAX_BITS bits */ + +#define INIT_STATE 42 +#define BUSY_STATE 113 +#define FINISH_STATE 666 +/* Stream status */ + + +/* Data structure describing a single value and its code string. */ +typedef struct ct_data_s { + union { + ush freq; /* frequency count */ + ush code; /* bit string */ + } fc; + union { + ush dad; /* father node in Huffman tree */ + ush len; /* length of bit string */ + } dl; +} FAR ct_data; + +#define Freq fc.freq +#define Code fc.code +#define Dad dl.dad +#define Len dl.len + +typedef struct static_tree_desc_s static_tree_desc; + +typedef struct tree_desc_s { + ct_data *dyn_tree; /* the dynamic tree */ + int max_code; /* largest code with non zero frequency */ + static_tree_desc *stat_desc; /* the corresponding static tree */ +} FAR tree_desc; + +typedef ush Pos; +typedef Pos FAR Posf; +typedef unsigned IPos; + +/* A Pos is an index in the character window. We use short instead of int to + * save space in the various tables. IPos is used only for parameter passing. + */ + +typedef struct internal_state { + z_streamp strm; /* pointer back to this zlib stream */ + int status; /* as the name implies */ + Bytef *pending_buf; /* output still pending */ + ulg pending_buf_size; /* size of pending_buf */ + Bytef *pending_out; /* next pending byte to output to the stream */ + int pending; /* nb of bytes in the pending buffer */ + int noheader; /* suppress zlib header and adler32 */ + Byte data_type; /* UNKNOWN, BINARY or ASCII */ + Byte method; /* STORED (for zip only) or DEFLATED */ + int last_flush; /* value of flush param for previous deflate call */ + + /* used by deflate.c: */ + + uInt w_size; /* LZ77 window size (32K by default) */ + uInt w_bits; /* log2(w_size) (8..16) */ + uInt w_mask; /* w_size - 1 */ + + Bytef *window; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. Also, it limits + * the window size to 64K, which is quite useful on MSDOS. + * To do: use the user input buffer as sliding window. + */ + + ulg window_size; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + Posf *prev; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + Posf *head; /* Heads of the hash chains or NIL. */ + + uInt ins_h; /* hash index of string to be inserted */ + uInt hash_size; /* number of elements in hash table */ + uInt hash_bits; /* log2(hash_size) */ + uInt hash_mask; /* hash_size-1 */ + + uInt hash_shift; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + long block_start; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + uInt match_length; /* length of best match */ + IPos prev_match; /* previous match */ + int match_available; /* set if previous match exists */ + uInt strstart; /* start of string to insert */ + uInt match_start; /* start of matching string */ + uInt lookahead; /* number of valid bytes ahead in window */ + + uInt prev_length; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + uInt max_chain_length; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + uInt max_lazy_match; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ +# define max_insert_length max_lazy_match + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + int level; /* compression level (1..9) */ + int strategy; /* favor or force Huffman coding*/ + + uInt good_match; + /* Use a faster search when the previous match is longer than this */ + + int nice_match; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + /* Didn't use ct_data typedef below to supress compiler warning */ + struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + struct tree_desc_s l_desc; /* desc. for literal tree */ + struct tree_desc_s d_desc; /* desc. for distance tree */ + struct tree_desc_s bl_desc; /* desc. for bit length tree */ + + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + int heap_len; /* number of elements in the heap */ + int heap_max; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + uch depth[2*L_CODES+1]; + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + uchf *l_buf; /* buffer for literals or lengths */ + + uInt lit_bufsize; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + uInt last_lit; /* running index in l_buf */ + + ushf *d_buf; + /* Buffer for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + ulg opt_len; /* bit length of current block with optimal trees */ + ulg static_len; /* bit length of current block with static trees */ + uInt matches; /* number of string matches in current block */ + int last_eob_len; /* bit length of EOB code for last block */ + +#ifdef DEBUG + ulg compressed_len; /* total bit length of compressed file mod 2^32 */ + ulg bits_sent; /* bit length of compressed data sent mod 2^32 */ +#endif + + ush bi_buf; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + int bi_valid; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + +} FAR deflate_state; + +/* Output a byte on the stream. + * IN assertion: there is enough room in pending_buf. + */ +#define put_byte(s, c) {s->pending_buf[s->pending++] = (c);} + + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +#define MAX_DIST(s) ((s)->w_size-MIN_LOOKAHEAD) +/* In order to simplify the code, particularly on 16 bit machines, match + * distances are limited to MAX_DIST instead of WSIZE. + */ + + /* in trees.c */ +void _tr_init OF((deflate_state *s)); +int _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); +void _tr_flush_block OF((deflate_state *s, charf *buf, ulg stored_len, + int eof)); +void _tr_align OF((deflate_state *s)); +void _tr_stored_block OF((deflate_state *s, charf *buf, ulg stored_len, + int eof)); + +#define d_code(dist) \ + ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) +/* Mapping from a distance to a distance code. dist is the distance - 1 and + * must not have side effects. _dist_code[256] and _dist_code[257] are never + * used. + */ + +#ifndef DEBUG +/* Inline versions of _tr_tally for speed: */ + +#if defined(GEN_TREES_H) || !defined(STDC) + extern uch _length_code[]; + extern uch _dist_code[]; +#else + extern const uch _length_code[]; + extern const uch _dist_code[]; +#endif + +# define _tr_tally_lit(s, c, flush) \ + { uch cc = (c); \ + s->d_buf[s->last_lit] = 0; \ + s->l_buf[s->last_lit++] = cc; \ + s->dyn_ltree[cc].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +# define _tr_tally_dist(s, distance, length, flush) \ + { uch len = (length); \ + ush dist = (distance); \ + s->d_buf[s->last_lit] = dist; \ + s->l_buf[s->last_lit++] = len; \ + dist--; \ + s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ + s->dyn_dtree[d_code(dist)].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +#else +# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) +# define _tr_tally_dist(s, distance, length, flush) \ + flush = _tr_tally(s, distance, length) +#endif + +#endif diff --git a/code/zlib/infblock.c b/code/zlib/infblock.c new file mode 100644 index 0000000..ebfe311 --- /dev/null +++ b/code/zlib/infblock.c @@ -0,0 +1,401 @@ +/* infblock.c -- interpret and process block types to last block + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zutil.h" +#include "infblock.h" +#include "inftrees.h" +#include "infcodes.h" +#include "infutil.h" + +struct inflate_codes_state {int dummy;}; /* for buggy compilers */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +/* Table for deflate from PKZIP's appnote.txt. */ +local const uInt border[] = { /* Order of the bit length code lengths */ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* + Notes beyond the 1.93a appnote.txt: + + 1. Distance pointers never point before the beginning of the output + stream. + 2. Distance pointers can point back across blocks, up to 32k away. + 3. There is an implied maximum of 7 bits for the bit length table and + 15 bits for the actual data. + 4. If only one code exists, then it is encoded using one bit. (Zero + would be more efficient, but perhaps a little confusing.) If two + codes exist, they are coded using one bit each (0 and 1). + 5. There is no way of sending zero distance codes--a dummy must be + sent if there are none. (History: a pre 2.0 version of PKZIP would + store blocks with no distance codes, but this was discovered to be + too harsh a criterion.) Valid only for 1.93a. 2.04c does allow + zero distance codes, which is sent as one code of zero bits in + length. + 6. There are up to 286 literal/length codes. Code 256 represents the + end-of-block. Note however that the static length tree defines + 288 codes just to fill out the Huffman codes. Codes 286 and 287 + cannot be used though, since there is no length base or extra bits + defined for them. Similarily, there are up to 30 distance codes. + However, static trees define 32 codes (all 5 bits) to fill out the + Huffman codes, but the last two had better not show up in the data. + 7. Unzip can check dynamic Huffman blocks for complete code sets. + The exception is that a single code would not be complete (see #4). + 8. The five bits following the block type is really the number of + literal codes sent minus 257. + 9. Length codes 8,16,16 are interpreted as 13 length codes of 8 bits + (1+6+6). Therefore, to output three times the length, you output + three codes (1+1+1), whereas to output four times the same length, + you only need two codes (1+3). Hmm. + 10. In the tree reconstruction algorithm, Code = Code + Increment + only if BitLength(i) is not zero. (Pretty obvious.) + 11. Correction: 4 Bits: # of Bit Length codes - 4 (4 - 19) + 12. Note: length code 284 can represent 227-258, but length code 285 + really is 258. The last length deserves its own, short code + since it gets used a lot in very redundant files. The length + 258 is special since 258 - 3 (the min match length) is 255. + 13. The literal/length and distance code bit lengths are read as a + single stream of lengths. It is possible (and advantageous) for + a repeat code (16, 17, or 18) to go across the boundary between + the two sets of lengths. + */ + + +void inflate_blocks_reset(s, z, c) +inflate_blocks_statef *s; +z_streamp z; +uLongf *c; +{ + if (c != Z_NULL) + *c = s->check; + if (s->mode == BTREE || s->mode == DTREE) + ZFREE(z, s->sub.trees.blens); + if (s->mode == CODES) + inflate_codes_free(s->sub.decode.codes, z); + s->mode = TYPE; + s->bitk = 0; + s->bitb = 0; + s->read = s->write = s->window; + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(0L, (const Bytef *)Z_NULL, 0); + Tracev((stderr, "inflate: blocks reset\n")); +} + + +inflate_blocks_statef *inflate_blocks_new(z, c, w) +z_streamp z; +check_func c; +uInt w; +{ + inflate_blocks_statef *s; + + if ((s = (inflate_blocks_statef *)ZALLOC + (z,1,sizeof(struct inflate_blocks_state))) == Z_NULL) + return s; + if ((s->hufts = + (inflate_huft *)ZALLOC(z, sizeof(inflate_huft), MANY)) == Z_NULL) + { + ZFREE(z, s); + return Z_NULL; + } + if ((s->window = (Bytef *)ZALLOC(z, 1, w)) == Z_NULL) + { + ZFREE(z, s->hufts); + ZFREE(z, s); + return Z_NULL; + } + s->end = s->window + w; + s->checkfn = c; + s->mode = TYPE; + Tracev((stderr, "inflate: blocks allocated\n")); + inflate_blocks_reset(s, z, Z_NULL); + return s; +} + + +int inflate_blocks(s, z, r) +inflate_blocks_statef *s; +z_streamp z; +int r; +{ + uInt t; /* temporary storage */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Bytef *p; /* input data pointer */ + uInt n; /* bytes available there */ + Bytef *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + + /* copy input/output information to locals (UPDATE macro restores) */ + LOAD + + /* process input based on current state */ + while (1) switch (s->mode) + { + case TYPE: + NEEDBITS(3) + t = (uInt)b & 7; + s->last = t & 1; + switch (t >> 1) + { + case 0: /* stored */ + Tracev((stderr, "inflate: stored block%s\n", + s->last ? " (last)" : "")); + DUMPBITS(3) + t = k & 7; /* go to byte boundary */ + DUMPBITS(t) + s->mode = LENS; /* get length of stored block */ + break; + case 1: /* fixed */ + Tracev((stderr, "inflate: fixed codes block%s\n", + s->last ? " (last)" : "")); + { + uInt bl, bd; + inflate_huft *tl, *td; + + inflate_trees_fixed(&bl, &bd, &tl, &td, z); + s->sub.decode.codes = inflate_codes_new(bl, bd, tl, td, z); + if (s->sub.decode.codes == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + } + DUMPBITS(3) + s->mode = CODES; + break; + case 2: /* dynamic */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + s->last ? " (last)" : "")); + DUMPBITS(3) + s->mode = TABLE; + break; + case 3: /* illegal */ + DUMPBITS(3) + s->mode = BAD; + z->msg = (char*)"invalid block type"; + r = Z_DATA_ERROR; + LEAVE + } + break; + case LENS: + NEEDBITS(32) + if ((((~b) >> 16) & 0xffff) != (b & 0xffff)) + { + s->mode = BAD; + z->msg = (char*)"invalid stored block lengths"; + r = Z_DATA_ERROR; + LEAVE + } + s->sub.left = (uInt)b & 0xffff; + b = k = 0; /* dump bits */ + Tracev((stderr, "inflate: stored length %u\n", s->sub.left)); + s->mode = s->sub.left ? STORED : (s->last ? DRY : TYPE); + break; + case STORED: + if (n == 0) + LEAVE + NEEDOUT + t = s->sub.left; + if (t > n) t = n; + if (t > m) t = m; + zmemcpy(q, p, t); + p += t; n -= t; + q += t; m -= t; + if ((s->sub.left -= t) != 0) + break; + Tracev((stderr, "inflate: stored end, %lu total out\n", + z->total_out + (q >= s->read ? q - s->read : + (s->end - s->read) + (q - s->window)))); + s->mode = s->last ? DRY : TYPE; + break; + case TABLE: + NEEDBITS(14) + s->sub.trees.table = t = (uInt)b & 0x3fff; +#ifndef PKZIP_BUG_WORKAROUND + if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) + { + s->mode = BAD; + z->msg = (char*)"too many length or distance symbols"; + r = Z_DATA_ERROR; + LEAVE + } +#endif + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + if ((s->sub.trees.blens = (uIntf*)ZALLOC(z, t, sizeof(uInt))) == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + DUMPBITS(14) + s->sub.trees.index = 0; + Tracev((stderr, "inflate: table sizes ok\n")); + s->mode = BTREE; + case BTREE: + while (s->sub.trees.index < 4 + (s->sub.trees.table >> 10)) + { + NEEDBITS(3) + s->sub.trees.blens[border[s->sub.trees.index++]] = (uInt)b & 7; + DUMPBITS(3) + } + while (s->sub.trees.index < 19) + s->sub.trees.blens[border[s->sub.trees.index++]] = 0; + s->sub.trees.bb = 7; + t = inflate_trees_bits(s->sub.trees.blens, &s->sub.trees.bb, + &s->sub.trees.tb, s->hufts, z); + if (t != Z_OK) + { + ZFREE(z, s->sub.trees.blens); + r = t; + if (r == Z_DATA_ERROR) + s->mode = BAD; + LEAVE + } + s->sub.trees.index = 0; + Tracev((stderr, "inflate: bits tree ok\n")); + s->mode = DTREE; + case DTREE: + while (t = s->sub.trees.table, + s->sub.trees.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f)) + { + inflate_huft *h; + uInt i, j, c; + + t = s->sub.trees.bb; + NEEDBITS(t) + h = s->sub.trees.tb + ((uInt)b & inflate_mask[t]); + t = h->bits; + c = h->base; + if (c < 16) + { + DUMPBITS(t) + s->sub.trees.blens[s->sub.trees.index++] = c; + } + else /* c == 16..18 */ + { + i = c == 18 ? 7 : c - 14; + j = c == 18 ? 11 : 3; + NEEDBITS(t + i) + DUMPBITS(t) + j += (uInt)b & inflate_mask[i]; + DUMPBITS(i) + i = s->sub.trees.index; + t = s->sub.trees.table; + if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || + (c == 16 && i < 1)) + { + ZFREE(z, s->sub.trees.blens); + s->mode = BAD; + z->msg = (char*)"invalid bit length repeat"; + r = Z_DATA_ERROR; + LEAVE + } + c = c == 16 ? s->sub.trees.blens[i - 1] : 0; + do { + s->sub.trees.blens[i++] = c; + } while (--j); + s->sub.trees.index = i; + } + } + s->sub.trees.tb = Z_NULL; + { + uInt bl, bd; + inflate_huft *tl, *td; + inflate_codes_statef *c; + + bl = 9; /* must be <= 9 for lookahead assumptions */ + bd = 6; /* must be <= 9 for lookahead assumptions */ + t = s->sub.trees.table; + t = inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), + s->sub.trees.blens, &bl, &bd, &tl, &td, + s->hufts, z); + ZFREE(z, s->sub.trees.blens); + if (t != Z_OK) + { + if (t == (uInt)Z_DATA_ERROR) + s->mode = BAD; + r = t; + LEAVE + } + Tracev((stderr, "inflate: trees ok\n")); + if ((c = inflate_codes_new(bl, bd, tl, td, z)) == Z_NULL) + { + r = Z_MEM_ERROR; + LEAVE + } + s->sub.decode.codes = c; + } + s->mode = CODES; + case CODES: + UPDATE + if ((r = inflate_codes(s, z, r)) != Z_STREAM_END) + return inflate_flush(s, z, r); + r = Z_OK; + inflate_codes_free(s->sub.decode.codes, z); + LOAD + Tracev((stderr, "inflate: codes end, %lu total out\n", + z->total_out + (q >= s->read ? q - s->read : + (s->end - s->read) + (q - s->window)))); + if (!s->last) + { + s->mode = TYPE; + break; + } + s->mode = DRY; + case DRY: + FLUSH + if (s->read != s->write) + LEAVE + s->mode = DONE; + case DONE: + r = Z_STREAM_END; + LEAVE + case BAD: + r = Z_DATA_ERROR; + LEAVE + default: + r = Z_STREAM_ERROR; + LEAVE + } +} + + +int inflate_blocks_free(s, z) +inflate_blocks_statef *s; +z_streamp z; +{ + inflate_blocks_reset(s, z, Z_NULL); + ZFREE(z, s->window); + ZFREE(z, s->hufts); + ZFREE(z, s); + Tracev((stderr, "inflate: blocks freed\n")); + return Z_OK; +} + + +void inflate_set_dictionary(s, d, n) +inflate_blocks_statef *s; +const Bytef *d; +uInt n; +{ + zmemcpy(s->window, d, n); + s->read = s->write = s->window + n; +} + + +/* Returns true if inflate is currently at the end of a block generated + * by Z_SYNC_FLUSH or Z_FULL_FLUSH. + * IN assertion: s != Z_NULL + */ +int inflate_blocks_sync_point(s) +inflate_blocks_statef *s; +{ + return s->mode == LENS; +} diff --git a/code/zlib/infblock.h b/code/zlib/infblock.h new file mode 100644 index 0000000..bd25c80 --- /dev/null +++ b/code/zlib/infblock.h @@ -0,0 +1,39 @@ +/* infblock.h -- header to use infblock.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +struct inflate_blocks_state; +typedef struct inflate_blocks_state FAR inflate_blocks_statef; + +extern inflate_blocks_statef * inflate_blocks_new OF(( + z_streamp z, + check_func c, /* check function */ + uInt w)); /* window size */ + +extern int inflate_blocks OF(( + inflate_blocks_statef *, + z_streamp , + int)); /* initial return code */ + +extern void inflate_blocks_reset OF(( + inflate_blocks_statef *, + z_streamp , + uLongf *)); /* check value on output */ + +extern int inflate_blocks_free OF(( + inflate_blocks_statef *, + z_streamp)); + +extern void inflate_set_dictionary OF(( + inflate_blocks_statef *s, + const Bytef *d, /* dictionary */ + uInt n)); /* dictionary length */ + +extern int inflate_blocks_sync_point OF(( + inflate_blocks_statef *s)); diff --git a/code/zlib/infcodes.c b/code/zlib/infcodes.c new file mode 100644 index 0000000..5967b0d --- /dev/null +++ b/code/zlib/infcodes.c @@ -0,0 +1,260 @@ +/* infcodes.c -- process literals and length/distance pairs + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zutil.h" +#include "inftrees.h" +#include "infblock.h" +#include "infcodes.h" +#include "infutil.h" +#include "inffast.h" + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +typedef enum { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ + START, /* x: set up for LEN */ + LEN, /* i: get length/literal/eob next */ + LENEXT, /* i: getting length extra (have base) */ + DIST, /* i: get distance next */ + DISTEXT, /* i: getting distance extra */ + COPY, /* o: copying bytes in window, waiting for space */ + LIT, /* o: got literal, waiting for output space */ + WASH, /* o: got eob, possibly still output waiting */ + END, /* x: got eob and all data flushed */ + BADCODE} /* x: got error */ +inflate_codes_mode; + +/* inflate codes private state */ +struct inflate_codes_state { + + /* mode */ + inflate_codes_mode mode; /* current inflate_codes mode */ + + /* mode dependent information */ + uInt len; + union { + struct { + inflate_huft *tree; /* pointer into tree */ + uInt need; /* bits needed */ + } code; /* if LEN or DIST, where in tree */ + uInt lit; /* if LIT, literal */ + struct { + uInt get; /* bits to get for extra */ + uInt dist; /* distance back to copy from */ + } copy; /* if EXT or COPY, where and how much */ + } sub; /* submode */ + + /* mode independent information */ + Byte lbits; /* ltree bits decoded per branch */ + Byte dbits; /* dtree bits decoder per branch */ + inflate_huft *ltree; /* literal/length/eob tree */ + inflate_huft *dtree; /* distance tree */ + +}; + + +inflate_codes_statef *inflate_codes_new(bl, bd, tl, td, z) +uInt bl, bd; +inflate_huft *tl; +inflate_huft *td; /* need separate declaration for Borland C++ */ +z_streamp z; +{ + inflate_codes_statef *c; + + if ((c = (inflate_codes_statef *) + ZALLOC(z,1,sizeof(struct inflate_codes_state))) != Z_NULL) + { + c->mode = START; + c->lbits = (Byte)bl; + c->dbits = (Byte)bd; + c->ltree = tl; + c->dtree = td; + Tracev((stderr, "inflate: codes new\n")); + } + return c; +} + + +int inflate_codes(s, z, r) +inflate_blocks_statef *s; +z_streamp z; +int r; +{ + uInt j; /* temporary storage */ + inflate_huft *t; /* temporary pointer */ + uInt e; /* extra bits or operation */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Bytef *p; /* input data pointer */ + uInt n; /* bytes available there */ + Bytef *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + Bytef *f; /* pointer to copy strings from */ + inflate_codes_statef *c = s->sub.decode.codes; /* codes state */ + + /* copy input/output information to locals (UPDATE macro restores) */ + LOAD + + /* process input and output based on current state */ + while (1) switch (c->mode) + { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ + case START: /* x: set up for LEN */ +#ifndef SLOW + if (m >= 258 && n >= 10) + { + UPDATE + r = inflate_fast(c->lbits, c->dbits, c->ltree, c->dtree, s, z); + LOAD + if (r != Z_OK) + { + c->mode = r == Z_STREAM_END ? WASH : BADCODE; + break; + } + } +#endif /* !SLOW */ + c->sub.code.need = c->lbits; + c->sub.code.tree = c->ltree; + c->mode = LEN; + case LEN: /* i: get length/literal/eob next */ + j = c->sub.code.need; + NEEDBITS(j) + t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); + DUMPBITS(t->bits) + e = (uInt)(t->exop); + if (e == 0) /* literal */ + { + c->sub.lit = t->base; + Tracevv((stderr, t->base >= 0x20 && t->base < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", t->base)); + c->mode = LIT; + break; + } + if (e & 16) /* length */ + { + c->sub.copy.get = e & 15; + c->len = t->base; + c->mode = LENEXT; + break; + } + if ((e & 64) == 0) /* next table */ + { + c->sub.code.need = e; + c->sub.code.tree = t + t->base; + break; + } + if (e & 32) /* end of block */ + { + Tracevv((stderr, "inflate: end of block\n")); + c->mode = WASH; + break; + } + c->mode = BADCODE; /* invalid code */ + z->msg = (char*)"invalid literal/length code"; + r = Z_DATA_ERROR; + LEAVE + case LENEXT: /* i: getting length extra (have base) */ + j = c->sub.copy.get; + NEEDBITS(j) + c->len += (uInt)b & inflate_mask[j]; + DUMPBITS(j) + c->sub.code.need = c->dbits; + c->sub.code.tree = c->dtree; + Tracevv((stderr, "inflate: length %u\n", c->len)); + c->mode = DIST; + case DIST: /* i: get distance next */ + j = c->sub.code.need; + NEEDBITS(j) + t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); + DUMPBITS(t->bits) + e = (uInt)(t->exop); + if (e & 16) /* distance */ + { + c->sub.copy.get = e & 15; + c->sub.copy.dist = t->base; + c->mode = DISTEXT; + break; + } + if ((e & 64) == 0) /* next table */ + { + c->sub.code.need = e; + c->sub.code.tree = t + t->base; + break; + } + c->mode = BADCODE; /* invalid code */ + z->msg = (char*)"invalid distance code"; + r = Z_DATA_ERROR; + LEAVE + case DISTEXT: /* i: getting distance extra */ + j = c->sub.copy.get; + NEEDBITS(j) + c->sub.copy.dist += (uInt)b & inflate_mask[j]; + DUMPBITS(j) + Tracevv((stderr, "inflate: distance %u\n", c->sub.copy.dist)); + c->mode = COPY; + case COPY: /* o: copying bytes in window, waiting for space */ +#ifndef __TURBOC__ /* Turbo C bug for following expression */ + f = (uInt)(q - s->window) < c->sub.copy.dist ? + s->end - (c->sub.copy.dist - (q - s->window)) : + q - c->sub.copy.dist; +#else + f = q - c->sub.copy.dist; + if ((uInt)(q - s->window) < c->sub.copy.dist) + f = s->end - (c->sub.copy.dist - (uInt)(q - s->window)); +#endif + while (c->len) + { + NEEDOUT + OUTBYTE(*f++) + if (f == s->end) + f = s->window; + c->len--; + } + c->mode = START; + break; + case LIT: /* o: got literal, waiting for output space */ + NEEDOUT + OUTBYTE(c->sub.lit) + c->mode = START; + break; + case WASH: /* o: got eob, possibly more output */ + if (k > 7) /* return unused byte, if any */ + { + Assert(k < 16, "inflate_codes grabbed too many bytes") + k -= 8; + n++; + p--; /* can always return one */ + } + FLUSH + if (s->read != s->write) + LEAVE + c->mode = END; + case END: + r = Z_STREAM_END; + LEAVE + case BADCODE: /* x: got error */ + r = Z_DATA_ERROR; + LEAVE + default: + r = Z_STREAM_ERROR; + LEAVE + } +#ifdef NEED_DUMMY_RETURN + return Z_STREAM_ERROR; /* Some dumb compilers complain without this */ +#endif +} + + +void inflate_codes_free(c, z) +inflate_codes_statef *c; +z_streamp z; +{ + ZFREE(z, c); + Tracev((stderr, "inflate: codes free\n")); +} diff --git a/code/zlib/infcodes.h b/code/zlib/infcodes.h new file mode 100644 index 0000000..6c750d8 --- /dev/null +++ b/code/zlib/infcodes.h @@ -0,0 +1,27 @@ +/* infcodes.h -- header to use infcodes.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +struct inflate_codes_state; +typedef struct inflate_codes_state FAR inflate_codes_statef; + +extern inflate_codes_statef *inflate_codes_new OF(( + uInt, uInt, + inflate_huft *, inflate_huft *, + z_streamp )); + +extern int inflate_codes OF(( + inflate_blocks_statef *, + z_streamp , + int)); + +extern void inflate_codes_free OF(( + inflate_codes_statef *, + z_streamp )); + diff --git a/code/zlib/inffast.c b/code/zlib/inffast.c new file mode 100644 index 0000000..848fa71 --- /dev/null +++ b/code/zlib/inffast.c @@ -0,0 +1,173 @@ +/* inffast.c -- process literals and length/distance pairs fast + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zutil.h" +#include "inftrees.h" +#include "infblock.h" +#include "infcodes.h" +#include "infutil.h" +#include "inffast.h" + +struct inflate_codes_state {int dummy;}; /* for buggy compilers */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + +/* macros for bit input with no checking and for returning unused bytes */ +#define GRABBITS(j) {while(k<(j)){b|=((uLong)NEXTBYTE)<avail_in-n;c=(k>>3)>3:c;n+=c;p-=c;k-=c<<3;} + +/* Called with number of bytes left to write in window at least 258 + (the maximum string length) and number of input bytes available + at least ten. The ten bytes are six bytes for the longest length/ + distance pair plus four bytes for overloading the bit buffer. */ + +int inflate_fast(bl, bd, tl, td, s, z) +uInt bl, bd; +inflate_huft *tl; +inflate_huft *td; /* need separate declaration for Borland C++ */ +inflate_blocks_statef *s; +z_streamp z; +{ + inflate_huft *t; /* temporary pointer */ + uInt e; /* extra bits or operation */ + uLong b; /* bit buffer */ + uInt k; /* bits in bit buffer */ + Bytef *p; /* input data pointer */ + uInt n; /* bytes available there */ + Bytef *q; /* output window write pointer */ + uInt m; /* bytes to end of window or read pointer */ + uInt ml; /* mask for literal/length tree */ + uInt md; /* mask for distance tree */ + uInt c; /* bytes to copy */ + uInt d; /* distance back to copy from */ + Bytef *r; /* copy source pointer */ + + /* load input, output, bit values */ + LOAD + + /* initialize masks */ + ml = inflate_mask[bl]; + md = inflate_mask[bd]; + + /* do until not enough input or output space for fast loop */ + do { /* assume called with m >= 258 && n >= 10 */ + /* get literal/length code */ + GRABBITS(20) /* max bits for literal/length code */ + if ((e = (t = tl + ((uInt)b & ml))->exop) == 0) + { + DUMPBITS(t->bits) + Tracevv((stderr, t->base >= 0x20 && t->base < 0x7f ? + "inflate: * literal '%c'\n" : + "inflate: * literal 0x%02x\n", t->base)); + *q++ = (Byte)t->base; + m--; + continue; + } + do { + DUMPBITS(t->bits) + if (e & 16) + { + /* get extra bits for length */ + e &= 15; + c = t->base + ((uInt)b & inflate_mask[e]); + DUMPBITS(e) + Tracevv((stderr, "inflate: * length %u\n", c)); + + /* decode distance base of block to copy */ + GRABBITS(15); /* max bits for distance code */ + e = (t = td + ((uInt)b & md))->exop; + do { + DUMPBITS(t->bits) + if (e & 16) + { + /* get extra bits to add to distance base */ + e &= 15; + GRABBITS(e) /* get extra bits (up to 13) */ + d = t->base + ((uInt)b & inflate_mask[e]); + DUMPBITS(e) + Tracevv((stderr, "inflate: * distance %u\n", d)); + + /* do the copy */ + m -= c; + if ((uInt)(q - s->window) >= d) /* offset before dest */ + { /* just copy */ + r = q - d; + *q++ = *r++; c--; /* minimum count is three, */ + *q++ = *r++; c--; /* so unroll loop a little */ + } + else /* else offset after destination */ + { + e = d - (uInt)(q - s->window); /* bytes from offset to end */ + r = s->end - e; /* pointer to offset */ + if (c > e) /* if source crosses, */ + { + c -= e; /* copy to end of window */ + do { + *q++ = *r++; + } while (--e); + r = s->window; /* copy rest from start of window */ + } + } + do { /* copy all or what's left */ + *q++ = *r++; + } while (--c); + break; + } + else if ((e & 64) == 0) + { + t += t->base; + e = (t += ((uInt)b & inflate_mask[e]))->exop; + } + else + { + z->msg = (char*)"invalid distance code"; + UNGRAB + UPDATE + return Z_DATA_ERROR; + } + } while (1); + break; + } + if ((e & 64) == 0) + { + t += t->base; + if ((e = (t += ((uInt)b & inflate_mask[e]))->exop) == 0) + { + DUMPBITS(t->bits) + Tracevv((stderr, t->base >= 0x20 && t->base < 0x7f ? + "inflate: * literal '%c'\n" : + "inflate: * literal 0x%02x\n", t->base)); + *q++ = (Byte)t->base; + m--; + break; + } + } + else if (e & 32) + { + Tracevv((stderr, "inflate: * end of block\n")); + UNGRAB + UPDATE + return Z_STREAM_END; + } + else + { + z->msg = (char*)"invalid literal/length code"; + UNGRAB + UPDATE + return Z_DATA_ERROR; + } + } while (1); + } while (m >= 258 && n >= 10); + + /* not enough input or output--restore pointers and return */ + UNGRAB + UPDATE + return Z_OK; +} diff --git a/code/zlib/inffast.h b/code/zlib/inffast.h new file mode 100644 index 0000000..8facec5 --- /dev/null +++ b/code/zlib/inffast.h @@ -0,0 +1,17 @@ +/* inffast.h -- header to use inffast.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +extern int inflate_fast OF(( + uInt, + uInt, + inflate_huft *, + inflate_huft *, + inflate_blocks_statef *, + z_streamp )); diff --git a/code/zlib/inffixed.h b/code/zlib/inffixed.h new file mode 100644 index 0000000..77f7e76 --- /dev/null +++ b/code/zlib/inffixed.h @@ -0,0 +1,151 @@ +/* inffixed.h -- table for decoding fixed codes + * Generated automatically by the maketree.c program + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +local uInt fixed_bl = 9; +local uInt fixed_bd = 5; +local inflate_huft fixed_tl[] = { + {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, + {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},192}, + {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},160}, + {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},224}, + {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},144}, + {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},208}, + {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},176}, + {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},240}, + {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, + {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},200}, + {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},168}, + {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},232}, + {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},152}, + {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},216}, + {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},184}, + {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},248}, + {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, + {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},196}, + {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},164}, + {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},228}, + {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},148}, + {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},212}, + {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},180}, + {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},244}, + {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},204}, + {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},172}, + {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},236}, + {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},156}, + {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},220}, + {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},188}, + {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},252}, + {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, + {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},194}, + {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},162}, + {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},226}, + {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},146}, + {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},210}, + {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},178}, + {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},242}, + {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, + {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},202}, + {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},170}, + {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},234}, + {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},154}, + {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},218}, + {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},186}, + {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},250}, + {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, + {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},198}, + {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},166}, + {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},230}, + {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},150}, + {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},214}, + {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},182}, + {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},246}, + {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},206}, + {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},174}, + {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},238}, + {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},158}, + {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},222}, + {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},190}, + {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},254}, + {{{96,7}},256}, {{{0,8}},80}, {{{0,8}},16}, {{{84,8}},115}, + {{{82,7}},31}, {{{0,8}},112}, {{{0,8}},48}, {{{0,9}},193}, + {{{80,7}},10}, {{{0,8}},96}, {{{0,8}},32}, {{{0,9}},161}, + {{{0,8}},0}, {{{0,8}},128}, {{{0,8}},64}, {{{0,9}},225}, + {{{80,7}},6}, {{{0,8}},88}, {{{0,8}},24}, {{{0,9}},145}, + {{{83,7}},59}, {{{0,8}},120}, {{{0,8}},56}, {{{0,9}},209}, + {{{81,7}},17}, {{{0,8}},104}, {{{0,8}},40}, {{{0,9}},177}, + {{{0,8}},8}, {{{0,8}},136}, {{{0,8}},72}, {{{0,9}},241}, + {{{80,7}},4}, {{{0,8}},84}, {{{0,8}},20}, {{{85,8}},227}, + {{{83,7}},43}, {{{0,8}},116}, {{{0,8}},52}, {{{0,9}},201}, + {{{81,7}},13}, {{{0,8}},100}, {{{0,8}},36}, {{{0,9}},169}, + {{{0,8}},4}, {{{0,8}},132}, {{{0,8}},68}, {{{0,9}},233}, + {{{80,7}},8}, {{{0,8}},92}, {{{0,8}},28}, {{{0,9}},153}, + {{{84,7}},83}, {{{0,8}},124}, {{{0,8}},60}, {{{0,9}},217}, + {{{82,7}},23}, {{{0,8}},108}, {{{0,8}},44}, {{{0,9}},185}, + {{{0,8}},12}, {{{0,8}},140}, {{{0,8}},76}, {{{0,9}},249}, + {{{80,7}},3}, {{{0,8}},82}, {{{0,8}},18}, {{{85,8}},163}, + {{{83,7}},35}, {{{0,8}},114}, {{{0,8}},50}, {{{0,9}},197}, + {{{81,7}},11}, {{{0,8}},98}, {{{0,8}},34}, {{{0,9}},165}, + {{{0,8}},2}, {{{0,8}},130}, {{{0,8}},66}, {{{0,9}},229}, + {{{80,7}},7}, {{{0,8}},90}, {{{0,8}},26}, {{{0,9}},149}, + {{{84,7}},67}, {{{0,8}},122}, {{{0,8}},58}, {{{0,9}},213}, + {{{82,7}},19}, {{{0,8}},106}, {{{0,8}},42}, {{{0,9}},181}, + {{{0,8}},10}, {{{0,8}},138}, {{{0,8}},74}, {{{0,9}},245}, + {{{80,7}},5}, {{{0,8}},86}, {{{0,8}},22}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},118}, {{{0,8}},54}, {{{0,9}},205}, + {{{81,7}},15}, {{{0,8}},102}, {{{0,8}},38}, {{{0,9}},173}, + {{{0,8}},6}, {{{0,8}},134}, {{{0,8}},70}, {{{0,9}},237}, + {{{80,7}},9}, {{{0,8}},94}, {{{0,8}},30}, {{{0,9}},157}, + {{{84,7}},99}, {{{0,8}},126}, {{{0,8}},62}, {{{0,9}},221}, + {{{82,7}},27}, {{{0,8}},110}, {{{0,8}},46}, {{{0,9}},189}, + {{{0,8}},14}, {{{0,8}},142}, {{{0,8}},78}, {{{0,9}},253}, + {{{96,7}},256}, {{{0,8}},81}, {{{0,8}},17}, {{{85,8}},131}, + {{{82,7}},31}, {{{0,8}},113}, {{{0,8}},49}, {{{0,9}},195}, + {{{80,7}},10}, {{{0,8}},97}, {{{0,8}},33}, {{{0,9}},163}, + {{{0,8}},1}, {{{0,8}},129}, {{{0,8}},65}, {{{0,9}},227}, + {{{80,7}},6}, {{{0,8}},89}, {{{0,8}},25}, {{{0,9}},147}, + {{{83,7}},59}, {{{0,8}},121}, {{{0,8}},57}, {{{0,9}},211}, + {{{81,7}},17}, {{{0,8}},105}, {{{0,8}},41}, {{{0,9}},179}, + {{{0,8}},9}, {{{0,8}},137}, {{{0,8}},73}, {{{0,9}},243}, + {{{80,7}},4}, {{{0,8}},85}, {{{0,8}},21}, {{{80,8}},258}, + {{{83,7}},43}, {{{0,8}},117}, {{{0,8}},53}, {{{0,9}},203}, + {{{81,7}},13}, {{{0,8}},101}, {{{0,8}},37}, {{{0,9}},171}, + {{{0,8}},5}, {{{0,8}},133}, {{{0,8}},69}, {{{0,9}},235}, + {{{80,7}},8}, {{{0,8}},93}, {{{0,8}},29}, {{{0,9}},155}, + {{{84,7}},83}, {{{0,8}},125}, {{{0,8}},61}, {{{0,9}},219}, + {{{82,7}},23}, {{{0,8}},109}, {{{0,8}},45}, {{{0,9}},187}, + {{{0,8}},13}, {{{0,8}},141}, {{{0,8}},77}, {{{0,9}},251}, + {{{80,7}},3}, {{{0,8}},83}, {{{0,8}},19}, {{{85,8}},195}, + {{{83,7}},35}, {{{0,8}},115}, {{{0,8}},51}, {{{0,9}},199}, + {{{81,7}},11}, {{{0,8}},99}, {{{0,8}},35}, {{{0,9}},167}, + {{{0,8}},3}, {{{0,8}},131}, {{{0,8}},67}, {{{0,9}},231}, + {{{80,7}},7}, {{{0,8}},91}, {{{0,8}},27}, {{{0,9}},151}, + {{{84,7}},67}, {{{0,8}},123}, {{{0,8}},59}, {{{0,9}},215}, + {{{82,7}},19}, {{{0,8}},107}, {{{0,8}},43}, {{{0,9}},183}, + {{{0,8}},11}, {{{0,8}},139}, {{{0,8}},75}, {{{0,9}},247}, + {{{80,7}},5}, {{{0,8}},87}, {{{0,8}},23}, {{{192,8}},0}, + {{{83,7}},51}, {{{0,8}},119}, {{{0,8}},55}, {{{0,9}},207}, + {{{81,7}},15}, {{{0,8}},103}, {{{0,8}},39}, {{{0,9}},175}, + {{{0,8}},7}, {{{0,8}},135}, {{{0,8}},71}, {{{0,9}},239}, + {{{80,7}},9}, {{{0,8}},95}, {{{0,8}},31}, {{{0,9}},159}, + {{{84,7}},99}, {{{0,8}},127}, {{{0,8}},63}, {{{0,9}},223}, + {{{82,7}},27}, {{{0,8}},111}, {{{0,8}},47}, {{{0,9}},191}, + {{{0,8}},15}, {{{0,8}},143}, {{{0,8}},79}, {{{0,9}},255} + }; +local inflate_huft fixed_td[] = { + {{{80,5}},1}, {{{87,5}},257}, {{{83,5}},17}, {{{91,5}},4097}, + {{{81,5}},5}, {{{89,5}},1025}, {{{85,5}},65}, {{{93,5}},16385}, + {{{80,5}},3}, {{{88,5}},513}, {{{84,5}},33}, {{{92,5}},8193}, + {{{82,5}},9}, {{{90,5}},2049}, {{{86,5}},129}, {{{192,5}},24577}, + {{{80,5}},2}, {{{87,5}},385}, {{{83,5}},25}, {{{91,5}},6145}, + {{{81,5}},7}, {{{89,5}},1537}, {{{85,5}},97}, {{{93,5}},24577}, + {{{80,5}},4}, {{{88,5}},769}, {{{84,5}},49}, {{{92,5}},12289}, + {{{82,5}},13}, {{{90,5}},3073}, {{{86,5}},193}, {{{192,5}},24577} + }; diff --git a/code/zlib/inflate.c b/code/zlib/inflate.c new file mode 100644 index 0000000..42332c0 --- /dev/null +++ b/code/zlib/inflate.c @@ -0,0 +1,369 @@ +/* inflate.c -- zlib interface to inflate modules + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zutil.h" +#include "infblock.h" + +struct inflate_blocks_state {int dummy;}; /* for buggy compilers */ + +typedef enum { + METHOD, /* waiting for method byte */ + FLAG, /* waiting for flag byte */ + DICT4, /* four dictionary check bytes to go */ + DICT3, /* three dictionary check bytes to go */ + DICT2, /* two dictionary check bytes to go */ + DICT1, /* one dictionary check byte to go */ + DICT0, /* waiting for inflateSetDictionary */ + BLOCKS, /* decompressing blocks */ + CHECK4, /* four check bytes to go */ + CHECK3, /* three check bytes to go */ + CHECK2, /* two check bytes to go */ + CHECK1, /* one check byte to go */ + DONE, /* finished check, done */ + BAD} /* got an error--stay here */ +inflate_mode; + +/* inflate private state */ +struct internal_state { + + /* mode */ + inflate_mode mode; /* current inflate mode */ + + /* mode dependent information */ + union { + uInt method; /* if FLAGS, method byte */ + struct { + uLong was; /* computed check value */ + uLong need; /* stream check value */ + } check; /* if CHECK, check values to compare */ + uInt marker; /* if BAD, inflateSync's marker bytes count */ + } sub; /* submode */ + + /* mode independent information */ + int nowrap; /* flag for no wrapper */ + uInt wbits; /* log2(window size) (8..15, defaults to 15) */ + inflate_blocks_statef + *blocks; /* current inflate_blocks state */ + +}; + + +int ZEXPORT inflateReset(z) +z_streamp z; +{ + if (z == Z_NULL || z->state == Z_NULL) + return Z_STREAM_ERROR; + z->total_in = z->total_out = 0; + z->msg = Z_NULL; + z->state->mode = z->state->nowrap ? BLOCKS : METHOD; + inflate_blocks_reset(z->state->blocks, z, Z_NULL); + Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + + +int ZEXPORT inflateEnd(z) +z_streamp z; +{ + if (z == Z_NULL || z->state == Z_NULL || z->zfree == Z_NULL) + return Z_STREAM_ERROR; + if (z->state->blocks != Z_NULL) + inflate_blocks_free(z->state->blocks, z); + ZFREE(z, z->state); + z->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} + + +int ZEXPORT inflateInit2_(z, w, version, stream_size) +z_streamp z; +int w; +const char *version; +int stream_size; +{ + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != sizeof(z_stream)) + return Z_VERSION_ERROR; + + /* initialize state */ + if (z == Z_NULL) + return Z_STREAM_ERROR; + z->msg = Z_NULL; + if (z->zalloc == Z_NULL) + { + z->zalloc = zcalloc; + z->opaque = (voidpf)0; + } + if (z->zfree == Z_NULL) z->zfree = zcfree; + if ((z->state = (struct internal_state FAR *) + ZALLOC(z,1,sizeof(struct internal_state))) == Z_NULL) + return Z_MEM_ERROR; + z->state->blocks = Z_NULL; + + /* handle undocumented nowrap option (no zlib header or check) */ + z->state->nowrap = 0; + if (w < 0) + { + w = - w; + z->state->nowrap = 1; + } + + /* set window size */ + if (w < 8 || w > 15) + { + inflateEnd(z); + return Z_STREAM_ERROR; + } + z->state->wbits = (uInt)w; + + /* create inflate_blocks state */ + if ((z->state->blocks = + inflate_blocks_new(z, z->state->nowrap ? Z_NULL : adler32, (uInt)1 << w)) + == Z_NULL) + { + inflateEnd(z); + return Z_MEM_ERROR; + } + Tracev((stderr, "inflate: allocated\n")); + + /* reset state */ + inflateReset(z); + return Z_OK; +} + + +int ZEXPORT inflateInit_(z, version, stream_size) +z_streamp z; +const char *version; +int stream_size; +{ + return inflateInit2_(z, DEF_WBITS, version, stream_size); +} + + +#define NEEDBYTE {if(z->avail_in==0)return r;r=f;} +#define NEXTBYTE (z->avail_in--,z->total_in++,*z->next_in++) + +int ZEXPORT inflate(z, f) +z_streamp z; +int f; +{ + int r; + uInt b; + + if (z == Z_NULL || z->state == Z_NULL || z->next_in == Z_NULL) + return Z_STREAM_ERROR; + f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK; + r = Z_BUF_ERROR; + while (1) switch (z->state->mode) + { + case METHOD: + NEEDBYTE + if (((z->state->sub.method = NEXTBYTE) & 0xf) != Z_DEFLATED) + { + z->state->mode = BAD; + z->msg = (char*)"unknown compression method"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + if ((z->state->sub.method >> 4) + 8 > z->state->wbits) + { + z->state->mode = BAD; + z->msg = (char*)"invalid window size"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + z->state->mode = FLAG; + case FLAG: + NEEDBYTE + b = NEXTBYTE; + if (((z->state->sub.method << 8) + b) % 31) + { + z->state->mode = BAD; + z->msg = (char*)"incorrect header check"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + Tracev((stderr, "inflate: zlib header ok\n")); + if (!(b & PRESET_DICT)) + { + z->state->mode = BLOCKS; + break; + } + z->state->mode = DICT4; + case DICT4: + NEEDBYTE + z->state->sub.check.need = (uLong)NEXTBYTE << 24; + z->state->mode = DICT3; + case DICT3: + NEEDBYTE + z->state->sub.check.need += (uLong)NEXTBYTE << 16; + z->state->mode = DICT2; + case DICT2: + NEEDBYTE + z->state->sub.check.need += (uLong)NEXTBYTE << 8; + z->state->mode = DICT1; + case DICT1: + NEEDBYTE + z->state->sub.check.need += (uLong)NEXTBYTE; + z->adler = z->state->sub.check.need; + z->state->mode = DICT0; + return Z_NEED_DICT; + case DICT0: + z->state->mode = BAD; + z->msg = (char*)"need dictionary"; + z->state->sub.marker = 0; /* can try inflateSync */ + return Z_STREAM_ERROR; + case BLOCKS: + r = inflate_blocks(z->state->blocks, z, r); + if (r == Z_DATA_ERROR) + { + z->state->mode = BAD; + z->state->sub.marker = 0; /* can try inflateSync */ + break; + } + if (r == Z_OK) + r = f; + if (r != Z_STREAM_END) + return r; + r = f; + inflate_blocks_reset(z->state->blocks, z, &z->state->sub.check.was); + if (z->state->nowrap) + { + z->state->mode = DONE; + break; + } + z->state->mode = CHECK4; + case CHECK4: + NEEDBYTE + z->state->sub.check.need = (uLong)NEXTBYTE << 24; + z->state->mode = CHECK3; + case CHECK3: + NEEDBYTE + z->state->sub.check.need += (uLong)NEXTBYTE << 16; + z->state->mode = CHECK2; + case CHECK2: + NEEDBYTE + z->state->sub.check.need += (uLong)NEXTBYTE << 8; + z->state->mode = CHECK1; + case CHECK1: + NEEDBYTE + z->state->sub.check.need += (uLong)NEXTBYTE; + + if (z->state->sub.check.was != z->state->sub.check.need) + { + z->state->mode = BAD; + z->msg = (char*)"incorrect data check"; + z->state->sub.marker = 5; /* can't try inflateSync */ + break; + } + Tracev((stderr, "inflate: zlib check ok\n")); + z->state->mode = DONE; + case DONE: + return Z_STREAM_END; + case BAD: + return Z_DATA_ERROR; + default: + return Z_STREAM_ERROR; + } +#ifdef NEED_DUMMY_RETURN + return Z_STREAM_ERROR; /* Some dumb compilers complain without this */ +#endif +} + + +int ZEXPORT inflateSetDictionary(z, dictionary, dictLength) +z_streamp z; +const Bytef *dictionary; +uInt dictLength; +{ + uInt length = dictLength; + + if (z == Z_NULL || z->state == Z_NULL || z->state->mode != DICT0) + return Z_STREAM_ERROR; + + if (adler32(1L, dictionary, dictLength) != z->adler) return Z_DATA_ERROR; + z->adler = 1L; + + if (length >= ((uInt)1<state->wbits)) + { + length = (1<state->wbits)-1; + dictionary += dictLength - length; + } + inflate_set_dictionary(z->state->blocks, dictionary, length); + z->state->mode = BLOCKS; + return Z_OK; +} + + +int ZEXPORT inflateSync(z) +z_streamp z; +{ + uInt n; /* number of bytes to look at */ + Bytef *p; /* pointer to bytes */ + uInt m; /* number of marker bytes found in a row */ + uLong r, w; /* temporaries to save total_in and total_out */ + + /* set up */ + if (z == Z_NULL || z->state == Z_NULL) + return Z_STREAM_ERROR; + if (z->state->mode != BAD) + { + z->state->mode = BAD; + z->state->sub.marker = 0; + } + if ((n = z->avail_in) == 0) + return Z_BUF_ERROR; + p = z->next_in; + m = z->state->sub.marker; + + /* search */ + while (n && m < 4) + { + static const Byte mark[4] = {0, 0, 0xff, 0xff}; + if (*p == mark[m]) + m++; + else if (*p) + m = 0; + else + m = 4 - m; + p++, n--; + } + + /* restore */ + z->total_in += p - z->next_in; + z->next_in = p; + z->avail_in = n; + z->state->sub.marker = m; + + /* return no joy or set up to restart on a new block */ + if (m != 4) + return Z_DATA_ERROR; + r = z->total_in; w = z->total_out; + inflateReset(z); + z->total_in = r; z->total_out = w; + z->state->mode = BLOCKS; + return Z_OK; +} + + +/* Returns true if inflate is currently at the end of a block generated + * by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + * implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH + * but removes the length bytes of the resulting empty stored block. When + * decompressing, PPP checks that at the end of input packet, inflate is + * waiting for these length bytes. + */ +int ZEXPORT inflateSyncPoint(z) +z_streamp z; +{ + if (z == Z_NULL || z->state == Z_NULL || z->state->blocks == Z_NULL) + return Z_STREAM_ERROR; + return inflate_blocks_sync_point(z->state->blocks); +} diff --git a/code/zlib/inftrees.c b/code/zlib/inftrees.c new file mode 100644 index 0000000..bf5ca6f --- /dev/null +++ b/code/zlib/inftrees.c @@ -0,0 +1,458 @@ +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zutil.h" +#include "inftrees.h" + +#if !defined(BUILDFIXED) && !defined(STDC) +# define BUILDFIXED /* non ANSI compilers may not accept inffixed.h */ +#endif + +const char inflate_copyright[] = + " inflate 1.1.3 Copyright 1995-1998 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ +struct internal_state {int dummy;}; /* for buggy compilers */ + +/* simplify the use of the inflate_huft type with some defines */ +#define exop word.what.Exop +#define bits word.what.Bits + + +local int huft_build OF(( + uIntf *, /* code lengths in bits */ + uInt, /* number of codes */ + uInt, /* number of "simple" codes */ + const uIntf *, /* list of base values for non-simple codes */ + const uIntf *, /* list of extra bits for non-simple codes */ + inflate_huft * FAR*,/* result: starting table */ + uIntf *, /* maximum lookup bits (returns actual) */ + inflate_huft *, /* space for trees */ + uInt *, /* hufts used in space */ + uIntf * )); /* space for values */ + +/* Tables for deflate from PKZIP's appnote.txt. */ +local const uInt cplens[31] = { /* Copy lengths for literal codes 257..285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + /* see note #13 above about 258 */ +local const uInt cplext[31] = { /* Extra bits for literal codes 257..285 */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112}; /* 112==invalid */ +local const uInt cpdist[30] = { /* Copy offsets for distance codes 0..29 */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577}; +local const uInt cpdext[30] = { /* Extra bits for distance codes */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + +/* + Huffman code decoding is performed using a multi-level table lookup. + The fastest way to decode is to simply build a lookup table whose + size is determined by the longest code. However, the time it takes + to build this table can also be a factor if the data being decoded + is not very long. The most common codes are necessarily the + shortest codes, so those codes dominate the decoding time, and hence + the speed. The idea is you can have a shorter table that decodes the + shorter, more probable codes, and then point to subsidiary tables for + the longer codes. The time it costs to decode the longer codes is + then traded against the time it takes to make longer tables. + + This results of this trade are in the variables lbits and dbits + below. lbits is the number of bits the first level table for literal/ + length codes can decode in one step, and dbits is the same thing for + the distance codes. Subsequent tables are also less than or equal to + those sizes. These values may be adjusted either when all of the + codes are shorter than that, in which case the longest code length in + bits is used, or when the shortest code is *longer* than the requested + table size, in which case the length of the shortest code in bits is + used. + + There are two different values for the two tables, since they code a + different number of possibilities each. The literal/length table + codes 286 possible values, or in a flat code, a little over eight + bits. The distance table codes 30 possible values, or a little less + than five bits, flat. The optimum values for speed end up being + about one bit more than those, so lbits is 8+1 and dbits is 5+1. + The optimum values may differ though from machine to machine, and + possibly even between compilers. Your mileage may vary. + */ + + +/* If BMAX needs to be larger than 16, then h and x[] should be uLong. */ +#define BMAX 15 /* maximum bit length of any code */ + +local int huft_build(b, n, s, d, e, t, m, hp, hn, v) +uIntf *b; /* code lengths in bits (all assumed <= BMAX) */ +uInt n; /* number of codes (assumed <= 288) */ +uInt s; /* number of simple-valued codes (0..s-1) */ +const uIntf *d; /* list of base values for non-simple codes */ +const uIntf *e; /* list of extra bits for non-simple codes */ +inflate_huft * FAR *t; /* result: starting table */ +uIntf *m; /* maximum lookup bits, returns actual */ +inflate_huft *hp; /* space for trees */ +uInt *hn; /* hufts used in space */ +uIntf *v; /* working area: values in order of bit length */ +/* Given a list of code lengths and a maximum table size, make a set of + tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR + if the given code set is incomplete (the tables are still built in this + case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of + lengths), or Z_MEM_ERROR if not enough memory. */ +{ + + uInt a; /* counter for codes of length k */ + uInt c[BMAX+1]; /* bit length count table */ + uInt f; /* i repeats in table every f entries */ + int g; /* maximum code length */ + int h; /* table level */ + register uInt i; /* counter, current code */ + register uInt j; /* counter */ + register int k; /* number of bits in current code */ + int l; /* bits per table (returned in m) */ + uInt mask; /* (1 << w) - 1, to avoid cc -O bug on HP */ + register uIntf *p; /* pointer into c[], b[], or v[] */ + inflate_huft *q; /* points to current table */ + struct inflate_huft_s r; /* table entry for structure assignment */ + inflate_huft *u[BMAX]; /* table stack */ + register int w; /* bits before this table == (l * h) */ + uInt x[BMAX+1]; /* bit offsets, then code stack */ + uIntf *xp; /* pointer into x */ + int y; /* number of dummy codes added */ + uInt z; /* number of entries in current table */ + + + /* Generate counts for each bit length */ + p = c; +#define C0 *p++ = 0; +#define C2 C0 C0 C0 C0 +#define C4 C2 C2 C2 C2 + C4 /* clear c[]--assume BMAX+1 is 16 */ + p = b; i = n; + do { + c[*p++]++; /* assume all entries <= BMAX */ + } while (--i); + if (c[0] == n) /* null input--all zero length codes */ + { + *t = (inflate_huft *)Z_NULL; + *m = 0; + return Z_OK; + } + + + /* Find minimum and maximum length, bound *m by those */ + l = *m; + for (j = 1; j <= BMAX; j++) + if (c[j]) + break; + k = j; /* minimum code length */ + if ((uInt)l < j) + l = j; + for (i = BMAX; i; i--) + if (c[i]) + break; + g = i; /* maximum code length */ + if ((uInt)l > i) + l = i; + *m = l; + + + /* Adjust last length count to fill out codes, if needed */ + for (y = 1 << j; j < i; j++, y <<= 1) + if ((y -= c[j]) < 0) + return Z_DATA_ERROR; + if ((y -= c[i]) < 0) + return Z_DATA_ERROR; + c[i] += y; + + + /* Generate starting offsets into the value table for each length */ + x[1] = j = 0; + p = c + 1; xp = x + 2; + while (--i) { /* note that i == g from above */ + *xp++ = (j += *p++); + } + + + /* Make a table of values in order of bit lengths */ + p = b; i = 0; + do { + if ((j = *p++) != 0) + v[x[j]++] = i; + } while (++i < n); + n = x[g]; /* set n to length of v */ + + + /* Generate the Huffman codes and for each, make the table entries */ + x[0] = i = 0; /* first Huffman code is zero */ + p = v; /* grab values in bit order */ + h = -1; /* no tables yet--level -1 */ + w = -l; /* bits decoded == (l * h) */ + u[0] = (inflate_huft *)Z_NULL; /* just to keep compilers happy */ + q = (inflate_huft *)Z_NULL; /* ditto */ + z = 0; /* ditto */ + + /* go through the bit lengths (k already is bits in shortest code) */ + for (; k <= g; k++) + { + a = c[k]; + while (a--) + { + /* here i is the Huffman code of length k bits for value *p */ + /* make tables up to required level */ + while (k > w + l) + { + h++; + w += l; /* previous table always l bits */ + + /* compute minimum size table less than or equal to l bits */ + z = g - w; + z = z > (uInt)l ? l : z; /* table size upper limit */ + if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */ + { /* too few codes for k-w bit table */ + f -= a + 1; /* deduct codes from patterns left */ + xp = c + k; + if (j < z) + while (++j < z) /* try smaller tables up to z bits */ + { + if ((f <<= 1) <= *++xp) + break; /* enough codes to use up j bits */ + f -= *xp; /* else deduct codes from patterns */ + } + } + z = 1 << j; /* table entries for j-bit table */ + + /* allocate new table */ + if (*hn + z > MANY) /* (note: doesn't matter for fixed) */ + return Z_MEM_ERROR; /* not enough memory */ + u[h] = q = hp + *hn; + *hn += z; + + /* connect to last table, if there is one */ + if (h) + { + x[h] = i; /* save pattern for backing up */ + r.bits = (Byte)l; /* bits to dump before this table */ + r.exop = (Byte)j; /* bits in this table */ + j = i >> (w - l); + r.base = (uInt)(q - u[h-1] - j); /* offset to this table */ + u[h-1][j] = r; /* connect to last table */ + } + else + *t = q; /* first table is returned result */ + } + + /* set up table entry in r */ + r.bits = (Byte)(k - w); + if (p >= v + n) + r.exop = 128 + 64; /* out of values--invalid code */ + else if (*p < s) + { + r.exop = (Byte)(*p < 256 ? 0 : 32 + 64); /* 256 is end-of-block */ + r.base = *p++; /* simple code is just the value */ + } + else + { + r.exop = (Byte)(e[*p - s] + 16 + 64);/* non-simple--look up in lists */ + r.base = d[*p++ - s]; + } + + /* fill code-like entries with r */ + f = 1 << (k - w); + for (j = i >> w; j < z; j += f) + q[j] = r; + + /* backwards increment the k-bit code i */ + for (j = 1 << (k - 1); i & j; j >>= 1) + i ^= j; + i ^= j; + + /* backup over finished tables */ + mask = (1 << w) - 1; /* needed on HP, cc -O bug */ + while ((i & mask) != x[h]) + { + h--; /* don't need to update q */ + w -= l; + mask = (1 << w) - 1; + } + } + } + + + /* Return Z_BUF_ERROR if we were given an incomplete table */ + return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; +} + + +int inflate_trees_bits(c, bb, tb, hp, z) +uIntf *c; /* 19 code lengths */ +uIntf *bb; /* bits tree desired/actual depth */ +inflate_huft * FAR *tb; /* bits tree result */ +inflate_huft *hp; /* space for trees */ +z_streamp z; /* for messages */ +{ + int r; + uInt hn = 0; /* hufts used in space */ + uIntf *v; /* work area for huft_build */ + + if ((v = (uIntf*)ZALLOC(z, 19, sizeof(uInt))) == Z_NULL) + return Z_MEM_ERROR; + r = huft_build(c, 19, 19, (uIntf*)Z_NULL, (uIntf*)Z_NULL, + tb, bb, hp, &hn, v); + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed dynamic bit lengths tree"; + else if (r == Z_BUF_ERROR || *bb == 0) + { + z->msg = (char*)"incomplete dynamic bit lengths tree"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; +} + + +int inflate_trees_dynamic(nl, nd, c, bl, bd, tl, td, hp, z) +uInt nl; /* number of literal/length codes */ +uInt nd; /* number of distance codes */ +uIntf *c; /* that many (total) code lengths */ +uIntf *bl; /* literal desired/actual bit depth */ +uIntf *bd; /* distance desired/actual bit depth */ +inflate_huft * FAR *tl; /* literal/length tree result */ +inflate_huft * FAR *td; /* distance tree result */ +inflate_huft *hp; /* space for trees */ +z_streamp z; /* for messages */ +{ + int r; + uInt hn = 0; /* hufts used in space */ + uIntf *v; /* work area for huft_build */ + + /* allocate work area */ + if ((v = (uIntf*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL) + return Z_MEM_ERROR; + + /* build literal/length tree */ + r = huft_build(c, nl, 257, cplens, cplext, tl, bl, hp, &hn, v); + if (r != Z_OK || *bl == 0) + { + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed literal/length tree"; + else if (r != Z_MEM_ERROR) + { + z->msg = (char*)"incomplete literal/length tree"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; + } + + /* build distance tree */ + r = huft_build(c + nl, nd, 0, cpdist, cpdext, td, bd, hp, &hn, v); + if (r != Z_OK || (*bd == 0 && nl > 257)) + { + if (r == Z_DATA_ERROR) + z->msg = (char*)"oversubscribed distance tree"; + else if (r == Z_BUF_ERROR) { +#ifdef PKZIP_BUG_WORKAROUND + r = Z_OK; + } +#else + z->msg = (char*)"incomplete distance tree"; + r = Z_DATA_ERROR; + } + else if (r != Z_MEM_ERROR) + { + z->msg = (char*)"empty distance tree with lengths"; + r = Z_DATA_ERROR; + } + ZFREE(z, v); + return r; +#endif + } + + /* done */ + ZFREE(z, v); + return Z_OK; +} + + +/* build fixed tables only once--keep them here */ +#ifdef BUILDFIXED +local int fixed_built = 0; +#define FIXEDH 544 /* number of hufts used by fixed tables */ +local inflate_huft fixed_mem[FIXEDH]; +local uInt fixed_bl; +local uInt fixed_bd; +local inflate_huft *fixed_tl; +local inflate_huft *fixed_td; +#else +#include "inffixed.h" +#endif + + +int inflate_trees_fixed(bl, bd, tl, td, z) +uIntf *bl; /* literal desired/actual bit depth */ +uIntf *bd; /* distance desired/actual bit depth */ +inflate_huft * FAR *tl; /* literal/length tree result */ +inflate_huft * FAR *td; /* distance tree result */ +z_streamp z; /* for memory allocation */ +{ +#ifdef BUILDFIXED + /* build fixed tables if not already */ + if (!fixed_built) + { + int k; /* temporary variable */ + uInt f = 0; /* number of hufts used in fixed_mem */ + uIntf *c; /* length list for huft_build */ + uIntf *v; /* work area for huft_build */ + + /* allocate memory */ + if ((c = (uIntf*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL) + return Z_MEM_ERROR; + if ((v = (uIntf*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL) + { + ZFREE(z, c); + return Z_MEM_ERROR; + } + + /* literal table */ + for (k = 0; k < 144; k++) + c[k] = 8; + for (; k < 256; k++) + c[k] = 9; + for (; k < 280; k++) + c[k] = 7; + for (; k < 288; k++) + c[k] = 8; + fixed_bl = 9; + huft_build(c, 288, 257, cplens, cplext, &fixed_tl, &fixed_bl, + fixed_mem, &f, v); + + /* distance table */ + for (k = 0; k < 30; k++) + c[k] = 5; + fixed_bd = 5; + huft_build(c, 30, 0, cpdist, cpdext, &fixed_td, &fixed_bd, + fixed_mem, &f, v); + + /* done */ + ZFREE(z, v); + ZFREE(z, c); + fixed_built = 1; + } +#endif + *bl = fixed_bl; + *bd = fixed_bd; + *tl = fixed_tl; + *td = fixed_td; + return Z_OK; +} diff --git a/code/zlib/inftrees.h b/code/zlib/inftrees.h new file mode 100644 index 0000000..85853e0 --- /dev/null +++ b/code/zlib/inftrees.h @@ -0,0 +1,58 @@ +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Huffman code lookup table entry--this entry is four bytes for machines + that have 16-bit pointers (e.g. PC's in the small or medium model). */ + +typedef struct inflate_huft_s FAR inflate_huft; + +struct inflate_huft_s { + union { + struct { + Byte Exop; /* number of extra bits or operation */ + Byte Bits; /* number of bits in this code or subcode */ + } what; + uInt pad; /* pad structure to a power of 2 (4 bytes for */ + } word; /* 16-bit, 8 bytes for 32-bit int's) */ + uInt base; /* literal, length base, distance base, + or table offset */ +}; + +/* Maximum size of dynamic tree. The maximum found in a long but non- + exhaustive search was 1004 huft structures (850 for length/literals + and 154 for distances, the latter actually the result of an + exhaustive search). The actual maximum is not known, but the + value below is more than safe. */ +#define MANY 1440 + +extern int inflate_trees_bits OF(( + uIntf *, /* 19 code lengths */ + uIntf *, /* bits tree desired/actual depth */ + inflate_huft * FAR *, /* bits tree result */ + inflate_huft *, /* space for trees */ + z_streamp)); /* for messages */ + +extern int inflate_trees_dynamic OF(( + uInt, /* number of literal/length codes */ + uInt, /* number of distance codes */ + uIntf *, /* that many (total) code lengths */ + uIntf *, /* literal desired/actual bit depth */ + uIntf *, /* distance desired/actual bit depth */ + inflate_huft * FAR *, /* literal/length tree result */ + inflate_huft * FAR *, /* distance tree result */ + inflate_huft *, /* space for trees */ + z_streamp)); /* for messages */ + +extern int inflate_trees_fixed OF(( + uIntf *, /* literal desired/actual bit depth */ + uIntf *, /* distance desired/actual bit depth */ + inflate_huft * FAR *, /* literal/length tree result */ + inflate_huft * FAR *, /* distance tree result */ + z_streamp)); /* for memory allocation */ diff --git a/code/zlib/infutil.c b/code/zlib/infutil.c new file mode 100644 index 0000000..3b28e89 --- /dev/null +++ b/code/zlib/infutil.c @@ -0,0 +1,90 @@ +/* inflate_util.c -- data and routines common to blocks and codes + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zutil.h" +#include "infblock.h" +#include "inftrees.h" +#include "infcodes.h" +#include "infutil.h" + +struct inflate_codes_state {int dummy;}; /* for buggy compilers */ + +/* And'ing with mask[n] masks the lower n bits */ +uInt inflate_mask[17] = { + 0x0000, + 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + + +/* copy as much as possible from the sliding window to the output area */ +int inflate_flush(s, z, r) +inflate_blocks_statef *s; +z_streamp z; +int r; +{ + uInt n; + Bytef *p; + Bytef *q; + + /* local copies of source and destination pointers */ + p = z->next_out; + q = s->read; + + /* compute number of bytes to copy as far as end of window */ + n = (uInt)((q <= s->write ? s->write : s->end) - q); + if (n > z->avail_out) n = z->avail_out; + if (n && r == Z_BUF_ERROR) r = Z_OK; + + /* update counters */ + z->avail_out -= n; + z->total_out += n; + + /* update check information */ + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(s->check, q, n); + + /* copy as far as end of window */ + zmemcpy(p, q, n); + p += n; + q += n; + + /* see if more to copy at beginning of window */ + if (q == s->end) + { + /* wrap pointers */ + q = s->window; + if (s->write == s->end) + s->write = s->window; + + /* compute bytes to copy */ + n = (uInt)(s->write - q); + if (n > z->avail_out) n = z->avail_out; + if (n && r == Z_BUF_ERROR) r = Z_OK; + + /* update counters */ + z->avail_out -= n; + z->total_out += n; + + /* update check information */ + if (s->checkfn != Z_NULL) + z->adler = s->check = (*s->checkfn)(s->check, q, n); + + /* copy */ + zmemcpy(p, q, n); + p += n; + q += n; + } + + /* update pointers */ + z->next_out = p; + s->read = q; + + /* done */ + return r; +} diff --git a/code/zlib/infutil.h b/code/zlib/infutil.h new file mode 100644 index 0000000..99d1135 --- /dev/null +++ b/code/zlib/infutil.h @@ -0,0 +1,98 @@ +/* infutil.h -- types and macros common to blocks and codes + * Copyright (C) 1995-1998 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +#ifndef _INFUTIL_H +#define _INFUTIL_H + +typedef enum { + TYPE, /* get type bits (3, including end bit) */ + LENS, /* get lengths for stored */ + STORED, /* processing stored block */ + TABLE, /* get table lengths */ + BTREE, /* get bit lengths tree for a dynamic block */ + DTREE, /* get length, distance trees for a dynamic block */ + CODES, /* processing fixed or dynamic block */ + DRY, /* output remaining window bytes */ + DONE, /* finished last block, done */ + BAD} /* got a data error--stuck here */ +inflate_block_mode; + +/* inflate blocks semi-private state */ +struct inflate_blocks_state { + + /* mode */ + inflate_block_mode mode; /* current inflate_block mode */ + + /* mode dependent information */ + union { + uInt left; /* if STORED, bytes left to copy */ + struct { + uInt table; /* table lengths (14 bits) */ + uInt index; /* index into blens (or border) */ + uIntf *blens; /* bit lengths of codes */ + uInt bb; /* bit length tree depth */ + inflate_huft *tb; /* bit length decoding tree */ + } trees; /* if DTREE, decoding info for trees */ + struct { + inflate_codes_statef + *codes; + } decode; /* if CODES, current state */ + } sub; /* submode */ + uInt last; /* true if this block is the last block */ + + /* mode independent information */ + uInt bitk; /* bits in bit buffer */ + uLong bitb; /* bit buffer */ + inflate_huft *hufts; /* single malloc for tree space */ + Bytef *window; /* sliding window */ + Bytef *end; /* one byte after sliding window */ + Bytef *read; /* window read pointer */ + Bytef *write; /* window write pointer */ + check_func checkfn; /* check function */ + uLong check; /* check on output */ + +}; + + +/* defines for inflate input/output */ +/* update pointers and return */ +#define UPDBITS {s->bitb=b;s->bitk=k;} +#define UPDIN {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} +#define UPDOUT {s->write=q;} +#define UPDATE {UPDBITS UPDIN UPDOUT} +#define LEAVE {UPDATE return inflate_flush(s,z,r);} +/* get bytes and bits */ +#define LOADIN {p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} +#define NEEDBYTE {if(n)r=Z_OK;else LEAVE} +#define NEXTBYTE (n--,*p++) +#define NEEDBITS(j) {while(k<(j)){NEEDBYTE;b|=((uLong)NEXTBYTE)<>=(j);k-=(j);} +/* output bytes */ +#define WAVAIL (uInt)(qread?s->read-q-1:s->end-q) +#define LOADOUT {q=s->write;m=(uInt)WAVAIL;} +#define WRAP {if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)WAVAIL;}} +#define FLUSH {UPDOUT r=inflate_flush(s,z,r); LOADOUT} +#define NEEDOUT {if(m==0){WRAP if(m==0){FLUSH WRAP if(m==0) LEAVE}}r=Z_OK;} +#define OUTBYTE(a) {*q++=(Byte)(a);m--;} +/* load local pointers */ +#define LOAD {LOADIN LOADOUT} + +/* masks for lower bits (size given to avoid silly warnings with Visual C++) */ +extern uInt inflate_mask[17]; + +/* copy as much as possible from the sliding window to the output area */ +extern int inflate_flush OF(( + inflate_blocks_statef *, + z_streamp , + int)); + +struct internal_state {int dummy;}; /* for buggy compilers */ + +#endif diff --git a/code/zlib/trees.c b/code/zlib/trees.c new file mode 100644 index 0000000..77b4f5d --- /dev/null +++ b/code/zlib/trees.c @@ -0,0 +1,1217 @@ +/* trees.c -- output deflated data using Huffman coding + * Copyright (C) 1995-1998 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process uses several Huffman trees. The more + * common source values are represented by shorter bit sequences. + * + * Each code tree is stored in a compressed form which is itself + * a Huffman encoding of the lengths of all the code strings (in + * ascending order by source values). The actual code strings are + * reconstructed from the lengths in the inflate process, as described + * in the deflate specification. + * + * REFERENCES + * + * Deutsch, L.P.,"'Deflate' Compressed Data Format Specification". + * Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc + * + * Storer, James A. + * Data Compression: Methods and Theory, pp. 49-50. + * Computer Science Press, 1988. ISBN 0-7167-8156-5. + * + * Sedgewick, R. + * Algorithms, p290. + * Addison-Wesley, 1983. ISBN 0-201-06672-6. + */ + +/* @(#) $Id: trees.c,v 1.1 2003/03/19 19:08:52 osman Exp $ */ + +/* #define GEN_TREES_H */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "deflate.h" + +#ifdef DEBUG +# include +#endif + +/* =========================================================================== + * Constants + */ + +#define MAX_BL_BITS 7 +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +#define END_BLOCK 256 +/* end of block literal code */ + +#define REP_3_6 16 +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +#define REPZ_3_10 17 +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +#define REPZ_11_138 18 +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +local const int extra_lbits[LENGTH_CODES] /* extra bits for each length code */ + = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0}; + +local const int extra_dbits[D_CODES] /* extra bits for each distance code */ + = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +local const int extra_blbits[BL_CODES]/* extra bits for each bit length code */ + = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7}; + +local const uch bl_order[BL_CODES] + = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +#define Buf_size (8 * 2*sizeof(char)) +/* Number of bits used within bi_buf. (bi_buf might be implemented on + * more than 16 bits on some systems.) + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +#define DIST_CODE_LEN 512 /* see definition of array dist_code below */ + +#if defined(GEN_TREES_H) || !defined(STDC) +/* non ANSI compilers may not accept trees.h */ + +local ct_data static_ltree[L_CODES+2]; +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +local ct_data static_dtree[D_CODES]; +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +uch _dist_code[DIST_CODE_LEN]; +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +uch _length_code[MAX_MATCH-MIN_MATCH+1]; +/* length code for each normalized match length (0 == MIN_MATCH) */ + +local int base_length[LENGTH_CODES]; +/* First normalized length for each code (0 = MIN_MATCH) */ + +local int base_dist[D_CODES]; +/* First normalized distance for each code (0 = distance of 1) */ + +#else +# include "trees.h" +#endif /* GEN_TREES_H */ + +struct static_tree_desc_s { + const ct_data *static_tree; /* static tree or NULL */ + const intf *extra_bits; /* extra bits for each code or NULL */ + int extra_base; /* base index for extra_bits */ + int elems; /* max number of elements in the tree */ + int max_length; /* max bit length for the codes */ +}; + +local static_tree_desc static_l_desc = +{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS}; + +local static_tree_desc static_d_desc = +{static_dtree, extra_dbits, 0, D_CODES, MAX_BITS}; + +local static_tree_desc static_bl_desc = +{(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS}; + +/* =========================================================================== + * Local (static) routines in this file. + */ + +local void tr_static_init OF((void)); +local void init_block OF((deflate_state *s)); +local void pqdownheap OF((deflate_state *s, ct_data *tree, int k)); +local void gen_bitlen OF((deflate_state *s, tree_desc *desc)); +local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count)); +local void build_tree OF((deflate_state *s, tree_desc *desc)); +local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local int build_bl_tree OF((deflate_state *s)); +local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, + int blcodes)); +local void compress_block OF((deflate_state *s, ct_data *ltree, + ct_data *dtree)); +local void set_data_type OF((deflate_state *s)); +local unsigned bi_reverse OF((unsigned value, int length)); +local void bi_windup OF((deflate_state *s)); +local void bi_flush OF((deflate_state *s)); +local void copy_block OF((deflate_state *s, charf *buf, unsigned len, + int header)); + +#ifdef GEN_TREES_H +local void gen_trees_header OF((void)); +#endif + +#ifndef DEBUG +# define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len) + /* Send a code of the given tree. c and tree must not have side effects */ + +#else /* DEBUG */ +# define send_code(s, c, tree) \ + { if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \ + send_bits(s, tree[c].Code, tree[c].Len); } +#endif + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +#define put_short(s, w) { \ + put_byte(s, (uch)((w) & 0xff)); \ + put_byte(s, (uch)((ush)(w) >> 8)); \ +} + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +#ifdef DEBUG +local void send_bits OF((deflate_state *s, int value, int length)); + +local void send_bits(s, value, length) + deflate_state *s; + int value; /* value to send */ + int length; /* number of bits */ +{ + Tracevv((stderr," l %2d v %4x ", length, value)); + Assert(length > 0 && length <= 15, "invalid length"); + s->bits_sent += (ulg)length; + + /* If not enough room in bi_buf, use (valid) bits from bi_buf and + * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * unused bits in value. + */ + if (s->bi_valid > (int)Buf_size - length) { + s->bi_buf |= (value << s->bi_valid); + put_short(s, s->bi_buf); + s->bi_buf = (ush)value >> (Buf_size - s->bi_valid); + s->bi_valid += length - Buf_size; + } else { + s->bi_buf |= value << s->bi_valid; + s->bi_valid += length; + } +} +#else /* !DEBUG */ + +#define send_bits(s, value, length) \ +{ int len = length;\ + if (s->bi_valid > (int)Buf_size - len) {\ + int val = value;\ + s->bi_buf |= (val << s->bi_valid);\ + put_short(s, s->bi_buf);\ + s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\ + s->bi_valid += len - Buf_size;\ + } else {\ + s->bi_buf |= (value) << s->bi_valid;\ + s->bi_valid += len;\ + }\ +} +#endif /* DEBUG */ + + +#define MAX(a,b) (a >= b ? a : b) +/* the arguments must not have side effects */ + +/* =========================================================================== + * Initialize the various 'constant' tables. + */ +local void tr_static_init() +{ +#if defined(GEN_TREES_H) || !defined(STDC) + static int static_init_done = 0; + int n; /* iterates over tree elements */ + int bits; /* bit counter */ + int length; /* length value */ + int code; /* code value */ + int dist; /* distance index */ + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + if (static_init_done) return; + + /* For some embedded targets, global variables are not initialized: */ + static_l_desc.static_tree = static_ltree; + static_l_desc.extra_bits = extra_lbits; + static_d_desc.static_tree = static_dtree; + static_d_desc.extra_bits = extra_dbits; + static_bl_desc.extra_bits = extra_blbits; + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES-1; code++) { + base_length[code] = length; + for (n = 0; n < (1< dist code (0..29) */ + dist = 0; + for (code = 0 ; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ + for ( ; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + _dist_code[256 + dist++] = (uch)code; + } + } + Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; + n = 0; + while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++; + while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++; + while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++; + while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++; + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes((ct_data *)static_ltree, L_CODES+1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n].Len = 5; + static_dtree[n].Code = bi_reverse((unsigned)n, 5); + } + static_init_done = 1; + +# ifdef GEN_TREES_H + gen_trees_header(); +# endif +#endif /* defined(GEN_TREES_H) || !defined(STDC) */ +} + +/* =========================================================================== + * Genererate the file trees.h describing the static trees. + */ +#ifdef GEN_TREES_H +# ifndef DEBUG +# include +# endif + +# define SEPARATOR(i, last, width) \ + ((i) == (last)? "\n};\n\n" : \ + ((i) % (width) == (width)-1 ? ",\n" : ", ")) + +void gen_trees_header() +{ + FILE *header = fopen("trees.h", "w"); + int i; + + Assert (header != NULL, "Can't open trees.h"); + fprintf(header, + "/* header created automatically with -DGEN_TREES_H */\n\n"); + + fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n"); + for (i = 0; i < L_CODES+2; i++) { + fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code, + static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5)); + } + + fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code, + static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5)); + } + + fprintf(header, "const uch _dist_code[DIST_CODE_LEN] = {\n"); + for (i = 0; i < DIST_CODE_LEN; i++) { + fprintf(header, "%2u%s", _dist_code[i], + SEPARATOR(i, DIST_CODE_LEN-1, 20)); + } + + fprintf(header, "const uch _length_code[MAX_MATCH-MIN_MATCH+1]= {\n"); + for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) { + fprintf(header, "%2u%s", _length_code[i], + SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20)); + } + + fprintf(header, "local const int base_length[LENGTH_CODES] = {\n"); + for (i = 0; i < LENGTH_CODES; i++) { + fprintf(header, "%1u%s", base_length[i], + SEPARATOR(i, LENGTH_CODES-1, 20)); + } + + fprintf(header, "local const int base_dist[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "%5u%s", base_dist[i], + SEPARATOR(i, D_CODES-1, 10)); + } + + fclose(header); +} +#endif /* GEN_TREES_H */ + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +void _tr_init(s) + deflate_state *s; +{ + tr_static_init(); + + s->l_desc.dyn_tree = s->dyn_ltree; + s->l_desc.stat_desc = &static_l_desc; + + s->d_desc.dyn_tree = s->dyn_dtree; + s->d_desc.stat_desc = &static_d_desc; + + s->bl_desc.dyn_tree = s->bl_tree; + s->bl_desc.stat_desc = &static_bl_desc; + + s->bi_buf = 0; + s->bi_valid = 0; + s->last_eob_len = 8; /* enough lookahead for inflate */ +#ifdef DEBUG + s->compressed_len = 0L; + s->bits_sent = 0L; +#endif + + /* Initialize the first block of the first file: */ + init_block(s); +} + +/* =========================================================================== + * Initialize a new block. + */ +local void init_block(s) + deflate_state *s; +{ + int n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0; + for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0; + for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0; + + s->dyn_ltree[END_BLOCK].Freq = 1; + s->opt_len = s->static_len = 0L; + s->last_lit = s->matches = 0; +} + +#define SMALLEST 1 +/* Index within the heap array of least frequent node in the Huffman tree */ + + +/* =========================================================================== + * Remove the smallest element from the heap and recreate the heap with + * one less element. Updates heap and heap_len. + */ +#define pqremove(s, tree, top) \ +{\ + top = s->heap[SMALLEST]; \ + s->heap[SMALLEST] = s->heap[s->heap_len--]; \ + pqdownheap(s, tree, SMALLEST); \ +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +#define smaller(tree, n, m, depth) \ + (tree[n].Freq < tree[m].Freq || \ + (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m])) + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +local void pqdownheap(s, tree, k) + deflate_state *s; + ct_data *tree; /* the tree to restore */ + int k; /* node to move down */ +{ + int v = s->heap[k]; + int j = k << 1; /* left son of k */ + while (j <= s->heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s->heap_len && + smaller(tree, s->heap[j+1], s->heap[j], s->depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s->heap[j], s->depth)) break; + + /* Exchange v with the smallest son */ + s->heap[k] = s->heap[j]; k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s->heap[k] = v; +} + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +local void gen_bitlen(s, desc) + deflate_state *s; + tree_desc *desc; /* the tree descriptor */ +{ + ct_data *tree = desc->dyn_tree; + int max_code = desc->max_code; + const ct_data *stree = desc->stat_desc->static_tree; + const intf *extra = desc->stat_desc->extra_bits; + int base = desc->stat_desc->extra_base; + int max_length = desc->stat_desc->max_length; + int h; /* heap index */ + int n, m; /* iterate over the tree elements */ + int bits; /* bit length */ + int xbits; /* extra bits */ + ush f; /* frequency */ + int overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) s->bl_count[bits] = 0; + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */ + + for (h = s->heap_max+1; h < HEAP_SIZE; h++) { + n = s->heap[h]; + bits = tree[tree[n].Dad].Len + 1; + if (bits > max_length) bits = max_length, overflow++; + tree[n].Len = (ush)bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) continue; /* not a leaf node */ + + s->bl_count[bits]++; + xbits = 0; + if (n >= base) xbits = extra[n-base]; + f = tree[n].Freq; + s->opt_len += (ulg)f * (bits + xbits); + if (stree) s->static_len += (ulg)f * (stree[n].Len + xbits); + } + if (overflow == 0) return; + + //Trace((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length-1; + while (s->bl_count[bits] == 0) bits--; + s->bl_count[bits]--; /* move one leaf down the tree */ + s->bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s->bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits != 0; bits--) { + n = s->bl_count[bits]; + while (n != 0) { + m = s->heap[--h]; + if (m > max_code) continue; + if (tree[m].Len != (unsigned) bits) { + //Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s->opt_len += ((long)bits - (long)tree[m].Len) + *(long)tree[m].Freq; + tree[m].Len = (ush)bits; + } + n--; + } + } +} + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +local void gen_codes (tree, max_code, bl_count) + ct_data *tree; /* the tree to decorate */ + int max_code; /* largest code with non zero frequency */ + ushf *bl_count; /* number of codes at each bit length */ +{ + ush next_code[MAX_BITS+1]; /* next code value for each bit length */ + ush code = 0; /* running code value */ + int bits; /* bit index */ + int n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (code + bl_count[bits-1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + Assert (code + bl_count[MAX_BITS]-1 == (1<dyn_tree; + const ct_data *stree = desc->stat_desc->static_tree; + int elems = desc->stat_desc->elems; + int n, m; /* iterate over heap elements */ + int max_code = -1; /* largest code with non zero frequency */ + int node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s->heap_len = 0, s->heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n].Freq != 0) { + s->heap[++(s->heap_len)] = max_code = n; + s->depth[n] = 0; + } else { + tree[n].Len = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s->heap_len < 2) { + node = s->heap[++(s->heap_len)] = (max_code < 2 ? ++max_code : 0); + tree[node].Freq = 1; + s->depth[node] = 0; + s->opt_len--; if (stree) s->static_len -= stree[node].Len; + /* node is 0 or 1 so it does not have extra bits */ + } + desc->max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n); + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + pqremove(s, tree, n); /* n = node of least frequency */ + m = s->heap[SMALLEST]; /* m = node of next least frequency */ + + s->heap[--(s->heap_max)] = n; /* keep the nodes sorted by frequency */ + s->heap[--(s->heap_max)] = m; + + /* Create a new node father of n and m */ + tree[node].Freq = tree[n].Freq + tree[m].Freq; + s->depth[node] = (uch) (MAX(s->depth[n], s->depth[m]) + 1); + tree[n].Dad = tree[m].Dad = (ush)node; +#ifdef DUMP_BL_TREE + if (tree == s->bl_tree) { + fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)", + node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq); + } +#endif + /* and insert the new node in the heap */ + s->heap[SMALLEST] = node++; + pqdownheap(s, tree, SMALLEST); + + } while (s->heap_len >= 2); + + s->heap[--(s->heap_max)] = s->heap[SMALLEST]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, (tree_desc *)desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes ((ct_data *)tree, max_code, s->bl_count); +} + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +local void scan_tree (s, tree, max_code) + deflate_state *s; + ct_data *tree; /* the tree to be scanned */ + int max_code; /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + if (nextlen == 0) max_count = 138, min_count = 3; + tree[max_code+1].Len = (ush)0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + s->bl_tree[curlen].Freq += count; + } else if (curlen != 0) { + if (curlen != prevlen) s->bl_tree[curlen].Freq++; + s->bl_tree[REP_3_6].Freq++; + } else if (count <= 10) { + s->bl_tree[REPZ_3_10].Freq++; + } else { + s->bl_tree[REPZ_11_138].Freq++; + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +local void send_tree (s, tree, max_code) + deflate_state *s; + ct_data *tree; /* the tree to be scanned */ + int max_code; /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen == 0) max_count = 138, min_count = 3; + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + do { send_code(s, curlen, s->bl_tree); } while (--count != 0); + + } else if (curlen != 0) { + if (curlen != prevlen) { + send_code(s, curlen, s->bl_tree); count--; + } + Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3); + + } else { + send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7); + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +local int build_bl_tree(s) + deflate_state *s; +{ + int max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, (ct_data *)s->dyn_ltree, s->l_desc.max_code); + scan_tree(s, (ct_data *)s->dyn_dtree, s->d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, (tree_desc *)(&(s->bl_desc))); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { + if (s->bl_tree[bl_order[max_blindex]].Len != 0) break; + } + /* Update opt_len to include the bit length tree and counts */ + s->opt_len += 3*(max_blindex+1) + 5+5+4; + Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + s->opt_len, s->static_len)); + + return max_blindex; +} + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +local void send_all_trees(s, lcodes, dcodes, blcodes) + deflate_state *s; + int lcodes, dcodes, blcodes; /* number of codes for each tree */ +{ + int rank; /* index in bl_order */ + + Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + "too many codes"); + Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes-1, 5); + send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s->bl_tree[bl_order[rank]].Len, 3); + } + Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */ + Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */ + Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +} + +/* =========================================================================== + * Send a stored block + */ +void _tr_stored_block(s, buf, stored_len, eof) + deflate_state *s; + charf *buf; /* input block */ + ulg stored_len; /* length of input block */ + int eof; /* true if this is the last block for a file */ +{ + send_bits(s, (STORED_BLOCK<<1)+eof, 3); /* send block type */ +#ifdef DEBUG + s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L; + s->compressed_len += (stored_len + 4) << 3; +#endif + copy_block(s, buf, (unsigned)stored_len, 1); /* with header */ +} + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + * The current inflate code requires 9 bits of lookahead. If the + * last two codes for the previous block (real code plus EOB) were coded + * on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode + * the last real code. In this case we send two empty static blocks instead + * of one. (There are no problems if the previous block is stored or fixed.) + * To simplify the code, we assume the worst case of last real code encoded + * on one bit only. + */ +void _tr_align(s) + deflate_state *s; +{ + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); +#ifdef DEBUG + s->compressed_len += 10L; /* 3 for block type, 7 for EOB */ +#endif + bi_flush(s); + /* Of the 10 bits for the empty block, we have already sent + * (10 - bi_valid) bits. The lookahead for the last real code (before + * the EOB of the previous block) was thus at least one plus the length + * of the EOB plus what we have just sent of the empty static block. + */ + if (1 + s->last_eob_len + 10 - s->bi_valid < 9) { + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); +#ifdef DEBUG + s->compressed_len += 10L; +#endif + bi_flush(s); + } + s->last_eob_len = 7; +} + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. + */ +void _tr_flush_block(s, buf, stored_len, eof) + deflate_state *s; + charf *buf; /* input block, or NULL if too old */ + ulg stored_len; /* length of input block */ + int eof; /* true if this is the last block for a file */ +{ + ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + int max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s->level > 0) { + + /* Check if the file is ascii or binary */ + if (s->data_type == Z_UNKNOWN) set_data_type(s); + + /* Construct the literal and distance trees */ + build_tree(s, (tree_desc *)(&(s->l_desc))); + Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + + build_tree(s, (tree_desc *)(&(s->d_desc))); + Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute first the block length in bytes*/ + opt_lenb = (s->opt_len+3+7)>>3; + static_lenb = (s->static_len+3+7)>>3; + + Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + s->last_lit)); + + if (static_lenb <= opt_lenb) opt_lenb = static_lenb; + + } else { + Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + +#ifdef FORCE_STORED + if (buf != (char*)0) { /* force stored block */ +#else + if (stored_len+4 <= opt_lenb && buf != (char*)0) { + /* 4: two words for the lengths */ +#endif + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block(s, buf, stored_len, eof); + +#ifdef FORCE_STATIC + } else if (static_lenb >= 0) { /* force static trees */ +#else + } else if (static_lenb == opt_lenb) { +#endif + send_bits(s, (STATIC_TREES<<1)+eof, 3); + compress_block(s, (ct_data *)static_ltree, (ct_data *)static_dtree); +#ifdef DEBUG + s->compressed_len += 3 + s->static_len; +#endif + } else { + send_bits(s, (DYN_TREES<<1)+eof, 3); + send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, + max_blindex+1); + compress_block(s, (ct_data *)s->dyn_ltree, (ct_data *)s->dyn_dtree); +#ifdef DEBUG + s->compressed_len += 3 + s->opt_len; +#endif + } + Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (eof) { + bi_windup(s); +#ifdef DEBUG + s->compressed_len += 7; /* align on byte boundary */ +#endif + } + Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + s->compressed_len-7*eof)); +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +int _tr_tally (s, dist, lc) + deflate_state *s; + unsigned dist; /* distance of matched string */ + unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ +{ + s->d_buf[s->last_lit] = (ush)dist; + s->l_buf[s->last_lit++] = (uch)lc; + if (dist == 0) { + /* lc is the unmatched char */ + s->dyn_ltree[lc].Freq++; + } else { + s->matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + Assert((ush)dist < (ush)MAX_DIST(s) && + (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s->dyn_dtree[d_code(dist)].Freq++; + } + +#ifdef TRUNCATE_BLOCK + /* Try to guess if it is profitable to stop the current block here */ + if ((s->last_lit & 0x1fff) == 0 && s->level > 2) { + /* Compute an upper bound for the compressed length */ + ulg out_length = (ulg)s->last_lit*8L; + ulg in_length = (ulg)((long)s->strstart - s->block_start); + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (ulg)s->dyn_dtree[dcode].Freq * + (5L+extra_dbits[dcode]); + } + out_length >>= 3; + Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", + s->last_lit, in_length, out_length, + 100L - out_length*100L/in_length)); + if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1; + } +#endif + return (s->last_lit == s->lit_bufsize-1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +local void compress_block(s, ltree, dtree) + deflate_state *s; + ct_data *ltree; /* literal tree */ + ct_data *dtree; /* distance tree */ +{ + unsigned dist; /* distance of matched string */ + int lc; /* match length or unmatched char (if dist == 0) */ + unsigned lx = 0; /* running index in l_buf */ + unsigned code; /* the code to send */ + int extra; /* number of extra bits to send */ + + if (s->last_lit != 0) do { + dist = s->d_buf[lx]; + lc = s->l_buf[lx++]; + if (dist == 0) { + send_code(s, lc, ltree); /* send a literal byte */ + Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code+LITERALS+1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra != 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra != 0) { + dist -= base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ + Assert(s->pending < s->lit_bufsize + 2*lx, "pendingBuf overflow"); + + } while (lx < s->last_lit); + + send_code(s, END_BLOCK, ltree); + s->last_eob_len = ltree[END_BLOCK].Len; +} + +/* =========================================================================== + * Set the data type to ASCII or BINARY, using a crude approximation: + * binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + * IN assertion: the fields freq of dyn_ltree are set and the total of all + * frequencies does not exceed 64K (to fit in an int on 16 bit machines). + */ +local void set_data_type(s) + deflate_state *s; +{ + int n = 0; + unsigned ascii_freq = 0; + unsigned bin_freq = 0; + while (n < 7) bin_freq += s->dyn_ltree[n++].Freq; + while (n < 128) ascii_freq += s->dyn_ltree[n++].Freq; + while (n < LITERALS) bin_freq += s->dyn_ltree[n++].Freq; + s->data_type = (Byte)(bin_freq > (ascii_freq >> 2) ? Z_BINARY : Z_ASCII); +} + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +local unsigned bi_reverse(code, len) + unsigned code; /* the value to invert */ + int len; /* its bit length */ +{ + register unsigned res = 0; + do { + res |= code & 1; + code >>= 1, res <<= 1; + } while (--len > 0); + return res >> 1; +} + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +local void bi_flush(s) + deflate_state *s; +{ + if (s->bi_valid == 16) { + put_short(s, s->bi_buf); + s->bi_buf = 0; + s->bi_valid = 0; + } else if (s->bi_valid >= 8) { + put_byte(s, (Byte)s->bi_buf); + s->bi_buf >>= 8; + s->bi_valid -= 8; + } +} + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +local void bi_windup(s) + deflate_state *s; +{ + if (s->bi_valid > 8) { + put_short(s, s->bi_buf); + } else if (s->bi_valid > 0) { + put_byte(s, (Byte)s->bi_buf); + } + s->bi_buf = 0; + s->bi_valid = 0; +#ifdef DEBUG + s->bits_sent = (s->bits_sent+7) & ~7; +#endif +} + +/* =========================================================================== + * Copy a stored block, storing first the length and its + * one's complement if requested. + */ +local void copy_block(s, buf, len, header) + deflate_state *s; + charf *buf; /* the input data */ + unsigned len; /* its length */ + int header; /* true if block header must be written */ +{ + bi_windup(s); /* align on byte boundary */ + s->last_eob_len = 8; /* enough lookahead for inflate */ + + if (header) { + put_short(s, (ush)len); + put_short(s, (ush)~len); +#ifdef DEBUG + s->bits_sent += 2*16; +#endif + } +#ifdef DEBUG + s->bits_sent += (ulg)len<<3; +#endif + while (len--) { + put_byte(s, *buf++); + } +} diff --git a/code/zlib/trees.h b/code/zlib/trees.h new file mode 100644 index 0000000..72facf9 --- /dev/null +++ b/code/zlib/trees.h @@ -0,0 +1,128 @@ +/* header created automatically with -DGEN_TREES_H */ + +local const ct_data static_ltree[L_CODES+2] = { +{{ 12},{ 8}}, {{140},{ 8}}, {{ 76},{ 8}}, {{204},{ 8}}, {{ 44},{ 8}}, +{{172},{ 8}}, {{108},{ 8}}, {{236},{ 8}}, {{ 28},{ 8}}, {{156},{ 8}}, +{{ 92},{ 8}}, {{220},{ 8}}, {{ 60},{ 8}}, {{188},{ 8}}, {{124},{ 8}}, +{{252},{ 8}}, {{ 2},{ 8}}, {{130},{ 8}}, {{ 66},{ 8}}, {{194},{ 8}}, +{{ 34},{ 8}}, {{162},{ 8}}, {{ 98},{ 8}}, {{226},{ 8}}, {{ 18},{ 8}}, +{{146},{ 8}}, {{ 82},{ 8}}, {{210},{ 8}}, {{ 50},{ 8}}, {{178},{ 8}}, +{{114},{ 8}}, {{242},{ 8}}, {{ 10},{ 8}}, {{138},{ 8}}, {{ 74},{ 8}}, +{{202},{ 8}}, {{ 42},{ 8}}, {{170},{ 8}}, {{106},{ 8}}, {{234},{ 8}}, +{{ 26},{ 8}}, {{154},{ 8}}, {{ 90},{ 8}}, {{218},{ 8}}, {{ 58},{ 8}}, +{{186},{ 8}}, {{122},{ 8}}, {{250},{ 8}}, {{ 6},{ 8}}, {{134},{ 8}}, +{{ 70},{ 8}}, {{198},{ 8}}, {{ 38},{ 8}}, {{166},{ 8}}, {{102},{ 8}}, +{{230},{ 8}}, {{ 22},{ 8}}, {{150},{ 8}}, {{ 86},{ 8}}, {{214},{ 8}}, +{{ 54},{ 8}}, {{182},{ 8}}, {{118},{ 8}}, {{246},{ 8}}, {{ 14},{ 8}}, +{{142},{ 8}}, {{ 78},{ 8}}, {{206},{ 8}}, {{ 46},{ 8}}, {{174},{ 8}}, +{{110},{ 8}}, {{238},{ 8}}, {{ 30},{ 8}}, {{158},{ 8}}, {{ 94},{ 8}}, +{{222},{ 8}}, {{ 62},{ 8}}, {{190},{ 8}}, {{126},{ 8}}, {{254},{ 8}}, +{{ 1},{ 8}}, {{129},{ 8}}, {{ 65},{ 8}}, {{193},{ 8}}, {{ 33},{ 8}}, +{{161},{ 8}}, {{ 97},{ 8}}, {{225},{ 8}}, {{ 17},{ 8}}, {{145},{ 8}}, +{{ 81},{ 8}}, {{209},{ 8}}, {{ 49},{ 8}}, {{177},{ 8}}, {{113},{ 8}}, +{{241},{ 8}}, {{ 9},{ 8}}, {{137},{ 8}}, {{ 73},{ 8}}, {{201},{ 8}}, +{{ 41},{ 8}}, {{169},{ 8}}, {{105},{ 8}}, {{233},{ 8}}, {{ 25},{ 8}}, +{{153},{ 8}}, {{ 89},{ 8}}, {{217},{ 8}}, {{ 57},{ 8}}, {{185},{ 8}}, +{{121},{ 8}}, {{249},{ 8}}, {{ 5},{ 8}}, {{133},{ 8}}, {{ 69},{ 8}}, +{{197},{ 8}}, {{ 37},{ 8}}, {{165},{ 8}}, {{101},{ 8}}, {{229},{ 8}}, +{{ 21},{ 8}}, {{149},{ 8}}, {{ 85},{ 8}}, {{213},{ 8}}, {{ 53},{ 8}}, +{{181},{ 8}}, {{117},{ 8}}, {{245},{ 8}}, {{ 13},{ 8}}, {{141},{ 8}}, +{{ 77},{ 8}}, {{205},{ 8}}, {{ 45},{ 8}}, {{173},{ 8}}, {{109},{ 8}}, +{{237},{ 8}}, {{ 29},{ 8}}, {{157},{ 8}}, {{ 93},{ 8}}, {{221},{ 8}}, +{{ 61},{ 8}}, {{189},{ 8}}, {{125},{ 8}}, {{253},{ 8}}, {{ 19},{ 9}}, +{{275},{ 9}}, {{147},{ 9}}, {{403},{ 9}}, {{ 83},{ 9}}, {{339},{ 9}}, +{{211},{ 9}}, {{467},{ 9}}, {{ 51},{ 9}}, {{307},{ 9}}, {{179},{ 9}}, +{{435},{ 9}}, {{115},{ 9}}, {{371},{ 9}}, {{243},{ 9}}, {{499},{ 9}}, +{{ 11},{ 9}}, {{267},{ 9}}, {{139},{ 9}}, {{395},{ 9}}, {{ 75},{ 9}}, +{{331},{ 9}}, {{203},{ 9}}, {{459},{ 9}}, {{ 43},{ 9}}, {{299},{ 9}}, +{{171},{ 9}}, {{427},{ 9}}, {{107},{ 9}}, {{363},{ 9}}, {{235},{ 9}}, +{{491},{ 9}}, {{ 27},{ 9}}, {{283},{ 9}}, {{155},{ 9}}, {{411},{ 9}}, +{{ 91},{ 9}}, {{347},{ 9}}, {{219},{ 9}}, {{475},{ 9}}, {{ 59},{ 9}}, +{{315},{ 9}}, {{187},{ 9}}, {{443},{ 9}}, {{123},{ 9}}, {{379},{ 9}}, +{{251},{ 9}}, {{507},{ 9}}, {{ 7},{ 9}}, {{263},{ 9}}, {{135},{ 9}}, +{{391},{ 9}}, {{ 71},{ 9}}, {{327},{ 9}}, {{199},{ 9}}, {{455},{ 9}}, +{{ 39},{ 9}}, {{295},{ 9}}, {{167},{ 9}}, {{423},{ 9}}, {{103},{ 9}}, +{{359},{ 9}}, {{231},{ 9}}, {{487},{ 9}}, {{ 23},{ 9}}, {{279},{ 9}}, +{{151},{ 9}}, {{407},{ 9}}, {{ 87},{ 9}}, {{343},{ 9}}, {{215},{ 9}}, +{{471},{ 9}}, {{ 55},{ 9}}, {{311},{ 9}}, {{183},{ 9}}, {{439},{ 9}}, +{{119},{ 9}}, {{375},{ 9}}, {{247},{ 9}}, {{503},{ 9}}, {{ 15},{ 9}}, +{{271},{ 9}}, {{143},{ 9}}, {{399},{ 9}}, {{ 79},{ 9}}, {{335},{ 9}}, +{{207},{ 9}}, {{463},{ 9}}, {{ 47},{ 9}}, {{303},{ 9}}, {{175},{ 9}}, +{{431},{ 9}}, {{111},{ 9}}, {{367},{ 9}}, {{239},{ 9}}, {{495},{ 9}}, +{{ 31},{ 9}}, {{287},{ 9}}, {{159},{ 9}}, {{415},{ 9}}, {{ 95},{ 9}}, +{{351},{ 9}}, {{223},{ 9}}, {{479},{ 9}}, {{ 63},{ 9}}, {{319},{ 9}}, +{{191},{ 9}}, {{447},{ 9}}, {{127},{ 9}}, {{383},{ 9}}, {{255},{ 9}}, +{{511},{ 9}}, {{ 0},{ 7}}, {{ 64},{ 7}}, {{ 32},{ 7}}, {{ 96},{ 7}}, +{{ 16},{ 7}}, {{ 80},{ 7}}, {{ 48},{ 7}}, {{112},{ 7}}, {{ 8},{ 7}}, +{{ 72},{ 7}}, {{ 40},{ 7}}, {{104},{ 7}}, {{ 24},{ 7}}, {{ 88},{ 7}}, +{{ 56},{ 7}}, {{120},{ 7}}, {{ 4},{ 7}}, {{ 68},{ 7}}, {{ 36},{ 7}}, +{{100},{ 7}}, {{ 20},{ 7}}, {{ 84},{ 7}}, {{ 52},{ 7}}, {{116},{ 7}}, +{{ 3},{ 8}}, {{131},{ 8}}, {{ 67},{ 8}}, {{195},{ 8}}, {{ 35},{ 8}}, +{{163},{ 8}}, {{ 99},{ 8}}, {{227},{ 8}} +}; + +local const ct_data static_dtree[D_CODES] = { +{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}}, +{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}}, +{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}}, +{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}}, +{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}}, +{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}} +}; + +const uch _dist_code[DIST_CODE_LEN] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, +10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, +11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, +12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, +18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 +}; + +const uch _length_code[MAX_MATCH-MIN_MATCH+1]= { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, +13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, +17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, +19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, +22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 +}; + +local const int base_length[LENGTH_CODES] = { +0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, +64, 80, 96, 112, 128, 160, 192, 224, 0 +}; + +local const int base_dist[D_CODES] = { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 +}; + diff --git a/code/zlib/uncompr.c b/code/zlib/uncompr.c new file mode 100644 index 0000000..4815e83 --- /dev/null +++ b/code/zlib/uncompr.c @@ -0,0 +1,61 @@ +/* uncompr.c -- decompress a memory buffer + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: uncompr.c,v 1.1 2003/03/19 19:08:52 osman Exp $ */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zlib.h" + +/* =========================================================================== + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ +int ZEXPORT uncompress (dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} diff --git a/code/zlib/zconf.h b/code/zlib/zconf.h new file mode 100644 index 0000000..b9c5a44 --- /dev/null +++ b/code/zlib/zconf.h @@ -0,0 +1,279 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: zconf.h,v 1.1 2003/03/19 19:08:52 osman Exp $ */ + +#ifndef _ZCONF_H +#define _ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateReset z_inflateReset +# define compress z_compress +# define compress2 z_compress2 +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table + +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) +# define WIN32 +#endif +#if defined(__GNUC__) || defined(WIN32) || defined(__386__) || defined(i386) +# ifndef __32BIT__ +# define __32BIT__ +# endif +#endif +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#if defined(MSDOS) && !defined(__32BIT__) +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#if (defined(MSDOS) || defined(_WINDOWS) || defined(WIN32)) && !defined(STDC) +# define STDC +#endif +#if defined(__STDC__) || defined(__cplusplus) || defined(__OS2__) +# ifndef STDC +# define STDC +# endif +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__) || defined(applec) ||defined(THINK_C) ||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Old Borland C incorrectly complains about missing returns: */ +#if defined(__BORLANDC__) && (__BORLANDC__ < 0x500) +# define NEED_DUMMY_RETURN +#endif + + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#if (defined(M_I86SM) || defined(M_I86MM)) && !defined(__32BIT__) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +#endif +#if defined(__BORLANDC__) && (defined(__SMALL__) || defined(__MEDIUM__)) +# ifndef __32BIT__ +# define SMALL_MEDIUM +# define FAR _far +# endif +#endif + +/* Compile with -DZLIB_DLL for Windows DLL support */ +#if defined(ZLIB_DLL) +# if defined(_WINDOWS) || defined(WINDOWS) +# ifdef FAR +# undef FAR +# endif +# include +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR _cdecl _export +# endif +# endif +# if defined (__BORLANDC__) +# if (__BORLANDC__ >= 0x0500) && defined (WIN32) +# include +# define ZEXPORT __declspec(dllexport) WINAPI +# define ZEXPORTRVA __declspec(dllexport) WINAPIV +# else +# if defined (_Windows) && defined (__DLL__) +# define ZEXPORT _export +# define ZEXPORTVA _export +# endif +# endif +# endif +#endif + +#if defined (__BEOS__) +# if defined (ZLIB_DLL) +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +#endif + +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif +#ifndef ZEXTERN +# define ZEXTERN extern +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(MACOS) && !defined(TARGET_OS_MAC) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#ifdef HAVE_UNISTD_H +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(inflate_blocks,"INBL") +# pragma map(inflate_blocks_new,"INBLNE") +# pragma map(inflate_blocks_free,"INBLFR") +# pragma map(inflate_blocks_reset,"INBLRE") +# pragma map(inflate_codes_free,"INCOFR") +# pragma map(inflate_codes,"INCO") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_flush,"INFLU") +# pragma map(inflate_mask,"INMA") +# pragma map(inflate_set_dictionary,"INSEDI2") +# pragma map(inflate_copyright,"INCOPY") +# pragma map(inflate_trees_bits,"INTRBI") +# pragma map(inflate_trees_dynamic,"INTRDY") +# pragma map(inflate_trees_fixed,"INTRFI") +# pragma map(inflate_trees_free,"INTRFR") +#endif + +#endif /* _ZCONF_H */ diff --git a/code/zlib/zlib.h b/code/zlib/zlib.h new file mode 100644 index 0000000..49f56b4 --- /dev/null +++ b/code/zlib/zlib.h @@ -0,0 +1,893 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.1.3, July 9th, 1998 + + Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +#ifndef _ZLIB_H +#define _ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_VERSION "1.1.3" + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total nb of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total nb of bytes output so far */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: ascii or binary */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +/* Allowed flush values; see deflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_ASCII 1 +#define Z_UNKNOWN 2 +/* Possible values of the data_type field */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + the compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + 0.1% larger than avail_in plus 12 bytes. If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update data_type if it can make a good guess about + the input data type (Z_ASCII or Z_BINARY). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may some + introduce some output latency (reading input without producing any output) + except when forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much + output as possible to the output buffer. The flushing behavior of inflate is + not specified for values of the flush parameter other than Z_SYNC_FLUSH + and Z_FINISH, but the current implementation actually flushes as much output + as possible anyway. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster routine + may be used for the single inflate() call. + + If a preset dictionary is needed at this point (see inflateSetDictionary + below), inflate sets strm-adler to the adler32 checksum of the + dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise + it sets strm->adler to the adler32 checksum of all output produced + so far (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or + an error code as described below. At the end of the stream, inflate() + checks that its computed adler32 checksum is equal to that saved by the + compressor and returns Z_STREAM_END only if the checksum is correct. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect + adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent + (for example if next_in or next_out was NULL), Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if no progress is possible or if there was not + enough room in the output buffer when Z_FINISH is used. In the Z_DATA_ERROR + case, the application may then call inflateSync to look for a good + compression block. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), or Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match). Filtered data consists mostly of small values with a + somewhat random distribution. In this case, the compression algorithm is + tuned to compress them better. The effect of Z_FILTERED is to force more + Huffman coding and less string matching; it is somewhat intermediate + between Z_DEFAULT and Z_HUFFMAN_ONLY. The strategy parameter only affects + the compression ratio but not the correctness of the compressed output even + if it is not set appropriately. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. + + Upon return of this function, strm->adler is set to the Adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The Adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. If a compressed stream with a larger window size is given as + input, inflate() will return with the error code Z_DATA_ERROR instead of + trying to allocate a larger window. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a negative + memLevel). msg is set to null if there is no error message. inflateInit2 + does not perform any decompression apart from reading the zlib header if + present: this will be done by inflate(). (So next_in and avail_in may be + modified, but next_out and avail_out are unchanged.) +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate + if this call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the Adler32 value returned by this call of + inflate. The compressor and decompressor must use exactly the same + dictionary (see deflateSetDictionary). + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect Adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least 0.1% larger than + sourceLen plus 12 bytes. Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least 0.1% larger than sourceLen plus + 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted. +*/ + + +typedef voidp gzFile; + +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h". (See the description + of deflateInit2 for more information about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + const voidp buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); + +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running crc with the bytes buf[0..len-1] and return the updated + crc. If buf is NULL, this function returns the required initial value + for the crc. Pre- and post-conditioning (one's complement) is performed + within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) + + +#if !defined(_Z_UTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; /* hack for buggy compilers */ +#endif + +ZEXTERN const char * ZEXPORT zError OF((int err)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z)); +ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZLIB_H */ diff --git a/code/zlib/zutil.c b/code/zlib/zutil.c new file mode 100644 index 0000000..7a47155 --- /dev/null +++ b/code/zlib/zutil.c @@ -0,0 +1,228 @@ +/* zutil.c -- target dependent utility functions for the compression library + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id: zutil.c,v 1.1 2003/03/19 19:08:52 osman Exp $ */ +#ifdef __MWERKS__ +#pragma cplusplus off +#endif + +#include "zutil.h" + +struct internal_state {int dummy;}; /* for buggy compilers */ + +#ifndef STDC +extern void exit OF((int)); +#endif + +const char *z_errmsg[10] = { +"need dictionary", /* Z_NEED_DICT 2 */ +"stream end", /* Z_STREAM_END 1 */ +"", /* Z_OK 0 */ +"file error", /* Z_ERRNO (-1) */ +"stream error", /* Z_STREAM_ERROR (-2) */ +"data error", /* Z_DATA_ERROR (-3) */ +"insufficient memory", /* Z_MEM_ERROR (-4) */ +"buffer error", /* Z_BUF_ERROR (-5) */ +"incompatible version",/* Z_VERSION_ERROR (-6) */ +""}; + + +const char * ZEXPORT zlibVersion() +{ + return ZLIB_VERSION; +} + +#ifdef DEBUG + +# ifndef verbose +# define verbose 0 +# endif +int z_verbose = verbose; + +void z_error (m) + char *m; +{ + fprintf(stderr, "%s\n", m); + exit(1); +} +#endif + +/* exported to allow conversion of error code to string for compress() and + * uncompress() + */ +const char * ZEXPORT zError(err) + int err; +{ + return ERR_MSG(err); +} + + +#ifndef HAVE_MEMCPY + +void zmemcpy(dest, source, len) + Bytef* dest; + const Bytef* source; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = *source++; /* ??? to be unrolled */ + } while (--len != 0); +} + +int zmemcmp(s1, s2, len) + const Bytef* s1; + const Bytef* s2; + uInt len; +{ + uInt j; + + for (j = 0; j < len; j++) { + if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1; + } + return 0; +} + +void zmemzero(dest, len) + Bytef* dest; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = 0; /* ??? to be unrolled */ + } while (--len != 0); +} +#endif + +#ifdef __TURBOC__ +#if (defined( __BORLANDC__) || !defined(SMALL_MEDIUM)) && !defined(__32BIT__) +/* Small and medium model in Turbo C are for now limited to near allocation + * with reduced MAX_WBITS and MAX_MEM_LEVEL + */ +# define MY_ZCALLOC + +/* Turbo C malloc() does not allow dynamic allocation of 64K bytes + * and farmalloc(64K) returns a pointer with an offset of 8, so we + * must fix the pointer. Warning: the pointer must be put back to its + * original form in order to free it, use zcfree(). + */ + +#define MAX_PTR 10 +/* 10*64K = 640K */ + +local int next_ptr = 0; + +typedef struct ptr_table_s { + voidpf org_ptr; + voidpf new_ptr; +} ptr_table; + +local ptr_table table[MAX_PTR]; +/* This table is used to remember the original form of pointers + * to large buffers (64K). Such pointers are normalized with a zero offset. + * Since MSDOS is not a preemptive multitasking OS, this table is not + * protected from concurrent access. This hack doesn't work anyway on + * a protected system like OS/2. Use Microsoft C instead. + */ + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + voidpf buf = opaque; /* just to make some compilers happy */ + ulg bsize = (ulg)items*size; + + /* If we allocate less than 65520 bytes, we assume that farmalloc + * will return a usable pointer which doesn't have to be normalized. + */ + if (bsize < 65520L) { + buf = farmalloc(bsize); + if (*(ush*)&buf != 0) return buf; + } else { + buf = farmalloc(bsize + 16L); + } + if (buf == NULL || next_ptr >= MAX_PTR) return NULL; + table[next_ptr].org_ptr = buf; + + /* Normalize the pointer to seg:0 */ + *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4; + *(ush*)&buf = 0; + table[next_ptr++].new_ptr = buf; + return buf; +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + int n; + if (*(ush*)&ptr != 0) { /* object < 64K */ + farfree(ptr); + return; + } + /* Find the original pointer */ + for (n = 0; n < next_ptr; n++) { + if (ptr != table[n].new_ptr) continue; + + farfree(table[n].org_ptr); + while (++n < next_ptr) { + table[n-1] = table[n]; + } + next_ptr--; + return; + } + ptr = opaque; /* just to make some compilers happy */ + Assert(0, "zcfree: ptr not found"); +} +#endif +#endif /* __TURBOC__ */ + + +#if defined(M_I86) && !defined(__32BIT__) +/* Microsoft C in 16-bit mode */ + +# define MY_ZCALLOC + +#if (!defined(_MSC_VER) || (_MSC_VER <= 600)) +# define _halloc halloc +# define _hfree hfree +#endif + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + return _halloc((long)items, size); +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + _hfree(ptr); +} + +#endif /* MSC */ + + +#ifndef MY_ZCALLOC /* Any system without a special alloc function */ + +#ifndef STDC +extern voidp calloc OF((uInt items, uInt size)); +extern void free OF((voidpf ptr)); +#endif + +voidpf zcalloc (opaque, items, size) + voidpf opaque; + unsigned items; + unsigned size; +{ + if (opaque) items += size - size; /* make compiler happy */ + return (voidpf)calloc(items, size); +} + +void zcfree (opaque, ptr) + voidpf opaque; + voidpf ptr; +{ + free(ptr); + if (opaque) return; /* make compiler happy */ +} + +#endif /* MY_ZCALLOC */ diff --git a/code/zlib/zutil.h b/code/zlib/zutil.h new file mode 100644 index 0000000..92718bb --- /dev/null +++ b/code/zlib/zutil.h @@ -0,0 +1,220 @@ +/* zutil.h -- internal interface and configuration of the compression library + * Copyright (C) 1995-1998 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id: zutil.h,v 1.1 2003/03/19 19:08:52 osman Exp $ */ + +#ifndef _Z_UTIL_H +#define _Z_UTIL_H + +#include "zlib.h" + +#ifdef STDC +# include +# include +# include +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include +#endif + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +typedef unsigned char uch; +typedef uch FAR uchf; +typedef unsigned short ush; +typedef ush FAR ushf; +typedef unsigned long ulg; + +extern const char *z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + +#ifdef MSDOS +# define OS_CODE 0x00 +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if(__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include +# endif +#endif + +#ifdef OS2 +# define OS_CODE 0x06 +#endif + +#ifdef WIN32 /* Window 95 & Windows NT */ +# define OS_CODE 0x0b +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 0x02 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#ifdef AMIGA +# define OS_CODE 0x01 +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 0x05 +#endif + +#if defined(MACOS) || defined(TARGET_OS_MAC) +# define OS_CODE 0x07 +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define OS_CODE 0x0F +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#if defined(_BEOS_) || defined(RISCOS) +# define fdopen(fd,mode) NULL /* No fdopen() */ +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 600)) +# define fdopen(fd,type) _fdopen(fd,type) +#endif + + + /* Common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#ifdef HAVE_STRERROR + extern char *strerror OF((int)); +# define zstrerror(errnum) strerror(errnum) +#else +# define zstrerror(errnum) "" +#endif + +#if defined(pyr) +# define NO_MEMCPY +#endif +#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) + /* Use our own functions for small and medium model with MSC <= 5.0. + * You may have to use the same strategy for Borland C (untested). + * The __SC__ check is for Symantec. + */ +# define NO_MEMCPY +#endif +#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) +# define HAVE_MEMCPY +#endif +#ifdef HAVE_MEMCPY +# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ +# define zmemcpy _fmemcpy +# define zmemcmp _fmemcmp +# define zmemzero(dest, len) _fmemset(dest, 0, len) +# else +# define zmemcpy memcpy +# define zmemcmp memcmp +# define zmemzero(dest, len) memset(dest, 0, len) +# endif +#else + extern void zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); + extern int zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); + extern void zmemzero OF((Bytef* dest, uInt len)); +#endif + +/* Diagnostic functions */ +#ifdef DEBUG +# include + extern int z_verbose; + extern void z_error OF((char *m)); +# define Assert(cond,msg) {if(!(cond)) z_error(msg);} +# define Trace(x) {if (z_verbose>=0) fprintf x ;} +# define Tracev(x) {if (z_verbose>0) fprintf x ;} +# define Tracevv(x) {if (z_verbose>1) fprintf x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + + +typedef uLong (ZEXPORT *check_func) OF((uLong check, const Bytef *buf, + uInt len)); +voidpf zcalloc OF((voidpf opaque, unsigned items, unsigned size)); +void zcfree OF((voidpf opaque, voidpf ptr)); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + +#endif /* _Z_UTIL_H */ diff --git a/code/zlib32/deflate.cpp b/code/zlib32/deflate.cpp new file mode 100644 index 0000000..7f46519 --- /dev/null +++ b/code/zlib32/deflate.cpp @@ -0,0 +1,2078 @@ +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" + +#include "zip.h" +#include "deflate.h" + +#ifdef _TIMING +int totalDeflateTime[Z_MAX_COMPRESSION + 1]; +int totalDeflateCount[Z_MAX_COMPRESSION + 1]; +#endif + +// If you use the zlib library in a product, an acknowledgment is welcome +// in the documentation of your product. If for some reason you cannot +// include such an acknowledgment, I would appreciate that you keep this +// copyright string in the executable of your product. +const char deflate_copyright[] = "Deflate 1.1.3 Copyright 1995-1998 Jean-loup Gailly "; + +static const char *deflate_error = "OK"; + +// ALGORITHM +// +// The "deflation" process depends on being able to identify portions +// of the input text which are identical to earlier input (within a +// sliding window trailing behind the input currently being processed). +// +// The most straightforward technique turns out to be the fastest for +// most input files: try all possible matches and select the longest. +// The key feature of this algorithm is that insertions into the string +// dictionary are very simple and thus fast, and deletions are avoided +// completely. Insertions are performed at each input character, whereas +// string matches are performed only when the previous match ends. So it +// is preferable to spend more time in matches to allow very fast string +// insertions and avoid deletions. The matching algorithm for small +// strings is inspired from that of Rabin & Karp. A brute force approach +// is used to find longer strings when a small match has been found. +// A similar algorithm is used in comic (by Jan-Mark Wams) and freeze +// (by Leonid Broukhis). +// +// ACKNOWLEDGEMENTS +// +// The idea of lazy evaluation of matches is due to Jan-Mark Wams, and +// I found it in 'freeze' written by Leonid Broukhis. +// Thanks to many people for bug reports and testing. +// +// REFERENCES +// +// Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". +// Available in ftp://ds.internic.net/rfc/rfc1951.txt +// +// A description of the Rabin and Karp algorithm is given in the book +// "Algorithms" by R. Sedgewick, Addison-Wesley, p252. +// +// Fiala,E.R., and Greene,D.H. +// Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 + +// =============================================================================== +// A word is an index in the character window. We use short instead of int to +// save space in the various tables. ulong is used only for parameter passing. + +// The static literal tree. Since the bit lengths are imposed, there is no +// need for the L_CODES extra codes used during heap construction. However +// The codes 286 and 287 are needed to build a canonical tree (see _tr_init +// below). +static const ct_data static_ltree[L_CODES + 2] = +{ + {{ 12 }, { 8 }}, {{ 140 }, { 8 }}, {{ 76 }, { 8 }}, {{ 204 }, { 8 }}, {{ 44 }, { 8 }}, + {{ 172 }, { 8 }}, {{ 108 }, { 8 }}, {{ 236 }, { 8 }}, {{ 28 }, { 8 }}, {{ 156 }, { 8 }}, + {{ 92 }, { 8 }}, {{ 220 }, { 8 }}, {{ 60 }, { 8 }}, {{ 188 }, { 8 }}, {{ 124 }, { 8 }}, + {{ 252 }, { 8 }}, {{ 2 }, { 8 }}, {{ 130 }, { 8 }}, {{ 66 }, { 8 }}, {{ 194 }, { 8 }}, + {{ 34 }, { 8 }}, {{ 162 }, { 8 }}, {{ 98 }, { 8 }}, {{ 226 }, { 8 }}, {{ 18 }, { 8 }}, + {{ 146 }, { 8 }}, {{ 82 }, { 8 }}, {{ 210 }, { 8 }}, {{ 50 }, { 8 }}, {{ 178 }, { 8 }}, + {{ 114 }, { 8 }}, {{ 242 }, { 8 }}, {{ 10 }, { 8 }}, {{ 138 }, { 8 }}, {{ 74 }, { 8 }}, + {{ 202 }, { 8 }}, {{ 42 }, { 8 }}, {{ 170 }, { 8 }}, {{ 106 }, { 8 }}, {{ 234 }, { 8 }}, + {{ 26 }, { 8 }}, {{ 154 }, { 8 }}, {{ 90 }, { 8 }}, {{ 218 }, { 8 }}, {{ 58 }, { 8 }}, + {{ 186 }, { 8 }}, {{ 122 }, { 8 }}, {{ 250 }, { 8 }}, {{ 6 }, { 8 }}, {{ 134 }, { 8 }}, + {{ 70 }, { 8 }}, {{ 198 }, { 8 }}, {{ 38 }, { 8 }}, {{ 166 }, { 8 }}, {{ 102 }, { 8 }}, + {{ 230 }, { 8 }}, {{ 22 }, { 8 }}, {{ 150 }, { 8 }}, {{ 86 }, { 8 }}, {{ 214 }, { 8 }}, + {{ 54 }, { 8 }}, {{ 182 }, { 8 }}, {{ 118 }, { 8 }}, {{ 246 }, { 8 }}, {{ 14 }, { 8 }}, + {{ 142 }, { 8 }}, {{ 78 }, { 8 }}, {{ 206 }, { 8 }}, {{ 46 }, { 8 }}, {{ 174 }, { 8 }}, + {{ 110 }, { 8 }}, {{ 238 }, { 8 }}, {{ 30 }, { 8 }}, {{ 158 }, { 8 }}, {{ 94 }, { 8 }}, + {{ 222 }, { 8 }}, {{ 62 }, { 8 }}, {{ 190 }, { 8 }}, {{ 126 }, { 8 }}, {{ 254 }, { 8 }}, + {{ 1 }, { 8 }}, {{ 129 }, { 8 }}, {{ 65 }, { 8 }}, {{ 193 }, { 8 }}, {{ 33 }, { 8 }}, + {{ 161 }, { 8 }}, {{ 97 }, { 8 }}, {{ 225 }, { 8 }}, {{ 17 }, { 8 }}, {{ 145 }, { 8 }}, + {{ 81 }, { 8 }}, {{ 209 }, { 8 }}, {{ 49 }, { 8 }}, {{ 177 }, { 8 }}, {{ 113 }, { 8 }}, + {{ 241 }, { 8 }}, {{ 9 }, { 8 }}, {{ 137 }, { 8 }}, {{ 73 }, { 8 }}, {{ 201 }, { 8 }}, + {{ 41 }, { 8 }}, {{ 169 }, { 8 }}, {{ 105 }, { 8 }}, {{ 233 }, { 8 }}, {{ 25 }, { 8 }}, + {{ 153 }, { 8 }}, {{ 89 }, { 8 }}, {{ 217 }, { 8 }}, {{ 57 }, { 8 }}, {{ 185 }, { 8 }}, + {{ 121 }, { 8 }}, {{ 249 }, { 8 }}, {{ 5 }, { 8 }}, {{ 133 }, { 8 }}, {{ 69 }, { 8 }}, + {{ 197 }, { 8 }}, {{ 37 }, { 8 }}, {{ 165 }, { 8 }}, {{ 101 }, { 8 }}, {{ 229 }, { 8 }}, + {{ 21 }, { 8 }}, {{ 149 }, { 8 }}, {{ 85 }, { 8 }}, {{ 213 }, { 8 }}, {{ 53 }, { 8 }}, + {{ 181 }, { 8 }}, {{ 117 }, { 8 }}, {{ 245 }, { 8 }}, {{ 13 }, { 8 }}, {{ 141 }, { 8 }}, + {{ 77 }, { 8 }}, {{ 205 }, { 8 }}, {{ 45 }, { 8 }}, {{ 173 }, { 8 }}, {{ 109 }, { 8 }}, + {{ 237 }, { 8 }}, {{ 29 }, { 8 }}, {{ 157 }, { 8 }}, {{ 93 }, { 8 }}, {{ 221 }, { 8 }}, + {{ 61 }, { 8 }}, {{ 189 }, { 8 }}, {{ 125 }, { 8 }}, {{ 253 }, { 8 }}, {{ 19 }, { 9 }}, + {{ 275 }, { 9 }}, {{ 147 }, { 9 }}, {{ 403 }, { 9 }}, {{ 83 }, { 9 }}, {{ 339 }, { 9 }}, + {{ 211 }, { 9 }}, {{ 467 }, { 9 }}, {{ 51 }, { 9 }}, {{ 307 }, { 9 }}, {{ 179 }, { 9 }}, + {{ 435 }, { 9 }}, {{ 115 }, { 9 }}, {{ 371 }, { 9 }}, {{ 243 }, { 9 }}, {{ 499 }, { 9 }}, + {{ 11 }, { 9 }}, {{ 267 }, { 9 }}, {{ 139 }, { 9 }}, {{ 395 }, { 9 }}, {{ 75 }, { 9 }}, + {{ 331 }, { 9 }}, {{ 203 }, { 9 }}, {{ 459 }, { 9 }}, {{ 43 }, { 9 }}, {{ 299 }, { 9 }}, + {{ 171 }, { 9 }}, {{ 427 }, { 9 }}, {{ 107 }, { 9 }}, {{ 363 }, { 9 }}, {{ 235 }, { 9 }}, + {{ 491 }, { 9 }}, {{ 27 }, { 9 }}, {{ 283 }, { 9 }}, {{ 155 }, { 9 }}, {{ 411 }, { 9 }}, + {{ 91 }, { 9 }}, {{ 347 }, { 9 }}, {{ 219 }, { 9 }}, {{ 475 }, { 9 }}, {{ 59 }, { 9 }}, + {{ 315 }, { 9 }}, {{ 187 }, { 9 }}, {{ 443 }, { 9 }}, {{ 123 }, { 9 }}, {{ 379 }, { 9 }}, + {{ 251 }, { 9 }}, {{ 507 }, { 9 }}, {{ 7 }, { 9 }}, {{ 263 }, { 9 }}, {{ 135 }, { 9 }}, + {{ 391 }, { 9 }}, {{ 71 }, { 9 }}, {{ 327 }, { 9 }}, {{ 199 }, { 9 }}, {{ 455 }, { 9 }}, + {{ 39 }, { 9 }}, {{ 295 }, { 9 }}, {{ 167 }, { 9 }}, {{ 423 }, { 9 }}, {{ 103 }, { 9 }}, + {{ 359 }, { 9 }}, {{ 231 }, { 9 }}, {{ 487 }, { 9 }}, {{ 23 }, { 9 }}, {{ 279 }, { 9 }}, + {{ 151 }, { 9 }}, {{ 407 }, { 9 }}, {{ 87 }, { 9 }}, {{ 343 }, { 9 }}, {{ 215 }, { 9 }}, + {{ 471 }, { 9 }}, {{ 55 }, { 9 }}, {{ 311 }, { 9 }}, {{ 183 }, { 9 }}, {{ 439 }, { 9 }}, + {{ 119 }, { 9 }}, {{ 375 }, { 9 }}, {{ 247 }, { 9 }}, {{ 503 }, { 9 }}, {{ 15 }, { 9 }}, + {{ 271 }, { 9 }}, {{ 143 }, { 9 }}, {{ 399 }, { 9 }}, {{ 79 }, { 9 }}, {{ 335 }, { 9 }}, + {{ 207 }, { 9 }}, {{ 463 }, { 9 }}, {{ 47 }, { 9 }}, {{ 303 }, { 9 }}, {{ 175 }, { 9 }}, + {{ 431 }, { 9 }}, {{ 111 }, { 9 }}, {{ 367 }, { 9 }}, {{ 239 }, { 9 }}, {{ 495 }, { 9 }}, + {{ 31 }, { 9 }}, {{ 287 }, { 9 }}, {{ 159 }, { 9 }}, {{ 415 }, { 9 }}, {{ 95 }, { 9 }}, + {{ 351 }, { 9 }}, {{ 223 }, { 9 }}, {{ 479 }, { 9 }}, {{ 63 }, { 9 }}, {{ 319 }, { 9 }}, + {{ 191 }, { 9 }}, {{ 447 }, { 9 }}, {{ 127 }, { 9 }}, {{ 383 }, { 9 }}, {{ 255 }, { 9 }}, + {{ 511 }, { 9 }}, {{ 0 }, { 7 }}, {{ 64 }, { 7 }}, {{ 32 }, { 7 }}, {{ 96 }, { 7 }}, + {{ 16 }, { 7 }}, {{ 80 }, { 7 }}, {{ 48 }, { 7 }}, {{ 112 }, { 7 }}, {{ 8 }, { 7 }}, + {{ 72 }, { 7 }}, {{ 40 }, { 7 }}, {{ 104 }, { 7 }}, {{ 24 }, { 7 }}, {{ 88 }, { 7 }}, + {{ 56 }, { 7 }}, {{ 120 }, { 7 }}, {{ 4 }, { 7 }}, {{ 68 }, { 7 }}, {{ 36 }, { 7 }}, + {{ 100 }, { 7 }}, {{ 20 }, { 7 }}, {{ 84 }, { 7 }}, {{ 52 }, { 7 }}, {{ 116 }, { 7 }}, + {{ 3 }, { 8 }}, {{ 131 }, { 8 }}, {{ 67 }, { 8 }}, {{ 195 }, { 8 }}, {{ 35 }, { 8 }}, + {{ 163 }, { 8 }}, {{ 99 }, { 8 }}, {{ 227 }, { 8 }} +}; + +// The static distance tree. (Actually a trivial tree since all codes use 5 bits.) +static const ct_data static_dtree[D_CODES] = +{ + {{ 0 }, { 5 }}, {{ 16 }, { 5 }}, {{ 8 },{ 5 }}, {{ 24 },{ 5 }}, {{ 4 },{ 5 }}, + {{ 20 }, { 5 }}, {{ 12 }, { 5 }}, {{ 28 },{ 5 }}, {{ 2 },{ 5 }}, {{ 18 },{ 5 }}, + {{ 10 }, { 5 }}, {{ 26 }, { 5 }}, {{ 6 },{ 5 }}, {{ 22 },{ 5 }}, {{ 14 },{ 5 }}, + {{ 30 }, { 5 }}, {{ 1 }, { 5 }}, {{ 17 },{ 5 }}, {{ 9 },{ 5 }}, {{ 25 },{ 5 }}, + {{ 5 }, { 5 }}, {{ 21 }, { 5 }}, {{ 13 },{ 5 }}, {{ 29 },{ 5 }}, {{ 3 },{ 5 }}, + {{ 19 }, { 5 }}, {{ 11 }, { 5 }}, {{ 27 },{ 5 }}, {{ 7 },{ 5 }}, {{ 23 },{ 5 }} +}; + +// Distance codes. The first 256 values correspond to the distances +// 3 .. 258, the last 256 values correspond to the top 8 bits of +// the 15 bit distances. +static const byte tr_dist_code[DIST_CODE_LEN] = +{ + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, + 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 +}; + +// length code for each normalized match length (0 == MIN_MATCH) +static const byte tr_length_code[MAX_MATCH - MIN_MATCH + 1] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, + 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, + 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, + 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 +}; + +// First normalized length for each code (0 = MIN_MATCH) +static const int base_length[LENGTH_CODES] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, + 64, 80, 96, 112, 128, 160, 192, 224, 0 +}; + +// First normalized distance for each code (0 = distance of 1) +static const int base_dist[D_CODES] = +{ + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 +}; + +// Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 +// For deflate_fast() (levels <= 3) good is ignored and lazy has a different +// meaning. +static block_state deflate_stored(deflate_state *s, EFlush flush); +static block_state deflate_fast(deflate_state *s, EFlush flush); +static block_state deflate_slow(deflate_state *s, EFlush flush); + +// Values for max_lazy_match, good_match and max_chain_length, depending on +// the desired pack level (0..9). The values given below have been tuned to +// exclude worst case performance for pathological files. Better values may be +// found for specific files. +static const config configuration_table[10] = +{ + // good lazy nice chain + { 0, 0, 0, 0, deflate_stored }, // store only + + { 4, 4, 8, 4, deflate_fast }, // maximum speed, no lazy matches + { 4, 5, 16, 8, deflate_fast }, + { 4, 6, 32, 32, deflate_fast }, + + { 4, 4, 16, 16, deflate_slow }, // lazy matches + { 8, 16, 32, 32, deflate_slow }, + { 8, 16, 128, 128, deflate_slow }, + { 8, 32, 128, 256, deflate_slow }, + { 32, 128, 258, 1024, deflate_slow }, + { 32, 258, 258, 4096, deflate_slow } // maximum compression +}; + +// extra bits for each length code +static ulong extra_lbits[LENGTH_CODES] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 +}; + +// Extra bits for distance codes +const ulong extra_dbits[D_CODES] = +{ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 +}; + +// extra bits for each bit length code +static ulong extra_blbits[BL_CODES] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7 +}; + +// The lengths of the bit length codes are sent in order of decreasing +// probability, to avoid transmitting the lengths for unused bit length codes. +static const byte bl_order[BL_CODES] = +{ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 +}; + +static static_tree_desc static_l_desc = +{ + static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_WBITS +}; + +static static_tree_desc static_d_desc = +{ + static_dtree, extra_dbits, 0, D_CODES, MAX_WBITS +}; + +static static_tree_desc static_bl_desc = +{ + NULL, extra_blbits, 0, BL_CODES, MAX_BL_BITS +}; + +// =============================================================================== +// Output bytes to the output stream. Inlined for speed +// =============================================================================== + +inline void put_byte(deflate_state *s, const byte c) +{ + s->pending_buf[s->pending++] = c; +} + +// Fixme: write as 1 short +inline void put_short(deflate_state *s, const word w) +{ + s->pending_buf[s->pending++] = (byte)(w & 0xff); + s->pending_buf[s->pending++] = (byte)(w >> 8); +} + +inline void put_shortMSB(deflate_state *s, const word w) +{ + s->pending_buf[s->pending++] = (byte)(w >> 8); + s->pending_buf[s->pending++] = (byte)(w & 0xff); +} + +inline void put_longMSB(deflate_state *s, const ulong l) +{ + s->pending_buf[s->pending++] = (byte)(l >> 24); + s->pending_buf[s->pending++] = (byte)(l >> 16); + s->pending_buf[s->pending++] = (byte)(l >> 8); + s->pending_buf[s->pending++] = (byte)(l & 0xff); +} + +// =============================================================================== +// Send a value on a given number of bits. +// IN assertion: length <= 16 and value fits in length bits. +// =============================================================================== + +static void send_bits(deflate_state *s, const ulong val, const ulong len) +{ + assert(len <= 16); + assert(val <= 65536); + + if(s->bi_valid > (BUF_SIZE - len)) + { + s->bi_buf |= val << s->bi_valid; + put_short(s, s->bi_buf); + s->bi_buf = (word)(val >> (BUF_SIZE - s->bi_valid)); + s->bi_valid += len - BUF_SIZE; + } + else + { + s->bi_buf |= val << s->bi_valid; + s->bi_valid += len; + } +} + +// =============================================================================== +// Initialize a new block. +// =============================================================================== + +static void init_block(deflate_state *s) +{ + int n; // iterates over tree elements + + // Initialize the trees. + for(n = 0; n < L_CODES; n++) + { + s->dyn_ltree[n].fc.freq = 0; + } + for(n = 0; n < D_CODES; n++) + { + s->dyn_dtree[n].fc.freq = 0; + } + for(n = 0; n < BL_CODES; n++) + { + s->bl_tree[n].fc.freq = 0; + } + s->dyn_ltree[END_BLOCK].fc.freq = 1; + s->opt_len = 0; + s->static_len = 0; + s->last_lit = 0; + s->matches = 0; +} + +// =============================================================================== +// Initialize the tree data structures for a new zlib stream. +// =============================================================================== + +static void tr_init(deflate_state *s) +{ + s->l_desc.dyn_tree = s->dyn_ltree; + s->l_desc.stat_desc = &static_l_desc; + + s->d_desc.dyn_tree = s->dyn_dtree; + s->d_desc.stat_desc = &static_d_desc; + + s->bl_desc.dyn_tree = s->bl_tree; + s->bl_desc.stat_desc = &static_bl_desc; + + s->bi_buf = 0; + s->bi_valid = 0; + // enough lookahead for inflate + s->last_eob_len = 8; + + // Initialize the first block of the first file: + init_block(s); +} + +// =============================================================================== +// Compares to subtrees, using the tree depth as tie breaker when +// the subtrees have equal frequency. This minimizes the worst case length. +// =============================================================================== + +static bool smaller(ct_data *tree, ulong son, ulong daughter, byte *depth) +{ + if(tree[son].fc.freq < tree[daughter].fc.freq) + { + return(true); + } + if((tree[son].fc.freq == tree[daughter].fc.freq) && (depth[son] <= depth[daughter])) + { + return(true); + } + return(false); +} + +// =============================================================================== +// Restore the heap property by moving down the tree starting at node k, +// exchanging a node with the smallest of its two sons if necessary, stopping +// when the heap property is re-established (each father smaller than its +// two sons). +// =============================================================================== + +static void pqdownheap(deflate_state *s, ct_data *tree, ulong node) +{ + ulong base; + ulong sibling; // left son of node + + base = s->heap[node]; + sibling = node << 1; + + while(sibling <= s->heap_len) + { + // Set sibling to the smallest of the two children + if((sibling < s->heap_len) && smaller(tree, s->heap[sibling + 1], s->heap[sibling], s->depth)) + { + sibling++; + } + // Exit if base is smaller than both sons + if(smaller(tree, base, s->heap[sibling], s->depth)) + { + break; + } + // Exchange base with the smallest son + s->heap[node] = s->heap[sibling]; + node = sibling; + + // And continue down the tree, setting sibling to the left son of base + sibling <<= 1; + } + s->heap[node] = base; +} + +// =============================================================================== +// Compute the optimal bit lengths for a tree and update the total bit length +// for the current block. +// IN assertion: the fields freq and dad are set, heap[heap_max] and +// above are the tree nodes sorted by increasing frequency. +// OUT assertions: the field len is set to the optimal bit length, the +// array bl_count contains the frequencies for each bit length. +// The length opt_len is updated; static_len is also updated if stree is +// not null. +// =============================================================================== + +static void gen_bitlen(deflate_state *s, tree_desc *desc) +{ + const ct_data *stree; + const ulong *extra; + ulong base; + ulong max_length; + ulong heapIdx; // heap index + ulong n, m; // iterate over the tree elements + ulong bits; // bit length + ulong xbits; // extra bits + word freq; // frequency + ulong overflow; // number of elements with bit length too large + + stree = desc->stat_desc->static_tree; + extra = desc->stat_desc->extra_bits; + base = desc->stat_desc->extra_base; + max_length = desc->stat_desc->max_length; + overflow = 0; + + for(bits = 0; bits <= MAX_WBITS; bits++) + { + s->bl_count[bits] = 0; + } + + // In a first pass, compute the optimal bit lengths (which may + // overflow in the case of the bit length tree). + // root of the heap + desc->dyn_tree[s->heap[s->heap_max]].dl.len = 0; + + for(heapIdx = s->heap_max + 1; heapIdx < HEAP_SIZE; heapIdx++) + { + n = s->heap[heapIdx]; + bits = desc->dyn_tree[desc->dyn_tree[n].dl.dad].dl.len + 1; + if(bits > max_length) + { + bits = max_length; + overflow++; + } + // We overwrite tree[n].dl.dad which is no longer needed + desc->dyn_tree[n].dl.len = (word)bits; + + // not a leaf node + if(n > desc->max_code) + { + continue; + } + + s->bl_count[bits]++; + xbits = 0; + if(n >= base) + { + xbits = extra[n - base]; + } + freq = desc->dyn_tree[n].fc.freq; + s->opt_len += freq * (bits + xbits); + if(stree) + { + s->static_len += freq * (stree[n].dl.len + xbits); + } + } + if(!overflow) + { + return; + } + + // Find the first bit length which could increase + do + { + bits = max_length - 1; + while(!s->bl_count[bits]) + { + bits--; + } + // move one leaf down the tree + s->bl_count[bits]--; + // move one overflow item as its brother + s->bl_count[bits + 1] += 2; + // The brother of the overflow item also moves one step up, + // but this does not affect bl_count[max_length] + s->bl_count[max_length]--; + overflow -= 2; + } + while(overflow > 0); + + // Now recompute all bit lengths, scanning in increasing frequency. + // heapIdx is still equal to HEAP_SIZE. (It is simpler to reconstruct all + // lengths instead of fixing only the wrong ones. This idea is taken + // from 'ar' written by Haruhiko Okumura.) + for(bits = max_length; bits; bits--) + { + n = s->bl_count[bits]; + while(n) + { + m = s->heap[--heapIdx]; + if(m > desc->max_code) + { + continue; + } + if(desc->dyn_tree[m].dl.len != bits) + { + s->opt_len += (bits - desc->dyn_tree[m].dl.len) * desc->dyn_tree[m].fc.freq; + desc->dyn_tree[m].dl.len = (word)bits; + } + n--; + } + } +} + +// =============================================================================== +// Flush the bit buffer and align the output on a byte boundary +// =============================================================================== + +static void bi_windup(deflate_state *s) +{ + if(s->bi_valid > 8) + { + put_short(s, s->bi_buf); + } + else if(s->bi_valid > 0) + { + put_byte(s, (byte)s->bi_buf); + } + s->bi_buf = 0; + s->bi_valid = 0; +} + +// =============================================================================== +// Reverse the first len bits of a code, using straightforward code (a faster +// method would use a table) +// =============================================================================== + +static ulong bi_reverse(ulong code, ulong len) +{ + ulong res; + + assert(1 <= len); + assert(len <= 15); + + res = 0; + do + { + res |= code & 1; + code >>= 1; + res <<= 1; + } + while(--len > 0); + + return(res >> 1); +} + +// =============================================================================== +// Generate the codes for a given tree and bit counts (which need not be optimal). +// IN assertion: the array bl_count contains the bit length statistics for +// the given tree and the field len is set for all tree elements. +// OUT assertion: the field code is set for all tree elements of non zero code length. +// =============================================================================== + +static void gen_codes(ct_data *tree, ulong max_code, word *bl_count) +{ + word next_code[MAX_WBITS + 1]; // next code value for each bit length + word code; // running code value + ulong bits; // bit index + ulong codes; // code index + ulong len; + + // The distribution counts are first used to generate the code values + // without bit reversal. + code = 0; + for(bits = 1; bits <= MAX_WBITS; bits++) + { + code = (word)((code + bl_count[bits - 1]) << 1); + next_code[bits] = code; + } + + // Check that the bit counts in bl_count are consistent. The last code + // must be all ones. + for(codes = 0; codes <= max_code; codes++) + { + len = tree[codes].dl.len; + + if(!len) + { + continue; + } + // Now reverse the bits + tree[codes].fc.code = (word)bi_reverse(next_code[len]++, len); + } +} + +// =============================================================================== +// Construct one Huffman tree and assigns the code bit strings and lengths. +// Update the total bit length for the current block. +// IN assertion: the field freq is set for all tree elements. +// OUT assertions: the fields len and code are set to the optimal bit length +// and corresponding code. The length opt_len is updated; static_len is +// also updated if stree is not null. The field max_code is set. +// =============================================================================== + +static void build_tree(deflate_state *s, tree_desc *desc) +{ + ct_data *tree; + const ct_data *stree; + ulong elems; + ulong n, m; // iterate over heap elements + ulong max_code; // largest code with non zero frequency + ulong node; // new node being created + + tree = desc->dyn_tree; + stree = desc->stat_desc->static_tree; + elems = desc->stat_desc->elems; + max_code = 0; + + // Construct the initial heap, with least frequent element in + // heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + // heap[0] is not used. + s->heap_len = 0; + s->heap_max = HEAP_SIZE; + + for(n = 0; n < elems; n++) + { + if(tree[n].fc.freq) + { + max_code = n; + s->heap[++s->heap_len] = n; + s->depth[n] = 0; + } + else + { + tree[n].dl.len = 0; + } + } + + // The pkzip format requires that at least one distance code exists, + // and that at least one bit should be sent even if there is only one + // possible code. So to avoid special checks later on we force at least + // two codes of non zero frequency. + while(s->heap_len < 2) + { + s->heap[++s->heap_len] = (max_code < 2 ? ++max_code : 0); + node = s->heap[s->heap_len]; + tree[node].fc.freq = 1; + s->depth[node] = 0; + s->opt_len--; + if(stree) + { + s->static_len -= stree[node].dl.len; + } + // node is 0 or 1 so it does not have extra bits + } + desc->max_code = max_code; + + // The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + // establish sub-heaps of increasing lengths: + for(n = s->heap_len >> 1; n >= 1; n--) + { + pqdownheap(s, tree, n); + } + + // Construct the Huffman tree by repeatedly combining the least two + // frequent nodes. + + // next internal node of the tree + node = elems; + do + { + n = s->heap[SMALLEST]; + s->heap[SMALLEST] = s->heap[s->heap_len--]; + pqdownheap(s, tree, SMALLEST); + m = s->heap[SMALLEST]; // m = node of next least frequency + + s->heap[--s->heap_max] = n; // keep the nodes sorted by frequency + s->heap[--s->heap_max] = m; + + // Create a new node father of n and m + tree[node].fc.freq = (word)(tree[n].fc.freq + tree[m].fc.freq); + if(s->depth[n] > s->depth[m]) + { + s->depth[node] = s->depth[n]; + } + else + { + s->depth[node] = s->depth[m]; + s->depth[node]++; + } + tree[m].dl.dad = (word)node; + tree[n].dl.dad = (word)node; + + // and insert the new node in the heap + s->heap[SMALLEST] = node++; + pqdownheap(s, tree, SMALLEST); + } + while(s->heap_len >= 2); + + s->heap[--s->heap_max] = s->heap[SMALLEST]; + + // At this point, the fields freq and dad are set. We can now + // generate the bit lengths. + gen_bitlen(s, desc); + + // The field len is now set, we can generate the bit codes + gen_codes (tree, max_code, s->bl_count); +} + +// =============================================================================== +// Scan a literal or distance tree to determine the frequencies of the codes +// in the bit length tree. +// =============================================================================== + +static void scan_tree (deflate_state *s, ct_data *tree, ulong max_code) +{ + ulong n; // iterates over all tree elements + ulong prevlen; // last emitted length + ulong curlen; // length of current code + ulong nextlen; // length of next code + ulong count; // repeat count of the current code + ulong max_count; // max repeat count + ulong min_count; // min repeat count + + prevlen = 0xffff; + nextlen = tree[0].dl.len; + count = 0; + max_count = 7; + min_count = 4; + + if(!nextlen) + { + max_count = 138; + min_count = 3; + } + // guard + tree[max_code + 1].dl.len = (word)prevlen; + + for(n = 0; n <= max_code; n++) + { + curlen = nextlen; + nextlen = tree[n + 1].dl.len; + if((++count < max_count) && (curlen == nextlen)) + { + continue; + } + else if(count < min_count) + { + s->bl_tree[curlen].fc.freq += (word)count; + } + else if(curlen) + { + if(curlen != prevlen) + { + s->bl_tree[curlen].fc.freq++; + } + s->bl_tree[REP_3_6].fc.freq++; + } + else if(count <= 10) + { + s->bl_tree[REPZ_3_10].fc.freq++; + } + else + { + s->bl_tree[REPZ_11_138].fc.freq++; + } + count = 0; + prevlen = curlen; + if(!nextlen) + { + max_count = 138; + min_count = 3; + } + else if(curlen == nextlen) + { + max_count = 6; + min_count = 3; + } + else + { + max_count = 7; + min_count = 4; + } + } +} + +// =============================================================================== +// Send a literal or distance tree in compressed form, using the codes in bl_tree. +// =============================================================================== + +static void send_tree(deflate_state *s, ct_data *tree, ulong max_code) +{ + ulong n; // iterates over all tree elements + ulong prevlen; // last emitted length + ulong curlen; // length of current code + ulong nextlen; // length of next code + ulong count; // repeat count of the current code + ulong max_count; // max repeat count + ulong min_count; // min repeat count + + prevlen = 0xffff; + nextlen = tree[0].dl.len; + count = 0; + max_count = 7; + min_count = 4; + + if(!nextlen) + { + max_count = 138; + min_count = 3; + } + + for(n = 0; n <= max_code; n++) + { + curlen = nextlen; + nextlen = tree[n + 1].dl.len; + if((++count < max_count) && (curlen == nextlen)) + { + continue; + } + else if(count < min_count) + { + do + { + send_bits(s, s->bl_tree[curlen].fc.code, s->bl_tree[curlen].dl.len); + } + while(--count); + + } + else if(curlen) + { + if(curlen != prevlen) + { + send_bits(s, s->bl_tree[curlen].fc.code, s->bl_tree[curlen].dl.len); + count--; + } + send_bits(s, s->bl_tree[REP_3_6].fc.code, s->bl_tree[REP_3_6].dl.len); + send_bits(s, count - 3, 2); + + } + else if(count <= 10) + { + send_bits(s, s->bl_tree[REPZ_3_10].fc.code, s->bl_tree[REPZ_3_10].dl.len); + send_bits(s, count - 3, 3); + + } + else + { + send_bits(s, s->bl_tree[REPZ_11_138].fc.code, s->bl_tree[REPZ_11_138].dl.len); + send_bits(s, count - 11, 7); + } + count = 0; + prevlen = curlen; + if(!nextlen) + { + max_count = 138; + min_count = 3; + } + else if(curlen == nextlen) + { + max_count = 6; + min_count = 3; + } + else + { + max_count = 7; + min_count = 4; + } + } +} + +// =============================================================================== +// Construct the Huffman tree for the bit lengths and return the index in +// bl_order of the last bit length code to send. +// =============================================================================== + +static ulong build_bl_tree(deflate_state *s) +{ + ulong max_blindex; // index of last bit length code of non zero freq + + // Determine the bit length frequencies for literal and distance trees + scan_tree(s, s->dyn_ltree, s->l_desc.max_code); + scan_tree(s, s->dyn_dtree, s->d_desc.max_code); + + // Build the bit length tree + build_tree(s, &s->bl_desc); + // opt_len now includes the length of the tree representations, except + // the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + + // Determine the number of bit length codes to send. The pkzip format + // requires that at least 4 bit length codes be sent. (appnote.txt says + // 3 but the actual value used is 4.) + for(max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) + { + if(s->bl_tree[bl_order[max_blindex]].dl.len) + { + break; + } + } + // Update opt_len to include the bit length tree and counts + s->opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; + return(max_blindex); +} + +// =========================================================================== +// Send the header for a block using dynamic Huffman trees: the counts, the +// lengths of the bit length codes, the literal tree and the distance tree. +// IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. +// =========================================================================== + +static void send_all_trees(deflate_state *s, ulong lcodes, ulong dcodes, ulong blcodes) +{ + ulong rank; // index in bl_order + + // not +255 as stated in appnote.txt + send_bits(s, lcodes - 257, 5); + send_bits(s, dcodes - 1, 5); + // not -3 as stated in appnote.txt + send_bits(s, blcodes - 4, 4); + + for(rank = 0; rank < blcodes; rank++) + { + send_bits(s, s->bl_tree[bl_order[rank]].dl.len, 3); + } + + // literal tree + send_tree(s, (ct_data *)s->dyn_ltree, lcodes - 1); + // distance tree + send_tree(s, (ct_data *)s->dyn_dtree, dcodes - 1); +} + +// =========================================================================== +// Send the block data compressed using the given Huffman trees +// =========================================================================== + +static void compress_block(deflate_state *s, const ct_data *ltree, const ct_data *dtree) +{ + ulong dist; // distance of matched string + ulong lenCount; // match length or unmatched char (if dist == 0) + ulong lenIdx; // running index in l_buf + ulong code; // the code to send + ulong extra; // number of extra bits to send + + lenIdx = 0; + if(s->last_lit) + { + do + { + dist = s->d_buf[lenIdx]; + lenCount = s->l_buf[lenIdx++]; + if(!dist) + { + // send a literal byte + send_bits(s, ltree[lenCount].fc.code, ltree[lenCount].dl.len); + } + else + { + // Here, lenCount is the match length - MIN_MATCH + code = tr_length_code[lenCount]; + // send the length code + send_bits(s, ltree[code + LITERALS + 1].fc.code, ltree[code + LITERALS + 1].dl.len); + extra = extra_lbits[code]; + if(extra) + { + lenCount -= base_length[code]; + // send the extra length bits + send_bits(s, lenCount, extra); + } + // dist is now the match distance - 1 + dist--; + code = (dist < 256 ? tr_dist_code[dist] : tr_dist_code[256 + (dist >> 7)]); + + // send the distance code + send_bits(s, dtree[code].fc.code, dtree[code].dl.len); + extra = extra_dbits[code]; + if(extra) + { + dist -= base_dist[code]; + // send the extra distance bits + send_bits(s, dist, extra); + } + } + } + while(lenIdx < s->last_lit); + } + + send_bits(s, ltree[END_BLOCK].fc.code, ltree[END_BLOCK].dl.len); + s->last_eob_len = ltree[END_BLOCK].dl.len; +} + +// =========================================================================== +// Send a stored block +// =========================================================================== + +static void tr_stored_block(deflate_state *s, const byte *buf, ulong stored_len, bool eof) +{ + // send block type + send_bits(s, (STORED_BLOCK << 1) + (ulong)eof, 3); + + // align on byte boundary + bi_windup(s); + // enough lookahead for inflate + s->last_eob_len = 8; + + put_short(s, (word)stored_len); + put_short(s, (word)~stored_len); + + while(stored_len--) + { + put_byte(s, *buf++); + } +} + +// =========================================================================== +// Determine the best encoding for the current block: dynamic trees, static +// trees or store, and output the encoded block to the zip file. +// =========================================================================== + +static void tr_flush_block(deflate_state *s, const byte *buf, ulong stored_len, bool eof) +{ + ulong opt_lenb; + ulong static_lenb; + ulong max_blindex; // index of last bit length code of non zero freq + + max_blindex = 0; + + // Build the Huffman trees unless a stored block is forced + if(s->level > 0) + { + // Construct the literal and distance trees + build_tree(s, &s->l_desc); + build_tree(s, &s->d_desc); + // At this point, opt_len and static_len are the total bit lengths of + // the compressed block data, excluding the tree representations. + + // Build the bit length tree for the above two trees, and get the index + // in bl_order of the last bit length code to send. + max_blindex = build_bl_tree(s); + + // Determine the best encoding. Compute first the block length in bytes + opt_lenb = (s->opt_len + 3 + 7) >> 3; + static_lenb = (s->static_len + 3 + 7) >> 3; + + if(static_lenb <= opt_lenb) + { + opt_lenb = static_lenb; + } + } + else + { + static_lenb = stored_len + 5; + // force a stored block + opt_lenb = static_lenb; + } + + if(stored_len + 4 <= opt_lenb && buf) + { + // 4: two words for the lengths + // The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + // Otherwise we can't have processed more than WSIZE input bytes since + // the last block flush, because compression would have been + // successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + // transform a block into a stored block. + tr_stored_block(s, buf, stored_len, eof); + + } + else if(static_lenb == opt_lenb) + { + send_bits(s, (STATIC_TREES << 1) + (ulong)eof, 3); + compress_block(s, static_ltree, static_dtree); + } + else + { + send_bits(s, (DYN_TREES << 1) + (ulong)eof, 3); + send_all_trees(s, s->l_desc.max_code + 1, s->d_desc.max_code + 1, max_blindex + 1); + compress_block(s, s->dyn_ltree, s->dyn_dtree); + } + + // The above check is made mod 2^32, for files larger than 512 MB + // and uLong implemented on 32 bits. + init_block(s); + + if(eof) + { + bi_windup(s); + } +} + +// =============================================================================== +// =============================================================================== + +inline bool tr_tally_lit(deflate_state *s, byte c) +{ + s->d_buf[s->last_lit] = 0; + s->l_buf[s->last_lit++] = c; + s->dyn_ltree[c].fc.freq++; + + return(s->last_lit == LIT_BUFSIZE - 1); +} + +// =============================================================================== +// =============================================================================== + +inline bool tr_tally_dist(deflate_state *s, ulong dist, ulong len) +{ + assert(dist < 65536); + assert(len < 256); + + dist &= 0xffff; + len &= 0xff; + + s->d_buf[s->last_lit] = (word)dist; + s->l_buf[s->last_lit++] = (byte)len; + dist--; + s->dyn_ltree[tr_length_code[len] + LITERALS + 1].fc.freq++; + s->dyn_dtree[(dist < 256 ? tr_dist_code[dist] : tr_dist_code[256 + (dist >> 7)])].fc.freq++; + + return(s->last_lit == LIT_BUFSIZE - 1); +} + +// =============================================================================== +// Insert string str in the dictionary and set match_head to the previous head +// of the hash chain (the most recent string with same hash key). Return +// the previous length of the hash chain. +// If this file is compiled with -DFASTEST, the compression level is forced +// to 1, and no hash chains are maintained. +// IN assertion: all calls to to INSERT_STRING are made with consecutive +// input characters and the first MIN_MATCH bytes of str are valid +// (except for the last MIN_MATCH-1 bytes of the input file). +// =============================================================================== + +inline void insert_string(deflate_state *s, ulong str, ulong &match_head) +{ + s->ins_h = ((s->ins_h << HASH_SHIFT) ^ s->window[str + (MIN_MATCH - 1)]) & HASH_MASK; + match_head = s->head[s->ins_h]; + s->prev[str & WINDOW_MASK] = s->head[s->ins_h]; + s->head[s->ins_h] = (word)str; +} + +// =========================================================================== +// Initialize the "longest match" routines for a new zlib stream +// =========================================================================== + +static void lm_init(deflate_state *s) +{ + s->head[HASH_SIZE - 1] = NULL; + memset(s->head, 0, (HASH_SIZE - 1) * sizeof(*s->head)); + + // Set the default configuration parameters: + s->max_lazy_match = configuration_table[s->level].max_lazy; + s->good_match = configuration_table[s->level].good_length; + s->nice_match = configuration_table[s->level].nice_length; + s->max_chain_length = configuration_table[s->level].max_chain; + + s->strstart = 0; + s->block_start = 0; + s->lookahead = 0; + s->prev_length = MIN_MATCH - 1; + s->match_length = MIN_MATCH - 1; + s->match_available = 0; + s->ins_h = 0; +} + +// =========================================================================== +// Set match_start to the longest match starting at the given string and +// return its length. Matches shorter or equal to prev_length are discarded, +// in which case the result is equal to prev_length and match_start is +// garbage. +// IN assertions: cur_match is the head of the hash chain for the current +// string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 +// OUT assertion: the match length is not greater than s->lookahead. +// =========================================================================== + +inline byte *qcmp(byte *scan, byte *match, ulong count) +{ + byte *retval; + _asm + { + push esi + push edi + push ecx + + mov esi, [scan] + mov edi, [match] + mov ecx, [count] + repe cmpsb + + pop ecx + pop edi + mov [retval], esi + pop esi + } + return(--retval); +} + +static ulong longest_match(deflate_state *s, ulong cur_match) +{ + ulong chain_length; // max hash chain length + ulong limit; + byte *scan; // current string + byte *match; // matched string + ulong len; // length of current match + ulong best_len; // best match length so far + ulong nice_match; // stop if match long enough + byte scan_end1; + byte scan_end; + + chain_length = s->max_chain_length; + scan = s->window + s->strstart; + best_len = s->prev_length; + nice_match = s->nice_match; + + // Stop when cur_match becomes <= limit. To simplify the code, + // we prevent matches with the string of window index 0. + limit = s->strstart > (WINDOW_SIZE - MIN_LOOKAHEAD) ? s->strstart - (WINDOW_SIZE - MIN_LOOKAHEAD) : NULL; + + scan_end1 = scan[best_len - 1]; + scan_end = scan[best_len]; + + // Do not waste too much time if we already have a good match: + if(s->prev_length >= s->good_match) + { + chain_length >>= 2; + } + // Do not look for matches beyond the end of the input. This is necessary + // to make deflate deterministic. + if(nice_match > s->lookahead) + { + nice_match = s->lookahead; + } + do + { + match = s->window + cur_match; + + // Skip to next match if the match length cannot increase + // or if the match length is less than 2: + if((match[best_len] != scan_end) || (match[best_len - 1] != scan_end1) || (match[0] != scan[0]) || (match[1] != scan[1])) + { + continue; + } + + // The check at best_len-1 can be removed because it will be made + // again later. (This heuristic is not always a win.) + // It is not necessary to compare scan[2] and match[2] since they + // are always equal when the other bytes match, given that + // the hash keys are equal + scan = qcmp(scan + 3, match + 3, MAX_MATCH - 2); + + len = scan - (s->window + s->strstart); + scan = s->window + s->strstart; + + if(len > best_len) + { + s->match_start = cur_match; + best_len = len; + if(len >= nice_match) + { + break; + } + scan_end1 = scan[best_len - 1]; + scan_end = scan[best_len]; + } + } while((cur_match = s->prev[cur_match & WINDOW_MASK]) > limit && --chain_length); + + if(best_len <= s->lookahead) + { + return(best_len); + } + return(s->lookahead); +} + +// =========================================================================== +// Flush as much pending output as possible. All deflate() output goes +// through this function so some applications may wish to modify it +// to avoid allocating a large z->next_out buffer and copying into it. +// (See also read_buf()). +// =========================================================================== + +static void flush_pending(z_stream *z) +{ + ulong len = z->dstate->pending; + + if(len > z->avail_out) + { + len = z->avail_out; + } + if(!len) + { + return; + } + assert(len <= MAX_BLOCK_SIZE + 5); + assert(z->dstate->pending_out + len <= z->dstate->pending_buf + MAX_BLOCK_SIZE + 5); + + memcpy(z->next_out, z->dstate->pending_out, len); + z->next_out += len; + z->total_out += len; + z->dstate->pending_out += len; + z->avail_out -= len; + z->dstate->pending -= len; + if(!z->dstate->pending) + { + z->dstate->pending_out = z->dstate->pending_buf; + } +} + +// =========================================================================== +// Read a new buffer from the current input stream, update the adler32 +// and total number of bytes read. All deflate() input goes through +// this function so some applications may wish to modify it to avoid +// allocating a large z->next_in buffer and copying from it. +// (See also flush_pending()). +// =========================================================================== + +static ulong read_buf(z_stream *z, byte *buf, ulong size) +{ + ulong len; + + len = z->avail_in; + if(len > size) + { + len = size; + } + if(!len) + { + return(0); + } + z->avail_in -= len; + + if(!z->dstate->noheader) + { + z->dstate->adler = adler32(z->dstate->adler, z->next_in, len); + } + memcpy(buf, z->next_in, len); + z->next_in += len; + return(len); +} + +// =========================================================================== +// Fill the window when the lookahead becomes insufficient. +// Updates strstart and lookahead. +// +// IN assertion: lookahead < MIN_LOOKAHEAD +// OUT assertions: strstart <= BIG_WINDOW_SIZE - MIN_LOOKAHEAD +// At least one byte has been read, or avail_in == 0; reads are +// performed for at least two bytes (required for the zip translate_eol +// option -- not supported here). +// =========================================================================== + +static void fill_window(deflate_state *s) +{ + ulong n, m; + word *p; + ulong more; // Amount of free space at the end of the window. + + do + { + more = BIG_WINDOW_SIZE - s->lookahead - s->strstart; + + if(s->strstart >= WINDOW_SIZE + (WINDOW_SIZE - MIN_LOOKAHEAD)) + { + memcpy(s->window, s->window + WINDOW_SIZE, WINDOW_SIZE); + s->match_start -= WINDOW_SIZE; + // Make strstart >= MAX_DIST + s->strstart -= WINDOW_SIZE; + s->block_start -= WINDOW_SIZE; + + // Slide the hash table (could be avoided with 32 bit values + // at the expense of memory usage). We slide even when level == 0 + // to keep the hash table consistent if we switch back to level > 0 + // later. (Using level 0 permanently is not an optimal usage of + // zlib, so we don't care about this pathological case.) + n = HASH_SIZE; + p = &s->head[n]; + do + { + m = *--p; + *p = (word)(m >= WINDOW_SIZE ? m - WINDOW_SIZE : 0); + } + while(--n); + + n = WINDOW_SIZE; + p = &s->prev[n]; + do + { + m = *--p; + *p = (word)(m >= WINDOW_SIZE ? m - WINDOW_SIZE : 0); + // If n is not on any hash chain, prev[n] is garbage but + // its value will never be used. + } + while(--n); + + more += WINDOW_SIZE; + } + if(!s->z->avail_in) + { + return; + } + + // If there was no sliding: + // strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + // more == BIG_WINDOW_SIZE - lookahead - strstart + // => more >= BIG_WINDOW_SIZE - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + // => more >= BIG_WINDOW_SIZE- 2*WSIZE + 2 + // If there was sliding, more >= WSIZE. So in all cases, more >= 2. + n = read_buf(s->z, s->window + s->strstart + s->lookahead, more); + s->lookahead += n; + + // Initialize the hash value now that we have some input: + if(s->lookahead >= MIN_MATCH) + { + s->ins_h = ((s->window[s->strstart] << HASH_SHIFT) ^ s->window[s->strstart + 1]) & HASH_MASK; + } + // If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + // but this is not important since only literal bytes will be emitted. + } + while(s->lookahead < MIN_LOOKAHEAD && s->z->avail_in); +} + +// =========================================================================== +// Flush the current block, with given end-of-file flag. +// IN assertion: strstart is set to the end of the current match. +// =========================================================================== + +inline void flush_block_only(deflate_state *s, bool eof) +{ + if(s->block_start >= 0) + { + tr_flush_block(s, &s->window[s->block_start], s->strstart - s->block_start, eof); + } + else + { + tr_flush_block(s, 0, s->strstart - s->block_start, eof); + } + s->block_start = s->strstart; + flush_pending(s->z); +} + +// =========================================================================== +// Copy without compression as much as possible from the input stream, return +// the current block state. +// This function does not insert new strings in the dictionary since +// uncompressible data is probably not useful. This function is used +// only for the level=0 compression option. +// NOTE: this function should be optimized to avoid extra copying from +// window to pending_buf. +// =========================================================================== + +static block_state deflate_stored(deflate_state *s, EFlush flush) +{ + // Stored blocks are limited to 0xffff bytes, pending_buf is limited + // to pending_buf_size, and each stored block has a 5 byte header: + ulong max_start; + + // Copy as much as possible from input to output: + while(true) + { + // Fill the window as much as possible + if(s->lookahead <= 1) + { + fill_window(s); + if(!s->lookahead && (flush == Z_NO_FLUSH)) + { + return(NEED_MORE); + } + if(!s->lookahead) + { + // flush the current block + break; + } + } + s->strstart += s->lookahead; + s->lookahead = 0; + + // Emit a stored block if pending_buf will be full + max_start = s->block_start + MAX_BLOCK_SIZE; + if(!s->strstart || (s->strstart >= max_start)) + { + // strstart == 0 is possible when wraparound on 16-bit machine + s->lookahead = s->strstart - max_start; + s->strstart = max_start; + flush_block_only(s, false); + if(!s->z->avail_out) + { + return(NEED_MORE); + } + } + // Flush if we may have to slide, otherwise block_start may become + // negative and the data will be gone: + if(s->strstart - s->block_start >= WINDOW_SIZE - MIN_LOOKAHEAD) + { + flush_block_only(s, false); + if(!s->z->avail_out) + { + return(NEED_MORE); + } + } + } + flush_block_only(s, flush == Z_FINISH); + if(!s->z->avail_out) + { + return((flush == Z_FINISH) ? FINISH_STARTED : NEED_MORE); + } + return(flush == Z_FINISH ? FINISH_DONE : BLOCK_DONE); +} + +// =========================================================================== +// Compress as much as possible from the input stream, return the current block state. +// This function does not perform lazy evaluation of matches and inserts +// new strings in the dictionary only for unmatched strings or for short +// matches. It is used only for the fast compression options. +// =========================================================================== + +static block_state deflate_fast(deflate_state *s, EFlush flush) +{ + ulong hash_head; // head of the hash chain + bool bflush; // set if current block must be flushed + + hash_head = 0; + while(true) + { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + if(s->lookahead < MIN_LOOKAHEAD) + { + fill_window(s); + if((s->lookahead < MIN_LOOKAHEAD) && (flush == Z_NO_FLUSH)) + { + return(NEED_MORE); + } + if(!s->lookahead) + { + // flush the current block + break; + } + } + + // Insert the string window[strstart .. strstart+2] in the + // dictionary, and set hash_head to the head of the hash chain: + if(s->lookahead >= MIN_MATCH) + { + insert_string(s, s->strstart, hash_head); + } + + // Find the longest match, discarding those <= prev_length. + // At this point we have always match_length < MIN_MATCH + if(hash_head && (s->strstart - hash_head <= WINDOW_SIZE - MIN_LOOKAHEAD)) + { + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + s->match_length = longest_match(s, hash_head); + // longest_match() sets match_start + } + if(s->match_length >= MIN_MATCH) + { + s->z->quality++; + + bflush = tr_tally_dist(s, s->strstart - s->match_start, s->match_length - MIN_MATCH); + s->lookahead -= s->match_length; + + // Insert new strings in the hash table only if the match length + // is not too large. This saves time but degrades compression. + if((s->match_length <= s->max_lazy_match) && (s->lookahead >= MIN_MATCH)) + { + // string at strstart already in hash table + s->match_length--; + do + { + // strstart never exceeds WSIZE-MAX_MATCH, so there are + // always MIN_MATCH bytes ahead. + s->strstart++; + insert_string(s, s->strstart, hash_head); + } + while(--s->match_length); + s->strstart++; + } + else + { + s->z->quality++; + + s->strstart += s->match_length; + s->match_length = 0; + s->ins_h = ((s->window[s->strstart] << HASH_SHIFT) ^ s->window[s->strstart + 1]) & HASH_MASK; + // If lookahead < MIN_MATCH, ins_h is garbage, but it does not + // matter since it will be recomputed at next deflate call. + } + } + else + { + // No match, output a literal byte + bflush = tr_tally_lit(s, s->window[s->strstart]); + s->lookahead--; + s->strstart++; + } + if(bflush) + { + flush_block_only(s, false); + if(!s->z->avail_out) + { + return(NEED_MORE); + } + } + } + flush_block_only(s, flush == Z_FINISH); + if(!s->z->avail_out) + { + return(flush == Z_FINISH ? FINISH_STARTED : NEED_MORE); + } + return(flush == Z_FINISH ? FINISH_DONE : BLOCK_DONE); +} + +// =========================================================================== +// Same as above, but achieves better compression. We use a lazy +// evaluation for matches: a match is finally adopted only if there is +// no better match at the next window position. +// =========================================================================== + +static block_state deflate_slow(deflate_state *s, EFlush flush) +{ + ulong hash_head; // head of hash chain + ulong max_insert; + bool bflush; // set if current block must be flushed + + hash_head = 0; + // Process the input block. + while(true) + { + // Make sure that we always have enough lookahead, except + // at the end of the input file. We need MAX_MATCH bytes + // for the next match, plus MIN_MATCH bytes to insert the + // string following the next match. + if(s->lookahead < MIN_LOOKAHEAD) + { + fill_window(s); + if((s->lookahead < MIN_LOOKAHEAD) && (flush == Z_NO_FLUSH)) + { + return(NEED_MORE); + } + if(!s->lookahead) + { + // flush the current block + break; + } + } + + // Insert the string window[strstart .. strstart+2] in the + // dictionary, and set hash_head to the head of the hash chain: + if(s->lookahead >= MIN_MATCH) + { + insert_string(s, s->strstart, hash_head); + } + + // Find the longest match, discarding those <= prev_length. + s->prev_length = s->match_length; + s->prev_match = s->match_start; + s->match_length = MIN_MATCH - 1; + + if(hash_head && (s->prev_length < s->max_lazy_match) && (s->strstart - hash_head <= WINDOW_SIZE - MIN_LOOKAHEAD)) + { + // To simplify the code, we prevent matches with the string + // of window index 0 (in particular we have to avoid a match + // of the string with itself at the start of the input file). + // longest_match() sets match_start + s->match_length = longest_match(s, hash_head); + + if((s->match_length <= 5) && ((s->match_length == MIN_MATCH) && (s->strstart - s->match_start > TOO_FAR))) + { + // If prev_match is also MIN_MATCH, match_start is garbage + // but we will ignore the current match anyway. + s->match_length = MIN_MATCH - 1; + } + } + // If there was a match at the previous step and the current + // match is not better, output the previous match: + if((s->prev_length >= MIN_MATCH) && (s->match_length <= s->prev_length)) + { + // Do not insert strings in hash table beyond this. + max_insert = s->strstart + s->lookahead - MIN_MATCH; + + bflush = tr_tally_dist(s, s->strstart - 1 - s->prev_match, s->prev_length - MIN_MATCH); + + // Insert in hash table all strings up to the end of the match. + // strstart-1 and strstart are already inserted. If there is not + // enough lookahead, the last two strings are not inserted in + // the hash table. + s->lookahead -= s->prev_length - 1; + s->prev_length -= 2; + do + { + if(++s->strstart <= max_insert) + { + insert_string(s, s->strstart, hash_head); + } + } + while(--s->prev_length); + + s->match_available = 0; + s->match_length = MIN_MATCH - 1; + s->strstart++; + + if(bflush) + { + flush_block_only(s, false); + if(!s->z->avail_out) + { + return(NEED_MORE); + } + } + } + else if(s->match_available) + { + // If there was no match at the previous position, output a + // single literal. If there was a match but the current match + // is longer, truncate the previous match to a single literal. + bflush = tr_tally_lit(s, s->window[s->strstart - 1]); + if(bflush) + { + flush_block_only(s, false); + } + s->strstart++; + s->lookahead--; + if(!s->z->avail_out) + { + return(NEED_MORE); + } + } + else + { + // There is no previous match to compare with, wait for + // the next step to decide. + s->match_available = 1; + s->strstart++; + s->lookahead--; + } + } + if(s->match_available) + { + bflush = tr_tally_lit(s, s->window[s->strstart - 1]); + s->match_available = 0; + } + flush_block_only(s, flush == Z_FINISH); + if(!s->z->avail_out) + { + return(flush == Z_FINISH ? FINISH_STARTED : NEED_MORE); + } + return(flush == Z_FINISH ? FINISH_DONE : BLOCK_DONE); +} + +// ------------------------------------------------------------------------------------------------- +// Controlling routines +// ------------------------------------------------------------------------------------------------- + +EStatus deflateInit(z_stream *z, ELevel level, int noWrap) +{ + deflate_state *s; + + assert(z); + + deflate_error = "OK"; + if((level < Z_STORE_COMPRESSION) || (level > Z_MAX_COMPRESSION)) + { + deflate_error = "Invalid compression level"; + return(Z_STREAM_ERROR); + } + s = (deflate_state *)Z_Malloc(sizeof(deflate_state), TAG_DEFLATE, qtrue); + z->dstate = (deflate_state *)s; + s->z = z; + + // undocumented feature: suppress zlib header + s->noheader = noWrap; + s->level = level; + + z->total_out = 0; + z->quality = 0; + + s->pending = 0; + s->pending_out = s->pending_buf; + + s->status = s->noheader ? BUSY_STATE : INIT_STATE; + s->adler = 1; + s->last_flush = Z_NO_FLUSH; + + tr_init(s); + lm_init(s); + return(Z_OK); +} + +// =========================================================================== +// Copy the source state to the destination state. +// To simplify the source, this is not supported for 16-bit MSDOS (which +// doesn't have enough memory anyway to duplicate compression states). +// =========================================================================== + +EStatus deflateCopy(z_stream *dest, z_stream *source) +{ + deflate_state *ds; + deflate_state *ss; + + assert(source); + assert(dest); + assert(source->dstate); + assert(!dest->dstate); + + *dest = *source; + + ss = source->dstate; + ds = (deflate_state *)Z_Malloc(sizeof(deflate_state), TAG_DEFLATE, qtrue); + dest->dstate = ds; + *ds = *ss; + ds->z = dest; + + ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); + + ds->l_desc.dyn_tree = ds->dyn_ltree; + ds->d_desc.dyn_tree = ds->dyn_dtree; + ds->bl_desc.dyn_tree = ds->bl_tree; + + return(Z_OK); +} + +// =========================================================================== +// =========================================================================== + +EStatus deflate(z_stream *z, EFlush flush) +{ + EFlush old_flush; // value of flush param for previous deflate call + deflate_state *s; + ulong header; + ulong level_flags; + + assert(z); + assert(z->dstate); + + if((flush > Z_FINISH) || (flush < Z_NO_FLUSH)) + { + deflate_error = "Invalid flush type"; + return(Z_STREAM_ERROR); + } + s = z->dstate; + + if(!z->next_out || (!z->next_in && z->avail_in) || (s->status == FINISH_STATE && flush != Z_FINISH)) + { + deflate_error = "Invalid output data"; + return (Z_STREAM_ERROR); + } + if(!z->avail_out) + { + deflate_error = "No output space"; + return (Z_BUF_ERROR); + } + + old_flush = s->last_flush; + s->last_flush = flush; + + // Write the zlib header + if(s->status == INIT_STATE) + { + header = (ZF_DEFLATED + ((MAX_WBITS - 8) << 4)) << 8; + level_flags = (s->level - 1) >> 1; + + if(level_flags > 3) + { + level_flags = 3; + } + header |= (level_flags << 6); + + header += 31 - (header % 31); + + s->status = BUSY_STATE; + put_shortMSB(s, (word)header); + s->adler = 1; + } + + // Flush as much pending output as possible + if(s->pending) + { + flush_pending(z); + if(!z->avail_out) + { + // Since avail_out is 0, deflate will be called again with + // more output space, but possibly with both pending and + // avail_in equal to zero. There won't be anything to do, + // but this is not an error situation so make sure we + // return OK instead of BUF_ERROR at next call of deflate: + s->last_flush = Z_NEED_MORE; + return(Z_OK); + } + + // Make sure there is something to do and avoid duplicate consecutive + // flushes. For repeated and useless calls with Z_FINISH, we keep + // returning Z_STREAM_END instead of Z_BUFF_ERROR. + } + else if(!z->avail_in && (flush <= old_flush) && (flush != Z_FINISH)) + { + deflate_error = "No available input"; + return(Z_BUF_ERROR); + } + + // User must not provide more input after the first FINISH + if((s->status == FINISH_STATE) && z->avail_in) + { + deflate_error = "Trying to finish while input available"; + return(Z_BUF_ERROR); + } + + // Start a new block or continue the current one. + if(z->avail_in || s->lookahead || ((flush != Z_NO_FLUSH) && (s->status != FINISH_STATE))) + { + block_state bstate; + + bstate = (*(configuration_table[s->level].func))(s, flush); + + if((bstate == FINISH_STARTED) || (bstate == FINISH_DONE)) + { + s->status = FINISH_STATE; + } + if((bstate == NEED_MORE) || (bstate == FINISH_STARTED)) + { + if(!z->avail_out) + { + // avoid BUF_ERROR next call, see above + s->last_flush = Z_NEED_MORE; + } + return(Z_OK); + + // If flush != Z_NO_FLUSH && avail_out == 0, the next call + // of deflate should use the same flush parameter to make sure + // that the flush is complete. So we don't have to output an + // empty block here, this will be done at next call. This also + // ensures that for a very small output buffer, we emit at most + // one empty block. + } + if(bstate == BLOCK_DONE) + { + // FULL_FLUSH or SYNC_FLUSH + tr_stored_block(s, NULL, 0, false); + + flush_pending(z); + if(!z->avail_out) + { + // avoid BUF_ERROR at next call, see above + s->last_flush = Z_NEED_MORE; + return(Z_OK); + } + } + } + + if(flush != Z_FINISH) + { + return(Z_OK); + } + if(s->noheader) + { + return(Z_STREAM_END); + } + + // Write the zlib trailer (adler32) + put_longMSB(s, s->adler); + flush_pending(z); + + // If avail_out is zero, the application will call deflate again + // to flush the rest. Write the trailer only once! + s->noheader = -1; + return(!!s->pending ? Z_OK : Z_STREAM_END); +} + +// =========================================================================== +// =========================================================================== + +EStatus deflateEnd(z_stream *z) +{ + int status; + + assert(z); + assert(z->dstate); + + status = z->dstate->status; + if((status != INIT_STATE) && (status != BUSY_STATE) && (status != FINISH_STATE)) + { + deflate_error = "Invalid state while ending"; + return(Z_STREAM_ERROR); + } + + Z_Free(z->dstate); + z->dstate = NULL; + + if(status == BUSY_STATE) + { + deflate_error = "Ending while in busy state"; + return(Z_DATA_ERROR); + } + return(Z_OK); +} + +// =========================================================================== +// =========================================================================== + +const char *deflateError(void) +{ + return(deflate_error); +} + +// =============================================================================== +// External calls +// =============================================================================== + +bool DeflateFile(byte *src, ulong uncompressedSize, byte *dst, ulong maxCompressedSize, ulong *compressedSize, ELevel level, int noWrap) +{ + z_stream z = { 0 }; + + if(deflateInit(&z, level, noWrap) != Z_OK) + { + return(false); + } + + z.next_in = src; + z.avail_in = uncompressedSize; + z.next_out = dst; + z.avail_out = maxCompressedSize; +#ifdef _TIMING + int temp = timeGetTime(); +#endif + if(deflate(&z, Z_FINISH) != Z_STREAM_END) + { + deflateEnd(&z); + return(false); + } +#ifdef _TIMING + totalDeflateTime[level] += timeGetTime() - temp; + totalDeflateCount[level]++; +#endif + if(deflateEnd(&z) != Z_OK) + { + return(false); + } + *compressedSize = z.total_out; + return(true); +} + +// end \ No newline at end of file diff --git a/code/zlib32/deflate.h b/code/zlib32/deflate.h new file mode 100644 index 0000000..2eceb1a --- /dev/null +++ b/code/zlib32/deflate.h @@ -0,0 +1,231 @@ +// Stream status +#define INIT_STATE 42 +#define BUSY_STATE 113 +#define FINISH_STATE 666 + +#define HASH_BITS 15 +#define HASH_SIZE (1 << HASH_BITS) +#define HASH_MASK (HASH_SIZE - 1) + +// Size of match buffer for literals/lengths. There are 4 reasons for +// limiting lit_bufsize to 64K: +// - frequencies can be kept in 16 bit counters +// - if compression is not successful for the first block, all input +// data is still in the window so we can still emit a stored block even +// when input comes from standard input. (This can also be done for +// all blocks if lit_bufsize is not greater than 32K.) +// - if compression is not successful for a file smaller than 64K, we can +// even emit a stored file instead of a stored block (saving 5 bytes). +// This is applicable only for zip (not gzip or zlib). +// - creating new Huffman trees less frequently may not provide fast +// adaptation to changes in the input data statistics. (Take for +// example a binary file with poorly compressible code followed by +// a highly compressible string table.) Smaller buffer sizes give +// fast adaptation but have of course the overhead of transmitting +// trees more frequently. +// - I can't count above 4 +#define LIT_BUFSIZE (1 << 14) + +#define MAX_BLOCK_SIZE 0xffff + +// Number of bits by which ins_h must be shifted at each input +// step. It must be such that after MIN_MATCH steps, the oldest +// byte no longer takes part in the hash key. +#define HASH_SHIFT ((HASH_BITS + MIN_MATCH - 1) / MIN_MATCH) + +// Matches of length 3 are discarded if their distance exceeds TOO_FAR +#define TOO_FAR 32767 + +// Number of length codes, not counting the special END_BLOCK code +#define LENGTH_CODES 29 + +// Number of codes used to transfer the bit lengths +#define BL_CODES 19 + +// Number of literal bytes 0..255 +#define LITERALS 256 + +// Number of Literal or Length codes, including the END_BLOCK code +#define L_CODES (LITERALS + 1 + LENGTH_CODES) + +// See definition of array dist_code below +#define DIST_CODE_LEN 512 + +// Maximum heap size +#define HEAP_SIZE (2 * L_CODES + 1) + +// Index within the heap array of least frequent node in the Huffman tree +#define SMALLEST 1 + +// Bit length codes must not exceed MAX_BL_BITS bits +#define MAX_BL_BITS 7 + +// End of block literal code +#define END_BLOCK 256 + +// Repeat previous bit length 3-6 times (2 bits of repeat count) +#define REP_3_6 16 + +// Repeat a zero length 3-10 times (3 bits of repeat count) +#define REPZ_3_10 17 + +// Repeat a zero length 11-138 times (7 bits of repeat count) +#define REPZ_11_138 18 + +// Number of bits used within bi_buf. (bi_buf might be implemented on +// more than 16 bits on some systems.) +#define BUF_SIZE (8 * 2) + +// Minimum amount of lookahead, except at the end of the input file. +// See deflate.c for comments about the MIN_MATCH+1. +#define MIN_LOOKAHEAD (MAX_MATCH + MIN_MATCH + 1) + +typedef enum +{ + NEED_MORE, // block not completed, need more input or more output + BLOCK_DONE, // block flush performed + FINISH_STARTED, // finish started, need only more output at next deflate + FINISH_DONE // finish done, accept no more input or output +} block_state; + +// Data structure describing a single value and its code string. +typedef struct ct_data_s +{ + union + { + word freq; // frequency count + word code; // bit string + } fc; + union + { + word dad; // father node in Huffman tree + word len; // length of bit string + } dl; +} ct_data; + +typedef struct static_tree_desc_s +{ + const ct_data *static_tree; // static tree or NULL + const ulong *extra_bits; // extra bits for each code or NULL + ulong extra_base; // base index for extra_bits + ulong elems; // max number of elements in the tree + ulong max_length; // max bit length for the codes +} static_tree_desc; + +typedef struct tree_desc_s +{ + ct_data *dyn_tree; // the dynamic tree + ulong max_code; // largest code with non zero frequency + static_tree_desc *stat_desc; // the corresponding static tree +} tree_desc; + +// Main structure which the deflate algorithm works from +typedef struct deflate_state_s +{ + z_stream *z; // pointer back to this zlib stream + ulong status; // as the name implies + + EFlush last_flush; // value of flush param for previous deflate call + int noheader; // suppress zlib header and adler32 + + byte pending_buf[MAX_BLOCK_SIZE + 5];// output still pending + byte *pending_out; // next pending byte to output to the stream + ulong pending; // nb of bytes in the pending buffer + + // Sliding window. Input bytes are read into the second half of the window, + // and move to the first half later to keep a dictionary of at least wSize + // bytes. With this organization, matches are limited to a distance of + // wSize-MAX_MATCH bytes, but this ensures that IO is always + // performed with a length multiple of the block size. Also, it limits + // the window size to 64K, which is quite useful on MSDOS. + // To do: use the user input buffer as sliding window. + byte window[WINDOW_SIZE * 2]; + + // Link to older string with same hash index. To limit the size of this + // array to 64K, this link is maintained only for the last 32K strings. + // An index in this array is thus a window index modulo 32K. + word prev[WINDOW_SIZE]; + + word head[HASH_SIZE]; // Heads of the hash chains or NULL. + + ulong ins_h; // hash index of string to be inserted + + // Window position at the beginning of the current output block. Gets + // negative when the window is moved backwards. + int block_start; + + ulong match_length; // length of best match + ulong prev_match; // previous match + ulong match_available; // set if previous match exists + ulong strstart; // start of string to insert + ulong match_start; // start of matching string + ulong lookahead; // number of valid bytes ahead in window + + // Length of the best match at previous step. Matches not greater than this + // are discarded. This is used in the lazy match evaluation. + ulong prev_length; + + // Attempt to find a better match only when the current match is strictly + // smaller than this value. This mechanism is used only for compression levels >= 4. + ulong max_lazy_match; + + ulong good_match; // Use a faster search when the previous match is longer than this + ulong nice_match; // Stop searching when current match exceeds this + + // To speed up deflation, hash chains are never searched beyond this + // length. A higher limit improves compression ratio but degrades the speed. + ulong max_chain_length; + + ELevel level; // compression level (0..9) + + ct_data dyn_ltree[HEAP_SIZE]; // literal and length tree + ct_data dyn_dtree[(2 * D_CODES) + 1]; // distance tree + ct_data bl_tree[(2 * BL_CODES) + 1]; // Huffman tree for bit lengths + + tree_desc l_desc; // desc. for literal tree + tree_desc d_desc; // desc. for distance tree + tree_desc bl_desc; // desc. for bit length tree + + word bl_count[MAX_WBITS + 1]; // number of codes at each bit length for an optimal tree + + // The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + // The same heap array is used to build all trees. + ulong heap[(2 * L_CODES) + 1]; // heap used to build the Huffman trees + ulong heap_len; // number of elements in the heap + ulong heap_max; // element of largest frequency + + byte depth[(2 * L_CODES) + 1]; // Depth of each subtree used as tie breaker for trees of equal frequency + + byte l_buf[LIT_BUFSIZE]; // buffer for literals or lengths + + ulong last_lit; // running index in l_buf + + // Buffer for distances. To simplify the code, d_buf and l_buf have + // the same number of elements. To use different lengths, an extra flag + // array would be necessary. + word d_buf[LIT_BUFSIZE]; + + ulong opt_len; // bit length of current block with optimal trees + ulong static_len; // bit length of current block with static trees + ulong matches; // number of string matches in current block + ulong last_eob_len; // bit length of EOB code for last block + + word bi_buf; // Output buffer. bits are inserted starting at the bottom (least significant bits). + ulong bi_valid; // Number of valid bits in bi_buf. All bits above the last valid bit are always zero. + + ulong adler; +} deflate_state; + +// Compression function. Returns the block state after the call. +typedef block_state (*compress_func) (deflate_state *s, EFlush flush); + +typedef struct config_s +{ + word good_length; // reduce lazy search above this match length + word max_lazy; // do not perform lazy search above this match length + word nice_length; // quit search above this match length + word max_chain; + compress_func func; +} config; + +// end \ No newline at end of file diff --git a/code/zlib32/inflate.cpp b/code/zlib32/inflate.cpp new file mode 100644 index 0000000..e40e96c --- /dev/null +++ b/code/zlib32/inflate.cpp @@ -0,0 +1,1839 @@ +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" + +#include "zip.h" +#include "inflate.h" + +#ifdef _TIMING +int totalInflateTime; +int totalInflateCount; +#endif + +// If you use the zlib library in a product, an acknowledgment is welcome +// in the documentation of your product. If for some reason you cannot +// include such an acknowledgment, I would appreciate that you keep this +// copyright string in the executable of your product. +const char inflate_copyright[] = "Inflate 1.1.3 Copyright 1995-1998 Mark Adler "; + +static const char *inflate_error = "OK"; + +// int inflate(z_stream *strm); +// +// inflate decompresses as much data as possible, and stops when the input +// buffer becomes empty or the output buffer becomes full. It may some +// introduce some output latency (reading input without producing any output) +// except when forced to flush. +// +// The detailed semantics are as follows. inflate performs one or both of the +// following actions: +// +// - Decompress more input starting at next_in and update next_in and avail_in +// accordingly. If not all input can be processed (because there is not +// enough room in the output buffer), next_in is updated and processing +// will resume at this point for the next call of inflate(). +// +// - Provide more output starting at next_out and update next_out and avail_out +// accordingly. inflate() provides as much output as possible, until there +// is no more input data or no more space in the output buffer (see below +// about the flush parameter). +// +// Before the call of inflate(), the application should ensure that at least +// one of the actions is possible, by providing more input and/or consuming +// more output, and updating the next_* and avail_* values accordingly. +// The application can consume the uncompressed output when it wants, for +// example when the output buffer is full (avail_out == 0), or after each +// call of inflate(). If inflate returns Z_OK and with zero avail_out, it +// must be called again after making room in the output buffer because there +// might be more output pending. +// +// If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much +// output as possible to the output buffer. The flushing behavior of inflate is +// not specified for values of the flush parameter other than Z_SYNC_FLUSH +// and Z_FINISH, but the current implementation actually flushes as much output +// as possible anyway. +// +// inflate() should normally be called until it returns Z_STREAM_END or an +// error. However if all decompression is to be performed in a single step +// (a single call of inflate), the parameter flush should be set to +// Z_FINISH. In this case all pending input is processed and all pending +// output is flushed; avail_out must be large enough to hold all the +// uncompressed data. (The size of the uncompressed data may have been saved +// by the compressor for this purpose.) The next operation on this stream must +// be inflateEnd to deallocate the decompression state. The use of Z_FINISH +// is never required, but can be used to inform inflate that a faster routine +// may be used for the single inflate() call. +// +// It sets strm->adler to the adler32 checksum of all output produced +// so and returns Z_OK, Z_STREAM_END or +// an error code as described below. At the end of the stream, inflate() +// checks that its computed adler32 checksum is equal to that saved by the +// compressor and returns Z_STREAM_END only if the checksum is correct. +// +// inflate() returns Z_OK if some progress has been made (more input processed +// or more output produced), Z_STREAM_END if the end of the compressed data has +// been reached and all uncompressed output has been produced, +// Z_DATA_ERROR if the input data was +// corrupted (input stream not conforming to the zlib format or incorrect +// adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent +// (for example if next_in or next_out was NULL), +// Z_BUF_ERROR if no progress is possible or if there was not +// enough room in the output buffer when Z_FINISH is used. + +// int inflateEnd (z_stream *strm); +// +// All dynamically allocated data structures for this stream are freed. +// This function discards any unprocessed input and does not flush any +// pending output. +// +// inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state +// was inconsistent. In the error case, msg may be set but then points to a +// static string (which must not be deallocated). + +// EStatus inflateInit(z_stream *strm, EFlush flush, int noWrap = 0); +// +// inflateInit returns Z_OK if success, +// Z_STREAM_ERROR if a parameter is invalid. +// msg is set to "OK" if there is no error message. inflateInit +// does not perform any decompression apart from reading the zlib header if +// present: this will be done by inflate(). (So next_in and avail_in may be +// modified, but next_out and avail_out are unchanged.) + +// Notes beyond the 1.93a appnote.txt: +// +// 1. Distance pointers never point before the beginning of the output +// stream. +// 2. Distance pointers can point back across blocks, up to 32k away. +// 3. There is an implied maximum of 7 bits for the bit length table and +// 15 bits for the actual data. +// 4. If only one code exists, then it is encoded using one bit. (Zero +// would be more efficient, but perhaps a little confusing.) If two +// codes exist, they are coded using one bit each (0 and 1). +// 5. There is no way of sending zero distance codes--a dummy must be +// sent if there are none. (History: a pre 2.0 version of PKZIP would +// store blocks with no distance codes, but this was discovered to be +// too harsh a criterion.) Valid only for 1.93a. 2.04c does allow +// zero distance codes, which is sent as one code of zero bits in +// length. +// 6. There are up to 286 literal/length codes. Code 256 represents the +// end-of-block. Note however that the static length tree defines +// 288 codes just to fill out the Huffman codes. Codes 286 and 287 +// cannot be used though, since there is no length base or extra bits +// defined for them. Similarily, there are up to 30 distance codes. +// However, static trees define 32 codes (all 5 bits) to fill out the +// Huffman codes, but the last two had better not show up in the data. +// 7. Unzip can check dynamic Huffman blocks for complete code sets. +// The exception is that a single code would not be complete (see #4). +// 8. The five bits following the block type is really the number of +// literal codes sent minus 257. +// 9. Length codes 8,16,16 are interpreted as 13 length codes of 8 bits +// (1+6+6). Therefore, to output three times the length, you output +// three codes (1+1+1), whereas to output four times the same length, +// you only need two codes (1+3). Hmm. +// 10. In the tree reconstruction algorithm, Code = Code + Increment +// only if BitLength(i) is not zero. (Pretty obvious.) +// 11. Correction: 4 Bits: # of Bit Length codes - 4 (4 - 19) +// 12. Note: length code 284 can represent 227-258, but length code 285 +// really is 258. The last length deserves its own, short code +// since it gets used a lot in very redundant files. The length +// 258 is special since 258 - 3 (the min match length) is 255. +// 13. The literal/length and distance code bit lengths are read as a +// single stream of lengths. It is possible (and advantageous) for +// a repeat code (16, 17, or 18) to go across the boundary between +// the two sets of lengths. + +// And'ing with mask[n] masks the lower n bits +static const ulong inflate_mask[17] = +{ + 0x0000, + 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + +// Order of the bit length code lengths +static const ulong border[] = +{ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 +}; + +// Copy lengths for literal codes 257..285 (see note #13 above about 258) +static const ulong cplens[31] = +{ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +}; + +// Extra bits for literal codes 257..285 (112 == invalid) +static const ulong cplext[31] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112 +}; + +// Copy offsets for distance codes 0..29 +static const ulong cpdist[30] = +{ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577 +}; + +static ulong fixed_bl = 9; +static ulong fixed_bd = 5; + +static inflate_huft_t fixed_tl[] = +{ + { 96, 7, 256 }, { 0, 8, 80 }, { 0, 8, 16 }, { 84, 8, 115 }, + { 82, 7, 31 }, { 0, 8, 112 }, { 0, 8, 48 }, { 0, 9, 192 }, + { 80, 7, 10 }, { 0, 8, 96 }, { 0, 8, 32 }, { 0, 9, 160 }, + { 0, 8, 0 }, { 0, 8, 128 }, { 0, 8, 64 }, { 0, 9, 224 }, + { 80, 7, 6 }, { 0, 8, 88 }, { 0, 8, 24 }, { 0, 9, 144 }, + { 83, 7, 59 }, { 0, 8, 120 }, { 0, 8, 56 }, { 0, 9, 208 }, + { 81, 7, 17 }, { 0, 8, 104 }, { 0, 8, 40 }, { 0, 9, 176 }, + { 0, 8, 8 }, { 0, 8, 136 }, { 0, 8, 72 }, { 0, 9, 240 }, + { 80, 7, 4 }, { 0, 8, 84 }, { 0, 8, 20 }, { 85, 8, 227 }, + { 83, 7, 43 }, { 0, 8, 116 }, { 0, 8, 52 }, { 0, 9, 200 }, + { 81, 7, 13 }, { 0, 8, 100 }, { 0, 8, 36 }, { 0, 9, 168 }, + { 0, 8, 4 }, { 0, 8, 132 }, { 0, 8, 68 }, { 0, 9, 232 }, + { 80, 7, 8 }, { 0, 8, 92 }, { 0, 8, 28 }, { 0, 9, 152 }, + { 84, 7, 83 }, { 0, 8, 124 }, { 0, 8, 60 }, { 0, 9, 216 }, + { 82, 7, 23 }, { 0, 8, 108 }, { 0, 8, 44 }, { 0, 9, 184 }, + { 0, 8, 12 }, { 0, 8, 140 }, { 0, 8, 76 }, { 0, 9, 248 }, + { 80, 7, 3 }, { 0, 8, 82 }, { 0, 8, 18 }, { 85, 8, 163 }, + { 83, 7, 35 }, { 0, 8, 114 }, { 0, 8, 50 }, { 0, 9, 196 }, + { 81, 7, 11 }, { 0, 8, 98 }, { 0, 8, 34 }, { 0, 9, 164 }, + { 0, 8, 2 }, { 0, 8, 130 }, { 0, 8, 66 }, { 0, 9, 228 }, + { 80, 7, 7 }, { 0, 8, 90 }, { 0, 8, 26 }, { 0, 9, 148 }, + { 84, 7, 67 }, { 0, 8, 122 }, { 0, 8, 58 }, { 0, 9, 212 }, + { 82, 7, 19 }, { 0, 8, 106 }, { 0, 8, 42 }, { 0, 9, 180 }, + { 0, 8, 10 }, { 0, 8, 138 }, { 0, 8, 74 }, { 0, 9, 244 }, + { 80, 7, 5 }, { 0, 8, 86 }, { 0, 8, 22 }, { 192, 8, 0 }, + { 83, 7, 51 }, { 0, 8, 118 }, { 0, 8, 54 }, { 0, 9, 204 }, + { 81, 7, 15 }, { 0, 8, 102 }, { 0, 8, 38 }, { 0, 9, 172 }, + { 0, 8, 6 }, { 0, 8, 134 }, { 0, 8, 70 }, { 0, 9, 236 }, + { 80, 7, 9 }, { 0, 8, 94 }, { 0, 8, 30 }, { 0, 9, 156 }, + { 84, 7, 99 }, { 0, 8, 126 }, { 0, 8, 62 }, { 0, 9, 220 }, + { 82, 7, 27 }, { 0, 8, 110 }, { 0, 8, 46 }, { 0, 9, 188 }, + { 0, 8, 14 }, { 0, 8, 142 }, { 0, 8, 78 }, { 0, 9, 252 }, + { 96, 7, 256 }, { 0, 8, 81 }, { 0, 8, 17 }, { 85, 8, 131 }, + { 82, 7, 31 }, { 0, 8, 113 }, { 0, 8, 49 }, { 0, 9, 194 }, + { 80, 7, 10 }, { 0, 8, 97 }, { 0, 8, 33 }, { 0, 9, 162 }, + { 0, 8, 1 }, { 0, 8, 129 }, { 0, 8, 65 }, { 0, 9, 226 }, + { 80, 7, 6 }, { 0, 8, 89 }, { 0, 8, 25 }, { 0, 9, 146 }, + { 83, 7, 59 }, { 0, 8, 121 }, { 0, 8, 57 }, { 0, 9, 210 }, + { 81, 7, 17 }, { 0, 8, 105 }, { 0, 8, 41 }, { 0, 9, 178 }, + { 0, 8, 9 }, { 0, 8, 137 }, { 0, 8, 73 }, { 0, 9, 242 }, + { 80, 7, 4 }, { 0, 8, 85 }, { 0, 8, 21 }, { 80, 8, 258 }, + { 83, 7, 43 }, { 0, 8, 117 }, { 0, 8, 53 }, { 0, 9, 202 }, + { 81, 7, 13 }, { 0, 8, 101 }, { 0, 8, 37 }, { 0, 9, 170 }, + { 0, 8, 5 }, { 0, 8, 133 }, { 0, 8, 69 }, { 0, 9, 234 }, + { 80, 7, 8 }, { 0, 8, 93 }, { 0, 8, 29 }, { 0, 9, 154 }, + { 84, 7, 83 }, { 0, 8, 125 }, { 0, 8, 61 }, { 0, 9, 218 }, + { 82, 7, 23 }, { 0, 8, 109 }, { 0, 8, 45 }, { 0, 9, 186 }, + { 0, 8, 13 }, { 0, 8, 141 }, { 0, 8, 77 }, { 0, 9, 250 }, + { 80, 7, 3 }, { 0, 8, 83 }, { 0, 8, 19 }, { 85, 8, 195 }, + { 83, 7, 35 }, { 0, 8, 115 }, { 0, 8, 51 }, { 0, 9, 198 }, + { 81, 7, 11 }, { 0, 8, 99 }, { 0, 8, 35 }, { 0, 9, 166 }, + { 0, 8, 3 }, { 0, 8, 131 }, { 0, 8, 67 }, { 0, 9, 230 }, + { 80, 7, 7 }, { 0, 8, 91 }, { 0, 8, 27 }, { 0, 9, 150 }, + { 84, 7, 67 }, { 0, 8, 123 }, { 0, 8, 59 }, { 0, 9, 214 }, + { 82, 7, 19 }, { 0, 8, 107 }, { 0, 8, 43 }, { 0, 9, 182 }, + { 0, 8, 11 }, { 0, 8, 139 }, { 0, 8, 75 }, { 0, 9, 246 }, + { 80, 7, 5 }, { 0, 8, 87 }, { 0, 8, 23 }, { 192, 8, 0 }, + { 83, 7, 51 }, { 0, 8, 119 }, { 0, 8, 55 }, { 0, 9, 206 }, + { 81, 7, 15 }, { 0, 8, 103 }, { 0, 8, 39 }, { 0, 9, 174 }, + { 0, 8, 7 }, { 0, 8, 135 }, { 0, 8, 71 }, { 0, 9, 238 }, + { 80, 7, 9 }, { 0, 8, 95 }, { 0, 8, 31 }, { 0, 9, 158 }, + { 84, 7, 99 }, { 0, 8, 127 }, { 0, 8, 63 }, { 0, 9, 222 }, + { 82, 7, 27 }, { 0, 8, 111 }, { 0, 8, 47 }, { 0, 9, 190 }, + { 0, 8, 15 }, { 0, 8, 143 }, { 0, 8, 79 }, { 0, 9, 254 }, + { 96, 7, 256 }, { 0, 8, 80 }, { 0, 8, 16 }, { 84, 8, 115 }, + { 82, 7, 31 }, { 0, 8, 112 }, { 0, 8, 48 }, { 0, 9, 193 }, + { 80, 7, 10 }, { 0, 8, 96 }, { 0, 8, 32 }, { 0, 9, 161 }, + { 0, 8, 0 }, { 0, 8, 128 }, { 0, 8, 64 }, { 0, 9, 225 }, + { 80, 7, 6 }, { 0, 8, 88 }, { 0, 8, 24 }, { 0, 9, 145 }, + { 83, 7, 59 }, { 0, 8, 120 }, { 0, 8, 56 }, { 0, 9, 209 }, + { 81, 7, 17 }, { 0, 8, 104 }, { 0, 8, 40 }, { 0, 9, 177 }, + { 0, 8, 8 }, { 0, 8, 136 }, { 0, 8, 72 }, { 0, 9, 241 }, + { 80, 7, 4 }, { 0, 8, 84 }, { 0, 8, 20 }, { 85, 8, 227 }, + { 83, 7, 43 }, { 0, 8, 116 }, { 0, 8, 52 }, { 0, 9, 201 }, + { 81, 7, 13 }, { 0, 8, 100 }, { 0, 8, 36 }, { 0, 9, 169 }, + { 0, 8, 4 }, { 0, 8, 132 }, { 0, 8, 68 }, { 0, 9, 233 }, + { 80, 7, 8 }, { 0, 8, 92 }, { 0, 8, 28 }, { 0, 9, 153 }, + { 84, 7, 83 }, { 0, 8, 124 }, { 0, 8, 60 }, { 0, 9, 217 }, + { 82, 7, 23 }, { 0, 8, 108 }, { 0, 8, 44 }, { 0, 9, 185 }, + { 0, 8, 12 }, { 0, 8, 140 }, { 0, 8, 76 }, { 0, 9, 249 }, + { 80, 7, 3 }, { 0, 8, 82 }, { 0, 8, 18 }, { 85, 8, 163 }, + { 83, 7, 35 }, { 0, 8, 114 }, { 0, 8, 50 }, { 0, 9, 197 }, + { 81, 7, 11 }, { 0, 8, 98 }, { 0, 8, 34 }, { 0, 9, 165 }, + { 0, 8, 2 }, { 0, 8, 130 }, { 0, 8, 66 }, { 0, 9, 229 }, + { 80, 7, 7 }, { 0, 8, 90 }, { 0, 8, 26 }, { 0, 9, 149 }, + { 84, 7, 67 }, { 0, 8, 122 }, { 0, 8, 58 }, { 0, 9, 213 }, + { 82, 7, 19 }, { 0, 8, 106 }, { 0, 8, 42 }, { 0, 9, 181 }, + { 0, 8, 10 }, { 0, 8, 138 }, { 0, 8, 74 }, { 0, 9, 245 }, + { 80, 7, 5 }, { 0, 8, 86 }, { 0, 8, 22 }, { 192, 8, 0 }, + { 83, 7, 51 }, { 0, 8, 118 }, { 0, 8, 54 }, { 0, 9, 205 }, + { 81, 7, 15 }, { 0, 8, 102 }, { 0, 8, 38 }, { 0, 9, 173 }, + { 0, 8, 6 }, { 0, 8, 134 }, { 0, 8, 70 }, { 0, 9, 237 }, + { 80, 7, 9 }, { 0, 8, 94 }, { 0, 8, 30 }, { 0, 9, 157 }, + { 84, 7, 99 }, { 0, 8, 126 }, { 0, 8, 62 }, { 0, 9, 221 }, + { 82, 7, 27 }, { 0, 8, 110 }, { 0, 8, 46 }, { 0, 9, 189 }, + { 0, 8, 14 }, { 0, 8, 142 }, { 0, 8, 78 }, { 0, 9, 253 }, + { 96, 7, 256 }, { 0, 8, 81 }, { 0, 8, 17 }, { 85, 8, 131 }, + { 82, 7, 31 }, { 0, 8, 113 }, { 0, 8, 49 }, { 0, 9, 195 }, + { 80, 7, 10 }, { 0, 8, 97 }, { 0, 8, 33 }, { 0, 9, 163 }, + { 0, 8, 1 }, { 0, 8, 129 }, { 0, 8, 65 }, { 0, 9, 227 }, + { 80, 7, 6 }, { 0, 8, 89 }, { 0, 8, 25 }, { 0, 9, 147 }, + { 83, 7, 59 }, { 0, 8, 121 }, { 0, 8, 57 }, { 0, 9, 211 }, + { 81, 7, 17 }, { 0, 8, 105 }, { 0, 8, 41 }, { 0, 9, 179 }, + { 0, 8, 9 }, { 0, 8, 137 }, { 0, 8, 73 }, { 0, 9, 243 }, + { 80, 7, 4 }, { 0, 8, 85 }, { 0, 8, 21 }, { 80, 8, 258 }, + { 83, 7, 43 }, { 0, 8, 117 }, { 0, 8, 53 }, { 0, 9, 203 }, + { 81, 7, 13 }, { 0, 8, 101 }, { 0, 8, 37 }, { 0, 9, 171 }, + { 0, 8, 5 }, { 0, 8, 133 }, { 0, 8, 69 }, { 0, 9, 235 }, + { 80, 7, 8 }, { 0, 8, 93 }, { 0, 8, 29 }, { 0, 9, 155 }, + { 84, 7, 83 }, { 0, 8, 125 }, { 0, 8, 61 }, { 0, 9, 219 }, + { 82, 7, 23 }, { 0, 8, 109 }, { 0, 8, 45 }, { 0, 9, 187 }, + { 0, 8, 13 }, { 0, 8, 141 }, { 0, 8, 77 }, { 0, 9, 251 }, + { 80, 7, 3 }, { 0, 8, 83 }, { 0, 8, 19 }, { 85, 8, 195 }, + { 83, 7, 35 }, { 0, 8, 115 }, { 0, 8, 51 }, { 0, 9, 199 }, + { 81, 7, 11 }, { 0, 8, 99 }, { 0, 8, 35 }, { 0, 9, 167 }, + { 0, 8, 3 }, { 0, 8, 131 }, { 0, 8, 67 }, { 0, 9, 231 }, + { 80, 7, 7 }, { 0, 8, 91 }, { 0, 8, 27 }, { 0, 9, 151 }, + { 84, 7, 67 }, { 0, 8, 123 }, { 0, 8, 59 }, { 0, 9, 215 }, + { 82, 7, 19 }, { 0, 8, 107 }, { 0, 8, 43 }, { 0, 9, 183 }, + { 0, 8, 11 }, { 0, 8, 139 }, { 0, 8, 75 }, { 0, 9, 247 }, + { 80, 7, 5 }, { 0, 8, 87 }, { 0, 8, 23 }, { 192, 8, 0 }, + { 83, 7, 51 }, { 0, 8, 119 }, { 0, 8, 55 }, { 0, 9, 207 }, + { 81, 7, 15 }, { 0, 8, 103 }, { 0, 8, 39 }, { 0, 9, 175 }, + { 0, 8, 7 }, { 0, 8, 135 }, { 0, 8, 71 }, { 0, 9, 239 }, + { 80, 7, 9 }, { 0, 8, 95 }, { 0, 8, 31 }, { 0, 9, 159 }, + { 84, 7, 99 }, { 0, 8, 127 }, { 0, 8, 63 }, { 0, 9, 223 }, + { 82, 7, 27 }, { 0, 8, 111 }, { 0, 8, 47 }, { 0, 9, 191 }, + { 0, 8, 15 }, { 0, 8, 143 }, { 0, 8, 79 }, { 0, 9, 255 } +}; + +static inflate_huft_t fixed_td[] = +{ + { 80, 5, 1 }, { 87, 5, 257 }, { 83, 5, 17 }, { 91, 5, 4097 }, + { 81, 5, 5 }, { 89, 5, 1025 }, { 85, 5, 65 }, { 93, 5, 16385 }, + { 80, 5, 3 }, { 88, 5, 513 }, { 84, 5, 33 }, { 92, 5, 8193 }, + { 82, 5, 9 }, { 90, 5, 2049 }, { 86, 5, 129 }, { 192, 5, 24577 }, + { 80, 5, 2 }, { 87, 5, 385 }, { 83, 5, 25 }, { 91, 5, 6145 }, + { 81, 5, 7 }, { 89, 5, 1537 }, { 85, 5, 97 }, { 93, 5, 24577 }, + { 80, 5, 4 }, { 88, 5, 769 }, { 84, 5, 49 }, { 92, 5, 12289 }, + { 82, 5, 13 }, { 90, 5, 3073 }, { 86, 5, 193 }, { 192, 5, 24577 } +}; + +// =============================================================================== +// =============================================================================== + +static void inflate_blocks_reset(z_stream *z, inflate_blocks_state_t *s) +{ + if((s->mode == BTREE) || (s->mode == DTREE)) + { + Z_Free(s->trees.blens); + } + if(s->mode == CODES) + { + Z_Free(s->decode.codes); + } + s->mode = TYPE; + s->bitk = 0; + s->bitb = 0; + s->write = s->window; + s->read = s->window; + z->istate->adler = 1; +} + +// =============================================================================== +// =============================================================================== + +static int inflate_blocks_free(z_stream *z, inflate_blocks_state_t *s) +{ + inflate_blocks_reset(z, s); + Z_Free(s->hufts); + s->hufts = NULL; + Z_Free(s); + return(Z_OK); +} + +// =============================================================================== +// =============================================================================== + +static inflate_blocks_state_t *inflate_blocks_new(z_stream *z, check_func check) +{ + inflate_blocks_state_t *s; + + s = (inflate_blocks_state_t *)Z_Malloc(sizeof(inflate_blocks_state_t), TAG_INFLATE, qtrue); + s->hufts = (inflate_huft_t *)Z_Malloc(sizeof(inflate_huft_t) * MANY, TAG_INFLATE, qtrue); + s->end = s->window + WINDOW_SIZE; + s->mode = TYPE; + inflate_blocks_reset(z, s); + + return(s); +} + +// =============================================================================== +// copy as much as possible from the sliding window to the output area +// =============================================================================== + +static void inflate_flush_copy(z_stream *z, inflate_blocks_state_t *s, ulong count) +{ + if(count > z->avail_out) + { + count = z->avail_out; + } + if(count && (z->error == Z_BUF_ERROR)) + { + z->error = Z_OK; + } + + // Calculate the checksum if required + if(!z->istate->nowrap) + { + z->istate->adler = adler32(z->istate->adler, s->read, count); + } + + // copy as as end of window + memcpy(z->next_out, s->read, count); + + // update counters + z->avail_out -= count; + z->total_out += count; + z->next_out += count; + s->read += count; +} + +// =============================================================================== +// =============================================================================== + +static void inflate_flush(z_stream *z, inflate_blocks_state_t *s) +{ + ulong count; + + // compute number of bytes to copy as as end of window + count = (s->read <= s->write ? s->write : s->end) - s->read; + + inflate_flush_copy(z, s, count); + + // see if more to copy at beginning of window + if(s->read == s->end) + { + // wrap pointers + s->read = s->window; + if(s->write == s->end) + { + s->write = s->window; + } + // compute bytes to copy + count = s->write - s->read; + inflate_flush_copy(z, s, count); + } +} + +// =============================================================================== +// get bytes and bits +// =============================================================================== + +static bool getbits(z_stream *z, inflate_blocks_state_t *s, ulong bits) +{ + while(s->bitk < bits) + { + if(z->avail_in) + { + z->error = Z_OK; + } + else + { + inflate_flush(z, s); + return(false); + } + z->avail_in--; + z->total_in++; + s->bitb |= *z->next_in++ << s->bitk; + s->bitk += 8; + } + return(true); +} + +// =============================================================================== +// output bytes +// =============================================================================== + +static ulong needout(z_stream *z, inflate_blocks_state_t *s, ulong bytesToEnd) +{ + if(!bytesToEnd) + { + if((s->write == s->end) && (s->read != s->window)) + { + s->write = s->window; + bytesToEnd = s->write < s->read ? s->read - s->write - 1 : s->end - s->write; + } + if(!bytesToEnd) + { + inflate_flush(z, s); + bytesToEnd = s->write < s->read ? s->read - s->write - 1 : s->end - s->write; + if((s->write == s->end) && (s->read != s->window)) + { + s->write = s->window; + bytesToEnd = s->write < s->read ? s->read - s->write - 1 : s->end - s->write; + } + if(!bytesToEnd) + { + inflate_flush(z, s); + return(bytesToEnd); + } + } + } + z->error = Z_OK; + return(bytesToEnd); +} + +// =============================================================================== +// Called with number of bytes left to write in window at least 258 +// (the maximum string length) and number of input bytes available +// at least ten. The ten bytes are six bytes for the longest length/ +// distance pair plus four bytes for overloading the bit buffer. +// =============================================================================== + +inline byte *qcopy(byte *dst, byte *src, int count) +{ + byte *retval; + _asm + { + push ecx + push esi + push edi + + mov edi, [dst] + mov esi, [src] + mov ecx, [count] + rep movsb + + mov [retval], edi + pop edi + pop esi + pop ecx + } + return(retval); +} + +inline ulong get_remaining(inflate_blocks_state_t *s) +{ + if(s->write < s->read) + { + return(s->read - s->write - 1); + } + return(s->end - s->write); +} + +static EStatus inflate_fast(ulong lengthMask, ulong distMask, inflate_huft_t *lengthTree, inflate_huft_t *distTree, inflate_blocks_state_t *s, z_stream *z) +{ + inflate_huft_t *huft; // temporary pointer + byte *data; + byte *src; // copy source pointer + byte *dst; + ulong extraBits; // extra bits or operation + ulong bytesToEnd; // bytes to end of window or read pointer + ulong count; // bytes to copy + ulong dist; // distance back to copy from + ulong bitb; + ulong bitk; + ulong availin; + ulong morebits; + ulong copymore; + + // load input, output, bit values + data = z->next_in; + dst = s->write; + availin = z->avail_in; + bitb = s->bitb; + bitk = s->bitk; + + bytesToEnd = get_remaining(s); + + // do until not enough input or output space for fast loop + // assume called with bytesToEnd >= 258 && availIn >= 10 + while((bytesToEnd >= 258) && (availin >= 10)) + { + // get literal/length code + while(bitk < 20) + { + bitb |= *data++ << bitk; + bitk += 8; + availin--; + } + + huft = lengthTree + (bitb & lengthMask); + if(!huft->Exop) + { + bitb >>= huft->Bits; + bitk -= huft->Bits; + *dst++ = (byte)huft->base; + bytesToEnd--; + } + else + { + extraBits = huft->Exop; + morebits = 1; + do + { + bitb >>= huft->Bits; + bitk -= huft->Bits; + if(extraBits & 16) + { + // get extra bits for length + extraBits &= 15; + count = huft->base + (bitb & inflate_mask[extraBits]); + bitb >>= extraBits; + bitk -= extraBits; + // decode distance base of block to copy + while(bitk < 15) + { + bitb |= *data++ << bitk; + bitk += 8; + availin--; + } + huft = distTree + (bitb & distMask); + extraBits = huft->Exop; + copymore = 1; + do + { + bitb >>= huft->Bits; + bitk -= huft->Bits; + if(extraBits & 16) + { + // get extra bits to add to distance base + extraBits &= 15; + while(bitk < extraBits) + { + bitb |= *data++ << bitk; + bitk += 8; + availin--; + } + dist = huft->base + (bitb & inflate_mask[extraBits]); + bitb >>= extraBits; + bitk -= extraBits; + + // do the copy + bytesToEnd -= count; + // offset before dest + if((dst - s->window) >= dist) + { + // just copy + src = dst - dist; + } + // else offset after destination + else + { + // bytes from offset to end + extraBits = dist - (dst - s->window); + // pointer to offset + src = s->end - extraBits; + // if source crosses, + if(count > extraBits) + { + // copy to end of window + dst = qcopy(dst, src, extraBits); + // copy rest from start of window + count -= extraBits; + src = s->window; + } + } + // copy all or what's left + dst = qcopy(dst, src, count); + copymore = 0; + } + else + { + if(!(extraBits & 64)) + { + huft += huft->base + (bitb & inflate_mask[extraBits]); + extraBits = huft->Exop; + } + else + { + inflate_error = "Inflate data: Invalid distance code"; + return(Z_DATA_ERROR); + } + } + } while(copymore); + + morebits = 0; + } + else + { + if(!(extraBits & 64)) + { + huft += huft->base + (bitb & inflate_mask[extraBits]); + extraBits = huft->Exop; + if(!extraBits) + { + bitb >>= huft->Bits; + bitk -= huft->Bits; + *dst++ = (byte)huft->base; + bytesToEnd--; + morebits = 0; + } + } + else if(extraBits & 32) + { + count = data - z->next_in; + + z->avail_in = availin; + z->total_in += count; + z->next_in = data; + + s->write = dst; + + count = (bitk >> 3) < count ? bitk >> 3 : count; + + s->bitb = bitb; + s->bitk = bitk - (count << 3); + z->avail_in += count; + z->total_in -= count; + z->next_in -= count ; + return(Z_STREAM_END); + } + else + { + inflate_error = "Inflate data: Invalid literal/length code"; + return(Z_DATA_ERROR); + } + } + } while(morebits); + } + } + + // not enough input or output--restore pointers and return + count = data - z->next_in; + + z->avail_in = availin; + z->total_in += count; + z->next_in = data; + + s->write = dst; + + count = (bitk >> 3) < count ? bitk >> 3 : count; + s->bitb = bitb; + s->bitk = bitk - (count << 3); + z->avail_in += count; + z->total_in -= count; + z->next_in -= count; + + return(Z_OK); +} + +// =============================================================================== +// =============================================================================== + +static void inflate_codes(z_stream *z, inflate_blocks_state_t *s) +{ + inflate_huft_t *huft; // temporary pointer + ulong extraBits; // extra bits or operation + ulong bytesToEnd; // bytes to end of window or read pointer + byte *src; // pointer to copy strings from + inflate_codes_state_t *infCodes; // codes state + + infCodes = s->decode.codes; + + // copy input/output information to locals + bytesToEnd = get_remaining(s); + + // process input and output based on current state + while(true) + { + // waiting for "i:"=input, "o:"=output, "x:"=nothing + switch (infCodes->mode) + { + // x: set up for LEN + case START: + if((bytesToEnd >= 258) && (z->avail_in >= 10)) + { + z->error = inflate_fast(inflate_mask[infCodes->lbits], inflate_mask[infCodes->dbits], infCodes->ltree, infCodes->dtree, s, z); + bytesToEnd = get_remaining(s); + if(z->error != Z_OK) + { + infCodes->mode = (z->error == Z_STREAM_END) ? WASH : BADCODE; + break; + } + } + infCodes->code.need = infCodes->lbits; + infCodes->code.tree = infCodes->ltree; + infCodes->mode = LEN; + // i: get length/literal/eob next + case LEN: + if(!getbits(z, s, infCodes->code.need)) + { + // We could get here because we have run out of input data *or* the stream has ended + if(z->status == Z_BUF_ERROR) + { + z->error = Z_STREAM_END; + } + return; + } + huft = infCodes->code.tree + (s->bitb & inflate_mask[infCodes->code.need]); + s->bitb >>= huft->Bits; + s->bitk -= huft->Bits; + extraBits = huft->Exop; + // literal + if(!extraBits) + { + infCodes->lit = huft->base; + infCodes->mode = LIT; + break; + } + // length + if(extraBits & 16) + { + infCodes->copy.get = extraBits & 15; + infCodes->len = huft->base; + infCodes->mode = LENEXT; + break; + } + // next table + if(!(extraBits & 64)) + { + infCodes->code.need = extraBits; + infCodes->code.tree = huft + huft->base; + break; + } + // end of block + if(extraBits & 32) + { + infCodes->mode = WASH; + break; + } + // invalid code + infCodes->mode = BADCODE; + inflate_error = "Inflate data: Invalid literal/length code"; + z->error = Z_DATA_ERROR; + inflate_flush(z, s); + return; + // i: getting length extra (have base) + case LENEXT: + if(!getbits(z, s, infCodes->copy.get)) + { + return; + } + infCodes->len += s->bitb & inflate_mask[infCodes->copy.get]; + s->bitb >>= infCodes->copy.get; + s->bitk -= infCodes->copy.get; + infCodes->code.need = infCodes->dbits; + infCodes->code.tree = infCodes->dtree; + infCodes->mode = DIST; + // i: get distance next + case DIST: + if(!getbits(z, s, infCodes->code.need)) + { + return; + } + huft = infCodes->code.tree + (s->bitb & inflate_mask[infCodes->code.need]); + s->bitb >>= huft->Bits; + s->bitk -= huft->Bits; + extraBits = huft->Exop; + // distance + if(extraBits & 16) + { + infCodes->copy.get = extraBits & 15; + infCodes->copy.dist = huft->base; + infCodes->mode = DISTEXT; + break; + } + // next table + if(!(extraBits & 64)) + { + infCodes->code.need = extraBits; + infCodes->code.tree = huft + huft->base; + break; + } + // invalid code + infCodes->mode = BADCODE; + inflate_error = "Inflate data: Invalid distance code"; + z->error = Z_DATA_ERROR; + inflate_flush(z, s); + return; + // i: getting distance extra + case DISTEXT: + if(!getbits(z, s, infCodes->copy.get)) + { + return; + } + infCodes->copy.dist += s->bitb & inflate_mask[infCodes->copy.get]; + s->bitb >>= infCodes->copy.get; + s->bitk -= infCodes->copy.get; + infCodes->mode = COPY; + // o: copying bytes in window, waiting for space + case COPY: + if(s->write - s->window < infCodes->copy.dist) + { + src = s->end - (infCodes->copy.dist - (s->write - s->window)); + } + else + { + src = s->write - infCodes->copy.dist; + } + while(infCodes->len) + { + bytesToEnd = needout(z, s, bytesToEnd); + if(!bytesToEnd) + { + return; + } + *s->write++ = (byte)(*src++); + bytesToEnd--; + if(src == s->end) + { + src = s->window; + } + infCodes->len--; + } + infCodes->mode = START; + break; + // o: got literal, waiting for output space + case LIT: + bytesToEnd = needout(z, s, bytesToEnd); + if(!bytesToEnd) + { + return; + } + *s->write++ = (byte)infCodes->lit; + bytesToEnd--; + infCodes->mode = START; + break; + // o: got eob, possibly more output + case WASH: + // return unused byte, if any + if(s->bitk > 7) + { + s->bitk -= 8; + z->avail_in++; + z->total_in--; + // can always return one + z->next_in--; + } + inflate_flush(z, s); + bytesToEnd = get_remaining(s); + if(s->read != s->write) + { + inflate_error = "Inflate data: read != write while in WASH"; + inflate_flush(z, s); + return; + } + infCodes->mode = END; + case END: + z->error = Z_STREAM_END; + inflate_flush(z, s); + return; + // x: got error + case BADCODE: + z->error = Z_DATA_ERROR; + inflate_flush(z, s); + return; + default: + z->error = Z_STREAM_ERROR; + inflate_flush(z, s); + return; + } + } +} + +// =============================================================================== +// =============================================================================== + +static inflate_codes_state_t *inflate_codes_new(z_stream *z, ulong bl, ulong bd, inflate_huft_t *lengthTree, inflate_huft_t *distTree) +{ + inflate_codes_state_t *c; + + c = (inflate_codes_state_t *)Z_Malloc(sizeof(inflate_codes_state_t), TAG_INFLATE, qtrue); + c->mode = START; + c->lbits = (byte)bl; + c->dbits = (byte)bd; + c->ltree = lengthTree; + c->dtree = distTree; + + return(c); +} + +// =============================================================================== +// Generate Huffman trees for efficient decoding + +// ulong b // code lengths in bits (all assumed <= BMAX) +// ulong n // number of codes (assumed <= 288) +// ulong s // number of simple-valued codes (0..s-1) +// const ulong *d // list of base values for non-simple codes +// const ulong *e // list of extra bits for non-simple codes +// inflate_huft ** t // result: starting table +// ulong *m // maximum lookup bits, returns actual +// inflate_huft *hp // space for trees +// ulong *hn // hufts used in space +// ulong *workspace // working area: values in order of bit length +// +// Given a list of code lengths and a maximum table size, make a set of +// tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR +// if the given code set is incomplete (the tables are still built in this +// case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of +// lengths). +// +// Huffman code decoding is performed using a multi-level table lookup. +// The fastest way to decode is to simply build a lookup table whose +// size is determined by the longest code. However, the time it takes +// to build this table can also be a factor if the data being decoded +// is not very long. The most common codes are necessarily the +// shortest codes, so those codes dominate the decoding time, and hence +// the speed. The idea is you can have a shorter table that decodes the +// shorter, more probable codes, and then point to subsidiary tables for +// the longer codes. The time it costs to decode the longer codes is +// then traded against the time it takes to make longer tables. +// +// This results of this trade are in the variables lbits and dbits +// below. lbits is the number of bits the first level table for literal/ +// length codes can decode in one step, and dbits is the same thing for +// the distance codes. Subsequent tables are also less than or equal to +// those sizes. These values may be adjusted either when all of the +// codes are shorter than that, in which case the longest code length in +// bits is used, or when the shortest code is *longer* than the requested +// table size, in which case the length of the shortest code in bits is +// used. +// +// There are two different values for the two tables, since they code a +// different number of possibilities each. The literal/length table +// codes 286 possible values, or in a flat code, a little over eight +// bits. The distance table codes 30 possible values, or a little less +// than five bits, flat. The optimum values for speed end up being +// about one bit more than those, so lbits is 8+1 and dbits is 5+1. +// The optimum values may differ though from machine to machine, and +// possibly even between compilers. Your mileage may vary. +// =============================================================================== + +static EStatus huft_build(ulong *b, ulong numCodes, ulong s, const ulong *d, const ulong *e, inflate_huft_t **t, ulong *m, inflate_huft_t *hp, ulong *hn, ulong *workspace) +{ + ulong codeCounter; // counter for codes of length bitsPerCode + ulong bitLengths[BMAX + 1] = { 0 }; // bit length count table + ulong bitOffsets[BMAX + 1]; // bit offsets, then code stack + ulong f; // i repeats in table every f entries + int maxCodeLen; // maximum code length + int tableLevel; // table level + ulong i; // counter, current code + ulong j; // counter + int bitsPerCode; // number of bits in current code + ulong bitsPerTable; // bits per table (returned in m) + int bitsBeforeTable; // bits before this table == (bitsPerTable * tableLevel) + ulong *p; // pointer into bitLengths[], b[], or workspace[] + inflate_huft_t *q; // points to current table + inflate_huft_t r; // table entry for structure assignment + inflate_huft_t *tableStack[BMAX]; // table stack + ulong *xp; // pointer into bitOffsets + int dummyCodes; // number of dummy codes added + ulong entryCount; // number of entries in current table + + // Generate counts for each bit length + // assume all entries <= BMAX + p = b; + i = numCodes; + do + { + bitLengths[*p++]++; + } while(--i); + + // null input--all zero length codes + if(bitLengths[0] == numCodes) + { + *t = NULL; + *m = 0; + return(Z_OK); + } + + // Find minimum and maximum length, bound *m by those + bitsPerTable = *m; + for(j = 1; j <= BMAX; j++) + { + if(bitLengths[j]) + { + break; + } + } + // minimum code length + bitsPerCode = j; + + if(bitsPerTable < j) + { + bitsPerTable = j; + } + for(i = BMAX; i; i--) + { + if(bitLengths[i]) + { + break; + } + } + // maximum code length + maxCodeLen = i; + + if(bitsPerTable > i) + { + bitsPerTable = i; + } + *m = bitsPerTable; + + // Adjust last length count to fill out codes, if needed + for(dummyCodes = 1 << j; j < i; j++, dummyCodes <<= 1) + { + dummyCodes -= bitLengths[j]; + if(dummyCodes < 0) + { + return(Z_DATA_ERROR); + } + } + dummyCodes -= bitLengths[i]; + if(dummyCodes < 0) + { + return(Z_DATA_ERROR); + } + bitLengths[i] += dummyCodes; + + // Generate starting offsets into the value table for each length + bitOffsets[1] = 0; + j = 0; + p = bitLengths + 1; + xp = bitOffsets + 2; + // note that i == maxCodeLen from above + while(--i) + { + j += *p++; + *xp++ = j; + } + + // Make a table of values in order of bit lengths + p = b; + i = 0; + do + { + j = *p++; + if(j) + { + workspace[bitOffsets[j]++] = i; + } + } while(++i < numCodes); + + // set numCodes to length of workspace + numCodes = bitOffsets[maxCodeLen]; + + // Generate the Huffman codes and for each, make the table entries + bitOffsets[0] = 0; // first Huffman code is zero + i = 0; + p = workspace; // grab values in bit order + tableLevel = -1; // no tables yet--level -1 + bitsBeforeTable = bitsPerTable; // bits decoded == (bitsPerTable * tableLevel) + bitsBeforeTable = -bitsBeforeTable; + tableStack[0] = NULL; // just to keep compilers happy + q = NULL; // ditto + entryCount = 0; // ditto + + // go through the bit lengths (bitsPerCode already is bits in shortest code) + for(; bitsPerCode <= maxCodeLen; bitsPerCode++) + { + codeCounter = bitLengths[bitsPerCode]; + while(codeCounter--) + { + // here i is the Huffman code of length bitsPerCode bits for value *p + // make tables up to required level + while(bitsPerCode > bitsBeforeTable + bitsPerTable) + { + tableLevel++; + bitsBeforeTable += bitsPerTable; // previous table always bitsPerTable bits + + // compute minimum size table less than or equal to bitsPerTable bits + entryCount = maxCodeLen - bitsBeforeTable; + entryCount = entryCount > bitsPerTable ? bitsPerTable : entryCount; // table size upper limit + j = bitsPerCode - bitsBeforeTable; + f = 1 << j; + if(f > codeCounter + 1) // try a bitsPerCode-bitsBeforeTable bit table + { // too few codes for bitsPerCode-bitsBeforeTable bit table + f -= codeCounter + 1; // deduct codes from patterns left + xp = bitLengths + bitsPerCode; + if(j < entryCount) + { + while(++j < entryCount) // try smaller tables up to entryCount bits + { + f <<= 1; + if(f <= *++xp) + { + break; // enough codes to use up j bits + } + f -= *xp; // else deduct codes from patterns + } + } + } + entryCount = 1 << j; // table entries for j-bit table + + // allocate new table + if(*hn + entryCount > MANY) // (note: doesn't matter for fixed) + { + return(Z_DATA_ERROR); // not enough memory + } + q = hp + *hn; + tableStack[tableLevel] = q; + *hn += entryCount; + + // connect to last table, if there is one + if(tableLevel) + { + bitOffsets[tableLevel] = i; // save pattern for backing up + r.Bits = (byte)bitsPerTable; // bits to dump before this table + r.Exop = (byte)j; // bits in this table + j = i >> (bitsBeforeTable - bitsPerTable); + r.base = q - tableStack[tableLevel - 1] - j; // offset to this table + tableStack[tableLevel - 1][j] = r; // connect to last table + } + else + { + *t = q; // first table is returned result + } + } + + // set up table entry in r + r.Bits = (byte)(bitsPerCode - bitsBeforeTable); + if(p >= workspace + numCodes) + { + r.Exop = 128 + 64; // out of values--invalid code + } + else if(*p < s) + { + r.Exop = (byte)(*p < 256 ? 0 : 32 + 64); // 256 is end-of-block + r.base = *p++; // simple code is just the value + } + else + { + r.Exop = (byte)(e[*p - s] + 16 + 64); // non-simple--look up in lists + r.base = d[*p++ - s]; + } + + // fill code-like entries with r + f = 1 << (bitsPerCode - bitsBeforeTable); + for(j = i >> bitsBeforeTable; j < entryCount; j += f) + { + q[j] = r; + } + + // backwards increment the bitsPerCode-bit code i + for(j = 1 << (bitsPerCode - 1); i & j; j >>= 1) + { + i ^= j; + } + i ^= j; + + // backup over finished tables + while((i & ((1 << bitsBeforeTable) - 1)) != bitOffsets[tableLevel]) + { + tableLevel--; // don't need to update q + bitsBeforeTable -= bitsPerTable; + } + } + } + + // Return Z_BUF_ERROR if we were given an incomplete table + if(dummyCodes && (maxCodeLen != 1)) + { + return(Z_BUF_ERROR); + } + return(Z_OK); +} + +// =============================================================================== +// ulong *c 19 code lengths +// ulong *bb bits tree desired/actual depth +// inflate_huft **tb bits tree result +// inflate_huft *hp space for trees +// =============================================================================== + +static void inflate_trees_bits(z_stream *z, ulong *c, ulong *bb, inflate_huft_t **tb, inflate_huft_t *hp) +{ + ulong hn = 0; // hufts used in space + ulong workspace[19]; // work area for huft_build + + z->error = huft_build(c, 19, 19, NULL, NULL, tb, bb, hp, &hn, workspace); + if(z->error == Z_DATA_ERROR) + { + inflate_error = "Inflate data: Oversubscribed dynamic bit lengths tree"; + } + else if((z->error == Z_BUF_ERROR) || !*bb) + { + inflate_error = "Inflate data: Incomplete dynamic bit lengths tree"; + z->error = Z_DATA_ERROR; + } +} + +// =============================================================================== +// ulong *c // that many (total) code lengths +// ulong *bl // literal desired/actual bit depth +// ulong *bd // distance desired/actual bit depth +// inflate_huft **tl // literal/length tree result +// inflate_huft **td // distance tree result +// inflate_huft *hp // space for trees +// =============================================================================== + +static void inflate_trees_dynamic(z_stream *z, ulong numLiteral, ulong numDist, ulong *c, ulong *bl, ulong *bd, inflate_huft_t **tl, inflate_huft_t **td, inflate_huft_t *hp) +{ + ulong hn = 0; // hufts used in space + ulong workspace[288]; // work area for huft_build + + // build literal/length tree + z->error = huft_build(c, numLiteral, 257, cplens, cplext, tl, bl, hp, &hn, workspace); + if(z->error != Z_OK || !*bl) + { + inflate_error = "Inflate data: Erroneous literal/length tree"; + z->error = Z_DATA_ERROR; + return; + } + // build distance tree + z->error = huft_build(c + numLiteral, numDist, 0, cpdist, extra_dbits, td, bd, hp, &hn, workspace); + if((z->error != Z_OK) || (!*bd && numLiteral > 257)) + { + inflate_error = "Inflate data: Erroneous distance tree"; + z->error = Z_DATA_ERROR; + return; + } +} + +// =============================================================================== +// ulong *bl // literal desired/actual bit depth +// ulong *bd // distance desired/actual bit depth +// inflate_huft **tl // literal/length tree result +// inflate_huft **td // distance tree result +// =============================================================================== + +// Fixme: Calculate dynamically + +static void inflate_trees_fixed(z_stream *z, ulong *bl, ulong *bd, inflate_huft_t **tl, inflate_huft_t **td) +{ + *bl = fixed_bl; + *bd = fixed_bd; + *tl = fixed_tl; + *td = fixed_td; + z->error = Z_OK; +} + +// =============================================================================== +// =============================================================================== + +static void inflate_blocks(inflate_blocks_state_t *s, z_stream *z) +{ + ulong t; // temporary storage + ulong bytesToEnd; // bytes to end of window or read pointer + ulong bl, bd; + inflate_huft_t *lengthTree = NULL; + inflate_huft_t *distTree = NULL; + inflate_codes_state_t *c; + + // copy input/output information to locals (UPDATE macro restores) + bytesToEnd = s->write < s->read ? s->read - s->write - 1 : s->end - s->write; + + // process input based on current state + while(true) + { + switch (s->mode) + { + case TYPE: + if(!getbits(z, s, 3)) + { + return; + } + t = s->bitb & 7; + s->last = !!(t & 1); + + switch (t >> 1) + { + case STORED_BLOCK: + s->bitb >>= 3; + s->bitk -= 3; + t = s->bitk & 7; // go to byte boundary + s->bitb >>= t; + s->bitk -= t; + s->mode = LENS; // get length of stored block + break; + case STATIC_TREES: + inflate_trees_fixed(z, &bl, &bd, &lengthTree, &distTree); + s->decode.codes = inflate_codes_new(z, bl, bd, lengthTree, distTree); + s->bitb >>= 3; + s->bitk -= 3; + s->mode = CODES; + break; + case DYN_TREES: + s->bitb >>= 3; + s->bitk -= 3; + s->mode = TABLE; + break; + case MODE_ILLEGAL: + s->bitb >>= 3; + s->bitk -= 3; + s->mode = BAD; + inflate_error = "Inflate data: Invalid block type"; + z->error = Z_DATA_ERROR; + inflate_flush(z, s); + return; + } + break; + case LENS: + if(!getbits(z, s, 32)) + { + return; + } + if(((~s->bitb) >> 16) != (s->bitb & 0xffff)) + { + s->mode = BAD; + inflate_error = "Inflate data: Invalid stored block lengths"; + z->error = Z_DATA_ERROR; + inflate_flush(z, s); + return; + } + s->left = s->bitb & 0xffff; + s->bitb = 0; + s->bitk = 0; // dump bits + s->mode = s->left ? STORED : (s->last ? DRY : TYPE); + break; + case STORED: + if(!z->avail_in) + { + inflate_flush(z, s); + return; + } + bytesToEnd = needout(z, s, bytesToEnd); + if(!bytesToEnd) + { + return; + } + t = s->left; + if(t > z->avail_in) + { + t = z->avail_in; + } + if(t > bytesToEnd) + { + t = bytesToEnd; + } + memcpy(s->write, z->next_in, t); + z->next_in += t; + z->avail_in -= t; + z->total_in += t; + s->write += t; + bytesToEnd -= t; + s->left -= t; + if(s->left) + { + break; + } + s->mode = s->last ? DRY : TYPE; + break; + case TABLE: + if(!getbits(z, s, 14)) + { + return; + } + t = s->bitb & 0x3fff; + s->trees.table = t; + if((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) + { + s->mode = BAD; + inflate_error = "Inflate data: Too many length or distance symbols"; + z->error = Z_DATA_ERROR; + inflate_flush(z, s); + return; + } + t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); + s->trees.blens = (ulong *)Z_Malloc(t * sizeof(ulong), TAG_INFLATE, qfalse); + s->bitb >>= 14; + s->bitk -= 14; + s->trees.index = 0; + s->mode = BTREE; + case BTREE: + while(s->trees.index < 4 + (s->trees.table >> 10)) + { + if(!getbits(z, s, 3)) + { + return; + } + s->trees.blens[border[s->trees.index++]] = s->bitb & 7; + s->bitb >>= 3; + s->bitk -= 3; + } + while(s->trees.index < 19) + { + s->trees.blens[border[s->trees.index++]] = 0; + } + s->trees.bb = 7; + inflate_trees_bits(z, s->trees.blens, &s->trees.bb, &s->trees.tb, s->hufts); + if(z->error != Z_OK) + { + Z_Free(s->trees.blens); + s->mode = BAD; + inflate_flush(z, s); + return; + } + s->trees.index = 0; + s->mode = DTREE; + case DTREE: + while(t = s->trees.table, s->trees.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f)) + { + inflate_huft_t *h; + ulong i, j, c; + + t = s->trees.bb; + if(!getbits(z, s, t)) + { + return; + } + h = s->trees.tb + (s->bitb & inflate_mask[t]); + t = h->Bits; + c = h->base; + if(c < 16) + { + s->bitb >>= t; + s->bitk -= t; + s->trees.blens[s->trees.index++] = c; + } + else // c == 16..18 + { + i = (c == 18) ? 7 : c - 14; + j = (c == 18) ? 11 : 3; + if(!getbits(z, s, t + i)) + { + return; + } + s->bitb >>= t; + s->bitk -= t; + j += s->bitb & inflate_mask[i]; + s->bitb >>= i; + s->bitk -= i; + i = s->trees.index; + t = s->trees.table; + if(i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || (c == 16 && i < 1)) + { + Z_Free(s->trees.blens); + s->mode = BAD; + inflate_error = "Inflate data: Invalid bit length repeat"; + z->error = Z_DATA_ERROR; + inflate_flush(z, s); + return; + } + c = (c == 16) ? s->trees.blens[i - 1] : 0; + do + { + s->trees.blens[i++] = c; + } while(--j); + s->trees.index = i; + } + } + s->trees.tb = NULL; + + bl = 9; // must be <= 9 for lookahead assumptions + bd = 6; // must be <= 9 for lookahead assumptions + t = s->trees.table; + inflate_trees_dynamic(z, 257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), s->trees.blens, &bl, &bd, &lengthTree, &distTree, s->hufts); + Z_Free(s->trees.blens); + if(z->error != Z_OK) + { + s->mode = BAD; + inflate_flush(z, s); + return; + } + c = inflate_codes_new(z, bl, bd, lengthTree, distTree); + s->decode.codes = c; + s->mode = CODES; + case CODES: + inflate_codes(z, s); + if(z->error != Z_STREAM_END) + { + inflate_flush(z, s); + return; + } + z->error = Z_OK; + Z_Free(s->decode.codes); + bytesToEnd = s->write < s->read ? s->read - s->write - 1 : s->end - s->write; + if(!s->last) + { + s->mode = TYPE; + break; + } + s->mode = DRY; + case DRY: + inflate_flush(z, s); + bytesToEnd = s->write < s->read ? s->read - s->write - 1 : s->end - s->write; + if(s->read != s->write) + { + inflate_error = "Inflate data: read != write in DRY"; + inflate_flush(z, s); + return; + } + s->mode = DONE; + case DONE: + z->error = Z_STREAM_END; + inflate_flush(z, s); + return; + case BAD: + default: + z->error = Z_DATA_ERROR; + inflate_flush(z, s); + return; + } + } +} + +// ------------------------------------------------------------------------------------------------- +// Controlling routines +// ------------------------------------------------------------------------------------------------- + +EStatus inflateEnd(z_stream *z) +{ + assert(z); + + if(z->istate->blocks) + { + inflate_blocks_free(z, z->istate->blocks); + z->istate->blocks = NULL; + } + if(z->istate) + { + Z_Free(z->istate); + z->istate = NULL; + } + return(Z_OK); +} + +// =============================================================================== +// =============================================================================== + +EStatus inflateInit(z_stream *z, EFlush flush, int noWrap) +{ + // initialize state + assert(z); + + inflate_error = "OK"; + + z->istate = (inflate_state *)Z_Malloc(sizeof(inflate_state), TAG_INFLATE, qtrue); + z->istate->blocks = NULL; + + // handle nowrap option (no zlib header or check) + z->istate->nowrap = noWrap; + z->istate->wbits = MAX_WBITS; + + // create inflate_blocks state + z->istate->blocks = inflate_blocks_new(z, NULL); + + z->status = Z_OK; + if(flush == Z_FINISH) + { + z->status = Z_BUF_ERROR; + } + + // reset state + z->istate->mode = imMETHOD; + if(z->istate->nowrap) + { + z->istate->mode = imBLOCKS; + } + inflate_blocks_reset(z, z->istate->blocks); + return(Z_OK); +} + +// =============================================================================== +// =============================================================================== + +EStatus inflate(z_stream *z) +{ + ulong b; + + // Sanity check data + assert(z); + assert(z->istate); + + while(true) + { + switch (z->istate->mode) + { + case imMETHOD: + if(!z->avail_in) + { + return(z->status); + } + z->istate->method = *z->next_in++; + z->avail_in--; + z->total_in++; + if((z->istate->method & 0xf) != ZF_DEFLATED) + { + z->istate->mode = imBAD; + inflate_error = "Inflate data: Unknown compression method"; + return(Z_DATA_ERROR); + } + if((z->istate->method >> 4) + 8 > z->istate->wbits) + { + z->istate->mode = imBAD; + inflate_error = "Inflate data: Invalid window size"; + return(Z_DATA_ERROR); + } + z->istate->mode = imFLAG; + break; + case imFLAG: + if(!z->avail_in) + { + return(z->status); + } + b = *z->next_in++; + z->avail_in--; + z->total_in++; + if(((z->istate->method << 8) + b) % 31) + { + z->istate->mode = imBAD; + inflate_error = "Inflate data: Incorrect header check"; + return(Z_DATA_ERROR); + } + z->istate->mode = imBLOCKS; + break; + case imBLOCKS: + inflate_blocks(z->istate->blocks, z); + + // Make sure everything processed ok + if(z->error == Z_DATA_ERROR) + { + z->istate->mode = imBAD; + return(Z_DATA_ERROR); + } + + if(z->error != Z_STREAM_END) + { + return(z->status); + } + z->istate->calcadler = z->istate->adler; + inflate_blocks_reset(z, z->istate->blocks); + if(z->istate->nowrap) + { + z->istate->mode = imDONE; + break; + } + z->istate->mode = imCHECK4; + break; + case imCHECK4: + if(!z->avail_in) + { + return(z->status); + } + z->istate->adler = *z->next_in++ << 24; + z->avail_in--; + z->total_in++; + z->istate->mode = imCHECK3; + break; + case imCHECK3: + if(!z->avail_in) + { + return(z->status); + } + z->istate->adler += *z->next_in++ << 16; + z->avail_in--; + z->total_in++; + z->istate->mode = imCHECK2; + break; + case imCHECK2: + if(!z->avail_in) + { + return(z->status); + } + z->istate->adler += *z->next_in++ << 8; + z->avail_in--; + z->total_in++; + z->istate->mode = imCHECK1; + break; + case imCHECK1: + if(!z->avail_in) + { + return(z->status); + } + z->istate->adler += *z->next_in++; + z->avail_in--; + z->total_in++; + + if(z->istate->calcadler != z->istate->adler) + { + inflate_error = "Inflate data: Failed Adler checksum"; + z->istate->mode = imBAD; + break; + } + z->istate->mode = imDONE; + break; + case imDONE: + return(Z_STREAM_END); + case imBAD: + return(Z_DATA_ERROR); + default: + return(Z_STREAM_ERROR); + } + } + assert(0); + return(Z_OK); +} + +// =============================================================================== +// =============================================================================== + +const char *inflateError(void) +{ + return(inflate_error); +} + +// =============================================================================== +// External calls +// =============================================================================== + +bool InflateFile(byte *src, ulong compressedSize, byte *dst, ulong uncompressedSize, int noWrap) +{ + z_stream z = { 0 }; + + inflateInit(&z, Z_FINISH, noWrap); + + z.next_in = src; + z.avail_in = compressedSize; + z.next_out = dst; + z.avail_out = uncompressedSize; + +#ifdef _TIMING + int temp = timeGetTime(); +#endif + if(inflate(&z) != Z_STREAM_END) + { + inflate_error = "Inflate data: Stream did not end"; + inflateEnd(&z); + return(false); + } +#ifdef _TIMING + totalInflateTime += timeGetTime() - temp; + totalInflateCount++; +#endif + + if(z.avail_in) + { + inflate_error = "Inflate data: Remaining input data at stream end"; + inflateEnd(&z); + return(false); + } + if(z.avail_out) + { + inflate_error = "Inflate data: Remaining output space at stream end"; + inflateEnd(&z); + return(false); + } + if(z.total_in != compressedSize) + { + inflate_error = "Inflate data: Number of processed bytes != compressed size"; + inflateEnd(&z); + return(false); + } + if(z.total_out != uncompressedSize) + { + inflate_error = "Inflate data: Number of bytes output != uncompressed size"; + inflateEnd(&z); + return(false); + } + inflateEnd(&z); + return(true); +} + +// end diff --git a/code/zlib32/inflate.h b/code/zlib32/inflate.h new file mode 100644 index 0000000..5c738d9 --- /dev/null +++ b/code/zlib32/inflate.h @@ -0,0 +1,145 @@ +// Maximum size of dynamic tree. The maximum found in a long but non- +// exhaustive search was 1004 huft structures (850 for length/literals +// and 154 for distances, the latter actually the result of an +// exhaustive search). The actual maximum is not known, but the +// value below is more than safe. + +#define MANY 1440 + +// maximum bit length of any code (if BMAX needs to be larger than 16, then h and x[] should be ulong.) +#define BMAX 15 + +typedef ulong (*check_func) (ulong check, const byte *buf, ulong len); + +typedef enum +{ + TYPE, // get type bits (3, including end bit) + LENS, // get lengths for stored + STORED, // processing stored block + TABLE, // get table lengths + BTREE, // get bit lengths tree for a dynamic block + DTREE, // get length, distance trees for a dynamic block + CODES, // processing fixed or dynamic block + DRY, // output remaining window bytes + DONE, // finished last block, done + BAD // got a data error--stuck here +} inflate_block_mode; + +// waiting for "i:"=input, "o:"=output, "x:"=nothing +typedef enum +{ + START, // x: set up for LEN + LEN, // i: get length/literal/eob next + LENEXT, // i: getting length extra (have base) + DIST, // i: get distance next + DISTEXT, // i: getting distance extra + COPY, // o: copying bytes in window, waiting for space + LIT, // o: got literal, waiting for output space + WASH, // o: got eob, possibly still output waiting + END, // x: got eob and all data flushed + BADCODE // x: got error +} inflate_codes_mode; + +typedef enum +{ + imMETHOD, // waiting for method byte + imFLAG, // waiting for flag byte + imBLOCKS, // decompressing blocks + imCHECK4, // four check bytes to go + imCHECK3, // three check bytes to go + imCHECK2, // two check bytes to go + imCHECK1, // one check byte to go + imDONE, // finished check, done + imBAD // got an error--stay here +} inflate_mode; + +typedef struct inflate_huft_s +{ + byte Exop; // number of extra bits or operation + byte Bits; // number of bits in this code or subcode + ulong base; // literal, length base, distance base, or table offset +} inflate_huft_t; + +// inflate codes private state +typedef struct inflate_codes_state_s +{ + inflate_codes_mode mode; // current inflate_codes mode + + // mode dependent information + ulong len; + union + { + struct + { + inflate_huft_t *tree; // pointer into tree + ulong need; // bits needed + } code; // if LEN or DIST, where in tree + ulong lit; // if LIT, literal + struct + { + ulong get; // bits to get for extra + ulong dist; // distance back to copy from + } copy; // if EXT or COPY, where and how much + }; // submode + + // mode independent information + byte lbits; // ltree bits decoded per branch + byte dbits; // dtree bits decoder per branch + inflate_huft_t *ltree; // literal/length/eob tree + inflate_huft_t *dtree; // distance tree +} inflate_codes_state_t; + +// inflate blocks semi-private state +typedef struct inflate_blocks_state_s +{ + // mode + inflate_block_mode mode; // current inflate_block mode + + // mode dependent information + union + { + ulong left; // if STORED, bytes left to copy + struct + { + ulong table; // table lengths (14 bits) + ulong index; // index into blens (or border) + ulong *blens; // bit lengths of codes + ulong bb; // bit length tree depth + inflate_huft_t *tb; // bit length decoding tree + } trees; // if DTREE, decoding info for trees + struct + { + inflate_codes_state_t *codes; + } decode; // if CODES, current state + }; // submode + bool last; // true if this block is the last block + + // mode independent information + ulong bitk; // bits in bit buffer + ulong bitb; // bit buffer + inflate_huft_t *hufts; // single malloc for tree space + byte window[WINDOW_SIZE]; // sliding window + byte *end; // one byte after sliding window + byte *read; // window read pointer + byte *write; // window write pointer + ulong check; // check on output +} inflate_blocks_state_t; + +// inflate private state +typedef struct inflate_state_s +{ + inflate_mode mode; // current inflate mode + + ulong method; // if FLAGS, method byte + + // mode independent information + int nowrap; // flag for no wrapper + ulong wbits; // log2(window size) (8..15, defaults to 15) + inflate_blocks_state_t *blocks; // current inflate_blocks state + + ulong adler; + ulong calcadler; +} inflate_state; + + +// end diff --git a/code/zlib32/zip.h b/code/zlib32/zip.h new file mode 100644 index 0000000..0b16a1a --- /dev/null +++ b/code/zlib32/zip.h @@ -0,0 +1,195 @@ +// +// zlib.h -- interface of the 'zlib' general purpose compression library +// version 1.1.3, July 9th, 1998 +// +// Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// +// Jean-loup Gailly Mark Adler +// jloup@gzip.org madler@alumni.caltech.edu +// +// The data format used by the zlib library is described by RFCs (Request for +// Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt +// (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +// + +// The 'zlib' compression library provides in-memory compression and +// decompression functions, including integrity checks of the uncompressed +// data. This version of the library supports only one compression method +// (deflation) but other algorithms will be added later and will have the same +// stream interface. +// +// Compression can be done in a single step if the buffers are large +// enough (for example if an input file is mmap'ed), or can be done by +// repeated calls of the compression function. In the latter case, the +// application must provide more input and/or consume the output +// (providing more output space) before each call. +// +// The library does not install any signal handler. The decoder checks +// the consistency of the compressed data, so the library should never +// crash even in case of corrupted input. + +// This particular implementation has been heavily modified by jscott@ravensoft.com +// to increase inflate/deflate speeds on 32 bit machines. + +// for more info about .ZIP format, see +// ftp://ftp.cdrom.com/pub/infozip/doc/appnote-970311-iz.zip +// PkWare has also a specification at : +// ftp://ftp.pkware.com/probdesc.zip + +// ======================================================================================== +// External calls and defines required for the zlib +// ======================================================================================== + +// The deflate compression method +#define ZF_STORED 0 +#define ZF_DEFLATED 8 + +// Compression levels +typedef enum +{ + Z_STORE_COMPRESSION, + Z_FAST_COMPRESSION_LOW, + Z_FAST_COMPRESSION, + Z_FAST_COMPRESSION_HIGH, + Z_SLOW_COMPRESSION_LOWEST, + Z_SLOW_COMPRESSION_LOW, + Z_DEFAULT_COMPRESSION, + Z_SLOW_COMPRESSION_HIGH, + Z_SLOW_COMPRESSION_HIGHEST, + Z_MAX_COMPRESSION, +} ELevel; + +// Allowed flush values +typedef enum +{ + Z_NEED_MORE = -1, // Special case when finishing up the stream + Z_NO_FLUSH, + Z_SYNC_FLUSH, // Sync up the stream ready for another call + Z_FINISH // Finish up the stream +} EFlush; + +// Return codes for the compression/decompression functions. Negative +// values are errors, positive values are used for special but normal events. +typedef enum +{ + Z_STREAM_ERROR = -3, // Basic error from failed sanity checks + Z_BUF_ERROR, // Not enough input or output + Z_DATA_ERROR, // Invalid data in the stream + Z_OK, + Z_STREAM_END // End of stream +} EStatus; + +// Maximum value for windowBits in deflateInit and inflateInit. +// The memory requirements for inflate are (in bytes) 1 << windowBits +// that is, 32K for windowBits=15 (default value) plus a few kilobytes +// for small objects. +#define MAX_WBITS 15 // 32K LZ77 window +#define WINDOW_SIZE (1 << MAX_WBITS) +#define BIG_WINDOW_SIZE (WINDOW_SIZE << 1) +#define WINDOW_MASK (WINDOW_SIZE - 1) + +// The three kinds of block type +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +#define MODE_ILLEGAL 3 + +// The minimum and maximum match lengths +#define MIN_MATCH 3 +#define MAX_MATCH 258 + +// number of distance codes +#define D_CODES 30 + +extern const ulong extra_dbits[D_CODES]; + +// Structure to be used by external applications + +// The application must update next_in and avail_in when avail_in has +// dropped to zero. It must update next_out and avail_out when avail_out +// has dropped to zero. All other fields are set by the +// compression library and must not be updated by the application. + +typedef struct z_stream_s +{ + byte *next_in; // next input unsigned char + ulong avail_in; // number of unsigned chars available at next_in + ulong total_in; // total number of bytes processed so far + + byte *next_out; // next output unsigned char should be put there + ulong avail_out; // remaining free space at next_out + ulong total_out; // total number of bytes output + + EStatus status; + EStatus error; // error code + + struct inflate_state_s *istate; // not visible by applications + struct deflate_state_s *dstate; // not visible by applications + + ulong quality; +} z_stream; + +// Update a running crc with the bytes buf[0..len-1] and return the updated +// crc. If buf is NULL, this function returns the required initial value +// for the crc. Pre- and post-conditioning (one's complement) is performed +// within this function so it shouldn't be done by the application. +// Usage example: +// +// ulong crc = crc32(0L, NULL, 0); +// +// while (read_buffer(buffer, length) != EOF) { +// crc = crc32(crc, buffer, length); +// } +// if (crc != original_crc) error(); + +ulong crc32(ulong crc, const byte *buf, ulong len); + +// Update a running Adler-32 checksum with the bytes buf[0..len-1] and +// return the updated checksum. If buf is NULL, this function returns +// the required initial value for the checksum. +// An Adler-32 checksum is almost as reliable as a CRC32 but can be computed +// much faster. Usage example: +// +// ulong adler = adler32(0L, NULL, 0); +// +// while (read_buffer(buffer, length) != EOF) { +// adler = adler32(adler, buffer, length); +// } +// if (adler != original_adler) error(); + +ulong adler32(ulong adler, const byte *buf, ulong len); + +// External calls to the deflate code +EStatus deflateInit(z_stream *strm, ELevel level, int noWrap = 0); +EStatus deflateCopy(z_stream *dest, z_stream *source); +EStatus deflate(z_stream *strm, EFlush flush); +EStatus deflateEnd(z_stream *strm); +const char *deflateError(void); + +// External calls to the deflate code +EStatus inflateInit(z_stream *strm, EFlush flush, int noWrap = 0); +EStatus inflate(z_stream *z); +EStatus inflateEnd(z_stream *strm); +const char *inflateError(void); + +// External calls to the zipfile code +bool InflateFile(byte *src, ulong compressedSize, byte *dst, ulong uncompressedSize, int noWrap = 0); +bool DeflateFile(byte *src, ulong uncompressedSize, byte *dst, ulong maxCompressedSize, ulong *compressedSize, ELevel level, int noWrap = 0); + +// end diff --git a/code/zlib32/zipcommon.cpp b/code/zlib32/zipcommon.cpp new file mode 100644 index 0000000..07e1106 --- /dev/null +++ b/code/zlib32/zipcommon.cpp @@ -0,0 +1,117 @@ +// ----------------------------------------------------------------------------------------------- +// Table of CRC-32's of all single-byte values (made by make_crc_table) +// ----------------------------------------------------------------------------------------------- + +static const unsigned long crc_table[256] = +{ + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; + +// ----------------------------------------------------------------------------------------------- +// Calculate 32 bit CRC checksum for len bytes +// ----------------------------------------------------------------------------------------------- + +unsigned long crc32(unsigned long crc, const unsigned char *buf, unsigned long len) +{ + if(!buf) + { + return(0); + } + crc = crc ^ 0xffffffff; + while(len--) + { + crc = crc_table[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8); + } + return(crc ^ 0xffffffff); +} + +// ----------------------------------------------------------------------------------------------- +// Calculate 32 bit Adler checksum (quicker than CRC) +// ----------------------------------------------------------------------------------------------- + +// largest prime smaller than 65536 +#define BASE 65521 +// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 +#define NMAX 5552 + +unsigned long adler32(unsigned long adler, const unsigned char *buf, unsigned long len) +{ + unsigned long s1; + unsigned long s2; + int k; + + if(!buf) + { + return(1); + } + s1 = adler & 0xffff; + s2 = (adler >> 16) & 0xffff; + + while (len > 0) + { + k = len < NMAX ? len : NMAX; + len -= k; + + while (k--) + { + s1 += *buf++; + s2 += s1; + } + s1 %= BASE; + s2 %= BASE; + } + return (s2 << 16) | s1; +} + +// end \ No newline at end of file diff --git a/codemp/CommandLine.txt b/codemp/CommandLine.txt new file mode 100644 index 0000000..be6c595 --- /dev/null +++ b/codemp/CommandLine.txt @@ -0,0 +1,3 @@ +JKA Multiplayer, select all build types, set these command line parms: + ++set fs_cdpath w:\game +set r_fullscreen 0 +set fs_copyfiles 0 +set vm_ui 0 +set vm_cgame 0 +set vm_game 0 +set sv_pure 0 diff --git a/codemp/JKA_mp.sln b/codemp/JKA_mp.sln new file mode 100644 index 0000000..2314aae --- /dev/null +++ b/codemp/JKA_mp.sln @@ -0,0 +1,76 @@ +Microsoft Visual Studio Solution File, Format Version 7.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "x_botlib", "x_botlib\x_botlib.vcproj", "{ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "x_exe", "x_exe\x_exe.vcproj", "{EEDF772D-5D2B-4C0F-B5C8-0A5828917847}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "x_jk2cgame", "x_jk2cgame\x_jk2cgame.vcproj", "{C79C9037-8F32-4833-A276-96B2FB96CCA5}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "x_jk2game", "x_jk2game\x_jk2game.vcproj", "{A0451C0A-0B2F-400A-BBB1-98F517FAB30B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "x_ui", "x_ui\x_ui.vcproj", "{613EA919-780E-416D-90A9-67D3EB27F899}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "goblib", "goblib\goblib.vcproj", "{0959DD50-BCBA-4551-9617-E6FD4DF85298}" +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + ConfigName.0 = Debug + ConfigName.1 = Final + ConfigName.2 = Release + EndGlobalSection + GlobalSection(ProjectDependencies) = postSolution + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.0 = {613EA919-780E-416D-90A9-67D3EB27F899} + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.1 = {A0451C0A-0B2F-400A-BBB1-98F517FAB30B} + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.2 = {C79C9037-8F32-4833-A276-96B2FB96CCA5} + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.3 = {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD} + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.4 = {0959DD50-BCBA-4551-9617-E6FD4DF85298} + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Debug.ActiveCfg = Debug|Xbox + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Debug.Build.0 = Debug|Xbox + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Final.ActiveCfg = FinalBuild|Xbox + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Final.Build.0 = FinalBuild|Xbox + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Release.ActiveCfg = Release|Xbox + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Release.Build.0 = Release|Xbox + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.Debug.ActiveCfg = Debug|Xbox + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.Debug.Build.0 = Debug|Xbox + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.Final.ActiveCfg = FinalBuild|Xbox + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.Final.Build.0 = FinalBuild|Xbox + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.Release.ActiveCfg = Release|Xbox + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.Release.Build.0 = Release|Xbox + {C79C9037-8F32-4833-A276-96B2FB96CCA5}.Debug.ActiveCfg = Debug|Xbox + {C79C9037-8F32-4833-A276-96B2FB96CCA5}.Debug.Build.0 = Debug|Xbox + {C79C9037-8F32-4833-A276-96B2FB96CCA5}.Final.ActiveCfg = FinalBuild|Xbox + {C79C9037-8F32-4833-A276-96B2FB96CCA5}.Final.Build.0 = FinalBuild|Xbox + {C79C9037-8F32-4833-A276-96B2FB96CCA5}.Release.ActiveCfg = Release|Xbox + {C79C9037-8F32-4833-A276-96B2FB96CCA5}.Release.Build.0 = Release|Xbox + {A0451C0A-0B2F-400A-BBB1-98F517FAB30B}.Debug.ActiveCfg = Debug|Xbox + {A0451C0A-0B2F-400A-BBB1-98F517FAB30B}.Debug.Build.0 = Debug|Xbox + {A0451C0A-0B2F-400A-BBB1-98F517FAB30B}.Final.ActiveCfg = FinalBuild|Xbox + {A0451C0A-0B2F-400A-BBB1-98F517FAB30B}.Final.Build.0 = FinalBuild|Xbox + {A0451C0A-0B2F-400A-BBB1-98F517FAB30B}.Release.ActiveCfg = Release|Xbox + {A0451C0A-0B2F-400A-BBB1-98F517FAB30B}.Release.Build.0 = Release|Xbox + {613EA919-780E-416D-90A9-67D3EB27F899}.Debug.ActiveCfg = Debug|Xbox + {613EA919-780E-416D-90A9-67D3EB27F899}.Debug.Build.0 = Debug|Xbox + {613EA919-780E-416D-90A9-67D3EB27F899}.Final.ActiveCfg = FinalBuild|Xbox + {613EA919-780E-416D-90A9-67D3EB27F899}.Final.Build.0 = FinalBuild|Xbox + {613EA919-780E-416D-90A9-67D3EB27F899}.Release.ActiveCfg = Release|Xbox + {613EA919-780E-416D-90A9-67D3EB27F899}.Release.Build.0 = Release|Xbox + {0959DD50-BCBA-4551-9617-E6FD4DF85298}.Debug.ActiveCfg = Debug|Xbox + {0959DD50-BCBA-4551-9617-E6FD4DF85298}.Debug.Build.0 = Debug|Xbox + {0959DD50-BCBA-4551-9617-E6FD4DF85298}.Final.ActiveCfg = FinalBuild|Xbox + {0959DD50-BCBA-4551-9617-E6FD4DF85298}.Final.Build.0 = FinalBuild|Xbox + {0959DD50-BCBA-4551-9617-E6FD4DF85298}.Release.ActiveCfg = Release|Xbox + {0959DD50-BCBA-4551-9617-E6FD4DF85298}.Release.Build.0 = Release|Xbox + EndGlobalSection + GlobalSection(SolutionItems) = postSolution + Item:1 = CommandLine.txt + Item:2 = install.bat + Item:3 = VU.bat + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection + GlobalSection(DevPartner) = postSolution + EndGlobalSection +EndGlobal diff --git a/codemp/Splines/Splines.dsp b/codemp/Splines/Splines.dsp new file mode 100644 index 0000000..f003be8 --- /dev/null +++ b/codemp/Splines/Splines.dsp @@ -0,0 +1,156 @@ +# Microsoft Developer Studio Project File - Name="Splines" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=Splines - Win32 Debug +!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 "Splines.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 "Splines.mak" CFG="Splines - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Splines - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "Splines - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/General/code/Splines", GAAAAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Splines - 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 Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "Splines - 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 Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GR /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FR /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "Splines - Win32 Release" +# Name "Splines - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\math_angles.cpp +# End Source File +# Begin Source File + +SOURCE=.\math_matrix.cpp +# End Source File +# Begin Source File + +SOURCE=.\math_quaternion.cpp +# End Source File +# Begin Source File + +SOURCE=.\math_vector.cpp +# End Source File +# Begin Source File + +SOURCE=.\q_parse.cpp +# End Source File +# Begin Source File + +SOURCE=.\q_shared.cpp +# End Source File +# Begin Source File + +SOURCE=.\splines.cpp +# End Source File +# Begin Source File + +SOURCE=.\util_str.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\math_angles.h +# End Source File +# Begin Source File + +SOURCE=.\math_matrix.h +# End Source File +# Begin Source File + +SOURCE=.\math_quaternion.h +# End Source File +# Begin Source File + +SOURCE=.\math_vector.h +# End Source File +# Begin Source File + +SOURCE=.\q_shared.h +# End Source File +# Begin Source File + +SOURCE=.\splines.h +# End Source File +# Begin Source File + +SOURCE=.\util_list.h +# End Source File +# Begin Source File + +SOURCE=.\util_str.h +# End Source File +# End Group +# End Target +# End Project diff --git a/codemp/Splines/math_angles.cpp b/codemp/Splines/math_angles.cpp new file mode 100644 index 0000000..b1c6843 --- /dev/null +++ b/codemp/Splines/math_angles.cpp @@ -0,0 +1,129 @@ +#include "q_shared.h" +#include + +angles_t ang_zero( 0.0f, 0.0f, 0.0f ); + +void toAngles( mat3_t &src, angles_t &dst ) { + double theta; + double cp; + double sp; + + sp = src[ 0 ][ 2 ]; + + // cap off our sin value so that we don't get any NANs + if ( sp > 1.0 ) { + sp = 1.0; + } else if ( sp < -1.0 ) { + sp = -1.0; + } + + theta = -asin( sp ); + cp = cos( theta ); + + if ( cp > 8192 * FLT_EPSILON ) { + dst.pitch = theta * 180 / M_PI; + dst.yaw = atan2( src[ 0 ][ 1 ], src[ 0 ][ 0 ] ) * 180 / M_PI; + dst.roll = atan2( src[ 1 ][ 2 ], src[ 2 ][ 2 ] ) * 180 / M_PI; + } else { + dst.pitch = theta * 180 / M_PI; + dst.yaw = -atan2( src[ 1 ][ 0 ], src[ 1 ][ 1 ] ) * 180 / M_PI; + dst.roll = 0; + } +} + +void toAngles( quat_t &src, angles_t &dst ) { + mat3_t temp; + + toMatrix( src, temp ); + toAngles( temp, dst ); +} + +void toAngles( idVec3_t &src, angles_t &dst ) { + dst.pitch = src[ 0 ]; + dst.yaw = src[ 1 ]; + dst.roll = src[ 2 ]; +} + +void angles_t::toVectors( idVec3_t *forward, idVec3_t *right, idVec3_t *up ) { + float angle; + static float sr, sp, sy, cr, cp, cy; // static to help MS compiler fp bugs + + angle = yaw * ( M_PI * 2 / 360 ); + sy = sin( angle ); + cy = cos( angle ); + + angle = pitch * ( M_PI * 2 / 360 ); + sp = sin( angle ); + cp = cos( angle ); + + angle = roll * ( M_PI * 2 / 360 ); + sr = sin( angle ); + cr = cos( angle ); + + if ( forward ) { + forward->set( cp * cy, cp * sy, -sp ); + } + + if ( right ) { + right->set( -sr * sp * cy + cr * sy, -sr * sp * sy + -cr * cy, -sr * cp ); + } + + if ( up ) { + up->set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); + } +} + +idVec3_t angles_t::toForward( void ) { + float angle; + static float sp, sy, cp, cy; // static to help MS compiler fp bugs + + angle = yaw * ( M_PI * 2 / 360 ); + sy = sin( angle ); + cy = cos( angle ); + + angle = pitch * ( M_PI * 2 / 360 ); + sp = sin( angle ); + cp = cos( angle ); + + return idVec3_t( cp * cy, cp * sy, -sp ); +} + +/* +================= +Normalize360 + +returns angles normalized to the range [0 <= angle < 360] +================= +*/ +angles_t& angles_t::Normalize360( void ) { + pitch = (360.0 / 65536) * ( ( int )( pitch * ( 65536 / 360.0 ) ) & 65535 ); + yaw = (360.0 / 65536) * ( ( int )( yaw * ( 65536 / 360.0 ) ) & 65535 ); + roll = (360.0 / 65536) * ( ( int )( roll * ( 65536 / 360.0 ) ) & 65535 ); + + return *this; +} + + +/* +================= +Normalize180 + +returns angles normalized to the range [-180 < angle <= 180] +================= +*/ +angles_t& angles_t::Normalize180( void ) { + Normalize360(); + + if ( pitch > 180.0 ) { + pitch -= 360.0; + } + + if ( yaw > 180.0 ) { + yaw -= 360.0; + } + + if ( roll > 180.0 ) { + roll -= 360.0; + } + return *this; +} diff --git a/codemp/Splines/math_angles.h b/codemp/Splines/math_angles.h new file mode 100644 index 0000000..e25f48a --- /dev/null +++ b/codemp/Splines/math_angles.h @@ -0,0 +1,174 @@ +#ifndef __MATH_ANGLES_H__ +#define __MATH_ANGLES_H__ + +#include +#include + +#include "math_vector.h" + +class mat3_t; +class quat_t; +class idVec3_t; +typedef idVec3_t &vec3_p; + +class angles_t { +public: + float pitch; + float yaw; + float roll; + + angles_t(); + angles_t( float pitch, float yaw, float roll ); + angles_t( const idVec3_t &vec ); + + friend void toAngles( idVec3_t &src, angles_t &dst ); + friend void toAngles( quat_t &src, angles_t &dst ); + friend void toAngles( mat3_t &src, angles_t &dst ); + + operator vec3_p(); + + float operator[]( int index ) const; + float& operator[]( int index ); + + void set( float pitch, float yaw, float roll ); + + void operator=( angles_t const &a ); + void operator=( idVec3_t const &a ); + + friend angles_t operator+( const angles_t &a, const angles_t &b ); + angles_t &operator+=( angles_t const &a ); + angles_t &operator+=( idVec3_t const &a ); + + friend angles_t operator-( angles_t &a, angles_t &b ); + angles_t &operator-=( angles_t &a ); + + friend angles_t operator*( const angles_t &a, float b ); + friend angles_t operator*( float a, const angles_t &b ); + angles_t &operator*=( float a ); + + friend int operator==( angles_t &a, angles_t &b ); + + friend int operator!=( angles_t &a, angles_t &b ); + + void toVectors( idVec3_t *forward, idVec3_t *right = NULL, idVec3_t *up = NULL ); + idVec3_t toForward( void ); + + angles_t &Zero( void ); + + angles_t &Normalize360( void ); + angles_t &Normalize180( void ); +}; + +extern angles_t ang_zero; + +inline angles_t::angles_t() {} + +inline angles_t::angles_t( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +inline angles_t::angles_t( const idVec3_t &vec ) { + this->pitch = vec.x; + this->yaw = vec.y; + this->roll = vec.z; +} + +inline float angles_t::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +inline float& angles_t::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &pitch )[ index ]; +} + +inline angles_t::operator vec3_p( void ) { + return *( idVec3_t * )&pitch; +} + +inline void angles_t::set( float pitch, float yaw, float roll ) { + this->pitch = pitch; + this->yaw = yaw; + this->roll = roll; +} + +inline void angles_t::operator=( angles_t const &a ) { + pitch = a.pitch; + yaw = a.yaw; + roll = a.roll; +} + +inline void angles_t::operator=( idVec3_t const &a ) { + pitch = a[ 0 ]; + yaw = a[ 1 ]; + roll = a[ 2 ]; +} + +inline angles_t operator+( const angles_t &a, const angles_t &b ) { + return angles_t( a.pitch + b.pitch, a.yaw + b.yaw, a.roll + b.roll ); +} + +inline angles_t& angles_t::operator+=( angles_t const &a ) { + pitch += a.pitch; + yaw += a.yaw; + roll += a.roll; + + return *this; +} + +inline angles_t& angles_t::operator+=( idVec3_t const &a ) { + pitch += a.x; + yaw += a.y; + roll += a.z; + + return *this; +} + +inline angles_t operator-( angles_t &a, angles_t &b ) { + return angles_t( a.pitch - b.pitch, a.yaw - b.yaw, a.roll - b.roll ); +} + +inline angles_t& angles_t::operator-=( angles_t &a ) { + pitch -= a.pitch; + yaw -= a.yaw; + roll -= a.roll; + + return *this; +} + +inline angles_t operator*( const angles_t &a, float b ) { + return angles_t( a.pitch * b, a.yaw * b, a.roll * b ); +} + +inline angles_t operator*( float a, const angles_t &b ) { + return angles_t( a * b.pitch, a * b.yaw, a * b.roll ); +} + +inline angles_t& angles_t::operator*=( float a ) { + pitch *= a; + yaw *= a; + roll *= a; + + return *this; +} + +inline int operator==( angles_t &a, angles_t &b ) { + return ( ( a.pitch == b.pitch ) && ( a.yaw == b.yaw ) && ( a.roll == b.roll ) ); +} + +inline int operator!=( angles_t &a, angles_t &b ) { + return ( ( a.pitch != b.pitch ) || ( a.yaw != b.yaw ) || ( a.roll != b.roll ) ); +} + +inline angles_t& angles_t::Zero( void ) { + pitch = 0.0f; + yaw = 0.0f; + roll = 0.0f; + + return *this; +} + +#endif /* !__MATH_ANGLES_H__ */ diff --git a/codemp/Splines/math_matrix.cpp b/codemp/Splines/math_matrix.cpp new file mode 100644 index 0000000..e8679ff --- /dev/null +++ b/codemp/Splines/math_matrix.cpp @@ -0,0 +1,113 @@ +#include "q_shared.h" + +mat3_t mat3_default( idVec3_t( 1, 0, 0 ), idVec3_t( 0, 1, 0 ), idVec3_t( 0, 0, 1 ) ); + +void toMatrix( quat_t const &src, mat3_t &dst ) { + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + + x2 = src.x + src.x; + y2 = src.y + src.y; + z2 = src.z + src.z; + + xx = src.x * x2; + xy = src.x * y2; + xz = src.x * z2; + + yy = src.y * y2; + yz = src.y * z2; + zz = src.z * z2; + + wx = src.w * x2; + wy = src.w * y2; + wz = src.w * z2; + + dst[ 0 ][ 0 ] = 1.0f - ( yy + zz ); + dst[ 0 ][ 1 ] = xy - wz; + dst[ 0 ][ 2 ] = xz + wy; + + dst[ 1 ][ 0 ] = xy + wz; + dst[ 1 ][ 1 ] = 1.0f - ( xx + zz ); + dst[ 1 ][ 2 ] = yz - wx; + + dst[ 2 ][ 0 ] = xz - wy; + dst[ 2 ][ 1 ] = yz + wx; + dst[ 2 ][ 2 ] = 1.0f - ( xx + yy ); +} + +void toMatrix( angles_t const &src, mat3_t &dst ) { + float angle; + static float sr, sp, sy, cr, cp, cy; // static to help MS compiler fp bugs + + angle = src.yaw * ( M_PI * 2.0f / 360.0f ); + sy = sin( angle ); + cy = cos( angle ); + + angle = src.pitch * ( M_PI * 2.0f / 360.0f ); + sp = sin( angle ); + cp = cos( angle ); + + angle = src.roll * ( M_PI * 2.0f / 360.0f ); + sr = sin( angle ); + cr = cos( angle ); + + dst[ 0 ].set( cp * cy, cp * sy, -sp ); + dst[ 1 ].set( sr * sp * cy + cr * -sy, sr * sp * sy + cr * cy, sr * cp ); + dst[ 2 ].set( cr * sp * cy + -sr * -sy, cr * sp * sy + -sr * cy, cr * cp ); +} + +void toMatrix( idVec3_t const &src, mat3_t &dst ) { + angles_t sup = src; + toMatrix(sup, dst); +} + +void mat3_t::ProjectVector( const idVec3_t &src, idVec3_t &dst ) const { + dst.x = src * mat[ 0 ]; + dst.y = src * mat[ 1 ]; + dst.z = src * mat[ 2 ]; +} + +void mat3_t::UnprojectVector( const idVec3_t &src, idVec3_t &dst ) const { + dst = mat[ 0 ] * src.x + mat[ 1 ] * src.y + mat[ 2 ] * src.z; +} + +void mat3_t::Transpose( mat3_t &matrix ) { + int i; + int j; + + for( i = 0; i < 3; i++ ) { + for( j = 0; j < 3; j++ ) { + matrix[ i ][ j ] = mat[ j ][ i ]; + } + } +} + +void mat3_t::Transpose( void ) { + float temp; + int i; + int j; + + for( i = 0; i < 3; i++ ) { + for( j = i + 1; j < 3; j++ ) { + temp = mat[ i ][ j ]; + mat[ i ][ j ] = mat[ j ][ i ]; + mat[ j ][ i ] = temp; + } + } +} + +mat3_t mat3_t::Inverse( void ) const { + mat3_t inv( *this ); + + inv.Transpose(); + + return inv; +} + +void mat3_t::Clear( void ) { + mat[0].set( 1, 0, 0 ); + mat[1].set( 0, 1, 0 ); + mat[2].set( 0, 0, 1 ); +} diff --git a/codemp/Splines/math_matrix.h b/codemp/Splines/math_matrix.h new file mode 100644 index 0000000..cb3368b --- /dev/null +++ b/codemp/Splines/math_matrix.h @@ -0,0 +1,202 @@ +#ifndef __MATH_MATRIX_H__ +#define __MATH_MATRIX_H__ + +#include +#include "math_vector.h" + +#ifndef ID_INLINE +#ifdef _WIN32 +#define ID_INLINE __inline +#else +#define ID_INLINE inline +#endif +#endif + +class quat_t; +class angles_t; + +class mat3_t { +public: + idVec3_t mat[ 3 ]; + + mat3_t(); + mat3_t( float src[ 3 ][ 3 ] ); + mat3_t( idVec3_t const &x, idVec3_t const &y, idVec3_t const &z ); + mat3_t( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ); + + friend void toMatrix( quat_t const &src, mat3_t &dst ); + friend void toMatrix( angles_t const &src, mat3_t &dst ); + friend void toMatrix( idVec3_t const &src, mat3_t &dst ); + + idVec3_t operator[]( int index ) const; + idVec3_t &operator[]( int index ); + + idVec3_t operator*( const idVec3_t &vec ) const; + mat3_t operator*( const mat3_t &a ) const; + mat3_t operator*( float a ) const; + mat3_t operator+( mat3_t const &a ) const; + mat3_t operator-( mat3_t const &a ) const; + + friend idVec3_t operator*( const idVec3_t &vec, const mat3_t &mat ); + friend mat3_t operator*( float a, mat3_t const &b ); + + mat3_t &operator*=( float a ); + mat3_t &operator+=( mat3_t const &a ); + mat3_t &operator-=( mat3_t const &a ); + + void Clear( void ); + + void ProjectVector( const idVec3_t &src, idVec3_t &dst ) const; + void UnprojectVector( const idVec3_t &src, idVec3_t &dst ) const; + + void OrthoNormalize( void ); + void Transpose( mat3_t &matrix ); + void Transpose( void ); + mat3_t Inverse( void ) const; + void Identity( void ); + + friend void InverseMultiply( const mat3_t &inv, const mat3_t &b, mat3_t &dst ); + friend mat3_t SkewSymmetric( idVec3_t const &src ); +}; + +ID_INLINE mat3_t::mat3_t() { +} + +ID_INLINE mat3_t::mat3_t( float src[ 3 ][ 3 ] ) { + memcpy( mat, src, sizeof( src ) ); +} + +ID_INLINE mat3_t::mat3_t( idVec3_t const &x, idVec3_t const &y, idVec3_t const &z ) { + mat[ 0 ].x = x.x; mat[ 0 ].y = x.y; mat[ 0 ].z = x.z; + mat[ 1 ].x = y.x; mat[ 1 ].y = y.y; mat[ 1 ].z = y.z; + mat[ 2 ].x = z.x; mat[ 2 ].y = z.y; mat[ 2 ].z = z.z; +} + +ID_INLINE mat3_t::mat3_t( const float xx, const float xy, const float xz, const float yx, const float yy, const float yz, const float zx, const float zy, const float zz ) { + mat[ 0 ].x = xx; mat[ 0 ].y = xy; mat[ 0 ].z = xz; + mat[ 1 ].x = yx; mat[ 1 ].y = yy; mat[ 1 ].z = yz; + mat[ 2 ].x = zx; mat[ 2 ].y = zy; mat[ 2 ].z = zz; +} + +ID_INLINE idVec3_t mat3_t::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idVec3_t& mat3_t::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 3 ) ); + return mat[ index ]; +} + +ID_INLINE idVec3_t mat3_t::operator*( const idVec3_t &vec ) const { + return idVec3_t( + mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z, + mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z, + mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z ); +} + +ID_INLINE mat3_t mat3_t::operator*( const mat3_t &a ) const { + return mat3_t( + mat[0].x * a[0].x + mat[0].y * a[1].x + mat[0].z * a[2].x, + mat[0].x * a[0].y + mat[0].y * a[1].y + mat[0].z * a[2].y, + mat[0].x * a[0].z + mat[0].y * a[1].z + mat[0].z * a[2].z, + mat[1].x * a[0].x + mat[1].y * a[1].x + mat[1].z * a[2].x, + mat[1].x * a[0].y + mat[1].y * a[1].y + mat[1].z * a[2].y, + mat[1].x * a[0].z + mat[1].y * a[1].z + mat[1].z * a[2].z, + mat[2].x * a[0].x + mat[2].y * a[1].x + mat[2].z * a[2].x, + mat[2].x * a[0].y + mat[2].y * a[1].y + mat[2].z * a[2].y, + mat[2].x * a[0].z + mat[2].y * a[1].z + mat[2].z * a[2].z ); +} + +ID_INLINE mat3_t mat3_t::operator*( float a ) const { + return mat3_t( + mat[0].x * a, mat[0].y * a, mat[0].z * a, + mat[1].x * a, mat[1].y * a, mat[1].z * a, + mat[2].x * a, mat[2].y * a, mat[2].z * a ); +} + +ID_INLINE mat3_t mat3_t::operator+( mat3_t const &a ) const { + return mat3_t( + mat[0].x + a[0].x, mat[0].y + a[0].y, mat[0].z + a[0].z, + mat[1].x + a[1].x, mat[1].y + a[1].y, mat[1].z + a[1].z, + mat[2].x + a[2].x, mat[2].y + a[2].y, mat[2].z + a[2].z ); +} + +ID_INLINE mat3_t mat3_t::operator-( mat3_t const &a ) const { + return mat3_t( + mat[0].x - a[0].x, mat[0].y - a[0].y, mat[0].z - a[0].z, + mat[1].x - a[1].x, mat[1].y - a[1].y, mat[1].z - a[1].z, + mat[2].x - a[2].x, mat[2].y - a[2].y, mat[2].z - a[2].z ); +} + +ID_INLINE idVec3_t operator*( const idVec3_t &vec, const mat3_t &mat ) { + return idVec3_t( + mat[ 0 ].x * vec.x + mat[ 1 ].x * vec.y + mat[ 2 ].x * vec.z, + mat[ 0 ].y * vec.x + mat[ 1 ].y * vec.y + mat[ 2 ].y * vec.z, + mat[ 0 ].z * vec.x + mat[ 1 ].z * vec.y + mat[ 2 ].z * vec.z ); +} + +ID_INLINE mat3_t operator*( float a, mat3_t const &b ) { + return mat3_t( + b[0].x * a, b[0].y * a, b[0].z * a, + b[1].x * a, b[1].y * a, b[1].z * a, + b[2].x * a, b[2].y * a, b[2].z * a ); +} + +ID_INLINE mat3_t &mat3_t::operator*=( float a ) { + mat[0].x *= a; mat[0].y *= a; mat[0].z *= a; + mat[1].x *= a; mat[1].y *= a; mat[1].z *= a; + mat[2].x *= a; mat[2].y *= a; mat[2].z *= a; + + return *this; +} + +ID_INLINE mat3_t &mat3_t::operator+=( mat3_t const &a ) { + mat[0].x += a[0].x; mat[0].y += a[0].y; mat[0].z += a[0].z; + mat[1].x += a[1].x; mat[1].y += a[1].y; mat[1].z += a[1].z; + mat[2].x += a[2].x; mat[2].y += a[2].y; mat[2].z += a[2].z; + + return *this; +} + +ID_INLINE mat3_t &mat3_t::operator-=( mat3_t const &a ) { + mat[0].x -= a[0].x; mat[0].y -= a[0].y; mat[0].z -= a[0].z; + mat[1].x -= a[1].x; mat[1].y -= a[1].y; mat[1].z -= a[1].z; + mat[2].x -= a[2].x; mat[2].y -= a[2].y; mat[2].z -= a[2].z; + + return *this; +} + +ID_INLINE void mat3_t::OrthoNormalize( void ) { + mat[ 0 ].Normalize(); + mat[ 2 ].Cross( mat[ 0 ], mat[ 1 ] ); + mat[ 2 ].Normalize(); + mat[ 1 ].Cross( mat[ 2 ], mat[ 0 ] ); + mat[ 1 ].Normalize(); +} + +ID_INLINE void mat3_t::Identity( void ) { + mat[ 0 ].x = 1.f; mat[ 0 ].y = 0.f; mat[ 0 ].z = 0.f; + mat[ 1 ].x = 0.f; mat[ 1 ].y = 1.f; mat[ 1 ].z = 0.f; + mat[ 2 ].x = 0.f; mat[ 2 ].y = 0.f; mat[ 2 ].z = 1.f; +} + +ID_INLINE void InverseMultiply( const mat3_t &inv, const mat3_t &b, mat3_t &dst ) { + dst[0].x = inv[0].x * b[0].x + inv[1].x * b[1].x + inv[2].x * b[2].x; + dst[0].y = inv[0].x * b[0].y + inv[1].x * b[1].y + inv[2].x * b[2].y; + dst[0].z = inv[0].x * b[0].z + inv[1].x * b[1].z + inv[2].x * b[2].z; + dst[1].x = inv[0].y * b[0].x + inv[1].y * b[1].x + inv[2].y * b[2].x; + dst[1].y = inv[0].y * b[0].y + inv[1].y * b[1].y + inv[2].y * b[2].y; + dst[1].z = inv[0].y * b[0].z + inv[1].y * b[1].z + inv[2].y * b[2].z; + dst[2].x = inv[0].z * b[0].x + inv[1].z * b[1].x + inv[2].z * b[2].x; + dst[2].y = inv[0].z * b[0].y + inv[1].z * b[1].y + inv[2].z * b[2].y; + dst[2].z = inv[0].z * b[0].z + inv[1].z * b[1].z + inv[2].z * b[2].z; +} + +ID_INLINE mat3_t SkewSymmetric( idVec3_t const &src ) { + return mat3_t( 0.0f, -src.z, src.y, src.z, 0.0f, -src.x, -src.y, src.x, 0.0f ); +} + +extern mat3_t mat3_default; + +#endif /* !__MATH_MATRIX_H__ */ diff --git a/codemp/Splines/math_quaternion.cpp b/codemp/Splines/math_quaternion.cpp new file mode 100644 index 0000000..5379bf7 --- /dev/null +++ b/codemp/Splines/math_quaternion.cpp @@ -0,0 +1,57 @@ +#include "math_quaternion.h" +#include "math_matrix.h" + +void toQuat( idVec3_t &src, quat_t &dst ) { + dst.x = src.x; + dst.y = src.y; + dst.z = src.z; + dst.w = 0.0f; +} + +void toQuat( angles_t &src, quat_t &dst ) { + mat3_t temp; + + toMatrix( src, temp ); + toQuat( temp, dst ); +} + +void toQuat( mat3_t &src, quat_t &dst ) { + float trace; + float s; + int i; + int j; + int k; + + static int next[ 3 ] = { 1, 2, 0 }; + + trace = src[ 0 ][ 0 ] + src[ 1 ][ 1 ] + src[ 2 ][ 2 ]; + if ( trace > 0.0f ) { + s = ( float )sqrt( trace + 1.0f ); + dst.w = s * 0.5f; + s = 0.5f / s; + + dst.x = ( src[ 2 ][ 1 ] - src[ 1 ][ 2 ] ) * s; + dst.y = ( src[ 0 ][ 2 ] - src[ 2 ][ 0 ] ) * s; + dst.z = ( src[ 1 ][ 0 ] - src[ 0 ][ 1 ] ) * s; + } else { + i = 0; + if ( src[ 1 ][ 1 ] > src[ 0 ][ 0 ] ) { + i = 1; + } + if ( src[ 2 ][ 2 ] > src[ i ][ i ] ) { + i = 2; + } + + j = next[ i ]; + k = next[ j ]; + + s = ( float )sqrt( ( src[ i ][ i ] - ( src[ j ][ j ] + src[ k ][ k ] ) ) + 1.0f ); + dst[ i ] = s * 0.5f; + + s = 0.5f / s; + + dst.w = ( src[ k ][ j ] - src[ j ][ k ] ) * s; + dst[ j ] = ( src[ j ][ i ] + src[ i ][ j ] ) * s; + dst[ k ] = ( src[ k ][ i ] + src[ i ][ k ] ) * s; + } +} diff --git a/codemp/Splines/math_quaternion.h b/codemp/Splines/math_quaternion.h new file mode 100644 index 0000000..b20d368 --- /dev/null +++ b/codemp/Splines/math_quaternion.h @@ -0,0 +1,169 @@ +#ifndef __MATH_QUATERNION_H__ +#define __MATH_QUATERNION_H__ + +#include +#include + +class idVec3_t; +class angles_t; +class mat3_t; + +class quat_t { +public: + float x; + float y; + float z; + float w; + + quat_t(); + quat_t( float x, float y, float z, float w ); + + friend void toQuat( idVec3_t &src, quat_t &dst ); + friend void toQuat( angles_t &src, quat_t &dst ); + friend void toQuat( mat3_t &src, quat_t &dst ); + + float *vec4( void ); + + float operator[]( int index ) const; + float &operator[]( int index ); + + void set( float x, float y, float z, float w ); + + void operator=( quat_t a ); + + friend quat_t operator+( quat_t a, quat_t b ); + quat_t &operator+=( quat_t a ); + + friend quat_t operator-( quat_t a, quat_t b ); + quat_t &operator-=( quat_t a ); + + friend quat_t operator*( quat_t a, float b ); + friend quat_t operator*( float a, quat_t b ); + quat_t &operator*=( float a ); + + friend int operator==( quat_t a, quat_t b ); + friend int operator!=( quat_t a, quat_t b ); + + float Length( void ); + quat_t &Normalize( void ); + + quat_t operator-(); +}; + +inline quat_t::quat_t() { +} + +inline quat_t::quat_t( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +inline float *quat_t::vec4( void ) { + return &x; +} + +inline float quat_t::operator[]( int index ) const { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +inline float& quat_t::operator[]( int index ) { + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +inline void quat_t::set( float x, float y, float z, float w ) { + this->x = x; + this->y = y; + this->z = z; + this->w = w; +} + +inline void quat_t::operator=( quat_t a ) { + x = a.x; + y = a.y; + z = a.z; + w = a.w; +} + +inline quat_t operator+( quat_t a, quat_t b ) { + return quat_t( a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w ); +} + +inline quat_t& quat_t::operator+=( quat_t a ) { + x += a.x; + y += a.y; + z += a.z; + w += a.w; + + return *this; +} + +inline quat_t operator-( quat_t a, quat_t b ) { + return quat_t( a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w ); +} + +inline quat_t& quat_t::operator-=( quat_t a ) { + x -= a.x; + y -= a.y; + z -= a.z; + w -= a.w; + + return *this; +} + +inline quat_t operator*( quat_t a, float b ) { + return quat_t( a.x * b, a.y * b, a.z * b, a.w * b ); +} + +inline quat_t operator*( float a, quat_t b ) { + return b * a; +} + +inline quat_t& quat_t::operator*=( float a ) { + x *= a; + y *= a; + z *= a; + w *= a; + + return *this; +} + +inline int operator==( quat_t a, quat_t b ) { + return ( ( a.x == b.x ) && ( a.y == b.y ) && ( a.z == b.z ) && ( a.w == b.w ) ); +} + +inline int operator!=( quat_t a, quat_t b ) { + return ( ( a.x != b.x ) || ( a.y != b.y ) || ( a.z != b.z ) && ( a.w != b.w ) ); +} + +inline float quat_t::Length( void ) { + float length; + + length = x * x + y * y + z * z + w * w; + return ( float )sqrt( length ); +} + +inline quat_t& quat_t::Normalize( void ) { + float length; + float ilength; + + length = this->Length(); + if ( length ) { + ilength = 1 / length; + x *= ilength; + y *= ilength; + z *= ilength; + w *= ilength; + } + + return *this; +} + +inline quat_t quat_t::operator-() { + return quat_t( -x, -y, -z, -w ); +} + +#endif /* !__MATH_QUATERNION_H__ */ diff --git a/codemp/Splines/math_vector.cpp b/codemp/Splines/math_vector.cpp new file mode 100644 index 0000000..73e1e0b --- /dev/null +++ b/codemp/Splines/math_vector.cpp @@ -0,0 +1,123 @@ +//#include "../game/q_shared.h" +#include "math_vector.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h + +#define LERP_DELTA 1e-6 + +idVec3_t vec_zero( 0.0f, 0.0f, 0.0f ); + +Bounds boundsZero; + +float idVec3_t::toYaw( void ) { + float yaw; + + if ( ( y == 0 ) && ( x == 0 ) ) { + yaw = 0; + } else { + yaw = atan2( y, x ) * 180 / M_PI; + if ( yaw < 0 ) { + yaw += 360; + } + } + + return yaw; +} + +float idVec3_t::toPitch( void ) { + float forward; + float pitch; + + if ( ( x == 0 ) && ( y == 0 ) ) { + if ( z > 0 ) { + pitch = 90; + } else { + pitch = 270; + } + } else { + forward = ( float )idSqrt( x * x + y * y ); + pitch = atan2( z, forward ) * 180 / M_PI; + if ( pitch < 0 ) { + pitch += 360; + } + } + + return pitch; +} + +/* +angles_t idVec3_t::toAngles( void ) { + float forward; + float yaw; + float pitch; + + if ( ( x == 0 ) && ( y == 0 ) ) { + yaw = 0; + if ( z > 0 ) { + pitch = 90; + } else { + pitch = 270; + } + } else { + yaw = atan2( y, x ) * 180 / M_PI; + if ( yaw < 0 ) { + yaw += 360; + } + + forward = ( float )idSqrt( x * x + y * y ); + pitch = atan2( z, forward ) * 180 / M_PI; + if ( pitch < 0 ) { + pitch += 360; + } + } + + return angles_t( -pitch, yaw, 0 ); +} +*/ + +idVec3_t LerpVector( idVec3_t &w1, idVec3_t &w2, const float t ) { + float omega, cosom, sinom, scale0, scale1; + + cosom = w1 * w2; + if ( ( 1.0 - cosom ) > LERP_DELTA ) { + omega = acos( cosom ); + sinom = sin( omega ); + scale0 = sin( ( 1.0 - t ) * omega ) / sinom; + scale1 = sin( t * omega ) / sinom; + } else { + scale0 = 1.0 - t; + scale1 = t; + } + + return ( w1 * scale0 + w2 * scale1 ); +} + +/* +============= +idVec3_t::string + +This is just a convenience function +for printing vectors +============= +*/ +char *idVec3_t::string( void ) { + static int index = 0; + static char str[ 8 ][ 36 ]; + char *s; + + // use an array so that multiple toString's won't collide + s = str[ index ]; + index = (index + 1)&7; + + sprintf( s, "%.2f %.2f %.2f", x, y, z ); + + return s; +} diff --git a/codemp/Splines/math_vector.h b/codemp/Splines/math_vector.h new file mode 100644 index 0000000..d0ee5a0 --- /dev/null +++ b/codemp/Splines/math_vector.h @@ -0,0 +1,553 @@ +#ifndef __MATH_VECTOR_H__ +#define __MATH_VECTOR_H__ + +#if defined(_WIN32) +#pragma warning(disable : 4244) +#endif + +#include +#include + +//#define DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[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 VectorCopy(a,b) ((b).x=(a).x,(b).y=(a).y,(b).z=(a).z]) + +//#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define __VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) +//#define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) + +#define DotProduct4(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]+(x)[3]*(y)[3]) +#define VectorSubtract4(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2],(c)[3]=(a)[3]-(b)[3]) +#define VectorAdd4(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3]) +#define VectorCopy4(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) +#define VectorScale4(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s),(o)[3]=(v)[3]*(s)) +#define VectorMA4(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s),(o)[3]=(v)[3]+(b)[3]*(s)) + + +//#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)) +#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) + +#define SnapVector(v) {v[0]=(int)v[0];v[1]=(int)v[1];v[2]=(int)v[2];} + + +//#include "util_heap.h" + +#ifndef EQUAL_EPSILON +#define EQUAL_EPSILON 0.001 +#endif + +float Q_fabs( float f ); + +#ifndef ID_INLINE +#ifdef _WIN32 +#define ID_INLINE __inline +#else +#define ID_INLINE inline +#endif +#endif + +// if this is defined, vec3 will take four elements, which may allow +// easier SIMD optimizations +//#define FAT_VEC3 +//#ifdef __ppc__ +//#pragma align(16) +//#endif + +class angles_t; +#ifdef __ppc__ +// Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction, +// runs *much* faster than calling sqrt(). We'll use two Newton-Raphson +// refinement steps to get bunch more precision in the 1/sqrt() value for very little cost. +// We'll then multiply 1/sqrt times the original value to get the sqrt. +// This is about 12.4 times faster than sqrt() and according to my testing (not exhaustive) +// it returns fairly accurate results (error below 1.0e-5 up to 100000.0 in 0.1 increments). + +static inline float idSqrt(float x) { + const float half = 0.5; + const float one = 1.0; + float B, y0, y1; + + // This'll NaN if it hits frsqrte. Handle both +0.0 and -0.0 + if (fabs(x) == 0.0) + return x; + B = x; + +#ifdef __GNUC__ + asm("frsqrte %0,%1" : "=f" (y0) : "f" (B)); +#else + y0 = __frsqrte(B); +#endif + /* First refinement step */ + + y1 = y0 + half*y0*(one - B*y0*y0); + + /* Second refinement step -- copy the output of the last step to the input of this step */ + + y0 = y1; + y1 = y0 + half*y0*(one - B*y0*y0); + + /* Get sqrt(x) from x * 1/sqrt(x) */ + return x * y1; +} +#else +static inline double idSqrt(double x) { + return sqrt(x); +} +#endif + + +//class idVec3_t : public idHeap { +class idVec3_t { +public: +#ifndef FAT_VEC3 + float x,y,z; +#else + float x,y,z,dist; +#endif + +#ifndef FAT_VEC3 + idVec3_t() {}; +#else + idVec3_t() {dist = 0.0f;}; +#endif + idVec3_t( const float x, const float y, const float z ); + + operator float *(); + + float operator[]( const int index ) const; + float &operator[]( const int index ); + + void set( const float x, const float y, const float z ); + + idVec3_t operator-() const; + + idVec3_t &operator=( const idVec3_t &a ); + + float operator*( const idVec3_t &a ) const; + idVec3_t operator*( const float a ) const; + friend idVec3_t operator*( float a, idVec3_t b ); + + idVec3_t operator+( const idVec3_t &a ) const; + idVec3_t operator-( const idVec3_t &a ) const; + + idVec3_t &operator+=( const idVec3_t &a ); + idVec3_t &operator-=( const idVec3_t &a ); + idVec3_t &operator*=( const float a ); + + int operator==( const idVec3_t &a ) const; + int operator!=( const idVec3_t &a ) const; + + idVec3_t Cross( const idVec3_t &a ) const; + idVec3_t &Cross( const idVec3_t &a, const idVec3_t &b ); + + float Length( void ) const; + float Normalize( void ); + + void Zero( void ); + void Snap( void ); + void SnapTowards( const idVec3_t &to ); + + float toYaw( void ); + float toPitch( void ); + angles_t toAngles( void ); + friend idVec3_t LerpVector( const idVec3_t &w1, const idVec3_t &w2, const float t ); + + char *string( void ); +}; + +extern idVec3_t vec_zero; + +ID_INLINE idVec3_t::idVec3_t( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +#ifdef FAT_VEC3 + this->dist = 0.0f; +#endif +} + +ID_INLINE float idVec3_t::operator[]( const int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float &idVec3_t::operator[]( const int index ) { + return ( &x )[ index ]; +} + +ID_INLINE idVec3_t::operator float *( void ) { + return &x; +} + +ID_INLINE idVec3_t idVec3_t::operator-() const { + return idVec3_t( -x, -y, -z ); +} + +ID_INLINE idVec3_t &idVec3_t::operator=( const idVec3_t &a ) { + x = a.x; + y = a.y; + z = a.z; + + return *this; +} + +ID_INLINE void idVec3_t::set( const float x, const float y, const float z ) { + this->x = x; + this->y = y; + this->z = z; +} + +ID_INLINE idVec3_t idVec3_t::operator-( const idVec3_t &a ) const { + return idVec3_t( x - a.x, y - a.y, z - a.z ); +} + +ID_INLINE float idVec3_t::operator*( const idVec3_t &a ) const { + return x * a.x + y * a.y + z * a.z; +} + +ID_INLINE idVec3_t idVec3_t::operator*( const float a ) const { + return idVec3_t( x * a, y * a, z * a ); +} + +ID_INLINE idVec3_t operator*( const float a, const idVec3_t b ) { + return idVec3_t( b.x * a, b.y * a, b.z * a ); +} + +ID_INLINE idVec3_t idVec3_t::operator+( const idVec3_t &a ) const { + return idVec3_t( x + a.x, y + a.y, z + a.z ); +} + +ID_INLINE idVec3_t &idVec3_t::operator+=( const idVec3_t &a ) { + x += a.x; + y += a.y; + z += a.z; + + return *this; +} + +ID_INLINE idVec3_t &idVec3_t::operator-=( const idVec3_t &a ) { + x -= a.x; + y -= a.y; + z -= a.z; + + return *this; +} + +ID_INLINE idVec3_t &idVec3_t::operator*=( const float a ) { + x *= a; + y *= a; + z *= a; + + return *this; +} + +ID_INLINE int idVec3_t::operator==( const idVec3_t &a ) const { + if ( Q_fabs( x - a.x ) > EQUAL_EPSILON ) { + return false; + } + + if ( Q_fabs( y - a.y ) > EQUAL_EPSILON ) { + return false; + } + + if ( Q_fabs( z - a.z ) > EQUAL_EPSILON ) { + return false; + } + + return true; +} + +ID_INLINE int idVec3_t::operator!=( const idVec3_t &a ) const { + if ( Q_fabs( x - a.x ) > EQUAL_EPSILON ) { + return true; + } + + if ( Q_fabs( y - a.y ) > EQUAL_EPSILON ) { + return true; + } + + if ( Q_fabs( z - a.z ) > EQUAL_EPSILON ) { + return true; + } + + return false; +} + +ID_INLINE idVec3_t idVec3_t::Cross( const idVec3_t &a ) const { + return idVec3_t( y * a.z - z * a.y, z * a.x - x * a.z, x * a.y - y * a.x ); +} + +ID_INLINE idVec3_t &idVec3_t::Cross( const idVec3_t &a, const idVec3_t &b ) { + x = a.y * b.z - a.z * b.y; + y = a.z * b.x - a.x * b.z; + z = a.x * b.y - a.y * b.x; + + return *this; +} + +ID_INLINE float idVec3_t::Length( void ) const { + float length; + + length = x * x + y * y + z * z; + return ( float )idSqrt( length ); +} + +ID_INLINE float idVec3_t::Normalize( void ) { + float length; + float ilength; + + length = this->Length(); + if ( length ) { + ilength = 1.0f / length; + x *= ilength; + y *= ilength; + z *= ilength; + } + + return length; +} + +ID_INLINE void idVec3_t::Zero( void ) { + x = 0.0f; + y = 0.0f; + z = 0.0f; +} + +ID_INLINE void idVec3_t::Snap( void ) { + x = float( int( x ) ); + y = float( int( y ) ); + z = float( int( z ) ); +} + +/* +====================== +SnapTowards + +Round a vector to integers for more efficient network +transmission, but make sure that it rounds towards a given point +rather than blindly truncating. This prevents it from truncating +into a wall. +====================== +*/ +ID_INLINE void idVec3_t::SnapTowards( const idVec3_t &to ) { + if ( to.x <= x ) { + x = float( int( x ) ); + } else { + x = float( int( x ) + 1 ); + } + + if ( to.y <= y ) { + y = float( int( y ) ); + } else { + y = float( int( y ) + 1 ); + } + + if ( to.z <= z ) { + z = float( int( z ) ); + } else { + z = float( int( z ) + 1 ); + } +} + +//=============================================================== + +class Bounds { +public: + idVec3_t b[2]; + + Bounds(); + Bounds( const idVec3_t &mins, const idVec3_t &maxs ); + + void Clear(); + void Zero(); + float Radius(); // radius from origin, not from center + idVec3_t Center(); + void AddPoint( const idVec3_t &v ); + void AddBounds( const Bounds &bb ); + bool IsCleared(); + bool ContainsPoint( const idVec3_t &p ); + bool IntersectsBounds( const Bounds &b2 ); // touching is NOT intersecting +}; + +extern Bounds boundsZero; + +ID_INLINE Bounds::Bounds(){ +} + +ID_INLINE bool Bounds::IsCleared() { + return b[0][0] > b[1][0]; +} + +ID_INLINE bool Bounds::ContainsPoint( const idVec3_t &p ) { + if ( p[0] < b[0][0] || p[1] < b[0][1] || p[2] < b[0][2] + || p[0] > b[1][0] || p[1] > b[1][1] || p[2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE bool Bounds::IntersectsBounds( const Bounds &b2 ) { + if ( b2.b[1][0] < b[0][0] || b2.b[1][1] < b[0][1] || b2.b[1][2] < b[0][2] + || b2.b[0][0] > b[1][0] || b2.b[0][1] > b[1][1] || b2.b[0][2] > b[1][2] ) { + return false; + } + return true; +} + +ID_INLINE Bounds::Bounds( const idVec3_t &mins, const idVec3_t &maxs ) { + b[0] = mins; + b[1] = maxs; +} + +ID_INLINE idVec3_t Bounds::Center() { + return idVec3_t( ( b[1][0] + b[0][0] ) * 0.5f, ( b[1][1] + b[0][1] ) * 0.5f, ( b[1][2] + b[0][2] ) * 0.5f ); +} + +ID_INLINE void Bounds::Clear() { + b[0][0] = b[0][1] = b[0][2] = 99999; + b[1][0] = b[1][1] = b[1][2] = -99999; +} + +ID_INLINE void Bounds::Zero() { + b[0][0] = b[0][1] = b[0][2] = + b[1][0] = b[1][1] = b[1][2] = 0; +} + +ID_INLINE void Bounds::AddPoint( const idVec3_t &v ) { + if ( v[0] < b[0][0]) { + b[0][0] = v[0]; + } + if ( v[0] > b[1][0]) { + b[1][0] = v[0]; + } + if ( v[1] < b[0][1] ) { + b[0][1] = v[1]; + } + if ( v[1] > b[1][1]) { + b[1][1] = v[1]; + } + if ( v[2] < b[0][2] ) { + b[0][2] = v[2]; + } + if ( v[2] > b[1][2]) { + b[1][2] = v[2]; + } +} + + +ID_INLINE void Bounds::AddBounds( const Bounds &bb ) { + if ( bb.b[0][0] < b[0][0]) { + b[0][0] = bb.b[0][0]; + } + if ( bb.b[0][1] < b[0][1]) { + b[0][1] = bb.b[0][1]; + } + if ( bb.b[0][2] < b[0][2]) { + b[0][2] = bb.b[0][2]; + } + + if ( bb.b[1][0] > b[1][0]) { + b[1][0] = bb.b[1][0]; + } + if ( bb.b[1][1] > b[1][1]) { + b[1][1] = bb.b[1][1]; + } + if ( bb.b[1][2] > b[1][2]) { + b[1][2] = bb.b[1][2]; + } +} + +ID_INLINE float Bounds::Radius( ) { + int i; + float total; + float a, aa; + + total = 0; + for (i=0 ; i<3 ; i++) { + a = (float)fabs( b[0][i] ); + aa = (float)fabs( b[1][i] ); + if ( aa > a ) { + a = aa; + } + total += a * a; + } + + return (float)idSqrt( total ); +} + +//=============================================================== + + +class idVec2_t { +public: + float x; + float y; + + operator float *(); + float operator[]( int index ) const; + float &operator[]( int index ); +}; + +ID_INLINE float idVec2_t::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec2_t::operator[]( int index ) { + return ( &x )[ index ]; +} + +ID_INLINE idVec2_t::operator float *( void ) { + return &x; +} + +class vec4_t : public idVec3_t { +public: +#ifndef FAT_VEC3 + float dist; +#endif + vec4_t(); + ~vec4_t() {}; + + vec4_t( float x, float y, float z, float dist ); + float operator[]( int index ) const; + float &operator[]( int index ); +}; + +ID_INLINE vec4_t::vec4_t() {} +ID_INLINE vec4_t::vec4_t( float x, float y, float z, float dist ) { + this->x = x; + this->y = y; + this->z = z; + this->dist = dist; +} + +ID_INLINE float vec4_t::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& vec4_t::operator[]( int index ) { + return ( &x )[ index ]; +} + + +class idVec5_t : public idVec3_t { +public: + float s; + float t; + float operator[]( int index ) const; + float &operator[]( int index ); +}; + + +ID_INLINE float idVec5_t::operator[]( int index ) const { + return ( &x )[ index ]; +} + +ID_INLINE float& idVec5_t::operator[]( int index ) { + return ( &x )[ index ]; +} + +#endif /* !__MATH_VECTOR_H__ */ diff --git a/codemp/Splines/q_parse.cpp b/codemp/Splines/q_parse.cpp new file mode 100644 index 0000000..7311b8c --- /dev/null +++ b/codemp/Splines/q_parse.cpp @@ -0,0 +1,514 @@ +// q_parse.c -- support for parsing text files + +#include "q_shared.h" + +/* +============================================================================ + +PARSING + +============================================================================ +*/ + +// multiple character punctuation tokens +static const char *punctuation[] = { + "+=", "-=", "*=", "/=", "&=", "|=", "++", "--", + "&&", "||", "<=", ">=", "==", "!=", + NULL +}; + +typedef struct { + char token[MAX_TOKEN_CHARS]; + int lines; + qboolean ungetToken; + char parseFile[MAX_QPATH]; +} parseInfo_t; + +#define MAX_PARSE_INFO 16 +static parseInfo_t parseInfo[MAX_PARSE_INFO]; +static int parseInfoNum; +static parseInfo_t *pi = &parseInfo[0]; + +/* +=================== +Com_BeginParseSession +=================== +*/ +void Com_BeginParseSession( const char *filename ) { + if ( parseInfoNum == MAX_PARSE_INFO - 1 ) { + Com_Error( ERR_FATAL, "Com_BeginParseSession: session overflow" ); + } + parseInfoNum++; + pi = &parseInfo[parseInfoNum]; + + pi->lines = 1; + Q_strncpyz( pi->parseFile, filename, sizeof( pi->parseFile ) ); +} + +/* +=================== +Com_EndParseSession +=================== +*/ +void Com_EndParseSession( void ) { + if ( parseInfoNum == 0 ) { + Com_Error( ERR_FATAL, "Com_EndParseSession: session underflow" ); + } + parseInfoNum--; + pi = &parseInfo[parseInfoNum]; +} + +/* +=================== +Com_GetCurrentParseLine +=================== +*/ +int Com_GetCurrentParseLine( void ) { + return pi->lines; +} + +/* +=================== +Com_ScriptError + +Prints the script name and line number in the message +=================== +*/ +void Com_ScriptError( const char *msg, ... ) { + va_list argptr; + char string[32000]; + + va_start( argptr, msg ); + vsprintf( string, msg,argptr ); + va_end( argptr ); + + Com_Error( ERR_DROP, "File %s, line %i: %s", pi->parseFile, pi->lines, string ); +} + +void Com_ScriptWarning( const char *msg, ... ) { + va_list argptr; + char string[32000]; + + va_start( argptr, msg ); + vsprintf( string, msg,argptr ); + va_end( argptr ); + + Com_Printf( "File %s, line %i: %s", pi->parseFile, pi->lines, string ); +} + + +/* +=================== +Com_UngetToken + +Calling this will make the next Com_Parse return +the current token instead of advancing the pointer +=================== +*/ +void Com_UngetToken( void ) { + if ( pi->ungetToken ) { + Com_ScriptError( "UngetToken called twice" ); + } + pi->ungetToken = qtrue; +} + + +static const char *SkipWhitespace( const char (*data), qboolean *hasNewLines ) { + int c; + + while( (c = *data) <= ' ') { + if( !c ) { + return NULL; + } + if( c == '\n' ) { + pi->lines++; + *hasNewLines = qtrue; + } + data++; + } + + return data; +} + +/* +============== +Com_ParseExt + +Parse a token out of a string +Will never return NULL, just empty strings. +An empty string will only be returned at end of file. + +If "allowLineBreaks" is qtrue then an empty +string will be returned if the next token is +a newline. +============== +*/ +static char *Com_ParseExt( const char *(*data_p), qboolean allowLineBreaks ) { + int c = 0, len; + qboolean hasNewLines = qfalse; + const char *data; + const char **punc; + + if ( !data_p ) { + Com_Error( ERR_FATAL, "Com_ParseExt: NULL data_p" ); + } + + data = *data_p; + len = 0; + pi->token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return pi->token; + } + + // skip any leading whitespace + while ( 1 ) { + // skip whitespace + data = SkipWhitespace( data, &hasNewLines ); + if ( !data ) { + *data_p = NULL; + return pi->token; + } + if ( hasNewLines && !allowLineBreaks ) { + *data_p = data; + return pi->token; + } + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) { + while (*data && *data != '\n') { + data++; + } + continue; + } + + // skip /* */ comments + if ( c=='/' && data[1] == '*' ) { + while ( *data && ( *data != '*' || data[1] != '/' ) ) { + if( *data == '\n' ) { + pi->lines++; + } + data++; + } + if ( *data ) { + data += 2; + } + continue; + } + + // a real token to parse + break; + } + + // handle quoted strings + if ( c == '\"' ) { + data++; + while( 1 ) { + c = *data++; + if ( ( c=='\\' ) && ( *data == '\"' ) ) { + // allow quoted strings to use \" to indicate the " character + data++; + } else if ( c=='\"' || !c ) { + pi->token[len] = 0; + *data_p = ( char * ) data; + return pi->token; + } else if( *data == '\n' ) { + pi->lines++; + } + if ( len < MAX_TOKEN_CHARS - 1 ) { + pi->token[len] = c; + len++; + } + } + } + + // check for a number + // is this parsing of negative numbers going to cause expression problems + if ( ( c >= '0' && c <= '9' ) || ( c == '-' && data[ 1 ] >= '0' && data[ 1 ] <= '9' ) || + ( c == '.' && data[ 1 ] >= '0' && data[ 1 ] <= '9' ) ) { + do { + + if (len < MAX_TOKEN_CHARS - 1) { + pi->token[len] = c; + len++; + } + data++; + + c = *data; + } while ( ( c >= '0' && c <= '9' ) || c == '.' ); + + // parse the exponent + if ( c == 'e' || c == 'E' ) { + if (len < MAX_TOKEN_CHARS - 1) { + pi->token[len] = c; + len++; + } + data++; + c = *data; + + if ( c == '-' || c == '+' ) { + if (len < MAX_TOKEN_CHARS - 1) { + pi->token[len] = c; + len++; + } + data++; + c = *data; + } + + do { + if (len < MAX_TOKEN_CHARS - 1) { + pi->token[len] = c; + len++; + } + data++; + + c = *data; + } while ( c >= '0' && c <= '9' ); + } + + if (len == MAX_TOKEN_CHARS) { + len = 0; + } + pi->token[len] = 0; + + *data_p = ( char * ) data; + return pi->token; + } + + // check for a regular word + // we still allow forward and back slashes in name tokens for pathnames + // and also colons for drive letters + if ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_' || c == '/' || c == '\\' ) { + do { + if (len < MAX_TOKEN_CHARS - 1) { + pi->token[len] = c; + len++; + } + data++; + + c = *data; + } while ( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_' + || ( c >= '0' && c <= '9' ) || c == '/' || c == '\\' || c == ':' || c == '.' ); + + if (len == MAX_TOKEN_CHARS) { + len = 0; + } + pi->token[len] = 0; + + *data_p = ( char * ) data; + return pi->token; + } + + // check for multi-character punctuation token + for ( punc = punctuation ; *punc ; punc++ ) { + int l; + int j; + + l = strlen( *punc ); + for ( j = 0 ; j < l ; j++ ) { + if ( data[j] != (*punc)[j] ) { + break; + } + } + if ( j == l ) { + // a valid multi-character punctuation + memcpy( pi->token, *punc, l ); + pi->token[l] = 0; + data += l; + *data_p = (char *)data; + return pi->token; + } + } + + // single character punctuation + pi->token[0] = *data; + pi->token[1] = 0; + data++; + *data_p = (char *)data; + + return pi->token; +} + +/* +=================== +Com_Parse +=================== +*/ +const char *Com_Parse( const char *(*data_p) ) { + if ( pi->ungetToken ) { + pi->ungetToken = qfalse; + return pi->token; + } + return Com_ParseExt( data_p, qtrue ); +} + +/* +=================== +Com_ParseOnLine +=================== +*/ +const char *Com_ParseOnLine( const char *(*data_p) ) { + if ( pi->ungetToken ) { + pi->ungetToken = qfalse; + return pi->token; + } + return Com_ParseExt( data_p, qfalse ); +} + + + +/* +================== +Com_MatchToken +================== +*/ +void Com_MatchToken( const char *(*buf_p), const char *match, qboolean warning ) { + const char *token; + + token = Com_Parse( buf_p ); + if ( strcmp( token, match ) ) { + if (warning) { + Com_ScriptWarning( "MatchToken: %s != %s", token, match ); + } else { + Com_ScriptError( "MatchToken: %s != %s", token, match ); + } + } +} + + +/* +================= +Com_SkipBracedSection + +The next token should be an open brace. +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +void Com_SkipBracedSection( const char *(*program) ) { + const char *token; + int depth; + + depth = 0; + do { + token = Com_Parse( program ); + if( token[1] == 0 ) { + if( token[0] == '{' ) { + depth++; + } + else if( token[0] == '}' ) { + depth--; + } + } + } while( depth && *program ); +} + +/* +================= +Com_SkipRestOfLine +================= +*/ +void Com_SkipRestOfLine ( const char *(*data) ) { + const char *p; + int c; + + p = *data; + while ( (c = *p++) != 0 ) { + if ( c == '\n' ) { + pi->lines++; + break; + } + } + + *data = p; +} + +/* +==================== +Com_ParseRestOfLine +==================== +*/ +const char *Com_ParseRestOfLine( const char *(*data_p) ) { + static char line[MAX_TOKEN_CHARS]; + const char *token; + + line[0] = 0; + while( 1 ) { + token = Com_ParseOnLine( data_p ); + if ( !token[0] ) { + break; + } + if ( line[0] ) { + Q_strcat( line, sizeof(line), " " ); + } + Q_strcat( line, sizeof(line), token ); + } + + return line; +} + + +float Com_ParseFloat( const char *(*buf_p) ) { + const char *token; + + token = Com_Parse( buf_p ); + if ( !token[0] ) { + return 0; + } + return atof( token ); +} + +int Com_ParseInt( const char *(*buf_p) ) { + const char *token; + + token = Com_Parse( buf_p ); + if ( !token[0] ) { + return 0; + } + return atoi( token ); +} + + + +void Com_Parse1DMatrix( const char *(*buf_p), int x, float *m ) { + const char *token; + int i; + + Com_MatchToken( buf_p, "(" ); + + for (i = 0 ; i < x ; i++) { + token = Com_Parse(buf_p); + m[i] = atof(token); + } + + Com_MatchToken( buf_p, ")" ); +} + +void Com_Parse2DMatrix( const char *(*buf_p), int y, int x, float *m ) { + int i; + + Com_MatchToken( buf_p, "(" ); + + for (i = 0 ; i < y ; i++) { + Com_Parse1DMatrix (buf_p, x, m + i * x); + } + + Com_MatchToken( buf_p, ")" ); +} + +void Com_Parse3DMatrix( const char *(*buf_p), int z, int y, int x, float *m ) { + int i; + + Com_MatchToken( buf_p, "(" ); + + for (i = 0 ; i < z ; i++) { + Com_Parse2DMatrix (buf_p, y, x, m + i * x*y); + } + + Com_MatchToken( buf_p, ")" ); +} + diff --git a/codemp/Splines/q_shared.cpp b/codemp/Splines/q_shared.cpp new file mode 100644 index 0000000..6f953f4 --- /dev/null +++ b/codemp/Splines/q_shared.cpp @@ -0,0 +1,955 @@ +// q_shared.c -- stateless support routines that are included in each code dll +#include "q_shared.h" + +/* +============================================================================ + +GROWLISTS + +============================================================================ +*/ + +// malloc / free all in one place for debugging +extern "C" void *Com_Allocate( int bytes ); +extern "C" void Com_Dealloc( void *ptr ); + +void Com_InitGrowList( growList_t *list, int maxElements ) { + list->maxElements = maxElements; + list->currentElements = 0; + list->elements = (void **)Com_Allocate( list->maxElements * sizeof( void * ) ); +} + +int Com_AddToGrowList( growList_t *list, void *data ) { + void **old; + + if ( list->currentElements != list->maxElements ) { + list->elements[list->currentElements] = data; + return list->currentElements++; + } + + // grow, reallocate and move + old = list->elements; + + if ( list->maxElements < 0 ) { + Com_Error( ERR_FATAL, "Com_AddToGrowList: maxElements = %i", list->maxElements ); + } + + if ( list->maxElements == 0 ) { + // initialize the list to hold 100 elements + Com_InitGrowList( list, 100 ); + return Com_AddToGrowList( list, data ); + } + + list->maxElements *= 2; + + Com_DPrintf( "Resizing growlist to %i maxElements\n", list->maxElements ); + + list->elements = (void **)Com_Allocate( list->maxElements * sizeof( void * ) ); + + if ( !list->elements ) { + Com_Error( ERR_DROP, "Growlist alloc failed" ); + } + + memcpy( list->elements, old, list->currentElements * sizeof( void * ) ); + + Com_Dealloc( old ); + + return Com_AddToGrowList( list, data ); +} + +void *Com_GrowListElement( const growList_t *list, int index ) { + if ( index < 0 || index >= list->currentElements ) { + Com_Error( ERR_DROP, "Com_GrowListElement: %i out of range of %i", + index, list->currentElements ); + } + return list->elements[index]; +} + +int Com_IndexForGrowListElement( const growList_t *list, const void *element ) { + int i; + + for ( i = 0 ; i < list->currentElements ; i++ ) { + if ( list->elements[i] == element ) { + return i; + } + } + return -1; +} + +//============================================================================ + + +float Com_Clamp( float min, float max, float value ) { + if ( value < min ) { + return min; + } + if ( value > max ) { + return max; + } + return value; +} + +/* +============ +Com_StringContains +============ +*/ +const char *Com_StringContains( const char *str1, const char *str2, int casesensitive) { + int len, i, j; + + len = strlen(str1) - strlen(str2); + for (i = 0; i <= len; i++, str1++) { + for (j = 0; str2[j]; j++) { + if (casesensitive) { + if (str1[j] != str2[j]) { + break; + } + } + else { + if (toupper(str1[j]) != toupper(str2[j])) { + break; + } + } + } + if (!str2[j]) { + return str1; + } + } + return NULL; +} + +/* +============ +Com_Filter +============ +*/ +int Com_Filter( const char *filter, const char *name, int casesensitive) +{ + char buf[MAX_TOKEN_CHARS]; + const char *ptr; + int i, found; + + while(*filter) { + if (*filter == '*') { + filter++; + for (i = 0; *filter; i++) { + if (*filter == '*' || *filter == '?') break; + buf[i] = *filter; + filter++; + } + buf[i] = '\0'; + if (strlen(buf)) { + ptr = Com_StringContains(name, buf, casesensitive); + if (!ptr) return qfalse; + name = ptr + strlen(buf); + } + } + else if (*filter == '?') { + filter++; + name++; + } + else if (*filter == '[' && *(filter+1) == '[') { + filter++; + } + else if (*filter == '[') { + filter++; + found = qfalse; + while(*filter && !found) { + if (*filter == ']' && *(filter+1) != ']') break; + if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { + if (casesensitive) { + if (*name >= *filter && *name <= *(filter+2)) found = qtrue; + } + else { + if (toupper(*name) >= toupper(*filter) && + toupper(*name) <= toupper(*(filter+2))) found = qtrue; + } + filter += 3; + } + else { + if (casesensitive) { + if (*filter == *name) found = qtrue; + } + else { + if (toupper(*filter) == toupper(*name)) found = qtrue; + } + filter++; + } + } + if (!found) return qfalse; + while(*filter) { + if (*filter == ']' && *(filter+1) != ']') break; + filter++; + } + filter++; + name++; + } + else { + if (casesensitive) { + if (*filter != *name) return qfalse; + } + else { + if (toupper(*filter) != toupper(*name)) return qfalse; + } + filter++; + name++; + } + } + return qtrue; +} + + +/* +================ +Com_HashString + +================ +*/ +int Com_HashString( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower((unsigned char)fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + + +/* +============ +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( const char *in, char *out ) { + while ( *in && *in != '.' ) { + *out++ = *in++; + } + *out = 0; +} + + +/* +================== +Com_DefaultExtension +================== +*/ +void Com_DefaultExtension (char *path, int maxSize, const char *extension ) { + char oldPath[MAX_QPATH]; + 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--; + } + + Q_strncpyz( oldPath, path, sizeof( oldPath ) ); + Com_sprintf( path, maxSize, "%s%s", oldPath, extension ); +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +static short (*_BigShort) (short l); +static short (*_LittleShort) (short l); +static int (*_BigLong) (int l); +static int (*_LittleLong) (int l); +static float (*_BigFloat) (float l); +static 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) + { + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + +/* +=============== +Com_ParseInfos +=============== +*/ +int Com_ParseInfos( const char *buf, int max, char infos[][MAX_INFO_STRING] ) { + const char *token; + int count; + char key[MAX_TOKEN_CHARS]; + + count = 0; + + while ( 1 ) { + token = Com_Parse( &buf ); + if ( !token[0] ) { + break; + } + if ( strcmp( token, "{" ) ) { + Com_Printf( "Missing { in info file\n" ); + break; + } + + if ( count == max ) { + Com_Printf( "Max infos exceeded\n" ); + break; + } + + infos[count][0] = 0; + while ( 1 ) { + token = Com_Parse( &buf ); + if ( !token[0] ) { + Com_Printf( "Unexpected end of info file\n" ); + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + Q_strncpyz( key, token, sizeof( key ) ); + + token = Com_ParseOnLine( &buf ); + if ( !token[0] ) { + token = ""; + } + Info_SetValueForKey( infos[count], key, token ); + } + count++; + } + + return count; +} + + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +int Q_isprint( int c ) +{ + if ( c >= 0x20 && c <= 0x7E ) + return ( 1 ); + return ( 0 ); +} + +int Q_islower( int c ) +{ + if (c >= 'a' && c <= 'z') + return ( 1 ); + return ( 0 ); +} + +int Q_isupper( int c ) +{ + if (c >= 'A' && c <= 'Z') + return ( 1 ); + return ( 0 ); +} + +int Q_isalpha( int c ) +{ + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) + return ( 1 ); + return ( 0 ); +} + +char* Q_strrchr( const char* string, int c ) +{ + char cc = c; + char *s; + char *sp=(char *)0; + + s = (char*)string; + + while (*s) + { + if (*s == cc) + sp = s; + s++; + } + if (cc == 0) + sp = s; + + return sp; +} + +/* +============= +Q_strncpyz + +Safe strncpy that ensures a trailing zero +============= +*/ +void Q_strncpyz( char *dest, const char *src, int destsize ) { + if ( !src ) { + Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" ); + } + if ( destsize < 1 ) { + Com_Error(ERR_FATAL,"Q_strncpyz: destsize < 1" ); + } + + strncpy( dest, src, destsize-1 ); + dest[destsize-1] = 0; +} + +int Q_stricmpn (const char *s1, const 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 c1 < c2 ? -1 : 1; + } + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strncmp (const char *s1, const 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) { + return c1 < c2 ? -1 : 1; + } + } while (c1); + + return 0; // strings are equal +} + +int Q_stricmp (const char *s1, const char *s2) { + return Q_stricmpn (s1, s2, 99999); +} + + +char *Q_strlwr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = tolower(*s); + s++; + } + return s1; +} + +char *Q_strupr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = toupper(*s); + s++; + } + return s1; +} + + +// never goes past bounds or leaves without a terminating 0 +void Q_strcat( char *dest, int size, const char *src ) { + int l1; + + l1 = strlen( dest ); + if ( l1 >= size ) { + Com_Error( ERR_FATAL, "Q_strcat: already overflowed" ); + } + Q_strncpyz( dest + l1, src, size - l1 ); +} + + +int Q_PrintStrlen( const char *string ) { + int len; + const char *p; + + if( !string ) { + return 0; + } + + len = 0; + p = string; + while( *p ) { + if( Q_IsColorString( p ) ) { + p += 2; + continue; + } + p++; + len++; + } + + return len; +} + + +char *Q_CleanStr( char *string ) { + char* d; + char* s; + int c; + + s = string; + d = string; + while ((c = *s) != 0 ) { + if ( Q_IsColorString( s ) ) { + s++; + } + else if ( c >= 0x20 && c <= 0x7E ) { + *d++ = c; + } + s++; + } + *d = '\0'; + + return string; +} + + +void QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) { + int len; + va_list argptr; + char bigbuffer[32000]; // big, but small enough to fit in PPC stack + + va_start (argptr,fmt); + len = vsprintf (bigbuffer,fmt,argptr); + va_end (argptr); + if ( len >= sizeof( bigbuffer ) ) { + Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" ); + } + if (len >= size) { + Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size); + } + Q_strncpyz (dest, bigbuffer, size ); +} + + +/* +============ +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 * QDECL va( char *format, ... ) { + va_list argptr; + static char string[2][32000]; // in case va is called by nested functions + static int index = 0; + char *buf; + + buf = string[index & 1]; + index++; + + va_start (argptr, format); + vsprintf (buf, format,argptr); + va_end (argptr); + + return buf; +} + + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +FIXME: overflow check? +=============== +*/ +char *Info_ValueForKey( const char *s, const char *key ) { + char pkey[MAX_INFO_KEY]; + static char value[2][MAX_INFO_VALUE]; // use two buffers so compares + // work without stomping on each other + static int valueindex = 0; + char *o; + + if ( !s || !key ) { + return ""; + } + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" ); + } + + 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) + { + *o++ = *s++; + } + *o = 0; + + if (!Q_stricmp (key, pkey) ) + return value[valueindex]; + + if (!*s) + break; + s++; + } + + return ""; +} + + +/* +=================== +Info_NextPair + +Used to itterate through all the key/value pairs in an info string +=================== +*/ +void Info_NextPair( const char *(*head), char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ) { + char *o; + const char *s; + + s = *head; + + if ( *s == '\\' ) { + s++; + } + key[0] = 0; + value[0] = 0; + + o = key; + while ( *s != '\\' ) { + if ( !*s ) { + *o = 0; + *head = s; + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) { + *o++ = *s++; + } + *o = 0; + + *head = s; +} + + +/* +=================== +Info_RemoveKey +=================== +*/ +void Info_RemoveKey( char *s, const char *key ) { + char *start; + char pkey[MAX_INFO_KEY]; + char value[MAX_INFO_VALUE]; + char *o; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" ); + } + + if (strchr (key, '\\')) { + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + 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( const char *s ) { + if ( strchr( s, '\"' ) ) { + return qfalse; + } + if ( strchr( s, ';' ) ) { + return qfalse; + } + return qtrue; +} + +/* +================== +Info_SetValueForKey + +Changes or adds a key/value pair +================== +*/ +void Info_SetValueForKey( char *s, const char *key, const char *value ) { + char newi[MAX_INFO_STRING]; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" ); + } + + if (strchr (key, '\\') || strchr (value, '\\')) + { + Com_Printf ("Can't use keys or values with a \\\n"); + return; + } + + if (strchr (key, ';') || strchr (value, ';')) + { + Com_Printf ("Can't use keys or values with a semicolon\n"); + return; + } + + if (strchr (key, '\"') || strchr (value, '\"')) + { + Com_Printf ("Can't use keys or values with a \"\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) > MAX_INFO_STRING) + { + Com_Printf ("Info string length exceeded\n"); + return; + } + + strcat (s, newi); +} + +//==================================================================== + + +/* +=============== +ParseHex +=============== +*/ +int ParseHex( const char *text ) { + int value; + int c; + + value = 0; + while ( ( c = *text++ ) != 0 ) { + if ( c >= '0' && c <= '9' ) { + value = value * 16 + c - '0'; + continue; + } + if ( c >= 'a' && c <= 'f' ) { + value = value * 16 + 10 + c - 'a'; + continue; + } + if ( c >= 'A' && c <= 'F' ) { + value = value * 16 + 10 + c - 'A'; + continue; + } + } + + return value; +} diff --git a/codemp/Splines/q_shared.h b/codemp/Splines/q_shared.h new file mode 100644 index 0000000..28691ee --- /dev/null +++ b/codemp/Splines/q_shared.h @@ -0,0 +1,792 @@ +#ifndef __Q_SHARED_H +#define __Q_SHARED_H + +// q_shared.h -- included first by ALL program modules. +// these are the definitions that have no dependance on +// central system services, and can be used by any part +// of the program without any state issues. + +// A user mod should never modify this file + +// incursion of DOOM code into the Q3A codebase +//#define Q3_VERSION "DOOM 0.01" + +// alignment macros for SIMD +#define ALIGN_ON +#define ALIGN_OFF + +#ifdef _WIN32 + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4032) +#pragma warning(disable : 4051) +#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4115) +#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4136) +#pragma warning(disable : 4201) +#pragma warning(disable : 4214) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) // truncation from const double to float +#pragma warning(disable : 4310) // cast truncates constant value +#pragma warning(disable : 4514) +#pragma warning(disable : 4711) // selected for automatic inline expansion +#pragma warning(disable : 4220) // varargs matches remaining parameters + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WIN32 // mac doesn't have malloc.h +#include // for _alloca() +#endif +#ifdef _WIN32 + +//#pragma intrinsic( memset, memcpy ) + +#endif + + +// this is the define for determining if we have an asm version of a C function +#if (defined _M_IX86 || defined __i386__) && !defined __sun__ && !defined __LCC__ +#define id386 1 +#else +#define id386 0 +#endif + +// for windows fastcall option + +#define QDECL + +//======================= WIN32 DEFINES ================================= + +#ifdef WIN32 + +#define MAC_STATIC + +#undef QDECL +#define QDECL __cdecl + +// buildstring will be incorporated into the version string +#ifdef NDEBUG +#ifdef _M_IX86 +#define CPUSTRING "win-x86" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP" +#endif +#else +#ifdef _M_IX86 +#define CPUSTRING "win-x86-debug" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP-debug" +#endif +#endif + + +#define PATH_SEP '\\' + +#endif + +//======================= MAC OS X SERVER DEFINES ===================== + +#if defined(__MACH__) && defined(__APPLE__) + +#define MAC_STATIC + +#ifdef __ppc__ +#define CPUSTRING "MacOSXS-ppc" +#elif defined __i386__ +#define CPUSTRING "MacOSXS-i386" +#else +#define CPUSTRING "MacOSXS-other" +#endif + +#define PATH_SEP '/' + +#define GAME_HARD_LINKED +#define CGAME_HARD_LINKED +#define UI_HARD_LINKED +#define _alloca alloca + +#undef ALIGN_ON +#undef ALIGN_OFF +#define ALIGN_ON #pragma align(16) +#define ALIGN_OFF #pragma align() + +#ifdef __cplusplus + extern "C" { +#endif + +void *osxAllocateMemory(long size); +void osxFreeMemory(void *pointer); + +#ifdef __cplusplus + } +#endif + +#endif + +//======================= MAC DEFINES ================================= + +#ifdef __MACOS__ + +#define MAC_STATIC static + +#define CPUSTRING "MacOS-PPC" + +#define PATH_SEP ':' + +void Sys_PumpEvents( void ); + +#endif + +#ifdef __MRC__ + +#define MAC_STATIC + +#define CPUSTRING "MacOS-PPC" + +#define PATH_SEP ':' + +void Sys_PumpEvents( void ); + +#undef QDECL +#define QDECL __cdecl + +#define _alloca alloca +#endif + +//======================= LINUX DEFINES ================================= + +// the mac compiler can't handle >32k of locals, so we +// just waste space and make big arrays static... +#ifdef __linux__ + +// bk001205 - from Makefile +#define stricmp strcasecmp + +#define MAC_STATIC // bk: FIXME + +#ifdef __i386__ +#define CPUSTRING "linux-i386" +#elif defined __axp__ +#define CPUSTRING "linux-alpha" +#else +#define CPUSTRING "linux-other" +#endif + +#define PATH_SEP '/' + +// bk001205 - try +#ifdef Q3_STATIC +#define GAME_HARD_LINKED +#define CGAME_HARD_LINKED +#define UI_HARD_LINKED +#define BOTLIB_HARD_LINKED +#endif + +#endif + +//============================================================= + + + +typedef enum {qfalse, qtrue} qboolean; +#ifdef _XBOX +#define qboolean int //don't want strict type checking on the qboolean +#endif + +typedef unsigned char byte; + +#define EQUAL_EPSILON 0.001 + +typedef int qhandle_t; +typedef int sfxHandle_t; +typedef int fileHandle_t; +typedef int clipHandle_t; + +typedef enum { + INVALID_JOINT = -1 +} jointHandle_t; + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define MAX_QINT 0x7fffffff +#define MIN_QINT (-MAX_QINT-1) + +#ifndef max +#define max( x, y ) ( ( ( x ) > ( y ) ) ? ( x ) : ( y ) ) +#define min( x, y ) ( ( ( x ) < ( y ) ) ? ( x ) : ( y ) ) +#endif + +#ifndef sign +#define sign( f ) ( ( f > 0 ) ? 1 : ( ( f < 0 ) ? -1 : 0 ) ) +#endif + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +// the game guarantees that no string from the network will ever +// exceed MAX_STRING_CHARS +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 256 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 1024 // max length of an individual token + +#define MAX_INFO_STRING 1024 +#define MAX_INFO_KEY 1024 +#define MAX_INFO_VALUE 1024 + + +#define MAX_QPATH 64 // max length of a quake game pathname +#define MAX_OSPATH 128 // max length of a filesystem pathname + +#define MAX_NAME_LENGTH 32 // max length of a client name + +// paramters for command buffer stuffing +typedef enum { + EXEC_NOW, // don't return until completed, a VM should NEVER use this, + // because some commands might cause the VM to be unloaded... + EXEC_INSERT, // insert at current position, but don't run yet + EXEC_APPEND // add to end of the command buffer (normal case) +} cbufExec_t; + + +// +// these aren't needed by any of the VMs. put in another header? +// +#define MAX_MAP_AREA_BYTES 32 // bit vector of area visibility + +#undef ERR_FATAL // malloc.h on unix + +// parameters to the main Error routine +typedef enum { + ERR_NONE, + ERR_FATAL, // exit the entire game with a popup window + ERR_DROP, // print to console and disconnect from game + ERR_DISCONNECT, // don't kill server + ERR_NEED_CD // pop up the need-cd dialog +} errorParm_t; + + +// font rendering values used by ui and cgame + +#define PROP_GAP_WIDTH 3 +#define PROP_SPACE_WIDTH 8 +#define PROP_HEIGHT 27 +#define PROP_SMALL_SIZE_SCALE 0.75 + +#define BLINK_DIVISOR 200 +#define PULSE_DIVISOR 75 + +#define UI_LEFT 0x00000000 // default +#define UI_CENTER 0x00000001 +#define UI_RIGHT 0x00000002 +#define UI_FORMATMASK 0x00000007 +#define UI_SMALLFONT 0x00000010 +#define UI_BIGFONT 0x00000020 // default +#define UI_GIANTFONT 0x00000040 +#define UI_DROPSHADOW 0x00000800 +#define UI_BLINK 0x00001000 +#define UI_INVERSE 0x00002000 +#define UI_PULSE 0x00004000 + + +/* +============================================================== + +MATHLIB + +============================================================== +*/ +#ifdef __cplusplus // so we can include this in C code +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +#define Q_PI 3.14159265358979323846 +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#include "math_vector.h" +#include "math_angles.h" +#include "math_matrix.h" +#include "math_quaternion.h" + +class idVec3_t; // for defining vectors +typedef idVec3_t &vec3_p; // for passing vectors as function arguments +typedef const idVec3_t &vec3_c; // for passing vectors as const function arguments + +class angles_t; // for defining angle vectors +typedef angles_t &angles_p; // for passing angles as function arguments +typedef const angles_t &angles_c; // for passing angles as const function arguments + +class mat3_t; // for defining matrices +typedef mat3_t &mat3_p; // for passing matrices as function arguments +typedef const mat3_t &mat3_c; // for passing matrices as const function arguments + + + +#define NUMVERTEXNORMALS 162 +extern idVec3_t bytedirs[NUMVERTEXNORMALS]; + +// all drawing is done to a 640*480 virtual screen size +// and will be automatically scaled to the real resolution +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 + +#define TINYCHAR_WIDTH (SMALLCHAR_WIDTH) +#define TINYCHAR_HEIGHT (SMALLCHAR_HEIGHT/2) + +#define SMALLCHAR_WIDTH 8 +#define SMALLCHAR_HEIGHT 16 + +#define BIGCHAR_WIDTH 16 +#define BIGCHAR_HEIGHT 16 + +#define GIANTCHAR_WIDTH 32 +#define GIANTCHAR_HEIGHT 48 + +extern vec4_t colorBlack; +extern vec4_t colorRed; +extern vec4_t colorGreen; +extern vec4_t colorBlue; +extern vec4_t colorYellow; +extern vec4_t colorMagenta; +extern vec4_t colorCyan; +extern vec4_t colorWhite; +extern vec4_t colorLtGrey; +extern vec4_t colorMdGrey; +extern vec4_t colorDkGrey; + +#define Q_COLOR_ESCAPE '^' +#define Q_IsColorString(p) ( p && *(p) == Q_COLOR_ESCAPE && *((p)+1) && *((p)+1) != Q_COLOR_ESCAPE ) + +#define COLOR_BLACK '0' +#define COLOR_RED '1' +#define COLOR_GREEN '2' +#define COLOR_YELLOW '3' +#define COLOR_BLUE '4' +#define COLOR_CYAN '5' +#define COLOR_MAGENTA '6' +#define COLOR_WHITE '7' +#define ColorIndex(c) ( ( (c) - '0' ) & 7 ) + +#define S_COLOR_BLACK "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" + +extern vec4_t g_color_table[8]; + +#define MAKERGB( v, r, g, b ) v[0]=r;v[1]=g;v[2]=b +#define MAKERGBA( v, r, g, b, a ) v[0]=r;v[1]=g;v[2]=b;v[3]=a + +#define DEG2RAD( a ) ( ( (a) * M_PI ) / 180.0F ) +#define RAD2DEG( a ) ( ( (a) * 180.0f ) / M_PI ) + +struct cplane_s; + +extern idVec3_t vec3_origin; +extern vec4_t vec4_origin; +extern mat3_t axisDefault; + +#define nanmask (255<<23) + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +float Q_fabs( float f ); +float Q_rsqrt( float f ); // reciprocal square root + +#define SQRTFAST( x ) ( 1.0f / Q_rsqrt( x ) ) + +signed char ClampChar( int i ); +signed short ClampShort( int i ); + +// this isn't a real cheap function to call! +int DirToByte( const idVec3_t &dir ); +void ByteToDir( int b, vec3_p dir ); + +#define DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[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 VectorCopy(a,b) ((b).x=(a).x,(b).y=(a).y,(b).z=(a).z]) + +#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) +#define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) + +#define DotProduct4(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]+(x)[3]*(y)[3]) +#define VectorSubtract4(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2],(c)[3]=(a)[3]-(b)[3]) +#define VectorAdd4(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3]) +#define VectorCopy4(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) +#define VectorScale4(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s),(o)[3]=(v)[3]*(s)) +#define VectorMA4(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s),(o)[3]=(v)[3]+(b)[3]*(s)) + + +#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)) +#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) + +#define SnapVector(v) {v[0]=(int)v[0];v[1]=(int)v[1];v[2]=(int)v[2];} + +float NormalizeColor( vec3_c in, vec3_p out ); + +int VectorCompare( vec3_c v1, vec3_c v2 ); +float VectorLength( vec3_c v ); +float Distance( vec3_c p1, vec3_c p2 ); +float DistanceSquared( vec3_c p1, vec3_c p2 ); +float VectorNormalize (vec3_p v); // returns vector length +void VectorNormalizeFast(vec3_p v); // does NOT return vector length, uses rsqrt approximation +float VectorNormalize2( vec3_c v, vec3_p out ); +void VectorInverse (vec3_p v); +void VectorRotate( vec3_c in, mat3_c matrix, vec3_p out ); +void VectorPolar(vec3_p v, float radius, float theta, float phi); +void VectorSnap(vec3_p v); +void Vector53Copy( const idVec5_t &in, vec3_p out); +void Vector5Scale( const idVec5_t &v, float scale, idVec5_t &out); +void Vector5Add( const idVec5_t &va, const idVec5_t &vb, idVec5_t &out); +void VectorRotate3( vec3_c vIn, vec3_c vRotation, vec3_p out); +void VectorRotate3Origin(vec3_c vIn, vec3_c vRotation, vec3_c vOrigin, vec3_p out); + + +int Q_log2(int val); + +int Q_rand( int *seed ); +float Q_random( int *seed ); +float Q_crandom( int *seed ); + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +float Q_rint( float in ); + +void vectoangles( vec3_c value1, angles_p angles); +void AnglesToAxis( angles_c angles, mat3_p axis ); + +void AxisCopy( mat3_c in, mat3_p out ); +qboolean AxisRotated( mat3_c in ); // assumes a non-degenerate axis + +int SignbitsForNormal( vec3_c normal ); +int BoxOnPlaneSide( const Bounds &b, struct cplane_s *p ); + +float AngleMod(float a); +float LerpAngle (float from, float to, float frac); +float AngleSubtract( float a1, float a2 ); +void AnglesSubtract( angles_c v1, angles_c v2, angles_p v3 ); + +float AngleNormalize360 ( float angle ); +float AngleNormalize180 ( float angle ); +float AngleDelta ( float angle1, float angle2 ); + +qboolean PlaneFromPoints( vec4_t &plane, vec3_c a, vec3_c b, vec3_c c ); +void ProjectPointOnPlane( vec3_p dst, vec3_c p, vec3_c normal ); +void RotatePointAroundVector( vec3_p dst, vec3_c dir, vec3_c point, float degrees ); +void RotateAroundDirection( mat3_p axis, float yaw ); +void MakeNormalVectors( vec3_c forward, vec3_p right, vec3_p up ); +// perpendicular vector could be replaced by this + +int PlaneTypeForNormal( vec3_c normal ); + +void MatrixMultiply( mat3_c in1, mat3_c in2, mat3_p out ); +void MatrixInverseMultiply( mat3_c in1, mat3_c in2, mat3_p out ); // in2 is transposed during multiply +void MatrixTransformVector( vec3_c in, mat3_c matrix, vec3_p out ); +void MatrixProjectVector( vec3_c in, mat3_c matrix, vec3_p out ); // Places the vector into a new coordinate system. +void AngleVectors( angles_c angles, vec3_p forward, vec3_p right, vec3_p up); +void PerpendicularVector( vec3_p dst, vec3_c src ); + +float TriangleArea( vec3_c a, vec3_c b, vec3_c c ); +#endif // __cplusplus + +//============================================= + +float Com_Clamp( float min, float max, float value ); + +#define FILE_HASH_SIZE 1024 +int Com_HashString( const char *fname ); + +char *Com_SkipPath( char *pathname ); + +// it is ok for out == in +void Com_StripExtension( const char *in, char *out ); + +// "extension" should include the dot: ".map" +void Com_DefaultExtension( char *path, int maxSize, const char *extension ); + +int Com_ParseInfos( const char *buf, int max, char infos[][MAX_INFO_STRING] ); + +/* +===================================================================================== + +SCRIPT PARSING + +===================================================================================== +*/ + +// this just controls the comment printing, it doesn't actually load a file +void Com_BeginParseSession( const char *filename ); +void Com_EndParseSession( void ); + +int Com_GetCurrentParseLine( void ); + +// Will never return NULL, just empty strings. +// An empty string will only be returned at end of file. +// ParseOnLine will return empty if there isn't another token on this line + +// this funny typedef just means a moving pointer into a const char * buffer +const char *Com_Parse( const char *(*data_p) ); +const char *Com_ParseOnLine( const char *(*data_p) ); +const char *Com_ParseRestOfLine( const char *(*data_p) ); + +void Com_UngetToken( void ); + +#ifdef __cplusplus +void Com_MatchToken( const char *(*buf_p), const char *match, qboolean warning = qfalse ); +#else +void Com_MatchToken( const char *(*buf_p), const char *match, qboolean warning ); +#endif + +void Com_ScriptError( const char *msg, ... ); +void Com_ScriptWarning( const char *msg, ... ); + +void Com_SkipBracedSection( const char *(*program) ); +void Com_SkipRestOfLine( const char *(*data) ); + +float Com_ParseFloat( const char *(*buf_p) ); +int Com_ParseInt( const char *(*buf_p) ); + +void Com_Parse1DMatrix( const char *(*buf_p), int x, float *m ); +void Com_Parse2DMatrix( const char *(*buf_p), int y, int x, float *m ); +void Com_Parse3DMatrix( const char *(*buf_p), int z, int y, int x, float *m ); + +//===================================================================================== +#ifdef __cplusplus + extern "C" { +#endif + +void QDECL Com_sprintf (char *dest, int size, const char *fmt, ...); + + +// mode parm for FS_FOpenFile +typedef enum { + FS_READ, + FS_WRITE, + FS_APPEND, + FS_APPEND_SYNC +} fsMode_t; + +typedef enum { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +} fsOrigin_t; + +//============================================= + +int Q_isprint( int c ); +int Q_islower( int c ); +int Q_isupper( int c ); +int Q_isalpha( int c ); + +// portable case insensitive compare +int Q_stricmp (const char *s1, const char *s2); +int Q_strncmp (const char *s1, const char *s2, int n); +int Q_stricmpn (const char *s1, const char *s2, int n); +char *Q_strlwr( char *s1 ); +char *Q_strupr( char *s1 ); +char *Q_strrchr( const char* string, int c ); + +// buffer size safe library replacements +void Q_strncpyz( char *dest, const char *src, int destsize ); +void Q_strcat( char *dest, int size, const char *src ); + +// strlen that discounts Quake color sequences +int Q_PrintStrlen( const char *string ); +// removes color sequences from string +char *Q_CleanStr( char *string ); + +int Com_Filter( const char *filter, const char *name, int casesensitive ); +const char *Com_StringContains( const char *str1, const char *str2, int casesensitive ); + + +//============================================= + +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 * QDECL va(char *format, ...); + +#if defined(__cplusplus) && !defined(_XBOX) + } +#endif + + +//============================================= +#ifdef __cplusplus +// +// mapfile parsing +// +typedef struct ePair_s { + char *key; + char *value; +} ePair_t; + +typedef struct mapSide_s { + char material[MAX_QPATH]; + vec4_t plane; + vec4_t textureVectors[2]; +} mapSide_t; + +typedef struct { + int numSides; + mapSide_t **sides; +} mapBrush_t; + +typedef struct { + idVec3_t xyz; + float st[2]; +} patchVertex_t; + +typedef struct { + char material[MAX_QPATH]; + int width, height; + patchVertex_t *patchVerts; +} mapPatch_t; + +typedef struct { + char modelName[MAX_QPATH]; + float matrix[16]; +} mapModel_t; + +typedef struct mapPrimitive_s { + int numEpairs; + ePair_t **ePairs; + + // only one of these will be non-NULL + mapBrush_t *brush; + mapPatch_t *patch; + mapModel_t *model; +} mapPrimitive_t; + +typedef struct mapEntity_s { + int numPrimitives; + mapPrimitive_t **primitives; + + int numEpairs; + ePair_t **ePairs; +} mapEntity_t; + +typedef struct { + int numEntities; + mapEntity_t **entities; +} mapFile_t; + + +// the order of entities, brushes, and sides will be maintained, the +// lists won't be swapped on each load or save +mapFile_t *ParseMapFile( const char *text ); +void FreeMapFile( mapFile_t *mapFile ); +void WriteMapFile( const mapFile_t *mapFile, FILE *f ); + +// key names are case-insensitive +const char *ValueForMapEntityKey( const mapEntity_t *ent, const char *key ); +float FloatForMapEntityKey( const mapEntity_t *ent, const char *key ); +qboolean GetVectorForMapEntityKey( const mapEntity_t *ent, const char *key, idVec3_t &vec ); + +typedef struct { + idVec3_t xyz; + idVec2_t st; + idVec3_t normal; + idVec3_t tangents[2]; + byte smoothing[4]; // colors for silhouette smoothing +} drawVert_t; + +typedef struct { + int width, height; + drawVert_t *verts; +} drawVertMesh_t; + +// Tesselate a map patch into smoothed, drawable vertexes +// MaxError of around 4 is reasonable +drawVertMesh_t *SubdivideMapPatch( const mapPatch_t *patch, float maxError ); +#endif // __cplusplus + +//========================================= + +#ifdef __cplusplus + extern "C" { +#endif + +void QDECL Com_Error( int level, const char *error, ... ); +void QDECL Com_Printf( const char *msg, ... ); +void QDECL Com_DPrintf( const char *msg, ... ); + +#if defined(__cplusplus) && !defined(_XBOX) + } +#endif + + +typedef struct { + qboolean frameMemory; + int currentElements; + int maxElements; // will reallocate and move when exceeded + void **elements; +} growList_t; + +// you don't need to init the growlist if you don't mind it growing and moving +// the list as it expands +void Com_InitGrowList( growList_t *list, int maxElements ); +int Com_AddToGrowList( growList_t *list, void *data ); +void *Com_GrowListElement( const growList_t *list, int index ); +int Com_IndexForGrowListElement( const growList_t *list, const void *element ); + + +// +// key / value info strings +// +char *Info_ValueForKey( const char *s, const char *key ); +void Info_RemoveKey( char *s, const char *key ); +void Info_SetValueForKey( char *s, const char *key, const char *value ); +qboolean Info_Validate( const char *s ); +void Info_NextPair( const char *(*s), char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ); + +// get cvar defs, collision defs, etc +//#include "../shared/interface.h" + +// get key code numbers for events +//#include "../shared/keycodes.h" + +#ifdef __cplusplus +// get the polygon winding functions +//#include "../shared/windings.h" + +// get the flags class +//#include "../shared/idflags.h" +#endif // __cplusplus + +#endif // __Q_SHARED_H + diff --git a/codemp/Splines/splines.cpp b/codemp/Splines/splines.cpp new file mode 100644 index 0000000..7554ab6 --- /dev/null +++ b/codemp/Splines/splines.cpp @@ -0,0 +1,1226 @@ + +//#include "stdafx.h" +//#include "qe3.h" + +#include "q_shared.h" +#include "splines.h" + +extern "C" { +int FS_Write( const void *buffer, int len, fileHandle_t h ); +int FS_ReadFile( const char *qpath, void **buffer ); +void FS_FreeFile( void *buffer ); +fileHandle_t FS_FOpenFileWrite( const char *filename ); +void FS_FCloseFile( fileHandle_t f ); +} + +float Q_fabs( float f ) { + int tmp = * ( int * ) &f; + tmp &= 0x7FFFFFFF; + return * ( float * ) &tmp; +} + + +//#include "../shared/windings.h" +//#include "../qcommon/qcommon.h" +//#include "../sys/sys_public.h" +//#include "../game/game_entity.h" + +idCameraDef splineList; +idCameraDef *g_splineList = &splineList; + +idVec3_t idSplineList::zero(0,0,0); + +void glLabeledPoint(idVec3_t &color, idVec3_t &point, float size, const char *label) { + qglColor3fv(color); + qglPointSize(size); + qglBegin(GL_POINTS); + qglVertex3fv(point); + qglEnd(); + idVec3_t v = point; + v.x += 1; + v.y += 1; + v.z += 1; + qglRasterPos3fv (v); + qglCallLists (strlen(label), GL_UNSIGNED_BYTE, label); +} + + +void glBox(idVec3_t &color, idVec3_t &point, float size) { + idVec3_t mins(point); + idVec3_t maxs(point); + mins[0] -= size; + mins[1] += size; + mins[2] -= size; + maxs[0] += size; + maxs[1] -= size; + maxs[2] += size; + qglColor3fv(color); + qglBegin(GL_LINE_LOOP); + qglVertex3f(mins[0],mins[1],mins[2]); + qglVertex3f(maxs[0],mins[1],mins[2]); + qglVertex3f(maxs[0],maxs[1],mins[2]); + qglVertex3f(mins[0],maxs[1],mins[2]); + qglEnd(); + qglBegin(GL_LINE_LOOP); + qglVertex3f(mins[0],mins[1],maxs[2]); + qglVertex3f(maxs[0],mins[1],maxs[2]); + qglVertex3f(maxs[0],maxs[1],maxs[2]); + qglVertex3f(mins[0],maxs[1],maxs[2]); + qglEnd(); + + qglBegin(GL_LINES); + qglVertex3f(mins[0],mins[1],mins[2]); + qglVertex3f(mins[0],mins[1],maxs[2]); + qglVertex3f(mins[0],maxs[1],maxs[2]); + qglVertex3f(mins[0],maxs[1],mins[2]); + qglVertex3f(maxs[0],mins[1],mins[2]); + qglVertex3f(maxs[0],mins[1],maxs[2]); + qglVertex3f(maxs[0],maxs[1],maxs[2]); + qglVertex3f(maxs[0],maxs[1],mins[2]); + qglEnd(); + +} + +void splineTest() { + //g_splineList->load("p:/doom/base/maps/test_base1.camera"); +} + +void splineDraw() { + //g_splineList->addToRenderer(); +} + + +//extern void D_DebugLine( const idVec3_t &color, const idVec3_t &start, const idVec3_t &end ); + +void debugLine(idVec3_t &color, float x, float y, float z, float x2, float y2, float z2) { + //idVec3_t from(x, y, z); + //idVec3_t to(x2, y2, z2); + //D_DebugLine(color, from, to); +} + +void idSplineList::addToRenderer() { + + if (controlPoints.Num() == 0) { + return; + } + + idVec3_t mins, maxs; + idVec3_t yellow(1.0, 1.0, 0); + idVec3_t white(1.0, 1.0, 1.0); + int i; + + for(i = 0; i < controlPoints.Num(); i++) { + VectorCopy(*controlPoints[i], mins); + VectorCopy(mins, maxs); + mins[0] -= 8; + mins[1] += 8; + mins[2] -= 8; + maxs[0] += 8; + maxs[1] -= 8; + maxs[2] += 8; + debugLine( yellow, mins[0], mins[1], mins[2], maxs[0], mins[1], mins[2]); + debugLine( yellow, maxs[0], mins[1], mins[2], maxs[0], maxs[1], mins[2]); + debugLine( yellow, maxs[0], maxs[1], mins[2], mins[0], maxs[1], mins[2]); + debugLine( yellow, mins[0], maxs[1], mins[2], mins[0], mins[1], mins[2]); + + debugLine( yellow, mins[0], mins[1], maxs[2], maxs[0], mins[1], maxs[2]); + debugLine( yellow, maxs[0], mins[1], maxs[2], maxs[0], maxs[1], maxs[2]); + debugLine( yellow, maxs[0], maxs[1], maxs[2], mins[0], maxs[1], maxs[2]); + debugLine( yellow, mins[0], maxs[1], maxs[2], mins[0], mins[1], maxs[2]); + + } + + int step = 0; + idVec3_t step1; + for(i = 3; i < controlPoints.Num(); i++) { + for (float tension = 0.0f; tension < 1.001f; tension += 0.1f) { + float x = 0; + float y = 0; + float z = 0; + for (int j = 0; j < 4; j++) { + x += controlPoints[i - (3 - j)]->x * calcSpline(j, tension); + y += controlPoints[i - (3 - j)]->y * calcSpline(j, tension); + z += controlPoints[i - (3 - j)]->z * calcSpline(j, tension); + } + if (step == 0) { + step1[0] = x; + step1[1] = y; + step1[2] = z; + step = 1; + } else { + debugLine( white, step1[0], step1[1], step1[2], x, y, z); + step = 0; + } + + } + } +} + +void idSplineList::buildSpline() { + //int start = Sys_Milliseconds(); + clearSpline(); + for(int i = 3; i < controlPoints.Num(); i++) { + for (float tension = 0.0f; tension < 1.001f; tension += granularity) { + float x = 0; + float y = 0; + float z = 0; + for (int j = 0; j < 4; j++) { + x += controlPoints[i - (3 - j)]->x * calcSpline(j, tension); + y += controlPoints[i - (3 - j)]->y * calcSpline(j, tension); + z += controlPoints[i - (3 - j)]->z * calcSpline(j, tension); + } + splinePoints.Append(new idVec3_t(x, y, z)); + } + } + dirty = false; + //Com_Printf("Spline build took %f seconds\n", (float)(Sys_Milliseconds() - start) / 1000); +} + + +void idSplineList::draw(bool editMode) { + int i; + vec4_t yellow(1, 1, 0, 1); + + if (controlPoints.Num() == 0) { + return; + } + + if (dirty) { + buildSpline(); + } + + + qglColor3fv(controlColor); + qglPointSize(5); + + qglBegin(GL_POINTS); + for (i = 0; i < controlPoints.Num(); i++) { + qglVertex3fv(*controlPoints[i]); + } + qglEnd(); + + if (editMode) { + for(i = 0; i < controlPoints.Num(); i++) { + glBox(activeColor, *controlPoints[i], 4); + } + } + + //Draw the curve + qglColor3fv(pathColor); + qglBegin(GL_LINE_STRIP); + int count = splinePoints.Num(); + for (i = 0; i < count; i++) { + qglVertex3fv(*splinePoints[i]); + } + qglEnd(); + + if (editMode) { + qglColor3fv(segmentColor); + qglPointSize(3); + qglBegin(GL_POINTS); + for (i = 0; i < count; i++) { + qglVertex3fv(*splinePoints[i]); + } + qglEnd(); + } + if (count > 0) { + //assert(activeSegment >=0 && activeSegment < count); + if (activeSegment >=0 && activeSegment < count) { + glBox(activeColor, *splinePoints[activeSegment], 6); + glBox(yellow, *splinePoints[activeSegment], 8); + } + } + +} + +float idSplineList::totalDistance() { + + if (controlPoints.Num() == 0) { + return 0.0; + } + + if (dirty) { + buildSpline(); + } + + float dist = 0.0; + idVec3_t temp; + int count = splinePoints.Num(); + for(int i = 1; i < count; i++) { + temp = *splinePoints[i-1]; + temp -= *splinePoints[i]; + dist += temp.Length(); + } + return dist; +} + +void idSplineList::initPosition(long bt, long totalTime) { + + if (dirty) { + buildSpline(); + } + + if (splinePoints.Num() == 0) { + return; + } + + baseTime = bt; + time = totalTime; + + // calc distance to travel ( this will soon be broken into time segments ) + splineTime.Clear(); + splineTime.Append(bt); + double dist = totalDistance(); + double distSoFar = 0.0; + idVec3_t temp; + int count = splinePoints.Num(); + //for(int i = 2; i < count - 1; i++) { + for(int i = 1; i < count; i++) { + temp = *splinePoints[i-1]; + temp -= *splinePoints[i]; + distSoFar += temp.Length(); + double percent = distSoFar / dist; + percent *= totalTime; + splineTime.Append(percent + bt); + } + assert(splineTime.Num() == splinePoints.Num()); + activeSegment = 0; +} + + + +float idSplineList::calcSpline(int step, float tension) { + switch(step) { + case 0: return (pow(1 - tension, 3)) / 6; + case 1: return (3 * pow(tension, 3) - 6 * pow(tension, 2) + 4) / 6; + case 2: return (-3 * pow(tension, 3) + 3 * pow(tension, 2) + 3 * tension + 1) / 6; + case 3: return pow(tension, 3) / 6; + } + return 0.0; +} + + + +void idSplineList::updateSelection(const idVec3_t &move) { + if (selected) { + dirty = true; + VectorAdd(*selected, move, *selected); + } +} + + +void idSplineList::setSelectedPoint(idVec3_t *p) { + if (p) { + p->Snap(); + for(int i = 0; i < controlPoints.Num(); i++) { + if (*p == *controlPoints[i]) { + selected = controlPoints[i]; + } + } + } else { + selected = NULL; + } +} + +const idVec3_t *idSplineList::getPosition(long t) { + static idVec3_t interpolatedPos; + //static long lastTime = -1; + + int count = splineTime.Num(); + if (count == 0) { + return &zero; + } + + Com_Printf("Time: %d\n", t); + assert(splineTime.Num() == splinePoints.Num()); + + while (activeSegment < count) { + if (splineTime[activeSegment] >= t) { + if (activeSegment > 0 && activeSegment < count - 1) { + double timeHi = splineTime[activeSegment + 1]; + double timeLo = splineTime[activeSegment - 1]; + double percent = (timeHi - t) / (timeHi - timeLo); + // pick two bounding points + idVec3_t v1 = *splinePoints[activeSegment-1]; + idVec3_t v2 = *splinePoints[activeSegment+1]; + v2 *= (1.0 - percent); + v1 *= percent; + v2 += v1; + interpolatedPos = v2; + return &interpolatedPos; + } + return splinePoints[activeSegment]; + } else { + activeSegment++; + } + } + return splinePoints[count-1]; +} + +void idSplineList::parse(const char *(*text) ) { + const char *token; + //Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !Q_stricmp (token, "}") ) { + break; + } + + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !Q_stricmp (token, "(") || !Q_stricmp(token, "}")) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine(text); + const char *token = Com_Parse(text); + if (Q_stricmp(key.c_str(), "granularity") == 0) { + granularity = atof(token); + } else if (Q_stricmp(key.c_str(), "name") == 0) { + name = token; + } + token = Com_Parse(text); + + } while (1); + + if ( !Q_stricmp (token, "}") ) { + break; + } + + Com_UngetToken(); + // read the control point + idVec3_t point; + Com_Parse1DMatrix( text, 3, point ); + addPoint(point.x, point.y, point.z); + } while (1); + + //Com_UngetToken(); + //Com_MatchToken( text, "}" ); + dirty = true; +} + +void idSplineList::write(fileHandle_t file, const char *p) { + idStr s = va("\t\t%s {\n", p); + FS_Write(s.c_str(), s.length(), file); + //s = va("\t\tname %s\n", name.c_str()); + //FS_Write(s.c_str(), s.length(), file); + s = va("\t\t\tgranularity %f\n", granularity); + FS_Write(s.c_str(), s.length(), file); + int count = controlPoints.Num(); + for (int i = 0; i < count; i++) { + s = va("\t\t\t( %f %f %f )\n", controlPoints[i]->x, controlPoints[i]->y, controlPoints[i]->z); + FS_Write(s.c_str(), s.length(), file); + } + s = "\t\t}\n"; + FS_Write(s.c_str(), s.length(), file); +} + + +void idCameraDef::getActiveSegmentInfo(int segment, idVec3_t &origin, idVec3_t &direction, float *fov) { +#if 0 + if (!cameraSpline.validTime()) { + buildCamera(); + } + double d = (double)segment / numSegments(); + getCameraInfo(d * totalTime * 1000, origin, direction, fov); +#endif +/* + if (!cameraSpline.validTime()) { + buildCamera(); + } + origin = *cameraSpline.getSegmentPoint(segment); + + + idVec3_t temp; + + int numTargets = getTargetSpline()->controlPoints.Num(); + int count = cameraSpline.splineTime.Num(); + if (numTargets == 0) { + // follow the path + if (cameraSpline.getActiveSegment() < count - 1) { + temp = *cameraSpline.splinePoints[cameraSpline.getActiveSegment()+1]; + } + } else if (numTargets == 1) { + temp = *getTargetSpline()->controlPoints[0]; + } else { + temp = *getTargetSpline()->getSegmentPoint(segment); + } + + temp -= origin; + temp.Normalize(); + direction = temp; +*/ +} + +bool idCameraDef::getCameraInfo(long time, idVec3_t &origin, idVec3_t &direction, float *fv) { + + + if ((time - startTime) / 1000 > totalTime) { + return false; + } + + + for (int i = 0; i < events.Num(); i++) { + if (time >= startTime + events[i]->getTime() && !events[i]->getTriggered()) { + events[i]->setTriggered(true); + if (events[i]->getType() == idCameraEvent::EVENT_TARGET) { + setActiveTargetByName(events[i]->getParam()); + getActiveTarget()->start(startTime + events[i]->getTime()); + //Com_Printf("Triggered event switch to target: %s\n",events[i]->getParam()); + } else if (events[i]->getType() == idCameraEvent::EVENT_TRIGGER) { + //idEntity *ent = NULL; + //ent = level.FindTarget( ent, events[i]->getParam()); + //if (ent) { + // ent->signal( SIG_TRIGGER ); + // ent->ProcessEvent( &EV_Activate, world ); + //} + } else if (events[i]->getType() == idCameraEvent::EVENT_FOV) { + //*fv = fov = atof(events[i]->getParam()); + } else if (events[i]->getType() == idCameraEvent::EVENT_STOP) { + return false; + } + } + } + + origin = *cameraPosition->getPosition(time); + + *fv = fov.getFOV(time); + + idVec3_t temp = origin; + + int numTargets = targetPositions.Num(); + if (numTargets == 0) { +/* + // follow the path + if (cameraSpline.getActiveSegment() < count - 1) { + temp = *cameraSpline.splinePoints[cameraSpline.getActiveSegment()+1]; + if (temp == origin) { + int index = cameraSpline.getActiveSegment() + 2; + while (temp == origin && index < count - 1) { + temp = *cameraSpline.splinePoints[index++]; + } + } + } +*/ + } else { + temp = *getActiveTarget()->getPosition(time); + } + + temp -= origin; + temp.Normalize(); + direction = temp; + + return true; +} + +bool idCameraDef::waitEvent(int index) { + //for (int i = 0; i < events.Num(); i++) { + // if (events[i]->getSegment() == index && events[i]->getType() == idCameraEvent::EVENT_WAIT) { + // return true; + // } + //} + return false; +} + + +#define NUM_CCELERATION_SEGS 10 +#define CELL_AMT 5 + +void idCameraDef::buildCamera() { + int i; + //int lastSwitch = 0; + idList waits; + idList targets; + + totalTime = baseTime; + cameraPosition->setTime(totalTime * 1000); + // we have a base time layout for the path and the target path + // now we need to layer on any wait or speed changes + for (i = 0; i < events.Num(); i++) { + //idCameraEvent *ev = events[i]; + events[i]->setTriggered(false); + switch (events[i]->getType()) { + case idCameraEvent::EVENT_TARGET : { + targets.Append(i); + break; + } + case idCameraEvent::EVENT_WAIT : { + waits.Append(atof(events[i]->getParam())); + cameraPosition->addVelocity(events[i]->getTime(), atof(events[i]->getParam()) * 1000, 0); + break; + } + case idCameraEvent::EVENT_TARGETWAIT : { + //targetWaits.Append(i); + break; + } + case idCameraEvent::EVENT_SPEED : { +/* + // take the average delay between up to the next five segments + float adjust = atof(events[i]->getParam()); + int index = events[i]->getSegment(); + total = 0; + count = 0; + + // get total amount of time over the remainder of the segment + for (j = index; j < cameraSpline.numSegments() - 1; j++) { + total += cameraSpline.getSegmentTime(j + 1) - cameraSpline.getSegmentTime(j); + count++; + } + + // multiply that by the adjustment + double newTotal = total * adjust; + // what is the difference.. + newTotal -= total; + totalTime += newTotal / 1000; + + // per segment difference + newTotal /= count; + int additive = newTotal; + + // now propogate that difference out to each segment + for (j = index; j < cameraSpline.numSegments(); j++) { + cameraSpline.addSegmentTime(j, additive); + additive += newTotal; + } + break; +*/ + } + default: break; // FIXME: what about other idCameraEvent? + } + } + + + for (i = 0; i < waits.Num(); i++) { + totalTime += waits[i]; + } + + // on a new target switch, we need to take time to this point ( since last target switch ) + // and allocate it across the active target, then reset time to this point + long timeSoFar = 0; + long total = (int)(totalTime * 1000); + for (i = 0; i < targets.Num(); i++) { + long t; + if (i < targets.Num() - 1) { + t = events[targets[i+1]]->getTime(); + } else { + t = total - timeSoFar; + } + // t is how much time to use for this target + setActiveTargetByName(events[targets[i]]->getParam()); + getActiveTarget()->setTime(t); + timeSoFar += t; + } + + +} + +void idCameraDef::startCamera(long t) { + buildCamera(); + cameraPosition->start(t); + //for (int i = 0; i < targetPositions.Num(); i++) { + // targetPositions[i]-> + //} + startTime = t; + cameraRunning = true; +} + + +void idCameraDef::parse(const char *(*text) ) { + + const char *token; + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !Q_stricmp (token, "}") ) { + break; + } + + if (Q_stricmp(token, "time") == 0) { + baseTime = Com_ParseFloat(text); + } + + if (Q_stricmp(token, "camera_fixed") == 0) { + cameraPosition = new idFixedPosition(); + cameraPosition->parse(text); + } + + if (Q_stricmp(token, "camera_interpolated") == 0) { + cameraPosition = new idInterpolatedPosition(); + cameraPosition->parse(text); + } + + if (Q_stricmp(token, "camera_spline") == 0) { + cameraPosition = new idSplinePosition(); + cameraPosition->parse(text); + } + + if (Q_stricmp(token, "target_fixed") == 0) { + idFixedPosition *pos = new idFixedPosition(); + pos->parse(text); + targetPositions.Append(pos); + } + + if (Q_stricmp(token, "target_interpolated") == 0) { + idInterpolatedPosition *pos = new idInterpolatedPosition(); + pos->parse(text); + targetPositions.Append(pos); + } + + if (Q_stricmp(token, "target_spline") == 0) { + idSplinePosition *pos = new idSplinePosition(); + pos->parse(text); + targetPositions.Append(pos); + } + + if (Q_stricmp(token, "fov") == 0) { + fov.parse(text); + } + + if (Q_stricmp(token, "event") == 0) { + idCameraEvent *event = new idCameraEvent(); + event->parse(text); + addEvent(event); + } + + + } while (1); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); + +} + +qboolean idCameraDef::load(const char *filename) { + char *buf; + const char *buf_p; + //int length = + FS_ReadFile( filename, (void **)&buf ); + if ( !buf ) { + return qfalse; + } + + clear(); + Com_BeginParseSession( filename ); + buf_p = buf; + parse(&buf_p); + Com_EndParseSession(); + FS_FreeFile( buf ); + + return qtrue; +} + +void idCameraDef::save(const char *filename) { + fileHandle_t file = FS_FOpenFileWrite(filename); + if (file) { + int i; + idStr s = "cameraPathDef { \n"; + FS_Write(s.c_str(), s.length(), file); + s = va("\ttime %f\n", baseTime); + FS_Write(s.c_str(), s.length(), file); + + cameraPosition->write(file, va("camera_%s",cameraPosition->typeStr())); + + for (i = 0; i < numTargets(); i++) { + targetPositions[i]->write(file, va("target_%s", targetPositions[i]->typeStr())); + } + + for (i = 0; i < events.Num(); i++) { + events[i]->write(file, "event"); + } + + fov.write(file, "fov"); + + s = "}\n"; + FS_Write(s.c_str(), s.length(), file); + } + FS_FCloseFile(file); +} + +int idCameraDef::sortEvents(const void *p1, const void *p2) { + idCameraEvent *ev1 = (idCameraEvent*)(p1); + idCameraEvent *ev2 = (idCameraEvent*)(p2); + + if (ev1->getTime() > ev2->getTime()) { + return -1; + } + if (ev1->getTime() < ev2->getTime()) { + return 1; + } + return 0; +} + +void idCameraDef::addEvent(idCameraEvent *event) { + events.Append(event); + //events.Sort(&sortEvents); + +} +void idCameraDef::addEvent(idCameraEvent::eventType t, const char *param, long time) { + addEvent(new idCameraEvent(t, param, time)); + buildCamera(); +} + + +const char *idCameraEvent::eventStr[] = { + "NA", + "WAIT", + "TARGETWAIT", + "SPEED", + "TARGET", + "SNAPTARGET", + "FOV", + "SCRIPT", + "TRIGGER", + "STOP" +}; + +void idCameraEvent::parse(const char *(*text) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp (token, "}") ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp (token, "(") || !strcmp(token, "}")) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine(text); + const char *token = Com_Parse(text); + if (Q_stricmp(key.c_str(), "type") == 0) { + type = static_cast(atoi(token)); + } else if (Q_stricmp(key.c_str(), "param") == 0) { + paramStr = token; + } else if (Q_stricmp(key.c_str(), "time") == 0) { + time = atoi(token); + } + token = Com_Parse(text); + + } while (1); + + if ( !strcmp (token, "}") ) { + break; + } + + } while (1); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + +void idCameraEvent::write(fileHandle_t file, const char *name) { + idStr s = va("\t%s {\n", name); + FS_Write(s.c_str(), s.length(), file); + s = va("\t\ttype %d\n", static_cast(type)); + FS_Write(s.c_str(), s.length(), file); + s = va("\t\tparam %s\n", paramStr.c_str()); + FS_Write(s.c_str(), s.length(), file); + s = va("\t\ttime %d\n", time); + FS_Write(s.c_str(), s.length(), file); + s = "\t}\n"; + FS_Write(s.c_str(), s.length(), file); +} + + +const char *idCameraPosition::positionStr[] = { + "Fixed", + "Interpolated", + "Spline", +}; + + + +const idVec3_t *idInterpolatedPosition::getPosition(long t) { + static idVec3_t interpolatedPos; + + float velocity = getVelocity(t); + float timePassed = t - lastTime; + lastTime = t; + + // convert to seconds + timePassed /= 1000; + + float distToTravel = timePassed *= velocity; + + idVec3_t temp = startPos; + temp -= endPos; + float distance = temp.Length(); + + distSoFar += distToTravel; + float percent = (float)(distSoFar) / distance; + + if (percent > 1.0) { + percent = 1.0; + } else if (percent < 0.0) { + percent = 0.0; + } + + // the following line does a straigt calc on percentage of time + // float percent = (float)(startTime + time - t) / time; + + idVec3_t v1 = startPos; + idVec3_t v2 = endPos; + v1 *= (1.0 - percent); + v2 *= percent; + v1 += v2; + interpolatedPos = v1; + return &interpolatedPos; +} + + +void idCameraFOV::parse(const char *(*text) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp (token, "}") ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp (token, "(") || !strcmp(token, "}")) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine(text); + const char *token = Com_Parse(text); + if (Q_stricmp(key.c_str(), "fov") == 0) { + fov = atof(token); + } else if (Q_stricmp(key.c_str(), "startFOV") == 0) { + startFOV = atof(token); + } else if (Q_stricmp(key.c_str(), "endFOV") == 0) { + endFOV = atof(token); + } else if (Q_stricmp(key.c_str(), "time") == 0) { + time = atoi(token); + } + token = Com_Parse(text); + + } while (1); + + if ( !strcmp (token, "}") ) { + break; + } + + } while (1); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + +bool idCameraPosition::parseToken(const char *key, const char *(*text)) { + const char *token = Com_Parse(text); + if (Q_stricmp(key, "time") == 0) { + time = atol(token); + return true; + } else if (Q_stricmp(key, "type") == 0) { + type = static_cast(atoi(token)); + return true; + } else if (Q_stricmp(key, "velocity") == 0) { + long t = atol(token); + token = Com_Parse(text); + long d = atol(token); + token = Com_Parse(text); + float s = atof(token); + addVelocity(t, d, s); + return true; + } else if (Q_stricmp(key, "baseVelocity") == 0) { + baseVelocity = atof(token); + return true; + } else if (Q_stricmp(key, "name") == 0) { + name = token; + return true; + } else if (Q_stricmp(key, "time") == 0) { + time = atoi(token); + return true; + } + Com_UngetToken(); + return false; +} + + + +void idFixedPosition::parse(const char *(*text) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp (token, "}") ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp (token, "(") || !strcmp(token, "}")) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine(text); + + const char *token = Com_Parse(text); + if (Q_stricmp(key.c_str(), "pos") == 0) { + Com_UngetToken(); + Com_Parse1DMatrix( text, 3, pos ); + } else { + Com_UngetToken(); + idCameraPosition::parseToken(key.c_str(), text); + } + token = Com_Parse(text); + + } while (1); + + if ( !strcmp (token, "}") ) { + break; + } + + } while (1); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + +void idInterpolatedPosition::parse(const char *(*text) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp (token, "}") ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp (token, "(") || !strcmp(token, "}")) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine(text); + + const char *token = Com_Parse(text); + if (Q_stricmp(key.c_str(), "startPos") == 0) { + Com_UngetToken(); + Com_Parse1DMatrix( text, 3, startPos ); + } else if (Q_stricmp(key.c_str(), "endPos") == 0) { + Com_UngetToken(); + Com_Parse1DMatrix( text, 3, endPos ); + } else { + Com_UngetToken(); + idCameraPosition::parseToken(key.c_str(), text); + } + token = Com_Parse(text); + + } while (1); + + if ( !strcmp (token, "}") ) { + break; + } + + } while (1); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + + +void idSplinePosition::parse(const char *(*text) ) { + const char *token; + Com_MatchToken( text, "{" ); + do { + token = Com_Parse( text ); + + if ( !token[0] ) { + break; + } + if ( !strcmp (token, "}") ) { + break; + } + + // here we may have to jump over brush epairs ( only used in editor ) + do { + // if token is not a brace, it is a key for a key/value pair + if ( !token[0] || !strcmp (token, "(") || !strcmp(token, "}")) { + break; + } + + Com_UngetToken(); + idStr key = Com_ParseOnLine(text); + + const char *token = Com_Parse(text); + if (Q_stricmp(key.c_str(), "target") == 0) { + target.parse(text); + } else { + Com_UngetToken(); + idCameraPosition::parseToken(key.c_str(), text); + } + token = Com_Parse(text); + + } while (1); + + if ( !strcmp (token, "}") ) { + break; + } + + } while (1); + + Com_UngetToken(); + Com_MatchToken( text, "}" ); +} + + + +void idCameraFOV::write(fileHandle_t file, const char *p) { + idStr s = va("\t%s {\n", p); + FS_Write(s.c_str(), s.length(), file); + + s = va("\t\tfov %f\n", fov); + FS_Write(s.c_str(), s.length(), file); + + s = va("\t\tstartFOV %f\n", startFOV); + FS_Write(s.c_str(), s.length(), file); + + s = va("\t\tendFOV %f\n", endFOV); + FS_Write(s.c_str(), s.length(), file); + + s = va("\t\ttime %i\n", time); + FS_Write(s.c_str(), s.length(), file); + + s = "\t}\n"; + FS_Write(s.c_str(), s.length(), file); +} + + +void idCameraPosition::write(fileHandle_t file, const char *p) { + + idStr s = va("\t\ttime %i\n", time); + FS_Write(s.c_str(), s.length(), file); + + s = va("\t\ttype %i\n", static_cast(type)); + FS_Write(s.c_str(), s.length(), file); + + s = va("\t\tname %s\n", name.c_str()); + FS_Write(s.c_str(), s.length(), file); + + s = va("\t\tbaseVelocity %f\n", baseVelocity); + FS_Write(s.c_str(), s.length(), file); + + for (int i = 0; i < velocities.Num(); i++) { + s = va("\t\tvelocity %i %i %f\n", velocities[i]->startTime, velocities[i]->time, velocities[i]->speed); + FS_Write(s.c_str(), s.length(), file); + } + +} + +void idFixedPosition::write(fileHandle_t file, const char *p) { + idStr s = va("\t%s {\n", p); + FS_Write(s.c_str(), s.length(), file); + idCameraPosition::write(file, p); + s = va("\t\tpos ( %f %f %f )\n", pos.x, pos.y, pos.z); + FS_Write(s.c_str(), s.length(), file); + s = "\t}\n"; + FS_Write(s.c_str(), s.length(), file); +} + +void idInterpolatedPosition::write(fileHandle_t file, const char *p) { + idStr s = va("\t%s {\n", p); + FS_Write(s.c_str(), s.length(), file); + idCameraPosition::write(file, p); + s = va("\t\tstartPos ( %f %f %f )\n", startPos.x, startPos.y, startPos.z); + FS_Write(s.c_str(), s.length(), file); + s = va("\t\tendPos ( %f %f %f )\n", endPos.x, endPos.y, endPos.z); + FS_Write(s.c_str(), s.length(), file); + s = "\t}\n"; + FS_Write(s.c_str(), s.length(), file); +} + +void idSplinePosition::write(fileHandle_t file, const char *p) { + idStr s = va("\t%s {\n", p); + FS_Write(s.c_str(), s.length(), file); + idCameraPosition::write(file, p); + target.write(file, "target"); + s = "\t}\n"; + FS_Write(s.c_str(), s.length(), file); +} + +void idCameraDef::addTarget(const char *name, idCameraPosition::positionType type) { + //const char *text = (name == NULL) ? va("target0%d", numTargets()+1) : name; // TTimo: unused + idCameraPosition *pos = newFromType(type); + if (pos) { + pos->setName(name); + targetPositions.Append(pos); + activeTarget = numTargets()-1; + if (activeTarget == 0) { + // first one + addEvent(idCameraEvent::EVENT_TARGET, name, 0); + } + } +} + + + +idCameraDef camera; + +extern "C" { +qboolean loadCamera(const char *name) { + camera.clear(); + return static_cast(camera.load(name)); +} + +qboolean getCameraInfo(int time, float *origin, float*angles) { + idVec3_t dir, org; + org[0] = origin[0]; + org[1] = origin[1]; + org[2] = origin[2]; + float fov = 90; + if (camera.getCameraInfo(time, org, dir, &fov)) { + origin[0] = org[0]; + origin[1] = org[1]; + origin[2] = org[2]; + angles[1] = atan2 (dir[1], dir[0])*180/3.14159; + angles[0] = asin (dir[2])*180/3.14159; + return qtrue; + } + return qfalse; +} + +void startCamera(int time) { + camera.startCamera(time); +} + +} + + diff --git a/codemp/Splines/splines.h b/codemp/Splines/splines.h new file mode 100644 index 0000000..b258349 --- /dev/null +++ b/codemp/Splines/splines.h @@ -0,0 +1,1061 @@ +#ifndef __SPLINES_H +#define __SPLINES_H + +extern "C" { +#ifdef Q3RADIANT +#include "../qgl.h" +#else +#include "../renderer/qgl.h" +#endif +} +#include "util_list.h" +#include "util_str.h" +#include "math_vector.h" + +typedef int fileHandle_t; + +extern void glBox(idVec3_t &color, idVec3_t &point, float size); +extern void glLabeledPoint(idVec3_t &color, idVec3_t &point, float size, const char *label); + +static vec4_t blue(0, 0, 1, 1); +static vec4_t red(1, 0, 0, 1); + +class idPointListInterface { +public: + idPointListInterface() { + selectedPoints.Clear(); + } + virtual ~idPointListInterface() {} + + virtual int numPoints() { + return 0; + } + + virtual void addPoint(const float x, const float y, const float z) {} + virtual void addPoint(const idVec3_t &v) {} + virtual void removePoint(int index) {} + virtual idVec3_t *getPoint(int index) { return NULL; } + + int selectPointByRay(float ox, float oy, float oz, float dx, float dy, float dz, bool single) { + idVec3_t origin(ox, oy, oz); + idVec3_t dir(dx, dy, dz); + return selectPointByRay(origin, dir, single); + } + + int selectPointByRay(const idVec3_t origin, const idVec3_t direction, bool single) { + int i, besti, count; + float d, bestd; + idVec3_t temp, temp2; + + // find the point closest to the ray + besti = -1; + bestd = 8; + count = numPoints(); + + for (i=0; i < count; i++) { + temp = *getPoint(i); + temp2 = temp; + temp -= origin; + d = DotProduct(temp, direction); + __VectorMA (origin, d, direction, temp); + temp2 -= temp; + d = temp2.Length(); + if (d <= bestd) { + bestd = d; + besti = i; + } + } + + if (besti >= 0) { + selectPoint(besti, single); + } + + return besti; + } + + int isPointSelected(int index) { + int count = selectedPoints.Num(); + for (int i = 0; i < count; i++) { + if (selectedPoints[i] == index) { + return i; + } + } + return -1; + } + + int selectPoint(int index, bool single) { + if (index >= 0 && index < numPoints()) { + if (single) { + deselectAll(); + } else { + if (isPointSelected(index) >= 0) { + selectedPoints.Remove(index); + } + } + return selectedPoints.Append(index); + } + return -1; + } + + void selectAll() { + selectedPoints.Clear(); + for (int i = 0; i < numPoints(); i++) { + selectedPoints.Append(i); + } + } + + void deselectAll() { + selectedPoints.Clear(); + } + + int numSelectedPoints(); + + idVec3_t *getSelectedPoint(int index) { + assert(index >= 0 && index < numSelectedPoints()); + return getPoint(selectedPoints[index]); + } + + virtual void updateSelection(float x, float y, float z) { + idVec3_t move(x, y, z); + updateSelection(move); + } + + virtual void updateSelection(const idVec3_t &move) { + int count = selectedPoints.Num(); + for (int i = 0; i < count; i++) { + *getPoint(selectedPoints[i]) += move; + } + } + + void drawSelection() { + int count = selectedPoints.Num(); + for (int i = 0; i < count; i++) { + glBox(red, *getPoint(selectedPoints[i]), 4); + } + } + +protected: + idList selectedPoints; + +}; + + +class idSplineList { + +public: + + idSplineList() { + clear(); + } + + idSplineList(const char *p) { + clear(); + name = p; + }; + + ~idSplineList() { + clear(); + }; + + void clearControl() { + for (int i = 0; i < controlPoints.Num(); i++) { + delete controlPoints[i]; + } + controlPoints.Clear(); + } + + void clearSpline() { + for (int i = 0; i < splinePoints.Num(); i++) { + delete splinePoints[i]; + } + splinePoints.Clear(); + } + + void parse(const char *(*text)); + void write(fileHandle_t file, const char *name); + + void clear() { + clearControl(); + clearSpline(); + splineTime.Clear(); + selected = NULL; + dirty = true; + activeSegment = 0; + granularity = 0.025; + pathColor.set(1.0, 0.5, 0.0); + controlColor.set(0.7, 0.0, 1.0); + segmentColor.set(0.0, 0.0, 1.0); + activeColor.set(1.0, 0.0, 0.0); + } + + void initPosition(long startTime, long totalTime); + const idVec3_t *getPosition(long time); + + + void draw(bool editMode); + void addToRenderer(); + + void setSelectedPoint(idVec3_t *p); + idVec3_t *getSelectedPoint() { + return selected; + } + + void addPoint(const idVec3_t &v) { + controlPoints.Append(new idVec3_t(v)); + dirty = true; + } + + void addPoint(float x, float y, float z) { + controlPoints.Append(new idVec3_t(x, y, z)); + dirty = true; + } + + void updateSelection(const idVec3_t &move); + + void startEdit() { + editMode = true; + } + + void stopEdit() { + editMode = false; + } + + void buildSpline(); + + void setGranularity(float f) { + granularity = f; + } + + float getGranularity() { + return granularity; + } + + int numPoints() { + return controlPoints.Num(); + } + + idVec3_t *getPoint(int index) { + assert(index >= 0 && index < controlPoints.Num()); + return controlPoints[index]; + } + + idVec3_t *getSegmentPoint(int index) { + assert(index >= 0 && index < splinePoints.Num()); + return splinePoints[index]; + } + + + void setSegmentTime(int index, int time) { + assert(index >= 0 && index < splinePoints.Num()); + splineTime[index] = time; + } + + double getSegmentTime(int index) { + assert(index >= 0 && index < splinePoints.Num()); + return splineTime[index]; + } + void addSegmentTime(int index, int time) { + assert(index >= 0 && index < splinePoints.Num()); + splineTime[index] += time; + } + + float totalDistance(); + + static idVec3_t zero; + + int getActiveSegment() { + return activeSegment; + } + + void setActiveSegment(int i) { + //assert(i >= 0 && (splinePoints.Num() > 0 && i < splinePoints.Num())); + activeSegment = i; + } + + int numSegments() { + return splinePoints.Num(); + } + + void setColors(idVec3_t &path, idVec3_t &segment, idVec3_t &control, idVec3_t &active) { + pathColor = path; + segmentColor = segment; + controlColor = control; + activeColor = active; + } + + const char *getName() { + return name.c_str(); + } + + void setName(const char *p) { + name = p; + } + + bool validTime() { + if (dirty) { + buildSpline(); + } + // gcc doesn't allow static casting away from bools + // why? I've no idea... + return (bool)(splineTime.Num() > 0 && splineTime.Num() == splinePoints.Num()); + } + + void setTime(long t) { + time = t; + } + + void setBaseTime(long t) { + baseTime = t; + } + +protected: + idStr name; + float calcSpline(int step, float tension); + idList controlPoints; + idList splinePoints; + idList splineTime; + idVec3_t *selected; + idVec3_t pathColor, segmentColor, controlColor, activeColor; + float granularity; + bool editMode; + bool dirty; + int activeSegment; + long baseTime; + long time; + friend class idCamera; +}; + +// time in milliseconds +// velocity where 1.0 equal rough walking speed +struct idVelocity { + idVelocity(long start, long duration, float s) { + startTime = start; + time = duration; + speed = s; + } + long startTime; + long time; + float speed; +}; + +// can either be a look at or origin position for a camera +// +class idCameraPosition : public idPointListInterface { +public: + + virtual void clear() { + editMode = false; + for (int i = 0; i < velocities.Num(); i++) { + delete velocities[i]; + velocities[i] = NULL; + } + velocities.Clear(); + } + + idCameraPosition(const char *p) { + name = p; + } + + idCameraPosition() { + time = 0; + name = "position"; + } + + idCameraPosition(long t) { + time = t; + } + + virtual ~idCameraPosition() { + clear(); + } + + + // this can be done with RTTI syntax but i like the derived classes setting a type + // makes serialization a bit easier to see + // + enum positionType { + FIXED = 0x00, + INTERPOLATED, + SPLINE, + POSITION_COUNT + }; + + + virtual void start(long t) { + startTime = t; + } + + long getTime() { + return time; + } + + virtual void setTime(long t) { + time = t; + } + + float getVelocity(long t) { + long check = t - startTime; + for (int i = 0; i < velocities.Num(); i++) { + if (check >= velocities[i]->startTime && check <= velocities[i]->startTime + velocities[i]->time) { + return velocities[i]->speed; + } + } + return baseVelocity; + } + + void addVelocity(long start, long duration, float speed) { + velocities.Append(new idVelocity(start, duration, speed)); + } + + virtual const idVec3_t *getPosition(long t) { + assert(true); + return NULL; + } + + virtual void draw(bool editMode) {}; + + virtual void parse(const char *(*text)) {}; + virtual void write(fileHandle_t file, const char *name); + virtual bool parseToken(const char *key, const char *(*text)); + + const char *getName() { + return name.c_str(); + } + + void setName(const char *p) { + name = p; + } + + virtual void startEdit() { + editMode = true; + } + + virtual void stopEdit() { + editMode = false; + } + + virtual void draw() {}; + + const char *typeStr() { + return positionStr[static_cast(type)]; + } + + void calcVelocity(float distance) { + float secs = (float)time / 1000; + baseVelocity = distance / secs; + } + +protected: + static const char* positionStr[POSITION_COUNT]; + long startTime; + long time; + idCameraPosition::positionType type; + idStr name; + bool editMode; + idList velocities; + float baseVelocity; +}; + +class idFixedPosition : public idCameraPosition { +public: + + void init() { + pos.Zero(); + type = idCameraPosition::FIXED; + } + + idFixedPosition() : idCameraPosition() { + init(); + } + + idFixedPosition(idVec3_t p) : idCameraPosition() { + init(); + pos = p; + } + + virtual void addPoint(const idVec3_t &v) { + pos = v; + } + + virtual void addPoint(const float x, const float y, const float z) { + pos.set(x, y, z); + } + + + ~idFixedPosition() { + } + + virtual const idVec3_t *getPosition(long t) { + return &pos; + } + + void parse(const char *(*text)); + void write(fileHandle_t file, const char *name); + + virtual int numPoints() { + return 1; + } + + virtual idVec3_t *getPoint(int index) { + if (index != 0) { + assert(true); + }; + return &pos; + } + + virtual void draw(bool editMode) { + glLabeledPoint(blue, pos, (editMode) ? 5 : 3, "Fixed point"); + } + +protected: + idVec3_t pos; +}; + +class idInterpolatedPosition : public idCameraPosition { +public: + + void init() { + type = idCameraPosition::INTERPOLATED; + first = true; + startPos.Zero(); + endPos.Zero(); + } + + idInterpolatedPosition() : idCameraPosition() { + init(); + } + + idInterpolatedPosition(idVec3_t start, idVec3_t end, long time) : idCameraPosition(time) { + init(); + startPos = start; + endPos = end; + } + + ~idInterpolatedPosition() { + } + + virtual const idVec3_t *getPosition(long t); + + void parse(const char *(*text)); + void write(fileHandle_t file, const char *name); + + virtual int numPoints() { + return 2; + } + + virtual idVec3_t *getPoint(int index) { + assert(index >= 0 && index < 2); + if (index == 0) { + return &startPos; + } + return &endPos; + } + + virtual void addPoint(const float x, const float y, const float z) { + if (first) { + startPos.set(x, y, z); + first = false; + } else { + endPos.set(x, y, z); + first = true; + } + } + + virtual void addPoint(const idVec3_t &v) { + if (first) { + startPos = v; + first = false; + } else { + endPos = v; + first = true; + } + } + + virtual void draw(bool editMode) { + glLabeledPoint(blue, startPos, (editMode) ? 5 : 3, "Start interpolated"); + glLabeledPoint(blue, endPos, (editMode) ? 5 : 3, "End interpolated"); + qglBegin(GL_LINES); + qglVertex3fv(startPos); + qglVertex3fv(endPos); + qglEnd(); + } + + virtual void start(long t) { + idCameraPosition::start(t); + lastTime = startTime; + distSoFar = 0.0; + idVec3_t temp = startPos; + temp -= endPos; + calcVelocity(temp.Length()); + } + +protected: + bool first; + idVec3_t startPos; + idVec3_t endPos; + long lastTime; + float distSoFar; +}; + +class idSplinePosition : public idCameraPosition { +public: + + void init() { + type = idCameraPosition::SPLINE; + } + + idSplinePosition() : idCameraPosition() { + init(); + } + + idSplinePosition(long time) : idCameraPosition(time) { + init(); + } + + ~idSplinePosition() { + } + + virtual void start(long t) { + idCameraPosition::start(t); + target.initPosition(t, time); + calcVelocity(target.totalDistance()); + } + + virtual const idVec3_t *getPosition(long t) { + return target.getPosition(t); + } + + //virtual const idVec3_t *getPosition(long t) const { + + void addControlPoint(idVec3_t &v) { + target.addPoint(v); + } + + void parse(const char *(*text)); + void write(fileHandle_t file, const char *name); + + virtual int numPoints() { + return target.numPoints(); + } + + virtual idVec3_t *getPoint(int index) { + return target.getPoint(index); + } + + virtual void addPoint(const idVec3_t &v) { + target.addPoint(v); + } + + virtual void addPoint(const float x, const float y, const float z) { + target.addPoint(x, y, z); + } + + virtual void draw(bool editMode) { + target.draw(editMode); + } + + virtual void updateSelection(const idVec3_t &move) { + idCameraPosition::updateSelection(move); + target.buildSpline(); + } + +protected: + idSplineList target; +}; + +class idCameraFOV { +public: + + idCameraFOV() { + time = 0; + fov = 90; + } + + idCameraFOV(int v) { + time = 0; + fov = v; + } + + idCameraFOV(int s, int e, long t) { + startFOV = s; + endFOV = e; + time = t; + } + + + ~idCameraFOV(){} + + void setFOV(float f) { + fov = f; + } + + float getFOV(long t) { + if (time) { + assert(startTime); + float percent = t / startTime; + float temp = startFOV - endFOV; + temp *= percent; + fov = startFOV + temp; + } + return fov; + } + + void start(long t) { + startTime = t; + } + + void parse(const char *(*text)); + void write(fileHandle_t file, const char *name); + +protected: + float fov; + float startFOV; + float endFOV; + int startTime; + int time; +}; + + + + +class idCameraEvent { +public: + enum eventType { + EVENT_NA = 0x00, + EVENT_WAIT, + EVENT_TARGETWAIT, + EVENT_SPEED, + EVENT_TARGET, + EVENT_SNAPTARGET, + EVENT_FOV, + EVENT_SCRIPT, + EVENT_TRIGGER, + EVENT_STOP, + EVENT_COUNT + }; + + static const char* eventStr[EVENT_COUNT]; + + idCameraEvent() { + paramStr = ""; + type = EVENT_NA; + time = 0; + } + + idCameraEvent(eventType t, const char *param, long n) { + type = t; + paramStr = param; + time = n; + } + + ~idCameraEvent() {}; + + eventType getType() { + return type; + } + + const char *typeStr() { + return eventStr[static_cast(type)]; + } + + const char *getParam() { + return paramStr.c_str(); + } + + long getTime() { + return time; + } + + void setTime(long n) { + time = n; + } + + void parse(const char *(*text)); + void write(fileHandle_t file, const char *name); + + void setTriggered(bool b) { + triggered = b; + } + + bool getTriggered() { + return triggered; + } + +protected: + eventType type; + idStr paramStr; + long time; + bool triggered; + +}; + +class idCameraDef { +public: + + void clear() { + currentCameraPosition = 0; + cameraRunning = false; + lastDirection.Zero(); + baseTime = 30; + activeTarget = 0; + name = "camera01"; + fov.setFOV(90); + int i; + for (i = 0; i < targetPositions.Num(); i++) { + delete targetPositions[i]; + } + for (i = 0; i < events.Num(); i++) { + delete events[i]; + } + delete cameraPosition; + cameraPosition = NULL; + events.Clear(); + targetPositions.Clear(); + } + + idCameraPosition *startNewCamera(idCameraPosition::positionType type) { + clear(); + if (type == idCameraPosition::SPLINE) { + cameraPosition = new idSplinePosition(); + } else if (type == idCameraPosition::INTERPOLATED) { + cameraPosition = new idInterpolatedPosition(); + } else { + cameraPosition = new idFixedPosition(); + } + return cameraPosition; + } + + idCameraDef() { + clear(); + } + + ~idCameraDef() { + clear(); + } + + void addEvent(idCameraEvent::eventType t, const char *param, long time); + + void addEvent(idCameraEvent *event); + + static int sortEvents(const void *p1, const void *p2); + + int numEvents() { + return events.Num(); + } + + idCameraEvent *getEvent(int index) { + assert(index >= 0 && index < events.Num()); + return events[index]; + } + + void parse(const char *(*text)); + qboolean load(const char *filename); + void save(const char *filename); + + void buildCamera(); + + //idSplineList *getcameraPosition() { + // return &cameraPosition; + //} + + static idCameraPosition *newFromType(idCameraPosition::positionType t) { + switch (t) { + case idCameraPosition::FIXED : return new idFixedPosition(); + case idCameraPosition::INTERPOLATED : return new idInterpolatedPosition(); + case idCameraPosition::SPLINE : return new idSplinePosition(); + default: + break; + }; + return NULL; + } + + void addTarget(const char *name, idCameraPosition::positionType type); + + idCameraPosition *getActiveTarget() { + if (targetPositions.Num() == 0) { + addTarget(NULL, idCameraPosition::FIXED); + } + return targetPositions[activeTarget]; + } + + idCameraPosition *getActiveTarget(int index) { + if (targetPositions.Num() == 0) { + addTarget(NULL, idCameraPosition::FIXED); + return targetPositions[0]; + } + return targetPositions[index]; + } + + int numTargets() { + return targetPositions.Num(); + } + + + void setActiveTargetByName(const char *name) { + for (int i = 0; i < targetPositions.Num(); i++) { + if (Q_stricmp(name, targetPositions[i]->getName()) == 0) { + setActiveTarget(i); + return; + } + } + } + + void setActiveTarget(int index) { + assert(index >= 0 && index < targetPositions.Num()); + activeTarget = index; + } + + void setRunning(bool b) { + cameraRunning = b; + } + + void setBaseTime(float f) { + baseTime = f; + } + + float getBaseTime() { + return baseTime; + } + + float getTotalTime() { + return totalTime; + } + + void startCamera(long t); + void stopCamera() { + cameraRunning = true; + } + void getActiveSegmentInfo(int segment, idVec3_t &origin, idVec3_t &direction, float *fv); + + bool getCameraInfo(long time, idVec3_t &origin, idVec3_t &direction, float *fv); + bool getCameraInfo(long time, float *origin, float *direction, float *fv) { + idVec3_t org, dir; + org[0] = origin[0]; + org[1] = origin[1]; + org[2] = origin[2]; + dir[0] = direction[0]; + dir[1] = direction[1]; + dir[2] = direction[2]; + bool b = getCameraInfo(time, org, dir, fv); + origin[0] = org[0]; + origin[1] = org[1]; + origin[2] = org[2]; + direction[0] = dir[0]; + direction[1] = dir[1]; + direction[2] = dir[2]; + return b; + } + + void draw(bool editMode) { + // gcc doesn't allow casting away from bools + // why? I've no idea... + if (cameraPosition) { + cameraPosition->draw((bool)((editMode || cameraRunning) && cameraEdit)); + int count = targetPositions.Num(); + for (int i = 0; i < count; i++) { + targetPositions[i]->draw((bool)((editMode || cameraRunning) && i == activeTarget && !cameraEdit)); + } + } + } + +/* + int numSegments() { + if (cameraEdit) { + return cameraPosition.numSegments(); + } + return getTargetSpline()->numSegments(); + } + + int getActiveSegment() { + if (cameraEdit) { + return cameraPosition.getActiveSegment(); + } + return getTargetSpline()->getActiveSegment(); + } + + void setActiveSegment(int i) { + if (cameraEdit) { + cameraPosition.setActiveSegment(i); + } else { + getTargetSpline()->setActiveSegment(i); + } + } +*/ + int numPoints() { + if (cameraEdit) { + return cameraPosition->numPoints(); + } + return getActiveTarget()->numPoints(); + } + + const idVec3_t *getPoint(int index) { + if (cameraEdit) { + return cameraPosition->getPoint(index); + } + return getActiveTarget()->getPoint(index); + } + + void stopEdit() { + editMode = false; + if (cameraEdit) { + cameraPosition->stopEdit(); + } else { + getActiveTarget()->stopEdit(); + } + } + + void startEdit(bool camera) { + cameraEdit = camera; + if (camera) { + cameraPosition->startEdit(); + for (int i = 0; i < targetPositions.Num(); i++) { + targetPositions[i]->stopEdit(); + } + } else { + getActiveTarget()->startEdit(); + cameraPosition->stopEdit(); + } + editMode = true; + } + + bool waitEvent(int index); + + const char *getName() { + return name.c_str(); + } + + void setName(const char *p) { + name = p; + } + + idCameraPosition *getPositionObj() { + if (cameraPosition == NULL) { + cameraPosition = new idFixedPosition(); + } + return cameraPosition; + } + +protected: + idStr name; + int currentCameraPosition; + idVec3_t lastDirection; + bool cameraRunning; + idCameraPosition *cameraPosition; + idList targetPositions; + idList events; + idCameraFOV fov; + int activeTarget; + float totalTime; + float baseTime; + long startTime; + + bool cameraEdit; + bool editMode; +}; + +extern bool g_splineMode; + +extern idCameraDef *g_splineList; + + +#endif diff --git a/codemp/Splines/util_list.h b/codemp/Splines/util_list.h new file mode 100644 index 0000000..156daf7 --- /dev/null +++ b/codemp/Splines/util_list.h @@ -0,0 +1,325 @@ +#ifndef __UTIL_LIST_H__ +#define __UTIL_LIST_H__ + +#include +#include + +template< class type > +class idList { +private: + int m_num; + int m_size; + int m_granularity; + type *m_list; + +public: + idList( int granularity = 16 ); + ~idList(); + void Clear( void ); + int Num( void ); + void SetNum( int num ); + void SetGranularity( int granularity ); + void Condense( void ); + int Size( void ); + void Resize( int size ); + type operator[]( int index ) const; + type &operator[]( int index ); + int Append( type const & obj ); + int AddUnique( type const & obj ); + type *Find( type const & obj, int *index = NULL ); + bool RemoveIndex( int index ); + bool Remove( type const & obj ); + typedef int cmp_t(const void *, const void *); + void Sort( cmp_t *compare ); +}; + +/* +================ +idList::idList( int ) +================ +*/ +template< class type > +inline idList::idList( int granularity ) { + assert( granularity > 0 ); + + m_list = NULL; + m_granularity = granularity; + Clear(); +} + +/* +================ +idList::~idList +================ +*/ +template< class type > +inline idList::~idList() { + Clear(); +} + +/* +================ +idList::Clear +================ +*/ +template< class type > +inline void idList::Clear( void ) { + if ( m_list ) { + delete[] m_list; + } + + m_list = NULL; + m_num = 0; + m_size = 0; +} + +/* +================ +idList::Num +================ +*/ +template< class type > +inline int idList::Num( void ) { + return m_num; +} + +/* +================ +idList::SetNum +================ +*/ +template< class type > +inline void idList::SetNum( int num ) { + assert( num >= 0 ); + if ( num > m_size ) { + // resize it up to the closest level of granularity + Resize( ( ( num + m_granularity - 1 ) / m_granularity ) * m_granularity ); + } + m_num = num; +} + +/* +================ +idList::SetGranularity +================ +*/ +template< class type > +inline void idList::SetGranularity( int granularity ) { + int newsize; + + assert( granularity > 0 ); + m_granularity = granularity; + + if ( m_list ) { + // resize it to the closest level of granularity + newsize = ( ( m_num + m_granularity - 1 ) / m_granularity ) * m_granularity; + if ( newsize != m_size ) { + Resize( newsize ); + } + } +} + +/* +================ +idList::Condense + +Resizes the array to exactly the number of elements it contains +================ +*/ +template< class type > +inline void idList::Condense( void ) { + if ( m_list ) { + if ( m_num ) { + Resize( m_num ); + } else { + Clear(); + } + } +} + +/* +================ +idList::Size +================ +*/ +template< class type > +inline int idList::Size( void ) { + return m_size; +} + +/* +================ +idList::Resize +================ +*/ +template< class type > +inline void idList::Resize( int size ) { + type *temp; + int i; + + assert( size > 0 ); + + if ( size <= 0 ) { + Clear(); + return; + } + + temp = m_list; + m_size = size; + if ( m_size < m_num ) { + m_num = m_size; + } + + m_list = new type[ m_size ]; + for( i = 0; i < m_num; i++ ) { + m_list[ i ] = temp[ i ]; + } + + if ( temp ) { + delete[] temp; + } +} + +/* +================ +idList::operator[] const +================ +*/ +template< class type > +inline type idList::operator[]( int index ) const { + assert( index >= 0 ); + assert( index < m_num ); + + return m_list[ index ]; +} + +/* +================ +idList::operator[] +================ +*/ +template< class type > +inline type &idList::operator[]( int index ) { + assert( index >= 0 ); + assert( index < m_num ); + + return m_list[ index ]; +} + +/* +================ +idList::Append +================ +*/ +template< class type > +inline int idList::Append( type const & obj ) { + if ( !m_list ) { + Resize( m_granularity ); + } + + if ( m_num == m_size ) { + Resize( m_size + m_granularity ); + } + + m_list[ m_num ] = obj; + m_num++; + + return m_num - 1; +} + +/* +================ +idList::AddUnique +================ +*/ +template< class type > +inline int idList::AddUnique( type const & obj ) { + int index; + + if ( !Find( obj, &index ) ) { + index = Append( obj ); + } + + return index; +} + +/* +================ +idList::Find +================ +*/ +template< class type > +inline type *idList::Find( type const & obj, int *index ) { + int i; + + for( i = 0; i < m_num; i++ ) { + if ( m_list[ i ] == obj ) { + if ( index ) { + *index = i; + } + return &m_list[ i ]; + } + } + + return NULL; +} + +/* +================ +idList::RemoveIndex +================ +*/ +template< class type > +inline bool idList::RemoveIndex( int index ) { + int i; + + if ( !m_list || !m_num ) { + return false; + } + + assert( index >= 0 ); + assert( index < m_num ); + + if ( ( index < 0 ) || ( index >= m_num ) ) { + return false; + } + + m_num--; + for( i = index; i < m_num; i++ ) { + m_list[ i ] = m_list[ i + 1 ]; + } + + return true; +} + +/* +================ +idList::Remove +================ +*/ +template< class type > +inline bool idList::Remove( type const & obj ) { + int index; + + if ( Find( obj, &index ) ) { + return RemoveIndex( index ); + } + + return false; +} + +/* +================ +idList::Sort +================ +*/ +template< class type > +inline void idList::Sort( cmp_t *compare ) { + if ( !m_list ) { + return; + } + + qsort( ( void * )m_list, ( size_t )m_num, sizeof( type ), compare ); +} + +#endif /* !__UTIL_LIST_H__ */ diff --git a/codemp/Splines/util_str.cpp b/codemp/Splines/util_str.cpp new file mode 100644 index 0000000..d5a8954 --- /dev/null +++ b/codemp/Splines/util_str.cpp @@ -0,0 +1,598 @@ +//need to rewrite this + +#include "util_str.h" +#include +#include +#include +#include + +#ifdef _WIN32 +#pragma warning(disable : 4244) // 'conversion' conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable : 4710) // function 'blah' not inlined +#endif + +static const int STR_ALLOC_GRAN = 20; + +char *idStr::tolower + ( + char *s1 + ) + + { + char *s; + + s = s1; + while( *s ) + { + *s = ::tolower( *s ); + s++; + } + + return s1; + } + +char *idStr::toupper + ( + char *s1 + ) + + { + char *s; + + s = s1; + while( *s ) + { + *s = ::toupper( *s ); + s++; + } + + return s1; + } + +int idStr::icmpn + ( + const char *s1, + const char *s2, + int n + ) + + { + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) + { + // idStrings are equal until end point + return 0; + } + + if ( c1 != c2 ) + { + if ( c1 >= 'a' && c1 <= 'z' ) + { + c1 -= ( 'a' - 'A' ); + } + + if ( c2 >= 'a' && c2 <= 'z' ) + { + c2 -= ( 'a' - 'A' ); + } + + if ( c1 < c2 ) + { + // strings less than + return -1; + } + else if ( c1 > c2 ) + { + // strings greater than + return 1; + } + } + } + while( c1 ); + + // strings are equal + return 0; + } + +int idStr::icmp + ( + const char *s1, + const char *s2 + ) + + { + int c1; + int 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 ) + { + // strings less than + return -1; + } + else if ( c1 > c2 ) + { + // strings greater than + return 1; + } + } + } + while( c1 ); + + // strings are equal + return 0; + } + +int idStr::cmpn + ( + const char *s1, + const char *s2, + int n + ) + + { + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) + { + // strings are equal until end point + return 0; + } + + if ( c1 < c2 ) + { + // strings less than + return -1; + } + else if ( c1 > c2 ) + { + // strings greater than + return 1; + } + } + while( c1 ); + + // strings are equal + return 0; + } + +int idStr::cmp + ( + const char *s1, + const char *s2 + ) + + { + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( c1 < c2 ) + { + // strings less than + return -1; + } + else if ( c1 > c2 ) + { + // strings greater than + return 1; + } + } + while( c1 ); + + // strings are equal + return 0; + } + +/* +============ +IsNumeric + +Checks a string to see if it contains only numerical values. +============ +*/ +bool idStr::isNumeric + ( + const char *str + ) + + { + int len; + int i; + bool dot; + + if ( *str == '-' ) + { + str++; + } + + dot = false; + len = strlen( str ); + for( i = 0; i < len; i++ ) + { + if ( !isdigit( str[ i ] ) ) + { + if ( ( str[ i ] == '.' ) && !dot ) + { + dot = true; + continue; + } + return false; + } + } + + return true; + } + +idStr operator+ + ( + const idStr& a, + const float b + ) + + { + char text[ 20 ]; + + idStr result( a ); + + sprintf( text, "%f", b ); + result.append( text ); + + return result; + } + +idStr operator+ + ( + const idStr& a, + const int b + ) + + { + char text[ 20 ]; + + idStr result( a ); + + sprintf( text, "%d", b ); + result.append( text ); + + return result; + } + +idStr operator+ + ( + const idStr& a, + const unsigned b + ) + + { + char text[ 20 ]; + + idStr result( a ); + + sprintf( text, "%u", b ); + result.append( text ); + + return result; + } + +idStr& idStr::operator+= + ( + const float a + ) + + { + char text[ 20 ]; + + sprintf( text, "%f", a ); + append( text ); + + return *this; + } + +idStr& idStr::operator+= + ( + const int a + ) + + { + char text[ 20 ]; + + sprintf( text, "%d", a ); + append( text ); + + return *this; + } + +idStr& idStr::operator+= + ( + const unsigned a + ) + + { + char text[ 20 ]; + + sprintf( text, "%u", a ); + append( text ); + + return *this; + } + +void idStr::CapLength + ( + int newlen + ) + + { + assert ( m_data ); + + if ( length() <= newlen ) + return; + + EnsureDataWritable (); + + m_data->data[newlen] = 0; + m_data->len = newlen; + } + +void idStr::EnsureDataWritable + ( + void + ) + + { + assert ( m_data ); + strdata *olddata; + int len; + + if ( !m_data->refcount ) + return; + + olddata = m_data; + len = length(); + + m_data = new strdata; + + EnsureAlloced ( len + 1, false ); + strncpy ( m_data->data, olddata->data, len+1 ); + m_data->len = len; + + olddata->DelRef (); + } + +void idStr::EnsureAlloced (int amount, bool keepold) { + + if ( !m_data ) { + m_data = new strdata(); + } + + // Now, let's make sure it's writable + EnsureDataWritable (); + + char *newbuffer; + bool wasalloced = ( m_data->alloced != 0 ); + + if ( amount < m_data->alloced ) { + return; + } + + assert ( amount ); + if ( amount == 1 ) { + m_data->alloced = 1; + } else { + int newsize, mod; + mod = amount % STR_ALLOC_GRAN; + if ( !mod ) { + newsize = amount; + } else { + newsize = amount + STR_ALLOC_GRAN - mod; + } + m_data->alloced = newsize; + } + + newbuffer = new char[m_data->alloced]; + if ( wasalloced && keepold ) { + strcpy ( newbuffer, m_data->data ); + } + + if ( m_data->data ) { + delete [] m_data->data; + } + m_data->data = newbuffer; +} + +void idStr::BackSlashesToSlashes + ( + void + ) + + { + int i; + + EnsureDataWritable (); + + for ( i=0; i < m_data->len; i++ ) + { + if ( m_data->data[i] == '\\' ) + m_data->data[i] = '/'; + } + } + +void idStr::snprintf + ( + char *dst, + int size, + const char *fmt, + ... + ) + + { + char buffer[0x10000]; + int len; + va_list argptr; + + va_start (argptr,fmt); + len = vsprintf (buffer,fmt,argptr); + va_end (argptr); + + assert ( len < size ); + + strncpy (dst, buffer, size-1); + } + +#ifdef _WIN32 +#pragma warning(disable : 4189) // local variable is initialized but not referenced +#endif + +/* +================= +TestStringClass + +This is a fairly rigorous test of the idStr class's functionality. +Because of the fairly global and subtle ramifications of a bug occuring +in this class, it should be run after any changes to the class. +Add more tests as functionality is changed. Tests should include +any possible bounds violation and NULL data tests. +================= +*/ +void TestStringClass + ( + void + ) + + { + char ch; // ch == ? + idStr *t; // t == ? + idStr a; // a.len == 0, a.data == "\0" + idStr b; // b.len == 0, b.data == "\0" + idStr c( "test" ); // c.len == 4, c.data == "test\0" + idStr d( c ); // d.len == 4, d.data == "test\0" + idStr e( reinterpret_cast(NULL) ); + // e.len == 0, e.data == "\0" ASSERT! + int i; // i == ? + + i = a.length(); // i == 0 + i = c.length(); // i == 4 + + // TTimo: not used +// const char *s1 = a.c_str(); // s1 == "\0" +// const char *s2 = c.c_str(); // s2 == "test\0" + + t = new idStr(); // t->len == 0, t->data == "\0" + delete t; // t == ? + + b = "test"; // b.len == 4, b.data == "test\0" + t = new idStr( "test" ); // t->len == 4, t->data == "test\0" + delete t; // t == ? + + a = c; // a.len == 4, a.data == "test\0" +// a = ""; + a = NULL; // a.len == 0, a.data == "\0" ASSERT! + a = c + d; // a.len == 8, a.data == "testtest\0" + a = c + "wow"; // a.len == 7, a.data == "testwow\0" + a = c + reinterpret_cast(NULL); + // a.len == 4, a.data == "test\0" ASSERT! + a = "this" + d; // a.len == 8, a.data == "thistest\0" + a = reinterpret_cast(NULL) + d; + // a.len == 4, a.data == "test\0" ASSERT! + a += c; // a.len == 8, a.data == "testtest\0" + a += "wow"; // a.len == 11, a.data == "testtestwow\0" + a += reinterpret_cast(NULL); + // a.len == 11, a.data == "testtestwow\0" ASSERT! + + a = "test"; // a.len == 4, a.data == "test\0" + ch = a[ 0 ]; // ch == 't' + ch = a[ -1 ]; // ch == 0 ASSERT! + ch = a[ 1000 ]; // ch == 0 ASSERT! + ch = a[ 0 ]; // ch == 't' + ch = a[ 1 ]; // ch == 'e' + ch = a[ 2 ]; // ch == 's' + ch = a[ 3 ]; // ch == 't' + ch = a[ 4 ]; // ch == '\0' ASSERT! + ch = a[ 5 ]; // ch == '\0' ASSERT! + + a[ 1 ] = 'b'; // a.len == 4, a.data == "tbst\0" + a[ -1 ] = 'b'; // a.len == 4, a.data == "tbst\0" ASSERT! + a[ 0 ] = '0'; // a.len == 4, a.data == "0bst\0" + a[ 1 ] = '1'; // a.len == 4, a.data == "01st\0" + a[ 2 ] = '2'; // a.len == 4, a.data == "012t\0" + a[ 3 ] = '3'; // a.len == 4, a.data == "0123\0" + a[ 4 ] = '4'; // a.len == 4, a.data == "0123\0" ASSERT! + a[ 5 ] = '5'; // a.len == 4, a.data == "0123\0" ASSERT! + a[ 7 ] = '7'; // a.len == 4, a.data == "0123\0" ASSERT! + + a = "test"; // a.len == 4, a.data == "test\0" + b = "no"; // b.len == 2, b.data == "no\0" + + i = ( a == b ); // i == 0 + i = ( a == c ); // i == 1 + + i = ( a == "blow" ); // i == 0 + i = ( a == "test" ); // i == 1 + i = ( a == NULL ); // i == 0 ASSERT! + + i = ( "test" == b ); // i == 0 + i = ( "test" == a ); // i == 1 + i = ( NULL == a ); // i == 0 ASSERT! + + i = ( a != b ); // i == 1 + i = ( a != c ); // i == 0 + + i = ( a != "blow" ); // i == 1 + i = ( a != "test" ); // i == 0 + i = ( a != NULL ); // i == 1 ASSERT! + + i = ( "test" != b ); // i == 1 + i = ( "test" != a ); // i == 0 + i = ( NULL != a ); // i == 1 ASSERT! + + a = "test"; // a.data == "test" + b = a; // b.data == "test" + + a = "not"; // a.data == "not", b.data == "test" + + a = b; // a.data == b.data == "test" + + a += b; // a.data == "testtest", b.data = "test" + + a = b; + + a[1] = '1'; // a.data = "t1st", b.data = "test" + } + +#ifdef _WIN32 +#pragma warning(default : 4189) // local variable is initialized but not referenced +#pragma warning(disable : 4514) // unreferenced inline function has been removed +#endif diff --git a/codemp/Splines/util_str.h b/codemp/Splines/util_str.h new file mode 100644 index 0000000..e0be059 --- /dev/null +++ b/codemp/Splines/util_str.h @@ -0,0 +1,796 @@ +//need to rewrite this + +#ifndef __UTIL_STR_H__ +#define __UTIL_STR_H__ + +#include +#include +#include + +#ifdef _WIN32 +#pragma warning(disable : 4710) // function 'blah' not inlined +#endif + +void TestStringClass (); + +class strdata + { + public: + strdata () : len( 0 ), refcount ( 0 ), data ( NULL ), alloced ( 0 ) {} + ~strdata () + { + if ( data ) + delete [] data; + } + + void AddRef () { refcount++; } + bool DelRef () // True if killed + { + refcount--; + if ( refcount < 0 ) + { + delete this; + return true; + } + + return false; + } + + int len; + int refcount; + char *data; + int alloced; + }; + +class idStr { +protected: + strdata *m_data; + void EnsureAlloced ( int, bool keepold = true ); + void EnsureDataWritable (); + +public: + ~idStr(); + idStr(); + idStr( const char *text ); + idStr( const idStr& string ); + idStr( const idStr string, int start, int end ); + idStr( const char ch ); + idStr( const int num ); + idStr( const float num ); + idStr( const unsigned num ); + int length( void ) const; + int allocated( void ) const; + const char * c_str( void ) const; + + void append( const char *text ); + void append( const idStr& text ); + char operator[]( int index ) const; + char& operator[]( int index ); + + void operator=( const idStr& text ); + void operator=( const char *text ); + + friend idStr operator+( const idStr& a, const idStr& b ); + friend idStr operator+( const idStr& a, const char *b ); + friend idStr operator+( const char *a, const idStr& b ); + + friend idStr operator+( const idStr& a, const float b ); + friend idStr operator+( const idStr& a, const int b ); + friend idStr operator+( const idStr& a, const unsigned b ); + friend idStr operator+( const idStr& a, const bool b ); + friend idStr operator+( const idStr& a, const char b ); + + idStr& operator+=( const idStr& a ); + idStr& operator+=( const char *a ); + idStr& operator+=( const float a ); + idStr& operator+=( const char a ); + idStr& operator+=( const int a ); + idStr& operator+=( const unsigned a ); + idStr& operator+=( const bool a ); + + friend bool operator==( const idStr& a, const idStr& b ); + friend bool operator==( const idStr& a, const char *b ); + friend bool operator==( const char *a, const idStr& b ); + + friend bool operator!=( const idStr& a, const idStr& b ); + friend bool operator!=( const idStr& a, const char *b ); + friend bool operator!=( const char *a, const idStr& b ); + + operator const char * () const; + operator const char * (); + + int icmpn( const char *text, int n ) const; + int icmpn( const idStr& text, int n ) const; + int icmp( const char *text ) const; + int icmp( const idStr& text ) const; + int cmpn( const char *text, int n ) const; + int cmpn( const idStr& text, int n ) const; + int cmp( const char *text ) const; + int cmp( const idStr& text ) const; + + void tolower( void ); + void toupper( void ); + + static char *tolower( char *s1 ); + static char *toupper( char *s1 ); + + static int icmpn( const char *s1, const char *s2, int n ); + static int icmp( const char *s1, const char *s2 ); + static int cmpn( const char *s1, const char *s2, int n ); + static int cmp( const char *s1, const char *s2 ); + + static void snprintf ( char *dst, int size, const char *fmt, ... ); + + static bool isNumeric( const char *str ); + bool isNumeric( void ) const; + + void CapLength ( int ); + + void BackSlashesToSlashes (); + +}; + +inline idStr::~idStr() + { + if ( m_data ) + { + m_data->DelRef (); + m_data = NULL; + } + } + +inline idStr::idStr() : m_data ( NULL ) + { + EnsureAlloced ( 1 ); + m_data->data[ 0 ] = 0; + } + +inline idStr::idStr + ( + const char *text + ) : m_data ( NULL ) + + { + int len; + + assert( text ); + + if ( text ) + { + len = strlen( text ); + EnsureAlloced ( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; + } + else + { + EnsureAlloced ( 1 ); + m_data->data[ 0 ] = 0; + m_data->len = 0; + } + } + +inline idStr::idStr + ( + const idStr& text + ) : m_data ( NULL ) + + { + m_data = text.m_data; + m_data->AddRef (); + } + +inline idStr::idStr + ( + const idStr text, + int start, + int end + ) : m_data ( NULL ) + + { + int i; + int len; + + if ( end > text.length() ) + { + end = text.length(); + } + + if ( start > text.length() ) + { + start = text.length(); + } + + len = end - start; + if ( len < 0 ) + { + len = 0; + } + + EnsureAlloced ( len + 1 ); + + for( i = 0; i < len; i++ ) + { + m_data->data[ i ] = text[ start + i ]; + } + + m_data->data[ len ] = 0; + m_data->len = len; + } + +inline idStr::idStr + ( + const char ch + ) : m_data ( NULL ) + + { + EnsureAlloced ( 2 ); + + m_data->data[ 0 ] = ch; + m_data->data[ 1 ] = 0; + m_data->len = 1; + } + +inline idStr::idStr + ( + const float num + ) : m_data ( NULL ) + + { + char text[ 32 ]; + int len; + + sprintf( text, "%.3f", num ); + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; + } + +inline idStr::idStr + ( + const int num + ) : m_data ( NULL ) + + { + char text[ 32 ]; + int len; + + sprintf( text, "%d", num ); + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; + } + +inline idStr::idStr + ( + const unsigned num + ) : m_data ( NULL ) + + { + char text[ 32 ]; + int len; + + sprintf( text, "%u", num ); + len = strlen( text ); + EnsureAlloced( len + 1 ); + strcpy( m_data->data, text ); + m_data->len = len; + } + +inline int idStr::length( void ) const + { + return ( m_data != NULL ) ? m_data->len : 0; + } + +inline int idStr::allocated( void ) const + { + return ( m_data != NULL ) ? m_data->alloced + sizeof( *m_data ) : 0; + } + +inline const char *idStr::c_str( void ) const + { + assert( m_data ); + + return m_data->data; + } + +inline void idStr::append + ( + const char *text + ) + + { + int len; + + assert( text ); + + if ( text ) + { + len = length() + strlen( text ); + EnsureAlloced( len + 1 ); + + strcat( m_data->data, text ); + m_data->len = len; + } + } + +inline void idStr::append + ( + const idStr& text + ) + + { + int len; + + len = length() + text.length(); + EnsureAlloced ( len + 1 ); + + strcat ( m_data->data, text.c_str () ); + m_data->len = len; + } + +inline char idStr::operator[]( int index ) const + { + assert ( m_data ); + + if ( !m_data ) + return 0; + + // don't include the '/0' in the test, because technically, it's out of bounds + assert( ( index >= 0 ) && ( index < m_data->len ) ); + + // In release mode, give them a null character + // don't include the '/0' in the test, because technically, it's out of bounds + if ( ( index < 0 ) || ( index >= m_data->len ) ) + { + return 0; + } + + return m_data->data[ index ]; + } + +inline char& idStr::operator[] + ( + int index + ) + + { + // Used for result for invalid indices + static char dummy = 0; + assert ( m_data ); + + // We don't know if they'll write to it or not + // if it's not a const object + EnsureDataWritable (); + + if ( !m_data ) + return dummy; + + // don't include the '/0' in the test, because technically, it's out of bounds + assert( ( index >= 0 ) && ( index < m_data->len ) ); + + // In release mode, let them change a safe variable + // don't include the '/0' in the test, because technically, it's out of bounds + if ( ( index < 0 ) || ( index >= m_data->len ) ) + { + return dummy; + } + + return m_data->data[ index ]; + } + +inline void idStr::operator= + ( + const idStr& text + ) + + { + // adding the reference before deleting our current reference prevents + // us from deleting our string if we are copying from ourself + text.m_data->AddRef(); + m_data->DelRef(); + m_data = text.m_data; + } + +inline void idStr::operator= + ( + const char *text + ) + + { + int len; + + assert( text ); + + if ( !text ) + { + // safe behaviour if NULL + EnsureAlloced ( 1, false ); + m_data->data[0] = 0; + m_data->len = 0; + return; + } + + if ( !m_data ) + { + len = strlen ( text ); + EnsureAlloced( len + 1, false ); + strcpy ( m_data->data, text ); + m_data->len = len; + return; + } + + if ( text == m_data->data ) + return; // Copying same thing. Punt. + + // If we alias and I don't do this, I could corrupt other strings... This + // will get called with EnsureAlloced anyway + EnsureDataWritable (); + + // Now we need to check if we're aliasing.. + if ( text >= m_data->data && text <= m_data->data + m_data->len ) + { + // Great, we're aliasing. We're copying from inside ourselves. + // This means that I don't have to ensure that anything is alloced, + // though I'll assert just in case. + int diff = text - m_data->data; + int i; + + assert ( strlen ( text ) < (unsigned) m_data->len ); + + for ( i = 0; text[i]; i++ ) + { + m_data->data[i] = text[i]; + } + + m_data->data[i] = 0; + + m_data->len -= diff; + + return; + } + + len = strlen( text ); + EnsureAlloced ( len + 1, false ); + strcpy( m_data->data, text ); + m_data->len = len; + } + +inline idStr operator+ + ( + const idStr& a, + const idStr& b + ) + + { + idStr result( a ); + + result.append( b ); + + return result; + } + +inline idStr operator+ + ( + const idStr& a, + const char *b + ) + + { + idStr result( a ); + + result.append( b ); + + return result; + } + +inline idStr operator+ + ( + const char *a, + const idStr& b + ) + + { + idStr result( a ); + + result.append( b ); + + return result; + } + +inline idStr operator+ + ( + const idStr& a, + const bool b + ) + + { + idStr result( a ); + + result.append( b ? "true" : "false" ); + + return result; + } + +inline idStr operator+ + ( + const idStr& a, + const char b + ) + + { + char text[ 2 ]; + + text[ 0 ] = b; + text[ 1 ] = 0; + + return a + text; + } + +inline idStr& idStr::operator+= + ( + const idStr& a + ) + + { + append( a ); + return *this; + } + +inline idStr& idStr::operator+= + ( + const char *a + ) + + { + append( a ); + return *this; + } + +inline idStr& idStr::operator+= + ( + const char a + ) + + { + char text[ 2 ]; + + text[ 0 ] = a; + text[ 1 ] = 0; + append( text ); + + return *this; + } + +inline idStr& idStr::operator+= + ( + const bool a + ) + + { + append( a ? "true" : "false" ); + return *this; + } + +inline bool operator== + ( + const idStr& a, + const idStr& b + ) + + { + return ( !strcmp( a.c_str(), b.c_str() ) ); + } + +inline bool operator== + ( + const idStr& a, + const char *b + ) + + { + assert( b ); + if ( !b ) + { + return false; + } + return ( !strcmp( a.c_str(), b ) ); + } + +inline bool operator== + ( + const char *a, + const idStr& b + ) + + { + assert( a ); + if ( !a ) + { + return false; + } + return ( !strcmp( a, b.c_str() ) ); + } + +inline bool operator!= + ( + const idStr& a, + const idStr& b + ) + + { + return !( a == b ); + } + +inline bool operator!= + ( + const idStr& a, + const char *b + ) + + { + return !( a == b ); + } + +inline bool operator!= + ( + const char *a, + const idStr& b + ) + + { + return !( a == b ); + } + +inline int idStr::icmpn + ( + const char *text, + int n + ) const + + { + assert( m_data ); + assert( text ); + + return idStr::icmpn( m_data->data, text, n ); + } + +inline int idStr::icmpn + ( + const idStr& text, + int n + ) const + + { + assert( m_data ); + assert( text.m_data ); + + return idStr::icmpn( m_data->data, text.m_data->data, n ); + } + +inline int idStr::icmp + ( + const char *text + ) const + + { + assert( m_data ); + assert( text ); + + return idStr::icmp( m_data->data, text ); + } + +inline int idStr::icmp + ( + const idStr& text + ) const + + { + assert( c_str () ); + assert( text.c_str () ); + + return idStr::icmp( c_str () , text.c_str () ); + } + +inline int idStr::cmp + ( + const char *text + ) const + + { + assert( m_data ); + assert( text ); + + return idStr::cmp( m_data->data, text ); + } + +inline int idStr::cmp + ( + const idStr& text + ) const + + { + assert( c_str () ); + assert( text.c_str () ); + + return idStr::cmp( c_str () , text.c_str () ); + } + +inline int idStr::cmpn + ( + const char *text, + int n + ) const + + { + assert( c_str () ); + assert( text ); + + return idStr::cmpn( c_str () , text, n ); + } + +inline int idStr::cmpn + ( + const idStr& text, + int n + ) const + + { + assert( c_str () ); + assert( text.c_str () ); + + return idStr::cmpn( c_str () , text.c_str () , n ); + } + +inline void idStr::tolower + ( + void + ) + + { + assert( m_data ); + + EnsureDataWritable (); + + idStr::tolower( m_data->data ); + } + +inline void idStr::toupper + ( + void + ) + + { + assert( m_data ); + + EnsureDataWritable (); + + idStr::toupper( m_data->data ); + } + +inline bool idStr::isNumeric + ( + void + ) const + + { + assert( m_data ); + return idStr::isNumeric( m_data->data ); + } + +inline idStr::operator const char *() { + return c_str(); +} + +inline idStr::operator const char * + ( + void + ) const + + { + return c_str (); + } + +#endif \ No newline at end of file diff --git a/codemp/WinDed.vcproj b/codemp/WinDed.vcproj new file mode 100644 index 0000000..983fe13 --- /dev/null +++ b/codemp/WinDed.vcproj @@ -0,0 +1,901 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/alut.lib b/codemp/alut.lib new file mode 100644 index 0000000000000000000000000000000000000000..67cde9f676b7dbead6771de243a29cdf1b73f6be GIT binary patch literal 4702 zcmds5e{5Sv9lvL%ju*1p9hTO1fx78q+E|u3NlDkH5a*ZIcFvl`t|PRxiQ_ys&YU=N zo=Y}`X~i+rx&{SUrHMZf5=EL;iT@bwK;Wcl(`g`eAS49;FeZ(q-B6`a)`67p`QE)} z`y~r>{K;8&?|a|--1qLj_xZj%zugC;>Db6usvh%NQ-jaf+}P02ylZt9ePfoL+LcTFX9CZxmTrW8y?hx^(h zfkaZ1O@^Xd8%w31m#SmqNr_^g-dByL69=mlIP1JkIE*Qu@cX~;&pI;wH!d}pRPqj1 z)%W*mNi905^|u8xdVOkW6#h0zXE3g4?{OsfJAb9E2O|aDL`)uh+FH-2~z3MSj7~*!Phs zRUmuaJ%jbcRiTl^Ry-CAjH&j!eFbCOfT?psxDMQ(4J>iU@7_4B5BeLx?4$vawjj%K z#X2nZg240}xXtYKV6AarUUvv{Y+KQubsaR$V5P*ErCi5GoOYME(YwHrJ5ryB+qm8| zaNjVno2|EihO@u~!4*p6p3VHH12bdbHnT_j_HAH(P=?#g-mAb|EW^?MKGxY4Xgd}R z2M@O&sLf;&!}ZC;P_1_pH4(FiNfU>=4)ph=Q=_2yWdG3#ZM-emmyS+MXzBhGYSCo> z@Z{GFg=}tW6alv(`W|PWbu*aW!m45aN3~Rj_w4RY#L}tBR9yEy)cdga=!BjaOT3U6 zKk4mAjZNS+nr3VSNcKJzqpU|fWtm-cU!FrEuic02z>hH(vWHPbQ1;-**u6rsyvU(p ztVWbLZHy<)sggzd#31}u#2A#{5rVABMvOghLX18)N9=h3*~31Aa;K0=qf&i`(0NgA z7ZM8gMJhzHiQ(^uT*-K+>f}My&BLlE`@!H6!&`@xQ^$Bxoro-Ti5^R|WVU3hjWBjq zv?p@~*UT!^Bh(`O!*695owk!gtU(8&=(yXcTVa+gcV}xo;A9)D63JhF#c<@9$UD zOrWVi!h;!CeE+oC=sH)}LcxFY-eT28>kF$>{H!_yJE6P{we47OU3TGCUg=$aPgt** zossRv(t@fQ%FciAwA_>IV$JdraG0?B4Llila$C!_%#fIcD|dlZFRL%nqf_xg-8%g#n}Trz-f1&iu4m z$4^z~b=Bv}y^9GT2(r`agG#2Bo{dhft5aqu_QM{11=^p+Tzr*MG?fGTf%8&WA>^t_A^ZbBG z>3^+UfJ1AD*1q+F?7}S1z~Uu(z4F_$)9PMW2&z4~H$mt#PWw&%)w2E z^^wc=>*9@)y9kaBoOq^;ni&|8Y+HpS`J}?(l20*6l0|4_Z=fa_uv0Lg89K>;TtTmt zQT)}*^w;4avVbr;FDcQvOQ{99lGVrQS}xNnBK%~<|9rTKD`m{Eg*#LXhgE%IIK0Ql z=-pw*jjwbw!~&RIq1dyg@>?Mh)vnB00jQPV2ZA#U3h)6VHXH5W>DM3|$UFt8J!)U;z4rsew;p%k}1D_iJRX1DBxbHiQ`1MNxy zHi&J1JTrHtv=?=T<*j0d<-1HTDXZnJ8!QtCN|=?u?Eo!`x%Z!}76+ zBPBbi;O#?uMF)uG>p-kK`mENG8mIqw9hr1m8yBAsVjb|cZZ=St7+s`0wNr_hW`5AP z%TE!p4x*#cl`M5?lX^P!yfLaU{)DC*)yXm-Y2}>Cuo=DldxrUKfraction) + { + Com_Memcpy(trace, &enttrace, sizeof(bsp_trace_t)); + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_EntityCollision +//=========================================================================== +// returns true if in Potentially Hearable Set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_inPVS(vec3_t p1, vec3_t p2) +{ + return (qboolean)botimport.inPVS(p1, p2); +} //end of the function AAS_InPVS +//=========================================================================== +// returns true if in Potentially Visible Set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_inPHS(vec3_t p1, vec3_t p2) +{ + return qtrue; +} //end of the function AAS_inPHS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin) +{ + botimport.BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); +} //end of the function AAS_BSPModelMinsMaxs +//=========================================================================== +// unlinks the entity from all leaves +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves) +{ +} //end of the function AAS_UnlinkFromBSPLeaves +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum, int modelnum) +{ + return NULL; +} //end of the function AAS_BSPLinkEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount) +{ + return 0; +} //end of the function AAS_BoxEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextBSPEntity(int ent) +{ + ent++; + if (ent >= 1 && ent < bspworld.numentities) return ent; + return 0; +} //end of the function AAS_NextBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BSPEntityInRange(int ent) +{ + if (ent <= 0 || ent >= bspworld.numentities) + { + botimport.Print(PRT_MESSAGE, "bsp entity out of range\n"); + return qfalse; + } //end if + return qtrue; +} //end of the function AAS_BSPEntityInRange +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size) +{ + bsp_epair_t *epair; + + value[0] = '\0'; + if (!AAS_BSPEntityInRange(ent)) return qfalse; + for (epair = bspworld.entities[ent].epairs; epair; epair = epair->next) + { + if (!strcmp(epair->key, key)) + { + strncpy(value, epair->value, size-1); + value[size-1] = '\0'; + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_FindBSPEpair +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v) +{ + char buf[MAX_EPAIRKEY]; + double v1, v2, v3; + + VectorClear(v); + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; + //scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf(buf, "%lf %lf %lf", &v1, &v2, &v3); + v[0] = v1; + v[1] = v2; + v[2] = v3; + return qtrue; +} //end of the function AAS_VectorForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloatForBSPEpairKey(int ent, char *key, float *value) +{ + char buf[MAX_EPAIRKEY]; + + *value = 0; + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; + *value = atof(buf); + return qtrue; +} //end of the function AAS_FloatForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IntForBSPEpairKey(int ent, char *key, int *value) +{ + char buf[MAX_EPAIRKEY]; + + *value = 0; + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; + *value = atoi(buf); + return qtrue; +} //end of the function AAS_IntForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeBSPEntities(void) +{ + int i; + bsp_entity_t *ent; + bsp_epair_t *epair, *nextepair; + + for (i = 1; i < bspworld.numentities; i++) + { + ent = &bspworld.entities[i]; + for (epair = ent->epairs; epair; epair = nextepair) + { + nextepair = epair->next; + // + if (epair->key) FreeMemory(epair->key); + if (epair->value) FreeMemory(epair->value); + FreeMemory(epair); + } //end for + } //end for + bspworld.numentities = 0; +} //end of the function AAS_FreeBSPEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ParseBSPEntities(void) +{ + script_t *script; + token_t token; + bsp_entity_t *ent; + bsp_epair_t *epair; + + script = LoadScriptMemory(bspworld.dentdata, bspworld.entdatasize, "entdata"); + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES|SCFL_NOSTRINGESCAPECHARS);//SCFL_PRIMITIVE); + + bspworld.numentities = 1; + + while(PS_ReadToken(script, &token)) + { + if (strcmp(token.string, "{")) + { + ScriptError(script, "invalid %s\n", token.string); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + if (bspworld.numentities >= MAX_BSPENTITIES) + { + botimport.Print(PRT_MESSAGE, "too many entities in BSP file\n"); + break; + } //end if + ent = &bspworld.entities[bspworld.numentities]; + bspworld.numentities++; + ent->epairs = NULL; + while(PS_ReadToken(script, &token)) + { + if (!strcmp(token.string, "}")) break; + epair = (bsp_epair_t *) GetClearedHunkMemory(sizeof(bsp_epair_t)); + epair->next = ent->epairs; + ent->epairs = epair; + if (token.type != TT_STRING) + { + ScriptError(script, "invalid %s\n", token.string); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + StripDoubleQuotes(token.string); + epair->key = (char *) GetHunkMemory(strlen(token.string) + 1); + strcpy(epair->key, token.string); + if (!PS_ExpectTokenType(script, TT_STRING, 0, &token)) + { + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + StripDoubleQuotes(token.string); + epair->value = (char *) GetHunkMemory(strlen(token.string) + 1); + strcpy(epair->value, token.string); + } //end while + if (strcmp(token.string, "}")) + { + ScriptError(script, "missing }\n"); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + } //end while + FreeScript(script); +} //end of the function AAS_ParseBSPEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BSPTraceLight(vec3_t start, vec3_t end, vec3_t endpos, int *red, int *green, int *blue) +{ + return 0; +} //end of the function AAS_BSPTraceLight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpBSPData(void) +{ + AAS_FreeBSPEntities(); + + if (bspworld.dentdata) FreeMemory(bspworld.dentdata); + bspworld.dentdata = NULL; + bspworld.entdatasize = 0; + // + bspworld.loaded = qfalse; + Com_Memset( &bspworld, 0, sizeof(bspworld) ); +} //end of the function AAS_DumpBSPData +//=========================================================================== +// load an bsp file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadBSPFile(void) +{ + AAS_DumpBSPData(); + bspworld.entdatasize = strlen(botimport.BSPEntityData()) + 1; + bspworld.dentdata = (char *) GetClearedHunkMemory(bspworld.entdatasize); + Com_Memcpy(bspworld.dentdata, botimport.BSPEntityData(), bspworld.entdatasize); + AAS_ParseBSPEntities(); + bspworld.loaded = qtrue; + return BLERR_NOERROR; +} //end of the function AAS_LoadBSPFile diff --git a/codemp/botlib/be_aas_cluster.cpp b/codemp/botlib/be_aas_cluster.cpp new file mode 100644 index 0000000..fd14864 --- /dev/null +++ b/codemp/botlib/be_aas_cluster.cpp @@ -0,0 +1,1528 @@ + +/***************************************************************************** + * name: be_aas_cluster.c + * + * desc: area clustering + * + * $Archive: /MissionPack/code/botlib/be_aas_cluster.c $ + * $Author: Ttimo $ + * $Revision: 10 $ + * $Modtime: 4/21/01 9:15a $ + * $Date: 4/21/01 9:15a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +#define AAS_MAX_PORTALS 65536 +#define AAS_MAX_PORTALINDEXSIZE 65536 +#define AAS_MAX_CLUSTERS 65536 +// +#define MAX_PORTALAREAS 1024 + +// do not flood through area faces, only use reachabilities +int nofaceflood = qtrue; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveClusterAreas(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + aasworld.areasettings[i].cluster = 0; + } //end for +} //end of the function AAS_RemoveClusterAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearCluster(int clusternum) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].cluster == clusternum) + { + aasworld.areasettings[i].cluster = 0; + } //end if + } //end for +} //end of the function AAS_ClearCluster +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemovePortalsClusterReference(int clusternum) +{ + int portalnum; + + for (portalnum = 1; portalnum < aasworld.numportals; portalnum++) + { + if (aasworld.portals[portalnum].frontcluster == clusternum) + { + aasworld.portals[portalnum].frontcluster = 0; + } //end if + if (aasworld.portals[portalnum].backcluster == clusternum) + { + aasworld.portals[portalnum].backcluster = 0; + } //end if + } //end for +} //end of the function AAS_RemovePortalsClusterReference +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_UpdatePortal(int areanum, int clusternum) +{ + int portalnum; + aas_portal_t *portal; + aas_cluster_t *cluster; + + //find the portal of the area + for (portalnum = 1; portalnum < aasworld.numportals; portalnum++) + { + if (aasworld.portals[portalnum].areanum == areanum) break; + } //end for + // + if (portalnum == aasworld.numportals) + { + AAS_Error("no portal of area %d", areanum); + return qtrue; + } //end if + // + portal = &aasworld.portals[portalnum]; + //if the portal is already fully updated + if (portal->frontcluster == clusternum) return qtrue; + if (portal->backcluster == clusternum) return qtrue; + //if the portal has no front cluster yet + if (!portal->frontcluster) + { + portal->frontcluster = clusternum; + } //end if + //if the portal has no back cluster yet + else if (!portal->backcluster) + { + portal->backcluster = clusternum; + } //end else if + else + { + //remove the cluster portal flag contents + aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d is seperating more than two clusters\r\n", areanum); + return qfalse; + } //end else + if (aasworld.portalindexsize >= AAS_MAX_PORTALINDEXSIZE) + { + AAS_Error("AAS_MAX_PORTALINDEXSIZE"); + return qtrue; + } //end if + //set the area cluster number to the negative portal number + aasworld.areasettings[areanum].cluster = -portalnum; + //add the portal to the cluster using the portal index + cluster = &aasworld.clusters[clusternum]; + aasworld.portalindex[cluster->firstportal + cluster->numportals] = portalnum; + aasworld.portalindexsize++; + cluster->numportals++; + return qtrue; +} //end of the function AAS_UpdatePortal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloodClusterAreas_r(int areanum, int clusternum) +{ + aas_area_t *area; + aas_face_t *face; + int facenum, i; + + // + if (areanum <= 0 || areanum >= aasworld.numareas) + { + AAS_Error("AAS_FloodClusterAreas_r: areanum out of range"); + return qfalse; + } //end if + //if the area is already part of a cluster + if (aasworld.areasettings[areanum].cluster > 0) + { + if (aasworld.areasettings[areanum].cluster == clusternum) return qtrue; + // + //there's a reachability going from one cluster to another only in one direction + // + AAS_Error("cluster %d touched cluster %d at area %d\r\n", + clusternum, aasworld.areasettings[areanum].cluster, areanum); + return qfalse; + } //end if + //don't add the cluster portal areas to the clusters + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + return AAS_UpdatePortal(areanum, clusternum); + } //end if + //set the area cluster number + aasworld.areasettings[areanum].cluster = clusternum; + aasworld.areasettings[areanum].clusterareanum = + aasworld.clusters[clusternum].numareas; + //the cluster has an extra area + aasworld.clusters[clusternum].numareas++; + + area = &aasworld.areas[areanum]; + //use area faces to flood into adjacent areas + if (!nofaceflood) + { + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + if (face->frontarea == areanum) + { + if (face->backarea) if (!AAS_FloodClusterAreas_r(face->backarea, clusternum)) return qfalse; + } //end if + else + { + if (face->frontarea) if (!AAS_FloodClusterAreas_r(face->frontarea, clusternum)) return qfalse; + } //end else + } //end for + } //end if + //use the reachabilities to flood into other areas + for (i = 0; i < aasworld.areasettings[areanum].numreachableareas; i++) + { + if (!aasworld.reachability[ + aasworld.areasettings[areanum].firstreachablearea + i].areanum) + { + continue; + } //end if + if (!AAS_FloodClusterAreas_r(aasworld.reachability[ + aasworld.areasettings[areanum].firstreachablearea + i].areanum, clusternum)) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_FloodClusterAreas_r +//=========================================================================== +// try to flood from all areas without cluster into areas with a cluster set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloodClusterAreasUsingReachabilities(int clusternum) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + //if this area already has a cluster set + if (aasworld.areasettings[i].cluster) + continue; + //if this area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + continue; + //loop over the reachable areas from this area + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + //the reachable area + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + //if this area is a cluster portal + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) + continue; + //if this area has a cluster set + if (aasworld.areasettings[areanum].cluster) + { + if (!AAS_FloodClusterAreas_r(i, clusternum)) + return qfalse; + i = 0; + break; + } //end if + } //end for + } //end for + return qtrue; +} //end of the function AAS_FloodClusterAreasUsingReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterPortals(int clusternum) +{ + int i, portalnum; + aas_cluster_t *cluster; + aas_portal_t *portal; + + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + } //end else + } //end for +} //end of the function AAS_NumberClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterAreas(int clusternum) +{ + int i, portalnum; + aas_cluster_t *cluster; + aas_portal_t *portal; + + aasworld.clusters[clusternum].numareas = 0; + aasworld.clusters[clusternum].numreachabilityareas = 0; + //number all areas in this cluster WITH reachabilities + for (i = 1; i < aasworld.numareas; i++) + { + // + if (aasworld.areasettings[i].cluster != clusternum) continue; + // + if (!AAS_AreaReachability(i)) continue; + // + aasworld.areasettings[i].clusterareanum = aasworld.clusters[clusternum].numareas; + //the cluster has an extra area + aasworld.clusters[clusternum].numareas++; + aasworld.clusters[clusternum].numreachabilityareas++; + } //end for + //number all portals in this cluster WITH reachabilities + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + if (!AAS_AreaReachability(portal->areanum)) continue; + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + aasworld.clusters[clusternum].numreachabilityareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + aasworld.clusters[clusternum].numreachabilityareas++; + } //end else + } //end for + //number all areas in this cluster WITHOUT reachabilities + for (i = 1; i < aasworld.numareas; i++) + { + // + if (aasworld.areasettings[i].cluster != clusternum) continue; + // + if (AAS_AreaReachability(i)) continue; + // + aasworld.areasettings[i].clusterareanum = aasworld.clusters[clusternum].numareas; + //the cluster has an extra area + aasworld.clusters[clusternum].numareas++; + } //end for + //number all portals in this cluster WITHOUT reachabilities + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + if (AAS_AreaReachability(portal->areanum)) continue; + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + } //end else + } //end for +} //end of the function AAS_NumberClusterAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FindClusters(void) +{ + int i; + aas_cluster_t *cluster; + + AAS_RemoveClusterAreas(); + // + for (i = 1; i < aasworld.numareas; i++) + { + //if the area is already part of a cluster + if (aasworld.areasettings[i].cluster) + continue; + // if not flooding through faces only use areas that have reachabilities + if (nofaceflood) + { + if (!aasworld.areasettings[i].numreachableareas) + continue; + } //end if + //if the area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + continue; + if (aasworld.numclusters >= AAS_MAX_CLUSTERS) + { + AAS_Error("AAS_MAX_CLUSTERS"); + return qfalse; + } //end if + cluster = &aasworld.clusters[aasworld.numclusters]; + cluster->numareas = 0; + cluster->numreachabilityareas = 0; + cluster->firstportal = aasworld.portalindexsize; + cluster->numportals = 0; + //flood the areas in this cluster + if (!AAS_FloodClusterAreas_r(i, aasworld.numclusters)) + return qfalse; + if (!AAS_FloodClusterAreasUsingReachabilities(aasworld.numclusters)) + return qfalse; + //number the cluster areas + //AAS_NumberClusterPortals(aasworld.numclusters); + AAS_NumberClusterAreas(aasworld.numclusters); + //Log_Write("cluster %d has %d areas\r\n", aasworld.numclusters, cluster->numareas); + aasworld.numclusters++; + } //end for + return qtrue; +} //end of the function AAS_FindClusters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreatePortals(void) +{ + int i; + aas_portal_t *portal; + + for (i = 1; i < aasworld.numareas; i++) + { + //if the area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + if (aasworld.numportals >= AAS_MAX_PORTALS) + { + AAS_Error("AAS_MAX_PORTALS"); + return; + } //end if + portal = &aasworld.portals[aasworld.numportals]; + portal->areanum = i; + portal->frontcluster = 0; + portal->backcluster = 0; + aasworld.numportals++; + } //end if + } //end for +} //end of the function AAS_CreatePortals +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_MapContainsTeleporters(void) +{ + bsp_entity_t *entities, *ent; + char *classname; + + entities = AAS_ParseBSPEntities(); + + for (ent = entities; ent; ent = ent->next) + { + classname = AAS_ValueForBSPEpairKey(ent, "classname"); + if (classname && !strcmp(classname, "misc_teleporter")) + { + AAS_FreeBSPEntities(entities); + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_MapContainsTeleporters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NonConvexFaces(aas_face_t *face1, aas_face_t *face2, int side1, int side2) +{ + int i, j, edgenum; + aas_plane_t *plane1, *plane2; + aas_edge_t *edge; + + + plane1 = &aasworld.planes[face1->planenum ^ side1]; + plane2 = &aasworld.planes[face2->planenum ^ side2]; + + //check if one of the points of face1 is at the back of the plane of face2 + for (i = 0; i < face1->numedges; i++) + { + edgenum = abs(aasworld.edgeindex[face1->firstedge + i]); + edge = &aasworld.edges[edgenum]; + for (j = 0; j < 2; j++) + { + if (DotProduct(plane2->normal, aasworld.vertexes[edge->v[j]]) - + plane2->dist < -0.01) return qtrue; + } //end for + } //end for + for (i = 0; i < face2->numedges; i++) + { + edgenum = abs(aasworld.edgeindex[face2->firstedge + i]); + edge = &aasworld.edges[edgenum]; + for (j = 0; j < 2; j++) + { + if (DotProduct(plane1->normal, aasworld.vertexes[edge->v[j]]) - + plane1->dist < -0.01) return qtrue; + } //end for + } //end for + + return qfalse; +} //end of the function AAS_NonConvexFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_CanMergeAreas(int *areanums, int numareas) +{ + int i, j, s, face1num, face2num, side1, side2, fn1, fn2; + aas_face_t *face1, *face2; + aas_area_t *area1, *area2; + + for (i = 0; i < numareas; i++) + { + area1 = &aasworld.areas[areanums[i]]; + for (fn1 = 0; fn1 < area1->numfaces; fn1++) + { + face1num = abs(aasworld.faceindex[area1->firstface + fn1]); + face1 = &aasworld.faces[face1num]; + side1 = face1->frontarea != areanums[i]; + //check if the face isn't a shared one with one of the other areas + for (s = 0; s < numareas; s++) + { + if (s == i) continue; + if (face1->frontarea == s || face1->backarea == s) break; + } //end for + //if the face was a shared one + if (s != numareas) continue; + // + for (j = 0; j < numareas; j++) + { + if (j == i) continue; + area2 = &aasworld.areas[areanums[j]]; + for (fn2 = 0; fn2 < area2->numfaces; fn2++) + { + face2num = abs(aasworld.faceindex[area2->firstface + fn2]); + face2 = &aasworld.faces[face2num]; + side2 = face2->frontarea != areanums[j]; + //check if the face isn't a shared one with one of the other areas + for (s = 0; s < numareas; s++) + { + if (s == j) continue; + if (face2->frontarea == s || face2->backarea == s) break; + } //end for + //if the face was a shared one + if (s != numareas) continue; + // + if (AAS_NonConvexFaces(face1, face2, side1, side2)) return qfalse; + } //end for + } //end for + } //end for + } //end for + return qtrue; +} //end of the function AAS_CanMergeAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_NonConvexEdges(aas_edge_t *edge1, aas_edge_t *edge2, int side1, int side2, int planenum) +{ + int i; + vec3_t edgevec1, edgevec2, normal1, normal2; + float dist1, dist2; + aas_plane_t *plane; + + plane = &aasworld.planes[planenum]; + VectorSubtract(aasworld.vertexes[edge1->v[1]], aasworld.vertexes[edge1->v[0]], edgevec1); + VectorSubtract(aasworld.vertexes[edge2->v[1]], aasworld.vertexes[edge2->v[0]], edgevec2); + if (side1) VectorInverse(edgevec1); + if (side2) VectorInverse(edgevec2); + // + CrossProduct(edgevec1, plane->normal, normal1); + dist1 = DotProduct(normal1, aasworld.vertexes[edge1->v[0]]); + CrossProduct(edgevec2, plane->normal, normal2); + dist2 = DotProduct(normal2, aasworld.vertexes[edge2->v[0]]); + + for (i = 0; i < 2; i++) + { + if (DotProduct(aasworld.vertexes[edge1->v[i]], normal2) - dist2 < -0.01) return qfalse; + } //end for + for (i = 0; i < 2; i++) + { + if (DotProduct(aasworld.vertexes[edge2->v[i]], normal1) - dist1 < -0.01) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_NonConvexEdges +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_CanMergeFaces(int *facenums, int numfaces, int planenum) +{ + int i, j, s, edgenum1, edgenum2, side1, side2, en1, en2, ens; + aas_face_t *face1, *face2, *otherface; + aas_edge_t *edge1, *edge2; + + for (i = 0; i < numfaces; i++) + { + face1 = &aasworld.faces[facenums[i]]; + for (en1 = 0; en1 < face1->numedges; en1++) + { + edgenum1 = aasworld.edgeindex[face1->firstedge + en1]; + side1 = (edgenum1 < 0) ^ (face1->planenum != planenum); + edgenum1 = abs(edgenum1); + edge1 = &aasworld.edges[edgenum1]; + //check if the edge is shared with another face + for (s = 0; s < numfaces; s++) + { + if (s == i) continue; + otherface = &aasworld.faces[facenums[s]]; + for (ens = 0; ens < otherface->numedges; ens++) + { + if (edgenum1 == abs(aasworld.edgeindex[otherface->firstedge + ens])) break; + } //end for + if (ens != otherface->numedges) break; + } //end for + //if the edge was shared + if (s != numfaces) continue; + // + for (j = 0; j < numfaces; j++) + { + if (j == i) continue; + face2 = &aasworld.faces[facenums[j]]; + for (en2 = 0; en2 < face2->numedges; en2++) + { + edgenum2 = aasworld.edgeindex[face2->firstedge + en2]; + side2 = (edgenum2 < 0) ^ (face2->planenum != planenum); + edgenum2 = abs(edgenum2); + edge2 = &aasworld.edges[edgenum2]; + //check if the edge is shared with another face + for (s = 0; s < numfaces; s++) + { + if (s == i) continue; + otherface = &aasworld.faces[facenums[s]]; + for (ens = 0; ens < otherface->numedges; ens++) + { + if (edgenum2 == abs(aasworld.edgeindex[otherface->firstedge + ens])) break; + } //end for + if (ens != otherface->numedges) break; + } //end for + //if the edge was shared + if (s != numfaces) continue; + // + if (AAS_NonConvexEdges(edge1, edge2, side1, side2, planenum)) return qfalse; + } //end for + } //end for + } //end for + } //end for + return qtrue; +} //end of the function AAS_CanMergeFaces*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ConnectedAreas_r(int *areanums, int numareas, int *connectedareas, int curarea) +{ + int i, j, otherareanum, facenum; + aas_area_t *area; + aas_face_t *face; + + connectedareas[curarea] = qtrue; + area = &aasworld.areas[areanums[curarea]]; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) continue; + //get the area at the other side of the face + if (face->frontarea != areanums[curarea]) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //check if the face is leading to one of the other areas + for (j = 0; j < numareas; j++) + { + if (areanums[j] == otherareanum) break; + } //end for + //if the face isn't leading to one of the other areas + if (j == numareas) continue; + //if the other area is already connected + if (connectedareas[j]) continue; + //recursively proceed with the other area + AAS_ConnectedAreas_r(areanums, numareas, connectedareas, j); + } //end for +} //end of the function AAS_ConnectedAreas_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_ConnectedAreas(int *areanums, int numareas) +{ + int connectedareas[MAX_PORTALAREAS], i; + + Com_Memset(connectedareas, 0, sizeof(connectedareas)); + if (numareas < 1) return qfalse; + if (numareas == 1) return qtrue; + AAS_ConnectedAreas_r(areanums, numareas, connectedareas, 0); + for (i = 0; i < numareas; i++) + { + if (!connectedareas[i]) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_ConnectedAreas +//=========================================================================== +// gets adjacent areas with less presence types recursively +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetAdjacentAreasWithLessPresenceTypes_r(int *areanums, int numareas, int curareanum) +{ + int i, j, presencetype, otherpresencetype, otherareanum, facenum; + aas_area_t *area; + aas_face_t *face; + + areanums[numareas++] = curareanum; + area = &aasworld.areas[curareanum]; + presencetype = aasworld.areasettings[curareanum].presencetype; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) continue; + //the area at the other side of the face + if (face->frontarea != curareanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + otherpresencetype = aasworld.areasettings[otherareanum].presencetype; + //if the other area has less presence types + if ((presencetype & ~otherpresencetype) && + !(otherpresencetype & ~presencetype)) + { + //check if the other area isn't already in the list + for (j = 0; j < numareas; j++) + { + if (otherareanum == areanums[j]) break; + } //end for + //if the other area isn't already in the list + if (j == numareas) + { + if (numareas >= MAX_PORTALAREAS) + { + AAS_Error("MAX_PORTALAREAS"); + return numareas; + } //end if + numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, numareas, otherareanum); + } //end if + } //end if + } //end for + return numareas; +} //end of the function AAS_GetAdjacentAreasWithLessPresenceTypes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CheckAreaForPossiblePortals(int areanum) +{ + int i, j, k, fen, ben, frontedgenum, backedgenum, facenum; + int areanums[MAX_PORTALAREAS], numareas, otherareanum; + int numareafrontfaces[MAX_PORTALAREAS], numareabackfaces[MAX_PORTALAREAS]; + int frontfacenums[MAX_PORTALAREAS], backfacenums[MAX_PORTALAREAS]; + int numfrontfaces, numbackfaces; + int frontareanums[MAX_PORTALAREAS], backareanums[MAX_PORTALAREAS]; + int numfrontareas, numbackareas; + int frontplanenum, backplanenum, faceplanenum; + aas_area_t *area; + aas_face_t *frontface, *backface, *face; + + //if it isn't already a portal + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return 0; + //it must be a grounded area + if (!(aasworld.areasettings[areanum].areaflags & AREA_GROUNDED)) return 0; + // + Com_Memset(numareafrontfaces, 0, sizeof(numareafrontfaces)); + Com_Memset(numareabackfaces, 0, sizeof(numareabackfaces)); + numareas = numfrontfaces = numbackfaces = 0; + numfrontareas = numbackareas = 0; + frontplanenum = backplanenum = -1; + //add any adjacent areas with less presence types + numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, 0, areanum); + // + for (i = 0; i < numareas; i++) + { + area = &aasworld.areas[areanums[i]]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) continue; + //check if the face is shared with one of the other areas + for (k = 0; k < numareas; k++) + { + if (k == i) continue; + if (face->frontarea == areanums[k] || face->backarea == areanums[k]) break; + } //end for + //if the face is shared + if (k != numareas) continue; + //the number of the area at the other side of the face + if (face->frontarea == areanums[i]) otherareanum = face->backarea; + else otherareanum = face->frontarea; + //if the other area already is a cluter portal + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) return 0; + //number of the plane of the area + faceplanenum = face->planenum & ~1; + // + if (frontplanenum < 0 || faceplanenum == frontplanenum) + { + frontplanenum = faceplanenum; + frontfacenums[numfrontfaces++] = facenum; + for (k = 0; k < numfrontareas; k++) + { + if (frontareanums[k] == otherareanum) break; + } //end for + if (k == numfrontareas) frontareanums[numfrontareas++] = otherareanum; + numareafrontfaces[i]++; + } //end if + else if (backplanenum < 0 || faceplanenum == backplanenum) + { + backplanenum = faceplanenum; + backfacenums[numbackfaces++] = facenum; + for (k = 0; k < numbackareas; k++) + { + if (backareanums[k] == otherareanum) break; + } //end for + if (k == numbackareas) backareanums[numbackareas++] = otherareanum; + numareabackfaces[i]++; + } //end else + else + { + return 0; + } //end else + } //end for + } //end for + //every area should have at least one front face and one back face + for (i = 0; i < numareas; i++) + { + if (!numareafrontfaces[i] || !numareabackfaces[i]) return 0; + } //end for + //the front areas should all be connected + if (!AAS_ConnectedAreas(frontareanums, numfrontareas)) return 0; + //the back areas should all be connected + if (!AAS_ConnectedAreas(backareanums, numbackareas)) return 0; + //none of the front faces should have a shared edge with a back face + for (i = 0; i < numfrontfaces; i++) + { + frontface = &aasworld.faces[frontfacenums[i]]; + for (fen = 0; fen < frontface->numedges; fen++) + { + frontedgenum = abs(aasworld.edgeindex[frontface->firstedge + fen]); + for (j = 0; j < numbackfaces; j++) + { + backface = &aasworld.faces[backfacenums[j]]; + for (ben = 0; ben < backface->numedges; ben++) + { + backedgenum = abs(aasworld.edgeindex[backface->firstedge + ben]); + if (frontedgenum == backedgenum) break; + } //end for + if (ben != backface->numedges) break; + } //end for + if (j != numbackfaces) break; + } //end for + if (fen != frontface->numedges) break; + } //end for + if (i != numfrontfaces) return 0; + //set the cluster portal contents + for (i = 0; i < numareas; i++) + { + aasworld.areasettings[areanums[i]].contents |= AREACONTENTS_CLUSTERPORTAL; + //this area can be used as a route portal + aasworld.areasettings[areanums[i]].contents |= AREACONTENTS_ROUTEPORTAL; + Log_Write("possible portal: %d\r\n", areanums[i]); + } //end for + // + return numareas; +} //end of the function AAS_CheckAreaForPossiblePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FindPossiblePortals(void) +{ + int i, numpossibleportals; + + numpossibleportals = 0; + for (i = 1; i < aasworld.numareas; i++) + { + numpossibleportals += AAS_CheckAreaForPossiblePortals(i); + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d possible portal areas\n", numpossibleportals); +} //end of the function AAS_FindPossiblePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveAllPortals(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + } //end for +} //end of the function AAS_RemoveAllPortals + +#if 0 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FloodCluster_r(int areanum, int clusternum) +{ + int i, otherareanum; + aas_face_t *face; + aas_area_t *area; + + //set cluster mark + aasworld.areasettings[areanum].cluster = clusternum; + //if the area is a portal + //if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return; + // + area = &aasworld.areas[areanum]; + //use area faces to flood into adjacent areas + for (i = 0; i < area->numfaces; i++) + { + face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; + // + if (face->frontarea != areanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //if there's no area at the other side + if (!otherareanum) continue; + //if the area is a portal + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area is already marked + if (aasworld.areasettings[otherareanum].cluster) continue; + // + AAS_FloodCluster_r(otherareanum, clusternum); + } //end for + //use the reachabilities to flood into other areas + for (i = 0; i < aasworld.areasettings[areanum].numreachableareas; i++) + { + otherareanum = aasworld.reachability[ + aasworld.areasettings[areanum].firstreachablearea + i].areanum; + if (!otherareanum) + { + continue; + AAS_Error("reachability %d has zero area\n", aasworld.areasettings[areanum].firstreachablearea + i); + } //end if + //if the area is a portal + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area is already marked + if (aasworld.areasettings[otherareanum].cluster) continue; + // + AAS_FloodCluster_r(otherareanum, clusternum); + } //end for +} //end of the function AAS_FloodCluster_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveTeleporterPortals(void) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + if (aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].traveltype == TRAVEL_TELEPORT) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + break; + } //end if + } //end for + } //end for +} //end of the function AAS_RemoveTeleporterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FloodClusterReachabilities(int clusternum) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + //if this area already has a cluster set + if (aasworld.areasettings[i].cluster) continue; + //if this area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //loop over the reachable areas from this area + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + //the reachable area + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + //if this area is a cluster portal + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if this area has a cluster set + if (aasworld.areasettings[areanum].cluster == clusternum) + { + AAS_FloodCluster_r(i, clusternum); + i = 0; + break; + } //end if + } //end for + } //end for +} //end of the function AAS_FloodClusterReachabilities + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveNotClusterClosingPortals(void) +{ + int i, j, k, facenum, otherareanum, nonclosingportals; + aas_area_t *area; + aas_face_t *face; + + AAS_RemoveTeleporterPortals(); + // + nonclosingportals = 0; + for (i = 1; i < aasworld.numareas; i++) + { + if (!(aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &aasworld.areas[i]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + //reset all cluster fields + AAS_RemoveClusterAreas(); + // + AAS_FloodCluster_r(otherareanum, 1); + AAS_FloodClusterReachabilities(1); + //check if all adjacent non-portal areas have a cluster set + for (k = 0; k < area->numfaces; k++) + { + facenum = abs(aasworld.faceindex[area->firstface + k]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + // + if (!aasworld.areasettings[otherareanum].cluster) break; + } //end for + //if all adjacent non-portal areas have a cluster set then the portal + //didn't seal a cluster + if (k >= area->numfaces) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + nonclosingportals++; + //recheck all the other portals again + i = 0; + break; + } //end if + } //end for + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); +} //end of the function AAS_RemoveNotClusterClosingPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_RemoveNotClusterClosingPortals(void) +{ + int i, j, facenum, otherareanum, nonclosingportals, numseperatedclusters; + aas_area_t *area; + aas_face_t *face; + + AAS_RemoveTeleporterPortals(); + // + nonclosingportals = 0; + for (i = 1; i < aasworld.numareas; i++) + { + if (!(aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; + // + numseperatedclusters = 0; + //reset all cluster fields + AAS_RemoveClusterAreas(); + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &aasworld.areas[i]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //if not solid at the other side of the face + if (!otherareanum) continue; + //don't flood into other portals + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area already has a cluster set + if (aasworld.areasettings[otherareanum].cluster) continue; + //another cluster is seperated by this portal + numseperatedclusters++; + //flood the cluster + AAS_FloodCluster_r(otherareanum, numseperatedclusters); + AAS_FloodClusterReachabilities(numseperatedclusters); + } //end for + //use the reachabilities to flood into other areas + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + otherareanum = aasworld.reachability[ + aasworld.areasettings[i].firstreachablearea + j].areanum; + //this should never be qtrue but we check anyway + if (!otherareanum) continue; + //don't flood into other portals + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area already has a cluster set + if (aasworld.areasettings[otherareanum].cluster) continue; + //another cluster is seperated by this portal + numseperatedclusters++; + //flood the cluster + AAS_FloodCluster_r(otherareanum, numseperatedclusters); + AAS_FloodClusterReachabilities(numseperatedclusters); + } //end for + //a portal must seperate no more and no less than 2 clusters + if (numseperatedclusters != 2) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + nonclosingportals++; + //recheck all the other portals again + i = 0; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); +} //end of the function AAS_RemoveNotClusterClosingPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_AddTeleporterPortals(void) +{ + int j, area2num, facenum, otherareanum; + char *target, *targetname, *classname; + bsp_entity_t *entities, *ent, *dest; + vec3_t origin, destorigin, mins, maxs, end; + vec3_t bbmins, bbmaxs; + aas_area_t *area; + aas_face_t *face; + aas_trace_t trace; + aas_link_t *areas, *link; + + entities = AAS_ParseBSPEntities(); + + for (ent = entities; ent; ent = ent->next) + { + classname = AAS_ValueForBSPEpairKey(ent, "classname"); + if (classname && !strcmp(classname, "misc_teleporter")) + { + if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + botimport.Print(PRT_ERROR, "teleporter (%s) without origin\n", target); + continue; + } //end if + // + target = AAS_ValueForBSPEpairKey(ent, "target"); + if (!target) + { + botimport.Print(PRT_ERROR, "teleporter (%s) without target\n", target); + continue; + } //end if + for (dest = entities; dest; dest = dest->next) + { + classname = AAS_ValueForBSPEpairKey(dest, "classname"); + if (classname && !strcmp(classname, "misc_teleporter_dest")) + { + targetname = AAS_ValueForBSPEpairKey(dest, "targetname"); + if (targetname && !strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + botimport.Print(PRT_ERROR, "teleporter without destination (%s)\n", target); + continue; + } //end if + if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); + continue; + } //end if + destorigin[2] += 24; //just for q2e1m2, the dork has put the telepads in the ground + VectorCopy(destorigin, end); + end[2] -= 100; + trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); + continue; + } //end if + VectorCopy(trace.endpos, destorigin); + area2num = AAS_PointAreaNum(destorigin); + //reset all cluster fields + for (j = 0; j < aasworld.numareas; j++) + { + aasworld.areasettings[j].cluster = 0; + } //end for + // + VectorSet(mins, -8, -8, 8); + VectorSet(maxs, 8, 8, 24); + // + AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); + // + VectorAdd(origin, mins, mins); + VectorAdd(origin, maxs, maxs); + //add bounding box size + VectorSubtract(mins, bbmaxs, mins); + VectorSubtract(maxs, bbmins, maxs); + //link an invalid (-1) entity + areas = AAS_AASLinkEntity(mins, maxs, -1); + // + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //add the teleporter portal mark + aasworld.areasettings[link->areanum].contents |= AREACONTENTS_CLUSTERPORTAL | + AREACONTENTS_TELEPORTAL; + } //end for + // + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &aasworld.areas[link->areanum]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != link->areanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + // + AAS_FloodCluster_r(otherareanum, 1); + } //end for + } //end for + //if the teleport destination IS in the same cluster + if (aasworld.areasettings[area2num].cluster) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //add the teleporter portal mark + aasworld.areasettings[link->areanum].contents &= ~(AREACONTENTS_CLUSTERPORTAL | + AREACONTENTS_TELEPORTAL); + } //end for + } //end if + } //end if + } //end for + AAS_FreeBSPEntities(entities); +} //end of the function AAS_AddTeleporterPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddTeleporterPortals(void) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + if (aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].traveltype != TRAVEL_TELEPORT) continue; + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + aasworld.areasettings[areanum].contents |= AREACONTENTS_CLUSTERPORTAL; + } //end for + } //end for +} //end of the function AAS_AddTeleporterPortals + +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TestPortals(void) +{ + int i; + aas_portal_t *portal; + + for (i = 1; i < aasworld.numportals; i++) + { + portal = &aasworld.portals[i]; + if (!portal->frontcluster) + { + aasworld.areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d has no front cluster\r\n", portal->areanum); + return qfalse; + } //end if + if (!portal->backcluster) + { + aasworld.areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d has no back cluster\r\n", portal->areanum); + return qfalse; + } //end if + } //end for + return qtrue; +} //end of the function +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CountForcedClusterPortals(void) +{ + int num, i; + + num = 0; + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + Log_Write("area %d is a forced portal area\r\n", i); + num++; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "%6d forced portal areas\n", num); +} //end of the function AAS_CountForcedClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateViewPortals(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + aasworld.areasettings[i].contents |= AREACONTENTS_VIEWPORTAL; + } //end if + } //end for +} //end of the function AAS_CreateViewPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetViewPortalsAsClusterPortals(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL) + { + aasworld.areasettings[i].contents |= AREACONTENTS_CLUSTERPORTAL; + } //end if + } //end for +} //end of the function AAS_SetViewPortalsAsClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitClustering(void) +{ + int i, removedPortalAreas; + int n, total, numreachabilityareas; + + if (!aasworld.loaded) return; + //if there are clusters + if (aasworld.numclusters >= 1) + { +#ifndef BSPC + //if clustering isn't forced + if (!((int)LibVarGetValue("forceclustering")) && + !((int)LibVarGetValue("forcereachability"))) return; +#endif + } //end if + //set all view portals as cluster portals in case we re-calculate the reachabilities and clusters (with -reach) + AAS_SetViewPortalsAsClusterPortals(); + //count the number of forced cluster portals + AAS_CountForcedClusterPortals(); + //remove all area cluster marks + AAS_RemoveClusterAreas(); + //find possible cluster portals + AAS_FindPossiblePortals(); + //craete portals to for the bot view + AAS_CreateViewPortals(); + //remove all portals that are not closing a cluster + //AAS_RemoveNotClusterClosingPortals(); + //initialize portal memory + if (aasworld.portals) FreeMemory(aasworld.portals); + aasworld.portals = (aas_portal_t *) GetClearedMemory(AAS_MAX_PORTALS * sizeof(aas_portal_t)); + //initialize portal index memory + if (aasworld.portalindex) FreeMemory(aasworld.portalindex); + aasworld.portalindex = (aas_portalindex_t *) GetClearedMemory(AAS_MAX_PORTALINDEXSIZE * sizeof(aas_portalindex_t)); + //initialize cluster memory + if (aasworld.clusters) FreeMemory(aasworld.clusters); + aasworld.clusters = (aas_cluster_t *) GetClearedMemory(AAS_MAX_CLUSTERS * sizeof(aas_cluster_t)); + // + removedPortalAreas = 0; + botimport.Print(PRT_MESSAGE, "\r%6d removed portal areas", removedPortalAreas); + while(1) + { + botimport.Print(PRT_MESSAGE, "\r%6d", removedPortalAreas); + //initialize the number of portals and clusters + aasworld.numportals = 1; //portal 0 is a dummy + aasworld.portalindexsize = 0; + aasworld.numclusters = 1; //cluster 0 is a dummy + //create the portals from the portal areas + AAS_CreatePortals(); + // + removedPortalAreas++; + //find the clusters + if (!AAS_FindClusters()) + continue; + //test the portals + if (!AAS_TestPortals()) + continue; + // + break; + } //end while + botimport.Print(PRT_MESSAGE, "\n"); + //the AAS file should be saved + aasworld.savefile = qtrue; + //write the portal areas to the log file + for (i = 1; i < aasworld.numportals; i++) + { + Log_Write("portal %d: area %d\r\n", i, aasworld.portals[i].areanum); + } //end for + // report cluster info + botimport.Print(PRT_MESSAGE, "%6d portals created\n", aasworld.numportals); + botimport.Print(PRT_MESSAGE, "%6d clusters created\n", aasworld.numclusters); + for (i = 1; i < aasworld.numclusters; i++) + { + botimport.Print(PRT_MESSAGE, "cluster %d has %d reachability areas\n", i, + aasworld.clusters[i].numreachabilityareas); + } //end for + // report AAS file efficiency + numreachabilityareas = 0; + total = 0; + for (i = 0; i < aasworld.numclusters; i++) { + n = aasworld.clusters[i].numreachabilityareas; + numreachabilityareas += n; + total += n * n; + } + total += numreachabilityareas * aasworld.numportals; + // + botimport.Print(PRT_MESSAGE, "%6i total reachability areas\n", numreachabilityareas); + botimport.Print(PRT_MESSAGE, "%6i AAS memory/CPU usage (the lower the better)\n", total * 3); +} //end of the function AAS_InitClustering diff --git a/codemp/botlib/be_aas_cluster.h b/codemp/botlib/be_aas_cluster.h new file mode 100644 index 0000000..3787773 --- /dev/null +++ b/codemp/botlib/be_aas_cluster.h @@ -0,0 +1,21 @@ + +/***************************************************************************** + * name: be_aas_cluster.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_cluster.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize the AAS clustering +void AAS_InitClustering(void); +// +void AAS_SetViewPortalsAsClusterPortals(void); +#endif //AASINTERN + diff --git a/codemp/botlib/be_aas_debug.cpp b/codemp/botlib/be_aas_debug.cpp new file mode 100644 index 0000000..2db6f72 --- /dev/null +++ b/codemp/botlib/be_aas_debug.cpp @@ -0,0 +1,764 @@ + +/***************************************************************************** + * name: be_aas_debug.c + * + * desc: AAS debug code + * + * $Archive: /MissionPack/code/botlib/be_aas_debug.c $ + * $Author: Ttimo $ + * $Revision: 8 $ + * $Modtime: 4/22/01 8:52a $ + * $Date: 4/22/01 8:52a $ + * + *****************************************************************************/ + +#if 0 // Removing on Xbox + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_interface.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +#define MAX_DEBUGLINES 1024 +#define MAX_DEBUGPOLYGONS 8192 + +int debuglines[MAX_DEBUGLINES]; +int debuglinevisible[MAX_DEBUGLINES]; +int numdebuglines; + +static int debugpolygons[MAX_DEBUGPOLYGONS]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownPolygons(void) +{ + int i; +//* + for (i = 0; i < MAX_DEBUGPOLYGONS; i++) + { + if (debugpolygons[i]) botimport.DebugPolygonDelete(debugpolygons[i]); + debugpolygons[i] = 0; + } //end for +//*/ +/* + for (i = 0; i < MAX_DEBUGPOLYGONS; i++) + { + botimport.DebugPolygonDelete(i); + debugpolygons[i] = 0; + } //end for +*/ +} //end of the function AAS_ClearShownPolygons +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowPolygon(int color, int numpoints, vec3_t *points) +{ + int i; + + for (i = 0; i < MAX_DEBUGPOLYGONS; i++) + { + if (!debugpolygons[i]) + { + debugpolygons[i] = botimport.DebugPolygonCreate(color, numpoints, points); + break; + } //end if + } //end for +} //end of the function AAS_ShowPolygon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownDebugLines(void) +{ + int i; + + //make all lines invisible + for (i = 0; i < MAX_DEBUGLINES; i++) + { + if (debuglines[i]) + { + //botimport.DebugLineShow(debuglines[i], NULL, NULL, LINECOLOR_NONE); + botimport.DebugLineDelete(debuglines[i]); + debuglines[i] = 0; + debuglinevisible[i] = qfalse; + } //end if + } //end for +} //end of the function AAS_ClearShownDebugLines +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DebugLine(vec3_t start, vec3_t end, int color) +{ + int line; + + for (line = 0; line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + debuglinevisible[line] = qfalse; + numdebuglines++; + } //end if + if (!debuglinevisible[line]) + { + botimport.DebugLineShow(debuglines[line], start, end, color); + debuglinevisible[line] = qtrue; + return; + } //end else + } //end for +} //end of the function AAS_DebugLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PermanentLine(vec3_t start, vec3_t end, int color) +{ + int line; + + line = botimport.DebugLineCreate(); + botimport.DebugLineShow(line, start, end, color); +} //end of the function AAS_PermenentLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawPermanentCross(vec3_t origin, float size, int color) +{ + int i, debugline; + vec3_t start, end; + + for (i = 0; i < 3; i++) + { + VectorCopy(origin, start); + start[i] += size; + VectorCopy(origin, end); + end[i] -= size; + AAS_DebugLine(start, end, color); + debugline = botimport.DebugLineCreate(); + botimport.DebugLineShow(debugline, start, end, color); + } //end for +} //end of the function AAS_DrawPermanentCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color) +{ + int n0, n1, n2, j, line, lines[2]; + vec3_t start1, end1, start2, end2; + + //make a cross in the hit plane at the hit point + VectorCopy(point, start1); + VectorCopy(point, end1); + VectorCopy(point, start2); + VectorCopy(point, end2); + + n0 = type % 3; + n1 = (type + 1) % 3; + n2 = (type + 2) % 3; + start1[n1] -= 6; + start1[n2] -= 6; + end1[n1] += 6; + end1[n2] += 6; + start2[n1] += 6; + start2[n2] -= 6; + end2[n1] -= 6; + end2[n2] += 6; + + start1[n0] = (dist - (start1[n1] * normal[n1] + + start1[n2] * normal[n2])) / normal[n0]; + end1[n0] = (dist - (end1[n1] * normal[n1] + + end1[n2] * normal[n2])) / normal[n0]; + start2[n0] = (dist - (start2[n1] * normal[n1] + + start2[n2] * normal[n2])) / normal[n0]; + end2[n0] = (dist - (end2[n1] * normal[n1] + + end2[n2] * normal[n2])) / normal[n0]; + + for (j = 0, line = 0; j < 2 && line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + numdebuglines++; + } //end if + else if (!debuglinevisible[line]) + { + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + } //end else + } //end for + botimport.DebugLineShow(lines[0], start1, end1, color); + botimport.DebugLineShow(lines[1], start2, end2, color); +} //end of the function AAS_DrawPlaneCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + vec3_t bboxcorners[8]; + int lines[3]; + int i, j, line; + + //upper corners + bboxcorners[0][0] = origin[0] + maxs[0]; + bboxcorners[0][1] = origin[1] + maxs[1]; + bboxcorners[0][2] = origin[2] + maxs[2]; + // + bboxcorners[1][0] = origin[0] + mins[0]; + bboxcorners[1][1] = origin[1] + maxs[1]; + bboxcorners[1][2] = origin[2] + maxs[2]; + // + bboxcorners[2][0] = origin[0] + mins[0]; + bboxcorners[2][1] = origin[1] + mins[1]; + bboxcorners[2][2] = origin[2] + maxs[2]; + // + bboxcorners[3][0] = origin[0] + maxs[0]; + bboxcorners[3][1] = origin[1] + mins[1]; + bboxcorners[3][2] = origin[2] + maxs[2]; + //lower corners + Com_Memcpy(bboxcorners[4], bboxcorners[0], sizeof(vec3_t) * 4); + for (i = 0; i < 4; i++) bboxcorners[4 + i][2] = origin[2] + mins[2]; + //draw bounding box + for (i = 0; i < 4; i++) + { + for (j = 0, line = 0; j < 3 && line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + numdebuglines++; + } //end if + else if (!debuglinevisible[line]) + { + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + } //end else + } //end for + //top plane + botimport.DebugLineShow(lines[0], bboxcorners[i], + bboxcorners[(i+1)&3], LINECOLOR_RED); + //bottom plane + botimport.DebugLineShow(lines[1], bboxcorners[4+i], + bboxcorners[4+((i+1)&3)], LINECOLOR_RED); + //vertical lines + botimport.DebugLineShow(lines[2], bboxcorners[i], + bboxcorners[4+i], LINECOLOR_RED); + } //end for +} //end of the function AAS_ShowBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowFace(int facenum) +{ + int i, color, edgenum; + aas_edge_t *edge; + aas_face_t *face; + aas_plane_t *plane; + vec3_t start, end; + + color = LINECOLOR_YELLOW; + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //walk through the edges of the face + for (i = 0; i < face->numedges; i++) + { + //edge number + edgenum = abs(aasworld.edgeindex[face->firstedge + i]); + //check if edge number is in range + if (edgenum >= aasworld.numedges) + { + botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); + } //end if + edge = &aasworld.edges[edgenum]; + if (color == LINECOLOR_RED) color = LINECOLOR_GREEN; + else if (color == LINECOLOR_GREEN) color = LINECOLOR_BLUE; + else if (color == LINECOLOR_BLUE) color = LINECOLOR_YELLOW; + else color = LINECOLOR_RED; + AAS_DebugLine(aasworld.vertexes[edge->v[0]], + aasworld.vertexes[edge->v[1]], + color); + } //end for + plane = &aasworld.planes[face->planenum]; + edgenum = abs(aasworld.edgeindex[face->firstedge]); + edge = &aasworld.edges[edgenum]; + VectorCopy(aasworld.vertexes[edge->v[0]], start); + VectorMA(start, 20, plane->normal, end); + AAS_DebugLine(start, end, LINECOLOR_RED); +} //end of the function AAS_ShowFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowFacePolygon(int facenum, int color, int flip) +{ + int i, edgenum, numpoints; + vec3_t points[128]; + aas_edge_t *edge; + aas_face_t *face; + + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //walk through the edges of the face + numpoints = 0; + if (flip) + { + for (i = face->numedges-1; i >= 0; i--) + { + //edge number + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]); + numpoints++; + } //end for + } //end if + else + { + for (i = 0; i < face->numedges; i++) + { + //edge number + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]); + numpoints++; + } //end for + } //end else + AAS_ShowPolygon(color, numpoints, points); +} //end of the function AAS_ShowFacePolygon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowArea(int areanum, int groundfacesonly) +{ + int areaedges[MAX_DEBUGLINES]; + int numareaedges, i, j, n, color = 0, line; + int facenum, edgenum; + aas_area_t *area; + aas_face_t *face; + aas_edge_t *edge; + + // + numareaedges = 0; + // + if (areanum < 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", + areanum, aasworld.numareas); + return; + } //end if + //pointer to the convex area + area = &aasworld.areas[areanum]; + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //ground faces only + if (groundfacesonly) + { + if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) continue; + } //end if + //walk through the edges of the face + for (j = 0; j < face->numedges; j++) + { + //edge number + edgenum = abs(aasworld.edgeindex[face->firstedge + j]); + //check if edge number is in range + if (edgenum >= aasworld.numedges) + { + botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); + } //end if + //check if the edge is stored already + for (n = 0; n < numareaedges; n++) + { + if (areaedges[n] == edgenum) break; + } //end for + if (n == numareaedges && numareaedges < MAX_DEBUGLINES) + { + areaedges[numareaedges++] = edgenum; + } //end if + } //end for + //AAS_ShowFace(facenum); + } //end for + //draw all the edges + for (n = 0; n < numareaedges; n++) + { + for (line = 0; line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + debuglinevisible[line] = qfalse; + numdebuglines++; + } //end if + if (!debuglinevisible[line]) + { + break; + } //end else + } //end for + if (line >= MAX_DEBUGLINES) return; + edge = &aasworld.edges[areaedges[n]]; + if (color == LINECOLOR_RED) color = LINECOLOR_BLUE; + else if (color == LINECOLOR_BLUE) color = LINECOLOR_GREEN; + else if (color == LINECOLOR_GREEN) color = LINECOLOR_YELLOW; + else color = LINECOLOR_RED; + botimport.DebugLineShow(debuglines[line], + aasworld.vertexes[edge->v[0]], + aasworld.vertexes[edge->v[1]], + color); + debuglinevisible[line] = qtrue; + } //end for*/ +} //end of the function AAS_ShowArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly) +{ + int i, facenum; + aas_area_t *area; + aas_face_t *face; + + // + if (areanum < 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", + areanum, aasworld.numareas); + return; + } //end if + //pointer to the convex area + area = &aasworld.areas[areanum]; + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //ground faces only + if (groundfacesonly) + { + if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) continue; + } //end if + AAS_ShowFacePolygon(facenum, color, face->frontarea != areanum); + } //end for +} //end of the function AAS_ShowAreaPolygons +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawCross(vec3_t origin, float size, int color) +{ + int i; + vec3_t start, end; + + for (i = 0; i < 3; i++) + { + VectorCopy(origin, start); + start[i] += size; + VectorCopy(origin, end); + end[i] -= size; + AAS_DebugLine(start, end, color); + } //end for +} //end of the function AAS_DrawCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PrintTravelType(int traveltype) +{ +#ifdef DEBUG + char *str; + // + switch(traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_INVALID: str = "TRAVEL_INVALID"; break; + case TRAVEL_WALK: str = "TRAVEL_WALK"; break; + case TRAVEL_CROUCH: str = "TRAVEL_CROUCH"; break; + case TRAVEL_BARRIERJUMP: str = "TRAVEL_BARRIERJUMP"; break; + case TRAVEL_JUMP: str = "TRAVEL_JUMP"; break; + case TRAVEL_LADDER: str = "TRAVEL_LADDER"; break; + case TRAVEL_WALKOFFLEDGE: str = "TRAVEL_WALKOFFLEDGE"; break; + case TRAVEL_SWIM: str = "TRAVEL_SWIM"; break; + case TRAVEL_WATERJUMP: str = "TRAVEL_WATERJUMP"; break; + case TRAVEL_TELEPORT: str = "TRAVEL_TELEPORT"; break; + case TRAVEL_ELEVATOR: str = "TRAVEL_ELEVATOR"; break; + case TRAVEL_ROCKETJUMP: str = "TRAVEL_ROCKETJUMP"; break; + case TRAVEL_BFGJUMP: str = "TRAVEL_BFGJUMP"; break; + case TRAVEL_GRAPPLEHOOK: str = "TRAVEL_GRAPPLEHOOK"; break; + case TRAVEL_JUMPPAD: str = "TRAVEL_JUMPPAD"; break; + case TRAVEL_FUNCBOB: str = "TRAVEL_FUNCBOB"; break; + default: str = "UNKNOWN TRAVEL TYPE"; break; + } //end switch + botimport.Print(PRT_MESSAGE, "%s", str); +#endif +} //end of the function AAS_PrintTravelType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor) +{ + vec3_t dir, cross, p1, p2, up = {0, 0, 1}; + float dot; + + VectorSubtract(end, start, dir); + VectorNormalize(dir); + dot = DotProduct(dir, up); + if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0); + else CrossProduct(dir, up, cross); + + VectorMA(end, -6, dir, p1); + VectorCopy(p1, p2); + VectorMA(p1, 6, cross, p1); + VectorMA(p2, -6, cross, p2); + + AAS_DebugLine(start, end, linecolor); + AAS_DebugLine(p1, end, arrowcolor); + AAS_DebugLine(p2, end, arrowcolor); +} //end of the function AAS_DrawArrow +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowReachability(aas_reachability_t *reach) +{ + vec3_t dir, cmdmove, velocity; + float speed, zvel; + aas_clientmove_t move; + + AAS_ShowAreaPolygons(reach->areanum, 5, qtrue); + //AAS_ShowArea(reach->areanum, qtrue); + AAS_DrawArrow(reach->start, reach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); + // + if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP || + (reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE) + { + AAS_HorizontalVelocityForJump(aassettings.phys_jumpvel, reach->start, reach->end, &speed); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //set the velocity + VectorScale(dir, speed, velocity); + //set the command movement + VectorClear(cmdmove); + cmdmove[2] = aassettings.phys_jumpvel; + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 3, 30, 0.1f, + SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE, 0, qtrue); + // + if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) + { + AAS_JumpReachRunStart(reach, dir); + AAS_DrawCross(dir, 4, LINECOLOR_BLUE); + } //end if + } //end if + else if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) + { + zvel = AAS_RocketJumpZVelocity(reach->start); + AAS_HorizontalVelocityForJump(zvel, reach->start, reach->end, &speed); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //get command movement + VectorScale(dir, speed, cmdmove); + VectorSet(velocity, 0, 0, zvel); + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue); + } //end else if + else if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) + { + VectorSet(cmdmove, 0, 0, 0); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //set the velocity + //NOTE: the edgenum is the horizontal velocity + VectorScale(dir, reach->edgenum, velocity); + //NOTE: the facenum is the Z velocity + velocity[2] = reach->facenum; + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue); + } //end else if +} //end of the function AAS_ShowReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowReachableAreas(int areanum) +{ + aas_areasettings_t *settings; + static aas_reachability_t reach; + static int index, lastareanum; + static float lasttime; + + if (areanum != lastareanum) + { + index = 0; + lastareanum = areanum; + } //end if + settings = &aasworld.areasettings[areanum]; + // + if (!settings->numreachableareas) return; + // + if (index >= settings->numreachableareas) index = 0; + // + if (AAS_Time() - lasttime > 1.5) + { + Com_Memcpy(&reach, &aasworld.reachability[settings->firstreachablearea + index], sizeof(aas_reachability_t)); + index++; + lasttime = AAS_Time(); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + AAS_ShowReachability(&reach); +} //end of the function ShowReachableAreas + +void AAS_FloodAreas_r(int areanum, int cluster, int *done) +{ + int nextareanum, i, facenum; + aas_area_t *area; + aas_face_t *face; + aas_areasettings_t *settings; + aas_reachability_t *reach; + + AAS_ShowAreaPolygons(areanum, 1, qtrue); + //pointer to the convex area + area = &aasworld.areas[areanum]; + settings = &aasworld.areasettings[areanum]; + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + if (face->frontarea == areanum) + nextareanum = face->backarea; + else + nextareanum = face->frontarea; + if (!nextareanum) + continue; + if (done[nextareanum]) + continue; + done[nextareanum] = qtrue; + if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL) + continue; + if (AAS_AreaCluster(nextareanum) != cluster) + continue; + AAS_FloodAreas_r(nextareanum, cluster, done); + } //end for + // + for (i = 0; i < settings->numreachableareas; i++) + { + reach = &aasworld.reachability[settings->firstreachablearea + i]; + nextareanum = reach->areanum; + if (!nextareanum) + continue; + if (done[nextareanum]) + continue; + done[nextareanum] = qtrue; + if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL) + continue; + if (AAS_AreaCluster(nextareanum) != cluster) + continue; + /* + if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE) + { + AAS_DebugLine(reach->start, reach->end, 1); + } + */ + AAS_FloodAreas_r(nextareanum, cluster, done); + } +} + +void AAS_FloodAreas(vec3_t origin) +{ + int areanum, cluster, *done; + + done = (int *) GetClearedMemory(aasworld.numareas * sizeof(int)); + areanum = AAS_PointAreaNum(origin); + cluster = AAS_AreaCluster(areanum); + AAS_FloodAreas_r(areanum, cluster, done); +} + +#endif // _XBOX diff --git a/codemp/botlib/be_aas_debug.h b/codemp/botlib/be_aas_debug.h new file mode 100644 index 0000000..c59e896 --- /dev/null +++ b/codemp/botlib/be_aas_debug.h @@ -0,0 +1,45 @@ + +/***************************************************************************** + * name: be_aas_debug.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_debug.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +//clear the shown debug lines +void AAS_ClearShownDebugLines(void); +// +void AAS_ClearShownPolygons(void); +//show a debug line +void AAS_DebugLine(vec3_t start, vec3_t end, int color); +//show a permenent line +void AAS_PermanentLine(vec3_t start, vec3_t end, int color); +//show a permanent cross +void AAS_DrawPermanentCross(vec3_t origin, float size, int color); +//draw a cross in the plane +void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color); +//show a bounding box +void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs); +//show a face +void AAS_ShowFace(int facenum); +//show an area +void AAS_ShowArea(int areanum, int groundfacesonly); +// +void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly); +//draw a cros +void AAS_DrawCross(vec3_t origin, float size, int color); +//print the travel type +void AAS_PrintTravelType(int traveltype); +//draw an arrow +void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor); +//visualize the given reachability +void AAS_ShowReachability(struct aas_reachability_s *reach); +//show the reachable areas from the given area +void AAS_ShowReachableAreas(int areanum); + diff --git a/codemp/botlib/be_aas_def.h b/codemp/botlib/be_aas_def.h new file mode 100644 index 0000000..3747c75 --- /dev/null +++ b/codemp/botlib/be_aas_def.h @@ -0,0 +1,295 @@ + +/***************************************************************************** + * name: be_aas_def.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_def.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:43:54 $ + * + *****************************************************************************/ + +//debugging on +#define AAS_DEBUG + +// these are also in q_shared.h - argh (rjr) +#ifdef _XBOX +#define MAX_CLIENTS 10 +#else +#define MAX_CLIENTS 32 +#endif +#define MAX_RADAR_ENTITIES MAX_GENTITIES +#define MAX_MODELS 512 // these are sent over the net as 8 bits +#define MAX_SOUNDS 256 // so they cannot be blindly increased + +// these are also in bg_public.h - argh (rjr) +#define CS_SCORES 32 +#define CS_MODELS (CS_SCORES+MAX_CLIENTS) +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) + +#define DF_AASENTNUMBER(x) (x - aasworld.entities) +#define DF_NUMBERAASENT(x) (&aasworld.entities[x]) +#define DF_AASENTCLIENT(x) (x - aasworld.entities - 1) +#define DF_CLIENTAASENT(x) (&aasworld.entities[x + 1]) + +#ifndef MAX_PATH + #define MAX_PATH MAX_QPATH +#endif + +//string index (for model, sound and image index) +typedef struct aas_stringindex_s +{ + int numindexes; + char **index; +} aas_stringindex_t; + +//structure to link entities to areas and areas to entities +typedef struct aas_link_s +{ + int entnum; + int areanum; + struct aas_link_s *next_ent, *prev_ent; + struct aas_link_s *next_area, *prev_area; +} aas_link_t; + +//structure to link entities to leaves and leaves to entities +typedef struct bsp_link_s +{ + int entnum; + int leafnum; + struct bsp_link_s *next_ent, *prev_ent; + struct bsp_link_s *next_leaf, *prev_leaf; +} bsp_link_t; + +typedef struct bsp_entdata_s +{ + vec3_t origin; + vec3_t angles; + vec3_t absmins; + vec3_t absmaxs; + int solid; + int modelnum; +} bsp_entdata_t; + +//entity +typedef struct aas_entity_s +{ + //entity info + aas_entityinfo_t i; + //links into the AAS areas + aas_link_t *areas; + //links into the BSP leaves + bsp_link_t *leaves; +} aas_entity_t; + +typedef struct aas_settings_s +{ + vec3_t phys_gravitydirection; + float phys_friction; + float phys_stopspeed; + float phys_gravity; + float phys_waterfriction; + float phys_watergravity; + float phys_maxvelocity; + float phys_maxwalkvelocity; + float phys_maxcrouchvelocity; + float phys_maxswimvelocity; + float phys_walkaccelerate; + float phys_airaccelerate; + float phys_swimaccelerate; + float phys_maxstep; + float phys_maxsteepness; + float phys_maxwaterjump; + float phys_maxbarrier; + float phys_jumpvel; + float phys_falldelta5; + float phys_falldelta10; + float rs_waterjump; + float rs_teleport; + float rs_barrierjump; + float rs_startcrouch; + float rs_startgrapple; + float rs_startwalkoffledge; + float rs_startjump; + float rs_rocketjump; + float rs_bfgjump; + float rs_jumppad; + float rs_aircontrolledjumppad; + float rs_funcbob; + float rs_startelevator; + float rs_falldamage5; + float rs_falldamage10; + float rs_maxfallheight; + float rs_maxjumpfallheight; +} aas_settings_t; + +#define CACHETYPE_PORTAL 0 +#define CACHETYPE_AREA 1 + +//routing cache +typedef struct aas_routingcache_s +{ + byte type; //portal or area cache + float time; //last time accessed or updated + int size; //size of the routing cache + int cluster; //cluster the cache is for + int areanum; //area the cache is created for + vec3_t origin; //origin within the area + float starttraveltime; //travel time to start with + int travelflags; //combinations of the travel flags + struct aas_routingcache_s *prev, *next; + struct aas_routingcache_s *time_prev, *time_next; + unsigned char *reachabilities; //reachabilities used for routing + unsigned short int traveltimes[1]; //travel time for every area (variable sized) +} aas_routingcache_t; + +//fields for the routing algorithm +typedef struct aas_routingupdate_s +{ + int cluster; + int areanum; //area number of the update + vec3_t start; //start point the area was entered + unsigned short int tmptraveltime; //temporary travel time + unsigned short int *areatraveltimes; //travel times within the area + qboolean inlist; //true if the update is in the list + struct aas_routingupdate_s *next; + struct aas_routingupdate_s *prev; +} aas_routingupdate_t; + +//reversed reachability link +typedef struct aas_reversedlink_s +{ + int linknum; //the aas_areareachability_t + int areanum; //reachable from this area + struct aas_reversedlink_s *next; //next link +} aas_reversedlink_t; + +//reversed area reachability +typedef struct aas_reversedreachability_s +{ + int numlinks; + aas_reversedlink_t *first; +} aas_reversedreachability_t; + +//areas a reachability goes through +typedef struct aas_reachabilityareas_s +{ + int firstarea, numareas; +} aas_reachabilityareas_t; + +typedef struct aas_s +{ + int loaded; //true when an AAS file is loaded + int initialized; //true when AAS has been initialized + int savefile; //set true when file should be saved + int bspchecksum; + //current time + float time; + int numframes; + //name of the aas file + char filename[MAX_PATH]; + char mapname[MAX_PATH]; + //bounding boxes + int numbboxes; + aas_bbox_t *bboxes; + //vertexes + int numvertexes; + aas_vertex_t *vertexes; + //planes + int numplanes; + aas_plane_t *planes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + //convex area settings + int numareasettings; + aas_areasettings_t *areasettings; + //reachablity list + int reachabilitysize; + aas_reachability_t *reachability; + //nodes of the bsp tree + int numnodes; + aas_node_t *nodes; + //cluster portals + int numportals; + aas_portal_t *portals; + //cluster portal index + int portalindexsize; + aas_portalindex_t *portalindex; + //clusters + int numclusters; + aas_cluster_t *clusters; + // + int numreachabilityareas; + float reachabilitytime; + //enities linked in the areas + aas_link_t *linkheap; //heap with link structures + int linkheapsize; //size of the link heap + aas_link_t *freelinks; //first free link + aas_link_t **arealinkedentities; //entities linked into areas + //entities + int maxentities; + int maxclients; + aas_entity_t *entities; + //string indexes + char *configstrings[MAX_CONFIGSTRINGS]; + int indexessetup; + //index to retrieve travel flag for a travel type + int travelflagfortype[MAX_TRAVELTYPES]; + //travel flags for each area based on contents + int *areacontentstravelflags; + //routing update + aas_routingupdate_t *areaupdate; + aas_routingupdate_t *portalupdate; + //number of routing updates during a frame (reset every frame) + int frameroutingupdates; + //reversed reachability links + aas_reversedreachability_t *reversedreachability; + //travel times within the areas + unsigned short ***areatraveltimes; + //array of size numclusters with cluster cache + aas_routingcache_t ***clusterareacache; + aas_routingcache_t **portalcache; + //cache list sorted on time + aas_routingcache_t *oldestcache; // start of cache list sorted on time + aas_routingcache_t *newestcache; // end of cache list sorted on time + //maximum travel time through portal areas + int *portalmaxtraveltimes; + //areas the reachabilities go through + int *reachabilityareaindex; + aas_reachabilityareas_t *reachabilityareas; +} aas_t; + +#define AASINTERN + +#ifndef BSPCINCLUDE + +#include "be_aas_main.h" +#include "be_aas_entity.h" +#include "be_aas_sample.h" +#include "be_aas_cluster.h" +#include "be_aas_reach.h" +#include "be_aas_route.h" +#include "be_aas_routealt.h" +#include "be_aas_debug.h" +#include "be_aas_file.h" +#include "be_aas_optimize.h" +#include "be_aas_bsp.h" +#include "be_aas_move.h" + +#endif //BSPCINCLUDE diff --git a/codemp/botlib/be_aas_entity.cpp b/codemp/botlib/be_aas_entity.cpp new file mode 100644 index 0000000..c60f3dd --- /dev/null +++ b/codemp/botlib/be_aas_entity.cpp @@ -0,0 +1,420 @@ + +/***************************************************************************** + * name: be_aas_entity.c + * + * desc: AAS entities + * + * $Archive: /MissionPack/code/botlib/be_aas_entity.c $ + * $Author: Zaphod $ + * $Revision: 11 $ + * $Modtime: 11/22/00 8:50a $ + * $Date: 11/22/00 8:55a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define MASK_SOLID CONTENTS_PLAYERCLIP + +//FIXME: these might change +enum { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_MOVER +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_UpdateEntity(int entnum, bot_entitystate_t *state) +{ + int relink; + aas_entity_t *ent; + vec3_t absmins, absmaxs; + + if (!aasworld.loaded) + { + botimport.Print(PRT_MESSAGE, "AAS_UpdateEntity: not loaded\n"); + return BLERR_NOAASFILE; + } //end if + + ent = &aasworld.entities[entnum]; + + if (!state) { + //unlink the entity + AAS_UnlinkFromAreas(ent->areas); + //unlink the entity from the BSP leaves + AAS_UnlinkFromBSPLeaves(ent->leaves); + // + ent->areas = NULL; + // + ent->leaves = NULL; + return BLERR_NOERROR; + } + + ent->i.update_time = AAS_Time() - ent->i.ltime; + ent->i.type = state->type; + ent->i.flags = state->flags; + ent->i.ltime = AAS_Time(); + VectorCopy(ent->i.origin, ent->i.lastvisorigin); + VectorCopy(state->old_origin, ent->i.old_origin); + ent->i.solid = state->solid; + ent->i.groundent = state->groundent; + ent->i.modelindex = state->modelindex; + ent->i.modelindex2 = state->modelindex2; + ent->i.frame = state->frame; + ent->i.event = state->event; + ent->i.eventParm = state->eventParm; + ent->i.powerups = state->powerups; + ent->i.weapon = state->weapon; + ent->i.legsAnim = state->legsAnim; + ent->i.torsoAnim = state->torsoAnim; + //number of the entity + ent->i.number = entnum; + //updated so set valid flag + ent->i.valid = qtrue; + //link everything the first frame + if (aasworld.numframes == 1) relink = qtrue; + else relink = qfalse; + // + if (ent->i.solid == SOLID_BSP) + { + //if the angles of the model changed + if (!VectorCompare(state->angles, ent->i.angles)) + { + VectorCopy(state->angles, ent->i.angles); + relink = qtrue; + } //end if + //get the mins and maxs of the model + //FIXME: rotate mins and maxs + AAS_BSPModelMinsMaxsOrigin(ent->i.modelindex, ent->i.angles, ent->i.mins, ent->i.maxs, NULL); + } //end if + else if (ent->i.solid == SOLID_BBOX) + { + //if the bounding box size changed + if (!VectorCompare(state->mins, ent->i.mins) || + !VectorCompare(state->maxs, ent->i.maxs)) + { + VectorCopy(state->mins, ent->i.mins); + VectorCopy(state->maxs, ent->i.maxs); + relink = qtrue; + } //end if + VectorCopy(state->angles, ent->i.angles); + } //end if + //if the origin changed + if (!VectorCompare(state->origin, ent->i.origin)) + { + VectorCopy(state->origin, ent->i.origin); + relink = qtrue; + } //end if + //if the entity should be relinked + if (relink) + { + //don't link the world model + if (entnum != ENTITYNUM_WORLD) + { + //absolute mins and maxs + VectorAdd(ent->i.mins, ent->i.origin, absmins); + VectorAdd(ent->i.maxs, ent->i.origin, absmaxs); + //unlink the entity + AAS_UnlinkFromAreas(ent->areas); + //relink the entity to the AAS areas (use the larges bbox) + ent->areas = AAS_LinkEntityClientBBox(absmins, absmaxs, entnum, PRESENCE_NORMAL); + //unlink the entity from the BSP leaves + AAS_UnlinkFromBSPLeaves(ent->leaves); + //link the entity to the world BSP tree + ent->leaves = AAS_BSPLinkEntity(absmins, absmaxs, entnum, 0); + } //end if + } //end if + return BLERR_NOERROR; +} //end of the function AAS_UpdateEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityInfo(int entnum, aas_entityinfo_t *info) +{ + if (!aasworld.initialized) + { + botimport.Print(PRT_FATAL, "AAS_EntityInfo: aasworld not initialized\n"); + Com_Memset(info, 0, sizeof(aas_entityinfo_t)); + return; + } //end if + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityInfo: entnum %d out of range\n", entnum); + Com_Memset(info, 0, sizeof(aas_entityinfo_t)); + return; + } //end if + + Com_Memcpy(info, &aasworld.entities[entnum].i, sizeof(aas_entityinfo_t)); +} //end of the function AAS_EntityInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityOrigin(int entnum, vec3_t origin) +{ + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityOrigin: entnum %d out of range\n", entnum); + VectorClear(origin); + return; + } //end if + + VectorCopy(aasworld.entities[entnum].i.origin, origin); +} //end of the function AAS_EntityOrigin +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityModelindex(int entnum) +{ + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityModelindex: entnum %d out of range\n", entnum); + return 0; + } //end if + return aasworld.entities[entnum].i.modelindex; +} //end of the function AAS_EntityModelindex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityType(int entnum) +{ + if (!aasworld.initialized) return 0; + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityType: entnum %d out of range\n", entnum); + return 0; + } //end if + return aasworld.entities[entnum].i.type; +} //end of the AAS_EntityType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityModelNum(int entnum) +{ + if (!aasworld.initialized) return 0; + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityModelNum: entnum %d out of range\n", entnum); + return 0; + } //end if + return aasworld.entities[entnum].i.modelindex; +} //end of the function AAS_EntityModelNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin) +{ + int i; + aas_entity_t *ent; + + for (i = 0; i < aasworld.maxentities; i++) + { + ent = &aasworld.entities[i]; + if (ent->i.type == ET_MOVER) + { + if (ent->i.modelindex == modelnum) + { + VectorCopy(ent->i.origin, origin); + return qtrue; + } //end if + } //end if + } //end for + return qfalse; +} //end of the function AAS_OriginOfMoverWithModelNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs) +{ + aas_entity_t *ent; + + if (!aasworld.initialized) return; + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntitySize: entnum %d out of range\n", entnum); + return; + } //end if + + ent = &aasworld.entities[entnum]; + VectorCopy(ent->i.mins, mins); + VectorCopy(ent->i.maxs, maxs); +} //end of the function AAS_EntitySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata) +{ + aas_entity_t *ent; + + ent = &aasworld.entities[entnum]; + VectorCopy(ent->i.origin, entdata->origin); + VectorCopy(ent->i.angles, entdata->angles); + VectorAdd(ent->i.origin, ent->i.mins, entdata->absmins); + VectorAdd(ent->i.origin, ent->i.maxs, entdata->absmaxs); + entdata->solid = ent->i.solid; + entdata->modelnum = ent->i.modelindex - 1; +} //end of the function AAS_EntityBSPData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ResetEntityLinks(void) +{ + int i; + for (i = 0; i < aasworld.maxentities; i++) + { + aasworld.entities[i].areas = NULL; + aasworld.entities[i].leaves = NULL; + } //end for +} //end of the function AAS_ResetEntityLinks +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InvalidateEntities(void) +{ + int i; + for (i = 0; i < aasworld.maxentities; i++) + { + aasworld.entities[i].i.valid = qfalse; + aasworld.entities[i].i.number = i; + } //end for +} //end of the function AAS_InvalidateEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkInvalidEntities(void) +{ + int i; + aas_entity_t *ent; + + for (i = 0; i < aasworld.maxentities; i++) + { + ent = &aasworld.entities[i]; + if (!ent->i.valid) + { + AAS_UnlinkFromAreas( ent->areas ); + ent->areas = NULL; + AAS_UnlinkFromBSPLeaves( ent->leaves ); + ent->leaves = NULL; + } //end for + } //end for +} //end of the function AAS_UnlinkInvalidEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearestEntity(vec3_t origin, int modelindex) +{ + int i, bestentnum; + float dist, bestdist; + aas_entity_t *ent; + vec3_t dir; + + bestentnum = 0; + bestdist = 99999; + for (i = 0; i < aasworld.maxentities; i++) + { + ent = &aasworld.entities[i]; + if (ent->i.modelindex != modelindex) continue; + VectorSubtract(ent->i.origin, origin, dir); + if (abs(dir[0]) < 40) + { + if (abs(dir[1]) < 40) + { + dist = VectorLength(dir); + if (dist < bestdist) + { + bestdist = dist; + bestentnum = i; + } //end if + } //end if + } //end if + } //end for + return bestentnum; +} //end of the function AAS_NearestEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableEntityArea(int entnum) +{ + aas_entity_t *ent; + + ent = &aasworld.entities[entnum]; + return AAS_BestReachableLinkArea(ent->areas); +} //end of the function AAS_BestReachableEntityArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextEntity(int entnum) +{ + if (!aasworld.loaded) return 0; + + if (entnum < 0) entnum = -1; + while(++entnum < aasworld.maxentities) + { + if (aasworld.entities[entnum].i.valid) return entnum; + } //end while + return 0; +} //end of the function AAS_NextEntity diff --git a/codemp/botlib/be_aas_entity.h b/codemp/botlib/be_aas_entity.h new file mode 100644 index 0000000..f8009b4 --- /dev/null +++ b/codemp/botlib/be_aas_entity.h @@ -0,0 +1,46 @@ + +/***************************************************************************** + * name: be_aas_entity.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_entity.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//invalidates all entity infos +void AAS_InvalidateEntities(void); +//unlink not updated entities +void AAS_UnlinkInvalidEntities(void); +//resets the entity AAS and BSP links (sets areas and leaves pointers to NULL) +void AAS_ResetEntityLinks(void); +//updates an entity +int AAS_UpdateEntity(int ent, bot_entitystate_t *state); +//gives the entity data used for collision detection +void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata); +#endif //AASINTERN + +//returns the size of the entity bounding box in mins and maxs +void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs); +//returns the BSP model number of the entity +int AAS_EntityModelNum(int entnum); +//returns the origin of an entity with the given model number +int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin); +//returns the best reachable area the entity is situated in +int AAS_BestReachableEntityArea(int entnum); +//returns the info of the given entity +void AAS_EntityInfo(int entnum, aas_entityinfo_t *info); +//returns the next entity +int AAS_NextEntity(int entnum); +//returns the origin of the entity +void AAS_EntityOrigin(int entnum, vec3_t origin); +//returns the entity type +int AAS_EntityType(int entnum); +//returns the model index of the entity +int AAS_EntityModelindex(int entnum); + diff --git a/codemp/botlib/be_aas_file.cpp b/codemp/botlib/be_aas_file.cpp new file mode 100644 index 0000000..e0b1611 --- /dev/null +++ b/codemp/botlib/be_aas_file.cpp @@ -0,0 +1,565 @@ + +/***************************************************************************** + * name: be_aas_file.c + * + * desc: AAS file loading/writing + * + * $Archive: /MissionPack/code/botlib/be_aas_file.c $ + * $Author: Zaphod $ + * $Revision: 5 $ + * $Modtime: 5/16/01 2:36p $ + * $Date: 5/16/01 2:41p $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +//#define AASFILEDEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SwapAASData(void) +{ + int i, j; + //bounding boxes + for (i = 0; i < aasworld.numbboxes; i++) + { + aasworld.bboxes[i].presencetype = LittleLong(aasworld.bboxes[i].presencetype); + aasworld.bboxes[i].flags = LittleLong(aasworld.bboxes[i].flags); + for (j = 0; j < 3; j++) + { + aasworld.bboxes[i].mins[j] = LittleLong(aasworld.bboxes[i].mins[j]); + aasworld.bboxes[i].maxs[j] = LittleLong(aasworld.bboxes[i].maxs[j]); + } //end for + } //end for + //vertexes + for (i = 0; i < aasworld.numvertexes; i++) + { + for (j = 0; j < 3; j++) + aasworld.vertexes[i][j] = LittleFloat(aasworld.vertexes[i][j]); + } //end for + //planes + for (i = 0; i < aasworld.numplanes; i++) + { + for (j = 0; j < 3; j++) + aasworld.planes[i].normal[j] = LittleFloat(aasworld.planes[i].normal[j]); + aasworld.planes[i].dist = LittleFloat(aasworld.planes[i].dist); + aasworld.planes[i].type = LittleLong(aasworld.planes[i].type); + } //end for + //edges + for (i = 0; i < aasworld.numedges; i++) + { + aasworld.edges[i].v[0] = LittleLong(aasworld.edges[i].v[0]); + aasworld.edges[i].v[1] = LittleLong(aasworld.edges[i].v[1]); + } //end for + //edgeindex + for (i = 0; i < aasworld.edgeindexsize; i++) + { + aasworld.edgeindex[i] = LittleLong(aasworld.edgeindex[i]); + } //end for + //faces + for (i = 0; i < aasworld.numfaces; i++) + { + aasworld.faces[i].planenum = LittleLong(aasworld.faces[i].planenum); + aasworld.faces[i].faceflags = LittleLong(aasworld.faces[i].faceflags); + aasworld.faces[i].numedges = LittleLong(aasworld.faces[i].numedges); + aasworld.faces[i].firstedge = LittleLong(aasworld.faces[i].firstedge); + aasworld.faces[i].frontarea = LittleLong(aasworld.faces[i].frontarea); + aasworld.faces[i].backarea = LittleLong(aasworld.faces[i].backarea); + } //end for + //face index + for (i = 0; i < aasworld.faceindexsize; i++) + { + aasworld.faceindex[i] = LittleLong(aasworld.faceindex[i]); + } //end for + //convex areas + for (i = 0; i < aasworld.numareas; i++) + { + aasworld.areas[i].areanum = LittleLong(aasworld.areas[i].areanum); + aasworld.areas[i].numfaces = LittleLong(aasworld.areas[i].numfaces); + aasworld.areas[i].firstface = LittleLong(aasworld.areas[i].firstface); + for (j = 0; j < 3; j++) + { + aasworld.areas[i].mins[j] = LittleFloat(aasworld.areas[i].mins[j]); + aasworld.areas[i].maxs[j] = LittleFloat(aasworld.areas[i].maxs[j]); + aasworld.areas[i].center[j] = LittleFloat(aasworld.areas[i].center[j]); + } //end for + } //end for + //area settings + for (i = 0; i < aasworld.numareasettings; i++) + { + aasworld.areasettings[i].contents = LittleLong(aasworld.areasettings[i].contents); + aasworld.areasettings[i].areaflags = LittleLong(aasworld.areasettings[i].areaflags); + aasworld.areasettings[i].presencetype = LittleLong(aasworld.areasettings[i].presencetype); + aasworld.areasettings[i].cluster = LittleLong(aasworld.areasettings[i].cluster); + aasworld.areasettings[i].clusterareanum = LittleLong(aasworld.areasettings[i].clusterareanum); + aasworld.areasettings[i].numreachableareas = LittleLong(aasworld.areasettings[i].numreachableareas); + aasworld.areasettings[i].firstreachablearea = LittleLong(aasworld.areasettings[i].firstreachablearea); + } //end for + //area reachability + for (i = 0; i < aasworld.reachabilitysize; i++) + { + aasworld.reachability[i].areanum = LittleLong(aasworld.reachability[i].areanum); + aasworld.reachability[i].facenum = LittleLong(aasworld.reachability[i].facenum); + aasworld.reachability[i].edgenum = LittleLong(aasworld.reachability[i].edgenum); + for (j = 0; j < 3; j++) + { + aasworld.reachability[i].start[j] = LittleFloat(aasworld.reachability[i].start[j]); + aasworld.reachability[i].end[j] = LittleFloat(aasworld.reachability[i].end[j]); + } //end for + aasworld.reachability[i].traveltype = LittleLong(aasworld.reachability[i].traveltype); + aasworld.reachability[i].traveltime = LittleShort(aasworld.reachability[i].traveltime); + } //end for + //nodes + for (i = 0; i < aasworld.numnodes; i++) + { + aasworld.nodes[i].planenum = LittleLong(aasworld.nodes[i].planenum); + aasworld.nodes[i].children[0] = LittleLong(aasworld.nodes[i].children[0]); + aasworld.nodes[i].children[1] = LittleLong(aasworld.nodes[i].children[1]); + } //end for + //cluster portals + for (i = 0; i < aasworld.numportals; i++) + { + aasworld.portals[i].areanum = LittleLong(aasworld.portals[i].areanum); + aasworld.portals[i].frontcluster = LittleLong(aasworld.portals[i].frontcluster); + aasworld.portals[i].backcluster = LittleLong(aasworld.portals[i].backcluster); + aasworld.portals[i].clusterareanum[0] = LittleLong(aasworld.portals[i].clusterareanum[0]); + aasworld.portals[i].clusterareanum[1] = LittleLong(aasworld.portals[i].clusterareanum[1]); + } //end for + //cluster portal index + for (i = 0; i < aasworld.portalindexsize; i++) + { + aasworld.portalindex[i] = LittleLong(aasworld.portalindex[i]); + } //end for + //cluster + for (i = 0; i < aasworld.numclusters; i++) + { + aasworld.clusters[i].numareas = LittleLong(aasworld.clusters[i].numareas); + aasworld.clusters[i].numreachabilityareas = LittleLong(aasworld.clusters[i].numreachabilityareas); + aasworld.clusters[i].numportals = LittleLong(aasworld.clusters[i].numportals); + aasworld.clusters[i].firstportal = LittleLong(aasworld.clusters[i].firstportal); + } //end for +} //end of the function AAS_SwapAASData +//=========================================================================== +// dump the current loaded aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpAASData(void) +{ + aasworld.numbboxes = 0; + if (aasworld.bboxes) FreeMemory(aasworld.bboxes); + aasworld.bboxes = NULL; + aasworld.numvertexes = 0; + if (aasworld.vertexes) FreeMemory(aasworld.vertexes); + aasworld.vertexes = NULL; + aasworld.numplanes = 0; + if (aasworld.planes) FreeMemory(aasworld.planes); + aasworld.planes = NULL; + aasworld.numedges = 0; + if (aasworld.edges) FreeMemory(aasworld.edges); + aasworld.edges = NULL; + aasworld.edgeindexsize = 0; + if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); + aasworld.edgeindex = NULL; + aasworld.numfaces = 0; + if (aasworld.faces) FreeMemory(aasworld.faces); + aasworld.faces = NULL; + aasworld.faceindexsize = 0; + if (aasworld.faceindex) FreeMemory(aasworld.faceindex); + aasworld.faceindex = NULL; + aasworld.numareas = 0; + if (aasworld.areas) FreeMemory(aasworld.areas); + aasworld.areas = NULL; + aasworld.numareasettings = 0; + if (aasworld.areasettings) FreeMemory(aasworld.areasettings); + aasworld.areasettings = NULL; + aasworld.reachabilitysize = 0; + if (aasworld.reachability) FreeMemory(aasworld.reachability); + aasworld.reachability = NULL; + aasworld.numnodes = 0; + if (aasworld.nodes) FreeMemory(aasworld.nodes); + aasworld.nodes = NULL; + aasworld.numportals = 0; + if (aasworld.portals) FreeMemory(aasworld.portals); + aasworld.portals = NULL; + aasworld.numportals = 0; + if (aasworld.portalindex) FreeMemory(aasworld.portalindex); + aasworld.portalindex = NULL; + aasworld.portalindexsize = 0; + if (aasworld.clusters) FreeMemory(aasworld.clusters); + aasworld.clusters = NULL; + aasworld.numclusters = 0; + // + aasworld.loaded = qfalse; + aasworld.initialized = qfalse; + aasworld.savefile = qfalse; +} //end of the function AAS_DumpAASData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef AASFILEDEBUG +void AAS_FileInfo(void) +{ + int i, n, optimized; + + botimport.Print(PRT_MESSAGE, "version = %d\n", AASVERSION); + botimport.Print(PRT_MESSAGE, "numvertexes = %d\n", aasworld.numvertexes); + botimport.Print(PRT_MESSAGE, "numplanes = %d\n", aasworld.numplanes); + botimport.Print(PRT_MESSAGE, "numedges = %d\n", aasworld.numedges); + botimport.Print(PRT_MESSAGE, "edgeindexsize = %d\n", aasworld.edgeindexsize); + botimport.Print(PRT_MESSAGE, "numfaces = %d\n", aasworld.numfaces); + botimport.Print(PRT_MESSAGE, "faceindexsize = %d\n", aasworld.faceindexsize); + botimport.Print(PRT_MESSAGE, "numareas = %d\n", aasworld.numareas); + botimport.Print(PRT_MESSAGE, "numareasettings = %d\n", aasworld.numareasettings); + botimport.Print(PRT_MESSAGE, "reachabilitysize = %d\n", aasworld.reachabilitysize); + botimport.Print(PRT_MESSAGE, "numnodes = %d\n", aasworld.numnodes); + botimport.Print(PRT_MESSAGE, "numportals = %d\n", aasworld.numportals); + botimport.Print(PRT_MESSAGE, "portalindexsize = %d\n", aasworld.portalindexsize); + botimport.Print(PRT_MESSAGE, "numclusters = %d\n", aasworld.numclusters); + // + for (n = 0, i = 0; i < aasworld.numareasettings; i++) + { + if (aasworld.areasettings[i].areaflags & AREA_GROUNDED) n++; + } //end for + botimport.Print(PRT_MESSAGE, "num grounded areas = %d\n", n); + // + botimport.Print(PRT_MESSAGE, "planes size %d bytes\n", aasworld.numplanes * sizeof(aas_plane_t)); + botimport.Print(PRT_MESSAGE, "areas size %d bytes\n", aasworld.numareas * sizeof(aas_area_t)); + botimport.Print(PRT_MESSAGE, "areasettings size %d bytes\n", aasworld.numareasettings * sizeof(aas_areasettings_t)); + botimport.Print(PRT_MESSAGE, "nodes size %d bytes\n", aasworld.numnodes * sizeof(aas_node_t)); + botimport.Print(PRT_MESSAGE, "reachability size %d bytes\n", aasworld.reachabilitysize * sizeof(aas_reachability_t)); + botimport.Print(PRT_MESSAGE, "portals size %d bytes\n", aasworld.numportals * sizeof(aas_portal_t)); + botimport.Print(PRT_MESSAGE, "clusters size %d bytes\n", aasworld.numclusters * sizeof(aas_cluster_t)); + + optimized = aasworld.numplanes * sizeof(aas_plane_t) + + aasworld.numareas * sizeof(aas_area_t) + + aasworld.numareasettings * sizeof(aas_areasettings_t) + + aasworld.numnodes * sizeof(aas_node_t) + + aasworld.reachabilitysize * sizeof(aas_reachability_t) + + aasworld.numportals * sizeof(aas_portal_t) + + aasworld.numclusters * sizeof(aas_cluster_t); + botimport.Print(PRT_MESSAGE, "optimzed size %d KB\n", optimized >> 10); +} //end of the function AAS_FileInfo +#endif //AASFILEDEBUG +//=========================================================================== +// allocate memory and read a lump of a AAS file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_LoadAASLump(fileHandle_t fp, int offset, int length, int *lastoffset, int size) +{ + char *buf; + // + if (!length) + { + //just alloc a dummy + return (char *) GetClearedHunkMemory(size+1); + } //end if + //seek to the data + if (offset != *lastoffset) + { + botimport.Print(PRT_WARNING, "AAS file not sequentially read\n"); + if (botimport.FS_Seek(fp, offset, FS_SEEK_SET)) + { + AAS_Error("can't seek to aas lump\n"); + AAS_DumpAASData(); + botimport.FS_FCloseFile(fp); + return 0; + } //end if + } //end if + //allocate memory + buf = (char *) GetClearedHunkMemory(length+1); + //read the data + if (length) + { + botimport.FS_Read(buf, length, fp ); + *lastoffset += length; + } //end if + return buf; +} //end of the function AAS_LoadAASLump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DData(unsigned char *data, int size) +{ + int i; + + for (i = 0; i < size; i++) + { + data[i] ^= (unsigned char) i * 119; + } //end for +} //end of the function AAS_DData +//=========================================================================== +// load an aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadAASFile(char *filename) +{ + fileHandle_t fp; + aas_header_t header; + int offset, length, lastoffset; + + botimport.Print(PRT_MESSAGE, "trying to load %s\n", filename); + //dump current loaded aas file + AAS_DumpAASData(); + //open the file + botimport.FS_FOpenFile( filename, &fp, FS_READ ); + if (!fp) + { + AAS_Error("can't open %s\n", filename); + return BLERR_CANNOTOPENAASFILE; + } //end if + //read the header + botimport.FS_Read(&header, sizeof(aas_header_t), fp ); + lastoffset = sizeof(aas_header_t); + //check header identification + header.ident = LittleLong(header.ident); + if (header.ident != AASID) + { + AAS_Error("%s is not an AAS file\n", filename); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEID; + } //end if + //check the version + header.version = LittleLong(header.version); + // + if (header.version != AASVERSION_OLD && header.version != AASVERSION) + { + AAS_Error("aas file %s is version %i, not %i\n", filename, header.version, AASVERSION); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEVERSION; + } //end if + // + if (header.version == AASVERSION) + { + AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); + } //end if + // + aasworld.bspchecksum = atoi(LibVarGetString( "sv_mapChecksum")); + if (LittleLong(header.bspchecksum) != aasworld.bspchecksum) + { + AAS_Error("aas file %s is out of date\n", filename); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEVERSION; + } //end if + //load the lumps: + //bounding boxes + offset = LittleLong(header.lumps[AASLUMP_BBOXES].fileofs); + length = LittleLong(header.lumps[AASLUMP_BBOXES].filelen); + aasworld.bboxes = (aas_bbox_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_bbox_t)); + aasworld.numbboxes = length / sizeof(aas_bbox_t); + if (aasworld.numbboxes && !aasworld.bboxes) return BLERR_CANNOTREADAASLUMP; + //vertexes + offset = LittleLong(header.lumps[AASLUMP_VERTEXES].fileofs); + length = LittleLong(header.lumps[AASLUMP_VERTEXES].filelen); + aasworld.vertexes = (aas_vertex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_vertex_t)); + aasworld.numvertexes = length / sizeof(aas_vertex_t); + if (aasworld.numvertexes && !aasworld.vertexes) return BLERR_CANNOTREADAASLUMP; + //planes + offset = LittleLong(header.lumps[AASLUMP_PLANES].fileofs); + length = LittleLong(header.lumps[AASLUMP_PLANES].filelen); + aasworld.planes = (aas_plane_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_plane_t)); + aasworld.numplanes = length / sizeof(aas_plane_t); + if (aasworld.numplanes && !aasworld.planes) return BLERR_CANNOTREADAASLUMP; + //edges + offset = LittleLong(header.lumps[AASLUMP_EDGES].fileofs); + length = LittleLong(header.lumps[AASLUMP_EDGES].filelen); + aasworld.edges = (aas_edge_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_edge_t)); + aasworld.numedges = length / sizeof(aas_edge_t); + if (aasworld.numedges && !aasworld.edges) return BLERR_CANNOTREADAASLUMP; + //edgeindex + offset = LittleLong(header.lumps[AASLUMP_EDGEINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_EDGEINDEX].filelen); + aasworld.edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_edgeindex_t)); + aasworld.edgeindexsize = length / sizeof(aas_edgeindex_t); + if (aasworld.edgeindexsize && !aasworld.edgeindex) return BLERR_CANNOTREADAASLUMP; + //faces + offset = LittleLong(header.lumps[AASLUMP_FACES].fileofs); + length = LittleLong(header.lumps[AASLUMP_FACES].filelen); + aasworld.faces = (aas_face_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_face_t)); + aasworld.numfaces = length / sizeof(aas_face_t); + if (aasworld.numfaces && !aasworld.faces) return BLERR_CANNOTREADAASLUMP; + //faceindex + offset = LittleLong(header.lumps[AASLUMP_FACEINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_FACEINDEX].filelen); + aasworld.faceindex = (aas_faceindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_faceindex_t)); + aasworld.faceindexsize = length / sizeof(aas_faceindex_t); + if (aasworld.faceindexsize && !aasworld.faceindex) return BLERR_CANNOTREADAASLUMP; + //convex areas + offset = LittleLong(header.lumps[AASLUMP_AREAS].fileofs); + length = LittleLong(header.lumps[AASLUMP_AREAS].filelen); + aasworld.areas = (aas_area_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_area_t)); + aasworld.numareas = length / sizeof(aas_area_t); + if (aasworld.numareas && !aasworld.areas) return BLERR_CANNOTREADAASLUMP; + //area settings + offset = LittleLong(header.lumps[AASLUMP_AREASETTINGS].fileofs); + length = LittleLong(header.lumps[AASLUMP_AREASETTINGS].filelen); + aasworld.areasettings = (aas_areasettings_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_areasettings_t)); + aasworld.numareasettings = length / sizeof(aas_areasettings_t); + if (aasworld.numareasettings && !aasworld.areasettings) return BLERR_CANNOTREADAASLUMP; + //reachability list + offset = LittleLong(header.lumps[AASLUMP_REACHABILITY].fileofs); + length = LittleLong(header.lumps[AASLUMP_REACHABILITY].filelen); + aasworld.reachability = (aas_reachability_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_reachability_t)); + aasworld.reachabilitysize = length / sizeof(aas_reachability_t); + if (aasworld.reachabilitysize && !aasworld.reachability) return BLERR_CANNOTREADAASLUMP; + //nodes + offset = LittleLong(header.lumps[AASLUMP_NODES].fileofs); + length = LittleLong(header.lumps[AASLUMP_NODES].filelen); + aasworld.nodes = (aas_node_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_node_t)); + aasworld.numnodes = length / sizeof(aas_node_t); + if (aasworld.numnodes && !aasworld.nodes) return BLERR_CANNOTREADAASLUMP; + //cluster portals + offset = LittleLong(header.lumps[AASLUMP_PORTALS].fileofs); + length = LittleLong(header.lumps[AASLUMP_PORTALS].filelen); + aasworld.portals = (aas_portal_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_portal_t)); + aasworld.numportals = length / sizeof(aas_portal_t); + if (aasworld.numportals && !aasworld.portals) return BLERR_CANNOTREADAASLUMP; + //cluster portal index + offset = LittleLong(header.lumps[AASLUMP_PORTALINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_PORTALINDEX].filelen); + aasworld.portalindex = (aas_portalindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_portalindex_t)); + aasworld.portalindexsize = length / sizeof(aas_portalindex_t); + if (aasworld.portalindexsize && !aasworld.portalindex) return BLERR_CANNOTREADAASLUMP; + //clusters + offset = LittleLong(header.lumps[AASLUMP_CLUSTERS].fileofs); + length = LittleLong(header.lumps[AASLUMP_CLUSTERS].filelen); + aasworld.clusters = (aas_cluster_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_cluster_t)); + aasworld.numclusters = length / sizeof(aas_cluster_t); + if (aasworld.numclusters && !aasworld.clusters) return BLERR_CANNOTREADAASLUMP; + //swap everything + AAS_SwapAASData(); + //aas file is loaded + aasworld.loaded = qtrue; + //close the file + botimport.FS_FCloseFile(fp); + // +#ifdef AASFILEDEBUG + AAS_FileInfo(); +#endif //AASFILEDEBUG + // + return BLERR_NOERROR; +} //end of the function AAS_LoadAASFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static int AAS_WriteAASLump_offset; + +int AAS_WriteAASLump(fileHandle_t fp, aas_header_t *h, int lumpnum, void *data, int length) +{ + aas_lump_t *lump; + + lump = &h->lumps[lumpnum]; + + lump->fileofs = LittleLong(AAS_WriteAASLump_offset); //LittleLong(ftell(fp)); + lump->filelen = LittleLong(length); + + if (length > 0) + { + botimport.FS_Write(data, length, fp ); + } //end if + + AAS_WriteAASLump_offset += length; + + return qtrue; +} //end of the function AAS_WriteAASLump +//=========================================================================== +// aas data is useless after writing to file because it is byte swapped +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_WriteAASFile(char *filename) +{ + aas_header_t header; + fileHandle_t fp; + + botimport.Print(PRT_MESSAGE, "writing %s\n", filename); + //swap the aas data + AAS_SwapAASData(); + //initialize the file header + Com_Memset(&header, 0, sizeof(aas_header_t)); + header.ident = LittleLong(AASID); + header.version = LittleLong(AASVERSION); + header.bspchecksum = LittleLong(aasworld.bspchecksum); + //open a new file + botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); + if (!fp) + { + botimport.Print(PRT_ERROR, "error opening %s\n", filename); + return qfalse; + } //end if + //write the header + botimport.FS_Write(&header, sizeof(aas_header_t), fp); + AAS_WriteAASLump_offset = sizeof(aas_header_t); + //add the data lumps to the file + if (!AAS_WriteAASLump(fp, &header, AASLUMP_BBOXES, aasworld.bboxes, + aasworld.numbboxes * sizeof(aas_bbox_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_VERTEXES, aasworld.vertexes, + aasworld.numvertexes * sizeof(aas_vertex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PLANES, aasworld.planes, + aasworld.numplanes * sizeof(aas_plane_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGES, aasworld.edges, + aasworld.numedges * sizeof(aas_edge_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGEINDEX, aasworld.edgeindex, + aasworld.edgeindexsize * sizeof(aas_edgeindex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACES, aasworld.faces, + aasworld.numfaces * sizeof(aas_face_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACEINDEX, aasworld.faceindex, + aasworld.faceindexsize * sizeof(aas_faceindex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREAS, aasworld.areas, + aasworld.numareas * sizeof(aas_area_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREASETTINGS, aasworld.areasettings, + aasworld.numareasettings * sizeof(aas_areasettings_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_REACHABILITY, aasworld.reachability, + aasworld.reachabilitysize * sizeof(aas_reachability_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_NODES, aasworld.nodes, + aasworld.numnodes * sizeof(aas_node_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALS, aasworld.portals, + aasworld.numportals * sizeof(aas_portal_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALINDEX, aasworld.portalindex, + aasworld.portalindexsize * sizeof(aas_portalindex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_CLUSTERS, aasworld.clusters, + aasworld.numclusters * sizeof(aas_cluster_t))) return qfalse; + //rewrite the header with the added lumps + botimport.FS_Seek(fp, 0, FS_SEEK_SET); + AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); + botimport.FS_Write(&header, sizeof(aas_header_t), fp); + //close the file + botimport.FS_FCloseFile(fp); + return qtrue; +} //end of the function AAS_WriteAASFile diff --git a/codemp/botlib/be_aas_file.h b/codemp/botlib/be_aas_file.h new file mode 100644 index 0000000..f5ea328 --- /dev/null +++ b/codemp/botlib/be_aas_file.h @@ -0,0 +1,25 @@ + +/***************************************************************************** + * name: be_aas_file.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_file.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//loads the AAS file with the given name +int AAS_LoadAASFile(char *filename); +//writes an AAS file with the given name +qboolean AAS_WriteAASFile(char *filename); +//dumps the loaded AAS data +void AAS_DumpAASData(void); +//print AAS file information +void AAS_FileInfo(void); +#endif //AASINTERN + diff --git a/codemp/botlib/be_aas_funcs.h b/codemp/botlib/be_aas_funcs.h new file mode 100644 index 0000000..82c04ae --- /dev/null +++ b/codemp/botlib/be_aas_funcs.h @@ -0,0 +1,30 @@ + +/***************************************************************************** + * name: be_aas_funcs.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_funcs.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifndef BSPCINCLUDE + +#include "be_aas_main.h" +#include "be_aas_entity.h" +#include "be_aas_sample.h" +#include "be_aas_cluster.h" +#include "be_aas_reach.h" +#include "be_aas_route.h" +#include "be_aas_routealt.h" +#include "be_aas_debug.h" +#include "be_aas_file.h" +#include "be_aas_optimize.h" +#include "be_aas_bsp.h" +#include "be_aas_move.h" + +#endif //BSPCINCLUDE diff --git a/codemp/botlib/be_aas_main.cpp b/codemp/botlib/be_aas_main.cpp new file mode 100644 index 0000000..2ce0b6b --- /dev/null +++ b/codemp/botlib/be_aas_main.cpp @@ -0,0 +1,412 @@ + +/***************************************************************************** + * name: be_aas_main.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_main.c $ + * $Author: Mrelusive $ + * $Revision: 8 $ + * $Modtime: 11/28/00 7:52a $ + * $Date: 11/28/00 7:52a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +aas_t aasworld; + +libvar_t *saveroutingcache; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL AAS_Error(char *fmt, ...) +{ + char str[1024]; + va_list arglist; + + va_start(arglist, fmt); + vsprintf(str, fmt, arglist); + va_end(arglist); + botimport.Print(PRT_FATAL, str); +} //end of the function AAS_Error +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_StringFromIndex(char *indexname, char *stringindex[], int numindexes, int index) +{ + if (!aasworld.indexessetup) + { + botimport.Print(PRT_ERROR, "%s: index %d not setup\n", indexname, index); + return ""; + } //end if + if (index < 0 || index >= numindexes) + { + botimport.Print(PRT_ERROR, "%s: index %d out of range\n", indexname, index); + return ""; + } //end if + if (!stringindex[index]) + { + if (index) + { + botimport.Print(PRT_ERROR, "%s: reference to unused index %d\n", indexname, index); + } //end if + return ""; + } //end if + return stringindex[index]; +} //end of the function AAS_StringFromIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IndexFromString(char *indexname, char *stringindex[], int numindexes, char *string) +{ + int i; + if (!aasworld.indexessetup) + { + botimport.Print(PRT_ERROR, "%s: index not setup \"%s\"\n", indexname, string); + return 0; + } //end if + for (i = 0; i < numindexes; i++) + { + if (!stringindex[i]) continue; + if (!Q_stricmp(stringindex[i], string)) return i; + } //end for + return 0; +} //end of the function AAS_IndexFromString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_ModelFromIndex(int index) +{ + return AAS_StringFromIndex("ModelFromIndex", &aasworld.configstrings[CS_MODELS], MAX_MODELS, index); +} //end of the function AAS_ModelFromIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IndexFromModel(char *modelname) +{ + return AAS_IndexFromString("IndexFromModel", &aasworld.configstrings[CS_MODELS], MAX_MODELS, modelname); +} //end of the function AAS_IndexFromModel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdateStringIndexes(int numconfigstrings, char *configstrings[]) +{ + int i; + //set string pointers and copy the strings + for (i = 0; i < numconfigstrings; i++) + { + if (configstrings[i]) + { + //if (aasworld.configstrings[i]) FreeMemory(aasworld.configstrings[i]); + aasworld.configstrings[i] = (char *) GetMemory(strlen(configstrings[i]) + 1); + strcpy(aasworld.configstrings[i], configstrings[i]); + } //end if + } //end for + aasworld.indexessetup = qtrue; +} //end of the function AAS_UpdateStringIndexes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Loaded(void) +{ + return aasworld.loaded; +} //end of the function AAS_Loaded +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Initialized(void) +{ + return aasworld.initialized; +} //end of the function AAS_Initialized +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetInitialized(void) +{ + aasworld.initialized = qtrue; + botimport.Print(PRT_MESSAGE, "AAS initialized.\n"); +#ifdef DEBUG + //create all the routing cache + //AAS_CreateAllRoutingCache(); + // + //AAS_RoutingInfo(); +#endif +} //end of the function AAS_SetInitialized +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ContinueInit(float time) +{ + //if no AAS file loaded + if (!aasworld.loaded) return; + //if AAS is already initialized + if (aasworld.initialized) return; + //calculate reachability, if not finished return + if (AAS_ContinueInitReachability(time)) return; + //initialize clustering for the new map + AAS_InitClustering(); + //if reachability has been calculated and an AAS file should be written + //or there is a forced data optimization + if (aasworld.savefile || ((int)LibVarGetValue("forcewrite"))) + { + //optimize the AAS data + if ((int)LibVarValue("aasoptimize", "0")) AAS_Optimize(); + //save the AAS file + if (AAS_WriteAASFile(aasworld.filename)) + { + botimport.Print(PRT_MESSAGE, "%s written succesfully\n", aasworld.filename); + } //end if + else + { + botimport.Print(PRT_ERROR, "couldn't write %s\n", aasworld.filename); + } //end else + } //end if + //initialize the routing + AAS_InitRouting(); + //at this point AAS is initialized + AAS_SetInitialized(); +} //end of the function AAS_ContinueInit +//=========================================================================== +// called at the start of every frame +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_StartFrame(float time) +{ + aasworld.time = time; + //unlink all entities that were not updated last frame + AAS_UnlinkInvalidEntities(); + //invalidate the entities + AAS_InvalidateEntities(); + //initialize AAS + AAS_ContinueInit(time); + // + aasworld.frameroutingupdates = 0; + // + if (bot_developer) + { + if (LibVarGetValue("showcacheupdates")) + { + AAS_RoutingInfo(); + LibVarSet("showcacheupdates", "0"); + } //end if + if (LibVarGetValue("showmemoryusage")) + { + PrintUsedMemorySize(); + LibVarSet("showmemoryusage", "0"); + } //end if + if (LibVarGetValue("memorydump")) + { + PrintMemoryLabels(); + LibVarSet("memorydump", "0"); + } //end if + } //end if + // + if (saveroutingcache->value) + { + AAS_WriteRouteCache(); + LibVarSet("saveroutingcache", "0"); + } //end if + // + aasworld.numframes++; + return BLERR_NOERROR; +} //end of the function AAS_StartFrame +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_Time(void) +{ + return aasworld.time; +} //end of the function AAS_Time +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) +{ + vec3_t pVec, vec; + + VectorSubtract( point, vStart, pVec ); + VectorSubtract( vEnd, vStart, vec ); + VectorNormalize( vec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); +} //end of the function AAS_ProjectPointOntoVector +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadFiles(const char *mapname) +{ + int errnum; + char aasfile[MAX_PATH]; +// char bspfile[MAX_PATH]; + + strcpy(aasworld.mapname, mapname); + //NOTE: first reset the entity links into the AAS areas and BSP leaves + // the AAS link heap and BSP link heap are reset after respectively the + // AAS file and BSP file are loaded + AAS_ResetEntityLinks(); + // load bsp info + AAS_LoadBSPFile(); + + //load the aas file + Com_sprintf(aasfile, MAX_PATH, "maps/%s.aas", mapname); + errnum = AAS_LoadAASFile(aasfile); + if (errnum != BLERR_NOERROR) + return errnum; + + botimport.Print(PRT_MESSAGE, "loaded %s\n", aasfile); + strncpy(aasworld.filename, aasfile, MAX_PATH); + return BLERR_NOERROR; +} //end of the function AAS_LoadFiles +//=========================================================================== +// called everytime a map changes +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadMap(const char *mapname) +{ + int errnum; + + //if no mapname is provided then the string indexes are updated + if (!mapname) + { + return 0; + } //end if + // + aasworld.initialized = qfalse; + //NOTE: free the routing caches before loading a new map because + // to free the caches the old number of areas, number of clusters + // and number of areas in a clusters must be available + AAS_FreeRoutingCaches(); + //load the map + errnum = AAS_LoadFiles(mapname); + if (errnum != BLERR_NOERROR) + { + aasworld.loaded = qfalse; + return errnum; + } //end if + // + AAS_InitSettings(); + //initialize the AAS link heap for the new map + AAS_InitAASLinkHeap(); + //initialize the AAS linked entities for the new map + AAS_InitAASLinkedEntities(); + //initialize reachability for the new map + AAS_InitReachability(); + //initialize the alternative routing + AAS_InitAlternativeRouting(); + //everything went ok + return 0; +} //end of the function AAS_LoadMap +//=========================================================================== +// called when the library is first loaded +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Setup(void) +{ + aasworld.maxclients = (int) LibVarValue("maxclients", "128"); + aasworld.maxentities = (int) LibVarValue("maxentities", "1024"); + // as soon as it's set to 1 the routing cache will be saved + saveroutingcache = LibVar("saveroutingcache", "0"); + //allocate memory for the entities + if (aasworld.entities) FreeMemory(aasworld.entities); + aasworld.entities = (aas_entity_t *) GetClearedHunkMemory(aasworld.maxentities * sizeof(aas_entity_t)); + //invalidate all the entities + AAS_InvalidateEntities(); + //force some recalculations + //LibVarSet("forceclustering", "1"); //force clustering calculation + //LibVarSet("forcereachability", "1"); //force reachability calculation + aasworld.numframes = 0; + return BLERR_NOERROR; +} //end of the function AAS_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Shutdown(void) +{ + AAS_ShutdownAlternativeRouting(); + // + AAS_DumpBSPData(); + //free routing caches + AAS_FreeRoutingCaches(); + //free aas link heap + AAS_FreeAASLinkHeap(); + //free aas linked entities + AAS_FreeAASLinkedEntities(); + //free the aas data + AAS_DumpAASData(); + //free the entities + if (aasworld.entities) FreeMemory(aasworld.entities); + //clear the aasworld structure + Com_Memset(&aasworld, 0, sizeof(aas_t)); + //aas has not been initialized + aasworld.initialized = qfalse; + //NOTE: as soon as a new .bsp file is loaded the .bsp file memory is + // freed an reallocated, so there's no need to free that memory here + //print shutdown +// botimport.Print(PRT_MESSAGE, "AAS shutdown.\n"); +} //end of the function AAS_Shutdown diff --git a/codemp/botlib/be_aas_main.h b/codemp/botlib/be_aas_main.h new file mode 100644 index 0000000..6128380 --- /dev/null +++ b/codemp/botlib/be_aas_main.h @@ -0,0 +1,44 @@ + +/***************************************************************************** + * name: be_aas_main.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_main.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifdef AASINTERN + +extern aas_t aasworld; + +//AAS error message +void QDECL AAS_Error(char *fmt, ...); +//set AAS initialized +void AAS_SetInitialized(void); +//setup AAS with the given number of entities and clients +int AAS_Setup(void); +//shutdown AAS +void AAS_Shutdown(void); +//start a new map +int AAS_LoadMap(const char *mapname); +//start a new time frame +int AAS_StartFrame(float time); +#endif //AASINTERN + +//returns true if AAS is initialized +int AAS_Initialized(void); +//returns true if the AAS file is loaded +int AAS_Loaded(void); +//returns the model name from the given index +char *AAS_ModelFromIndex(int index); +//returns the index from the given model name +int AAS_IndexFromModel(char *modelname); +//returns the current time +float AAS_Time(void); +// +void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ); diff --git a/codemp/botlib/be_aas_move.cpp b/codemp/botlib/be_aas_move.cpp new file mode 100644 index 0000000..f5243ee --- /dev/null +++ b/codemp/botlib/be_aas_move.cpp @@ -0,0 +1,1090 @@ + +/***************************************************************************** + * name: be_aas_move.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_move.c $ + * $Author: Ttimo $ + * $Revision: 9 $ + * $Modtime: 4/13/01 4:45p $ + * $Date: 4/13/01 4:45p $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +aas_settings_t aassettings; + +//#define AAS_MOVE_DEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + vec3_t end; + bsp_trace_t trace; + + VectorCopy(origin, end); + end[2] -= 100; + trace = AAS_Trace(origin, mins, maxs, end, 0, CONTENTS_SOLID); + if (trace.startsolid) return qfalse; + VectorCopy(trace.endpos, origin); + return qtrue; +} //end of the function AAS_DropToFloor +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitSettings(void) +{ + aassettings.phys_gravitydirection[0] = 0; + aassettings.phys_gravitydirection[1] = 0; + aassettings.phys_gravitydirection[2] = -1; + aassettings.phys_friction = LibVarValue("phys_friction", "6"); + aassettings.phys_stopspeed = LibVarValue("phys_stopspeed", "100"); + aassettings.phys_gravity = LibVarValue("phys_gravity", "800"); + aassettings.phys_waterfriction = LibVarValue("phys_waterfriction", "1"); + aassettings.phys_watergravity = LibVarValue("phys_watergravity", "400"); + aassettings.phys_maxvelocity = LibVarValue("phys_maxvelocity", "320"); + aassettings.phys_maxwalkvelocity = LibVarValue("phys_maxwalkvelocity", "320"); + aassettings.phys_maxcrouchvelocity = LibVarValue("phys_maxcrouchvelocity", "100"); + aassettings.phys_maxswimvelocity = LibVarValue("phys_maxswimvelocity", "150"); + aassettings.phys_walkaccelerate = LibVarValue("phys_walkaccelerate", "10"); + aassettings.phys_airaccelerate = LibVarValue("phys_airaccelerate", "1"); + aassettings.phys_swimaccelerate = LibVarValue("phys_swimaccelerate", "4"); + aassettings.phys_maxstep = LibVarValue("phys_maxstep", "19"); + aassettings.phys_maxsteepness = LibVarValue("phys_maxsteepness", "0.7"); + aassettings.phys_maxwaterjump = LibVarValue("phys_maxwaterjump", "18"); + aassettings.phys_maxbarrier = LibVarValue("phys_maxbarrier", "33"); + aassettings.phys_jumpvel = LibVarValue("phys_jumpvel", "270"); + aassettings.phys_falldelta5 = LibVarValue("phys_falldelta5", "40"); + aassettings.phys_falldelta10 = LibVarValue("phys_falldelta10", "60"); + aassettings.rs_waterjump = LibVarValue("rs_waterjump", "400"); + aassettings.rs_teleport = LibVarValue("rs_teleport", "50"); + aassettings.rs_barrierjump = LibVarValue("rs_barrierjump", "100"); + aassettings.rs_startcrouch = LibVarValue("rs_startcrouch", "300"); + aassettings.rs_startgrapple = LibVarValue("rs_startgrapple", "500"); + aassettings.rs_startwalkoffledge = LibVarValue("rs_startwalkoffledge", "70"); + aassettings.rs_startjump = LibVarValue("rs_startjump", "300"); + aassettings.rs_rocketjump = LibVarValue("rs_rocketjump", "500"); + aassettings.rs_bfgjump = LibVarValue("rs_bfgjump", "500"); + aassettings.rs_jumppad = LibVarValue("rs_jumppad", "250"); + aassettings.rs_aircontrolledjumppad = LibVarValue("rs_aircontrolledjumppad", "300"); + aassettings.rs_funcbob = LibVarValue("rs_funcbob", "300"); + aassettings.rs_startelevator = LibVarValue("rs_startelevator", "50"); + aassettings.rs_falldamage5 = LibVarValue("rs_falldamage5", "300"); + aassettings.rs_falldamage10 = LibVarValue("rs_falldamage10", "500"); + aassettings.rs_maxfallheight = LibVarValue("rs_maxfallheight", "0"); + aassettings.rs_maxjumpfallheight = LibVarValue("rs_maxjumpfallheight", "450"); +} //end of the function AAS_InitSettings +//=========================================================================== +// returns qtrue if the bot is against a ladder +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AgainstLadder(vec3_t origin) +{ + int areanum, i, facenum, side; + vec3_t org; + aas_plane_t *plane; + aas_face_t *face; + aas_area_t *area; + + VectorCopy(origin, org); + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[0] += 1; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[1] += 1; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[0] -= 2; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[1] -= 2; + areanum = AAS_PointAreaNum(org); + } //end if + } //end if + } //end if + } //end if + //if in solid... wrrr shouldn't happen + if (!areanum) return qfalse; + //if not in a ladder area + if (!(aasworld.areasettings[areanum].areaflags & AREA_LADDER)) return qfalse; + //if a crouch only area + if (!(aasworld.areasettings[areanum].presencetype & PRESENCE_NORMAL)) return qfalse; + // + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + side = facenum < 0; + face = &aasworld.faces[abs(facenum)]; + //if the face isn't a ladder face + if (!(face->faceflags & FACE_LADDER)) continue; + //get the plane the face is in + plane = &aasworld.planes[face->planenum ^ side]; + //if the origin is pretty close to the plane + if (abs(DotProduct(plane->normal, origin) - plane->dist) < 3) + { + if (AAS_PointInsideFace(abs(facenum), origin, 0.1f)) return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_AgainstLadder +//=========================================================================== +// returns qtrue if the bot is on the ground +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OnGround(vec3_t origin, int presencetype, int passent) +{ + aas_trace_t trace; + vec3_t end, up = {0, 0, 1}; + aas_plane_t *plane; + + VectorCopy(origin, end); + end[2] -= 10; + + trace = AAS_TraceClientBBox(origin, end, presencetype, passent); + + //if in solid + if (trace.startsolid) return qfalse; + //if nothing hit at all + if (trace.fraction >= 1.0) return qfalse; + //if too far from the hit plane + if (origin[2] - trace.endpos[2] > 10) return qfalse; + //check if the plane isn't too steep + plane = AAS_PlaneFromNum(trace.planenum); + if (DotProduct(plane->normal, up) < aassettings.phys_maxsteepness) return qfalse; + //the bot is on the ground + return qtrue; +} //end of the function AAS_OnGround +//=========================================================================== +// returns qtrue if a bot at the given position is swimming +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Swimming(vec3_t origin) +{ + vec3_t testorg; + + VectorCopy(origin, testorg); + testorg[2] -= 2; + if (AAS_PointContents(testorg) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) return qtrue; + return qfalse; +} //end of the function AAS_Swimming +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static vec3_t VEC_UP = {0, -1, 0}; +static vec3_t MOVEDIR_UP = {0, 0, 1}; +static vec3_t VEC_DOWN = {0, -2, 0}; +static vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void AAS_SetMovedir(vec3_t angles, vec3_t movedir) +{ + if (VectorCompare(angles, VEC_UP)) + { + VectorCopy(MOVEDIR_UP, movedir); + } //end if + else if (VectorCompare(angles, VEC_DOWN)) + { + VectorCopy(MOVEDIR_DOWN, movedir); + } //end else if + else + { + AngleVectors(angles, movedir, NULL, NULL); + } //end else +} //end of the function AAS_SetMovedir +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_JumpReachRunStart(aas_reachability_t *reach, vec3_t runstart) +{ + vec3_t hordir, start, cmdmove; + aas_clientmove_t move; + + // + hordir[0] = reach->start[0] - reach->end[0]; + hordir[1] = reach->start[1] - reach->end[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //start point + VectorCopy(reach->start, start); + start[2] += 1; + //get command movement + VectorScale(hordir, 400, cmdmove); + // + AAS_PredictClientMovement(&move, -1, start, PRESENCE_NORMAL, qtrue, + vec3_origin, cmdmove, 1, 2, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA| + SE_HITGROUNDDAMAGE|SE_GAP, 0, qfalse); + VectorCopy(move.endpos, runstart); + //don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + { + VectorCopy(start, runstart); + } //end if +} //end of the function AAS_JumpReachRunStart +//=========================================================================== +// returns the Z velocity when rocket jumping at the origin +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_WeaponJumpZVelocity(vec3_t origin, float radiusdamage) +{ + vec3_t kvel, v, start, end, forward, right, viewangles, dir; + float mass, knockback, points; + vec3_t rocketoffset = {8, 8, -8}; + vec3_t botmins = {-16, -16, -24}; + vec3_t botmaxs = {16, 16, 32}; + bsp_trace_t bsptrace; + + //look down (90 degrees) + viewangles[PITCH] = 90; + viewangles[YAW] = 0; + viewangles[ROLL] = 0; + //get the start point shooting from + VectorCopy(origin, start); + start[2] += 8; //view offset Z + AngleVectors(viewangles, forward, right, NULL); + start[0] += forward[0] * rocketoffset[0] + right[0] * rocketoffset[1]; + start[1] += forward[1] * rocketoffset[0] + right[1] * rocketoffset[1]; + start[2] += forward[2] * rocketoffset[0] + right[2] * rocketoffset[1] + rocketoffset[2]; + //end point of the trace + VectorMA(start, 500, forward, end); + //trace a line to get the impact point + bsptrace = AAS_Trace(start, NULL, NULL, end, 1, CONTENTS_SOLID); + //calculate the damage the bot will get from the rocket impact + VectorAdd(botmins, botmaxs, v); + VectorMA(origin, 0.5, v, v); + VectorSubtract(bsptrace.endpos, v, v); + // + points = radiusdamage - 0.5 * VectorLength(v); + if (points < 0) points = 0; + //the owner of the rocket gets half the damage + points *= 0.5; + //mass of the bot (p_client.c: PutClientInServer) + mass = 200; + //knockback is the same as the damage points + knockback = points; + //direction of the damage (from trace.endpos to bot origin) + VectorSubtract(origin, bsptrace.endpos, dir); + VectorNormalize(dir); + //damage velocity + VectorScale(dir, 1600.0 * (float)knockback / mass, kvel); //the rocket jump hack... + //rocket impact velocity + jump velocity + return kvel[2] + aassettings.phys_jumpvel; +} //end of the function AAS_WeaponJumpZVelocity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_RocketJumpZVelocity(vec3_t origin) +{ + //rocket radius damage is 120 (p_weapon.c: Weapon_RocketLauncher_Fire) + return AAS_WeaponJumpZVelocity(origin, 120); +} //end of the function AAS_RocketJumpZVelocity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_BFGJumpZVelocity(vec3_t origin) +{ + //bfg radius damage is 1000 (p_weapon.c: weapon_bfg_fire) + return AAS_WeaponJumpZVelocity(origin, 120); +} //end of the function AAS_BFGJumpZVelocity +//=========================================================================== +// applies ground friction to the given velocity +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Accelerate(vec3_t velocity, float frametime, vec3_t wishdir, float wishspeed, float accel) +{ + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct(velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } + accelspeed = accel*frametime*wishspeed; + if (accelspeed > addspeed) { + accelspeed = addspeed; + } + + for (i=0 ; i<3 ; i++) { + velocity[i] += accelspeed*wishdir[i]; + } +} //end of the function AAS_Accelerate +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AirControl(vec3_t start, vec3_t end, vec3_t velocity, vec3_t cmdmove) +{ + vec3_t dir; + + VectorSubtract(end, start, dir); +} //end of the function AAS_AirControl +//=========================================================================== +// applies ground friction to the given velocity +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ApplyFriction(vec3_t vel, float friction, float stopspeed, + float frametime) +{ + float speed, control, newspeed; + + //horizontal speed + speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]); + if (speed) + { + control = speed < stopspeed ? stopspeed : speed; + newspeed = speed - frametime * control * friction; + if (newspeed < 0) newspeed = 0; + newspeed /= speed; + vel[0] *= newspeed; + vel[1] *= newspeed; + } //end if +} //end of the function AAS_ApplyFriction +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ClipToBBox(aas_trace_t *trace, vec3_t start, vec3_t end, int presencetype, vec3_t mins, vec3_t maxs) +{ + int i, j, side; + float front, back, frac, planedist; + vec3_t bboxmins, bboxmaxs, absmins, absmaxs, dir, mid; + + AAS_PresenceTypeBoundingBox(presencetype, bboxmins, bboxmaxs); + VectorSubtract(mins, bboxmaxs, absmins); + VectorSubtract(maxs, bboxmins, absmaxs); + // + VectorCopy(end, trace->endpos); + trace->fraction = 1; + for (i = 0; i < 3; i++) + { + if (start[i] < absmins[i] && end[i] < absmins[i]) return qfalse; + if (start[i] > absmaxs[i] && end[i] > absmaxs[i]) return qfalse; + } //end for + //check bounding box collision + VectorSubtract(end, start, dir); + frac = 1; + for (i = 0; i < 3; i++) + { + //get plane to test collision with for the current axis direction + if (dir[i] > 0) planedist = absmins[i]; + else planedist = absmaxs[i]; + //calculate collision fraction + front = start[i] - planedist; + back = end[i] - planedist; + frac = front / (front-back); + //check if between bounding planes of next axis + side = i + 1; + if (side > 2) side = 0; + mid[side] = start[side] + dir[side] * frac; + if (mid[side] > absmins[side] && mid[side] < absmaxs[side]) + { + //check if between bounding planes of next axis + side++; + if (side > 2) side = 0; + mid[side] = start[side] + dir[side] * frac; + if (mid[side] > absmins[side] && mid[side] < absmaxs[side]) + { + mid[i] = planedist; + break; + } //end if + } //end if + } //end for + //if there was a collision + if (i != 3) + { + trace->startsolid = qfalse; + trace->fraction = frac; + trace->ent = 0; + trace->planenum = 0; + trace->area = 0; + trace->lastarea = 0; + //trace endpos + for (j = 0; j < 3; j++) trace->endpos[j] = start[j] + dir[j] * frac; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_ClipToBBox +//=========================================================================== +// predicts the movement +// assumes regular bounding box sizes +// NOTE: out of water jumping is not included +// NOTE: grappling hook is not included +// +// Parameter: origin : origin to start with +// presencetype : presence type to start with +// velocity : velocity to start with +// cmdmove : client command movement +// cmdframes : number of frame cmdmove is valid +// maxframes : maximum number of predicted frames +// frametime : duration of one predicted frame +// stopevent : events that stop the prediction +// stopareanum : stop as soon as entered this area +// Returns: aas_clientmove_t +// Changes Globals: - +//=========================================================================== +int AAS_ClientMovementPrediction(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, + vec3_t mins, vec3_t maxs, int visualize) +{ + float phys_friction, phys_stopspeed, phys_gravity, phys_waterfriction; + float phys_watergravity; + float phys_walkaccelerate, phys_airaccelerate, phys_swimaccelerate; + float phys_maxwalkvelocity, phys_maxcrouchvelocity, phys_maxswimvelocity; + float phys_maxstep, phys_maxsteepness, phys_jumpvel, friction; + float gravity, delta, maxvel, wishspeed, accelerate; + //float velchange, newvel; + int n, i, j, pc, step, swimming, ax, crouch, event, jump_frame, areanum; + int areas[20], numareas; + vec3_t points[20]; + vec3_t org, end, feet, start, stepend, lastorg, wishdir; + vec3_t frame_test_vel, old_frame_test_vel, left_test_vel; + vec3_t up = {0, 0, 1}; + aas_plane_t *plane, *plane2; + aas_trace_t trace, steptrace; + + if (frametime <= 0) frametime = 0.1f; + // + phys_friction = aassettings.phys_friction; + phys_stopspeed = aassettings.phys_stopspeed; + phys_gravity = aassettings.phys_gravity; + phys_waterfriction = aassettings.phys_waterfriction; + phys_watergravity = aassettings.phys_watergravity; + phys_maxwalkvelocity = aassettings.phys_maxwalkvelocity;// * frametime; + phys_maxcrouchvelocity = aassettings.phys_maxcrouchvelocity;// * frametime; + phys_maxswimvelocity = aassettings.phys_maxswimvelocity;// * frametime; + phys_walkaccelerate = aassettings.phys_walkaccelerate; + phys_airaccelerate = aassettings.phys_airaccelerate; + phys_swimaccelerate = aassettings.phys_swimaccelerate; + phys_maxstep = aassettings.phys_maxstep; + phys_maxsteepness = aassettings.phys_maxsteepness; + phys_jumpvel = aassettings.phys_jumpvel * frametime; + // + Com_Memset(move, 0, sizeof(aas_clientmove_t)); + Com_Memset(&trace, 0, sizeof(aas_trace_t)); + //start at the current origin + VectorCopy(origin, org); + org[2] += 0.25; + //velocity to test for the first frame + VectorScale(velocity, frametime, frame_test_vel); + // + jump_frame = -1; + //predict a maximum of 'maxframes' ahead + for (n = 0; n < maxframes; n++) + { + swimming = AAS_Swimming(org); + //get gravity depending on swimming or not + gravity = swimming ? phys_watergravity : phys_gravity; + //apply gravity at the START of the frame + frame_test_vel[2] = frame_test_vel[2] - (gravity * 0.1 * frametime); + //if on the ground or swimming + if (onground || swimming) + { + friction = swimming ? phys_friction : phys_waterfriction; + //apply friction + VectorScale(frame_test_vel, 1/frametime, frame_test_vel); + AAS_ApplyFriction(frame_test_vel, friction, phys_stopspeed, frametime); + VectorScale(frame_test_vel, frametime, frame_test_vel); + } //end if + crouch = qfalse; + //apply command movement + if (n < cmdframes) + { + ax = 0; + maxvel = phys_maxwalkvelocity; + accelerate = phys_airaccelerate; + VectorCopy(cmdmove, wishdir); + if (onground) + { + if (cmdmove[2] < -300) + { + crouch = qtrue; + maxvel = phys_maxcrouchvelocity; + } //end if + //if not swimming and upmove is positive then jump + if (!swimming && cmdmove[2] > 1) + { + //jump velocity minus the gravity for one frame + 5 for safety + frame_test_vel[2] = phys_jumpvel - (gravity * 0.1 * frametime) + 5; + jump_frame = n; + //jumping so air accelerate + accelerate = phys_airaccelerate; + } //end if + else + { + accelerate = phys_walkaccelerate; + } //end else + ax = 2; + } //end if + if (swimming) + { + maxvel = phys_maxswimvelocity; + accelerate = phys_swimaccelerate; + ax = 3; + } //end if + else + { + wishdir[2] = 0; + } //end else + // + wishspeed = VectorNormalize(wishdir); + if (wishspeed > maxvel) wishspeed = maxvel; + VectorScale(frame_test_vel, 1/frametime, frame_test_vel); + AAS_Accelerate(frame_test_vel, frametime, wishdir, wishspeed, accelerate); + VectorScale(frame_test_vel, frametime, frame_test_vel); + /* + for (i = 0; i < ax; i++) + { + velchange = (cmdmove[i] * frametime) - frame_test_vel[i]; + if (velchange > phys_maxacceleration) velchange = phys_maxacceleration; + else if (velchange < -phys_maxacceleration) velchange = -phys_maxacceleration; + newvel = frame_test_vel[i] + velchange; + // + if (frame_test_vel[i] <= maxvel && newvel > maxvel) frame_test_vel[i] = maxvel; + else if (frame_test_vel[i] >= -maxvel && newvel < -maxvel) frame_test_vel[i] = -maxvel; + else frame_test_vel[i] = newvel; + } //end for + */ + } //end if + if (crouch) + { + presencetype = PRESENCE_CROUCH; + } //end if + else if (presencetype == PRESENCE_CROUCH) + { + if (AAS_PointPresenceType(org) & PRESENCE_NORMAL) + { + presencetype = PRESENCE_NORMAL; + } //end if + } //end else + //save the current origin + VectorCopy(org, lastorg); + //move linear during one frame + VectorCopy(frame_test_vel, left_test_vel); + j = 0; + do + { + VectorAdd(org, left_test_vel, end); + //trace a bounding box + trace = AAS_TraceClientBBox(org, end, presencetype, entnum); + // +#ifndef _XBOX +//#ifdef AAS_MOVE_DEBUG + if (visualize) + { + if (trace.startsolid) botimport.Print(PRT_MESSAGE, "PredictMovement: start solid\n"); + AAS_DebugLine(org, trace.endpos, LINECOLOR_RED); + } //end if +//#endif //AAS_MOVE_DEBUG +#endif + // + if (stopevent & (SE_ENTERAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_TOUCHCLUSTERPORTAL)) + { + numareas = AAS_TraceAreas(org, trace.endpos, areas, points, 20); + for (i = 0; i < numareas; i++) + { + if (stopevent & SE_ENTERAREA) + { + if (areas[i] == stopareanum) + { + VectorCopy(points[i], move->endpos); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->endarea = areas[i]; + move->trace = trace; + move->stopevent = SE_ENTERAREA; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + //NOTE: if not the first frame + if ((stopevent & SE_TOUCHJUMPPAD) && n) + { + if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_JUMPPAD) + { + VectorCopy(points[i], move->endpos); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->endarea = areas[i]; + move->trace = trace; + move->stopevent = SE_TOUCHJUMPPAD; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + if (stopevent & SE_TOUCHTELEPORTER) + { + if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_TELEPORTER) + { + VectorCopy(points[i], move->endpos); + move->endarea = areas[i]; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_TOUCHTELEPORTER; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + if (stopevent & SE_TOUCHCLUSTERPORTAL) + { + if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_CLUSTERPORTAL) + { + VectorCopy(points[i], move->endpos); + move->endarea = areas[i]; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_TOUCHCLUSTERPORTAL; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end for + } //end if + // + if (stopevent & SE_HITBOUNDINGBOX) + { + if (AAS_ClipToBBox(&trace, org, trace.endpos, presencetype, mins, maxs)) + { + VectorCopy(trace.endpos, move->endpos); + move->endarea = AAS_PointAreaNum(move->endpos); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITBOUNDINGBOX; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + //move the entity to the trace end point + VectorCopy(trace.endpos, org); + //if there was a collision + if (trace.fraction < 1.0) + { + //get the plane the bounding box collided with + plane = AAS_PlaneFromNum(trace.planenum); + // + if (stopevent & SE_HITGROUNDAREA) + { + if (DotProduct(plane->normal, up) > phys_maxsteepness) + { + VectorCopy(org, start); + start[2] += 0.5; + if (AAS_PointAreaNum(start) == stopareanum) + { + VectorCopy(start, move->endpos); + move->endarea = stopareanum; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUNDAREA; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + //assume there's no step + step = qfalse; + //if it is a vertical plane and the bot didn't jump recently + if (plane->normal[2] == 0 && (jump_frame < 0 || n - jump_frame > 2)) + { + //check for a step + VectorMA(org, -0.25, plane->normal, start); + VectorCopy(start, stepend); + start[2] += phys_maxstep; + steptrace = AAS_TraceClientBBox(start, stepend, presencetype, entnum); + // + if (!steptrace.startsolid) + { + plane2 = AAS_PlaneFromNum(steptrace.planenum); + if (DotProduct(plane2->normal, up) > phys_maxsteepness) + { + VectorSubtract(end, steptrace.endpos, left_test_vel); + left_test_vel[2] = 0; + frame_test_vel[2] = 0; +#ifndef _XBOX +//#ifdef AAS_MOVE_DEBUG + if (visualize) + { + if (steptrace.endpos[2] - org[2] > 0.125) + { + VectorCopy(org, start); + start[2] = steptrace.endpos[2]; + AAS_DebugLine(org, start, LINECOLOR_BLUE); + } //end if + } //end if +//#endif //AAS_MOVE_DEBUG +#endif + org[2] = steptrace.endpos[2]; + step = qtrue; + } //end if + } //end if + } //end if + // + if (!step) + { + //velocity left to test for this frame is the projection + //of the current test velocity into the hit plane + VectorMA(left_test_vel, -DotProduct(left_test_vel, plane->normal), + plane->normal, left_test_vel); + //store the old velocity for landing check + VectorCopy(frame_test_vel, old_frame_test_vel); + //test velocity for the next frame is the projection + //of the velocity of the current frame into the hit plane + VectorMA(frame_test_vel, -DotProduct(frame_test_vel, plane->normal), + plane->normal, frame_test_vel); + //check for a landing on an almost horizontal floor + if (DotProduct(plane->normal, up) > phys_maxsteepness) + { + onground = qtrue; + } //end if + if (stopevent & SE_HITGROUNDDAMAGE) + { + delta = 0; + if (old_frame_test_vel[2] < 0 && + frame_test_vel[2] > old_frame_test_vel[2] && + !onground) + { + delta = old_frame_test_vel[2]; + } //end if + else if (onground) + { + delta = frame_test_vel[2] - old_frame_test_vel[2]; + } //end else + if (delta) + { + delta = delta * 10; + delta = delta * delta * 0.0001; + if (swimming) delta = 0; + // 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 > 40) + { + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorCopy(frame_test_vel, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUNDDAMAGE; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + } //end if + } //end if + //extra check to prevent endless loop + if (++j > 20) return qfalse; + //while there is a plane hit + } while(trace.fraction < 1.0); + //if going down + if (frame_test_vel[2] <= 10) + { + //check for a liquid at the feet of the bot + VectorCopy(org, feet); + feet[2] -= 22; + pc = AAS_PointContents(feet); + //get event from pc + event = SE_NONE; + if (pc & CONTENTS_LAVA) event |= SE_ENTERLAVA; + if (pc & CONTENTS_SLIME) event |= SE_ENTERSLIME; + if (pc & CONTENTS_WATER) event |= SE_ENTERWATER; + // + areanum = AAS_PointAreaNum(org); + if (aasworld.areasettings[areanum].contents & AREACONTENTS_LAVA) + event |= SE_ENTERLAVA; + if (aasworld.areasettings[areanum].contents & AREACONTENTS_SLIME) + event |= SE_ENTERSLIME; + if (aasworld.areasettings[areanum].contents & AREACONTENTS_WATER) + event |= SE_ENTERWATER; + //if in lava or slime + if (event & stopevent) + { + VectorCopy(org, move->endpos); + move->endarea = areanum; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->stopevent = event & stopevent; + move->presencetype = presencetype; + move->endcontents = pc; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + // + onground = AAS_OnGround(org, presencetype, entnum); + //if onground and on the ground for at least one whole frame + if (onground) + { + if (stopevent & SE_HITGROUND) + { + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUND; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + else if (stopevent & SE_LEAVEGROUND) + { + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_LEAVEGROUND; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end else if + else if (stopevent & SE_GAP) + { + aas_trace_t gaptrace; + + VectorCopy(org, start); + VectorCopy(start, end); + end[2] -= 48 + aassettings.phys_maxbarrier; + gaptrace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + //if solid is found the bot cannot walk any further and will not fall into a gap + if (!gaptrace.startsolid) + { + //if it is a gap (lower than one step height) + if (gaptrace.endpos[2] < org[2] - aassettings.phys_maxstep - 1) + { + if (!(AAS_PointContents(end) & CONTENTS_WATER)) + { + VectorCopy(lastorg, move->endpos); + move->endarea = AAS_PointAreaNum(lastorg); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_GAP; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + } //end else if + } //end for + // + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->stopevent = SE_NONE; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + // + return qtrue; +} //end of the function AAS_ClientMovementPrediction +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PredictClientMovement(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize) +{ + vec3_t mins, maxs; + return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground, + velocity, cmdmove, cmdframes, maxframes, + frametime, stopevent, stopareanum, + mins, maxs, visualize); +} //end of the function AAS_PredictClientMovement +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ClientMovementHitBBox(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + vec3_t mins, vec3_t maxs, int visualize) +{ + return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground, + velocity, cmdmove, cmdframes, maxframes, + frametime, SE_HITBOUNDINGBOX, 0, + mins, maxs, visualize); +} //end of the function AAS_ClientMovementHitBBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifndef _XBOX +void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir) +{ + vec3_t velocity, cmdmove; + aas_clientmove_t move; + + VectorClear(velocity); + if (!AAS_Swimming(origin)) dir[2] = 0; + VectorNormalize(dir); + VectorScale(dir, 400, cmdmove); + cmdmove[2] = 224; + AAS_ClearShownDebugLines(); + AAS_PredictClientMovement(&move, entnum, origin, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 13, 13, 0.1f, SE_HITGROUND, 0, qtrue);//SE_LEAVEGROUND); + if (move.stopevent & SE_LEAVEGROUND) + { + botimport.Print(PRT_MESSAGE, "leave ground\n"); + } //end if +} //end of the function TestMovementPrediction +#endif +//=========================================================================== +// calculates the horizontal velocity needed to perform a jump from start +// to end +// +// Parameter: zvel : z velocity for jump +// start : start position of jump +// end : end position of jump +// *speed : returned speed for jump +// Returns: qfalse if too high or too far from start to end +// Changes Globals: - +//=========================================================================== +int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity) +{ + float phys_gravity, phys_maxvelocity; + float maxjump, height2fall, t, top; + vec3_t dir; + + phys_gravity = aassettings.phys_gravity; + phys_maxvelocity = aassettings.phys_maxvelocity; + + //maximum height a player can jump with the given initial z velocity + maxjump = 0.5 * phys_gravity * (zvel / phys_gravity) * (zvel / phys_gravity); + //top of the parabolic jump + top = start[2] + maxjump; + //height the bot will fall from the top + height2fall = top - end[2]; + //if the goal is to high to jump to + if (height2fall < 0) + { + *velocity = phys_maxvelocity; + return 0; + } //end if + //time a player takes to fall the height + t = sqrt(height2fall / (0.5 * phys_gravity)); + //direction from start to end + VectorSubtract(end, start, dir); + // + if ( (t + zvel / phys_gravity) == 0.0f ) { + *velocity = phys_maxvelocity; + return 0; + } + //calculate horizontal speed + *velocity = sqrt(dir[0]*dir[0] + dir[1]*dir[1]) / (t + zvel / phys_gravity); + //the horizontal speed must be lower than the max speed + if (*velocity > phys_maxvelocity) + { + *velocity = phys_maxvelocity; + return 0; + } //end if + return 1; +} //end of the function AAS_HorizontalVelocityForJump diff --git a/codemp/botlib/be_aas_move.h b/codemp/botlib/be_aas_move.h new file mode 100644 index 0000000..632b80a --- /dev/null +++ b/codemp/botlib/be_aas_move.h @@ -0,0 +1,54 @@ + +/***************************************************************************** + * name: be_aas_move.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_move.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifdef AASINTERN +extern aas_settings_t aassettings; +#endif //AASINTERN + +//movement prediction +int AAS_PredictClientMovement(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize); +//predict movement until bounding box is hit +int AAS_ClientMovementHitBBox(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + vec3_t mins, vec3_t maxs, int visualize); +//returns true if on the ground at the given origin +int AAS_OnGround(vec3_t origin, int presencetype, int passent); +//returns true if swimming at the given origin +int AAS_Swimming(vec3_t origin); +//returns the jump reachability run start point +void AAS_JumpReachRunStart(struct aas_reachability_s *reach, vec3_t runstart); +//returns true if against a ladder at the given origin +int AAS_AgainstLadder(vec3_t origin); +//rocket jump Z velocity when rocket-jumping at origin +float AAS_RocketJumpZVelocity(vec3_t origin); +//bfg jump Z velocity when bfg-jumping at origin +float AAS_BFGJumpZVelocity(vec3_t origin); +//calculates the horizontal velocity needed for a jump and returns true this velocity could be calculated +int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity); +// +void AAS_SetMovedir(vec3_t angles, vec3_t movedir); +// +int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs); +// +void AAS_InitSettings(void); diff --git a/codemp/botlib/be_aas_optimize.cpp b/codemp/botlib/be_aas_optimize.cpp new file mode 100644 index 0000000..dfb654a --- /dev/null +++ b/codemp/botlib/be_aas_optimize.cpp @@ -0,0 +1,295 @@ + +/***************************************************************************** + * name: be_aas_optimize.c + * + * desc: decreases the .aas file size after the reachabilities have + * been calculated, just dumps all the faces, edges and vertexes + * + * $Archive: /MissionPack/code/botlib/be_aas_optimize.c $ + * $Author: Zaphod $ + * $Revision: 5 $ + * $Modtime: 11/22/00 8:50a $ + * $Date: 11/22/00 8:55a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_libvar.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +typedef struct optimized_s +{ + //vertexes + int numvertexes; + aas_vertex_t *vertexes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + // + int *vertexoptimizeindex; + int *edgeoptimizeindex; + int *faceoptimizeindex; +} optimized_t; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_KeepEdge(aas_edge_t *edge) +{ + return 1; +} //end of the function AAS_KeepFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OptimizeEdge(optimized_t *optimized, int edgenum) +{ + int i, optedgenum; + aas_edge_t *edge, *optedge; + + edge = &aasworld.edges[abs(edgenum)]; + if (!AAS_KeepEdge(edge)) return 0; + + optedgenum = optimized->edgeoptimizeindex[abs(edgenum)]; + if (optedgenum) + { + //keep the edge reversed sign + if (edgenum > 0) return optedgenum; + else return -optedgenum; + } //end if + + optedge = &optimized->edges[optimized->numedges]; + + for (i = 0; i < 2; i++) + { + if (optimized->vertexoptimizeindex[edge->v[i]]) + { + optedge->v[i] = optimized->vertexoptimizeindex[edge->v[i]]; + } //end if + else + { + VectorCopy(aasworld.vertexes[edge->v[i]], optimized->vertexes[optimized->numvertexes]); + optedge->v[i] = optimized->numvertexes; + optimized->vertexoptimizeindex[edge->v[i]] = optimized->numvertexes; + optimized->numvertexes++; + } //end else + } //end for + optimized->edgeoptimizeindex[abs(edgenum)] = optimized->numedges; + optedgenum = optimized->numedges; + optimized->numedges++; + //keep the edge reversed sign + if (edgenum > 0) return optedgenum; + else return -optedgenum; +} //end of the function AAS_OptimizeEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_KeepFace(aas_face_t *face) +{ + if (!(face->faceflags & FACE_LADDER)) return 0; + else return 1; +} //end of the function AAS_KeepFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OptimizeFace(optimized_t *optimized, int facenum) +{ + int i, edgenum, optedgenum, optfacenum; + aas_face_t *face, *optface; + + face = &aasworld.faces[abs(facenum)]; + if (!AAS_KeepFace(face)) return 0; + + optfacenum = optimized->faceoptimizeindex[abs(facenum)]; + if (optfacenum) + { + //keep the face side sign + if (facenum > 0) return optfacenum; + else return -optfacenum; + } //end if + + optface = &optimized->faces[optimized->numfaces]; + Com_Memcpy(optface, face, sizeof(aas_face_t)); + + optface->numedges = 0; + optface->firstedge = optimized->edgeindexsize; + for (i = 0; i < face->numedges; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + optedgenum = AAS_OptimizeEdge(optimized, edgenum); + if (optedgenum) + { + optimized->edgeindex[optface->firstedge + optface->numedges] = optedgenum; + optface->numedges++; + optimized->edgeindexsize++; + } //end if + } //end for + optimized->faceoptimizeindex[abs(facenum)] = optimized->numfaces; + optfacenum = optimized->numfaces; + optimized->numfaces++; + //keep the face side sign + if (facenum > 0) return optfacenum; + else return -optfacenum; +} //end of the function AAS_OptimizeFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeArea(optimized_t *optimized, int areanum) +{ + int i, facenum, optfacenum; + aas_area_t *area, *optarea; + + area = &aasworld.areas[areanum]; + optarea = &optimized->areas[areanum]; + Com_Memcpy(optarea, area, sizeof(aas_area_t)); + + optarea->numfaces = 0; + optarea->firstface = optimized->faceindexsize; + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + optfacenum = AAS_OptimizeFace(optimized, facenum); + if (optfacenum) + { + optimized->faceindex[optarea->firstface + optarea->numfaces] = optfacenum; + optarea->numfaces++; + optimized->faceindexsize++; + } //end if + } //end for +} //end of the function AAS_OptimizeArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeAlloc(optimized_t *optimized) +{ + optimized->vertexes = (aas_vertex_t *) GetClearedMemory(aasworld.numvertexes * sizeof(aas_vertex_t)); + optimized->numvertexes = 0; + optimized->edges = (aas_edge_t *) GetClearedMemory(aasworld.numedges * sizeof(aas_edge_t)); + optimized->numedges = 1; //edge zero is a dummy + optimized->edgeindex = (aas_edgeindex_t *) GetClearedMemory(aasworld.edgeindexsize * sizeof(aas_edgeindex_t)); + optimized->edgeindexsize = 0; + optimized->faces = (aas_face_t *) GetClearedMemory(aasworld.numfaces * sizeof(aas_face_t)); + optimized->numfaces = 1; //face zero is a dummy + optimized->faceindex = (aas_faceindex_t *) GetClearedMemory(aasworld.faceindexsize * sizeof(aas_faceindex_t)); + optimized->faceindexsize = 0; + optimized->areas = (aas_area_t *) GetClearedMemory(aasworld.numareas * sizeof(aas_area_t)); + optimized->numareas = aasworld.numareas; + // + optimized->vertexoptimizeindex = (int *) GetClearedMemory(aasworld.numvertexes * sizeof(int)); + optimized->edgeoptimizeindex = (int *) GetClearedMemory(aasworld.numedges * sizeof(int)); + optimized->faceoptimizeindex = (int *) GetClearedMemory(aasworld.numfaces * sizeof(int)); +} //end of the function AAS_OptimizeAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeStore(optimized_t *optimized) +{ + //store the optimized vertexes + if (aasworld.vertexes) FreeMemory(aasworld.vertexes); + aasworld.vertexes = optimized->vertexes; + aasworld.numvertexes = optimized->numvertexes; + //store the optimized edges + if (aasworld.edges) FreeMemory(aasworld.edges); + aasworld.edges = optimized->edges; + aasworld.numedges = optimized->numedges; + //store the optimized edge index + if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); + aasworld.edgeindex = optimized->edgeindex; + aasworld.edgeindexsize = optimized->edgeindexsize; + //store the optimized faces + if (aasworld.faces) FreeMemory(aasworld.faces); + aasworld.faces = optimized->faces; + aasworld.numfaces = optimized->numfaces; + //store the optimized face index + if (aasworld.faceindex) FreeMemory(aasworld.faceindex); + aasworld.faceindex = optimized->faceindex; + aasworld.faceindexsize = optimized->faceindexsize; + //store the optimized areas + if (aasworld.areas) FreeMemory(aasworld.areas); + aasworld.areas = optimized->areas; + aasworld.numareas = optimized->numareas; + //free optimize indexes + FreeMemory(optimized->vertexoptimizeindex); + FreeMemory(optimized->edgeoptimizeindex); + FreeMemory(optimized->faceoptimizeindex); +} //end of the function AAS_OptimizeStore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Optimize(void) +{ + int i, sign; + optimized_t optimized; + + AAS_OptimizeAlloc(&optimized); + for (i = 1; i < aasworld.numareas; i++) + { + AAS_OptimizeArea(&optimized, i); + } //end for + //reset the reachability face pointers + for (i = 0; i < aasworld.reachabilitysize; i++) + { + //NOTE: for TRAVEL_ELEVATOR the facenum is the model number of + // the elevator + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) continue; + //NOTE: for TRAVEL_JUMPPAD the facenum is the Z velocity and the edgenum is the hor velocity + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) continue; + //NOTE: for TRAVEL_FUNCBOB the facenum and edgenum contain other coded information + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) continue; + // + sign = aasworld.reachability[i].facenum; + aasworld.reachability[i].facenum = optimized.faceoptimizeindex[abs(aasworld.reachability[i].facenum)]; + if (sign < 0) aasworld.reachability[i].facenum = -aasworld.reachability[i].facenum; + sign = aasworld.reachability[i].edgenum; + aasworld.reachability[i].edgenum = optimized.edgeoptimizeindex[abs(aasworld.reachability[i].edgenum)]; + if (sign < 0) aasworld.reachability[i].edgenum = -aasworld.reachability[i].edgenum; + } //end for + //store the optimized AAS data into aasworld + AAS_OptimizeStore(&optimized); + //print some nice stuff :) + botimport.Print(PRT_MESSAGE, "AAS data optimized.\n"); +} //end of the function AAS_Optimize diff --git a/codemp/botlib/be_aas_optimize.h b/codemp/botlib/be_aas_optimize.h new file mode 100644 index 0000000..3f1e0a0 --- /dev/null +++ b/codemp/botlib/be_aas_optimize.h @@ -0,0 +1,16 @@ + +/***************************************************************************** + * name: be_aas_optimize.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_optimize.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +void AAS_Optimize(void); + diff --git a/codemp/botlib/be_aas_reach.cpp b/codemp/botlib/be_aas_reach.cpp new file mode 100644 index 0000000..de1c3c4 --- /dev/null +++ b/codemp/botlib/be_aas_reach.cpp @@ -0,0 +1,4527 @@ + +/***************************************************************************** + * name: be_aas_reach.c + * + * desc: reachability calculations + * + * $Archive: /MissionPack/code/botlib/be_aas_reach.c $ + * $Author: Ttimo $ + * $Revision: 12 $ + * $Modtime: 4/21/01 9:15a $ + * $Date: 4/21/01 9:15a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_libvar.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern int Sys_MilliSeconds(void); + + +extern botlib_import_t botimport; + +//#define REACH_DEBUG + +//NOTE: all travel times are in hundreth of a second +//maximum number of reachability links +#define AAS_MAX_REACHABILITYSIZE 65536 +//number of areas reachability is calculated for each frame +#define REACHABILITYAREASPERCYCLE 15 +//number of units reachability points are placed inside the areas +#define INSIDEUNITS 2 +#define INSIDEUNITS_WALKEND 5 +#define INSIDEUNITS_WALKSTART 0.1 +#define INSIDEUNITS_WATERJUMP 15 +//area flag used for weapon jumping +#define AREA_WEAPONJUMP 8192 //valid area to weapon jump to +//number of reachabilities of each type +int reach_swim; //swim +int reach_equalfloor; //walk on floors with equal height +int reach_step; //step up +int reach_walk; //walk of step +int reach_barrier; //jump up to a barrier +int reach_waterjump; //jump out of water +int reach_walkoffledge; //walk of a ledge +int reach_jump; //jump +int reach_ladder; //climb or descent a ladder +int reach_teleport; //teleport +int reach_elevator; //use an elevator +int reach_funcbob; //use a func bob +int reach_grapple; //grapple hook +int reach_doublejump; //double jump +int reach_rampjump; //ramp jump +int reach_strafejump; //strafe jump (just normal jump but further) +int reach_rocketjump; //rocket jump +int reach_bfgjump; //bfg jump +int reach_jumppad; //jump pads +//if true grapple reachabilities are skipped +int calcgrapplereach; +//linked reachability +typedef struct aas_lreachability_s +{ + int areanum; //number of the reachable area + int facenum; //number of the face towards the other area + int edgenum; //number of the edge towards the other area + vec3_t start; //start point of inter area movement + vec3_t end; //end point of inter area movement + int traveltype; //type of travel required to get to the area + unsigned short int traveltime; //travel time of the inter area movement + // + struct aas_lreachability_s *next; +} aas_lreachability_t; +//temporary reachabilities +aas_lreachability_t *reachabilityheap; //heap with reachabilities +aas_lreachability_t *nextreachability; //next free reachability from the heap +aas_lreachability_t **areareachability; //reachability links for every area +int numlreachabilities; + +//=========================================================================== +// returns the surface area of the given face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_FaceArea(aas_face_t *face) +{ + int i, edgenum, side; + float total; + vec_t *v; + vec3_t d1, d2, cross; + aas_edge_t *edge; + + edgenum = aasworld.edgeindex[face->firstedge]; + side = edgenum < 0; + edge = &aasworld.edges[abs(edgenum)]; + v = aasworld.vertexes[edge->v[side]]; + + total = 0; + for (i = 1; i < face->numedges - 1; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + side = edgenum < 0; + edge = &aasworld.edges[abs(edgenum)]; + VectorSubtract(aasworld.vertexes[edge->v[side]], v, d1); + VectorSubtract(aasworld.vertexes[edge->v[!side]], v, d2); + CrossProduct(d1, d2, cross); + total += 0.5 * VectorLength(cross); + } //end for + return total; +} //end of the function AAS_FaceArea +//=========================================================================== +// returns the volume of an area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaVolume(int areanum) +{ + int i, edgenum, facenum, side; + vec_t d, a, volume; + vec3_t corner; + aas_plane_t *plane; + aas_edge_t *edge; + aas_face_t *face; + aas_area_t *area; + + area = &aasworld.areas[areanum]; + facenum = aasworld.faceindex[area->firstface]; + face = &aasworld.faces[abs(facenum)]; + edgenum = aasworld.edgeindex[face->firstedge]; + edge = &aasworld.edges[abs(edgenum)]; + // + VectorCopy(aasworld.vertexes[edge->v[0]], corner); + + //make tetrahedrons to all other faces + volume = 0; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + side = face->backarea != areanum; + plane = &aasworld.planes[face->planenum ^ side]; + d = -(DotProduct (corner, plane->normal) - plane->dist); + a = AAS_FaceArea(face); + volume += d * a; + } //end for + + volume /= 3; + return volume; +} //end of the function AAS_AreaVolume +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableLinkArea(aas_link_t *areas) +{ + aas_link_t *link; + + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaGrounded(link->areanum) || AAS_AreaSwim(link->areanum)) + { + return link->areanum; + } //end if + } //end for + // + for (link = areas; link; link = link->next_area) + { + if (link->areanum) return link->areanum; + //FIXME: this is a bad idea when the reachability is not yet + // calculated when the level items are loaded + if (AAS_AreaReachability(link->areanum)) + return link->areanum; + } //end for + return 0; +} //end of the function AAS_BestReachableLinkArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetJumpPadInfo(int ent, vec3_t areastart, vec3_t absmins, vec3_t absmaxs, vec3_t velocity) +{ + int modelnum, ent2; + float speed, height, gravity, time, dist, forward; + vec3_t origin, angles, teststart, ent2origin; + aas_trace_t trace; + char model[MAX_EPAIRKEY]; + char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; + + // + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) speed = 1000; + VectorClear(angles); + //get the mins, maxs and origin of the model + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); + if (model[0]) modelnum = atoi(model+1); + else modelnum = 0; + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); + VectorAdd(origin, absmins, absmins); + VectorAdd(origin, absmaxs, absmaxs); + VectorAdd(absmins, absmaxs, origin); + VectorScale (origin, 0.5, origin); + + //get the start areas + VectorCopy(origin, teststart); + teststart[2] += 64; + trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); + VectorCopy(origin, areastart); + } //end if + else + { + VectorCopy(trace.endpos, areastart); + } //end else + areastart[2] += 0.125; + // + //AAS_DrawPermanentCross(origin, 4, 4); + //get the target entity + AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); + for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) + { + if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) continue; + if (!strcmp(targetname, target)) break; + } //end for + if (!ent2) + { + botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); + return qfalse; + } //end if + AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); + // + height = ent2origin[2] - origin[2]; + gravity = aassettings.phys_gravity; + time = sqrt( height / ( 0.5 * gravity ) ); + if (!time) + { + botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); + return qfalse; + } //end if + // set s.origin2 to the push velocity + VectorSubtract ( ent2origin, origin, velocity); + dist = VectorNormalize( velocity); + forward = dist / time; + //FIXME: why multiply by 1.1 + forward *= 1.1f; + VectorScale(velocity, forward, velocity); + velocity[2] = time * gravity; + return qtrue; +} //end of the function AAS_GetJumpPadInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + int area2num, ent, bot_visualizejumppads, bestareanum; + float volume, bestareavolume; + vec3_t areastart, cmdmove, bboxmins, bboxmaxs; + vec3_t absmins, absmaxs, velocity; + aas_clientmove_t move; + aas_link_t *areas, *link; + char classname[MAX_EPAIRKEY]; + +#ifdef BSPC + bot_visualizejumppads = 0; +#else + bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0"); +#endif + VectorAdd(origin, mins, bboxmins); + VectorAdd(origin, maxs, bboxmaxs); + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (strcmp(classname, "trigger_push")) continue; + // + if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue; + //get the areas the jump pad brush is in + areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaJumpPad(link->areanum)) break; + } //end for + if (!link) + { + botimport.Print(PRT_MESSAGE, "trigger_push not in any jump pad area\n"); + AAS_UnlinkFromAreas(areas); + continue; + } //end if + // + //botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); + // + VectorSet(cmdmove, 0, 0, 0); + Com_Memset(&move, 0, sizeof(aas_clientmove_t)); + area2num = 0; + AAS_ClientMovementHitBBox(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1f, bboxmins, bboxmaxs, bot_visualizejumppads); + if (move.frames < 30) + { + bestareanum = 0; + bestareavolume = 0; + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + volume = AAS_AreaVolume(link->areanum); + if (volume >= bestareavolume) + { + bestareanum = link->areanum; + bestareavolume = volume; + } //end if + } //end if + AAS_UnlinkFromAreas(areas); + return bestareanum; + } //end if + AAS_UnlinkFromAreas(areas); + } //end for + return 0; +} //end of the function AAS_BestReachableFromJumpPadArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin) +{ + int areanum, i, j, k, l; + aas_link_t *areas; + vec3_t absmins, absmaxs; + //vec3_t bbmins, bbmaxs; + vec3_t start, end; + aas_trace_t trace; + + if (!aasworld.loaded) + { + botimport.Print(PRT_ERROR, "AAS_BestReachableArea: aas not loaded\n"); + return 0; + } //end if + //find a point in an area + VectorCopy(origin, start); + areanum = AAS_PointAreaNum(start); + //while no area found fudge around a little + for (i = 0; i < 5 && !areanum; i++) + { + for (j = 0; j < 5 && !areanum; j++) + { + for (k = -1; k <= 1 && !areanum; k++) + { + for (l = -1; l <= 1 && !areanum; l++) + { + VectorCopy(origin, start); + start[0] += (float) j * 4 * k; + start[1] += (float) j * 4 * l; + start[2] += (float) i * 4; + areanum = AAS_PointAreaNum(start); + } //end for + } //end for + } //end for + } //end for + //if an area was found + if (areanum) + { + //drop client bbox down and try again + VectorCopy(start, end); + start[2] += 0.25; + end[2] -= 50; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + areanum = AAS_PointAreaNum(trace.endpos); + VectorCopy(trace.endpos, goalorigin); + //FIXME: cannot enable next line right now because the reachability + // does not have to be calculated when the level items are loaded + //if the origin is in an area with reachability + //if (AAS_AreaReachability(areanum)) return areanum; + if (areanum) return areanum; + } //end if + else + { + //it can very well happen that the AAS_PointAreaNum function tells that + //a point is in an area and that starting a AAS_TraceClientBBox from that + //point will return trace.startsolid qtrue +#if 0 + if (AAS_PointAreaNum(start)) + { + Log_Write("point %f %f %f in area %d but trace startsolid", start[0], start[1], start[2], areanum); + AAS_DrawPermanentCross(start, 4, LINECOLOR_RED); + } //end if + botimport.Print(PRT_MESSAGE, "AAS_BestReachableArea: start solid\n"); +#endif + VectorCopy(start, goalorigin); + return areanum; + } //end else + } //end if + // + //AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); + //NOTE: the goal origin does not have to be in the goal area + // because the bot will have to move towards the item origin anyway + VectorCopy(origin, goalorigin); + // + VectorAdd(origin, mins, absmins); + VectorAdd(origin, maxs, absmaxs); + //add bounding box size + //VectorSubtract(absmins, bbmaxs, absmins); + //VectorSubtract(absmaxs, bbmins, absmaxs); + //link an invalid (-1) entity + areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); + //get the reachable link arae + areanum = AAS_BestReachableLinkArea(areas); + //unlink the invalid entity + AAS_UnlinkFromAreas(areas); + // + return areanum; +} //end of the function AAS_BestReachableArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetupReachabilityHeap(void) +{ + int i; + + reachabilityheap = (aas_lreachability_t *) GetClearedMemory( + AAS_MAX_REACHABILITYSIZE * sizeof(aas_lreachability_t)); + for (i = 0; i < AAS_MAX_REACHABILITYSIZE-1; i++) + { + reachabilityheap[i].next = &reachabilityheap[i+1]; + } //end for + reachabilityheap[AAS_MAX_REACHABILITYSIZE-1].next = NULL; + nextreachability = reachabilityheap; + numlreachabilities = 0; +} //end of the function AAS_InitReachabilityHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShutDownReachabilityHeap(void) +{ + FreeMemory(reachabilityheap); + numlreachabilities = 0; +} //end of the function AAS_ShutDownReachabilityHeap +//=========================================================================== +// returns a reachability link +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_lreachability_t *AAS_AllocReachability(void) +{ + aas_lreachability_t *r; + + if (!nextreachability) return NULL; + //make sure the error message only shows up once + if (!nextreachability->next) AAS_Error("AAS_MAX_REACHABILITYSIZE"); + // + r = nextreachability; + nextreachability = nextreachability->next; + numlreachabilities++; + return r; +} //end of the function AAS_AllocReachability +//=========================================================================== +// frees a reachability link +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeReachability(aas_lreachability_t *lreach) +{ + Com_Memset(lreach, 0, sizeof(aas_lreachability_t)); + + lreach->next = nextreachability; + nextreachability = lreach; + numlreachabilities--; +} //end of the function AAS_FreeReachability +//=========================================================================== +// returns qtrue if the area has reachability links +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaReachability(int areanum) +{ + if (areanum < 0 || areanum >= aasworld.numareas) + { + AAS_Error("AAS_AreaReachability: areanum %d out of range", areanum); + return 0; + } //end if + return aasworld.areasettings[areanum].numreachableareas; +} //end of the function AAS_AreaReachability +//=========================================================================== +// returns the surface area of all ground faces together of the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaGroundFaceArea(int areanum) +{ + int i; + float total; + aas_area_t *area; + aas_face_t *face; + + total = 0; + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; + if (!(face->faceflags & FACE_GROUND)) continue; + // + total += AAS_FaceArea(face); + } //end for + return total; +} //end of the function AAS_AreaGroundFaceArea +//=========================================================================== +// returns the center of a face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FaceCenter(int facenum, vec3_t center) +{ + int i; + float scale; + aas_face_t *face; + aas_edge_t *edge; + + face = &aasworld.faces[facenum]; + + VectorClear(center); + for (i = 0; i < face->numedges; i++) + { + edge = &aasworld.edges[abs(aasworld.edgeindex[face->firstedge + i])]; + VectorAdd(center, aasworld.vertexes[edge->v[0]], center); + VectorAdd(center, aasworld.vertexes[edge->v[1]], center); + } //end for + scale = 0.5 / face->numedges; + VectorScale(center, scale, center); +} //end of the function AAS_FaceCenter +//=========================================================================== +// returns the maximum distance a player can fall before being damaged +// damage = deltavelocity*deltavelocity * 0.0001 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FallDamageDistance(void) +{ + float maxzvelocity, gravity, t; + + maxzvelocity = sqrt((float)(30 * 10000)); + gravity = aassettings.phys_gravity; + t = maxzvelocity / gravity; + return 0.5 * gravity * t * t; +} //end of the function AAS_FallDamageDistance +//=========================================================================== +// distance = 0.5 * gravity * t * t +// vel = t * gravity +// damage = vel * vel * 0.0001 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_FallDelta(float distance) +{ + float t, delta, gravity; + + gravity = aassettings.phys_gravity; + t = sqrt(fabs(distance) * 2 / gravity); + delta = t * gravity; + return delta * delta * 0.0001; +} //end of the function AAS_FallDelta +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_MaxJumpHeight(float phys_jumpvel) +{ + float phys_gravity; + + phys_gravity = aassettings.phys_gravity; + //maximum height a player can jump with the given initial z velocity + return 0.5 * phys_gravity * (phys_jumpvel / phys_gravity) * (phys_jumpvel / phys_gravity); +} //end of the function MaxJumpHeight +//=========================================================================== +// returns true if a player can only crouch in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_MaxJumpDistance(float phys_jumpvel) +{ + float phys_gravity, phys_maxvelocity, t; + + phys_gravity = aassettings.phys_gravity; + phys_maxvelocity = aassettings.phys_maxvelocity; + //time a player takes to fall the height + t = sqrt(aassettings.rs_maxjumpfallheight / (0.5 * phys_gravity)); + //maximum distance + return phys_maxvelocity * (t + phys_jumpvel / phys_gravity); +} //end of the function AAS_MaxJumpDistance +//=========================================================================== +// returns true if a player can only crouch in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaCrouch(int areanum) +{ + if (!(aasworld.areasettings[areanum].presencetype & PRESENCE_NORMAL)) return qtrue; + else return qfalse; +} //end of the function AAS_AreaCrouch +//=========================================================================== +// returns qtrue if it is possible to swim in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaSwim(int areanum) +{ + if (aasworld.areasettings[areanum].areaflags & AREA_LIQUID) return qtrue; + else return qfalse; +} //end of the function AAS_AreaSwim +//=========================================================================== +// returns qtrue if the area contains a liquid +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLiquid(int areanum) +{ + if (aasworld.areasettings[areanum].areaflags & AREA_LIQUID) return qtrue; + else return qfalse; +} //end of the function AAS_AreaLiquid +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLava(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_LAVA); +} //end of the function AAS_AreaLava +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaSlime(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_SLIME); +} //end of the function AAS_AreaSlime +//=========================================================================== +// returns qtrue if the area contains ground faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaGrounded(int areanum) +{ + return (aasworld.areasettings[areanum].areaflags & AREA_GROUNDED); +} //end of the function AAS_AreaGround +//=========================================================================== +// returns true if the area contains ladder faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLadder(int areanum) +{ + return (aasworld.areasettings[areanum].areaflags & AREA_LADDER); +} //end of the function AAS_AreaLadder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaJumpPad(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_JUMPPAD); +} //end of the function AAS_AreaJumpPad +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTeleporter(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_TELEPORTER); +} //end of the function AAS_AreaTeleporter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaClusterPortal(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL); +} //end of the function AAS_AreaClusterPortal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaDoNotEnter(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_DONOTENTER); +} //end of the function AAS_AreaDoNotEnter +//=========================================================================== +// returns the time it takes perform a barrier jump +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short int AAS_BarrierJumpTravelTime(void) +{ + return aassettings.phys_jumpvel / (aassettings.phys_gravity * 0.1); +} //end op the function AAS_BarrierJumpTravelTime +//=========================================================================== +// returns true if there already exists a reachability from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_ReachabilityExists(int area1num, int area2num) +{ + aas_lreachability_t *r; + + for (r = areareachability[area1num]; r; r = r->next) + { + if (r->areanum == area2num) return qtrue; + } //end for + return qfalse; +} //end of the function AAS_ReachabilityExists +//=========================================================================== +// returns true if there is a solid just after the end point when going +// from start to end +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearbySolidOrGap(vec3_t start, vec3_t end) +{ + vec3_t dir, testpoint; + int areanum; + + VectorSubtract(end, start, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorMA(end, 48, dir, testpoint); + + areanum = AAS_PointAreaNum(testpoint); + if (!areanum) + { + testpoint[2] += 16; + areanum = AAS_PointAreaNum(testpoint); + if (!areanum) return qtrue; + } //end if + VectorMA(end, 64, dir, testpoint); + areanum = AAS_PointAreaNum(testpoint); + if (areanum) + { + if (!AAS_AreaSwim(areanum) && !AAS_AreaGrounded(areanum)) return qtrue; + } //end if + return qfalse; +} //end of the function AAS_SolidGapTime +//=========================================================================== +// searches for swim reachabilities between adjacent areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Swim(int area1num, int area2num) +{ + int i, j, face1num, face2num, side1; + aas_area_t *area1, *area2; + aas_areasettings_t *areasettings; + aas_lreachability_t *lreach; + aas_face_t *face1; + aas_plane_t *plane; + vec3_t start; + + if (!AAS_AreaSwim(area1num) || !AAS_AreaSwim(area2num)) return qfalse; + //if the second area is crouch only + if (!(aasworld.areasettings[area2num].presencetype & PRESENCE_NORMAL)) return qfalse; + + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + + //if the areas are not near anough + for (i = 0; i < 3; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; + if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; + } //end for + //find a shared face and create a reachability link + for (i = 0; i < area1->numfaces; i++) + { + face1num = aasworld.faceindex[area1->firstface + i]; + side1 = face1num < 0; + face1num = abs(face1num); + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = abs(aasworld.faceindex[area2->firstface + j]); + // + if (face1num == face2num) + { + AAS_FaceCenter(face1num, start); + // + if (AAS_PointContents(start) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) + { + // + face1 = &aasworld.faces[face1num]; + areasettings = &aasworld.areasettings[area1num]; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = face1num; + lreach->edgenum = 0; + VectorCopy(start, lreach->start); + plane = &aasworld.planes[face1->planenum ^ side1]; + VectorMA(lreach->start, -INSIDEUNITS, plane->normal, lreach->end); + lreach->traveltype = TRAVEL_SWIM; + lreach->traveltime = 1; + //if the volume of the area is rather small + if (AAS_AreaVolume(area2num) < 800) + lreach->traveltime += 200; + //if (!(AAS_PointContents(start) & MASK_WATER)) lreach->traveltime += 500; + //link the reachability + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + reach_swim++; + return qtrue; + } //end if + } //end if + } //end for + } //end for + return qfalse; +} //end of the function AAS_Reachability_Swim +//=========================================================================== +// searches for reachabilities between adjacent areas with equal floor +// heights +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_EqualFloorHeight(int area1num, int area2num) +{ + int i, j, edgenum, edgenum1, edgenum2, foundreach, side; + float height, bestheight, length, bestlength; + vec3_t dir, start, end, normal, invgravity, gravitydirection = {0, 0, -1}; + vec3_t edgevec; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2; + aas_edge_t *edge; + aas_plane_t *plane2; + aas_lreachability_t lr, *lreach; + + if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) return qfalse; + + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; + if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; + } //end for + //if area 2 is too high above area 1 + if (area2->mins[2] > area1->maxs[2]) return qfalse; + // + VectorCopy(gravitydirection, invgravity); + VectorInverse(invgravity); + // + bestheight = 99999; + bestlength = 0; + foundreach = qfalse; + Com_Memset(&lr, 0, sizeof(aas_lreachability_t)); //make the compiler happy + // + //check if the areas have ground faces with a common edge + //if existing use the lowest common edge for a reachability link + for (i = 0; i < area1->numfaces; i++) + { + face1 = &aasworld.faces[abs(aasworld.faceindex[area1->firstface + i])]; + if (!(face1->faceflags & FACE_GROUND)) continue; + // + for (j = 0; j < area2->numfaces; j++) + { + face2 = &aasworld.faces[abs(aasworld.faceindex[area2->firstface + j])]; + if (!(face2->faceflags & FACE_GROUND)) continue; + //if there is a common edge + for (edgenum1 = 0; edgenum1 < face1->numedges; edgenum1++) + { + for (edgenum2 = 0; edgenum2 < face2->numedges; edgenum2++) + { + if (abs(aasworld.edgeindex[face1->firstedge + edgenum1]) != + abs(aasworld.edgeindex[face2->firstedge + edgenum2])) + continue; + edgenum = aasworld.edgeindex[face1->firstedge + edgenum1]; + side = edgenum < 0; + edge = &aasworld.edges[abs(edgenum)]; + //get the length of the edge + VectorSubtract(aasworld.vertexes[edge->v[1]], + aasworld.vertexes[edge->v[0]], dir); + length = VectorLength(dir); + //get the start point + VectorAdd(aasworld.vertexes[edge->v[0]], + aasworld.vertexes[edge->v[1]], start); + VectorScale(start, 0.5, start); + VectorCopy(start, end); + //get the end point several units inside area2 + //and the start point several units inside area1 + //NOTE: normal is pointing into area2 because the + //face edges are stored counter clockwise + VectorSubtract(aasworld.vertexes[edge->v[side]], + aasworld.vertexes[edge->v[!side]], edgevec); + plane2 = &aasworld.planes[face2->planenum]; + CrossProduct(edgevec, plane2->normal, normal); + VectorNormalize(normal); + // + //VectorMA(start, -1, normal, start); + VectorMA(end, INSIDEUNITS_WALKEND, normal, end); + VectorMA(start, INSIDEUNITS_WALKSTART, normal, start); + end[2] += 0.125; + // + height = DotProduct(invgravity, start); + //NOTE: if there's nearby solid or a gap area after this area + //disabled this crap + //if (AAS_NearbySolidOrGap(start, end)) height += 200; + //NOTE: disabled because it disables reachabilities to very small areas + //if (AAS_PointAreaNum(end) != area2num) continue; + //get the longest lowest edge + if (height < bestheight || + (height < bestheight + 1 && length > bestlength)) + { + bestheight = height; + bestlength = length; + //create a new reachability link + lr.areanum = area2num; + lr.facenum = 0; + lr.edgenum = edgenum; + VectorCopy(start, lr.start); + VectorCopy(end, lr.end); + lr.traveltype = TRAVEL_WALK; + lr.traveltime = 1; + foundreach = qtrue; + } //end if + } //end for + } //end for + } //end for + } //end for + if (foundreach) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = lr.areanum; + lreach->facenum = lr.facenum; + lreach->edgenum = lr.edgenum; + VectorCopy(lr.start, lreach->start); + VectorCopy(lr.end, lreach->end); + lreach->traveltype = lr.traveltype; + lreach->traveltime = lr.traveltime; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //if going into a crouch area + if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) + { + lreach->traveltime += aassettings.rs_startcrouch; + } //end if + /* + //NOTE: if there's nearby solid or a gap area after this area + if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) + { + lreach->traveltime += 100; + } //end if + */ + //avoid rather small areas + //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; + // + reach_equalfloor++; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_Reachability_EqualFloorHeight +//=========================================================================== +// searches step, barrier, waterjump and walk off ledge reachabilities +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(int area1num, int area2num) +{ + int i, j, k, l, edge1num, edge2num, areas[10], numareas; + int ground_bestarea2groundedgenum, ground_foundreach; + int water_bestarea2groundedgenum, water_foundreach; + int side1, area1swim, faceside1, groundface1num; + float dist, dist1, dist2, diff, invgravitydot, ortdot; + float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y; + float length, ground_bestlength, water_bestlength, ground_bestdist, water_bestdist; + vec3_t v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2; + vec3_t normal, ort, edgevec, start, end, dir; + vec3_t ground_beststart, ground_bestend, ground_bestnormal; + vec3_t water_beststart, water_bestend, water_bestnormal; + vec3_t invgravity = {0, 0, 1}; + vec3_t testpoint; + aas_plane_t *plane; + aas_area_t *area1, *area2; + aas_face_t *groundface1, *groundface2, *ground_bestface1, *water_bestface1; + aas_edge_t *edge1, *edge2; + aas_lreachability_t *lreach; + aas_trace_t trace; + + //must be able to walk or swim in the first area + if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) return qfalse; + // + if (!AAS_AreaGrounded(area2num) && !AAS_AreaSwim(area2num)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //if the first area contains a liquid + area1swim = AAS_AreaSwim(area1num); + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; + if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; + } //end for + // + ground_foundreach = qfalse; + ground_bestdist = 99999; + ground_bestlength = 0; + ground_bestarea2groundedgenum = 0; + // + water_foundreach = qfalse; + water_bestdist = 99999; + water_bestlength = 0; + water_bestarea2groundedgenum = 0; + // + for (i = 0; i < area1->numfaces; i++) + { + groundface1num = aasworld.faceindex[area1->firstface + i]; + faceside1 = groundface1num < 0; + groundface1 = &aasworld.faces[abs(groundface1num)]; + //if this isn't a ground face + if (!(groundface1->faceflags & FACE_GROUND)) + { + //if we can swim in the first area + if (area1swim) + { + //face plane must be more or less horizontal + plane = &aasworld.planes[groundface1->planenum ^ (!faceside1)]; + if (DotProduct(plane->normal, invgravity) < 0.7) continue; + } //end if + else + { + //if we can't swim in the area it must be a ground face + continue; + } //end else + } //end if + // + for (k = 0; k < groundface1->numedges; k++) + { + edge1num = aasworld.edgeindex[groundface1->firstedge + k]; + side1 = (edge1num < 0); + //NOTE: for water faces we must take the side area 1 is + // on into account because the face is shared and doesn't + // have to be oriented correctly + if (!(groundface1->faceflags & FACE_GROUND)) side1 = (side1 == faceside1); + edge1num = abs(edge1num); + edge1 = &aasworld.edges[edge1num]; + //vertexes of the edge + VectorCopy(aasworld.vertexes[edge1->v[!side1]], v1); + VectorCopy(aasworld.vertexes[edge1->v[side1]], v2); + //get a vertical plane through the edge + //NOTE: normal is pointing into area 2 because the + //face edges are stored counter clockwise + VectorSubtract(v2, v1, edgevec); + CrossProduct(edgevec, invgravity, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + //check the faces from the second area + for (j = 0; j < area2->numfaces; j++) + { + groundface2 = &aasworld.faces[abs(aasworld.faceindex[area2->firstface + j])]; + //must be a ground face + if (!(groundface2->faceflags & FACE_GROUND)) continue; + //check the edges of this ground face + for (l = 0; l < groundface2->numedges; l++) + { + edge2num = abs(aasworld.edgeindex[groundface2->firstedge + l]); + edge2 = &aasworld.edges[edge2num]; + //vertexes of the edge + VectorCopy(aasworld.vertexes[edge2->v[0]], v3); + VectorCopy(aasworld.vertexes[edge2->v[1]], v4); + //check the distance between the two points and the vertical plane + //through the edge of area1 + diff = DotProduct(normal, v3) - dist; + if (diff < -0.1 || diff > 0.1) continue; + diff = DotProduct(normal, v4) - dist; + if (diff < -0.1 || diff > 0.1) continue; + // + //project the two ground edges into the step side plane + //and calculate the shortest distance between the two + //edges if they overlap in the direction orthogonal to + //the gravity direction + CrossProduct(invgravity, normal, ort); + invgravitydot = DotProduct(invgravity, invgravity); + ortdot = DotProduct(ort, ort); + //projection into the step plane + //NOTE: since gravity is vertical this is just the z coordinate + y1 = v1[2];//DotProduct(v1, invgravity) / invgravitydot; + y2 = v2[2];//DotProduct(v2, invgravity) / invgravitydot; + y3 = v3[2];//DotProduct(v3, invgravity) / invgravitydot; + y4 = v4[2];//DotProduct(v4, invgravity) / invgravitydot; + // + x1 = DotProduct(v1, ort) / ortdot; + x2 = DotProduct(v2, ort) / ortdot; + x3 = DotProduct(v3, ort) / ortdot; + x4 = DotProduct(v4, ort) / ortdot; + // + if (x1 > x2) + { + tmp = x1; x1 = x2; x2 = tmp; + tmp = y1; y1 = y2; y2 = tmp; + VectorCopy(v1, tmpv); VectorCopy(v2, v1); VectorCopy(tmpv, v2); + } //end if + if (x3 > x4) + { + tmp = x3; x3 = x4; x4 = tmp; + tmp = y3; y3 = y4; y4 = tmp; + VectorCopy(v3, tmpv); VectorCopy(v4, v3); VectorCopy(tmpv, v4); + } //end if + //if the two projected edge lines have no overlap + if (x2 <= x3 || x4 <= x1) + { +// Log_Write("lines no overlap: from area %d to %d\r\n", area1num, area2num); + continue; + } //end if + //if the two lines fully overlap + if ((x1 - 0.5 < x3 && x4 < x2 + 0.5) && + (x3 - 0.5 < x1 && x2 < x4 + 0.5)) + { + dist1 = y3 - y1; + dist2 = y4 - y2; + VectorCopy(v1, p1area1); + VectorCopy(v2, p2area1); + VectorCopy(v3, p1area2); + VectorCopy(v4, p2area2); + } //end if + else + { + //if the points are equal + if (x1 > x3 - 0.1 && x1 < x3 + 0.1) + { + dist1 = y3 - y1; + VectorCopy(v1, p1area1); + VectorCopy(v3, p1area2); + } //end if + else if (x1 < x3) + { + y = y1 + (x3 - x1) * (y2 - y1) / (x2 - x1); + dist1 = y3 - y; + VectorCopy(v3, p1area1); + p1area1[2] = y; + VectorCopy(v3, p1area2); + } //end if + else + { + y = y3 + (x1 - x3) * (y4 - y3) / (x4 - x3); + dist1 = y - y1; + VectorCopy(v1, p1area1); + VectorCopy(v1, p1area2); + p1area2[2] = y; + } //end if + //if the points are equal + if (x2 > x4 - 0.1 && x2 < x4 + 0.1) + { + dist2 = y4 - y2; + VectorCopy(v2, p2area1); + VectorCopy(v4, p2area2); + } //end if + else if (x2 < x4) + { + y = y3 + (x2 - x3) * (y4 - y3) / (x4 - x3); + dist2 = y - y2; + VectorCopy(v2, p2area1); + VectorCopy(v2, p2area2); + p2area2[2] = y; + } //end if + else + { + y = y1 + (x4 - x1) * (y2 - y1) / (x2 - x1); + dist2 = y4 - y; + VectorCopy(v4, p2area1); + p2area1[2] = y; + VectorCopy(v4, p2area2); + } //end else + } //end else + //if both distances are pretty much equal + //then we take the middle of the points + if (dist1 > dist2 - 1 && dist1 < dist2 + 1) + { + dist = dist1; + VectorAdd(p1area1, p2area1, start); + VectorScale(start, 0.5, start); + VectorAdd(p1area2, p2area2, end); + VectorScale(end, 0.5, end); + } //end if + else if (dist1 < dist2) + { + dist = dist1; + VectorCopy(p1area1, start); + VectorCopy(p1area2, end); + } //end else if + else + { + dist = dist2; + VectorCopy(p2area1, start); + VectorCopy(p2area2, end); + } //end else + //get the length of the overlapping part of the edges of the two areas + VectorSubtract(p2area2, p1area2, dir); + length = VectorLength(dir); + // + if (groundface1->faceflags & FACE_GROUND) + { + //if the vertical distance is smaller + if (dist < ground_bestdist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + (dist < ground_bestdist + 1 && length > ground_bestlength)) + { + ground_bestdist = dist; + ground_bestlength = length; + ground_foundreach = qtrue; + ground_bestarea2groundedgenum = edge1num; + ground_bestface1 = groundface1; + //best point towards area1 + VectorCopy(start, ground_beststart); + //normal is pointing into area2 + VectorCopy(normal, ground_bestnormal); + //best point towards area2 + VectorCopy(end, ground_bestend); + } //end if + } //end if + else + { + //if the vertical distance is smaller + if (dist < water_bestdist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + (dist < water_bestdist + 1 && length > water_bestlength)) + { + water_bestdist = dist; + water_bestlength = length; + water_foundreach = qtrue; + water_bestarea2groundedgenum = edge1num; + water_bestface1 = groundface1; + //best point towards area1 + VectorCopy(start, water_beststart); + //normal is pointing into area2 + VectorCopy(normal, water_bestnormal); + //best point towards area2 + VectorCopy(end, water_bestend); + } //end if + } //end else + } //end for + } //end for + } //end for + } //end for + // + // NOTE: swim reachabilities are already filtered out + // + // Steps + // + // --------- + // | step height -> TRAVEL_WALK + //--------| + // + // --------- + //~~~~~~~~| step height and low water -> TRAVEL_WALK + //--------| + // + //~~~~~~~~~~~~~~~~~~ + // --------- + // | step height and low water up to the step -> TRAVEL_WALK + //--------| + // + //check for a step reachability + if (ground_foundreach) + { + //if area2 is higher but lower than the maximum step height + //NOTE: ground_bestdist >= 0 also catches equal floor reachabilities + if (ground_bestdist >= 0 && ground_bestdist < aassettings.phys_maxstep) + { + //create walk reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WALK; + lreach->traveltime = 0;//1; + //if going into a crouch area + if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) + { + lreach->traveltime += aassettings.rs_startcrouch; + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //NOTE: if there's nearby solid or a gap area after this area + /* + if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) + { + lreach->traveltime += 100; + } //end if + */ + //avoid rather small areas + //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; + // + reach_step++; + return qtrue; + } //end if + } //end if + // + // Water Jumps + // + // --------- + // | + //~~~~~~~~| + // | + // | higher than step height and water up to waterjump height -> TRAVEL_WATERJUMP + //--------| + // + //~~~~~~~~~~~~~~~~~~ + // --------- + // | + // | + // | + // | higher than step height and low water up to the step -> TRAVEL_WATERJUMP + //--------| + // + //check for a waterjump reachability + if (water_foundreach) + { + //get a test point a little bit towards area1 + VectorMA(water_bestend, -INSIDEUNITS, water_bestnormal, testpoint); + //go down the maximum waterjump height + testpoint[2] -= aassettings.phys_maxwaterjump; + //if there IS water the sv_maxwaterjump height below the bestend point + if (aasworld.areasettings[AAS_PointAreaNum(testpoint)].areaflags & AREA_LIQUID) + { + //don't create rediculous water jump reachabilities from areas very far below + //the water surface + if (water_bestdist < aassettings.phys_maxwaterjump + 24) + { + //waterjumping from or towards a crouch only area is not possible in Quake2 + if ((aasworld.areasettings[area1num].presencetype & PRESENCE_NORMAL) && + (aasworld.areasettings[area2num].presencetype & PRESENCE_NORMAL)) + { + //create water jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = water_bestarea2groundedgenum; + VectorCopy(water_beststart, lreach->start); + VectorMA(water_bestend, INSIDEUNITS_WATERJUMP, water_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WATERJUMP; + lreach->traveltime = aassettings.rs_waterjump; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another waterjump reachability + reach_waterjump++; + return qtrue; + } //end if + } //end if + } //end if + } //end if + // + // Barrier Jumps + // + // --------- + // | + // | + // | + // | higher than step height lower than barrier height -> TRAVEL_BARRIERJUMP + //--------| + // + // --------- + // | + // | + // | + //~~~~~~~~| higher than step height lower than barrier height + //--------| and a thin layer of water in the area to jump from -> TRAVEL_BARRIERJUMP + // + //check for a barrier jump reachability + if (ground_foundreach) + { + //if area2 is higher but lower than the maximum barrier jump height + if (ground_bestdist > 0 && ground_bestdist < aassettings.phys_maxbarrier) + { + //if no water in area1 or a very thin layer of water on the ground + if (!water_foundreach || (ground_bestdist - water_bestdist < 16)) + { + //cannot perform a barrier jump towards or from a crouch area in Quake2 + if (!AAS_AreaCrouch(area1num) && !AAS_AreaCrouch(area2num)) + { + //create barrier jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_BARRIERJUMP; + lreach->traveltime = aassettings.rs_barrierjump;//AAS_BarrierJumpTravelTime(); + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another barrierjump reachability + reach_barrier++; + return qtrue; + } //end if + } //end if + } //end if + } //end if + // + // Walk and Walk Off Ledge + // + //--------| + // | can walk or step back -> TRAVEL_WALK + // --------- + // + //--------| + // | + // | + // | + // | cannot walk/step back -> TRAVEL_WALKOFFLEDGE + // --------- + // + //--------| + // | + // |~~~~~~~~ + // | + // | cannot step back but can waterjump back -> TRAVEL_WALKOFFLEDGE + // --------- FIXME: create TRAVEL_WALK reach?? + // + //check for a walk or walk off ledge reachability + if (ground_foundreach) + { + if (ground_bestdist < 0) + { + if (ground_bestdist > -aassettings.phys_maxstep) + { + //create walk reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WALK; + lreach->traveltime = 1; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another walk reachability + reach_walk++; + return qtrue; + } //end if + // if no maximum fall height set or less than the max + if (!aassettings.rs_maxfallheight || fabs(ground_bestdist) < aassettings.rs_maxfallheight) { + //trace a bounding box vertically to check for solids + VectorMA(ground_bestend, INSIDEUNITS, ground_bestnormal, ground_bestend); + VectorCopy(ground_bestend, start); + start[2] = ground_beststart[2]; + VectorCopy(ground_bestend, end); + end[2] += 4; + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + //if no solids were found + if (!trace.startsolid && trace.fraction >= 1.0) + { + //the trace end point must be in the goal area + trace.endpos[2] += 1; + if (AAS_PointAreaNum(trace.endpos) == area2num) + { + //if not going through a cluster portal + numareas = AAS_TraceAreas(start, end, areas, NULL, sizeof(areas) / sizeof(int)); + for (i = 0; i < numareas; i++) + if (AAS_AreaClusterPortal(areas[i])) + break; + if (i >= numareas) + { + //create a walk off ledge reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorCopy(ground_beststart, lreach->start); + VectorCopy(ground_bestend, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = aassettings.rs_startwalkoffledge + fabs(ground_bestdist) * 50 / aassettings.phys_gravity; + //if falling from too high and not falling into water + if (!AAS_AreaSwim(area2num) && !AAS_AreaJumpPad(area2num)) + { + if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta5) + { + lreach->traveltime += aassettings.rs_falldamage5; + } //end if + if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta10) + { + lreach->traveltime += aassettings.rs_falldamage10; + } //end if + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_walkoffledge++; + //NOTE: don't create a weapon (rl, bfg) jump reachability here + //because it interferes with other reachabilities + //like the ladder reachability + return qtrue; + } //end if + } //end if + } //end if + } //end if + } //end else + } //end if + return qfalse; +} //end of the function AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge +//=========================================================================== +// returns the distance between the two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float VectorDistance(vec3_t v1, vec3_t v2) +{ + vec3_t dir; + + VectorSubtract(v2, v1, dir); + return VectorLength(dir); +} //end of the function VectorDistance +//=========================================================================== +// returns true if the first vector is between the last two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int VectorBetweenVectors(vec3_t v, vec3_t v1, vec3_t v2) +{ + vec3_t dir1, dir2; + + VectorSubtract(v, v1, dir1); + VectorSubtract(v, v2, dir2); + return (DotProduct(dir1, dir2) <= 0); +} //end of the function VectorBetweenVectors +//=========================================================================== +// returns the mid point between the two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void VectorMiddle(vec3_t v1, vec3_t v2, vec3_t middle) +{ + VectorAdd(v1, v2, middle); + VectorScale(middle, 0.5, middle); +} //end of the function VectorMiddle +//=========================================================================== +// calculate a range of points closest to each other on both edges +// +// Parameter: beststart1 start of the range of points on edge v1-v2 +// beststart2 end of the range of points on edge v1-v2 +// bestend1 start of the range of points on edge v3-v4 +// bestend2 end of the range of points on edge v3-v4 +// bestdist best distance so far +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, + aas_plane_t *plane1, aas_plane_t *plane2, + vec3_t beststart, vec3_t bestend, float bestdist) +{ + vec3_t dir1, dir2, p1, p2, p3, p4; + float a1, a2, b1, b2, dist; + int founddist; + + //edge vectors + VectorSubtract(v2, v1, dir1); + VectorSubtract(v4, v3, dir2); + //get the horizontal directions + dir1[2] = 0; + dir2[2] = 0; + // + // p1 = point on an edge vector of area2 closest to v1 + // p2 = point on an edge vector of area2 closest to v2 + // p3 = point on an edge vector of area1 closest to v3 + // p4 = point on an edge vector of area1 closest to v4 + // + if (dir2[0]) + { + a2 = dir2[1] / dir2[0]; + b2 = v3[1] - a2 * v3[0]; + //point on the edge vector of area2 closest to v1 + p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p1[1] = a2 * p1[0] + b2; + //point on the edge vector of area2 closest to v2 + p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p2[1] = a2 * p2[0] + b2; + } //end if + else + { + //point on the edge vector of area2 closest to v1 + p1[0] = v3[0]; + p1[1] = v1[1]; + //point on the edge vector of area2 closest to v2 + p2[0] = v3[0]; + p2[1] = v2[1]; + } //end else + // + if (dir1[0]) + { + // + a1 = dir1[1] / dir1[0]; + b1 = v1[1] - a1 * v1[0]; + //point on the edge vector of area1 closest to v3 + p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p3[1] = a1 * p3[0] + b1; + //point on the edge vector of area1 closest to v4 + p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p4[1] = a1 * p4[0] + b1; + } //end if + else + { + //point on the edge vector of area1 closest to v3 + p3[0] = v1[0]; + p3[1] = v3[1]; + //point on the edge vector of area1 closest to v4 + p4[0] = v1[0]; + p4[1] = v4[1]; + } //end else + //start with zero z-coordinates + p1[2] = 0; + p2[2] = 0; + p3[2] = 0; + p4[2] = 0; + //calculate the z-coordinates from the ground planes + p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; + p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; + p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; + p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; + // + founddist = qfalse; + // + if (VectorBetweenVectors(p1, v3, v4)) + { + dist = VectorDistance(v1, p1); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, v1, beststart); + VectorMiddle(bestend, p1, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(p1, bestend); + } //end if + founddist = qtrue; + } //end if + if (VectorBetweenVectors(p2, v3, v4)) + { + dist = VectorDistance(v2, p2); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, v2, beststart); + VectorMiddle(bestend, p2, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(p2, bestend); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p3, v1, v2)) + { + dist = VectorDistance(v3, p3); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, p3, beststart); + VectorMiddle(bestend, v3, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p3, beststart); + VectorCopy(v3, bestend); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p4, v1, v2)) + { + dist = VectorDistance(v4, p4); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, p4, beststart); + VectorMiddle(bestend, v4, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p4, beststart); + VectorCopy(v4, bestend); + } //end if + founddist = qtrue; + } //end else if + //if no shortest distance was found the shortest distance + //is between one of the vertexes of edge1 and one of edge2 + if (!founddist) + { + dist = VectorDistance(v1, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(v3, bestend); + } //end if + dist = VectorDistance(v1, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(v4, bestend); + } //end if + dist = VectorDistance(v2, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(v3, bestend); + } //end if + dist = VectorDistance(v2, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(v4, bestend); + } //end if + } //end if + return bestdist; +} //end of the function AAS_ClosestEdgePoints*/ + +float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, + aas_plane_t *plane1, aas_plane_t *plane2, + vec3_t beststart1, vec3_t bestend1, + vec3_t beststart2, vec3_t bestend2, float bestdist) +{ + vec3_t dir1, dir2, p1, p2, p3, p4; + float a1, a2, b1, b2, dist, dist1, dist2; + int founddist; + + //edge vectors + VectorSubtract(v2, v1, dir1); + VectorSubtract(v4, v3, dir2); + //get the horizontal directions + dir1[2] = 0; + dir2[2] = 0; + // + // p1 = point on an edge vector of area2 closest to v1 + // p2 = point on an edge vector of area2 closest to v2 + // p3 = point on an edge vector of area1 closest to v3 + // p4 = point on an edge vector of area1 closest to v4 + // + if (dir2[0]) + { + a2 = dir2[1] / dir2[0]; + b2 = v3[1] - a2 * v3[0]; + //point on the edge vector of area2 closest to v1 + p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p1[1] = a2 * p1[0] + b2; + //point on the edge vector of area2 closest to v2 + p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p2[1] = a2 * p2[0] + b2; + } //end if + else + { + //point on the edge vector of area2 closest to v1 + p1[0] = v3[0]; + p1[1] = v1[1]; + //point on the edge vector of area2 closest to v2 + p2[0] = v3[0]; + p2[1] = v2[1]; + } //end else + // + if (dir1[0]) + { + // + a1 = dir1[1] / dir1[0]; + b1 = v1[1] - a1 * v1[0]; + //point on the edge vector of area1 closest to v3 + p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p3[1] = a1 * p3[0] + b1; + //point on the edge vector of area1 closest to v4 + p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p4[1] = a1 * p4[0] + b1; + } //end if + else + { + //point on the edge vector of area1 closest to v3 + p3[0] = v1[0]; + p3[1] = v3[1]; + //point on the edge vector of area1 closest to v4 + p4[0] = v1[0]; + p4[1] = v4[1]; + } //end else + //start with zero z-coordinates + p1[2] = 0; + p2[2] = 0; + p3[2] = 0; + p4[2] = 0; + //calculate the z-coordinates from the ground planes + p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; + p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; + p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; + p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; + // + founddist = qfalse; + // + if (VectorBetweenVectors(p1, v3, v4)) + { + dist = VectorDistance(v1, p1); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, v1); + dist2 = VectorDistance(beststart2, v1); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(v1, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(v1, beststart1); + } //end else + dist1 = VectorDistance(bestend1, p1); + dist2 = VectorDistance(bestend2, p1); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(p1, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(p1, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(p1, bestend1); + VectorCopy(p1, bestend2); + } //end if + founddist = qtrue; + } //end if + if (VectorBetweenVectors(p2, v3, v4)) + { + dist = VectorDistance(v2, p2); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, v2); + dist2 = VectorDistance(beststart2, v2); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(v2, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(v2, beststart1); + } //end else + dist1 = VectorDistance(bestend1, p2); + dist2 = VectorDistance(bestend2, p2); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(p2, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(p2, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(p2, bestend1); + VectorCopy(p2, bestend2); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p3, v1, v2)) + { + dist = VectorDistance(v3, p3); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, p3); + dist2 = VectorDistance(beststart2, p3); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(p3, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(p3, beststart1); + } //end else + dist1 = VectorDistance(bestend1, v3); + dist2 = VectorDistance(bestend2, v3); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(v3, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(v3, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p3, beststart1); + VectorCopy(p3, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p4, v1, v2)) + { + dist = VectorDistance(v4, p4); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, p4); + dist2 = VectorDistance(beststart2, p4); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(p4, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(p4, beststart1); + } //end else + dist1 = VectorDistance(bestend1, v4); + dist2 = VectorDistance(bestend2, v4); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(v4, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(v4, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p4, beststart1); + VectorCopy(p4, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + founddist = qtrue; + } //end else if + //if no shortest distance was found the shortest distance + //is between one of the vertexes of edge1 and one of edge2 + if (!founddist) + { + dist = VectorDistance(v1, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + dist = VectorDistance(v1, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + dist = VectorDistance(v2, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + dist = VectorDistance(v2, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + } //end if + return bestdist; +} //end of the function AAS_ClosestEdgePoints +//=========================================================================== +// creates possible jump reachabilities between the areas +// +// The two closest points on the ground of the areas are calculated +// One of the points will be on an edge of a ground face of area1 and +// one on an edge of a ground face of area2. +// If there is a range of closest points the point in the middle of this range +// is selected. +// Between these two points there must be one or more gaps. +// If the gaps exist a potential jump is predicted. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Jump(int area1num, int area2num) +{ + int i, j, k, l, face1num, face2num, edge1num, edge2num, traveltype; + int stopevent, areas[10], numareas; + float phys_jumpvel, maxjumpdistance, maxjumpheight, height, bestdist, speed; + vec_t *v1, *v2, *v3, *v4; + vec3_t beststart, beststart2, bestend, bestend2; + vec3_t teststart, testend, dir, velocity, cmdmove, up = {0, 0, 1}, sidewards; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2; + aas_edge_t *edge1, *edge2; + aas_plane_t *plane1, *plane2, *plane; + aas_trace_t trace; + aas_clientmove_t move; + aas_lreachability_t *lreach; + + if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) return qfalse; + //cannot jump from or to a crouch area + if (AAS_AreaCrouch(area1num) || AAS_AreaCrouch(area2num)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + // + phys_jumpvel = aassettings.phys_jumpvel; + //maximum distance a player can jump + maxjumpdistance = 2 * AAS_MaxJumpDistance(phys_jumpvel); + //maximum height a player can jump with the given initial z velocity + maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel); + + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + maxjumpdistance) return qfalse; + if (area1->maxs[i] < area2->mins[i] - maxjumpdistance) return qfalse; + } //end for + //if area2 is way to high to jump up to + if (area2->mins[2] > area1->maxs[2] + maxjumpheight) return qfalse; + // + bestdist = 999999; + // + for (i = 0; i < area1->numfaces; i++) + { + face1num = aasworld.faceindex[area1->firstface + i]; + face1 = &aasworld.faces[abs(face1num)]; + //if not a ground face + if (!(face1->faceflags & FACE_GROUND)) continue; + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = aasworld.faceindex[area2->firstface + j]; + face2 = &aasworld.faces[abs(face2num)]; + //if not a ground face + if (!(face2->faceflags & FACE_GROUND)) continue; + // + for (k = 0; k < face1->numedges; k++) + { + edge1num = abs(aasworld.edgeindex[face1->firstedge + k]); + edge1 = &aasworld.edges[edge1num]; + for (l = 0; l < face2->numedges; l++) + { + edge2num = abs(aasworld.edgeindex[face2->firstedge + l]); + edge2 = &aasworld.edges[edge2num]; + //calculate the minimum distance between the two edges + v1 = aasworld.vertexes[edge1->v[0]]; + v2 = aasworld.vertexes[edge1->v[1]]; + v3 = aasworld.vertexes[edge2->v[0]]; + v4 = aasworld.vertexes[edge2->v[1]]; + //get the ground planes + plane1 = &aasworld.planes[face1->planenum]; + plane2 = &aasworld.planes[face2->planenum]; + // + bestdist = AAS_ClosestEdgePoints(v1, v2, v3, v4, plane1, plane2, + beststart, bestend, + beststart2, bestend2, bestdist); + } //end for + } //end for + } //end for + } //end for + VectorMiddle(beststart, beststart2, beststart); + VectorMiddle(bestend, bestend2, bestend); + if (bestdist > 4 && bestdist < maxjumpdistance) + { +// Log_Write("shortest distance between %d and %d is %f\r\n", area1num, area2num, bestdist); + // if very close and almost no height difference then the bot can walk + if (bestdist <= 48 && fabs(beststart[2] - bestend[2]) < 8) + { + speed = 400; + traveltype = TRAVEL_WALKOFFLEDGE; + } //end if + else if (AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) + { + //FIXME: why multiply with 1.2??? + speed *= 1.2f; + traveltype = TRAVEL_WALKOFFLEDGE; + } //end else if + else + { + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + if (!AAS_HorizontalVelocityForJump(phys_jumpvel, beststart, bestend, &speed)) + return qfalse; + speed *= 1.05f; + traveltype = TRAVEL_JUMP; + // + //NOTE: test if the horizontal distance isn't too small + VectorSubtract(bestend, beststart, dir); + dir[2] = 0; + if (VectorLength(dir) < 10) + return qfalse; + } //end if + // + VectorSubtract(bestend, beststart, dir); + VectorNormalize(dir); + VectorMA(beststart, 1, dir, teststart); + // + VectorCopy(teststart, testend); + testend[2] -= 100; + trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) + return qfalse; + if (trace.fraction < 1) + { + plane = &aasworld.planes[trace.planenum]; + // if the bot can stand on the surface + if (DotProduct(plane->normal, up) >= 0.7) + { + // if no lava or slime below + if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) + { + if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier) + return qfalse; + } //end if + } //end if + } //end if + // + VectorMA(bestend, -1, dir, teststart); + // + VectorCopy(teststart, testend); + testend[2] -= 100; + trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) + return qfalse; + if (trace.fraction < 1) + { + plane = &aasworld.planes[trace.planenum]; + // if the bot can stand on the surface + if (DotProduct(plane->normal, up) >= 0.7) + { + // if no lava or slime below + if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) + { + if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier) + return qfalse; + } //end if + } //end if + } //end if + // + // get command movement + VectorClear(cmdmove); + if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) + cmdmove[2] = aassettings.phys_jumpvel; + else + cmdmove[2] = 0; + // + VectorSubtract(bestend, beststart, dir); + dir[2] = 0; + VectorNormalize(dir); + CrossProduct(dir, up, sidewards); + // + stopevent = SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE; + if (!AAS_AreaClusterPortal(area1num) && !AAS_AreaClusterPortal(area2num)) + stopevent |= SE_TOUCHCLUSTERPORTAL; + // + for (i = 0; i < 3; i++) + { + // + if (i == 1) + VectorAdd(testend, sidewards, testend); + else if (i == 2) + VectorSubtract(bestend, sidewards, testend); + else + VectorCopy(bestend, testend); + VectorSubtract(testend, beststart, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorScale(dir, speed, velocity); + // + AAS_PredictClientMovement(&move, -1, beststart, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 3, 30, 0.1f, + stopevent, 0, qfalse); + // if prediction time wasn't enough to fully predict the movement + if (move.frames >= 30) + return qfalse; + // don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) + return qfalse; + // never jump or fall through a cluster portal + if (move.stopevent & SE_TOUCHCLUSTERPORTAL) + return qfalse; + //the end position should be in area2, also test a little bit back + //because the predicted jump could have rushed through the area + VectorMA(move.endpos, -64, dir, teststart); + teststart[2] += 1; + numareas = AAS_TraceAreas(move.endpos, teststart, areas, NULL, sizeof(areas) / sizeof(int)); + for (j = 0; j < numareas; j++) + { + if (areas[j] == area2num) + break; + } //end for + if (j < numareas) + break; + } + if (i >= 3) + return qfalse; + // +#ifdef REACH_DEBUG + //create the reachability + Log_Write("jump reachability between %d and %d\r\n", area1num, area2num); +#endif //REACH_DEBUG + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(beststart, lreach->start); + VectorCopy(bestend, lreach->end); + lreach->traveltype = traveltype; + + VectorSubtract(bestend, beststart, dir); + height = dir[2]; + dir[2] = 0; + if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE && height > VectorLength(dir)) + { + lreach->traveltime = aassettings.rs_startwalkoffledge + height * 50 / aassettings.phys_gravity; + } + else + { + lreach->traveltime = aassettings.rs_startjump + VectorDistance(bestend, beststart) * 240 / aassettings.phys_maxwalkvelocity; + } //end if + // + if (!AAS_AreaJumpPad(area2num)) + { + if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta5) + { + lreach->traveltime += aassettings.rs_falldamage5; + } //end if + else if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta10) + { + lreach->traveltime += aassettings.rs_falldamage10; + } //end if + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) + reach_jump++; + else + reach_walkoffledge++; + } //end if + return qfalse; +} //end of the function AAS_Reachability_Jump +//=========================================================================== +// create a possible ladder reachability from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Ladder(int area1num, int area2num) +{ + int i, j, k, l, edge1num, edge2num, sharededgenum, lowestedgenum; + int face1num, face2num, ladderface1num, ladderface2num; + int ladderface1vertical, ladderface2vertical, firstv; + float face1area, face2area, bestface1area, bestface2area; + float phys_jumpvel, maxjumpheight; + vec3_t area1point, area2point, v1, v2, up = {0, 0, 1}; + vec3_t mid, lowestpoint, start, end, sharededgevec, dir; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2, *ladderface1, *ladderface2; + aas_plane_t *plane1, *plane2; + aas_edge_t *sharededge, *edge1; + aas_lreachability_t *lreach; + aas_trace_t trace; + + if (!AAS_AreaLadder(area1num) || !AAS_AreaLadder(area2num)) return qfalse; + // + phys_jumpvel = aassettings.phys_jumpvel; + //maximum height a player can jump with the given initial z velocity + maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel); + + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + // + ladderface1 = NULL; + ladderface2 = NULL; + ladderface1num = 0; //make compiler happy + ladderface2num = 0; //make compiler happy + bestface1area = -9999; + bestface2area = -9999; + sharededgenum = 0; //make compiler happy + lowestedgenum = 0; //make compiler happy + // + for (i = 0; i < area1->numfaces; i++) + { + face1num = aasworld.faceindex[area1->firstface + i]; + face1 = &aasworld.faces[abs(face1num)]; + //if not a ladder face + if (!(face1->faceflags & FACE_LADDER)) continue; + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = aasworld.faceindex[area2->firstface + j]; + face2 = &aasworld.faces[abs(face2num)]; + //if not a ladder face + if (!(face2->faceflags & FACE_LADDER)) continue; + //check if the faces share an edge + for (k = 0; k < face1->numedges; k++) + { + edge1num = aasworld.edgeindex[face1->firstedge + k]; + for (l = 0; l < face2->numedges; l++) + { + edge2num = aasworld.edgeindex[face2->firstedge + l]; + if (abs(edge1num) == abs(edge2num)) + { + //get the face with the largest area + face1area = AAS_FaceArea(face1); + face2area = AAS_FaceArea(face2); + if (face1area > bestface1area && face2area > bestface2area) + { + bestface1area = face1area; + bestface2area = face2area; + ladderface1 = face1; + ladderface2 = face2; + ladderface1num = face1num; + ladderface2num = face2num; + sharededgenum = edge1num; + } //end if + break; + } //end if + } //end for + if (l != face2->numedges) break; + } //end for + } //end for + } //end for + // + if (ladderface1 && ladderface2) + { + //get the middle of the shared edge + sharededge = &aasworld.edges[abs(sharededgenum)]; + firstv = sharededgenum < 0; + // + VectorCopy(aasworld.vertexes[sharededge->v[firstv]], v1); + VectorCopy(aasworld.vertexes[sharededge->v[!firstv]], v2); + VectorAdd(v1, v2, area1point); + VectorScale(area1point, 0.5, area1point); + VectorCopy(area1point, area2point); + // + //if the face plane in area 1 is pretty much vertical + plane1 = &aasworld.planes[ladderface1->planenum ^ (ladderface1num < 0)]; + plane2 = &aasworld.planes[ladderface2->planenum ^ (ladderface2num < 0)]; + // + //get the points really into the areas + VectorSubtract(v2, v1, sharededgevec); + CrossProduct(plane1->normal, sharededgevec, dir); + VectorNormalize(dir); + //NOTE: 32 because that's larger than 16 (bot bbox x,y) + VectorMA(area1point, -32, dir, area1point); + VectorMA(area2point, 32, dir, area2point); + // + ladderface1vertical = abs(DotProduct(plane1->normal, up)) < 0.1; + ladderface2vertical = abs(DotProduct(plane2->normal, up)) < 0.1; + //there's only reachability between vertical ladder faces + if (!ladderface1vertical && !ladderface2vertical) return qfalse; + //if both vertical ladder faces + if (ladderface1vertical && ladderface2vertical + //and the ladder faces do not make a sharp corner + && DotProduct(plane1->normal, plane2->normal) > 0.7 + //and the shared edge is not too vertical + && abs(DotProduct(sharededgevec, up)) < 0.7) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area1point, lreach->start); + //VectorCopy(area2point, lreach->end); + VectorMA(area2point, -3, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface2num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area2point, lreach->start); + //VectorCopy(area1point, lreach->end); + VectorMA(area1point, -3, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_ladder++; + // + return qtrue; + } //end if + //if the second ladder face is also a ground face + //create ladder end (just ladder) reachability and + //walk off a ladder (ledge) reachability + if (ladderface1vertical && (ladderface2->faceflags & FACE_GROUND)) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area1point, lreach->start); + VectorCopy(area2point, lreach->end); + lreach->end[2] += 16; + VectorMA(lreach->end, -15, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface2num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area2point, lreach->start); + VectorCopy(area1point, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_walkoffledge++; + // + return qtrue; + } //end if + // + if (ladderface1vertical) + { + //find lowest edge of the ladder face + lowestpoint[2] = 99999; + for (i = 0; i < ladderface1->numedges; i++) + { + edge1num = abs(aasworld.edgeindex[ladderface1->firstedge + i]); + edge1 = &aasworld.edges[edge1num]; + // + VectorCopy(aasworld.vertexes[edge1->v[0]], v1); + VectorCopy(aasworld.vertexes[edge1->v[1]], v2); + // + VectorAdd(v1, v2, mid); + VectorScale(mid, 0.5, mid); + // + if (mid[2] < lowestpoint[2]) + { + VectorCopy(mid, lowestpoint); + lowestedgenum = edge1num; + } //end if + } //end for + // + plane1 = &aasworld.planes[ladderface1->planenum]; + //trace down in the middle of this edge + VectorMA(lowestpoint, 5, plane1->normal, start); + VectorCopy(start, end); + start[2] += 5; + end[2] -= 100; + //trace without entity collision + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + // + // +#ifdef REACH_DEBUG + if (trace.startsolid) + { + Log_Write("trace from area %d started in solid\r\n", area1num); + } //end if +#endif //REACH_DEBUG + // + trace.endpos[2] += 1; + area2num = AAS_PointAreaNum(trace.endpos); + // + area2 = &aasworld.areas[area2num]; + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + // + if (face2->faceflags & FACE_LADDER) + { + plane2 = &aasworld.planes[face2->planenum]; + if (abs(DotProduct(plane2->normal, up)) < 0.1) break; + } //end if + } //end for + //if from another area without vertical ladder faces + if (i >= area2->numfaces && area2num != area1num && + //the reachabilities shouldn't exist already + !AAS_ReachabilityExists(area1num, area2num) && + !AAS_ReachabilityExists(area2num, area1num)) + { + //if the height is jumpable + if (start[2] - trace.endpos[2] < maxjumpheight) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(lowestpoint, lreach->start); + VectorCopy(trace.endpos, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(trace.endpos, lreach->start); + //get the end point a little bit into the ladder + VectorMA(lowestpoint, -5, plane1->normal, lreach->end); + //get the end point a little higher + lreach->end[2] += 10; + lreach->traveltype = TRAVEL_JUMP; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_jump++; + // + return qtrue; +#ifdef REACH_DEBUG + Log_Write("jump up to ladder reach between %d and %d\r\n", area2num, area1num); +#endif //REACH_DEBUG + } //end if +#ifdef REACH_DEBUG + else Log_Write("jump too high between area %d and %d\r\n", area2num, area1num); +#endif //REACH_DEBUG + } //end if + /*//if slime or lava below the ladder + //try jump reachability from far towards the ladder + if (aasworld.areasettings[area2num].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) + { + for (i = 20; i <= 120; i += 20) + { + //trace down in the middle of this edge + VectorMA(lowestpoint, i, plane1->normal, start); + VectorCopy(start, end); + start[2] += 5; + end[2] -= 100; + //trace without entity collision + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) break; + trace.endpos[2] += 1; + area2num = AAS_PointAreaNum(trace.endpos); + if (area2num == area1num) continue; + // + if (start[2] - trace.endpos[2] > maxjumpheight) continue; + if (aasworld.areasettings[area2num].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) continue; + // + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(trace.endpos, lreach->start); + VectorCopy(lowestpoint, lreach->end); + lreach->end[2] += 5; + lreach->traveltype = TRAVEL_JUMP; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_jump++; + // + Log_Write("jump far to ladder reach between %d and %d\r\n", area2num, area1num); + // + break; + } //end for + } //end if*/ + } //end if + } //end if + return qfalse; +} //end of the function AAS_Reachability_Ladder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TravelFlagsForTeam(int ent) +{ + int notteam; + + if (!AAS_IntForBSPEpairKey(ent, "bot_notteam", ¬team)) + return 0; + if (notteam == 1) + return TRAVELFLAG_NOTTEAM1; + if (notteam == 2) + return TRAVELFLAG_NOTTEAM2; + return 0; +} //end of the function AAS_TravelFlagsForTeam +//=========================================================================== +// create possible teleporter reachabilities +// this is very game dependent.... :( +// +// classname = trigger_multiple or trigger_teleport +// target = "t1" +// +// classname = target_teleporter +// targetname = "t1" +// target = "t2" +// +// classname = misc_teleporter_dest +// targetname = "t2" +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_Teleport(void) +{ + int area1num, area2num; + char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + int ent, dest; + float angle; + vec3_t origin, destorigin, mins, maxs, end, angles; + vec3_t mid, velocity, cmdmove; + aas_lreachability_t *lreach; + aas_clientmove_t move; + aas_trace_t trace; + aas_link_t *areas, *link; + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (!strcmp(classname, "trigger_multiple")) + { + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); +//#ifdef REACH_DEBUG + botimport.Print(PRT_MESSAGE, "trigger_multiple model = \"%s\"\n", model); +//#endif REACH_DEBUG + VectorClear(angles); + AAS_BSPModelMinsMaxsOrigin(atoi(model+1), angles, mins, maxs, origin); + // + if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "trigger_multiple at %1.0f %1.0f %1.0f without target\n", + origin[0], origin[1], origin[2]); + continue; + } //end if + for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) + { + if (!AAS_ValueForBSPEpairKey(dest, "classname", classname, MAX_EPAIRKEY)) continue; + if (!strcmp(classname, "target_teleporter")) + { + if (!AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) continue; + if (!strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + continue; + } //end if + if (!AAS_ValueForBSPEpairKey(dest, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "target_teleporter without target\n"); + continue; + } //end if + } //end else + else if (!strcmp(classname, "trigger_teleport")) + { + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); +//#ifdef REACH_DEBUG + botimport.Print(PRT_MESSAGE, "trigger_teleport model = \"%s\"\n", model); +//#endif REACH_DEBUG + VectorClear(angles); + AAS_BSPModelMinsMaxsOrigin(atoi(model+1), angles, mins, maxs, origin); + // + if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "trigger_teleport at %1.0f %1.0f %1.0f without target\n", + origin[0], origin[1], origin[2]); + continue; + } //end if + } //end if + else + { + continue; + } //end else + // + for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) + { + //classname should be misc_teleporter_dest + //but I've also seen target_position and actually any + //entity could be used... burp + if (AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) + { + if (!strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + botimport.Print(PRT_ERROR, "teleporter without misc_teleporter_dest (%s)\n", target); + continue; + } //end if + if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); + continue; + } //end if + // + area2num = AAS_PointAreaNum(destorigin); + //if not teleported into a teleporter or into a jumppad + if (!AAS_AreaTeleporter(area2num) && !AAS_AreaJumpPad(area2num)) + { + VectorCopy(destorigin, end); + end[2] -= 64; + trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); + continue; + } //end if + area2num = AAS_PointAreaNum(trace.endpos); + // + /* + if (!AAS_AreaTeleporter(area2num) && + !AAS_AreaJumpPad(area2num) && + !AAS_AreaGrounded(area2num)) + { + VectorCopy(trace.endpos, destorigin); + } + else*/ + { + //predict where you'll end up + AAS_FloatForBSPEpairKey(dest, "angle", &angle); + if (angle) + { + VectorSet(angles, 0, angle, 0); + AngleVectors(angles, velocity, NULL, NULL); + VectorScale(velocity, 400, velocity); + } //end if + else + { + VectorClear(velocity); + } //end else + VectorClear(cmdmove); + AAS_PredictClientMovement(&move, -1, destorigin, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1f, + SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, qfalse); //qtrue); + area2num = AAS_PointAreaNum(move.endpos); + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) + { + botimport.Print(PRT_WARNING, "teleported into slime or lava at dest %s\n", target); + } //end if + VectorCopy(move.endpos, destorigin); + } //end else + } //end if + // + //botimport.Print(PRT_MESSAGE, "teleporter brush origin at %f %f %f\n", origin[0], origin[1], origin[2]); + //botimport.Print(PRT_MESSAGE, "teleporter brush mins = %f %f %f\n", mins[0], mins[1], mins[2]); + //botimport.Print(PRT_MESSAGE, "teleporter brush maxs = %f %f %f\n", maxs[0], maxs[1], maxs[2]); + VectorAdd(origin, mins, mins); + VectorAdd(origin, maxs, maxs); + // + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + //link an invalid (-1) entity + areas = AAS_LinkEntityClientBBox(mins, maxs, -1, PRESENCE_CROUCH); + if (!areas) botimport.Print(PRT_MESSAGE, "trigger_multiple not in any area\n"); + // + for (link = areas; link; link = link->next_area) + { + //if (!AAS_AreaGrounded(link->areanum)) continue; + if (!AAS_AreaTeleporter(link->areanum)) continue; + // + area1num = link->areanum; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) break; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(mid, lreach->start); + VectorCopy(destorigin, lreach->end); + lreach->traveltype = TRAVEL_TELEPORT; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_teleport; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_teleport++; + } //end for + //unlink the invalid entity + AAS_UnlinkFromAreas(areas); + } //end for +} //end of the function AAS_Reachability_Teleport +//=========================================================================== +// create possible elevator (func_plat) reachabilities +// this is very game dependent.... :( +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_Elevator(void) +{ + int area1num, area2num, modelnum, i, j, k, l, n, p; + float lip, height, speed; + char model[MAX_EPAIRKEY], classname[MAX_EPAIRKEY]; + int ent; + vec3_t mins, maxs, origin, angles = {0, 0, 0}; + vec3_t pos1, pos2, mids, platbottom, plattop; + vec3_t bottomorg, toporg, start, end, dir; + vec_t xvals[8], yvals[8], xvals_top[8], yvals_top[8]; + aas_lreachability_t *lreach; + aas_trace_t trace; + +#ifdef REACH_DEBUG + Log_Write("AAS_Reachability_Elevator\r\n"); +#endif //REACH_DEBUG + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (!strcmp(classname, "func_plat")) + { +#ifdef REACH_DEBUG + Log_Write("found func plat\r\n"); +#endif //REACH_DEBUG + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "func_plat without model\n"); + continue; + } //end if + //get the model number, and skip the leading * + modelnum = atoi(model+1); + if (modelnum <= 0) + { + botimport.Print(PRT_ERROR, "func_plat with invalid model number\n"); + continue; + } //end if + //get the mins, maxs and origin of the model + //NOTE: the origin is usually (0,0,0) and the mins and maxs + // are the absolute mins and maxs + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + AAS_VectorForBSPEpairKey(ent, "origin", origin); + //pos1 is the top position, pos2 is the bottom + VectorCopy(origin, pos1); + VectorCopy(origin, pos2); + //get the lip of the plat + AAS_FloatForBSPEpairKey(ent, "lip", &lip); + if (!lip) lip = 8; + //get the movement height of the plat + AAS_FloatForBSPEpairKey(ent, "height", &height); + if (!height) height = (maxs[2] - mins[2]) - lip; + //get the speed of the plat + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) speed = 200; + //get bottom position below pos1 + pos2[2] -= height; + // + //get a point just above the plat in the bottom position + VectorAdd(mins, maxs, mids); + VectorMA(pos2, 0.5, mids, platbottom); + platbottom[2] = maxs[2] - (pos1[2] - pos2[2]) + 2; + //get a point just above the plat in the top position + VectorAdd(mins, maxs, mids); + VectorMA(pos2, 0.5, mids, plattop); + plattop[2] = maxs[2] + 2; + // + /*if (!area1num) + { + Log_Write("no grounded area near plat bottom\r\n"); + continue; + } //end if*/ + //get the mins and maxs a little larger + for (i = 0; i < 3; i++) + { + mins[i] -= 1; + maxs[i] += 1; + } //end for + // + //botimport.Print(PRT_MESSAGE, "platbottom[2] = %1.1f plattop[2] = %1.1f\n", platbottom[2], plattop[2]); + // + VectorAdd(mins, maxs, mids); + VectorScale(mids, 0.5, mids); + // + xvals[0] = mins[0]; xvals[1] = mids[0]; xvals[2] = maxs[0]; xvals[3] = mids[0]; + yvals[0] = mids[1]; yvals[1] = maxs[1]; yvals[2] = mids[1]; yvals[3] = mins[1]; + // + xvals[4] = mins[0]; xvals[5] = maxs[0]; xvals[6] = maxs[0]; xvals[7] = mins[0]; + yvals[4] = maxs[1]; yvals[5] = maxs[1]; yvals[6] = mins[1]; yvals[7] = mins[1]; + //find adjacent areas around the bottom of the plat + for (i = 0; i < 9; i++) + { + if (i < 8) //check at the sides of the plat + { + bottomorg[0] = origin[0] + xvals[i]; + bottomorg[1] = origin[1] + yvals[i]; + bottomorg[2] = platbottom[2] + 16; + //get a grounded or swim area near the plat in the bottom position + area1num = AAS_PointAreaNum(bottomorg); + for (k = 0; k < 16; k++) + { + if (area1num) + { + if (AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) break; + } //end if + bottomorg[2] += 4; + area1num = AAS_PointAreaNum(bottomorg); + } //end if + //if in solid + if (k >= 16) + { + continue; + } //end if + } //end if + else //at the middle of the plat + { + VectorCopy(plattop, bottomorg); + bottomorg[2] += 24; + area1num = AAS_PointAreaNum(bottomorg); + if (!area1num) continue; + VectorCopy(platbottom, bottomorg); + bottomorg[2] += 24; + } //end else + //look at adjacent areas around the top of the plat + //make larger steps to outside the plat everytime + for (n = 0; n < 3; n++) + { + for (k = 0; k < 3; k++) + { + mins[k] -= 4; + maxs[k] += 4; + } //end for + xvals_top[0] = mins[0]; xvals_top[1] = mids[0]; xvals_top[2] = maxs[0]; xvals_top[3] = mids[0]; + yvals_top[0] = mids[1]; yvals_top[1] = maxs[1]; yvals_top[2] = mids[1]; yvals_top[3] = mins[1]; + // + xvals_top[4] = mins[0]; xvals_top[5] = maxs[0]; xvals_top[6] = maxs[0]; xvals_top[7] = mins[0]; + yvals_top[4] = maxs[1]; yvals_top[5] = maxs[1]; yvals_top[6] = mins[1]; yvals_top[7] = mins[1]; + // + for (j = 0; j < 8; j++) + { + toporg[0] = origin[0] + xvals_top[j]; + toporg[1] = origin[1] + yvals_top[j]; + toporg[2] = plattop[2] + 16; + //get a grounded or swim area near the plat in the top position + area2num = AAS_PointAreaNum(toporg); + for (l = 0; l < 16; l++) + { + if (area2num) + { + if (AAS_AreaGrounded(area2num) || AAS_AreaSwim(area2num)) + { + VectorCopy(plattop, start); + start[2] += 32; + VectorCopy(toporg, end); + end[2] += 1; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.fraction >= 1) break; + } //end if + } //end if + toporg[2] += 4; + area2num = AAS_PointAreaNum(toporg); + } //end if + //if in solid + if (l >= 16) continue; + //never create a reachability in the same area + if (area2num == area1num) continue; + //if the area isn't grounded + if (!AAS_AreaGrounded(area2num)) continue; + //if there already exists reachability between the areas + if (AAS_ReachabilityExists(area1num, area2num)) continue; + //if the reachability start is within the elevator bounding box + VectorSubtract(bottomorg, platbottom, dir); + VectorNormalize(dir); + dir[0] = bottomorg[0] + 24 * dir[0]; + dir[1] = bottomorg[1] + 24 * dir[1]; + dir[2] = bottomorg[2]; + // + for (p = 0; p < 3; p++) + if (dir[p] < origin[p] + mins[p] || dir[p] > origin[p] + maxs[p]) break; + if (p >= 3) continue; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) continue; + lreach->areanum = area2num; + //the facenum is the model number + lreach->facenum = modelnum; + //the edgenum is the height + lreach->edgenum = (int) height; + // + VectorCopy(dir, lreach->start); + VectorCopy(toporg, lreach->end); + lreach->traveltype = TRAVEL_ELEVATOR; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_startelevator + height * 100 / speed; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //don't go any further to the outside + n = 9999; + // +#ifdef REACH_DEBUG + Log_Write("elevator reach from %d to %d\r\n", area1num, area2num); +#endif //REACH_DEBUG + // + reach_elevator++; + } //end for + } //end for + } //end for + } //end if + } //end for +} //end of the function AAS_Reachability_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_lreachability_t *AAS_FindFaceReachabilities(vec3_t *facepoints, int numpoints, aas_plane_t *plane, int towardsface) +{ + int i, j, k, l; + int facenum, edgenum, bestfacenum; + float *v1, *v2, *v3, *v4; + float bestdist, speed, hordist, dist; + vec3_t beststart, beststart2, bestend, bestend2, tmp, hordir, testpoint; + aas_lreachability_t *lreach, *lreachabilities; + aas_area_t *area; + aas_face_t *face; + aas_edge_t *edge; + aas_plane_t *faceplane, *bestfaceplane; + + // + lreachabilities = NULL; + bestfacenum = 0; + bestfaceplane = NULL; + // + for (i = 1; i < aasworld.numareas; i++) + { + area = &aasworld.areas[i]; + // get the shortest distance between one of the func_bob start edges and + // one of the face edges of area1 + bestdist = 999999; + for (j = 0; j < area->numfaces; j++) + { + facenum = aasworld.faceindex[area->firstface + j]; + face = &aasworld.faces[abs(facenum)]; + //if not a ground face + if (!(face->faceflags & FACE_GROUND)) continue; + //get the ground planes + faceplane = &aasworld.planes[face->planenum]; + // + for (k = 0; k < face->numedges; k++) + { + edgenum = abs(aasworld.edgeindex[face->firstedge + k]); + edge = &aasworld.edges[edgenum]; + //calculate the minimum distance between the two edges + v1 = aasworld.vertexes[edge->v[0]]; + v2 = aasworld.vertexes[edge->v[1]]; + // + for (l = 0; l < numpoints; l++) + { + v3 = facepoints[l]; + v4 = facepoints[(l+1) % numpoints]; + dist = AAS_ClosestEdgePoints(v1, v2, v3, v4, faceplane, plane, + beststart, bestend, + beststart2, bestend2, bestdist); + if (dist < bestdist) + { + bestfacenum = facenum; + bestfaceplane = faceplane; + bestdist = dist; + } //end if + } //end for + } //end for + } //end for + // + if (bestdist > 192) continue; + // + VectorMiddle(beststart, beststart2, beststart); + VectorMiddle(bestend, bestend2, bestend); + // + if (!towardsface) + { + VectorCopy(beststart, tmp); + VectorCopy(bestend, beststart); + VectorCopy(tmp, bestend); + } //end if + // + VectorSubtract(bestend, beststart, hordir); + hordir[2] = 0; + hordist = VectorLength(hordir); + // + if (hordist > 2 * AAS_MaxJumpDistance(aassettings.phys_jumpvel)) continue; + //the end point should not be significantly higher than the start point + if (bestend[2] - 32 > beststart[2]) continue; + //don't fall down too far + if (bestend[2] < beststart[2] - 128) continue; + //the distance should not be too far + if (hordist > 32) + { + //check for walk off ledge + if (!AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) continue; + } //end if + // + beststart[2] += 1; + bestend[2] += 1; + // + if (towardsface) VectorCopy(bestend, testpoint); + else VectorCopy(beststart, testpoint); + testpoint[2] = 0; + testpoint[2] = (bestfaceplane->dist - DotProduct(bestfaceplane->normal, testpoint)) / bestfaceplane->normal[2]; + // + if (!AAS_PointInsideFace(bestfacenum, testpoint, 0.1f)) + { + //if the faces are not overlapping then only go down + if (bestend[2] - 16 > beststart[2]) continue; + } //end if + lreach = AAS_AllocReachability(); + if (!lreach) return lreachabilities; + lreach->areanum = i; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(beststart, lreach->start); + VectorCopy(bestend, lreach->end); + lreach->traveltype = 0; + lreach->traveltime = 0; + lreach->next = lreachabilities; + lreachabilities = lreach; +#ifndef _XBOX +#ifndef BSPC + if (towardsface) AAS_PermanentLine(lreach->start, lreach->end, 1); + else AAS_PermanentLine(lreach->start, lreach->end, 2); +#endif +#endif + } //end for + return lreachabilities; +} //end of the function AAS_FindFaceReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_FuncBobbing(void) +{ + int ent, spawnflags, modelnum, axis; + int i, numareas, areas[10]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + vec3_t origin, move_end, move_start, move_start_top, move_end_top; + vec3_t mins, maxs, angles = {0, 0, 0}; + vec3_t start_edgeverts[4], end_edgeverts[4], mid; + vec3_t org, start, end, dir, points[10]; + float height; + aas_plane_t start_plane, end_plane; + aas_lreachability_t *startreach, *endreach, *nextstartreach, *nextendreach, *lreach; + aas_lreachability_t *firststartreach, *firstendreach; + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (strcmp(classname, "func_bobbing")) continue; + AAS_FloatForBSPEpairKey(ent, "height", &height); + if (!height) height = 32; + // + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "func_bobbing without model\n"); + continue; + } //end if + //get the model number, and skip the leading * + modelnum = atoi(model+1); + if (modelnum <= 0) + { + botimport.Print(PRT_ERROR, "func_bobbing with invalid model number\n"); + continue; + } //end if + //if the entity has an origin set then use it + if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) + VectorSet(origin, 0, 0, 0); + // + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + // + VectorAdd(mins, origin, mins); + VectorAdd(maxs, origin, maxs); + // + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + VectorCopy(mid, origin); + // + VectorCopy(origin, move_end); + VectorCopy(origin, move_start); + // + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // set the axis of bobbing + if (spawnflags & 1) axis = 0; + else if (spawnflags & 2) axis = 1; + else axis = 2; + // + move_start[axis] -= height; + move_end[axis] += height; + // + Log_Write("funcbob model %d, start = {%1.1f, %1.1f, %1.1f} end = {%1.1f, %1.1f, %1.1f}\n", + modelnum, move_start[0], move_start[1], move_start[2], move_end[0], move_end[1], move_end[2]); + // +#ifndef BSPC + /* + AAS_DrawPermanentCross(move_start, 4, 1); + AAS_DrawPermanentCross(move_end, 4, 2); + */ +#endif + // + for (i = 0; i < 4; i++) + { + VectorCopy(move_start, start_edgeverts[i]); + start_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z + start_edgeverts[i][2] += 24; //+ player origin to ground dist + } //end for + start_edgeverts[0][0] += maxs[0] - mid[0]; + start_edgeverts[0][1] += maxs[1] - mid[1]; + start_edgeverts[1][0] += maxs[0] - mid[0]; + start_edgeverts[1][1] += mins[1] - mid[1]; + start_edgeverts[2][0] += mins[0] - mid[0]; + start_edgeverts[2][1] += mins[1] - mid[1]; + start_edgeverts[3][0] += mins[0] - mid[0]; + start_edgeverts[3][1] += maxs[1] - mid[1]; + // + start_plane.dist = start_edgeverts[0][2]; + VectorSet(start_plane.normal, 0, 0, 1); + // + for (i = 0; i < 4; i++) + { + VectorCopy(move_end, end_edgeverts[i]); + end_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z + end_edgeverts[i][2] += 24; //+ player origin to ground dist + } //end for + end_edgeverts[0][0] += maxs[0] - mid[0]; + end_edgeverts[0][1] += maxs[1] - mid[1]; + end_edgeverts[1][0] += maxs[0] - mid[0]; + end_edgeverts[1][1] += mins[1] - mid[1]; + end_edgeverts[2][0] += mins[0] - mid[0]; + end_edgeverts[2][1] += mins[1] - mid[1]; + end_edgeverts[3][0] += mins[0] - mid[0]; + end_edgeverts[3][1] += maxs[1] - mid[1]; + // + end_plane.dist = end_edgeverts[0][2]; + VectorSet(end_plane.normal, 0, 0, 1); + // +#ifndef BSPC +#if 0 + for (i = 0; i < 4; i++) + { + AAS_PermanentLine(start_edgeverts[i], start_edgeverts[(i+1)%4], 1); + AAS_PermanentLine(end_edgeverts[i], end_edgeverts[(i+1)%4], 1); + } //end for +#endif +#endif + VectorCopy(move_start, move_start_top); + move_start_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z + VectorCopy(move_end, move_end_top); + move_end_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z + // + if (!AAS_PointAreaNum(move_start_top)) continue; + if (!AAS_PointAreaNum(move_end_top)) continue; + // + for (i = 0; i < 2; i++) + { + firststartreach = firstendreach = NULL; + // + if (i == 0) + { + firststartreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qtrue); + firstendreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qfalse); + } //end if + else + { + firststartreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qtrue); + firstendreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qfalse); + } //end else + // + //create reachabilities from start to end + for (startreach = firststartreach; startreach; startreach = nextstartreach) + { + nextstartreach = startreach->next; + // + //trace = AAS_TraceClientBBox(startreach->start, move_start_top, PRESENCE_NORMAL, -1); + //if (trace.fraction < 1) continue; + // + for (endreach = firstendreach; endreach; endreach = nextendreach) + { + nextendreach = endreach->next; + // + //trace = AAS_TraceClientBBox(endreach->end, move_end_top, PRESENCE_NORMAL, -1); + //if (trace.fraction < 1) continue; + // + Log_Write("funcbob reach from area %d to %d\n", startreach->areanum, endreach->areanum); + // + // + if (i == 0) VectorCopy(move_start_top, org); + else VectorCopy(move_end_top, org); + VectorSubtract(startreach->start, org, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorCopy(startreach->start, start); + VectorMA(startreach->start, 1, dir, start); + start[2] += 1; + VectorMA(startreach->start, 16, dir, end); + end[2] += 1; + // + numareas = AAS_TraceAreas(start, end, areas, points, 10); + if (numareas <= 0) continue; + if (numareas > 1) VectorCopy(points[1], startreach->start); + else VectorCopy(end, startreach->start); + // + if (!AAS_PointAreaNum(startreach->start)) continue; + if (!AAS_PointAreaNum(endreach->end)) continue; + // + lreach = AAS_AllocReachability(); + lreach->areanum = endreach->areanum; + if (i == 0) lreach->edgenum = ((int)move_start[axis] << 16) | ((int) move_end[axis] & 0x0000ffff); + else lreach->edgenum = ((int)move_end[axis] << 16) | ((int) move_start[axis] & 0x0000ffff); + lreach->facenum = (spawnflags << 16) | modelnum; + VectorCopy(startreach->start, lreach->start); + VectorCopy(endreach->end, lreach->end); +#ifndef BSPC +// AAS_DrawArrow(lreach->start, lreach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); +// AAS_PermanentLine(lreach->start, lreach->end, 1); +#endif + lreach->traveltype = TRAVEL_FUNCBOB; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_funcbob; + reach_funcbob++; + lreach->next = areareachability[startreach->areanum]; + areareachability[startreach->areanum] = lreach; + // + } //end for + } //end for + for (startreach = firststartreach; startreach; startreach = nextstartreach) + { + nextstartreach = startreach->next; + AAS_FreeReachability(startreach); + } //end for + for (endreach = firstendreach; endreach; endreach = nextendreach) + { + nextendreach = endreach->next; + AAS_FreeReachability(endreach); + } //end for + //only go up with func_bobbing entities that go up and down + if (!(spawnflags & 1) && !(spawnflags & 2)) break; + } //end for + } //end for +} //end of the function AAS_Reachability_FuncBobbing +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_JumpPad(void) +{ + int face2num, i, ret, area2num, visualize, ent, bot_visualizejumppads; + //int modelnum, ent2; + //float dist, time, height, gravity, forward; + float speed, zvel, hordist; + aas_face_t *face2; + aas_area_t *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, dir, cmdmove; + vec3_t velocity, absmins, absmaxs; + //vec3_t origin, ent2origin, angles, teststart; + aas_clientmove_t move; + //aas_trace_t trace; + aas_link_t *areas, *link; + //char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + char classname[MAX_EPAIRKEY]; + +#ifdef BSPC + bot_visualizejumppads = 0; +#else + bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0"); +#endif + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (strcmp(classname, "trigger_push")) continue; + // + if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue; + /* + // + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) speed = 1000; +// AAS_VectorForBSPEpairKey(ent, "angles", angles); +// AAS_SetMovedir(angles, velocity); +// VectorScale(velocity, speed, velocity); + VectorClear(angles); + //get the mins, maxs and origin of the model + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); + if (model[0]) modelnum = atoi(model+1); + else modelnum = 0; + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); + VectorAdd(origin, absmins, absmins); + VectorAdd(origin, absmaxs, absmaxs); + // +#ifdef REACH_DEBUG + botimport.Print(PRT_MESSAGE, "absmins = %f %f %f\n", absmins[0], absmins[1], absmins[2]); + botimport.Print(PRT_MESSAGE, "absmaxs = %f %f %f\n", absmaxs[0], absmaxs[1], absmaxs[2]); +#endif REACH_DEBUG + VectorAdd(absmins, absmaxs, origin); + VectorScale (origin, 0.5, origin); + + //get the start areas + VectorCopy(origin, teststart); + teststart[2] += 64; + trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); + VectorCopy(origin, areastart); + } //end if + else + { + VectorCopy(trace.endpos, areastart); + } //end else + areastart[2] += 0.125; + // + //AAS_DrawPermanentCross(origin, 4, 4); + //get the target entity + AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); + for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) + { + if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) continue; + if (!strcmp(targetname, target)) break; + } //end for + if (!ent2) + { + botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); + continue; + } //end if + AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); + // + height = ent2origin[2] - origin[2]; + gravity = aassettings.sv_gravity; + time = sqrt( height / ( 0.5 * gravity ) ); + if (!time) + { + botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); + continue; + } //end if + // set s.origin2 to the push velocity + VectorSubtract ( ent2origin, origin, velocity); + dist = VectorNormalize( velocity); + forward = dist / time; + //FIXME: why multiply by 1.1 + forward *= 1.1; + VectorScale(velocity, forward, velocity); + velocity[2] = time * gravity; + */ + //get the areas the jump pad brush is in + areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); + /* + for (link = areas; link; link = link->next_area) + { + if (link->areanum == 563) + { + ret = qfalse; + } + } + */ + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaJumpPad(link->areanum)) break; + } //end for + if (!link) + { + botimport.Print(PRT_MESSAGE, "trigger_push not in any jump pad area\n"); + AAS_UnlinkFromAreas(areas); + continue; + } //end if + // + botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); + //if there is a horizontal velocity check for a reachability without air control + if (velocity[0] || velocity[1]) + { + VectorSet(cmdmove, 0, 0, 0); + //VectorCopy(velocity, cmdmove); + //cmdmove[2] = 0; + Com_Memset(&move, 0, sizeof(aas_clientmove_t)); + area2num = 0; + for (i = 0; i < 20; i++) + { + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1f, + SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, bot_visualizejumppads); + area2num = move.endarea; + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + if (link->areanum == area2num) break; + } //end if + if (!link) break; + VectorCopy(move.endpos, areastart); + VectorCopy(move.velocity, velocity); + } //end for + if (area2num && i < 20) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + if (AAS_ReachabilityExists(link->areanum, area2num)) continue; + //create a rocket or bfg jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + AAS_UnlinkFromAreas(areas); + return; + } //end if + lreach->areanum = area2num; + //NOTE: the facenum is the Z velocity + lreach->facenum = velocity[2]; + //NOTE: the edgenum is the horizontal velocity + lreach->edgenum = sqrt(velocity[0] * velocity[0] + velocity[1] * velocity[1]); + VectorCopy(areastart, lreach->start); + VectorCopy(move.endpos, lreach->end); + lreach->traveltype = TRAVEL_JUMPPAD; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_jumppad; + lreach->next = areareachability[link->areanum]; + areareachability[link->areanum] = lreach; + // + reach_jumppad++; + } //end for + } //end if + } //end if + // + if (fabs(velocity[0]) > 100 || fabs(velocity[1]) > 100) continue; + //check for areas we can reach with air control + for (area2num = 1; area2num < aasworld.numareas; area2num++) + { + visualize = qfalse; + /* + if (area2num == 3568) + { + for (link = areas; link; link = link->next_area) + { + if (link->areanum == 3380) + { + visualize = qtrue; + botimport.Print(PRT_MESSAGE, "bah\n"); + } //end if + } //end for + } //end if*/ + //never try to go back to one of the original jumppad areas + //and don't create reachabilities if they already exist + for (link = areas; link; link = link->next_area) + { + if (AAS_ReachabilityExists(link->areanum, area2num)) break; + if (AAS_AreaJumpPad(link->areanum)) + { + if (link->areanum == area2num) break; + } //end if + } //end if + if (link) continue; + // + area2 = &aasworld.areas[area2num]; + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + //if it is not a ground face + if (!(face2->faceflags & FACE_GROUND)) continue; + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up + if (facecenter[2] < areastart[2]) continue; + //get the jumppad jump z velocity + zvel = velocity[2]; + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed + ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); + if (ret && speed < 150) + { + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + dir[2] = 0; + hordist = VectorNormalize(dir); + //if (hordist < 1.6 * facecenter[2] - areastart[2]) + { + //get command movement + VectorScale(dir, speed, cmdmove); + // + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_HITGROUNDAREA, area2num, visualize); + //if prediction time wasn't enough to fully predict the movement + //don't enter slime or lava and don't fall from too high + if (move.frames < 30 && + !(move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + && (move.stopevent & (SE_HITGROUNDAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER))) + { + //never go back to the same jumppad + for (link = areas; link; link = link->next_area) + { + if (link->areanum == move.endarea) break; + } + if (!link) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + if (AAS_ReachabilityExists(link->areanum, area2num)) continue; + //create a jumppad reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + AAS_UnlinkFromAreas(areas); + return; + } //end if + lreach->areanum = move.endarea; + //NOTE: the facenum is the Z velocity + lreach->facenum = velocity[2]; + //NOTE: the edgenum is the horizontal velocity + lreach->edgenum = sqrt(cmdmove[0] * cmdmove[0] + cmdmove[1] * cmdmove[1]); + VectorCopy(areastart, lreach->start); + VectorCopy(facecenter, lreach->end); + lreach->traveltype = TRAVEL_JUMPPAD; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_aircontrolledjumppad; + lreach->next = areareachability[link->areanum]; + areareachability[link->areanum] = lreach; + // + reach_jumppad++; + } //end for + } + } //end if + } //end if + } //end for + } //end for + } //end for + AAS_UnlinkFromAreas(areas); + } //end for +} //end of the function AAS_Reachability_JumpPad +//=========================================================================== +// never point at ground faces +// always a higher and pretty far area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Grapple(int area1num, int area2num) +{ + int face2num, i, j, areanum, numareas, areas[20]; + float mingrappleangle, z, hordist; + bsp_trace_t bsptrace; + aas_trace_t trace; + aas_face_t *face2; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, start, end, dir, down = {0, 0, -1}; + vec_t *v; + + //only grapple when on the ground or swimming + if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) return qfalse; + //don't grapple from a crouch area + if (!(AAS_AreaPresenceType(area1num) & PRESENCE_NORMAL)) return qfalse; + //NOTE: disabled area swim it doesn't work right + if (AAS_AreaSwim(area1num)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //don't grapple towards way lower areas + if (area2->maxs[2] < area1->mins[2]) return qfalse; + // + VectorCopy(aasworld.areas[area1num].center, start); + //if not a swim area + if (!AAS_AreaSwim(area1num)) + { + if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?\r\n", area1num, + start[0], start[1], start[2]); + VectorCopy(start, end); + end[2] -= 1000; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) return qfalse; + VectorCopy(trace.endpos, areastart); + } //end if + else + { + if (!(AAS_PointContents(start) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return qfalse; + } //end else + // + //start is now the start point + // + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + //if it is not a solid face + if (!(face2->faceflags & FACE_SOLID)) continue; + //direction towards the first vertex of the face + v = aasworld.vertexes[aasworld.edges[abs(aasworld.edgeindex[face2->firstedge])].v[0]]; + VectorSubtract(v, areastart, dir); + //if the face plane is facing away + if (DotProduct(aasworld.planes[face2->planenum].normal, dir) > 0) continue; + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up with the grapple + if (facecenter[2] < areastart[2] + 64) continue; + //only use vertical faces or downward facing faces + if (DotProduct(aasworld.planes[face2->planenum].normal, down) < 0) continue; + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + // + z = dir[2]; + dir[2] = 0; + hordist = VectorLength(dir); + if (!hordist) continue; + //if too far + if (hordist > 2000) continue; + //check the minimal angle of the movement + mingrappleangle = 15; //15 degrees + if (z / hordist < tan(2 * M_PI * mingrappleangle / 360)) continue; + // + VectorCopy(facecenter, start); + VectorMA(facecenter, -500, aasworld.planes[face2->planenum].normal, end); + // + bsptrace = AAS_Trace(start, NULL, NULL, end, 0, CONTENTS_SOLID); + //the grapple won't stick to the sky and the grapple point should be near the AAS wall + if ((bsptrace.surface.flags & SURF_SKY) || (bsptrace.fraction * 500 > 32)) continue; + //trace a full bounding box from the area center on the ground to + //the center of the face + VectorSubtract(facecenter, areastart, dir); + VectorNormalize(dir); + VectorMA(areastart, 4, dir, start); + VectorCopy(bsptrace.endpos, end); + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + VectorSubtract(trace.endpos, facecenter, dir); + if (VectorLength(dir) > 24) continue; + // + VectorCopy(trace.endpos, start); + VectorCopy(trace.endpos, end); + end[2] -= AAS_FallDamageDistance(); + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + if (trace.fraction >= 1) continue; + //area to end in + areanum = AAS_PointAreaNum(trace.endpos); + //if not in lava or slime + if (aasworld.areasettings[areanum].contents & (AREACONTENTS_SLIME|AREACONTENTS_LAVA)) + { + continue; + } //end if + //do not go the the source area + if (areanum == area1num) continue; + //don't create reachabilities if they already exist + if (AAS_ReachabilityExists(area1num, areanum)) continue; + //only end in areas we can stand + if (!AAS_AreaGrounded(areanum)) continue; + //never go through cluster portals!! + numareas = AAS_TraceAreas(areastart, bsptrace.endpos, areas, NULL, 20); + if (numareas >= 20) continue; + for (j = 0; j < numareas; j++) + { + if (aasworld.areasettings[areas[j]].contents & AREACONTENTS_CLUSTERPORTAL) break; + } //end for + if (j < numareas) continue; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = areanum; + lreach->facenum = face2num; + lreach->edgenum = 0; + VectorCopy(areastart, lreach->start); + //VectorCopy(facecenter, lreach->end); + VectorCopy(bsptrace.endpos, lreach->end); + lreach->traveltype = TRAVEL_GRAPPLEHOOK; + VectorSubtract(lreach->end, lreach->start, dir); + lreach->traveltime = aassettings.rs_startgrapple + VectorLength(dir) * 0.25; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_grapple++; + } //end for + // + return qfalse; +} //end of the function AAS_Reachability_Grapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetWeaponJumpAreaFlags(void) +{ + int ent, i; + vec3_t mins = {-15, -15, -15}, maxs = {15, 15, 15}; + vec3_t origin; + int areanum, weaponjumpareas, spawnflags; + char classname[MAX_EPAIRKEY]; + + weaponjumpareas = 0; + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if ( + !strcmp(classname, "item_armor_body") || + !strcmp(classname, "item_health") || + !strcmp(classname, "weapon_disruptor") || + !strcmp(classname, "weapon_repeater") || + !strcmp(classname, "weapon_demp2") || + !strcmp(classname, "weapon_flechette") || + !strcmp(classname, "weapon_rocket_launcher")) + { + if (AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + spawnflags = 0; + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + //if not a stationary item + if (!(spawnflags & 1)) + { + if (!AAS_DropToFloor(origin, mins, maxs)) + { + botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end if + //areanum = AAS_PointAreaNum(origin); + areanum = AAS_BestReachableArea(origin, mins, maxs, origin); + //the bot may rocket jump towards this area + aasworld.areasettings[areanum].areaflags |= AREA_WEAPONJUMP; + // + //if (!AAS_AreaGrounded(areanum)) + // botimport.Print(PRT_MESSAGE, "area not grounded\n"); + // + weaponjumpareas++; + } //end if + } //end if + } //end for + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + aasworld.areasettings[i].areaflags |= AREA_WEAPONJUMP; + weaponjumpareas++; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "%d weapon jump areas\n", weaponjumpareas); +} //end of the function AAS_SetWeaponJumpAreaFlags +//=========================================================================== +// create a possible weapon jump reachability from area1 to area2 +// +// check if there's a cool item in the second area +// check if area1 is lower than area2 +// check if the bot can rocketjump from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_WeaponJump(int area1num, int area2num) +{ + int face2num, i, n, ret, visualize; + float speed, zvel, hordist; + aas_face_t *face2; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, start, end, dir, cmdmove;// teststart; + vec3_t velocity; + aas_clientmove_t move; + aas_trace_t trace; + + visualize = qfalse; +// if (area1num == 4436 && area2num == 4318) +// { +// visualize = qtrue; +// } + if (!AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) return qfalse; + if (!AAS_AreaGrounded(area2num)) return qfalse; + //NOTE: only weapon jump towards areas with an interesting item in it?? + if (!(aasworld.areasettings[area2num].areaflags & AREA_WEAPONJUMP)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //don't weapon jump towards way lower areas + if (area2->maxs[2] < area1->mins[2]) return qfalse; + // + VectorCopy(aasworld.areas[area1num].center, start); + //if not a swim area + if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?\r\n", area1num, + start[0], start[1], start[2]); + VectorCopy(start, end); + end[2] -= 1000; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) return qfalse; + VectorCopy(trace.endpos, areastart); + // + //areastart is now the start point + // + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + //if it is not a solid face + if (!(face2->faceflags & FACE_GROUND)) continue; + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up with weapon jumps + if (facecenter[2] < areastart[2] + 64) continue; + //NOTE: set to 2 to allow bfg jump reachabilities + for (n = 0; n < 1/*2*/; n++) + { + //get the rocket jump z velocity + if (n) zvel = AAS_BFGJumpZVelocity(areastart); + else zvel = AAS_RocketJumpZVelocity(areastart); + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); + if (ret && speed < 300) + { + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + dir[2] = 0; + hordist = VectorNormalize(dir); + //if (hordist < 1.6 * (facecenter[2] - areastart[2])) + { + //get command movement + VectorScale(dir, speed, cmdmove); + VectorSet(velocity, 0, 0, zvel); + /* + //get command movement + VectorScale(dir, speed, velocity); + velocity[2] = zvel; + VectorSet(cmdmove, 0, 0, 0); + */ + // + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_HITGROUND|SE_HITGROUNDAREA, area2num, visualize); + //if prediction time wasn't enough to fully predict the movement + //don't enter slime or lava and don't fall from too high + if (move.frames < 30 && + !(move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + && (move.stopevent & (SE_HITGROUNDAREA|SE_TOUCHJUMPPAD))) + { + //create a rocket or bfg jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(areastart, lreach->start); + VectorCopy(facecenter, lreach->end); + if (n) + { + lreach->traveltype = TRAVEL_BFGJUMP; + lreach->traveltime = aassettings.rs_bfgjump; + } //end if + else + { + lreach->traveltype = TRAVEL_ROCKETJUMP; + lreach->traveltime = aassettings.rs_rocketjump; + } //end else + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_rocketjump++; + return qtrue; + } //end if + } //end if + } //end if + } //end for + } //end for + // + return qfalse; +} //end of the function AAS_Reachability_WeaponJump +//=========================================================================== +// calculates additional walk off ledge reachabilities for the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_WalkOffLedge(int areanum) +{ + int i, j, k, l, m, n, p, areas[10], numareas; + int face1num, face2num, face3num, edge1num, edge2num, edge3num; + int otherareanum, gap, reachareanum, side; + aas_area_t *area, *area2; + aas_face_t *face1, *face2, *face3; + aas_edge_t *edge; + aas_plane_t *plane; + vec_t *v1, *v2; + vec3_t sharededgevec, mid, dir, testend; + aas_lreachability_t *lreach; + aas_trace_t trace; + + if (!AAS_AreaGrounded(areanum) || AAS_AreaSwim(areanum)) return; + // + area = &aasworld.areas[areanum]; + // + for (i = 0; i < area->numfaces; i++) + { + face1num = aasworld.faceindex[area->firstface + i]; + face1 = &aasworld.faces[abs(face1num)]; + //face 1 must be a ground face + if (!(face1->faceflags & FACE_GROUND)) continue; + //go through all the edges of this ground face + for (k = 0; k < face1->numedges; k++) + { + edge1num = aasworld.edgeindex[face1->firstedge + k]; + //find another not ground face using this same edge + for (j = 0; j < area->numfaces; j++) + { + face2num = aasworld.faceindex[area->firstface + j]; + face2 = &aasworld.faces[abs(face2num)]; + //face 2 may not be a ground face + if (face2->faceflags & FACE_GROUND) continue; + //compare all the edges + for (l = 0; l < face2->numedges; l++) + { + edge2num = aasworld.edgeindex[face2->firstedge + l]; + if (abs(edge1num) == abs(edge2num)) + { + //get the area at the other side of the face + if (face2->frontarea == areanum) otherareanum = face2->backarea; + else otherareanum = face2->frontarea; + // + area2 = &aasworld.areas[otherareanum]; + //if the other area is grounded! + if (aasworld.areasettings[otherareanum].areaflags & AREA_GROUNDED) + { + //check for a possible gap + gap = qfalse; + for (n = 0; n < area2->numfaces; n++) + { + face3num = aasworld.faceindex[area2->firstface + n]; + //may not be the shared face of the two areas + if (abs(face3num) == abs(face2num)) continue; + // + face3 = &aasworld.faces[abs(face3num)]; + //find an edge shared by all three faces + for (m = 0; m < face3->numedges; m++) + { + edge3num = aasworld.edgeindex[face3->firstedge + m]; + //but the edge should be shared by all three faces + if (abs(edge3num) == abs(edge1num)) + { + if (!(face3->faceflags & FACE_SOLID)) + { + gap = qtrue; + break; + } //end if + // + if (face3->faceflags & FACE_GROUND) + { + gap = qfalse; + break; + } //end if + //FIXME: there are more situations to be handled + gap = qtrue; + break; + } //end if + } //end for + if (m < face3->numedges) break; + } //end for + if (!gap) break; + } //end if + //check for a walk off ledge reachability + edge = &aasworld.edges[abs(edge1num)]; + side = edge1num < 0; + // + v1 = aasworld.vertexes[edge->v[side]]; + v2 = aasworld.vertexes[edge->v[!side]]; + // + plane = &aasworld.planes[face1->planenum]; + //get the points really into the areas + VectorSubtract(v2, v1, sharededgevec); + CrossProduct(plane->normal, sharededgevec, dir); + VectorNormalize(dir); + // + VectorAdd(v1, v2, mid); + VectorScale(mid, 0.5, mid); + VectorMA(mid, 8, dir, mid); + // + VectorCopy(mid, testend); + testend[2] -= 1000; + trace = AAS_TraceClientBBox(mid, testend, PRESENCE_CROUCH, -1); + // + if (trace.startsolid) + { + //Log_Write("area %d: trace.startsolid\r\n", areanum); + break; + } //end if + reachareanum = AAS_PointAreaNum(trace.endpos); + if (reachareanum == areanum) + { + //Log_Write("area %d: same area\r\n", areanum); + break; + } //end if + if (AAS_ReachabilityExists(areanum, reachareanum)) + { + //Log_Write("area %d: reachability already exists\r\n", areanum); + break; + } //end if + if (!AAS_AreaGrounded(reachareanum) && !AAS_AreaSwim(reachareanum)) + { + //Log_Write("area %d, reach area %d: not grounded and not swim\r\n", areanum, reachareanum); + break; + } //end if + // + if (aasworld.areasettings[reachareanum].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) + { + //Log_Write("area %d, reach area %d: lava or slime\r\n", areanum, reachareanum); + break; + } //end if + //if not going through a cluster portal + numareas = AAS_TraceAreas(mid, testend, areas, NULL, sizeof(areas) / sizeof(int)); + for (p = 0; p < numareas; p++) + if (AAS_AreaClusterPortal(areas[p])) + break; + if (p < numareas) + break; + // if a maximum fall height is set and the bot would fall down further + if (aassettings.rs_maxfallheight && fabs(mid[2] - trace.endpos[2]) > aassettings.rs_maxfallheight) + break; + // + lreach = AAS_AllocReachability(); + if (!lreach) break; + lreach->areanum = reachareanum; + lreach->facenum = 0; + lreach->edgenum = edge1num; + VectorCopy(mid, lreach->start); + VectorCopy(trace.endpos, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = aassettings.rs_startwalkoffledge + fabs(mid[2] - trace.endpos[2]) * 50 / aassettings.phys_gravity; + if (!AAS_AreaSwim(reachareanum) && !AAS_AreaJumpPad(reachareanum)) + { + if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta5) + { + lreach->traveltime += aassettings.rs_falldamage5; + } //end if + else if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta10) + { + lreach->traveltime += aassettings.rs_falldamage10; + } //end if + } //end if + lreach->next = areareachability[areanum]; + areareachability[areanum] = lreach; + //we've got another walk off ledge reachability + reach_walkoffledge++; + } //end if + } //end for + } //end for + } //end for + } //end for +} //end of the function AAS_Reachability_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreReachability(void) +{ + int i; + aas_areasettings_t *areasettings; + aas_lreachability_t *lreach; + aas_reachability_t *reach; + + if (aasworld.reachability) FreeMemory(aasworld.reachability); + aasworld.reachability = (aas_reachability_t *) GetClearedMemory((numlreachabilities + 10) * sizeof(aas_reachability_t)); + aasworld.reachabilitysize = 1; + for (i = 0; i < aasworld.numareas; i++) + { + areasettings = &aasworld.areasettings[i]; + areasettings->firstreachablearea = aasworld.reachabilitysize; + areasettings->numreachableareas = 0; + for (lreach = areareachability[i]; lreach; lreach = lreach->next) + { + reach = &aasworld.reachability[areasettings->firstreachablearea + + areasettings->numreachableareas]; + reach->areanum = lreach->areanum; + reach->facenum = lreach->facenum; + reach->edgenum = lreach->edgenum; + VectorCopy(lreach->start, reach->start); + VectorCopy(lreach->end, reach->end); + reach->traveltype = lreach->traveltype; + reach->traveltime = lreach->traveltime; + // + areasettings->numreachableareas++; + } //end for + aasworld.reachabilitysize += areasettings->numreachableareas; + } //end for +} //end of the function AAS_StoreReachability +//=========================================================================== +// +// TRAVEL_WALK 100% equal floor height + steps +// TRAVEL_CROUCH 100% +// TRAVEL_BARRIERJUMP 100% +// TRAVEL_JUMP 80% +// TRAVEL_LADDER 100% + fall down from ladder + jump up to ladder +// TRAVEL_WALKOFFLEDGE 90% walk off very steep walls? +// TRAVEL_SWIM 100% +// TRAVEL_WATERJUMP 100% +// TRAVEL_TELEPORT 100% +// TRAVEL_ELEVATOR 100% +// TRAVEL_GRAPPLEHOOK 100% +// TRAVEL_DOUBLEJUMP 0% +// TRAVEL_RAMPJUMP 0% +// TRAVEL_STRAFEJUMP 0% +// TRAVEL_ROCKETJUMP 100% (currently limited towards areas with items) +// TRAVEL_BFGJUMP 0% (currently disabled) +// TRAVEL_JUMPPAD 100% +// TRAVEL_FUNCBOB 100% +// +// Parameter: - +// Returns: true if NOT finished +// Changes Globals: - +//=========================================================================== +int AAS_ContinueInitReachability(float time) +{ + int i, j, todo, start_time; + static float framereachability, reachability_delay; + static int lastpercentage; + + if (!aasworld.loaded) return qfalse; + //if reachability is calculated for all areas + if (aasworld.numreachabilityareas >= aasworld.numareas + 2) return qfalse; + //if starting with area 1 (area 0 is a dummy) + if (aasworld.numreachabilityareas == 1) + { + botimport.Print(PRT_MESSAGE, "calculating reachability...\n"); + lastpercentage = 0; + framereachability = 2000; + reachability_delay = 1000; + } //end if + //number of areas to calculate reachability for this cycle + todo = aasworld.numreachabilityareas + (int) framereachability; + start_time = Sys_MilliSeconds(); + //loop over the areas + for (i = aasworld.numreachabilityareas; i < aasworld.numareas && i < todo; i++) + { + aasworld.numreachabilityareas++; + //only create jumppad reachabilities from jumppad areas + if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + continue; + } //end if + //loop over the areas + for (j = 1; j < aasworld.numareas; j++) + { + if (i == j) continue; + //never create reachabilities from teleporter or jumppad areas to regular areas + if (aasworld.areasettings[i].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD)) + { + if (!(aasworld.areasettings[j].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD))) + { + continue; + } //end if + } //end if + //if there already is a reachability link from area i to j + if (AAS_ReachabilityExists(i, j)) continue; + //check for a swim reachability + if (AAS_Reachability_Swim(i, j)) continue; + //check for a simple walk on equal floor height reachability + if (AAS_Reachability_EqualFloorHeight(i, j)) continue; + //check for step, barrier, waterjump and walk off ledge reachabilities + if (AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(i, j)) continue; + //check for ladder reachabilities + if (AAS_Reachability_Ladder(i, j)) continue; + //check for a jump reachability + if (AAS_Reachability_Jump(i, j)) continue; + } //end for + //never create these reachabilities from teleporter or jumppad areas + if (aasworld.areasettings[i].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD)) + { + continue; + } //end if + //loop over the areas + for (j = 1; j < aasworld.numareas; j++) + { + if (i == j) continue; + // + if (AAS_ReachabilityExists(i, j)) continue; + //check for a grapple hook reachability + if (calcgrapplereach) AAS_Reachability_Grapple(i, j); + //check for a weapon jump reachability + AAS_Reachability_WeaponJump(i, j); + } //end for + //if the calculation took more time than the max reachability delay + if (Sys_MilliSeconds() - start_time > (int) reachability_delay) break; + // + if (aasworld.numreachabilityareas * 1000 / aasworld.numareas > lastpercentage) break; + } //end for + // + if (aasworld.numreachabilityareas == aasworld.numareas) + { + botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) 100.0); + botimport.Print(PRT_MESSAGE, "\nplease wait while storing reachability...\n"); + aasworld.numreachabilityareas++; + } //end if + //if this is the last step in the reachability calculations + else if (aasworld.numreachabilityareas == aasworld.numareas + 1) + { + //create additional walk off ledge reachabilities for every area + for (i = 1; i < aasworld.numareas; i++) + { + //only create jumppad reachabilities from jumppad areas + if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + continue; + } //end if + AAS_Reachability_WalkOffLedge(i); + } //end for + //create jump pad reachabilities + AAS_Reachability_JumpPad(); + //create teleporter reachabilities + AAS_Reachability_Teleport(); + //create elevator (func_plat) reachabilities + AAS_Reachability_Elevator(); + //create func_bobbing reachabilities + AAS_Reachability_FuncBobbing(); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "%6d reach swim\n", reach_swim); + botimport.Print(PRT_MESSAGE, "%6d reach equal floor\n", reach_equalfloor); + botimport.Print(PRT_MESSAGE, "%6d reach step\n", reach_step); + botimport.Print(PRT_MESSAGE, "%6d reach barrier\n", reach_barrier); + botimport.Print(PRT_MESSAGE, "%6d reach waterjump\n", reach_waterjump); + botimport.Print(PRT_MESSAGE, "%6d reach walkoffledge\n", reach_walkoffledge); + botimport.Print(PRT_MESSAGE, "%6d reach jump\n", reach_jump); + botimport.Print(PRT_MESSAGE, "%6d reach ladder\n", reach_ladder); + botimport.Print(PRT_MESSAGE, "%6d reach walk\n", reach_walk); + botimport.Print(PRT_MESSAGE, "%6d reach teleport\n", reach_teleport); + botimport.Print(PRT_MESSAGE, "%6d reach funcbob\n", reach_funcbob); + botimport.Print(PRT_MESSAGE, "%6d reach elevator\n", reach_elevator); + botimport.Print(PRT_MESSAGE, "%6d reach grapple\n", reach_grapple); + botimport.Print(PRT_MESSAGE, "%6d reach rocketjump\n", reach_rocketjump); + botimport.Print(PRT_MESSAGE, "%6d reach jumppad\n", reach_jumppad); +#endif + //*/ + //store all the reachabilities + AAS_StoreReachability(); + //free the reachability link heap + AAS_ShutDownReachabilityHeap(); + // + FreeMemory(areareachability); + // + aasworld.numreachabilityareas++; + // + botimport.Print(PRT_MESSAGE, "calculating clusters...\n"); + } //end if + else + { + lastpercentage = aasworld.numreachabilityareas * 1000 / aasworld.numareas; + botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) lastpercentage / 10); + } //end else + //not yet finished + return qtrue; +} //end of the function AAS_ContinueInitReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitReachability(void) +{ + if (!aasworld.loaded) return; + + if (aasworld.reachabilitysize) + { +#ifndef BSPC + if (!((int)LibVarGetValue("forcereachability"))) + { + aasworld.numreachabilityareas = aasworld.numareas + 2; + return; + } //end if +#else + aasworld.numreachabilityareas = aasworld.numareas + 2; + return; +#endif //BSPC + } //end if +#ifndef BSPC + calcgrapplereach = LibVarGetValue("grapplereach"); +#endif + aasworld.savefile = qtrue; + //start with area 1 because area zero is a dummy + aasworld.numreachabilityareas = 1; + ////aasworld.numreachabilityareas = aasworld.numareas + 1; //only calculate entity reachabilities + //setup the heap with reachability links + AAS_SetupReachabilityHeap(); + //allocate area reachability link array + areareachability = (aas_lreachability_t **) GetClearedMemory( + aasworld.numareas * sizeof(aas_lreachability_t *)); + // + AAS_SetWeaponJumpAreaFlags(); +} //end of the function AAS_InitReachable diff --git a/codemp/botlib/be_aas_reach.h b/codemp/botlib/be_aas_reach.h new file mode 100644 index 0000000..e233589 --- /dev/null +++ b/codemp/botlib/be_aas_reach.h @@ -0,0 +1,51 @@ + +/***************************************************************************** + * name: be_aas_reach.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_reach.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize calculating the reachabilities +void AAS_InitReachability(void); +//continue calculating the reachabilities +int AAS_ContinueInitReachability(float time); +// +int AAS_BestReachableLinkArea(aas_link_t *areas); +#endif //AASINTERN + +//returns true if the are has reachabilities to other areas +int AAS_AreaReachability(int areanum); +//returns the best reachable area and goal origin for a bounding box at the given origin +int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin); +//returns the best jumppad area from which the bbox at origin is reachable +int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs); +//returns the next reachability using the given model +int AAS_NextModelReachability(int num, int modelnum); +//returns the total area of the ground faces of the given area +float AAS_AreaGroundFaceArea(int areanum); +//returns true if the area is crouch only +int AAS_AreaCrouch(int areanum); +//returns true if a player can swim in this area +int AAS_AreaSwim(int areanum); +//returns true if the area is filled with a liquid +int AAS_AreaLiquid(int areanum); +//returns true if the area contains lava +int AAS_AreaLava(int areanum); +//returns true if the area contains slime +int AAS_AreaSlime(int areanum); +//returns true if the area has one or more ground faces +int AAS_AreaGrounded(int areanum); +//returns true if the area has one or more ladder faces +int AAS_AreaLadder(int areanum); +//returns true if the area is a jump pad +int AAS_AreaJumpPad(int areanum); +//returns true if the area is donotenter +int AAS_AreaDoNotEnter(int areanum); diff --git a/codemp/botlib/be_aas_route.cpp b/codemp/botlib/be_aas_route.cpp new file mode 100644 index 0000000..3f3dfea --- /dev/null +++ b/codemp/botlib/be_aas_route.cpp @@ -0,0 +1,2192 @@ + +/***************************************************************************** + * name: be_aas_route.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_route.c $ + * $Author: Mrelusive $ + * $Revision: 18 $ + * $Modtime: 12/01/00 1:11p $ + * $Date: 12/01/00 1:11p $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_utils.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_crc.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define ROUTING_DEBUG + +//travel time in hundreths of a second = distance * 100 / speed +#define DISTANCEFACTOR_CROUCH 1.3f //crouch speed = 100 +#define DISTANCEFACTOR_SWIM 1 //should be 0.66, swim speed = 150 +#define DISTANCEFACTOR_WALK 0.33f //walk speed = 300 + +//cache refresh time +#define CACHE_REFRESHTIME 15.0f //15 seconds refresh time + +//maximum number of routing updates each frame +#define MAX_FRAMEROUTINGUPDATES 10 + + +/* + + area routing cache: + stores the distances within one cluster to a specific goal area + this goal area is in this same cluster and could be a cluster portal + for every cluster there's a list with routing cache for every area + in that cluster (including the portals of that cluster) + area cache stores aasworld.clusters[?].numreachabilityareas travel times + + portal routing cache: + stores the distances of all portals to a specific goal area + this goal area could be in any cluster and could also be a cluster portal + for every area (aasworld.numareas) the portal cache stores + aasworld.numportals travel times + +*/ + +#ifdef ROUTING_DEBUG +int numareacacheupdates; +int numportalcacheupdates; +#endif //ROUTING_DEBUG + +int routingcachesize; +int max_routingcachesize; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef ROUTING_DEBUG +void AAS_RoutingInfo(void) +{ + botimport.Print(PRT_MESSAGE, "%d area cache updates\n", numareacacheupdates); + botimport.Print(PRT_MESSAGE, "%d portal cache updates\n", numportalcacheupdates); + botimport.Print(PRT_MESSAGE, "%d bytes routing cache\n", routingcachesize); +} //end of the function AAS_RoutingInfo +#endif //ROUTING_DEBUG +//=========================================================================== +// returns the number of the area in the cluster +// assumes the given area is in the given cluster or a portal of the cluster +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline int AAS_ClusterAreaNum(int cluster, int areanum) +{ + int side, areacluster; + + areacluster = aasworld.areasettings[areanum].cluster; + if (areacluster > 0) return aasworld.areasettings[areanum].clusterareanum; + else + { +/*#ifdef ROUTING_DEBUG + if (aasworld.portals[-areacluster].frontcluster != cluster && + aasworld.portals[-areacluster].backcluster != cluster) + { + botimport.Print(PRT_ERROR, "portal %d: does not belong to cluster %d\n" + , -areacluster, cluster); + } //end if +#endif //ROUTING_DEBUG*/ + side = aasworld.portals[-areacluster].frontcluster != cluster; + return aasworld.portals[-areacluster].clusterareanum[side]; + } //end else +} //end of the function AAS_ClusterAreaNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitTravelFlagFromType(void) +{ + int i; + + for (i = 0; i < MAX_TRAVELTYPES; i++) + { + aasworld.travelflagfortype[i] = TFL_INVALID; + } //end for + aasworld.travelflagfortype[TRAVEL_INVALID] = TFL_INVALID; + aasworld.travelflagfortype[TRAVEL_WALK] = TFL_WALK; + aasworld.travelflagfortype[TRAVEL_CROUCH] = TFL_CROUCH; + aasworld.travelflagfortype[TRAVEL_BARRIERJUMP] = TFL_BARRIERJUMP; + aasworld.travelflagfortype[TRAVEL_JUMP] = TFL_JUMP; + aasworld.travelflagfortype[TRAVEL_LADDER] = TFL_LADDER; + aasworld.travelflagfortype[TRAVEL_WALKOFFLEDGE] = TFL_WALKOFFLEDGE; + aasworld.travelflagfortype[TRAVEL_SWIM] = TFL_SWIM; + aasworld.travelflagfortype[TRAVEL_WATERJUMP] = TFL_WATERJUMP; + aasworld.travelflagfortype[TRAVEL_TELEPORT] = TFL_TELEPORT; + aasworld.travelflagfortype[TRAVEL_ELEVATOR] = TFL_ELEVATOR; + aasworld.travelflagfortype[TRAVEL_ROCKETJUMP] = TFL_ROCKETJUMP; + aasworld.travelflagfortype[TRAVEL_BFGJUMP] = TFL_BFGJUMP; + aasworld.travelflagfortype[TRAVEL_GRAPPLEHOOK] = TFL_GRAPPLEHOOK; + aasworld.travelflagfortype[TRAVEL_DOUBLEJUMP] = TFL_DOUBLEJUMP; + aasworld.travelflagfortype[TRAVEL_RAMPJUMP] = TFL_RAMPJUMP; + aasworld.travelflagfortype[TRAVEL_STRAFEJUMP] = TFL_STRAFEJUMP; + aasworld.travelflagfortype[TRAVEL_JUMPPAD] = TFL_JUMPPAD; + aasworld.travelflagfortype[TRAVEL_FUNCBOB] = TFL_FUNCBOB; +} //end of the function AAS_InitTravelFlagFromType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline int AAS_TravelFlagForType_inline(int traveltype) +{ + int tfl; + + tfl = 0; + if (tfl & TRAVELFLAG_NOTTEAM1) + tfl |= TFL_NOTTEAM1; + if (tfl & TRAVELFLAG_NOTTEAM2) + tfl |= TFL_NOTTEAM2; + traveltype &= TRAVELTYPE_MASK; + if (traveltype < 0 || traveltype >= MAX_TRAVELTYPES) + return TFL_INVALID; + tfl |= aasworld.travelflagfortype[traveltype]; + return tfl; +} //end of the function AAS_TravelFlagForType_inline +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TravelFlagForType(int traveltype) +{ + return AAS_TravelFlagForType_inline(traveltype); +} //end of the function AAS_TravelFlagForType_inline +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkCache(aas_routingcache_t *cache) +{ + if (cache->time_next) cache->time_next->time_prev = cache->time_prev; + else aasworld.newestcache = cache->time_prev; + if (cache->time_prev) cache->time_prev->time_next = cache->time_next; + else aasworld.oldestcache = cache->time_next; + cache->time_next = NULL; + cache->time_prev = NULL; +} //end of the function AAS_UnlinkCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_LinkCache(aas_routingcache_t *cache) +{ + if (aasworld.newestcache) + { + aasworld.newestcache->time_next = cache; + cache->time_prev = aasworld.newestcache; + } //end if + else + { + aasworld.oldestcache = cache; + cache->time_prev = NULL; + } //end else + cache->time_next = NULL; + aasworld.newestcache = cache; +} //end of the function AAS_LinkCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeRoutingCache(aas_routingcache_t *cache) +{ + AAS_UnlinkCache(cache); + routingcachesize -= cache->size; + FreeMemory(cache); +} //end of the function AAS_FreeRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveRoutingCacheInCluster( int clusternum ) +{ + int i; + aas_routingcache_t *cache, *nextcache; + aas_cluster_t *cluster; + + if (!aasworld.clusterareacache) + return; + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numareas; i++) + { + for (cache = aasworld.clusterareacache[clusternum][i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.clusterareacache[clusternum][i] = NULL; + } //end for +} //end of the function AAS_RemoveRoutingCacheInCluster +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveRoutingCacheUsingArea( int areanum ) +{ + int i, clusternum; + aas_routingcache_t *cache, *nextcache; + + clusternum = aasworld.areasettings[areanum].cluster; + if (clusternum > 0) + { + //remove all the cache in the cluster the area is in + AAS_RemoveRoutingCacheInCluster( clusternum ); + } //end if + else + { + // if this is a portal remove all cache in both the front and back cluster + AAS_RemoveRoutingCacheInCluster( aasworld.portals[-clusternum].frontcluster ); + AAS_RemoveRoutingCacheInCluster( aasworld.portals[-clusternum].backcluster ); + } //end else + // remove all portal cache + for (i = 0; i < aasworld.numareas; i++) + { + //refresh portal cache + for (cache = aasworld.portalcache[i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.portalcache[i] = NULL; + } //end for +} //end of the function AAS_RemoveRoutingCacheUsingArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EnableRoutingArea(int areanum, int enable) +{ + int flags; + + if (areanum <= 0 || areanum >= aasworld.numareas) + { + if (bot_developer) + { + botimport.Print(PRT_ERROR, "AAS_EnableRoutingArea: areanum %d out of range\n", areanum); + } //end if + return 0; + } //end if + flags = aasworld.areasettings[areanum].areaflags & AREA_DISABLED; + if (enable < 0) + return !flags; + + if (enable) + aasworld.areasettings[areanum].areaflags &= ~AREA_DISABLED; + else + aasworld.areasettings[areanum].areaflags |= AREA_DISABLED; + // if the status of the area changed + if ( (flags & AREA_DISABLED) != (aasworld.areasettings[areanum].areaflags & AREA_DISABLED) ) + { + //remove all routing cache involving this area + AAS_RemoveRoutingCacheUsingArea( areanum ); + } //end if + return !flags; +} //end of the function AAS_EnableRoutingArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline float AAS_RoutingTime(void) +{ + return AAS_Time(); +} //end of the function AAS_RoutingTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetAreaContentsTravelFlags(int areanum) +{ + int contents, tfl; + + contents = aasworld.areasettings[areanum].contents; + tfl = 0; + if (contents & AREACONTENTS_WATER) + tfl |= TFL_WATER; + else if (contents & AREACONTENTS_SLIME) + tfl |= TFL_SLIME; + else if (contents & AREACONTENTS_LAVA) + tfl |= TFL_LAVA; + else + tfl |= TFL_AIR; + if (contents & AREACONTENTS_DONOTENTER) + tfl |= TFL_DONOTENTER; + if (contents & AREACONTENTS_NOTTEAM1) + tfl |= TFL_NOTTEAM1; + if (contents & AREACONTENTS_NOTTEAM2) + tfl |= TFL_NOTTEAM2; + if (aasworld.areasettings[areanum].areaflags & AREA_BRIDGE) + tfl |= TFL_BRIDGE; + return tfl; +} //end of the function AAS_GetAreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +__inline int AAS_AreaContentsTravelFlags_inline(int areanum) +{ + return aasworld.areacontentstravelflags[areanum]; +} //end of the function AAS_AreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaContentsTravelFlags(int areanum) +{ + return aasworld.areacontentstravelflags[areanum]; +} //end of the function AAS_AreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAreaContentsTravelFlags(void) +{ + int i; + + if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags); + aasworld.areacontentstravelflags = (int *) GetClearedMemory(aasworld.numareas * sizeof(int)); + // + for (i = 0; i < aasworld.numareas; i++) { + aasworld.areacontentstravelflags[i] = AAS_GetAreaContentsTravelFlags(i); + } +} //end of the function AAS_InitAreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateReversedReachability(void) +{ + int i, n; + aas_reversedlink_t *revlink; + aas_reachability_t *reach; + aas_areasettings_t *settings; + char *ptr; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif + //free reversed links that have already been created + if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability); + //allocate memory for the reversed reachability links + ptr = (char *) GetClearedMemory(aasworld.numareas * sizeof(aas_reversedreachability_t) + + aasworld.reachabilitysize * sizeof(aas_reversedlink_t)); + // + aasworld.reversedreachability = (aas_reversedreachability_t *) ptr; + //pointer to the memory for the reversed links + ptr += aasworld.numareas * sizeof(aas_reversedreachability_t); + //check all reachabilities of all areas + for (i = 1; i < aasworld.numareas; i++) + { + //settings of the area + settings = &aasworld.areasettings[i]; + // + if (settings->numreachableareas >= 128) + botimport.Print(PRT_WARNING, "area %d has more than 128 reachabilities\n", i); + //create reversed links for the reachabilities + for (n = 0; n < settings->numreachableareas && n < 128; n++) + { + //reachability link + reach = &aasworld.reachability[settings->firstreachablearea + n]; + // + revlink = (aas_reversedlink_t *) ptr; + ptr += sizeof(aas_reversedlink_t); + // + revlink->areanum = i; + revlink->linknum = settings->firstreachablearea + n; + revlink->next = aasworld.reversedreachability[reach->areanum].first; + aasworld.reversedreachability[reach->areanum].first = revlink; + aasworld.reversedreachability[reach->areanum].numlinks++; + } //end for + } //end for +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "reversed reachability %d msec\n", Sys_MilliSeconds() - starttime); +#endif +} //end of the function AAS_CreateReversedReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end) +{ + int intdist; + float dist; + vec3_t dir; + + VectorSubtract(start, end, dir); + dist = VectorLength(dir); + //if crouch only area + if (AAS_AreaCrouch(areanum)) dist *= DISTANCEFACTOR_CROUCH; + //if swim area + else if (AAS_AreaSwim(areanum)) dist *= DISTANCEFACTOR_SWIM; + //normal walk area + else dist *= DISTANCEFACTOR_WALK; + // + intdist = (int) dist; + //make sure the distance isn't zero + if (intdist <= 0) intdist = 1; + return intdist; +} //end of the function AAS_AreaTravelTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CalculateAreaTravelTimes(void) +{ + int i, l, n, size; + char *ptr; + vec3_t end; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + aas_reachability_t *reach; + aas_areasettings_t *settings; + int starttime; + + starttime = Sys_MilliSeconds(); + //if there are still area travel times, free the memory + if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes); + //get the total size of all the area travel times + size = aasworld.numareas * sizeof(unsigned short **); + for (i = 0; i < aasworld.numareas; i++) + { + revreach = &aasworld.reversedreachability[i]; + //settings of the area + settings = &aasworld.areasettings[i]; + // + size += settings->numreachableareas * sizeof(unsigned short *); + // + size += settings->numreachableareas * revreach->numlinks * sizeof(unsigned short); + } //end for + //allocate memory for the area travel times + ptr = (char *) GetClearedMemory(size); + aasworld.areatraveltimes = (unsigned short ***) ptr; + ptr += aasworld.numareas * sizeof(unsigned short **); + //calcluate the travel times for all the areas + for (i = 0; i < aasworld.numareas; i++) + { + //reversed reachabilities of this area + revreach = &aasworld.reversedreachability[i]; + //settings of the area + settings = &aasworld.areasettings[i]; + // + aasworld.areatraveltimes[i] = (unsigned short **) ptr; + ptr += settings->numreachableareas * sizeof(unsigned short *); + // + for (l = 0; l < settings->numreachableareas; l++) + { + aasworld.areatraveltimes[i][l] = (unsigned short *) ptr; + ptr += revreach->numlinks * sizeof(unsigned short); + //reachability link + reach = &aasworld.reachability[settings->firstreachablearea + l]; + // + for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) + { + VectorCopy(aasworld.reachability[revlink->linknum].end, end); + // + aasworld.areatraveltimes[i][l][n] = AAS_AreaTravelTime(i, end, reach->start); + } //end for + } //end for + } //end for +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "area travel times %d msec\n", Sys_MilliSeconds() - starttime); +#endif +} //end of the function AAS_CalculateAreaTravelTimes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PortalMaxTravelTime(int portalnum) +{ + int l, n, t, maxt; + aas_portal_t *portal; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + aas_areasettings_t *settings; + + portal = &aasworld.portals[portalnum]; + //reversed reachabilities of this portal area + revreach = &aasworld.reversedreachability[portal->areanum]; + //settings of the portal area + settings = &aasworld.areasettings[portal->areanum]; + // + maxt = 0; + for (l = 0; l < settings->numreachableareas; l++) + { + for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) + { + t = aasworld.areatraveltimes[portal->areanum][l][n]; + if (t > maxt) + { + maxt = t; + } //end if + } //end for + } //end for + return maxt; +} //end of the function AAS_PortalMaxTravelTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitPortalMaxTravelTimes(void) +{ + int i; + + if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes); + + aasworld.portalmaxtraveltimes = (int *) GetClearedMemory(aasworld.numportals * sizeof(int)); + + for (i = 0; i < aasworld.numportals; i++) + { + aasworld.portalmaxtraveltimes[i] = AAS_PortalMaxTravelTime(i); + //botimport.Print(PRT_MESSAGE, "portal %d max tt = %d\n", i, aasworld.portalmaxtraveltimes[i]); + } //end for +} //end of the function AAS_InitPortalMaxTravelTimes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +int AAS_FreeOldestCache(void) +{ + int i, j, bestcluster, bestarea, freed; + float besttime; + aas_routingcache_t *cache, *bestcache; + + freed = qfalse; + besttime = 999999999; + bestcache = NULL; + bestcluster = 0; + bestarea = 0; + //refresh cluster cache + for (i = 0; i < aasworld.numclusters; i++) + { + for (j = 0; j < aasworld.clusters[i].numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) + { + //never remove cache leading towards a portal + if (aasworld.areasettings[cache->areanum].cluster < 0) continue; + //if this cache is older than the cache we found so far + if (cache->time < besttime) + { + bestcache = cache; + bestcluster = i; + bestarea = j; + besttime = cache->time; + } //end if + } //end for + } //end for + } //end for + if (bestcache) + { + cache = bestcache; + if (cache->prev) cache->prev->next = cache->next; + else aasworld.clusterareacache[bestcluster][bestarea] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + AAS_FreeRoutingCache(cache); + freed = qtrue; + } //end if + besttime = 999999999; + bestcache = NULL; + bestarea = 0; + for (i = 0; i < aasworld.numareas; i++) + { + //refresh portal cache + for (cache = aasworld.portalcache[i]; cache; cache = cache->next) + { + if (cache->time < besttime) + { + bestcache = cache; + bestarea = i; + besttime = cache->time; + } //end if + } //end for + } //end for + if (bestcache) + { + cache = bestcache; + if (cache->prev) cache->prev->next = cache->next; + else aasworld.portalcache[bestarea] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + AAS_FreeRoutingCache(cache); + freed = qtrue; + } //end if + return freed; +} //end of the function AAS_FreeOldestCache +*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FreeOldestCache(void) +{ + int clusterareanum; + aas_routingcache_t *cache; + + for (cache = aasworld.oldestcache; cache; cache = cache->time_next) { + // never free area cache leading towards a portal + if (cache->type == CACHETYPE_AREA && aasworld.areasettings[cache->areanum].cluster < 0) { + continue; + } + break; + } + if (cache) { + // unlink the cache + if (cache->type == CACHETYPE_AREA) { + //number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); + // unlink from cluster area cache + if (cache->prev) cache->prev->next = cache->next; + else aasworld.clusterareacache[cache->cluster][clusterareanum] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + } + else { + // unlink from portal cache + if (cache->prev) cache->prev->next = cache->next; + else aasworld.portalcache[cache->areanum] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + } + AAS_FreeRoutingCache(cache); + return qtrue; + } + return qfalse; +} //end of the function AAS_FreeOldestCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_AllocRoutingCache(int numtraveltimes) +{ + aas_routingcache_t *cache; + int size; + + // + size = sizeof(aas_routingcache_t) + + numtraveltimes * sizeof(unsigned short int) + + numtraveltimes * sizeof(unsigned char); + // + routingcachesize += size; + // + cache = (aas_routingcache_t *) GetClearedMemory(size); + cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) + + numtraveltimes * sizeof(unsigned short int); + cache->size = size; + return cache; +} //end of the function AAS_AllocRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAllClusterAreaCache(void) +{ + int i, j; + aas_routingcache_t *cache, *nextcache; + aas_cluster_t *cluster; + + //free all cluster cache if existing + if (!aasworld.clusterareacache) return; + //free caches + for (i = 0; i < aasworld.numclusters; i++) + { + cluster = &aasworld.clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.clusterareacache[i][j] = NULL; + } //end for + } //end for + //free the cluster cache array + FreeMemory(aasworld.clusterareacache); + aasworld.clusterareacache = NULL; +} //end of the function AAS_FreeAllClusterAreaCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitClusterAreaCache(void) +{ + int i, size; + char *ptr; + + // + for (size = 0, i = 0; i < aasworld.numclusters; i++) + { + size += aasworld.clusters[i].numareas; + } //end for + //two dimensional array with pointers for every cluster to routing cache + //for every area in that cluster + ptr = (char *) GetClearedMemory( + aasworld.numclusters * sizeof(aas_routingcache_t **) + + size * sizeof(aas_routingcache_t *)); + aasworld.clusterareacache = (aas_routingcache_t ***) ptr; + ptr += aasworld.numclusters * sizeof(aas_routingcache_t **); + for (i = 0; i < aasworld.numclusters; i++) + { + aasworld.clusterareacache[i] = (aas_routingcache_t **) ptr; + ptr += aasworld.clusters[i].numareas * sizeof(aas_routingcache_t *); + } //end for +} //end of the function AAS_InitClusterAreaCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAllPortalCache(void) +{ + int i; + aas_routingcache_t *cache, *nextcache; + + //free all portal cache if existing + if (!aasworld.portalcache) return; + //free portal caches + for (i = 0; i < aasworld.numareas; i++) + { + for (cache = aasworld.portalcache[i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.portalcache[i] = NULL; + } //end for + FreeMemory(aasworld.portalcache); + aasworld.portalcache = NULL; +} //end of the function AAS_FreeAllPortalCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitPortalCache(void) +{ + // + aasworld.portalcache = (aas_routingcache_t **) GetClearedMemory( + aasworld.numareas * sizeof(aas_routingcache_t *)); +} //end of the function AAS_InitPortalCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitRoutingUpdate(void) +{ + int i, maxreachabilityareas; + + //free routing update fields if already existing + if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate); + // + maxreachabilityareas = 0; + for (i = 0; i < aasworld.numclusters; i++) + { + if (aasworld.clusters[i].numreachabilityareas > maxreachabilityareas) + { + maxreachabilityareas = aasworld.clusters[i].numreachabilityareas; + } //end if + } //end for + //allocate memory for the routing update fields + aasworld.areaupdate = (aas_routingupdate_t *) GetClearedMemory( + maxreachabilityareas * sizeof(aas_routingupdate_t)); + // + if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate); + //allocate memory for the portal update fields + aasworld.portalupdate = (aas_routingupdate_t *) GetClearedMemory( + (aasworld.numportals+1) * sizeof(aas_routingupdate_t)); +} //end of the function AAS_InitRoutingUpdate +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateAllRoutingCache(void) +{ + int i, j, t; + + aasworld.initialized = qtrue; + botimport.Print(PRT_MESSAGE, "AAS_CreateAllRoutingCache\n"); + for (i = 1; i < aasworld.numareas; i++) + { + if (!AAS_AreaReachability(i)) continue; + for (j = 1; j < aasworld.numareas; j++) + { + if (i == j) continue; + if (!AAS_AreaReachability(j)) continue; + t = AAS_AreaTravelTimeToGoalArea(i, aasworld.areas[i].center, j, TFL_DEFAULT); + //Log_Write("traveltime from %d to %d is %d", i, j, t); + } //end for + } //end for + aasworld.initialized = qfalse; +} //end of the function AAS_CreateAllRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +//the route cache header +//this header is followed by numportalcache + numareacache aas_routingcache_t +//structures that store routing cache +typedef struct routecacheheader_s +{ + int ident; + int version; + int numareas; + int numclusters; + int areacrc; + int clustercrc; + int numportalcache; + int numareacache; +} routecacheheader_t; + +#define RCID (('C'<<24)+('R'<<16)+('E'<<8)+'M') +#define RCVERSION 2 + +//void AAS_DecompressVis(byte *in, int numareas, byte *decompressed); +//int AAS_CompressVis(byte *vis, int numareas, byte *dest); + +void AAS_WriteRouteCache(void) +{ + int i, j, numportalcache, numareacache, totalsize; + aas_routingcache_t *cache; + aas_cluster_t *cluster; + fileHandle_t fp; + char filename[MAX_QPATH]; + routecacheheader_t routecacheheader; + + numportalcache = 0; + for (i = 0; i < aasworld.numareas; i++) + { + for (cache = aasworld.portalcache[i]; cache; cache = cache->next) + { + numportalcache++; + } //end for + } //end for + numareacache = 0; + for (i = 0; i < aasworld.numclusters; i++) + { + cluster = &aasworld.clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) + { + numareacache++; + } //end for + } //end for + } //end for + // open the file for writing + Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld.mapname); + botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); + if (!fp) + { + AAS_Error("Unable to open file: %s\n", filename); + return; + } //end if + //create the header + routecacheheader.ident = RCID; + routecacheheader.version = RCVERSION; + routecacheheader.numareas = aasworld.numareas; + routecacheheader.numclusters = aasworld.numclusters; + routecacheheader.areacrc = CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas ); + routecacheheader.clustercrc = CRC_ProcessString( (unsigned char *)aasworld.clusters, sizeof(aas_cluster_t) * aasworld.numclusters ); + routecacheheader.numportalcache = numportalcache; + routecacheheader.numareacache = numareacache; + //write the header + botimport.FS_Write(&routecacheheader, sizeof(routecacheheader_t), fp); + // + totalsize = 0; + //write all the cache + for (i = 0; i < aasworld.numareas; i++) + { + for (cache = aasworld.portalcache[i]; cache; cache = cache->next) + { + botimport.FS_Write(cache, cache->size, fp); + totalsize += cache->size; + } //end for + } //end for + for (i = 0; i < aasworld.numclusters; i++) + { + cluster = &aasworld.clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) + { + botimport.FS_Write(cache, cache->size, fp); + totalsize += cache->size; + } //end for + } //end for + } //end for + // write the visareas + /* + for (i = 0; i < aasworld.numareas; i++) + { + if (!aasworld.areavisibility[i]) { + size = 0; + botimport.FS_Write(&size, sizeof(int), fp); + continue; + } + AAS_DecompressVis( aasworld.areavisibility[i], aasworld.numareas, aasworld.decompressedvis ); + size = AAS_CompressVis( aasworld.decompressedvis, aasworld.numareas, aasworld.decompressedvis ); + botimport.FS_Write(&size, sizeof(int), fp); + botimport.FS_Write(aasworld.decompressedvis, size, fp); + } + */ + // + botimport.FS_FCloseFile(fp); + botimport.Print(PRT_MESSAGE, "\nroute cache written to %s\n", filename); + botimport.Print(PRT_MESSAGE, "written %d bytes of routing cache\n", totalsize); +} //end of the function AAS_WriteRouteCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_ReadCache(fileHandle_t fp) +{ + int size; + aas_routingcache_t *cache; + + botimport.FS_Read(&size, sizeof(size), fp); + cache = (aas_routingcache_t *) GetMemory(size); + cache->size = size; + botimport.FS_Read((unsigned char *)cache + sizeof(size), size - sizeof(size), fp); + cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) - sizeof(unsigned short) + + (size - sizeof(aas_routingcache_t) + sizeof(unsigned short)) / 3 * 2; + return cache; +} //end of the function AAS_ReadCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ReadRouteCache(void) +{ + int i, clusterareanum;//, size; + fileHandle_t fp; + char filename[MAX_QPATH]; + routecacheheader_t routecacheheader; + aas_routingcache_t *cache; + + Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld.mapname); + botimport.FS_FOpenFile( filename, &fp, FS_READ ); + if (!fp) + { + return qfalse; + } //end if + botimport.FS_Read(&routecacheheader, sizeof(routecacheheader_t), fp ); + if (routecacheheader.ident != RCID) + { + AAS_Error("%s is not a route cache dump\n"); + return qfalse; + } //end if + if (routecacheheader.version != RCVERSION) + { + AAS_Error("route cache dump has wrong version %d, should be %d", routecacheheader.version, RCVERSION); + return qfalse; + } //end if + if (routecacheheader.numareas != aasworld.numareas) + { + //AAS_Error("route cache dump has wrong number of areas\n"); + return qfalse; + } //end if + if (routecacheheader.numclusters != aasworld.numclusters) + { + //AAS_Error("route cache dump has wrong number of clusters\n"); + return qfalse; + } //end if + if (routecacheheader.areacrc != + CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas )) + { + //AAS_Error("route cache dump area CRC incorrect\n"); + return qfalse; + } //end if + if (routecacheheader.clustercrc != + CRC_ProcessString( (unsigned char *)aasworld.clusters, sizeof(aas_cluster_t) * aasworld.numclusters )) + { + //AAS_Error("route cache dump cluster CRC incorrect\n"); + return qfalse; + } //end if + //read all the portal cache + for (i = 0; i < routecacheheader.numportalcache; i++) + { + cache = AAS_ReadCache(fp); + cache->next = aasworld.portalcache[cache->areanum]; + cache->prev = NULL; + if (aasworld.portalcache[cache->areanum]) + aasworld.portalcache[cache->areanum]->prev = cache; + aasworld.portalcache[cache->areanum] = cache; + } //end for + //read all the cluster area cache + for (i = 0; i < routecacheheader.numareacache; i++) + { + cache = AAS_ReadCache(fp); + clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); + cache->next = aasworld.clusterareacache[cache->cluster][clusterareanum]; + cache->prev = NULL; + if (aasworld.clusterareacache[cache->cluster][clusterareanum]) + aasworld.clusterareacache[cache->cluster][clusterareanum]->prev = cache; + aasworld.clusterareacache[cache->cluster][clusterareanum] = cache; + } //end for + // read the visareas + /* + aasworld.areavisibility = (byte **) GetClearedMemory(aasworld.numareas * sizeof(byte *)); + aasworld.decompressedvis = (byte *) GetClearedMemory(aasworld.numareas * sizeof(byte)); + for (i = 0; i < aasworld.numareas; i++) + { + botimport.FS_Read(&size, sizeof(size), fp ); + if (size) { + aasworld.areavisibility[i] = (byte *) GetMemory(size); + botimport.FS_Read(aasworld.areavisibility[i], size, fp ); + } + } + */ + // + botimport.FS_FCloseFile(fp); + return qtrue; +} //end of the function AAS_ReadRouteCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define MAX_REACHABILITYPASSAREAS 32 + +void AAS_InitReachabilityAreas(void) +{ + int i, j, numareas, areas[MAX_REACHABILITYPASSAREAS]; + int numreachareas; + aas_reachability_t *reach; + vec3_t start, end; + + if (aasworld.reachabilityareas) + FreeMemory(aasworld.reachabilityareas); + if (aasworld.reachabilityareaindex) + FreeMemory(aasworld.reachabilityareaindex); + + aasworld.reachabilityareas = (aas_reachabilityareas_t *) + GetClearedMemory(aasworld.reachabilitysize * sizeof(aas_reachabilityareas_t)); + aasworld.reachabilityareaindex = (int *) + GetClearedMemory(aasworld.reachabilitysize * MAX_REACHABILITYPASSAREAS * sizeof(int)); + numreachareas = 0; + for (i = 0; i < aasworld.reachabilitysize; i++) + { + reach = &aasworld.reachability[i]; + numareas = 0; + switch(reach->traveltype & TRAVELTYPE_MASK) + { + //trace areas from start to end + case TRAVEL_BARRIERJUMP: + case TRAVEL_WATERJUMP: + VectorCopy(reach->start, end); + end[2] = reach->end[2]; + numareas = AAS_TraceAreas(reach->start, end, areas, NULL, MAX_REACHABILITYPASSAREAS); + break; + case TRAVEL_WALKOFFLEDGE: + VectorCopy(reach->end, start); + start[2] = reach->start[2]; + numareas = AAS_TraceAreas(start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS); + break; + case TRAVEL_GRAPPLEHOOK: + numareas = AAS_TraceAreas(reach->start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS); + break; + + //trace arch + case TRAVEL_JUMP: break; + case TRAVEL_ROCKETJUMP: break; + case TRAVEL_BFGJUMP: break; + case TRAVEL_JUMPPAD: break; + + //trace from reach->start to entity center, along entity movement + //and from entity center to reach->end + case TRAVEL_ELEVATOR: break; + case TRAVEL_FUNCBOB: break; + + //no areas in between + case TRAVEL_WALK: break; + case TRAVEL_CROUCH: break; + case TRAVEL_LADDER: break; + case TRAVEL_SWIM: break; + case TRAVEL_TELEPORT: break; + default: break; + } //end switch + aasworld.reachabilityareas[i].firstarea = numreachareas; + aasworld.reachabilityareas[i].numareas = numareas; + for (j = 0; j < numareas; j++) + { + aasworld.reachabilityareaindex[numreachareas++] = areas[j]; + } //end for + } //end for +} //end of the function AAS_InitReachabilityAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitRouting(void) +{ + AAS_InitTravelFlagFromType(); + // + AAS_InitAreaContentsTravelFlags(); + //initialize the routing update fields + AAS_InitRoutingUpdate(); + //create reversed reachability links used by the routing update algorithm + AAS_CreateReversedReachability(); + //initialize the cluster cache + AAS_InitClusterAreaCache(); + //initialize portal cache + AAS_InitPortalCache(); + //initialize the area travel times + AAS_CalculateAreaTravelTimes(); + //calculate the maximum travel times through portals + AAS_InitPortalMaxTravelTimes(); + //get the areas reachabilities go through + AAS_InitReachabilityAreas(); + // +#ifdef ROUTING_DEBUG + numareacacheupdates = 0; + numportalcacheupdates = 0; +#endif //ROUTING_DEBUG + // + routingcachesize = 0; + max_routingcachesize = 1024 * (int) LibVarValue("max_routingcache", "4096"); + // read any routing cache if available + AAS_ReadRouteCache(); +} //end of the function AAS_InitRouting +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeRoutingCaches(void) +{ + // free all the existing cluster area cache + AAS_FreeAllClusterAreaCache(); + // free all the existing portal cache + AAS_FreeAllPortalCache(); + // free cached travel times within areas + if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes); + aasworld.areatraveltimes = NULL; + // free cached maximum travel time through cluster portals + if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes); + aasworld.portalmaxtraveltimes = NULL; + // free reversed reachability links + if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability); + aasworld.reversedreachability = NULL; + // free routing algorithm memory + if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate); + aasworld.areaupdate = NULL; + if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate); + aasworld.portalupdate = NULL; + // free lists with areas the reachabilities go through + if (aasworld.reachabilityareas) FreeMemory(aasworld.reachabilityareas); + aasworld.reachabilityareas = NULL; + // free the reachability area index + if (aasworld.reachabilityareaindex) FreeMemory(aasworld.reachabilityareaindex); + aasworld.reachabilityareaindex = NULL; + // free area contents travel flags look up table + if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags); + aasworld.areacontentstravelflags = NULL; +} //end of the function AAS_FreeRoutingCaches +//=========================================================================== +// update the given routing cache +// +// Parameter: areacache : routing cache to update +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdateAreaRoutingCache(aas_routingcache_t *areacache) +{ + int i, nextareanum, cluster, badtravelflags, clusterareanum, linknum; + int numreachabilityareas; + unsigned short int t, startareatraveltimes[128]; //NOTE: not more than 128 reachabilities per area allowed + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + +#ifdef ROUTING_DEBUG + numareacacheupdates++; +#endif //ROUTING_DEBUG + //number of reachability areas within this cluster + numreachabilityareas = aasworld.clusters[areacache->cluster].numreachabilityareas; + // + aasworld.frameroutingupdates++; + //clear the routing update fields +// Com_Memset(aasworld.areaupdate, 0, aasworld.numareas * sizeof(aas_routingupdate_t)); + // + badtravelflags = ~areacache->travelflags; + // + clusterareanum = AAS_ClusterAreaNum(areacache->cluster, areacache->areanum); + if (clusterareanum >= numreachabilityareas) return; + // + Com_Memset(startareatraveltimes, 0, sizeof(startareatraveltimes)); + // + curupdate = &aasworld.areaupdate[clusterareanum]; + curupdate->areanum = areacache->areanum; + //VectorCopy(areacache->origin, curupdate->start); + curupdate->areatraveltimes = startareatraveltimes; + curupdate->tmptraveltime = areacache->starttraveltime; + // + areacache->traveltimes[clusterareanum] = areacache->starttraveltime; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) curupdate->next->prev = NULL; + else updatelistend = NULL; + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + revreach = &aasworld.reversedreachability[curupdate->areanum]; + // + for (i = 0, revlink = revreach->first; revlink; revlink = revlink->next, i++) + { + linknum = revlink->linknum; + reach = &aasworld.reachability[linknum]; + //if there is used an undesired travel type + if (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue; + //if not allowed to enter the next area + if (aasworld.areasettings[reach->areanum].areaflags & AREA_DISABLED) continue; + //if the next area has a not allowed travel flag + if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) continue; + //number of the area the reversed reachability leads to + nextareanum = revlink->areanum; + //get the cluster number of the area + cluster = aasworld.areasettings[nextareanum].cluster; + //don't leave the cluster + if (cluster > 0 && cluster != areacache->cluster) continue; + //get the number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(areacache->cluster, nextareanum); + if (clusterareanum >= numreachabilityareas) continue; + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + t = curupdate->tmptraveltime + + //AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->end) + + curupdate->areatraveltimes[i] + + reach->traveltime; + // + if (!areacache->traveltimes[clusterareanum] || + areacache->traveltimes[clusterareanum] > t) + { + areacache->traveltimes[clusterareanum] = t; + areacache->reachabilities[clusterareanum] = linknum - aasworld.areasettings[nextareanum].firstreachablearea; + nextupdate = &aasworld.areaupdate[clusterareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //VectorCopy(reach->start, nextupdate->start); + nextupdate->areatraveltimes = aasworld.areatraveltimes[nextareanum][linknum - + aasworld.areasettings[nextareanum].firstreachablearea]; + if (!nextupdate->inlist) + { + // we add the update to the end of the list + // we could also use a B+ tree to have a real sorted list + // on travel time which makes for faster routing updates + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) updatelistend->next = nextupdate; + else updateliststart = nextupdate; + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while +} //end of the function AAS_UpdateAreaRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_GetAreaRoutingCache(int clusternum, int areanum, int travelflags) +{ + int clusterareanum; + aas_routingcache_t *cache, *clustercache; + + //number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //pointer to the cache for the area in the cluster + clustercache = aasworld.clusterareacache[clusternum][clusterareanum]; + //find the cache without undesired travel flags + for (cache = clustercache; cache; cache = cache->next) + { + //if there aren't used any undesired travel types for the cache + if (cache->travelflags == travelflags) break; + } //end for + //if there was no cache + if (!cache) + { + cache = AAS_AllocRoutingCache(aasworld.clusters[clusternum].numreachabilityareas); + cache->cluster = clusternum; + cache->areanum = areanum; + VectorCopy(aasworld.areas[areanum].center, cache->origin); + cache->starttraveltime = 1; + cache->travelflags = travelflags; + cache->prev = NULL; + cache->next = clustercache; + if (clustercache) clustercache->prev = cache; + aasworld.clusterareacache[clusternum][clusterareanum] = cache; + AAS_UpdateAreaRoutingCache(cache); + } //end if + else + { + AAS_UnlinkCache(cache); + } //end else + //the cache has been accessed + cache->time = AAS_RoutingTime(); + cache->type = CACHETYPE_AREA; + AAS_LinkCache(cache); + return cache; +} //end of the function AAS_GetAreaRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdatePortalRoutingCache(aas_routingcache_t *portalcache) +{ + int i, portalnum, clusterareanum, clusternum; + unsigned short int t; + aas_portal_t *portal; + aas_cluster_t *cluster; + aas_routingcache_t *cache; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + +#ifdef ROUTING_DEBUG + numportalcacheupdates++; +#endif //ROUTING_DEBUG + //clear the routing update fields +// Com_Memset(aasworld.portalupdate, 0, (aasworld.numportals+1) * sizeof(aas_routingupdate_t)); + // + curupdate = &aasworld.portalupdate[aasworld.numportals]; + curupdate->cluster = portalcache->cluster; + curupdate->areanum = portalcache->areanum; + curupdate->tmptraveltime = portalcache->starttraveltime; + //if the start area is a cluster portal, store the travel time for that portal + clusternum = aasworld.areasettings[portalcache->areanum].cluster; + if (clusternum < 0) + { + portalcache->traveltimes[-clusternum] = portalcache->starttraveltime; + } //end if + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list + while (updateliststart) + { + curupdate = updateliststart; + //remove the current update from the list + if (curupdate->next) curupdate->next->prev = NULL; + else updatelistend = NULL; + updateliststart = curupdate->next; + //current update is removed from the list + curupdate->inlist = qfalse; + // + cluster = &aasworld.clusters[curupdate->cluster]; + // + cache = AAS_GetAreaRoutingCache(curupdate->cluster, + curupdate->areanum, portalcache->travelflags); + //take all portals of the cluster + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + //if this is the portal of the current update continue + if (portal->areanum == curupdate->areanum) continue; + // + clusterareanum = AAS_ClusterAreaNum(curupdate->cluster, portal->areanum); + if (clusterareanum >= cluster->numreachabilityareas) continue; + // + t = cache->traveltimes[clusterareanum]; + if (!t) continue; + t += curupdate->tmptraveltime; + // + if (!portalcache->traveltimes[portalnum] || + portalcache->traveltimes[portalnum] > t) + { + portalcache->traveltimes[portalnum] = t; + nextupdate = &aasworld.portalupdate[portalnum]; + if (portal->frontcluster == curupdate->cluster) + { + nextupdate->cluster = portal->backcluster; + } //end if + else + { + nextupdate->cluster = portal->frontcluster; + } //end else + nextupdate->areanum = portal->areanum; + //add travel time through the actual portal area for the next update + nextupdate->tmptraveltime = t + aasworld.portalmaxtraveltimes[portalnum]; + if (!nextupdate->inlist) + { + // we add the update to the end of the list + // we could also use a B+ tree to have a real sorted list + // on travel time which makes for faster routing updates + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) updatelistend->next = nextupdate; + else updateliststart = nextupdate; + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while +} //end of the function AAS_UpdatePortalRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_GetPortalRoutingCache(int clusternum, int areanum, int travelflags) +{ + aas_routingcache_t *cache; + + //find the cached portal routing if existing + for (cache = aasworld.portalcache[areanum]; cache; cache = cache->next) + { + if (cache->travelflags == travelflags) break; + } //end for + //if the portal routing isn't cached + if (!cache) + { + cache = AAS_AllocRoutingCache(aasworld.numportals); + cache->cluster = clusternum; + cache->areanum = areanum; + VectorCopy(aasworld.areas[areanum].center, cache->origin); + cache->starttraveltime = 1; + cache->travelflags = travelflags; + //add the cache to the cache list + cache->prev = NULL; + cache->next = aasworld.portalcache[areanum]; + if (aasworld.portalcache[areanum]) aasworld.portalcache[areanum]->prev = cache; + aasworld.portalcache[areanum] = cache; + //update the cache + AAS_UpdatePortalRoutingCache(cache); + } //end if + else + { + AAS_UnlinkCache(cache); + } //end else + //the cache has been accessed + cache->time = AAS_RoutingTime(); + cache->type = CACHETYPE_PORTAL; + AAS_LinkCache(cache); + return cache; +} //end of the function AAS_GetPortalRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaRouteToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum) +{ + int clusternum, goalclusternum, portalnum, i, clusterareanum, bestreachnum; + unsigned short int t, besttime; + aas_portal_t *portal; + aas_cluster_t *cluster; + aas_routingcache_t *areacache, *portalcache; + aas_reachability_t *reach; + + if (!aasworld.initialized) return qfalse; + + if (areanum == goalareanum) + { + *traveltime = 1; + *reachnum = 0; + return qtrue; + } + // + if (areanum <= 0 || areanum >= aasworld.numareas) + { + if (bot_developer) + { + botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: areanum %d out of range\n", areanum); + } //end if + return qfalse; + } //end if + if (goalareanum <= 0 || goalareanum >= aasworld.numareas) + { + if (bot_developer) + { + botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: goalareanum %d out of range\n", goalareanum); + } //end if + return qfalse; + } //end if + // make sure the routing cache doesn't grow to large + while(AvailableMemory() < 1 * 1024 * 1024) { + if (!AAS_FreeOldestCache()) break; + } + // + if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goalareanum)) + { + travelflags |= TFL_DONOTENTER; + } //end if + //NOTE: the number of routing updates is limited per frame + /* + if (aasworld.frameroutingupdates > MAX_FRAMEROUTINGUPDATES) + { +#ifdef DEBUG + //Log_Write("WARNING: AAS_AreaTravelTimeToGoalArea: frame routing updates overflowed"); +#endif + return 0; + } //end if + */ + // + clusternum = aasworld.areasettings[areanum].cluster; + goalclusternum = aasworld.areasettings[goalareanum].cluster; + //check if the area is a portal of the goal area cluster + if (clusternum < 0 && goalclusternum > 0) + { + portal = &aasworld.portals[-clusternum]; + if (portal->frontcluster == goalclusternum || + portal->backcluster == goalclusternum) + { + clusternum = goalclusternum; + } //end if + } //end if + //check if the goalarea is a portal of the area cluster + else if (clusternum > 0 && goalclusternum < 0) + { + portal = &aasworld.portals[-goalclusternum]; + if (portal->frontcluster == clusternum || + portal->backcluster == clusternum) + { + goalclusternum = clusternum; + } //end if + } //end if + //if both areas are in the same cluster + //NOTE: there might be a shorter route via another cluster!!! but we don't care + if (clusternum > 0 && goalclusternum > 0 && clusternum == goalclusternum) + { + // + areacache = AAS_GetAreaRoutingCache(clusternum, goalareanum, travelflags); + //the number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //the cluster the area is in + cluster = &aasworld.clusters[clusternum]; + //if the area is NOT a reachability area + if (clusterareanum >= cluster->numreachabilityareas) return 0; + //if it is possible to travel to the goal area through this cluster + if (areacache->traveltimes[clusterareanum] != 0) + { + *reachnum = aasworld.areasettings[areanum].firstreachablearea + + areacache->reachabilities[clusterareanum]; + if (!origin) { + *traveltime = areacache->traveltimes[clusterareanum]; + return qtrue; + } + reach = &aasworld.reachability[*reachnum]; + *traveltime = areacache->traveltimes[clusterareanum] + + AAS_AreaTravelTime(areanum, origin, reach->start); + // + return qtrue; + } //end if + } //end if + // + clusternum = aasworld.areasettings[areanum].cluster; + goalclusternum = aasworld.areasettings[goalareanum].cluster; + //if the goal area is a portal + if (goalclusternum < 0) + { + //just assume the goal area is part of the front cluster + portal = &aasworld.portals[-goalclusternum]; + goalclusternum = portal->frontcluster; + } //end if + //get the portal routing cache + portalcache = AAS_GetPortalRoutingCache(goalclusternum, goalareanum, travelflags); + //if the area is a cluster portal, read directly from the portal cache + if (clusternum < 0) + { + *traveltime = portalcache->traveltimes[-clusternum]; + *reachnum = aasworld.areasettings[areanum].firstreachablearea + + portalcache->reachabilities[-clusternum]; + return qtrue; + } //end if + // + besttime = 0; + bestreachnum = -1; + //the cluster the area is in + cluster = &aasworld.clusters[clusternum]; + //find the portal of the area cluster leading towards the goal area + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + //if the goal area isn't reachable from the portal + if (!portalcache->traveltimes[portalnum]) continue; + // + portal = &aasworld.portals[portalnum]; + //get the cache of the portal area + areacache = AAS_GetAreaRoutingCache(clusternum, portal->areanum, travelflags); + //current area inside the current cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //if the area is NOT a reachability area + if (clusterareanum >= cluster->numreachabilityareas) continue; + //if the portal is NOT reachable from this area + if (!areacache->traveltimes[clusterareanum]) continue; + //total travel time is the travel time the portal area is from + //the goal area plus the travel time towards the portal area + t = portalcache->traveltimes[portalnum] + areacache->traveltimes[clusterareanum]; + //FIXME: add the exact travel time through the actual portal area + //NOTE: for now we just add the largest travel time through the portal area + // because we can't directly calculate the exact travel time + // to be more specific we don't know which reachability was used to travel + // into the portal area + t += aasworld.portalmaxtraveltimes[portalnum]; + // + if (origin) + { + *reachnum = aasworld.areasettings[areanum].firstreachablearea + + areacache->reachabilities[clusterareanum]; + reach = aasworld.reachability + *reachnum; + t += AAS_AreaTravelTime(areanum, origin, reach->start); + } //end if + //if the time is better than the one already found + if (!besttime || t < besttime) + { + bestreachnum = *reachnum; + besttime = t; + } //end if + } //end for + if (bestreachnum < 0) { + return qfalse; + } + *reachnum = bestreachnum; + *traveltime = besttime; + return qtrue; +} //end of the function AAS_AreaRouteToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) +{ + int traveltime, reachnum; + + if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) + { + return traveltime; + } + return 0; +} //end of the function AAS_AreaTravelTimeToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaReachabilityToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) +{ + int traveltime, reachnum; + + if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) + { + return reachnum; + } + return 0; +} //end of the function AAS_AreaReachabilityToGoalArea +//=========================================================================== +// predict the route and stop on one of the stop events +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PredictRoute(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum) +{ + int curareanum, reachnum, i, j, testareanum; + vec3_t curorigin; + aas_reachability_t *reach; + aas_reachabilityareas_t *reachareas; + + //init output + route->stopevent = RSE_NONE; + route->endarea = goalareanum; + route->endcontents = 0; + route->endtravelflags = 0; + VectorCopy(origin, route->endpos); + route->time = 0; + + curareanum = areanum; + VectorCopy(origin, curorigin); + + for (i = 0; curareanum != goalareanum && (!maxareas || i < maxareas) && i < aasworld.numareas; i++) + { + reachnum = AAS_AreaReachabilityToGoalArea(curareanum, curorigin, goalareanum, travelflags); + if (!reachnum) + { + route->stopevent = RSE_NOROUTE; + return qfalse; + } //end if + reach = &aasworld.reachability[reachnum]; + // + if (stopevent & RSE_USETRAVELTYPE) + { + if (AAS_TravelFlagForType_inline(reach->traveltype) & stoptfl) + { + route->stopevent = RSE_USETRAVELTYPE; + route->endarea = curareanum; + route->endcontents = aasworld.areasettings[curareanum].contents; + route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype); + VectorCopy(reach->start, route->endpos); + return qtrue; + } //end if + if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & stoptfl) + { + route->stopevent = RSE_USETRAVELTYPE; + route->endarea = reach->areanum; + route->endcontents = aasworld.areasettings[reach->areanum].contents; + route->endtravelflags = AAS_AreaContentsTravelFlags_inline(reach->areanum); + VectorCopy(reach->end, route->endpos); + route->time += AAS_AreaTravelTime(areanum, origin, reach->start); + route->time += reach->traveltime; + return qtrue; + } //end if + } //end if + reachareas = &aasworld.reachabilityareas[reachnum]; + for (j = 0; j < reachareas->numareas + 1; j++) + { + if (j >= reachareas->numareas) + testareanum = reach->areanum; + else + testareanum = aasworld.reachabilityareaindex[reachareas->firstarea + j]; + if (stopevent & RSE_ENTERCONTENTS) + { + if (aasworld.areasettings[testareanum].contents & stopcontents) + { + route->stopevent = RSE_ENTERCONTENTS; + route->endarea = testareanum; + route->endcontents = aasworld.areasettings[testareanum].contents; + VectorCopy(reach->end, route->endpos); + route->time += AAS_AreaTravelTime(areanum, origin, reach->start); + route->time += reach->traveltime; + return qtrue; + } //end if + } //end if + if (stopevent & RSE_ENTERAREA) + { + if (testareanum == stopareanum) + { + route->stopevent = RSE_ENTERAREA; + route->endarea = testareanum; + route->endcontents = aasworld.areasettings[testareanum].contents; + VectorCopy(reach->start, route->endpos); + return qtrue; + } //end if + } //end if + } //end for + + route->time += AAS_AreaTravelTime(areanum, origin, reach->start); + route->time += reach->traveltime; + route->endarea = reach->areanum; + route->endcontents = aasworld.areasettings[reach->areanum].contents; + route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype); + VectorCopy(reach->end, route->endpos); + // + curareanum = reach->areanum; + VectorCopy(reach->end, curorigin); + // + if (maxtime && route->time > maxtime) + break; + } //end while + if (curareanum != goalareanum) + return qfalse; + return qtrue; +} //end of the function AAS_PredictRoute +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BridgeWalkable(int areanum) +{ + return qfalse; +} //end of the function AAS_BridgeWalkable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach) +{ + if (!aasworld.initialized) + { + Com_Memset(reach, 0, sizeof(aas_reachability_t)); + return; + } //end if + if (num < 0 || num >= aasworld.reachabilitysize) + { + Com_Memset(reach, 0, sizeof(aas_reachability_t)); + return; + } //end if + Com_Memcpy(reach, &aasworld.reachability[num], sizeof(aas_reachability_t));; +} //end of the function AAS_ReachabilityFromNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextAreaReachability(int areanum, int reachnum) +{ + aas_areasettings_t *settings; + + if (!aasworld.initialized) return 0; + + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_NextAreaReachability: areanum %d out of range\n", areanum); + return 0; + } //end if + + settings = &aasworld.areasettings[areanum]; + if (!reachnum) + { + return settings->firstreachablearea; + } //end if + if (reachnum < settings->firstreachablearea) + { + botimport.Print(PRT_FATAL, "AAS_NextAreaReachability: reachnum < settings->firstreachableara"); + return 0; + } //end if + reachnum++; + if (reachnum >= settings->firstreachablearea + settings->numreachableareas) + { + return 0; + } //end if + return reachnum; +} //end of the function AAS_NextAreaReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextModelReachability(int num, int modelnum) +{ + int i; + + if (num <= 0) num = 1; + else if (num >= aasworld.reachabilitysize) return 0; + else num++; + // + for (i = num; i < aasworld.reachabilitysize; i++) + { + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) + { + if (aasworld.reachability[i].facenum == modelnum) return i; + } //end if + else if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) + { + if ((aasworld.reachability[i].facenum & 0x0000FFFF) == modelnum) return i; + } //end if + } //end for + return 0; +} //end of the function AAS_NextModelReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin) +{ + int i, n, t; + vec3_t start, end; + aas_trace_t trace; + + //if the area has no reachabilities + if (!AAS_AreaReachability(areanum)) return qfalse; + // + n = aasworld.numareas * random(); + for (i = 0; i < aasworld.numareas; i++) + { + if (n <= 0) n = 1; + if (n >= aasworld.numareas) n = 1; + if (AAS_AreaReachability(n)) + { + t = AAS_AreaTravelTimeToGoalArea(areanum, aasworld.areas[areanum].center, n, travelflags); + //if the goal is reachable + if (t > 0) + { + if (AAS_AreaSwim(n)) + { + *goalareanum = n; + VectorCopy(aasworld.areas[n].center, goalorigin); + //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); + return qtrue; + } //end if + VectorCopy(aasworld.areas[n].center, start); + if (!AAS_PointAreaNum(start)) + Log_Write("area %d center %f %f %f in solid?", n, start[0], start[1], start[2]); + VectorCopy(start, end); + end[2] -= 300; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid && trace.fraction < 1 && AAS_PointAreaNum(trace.endpos) == n) + { + if (AAS_AreaGroundFaceArea(n) > 300) + { + *goalareanum = n; + VectorCopy(trace.endpos, goalorigin); + //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); + return qtrue; + } //end if + } //end if + } //end if + } //end if + n++; + } //end for + return qfalse; +} //end of the function AAS_RandomGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaVisible(int srcarea, int destarea) +{ + return qfalse; +} //end of the function AAS_AreaVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float DistancePointToLine(vec3_t v1, vec3_t v2, vec3_t point) +{ + vec3_t vec, p2; + + AAS_ProjectPointOntoVector(point, v1, v2, p2); + VectorSubtract(point, p2, vec); + return VectorLength(vec); +} //end of the function DistancePointToLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearestHideArea(int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags) +{ + int i, j, nextareanum, badtravelflags, numreach, bestarea; + unsigned short int t, besttraveltime; + static unsigned short int *hidetraveltimes; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + float dist1, dist2; + vec3_t v1, v2, p; + qboolean startVisible; + + // + if (!hidetraveltimes) + { + hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld.numareas * sizeof(unsigned short int)); + } //end if + else + { + Com_Memset(hidetraveltimes, 0, aasworld.numareas * sizeof(unsigned short int)); + } //end else + besttraveltime = 0; + bestarea = 0; + //assume visible + startVisible = qtrue; + // + badtravelflags = ~travelflags; + // + curupdate = &aasworld.areaupdate[areanum]; + curupdate->areanum = areanum; + VectorCopy(origin, curupdate->start); + curupdate->areatraveltimes = aasworld.areatraveltimes[areanum][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the list + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) curupdate->next->prev = NULL; + else updatelistend = NULL; + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = aasworld.areasettings[curupdate->areanum].numreachableareas; + reach = &aasworld.reachability[aasworld.areasettings[curupdate->areanum].firstreachablearea]; + // + for (i = 0; i < numreach; i++, reach++) + { + //if an undesired travel type is used + if (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue; + // + if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) continue; + //number of the area the reachability leads to + nextareanum = reach->areanum; + // if this moves us into the enemies area, skip it + if (nextareanum == enemyareanum) continue; + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + t = curupdate->tmptraveltime + + AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->start) + + reach->traveltime; + + //avoid going near the enemy + AAS_ProjectPointOntoVector(enemyorigin, curupdate->start, reach->end, p); + for (j = 0; j < 3; j++) + if ((p[j] > curupdate->start[j] && p[j] > reach->end[j]) || + (p[j] < curupdate->start[j] && p[j] < reach->end[j])) + break; + if (j < 3) + { + VectorSubtract(enemyorigin, reach->end, v2); + } //end if + else + { + VectorSubtract(enemyorigin, p, v2); + } //end else + dist2 = VectorLength(v2); + //never go through the enemy + if (dist2 < 40) continue; + // + VectorSubtract(enemyorigin, curupdate->start, v1); + dist1 = VectorLength(v1); + // + if (dist2 < dist1) + { + t += (dist1 - dist2) * 10; + } + // if we weren't visible when starting, make sure we don't move into their view + if (!startVisible && AAS_AreaVisible(enemyareanum, nextareanum)) { + continue; + } + // + if (besttraveltime && t >= besttraveltime) continue; + // + if (!hidetraveltimes[nextareanum] || + hidetraveltimes[nextareanum] > t) + { + //if the nextarea is not visible from the enemy area + if (!AAS_AreaVisible(enemyareanum, nextareanum)) + { + besttraveltime = t; + bestarea = nextareanum; + } //end if + hidetraveltimes[nextareanum] = t; + nextupdate = &aasworld.areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //remember where we entered this area + VectorCopy(reach->end, nextupdate->start); + //if this update is not in the list yet + if (!nextupdate->inlist) + { + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) updatelistend->next = nextupdate; + else updateliststart = nextupdate; + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while + return bestarea; +} //end of the function AAS_NearestHideArea diff --git a/codemp/botlib/be_aas_route.h b/codemp/botlib/be_aas_route.h new file mode 100644 index 0000000..05f7de5 --- /dev/null +++ b/codemp/botlib/be_aas_route.h @@ -0,0 +1,50 @@ + +/***************************************************************************** + * name: be_aas_route.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_route.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize the AAS routing +void AAS_InitRouting(void); +//free the AAS routing caches +void AAS_FreeRoutingCaches(void); +//returns the travel time from start to end in the given area +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); +// +void AAS_CreateAllRoutingCache(void); +void AAS_WriteRouteCache(void); +// +void AAS_RoutingInfo(void); +#endif //AASINTERN + +//returns the travel flag for the given travel type +int AAS_TravelFlagForType(int traveltype); +//return the travel flag(s) for traveling through this area +int AAS_AreaContentsTravelFlags(int areanum); +//returns the index of the next reachability for the given area +int AAS_NextAreaReachability(int areanum, int reachnum); +//returns the reachability with the given index +void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach); +//returns a random goal area and goal origin +int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin); +//enable or disable an area for routing +int AAS_EnableRoutingArea(int areanum, int enable); +//returns the travel time within the given area from start to end +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); +//returns the travel time from the area to the goal area using the given travel flags +int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags); +//predict a route up to a stop event +int AAS_PredictRoute(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum); + + diff --git a/codemp/botlib/be_aas_routealt.cpp b/codemp/botlib/be_aas_routealt.cpp new file mode 100644 index 0000000..c5de1da --- /dev/null +++ b/codemp/botlib/be_aas_routealt.cpp @@ -0,0 +1,223 @@ + +/***************************************************************************** + * name: be_aas_routealt.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_routealt.c $ + * $Author: Zaphod $ + * $Revision: 5 $ + * $Modtime: 11/22/00 8:47a $ + * $Date: 11/22/00 8:55a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_utils.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define ENABLE_ALTROUTING +//#define ALTROUTE_DEBUG + +typedef struct midrangearea_s +{ + int valid; + unsigned short starttime; + unsigned short goaltime; +} midrangearea_t; + +midrangearea_t *midrangeareas; +int *clusterareas; +int numclusterareas; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AltRoutingFloodCluster_r(int areanum) +{ + int i, otherareanum; + aas_area_t *area; + aas_face_t *face; + + //add the current area to the areas of the current cluster + clusterareas[numclusterareas] = areanum; + numclusterareas++; + //remove the area from the mid range areas + midrangeareas[areanum].valid = qfalse; + //flood to other areas through the faces of this area + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; + //get the area at the other side of the face + if (face->frontarea == areanum) otherareanum = face->backarea; + else otherareanum = face->frontarea; + //if there is an area at the other side of this face + if (!otherareanum) continue; + //if the other area is not a midrange area + if (!midrangeareas[otherareanum].valid) continue; + // + AAS_AltRoutingFloodCluster_r(otherareanum); + } //end for +} //end of the function AAS_AltRoutingFloodCluster_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int type) +{ +#ifndef ENABLE_ALTROUTING + return 0; +#else + int i, j, bestareanum; + int numaltroutegoals, nummidrangeareas; + int starttime, goaltime, goaltraveltime; + float dist, bestdist; + vec3_t mid, dir; +#ifdef ALTROUTE_DEBUG + int startmillisecs; + + startmillisecs = Sys_MilliSeconds(); +#endif + + if (!startareanum || !goalareanum) + return 0; + //travel time towards the goal area + goaltraveltime = AAS_AreaTravelTimeToGoalArea(startareanum, start, goalareanum, travelflags); + //clear the midrange areas + Com_Memset(midrangeareas, 0, aasworld.numareas * sizeof(midrangearea_t)); + numaltroutegoals = 0; + // + nummidrangeareas = 0; + // + for (i = 1; i < aasworld.numareas; i++) + { + // + if (!(type & ALTROUTEGOAL_ALL)) + { + if (!(type & ALTROUTEGOAL_CLUSTERPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL))) + { + if (!(type & ALTROUTEGOAL_VIEWPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL))) + { + continue; + } //end if + } //end if + } //end if + //if the area has no reachabilities + if (!AAS_AreaReachability(i)) continue; + //tavel time from the area to the start area + starttime = AAS_AreaTravelTimeToGoalArea(startareanum, start, i, travelflags); + if (!starttime) continue; + //if the travel time from the start to the area is greater than the shortest goal travel time + if (starttime > (float) 1.1 * goaltraveltime) continue; + //travel time from the area to the goal area + goaltime = AAS_AreaTravelTimeToGoalArea(i, NULL, goalareanum, travelflags); + if (!goaltime) continue; + //if the travel time from the area to the goal is greater than the shortest goal travel time + if (goaltime > (float) 0.8 * goaltraveltime) continue; + //this is a mid range area + midrangeareas[i].valid = qtrue; + midrangeareas[i].starttime = starttime; + midrangeareas[i].goaltime = goaltime; + Log_Write("%d midrange area %d", nummidrangeareas, i); + nummidrangeareas++; + } //end for + // + for (i = 1; i < aasworld.numareas; i++) + { + if (!midrangeareas[i].valid) continue; + //get the areas in one cluster + numclusterareas = 0; + AAS_AltRoutingFloodCluster_r(i); + //now we've got a cluster with areas through which an alternative route could go + //get the 'center' of the cluster + VectorClear(mid); + for (j = 0; j < numclusterareas; j++) + { + VectorAdd(mid, aasworld.areas[clusterareas[j]].center, mid); + } //end for + VectorScale(mid, 1.0 / numclusterareas, mid); + //get the area closest to the center of the cluster + bestdist = 999999; + bestareanum = 0; + for (j = 0; j < numclusterareas; j++) + { + VectorSubtract(mid, aasworld.areas[clusterareas[j]].center, dir); + dist = VectorLength(dir); + if (dist < bestdist) + { + bestdist = dist; + bestareanum = clusterareas[j]; + } //end if + } //end for + //now we've got an area for an alternative route + //FIXME: add alternative goal origin + VectorCopy(aasworld.areas[bestareanum].center, altroutegoals[numaltroutegoals].origin); + altroutegoals[numaltroutegoals].areanum = bestareanum; + altroutegoals[numaltroutegoals].starttraveltime = midrangeareas[bestareanum].starttime; + altroutegoals[numaltroutegoals].goaltraveltime = midrangeareas[bestareanum].goaltime; + altroutegoals[numaltroutegoals].extratraveltime = + (midrangeareas[bestareanum].starttime + midrangeareas[bestareanum].goaltime) - + goaltraveltime; + numaltroutegoals++; + // +#ifdef ALTROUTE_DEBUG + AAS_ShowAreaPolygons(bestareanum, 1, qtrue); +#endif + //don't return more than the maximum alternative route goals + if (numaltroutegoals >= maxaltroutegoals) break; + } //end for +#ifdef ALTROUTE_DEBUG + botimport.Print(PRT_MESSAGE, "alternative route goals in %d msec\n", Sys_MilliSeconds() - startmillisecs); +#endif + return numaltroutegoals; +#endif +} //end of the function AAS_AlternativeRouteGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAlternativeRouting(void) +{ +#ifdef ENABLE_ALTROUTING + if (midrangeareas) FreeMemory(midrangeareas); + midrangeareas = (midrangearea_t *) GetMemory(aasworld.numareas * sizeof(midrangearea_t)); + if (clusterareas) FreeMemory(clusterareas); + clusterareas = (int *) GetMemory(aasworld.numareas * sizeof(int)); +#endif +} //end of the function AAS_InitAlternativeRouting +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShutdownAlternativeRouting(void) +{ +#ifdef ENABLE_ALTROUTING + if (midrangeareas) FreeMemory(midrangeareas); + midrangeareas = NULL; + if (clusterareas) FreeMemory(clusterareas); + clusterareas = NULL; + numclusterareas = 0; +#endif +} //end of the function AAS_ShutdownAlternativeRouting diff --git a/codemp/botlib/be_aas_routealt.h b/codemp/botlib/be_aas_routealt.h new file mode 100644 index 0000000..5472ccf --- /dev/null +++ b/codemp/botlib/be_aas_routealt.h @@ -0,0 +1,23 @@ + +/***************************************************************************** + * name: be_aas_routealt.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_routealt.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifdef AASINTERN +void AAS_InitAlternativeRouting(void); +void AAS_ShutdownAlternativeRouting(void); +#endif //AASINTERN + + +int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int type); diff --git a/codemp/botlib/be_aas_sample.cpp b/codemp/botlib/be_aas_sample.cpp new file mode 100644 index 0000000..ce98bf1 --- /dev/null +++ b/codemp/botlib/be_aas_sample.cpp @@ -0,0 +1,1377 @@ + +/***************************************************************************** + * name: be_aas_sample.c + * + * desc: AAS environment sampling + * + * $Archive: /MissionPack/code/botlib/be_aas_sample.c $ + * $Author: Ttimo $ + * $Revision: 13 $ + * $Modtime: 4/13/01 4:45p $ + * $Date: 4/13/01 4:45p $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#ifndef BSPC +#include "l_libvar.h" +#endif +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_interface.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +//#define AAS_SAMPLE_DEBUG + +#define BBOX_NORMAL_EPSILON 0.001 + +#define ON_EPSILON 0 //0.0005 + +#define TRACEPLANE_EPSILON 0.125 + +typedef struct aas_tracestack_s +{ + vec3_t start; //start point of the piece of line to trace + vec3_t end; //end point of the piece of line to trace + int planenum; //last plane used as splitter + int nodenum; //node found after splitting with planenum +} aas_tracestack_t; + +int numaaslinks; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs) +{ + int index; + //bounding box size for each presence type + vec3_t boxmins[3] = {{0, 0, 0}, {-15, -15, -24}, {-15, -15, -24}}; + vec3_t boxmaxs[3] = {{0, 0, 0}, { 15, 15, 32}, { 15, 15, 8}}; + + if (presencetype == PRESENCE_NORMAL) index = 1; + else if (presencetype == PRESENCE_CROUCH) index = 2; + else + { + botimport.Print(PRT_FATAL, "AAS_PresenceTypeBoundingBox: unknown presence type\n"); + index = 2; + } //end if + VectorCopy(boxmins[index], mins); + VectorCopy(boxmaxs[index], maxs); +} //end of the function AAS_PresenceTypeBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAASLinkHeap(void) +{ + int i, max_aaslinks; + + max_aaslinks = aasworld.linkheapsize; + //if there's no link heap present + if (!aasworld.linkheap) + { +#ifdef BSPC + max_aaslinks = 6144; +#else + max_aaslinks = (int) LibVarValue("max_aaslinks", "6144"); +#endif + if (max_aaslinks < 0) max_aaslinks = 0; + aasworld.linkheapsize = max_aaslinks; + aasworld.linkheap = (aas_link_t *) GetHunkMemory(max_aaslinks * sizeof(aas_link_t)); + } //end if + //link the links on the heap + aasworld.linkheap[0].prev_ent = NULL; + aasworld.linkheap[0].next_ent = &aasworld.linkheap[1]; + for (i = 1; i < max_aaslinks-1; i++) + { + aasworld.linkheap[i].prev_ent = &aasworld.linkheap[i - 1]; + aasworld.linkheap[i].next_ent = &aasworld.linkheap[i + 1]; + } //end for + aasworld.linkheap[max_aaslinks-1].prev_ent = &aasworld.linkheap[max_aaslinks-2]; + aasworld.linkheap[max_aaslinks-1].next_ent = NULL; + //pointer to the first free link + aasworld.freelinks = &aasworld.linkheap[0]; + // + numaaslinks = max_aaslinks; +} //end of the function AAS_InitAASLinkHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAASLinkHeap(void) +{ + if (aasworld.linkheap) FreeMemory(aasworld.linkheap); + aasworld.linkheap = NULL; + aasworld.linkheapsize = 0; +} //end of the function AAS_FreeAASLinkHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_link_t *AAS_AllocAASLink(void) +{ + aas_link_t *link; + + link = aasworld.freelinks; + if (!link) + { +#ifndef BSPC + if (bot_developer) +#endif + { + botimport.Print(PRT_FATAL, "empty aas link heap\n"); + } //end if + return NULL; + } //end if + if (aasworld.freelinks) aasworld.freelinks = aasworld.freelinks->next_ent; + if (aasworld.freelinks) aasworld.freelinks->prev_ent = NULL; + numaaslinks--; + return link; +} //end of the function AAS_AllocAASLink +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DeAllocAASLink(aas_link_t *link) +{ + if (aasworld.freelinks) aasworld.freelinks->prev_ent = link; + link->prev_ent = NULL; + link->next_ent = aasworld.freelinks; + link->prev_area = NULL; + link->next_area = NULL; + aasworld.freelinks = link; + numaaslinks++; +} //end of the function AAS_DeAllocAASLink +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAASLinkedEntities(void) +{ + if (!aasworld.loaded) return; + if (aasworld.arealinkedentities) FreeMemory(aasworld.arealinkedentities); + aasworld.arealinkedentities = (aas_link_t **) GetClearedHunkMemory( + aasworld.numareas * sizeof(aas_link_t *)); +} //end of the function AAS_InitAASLinkedEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAASLinkedEntities(void) +{ + if (aasworld.arealinkedentities) FreeMemory(aasworld.arealinkedentities); + aasworld.arealinkedentities = NULL; +} //end of the function AAS_InitAASLinkedEntities +//=========================================================================== +// returns the AAS area the point is in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointAreaNum(vec3_t point) +{ + int nodenum; + vec_t dist; + aas_node_t *node; + aas_plane_t *plane; + + if (!aasworld.loaded) + { + botimport.Print(PRT_ERROR, "AAS_PointAreaNum: aas not loaded\n"); + return 0; + } //end if + + //start with node 1 because node zero is a dummy used for solid leafs + nodenum = 1; + while (nodenum > 0) + { +// botimport.Print(PRT_MESSAGE, "[%d]", nodenum); +#ifdef AAS_SAMPLE_DEBUG + if (nodenum >= aasworld.numnodes) + { + botimport.Print(PRT_ERROR, "nodenum = %d >= aasworld.numnodes = %d\n", nodenum, aasworld.numnodes); + return 0; + } //end if +#endif //AAS_SAMPLE_DEBUG + node = &aasworld.nodes[nodenum]; +#ifdef AAS_SAMPLE_DEBUG + if (node->planenum < 0 || node->planenum >= aasworld.numplanes) + { + botimport.Print(PRT_ERROR, "node->planenum = %d >= aasworld.numplanes = %d\n", node->planenum, aasworld.numplanes); + return 0; + } //end if +#endif //AAS_SAMPLE_DEBUG + plane = &aasworld.planes[node->planenum]; + dist = DotProduct(point, plane->normal) - plane->dist; + if (dist > 0) nodenum = node->children[0]; + else nodenum = node->children[1]; + } //end while + if (!nodenum) + { +#ifdef AAS_SAMPLE_DEBUG + botimport.Print(PRT_MESSAGE, "in solid\n"); +#endif //AAS_SAMPLE_DEBUG + return 0; + } //end if + return -nodenum; +} //end of the function AAS_PointAreaNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointReachabilityAreaIndex( vec3_t origin ) +{ + int areanum, cluster, i, index; + + if (!aasworld.initialized) + return 0; + + if ( !origin ) + { + index = 0; + for (i = 0; i < aasworld.numclusters; i++) + { + index += aasworld.clusters[i].numreachabilityareas; + } //end for + return index; + } //end if + + areanum = AAS_PointAreaNum( origin ); + if ( !areanum || !AAS_AreaReachability(areanum) ) + return 0; + cluster = aasworld.areasettings[areanum].cluster; + areanum = aasworld.areasettings[areanum].clusterareanum; + if (cluster < 0) + { + cluster = aasworld.portals[-cluster].frontcluster; + areanum = aasworld.portals[-cluster].clusterareanum[0]; + } //end if + + index = 0; + for (i = 0; i < cluster; i++) + { + index += aasworld.clusters[i].numreachabilityareas; + } //end for + index += areanum; + return index; +} //end of the function AAS_PointReachabilityAreaIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaCluster(int areanum) +{ + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaCluster: invalid area number\n"); + return 0; + } //end if + return aasworld.areasettings[areanum].cluster; +} //end of the function AAS_AreaCluster +//=========================================================================== +// returns the presence types of the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaPresenceType(int areanum) +{ + if (!aasworld.loaded) return 0; + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaPresenceType: invalid area number\n"); + return 0; + } //end if + return aasworld.areasettings[areanum].presencetype; +} //end of the function AAS_AreaPresenceType +//=========================================================================== +// returns the presence type at the given point +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointPresenceType(vec3_t point) +{ + int areanum; + + if (!aasworld.loaded) return 0; + + areanum = AAS_PointAreaNum(point); + if (!areanum) return PRESENCE_NONE; + return aasworld.areasettings[areanum].presencetype; +} //end of the function AAS_PointPresenceType +//=========================================================================== +// calculates the minimum distance between the origin of the box and the +// given plane when both will collide on the given side of the plane +// +// normal = normal vector of plane to calculate distance from +// mins = minimums of box relative to origin +// maxs = maximums of box relative to origin +// side = side of the plane we want to calculate the distance from +// 0 normal vector side +// 1 not normal vector side +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec_t AAS_BoxOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs, int side) +{ + vec3_t v1, v2; + int i; + + //swap maxs and mins when on the other side of the plane + if (side) + { + //get a point of the box that would be one of the first + //to collide with the plane + for (i = 0; i < 3; i++) + { + if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; + else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = mins[i]; + else v1[i] = 0; + } //end for + } //end if + else + { + //get a point of the box that would be one of the first + //to collide with the plane + for (i = 0; i < 3; i++) + { + if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = mins[i]; + else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; + else v1[i] = 0; + } //end for + } //end else + // + VectorCopy(normal, v2); + VectorInverse(v2); +// VectorNegate(normal, v2); + return DotProduct(v1, v2); +} //end of the function AAS_BoxOriginDistanceFromPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_AreaEntityCollision(int areanum, vec3_t start, vec3_t end, + int presencetype, int passent, aas_trace_t *trace) +{ + int collision; + vec3_t boxmins, boxmaxs; + aas_link_t *link; + bsp_trace_t bsptrace; + + AAS_PresenceTypeBoundingBox(presencetype, boxmins, boxmaxs); + + Com_Memset(&bsptrace, 0, sizeof(bsp_trace_t)); //make compiler happy + //assume no collision + bsptrace.fraction = 1; + collision = qfalse; + for (link = aasworld.arealinkedentities[areanum]; link; link = link->next_ent) + { + //ignore the pass entity + if (link->entnum == passent) continue; + // + if (AAS_EntityCollision(link->entnum, start, boxmins, boxmaxs, end, + CONTENTS_SOLID|CONTENTS_PLAYERCLIP, &bsptrace)) + { + collision = qtrue; + } //end if + } //end for + if (collision) + { + trace->startsolid = bsptrace.startsolid; + trace->ent = bsptrace.ent; + VectorCopy(bsptrace.endpos, trace->endpos); + trace->area = 0; + trace->planenum = 0; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_AreaEntityCollision +//=========================================================================== +// recursive subdivision of the line by the BSP tree. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, + int passent) +{ + int side, nodenum, tmpplanenum; + float front, back, frac; + vec3_t cur_start, cur_end, cur_mid, v1, v2; + aas_tracestack_t tracestack[127]; + aas_tracestack_t *tstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + aas_trace_t trace; + + //clear the trace structure + Com_Memset(&trace, 0, sizeof(aas_trace_t)); + + if (!aasworld.loaded) return trace; + + tstack_p = tracestack; + //we start with the whole line on the stack + VectorCopy(start, tstack_p->start); + VectorCopy(end, tstack_p->end); + tstack_p->planenum = 0; + //start with node 1 because node zero is a dummy for a solid leaf + tstack_p->nodenum = 1; //starting at the root of the tree + tstack_p++; + + while (1) + { + //pop up the stack + tstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (tstack_p < tracestack) + { + tstack_p++; + //nothing was hit + trace.startsolid = qfalse; + trace.fraction = 1.0; + //endpos is the end of the line + VectorCopy(end, trace.endpos); + //nothing hit + trace.ent = 0; + trace.area = 0; + trace.planenum = 0; + return trace; + } //end if + //number of the current node to test the line against + nodenum = tstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { +#ifdef AAS_SAMPLE_DEBUG + if (-nodenum > aasworld.numareasettings) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: -nodenum out of range\n"); + return trace; + } //end if +#endif //AAS_SAMPLE_DEBUG + //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); + //if can't enter the area because it hasn't got the right presence type + if (!(aasworld.areasettings[-nodenum].presencetype & presencetype)) + { + //if the start point is still the initial start point + //NOTE: no need for epsilons because the points will be + //exactly the same when they're both the start point + if (tstack_p->start[0] == start[0] && + tstack_p->start[1] == start[1] && + tstack_p->start[2] == start[2]) + { + trace.startsolid = qtrue; + trace.fraction = 0.0; + VectorClear(v1); + } //end if + else + { + trace.startsolid = qfalse; + VectorSubtract(end, start, v1); + VectorSubtract(tstack_p->start, start, v2); + trace.fraction = VectorLength(v2) / VectorNormalize(v1); + VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); + } //end else + VectorCopy(tstack_p->start, trace.endpos); + trace.ent = 0; + trace.area = -nodenum; +// VectorSubtract(end, start, v1); + trace.planenum = tstack_p->planenum; + //always take the plane with normal facing towards the trace start + plane = &aasworld.planes[trace.planenum]; + if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; + return trace; + } //end if + else + { + if (passent >= 0) + { + if (AAS_AreaEntityCollision(-nodenum, tstack_p->start, + tstack_p->end, presencetype, passent, + &trace)) + { + if (!trace.startsolid) + { + VectorSubtract(end, start, v1); + VectorSubtract(trace.endpos, start, v2); + trace.fraction = VectorLength(v2) / VectorLength(v1); + } //end if + return trace; + } //end if + } //end if + } //end else + trace.lastarea = -nodenum; + continue; + } //end if + //if it is a solid leaf + if (!nodenum) + { + //if the start point is still the initial start point + //NOTE: no need for epsilons because the points will be + //exactly the same when they're both the start point + if (tstack_p->start[0] == start[0] && + tstack_p->start[1] == start[1] && + tstack_p->start[2] == start[2]) + { + trace.startsolid = qtrue; + trace.fraction = 0.0; + VectorClear(v1); + } //end if + else + { + trace.startsolid = qfalse; + VectorSubtract(end, start, v1); + VectorSubtract(tstack_p->start, start, v2); + trace.fraction = VectorLength(v2) / VectorNormalize(v1); + VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); + } //end else + VectorCopy(tstack_p->start, trace.endpos); + trace.ent = 0; + trace.area = 0; //hit solid leaf +// VectorSubtract(end, start, v1); + trace.planenum = tstack_p->planenum; + //always take the plane with normal facing towards the trace start + plane = &aasworld.planes[trace.planenum]; + if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; + return trace; + } //end if +#ifdef AAS_SAMPLE_DEBUG + if (nodenum > aasworld.numnodes) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: nodenum out of range\n"); + return trace; + } //end if +#endif //AAS_SAMPLE_DEBUG + //the node to test against + aasnode = &aasworld.nodes[nodenum]; + //start point of current line to test against node + VectorCopy(tstack_p->start, cur_start); + //end point of the current line to test against node + VectorCopy(tstack_p->end, cur_end); + //the current node plane + plane = &aasworld.planes[aasnode->planenum]; + +// switch(plane->type) + {/*FIXME: wtf doesn't this work? obviously the axial node planes aren't always facing positive!!! + //check for axial planes + case PLANE_X: + { + front = cur_start[0] - plane->dist; + back = cur_end[0] - plane->dist; + break; + } //end case + case PLANE_Y: + { + front = cur_start[1] - plane->dist; + back = cur_end[1] - plane->dist; + break; + } //end case + case PLANE_Z: + { + front = cur_start[2] - plane->dist; + back = cur_end[2] - plane->dist; + break; + } //end case*/ +// default: //gee it's not an axial plane + { + front = DotProduct(cur_start, plane->normal) - plane->dist; + back = DotProduct(cur_end, plane->normal) - plane->dist; +// break; + } //end default + } //end switch + // bk010221 - old location of FPE hack and divide by zero expression + //if the whole to be traced line is totally at the front of this node + //only go down the tree with the front child + if ((front >= -ON_EPSILON && back >= -ON_EPSILON)) + { + //keep the current start and end point on the stack + //and go down the tree with the front child + tstack_p->nodenum = aasnode->children[0]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end if + //if the whole to be traced line is totally at the back of this node + //only go down the tree with the back child + else if ((front < ON_EPSILON && back < ON_EPSILON)) + { + //keep the current start and end point on the stack + //and go down the tree with the back child + tstack_p->nodenum = aasnode->children[1]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end if + //go down the tree both at the front and back of the node + else + { + tmpplanenum = tstack_p->planenum; + // bk010221 - new location of divide by zero (see above) + if ( front == back ) front -= 0.001f; // bk0101022 - hack/FPE + //calculate the hitpoint with the node (split point of the line) + //put the crosspoint TRACEPLANE_EPSILON pixels on the near side + if (front < 0) frac = (front + TRACEPLANE_EPSILON)/(front-back); + else frac = (front - TRACEPLANE_EPSILON)/(front-back); // bk010221 + // + if (frac < 0) + frac = 0.001f; //0 + else if (frac > 1) + frac = 0.999f; //1 + //frac = front / (front-back); + // + cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; + cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; + cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; + +// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); + //side the front part of the line is on + side = front < 0; + //first put the end part of the line on the stack (back side) + VectorCopy(cur_mid, tstack_p->start); + //not necesary to store because still on stack + //VectorCopy(cur_end, tstack_p->end); + tstack_p->planenum = aasnode->planenum; + tstack_p->nodenum = aasnode->children[!side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + //now put the part near the start of the line on the stack so we will + //continue with thats part first. This way we'll find the first + //hit of the bbox + VectorCopy(cur_start, tstack_p->start); + VectorCopy(cur_mid, tstack_p->end); + tstack_p->planenum = tmpplanenum; + tstack_p->nodenum = aasnode->children[side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end else + } //end while +// return trace; +} //end of the function AAS_TraceClientBBox +//=========================================================================== +// recursive subdivision of the line by the BSP tree. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas) +{ + int side, nodenum, tmpplanenum; + int numareas; + float front, back, frac; + vec3_t cur_start, cur_end, cur_mid; + aas_tracestack_t tracestack[127]; + aas_tracestack_t *tstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + + numareas = 0; + areas[0] = 0; + if (!aasworld.loaded) return numareas; + + tstack_p = tracestack; + //we start with the whole line on the stack + VectorCopy(start, tstack_p->start); + VectorCopy(end, tstack_p->end); + tstack_p->planenum = 0; + //start with node 1 because node zero is a dummy for a solid leaf + tstack_p->nodenum = 1; //starting at the root of the tree + tstack_p++; + + while (1) + { + //pop up the stack + tstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (tstack_p < tracestack) + { + return numareas; + } //end if + //number of the current node to test the line against + nodenum = tstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { +#ifdef AAS_SAMPLE_DEBUG + if (-nodenum > aasworld.numareasettings) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: -nodenum = %d out of range\n", -nodenum); + return numareas; + } //end if +#endif //AAS_SAMPLE_DEBUG + //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); + areas[numareas] = -nodenum; + if (points) VectorCopy(tstack_p->start, points[numareas]); + numareas++; + if (numareas >= maxareas) return numareas; + continue; + } //end if + //if it is a solid leaf + if (!nodenum) + { + continue; + } //end if +#ifdef AAS_SAMPLE_DEBUG + if (nodenum > aasworld.numnodes) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: nodenum out of range\n"); + return numareas; + } //end if +#endif //AAS_SAMPLE_DEBUG + //the node to test against + aasnode = &aasworld.nodes[nodenum]; + //start point of current line to test against node + VectorCopy(tstack_p->start, cur_start); + //end point of the current line to test against node + VectorCopy(tstack_p->end, cur_end); + //the current node plane + plane = &aasworld.planes[aasnode->planenum]; + +// switch(plane->type) + {/*FIXME: wtf doesn't this work? obviously the node planes aren't always facing positive!!! + //check for axial planes + case PLANE_X: + { + front = cur_start[0] - plane->dist; + back = cur_end[0] - plane->dist; + break; + } //end case + case PLANE_Y: + { + front = cur_start[1] - plane->dist; + back = cur_end[1] - plane->dist; + break; + } //end case + case PLANE_Z: + { + front = cur_start[2] - plane->dist; + back = cur_end[2] - plane->dist; + break; + } //end case*/ +// default: //gee it's not an axial plane + { + front = DotProduct(cur_start, plane->normal) - plane->dist; + back = DotProduct(cur_end, plane->normal) - plane->dist; +// break; + } //end default + } //end switch + + //if the whole to be traced line is totally at the front of this node + //only go down the tree with the front child + if (front > 0 && back > 0) + { + //keep the current start and end point on the stack + //and go down the tree with the front child + tstack_p->nodenum = aasnode->children[0]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end if + //if the whole to be traced line is totally at the back of this node + //only go down the tree with the back child + else if (front <= 0 && back <= 0) + { + //keep the current start and end point on the stack + //and go down the tree with the back child + tstack_p->nodenum = aasnode->children[1]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end if + //go down the tree both at the front and back of the node + else + { + tmpplanenum = tstack_p->planenum; + //calculate the hitpoint with the node (split point of the line) + //put the crosspoint TRACEPLANE_EPSILON pixels on the near side + if (front < 0) frac = (front)/(front-back); + else frac = (front)/(front-back); + if (frac < 0) frac = 0; + else if (frac > 1) frac = 1; + //frac = front / (front-back); + // + cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; + cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; + cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; + +// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); + //side the front part of the line is on + side = front < 0; + //first put the end part of the line on the stack (back side) + VectorCopy(cur_mid, tstack_p->start); + //not necesary to store because still on stack + //VectorCopy(cur_end, tstack_p->end); + tstack_p->planenum = aasnode->planenum; + tstack_p->nodenum = aasnode->children[!side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + //now put the part near the start of the line on the stack so we will + //continue with thats part first. This way we'll find the first + //hit of the bbox + VectorCopy(cur_start, tstack_p->start); + VectorCopy(cur_mid, tstack_p->end); + tstack_p->planenum = tmpplanenum; + tstack_p->nodenum = aasnode->children[side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end else + } //end while +// return numareas; +} //end of the function AAS_TraceAreas +//=========================================================================== +// a simple cross product +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +// void AAS_OrthogonalToVectors(vec3_t v1, vec3_t v2, vec3_t res) +#define AAS_OrthogonalToVectors(v1, v2, res) \ + (res)[0] = ((v1)[1] * (v2)[2]) - ((v1)[2] * (v2)[1]);\ + (res)[1] = ((v1)[2] * (v2)[0]) - ((v1)[0] * (v2)[2]);\ + (res)[2] = ((v1)[0] * (v2)[1]) - ((v1)[1] * (v2)[0]); +//=========================================================================== +// tests if the given point is within the face boundaries +// +// Parameter: face : face to test if the point is in it +// pnormal : normal of the plane to use for the face +// point : point to test if inside face boundaries +// Returns: qtrue if the point is within the face boundaries +// Changes Globals: - +//=========================================================================== +qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon) +{ + int i, firstvertex, edgenum; + vec3_t v0; + vec3_t edgevec, pointvec, sepnormal; + aas_edge_t *edge; +#ifdef AAS_SAMPLE_DEBUG + int lastvertex = 0; +#endif //AAS_SAMPLE_DEBUG + + if (!aasworld.loaded) return qfalse; + + for (i = 0; i < face->numedges; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + //get the first vertex of the edge + firstvertex = edgenum < 0; + VectorCopy(aasworld.vertexes[edge->v[firstvertex]], v0); + //edge vector + VectorSubtract(aasworld.vertexes[edge->v[!firstvertex]], v0, edgevec); + // +#ifdef AAS_SAMPLE_DEBUG + if (lastvertex && lastvertex != edge->v[firstvertex]) + { + botimport.Print(PRT_MESSAGE, "winding not counter clockwise\n"); + } //end if + lastvertex = edge->v[!firstvertex]; +#endif //AAS_SAMPLE_DEBUG + //vector from first edge point to point possible in face + VectorSubtract(point, v0, pointvec); + //get a vector pointing inside the face orthogonal to both the + //edge vector and the normal vector of the plane the face is in + //this vector defines a plane through the origin (first vertex of + //edge) and through both the edge vector and the normal vector + //of the plane + AAS_OrthogonalToVectors(edgevec, pnormal, sepnormal); + //check on wich side of the above plane the point is + //this is done by checking the sign of the dot product of the + //vector orthogonal vector from above and the vector from the + //origin (first vertex of edge) to the point + //if the dotproduct is smaller than zero the point is outside the face + if (DotProduct(pointvec, sepnormal) < -epsilon) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_InsideFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon) +{ + int i, firstvertex, edgenum; + vec_t *v1, *v2; + vec3_t edgevec, pointvec, sepnormal; + aas_edge_t *edge; + aas_plane_t *plane; + aas_face_t *face; + + if (!aasworld.loaded) return qfalse; + + face = &aasworld.faces[facenum]; + plane = &aasworld.planes[face->planenum]; + // + for (i = 0; i < face->numedges; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + //get the first vertex of the edge + firstvertex = edgenum < 0; + v1 = aasworld.vertexes[edge->v[firstvertex]]; + v2 = aasworld.vertexes[edge->v[!firstvertex]]; + //edge vector + VectorSubtract(v2, v1, edgevec); + //vector from first edge point to point possible in face + VectorSubtract(point, v1, pointvec); + // + CrossProduct(edgevec, plane->normal, sepnormal); + // + if (DotProduct(pointvec, sepnormal) < -epsilon) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_PointInsideFace +//=========================================================================== +// returns the ground face the given point is above in the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point) +{ + int i, facenum; + vec3_t up = {0, 0, 1}; + vec3_t normal; + aas_area_t *area; + aas_face_t *face; + + if (!aasworld.loaded) return NULL; + + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + face = &aasworld.faces[abs(facenum)]; + //if this is a ground face + if (face->faceflags & FACE_GROUND) + { + //get the up or down normal + if (aasworld.planes[face->planenum].normal[2] < 0) VectorNegate(up, normal); + else VectorCopy(up, normal); + //check if the point is in the face + if (AAS_InsideFace(face, normal, point, 0.01f)) return face; + } //end if + } //end for + return NULL; +} //end of the function AAS_AreaGroundFace +//=========================================================================== +// returns the face the trace end position is situated in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FacePlane(int facenum, vec3_t normal, float *dist) +{ + aas_plane_t *plane; + + plane = &aasworld.planes[aasworld.faces[facenum].planenum]; + VectorCopy(plane->normal, normal); + *dist = plane->dist; +} //end of the function AAS_FacePlane +//=========================================================================== +// returns the face the trace end position is situated in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_face_t *AAS_TraceEndFace(aas_trace_t *trace) +{ + int i, facenum; + aas_area_t *area; + aas_face_t *face, *firstface = NULL; + + if (!aasworld.loaded) return NULL; + + //if started in solid no face was hit + if (trace->startsolid) return NULL; + //trace->lastarea is the last area the trace was in + area = &aasworld.areas[trace->lastarea]; + //check which face the trace.endpos was in + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + face = &aasworld.faces[abs(facenum)]; + //if the face is in the same plane as the trace end point + if ((face->planenum & ~1) == (trace->planenum & ~1)) + { + //firstface is used for optimization, if theres only one + //face in the plane then it has to be the good one + //if there are more faces in the same plane then always + //check the one with the fewest edges first +/* if (firstface) + { + if (firstface->numedges < face->numedges) + { + if (AAS_InsideFace(firstface, + aasworld.planes[face->planenum].normal, trace->endpos)) + { + return firstface; + } //end if + firstface = face; + } //end if + else + { + if (AAS_InsideFace(face, + aasworld.planes[face->planenum].normal, trace->endpos)) + { + return face; + } //end if + } //end else + } //end if + else + { + firstface = face; + } //end else*/ + if (AAS_InsideFace(face, + aasworld.planes[face->planenum].normal, trace->endpos, 0.01f)) return face; + } //end if + } //end for + return firstface; +} //end of the function AAS_TraceEndFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BoxOnPlaneSide2(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) +{ + int i, sides; + float dist1, dist2; + vec3_t corners[2]; + + for (i = 0; i < 3; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = absmins[i]; + corners[1][i] = absmaxs[i]; + } //end if + else + { + corners[1][i] = absmins[i]; + corners[0][i] = absmaxs[i]; + } //end else + } //end for + 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; +} //end of the function AAS_BoxOnPlaneSide2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +//int AAS_BoxOnPlaneSide(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) +#define AAS_BoxOnPlaneSide(absmins, absmaxs, p) (\ + ( (p)->type < 3) ?\ + (\ + ( (p)->dist <= (absmins)[(p)->type]) ?\ + (\ + 1\ + )\ + :\ + (\ + ( (p)->dist >= (absmaxs)[(p)->type]) ?\ + (\ + 2\ + )\ + :\ + (\ + 3\ + )\ + )\ + )\ + :\ + (\ + AAS_BoxOnPlaneSide2((absmins), (absmaxs), (p))\ + )\ +) //end of the function AAS_BoxOnPlaneSide +//=========================================================================== +// remove the links to this entity from all areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkFromAreas(aas_link_t *areas) +{ + aas_link_t *link, *nextlink; + + for (link = areas; link; link = nextlink) + { + //next area the entity is linked in + nextlink = link->next_area; + //remove the entity from the linked list of this area + if (link->prev_ent) link->prev_ent->next_ent = link->next_ent; + else aasworld.arealinkedentities[link->areanum] = link->next_ent; + if (link->next_ent) link->next_ent->prev_ent = link->prev_ent; + //deallocate the link structure + AAS_DeAllocAASLink(link); + } //end for +} //end of the function AAS_UnlinkFromAreas +//=========================================================================== +// link the entity to the areas the bounding box is totally or partly +// situated in. This is done with recursion down the tree using the +// bounding box to test for plane sides +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +typedef struct +{ + int nodenum; //node found after splitting +} aas_linkstack_t; + +aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum) +{ + int side, nodenum; + aas_linkstack_t linkstack[128]; + aas_linkstack_t *lstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + aas_link_t *link, *areas; + + if (!aasworld.loaded) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: aas not loaded\n"); + return NULL; + } //end if + + areas = NULL; + // + lstack_p = linkstack; + //we start with the whole line on the stack + //start with node 1 because node zero is a dummy used for solid leafs + lstack_p->nodenum = 1; //starting at the root of the tree + lstack_p++; + + while (1) + { + //pop up the stack + lstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (lstack_p < linkstack) break; + //number of the current node to test the line against + nodenum = lstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { + //NOTE: the entity might have already been linked into this area + // because several node children can point to the same area + for (link = aasworld.arealinkedentities[-nodenum]; link; link = link->next_ent) + { + if (link->entnum == entnum) break; + } //end for + if (link) continue; + // + link = AAS_AllocAASLink(); + if (!link) return areas; + link->entnum = entnum; + link->areanum = -nodenum; + //put the link into the double linked area list of the entity + link->prev_area = NULL; + link->next_area = areas; + if (areas) areas->prev_area = link; + areas = link; + //put the link into the double linked entity list of the area + link->prev_ent = NULL; + link->next_ent = aasworld.arealinkedentities[-nodenum]; + if (aasworld.arealinkedentities[-nodenum]) + aasworld.arealinkedentities[-nodenum]->prev_ent = link; + aasworld.arealinkedentities[-nodenum] = link; + // + continue; + } //end if + //if solid leaf + if (!nodenum) continue; + //the node to test against + aasnode = &aasworld.nodes[nodenum]; + //the current node plane + plane = &aasworld.planes[aasnode->planenum]; + //get the side(s) the box is situated relative to the plane + side = AAS_BoxOnPlaneSide2(absmins, absmaxs, plane); + //if on the front side of the node + if (side & 1) + { + lstack_p->nodenum = aasnode->children[0]; + lstack_p++; + } //end if + if (lstack_p >= &linkstack[127]) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); + break; + } //end if + //if on the back side of the node + if (side & 2) + { + lstack_p->nodenum = aasnode->children[1]; + lstack_p++; + } //end if + if (lstack_p >= &linkstack[127]) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); + break; + } //end if + } //end while + return areas; +} //end of the function AAS_AASLinkEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype) +{ + vec3_t mins, maxs; + vec3_t newabsmins, newabsmaxs; + + AAS_PresenceTypeBoundingBox(presencetype, mins, maxs); + VectorSubtract(absmins, maxs, newabsmins); + VectorSubtract(absmaxs, mins, newabsmaxs); + //relink the entity + return AAS_AASLinkEntity(newabsmins, newabsmaxs, entnum); +} //end of the function AAS_LinkEntityClientBBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas) +{ + aas_link_t *linkedareas, *link; + int num; + + linkedareas = AAS_AASLinkEntity(absmins, absmaxs, -1); + num = 0; + for (link = linkedareas; link; link = link->next_area) + { + areas[num] = link->areanum; + num++; + if (num >= maxareas) + break; + } //end for + AAS_UnlinkFromAreas(linkedareas); + return num; +} //end of the function AAS_BBoxAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaInfo( int areanum, aas_areainfo_t *info ) +{ + aas_areasettings_t *settings; + if (!info) + return 0; + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaInfo: areanum %d out of range\n", areanum); + return 0; + } //end if + settings = &aasworld.areasettings[areanum]; + info->cluster = settings->cluster; + info->contents = settings->contents; + info->flags = settings->areaflags; + info->presencetype = settings->presencetype; + VectorCopy(aasworld.areas[areanum].mins, info->mins); + VectorCopy(aasworld.areas[areanum].maxs, info->maxs); + VectorCopy(aasworld.areas[areanum].center, info->center); + return sizeof(aas_areainfo_t); +} //end of the function AAS_AreaInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_plane_t *AAS_PlaneFromNum(int planenum) +{ + if (!aasworld.loaded) return 0; + + return &aasworld.planes[planenum]; +} //end of the function AAS_PlaneFromNum diff --git a/codemp/botlib/be_aas_sample.h b/codemp/botlib/be_aas_sample.h new file mode 100644 index 0000000..2024543 --- /dev/null +++ b/codemp/botlib/be_aas_sample.h @@ -0,0 +1,52 @@ + +/***************************************************************************** + * name: be_aas_sample.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_sample.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifdef AASINTERN +void AAS_InitAASLinkHeap(void); +void AAS_InitAASLinkedEntities(void); +void AAS_FreeAASLinkHeap(void); +void AAS_FreeAASLinkedEntities(void); +aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point); +aas_face_t *AAS_TraceEndFace(aas_trace_t *trace); +aas_plane_t *AAS_PlaneFromNum(int planenum); +aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum); +aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype); +qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon); +qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon); +void AAS_UnlinkFromAreas(aas_link_t *areas); +#endif //AASINTERN + +//returns the mins and maxs of the bounding box for the given presence type +void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs); +//returns the cluster the area is in (negative portal number if the area is a portal) +int AAS_AreaCluster(int areanum); +//returns the presence type(s) of the area +int AAS_AreaPresenceType(int areanum); +//returns the presence type(s) at the given point +int AAS_PointPresenceType(vec3_t point); +//returns the result of the trace of a client bbox +aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, int passent); +//stores the areas the trace went through and returns the number of passed areas +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); +//returns the areas the bounding box is in +int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); +//return area information +int AAS_AreaInfo( int areanum, aas_areainfo_t *info ); +//returns the area the point is in +int AAS_PointAreaNum(vec3_t point); +// +int AAS_PointReachabilityAreaIndex( vec3_t point ); +//returns the plane the given face is in +void AAS_FacePlane(int facenum, vec3_t normal, float *dist); + diff --git a/codemp/botlib/be_ai_char.cpp b/codemp/botlib/be_ai_char.cpp new file mode 100644 index 0000000..c05ab3b --- /dev/null +++ b/codemp/botlib/be_ai_char.cpp @@ -0,0 +1,773 @@ + +/***************************************************************************** + * name: be_ai_char.c + * + * desc: bot characters + * + * $Archive: /MissionPack/code/botlib/be_ai_char.c $ + * $Author: Ttimo $ + * $Revision: 6 $ + * $Modtime: 4/22/01 8:52a $ + * $Date: 4/22/01 8:52a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ai_char.h" + +#define MAX_CHARACTERISTICS 80 + +#define CT_INTEGER 1 +#define CT_FLOAT 2 +#define CT_STRING 3 + +#define DEFAULT_CHARACTER "bots/default_c.c" + +//characteristic value +union cvalue +{ + int integer; + float _float; + char *string; +}; +//a characteristic +typedef struct bot_characteristic_s +{ + char type; //characteristic type + union cvalue value; //characteristic value +} bot_characteristic_t; + +//a bot character +typedef struct bot_character_s +{ + char filename[MAX_QPATH]; + float skill; + bot_characteristic_t c[1]; //variable sized +} bot_character_t; + +bot_character_t *botcharacters[MAX_CLIENTS + 1]; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_character_t *BotCharacterFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); + return NULL; + } //end if + if (!botcharacters[handle]) + { + botimport.Print(PRT_FATAL, "invalid character %d\n", handle); + return NULL; + } //end if + return botcharacters[handle]; +} //end of the function BotCharacterFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpCharacter(bot_character_t *ch) +{ + int i; + + Log_Write("%s", ch->filename); + Log_Write("skill %d\n", ch->skill); + Log_Write("{\n"); + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + switch(ch->c[i].type) + { + case CT_INTEGER: Log_Write(" %4d %d\n", i, ch->c[i].value.integer); break; + case CT_FLOAT: Log_Write(" %4d %f\n", i, ch->c[i].value._float); break; + case CT_STRING: Log_Write(" %4d %s\n", i, ch->c[i].value.string); break; + } //end case + } //end for + Log_Write("}\n"); +} //end of the function BotDumpCharacter +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacterStrings(bot_character_t *ch) +{ + int i; + + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + if (ch->c[i].type == CT_STRING) + { + FreeMemory(ch->c[i].value.string); + } //end if + } //end for +} //end of the function BotFreeCharacterStrings +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacter2(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); + return; + } //end if + if (!botcharacters[handle]) + { + botimport.Print(PRT_FATAL, "invalid character %d\n", handle); + return; + } //end if + BotFreeCharacterStrings(botcharacters[handle]); + FreeMemory(botcharacters[handle]); + botcharacters[handle] = NULL; +} //end of the function BotFreeCharacter2 +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacter(int handle) +{ + if (!LibVarGetValue("bot_reloadcharacters")) return; + BotFreeCharacter2(handle); +} //end of the function BotFreeCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDefaultCharacteristics(bot_character_t *ch, bot_character_t *defaultch) +{ + int i; + + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + if (ch->c[i].type) continue; + // + if (defaultch->c[i].type == CT_FLOAT) + { + ch->c[i].type = CT_FLOAT; + ch->c[i].value._float = defaultch->c[i].value._float; + } //end if + else if (defaultch->c[i].type == CT_INTEGER) + { + ch->c[i].type = CT_INTEGER; + ch->c[i].value.integer = defaultch->c[i].value.integer; + } //end else if + else if (defaultch->c[i].type == CT_STRING) + { + ch->c[i].type = CT_STRING; + ch->c[i].value.string = (char *) GetMemory(strlen(defaultch->c[i].value.string)+1); + strcpy(ch->c[i].value.string, defaultch->c[i].value.string); + } //end else if + } //end for +} //end of the function BotDefaultCharacteristics +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_character_t *BotLoadCharacterFromFile(char *charfile, int skill) +{ + int indent, index, foundcharacter; + bot_character_t *ch; + source_t *source; + token_t token; + + foundcharacter = qfalse; + //a bot character is parsed in two phases + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(charfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", charfile); + return NULL; + } //end if + ch = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); + strcpy(ch->filename, charfile); + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "skill")) + { + if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + //if it's the correct skill + if (skill < 0 || token.intvalue == skill) + { + foundcharacter = qtrue; + ch->skill = token.intvalue; + while(PC_ExpectAnyToken(source, &token)) + { + if (!strcmp(token.string, "}")) break; + if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) + { + SourceError(source, "expected integer index, found %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + index = token.intvalue; + if (index < 0 || index > MAX_CHARACTERISTICS) + { + SourceError(source, "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (ch->c[index].type) + { + SourceError(source, "characteristic %d already initialized\n", index); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (token.type == TT_NUMBER) + { + if (token.subtype & TT_FLOAT) + { + ch->c[index].value._float = token.floatvalue; + ch->c[index].type = CT_FLOAT; + } //end if + else + { + ch->c[index].value.integer = token.intvalue; + ch->c[index].type = CT_INTEGER; + } //end else + } //end if + else if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + ch->c[index].value.string = (char *)GetMemory(strlen(token.string)+1); + strcpy(ch->c[index].value.string, token.string); + ch->c[index].type = CT_STRING; + } //end else if + else + { + SourceError(source, "expected integer, float or string, found %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end else + } //end if + break; + } //end if + else + { + indent = 1; + while(indent) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!strcmp(token.string, "{")) indent++; + else if (!strcmp(token.string, "}")) indent--; + } //end while + } //end else + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end else + } //end while + FreeSource(source); + // + if (!foundcharacter) + { + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + return ch; +} //end of the function BotLoadCharacterFromFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFindCachedCharacter(char *charfile, float skill) +{ + int handle; + + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if ( !botcharacters[handle] ) continue; + if ( strcmp( botcharacters[handle]->filename, charfile ) == 0 && + (skill < 0 || fabs(botcharacters[handle]->skill - skill) < 0.01) ) + { + return handle; + } //end if + } //end for + return 0; +} //end of the function BotFindCachedCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCachedCharacter(char *charfile, float skill, int reload) +{ + int handle, cachedhandle, intskill; + bot_character_t *ch = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + + //find a free spot for a character + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (!botcharacters[handle]) break; + } //end for + if (handle > MAX_CLIENTS) return 0; + //try to load a cached character with the given skill + if (!reload) + { + cachedhandle = BotFindCachedCharacter(charfile, skill); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); + return cachedhandle; + } //end if + } //end else + // + intskill = (int) (skill + 0.5); + //try to load the character with the given skill + ch = BotLoadCharacterFromFile(charfile, intskill); + if (ch) + { + botcharacters[handle] = ch; + // + botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", intskill, charfile); +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", intskill, Sys_MilliSeconds() - starttime, charfile); + } //end if +#endif //DEBUG + return handle; + } //end if + // + botimport.Print(PRT_WARNING, "couldn't find skill %d in %s\n", intskill, charfile); + // + if (!reload) + { + //try to load a cached default character with the given skill + cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, skill); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", intskill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load the default character with the given skill + ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, intskill); + if (ch) + { + botcharacters[handle] = ch; + botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", intskill, charfile); + return handle; + } //end if + // + if (!reload) + { + //try to load a cached character with any skill + cachedhandle = BotFindCachedCharacter(charfile, -1); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load a character with any skill + ch = BotLoadCharacterFromFile(charfile, -1); + if (ch) + { + botcharacters[handle] = ch; + botimport.Print(PRT_MESSAGE, "loaded skill %f from %s\n", ch->skill, charfile); + return handle; + } //end if + // + if (!reload) + { + //try to load a cached character with any skill + cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, -1); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached default skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load a character with any skill + ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, -1); + if (ch) + { + botcharacters[handle] = ch; + botimport.Print(PRT_MESSAGE, "loaded default skill %f from %s\n", ch->skill, charfile); + return handle; + } //end if + // + botimport.Print(PRT_WARNING, "couldn't load any skill from %s\n", charfile); + //couldn't load any character + return 0; +} //end of the function BotLoadCachedCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCharacterSkill(char *charfile, float skill) +{ + int ch, defaultch; + + defaultch = BotLoadCachedCharacter(DEFAULT_CHARACTER, skill, qfalse); + ch = BotLoadCachedCharacter(charfile, skill, LibVarGetValue("bot_reloadcharacters")); + + if (defaultch && ch) + { + BotDefaultCharacteristics(botcharacters[ch], botcharacters[defaultch]); + } //end if + + return ch; +} //end of the function BotLoadCharacterSkill +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotInterpolateCharacters(int handle1, int handle2, float desiredskill) +{ + bot_character_t *ch1, *ch2, *out; + int i, handle; + float scale; + + ch1 = BotCharacterFromHandle(handle1); + ch2 = BotCharacterFromHandle(handle2); + if (!ch1 || !ch2) + return 0; + //find a free spot for a character + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (!botcharacters[handle]) break; + } //end for + if (handle > MAX_CLIENTS) return 0; + out = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); + out->skill = desiredskill; + strcpy(out->filename, ch1->filename); + botcharacters[handle] = out; + + scale = (float) (desiredskill - ch1->skill) / (ch2->skill - ch1->skill); + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + // + if (ch1->c[i].type == CT_FLOAT && ch2->c[i].type == CT_FLOAT) + { + out->c[i].type = CT_FLOAT; + out->c[i].value._float = ch1->c[i].value._float + + (ch2->c[i].value._float - ch1->c[i].value._float) * scale; + } //end if + else if (ch1->c[i].type == CT_INTEGER) + { + out->c[i].type = CT_INTEGER; + out->c[i].value.integer = ch1->c[i].value.integer; + } //end else if + else if (ch1->c[i].type == CT_STRING) + { + out->c[i].type = CT_STRING; + out->c[i].value.string = (char *) GetMemory(strlen(ch1->c[i].value.string)+1); + strcpy(out->c[i].value.string, ch1->c[i].value.string); + } //end else if + } //end for + return handle; +} //end of the function BotInterpolateCharacters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCharacter(char *charfile, float skill) +{ + int firstskill, secondskill, handle; + + //make sure the skill is in the valid range + if (skill < 1.0) skill = 1.0; + else if (skill > 5.0) skill = 5.0; + //skill 1, 4 and 5 should be available in the character files + if (skill == 1.0 || skill == 4.0 || skill == 5.0) + { + return BotLoadCharacterSkill(charfile, skill); + } //end if + //check if there's a cached skill + handle = BotFindCachedCharacter(charfile, skill); + if (handle) + { + botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); + return handle; + } //end if + if (skill < 4.0) + { + //load skill 1 and 4 + firstskill = BotLoadCharacterSkill(charfile, 1); + if (!firstskill) return 0; + secondskill = BotLoadCharacterSkill(charfile, 4); + if (!secondskill) return firstskill; + } //end if + else + { + //load skill 4 and 5 + firstskill = BotLoadCharacterSkill(charfile, 4); + if (!firstskill) return 0; + secondskill = BotLoadCharacterSkill(charfile, 5); + if (!secondskill) return firstskill; + } //end else + //interpolate between the two skills + handle = BotInterpolateCharacters(firstskill, secondskill, skill); + if (!handle) return 0; + //write the character to the log file + BotDumpCharacter(botcharacters[handle]); + // + return handle; +} //end of the function BotLoadCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int CheckCharacteristicIndex(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return qfalse; + if (index < 0 || index >= MAX_CHARACTERISTICS) + { + botimport.Print(PRT_ERROR, "characteristic %d does not exist\n", index); + return qfalse; + } //end if + if (!ch->c[index].type) + { + botimport.Print(PRT_ERROR, "characteristic %d is not initialized\n", index); + return qfalse; + } //end if + return qtrue; +} //end of the function CheckCharacteristicIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Characteristic_Float(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) return 0; + //an integer will be converted to a float + if (ch->c[index].type == CT_INTEGER) + { + return (float) ch->c[index].value.integer; + } //end if + //floats are just returned + else if (ch->c[index].type == CT_FLOAT) + { + return ch->c[index].value._float; + } //end else if + //cannot convert a string pointer to a float + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a float\n", index); + return 0; + } //end else if +// return 0; +} //end of the function Characteristic_Float +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Characteristic_BFloat(int character, int index, float min, float max) +{ + float value; + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + if (min > max) + { + botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %f and %f\n", index, min, max); + return 0; + } //end if + value = Characteristic_Float(character, index); + if (value < min) return min; + if (value > max) return max; + return value; +} //end of the function Characteristic_BFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Characteristic_Integer(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) return 0; + //an integer will just be returned + if (ch->c[index].type == CT_INTEGER) + { + return ch->c[index].value.integer; + } //end if + //floats are casted to integers + else if (ch->c[index].type == CT_FLOAT) + { + return (int) ch->c[index].value._float; + } //end else if + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a integer\n", index); + return 0; + } //end else if +// return 0; +} //end of the function Characteristic_Integer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Characteristic_BInteger(int character, int index, int min, int max) +{ + int value; + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + if (min > max) + { + botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max); + return 0; + } //end if + value = Characteristic_Integer(character, index); + if (value < min) return min; + if (value > max) return max; + return value; +} //end of the function Characteristic_BInteger +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Characteristic_String(int character, int index, char *buf, int size) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return; + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) return; + //an integer will be converted to a float + if (ch->c[index].type == CT_STRING) + { + strncpy(buf, ch->c[index].value.string, size-1); + buf[size-1] = '\0'; + return; + } //end if + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a string\n", index); + return; + } //end else if + return; +} //end of the function Characteristic_String +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownCharacters(void) +{ + int handle; + + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (botcharacters[handle]) + { + BotFreeCharacter2(handle); + } //end if + } //end for +} //end of the function BotShutdownCharacters + diff --git a/codemp/botlib/be_ai_chat.cpp b/codemp/botlib/be_ai_chat.cpp new file mode 100644 index 0000000..7a9a967 --- /dev/null +++ b/codemp/botlib/be_ai_chat.cpp @@ -0,0 +1,3000 @@ + +/***************************************************************************** + * name: be_ai_chat.c + * + * desc: bot chat AI + * + * $Archive: /MissionPack/code/botlib/be_ai_chat.c $ + * $Author: Ttimo $ + * $Revision: 12 $ + * $Modtime: 4/13/01 4:45p $ + * $Date: 4/13/01 4:45p $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "l_log.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ea.h" +#include "../game/be_ai_chat.h" + + +//escape character +#define ESCAPE_CHAR 0x01 //'_' +// +// "hi ", people, " ", 0, " entered the game" +//becomes: +// "hi _rpeople_ _v0_ entered the game" +// + +//match piece types +#define MT_VARIABLE 1 //variable match piece +#define MT_STRING 2 //string match piece +//reply chat key flags +#define RCKFL_AND 1 //key must be present +#define RCKFL_NOT 2 //key must be absent +#define RCKFL_NAME 4 //name of bot must be present +#define RCKFL_STRING 8 //key is a string +#define RCKFL_VARIABLES 16 //key is a match template +#define RCKFL_BOTNAMES 32 //key is a series of botnames +#define RCKFL_GENDERFEMALE 64 //bot must be female +#define RCKFL_GENDERMALE 128 //bot must be male +#define RCKFL_GENDERLESS 256 //bot must be genderless +//time to ignore a chat message after using it +#define CHATMESSAGE_RECENTTIME 20 + +//the actuall chat messages +typedef struct bot_chatmessage_s +{ + char *chatmessage; //chat message string + float time; //last time used + struct bot_chatmessage_s *next; //next chat message in a list +} bot_chatmessage_t; +//bot chat type with chat lines +typedef struct bot_chattype_s +{ + char name[MAX_CHATTYPE_NAME]; + int numchatmessages; + bot_chatmessage_t *firstchatmessage; + struct bot_chattype_s *next; +} bot_chattype_t; +//bot chat lines +typedef struct bot_chat_s +{ + bot_chattype_t *types; +} bot_chat_t; + +//random string +typedef struct bot_randomstring_s +{ + char *string; + struct bot_randomstring_s *next; +} bot_randomstring_t; +//list with random strings +typedef struct bot_randomlist_s +{ + char *string; + int numstrings; + bot_randomstring_t *firstrandomstring; + struct bot_randomlist_s *next; +} bot_randomlist_t; + +//synonym +typedef struct bot_synonym_s +{ + char *string; + float weight; + struct bot_synonym_s *next; +} bot_synonym_t; +//list with synonyms +typedef struct bot_synonymlist_s +{ + unsigned long int context; + float totalweight; + bot_synonym_t *firstsynonym; + struct bot_synonymlist_s *next; +} bot_synonymlist_t; + +//fixed match string +typedef struct bot_matchstring_s +{ + char *string; + struct bot_matchstring_s *next; +} bot_matchstring_t; + +//piece of a match template +typedef struct bot_matchpiece_s +{ + int type; + bot_matchstring_t *firststring; + int variable; + struct bot_matchpiece_s *next; +} bot_matchpiece_t; +//match template +typedef struct bot_matchtemplate_s +{ + unsigned long int context; + int type; + int subtype; + bot_matchpiece_t *first; + struct bot_matchtemplate_s *next; +} bot_matchtemplate_t; + +//reply chat key +typedef struct bot_replychatkey_s +{ + int flags; + char *string; + bot_matchpiece_t *match; + struct bot_replychatkey_s *next; +} bot_replychatkey_t; +//reply chat +typedef struct bot_replychat_s +{ + bot_replychatkey_t *keys; + float priority; + int numchatmessages; + bot_chatmessage_t *firstchatmessage; + struct bot_replychat_s *next; +} bot_replychat_t; + +//string list +typedef struct bot_stringlist_s +{ + char *string; + struct bot_stringlist_s *next; +} bot_stringlist_t; + +//chat state of a bot +typedef struct bot_chatstate_s +{ + int gender; //0=it, 1=female, 2=male + int client; //client number + char name[32]; //name of the bot + char chatmessage[MAX_MESSAGE_SIZE]; + int handle; + //the console messages visible to the bot + bot_consolemessage_t *firstmessage; //first message is the first typed message + bot_consolemessage_t *lastmessage; //last message is the last typed message, bottom of console + //number of console messages stored in the state + int numconsolemessages; + //the bot chat lines + bot_chat_t *chat; +} bot_chatstate_t; + +typedef struct { + bot_chat_t *chat; + char filename[MAX_QPATH]; + char chatname[MAX_QPATH]; +} bot_ichatdata_t; + +bot_ichatdata_t *ichatdata[MAX_CLIENTS]; + +bot_chatstate_t *botchatstates[MAX_CLIENTS+1]; +//console message heap +bot_consolemessage_t *consolemessageheap = NULL; +bot_consolemessage_t *freeconsolemessages = NULL; +//list with match strings +bot_matchtemplate_t *matchtemplates = NULL; +//list with synonyms +bot_synonymlist_t *synonyms = NULL; +//list with random strings +bot_randomlist_t *randomstrings = NULL; +//reply chats +bot_replychat_t *replychats = NULL; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_chatstate_t *BotChatStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botchatstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); + return NULL; + } //end if + return botchatstates[handle]; +} //end of the function BotChatStateFromHandle +//=========================================================================== +// initialize the heap with unused console messages +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InitConsoleMessageHeap(void) +{ + int i, max_messages; + + if (consolemessageheap) FreeMemory(consolemessageheap); + // + max_messages = (int) LibVarValue("max_messages", "1024"); + consolemessageheap = (bot_consolemessage_t *) GetClearedHunkMemory(max_messages * + sizeof(bot_consolemessage_t)); + consolemessageheap[0].prev = NULL; + consolemessageheap[0].next = &consolemessageheap[1]; + for (i = 1; i < max_messages-1; i++) + { + consolemessageheap[i].prev = &consolemessageheap[i - 1]; + consolemessageheap[i].next = &consolemessageheap[i + 1]; + } //end for + consolemessageheap[max_messages-1].prev = &consolemessageheap[max_messages-2]; + consolemessageheap[max_messages-1].next = NULL; + //pointer to the free console messages + freeconsolemessages = consolemessageheap; +} //end of the function InitConsoleMessageHeap +//=========================================================================== +// allocate one console message from the heap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_consolemessage_t *AllocConsoleMessage(void) +{ + bot_consolemessage_t *message; + message = freeconsolemessages; + if (freeconsolemessages) freeconsolemessages = freeconsolemessages->next; + if (freeconsolemessages) freeconsolemessages->prev = NULL; + return message; +} //end of the function AllocConsoleMessage +//=========================================================================== +// deallocate one console message from the heap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeConsoleMessage(bot_consolemessage_t *message) +{ + if (freeconsolemessages) freeconsolemessages->prev = message; + message->prev = NULL; + message->next = freeconsolemessages; + freeconsolemessages = message; +} //end of the function FreeConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveConsoleMessage(int chatstate, int handle) +{ + bot_consolemessage_t *m, *nextm; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + for (m = cs->firstmessage; m; m = nextm) + { + nextm = m->next; + if (m->handle == handle) + { + if (m->next) m->next->prev = m->prev; + else cs->lastmessage = m->prev; + if (m->prev) m->prev->next = m->next; + else cs->firstmessage = m->next; + + FreeConsoleMessage(m); + cs->numconsolemessages--; + break; + } //end if + } //end for +} //end of the function BotRemoveConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotQueueConsoleMessage(int chatstate, int type, char *message) +{ + bot_consolemessage_t *m; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + m = AllocConsoleMessage(); + if (!m) + { + botimport.Print(PRT_ERROR, "empty console message heap\n"); + return; + } //end if + cs->handle++; + if (cs->handle <= 0 || cs->handle > 8192) cs->handle = 1; + m->handle = cs->handle; + m->time = AAS_Time(); + m->type = type; + strncpy(m->message, message, MAX_MESSAGE_SIZE); + m->next = NULL; + if (cs->lastmessage) + { + cs->lastmessage->next = m; + m->prev = cs->lastmessage; + cs->lastmessage = m; + } //end if + else + { + cs->lastmessage = m; + cs->firstmessage = m; + m->prev = NULL; + } //end if + cs->numconsolemessages++; +} //end of the function BotQueueConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + if (cs->firstmessage) + { + Com_Memcpy(cm, cs->firstmessage, sizeof(bot_consolemessage_t)); + cm->next = cm->prev = NULL; + return cm->handle; + } //end if + return 0; +} //end of the function BotConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNumConsoleMessages(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + return cs->numconsolemessages; +} //end of the function BotNumConsoleMessages +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int IsWhiteSpace(char c) +{ + if ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '(' || c == ')' + || c == '?' || c == ':' + || c == '\''|| c == '/' + || c == ',' || c == '.' + || c == '[' || c == ']' + || c == '-' || c == '_' + || c == '+' || c == '=') return qfalse; + return qtrue; +} //end of the function IsWhiteSpace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveTildes(char *message) +{ + int i; + + //remove all tildes from the chat message + for (i = 0; message[i]; i++) + { + if (message[i] == '~') + { + memmove(&message[i], &message[i+1], strlen(&message[i+1])+1); + } //end if + } //end for +} //end of the function BotRemoveTildes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnifyWhiteSpaces(char *string) +{ + char *ptr, *oldptr; + + for (ptr = oldptr = string; *ptr; oldptr = ptr) + { + while(*ptr && IsWhiteSpace(*ptr)) ptr++; + if (ptr > oldptr) + { + //if not at the start and not at the end of the string + //write only one space + if (oldptr > string && *ptr) *oldptr++ = ' '; + //remove all other white spaces + if (ptr > oldptr) memmove(oldptr, ptr, strlen(ptr)+1); + } //end if + while(*ptr && !IsWhiteSpace(*ptr)) ptr++; + } //end while +} //end of the function UnifyWhiteSpaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int StringContains(char *str1, char *str2, int casesensitive) +{ + int len, i, j, index; + + if (str1 == NULL || str2 == NULL) return -1; + + len = strlen(str1) - strlen(str2); + index = 0; + for (i = 0; i <= len; i++, str1++, index++) + { + for (j = 0; str2[j]; j++) + { + if (casesensitive) + { + if (str1[j] != str2[j]) break; + } //end if + else + { + if (toupper(str1[j]) != toupper(str2[j])) break; + } //end else + } //end for + if (!str2[j]) return index; + } //end for + return -1; +} //end of the function StringContains +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *StringContainsWord(char *str1, char *str2, int casesensitive) +{ + int len, i, j; + + len = strlen(str1) - strlen(str2); + for (i = 0; i <= len; i++, str1++) + { + //if not at the start of the string + if (i) + { + //skip to the start of the next word + while(*str1 && *str1 != ' ' && *str1 != '.' && *str1 != ',' && *str1 != '!') str1++; + if (!*str1) break; + str1++; + } //end for + //compare the word + for (j = 0; str2[j]; j++) + { + if (casesensitive) + { + if (str1[j] != str2[j]) break; + } //end if + else + { + if (toupper(str1[j]) != toupper(str2[j])) break; + } //end else + } //end for + //if there was a word match + if (!str2[j]) + { + //if the first string has an end of word + if (!str1[j] || str1[j] == ' ' || str1[j] == '.' || str1[j] == ',' || str1[j] == '!') return str1; + } //end if + } //end for + return NULL; +} //end of the function StringContainsWord +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void StringReplaceWords(char *string, char *synonym, char *replacement) +{ + char *str, *str2; + + //find the synonym in the string + str = StringContainsWord(string, synonym, qfalse); + //if the synonym occured in the string + while(str) + { + //if the synonym isn't part of the replacement which is already in the string + //usefull for abreviations + str2 = StringContainsWord(string, replacement, qfalse); + while(str2) + { + if (str2 <= str && str < str2 + strlen(replacement)) break; + str2 = StringContainsWord(str2+1, replacement, qfalse); + } //end while + if (!str2) + { + memmove(str + strlen(replacement), str+strlen(synonym), strlen(str+strlen(synonym))+1); + //append the synonum replacement + Com_Memcpy(str, replacement, strlen(replacement)); + } //end if + //find the next synonym in the string + str = StringContainsWord(str+strlen(replacement), synonym, qfalse); + } //end if +} //end of the function StringReplaceWords +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpSynonymList(bot_synonymlist_t *synlist) +{ + FILE *fp; + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + fp = Log_FilePointer(); + if (!fp) return; + for (syn = synlist; syn; syn = syn->next) + { + fprintf(fp, "%ld : [", syn->context); + for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) + { + fprintf(fp, "(\"%s\", %1.2f)", synonym->string, synonym->weight); + if (synonym->next) fprintf(fp, ", "); + } //end for + fprintf(fp, "]\n"); + } //end for +} //end of the function BotDumpSynonymList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_synonymlist_t *BotLoadSynonyms(char *filename) +{ + int pass, size, contextlevel, numsynonyms; + unsigned long int context, contextstack[32]; + char *ptr = NULL; + source_t *source; + token_t token; + bot_synonymlist_t *synlist, *lastsyn, *syn; + bot_synonym_t *synonym, *lastsynonym; + + size = 0; + synlist = NULL; //make compiler happy + syn = NULL; //make compiler happy + synonym = NULL; //make compiler happy + //the synonyms are parsed in two phases + for (pass = 0; pass < 2; pass++) + { + // + if (pass && size) ptr = (char *) GetClearedHunkMemory(size); + // + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + context = 0; + contextlevel = 0; + synlist = NULL; //list synonyms + lastsyn = NULL; //last synonym in the list + // + while(PC_ReadToken(source, &token)) + { + if (token.type == TT_NUMBER) + { + context |= token.intvalue; + contextstack[contextlevel] = token.intvalue; + contextlevel++; + if (contextlevel >= 32) + { + SourceError(source, "more than 32 context levels"); + FreeSource(source); + return NULL; + } //end if + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + } //end if + else if (token.type == TT_PUNCTUATION) + { + if (!strcmp(token.string, "}")) + { + contextlevel--; + if (contextlevel < 0) + { + SourceError(source, "too many }"); + FreeSource(source); + return NULL; + } //end if + context &= ~contextstack[contextlevel]; + } //end if + else if (!strcmp(token.string, "[")) + { + size += sizeof(bot_synonymlist_t); + if (pass) + { + syn = (bot_synonymlist_t *) ptr; + ptr += sizeof(bot_synonymlist_t); + syn->context = context; + syn->firstsynonym = NULL; + syn->next = NULL; + if (lastsyn) lastsyn->next = syn; + else synlist = syn; + lastsyn = syn; + } //end if + numsynonyms = 0; + lastsynonym = NULL; + while(1) + { + if (!PC_ExpectTokenString(source, "(") || + !PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (strlen(token.string) <= 0) + { + SourceError(source, "empty string", token.string); + FreeSource(source); + return NULL; + } //end if + size += sizeof(bot_synonym_t) + strlen(token.string) + 1; + if (pass) + { + synonym = (bot_synonym_t *) ptr; + ptr += sizeof(bot_synonym_t); + synonym->string = ptr; + ptr += strlen(token.string) + 1; + strcpy(synonym->string, token.string); + // + if (lastsynonym) lastsynonym->next = synonym; + else syn->firstsynonym = synonym; + lastsynonym = synonym; + } //end if + numsynonyms++; + if (!PC_ExpectTokenString(source, ",") || + !PC_ExpectTokenType(source, TT_NUMBER, 0, &token) || + !PC_ExpectTokenString(source, ")")) + { + FreeSource(source); + return NULL; + } //end if + if (pass) + { + synonym->weight = token.floatvalue; + syn->totalweight += synonym->weight; + } //end if + if (PC_CheckTokenString(source, "]")) break; + if (!PC_ExpectTokenString(source, ",")) + { + FreeSource(source); + return NULL; + } //end if + } //end while + if (numsynonyms < 2) + { + SourceError(source, "synonym must have at least two entries\n"); + FreeSource(source); + return NULL; + } //end if + } //end else + else + { + SourceError(source, "unexpected %s", token.string); + FreeSource(source); + return NULL; + } //end if + } //end else if + } //end while + // + FreeSource(source); + // + if (contextlevel > 0) + { + SourceError(source, "missing }"); + return NULL; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // + //BotDumpSynonymList(synlist); + // + return synlist; +} //end of the function BotLoadSynonyms +//=========================================================================== +// replace all the synonyms in the string +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceSynonyms(char *string, unsigned long int context) +{ + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) continue; + for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) + { + StringReplaceWords(string, synonym->string, syn->firstsynonym->string); + } //end for + } //end for +} //end of the function BotReplaceSynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceWeightedSynonyms(char *string, unsigned long int context) +{ + bot_synonymlist_t *syn; + bot_synonym_t *synonym, *replacement; + float weight, curweight; + + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) continue; + //choose a weighted random replacement synonym + weight = random() * syn->totalweight; + if (!weight) continue; + curweight = 0; + for (replacement = syn->firstsynonym; replacement; replacement = replacement->next) + { + curweight += replacement->weight; + if (weight < curweight) break; + } //end for + if (!replacement) continue; + //replace all synonyms with the replacement + for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) + { + if (synonym == replacement) continue; + StringReplaceWords(string, synonym->string, replacement->string); + } //end for + } //end for +} //end of the function BotReplaceWeightedSynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceReplySynonyms(char *string, unsigned long int context) +{ + char *str1, *str2, *replacement; + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + for (str1 = string; *str1; ) + { + //go to the start of the next word + while(*str1 && *str1 <= ' ') str1++; + if (!*str1) break; + // + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) continue; + for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) + { + str2 = synonym->string; + //if the synonym is not at the front of the string continue + str2 = StringContainsWord(str1, synonym->string, qfalse); + if (!str2 || str2 != str1) continue; + // + replacement = syn->firstsynonym->string; + //if the replacement IS in front of the string continue + str2 = StringContainsWord(str1, replacement, qfalse); + if (str2 && str2 == str1) continue; + // + memmove(str1 + strlen(replacement), str1+strlen(synonym->string), + strlen(str1+strlen(synonym->string)) + 1); + //append the synonum replacement + Com_Memcpy(str1, replacement, strlen(replacement)); + // + break; + } //end for + //if a synonym has been replaced + if (synonym) break; + } //end for + //skip over this word + while(*str1 && *str1 > ' ') str1++; + if (!*str1) break; + } //end while +} //end of the function BotReplaceReplySynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadChatMessage(source_t *source, char *chatmessagestring) +{ + char *ptr; + token_t token; + + ptr = chatmessagestring; + *ptr = 0; + // + while(1) + { + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + //fixed string + if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + if (strlen(ptr) + strlen(token.string) + 1 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + strcat(ptr, token.string); + } //end else if + //variable string + else if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) + { + if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + sprintf(&ptr[strlen(ptr)], "%cv%ld%c", ESCAPE_CHAR, token.intvalue, ESCAPE_CHAR); + } //end if + //random string + else if (token.type == TT_NAME) + { + if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + sprintf(&ptr[strlen(ptr)], "%cr%s%c", ESCAPE_CHAR, token.string, ESCAPE_CHAR); + } //end else if + else + { + SourceError(source, "unknown message component %s\n", token.string); + return qfalse; + } //end else + if (PC_CheckTokenString(source, ";")) break; + if (!PC_ExpectTokenString(source, ",")) return qfalse; + } //end while + // + return qtrue; +} //end of the function BotLoadChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpRandomStringList(bot_randomlist_t *randomlist) +{ + FILE *fp; + bot_randomlist_t *random; + bot_randomstring_t *rs; + + fp = Log_FilePointer(); + if (!fp) return; + for (random = randomlist; random; random = random->next) + { + fprintf(fp, "%s = {", random->string); + for (rs = random->firstrandomstring; rs; rs = rs->next) + { + fprintf(fp, "\"%s\"", rs->string); + if (rs->next) fprintf(fp, ", "); + else fprintf(fp, "}\n"); + } //end for + } //end for +} //end of the function BotDumpRandomStringList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_randomlist_t *BotLoadRandomStrings(char *filename) +{ + int pass, size; + char *ptr = NULL, chatmessagestring[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_randomlist_t *randomlist, *lastrandom, *random; + bot_randomstring_t *randomstring; + +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif //DEBUG + + size = 0; + randomlist = NULL; + random = NULL; + //the synonyms are parsed in two phases + for (pass = 0; pass < 2; pass++) + { + // + if (pass && size) ptr = (char *) GetClearedHunkMemory(size); + // + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + randomlist = NULL; //list + lastrandom = NULL; //last + // + while(PC_ReadToken(source, &token)) + { + if (token.type != TT_NAME) + { + SourceError(source, "unknown random %s", token.string); + FreeSource(source); + return NULL; + } //end if + size += sizeof(bot_randomlist_t) + strlen(token.string) + 1; + if (pass) + { + random = (bot_randomlist_t *) ptr; + ptr += sizeof(bot_randomlist_t); + random->string = ptr; + ptr += strlen(token.string) + 1; + strcpy(random->string, token.string); + random->firstrandomstring = NULL; + random->numstrings = 0; + // + if (lastrandom) lastrandom->next = random; + else randomlist = random; + lastrandom = random; + } //end if + if (!PC_ExpectTokenString(source, "=") || + !PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + while(!PC_CheckTokenString(source, "}")) + { + if (!BotLoadChatMessage(source, chatmessagestring)) + { + FreeSource(source); + return NULL; + } //end if + size += sizeof(bot_randomstring_t) + strlen(chatmessagestring) + 1; + if (pass) + { + randomstring = (bot_randomstring_t *) ptr; + ptr += sizeof(bot_randomstring_t); + randomstring->string = ptr; + ptr += strlen(chatmessagestring) + 1; + strcpy(randomstring->string, chatmessagestring); + // + random->numstrings++; + randomstring->next = random->firstrandomstring; + random->firstrandomstring = randomstring; + } //end if + } //end while + } //end while + //free the source after one pass + FreeSource(source); + } //end for + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "random strings %d msec\n", Sys_MilliSeconds() - starttime); + //BotDumpRandomStringList(randomlist); +#endif //DEBUG + // + return randomlist; +} //end of the function BotLoadRandomStrings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *RandomString(char *name) +{ + bot_randomlist_t *random; + bot_randomstring_t *rs; + int i; + + for (random = randomstrings; random; random = random->next) + { + if (!strcmp(random->string, name)) + { + i = random() * random->numstrings; + for (rs = random->firstrandomstring; rs; rs = rs->next) + { + if (--i < 0) break; + } //end for + if (rs) + { + return rs->string; + } //end if + } //end for + } //end for + return NULL; +} //end of the function RandomString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpMatchTemplates(bot_matchtemplate_t *matches) +{ + FILE *fp; + bot_matchtemplate_t *mt; + bot_matchpiece_t *mp; + bot_matchstring_t *ms; + + fp = Log_FilePointer(); + if (!fp) return; + for (mt = matches; mt; mt = mt->next) + { + fprintf(fp, "{ " ); + for (mp = mt->first; mp; mp = mp->next) + { + if (mp->type == MT_STRING) + { + for (ms = mp->firststring; ms; ms = ms->next) + { + fprintf(fp, "\"%s\"", ms->string); + if (ms->next) fprintf(fp, "|"); + } //end for + } //end if + else if (mp->type == MT_VARIABLE) + { + fprintf(fp, "%d", mp->variable); + } //end else if + if (mp->next) fprintf(fp, ", "); + } //end for + fprintf(fp, " = (%d, %d);}\n", mt->type, mt->subtype); + } //end for +} //end of the function BotDumpMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeMatchPieces(bot_matchpiece_t *matchpieces) +{ + bot_matchpiece_t *mp, *nextmp; + bot_matchstring_t *ms, *nextms; + + for (mp = matchpieces; mp; mp = nextmp) + { + nextmp = mp->next; + if (mp->type == MT_STRING) + { + for (ms = mp->firststring; ms; ms = nextms) + { + nextms = ms->next; + FreeMemory(ms); + } //end for + } //end if + FreeMemory(mp); + } //end for +} //end of the function BotFreeMatchPieces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_matchpiece_t *BotLoadMatchPieces(source_t *source, char *endtoken) +{ + int lastwasvariable, emptystring; + token_t token; + bot_matchpiece_t *matchpiece, *firstpiece, *lastpiece; + bot_matchstring_t *matchstring, *lastmatchstring; + + firstpiece = NULL; + lastpiece = NULL; + // + lastwasvariable = qfalse; + // + while(PC_ReadToken(source, &token)) + { + if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) + { + if (token.intvalue < 0 || token.intvalue >= MAX_MATCHVARIABLES) + { + SourceError(source, "can't have more than %d match variables\n", MAX_MATCHVARIABLES); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + if (lastwasvariable) + { + SourceError(source, "not allowed to have adjacent variables\n"); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + lastwasvariable = qtrue; + // + matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); + matchpiece->type = MT_VARIABLE; + matchpiece->variable = token.intvalue; + matchpiece->next = NULL; + if (lastpiece) lastpiece->next = matchpiece; + else firstpiece = matchpiece; + lastpiece = matchpiece; + } //end if + else if (token.type == TT_STRING) + { + // + matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); + matchpiece->firststring = NULL; + matchpiece->type = MT_STRING; + matchpiece->variable = 0; + matchpiece->next = NULL; + if (lastpiece) lastpiece->next = matchpiece; + else firstpiece = matchpiece; + lastpiece = matchpiece; + // + lastmatchstring = NULL; + emptystring = qfalse; + // + do + { + if (matchpiece->firststring) + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + } //end if + StripDoubleQuotes(token.string); + matchstring = (bot_matchstring_t *) GetClearedHunkMemory(sizeof(bot_matchstring_t) + strlen(token.string) + 1); + matchstring->string = (char *) matchstring + sizeof(bot_matchstring_t); + strcpy(matchstring->string, token.string); + if (!strlen(token.string)) emptystring = qtrue; + matchstring->next = NULL; + if (lastmatchstring) lastmatchstring->next = matchstring; + else matchpiece->firststring = matchstring; + lastmatchstring = matchstring; + } while(PC_CheckTokenString(source, "|")); + //if there was no empty string found + if (!emptystring) lastwasvariable = qfalse; + } //end if + else + { + SourceError(source, "invalid token %s\n", token.string); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end else + if (PC_CheckTokenString(source, endtoken)) break; + if (!PC_ExpectTokenString(source, ",")) + { + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + } //end while + return firstpiece; +} //end of the function BotLoadMatchPieces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeMatchTemplates(bot_matchtemplate_t *mt) +{ + bot_matchtemplate_t *nextmt; + + for (; mt; mt = nextmt) + { + nextmt = mt->next; + BotFreeMatchPieces(mt->first); + FreeMemory(mt); + } //end for +} //end of the function BotFreeMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_matchtemplate_t *BotLoadMatchTemplates(char *matchfile) +{ + source_t *source; + token_t token; + bot_matchtemplate_t *matchtemplate, *matches, *lastmatch; + unsigned long int context; + + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(matchfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", matchfile); + return NULL; + } //end if + // + matches = NULL; //list with matches + lastmatch = NULL; //last match in the list + + while(PC_ReadToken(source, &token)) + { + if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) + { + SourceError(source, "expected integer, found %s\n", token.string); + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + //the context + context = token.intvalue; + // + if (!PC_ExpectTokenString(source, "{")) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + // + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "}")) break; + // + PC_UnreadLastToken(source); + // + matchtemplate = (bot_matchtemplate_t *) GetClearedHunkMemory(sizeof(bot_matchtemplate_t)); + matchtemplate->context = context; + matchtemplate->next = NULL; + //add the match template to the list + if (lastmatch) lastmatch->next = matchtemplate; + else matches = matchtemplate; + lastmatch = matchtemplate; + //load the match template + matchtemplate->first = BotLoadMatchPieces(source, "="); + if (!matchtemplate->first) + { + BotFreeMatchTemplates(matches); + return NULL; + } //end if + //read the match type + if (!PC_ExpectTokenString(source, "(") || + !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + matchtemplate->type = token.intvalue; + //read the match subtype + if (!PC_ExpectTokenString(source, ",") || + !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + matchtemplate->subtype = token.intvalue; + //read trailing punctuations + if (!PC_ExpectTokenString(source, ")") || + !PC_ExpectTokenString(source, ";")) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + } //end while + } //end while + //free the source + FreeSource(source); + botimport.Print(PRT_MESSAGE, "loaded %s\n", matchfile); + // + //BotDumpMatchTemplates(matches); + // + return matches; +} //end of the function BotLoadMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int StringsMatch(bot_matchpiece_t *pieces, bot_match_t *match) +{ + int lastvariable, index; + char *strptr, *newstrptr; + bot_matchpiece_t *mp; + bot_matchstring_t *ms; + + //no last variable + lastvariable = -1; + //pointer to the string to compare the match string with + strptr = match->string; + //Log_Write("match: %s", strptr); + //compare the string with the current match string + for (mp = pieces; mp; mp = mp->next) + { + //if it is a piece of string + if (mp->type == MT_STRING) + { + newstrptr = NULL; + for (ms = mp->firststring; ms; ms = ms->next) + { + if (!strlen(ms->string)) + { + newstrptr = strptr; + break; + } //end if + //Log_Write("MT_STRING: %s", mp->string); + index = StringContains(strptr, ms->string, qfalse); + if (index >= 0) + { + newstrptr = strptr + index; + if (lastvariable >= 0) + { + match->variables[lastvariable].length = + (newstrptr - match->string) - match->variables[lastvariable].offset; + //newstrptr - match->variables[lastvariable].ptr; + lastvariable = -1; + break; + } //end if + else if (index == 0) + { + break; + } //end else + newstrptr = NULL; + } //end if + } //end for + if (!newstrptr) return qfalse; + strptr = newstrptr + strlen(ms->string); + } //end if + //if it is a variable piece of string + else if (mp->type == MT_VARIABLE) + { + //Log_Write("MT_VARIABLE"); + match->variables[mp->variable].offset = strptr - match->string; + lastvariable = mp->variable; + } //end else if + } //end for + //if a match was found + if (!mp && (lastvariable >= 0 || !strlen(strptr))) + { + //if the last piece was a variable string + if (lastvariable >= 0) + { + assert( match->variables[lastvariable].offset >= 0 ); // bk001204 + match->variables[lastvariable].length = + strlen(&match->string[ (int) match->variables[lastvariable].offset]); + } //end if + return qtrue; + } //end if + return qfalse; +} //end of the function StringsMatch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFindMatch(char *str, bot_match_t *match, unsigned long int context) +{ + int i; + bot_matchtemplate_t *ms; + + strncpy(match->string, str, MAX_MESSAGE_SIZE); + //remove any trailing enters + while(strlen(match->string) && + match->string[strlen(match->string)-1] == '\n') + { + match->string[strlen(match->string)-1] = '\0'; + } //end while + //compare the string with all the match strings + for (ms = matchtemplates; ms; ms = ms->next) + { + if (!(ms->context & context)) continue; + //reset the match variable offsets + for (i = 0; i < MAX_MATCHVARIABLES; i++) match->variables[i].offset = -1; + // + if (StringsMatch(ms->first, match)) + { + match->type = ms->type; + match->subtype = ms->subtype; + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function BotFindMatch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size) +{ + if (variable < 0 || variable >= MAX_MATCHVARIABLES) + { + botimport.Print(PRT_FATAL, "BotMatchVariable: variable out of range\n"); + strcpy(buf, ""); + return; + } //end if + + if (match->variables[variable].offset >= 0) + { + if (match->variables[variable].length < size) + size = match->variables[variable].length+1; + assert( match->variables[variable].offset >= 0 ); // bk001204 + strncpy(buf, &match->string[ (int) match->variables[variable].offset], size-1); + buf[size-1] = '\0'; + } //end if + else + { + strcpy(buf, ""); + } //end else + return; +} //end of the function BotMatchVariable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_stringlist_t *BotFindStringInList(bot_stringlist_t *list, char *string) +{ + bot_stringlist_t *s; + + for (s = list; s; s = s->next) + { + if (!strcmp(s->string, string)) return s; + } //end for + return NULL; +} //end of the function BotFindStringInList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_stringlist_t *BotCheckChatMessageIntegrety(char *message, bot_stringlist_t *stringlist) +{ + int i; + char *msgptr; + char temp[MAX_MESSAGE_SIZE]; + bot_stringlist_t *s; + + msgptr = message; + // + while(*msgptr) + { + if (*msgptr == ESCAPE_CHAR) + { + msgptr++; + switch(*msgptr) + { + case 'v': //variable + { + //step over the 'v' + msgptr++; + while(*msgptr && *msgptr != ESCAPE_CHAR) msgptr++; + //step over the trailing escape char + if (*msgptr) msgptr++; + break; + } //end case + case 'r': //random + { + //step over the 'r' + msgptr++; + for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) + { + temp[i] = *msgptr++; + } //end while + temp[i] = '\0'; + //step over the trailing escape char + if (*msgptr) msgptr++; + //find the random keyword + if (!RandomString(temp)) + { + if (!BotFindStringInList(stringlist, temp)) + { + Log_Write("%s = {\"%s\"} //MISSING RANDOM\r\n", temp, temp); + s = (struct bot_stringlist_s *)GetClearedMemory(sizeof(bot_stringlist_t) + strlen(temp) + 1); + s->string = (char *) s + sizeof(bot_stringlist_t); + strcpy(s->string, temp); + s->next = stringlist; + stringlist = s; + } //end if + } //end if + break; + } //end case + default: + { + botimport.Print(PRT_FATAL, "BotCheckChatMessageIntegrety: message \"%s\" invalid escape char\n", message); + break; + } //end default + } //end switch + } //end if + else + { + msgptr++; + } //end else + } //end while + return stringlist; +} //end of the function BotCheckChatMessageIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckInitialChatIntegrety(bot_chat_t *chat) +{ + bot_chattype_t *t; + bot_chatmessage_t *cm; + bot_stringlist_t *stringlist, *s, *nexts; + + stringlist = NULL; + for (t = chat->types; t; t = t->next) + { + for (cm = t->firstchatmessage; cm; cm = cm->next) + { + stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); + } //end for + } //end for + for (s = stringlist; s; s = nexts) + { + nexts = s->next; + FreeMemory(s); + } //end for +} //end of the function BotCheckInitialChatIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckReplyChatIntegrety(bot_replychat_t *replychat) +{ + bot_replychat_t *rp; + bot_chatmessage_t *cm; + bot_stringlist_t *stringlist, *s, *nexts; + + stringlist = NULL; + for (rp = replychat; rp; rp = rp->next) + { + for (cm = rp->firstchatmessage; cm; cm = cm->next) + { + stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); + } //end for + } //end for + for (s = stringlist; s; s = nexts) + { + nexts = s->next; + FreeMemory(s); + } //end for +} //end of the function BotCheckReplyChatIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpReplyChat(bot_replychat_t *replychat) +{ + FILE *fp; + bot_replychat_t *rp; + bot_replychatkey_t *key; + bot_chatmessage_t *cm; + bot_matchpiece_t *mp; + + fp = Log_FilePointer(); + if (!fp) return; + fprintf(fp, "BotDumpReplyChat:\n"); + for (rp = replychat; rp; rp = rp->next) + { + fprintf(fp, "["); + for (key = rp->keys; key; key = key->next) + { + if (key->flags & RCKFL_AND) fprintf(fp, "&"); + else if (key->flags & RCKFL_NOT) fprintf(fp, "!"); + // + if (key->flags & RCKFL_NAME) fprintf(fp, "name"); + else if (key->flags & RCKFL_GENDERFEMALE) fprintf(fp, "female"); + else if (key->flags & RCKFL_GENDERMALE) fprintf(fp, "male"); + else if (key->flags & RCKFL_GENDERLESS) fprintf(fp, "it"); + else if (key->flags & RCKFL_VARIABLES) + { + fprintf(fp, "("); + for (mp = key->match; mp; mp = mp->next) + { + if (mp->type == MT_STRING) fprintf(fp, "\"%s\"", mp->firststring->string); + else fprintf(fp, "%d", mp->variable); + if (mp->next) fprintf(fp, ", "); + } //end for + fprintf(fp, ")"); + } //end if + else if (key->flags & RCKFL_STRING) + { + fprintf(fp, "\"%s\"", key->string); + } //end if + if (key->next) fprintf(fp, ", "); + else fprintf(fp, "] = %1.0f\n", rp->priority); + } //end for + fprintf(fp, "{\n"); + for (cm = rp->firstchatmessage; cm; cm = cm->next) + { + fprintf(fp, "\t\"%s\";\n", cm->chatmessage); + } //end for + fprintf(fp, "}\n"); + } //end for +} //end of the function BotDumpReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeReplyChat(bot_replychat_t *replychat) +{ + bot_replychat_t *rp, *nextrp; + bot_replychatkey_t *key, *nextkey; + bot_chatmessage_t *cm, *nextcm; + + for (rp = replychat; rp; rp = nextrp) + { + nextrp = rp->next; + for (key = rp->keys; key; key = nextkey) + { + nextkey = key->next; + if (key->match) BotFreeMatchPieces(key->match); + if (key->string) FreeMemory(key->string); + FreeMemory(key); + } //end for + for (cm = rp->firstchatmessage; cm; cm = nextcm) + { + nextcm = cm->next; + FreeMemory(cm); + } //end for + FreeMemory(rp); + } //end for +} //end of the function BotFreeReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckValidReplyChatKeySet(source_t *source, bot_replychatkey_t *keys) +{ + int allprefixed, hasvariableskey, hasstringkey; + bot_matchpiece_t *m; + bot_matchstring_t *ms; + bot_replychatkey_t *key, *key2; + + // + allprefixed = qtrue; + hasvariableskey = hasstringkey = qfalse; + for (key = keys; key; key = key->next) + { + if (!(key->flags & (RCKFL_AND|RCKFL_NOT))) + { + allprefixed = qfalse; + if (key->flags & RCKFL_VARIABLES) + { + for (m = key->match; m; m = m->next) + { + if (m->type == MT_VARIABLE) hasvariableskey = qtrue; + } //end for + } //end if + else if (key->flags & RCKFL_STRING) + { + hasstringkey = qtrue; + } //end else if + } //end if + else if ((key->flags & RCKFL_AND) && (key->flags & RCKFL_STRING)) + { + for (key2 = keys; key2; key2 = key2->next) + { + if (key2 == key) continue; + if (key2->flags & RCKFL_NOT) continue; + if (key2->flags & RCKFL_VARIABLES) + { + for (m = key2->match; m; m = m->next) + { + if (m->type == MT_STRING) + { + for (ms = m->firststring; ms; ms = ms->next) + { + if (StringContains(ms->string, key->string, qfalse) != -1) + { + break; + } //end if + } //end for + if (ms) break; + } //end if + else if (m->type == MT_VARIABLE) + { + break; + } //end if + } //end for + if (!m) + { + SourceWarning(source, "one of the match templates does not " + "leave space for the key %s with the & prefix", key->string); + } //end if + } //end if + } //end for + } //end else + if ((key->flags & RCKFL_NOT) && (key->flags & RCKFL_STRING)) + { + for (key2 = keys; key2; key2 = key2->next) + { + if (key2 == key) continue; + if (key2->flags & RCKFL_NOT) continue; + if (key2->flags & RCKFL_STRING) + { + if (StringContains(key2->string, key->string, qfalse) != -1) + { + SourceWarning(source, "the key %s with prefix ! is inside the key %s", key->string, key2->string); + } //end if + } //end if + else if (key2->flags & RCKFL_VARIABLES) + { + for (m = key2->match; m; m = m->next) + { + if (m->type == MT_STRING) + { + for (ms = m->firststring; ms; ms = ms->next) + { + if (StringContains(ms->string, key->string, qfalse) != -1) + { + SourceWarning(source, "the key %s with prefix ! is inside " + "the match template string %s", key->string, ms->string); + } //end if + } //end for + } //end if + } //end for + } //end else if + } //end for + } //end if + } //end for + if (allprefixed) SourceWarning(source, "all keys have a & or ! prefix"); + if (hasvariableskey && hasstringkey) + { + SourceWarning(source, "variables from the match template(s) could be " + "invalid when outputting one of the chat messages"); + } //end if +} //end of the function BotCheckValidReplyChatKeySet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_replychat_t *BotLoadReplyChat(char *filename) +{ + char chatmessagestring[MAX_MESSAGE_SIZE]; + char namebuffer[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_chatmessage_t *chatmessage = NULL; + bot_replychat_t *replychat, *replychatlist; + bot_replychatkey_t *key; + + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + replychatlist = NULL; + // + while(PC_ReadToken(source, &token)) + { + if (strcmp(token.string, "[")) + { + SourceError(source, "expected [, found %s", token.string); + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + // + replychat = (struct bot_replychat_s *)GetClearedHunkMemory(sizeof(bot_replychat_t)); + replychat->keys = NULL; + replychat->next = replychatlist; + replychatlist = replychat; + //read the keys, there must be at least one key + do + { + //allocate a key + key = (bot_replychatkey_t *) GetClearedHunkMemory(sizeof(bot_replychatkey_t)); + key->flags = 0; + key->string = NULL; + key->match = NULL; + key->next = replychat->keys; + replychat->keys = key; + //check for MUST BE PRESENT and MUST BE ABSENT keys + if (PC_CheckTokenString(source, "&")) key->flags |= RCKFL_AND; + else if (PC_CheckTokenString(source, "!")) key->flags |= RCKFL_NOT; + //special keys + if (PC_CheckTokenString(source, "name")) key->flags |= RCKFL_NAME; + else if (PC_CheckTokenString(source, "female")) key->flags |= RCKFL_GENDERFEMALE; + else if (PC_CheckTokenString(source, "male")) key->flags |= RCKFL_GENDERMALE; + else if (PC_CheckTokenString(source, "it")) key->flags |= RCKFL_GENDERLESS; + else if (PC_CheckTokenString(source, "(")) //match key + { + key->flags |= RCKFL_VARIABLES; + key->match = BotLoadMatchPieces(source, ")"); + if (!key->match) + { + BotFreeReplyChat(replychatlist); + return NULL; + } //end if + } //end else if + else if (PC_CheckTokenString(source, "<")) //bot names + { + key->flags |= RCKFL_BOTNAMES; + strcpy(namebuffer, ""); + do + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (strlen(namebuffer)) strcat(namebuffer, "\\"); + strcat(namebuffer, token.string); + } while(PC_CheckTokenString(source, ",")); + if (!PC_ExpectTokenString(source, ">")) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + key->string = (char *) GetClearedHunkMemory(strlen(namebuffer) + 1); + strcpy(key->string, namebuffer); + } //end else if + else //normal string key + { + key->flags |= RCKFL_STRING; + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + key->string = (char *) GetClearedHunkMemory(strlen(token.string) + 1); + strcpy(key->string, token.string); + } //end else + // + PC_CheckTokenString(source, ","); + } while(!PC_CheckTokenString(source, "]")); + // + BotCheckValidReplyChatKeySet(source, replychat->keys); + //read the = sign and the priority + if (!PC_ExpectTokenString(source, "=") || + !PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + replychat->priority = token.floatvalue; + //read the leading { + if (!PC_ExpectTokenString(source, "{")) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + replychat->numchatmessages = 0; + //while the trailing } is not found + while(!PC_CheckTokenString(source, "}")) + { + if (!BotLoadChatMessage(source, chatmessagestring)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + chatmessage = (bot_chatmessage_t *) GetClearedHunkMemory(sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1); + chatmessage->chatmessage = (char *) chatmessage + sizeof(bot_chatmessage_t); + strcpy(chatmessage->chatmessage, chatmessagestring); + chatmessage->time = -2*CHATMESSAGE_RECENTTIME; + chatmessage->next = replychat->firstchatmessage; + //add the chat message to the reply chat + replychat->firstchatmessage = chatmessage; + replychat->numchatmessages++; + } //end while + } //end while + FreeSource(source); + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // + //BotDumpReplyChat(replychatlist); + if (bot_developer) + { + BotCheckReplyChatIntegrety(replychatlist); + } //end if + // + if (!replychatlist) botimport.Print(PRT_MESSAGE, "no rchats\n"); + // + return replychatlist; +} //end of the function BotLoadReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpInitialChat(bot_chat_t *chat) +{ + bot_chattype_t *t; + bot_chatmessage_t *m; + + Log_Write("{"); + for (t = chat->types; t; t = t->next) + { + Log_Write(" type \"%s\"", t->name); + Log_Write(" {"); + Log_Write(" numchatmessages = %d", t->numchatmessages); + for (m = t->firstchatmessage; m; m = m->next) + { + Log_Write(" \"%s\"", m->chatmessage); + } //end for + Log_Write(" }"); + } //end for + Log_Write("}"); +} //end of the function BotDumpInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_chat_t *BotLoadInitialChat(char *chatfile, char *chatname) +{ + int pass, foundchat, indent, size; + char *ptr = NULL; + char chatmessagestring[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_chat_t *chat = NULL; + bot_chattype_t *chattype = NULL; + bot_chatmessage_t *chatmessage = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + // + size = 0; + foundchat = qfalse; + //a bot chat is parsed in two phases + for (pass = 0; pass < 2; pass++) + { + //allocate memory + if (pass && size) ptr = (char *) GetClearedMemory(size); + //load the source file + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(chatfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", chatfile); + return NULL; + } //end if + //chat structure + if (pass) + { + chat = (bot_chat_t *) ptr; + ptr += sizeof(bot_chat_t); + } //end if + size = sizeof(bot_chat_t); + // + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "chat")) + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + //after the chat name we expect a opening brace + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + //if the chat name is found + if (!Q_stricmp(token.string, chatname)) + { + foundchat = qtrue; + //read the chat types + while(1) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + return NULL; + } //end if + if (!strcmp(token.string, "}")) break; + if (strcmp(token.string, "type")) + { + SourceError(source, "expected type found %s\n", token.string); + FreeSource(source); + return NULL; + } //end if + //expect the chat type name + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token) || + !PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (pass) + { + chattype = (bot_chattype_t *) ptr; + strncpy(chattype->name, token.string, MAX_CHATTYPE_NAME); + chattype->firstchatmessage = NULL; + //add the chat type to the chat + chattype->next = chat->types; + chat->types = chattype; + // + ptr += sizeof(bot_chattype_t); + } //end if + size += sizeof(bot_chattype_t); + //read the chat messages + while(!PC_CheckTokenString(source, "}")) + { + if (!BotLoadChatMessage(source, chatmessagestring)) + { + FreeSource(source); + return NULL; + } //end if + if (pass) + { + chatmessage = (bot_chatmessage_t *) ptr; + chatmessage->time = -2*CHATMESSAGE_RECENTTIME; + //put the chat message in the list + chatmessage->next = chattype->firstchatmessage; + chattype->firstchatmessage = chatmessage; + //store the chat message + ptr += sizeof(bot_chatmessage_t); + chatmessage->chatmessage = ptr; + strcpy(chatmessage->chatmessage, chatmessagestring); + ptr += strlen(chatmessagestring) + 1; + //the number of chat messages increased + chattype->numchatmessages++; + } //end if + size += sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1; + } //end if + } //end while + } //end if + else //skip the bot chat + { + indent = 1; + while(indent) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + return NULL; + } //end if + if (!strcmp(token.string, "{")) indent++; + else if (!strcmp(token.string, "}")) indent--; + } //end while + } //end else + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeSource(source); + return NULL; + } //end else + } //end while + //free the source + FreeSource(source); + //if the requested character is not found + if (!foundchat) + { + botimport.Print(PRT_ERROR, "couldn't find chat %s in %s\n", chatname, chatfile); + return NULL; + } //end if + } //end for + // + botimport.Print(PRT_MESSAGE, "loaded %s from %s\n", chatname, chatfile); + // + //BotDumpInitialChat(chat); + if (bot_developer) + { + BotCheckInitialChatIntegrety(chat); + } //end if +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "initial chats loaded in %d msec\n", Sys_MilliSeconds() - starttime); +#endif //DEBUG + //character was read succesfully + return chat; +} //end of the function BotLoadInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeChatFile(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + if (cs->chat) FreeMemory(cs->chat); + cs->chat = NULL; +} //end of the function BotFreeChatFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadChatFile(int chatstate, char *chatfile, char *chatname) +{ + bot_chatstate_t *cs; + int n, avail = 0; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return BLERR_CANNOTLOADICHAT; + BotFreeChatFile(chatstate); + + if (!LibVarGetValue("bot_reloadcharacters")) + { + avail = -1; + for( n = 0; n < MAX_CLIENTS; n++ ) { + if( !ichatdata[n] ) { + if( avail == -1 ) { + avail = n; + } + continue; + } + if( strcmp( chatfile, ichatdata[n]->filename ) != 0 ) { + continue; + } + if( strcmp( chatname, ichatdata[n]->chatname ) != 0 ) { + continue; + } + cs->chat = ichatdata[n]->chat; + // botimport.Print( PRT_MESSAGE, "retained %s from %s\n", chatname, chatfile ); + return BLERR_NOERROR; + } + + if( avail == -1 ) { + botimport.Print(PRT_FATAL, "ichatdata table full; couldn't load chat %s from %s\n", chatname, chatfile); + return BLERR_CANNOTLOADICHAT; + } + } + + cs->chat = BotLoadInitialChat(chatfile, chatname); + if (!cs->chat) + { + botimport.Print(PRT_FATAL, "couldn't load chat %s from %s\n", chatname, chatfile); + return BLERR_CANNOTLOADICHAT; + } //end if + if (!LibVarGetValue("bot_reloadcharacters")) + { + ichatdata[avail] = (bot_ichatdata_t *)GetClearedMemory( sizeof(bot_ichatdata_t) ); + ichatdata[avail]->chat = cs->chat; + Q_strncpyz( ichatdata[avail]->chatname, chatname, sizeof(ichatdata[avail]->chatname) ); + Q_strncpyz( ichatdata[avail]->filename, chatfile, sizeof(ichatdata[avail]->filename) ); + } //end if + + return BLERR_NOERROR; +} //end of the function BotLoadChatFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotExpandChatMessage(char *outmessage, char *message, unsigned long mcontext, + bot_match_t *match, unsigned long vcontext, int reply) +{ + int num, len, i, expansion; + char *outputbuf, *ptr, *msgptr; + char temp[MAX_MESSAGE_SIZE]; + + expansion = qfalse; + msgptr = message; + outputbuf = outmessage; + len = 0; + // + while(*msgptr) + { + if (*msgptr == ESCAPE_CHAR) + { + msgptr++; + switch(*msgptr) + { + case 'v': //variable + { + msgptr++; + num = 0; + while(*msgptr && *msgptr != ESCAPE_CHAR) + { + num = num * 10 + (*msgptr++) - '0'; + } //end while + //step over the trailing escape char + if (*msgptr) msgptr++; + if (num > MAX_MATCHVARIABLES) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message %s variable %d out of range\n", message, num); + return qfalse; + } //end if + if (match->variables[num].offset >= 0) + { + assert( match->variables[num].offset >= 0 ); // bk001204 + ptr = &match->string[ (int) match->variables[num].offset]; + for (i = 0; i < match->variables[num].length; i++) + { + temp[i] = ptr[i]; + } //end for + temp[i] = 0; + //if it's a reply message + if (reply) + { + //replace the reply synonyms in the variables + BotReplaceReplySynonyms(temp, vcontext); + } //end if + else + { + //replace synonyms in the variable context + BotReplaceSynonyms(temp, vcontext); + } //end else + // + if (len + strlen(temp) >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message %s too long\n", message); + return qfalse; + } //end if + strcpy(&outputbuf[len], temp); + len += strlen(temp); + } //end if + break; + } //end case + case 'r': //random + { + msgptr++; + for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) + { + temp[i] = *msgptr++; + } //end while + temp[i] = '\0'; + //step over the trailing escape char + if (*msgptr) msgptr++; + //find the random keyword + ptr = RandomString(temp); + if (!ptr) + { + botimport.Print(PRT_ERROR, "BotConstructChat: unknown random string %s\n", temp); + return qfalse; + } //end if + if (len + strlen(ptr) >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); + return qfalse; + } //end if + strcpy(&outputbuf[len], ptr); + len += strlen(ptr); + expansion = qtrue; + break; + } //end case + default: + { + botimport.Print(PRT_FATAL, "BotConstructChat: message \"%s\" invalid escape char\n", message); + break; + } //end default + } //end switch + } //end if + else + { + outputbuf[len++] = *msgptr++; + if (len >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); + break; + } //end if + } //end else + } //end while + outputbuf[len] = '\0'; + //replace synonyms weighted in the message context + BotReplaceWeightedSynonyms(outputbuf, mcontext); + //return true if a random was expanded + return expansion; +} //end of the function BotExpandChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotConstructChatMessage(bot_chatstate_t *chatstate, char *message, unsigned long mcontext, + bot_match_t *match, unsigned long vcontext, int reply) +{ + int i; + char srcmessage[MAX_MESSAGE_SIZE]; + + strcpy(srcmessage, message); + for (i = 0; i < 10; i++) + { + if (!BotExpandChatMessage(chatstate->chatmessage, srcmessage, mcontext, match, vcontext, reply)) + { + break; + } //end if + strcpy(srcmessage, chatstate->chatmessage); + } //end for + if (i >= 10) + { + botimport.Print(PRT_WARNING, "too many expansions in chat message\n"); + botimport.Print(PRT_WARNING, "%s\n", chatstate->chatmessage); + } //end if +} //end of the function BotConstructChatMessage +//=========================================================================== +// randomly chooses one of the chat message of the given type +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *BotChooseInitialChatMessage(bot_chatstate_t *cs, char *type) +{ + int n, numchatmessages; + float besttime; + bot_chattype_t *t; + bot_chatmessage_t *m, *bestchatmessage; + bot_chat_t *chat; + + chat = cs->chat; + for (t = chat->types; t; t = t->next) + { + if (!Q_stricmp(t->name, type)) + { + numchatmessages = 0; + for (m = t->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) continue; + numchatmessages++; + } //end if + //if all chat messages have been used recently + if (numchatmessages <= 0) + { + besttime = 0; + bestchatmessage = NULL; + for (m = t->firstchatmessage; m; m = m->next) + { + if (!besttime || m->time < besttime) + { + bestchatmessage = m; + besttime = m->time; + } //end if + } //end for + if (bestchatmessage) return bestchatmessage->chatmessage; + } //end if + else //choose a chat message randomly + { + n = random() * numchatmessages; + for (m = t->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) continue; + if (--n < 0) + { + m->time = AAS_Time() + CHATMESSAGE_RECENTTIME; + return m->chatmessage; + } //end if + } //end for + } //end else + return NULL; + } //end if + } //end for + return NULL; +} //end of the function BotChooseInitialChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNumInitialChats(int chatstate, char *type) +{ + bot_chatstate_t *cs; + bot_chattype_t *t; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + + for (t = cs->chat->types; t; t = t->next) + { + if (!Q_stricmp(t->name, type)) + { + if (LibVarGetValue("bot_testichat")) { + botimport.Print(PRT_MESSAGE, "%s has %d chat lines\n", type, t->numchatmessages); + botimport.Print(PRT_MESSAGE, "-------------------\n"); + } + return t->numchatmessages; + } //end if + } //end for + return 0; +} //end of the function BotNumInitialChats +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) +{ + char *message; + int index; + bot_match_t match; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + //if no chat file is loaded + if (!cs->chat) return; + //choose a chat message randomly of the given type + message = BotChooseInitialChatMessage(cs, type); + //if there's no message of the given type + if (!message) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "no chat messages of type %s\n", type); +#endif //DEBUG + return; + } //end if + // + Com_Memset(&match, 0, sizeof(match)); + index = 0; + if( var0 ) { + strcat(match.string, var0); + match.variables[0].offset = index; + match.variables[0].length = strlen(var0); + index += strlen(var0); + } + if( var1 ) { + strcat(match.string, var1); + match.variables[1].offset = index; + match.variables[1].length = strlen(var1); + index += strlen(var1); + } + if( var2 ) { + strcat(match.string, var2); + match.variables[2].offset = index; + match.variables[2].length = strlen(var2); + index += strlen(var2); + } + if( var3 ) { + strcat(match.string, var3); + match.variables[3].offset = index; + match.variables[3].length = strlen(var3); + index += strlen(var3); + } + if( var4 ) { + strcat(match.string, var4); + match.variables[4].offset = index; + match.variables[4].length = strlen(var4); + index += strlen(var4); + } + if( var5 ) { + strcat(match.string, var5); + match.variables[5].offset = index; + match.variables[5].length = strlen(var5); + index += strlen(var5); + } + if( var6 ) { + strcat(match.string, var6); + match.variables[6].offset = index; + match.variables[6].length = strlen(var6); + index += strlen(var6); + } + if( var7 ) { + strcat(match.string, var7); + match.variables[7].offset = index; + match.variables[7].length = strlen(var7); + index += strlen(var7); + } + // + BotConstructChatMessage(cs, message, mcontext, &match, 0, qfalse); +} //end of the function BotInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPrintReplyChatKeys(bot_replychat_t *replychat) +{ + bot_replychatkey_t *key; + bot_matchpiece_t *mp; + + botimport.Print(PRT_MESSAGE, "["); + for (key = replychat->keys; key; key = key->next) + { + if (key->flags & RCKFL_AND) botimport.Print(PRT_MESSAGE, "&"); + else if (key->flags & RCKFL_NOT) botimport.Print(PRT_MESSAGE, "!"); + // + if (key->flags & RCKFL_NAME) botimport.Print(PRT_MESSAGE, "name"); + else if (key->flags & RCKFL_GENDERFEMALE) botimport.Print(PRT_MESSAGE, "female"); + else if (key->flags & RCKFL_GENDERMALE) botimport.Print(PRT_MESSAGE, "male"); + else if (key->flags & RCKFL_GENDERLESS) botimport.Print(PRT_MESSAGE, "it"); + else if (key->flags & RCKFL_VARIABLES) + { + botimport.Print(PRT_MESSAGE, "("); + for (mp = key->match; mp; mp = mp->next) + { + if (mp->type == MT_STRING) botimport.Print(PRT_MESSAGE, "\"%s\"", mp->firststring->string); + else botimport.Print(PRT_MESSAGE, "%d", mp->variable); + if (mp->next) botimport.Print(PRT_MESSAGE, ", "); + } //end for + botimport.Print(PRT_MESSAGE, ")"); + } //end if + else if (key->flags & RCKFL_STRING) + { + botimport.Print(PRT_MESSAGE, "\"%s\"", key->string); + } //end if + if (key->next) botimport.Print(PRT_MESSAGE, ", "); + else botimport.Print(PRT_MESSAGE, "] = %1.0f\n", replychat->priority); + } //end for + botimport.Print(PRT_MESSAGE, "{\n"); +} //end of the function BotPrintReplyChatKeys +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) +{ + bot_replychat_t *rchat, *bestrchat; + bot_replychatkey_t *key; + bot_chatmessage_t *m, *bestchatmessage; + bot_match_t match, bestmatch; + int bestpriority, num, found, res, numchatmessages, index; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return qfalse; + Com_Memset(&match, 0, sizeof(bot_match_t)); + strcpy(match.string, message); + bestpriority = -1; + bestchatmessage = NULL; + bestrchat = NULL; + //go through all the reply chats + for (rchat = replychats; rchat; rchat = rchat->next) + { + found = qfalse; + for (key = rchat->keys; key; key = key->next) + { + res = qfalse; + //get the match result + if (key->flags & RCKFL_NAME) res = (StringContains(message, cs->name, qfalse) != -1); + else if (key->flags & RCKFL_BOTNAMES) res = (StringContains(key->string, cs->name, qfalse) != -1); + else if (key->flags & RCKFL_GENDERFEMALE) res = (cs->gender == CHAT_GENDERFEMALE); + else if (key->flags & RCKFL_GENDERMALE) res = (cs->gender == CHAT_GENDERMALE); + else if (key->flags & RCKFL_GENDERLESS) res = (cs->gender == CHAT_GENDERLESS); + else if (key->flags & RCKFL_VARIABLES) res = StringsMatch(key->match, &match); + else if (key->flags & RCKFL_STRING) res = (StringContainsWord(message, key->string, qfalse) != NULL); + //if the key must be present + if (key->flags & RCKFL_AND) + { + if (!res) + { + found = qfalse; + break; + } //end if + } //end else if + //if the key must be absent + else if (key->flags & RCKFL_NOT) + { + if (res) + { + found = qfalse; + break; + } //end if + } //end if + else if (res) + { + found = qtrue; + } //end else + } //end for + // + if (found) + { + if (rchat->priority > bestpriority) + { + numchatmessages = 0; + for (m = rchat->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) continue; + numchatmessages++; + } //end if + num = random() * numchatmessages; + for (m = rchat->firstchatmessage; m; m = m->next) + { + if (--num < 0) break; + if (m->time > AAS_Time()) continue; + } //end for + //if the reply chat has a message + if (m) + { + Com_Memcpy(&bestmatch, &match, sizeof(bot_match_t)); + bestchatmessage = m; + bestrchat = rchat; + bestpriority = rchat->priority; + } //end if + } //end if + } //end if + } //end for + if (bestchatmessage) + { + index = strlen(bestmatch.string); + if( var0 ) { + strcat(bestmatch.string, var0); + bestmatch.variables[0].offset = index; + bestmatch.variables[0].length = strlen(var0); + index += strlen(var0); + } + if( var1 ) { + strcat(bestmatch.string, var1); + bestmatch.variables[1].offset = index; + bestmatch.variables[1].length = strlen(var1); + index += strlen(var1); + } + if( var2 ) { + strcat(bestmatch.string, var2); + bestmatch.variables[2].offset = index; + bestmatch.variables[2].length = strlen(var2); + index += strlen(var2); + } + if( var3 ) { + strcat(bestmatch.string, var3); + bestmatch.variables[3].offset = index; + bestmatch.variables[3].length = strlen(var3); + index += strlen(var3); + } + if( var4 ) { + strcat(bestmatch.string, var4); + bestmatch.variables[4].offset = index; + bestmatch.variables[4].length = strlen(var4); + index += strlen(var4); + } + if( var5 ) { + strcat(bestmatch.string, var5); + bestmatch.variables[5].offset = index; + bestmatch.variables[5].length = strlen(var5); + index += strlen(var5); + } + if( var6 ) { + strcat(bestmatch.string, var6); + bestmatch.variables[6].offset = index; + bestmatch.variables[6].length = strlen(var6); + index += strlen(var6); + } + if( var7 ) { + strcat(bestmatch.string, var7); + bestmatch.variables[7].offset = index; + bestmatch.variables[7].length = strlen(var7); + index += strlen(var7); + } + if (LibVarGetValue("bot_testrchat")) + { + for (m = bestrchat->firstchatmessage; m; m = m->next) + { + BotConstructChatMessage(cs, m->chatmessage, mcontext, &bestmatch, vcontext, qtrue); + BotRemoveTildes(cs->chatmessage); + botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); + } //end if + } //end if + else + { + bestchatmessage->time = AAS_Time() + CHATMESSAGE_RECENTTIME; + BotConstructChatMessage(cs, bestchatmessage->chatmessage, mcontext, &bestmatch, vcontext, qtrue); + } //end else + return qtrue; + } //end if + return qfalse; +} //end of the function BotReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChatLength(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + return strlen(cs->chatmessage); +} //end of the function BotChatLength +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotEnterChat(int chatstate, int clientto, int sendto) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + if (strlen(cs->chatmessage)) + { + BotRemoveTildes(cs->chatmessage); + if (LibVarGetValue("bot_testichat")) { + botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); + } + else { + switch(sendto) { + case CHAT_TEAM: + EA_Command(cs->client, va("say_team %s", cs->chatmessage)); + break; + case CHAT_TELL: + EA_Command(cs->client, va("tell %d %s", clientto, cs->chatmessage)); + break; + default: //CHAT_ALL + EA_Command(cs->client, va("say %s", cs->chatmessage)); + break; + } + } + //clear the chat message from the state + strcpy(cs->chatmessage, ""); + } //end if +} //end of the function BotEnterChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGetChatMessage(int chatstate, char *buf, int size) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + BotRemoveTildes(cs->chatmessage); + strncpy(buf, cs->chatmessage, size-1); + buf[size-1] = '\0'; + //clear the chat message from the state + strcpy(cs->chatmessage, ""); +} //end of the function BotGetChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetChatGender(int chatstate, int gender) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + switch(gender) + { + case CHAT_GENDERFEMALE: cs->gender = CHAT_GENDERFEMALE; break; + case CHAT_GENDERMALE: cs->gender = CHAT_GENDERMALE; break; + default: cs->gender = CHAT_GENDERLESS; break; + } //end switch +} //end of the function BotSetChatGender +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetChatName(int chatstate, char *name, int client) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + cs->client = client; + Com_Memset(cs->name, 0, sizeof(cs->name)); + strncpy(cs->name, name, sizeof(cs->name)); + cs->name[sizeof(cs->name)-1] = '\0'; +} //end of the function BotSetChatName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetChatAI(void) +{ + bot_replychat_t *rchat; + bot_chatmessage_t *m; + + for (rchat = replychats; rchat; rchat = rchat->next) + { + for (m = rchat->firstchatmessage; m; m = m->next) + { + m->time = 0; + } //end for + } //end for +} //end of the function BotResetChatAI +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocChatState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botchatstates[i]) + { + botchatstates[i] = (struct bot_chatstate_s *)GetClearedMemory(sizeof(bot_chatstate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocChatState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeChatState(int handle) +{ + bot_chatstate_t *cs; + bot_consolemessage_t m; + int h; + + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); + return; + } //end if + if (!botchatstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); + return; + } //end if + cs = botchatstates[handle]; + if (LibVarGetValue("bot_reloadcharacters")) + { + BotFreeChatFile(handle); + } //end if + //free all the console messages left in the chat state + for (h = BotNextConsoleMessage(handle, &m); h; h = BotNextConsoleMessage(handle, &m)) + { + //remove the console message + BotRemoveConsoleMessage(handle, h); + } //end for + FreeMemory(botchatstates[handle]); + botchatstates[handle] = NULL; +} //end of the function BotFreeChatState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupChatAI(void) +{ + char *file; + +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif //DEBUG + + file = LibVarString("synfile", "syn.c"); + synonyms = BotLoadSynonyms(file); + file = LibVarString("rndfile", "rnd.c"); + randomstrings = BotLoadRandomStrings(file); + file = LibVarString("matchfile", "match.c"); + matchtemplates = BotLoadMatchTemplates(file); + // + if (!LibVarValue("nochat", "0")) + { + file = LibVarString("rchatfile", "rchat.c"); + replychats = BotLoadReplyChat(file); + } //end if + + InitConsoleMessageHeap(); + +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "setup chat AI %d msec\n", Sys_MilliSeconds() - starttime); +#endif //DEBUG + return BLERR_NOERROR; +} //end of the function BotSetupChatAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownChatAI(void) +{ + int i; + + //free all remaining chat states + for(i = 0; i < MAX_CLIENTS; i++) + { + if (botchatstates[i]) + { + BotFreeChatState(i); + } //end if + } //end for + //free all cached chats + for(i = 0; i < MAX_CLIENTS; i++) + { + if (ichatdata[i]) + { + FreeMemory(ichatdata[i]->chat); + FreeMemory(ichatdata[i]); + ichatdata[i] = NULL; + } //end if + } //end for + if (consolemessageheap) FreeMemory(consolemessageheap); + consolemessageheap = NULL; + if (matchtemplates) BotFreeMatchTemplates(matchtemplates); + matchtemplates = NULL; + if (randomstrings) FreeMemory(randomstrings); + randomstrings = NULL; + if (synonyms) FreeMemory(synonyms); + synonyms = NULL; + if (replychats) BotFreeReplyChat(replychats); + replychats = NULL; +} //end of the function BotShutdownChatAI diff --git a/codemp/botlib/be_ai_gen.cpp b/codemp/botlib/be_ai_gen.cpp new file mode 100644 index 0000000..962eb1f --- /dev/null +++ b/codemp/botlib/be_ai_gen.cpp @@ -0,0 +1,117 @@ + +/***************************************************************************** + * name: be_ai_gen.c + * + * desc: genetic selection + * + * $Archive: /MissionPack/code/botlib/be_ai_gen.c $ + * $Author: Zaphod $ + * $Revision: 3 $ + * $Modtime: 11/22/00 8:50a $ + * $Date: 11/22/00 8:55a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "../game/be_ai_gen.h" + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GeneticSelection(int numranks, float *rankings) +{ + float sum, select; + int i, index; + + sum = 0; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + sum += rankings[i]; + } //end for + if (sum > 0) + { + //select a bot where the ones with the higest rankings have + //the highest chance of being selected + select = random() * sum; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + sum -= rankings[i]; + if (sum <= 0) return i; + } //end for + } //end if + //select a bot randomly + index = random() * numranks; + for (i = 0; i < numranks; i++) + { + if (rankings[index] >= 0) return index; + index = (index + 1) % numranks; + } //end for + return 0; +} //end of the function GeneticSelection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child) +{ + float rankings[256], max; + int i; + + if (numranks > 256) + { + botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too many bots\n"); + *parent1 = *parent2 = *child = 0; + return qfalse; + } //end if + for (max = 0, i = 0; i < numranks; i++) + { + if (ranks[i] < 0) continue; + max++; + } //end for + if (max < 3) + { + botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too few valid bots\n"); + *parent1 = *parent2 = *child = 0; + return qfalse; + } //end if + Com_Memcpy(rankings, ranks, sizeof(float) * numranks); + //select first parent + *parent1 = GeneticSelection(numranks, rankings); + rankings[*parent1] = -1; + //select second parent + *parent2 = GeneticSelection(numranks, rankings); + rankings[*parent2] = -1; + //reverse the rankings + max = 0; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + if (rankings[i] > max) max = rankings[i]; + } //end for + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + rankings[i] = max - rankings[i]; + } //end for + //select child + *child = GeneticSelection(numranks, rankings); + return qtrue; +} //end of the function GeneticParentsAndChildSelection diff --git a/codemp/botlib/be_ai_goal.cpp b/codemp/botlib/be_ai_goal.cpp new file mode 100644 index 0000000..dad0cc8 --- /dev/null +++ b/codemp/botlib/be_ai_goal.cpp @@ -0,0 +1,1805 @@ + +/***************************************************************************** + * name: be_ai_goal.c + * + * desc: goal AI + * + * $Archive: /MissionPack/code/botlib/be_ai_goal.c $ + * $Author: Ttimo $ + * $Revision: 14 $ + * $Modtime: 4/13/01 4:45p $ + * $Date: 4/13/01 4:45p $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_utils.h" +#include "l_libvar.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" + +//#define DEBUG_AI_GOAL +#ifdef RANDOMIZE +#define UNDECIDEDFUZZY +#endif //RANDOMIZE +#define DROPPEDWEIGHT +//minimum avoid goal time +#define AVOID_MINIMUM_TIME 10 +//default avoid goal time +#define AVOID_DEFAULT_TIME 30 +//avoid dropped goal time +#define AVOID_DROPPED_TIME 10 +// +#define TRAVELTIME_SCALE 0.01 +//item flags +#define IFL_NOTFREE 1 //not in free for all +#define IFL_NOTTEAM 2 //not in team play +#define IFL_NOTSINGLE 4 //not in single player +#define IFL_NOTBOT 8 //bot should never go for this +#define IFL_ROAM 16 //bot roam goal + +//location in the map "target_location" +typedef struct maplocation_s +{ + vec3_t origin; + int areanum; + char name[MAX_EPAIRKEY]; + struct maplocation_s *next; +} maplocation_t; + +//camp spots "info_camp" +typedef struct campspot_s +{ + vec3_t origin; + int areanum; + char name[MAX_EPAIRKEY]; + float range; + float weight; + float wait; + float random; + struct campspot_s *next; +} campspot_t; + +//FIXME: these are game specific +typedef enum { + GT_FFA, // free for all + GT_HOLOCRON, // holocron match + GT_JEDIMASTER, // jedi master + GT_DUEL, // one on one tournament + GT_POWERDUEL, + GT_SINGLE_PLAYER, // single player tournament + + //-- team games go after this -- + + GT_TEAM, // team deathmatch + GT_SIEGE, // siege + GT_CTF, // capture the flag + GT_CTY, + GT_MAX_GAME_TYPE +}; +typedef int gametype_t; + +typedef struct levelitem_s +{ + int number; //number of the level item + int iteminfo; //index into the item info + int flags; //item flags + float weight; //fixed roam weight + vec3_t origin; //origin of the item + int goalareanum; //area the item is in + vec3_t goalorigin; //goal origin within the area + int entitynum; //entity number + float timeout; //item is removed after this time + struct levelitem_s *prev, *next; +} levelitem_t; + +typedef struct iteminfo_s +{ + char classname[32]; //classname of the item + char name[MAX_STRINGFIELD]; //name of the item + char model[MAX_STRINGFIELD]; //model of the item + int modelindex; //model index + int type; //item type + int index; //index in the inventory + float respawntime; //respawn time + vec3_t mins; //mins of the item + vec3_t maxs; //maxs of the item + int number; //number of the item info +} iteminfo_t; + +#define ITEMINFO_OFS(x) (int)&(((iteminfo_t *)0)->x) + +fielddef_t iteminfo_fields[] = +{ +{"name", ITEMINFO_OFS(name), FT_STRING}, +{"model", ITEMINFO_OFS(model), FT_STRING}, +{"modelindex", ITEMINFO_OFS(modelindex), FT_INT}, +{"type", ITEMINFO_OFS(type), FT_INT}, +{"index", ITEMINFO_OFS(index), FT_INT}, +{"respawntime", ITEMINFO_OFS(respawntime), FT_FLOAT}, +{"mins", ITEMINFO_OFS(mins), FT_FLOAT|FT_ARRAY, 3}, +{"maxs", ITEMINFO_OFS(maxs), FT_FLOAT|FT_ARRAY, 3}, +{0, 0, 0} +}; + +structdef_t iteminfo_struct = +{ + sizeof(iteminfo_t), iteminfo_fields +}; + +typedef struct itemconfig_s +{ + int numiteminfo; + iteminfo_t *iteminfo; +} itemconfig_t; + +//goal state +typedef struct bot_goalstate_s +{ + struct weightconfig_s *itemweightconfig; //weight config + int *itemweightindex; //index from item to weight + // + int client; //client using this goal state + int lastreachabilityarea; //last area with reachabilities the bot was in + // + bot_goal_t goalstack[MAX_GOALSTACK]; //goal stack + int goalstacktop; //the top of the goal stack + // + int avoidgoals[MAX_AVOIDGOALS]; //goals to avoid + float avoidgoaltimes[MAX_AVOIDGOALS]; //times to avoid the goals +} bot_goalstate_t; + +bot_goalstate_t *botgoalstates[MAX_CLIENTS + 1]; // bk001206 - FIXME: init? +//item configuration +itemconfig_t *itemconfig = NULL; // bk001206 - init +//level items +levelitem_t *levelitemheap = NULL; // bk001206 - init +levelitem_t *freelevelitems = NULL; // bk001206 - init +levelitem_t *levelitems = NULL; // bk001206 - init +int numlevelitems = 0; +//map locations +maplocation_t *maplocations = NULL; // bk001206 - init +//camp spots +campspot_t *campspots = NULL; // bk001206 - init +//the game type +int g_gametype = 0; // bk001206 - init +//additional dropped item weight +libvar_t *droppedweight = NULL; // bk001206 - init + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_goalstate_t *BotGoalStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botgoalstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid goal state %d\n", handle); + return NULL; + } //end if + return botgoalstates[handle]; +} //end of the function BotGoalStateFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child) +{ + bot_goalstate_t *p1, *p2, *c; + + p1 = BotGoalStateFromHandle(parent1); + p2 = BotGoalStateFromHandle(parent2); + c = BotGoalStateFromHandle(child); + + InterbreedWeightConfigs(p1->itemweightconfig, p2->itemweightconfig, + c->itemweightconfig); +} //end of the function BotInterbreedingGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSaveGoalFuzzyLogic(int goalstate, char *filename) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + + //WriteWeightConfig(filename, gs->itemweightconfig); +} //end of the function BotSaveGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMutateGoalFuzzyLogic(int goalstate, float range) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + + EvolveWeightConfig(gs->itemweightconfig); +} //end of the function BotMutateGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +itemconfig_t *LoadItemConfig(char *filename) +{ + int max_iteminfo; + token_t token; + char path[MAX_PATH]; + source_t *source; + itemconfig_t *ic; + iteminfo_t *ii; + + max_iteminfo = (int) LibVarValue("max_iteminfo", "256"); + if (max_iteminfo < 0) + { + botimport.Print(PRT_ERROR, "max_iteminfo = %d\n", max_iteminfo); + max_iteminfo = 256; + LibVarSet( "max_iteminfo", "256" ); + } + + strncpy( path, filename, MAX_PATH ); + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile( path ); + if( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", path ); + return NULL; + } //end if + //initialize item config + ic = (itemconfig_t *) GetClearedHunkMemory(sizeof(itemconfig_t) + + max_iteminfo * sizeof(iteminfo_t)); + ic->iteminfo = (iteminfo_t *) ((char *) ic + sizeof(itemconfig_t)); + ic->numiteminfo = 0; + //parse the item config file + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "iteminfo")) + { + if (ic->numiteminfo >= max_iteminfo) + { + SourceError(source, "more than %d item info defined\n", max_iteminfo); + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end if + ii = &ic->iteminfo[ic->numiteminfo]; + Com_Memset(ii, 0, sizeof(iteminfo_t)); + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeMemory(ic); + FreeMemory(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + strncpy(ii->classname, token.string, sizeof(ii->classname)-1); + if (!ReadStructure(source, &iteminfo_struct, (char *) ii)) + { + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end if + ii->number = ic->numiteminfo; + ic->numiteminfo++; + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end else + } //end while + FreeSource(source); + // + if (!ic->numiteminfo) botimport.Print(PRT_WARNING, "no item info loaded\n"); + botimport.Print(PRT_MESSAGE, "loaded %s\n", path); + return ic; +} //end of the function LoadItemConfig +//=========================================================================== +// index to find the weight function of an iteminfo +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int *ItemWeightIndex(weightconfig_t *iwc, itemconfig_t *ic) +{ + int *index, i; + + //initialize item weight index + index = (int *) GetClearedMemory(sizeof(int) * ic->numiteminfo); + + for (i = 0; i < ic->numiteminfo; i++) + { + index[i] = FindFuzzyWeight(iwc, ic->iteminfo[i].classname); + if (index[i] < 0) + { + Log_Write("item info %d \"%s\" has no fuzzy weight\r\n", i, ic->iteminfo[i].classname); + } //end if + } //end for + return index; +} //end of the function ItemWeightIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InitLevelItemHeap(void) +{ + int i, max_levelitems; + + if (levelitemheap) FreeMemory(levelitemheap); + + max_levelitems = (int) LibVarValue("max_levelitems", "256"); + levelitemheap = (levelitem_t *) GetClearedMemory(max_levelitems * sizeof(levelitem_t)); + + for (i = 0; i < max_levelitems-1; i++) + { + levelitemheap[i].next = &levelitemheap[i + 1]; + } //end for + levelitemheap[max_levelitems-1].next = NULL; + // + freelevelitems = levelitemheap; +} //end of the function InitLevelItemHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +levelitem_t *AllocLevelItem(void) +{ + levelitem_t *li; + + li = freelevelitems; + if (!li) + { + botimport.Print(PRT_FATAL, "out of level items\n"); + return NULL; + } //end if + // + freelevelitems = freelevelitems->next; + Com_Memset(li, 0, sizeof(levelitem_t)); + return li; +} //end of the function AllocLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeLevelItem(levelitem_t *li) +{ + li->next = freelevelitems; + freelevelitems = li; +} //end of the function FreeLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddLevelItemToList(levelitem_t *li) +{ + if (levelitems) levelitems->prev = li; + li->prev = NULL; + li->next = levelitems; + levelitems = li; +} //end of the function AddLevelItemToList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveLevelItemFromList(levelitem_t *li) +{ + if (li->prev) li->prev->next = li->next; + else levelitems = li->next; + if (li->next) li->next->prev = li->prev; +} //end of the function RemoveLevelItemFromList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeInfoEntities(void) +{ + maplocation_t *ml, *nextml; + campspot_t *cs, *nextcs; + + for (ml = maplocations; ml; ml = nextml) + { + nextml = ml->next; + FreeMemory(ml); + } //end for + maplocations = NULL; + for (cs = campspots; cs; cs = nextcs) + { + nextcs = cs->next; + FreeMemory(cs); + } //end for + campspots = NULL; +} //end of the function BotFreeInfoEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitInfoEntities(void) +{ + char classname[MAX_EPAIRKEY]; + maplocation_t *ml; + campspot_t *cs; + int ent, numlocations, numcampspots; + + BotFreeInfoEntities(); + // + numlocations = 0; + numcampspots = 0; + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + + //map locations + if (!strcmp(classname, "target_location")) + { + ml = (maplocation_t *) GetClearedMemory(sizeof(maplocation_t)); + AAS_VectorForBSPEpairKey(ent, "origin", ml->origin); + AAS_ValueForBSPEpairKey(ent, "message", ml->name, sizeof(ml->name)); + ml->areanum = AAS_PointAreaNum(ml->origin); + ml->next = maplocations; + maplocations = ml; + numlocations++; + } //end if + //camp spots + else if (!strcmp(classname, "info_camp")) + { + cs = (campspot_t *) GetClearedMemory(sizeof(campspot_t)); + AAS_VectorForBSPEpairKey(ent, "origin", cs->origin); + //cs->origin[2] += 16; + AAS_ValueForBSPEpairKey(ent, "message", cs->name, sizeof(cs->name)); + AAS_FloatForBSPEpairKey(ent, "range", &cs->range); + AAS_FloatForBSPEpairKey(ent, "weight", &cs->weight); + AAS_FloatForBSPEpairKey(ent, "wait", &cs->wait); + AAS_FloatForBSPEpairKey(ent, "random", &cs->random); + cs->areanum = AAS_PointAreaNum(cs->origin); + if (!cs->areanum) + { + botimport.Print(PRT_MESSAGE, "camp spot at %1.1f %1.1f %1.1f in solid\n", cs->origin[0], cs->origin[1], cs->origin[2]); + FreeMemory(cs); + continue; + } //end if + cs->next = campspots; + campspots = cs; + //AAS_DrawPermanentCross(cs->origin, 4, LINECOLOR_YELLOW); + numcampspots++; + } //end else if + } //end for + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "%d map locations\n", numlocations); + botimport.Print(PRT_MESSAGE, "%d camp spots\n", numcampspots); + } //end if +} //end of the function BotInitInfoEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitLevelItems(void) +{ + int i, spawnflags, value; + char classname[MAX_EPAIRKEY]; + vec3_t origin, end; + int ent, goalareanum; + itemconfig_t *ic; + levelitem_t *li; + bsp_trace_t trace; + + //initialize the map locations and camp spots + BotInitInfoEntities(); + + //initialize the level item heap + InitLevelItemHeap(); + levelitems = NULL; + numlevelitems = 0; + // + ic = itemconfig; + if (!ic) return; + + //if there's no AAS file loaded + if (!AAS_Loaded()) return; + + //update the modelindexes of the item info + for (i = 0; i < ic->numiteminfo; i++) + { + //ic->iteminfo[i].modelindex = AAS_IndexFromModel(ic->iteminfo[i].model); + if (!ic->iteminfo[i].modelindex) + { + Log_Write("item %s has modelindex 0", ic->iteminfo[i].classname); + } //end if + } //end for + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + // + spawnflags = 0; + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // + for (i = 0; i < ic->numiteminfo; i++) + { + if (!strcmp(classname, ic->iteminfo[i].classname)) break; + } //end for + if (i >= ic->numiteminfo) + { + Log_Write("entity %s unknown item\r\n", classname); + continue; + } //end if + //get the origin of the item + if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + botimport.Print(PRT_ERROR, "item %s without origin\n", classname); + continue; + } //end else + // + goalareanum = 0; + //if it is a floating item + if (spawnflags & 1) + { + //if the item is not floating in water + if (!(AAS_PointContents(origin) & CONTENTS_WATER)) + { + VectorCopy(origin, end); + end[2] -= 32; + trace = AAS_Trace(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs, end, -1, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + //if the item not near the ground + if (trace.fraction >= 1) + { + //if the item is not reachable from a jumppad + goalareanum = AAS_BestReachableFromJumpPadArea(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs); + Log_Write("item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); + //botimport.Print(PRT_MESSAGE, "item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); + if (!goalareanum) continue; + } //end if + } //end if + } //end if + + li = AllocLevelItem(); + if (!li) return; + // + li->number = ++numlevelitems; + li->timeout = 0; + li->entitynum = 0; + // + li->flags = 0; + AAS_IntForBSPEpairKey(ent, "notfree", &value); + if (value) li->flags |= IFL_NOTFREE; + AAS_IntForBSPEpairKey(ent, "notteam", &value); + if (value) li->flags |= IFL_NOTTEAM; + AAS_IntForBSPEpairKey(ent, "notsingle", &value); + if (value) li->flags |= IFL_NOTSINGLE; + AAS_IntForBSPEpairKey(ent, "notbot", &value); + if (value) li->flags |= IFL_NOTBOT; + if (!strcmp(classname, "item_botroam")) + { + li->flags |= IFL_ROAM; + AAS_FloatForBSPEpairKey(ent, "weight", &li->weight); + } //end if + //if not a stationary item + if (!(spawnflags & 1)) + { + if (!AAS_DropToFloor(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs)) + { + botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end if + //item info of the level item + li->iteminfo = i; + //origin of the item + VectorCopy(origin, li->origin); + // + if (goalareanum) + { + li->goalareanum = goalareanum; + VectorCopy(origin, li->goalorigin); + } //end if + else + { + //get the item goal area and goal origin + li->goalareanum = AAS_BestReachableArea(origin, + ic->iteminfo[i].mins, ic->iteminfo[i].maxs, + li->goalorigin); + if (!li->goalareanum) + { + botimport.Print(PRT_MESSAGE, "%s not reachable for bots at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end else + // + AddLevelItemToList(li); + } //end for + botimport.Print(PRT_MESSAGE, "found %d level items\n", numlevelitems); +} //end of the function BotInitLevelItems +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGoalName(int number, char *name, int size) +{ + levelitem_t *li; + + if (!itemconfig) return; + // + for (li = levelitems; li; li = li->next) + { + if (li->number == number) + { + strncpy(name, itemconfig->iteminfo[li->iteminfo].name, size-1); + name[size-1] = '\0'; + return; + } //end for + } //end for + strcpy(name, ""); + return; +} //end of the function BotGoalName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetAvoidGoals(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + Com_Memset(gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof(int)); + Com_Memset(gs->avoidgoaltimes, 0, MAX_AVOIDGOALS * sizeof(float)); +} //end of the function BotResetAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpAvoidGoals(int goalstate) +{ + int i; + bot_goalstate_t *gs; + char name[32]; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoaltimes[i] >= AAS_Time()) + { + BotGoalName(gs->avoidgoals[i], name, 32); + Log_Write("avoid goal %s, number %d for %f seconds", name, + gs->avoidgoals[i], gs->avoidgoaltimes[i] - AAS_Time()); + } //end if + } //end for +} //end of the function BotDumpAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddToAvoidGoals(bot_goalstate_t *gs, int number, float avoidtime) +{ + int i; + + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + //if the avoid goal is already stored + if (gs->avoidgoals[i] == number) + { + gs->avoidgoals[i] = number; + gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for + + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + //if this avoid goal has expired + if (gs->avoidgoaltimes[i] < AAS_Time()) + { + gs->avoidgoals[i] = number; + gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for +} //end of the function BotAddToAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveFromAvoidGoals(int goalstate, int number) +{ + int i; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + //don't use the goals the bot wants to avoid + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) + { + gs->avoidgoaltimes[i] = 0; + return; + } //end if + } //end for +} //end of the function BotRemoveFromAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float BotAvoidGoalTime(int goalstate, int number) +{ + int i; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return 0; + //don't use the goals the bot wants to avoid + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) + { + return gs->avoidgoaltimes[i] - AAS_Time(); + } //end if + } //end for + return 0; +} //end of the function BotAvoidGoalTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime) +{ + bot_goalstate_t *gs; + levelitem_t *li; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + return; + if (avoidtime < 0) + { + if (!itemconfig) + return; + // + for (li = levelitems; li; li = li->next) + { + if (li->number == number) + { + avoidtime = itemconfig->iteminfo[li->iteminfo].respawntime; + if (!avoidtime) + avoidtime = AVOID_DEFAULT_TIME; + if (avoidtime < AVOID_MINIMUM_TIME) + avoidtime = AVOID_MINIMUM_TIME; + BotAddToAvoidGoals(gs, number, avoidtime); + return; + } //end for + } //end for + return; + } //end if + else + { + BotAddToAvoidGoals(gs, number, avoidtime); + } //end else +} //end of the function BotSetAvoidGoalTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetLevelItemGoal(int index, char *name, bot_goal_t *goal) +{ + levelitem_t *li; + + if (!itemconfig) return -1; + li = levelitems; + if (index >= 0) + { + for (; li; li = li->next) + { + if (li->number == index) + { + li = li->next; + break; + } //end if + } //end for + } //end for + for (; li; li = li->next) + { + // + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) continue; + } + else { + if (li->flags & IFL_NOTFREE) continue; + } + if (li->flags & IFL_NOTBOT) continue; + // + if (!Q_stricmp(name, itemconfig->iteminfo[li->iteminfo].name)) + { + goal->areanum = li->goalareanum; + VectorCopy(li->goalorigin, goal->origin); + goal->entitynum = li->entitynum; + VectorCopy(itemconfig->iteminfo[li->iteminfo].mins, goal->mins); + VectorCopy(itemconfig->iteminfo[li->iteminfo].maxs, goal->maxs); + goal->number = li->number; + goal->flags = GFL_ITEM; + if (li->timeout) goal->flags |= GFL_DROPPED; + //botimport.Print(PRT_MESSAGE, "found li %s\n", itemconfig->iteminfo[li->iteminfo].name); + return li->number; + } //end if + } //end for + return -1; +} //end of the function BotGetLevelItemGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetMapLocationGoal(char *name, bot_goal_t *goal) +{ + maplocation_t *ml; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + for (ml = maplocations; ml; ml = ml->next) + { + if (!Q_stricmp(ml->name, name)) + { + goal->areanum = ml->areanum; + VectorCopy(ml->origin, goal->origin); + goal->entitynum = 0; + VectorCopy(mins, goal->mins); + VectorCopy(maxs, goal->maxs); + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function BotGetMapLocationGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal) +{ + int i; + campspot_t *cs; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + if (num < 0) num = 0; + i = num; + for (cs = campspots; cs; cs = cs->next) + { + if (--i < 0) + { + goal->areanum = cs->areanum; + VectorCopy(cs->origin, goal->origin); + goal->entitynum = 0; + VectorCopy(mins, goal->mins); + VectorCopy(maxs, goal->maxs); + return num+1; + } //end if + } //end for + return 0; +} //end of the function BotGetNextCampSpotGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFindEntityForLevelItem(levelitem_t *li) +{ + int ent, modelindex; + itemconfig_t *ic; + aas_entityinfo_t entinfo; + vec3_t dir; + + ic = itemconfig; + if (!itemconfig) return; + for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) + { + //get the model index of the entity + modelindex = AAS_EntityModelindex(ent); + // + if (!modelindex) continue; + //get info about the entity + AAS_EntityInfo(ent, &entinfo); + //if the entity is still moving + if (entinfo.origin[0] != entinfo.lastvisorigin[0] || + entinfo.origin[1] != entinfo.lastvisorigin[1] || + entinfo.origin[2] != entinfo.lastvisorigin[2]) continue; + // + if (ic->iteminfo[li->iteminfo].modelindex == modelindex) + { + //check if the entity is very close + VectorSubtract(li->origin, entinfo.origin, dir); + if (VectorLength(dir) < 30) + { + //found an entity for this level item + li->entitynum = ent; + } //end if + } //end if + } //end for +} //end of the function BotFindEntityForLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +//NOTE: enum entityType_t in bg_public.h +#define ET_ITEM 2 + +void BotUpdateEntityItems(void) +{ + int ent, i, modelindex; + vec3_t dir; + levelitem_t *li, *nextli; + aas_entityinfo_t entinfo; + itemconfig_t *ic; + + //timeout current entity items if necessary + for (li = levelitems; li; li = nextli) + { + nextli = li->next; + //if it is a item that will time out + if (li->timeout) + { + //timeout the item + if (li->timeout < AAS_Time()) + { + RemoveLevelItemFromList(li); + FreeLevelItem(li); + } //end if + } //end if + } //end for + //find new entity items + ic = itemconfig; + if (!itemconfig) return; + // + for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) + { + if (AAS_EntityType(ent) != ET_ITEM) continue; + //get the model index of the entity + modelindex = AAS_EntityModelindex(ent); + // + if (!modelindex) continue; + //get info about the entity + AAS_EntityInfo(ent, &entinfo); + //FIXME: don't do this + //skip all floating items for now + //if (entinfo.groundent != ENTITYNUM_WORLD) continue; + //if the entity is still moving + if (entinfo.origin[0] != entinfo.lastvisorigin[0] || + entinfo.origin[1] != entinfo.lastvisorigin[1] || + entinfo.origin[2] != entinfo.lastvisorigin[2]) continue; + //check if the entity is already stored as a level item + for (li = levelitems; li; li = li->next) + { + //if the level item is linked to an entity + if (li->entitynum && li->entitynum == ent) + { + //the entity is re-used if the models are different + if (ic->iteminfo[li->iteminfo].modelindex != modelindex) + { + //remove this level item + RemoveLevelItemFromList(li); + FreeLevelItem(li); + li = NULL; + break; + } //end if + else + { + if (entinfo.origin[0] != li->origin[0] || + entinfo.origin[1] != li->origin[1] || + entinfo.origin[2] != li->origin[2]) + { + VectorCopy(entinfo.origin, li->origin); + //also update the goal area number + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, + li->goalorigin); + } //end if + break; + } //end else + } //end if + } //end for + if (li) continue; + //try to link the entity to a level item + for (li = levelitems; li; li = li->next) + { + //if this level item is already linked + if (li->entitynum) continue; + // + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) continue; + } + else { + if (li->flags & IFL_NOTFREE) continue; + } + //if the model of the level item and the entity are the same + if (ic->iteminfo[li->iteminfo].modelindex == modelindex) + { + //check if the entity is very close + VectorSubtract(li->origin, entinfo.origin, dir); + if (VectorLength(dir) < 30) + { + //found an entity for this level item + li->entitynum = ent; + //if the origin is different + if (entinfo.origin[0] != li->origin[0] || + entinfo.origin[1] != li->origin[1] || + entinfo.origin[2] != li->origin[2]) + { + //update the level item origin + VectorCopy(entinfo.origin, li->origin); + //also update the goal area number + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, + li->goalorigin); + } //end if +#ifdef DEBUG + Log_Write("linked item %s to an entity", ic->iteminfo[li->iteminfo].classname); +#endif //DEBUG + break; + } //end if + } //end else + } //end for + if (li) continue; + //check if the model is from a known item + for (i = 0; i < ic->numiteminfo; i++) + { + if (ic->iteminfo[i].modelindex == modelindex) + { + break; + } //end if + } //end for + //if the model is not from a known item + if (i >= ic->numiteminfo) continue; + //allocate a new level item + li = AllocLevelItem(); + // + if (!li) continue; + //entity number of the level item + li->entitynum = ent; + //number for the level item + li->number = numlevelitems + ent; + //set the item info index for the level item + li->iteminfo = i; + //origin of the item + VectorCopy(entinfo.origin, li->origin); + //get the item goal area and goal origin + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[i].mins, ic->iteminfo[i].maxs, + li->goalorigin); + //never go for items dropped into jumppads + if (AAS_AreaJumpPad(li->goalareanum)) + { + FreeLevelItem(li); + continue; + } //end if + //time this item out after 30 seconds + //dropped items disappear after 30 seconds + li->timeout = AAS_Time() + 30; + //add the level item to the list + AddLevelItemToList(li); + //botimport.Print(PRT_MESSAGE, "found new level item %s\n", ic->iteminfo[i].classname); + } //end for + /* + for (li = levelitems; li; li = li->next) + { + if (!li->entitynum) + { + BotFindEntityForLevelItem(li); + } //end if + } //end for*/ +} //end of the function BotUpdateEntityItems +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpGoalStack(int goalstate) +{ + int i; + bot_goalstate_t *gs; + char name[32]; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + for (i = 1; i <= gs->goalstacktop; i++) + { + BotGoalName(gs->goalstack[i].number, name, 32); + Log_Write("%d: %s", i, name); + } //end for +} //end of the function BotDumpGoalStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPushGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + if (gs->goalstacktop >= MAX_GOALSTACK-1) + { + botimport.Print(PRT_ERROR, "goal heap overflow\n"); + BotDumpGoalStack(goalstate); + return; + } //end if + gs->goalstacktop++; + Com_Memcpy(&gs->goalstack[gs->goalstacktop], goal, sizeof(bot_goal_t)); +} //end of the function BotPushGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPopGoal(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + if (gs->goalstacktop > 0) gs->goalstacktop--; +} //end of the function BotPopGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotEmptyGoalStack(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + gs->goalstacktop = 0; +} //end of the function BotEmptyGoalStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetTopGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return qfalse; + if (!gs->goalstacktop) return qfalse; + Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop], sizeof(bot_goal_t)); + return qtrue; +} //end of the function BotGetTopGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetSecondGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return qfalse; + if (gs->goalstacktop <= 1) return qfalse; + Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop-1], sizeof(bot_goal_t)); + return qtrue; +} //end of the function BotGetSecondGoal +//=========================================================================== +// pops a new long term goal on the goal stack in the goalstate +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags) +{ + int areanum, t, weightnum; + float weight, bestweight, avoidtime; + iteminfo_t *iteminfo; + itemconfig_t *ic; + levelitem_t *li, *bestitem; + bot_goal_t goal; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + return qfalse; + if (!gs->itemweightconfig) + return qfalse; + //get the area the bot is in + areanum = BotReachabilityArea(origin, gs->client); + //if the bot is in solid or if the area the bot is in has no reachability links + if (!areanum || !AAS_AreaReachability(areanum)) + { + //use the last valid area the bot was in + areanum = gs->lastreachabilityarea; + } //end if + //remember the last area with reachabilities the bot was in + gs->lastreachabilityarea = areanum; + //if still in solid + if (!areanum) + return qfalse; + //the item configuration + ic = itemconfig; + if (!itemconfig) + return qfalse; + //best weight and item so far + bestweight = 0; + bestitem = NULL; + Com_Memset(&goal, 0, sizeof(bot_goal_t)); + //go through the items in the level + for (li = levelitems; li; li = li->next) + { + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) + continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) + continue; + } + else { + if (li->flags & IFL_NOTFREE) + continue; + } + if (li->flags & IFL_NOTBOT) + continue; + //if the item is not in a possible goal area + if (!li->goalareanum) + continue; + //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk) + if (!li->entitynum && !(li->flags & IFL_ROAM)) + continue; + //get the fuzzy weight function for this item + iteminfo = &ic->iteminfo[li->iteminfo]; + weightnum = gs->itemweightindex[iteminfo->number]; + if (weightnum < 0) + continue; + +#ifdef UNDECIDEDFUZZY + weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); +#else + weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); +#endif //UNDECIDEDFUZZY +#ifdef DROPPEDWEIGHT + //HACK: to make dropped items more attractive + if (li->timeout) + weight += droppedweight->value; +#endif //DROPPEDWEIGHT + //use weight scale for item_botroam + if (li->flags & IFL_ROAM) weight *= li->weight; + // + if (weight > 0) + { + //get the travel time towards the goal area + t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); + //if the goal is reachable + if (t > 0) + { + //if this item won't respawn before we get there + avoidtime = BotAvoidGoalTime(goalstate, li->number); + if (avoidtime - t * 0.009 > 0) + continue; + // + weight /= (float) t * TRAVELTIME_SCALE; + // + if (weight > bestweight) + { + bestweight = weight; + bestitem = li; + } //end if + } //end if + } //end if + } //end for + //if no goal item found + if (!bestitem) + { + /* + //if not in lava or slime + if (!AAS_AreaLava(areanum) && !AAS_AreaSlime(areanum)) + { + if (AAS_RandomGoalArea(areanum, travelflags, &goal.areanum, goal.origin)) + { + VectorSet(goal.mins, -15, -15, -15); + VectorSet(goal.maxs, 15, 15, 15); + goal.entitynum = 0; + goal.number = 0; + goal.flags = GFL_ROAM; + goal.iteminfo = 0; + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "chosen roam goal area %d\n", goal.areanum); +#endif //DEBUG + return qtrue; + } //end if + } //end if + */ + return qfalse; + } //end if + //create a bot goal for this item + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + VectorCopy(bestitem->goalorigin, goal.origin); + VectorCopy(iteminfo->mins, goal.mins); + VectorCopy(iteminfo->maxs, goal.maxs); + goal.areanum = bestitem->goalareanum; + goal.entitynum = bestitem->entitynum; + goal.number = bestitem->number; + goal.flags = GFL_ITEM; + if (bestitem->timeout) + goal.flags |= GFL_DROPPED; + if (bestitem->flags & IFL_ROAM) + goal.flags |= GFL_ROAM; + goal.iteminfo = bestitem->iteminfo; + //if it's a dropped item + if (bestitem->timeout) + { + avoidtime = AVOID_DROPPED_TIME; + } //end if + else + { + avoidtime = iteminfo->respawntime; + if (!avoidtime) + avoidtime = AVOID_DEFAULT_TIME; + if (avoidtime < AVOID_MINIMUM_TIME) + avoidtime = AVOID_MINIMUM_TIME; + } //end else + //add the chosen goal to the goals to avoid for a while + BotAddToAvoidGoals(gs, bestitem->number, avoidtime); + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // + return qtrue; +} //end of the function BotChooseLTGItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime) +{ + int areanum, t, weightnum, ltg_time; + float weight, bestweight, avoidtime; + iteminfo_t *iteminfo; + itemconfig_t *ic; + levelitem_t *li, *bestitem; + bot_goal_t goal; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + return qfalse; + if (!gs->itemweightconfig) + return qfalse; + //get the area the bot is in + areanum = BotReachabilityArea(origin, gs->client); + //if the bot is in solid or if the area the bot is in has no reachability links + if (!areanum || !AAS_AreaReachability(areanum)) + { + //use the last valid area the bot was in + areanum = gs->lastreachabilityarea; + } //end if + //remember the last area with reachabilities the bot was in + gs->lastreachabilityarea = areanum; + //if still in solid + if (!areanum) + return qfalse; + // + if (ltg) ltg_time = AAS_AreaTravelTimeToGoalArea(areanum, origin, ltg->areanum, travelflags); + else ltg_time = 99999; + //the item configuration + ic = itemconfig; + if (!itemconfig) + return qfalse; + //best weight and item so far + bestweight = 0; + bestitem = NULL; + Com_Memset(&goal, 0, sizeof(bot_goal_t)); + //go through the items in the level + for (li = levelitems; li; li = li->next) + { + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) + continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) + continue; + } + else { + if (li->flags & IFL_NOTFREE) + continue; + } + if (li->flags & IFL_NOTBOT) + continue; + //if the item is in a possible goal area + if (!li->goalareanum) + continue; + //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk) + if (!li->entitynum && !(li->flags & IFL_ROAM)) + continue; + //get the fuzzy weight function for this item + iteminfo = &ic->iteminfo[li->iteminfo]; + weightnum = gs->itemweightindex[iteminfo->number]; + if (weightnum < 0) + continue; + // +#ifdef UNDECIDEDFUZZY + weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); +#else + weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); +#endif //UNDECIDEDFUZZY +#ifdef DROPPEDWEIGHT + //HACK: to make dropped items more attractive + if (li->timeout) + weight += droppedweight->value; +#endif //DROPPEDWEIGHT + //use weight scale for item_botroam + if (li->flags & IFL_ROAM) weight *= li->weight; + // + if (weight > 0) + { + //get the travel time towards the goal area + t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); + //if the goal is reachable + if (t > 0 && t < maxtime) + { + //if this item won't respawn before we get there + avoidtime = BotAvoidGoalTime(goalstate, li->number); + if (avoidtime - t * 0.009 > 0) + continue; + // + weight /= (float) t * TRAVELTIME_SCALE; + // + if (weight > bestweight) + { + t = 0; + if (ltg && !li->timeout) + { + //get the travel time from the goal to the long term goal + t = AAS_AreaTravelTimeToGoalArea(li->goalareanum, li->goalorigin, ltg->areanum, travelflags); + } //end if + //if the travel back is possible and doesn't take too long + if (t <= ltg_time) + { + bestweight = weight; + bestitem = li; + } //end if + } //end if + } //end if + } //end if + } //end for + //if no goal item found + if (!bestitem) + return qfalse; + //create a bot goal for this item + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + VectorCopy(bestitem->goalorigin, goal.origin); + VectorCopy(iteminfo->mins, goal.mins); + VectorCopy(iteminfo->maxs, goal.maxs); + goal.areanum = bestitem->goalareanum; + goal.entitynum = bestitem->entitynum; + goal.number = bestitem->number; + goal.flags = GFL_ITEM; + if (bestitem->timeout) + goal.flags |= GFL_DROPPED; + if (bestitem->flags & IFL_ROAM) + goal.flags |= GFL_ROAM; + goal.iteminfo = bestitem->iteminfo; + //if it's a dropped item + if (bestitem->timeout) + { + avoidtime = AVOID_DROPPED_TIME; + } //end if + else + { + avoidtime = iteminfo->respawntime; + if (!avoidtime) + avoidtime = AVOID_DEFAULT_TIME; + if (avoidtime < AVOID_MINIMUM_TIME) + avoidtime = AVOID_MINIMUM_TIME; + } //end else + //add the chosen goal to the goals to avoid for a while + BotAddToAvoidGoals(gs, bestitem->number, avoidtime); + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // + return qtrue; +} //end of the function BotChooseNBGItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotTouchingGoal(vec3_t origin, bot_goal_t *goal) +{ + int i; + vec3_t boxmins, boxmaxs; + vec3_t absmins, absmaxs; + vec3_t safety_maxs = {0, 0, 0}; //{4, 4, 10}; + vec3_t safety_mins = {0, 0, 0}; //{-4, -4, 0}; + + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, boxmins, boxmaxs); + VectorSubtract(goal->mins, boxmaxs, absmins); + VectorSubtract(goal->maxs, boxmins, absmaxs); + VectorAdd(absmins, goal->origin, absmins); + VectorAdd(absmaxs, goal->origin, absmaxs); + //make the box a little smaller for safety + VectorSubtract(absmaxs, safety_maxs, absmaxs); + VectorSubtract(absmins, safety_mins, absmins); + + for (i = 0; i < 3; i++) + { + if (origin[i] < absmins[i] || origin[i] > absmaxs[i]) return qfalse; + } //end for + return qtrue; +} //end of the function BotTouchingGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal) +{ + aas_entityinfo_t entinfo; + bsp_trace_t trace; + vec3_t middle; + + if (!(goal->flags & GFL_ITEM)) return qfalse; + // + VectorAdd(goal->mins, goal->mins, middle); + VectorScale(middle, 0.5, middle); + VectorAdd(goal->origin, middle, middle); + // + trace = AAS_Trace(eye, NULL, NULL, middle, viewer, CONTENTS_SOLID); + //if the goal middle point is visible + if (trace.fraction >= 1) + { + //the goal entity number doesn't have to be valid + //just assume it's valid + if (goal->entitynum <= 0) + return qfalse; + // + //if the entity data isn't valid + AAS_EntityInfo(goal->entitynum, &entinfo); + //NOTE: for some wacko reason entities are sometimes + // not updated + //if (!entinfo.valid) return qtrue; + if (entinfo.ltime < AAS_Time() - 0.5) + return qtrue; + } //end if + return qfalse; +} //end of the function BotItemGoalInVisButNotVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetGoalState(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + Com_Memset(gs->goalstack, 0, MAX_GOALSTACK * sizeof(bot_goal_t)); + gs->goalstacktop = 0; + BotResetAvoidGoals(goalstate); +} //end of the function BotResetGoalState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadItemWeights(int goalstate, char *filename) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return BLERR_CANNOTLOADITEMWEIGHTS; + //load the weight configuration + gs->itemweightconfig = ReadWeightConfig(filename); + if (!gs->itemweightconfig) + { + botimport.Print(PRT_FATAL, "couldn't load weights\n"); + return BLERR_CANNOTLOADITEMWEIGHTS; + } //end if + //if there's no item configuration + if (!itemconfig) return BLERR_CANNOTLOADITEMWEIGHTS; + //create the item weight index + gs->itemweightindex = ItemWeightIndex(gs->itemweightconfig, itemconfig); + //everything went ok + return BLERR_NOERROR; +} //end of the function BotLoadItemWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeItemWeights(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + if (gs->itemweightconfig) FreeWeightConfig(gs->itemweightconfig); + if (gs->itemweightindex) FreeMemory(gs->itemweightindex); +} //end of the function BotFreeItemWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAllocGoalState(int client) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botgoalstates[i]) + { + botgoalstates[i] = (struct bot_goalstate_s *)GetClearedMemory(sizeof(bot_goalstate_t)); + botgoalstates[i]->client = client; + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocGoalState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeGoalState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); + return; + } //end if + if (!botgoalstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid goal state handle %d\n", handle); + return; + } //end if + BotFreeItemWeights(handle); + FreeMemory(botgoalstates[handle]); + botgoalstates[handle] = NULL; +} //end of the function BotFreeGoalState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupGoalAI(void) +{ + char *filename; + + //check if teamplay is on + g_gametype = LibVarValue("g_gametype", "0"); + //item configuration file + filename = LibVarString("itemconfig", "items.c"); + //load the item configuration + itemconfig = LoadItemConfig(filename); + if (!itemconfig) + { + botimport.Print(PRT_FATAL, "couldn't load item config\n"); + return BLERR_CANNOTLOADITEMCONFIG; + } //end if + // + droppedweight = LibVar("droppedweight", "1000"); + //everything went ok + return BLERR_NOERROR; +} //end of the function BotSetupGoalAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownGoalAI(void) +{ + int i; + + if (itemconfig) FreeMemory(itemconfig); + itemconfig = NULL; + if (levelitemheap) FreeMemory(levelitemheap); + levelitemheap = NULL; + freelevelitems = NULL; + levelitems = NULL; + numlevelitems = 0; + + BotFreeInfoEntities(); + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botgoalstates[i]) + { + BotFreeGoalState(i); + } //end if + } //end for +} //end of the function BotShutdownGoalAI diff --git a/codemp/botlib/be_ai_move.cpp b/codemp/botlib/be_ai_move.cpp new file mode 100644 index 0000000..cb7baed --- /dev/null +++ b/codemp/botlib/be_ai_move.cpp @@ -0,0 +1,3599 @@ + +/***************************************************************************** + * name: be_ai_move.c + * + * desc: bot movement AI + * + * $Archive: /MissionPack/code/botlib/be_ai_move.c $ + * $Author: Ttimo $ + * $Revision: 14 $ + * $Modtime: 4/22/01 8:52a $ + * $Date: 4/22/01 8:52a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" + +#include "../game/be_ea.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" + + +//#define DEBUG_AI_MOVE +//#define DEBUG_ELEVATOR +//#define DEBUG_GRAPPLE + +// bk001204 - redundant bot_avoidspot_t, see ../game/be_ai_move.h + +//movement state +//NOTE: the moveflags MFL_ONGROUND, MFL_TELEPORTED, MFL_WATERJUMP and +// MFL_GRAPPLEPULL must be set outside the movement code +typedef struct bot_movestate_s +{ + //input vars (all set outside the movement code) + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + //state vars + int areanum; //area the bot is in + int lastareanum; //last area the bot was in + int lastgoalareanum; //last goal area number + int lastreachnum; //last reachability number + vec3_t lastorigin; //origin previous cycle + int reachareanum; //area number of the reachabilty + int moveflags; //movement flags + int jumpreach; //set when jumped + float grapplevisible_time; //last time the grapple was visible + float lastgrappledist; //last distance to the grapple end + float reachability_time; //time to use current reachability + int avoidreach[MAX_AVOIDREACH]; //reachabilities to avoid + float avoidreachtimes[MAX_AVOIDREACH]; //times to avoid the reachabilities + int avoidreachtries[MAX_AVOIDREACH]; //number of tries before avoiding + // + bot_avoidspot_t avoidspots[MAX_AVOIDSPOTS]; //spots to avoid + int numavoidspots; +} bot_movestate_t; + +//used to avoid reachability links for some time after being used +#define AVOIDREACH +#define AVOIDREACH_TIME 6 //avoid links for 6 seconds after use +#define AVOIDREACH_TRIES 4 +//prediction times +#define PREDICTIONTIME_JUMP 3 //in seconds +#define PREDICTIONTIME_MOVE 2 //in seconds +//weapon indexes for weapon jumping +#define WEAPONINDEX_ROCKET_LAUNCHER 5 +#define WEAPONINDEX_BFG 9 + +#define MODELTYPE_FUNC_PLAT 1 +#define MODELTYPE_FUNC_BOB 2 +#define MODELTYPE_FUNC_DOOR 3 +#define MODELTYPE_FUNC_STATIC 4 + +libvar_t *sv_maxstep; +libvar_t *sv_maxbarrier; +libvar_t *sv_gravity; +libvar_t *weapindex_rocketlauncher; +libvar_t *weapindex_bfg10k; +libvar_t *weapindex_grapple; +libvar_t *entitytypemissile; +libvar_t *offhandgrapple; +libvar_t *cmd_grappleoff; +libvar_t *cmd_grappleon; +//type of model, func_plat or func_bobbing +int modeltypes[MAX_MODELS]; + +bot_movestate_t *botmovestates[MAX_CLIENTS+1]; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocMoveState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botmovestates[i]) + { + botmovestates[i] = (struct bot_movestate_s *)GetClearedMemory(sizeof(bot_movestate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeMoveState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return; + } //end if + if (!botmovestates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return; + } //end if + FreeMemory(botmovestates[handle]); + botmovestates[handle] = NULL; +} //end of the function BotFreeMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_movestate_t *BotMoveStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botmovestates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return NULL; + } //end if + return botmovestates[handle]; +} //end of the function BotMoveStateFromHandle +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotInitMoveState(int handle, bot_initmove_t *initmove) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(handle); + if (!ms) return; + VectorCopy(initmove->origin, ms->origin); + VectorCopy(initmove->velocity, ms->velocity); + VectorCopy(initmove->viewoffset, ms->viewoffset); + ms->entitynum = initmove->entitynum; + ms->client = initmove->client; + ms->thinktime = initmove->thinktime; + ms->presencetype = initmove->presencetype; + VectorCopy(initmove->viewangles, ms->viewangles); + // + ms->moveflags &= ~MFL_ONGROUND; + if (initmove->or_moveflags & MFL_ONGROUND) ms->moveflags |= MFL_ONGROUND; + ms->moveflags &= ~MFL_TELEPORTED; + if (initmove->or_moveflags & MFL_TELEPORTED) ms->moveflags |= MFL_TELEPORTED; + ms->moveflags &= ~MFL_WATERJUMP; + if (initmove->or_moveflags & MFL_WATERJUMP) ms->moveflags |= MFL_WATERJUMP; + ms->moveflags &= ~MFL_WALK; + if (initmove->or_moveflags & MFL_WALK) ms->moveflags |= MFL_WALK; + ms->moveflags &= ~MFL_GRAPPLEPULL; + if (initmove->or_moveflags & MFL_GRAPPLEPULL) ms->moveflags |= MFL_GRAPPLEPULL; +} //end of the function BotInitMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +float AngleDiff(float ang1, float ang2) +{ + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) + { + if (diff > 180.0) diff -= 360.0; + } //end if + else + { + if (diff < -180.0) diff += 360.0; + } //end else + return diff; +} //end of the function AngleDiff +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFuzzyPointReachabilityArea(vec3_t origin) +{ + int firstareanum, j, x, y, z; + int areas[10], numareas, areanum, bestareanum; + float dist, bestdist; + vec3_t points[10], v, end; + + firstareanum = 0; + areanum = AAS_PointAreaNum(origin); + if (areanum) + { + firstareanum = areanum; + if (AAS_AreaReachability(areanum)) return areanum; + } //end if + VectorCopy(origin, end); + end[2] += 4; + numareas = AAS_TraceAreas(origin, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) return areas[j]; + } //end for + bestdist = 999999; + bestareanum = 0; + for (z = 1; z >= -1; z -= 1) + { + for (x = 1; x >= -1; x -= 1) + { + for (y = 1; y >= -1; y -= 1) + { + VectorCopy(origin, end); + end[0] += x * 8; + end[1] += y * 8; + end[2] += z * 12; + numareas = AAS_TraceAreas(origin, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) + { + VectorSubtract(points[j], origin, v); + dist = VectorLength(v); + if (dist < bestdist) + { + bestareanum = areas[j]; + bestdist = dist; + } //end if + } //end if + if (!firstareanum) firstareanum = areas[j]; + } //end for + } //end for + } //end for + if (bestareanum) return bestareanum; + } //end for + return firstareanum; +} //end of the function BotFuzzyPointReachabilityArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReachabilityArea(vec3_t origin, int client) +{ + int modelnum, modeltype, reachnum, areanum; + aas_reachability_t reach; + vec3_t org, end, mins, maxs, up = {0, 0, 1}; + bsp_trace_t bsptrace; + aas_trace_t trace; + + //check if the bot is standing on something + AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); + VectorMA(origin, -3, up, end); + bsptrace = AAS_Trace(origin, mins, maxs, end, client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!bsptrace.startsolid && bsptrace.fraction < 1 && bsptrace.ent != ENTITYNUM_NONE) + { + //if standing on the world the bot should be in a valid area + if (bsptrace.ent == ENTITYNUM_WORLD) + { + return BotFuzzyPointReachabilityArea(origin); + } //end if + + modelnum = AAS_EntityModelindex(bsptrace.ent); + modeltype = modeltypes[modelnum]; + + //if standing on a func_plat or func_bobbing then the bot is assumed to be + //in the area the reachability points to + if (modeltype == MODELTYPE_FUNC_PLAT || modeltype == MODELTYPE_FUNC_BOB) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + return reach.areanum; + } //end if + } //end else if + + //if the bot is swimming the bot should be in a valid area + if (AAS_Swimming(origin)) + { + return BotFuzzyPointReachabilityArea(origin); + } //end if + // + areanum = BotFuzzyPointReachabilityArea(origin); + //if the bot is in an area with reachabilities + if (areanum && AAS_AreaReachability(areanum)) return areanum; + //trace down till the ground is hit because the bot is standing on some other entity + VectorCopy(origin, org); + VectorCopy(org, end); + end[2] -= 800; + trace = AAS_TraceClientBBox(org, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + VectorCopy(trace.endpos, org); + } //end if + // + return BotFuzzyPointReachabilityArea(org); + } //end if + // + return BotFuzzyPointReachabilityArea(origin); +} //end of the function BotReachabilityArea +//=========================================================================== +// returns the reachability area the bot is in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +int BotReachabilityArea(vec3_t origin, int testground) +{ + int firstareanum, i, j, x, y, z; + int areas[10], numareas, areanum, bestareanum; + float dist, bestdist; + vec3_t org, end, points[10], v; + aas_trace_t trace; + + firstareanum = 0; + for (i = 0; i < 2; i++) + { + VectorCopy(origin, org); + //if test at the ground (used when bot is standing on an entity) + if (i > 0) + { + VectorCopy(origin, end); + end[2] -= 800; + trace = AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + VectorCopy(trace.endpos, org); + } //end if + } //end if + + firstareanum = 0; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + firstareanum = areanum; + if (AAS_AreaReachability(areanum)) return areanum; + } //end if + bestdist = 999999; + bestareanum = 0; + for (z = 1; z >= -1; z -= 1) + { + for (x = 1; x >= -1; x -= 1) + { + for (y = 1; y >= -1; y -= 1) + { + VectorCopy(org, end); + end[0] += x * 8; + end[1] += y * 8; + end[2] += z * 12; + numareas = AAS_TraceAreas(org, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) + { + VectorSubtract(points[j], org, v); + dist = VectorLength(v); + if (dist < bestdist) + { + bestareanum = areas[j]; + bestdist = dist; + } //end if + } //end if + } //end for + } //end for + } //end for + if (bestareanum) return bestareanum; + } //end for + if (!testground) break; + } //end for +//#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "no reachability area\n"); +//#endif //DEBUG + return firstareanum; +} //end of the function BotReachabilityArea*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotOnMover(vec3_t origin, int entnum, aas_reachability_t *reach) +{ + int i, modelnum; + vec3_t mins, maxs, modelorigin, org, end; + vec3_t angles = {0, 0, 0}; + vec3_t boxmins = {-16, -16, -8}, boxmaxs = {16, 16, 8}; + bsp_trace_t trace; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + // + if (!AAS_OriginOfMoverWithModelNum(modelnum, modelorigin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + return qfalse; + } //end if + // + for (i = 0; i < 2; i++) + { + if (origin[i] > modelorigin[i] + maxs[i] + 16) return qfalse; + if (origin[i] < modelorigin[i] + mins[i] - 16) return qfalse; + } //end for + // + VectorCopy(origin, org); + org[2] += 24; + VectorCopy(origin, end); + end[2] -= 48; + // + trace = AAS_Trace(org, boxmins, boxmaxs, end, entnum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!trace.startsolid && !trace.allsolid) + { + //NOTE: the reachability face number is the model number of the elevator + if (trace.ent != ENTITYNUM_NONE && AAS_EntityModelNum(trace.ent) == modelnum) + { + return qtrue; + } //end if + } //end if + return qfalse; +} //end of the function BotOnMover +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MoverDown(aas_reachability_t *reach) +{ + int modelnum; + vec3_t mins, maxs, origin; + vec3_t angles = {0, 0, 0}; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + return qfalse; + } //end if + //if the top of the plat is below the reachability start point + if (origin[2] + maxs[2] < reach->start[2]) return qtrue; + return qfalse; +} //end of the function MoverDown +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotSetBrushModelTypes(void) +{ + int ent, modelnum; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + + Com_Memset(modeltypes, 0, MAX_MODELS * sizeof(int)); + // + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) continue; + if (model[0]) modelnum = atoi(model+1); + else modelnum = 0; + + if (modelnum < 0 || modelnum > MAX_MODELS) + { + botimport.Print(PRT_MESSAGE, "entity %s model number out of range\n", classname); + continue; + } //end if + + if (!Q_stricmp(classname, "func_bobbing")) + modeltypes[modelnum] = MODELTYPE_FUNC_BOB; + else if (!Q_stricmp(classname, "func_plat")) + modeltypes[modelnum] = MODELTYPE_FUNC_PLAT; + else if (!Q_stricmp(classname, "func_door")) + modeltypes[modelnum] = MODELTYPE_FUNC_DOOR; + else if (!Q_stricmp(classname, "func_static")) + modeltypes[modelnum] = MODELTYPE_FUNC_STATIC; + } //end for +} //end of the function BotSetBrushModelTypes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotOnTopOfEntity(bot_movestate_t *ms) +{ + vec3_t mins, maxs, end, up = {0, 0, 1}; + bsp_trace_t trace; + + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + VectorMA(ms->origin, -3, up, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) + { + return trace.ent; + } //end if + return -1; +} //end of the function BotOnTopOfEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotValidTravel(vec3_t origin, aas_reachability_t *reach, int travelflags) +{ + //if the reachability uses an unwanted travel type + if (AAS_TravelFlagForType(reach->traveltype) & ~travelflags) return qfalse; + //don't go into areas with bad travel types + if (AAS_AreaContentsTravelFlags(reach->areanum) & ~travelflags) return qfalse; + return qtrue; +} //end of the function BotValidTravel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddToAvoidReach(bot_movestate_t *ms, int number, float avoidtime) +{ + int i; + + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreach[i] == number) + { + if (ms->avoidreachtimes[i] > AAS_Time()) ms->avoidreachtries[i]++; + else ms->avoidreachtries[i] = 1; + ms->avoidreachtimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for + //add the reachability to the reachabilities to avoid for a while + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreachtimes[i] < AAS_Time()) + { + ms->avoidreach[i] = number; + ms->avoidreachtimes[i] = AAS_Time() + avoidtime; + ms->avoidreachtries[i] = 1; + return; + } //end if + } //end for +} //end of the function BotAddToAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2) +{ + vec3_t proj, dir; + int j; + + AAS_ProjectPointOntoVector(p, lp1, lp2, proj); + for (j = 0; j < 3; j++) + if ((proj[j] > lp1[j] && proj[j] > lp2[j]) || + (proj[j] < lp1[j] && proj[j] < lp2[j])) + break; + if (j < 3) { + if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j])) + VectorSubtract(p, lp1, dir); + else + VectorSubtract(p, lp2, dir); + return VectorLengthSquared(dir); + } + VectorSubtract(p, proj, dir); + return VectorLengthSquared(dir); +} //end of the function DistanceFromLineSquared +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float VectorDistanceSquared(vec3_t p1, vec3_t p2) +{ + vec3_t dir; + VectorSubtract(p2, p1, dir); + return VectorLengthSquared(dir); +} //end of the function VectorDistanceSquared +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAvoidSpots(vec3_t origin, aas_reachability_t *reach, bot_avoidspot_t *avoidspots, int numavoidspots) +{ + int checkbetween, i, type; + float squareddist, squaredradius; + + switch(reach->traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: checkbetween = qtrue; break; + case TRAVEL_CROUCH: checkbetween = qtrue; break; + case TRAVEL_BARRIERJUMP: checkbetween = qtrue; break; + case TRAVEL_LADDER: checkbetween = qtrue; break; + case TRAVEL_WALKOFFLEDGE: checkbetween = qfalse; break; + case TRAVEL_JUMP: checkbetween = qfalse; break; + case TRAVEL_SWIM: checkbetween = qtrue; break; + case TRAVEL_WATERJUMP: checkbetween = qtrue; break; + case TRAVEL_TELEPORT: checkbetween = qfalse; break; + case TRAVEL_ELEVATOR: checkbetween = qfalse; break; + case TRAVEL_GRAPPLEHOOK: checkbetween = qfalse; break; + case TRAVEL_ROCKETJUMP: checkbetween = qfalse; break; + case TRAVEL_BFGJUMP: checkbetween = qfalse; break; + case TRAVEL_JUMPPAD: checkbetween = qfalse; break; + case TRAVEL_FUNCBOB: checkbetween = qfalse; break; + default: checkbetween = qtrue; break; + } //end switch + + type = AVOID_CLEAR; + for (i = 0; i < numavoidspots; i++) + { + squaredradius = Square(avoidspots[i].radius); + squareddist = DistanceFromLineSquared(avoidspots[i].origin, origin, reach->start); + // if moving towards the avoid spot + if (squareddist < squaredradius && + VectorDistanceSquared(avoidspots[i].origin, origin) > squareddist) + { + type = avoidspots[i].type; + } //end if + else if (checkbetween) { + squareddist = DistanceFromLineSquared(avoidspots[i].origin, reach->start, reach->end); + // if moving towards the avoid spot + if (squareddist < squaredradius && + VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist) + { + type = avoidspots[i].type; + } //end if + } //end if + else + { + VectorDistanceSquared(avoidspots[i].origin, reach->end); + // if the reachability leads closer to the avoid spot + if (squareddist < squaredradius && + VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist) + { + type = avoidspots[i].type; + } //end if + } //end else + if (type == AVOID_ALWAYS) + return type; + } //end for + return type; +} //end of the function BotAvoidSpots +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + if (type == AVOID_CLEAR) + { + ms->numavoidspots = 0; + return; + } //end if + + if (ms->numavoidspots >= MAX_AVOIDSPOTS) + return; + VectorCopy(origin, ms->avoidspots[ms->numavoidspots].origin); + ms->avoidspots[ms->numavoidspots].radius = radius; + ms->avoidspots[ms->numavoidspots].type = type; + ms->numavoidspots++; +} //end of the function BotAddAvoidSpot +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetReachabilityToGoal(vec3_t origin, int areanum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags, + struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags) +{ + int i, t, besttime, bestreachnum, reachnum; + aas_reachability_t reach; + + //if not in a valid area + if (!areanum) return 0; + // + if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goal->areanum)) + { + travelflags |= TFL_DONOTENTER; + movetravelflags |= TFL_DONOTENTER; + } //end if + //use the routing to find the next area to go to + besttime = 0; + bestreachnum = 0; + // + for (reachnum = AAS_NextAreaReachability(areanum, 0); reachnum; + reachnum = AAS_NextAreaReachability(areanum, reachnum)) + { +#ifdef AVOIDREACH + //check if it isn't an reachability to avoid + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (avoidreach[i] == reachnum && avoidreachtimes[i] >= AAS_Time()) break; + } //end for + if (i != MAX_AVOIDREACH && avoidreachtries[i] > AVOIDREACH_TRIES) + { +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "avoiding reachability %d\n", avoidreach[i]); + } //end if +#endif //DEBUG + continue; + } //end if +#endif //AVOIDREACH + //get the reachability from the number + AAS_ReachabilityFromNum(reachnum, &reach); + //NOTE: do not go back to the previous area if the goal didn't change + //NOTE: is this actually avoidance of local routing minima between two areas??? + if (lastgoalareanum == goal->areanum && reach.areanum == lastareanum) continue; + //if (AAS_AreaContentsTravelFlags(reach.areanum) & ~travelflags) continue; + //if the travel isn't valid + if (!BotValidTravel(origin, &reach, movetravelflags)) continue; + //get the travel time + t = AAS_AreaTravelTimeToGoalArea(reach.areanum, reach.end, goal->areanum, travelflags); + //if the goal area isn't reachable from the reachable area + if (!t) continue; + //if the bot should not use this reachability to avoid bad spots + if (BotAvoidSpots(origin, &reach, avoidspots, numavoidspots)) { + if (flags) { + *flags |= MOVERESULT_BLOCKEDBYAVOIDSPOT; + } + continue; + } + //add the travel time towards the area + t += reach.traveltime;// + AAS_AreaTravelTime(areanum, origin, reach.start); + //if the travel time is better than the ones already found + if (!besttime || t < besttime) + { + besttime = t; + bestreachnum = reachnum; + } //end if + } //end for + // + return bestreachnum; +} //end of the function BotGetReachabilityToGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAddToTarget(vec3_t start, vec3_t end, float maxdist, float *dist, vec3_t target) +{ + vec3_t dir; + float curdist; + + VectorSubtract(end, start, dir); + curdist = VectorNormalize(dir); + if (*dist + curdist < maxdist) + { + VectorCopy(end, target); + *dist += curdist; + return qfalse; + } //end if + else + { + VectorMA(start, maxdist - *dist, dir, target); + *dist = maxdist; + return qtrue; + } //end else +} //end of the function BotAddToTarget + +int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target) +{ + aas_reachability_t reach; + int reachnum, lastareanum; + bot_movestate_t *ms; + vec3_t end; + float dist; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return qfalse; + reachnum = 0; + //if the bot has no goal or no last reachability + if (!ms->lastreachnum || !goal) return qfalse; + + reachnum = ms->lastreachnum; + VectorCopy(ms->origin, end); + lastareanum = ms->lastareanum; + dist = 0; + while(reachnum && dist < lookahead) + { + AAS_ReachabilityFromNum(reachnum, &reach); + if (BotAddToTarget(end, reach.start, lookahead, &dist, target)) return qtrue; + //never look beyond teleporters + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_TELEPORT) return qtrue; + //never look beyond the weapon jump point + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) return qtrue; + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_BFGJUMP) return qtrue; + //don't add jump pad distances + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_JUMPPAD && + (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR && + (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB) + { + if (BotAddToTarget(reach.start, reach.end, lookahead, &dist, target)) return qtrue; + } //end if + reachnum = BotGetReachabilityToGoal(reach.end, reach.areanum, + ms->lastgoalareanum, lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, travelflags, NULL, 0, NULL); + VectorCopy(reach.end, end); + lastareanum = reach.areanum; + if (lastareanum == goal->areanum) + { + BotAddToTarget(reach.end, goal->origin, lookahead, &dist, target); + return qtrue; + } //end if + } //end while + // + return qfalse; +} //end of the function BotMovementViewTarget +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotVisible(int ent, vec3_t eye, vec3_t target) +{ + bsp_trace_t trace; + + trace = AAS_Trace(eye, NULL, NULL, target, ent, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction >= 1) return qtrue; + return qfalse; +} //end of the function BotVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target) +{ + aas_reachability_t reach; + int reachnum, lastgoalareanum, lastareanum, i; + int avoidreach[MAX_AVOIDREACH]; + float avoidreachtimes[MAX_AVOIDREACH]; + int avoidreachtries[MAX_AVOIDREACH]; + vec3_t end; + + //if the bot has no goal or no last reachability + if (!goal) return qfalse; + //if the areanum is not valid + if (!areanum) return qfalse; + //if the goal areanum is not valid + if (!goal->areanum) return qfalse; + + Com_Memset(avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); + lastgoalareanum = goal->areanum; + lastareanum = areanum; + VectorCopy(origin, end); + //only do 20 hops + for (i = 0; i < 20 && (areanum != goal->areanum); i++) + { + // + reachnum = BotGetReachabilityToGoal(end, areanum, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + goal, travelflags, travelflags, NULL, 0, NULL); + if (!reachnum) return qfalse; + AAS_ReachabilityFromNum(reachnum, &reach); + // + if (BotVisible(goal->entitynum, goal->origin, reach.start)) + { + VectorCopy(reach.start, target); + return qtrue; + } //end if + // + if (BotVisible(goal->entitynum, goal->origin, reach.end)) + { + VectorCopy(reach.end, target); + return qtrue; + } //end if + // + if (reach.areanum == goal->areanum) + { + VectorCopy(reach.end, target); + return qtrue; + } //end if + // + lastareanum = areanum; + areanum = reach.areanum; + VectorCopy(reach.end, end); + // + } //end while + // + return qfalse; +} //end of the function BotPredictVisiblePosition +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MoverBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter) +{ + int modelnum; + vec3_t mins, maxs, origin, mids; + vec3_t angles = {0, 0, 0}; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + } //end if + //get a point just above the plat in the bottom position + VectorAdd(mins, maxs, mids); + VectorMA(origin, 0.5, mids, bottomcenter); + bottomcenter[2] = reach->start[2]; +} //end of the function MoverBottomCenter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum) +{ + float dist, startz; + vec3_t start, end; + aas_trace_t trace; + + //do gap checking + startz = origin[2]; + //this enables walking down stairs more fluidly + { + VectorCopy(origin, start); + VectorCopy(origin, end); + end[2] -= 60; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); + if (trace.fraction >= 1) return 1; + startz = trace.endpos[2] + 1; + } + // + for (dist = 8; dist <= 100; dist += 8) + { + VectorMA(origin, dist, hordir, start); + start[2] = startz + 24; + VectorCopy(start, end); + end[2] -= 48 + sv_maxbarrier->value; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); + //if solid is found the bot can't walk any further and fall into a gap + if (!trace.startsolid) + { + //if it is a gap + if (trace.endpos[2] < startz - sv_maxstep->value - 8) + { + VectorCopy(trace.endpos, end); + end[2] -= 20; + if (AAS_PointContents(end) & CONTENTS_WATER) break; + //if a gap is found slow down + //botimport.Print(PRT_MESSAGE, "gap at %f\n", dist); + return dist; + } //end if + startz = trace.endpos[2]; + } //end if + } //end for + return 0; +} //end of the function BotGapDistance +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotCheckBarrierJump(bot_movestate_t *ms, vec3_t dir, float speed) +{ + vec3_t start, hordir, end; + aas_trace_t trace; + + VectorCopy(ms->origin, end); + end[2] += sv_maxbarrier->value; + //trace right up + trace = AAS_TraceClientBBox(ms->origin, end, PRESENCE_NORMAL, ms->entitynum); + //this shouldn't happen... but we check anyway + if (trace.startsolid) return qfalse; + //if very low ceiling it isn't possible to jump up to a barrier + if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse; + // + hordir[0] = dir[0]; + hordir[1] = dir[1]; + hordir[2] = 0; + VectorNormalize(hordir); + VectorMA(ms->origin, ms->thinktime * speed * 0.5, hordir, end); + VectorCopy(trace.endpos, start); + end[2] = trace.endpos[2]; + //trace from previous trace end pos horizontally in the move direction + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); + //again this shouldn't happen + if (trace.startsolid) return qfalse; + // + VectorCopy(trace.endpos, start); + VectorCopy(trace.endpos, end); + end[2] = ms->origin[2]; + //trace down from the previous trace end pos + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); + //if solid + if (trace.startsolid) return qfalse; + //if no obstacle at all + if (trace.fraction >= 1.0) return qfalse; + //if less than the maximum step height + if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse; + // + EA_Jump(ms->client); + EA_Move(ms->client, hordir, speed); + ms->moveflags |= MFL_BARRIERJUMP; + //there is a barrier + return qtrue; +} //end of the function BotCheckBarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSwimInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) +{ + vec3_t normdir; + + VectorCopy(dir, normdir); + VectorNormalize(normdir); + EA_Move(ms->client, normdir, speed); + return qtrue; +} //end of the function BotSwimInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotWalkInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) +{ + vec3_t hordir, cmdmove, velocity, tmpdir, origin; + int presencetype, maxframes, cmdframes, stopevent; + aas_clientmove_t move; + float dist; + + if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND; + //if the bot is on the ground + if (ms->moveflags & MFL_ONGROUND) + { + //if there is a barrier the bot can jump on + if (BotCheckBarrierJump(ms, dir, speed)) return qtrue; + //remove barrier jump flag + ms->moveflags &= ~MFL_BARRIERJUMP; + //get the presence type for the movement + if ((type & MOVE_CROUCH) && !(type & MOVE_JUMP)) presencetype = PRESENCE_CROUCH; + else presencetype = PRESENCE_NORMAL; + //horizontal direction + hordir[0] = dir[0]; + hordir[1] = dir[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //if the bot is not supposed to jump + if (!(type & MOVE_JUMP)) + { + //if there is a gap, try to jump over it + if (BotGapDistance(ms->origin, hordir, ms->entitynum) > 0) type |= MOVE_JUMP; + } //end if + //get command movement + VectorScale(hordir, speed, cmdmove); + VectorCopy(ms->velocity, velocity); + // + if (type & MOVE_JUMP) + { + //botimport.Print(PRT_MESSAGE, "trying jump\n"); + cmdmove[2] = 400; + maxframes = PREDICTIONTIME_JUMP / 0.1; + cmdframes = 1; + stopevent = SE_HITGROUND|SE_HITGROUNDDAMAGE| + SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA; + } //end if + else + { + maxframes = 2; + cmdframes = 2; + stopevent = SE_HITGROUNDDAMAGE| + SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA; + } //end else + //AAS_ClearShownDebugLines(); + // + VectorCopy(ms->origin, origin); + origin[2] += 0.5; + AAS_PredictClientMovement(&move, ms->entitynum, origin, presencetype, qtrue, + velocity, cmdmove, cmdframes, maxframes, 0.1f, + stopevent, 0, qfalse);//qtrue); + //if prediction time wasn't enough to fully predict the movement + if (move.frames >= maxframes && (type & MOVE_JUMP)) + { + //botimport.Print(PRT_MESSAGE, "client %d: max prediction frames\n", ms->client); + return qfalse; + } //end if + //don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + { + //botimport.Print(PRT_MESSAGE, "client %d: would be hurt ", ms->client); + //if (move.stopevent & SE_ENTERSLIME) botimport.Print(PRT_MESSAGE, "slime\n"); + //if (move.stopevent & SE_ENTERLAVA) botimport.Print(PRT_MESSAGE, "lava\n"); + //if (move.stopevent & SE_HITGROUNDDAMAGE) botimport.Print(PRT_MESSAGE, "hitground\n"); + return qfalse; + } //end if + //if ground was hit + if (move.stopevent & SE_HITGROUND) + { + //check for nearby gap + VectorNormalize2(move.velocity, tmpdir); + dist = BotGapDistance(move.endpos, tmpdir, ms->entitynum); + if (dist > 0) return qfalse; + // + dist = BotGapDistance(move.endpos, hordir, ms->entitynum); + if (dist > 0) return qfalse; + } //end if + //get horizontal movement + tmpdir[0] = move.endpos[0] - ms->origin[0]; + tmpdir[1] = move.endpos[1] - ms->origin[1]; + tmpdir[2] = 0; + // + //AAS_DrawCross(move.endpos, 4, LINECOLOR_BLUE); + //the bot is blocked by something + if (VectorLength(tmpdir) < speed * ms->thinktime * 0.5) return qfalse; + //perform the movement + if (type & MOVE_JUMP) EA_Jump(ms->client); + if (type & MOVE_CROUCH) EA_Crouch(ms->client); + EA_Move(ms->client, hordir, speed); + //movement was succesfull + return qtrue; + } //end if + else + { + if (ms->moveflags & MFL_BARRIERJUMP) + { + //if near the top or going down + if (ms->velocity[2] < 50) + { + EA_Move(ms->client, dir, speed); + } //end if + } //end if + //FIXME: do air control to avoid hazards + return qtrue; + } //end else +} //end of the function BotWalkInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return qfalse; + //if swimming + if (AAS_Swimming(ms->origin)) + { + return BotSwimInDirection(ms, dir, speed, type); + } //end if + else + { + return BotWalkInDirection(ms, dir, speed, type); + } //end else +} //end of the function BotMoveInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Intersection(vec2_t p1, vec2_t p2, vec2_t p3, vec2_t p4, vec2_t out) +{ + float x1, dx1, dy1, x2, dx2, dy2, d; + + dx1 = p2[0] - p1[0]; + dy1 = p2[1] - p1[1]; + dx2 = p4[0] - p3[0]; + dy2 = p4[1] - p3[1]; + + d = dy1 * dx2 - dx1 * dy2; + if (d != 0) + { + x1 = p1[1] * dx1 - p1[0] * dy1; + x2 = p3[1] * dx2 - p3[0] * dy2; + out[0] = (int) ((dx1 * x2 - dx2 * x1) / d); + out[1] = (int) ((dy1 * x2 - dy2 * x1) / d); + return qtrue; + } //end if + else + { + return qfalse; + } //end else +} //end of the function Intersection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckBlocked(bot_movestate_t *ms, vec3_t dir, int checkbottom, bot_moveresult_t *result) +{ + vec3_t mins, maxs, end, up = {0, 0, 1}; + bsp_trace_t trace; + + //test for entities obstructing the bot's path + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + // + if (fabs(DotProduct(dir, up)) < 0.7) + { + mins[2] += sv_maxstep->value; //if the bot can step on + maxs[2] -= 10; //a little lower to avoid low ceiling + } //end if + VectorMA(ms->origin, 3, dir, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY); + //if not started in solid and not hitting the world entity + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) + { + result->blocked = qtrue; + result->blockentity = trace.ent; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); +#endif //DEBUG + } //end if + //if not in an area with reachability + else if (checkbottom && !AAS_AreaReachability(ms->areanum)) + { + //check if the bot is standing on something + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + VectorMA(ms->origin, -3, up, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) + { + result->blocked = qtrue; + result->blockentity = trace.ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); +#endif //DEBUG + } //end if + } //end else +} //end of the function BotCheckBlocked +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotClearMoveResult(bot_moveresult_t *moveresult) +{ + moveresult->failure = qfalse; + moveresult->type = 0; + moveresult->blocked = qfalse; + moveresult->blockentity = 0; + moveresult->traveltype = 0; + moveresult->flags = 0; +} //end of the function BotClearMoveResult +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //first walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + // + if (dist < 10) + { + //walk straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + } //end if + //if going towards a crouch area + if (!(AAS_AreaPresenceType(reach->areanum) & PRESENCE_NORMAL)) + { + //if pretty close to the reachable area + if (dist < 20) EA_Crouch(ms->client); + } //end if + // + dist = BotGapDistance(ms->origin, hordir, ms->entitynum); + // + if (ms->moveflags & MFL_WALK) + { + if (dist > 0) speed = 200 - (180 - 1 * dist); + else speed = 200; + EA_Walk(ms->client); + } //end if + else + { + if (dist > 0) speed = 400 - (360 - 2 * dist); + else speed = 400; + } //end else + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if not on the ground and changed areas... don't walk back!! + //(doesn't seem to help) + /* + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + if (ms->areanum == reach->areanum) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "BotFinishTravel_Walk: already in reach area\n"); +#endif //DEBUG + return result; + } //end if*/ + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 100) dist = 100; + speed = 400 - (400 - 3 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Crouch(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + speed = 400; + //walk straight to reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary actions + EA_Crouch(ms->client); + EA_Move(ms->client, hordir, speed); + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Crouch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //walk straight to reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //if pretty close to the barrier + if (dist < 9) + { + EA_Jump(ms->client); + } //end if + else + { + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_BarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if near the top or going down + if (ms->velocity[2] < 250) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + // + EA_Move(ms->client, hordir, 400); + VectorCopy(hordir, result.movedir); + } //end if + // + return result; +} //end of the function BotFinishTravel_BarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Swim(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //swim straight to reachability end + VectorSubtract(reach->start, ms->origin, dir); + VectorNormalize(dir); + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary actions + EA_Move(ms->client, dir, 400); + // + VectorCopy(dir, result.movedir); + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_SWIMVIEW; + // + return result; +} //end of the function BotTravel_Swim +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, hordir; + float dist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //swim straight to reachability end + VectorSubtract(reach->end, ms->origin, dir); + VectorCopy(dir, hordir); + hordir[2] = 0; + dir[2] += 15 + crandom() * 40; + //botimport.Print(PRT_MESSAGE, "BotTravel_WaterJump: dir[2] = %f\n", dir[2]); + VectorNormalize(dir); + dist = VectorNormalize(hordir); + //elemantary actions + //EA_Move(ms->client, dir, 400); + EA_MoveForward(ms->client); + //move up if close to the actual out of water jump spot + if (dist < 40) EA_MoveUp(ms->client); + //set the ideal view angles + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotTravel_WaterJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, pnt; + float dist; + bot_moveresult_t result; + + //botimport.Print(PRT_MESSAGE, "BotFinishTravel_WaterJump\n"); + BotClearMoveResult(&result); + //if waterjumping there's nothing to do + if (ms->moveflags & MFL_WATERJUMP) return result; + //if not touching any water anymore don't do anything + //otherwise the bot sometimes keeps jumping? + VectorCopy(ms->origin, pnt); + pnt[2] -= 32; //extra for q2dm4 near red armor/mega health + if (!(AAS_PointContents(pnt) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return result; + //swim straight to reachability end + VectorSubtract(reach->end, ms->origin, dir); + dir[0] += crandom() * 10; + dir[1] += crandom() * 10; + dir[2] += 70 + crandom() * 10; + dist = VectorNormalize(dir); + //elemantary actions + EA_Move(ms->client, dir, 400); + //set the ideal view angles + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WaterJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir; + float dist, speed, reachhordist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //check if the bot is blocked by anything + VectorSubtract(reach->start, ms->origin, dir); + VectorNormalize(dir); + BotCheckBlocked(ms, dir, qtrue, &result); + //if the reachability start and end are practially above each other + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + reachhordist = VectorLength(dir); + //walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + //if pretty close to the start focus on the reachability end + if (dist < 48) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (reachhordist < 20) + { + speed = 100; + } //end if + else if (!AAS_HorizontalVelocityForJump(0, reach->start, reach->end, &speed)) + { + speed = 400; + } //end if + } //end if + else + { + if (reachhordist < 20) + { + if (dist > 64) dist = 64; + speed = 400 - (256 - 4 * dist); + } //end if + else + { + speed = 400; + } //end else + } //end else + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary action + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAirControl(vec3_t origin, vec3_t velocity, vec3_t goal, vec3_t dir, float *speed) +{ + vec3_t org, vel; + float dist; + int i; + + VectorCopy(origin, org); + VectorScale(velocity, 0.1, vel); + for (i = 0; i < 50; i++) + { + vel[2] -= sv_gravity->value * 0.01; + //if going down and next position would be below the goal + if (vel[2] < 0 && org[2] + vel[2] < goal[2]) + { + VectorScale(vel, (goal[2] - org[2]) / vel[2], vel); + VectorAdd(org, vel, org); + VectorSubtract(goal, org, dir); + dist = VectorNormalize(dir); + if (dist > 32) dist = 32; + *speed = 400 - (400 - 13 * dist); + return qtrue; + } //end if + else + { + VectorAdd(org, vel, org); + } //end else + } //end for + VectorSet(dir, 0, 0, 0); + *speed = 400; + return qfalse; +} //end of the function BotAirControl +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, hordir, end, v; + float dist, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + VectorSubtract(reach->end, ms->origin, dir); + BotCheckBlocked(ms, dir, qtrue, &result); + // + VectorSubtract(reach->end, ms->origin, v); + v[2] = 0; + dist = VectorNormalize(v); + if (dist > 16) VectorMA(reach->end, 16, v, end); + else VectorCopy(reach->end, end); + // + if (!BotAirControl(ms->origin, ms->velocity, end, hordir, &speed)) + { + //go straight to the reachability end + VectorCopy(dir, hordir); + hordir[2] = 0; + // + dist = VectorNormalize(hordir); + speed = 400; + } //end if + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, gapdist, speed, horspeed, sv_jumpvel; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + sv_jumpvel = botlibglobals.sv_jumpvel->value; + //walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + speed = 350; + // + gapdist = BotGapDistance(ms, hordir, ms->entitynum); + //if pretty close to the start focus on the reachability end + if (dist < 50 || (gapdist && gapdist < 50)) + { + //NOTE: using max speed (400) works best + //if (AAS_HorizontalVelocityForJump(sv_jumpvel, ms->origin, reach->end, &horspeed)) + //{ + // speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; + //} //end if + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + // + ms->jumpreach = ms->lastreachnum; + speed = 600; + } //end if + else + { + if (AAS_HorizontalVelocityForJump(sv_jumpvel, reach->start, reach->end, &horspeed)) + { + speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; + } //end if + } //end else + //elemantary action + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +/* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir1, dir2, mins, maxs, start, end; + float dist1, dist2, speed; + bot_moveresult_t result; + bsp_trace_t trace; + + BotClearMoveResult(&result); + // + hordir[0] = reach->start[0] - reach->end[0]; + hordir[1] = reach->start[1] - reach->end[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + //minus back the bouding box size plus 16 + VectorMA(reach->start, 80, hordir, end); + // + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs); + //check for solids + trace = AAS_Trace(start, mins, maxs, end, ms->entitynum, MASK_PLAYERSOLID); + if (trace.startsolid) VectorCopy(start, trace.endpos); + //check for a gap + for (dist1 = 0; dist1 < 80; dist1 += 10) + { + VectorMA(start, dist1+10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) break; + } //end for + if (dist1 < 80) VectorMA(reach->start, dist1, hordir, trace.endpos); +// dist1 = BotGapDistance(start, hordir, ms->entitynum); +// if (dist1 && dist1 <= trace.fraction * 80) VectorMA(reach->start, dist1-20, hordir, trace.endpos); + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, trace.endpos, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run to point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) EA_Jump(ms->client); + else if (dist1 < 32) EA_DelayedJump(ms->client); + EA_Move(ms->client, hordir, 600); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + //botimport.Print(PRT_MESSAGE, "going towards run to point\n"); + hordir[0] = trace.endpos[0] - ms->origin[0]; + hordir[1] = trace.endpos[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) dist2 = 80; + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +//* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir1, dir2, start, end, runstart; +// vec3_t runstart, dir1, dir2, hordir; + float dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + AAS_JumpReachRunStart(reach, runstart); + //* + hordir[0] = runstart[0] - reach->start[0]; + hordir[1] = runstart[1] - reach->start[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + VectorMA(reach->start, 80, hordir, runstart); + //check for a gap + for (dist1 = 0; dist1 < 80; dist1 += 10) + { + VectorMA(start, dist1+10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) break; + } //end for + if (dist1 < 80) VectorMA(reach->start, dist1, hordir, runstart); + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, runstart, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) + { +// botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) EA_Jump(ms->client); + else if (dist1 < 32) EA_DelayedJump(ms->client); + EA_Move(ms->client, hordir, 600); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { +// botimport.Print(PRT_MESSAGE, "going towards run start point\n"); + hordir[0] = runstart[0] - ms->origin[0]; + hordir[1] = runstart[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) dist2 = 80; + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, hordir2; + float speed, dist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if not jumped yet + if (!ms->jumpreach) return result; + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + hordir2[0] = reach->end[0] - reach->start[0]; + hordir2[1] = reach->end[1] - reach->start[1]; + hordir2[2] = 0; + VectorNormalize(hordir2); + // + if (DotProduct(hordir, hordir2) < -0.5 && dist < 24) return result; + //always use max speed when traveling through the air + speed = 800; + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_Jump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Ladder(bot_movestate_t *ms, aas_reachability_t *reach) +{ + //float dist, speed; + vec3_t dir, viewdir;//, hordir; + vec3_t origin = {0, 0, 0}; +// vec3_t up = {0, 0, 1}; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // +// if ((ms->moveflags & MFL_AGAINSTLADDER)) + //NOTE: not a good idea for ladders starting in water + // || !(ms->moveflags & MFL_ONGROUND)) + { + //botimport.Print(PRT_MESSAGE, "against ladder or not on ground\n"); + VectorSubtract(reach->end, ms->origin, dir); + VectorNormalize(dir); + //set the ideal view angles, facing the ladder up or down + viewdir[0] = dir[0]; + viewdir[1] = dir[1]; + viewdir[2] = 3 * dir[2]; + Vector2Angles(viewdir, result.ideal_viewangles); + //elemantary action + EA_Move(ms->client, origin, 0); + EA_MoveForward(ms->client); + //set movement view flag so the AI can see the view is focussed + result.flags |= MOVERESULT_MOVEMENTVIEW; + } //end if +/* else + { + //botimport.Print(PRT_MESSAGE, "moving towards ladder\n"); + VectorSubtract(reach->end, ms->origin, dir); + //make sure the horizontal movement is large anough + VectorCopy(dir, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + dir[0] = hordir[0]; + dir[1] = hordir[1]; + if (dir[2] > 0) dir[2] = 1; + else dir[2] = -1; + if (dist > 50) dist = 50; + speed = 400 - (200 - 4 * dist); + EA_Move(ms->client, dir, speed); + } //end else*/ + //save the movement direction + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotTravel_Ladder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Teleport(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if the bot is being teleported + if (ms->moveflags & MFL_TELEPORTED) return result; + + //walk straight to center of the teleporter + VectorSubtract(reach->start, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + + if (dist < 30) EA_Move(ms->client, hordir, 200); + else EA_Move(ms->client, hordir, 400); + + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + + VectorCopy(hordir, result.movedir); + return result; +} //end of the function BotTravel_Teleport +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, dir1, dir2, hordir, bottomcenter; + float dist, dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if standing on the plat + if (BotOnMover(ms->origin, ms->entitynum, reach)) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot on elevator\n"); +#endif //DEBUG_ELEVATOR + //if vertically not too far from the end point + if (abs(ms->origin[2] - reach->end[2]) < sv_maxbarrier->value) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to end\n"); +#endif //DEBUG_ELEVATOR + //move to the end point + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + if (!BotCheckBarrierJump(ms, hordir, 100)) + { + EA_Move(ms->client, hordir, 400); + } //end if + VectorCopy(hordir, result.movedir); + } //end else + //if not really close to the center of the elevator + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 10) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to center\n"); +#endif //DEBUG_ELEVATOR + //move to the center of the plat + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + } //end if + else + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot not on elevator\n"); +#endif //DEBUG_ELEVATOR + //if very near the reachability end + VectorSubtract(reach->end, ms->origin, dir); + dist = VectorLength(dir); + if (dist < 64) + { + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //stop using this reachability + ms->reachability_time = 0; + return result; + } //end if + //get direction and distance to reachability start + VectorSubtract(reach->start, ms->origin, dir1); + if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0; + dist1 = VectorNormalize(dir1); + //if the elevator isn't down + if (!MoverDown(reach)) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "elevator not down\n"); +#endif //DEBUG_ELEVATOR + dist = dist1; + VectorCopy(dir1, dir); + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //this isn't a failure... just wait till the elevator comes down + result.type = RESULTTYPE_ELEVATORUP; + result.flags |= MOVERESULT_WAITING; + return result; + } //end if + //get direction and distance to elevator bottom center + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, dir2); + if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if very close to the reachability start or + //closer to the elevator center or + //between reachability start and elevator center + if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to center\n"); +#endif //DEBUG_ELEVATOR + dist = dist2; + VectorCopy(dir2, dir); + } //end if + else //closer to the reachability start + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to start\n"); +#endif //DEBUG_ELEVATOR + dist = dist1; + VectorCopy(dir1, dir); + } //end else + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 400 - (400 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + } //end else + return result; +} //end of the function BotTravel_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t bottomcenter, bottomdir, topdir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, bottomdir); + // + VectorSubtract(reach->end, ms->origin, topdir); + // + if (fabs(bottomdir[2]) < fabs(topdir[2])) + { + VectorNormalize(bottomdir); + EA_Move(ms->client, bottomdir, 300); + } //end if + else + { + VectorNormalize(topdir); + EA_Move(ms->client, topdir, 300); + } //end else + return result; +} //end of the function BotFinishTravel_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFuncBobStartEnd(aas_reachability_t *reach, vec3_t start, vec3_t end, vec3_t origin) +{ + int spawnflags, modelnum; + vec3_t mins, maxs, mid, angles = {0, 0, 0}; + int num0, num1; + + modelnum = reach->facenum & 0x0000FFFF; + if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "BotFuncBobStartEnd: no entity with model %d\n", modelnum); + VectorSet(start, 0, 0, 0); + VectorSet(end, 0, 0, 0); + return; + } //end if + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + VectorCopy(mid, start); + VectorCopy(mid, end); + spawnflags = reach->facenum >> 16; + num0 = reach->edgenum >> 16; + if (num0 > 0x00007FFF) num0 |= 0xFFFF0000; + num1 = reach->edgenum & 0x0000FFFF; + if (num1 > 0x00007FFF) num1 |= 0xFFFF0000; + if (spawnflags & 1) + { + start[0] = num0; + end[0] = num1; + // + origin[0] += mid[0]; + origin[1] = mid[1]; + origin[2] = mid[2]; + } //end if + else if (spawnflags & 2) + { + start[1] = num0; + end[1] = num1; + // + origin[0] = mid[0]; + origin[1] += mid[1]; + origin[2] = mid[2]; + } //end else if + else + { + start[2] = num0; + end[2] = num1; + // + origin[0] = mid[0]; + origin[1] = mid[1]; + origin[2] += mid[2]; + } //end else +} //end of the function BotFuncBobStartEnd +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, dir1, dir2, hordir, bottomcenter, bob_start, bob_end, bob_origin; + float dist, dist1, dist2, speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + // + BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); + //if standing ontop of the func_bobbing + if (BotOnMover(ms->origin, ms->entitynum, reach)) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot on func_bobbing\n"); +#endif + //if near end point of reachability + VectorSubtract(bob_origin, bob_end, dir); + if (VectorLength(dir) < 24) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to reachability end\n"); +#endif + //move to the end point + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + if (!BotCheckBarrierJump(ms, hordir, 100)) + { + EA_Move(ms->client, hordir, 400); + } //end if + VectorCopy(hordir, result.movedir); + } //end else + //if not really close to the center of the elevator + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 10) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); +#endif + //move to the center of the plat + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + } //end if + else + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot not ontop of func_bobbing\n"); +#endif + //if very near the reachability end + VectorSubtract(reach->end, ms->origin, dir); + dist = VectorLength(dir); + if (dist < 64) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to end\n"); +#endif + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + //if swimming or no barrier jump + if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //stop using this reachability + ms->reachability_time = 0; + return result; + } //end if + //get direction and distance to reachability start + VectorSubtract(reach->start, ms->origin, dir1); + if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0; + dist1 = VectorNormalize(dir1); + //if func_bobbing is Not it's start position + VectorSubtract(bob_origin, bob_start, dir); + if (VectorLength(dir) > 16) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "func_bobbing not at start\n"); +#endif + dist = dist1; + VectorCopy(dir1, dir); + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //this isn't a failure... just wait till the func_bobbing arrives + result.type = RESULTTYPE_WAITFORFUNCBOBBING; + result.flags |= MOVERESULT_WAITING; + return result; + } //end if + //get direction and distance to func_bob bottom center + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, dir2); + if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if very close to the reachability start or + //closer to the elevator center or + //between reachability start and func_bobbing center + if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); +#endif + dist = dist2; + VectorCopy(dir2, dir); + } //end if + else //closer to the reachability start + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to reachability start\n"); +#endif + dist = dist1; + VectorCopy(dir1, dir); + } //end else + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 400 - (400 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + } //end else + return result; +} //end of the function BotTravel_FuncBobbing +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t bob_origin, bob_start, bob_end, dir, hordir, bottomcenter; + bot_moveresult_t result; + float dist, speed; + + BotClearMoveResult(&result); + // + BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); + // + VectorSubtract(bob_origin, bob_end, dir); + dist = VectorLength(dir); + //if the func_bobbing is near the end + if (dist < 16) + { + VectorSubtract(reach->end, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if (speed > 5) EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + } //end if + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 5) + { + //move to the center of the plat + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + return result; +} //end of the function BotFinishTravel_FuncBobbing +//=========================================================================== +// 0 no valid grapple hook visible +// 1 the grapple hook is still flying +// 2 the grapple hooked into a wall +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GrappleState(bot_movestate_t *ms, aas_reachability_t *reach) +{ + int i; + aas_entityinfo_t entinfo; + + //if the grapple hook is pulling + if (ms->moveflags & MFL_GRAPPLEPULL) + return 2; + //check for a visible grapple missile entity + //or visible grapple entity + for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i)) + { + if (AAS_EntityType(i) == (int) entitytypemissile->value) + { + AAS_EntityInfo(i, &entinfo); + if (entinfo.weapon == (int) weapindex_grapple->value) + { + return 1; + } //end if + } //end if + } //end for + //no valid grapple at all + return 0; +} //end of the function GrappleState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetGrapple(bot_movestate_t *ms) +{ + aas_reachability_t reach; + + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if not using the grapple hook reachability anymore + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_GRAPPLEHOOK) + { + if ((ms->moveflags & MFL_ACTIVEGRAPPLE) || ms->grapplevisible_time) + { + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->grapplevisible_time = 0; +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "reset grapple\n"); +#endif //DEBUG_GRAPPLE + } //end if + } //end if +} //end of the function BotResetGrapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Grapple(bot_movestate_t *ms, aas_reachability_t *reach) +{ + bot_moveresult_t result; + float dist, speed; + vec3_t dir, viewdir, org; + int state, areanum; + bsp_trace_t trace; + +#ifdef DEBUG_GRAPPLE + static int debugline; + if (!debugline) debugline = botimport.DebugLineCreate(); + botimport.DebugLineShow(debugline, reach->start, reach->end, LINECOLOR_BLUE); +#endif //DEBUG_GRAPPLE + + BotClearMoveResult(&result); + // + if (ms->moveflags & MFL_GRAPPLERESET) + { + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + return result; + } //end if + // + if (!(int) offhandgrapple->value) + { + result.weapon = weapindex_grapple->value; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + } //end if + // + if (ms->moveflags & MFL_ACTIVEGRAPPLE) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: active grapple\n"); +#endif //DEBUG_GRAPPLE + // + state = GrappleState(ms, reach); + // + VectorSubtract(reach->end, ms->origin, dir); + dir[2] = 0; + dist = VectorLength(dir); + //if very close to the grapple end or the grappled is hooked and + //the bot doesn't get any closer + if (state && dist < 48) + { + if (ms->lastgrappledist - dist < 1) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_ERROR, "grapple normal end\n"); +#endif //DEBUG_GRAPPLE + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->moveflags |= MFL_GRAPPLERESET; + ms->reachability_time = 0; //end the reachability + return result; + } //end if + } //end if + //if no valid grapple at all, or the grapple hooked and the bot + //isn't moving anymore + else if (!state || (state == 2 && dist > ms->lastgrappledist - 2)) + { + if (ms->grapplevisible_time < AAS_Time() - 0.4) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_ERROR, "grapple not visible\n"); +#endif //DEBUG_GRAPPLE + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->moveflags |= MFL_GRAPPLERESET; + ms->reachability_time = 0; //end the reachability + return result; + } //end if + } //end if + else + { + ms->grapplevisible_time = AAS_Time(); + } //end else + // + if (!(int) offhandgrapple->value) + { + EA_Attack(ms->client); + } //end if + //remember the current grapple distance + ms->lastgrappledist = dist; + } //end if + else + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: inactive grapple\n"); +#endif //DEBUG_GRAPPLE + // + ms->grapplevisible_time = AAS_Time(); + // + VectorSubtract(reach->start, ms->origin, dir); + if (!(ms->moveflags & MFL_SWIMMING)) dir[2] = 0; + VectorAdd(ms->origin, ms->viewoffset, org); + VectorSubtract(reach->end, org, viewdir); + // + dist = VectorNormalize(dir); + Vector2Angles(viewdir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + if (dist < 5 && + fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 2 && + fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 2) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: activating grapple\n"); +#endif //DEBUG_GRAPPLE + //check if the grapple missile path is clear + VectorAdd(ms->origin, ms->viewoffset, org); + trace = AAS_Trace(org, NULL, NULL, reach->end, ms->entitynum, CONTENTS_SOLID); + VectorSubtract(reach->end, trace.endpos, dir); + if (VectorLength(dir) > 16) + { + result.failure = qtrue; + return result; + } //end if + //activate the grapple + if (offhandgrapple->value) + { + EA_Command(ms->client, cmd_grappleon->string); + } //end if + else + { + EA_Attack(ms->client); + } //end else + ms->moveflags |= MFL_ACTIVEGRAPPLE; + ms->lastgrappledist = 999999; + } //end if + else + { + if (dist < 70) speed = 300 - (300 - 4 * dist); + else speed = 400; + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + } //end else + //if in another area before actually grappling + areanum = AAS_PointAreaNum(ms->origin); + if (areanum && areanum != ms->reachareanum) ms->reachability_time = 0; + } //end else + return result; +} //end of the function BotTravel_Grapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_RocketJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t result; + + //botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n"); + BotClearMoveResult(&result); + // + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + // + dist = VectorNormalize(hordir); + //look in the movement direction + Vector2Angles(hordir, result.ideal_viewangles); + //look straight down + result.ideal_viewangles[PITCH] = 90; + // + if (dist < 5 && + fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 && + fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + EA_Attack(ms->client); + EA_Move(ms->client, hordir, 800); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + if (dist > 80) dist = 80; + speed = 400 - (400 - 5 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + //look in the movement direction + Vector2Angles(hordir, result.ideal_viewangles); + //look straight down + result.ideal_viewangles[PITCH] = 90; + //set the view angles directly + EA_View(ms->client, result.ideal_viewangles); + //view is important for the movment + result.flags |= MOVERESULT_MOVEMENTVIEWSET; + //select the rocket launcher + EA_SelectWeapon(ms->client, (int) weapindex_rocketlauncher->value); + //weapon is used for movement + result.weapon = (int) weapindex_rocketlauncher->value; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_RocketJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_BFGJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t result; + + //botimport.Print(PRT_MESSAGE, "BotTravel_BFGJump: bah\n"); + BotClearMoveResult(&result); + // + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + // + dist = VectorNormalize(hordir); + // + if (dist < 5 && + fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 && + fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + EA_Attack(ms->client); + EA_Move(ms->client, hordir, 800); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + if (dist > 80) dist = 80; + speed = 400 - (400 - 5 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + //look in the movement direction + Vector2Angles(hordir, result.ideal_viewangles); + //look straight down + result.ideal_viewangles[PITCH] = 90; + //set the view angles directly + EA_View(ms->client, result.ideal_viewangles); + //view is important for the movment + result.flags |= MOVERESULT_MOVEMENTVIEWSET; + //select the rocket launcher + EA_SelectWeapon(ms->client, (int) weapindex_bfg10k->value); + //weapon is used for movement + result.weapon = (int) weapindex_bfg10k->value; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_BFGJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WeaponJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float speed; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //if not jumped yet + if (!ms->jumpreach) return result; + /* + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //always use max speed when traveling through the air + EA_Move(ms->client, hordir, 800); + VectorCopy(hordir, result.movedir); + */ + // + if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) + { + //go straight to the reachability end + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + speed = 400; + } //end if + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WeaponJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + //first walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + speed = 400; + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_JumpPad +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float speed; + vec3_t hordir; + bot_moveresult_t result; + + BotClearMoveResult(&result); + if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + speed = 400; + } //end if + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_JumpPad +//=========================================================================== +// time before the reachability times out +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReachabilityTime(aas_reachability_t *reach) +{ + switch(reach->traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: return 5; + case TRAVEL_CROUCH: return 5; + case TRAVEL_BARRIERJUMP: return 5; + case TRAVEL_LADDER: return 6; + case TRAVEL_WALKOFFLEDGE: return 5; + case TRAVEL_JUMP: return 5; + case TRAVEL_SWIM: return 5; + case TRAVEL_WATERJUMP: return 5; + case TRAVEL_TELEPORT: return 5; + case TRAVEL_ELEVATOR: return 10; + case TRAVEL_GRAPPLEHOOK: return 8; + case TRAVEL_ROCKETJUMP: return 6; + case TRAVEL_BFGJUMP: return 6; + case TRAVEL_JUMPPAD: return 10; + case TRAVEL_FUNCBOB: return 10; + default: + { + botimport.Print(PRT_ERROR, "travel type %d not implemented yet\n", reach->traveltype); + return 8; + } //end case + } //end switch +} //end of the function BotReachabilityTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotMoveInGoalArea(bot_movestate_t *ms, bot_goal_t *goal) +{ + bot_moveresult_t result; + vec3_t dir; + float dist, speed; + +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%s: moving straight to goal\n", ClientName(ms->entitynum-1)); + //AAS_ClearShownDebugLines(); + //AAS_DebugLine(ms->origin, goal->origin, LINECOLOR_RED); +#endif //DEBUG + BotClearMoveResult(&result); + //walk straight to the goal origin + dir[0] = goal->origin[0] - ms->origin[0]; + dir[1] = goal->origin[1] - ms->origin[1]; + if (ms->moveflags & MFL_SWIMMING) + { + dir[2] = goal->origin[2] - ms->origin[2]; + result.traveltype = TRAVEL_SWIM; + } //end if + else + { + dir[2] = 0; + result.traveltype = TRAVEL_WALK; + } //endif + // + dist = VectorNormalize(dir); + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + if (speed < 10) speed = 0; + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_SWIMVIEW; + } //end if + //if (!debugline) debugline = botimport.DebugLineCreate(); + //botimport.DebugLineShow(debugline, ms->origin, goal->origin, LINECOLOR_BLUE); + // + ms->lastreachnum = 0; + ms->lastareanum = 0; + ms->lastgoalareanum = goal->areanum; + VectorCopy(ms->origin, ms->lastorigin); + // + return result; +} //end of the function BotMoveInGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags) +{ + int reachnum, lastreachnum, foundjumppad, ent, resultflags; + aas_reachability_t reach, lastreach; + bot_movestate_t *ms; + //vec3_t mins, maxs, up = {0, 0, 1}; + //bsp_trace_t trace; + //static int debugline; + + + BotClearMoveResult(result); + // + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + //reset the grapple before testing if the bot has a valid goal + //because the bot could loose all it's goals when stuck to a wall + BotResetGrapple(ms); + // + if (!goal) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "client %d: movetogoal -> no goal\n", ms->client); +#endif //DEBUG + result->failure = qtrue; + return; + } //end if + //botimport.Print(PRT_MESSAGE, "numavoidreach = %d\n", ms->numavoidreach); + //remove some of the move flags + ms->moveflags &= ~(MFL_SWIMMING|MFL_AGAINSTLADDER); + //set some of the move flags + //NOTE: the MFL_ONGROUND flag is also set in the higher AI + if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND; + // + if (ms->moveflags & MFL_ONGROUND) + { + int modeltype, modelnum; + + ent = BotOnTopOfEntity(ms); + + if (ent != -1) + { + modelnum = AAS_EntityModelindex(ent); + if (modelnum >= 0 && modelnum < MAX_MODELS) + { + modeltype = modeltypes[modelnum]; + + if (modeltype == MODELTYPE_FUNC_PLAT) + { + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if the bot is Not using the elevator + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR || + //NOTE: the face number is the plat model number + (reach.facenum & 0x0000FFFF) != modelnum) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_plat\n", ms->client); + AAS_ReachabilityFromNum(reachnum, &reach); + ms->lastreachnum = reachnum; + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + } //end if + else + { + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "client %d: on func_plat without reachability\n", ms->client); + } //end if + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + result->flags |= MOVERESULT_ONTOPOF_ELEVATOR; + } //end if + else if (modeltype == MODELTYPE_FUNC_BOB) + { + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if the bot is Not using the func bobbing + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB || + //NOTE: the face number is the func_bobbing model number + (reach.facenum & 0x0000FFFF) != modelnum) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_bobbing\n", ms->client); + AAS_ReachabilityFromNum(reachnum, &reach); + ms->lastreachnum = reachnum; + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + } //end if + else + { + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "client %d: on func_bobbing without reachability\n", ms->client); + } //end if + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + result->flags |= MOVERESULT_ONTOPOF_FUNCBOB; + } //end if + else if (modeltype == MODELTYPE_FUNC_STATIC || modeltype == MODELTYPE_FUNC_DOOR) + { + // check if ontop of a door bridge ? + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + // if not in a reachability area + if (!AAS_AreaReachability(ms->areanum)) + { + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end if + } //end else if + else + { + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + } //end if + } //end if + //if swimming + if (AAS_Swimming(ms->origin)) ms->moveflags |= MFL_SWIMMING; + //if against a ladder + if (AAS_AgainstLadder(ms->origin)) ms->moveflags |= MFL_AGAINSTLADDER; + //if the bot is on the ground, swimming or against a ladder + if (ms->moveflags & (MFL_ONGROUND|MFL_SWIMMING|MFL_AGAINSTLADDER)) + { + //botimport.Print(PRT_MESSAGE, "%s: onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); + // + AAS_ReachabilityFromNum(ms->lastreachnum, &lastreach); + //reachability area the bot is in + //ms->areanum = BotReachabilityArea(ms->origin, ((lastreach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR)); + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + // + if ( !ms->areanum ) + { + result->failure = qtrue; + result->blocked = qtrue; + result->blockentity = 0; + result->type = RESULTTYPE_INSOLIDAREA; + return; + } //end if + //if the bot is in the goal area + if (ms->areanum == goal->areanum) + { + *result = BotMoveInGoalArea(ms, goal); + return; + } //end if + //assume we can use the reachability from the last frame + reachnum = ms->lastreachnum; + //if there is a last reachability + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + //check if the reachability is still valid + if (!(AAS_TravelFlagForType(reach.traveltype) & travelflags)) + { + reachnum = 0; + } //end if + //special grapple hook case + else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_GRAPPLEHOOK) + { + if (ms->reachability_time < AAS_Time() || + (ms->moveflags & MFL_GRAPPLERESET)) + { + reachnum = 0; + } //end if + } //end if + //special elevator case + else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR || + (reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) + { + if ((result->flags & MOVERESULT_ONTOPOF_FUNCBOB) || + (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)) + { + ms->reachability_time = AAS_Time() + 5; + } //end if + //if the bot was going for an elevator and reached the reachability area + if (ms->areanum == reach.areanum || + ms->reachability_time < AAS_Time()) + { + reachnum = 0; + } //end if + } //end if + else + { +#ifndef _XBOX +#ifdef DEBUG + if (bot_developer) + { + if (ms->reachability_time < AAS_Time()) + { + botimport.Print(PRT_MESSAGE, "client %d: reachability timeout in ", ms->client); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + /* + if (ms->lastareanum != ms->areanum) + { + botimport.Print(PRT_MESSAGE, "changed from area %d to %d\n", ms->lastareanum, ms->areanum); + } //end if*/ + } //end if +#endif //DEBUG +#endif + //if the goal area changed or the reachability timed out + //or the area changed + if (ms->lastgoalareanum != goal->areanum || + ms->reachability_time < AAS_Time() || + ms->lastareanum != ms->areanum) + { + reachnum = 0; + //botimport.Print(PRT_MESSAGE, "area change or timeout\n"); + } //end else if + } //end else + } //end if + resultflags = 0; + //if the bot needs a new reachability + if (!reachnum) + { + //if the area has no reachability links + if (!AAS_AreaReachability(ms->areanum)) + { +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "area %d no reachability\n", ms->areanum); + } //end if +#endif //DEBUG + } //end if + //get a new reachability leading towards the goal + reachnum = BotGetReachabilityToGoal(ms->origin, ms->areanum, + ms->lastgoalareanum, ms->lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, travelflags, + ms->avoidspots, ms->numavoidspots, &resultflags); + //the area number the reachability starts in + ms->reachareanum = ms->areanum; + //reset some state variables + ms->jumpreach = 0; //for TRAVEL_JUMP + ms->moveflags &= ~MFL_GRAPPLERESET; //for TRAVEL_GRAPPLEHOOK + //if there is a reachability to the goal + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + //set a timeout for this reachability + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + // +#ifdef AVOIDREACH + //add the reachability to the reachabilities to avoid for a while + BotAddToAvoidReach(ms, reachnum, AVOIDREACH_TIME); +#endif //AVOIDREACH + } //end if +#ifdef DEBUG + + else if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "goal not reachable\n"); + Com_Memset(&reach, 0, sizeof(aas_reachability_t)); //make compiler happy + } //end else + if (bot_developer) + { + //if still going for the same goal + if (ms->lastgoalareanum == goal->areanum) + { + if (ms->lastareanum == reach.areanum) + { + botimport.Print(PRT_MESSAGE, "same goal, going back to previous area\n"); + } //end if + } //end if + } //end if +#endif //DEBUG + } //end else + // + ms->lastreachnum = reachnum; + ms->lastgoalareanum = goal->areanum; + ms->lastareanum = ms->areanum; + //if the bot has a reachability + if (reachnum) + { + //get the reachability from the number + AAS_ReachabilityFromNum(reachnum, &reach); + result->traveltype = reach.traveltype; + // +#ifdef DEBUG_AI_MOVE + AAS_ClearShownDebugLines(); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + AAS_ShowReachability(&reach); +#endif //DEBUG_AI_MOVE + // +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "client %d: ", ms->client); + //AAS_PrintTravelType(reach.traveltype); + //botimport.Print(PRT_MESSAGE, "\n"); +#endif //DEBUG + switch(reach.traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break; + case TRAVEL_CROUCH: *result = BotTravel_Crouch(ms, &reach); break; + case TRAVEL_BARRIERJUMP: *result = BotTravel_BarrierJump(ms, &reach); break; + case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; + case TRAVEL_WALKOFFLEDGE: *result = BotTravel_WalkOffLedge(ms, &reach); break; + case TRAVEL_JUMP: *result = BotTravel_Jump(ms, &reach); break; + case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; + case TRAVEL_WATERJUMP: *result = BotTravel_WaterJump(ms, &reach); break; + case TRAVEL_TELEPORT: *result = BotTravel_Teleport(ms, &reach); break; + case TRAVEL_ELEVATOR: *result = BotTravel_Elevator(ms, &reach); break; + case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; + case TRAVEL_ROCKETJUMP: *result = BotTravel_RocketJump(ms, &reach); break; + case TRAVEL_BFGJUMP: *result = BotTravel_BFGJump(ms, &reach); break; + case TRAVEL_JUMPPAD: *result = BotTravel_JumpPad(ms, &reach); break; + case TRAVEL_FUNCBOB: *result = BotTravel_FuncBobbing(ms, &reach); break; + default: + { + botimport.Print(PRT_FATAL, "travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK)); + break; + } //end case + } //end switch + result->traveltype = reach.traveltype; + result->flags |= resultflags; + } //end if + else + { + result->failure = qtrue; + result->flags |= resultflags; + Com_Memset(&reach, 0, sizeof(aas_reachability_t)); + } //end else +#ifndef _XBOX +#ifdef DEBUG + if (bot_developer) + { + if (result->failure) + { + botimport.Print(PRT_MESSAGE, "client %d: movement failure in ", ms->client); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + } //end if +#endif //DEBUG +#endif + } //end if + else + { + int i, numareas, areas[16]; + vec3_t end; + + //special handling of jump pads when the bot uses a jump pad without knowing it + foundjumppad = qfalse; + VectorMA(ms->origin, -2 * ms->thinktime, ms->velocity, end); + numareas = AAS_TraceAreas(ms->origin, end, areas, NULL, 16); + for (i = numareas-1; i >= 0; i--) + { + if (AAS_AreaJumpPad(areas[i])) + { + //botimport.Print(PRT_MESSAGE, "client %d used a jumppad without knowing, area %d\n", ms->client, areas[i]); + foundjumppad = qtrue; + lastreachnum = BotGetReachabilityToGoal(end, areas[i], + ms->lastgoalareanum, ms->lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, TFL_JUMPPAD, ms->avoidspots, ms->numavoidspots, NULL); + if (lastreachnum) + { + ms->lastreachnum = lastreachnum; + ms->lastareanum = areas[i]; + //botimport.Print(PRT_MESSAGE, "found jumppad reachability\n"); + break; + } //end if + else + { + for (lastreachnum = AAS_NextAreaReachability(areas[i], 0); lastreachnum; + lastreachnum = AAS_NextAreaReachability(areas[i], lastreachnum)) + { + //get the reachability from the number + AAS_ReachabilityFromNum(lastreachnum, &reach); + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) + { + ms->lastreachnum = lastreachnum; + ms->lastareanum = areas[i]; + //botimport.Print(PRT_MESSAGE, "found jumppad reachability hard!!\n"); + break; + } //end if + } //end for + if (lastreachnum) break; + } //end else + } //end if + } //end for + if (bot_developer) + { + //if a jumppad is found with the trace but no reachability is found + if (foundjumppad && !ms->lastreachnum) + { + botimport.Print(PRT_MESSAGE, "client %d didn't find jumppad reachability\n", ms->client); + } //end if + } //end if + // + if (ms->lastreachnum) + { + //botimport.Print(PRT_MESSAGE, "%s: NOT onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + result->traveltype = reach.traveltype; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "client %d finish: ", ms->client); + //AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + //botimport.Print(PRT_MESSAGE, "\n"); +#endif //DEBUG + // + switch(reach.traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break;//BotFinishTravel_Walk(ms, &reach); break; + case TRAVEL_CROUCH: /*do nothing*/ break; + case TRAVEL_BARRIERJUMP: *result = BotFinishTravel_BarrierJump(ms, &reach); break; + case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; + case TRAVEL_WALKOFFLEDGE: *result = BotFinishTravel_WalkOffLedge(ms, &reach); break; + case TRAVEL_JUMP: *result = BotFinishTravel_Jump(ms, &reach); break; + case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; + case TRAVEL_WATERJUMP: *result = BotFinishTravel_WaterJump(ms, &reach); break; + case TRAVEL_TELEPORT: /*do nothing*/ break; + case TRAVEL_ELEVATOR: *result = BotFinishTravel_Elevator(ms, &reach); break; + case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; + case TRAVEL_ROCKETJUMP: + case TRAVEL_BFGJUMP: *result = BotFinishTravel_WeaponJump(ms, &reach); break; + case TRAVEL_JUMPPAD: *result = BotFinishTravel_JumpPad(ms, &reach); break; + case TRAVEL_FUNCBOB: *result = BotFinishTravel_FuncBobbing(ms, &reach); break; + default: + { + botimport.Print(PRT_FATAL, "(last) travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK)); + break; + } //end case + } //end switch + result->traveltype = reach.traveltype; +#ifndef _XBOX +#ifdef DEBUG + if (bot_developer) + { + if (result->failure) + { + botimport.Print(PRT_MESSAGE, "client %d: movement failure in finish ", ms->client); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + } //end if +#endif //DEBUG +#endif + } //end if + } //end else + //FIXME: is it right to do this here? + if (result->blocked) ms->reachability_time -= 10 * ms->thinktime; + //copy the last origin + VectorCopy(ms->origin, ms->lastorigin); + //return the movement result + return; +} //end of the function BotMoveToGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetAvoidReach(int movestate) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + Com_Memset(ms->avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); + Com_Memset(ms->avoidreachtimes, 0, MAX_AVOIDREACH * sizeof(float)); + Com_Memset(ms->avoidreachtries, 0, MAX_AVOIDREACH * sizeof(int)); +} //end of the function BotResetAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetLastAvoidReach(int movestate) +{ + int i, latest; + float latesttime; + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + latesttime = 0; + latest = 0; + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreachtimes[i] > latesttime) + { + latesttime = ms->avoidreachtimes[i]; + latest = i; + } //end if + } //end for + if (latesttime) + { + ms->avoidreachtimes[latest] = 0; + if (ms->avoidreachtries[i] > 0) ms->avoidreachtries[latest]--; + } //end if +} //end of the function BotResetLastAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetMoveState(int movestate) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + Com_Memset(ms, 0, sizeof(bot_movestate_t)); +} //end of the function BotResetMoveState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupMoveAI(void) +{ + BotSetBrushModelTypes(); + sv_maxstep = LibVar("sv_step", "18"); + sv_maxbarrier = LibVar("sv_maxbarrier", "32"); + sv_gravity = LibVar("sv_gravity", "800"); + weapindex_rocketlauncher = LibVar("weapindex_rocketlauncher", "5"); + weapindex_bfg10k = LibVar("weapindex_bfg10k", "9"); + weapindex_grapple = LibVar("weapindex_grapple", "10"); + entitytypemissile = LibVar("entitytypemissile", "3"); + offhandgrapple = LibVar("offhandgrapple", "0"); + cmd_grappleon = LibVar("cmd_grappleon", "grappleon"); + cmd_grappleoff = LibVar("cmd_grappleoff", "grappleoff"); + return BLERR_NOERROR; +} //end of the function BotSetupMoveAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownMoveAI(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botmovestates[i]) + { + FreeMemory(botmovestates[i]); + botmovestates[i] = NULL; + } //end if + } //end for +} //end of the function BotShutdownMoveAI + + diff --git a/codemp/botlib/be_ai_weap.cpp b/codemp/botlib/be_ai_weap.cpp new file mode 100644 index 0000000..c5f04a8 --- /dev/null +++ b/codemp/botlib/be_ai_weap.cpp @@ -0,0 +1,526 @@ + +/***************************************************************************** + * name: be_ai_weap.c + * + * desc: weapon AI + * + * $Archive: /MissionPack/code/botlib/be_ai_weap.c $ + * $Author: Ttimo $ + * $Revision: 6 $ + * $Modtime: 4/13/01 4:45p $ + * $Date: 4/13/01 4:45p $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_libvar.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" //fuzzy weights +#include "../game/be_ai_weap.h" + +//#define DEBUG_AI_WEAP + +//structure field offsets +#define WEAPON_OFS(x) (int)&(((weaponinfo_t *)0)->x) +#define PROJECTILE_OFS(x) (int)&(((projectileinfo_t *)0)->x) + +//weapon definition // bk001212 - static +static fielddef_t weaponinfo_fields[] = +{ +{"number", WEAPON_OFS(number), FT_INT}, //weapon number +{"name", WEAPON_OFS(name), FT_STRING}, //name of the weapon +{"level", WEAPON_OFS(level), FT_INT}, +{"model", WEAPON_OFS(model), FT_STRING}, //model of the weapon +{"weaponindex", WEAPON_OFS(weaponindex), FT_INT}, //index of weapon in inventory +{"flags", WEAPON_OFS(flags), FT_INT}, //special flags +{"projectile", WEAPON_OFS(projectile), FT_STRING}, //projectile used by the weapon +{"numprojectiles", WEAPON_OFS(numprojectiles), FT_INT}, //number of projectiles +{"hspread", WEAPON_OFS(hspread), FT_FLOAT}, //horizontal spread of projectiles (degrees from middle) +{"vspread", WEAPON_OFS(vspread), FT_FLOAT}, //vertical spread of projectiles (degrees from middle) +{"speed", WEAPON_OFS(speed), FT_FLOAT}, //speed of the projectile (0 = instant hit) +{"acceleration", WEAPON_OFS(acceleration), FT_FLOAT}, //"acceleration" * time (in seconds) + "speed" = projectile speed +{"recoil", WEAPON_OFS(recoil), FT_FLOAT|FT_ARRAY, 3}, //amount of recoil the player gets from the weapon +{"offset", WEAPON_OFS(offset), FT_FLOAT|FT_ARRAY, 3}, //projectile start offset relative to eye and view angles +{"angleoffset", WEAPON_OFS(angleoffset), FT_FLOAT|FT_ARRAY, 3},//offset of the shoot angles relative to the view angles +{"extrazvelocity", WEAPON_OFS(extrazvelocity), FT_FLOAT},//extra z velocity the projectile gets +{"ammoamount", WEAPON_OFS(ammoamount), FT_INT}, //ammo amount used per shot +{"ammoindex", WEAPON_OFS(ammoindex), FT_INT}, //index of ammo in inventory +{"activate", WEAPON_OFS(activate), FT_FLOAT}, //time it takes to select the weapon +{"reload", WEAPON_OFS(reload), FT_FLOAT}, //time it takes to reload the weapon +{"spinup", WEAPON_OFS(spinup), FT_FLOAT}, //time it takes before first shot +{"spindown", WEAPON_OFS(spindown), FT_FLOAT}, //time it takes before weapon stops firing +{NULL, 0, 0, 0} +}; + +//projectile definition +static fielddef_t projectileinfo_fields[] = +{ +{"name", PROJECTILE_OFS(name), FT_STRING}, //name of the projectile +{"model", WEAPON_OFS(model), FT_STRING}, //model of the projectile +{"flags", PROJECTILE_OFS(flags), FT_INT}, //special flags +{"gravity", PROJECTILE_OFS(gravity), FT_FLOAT}, //amount of gravity applied to the projectile [0,1] +{"damage", PROJECTILE_OFS(damage), FT_INT}, //damage of the projectile +{"radius", PROJECTILE_OFS(radius), FT_FLOAT}, //radius of damage +{"visdamage", PROJECTILE_OFS(visdamage), FT_INT}, //damage of the projectile to visible entities +{"damagetype", PROJECTILE_OFS(damagetype), FT_INT}, //type of damage (combination of the DAMAGETYPE_? flags) +{"healthinc", PROJECTILE_OFS(healthinc), FT_INT}, //health increase the owner gets +{"push", PROJECTILE_OFS(push), FT_FLOAT}, //amount a player is pushed away from the projectile impact +{"detonation", PROJECTILE_OFS(detonation), FT_FLOAT}, //time before projectile explodes after fire pressed +{"bounce", PROJECTILE_OFS(bounce), FT_FLOAT}, //amount the projectile bounces +{"bouncefric", PROJECTILE_OFS(bouncefric), FT_FLOAT}, //amount the bounce decreases per bounce +{"bouncestop", PROJECTILE_OFS(bouncestop), FT_FLOAT}, //minimum bounce value before bouncing stops +//recurive projectile definition?? +{NULL, 0, 0, 0} +}; + +static structdef_t weaponinfo_struct = +{ + sizeof(weaponinfo_t), weaponinfo_fields +}; +static structdef_t projectileinfo_struct = +{ + sizeof(projectileinfo_t), projectileinfo_fields +}; + +//weapon configuration: set of weapons with projectiles +typedef struct weaponconfig_s +{ + int numweapons; + int numprojectiles; + projectileinfo_t *projectileinfo; + weaponinfo_t *weaponinfo; +} weaponconfig_t; + +//the bot weapon state +typedef struct bot_weaponstate_s +{ + struct weightconfig_s *weaponweightconfig; //weapon weight configuration + int *weaponweightindex; //weapon weight index +} bot_weaponstate_t; + +static bot_weaponstate_t *botweaponstates[MAX_CLIENTS+1]; +static weaponconfig_t *weaponconfig; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotValidWeaponNumber(int weaponnum) +{ + if (weaponnum <= 0 || weaponnum > weaponconfig->numweapons) + { + botimport.Print(PRT_ERROR, "weapon number out of range\n"); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidWeaponNumber +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_weaponstate_t *BotWeaponStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botweaponstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return NULL; + } //end if + return botweaponstates[handle]; +} //end of the function BotWeaponStateFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef DEBUG_AI_WEAP +void DumpWeaponConfig(weaponconfig_t *wc) +{ + FILE *fp; + int i; + + fp = Log_FileStruct(); + if (!fp) return; + for (i = 0; i < wc->numprojectiles; i++) + { + WriteStructure(fp, &projectileinfo_struct, (char *) &wc->projectileinfo[i]); + Log_Flush(); + } //end for + for (i = 0; i < wc->numweapons; i++) + { + WriteStructure(fp, &weaponinfo_struct, (char *) &wc->weaponinfo[i]); + Log_Flush(); + } //end for +} //end of the function DumpWeaponConfig +#endif //DEBUG_AI_WEAP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +weaponconfig_t *LoadWeaponConfig(char *filename) +{ + int max_weaponinfo, max_projectileinfo; + token_t token; + char path[MAX_PATH]; + int i, j; + source_t *source; + weaponconfig_t *wc; + weaponinfo_t weaponinfo; + + max_weaponinfo = (int) LibVarValue("max_weaponinfo", "32"); + if (max_weaponinfo < 0) + { + botimport.Print(PRT_ERROR, "max_weaponinfo = %d\n", max_weaponinfo); + max_weaponinfo = 32; + LibVarSet("max_weaponinfo", "32"); + } //end if + max_projectileinfo = (int) LibVarValue("max_projectileinfo", "32"); + if (max_projectileinfo < 0) + { + botimport.Print(PRT_ERROR, "max_projectileinfo = %d\n", max_projectileinfo); + max_projectileinfo = 32; + LibVarSet("max_projectileinfo", "32"); + } //end if + strncpy(path, filename, MAX_PATH); + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(path); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", path); + return NULL; + } //end if + //initialize weapon config + wc = (weaponconfig_t *) GetClearedHunkMemory(sizeof(weaponconfig_t) + + max_weaponinfo * sizeof(weaponinfo_t) + + max_projectileinfo * sizeof(projectileinfo_t)); + wc->weaponinfo = (weaponinfo_t *) ((char *) wc + sizeof(weaponconfig_t)); + wc->projectileinfo = (projectileinfo_t *) ((char *) wc->weaponinfo + + max_weaponinfo * sizeof(weaponinfo_t)); + wc->numweapons = max_weaponinfo; + wc->numprojectiles = 0; + //parse the source file + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "weaponinfo")) + { + Com_Memset(&weaponinfo, 0, sizeof(weaponinfo_t)); + if (!ReadStructure(source, &weaponinfo_struct, (char *) &weaponinfo)) + { + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + if (weaponinfo.number < 0 || weaponinfo.number >= max_weaponinfo) + { + botimport.Print(PRT_ERROR, "weapon info number %d out of range in %s\n", weaponinfo.number, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + Com_Memcpy(&wc->weaponinfo[weaponinfo.number], &weaponinfo, sizeof(weaponinfo_t)); + wc->weaponinfo[weaponinfo.number].valid = qtrue; + } //end if + else if (!strcmp(token.string, "projectileinfo")) + { + if (wc->numprojectiles >= max_projectileinfo) + { + botimport.Print(PRT_ERROR, "more than %d projectiles defined in %s\n", max_projectileinfo, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + Com_Memset(&wc->projectileinfo[wc->numprojectiles], 0, sizeof(projectileinfo_t)); + if (!ReadStructure(source, &projectileinfo_struct, (char *) &wc->projectileinfo[wc->numprojectiles])) + { + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + wc->numprojectiles++; + } //end if + else + { + botimport.Print(PRT_ERROR, "unknown definition %s in %s\n", token.string, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end else + } //end while + FreeSource(source); + //fix up weapons + for (i = 0; i < wc->numweapons; i++) + { + if (!wc->weaponinfo[i].valid) continue; + if (!wc->weaponinfo[i].name[0]) + { + botimport.Print(PRT_ERROR, "weapon %d has no name in %s\n", i, path); + FreeMemory(wc); + return NULL; + } //end if + if (!wc->weaponinfo[i].projectile[0]) + { + botimport.Print(PRT_ERROR, "weapon %s has no projectile in %s\n", wc->weaponinfo[i].name, path); + FreeMemory(wc); + return NULL; + } //end if + //find the projectile info and copy it to the weapon info + for (j = 0; j < wc->numprojectiles; j++) + { + if (!strcmp(wc->projectileinfo[j].name, wc->weaponinfo[i].projectile)) + { + Com_Memcpy(&wc->weaponinfo[i].proj, &wc->projectileinfo[j], sizeof(projectileinfo_t)); + break; + } //end if + } //end for + if (j == wc->numprojectiles) + { + botimport.Print(PRT_ERROR, "weapon %s uses undefined projectile in %s\n", wc->weaponinfo[i].name, path); + FreeMemory(wc); + return NULL; + } //end if + } //end for + if (!wc->numweapons) botimport.Print(PRT_WARNING, "no weapon info loaded\n"); + botimport.Print(PRT_MESSAGE, "loaded %s\n", path); + return wc; +} //end of the function LoadWeaponConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int *WeaponWeightIndex(weightconfig_t *wwc, weaponconfig_t *wc) +{ + int *index, i; + + //initialize item weight index + index = (int *) GetClearedMemory(sizeof(int) * wc->numweapons); + + for (i = 0; i < wc->numweapons; i++) + { + index[i] = FindFuzzyWeight(wwc, wc->weaponinfo[i].name); + } //end for + return index; +} //end of the function WeaponWeightIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeWeaponWeights(int weaponstate) +{ + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return; + if (ws->weaponweightconfig) FreeWeightConfig(ws->weaponweightconfig); + if (ws->weaponweightindex) FreeMemory(ws->weaponweightindex); +} //end of the function BotFreeWeaponWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadWeaponWeights(int weaponstate, char *filename) +{ + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return BLERR_CANNOTLOADWEAPONWEIGHTS; + BotFreeWeaponWeights(weaponstate); + // + ws->weaponweightconfig = ReadWeightConfig(filename); + if (!ws->weaponweightconfig) + { + botimport.Print(PRT_FATAL, "couldn't load weapon config %s\n", filename); + return BLERR_CANNOTLOADWEAPONWEIGHTS; + } //end if + if (!weaponconfig) return BLERR_CANNOTLOADWEAPONCONFIG; + ws->weaponweightindex = WeaponWeightIndex(ws->weaponweightconfig, weaponconfig); + return BLERR_NOERROR; +} //end of the function BotLoadWeaponWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo) +{ + bot_weaponstate_t *ws; + + if (!BotValidWeaponNumber(weapon)) return; + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return; + if (!weaponconfig) return; + Com_Memcpy(weaponinfo, &weaponconfig->weaponinfo[weapon], sizeof(weaponinfo_t)); +} //end of the function BotGetWeaponInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseBestFightWeapon(int weaponstate, int *inventory) +{ + int i, index, bestweapon; + float weight, bestweight; + weaponconfig_t *wc; + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return 0; + wc = weaponconfig; + if (!weaponconfig) return 0; + + //if the bot has no weapon weight configuration + if (!ws->weaponweightconfig) return 0; + + bestweight = 0; + bestweapon = 0; + for (i = 0; i < wc->numweapons; i++) + { + if (!wc->weaponinfo[i].valid) continue; + index = ws->weaponweightindex[i]; + if (index < 0) continue; + weight = FuzzyWeight(inventory, ws->weaponweightconfig, index); + if (weight > bestweight) + { + bestweight = weight; + bestweapon = i; + } //end if + } //end for + return bestweapon; +} //end of the function BotChooseBestFightWeapon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetWeaponState(int weaponstate) +{ + struct weightconfig_s *weaponweightconfig; + int *weaponweightindex; + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return; + weaponweightconfig = ws->weaponweightconfig; + weaponweightindex = ws->weaponweightindex; + + //Com_Memset(ws, 0, sizeof(bot_weaponstate_t)); + ws->weaponweightconfig = weaponweightconfig; + ws->weaponweightindex = weaponweightindex; +} //end of the function BotResetWeaponState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocWeaponState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botweaponstates[i]) + { + botweaponstates[i] = (struct bot_weaponstate_s *)GetClearedMemory(sizeof(bot_weaponstate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocWeaponState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeWeaponState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return; + } //end if + if (!botweaponstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return; + } //end if + BotFreeWeaponWeights(handle); + FreeMemory(botweaponstates[handle]); + botweaponstates[handle] = NULL; +} //end of the function BotFreeWeaponState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupWeaponAI(void) +{ + char *file; + + file = LibVarString("weaponconfig", "weapons.c"); + weaponconfig = LoadWeaponConfig(file); + if (!weaponconfig) + { + botimport.Print(PRT_FATAL, "couldn't load the weapon config\n"); + return BLERR_CANNOTLOADWEAPONCONFIG; + } //end if + +#ifdef DEBUG_AI_WEAP + DumpWeaponConfig(weaponconfig); +#endif //DEBUG_AI_WEAP + // + return BLERR_NOERROR; +} //end of the function BotSetupWeaponAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownWeaponAI(void) +{ + int i; + + if (weaponconfig) FreeMemory(weaponconfig); + weaponconfig = NULL; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botweaponstates[i]) + { + BotFreeWeaponState(i); + } //end if + } //end for +} //end of the function BotShutdownWeaponAI + diff --git a/codemp/botlib/be_ai_weight.cpp b/codemp/botlib/be_ai_weight.cpp new file mode 100644 index 0000000..842a3e6 --- /dev/null +++ b/codemp/botlib/be_ai_weight.cpp @@ -0,0 +1,895 @@ + +/***************************************************************************** + * name: be_ai_weight.c + * + * desc: fuzzy logic + * + * $Archive: /MissionPack/code/botlib/be_ai_weight.c $ + * $Author: Mrelusive $ + * $Revision: 3 $ + * $Modtime: 8/06/00 5:25p $ + * $Date: 8/06/00 11:07p $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" + +#define MAX_INVENTORYVALUE 999999 +#define EVALUATERECURSIVELY + +#define MAX_WEIGHT_FILES 128 +weightconfig_t *weightFileList[MAX_WEIGHT_FILES]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadValue(source_t *source, float *value) +{ + token_t token; + + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + if (!strcmp(token.string, "-")) + { + SourceWarning(source, "negative value set to zero\n"); + if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) return qfalse; + } //end if + if (token.type != TT_NUMBER) + { + SourceError(source, "invalid return value %s\n", token.string); + return qfalse; + } //end if + *value = token.floatvalue; + return qtrue; +} //end of the function ReadValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadFuzzyWeight(source_t *source, fuzzyseperator_t *fs) +{ + if (PC_CheckTokenString(source, "balance")) + { + fs->type = WT_BALANCE; + if (!PC_ExpectTokenString(source, "(")) return qfalse; + if (!ReadValue(source, &fs->weight)) return qfalse; + if (!PC_ExpectTokenString(source, ",")) return qfalse; + if (!ReadValue(source, &fs->minweight)) return qfalse; + if (!PC_ExpectTokenString(source, ",")) return qfalse; + if (!ReadValue(source, &fs->maxweight)) return qfalse; + if (!PC_ExpectTokenString(source, ")")) return qfalse; + } //end if + else + { + fs->type = 0; + if (!ReadValue(source, &fs->weight)) return qfalse; + fs->minweight = fs->weight; + fs->maxweight = fs->weight; + } //end if + if (!PC_ExpectTokenString(source, ";")) return qfalse; + return qtrue; +} //end of the function ReadFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeFuzzySeperators_r(fuzzyseperator_t *fs) +{ + if (!fs) return; + if (fs->child) FreeFuzzySeperators_r(fs->child); + if (fs->next) FreeFuzzySeperators_r(fs->next); + FreeMemory(fs); +} //end of the function FreeFuzzySeperators +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeWeightConfig2(weightconfig_t *config) +{ + int i; + + for (i = 0; i < config->numweights; i++) + { + FreeFuzzySeperators_r(config->weights[i].firstseperator); + if (config->weights[i].name) FreeMemory(config->weights[i].name); + } //end for + FreeMemory(config); +} //end of the function FreeWeightConfig2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeWeightConfig(weightconfig_t *config) +{ + if (!LibVarGetValue("bot_reloadcharacters")) return; + FreeWeightConfig2(config); +} //end of the function FreeWeightConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +fuzzyseperator_t *ReadFuzzySeperators_r(source_t *source) +{ + int newindent, index, def, founddefault; + token_t token; + fuzzyseperator_t *fs, *lastfs, *firstfs; + + founddefault = qfalse; + firstfs = NULL; + lastfs = NULL; + if (!PC_ExpectTokenString(source, "(")) return NULL; + if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) return NULL; + index = token.intvalue; + if (!PC_ExpectTokenString(source, ")")) return NULL; + if (!PC_ExpectTokenString(source, "{")) return NULL; + if (!PC_ExpectAnyToken(source, &token)) return NULL; + do + { + def = !strcmp(token.string, "default"); + if (def || !strcmp(token.string, "case")) + { + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = index; + if (lastfs) lastfs->next = fs; + else firstfs = fs; + lastfs = fs; + if (def) + { + if (founddefault) + { + SourceError(source, "switch already has a default\n"); + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + fs->value = MAX_INVENTORYVALUE; + founddefault = qtrue; + } //end if + else + { + if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + fs->value = token.intvalue; + } //end else + if (!PC_ExpectTokenString(source, ":") || !PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + newindent = qfalse; + if (!strcmp(token.string, "{")) + { + newindent = qtrue; + if (!PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + if (!strcmp(token.string, "return")) + { + if (!ReadFuzzyWeight(source, fs)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + else if (!strcmp(token.string, "switch")) + { + fs->child = ReadFuzzySeperators_r(source); + if (!fs->child) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end else if + else + { + SourceError(source, "invalid name %s\n", token.string); + return NULL; + } //end else + if (newindent) + { + if (!PC_ExpectTokenString(source, "}")) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + } //end if + else + { + FreeFuzzySeperators_r(firstfs); + SourceError(source, "invalid name %s\n", token.string); + return NULL; + } //end else + if (!PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } while(strcmp(token.string, "}")); + // + if (!founddefault) + { + SourceWarning(source, "switch without default\n"); + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = index; + fs->value = MAX_INVENTORYVALUE; + fs->weight = 0; + fs->next = NULL; + fs->child = NULL; + if (lastfs) lastfs->next = fs; + else firstfs = fs; + lastfs = fs; + } //end if + // + return firstfs; +} //end of the function ReadFuzzySeperators_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +weightconfig_t *ReadWeightConfig(char *filename) +{ + int newindent, avail = 0, n; + token_t token; + source_t *source; + fuzzyseperator_t *fs; + weightconfig_t *config = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + + if (!LibVarGetValue("bot_reloadcharacters")) + { + avail = -1; + for( n = 0; n < MAX_WEIGHT_FILES; n++ ) + { + config = weightFileList[n]; + if( !config ) + { + if( avail == -1 ) + { + avail = n; + } //end if + continue; + } //end if + if( strcmp( filename, config->filename ) == 0 ) + { + //botimport.Print( PRT_MESSAGE, "retained %s\n", filename ); + return config; + } //end if + } //end for + + if( avail == -1 ) + { + botimport.Print( PRT_ERROR, "weightFileList was full trying to load %s\n", filename ); + return NULL; + } //end if + } //end if + + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + config = (weightconfig_t *) GetClearedMemory(sizeof(weightconfig_t)); + config->numweights = 0; + Q_strncpyz( config->filename, filename, sizeof(config->filename) ); + //parse the item config file + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "weight")) + { + if (config->numweights >= MAX_WEIGHTS) + { + SourceWarning(source, "too many fuzzy weights\n"); + break; + } //end if + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + config->weights[config->numweights].name = (char *) GetClearedMemory(strlen(token.string) + 1); + strcpy(config->weights[config->numweights].name, token.string); + if (!PC_ExpectAnyToken(source, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + newindent = qfalse; + if (!strcmp(token.string, "{")) + { + newindent = qtrue; + if (!PC_ExpectAnyToken(source, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + } //end if + if (!strcmp(token.string, "switch")) + { + fs = ReadFuzzySeperators_r(source); + if (!fs) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + config->weights[config->numweights].firstseperator = fs; + } //end if + else if (!strcmp(token.string, "return")) + { + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = 0; + fs->value = MAX_INVENTORYVALUE; + fs->next = NULL; + fs->child = NULL; + if (!ReadFuzzyWeight(source, fs)) + { + FreeMemory(fs); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + config->weights[config->numweights].firstseperator = fs; + } //end else if + else + { + SourceError(source, "invalid name %s\n", token.string); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end else + if (newindent) + { + if (!PC_ExpectTokenString(source, "}")) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + } //end if + config->numweights++; + } //end if + else + { + SourceError(source, "invalid name %s\n", token.string); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end else + } //end while + //free the source at the end of a pass + FreeSource(source); + //if the file was located in a pak file + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); +#ifdef DEBUG + if (bot_developer) + { + botimport.Print(PRT_MESSAGE, "weights loaded in %d msec\n", Sys_MilliSeconds() - starttime); + } //end if +#endif //DEBUG + // + if (!LibVarGetValue("bot_reloadcharacters")) + { + weightFileList[avail] = config; + } //end if + // + return config; +} //end of the function ReadWeightConfig +#if 0 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteFuzzyWeight(FILE *fp, fuzzyseperator_t *fs) +{ + if (fs->type == WT_BALANCE) + { + if (fprintf(fp, " return balance(") < 0) return qfalse; + if (!WriteFloat(fp, fs->weight)) return qfalse; + if (fprintf(fp, ",") < 0) return qfalse; + if (!WriteFloat(fp, fs->minweight)) return qfalse; + if (fprintf(fp, ",") < 0) return qfalse; + if (!WriteFloat(fp, fs->maxweight)) return qfalse; + if (fprintf(fp, ");\n") < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, " return ") < 0) return qfalse; + if (!WriteFloat(fp, fs->weight)) return qfalse; + if (fprintf(fp, ";\n") < 0) return qfalse; + } //end else + return qtrue; +} //end of the function WriteFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteFuzzySeperators_r(FILE *fp, fuzzyseperator_t *fs, int indent) +{ + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "switch(%d)\n", fs->index) < 0) return qfalse; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "{\n") < 0) return qfalse; + indent++; + do + { + if (!WriteIndent(fp, indent)) return qfalse; + if (fs->next) + { + if (fprintf(fp, "case %d:", fs->value) < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, "default:") < 0) return qfalse; + } //end else + if (fs->child) + { + if (fprintf(fp, "\n") < 0) return qfalse; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "{\n") < 0) return qfalse; + if (!WriteFuzzySeperators_r(fp, fs->child, indent + 1)) return qfalse; + if (!WriteIndent(fp, indent)) return qfalse; + if (fs->next) + { + if (fprintf(fp, "} //end case\n") < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, "} //end default\n") < 0) return qfalse; + } //end else + } //end if + else + { + if (!WriteFuzzyWeight(fp, fs)) return qfalse; + } //end else + fs = fs->next; + } while(fs); + indent--; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "} //end switch\n") < 0) return qfalse; + return qtrue; +} //end of the function WriteItemFuzzyWeights_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteWeightConfig(char *filename, weightconfig_t *config) +{ + int i; + FILE *fp; + weight_t *ifw; + + fp = fopen(filename, "wb"); + if (!fp) return qfalse; + + for (i = 0; i < config->numweights; i++) + { + ifw = &config->weights[i]; + if (fprintf(fp, "\nweight \"%s\"\n", ifw->name) < 0) return qfalse; + if (fprintf(fp, "{\n") < 0) return qfalse; + if (ifw->firstseperator->index > 0) + { + if (!WriteFuzzySeperators_r(fp, ifw->firstseperator, 1)) return qfalse; + } //end if + else + { + if (!WriteIndent(fp, 1)) return qfalse; + if (!WriteFuzzyWeight(fp, ifw->firstseperator)) return qfalse; + } //end else + if (fprintf(fp, "} //end weight\n") < 0) return qfalse; + } //end for + fclose(fp); + return qtrue; +} //end of the function WriteWeightConfig +#endif +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FindFuzzyWeight(weightconfig_t *wc, char *name) +{ + int i; + + for (i = 0; i < wc->numweights; i++) + { + if (!strcmp(wc->weights[i].name, name)) + { + return i; + } //end if + } //end if + return -1; +} //end of the function FindFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeight_r(int *inventory, fuzzyseperator_t *fs) +{ + float scale, w1, w2; + + if (inventory[fs->index] < fs->value) + { + if (fs->child) return FuzzyWeight_r(inventory, fs->child); + else return fs->weight; + } //end if + else if (fs->next) + { + if (inventory[fs->index] < fs->next->value) + { + //first weight + if (fs->child) w1 = FuzzyWeight_r(inventory, fs->child); + else w1 = fs->weight; + //second weight + if (fs->next->child) w2 = FuzzyWeight_r(inventory, fs->next->child); + else w2 = fs->next->weight; + //the scale factor + scale = (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); + //scale between the two weights + return scale * w1 + (1 - scale) * w2; + } //end if + return FuzzyWeight_r(inventory, fs->next); + } //end else if + return fs->weight; +} //end of the function FuzzyWeight_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeightUndecided_r(int *inventory, fuzzyseperator_t *fs) +{ + float scale, w1, w2; + + if (inventory[fs->index] < fs->value) + { + if (fs->child) return FuzzyWeightUndecided_r(inventory, fs->child); + else return fs->minweight + random() * (fs->maxweight - fs->minweight); + } //end if + else if (fs->next) + { + if (inventory[fs->index] < fs->next->value) + { + //first weight + if (fs->child) w1 = FuzzyWeightUndecided_r(inventory, fs->child); + else w1 = fs->minweight + random() * (fs->maxweight - fs->minweight); + //second weight + if (fs->next->child) w2 = FuzzyWeight_r(inventory, fs->next->child); + else w2 = fs->next->minweight + random() * (fs->next->maxweight - fs->next->minweight); + //the scale factor + scale = (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); + //scale between the two weights + return scale * w1 + (1 - scale) * w2; + } //end if + return FuzzyWeightUndecided_r(inventory, fs->next); + } //end else if + return fs->weight; +} //end of the function FuzzyWeightUndecided_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum) +{ +#ifdef EVALUATERECURSIVELY + return FuzzyWeight_r(inventory, wc->weights[weightnum].firstseperator); +#else + fuzzyseperator_t *s; + + s = wc->weights[weightnum].firstseperator; + if (!s) return 0; + while(1) + { + if (inventory[s->index] < s->value) + { + if (s->child) s = s->child; + else return s->weight; + } //end if + else + { + if (s->next) s = s->next; + else return s->weight; + } //end else + } //end if + return 0; +#endif +} //end of the function FuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum) +{ +#ifdef EVALUATERECURSIVELY + return FuzzyWeightUndecided_r(inventory, wc->weights[weightnum].firstseperator); +#else + fuzzyseperator_t *s; + + s = wc->weights[weightnum].firstseperator; + if (!s) return 0; + while(1) + { + if (inventory[s->index] < s->value) + { + if (s->child) s = s->child; + else return s->minweight + random() * (s->maxweight - s->minweight); + } //end if + else + { + if (s->next) s = s->next; + else return s->minweight + random() * (s->maxweight - s->minweight); + } //end else + } //end if + return 0; +#endif +} //end of the function FuzzyWeightUndecided +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EvolveFuzzySeperator_r(fuzzyseperator_t *fs) +{ + if (fs->child) + { + EvolveFuzzySeperator_r(fs->child); + } //end if + else if (fs->type == WT_BALANCE) + { + //every once in a while an evolution leap occurs, mutation + if (random() < 0.01) fs->weight += crandom() * (fs->maxweight - fs->minweight); + else fs->weight += crandom() * (fs->maxweight - fs->minweight) * 0.5; + //modify bounds if necesary because of mutation + if (fs->weight < fs->minweight) fs->minweight = fs->weight; + else if (fs->weight > fs->maxweight) fs->maxweight = fs->weight; + } //end else if + if (fs->next) EvolveFuzzySeperator_r(fs->next); +} //end of the function EvolveFuzzySeperator_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EvolveWeightConfig(weightconfig_t *config) +{ + int i; + + for (i = 0; i < config->numweights; i++) + { + EvolveFuzzySeperator_r(config->weights[i].firstseperator); + } //end for +} //end of the function EvolveWeightConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzySeperator_r(fuzzyseperator_t *fs, float scale) +{ + if (fs->child) + { + ScaleFuzzySeperator_r(fs->child, scale); + } //end if + else if (fs->type == WT_BALANCE) + { + // + fs->weight = (fs->maxweight + fs->minweight) * scale; + //get the weight between bounds + if (fs->weight < fs->minweight) fs->weight = fs->minweight; + else if (fs->weight > fs->maxweight) fs->weight = fs->maxweight; + } //end else if + if (fs->next) ScaleFuzzySeperator_r(fs->next, scale); +} //end of the function ScaleFuzzySeperator_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleWeight(weightconfig_t *config, char *name, float scale) +{ + int i; + + if (scale < 0) scale = 0; + else if (scale > 1) scale = 1; + for (i = 0; i < config->numweights; i++) + { + if (!strcmp(name, config->weights[i].name)) + { + ScaleFuzzySeperator_r(config->weights[i].firstseperator, scale); + break; + } //end if + } //end for +} //end of the function ScaleWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzySeperatorBalanceRange_r(fuzzyseperator_t *fs, float scale) +{ + if (fs->child) + { + ScaleFuzzySeperatorBalanceRange_r(fs->child, scale); + } //end if + else if (fs->type == WT_BALANCE) + { + float mid = (fs->minweight + fs->maxweight) * 0.5; + //get the weight between bounds + fs->maxweight = mid + (fs->maxweight - mid) * scale; + fs->minweight = mid + (fs->minweight - mid) * scale; + if (fs->maxweight < fs->minweight) + { + fs->maxweight = fs->minweight; + } //end if + } //end else if + if (fs->next) ScaleFuzzySeperatorBalanceRange_r(fs->next, scale); +} //end of the function ScaleFuzzySeperatorBalanceRange_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzyBalanceRange(weightconfig_t *config, float scale) +{ + int i; + + if (scale < 0) scale = 0; + else if (scale > 100) scale = 100; + for (i = 0; i < config->numweights; i++) + { + ScaleFuzzySeperatorBalanceRange_r(config->weights[i].firstseperator, scale); + } //end for +} //end of the function ScaleFuzzyBalanceRange +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int InterbreedFuzzySeperator_r(fuzzyseperator_t *fs1, fuzzyseperator_t *fs2, + fuzzyseperator_t *fsout) +{ + if (fs1->child) + { + if (!fs2->child || !fsout->child) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal child\n"); + return qfalse; + } //end if + if (!InterbreedFuzzySeperator_r(fs2->child, fs2->child, fsout->child)) + { + return qfalse; + } //end if + } //end if + else if (fs1->type == WT_BALANCE) + { + if (fs2->type != WT_BALANCE || fsout->type != WT_BALANCE) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal balance\n"); + return qfalse; + } //end if + fsout->weight = (fs1->weight + fs2->weight) / 2; + if (fsout->weight > fsout->maxweight) fsout->maxweight = fsout->weight; + if (fsout->weight > fsout->minweight) fsout->minweight = fsout->weight; + } //end else if + if (fs1->next) + { + if (!fs2->next || !fsout->next) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal next\n"); + return qfalse; + } //end if + if (!InterbreedFuzzySeperator_r(fs1->next, fs2->next, fsout->next)) + { + return qfalse; + } //end if + } //end if + return qtrue; +} //end of the function InterbreedFuzzySeperator_r +//=========================================================================== +// config1 and config2 are interbreeded and stored in configout +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, + weightconfig_t *configout) +{ + int i; + + if (config1->numweights != config2->numweights || + config1->numweights != configout->numweights) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal numweights\n"); + return; + } //end if + for (i = 0; i < config1->numweights; i++) + { + InterbreedFuzzySeperator_r(config1->weights[i].firstseperator, + config2->weights[i].firstseperator, + configout->weights[i].firstseperator); + } //end for +} //end of the function InterbreedWeightConfigs +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownWeights(void) +{ + int i; + + for( i = 0; i < MAX_WEIGHT_FILES; i++ ) + { + if (weightFileList[i]) + { + FreeWeightConfig2(weightFileList[i]); + weightFileList[i] = NULL; + } //end if + } //end for +} //end of the function BotShutdownWeights diff --git a/codemp/botlib/be_ai_weight.h b/codemp/botlib/be_ai_weight.h new file mode 100644 index 0000000..fdce2ba --- /dev/null +++ b/codemp/botlib/be_ai_weight.h @@ -0,0 +1,66 @@ + +/***************************************************************************** + * name: be_ai_weight.h + * + * desc: fuzzy weights + * + * $Archive: /source/code/botlib/be_ai_weight.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#define WT_BALANCE 1 +#define MAX_WEIGHTS 128 + +//fuzzy seperator +typedef struct fuzzyseperator_s +{ + int index; + int value; + int type; + float weight; + float minweight; + float maxweight; + struct fuzzyseperator_s *child; + struct fuzzyseperator_s *next; +} fuzzyseperator_t; + +//fuzzy weight +typedef struct weight_s +{ + char *name; + struct fuzzyseperator_s *firstseperator; +} weight_t; + +//weight configuration +typedef struct weightconfig_s +{ + int numweights; + weight_t weights[MAX_WEIGHTS]; + char filename[MAX_QPATH]; +} weightconfig_t; + +//reads a weight configuration +weightconfig_t *ReadWeightConfig(char *filename); +//free a weight configuration +void FreeWeightConfig(weightconfig_t *config); +//writes a weight configuration, returns true if successfull +qboolean WriteWeightConfig(char *filename, weightconfig_t *config); +//find the fuzzy weight with the given name +int FindFuzzyWeight(weightconfig_t *wc, char *name); +//returns the fuzzy weight for the given inventory and weight +float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum); +float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum); +//scales the weight with the given name +void ScaleWeight(weightconfig_t *config, char *name, float scale); +//scale the balance range +void ScaleBalanceRange(weightconfig_t *config, float scale); +//evolves the weight configuration +void EvolveWeightConfig(weightconfig_t *config); +//interbreed the weight configurations and stores the interbreeded one in configout +void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, weightconfig_t *configout); +//frees cached weight configurations +void BotShutdownWeights(void); diff --git a/codemp/botlib/be_ea.cpp b/codemp/botlib/be_ea.cpp new file mode 100644 index 0000000..026ba4f --- /dev/null +++ b/codemp/botlib/be_ea.cpp @@ -0,0 +1,525 @@ + +/***************************************************************************** + * name: be_ea.c + * + * desc: elementary actions + * + * $Archive: /MissionPack/code/botlib/be_ea.c $ + * $Author: Zaphod $ + * $Revision: 5 $ + * $Modtime: 11/22/00 8:50a $ + * $Date: 11/22/00 8:55a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "../game/botlib.h" +#include "be_interface.h" + +#define MAX_USERMOVE 400 +#define MAX_COMMANDARGUMENTS 10 +#define ACTION_JUMPEDLASTFRAME 0x0800000//128 + +bot_input_t *botinputs; + +#include "../namespace_begin.h" + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Say(int client, char *str) +{ + botimport.BotClientCommand(client, va("say %s", str) ); +} //end of the function EA_Say +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_SayTeam(int client, char *str) +{ + botimport.BotClientCommand(client, va("say_team %s", str)); +} //end of the function EA_SayTeam +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Tell(int client, int clientto, char *str) +{ + botimport.BotClientCommand(client, va("tell %d, %s", clientto, str)); +} //end of the function EA_SayTeam +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_UseItem(int client, char *it) +{ + botimport.BotClientCommand(client, va("use %s", it)); +} //end of the function EA_UseItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_DropItem(int client, char *it) +{ + botimport.BotClientCommand(client, va("drop %s", it)); +} //end of the function EA_DropItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_UseInv(int client, char *inv) +{ + botimport.BotClientCommand(client, va("invuse %s", inv)); +} //end of the function EA_UseInv +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_DropInv(int client, char *inv) +{ + botimport.BotClientCommand(client, va("invdrop %s", inv)); +} //end of the function EA_DropInv +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Gesture(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_GESTURE; +} //end of the function EA_Gesture +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Command(int client, char *command) +{ + botimport.BotClientCommand(client, command); +} //end of the function EA_Command +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_SelectWeapon(int client, int weapon) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->weapon = weapon; +} //end of the function EA_SelectWeapon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Attack(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_ATTACK; +} //end of the function EA_Attack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Alt_Attack(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_ALT_ATTACK; +} //end of the function EA_Alt_Attack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_ForcePower(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_FORCEPOWER; +} //end of the function EA_ForcePower +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Talk(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_TALK; +} //end of the function EA_Talk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Use(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_USE; +} //end of the function EA_Use +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Respawn(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_RESPAWN; +} //end of the function EA_Respawn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Jump(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + if (bi->actionflags & ACTION_JUMPEDLASTFRAME) + { + bi->actionflags &= ~ACTION_JUMP; + } //end if + else + { + bi->actionflags |= ACTION_JUMP; + } //end if +} //end of the function EA_Jump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_DelayedJump(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + if (bi->actionflags & ACTION_JUMPEDLASTFRAME) + { + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } //end if + else + { + bi->actionflags |= ACTION_DELAYEDJUMP; + } //end if +} //end of the function EA_DelayedJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Crouch(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_CROUCH; +} //end of the function EA_Crouch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Walk(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_WALK; +} //end of the function EA_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Action(int client, int action) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= action; +} //end of function EA_Action +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_MoveUp(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEUP; +} //end of the function EA_MoveUp +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_MoveDown(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEDOWN; +} //end of the function EA_MoveDown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_MoveForward(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEFORWARD; +} //end of the function EA_MoveForward +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_MoveBack(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEBACK; +} //end of the function EA_MoveBack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_MoveLeft(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVELEFT; +} //end of the function EA_MoveLeft +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_MoveRight(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVERIGHT; +} //end of the function EA_MoveRight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_Move(int client, vec3_t dir, float speed) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + VectorCopy(dir, bi->dir); + //cap speed + if (speed > MAX_USERMOVE) speed = MAX_USERMOVE; + else if (speed < -MAX_USERMOVE) speed = -MAX_USERMOVE; + bi->speed = speed; +} //end of the function EA_Move +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_View(int client, vec3_t viewangles) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + VectorCopy(viewangles, bi->viewangles); +} //end of the function EA_View +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_EndRegular(int client, float thinktime) +{ +/* + bot_input_t *bi; + int jumped = qfalse; + + bi = &botinputs[client]; + + bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = thinktime; + botimport.BotInput(client, bi); + + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; +*/ +} //end of the function EA_EndRegular +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_GetInput(int client, float thinktime, void *input) +{ + bot_input_t *bi; +// int jumped = qfalse; + + bi = &botinputs[client]; + +// bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = thinktime; + Com_Memcpy(input, bi, sizeof(bot_input_t)); + + /* + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; + */ +} //end of the function EA_GetInput +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void trap_EA_ResetInput(int client) +{ + bot_input_t *bi; + int jumped = qfalse; + + bi = &botinputs[client]; + bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; + + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; +} //end of the function EA_ResetInput + +#include "../namespace_end.h" + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int EA_Setup(void) +{ + //initialize the bot inputs + botinputs = (bot_input_t *) GetClearedHunkMemory( + botlibglobals.maxclients * sizeof(bot_input_t)); + return BLERR_NOERROR; +} //end of the function EA_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Shutdown(void) +{ + FreeMemory(botinputs); + botinputs = NULL; +} //end of the function EA_Shutdown + diff --git a/codemp/botlib/be_interface.cpp b/codemp/botlib/be_interface.cpp new file mode 100644 index 0000000..3d510ff --- /dev/null +++ b/codemp/botlib/be_interface.cpp @@ -0,0 +1,688 @@ + +/***************************************************************************** + * name: be_interface.c // bk010221 - FIXME - DEAD code elimination + * + * desc: bot library interface + * + * $Archive: /MissionPack/code/botlib/be_interface.c $ + * $Author: Zaphod $ + * $Revision: 16 $ + * $Modtime: 5/16/01 2:36p $ + * $Date: 5/16/01 2:41p $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "../game/botlib.h" +#include "../game/be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" +#include "be_interface.h" + +#include "../game/be_ea.h" +#include "be_ai_weight.h" +#include "../game/be_ai_goal.h" +#include "../game/be_ai_move.h" +#include "../game/be_ai_weap.h" +#include "../game/be_ai_chat.h" +#include "../game/be_ai_char.h" +#include "../game/be_ai_gen.h" + +//library globals in a structure +botlib_globals_t botlibglobals; + +botlib_export_t be_botlib_export; +botlib_import_t botimport; +// +int bot_developer; +//qtrue if the library is setup +int botlibsetup = qfalse; + +//=========================================================================== +// +// several functions used by the exported functions +// +//=========================================================================== + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Sys_MilliSeconds(void) +{ + return clock() * 1000 / CLOCKS_PER_SEC; +} //end of the function Sys_MilliSeconds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ValidClientNumber(int num, char *str) +{ + if (num < 0 || num > botlibglobals.maxclients) + { + //weird: the disabled stuff results in a crash + botimport.Print(PRT_ERROR, "%s: invalid client number %d, [0, %d]\n", + str, num, botlibglobals.maxclients); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidateClientNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ValidEntityNumber(int num, char *str) +{ + if (num < 0 || num > botlibglobals.maxentities) + { + botimport.Print(PRT_ERROR, "%s: invalid entity number %d, [0, %d]\n", + str, num, botlibglobals.maxentities); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidateClientNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean BotLibSetup(char *str) +{ + if (!botlibglobals.botlibsetup) + { + botimport.Print(PRT_ERROR, "%s: bot library used before being setup\n", str); + return qfalse; + } //end if + return qtrue; +} //end of the function BotLibSetup + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibSetup(void) +{ + int errnum; + + bot_developer = LibVarGetValue("bot_developer"); + memset( &botlibglobals, 0, sizeof(botlibglobals) ); // bk001207 - init + //initialize byte swapping (litte endian etc.) +// Swap_Init(); + Log_Open("botlib.log"); + // +// botimport.Print(PRT_MESSAGE, "------- BotLib Initialization -------\n"); + // + botlibglobals.maxclients = (int) LibVarValue("maxclients", "128"); + botlibglobals.maxentities = (int) LibVarValue("maxentities", "1024"); + + EA_Setup(); + + /* + errnum = BotSetupWeaponAI(); //be_ai_weap.c + if (errnum != BLERR_NOERROR)return errnum; + errnum = BotSetupGoalAI(); //be_ai_goal.c + if (errnum != BLERR_NOERROR) return errnum; + errnum = BotSetupChatAI(); //be_ai_chat.c + if (errnum != BLERR_NOERROR) return errnum; + errnum = BotSetupMoveAI(); //be_ai_move.c + if (errnum != BLERR_NOERROR) return errnum; + */ + botlibsetup = qtrue; + botlibglobals.botlibsetup = qtrue; + + return BLERR_NOERROR; +} //end of the function Export_BotLibSetup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibShutdown(void) +{ + if (!BotLibSetup("BotLibShutdown")) return BLERR_LIBRARYNOTSETUP; +#ifndef DEMO + //DumpFileCRCs(); +#endif //DEMO + // + //free all libvars + LibVarDeAllocAll(); + //remove all global defines from the pre compiler + PC_RemoveAllGlobalDefines(); + + EA_Shutdown(); + + //dump all allocated memory +// DumpMemory(); +#ifdef DEBUG + PrintMemoryLabels(); +#endif + //shut down library log file + Log_Shutdown(); + // + botlibsetup = qfalse; + botlibglobals.botlibsetup = qfalse; + // print any files still open + PC_CheckOpenSourceHandles(); + // + return BLERR_NOERROR; +} //end of the function Export_BotLibShutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibVarSet(char *var_name, char *value) +{ + LibVarSet(var_name, value); + return BLERR_NOERROR; +} //end of the function Export_BotLibVarSet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibVarGet(char *var_name, char *value, int size) +{ + char *varvalue; + + varvalue = LibVarGetString(var_name); + strncpy(value, varvalue, size-1); + value[size-1] = '\0'; + return BLERR_NOERROR; +} //end of the function Export_BotLibVarGet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibStartFrame(float time) +{ + if (!BotLibSetup("BotStartFrame")) return BLERR_LIBRARYNOTSETUP; + return 0; +} //end of the function Export_BotLibStartFrame +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibLoadMap(const char *mapname) +{ +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif + int errnum; + + if (!BotLibSetup("BotLoadMap")) return BLERR_LIBRARYNOTSETUP; + // + botimport.Print(PRT_MESSAGE, "------------ Map Loading ------------\n"); + //startup AAS for the current map, model and sound index + //initialize the items in the level + // + botimport.Print(PRT_MESSAGE, "-------------------------------------\n"); +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "map loaded in %d msec\n", Sys_MilliSeconds() - starttime); +#endif + // + return BLERR_NOERROR; +} //end of the function Export_BotLibLoadMap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibUpdateEntity(int ent, bot_entitystate_t *state) +{ + if (!BotLibSetup("BotUpdateEntity")) return BLERR_LIBRARYNOTSETUP; + if (!ValidEntityNumber(ent, "BotUpdateEntity")) return BLERR_INVALIDENTITYNUMBER; + + return 0; +} //end of the function Export_BotLibUpdateEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir); +void ElevatorBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter); +int BotGetReachabilityToGoal(vec3_t origin, int areanum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags, + struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags); + +int AAS_PointLight(vec3_t origin, int *red, int *green, int *blue); + +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + +int AAS_Reachability_WeaponJump(int area1num, int area2num); + +int BotFuzzyPointReachabilityArea(vec3_t origin); + +float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum); + +void AAS_FloodAreas(vec3_t origin); + +int BotExportTest(int parm0, char *parm1, vec3_t parm2, vec3_t parm3) +{ +#ifndef _XBOX + +// return AAS_PointLight(parm2, NULL, NULL, NULL); + +#ifdef DEBUG + static int area = -1; + static int line[2]; + int newarea, i, highlightarea, flood; +// int reachnum; + vec3_t eye, forward, right, end, origin; +// vec3_t bottomcenter; +// aas_trace_t trace; +// aas_face_t *face; +// aas_entity_t *ent; +// bsp_trace_t bsptrace; +// aas_reachability_t reach; +// bot_goal_t goal; + + // clock_t start_time, end_time; + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + +// int areas[10], numareas; + + + //return 0; + + if (!aasworld.loaded) return 0; + + /* + if (parm0 & 1) + { + AAS_ClearShownPolygons(); + AAS_FloodAreas(parm2); + } //end if + return 0; + */ + for (i = 0; i < 2; i++) if (!line[i]) line[i] = botimport.DebugLineCreate(); + +// AAS_ClearShownDebugLines(); + + //if (AAS_AgainstLadder(parm2)) botimport.Print(PRT_MESSAGE, "against ladder\n"); + //BotOnGround(parm2, PRESENCE_NORMAL, 1, &newarea, &newarea); + //botimport.Print(PRT_MESSAGE, "%f %f %f\n", parm2[0], parm2[1], parm2[2]); + //* + highlightarea = LibVarGetValue("bot_highlightarea"); + if (highlightarea > 0) + { + newarea = highlightarea; + } //end if + else + { + VectorCopy(parm2, origin); + origin[2] += 0.5; + //newarea = AAS_PointAreaNum(origin); + newarea = BotFuzzyPointReachabilityArea(origin); + } //end else + + botimport.Print(PRT_MESSAGE, "\rtravel time to goal (%d) = %d ", botlibglobals.goalareanum, + AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT)); + //newarea = BotReachabilityArea(origin, qtrue); + if (newarea != area) + { + botimport.Print(PRT_MESSAGE, "origin = %f, %f, %f\n", origin[0], origin[1], origin[2]); + area = newarea; + botimport.Print(PRT_MESSAGE, "new area %d, cluster %d, presence type %d\n", + area, AAS_AreaCluster(area), AAS_PointPresenceType(origin)); + botimport.Print(PRT_MESSAGE, "area contents: "); + if (aasworld.areasettings[area].contents & AREACONTENTS_WATER) + { + botimport.Print(PRT_MESSAGE, "water &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_LAVA) + { + botimport.Print(PRT_MESSAGE, "lava &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_SLIME) + { + botimport.Print(PRT_MESSAGE, "slime &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_JUMPPAD) + { + botimport.Print(PRT_MESSAGE, "jump pad &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_CLUSTERPORTAL) + { + botimport.Print(PRT_MESSAGE, "cluster portal &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_VIEWPORTAL) + { + botimport.Print(PRT_MESSAGE, "view portal &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_DONOTENTER) + { + botimport.Print(PRT_MESSAGE, "do not enter &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_MOVER) + { + botimport.Print(PRT_MESSAGE, "mover &"); + } //end if + if (!aasworld.areasettings[area].contents) + { + botimport.Print(PRT_MESSAGE, "empty"); + } //end if + botimport.Print(PRT_MESSAGE, "\n"); + botimport.Print(PRT_MESSAGE, "travel time to goal (%d) = %d\n", botlibglobals.goalareanum, + AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT|TFL_ROCKETJUMP)); + /* + VectorCopy(origin, end); + end[2] += 5; + numareas = AAS_TraceAreas(origin, end, areas, NULL, 10); + AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); + botimport.Print(PRT_MESSAGE, "num areas = %d, area = %d\n", numareas, areas[0]); + */ + /* + botlibglobals.goalareanum = newarea; + VectorCopy(parm2, botlibglobals.goalorigin); + botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", + origin[0], origin[1], origin[2], newarea); + */ + } //end if + //* + flood = LibVarGetValue("bot_flood"); + if (parm0 & 1) + { + if (flood) + { + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + AAS_FloodAreas(parm2); + } + else + { + botlibglobals.goalareanum = newarea; + VectorCopy(parm2, botlibglobals.goalorigin); + botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", + origin[0], origin[1], origin[2], newarea); + } + } //end if*/ + if (flood) + return 0; +// if (parm0 & BUTTON_USE) +// { +// botlibglobals.runai = !botlibglobals.runai; +// if (botlibglobals.runai) botimport.Print(PRT_MESSAGE, "started AI\n"); +// else botimport.Print(PRT_MESSAGE, "stopped AI\n"); + //* / + /* + goal.areanum = botlibglobals.goalareanum; + reachnum = BotGetReachabilityToGoal(parm2, newarea, 1, + ms.avoidreach, ms.avoidreachtimes, + &goal, TFL_DEFAULT); + if (!reachnum) + { + botimport.Print(PRT_MESSAGE, "goal not reachable\n"); + } //end if + else + { + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ClearShownDebugLines(); + AAS_ShowArea(area, qtrue); + AAS_ShowArea(reach.areanum, qtrue); + AAS_DrawCross(reach.start, 6, LINECOLOR_BLUE); + AAS_DrawCross(reach.end, 6, LINECOLOR_RED); + // + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) + { + ElevatorBottomCenter(&reach, bottomcenter); + AAS_DrawCross(bottomcenter, 10, LINECOLOR_GREEN); + } //end if + } //end else*/ +// botimport.Print(PRT_MESSAGE, "travel time to goal = %d\n", +// AAS_AreaTravelTimeToGoalArea(area, origin, botlibglobals.goalareanum, TFL_DEFAULT)); +// botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); +// AAS_Reachability_WeaponJump(703, 716); +// } //end if*/ + +/* face = AAS_AreaGroundFace(newarea, parm2); + if (face) + { + AAS_ShowFace(face - aasworld.faces); + } //end if*/ + /* + AAS_ClearShownDebugLines(); + AAS_ShowArea(newarea, parm0 & BUTTON_USE); + AAS_ShowReachableAreas(area); + */ + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + AAS_ShowAreaPolygons(newarea, 1, parm0 & 4); + if (parm0 & 2) AAS_ShowReachableAreas(area); + else + { + static int lastgoalareanum, lastareanum; + static int avoidreach[MAX_AVOIDREACH]; + static float avoidreachtimes[MAX_AVOIDREACH]; + static int avoidreachtries[MAX_AVOIDREACH]; + int reachnum, resultFlags; + bot_goal_t goal; + aas_reachability_t reach; + + /* + goal.areanum = botlibglobals.goalareanum; + VectorCopy(botlibglobals.goalorigin, goal.origin); + reachnum = BotGetReachabilityToGoal(origin, newarea, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, + NULL, 0, &resultFlags); + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ShowReachability(&reach); + */ + int curarea; + vec3_t curorigin; + + goal.areanum = botlibglobals.goalareanum; + VectorCopy(botlibglobals.goalorigin, goal.origin); + VectorCopy(origin, curorigin); + curarea = newarea; + for ( i = 0; i < 100; i++ ) { + if ( curarea == goal.areanum ) { + break; + } + reachnum = BotGetReachabilityToGoal(curorigin, curarea, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, + NULL, 0, &resultFlags); + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ShowReachability(&reach); + VectorCopy(reach.end, origin); + lastareanum = curarea; + curarea = reach.areanum; + } + } //end else + VectorClear(forward); + //BotGapDistance(origin, forward, 0); + /* + if (parm0 & BUTTON_USE) + { + botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); + AAS_Reachability_WeaponJump(703, 716); + } //end if*/ + + AngleVectors(parm3, forward, right, NULL); + //get the eye 16 units to the right of the origin + VectorMA(parm2, 8, right, eye); + //get the eye 24 units up + eye[2] += 24; + //get the end point for the line to be traced + VectorMA(eye, 800, forward, end); + +// AAS_TestMovementPrediction(1, parm2, forward); +/* + //trace the line to find the hit point + trace = AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); + if (!line[0]) line[0] = botimport.DebugLineCreate(); + botimport.DebugLineShow(line[0], eye, trace.endpos, LINECOLOR_BLUE); + // + AAS_ClearShownDebugLines(); + if (trace.ent) + { + ent = &aasworld.entities[trace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if +*/ + +/* + start_time = clock(); + for (i = 0; i < 2000; i++) + { + AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); +// AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); + } //end for + end_time = clock(); + botimport.Print(PRT_MESSAGE, "me %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); + start_time = clock(); + for (i = 0; i < 2000; i++) + { + AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + } //end for + end_time = clock(); + botimport.Print(PRT_MESSAGE, "id %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); +*/ + + // TTimo: nested comments are BAD for gcc -Werror, use #if 0 instead.. +#if 0 + AAS_ClearShownDebugLines(); + //bsptrace = AAS_Trace(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); + bsptrace = AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + if (!line[0]) line[0] = botimport.DebugLineCreate(); + botimport.DebugLineShow(line[0], eye, bsptrace.endpos, LINECOLOR_YELLOW); + if (bsptrace.fraction < 1.0) + { + face = AAS_TraceEndFace(&trace); + if (face) + { + AAS_ShowFace(face - aasworld.faces); + } //end if + + AAS_DrawPlaneCross(bsptrace.endpos, + bsptrace.plane.normal, + bsptrace.plane.dist + bsptrace.exp_dist, + bsptrace.plane.type, LINECOLOR_GREEN); + if (trace.ent) + { + ent = &aasworld.entities[trace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if + } //end if + //bsptrace = AAS_Trace2(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); + bsptrace = AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + botimport.DebugLineShow(line[1], eye, bsptrace.endpos, LINECOLOR_BLUE); + if (bsptrace.fraction < 1.0) + { + AAS_DrawPlaneCross(bsptrace.endpos, + bsptrace.plane.normal, + bsptrace.plane.dist,// + bsptrace.exp_dist, + bsptrace.plane.type, LINECOLOR_RED); + if (bsptrace.ent) + { + ent = &aasworld.entities[bsptrace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if + } //end if +#endif +#endif +#endif // _XBOX + return 0; +} //end of the function BotExportTest + +/* +============ +Init_AAS_Export +============ +*/ +static void Init_AAS_Export( aas_export_t *aas ) { +} + + +/* +============ +Init_EA_Export +============ +*/ +static void Init_EA_Export( ea_export_t *ea ) { +} + + +/* +============ +Init_AI_Export +============ +*/ +static void Init_AI_Export( ai_export_t *ai ) { +} + + +/* +============ +GetBotLibAPI +============ +*/ +botlib_export_t *GetBotLibAPI(int apiVersion, botlib_import_t *import) { + assert(import); // bk001129 - this wasn't set for base/ + botimport = *import; + assert(botimport.Print); // bk001129 - pars pro toto + + Com_Memset( &be_botlib_export, 0, sizeof( be_botlib_export ) ); + + if ( apiVersion != BOTLIB_API_VERSION ) { + botimport.Print( PRT_ERROR, "Mismatched BOTLIB_API_VERSION: expected %i, got %i\n", BOTLIB_API_VERSION, apiVersion ); + return NULL; + } + + Init_AAS_Export(&be_botlib_export.aas); + Init_EA_Export(&be_botlib_export.ea); + Init_AI_Export(&be_botlib_export.ai); + + be_botlib_export.BotLibSetup = Export_BotLibSetup; + be_botlib_export.BotLibShutdown = Export_BotLibShutdown; + be_botlib_export.BotLibVarSet = Export_BotLibVarSet; + be_botlib_export.BotLibVarGet = Export_BotLibVarGet; + + be_botlib_export.PC_AddGlobalDefine = PC_AddGlobalDefine; + be_botlib_export.PC_LoadSourceHandle = PC_LoadSourceHandle; + be_botlib_export.PC_FreeSourceHandle = PC_FreeSourceHandle; + be_botlib_export.PC_ReadTokenHandle = PC_ReadTokenHandle; + be_botlib_export.PC_SourceFileAndLine = PC_SourceFileAndLine; + be_botlib_export.PC_LoadGlobalDefines = PC_LoadGlobalDefines; + be_botlib_export.PC_RemoveAllGlobalDefines = PC_RemoveAllGlobalDefines; + + be_botlib_export.BotLibStartFrame = Export_BotLibStartFrame; + be_botlib_export.BotLibLoadMap = Export_BotLibLoadMap; + be_botlib_export.BotLibUpdateEntity = Export_BotLibUpdateEntity; + be_botlib_export.Test = BotExportTest; + + return &be_botlib_export; +} diff --git a/codemp/botlib/be_interface.h b/codemp/botlib/be_interface.h new file mode 100644 index 0000000..cb6216b --- /dev/null +++ b/codemp/botlib/be_interface.h @@ -0,0 +1,40 @@ + +/***************************************************************************** + * name: be_interface.h + * + * desc: botlib interface + * + * $Archive: /source/code/botlib/be_interface.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +//#define DEBUG //debug code +#define RANDOMIZE //randomize bot behaviour + +//FIXME: get rid of this global structure +typedef struct botlib_globals_s +{ + int botlibsetup; //true when the bot library has been setup + int maxentities; //maximum number of entities + int maxclients; //maximum number of clients + float time; //the global time +#ifdef DEBUG + qboolean debug; //true if debug is on + int goalareanum; + vec3_t goalorigin; + int runai; +#endif +} botlib_globals_t; + + +extern botlib_globals_t botlibglobals; +extern botlib_import_t botimport; +extern int bot_developer; //true if developer is on + +// +int Sys_MilliSeconds(void); + diff --git a/codemp/botlib/botlib.dsp b/codemp/botlib/botlib.dsp new file mode 100644 index 0000000..261a8c5 --- /dev/null +++ b/codemp/botlib/botlib.dsp @@ -0,0 +1,400 @@ +# Microsoft Developer Studio Project File - Name="botlib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=botlib - Win32 Release JK2 +!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 "botlib.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 "botlib.mak" CFG="botlib - Win32 Release JK2" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "botlib - Win32 Release JK2" (based on "Win32 (x86) Static Library") +!MESSAGE "botlib - Win32 Debug JK2" (based on "Win32 (x86) Static Library") +!MESSAGE "botlib - Win32 Final JK2" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/jedi/codemp/botlib", EAAAAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "botlib - Win32 Release JK2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "botlib___Win32_Release_TA" +# PROP BASE Intermediate_Dir "botlib___Win32_Release_TA" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Release" +# PROP Intermediate_Dir "../Release/botlib" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G6 /W4 /GX /O2 /D "WIN32" /D "NDebug" /D "_MBCS" /D "_LIB" /D "BOTLIB" /YX /FD /c +# ADD CPP /nologo /G6 /W4 /GX /Zi /O2 /I "../game" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D "BOTLIB" /D "_JK2" /YX /FD /c +# SUBTRACT CPP /Fr +# ADD BASE RSC /l 0x409 /d "NDebug" +# ADD RSC /l 0x409 /d "NDebug" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "botlib - Win32 Debug JK2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "botlib___Win32_Debug_TA" +# PROP BASE Intermediate_Dir "botlib___Win32_Debug_TA" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "../Debug" +# PROP Intermediate_Dir "../Debug/botlib" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G5 /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_Debug" /D "_MBCS" /D "_LIB" /D "BOTLIB" /D "Debug" /FR /YX /FD /GZ /c +# ADD CPP /nologo /G6 /W3 /Gm /GX /ZI /Od /I "../game" /D "_DEBUG" /D "DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D "BOTLIB" /D "_JK2" /FR /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_Debug" +# ADD RSC /l 0x409 /d "_Debug" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "botlib - Win32 Final JK2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "botlib___Win32_Final_JK2" +# PROP BASE Intermediate_Dir "botlib___Win32_Final_JK2" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Final" +# PROP Intermediate_Dir "../Final/botlib" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G6 /W4 /GX /Zi /O2 /I "../jk2/game" /D "NDebug" /D "WIN32" /D "_MBCS" /D "_LIB" /D "BOTLIB" /D "_JK2" /YX /FD /c +# SUBTRACT BASE CPP /Fr +# ADD CPP /nologo /G6 /W4 /GX /O2 /I "../game" /D "NDEBUG" /D "_MBCS" /D "_LIB" /D "BOTLIB" /D "WIN32" /D "_JK2" /D "FINAL_BUILD" /YX /FD /c +# SUBTRACT CPP /Fr +# ADD BASE RSC /l 0x409 /d "NDebug" +# ADD RSC /l 0x409 /d "NDebug" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "botlib - Win32 Release JK2" +# Name "botlib - Win32 Debug JK2" +# Name "botlib - Win32 Final JK2" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\be_aas_bspq3.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_cluster.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_debug.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_entity.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_file.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_main.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_move.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_optimize.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_reach.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_route.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_routealt.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_aas_sample.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_ai_char.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_ai_chat.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_ai_gen.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_ai_goal.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_ai_move.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_ai_weap.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_ai_weight.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_ea.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_interface.cpp +# End Source File +# Begin Source File + +SOURCE=.\l_crc.cpp +# End Source File +# Begin Source File + +SOURCE=.\l_libvar.cpp +# End Source File +# Begin Source File + +SOURCE=.\l_log.cpp +# End Source File +# Begin Source File + +SOURCE=.\l_memory.cpp +# End Source File +# Begin Source File + +SOURCE=.\l_precomp.cpp +# End Source File +# Begin Source File + +SOURCE=.\l_script.cpp +# End Source File +# Begin Source File + +SOURCE=.\l_struct.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\aasfile.h +# End Source File +# Begin Source File + +SOURCE=..\game\be_aas.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_bsp.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_cluster.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_debug.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_def.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_entity.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_file.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_funcs.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_main.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_move.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_optimize.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_reach.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_route.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_routealt.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas_sample.h +# End Source File +# Begin Source File + +SOURCE=..\game\be_ai_char.h +# End Source File +# Begin Source File + +SOURCE=..\game\be_ai_chat.h +# End Source File +# Begin Source File + +SOURCE=..\game\be_ai_gen.h +# End Source File +# Begin Source File + +SOURCE=..\game\be_ai_goal.h +# End Source File +# Begin Source File + +SOURCE=..\game\be_ai_move.h +# End Source File +# Begin Source File + +SOURCE=..\game\be_ai_weap.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_weight.h +# End Source File +# Begin Source File + +SOURCE=..\game\be_ea.h +# End Source File +# Begin Source File + +SOURCE=.\be_interface.h +# End Source File +# Begin Source File + +SOURCE=..\game\botlib.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\cm_public.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\disablewarnings.h +# End Source File +# Begin Source File + +SOURCE=.\l_crc.h +# End Source File +# Begin Source File + +SOURCE=.\l_libvar.h +# End Source File +# Begin Source File + +SOURCE=.\l_log.h +# End Source File +# Begin Source File + +SOURCE=.\l_memory.h +# End Source File +# Begin Source File + +SOURCE=.\l_precomp.h +# End Source File +# Begin Source File + +SOURCE=.\l_script.h +# End Source File +# Begin Source File + +SOURCE=.\l_struct.h +# End Source File +# Begin Source File + +SOURCE=.\l_utils.h +# End Source File +# Begin Source File + +SOURCE=..\game\q_shared.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\qcommon.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\qfiles.h +# End Source File +# Begin Source File + +SOURCE=..\server\server.h +# End Source File +# Begin Source File + +SOURCE=..\game\surfaceflags.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\tags.h +# End Source File +# End Group +# End Target +# End Project diff --git a/codemp/botlib/botlib.vcproj b/codemp/botlib/botlib.vcproj new file mode 100644 index 0000000..88002cc --- /dev/null +++ b/codemp/botlib/botlib.vcproj @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/botlib/l_crc.cpp b/codemp/botlib/l_crc.cpp new file mode 100644 index 0000000..6f69c02 --- /dev/null +++ b/codemp/botlib/l_crc.cpp @@ -0,0 +1,134 @@ + +/***************************************************************************** + * name: l_crc.c + * + * desc: CRC calculation + * + * $Archive: /MissionPack/CODE/botlib/l_crc.c $ + * $Author: Raduffy $ + * $Revision: 1 $ + * $Modtime: 12/20/99 8:42p $ + * $Date: 3/08/00 11:28a $ + * + *****************************************************************************/ + +#include +#include +#include + +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" //for botimport.Print + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +unsigned short crctable[257] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} //end of the function CRC_Init +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} //end of the function CRC_ProcessByte +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} //end of the function CRC_Value +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short CRC_ProcessString(unsigned char *data, int length) +{ + unsigned short crcvalue; + int i, ind; + + CRC_Init(&crcvalue); + + for (i = 0; i < length; i++) + { + ind = (crcvalue >> 8) ^ data[i]; + if (ind < 0 || ind > 256) ind = 0; + crcvalue = (crcvalue << 8) ^ crctable[ind]; + } //end for + return CRC_Value(crcvalue); +} //end of the function CRC_ProcessString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_ContinueProcessString(unsigned short *crc, char *data, int length) +{ + int i; + + for (i = 0; i < length; i++) + { + *crc = (*crc << 8) ^ crctable[(*crc >> 8) ^ data[i]]; + } //end for +} //end of the function CRC_ProcessString diff --git a/codemp/botlib/l_crc.h b/codemp/botlib/l_crc.h new file mode 100644 index 0000000..d78952b --- /dev/null +++ b/codemp/botlib/l_crc.h @@ -0,0 +1,16 @@ +//=========================================================================== +// +// Name: l_crc.h +// Function: for CRC checks +// Programmer: Mr Elusive (MrElusive@demigod.demon.nl) +// Last update: 1997-12-31 +// Tab Size: 3 +//=========================================================================== + +typedef unsigned short crc_t; + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); +unsigned short CRC_ProcessString(unsigned char *data, int length); +void CRC_ContinueProcessString(unsigned short *crc, char *data, int length); diff --git a/codemp/botlib/l_libvar.cpp b/codemp/botlib/l_libvar.cpp new file mode 100644 index 0000000..e3aa640 --- /dev/null +++ b/codemp/botlib/l_libvar.cpp @@ -0,0 +1,277 @@ + +/***************************************************************************** + * name: l_libvar.c + * + * desc: bot library variables + * + * $Archive: /MissionPack/code/botlib/l_libvar.c $ + * $Author: Zaphod $ + * $Revision: 2 $ + * $Modtime: 11/21/00 11:33a $ + * $Date: 11/21/00 11:49a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" + +//list with library variables +libvar_t *libvarlist; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarStringValue(char *string) +{ + int dotfound = 0; + float value = 0; + + while(*string) + { + if (*string < '0' || *string > '9') + { + if (dotfound || *string != '.') + { + return 0; + } //end if + else + { + dotfound = 10; + string++; + } //end if + } //end if + if (dotfound) + { + value = value + (float) (*string - '0') / (float) dotfound; + dotfound *= 10; + } //end if + else + { + value = value * 10.0 + (float) (*string - '0'); + } //end else + string++; + } //end while + return value; +} //end of the function LibVarStringValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVarAlloc(char *var_name) +{ + libvar_t *v; + + v = (libvar_t *) GetMemory(sizeof(libvar_t) + strlen(var_name) + 1); + Com_Memset(v, 0, sizeof(libvar_t)); + v->name = (char *) v + sizeof(libvar_t); + strcpy(v->name, var_name); + //add the variable in the list + v->next = libvarlist; + libvarlist = v; + return v; +} //end of the function LibVarAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarDeAlloc(libvar_t *v) +{ + if (v->string) FreeMemory(v->string); + FreeMemory(v); +} //end of the function LibVarDeAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarDeAllocAll(void) +{ + libvar_t *v; + + for (v = libvarlist; v; v = libvarlist) + { + libvarlist = libvarlist->next; + LibVarDeAlloc(v); + } //end for + libvarlist = NULL; +} //end of the function LibVarDeAllocAll +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVarGet(char *var_name) +{ + libvar_t *v; + + for (v = libvarlist; v; v = v->next) + { + if (!Q_stricmp(v->name, var_name)) + { + return v; + } //end if + } //end for + return NULL; +} //end of the function LibVarGet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *LibVarGetString(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + return v->string; + } //end if + else + { + return ""; + } //end else +} //end of the function LibVarGetString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarGetValue(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + return v->value; + } //end if + else + { + return 0; + } //end else +} //end of the function LibVarGetValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVar(char *var_name, char *value) +{ + libvar_t *v; + v = LibVarGet(var_name); + if (v) return v; + //create new variable + v = LibVarAlloc(var_name); + //variable string + v->string = (char *) GetMemory(strlen(value) + 1); + strcpy(v->string, value); + //the value + v->value = LibVarStringValue(v->string); + //variable is modified + v->modified = qtrue; + // + return v; +} //end of the function LibVar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *LibVarString(char *var_name, char *value) +{ + libvar_t *v; + + v = LibVar(var_name, value); + return v->string; +} //end of the function LibVarString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarValue(char *var_name, char *value) +{ + libvar_t *v; + + v = LibVar(var_name, value); + return v->value; +} //end of the function LibVarValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarSet(char *var_name, char *value) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + FreeMemory(v->string); + } //end if + else + { + v = LibVarAlloc(var_name); + } //end else + //variable string + v->string = (char *) GetMemory(strlen(value) + 1); + strcpy(v->string, value); + //the value + v->value = LibVarStringValue(v->string); + //variable is modified + v->modified = qtrue; +} //end of the function LibVarSet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean LibVarChanged(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + return v->modified; + } //end if + else + { + return qfalse; + } //end else +} //end of the function LibVarChanged +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarSetNotModified(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + v->modified = qfalse; + } //end if +} //end of the function LibVarSetNotModified diff --git a/codemp/botlib/l_libvar.h b/codemp/botlib/l_libvar.h new file mode 100644 index 0000000..8ff789e --- /dev/null +++ b/codemp/botlib/l_libvar.h @@ -0,0 +1,46 @@ + +/***************************************************************************** + * name: l_libvar.h + * + * desc: botlib vars + * + * $Archive: /source/code/botlib/l_libvar.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +//library variable +typedef struct libvar_s +{ + char *name; + char *string; + int flags; + qboolean modified; // set each time the cvar is changed + float value; + struct libvar_s *next; +} libvar_t; + +//removes all library variables +void LibVarDeAllocAll(void); +//gets the library variable with the given name +libvar_t *LibVarGet(char *var_name); +//gets the string of the library variable with the given name +char *LibVarGetString(char *var_name); +//gets the value of the library variable with the given name +float LibVarGetValue(char *var_name); +//creates the library variable if not existing already and returns it +libvar_t *LibVar(char *var_name, char *value); +//creates the library variable if not existing already and returns the value +float LibVarValue(char *var_name, char *value); +//creates the library variable if not existing already and returns the value string +char *LibVarString(char *var_name, char *value); +//sets the library variable +void LibVarSet(char *var_name, char *value); +//returns true if the library variable has been modified +qboolean LibVarChanged(char *var_name); +//sets the library variable to unmodified +void LibVarSetNotModified(char *var_name); + diff --git a/codemp/botlib/l_log.cpp b/codemp/botlib/l_log.cpp new file mode 100644 index 0000000..327f223 --- /dev/null +++ b/codemp/botlib/l_log.cpp @@ -0,0 +1,152 @@ + +/***************************************************************************** + * name: l_log.c + * + * desc: log file + * + * $Archive: /MissionPack/CODE/botlib/l_log.c $ + * $Author: Raduffy $ + * $Revision: 1 $ + * $Modtime: 12/20/99 8:43p $ + * $Date: 3/08/00 11:28a $ + * + *****************************************************************************/ + +#include +#include +#include + +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" //for botimport.Print +#include "l_libvar.h" + +#define MAX_LOGFILENAMESIZE 1024 + +typedef struct logfile_s +{ + char filename[MAX_LOGFILENAMESIZE]; + FILE *fp; + int numwrites; +} logfile_t; + +static logfile_t logfile; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Open(char *filename) +{ + if (!LibVarValue("log", "0")) return; + if (!filename || !strlen(filename)) + { + botimport.Print(PRT_MESSAGE, "openlog \n"); + return; + } //end if + if (logfile.fp) + { + botimport.Print(PRT_ERROR, "log file %s is already opened\n", logfile.filename); + return; + } //end if + logfile.fp = fopen(filename, "wb"); + if (!logfile.fp) + { + botimport.Print(PRT_ERROR, "can't open the log file %s\n", filename); + return; + } //end if + strncpy(logfile.filename, filename, MAX_LOGFILENAMESIZE); + botimport.Print(PRT_MESSAGE, "Opened log %s\n", logfile.filename); +} //end of the function Log_Create +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Close(void) +{ + if (!logfile.fp) return; + if (fclose(logfile.fp)) + { + botimport.Print(PRT_ERROR, "can't close log file %s\n", logfile.filename); + return; + } //end if + logfile.fp = NULL; + botimport.Print(PRT_MESSAGE, "Closed log %s\n", logfile.filename); +} //end of the function Log_Close +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Shutdown(void) +{ + if (logfile.fp) Log_Close(); +} //end of the function Log_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL Log_Write(char *fmt, ...) +{ + va_list ap; + + if (!logfile.fp) return; + va_start(ap, fmt); + vfprintf(logfile.fp, fmt, ap); + va_end(ap); + //fprintf(logfile.fp, "\r\n"); + fflush(logfile.fp); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL Log_WriteTimeStamped(char *fmt, ...) +{ + va_list ap; + + if (!logfile.fp) return; + fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", + logfile.numwrites, + (int) (botlibglobals.time / 60 / 60), + (int) (botlibglobals.time / 60), + (int) (botlibglobals.time), + (int) ((int) (botlibglobals.time * 100)) - + ((int) botlibglobals.time) * 100); + va_start(ap, fmt); + vfprintf(logfile.fp, fmt, ap); + va_end(ap); + fprintf(logfile.fp, "\r\n"); + logfile.numwrites++; + fflush(logfile.fp); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +FILE *Log_FilePointer(void) +{ + return logfile.fp; +} //end of the function Log_FilePointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Flush(void) +{ + if (logfile.fp) fflush(logfile.fp); +} //end of the function Log_Flush + diff --git a/codemp/botlib/l_log.h b/codemp/botlib/l_log.h new file mode 100644 index 0000000..101daad --- /dev/null +++ b/codemp/botlib/l_log.h @@ -0,0 +1,29 @@ + +/***************************************************************************** + * name: l_log.h + * + * desc: log file + * + * $Archive: /source/code/botlib/l_log.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +//open a log file +void Log_Open(char *filename); +//close the current log file +void Log_Close(void); +//close log file if present +void Log_Shutdown(void); +//write to the current opened log file +void QDECL Log_Write(char *fmt, ...); +//write to the current opened log file with a time stamp +void QDECL Log_WriteTimeStamped(char *fmt, ...); +//returns a pointer to the log file +FILE *Log_FilePointer(void); +//flush log file +void Log_Flush(void); + diff --git a/codemp/botlib/l_memory.cpp b/codemp/botlib/l_memory.cpp new file mode 100644 index 0000000..2eb0f50 --- /dev/null +++ b/codemp/botlib/l_memory.cpp @@ -0,0 +1,446 @@ + +/***************************************************************************** + * name: l_memory.c + * + * desc: memory allocation + * + * $Archive: /MissionPack/code/botlib/l_memory.c $ + * $Author: Ttimo $ + * $Revision: 6 $ + * $Modtime: 4/22/01 8:52a $ + * $Date: 4/22/01 8:52a $ + * + *****************************************************************************/ + +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "l_log.h" +#include "be_interface.h" + +//#define MEMDEBUG +//#define MEMORYMANEGER + +#define MEM_ID 0x12345678l +#define HUNK_ID 0x87654321l + +int allocatedmemory; +int totalmemorysize; +int numblocks; + +#ifdef MEMORYMANEGER + +typedef struct memoryblock_s +{ + unsigned long int id; + void *ptr; + int size; +#ifdef MEMDEBUG + char *label; + char *file; + int line; +#endif //MEMDEBUG + struct memoryblock_s *prev, *next; +} memoryblock_t; + +memoryblock_t *memory; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LinkMemoryBlock(memoryblock_t *block) +{ + block->prev = NULL; + block->next = memory; + if (memory) memory->prev = block; + memory = block; +} //end of the function LinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnlinkMemoryBlock(memoryblock_t *block) +{ + if (block->prev) block->prev->next = block->next; + else memory = block->next; + if (block->next) block->next->prev = block->prev; +} //end of the function UnlinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + assert(botimport.GetMemory); // bk001129 - was NULL'ed + ptr = botimport.GetMemory(size + sizeof(memoryblock_t)); + block = (memoryblock_t *) ptr; + block->id = MEM_ID; + block->ptr = (char *) ptr + sizeof(memoryblock_t); + block->size = size + sizeof(memoryblock_t); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock(block); + allocatedmemory += block->size; + totalmemorysize += block->size + sizeof(memoryblock_t); + numblocks++; + return block->ptr; +} //end of the function GetMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug(size, label, file, line); +#else + ptr = GetMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + + ptr = botimport.HunkAlloc(size + sizeof(memoryblock_t)); + block = (memoryblock_t *) ptr; + block->id = HUNK_ID; + block->ptr = (char *) ptr + sizeof(memoryblock_t); + block->size = size + sizeof(memoryblock_t); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock(block); + allocatedmemory += block->size; + totalmemorysize += block->size + sizeof(memoryblock_t); + numblocks++; + return block->ptr; +} //end of the function GetHunkMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetHunkMemoryDebug(size, label, file, line); +#else + ptr = GetHunkMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +memoryblock_t *BlockFromPointer(void *ptr, char *str) +{ + memoryblock_t *block; + + if (!ptr) + { +#ifdef MEMDEBUG + //char *crash = (char *) NULL; + //crash[0] = 1; + botimport.Print(PRT_FATAL, "%s: NULL pointer\n", str); +#endif // MEMDEBUG + return NULL; + } //end if + block = (memoryblock_t *) ((char *) ptr - sizeof(memoryblock_t)); + if (block->id != MEM_ID && block->id != HUNK_ID) + { + botimport.Print(PRT_FATAL, "%s: invalid memory block\n", str); + return NULL; + } //end if + if (block->ptr != ptr) + { + botimport.Print(PRT_FATAL, "%s: memory block pointer invalid\n", str); + return NULL; + } //end if + return block; +} //end of the function BlockFromPointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory(void *ptr) +{ + memoryblock_t *block; + + block = BlockFromPointer(ptr, "FreeMemory"); + if (!block) return; + UnlinkMemoryBlock(block); + allocatedmemory -= block->size; + totalmemorysize -= block->size + sizeof(memoryblock_t); + numblocks--; + // + if (block->id == MEM_ID) + { + botimport.FreeMemory(block); + } //end if +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AvailableMemory(void) +{ + return botimport.AvailableMemory(); +} //end of the function AvailableMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemoryByteSize(void *ptr) +{ + memoryblock_t *block; + + block = BlockFromPointer(ptr, "MemoryByteSize"); + if (!block) return 0; + return block->size; +} //end of the function MemoryByteSize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize(void) +{ + botimport.Print(PRT_MESSAGE, "total allocated memory: %d KB\n", allocatedmemory >> 10); + botimport.Print(PRT_MESSAGE, "total botlib memory: %d KB\n", totalmemorysize >> 10); + botimport.Print(PRT_MESSAGE, "total memory blocks: %d\n", numblocks); +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels(void) +{ + memoryblock_t *block; + int i; + + PrintUsedMemorySize(); + i = 0; + Log_Write("============= Botlib memory log ==============\r\n"); + Log_Write("\r\n"); + for (block = memory; block; block = block->next) + { +#ifdef MEMDEBUG + if (block->id == HUNK_ID) + { + Log_Write("%6d, hunk %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label); + } //end if + else + { + Log_Write("%6d, %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label); + } //end else +#endif //MEMDEBUG + i++; + } //end for +} //end of the function PrintMemoryLabels +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DumpMemory(void) +{ + memoryblock_t *block; + + for (block = memory; block; block = memory) + { + FreeMemory(block->ptr); + } //end for + totalmemorysize = 0; + allocatedmemory = 0; +} //end of the function DumpMemory + +#else + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + unsigned long int *memid; + + ptr = botimport.GetMemory(size + sizeof(unsigned long int)); + if (!ptr) return NULL; + memid = (unsigned long int *) ptr; + *memid = MEM_ID; + return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int)); +} //end of the function GetMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug(size, label, file, line); +#else + ptr = GetMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + unsigned long int *memid; + + ptr = botimport.HunkAlloc(size + sizeof(unsigned long int)); + if (!ptr) return NULL; + memid = (unsigned long int *) ptr; + *memid = HUNK_ID; + return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int)); +} //end of the function GetHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetHunkMemoryDebug(size, label, file, line); +#else + ptr = GetHunkMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory(void *ptr) +{ + unsigned long int *memid; + + memid = (unsigned long int *) ((char *) ptr - sizeof(unsigned long int)); + + if (*memid == MEM_ID) + { + botimport.FreeMemory(memid); + } //end if +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AvailableMemory(void) +{ + return botimport.AvailableMemory(); +} //end of the function AvailableMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize(void) +{ +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels(void) +{ +} //end of the function PrintMemoryLabels + +#endif diff --git a/codemp/botlib/l_memory.h b/codemp/botlib/l_memory.h new file mode 100644 index 0000000..3adeabf --- /dev/null +++ b/codemp/botlib/l_memory.h @@ -0,0 +1,59 @@ + +/***************************************************************************** + * name: l_memory.h + * + * desc: memory management + * + * $Archive: /source/code/botlib/l_memory.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +//#define MEMDEBUG + +#ifdef MEMDEBUG +#define GetMemory(size) GetMemoryDebug(size, #size, __FILE__, __LINE__); +#define GetClearedMemory(size) GetClearedMemoryDebug(size, #size, __FILE__, __LINE__); +//allocate a memory block of the given size +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line); +//allocate a memory block of the given size and clear it +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line); +// +#define GetHunkMemory(size) GetHunkMemoryDebug(size, #size, __FILE__, __LINE__); +#define GetClearedHunkMemory(size) GetClearedHunkMemoryDebug(size, #size, __FILE__, __LINE__); +//allocate a memory block of the given size +void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line); +//allocate a memory block of the given size and clear it +void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line); +#else +//allocate a memory block of the given size +void *GetMemory(unsigned long size); +//allocate a memory block of the given size and clear it +void *GetClearedMemory(unsigned long size); +// +#ifdef BSPC +#define GetHunkMemory GetMemory +#define GetClearedHunkMemory GetClearedMemory +#else +//allocate a memory block of the given size +void *GetHunkMemory(unsigned long size); +//allocate a memory block of the given size and clear it +void *GetClearedHunkMemory(unsigned long size); +#endif +#endif + +//free the given memory block +void FreeMemory(void *ptr); +//returns the amount available memory +int AvailableMemory(void); +//prints the total used memory size +void PrintUsedMemorySize(void); +//print all memory blocks with label +void PrintMemoryLabels(void); +//returns the size of the memory block in bytes +int MemoryByteSize(void *ptr); +//free all allocated memory +void DumpMemory(void); diff --git a/codemp/botlib/l_precomp.cpp b/codemp/botlib/l_precomp.cpp new file mode 100644 index 0000000..b942d08 --- /dev/null +++ b/codemp/botlib/l_precomp.cpp @@ -0,0 +1,3324 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: l_precomp.c + * + * desc: pre compiler + * + * $Archive: /MissionPack/code/botlib/l_precomp.c $ + * $Author: Ttimo $ + * $Revision: 20 $ + * $Modtime: 5/15/01 4:10a $ + * $Date: 5/15/01 4:10a $ + * + *****************************************************************************/ + +//Notes: fix: PC_StringizeTokens + +//#define SCREWUP +//#define BOTLIB +//#define QUAKE +//#define QUAKEC +//#define MEQCC + +#ifdef SCREWUP +#include +#include +#include +#include +#include +#include +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" + +typedef enum {qfalse, qtrue} qboolean; +#endif //SCREWUP + +#ifdef BOTLIB +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" +#endif //BOTLIB + +#ifdef MEQCC +#include "qcc.h" +#include "time.h" //time & ctime +#include "math.h" //fabs +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" + +#define qtrue true +#define qfalse false +#endif //MEQCC + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" +#include "l_precomp.h" + +#define qtrue true +#define qfalse false +#define Q_stricmp stricmp + +#endif //BSPC + +#if defined(QUAKE) && !defined(BSPC) +#include "l_utils.h" +#endif //QUAKE + +//#define DEBUG_EVAL + +#define MAX_DEFINEPARMS 128 + +#define DEFINEHASHING 1 + +//directive name with parse function +typedef struct directive_s +{ + char *name; + int (*func)(source_t *source); +} directive_t; + +#define DEFINEHASHSIZE 1024 + +#define TOKEN_HEAP_SIZE 4096 + +int numtokens; +/* +int tokenheapinitialized; //true when the token heap is initialized +token_t token_heap[TOKEN_HEAP_SIZE]; //heap with tokens +token_t *freetokens; //free tokens from the heap +*/ + +//list with global defines added to every source loaded +#if DEFINEHASHING +define_t **globaldefines = NULL; +#else +define_t *globaldefines = NULL; +#endif +qboolean addGlobalDefine = qfalse; + +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void QDECL SourceError(source_t *source, char *str, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BSPC +} //end of the function SourceError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL SourceWarning(source_t *source, char *str, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BSPC +} //end of the function ScriptWarning +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PushIndent(source_t *source, int type, int skip) +{ + indent_t *indent; + + indent = (indent_t *) GetMemory(sizeof(indent_t)); + indent->type = type; + indent->script = source->scriptstack; + indent->skip = (skip != 0); + source->skip += indent->skip; + indent->next = source->indentstack; + source->indentstack = indent; +} //end of the function PC_PushIndent +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PopIndent(source_t *source, int *type, int *skip) +{ + indent_t *indent; + + *type = 0; + *skip = 0; + + indent = source->indentstack; + if (!indent) return; + + //must be an indent from the current script + if (source->indentstack->script != source->scriptstack) return; + + *type = indent->type; + *skip = indent->skip; + source->indentstack = source->indentstack->next; + source->skip -= indent->skip; + FreeMemory(indent); +} //end of the function PC_PopIndent +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PushScript(source_t *source, script_t *script) +{ + script_t *s; + + for (s = source->scriptstack; s; s = s->next) + { + if (!Q_stricmp(s->filename, script->filename)) + { + SourceError(source, "%s recursively included", script->filename); + return; + } //end if + } //end for + //push the script on the script stack + script->next = source->scriptstack; + source->scriptstack = script; +} //end of the function PC_PushScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_InitTokenHeap(void) +{ + /* + int i; + + if (tokenheapinitialized) return; + freetokens = NULL; + for (i = 0; i < TOKEN_HEAP_SIZE; i++) + { + token_heap[i].next = freetokens; + freetokens = &token_heap[i]; + } //end for + tokenheapinitialized = qtrue; + */ +} //end of the function PC_InitTokenHeap +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +token_t *PC_CopyToken(token_t *token) +{ + token_t *t; + +// t = (token_t *) malloc(sizeof(token_t)); + t = (token_t *) GetMemory(sizeof(token_t)); +// t = freetokens; + if (!t) + { +#ifdef BSPC + Error("out of token space\n"); +#else + Com_Error(ERR_FATAL, "out of token space\n"); +#endif + return NULL; + } //end if +// freetokens = freetokens->next; + Com_Memcpy(t, token, sizeof(token_t)); + t->next = NULL; + numtokens++; + return t; +} //end of the function PC_CopyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_FreeToken(token_t *token) +{ + //free(token); + FreeMemory(token); +// token->next = freetokens; +// freetokens = token; + numtokens--; +} //end of the function PC_FreeToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadSourceToken(source_t *source, token_t *token) +{ + token_t *t; + script_t *script; + int type, skip; + + //if there's no token already available + while(!source->tokens) + { + //if there's a token to read from the script + if (PS_ReadToken(source->scriptstack, token)) return qtrue; + //if at the end of the script + if (EndOfScript(source->scriptstack)) + { + //remove all indents of the script + while(source->indentstack && + source->indentstack->script == source->scriptstack) + { + SourceWarning(source, "missing #endif"); + PC_PopIndent(source, &type, &skip); + } //end if + } //end if + //if this was the initial script + if (!source->scriptstack->next) return qfalse; + //remove the script and return to the last one + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + FreeScript(script); + } //end while + //copy the already available token + Com_Memcpy(token, source->tokens, sizeof(token_t)); + //free the read token + t = source->tokens; + source->tokens = source->tokens->next; + PC_FreeToken(t); + return qtrue; +} //end of the function PC_ReadSourceToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_UnreadSourceToken(source_t *source, token_t *token) +{ + token_t *t; + + t = PC_CopyToken(token); + t->next = source->tokens; + source->tokens = t; + return qtrue; +} //end of the function PC_UnreadSourceToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadDefineParms(source_t *source, define_t *define, token_t **parms, int maxparms) +{ + token_t token, *t, *last; + int i, done, lastcomma, numparms, indent; + + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "define %s missing parms", define->name); + return qfalse; + } //end if + // + if (define->numparms > maxparms) + { + SourceError(source, "define with more than %d parameters", maxparms); + return qfalse; + } //end if + // + for (i = 0; i < define->numparms; i++) parms[i] = NULL; + //if no leading "(" + if (strcmp(token.string, "(")) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "define %s missing parms", define->name); + return qfalse; + } //end if + //read the define parameters + for (done = 0, numparms = 0, indent = 0; !done;) + { + if (numparms >= maxparms) + { + SourceError(source, "define %s with too many parms", define->name); + return qfalse; + } //end if + if (numparms >= define->numparms) + { + SourceWarning(source, "define %s has too many parms", define->name); + return qfalse; + } //end if + parms[numparms] = NULL; + lastcomma = 1; + last = NULL; + while(!done) + { + // + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "define %s incomplete", define->name); + return qfalse; + } //end if + // + if (!strcmp(token.string, ",")) + { + if (indent <= 0) + { + if (lastcomma) SourceWarning(source, "too many comma's"); + lastcomma = 1; + break; + } //end if + } //end if + lastcomma = 0; + // + if (!strcmp(token.string, "(")) + { + indent++; + continue; + } //end if + else if (!strcmp(token.string, ")")) + { + if (--indent <= 0) + { + if (!parms[define->numparms-1]) + { + SourceWarning(source, "too few define parms"); + } //end if + done = 1; + break; + } //end if + } //end if + // + if (numparms < define->numparms) + { + // + t = PC_CopyToken(&token); + t->next = NULL; + if (last) last->next = t; + else parms[numparms] = t; + last = t; + } //end if + } //end while + numparms++; + } //end for + return qtrue; +} //end of the function PC_ReadDefineParms +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_StringizeTokens(token_t *tokens, token_t *token) +{ + token_t *t; + + token->type = TT_STRING; + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->string[0] = '\0'; + strcat(token->string, "\""); + for (t = tokens; t; t = t->next) + { + strncat(token->string, t->string, MAX_TOKEN - strlen(token->string)); + } //end for + strncat(token->string, "\"", MAX_TOKEN - strlen(token->string)); + return qtrue; +} //end of the function PC_StringizeTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_MergeTokens(token_t *t1, token_t *t2) +{ + //merging of a name with a name or number + if (t1->type == TT_NAME && (t2->type == TT_NAME || t2->type == TT_NUMBER)) + { + strcat(t1->string, t2->string); + return qtrue; + } //end if + //merging of two strings + if (t1->type == TT_STRING && t2->type == TT_STRING) + { + //remove trailing double quote + t1->string[strlen(t1->string)-1] = '\0'; + //concat without leading double quote + strcat(t1->string, &t2->string[1]); + return qtrue; + } //end if + //FIXME: merging of two number of the same sub type + return qfalse; +} //end of the function PC_MergeTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +/* +void PC_PrintDefine(define_t *define) +{ + printf("define->name = %s\n", define->name); + printf("define->flags = %d\n", define->flags); + printf("define->builtin = %d\n", define->builtin); + printf("define->numparms = %d\n", define->numparms); +// token_t *parms; //define parameters +// token_t *tokens; //macro tokens (possibly containing parm tokens) +// struct define_s *next; //next defined macro in a list +} //end of the function PC_PrintDefine*/ +#if DEFINEHASHING +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PrintDefineHashTable(define_t **definehash) +{ + int i; + define_t *d; + + for (i = 0; i < DEFINEHASHSIZE; i++) + { + Log_Write("%4d:", i); + for (d = definehash[i]; d; d = d->hashnext) + { + Log_Write(" %s", d->name); + } //end for + Log_Write("\n"); + } //end for +} //end of the function PC_PrintDefineHashTable +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47}; + +int PC_NameHash(char *name) +{ + int register hash, i; + + hash = 0; + for (i = 0; name[i] != '\0'; i++) + { + hash += name[i] * (119 + i); + //hash += (name[i] << 7) + i; + //hash += (name[i] << (i&15)); + } //end while + hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); + return hash; +} //end of the function PC_NameHash +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddDefineToHash(define_t *define, define_t **definehash) +{ + int hash; + + + if ( addGlobalDefine ) + { + definehash = globaldefines; + define->flags |= DEFINE_GLOBAL; + } + + hash = PC_NameHash(define->name); + define->hashnext = definehash[hash]; + definehash[hash] = define; + + if ( addGlobalDefine ) + { + define->globalnext = define->hashnext; + } +} //end of the function PC_AddDefineToHash +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_FindHashedDefine(define_t **definehash, char *name) +{ + define_t *d; + int hash; + + hash = PC_NameHash(name); + for (d = definehash[hash]; d; d = d->hashnext) + { + if (!strcmp(d->name, name)) return d; + } //end for + return NULL; +} //end of the function PC_FindHashedDefine +#endif //DEFINEHASHING +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_FindDefine(define_t *defines, char *name) +{ + define_t *d; + + for (d = defines; d; d = d->next) + { + if (!strcmp(d->name, name)) return d; + } //end for + return NULL; +} //end of the function PC_FindDefine +//============================================================================ +// +// Parameter: - +// Returns: number of the parm +// if no parm found with the given name -1 is returned +// Changes Globals: - +//============================================================================ +int PC_FindDefineParm(define_t *define, char *name) +{ + token_t *p; + int i; + + i = 0; + for (p = define->parms; p; p = p->next) + { + if (!strcmp(p->string, name)) return i; + i++; + } //end for + return -1; +} //end of the function PC_FindDefineParm +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_FreeDefine(define_t *define) +{ + token_t *t, *next; + + //free the define parameters + for (t = define->parms; t; t = next) + { + next = t->next; + PC_FreeToken(t); + } //end for + //free the define tokens + for (t = define->tokens; t; t = next) + { + next = t->next; + PC_FreeToken(t); + } //end for + //free the define + FreeMemory(define); +} //end of the function PC_FreeDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddBuiltinDefines(source_t *source) +{ + int i; + define_t *define; + struct builtin + { + char *string; + int mBuiltin; + } builtin[] = { // bk001204 - brackets + { "__LINE__", BUILTIN_LINE }, + { "__FILE__", BUILTIN_FILE }, + { "__DATE__", BUILTIN_DATE }, + { "__TIME__", BUILTIN_TIME }, +// { "__STDC__", BUILTIN_STDC }, + { NULL, 0 } + }; + + for (i = 0; builtin[i].string; i++) + { + define = (define_t *) GetMemory(sizeof(define_t) + strlen(builtin[i].string) + 1); + Com_Memset(define, 0, sizeof(define_t)); + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, builtin[i].string); + define->flags |= DEFINE_FIXED; + define->builtin = builtin[i].mBuiltin; + //add the define to the source +#if DEFINEHASHING + PC_AddDefineToHash(define, source->definehash); +#else + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + } //end for +} //end of the function PC_AddBuiltinDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken) +{ + token_t *token; + unsigned long t; // time_t t; //to prevent LCC warning + char *curtime; + + token = PC_CopyToken(deftoken); + switch(define->builtin) + { + case BUILTIN_LINE: + { + sprintf(token->string, "%d", deftoken->line); +#ifdef NUMBERVALUE + token->intvalue = deftoken->line; + token->floatvalue = deftoken->line; +#endif //NUMBERVALUE + token->type = TT_NUMBER; + token->subtype = TT_DECIMAL | TT_INTEGER; + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_FILE: + { + strcpy(token->string, source->scriptstack->filename); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_DATE: + { + t = time(NULL); + curtime = ctime((const long *)&t); + strcpy(token->string, "\""); + strncat(token->string, curtime+4, 7); + strncat(token->string+7, curtime+20, 4); + strcat(token->string, "\""); + free(curtime); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_TIME: + { + t = time(NULL); + curtime = ctime((const long *)&t); + strcpy(token->string, "\""); + strncat(token->string, curtime+11, 8); + strcat(token->string, "\""); + free(curtime); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_STDC: + default: + { + *firsttoken = NULL; + *lasttoken = NULL; + break; + } //end case + } //end switch + return qtrue; +} //end of the function PC_ExpandBuiltinDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandDefine(source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken) +{ + token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t; + token_t *t1, *t2, *first, *last, *nextpt, token; + int parmnum, i; + + //if it is a builtin define + if (define->builtin) + { + return PC_ExpandBuiltinDefine(source, deftoken, define, firsttoken, lasttoken); + } //end if + //if the define has parameters + if (define->numparms) + { + if (!PC_ReadDefineParms(source, define, parms, MAX_DEFINEPARMS)) return qfalse; +#ifdef DEBUG_EVAL + for (i = 0; i < define->numparms; i++) + { + Log_Write("define parms %d:", i); + for (pt = parms[i]; pt; pt = pt->next) + { + Log_Write("%s", pt->string); + } //end for + } //end for +#endif //DEBUG_EVAL + } //end if + //empty list at first + first = NULL; + last = NULL; + //create a list with tokens of the expanded define + for (dt = define->tokens; dt; dt = dt->next) + { + parmnum = -1; + //if the token is a name, it could be a define parameter + if (dt->type == TT_NAME) + { + parmnum = PC_FindDefineParm(define, dt->string); + } //end if + //if it is a define parameter + if (parmnum >= 0) + { + for (pt = parms[parmnum]; pt; pt = pt->next) + { + t = PC_CopyToken(pt); + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } //end for + } //end if + else + { + //if stringizing operator + if (dt->string[0] == '#' && dt->string[1] == '\0') + { + //the stringizing operator must be followed by a define parameter + if (dt->next) parmnum = PC_FindDefineParm(define, dt->next->string); + else parmnum = -1; + // + if (parmnum >= 0) + { + //step over the stringizing operator + dt = dt->next; + //stringize the define parameter tokens + if (!PC_StringizeTokens(parms[parmnum], &token)) + { + SourceError(source, "can't stringize tokens"); + return qfalse; + } //end if + t = PC_CopyToken(&token); + } //end if + else + { + SourceWarning(source, "stringizing operator without define parameter"); + continue; + } //end if + } //end if + else + { + t = PC_CopyToken(dt); + } //end else + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } //end else + } //end for + //check for the merging operator + for (t = first; t; ) + { + if (t->next) + { + //if the merging operator + if (t->next->string[0] == '#' && t->next->string[1] == '#') + { + t1 = t; + t2 = t->next->next; + if (t2) + { + if (!PC_MergeTokens(t1, t2)) + { + SourceError(source, "can't merge %s with %s", t1->string, t2->string); + return qfalse; + } //end if + PC_FreeToken(t1->next); + t1->next = t2->next; + if (t2 == last) last = t1; + PC_FreeToken(t2); + continue; + } //end if + } //end if + } //end if + t = t->next; + } //end for + //store the first and last token of the list + *firsttoken = first; + *lasttoken = last; + //free all the parameter tokens + for (i = 0; i < define->numparms; i++) + { + for (pt = parms[i]; pt; pt = nextpt) + { + nextpt = pt->next; + PC_FreeToken(pt); + } //end for + } //end for + // + return qtrue; +} //end of the function PC_ExpandDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define) +{ + token_t *firsttoken, *lasttoken; + + if (!PC_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return qfalse; + + if (firsttoken && lasttoken) + { + lasttoken->next = source->tokens; + source->tokens = firsttoken; + return qtrue; + } //end if + return qfalse; +} //end of the function PC_ExpandDefineIntoSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_ConvertPath(char *path) +{ + char *ptr; + + //remove double path seperators + for (ptr = path; *ptr;) + { + if ((*ptr == '\\' || *ptr == '/') && + (*(ptr+1) == '\\' || *(ptr+1) == '/')) + { + strcpy(ptr, ptr+1); + } //end if + else + { + ptr++; + } //end else + } //end while + //set OS dependent path seperators + for (ptr = path; *ptr;) + { + if (*ptr == '/' || *ptr == '\\') *ptr = PATHSEPERATOR_CHAR; + ptr++; + } //end while +} //end of the function PC_ConvertPath +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_include(source_t *source) +{ + script_t *script; + token_t token; + char path[MAX_PATH]; +#ifdef QUAKE + foundfile_t file; +#endif //QUAKE + + if (source->skip > 0) return qtrue; + // + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "#include without file name"); + return qfalse; + } //end if + if (token.linescrossed > 0) + { + SourceError(source, "#include without file name"); + return qfalse; + } //end if + if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + PC_ConvertPath(token.string); + script = LoadScriptFile(token.string); + if (!script) + { + strcpy(path, source->includepath); + strcat(path, token.string); + script = LoadScriptFile(path); + } //end if + } //end if + else if (token.type == TT_PUNCTUATION && *token.string == '<') + { + strcpy(path, source->includepath); + while(PC_ReadSourceToken(source, &token)) + { + if (token.linescrossed > 0) + { + PC_UnreadSourceToken(source, &token); + break; + } //end if + if (token.type == TT_PUNCTUATION && *token.string == '>') break; + strncat(path, token.string, MAX_PATH); + } //end while + if (*token.string != '>') + { + SourceWarning(source, "#include missing trailing >"); + } //end if + if (!strlen(path)) + { + SourceError(source, "#include without file name between < >"); + return qfalse; + } //end if + PC_ConvertPath(path); + script = LoadScriptFile(path); + } //end if + else + { + SourceError(source, "#include without file name"); + return qfalse; + } //end else +#ifdef QUAKE + if (!script) + { + Com_Memset(&file, 0, sizeof(foundfile_t)); + script = LoadScriptFile(path); + if (script) strncpy(script->filename, path, MAX_PATH); + } //end if +#endif //QUAKE + if (!script) + { +#ifdef SCREWUP + SourceWarning(source, "file %s not found", path); + return qtrue; +#else + SourceError(source, "file %s not found", path); + return qfalse; +#endif //SCREWUP + } //end if + PC_PushScript(source, script); + return qtrue; +} //end of the function PC_Directive_include +//============================================================================ +// reads a token from the current line, continues reading on the next +// line only if a backslash '\' is encountered. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadLine(source_t *source, token_t *token) +{ + int crossline; + + crossline = 0; + do + { + if (!PC_ReadSourceToken(source, token)) return qfalse; + + if (token->linescrossed > crossline) + { + PC_UnreadSourceToken(source, token); + return qfalse; + } //end if + crossline = 1; + } while(!strcmp(token->string, "\\")); + return qtrue; +} //end of the function PC_ReadLine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_WhiteSpaceBeforeToken(token_t *token) +{ + return token->endwhitespace_p - token->whitespace_p > 0; +} //end of the function PC_WhiteSpaceBeforeToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_ClearTokenWhiteSpace(token_t *token) +{ + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->linescrossed = 0; +} //end of the function PC_ClearTokenWhiteSpace +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_undef(source_t *source) +{ + token_t token; + define_t *define, *lastdefine; + int hash; + + if (source->skip > 0) return qtrue; + // + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "undef without name"); + return qfalse; + } //end if + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "expected name, found %s", token.string); + return qfalse; + } //end if +#if DEFINEHASHING + + hash = PC_NameHash(token.string); + for (lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext) + { + if (!strcmp(define->name, token.string)) + { + if (define->flags & DEFINE_FIXED) + { + SourceWarning(source, "can't undef %s", token.string); + } //end if + else + { + if (lastdefine) lastdefine->hashnext = define->hashnext; + else source->definehash[hash] = define->hashnext; + + if ( !(define->flags & DEFINE_GLOBAL ) ) + { + PC_FreeDefine(define); + } + } //end else + break; + } //end if + lastdefine = define; + } //end for +#else //DEFINEHASHING + for (lastdefine = NULL, define = source->defines; define; define = define->next) + { + if (!strcmp(define->name, token.string)) + { + if (define->flags & DEFINE_FIXED) + { + SourceWarning(source, "can't undef %s", token.string); + } //end if + else + { + if (lastdefine) lastdefine->next = define->next; + else source->defines = define->next; + PC_FreeDefine(define); + } //end else + break; + } //end if + lastdefine = define; + } //end for +#endif //DEFINEHASHING + return qtrue; +} //end of the function PC_Directive_undef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_define(source_t *source) +{ + token_t token, *t, *last; + define_t *define; + + if (source->skip > 0) return qtrue; + // + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "#define without name"); + return qfalse; + } //end if + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "expected name after #define, found %s", token.string); + return qfalse; + } //end if + //check if the define already exists +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + if (define) + { + if (define->flags & DEFINE_FIXED) + { + SourceError(source, "can't redefine %s", token.string); + return qfalse; + } //end if + SourceWarning(source, "redefinition of %s", token.string); + //unread the define name before executing the #undef directive + PC_UnreadSourceToken(source, &token); + if (!PC_Directive_undef(source)) return qfalse; + //if the define was not removed (define->flags & DEFINE_FIXED) +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + } //end if + //allocate define + define = (define_t *) GetMemory(sizeof(define_t) + strlen(token.string) + 1); + Com_Memset(define, 0, sizeof(define_t)); + define->name = (char *) define + sizeof(define_t); + strcpy(define->name, token.string); + //add the define to the source +#if DEFINEHASHING + PC_AddDefineToHash(define, source->definehash); +#else //DEFINEHASHING + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + //if nothing is defined, just return + if (!PC_ReadLine(source, &token)) return qtrue; + //if it is a define with parameters + if (!PC_WhiteSpaceBeforeToken(&token) && !strcmp(token.string, "(")) + { + //read the define parameters + last = NULL; + if (!PC_CheckTokenString(source, ")")) + { + while(1) + { + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "expected define parameter"); + return qfalse; + } //end if + //if it isn't a name + if (token.type != TT_NAME) + { + SourceError(source, "invalid define parameter"); + return qfalse; + } //end if + // + if (PC_FindDefineParm(define, token.string) >= 0) + { + SourceError(source, "two the same define parameters"); + return qfalse; + } //end if + //add the define parm + t = PC_CopyToken(&token); + PC_ClearTokenWhiteSpace(t); + t->next = NULL; + if (last) last->next = t; + else define->parms = t; + last = t; + define->numparms++; + //read next token + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "define parameters not terminated"); + return qfalse; + } //end if + // + if (!strcmp(token.string, ")")) break; + //then it must be a comma + if (strcmp(token.string, ",")) + { + SourceError(source, "define not terminated"); + return qfalse; + } //end if + } //end while + } //end if + if (!PC_ReadLine(source, &token)) return qtrue; + } //end if + //read the defined stuff + last = NULL; + do + { + t = PC_CopyToken(&token); + if (t->type == TT_NAME && !strcmp(t->string, define->name)) + { + SourceError(source, "recursive define (removed recursion)"); + continue; + } //end if + PC_ClearTokenWhiteSpace(t); + t->next = NULL; + if (last) last->next = t; + else define->tokens = t; + last = t; + } while(PC_ReadLine(source, &token)); + // + if (last) + { + //check for merge operators at the beginning or end + if (!strcmp(define->tokens->string, "##") || + !strcmp(last->string, "##")) + { + SourceError(source, "define with misplaced ##"); + return qfalse; + } //end if + } //end if + return qtrue; +} //end of the function PC_Directive_define +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_DefineFromString(char *string) +{ + script_t *script; + source_t src; + token_t *t; + int res, i; + define_t *def; + + PC_InitTokenHeap(); + + script = LoadScriptMemory(string, strlen(string), "*extern"); + //create a new source + Com_Memset(&src, 0, sizeof(source_t)); + strncpy(src.filename, "*extern", MAX_PATH); + src.scriptstack = script; +#if DEFINEHASHING + src.definehash = (struct define_s **)GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); +#endif //DEFINEHASHING + //create a define from the source + res = PC_Directive_define(&src); + //free any tokens if left + for (t = src.tokens; t; t = src.tokens) + { + src.tokens = src.tokens->next; + PC_FreeToken(t); + } //end for +#ifdef DEFINEHASHING + def = NULL; + for (i = 0; i < DEFINEHASHSIZE; i++) + { + if (src.definehash[i]) + { + def = src.definehash[i]; + break; + } //end if + } //end for +#else + def = src.defines; +#endif //DEFINEHASHING + // +#if DEFINEHASHING + FreeMemory(src.definehash); +#endif //DEFINEHASHING + // + FreeScript(script); + //if the define was created succesfully + if (res > 0) return def; + //free the define is created + if (src.defines) PC_FreeDefine(def); + // + return NULL; +} //end of the function PC_DefineFromString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_AddDefine(source_t *source, char *string) +{ + define_t *define; + + if ( addGlobalDefine ) + { + return PC_AddGlobalDefine ( string ); + } + + define = PC_DefineFromString(string); + if (!define) return qfalse; +#if DEFINEHASHING + PC_AddDefineToHash(define, source->definehash); +#else //DEFINEHASHING + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + return qtrue; +} //end of the function PC_AddDefine +//============================================================================ +// add a globals define that will be added to all opened sources +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_AddGlobalDefine(char *string) +{ +#if !DEFINEHASHING + define_t *define; + + define = PC_DefineFromString(string); + if (!define) return qfalse; + define->next = globaldefines; + globaldefines = define; +#endif + return qtrue; +} //end of the function PC_AddGlobalDefine +//============================================================================ +// remove the given global define +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_RemoveGlobalDefine(char *name) +{ +#if !DEFINEHASHING + define_t *define; + + define = PC_FindDefine(globaldefines, name); + if (define) + { + PC_FreeDefine(define); + return qtrue; + } //end if +#endif + return qfalse; +} //end of the function PC_RemoveGlobalDefine +//============================================================================ +// remove all globals defines +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_RemoveAllGlobalDefines(void) +{ + define_t *define; + +#if DEFINEHASHING + int i; + if ( globaldefines ) + { + for (i = 0; i < DEFINEHASHSIZE; i++) + { + while(globaldefines[i]) + { + define = globaldefines[i]; + globaldefines[i] = globaldefines[i]->globalnext; + PC_FreeDefine(define); + } + } + } +#else //DEFINEHASHING + for (define = globaldefines; define; define = globaldefines) + { + globaldefines = globaldefines->next; + PC_FreeDefine(define); + } //end for +#endif +} //end of the function PC_RemoveAllGlobalDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_CopyDefine(source_t *source, define_t *define) +{ + define_t *newdefine; + token_t *token, *newtoken, *lasttoken; + + newdefine = (define_t *) GetMemory(sizeof(define_t) + strlen(define->name) + 1); + //copy the define name + newdefine->name = (char *) newdefine + sizeof(define_t); + strcpy(newdefine->name, define->name); + newdefine->flags = define->flags; + newdefine->builtin = define->builtin; + newdefine->numparms = define->numparms; + //the define is not linked + newdefine->next = NULL; + newdefine->hashnext = NULL; + //copy the define tokens + newdefine->tokens = NULL; + for (lasttoken = NULL, token = define->tokens; token; token = token->next) + { + newtoken = PC_CopyToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->tokens = newtoken; + lasttoken = newtoken; + } //end for + //copy the define parameters + newdefine->parms = NULL; + for (lasttoken = NULL, token = define->parms; token; token = token->next) + { + newtoken = PC_CopyToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->parms = newtoken; + lasttoken = newtoken; + } //end for + return newdefine; +} //end of the function PC_CopyDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddGlobalDefinesToSource(source_t *source) +{ + define_t *define; + +#if DEFINEHASHING + int i; + for (i = 0; i < DEFINEHASHSIZE; i++) + { + define = globaldefines[i]; + while(define) + { + define->hashnext = NULL; + PC_AddDefineToHash(define, source->definehash); + + define = define->globalnext; + } + } +#else //DEFINEHASHING + define_t* newdefine; + for (define = globaldefines; define; define = define->next) + { + newdefine = PC_CopyDefine(source, define); + + + + newdefine->next = source->defines; + source->defines = newdefine; + } +#endif +} //end of the function PC_AddGlobalDefinesToSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_if_def(source_t *source, int type) +{ + token_t token; + define_t *d; + int skip; + + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "#ifdef without name"); + return qfalse; + } //end if + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "expected name after #ifdef, found %s", token.string); + return qfalse; + } //end if +#if DEFINEHASHING + d = PC_FindHashedDefine(source->definehash, token.string); +#else + d = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + skip = (type == INDENT_IFDEF) == (d == NULL); + PC_PushIndent(source, type, skip); + return qtrue; +} //end of the function PC_Directiveif_def +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_ifdef(source_t *source) +{ + return PC_Directive_if_def(source, INDENT_IFDEF); +} //end of the function PC_Directive_ifdef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_ifndef(source_t *source) +{ + return PC_Directive_if_def(source, INDENT_IFNDEF); +} //end of the function PC_Directive_ifndef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_else(source_t *source) +{ + int type, skip; + + PC_PopIndent(source, &type, &skip); + if (!type) + { + SourceError(source, "misplaced #else"); + return qfalse; + } //end if + if (type == INDENT_ELSE) + { + SourceError(source, "#else after #else"); + return qfalse; + } //end if + PC_PushIndent(source, INDENT_ELSE, !skip); + return qtrue; +} //end of the function PC_Directive_else +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_endif(source_t *source) +{ + int type, skip; + + PC_PopIndent(source, &type, &skip); + if (!type) + { + SourceError(source, "misplaced #endif"); + return qfalse; + } //end if + return qtrue; +} //end of the function PC_Directive_endif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +typedef struct operator_s +{ + int mOperator; + int priority; + int parentheses; + struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ + signed long int intvalue; + double floatvalue; + int parentheses; + struct value_s *prev, *next; +} value_t; + +int PC_OperatorPriority(int op) +{ + switch(op) + { + case P_MUL: return 15; + case P_DIV: return 15; + case P_MOD: return 15; + case P_ADD: return 14; + case P_SUB: return 14; + + case P_LOGIC_AND: return 7; + case P_LOGIC_OR: return 6; + case P_LOGIC_GEQ: return 12; + case P_LOGIC_LEQ: return 12; + case P_LOGIC_EQ: return 11; + case P_LOGIC_UNEQ: return 11; + + case P_LOGIC_NOT: return 16; + case P_LOGIC_GREATER: return 12; + case P_LOGIC_LESS: return 12; + + case P_RSHIFT: return 13; + case P_LSHIFT: return 13; + + case P_BIN_AND: return 10; + case P_BIN_OR: return 8; + case P_BIN_XOR: return 9; + case P_BIN_NOT: return 16; + + case P_COLON: return 5; + case P_QUESTIONMARK: return 5; + } //end switch + return qfalse; +} //end of the function PC_OperatorPriority + +//#define AllocValue() GetClearedMemory(sizeof(value_t)); +//#define FreeValue(val) FreeMemory(val) +//#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); +//#define FreeOperator(op) FreeMemory(op); + +#define MAX_VALUES 64 +#define MAX_OPERATORS 64 +#define AllocValue(val) \ + if (numvalues >= MAX_VALUES) { \ + SourceError(source, "out of value space\n"); \ + error = 1; \ + break; \ + } \ + else \ + val = &value_heap[numvalues++]; +#define FreeValue(val) +// +#define AllocOperator(op) \ + if (numoperators >= MAX_OPERATORS) { \ + SourceError(source, "out of operator space\n"); \ + error = 1; \ + break; \ + } \ + else \ + op = &operator_heap[numoperators++]; +#define FreeOperator(op) + +int PC_EvaluateTokens(source_t *source, token_t *tokens, signed long int *intvalue, + double *floatvalue, int integer) +{ + operator_t *o, *firstoperator, *lastoperator; + value_t *v, *firstvalue, *lastvalue, *v1, *v2; + token_t *t; + int brace = 0; + int parentheses = 0; + int error = 0; + int lastwasvalue = 0; + int negativevalue = 0; + int questmarkintvalue = 0; + double questmarkfloatvalue = 0; + int gotquestmarkvalue = qfalse; + int lastoperatortype = 0; + // + operator_t operator_heap[MAX_OPERATORS]; + int numoperators = 0; + value_t value_heap[MAX_VALUES]; + int numvalues = 0; + + firstoperator = lastoperator = NULL; + firstvalue = lastvalue = NULL; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + for (t = tokens; t; t = t->next) + { + switch(t->type) + { + case TT_NAME: + { + if (lastwasvalue || negativevalue) + { + SourceError(source, "syntax error in #if/#elif"); + error = 1; + break; + } //end if + if (strcmp(t->string, "defined")) + { + SourceError(source, "undefined name %s in #if/#elif", t->string); + error = 1; + break; + } //end if + t = t->next; + if (!strcmp(t->string, "(")) + { + brace = qtrue; + t = t->next; + } //end if + if (!t || t->type != TT_NAME) + { + SourceError(source, "defined without name in #if/#elif"); + error = 1; + break; + } //end if + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); +#if DEFINEHASHING + if (PC_FindHashedDefine(source->definehash, t->string)) +#else + if (PC_FindDefine(source->defines, t->string)) +#endif //DEFINEHASHING + { + v->intvalue = 1; + v->floatvalue = 1; + } //end if + else + { + v->intvalue = 0; + v->floatvalue = 0; + } //end else + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + if (brace) + { + t = t->next; + if (!t || strcmp(t->string, ")")) + { + SourceError(source, "defined without ) in #if/#elif"); + error = 1; + break; + } //end if + } //end if + brace = qfalse; + // defined() creates a value + lastwasvalue = 1; + break; + } //end case + case TT_NUMBER: + { + if (lastwasvalue) + { + SourceError(source, "syntax error in #if/#elif"); + error = 1; + break; + } //end if + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); + if (negativevalue) + { + v->intvalue = - (signed int) t->intvalue; + v->floatvalue = - t->floatvalue; + } //end if + else + { + v->intvalue = t->intvalue; + v->floatvalue = t->floatvalue; + } //end else + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + //last token was a value + lastwasvalue = 1; + // + negativevalue = 0; + break; + } //end case + case TT_PUNCTUATION: + { + if (negativevalue) + { + SourceError(source, "misplaced minus sign in #if/#elif"); + error = 1; + break; + } //end if + if (t->subtype == P_PARENTHESESOPEN) + { + parentheses++; + break; + } //end if + else if (t->subtype == P_PARENTHESESCLOSE) + { + parentheses--; + if (parentheses < 0) + { + SourceError(source, "too many ) in #if/#elsif"); + error = 1; + } //end if + break; + } //end else if + //check for invalid operators on floating point values + if (!integer) + { + if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || + t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || + t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || + t->subtype == P_BIN_XOR) + { + SourceError(source, "illigal operator %s on floating point operands\n", t->string); + error = 1; + break; + } //end if + } //end if + switch(t->subtype) + { + case P_LOGIC_NOT: + case P_BIN_NOT: + { + if (lastwasvalue) + { + SourceError(source, "! or ~ after value in #if/#elif"); + error = 1; + break; + } //end if + break; + } //end case + case P_INC: + case P_DEC: + { + SourceError(source, "++ or -- used in #if/#elif"); + break; + } //end case + case P_SUB: + { + if (!lastwasvalue) + { + negativevalue = 1; + break; + } //end if + } //end case + + case P_MUL: + case P_DIV: + case P_MOD: + case P_ADD: + + case P_LOGIC_AND: + case P_LOGIC_OR: + case P_LOGIC_GEQ: + case P_LOGIC_LEQ: + case P_LOGIC_EQ: + case P_LOGIC_UNEQ: + + case P_LOGIC_GREATER: + case P_LOGIC_LESS: + + case P_RSHIFT: + case P_LSHIFT: + + case P_BIN_AND: + case P_BIN_OR: + case P_BIN_XOR: + + case P_COLON: + case P_QUESTIONMARK: + { + if (!lastwasvalue) + { + SourceError(source, "operator %s after operator in #if/#elif", t->string); + error = 1; + break; + } //end if + break; + } //end case + default: + { + SourceError(source, "invalid operator %s in #if/#elif", t->string); + error = 1; + break; + } //end default + } //end switch + if (!error && !negativevalue) + { + //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); + AllocOperator(o); + o->mOperator = t->subtype; + o->priority = PC_OperatorPriority(t->subtype); + o->parentheses = parentheses; + o->next = NULL; + o->prev = lastoperator; + if (lastoperator) lastoperator->next = o; + else firstoperator = o; + lastoperator = o; + lastwasvalue = 0; + } //end if + break; + } //end case + default: + { + SourceError(source, "unknown %s in #if/#elif", t->string); + error = 1; + break; + } //end default + } //end switch + if (error) break; + } //end for + if (!error) + { + if (!lastwasvalue) + { + SourceError(source, "trailing operator in #if/#elif"); + error = 1; + } //end if + else if (parentheses) + { + SourceError(source, "too many ( in #if/#elif"); + error = 1; + } //end else if + } //end if + // + gotquestmarkvalue = qfalse; + questmarkintvalue = 0; + questmarkfloatvalue = 0; + //while there are operators + while(!error && firstoperator) + { + v = firstvalue; + for (o = firstoperator; o->next; o = o->next) + { + //if the current operator is nested deeper in parentheses + //than the next operator + if (o->parentheses > o->next->parentheses) break; + //if the current and next operator are nested equally deep in parentheses + if (o->parentheses == o->next->parentheses) + { + //if the priority of the current operator is equal or higher + //than the priority of the next operator + if (o->priority >= o->next->priority) break; + } //end if + //if the arity of the operator isn't equal to 1 + if (o->mOperator != P_LOGIC_NOT + && o->mOperator != P_BIN_NOT) v = v->next; + //if there's no value or no next value + if (!v) + { + SourceError(source, "mising values in #if/#elif"); + error = 1; + break; + } //end if + } //end for + if (error) break; + v1 = v; + v2 = v->next; +#ifdef DEBUG_EVAL + if (integer) + { + Log_Write("operator %s, value1 = %d", PunctuationFromNum(source->scriptstack, o->mOperator), v1->intvalue); + if (v2) Log_Write("value2 = %d", v2->intvalue); + } //end if + else + { + Log_Write("operator %s, value1 = %f", PunctuationFromNum(source->scriptstack, o->mOperator), v1->floatvalue); + if (v2) Log_Write("value2 = %f", v2->floatvalue); + } //end else +#endif //DEBUG_EVAL + switch(o->mOperator) + { + case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; + v1->floatvalue = !v1->floatvalue; break; + case P_BIN_NOT: v1->intvalue = ~v1->intvalue; + break; + case P_MUL: v1->intvalue *= v2->intvalue; + v1->floatvalue *= v2->floatvalue; break; + case P_DIV: if (!v2->intvalue || !v2->floatvalue) + { + SourceError(source, "divide by zero in #if/#elif\n"); + error = 1; + break; + } + v1->intvalue /= v2->intvalue; + v1->floatvalue /= v2->floatvalue; break; + case P_MOD: if (!v2->intvalue) + { + SourceError(source, "divide by zero in #if/#elif\n"); + error = 1; + break; + } + v1->intvalue %= v2->intvalue; break; + case P_ADD: v1->intvalue += v2->intvalue; + v1->floatvalue += v2->floatvalue; break; + case P_SUB: v1->intvalue -= v2->intvalue; + v1->floatvalue -= v2->floatvalue; break; + case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; + v1->floatvalue = v1->floatvalue && v2->floatvalue; break; + case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; + v1->floatvalue = v1->floatvalue || v2->floatvalue; break; + case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; + v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; + case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; + v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; + case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; + v1->floatvalue = v1->floatvalue == v2->floatvalue; break; + case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; + v1->floatvalue = v1->floatvalue != v2->floatvalue; break; + case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; + v1->floatvalue = v1->floatvalue > v2->floatvalue; break; + case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; + v1->floatvalue = v1->floatvalue < v2->floatvalue; break; + case P_RSHIFT: v1->intvalue >>= v2->intvalue; + break; + case P_LSHIFT: v1->intvalue <<= v2->intvalue; + break; + case P_BIN_AND: v1->intvalue &= v2->intvalue; + break; + case P_BIN_OR: v1->intvalue |= v2->intvalue; + break; + case P_BIN_XOR: v1->intvalue ^= v2->intvalue; + break; + case P_COLON: + { + if (!gotquestmarkvalue) + { + SourceError(source, ": without ? in #if/#elif"); + error = 1; + break; + } //end if + if (integer) + { + if (!questmarkintvalue) v1->intvalue = v2->intvalue; + } //end if + else + { + if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue; + } //end else + gotquestmarkvalue = qfalse; + break; + } //end case + case P_QUESTIONMARK: + { + if (gotquestmarkvalue) + { + SourceError(source, "? after ? in #if/#elif"); + error = 1; + break; + } //end if + questmarkintvalue = v1->intvalue; + questmarkfloatvalue = v1->floatvalue; + gotquestmarkvalue = qtrue; + break; + } //end if + } //end switch +#ifdef DEBUG_EVAL + if (integer) Log_Write("result value = %d", v1->intvalue); + else Log_Write("result value = %f", v1->floatvalue); +#endif //DEBUG_EVAL + if (error) break; + lastoperatortype = o->mOperator; + //if not an operator with arity 1 + if (o->mOperator != P_LOGIC_NOT + && o->mOperator != P_BIN_NOT) + { + //remove the second value if not question mark operator + if (o->mOperator != P_QUESTIONMARK) v = v->next; + // + if (v->prev) v->prev->next = v->next; + else firstvalue = v->next; + if (v->next) v->next->prev = v->prev; + else lastvalue = v->prev; + //FreeMemory(v); + FreeValue(v); + } //end if + //remove the operator + if (o->prev) o->prev->next = o->next; + else firstoperator = o->next; + if (o->next) o->next->prev = o->prev; + else lastoperator = o->prev; + //FreeMemory(o); + FreeOperator(o); + } //end while + if (firstvalue) + { + if (intvalue) *intvalue = firstvalue->intvalue; + if (floatvalue) *floatvalue = firstvalue->floatvalue; + } //end if + for (o = firstoperator; o; o = lastoperator) + { + lastoperator = o->next; + //FreeMemory(o); + FreeOperator(o); + } //end for + for (v = firstvalue; v; v = lastvalue) + { + lastvalue = v->next; + //FreeMemory(v); + FreeValue(v); + } //end for + if (!error) return qtrue; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + return qfalse; +} //end of the function PC_EvaluateTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Evaluate(source_t *source, signed long int *intvalue, + double *floatvalue, int integer) +{ + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + int defined = qfalse; + + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + // + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "no value after #if/#elif"); + return qfalse; + } //end if + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if (token.type == TT_NAME) + { + if (defined) + { + defined = qfalse; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else if (!strcmp(token.string, "defined")) + { + defined = qtrue; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else + { + //then it must be a define +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + if (!define) + { + SourceError(source, "can't evaluate %s, not defined", token.string); + return qfalse; + } //end if + if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse; + } //end else + } //end if + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) + { + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end else + else //can't evaluate the token + { + SourceError(source, "can't evaluate %s", token.string); + return qfalse; + } //end else + } while(PC_ReadLine(source, &token)); + // + if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; + // +#ifdef DEBUG_EVAL + Log_Write("eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) + { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->string); +#endif //DEBUG_EVAL + nexttoken = t->next; + PC_FreeToken(t); + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("eval result: %d", *intvalue); + else Log_Write("eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return qtrue; +} //end of the function PC_Evaluate +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarEvaluate(source_t *source, signed long int *intvalue, + double *floatvalue, int integer) +{ + int indent, defined = qfalse; + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + // + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "no leading ( after $evalint/$evalfloat"); + return qfalse; + } //end if + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "nothing to evaluate"); + return qfalse; + } //end if + indent = 1; + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if (token.type == TT_NAME) + { + if (defined) + { + defined = qfalse; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else if (!strcmp(token.string, "defined")) + { + defined = qtrue; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else + { + //then it must be a define +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + if (!define) + { + SourceError(source, "can't evaluate %s, not defined", token.string); + return qfalse; + } //end if + if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse; + } //end else + } //end if + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) + { + if (*token.string == '(') indent++; + else if (*token.string == ')') indent--; + if (indent <= 0) break; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end else + else //can't evaluate the token + { + SourceError(source, "can't evaluate %s", token.string); + return qfalse; + } //end else + } while(PC_ReadSourceToken(source, &token)); + // + if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; + // +#ifdef DEBUG_EVAL + Log_Write("$eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) + { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->string); +#endif //DEBUG_EVAL + nexttoken = t->next; + PC_FreeToken(t); + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("$eval result: %d", *intvalue); + else Log_Write("$eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return qtrue; +} //end of the function PC_DollarEvaluate +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_elif(source_t *source) +{ + signed long int value; + int type, skip; + + PC_PopIndent(source, &type, &skip); + if (!type || type == INDENT_ELSE) + { + SourceError(source, "misplaced #elif"); + return qfalse; + } //end if + if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; + skip = (value == 0); + PC_PushIndent(source, INDENT_ELIF, skip); + return qtrue; +} //end of the function PC_Directive_elif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_if(source_t *source) +{ + signed long int value; + int skip; + + if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; + skip = (value == 0); + PC_PushIndent(source, INDENT_IF, skip); + return qtrue; +} //end of the function PC_Directive +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_line(source_t *source) +{ + SourceError(source, "#line directive not supported"); + return qfalse; +} //end of the function PC_Directive_line +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_error(source_t *source) +{ + token_t token; + + strcpy(token.string, ""); + PC_ReadSourceToken(source, &token); + SourceError(source, "#error directive: %s", token.string); + return qfalse; +} //end of the function PC_Directive_error +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_pragma(source_t *source) +{ + token_t token; + + SourceWarning(source, "#pragma directive not supported"); + while(PC_ReadLine(source, &token)) ; + return qtrue; +} //end of the function PC_Directive_pragma +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void UnreadSignToken(source_t *source) +{ + token_t token; + + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + strcpy(token.string, "-"); + token.type = TT_PUNCTUATION; + token.subtype = P_SUB; + PC_UnreadSourceToken(source, &token); +} //end of the function UnreadSignToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_eval(source_t *source) +{ + signed long int value; + token_t token; + + if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%d", abs(value)); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + PC_UnreadSourceToken(source, &token); + if (value < 0) UnreadSignToken(source); + return qtrue; +} //end of the function PC_Directive_eval +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_evalfloat(source_t *source) +{ + double value; + token_t token; + + if (!PC_Evaluate(source, NULL, &value, qfalse)) return qfalse; + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%1.2f", fabs(value)); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + PC_UnreadSourceToken(source, &token); + if (value < 0) UnreadSignToken(source); + return qtrue; +} //end of the function PC_Directive_evalfloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +directive_t directives[20] = +{ + {"if", PC_Directive_if}, + {"ifdef", PC_Directive_ifdef}, + {"ifndef", PC_Directive_ifndef}, + {"elif", PC_Directive_elif}, + {"else", PC_Directive_else}, + {"endif", PC_Directive_endif}, + {"include", PC_Directive_include}, + {"define", PC_Directive_define}, + {"undef", PC_Directive_undef}, + {"line", PC_Directive_line}, + {"error", PC_Directive_error}, + {"pragma", PC_Directive_pragma}, + {"eval", PC_Directive_eval}, + {"evalfloat", PC_Directive_evalfloat}, + {NULL, NULL} +}; + +int PC_ReadDirective(source_t *source) +{ + token_t token; + int i; + + //read the directive name + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "found # without name"); + return qfalse; + } //end if + //directive name must be on the same line + if (token.linescrossed > 0) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "found # at end of line"); + return qfalse; + } //end if + //if if is a name + if (token.type == TT_NAME) + { + //find the precompiler directive + for (i = 0; directives[i].name; i++) + { + if (!strcmp(directives[i].name, token.string)) + { + return directives[i].func(source); + } //end if + } //end for + } //end if + SourceError(source, "unknown precompiler directive %s", token.string); + return qfalse; +} //end of the function PC_ReadDirective +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarDirective_evalint(source_t *source) +{ + signed long int value; + token_t token; + + if (!PC_DollarEvaluate(source, &value, NULL, qtrue)) return qfalse; + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%d", abs(value)); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; +#ifdef NUMBERVALUE + token.intvalue = value; + token.floatvalue = value; +#endif //NUMBERVALUE + PC_UnreadSourceToken(source, &token); + if (value < 0) UnreadSignToken(source); + return qtrue; +} //end of the function PC_DollarDirective_evalint +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarDirective_evalfloat(source_t *source) +{ + double value; + token_t token; + + if (!PC_DollarEvaluate(source, NULL, &value, qfalse)) return qfalse; + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%1.2f", fabs(value)); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; +#ifdef NUMBERVALUE + token.intvalue = (unsigned long) value; + token.floatvalue = value; +#endif //NUMBERVALUE + PC_UnreadSourceToken(source, &token); + if (value < 0) UnreadSignToken(source); + return qtrue; +} //end of the function PC_DollarDirective_evalfloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +directive_t dollardirectives[20] = +{ + {"evalint", PC_DollarDirective_evalint}, + {"evalfloat", PC_DollarDirective_evalfloat}, + {NULL, NULL} +}; + +int PC_ReadDollarDirective(source_t *source) +{ + token_t token; + int i; + + //read the directive name + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "found $ without name"); + return qfalse; + } //end if + //directive name must be on the same line + if (token.linescrossed > 0) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "found $ at end of line"); + return qfalse; + } //end if + //if if is a name + if (token.type == TT_NAME) + { + //find the precompiler directive + for (i = 0; dollardirectives[i].name; i++) + { + if (!strcmp(dollardirectives[i].name, token.string)) + { + return dollardirectives[i].func(source); + } //end if + } //end for + } //end if + PC_UnreadSourceToken(source, &token); + SourceError(source, "unknown precompiler directive %s", token.string); + return qfalse; +} //end of the function PC_ReadDirective + +#ifdef QUAKEC +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int BuiltinFunction(source_t *source) +{ + token_t token; + + if (!PC_ReadSourceToken(source, &token)) return qfalse; + if (token.type == TT_NUMBER) + { + PC_UnreadSourceToken(source, &token); + return qtrue; + } //end if + else + { + PC_UnreadSourceToken(source, &token); + return qfalse; + } //end else +} //end of the function BuiltinFunction +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int QuakeCMacro(source_t *source) +{ + int i; + token_t token; + + if (!PC_ReadSourceToken(source, &token)) return qtrue; + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + return qtrue; + } //end if + //find the precompiler directive + for (i = 0; dollardirectives[i].name; i++) + { + if (!strcmp(dollardirectives[i].name, token.string)) + { + PC_UnreadSourceToken(source, &token); + return qfalse; + } //end if + } //end for + PC_UnreadSourceToken(source, &token); + return qtrue; +} //end of the function QuakeCMacro +#endif //QUAKEC +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadToken(source_t *source, token_t *token) +{ + define_t *define; + + while(1) + { + if (!PC_ReadSourceToken(source, token)) return qfalse; + //check for precompiler directives + if (token->type == TT_PUNCTUATION && *token->string == '@') // It is a StringEd key + { + char *holdString,holdString2[MAX_TOKEN]; + + PC_ReadSourceToken(source, token); + holdString = &token->string[1]; + Com_Memcpy( holdString2, token->string, sizeof(token->string)); + Com_Memcpy( holdString, holdString2, sizeof(token->string)); + token->string[0] = '@'; + return qtrue; + } + + if (token->type == TT_PUNCTUATION && *token->string == '#') + { +#ifdef QUAKEC + if (!BuiltinFunction(source)) +#endif //QUAKC + { + //read the precompiler directive + if (!PC_ReadDirective(source)) return qfalse; + continue; + } //end if + } //end if + if (token->type == TT_PUNCTUATION && *token->string == '$') + { +#ifdef QUAKEC + if (!QuakeCMacro(source)) +#endif //QUAKEC + { + //read the precompiler directive + if (!PC_ReadDollarDirective(source)) return qfalse; + continue; + } //end if + } //end if + // recursively concatenate strings that are behind each other still resolving defines + if (token->type == TT_STRING) + { + token_t newtoken; + if (PC_ReadToken(source, &newtoken)) + { + if (newtoken.type == TT_STRING) + { + token->string[strlen(token->string)-1] = '\0'; + if (strlen(token->string) + strlen(newtoken.string+1) + 1 >= MAX_TOKEN) + { + SourceError(source, "string longer than MAX_TOKEN %d\n", MAX_TOKEN); + return qfalse; + } + strcat(token->string, newtoken.string+1); + } + else + { + PC_UnreadToken(source, &newtoken); + } + } + } //end if + //if skipping source because of conditional compilation + if (source->skip) continue; + //if the token is a name + if (token->type == TT_NAME) + { + //check if the name is a define macro +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token->string); +#else + define = PC_FindDefine(source->defines, token->string); +#endif //DEFINEHASHING + //if it is a define macro + if (define) + { + //expand the defined macro + if (!PC_ExpandDefineIntoSource(source, token, define)) return qfalse; + continue; + } //end if + } //end if + //copy token for unreading + Com_Memcpy(&source->token, token, sizeof(token_t)); + //found a token + return qtrue; + } //end while +} //end of the function PC_ReadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectTokenString(source_t *source, char *string) +{ + token_t token; + + if (!PC_ReadToken(source, &token)) + { + SourceError(source, "couldn't find expected %s", string); + return qfalse; + } //end if + + if (strcmp(token.string, string)) + { + SourceError(source, "expected %s, found %s", string, token.string); + return qfalse; + } //end if + return qtrue; +} //end of the function PC_ExpectTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token) +{ + char str[MAX_TOKEN]; + + if (!PC_ReadToken(source, token)) + { + SourceError(source, "couldn't read expected token"); + return qfalse; + } //end if + + if (token->type != type) + { + strcpy(str, ""); + if (type == TT_STRING) strcpy(str, "string"); + if (type == TT_LITERAL) strcpy(str, "literal"); + if (type == TT_NUMBER) strcpy(str, "number"); + if (type == TT_NAME) strcpy(str, "name"); + if (type == TT_PUNCTUATION) strcpy(str, "punctuation"); + SourceError(source, "expected a %s, found %s", str, token->string); + return qfalse; + } //end if + if (token->type == TT_NUMBER) + { + if ((token->subtype & subtype) != subtype) + { + if (subtype & TT_DECIMAL) strcpy(str, "decimal"); + if (subtype & TT_HEX) strcpy(str, "hex"); + if (subtype & TT_OCTAL) strcpy(str, "octal"); + if (subtype & TT_BINARY) strcpy(str, "binary"); + if (subtype & TT_LONG) strcat(str, " long"); + if (subtype & TT_UNSIGNED) strcat(str, " unsigned"); + if (subtype & TT_FLOAT) strcat(str, " float"); + if (subtype & TT_INTEGER) strcat(str, " integer"); + SourceError(source, "expected %s, found %s", str, token->string); + return qfalse; + } //end if + } //end if + else if (token->type == TT_PUNCTUATION) + { + if (token->subtype != subtype) + { + SourceError(source, "found %s", token->string); + return qfalse; + } //end if + } //end else if + return qtrue; +} //end of the function PC_ExpectTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectAnyToken(source_t *source, token_t *token) +{ + if (!PC_ReadToken(source, token)) + { + SourceError(source, "couldn't read expected token"); + return qfalse; + } //end if + else + { + return qtrue; + } //end else +} //end of the function PC_ExpectAnyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_CheckTokenString(source_t *source, char *string) +{ + token_t tok; + + if (!PC_ReadToken(source, &tok)) return qfalse; + //if the token is available + if (!strcmp(tok.string, string)) return qtrue; + // + PC_UnreadSourceToken(source, &tok); + return qfalse; +} //end of the function PC_CheckTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token) +{ + token_t tok; + + if (!PC_ReadToken(source, &tok)) return qfalse; + //if the type matches + if (tok.type == type && + (tok.subtype & subtype) == subtype) + { + Com_Memcpy(token, &tok, sizeof(token_t)); + return qtrue; + } //end if + // + PC_UnreadSourceToken(source, &tok); + return qfalse; +} //end of the function PC_CheckTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_SkipUntilString(source_t *source, char *string) +{ + token_t token; + + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, string)) return qtrue; + } //end while + return qfalse; +} //end of the function PC_SkipUntilString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadLastToken(source_t *source) +{ + PC_UnreadSourceToken(source, &source->token); +} //end of the function PC_UnreadLastToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadToken(source_t *source, token_t *token) +{ + PC_UnreadSourceToken(source, token); +} //end of the function PC_UnreadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetIncludePath(source_t *source, char *path) +{ + strncpy(source->includepath, path, MAX_PATH); + //add trailing path seperator + if (source->includepath[strlen(source->includepath)-1] != '\\' && + source->includepath[strlen(source->includepath)-1] != '/') + { + strcat(source->includepath, PATHSEPERATOR_STR); + } //end if +} //end of the function PC_SetIncludePath +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetPunctuations(source_t *source, punctuation_t *p) +{ + source->punctuations = p; +} //end of the function PC_SetPunctuations +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +source_t *LoadSourceFile(const char *filename) +{ + source_t *source; + script_t *script; + + PC_InitTokenHeap(); + +#if DEFINEHASHING + if ( !globaldefines ) + { + globaldefines = (struct define_s **)GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); + } +#endif + + script = LoadScriptFile(filename); + if (!script) return NULL; + + script->next = NULL; + + source = (source_t *) GetMemory(sizeof(source_t)); + Com_Memset(source, 0, sizeof(source_t)); + + strncpy(source->filename, filename, MAX_PATH); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + +#if DEFINEHASHING + source->definehash = (struct define_s **)GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); +#endif //DEFINEHASHING + PC_AddGlobalDefinesToSource(source); + return source; +} //end of the function LoadSourceFile +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +source_t *LoadSourceMemory(char *ptr, int length, char *name) +{ + source_t *source; + script_t *script; + + PC_InitTokenHeap(); + + script = LoadScriptMemory(ptr, length, name); + if (!script) return NULL; + script->next = NULL; + + source = (source_t *) GetMemory(sizeof(source_t)); + Com_Memset(source, 0, sizeof(source_t)); + + strncpy(source->filename, name, MAX_PATH); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + +#if DEFINEHASHING + source->definehash = (struct define_s **)GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); +#endif //DEFINEHASHING + PC_AddGlobalDefinesToSource(source); + return source; +} //end of the function LoadSourceMemory +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void FreeSource(source_t *source) +{ + script_t *script; + token_t *token; + define_t *define; + indent_t *indent; + define_t *nextdefine; + int i; + + //PC_PrintDefineHashTable(source->definehash); + //free all the scripts + while(source->scriptstack) + { + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + FreeScript(script); + } //end for + //free all the tokens + while(source->tokens) + { + token = source->tokens; + source->tokens = source->tokens->next; + PC_FreeToken(token); + } //end for +#if DEFINEHASHING + for (i = 0; i < DEFINEHASHSIZE; i++) + { + define = source->definehash[i]; + while(define) + { + nextdefine = define->hashnext; + + if ( !(define->flags & DEFINE_GLOBAL) ) + { + PC_FreeDefine(define); + } + + define = nextdefine; + } //end while + + source->definehash[i] = NULL; + } //end for +#else //DEFINEHASHING + //free all defines + while(source->defines) + { + define = source->defines; + source->defines = source->defines->next; + PC_FreeDefine(define); + } //end for +#endif //DEFINEHASHING + //free all indents + while(source->indentstack) + { + indent = source->indentstack; + source->indentstack = source->indentstack->next; + FreeMemory(indent); + } //end for +#if DEFINEHASHING + // + if (source->definehash) FreeMemory(source->definehash); +#endif //DEFINEHASHING + //free the source itself + FreeMemory(source); +} //end of the function FreeSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ + +#define MAX_SOURCEFILES 64 + +source_t *sourceFiles[MAX_SOURCEFILES]; + +int PC_LoadSourceHandle(const char *filename) +{ + source_t *source; + int i; + + for (i = 1; i < MAX_SOURCEFILES; i++) + { + if (!sourceFiles[i]) + break; + } //end for + if (i >= MAX_SOURCEFILES) + return 0; + PS_SetBaseFolder(""); + source = LoadSourceFile(filename); + if (!source) + return 0; + sourceFiles[i] = source; + return i; +} //end of the function PC_LoadSourceHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_FreeSourceHandle(int handle) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + return qfalse; + if (!sourceFiles[handle]) + return qfalse; + + FreeSource(sourceFiles[handle]); + sourceFiles[handle] = NULL; + return qtrue; +} //end of the function PC_FreeSourceHandle + +int PC_LoadGlobalDefines ( const char* filename ) +{ + int handle; + token_t token; + + handle = PC_LoadSourceHandle ( filename ); + if ( handle < 1 ) + return qfalse; + + addGlobalDefine = qtrue; + + // Read all the token files which will add the defines globally + while ( PC_ReadToken(sourceFiles[handle], &token) ); + + addGlobalDefine = qfalse; + + PC_FreeSourceHandle ( handle ); + + return qtrue; +} + +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadTokenHandle(int handle, pc_token_t *pc_token) +{ + token_t token; + int ret; + + if (handle < 1 || handle >= MAX_SOURCEFILES) + return 0; + if (!sourceFiles[handle]) + return 0; + + ret = PC_ReadToken(sourceFiles[handle], &token); + strcpy(pc_token->string, token.string); + pc_token->type = token.type; + pc_token->subtype = token.subtype; + pc_token->intvalue = token.intvalue; + pc_token->floatvalue = token.floatvalue; + if ((pc_token->type == TT_STRING) && (pc_token->string[0]!='@')) + StripDoubleQuotes(pc_token->string); + + return ret; +} //end of the function PC_ReadTokenHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_SourceFileAndLine(int handle, char *filename, int *line) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + return qfalse; + if (!sourceFiles[handle]) + return qfalse; + + strcpy(filename, sourceFiles[handle]->filename); + if (sourceFiles[handle]->scriptstack) + *line = sourceFiles[handle]->scriptstack->line; + else + *line = 0; + return qtrue; +} //end of the function PC_SourceFileAndLine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetBaseFolder(char *path) +{ + PS_SetBaseFolder(path); +} //end of the function PC_SetBaseFolder +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_CheckOpenSourceHandles(void) +{ + int i; + + for (i = 1; i < MAX_SOURCEFILES; i++) + { + if (sourceFiles[i]) + { +#ifdef BOTLIB + botimport.Print(PRT_ERROR, "file %s still open in precompiler\n", sourceFiles[i]->scriptstack->filename); +#endif //BOTLIB + } //end if + } //end for +} //end of the function PC_CheckOpenSourceHandles + diff --git a/codemp/botlib/l_precomp.h b/codemp/botlib/l_precomp.h new file mode 100644 index 0000000..6781498 --- /dev/null +++ b/codemp/botlib/l_precomp.h @@ -0,0 +1,168 @@ + +/***************************************************************************** + * name: l_precomp.h + * + * desc: pre compiler + * + * $Archive: /source/code/botlib/l_precomp.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifndef MAX_PATH + #define MAX_PATH MAX_QPATH +#endif + +#ifndef PATH_SEPERATORSTR + #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) + #define PATHSEPERATOR_STR "\\" + #else + #define PATHSEPERATOR_STR "/" + #endif +#endif +#ifndef PATH_SEPERATORCHAR + #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) + #define PATHSEPERATOR_CHAR '\\' + #else + #define PATHSEPERATOR_CHAR '/' + #endif +#endif + +#if defined(BSPC) && !defined(QDECL) +#define QDECL +#endif + + +#define DEFINE_FIXED 0x0001 +#define DEFINE_GLOBAL 0x0002 + +#define BUILTIN_LINE 1 +#define BUILTIN_FILE 2 +#define BUILTIN_DATE 3 +#define BUILTIN_TIME 4 +#define BUILTIN_STDC 5 + +#define INDENT_IF 0x0001 +#define INDENT_ELSE 0x0002 +#define INDENT_ELIF 0x0004 +#define INDENT_IFDEF 0x0008 +#define INDENT_IFNDEF 0x0010 + +//macro definitions +typedef struct define_s +{ + char *name; //define name + int flags; //define flags + int builtin; // > 0 if builtin define + int numparms; //number of define parameters + token_t *parms; //define parameters + token_t *tokens; //macro tokens (possibly containing parm tokens) + struct define_s *next; //next defined macro in a list + struct define_s *hashnext; //next define in the hash chain + struct define_s *globalnext; //used to link up the globald defines +} define_t; + +//indents +//used for conditional compilation directives: +//#if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s +{ + int type; //indent type + int skip; //true if skipping current indent + script_t *script; //script the indent was in + struct indent_s *next; //next indent on the indent stack +} indent_t; + +//source file +typedef struct source_s +{ + char filename[1024]; //file name of the script + char includepath[1024]; //path to include files + punctuation_t *punctuations; //punctuations to use + script_t *scriptstack; //stack with scripts of the source + token_t *tokens; //tokens to read first + define_t *defines; //list with macro definitions + define_t **definehash; //hash chain with defines + indent_t *indentstack; //stack with indents + int skip; // > 0 if skipping conditional code + token_t token; //last read token +} source_t; + + +//read a token from the source +int PC_ReadToken(source_t *source, token_t *token); +//expect a certain token +int PC_ExpectTokenString(source_t *source, char *string); +//expect a certain token type +int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token); +//expect a token +int PC_ExpectAnyToken(source_t *source, token_t *token); +//returns true when the token is available +int PC_CheckTokenString(source_t *source, char *string); +//returns true an reads the token when a token with the given type is available +int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token); +//skip tokens until the given token string is read +int PC_SkipUntilString(source_t *source, char *string); +//unread the last token read from the script +void PC_UnreadLastToken(source_t *source); +//unread the given token +void PC_UnreadToken(source_t *source, token_t *token); +//read a token only if on the same line, lines are concatenated with a slash +int PC_ReadLine(source_t *source, token_t *token); +//returns true if there was a white space in front of the token +int PC_WhiteSpaceBeforeToken(token_t *token); +//add a define to the source +int PC_AddDefine(source_t *source, char *string); +//add a globals define that will be added to all opened sources +int PC_AddGlobalDefine(char *string); +//remove the given global define +int PC_RemoveGlobalDefine(char *name); +//remove all globals defines +void PC_RemoveAllGlobalDefines(void); +//add builtin defines +void PC_AddBuiltinDefines(source_t *source); +//set the source include path +void PC_SetIncludePath(source_t *source, char *path); +//set the punction set +void PC_SetPunctuations(source_t *source, punctuation_t *p); +//set the base folder to load files from +void PC_SetBaseFolder(char *path); +//load a source file +source_t *LoadSourceFile(const char *filename); +//load a source from memory +source_t *LoadSourceMemory(char *ptr, int length, char *name); +//free the given source +void FreeSource(source_t *source); +//print a source error +void QDECL SourceError(source_t *source, char *str, ...); +//print a source warning +void QDECL SourceWarning(source_t *source, char *str, ...); + +#ifdef BSPC +// some of BSPC source does include game/q_shared.h and some does not +// we define pc_token_s pc_token_t if needed (yes, it's ugly) +#ifndef __Q_SHARED_H +#define MAX_TOKENLENGTH 1024 +typedef struct pc_token_s +{ + int type; + int subtype; + int intvalue; + float floatvalue; + char string[MAX_TOKENLENGTH]; +} pc_token_t; +#endif //!_Q_SHARED_H +#endif //BSPC + +// +int PC_LoadSourceHandle(const char *filename); +int PC_FreeSourceHandle(int handle); +int PC_ReadTokenHandle(int handle, pc_token_t *pc_token); +int PC_SourceFileAndLine(int handle, char *filename, int *line); +void PC_CheckOpenSourceHandles(void); +int PC_LoadGlobalDefines ( const char* filename ); +void PC_RemoveAllGlobalDefines ( void ); + diff --git a/codemp/botlib/l_script.cpp b/codemp/botlib/l_script.cpp new file mode 100644 index 0000000..9b2d1f6 --- /dev/null +++ b/codemp/botlib/l_script.cpp @@ -0,0 +1,1418 @@ + +/***************************************************************************** + * name: l_script.c + * + * desc: lexicographical parser + * + * $Archive: /MissionPack/code/botlib/l_script.c $ + * $Author: Ttimo $ + * $Revision: 9 $ + * $Modtime: 4/13/01 4:45p $ + * $Date: 4/13/01 4:45p $ + * + *****************************************************************************/ + +//#define SCREWUP +//#define BOTLIB +//#define MEQCC +//#define BSPC + +#ifdef SCREWUP +#include +#include +#include +#include +#include +#include "l_memory.h" +#include "l_script.h" + +typedef enum {qfalse, qtrue} qboolean; + +#endif //SCREWUP + +#ifdef BOTLIB +//include files for usage in the bot library +#include "../game/q_shared.h" +#include "../game/botlib.h" +#include "be_interface.h" +#include "l_script.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_libvar.h" +#endif //BOTLIB + +#ifdef MEQCC +//include files for usage in MrElusive's QuakeC Compiler +#include "qcc.h" +#include "l_script.h" +#include "l_memory.h" +#include "l_log.h" + +#define qtrue true +#define qfalse false +#endif //MEQCC + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" + +#define qtrue true +#define qfalse false +#endif //BSPC + + +#define PUNCTABLE + +//longer punctuations first +punctuation_t default_punctuations[] = +{ + //binary operators + {">>=",P_RSHIFT_ASSIGN, NULL}, + {"<<=",P_LSHIFT_ASSIGN, NULL}, + // + {"...",P_PARMS, NULL}, + //define merge operator + {"##",P_PRECOMPMERGE, NULL}, + //logic operators + {"&&",P_LOGIC_AND, NULL}, + {"||",P_LOGIC_OR, NULL}, + {">=",P_LOGIC_GEQ, NULL}, + {"<=",P_LOGIC_LEQ, NULL}, + {"==",P_LOGIC_EQ, NULL}, + {"!=",P_LOGIC_UNEQ, NULL}, + //arithmatic operators + {"*=",P_MUL_ASSIGN, NULL}, + {"/=",P_DIV_ASSIGN, NULL}, + {"%=",P_MOD_ASSIGN, NULL}, + {"+=",P_ADD_ASSIGN, NULL}, + {"-=",P_SUB_ASSIGN, NULL}, + {"++",P_INC, NULL}, + {"--",P_DEC, NULL}, + //binary operators + {"&=",P_BIN_AND_ASSIGN, NULL}, + {"|=",P_BIN_OR_ASSIGN, NULL}, + {"^=",P_BIN_XOR_ASSIGN, NULL}, + {">>",P_RSHIFT, NULL}, + {"<<",P_LSHIFT, NULL}, + //reference operators + {"->",P_POINTERREF, NULL}, + //C++ + {"::",P_CPP1, NULL}, + {".*",P_CPP2, NULL}, + //arithmatic operators + {"*",P_MUL, NULL}, + {"/",P_DIV, NULL}, + {"%",P_MOD, NULL}, + {"+",P_ADD, NULL}, + {"-",P_SUB, NULL}, + {"=",P_ASSIGN, NULL}, + //binary operators + {"&",P_BIN_AND, NULL}, + {"|",P_BIN_OR, NULL}, + {"^",P_BIN_XOR, NULL}, + {"~",P_BIN_NOT, NULL}, + //logic operators + {"!",P_LOGIC_NOT, NULL}, + {">",P_LOGIC_GREATER, NULL}, + {"<",P_LOGIC_LESS, NULL}, + //reference operator + {".",P_REF, NULL}, + //seperators + {",",P_COMMA, NULL}, + {";",P_SEMICOLON, NULL}, + //label indication + {":",P_COLON, NULL}, + //if statement + {"?",P_QUESTIONMARK, NULL}, + //embracements + {"(",P_PARENTHESESOPEN, NULL}, + {")",P_PARENTHESESCLOSE, NULL}, + {"{",P_BRACEOPEN, NULL}, + {"}",P_BRACECLOSE, NULL}, + {"[",P_SQBRACKETOPEN, NULL}, + {"]",P_SQBRACKETCLOSE, NULL}, + // + {"\\",P_BACKSLASH, NULL}, + //precompiler operator + {"#",P_PRECOMP, NULL}, +#ifdef DOLLAR + {"$",P_DOLLAR, NULL}, +#endif //DOLLAR + // StringEd key + {"@",P_ATSIGN, NULL}, + {NULL, 0} +}; + +#ifdef BSPC +char basefolder[MAX_PATH]; +#else +char basefolder[MAX_QPATH]; +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PS_CreatePunctuationTable(script_t *script, punctuation_t *punctuations) +{ + int i; + punctuation_t *p, *lastp, *newp; + + //get memory for the table + if (!script->punctuationtable) script->punctuationtable = (punctuation_t **) + GetMemory(256 * sizeof(punctuation_t *)); + Com_Memset(script->punctuationtable, 0, 256 * sizeof(punctuation_t *)); + //add the punctuations in the list to the punctuation table + for (i = 0; punctuations[i].p; i++) + { + newp = &punctuations[i]; + lastp = NULL; + //sort the punctuations in this table entry on length (longer punctuations first) + for (p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next) + { + if (strlen(p->p) < strlen(newp->p)) + { + newp->next = p; + if (lastp) lastp->next = newp; + else script->punctuationtable[(unsigned int) newp->p[0]] = newp; + break; + } //end if + lastp = p; + } //end for + if (!p) + { + newp->next = NULL; + if (lastp) lastp->next = newp; + else script->punctuationtable[(unsigned int) newp->p[0]] = newp; + } //end if + } //end for +} //end of the function PS_CreatePunctuationTable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *PunctuationFromNum(script_t *script, int num) +{ + int i; + + for (i = 0; script->punctuations[i].p; i++) + { + if (script->punctuations[i].n == num) return script->punctuations[i].p; + } //end for + return "unkown punctuation"; +} //end of the function PunctuationFromNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL ScriptError(script_t *script, char *str, ...) +{ + char text[1024]; + va_list ap; + + if (script->flags & SCFL_NOERRORS) return; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("error: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("error: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BSPC +} //end of the function ScriptError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL ScriptWarning(script_t *script, char *str, ...) +{ + char text[1024]; + va_list ap; + + if (script->flags & SCFL_NOWARNINGS) return; + + va_start(ap, str); + vsprintf(text, str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("warning: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("warning: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BSPC +} //end of the function ScriptWarning +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SetScriptPunctuations(script_t *script, punctuation_t *p) +{ +#ifdef PUNCTABLE + if (p) PS_CreatePunctuationTable(script, p); + else PS_CreatePunctuationTable(script, default_punctuations); +#endif //PUNCTABLE + if (p) script->punctuations = p; + else script->punctuations = default_punctuations; +} //end of the function SetScriptPunctuations +//============================================================================ +// Reads spaces, tabs, C-like comments etc. +// When a newline character is found the scripts line counter is increased. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadWhiteSpace(script_t *script) +{ + while(1) + { + //skip white space + while(*script->script_p <= ' ') + { + if (!*script->script_p) return 0; + if (*script->script_p == '\n') script->line++; + script->script_p++; + } //end while + //skip comments + if (*script->script_p == '/') + { + //comments // + if (*(script->script_p+1) == '/') + { + script->script_p++; + do + { + script->script_p++; + if (!*script->script_p) return 0; + } //end do + while(*script->script_p != '\n'); + script->line++; + script->script_p++; + if (!*script->script_p) return 0; + continue; + } //end if + //comments /* */ + else if (*(script->script_p+1) == '*') + { + script->script_p++; + do + { + script->script_p++; + if (!*script->script_p) return 0; + if (*script->script_p == '\n') script->line++; + } //end do + while(!(*script->script_p == '*' && *(script->script_p+1) == '/')); + script->script_p++; + if (!*script->script_p) return 0; + script->script_p++; + if (!*script->script_p) return 0; + continue; + } //end if + } //end if + break; + } //end while + return 1; +} //end of the function PS_ReadWhiteSpace +//============================================================================ +// Reads an escape character. +// +// Parameter: script : script to read from +// ch : place to store the read escape character +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadEscapeCharacter(script_t *script, char *ch) +{ + int c, val, i; + + //step over the leading '\\' + script->script_p++; + //determine the escape character + switch(*script->script_p) + { + case '\\': c = '\\'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\'': c = '\''; break; + case '\"': c = '\"'; break; + case '\?': c = '\?'; break; + case 'x': + { + script->script_p++; + for (i = 0, val = 0; ; i++, script->script_p++) + { + c = *script->script_p; + if (c >= '0' && c <= '9') c = c - '0'; + else if (c >= 'A' && c <= 'Z') c = c - 'A' + 10; + else if (c >= 'a' && c <= 'z') c = c - 'a' + 10; + else break; + val = (val << 4) + c; + } //end for + script->script_p--; + if (val > 0xFF) + { + ScriptWarning(script, "too large value in escape character"); + val = 0xFF; + } //end if + c = val; + break; + } //end case + default: //NOTE: decimal ASCII code, NOT octal + { + if (*script->script_p < '0' || *script->script_p > '9') ScriptError(script, "unknown escape char"); + for (i = 0, val = 0; ; i++, script->script_p++) + { + c = *script->script_p; + if (c >= '0' && c <= '9') c = c - '0'; + else break; + val = val * 10 + c; + } //end for + script->script_p--; + if (val > 0xFF) + { + ScriptWarning(script, "too large value in escape character"); + val = 0xFF; + } //end if + c = val; + break; + } //end default + } //end switch + //step over the escape character or the last digit of the number + script->script_p++; + //store the escape character + *ch = c; + //succesfully read escape character + return 1; +} //end of the function PS_ReadEscapeCharacter +//============================================================================ +// Reads C-like string. Escape characters are interpretted. +// Quotes are included with the string. +// Reads two strings with a white space between them as one string. +// +// Parameter: script : script to read from +// token : buffer to store the string +// Returns: qtrue when a string was read succesfully +// Changes Globals: - +//============================================================================ +int PS_ReadString(script_t *script, token_t *token, int quote) +{ + int len, tmpline; + char *tmpscript_p; + + if (quote == '\"') token->type = TT_STRING; + else token->type = TT_LITERAL; + + len = 0; + //leading quote + token->string[len++] = *script->script_p++; + // + while(1) + { + //minus 2 because trailing double quote and zero have to be appended + if (len >= MAX_TOKEN - 2) + { + ScriptError(script, "string longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + //if there is an escape character and + //if escape characters inside a string are allowed + if (*script->script_p == '\\' && !(script->flags & SCFL_NOSTRINGESCAPECHARS)) + { + if (!PS_ReadEscapeCharacter(script, &token->string[len])) + { + token->string[len] = 0; + return 0; + } //end if + len++; + } //end if + //if a trailing quote + else if (*script->script_p == quote) + { + //step over the double quote + script->script_p++; + //if white spaces in a string are not allowed + if (script->flags & SCFL_NOSTRINGWHITESPACES) break; + // + tmpscript_p = script->script_p; + tmpline = script->line; + //read unusefull stuff between possible two following strings + if (!PS_ReadWhiteSpace(script)) + { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } //end if + //if there's no leading double qoute + if (*script->script_p != quote) + { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } //end if + //step over the new leading double quote + script->script_p++; + } //end if + else + { + if (*script->script_p == '\0') + { + token->string[len] = 0; + ScriptError(script, "missing trailing quote"); + return 0; + } //end if + if (*script->script_p == '\n') + { + token->string[len] = 0; + ScriptError(script, "newline inside string %s", token->string); + return 0; + } //end if + token->string[len++] = *script->script_p++; + } //end else + } //end while + //trailing quote + token->string[len++] = quote; + //end string with a zero + token->string[len] = '\0'; + //the sub type is the length of the string + token->subtype = len; + return 1; +} //end of the function PS_ReadString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadName(script_t *script, token_t *token) +{ + int len = 0; + char c; + + token->type = TT_NAME; + do + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN) + { + ScriptError(script, "name longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + c = *script->script_p; + } while ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '_'); + token->string[len] = '\0'; + //the sub type is the length of the name + token->subtype = len; + return 1; +} //end of the function PS_ReadName +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void NumberValue(char *string, int subtype, unsigned long int *intvalue, + long double *floatvalue) +{ + unsigned long int dotfound = 0; + + *intvalue = 0; + *floatvalue = 0; + //floating point number + if (subtype & TT_FLOAT) + { + while(*string) + { + if (*string == '.') + { + if (dotfound) return; + dotfound = 10; + string++; + } //end if + if (dotfound) + { + *floatvalue = *floatvalue + (long double) (*string - '0') / + (long double) dotfound; + dotfound *= 10; + } //end if + else + { + *floatvalue = *floatvalue * 10.0 + (long double) (*string - '0'); + } //end else + string++; + } //end while + *intvalue = (unsigned long) *floatvalue; + } //end if + else if (subtype & TT_DECIMAL) + { + while(*string) *intvalue = *intvalue * 10 + (*string++ - '0'); + *floatvalue = *intvalue; + } //end else if + else if (subtype & TT_HEX) + { + //step over the leading 0x or 0X + string += 2; + while(*string) + { + *intvalue <<= 4; + if (*string >= 'a' && *string <= 'f') *intvalue += *string - 'a' + 10; + else if (*string >= 'A' && *string <= 'F') *intvalue += *string - 'A' + 10; + else *intvalue += *string - '0'; + string++; + } //end while + *floatvalue = *intvalue; + } //end else if + else if (subtype & TT_OCTAL) + { + //step over the first zero + string += 1; + while(*string) *intvalue = (*intvalue << 3) + (*string++ - '0'); + *floatvalue = *intvalue; + } //end else if + else if (subtype & TT_BINARY) + { + //step over the leading 0b or 0B + string += 2; + while(*string) *intvalue = (*intvalue << 1) + (*string++ - '0'); + *floatvalue = *intvalue; + } //end else if +} //end of the function NumberValue +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadNumber(script_t *script, token_t *token) +{ + int len = 0, i; + int octal, dot; + char c; +// unsigned long int intvalue = 0; +// long double floatvalue = 0; + + token->type = TT_NUMBER; + //check for a hexadecimal number + if (*script->script_p == '0' && + (*(script->script_p + 1) == 'x' || + *(script->script_p + 1) == 'X')) + { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //hexadecimal + while((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'A')) + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN) + { + ScriptError(script, "hexadecimal number longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + c = *script->script_p; + } //end while + token->subtype |= TT_HEX; + } //end if +#ifdef BINARYNUMBERS + //check for a binary number + else if (*script->script_p == '0' && + (*(script->script_p + 1) == 'b' || + *(script->script_p + 1) == 'B')) + { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //binary + while(c == '0' || c == '1') + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN) + { + ScriptError(script, "binary number longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + c = *script->script_p; + } //end while + token->subtype |= TT_BINARY; + } //end if +#endif //BINARYNUMBERS + else //decimal or octal integer or floating point number + { + octal = qfalse; + dot = qfalse; + if (*script->script_p == '0') octal = qtrue; + while(1) + { + c = *script->script_p; + if (c == '.') dot = qtrue; + else if (c == '8' || c == '9') octal = qfalse; + else if (c < '0' || c > '9') break; + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN - 1) + { + ScriptError(script, "number longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + } //end while + if (octal) token->subtype |= TT_OCTAL; + else token->subtype |= TT_DECIMAL; + if (dot) token->subtype |= TT_FLOAT; + } //end else + for (i = 0; i < 2; i++) + { + c = *script->script_p; + //check for a LONG number + if ( (c == 'l' || c == 'L') // bk001204 - brackets + && !(token->subtype & TT_LONG)) + { + script->script_p++; + token->subtype |= TT_LONG; + } //end if + //check for an UNSIGNED number + else if ( (c == 'u' || c == 'U') // bk001204 - brackets + && !(token->subtype & (TT_UNSIGNED | TT_FLOAT))) + { + script->script_p++; + token->subtype |= TT_UNSIGNED; + } //end if + } //end for + token->string[len] = '\0'; +#ifdef NUMBERVALUE + NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue); +#endif //NUMBERVALUE + if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER; + return 1; +} //end of the function PS_ReadNumber +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadLiteral(script_t *script, token_t *token) +{ + token->type = TT_LITERAL; + //first quote + token->string[0] = *script->script_p++; + //check for end of file + if (!*script->script_p) + { + ScriptError(script, "end of file before trailing \'"); + return 0; + } //end if + //if it is an escape character + if (*script->script_p == '\\') + { + if (!PS_ReadEscapeCharacter(script, &token->string[1])) return 0; + } //end if + else + { + token->string[1] = *script->script_p++; + } //end else + //check for trailing quote + if (*script->script_p != '\'') + { + ScriptWarning(script, "too many characters in literal, ignored"); + while(*script->script_p && + *script->script_p != '\'' && + *script->script_p != '\n') + { + script->script_p++; + } //end while + if (*script->script_p == '\'') script->script_p++; + } //end if + //store the trailing quote + token->string[2] = *script->script_p++; + //store trailing zero to end the string + token->string[3] = '\0'; + //the sub type is the integer literal value + token->subtype = token->string[1]; + // + return 1; +} //end of the function PS_ReadLiteral +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadPunctuation(script_t *script, token_t *token) +{ + int len; + char *p; + punctuation_t *punc; + +#ifdef PUNCTABLE + for (punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next) + { +#else + int i; + + for (i = 0; script->punctuations[i].p; i++) + { + punc = &script->punctuations[i]; +#endif //PUNCTABLE + p = punc->p; + len = strlen(p); + //if the script contains at least as much characters as the punctuation + if (script->script_p + len <= script->end_p) + { + //if the script contains the punctuation + if (!strncmp(script->script_p, p, len)) + { + strncpy(token->string, p, MAX_TOKEN); + script->script_p += len; + token->type = TT_PUNCTUATION; + //sub type is the number of the punctuation + token->subtype = punc->n; + return 1; + } //end if + } //end if + } //end for + return 0; +} //end of the function PS_ReadPunctuation +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadPrimitive(script_t *script, token_t *token) +{ + int len; + + len = 0; + while(*script->script_p > ' ' && *script->script_p != ';') + { + if (len >= MAX_TOKEN) + { + ScriptError(script, "primitive token longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + token->string[len++] = *script->script_p++; + } //end while + token->string[len] = 0; + //copy the token into the script structure + Com_Memcpy(&script->token, token, sizeof(token_t)); + //primitive reading successfull + return 1; +} //end of the function PS_ReadPrimitive +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadToken(script_t *script, token_t *token) +{ + //if there is a token available (from UnreadToken) + if (script->tokenavailable) + { + script->tokenavailable = 0; + Com_Memcpy(token, &script->token, sizeof(token_t)); + return 1; + } //end if + //save script pointer + script->lastscript_p = script->script_p; + //save line counter + script->lastline = script->line; + //clear the token stuff + Com_Memset(token, 0, sizeof(token_t)); + //start of the white space + script->whitespace_p = script->script_p; + token->whitespace_p = script->script_p; + //read unusefull stuff + if (!PS_ReadWhiteSpace(script)) return 0; + //end of the white space + script->endwhitespace_p = script->script_p; + token->endwhitespace_p = script->script_p; + //line the token is on + token->line = script->line; + //number of lines crossed before token + token->linescrossed = script->line - script->lastline; + //if there is a leading double quote + if (*script->script_p == '\"') + { + if (!PS_ReadString(script, token, '\"')) return 0; + } //end if + //if an literal + else if (*script->script_p == '\'') + { + //if (!PS_ReadLiteral(script, token)) return 0; + if (!PS_ReadString(script, token, '\'')) return 0; + } //end if + //if there is a number + else if ((*script->script_p >= '0' && *script->script_p <= '9') || + (*script->script_p == '.' && + (*(script->script_p + 1) >= '0' && *(script->script_p + 1) <= '9'))) + { + if (!PS_ReadNumber(script, token)) return 0; + } //end if + //if this is a primitive script + else if (script->flags & SCFL_PRIMITIVE) + { + return PS_ReadPrimitive(script, token); + } //end else if + //if there is a name + else if ((*script->script_p >= 'a' && *script->script_p <= 'z') || + (*script->script_p >= 'A' && *script->script_p <= 'Z') || + *script->script_p == '_' || *script->script_p == '@') + { + if (!PS_ReadName(script, token)) return 0; + } //end if + //check for punctuations + else if (!PS_ReadPunctuation(script, token)) + { + ScriptError(script, "can't read token"); + return 0; + } //end if + //copy the token into the script structure + Com_Memcpy(&script->token, token, sizeof(token_t)); + //succesfully read a token + return 1; +} //end of the function PS_ReadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectTokenString(script_t *script, char *string) +{ + token_t token; + + if (!PS_ReadToken(script, &token)) + { + ScriptError(script, "couldn't find expected %s", string); + return 0; + } //end if + + if (strcmp(token.string, string)) + { + ScriptError(script, "expected %s, found %s", string, token.string); + return 0; + } //end if + return 1; +} //end of the function PS_ExpectToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token) +{ + char str[MAX_TOKEN]; + + if (!PS_ReadToken(script, token)) + { + ScriptError(script, "couldn't read expected token"); + return 0; + } //end if + + if (token->type != type) + { + if (type == TT_STRING) strcpy(str, "string"); + if (type == TT_LITERAL) strcpy(str, "literal"); + if (type == TT_NUMBER) strcpy(str, "number"); + if (type == TT_NAME) strcpy(str, "name"); + if (type == TT_PUNCTUATION) strcpy(str, "punctuation"); + ScriptError(script, "expected a %s, found %s", str, token->string); + return 0; + } //end if + if (token->type == TT_NUMBER) + { + if ((token->subtype & subtype) != subtype) + { + if (subtype & TT_DECIMAL) strcpy(str, "decimal"); + if (subtype & TT_HEX) strcpy(str, "hex"); + if (subtype & TT_OCTAL) strcpy(str, "octal"); + if (subtype & TT_BINARY) strcpy(str, "binary"); + if (subtype & TT_LONG) strcat(str, " long"); + if (subtype & TT_UNSIGNED) strcat(str, " unsigned"); + if (subtype & TT_FLOAT) strcat(str, " float"); + if (subtype & TT_INTEGER) strcat(str, " integer"); + ScriptError(script, "expected %s, found %s", str, token->string); + return 0; + } //end if + } //end if + else if (token->type == TT_PUNCTUATION) + { + if (subtype < 0) + { + ScriptError(script, "BUG: wrong punctuation subtype"); + return 0; + } //end if + if (token->subtype != subtype) + { + ScriptError(script, "expected %s, found %s", + script->punctuations[subtype], token->string); + return 0; + } //end if + } //end else if + return 1; +} //end of the function PS_ExpectTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectAnyToken(script_t *script, token_t *token) +{ + if (!PS_ReadToken(script, token)) + { + ScriptError(script, "couldn't read expected token"); + return 0; + } //end if + else + { + return 1; + } //end else +} //end of the function PS_ExpectAnyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_CheckTokenString(script_t *script, char *string) +{ + token_t tok; + + if (!PS_ReadToken(script, &tok)) return 0; + //if the token is available + if (!strcmp(tok.string, string)) return 1; + //token not available + script->script_p = script->lastscript_p; + return 0; +} //end of the function PS_CheckTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token) +{ + token_t tok; + + if (!PS_ReadToken(script, &tok)) return 0; + //if the type matches + if (tok.type == type && + (tok.subtype & subtype) == subtype) + { + Com_Memcpy(token, &tok, sizeof(token_t)); + return 1; + } //end if + //token is not available + script->script_p = script->lastscript_p; + return 0; +} //end of the function PS_CheckTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_SkipUntilString(script_t *script, char *string) +{ + token_t token; + + while(PS_ReadToken(script, &token)) + { + if (!strcmp(token.string, string)) return 1; + } //end while + return 0; +} //end of the function PS_SkipUntilString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_UnreadLastToken(script_t *script) +{ + script->tokenavailable = 1; +} //end of the function UnreadLastToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_UnreadToken(script_t *script, token_t *token) +{ + Com_Memcpy(&script->token, token, sizeof(token_t)); + script->tokenavailable = 1; +} //end of the function UnreadToken +//============================================================================ +// returns the next character of the read white space, returns NULL if none +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +char PS_NextWhiteSpaceChar(script_t *script) +{ + if (script->whitespace_p != script->endwhitespace_p) + { + return *script->whitespace_p++; + } //end if + else + { + return 0; + } //end else +} //end of the function PS_NextWhiteSpaceChar +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void StripDoubleQuotes(char *string) +{ + if (*string == '\"') + { + strcpy(string, string+1); + } //end if + if (string[strlen(string)-1] == '\"') + { + string[strlen(string)-1] = '\0'; + } //end if +} //end of the function StripDoubleQuotes +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void StripSingleQuotes(char *string) +{ + if (*string == '\'') + { + strcpy(string, string+1); + } //end if + if (string[strlen(string)-1] == '\'') + { + string[strlen(string)-1] = '\0'; + } //end if +} //end of the function StripSingleQuotes +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +long double ReadSignedFloat(script_t *script) +{ + token_t token; + long double sign = 1; + + PS_ExpectAnyToken(script, &token); + if (!strcmp(token.string, "-")) + { + sign = -1; + PS_ExpectTokenType(script, TT_NUMBER, 0, &token); + } //end if + else if (token.type != TT_NUMBER) + { + ScriptError(script, "expected float value, found %s\n", token.string); + } //end else if + return sign * token.floatvalue; +} //end of the function ReadSignedFloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +signed long int ReadSignedInt(script_t *script) +{ + token_t token; + signed long int sign = 1; + + PS_ExpectAnyToken(script, &token); + if (!strcmp(token.string, "-")) + { + sign = -1; + PS_ExpectTokenType(script, TT_NUMBER, TT_INTEGER, &token); + } //end if + else if (token.type != TT_NUMBER || token.subtype == TT_FLOAT) + { + ScriptError(script, "expected integer value, found %s\n", token.string); + } //end else if + return sign * token.intvalue; +} //end of the function ReadSignedInt +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void SetScriptFlags(script_t *script, int flags) +{ + script->flags = flags; +} //end of the function SetScriptFlags +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int GetScriptFlags(script_t *script) +{ + return script->flags; +} //end of the function GetScriptFlags +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void ResetScript(script_t *script) +{ + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //begin of white space + script->whitespace_p = NULL; + //end of white space + script->endwhitespace_p = NULL; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + //clear the saved token + Com_Memset(&script->token, 0, sizeof(token_t)); +} //end of the function ResetScript +//============================================================================ +// returns true if at the end of the script +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int EndOfScript(script_t *script) +{ + return script->script_p >= script->end_p; +} //end of the function EndOfScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int NumLinesCrossed(script_t *script) +{ + return script->line - script->lastline; +} //end of the function NumLinesCrossed +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int ScriptSkipTo(script_t *script, char *value) +{ + int len; + char firstchar; + + firstchar = *value; + len = strlen(value); + do + { + if (!PS_ReadWhiteSpace(script)) return 0; + if (*script->script_p == firstchar) + { + if (!strncmp(script->script_p, value, len)) + { + return 1; + } //end if + } //end if + script->script_p++; + } while(1); +} //end of the function ScriptSkipTo +#ifndef BOTLIB +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int FileLength(FILE *fp) +{ + int pos; + int end; + + pos = ftell(fp); + fseek(fp, 0, SEEK_END); + end = ftell(fp); + fseek(fp, pos, SEEK_SET); + + return end; +} //end of the function FileLength +#endif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +script_t *LoadScriptFile(const char *filename) +{ +#ifdef BOTLIB + fileHandle_t fp; + char pathname[MAX_QPATH]; +#else + FILE *fp; +#endif + int length; + void *buffer; + script_t *script; + +#ifdef BOTLIB + if (strlen(basefolder)) + Com_sprintf(pathname, sizeof(pathname), "%s/%s", basefolder, filename); + else + Com_sprintf(pathname, sizeof(pathname), "%s", filename); + length = botimport.FS_FOpenFile( pathname, &fp, FS_READ ); + if (!fp) return NULL; +#else + fp = fopen(filename, "rb"); + if (!fp) return NULL; + + length = FileLength(fp); +#endif + + buffer = GetClearedMemory(sizeof(script_t) + length + 1); + script = (script_t *) buffer; + Com_Memset(script, 0, sizeof(script_t)); + strcpy(script->filename, filename); + script->buffer = (char *) buffer + sizeof(script_t); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + SetScriptPunctuations(script, NULL); + // +#ifdef BOTLIB + botimport.FS_Read(script->buffer, length, fp); + botimport.FS_FCloseFile(fp); +#else + if (fread(script->buffer, length, 1, fp) != 1) + { + FreeMemory(buffer); + script = NULL; + } //end if + fclose(fp); +#endif + // + script->length = COM_Compress(script->buffer); + + return script; +} //end of the function LoadScriptFile +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +script_t *LoadScriptMemory(char *ptr, int length, char *name) +{ + void *buffer; + script_t *script; + + buffer = GetClearedMemory(sizeof(script_t) + length + 1); + script = (script_t *) buffer; + Com_Memset(script, 0, sizeof(script_t)); + strcpy(script->filename, name); + script->buffer = (char *) buffer + sizeof(script_t); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + SetScriptPunctuations(script, NULL); + // + Com_Memcpy(script->buffer, ptr, length); + // + return script; +} //end of the function LoadScriptMemory +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void FreeScript(script_t *script) +{ +#ifdef PUNCTABLE + if (script->punctuationtable) FreeMemory(script->punctuationtable); +#endif //PUNCTABLE + FreeMemory(script); +} //end of the function FreeScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_SetBaseFolder(char *path) +{ +#ifdef BSPC + sprintf(basefolder, path); +#else + Com_sprintf(basefolder, sizeof(basefolder), path); +#endif +} //end of the function PS_SetBaseFolder diff --git a/codemp/botlib/l_script.h b/codemp/botlib/l_script.h new file mode 100644 index 0000000..5366a3c --- /dev/null +++ b/codemp/botlib/l_script.h @@ -0,0 +1,232 @@ + +/***************************************************************************** + * name: l_script.h + * + * desc: lexicographical parser + * + * $Archive: /source/code/botlib/l_script.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +//undef if binary numbers of the form 0b... or 0B... are not allowed +#define BINARYNUMBERS +//undef if not using the token.intvalue and token.floatvalue +#define NUMBERVALUE +//use dollar sign also as punctuation +#define DOLLAR + +//maximum token length +#define MAX_TOKEN 1024 + +#if defined(BSPC) && !defined(QDECL) +#define QDECL +#endif + + +//script flags +#define SCFL_NOERRORS 0x0001 +#define SCFL_NOWARNINGS 0x0002 +#define SCFL_NOSTRINGWHITESPACES 0x0004 +#define SCFL_NOSTRINGESCAPECHARS 0x0008 +#define SCFL_PRIMITIVE 0x0010 +#define SCFL_NOBINARYNUMBERS 0x0020 +#define SCFL_NONUMBERVALUES 0x0040 + +//token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation + +//string sub type +//--------------- +// the length of the string +//literal sub type +//---------------- +// the ASCII code of the literal +//number sub type +//--------------- +#define TT_DECIMAL 0x0008 // decimal number +#define TT_HEX 0x0100 // hexadecimal number +#define TT_OCTAL 0x0200 // octal number +#ifdef BINARYNUMBERS +#define TT_BINARY 0x0400 // binary number +#endif //BINARYNUMBERS +#define TT_FLOAT 0x0800 // floating point number +#define TT_INTEGER 0x1000 // integer number +#define TT_LONG 0x2000 // long number +#define TT_UNSIGNED 0x4000 // unsigned number +//punctuation sub type +//-------------------- +#define P_RSHIFT_ASSIGN 1 +#define P_LSHIFT_ASSIGN 2 +#define P_PARMS 3 +#define P_PRECOMPMERGE 4 + +#define P_LOGIC_AND 5 +#define P_LOGIC_OR 6 +#define P_LOGIC_GEQ 7 +#define P_LOGIC_LEQ 8 +#define P_LOGIC_EQ 9 +#define P_LOGIC_UNEQ 10 + +#define P_MUL_ASSIGN 11 +#define P_DIV_ASSIGN 12 +#define P_MOD_ASSIGN 13 +#define P_ADD_ASSIGN 14 +#define P_SUB_ASSIGN 15 +#define P_INC 16 +#define P_DEC 17 + +#define P_BIN_AND_ASSIGN 18 +#define P_BIN_OR_ASSIGN 19 +#define P_BIN_XOR_ASSIGN 20 +#define P_RSHIFT 21 +#define P_LSHIFT 22 + +#define P_POINTERREF 23 +#define P_CPP1 24 +#define P_CPP2 25 +#define P_MUL 26 +#define P_DIV 27 +#define P_MOD 28 +#define P_ADD 29 +#define P_SUB 30 +#define P_ASSIGN 31 + +#define P_BIN_AND 32 +#define P_BIN_OR 33 +#define P_BIN_XOR 34 +#define P_BIN_NOT 35 + +#define P_LOGIC_NOT 36 +#define P_LOGIC_GREATER 37 +#define P_LOGIC_LESS 38 + +#define P_REF 39 +#define P_COMMA 40 +#define P_SEMICOLON 41 +#define P_COLON 42 +#define P_QUESTIONMARK 43 + +#define P_PARENTHESESOPEN 44 +#define P_PARENTHESESCLOSE 45 +#define P_BRACEOPEN 46 +#define P_BRACECLOSE 47 +#define P_SQBRACKETOPEN 48 +#define P_SQBRACKETCLOSE 49 +#define P_BACKSLASH 50 + +#define P_PRECOMP 51 +#define P_DOLLAR 52 +#define P_ATSIGN 53 +//name sub type +//------------- +// the length of the name + +//punctuation +typedef struct punctuation_s +{ + char *p; //punctuation character(s) + int n; //punctuation indication + struct punctuation_s *next; //next punctuation +} punctuation_t; + +//token +typedef struct token_s +{ + char string[MAX_TOKEN]; //available token + int type; //last read token type + int subtype; //last read token sub type +#ifdef NUMBERVALUE + unsigned long int intvalue; //integer value + long double floatvalue; //floating point value +#endif //NUMBERVALUE + char *whitespace_p; //start of white space before token + char *endwhitespace_p; //start of white space before token + int line; //line the token was on + int linescrossed; //lines crossed in white space + struct token_s *next; //next token in chain +} token_t; + +//script file +typedef struct script_s +{ + char filename[1024]; //file name of the script + char *buffer; //buffer containing the script + char *script_p; //current pointer in the script + char *end_p; //pointer to the end of the script + char *lastscript_p; //script pointer before reading token + char *whitespace_p; //begin of the white space + char *endwhitespace_p; //end of the white space + int length; //length of the script in bytes + int line; //current line in script + int lastline; //line before reading token + int tokenavailable; //set by UnreadLastToken + int flags; //several script flags + punctuation_t *punctuations; //the punctuations used in the script + punctuation_t **punctuationtable; + token_t token; //available token + struct script_s *next; //next script in a chain +} script_t; + +//read a token from the script +int PS_ReadToken(script_t *script, token_t *token); +//expect a certain token +int PS_ExpectTokenString(script_t *script, char *string); +//expect a certain token type +int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token); +//expect a token +int PS_ExpectAnyToken(script_t *script, token_t *token); +//returns true when the token is available +int PS_CheckTokenString(script_t *script, char *string); +//returns true an reads the token when a token with the given type is available +int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token); +//skip tokens until the given token string is read +int PS_SkipUntilString(script_t *script, char *string); +//unread the last token read from the script +void PS_UnreadLastToken(script_t *script); +//unread the given token +void PS_UnreadToken(script_t *script, token_t *token); +//returns the next character of the read white space, returns NULL if none +char PS_NextWhiteSpaceChar(script_t *script); +//remove any leading and trailing double quotes from the token +void StripDoubleQuotes(char *string); +//remove any leading and trailing single quotes from the token +void StripSingleQuotes(char *string); +//read a possible signed integer +signed long int ReadSignedInt(script_t *script); +//read a possible signed floating point number +long double ReadSignedFloat(script_t *script); +//set an array with punctuations, NULL restores default C/C++ set +void SetScriptPunctuations(script_t *script, punctuation_t *p); +//set script flags +void SetScriptFlags(script_t *script, int flags); +//get script flags +int GetScriptFlags(script_t *script); +//reset a script +void ResetScript(script_t *script); +//returns true if at the end of the script +int EndOfScript(script_t *script); +//returns a pointer to the punctuation with the given number +char *PunctuationFromNum(script_t *script, int num); +//load a script from the given file at the given offset with the given length +script_t *LoadScriptFile(const char *filename); +//load a script from the given memory with the given length +script_t *LoadScriptMemory(char *ptr, int length, char *name); +//free a script +void FreeScript(script_t *script); +//set the base folder to load files from +void PS_SetBaseFolder(char *path); +//print a script error with filename and line number +void QDECL ScriptError(script_t *script, char *str, ...); +//print a script warning with filename and line number +void QDECL ScriptWarning(script_t *script, char *str, ...); + + + diff --git a/codemp/botlib/l_struct.cpp b/codemp/botlib/l_struct.cpp new file mode 100644 index 0000000..cc32d65 --- /dev/null +++ b/codemp/botlib/l_struct.cpp @@ -0,0 +1,445 @@ + +/***************************************************************************** + * name: l_struct.c + * + * desc: structure reading / writing + * + * $Archive: /MissionPack/CODE/botlib/l_struct.c $ + * $Author: Raduffy $ + * $Revision: 1 $ + * $Modtime: 12/20/99 8:43p $ + * $Date: 3/08/00 11:28a $ + * + *****************************************************************************/ + +#ifdef BOTLIB +#include "../game/q_shared.h" +#include "../game/botlib.h" //for the include of be_interface.h +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "be_interface.h" +#endif //BOTLIB + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" +#include "l_precomp.h" +#include "l_struct.h" + +#define qtrue true +#define qfalse false +#endif //BSPC + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +fielddef_t *FindField(fielddef_t *defs, char *name) +{ + int i; + + for (i = 0; defs[i].name; i++) + { + if (!strcmp(defs[i].name, name)) return &defs[i]; + } //end for + return NULL; +} //end of the function FindField +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ReadNumber(source_t *source, fielddef_t *fd, void *p) +{ + token_t token; + int negative = qfalse; + long int intval, intmin = 0, intmax = 0; + double floatval; + + if (!PC_ExpectAnyToken(source, &token)) return (qboolean)0; + + //check for minus sign + if (token.type == TT_PUNCTUATION) + { + if (fd->type & FT_UNSIGNED) + { + SourceError(source, "expected unsigned value, found %s", token.string); + return (qboolean)0; + } //end if + //if not a minus sign + if (strcmp(token.string, "-")) + { + SourceError(source, "unexpected punctuation %s", token.string); + return (qboolean)0; + } //end if + negative = qtrue; + //read the number + if (!PC_ExpectAnyToken(source, &token)) return (qboolean)0; + } //end if + //check if it is a number + if (token.type != TT_NUMBER) + { + SourceError(source, "expected number, found %s", token.string); + return (qboolean)0; + } //end if + //check for a float value + if (token.subtype & TT_FLOAT) + { + if ((fd->type & FT_TYPE) != FT_FLOAT) + { + SourceError(source, "unexpected float"); + return (qboolean)0; + } //end if + floatval = token.floatvalue; + if (negative) floatval = -floatval; + if (fd->type & FT_BOUNDED) + { + if (floatval < fd->floatmin || floatval > fd->floatmax) + { + SourceError(source, "float out of range [%f, %f]", fd->floatmin, fd->floatmax); + return (qboolean)0; + } //end if + } //end if + *(float *) p = (float) floatval; + return (qboolean)1; + } //end if + // + intval = token.intvalue; + if (negative) intval = -intval; + //check bounds + if ((fd->type & FT_TYPE) == FT_CHAR) + { + if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 255;} + else {intmin = -128; intmax = 127;} + } //end if + if ((fd->type & FT_TYPE) == FT_INT) + { + if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 65535;} + else {intmin = -32768; intmax = 32767;} + } //end else if + if ((fd->type & FT_TYPE) == FT_CHAR || (fd->type & FT_TYPE) == FT_INT) + { + if (fd->type & FT_BOUNDED) + { + intmin = Maximum(intmin, fd->floatmin); + intmax = Minimum(intmax, fd->floatmax); + } //end if + if (intval < intmin || intval > intmax) + { + SourceError(source, "value %d out of range [%d, %d]", intval, intmin, intmax); + return (qboolean)0; + } //end if + } //end if + else if ((fd->type & FT_TYPE) == FT_FLOAT) + { + if (fd->type & FT_BOUNDED) + { + if (intval < fd->floatmin || intval > fd->floatmax) + { + SourceError(source, "value %d out of range [%f, %f]", intval, fd->floatmin, fd->floatmax); + return (qboolean)0; + } //end if + } //end if + } //end else if + //store the value + if ((fd->type & FT_TYPE) == FT_CHAR) + { + if (fd->type & FT_UNSIGNED) *(unsigned char *) p = (unsigned char) intval; + else *(char *) p = (char) intval; + } //end if + else if ((fd->type & FT_TYPE) == FT_INT) + { + if (fd->type & FT_UNSIGNED) *(unsigned int *) p = (unsigned int) intval; + else *(int *) p = (int) intval; + } //end else + else if ((fd->type & FT_TYPE) == FT_FLOAT) + { + *(float *) p = (float) intval; + } //end else + return (qboolean)1; +} //end of the function ReadNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ReadChar(source_t *source, fielddef_t *fd, void *p) +{ + token_t token; + + if (!PC_ExpectAnyToken(source, &token)) return (qboolean)0; + + //take literals into account + if (token.type == TT_LITERAL) + { + StripSingleQuotes(token.string); + *(char *) p = token.string[0]; + } //end if + else + { + PC_UnreadLastToken(source); + if (!ReadNumber(source, fd, p)) return (qboolean)0; + } //end if + return (qboolean)1; +} //end of the function ReadChar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadString(source_t *source, fielddef_t *fd, void *p) +{ + token_t token; + + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) return 0; + //remove the double quotes + StripDoubleQuotes(token.string); + //copy the string + strncpy((char *) p, token.string, MAX_STRINGFIELD); + //make sure the string is closed with a zero + ((char *)p)[MAX_STRINGFIELD-1] = '\0'; + // + return 1; +} //end of the function ReadString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadStructure(source_t *source, structdef_t *def, char *structure) +{ + token_t token; + fielddef_t *fd; + void *p; + int num; + + if (!PC_ExpectTokenString(source, "{")) return 0; + while(1) + { + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + //if end of structure + if (!strcmp(token.string, "}")) break; + //find the field with the name + fd = FindField(def->fields, token.string); + if (!fd) + { + SourceError(source, "unknown structure field %s", token.string); + return qfalse; + } //end if + if (fd->type & FT_ARRAY) + { + num = fd->maxarray; + if (!PC_ExpectTokenString(source, "{")) return qfalse; + } //end if + else + { + num = 1; + } //end else + p = (void *)(structure + fd->offset); + while (num-- > 0) + { + if (fd->type & FT_ARRAY) + { + if (PC_CheckTokenString(source, "}")) break; + } //end if + switch(fd->type & FT_TYPE) + { + case FT_CHAR: + { + if (!ReadChar(source, fd, p)) return qfalse; + p = (char *) p + sizeof(char); + break; + } //end case + case FT_INT: + { + if (!ReadNumber(source, fd, p)) return qfalse; + p = (char *) p + sizeof(int); + break; + } //end case + case FT_FLOAT: + { + if (!ReadNumber(source, fd, p)) return qfalse; + p = (char *) p + sizeof(float); + break; + } //end case + case FT_STRING: + { + if (!ReadString(source, fd, p)) return qfalse; + p = (char *) p + MAX_STRINGFIELD; + break; + } //end case + case FT_STRUCT: + { + if (!fd->substruct) + { + SourceError(source, "BUG: no sub structure defined"); + return qfalse; + } //end if + ReadStructure(source, fd->substruct, (char *) p); + p = (char *) p + fd->substruct->size; + break; + } //end case + } //end switch + if (fd->type & FT_ARRAY) + { + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + if (!strcmp(token.string, "}")) break; + if (strcmp(token.string, ",")) + { + SourceError(source, "expected a comma, found %s", token.string); + return qfalse; + } //end if + } //end if + } //end while + } //end while + return qtrue; +} //end of the function ReadStructure +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteIndent(FILE *fp, int indent) +{ + while(indent-- > 0) + { + if (fprintf(fp, "\t") < 0) return qfalse; + } //end while + return qtrue; +} //end of the function WriteIndent +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteFloat(FILE *fp, float value) +{ + char buf[128]; + int l; + + sprintf(buf, "%f", value); + l = strlen(buf); + //strip any trailing zeros + while(l-- > 1) + { + if (buf[l] != '0' && buf[l] != '.') break; + if (buf[l] == '.') + { + buf[l] = 0; + break; + } //end if + buf[l] = 0; + } //end while + //write the float to file + if (fprintf(fp, "%s", buf) < 0) return 0; + return 1; +} //end of the function WriteFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteStructWithIndent(FILE *fp, structdef_t *def, char *structure, int indent) +{ + int i, num; + void *p; + fielddef_t *fd; + + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "{\r\n") < 0) return qfalse; + + indent++; + for (i = 0; def->fields[i].name; i++) + { + fd = &def->fields[i]; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "%s\t", fd->name) < 0) return qfalse; + p = (void *)(structure + fd->offset); + if (fd->type & FT_ARRAY) + { + num = fd->maxarray; + if (fprintf(fp, "{") < 0) return qfalse; + } //end if + else + { + num = 1; + } //end else + while(num-- > 0) + { + switch(fd->type & FT_TYPE) + { + case FT_CHAR: + { + if (fprintf(fp, "%d", *(char *) p) < 0) return qfalse; + p = (char *) p + sizeof(char); + break; + } //end case + case FT_INT: + { + if (fprintf(fp, "%d", *(int *) p) < 0) return qfalse; + p = (char *) p + sizeof(int); + break; + } //end case + case FT_FLOAT: + { + if (!WriteFloat(fp, *(float *)p)) return qfalse; + p = (char *) p + sizeof(float); + break; + } //end case + case FT_STRING: + { + if (fprintf(fp, "\"%s\"", (char *) p) < 0) return qfalse; + p = (char *) p + MAX_STRINGFIELD; + break; + } //end case + case FT_STRUCT: + { + if (!WriteStructWithIndent(fp, fd->substruct, structure, indent)) return qfalse; + p = (char *) p + fd->substruct->size; + break; + } //end case + } //end switch + if (fd->type & FT_ARRAY) + { + if (num > 0) + { + if (fprintf(fp, ",") < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, "}") < 0) return qfalse; + } //end else + } //end if + } //end while + if (fprintf(fp, "\r\n") < 0) return qfalse; + } //end for + indent--; + + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "}\r\n") < 0) return qfalse; + return qtrue; +} //end of the function WriteStructWithIndent +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteStructure(FILE *fp, structdef_t *def, char *structure) +{ + return WriteStructWithIndent(fp, def, structure, 0); +} //end of the function WriteStructure + diff --git a/codemp/botlib/l_struct.h b/codemp/botlib/l_struct.h new file mode 100644 index 0000000..e5c6968 --- /dev/null +++ b/codemp/botlib/l_struct.h @@ -0,0 +1,58 @@ + +/***************************************************************************** + * name: l_struct.h + * + * desc: structure reading/writing + * + * $Archive: /source/code/botlib/l_struct.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + + +#define MAX_STRINGFIELD 80 +//field types +#define FT_CHAR 1 // char +#define FT_INT 2 // int +#define FT_FLOAT 3 // float +#define FT_STRING 4 // char [MAX_STRINGFIELD] +#define FT_STRUCT 6 // struct (sub structure) +//type only mask +#define FT_TYPE 0x00FF // only type, clear subtype +//sub types +#define FT_ARRAY 0x0100 // array of type +#define FT_BOUNDED 0x0200 // bounded value +#define FT_UNSIGNED 0x0400 + +//structure field definition +typedef struct fielddef_s +{ + char *name; //name of the field + int offset; //offset in the structure + int type; //type of the field + //type specific fields + int maxarray; //maximum array size + float floatmin, floatmax; //float min and max + struct structdef_s *substruct; //sub structure +} fielddef_t; + +//structure definition +typedef struct structdef_s +{ + int size; + fielddef_t *fields; +} structdef_t; + +//read a structure from a script +int ReadStructure(source_t *source, structdef_t *def, char *structure); +//write a structure to a file +int WriteStructure(FILE *fp, structdef_t *def, char *structure); +//writes indents +int WriteIndent(FILE *fp, int indent); +//writes a float without traling zeros +int WriteFloat(FILE *fp, float value); + + diff --git a/codemp/botlib/l_utils.h b/codemp/botlib/l_utils.h new file mode 100644 index 0000000..83d1d81 --- /dev/null +++ b/codemp/botlib/l_utils.h @@ -0,0 +1,18 @@ + +/***************************************************************************** + * name: l_util.h + * + * desc: utils + * + * $Archive: /source/code/botlib/l_util.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#define Vector2Angles(v,a) vectoangles(v,a) +#define MAX_PATH MAX_QPATH +#define Maximum(x,y) (x > y ? x : y) +#define Minimum(x,y) (x < y ? x : y) diff --git a/codemp/buildvms.bat b/codemp/buildvms.bat new file mode 100644 index 0000000..64ea829 --- /dev/null +++ b/codemp/buildvms.bat @@ -0,0 +1,8 @@ +set include= +cd game +call game +cd ..\cgame +call cgame +cd ..\ui +call ui +cd .. diff --git a/codemp/cgame/JK2_cgame.def b/codemp/cgame/JK2_cgame.def new file mode 100644 index 0000000..2ee748e --- /dev/null +++ b/codemp/cgame/JK2_cgame.def @@ -0,0 +1,3 @@ +EXPORTS + vmMain + dllEntry diff --git a/codemp/cgame/JK2_cgame.dsp b/codemp/cgame/JK2_cgame.dsp new file mode 100644 index 0000000..386dd8b --- /dev/null +++ b/codemp/cgame/JK2_cgame.dsp @@ -0,0 +1,412 @@ +# Microsoft Developer Studio Project File - Name="JK2cgame" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=JK2cgame - Win32 Release JK2 +!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 "JK2_cgame.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 "JK2_cgame.mak" CFG="JK2cgame - Win32 Release JK2" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "JK2cgame - Win32 Release JK2" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "JK2cgame - Win32 Debug JK2" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "JK2cgame - Win32 Final JK2" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/jedi/codemp/cgame", ICAAAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "JK2cgame - Win32 Release JK2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "JK2cgame___Win32_Release_TA" +# PROP BASE Intermediate_Dir "JK2cgame___Win32_Release_TA" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Release" +# PROP Intermediate_Dir "../Release/JK2cgame" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G6 /W4 /GX /O2 /D "WIN32" /D "NDebug" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /G6 /W4 /GX /Zi /O2 /I ".." /I "./../game" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "MISSIONPACK" /D "_JK2" /YX /FD /c +# SUBTRACT CPP /Fr +# ADD BASE MTL /nologo /D "NDebug" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDebug" /mktyplib203 /o "NUL" /win32 +# 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 /nologo /base:"0x30000000" /subsystem:windows /dll /map /machine:I386 /def:".\JK2_cgame.def" /out:"../Release/cgamex86.dll" +# SUBTRACT BASE LINK32 /debug +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /debug /machine:I386 /def:".\JK2_cgame.def" /out:"../Release/cgamex86.dll" + +!ELSEIF "$(CFG)" == "JK2cgame - Win32 Debug JK2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "JK2cgame___Win32_Debug_TA" +# PROP BASE Intermediate_Dir "JK2cgame___Win32_Debug_TA" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "../Debug" +# PROP Intermediate_Dir "../Debug/JK2cgame" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G5 /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_Debug" /D "_WINDOWS" /FR /YX /FD /c +# ADD CPP /nologo /G6 /MTd /W3 /Gm /GX /ZI /Od /I ".." /I "./../game" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "MISSIONPACK" /D "_JK2" /D "JK2AWARDS" /FR /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_Debug" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_Debug" /mktyplib203 /o "NUL" /win32 +# 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 /nologo /base:"0x30000000" /subsystem:windows /dll /map /debug /machine:I386 /out:"..\Debug/cgamex86.dll" +# SUBTRACT BASE LINK32 /profile /nodefaultlib +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /debug /machine:I386 /def:".\JK2_cgame.def" /out:"..\Debug\cgamex86.dll" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "JK2cgame - Win32 Final JK2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "../Final" +# PROP BASE Intermediate_Dir "../Final/JK2cgame" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Final" +# PROP Intermediate_Dir "../Final/JK2cgame" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G6 /W4 /GX /Zi /O2 /I ".." /I "../../jk2/game" /D "NDebug" /D "WIN32" /D "_WINDOWS" /D "MISSIONPACK" /D "_JK2" /YX /FD /c +# SUBTRACT BASE CPP /Fr +# ADD CPP /nologo /G6 /W4 /GX /O2 /I ".." /I "./../game" /D "NDEBUG" /D "_WINDOWS" /D "MISSIONPACK" /D "WIN32" /D "_JK2" /D "FINAL_BUILD" /YX /FD /c +# ADD BASE MTL /nologo /D "NDebug" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDebug" /mktyplib203 /o "NUL" /win32 +# 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 /nologo /base:"0x30000000" /subsystem:windows /dll /map /machine:I386 /def:".\JK2_cgame.def" /out:"../Final/cgamex86.dll" +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /machine:I386 /def:".\JK2_cgame.def" /out:"../Final/cgamex86.dll" + +!ENDIF + +# Begin Target + +# Name "JK2cgame - Win32 Release JK2" +# Name "JK2cgame - Win32 Debug JK2" +# Name "JK2cgame - Win32 Final JK2" +# Begin Group "Source Files" + +# PROP Default_Filter "c" +# Begin Source File + +SOURCE=..\game\bg_lib.c +# PROP BASE Exclude_From_Build 1 +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=..\game\bg_misc.c +# End Source File +# Begin Source File + +SOURCE=..\game\bg_panimate.c +# End Source File +# Begin Source File + +SOURCE=..\game\bg_pmove.c +# End Source File +# Begin Source File + +SOURCE=..\game\bg_saber.c +# End Source File +# Begin Source File + +SOURCE=..\game\bg_slidemove.c +# End Source File +# Begin Source File + +SOURCE=..\game\bg_weapons.c +# End Source File +# Begin Source File + +SOURCE=.\cg_consolecmds.c +# End Source File +# Begin Source File + +SOURCE=.\cg_draw.c +# End Source File +# Begin Source File + +SOURCE=.\cg_drawtools.c +# End Source File +# Begin Source File + +SOURCE=.\cg_effects.c +# End Source File +# Begin Source File + +SOURCE=.\cg_ents.c +# End Source File +# Begin Source File + +SOURCE=.\cg_event.c +# End Source File +# Begin Source File + +SOURCE=.\cg_info.c +# End Source File +# Begin Source File + +SOURCE=.\cg_light.c +# End Source File +# Begin Source File + +SOURCE=.\cg_localents.c +# End Source File +# Begin Source File + +SOURCE=.\cg_main.c +# End Source File +# Begin Source File + +SOURCE=.\cg_marks.c +# End Source File +# Begin Source File + +SOURCE=.\cg_newDraw.c +# End Source File +# Begin Source File + +SOURCE=.\cg_players.c +# End Source File +# Begin Source File + +SOURCE=.\cg_playerstate.c +# End Source File +# Begin Source File + +SOURCE=.\cg_predict.c +# End Source File +# Begin Source File + +SOURCE=.\cg_saga.c +# End Source File +# Begin Source File + +SOURCE=.\cg_scoreboard.c +# End Source File +# Begin Source File + +SOURCE=.\cg_servercmds.c +# End Source File +# Begin Source File + +SOURCE=.\cg_snapshot.c +# End Source File +# Begin Source File + +SOURCE=.\cg_strap.c +# End Source File +# Begin Source File + +SOURCE=.\cg_syscalls.c +# End Source File +# Begin Source File + +SOURCE=.\cg_turret.c +# End Source File +# Begin Source File + +SOURCE=.\cg_view.c +# End Source File +# Begin Source File + +SOURCE=.\cg_weaponinit.c +# End Source File +# Begin Source File + +SOURCE=.\cg_weapons.c +# End Source File +# Begin Source File + +SOURCE=.\fx_blaster.c +# End Source File +# Begin Source File + +SOURCE=.\fx_bowcaster.c +# End Source File +# Begin Source File + +SOURCE=.\fx_bryarpistol.c +# End Source File +# Begin Source File + +SOURCE=.\fx_demp2.c +# End Source File +# Begin Source File + +SOURCE=.\fx_disruptor.c +# End Source File +# Begin Source File + +SOURCE=.\fx_flechette.c +# End Source File +# Begin Source File + +SOURCE=.\fx_force.c +# End Source File +# Begin Source File + +SOURCE=.\fx_heavyrepeater.c +# End Source File +# Begin Source File + +SOURCE=.\fx_rocketlauncher.c +# End Source File +# Begin Source File + +SOURCE=..\game\q_math.c +# End Source File +# Begin Source File + +SOURCE=..\game\q_shared.c +# End Source File +# Begin Source File + +SOURCE=..\ui\ui_shared.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=..\game\anims.h +# End Source File +# Begin Source File + +SOURCE=.\animtable.h +# End Source File +# Begin Source File + +SOURCE=..\game\bg_local.h +# End Source File +# Begin Source File + +SOURCE=..\game\bg_public.h +# End Source File +# Begin Source File + +SOURCE=..\game\bg_saga.h +# End Source File +# Begin Source File + +SOURCE=..\game\bg_strap.h +# End Source File +# Begin Source File + +SOURCE=..\game\bg_weapons.h +# End Source File +# Begin Source File + +SOURCE=.\cg_lights.h +# End Source File +# Begin Source File + +SOURCE=.\cg_local.h +# End Source File +# Begin Source File + +SOURCE=.\cg_public.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\disablewarnings.h +# End Source File +# Begin Source File + +SOURCE=.\fx_local.h +# End Source File +# Begin Source File + +SOURCE=..\ghoul2\G2.h +# End Source File +# Begin Source File + +SOURCE=.\JK2_cgame.def +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=..\ui\keycodes.h +# End Source File +# Begin Source File + +SOURCE=..\..\ui\menudef.h +# End Source File +# Begin Source File + +SOURCE=..\game\q_shared.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\qfiles.h +# End Source File +# Begin Source File + +SOURCE=..\game\surfaceflags.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\tags.h +# End Source File +# Begin Source File + +SOURCE=.\tr_types.h +# End Source File +# Begin Source File + +SOURCE=..\ui\ui_shared.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\cgame.bat +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=.\cgame.q3asm +# PROP Exclude_From_Build 1 +# End Source File +# End Target +# End Project diff --git a/codemp/cgame/JK2_cgame.vcproj b/codemp/cgame/JK2_cgame.vcproj new file mode 100644 index 0000000..8325d70 --- /dev/null +++ b/codemp/cgame/JK2_cgame.vcproj @@ -0,0 +1,563 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/cgame/animtable.h b/codemp/cgame/animtable.h new file mode 100644 index 0000000..4772bf2 --- /dev/null +++ b/codemp/cgame/animtable.h @@ -0,0 +1,1792 @@ +// special file included only by cg_players.cpp & ui_players.cpp +// +// moved it from the original header file for PCH reasons... +// + +#if defined(_XBOX) && !defined(_JK2EXE) && !defined(_UI) // Linker only wants one copy +extern stringID_table_t animTable[MAX_ANIMATIONS+1]; +#else +stringID_table_t animTable [MAX_ANIMATIONS+1] = +{ + //================================================= + //HEAD ANIMS + //================================================= + //# #sep Head-only anims + ENUM2STRING(FACE_TALK0), //# silent + ENUM2STRING(FACE_TALK1), //# quiet + ENUM2STRING(FACE_TALK2), //# semi-quiet + ENUM2STRING(FACE_TALK3), //# semi-loud + ENUM2STRING(FACE_TALK4), //# loud + ENUM2STRING(FACE_ALERT), //# + ENUM2STRING(FACE_SMILE), //# + ENUM2STRING(FACE_FROWN), //# + ENUM2STRING(FACE_DEAD), //# + + //================================================= + //ANIMS IN WHICH UPPER AND LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep ENUM2STRING(BOTH_ DEATHS + ENUM2STRING(BOTH_DEATH1), //# First Death anim + ENUM2STRING(BOTH_DEATH2), //# Second Death anim + ENUM2STRING(BOTH_DEATH3), //# Third Death anim + ENUM2STRING(BOTH_DEATH4), //# Fourth Death anim + ENUM2STRING(BOTH_DEATH5), //# Fifth Death anim + ENUM2STRING(BOTH_DEATH6), //# Sixth Death anim + ENUM2STRING(BOTH_DEATH7), //# Seventh Death anim + ENUM2STRING(BOTH_DEATH8), //# + ENUM2STRING(BOTH_DEATH9), //# + ENUM2STRING(BOTH_DEATH10), //# + ENUM2STRING(BOTH_DEATH11), //# + ENUM2STRING(BOTH_DEATH12), //# + ENUM2STRING(BOTH_DEATH13), //# + ENUM2STRING(BOTH_DEATH14), //# + ENUM2STRING(BOTH_DEATH15), //# + ENUM2STRING(BOTH_DEATH16), //# + ENUM2STRING(BOTH_DEATH17), //# + ENUM2STRING(BOTH_DEATH18), //# + ENUM2STRING(BOTH_DEATH19), //# + ENUM2STRING(BOTH_DEATH20), //# + ENUM2STRING(BOTH_DEATH21), //# + ENUM2STRING(BOTH_DEATH22), //# + ENUM2STRING(BOTH_DEATH23), //# + ENUM2STRING(BOTH_DEATH24), //# + ENUM2STRING(BOTH_DEATH25), //# + + ENUM2STRING(BOTH_DEATHFORWARD1), //# First Death in which they get thrown forward + ENUM2STRING(BOTH_DEATHFORWARD2), //# Second Death in which they get thrown forward + ENUM2STRING(BOTH_DEATHFORWARD3), //# Tavion's falling in cin# 23 + ENUM2STRING(BOTH_DEATHBACKWARD1), //# First Death in which they get thrown backward + ENUM2STRING(BOTH_DEATHBACKWARD2), //# Second Death in which they get thrown backward + + ENUM2STRING(BOTH_DEATH1IDLE), //# Idle while close to death + ENUM2STRING(BOTH_LYINGDEATH1), //# Death to play when killed lying down + ENUM2STRING(BOTH_STUMBLEDEATH1), //# Stumble forward and fall face first death + ENUM2STRING(BOTH_FALLDEATH1), //# Fall forward off a high cliff and splat death - start + ENUM2STRING(BOTH_FALLDEATH1INAIR), //# Fall forward off a high cliff and splat death - loop + ENUM2STRING(BOTH_FALLDEATH1LAND), //# Fall forward off a high cliff and splat death - hit bottom + ENUM2STRING(BOTH_DEATH_ROLL), //# Death anim from a roll + ENUM2STRING(BOTH_DEATH_FLIP), //# Death anim from a flip + ENUM2STRING(BOTH_DEATH_SPIN_90_R), //# Death anim when facing 90 degrees right + ENUM2STRING(BOTH_DEATH_SPIN_90_L), //# Death anim when facing 90 degrees left + ENUM2STRING(BOTH_DEATH_SPIN_180), //# Death anim when facing backwards + ENUM2STRING(BOTH_DEATH_LYING_UP), //# Death anim when lying on back + ENUM2STRING(BOTH_DEATH_LYING_DN), //# Death anim when lying on front + ENUM2STRING(BOTH_DEATH_FALLING_DN), //# Death anim when falling on face + ENUM2STRING(BOTH_DEATH_FALLING_UP), //# Death anim when falling on back + ENUM2STRING(BOTH_DEATH_CROUCHED), //# Death anim when crouched + //# #sep ENUM2STRING(BOTH_ DEAD POSES # Should be last frame of corresponding previous anims + ENUM2STRING(BOTH_DEAD1), //# First Death finished pose + ENUM2STRING(BOTH_DEAD2), //# Second Death finished pose + ENUM2STRING(BOTH_DEAD3), //# Third Death finished pose + ENUM2STRING(BOTH_DEAD4), //# Fourth Death finished pose + ENUM2STRING(BOTH_DEAD5), //# Fifth Death finished pose + ENUM2STRING(BOTH_DEAD6), //# Sixth Death finished pose + ENUM2STRING(BOTH_DEAD7), //# Seventh Death finished pose + ENUM2STRING(BOTH_DEAD8), //# + ENUM2STRING(BOTH_DEAD9), //# + ENUM2STRING(BOTH_DEAD10), //# + ENUM2STRING(BOTH_DEAD11), //# + ENUM2STRING(BOTH_DEAD12), //# + ENUM2STRING(BOTH_DEAD13), //# + ENUM2STRING(BOTH_DEAD14), //# + ENUM2STRING(BOTH_DEAD15), //# + ENUM2STRING(BOTH_DEAD16), //# + ENUM2STRING(BOTH_DEAD17), //# + ENUM2STRING(BOTH_DEAD18), //# + ENUM2STRING(BOTH_DEAD19), //# + ENUM2STRING(BOTH_DEAD20), //# + ENUM2STRING(BOTH_DEAD21), //# + ENUM2STRING(BOTH_DEAD22), //# + ENUM2STRING(BOTH_DEAD23), //# + ENUM2STRING(BOTH_DEAD24), //# + ENUM2STRING(BOTH_DEAD25), //# + ENUM2STRING(BOTH_DEADFORWARD1), //# First thrown forward death finished pose + ENUM2STRING(BOTH_DEADFORWARD2), //# Second thrown forward death finished pose + ENUM2STRING(BOTH_DEADBACKWARD1), //# First thrown backward death finished pose + ENUM2STRING(BOTH_DEADBACKWARD2), //# Second thrown backward death finished pose + ENUM2STRING(BOTH_LYINGDEAD1), //# Killed lying down death finished pose + ENUM2STRING(BOTH_STUMBLEDEAD1), //# Stumble forward death finished pose + ENUM2STRING(BOTH_FALLDEAD1LAND), //# Fall forward and splat death finished pose + //# #sep ENUM2STRING(BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + ENUM2STRING(BOTH_DEADFLOP1), //# React to being shot from First Death finished pose + ENUM2STRING(BOTH_DEADFLOP2), //# React to being shot from Second Death finished pose + ENUM2STRING(BOTH_DISMEMBER_HEAD1), //# + ENUM2STRING(BOTH_DISMEMBER_TORSO1), //# + ENUM2STRING(BOTH_DISMEMBER_LLEG), //# + ENUM2STRING(BOTH_DISMEMBER_RLEG), //# + ENUM2STRING(BOTH_DISMEMBER_RARM), //# + ENUM2STRING(BOTH_DISMEMBER_LARM), //# + //# #sep ENUM2STRING(BOTH_ PAINS + ENUM2STRING(BOTH_PAIN1), //# First take pain anim + ENUM2STRING(BOTH_PAIN2), //# Second take pain anim + ENUM2STRING(BOTH_PAIN3), //# Third take pain anim + ENUM2STRING(BOTH_PAIN4), //# Fourth take pain anim + ENUM2STRING(BOTH_PAIN5), //# Fifth take pain anim - from behind + ENUM2STRING(BOTH_PAIN6), //# Sixth take pain anim - from behind + ENUM2STRING(BOTH_PAIN7), //# Seventh take pain anim - from behind + ENUM2STRING(BOTH_PAIN8), //# Eigth take pain anim - from behind + ENUM2STRING(BOTH_PAIN9), //# + ENUM2STRING(BOTH_PAIN10), //# + ENUM2STRING(BOTH_PAIN11), //# + ENUM2STRING(BOTH_PAIN12), //# + ENUM2STRING(BOTH_PAIN13), //# + ENUM2STRING(BOTH_PAIN14), //# + ENUM2STRING(BOTH_PAIN15), //# + ENUM2STRING(BOTH_PAIN16), //# + ENUM2STRING(BOTH_PAIN17), //# + ENUM2STRING(BOTH_PAIN18), //# + + //# #sep ENUM2STRING(BOTH_ ATTACKS + ENUM2STRING(BOTH_ATTACK1), //# Attack with stun baton + ENUM2STRING(BOTH_ATTACK2), //# Attack with one-handed pistol + ENUM2STRING(BOTH_ATTACK3), //# Attack with blaster rifle + ENUM2STRING(BOTH_ATTACK4), //# Attack with disruptor + ENUM2STRING(BOTH_ATTACK5), //# Another Rancor Attack + ENUM2STRING(BOTH_ATTACK6), //# Yet Another Rancor Attack + ENUM2STRING(BOTH_ATTACK7), //# Yet Another Rancor Attack + ENUM2STRING(BOTH_ATTACK10), //# Attack with thermal det + ENUM2STRING(BOTH_ATTACK11), //# "Attack" with tripmine and detpack + ENUM2STRING(BOTH_MELEE1), //# First melee attack + ENUM2STRING(BOTH_MELEE2), //# Second melee attack + ENUM2STRING(BOTH_THERMAL_READY), //# pull back with thermal + ENUM2STRING(BOTH_THERMAL_THROW), //# throw thermal + //* #sep ENUM2STRING(BOTH_ SABER ANIMS + //Saber attack anims - power level 1 + ENUM2STRING(BOTH_A1_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A1__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A1__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A1_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A1_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A1_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A1_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T1_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T1_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T1_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T1_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T1__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T1__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T1__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T1__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T1_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T1_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T1_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T1_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T1_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T1_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T1_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T1_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T1_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T1_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T1_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T1_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T1__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T1__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T1__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T1_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T1_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T1_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T1_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T1_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T1_TR_BR) + ENUM2STRING(BOTH_T1_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T1_T__BR) + ENUM2STRING(BOTH_T1__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T1_BR__R) + ENUM2STRING(BOTH_T1__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T1_T___R) + ENUM2STRING(BOTH_T1_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T1__R_TR) + ENUM2STRING(BOTH_T1_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T1_T__TR) + ENUM2STRING(BOTH_T1_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T1__R_TL) + ENUM2STRING(BOTH_T1_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T1_TR_TL) + ENUM2STRING(BOTH_T1_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T1_T__TL) + ENUM2STRING(BOTH_T1_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T1__L_TL) + ENUM2STRING(BOTH_T1__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T1_TR__L) + ENUM2STRING(BOTH_T1__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T1_T___L) + ENUM2STRING(BOTH_T1__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T1_BL__L) + ENUM2STRING(BOTH_T1_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T1_T__BL) + ENUM2STRING(BOTH_T1_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T1_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S1_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S1_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S1_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S1_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S1_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S1_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S1_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R1_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R1__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R1__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B1_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B1__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B1_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B1_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B1_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B1__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B1_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D1_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D1__R___), //# Deflection toward R + ENUM2STRING(BOTH_D1_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D1_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D1__L___), //# Deflection toward L + ENUM2STRING(BOTH_D1_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D1_B____), //# Deflection toward B + //Saber attack anims - power level 2 + ENUM2STRING(BOTH_A2_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A2__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A2__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A2_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A2_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A2_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A2_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T2_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T2_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T2_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T2_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T2__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T2__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T2__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T2__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T2_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T2_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T2_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T2_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T2_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T2_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T2_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T2_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T2_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T2_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T2_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T2_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T2__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T2__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T2__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T2_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T2_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T2_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T2_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T2_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T2_TR_BR) + ENUM2STRING(BOTH_T2_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T2_T__BR) + ENUM2STRING(BOTH_T2__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T2_BR__R) + ENUM2STRING(BOTH_T2__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T2_T___R) + ENUM2STRING(BOTH_T2_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T2__R_TR) + ENUM2STRING(BOTH_T2_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T2_T__TR) + ENUM2STRING(BOTH_T2_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T2__R_TL) + ENUM2STRING(BOTH_T2_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T2_TR_TL) + ENUM2STRING(BOTH_T2_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T2_T__TL) + ENUM2STRING(BOTH_T2_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T2__L_TL) + ENUM2STRING(BOTH_T2__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T2_TR__L) + ENUM2STRING(BOTH_T2__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T2_T___L) + ENUM2STRING(BOTH_T2__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T2_BL__L) + ENUM2STRING(BOTH_T2_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T2_T__BL) + ENUM2STRING(BOTH_T2_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T2_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S2_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S2_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S2_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S2_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S2_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S2_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S2_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R2_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R2__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R2__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B2_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B2__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B2_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B2_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B2_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B2__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B2_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D2_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D2__R___), //# Deflection toward R + ENUM2STRING(BOTH_D2_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D2_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D2__L___), //# Deflection toward L + ENUM2STRING(BOTH_D2_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D2_B____), //# Deflection toward B + //Saber attack anims - power level 3 + ENUM2STRING(BOTH_A3_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A3__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A3__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A3_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A3_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A3_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A3_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T3_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T3_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T3_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T3_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T3__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T3__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T3__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T3__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T3_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T3_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T3_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T3_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T3_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T3_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T3_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T3_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T3_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T3_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T3_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T3_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T3__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T3__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T3__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T3_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T3_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T3_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T3_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T3_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T3_TR_BR) + ENUM2STRING(BOTH_T3_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T3_T__BR) + ENUM2STRING(BOTH_T3__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T3_BR__R) + ENUM2STRING(BOTH_T3__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T3_T___R) + ENUM2STRING(BOTH_T3_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T3__R_TR) + ENUM2STRING(BOTH_T3_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T3_T__TR) + ENUM2STRING(BOTH_T3_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T3__R_TL) + ENUM2STRING(BOTH_T3_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T3_TR_TL) + ENUM2STRING(BOTH_T3_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T3_T__TL) + ENUM2STRING(BOTH_T3_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T3__L_TL) + ENUM2STRING(BOTH_T3__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T3_TR__L) + ENUM2STRING(BOTH_T3__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T3_T___L) + ENUM2STRING(BOTH_T3__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T3_BL__L) + ENUM2STRING(BOTH_T3_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T3_T__BL) + ENUM2STRING(BOTH_T3_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T3_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S3_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S3_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S3_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S3_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S3_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S3_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S3_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R3_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R3__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R3__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B3_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B3__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B3_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B3_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B3_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B3__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B3_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D3_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D3__R___), //# Deflection toward R + ENUM2STRING(BOTH_D3_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D3_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D3__L___), //# Deflection toward L + ENUM2STRING(BOTH_D3_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D3_B____), //# Deflection toward B + //Saber attack anims - power level 4 - Desann's + ENUM2STRING(BOTH_A4_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A4__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A4__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A4_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A4_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A4_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A4_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T4_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T4_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T4_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T4_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T4__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T4__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T4__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T4__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T4_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T4_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T4_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T4_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T4_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T4_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T4_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T4_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T4_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T4_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T4_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T4_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T4__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T4__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T4__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T4_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T4_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T4_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T4_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T4_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T4_TR_BR) + ENUM2STRING(BOTH_T4_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T4_T__BR) + ENUM2STRING(BOTH_T4__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T4_BR__R) + ENUM2STRING(BOTH_T4__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T4_T___R) + ENUM2STRING(BOTH_T4_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T4__R_TR) + ENUM2STRING(BOTH_T4_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T4_T__TR) + ENUM2STRING(BOTH_T4_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T4__R_TL) + ENUM2STRING(BOTH_T4_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T4_TR_TL) + ENUM2STRING(BOTH_T4_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T4_T__TL) + ENUM2STRING(BOTH_T4_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T4__L_TL) + ENUM2STRING(BOTH_T4__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T4_TR__L) + ENUM2STRING(BOTH_T4__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T4_T___L) + ENUM2STRING(BOTH_T4__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T4_BL__L) + ENUM2STRING(BOTH_T4_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T4_T__BL) + ENUM2STRING(BOTH_T4_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T4_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S4_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S4_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S4_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S4_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S4_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S4_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S4_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R4_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R4__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R4__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B4_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B4__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B4_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B4_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B4_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B4__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B4_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D4_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D4__R___), //# Deflection toward R + ENUM2STRING(BOTH_D4_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D4_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D4__L___), //# Deflection toward L + ENUM2STRING(BOTH_D4_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D4_B____), //# Deflection toward B + //Saber attack anims - power level 5 - Tavion's + ENUM2STRING(BOTH_A5_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A5__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A5__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A5_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A5_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A5_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A5_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T5_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T5_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T5_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T5_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T5__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T5__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T5__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T5__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T5_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T5_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T5_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T5_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T5_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T5_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T5_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T5_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T5_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T5_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T5_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T5_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T5__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T5__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T5__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T5_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T5_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T5_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T5_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T5_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T5_TR_BR) + ENUM2STRING(BOTH_T5_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T5_T__BR) + ENUM2STRING(BOTH_T5__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T5_BR__R) + ENUM2STRING(BOTH_T5__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T5_T___R) + ENUM2STRING(BOTH_T5_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T5__R_TR) + ENUM2STRING(BOTH_T5_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T5_T__TR) + ENUM2STRING(BOTH_T5_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T5__R_TL) + ENUM2STRING(BOTH_T5_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T5_TR_TL) + ENUM2STRING(BOTH_T5_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T5_T__TL) + ENUM2STRING(BOTH_T5_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T5__L_TL) + ENUM2STRING(BOTH_T5__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T5_TR__L) + ENUM2STRING(BOTH_T5__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T5_T___L) + ENUM2STRING(BOTH_T5__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T5_BL__L) + ENUM2STRING(BOTH_T5_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T5_T__BL) + ENUM2STRING(BOTH_T5_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T5_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S5_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S5_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S5_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S5_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S5_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S5_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S5_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R5_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R5__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R5__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B5_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B5__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B5_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B5_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B5_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B5__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B5_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D5_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D5__R___), //# Deflection toward R + ENUM2STRING(BOTH_D5_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D5_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D5__L___), //# Deflection toward L + ENUM2STRING(BOTH_D5_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D5_B____), //# Deflection toward B + //Saber attack anims - power level 6 + ENUM2STRING(BOTH_A6_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A6__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A6__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A6_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A6_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A6_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A6_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T6_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T6_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T6_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T6_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T6__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T6__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T6__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T6__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T6_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T6_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T6_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T6_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T6_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T6_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T6_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T6_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T6_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T6_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T6_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T6_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T6__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T6__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T6__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T6_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T6_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T6_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T6_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T6_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T6_TR_BR) + ENUM2STRING(BOTH_T6_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T6_T__BR) + ENUM2STRING(BOTH_T6__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T6_BR__R) + ENUM2STRING(BOTH_T6__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T6_T___R) + ENUM2STRING(BOTH_T6_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T6__R_TR) + ENUM2STRING(BOTH_T6_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T6_T__TR) + ENUM2STRING(BOTH_T6_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T6__R_TL) + ENUM2STRING(BOTH_T6_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T6_TR_TL) + ENUM2STRING(BOTH_T6_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T6_T__TL) + ENUM2STRING(BOTH_T6_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T6__L_TL) + ENUM2STRING(BOTH_T6__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T6_TR__L) + ENUM2STRING(BOTH_T6__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T6_T___L) + ENUM2STRING(BOTH_T6__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T6_BL__L) + ENUM2STRING(BOTH_T6_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T6_T__BL) + ENUM2STRING(BOTH_T6_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T6_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S6_S6_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S6_S6__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S6_S6__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S6_S6_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S6_S6_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S6_S6_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S6_S6_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R6_B__S6), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R6__L_S6), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R6__R_S6), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_TL_S6), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_BR_S6), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_BL_S6), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_TR_S6), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B6_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B6__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B6_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B6_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B6_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B6__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B6_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D6_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D6__R___), //# Deflection toward R + ENUM2STRING(BOTH_D6_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D6_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D6__L___), //# Deflection toward L + ENUM2STRING(BOTH_D6_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D6_B____), //# Deflection toward B + //Saber attack anims - power level 7 + ENUM2STRING(BOTH_A7_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A7__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A7__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A7_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A7_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A7_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A7_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T7_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T7_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T7_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T7_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T7__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T7__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T7__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T7__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T7_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T7_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T7_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T7_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T7_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T7_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T7_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T7_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T7_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T7_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T7_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T7_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T7__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T7__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T7__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T7_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T7_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T7_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T7_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T7_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T7_TR_BR) + ENUM2STRING(BOTH_T7_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T7_T__BR) + ENUM2STRING(BOTH_T7__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T7_BR__R) + ENUM2STRING(BOTH_T7__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T7_T___R) + ENUM2STRING(BOTH_T7_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T7__R_TR) + ENUM2STRING(BOTH_T7_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T7_T__TR) + ENUM2STRING(BOTH_T7_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T7__R_TL) + ENUM2STRING(BOTH_T7_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T7_TR_TL) + ENUM2STRING(BOTH_T7_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T7_T__TL) + ENUM2STRING(BOTH_T7_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T7__L_TL) + ENUM2STRING(BOTH_T7__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T7_TR__L) + ENUM2STRING(BOTH_T7__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T7_T___L) + ENUM2STRING(BOTH_T7__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T7_BL__L) + ENUM2STRING(BOTH_T7_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T7_T__BL) + ENUM2STRING(BOTH_T7_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T7_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S7_S7_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S7_S7__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S7_S7__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S7_S7_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S7_S7_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S7_S7_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S7_S7_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R7_B__S7), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R7__L_S7), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R7__R_S7), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_TL_S7), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_BR_S7), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_BL_S7), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_TR_S7), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B7_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B7__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B7_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B7_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B7_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B7__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B7_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D7_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D7__R___), //# Deflection toward R + ENUM2STRING(BOTH_D7_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D7_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D7__L___), //# Deflection toward L + ENUM2STRING(BOTH_D7_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D7_B____), //# Deflection toward B + //Saber parry anims + ENUM2STRING(BOTH_P1_S1_T_), //# Block shot/saber top + ENUM2STRING(BOTH_P1_S1_TR), //# Block shot/saber top right + ENUM2STRING(BOTH_P1_S1_TL), //# Block shot/saber top left + ENUM2STRING(BOTH_P1_S1_BL), //# Block shot/saber bottom left + ENUM2STRING(BOTH_P1_S1_BR), //# Block shot/saber bottom right + //Saber knockaway + ENUM2STRING(BOTH_K1_S1_T_), //# knockaway saber top + ENUM2STRING(BOTH_K1_S1_TR), //# knockaway saber top right + ENUM2STRING(BOTH_K1_S1_TL), //# knockaway saber top left + ENUM2STRING(BOTH_K1_S1_BL), //# knockaway saber bottom left + ENUM2STRING(BOTH_K1_S1_B_), //# knockaway saber bottom + ENUM2STRING(BOTH_K1_S1_BR), //# knockaway saber bottom right + //Saber attack knocked away + ENUM2STRING(BOTH_V1_BR_S1), //# BR attack knocked away + ENUM2STRING(BOTH_V1__R_S1), //# R attack knocked away + ENUM2STRING(BOTH_V1_TR_S1), //# TR attack knocked away + ENUM2STRING(BOTH_V1_T__S1), //# T attack knocked away + ENUM2STRING(BOTH_V1_TL_S1), //# TL attack knocked away + ENUM2STRING(BOTH_V1__L_S1), //# L attack knocked away + ENUM2STRING(BOTH_V1_BL_S1), //# BL attack knocked away + ENUM2STRING(BOTH_V1_B__S1), //# B attack knocked away + //Saber parry broken + ENUM2STRING(BOTH_H1_S1_T_), //# saber knocked down from top parry + ENUM2STRING(BOTH_H1_S1_TR), //# saber knocked down-left from TR parry + ENUM2STRING(BOTH_H1_S1_TL), //# saber knocked down-right from TL parry + ENUM2STRING(BOTH_H1_S1_BL), //# saber knocked up-right from BL parry + ENUM2STRING(BOTH_H1_S1_B_), //# saber knocked up over head from ready? + ENUM2STRING(BOTH_H1_S1_BR), //# saber knocked up-left from BR parry + //Dual Sabers parry anims + ENUM2STRING(BOTH_P6_S6_T_), //# Block shot/saber top + ENUM2STRING(BOTH_P6_S6_TR), //# Block shot/saber top right + ENUM2STRING(BOTH_P6_S6_TL), //# Block shot/saber top left + ENUM2STRING(BOTH_P6_S6_BL), //# Block shot/saber bottom left + ENUM2STRING(BOTH_P6_S6_BR), //# Block shot/saber bottom right + //Dual Sabers knockaway + ENUM2STRING(BOTH_K6_S6_T_), //# knockaway saber top + ENUM2STRING(BOTH_K6_S6_TR), //# knockaway saber top right + ENUM2STRING(BOTH_K6_S6_TL), //# knockaway saber top left + ENUM2STRING(BOTH_K6_S6_BL), //# knockaway saber bottom left + ENUM2STRING(BOTH_K6_S6_B_), //# knockaway saber bottom + ENUM2STRING(BOTH_K6_S6_BR), //# knockaway saber bottom right + //Dual Sabers attack knocked away + ENUM2STRING(BOTH_V6_BR_S6), //# BR attack knocked away + ENUM2STRING(BOTH_V6__R_S6), //# R attack knocked away + ENUM2STRING(BOTH_V6_TR_S6), //# TR attack knocked away + ENUM2STRING(BOTH_V6_T__S6), //# T attack knocked away + ENUM2STRING(BOTH_V6_TL_S6), //# TL attack knocked away + ENUM2STRING(BOTH_V6__L_S6), //# L attack knocked away + ENUM2STRING(BOTH_V6_BL_S6), //# BL attack knocked away + ENUM2STRING(BOTH_V6_B__S6), //# B attack knocked away + //Dual Sabers parry broken + ENUM2STRING(BOTH_H6_S6_T_), //# saber knocked down from top parry + ENUM2STRING(BOTH_H6_S6_TR), //# saber knocked down-left from TR parry + ENUM2STRING(BOTH_H6_S6_TL), //# saber knocked down-right from TL parry + ENUM2STRING(BOTH_H6_S6_BL), //# saber knocked up-right from BL parry + ENUM2STRING(BOTH_H6_S6_B_), //# saber knocked up over head from ready? + ENUM2STRING(BOTH_H6_S6_BR), //# saber knocked up-left from BR parry + //SaberStaff parry anims + ENUM2STRING(BOTH_P7_S7_T_), //# Block shot/saber top + ENUM2STRING(BOTH_P7_S7_TR), //# Block shot/saber top right + ENUM2STRING(BOTH_P7_S7_TL), //# Block shot/saber top left + ENUM2STRING(BOTH_P7_S7_BL), //# Block shot/saber bottom left + ENUM2STRING(BOTH_P7_S7_BR), //# Block shot/saber bottom right + //SaberStaff knockaway + ENUM2STRING(BOTH_K7_S7_T_), //# knockaway saber top + ENUM2STRING(BOTH_K7_S7_TR), //# knockaway saber top right + ENUM2STRING(BOTH_K7_S7_TL), //# knockaway saber top left + ENUM2STRING(BOTH_K7_S7_BL), //# knockaway saber bottom left + ENUM2STRING(BOTH_K7_S7_B_), //# knockaway saber bottom + ENUM2STRING(BOTH_K7_S7_BR), //# knockaway saber bottom right + //SaberStaff attack knocked away + ENUM2STRING(BOTH_V7_BR_S7), //# BR attack knocked away + ENUM2STRING(BOTH_V7__R_S7), //# R attack knocked away + ENUM2STRING(BOTH_V7_TR_S7), //# TR attack knocked away + ENUM2STRING(BOTH_V7_T__S7), //# T attack knocked away + ENUM2STRING(BOTH_V7_TL_S7), //# TL attack knocked away + ENUM2STRING(BOTH_V7__L_S7), //# L attack knocked away + ENUM2STRING(BOTH_V7_BL_S7), //# BL attack knocked away + ENUM2STRING(BOTH_V7_B__S7), //# B attack knocked away + //SaberStaff parry broken + ENUM2STRING(BOTH_H7_S7_T_), //# saber knocked down from top parry + ENUM2STRING(BOTH_H7_S7_TR), //# saber knocked down-left from TR parry + ENUM2STRING(BOTH_H7_S7_TL), //# saber knocked down-right from TL parry + ENUM2STRING(BOTH_H7_S7_BL), //# saber knocked up-right from BL parry + ENUM2STRING(BOTH_H7_S7_B_), //# saber knocked up over head from ready? + ENUM2STRING(BOTH_H7_S7_BR), //# saber knocked up-left from BR parry + //Sabers locked anims + //* #sep BOTH_ SABER LOCKED ANIMS + //BOTH_(DL, S, ST)_(DL, S, ST)_(T, S)_(L, B, SB)_1(_W, _L) +//===Single locks================================================================== +//SINGLE vs. DUAL + //side locks - I'm using a single and they're using dual + ENUM2STRING(BOTH_LK_S_DL_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_DL_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_DL_S_L_1), //lock if I'm using single vs. a dual + ENUM2STRING(BOTH_LK_S_DL_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_DL_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_S_DL_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_DL_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_DL_T_L_1), //lock if I'm using single vs. a dual + ENUM2STRING(BOTH_LK_S_DL_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_DL_T_SB_1_W), //super break I won +//SINGLE vs. STAFF + //side locks + ENUM2STRING(BOTH_LK_S_ST_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_ST_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_ST_S_L_1), //lock if I'm using single vs. a staff + ENUM2STRING(BOTH_LK_S_ST_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_ST_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_S_ST_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_ST_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_ST_T_L_1), //lock if I'm using single vs. a staff + ENUM2STRING(BOTH_LK_S_ST_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_ST_T_SB_1_W), //super break I won +//SINGLE vs. SINGLE + //side locks + ENUM2STRING(BOTH_LK_S_S_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_S_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_S_S_L_1), //lock if I'm using single vs. a single and I initiated + ENUM2STRING(BOTH_LK_S_S_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_S_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_S_S_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_S_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_S_T_L_1), //lock if I'm using single vs. a single and I initiated + ENUM2STRING(BOTH_LK_S_S_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_S_T_SB_1_W), //super break I won +//===Dual Saber locks================================================================== +//DUAL vs. DUAL + //side locks + ENUM2STRING(BOTH_LK_DL_DL_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_DL_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_DL_S_L_1), //lock if I'm using dual vs. dual and I initiated + ENUM2STRING(BOTH_LK_DL_DL_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_DL_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_DL_DL_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_DL_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_DL_T_L_1), //lock if I'm using dual vs. dual and I initiated + ENUM2STRING(BOTH_LK_DL_DL_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_DL_T_SB_1_W), //super break I won +//DUAL vs. STAFF + //side locks + ENUM2STRING(BOTH_LK_DL_ST_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_ST_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_ST_S_L_1), //lock if I'm using dual vs. a staff + ENUM2STRING(BOTH_LK_DL_ST_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_ST_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_DL_ST_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_ST_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_ST_T_L_1), //lock if I'm using dual vs. a staff + ENUM2STRING(BOTH_LK_DL_ST_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_ST_T_SB_1_W), //super break I won +//DUAL vs. SINGLE + //side locks + ENUM2STRING(BOTH_LK_DL_S_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_S_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_S_S_L_1), //lock if I'm using dual vs. a single + ENUM2STRING(BOTH_LK_DL_S_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_S_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_DL_S_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_S_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_S_T_L_1), //lock if I'm using dual vs. a single + ENUM2STRING(BOTH_LK_DL_S_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_S_T_SB_1_W), //super break I won +//===Saber Staff locks================================================================== +//STAFF vs. DUAL + //side locks + ENUM2STRING(BOTH_LK_ST_DL_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_DL_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_DL_S_L_1), //lock if I'm using staff vs. dual + ENUM2STRING(BOTH_LK_ST_DL_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_DL_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_ST_DL_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_DL_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_DL_T_L_1), //lock if I'm using staff vs. dual + ENUM2STRING(BOTH_LK_ST_DL_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_DL_T_SB_1_W), //super break I won +//STAFF vs. STAFF + //side locks + ENUM2STRING(BOTH_LK_ST_ST_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_ST_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_ST_S_L_1), //lock if I'm using staff vs. a staff and I initiated + ENUM2STRING(BOTH_LK_ST_ST_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_ST_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_ST_ST_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_ST_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_ST_T_L_1), //lock if I'm using staff vs. a staff and I initiated + ENUM2STRING(BOTH_LK_ST_ST_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_ST_T_SB_1_W), //super break I won +//STAFF vs. SINGLE + //side locks + ENUM2STRING(BOTH_LK_ST_S_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_S_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_S_S_L_1), //lock if I'm using staff vs. a single + ENUM2STRING(BOTH_LK_ST_S_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_S_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_ST_S_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_S_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_S_T_L_1), //lock if I'm using staff vs. a single + ENUM2STRING(BOTH_LK_ST_S_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_S_T_SB_1_W), //super break I won +//Special cases for same saber style vs. each other (won't fit in nice 5-anim size lists above) + ENUM2STRING(BOTH_LK_S_S_S_L_2), //lock if I'm using single vs. a single and other intitiated + ENUM2STRING(BOTH_LK_S_S_T_L_2), //lock if I'm using single vs. a single and other initiated + ENUM2STRING(BOTH_LK_DL_DL_S_L_2), //lock if I'm using dual vs. dual and other initiated + ENUM2STRING(BOTH_LK_DL_DL_T_L_2), //lock if I'm using dual vs. dual and other initiated + ENUM2STRING(BOTH_LK_ST_ST_S_L_2), //lock if I'm using staff vs. a staff and other initiated + ENUM2STRING(BOTH_LK_ST_ST_T_L_2), //lock if I'm using staff vs. a staff and other initiated +//===End Saber locks================================================================== + ENUM2STRING(BOTH_BF2RETURN), //# + ENUM2STRING(BOTH_BF2BREAK), //# + ENUM2STRING(BOTH_BF2LOCK), //# + ENUM2STRING(BOTH_BF1RETURN), //# + ENUM2STRING(BOTH_BF1BREAK), //# + ENUM2STRING(BOTH_BF1LOCK), //# + ENUM2STRING(BOTH_CWCIRCLE_R2__R_S1), //# + ENUM2STRING(BOTH_CCWCIRCLE_R2__L_S1), //# + ENUM2STRING(BOTH_CWCIRCLE_A2__L__R), //# + ENUM2STRING(BOTH_CCWCIRCLE_A2__R__L), //# + ENUM2STRING(BOTH_CWCIRCLEBREAK), //# + ENUM2STRING(BOTH_CCWCIRCLEBREAK), //# + ENUM2STRING(BOTH_CWCIRCLELOCK), //# + ENUM2STRING(BOTH_CCWCIRCLELOCK), //# + //other saber anims/attacks + ENUM2STRING(BOTH_SABERFAST_STANCE), + ENUM2STRING(BOTH_SABERSLOW_STANCE), + ENUM2STRING(BOTH_SABERDUAL_STANCE), + ENUM2STRING(BOTH_SABERSTAFF_STANCE), + ENUM2STRING(BOTH_A2_STABBACK1), //# Stab saber backward + ENUM2STRING(BOTH_ATTACK_BACK), //# Swing around backwards and attack + ENUM2STRING(BOTH_JUMPFLIPSLASHDOWN1),//# + ENUM2STRING(BOTH_JUMPFLIPSTABDOWN),//# + ENUM2STRING(BOTH_FORCELEAP2_T__B_),//# + ENUM2STRING(BOTH_LUNGE2_B__T_),//# + ENUM2STRING(BOTH_CROUCHATTACKBACK1),//# + //New specials for JKA: + ENUM2STRING(BOTH_JUMPATTACK6),//# + ENUM2STRING(BOTH_JUMPATTACK7),//# + ENUM2STRING(BOTH_SPINATTACK6),//# + ENUM2STRING(BOTH_SPINATTACK7),//# + ENUM2STRING(BOTH_S1_S6),//# From stand1 to saberdual stance - turning on your dual sabers + ENUM2STRING(BOTH_S6_S1),//# From dualstaff stance to stand1 - turning off your dual sabers + ENUM2STRING(BOTH_S1_S7),//# From stand1 to saberstaff stance - turning on your saberstaff + ENUM2STRING(BOTH_S7_S1),//# From saberstaff stance to stand1 - turning off your saberstaff + ENUM2STRING(BOTH_FORCELONGLEAP_START), + ENUM2STRING(BOTH_FORCELONGLEAP_ATTACK), + ENUM2STRING(BOTH_FORCELONGLEAP_LAND), + ENUM2STRING(BOTH_FORCEWALLRUNFLIP_START), + ENUM2STRING(BOTH_FORCEWALLRUNFLIP_END), + ENUM2STRING(BOTH_FORCEWALLRUNFLIP_ALT), + ENUM2STRING(BOTH_FORCEWALLREBOUND_FORWARD), + ENUM2STRING(BOTH_FORCEWALLREBOUND_LEFT), + ENUM2STRING(BOTH_FORCEWALLREBOUND_BACK), + ENUM2STRING(BOTH_FORCEWALLREBOUND_RIGHT), + ENUM2STRING(BOTH_FORCEWALLHOLD_FORWARD), + ENUM2STRING(BOTH_FORCEWALLHOLD_LEFT), + ENUM2STRING(BOTH_FORCEWALLHOLD_BACK), + ENUM2STRING(BOTH_FORCEWALLHOLD_RIGHT), + ENUM2STRING(BOTH_FORCEWALLRELEASE_FORWARD), + ENUM2STRING(BOTH_FORCEWALLRELEASE_LEFT), + ENUM2STRING(BOTH_FORCEWALLRELEASE_BACK), + ENUM2STRING(BOTH_FORCEWALLRELEASE_RIGHT), + ENUM2STRING(BOTH_A7_KICK_F), + ENUM2STRING(BOTH_A7_KICK_B), + ENUM2STRING(BOTH_A7_KICK_R), + ENUM2STRING(BOTH_A7_KICK_L), + ENUM2STRING(BOTH_A7_KICK_S), + ENUM2STRING(BOTH_A7_KICK_BF), + ENUM2STRING(BOTH_A7_KICK_BF_STOP), + ENUM2STRING(BOTH_A7_KICK_RL), + ENUM2STRING(BOTH_A7_KICK_F_AIR), + ENUM2STRING(BOTH_A7_KICK_B_AIR), + ENUM2STRING(BOTH_A7_KICK_R_AIR), + ENUM2STRING(BOTH_A7_KICK_L_AIR), + ENUM2STRING(BOTH_FLIP_ATTACK7), + ENUM2STRING(BOTH_FLIP_HOLD7), + ENUM2STRING(BOTH_FLIP_LAND), + ENUM2STRING(BOTH_PULL_IMPALE_STAB), + ENUM2STRING(BOTH_PULL_IMPALE_SWING), + ENUM2STRING(BOTH_PULLED_INAIR_B), + ENUM2STRING(BOTH_PULLED_INAIR_F), + ENUM2STRING(BOTH_STABDOWN), + ENUM2STRING(BOTH_STABDOWN_STAFF), + ENUM2STRING(BOTH_STABDOWN_DUAL), + ENUM2STRING(BOTH_A6_SABERPROTECT), + ENUM2STRING(BOTH_A7_SOULCAL), + ENUM2STRING(BOTH_A1_SPECIAL), + ENUM2STRING(BOTH_A2_SPECIAL), + ENUM2STRING(BOTH_A3_SPECIAL), + ENUM2STRING(BOTH_ROLL_STAB), + + //# #sep ENUM2STRING(BOTH_ STANDING + ENUM2STRING(BOTH_STAND1), //# Standing idle, no weapon, hands down + ENUM2STRING(BOTH_STAND1IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND2), //# Standing idle with a saber + ENUM2STRING(BOTH_STAND2IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND2IDLE2), + ENUM2STRING(BOTH_STAND3), //# Standing idle with 2-handed weapon + ENUM2STRING(BOTH_STAND3IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND4), //# hands clasp behind back + ENUM2STRING(BOTH_STAND5), //# standing idle, no weapon, hand down, back straight + ENUM2STRING(BOTH_STAND5IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND6), //# one handed), gun at side), relaxed stand + ENUM2STRING(BOTH_STAND8), //# both hands on hips (male) + ENUM2STRING(BOTH_STAND1TO2), //# Transition from stand1 to stand2 + ENUM2STRING(BOTH_STAND2TO1), //# Transition from stand2 to stand1 + ENUM2STRING(BOTH_STAND2TO4), //# Transition from stand2 to stand4 + ENUM2STRING(BOTH_STAND4TO2), //# Transition from stand4 to stand2 + ENUM2STRING(BOTH_STAND4TOATTACK2), //# relaxed stand to 1-handed pistol ready + ENUM2STRING(BOTH_STANDUP2), //# Luke standing up from his meditation platform (cin # 37) + ENUM2STRING(BOTH_STAND5TOSIT3), //# transition from stand 5 to sit 3 + ENUM2STRING(BOTH_STAND1TOSTAND5), //# Transition from stand1 to stand5 + ENUM2STRING(BOTH_STAND5TOSTAND1), //# Transition from stand5 to stand1 + ENUM2STRING(BOTH_STAND5TOAIM), //# Transition of Kye aiming his gun at Desann (cin #9) + ENUM2STRING(BOTH_STAND5STARTLEDLOOKLEFT), //# Kyle turning to watch the bridge drop (cin #9) + ENUM2STRING(BOTH_STARTLEDLOOKLEFTTOSTAND5), //# Kyle returning to stand 5 from watching the bridge drop (cin #9) + ENUM2STRING(BOTH_STAND5TOSTAND8), //# Transition from stand5 to stand8 + ENUM2STRING(BOTH_STAND7TOSTAND8), //# Tavion putting hands on back of chair (cin #11) + ENUM2STRING(BOTH_STAND8TOSTAND5), //# Transition from stand8 to stand5 + ENUM2STRING(BOTH_STAND9), //# Kyle's standing idle, no weapon, hands down + ENUM2STRING(BOTH_STAND9IDLE1), //# Kyle's random standing idle + ENUM2STRING(BOTH_STAND5SHIFTWEIGHT), //# Weightshift from stand5 to side and back to stand5 + ENUM2STRING(BOTH_STAND5SHIFTWEIGHTSTART), //# From stand5 to side + ENUM2STRING(BOTH_STAND5SHIFTWEIGHTSTOP), //# From side to stand5 + ENUM2STRING(BOTH_STAND5TURNLEFTSTART), //# Start turning left from stand5 + ENUM2STRING(BOTH_STAND5TURNLEFTSTOP), //# Stop turning left from stand5 + ENUM2STRING(BOTH_STAND5TURNRIGHTSTART), //# Start turning right from stand5 + ENUM2STRING(BOTH_STAND5TURNRIGHTSTOP), //# Stop turning right from stand5 + ENUM2STRING(BOTH_STAND5LOOK180LEFTSTART), //# Start looking over left shoulder (cin #17) + ENUM2STRING(BOTH_STAND5LOOK180LEFTSTOP), //# Stop looking over left shoulder (cin #17) + + ENUM2STRING(BOTH_CONSOLE1START), //# typing at a console + ENUM2STRING(BOTH_CONSOLE1), //# typing at a console + ENUM2STRING(BOTH_CONSOLE1STOP), //# typing at a console + ENUM2STRING(BOTH_CONSOLE2START), //# typing at a console with comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2), //# typing at a console with comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2STOP), //# typing at a console with comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2HOLDCOMSTART), //# lean in to type at console while holding comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2HOLDCOMSTOP), //# lean away after typing at console while holding comm link in hand (cin #5) + + ENUM2STRING(BOTH_GUARD_LOOKAROUND1), //# Cradling weapon and looking around + ENUM2STRING(BOTH_GUARD_IDLE1), //# Cradling weapon and standing + ENUM2STRING(BOTH_GESTURE1), //# Generic gesture), non-specific + ENUM2STRING(BOTH_GESTURE2), //# Generic gesture), non-specific + ENUM2STRING(BOTH_WALK1TALKCOMM1), //# Talking into coom link while walking + ENUM2STRING(BOTH_TALK1), //# Generic talk anim + ENUM2STRING(BOTH_TALK2), //# Generic talk anim + ENUM2STRING(BOTH_TALKCOMM1START), //# Start talking into a comm link + ENUM2STRING(BOTH_TALKCOMM1), //# Talking into a comm link + ENUM2STRING(BOTH_TALKCOMM1STOP), //# Stop talking into a comm link + ENUM2STRING(BOTH_TALKGESTURE1), //# Generic talk anim + + ENUM2STRING(BOTH_HEADTILTLSTART), //# Head tilt to left + ENUM2STRING(BOTH_HEADTILTLSTOP), //# Head tilt to left + ENUM2STRING(BOTH_HEADTILTRSTART), //# Head tilt to right + ENUM2STRING(BOTH_HEADTILTRSTOP), //# Head tilt to right + ENUM2STRING(BOTH_HEADNOD), //# Head shake YES + ENUM2STRING(BOTH_HEADSHAKE), //# Head shake NO + ENUM2STRING(BOTH_SIT2HEADTILTLSTART), //# Head tilt to left from seated position 2 + ENUM2STRING(BOTH_SIT2HEADTILTLSTOP), //# Head tilt to left from seated position 2 + + ENUM2STRING(BOTH_REACH1START), //# Monmothma reaching for crystal + ENUM2STRING(BOTH_REACH1STOP), //# Monmothma reaching for crystal + + ENUM2STRING(BOTH_COME_ON1), //# Jan gesturing to Kyle (cin #32a) + ENUM2STRING(BOTH_STEADYSELF1), //# Jan trying to keep footing (cin #32a) Kyle (cin#5) + ENUM2STRING(BOTH_STEADYSELF1END), //# Return hands to side from STEADSELF1 Kyle (cin#5) + ENUM2STRING(BOTH_SILENCEGESTURE1), //# Luke silencing Kyle with a raised hand (cin #37) + ENUM2STRING(BOTH_REACHFORSABER1), //# Luke holding hand out for Kyle's saber (cin #37) + ENUM2STRING(BOTH_SABERKILLER1), //# Tavion about to strike Jan with saber (cin #9) + ENUM2STRING(BOTH_SABERKILLEE1), //# Jan about to be struck by Tavion with saber (cin #9) + ENUM2STRING(BOTH_HUGGER1), //# Kyle hugging Jan (cin #29) + ENUM2STRING(BOTH_HUGGERSTOP1), //# Kyle stop hugging Jan but don't let her go (cin #29) + ENUM2STRING(BOTH_HUGGEE1), //# Jan being hugged (cin #29) + ENUM2STRING(BOTH_HUGGEESTOP1), //# Jan stop being hugged but don't let go (cin #29) + + ENUM2STRING(BOTH_SABERTHROW1START), //# Desann throwing his light saber (cin #26) + ENUM2STRING(BOTH_SABERTHROW1STOP), //# Desann throwing his light saber (cin #26) + ENUM2STRING(BOTH_SABERTHROW2START), //# Kyle throwing his light saber (cin #32) + ENUM2STRING(BOTH_SABERTHROW2STOP), //# Kyle throwing his light saber (cin #32) + + //# #sep ENUM2STRING(BOTH_ SITTING/CROUCHING + ENUM2STRING(BOTH_SIT1), //# Normal chair sit. + ENUM2STRING(BOTH_SIT2), //# Lotus position. + ENUM2STRING(BOTH_SIT3), //# Sitting in tired position), elbows on knees + + ENUM2STRING(BOTH_SIT2TOSTAND5), //# Transition from sit 2 to stand 5 + ENUM2STRING(BOTH_STAND5TOSIT2), //# Transition from stand 5 to sit 2 + ENUM2STRING(BOTH_SIT2TOSIT4), //# Trans from sit2 to sit4 (cin #12) Luke leaning back from lotus position. + ENUM2STRING(BOTH_SIT3TOSTAND5), //# transition from sit 3 to stand 5 + + ENUM2STRING(BOTH_CROUCH1), //# Transition from standing to crouch + ENUM2STRING(BOTH_CROUCH1IDLE), //# Crouching idle + ENUM2STRING(BOTH_CROUCH1WALK), //# Walking while crouched + ENUM2STRING(BOTH_CROUCH1WALKBACK), //# Walking while crouched + ENUM2STRING(BOTH_UNCROUCH1), //# Transition from crouch to standing + ENUM2STRING(BOTH_CROUCH2TOSTAND1), //# going from crouch2 to stand1 + ENUM2STRING(BOTH_CROUCH3), //# Desann crouching down to Kyle (cin 9) + ENUM2STRING(BOTH_UNCROUCH3), //# Desann uncrouching down to Kyle (cin 9) + ENUM2STRING(BOTH_CROUCH4), //# Slower version of crouch1 for cinematics + ENUM2STRING(BOTH_UNCROUCH4), //# Slower version of uncrouch1 for cinematics + + ENUM2STRING(BOTH_GUNSIT1), //# sitting on an emplaced gun. + + // Swoop Vehicle animations. + //* #sep BOTH_ SWOOP ANIMS + ENUM2STRING(BOTH_VS_MOUNT_L), //# Mount from left + ENUM2STRING(BOTH_VS_DISMOUNT_L), //# Dismount to left + ENUM2STRING(BOTH_VS_MOUNT_R), //# Mount from right (symmetry) + ENUM2STRING(BOTH_VS_DISMOUNT_R), //# Dismount to right (symmetry) + + ENUM2STRING(BOTH_VS_MOUNTJUMP_L), //# + ENUM2STRING(BOTH_VS_MOUNTTHROW), //# Land on an occupied vehicle & throw off current pilot + ENUM2STRING(BOTH_VS_MOUNTTHROW_L), //# Land on an occupied vehicle & throw off current pilot + ENUM2STRING(BOTH_VS_MOUNTTHROW_R), //# Land on an occupied vehicle & throw off current pilot + ENUM2STRING(BOTH_VS_MOUNTTHROWEE), //# Current pilot getting thrown off by another guy + + ENUM2STRING(BOTH_VS_LOOKLEFT), //# Turn & Look behind and to the left (no weapon) + ENUM2STRING(BOTH_VS_LOOKRIGHT), //# Turn & Look behind and to the right (no weapon) + + ENUM2STRING(BOTH_VS_TURBO), //# Hit The Turbo Button + + ENUM2STRING(BOTH_VS_REV), //# Player looks back as swoop reverses + + ENUM2STRING(BOTH_VS_AIR), //# Player stands up when swoop is airborn + ENUM2STRING(BOTH_VS_AIR_G), //# "" with Gun + ENUM2STRING(BOTH_VS_AIR_SL), //# "" with Saber Left + ENUM2STRING(BOTH_VS_AIR_SR), //# "" with Saber Right + + ENUM2STRING(BOTH_VS_LAND), //# Player bounces down when swoop lands + ENUM2STRING(BOTH_VS_LAND_G), //# "" with Gun + ENUM2STRING(BOTH_VS_LAND_SL), //# "" with Saber Left + ENUM2STRING(BOTH_VS_LAND_SR), //# "" with Saber Right + + ENUM2STRING(BOTH_VS_IDLE), //# Sit + ENUM2STRING(BOTH_VS_IDLE_G), //# Sit (gun) + ENUM2STRING(BOTH_VS_IDLE_SL), //# Sit (saber left) + ENUM2STRING(BOTH_VS_IDLE_SR), //# Sit (saber right) + + ENUM2STRING(BOTH_VS_LEANL), //# Lean left + ENUM2STRING(BOTH_VS_LEANL_G), //# Lean left (gun) + ENUM2STRING(BOTH_VS_LEANL_SL), //# Lean left (saber left) + ENUM2STRING(BOTH_VS_LEANL_SR), //# Lean left (saber right) + + ENUM2STRING(BOTH_VS_LEANR), //# Lean right + ENUM2STRING(BOTH_VS_LEANR_G), //# Lean right (gun) + ENUM2STRING(BOTH_VS_LEANR_SL), //# Lean right (saber left) + ENUM2STRING(BOTH_VS_LEANR_SR), //# Lean right (saber right) + + ENUM2STRING(BOTH_VS_ATL_S), //# Attack left with saber + ENUM2STRING(BOTH_VS_ATR_S), //# Attack right with saber + ENUM2STRING(BOTH_VS_ATR_TO_L_S), //# Attack toss saber from right to left hand + ENUM2STRING(BOTH_VS_ATL_TO_R_S), //# Attack toss saber from left to right hand + ENUM2STRING(BOTH_VS_ATR_G), //# Attack right with gun (90) + ENUM2STRING(BOTH_VS_ATL_G), //# Attack left with gun (90) + ENUM2STRING(BOTH_VS_ATF_G), //# Attack forward with gun + + ENUM2STRING(BOTH_VS_PAIN1), //# Pain + + // Added 12/04/02 by Aurelio. + //* #sep BOTH_ TAUNTAUN ANIMS + ENUM2STRING(BOTH_VT_MOUNT_L), //# Mount from left + ENUM2STRING(BOTH_VT_MOUNT_R), //# Mount from right + ENUM2STRING(BOTH_VT_MOUNT_B), //# Mount from air, behind + ENUM2STRING(BOTH_VT_DISMOUNT), //# Dismount for tauntaun + ENUM2STRING(BOTH_VT_DISMOUNT_L), //# Dismount to tauntauns left + ENUM2STRING(BOTH_VT_DISMOUNT_R), //# Dismount to tauntauns right (symmetry) + + ENUM2STRING(BOTH_VT_WALK_FWD), //# Walk forward + ENUM2STRING(BOTH_VT_WALK_REV), //# Walk backward + ENUM2STRING(BOTH_VT_WALK_FWD_L), //# walk lean left + ENUM2STRING(BOTH_VT_WALK_FWD_R), //# walk lean right + ENUM2STRING(BOTH_VT_RUN_FWD), //# Run forward + ENUM2STRING(BOTH_VT_RUN_REV), //# Look backwards while running (not weapon specific) + ENUM2STRING(BOTH_VT_RUN_FWD_L), //# run lean left + ENUM2STRING(BOTH_VT_RUN_FWD_R), //# run lean right + + ENUM2STRING(BOTH_VT_SLIDEF), //# Tauntaun slides forward with abrupt stop + ENUM2STRING(BOTH_VT_AIR), //# Tauntaun jump + ENUM2STRING(BOTH_VT_ATB), //# Tauntaun tail swipe + ENUM2STRING(BOTH_VT_PAIN1), //# Pain + ENUM2STRING(BOTH_VT_DEATH1), //# Die + ENUM2STRING(BOTH_VT_STAND), //# Stand still and breath + ENUM2STRING(BOTH_VT_BUCK), //# Tauntaun bucking loop animation + + ENUM2STRING(BOTH_VT_LAND), //# Player bounces down when tauntaun lands + ENUM2STRING(BOTH_VT_TURBO), //# Hit The Turbo Button + ENUM2STRING(BOTH_VT_IDLE_SL), //# Sit (saber left) + ENUM2STRING(BOTH_VT_IDLE_SR), //# Sit (saber right) + ENUM2STRING(BOTH_VT_IDLE), //# Sit with no weapon selected + ENUM2STRING(BOTH_VT_IDLE1), //# Sit with no weapon selected + ENUM2STRING(BOTH_VT_IDLE_S), //# Sit with saber selected + ENUM2STRING(BOTH_VT_IDLE_G), //# Sit with gun selected + ENUM2STRING(BOTH_VT_IDLE_T), //# Sit with thermal grenade selected + + ENUM2STRING(BOTH_VT_ATL_S), //# Attack left with saber + ENUM2STRING(BOTH_VT_ATR_S), //# Attack right with saber + ENUM2STRING(BOTH_VT_ATR_TO_L_S), //# Attack toss saber from right to left hand + ENUM2STRING(BOTH_VT_ATL_TO_R_S), //# Attack toss saber from left to right hand + ENUM2STRING(BOTH_VT_ATR_G), //# Attack right with gun (90) + ENUM2STRING(BOTH_VT_ATL_G), //# Attack left with gun (90) + ENUM2STRING(BOTH_VT_ATF_G), //# Attack forward with gun + + + // Added 2/26/02 by Aurelio. + //* #sep BOTH_ FIGHTER ANIMS + ENUM2STRING( BOTH_GEARS_OPEN ), + ENUM2STRING( BOTH_GEARS_CLOSE ), + ENUM2STRING( BOTH_WINGS_OPEN ), + ENUM2STRING( BOTH_WINGS_CLOSE ), + + /////////////////////////////////// + + ENUM2STRING(BOTH_DEATH14_UNGRIP), //# Desann's end death (cin #35) + ENUM2STRING(BOTH_DEATH14_SITUP), //# Tavion sitting up after having been thrown (cin #23) + ENUM2STRING(BOTH_KNEES1), //# Tavion on her knees + ENUM2STRING(BOTH_KNEES2), //# Tavion on her knees looking down + ENUM2STRING(BOTH_KNEES2TO1), //# Transition of KNEES2 to KNEES1 + + //# #sep ENUM2STRING(BOTH_ MOVING + ENUM2STRING(BOTH_WALK1), //# Normal walk + ENUM2STRING(BOTH_WALK2), //# Normal walk + ENUM2STRING(BOTH_WALK_STAFF), //# Walk with saberstaff turned on + ENUM2STRING(BOTH_WALKBACK_STAFF), //# Walk backwards with saberstaff turned on + ENUM2STRING(BOTH_WALK_DUAL), //# Walk with dual turned on + ENUM2STRING(BOTH_WALKBACK_DUAL), //# Walk backwards with dual turned on + ENUM2STRING(BOTH_WALK5), //# Tavion taunting Kyle (cin 22) + ENUM2STRING(BOTH_WALK6), //# Slow walk for Luke (cin 12) + ENUM2STRING(BOTH_WALK7), //# Fast walk + ENUM2STRING(BOTH_RUN1), //# Full run + ENUM2STRING(BOTH_RUN1START), //# Start into full run1 + ENUM2STRING(BOTH_RUN1STOP), //# Stop from full run1 + ENUM2STRING(BOTH_RUN2), //# Full run + ENUM2STRING(BOTH_RUN1TORUN2), //# Wampa run anim transition + ENUM2STRING(BOTH_RUN2TORUN1), //# Wampa run anim transition + ENUM2STRING(BOTH_RUN4), //# Jawa run + ENUM2STRING(BOTH_RUN_STAFF), //# Run with saberstaff turned on + ENUM2STRING(BOTH_RUNBACK_STAFF), //# Run backwards with saberstaff turned on + ENUM2STRING(BOTH_RUN_DUAL), //# Run with dual turned on + ENUM2STRING(BOTH_RUNBACK_DUAL), //# Run backwards with dual turned on + ENUM2STRING(BOTH_STRAFE_LEFT1), //# Sidestep left), should loop + ENUM2STRING(BOTH_STRAFE_RIGHT1), //# Sidestep right), should loop + ENUM2STRING(BOTH_RUNSTRAFE_LEFT1), //# Sidestep left), should loop + ENUM2STRING(BOTH_RUNSTRAFE_RIGHT1), //# Sidestep right), should loop + ENUM2STRING(BOTH_TURN_LEFT1), //# Turn left), should loop + ENUM2STRING(BOTH_TURN_RIGHT1), //# Turn right), should loop + ENUM2STRING(BOTH_TURNSTAND1), //# Turn from STAND1 position + ENUM2STRING(BOTH_TURNSTAND2), //# Turn from STAND2 position + ENUM2STRING(BOTH_TURNSTAND3), //# Turn from STAND3 position + ENUM2STRING(BOTH_TURNSTAND4), //# Turn from STAND4 position + ENUM2STRING(BOTH_TURNSTAND5), //# Turn from STAND5 position + ENUM2STRING(BOTH_TURNCROUCH1), //# Turn from CROUCH1 position + + ENUM2STRING(BOTH_WALKBACK1), //# Walk1 backwards + ENUM2STRING(BOTH_WALKBACK2), //# Walk2 backwards + ENUM2STRING(BOTH_RUNBACK1), //# Run1 backwards + ENUM2STRING(BOTH_RUNBACK2), //# Run1 backwards + + //# #sep BOTH_ JUMPING + ENUM2STRING(BOTH_JUMP1), //# Jump - wind-up and leave ground + ENUM2STRING(BOTH_INAIR1), //# In air loop (from jump) + ENUM2STRING(BOTH_LAND1), //# Landing (from in air loop) + ENUM2STRING(BOTH_LAND2), //# Landing Hard (from a great height) + + ENUM2STRING(BOTH_JUMPBACK1), //# Jump backwards - wind-up and leave ground + ENUM2STRING(BOTH_INAIRBACK1), //# In air loop (from jump back) + ENUM2STRING(BOTH_LANDBACK1), //# Landing backwards(from in air loop) + + ENUM2STRING(BOTH_JUMPLEFT1), //# Jump left - wind-up and leave ground + ENUM2STRING(BOTH_INAIRLEFT1), //# In air loop (from jump left) + ENUM2STRING(BOTH_LANDLEFT1), //# Landing left(from in air loop) + + ENUM2STRING(BOTH_JUMPRIGHT1), //# Jump right - wind-up and leave ground + ENUM2STRING(BOTH_INAIRRIGHT1), //# In air loop (from jump right) + ENUM2STRING(BOTH_LANDRIGHT1), //# Landing right(from in air loop) + + ENUM2STRING(BOTH_FORCEJUMP1), //# Jump - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIR1), //# In air loop (from jump) + ENUM2STRING(BOTH_FORCELAND1), //# Landing (from in air loop) + + ENUM2STRING(BOTH_FORCEJUMPBACK1), //# Jump backwards - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIRBACK1), //# In air loop (from jump back) + ENUM2STRING(BOTH_FORCELANDBACK1), //# Landing backwards(from in air loop) + + ENUM2STRING(BOTH_FORCEJUMPLEFT1), //# Jump left - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIRLEFT1), //# In air loop (from jump left) + ENUM2STRING(BOTH_FORCELANDLEFT1), //# Landing left(from in air loop) + + ENUM2STRING(BOTH_FORCEJUMPRIGHT1), //# Jump right - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIRRIGHT1), //# In air loop (from jump right) + ENUM2STRING(BOTH_FORCELANDRIGHT1), //# Landing right(from in air loop) + //# #sep BOTH_ ACROBATICS + ENUM2STRING(BOTH_FLIP_F), //# Flip forward + ENUM2STRING(BOTH_FLIP_B), //# Flip backwards + ENUM2STRING(BOTH_FLIP_L), //# Flip left + ENUM2STRING(BOTH_FLIP_R), //# Flip right + + ENUM2STRING(BOTH_ROLL_F), //# Roll forward + ENUM2STRING(BOTH_ROLL_B), //# Roll backward + ENUM2STRING(BOTH_ROLL_L), //# Roll left + ENUM2STRING(BOTH_ROLL_R), //# Roll right + + ENUM2STRING(BOTH_HOP_F), //# quickstep forward + ENUM2STRING(BOTH_HOP_B), //# quickstep backwards + ENUM2STRING(BOTH_HOP_L), //# quickstep left + ENUM2STRING(BOTH_HOP_R), //# quickstep right + + ENUM2STRING(BOTH_DODGE_FL), //# lean-dodge forward left + ENUM2STRING(BOTH_DODGE_FR), //# lean-dodge forward right + ENUM2STRING(BOTH_DODGE_BL), //# lean-dodge backwards left + ENUM2STRING(BOTH_DODGE_BR), //# lean-dodge backwards right + ENUM2STRING(BOTH_DODGE_L), //# lean-dodge left + ENUM2STRING(BOTH_DODGE_R), //# lean-dodge right + ENUM2STRING(BOTH_DODGE_HOLD_FL), //# lean-dodge pose forward left + ENUM2STRING(BOTH_DODGE_HOLD_FR), //# lean-dodge pose forward right + ENUM2STRING(BOTH_DODGE_HOLD_BL), //# lean-dodge pose backwards left + ENUM2STRING(BOTH_DODGE_HOLD_BR), //# lean-dodge pose backwards right + ENUM2STRING(BOTH_DODGE_HOLD_L), //# lean-dodge pose left + ENUM2STRING(BOTH_DODGE_HOLD_R), //# lean-dodge pose right + + //MP taunt anims + ENUM2STRING(BOTH_ENGAGETAUNT), + ENUM2STRING(BOTH_BOW), + ENUM2STRING(BOTH_MEDITATE), + ENUM2STRING(BOTH_MEDITATE_END), + ENUM2STRING(BOTH_SHOWOFF_FAST), + ENUM2STRING(BOTH_SHOWOFF_MEDIUM), + ENUM2STRING(BOTH_SHOWOFF_STRONG), + ENUM2STRING(BOTH_SHOWOFF_DUAL), + ENUM2STRING(BOTH_SHOWOFF_STAFF), + ENUM2STRING(BOTH_VICTORY_FAST), + ENUM2STRING(BOTH_VICTORY_MEDIUM), + ENUM2STRING(BOTH_VICTORY_STRONG), + ENUM2STRING(BOTH_VICTORY_DUAL), + ENUM2STRING(BOTH_VICTORY_STAFF), + //other saber/acro anims + ENUM2STRING(BOTH_ARIAL_LEFT), //# + ENUM2STRING(BOTH_ARIAL_RIGHT), //# + ENUM2STRING(BOTH_CARTWHEEL_LEFT), //# + ENUM2STRING(BOTH_CARTWHEEL_RIGHT), //# + ENUM2STRING(BOTH_FLIP_LEFT), //# + ENUM2STRING(BOTH_FLIP_BACK1), //# + ENUM2STRING(BOTH_FLIP_BACK2), //# + ENUM2STRING(BOTH_FLIP_BACK3), //# + ENUM2STRING(BOTH_BUTTERFLY_LEFT), //# + ENUM2STRING(BOTH_BUTTERFLY_RIGHT), //# + ENUM2STRING(BOTH_WALL_RUN_RIGHT), //# + ENUM2STRING(BOTH_WALL_RUN_RIGHT_FLIP),//# + ENUM2STRING(BOTH_WALL_RUN_RIGHT_STOP),//# + ENUM2STRING(BOTH_WALL_RUN_LEFT), //# + ENUM2STRING(BOTH_WALL_RUN_LEFT_FLIP),//# + ENUM2STRING(BOTH_WALL_RUN_LEFT_STOP),//# + ENUM2STRING(BOTH_WALL_FLIP_RIGHT), //# + ENUM2STRING(BOTH_WALL_FLIP_LEFT), //# + ENUM2STRING(BOTH_KNOCKDOWN1), //# knocked backwards + ENUM2STRING(BOTH_KNOCKDOWN2), //# knocked backwards hard + ENUM2STRING(BOTH_KNOCKDOWN3), //# knocked forwards + ENUM2STRING(BOTH_KNOCKDOWN4), //# knocked backwards from crouch + ENUM2STRING(BOTH_KNOCKDOWN5), //# dupe of 3 - will be removed + ENUM2STRING(BOTH_GETUP1), //# + ENUM2STRING(BOTH_GETUP2), //# + ENUM2STRING(BOTH_GETUP3), //# + ENUM2STRING(BOTH_GETUP4), //# + ENUM2STRING(BOTH_GETUP5), //# + ENUM2STRING(BOTH_GETUP_CROUCH_F1), //# + ENUM2STRING(BOTH_GETUP_CROUCH_B1), //# + ENUM2STRING(BOTH_FORCE_GETUP_F1), //# + ENUM2STRING(BOTH_FORCE_GETUP_F2), //# + ENUM2STRING(BOTH_FORCE_GETUP_B1), //# + ENUM2STRING(BOTH_FORCE_GETUP_B2), //# + ENUM2STRING(BOTH_FORCE_GETUP_B3), //# + ENUM2STRING(BOTH_FORCE_GETUP_B4), //# + ENUM2STRING(BOTH_FORCE_GETUP_B5), //# + ENUM2STRING(BOTH_FORCE_GETUP_B6), //# + ENUM2STRING(BOTH_GETUP_BROLL_B), //# + ENUM2STRING(BOTH_GETUP_BROLL_F), //# + ENUM2STRING(BOTH_GETUP_BROLL_L), //# + ENUM2STRING(BOTH_GETUP_BROLL_R), //# + ENUM2STRING(BOTH_GETUP_FROLL_B), //# + ENUM2STRING(BOTH_GETUP_FROLL_F), //# + ENUM2STRING(BOTH_GETUP_FROLL_L), //# + ENUM2STRING(BOTH_GETUP_FROLL_R), //# + ENUM2STRING(BOTH_WALL_FLIP_BACK1), //# + ENUM2STRING(BOTH_WALL_FLIP_BACK2), //# + ENUM2STRING(BOTH_SPIN1), //# + ENUM2STRING(BOTH_CEILING_CLING), //# clinging to ceiling + ENUM2STRING(BOTH_CEILING_DROP), //# dropping from ceiling cling + + //TESTING + ENUM2STRING(BOTH_FJSS_TR_BL), //# jump spin slash tr to bl + ENUM2STRING(BOTH_FJSS_TL_BR), //# jump spin slash bl to tr + ENUM2STRING(BOTH_RIGHTHANDCHOPPEDOFF),//# + ENUM2STRING(BOTH_DEFLECTSLASH__R__L_FIN),//# + ENUM2STRING(BOTH_BASHED1),//# + ENUM2STRING(BOTH_ARIAL_F1),//# + ENUM2STRING(BOTH_BUTTERFLY_FR1),//# + ENUM2STRING(BOTH_BUTTERFLY_FL1),//# + + //NEW SABER/JEDI/FORCE ANIMS + ENUM2STRING(BOTH_BACK_FLIP_UP), //# back flip up Bonus Animation!!!! + ENUM2STRING(BOTH_LOSE_SABER), //# player losing saber (pulled from hand by force pull 4 - Kyle?) + ENUM2STRING(BOTH_STAFF_TAUNT), //# taunt saberstaff + ENUM2STRING(BOTH_DUAL_TAUNT), //# taunt dual + ENUM2STRING(BOTH_A6_FB), //# dual attack front/back + ENUM2STRING(BOTH_A6_LR), //# dual attack left/right + ENUM2STRING(BOTH_A7_HILT), //# saber knock (alt + stand still) + //Alora + ENUM2STRING(BOTH_ALORA_SPIN), //#jump spin attack death ballet + ENUM2STRING(BOTH_ALORA_FLIP_1), //# gymnast move 1 + ENUM2STRING(BOTH_ALORA_FLIP_2), //# gymnast move 2 + ENUM2STRING(BOTH_ALORA_FLIP_3), //# gymnast move3 + ENUM2STRING(BOTH_ALORA_FLIP_B), //# gymnast move back + ENUM2STRING(BOTH_ALORA_SPIN_THROW), //# dual saber throw + ENUM2STRING(BOTH_ALORA_SPIN_SLASH), //# spin slash special bonus animation!! :) + ENUM2STRING(BOTH_ALORA_TAUNT), //# special taunt + //Rosh (Kothos battle) + ENUM2STRING(BOTH_ROSH_PAIN), //# hurt animation (exhausted) + ENUM2STRING(BOTH_ROSH_HEAL), //# healed/rejuvenated + //Tavion + ENUM2STRING(BOTH_TAVION_SCEPTERGROUND), //# stabbing ground with sith sword shoots electricity everywhere + ENUM2STRING(BOTH_TAVION_SWORDPOWER),//# Tavion doing the He-Man(tm) thing + ENUM2STRING(BOTH_SCEPTER_START), //#Point scepter and attack start + ENUM2STRING(BOTH_SCEPTER_HOLD), //#Point scepter and attack hold + ENUM2STRING(BOTH_SCEPTER_STOP), //#Point scepter and attack stop + //Kyle Boss + ENUM2STRING(BOTH_KYLE_GRAB), //# grab + ENUM2STRING(BOTH_KYLE_MISS), //# miss + ENUM2STRING(BOTH_KYLE_PA_1), //# hold 1 + ENUM2STRING(BOTH_PLAYER_PA_1), //# player getting held 1 + ENUM2STRING(BOTH_KYLE_PA_2), //# hold 2 + ENUM2STRING(BOTH_PLAYER_PA_2), //# player getting held 2 + ENUM2STRING(BOTH_PLAYER_PA_FLY), //# player getting knocked back from punch at end of hold 1 + ENUM2STRING(BOTH_KYLE_PA_3), //# hold 3 + ENUM2STRING(BOTH_PLAYER_PA_3), //# player getting held 3 + ENUM2STRING(BOTH_PLAYER_PA_3_FLY),//# player getting thrown at end of hold 3 + //Rancor + ENUM2STRING(BOTH_BUCK_RIDER), //# Rancor bucks when someone is on him + //WAMPA Grabbing enemy + ENUM2STRING(BOTH_HOLD_START), //# + ENUM2STRING(BOTH_HOLD_MISS), //# + ENUM2STRING(BOTH_HOLD_IDLE), //# + ENUM2STRING(BOTH_HOLD_END), //# + ENUM2STRING(BOTH_HOLD_ATTACK), //# + ENUM2STRING(BOTH_HOLD_SNIFF), //# Sniff the guy you're holding + ENUM2STRING(BOTH_HOLD_DROP), //# just drop 'em + //BEING GRABBED BY WAMPA + ENUM2STRING(BOTH_GRABBED), //# + ENUM2STRING(BOTH_RELEASED), //# + ENUM2STRING(BOTH_HANG_IDLE), //# + ENUM2STRING(BOTH_HANG_ATTACK), //# + ENUM2STRING(BOTH_HANG_PAIN), //# + + //# #sep BOTH_ MISC MOVEMENT + ENUM2STRING(BOTH_HIT1), //# Kyle hit by crate in cin #9 + ENUM2STRING(BOTH_LADDER_UP1), //# Climbing up a ladder with rungs at 16 unit intervals + ENUM2STRING(BOTH_LADDER_DWN1), //# Climbing down a ladder with rungs at 16 unit intervals + ENUM2STRING(BOTH_LADDER_IDLE), //# Just sitting on the ladder + + //# #sep ENUM2STRING(BOTH_ FLYING IDLE + ENUM2STRING(BOTH_FLY_SHIELDED), //# For sentry droid, shields in + + //# #sep BOTH_ SWIMMING + ENUM2STRING(BOTH_SWIM_IDLE1), //# Swimming Idle 1 + ENUM2STRING(BOTH_SWIMFORWARD), //# Swim forward loop + ENUM2STRING(BOTH_SWIMBACKWARD), //# Swim backward loop + + //# #sep ENUM2STRING(BOTH_ LYING + ENUM2STRING(BOTH_SLEEP1), //# laying on back-rknee up-rhand on torso + ENUM2STRING(BOTH_SLEEP6START), //# Kyle leaning back to sleep (cin 20) + ENUM2STRING(BOTH_SLEEP6STOP), //# Kyle waking up and shaking his head (cin 21) + ENUM2STRING(BOTH_SLEEP1GETUP), //# alarmed and getting up out of sleep1 pose to stand + ENUM2STRING(BOTH_SLEEP1GETUP2), //# + + ENUM2STRING(BOTH_CHOKE1START), //# tavion in force grip choke + ENUM2STRING(BOTH_CHOKE1STARTHOLD), //# loop of tavion in force grip choke + ENUM2STRING(BOTH_CHOKE1), //# tavion in force grip choke + + ENUM2STRING(BOTH_CHOKE2), //# tavion recovering from force grip choke + ENUM2STRING(BOTH_CHOKE3), //# left-handed choke (for people still holding a weapon) + + //# #sep ENUM2STRING(BOTH_ HUNTER-SEEKER BOT-SPECIFIC + ENUM2STRING(BOTH_POWERUP1), //# Wakes up + + ENUM2STRING(BOTH_TURNON), //# Protocol Droid wakes up + ENUM2STRING(BOTH_TURNOFF), //# Protocol Droid shuts off + ENUM2STRING(BOTH_BUTTON1), //# Single button push with right hand + ENUM2STRING(BOTH_BUTTON2), //# Single button push with left finger + ENUM2STRING(BOTH_BUTTON_HOLD), //# Single button hold with left hand + ENUM2STRING(BOTH_BUTTON_RELEASE), //# Single button release with left hand + + //# JEDI-SPECIFIC + //# #sep BOTH_ FORCE ANIMS + ENUM2STRING(BOTH_RESISTPUSH), //# plant yourself to resist force push/pulls. + ENUM2STRING(BOTH_FORCEPUSH), //# Use off-hand to do force power. + ENUM2STRING(BOTH_FORCEPULL), //# Use off-hand to do force power. + ENUM2STRING(BOTH_MINDTRICK1), //# Use off-hand to do mind trick + ENUM2STRING(BOTH_MINDTRICK2), //# Use off-hand to do distraction + ENUM2STRING(BOTH_FORCELIGHTNING), //# Use off-hand to do lightning + ENUM2STRING(BOTH_FORCELIGHTNING_START), //# Use off-hand to do lightning - start + ENUM2STRING(BOTH_FORCELIGHTNING_HOLD), //# Use off-hand to do lightning - hold + ENUM2STRING(BOTH_FORCELIGHTNING_RELEASE),//# Use off-hand to do lightning - release + ENUM2STRING(BOTH_FORCEHEAL_START), //# Healing meditation pose start + ENUM2STRING(BOTH_FORCEHEAL_STOP), //# Healing meditation pose end + ENUM2STRING(BOTH_FORCEHEAL_QUICK), //# Healing meditation gesture + ENUM2STRING(BOTH_SABERPULL), //# Use off-hand to do force power. + ENUM2STRING(BOTH_FORCEGRIP1), //# force-gripping (no anim?) + ENUM2STRING(BOTH_FORCEGRIP3), //# force-gripping (right-hand) + ENUM2STRING(BOTH_FORCEGRIP3THROW), //# throwing while force-gripping (right hand) + ENUM2STRING(BOTH_FORCEGRIP_HOLD), //# Use off-hand to do grip - hold + ENUM2STRING(BOTH_FORCEGRIP_RELEASE),//# Use off-hand to do grip - release + ENUM2STRING(BOTH_TOSS1), //# throwing to left after force gripping + ENUM2STRING(BOTH_TOSS2), //# throwing to right after force gripping + //NEW force anims for JKA: + ENUM2STRING(BOTH_FORCE_RAGE), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING_START), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING_HOLD), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING_RELEASE), + ENUM2STRING(BOTH_FORCE_DRAIN), + ENUM2STRING(BOTH_FORCE_DRAIN_START), + ENUM2STRING(BOTH_FORCE_DRAIN_HOLD), + ENUM2STRING(BOTH_FORCE_DRAIN_RELEASE), + ENUM2STRING(BOTH_FORCE_DRAIN_GRAB_START), + ENUM2STRING(BOTH_FORCE_DRAIN_GRAB_HOLD), + ENUM2STRING(BOTH_FORCE_DRAIN_GRAB_END), + ENUM2STRING(BOTH_FORCE_DRAIN_GRABBED), + ENUM2STRING(BOTH_FORCE_ABSORB), + ENUM2STRING(BOTH_FORCE_ABSORB_START), + ENUM2STRING(BOTH_FORCE_ABSORB_END), + ENUM2STRING(BOTH_FORCE_PROTECT), + ENUM2STRING(BOTH_FORCE_PROTECT_FAST), + + ENUM2STRING(BOTH_WIND), + + ENUM2STRING(BOTH_STAND_TO_KNEEL), + ENUM2STRING(BOTH_KNEEL_TO_STAND), + + ENUM2STRING(BOTH_TUSKENATTACK1), + ENUM2STRING(BOTH_TUSKENATTACK2), + ENUM2STRING(BOTH_TUSKENATTACK3), + ENUM2STRING(BOTH_TUSKENLUNGE1), + ENUM2STRING(BOTH_TUSKENTAUNT1), + + ENUM2STRING(BOTH_COWER1_START), //# cower start + ENUM2STRING(BOTH_COWER1), //# cower loop + ENUM2STRING(BOTH_COWER1_STOP), //# cower stop + ENUM2STRING(BOTH_SONICPAIN_START), + ENUM2STRING(BOTH_SONICPAIN_HOLD), + ENUM2STRING(BOTH_SONICPAIN_END), + + //new anim slots per Jarrod's request + ENUM2STRING(BOTH_STAND10), + ENUM2STRING(BOTH_STAND10_TALK1), + ENUM2STRING(BOTH_STAND10_TALK2), + ENUM2STRING(BOTH_STAND10TOSTAND1), + + ENUM2STRING(BOTH_STAND1_TALK1), + ENUM2STRING(BOTH_STAND1_TALK2), + ENUM2STRING(BOTH_STAND1_TALK3), + + ENUM2STRING(BOTH_SIT4), + ENUM2STRING(BOTH_SIT5), + ENUM2STRING(BOTH_SIT5_TALK1), + ENUM2STRING(BOTH_SIT5_TALK2), + ENUM2STRING(BOTH_SIT5_TALK3), + + ENUM2STRING(BOTH_SIT6), + ENUM2STRING(BOTH_SIT7), + //================================================= + //ANIMS IN WHICH ONLY THE UPPER OBJECTS ARE IN MD3 + //================================================= + //# #sep ENUM2STRING(TORSO_ WEAPON-RELATED + ENUM2STRING(TORSO_DROPWEAP1), //# Put weapon away + ENUM2STRING(TORSO_DROPWEAP4), //# Put weapon away + ENUM2STRING(TORSO_RAISEWEAP1), //# Draw Weapon + ENUM2STRING(TORSO_RAISEWEAP4), //# Draw Weapon + ENUM2STRING(TORSO_WEAPONREADY1), //# Ready to fire stun baton + ENUM2STRING(TORSO_WEAPONREADY2), //# Ready to fire one-handed blaster pistol + ENUM2STRING(TORSO_WEAPONREADY3), //# Ready to fire blaster rifle + ENUM2STRING(TORSO_WEAPONREADY4), //# Ready to fire sniper rifle + ENUM2STRING(TORSO_WEAPONREADY10), //# Ready to fire thermal det + ENUM2STRING(TORSO_WEAPONIDLE2), //# Holding one-handed blaster + ENUM2STRING(TORSO_WEAPONIDLE3), //# Holding blaster rifle + ENUM2STRING(TORSO_WEAPONIDLE4), //# Holding sniper rifle + ENUM2STRING(TORSO_WEAPONIDLE10), //# Holding thermal det + + //# #sep ENUM2STRING(TORSO_ USING NON-WEAPON OBJECTS + + //# #sep ENUM2STRING(TORSO_ MISC + ENUM2STRING(TORSO_SURRENDER_START), //# arms up + ENUM2STRING(TORSO_SURRENDER_STOP), //# arms back down + ENUM2STRING(TORSO_CHOKING1), //# TEMP + + ENUM2STRING(TORSO_HANDSIGNAL1), + ENUM2STRING(TORSO_HANDSIGNAL2), + ENUM2STRING(TORSO_HANDSIGNAL3), + ENUM2STRING(TORSO_HANDSIGNAL4), + ENUM2STRING(TORSO_HANDSIGNAL5), + + //================================================= + //ANIMS IN WHICH ONLY THE LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep Legs-only anims + ENUM2STRING(LEGS_TURN1), //# What legs do when you turn your lower body to match your upper body facing + ENUM2STRING(LEGS_TURN2), //# Leg turning from stand2 + ENUM2STRING(LEGS_LEAN_LEFT1), //# Lean left + ENUM2STRING(LEGS_LEAN_RIGHT1), //# Lean Right + ENUM2STRING(LEGS_CHOKING1), //# TEMP + ENUM2STRING(LEGS_LEFTUP1), //# On a slope with left foot 4 higher than right + ENUM2STRING(LEGS_LEFTUP2), //# On a slope with left foot 8 higher than right + ENUM2STRING(LEGS_LEFTUP3), //# On a slope with left foot 12 higher than right + ENUM2STRING(LEGS_LEFTUP4), //# On a slope with left foot 16 higher than right + ENUM2STRING(LEGS_LEFTUP5), //# On a slope with left foot 20 higher than right + ENUM2STRING(LEGS_RIGHTUP1), //# On a slope with RIGHT foot 4 higher than left + ENUM2STRING(LEGS_RIGHTUP2), //# On a slope with RIGHT foot 8 higher than left + ENUM2STRING(LEGS_RIGHTUP3), //# On a slope with RIGHT foot 12 higher than left + ENUM2STRING(LEGS_RIGHTUP4), //# On a slope with RIGHT foot 16 higher than left + ENUM2STRING(LEGS_RIGHTUP5), //# On a slope with RIGHT foot 20 higher than left + ENUM2STRING(LEGS_S1_LUP1), + ENUM2STRING(LEGS_S1_LUP2), + ENUM2STRING(LEGS_S1_LUP3), + ENUM2STRING(LEGS_S1_LUP4), + ENUM2STRING(LEGS_S1_LUP5), + ENUM2STRING(LEGS_S1_RUP1), + ENUM2STRING(LEGS_S1_RUP2), + ENUM2STRING(LEGS_S1_RUP3), + ENUM2STRING(LEGS_S1_RUP4), + ENUM2STRING(LEGS_S1_RUP5), + ENUM2STRING(LEGS_S3_LUP1), + ENUM2STRING(LEGS_S3_LUP2), + ENUM2STRING(LEGS_S3_LUP3), + ENUM2STRING(LEGS_S3_LUP4), + ENUM2STRING(LEGS_S3_LUP5), + ENUM2STRING(LEGS_S3_RUP1), + ENUM2STRING(LEGS_S3_RUP2), + ENUM2STRING(LEGS_S3_RUP3), + ENUM2STRING(LEGS_S3_RUP4), + ENUM2STRING(LEGS_S3_RUP5), + ENUM2STRING(LEGS_S4_LUP1), + ENUM2STRING(LEGS_S4_LUP2), + ENUM2STRING(LEGS_S4_LUP3), + ENUM2STRING(LEGS_S4_LUP4), + ENUM2STRING(LEGS_S4_LUP5), + ENUM2STRING(LEGS_S4_RUP1), + ENUM2STRING(LEGS_S4_RUP2), + ENUM2STRING(LEGS_S4_RUP3), + ENUM2STRING(LEGS_S4_RUP4), + ENUM2STRING(LEGS_S4_RUP5), + ENUM2STRING(LEGS_S5_LUP1), + ENUM2STRING(LEGS_S5_LUP2), + ENUM2STRING(LEGS_S5_LUP3), + ENUM2STRING(LEGS_S5_LUP4), + ENUM2STRING(LEGS_S5_LUP5), + ENUM2STRING(LEGS_S5_RUP1), + ENUM2STRING(LEGS_S5_RUP2), + ENUM2STRING(LEGS_S5_RUP3), + ENUM2STRING(LEGS_S5_RUP4), + ENUM2STRING(LEGS_S5_RUP5), + ENUM2STRING(LEGS_S6_LUP1), + ENUM2STRING(LEGS_S6_LUP2), + ENUM2STRING(LEGS_S6_LUP3), + ENUM2STRING(LEGS_S6_LUP4), + ENUM2STRING(LEGS_S6_LUP5), + ENUM2STRING(LEGS_S6_RUP1), + ENUM2STRING(LEGS_S6_RUP2), + ENUM2STRING(LEGS_S6_RUP3), + ENUM2STRING(LEGS_S6_RUP4), + ENUM2STRING(LEGS_S6_RUP5), + ENUM2STRING(LEGS_S7_LUP1), + ENUM2STRING(LEGS_S7_LUP2), + ENUM2STRING(LEGS_S7_LUP3), + ENUM2STRING(LEGS_S7_LUP4), + ENUM2STRING(LEGS_S7_LUP5), + ENUM2STRING(LEGS_S7_RUP1), + ENUM2STRING(LEGS_S7_RUP2), + ENUM2STRING(LEGS_S7_RUP3), + ENUM2STRING(LEGS_S7_RUP4), + ENUM2STRING(LEGS_S7_RUP5), + + //New anim as per Jarrod's request + ENUM2STRING(LEGS_TURN180), + + //====================================================== + //cinematic anims + //====================================================== + //# #sep BOTH_ CINEMATIC-ONLY + ENUM2STRING(BOTH_CIN_1), //# Level specific cinematic 1 + ENUM2STRING(BOTH_CIN_2), //# Level specific cinematic 2 + ENUM2STRING(BOTH_CIN_3), //# Level specific cinematic 3 + ENUM2STRING(BOTH_CIN_4), //# Level specific cinematic 4 + ENUM2STRING(BOTH_CIN_5), //# Level specific cinematic 5 + ENUM2STRING(BOTH_CIN_6), //# Level specific cinematic 6 + ENUM2STRING(BOTH_CIN_7), //# Level specific cinematic 7 + ENUM2STRING(BOTH_CIN_8), //# Level specific cinematic 8 + ENUM2STRING(BOTH_CIN_9), //# Level specific cinematic 9 + ENUM2STRING(BOTH_CIN_10), //# Level specific cinematic 10 + ENUM2STRING(BOTH_CIN_11), //# Level specific cinematic 11 + ENUM2STRING(BOTH_CIN_12), //# Level specific cinematic 12 + ENUM2STRING(BOTH_CIN_13), //# Level specific cinematic 13 + ENUM2STRING(BOTH_CIN_14), //# Level specific cinematic 14 + ENUM2STRING(BOTH_CIN_15), //# Level specific cinematic 15 + ENUM2STRING(BOTH_CIN_16), //# Level specific cinematic 16 + ENUM2STRING(BOTH_CIN_17), //# Level specific cinematic 17 + ENUM2STRING(BOTH_CIN_18), //# Level specific cinematic 18 + ENUM2STRING(BOTH_CIN_19), //# Level specific cinematic 19 + ENUM2STRING(BOTH_CIN_20), //# Level specific cinematic 20 + ENUM2STRING(BOTH_CIN_21), //# Level specific cinematic 21 + ENUM2STRING(BOTH_CIN_22), //# Level specific cinematic 22 + ENUM2STRING(BOTH_CIN_23), //# Level specific cinematic 23 + ENUM2STRING(BOTH_CIN_24), //# Level specific cinematic 24 + ENUM2STRING(BOTH_CIN_25), //# Level specific cinematic 25 + + ENUM2STRING(BOTH_CIN_26), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_27), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_28), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_29), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_30), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_31), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_32), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_33), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_34), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_35), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_36), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_37), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_38), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_39), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_40), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_41), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_42), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_43), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_44), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_45), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_46), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_47), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_48), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_49), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_50), //# Level specific cinematic + + //must be terminated + NULL,-1 +}; +#endif // _XBOX / _UI diff --git a/codemp/cgame/cg_consolecmds.c b/codemp/cgame/cg_consolecmds.c new file mode 100644 index 0000000..fa0a444 --- /dev/null +++ b/codemp/cgame/cg_consolecmds.c @@ -0,0 +1,416 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_consolecmds.c -- text commands typed in at the local console, or +// executed by a key binding + +#include "cg_local.h" +#include "../ui/ui_shared.h" +#include "bg_saga.h" +extern menuDef_t *menuScoreboard; + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + + +void CG_TargetCommand_f( void ) { + int targetNum; + char test[4]; + + targetNum = CG_CrosshairPlayer(); + if (!targetNum ) { + return; + } + + trap_Argv( 1, test, 4 ); + trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); +} + + + +/* +================= +CG_SizeUp_f + +Keybinding command +================= +*/ +static void CG_SizeUp_f (void) { + trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer+10))); +} + + +/* +================= +CG_SizeDown_f + +Keybinding command +================= +*/ +static void CG_SizeDown_f (void) { + trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer-10))); +} + + +/* +============= +CG_Viewpos_f + +Debugging command to print the current position +============= +*/ +static void CG_Viewpos_f (void) { + CG_Printf ("%s (%i %i %i) : %i\n", cgs.mapname, (int)cg->refdef.vieworg[0], + (int)cg->refdef.vieworg[1], (int)cg->refdef.vieworg[2], + (int)cg->refdef.viewangles[YAW]); +} + + +static void CG_ScoresDown_f( void ) { + + CG_BuildSpectatorString(); + if ( cg->scoresRequestTime + 2000 < cg->time ) { + // the scores are more than two seconds out of data, + // so request new ones + cg->scoresRequestTime = cg->time; + trap_SendClientCommand( "score" ); + + // leave the current scores up if they were already + // displayed, but if this is the first hit, clear them out + if ( !cg->showScores ) { + cg->showScores = qtrue; + cg->numScores = 0; + } + } else { + // show the cached contents even if they just pressed if it + // is within two seconds + cg->showScores = qtrue; + } +} + +static void CG_ScoresUp_f( void ) { + if ( cg->showScores ) { + cg->showScores = qfalse; + cg->scoreFadeTime = cg->time; + } +} + +extern menuDef_t *menuScoreboard; +void Menu_Reset(); // FIXME: add to right include file + +static void CG_scrollScoresDown_f( void) { + if (menuScoreboard && cg->scoreBoardShowing) { + Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qtrue); + Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qtrue); + Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qtrue); + } +} + + +static void CG_scrollScoresUp_f( void) { + if (menuScoreboard && cg->scoreBoardShowing) { + Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qfalse); + Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qfalse); + Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qfalse); + } +} + + +static void CG_spWin_f( void) { + trap_Cvar_Set("cg_cameraOrbit", "2"); + trap_Cvar_Set("cg_cameraOrbitDelay", "35"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + +#ifdef _XBOX + ClientManager::ActiveClient().cg_thirdPerson = 1; + ClientManager::ActiveClient().cg_thirdPersonRange = 100; + ClientManager::ActiveClient().cg_thirdPersonAngle = 0; +#endif + + CG_AddBufferedSound(cgs.media.winnerSound); + //trap_S_StartLocalSound(cgs.media.winnerSound, CHAN_ANNOUNCER); + CG_CenterPrint(CG_GetStringEdString("MP_INGAME", "YOU_WIN"), SCREEN_HEIGHT * .30, 0); +} + +static void CG_spLose_f( void) { + trap_Cvar_Set("cg_cameraOrbit", "2"); + trap_Cvar_Set("cg_cameraOrbitDelay", "35"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + +#ifdef _XBOX + ClientManager::ActiveClient().cg_thirdPerson = 1; + ClientManager::ActiveClient().cg_thirdPersonRange = 100; + ClientManager::ActiveClient().cg_thirdPersonAngle = 0; +#endif + + CG_AddBufferedSound(cgs.media.loserSound); + //trap_S_StartLocalSound(cgs.media.loserSound, CHAN_ANNOUNCER); + CG_CenterPrint(CG_GetStringEdString("MP_INGAME", "YOU_LOSE"), SCREEN_HEIGHT * .30, 0); +} + + +static void CG_TellTarget_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_TellAttacker_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_LastAttacker(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +/* +================== +CG_StartOrbit_f +================== +*/ + +static void CG_StartOrbit_f( void ) { + char var[MAX_TOKEN_CHARS]; + + trap_Cvar_VariableStringBuffer( "developer", var, sizeof( var ) ); + if ( !atoi(var) ) { + return; + } + if (cg_cameraOrbit.value != 0) { + trap_Cvar_Set ("cg_cameraOrbit", "0"); +#ifdef _XBOX + ClientManager::ActiveClient().cg_thirdPerson = 0; +#endif + trap_Cvar_Set("cg_thirdPerson", "0"); + } else { + trap_Cvar_Set("cg_cameraOrbit", "5"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + +#ifdef _XBOX + ClientManager::ActiveClient().cg_thirdPerson = 1; + ClientManager::ActiveClient().cg_thirdPersonRange = 100; + ClientManager::ActiveClient().cg_thirdPersonAngle = 0; +#endif + } +} + +void CG_SiegeBriefingDisplay(int team, int dontshow); +static void CG_SiegeBriefing_f(void) +{ + int team; + + if (cgs.gametype != GT_SIEGE) + { //Cannot be displayed unless in this gametype + return; + } + + team = cg->predictedPlayerState.persistant[PERS_TEAM]; + + if (team != SIEGETEAM_TEAM1 && + team != SIEGETEAM_TEAM2) + { //cannot be displayed if not on a valid team + return; + } + + CG_SiegeBriefingDisplay(team, 0); +} + +static void CG_SiegeCvarUpdate_f(void) +{ + int team; + + if (cgs.gametype != GT_SIEGE) + { //Cannot be displayed unless in this gametype + return; + } + + team = cg->predictedPlayerState.persistant[PERS_TEAM]; + + if (team != SIEGETEAM_TEAM1 && + team != SIEGETEAM_TEAM2) + { //cannot be displayed if not on a valid team + return; + } + + CG_SiegeBriefingDisplay(team, 1); +} +static void CG_SiegeCompleteCvarUpdate_f(void) +{ + + if (cgs.gametype != GT_SIEGE) + { //Cannot be displayed unless in this gametype + return; + } + + // Set up cvars for both teams + CG_SiegeBriefingDisplay(SIEGETEAM_TEAM1, 1); + CG_SiegeBriefingDisplay(SIEGETEAM_TEAM2, 1); +} +/* +static void CG_Camera_f( void ) { + char name[1024]; + trap_Argv( 1, name, sizeof(name)); + if (trap_loadCamera(name)) { + cg->cameraMode = qtrue; + trap_startCamera(cg->time); + } else { + CG_Printf ("Unable to load camera %s\n",name); + } +} +*/ + + +typedef struct { + char *cmd; + void (*function)(void); +} consoleCommand_t; + +static consoleCommand_t commands[] = { + { "testgun", CG_TestGun_f }, + { "testmodel", CG_TestModel_f }, + { "nextframe", CG_TestModelNextFrame_f }, + { "prevframe", CG_TestModelPrevFrame_f }, + { "nextskin", CG_TestModelNextSkin_f }, + { "prevskin", CG_TestModelPrevSkin_f }, + { "viewpos", CG_Viewpos_f }, + { "+scores", CG_ScoresDown_f }, + { "-scores", CG_ScoresUp_f }, + { "sizeup", CG_SizeUp_f }, + { "sizedown", CG_SizeDown_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapprev", CG_PrevWeapon_f }, + { "weapon", CG_Weapon_f }, + { "weaponclean", CG_WeaponClean_f }, + { "tell_target", CG_TellTarget_f }, + { "tell_attacker", CG_TellAttacker_f }, + { "tcmd", CG_TargetCommand_f }, + { "spWin", CG_spWin_f }, + { "spLose", CG_spLose_f }, + { "scoresDown", CG_scrollScoresDown_f }, + { "scoresUp", CG_scrollScoresUp_f }, + { "startOrbit", CG_StartOrbit_f }, + //{ "camera", CG_Camera_f }, + { "loaddeferred", CG_LoadDeferredPlayers }, + { "invnext", CG_NextInventory_f }, + { "invprev", CG_PrevInventory_f }, + { "forcenext", CG_NextForcePower_f }, + { "forceprev", CG_PrevForcePower_f }, + { "briefing", CG_SiegeBriefing_f }, + { "siegeCvarUpdate", CG_SiegeCvarUpdate_f }, + { "siegeCompleteCvarUpdate", CG_SiegeCompleteCvarUpdate_f }, +}; + + +/* +================= +CG_ConsoleCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +qboolean CG_ConsoleCommand( void ) { + const char *cmd; + int i; + + cmd = CG_Argv(0); + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { + if ( !Q_stricmp( cmd, commands[i].cmd ) ) { + commands[i].function(); + return qtrue; + } + } + + return qfalse; +} + + +/* +================= +CG_InitConsoleCommands + +Let the client system know about all of our commands +so it can perform tab completion +================= +*/ +void CG_InitConsoleCommands( void ) { + int i; + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { + trap_AddCommand( commands[i].cmd ); + } + + // + // the game server will interpret these commands, which will be automatically + // forwarded to the server after they are not recognized locally + // + trap_AddCommand ("forcechanged"); + trap_AddCommand ("sv_invnext"); + trap_AddCommand ("sv_invprev"); + trap_AddCommand ("sv_forcenext"); + trap_AddCommand ("sv_forceprev"); + trap_AddCommand ("sv_saberswitch"); + trap_AddCommand ("engage_duel"); + trap_AddCommand ("force_heal"); + trap_AddCommand ("force_speed"); + trap_AddCommand ("force_throw"); + trap_AddCommand ("force_pull"); + trap_AddCommand ("force_distract"); + trap_AddCommand ("force_rage"); + trap_AddCommand ("force_protect"); + trap_AddCommand ("force_absorb"); + trap_AddCommand ("force_healother"); + trap_AddCommand ("force_forcepowerother"); + trap_AddCommand ("force_seeing"); + trap_AddCommand ("use_seeker"); + trap_AddCommand ("use_field"); + trap_AddCommand ("use_bacta"); + trap_AddCommand ("use_electrobinoculars"); + trap_AddCommand ("zoom"); + trap_AddCommand ("use_sentry"); + trap_AddCommand ("bot_order"); + trap_AddCommand ("saberAttackCycle"); + trap_AddCommand ("kill"); + trap_AddCommand ("say"); + trap_AddCommand ("say_team"); + trap_AddCommand ("tell"); + trap_AddCommand ("give"); + trap_AddCommand ("god"); + trap_AddCommand ("notarget"); + trap_AddCommand ("noclip"); + trap_AddCommand ("team"); + trap_AddCommand ("follow"); + trap_AddCommand ("levelshot"); + trap_AddCommand ("addbot"); + trap_AddCommand ("setviewpos"); + trap_AddCommand ("callvote"); + trap_AddCommand ("vote"); + trap_AddCommand ("callteamvote"); + trap_AddCommand ("teamvote"); + trap_AddCommand ("stats"); + trap_AddCommand ("teamtask"); + trap_AddCommand ("loaddefered"); // spelled wrong, but not changing for demo +} diff --git a/codemp/cgame/cg_draw.c b/codemp/cgame/cg_draw.c new file mode 100644 index 0000000..b978485 --- /dev/null +++ b/codemp/cgame/cg_draw.c @@ -0,0 +1,10281 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_draw.c -- draw all of the graphical elements during +// active (after loading) gameplay + +#include "cg_local.h" + +#include "bg_saga.h" + +#include "../ui/ui_shared.h" +#include "../ui/ui_public.h" +#include "../renderer/tr_font.h" + +#ifdef _XBOX +#include "../client/cl_data.h" +#define SPLITSCREEN_HUD_SCALE 0.75f +#define SPLITSCREEN_HUD_P1OFFSET 220 +int lscalex, lscaley, rscalex, rscaley; +#endif + +#include "../Xbox/XBLive.h" +#include "../client/fffx.h" + +extern float CG_RadiusForCent( centity_t *cent ); +qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y); +qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ); +static void CG_DrawSiegeTimer(int timeRemaining, qboolean isMyTeam); +static void CG_DrawSiegeDeathTimer( int timeRemaining ); +// nmckenzie: DUEL_HEALTH +void CG_DrawDuelistHealth ( float x, float y, float w, float h, int duelist ); + +// used for scoreboard +extern displayContextDef_t cgDC; +menuDef_t *menuScoreboard = NULL; +vec4_t bluehudtint = {0.5, 0.5, 1.0, 1.0}; +vec4_t redhudtint = {1.0, 0.5, 0.5, 1.0}; +float *hudTintColor; + +int sortedTeamPlayers[TEAM_MAXOVERLAY]; +int numSortedTeamPlayers; + +int lastvalidlockdif; + +#ifdef _XBOX +extern float zoomFov[2]; +#else +extern float zoomFov; //this has to be global client-side +#endif + +char systemChat[256]; +char teamChat1[256]; +char teamChat2[256]; + +// The time at which you died and the time it will take for you to rejoin game. +int cg_siegeDeathTime = 0; + +#define MAX_HUD_TICS 4 +const char *armorTicName[MAX_HUD_TICS] = +{ +"armor_tic1", +"armor_tic2", +"armor_tic3", +"armor_tic4", +}; + +const char *healthTicName[MAX_HUD_TICS] = +{ +"health_tic1", +"health_tic2", +"health_tic3", +"health_tic4", +}; + +const char *forceTicName[MAX_HUD_TICS] = +{ +"force_tic1", +"force_tic2", +"force_tic3", +"force_tic4", +}; + +const char *ammoTicName[MAX_HUD_TICS] = +{ +"ammo_tic1", +"ammo_tic2", +"ammo_tic3", +"ammo_tic4", +}; + +char *showPowersName[] = +{ + "HEAL2",//FP_HEAL + "JUMP2",//FP_LEVITATION + "SPEED2",//FP_SPEED + "PUSH2",//FP_PUSH + "PULL2",//FP_PULL + "MINDTRICK2",//FP_TELEPTAHY + "GRIP2",//FP_GRIP + "LIGHTNING2",//FP_LIGHTNING + "DARK_RAGE2",//FP_RAGE + "PROTECT2",//FP_PROTECT + "ABSORB2",//FP_ABSORB + "TEAM_HEAL2",//FP_TEAM_HEAL + "TEAM_REPLENISH2",//FP_TEAM_FORCE + "DRAIN2",//FP_DRAIN + "SEEING2",//FP_SEE + "SABER_OFFENSE2",//FP_SABER_OFFENSE + "SABER_DEFENSE2",//FP_SABER_DEFENSE + "SABER_THROW2",//FP_SABERTHROW + NULL +}; + +//Called from UI shared code. For now we'll just redirect to the normal anim load function. +#include "../namespace_begin.h" + + +int UI_ParseAnimationFile(const char *filename, animation_t *animset, qboolean isHumanoid) +{ + return BG_ParseAnimationFile(filename, animset, isHumanoid); +} + +int MenuFontToHandle(int iMenuFont) +{ + switch (iMenuFont) + { + case FONT_SMALL: return cgDC.Assets.qhSmallFont; + case FONT_SMALL2: return cgDC.Assets.qhSmall2Font; + case FONT_MEDIUM: return cgDC.Assets.qhMediumFont; + case FONT_LARGE: return cgDC.Assets.qhMediumFont;//cgDC.Assets.qhBigFont; + //fixme? Big fonr isn't registered...? + } + + return cgDC.Assets.qhMediumFont; +} + +#include "../namespace_end.h" + +int CG_Text_Width(const char *text, float scale, int iMenuFont) +{ + int iFontIndex = MenuFontToHandle(iMenuFont); + + return trap_R_Font_StrLenPixels(text, iFontIndex, scale); +} + +int CG_Text_Height(const char *text, float scale, int iMenuFont) +{ + int iFontIndex = MenuFontToHandle(iMenuFont); + + return trap_R_Font_HeightPixels(iFontIndex, scale); +} + +#include "../qcommon/qfiles.h" // for STYLE_BLINK etc +void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style, int iMenuFont) +{ + int iStyleOR = 0; + int iFontIndex = MenuFontToHandle(iMenuFont); + + switch (style) + { + case ITEM_TEXTSTYLE_NORMAL: iStyleOR = 0;break; // JK2 normal text + case ITEM_TEXTSTYLE_BLINK: iStyleOR = STYLE_BLINK;break; // JK2 fast blinking + case ITEM_TEXTSTYLE_PULSE: iStyleOR = STYLE_BLINK;break; // JK2 slow pulsing + case ITEM_TEXTSTYLE_SHADOWED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + case ITEM_TEXTSTYLE_OUTLINED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + case ITEM_TEXTSTYLE_OUTLINESHADOWED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + case ITEM_TEXTSTYLE_SHADOWEDMORE: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + } + + trap_R_Font_DrawString( x, // int ox + y, // int oy + text, // const char *text + color, // paletteRGBA_c c + iStyleOR | iFontIndex, // const int iFontHandle + !limit?-1:limit, // iCharLimit (-1 = none) + scale // const float scale = 1.0f + ); +} + +/* +qboolean CG_WorldCoordToScreenCoord(vec3_t worldCoord, int *x, int *y) + + Take any world coord and convert it to a 2D virtual 640x480 screen coord +*/ +/* +qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y) +{ + int xcenter, ycenter; + vec3_t local, transformed; + +// xcenter = cg->refdef.width / 2;//gives screen coords adjusted for resolution +// ycenter = cg->refdef.height / 2;//gives screen coords adjusted for resolution + + //NOTE: did it this way because most draw functions expect virtual 640x480 coords + // and adjust them for current resolution + xcenter = 640 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn + ycenter = 480 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn + + VectorSubtract (worldCoord, cg->refdef.vieworg, local); + + transformed[0] = DotProduct(local,vright); + transformed[1] = DotProduct(local,vup); + transformed[2] = DotProduct(local,vfwd); + + // Make sure Z is not negative. + if(transformed[2] < 0.01) + { + return qfalse; + } + // Simple convert to screen coords. + float xzi = xcenter / transformed[2] * (90.0/cg->refdef.fov_x); + float yzi = ycenter / transformed[2] * (90.0/cg->refdef.fov_y); + + *x = xcenter + xzi * transformed[0]; + *y = ycenter - yzi * transformed[1]; + + return qtrue; +} + +qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y ) +{ + float xF, yF; + qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF ); + *x = (int)xF; + *y = (int)yF; + return retVal; +} +*/ + +/* +================ +CG_DrawZoomMask + +================ +*/ +static void CG_DrawZoomMask( void ) +{ + vec4_t color1; + float level; + static qboolean flip = qtrue; + +// int ammo = cg_entities[0].gent->client->ps.ammo[weaponData[cent->currentState.weapon].ammoIndex]; + float cx, cy; +// int val[5]; + float max, fi; + + // Check for Binocular specific zooming since we'll want to render different bits in each case + if ( cg->predictedPlayerState.zoomMode == 2 ) + { + assert( 0 ); + return; +/* + int val, i; + float off; + + // zoom level + level = (float)(80.0f - cg->predictedPlayerState.zoomFov) / 80.0f; + + // ...so we'll clamp it + if ( level < 0.0f ) + { + level = 0.0f; + } + else if ( level > 1.0f ) + { + level = 1.0f; + } + + // Using a magic number to convert the zoom level to scale amount + level *= 162.0f; + + // draw blue tinted distortion mask, trying to make it as small as is necessary to fill in the viewable area + trap_R_SetColor( colorTable[CT_WHITE] ); + CG_DrawPic( 34, 48, 570, 362, cgs.media.binocularStatic ); + + // Black out the area behind the numbers + trap_R_SetColor( colorTable[CT_BLACK]); + CG_DrawPic( 212, 367, 200, 40, cgs.media.whiteShader ); + + // Numbers should be kind of greenish + color1[0] = 0.2f; + color1[1] = 0.4f; + color1[2] = 0.2f; + color1[3] = 0.3f; + trap_R_SetColor( color1 ); + + // Draw scrolling numbers, use intervals 10 units apart--sorry, this section of code is just kind of hacked + // up with a bunch of magic numbers..... + val = ((int)((cg->refdef.viewangles[YAW] + 180) / 10)) * 10; + off = (cg->refdef.viewangles[YAW] + 180) - val; + + for ( i = -10; i < 30; i += 10 ) + { + val -= 10; + + if ( val < 0 ) + { + val += 360; + } + + // we only want to draw the very far left one some of the time, if it's too far to the left it will + // poke outside the mask. + if (( off > 3.0f && i == -10 ) || i > -10 ) + { + // draw the value, but add 200 just to bump the range up...arbitrary, so change it if you like + CG_DrawNumField( 155 + i * 10 + off * 10, 374, 3, val + 200, 24, 14, NUM_FONT_CHUNKY, qtrue ); + CG_DrawPic( 245 + (i-1) * 10 + off * 10, 376, 6, 6, cgs.media.whiteShader ); + } + } + + CG_DrawPic( 212, 367, 200, 28, cgs.media.binocularOverlay ); + + color1[0] = sin( cg->time * 0.01f ) * 0.5f + 0.5f; + color1[0] = color1[0] * color1[0]; + color1[1] = color1[0]; + color1[2] = color1[0]; + color1[3] = 1.0f; + + trap_R_SetColor( color1 ); + + CG_DrawPic( 82, 94, 16, 16, cgs.media.binocularCircle ); + + // Flickery color + color1[0] = 0.7f + crandom() * 0.1f; + color1[1] = 0.8f + crandom() * 0.1f; + color1[2] = 0.7f + crandom() * 0.1f; + color1[3] = 1.0f; + trap_R_SetColor( color1 ); + + CG_DrawPic( 0, 0, 640, 480, cgs.media.binocularMask ); + + CG_DrawPic( 4, 282 - level, 16, 16, cgs.media.binocularArrow ); + + // The top triangle bit randomly flips + if ( flip ) + { + CG_DrawPic( 330, 60, -26, -30, cgs.media.binocularTri ); + } + else + { + CG_DrawPic( 307, 40, 26, 30, cgs.media.binocularTri ); + } + + if ( random() > 0.98f && ( cg->time & 1024 )) + { + flip = !flip; + } +*/ + } + else if ( cg->predictedPlayerState.zoomMode) + { +#ifdef _XBOX + float mscale = 1.0f; + float moffy = 0.0f; + float moffx = 0.0f; + + if (ClientManager::splitScreenMode == qtrue) + { + mscale = .5f; + if (ClientManager::ActiveClientNum() == 1) + moffy = 240.f; + if(cg->widescreen) + moffx += 720 / 4; + else + moffx += 640 / 4; + trap_R_SetColor(colorTable[CT_BLACK]); + + if(cg->widescreen) { + CG_DrawPic( 0, moffy, moffx + 40, 240, cgs.media.whiteShader ); + CG_DrawPic( 720-moffx - 40, moffy, moffx + 40, 240, cgs.media.whiteShader ); + } + else { + CG_DrawPic( 0, moffy, moffx, 240, cgs.media.whiteShader ); + CG_DrawPic( 640-moffx, moffy, moffx, 240, cgs.media.whiteShader ); + } + + } + else { + if(cg->widescreen) { + trap_R_SetColor(colorTable[CT_BLACK]); + CG_DrawPic( 0, 0, 40, 480, cgs.media.whiteShader ); + CG_DrawPic( 720 - 40, 0, 40, 480, cgs.media.whiteShader ); + } + } + +#endif + + // disruptor zoom mode +#ifdef _XBOX + level = (float)(50.0f - zoomFov[ClientManager::ActiveClientNum()]) / 50.0f; +#else + level = (float)(50.0f - zoomFov) / 50.0f;//(float)(80.0f - zoomFov) / 80.0f; +#endif + + // ...so we'll clamp it + if ( level < 0.0f ) + { + level = 0.0f; + } + else if ( level > 1.0f ) + { + level = 1.0f; + } + + // Using a magic number to convert the zoom level to a rotation amount that correlates more or less with the zoom artwork. + level *= 1.84f; + + // Draw target mask + trap_R_SetColor( colorTable[CT_WHITE] ); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + if(cg->widescreen) + CG_DrawPic( 200, moffy, 320, 240, cgs.media.disruptorMask ); + else + CG_DrawPic( 160, moffy, 320, 240, cgs.media.disruptorMask ); + } + else + { + if(cg->widescreen) + CG_DrawPic( 40, 0, 640, 480, cgs.media.disruptorMask ); + else +#endif + CG_DrawPic( 0, 0, 640, 480, cgs.media.disruptorMask ); +#ifdef _XBOX + } +#endif + + //FFFX_START( fffx_StartConst ); + // apparently 99.0f is the full zoom level + if ( level >= 1.72f ) + { + // Fully zoomed, so make the rotating insert pulse + color1[0] = 1.0f; + color1[1] = 1.0f; + color1[2] = 1.0f; + color1[3] = 0.7f + sin( cg->time * 0.01f ) * 0.3f; + + trap_R_SetColor( color1 ); + } + + // Draw rotating insert +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + if(cg->widescreen) + CG_DrawRotatePic2( 360, 120 + moffy, 320, 240, -level, cgs.media.disruptorInsert ); + else + CG_DrawRotatePic2( 320, 120 + moffy, 320, 240, -level, cgs.media.disruptorInsert ); + } + else { + if(cg->widescreen) + CG_DrawRotatePic2( 360, 240, 640, 480, -level, cgs.media.disruptorInsert ); + else +#endif + CG_DrawRotatePic2( 320, 240, 640, 480, -level, cgs.media.disruptorInsert ); +#ifdef _XBOX + } +#endif + + // Increase the light levels under the center of the target +// CG_DrawPic( 198, 118, 246, 246, cgs.media.disruptorLight ); + + // weirdness.....converting ammo to a base five number scale just to be geeky. +/* val[0] = ammo % 5; + val[1] = (ammo / 5) % 5; + val[2] = (ammo / 25) % 5; + val[3] = (ammo / 125) % 5; + val[4] = (ammo / 625) % 5; + + color1[0] = 0.2f; + color1[1] = 0.55f + crandom() * 0.1f; + color1[2] = 0.5f + crandom() * 0.1f; + color1[3] = 1.0f; + trap_R_SetColor( color1 ); + + for ( int t = 0; t < 5; t++ ) + { + cx = 320 + sin( (t*10+45)/57.296f ) * 192; + cy = 240 + cos( (t*10+45)/57.296f ) * 192; + + CG_DrawRotatePic2( cx, cy, 24, 38, 45 - t * 10, trap_R_RegisterShader( va("gfx/2d/char%d",val[4-t] ))); + } +*/ + //max = ( cg_entities[0].gent->health / 100.0f ); + + + if ( (cg->snap->ps.eFlags & EF_DOUBLE_AMMO) ) + { + max = cg->snap->ps.ammo[weaponData[WP_DISRUPTOR].ammoIndex] / ((float)ammoData[weaponData[WP_DISRUPTOR].ammoIndex].max*2.0f); + } + else + { + max = cg->snap->ps.ammo[weaponData[WP_DISRUPTOR].ammoIndex] / (float)ammoData[weaponData[WP_DISRUPTOR].ammoIndex].max; + } + if ( max > 1.0f ) + { + max = 1.0f; + } + + color1[0] = (1.0f - max) * 2.0f; + color1[1] = max * 1.5f; + color1[2] = 0.0f; + color1[3] = 1.0f; + + // If we are low on health, make us flash + if ( max < 0.15f && ( cg->time & 512 )) + { + VectorClear( color1 ); + } + + if ( color1[0] > 1.0f ) + { + color1[0] = 1.0f; + } + + if ( color1[1] > 1.0f ) + { + color1[1] = 1.0f; + } + + trap_R_SetColor( color1 ); + + max *= 58.0f; + + for (fi = 18.5f; fi <= 18.5f + max; fi+= 3 ) // going from 15 to 45 degrees, with 5 degree increments + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cg->widescreen) { + cx = (360 + sin( (fi+90.0f)/57.296f ) * 190) * mscale + moffx; + cy = (240 + cos( (fi+90.0f)/57.296f ) * 190) * mscale + moffy; + } + else { + cx = (320 + sin( (fi+90.0f)/57.296f ) * 190) * mscale + moffx; + cy = (240 + cos( (fi+90.0f)/57.296f ) * 190) * mscale + moffy; + } + + CG_DrawRotatePic2( cx, cy, 6, 12, (90 - fi)/57.296f, cgs.media.disruptorInsertTick ); + } + else { +#endif + if(cg->widescreen) { + cx = 360 + sin( (fi+90.0f)/57.296f ) * 190; + cy = 240 + cos( (fi+90.0f)/57.296f ) * 190; + } + else { + cx = 320 + sin( (fi+90.0f)/57.296f ) * 190; + cy = 240 + cos( (fi+90.0f)/57.296f ) * 190; + } + + CG_DrawRotatePic2( cx, cy, 12, 24, (90 - fi)/57.296f, cgs.media.disruptorInsertTick ); +#ifdef _XBOX + } +#endif + } + + if ( cg->predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT ) + { + trap_R_SetColor( colorTable[CT_WHITE] ); + + // draw the charge level + max = ( cg->time - cg->predictedPlayerState.weaponChargeTime ) / ( 50.0f * 30.0f ); // bad hardcodedness 50 is disruptor charge unit and 30 is max charge units allowed. + + if ( max > 1.0f ) + { + max = 1.0f; + } + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cg->widescreen) + trap_R_DrawStretchPic(297 * mscale + moffx, 435 * mscale + moffy, 134*max*mscale, 34*mscale, 0, 0, max, 1, cgs.media.disruptorChargeShader); + else + trap_R_DrawStretchPic(257 * mscale + moffx, 435 * mscale + moffy, 134*max*mscale, 34*mscale, 0, 0, max, 1, cgs.media.disruptorChargeShader); + } + else { +#endif + if(cg->widescreen) + trap_R_DrawStretchPic(297, 435, 134*max, 34, 0, 0, max, 1, cgs.media.disruptorChargeShader); + else + trap_R_DrawStretchPic(257, 435, 134*max, 34, 0, 0, max, 1, cgs.media.disruptorChargeShader); +#ifdef _XBOX + } +#endif + } +// trap_R_SetColor( colorTable[CT_WHITE] ); +// CG_DrawPic( 0, 0, 640, 480, cgs.media.disruptorMask ); + + } + else + { + FFFX_START( fffx_StopConst ); + } +} + + +/* +================ +CG_Draw3DModel + +================ +*/ +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, void *ghoul2, int g2radius, qhandle_t skin, vec3_t origin, vec3_t angles ) { + refdef_t refdef; + refEntity_t ent; + + if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { + return; + } + + memset( &refdef, 0, sizeof( refdef ) ); + + memset( &ent, 0, sizeof( ent ) ); + AnglesToAxis( angles, ent.axis ); + VectorCopy( origin, ent.origin ); + ent.hModel = model; + ent.ghoul2 = ghoul2; + ent.radius = g2radius; + ent.customSkin = skin; + ent.renderfx = RF_NOSHADOW; // no stencil shadows + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + refdef.fov_x = 30; + refdef.fov_y = 30; + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + refdef.time = cg->time; + + trap_R_ClearScene(); + trap_R_AddRefEntityToScene( &ent ); + trap_R_RenderScene( &refdef ); +} + +/* +================ +CG_DrawHead + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) +{ + clientInfo_t *ci; + + if (clientNum >= MAX_CLIENTS) + { //npc? + return; + } + + ci = &cgs.clientinfo[ clientNum ]; + + CG_DrawPic( x, y, w, h, ci->modelIcon ); + + // if they are deferred, draw a cross out + if ( ci->deferred ) + { + CG_DrawPic( x, y, w, h, cgs.media.deferShader ); + } +} + +/* +================ +CG_DrawFlagModel + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ) { + qhandle_t cm; + float len; + vec3_t origin, angles; + vec3_t mins, maxs; + qhandle_t handle; + + if ( !force2D && cg_draw3dIcons.integer ) { + + VectorClear( angles ); + + cm = cgs.media.redFlagModel; + + // offset the origin y and z to center the flag + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the flag nearly fills the box + // assume heads are taller than wide + len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + angles[YAW] = 60 * sin( cg->time / 2000.0 );; + + if( team == TEAM_RED ) { + handle = cgs.media.redFlagModel; + } else if( team == TEAM_BLUE ) { + handle = cgs.media.blueFlagModel; + } else if( team == TEAM_FREE ) { + handle = 0;//cgs.media.neutralFlagModel; + } else { + return; + } + CG_Draw3DModel( x, y, w, h, handle, NULL, 0, 0, origin, angles ); + } else if ( cg_drawIcons.integer ) { + gitem_t *item; + + if( team == TEAM_RED ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + } else if( team == TEAM_BLUE ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + } else if( team == TEAM_FREE ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + } else { + return; + } + if (item) { + CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon ); + } + } +} + +/* +================ +DrawAmmo +================ +*/ +void DrawAmmo() +{ + int x, y; + +#ifdef _XBOX + if(cg->widescreen) + x = 720 - 80; + else +#endif + x = SCREEN_WIDTH-80; + y = SCREEN_HEIGHT-80; + +} + + + +/* +================ +CG_DrawHealth +================ +*/ +void CG_DrawHealth( menuDef_t *menuHUD ) +{ + vec4_t calcColor; + playerState_t *ps; + int healthAmt; + int i,currValue,inc; + itemDef_t *focusItem; + float percent; + + // Can we find the menu? + if (!menuHUD) + { + return; + } + + ps = &cg->snap->ps; + + // What's the health? + healthAmt = ps->stats[STAT_HEALTH]; + if (healthAmt > ps->stats[STAT_MAX_HEALTH]) + { + healthAmt = ps->stats[STAT_MAX_HEALTH]; + } + + + inc = (float) ps->stats[STAT_MAX_HEALTH] / MAX_HUD_TICS; + currValue = healthAmt; + + // Print the health tics, fading out the one which is partial health + for (i=(MAX_HUD_TICS-1);i>=0;i--) + { + focusItem = Menu_FindItemByName(menuHUD, healthTicName[i]); + + if (!focusItem) // This is bad + { + continue; + } + + memcpy(calcColor, hudTintColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + percent = (float) currValue / inc; + calcColor[3] *= percent; // Fade it out + } + + trap_R_SetColor( calcColor); + +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = focusItem->window.rect.x; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + + if(i == 0) { + wx -= 6; + wy += 21; + } + else if(i == 1) { + wx -= 9; + wy += 19; + } + else if(i == 2) { + wx -= 12; + wy += 16; + } + else if(i == 3) { + wx -= 13; + wy += 12; + } + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif // _XBOX + + currValue -= inc; + } + + // Print the mueric amount + focusItem = Menu_FindItemByName(menuHUD, "healthamount"); + if (focusItem) + { +#ifdef _XBOX + int wx = focusItem->window.rect.x; + int wy = focusItem->window.rect.y; + + if(ClientManager::splitScreenMode == qtrue) { + if(ClientManager::ActiveClientNum() == 0) { + wy -= 220; + } + + wy += 4; + wx -= 15; + } + + // Print black border around the numbers + float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + trap_R_SetColor( black ); + + CG_DrawNumField ( + wx, + wy, + 3, + ps->stats[STAT_HEALTH], + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qtrue); +#endif + + // Print health amount + trap_R_SetColor( focusItem->window.foreColor ); + +#ifdef _XBOX + CG_DrawNumField ( + wx, + wy, + 3, + ps->stats[STAT_HEALTH], + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); +#else + CG_DrawNumField ( + focusItem->window.rect.x, + focusItem->window.rect.y, + 3, + ps->stats[STAT_HEALTH], + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); +#endif + } + +} + +/* +================ +CG_DrawArmor +================ +*/ +void CG_DrawArmor( menuDef_t *menuHUD ) +{ + vec4_t calcColor; + playerState_t *ps; + int armor, maxArmor; + itemDef_t *focusItem; + float percent,quarterArmor; + int i,currValue,inc; + + //ps = &cg->snap->ps; + ps = &cg->predictedPlayerState; + + // Can we find the menu? + if (!menuHUD) + { + return; + } + + armor = ps->stats[STAT_ARMOR]; + maxArmor = ps->stats[STAT_MAX_HEALTH]; + + if (armor> maxArmor) + { + armor = maxArmor; + } + + currValue = armor; + inc = (float) maxArmor / MAX_HUD_TICS; + + memcpy(calcColor, hudTintColor, sizeof(vec4_t)); + for (i=(MAX_HUD_TICS-1);i>=0;i--) + { + focusItem = Menu_FindItemByName(menuHUD, armorTicName[i]); + + if (!focusItem) // This is bad + { + continue; + } + + memcpy(calcColor, hudTintColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + percent = (float) currValue / inc; + calcColor[3] *= percent; + } + + trap_R_SetColor( calcColor); + + if ((i==(MAX_HUD_TICS-1)) && (currValue < inc)) + { + if (cg->HUDArmorFlag) + { +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = focusItem->window.rect.x; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + + if(i == 0) { + wx -= 2; + wy += 15; + } + else if(i == 1) { + wx -= 9; + wy += 13; + } + else if(i == 2) { + wx -= 13; + wy += 8; + } + else if(i == 3) { + wx -= 15; + wy += 2; + } + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif // _XBOX + } + } + else + { +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = focusItem->window.rect.x; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + + if(i == 0) { + wx -= 2; + wy += 15; + } + else if(i == 1) { + wx -= 9; + wy += 13; + } + else if(i == 2) { + wx -= 13; + wy += 8; + } + else if(i == 3) { + wx -= 15; + wy += 2; + } + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif + } + + currValue -= inc; + } + + focusItem = Menu_FindItemByName(menuHUD, "armoramount"); + + if (focusItem) + { +#ifdef _XBOX + int wx = focusItem->window.rect.x; + int wy = focusItem->window.rect.y; + + if(ClientManager::splitScreenMode == qtrue) { + if(ClientManager::ActiveClientNum() == 0) { + wy -= 220; + } + + wy += 4; + wx -= 18; + } + + // Print a black border around the numbers + float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + trap_R_SetColor( black ); + + CG_DrawNumField ( + wx, + wy, + 3, + armor, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qtrue); +#endif + + // Print armor amount + trap_R_SetColor( focusItem->window.foreColor ); + +#ifdef _XBOX + CG_DrawNumField ( + wx, + wy, + 3, + armor, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); +#else + CG_DrawNumField ( + focusItem->window.rect.x, + focusItem->window.rect.y, + 3, + armor, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); +#endif + } + + // If armor is low, flash a graphic to warn the player + if (armor) // Is there armor? Draw the HUD Armor TIC + { + quarterArmor = (float) (ps->stats[STAT_MAX_HEALTH] / 4.0f); + + // Make tic flash if armor is at 25% of full armor + if (ps->stats[STAT_ARMOR] < quarterArmor) // Do whatever the flash timer says + { + if (cg->HUDTickFlashTime < cg->time) // Flip at the same time + { + cg->HUDTickFlashTime = cg->time + 400; + if (cg->HUDArmorFlag) + { + cg->HUDArmorFlag = qfalse; + } + else + { + cg->HUDArmorFlag = qtrue; + } + } + } + else + { + cg->HUDArmorFlag=qtrue; + } + } + else // No armor? Don't show it. + { + cg->HUDArmorFlag=qfalse; + } + +} + +/* +================ +CG_DrawSaberStyle + +If the weapon is a light saber (which needs no ammo) then draw a graphic showing +the saber style (fast, medium, strong) +================ +*/ +static void CG_DrawSaberStyle( centity_t *cent, menuDef_t *menuHUD) +{ + itemDef_t *focusItem; + int rectx; + + if (!cent->currentState.weapon ) // We don't have a weapon right now + { + return; + } + + if ( cent->currentState.weapon != WP_SABER ) + { + return; + } + + // Can we find the menu? + if (!menuHUD) + { + return; + } + + + // draw the current saber style in this window + switch ( cg->predictedPlayerState.fd.saberDrawAnimLevel ) + { + case 1://FORCE_LEVEL_1: + case 5://FORCE_LEVEL_5://Tavion + + focusItem = Menu_FindItemByName(menuHUD, "saberstyle_fast"); + + if (focusItem) + { + rectx = focusItem->window.rect.x; + if(cg->widescreen) + rectx += 80; + + trap_R_SetColor( hudTintColor ); + +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = rectx; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + + wx += 23; + wy += 0; + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + rectx, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif + } + + break; + case 2://FORCE_LEVEL_2: + case 6://SS_DUAL + case 7://SS_STAFF + focusItem = Menu_FindItemByName(menuHUD, "saberstyle_medium"); + + if (focusItem) + { + rectx = focusItem->window.rect.x; + if(cg->widescreen) + rectx += 80; + + trap_R_SetColor( hudTintColor ); + +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = rectx; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + + wx += 15; + wy += 11; + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + rectx, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif + } + break; + case 3://FORCE_LEVEL_3: + case 4://FORCE_LEVEL_4://Desann + focusItem = Menu_FindItemByName(menuHUD, "saberstyle_strong"); + + if (focusItem) + { + rectx = focusItem->window.rect.x; + if(cg->widescreen) + rectx += 80; + + trap_R_SetColor( hudTintColor ); + +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = rectx; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + + wx += 8; + wy += 20; + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + rectx, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif + } + break; + } + +} + +/* +================ +CG_DrawAmmo +================ +*/ +static void CG_DrawAmmo( centity_t *cent,menuDef_t *menuHUD) +{ + playerState_t *ps; + int i; + vec4_t calcColor; + float value,inc = 0.0f,percent; + itemDef_t *focusItem; + int rectx; + + ps = &cg->snap->ps; + + // Can we find the menu? + if (!menuHUD) + { + return; + } + + if (!cent->currentState.weapon ) // We don't have a weapon right now + { + return; + } + + value = ps->ammo[weaponData[cent->currentState.weapon].ammoIndex]; + if (value < 0) // No ammo + { + return; + } + + focusItem = Menu_FindItemByName(menuHUD, "ammoamount"); + trap_R_SetColor( hudTintColor ); + + if (weaponData[cent->currentState.weapon].energyPerShot == 0 && + weaponData[cent->currentState.weapon].altEnergyPerShot == 0) + { //just draw "infinite" + inc = 8 / MAX_HUD_TICS; + value = 8; + + focusItem = Menu_FindItemByName(menuHUD, "ammoinfinite"); + trap_R_SetColor( hudTintColor ); + if (focusItem) + { +#ifdef _XBOX + int wx = focusItem->window.rect.x; + if(cg->widescreen) + wx += 80; + + int wy = focusItem->window.rect.y; + + if(ClientManager::splitScreenMode == qtrue) { + if(ClientManager::ActiveClientNum() == 0) { + wy -= 220; + } + + wy += 4; + wx += 18; + } + + UI_DrawProportionalString(wx, + wy, + "--", + NUM_FONT_SMALL, + focusItem->window.foreColor); +#else + UI_DrawProportionalString(focusItem->window.rect.x, focusItem->window.rect.y, "--", NUM_FONT_SMALL, focusItem->window.foreColor); +#endif + } + } + else + { + focusItem = Menu_FindItemByName(menuHUD, "ammoamount"); + trap_R_SetColor( hudTintColor ); + if (focusItem) + { + + if ( (cent->currentState.eFlags & EF_DOUBLE_AMMO) ) + { + inc = (float) (ammoData[weaponData[cent->currentState.weapon].ammoIndex].max*2.0f) / MAX_HUD_TICS; + } + else + { + inc = (float) ammoData[weaponData[cent->currentState.weapon].ammoIndex].max / MAX_HUD_TICS; + } + value =ps->ammo[weaponData[cent->currentState.weapon].ammoIndex]; + +#ifdef _XBOX + int wx = focusItem->window.rect.x; + if(cg->widescreen) + wx += 80; + + int wy = focusItem->window.rect.y; + + if(ClientManager::splitScreenMode == qtrue) { + if(ClientManager::ActiveClientNum() == 0) { + wy -= 220; + } + + wy += 4; + wx += 16; + } + + // Print a black border around the numbers + float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + trap_R_SetColor( black ); + + CG_DrawNumField ( + wx, + wy, + 3, + value, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qtrue); + + trap_R_SetColor( hudTintColor ); +#endif + +#ifdef _XBOX + CG_DrawNumField ( + wx, + wy, + 3, + value, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); +#else + CG_DrawNumField ( + focusItem->window.rect.x, + focusItem->window.rect.y, + 3, + value, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); +#endif + } + } + + // Draw tics + for (i=MAX_HUD_TICS-1;i>=0;i--) + { + focusItem = Menu_FindItemByName(menuHUD, ammoTicName[i]); + + if (!focusItem) + { + continue; + } + + memcpy(calcColor, hudTintColor, sizeof(vec4_t)); + + if ( value <= 0 ) // done + { + break; + } + else if (value < inc) // partial tic + { + percent = value / inc; + calcColor[3] = percent; + } + + trap_R_SetColor( calcColor); + +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = focusItem->window.rect.x; + if(cg->widescreen) + wx += 80; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + + if(i == 0) { + wx += 14; + wy += 13; + } + else if(i == 1) { + wx += 17; + wy += 11; + } + else if(i == 2) { + wx += 20; + wy += 8; + } + else if(i == 3) { + wx += 21; + wy += 4; + } + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif + + value -= inc; + } + +} + +/* +================ +CG_DrawForcePower +================ +*/ +void CG_DrawForcePower( menuDef_t *menuHUD ) +{ + int i; + vec4_t calcColor; + float value,inc,percent; + itemDef_t *focusItem; + const int maxForcePower = 100; + qboolean flash=qfalse; + int rectx; + + // Can we find the menu? + if (!menuHUD) + { + return; + } + + // Make the hud flash by setting forceHUDTotalFlashTime above cg->time + if (cg->forceHUDTotalFlashTime > cg->time ) + { + flash = qtrue; + if (cg->forceHUDNextFlashTime < cg->time) + { + cg->forceHUDNextFlashTime = cg->time + 400; + trap_S_StartSound (NULL, 0, CHAN_LOCAL, cgs.media.noforceSound ); + + if (cg->forceHUDActive) + { + cg->forceHUDActive = qfalse; + } + else + { + cg->forceHUDActive = qtrue; + } + + } + } + else // turn HUD back on if it had just finished flashing time. + { + cg->forceHUDNextFlashTime = 0; + cg->forceHUDActive = qtrue; + } + +// if (!cg->forceHUDActive) +// { +// return; +// } + + inc = (float) maxForcePower / MAX_HUD_TICS; + value = cg->snap->ps.fd.forcePower; + + for (i=MAX_HUD_TICS-1;i>=0;i--) + { + focusItem = Menu_FindItemByName(menuHUD, forceTicName[i]); + + if (!focusItem) + { + continue; + } + +// memcpy(calcColor, hudTintColor, sizeof(vec4_t)); + + if ( value <= 0 ) // done + { + break; + } + else if (value < inc) // partial tic + { + if (flash) + { + memcpy(calcColor, colorTable[CT_RED], sizeof(vec4_t)); + } + else + { + memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); + } + + percent = value / inc; + calcColor[3] = percent; + } + else + { + if (flash) + { + memcpy(calcColor, colorTable[CT_RED], sizeof(vec4_t)); + } + else + { + memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); + } + } + + trap_R_SetColor( calcColor); + + rectx = focusItem->window.rect.x; + if(cg->widescreen) + rectx += 80; +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = rectx; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + + if(i == 0) { + wx += 17; + wy += 13; + } + else if(i == 1) { + wx += 22; + wy += 11; + } + else if(i == 2) { + wx += 26; + wy += 8; + } + else if(i == 3) { + wx += 29; + wy += 1; + } + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + rectx, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif + + value -= inc; + } + + focusItem = Menu_FindItemByName(menuHUD, "forceamount"); + + if (focusItem) + { + rectx = focusItem->window.rect.x; + if(cg->widescreen) + rectx += 80; + + // Print force amount + +#ifdef _XBOX + int wx = rectx; + int wy = focusItem->window.rect.y; + + if(ClientManager::splitScreenMode == qtrue) { + if(ClientManager::ActiveClientNum() == 0) { + wy -= 220; + } + + wy += 4; + wx += 18; + } + + // Print a black border around the numbers + float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + trap_R_SetColor( black ); + + CG_DrawNumField ( + wx, + wy, + 3, + cg->snap->ps.fd.forcePower, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qtrue); +#endif + + trap_R_SetColor( focusItem->window.foreColor ); + +#ifdef _XBOX + CG_DrawNumField ( + wx, + wy, + 3, + cg->snap->ps.fd.forcePower, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); +#else + CG_DrawNumField ( + rectx, + focusItem->window.rect.y, + 3, + cg->snap->ps.fd.forcePower, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); +#endif + } +} + +/* +================ +CG_DrawHUD +================ +*/ +void CG_DrawHUD(centity_t *cent) +{ + menuDef_t *menuHUD = NULL; + itemDef_t *focusItem = NULL; + const char *scoreStr = NULL; + int scoreBias; + char scoreBiasStr[16]; + +#ifdef _XBOX + float blackBar[4] = { 0.0429f, 0.078f, 1.0f, 0.4f }; +#endif + + if (cg_hudFiles.integer) + { + int x = 0; + int y = SCREEN_HEIGHT-80; + char ammoString[64]; + int weapX = x; + + UI_DrawProportionalString( x+16, y+40, va( "%i", cg->snap->ps.stats[STAT_HEALTH] ), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_RED] ); + + UI_DrawProportionalString( x+18+14, y+40+14, va( "%i", cg->snap->ps.stats[STAT_ARMOR] ), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_GREEN] ); + + if (cg->snap->ps.weapon == WP_SABER) + { + if (cg->snap->ps.fd.saberDrawAnimLevel == SS_DUAL) + { + Com_sprintf(ammoString, sizeof(ammoString), "AKIMBO"); + weapX += 16; + } + else if (cg->snap->ps.fd.saberDrawAnimLevel == SS_STAFF) + { + Com_sprintf(ammoString, sizeof(ammoString), "STAFF"); + weapX += 16; + } + else if (cg->snap->ps.fd.saberDrawAnimLevel == FORCE_LEVEL_3) + { + Com_sprintf(ammoString, sizeof(ammoString), "STRONG"); + weapX += 16; + } + else if (cg->snap->ps.fd.saberDrawAnimLevel == FORCE_LEVEL_2) + { + Com_sprintf(ammoString, sizeof(ammoString), "MEDIUM"); + weapX += 16; + } + else + { + Com_sprintf(ammoString, sizeof(ammoString), "FAST"); + } + } + else + { + Com_sprintf(ammoString, sizeof(ammoString), "%i", cg->snap->ps.ammo[weaponData[cent->currentState.weapon].ammoIndex]); + } + +#ifdef _XBOX + if(cg->widescreen) + UI_DrawProportionalString( 720-(weapX+16+32), y+40, va( "%s", ammoString ), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_ORANGE] ); + else +#endif + UI_DrawProportionalString( SCREEN_WIDTH-(weapX+16+32), y+40, va( "%s", ammoString ), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_ORANGE] ); + +#ifdef _XBOX + if(cg->widescreen) + UI_DrawProportionalString( 720-(x+18+14+32), y+40+14, va( "%i", cg->snap->ps.fd.forcePower), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_ICON_BLUE] ); + else +#endif + UI_DrawProportionalString( SCREEN_WIDTH-(x+18+14+32), y+40+14, va( "%i", cg->snap->ps.fd.forcePower), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_ICON_BLUE] ); + + return; + } + + if (cgs.gametype >= GT_TEAM && cgs.gametype != GT_SIEGE) + { // tint the hud items based on team + if (cg->snap->ps.persistant[PERS_TEAM] == TEAM_RED ) + hudTintColor = redhudtint; + else if (cg->snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) + hudTintColor = bluehudtint; + else // If we're not on a team for whatever reason, leave things as they are. + hudTintColor = colorTable[CT_WHITE]; + } + else + { // tint the hud items white (dont' tint) + hudTintColor = colorTable[CT_WHITE]; + } + + // Draw the left HUD + menuHUD = Menus_FindByName("lefthud"); + if (menuHUD) + { + itemDef_t *focusItem; + + // Print scanline + focusItem = Menu_FindItemByName(menuHUD, "scanline"); + if (focusItem) + { + trap_R_SetColor( hudTintColor ); +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = focusItem->window.rect.x; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif + } + + // Print frame + focusItem = Menu_FindItemByName(menuHUD, "frame"); + if (focusItem) + { + trap_R_SetColor( hudTintColor ); +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = focusItem->window.rect.x; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + + lscalex = focusItem->window.rect.w - ww; + lscaley = focusItem->window.rect.h - wh; + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif + } + +#ifdef _XBOX + // Draw the friend/game invitation icons + if (XBL_F_FriendNotice() && (focusItem = Menu_FindItemByName(menuHUD, "friendInvite"))) + { + trap_R_SetColor( hudTintColor ); + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } + + if (XBL_F_GameNotice() && (focusItem = Menu_FindItemByName(menuHUD, "gameInvite"))) + { + trap_R_SetColor( hudTintColor ); + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } +#endif + + if (cg->predictedPlayerState.pm_type != PM_SPECTATOR) + { +#ifdef _XBOX + // Draw the blue tinted bar behind the numbers + int yoff = 0; + + if(ClientManager::splitScreenMode == qtrue) { + if(ClientManager::ActiveClientNum() == 0) + yoff = -220; + + CG_FillRect(93, 433 + yoff, 50, 14, blackBar); + } + else + CG_FillRect(105, 428, 54, 16, blackBar); +#endif + CG_DrawArmor(menuHUD); + CG_DrawHealth(menuHUD); + } + } + else + { + //CG_Error("CG_ChatBox_ArrayInsert: unable to locate HUD menu file "); + } + + //scoreStr = va("Score: %i", cgs.clientinfo[cg->snap->ps.clientNum].score); + if ( cgs.gametype == GT_DUEL ) + {//A duel that requires more than one kill to knock the current enemy back to the queue + //show current kills out of how many needed + scoreStr = va("%s: %i/%i", CG_GetStringEdString("MP_INGAME", "SCORE"), cg->snap->ps.persistant[PERS_SCORE], cgs.fraglimit); + } + else if (0 && cgs.gametype < GT_TEAM ) + { // This is a teamless mode, draw the score bias. + scoreBias = cg->snap->ps.persistant[PERS_SCORE] - cgs.scores1; + if (scoreBias == 0) + { // We are the leader! + if (cgs.scores2 <= 0) + { // Nobody to be ahead of yet. + Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), ""); + } + else + { + scoreBias = cg->snap->ps.persistant[PERS_SCORE] - cgs.scores2; + if (scoreBias == 0) + { + Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (Tie)"); + } + else + { + Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (+%d)", scoreBias); + } + } + } + else // if (scoreBias < 0) + { // We are behind! + Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (%d)", scoreBias); + } + scoreStr = va("%s: %i%s", CG_GetStringEdString("MP_INGAME", "SCORE"), cg->snap->ps.persistant[PERS_SCORE], scoreBiasStr); + } + else + { // Don't draw a bias. + scoreStr = va("%s: %i", CG_GetStringEdString("MP_INGAME", "SCORE"), cg->snap->ps.persistant[PERS_SCORE]); + } + + menuHUD = Menus_FindByName("righthud"); + + int rectx; + if (menuHUD) + { + if (cgs.gametype != GT_POWERDUEL) + { + focusItem = Menu_FindItemByName(menuHUD, "score_line"); + if (focusItem) + { + if(cg->widescreen) + rectx = focusItem->window.rect.x + 80; + else + rectx = focusItem->window.rect.x; +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + UI_DrawScaledProportionalString( + rectx, + focusItem->window.rect.y - 220, + scoreStr, + UI_RIGHT|UI_DROPSHADOW, + focusItem->window.foreColor, + 0.7); + } + else { +#endif + UI_DrawScaledProportionalString( + rectx, + focusItem->window.rect.y, + scoreStr, + UI_RIGHT|UI_DROPSHADOW, + focusItem->window.foreColor, + 0.7); +#ifdef _XBOX + } +#endif + } + } + + // Print scanline + focusItem = Menu_FindItemByName(menuHUD, "scanline"); + if (focusItem) + { + if(cg->widescreen) + rectx = focusItem->window.rect.x + 80; + else + rectx = focusItem->window.rect.x; + trap_R_SetColor( hudTintColor ); +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = rectx; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + wx += focusItem->window.rect.w - ww; + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + rectx, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif + } + + focusItem = Menu_FindItemByName(menuHUD, "frame"); + if (focusItem) + { + if(cg->widescreen) + rectx = focusItem->window.rect.x + 80; + else + rectx = focusItem->window.rect.x; + trap_R_SetColor( hudTintColor ); +#ifdef _XBOX + int ww, wh, wx, wy; + ww = focusItem->window.rect.w; + wh = focusItem->window.rect.h; + wy = focusItem->window.rect.y; + wx = rectx; + + if(ClientManager::splitScreenMode == qtrue) { + ww = focusItem->window.rect.w * SPLITSCREEN_HUD_SCALE; + wh = focusItem->window.rect.h * SPLITSCREEN_HUD_SCALE; + + if(ClientManager::ActiveClientNum() == 0) { + wy = focusItem->window.rect.y - SPLITSCREEN_HUD_P1OFFSET; + } + wy += focusItem->window.rect.h - wh; + } + + CG_DrawPic( wx, wy, ww, wh, focusItem->window.background ); +#else + CG_DrawPic( + rectx, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); +#endif + } + +#ifdef _XBOX + int xoff = 0, yoff = 0; + if(cg->widescreen) + xoff = 80; + + if(ClientManager::splitScreenMode == qtrue) { + if(ClientManager::ActiveClientNum() == 0) + yoff = -220; + + CG_FillRect(500 + xoff, 433 + yoff, 50, 14, blackBar); + } + else + CG_FillRect(481 + xoff, 428 + yoff, 54, 16, blackBar); +#endif + + CG_DrawForcePower(menuHUD); + + // Draw ammo tics or saber style + if ( cent->currentState.weapon == WP_SABER ) + { + CG_DrawSaberStyle(cent,menuHUD); + } + else + { + CG_DrawAmmo(cent,menuHUD); + } + } + else + { + //CG_Error("CG_ChatBox_ArrayInsert: unable to locate HUD menu file "); + } +} + +#define MAX_SHOWPOWERS NUM_FORCE_POWERS + +qboolean ForcePower_Valid(int i) +{ + if (i == FP_LEVITATION || + i == FP_SABER_OFFENSE || + i == FP_SABER_DEFENSE || + i == FP_SABERTHROW) + { + return qfalse; + } + + if (cg->snap->ps.fd.forcePowersKnown & (1 << i)) + { + return qtrue; + } + + return qfalse; +} + +/* +=================== +CG_DrawForceSelect +=================== +*/ +#ifdef _XBOX +extern bool CL_ExtendSelectTime(void); +#endif +void CG_DrawForceSelect( void ) +{ + int i; + int count; + int smallIconSize,bigIconSize; + int holdX,x,y,x2,y2,pad,length; + int sideLeftIconCnt,sideRightIconCnt; + int sideMax,holdCount,iconCnt; + int yOffset = 0; + int xOffset = 0; + + + x2 = 0; + y2 = 0; + + // don't display if dead + if ( cg->snap->ps.stats[STAT_HEALTH] <= 0 ) + { + return; + } + + if ((cg->forceSelectTime+WEAPON_SELECT_TIME)time) // Time is up for the HUD to display + { + cg->forceSelect = cg->snap->ps.fd.forcePowerSelected; + return; + } + + if (!cg->snap->ps.fd.forcePowersKnown) + { + return; + } + +#ifdef _XBOX + if(CL_ExtendSelectTime()) { + cg->forceSelectTime = cg->time; + } + + if(cg->widescreen) + xOffset = 40; + + yOffset = -50; +#endif + + // count the number of powers owned + count = 0; + + for (i=0;i < NUM_FORCE_POWERS;++i) + { + if (ForcePower_Valid(i)) + { + count++; + } + } + + if (count == 0) // If no force powers, don't display + { + return; + } + + sideMax = 3; // Max number of icons on the side + + // Calculate how many icons will appear to either side of the center one + holdCount = count - 1; // -1 for the center icon + if (holdCount == 0) // No icons to either side + { + sideLeftIconCnt = 0; + sideRightIconCnt = 0; + } + else if (count > (2*sideMax)) // Go to the max on each side + { + sideLeftIconCnt = sideMax; + sideRightIconCnt = sideMax; + } + else // Less than max, so do the calc + { + sideLeftIconCnt = holdCount/2; + sideRightIconCnt = holdCount - sideLeftIconCnt; + } + + smallIconSize = 30; + bigIconSize = 60; + pad = 12; + + x = 320 + xOffset; + y = 425; + + // Background + length = (sideLeftIconCnt * smallIconSize) + (sideLeftIconCnt*pad) + + bigIconSize + (sideRightIconCnt * smallIconSize) + (sideRightIconCnt*pad) + 12; + + i = BG_ProperForceIndex(cg->forceSelect) - 1; + if (i < 0) + { + i = MAX_SHOWPOWERS; + } + + trap_R_SetColor(NULL); + // Work backwards from current icon + holdX = x - ((bigIconSize/2) + pad + smallIconSize); + for (iconCnt=1;iconCnt<(sideLeftIconCnt+1);i--) + { + if (i < 0) + { + i = MAX_SHOWPOWERS; + } + + if (!ForcePower_Valid(forcePowerSorted[i])) // Does he have this power? + { + continue; + } + + ++iconCnt; // Good icon + + if (cgs.media.forcePowerIcons[forcePowerSorted[i]]) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + CG_DrawPic( holdX, y + yOffset - 220, smallIconSize, smallIconSize, cgs.media.forcePowerIcons[forcePowerSorted[i]] ); + } + else { +#endif + CG_DrawPic( holdX, y + yOffset, smallIconSize, smallIconSize, cgs.media.forcePowerIcons[forcePowerSorted[i]] ); +#ifdef _XBOX + } +#endif + holdX -= (smallIconSize+pad); + } + } + + if (ForcePower_Valid(cg->forceSelect)) + { + // Current Center Icon + if (cgs.media.forcePowerIcons[cg->forceSelect]) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2)) + yOffset - 220, bigIconSize, bigIconSize, cgs.media.forcePowerIcons[cg->forceSelect] ); //only cache the icon for display + } + else { +#endif + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2)) + yOffset, bigIconSize, bigIconSize, cgs.media.forcePowerIcons[cg->forceSelect] ); //only cache the icon for display +#ifdef _XBOX + } +#endif + } + } + + i = BG_ProperForceIndex(cg->forceSelect) + 1; + if (i>MAX_SHOWPOWERS) + { + i = 0; + } + + // Work forwards from current icon + holdX = x + (bigIconSize/2) + pad; + for (iconCnt=1;iconCnt<(sideRightIconCnt+1);i++) + { + if (i>MAX_SHOWPOWERS) + { + i = 0; + } + + if (!ForcePower_Valid(forcePowerSorted[i])) // Does he have this power? + { + continue; + } + + ++iconCnt; // Good icon + + if (cgs.media.forcePowerIcons[forcePowerSorted[i]]) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + CG_DrawPic( holdX, y + yOffset - 220, smallIconSize, smallIconSize, cgs.media.forcePowerIcons[forcePowerSorted[i]] ); //only cache the icon for display + } + else { +#endif + CG_DrawPic( holdX, y + yOffset, smallIconSize, smallIconSize, cgs.media.forcePowerIcons[forcePowerSorted[i]] ); //only cache the icon for display +#ifdef _XBOX + } +#endif + holdX += (smallIconSize+pad); + } + } + + if ( showPowersName[cg->forceSelect] ) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + UI_DrawProportionalString(320 + xOffset, y + 30 + yOffset - 220, CG_GetStringEdString("SP_INGAME", showPowersName[cg->forceSelect]), UI_CENTER | UI_SMALLFONT, colorTable[CT_ICON_BLUE]); + } + else { +#endif + UI_DrawProportionalString(320 + xOffset, y + 30 + yOffset, CG_GetStringEdString("SP_INGAME", showPowersName[cg->forceSelect]), UI_CENTER | UI_SMALLFONT, colorTable[CT_ICON_BLUE]); +#ifdef _XBOX + } +#endif + } +} + +/* +=================== +CG_DrawInventorySelect +=================== +*/ +void CG_DrawInvenSelect( void ) +{ + int i; + int sideMax,holdCount,iconCnt; + int smallIconSize,bigIconSize; + int sideLeftIconCnt,sideRightIconCnt; + int count; + int holdX,x,y,y2,pad; + int height; + float addX; + int yOffset = 0; + + // don't display if dead + if ( cg->snap->ps.stats[STAT_HEALTH] <= 0 ) + { + return; + } + + if ((cg->invenSelectTime+WEAPON_SELECT_TIME)time) // Time is up for the HUD to display + { + return; + } + + if (!cg->snap->ps.stats[STAT_HOLDABLE_ITEM] || !cg->snap->ps.stats[STAT_HOLDABLE_ITEMS]) + { + return; + } + +#ifdef _XBOX + if(CL_ExtendSelectTime()) { + cg->invenSelectTime = cg->time; + } + + yOffset = -50; +#endif + + if (cg->itemSelect == -1) + { + cg->itemSelect = bg_itemlist[cg->snap->ps.stats[STAT_HOLDABLE_ITEM]].giTag; + } + +//const int bits = cg->snap->ps.stats[ STAT_ITEMS ]; + + // count the number of items owned + count = 0; + for ( i = 0 ; i < HI_NUM_HOLDABLE ; i++ ) + { + if (/*CG_InventorySelectable(i) && inv_icons[i]*/ + (cg->snap->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) ) + { + count++; + } + } + + if (!count) + { + y2 = 0; //err? +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + UI_DrawProportionalString(320, y2 + 22 - 220, "EMPTY INVENTORY", UI_CENTER | UI_SMALLFONT, colorTable[CT_ICON_BLUE]); + } + else { +#endif + UI_DrawProportionalString(320, y2 + 22, "EMPTY INVENTORY", UI_CENTER | UI_SMALLFONT, colorTable[CT_ICON_BLUE]); +#ifdef _XBOX + } +#endif + return; + } + + sideMax = 3; // Max number of icons on the side + + // Calculate how many icons will appear to either side of the center one + holdCount = count - 1; // -1 for the center icon + if (holdCount == 0) // No icons to either side + { + sideLeftIconCnt = 0; + sideRightIconCnt = 0; + } + else if (count > (2*sideMax)) // Go to the max on each side + { + sideLeftIconCnt = sideMax; + sideRightIconCnt = sideMax; + } + else // Less than max, so do the calc + { + sideLeftIconCnt = holdCount/2; + sideRightIconCnt = holdCount - sideLeftIconCnt; + } + + i = cg->itemSelect - 1; + if (i<0) + { + i = HI_NUM_HOLDABLE-1; + } + + smallIconSize = 40; + bigIconSize = 80; + pad = 16; + + x = 320; + y = 410; + + // Left side ICONS + // Work backwards from current icon + holdX = x - ((bigIconSize/2) + pad + smallIconSize); + height = smallIconSize * cg->iconHUDPercent; + addX = (float) smallIconSize * .75; + + for (iconCnt=0;iconCntsnap->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) || i == cg->itemSelect ) + { + continue; + } + + ++iconCnt; // Good icon + + if (!BG_IsItemSelectable(&cg->predictedPlayerState, i)) + { + continue; + } + + if (cgs.media.invenIcons[i]) + { + trap_R_SetColor(NULL); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + CG_DrawPic( holdX, y+10+yOffset - 220, smallIconSize, smallIconSize, cgs.media.invenIcons[i] ); + } + else { +#endif + CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, cgs.media.invenIcons[i] ); +#ifdef _XBOX + } +#endif + + trap_R_SetColor(colorTable[CT_ICON_BLUE]); + /*CG_DrawNumField (holdX + addX, y + smallIconSize, 2, cg->snap->ps.inventory[i], 6, 12, + NUM_FONT_SMALL,qfalse); + */ + + holdX -= (smallIconSize+pad); + } + } + + // Current Center Icon + height = bigIconSize * cg->iconHUDPercent; + if (cgs.media.invenIcons[cg->itemSelect] && BG_IsItemSelectable(&cg->predictedPlayerState, cg->itemSelect)) + { + int itemNdex; + trap_R_SetColor(NULL); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10+yOffset - 220, bigIconSize, bigIconSize, cgs.media.invenIcons[cg->itemSelect] ); + } + else { +#endif + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10+yOffset, bigIconSize, bigIconSize, cgs.media.invenIcons[cg->itemSelect] ); +#ifdef _XBOX + } +#endif + addX = (float) bigIconSize * .75; + trap_R_SetColor(colorTable[CT_ICON_BLUE]); + /*CG_DrawNumField ((x-(bigIconSize/2)) + addX, y, 2, cg->snap->ps.inventory[cg->inventorySelect], 6, 12, + NUM_FONT_SMALL,qfalse);*/ + + itemNdex = BG_GetItemIndexByTag(cg->itemSelect, IT_HOLDABLE); + if (bg_itemlist[itemNdex].classname) + { + vec4_t textColor = { .312f, .75f, .621f, 1.0f }; + char text[1024]; + char upperKey[1024]; + + strcpy(upperKey, bg_itemlist[itemNdex].classname); + + if ( trap_SP_GetStringTextString( va("SP_INGAME_%s",Q_strupr(upperKey)), text, sizeof( text ))) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + UI_DrawProportionalString(320, y+45+yOffset - 220, text, UI_CENTER | UI_SMALLFONT, textColor); + } + else { +#endif + UI_DrawProportionalString(320, y+45+yOffset, text, UI_CENTER | UI_SMALLFONT, textColor); +#ifdef _XBOX + } +#endif + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + UI_DrawProportionalString(320, y+45+yOffset - 220, bg_itemlist[itemNdex].classname, UI_CENTER | UI_SMALLFONT, textColor); + } + else { +#endif + UI_DrawProportionalString(320, y+45+yOffset, bg_itemlist[itemNdex].classname, UI_CENTER | UI_SMALLFONT, textColor); +#ifdef _XBOX + } +#endif + } + } + } + + i = cg->itemSelect + 1; + if (i> HI_NUM_HOLDABLE-1) + { + i = 0; + } + + // Right side ICONS + // Work forwards from current icon + holdX = x + (bigIconSize/2) + pad; + height = smallIconSize * cg->iconHUDPercent; + addX = (float) smallIconSize * .75; + for (iconCnt=0;iconCnt HI_NUM_HOLDABLE-1) + { + i = 0; + } + + if ( !(cg->snap->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) || i == cg->itemSelect ) + { + continue; + } + + ++iconCnt; // Good icon + + if (!BG_IsItemSelectable(&cg->predictedPlayerState, i)) + { + continue; + } + + if (cgs.media.invenIcons[i]) + { + trap_R_SetColor(NULL); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) { + CG_DrawPic( holdX, y+10+yOffset - 220, smallIconSize, smallIconSize, cgs.media.invenIcons[i] ); + } + else { +#endif + CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, cgs.media.invenIcons[i] ); +#ifdef _XBOX + } +#endif + + trap_R_SetColor(colorTable[CT_ICON_BLUE]); + /*CG_DrawNumField (holdX + addX, y + smallIconSize, 2, cg->snap->ps.inventory[i], 6, 12, + NUM_FONT_SMALL,qfalse);*/ + + holdX += (smallIconSize+pad); + } + } +} + +int cg_targVeh = ENTITYNUM_NONE; +int cg_targVehLastTime = 0; +qboolean CG_CheckTargetVehicle( centity_t **pTargetVeh, float *alpha ) +{ + int targetNum = ENTITYNUM_NONE; + centity_t *targetVeh = NULL; + + if ( !pTargetVeh || !alpha ) + {//hey, where are my pointers? + return qfalse; + } + + *alpha = 1.0f; + + if ( cg->predictedPlayerState.rocketLockIndex < ENTITYNUM_WORLD ) + { + targetNum = cg->predictedPlayerState.rocketLockIndex; + } + else if ( cg->crosshairClientNum < ENTITYNUM_WORLD ) + { + targetNum = cg->crosshairClientNum; + } + + if ( targetNum < MAX_CLIENTS ) + {//real client + if ( cg_entities[targetNum].currentState.m_iVehicleNum >= MAX_CLIENTS ) + {//in a vehicle + targetNum = cg_entities[targetNum].currentState.m_iVehicleNum; + } + } + if ( targetNum < ENTITYNUM_WORLD + && targetNum >= MAX_CLIENTS ) + { + centity_t *targetVeh = &cg_entities[targetNum]; + if ( targetVeh->currentState.NPC_class == CLASS_VEHICLE + && targetVeh->m_pVehicle + && targetVeh->m_pVehicle->m_pVehicleInfo + && targetVeh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + {//it's a vehicle + cg_targVeh = targetNum; + cg_targVehLastTime = cg->time; + *alpha = 1.0f; + } + else + { + targetVeh = NULL; + } + } + if ( !targetVeh ) + { + if ( cg_targVehLastTime && cg->time - cg_targVehLastTime < 3000 ) + { + targetVeh = &cg_entities[cg_targVeh];; + if ( cg->time-cg_targVehLastTime < 1000 ) + {//stay at full alpha for 1 sec after lose them from crosshair + *alpha = 1.0f; + } + else + {//fade out over 2 secs + *alpha = 1.0f-((cg->time-cg_targVehLastTime-1000)/2000.0f); + } + } + } + if ( targetVeh ) + { + *pTargetVeh = targetVeh; + return qtrue; + } + return qfalse; +} + +#define MAX_VHUD_SHIELD_TICS 12 +#define MAX_VHUD_SPEED_TICS 5 +#define MAX_VHUD_ARMOR_TICS 5 +#define MAX_VHUD_AMMO_TICS 5 + +float CG_DrawVehicleShields( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + char itemName[64]; + float inc, currValue,maxShields; + vec4_t calcColor; + itemDef_t *item; + float percShields; + + item = Menu_FindItemByName((menuDef_t *) menuHUD, "armorbackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + maxShields = veh->m_pVehicle->m_pVehicleInfo->shields; + currValue = cg->predictedVehicleState.stats[STAT_ARMOR]; + percShields = (float)currValue/(float)maxShields; + // Print all the tics of the shield graphic + // Look at the amount of health left and show only as much of the graphic as there is health. + // Use alpha to fade out partial section of health + inc = (float) maxShields / MAX_VHUD_ARMOR_TICS; + for (i=1;i<=MAX_VHUD_ARMOR_TICS;i++) + { + sprintf( itemName, "armor_tic%d", i ); + + item = Menu_FindItemByName((menuDef_t *) menuHUD, itemName); + + if (!item) + { + continue; + } + + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } + + return percShields; +} + +int cg_vehicleAmmoWarning = 0; +int cg_vehicleAmmoWarningTime = 0; +void CG_DrawVehicleAmmo( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + char itemName[64]; + float inc, currValue,maxAmmo; + vec4_t calcColor; + itemDef_t *item; + + item = Menu_FindItemByName((menuDef_t *) menuHUD, "ammobackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + maxAmmo = veh->m_pVehicle->m_pVehicleInfo->weapon[0].ammoMax; + currValue = cg->predictedVehicleState.ammo[0]; + + inc = (float) maxAmmo / MAX_VHUD_AMMO_TICS; + for (i=1;i<=MAX_VHUD_AMMO_TICS;i++) + { + sprintf( itemName, "ammo_tic%d", i ); + + item = Menu_FindItemByName((menuDef_t *)menuHUD, itemName); + + if (!item) + { + continue; + } + + if ( cg_vehicleAmmoWarningTime > cg->time + && cg_vehicleAmmoWarning == 0 ) + { + memcpy(calcColor, g_color_table[ColorIndex(COLOR_RED)], sizeof(vec4_t)); + calcColor[3] = sin(cg->time*0.005)*0.5f+0.5f; + } + else + { + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } +} + + +void CG_DrawVehicleAmmoUpper( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + char itemName[64]; + float inc, currValue,maxAmmo; + vec4_t calcColor; + itemDef_t *item; + + item = Menu_FindItemByName((menuDef_t *)menuHUD, "ammoupperbackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + maxAmmo = veh->m_pVehicle->m_pVehicleInfo->weapon[0].ammoMax; + currValue = cg->predictedVehicleState.ammo[0]; + + inc = (float) maxAmmo / MAX_VHUD_AMMO_TICS; + for (i=1;i cg->time + && cg_vehicleAmmoWarning == 0 ) + { + memcpy(calcColor, g_color_table[ColorIndex(COLOR_RED)], sizeof(vec4_t)); + calcColor[3] = sin(cg->time*0.005)*0.5f+0.5f; + } + else + { + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } +} + + +void CG_DrawVehicleAmmoLower( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + char itemName[64]; + float inc, currValue,maxAmmo; + vec4_t calcColor; + itemDef_t *item; + + + item = Menu_FindItemByName((menuDef_t *)menuHUD, "ammolowerbackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + maxAmmo = veh->m_pVehicle->m_pVehicleInfo->weapon[1].ammoMax; + currValue = cg->predictedVehicleState.ammo[1]; + + inc = (float) maxAmmo / MAX_VHUD_AMMO_TICS; + for (i=1;i cg->time + && cg_vehicleAmmoWarning == 1 ) + { + memcpy(calcColor, g_color_table[ColorIndex(COLOR_RED)], sizeof(vec4_t)); + calcColor[3] = sin(cg->time*0.005)*0.5f+0.5f; + } + else + { + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } +} + +// The HUD.menu file has the graphic print with a negative height, so it will print from the bottom up. +void CG_DrawVehicleTurboRecharge( const menuDef_t *menuHUD, const centity_t *veh ) +{ + itemDef_t *item; + int height; + + item = Menu_FindItemByName( (menuDef_t *) menuHUD, "turborecharge"); + + if (item) + { + float percent=0.0f; + int diff = ( cg->time - veh->m_pVehicle->m_iTurboTime ); + + height = item->window.rect.h; + + if (diff > veh->m_pVehicle->m_pVehicleInfo->turboRecharge) + { + percent = 1.0f; + trap_R_SetColor( colorTable[CT_GREEN] ); + } + else + { + percent = (float) diff / veh->m_pVehicle->m_pVehicleInfo->turboRecharge; + if (percent < 0.0f) + { + percent = 0.0f; + } + trap_R_SetColor( colorTable[CT_RED] ); + } + + height *= percent; + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + height, + cgs.media.whiteShader); + } +} + +qboolean cg_drawLink = qfalse; +void CG_DrawVehicleWeaponsLinked( const menuDef_t *menuHUD, const centity_t *veh ) +{ + qboolean drawLink = qfalse; + if ( veh->m_pVehicle + && veh->m_pVehicle->m_pVehicleInfo + && (veh->m_pVehicle->m_pVehicleInfo->weapon[0].linkable == 2|| veh->m_pVehicle->m_pVehicleInfo->weapon[1].linkable == 2) ) + {//weapon is always linked + drawLink = qtrue; + } + else + { +//MP way: + //must get sent over network + if ( cg->predictedVehicleState.vehWeaponsLinked ) + { + drawLink = qtrue; + } +//NOTE: below is SP way +/* + //just cheat it + if ( veh->gent->m_pVehicle->weaponStatus[0].linked + || veh->gent->m_pVehicle->weaponStatus[1].linked ) + { + drawLink = qtrue; + } +*/ + } + + if ( cg_drawLink != drawLink ) + {//state changed, play sound + cg_drawLink = drawLink; + trap_S_StartSound (NULL, cg->predictedPlayerState.clientNum, CHAN_LOCAL, trap_S_RegisterSound( "sound/vehicles/common/linkweaps.wav" ) ); + } + + if ( drawLink ) + { + itemDef_t *item; + + item = Menu_FindItemByName( (menuDef_t *) menuHUD, "weaponslinked"); + + if (item) + { + trap_R_SetColor( colorTable[CT_CYAN] ); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + cgs.media.whiteShader); + } + } +} + +void CG_DrawVehicleSpeed( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + char itemName[64]; + float inc, currValue,maxSpeed; + vec4_t calcColor; + itemDef_t *item; + + item = Menu_FindItemByName((menuDef_t *) menuHUD, "speedbackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + maxSpeed = veh->m_pVehicle->m_pVehicleInfo->speedMax; + currValue = cg->predictedVehicleState.speed; + + + // Print all the tics of the shield graphic + // Look at the amount of health left and show only as much of the graphic as there is health. + // Use alpha to fade out partial section of health + inc = (float) maxSpeed / MAX_VHUD_SPEED_TICS; + for (i=1;i<=MAX_VHUD_SPEED_TICS;i++) + { + sprintf( itemName, "speed_tic%d", i ); + + item = Menu_FindItemByName((menuDef_t *)menuHUD, itemName); + + if (!item) + { + continue; + } + + if ( cg->time > veh->m_pVehicle->m_iTurboTime ) + { + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + } + else // In turbo mode + { + if (cg->VHUDFlashTime < cg->time) + { + cg->VHUDFlashTime = cg->time + 200; + if (cg->VHUDTurboFlag) + { + cg->VHUDTurboFlag = qfalse; + } + else + { + cg->VHUDTurboFlag = qtrue; + } + } + + if (cg->VHUDTurboFlag) + { + memcpy(calcColor, colorTable[CT_LTRED1], sizeof(vec4_t)); + } + else + { + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + } + } + + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } +} + +void CG_DrawVehicleArmor( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + vec4_t calcColor; + char itemName[64]; + float inc, currValue,maxArmor; + itemDef_t *item; + + maxArmor = veh->m_pVehicle->m_pVehicleInfo->armor; + currValue = cg->predictedVehicleState.stats[STAT_HEALTH]; + + item = Menu_FindItemByName( (menuDef_t *) menuHUD, "shieldbackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + + // Print all the tics of the shield graphic + // Look at the amount of health left and show only as much of the graphic as there is health. + // Use alpha to fade out partial section of health + inc = (float) maxArmor / MAX_VHUD_SHIELD_TICS; + for (i=1;i <= MAX_VHUD_SHIELD_TICS;i++) + { + sprintf( itemName, "shield_tic%d", i ); + + item = Menu_FindItemByName((menuDef_t *) menuHUD, itemName); + + if (!item) + { + continue; + } + + + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } +} + +enum +{ + VEH_DAMAGE_FRONT=0, + VEH_DAMAGE_BACK, + VEH_DAMAGE_LEFT, + VEH_DAMAGE_RIGHT, +}; + +typedef struct +{ + char *itemName; + short heavyDamage; + short lightDamage; +} veh_damage_t; + +veh_damage_t vehDamageData[4] = +{ +"vehicle_front",SHIPSURF_DAMAGE_FRONT_HEAVY,SHIPSURF_DAMAGE_FRONT_LIGHT, +"vehicle_back",SHIPSURF_DAMAGE_BACK_HEAVY,SHIPSURF_DAMAGE_BACK_LIGHT, +"vehicle_left",SHIPSURF_DAMAGE_LEFT_HEAVY,SHIPSURF_DAMAGE_LEFT_LIGHT, +"vehicle_right",SHIPSURF_DAMAGE_RIGHT_HEAVY,SHIPSURF_DAMAGE_RIGHT_LIGHT, +}; + +// Draw health graphic for given part of vehicle +void CG_DrawVehicleDamage(const centity_t *veh,int brokenLimbs,const menuDef_t *menuHUD,float alpha,int index) +{ + itemDef_t *item; + int colorI; + vec4_t color; + int graphicHandle=0; + + item = Menu_FindItemByName((menuDef_t *)menuHUD, vehDamageData[index].itemName); + if (item) + { + if (brokenLimbs & (1<m_pVehicle->m_pVehicleInfo->iconFrontHandle; + break; + case VEH_DAMAGE_BACK : + graphicHandle = veh->m_pVehicle->m_pVehicleInfo->iconBackHandle; + break; + case VEH_DAMAGE_LEFT : + graphicHandle = veh->m_pVehicle->m_pVehicleInfo->iconLeftHandle; + break; + case VEH_DAMAGE_RIGHT : + graphicHandle = veh->m_pVehicle->m_pVehicleInfo->iconRightHandle; + break; + } + + if (graphicHandle) + { + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + graphicHandle ); + } + } +} + + +// Used on both damage indicators : player vehicle and the vehicle the player is locked on +void CG_DrawVehicleDamageHUD(const centity_t *veh,int brokenLimbs,float percShields,char *menuName, float alpha) +{ + menuDef_t *menuHUD; + itemDef_t *item; + vec4_t color; + + menuHUD = Menus_FindByName(menuName); + + if ( !menuHUD ) + { + return; + } + + item = Menu_FindItemByName(menuHUD, "background"); + if (item) + { + if (veh->m_pVehicle->m_pVehicleInfo->dmgIndicBackgroundHandle) + { + if ( veh->damageTime > cg->time ) + {//ship shields currently taking damage + //NOTE: cent->damageAngle can be accessed to get the direction from the ship origin to the impact point (in 3-D space) + float perc = 1.0f - ((veh->damageTime - cg->time) / 2000.0f/*MIN_SHIELD_TIME*/); + if ( perc < 0.0f ) + { + perc = 0.0f; + } + else if ( perc > 1.0f ) + { + perc = 1.0f; + } + color[0] = item->window.foreColor[0];//flash red + color[1] = item->window.foreColor[1]*perc;//fade other colors back in over time + color[2] = item->window.foreColor[2]*perc;//fade other colors back in over time + color[3] = item->window.foreColor[3];//always normal alpha + trap_R_SetColor( color ); + } + else + { + trap_R_SetColor( item->window.foreColor ); + } + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + veh->m_pVehicle->m_pVehicleInfo->dmgIndicBackgroundHandle ); + } + } + + item = Menu_FindItemByName(menuHUD, "outer_frame"); + if (item) + { + if (veh->m_pVehicle->m_pVehicleInfo->dmgIndicFrameHandle) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + veh->m_pVehicle->m_pVehicleInfo->dmgIndicFrameHandle ); + } + } + + item = Menu_FindItemByName(menuHUD, "shields"); + if (item) + { + if (veh->m_pVehicle->m_pVehicleInfo->dmgIndicShieldHandle) + { + VectorCopy4 ( colorTable[CT_HUD_GREEN], color ); + color[3] = percShields; + trap_R_SetColor( color ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + veh->m_pVehicle->m_pVehicleInfo->dmgIndicShieldHandle ); + } + } + + //TODO: if we check nextState.brokenLimbs & prevState.brokenLimbs, we can tell when a damage flag has been added and flash that part of the ship + //FIXME: when ship explodes, either stop drawing ship or draw all parts black + CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_FRONT); + CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_BACK); + CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_LEFT); + CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_RIGHT); +} + +qboolean CG_DrawVehicleHud( const centity_t *cent ) +{ + itemDef_t *item; + menuDef_t *menuHUD; + playerState_t *ps; + centity_t *veh; + float shieldPerc,alpha; + + menuHUD = Menus_FindByName("swoopvehiclehud"); + if (!menuHUD) + { + return qtrue; // Draw player HUD + } + + ps = &cg->predictedPlayerState; + + if (!ps || !(ps->m_iVehicleNum)) + { + return qtrue; // Draw player HUD + } + veh = &cg_entities[ps->m_iVehicleNum]; + + if ( !veh ) + { + return qtrue; // Draw player HUD + } + + CG_DrawVehicleTurboRecharge( menuHUD, veh ); + CG_DrawVehicleWeaponsLinked( menuHUD, veh ); + + item = Menu_FindItemByName(menuHUD, "leftframe"); + + // Draw frame + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + item = Menu_FindItemByName(menuHUD, "rightframe"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + + CG_DrawVehicleArmor( menuHUD, veh ); + + // Get animal hud for speed +// if (veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) +// { +// menuHUD = Menus_FindByName("tauntaunhud"); +// } + + + CG_DrawVehicleSpeed( menuHUD, veh ); + + // Revert to swoophud +// if (veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) +// { +// menuHUD = Menus_FindByName("swoopvehiclehud"); +// } + +// if (veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) +// { + shieldPerc = CG_DrawVehicleShields( menuHUD, veh ); +// } + + if (veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID && !veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID) + { + CG_DrawVehicleAmmo( menuHUD, veh ); + } + else if (veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID && veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID) + { + CG_DrawVehicleAmmoUpper( menuHUD, veh ); + CG_DrawVehicleAmmoLower( menuHUD, veh ); + } + + // If he's hidden, he must be in a vehicle + if (veh->m_pVehicle->m_pVehicleInfo->hideRider) + { + CG_DrawVehicleDamageHUD(veh,cg->predictedVehicleState.brokenLimbs,shieldPerc,"vehicledamagehud",1.0f); + + // Has he targeted an enemy? + if (CG_CheckTargetVehicle( &veh, &alpha )) + { + CG_DrawVehicleDamageHUD(veh,veh->currentState.brokenLimbs,((float)veh->currentState.activeForcePass/10.0f),"enemyvehicledamagehud",alpha); + } + + return qfalse; // Don't draw player HUD + } + + return qtrue; // Draw player HUD + +} + +/* +================ +CG_DrawStats + +================ +*/ +static void CG_DrawStats( void ) +{ + centity_t *cent; + playerState_t *ps; + qboolean drawHUD = qtrue; +/* playerState_t *ps; + vec3_t angles; +// vec3_t origin; + + if ( cg_drawStatus.integer == 0 ) { + return; + } +*/ + cent = &cg_entities[cg->snap->ps.clientNum]; +/* ps = &cg->snap->ps; + + VectorClear( angles ); + + // Do start + if (!cg->interfaceStartupDone) + { + CG_InterfaceStartup(); + } + + cgi_UI_MenuPaintAll();*/ + + if ( cent ) + { + ps = &cg->predictedPlayerState; + + if ( (ps->m_iVehicleNum ) ) // In a vehicle??? + { + drawHUD = CG_DrawVehicleHud( cent ); + } + } + + if (drawHUD) + { + CG_DrawHUD(cent); + } + + /*CG_DrawArmor(cent); + CG_DrawHealth(cent); + CG_DrawAmmo(cent); + + CG_DrawTalk(cent);*/ +} + +/* +=================== +CG_DrawPickupItem +=================== +*/ +static void CG_DrawPickupItem( void ) { + int value; + float *fadeColor; + + int xoff = 0, yoff = 20; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(ClientManager::ActiveClientNum() == 0) + yoff = 200; + else + yoff = 0; + + xoff = 120; + } + + if(cg->widescreen) + xoff = 40; +#endif + + value = cg->itemPickup; + if ( value && cg_items[ value ].icon != -1 ) + { + fadeColor = CG_FadeColor( cg->itemPickupTime, 3000 ); + if ( fadeColor ) + { + CG_RegisterItemVisuals( value ); + trap_R_SetColor( fadeColor ); + + CG_DrawPic( 573 - xoff, 320 - yoff, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + + trap_R_SetColor( NULL ); + } + } +} + +/* +================ +CG_DrawTeamBackground + +================ +*/ +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ) +{ + vec4_t hcolor; + + hcolor[3] = alpha; + if ( team == TEAM_RED ) { + hcolor[0] = 1; + hcolor[1] = .2f; + hcolor[2] = .2f; + } else if ( team == TEAM_BLUE ) { + hcolor[0] = .2f; + hcolor[1] = .2f; + hcolor[2] = 1; + } else { + return; + } +// trap_R_SetColor( hcolor ); + + CG_FillRect ( x, y, w, h, hcolor ); +// CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); +} + + +/* +=========================================================================================== + + UPPER RIGHT CORNER + +=========================================================================================== +*/ + +/* +================ +CG_DrawMiniScoreboard +================ +*/ +static float CG_DrawMiniScoreboard ( float y ) +{ + char temp[MAX_QPATH]; + int xOffset = 0; + +#ifdef _XBOX + xOffset = -40; +#endif + + if ( !cg_drawScores.integer ) + { + return y; + } + + if (cgs.gametype == GT_SIEGE) + { //don't bother with this in siege + return y; + } + + if ( cgs.gametype >= GT_TEAM ) + { + strcpy ( temp, va("%s: ", CG_GetStringEdString("MP_INGAME", "RED"))); + Q_strcat ( temp, MAX_QPATH, cgs.scores1==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores1)) ); + Q_strcat ( temp, MAX_QPATH, va(" %s: ", CG_GetStringEdString("MP_INGAME", "BLUE")) ); + Q_strcat ( temp, MAX_QPATH, cgs.scores2==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores2)) ); + +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint( 710 - CG_Text_Width ( temp, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, temp, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM ); + else +#endif + CG_Text_Paint( 630 - CG_Text_Width ( temp, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, temp, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM ); + y += 15; + } + else + { + /* + strcpy ( temp, "1st: " ); + Q_strcat ( temp, MAX_QPATH, cgs.scores1==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores1)) ); + + Q_strcat ( temp, MAX_QPATH, " 2nd: " ); + Q_strcat ( temp, MAX_QPATH, cgs.scores2==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores2)) ); + + CG_Text_Paint( 630 - CG_Text_Width ( temp, 0.7f, FONT_SMALL ), y, 0.7f, colorWhite, temp, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM ); + y += 15; + */ + //rww - no longer doing this. Since the attacker now shows who is first, we print the score there. + } + + + return y; +} + +/* +================ +CG_DrawEnemyInfo +================ +*/ +static float CG_DrawEnemyInfo ( float y ) +{ + float size; + int clientNum; + const char *title; + clientInfo_t *ci; + int xOffset = 0; + int ico_size; + + if (!cg->snap) + { + return y; + } + + ico_size = ICON_SIZE; +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + return y; + + xOffset = -40; +#endif + + if ( !cg_drawEnemyInfo.integer ) + { + return y; + } + + if ( cg->predictedPlayerState.stats[STAT_HEALTH] <= 0 ) + { + return y; + } + + if (cgs.gametype == GT_POWERDUEL) + { //just get out of here then + return y; + } + + if ( cgs.gametype == GT_JEDIMASTER ) + { + //title = "Jedi Master"; + title = CG_GetStringEdString("MP_INGAME", "MASTERY7"); + clientNum = cgs.jediMaster; + + if ( clientNum < 0 ) + { + //return y; +// title = "Get Saber!"; + title = CG_GetStringEdString("MP_INGAME", "GET_SABER"); + + + size = ico_size * 1.25; + y += 5; + +#ifdef _XBOX + if(cg->widescreen) + CG_DrawPic( 720 - size - 12 + xOffset, y, size, size, cgs.media.weaponIcons[WP_SABER] ); + else +#endif + CG_DrawPic( 640 - size - 12 + xOffset, y, size, size, cgs.media.weaponIcons[WP_SABER] ); + + y += size; + + /* + CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 0.7f, FONT_MEDIUM ), y, 0.7f, colorWhite, ci->name, 0, 0, 0, FONT_MEDIUM ); + y += 15; + */ + +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint( 710 - CG_Text_Width ( title, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, title, 0, 0, 0, FONT_MEDIUM ); + else +#endif + CG_Text_Paint( 630 - CG_Text_Width ( title, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, title, 0, 0, 0, FONT_MEDIUM ); + + return y + BIGCHAR_HEIGHT + 2; + } + } + else if ( cg->snap->ps.duelInProgress ) + { +// title = "Dueling"; + title = CG_GetStringEdString("MP_INGAME", "DUELING"); + clientNum = cg->snap->ps.duelIndex; + } + else if ( cgs.gametype == GT_DUEL && cgs.clientinfo[cg->snap->ps.clientNum].team != TEAM_SPECTATOR) + { + title = CG_GetStringEdString("MP_INGAME", "DUELING"); + if (cg->snap->ps.clientNum == cgs.duelist1) + { + clientNum = cgs.duelist2; //if power duel, should actually draw both duelists 2 and 3 I guess + } + else if (cg->snap->ps.clientNum == cgs.duelist2) + { + clientNum = cgs.duelist1; + } + else if (cg->snap->ps.clientNum == cgs.duelist3) + { + clientNum = cgs.duelist1; + } + else + { + return y; + } + } + else + { + /* + title = "Attacker"; + clientNum = cg->predictedPlayerState.persistant[PERS_ATTACKER]; + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg->snap->ps.clientNum ) + { + return y; + } + + if ( cg->time - cg->attackerTime > ATTACKER_HEAD_TIME ) + { + cg->attackerTime = 0; + return y; + } + */ + //As of current, we don't want to draw the attacker. Instead, draw whoever is in first place. + if (cgs.duelWinner < 0 || cgs.duelWinner >= MAX_CLIENTS) + { + return y; + } + + + title = va("%s: %i",CG_GetStringEdString("MP_INGAME", "LEADER"), cgs.scores1); + + /* + if (cgs.scores1 == 1) + { + title = va("%i kill", cgs.scores1); + } + else + { + title = va("%i kills", cgs.scores1); + } + */ + clientNum = cgs.duelWinner; + } + + ci = &cgs.clientinfo[ clientNum ]; + + if ( !ci ) + { + return y; + } + + size = ico_size * 1.25; + y += 5; + + if ( ci->modelIcon ) + { +#ifdef _XBOX + if(cg->widescreen) + CG_DrawPic( 720 - size - 5 + xOffset, y, size, size, ci->modelIcon ); + else +#endif + CG_DrawPic( 640 - size - 5 + xOffset, y, size, size, ci->modelIcon ); + } + + y += size; + +// CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, ci->name, 0, 0, 0, FONT_MEDIUM ); +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint( 710 - CG_Text_Width ( ci->name, 0.75f, FONT_MEDIUM ) + xOffset, y, 0.75f, colorWhite, ci->name, 0, 0, 0, FONT_MEDIUM ); + else +#endif + CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 0.75f, FONT_MEDIUM ) + xOffset, y, 0.75f, colorWhite, ci->name, 0, 0, 0, FONT_MEDIUM ); + + y += 15; +// CG_Text_Paint( 630 - CG_Text_Width ( title, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, title, 0, 0, 0, FONT_MEDIUM ); +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint( 710 - CG_Text_Width ( title, 0.75f, FONT_MEDIUM ) + xOffset, y, 0.75f, colorWhite, title, 0, 0, 0, FONT_MEDIUM ); + else +#endif + CG_Text_Paint( 630 - CG_Text_Width ( title, 0.75f, FONT_MEDIUM ) + xOffset, y, 0.75f, colorWhite, title, 0, 0, 0, FONT_MEDIUM ); + + if ( (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) && cgs.clientinfo[cg->snap->ps.clientNum].team != TEAM_SPECTATOR) + {//also print their score + char text[1024]; + y += 15; + Com_sprintf(text, sizeof(text), "%i/%i", cgs.clientinfo[clientNum].score, cgs.fraglimit ); +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint( 710 - CG_Text_Width ( text, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, text, 0, 0, 0, FONT_MEDIUM ); + else +#endif + CG_Text_Paint( 630 - CG_Text_Width ( text, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, text, 0, 0, 0, FONT_MEDIUM ); + } + +// nmckenzie: DUEL_HEALTH - fixme - need checks and such here. And this is coded to duelist 1 right now, which is wrongly. + if ( cgs.showDuelHealths >= 2) + { + y += 15; + if ( cgs.duelist1 == clientNum ) + { +#ifdef _XBOX + if(cg->widescreen) + CG_DrawDuelistHealth ( 720 - size - 5 + xOffset, y, 64, 8, 1 ); + else +#endif + CG_DrawDuelistHealth ( 640 - size - 5 + xOffset, y, 64, 8, 1 ); + } + else if ( cgs.duelist2 == clientNum ) + { +#ifdef _XBOX + if(cg->widescreen) + CG_DrawDuelistHealth ( 720 - size - 5 + xOffset, y, 64, 8, 2 ); + else +#endif + CG_DrawDuelistHealth ( 640 - size - 5 + xOffset, y, 64, 8, 2 ); + } + } + + return y + BIGCHAR_HEIGHT + 2; +} + +/* +================== +CG_DrawFPS +================== +*/ +#define FPS_FRAMES 16 +static float CG_DrawFPS( float y ) +{ + char *s; + int w; + static unsigned short previousTimes[FPS_FRAMES]; + static unsigned short index; + static int previous, lastupdate; + int t, i, fps, total; + unsigned short frameTime; + const int xOffset = -40; + + // don't use serverTime, because that will be drifting to + // correct for internet lag changes, timescales, timedemos, etc + t = trap_Milliseconds(); + frameTime = t - previous; + previous = t; + if (t - lastupdate > 50) //don't sample faster than this + { + lastupdate = t; + previousTimes[index % FPS_FRAMES] = frameTime; + index++; + } + // average multiple frames together to smooth changes out a bit + total = 0; + for ( i = 0 ; i < FPS_FRAMES ; i++ ) { + total += previousTimes[i]; + } + if ( !total ) { + total = 1; + } + fps = 1000 * FPS_FRAMES / total; + + s = va( "%ifps", fps ); + w = RE_Font_StrLenPixels( s, 1, 1.0f ); + + RE_Font_DrawString( 635 - w + xOffset, y + 2, s, g_color_table[7], 1, -1, 1.0f ); + + return y + RE_Font_HeightPixels( 1, 1.0f ) + 4; +} + +// nmckenzie: DUEL_HEALTH +#define MAX_HEALTH_FOR_IFACE 100 +void CG_DrawHealthBarRough (float x, float y, int width, int height, float ratio, const float *color1, const float *color2) +{ + float midpoint, remainder; + float color3[4] = {1, 0, 0, .7f}; + + midpoint = width * ratio - 1; + remainder = width - midpoint; + color3[0] = color1[0] * 0.5f; + + assert(!(height%4));//this won't line up otherwise. + CG_DrawRect(x + 1, y + height/2-1, midpoint, 1, height/4+1, color1); // creme-y filling. + CG_DrawRect(x + midpoint, y + height/2-1, remainder, 1, height/4+1, color3); // used-up-ness. + CG_DrawRect(x, y, width, height, 1, color2); // hard crispy shell +} + +void CG_DrawDuelistHealth ( float x, float y, float w, float h, int duelist ) +{ + float duelHealthColor[4] = {1, 0, 0, 0.7f}; + float healthSrc = 0.0f; + float ratio; + + if ( duelist == 1 ) + { + healthSrc = cgs.duelist1health; + } + else if (duelist == 2 ) + { + healthSrc = cgs.duelist2health; + } + + ratio = healthSrc / MAX_HEALTH_FOR_IFACE; + if ( ratio > 1.0f ) + { + ratio = 1.0f; + } + if ( ratio < 0.0f ) + { + ratio = 0.0f; + } + duelHealthColor[0] = (ratio * 0.2f) + 0.5f; + + CG_DrawHealthBarRough (x, y, w, h, ratio, duelHealthColor, colorTable[CT_WHITE]); // new art for this? I'm not crazy about how this looks. +} + +/* +===================== +CG_DrawRadar +===================== +*/ +//#define RADAR_RANGE 2500 +float cg_radarRange = 2500.0f; + +#define RADAR_RADIUS 60 +#define RADAR_X (580 - RADAR_RADIUS) +//#define RADAR_Y 10 //dynamic based on passed Y val +#define RADAR_CHAT_DURATION 6000 +static int radarLockSoundDebounceTime = 0; +static int impactSoundDebounceTime = 0; +#define RADAR_MISSILE_RANGE 3000.0f +#define RADAR_ASTEROID_RANGE 10000.0f +#define RADAR_MIN_ASTEROID_SURF_WARN_DIST 1200.0f + +#ifdef _XBOX +#define SPLITSCREEN_RADAR_RADIUS 45 +#define SPLITSCREEN_RADAR_X (590 - SPLITSCREEN_RADAR_RADIUS) +#endif + +float CG_DrawRadar ( float y ) +{ + vec4_t color; + vec4_t teamColor; + float arrow_w; + float arrow_h; + clientInfo_t *cl; + clientInfo_t *local; + int i; + float arrowBaseScale; + float zScale; + int xOffset = 0; + + if (!cg->snap) + { + return y; + } + +#ifdef _XBOX + xOffset = -40; +#endif + + // Make sure the radar should be showing + if ( cg->snap->ps.stats[STAT_HEALTH] <= 0 ) + { + return y; + } + + if ( (cg->predictedPlayerState.pm_flags & PMF_FOLLOW) || cg->predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR ) + { + return y; + } + + local = &cgs.clientinfo[ cg->snap->ps.clientNum ]; + if ( !local->infoValid ) + { + return y; + } + + // Draw the radar background image + color[0] = color[1] = color[2] = 1.0f; + color[3] = 0.6f; + trap_R_SetColor ( color ); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + y -= 15; + if(cg->widescreen) + CG_DrawPic( SPLITSCREEN_RADAR_X + xOffset + 80, y, SPLITSCREEN_RADAR_RADIUS*2, SPLITSCREEN_RADAR_RADIUS*2, cgs.media.radarShader ); + else + CG_DrawPic( SPLITSCREEN_RADAR_X + xOffset, y, SPLITSCREEN_RADAR_RADIUS*2, SPLITSCREEN_RADAR_RADIUS*2, cgs.media.radarShader ); + } + else { + if(cg->widescreen) + CG_DrawPic( RADAR_X + xOffset + 80, y, RADAR_RADIUS*2, RADAR_RADIUS*2, cgs.media.radarShader ); + else +#endif + CG_DrawPic( RADAR_X + xOffset, y, RADAR_RADIUS*2, RADAR_RADIUS*2, cgs.media.radarShader ); +#ifdef _XBOX + } +#endif + + //Always green for your own team. + VectorCopy ( g_color_table[ColorIndex(COLOR_GREEN)], teamColor ); + teamColor[3] = 1.0f; + + // Draw all of the radar entities. Draw them backwards so players are drawn last + for ( i = cg->radarEntityCount -1 ; i >= 0 ; i-- ) + { + vec3_t dirLook; + vec3_t dirPlayer; + float angleLook; + float anglePlayer; + float angle; + float distance, actualDist; + centity_t* cent; + + cent = &cg_entities[cg->radarEntities[i]]; + + // Get the distances first + VectorSubtract ( cg->predictedPlayerState.origin, cent->lerpOrigin, dirPlayer ); + dirPlayer[2] = 0; + actualDist = distance = VectorNormalize ( dirPlayer ); + + if ( distance > cg_radarRange * 0.8f) + { + if ( (cent->currentState.eFlags & EF_RADAROBJECT)//still want to draw the direction + || ( cent->currentState.eType==ET_NPC//FIXME: draw last, with players... + && cent->currentState.NPC_class == CLASS_VEHICLE + && cent->currentState.speed > 0 ) )//always draw vehicles + { + distance = cg_radarRange*0.8f; + } + else + { + continue; + } + } + + distance = distance / cg_radarRange; +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + distance *= SPLITSCREEN_RADAR_RADIUS; + else +#endif + distance *= RADAR_RADIUS; + + AngleVectors ( cg->predictedPlayerState.viewangles, dirLook, NULL, NULL ); + + dirLook[2] = 0; + anglePlayer = atan2(dirPlayer[0],dirPlayer[1]); + VectorNormalize ( dirLook ); + angleLook = atan2(dirLook[0],dirLook[1]); + angle = angleLook - anglePlayer; + + switch ( cent->currentState.eType ) + { + default: + { + float x; + float ly; + qhandle_t shader; + vec4_t color; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + x = (float)SPLITSCREEN_RADAR_X + (float)SPLITSCREEN_RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)SPLITSCREEN_RADAR_RADIUS + (float)cos (angle) * distance; + } + else { +#endif + x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance; +#ifdef _XBOX + } +#endif + + arrowBaseScale = 9.0f; + shader = 0; + zScale = 1.0f; + + //we want to scale the thing up/down based on the relative Z (up/down) positioning + if (cent->lerpOrigin[2] > cg->predictedPlayerState.origin[2]) + { //higher, scale up (between 16 and 24) + float dif = (cent->lerpOrigin[2] - cg->predictedPlayerState.origin[2]); + + //max out to 1.5x scale at 512 units above local player's height + dif /= 1024.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale += dif; + } + else if (cent->lerpOrigin[2] < cg->predictedPlayerState.origin[2]) + { //lower, scale down (between 16 and 8) + float dif = (cg->predictedPlayerState.origin[2] - cent->lerpOrigin[2]); + + //half scale at 512 units below local player's height + dif /= 1024.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale -= dif; + } + + arrowBaseScale *= zScale; + + if (cent->currentState.brokenLimbs) + { //slightly misleading to use this value, but don't want to add more to entstate. + //any ent with brokenLimbs non-0 and on radar is an objective ent. + //brokenLimbs is literal team value. + char objState[1024]; + int complete; + + //we only want to draw it if the objective for it is not complete. + //frame represents objective num. + trap_Cvar_VariableStringBuffer(va("team%i_objective%i", cent->currentState.brokenLimbs, cent->currentState.frame), objState, 1024); + + complete = atoi(objState); + + if (!complete) + { + + // generic enemy index specifies a shader to use for the radar entity. + if ( cent->currentState.genericenemyindex ) + { + color[0] = color[1] = color[2] = color[3] = 1.0f; + shader = cgs.gameIcons[cent->currentState.genericenemyindex]; + } + else + { + if (cg->snap && + cent->currentState.brokenLimbs == cg->snap->ps.persistant[PERS_TEAM]) + { + VectorCopy ( g_color_table[ColorIndex(COLOR_RED)], color ); + } + else + { + VectorCopy ( g_color_table[ColorIndex(COLOR_GREEN)], color ); + } + + shader = cgs.media.siegeItemShader; + } + } + } + else + { + color[0] = color[1] = color[2] = color[3] = 1.0f; + + // generic enemy index specifies a shader to use for the radar entity. + if ( cent->currentState.genericenemyindex ) + { + shader = cgs.gameIcons[cent->currentState.genericenemyindex]; + } + else + { + shader = cgs.media.siegeItemShader; + } + + } + + if ( shader ) + { + // Pulse the alpha if time2 is set. time2 gets set when the entity takes pain + if ( (cent->currentState.time2 && cg->time - cent->currentState.time2 < 5000) || + (cent->currentState.time2 == 0xFFFFFFFF) ) + { + if ( (cg->time / 200) & 1 ) + { + color[3] = 0.1f + 0.9f * (float) (cg->time % 200) / 200.0f; + } + else + { + color[3] = 1.0f - 0.9f * (float) (cg->time % 200) / 200.0f; + } + } + + trap_R_SetColor ( color ); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cg->widescreen) + CG_DrawPic ( x - 4 + xOffset + 80, ly - 4, arrowBaseScale, arrowBaseScale, shader ); + else + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, shader ); + } + else { + if(cg->widescreen) + CG_DrawPic ( x - 4 + xOffset + 80, ly - 4, arrowBaseScale, arrowBaseScale, shader ); + else +#endif + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, shader ); +#ifdef _XBOX + } +#endif + } + } + break; + + case ET_NPC://FIXME: draw last, with players... + if ( cent->currentState.NPC_class == CLASS_VEHICLE + && cent->currentState.speed > 0 ) + { + if ( cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->radarIconHandle ) + { + float x; + float ly; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + x = (float)SPLITSCREEN_RADAR_X + (float)SPLITSCREEN_RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)SPLITSCREEN_RADAR_RADIUS + (float)cos (angle) * distance; + } + else { +#endif + x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance; +#ifdef _XBOX + } +#endif + + arrowBaseScale = 9.0f; + zScale = 1.0f; + + //we want to scale the thing up/down based on the relative Z (up/down) positioning + if (cent->lerpOrigin[2] > cg->predictedPlayerState.origin[2]) + { //higher, scale up (between 16 and 24) + float dif = (cent->lerpOrigin[2] - cg->predictedPlayerState.origin[2]); + + //max out to 1.5x scale at 512 units above local player's height + dif /= 4096.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale += dif; + } + else if (cent->lerpOrigin[2] < cg->predictedPlayerState.origin[2]) + { //lower, scale down (between 16 and 8) + float dif = (cg->predictedPlayerState.origin[2] - cent->lerpOrigin[2]); + + //half scale at 512 units below local player's height + dif /= 4096.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale -= dif; + } + + arrowBaseScale *= zScale; + + if ( cent->currentState.m_iVehicleNum //vehicle has a driver + && cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].infoValid ) + { + if ( cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].team == local->team ) + { + trap_R_SetColor ( teamColor ); + } + else + { + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + } + } + else + { + trap_R_SetColor ( NULL ); + } +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cg->widescreen) + CG_DrawPic ( x - 4 + xOffset + 80, ly - 4, arrowBaseScale, arrowBaseScale, cent->m_pVehicle->m_pVehicleInfo->radarIconHandle ); + else + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, cent->m_pVehicle->m_pVehicleInfo->radarIconHandle ); + } + else { + if(cg->widescreen) + CG_DrawPic ( x - 4 + xOffset + 80, ly - 4, arrowBaseScale, arrowBaseScale, cent->m_pVehicle->m_pVehicleInfo->radarIconHandle ); + else +#endif + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, cent->m_pVehicle->m_pVehicleInfo->radarIconHandle ); +#ifdef _XBOX + } +#endif + } + } + break; //maybe do something? + + case ET_MOVER: + if ( cent->currentState.speed//the mover's size, actually + && actualDist < (cent->currentState.speed+RADAR_ASTEROID_RANGE) + && cg->predictedPlayerState.m_iVehicleNum ) + {//a mover that's close to me and I'm in a vehicle + qboolean mayImpact = qfalse; + float surfaceDist = (actualDist-cent->currentState.speed); + if ( surfaceDist < 0.0f ) + { + surfaceDist = 0.0f; + } + if ( surfaceDist < RADAR_MIN_ASTEROID_SURF_WARN_DIST ) + {//always warn! + mayImpact = qtrue; + } + else + {//not close enough to always warn, yet, so check its direction + vec3_t asteroidPos, myPos, moveDir; + int predictTime, timeStep = 500; + float newDist; + for ( predictTime = timeStep; predictTime < 5000; predictTime+=timeStep ) + { + //asteroid dir, speed, size, + my dir & speed... + BG_EvaluateTrajectory( ¢->currentState.pos, cg->time+predictTime, asteroidPos ); + //FIXME: I don't think it's calcing "myPos" correctly + AngleVectors( cg->predictedVehicleState.viewangles, moveDir, NULL, NULL ); + VectorMA( cg->predictedVehicleState.origin, cg->predictedVehicleState.speed*predictTime/1000.0f, moveDir, myPos ); + newDist = Distance( myPos, asteroidPos ); + if ( (newDist-cent->currentState.speed) <= RADAR_MIN_ASTEROID_SURF_WARN_DIST )//200.0f ) + {//heading for an impact within the next 5 seconds + mayImpact = qtrue; + break; + } + } + } + if ( mayImpact ) + {//possible collision + vec4_t asteroidColor = {0.5f,0.5f,0.5f,1.0f}; + float x; + float ly; + float asteroidScale = (cent->currentState.speed/2000.0f);//average asteroid radius? + if ( actualDist > RADAR_ASTEROID_RANGE ) + { + actualDist = RADAR_ASTEROID_RANGE; + } +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + distance = (actualDist/RADAR_ASTEROID_RANGE)*SPLITSCREEN_RADAR_RADIUS; + x = (float)SPLITSCREEN_RADAR_X + (float)SPLITSCREEN_RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)SPLITSCREEN_RADAR_RADIUS + (float)cos (angle) * distance; + } + else { +#endif + distance = (actualDist/RADAR_ASTEROID_RANGE)*RADAR_RADIUS; + x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance; +#ifdef _XBOX + } +#endif + + if ( asteroidScale > 3.0f ) + { + asteroidScale = 3.0f; + } + else if ( asteroidScale < 0.2f ) + { + asteroidScale = 0.2f; + } + arrowBaseScale = (9.0f*asteroidScale); + if ( impactSoundDebounceTime < cg->time ) + { + vec3_t soundOrg; + if ( surfaceDist > RADAR_ASTEROID_RANGE*0.66f ) + { + impactSoundDebounceTime = cg->time + 1000; + } + else if ( surfaceDist > RADAR_ASTEROID_RANGE/3.0f ) + { + impactSoundDebounceTime = cg->time + 400; + } + else + { + impactSoundDebounceTime = cg->time + 100; + } + VectorMA( cg->refdef.vieworg, -500.0f*(surfaceDist/RADAR_ASTEROID_RANGE), dirPlayer, soundOrg ); + trap_S_StartSound( soundOrg, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound( "sound/vehicles/common/impactalarm.wav" ) ); + } + //brighten it the closer it is + if ( surfaceDist > RADAR_ASTEROID_RANGE*0.66f ) + { + asteroidColor[0] = asteroidColor[1] = asteroidColor[2] = 0.7f; + } + else if ( surfaceDist > RADAR_ASTEROID_RANGE/3.0f ) + { + asteroidColor[0] = asteroidColor[1] = asteroidColor[2] = 0.85f; + } + else + { + asteroidColor[0] = asteroidColor[1] = asteroidColor[2] = 1.0f; + } + //alpha out the longer it's been since it was considered dangerous + if ( (cg->time-impactSoundDebounceTime) > 100 ) + { + asteroidColor[3] = (float)((cg->time-impactSoundDebounceTime)-100)/900.0f; + } + + trap_R_SetColor ( asteroidColor ); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cg->widescreen) + CG_DrawPic ( x - 4 + xOffset + 80, ly - 4, arrowBaseScale, arrowBaseScale, trap_R_RegisterShaderNoMip( "gfx/menus/radar/asteroid" ) ); + else + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, trap_R_RegisterShaderNoMip( "gfx/menus/radar/asteroid" ) ); + } + else { + if(cg->widescreen) + CG_DrawPic ( x - 4 + xOffset + 80, ly - 4, arrowBaseScale, arrowBaseScale, trap_R_RegisterShaderNoMip( "gfx/menus/radar/asteroid" ) ); + else +#endif + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, trap_R_RegisterShaderNoMip( "gfx/menus/radar/asteroid" ) ); +#ifdef _XBOX + } +#endif + } + } + break; + + case ET_MISSILE: + if ( //cent->currentState.weapon == WP_ROCKET_LAUNCHER &&//a rocket + cent->currentState.owner > MAX_CLIENTS //belongs to an NPC + && cg_entities[cent->currentState.owner].currentState.NPC_class == CLASS_VEHICLE ) + {//a rocket belonging to an NPC, FIXME: only tracking rockets! + float x; + float ly; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + x = (float)SPLITSCREEN_RADAR_X + (float)SPLITSCREEN_RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)SPLITSCREEN_RADAR_RADIUS + (float)cos (angle) * distance; + } + else { +#endif + x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance; +#ifdef _XBOX + } +#endif + + arrowBaseScale = 3.0f; + if ( cg->predictedPlayerState.m_iVehicleNum ) + {//I'm in a vehicle + //if it's targetting me, then play an alarm sound if I'm in a vehicle + if ( cent->currentState.otherEntityNum == cg->predictedPlayerState.clientNum || cent->currentState.otherEntityNum == cg->predictedPlayerState.m_iVehicleNum ) + { + if ( radarLockSoundDebounceTime < cg->time ) + { + vec3_t soundOrg; + int alarmSound; + if ( actualDist > RADAR_MISSILE_RANGE * 0.66f ) + { + radarLockSoundDebounceTime = cg->time + 1000; + arrowBaseScale = 3.0f; + alarmSound = trap_S_RegisterSound( "sound/vehicles/common/lockalarm1.wav" ); + } + else if ( actualDist > RADAR_MISSILE_RANGE/3.0f ) + { + radarLockSoundDebounceTime = cg->time + 500; + arrowBaseScale = 6.0f; + alarmSound = trap_S_RegisterSound( "sound/vehicles/common/lockalarm2.wav" ); + } + else + { + radarLockSoundDebounceTime = cg->time + 250; + arrowBaseScale = 9.0f; + alarmSound = trap_S_RegisterSound( "sound/vehicles/common/lockalarm3.wav" ); + } + if ( actualDist > RADAR_MISSILE_RANGE ) + { + actualDist = RADAR_MISSILE_RANGE; + } + VectorMA( cg->refdef.vieworg, -500.0f*(actualDist/RADAR_MISSILE_RANGE), dirPlayer, soundOrg ); + trap_S_StartSound( soundOrg, ENTITYNUM_WORLD, CHAN_AUTO, alarmSound ); + } + } + } + zScale = 1.0f; + + //we want to scale the thing up/down based on the relative Z (up/down) positioning + if (cent->lerpOrigin[2] > cg->predictedPlayerState.origin[2]) + { //higher, scale up (between 16 and 24) + float dif = (cent->lerpOrigin[2] - cg->predictedPlayerState.origin[2]); + + //max out to 1.5x scale at 512 units above local player's height + dif /= 1024.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale += dif; + } + else if (cent->lerpOrigin[2] < cg->predictedPlayerState.origin[2]) + { //lower, scale down (between 16 and 8) + float dif = (cg->predictedPlayerState.origin[2] - cent->lerpOrigin[2]); + + //half scale at 512 units below local player's height + dif /= 1024.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale -= dif; + } + + arrowBaseScale *= zScale; + + if ( cent->currentState.owner >= MAX_CLIENTS//missile owned by an NPC + && cg_entities[cent->currentState.owner].currentState.NPC_class == CLASS_VEHICLE//NPC is a vehicle + && cg_entities[cent->currentState.owner].currentState.m_iVehicleNum <= MAX_CLIENTS//Vehicle has a player driver + && cgs.clientinfo[cg_entities[cent->currentState.owner].currentState.m_iVehicleNum-1].infoValid ) //player driver is valid + { + cl = &cgs.clientinfo[cg_entities[cent->currentState.owner].currentState.m_iVehicleNum-1]; + if ( cl->team == local->team ) + { + trap_R_SetColor ( teamColor ); + } + else + { + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + } + } + else + { + trap_R_SetColor ( NULL ); + } +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cg->widescreen) + CG_DrawPic ( x - 4 + xOffset + 80, ly - 4, arrowBaseScale, arrowBaseScale, cgs.media.mAutomapRocketIcon ); + else + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, cgs.media.mAutomapRocketIcon ); + } + else { + if(cg->widescreen) + CG_DrawPic ( x - 4 + xOffset + 80, ly - 4, arrowBaseScale, arrowBaseScale, cgs.media.mAutomapRocketIcon ); + else +#endif + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, cgs.media.mAutomapRocketIcon ); +#ifdef _XBOX + } +#endif + } + break; + + case ET_PLAYER: + { + vec4_t color; + + cl = &cgs.clientinfo[ cent->currentState.number ]; + + // not valid then dont draw it + if ( !cl->infoValid ) + { + continue; + } + + VectorCopy4 ( teamColor, color ); + + arrowBaseScale = 16.0f; + zScale = 1.0f; + + // Pulse the radar icon after a voice message + if ( cent->vChatTime + 2000 > cg->time ) + { + float f = (cent->vChatTime + 2000 - cg->time) / 3000.0f; + arrowBaseScale = 16.0f + 4.0f * f; + color[0] = teamColor[0] + (1.0f - teamColor[0]) * f; + color[1] = teamColor[1] + (1.0f - teamColor[1]) * f; + color[2] = teamColor[2] + (1.0f - teamColor[2]) * f; + } + + trap_R_SetColor ( color ); + + //we want to scale the thing up/down based on the relative Z (up/down) positioning + if (cent->lerpOrigin[2] > cg->predictedPlayerState.origin[2]) + { //higher, scale up (between 16 and 32) + float dif = (cent->lerpOrigin[2] - cg->predictedPlayerState.origin[2]); + + //max out to 2x scale at 1024 units above local player's height + dif /= 1024.0f; + if (dif > 1.0f) + { + dif = 1.0f; + } + zScale += dif; + } + else if (cent->lerpOrigin[2] < cg->predictedPlayerState.origin[2]) + { //lower, scale down (between 16 and 8) + float dif = (cg->predictedPlayerState.origin[2] - cent->lerpOrigin[2]); + + //half scale at 512 units below local player's height + dif /= 1024.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale -= dif; + } + + arrowBaseScale *= zScale; + arrow_w = arrowBaseScale * RADAR_RADIUS / 128; + arrow_h = arrowBaseScale * RADAR_RADIUS / 128; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cg->widescreen) + CG_DrawRotatePic2( SPLITSCREEN_RADAR_X + SPLITSCREEN_RADAR_RADIUS + sin (angle) * distance + xOffset + 80, + (y + SPLITSCREEN_RADAR_RADIUS + cos (angle) * distance), + arrow_w, arrow_h, + (360 - cent->lerpAngles[YAW]) + cg->predictedPlayerState.viewangles[YAW], cgs.media.mAutomapPlayerIcon ); + else + CG_DrawRotatePic2( SPLITSCREEN_RADAR_X + SPLITSCREEN_RADAR_RADIUS + sin (angle) * distance + xOffset, + (y + SPLITSCREEN_RADAR_RADIUS + cos (angle) * distance), + arrow_w, arrow_h, + (360 - cent->lerpAngles[YAW]) + cg->predictedPlayerState.viewangles[YAW], cgs.media.mAutomapPlayerIcon ); + } + else { + if(cg->widescreen) + CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + sin (angle) * distance + xOffset + 80, + y + RADAR_RADIUS + cos (angle) * distance, + arrow_w, arrow_h, + (360 - cent->lerpAngles[YAW]) + cg->predictedPlayerState.viewangles[YAW], cgs.media.mAutomapPlayerIcon ); + else +#endif + CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + sin (angle) * distance + xOffset, + y + RADAR_RADIUS + cos (angle) * distance, + arrow_w, arrow_h, + (360 - cent->lerpAngles[YAW]) + cg->predictedPlayerState.viewangles[YAW], cgs.media.mAutomapPlayerIcon ); +#ifdef _XBOX + } +#endif + break; + } + } + } + + arrowBaseScale = 16.0f; + + arrow_w = arrowBaseScale * RADAR_RADIUS / 128; + arrow_h = arrowBaseScale * RADAR_RADIUS / 128; + + trap_R_SetColor ( colorWhite ); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cg->widescreen) + CG_DrawRotatePic2( SPLITSCREEN_RADAR_X + SPLITSCREEN_RADAR_RADIUS + xOffset + 80, y + SPLITSCREEN_RADAR_RADIUS, arrow_w, arrow_h, + 0, cgs.media.mAutomapPlayerIcon ); + else + CG_DrawRotatePic2( SPLITSCREEN_RADAR_X + SPLITSCREEN_RADAR_RADIUS + xOffset, y + SPLITSCREEN_RADAR_RADIUS, arrow_w, arrow_h, + 0, cgs.media.mAutomapPlayerIcon ); + } + else { + if(cg->widescreen) + CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + xOffset + 80, y + RADAR_RADIUS, arrow_w, arrow_h, + 0, cgs.media.mAutomapPlayerIcon ); + else +#endif + CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + xOffset, y + RADAR_RADIUS, arrow_w, arrow_h, + 0, cgs.media.mAutomapPlayerIcon ); +#ifdef _XBOX + } +#endif + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + return y+(SPLITSCREEN_RADAR_RADIUS*2); + else +#endif + return y+(RADAR_RADIUS*2); +} + +/* +================= +CG_DrawTimer +================= +*/ +static float CG_DrawTimer( float y ) { + char *s; + int w; + int mins, seconds, tens; + int msec; + int xOffset = 0; + +#ifdef _XBOX + xOffset = -40; + + if (ClientManager::ActiveClientNum() != 0) + { + return y; + } +#endif + + msec = cg->time - cgs.levelStartTime; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + s = va( "%i:%i%i", mins, tens, seconds ); + w = RE_Font_StrLenPixels( s, 1, 1.0f ); + + RE_Font_DrawString( 635 - w + xOffset, y + 2, s, g_color_table[7], 1, -1, 1.0f ); + + return y + RE_Font_HeightPixels( 1, 1.0f ) + 4; +} + +static void CG_DrawPowerupIcons(int y) +{ + int j; + int ico_size = 64; + //int y = ico_size/2; + int xOffset = 0; + gitem_t *item; + +#ifdef _XBOX + xOffset = -40; +#endif + + if (!cg->snap) + { + return; + } + + y += 16; + +#ifdef _XBOX + if (ClientManager::splitScreenMode == qtrue) + { + ico_size = 32; + if (ClientManager::ActiveClientNum() == 0) + y = 40; + else + y = 220 + 40; + + // This stuff automatically gets pulled to the left in splitscreen as to + // not overlap with the radar + xOffset -= 100; + + /*if(cg->widescreen) + xOffset -= 40;*/ + } +#endif + + for (j = 0; j <= PW_NUM_POWERUPS; j++) + { + if (cg->snap->ps.powerups[j] > cg->time) + { + int secondsleft = (cg->snap->ps.powerups[j] - cg->time)/1000; + + item = BG_FindItemForPowerup( j ); + + if (item) + { + int icoShader = 0; + if (cgs.gametype == GT_CTY && (j == PW_REDFLAG || j == PW_BLUEFLAG)) + { + if (j == PW_REDFLAG) + { + icoShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_ys" ); + } + else + { + icoShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_ys" ); + } + } + else + { + icoShader = trap_R_RegisterShader( item->icon ); + } + +#ifdef _XBOX + if(cg->widescreen) + CG_DrawPic( (720-(ico_size*1.1)) + xOffset, y, ico_size, ico_size, icoShader ); + else +#endif + CG_DrawPic( (640-(ico_size*1.1)) + xOffset, y, ico_size, ico_size, icoShader ); + + y += ico_size; + + if (j != PW_REDFLAG && j != PW_BLUEFLAG && secondsleft < 999) + { +#ifdef _XBOX + if(cg->widescreen) + UI_DrawProportionalString((720-(ico_size*1.1))+(ico_size/2) + xOffset, y-8, va("%i", secondsleft), UI_CENTER | UI_BIGFONT | UI_DROPSHADOW, colorTable[CT_WHITE]); + else +#endif + UI_DrawProportionalString((640-(ico_size*1.1))+(ico_size/2) + xOffset, y-8, va("%i", secondsleft), UI_CENTER | UI_BIGFONT | UI_DROPSHADOW, colorTable[CT_WHITE]); + } + + y += (ico_size/3); + } + } + } +} + + +/* +===================== +CG_DrawUpperRight + +===================== +*/ +float CG_DrawVote(float y); + +static void CG_DrawUpperRight( void ) { + float y; + +#ifdef _XBOX + y = 50; + if(ClientManager::ActiveClientNum() == 1) + y = 240 + 25; +#else + y = 0; +#endif + + trap_R_SetColor( colorTable[CT_WHITE] ); + +// if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) { +// y = CG_DrawTeamOverlay( y, qtrue, qtrue ); +// } +// if ( cg_drawSnapshot.integer ) { +// y = CG_DrawSnapshot( y ); +// } + + if ( cg_drawFPS.integer ) { + y = CG_DrawFPS( y ); + } + if ( cg_drawTimer.integer ) { + y = CG_DrawTimer( y ); + } + + if ( ( cgs.gametype >= GT_TEAM || cg->predictedPlayerState.m_iVehicleNum ) + && cg_drawRadar.integer ) + {//draw Radar in Siege mode or when in a vehicle of any kind + y = CG_DrawRadar ( y ); + } + + y = CG_DrawEnemyInfo ( y ); + + y = CG_DrawMiniScoreboard ( y ); + + y = CG_DrawVote(y); + + CG_DrawPowerupIcons(y); +} + + +/* +============== +CG_DrawDisconnect + +Should we draw something differnet for long lag vs no packets? +============== +*/ +static void CG_DrawDisconnect( void ) { + float x, y; + int cmdNum; + usercmd_t cmd; + const char *s; + int w; // bk010215 - FIXME char message[1024]; + + if (cg->mMapChange) + { + s = CG_GetStringEdString("MP_INGAME", "SERVER_CHANGING_MAPS"); // s = "Server Changing Maps"; + w = RE_Font_StrLenPixels( s, 1, 1.0f ); + RE_Font_DrawString( 320 - w/2, 100, s, g_color_table[7], 1, -1, 1.0f ); + + s = CG_GetStringEdString("MP_INGAME", "PLEASE_WAIT"); // s = "Please wait..."; + w = RE_Font_StrLenPixels( s, 1, 1.0f ); + RE_Font_DrawString( 320 - w/2, 200, s, g_color_table[7], 1, -1, 1.0f ); + return; + } + + // draw the phone jack if we are completely past our buffers + cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &cmd ); + if ( cmd.serverTime <= cg->snap->ps.commandTime + || cmd.serverTime > cg->time ) { // special check for map_restart // bk 0102165 - FIXME + return; + } + + // also add text in center of screen + s = CG_GetStringEdString("MP_INGAME", "CONNECTION_INTERRUPTED"); // s = "Connection Interrupted"; // bk 010215 - FIXME + w = RE_Font_StrLenPixels( s, 1, 1.0f ); + RE_Font_DrawString( 320 - w/2, 100, s, g_color_table[7], 1, -1, 1.0f ); +} + + +/* +============== +CG_DrawLagometer +============== +*/ +static void CG_DrawLagometer( void ) +{ + CG_DrawDisconnect(); + return; +} + +void CG_DrawSiegeMessage( const char *str, int objectiveScreen ) +{ +// if (!( trap_Key_GetCatcher() & KEYCATCH_UI )) + { + trap_OpenUIMenu(UIMENU_CLOSEALL); + trap_Cvar_Set("cg_siegeMessage", str); + if (objectiveScreen) + { + trap_OpenUIMenu(UIMENU_SIEGEOBJECTIVES); + } + else + { + trap_OpenUIMenu(UIMENU_SIEGEMESSAGE); + } + } +} + +void CG_DrawSiegeMessageNonMenu( const char *str ) +{ + char text[1024]; + if (str[0]=='@') + { + trap_SP_GetStringTextString(str+1, text, sizeof(text)); + str = text; + } + CG_CenterPrint(str, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH); +} + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + + +/* +============== +CG_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void CG_CenterPrint( const char *str, int y, int charWidth ) { + char *s; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + y = (240 - ((480 - y) / 2)) + 60; + + if(ClientManager::ActiveClientNum() == 1) + y += 220; + } +#endif + + Q_strncpyz( cg->centerPrint, str, sizeof(cg->centerPrint) ); + + cg->centerPrintTime = cg->time; + cg->centerPrintY = y; + cg->centerPrintCharWidth = charWidth; + + // count the number of lines for centering + cg->centerPrintLines = 1; + s = cg->centerPrint; + while( *s ) { + if (*s == '\n') + cg->centerPrintLines++; + s++; + } +} + + +/* +=================== +CG_DrawCenterString +=================== +*/ +static void CG_DrawCenterString( void ) { + char *start; + int l; + int x, y, w; + int h; + float *color; + const float scale = 1.0; //0.5 + + if ( !cg->centerPrintTime ) { + return; + } + + color = CG_FadeColor( cg->centerPrintTime, 1000 * cg_centertime.value ); + if ( !color ) { + return; + } + + trap_R_SetColor( color ); + + start = cg->centerPrint; + + y = cg->centerPrintY - cg->centerPrintLines * BIGCHAR_HEIGHT / 2; + + while ( 1 ) { + char linebuffer[1024]; + + for ( l = 0; l < 50; l++ ) { + if ( !start[l] || start[l] == '\n' ) { + break; + } + linebuffer[l] = start[l]; + } + linebuffer[l] = 0; + + w = CG_Text_Width(linebuffer, scale, FONT_MEDIUM); + h = CG_Text_Height(linebuffer, scale, FONT_MEDIUM); +#ifdef _XBOX + if(cg->widescreen) + x = (720 - w) / 2; + else +#endif + x = (SCREEN_WIDTH - w) / 2; + CG_Text_Paint(x, y + h, scale, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM); + y += h + 6; + + while ( *start && ( *start != '\n' ) ) { + start++; + } + if ( !*start ) { + break; + } + start++; + } + + trap_R_SetColor( NULL ); +} + + + +/* +================================================================================ + +CROSSHAIR + +================================================================================ +*/ + +#define HEALTH_WIDTH 50.0f +#define HEALTH_HEIGHT 5.0f + +//see if we can draw some extra info on this guy based on our class +void CG_DrawSiegeInfo(centity_t *cent, float chX, float chY, float chW, float chH) +{ + siegeExtended_t *se = &cg_siegeExtendedData[cent->currentState.number]; + clientInfo_t *ci; + const char *configstring, *v; + siegeClass_t *siegeClass; + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x; + float y; + float percent; + int ammoMax; + + assert(cent->currentState.number < MAX_CLIENTS); + + if (se->lastUpdated > cg->time) + { //strange, shouldn't happen + return; + } + + if ((cg->time - se->lastUpdated) > 10000) + { //if you haven't received a status update on this guy in 10 seconds, forget about it + return; + } + + if (cent->currentState.eFlags & EF_DEAD) + { //he's dead, don't display info on him + return; + } + + if (cent->currentState.weapon != se->weapon) + { //data is invalidated until it syncs back again + return; + } + + ci = &cgs.clientinfo[cent->currentState.number]; + if (ci->team != cg->predictedPlayerState.persistant[PERS_TEAM]) + { //not on the same team + return; + } + + configstring = CG_ConfigString( cg->predictedPlayerState.clientNum + CS_PLAYERS ); + v = Info_ValueForKey( configstring, "siegeclass" ); + + if (!v || !v[0]) + { //don't have siege class in info? + return; + } + + siegeClass = BG_SiegeFindClassByName(v); + + if (!siegeClass) + { //invalid + return; + } + + if (!(siegeClass->classflags & (1<health/(float)se->maxhealth)*HEALTH_WIDTH; + + //color of the bar + aColor[0] = 0.0f; + aColor[1] = 1.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing health" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.4f; + + //draw the background (black) + CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor); + + //then draw the other part greyed out + CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor); + + + //now draw his ammo + ammoMax = ammoData[weaponData[cent->currentState.weapon].ammoIndex].max; + if ( (cent->currentState.eFlags & EF_DOUBLE_AMMO) ) + { + ammoMax *= 2; + } + + x = chX+((chW/2)-(HEALTH_WIDTH/2)); + y = (chY+chH) + HEALTH_HEIGHT + 10.0f; + + if (!weaponData[cent->currentState.weapon].energyPerShot && + !weaponData[cent->currentState.weapon].altEnergyPerShot) + { //a weapon that takes no ammo, so show full + percent = HEALTH_WIDTH; + } + else + { + percent = ((float)se->ammo/(float)ammoMax)*HEALTH_WIDTH; + } + + //color of the bar + aColor[0] = 1.0f; + aColor[1] = 1.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing health" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.4f; + + //draw the background (black) + CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor); + + //then draw the other part greyed out + CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor); +} + +//draw the health bar based on current "health" and maxhealth +void CG_DrawHealthBar(centity_t *cent, float chX, float chY, float chW, float chH) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = chX+((chW/2)-(HEALTH_WIDTH/2)); + float y = (chY+chH) + 8.0f; + float percent = ((float)cent->currentState.health/(float)cent->currentState.maxhealth)*HEALTH_WIDTH; + // The HEALTH_WIDTH check here is a hacky attempt to fix all the crappy ents that don't properly + // set health or maxhealth when they die, thus allowing infinite length health bars for a few frames. - BTO + if (percent <= 0 || percent > HEALTH_WIDTH) + { + return; + } + + //color of the bar + if (!cent->currentState.teamowner || cgs.gametype < GT_TEAM) + { //not owned by a team or teamplay + aColor[0] = 1.0f; + aColor[1] = 1.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + } + else if (cent->currentState.teamowner == cg->predictedPlayerState.persistant[PERS_TEAM]) + { //owned by my team + aColor[0] = 0.0f; + aColor[1] = 1.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + } + else + { //hostile + aColor[0] = 1.0f; + aColor[1] = 0.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + } + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing health" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.4f; + + //draw the background (black) + CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor); + + //then draw the other part greyed out + CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor); +} + +//same routine (at least for now), draw progress of a "hack" or whatever +void CG_DrawHaqrBar(float chX, float chY, float chW, float chH) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = chX+((chW/2)-(HEALTH_WIDTH/2)); + float y = (chY+chH) + 8.0f; + float percent = (((float)cg->predictedPlayerState.hackingTime-(float)cg->time)/(float)cg->predictedPlayerState.hackingBaseTime)*HEALTH_WIDTH; + + if (percent > HEALTH_WIDTH || + percent < 1.0f) + { + return; + } + + //color of the bar + aColor[0] = 1.0f; + aColor[1] = 1.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out done area + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.1f; + + //draw the background (black) + CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor); + + //then draw the other part greyed out + CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor); + + //draw the hacker icon + CG_DrawPic(x, y-HEALTH_WIDTH, HEALTH_WIDTH, HEALTH_WIDTH, cgs.media.hackerIconShader); +} + +//generic timing bar +int cg_genericTimerBar = 0; +int cg_genericTimerDur = 0; +vec4_t cg_genericTimerColor; +#define CGTIMERBAR_H 50.0f +#define CGTIMERBAR_W 10.0f +#define CGTIMERBAR_X (SCREEN_WIDTH-CGTIMERBAR_W-53.0f) +#define CGTIMERBAR_Y (SCREEN_HEIGHT-CGTIMERBAR_H-96.0f) +void CG_DrawGenericTimerBar(void) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = CGTIMERBAR_X; + float y = CGTIMERBAR_Y; + float percent = ((float)(cg_genericTimerBar-cg->time)/(float)cg_genericTimerDur)*CGTIMERBAR_H; + + if (percent > CGTIMERBAR_H) + { + return; + } + + if (percent < 0.1f) + { + percent = 0.1f; + } + + //color of the bar + aColor[0] = cg_genericTimerColor[0]; + aColor[1] = cg_genericTimerColor[1]; + aColor[2] = cg_genericTimerColor[2]; + aColor[3] = cg_genericTimerColor[3]; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing fuel" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.1f; + + //draw the background (black) + CG_DrawRect(x, y, CGTIMERBAR_W, CGTIMERBAR_H, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f+(CGTIMERBAR_H-percent), CGTIMERBAR_W-2.0f, CGTIMERBAR_H-1.0f-(CGTIMERBAR_H-percent), aColor); + + //then draw the other part greyed out + CG_FillRect(x+1.0f, y+1.0f, CGTIMERBAR_W-2.0f, CGTIMERBAR_H-percent, cColor); +} + +/* +================= +CG_DrawCrosshair +================= +*/ + +#ifdef _XBOX +//int cg_crossHairStatus = 0; +#endif + +float cg_crosshairPrevPosX = 0; +float cg_crosshairPrevPosY = 0; +#define CRAZY_CROSSHAIR_MAX_ERROR_X (100.0f*640.0f/480.0f) +#define CRAZY_CROSSHAIR_MAX_ERROR_Y (100.0f) +void CG_LerpCrosshairPos( float *x, float *y ) +{ + if ( cg_crosshairPrevPosX ) + {//blend from old pos + float maxMove; +#ifdef _XBOX + if(cg->widescreen) + maxMove = 30.0f * ((float)cg->frametime/500.0f) * 720.0f/480.0f; + else +#endif + maxMove = 30.0f * ((float)cg->frametime/500.0f) * 640.0f/480.0f; + float xDiff = (*x - cg_crosshairPrevPosX); + if ( fabs(xDiff) > CRAZY_CROSSHAIR_MAX_ERROR_X ) + { + maxMove = CRAZY_CROSSHAIR_MAX_ERROR_X; + } + if ( xDiff > maxMove ) + { + *x = cg_crosshairPrevPosX + maxMove; + } + else if ( xDiff < -maxMove ) + { + *x = cg_crosshairPrevPosX - maxMove; + } + } + cg_crosshairPrevPosX = *x; + + if ( cg_crosshairPrevPosY ) + {//blend from old pos + float maxMove = 30.0f * ((float)cg->frametime/500.0f); + float yDiff = (*y - cg_crosshairPrevPosY); + if ( fabs(yDiff) > CRAZY_CROSSHAIR_MAX_ERROR_Y ) + { + maxMove = CRAZY_CROSSHAIR_MAX_ERROR_X; + } + if ( yDiff > maxMove ) + { + *y = cg_crosshairPrevPosY + maxMove; + } + else if ( yDiff < -maxMove ) + { + *y = cg_crosshairPrevPosY - maxMove; + } + } + cg_crosshairPrevPosY = *y; +} + +vec3_t cg_crosshairPos={0,0,0}; +static void CG_DrawCrosshair( vec3_t worldPoint, int chEntValid ) { + float w, h; + qhandle_t hShader = 0; + float f; + float x, y; + qboolean corona = qfalse; + vec4_t ecolor = {0,0,0,0}; + centity_t *crossEnt = NULL; + float chX, chY; + +#ifdef _XBOX + ClientManager::ActiveClient().cg_crossHairStatus = 0; +#endif + + if ( worldPoint ) + { + VectorCopy( worldPoint, cg_crosshairPos ); + } + + if ( !cg_drawCrosshair.integer ) + { + return; + } + + if (cg->snap->ps.fallingToDeath) + { + return; + } + + if ( cg->predictedPlayerState.zoomMode != 0 ) + {//not while scoped + return; + } + + if ( cg_crosshairHealth.integer ) + { + vec4_t hcolor; + + CG_ColorForHealth( hcolor ); + trap_R_SetColor( hcolor ); + } + else + { + //set color based on what kind of ent is under crosshair + if ( cg->crosshairClientNum >= ENTITYNUM_WORLD ) + { + trap_R_SetColor( NULL ); + } + //rwwFIXMEFIXME: Write this a different way, it's getting a bit too sloppy looking + else if (chEntValid && + (cg_entities[cg->crosshairClientNum].currentState.number < MAX_CLIENTS || + cg_entities[cg->crosshairClientNum].currentState.eType == ET_NPC || + cg_entities[cg->crosshairClientNum].currentState.shouldtarget || + cg_entities[cg->crosshairClientNum].currentState.health || //always show ents with health data under crosshair + (cg_entities[cg->crosshairClientNum].currentState.eType == ET_MOVER && cg_entities[cg->crosshairClientNum].currentState.bolt1 && cg->predictedPlayerState.weapon == WP_SABER) || + (cg_entities[cg->crosshairClientNum].currentState.eType == ET_MOVER && cg_entities[cg->crosshairClientNum].currentState.teamowner))) + { + crossEnt = &cg_entities[cg->crosshairClientNum]; + + if ( crossEnt->currentState.powerups & (1 <currentState.number < MAX_CLIENTS ) + { + if (cgs.gametype >= GT_TEAM && + cgs.clientinfo[crossEnt->currentState.number].team == cgs.clientinfo[cg->snap->ps.clientNum].team ) + { + //Allies are green + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else + { + if (cgs.gametype == GT_POWERDUEL && + cgs.clientinfo[crossEnt->currentState.number].duelTeam == cgs.clientinfo[cg->snap->ps.clientNum].duelTeam) + { //on the same duel team in powerduel, so he's a friend + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else + { //Enemies are red + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + ClientManager::ActiveClient().cg_crossHairStatus = 1; +#endif + } + } + + if (cg->snap->ps.duelInProgress) + { + if (crossEnt->currentState.number != cg->snap->ps.duelIndex) + { //grey out crosshair for everyone but your foe if you're in a duel + ecolor[0] = 0.4; + ecolor[1] = 0.4; + ecolor[2] = 0.4; + } + } + else if (crossEnt->currentState.bolt1) + { //this fellow is in a duel. We just checked if we were in a duel above, so + //this means we aren't and he is. Which of course means our crosshair greys out over him. + ecolor[0] = 0.4; + ecolor[1] = 0.4; + ecolor[2] = 0.4; + } + } + else if (crossEnt->currentState.shouldtarget || crossEnt->currentState.eType == ET_NPC) + { + //VectorCopy( crossEnt->startRGBA, ecolor ); + if ( !ecolor[0] && !ecolor[1] && !ecolor[2] ) + { + // We really don't want black, so set it to yellow + ecolor[0] = 1.0F;//R + ecolor[1] = 0.8F;//G + ecolor[2] = 0.3F;//B + } + + if (crossEnt->currentState.eType == ET_NPC) + { + int plTeam; + if (cgs.gametype == GT_SIEGE) + { + plTeam = cg->predictedPlayerState.persistant[PERS_TEAM]; + } + else + { + plTeam = NPCTEAM_PLAYER; + } + + if ( crossEnt->currentState.powerups & (1 <currentState.teamowner ) + { //not on a team + if (!crossEnt->currentState.teamowner || + crossEnt->currentState.NPC_class == CLASS_VEHICLE) + { //neutral + if (crossEnt->currentState.owner < MAX_CLIENTS) + { //base color on who is pilotting this thing + clientInfo_t *ci = &cgs.clientinfo[crossEnt->currentState.owner]; + + if (cgs.gametype >= GT_TEAM && ci->team == cg->predictedPlayerState.persistant[PERS_TEAM]) + { //friendly + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else + { //hostile + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + ClientManager::ActiveClient().cg_crossHairStatus = 1; +#endif + } + } + else + { //unmanned + ecolor[0] = 1.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + } + else + { + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + ClientManager::ActiveClient().cg_crossHairStatus = 1; +#endif + } + } + else if ( crossEnt->currentState.teamowner != plTeam ) + {// on enemy team + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + ClientManager::ActiveClient().cg_crossHairStatus = 1; +#endif + } + else + { //a friend + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + } + else if ( crossEnt->currentState.teamowner == TEAM_RED + || crossEnt->currentState.teamowner == TEAM_BLUE ) + { + if (cgs.gametype < GT_TEAM) + { //not teamplay, just neutral then + ecolor[0] = 1.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else if ( crossEnt->currentState.teamowner != cgs.clientinfo[cg->snap->ps.clientNum].team ) + { //on the enemy team + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + ClientManager::ActiveClient().cg_crossHairStatus = 1; +#endif + } + else + { //on my team + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + } + else if (crossEnt->currentState.owner == cg->snap->ps.clientNum || + (cgs.gametype >= GT_TEAM && crossEnt->currentState.teamowner == cgs.clientinfo[cg->snap->ps.clientNum].team)) + { + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else if (crossEnt->currentState.teamowner == 16 || + (cgs.gametype >= GT_TEAM && crossEnt->currentState.teamowner && crossEnt->currentState.teamowner != cgs.clientinfo[cg->snap->ps.clientNum].team)) + { + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + ClientManager::ActiveClient().cg_crossHairStatus = 1; +#endif + } + } + else if (crossEnt->currentState.eType == ET_MOVER && crossEnt->currentState.bolt1 && cg->predictedPlayerState.weapon == WP_SABER) + { //can push/pull this mover. Only show it if we're using the saber. + ecolor[0] = 0.2f; + ecolor[1] = 0.5f; + ecolor[2] = 1.0f; + + corona = qtrue; + } + else if (crossEnt->currentState.eType == ET_MOVER && crossEnt->currentState.teamowner) + { //a team owns this - if it's my team green, if not red, if not teamplay then yellow + if (cgs.gametype < GT_TEAM) + { + ecolor[0] = 1.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else if (cg->predictedPlayerState.persistant[PERS_TEAM] != crossEnt->currentState.teamowner) + { //not my team + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + ClientManager::ActiveClient().cg_crossHairStatus = 1; +#endif + } + else + { //my team + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + } + else if (crossEnt->currentState.health) + { + if (!crossEnt->currentState.teamowner || cgs.gametype < GT_TEAM) + { //not owned by a team or teamplay + ecolor[0] = 1.0f; + ecolor[1] = 1.0f; + ecolor[2] = 0.0f; + } + else if (crossEnt->currentState.teamowner == cg->predictedPlayerState.persistant[PERS_TEAM]) + { //owned by my team + ecolor[0] = 0.0f; + ecolor[1] = 1.0f; + ecolor[2] = 0.0f; + } + else + { //hostile + ecolor[0] = 1.0f; + ecolor[1] = 0.0f; + ecolor[2] = 0.0f; +#ifdef _XBOX + ClientManager::ActiveClient().cg_crossHairStatus = 1; +#endif + } + } + + ecolor[3] = 1.0; + + trap_R_SetColor( ecolor ); + } + else + { + trap_R_SetColor( NULL ); + } + } + + if ( cg->predictedPlayerState.m_iVehicleNum ) + {//I'm in a vehicle + centity_t *vehCent = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + if ( vehCent + && vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo + && vehCent->m_pVehicle->m_pVehicleInfo->crosshairShaderHandle ) + { + hShader = vehCent->m_pVehicle->m_pVehicleInfo->crosshairShaderHandle; + } + //bigger by default + w = cg_crosshairSize.value*2.0f; + h = w; + } + else + { + w = h = cg_crosshairSize.value; + } + + // pulse the size of the crosshair when picking up items + f = cg->time - cg->itemPickupBlendTime; + if ( f > 0 && f < ITEM_BLOB_TIME ) { + f /= ITEM_BLOB_TIME; + w *= ( 1 + f ); + h *= ( 1 + f ); + } + + if ( worldPoint && VectorLength( worldPoint ) ) + { + if ( !CG_WorldCoordToScreenCoordFloat( worldPoint, &x, &y ) ) + {//off screen, don't draw it + return; + } + //CG_LerpCrosshairPos( &x, &y ); + x -= 320; + y -= 240; +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + y += 120; +#endif + } + else + { + x = cg_crosshairX.integer; + y = cg_crosshairY.integer; + } + + if ( !hShader ) + { + hShader = cgs.media.crosshairShader[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ]; + } + +#ifdef _XBOX + if(cg->widescreen) + chX = x + cg->refdef.x + 0.5 * (720 - w); + else +#endif + chX = x + cg->refdef.x + 0.5 * (640 - w); + chY = y + cg->refdef.y + 0.5 * (480 - h); + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + trap_R_DrawStretchPic( x + cg->refdef.x + (cgs.glconfig.vidWidth - w) / 2, + y + cg->refdef.y + (cgs.glconfig.vidHeight - h) / 2, + w , h , 0, 0, 1, 1, hShader ); + } + else +#endif + trap_R_DrawStretchPic( chX, chY, w, h, 0, 0, 1, 1, hShader ); + + //draw a health bar directly under the crosshair if we're looking at something + //that takes damage + if (crossEnt && + crossEnt->currentState.maxhealth) + { + CG_DrawHealthBar(crossEnt, chX, chY, w, h); + chY += HEALTH_HEIGHT*2; + } + else if (crossEnt && crossEnt->currentState.number < MAX_CLIENTS) + { + if (cgs.gametype == GT_SIEGE) + { + CG_DrawSiegeInfo(crossEnt, chX, chY, w, h); + chY += HEALTH_HEIGHT*4; + } + if (cg->crosshairVehNum && cg->time == cg->crosshairVehTime) + { //it was in the crosshair this frame + centity_t *hisVeh = &cg_entities[cg->crosshairVehNum]; + + if (hisVeh->currentState.eType == ET_NPC && + hisVeh->currentState.NPC_class == CLASS_VEHICLE && + hisVeh->currentState.maxhealth && + hisVeh->m_pVehicle) + { //draw the health for this vehicle + CG_DrawHealthBar(hisVeh, chX, chY, w, h); + chY += HEALTH_HEIGHT*2; + } + } + } + + if (cg->predictedPlayerState.hackingTime) + { //hacking something + CG_DrawHaqrBar(chX, chY, w, h); + } + + if (cg_genericTimerBar > cg->time) + { //draw generic timing bar, can be used for whatever + CG_DrawGenericTimerBar(); + } + + if ( corona ) // drawing extra bits + { + ecolor[3] = 0.5f; + ecolor[0] = ecolor[1] = ecolor[2] = (1 - ecolor[3]) * ( sin( cg->time * 0.001f ) * 0.08f + 0.35f ); // don't draw full color + ecolor[3] = 1.0f; + + trap_R_SetColor( ecolor ); + + w *= 2.0f; + h *= 2.0f; + +#ifdef _XBOX + if(cg->widescreen) + trap_R_DrawStretchPic( x + cg->refdef.x + 0.5 * (720 - w), + y + cg->refdef.y + 0.5 * (480 - h), + w, h, 0, 0, 1, 1, cgs.media.forceCoronaShader ); + else +#endif + trap_R_DrawStretchPic( x + cg->refdef.x + 0.5 * (640 - w), + y + cg->refdef.y + 0.5 * (480 - h), + w, h, 0, 0, 1, 1, cgs.media.forceCoronaShader ); + } +} + +qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y) +{ + float xcenter, ycenter; + vec3_t local, transformed; + vec3_t vfwd; + vec3_t vright; + vec3_t vup; + float xzi; + float yzi; + +// xcenter = cg->refdef.width / 2;//gives screen coords adjusted for resolution +// ycenter = cg->refdef.height / 2;//gives screen coords adjusted for resolution + + //NOTE: did it this way because most draw functions expect virtual 640x480 coords + // and adjust them for current resolution +#ifdef _XBOX + if(cg->widescreen) + xcenter = 720.0f / 2.0f; + else +#endif + xcenter = 640.0f / 2.0f;//gives screen coords in virtual 640x480, to be adjusted when drawn + ycenter = 480.0f / 2.0f;//gives screen coords in virtual 640x480, to be adjusted when drawn + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + ycenter -= 120; +#endif + + AngleVectors (cg->refdef.viewangles, vfwd, vright, vup); + + VectorSubtract (worldCoord, cg->refdef.vieworg, local); + + transformed[0] = DotProduct(local,vright); + transformed[1] = DotProduct(local,vup); + transformed[2] = DotProduct(local,vfwd); + + // Make sure Z is not negative. + if(transformed[2] < 0.01f) + { + return qfalse; + } + + xzi = xcenter / transformed[2] * (96.0f/cg->refdef.fov_x); + yzi = ycenter / transformed[2] * (102.0f/cg->refdef.fov_y); + + *x = xcenter + xzi * transformed[0]; + *y = ycenter - yzi * transformed[1]; + + return qtrue; +} + +qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y ) +{ + float xF, yF; + qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF ); + *x = (int)xF; + *y = (int)yF; + return retVal; +} + +/* +==================== +CG_SaberClashFlare +==================== +*/ +int cg_saberFlashTime = 0; +vec3_t cg_saberFlashPos = {0, 0, 0}; +void CG_SaberClashFlare( void ) +{ + int t, maxTime = 150; + vec3_t dif; + vec3_t color; + int x,y; + float v, len; + trace_t tr; + + t = cg->time - cg_saberFlashTime; + + if ( t <= 0 || t >= maxTime ) + { + return; + } + + // Don't do clashes for things that are behind us + VectorSubtract( cg_saberFlashPos, cg->refdef.vieworg, dif ); + + if ( DotProduct( dif, cg->refdef.viewaxis[0] ) < 0.2 ) + { + return; + } + + CG_Trace( &tr, cg->refdef.vieworg, NULL, NULL, cg_saberFlashPos, -1, CONTENTS_SOLID ); + + if ( tr.fraction < 1.0f ) + { + return; + } + + len = VectorNormalize( dif ); + + // clamp to a known range + /* + if ( len > 800 ) + { + len = 800; + } + */ + if ( len > 1200 ) + { + return; + } + + v = ( 1.0f - ((float)t / maxTime )) * ((1.0f - ( len / 800.0f )) * 2.0f + 0.35f); + if (v < 0.001f) + { + v = 0.001f; + } + + CG_WorldCoordToScreenCoord( cg_saberFlashPos, &x, &y ); + + VectorSet( color, 0.8f, 0.8f, 0.8f ); + trap_R_SetColor( color ); + + CG_DrawPic( x - ( v * 300 ), y - ( v * 300 ), + v * 600, v * 600, + trap_R_RegisterShader( "gfx/effects/saberFlare" )); +} + +void CG_BracketEntity( centity_t *cent, float radius ) +{ + trace_t tr; + vec3_t dif; + float len, size, lineLength, lineWidth; + float x, y; + clientInfo_t *local; + + VectorSubtract( cent->lerpOrigin, cg->refdef.vieworg, dif ); + len = VectorNormalize( dif ); + + if ( cg->crosshairClientNum != cent->currentState.clientNum + && (!cg->snap||cg->snap->ps.rocketLockIndex!= cent->currentState.clientNum) ) + {//if they're the entity you're locking onto or under your crosshair, always draw bracket + //Hmm... for now, if they're closer than 2000, don't bracket? + if ( len < 2000.0f ) + { + return; + } + + CG_Trace( &tr, cg->refdef.vieworg, NULL, NULL, cent->lerpOrigin, -1, CONTENTS_OPAQUE ); + + //don't bracket if can't see them + if ( tr.fraction < 1.0f ) + { + return; + } + } + + if ( !CG_WorldCoordToScreenCoordFloat(cent->lerpOrigin, &x, &y) ) + {//off-screen, don't draw it + return; + } + + //just to see if it's centered + //CG_DrawPic( x-2, y-2, 4, 4, cgs.media.whiteShader ); + + local = &cgs.clientinfo[cg->snap->ps.clientNum]; + if ( cent->currentState.m_iVehicleNum //vehicle has a driver + && cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].infoValid ) + { + if ( cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].team == local->team ) + { + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_GREEN)] ); + } + else + { + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + } + } + else if ( cent->currentState.teamowner ) + { + if ( cent->currentState.teamowner != cg->predictedPlayerState.persistant[PERS_TEAM] ) + {// on enemy team + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + } + else + { //a friend + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_GREEN)] ); + } + } + else + {//FIXME: if we want to ever bracket anything besides vehicles (like siege objectives we want to blow up), we should handle the coloring here + trap_R_SetColor ( NULL ); + } + + if ( len <= 1.0f ) + {//super-close, max out at 400 times radius (which is HUGE) + size = radius*400.0f; + } + else + {//scale by dist + size = radius*(400.0f/len); + } + + if ( size < 1.0f ) + { + size = 1.0f; + } + + //length scales with dist + lineLength = (size*0.1f); + if ( lineLength < 0.5f ) + {//always visible + lineLength = 0.5f; + } + //always visible width + lineWidth = 1.0f; + + x -= (size*0.5f); + y -= (size*0.5f); + + /* + if ( x >= 0 && x <= 640 + && y >= 0 && y <= 480 ) + */ + {//brackets would be drawn on the screen, so draw them + //upper left corner + //horz + CG_DrawPic( x, y, lineLength, lineWidth, cgs.media.whiteShader ); + //vert + CG_DrawPic( x, y, lineWidth, lineLength, cgs.media.whiteShader ); + //upper right corner + //horz + CG_DrawPic( x+size-lineLength, y, lineLength, lineWidth, cgs.media.whiteShader ); + //vert + CG_DrawPic( x+size-lineWidth, y, lineWidth, lineLength, cgs.media.whiteShader ); + //lower left corner + //horz + CG_DrawPic( x, y+size-lineWidth, lineLength, lineWidth, cgs.media.whiteShader ); + //vert + CG_DrawPic( x, y+size-lineLength, lineWidth, lineLength, cgs.media.whiteShader ); + //lower right corner + //horz + CG_DrawPic( x+size-lineLength, y+size-lineWidth, lineLength, lineWidth, cgs.media.whiteShader ); + //vert + CG_DrawPic( x+size-lineWidth, y+size-lineLength, lineWidth, lineLength, cgs.media.whiteShader ); + } +} + +qboolean CG_InFighter( void ) +{ + if ( cg->predictedPlayerState.m_iVehicleNum ) + {//I'm in a vehicle + centity_t *vehCent = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + if ( vehCent + && vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo + && vehCent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + {//I'm in a fighter + return qtrue; + } + } + return qfalse; +} + +qboolean CG_InATST( void ) +{ + if ( cg->predictedPlayerState.m_iVehicleNum ) + {//I'm in a vehicle + centity_t *vehCent = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + if ( vehCent + && vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo + && vehCent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) + {//I'm in an atst + return qtrue; + } + } + return qfalse; +} + +void CG_DrawBracketedEntities( void ) +{ + int i; + for ( i = 0; i < cg->bracketedEntityCount; i++ ) + { + centity_t *cent = &cg_entities[cg->bracketedEntities[i]]; + CG_BracketEntity( cent, CG_RadiusForCent( cent ) ); + } +} + +//-------------------------------------------------------------- +static void CG_DrawHolocronIcons(void) +//-------------------------------------------------------------- +{ +#ifndef _XBOX + + int icon_size = 40; + int i = 0; + int startx = 10; + int starty = 10;//SCREEN_HEIGHT - icon_size*3; + + int endx = icon_size; + int endy = icon_size; + + if (cg->snap->ps.zoomMode) + { //don't display over zoom mask + return; + } + + if (cgs.clientinfo[cg->snap->ps.clientNum].team == TEAM_SPECTATOR) + { + return; + } + + while (i < NUM_FORCE_POWERS) + { + if (cg->snap->ps.holocronBits & (1 << forcePowerSorted[i])) + { + CG_DrawPic( startx, starty, endx, endy, cgs.media.forcePowerIcons[forcePowerSorted[i]]); + starty += (icon_size+2); //+2 for spacing + if ((starty+icon_size) >= SCREEN_HEIGHT-80) + { + starty = 10;//SCREEN_HEIGHT - icon_size*3; + startx += (icon_size+2); + } + } + + i++; + } +#endif // _XBOX +} + +static qboolean CG_IsDurationPower(int power) +{ + if (power == FP_HEAL || + power == FP_SPEED || + power == FP_TELEPATHY || + power == FP_RAGE || + power == FP_PROTECT || + power == FP_ABSORB || + power == FP_SEE) + { + return qtrue; + } + + return qfalse; +} + +//-------------------------------------------------------------- +static void CG_DrawActivePowers(void) +//-------------------------------------------------------------- +{ + int icon_size = 40; + int i = 0; + int startx = 160;//icon_size*2+16; + int starty = 332;//SCREEN_HEIGHT - icon_size*2; + + int endx = icon_size; + int endy = icon_size; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + startx -= 27; + starty += 72; + + endx = endy = icon_size = 30; + + if(ClientManager::ActiveClientNum() == 0) + { + starty -= 220; + } + } +#endif + + if (cg->snap->ps.zoomMode) + { //don't display over zoom mask + return; + } + + if (cgs.clientinfo[cg->snap->ps.clientNum].team == TEAM_SPECTATOR) + { + return; + } + + while (i < NUM_FORCE_POWERS) + { + if ((cg->snap->ps.fd.forcePowersActive & (1 << forcePowerSorted[i])) && + CG_IsDurationPower(forcePowerSorted[i])) + { + CG_DrawPic( startx, starty, endx, endy, cgs.media.forcePowerIcons[forcePowerSorted[i]]); + startx += (icon_size+2); //+2 for spacing + int screenw; +#ifdef _XBOX + if(cg->widescreen) + screenw = 720; + else +#endif + screenw = SCREEN_WIDTH; + if ((startx+icon_size) >= screenw-80) + { + startx = icon_size*2+16; + starty += (icon_size+2); + } + } + + i++; + } + + //additionally, draw an icon force force rage recovery + if (cg->snap->ps.fd.forceRageRecoveryTime > cg->time) + { + CG_DrawPic( startx, starty, endx, endy, cgs.media.rageRecShader); + } +} + +//-------------------------------------------------------------- +static void CG_DrawRocketLocking( int lockEntNum, int lockTime ) +//-------------------------------------------------------------- +{ + int cx, cy; + vec3_t org; + static int oldDif = 0; + centity_t *cent = &cg_entities[lockEntNum]; + vec4_t color={0.0f,0.0f,0.0f,0.0f}; + float lockTimeInterval = ((cgs.gametype==GT_SIEGE)?2400.0f:1200.0f)/16.0f; + //FIXME: if in a vehicle, use the vehicle's lockOnTime... + int dif = (cg->time - cg->snap->ps.rocketLockTime)/lockTimeInterval; + int i; + + int yOff = 0; +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 1) + yOff = 220; +#endif + + if (!cg->snap->ps.rocketLockTime) + { + return; + } + + if (cgs.clientinfo[cg->snap->ps.clientNum].team == TEAM_SPECTATOR) + { + return; + } + + if ( cg->snap->ps.m_iVehicleNum ) + {//driving a vehicle + centity_t *veh = &cg_entities[cg->snap->ps.m_iVehicleNum]; + if ( veh->m_pVehicle ) + { + vehWeaponInfo_t *vehWeapon = NULL; + if ( cg->predictedVehicleState.weaponstate == WEAPON_CHARGING_ALT ) + { + if ( veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID > VEH_WEAPON_BASE + && veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID < MAX_VEH_WEAPONS ) + { + vehWeapon = &g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID]; + } + } + else + { + if ( veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID > VEH_WEAPON_BASE + && veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID < MAX_VEH_WEAPONS ) + { + vehWeapon = &g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID]; + } + } + if ( vehWeapon != NULL ) + {//we are trying to lock on with a valid vehicle weapon, so use *its* locktime, not the hard-coded one + if ( !vehWeapon->iLockOnTime ) + {//instant lock-on + dif = 10.0f; + } + else + {//use the custom vehicle lockOnTime + lockTimeInterval = (vehWeapon->iLockOnTime/16.0f); + dif = (cg->time - cg->snap->ps.rocketLockTime)/lockTimeInterval; + } + } + } + } + //We can't check to see in pmove if players are on the same team, so we resort + //to just not drawing the lock if a teammate is the locked on ent + if (cg->snap->ps.rocketLockIndex >= 0 && + cg->snap->ps.rocketLockIndex < ENTITYNUM_NONE) + { + clientInfo_t *ci = NULL; + + if (cg->snap->ps.rocketLockIndex < MAX_CLIENTS) + { + ci = &cgs.clientinfo[cg->snap->ps.rocketLockIndex]; + } + else + { + ci = cg_entities[cg->snap->ps.rocketLockIndex].npcClient; + } + + if (ci) + { + if (ci->team == cgs.clientinfo[cg->snap->ps.clientNum].team) + { + if (cgs.gametype >= GT_TEAM) + { + return; + } + } + else if (cgs.gametype >= GT_TEAM) + { + centity_t *hitEnt = &cg_entities[cg->snap->ps.rocketLockIndex]; + if (hitEnt->currentState.eType == ET_NPC && + hitEnt->currentState.NPC_class == CLASS_VEHICLE && + hitEnt->currentState.owner < ENTITYNUM_WORLD) + { //this is a vehicle, if it has a pilot and that pilot is on my team, then... + if (hitEnt->currentState.owner < MAX_CLIENTS) + { + ci = &cgs.clientinfo[hitEnt->currentState.owner]; + } + else + { + ci = cg_entities[hitEnt->currentState.owner].npcClient; + } + if (ci && ci->team == cgs.clientinfo[cg->snap->ps.clientNum].team) + { + return; + } + } + } + } + } + + if (cg->snap->ps.rocketLockTime != -1) + { + lastvalidlockdif = dif; + } + else + { + dif = lastvalidlockdif; + } + + if ( !cent ) + { + return; + } + + VectorCopy( cent->lerpOrigin, org ); + + if ( CG_WorldCoordToScreenCoord( org, &cx, &cy )) + { + // we care about distance from enemy to eye, so this is good enough + float sz = Distance( cent->lerpOrigin, cg->refdef.vieworg ) / 1024.0f; + + if ( sz > 1.0f ) + { + sz = 1.0f; + } + else if ( sz < 0.0f ) + { + sz = 0.0f; + } + + sz = (1.0f - sz) * (1.0f - sz) * 32 + 6; + + cy += sz * 0.5f; + + if ( dif < 0 ) + { + oldDif = 0; + return; + } + else if ( dif > 8 ) + { + dif = 8; + } + + // do sounds + if ( oldDif != dif ) + { + if ( dif == 8 ) + { + if ( cg->snap->ps.m_iVehicleNum ) + { + trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/vehicles/weapons/common/lock.wav" )); + } + else + { + trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/rocket/lock.wav" )); + } + } + else + { + if ( cg->snap->ps.m_iVehicleNum ) + { + trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/vehicles/weapons/common/tick.wav" )); + } + else + { + trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/rocket/tick.wav" )); + } + } + } + + oldDif = dif; + + for ( i = 0; i < dif; i++ ) + { + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 0.1f * i + 0.2f; + + trap_R_SetColor( color ); + + // our slices are offset by about 45 degrees. + CG_DrawRotatePic( cx - sz, cy - sz + yOff, sz, sz, i * 45.0f, trap_R_RegisterShaderNoMip( "gfx/2d/wedge" )); + } + + // we are locked and loaded baby + if ( dif == 8 ) + { + color[0] = color[1] = color[2] = sin( cg->time * 0.05f ) * 0.5f + 0.5f; + color[3] = 1.0f; // this art is additive, so the alpha value does nothing + + trap_R_SetColor( color ); + + CG_DrawPic( cx - sz, cy - sz * 2 + yOff, sz * 2, sz * 2, trap_R_RegisterShaderNoMip( "gfx/2d/lock" )); + } + } +} + +extern void CG_CalcVehMuzzle(Vehicle_t *pVeh, centity_t *ent, int muzzleNum); +qboolean CG_CalcVehicleMuzzlePoint( int entityNum, vec3_t start, vec3_t d_f, vec3_t d_rt, vec3_t d_up) +{ + centity_t *vehCent = &cg_entities[entityNum]; + if ( vehCent->m_pVehicle && vehCent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) + {//draw from barrels + VectorCopy( vehCent->lerpOrigin, start ); + start[2] += vehCent->m_pVehicle->m_pVehicleInfo->height-DEFAULT_MINS_2-48; + AngleVectors( vehCent->lerpAngles, d_f, d_rt, d_up ); + /* + mdxaBone_t boltMatrix; + int bolt; + vec3_t yawOnlyAngles; + + VectorSet( yawOnlyAngles, 0, vehCent->lerpAngles[YAW], 0 ); + + bolt = trap_G2API_AddBolt( vehCent->ghoul2, 0, "*flash1"); + trap_G2API_GetBoltMatrix( vehCent->ghoul2, 0, bolt, &boltMatrix, + yawOnlyAngles, vehCent->lerpOrigin, cg->time, + NULL, vehCent->modelScale ); + + // work the matrix axis stuff into the original axis and origins used. + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, start ); + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_X, d_f ); + VectorClear( d_rt );//don't really need this, do we? + VectorClear( d_up );//don't really need this, do we? + */ + } + else + { + //check to see if we're a turret gunner on this vehicle + if ( cg->predictedPlayerState.generic1 )//as a passenger + {//passenger in a vehicle + if ( vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo + && vehCent->m_pVehicle->m_pVehicleInfo->maxPassengers ) + {//a vehicle capable of carrying passengers + int turretNum; + for ( turretNum = 0; turretNum < MAX_VEHICLE_TURRETS; turretNum++ ) + { + if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].iAmmoMax ) + {// valid turret + if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].passengerNum == cg->predictedPlayerState.generic1 ) + {//I control this turret + //Go through all muzzles, average their positions and directions and use the result for crosshair trace + int vehMuzzle, numMuzzles = 0; + vec3_t muzzlesAvgPos={0},muzzlesAvgDir={0}; + int i; + + for ( i = 0; i < MAX_VEHICLE_TURRET_MUZZLES; i++ ) + { + vehMuzzle = vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].iMuzzle[i]; + if ( vehMuzzle ) + { + vehMuzzle -= 1; + CG_CalcVehMuzzle( vehCent->m_pVehicle, vehCent, vehMuzzle ); + VectorAdd( muzzlesAvgPos, vehCent->m_pVehicle->m_vMuzzlePos[vehMuzzle], muzzlesAvgPos ); + VectorAdd( muzzlesAvgDir, vehCent->m_pVehicle->m_vMuzzleDir[vehMuzzle], muzzlesAvgDir ); + numMuzzles++; + } + if ( numMuzzles ) + { + VectorScale( muzzlesAvgPos, 1.0f/(float)numMuzzles, start ); + VectorScale( muzzlesAvgDir, 1.0f/(float)numMuzzles, d_f ); + VectorClear( d_rt ); + VectorClear( d_up ); + return qtrue; + } + } + } + } + } + } + } + VectorCopy( vehCent->lerpOrigin, start ); + AngleVectors( vehCent->lerpAngles, d_f, d_rt, d_up ); + } + return qfalse; +} + +//calc the muzzle point from the e-web itself +void CG_CalcEWebMuzzlePoint(centity_t *cent, vec3_t start, vec3_t d_f, vec3_t d_rt, vec3_t d_up) +{ + int bolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*cannonflash"); + + assert(bolt != -1); + + if (bolt != -1) + { + mdxaBone_t boltMatrix; + + trap_G2API_GetBoltMatrix_NoRecNoRot(cent->ghoul2, 0, bolt, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg->time, NULL, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, start); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, d_f); + + //these things start the shot a little inside the bbox to assure not starting in something solid + VectorMA(start, -16.0f, d_f, start); + + //I guess + VectorClear( d_rt );//don't really need this, do we? + VectorClear( d_up );//don't really need this, do we? + } +} + +/* +================= +CG_`Entity +================= +*/ +#define MAX_XHAIR_DIST_ACCURACY 20000.0f +static void CG_ScanForCrosshairEntity( void ) { + trace_t trace; + vec3_t start, end; + int content; + int ignore; + qboolean bVehCheckTraceFromCamPos = qfalse; + + ignore = cg->predictedPlayerState.clientNum; + + if ( cg_dynamicCrosshair.integer ) + { + vec3_t d_f, d_rt, d_up; + /* + if ( cg->snap->ps.weapon == WP_NONE || + cg->snap->ps.weapon == WP_SABER || + cg->snap->ps.weapon == WP_STUN_BATON) + { + VectorCopy( cg->refdef.vieworg, start ); + AngleVectors( cg->refdef.viewangles, d_f, d_rt, d_up ); + } + else + */ + //For now we still want to draw the crosshair in relation to the player's world coordinates + //even if we have a melee weapon/no weapon. + if ( cg->predictedPlayerState.m_iVehicleNum && (cg->predictedPlayerState.eFlags&EF_NODRAW) ) + {//we're *inside* a vehicle + //do the vehicle's crosshair instead + centity_t *veh = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + qboolean gunner = qfalse; + + //if (veh->currentState.owner == cg->predictedPlayerState.clientNum) + { //the pilot + ignore = cg->predictedPlayerState.m_iVehicleNum; + gunner = CG_CalcVehicleMuzzlePoint(cg->predictedPlayerState.m_iVehicleNum, start, d_f, d_rt, d_up); + } + /* + else + { //a passenger + ignore = cg->predictedPlayerState.m_iVehicleNum; + VectorCopy( veh->lerpOrigin, start ); + AngleVectors( veh->lerpAngles, d_f, d_rt, d_up ); + VectorMA(start, 32.0f, d_f, start); //super hack + } + */ + if ( veh->m_pVehicle + && veh->m_pVehicle->m_pVehicleInfo + && veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER + && cg->distanceCull > MAX_XHAIR_DIST_ACCURACY + && !gunner ) + { + //NOTE: on huge maps, the crosshair gets inaccurate at close range, + // so we'll do an extra G2 trace from the cg->refdef.vieworg + // to see if we hit anything closer and auto-aim at it if so + bVehCheckTraceFromCamPos = qtrue; + } + } + else if (cg->snap && cg->snap->ps.weapon == WP_EMPLACED_GUN && cg->snap->ps.emplacedIndex && + cg_entities[cg->snap->ps.emplacedIndex].ghoul2 && cg_entities[cg->snap->ps.emplacedIndex].currentState.weapon == WP_NONE) + { //locked into our e-web, calc the muzzle from it + CG_CalcEWebMuzzlePoint(&cg_entities[cg->snap->ps.emplacedIndex], start, d_f, d_rt, d_up); + } + else + { + if (cg->snap && cg->snap->ps.weapon == WP_EMPLACED_GUN && cg->snap->ps.emplacedIndex) + { + vec3_t pitchConstraint; + + ignore = cg->snap->ps.emplacedIndex; + + VectorCopy(cg->refdef.viewangles, pitchConstraint); + + if (cg->renderingThirdPerson) + { + VectorCopy(cg->predictedPlayerState.viewangles, pitchConstraint); + } + else + { + VectorCopy(cg->refdef.viewangles, pitchConstraint); + } + + if (pitchConstraint[PITCH] > 40) + { + pitchConstraint[PITCH] = 40; + } + + AngleVectors( pitchConstraint, d_f, d_rt, d_up ); + } + else + { + vec3_t pitchConstraint; + + if (cg->renderingThirdPerson) + { + VectorCopy(cg->predictedPlayerState.viewangles, pitchConstraint); + } + else + { + VectorCopy(cg->refdef.viewangles, pitchConstraint); + } + + AngleVectors( pitchConstraint, d_f, d_rt, d_up ); + } + CG_CalcMuzzlePoint(cg->snap->ps.clientNum, start); + } + + VectorMA( start, cg->distanceCull, d_f, end ); + } + else + { + VectorCopy( cg->refdef.vieworg, start ); + VectorMA( start, 131072, cg->refdef.viewaxis[0], end ); + } + + if ( cg_dynamicCrosshair.integer && cg_dynamicCrosshairPrecision.integer ) + { //then do a trace with ghoul2 models in mind + CG_G2Trace( &trace, start, vec3_origin, vec3_origin, end, + ignore, CONTENTS_SOLID|CONTENTS_BODY ); + if ( bVehCheckTraceFromCamPos ) + { + //NOTE: this MUST stay up to date with the method used in WP_VehCheckTraceFromCamPos + centity_t *veh = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + trace_t extraTrace; + vec3_t viewDir2End, extraEnd; + float minAutoAimDist = Distance( veh->lerpOrigin, cg->refdef.vieworg ) + (veh->m_pVehicle->m_pVehicleInfo->length/2.0f) + 200.0f; + + VectorSubtract( end, cg->refdef.vieworg, viewDir2End ); + VectorNormalize( viewDir2End ); + VectorMA( cg->refdef.vieworg, MAX_XHAIR_DIST_ACCURACY, viewDir2End, extraEnd ); + CG_G2Trace( &extraTrace, cg->refdef.vieworg, vec3_origin, vec3_origin, extraEnd, + ignore, CONTENTS_SOLID|CONTENTS_BODY ); + if ( !extraTrace.allsolid + && !extraTrace.startsolid ) + { + if ( extraTrace.fraction < 1.0f ) + { + if ( (extraTrace.fraction*MAX_XHAIR_DIST_ACCURACY) > minAutoAimDist ) + { + if ( ((extraTrace.fraction*MAX_XHAIR_DIST_ACCURACY)-Distance( veh->lerpOrigin, cg->refdef.vieworg )) < (trace.fraction*cg->distanceCull) ) + {//this trace hit *something* that's closer than the thing the main trace hit, so use this result instead + memcpy( &trace, &extraTrace, sizeof( trace_t ) ); + } + } + } + } + } + } + else + { + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, + ignore, CONTENTS_SOLID|CONTENTS_BODY ); + } + + if (trace.entityNum < MAX_CLIENTS) + { + if (CG_IsMindTricked(cg_entities[trace.entityNum].currentState.trickedentindex, + cg_entities[trace.entityNum].currentState.trickedentindex2, + cg_entities[trace.entityNum].currentState.trickedentindex3, + cg_entities[trace.entityNum].currentState.trickedentindex4, + cg->snap->ps.clientNum)) + { + if (cg->crosshairClientNum == trace.entityNum) + { + cg->crosshairClientNum = ENTITYNUM_NONE; + cg->crosshairClientTime = 0; + } + + CG_DrawCrosshair(trace.endpos, 0); + + return; //this entity is mind-tricking the current client, so don't render it + } + } + + if (cg->snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR) + { + if (trace.entityNum < /*MAX_CLIENTS*/ENTITYNUM_WORLD) + { + cg->crosshairClientNum = trace.entityNum; + cg->crosshairClientTime = cg->time; + + if (cg->crosshairClientNum < ENTITYNUM_WORLD) + { + centity_t *veh = &cg_entities[cg->crosshairClientNum]; + + if (veh->currentState.eType == ET_NPC && + veh->currentState.NPC_class == CLASS_VEHICLE && + veh->currentState.owner < MAX_CLIENTS) + { //draw the name of the pilot then + cg->crosshairClientNum = veh->currentState.owner; + cg->crosshairVehNum = veh->currentState.number; + cg->crosshairVehTime = cg->time; + } + } + + CG_DrawCrosshair(trace.endpos, 1); + } + else + { + CG_DrawCrosshair(trace.endpos, 0); + } + } + +// if ( trace.entityNum >= MAX_CLIENTS ) { +// return; +// } + + // if the player is in fog, don't show it + content = trap_CM_PointContents( trace.endpos, 0 ); + if ( content & CONTENTS_FOG ) { + return; + } + + // update the fade timer + cg->crosshairClientNum = trace.entityNum; + cg->crosshairClientTime = cg->time; +} + +void CG_SanitizeString( char *in, char *out ) +{ + int i = 0; + int r = 0; + + while (in[i]) + { + if (i >= 128-1) + { //the ui truncates the name here.. + break; + } + + if (in[i] == '^') + { + if (in[i+1] >= 48 && //'0' + in[i+1] <= 57) //'9' + { //only skip it if there's a number after it for the color + i += 2; + continue; + } + else + { //just skip the ^ + i++; + continue; + } + } + + if (in[i] < 32) + { + i++; + continue; + } + + out[r] = in[i]; + r++; + i++; + } + out[r] = 0; +} + +/* +===================== +CG_DrawCrosshairNames +===================== +*/ +static void CG_DrawCrosshairNames( void ) { + float *color; + vec4_t tcolor; + char *name; + char sanitized[1024]; + int baseColor; + qboolean isVeh = qfalse; + + if ( !cg_drawCrosshair.integer ) { + return; + } + + // scan the known entities to see if the crosshair is sighted on one + CG_ScanForCrosshairEntity(); + + if ( !cg_drawCrosshairNames.integer ) { + return; + } + //rww - still do the trace, our dynamic crosshair depends on it + + if (cg->crosshairClientNum < ENTITYNUM_WORLD) + { + centity_t *veh = &cg_entities[cg->crosshairClientNum]; + + if (veh->currentState.eType == ET_NPC && + veh->currentState.NPC_class == CLASS_VEHICLE && + veh->currentState.owner < MAX_CLIENTS) + { //draw the name of the pilot then + cg->crosshairClientNum = veh->currentState.owner; + cg->crosshairVehNum = veh->currentState.number; + cg->crosshairVehTime = cg->time; + isVeh = qtrue; //so we know we're drawing the pilot's name + } + } + + if (cg->crosshairClientNum >= MAX_CLIENTS) + { + return; + } + + if (cg_entities[cg->crosshairClientNum].currentState.powerups & (1 << PW_CLOAKED)) + { + return; + } + + // draw the name of the player being looked at + color = CG_FadeColor( cg->crosshairClientTime, 1000 ); + if ( !color ) { + trap_R_SetColor( NULL ); + return; + } + + name = cgs.clientinfo[ cg->crosshairClientNum ].name; + + if (cgs.gametype >= GT_TEAM) + { + //if (cgs.gametype == GT_SIEGE) + if (1) + { //instead of team-based we'll make it oriented based on which team we're on + if (cgs.clientinfo[cg->crosshairClientNum].team == cg->predictedPlayerState.persistant[PERS_TEAM]) + { + baseColor = CT_GREEN; + } + else + { + baseColor = CT_RED; + } + } + else + { + if (cgs.clientinfo[cg->crosshairClientNum].team == TEAM_RED) + { + baseColor = CT_RED; + } + else + { + baseColor = CT_BLUE; + } + } + } + else + { + //baseColor = CT_WHITE; + if (cgs.gametype == GT_POWERDUEL && + cgs.clientinfo[cg->snap->ps.clientNum].team != TEAM_SPECTATOR && + cgs.clientinfo[cg->crosshairClientNum].duelTeam == cgs.clientinfo[cg->predictedPlayerState.clientNum].duelTeam) + { //on the same duel team in powerduel, so he's a friend + baseColor = CT_GREEN; + } + else + { + baseColor = CT_RED; //just make it red in nonteam modes since everyone is hostile and crosshair will be red on them too + } + } + + if (cg->snap->ps.duelInProgress) + { + if (cg->crosshairClientNum != cg->snap->ps.duelIndex) + { //grey out crosshair for everyone but your foe if you're in a duel + baseColor = CT_BLACK; + } + } + else if (cg_entities[cg->crosshairClientNum].currentState.bolt1) + { //this fellow is in a duel. We just checked if we were in a duel above, so + //this means we aren't and he is. Which of course means our crosshair greys out over him. + baseColor = CT_BLACK; + } + + tcolor[0] = colorTable[baseColor][0]; + tcolor[1] = colorTable[baseColor][1]; + tcolor[2] = colorTable[baseColor][2]; + tcolor[3] = color[3]*0.5f; + + CG_SanitizeString(name, sanitized); + + int yoff = 0; +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 1) + yoff = 220; +#endif + + if (isVeh) + { + char str[MAX_STRING_CHARS]; + Com_sprintf(str, MAX_STRING_CHARS, "%s (pilot)", sanitized); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cg->widescreen) + UI_DrawProportionalString(360, 80 + yoff, str, UI_CENTER, tcolor); + else + UI_DrawProportionalString(320, 80 + yoff, str, UI_CENTER, tcolor); + } + else { + if(cg->widescreen) + UI_DrawProportionalString(360, 170, str, UI_CENTER, tcolor); + else +#endif + UI_DrawProportionalString(320, 170, str, UI_CENTER, tcolor); +#ifdef _XBOX + } +#endif + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cg->widescreen) + UI_DrawProportionalString(360, 80 + yoff, sanitized, UI_CENTER, tcolor); + else + UI_DrawProportionalString(320, 80 + yoff, sanitized, UI_CENTER, tcolor); + } + else { + if(cg->widescreen) + UI_DrawProportionalString(360, 170, sanitized, UI_CENTER, tcolor); + else +#endif + UI_DrawProportionalString(320, 170, sanitized, UI_CENTER, tcolor); +#ifdef _XBOX + } +#endif + } + + trap_R_SetColor( NULL ); +} + + +//============================================================================== + +/* +================= +CG_DrawSpectator +================= +*/ +static void CG_DrawSpectator(void) +{ + const char* s; + int yoff = 0, xoff = 0, yo = 0; +#ifdef _XBOX + if (ClientManager::splitScreenMode == qtrue) + { + if(ClientManager::ActiveClientNum() == 0) + yoff = 220; + else + yo = 220; + } + if(cg->widescreen) + xoff = 80; +#endif + + s = CG_GetStringEdString("MP_INGAME", "SPECTATOR"); + if ((cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) && + cgs.duelist1 != -1 && + cgs.duelist2 != -1) + { + char text[1024]; + int size = 64; + + if (cgs.gametype == GT_POWERDUEL && cgs.duelist3 != -1) + { + Com_sprintf(text, sizeof(text), "%s^7 %s %s^7 %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStringEdString("MP_INGAME", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name, CG_GetStringEdString("MP_INGAME", "AND"), cgs.clientinfo[cgs.duelist3].name); + } + else + { + Com_sprintf(text, sizeof(text), "%s^7 %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStringEdString("MP_INGAME", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name); + } +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint ( 360 - CG_Text_Width ( text, 1.0f, 3 ) / 2, 420 - yoff, 1.0f, colorWhite, text, 0, 0, 0, 3 ); + else +#endif + CG_Text_Paint ( 320 - CG_Text_Width ( text, 1.0f, 3 ) / 2, 420 - yoff, 1.0f, colorWhite, text, 0, 0, 0, 3 ); + + trap_R_SetColor( colorTable[CT_WHITE] ); + if ( cgs.clientinfo[cgs.duelist1].modelIcon ) + { + CG_DrawPic( 70, SCREEN_HEIGHT-(size*1.75) - yoff, size, size, cgs.clientinfo[cgs.duelist1].modelIcon ); + } + if ( cgs.clientinfo[cgs.duelist2].modelIcon ) + { + CG_DrawPic( SCREEN_WIDTH-size+10 - 70 + xoff, SCREEN_HEIGHT-(size*1.75) - yoff, size, size, cgs.clientinfo[cgs.duelist2].modelIcon ); + } + +// nmckenzie: DUEL_HEALTH + if (cgs.gametype == GT_DUEL) + { + if ( cgs.showDuelHealths >= 1) + { // draw the healths on the two guys - how does this interact with power duel, though? + CG_DrawDuelistHealth ( 70, SCREEN_HEIGHT-(size*1.75) - 12 - yoff, 64, 8, 1 ); + CG_DrawDuelistHealth ( SCREEN_WIDTH-size+10 + xoff, SCREEN_HEIGHT-(size*1.5) - 12 - yoff, 64, 8, 2 ); + } + } + + if (cgs.gametype != GT_POWERDUEL) + { + Com_sprintf(text, sizeof(text), "%i/%i", cgs.clientinfo[cgs.duelist1].score, cgs.fraglimit ); + CG_Text_Paint( 90, 340, 1.0f, colorWhite, text, 0, 0, 0, 2 ); + + Com_sprintf(text, sizeof(text), "%i/%i", cgs.clientinfo[cgs.duelist2].score, cgs.fraglimit ); +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint( 720-size+22 - CG_Text_Width( text, 1.0f, 2 ) / 2, SCREEN_HEIGHT-(size*1.5) + 64 - yoff, 1.0f, colorWhite, text, 0, 0, 0, 2 ); + else +#endif + CG_Text_Paint( 536,340, 1.0f, colorWhite, text, 0, 0, 0, 2 ); + } + + if (cgs.gametype == GT_POWERDUEL && cgs.duelist3 != -1) + { + if ( cgs.clientinfo[cgs.duelist3].modelIcon ) + { + CG_DrawPic( SCREEN_WIDTH-size+10-70 + xoff, SCREEN_HEIGHT-(size*2.8) - yoff, size, size, cgs.clientinfo[cgs.duelist3].modelIcon ); + } + } + } + else + { +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint ( 360 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 400 - yoff, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + else +#endif + CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 400 - yoff, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + } + + if ( cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL ) + { + s = CG_GetStringEdString("MP_INGAME", "WAITING_TO_PLAY"); // "waiting to play"; +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint ( 360 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 50 + yo, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + else +#endif + CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 50 + yo, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + } + else if ( cgs.gametype >= GT_TEAM ) + { + //s = "press ESC and use the JOIN menu to play"; + s = CG_GetStringEdString("MP_INGAME", "SPEC_TEAMJOIN"); +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint ( 360 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 420 - yoff, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + else +#endif + CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 420 - yoff, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + } + else //if ( cgs.gametype >= GT_TEAM ) + { + //s = "press ESC and use the JOIN menu to play"; + s = CG_GetStringEdString("MP_INGAME", "SPEC_CHOOSEJOIN"); +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint ( 360 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 420 - yoff, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + else +#endif + CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 420 - yoff, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + } +} + +float CG_DrawVote(float y) +{ + const char* s; + int sec; + + if ( !cgs.voteTime ) { + if(cgs.voteModified) + { + cgs.votePlaced = qfalse; + cgs.voteModified = qfalse; + } + return y; + } + + sec = ( VOTE_TIME - ( cg->time - cgs.voteTime ) ) / 1000; + if ( sec < 0 ) { + sec = 0; + } + + s = va("%s %d", CG_GetStringEdString("MP_INGAME", "VOTE_MESSAGE"), sec) ; + if(cg->widescreen) + CG_Text_Paint (710 - CG_Text_Width( s, 0.7f, FONT_MEDIUM) - 40.0f, y, 0.70f, colorWhite, s,0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else + CG_Text_Paint (630 - CG_Text_Width( s, 0.7f, FONT_MEDIUM) - 40.0f, y, 0.70f, colorWhite, s,0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + y += CG_Text_Height(s, 0.70f, FONT_MEDIUM); + + return y; +} + +/* +================= +CG_DrawTeamVote +================= +*/ +static void CG_DrawTeamVote(void) { +#ifdef _XBOX + int cs_offset; + + if ( cgs.clientinfo->team == TEAM_RED ) + cs_offset = 0; + else if ( cgs.clientinfo->team == TEAM_BLUE ) + cs_offset = 1; + else + return; + + if ( !cgs.teamVoteTime[cs_offset] ) { + return; + } + + assert( 0 ); +#else + char *s; + int sec, cs_offset; + + if ( cgs.clientinfo->team == TEAM_RED ) + cs_offset = 0; + else if ( cgs.clientinfo->team == TEAM_BLUE ) + cs_offset = 1; + else + return; + + if ( !cgs.teamVoteTime[cs_offset] ) { + return; + } + + // play a talk beep whenever it is modified + if ( cgs.teamVoteModified[cs_offset] ) { + cgs.teamVoteModified[cs_offset] = qfalse; +// trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } + + sec = ( VOTE_TIME - ( cg->time - cgs.teamVoteTime[cs_offset] ) ) / 1000; + if ( sec < 0 ) { + sec = 0; + } + if (strstr(cgs.teamVoteString[cs_offset], "leader")) + { + int i = 0; + + while (cgs.teamVoteString[cs_offset][i] && cgs.teamVoteString[cs_offset][i] != ' ') + { + i++; + } + + if (cgs.teamVoteString[cs_offset][i] == ' ') + { + int voteIndex = 0; + char voteIndexStr[256]; + + i++; + + while (cgs.teamVoteString[cs_offset][i]) + { + voteIndexStr[voteIndex] = cgs.teamVoteString[cs_offset][i]; + voteIndex++; + i++; + } + voteIndexStr[voteIndex] = 0; + + voteIndex = atoi(voteIndexStr); + + s = va("TEAMVOTE(%i):(Make %s the new team leader) yes:%i no:%i", sec, cgs.clientinfo[voteIndex].name, + cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); + } + else + { + s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], + cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); + } + } + else + { + s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], + cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); + } + CG_DrawSmallString( 4, 90, s, 1.0F ); +#endif +} + +static qboolean CG_DrawScoreboard() { + return CG_DrawOldScoreboard(); +#if 0 + static qboolean firstTime = qtrue; + float fade, *fadeColor; + + if (menuScoreboard) { + menuScoreboard->window.flags &= ~WINDOW_FORCED; + } + if (cg_paused.integer) { + cg->deferredPlayerLoading = 0; + firstTime = qtrue; + return qfalse; + } + + // should never happen in Team Arena + if (cgs.gametype == GT_SINGLE_PLAYER && cg->predictedPlayerState.pm_type == PM_INTERMISSION ) { + cg->deferredPlayerLoading = 0; + firstTime = qtrue; + return qfalse; + } + + // don't draw scoreboard during death while warmup up + if ( cg->warmup && !cg->showScores ) { + return qfalse; + } + + if ( cg->showScores || cg->predictedPlayerState.pm_type == PM_DEAD || cg->predictedPlayerState.pm_type == PM_INTERMISSION ) { + fade = 1.0; + fadeColor = colorWhite; + } else { + fadeColor = CG_FadeColor( cg->scoreFadeTime, FADE_TIME ); + if ( !fadeColor ) { + // next time scoreboard comes up, don't print killer + cg->deferredPlayerLoading = 0; + cg->killerName[0] = 0; + firstTime = qtrue; + return qfalse; + } + fade = *fadeColor; + } + + + if (menuScoreboard == NULL) { + if ( cgs.gametype >= GT_TEAM ) { + menuScoreboard = Menus_FindByName("teamscore_menu"); + } else { + menuScoreboard = Menus_FindByName("score_menu"); + } + } + + if (menuScoreboard) { + if (firstTime) { + CG_SetScoreSelection(menuScoreboard); + firstTime = qfalse; + } + Menu_Paint(menuScoreboard, qtrue); + } + + // load any models that have been deferred + if ( ++cg->deferredPlayerLoading > 10 ) { + CG_LoadDeferredPlayers(); + } + + return qtrue; +#endif +} + +/* +================= +CG_DrawIntermission +================= +*/ +static void CG_DrawIntermission( void ) { +// int key; + //if (cg_singlePlayer.integer) { + // CG_DrawCenterString(); + // return; + //} + cg->scoreFadeTime = cg->time; + cg->scoreBoardShowing = CG_DrawScoreboard(); +} + +/* +================= +CG_DrawFollow +================= +*/ +static qboolean CG_DrawFollow( void ) +{ + const char *s; + + if ( !(cg->snap->ps.pm_flags & PMF_FOLLOW) ) + { + return qfalse; + } + +// s = "following"; + if (cgs.gametype == GT_POWERDUEL) + { + clientInfo_t *ci = &cgs.clientinfo[ cg->snap->ps.clientNum ]; + + if (ci->duelTeam == DUELTEAM_LONE) + { + s = CG_GetStringEdString("MP_INGAME", "FOLLOWINGLONE"); + } + else if (ci->duelTeam == DUELTEAM_DOUBLE) + { + s = CG_GetStringEdString("MP_INGAME", "FOLLOWINGDOUBLE"); + } + else + { + s = CG_GetStringEdString("MP_INGAME", "FOLLOWING"); + } + } + else + { + s = CG_GetStringEdString("MP_INGAME", "FOLLOWING"); + } + + int yoff = 0, xoff = 0; +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 1) + yoff = 220; + + if(cg->widescreen) + xoff = 40; +#endif + + CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2 + xoff, 60 + yoff, 1.0f, colorWhite, s, 0, 0, 0, FONT_MEDIUM ); + + s = cgs.clientinfo[ cg->snap->ps.clientNum ].name; + CG_Text_Paint ( 320 - CG_Text_Width ( s, 2.0f, FONT_MEDIUM ) / 2 + xoff, 80 + yoff, 2.0f, colorWhite, s, 0, 0, 0, FONT_MEDIUM ); + + return qtrue; +} + + +/* +================= +CG_DrawAmmoWarning +================= +*/ +static void CG_DrawAmmoWarning( void ) { +#if 0 + const char *s; + int w; + + if (!cg_drawStatus.integer) + { + return; + } + + if ( cg_drawAmmoWarning.integer == 0 ) { + return; + } + + if ( !cg->lowAmmoWarning ) { + return; + } + + if ( cg->lowAmmoWarning == 2 ) { + s = "OUT OF AMMO"; + } else { + s = "LOW AMMO WARNING"; + } + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString(320 - w / 2, 64, s, 1.0F); +#endif +} + + + +/* +================= +CG_DrawWarmup +================= +*/ +static void CG_DrawWarmup( void ) { + int w; + int sec; + int i; + float scale; + int cw; + const char *s; + + sec = cg->warmup; + if ( !sec ) { + return; + } + + if ( sec < 0 ) { +// s = "Waiting for players"; + s = CG_GetStringEdString("MP_INGAME", "WAITING_FOR_PLAYERS"); + w = RE_Font_StrLenPixels( s, 1, 1.0f ); + RE_Font_DrawString( 320 - w/2, 60, s, g_color_table[7], 1, -1, 1.0f ); + cg->warmupCount = 0; + return; + } + + if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) + { + // find the two active players + clientInfo_t *ci1, *ci2, *ci3; + + ci1 = NULL; + ci2 = NULL; + ci3 = NULL; + + if (cgs.gametype == GT_POWERDUEL) + { + if (cgs.duelist1 != -1) + { + ci1 = &cgs.clientinfo[cgs.duelist1]; + } + if (cgs.duelist2 != -1) + { + ci2 = &cgs.clientinfo[cgs.duelist2]; + } + if (cgs.duelist3 != -1) + { + ci3 = &cgs.clientinfo[cgs.duelist3]; + } + } + else + { + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) { + if ( !ci1 ) { + ci1 = &cgs.clientinfo[i]; + } else { + ci2 = &cgs.clientinfo[i]; + } + } + } + } + if ( ci1 && ci2 ) + { + if (ci3) + { + s = va( "%s vs %s and %s", ci1->name, ci2->name, ci3->name ); + } + else + { + s = va( "%s vs %s", ci1->name, ci2->name ); + } + w = CG_Text_Width(s, 0.6f, FONT_MEDIUM); + CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE,FONT_MEDIUM); + } + } else { + if ( cgs.gametype == GT_FFA ) { + s = CG_GetStringEdString("MENUS", "FREE_FOR_ALL");//"Free For All"; + } else if ( cgs.gametype == GT_HOLOCRON ) { + s = CG_GetStringEdString("MENUS", "HOLOCRON_FFA");//"Holocron FFA"; + } else if ( cgs.gametype == GT_JEDIMASTER ) { + s = CG_GetStringEdString("MENUS", "POWERDUEL");//"Jedi Master";?? + } else if ( cgs.gametype == GT_TEAM ) { + s = CG_GetStringEdString("MENUS", "TEAM_FFA");//"Team FFA"; + } else if ( cgs.gametype == GT_SIEGE ) { + s = CG_GetStringEdString("MENUS", "SIEGE");//"Siege"; + } else if ( cgs.gametype == GT_CTF ) { + s = CG_GetStringEdString("MENUS", "CAPTURE_THE_FLAG");//"Capture the Flag"; + } else if ( cgs.gametype == GT_CTY ) { + s = CG_GetStringEdString("MENUS", "CAPTURE_THE_YSALIMARI");//"Capture the Ysalamiri"; + } else { + s = ""; + } + w = CG_Text_Width(s, 1.5f, FONT_MEDIUM); + CG_Text_Paint(320 - w / 2, 90, 1.5f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE,FONT_MEDIUM); + } + + sec = ( sec - cg->time ) / 1000; + if ( sec < 0 ) { + cg->warmup = 0; + sec = 0; + } +// s = va( "Starts in: %i", sec + 1 ); + s = va( "%s: %i",CG_GetStringEdString("MP_INGAME", "STARTS_IN"), sec + 1 ); + if ( sec != cg->warmupCount ) { + cg->warmupCount = sec; + + if (cgs.gametype != GT_SIEGE) + { + switch ( sec ) { + case 0: + trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); + break; + case 1: + trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); + break; + case 2: + trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); + break; + default: + break; + } + } + } + scale = 0.45f; + switch ( cg->warmupCount ) { + case 0: + cw = 28; + scale = 1.25f; + break; + case 1: + cw = 24; + scale = 1.15f; + break; + case 2: + cw = 20; + scale = 1.05f; + break; + default: + cw = 16; + scale = 0.9f; + break; + } + + w = CG_Text_Width(s, scale, FONT_MEDIUM); + CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM); +} + +//================================================================================== +/* +================= +CG_DrawTimedMenus +================= +*/ +void CG_DrawTimedMenus() { + if (cg->voiceTime) { + int t = cg->time - cg->voiceTime; + if ( t > 2500 ) { + Menus_CloseByName("voiceMenu"); + trap_Cvar_Set("cl_conXOffset", "0"); + cg->voiceTime = 0; + } + } +} + +void CG_DrawFlagStatus() +{ + int myFlagTakenShader = 0; + int theirFlagShader = 0; + int team = 0; + int startDrawPos = 2; + int ico_size = 32; + int yOff = 0; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + yOff = 220; +#endif + + if (!cg->snap) + { + return; + } + + if (cgs.gametype != GT_CTF && cgs.gametype != GT_CTY) + { + return; + } + + team = cg->snap->ps.persistant[PERS_TEAM]; + + if (cgs.gametype == GT_CTY) + { + if (team == TEAM_RED) + { + myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_x" ); + theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_ys" ); + } + else + { + myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_x" ); + theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_ys" ); + } + } + else + { + if (team == TEAM_RED) + { + myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_x" ); + theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag" ); + } + else + { + myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_x" ); + theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag" ); + } + } + + if (CG_YourTeamHasFlag()) + { + //CG_DrawPic( startDrawPos, 330, ico_size, ico_size, theirFlagShader ); + CG_DrawPic( 60, 290-startDrawPos - yOff, ico_size, ico_size, theirFlagShader ); + startDrawPos += ico_size+2; + } + + if (CG_OtherTeamHasFlag()) + { + //CG_DrawPic( startDrawPos, 330, ico_size, ico_size, myFlagTakenShader ); + CG_DrawPic( 60, 290-startDrawPos - yOff, ico_size, ico_size, myFlagTakenShader ); + } +} + +//draw meter showing jetpack fuel when it's not full +#define JPFUELBAR_H 100.0f +#define JPFUELBAR_W 20.0f +#define JPFUELBAR_X 560.0f +#define JPFUELBAR_Y 230.0f +void CG_DrawJetpackFuel(void) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = JPFUELBAR_X; + float y = JPFUELBAR_Y; +#ifdef _XBOX + if(cg->widescreen) + x += 80; +#endif + float percent = ((float)cg->snap->ps.jetpackFuel/100.0f)*JPFUELBAR_H; + + if (percent > JPFUELBAR_H) + { + return; + } + + if (percent < 0.1f) + { + percent = 0.1f; + } + + //color of the bar + aColor[0] = 0.5f; + aColor[1] = 0.0f; + aColor[2] = 0.0f; + aColor[3] = 0.8f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing fuel" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.1f; + + //draw the background (black) + CG_DrawRect(x, y, JPFUELBAR_W, JPFUELBAR_H, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x, y+1.0f+(JPFUELBAR_H-percent), JPFUELBAR_W-1.0f, JPFUELBAR_H-1.0f-(JPFUELBAR_H-percent), aColor); + + //then draw the other part greyed out + CG_FillRect(x+1.0f, y+1.0f, JPFUELBAR_W-1.0f, JPFUELBAR_H-percent, cColor); +} + +//draw meter showing e-web health when it is in use +#define EWEBHEALTH_H 100.0f +#define EWEBHEALTH_W 20.0f +#define EWEBHEALTH_X (SCREEN_WIDTH-EWEBHEALTH_W-8.0f) +#define EWEBHEALTH_Y 290.0f +void CG_DrawEWebHealth(void) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = EWEBHEALTH_X; + float y = EWEBHEALTH_Y; + centity_t *eweb = &cg_entities[cg->predictedPlayerState.emplacedIndex]; + float percent = ((float)eweb->currentState.health/eweb->currentState.maxhealth)*EWEBHEALTH_H; + + if (percent > EWEBHEALTH_H) + { + return; + } + + if (percent < 0.1f) + { + percent = 0.1f; + } + + //kind of hacky, need to pass a coordinate in here + if (cg->snap->ps.jetpackFuel < 100) + { + x -= (JPFUELBAR_W+8.0f); + } + if (cg->snap->ps.cloakFuel < 100) + { + x -= (JPFUELBAR_W+8.0f); + } + + //color of the bar + aColor[0] = 0.5f; + aColor[1] = 0.0f; + aColor[2] = 0.0f; + aColor[3] = 0.8f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing fuel" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.1f; + + //draw the background (black) + CG_DrawRect(x, y, EWEBHEALTH_W, EWEBHEALTH_H, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f+(EWEBHEALTH_H-percent), EWEBHEALTH_W-1.0f, EWEBHEALTH_H-1.0f-(EWEBHEALTH_H-percent), aColor); + + //then draw the other part greyed out + CG_FillRect(x+1.0f, y+1.0f, EWEBHEALTH_W-1.0f, EWEBHEALTH_H-percent, cColor); +} + +//draw meter showing cloak fuel when it's not full +#define CLFUELBAR_H 100.0f +#define CLFUELBAR_W 20.0f +#define CLFUELBAR_X 560.0f +#define CLFUELBAR_Y 220.0f +void CG_DrawCloakFuel(void) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = CLFUELBAR_X; + float y = CLFUELBAR_Y; + float percent = ((float)cg->snap->ps.cloakFuel/100.0f)*CLFUELBAR_H; + + if (percent > CLFUELBAR_H) + { + return; + } + + if ( cg->snap->ps.jetpackFuel < 100 ) + {//if drawing jetpack fuel bar too, then move this over...? + x -= (JPFUELBAR_W+8.0f); + } + + if (percent < 0.1f) + { + percent = 0.1f; + } + + //color of the bar + aColor[0] = 0.0f; + aColor[1] = 0.0f; + aColor[2] = 0.6f; + aColor[3] = 0.8f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing fuel" + cColor[0] = 0.1f; + cColor[1] = 0.1f; + cColor[2] = 0.3f; + cColor[3] = 0.1f; + + //draw the background (black) + CG_DrawRect(x, y, CLFUELBAR_W, CLFUELBAR_H, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much fuel there is in the color specified + CG_FillRect(x+1.0f, y+1.0f+(CLFUELBAR_H-percent), CLFUELBAR_W-1.0f, CLFUELBAR_H-1.0f-(CLFUELBAR_H-percent), aColor); + + //then draw the other part greyed out + CG_FillRect(x+1.0f, y+1.0f, CLFUELBAR_W-1.0f, CLFUELBAR_H-percent, cColor); +} + +#ifndef _XBOX +int cgRageTime = 0; +int cgRageFadeTime = 0; +float cgRageFadeVal = 0; + +int cgRageRecTime = 0; +int cgRageRecFadeTime = 0; +float cgRageRecFadeVal = 0; + +int cgAbsorbTime = 0; +int cgAbsorbFadeTime = 0; +float cgAbsorbFadeVal = 0; + +int cgProtectTime = 0; +int cgProtectFadeTime = 0; +float cgProtectFadeVal = 0; + +int cgYsalTime = 0; +int cgYsalFadeTime = 0; +float cgYsalFadeVal = 0; +#endif + +#ifdef _XBOX +qboolean gCGHasFallVector[2] = {qfalse, qfalse}; +vec3_t gCGFallVector[2]; +#else +qboolean gCGHasFallVector = qfalse; +vec3_t gCGFallVector; +#endif + +/* +================= +CG_Draw2D +================= +*/ +extern int cgSiegeRoundState; +extern int cgSiegeRoundTime; + +extern int team1Timed; +extern int team2Timed; + +int cg_beatingSiegeTime = 0; + +int cgSiegeRoundBeganTime = 0; +int cgSiegeRoundCountTime = 0; + +static void CG_DrawSiegeTimer(int timeRemaining, qboolean isMyTeam) +{ //rwwFIXMEFIXME: Make someone make assets and use them. + //this function is pretty much totally placeholder. +// int x = 0; +// int y = SCREEN_HEIGHT-160; + int fColor = 0; + int minutes = 0; + int seconds = 0; + char timeStr[1024]; + menuDef_t *menuHUD = NULL; + itemDef_t *item = NULL; + + menuHUD = Menus_FindByName("mp_timer"); + if (!menuHUD) + { + return; + } + + item = Menu_FindItemByName(menuHUD, "frame"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); +#ifdef _XBOX + if(cg->widescreen) + CG_DrawPic( + item->window.rect.x + 80, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + else +#endif + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + seconds = timeRemaining; + + while (seconds >= 60) + { + minutes++; + seconds -= 60; + } + + strcpy(timeStr, va( "%i:%02i", minutes, seconds )); + + if (isMyTeam) + { + fColor = CT_HUD_RED; + } + else + { + fColor = CT_HUD_GREEN; + } + +// trap_Cvar_Set("ui_siegeTimer", timeStr); + +// UI_DrawProportionalString( x+16, y+40, timeStr, +// UI_SMALLFONT|UI_DROPSHADOW, colorTable[fColor] ); + + item = Menu_FindItemByName(menuHUD, "timer"); + if (item) + { +#ifdef _XBOX + if(cg->widescreen) + UI_DrawProportionalString( + item->window.rect.x + 80, + item->window.rect.y, + timeStr, + UI_SMALLFONT|UI_DROPSHADOW, + colorTable[fColor] ); + else +#endif + UI_DrawProportionalString( + item->window.rect.x, + item->window.rect.y, + timeStr, + UI_SMALLFONT|UI_DROPSHADOW, + colorTable[fColor] ); + } + +} + +static void CG_DrawSiegeDeathTimer( int timeRemaining ) +{ + int minutes = 0; + int seconds = 0; + char timeStr[1024]; + menuDef_t *menuHUD = NULL; + itemDef_t *item = NULL; + + menuHUD = Menus_FindByName("mp_timer"); + if (!menuHUD) + { + return; + } + + item = Menu_FindItemByName(menuHUD, "frame"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); +#ifdef _XBOX + if(cg->widescreen) + CG_DrawPic( + item->window.rect.x + 80, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + else +#endif + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + seconds = timeRemaining; + + while (seconds >= 60) + { + minutes++; + seconds -= 60; + } + + if (seconds < 10) + { + strcpy(timeStr, va( "%i:0%i", minutes, seconds )); + } + else + { + strcpy(timeStr, va( "%i:%i", minutes, seconds )); + } + + item = Menu_FindItemByName(menuHUD, "deathtimer"); + if (item) + { +#ifdef _XBOX + if(cg->widescreen) + UI_DrawProportionalString( + item->window.rect.x + 80, + item->window.rect.y, + timeStr, + UI_SMALLFONT|UI_DROPSHADOW, + item->window.foreColor ); + else +#endif + UI_DrawProportionalString( + item->window.rect.x, + item->window.rect.y, + timeStr, + UI_SMALLFONT|UI_DROPSHADOW, + item->window.foreColor ); + } + +} + +int cgSiegeEntityRender = 0; + +static void CG_DrawSiegeHUDItem(void) +{ + void *g2; + qhandle_t handle; + vec3_t origin, angles; + vec3_t mins, maxs; + float len; + centity_t *cent = &cg_entities[cgSiegeEntityRender]; + + if (cent->ghoul2) + { + g2 = cent->ghoul2; + handle = 0; + } + else + { + handle = cgs.gameModels[cent->currentState.modelindex]; + g2 = NULL; + } + + if (handle) + { + trap_R_ModelBounds( handle, mins, maxs ); + } + else + { + VectorSet(mins, -16, -16, -20); + VectorSet(maxs, 16, 16, 32); + } + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; + + VectorClear(angles); + angles[YAW] = cg->autoAngles[YAW]; + + CG_Draw3DModel( 64, 48, 64, 64, handle, g2, cent->currentState.g2radius, 0, origin, angles ); + + cgSiegeEntityRender = 0; //reset for next frame +} + +/*==================================== +chatbox functionality -rww +====================================*/ +#define CHATBOX_CUTOFF_LEN 550 +#define CHATBOX_FONT_HEIGHT 20 + +//utility func, insert a string into a string at the specified +//place (assuming this will not overflow the buffer) +void CG_ChatBox_StrInsert(char *buffer, int place, char *str) +{ + int insLen = strlen(str); + int i = strlen(buffer); + int k = 0; + + buffer[i+insLen+1] = 0; //terminate the string at its new length + while (i >= place) + { + buffer[i+insLen] = buffer[i]; + i--; + } + + i++; + while (k < insLen) + { + buffer[i] = str[k]; + i++; + k++; + } +} + +//add chatbox string +void CG_ChatBox_AddString(char *chatStr) +{ + chatBoxItem_t *chat = &cg->chatItems[cg->chatItemActive]; + float chatLen; + + if (cg_chatBox.integer<=0) + { //don't bother then. + return; + } + + memset(chat, 0, sizeof(chatBoxItem_t)); + + if (strlen(chatStr) > sizeof(chat->string)) + { //too long, terminate at proper len. + chatStr[sizeof(chat->string)-1] = 0; + } + + strcpy(chat->string, chatStr); + chat->time = cg->time + cg_chatBox.integer; + + chat->lines = 1; + + chatLen = CG_Text_Width(chat->string, 1.0f, FONT_SMALL); + if (chatLen > CHATBOX_CUTOFF_LEN) + { //we have to break it into segments... + int i = 0; + int lastLinePt = 0; + char s[2]; + + chatLen = 0; + while (chat->string[i]) + { + s[0] = chat->string[i]; + s[1] = 0; + chatLen += CG_Text_Width(s, 0.65f, FONT_SMALL); + + if (chatLen >= CHATBOX_CUTOFF_LEN) + { + int j = i; + while (j > 0 && j > lastLinePt) + { + if (chat->string[j] == ' ') + { + break; + } + j--; + } + if (chat->string[j] == ' ') + { + i = j; + } + + chat->lines++; + CG_ChatBox_StrInsert(chat->string, i, "\n"); + i++; + chatLen = 0; + lastLinePt = i+1; + } + i++; + } + } + + cg->chatItemActive++; + if (cg->chatItemActive >= MAX_CHATBOX_ITEMS) + { + cg->chatItemActive = 0; + } +} + +//insert item into array (rearranging the array if necessary) +void CG_ChatBox_ArrayInsert(chatBoxItem_t **array, int insPoint, int maxNum, chatBoxItem_t *item) +{ + if (array[insPoint]) + { //recursively call, to move everything up to the top + if (insPoint+1 >= maxNum) + { + CG_Error("CG_ChatBox_ArrayInsert: Exceeded array size"); + } + CG_ChatBox_ArrayInsert(array, insPoint+1, maxNum, array[insPoint]); + } + + //now that we have moved anything that would be in this slot up, insert what we want into the slot + array[insPoint] = item; +} + +//go through all the chat strings and draw them if they are not yet expired +static CGAME_INLINE void CG_ChatBox_DrawStrings(void) +{ + chatBoxItem_t *drawThese[MAX_CHATBOX_ITEMS]; + int numToDraw = 0; + int linesToDraw = 0; + int i = 0; + int x = 30; + int y = cg->scoreBoardShowing ? 475 : cg_chatBoxHeight.integer; + float fontScale = 0.65f; + + if (!cg_chatBox.integer) + { + return; + } + + memset(drawThese, 0, sizeof(drawThese)); + + while (i < MAX_CHATBOX_ITEMS) + { + if (cg->chatItems[i].time >= cg->time) + { + int check = numToDraw; + int insertionPoint = numToDraw; + + while (check >= 0) + { + if (drawThese[check] && + cg->chatItems[i].time < drawThese[check]->time) + { //insert here + insertionPoint = check; + } + check--; + } + CG_ChatBox_ArrayInsert(drawThese, insertionPoint, MAX_CHATBOX_ITEMS, &cg->chatItems[i]); + numToDraw++; + linesToDraw += cg->chatItems[i].lines; + } + i++; + } + + if (!numToDraw) + { //nothing, then, just get out of here now. + return; + } + + //move initial point up so we draw bottom-up (visually) + y -= (CHATBOX_FONT_HEIGHT*fontScale)*linesToDraw; + + //we have the items we want to draw, just quickly loop through them now + i = 0; + while (i < numToDraw) + { + CG_Text_Paint(x, y, fontScale, colorWhite, drawThese[i]->string, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + y += ((CHATBOX_FONT_HEIGHT*fontScale)*drawThese[i]->lines); + i++; + } +} + + +#ifdef _XBOX +static void CG_Draw2DScreenTints( void ) +{ + float rageTime, rageRecTime, absorbTime, protectTime, ysalTime; + vec4_t hcolor; + if (cgs.clientinfo[cg->snap->ps.clientNum].team != TEAM_SPECTATOR) + { + if (cg->snap->ps.fd.forcePowersActive & (1 << FP_RAGE)) + { + if (!ClientManager::ActiveClient().cgRageTime) + { + ClientManager::ActiveClient().cgRageTime = cg->time; + } + + rageTime = (float)(cg->time - ClientManager::ActiveClient().cgRageTime); + + rageTime /= 9000; + + if (rageTime < 0) + { + rageTime = 0; + } + if (rageTime > 0.15) + { + rageTime = 0.15; + } + + hcolor[3] = rageTime; + hcolor[0] = 0.7; + hcolor[1] = 0; + hcolor[2] = 0; + + if (!cg->renderingThirdPerson) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + + ClientManager::ActiveClient().cgRageFadeTime = 0; + ClientManager::ActiveClient().cgRageFadeVal = 0; + } + else if (ClientManager::ActiveClient().cgRageTime) + { + if (!ClientManager::ActiveClient().cgRageFadeTime) + { + ClientManager::ActiveClient().cgRageFadeTime = cg->time; + ClientManager::ActiveClient().cgRageFadeVal = 0.15; + } + + rageTime = ClientManager::ActiveClient().cgRageFadeVal; + + ClientManager::ActiveClient().cgRageFadeVal -= (cg->time - ClientManager::ActiveClient().cgRageFadeTime)*0.000005; + + if (rageTime < 0) + { + rageTime = 0; + } + if (rageTime > 0.15) + { + rageTime = 0.15; + } + + if (cg->snap->ps.fd.forceRageRecoveryTime > cg->time) + { + float checkRageRecTime = rageTime; + + if (checkRageRecTime < 0.15) + { + checkRageRecTime = 0.15; + } + + hcolor[3] = checkRageRecTime; + hcolor[0] = rageTime*4; + if (hcolor[0] < 0.2) + { + hcolor[0] = 0.2; + } + hcolor[1] = 0.2; + hcolor[2] = 0.2; + } + else + { + hcolor[3] = rageTime; + hcolor[0] = 0.7; + hcolor[1] = 0; + hcolor[2] = 0; + } + + if (!cg->renderingThirdPerson && rageTime) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + else + { + if (cg->snap->ps.fd.forceRageRecoveryTime > cg->time) + { + hcolor[3] = 0.15; + hcolor[0] = 0.2; + hcolor[1] = 0.2; + hcolor[2] = 0.2; +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + ClientManager::ActiveClient().cgRageTime = 0; + } + } + else if (cg->snap->ps.fd.forceRageRecoveryTime > cg->time) + { + if (!ClientManager::ActiveClient().cgRageRecTime) + { + ClientManager::ActiveClient().cgRageRecTime = cg->time; + } + + rageRecTime = (float)(cg->time - ClientManager::ActiveClient().cgRageRecTime); + + rageRecTime /= 9000; + + if (rageRecTime < 0.15)//0) + { + rageRecTime = 0.15;//0; + } + if (rageRecTime > 0.15) + { + rageRecTime = 0.15; + } + + hcolor[3] = rageRecTime; + hcolor[0] = 0.2; + hcolor[1] = 0.2; + hcolor[2] = 0.2; + + if (!cg->renderingThirdPerson) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + + ClientManager::ActiveClient().cgRageRecFadeTime = 0; + ClientManager::ActiveClient().cgRageRecFadeVal = 0; + } + else if (ClientManager::ActiveClient().cgRageRecTime) + { + if (!ClientManager::ActiveClient().cgRageRecFadeTime) + { + ClientManager::ActiveClient().cgRageRecFadeTime = cg->time; + ClientManager::ActiveClient().cgRageRecFadeVal = 0.15; + } + + rageRecTime = ClientManager::ActiveClient().cgRageRecFadeVal; + + ClientManager::ActiveClient().cgRageRecFadeVal -= (cg->time - ClientManager::ActiveClient().cgRageRecFadeTime)*0.000005; + + if (rageRecTime < 0) + { + rageRecTime = 0; + } + if (rageRecTime > 0.15) + { + rageRecTime = 0.15; + } + + hcolor[3] = rageRecTime; + hcolor[0] = 0.2; + hcolor[1] = 0.2; + hcolor[2] = 0.2; + + if (!cg->renderingThirdPerson && rageRecTime) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + else + { + ClientManager::ActiveClient().cgRageRecTime = 0; + } + } + + if (cg->snap->ps.fd.forcePowersActive & (1 << FP_ABSORB)) + { + if (!ClientManager::ActiveClient().cgAbsorbTime) + { + ClientManager::ActiveClient().cgAbsorbTime = cg->time; + } + + absorbTime = (float)(cg->time - ClientManager::ActiveClient().cgAbsorbTime); + + absorbTime /= 9000; + + if (absorbTime < 0) + { + absorbTime = 0; + } + if (absorbTime > 0.15) + { + absorbTime = 0.15; + } + + hcolor[3] = absorbTime/2; + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7; + + if (!cg->renderingThirdPerson) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + + ClientManager::ActiveClient().cgAbsorbFadeTime = 0; + ClientManager::ActiveClient().cgAbsorbFadeVal = 0; + } + else if (ClientManager::ActiveClient().cgAbsorbTime) + { + if (!ClientManager::ActiveClient().cgAbsorbFadeTime) + { + ClientManager::ActiveClient().cgAbsorbFadeTime = cg->time; + ClientManager::ActiveClient().cgAbsorbFadeVal = 0.15; + } + + absorbTime = ClientManager::ActiveClient().cgAbsorbFadeVal; + + ClientManager::ActiveClient().cgAbsorbFadeVal -= (cg->time - ClientManager::ActiveClient().cgAbsorbFadeTime)*0.000005; + + if (absorbTime < 0) + { + absorbTime = 0; + } + if (absorbTime > 0.15) + { + absorbTime = 0.15; + } + + hcolor[3] = absorbTime/2; + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7; + + if (!cg->renderingThirdPerson && absorbTime) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + else + { + ClientManager::ActiveClient().cgAbsorbTime = 0; + } + } + + if (cg->snap->ps.fd.forcePowersActive & (1 << FP_PROTECT)) + { + if (!ClientManager::ActiveClient().cgProtectTime) + { + ClientManager::ActiveClient().cgProtectTime = cg->time; + } + + protectTime = (float)(cg->time - ClientManager::ActiveClient().cgProtectTime); + + protectTime /= 9000; + + if (protectTime < 0) + { + protectTime = 0; + } + if (protectTime > 0.15) + { + protectTime = 0.15; + } + + hcolor[3] = protectTime/2; + hcolor[0] = 0; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg->renderingThirdPerson) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + + ClientManager::ActiveClient().cgProtectFadeTime = 0; + ClientManager::ActiveClient().cgProtectFadeVal = 0; + } + else if (ClientManager::ActiveClient().cgProtectTime) + { + if (!ClientManager::ActiveClient().cgProtectFadeTime) + { + ClientManager::ActiveClient().cgProtectFadeTime = cg->time; + ClientManager::ActiveClient().cgProtectFadeVal = 0.15; + } + + protectTime = ClientManager::ActiveClient().cgProtectFadeVal; + + ClientManager::ActiveClient().cgProtectFadeVal -= (cg->time - ClientManager::ActiveClient().cgProtectFadeTime)*0.000005; + + if (protectTime < 0) + { + protectTime = 0; + } + if (protectTime > 0.15) + { + protectTime = 0.15; + } + + hcolor[3] = protectTime/2; + hcolor[0] = 0; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg->renderingThirdPerson && protectTime) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + else + { + ClientManager::ActiveClient().cgProtectTime = 0; + } + } + + if (cg->snap->ps.rocketLockIndex != ENTITYNUM_NONE && (cg->time - cg->snap->ps.rocketLockTime) > 0) + { + CG_DrawRocketLocking( cg->snap->ps.rocketLockIndex, cg->snap->ps.rocketLockTime ); + } + + if (BG_HasYsalamiri(cgs.gametype, &cg->snap->ps)) + { + if (!ClientManager::ActiveClient().cgYsalTime) + { + ClientManager::ActiveClient().cgYsalTime = cg->time; + } + + ysalTime = (float)(cg->time - ClientManager::ActiveClient().cgYsalTime); + + ysalTime /= 9000; + + if (ysalTime < 0) + { + ysalTime = 0; + } + if (ysalTime > 0.15) + { + ysalTime = 0.15; + } + + hcolor[3] = ysalTime/2; + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg->renderingThirdPerson) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + + ClientManager::ActiveClient().cgYsalFadeTime = 0; + ClientManager::ActiveClient().cgYsalFadeVal = 0; + } + else if (ClientManager::ActiveClient().cgYsalTime) + { + if (!ClientManager::ActiveClient().cgYsalFadeTime) + { + ClientManager::ActiveClient().cgYsalFadeTime = cg->time; + ClientManager::ActiveClient().cgYsalFadeVal = 0.15; + } + + ysalTime = ClientManager::ActiveClient().cgYsalFadeVal; + + ClientManager::ActiveClient().cgYsalFadeVal -= (cg->time - ClientManager::ActiveClient().cgYsalFadeTime)*0.000005; + + if (ysalTime < 0) + { + ysalTime = 0; + } + if (ysalTime > 0.15) + { + ysalTime = 0.15; + } + + hcolor[3] = ysalTime/2; + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg->renderingThirdPerson && ysalTime) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + else + { + ClientManager::ActiveClient().cgYsalTime = 0; + } + } + } + + if ( (cg->refdef.viewContents&CONTENTS_LAVA) ) + {//tint screen red + float phase = cg->time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + hcolor[3] = 0.5 + (0.15f*sin( phase )); + hcolor[0] = 0.7f; + hcolor[1] = 0; + hcolor[2] = 0; + +#ifdef _XBOX + if(cg->widescreen) + CG_DrawRect( 0, 0, 720, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + else +#endif + CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + } + else if ( (cg->refdef.viewContents&CONTENTS_SLIME) ) + {//tint screen green + float phase = cg->time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + hcolor[3] = 0.4 + (0.1f*sin( phase )); + hcolor[0] = 0; + hcolor[1] = 0.7f; + hcolor[2] = 0; + +#ifdef _XBOX + if(cg->widescreen) + CG_DrawRect( 0, 0, 720, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + else +#endif + CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + } + else if ( (cg->refdef.viewContents&CONTENTS_WATER) ) + {//tint screen light blue -- FIXME: don't do this if CONTENTS_FOG? (in case someone *does* make a water shader with fog in it?) + float phase = cg->time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + hcolor[3] = 0.3 + (0.05f*sin( phase )); + hcolor[0] = 0; + hcolor[1] = 0.2f; + hcolor[2] = 0.8; + +#ifdef _XBOX + if(cg->widescreen) + CG_DrawRect( 0, 0, 720, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + else +#endif + CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + } +} + +#else // _XBOX + +static void CG_Draw2DScreenTints( void ) +{ + float rageTime, rageRecTime, absorbTime, protectTime, ysalTime; + vec4_t hcolor; + if (cgs.clientinfo[cg->snap->ps.clientNum].team != TEAM_SPECTATOR) + { + if (cg->snap->ps.fd.forcePowersActive & (1 << FP_RAGE)) + { + if (!cgRageTime) + { + cgRageTime = cg->time; + } + + rageTime = (float)(cg->time - cgRageTime); + + rageTime /= 9000; + + if (rageTime < 0) + { + rageTime = 0; + } + if (rageTime > 0.15) + { + rageTime = 0.15; + } + + hcolor[3] = rageTime; + hcolor[0] = 0.7; + hcolor[1] = 0; + hcolor[2] = 0; + + if (!cg->renderingThirdPerson) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + + cgRageFadeTime = 0; + cgRageFadeVal = 0; + } + else if (cgRageTime) + { + if (!cgRageFadeTime) + { + cgRageFadeTime = cg->time; + cgRageFadeVal = 0.15; + } + + rageTime = cgRageFadeVal; + + cgRageFadeVal -= (cg->time - cgRageFadeTime)*0.000005; + + if (rageTime < 0) + { + rageTime = 0; + } + if (rageTime > 0.15) + { + rageTime = 0.15; + } + + if (cg->snap->ps.fd.forceRageRecoveryTime > cg->time) + { + float checkRageRecTime = rageTime; + + if (checkRageRecTime < 0.15) + { + checkRageRecTime = 0.15; + } + + hcolor[3] = checkRageRecTime; + hcolor[0] = rageTime*4; + if (hcolor[0] < 0.2) + { + hcolor[0] = 0.2; + } + hcolor[1] = 0.2; + hcolor[2] = 0.2; + } + else + { + hcolor[3] = rageTime; + hcolor[0] = 0.7; + hcolor[1] = 0; + hcolor[2] = 0; + } + + if (!cg->renderingThirdPerson && rageTime) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + else + { + if (cg->snap->ps.fd.forceRageRecoveryTime > cg->time) + { + hcolor[3] = 0.15; + hcolor[0] = 0.2; + hcolor[1] = 0.2; + hcolor[2] = 0.2; +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + cgRageTime = 0; + } + } + else if (cg->snap->ps.fd.forceRageRecoveryTime > cg->time) + { + if (!cgRageRecTime) + { + cgRageRecTime = cg->time; + } + + rageRecTime = (float)(cg->time - cgRageRecTime); + + rageRecTime /= 9000; + + if (rageRecTime < 0.15)//0) + { + rageRecTime = 0.15;//0; + } + if (rageRecTime > 0.15) + { + rageRecTime = 0.15; + } + + hcolor[3] = rageRecTime; + hcolor[0] = 0.2; + hcolor[1] = 0.2; + hcolor[2] = 0.2; + + if (!cg->renderingThirdPerson) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + + cgRageRecFadeTime = 0; + cgRageRecFadeVal = 0; + } + else if (cgRageRecTime) + { + if (!cgRageRecFadeTime) + { + cgRageRecFadeTime = cg->time; + cgRageRecFadeVal = 0.15; + } + + rageRecTime = cgRageRecFadeVal; + + cgRageRecFadeVal -= (cg->time - cgRageRecFadeTime)*0.000005; + + if (rageRecTime < 0) + { + rageRecTime = 0; + } + if (rageRecTime > 0.15) + { + rageRecTime = 0.15; + } + + hcolor[3] = rageRecTime; + hcolor[0] = 0.2; + hcolor[1] = 0.2; + hcolor[2] = 0.2; + + if (!cg->renderingThirdPerson && rageRecTime) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + else + { + cgRageRecTime = 0; + } + } + + if (cg->snap->ps.fd.forcePowersActive & (1 << FP_ABSORB)) + { + if (!cgAbsorbTime) + { + cgAbsorbTime = cg->time; + } + + absorbTime = (float)(cg->time - cgAbsorbTime); + + absorbTime /= 9000; + + if (absorbTime < 0) + { + absorbTime = 0; + } + if (absorbTime > 0.15) + { + absorbTime = 0.15; + } + + hcolor[3] = absorbTime/2; + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7; + + if (!cg->renderingThirdPerson) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + + cgAbsorbFadeTime = 0; + cgAbsorbFadeVal = 0; + } + else if (cgAbsorbTime) + { + if (!cgAbsorbFadeTime) + { + cgAbsorbFadeTime = cg->time; + cgAbsorbFadeVal = 0.15; + } + + absorbTime = cgAbsorbFadeVal; + + cgAbsorbFadeVal -= (cg->time - cgAbsorbFadeTime)*0.000005; + + if (absorbTime < 0) + { + absorbTime = 0; + } + if (absorbTime > 0.15) + { + absorbTime = 0.15; + } + + hcolor[3] = absorbTime/2; + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7; + + if (!cg->renderingThirdPerson && absorbTime) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + else + { + cgAbsorbTime = 0; + } + } + + if (cg->snap->ps.fd.forcePowersActive & (1 << FP_PROTECT)) + { + if (!cgProtectTime) + { + cgProtectTime = cg->time; + } + + protectTime = (float)(cg->time - cgProtectTime); + + protectTime /= 9000; + + if (protectTime < 0) + { + protectTime = 0; + } + if (protectTime > 0.15) + { + protectTime = 0.15; + } + + hcolor[3] = protectTime/2; + hcolor[0] = 0; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg->renderingThirdPerson) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + + cgProtectFadeTime = 0; + cgProtectFadeVal = 0; + } + else if (cgProtectTime) + { + if (!cgProtectFadeTime) + { + cgProtectFadeTime = cg->time; + cgProtectFadeVal = 0.15; + } + + protectTime = cgProtectFadeVal; + + cgProtectFadeVal -= (cg->time - cgProtectFadeTime)*0.000005; + + if (protectTime < 0) + { + protectTime = 0; + } + if (protectTime > 0.15) + { + protectTime = 0.15; + } + + hcolor[3] = protectTime/2; + hcolor[0] = 0; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg->renderingThirdPerson && protectTime) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + else + { + cgProtectTime = 0; + } + } + + if (cg->snap->ps.rocketLockIndex != ENTITYNUM_NONE && (cg->time - cg->snap->ps.rocketLockTime) > 0) + { + CG_DrawRocketLocking( cg->snap->ps.rocketLockIndex, cg->snap->ps.rocketLockTime ); + } + + if (BG_HasYsalamiri(cgs.gametype, &cg->snap->ps)) + { + if (!cgYsalTime) + { + cgYsalTime = cg->time; + } + + ysalTime = (float)(cg->time - cgYsalTime); + + ysalTime /= 9000; + + if (ysalTime < 0) + { + ysalTime = 0; + } + if (ysalTime > 0.15) + { + ysalTime = 0.15; + } + + hcolor[3] = ysalTime/2; + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg->renderingThirdPerson) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + + cgYsalFadeTime = 0; + cgYsalFadeVal = 0; + } + else if (cgYsalTime) + { + if (!cgYsalFadeTime) + { + cgYsalFadeTime = cg->time; + cgYsalFadeVal = 0.15; + } + + ysalTime = cgYsalFadeVal; + + cgYsalFadeVal -= (cg->time - cgYsalFadeTime)*0.000005; + + if (ysalTime < 0) + { + ysalTime = 0; + } + if (ysalTime > 0.15) + { + ysalTime = 0.15; + } + + hcolor[3] = ysalTime/2; + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg->renderingThirdPerson && ysalTime) + { +#ifdef _XBOX + int wx = 0; + int wy = 0; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + int wh = SCREEN_HEIGHT; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) { + wy = SCREEN_HEIGHT / 2; + } + } + + CG_DrawRect(wx, wy, ww, wh, ww*wh, hcolor); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + } + else + { + cgYsalTime = 0; + } + } + } + + if ( (cg->refdef.viewContents&CONTENTS_LAVA) ) + {//tint screen red + float phase = cg->time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + hcolor[3] = 0.5 + (0.15f*sin( phase )); + hcolor[0] = 0.7f; + hcolor[1] = 0; + hcolor[2] = 0; + +#ifdef _XBOX + if(cg->widescreen) + CG_DrawRect( 0, 0, 720, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + else +#endif + CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + } + else if ( (cg->refdef.viewContents&CONTENTS_SLIME) ) + {//tint screen green + float phase = cg->time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + hcolor[3] = 0.4 + (0.1f*sin( phase )); + hcolor[0] = 0; + hcolor[1] = 0.7f; + hcolor[2] = 0; + +#ifdef _XBOX + if(cg->widescreen) + CG_DrawRect( 0, 0, 720, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + else +#endif + CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + } + else if ( (cg->refdef.viewContents&CONTENTS_WATER) ) + {//tint screen light blue -- FIXME: don't do this if CONTENTS_FOG? (in case someone *does* make a water shader with fog in it?) + float phase = cg->time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + hcolor[3] = 0.3 + (0.05f*sin( phase )); + hcolor[0] = 0; + hcolor[1] = 0.2f; + hcolor[2] = 0.8; + +#ifdef _XBOX + if(cg->widescreen) + CG_DrawRect( 0, 0, 720, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + else +#endif + CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + } +} + +#endif // _XBOX + +static void CG_Draw2D( void ) { + float inTime = cg->invenSelectTime+WEAPON_SELECT_TIME; + float wpTime = cg->weaponSelectTime+WEAPON_SELECT_TIME; + float fallTime; + float bestTime; + int drawSelect = 0; + + // if we are taking a levelshot for the menu, don't draw anything + if ( cg->levelShot ) { + return; + } + + if (cgs.clientinfo[cg->snap->ps.clientNum].team == TEAM_SPECTATOR) + { +#ifdef _XBOX + ClientManager::ActiveClient().cgRageTime = 0; + ClientManager::ActiveClient().cgRageFadeTime = 0; + ClientManager::ActiveClient().cgRageFadeVal = 0; + + ClientManager::ActiveClient().cgRageRecTime = 0; + ClientManager::ActiveClient().cgRageRecFadeTime = 0; + ClientManager::ActiveClient().cgRageRecFadeVal = 0; + + ClientManager::ActiveClient().cgAbsorbTime = 0; + ClientManager::ActiveClient().cgAbsorbFadeTime = 0; + ClientManager::ActiveClient().cgAbsorbFadeVal = 0; + + ClientManager::ActiveClient().cgProtectTime = 0; + ClientManager::ActiveClient().cgProtectFadeTime = 0; + ClientManager::ActiveClient().cgProtectFadeVal = 0; + + ClientManager::ActiveClient().cgYsalTime = 0; + ClientManager::ActiveClient().cgYsalFadeTime = 0; + ClientManager::ActiveClient().cgYsalFadeVal = 0; +#else + cgRageTime = 0; + cgRageFadeTime = 0; + cgRageFadeVal = 0; + + cgRageRecTime = 0; + cgRageRecFadeTime = 0; + cgRageRecFadeVal = 0; + + cgAbsorbTime = 0; + cgAbsorbFadeTime = 0; + cgAbsorbFadeVal = 0; + + cgProtectTime = 0; + cgProtectFadeTime = 0; + cgProtectFadeVal = 0; + + cgYsalTime = 0; + cgYsalFadeTime = 0; + cgYsalFadeVal = 0; +#endif + } + + if ( cg_draw2D.integer == 0 ) { + return; + } + + if ( cg->snap->ps.pm_type == PM_INTERMISSION ) { + CG_DrawIntermission(); + CG_ChatBox_DrawStrings(); + return; + } + + CG_Draw2DScreenTints(); + + if (cg->snap->ps.rocketLockIndex != ENTITYNUM_NONE && (cg->time - cg->snap->ps.rocketLockTime) > 0) + { + CG_DrawRocketLocking( cg->snap->ps.rocketLockIndex, cg->snap->ps.rocketLockTime ); + } + +/* + if (cg->snap->ps.holocronBits) + { + CG_DrawHolocronIcons(); + } +*/ + if (cg->snap->ps.fd.forcePowersActive || cg->snap->ps.fd.forceRageRecoveryTime > cg->time) + { + CG_DrawActivePowers(); + } + + if (cg->snap->ps.jetpackFuel < 100) + { //draw it as long as it isn't full + CG_DrawJetpackFuel(); + } + if (cg->snap->ps.cloakFuel < 100) + { //draw it as long as it isn't full + CG_DrawCloakFuel(); + } + if (cg->predictedPlayerState.emplacedIndex > 0) + { + centity_t *eweb = &cg_entities[cg->predictedPlayerState.emplacedIndex]; + + if (eweb->currentState.weapon == WP_NONE) + { //using an e-web, draw its health + CG_DrawEWebHealth(); + } + } + + // Draw this before the text so that any text won't get clipped off + CG_DrawZoomMask(); + +/* + if (cg->cameraMode) { + return; + } +*/ + if ( cg->snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + CG_DrawSpectator(); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qfalse) { +#endif + CG_DrawCrosshair(NULL, 0); + CG_DrawCrosshairNames(); +#ifdef _XBOX + } +#endif + CG_SaberClashFlare(); + } else { + // don't draw any status if dead or the scoreboard is being explicitly shown + if ( !cg->showScores && cg->snap->ps.stats[STAT_HEALTH] > 0 ) { + + if ( /*cg_drawStatus.integer*/0 ) { + //Reenable if stats are drawn with menu system again + Menu_PaintAll(); + CG_DrawTimedMenus(); + } + + //CG_DrawTemporaryStats(); + + CG_DrawAmmoWarning(); + + CG_DrawCrosshairNames(); + + if (cg_drawStatus.integer) + { + CG_DrawIconBackground(); + } + + if (inTime > wpTime) + { + drawSelect = 1; + bestTime = cg->invenSelectTime; + } + else //only draw the most recent since they're drawn in the same place + { + drawSelect = 2; + bestTime = cg->weaponSelectTime; + } + + if (cg->forceSelectTime > bestTime) + { + drawSelect = 3; + } + + switch(drawSelect) + { + case 1: + CG_DrawInvenSelect(); + break; + case 2: + CG_DrawWeaponSelect(); + break; + case 3: + CG_DrawForceSelect(); + break; + default: + break; + } + + if (cg_drawStatus.integer) + { + //Powerups now done with upperright stuff + //CG_DrawPowerupIcons(); + + CG_DrawFlagStatus(); + } + + CG_SaberClashFlare(); + + if (cg_drawStatus.integer) + { + CG_DrawStats(); + } + + CG_DrawPickupItem(); + //Do we want to use this system again at some point? + //CG_DrawReward(); + } + + } + + if (cg->snap->ps.fallingToDeath) + { + vec4_t hcolor; + + fallTime = (float)(cg->time - cg->snap->ps.fallingToDeath); + + fallTime /= (FALL_FADE_TIME/2); + + if (fallTime < 0) + { + fallTime = 0; + } + if (fallTime > 1) + { + fallTime = 1; + } + + hcolor[3] = fallTime; + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0; + +#ifdef _XBOX + int wx = 0; + int wy = 0; + int wh = SCREEN_HEIGHT; + int ww = SCREEN_WIDTH; + if(cg->widescreen) + ww = 720; + + if(ClientManager::splitScreenMode == qtrue) { + wh = SCREEN_HEIGHT / 2; + + if(ClientManager::ActiveClientNum() == 1) + wy = SCREEN_HEIGHT / 2; + } +// CG_DrawRect(wx, wy, ww, wh, ww * wh, hcolor); + trap_R_SetColor( hcolor ); + trap_R_DrawStretchPic( wx, wy, ww, wh, 0, 0, 0, 0, cgs.media.whiteShader); +#else + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); +#endif + +#ifdef _XBOX + if (!gCGHasFallVector[ClientManager::ActiveClientNum()]) + { + VectorCopy(cg->snap->ps.origin, gCGFallVector[ClientManager::ActiveClientNum()]); + gCGHasFallVector[ClientManager::ActiveClientNum()] = qtrue; + } + } + else + { + if (gCGHasFallVector[ClientManager::ActiveClientNum()]) + { + gCGHasFallVector[ClientManager::ActiveClientNum()] = qfalse; + VectorClear(gCGFallVector[ClientManager::ActiveClientNum()]); + } +#else + if (!gCGHasFallVector) + { + VectorCopy(cg->snap->ps.origin, gCGFallVector); + gCGHasFallVector = qtrue; + } + } + else + { + if (gCGHasFallVector) + { + gCGHasFallVector = qfalse; + VectorClear(gCGFallVector); + } +#endif + + } + + CG_DrawTeamVote(); + + CG_DrawLagometer(); + + + if (!cg_paused.integer) { + CG_DrawBracketedEntities(); + CG_DrawUpperRight(); + } + + if ( !CG_DrawFollow() ) { + CG_DrawWarmup(); + } + + if (cgSiegeRoundState) + { + char pStr[1024]; + int rTime = 0; + + //cgSiegeRoundBeganTime = 0; + + switch (cgSiegeRoundState) + { + case 1: + CG_CenterPrint(CG_GetStringEdString("MP_INGAME", "WAITING_FOR_PLAYERS"), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH); + break; + case 2: + rTime = (SIEGE_ROUND_BEGIN_TIME - (cg->time - cgSiegeRoundTime)); + + if (rTime < 0) + { + rTime = 0; + } + if (rTime > SIEGE_ROUND_BEGIN_TIME) + { + rTime = SIEGE_ROUND_BEGIN_TIME; + } + + rTime /= 1000; + + rTime += 1; + + if (rTime < 1) + { + rTime = 1; + } + + if (rTime <= 3 && rTime != cgSiegeRoundCountTime) + { + cgSiegeRoundCountTime = rTime; + + switch (rTime) + { + case 1: + trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); + break; + case 2: + trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); + break; + case 3: + trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); + break; + default: + break; + } + } + + strcpy(pStr, va("%s %i...", CG_GetStringEdString("MP_INGAME", "ROUNDBEGINSIN"), rTime)); + CG_CenterPrint(pStr, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH); + //same + break; + default: + break; + } + + cgSiegeEntityRender = 0; + } + else if (cgSiegeRoundTime) + { + CG_CenterPrint("", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH); + cgSiegeRoundTime = 0; + + //cgSiegeRoundBeganTime = cg->time; + cgSiegeEntityRender = 0; + } + else if (cgSiegeRoundBeganTime) + { //Draw how much time is left in the round based on local info. + int timedTeam = TEAM_FREE; + int timedValue = 0; + + if (cgSiegeEntityRender) + { //render the objective item model since this client has it + CG_DrawSiegeHUDItem(); + } + + if (team1Timed) + { + timedTeam = TEAM_RED; //team 1 + if (cg_beatingSiegeTime) + { + timedValue = cg_beatingSiegeTime; + } + else + { + timedValue = team1Timed; + } + } + else if (team2Timed) + { + timedTeam = TEAM_BLUE; //team 2 + if (cg_beatingSiegeTime) + { + timedValue = cg_beatingSiegeTime; + } + else + { + timedValue = team2Timed; + } + } + + if (timedTeam != TEAM_FREE) + { //one of the teams has a timer + int timeRemaining; + qboolean isMyTeam = qfalse; + + if (cgs.siegeTeamSwitch && !cg_beatingSiegeTime) + { //in switchy mode but not beating a time, so count up. + timeRemaining = (cg->time-cgSiegeRoundBeganTime); + if (timeRemaining < 0) + { + timeRemaining = 0; + } + } + else + { + timeRemaining = (((cgSiegeRoundBeganTime)+timedValue) - cg->time); + } + + if (timeRemaining > timedValue) + { + timeRemaining = timedValue; + } + else if (timeRemaining < 0) + { + timeRemaining = 0; + } + + if (timeRemaining) + { + timeRemaining /= 1000; + } + + if (cg->predictedPlayerState.persistant[PERS_TEAM] == timedTeam) + { //the team that's timed is the one this client is on + isMyTeam = qtrue; + } + + CG_DrawSiegeTimer(timeRemaining, isMyTeam); + } + } + else + { + cgSiegeEntityRender = 0; + } + + if ( cg_siegeDeathTime ) + { + int timeRemaining = ( cg_siegeDeathTime - cg->time ); + + if ( timeRemaining < 0 ) + { + timeRemaining = 0; + cg_siegeDeathTime = 0; + } + + if ( timeRemaining ) + { + timeRemaining /= 1000; + } + + CG_DrawSiegeDeathTimer( timeRemaining ); + } + + // don't draw center string if scoreboard is up + cg->scoreBoardShowing = CG_DrawScoreboard(); + if ( !cg->scoreBoardShowing) { + CG_DrawCenterString(); + } + + // always draw chat + CG_ChatBox_DrawStrings(); +} + + +static void CG_DrawTourneyScoreboard() { +} + +/* +===================== +CG_DrawActive + +Perform all drawing needed to completely fill the screen +===================== +*/ +void CG_DrawActive( stereoFrame_t stereoView ) { + float separation; + vec3_t baseOrg; + + // optionally draw the info screen instead + if ( !cg->snap ) { + CG_DrawInformation(); + return; + } + + // optionally draw the tournement scoreboard instead + if ( cg->snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && + ( cg->snap->ps.pm_flags & PMF_SCOREBOARD ) ) { + CG_DrawTourneyScoreboard(); + return; + } + + switch ( stereoView ) { + case STEREO_CENTER: + separation = 0; + break; + case STEREO_LEFT: + separation = -cg_stereoSeparation.value / 2; + break; + case STEREO_RIGHT: + separation = cg_stereoSeparation.value / 2; + break; + default: + separation = 0; + CG_Error( "CG_DrawActive: Undefined stereoView" ); + } + + + // clear around the rendered view if sized down +#ifdef _XBOX + // MATT! - hack here, this was messing up split-screen + if(ClientManager::splitScreenMode == false) +#endif + CG_TileClear(); + + // offset vieworg appropriately if we're doing stereo separation + VectorCopy( cg->refdef.vieworg, baseOrg ); + if ( separation != 0 ) { + VectorMA( cg->refdef.vieworg, -separation, cg->refdef.viewaxis[1], cg->refdef.vieworg ); + } + + cg->refdef.rdflags |= RDF_DRAWSKYBOX; + + // draw 3D view + trap_R_RenderScene( &cg->refdef ); + + // restore original viewpoint if running stereo + if ( separation != 0 ) { + VectorCopy( baseOrg, cg->refdef.vieworg ); + } + + // draw status bar and other floating elements + CG_Draw2D(); +} + + + diff --git a/codemp/cgame/cg_drawtools.c b/codemp/cgame/cg_drawtools.c new file mode 100644 index 0000000..932e3fd --- /dev/null +++ b/codemp/cgame/cg_drawtools.c @@ -0,0 +1,544 @@ +/* +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "cg_text.h" +*/ + +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc +#include "cg_local.h" +#include "../game/q_shared.h" + + +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) { + trap_R_SetColor( color ); + + CG_DrawTopBottom(x, y, width, height, size); + CG_DrawSides(x, y, width, height, size); + + trap_R_SetColor( NULL ); +} + + + +/* +================= +CG_GetColorForHealth +================= +*/ +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ) { + int count; + int max; + + // calculate the total points of damage that can + // be sustained at the current health / armor level + if ( health <= 0 ) { + VectorClear( hcolor ); // black + hcolor[3] = 1; + return; + } + count = armor; + max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); + if ( max < count ) { + count = max; + } + health += count; + + // set the color based on health + hcolor[0] = 1.0; + hcolor[3] = 1.0; + if ( health >= 100 ) { + hcolor[2] = 1.0; + } else if ( health < 66 ) { + hcolor[2] = 0; + } else { + hcolor[2] = ( health - 66 ) / 33.0; + } + + if ( health > 60 ) { + hcolor[1] = 1.0; + } else if ( health < 30 ) { + hcolor[1] = 0; + } else { + hcolor[1] = ( health - 30 ) / 30.0; + } +} + +/* +================ +CG_DrawSides + +Coords are virtual 640x480 +================ +*/ +void CG_DrawSides(float x, float y, float w, float h, float size) { + size *= cgs.screenXScale; + trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); +} + +void CG_DrawTopBottom(float x, float y, float w, float h, float size) { + size *= cgs.screenYScale; + trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); +} + +/* +------------------------- +CGC_FillRect2 +real coords +------------------------- +*/ +void CG_FillRect2( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader); + trap_R_SetColor( NULL ); +} + +/* +================ +CG_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_FillRect( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader); + + trap_R_SetColor( NULL ); +} + + +/* +================ +CG_DrawPic + +Coordinates are 640*480 virtual values +A width of 0 will draw with the original image width +================= +*/ +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + +/* +================ +CG_DrawRotatePic + +Coordinates are 640*480 virtual values +A width of 0 will draw with the original image width +rotates around the upper right corner of the passed in point +================= +*/ +void CG_DrawRotatePic( float x, float y, float width, float height,float angle, qhandle_t hShader ) { + trap_R_DrawRotatePic( x, y, width, height, 0, 0, 1, 1, angle, hShader ); +} + +/* +================ +CG_DrawRotatePic2 + +Coordinates are 640*480 virtual values +A width of 0 will draw with the original image width +Actually rotates around the center point of the passed in coordinates +================= +*/ +void CG_DrawRotatePic2( float x, float y, float width, float height,float angle, qhandle_t hShader ) { + trap_R_DrawRotatePic2( x, y, width, height, 0, 0, 1, 1, angle, hShader ); +} + +/* +============= +CG_TileClearBox + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { + float s1, t1, s2, t2; + + s1 = x/64.0; + t1 = y/64.0; + s2 = (x+w)/64.0; + t2 = (y+h)/64.0; + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); +} + + + +/* +============== +CG_TileClear + +Clear around a sized down screen +============== +*/ +void CG_TileClear( void ) { +/* + int top, bottom, left, right; + int w, h; + + w = cgs.glconfig.vidWidth; + h = cgs.glconfig.vidHeight; + + if ( cg->refdef.x == 0 && cg->refdef.y == 0 && + cg->refdef.width == w && cg->refdef.height == h ) { + return; // full screen rendering + } + + top = cg->refdef.y; + bottom = top + cg->refdef.height-1; + left = cg->refdef.x; + right = left + cg->refdef.width-1; + + // clear above view screen + CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); + + // clear below view screen + CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); + + // clear left of view screen + CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); + + // clear right of view screen + CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); +*/ +} + + + +/* +================ +CG_FadeColor +================ +*/ +float *CG_FadeColor( int startMsec, int totalMsec ) { + static vec4_t color; + int t; + + if ( startMsec == 0 ) { + return NULL; + } + + t = cg->time - startMsec; + + if ( t >= totalMsec ) { + return NULL; + } + + // fade out + if ( totalMsec - t < FADE_TIME ) { + color[3] = ( totalMsec - t ) * 1.0/FADE_TIME; + } else { + color[3] = 1.0; + } + color[0] = color[1] = color[2] = 1; + + return color; +} + + +/* +================= +CG_ColorForHealth +================= +*/ +void CG_ColorForGivenHealth( vec4_t hcolor, int health ) +{ + // set the color based on health + hcolor[0] = 1.0; + if ( health >= 100 ) + { + hcolor[2] = 1.0; + } + else if ( health < 66 ) + { + hcolor[2] = 0; + } + else + { + hcolor[2] = ( health - 66 ) / 33.0; + } + + if ( health > 60 ) + { + hcolor[1] = 1.0; + } + else if ( health < 30 ) + { + hcolor[1] = 0; + } + else + { + hcolor[1] = ( health - 30 ) / 30.0; + } +} + +/* +================= +CG_ColorForHealth +================= +*/ +void CG_ColorForHealth( vec4_t hcolor ) +{ + int health; + int count; + int max; + + // calculate the total points of damage that can + // be sustained at the current health / armor level + health = cg->snap->ps.stats[STAT_HEALTH]; + + if ( health <= 0 ) + { + VectorClear( hcolor ); // black + hcolor[3] = 1; + return; + } + + count = cg->snap->ps.stats[STAT_ARMOR]; + max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); + if ( max < count ) + { + count = max; + } + health += count; + + hcolor[3] = 1.0; + CG_ColorForGivenHealth( hcolor, health ); +} + +/* +============== +CG_DrawNumField + +Take x,y positions as if 640 x 480 and scales them to the proper resolution + +============== +*/ +void CG_DrawNumField (int x, int y, int width, int value,int charWidth,int charHeight,int style,qboolean zeroFill) +{ + char num[16], *ptr; + int l; + int frame; + int xWidth; + int i = 0; + + if (width < 1) { + return; + } + + // draw number string + if (width > 5) { + width = 5; + } + + switch ( width ) { + case 1: + value = value > 9 ? 9 : value; + value = value < 0 ? 0 : value; + break; + case 2: + value = value > 99 ? 99 : value; + value = value < -9 ? -9 : value; + break; + case 3: + value = value > 999 ? 999 : value; + value = value < -99 ? -99 : value; + break; + case 4: + value = value > 9999 ? 9999 : value; + value = value < -999 ? -999 : value; + break; + } + + Com_sprintf (num, sizeof(num), "%i", value); + l = strlen(num); + if (l > width) + l = width; + + // FIXME: Might need to do something different for the chunky font?? + switch(style) + { + case NUM_FONT_SMALL: + xWidth = charWidth; + break; +// case NUM_FONT_CHUNKY: +// xWidth = (charWidth/1.2f) + 2; +// break; + default: + case NUM_FONT_BIG: + xWidth = (charWidth/2) + 7;//(charWidth/6); + break; + } + +#ifndef _XBOX + if ( zeroFill ) + { + for (i = 0; i < (width - l); i++ ) + { + switch(style) + { + case NUM_FONT_SMALL: + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.smallnumberShaders[0] ); + break; +// case NUM_FONT_CHUNKY: +// CG_DrawPic( x,y, charWidth, charHeight, cgs.media.chunkyNumberShaders[0] ); +// break; + default: + case NUM_FONT_BIG: + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[0] ); + break; + } + x += 2 + (xWidth); + } + } + else +#endif + { + x += 2 + (xWidth)*(width - l); + } + + ptr = num; + while (*ptr && l) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + switch(style) + { + case NUM_FONT_SMALL: + if(zeroFill) + { + CG_DrawPic( x-2,y, charWidth, charHeight, cgs.media.smallnumberShaders[frame] ); + CG_DrawPic( x+2,y, charWidth, charHeight, cgs.media.smallnumberShaders[frame] ); + CG_DrawPic( x,y-2, charWidth, charHeight, cgs.media.smallnumberShaders[frame] ); + CG_DrawPic( x,y+2, charWidth, charHeight, cgs.media.smallnumberShaders[frame] ); + x++; // For a one line gap + } + else { + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.smallnumberShaders[frame] ); + x++; // For a one line gap + } + break; +// case NUM_FONT_CHUNKY: +// CG_DrawPic( x,y, charWidth, charHeight, cgs.media.chunkyNumberShaders[frame] ); +// break; + default: + case NUM_FONT_BIG: + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[frame] ); + break; + } + + x += (xWidth); + ptr++; + l--; + } + +} + +#include "../ui/ui_shared.h" // for some text style junk +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ) +{ + // having all these different style defines (1 for UI, one for CG, and now one for the re->font stuff) + // is dumb, but for now... + // + int iStyle = 0; + int iMenuFont = (style & UI_SMALLFONT) ? FONT_SMALL : FONT_MEDIUM; + + switch (style & (UI_LEFT|UI_CENTER|UI_RIGHT)) + { + default: + case UI_LEFT: + { + // nada... + } + break; + + case UI_CENTER: + { + x -= CG_Text_Width(str, 1.0, iMenuFont) / 2; + } + break; + + case UI_RIGHT: + { + x -= CG_Text_Width(str, 1.0, iMenuFont) / 2; + } + break; + } + + if (style & UI_DROPSHADOW) + { + iStyle = ITEM_TEXTSTYLE_SHADOWED; + } + else + if ( style & (UI_BLINK|UI_PULSE) ) + { + iStyle = ITEM_TEXTSTYLE_BLINK; + } + + CG_Text_Paint(x, y, 1.0, color, str, 0, 0, iStyle, iMenuFont); +} + +void UI_DrawScaledProportionalString( int x, int y, const char* str, int style, vec4_t color, float scale) +{ + // having all these different style defines (1 for UI, one for CG, and now one for the re->font stuff) + // is dumb, but for now... + // + int iStyle = 0; + + switch (style & (UI_LEFT|UI_CENTER|UI_RIGHT)) + { + default: + case UI_LEFT: + { + // nada... + } + break; + + case UI_CENTER: + { + x -= CG_Text_Width(str, scale, FONT_MEDIUM) / 2; + } + break; + + case UI_RIGHT: + { + x -= CG_Text_Width(str, scale, FONT_MEDIUM) / 2; + } + break; + } + + if (style & UI_DROPSHADOW) + { + iStyle = ITEM_TEXTSTYLE_SHADOWED; + } + else + if ( style & (UI_BLINK|UI_PULSE) ) + { + iStyle = ITEM_TEXTSTYLE_BLINK; + } + + CG_Text_Paint(x, y, scale, color, str, 0, 0, iStyle, FONT_MEDIUM); +} + + + + diff --git a/codemp/cgame/cg_effects.c b/codemp/cgame/cg_effects.c new file mode 100644 index 0000000..f7c79c5 --- /dev/null +++ b/codemp/cgame/cg_effects.c @@ -0,0 +1,1546 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_effects.c -- these functions generate localentities, usually as a result +// of event processing + +#include "cg_local.h" + +/* +================== +CG_BubbleTrail + +Bullets shot underwater +================== +*/ +void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ) { + vec3_t move; + vec3_t vec; + float len; + int i; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + // advance a random amount first + i = rand() % (int)spacing; + VectorMA( move, i, vec, move ); + + VectorScale (vec, spacing, vec); + + for ( ; i < len; i += spacing ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg->time; + le->endTime = cg->time + 1000 + random() * 250; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + re->shaderTime = cg->time / 1000.0f; + + re->reType = RT_SPRITE; + re->rotation = 0; + re->radius = 3; + re->customShader = 0;//cgs.media.waterBubbleShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + + le->color[3] = 1.0; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg->time; + VectorCopy( move, le->pos.trBase ); + le->pos.trDelta[0] = crandom()*5; + le->pos.trDelta[1] = crandom()*5; + le->pos.trDelta[2] = crandom()*5 + 6; + + VectorAdd (move, vec, move); + } +} + +/* +===================== +CG_SmokePuff + +Adds a smoke puff or blood trail localEntity. +===================== +*/ +localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ) { + static int seed = 0x92; + localEntity_t *le; + refEntity_t *re; +// int fadeInTime = startTime + duration / 2; + + le = CG_AllocLocalEntity(); + le->leFlags = leFlags; + le->radius = radius; + + re = &le->refEntity; + re->rotation = Q_random( &seed ) * 360; + re->radius = radius; + re->shaderTime = startTime / 1000.0f; + + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = startTime; + le->fadeInTime = fadeInTime; + le->endTime = startTime + duration; + if ( fadeInTime > startTime ) { + le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime ); + } + else { + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + } + le->color[0] = r; + le->color[1] = g; + le->color[2] = b; + le->color[3] = a; + + + le->pos.trType = TR_LINEAR; + le->pos.trTime = startTime; + VectorCopy( vel, le->pos.trDelta ); + VectorCopy( p, le->pos.trBase ); + + VectorCopy( p, re->origin ); + re->customShader = hShader; + + re->shaderRGBA[0] = le->color[0] * 0xff; + re->shaderRGBA[1] = le->color[1] * 0xff; + re->shaderRGBA[2] = le->color[2] * 0xff; + re->shaderRGBA[3] = 0xff; + + re->reType = RT_SPRITE; + re->radius = le->radius; + + return le; +} + +int CGDEBUG_SaberColor( int saberColor ) +{ + switch( (int)(saberColor) ) + { + case SABER_RED: + return 0x000000ff; + break; + case SABER_ORANGE: + return 0x000088ff; + break; + case SABER_YELLOW: + return 0x0000ffff; + break; + case SABER_GREEN: + return 0x0000ff00; + break; + case SABER_BLUE: + return 0x00ff0000; + break; + case SABER_PURPLE: + return 0x00ff00ff; + break; + default: + return saberColor; + break; + } +} + +void CG_TestLine( vec3_t start, vec3_t end, int time, unsigned int color, int radius) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leType = LE_LINE; + le->startTime = cg->time; + le->endTime = cg->time + time; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + VectorCopy( start, re->origin ); + VectorCopy( end, re->oldorigin); + re->shaderTime = cg->time / 1000.0f; + + re->reType = RT_LINE; + re->radius = 0.5*radius; + re->customShader = cgs.media.whiteShader; //trap_R_RegisterShaderNoMip("textures/colombia/canvas_doublesided"); + + re->shaderTexCoord[0] = re->shaderTexCoord[1] = 1.0f; + + if (color==0) + { + re->shaderRGBA[0] = re->shaderRGBA[1] = re->shaderRGBA[2] = re->shaderRGBA[3] = 0xff; + } + else + { + color = CGDEBUG_SaberColor( color ); + re->shaderRGBA[0] = color & 0xff; + color >>= 8; + re->shaderRGBA[1] = color & 0xff; + color >>= 8; + re->shaderRGBA[2] = color & 0xff; +// color >>= 8; +// re->shaderRGBA[3] = color & 0xff; + re->shaderRGBA[3] = 0xff; + } + + le->color[3] = 1.0; + + //re->renderfx |= RF_DEPTHHACK; +} + +/* +================== +CG_ThrowChunk +================== +*/ +void CG_ThrowChunk( vec3_t origin, vec3_t velocity, qhandle_t hModel, int optionalSound, int startalpha ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg->time; + le->endTime = le->startTime + 5000 + random() * 3000; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = hModel; + + le->pos.trType = TR_GRAVITY; + le->angles.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + VectorSet(le->angles.trBase, 20, 20, 20); + VectorCopy( velocity, le->angles.trDelta ); + le->pos.trTime = cg->time; + le->angles.trTime = cg->time; + + le->leFlags = LEF_TUMBLE; + + le->angles.trBase[YAW] = 180; + + le->bounceFactor = 0.3f; + le->bounceSound = optionalSound; + + le->forceAlpha = startalpha; +} + +//---------------------------- +// +// Breaking Glass Technology +// +//---------------------------- + +// Since we have shared verts when we tesselate the glass sheet, it helps to have a +// random offset table set up up front. + +static float offX[20][20], + offZ[20][20]; + +#define FX_ALPHA_NONLINEAR 0x00000004 +#define FX_APPLY_PHYSICS 0x02000000 +#define FX_USE_ALPHA 0x08000000 + +static void CG_DoGlassQuad( vec3_t p[4], vec2_t uv[4], qboolean stick, int time, vec3_t dmgDir ) +{ + float bounce; + vec3_t rotDelta; + vec3_t vel, accel; + vec3_t rgb1; + addpolyArgStruct_t apArgs; + int i, i_2; + + VectorSet( vel, crandom() * 12, crandom() * 12, -1 ); + + if ( !stick ) + { + // We aren't a motion delayed chunk, so let us move quickly + VectorMA( vel, 0.3f, dmgDir, vel ); + } + + // Set up acceleration due to gravity, 800 is standard QuakeIII gravity, so let's use something close + VectorSet( accel, 0.0f, 0.0f, -(600.0f + random() * 100.0f ) ); + + // We are using an additive shader, so let's set the RGB low so we look more like transparent glass +// VectorSet( rgb1, 0.1f, 0.1f, 0.1f ); + VectorSet( rgb1, 1.0f, 1.0f, 1.0f ); + + // Being glass, we don't want to bounce much + bounce = random() * 0.2f + 0.15f; + + // Set up our random rotate, we only do PITCH and YAW, not ROLL. This is something like degrees per second + VectorSet( rotDelta, crandom() * 40.0f, crandom() * 40.0f, 0.0f ); + + //In an ideal world, this might actually work. + /* + CPoly *pol = FX_AddPoly(p, uv, 4, // verts, ST, vertCount + vel, accel, // motion + 0.15f, 0.0f, 85.0f, // alpha start, alpha end, alpha parm ( begin alpha fade when 85% of life is complete ) + rgb1, rgb1, 0.0f, // rgb start, rgb end, rgb parm ( not used ) + rotDelta, bounce, time, // rotation amount, bounce, and time to delay motion for ( zero if no delay ); + 6000, // life + cgi_R_RegisterShader( "gfx/misc/test_crackle" ), + FX_APPLY_PHYSICS | FX_ALPHA_NONLINEAR | FX_USE_ALPHA ); + + if ( random() > 0.95f && pol ) + { + pol->AddFlags( FX_IMPACT_RUNS_FX | FX_KILL_ON_IMPACT ); + pol->SetImpactFxID( theFxScheduler.RegisterEffect( "glass_impact" )); + } + */ + + //rww - this is dirty. + + i = 0; + i_2 = 0; + + while (i < 4) + { + while (i_2 < 3) + { + apArgs.p[i][i_2] = p[i][i_2]; + + i_2++; + } + + i_2 = 0; + i++; + } + + i = 0; + i_2 = 0; + + while (i < 4) + { + while (i_2 < 2) + { + apArgs.ev[i][i_2] = uv[i][i_2]; + + i_2++; + } + + i_2 = 0; + i++; + } + + apArgs.numVerts = 4; + VectorCopy(vel, apArgs.vel); + VectorCopy(accel, apArgs.accel); + + apArgs.alpha1 = 0.15f; + apArgs.alpha2 = 0.0f; + apArgs.alphaParm = 85.0f; + + VectorCopy(rgb1, apArgs.rgb1); + VectorCopy(rgb1, apArgs.rgb2); + + apArgs.rgbParm = 0.0f; + + VectorCopy(rotDelta, apArgs.rotationDelta); + + apArgs.bounce = bounce; + apArgs.motionDelay = time; + apArgs.killTime = 6000; + apArgs.shader = cgs.media.glassShardShader; + apArgs.flags = (FX_APPLY_PHYSICS | FX_ALPHA_NONLINEAR | FX_USE_ALPHA); + + trap_FX_AddPoly(&apArgs); +} + +static void CG_CalcBiLerp( vec3_t verts[4], vec3_t subVerts[4], vec2_t uv[4] ) +{ + vec3_t temp; + + // Nasty crap + VectorScale( verts[0], 1.0f - uv[0][0], subVerts[0] ); + VectorMA( subVerts[0], uv[0][0], verts[1], subVerts[0] ); + VectorScale( subVerts[0], 1.0f - uv[0][1], temp ); + VectorScale( verts[3], 1.0f - uv[0][0], subVerts[0] ); + VectorMA( subVerts[0], uv[0][0], verts[2], subVerts[0] ); + VectorMA( temp, uv[0][1], subVerts[0], subVerts[0] ); + + VectorScale( verts[0], 1.0f - uv[1][0], subVerts[1] ); + VectorMA( subVerts[1], uv[1][0], verts[1], subVerts[1] ); + VectorScale( subVerts[1], 1.0f - uv[1][1], temp ); + VectorScale( verts[3], 1.0f - uv[1][0], subVerts[1] ); + VectorMA( subVerts[1], uv[1][0], verts[2], subVerts[1] ); + VectorMA( temp, uv[1][1], subVerts[1], subVerts[1] ); + + VectorScale( verts[0], 1.0f - uv[2][0], subVerts[2] ); + VectorMA( subVerts[2], uv[2][0], verts[1], subVerts[2] ); + VectorScale( subVerts[2], 1.0f - uv[2][1], temp ); + VectorScale( verts[3], 1.0f - uv[2][0], subVerts[2] ); + VectorMA( subVerts[2], uv[2][0], verts[2], subVerts[2] ); + VectorMA( temp, uv[2][1], subVerts[2], subVerts[2] ); + + VectorScale( verts[0], 1.0f - uv[3][0], subVerts[3] ); + VectorMA( subVerts[3], uv[3][0], verts[1], subVerts[3] ); + VectorScale( subVerts[3], 1.0f - uv[3][1], temp ); + VectorScale( verts[3], 1.0f - uv[3][0], subVerts[3] ); + VectorMA( subVerts[3], uv[3][0], verts[2], subVerts[3] ); + VectorMA( temp, uv[3][1], subVerts[3], subVerts[3] ); +} +// bilinear +//f(p',q') = (1 - y) × {[(1 - x) × f(p,q)] + [x × f(p,q+1)]} + y × {[(1 - x) × f(p+1,q)] + [x × f(p+1,q+1)]}. + + +static void CG_CalcHeightWidth( vec3_t verts[4], float *height, float *width ) +{ + vec3_t dir1, dir2, cross; + + VectorSubtract( verts[3], verts[0], dir1 ); // v + VectorSubtract( verts[1], verts[0], dir2 ); // p-a + CrossProduct( dir1, dir2, cross ); + *width = VectorNormalize( cross ) / VectorNormalize( dir1 ); // v + VectorSubtract( verts[2], verts[0], dir2 ); // p-a + CrossProduct( dir1, dir2, cross ); + *width += VectorNormalize( cross ) / VectorNormalize( dir1 ); // v + *width *= 0.5f; + + VectorSubtract( verts[1], verts[0], dir1 ); // v + VectorSubtract( verts[2], verts[0], dir2 ); // p-a + CrossProduct( dir1, dir2, cross ); + *height = VectorNormalize( cross ) / VectorNormalize( dir1 ); // v + VectorSubtract( verts[3], verts[0], dir2 ); // p-a + CrossProduct( dir1, dir2, cross ); + *height += VectorNormalize( cross ) / VectorNormalize( dir1 ); // v + *height *= 0.5f; +} +//Consider a line in 3D with position vector "a" and direction vector "v" and +// let "p" be the position vector of an arbitrary point in 3D +//dist = len( crossprod(p-a,v) ) / len(v); + +void CG_InitGlass( void ) +{ + int i, t; + + // Build a table first, so that we can do a more unpredictable crack scheme + // do it once, up front to save a bit of time. + for ( i = 0; i < 20; i++ ) + { + for ( t = 0; t < 20; t++ ) + { + offX[t][i] = crandom() * 0.03f; + offZ[i][t] = crandom() * 0.03f; + } + } +} + +void Vector2Set(vec2_t a,float b,float c) +{ + a[0] = b; + a[1] = c; +} + +#define TIME_DECAY_SLOW 0.1f +#define TIME_DECAY_MED 0.04f +#define TIME_DECAY_FAST 0.009f + +void CG_DoGlass( vec3_t verts[4], vec3_t normal, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius, int maxShards ) +{ + int i, t; + int mxHeight, mxWidth; + float height, width; + float stepWidth, stepHeight; + float timeDecay; + float x, z; + float xx, zz; + float dif; + int time = 0; + int glassShards = 0; + qboolean stick = qtrue; + vec3_t subVerts[4]; + vec2_t biPoints[4]; + + // To do a smarter tesselation, we should figure out the relative height and width of the brush face, + // then use this to pick a lod value from 1-3 in each axis. This will give us 1-9 lod levels, which will + // hopefully be sufficient. + CG_CalcHeightWidth( verts, &height, &width ); + + trap_S_StartSound( dmgPt, -1, CHAN_AUTO, trap_S_RegisterSound("sound/effects/glassbreak1.wav")); + + // Pick "LOD" for height + if ( height < 100 ) + { + stepHeight = 0.2f; + mxHeight = 5; + timeDecay = TIME_DECAY_SLOW; + } + else if ( height > 220 ) + { + stepHeight = 0.05f; + mxHeight = 20; + timeDecay = TIME_DECAY_FAST; + } + else + { + stepHeight = 0.1f; + mxHeight = 10; + timeDecay = TIME_DECAY_MED; + } + + // Pick "LOD" for width + /* + if ( width < 100 ) + { + stepWidth = 0.2f; + mxWidth = 5; + timeDecay = ( timeDecay + TIME_DECAY_SLOW ) * 0.5f; + } + else if ( width > 220 ) + { + stepWidth = 0.05f; + mxWidth = 20; + timeDecay = ( timeDecay + TIME_DECAY_FAST ) * 0.5f; + } + else + { + stepWidth = 0.1f; + mxWidth = 10; + timeDecay = ( timeDecay + TIME_DECAY_MED ) * 0.5f; + } + */ + + //Attempt to scale the glass directly to the size of the window + + stepWidth = (0.25f - (width*0.0002)); //(width*0.0005)); + mxWidth = width*0.2; + timeDecay = ( timeDecay + TIME_DECAY_FAST ) * 0.5f; + + if (stepWidth < 0.01f) + { + stepWidth = 0.01f; + } + if (mxWidth < 5) + { + mxWidth = 5; + } + + for ( z = 0.0f, i = 0; z < 1.0f; z += stepHeight, i++ ) + { + for ( x = 0.0f, t = 0; x < 1.0f; x += stepWidth, t++ ) + { + // This is nasty.. + if ( t > 0 && t < mxWidth ) + { + xx = x - offX[i][t]; + } + else + { + xx = x; + } + + if ( i > 0 && i < mxHeight ) + { + zz = z - offZ[t][i]; + } + else + { + zz = z; + } + + Vector2Set( biPoints[0], xx, zz ); + + if ( t + 1 > 0 && t + 1 < mxWidth ) + { + xx = x - offX[i][t + 1]; + } + else + { + xx = x; + } + + if ( i > 0 && i < mxHeight ) + { + zz = z - offZ[t + 1][i]; + } + else + { + zz = z; + } + + Vector2Set( biPoints[1], xx + stepWidth, zz ); + + if ( t + 1 > 0 && t + 1 < mxWidth ) + { + xx = x - offX[i + 1][t + 1]; + } + else + { + xx = x; + } + + if ( i + 1 > 0 && i + 1 < mxHeight ) + { + zz = z - offZ[t + 1][i + 1]; + } + else + { + zz = z; + } + + Vector2Set( biPoints[2], xx + stepWidth, zz + stepHeight); + + if ( t > 0 && t < mxWidth ) + { + xx = x - offX[i + 1][t]; + } + else + { + xx = x; + } + + if ( i + 1 > 0 && i + 1 < mxHeight ) + { + zz = z - offZ[t][i + 1]; + } + else + { + zz = z; + } + + Vector2Set( biPoints[3], xx, zz + stepHeight ); + + CG_CalcBiLerp( verts, subVerts, biPoints ); + + dif = DistanceSquared( subVerts[0], dmgPt ) * timeDecay - random() * 32; + + // If we decrease dif, we are increasing the impact area, making it more likely to blow out large holes + dif -= dmgRadius * dmgRadius; + + if ( dif > 1 ) + { + stick = qtrue; + time = dif + random() * 200; + } + else + { + stick = qfalse; + time = 0; + } + + CG_DoGlassQuad( subVerts, biPoints, stick, time, dmgDir ); + glassShards++; + + if (maxShards && glassShards >= maxShards) + { + return; + } + } + } +} + +/* +================== +CG_GlassShatter +Break glass with fancy method +================== +*/ +void CG_GlassShatter(int entnum, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius, int maxShards) +{ + vec3_t verts[4], normal; + + if (cgs.inlineDrawModel[cg_entities[entnum].currentState.modelindex]) + { + trap_R_GetBModelVerts(cgs.inlineDrawModel[cg_entities[entnum].currentState.modelindex], verts, normal); + CG_DoGlass(verts, normal, dmgPt, dmgDir, dmgRadius, maxShards); + } + //otherwise something awful has happened. +} + +/* +================== +CG_GlassShatter_Old +Throws glass shards from within a given bounding box in the world +================== +*/ +void CG_GlassShatter_Old(int entnum, vec3_t org, vec3_t mins, vec3_t maxs) +{ + vec3_t velocity, a, shardorg, dif, difx; + float windowmass; + float shardsthrow = 0; + char chunkname[256]; + + trap_S_StartSound(org, entnum, CHAN_BODY, trap_S_RegisterSound("sound/effects/glassbreak1.wav")); + + VectorSubtract(maxs, mins, a); + + windowmass = VectorLength(a); //should give us some idea of how big the chunk of glass is + + while (shardsthrow < windowmass) + { + velocity[0] = crandom()*150; + velocity[1] = crandom()*150; + velocity[2] = 150 + crandom()*75; + + Com_sprintf(chunkname, sizeof(chunkname), "models/chunks/glass/glchunks_%i.md3", Q_irand(1, 6)); + VectorCopy(org, shardorg); + + dif[0] = (maxs[0]-mins[0])/2; + dif[1] = (maxs[1]-mins[1])/2; + dif[2] = (maxs[2]-mins[2])/2; + + if (dif[0] < 2) + { + dif[0] = 2; + } + if (dif[1] < 2) + { + dif[1] = 2; + } + if (dif[2] < 2) + { + dif[2] = 2; + } + + difx[0] = Q_irand(1, (dif[0]*0.9)*2); + difx[1] = Q_irand(1, (dif[1]*0.9)*2); + difx[2] = Q_irand(1, (dif[2]*0.9)*2); + + if (difx[0] > dif[0]) + { + shardorg[0] += difx[0]-(dif[0]); + } + else + { + shardorg[0] -= difx[0]; + } + if (difx[1] > dif[1]) + { + shardorg[1] += difx[1]-(dif[1]); + } + else + { + shardorg[1] -= difx[1]; + } + if (difx[2] > dif[2]) + { + shardorg[2] += difx[2]-(dif[2]); + } + else + { + shardorg[2] -= difx[2]; + } + + //CG_TestLine(org, shardorg, 5000, 0x0000ff, 3); + + CG_ThrowChunk( shardorg, velocity, trap_R_RegisterModel( chunkname ), 0, 254 ); + + shardsthrow += 10; + } +} + +/* +================== +CG_CreateDebris +Throws specified debris from within a given bounding box in the world +================== +*/ +#define DEBRIS_SPECIALCASE_ROCK -1 +#define DEBRIS_SPECIALCASE_CHUNKS -2 +#define DEBRIS_SPECIALCASE_WOOD -3 +#define DEBRIS_SPECIALCASE_GLASS -4 + +#define NUM_DEBRIS_MODELS_GLASS 8 +#define NUM_DEBRIS_MODELS_WOOD 8 +#define NUM_DEBRIS_MODELS_CHUNKS 3 +#define NUM_DEBRIS_MODELS_ROCKS 4 //12 + +int dbModels_Glass[NUM_DEBRIS_MODELS_GLASS]; +int dbModels_Wood[NUM_DEBRIS_MODELS_WOOD]; +int dbModels_Chunks[NUM_DEBRIS_MODELS_CHUNKS]; +int dbModels_Rocks[NUM_DEBRIS_MODELS_ROCKS]; + +void CG_CreateDebris(int entnum, vec3_t org, vec3_t mins, vec3_t maxs, int debrissound, int debrismodel) +{ + vec3_t velocity, a, shardorg, dif, difx; + float windowmass; + float shardsthrow = 0; + int omodel = debrismodel; + + if (omodel == DEBRIS_SPECIALCASE_GLASS && !dbModels_Glass[0]) + { //glass no longer exists, using it for metal. + dbModels_Glass[0] = trap_R_RegisterModel("models/chunks/metal/metal1_1.md3"); + dbModels_Glass[1] = trap_R_RegisterModel("models/chunks/metal/metal1_2.md3"); + dbModels_Glass[2] = trap_R_RegisterModel("models/chunks/metal/metal1_3.md3"); + dbModels_Glass[3] = trap_R_RegisterModel("models/chunks/metal/metal1_4.md3"); + dbModels_Glass[4] = trap_R_RegisterModel("models/chunks/metal/metal2_1.md3"); + dbModels_Glass[5] = trap_R_RegisterModel("models/chunks/metal/metal2_2.md3"); + dbModels_Glass[6] = trap_R_RegisterModel("models/chunks/metal/metal2_3.md3"); + dbModels_Glass[7] = trap_R_RegisterModel("models/chunks/metal/metal2_4.md3"); + } + if (omodel == DEBRIS_SPECIALCASE_WOOD && !dbModels_Wood[0]) + { + dbModels_Wood[0] = trap_R_RegisterModel("models/chunks/crate/crate1_1.md3"); + dbModels_Wood[1] = trap_R_RegisterModel("models/chunks/crate/crate1_2.md3"); + dbModels_Wood[2] = trap_R_RegisterModel("models/chunks/crate/crate1_3.md3"); + dbModels_Wood[3] = trap_R_RegisterModel("models/chunks/crate/crate1_4.md3"); + dbModels_Wood[4] = trap_R_RegisterModel("models/chunks/crate/crate2_1.md3"); + dbModels_Wood[5] = trap_R_RegisterModel("models/chunks/crate/crate2_2.md3"); + dbModels_Wood[6] = trap_R_RegisterModel("models/chunks/crate/crate2_3.md3"); + dbModels_Wood[7] = trap_R_RegisterModel("models/chunks/crate/crate2_4.md3"); + } + if (omodel == DEBRIS_SPECIALCASE_CHUNKS && !dbModels_Chunks[0]) + { + dbModels_Chunks[0] = trap_R_RegisterModel("models/chunks/generic/chunks_1.md3"); + dbModels_Chunks[1] = trap_R_RegisterModel("models/chunks/generic/chunks_2.md3"); + } + if (omodel == DEBRIS_SPECIALCASE_ROCK && !dbModels_Rocks[0]) + { + dbModels_Rocks[0] = trap_R_RegisterModel("models/chunks/rock/rock1_1.md3"); + dbModels_Rocks[1] = trap_R_RegisterModel("models/chunks/rock/rock1_2.md3"); + dbModels_Rocks[2] = trap_R_RegisterModel("models/chunks/rock/rock1_3.md3"); + dbModels_Rocks[3] = trap_R_RegisterModel("models/chunks/rock/rock1_4.md3"); + /* + dbModels_Rocks[4] = trap_R_RegisterModel("models/chunks/rock/rock2_1.md3"); + dbModels_Rocks[5] = trap_R_RegisterModel("models/chunks/rock/rock2_2.md3"); + dbModels_Rocks[6] = trap_R_RegisterModel("models/chunks/rock/rock2_3.md3"); + dbModels_Rocks[7] = trap_R_RegisterModel("models/chunks/rock/rock2_4.md3"); + dbModels_Rocks[8] = trap_R_RegisterModel("models/chunks/rock/rock3_1.md3"); + dbModels_Rocks[9] = trap_R_RegisterModel("models/chunks/rock/rock3_2.md3"); + dbModels_Rocks[10] = trap_R_RegisterModel("models/chunks/rock/rock3_3.md3"); + dbModels_Rocks[11] = trap_R_RegisterModel("models/chunks/rock/rock3_4.md3"); + */ + } + + VectorSubtract(maxs, mins, a); + + windowmass = VectorLength(a); //should give us some idea of how big the chunk of glass is + + while (shardsthrow < windowmass) + { + velocity[0] = crandom()*150; + velocity[1] = crandom()*150; + velocity[2] = 150 + crandom()*75; + + if (omodel == DEBRIS_SPECIALCASE_GLASS) + { + debrismodel = dbModels_Glass[Q_irand(0, NUM_DEBRIS_MODELS_GLASS-1)]; + } + else if (omodel == DEBRIS_SPECIALCASE_WOOD) + { + debrismodel = dbModels_Wood[Q_irand(0, NUM_DEBRIS_MODELS_WOOD-1)]; + } + else if (omodel == DEBRIS_SPECIALCASE_CHUNKS) + { + debrismodel = dbModels_Chunks[Q_irand(0, NUM_DEBRIS_MODELS_CHUNKS-1)]; + } + else if (omodel == DEBRIS_SPECIALCASE_ROCK) + { + debrismodel = dbModels_Rocks[Q_irand(0, NUM_DEBRIS_MODELS_ROCKS-1)]; + } + + VectorCopy(org, shardorg); + + dif[0] = (maxs[0]-mins[0])/2; + dif[1] = (maxs[1]-mins[1])/2; + dif[2] = (maxs[2]-mins[2])/2; + + if (dif[0] < 2) + { + dif[0] = 2; + } + if (dif[1] < 2) + { + dif[1] = 2; + } + if (dif[2] < 2) + { + dif[2] = 2; + } + + difx[0] = Q_irand(1, (dif[0]*0.9)*2); + difx[1] = Q_irand(1, (dif[1]*0.9)*2); + difx[2] = Q_irand(1, (dif[2]*0.9)*2); + + if (difx[0] > dif[0]) + { + shardorg[0] += difx[0]-(dif[0]); + } + else + { + shardorg[0] -= difx[0]; + } + if (difx[1] > dif[1]) + { + shardorg[1] += difx[1]-(dif[1]); + } + else + { + shardorg[1] -= difx[1]; + } + if (difx[2] > dif[2]) + { + shardorg[2] += difx[2]-(dif[2]); + } + else + { + shardorg[2] -= difx[2]; + } + + //CG_TestLine(org, shardorg, 5000, 0x0000ff, 3); + + CG_ThrowChunk( shardorg, velocity, debrismodel, debrissound, 0 ); + + shardsthrow += 10; + } +} + +//========================================================== +//SP-style chunks +//========================================================== + +/* +------------------------- +CG_ExplosionEffects + +Used to find the player and shake the camera if close enough +intensity ranges from 1 (minor tremble) to 16 (major quake) +------------------------- +*/ + +void CG_ExplosionEffects( vec3_t origin, float intensity, int radius, int time ) +{ + //FIXME: When exactly is the vieworg calculated in relation to the rest of the frame?s + + vec3_t dir; + float dist, intensityScale; + float realIntensity; + + VectorSubtract( cg->refdef.vieworg, origin, dir ); + dist = VectorNormalize( dir ); + + //Use the dir to add kick to the explosion + + if ( dist > radius ) + return; + + intensityScale = 1 - ( dist / (float) radius ); + realIntensity = intensity * intensityScale; + + CGCam_Shake( realIntensity, time ); +} + +/* +------------------------- +CG_MiscModelExplosion + +Adds an explosion to a misc model breakables +------------------------- +*/ + +void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType ) +{ + int ct = 13; + float r; + vec3_t org, mid, dir; + char *effect = NULL, *effect2 = NULL; + int eID1, eID2 = 0; + int i; + + VectorAdd( mins, maxs, mid ); + VectorScale( mid, 0.5f, mid ); + + switch( chunkType ) + { + case MAT_GLASS: + effect = "chunks/glassbreak"; + ct = 5; + break; + case MAT_GLASS_METAL: + effect = "chunks/glassbreak"; + effect2 = "chunks/metalexplode"; + ct = 5; + break; + case MAT_ELECTRICAL: + case MAT_ELEC_METAL: + effect = "chunks/sparkexplode"; + ct = 5; + break; + case MAT_METAL: + case MAT_METAL2: + case MAT_METAL3: + case MAT_CRATE1: + case MAT_CRATE2: + effect = "chunks/metalexplode"; + ct = 2; + break; + case MAT_GRATE1: + effect = "chunks/grateexplode"; + ct = 8; + break; + case MAT_ROPE: + ct = 20; + effect = "chunks/ropebreak"; + break; + case MAT_WHITE_METAL: //not sure what this crap is really supposed to be.. + case MAT_DRK_STONE: + case MAT_LT_STONE: + case MAT_GREY_STONE: + case MAT_SNOWY_ROCK: + switch( size ) + { + case 2: + effect = "chunks/rockbreaklg"; + break; + case 1: + default: + effect = "chunks/rockbreakmed"; + break; + } + } + + if ( !effect ) + { + return; + } + + ct += 7 * size; + + // FIXME: real precache .. VERify that these need to be here...don't think they would because the effects should be registered in g_breakable + //rww - No they don't.. indexed effects gameside get precached on load clientside, as server objects are setup before client asset load time. + //However, we need to index them, so.. + eID1 = trap_FX_RegisterEffect( effect ); + + if ( effect2 && effect2[0] ) + { + // FIXME: real precache + eID2 = trap_FX_RegisterEffect( effect2 ); + } + + // spawn chunk roughly in the bbox of the thing.. + for ( i = 0; i < ct; i++ ) + { + int j; + for( j = 0; j < 3; j++ ) + { + r = random() * 0.8f + 0.1f; + org[j] = ( r * mins[j] + ( 1 - r ) * maxs[j] ); + } + + // shoot effect away from center + VectorSubtract( org, mid, dir ); + VectorNormalize( dir ); + + if ( effect2 && effect2[0] && ( rand() & 1 )) + { + trap_FX_PlayEffectID( eID2, org, dir, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( eID1, org, dir, -1, -1 ); + } + } +} + +/* +------------------------- +CG_Chunks + +Fun chunk spewer +------------------------- +*/ + +void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs, + float speed, int numChunks, material_t chunkType, int customChunk, float baseScale ) +{ + localEntity_t *le; + refEntity_t *re; + vec3_t dir; + int i, j, k; + int chunkModel = 0; + leBounceSoundType_t bounce = LEBS_NONE; + float r, speedMod = 1.0f; + qboolean chunk = qfalse; + + if ( chunkType == MAT_NONE ) + { + // Well, we should do nothing + return; + } + + // Set up our chunk sound info...breaking sounds are done here so they are done once on breaking..some return instantly because the chunks are done with effects instead of models + switch( chunkType ) + { + case MAT_GLASS: + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound ); + return; + break; + case MAT_GRATE1: + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.grateSound ); + return; + break; + case MAT_ELECTRICAL:// (sparks) + trap_S_StartSound( NULL, owner, CHAN_BODY, trap_S_RegisterSound (va("sound/ambience/spark%d.wav", Q_irand(1, 6))) ); + return; + break; + case MAT_DRK_STONE: + case MAT_LT_STONE: + case MAT_GREY_STONE: + case MAT_WHITE_METAL: // not quite sure what this stuff is supposed to be...it's for Stu + case MAT_SNOWY_ROCK: + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.rockBreakSound ); + bounce = LEBS_ROCK; + speedMod = 0.5f; // rock blows up less + break; + case MAT_GLASS_METAL: + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound ); // FIXME: should probably have a custom sound + bounce = LEBS_METAL; + break; + case MAT_CRATE1: + case MAT_CRATE2: + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.crateBreakSound[Q_irand(0,1)] ); + break; + case MAT_METAL: + case MAT_METAL2: + case MAT_METAL3: + case MAT_ELEC_METAL:// FIXME: maybe have its own sound? + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.chunkSound ); + bounce = LEBS_METAL; + speedMod = 0.8f; // metal blows up a bit more + break; + case MAT_ROPE: +// trap_S_StartSound( NULL, owner, CHAN_BODY, cgi_S_RegisterSound( "" )); FIXME: needs a sound + return; + break; + } + + if ( baseScale <= 0.0f ) + { + baseScale = 1.0f; + } + + // Chunks + for( i = 0; i < numChunks; i++ ) + { + if ( customChunk > 0 ) + { + // Try to use a custom chunk. + if ( cgs.gameModels[customChunk] ) + { + chunk = qtrue; + chunkModel = cgs.gameModels[customChunk]; + } + } + + if ( !chunk ) + { + // No custom chunk. Pick a random chunk type at run-time so we don't get the same chunks + switch( chunkType ) + { + case MAT_METAL2: //bluegrey + chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)]; + break; + case MAT_GREY_STONE://gray + chunkModel = cgs.media.chunkModels[CHUNK_ROCK1][Q_irand(0, 3)]; + break; + case MAT_LT_STONE: //tan + chunkModel = cgs.media.chunkModels[CHUNK_ROCK2][Q_irand(0, 3)]; + break; + case MAT_DRK_STONE://brown + chunkModel = cgs.media.chunkModels[CHUNK_ROCK3][Q_irand(0, 3)]; + break; + case MAT_SNOWY_ROCK://gray & brown + if ( Q_irand( 0, 1 ) ) + { + chunkModel = cgs.media.chunkModels[CHUNK_ROCK1][Q_irand(0, 3)]; + } + else + { + chunkModel = cgs.media.chunkModels[CHUNK_ROCK3][Q_irand(0, 3)]; + } + break; + case MAT_WHITE_METAL: + chunkModel = cgs.media.chunkModels[CHUNK_WHITE_METAL][Q_irand(0, 3)]; + break; + case MAT_CRATE1://yellow multi-colored crate chunks + chunkModel = cgs.media.chunkModels[CHUNK_CRATE1][Q_irand(0, 3)]; + break; + case MAT_CRATE2://red multi-colored crate chunks + chunkModel = cgs.media.chunkModels[CHUNK_CRATE2][Q_irand(0, 3)]; + break; + case MAT_ELEC_METAL: + case MAT_GLASS_METAL: + case MAT_METAL://grey + chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)]; + break; + case MAT_METAL3: + if ( rand() & 1 ) + { + chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)]; + } + else + { + chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)]; + } + break; + } + } + + // It wouldn't look good to throw a bunch of RGB axis models...so make sure we have something to work with. + if ( chunkModel ) + { + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + re->hModel = chunkModel; + le->leType = LE_FRAGMENT; + le->endTime = cg->time + 1300 + random() * 900; + + // spawn chunk roughly in the bbox of the thing...bias towards center in case thing blowing up doesn't complete fill its bbox. + for( j = 0; j < 3; j++ ) + { + r = random() * 0.8f + 0.1f; + re->origin[j] = ( r * mins[j] + ( 1 - r ) * maxs[j] ); + } + VectorCopy( re->origin, le->pos.trBase ); + + // Move out from center of thing, otherwise you can end up things moving across the brush in an undesirable direction. Visually looks wrong + VectorSubtract( re->origin, origin, dir ); + VectorNormalize( dir ); + VectorScale( dir, flrand( speed * 0.5f, speed * 1.25f ) * speedMod, le->pos.trDelta ); + + // Angular Velocity + VectorSet( le->angles.trBase, random() * 360, random() * 360, random() * 360 ); + + le->angles.trDelta[0] = crandom(); + le->angles.trDelta[1] = crandom(); + le->angles.trDelta[2] = 0; // don't do roll + + VectorScale( le->angles.trDelta, random() * 600.0f + 200.0f, le->angles.trDelta ); + + le->pos.trType = TR_GRAVITY; + le->angles.trType = TR_LINEAR; + le->pos.trTime = le->angles.trTime = cg->time; + le->bounceFactor = 0.2f + random() * 0.2f; + le->leFlags |= LEF_TUMBLE; + //le->ownerGentNum = owner; + le->leBounceSoundType = bounce; + + // Make sure that we have the desired start size set + le->radius = flrand( baseScale * 0.75f, baseScale * 1.25f ); + re->nonNormalizedAxes = qtrue; + AxisCopy( axisDefault, re->axis ); // could do an angles to axis, but this is cheaper and works ok + for( k = 0; k < 3; k++ ) + { + re->modelScale[k] = le->radius; + } + ScaleModelAxis(re); + /* + for( k = 0; k < 3; k++ ) + { + VectorScale( re->axis[k], le->radius, re->axis[k] ); + } + */ + } + } +} + +/* +================== +CG_ScorePlum +================== +*/ +void CG_ScorePlum( int client, vec3_t org, int score ) { + localEntity_t *le; + refEntity_t *re; + vec3_t angles; + static vec3_t lastPos; + + // only visualize for the client that scored + if (client != cg->predictedPlayerState.clientNum || cg_scorePlum.integer == 0) { + return; + } + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_SCOREPLUM; + le->startTime = cg->time; + le->endTime = cg->time + 4000; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + le->radius = score; + + VectorCopy( org, le->pos.trBase ); + if (org[2] >= lastPos[2] - 20 && org[2] <= lastPos[2] + 20) { + le->pos.trBase[2] -= 20; + } + + //CG_Printf( "Plum origin %i %i %i -- %i\n", (int)org[0], (int)org[1], (int)org[2], (int)Distance(org, lastPos)); + VectorCopy(org, lastPos); + + + re = &le->refEntity; + + re->reType = RT_SPRITE; + re->radius = 16; + + VectorClear(angles); + AnglesToAxis( angles, re->axis ); +} + +/* +==================== +CG_MakeExplosion +==================== +*/ +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, int numFrames, qhandle_t shader, + int msec, qboolean isSprite, float scale, int flags ) +{ + float ang = 0; + localEntity_t *ex; + int offset; + vec3_t tmpVec, newOrigin; + + if ( msec <= 0 ) { + CG_Error( "CG_MakeExplosion: msec = %i", msec ); + } + + // skew the time a bit so they aren't all in sync + offset = rand() & 63; + + ex = CG_AllocLocalEntity(); + if ( isSprite ) { + ex->leType = LE_SPRITE_EXPLOSION; + ex->refEntity.rotation = rand() % 360; + ex->radius = scale; + VectorScale( dir, 16, tmpVec ); + VectorAdd( tmpVec, origin, newOrigin ); + } else { + ex->leType = LE_EXPLOSION; + VectorCopy( origin, newOrigin ); + + // set axis with random rotate when necessary + if ( !dir ) + { + AxisClear( ex->refEntity.axis ); + } + else + { + if ( !(flags & LEF_NO_RANDOM_ROTATE) ) + ang = rand() % 360; + VectorCopy( dir, ex->refEntity.axis[0] ); + RotateAroundDirection( ex->refEntity.axis, ang ); + } + } + + ex->startTime = cg->time - offset; + ex->endTime = ex->startTime + msec; + + // bias the time so all shader effects start correctly + ex->refEntity.shaderTime = ex->startTime / 1000.0f; + + ex->refEntity.hModel = hModel; + ex->refEntity.customShader = shader; + ex->lifeRate = (float)numFrames / msec; + ex->leFlags = flags; + + //Scale the explosion + if (scale != 1) { + ex->refEntity.nonNormalizedAxes = qtrue; + + VectorScale( ex->refEntity.axis[0], scale, ex->refEntity.axis[0] ); + VectorScale( ex->refEntity.axis[1], scale, ex->refEntity.axis[1] ); + VectorScale( ex->refEntity.axis[2], scale, ex->refEntity.axis[2] ); + } + // set origin + VectorCopy ( newOrigin, ex->refEntity.origin); + VectorCopy ( newOrigin, ex->refEntity.oldorigin ); + + ex->color[0] = ex->color[1] = ex->color[2] = 1.0; + + return ex; +} + + +/* +------------------------- +CG_SurfaceExplosion + +Adds an explosion to a surface +------------------------- +*/ + +#define NUM_SPARKS 12 +#define NUM_PUFFS 1 +#define NUM_EXPLOSIONS 4 + +void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke ) +{ + localEntity_t *le; + //FXTrail *particle; + vec3_t direction, new_org; + vec3_t velocity = { 0, 0, 0 }; + vec3_t temp_org, temp_vel; + float scale, dscale; + int i, numSparks; + + //Sparks + numSparks = 16 + (random() * 16.0f); + + for ( i = 0; i < numSparks; i++ ) + { + scale = 0.25f + (random() * 2.0f); + dscale = -scale*0.5; + +/* particle = FX_AddTrail( origin, + NULL, + NULL, + 32.0f, + -64.0f, + scale, + -scale, + 1.0f, + 0.0f, + 0.25f, + 4000.0f, + cgs.media.sparkShader, + rand() & FXF_BOUNCE); + if ( particle == NULL ) + return; + + FXE_Spray( normal, 500, 150, 1.0f, 768 + (rand() & 255), (FXPrimitive *) particle );*/ + } + + //Smoke + //Move this out a little from the impact surface + VectorMA( origin, 4, normal, new_org ); + VectorSet( velocity, 0.0f, 0.0f, 16.0f ); + + for ( i = 0; i < 4; i++ ) + { + VectorSet( temp_org, new_org[0] + (crandom() * 16.0f), new_org[1] + (crandom() * 16.0f), new_org[2] + (random() * 4.0f) ); + VectorSet( temp_vel, velocity[0] + (crandom() * 8.0f), velocity[1] + (crandom() * 8.0f), velocity[2] + (crandom() * 8.0f) ); + +/* FX_AddSprite( temp_org, + temp_vel, + NULL, + 64.0f + (random() * 32.0f), + 16.0f, + 1.0f, + 0.0f, + 20.0f + (crandom() * 90.0f), + 0.5f, + 1500.0f, + cgs.media.smokeShader, FXF_USE_ALPHA_CHAN );*/ + } + + //Core of the explosion + + //Orient the explosions to face the camera + VectorSubtract( cg->refdef.vieworg, origin, direction ); + VectorNormalize( direction ); + + //Tag the last one with a light + le = CG_MakeExplosion( origin, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 500, qfalse, radius * 0.02f + (random() * 0.3f), 0); + le->light = 150; + VectorSet( le->lightColor, 0.9f, 0.8f, 0.5f ); + + for ( i = 0; i < NUM_EXPLOSIONS-1; i ++) + { + VectorSet( new_org, (origin[0] + (16 + (crandom() * 8))*crandom()), (origin[1] + (16 + (crandom() * 8))*crandom()), (origin[2] + (16 + (crandom() * 8))*crandom()) ); + le = CG_MakeExplosion( new_org, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 300 + (rand() & 99), qfalse, radius * 0.05f + (crandom() *0.3f), 0); + } + + //Shake the camera + CG_ExplosionEffects( origin, shake_speed, 350, 750 ); + + // The level designers wanted to be able to turn the smoke spawners off. The rationale is that they + // want to blow up catwalks and such that fall down...when that happens, it shouldn't really leave a mark + // and a smoke spewer at the explosion point... + if ( smoke ) + { + VectorMA( origin, -8, normal, temp_org ); +// FX_AddSpawner( temp_org, normal, NULL, NULL, 100, random()*25.0f, 5000.0f, (void *) CG_SmokeSpawn ); + + //Impact mark + //FIXME: Replace mark + //CG_ImpactMark( cgs.media.burnMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 8, qfalse ); + } +} + +/* +================= +CG_Bleed + +This is the spurt of blood when a character gets hit +================= +*/ +void CG_Bleed( vec3_t origin, int entityNum ) { + localEntity_t *ex; + + if ( !cg_blood.integer ) { + return; + } + + ex = CG_AllocLocalEntity(); + ex->leType = LE_EXPLOSION; + + ex->startTime = cg->time; + ex->endTime = ex->startTime + 500; + + VectorCopy ( origin, ex->refEntity.origin); + ex->refEntity.reType = RT_SPRITE; + ex->refEntity.rotation = rand() % 360; + ex->refEntity.radius = 24; + + ex->refEntity.customShader = 0;//cgs.media.bloodExplosionShader; + + // don't show player's own blood in view + if ( entityNum == cg->snap->ps.clientNum ) { + ex->refEntity.renderfx |= RF_THIRD_PERSON; + } +} + + + +/* +================== +CG_LaunchGib +================== +*/ +void CG_LaunchGib( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg->time; + le->endTime = le->startTime + 5000 + random() * 3000; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = hModel; + + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg->time; + + le->bounceFactor = 0.6f; + + le->leBounceSoundType = LEBS_BLOOD; + le->leMarkType = LEMT_BLOOD; +} diff --git a/codemp/cgame/cg_ents.c b/codemp/cgame/cg_ents.c new file mode 100644 index 0000000..ad7259a --- /dev/null +++ b/codemp/cgame/cg_ents.c @@ -0,0 +1,3888 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_ents.c -- present snapshot entities, happens every single frame + +#include "cg_local.h" +/* +Ghoul2 Insert Start +*/ +#include "..\game\q_shared.h" +#include "..\ghoul2\g2.h" +/* +Ghoul2 Insert end +*/ + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + +extern qboolean CG_InFighter( void ); +static void CG_Missile( centity_t *cent ); + + +//Returns true if the given ghoul2 data is using a model which belongs to +//an already active client. cgs.clientinfo must be up to date when this +//is called. +bool CG_ModelAllowed(void *ghoul2) +{ + const char *modelName; + int i; + bool found = false; + + //Get the model name from ghoul2. Abort if something went wrong. + trap_G2API_GetModelName(ghoul2, 0, &modelName); + if(!modelName) { + return false; + } + + //Try to match the model name against the model for an active client. + for(i=0; ioldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis ); + entity->backlerp = parent->backlerp; +} + + +/* +====================== +CG_PositionRotatedEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + vec3_t tempAxis[3]; + +//AxisClear( entity->axis ); + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( entity->axis, lerped.axis, tempAxis ); + MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis ); +} + + + +/* +========================================================================== + +FUNCTIONS CALLED EACH FRAME + +========================================================================== +*/ + +//only need to use the CG_S_ system when you want a looping sound that isn't going to add itself +//via trap each frame. Such as ones generated by events. +/* +====================== +CG_SetEntitySoundPosition + +Also called by event processing code +====================== +*/ +void CG_SetEntitySoundPosition( centity_t *cent ) { + if ( cent->currentState.solid == SOLID_BMODEL ) + { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_UpdateEntityPosition( cent->currentState.number, origin ); + } + else + { + trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); + } +} + +/* +================== +CG_EntitiyPosition + +Used by Xbox sound code to get ent positions when adding sounds +================== +*/ +void CG_EntityPosition( int i, vec3_t ret ) +{ + if(!cg_entities) { + ret[0] = ret[1] = ret[2] = 0.0f; + return; + } + + centity_t *cent = &cg_entities[i]; + + if ( cent->currentState.solid == SOLID_BMODEL ) + { + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, ret ); + } + else + { + VectorCopy( cent->lerpOrigin, ret ); + } +} + +/* +================== +CG_S_AddLoopingSound + +Set the current looping sounds on the entity. +================== +*/ +void CG_S_AddLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + centity_t *cent = &cg_entities[entityNum]; + cgLoopSound_t *cSound = NULL; + int i = 0; + qboolean alreadyPlaying = qfalse; + + //first see if we're already looping this sound handle. + while (i < cent->numLoopingSounds) + { + cSound = ¢->loopingSound[i]; + + if (cSound->sfx == sfx) + { + alreadyPlaying = qtrue; + break; + } + } + + if (alreadyPlaying && cSound) + { //if this is the case, just update the properties of the looping sound and return. + VectorCopy(origin, cSound->origin); + VectorCopy(velocity, cSound->velocity); + } + else if (cent->numLoopingSounds >= MAX_CG_LOOPSOUNDS) + { //Just don't add it then I suppose. +#ifdef _XBOX // We decreased this number, so I'd like to know if it gets overflowed + Com_Printf( S_COLOR_YELLOW "Warning: MAX_CG_LOOPSOUNDS exceeded!!\n" ); +#endif + return; + } + + //Add a new looping sound. + cSound = ¢->loopingSound[cent->numLoopingSounds]; + + cSound->entityNum = entityNum; + VectorCopy(origin, cSound->origin); + VectorCopy(velocity, cSound->velocity); + cSound->sfx = sfx; + + cent->numLoopingSounds++; +} + +/* +================== +CG_S_AddLoopingSound + +For now just redirect, might eventually do something different. +================== +*/ +void CG_S_AddRealLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + CG_S_AddLoopingSound(entityNum, origin, velocity, sfx); +} + +/* +================== +CG_S_AddLoopingSound + +Clear looping sounds. +================== +*/ +void CG_S_StopLoopingSound(int entityNum, sfxHandle_t sfx) +{ + centity_t *cent = &cg_entities[entityNum]; + cgLoopSound_t *cSound; + + if (sfx == -1) + { //clear all the looping sounds on the entity + cent->numLoopingSounds = 0; + } + else + { //otherwise, clear only the specified looping sound + int i = 0; + + while (i < cent->numLoopingSounds) + { + cSound = ¢->loopingSound[i]; + + if (cSound->sfx == sfx) + { //remove it then + int x = i+1; + + while (x < cent->numLoopingSounds) + { + memcpy(¢->loopingSound[x-1], ¢->loopingSound[x], sizeof(cent->loopingSound[x])); + x++; + } + cent->numLoopingSounds--; + } + + i++; + } + } + //trap_S_StopLoopingSound(entityNum); +} + +/* +================== +CG_S_UpdateLoopingSounds + +Update any existing looping sounds on the entity. +================== +*/ +void CG_S_UpdateLoopingSounds(int entityNum) +{ + centity_t *cent = &cg_entities[entityNum]; + cgLoopSound_t *cSound; + vec3_t lerpOrg; + float *v; + int i = 0; + + if (!cent->numLoopingSounds) + { + return; + } + + if (cent->currentState.eType == ET_MOVER) + { + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, lerpOrg ); + } + else + { + VectorCopy(cent->lerpOrigin, lerpOrg); + } + + while (i < cent->numLoopingSounds) + { + cSound = ¢->loopingSound[i]; + + //trap_S_AddLoopingSound(entityNum, cSound->origin, cSound->velocity, cSound->sfx); + //I guess just keep using lerpOrigin for now, + trap_S_AddLoopingSound(entityNum, lerpOrg, cSound->velocity, cSound->sfx); + i++; + } +} + +/* +================== +CG_EntityEffects + +Add continuous entity effects, like local entity emission and lighting +================== +*/ +static void CG_EntityEffects( centity_t *cent ) { + + // update sound origins + CG_SetEntitySoundPosition( cent ); + + // add loop sound + if ( cent->currentState.loopSound || (cent->currentState.loopIsSoundset && cent->currentState.number >= MAX_CLIENTS) ) { + sfxHandle_t realSoundIndex = -1; + + if (cent->currentState.loopIsSoundset && cent->currentState.number >= MAX_CLIENTS) + { //If this is so, then first get our soundset from the index, and loopSound actually contains which part of the set to + //use rather than a sound index (BMS_START [0], BMS_MID [1], or BMS_END [2]). Typically loop sounds will be BMS_MID. + const char *soundSet; + + soundSet = CG_ConfigString( CS_AMBIENT_SET + cent->currentState.soundSetIndex ); + + if (soundSet && soundSet[0]) + { + realSoundIndex = trap_AS_GetBModelSound(soundSet, cent->currentState.loopSound); + } + } + else + { + realSoundIndex = cgs.gameSounds[ cent->currentState.loopSound ]; + } + + //rww - doors and things with looping sounds have a crazy origin (being brush models and all) + if (realSoundIndex != -1) + { + if ( cent->currentState.solid == SOLID_BMODEL ) + { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_AddLoopingSound( cent->currentState.number, origin, vec3_origin, + realSoundIndex ); + } + else if (cent->currentState.eType != ET_SPEAKER) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + realSoundIndex ); + } else { + trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + realSoundIndex ); + } + } + } + + + // constant light glow + if ( cent->currentState.constantLight ) { + int cl; + int i, r, g, b; + + cl = cent->currentState.constantLight; + r = cl & 255; + g = ( cl >> 8 ) & 255; + b = ( cl >> 16 ) & 255; + i = ( ( cl >> 24 ) & 255 ) * 4; + trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); + } + +} + +localEntity_t *FX_AddOrientedLine(vec3_t start, vec3_t end, vec3_t normal, float stScale, float scale, + float dscale, float startalpha, float endalpha, float killTime, qhandle_t shader) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddLine: NULL shader\n"); + } +#endif + + le = CG_AllocLocalEntity(); + le->leType = LE_OLINE; + + le->startTime = cg->time; + le->endTime = le->startTime + killTime; + le->data.line.width = scale; + le->data.line.dwidth = dscale; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + + le->refEntity.data.line.stscale = stScale; + le->refEntity.data.line.width = scale; + + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( start, le->refEntity.origin); + VectorCopy ( end, le->refEntity.oldorigin ); + + AxisClear(le->refEntity.axis); + VectorCopy( normal, le->refEntity.axis[0] ); + RotateAroundDirection( le->refEntity.axis, 0); // le->refEntity.data.sprite.rotation ); This is roll in quad land + + le->refEntity.shaderRGBA[0] = 0xff; + le->refEntity.shaderRGBA[1] = 0xff; + le->refEntity.shaderRGBA[2] = 0xff; + le->refEntity.shaderRGBA[3] = 0xff; + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = 1.0; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + return(le); +} + +void FX_DrawPortableShield(centity_t *cent) +{ + //rww - this code differs a bit from the draw code in EF, I don't know why I had to do + //it this way yet it worked in EF the other way. + + int xaxis, height, posWidth, negWidth, team; + vec3_t start, end, normal; + localEntity_t *le; + qhandle_t shader; + char buf[1024]; + + trap_Cvar_VariableStringBuffer("cl_paused", buf, sizeof(buf)); + + if (atoi(buf)) + { //rww - fix to keep from rendering repeatedly while HUD menu is up + return; + } + + if (cent->currentState.eFlags & EF_NODRAW) + { + return; + } + + // decode the data stored in time2 + xaxis = ((cent->currentState.time2 >> 24) & 1); + height = ((cent->currentState.time2 >> 16) & 255); + posWidth = ((cent->currentState.time2 >> 8) & 255); + negWidth = (cent->currentState.time2 & 255); + + team = (cent->currentState.otherEntityNum2); + + VectorClear(normal); + + VectorCopy(cent->lerpOrigin, start); + VectorCopy(cent->lerpOrigin, end); + + if (xaxis) // drawing along x-axis + { + start[0] -= negWidth; + end[0] += posWidth; + } + else + { + start[1] -= negWidth; + end[1] += posWidth; + } + + normal[0] = 1; + normal[1] = 1; + + start[2] += height/2; + end[2] += height/2; + + if (team == TEAM_RED) + { + if (cent->currentState.trickedentindex) + { + shader = trap_R_RegisterShader( "gfx/misc/red_dmgshield" ); + } + else + { + shader = trap_R_RegisterShader( "gfx/misc/red_portashield" ); + } + } + else + { + if (cent->currentState.trickedentindex) + { + shader = trap_R_RegisterShader( "gfx/misc/blue_dmgshield" ); + } + else + { + shader = trap_R_RegisterShader( "gfx/misc/blue_portashield" ); + } + } + + le = FX_AddOrientedLine(start, end, normal, 1.0f, height, 0.0f, 1.0f, 1.0f, 50.0, shader); +} + +/* +================== +CG_Special +================== +*/ +void CG_Special( centity_t *cent ) { + entityState_t *s1; + + s1 = ¢->currentState; + + if (!s1) + { + return; + } + + // if set to invisible, skip + if (!s1->modelindex) { + return; + } + + if (s1->modelindex == HI_SHIELD) + { // The portable shield should go through a different rendering function. + FX_DrawPortableShield(cent); + return; + } +} + +/* +Ghoul2 Insert Start +*/ + +// Copy the ghoul2 data into the ref ent correctly +void CG_SetGhoul2Info( refEntity_t *ent, centity_t *cent) +{ + + ent->ghoul2 = cent->ghoul2; + VectorCopy( cent->modelScale, ent->modelScale); + ent->radius = cent->radius; + VectorCopy (cent->lerpAngles, ent->angles); +} + + + +// create 8 new points on screen around a model so we can see it's bounding box +void CG_CreateBBRefEnts(entityState_t *s1, vec3_t origin ) +{ +/* +//g2r +#if _DEBUG + refEntity_t point[8]; + int i; + vec3_t angles = {0,0,0}; + + for (i=0; i<8; i++) + { + memset (&point[i], 0, sizeof(refEntity_t)); + point[i].reType = RT_SPRITE; + point[i].radius = 1; + point[i].customShader = trap_R_RegisterShader("textures/tests/circle"); + point[i].shaderRGBA[0] = 255; + point[i].shaderRGBA[1] = 255; + point[i].shaderRGBA[2] = 255; + point[i].shaderRGBA[3] = 255; + + AnglesToAxis( angles, point[i].axis ); + + // now, we need to put the correct origins into each origin from the mins and max's + switch(i) + { + case 0: + VectorCopy(s1->mins, point[i].origin); + break; + case 1: + VectorCopy(s1->mins, point[i].origin); + point[i].origin[0] = s1->maxs[0]; + break; + case 2: + VectorCopy(s1->mins, point[i].origin); + point[i].origin[1] = s1->maxs[1]; + break; + case 3: + VectorCopy(s1->mins, point[i].origin); + point[i].origin[0] = s1->maxs[0]; + point[i].origin[1] = s1->maxs[1]; + break; + case 4: + VectorCopy(s1->maxs, point[i].origin); + break; + case 5: + VectorCopy(s1->maxs, point[i].origin); + point[i].origin[0] = s1->mins[0]; + break; + case 6: + VectorCopy(s1->maxs, point[i].origin); + point[i].origin[1] = s1->mins[1]; + break; + case 7: + VectorCopy(s1->maxs, point[i].origin); + point[i].origin[0] = s1->mins[0]; + point[i].origin[1] = s1->mins[1]; + break; + } + + // add the original origin to each point and then stuff them out there + VectorAdd(point[i].origin, origin, point[i].origin); + + trap_R_AddRefEntityToScene (&point[i]); + } +#endif + */ +} + +// write in the axis and stuff +void G2_BoltToGhoul2Model(centity_t *cent, refEntity_t *ent) +{ + // extract the wraith ID from the bolt info + int modelNum = cent->boltInfo >> MODEL_SHIFT; + int boltNum = cent->boltInfo >> BOLT_SHIFT; + int entNum = cent->boltInfo >> ENTITY_SHIFT; + mdxaBone_t boltMatrix; + + modelNum &= MODEL_AND; + boltNum &= BOLT_AND; + entNum &= ENTITY_AND; + + + //NOTENOTE I put this here because the cgs.gamemodels array no longer gets initialized. + assert(0); + + + // go away and get me the bolt position for this frame please + trap_G2API_GetBoltMatrix(cent->ghoul2, modelNum, boltNum, &boltMatrix, cg_entities[entNum].currentState.angles, cg_entities[entNum].currentState.origin, cg->time, cgs.gameModels, cent->modelScale); + + // set up the axis and origin we need for the actual effect spawning + ent->origin[0] = boltMatrix.matrix[0][3]; + ent->origin[1] = boltMatrix.matrix[1][3]; + ent->origin[2] = boltMatrix.matrix[2][3]; + + ent->axis[0][0] = boltMatrix.matrix[0][0]; + ent->axis[0][1] = boltMatrix.matrix[1][0]; + ent->axis[0][2] = boltMatrix.matrix[2][0]; + + ent->axis[1][0] = boltMatrix.matrix[0][1]; + ent->axis[1][1] = boltMatrix.matrix[1][1]; + ent->axis[1][2] = boltMatrix.matrix[2][1]; + + ent->axis[2][0] = boltMatrix.matrix[0][2]; + ent->axis[2][1] = boltMatrix.matrix[1][2]; + ent->axis[2][2] = boltMatrix.matrix[2][2]; +} + +void ScaleModelAxis(refEntity_t *ent) + +{ // scale the model should we need to + if (ent->modelScale[0] && ent->modelScale[0] != 1.0f) + { + VectorScale( ent->axis[0], ent->modelScale[0] , ent->axis[0] ); + ent->nonNormalizedAxes = qtrue; + } + if (ent->modelScale[1] && ent->modelScale[1] != 1.0f) + { + VectorScale( ent->axis[1], ent->modelScale[1] , ent->axis[1] ); + ent->nonNormalizedAxes = qtrue; + } + if (ent->modelScale[2] && ent->modelScale[2] != 1.0f) + { + VectorScale( ent->axis[2], ent->modelScale[2] , ent->axis[2] ); + ent->nonNormalizedAxes = qtrue; + } +} +/* +Ghoul2 Insert End +*/ + +char *forceHolocronModels[] = { + "models/map_objects/mp/lt_heal.md3", //FP_HEAL, + "models/map_objects/mp/force_jump.md3", //FP_LEVITATION, + "models/map_objects/mp/force_speed.md3", //FP_SPEED, + "models/map_objects/mp/force_push.md3", //FP_PUSH, + "models/map_objects/mp/force_pull.md3", //FP_PULL, + "models/map_objects/mp/lt_telepathy.md3", //FP_TELEPATHY, + "models/map_objects/mp/dk_grip.md3", //FP_GRIP, + "models/map_objects/mp/dk_lightning.md3", //FP_LIGHTNING, + "models/map_objects/mp/dk_rage.md3", //FP_RAGE, + "models/map_objects/mp/lt_protect.md3", //FP_PROTECT, + "models/map_objects/mp/lt_absorb.md3", //FP_ABSORB, + "models/map_objects/mp/lt_healother.md3", //FP_TEAM_HEAL, + "models/map_objects/mp/dk_powerother.md3", //FP_TEAM_FORCE, + "models/map_objects/mp/dk_drain.md3", //FP_DRAIN, + "models/map_objects/mp/force_sight.md3", //FP_SEE, + "models/map_objects/mp/saber_attack.md3", //FP_SABER_OFFENSE, + "models/map_objects/mp/saber_defend.md3", //FP_SABER_DEFENSE, + "models/map_objects/mp/saber_throw.md3" //FP_SABERTHROW +}; + +void CG_Disintegration(centity_t *cent, refEntity_t *ent) +{ + vec3_t tempAng, hitLoc; + float tempLength; + + VectorCopy(cent->currentState.origin2, hitLoc); + + VectorSubtract( hitLoc, ent->origin, ent->oldorigin ); + + tempLength = VectorNormalize( ent->oldorigin ); + vectoangles( ent->oldorigin, tempAng ); + tempAng[YAW] -= cent->lerpAngles[YAW]; + AngleVectors( tempAng, ent->oldorigin, NULL, NULL ); + VectorScale( ent->oldorigin, tempLength, ent->oldorigin ); + + ent->endTime = cent->dustTrailTime; + + ent->renderfx |= RF_DISINTEGRATE2; + ent->customShader = cgs.media.disruptorShader; + trap_R_AddRefEntityToScene( ent ); + + ent->renderfx &= ~(RF_DISINTEGRATE2); + ent->renderfx |= (RF_DISINTEGRATE1); + ent->customShader = 0; + trap_R_AddRefEntityToScene( ent ); + + if ( cg->time - ent->endTime < 1000 && (cg_timescale.value * cg_timescale.value * random()) > 0.05f ) + { + vec3_t fxOrg, fxDir; + mdxaBone_t boltMatrix; + int torsoBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar"); + + VectorSet(fxDir, 0, 1, 0); + + trap_G2API_GetBoltMatrix( cent->ghoul2, 0, torsoBolt, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg->time, + cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, fxOrg ); + + VectorMA( fxOrg, -18, cg->refdef.viewaxis[0], fxOrg ); + fxOrg[2] += crandom() * 20; + trap_FX_PlayEffectID( cgs.effects.mDisruptorDeathSmoke, fxOrg, fxDir, -1, -1 ); + + if ( random() > 0.5f ) + { + trap_FX_PlayEffectID( cgs.effects.mDisruptorDeathSmoke, fxOrg, fxDir, -1, -1 ); + } + } +} + +extern int cgSiegeEntityRender; +static qboolean CG_RenderTimeEntBolt(centity_t *cent) +{ + int clientNum = cent->currentState.boltToPlayer-1; + centity_t *cl; + mdxaBone_t matrix; + vec3_t boltOrg, boltAng; + int getBolt = -1; + + if (clientNum >= MAX_CLIENTS || clientNum < 0) + { + assert(0); + return qfalse; + } + + cl = &cg_entities[clientNum]; + + if (!cl->ghoul2) + { + assert(0); + return qfalse; + } + + if (clientNum == cg->predictedPlayerState.clientNum && + !cg->renderingThirdPerson) + { //If in first person and you have it then render the thing spinning around on your hud. + cgSiegeEntityRender = cent->currentState.number; //set it to render at the end of the frame. + return qfalse; + } + + getBolt = trap_G2API_AddBolt(cl->ghoul2, 0, "lhand"); + + trap_G2API_GetBoltMatrix(cl->ghoul2, 0, getBolt, &matrix, cl->turAngles, cl->lerpOrigin, cg->time, cgs.gameModels, cl->modelScale); + + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Y, boltAng); + vectoangles(boltAng, boltAng); + boltAng[PITCH] = boltAng[ROLL] = 0; + + VectorCopy(boltOrg, cent->lerpOrigin); + VectorCopy(boltAng, cent->lerpAngles); + + return qtrue; +} + +/* +static void CG_SiegeEntRenderAboveHead(centity_t *cent) +{ + int clientNum = cent->currentState.boltToPlayer-1; + centity_t *cl; + refEntity_t ent; + vec3_t renderAngles; + + if (clientNum >= MAX_CLIENTS || clientNum < 0) + { + assert(0); + return; + } + + cl = &cg_entities[clientNum]; + + memset(&ent, 0, sizeof(ent)); + + //Set the angles to the global auto rotating ones, and the origin to slightly above the client + VectorCopy(cg->autoAngles, renderAngles); + AnglesToAxis( renderAngles, ent.axis ); + VectorCopy(cl->lerpOrigin, ent.origin); + ent.origin[2] += 50; + + //Set the model (ghoul2 or md3/other) + if (cent->ghoul2) + { + ent.ghoul2 = cent->ghoul2; + ent.hModel = 0; + } + else + { + ent.ghoul2 = NULL; + ent.hModel = cgs.gameModels[cent->currentState.modelindex]; + } + + //Scale it up + ent.modelScale[0] = 1.5f; + ent.modelScale[1] = 1.5f; + ent.modelScale[2] = 1.5f; + ScaleModelAxis(&ent); + + //Make it transparent + ent.renderfx = RF_FORCE_ENT_ALPHA; + ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 100; + + //And finally add it + trap_R_AddRefEntityToScene(&ent); +} +*/ + +void CG_AddRadarEnt(centity_t *cent) +{ + if (cg->radarEntityCount == sizeof(cg->radarEntities)/sizeof(cg->radarEntities[0])) + { +#ifdef _DEBUG + Com_Printf("^3Warning: CG_AddRadarEnt full. (%d max)\n", sizeof(cg->radarEntities)/sizeof(cg->radarEntities[0])); +#endif + return; + } + cg->radarEntities[cg->radarEntityCount++] = cent->currentState.number; +} + +void CG_AddBracketedEnt(centity_t *cent) +{ + if (cg->bracketedEntityCount == sizeof(cg->bracketedEntities)/sizeof(cg->bracketedEntities[0])) + { +#ifdef _DEBUG + Com_Printf("^3Warning: CG_AddBracketedEnt full. (%d max)\n", sizeof(cg->radarEntities)/sizeof(cg->bracketedEntities[0])); +#endif + return; + } + cg->bracketedEntities[cg->bracketedEntityCount++] = cent->currentState.number; +} +/* +================== +CG_General +================== +*/ +void CG_G2ServerBoneAngles(centity_t *cent); + +#include "../namespace_begin.h" +extern qboolean BG_GetRootSurfNameWithVariant( void *ghoul2, const char *rootSurfName, char *returnSurfName, int returnSize ); +#include "../namespace_end.h" + +static void CG_General( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + float val; + int beamID; + vec3_t beamOrg; + mdxaBone_t matrix; + qboolean doNotSetModel = qfalse; + + if (cent->currentState.modelGhoul2 == 127) + { //not ready to be drawn or initialized.. + return; + } + + //Special case for bodies. Because of the limited amount of memory + //for model slots, we can only render bodies if their model is also + //in use by an active client. Otherwise there is no guarentee that + //we have memory for it. + // + //WARNING: This assumes that cgs.clientinfo is up to date at this point. + if(cent->currentState.eType == ET_BODY && cent->ghoul2) { + if(!CG_ModelAllowed(cent->ghoul2)) { + return; + } + } + + if (cent->ghoul2 && !cent->currentState.modelGhoul2 && cent->currentState.eType != ET_BODY && + cent->currentState.number >= MAX_CLIENTS) + { //this is a bad thing + if (trap_G2_HaveWeGhoul2Models(cent->ghoul2)) + { + trap_G2API_CleanGhoul2Models(&(cent->ghoul2)); + } + } + + if (cent->currentState.eFlags & EF_RADAROBJECT) + { + CG_AddRadarEnt(cent); + } + if (cent->currentState.eFlags2 & EF2_BRACKET_ENTITY) + { + if ( CG_InFighter() ) + {//only bracken when in a fighter + CG_AddBracketedEnt(cent); + } + } + + if (cent->currentState.boltToPlayer) + { //Shove it into the player's left hand then. + centity_t *pl = &cg_entities[cent->currentState.boltToPlayer-1]; + if (CG_IsMindTricked(pl->currentState.trickedentindex, + pl->currentState.trickedentindex2, + pl->currentState.trickedentindex3, + pl->currentState.trickedentindex4, + cg->predictedPlayerState.clientNum)) + { //don't show if this guy is mindtricking + return; + } + if (!CG_RenderTimeEntBolt(cent)) + { //If this function returns qfalse we shouldn't render this ent at all. + if (cent->currentState.boltToPlayer > 0 && + cent->currentState.boltToPlayer <= MAX_CLIENTS) + { + VectorCopy(pl->lerpOrigin, cent->lerpOrigin); + + if (cent->currentState.eFlags & EF_CLIENTSMOOTH) + { //if it's set to smooth keep the smoothed lerp origin updated, as we don't want to smooth while bolted. + VectorCopy(cent->lerpOrigin, cent->turAngles); + } + } + return; + } + + if (cent->currentState.eFlags & EF_CLIENTSMOOTH) + { //if it's set to smooth keep the smoothed lerp origin updated, as we don't want to smooth while bolted. + VectorCopy(cent->lerpOrigin, cent->turAngles); + } + +/* disabled for now + if (pl->currentState.number != cg->predictedPlayerState.clientNum) + { //don't render thing above head to self + CG_SiegeEntRenderAboveHead(cent); + } +*/ + } + else if (cent->currentState.eFlags & EF_CLIENTSMOOTH) + { + if (cent->currentState.groundEntityNum >= ENTITYNUM_WORLD) + { + float smoothFactor = 0.5f*cg_timescale.value; + int k = 0; + vec3_t posDif; + + //Use origin smoothing since dismembered limbs use ExPhys + if (DistanceSquared(cent->turAngles,cent->lerpOrigin)>18000.0f) + { + VectorCopy(cent->lerpOrigin, cent->turAngles); + } + + VectorSubtract(cent->lerpOrigin, cent->turAngles, posDif); + + for (k=0;k<3;k++) + { + cent->turAngles[k]=(cent->turAngles[k]+posDif[k]*smoothFactor); + cent->lerpOrigin[k]=cent->turAngles[k]; + } + } + else + { //if we're sitting on an entity like a moving plat then we don't want to smooth either + VectorCopy(cent->lerpOrigin, cent->turAngles); + } + } + + //rww - now do ragdoll stuff + if (cent->ghoul2 && + (cent->currentState.eType == ET_BODY || (cent->currentState.eFlags & EF_RAG))) + { + if (!(cent->currentState.eFlags & EF_NODRAW) && + !(cent->currentState.eFlags & EF_DISINTEGRATION) && + cent->bodyFadeTime <= cg->time) + { + vec3_t forcedAngles; + + VectorClear(forcedAngles); + forcedAngles[YAW] = cent->lerpAngles[YAW]; + + CG_RagDoll(cent, forcedAngles); + } + } + else if (cent->isRagging) + { + cent->isRagging = qfalse; + + if (cent->ghoul2 && trap_G2_HaveWeGhoul2Models(cent->ghoul2)) + { //May not be valid, in the case of a ragged entity being removed and a non-g2 ent filling its slot. + trap_G2API_SetRagDoll(cent->ghoul2, NULL); //calling with null parms resets to no ragdoll. + } + } + + if (cent->currentState.boneOrient && cent->ghoul2) + { //server sent us some bone angles to use + CG_G2ServerBoneAngles(cent); + } + + if ((cent->currentState.eFlags & EF_G2ANIMATING) && cent->ghoul2) + { //mini-animation routine for general objects that want to play quick ghoul2 anims + //obviously lacks much of the functionality contained in player/npc animation. + //we actually use torsoAnim as the start frame and legsAnim as the end frame and + //always play the anim on the root bone. + if (cent->currentState.torsoAnim != cent->pe.torso.animationNumber || + cent->currentState.legsAnim != cent->pe.legs.animationNumber || + cent->currentState.torsoFlip != cent->pe.torso.lastFlip) + { + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", cent->currentState.torsoAnim, + cent->currentState.legsAnim, (BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND), 1.0f, cg->time, -1, 100); + + cent->pe.torso.animationNumber = cent->currentState.torsoAnim; + cent->pe.legs.animationNumber = cent->currentState.legsAnim; + cent->pe.torso.lastFlip = cent->currentState.torsoFlip; + } + } + + memset (&ent, 0, sizeof(ent)); + + ent.shaderRGBA[0] = cent->currentState.customRGBA[0]; + ent.shaderRGBA[1] = cent->currentState.customRGBA[1]; + ent.shaderRGBA[2] = cent->currentState.customRGBA[2]; + ent.shaderRGBA[3] = cent->currentState.customRGBA[3]; + + if (cent->currentState.modelGhoul2 >= G2_MODELPART_HEAD && + cent->currentState.modelGhoul2 <= G2_MODELPART_RLEG && + /*cent->currentState.modelindex < MAX_CLIENTS &&*/ + cent->currentState.weapon == G2_MODEL_PART) + { //special case for client limbs + centity_t *clEnt; + int dismember_settings = cg_dismember.integer; + float smoothFactor = 0.5f*cg_timescale.value; + int k = 0; + vec3_t posDif; + + doNotSetModel = qtrue; + + if (cent->currentState.modelindex >= 0) + { + clEnt = &cg_entities[cent->currentState.modelindex]; + } + else + { + clEnt = &cg_entities[cent->currentState.otherEntityNum2]; + } + + if (!dismember_settings) + { //This client does not wish to see dismemberment. + return; + } + + if (dismember_settings < 2 && (cent->currentState.modelGhoul2 == G2_MODELPART_HEAD || cent->currentState.modelGhoul2 == G2_MODELPART_WAIST)) + { //dismember settings are not high enough to display decaps and torso slashes + return; + } + + if (!cent->ghoul2) + { + const char *limbBone; + const char *rotateBone; + char limbName[MAX_QPATH]; + char stubName[MAX_QPATH]; + char limbCapName[MAX_QPATH]; + char stubCapName[MAX_QPATH]; + char *limbTagName; + char *stubTagName; + int limb_anim; + int newBolt; + int limbBit = (1 << (cent->currentState.modelGhoul2-10)); + + if (clEnt && (clEnt->torsoBolt & limbBit)) + { //already have this limb missing! + return; + } + + + if (clEnt && !(clEnt->currentState.eFlags & EF_DEAD)) + { //death flag hasn't made it through yet for the limb owner, we cannot create the limb until he's flagged as dead + return; + } + + if (clEnt && (!BG_InDeathAnim(clEnt->currentState.torsoAnim) || !BG_InDeathAnim(clEnt->pe.torso.animationNumber))) + { //don't make it unless we're in an actual death anim already + if (clEnt->currentState.torsoAnim != BOTH_RIGHTHANDCHOPPEDOFF) + { //exception + return; + } + } + + cent->bolt4 = -1; + cent->trailTime = 0; + + if (cent->currentState.modelGhoul2 == G2_MODELPART_HEAD) + { + limbBone = "cervical"; + rotateBone = "cranium"; + Q_strncpyz( limbName , "head", sizeof( limbName ) ); + Q_strncpyz( limbCapName, "head_cap_torso", sizeof( limbCapName ) ); + Q_strncpyz( stubCapName, "torso_cap_head", sizeof( stubCapName ) ); + limbTagName = "*head_cap_torso"; + stubTagName = "*torso_cap_head"; + limb_anim = BOTH_DISMEMBER_HEAD1; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_WAIST) + { + limbBone = "pelvis"; + + if (clEnt->localAnimIndex <= 1) + { //humanoid/rtrooper + rotateBone = "thoracic"; + } + else + { + rotateBone = "pelvis"; + } + Q_strncpyz( limbName, "torso", sizeof( limbName ) ); + Q_strncpyz( limbCapName, "torso_cap_hips", sizeof( limbCapName ) ); + Q_strncpyz( stubCapName, "hips_cap_torso", sizeof( stubCapName ) ); + limbTagName = "*torso_cap_hips"; + stubTagName = "*hips_cap_torso"; + limb_anim = BOTH_DISMEMBER_TORSO1; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_LARM) + { + limbBone = "lhumerus"; + rotateBone = "lradius"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "l_arm", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "torso", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_torso", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_arm", stubName ); + limbTagName = "*l_arm_cap_torso"; + stubTagName = "*torso_cap_l_arm"; + limb_anim = BOTH_DISMEMBER_LARM; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_RARM) + { + limbBone = "rhumerus"; + rotateBone = "rradius"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "r_arm", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "torso", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_torso", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_arm", stubName ); + limbTagName = "*r_arm_cap_torso"; + stubTagName = "*torso_cap_r_arm"; + limb_anim = BOTH_DISMEMBER_RARM; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_RHAND) + { + limbBone = "rradiusX"; + rotateBone = "rhand"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "r_hand", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "r_arm", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_r_arm", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_hand", stubName ); + limbTagName = "*r_hand_cap_r_arm"; + stubTagName = "*r_arm_cap_r_hand"; + limb_anim = BOTH_DISMEMBER_RARM; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_LLEG) + { + limbBone = "lfemurYZ"; + rotateBone = "ltibia"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "l_leg", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "hips", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_hips", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_leg", stubName ); + limbTagName = "*l_leg_cap_hips"; + stubTagName = "*hips_cap_l_leg"; + limb_anim = BOTH_DISMEMBER_LLEG; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_RLEG) + { + limbBone = "rfemurYZ"; + rotateBone = "rtibia"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "r_leg", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "hips", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_hips", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_leg", stubName ); + limbTagName = "*r_leg_cap_hips"; + stubTagName = "*hips_cap_r_leg"; + limb_anim = BOTH_DISMEMBER_RLEG; + } + else + {//umm... just default to the right leg, I guess (same as on server) + limbBone = "rfemurYZ"; + rotateBone = "rtibia"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "r_leg", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "hips", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_hips", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_leg", stubName ); + limbTagName = "*r_leg_cap_hips"; + stubTagName = "*hips_cap_r_leg"; + limb_anim = BOTH_DISMEMBER_RLEG; + } + + if (clEnt && clEnt->ghoul2) + { + if (trap_G2API_HasGhoul2ModelOnIndex(&(clEnt->ghoul2), 2)) + { //don't want to bother dealing with a second saber on limbs and stuff, just remove the thing + trap_G2API_RemoveGhoul2Model(&(clEnt->ghoul2), 2); + } + + if (trap_G2API_HasGhoul2ModelOnIndex(&(clEnt->ghoul2), 3)) + { //turn off jetpack also I suppose + trap_G2API_RemoveGhoul2Model(&(clEnt->ghoul2), 3); + } + + if (clEnt->localAnimIndex <= 0) + { //humanoid + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "model_root", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg->time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "pelvis", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg->time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg->time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg->time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg->time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, cgs.gameModels, 100, cg->time); + } + else + { + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "model_root", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg->time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "pelvis", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg->time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg->time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg->time); + } + + trap_G2API_DuplicateGhoul2Instance(clEnt->ghoul2, ¢->ghoul2); + } + + if (!cent->ghoul2) + { + return; + } + + newBolt = trap_G2API_AddBolt( cent->ghoul2, 0, limbTagName ); + if ( newBolt != -1 ) + { + vec3_t boltOrg, boltAng; + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, newBolt, &matrix, cent->lerpAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Y, boltAng); + + trap_FX_PlayEffectID(cgs.effects.mBlasterSmoke, boltOrg, boltAng, -1, -1); + } + + cent->bolt4 = newBolt; + + trap_G2API_SetRootSurface(cent->ghoul2, 0, limbName); + + trap_G2API_SetNewOrigin(cent->ghoul2, trap_G2API_AddBolt(cent->ghoul2, 0, rotateBone)); + + trap_G2API_SetSurfaceOnOff(cent->ghoul2, limbCapName, 0); + + trap_G2API_SetSurfaceOnOff(clEnt->ghoul2, limbName, 0x00000100); + trap_G2API_SetSurfaceOnOff(clEnt->ghoul2, stubCapName, 0); + + newBolt = trap_G2API_AddBolt( clEnt->ghoul2, 0, stubTagName ); + if ( newBolt != -1 ) + { + vec3_t boltOrg, boltAng; + + trap_G2API_GetBoltMatrix(clEnt->ghoul2, 0, newBolt, &matrix, clEnt->lerpAngles, clEnt->lerpOrigin, cg->time, cgs.gameModels, clEnt->modelScale); + + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Y, boltAng); + + trap_FX_PlayEffectID(cgs.effects.mBlasterSmoke, boltOrg, boltAng, -1, -1); + } + + if (cent->currentState.modelGhoul2 == G2_MODELPART_RARM || cent->currentState.modelGhoul2 == G2_MODELPART_RHAND || cent->currentState.modelGhoul2 == G2_MODELPART_WAIST) + { //Cut his weapon holding arm off, so remove the weapon + if (trap_G2API_HasGhoul2ModelOnIndex(&(clEnt->ghoul2), 1)) + { + trap_G2API_RemoveGhoul2Model(&(clEnt->ghoul2), 1); + } + } + + clEnt->torsoBolt |= limbBit; //reinit model after copying limbless one to queue + //This causes issues after respawning.. just keep track of limbs cut/made on server or something. + /* + if (cent->currentState.modelGhoul2 == G2_MODELPART_WAIST) + { + clEnt->torsoBolt |= (1 << (G2_MODELPART_HEAD-10)); + clEnt->torsoBolt |= (1 << (G2_MODELPART_RARM-10)); + clEnt->torsoBolt |= (1 << (G2_MODELPART_LARM-10)); + clEnt->torsoBolt |= (1 << (G2_MODELPART_RHAND-10)); + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_RARM) + { + clEnt->torsoBolt |= (1 << (G2_MODELPART_RHAND-10)); + } + */ + + VectorCopy(cent->lerpOrigin, cent->turAngles); + // return; + } + + //Use origin smoothing since dismembered limbs use ExPhys + if (DistanceSquared(cent->turAngles,cent->lerpOrigin)>18000.0f) + { + VectorCopy(cent->lerpOrigin, cent->turAngles); + } + + VectorSubtract(cent->lerpOrigin, cent->turAngles, posDif); + + for (k=0;k<3;k++) + { + cent->turAngles[k]=(cent->turAngles[k]+posDif[k]*smoothFactor); + cent->lerpOrigin[k]=cent->turAngles[k]; + } + + if (cent->ghoul2 && cent->bolt4 != -1 && cent->trailTime < cg->time) + { + if ( cent->bolt4 != -1 && + (cent->currentState.pos.trDelta[0] || cent->currentState.pos.trDelta[1] || cent->currentState.pos.trDelta[2]) ) + { + vec3_t boltOrg, boltAng; + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cent->bolt4, &matrix, cent->lerpAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Y, boltAng); + + if (!boltAng[0] && !boltAng[1] && !boltAng[2]) + { + boltAng[1] = 1; + } + trap_FX_PlayEffectID(cgs.effects.mBlasterSmoke, boltOrg, boltAng, -1, -1); + + cent->trailTime = cg->time + 400; + } + } + + ent.radius = cent->currentState.g2radius; + ent.hModel = 0; + } + + if (cent->currentState.number >= MAX_CLIENTS && + cent->currentState.activeForcePass == NUM_FORCE_POWERS+1&& + cent->currentState.NPC_class != CLASS_VEHICLE ) + { + vec3_t empAngles; + centity_t *empOwn; + + empOwn = &cg_entities[cent->currentState.emplacedOwner]; + + if (cg->snap->ps.clientNum == empOwn->currentState.number && + !cg->renderingThirdPerson) + { + VectorCopy(cg->refdef.viewangles, empAngles); + } + else + { + VectorCopy(empOwn->lerpAngles, empAngles); + } + + if (empAngles[PITCH] > 40) + { + empAngles[PITCH] = 40; + } + empAngles[YAW] -= cent->currentState.angles[YAW]; + + trap_G2API_SetBoneAngles( cent->ghoul2, 0, "Bone02", empAngles, BONE_ANGLES_REPLACE, NEGATIVE_Y, NEGATIVE_X, POSITIVE_Z, NULL, 0, cg->time); + } + + s1 = ¢->currentState; + + // if set to invisible, skip + if ((!s1->modelindex) && !(trap_G2_HaveWeGhoul2Models(cent->ghoul2))) + { + return; + } + + if ( ( s1->eFlags & EF_NODRAW ) ) + { + return; + } + + // set frame + if ( s1->eFlags & EF_SHADER_ANIM ) + { + // Deliberately setting it up so that shader anim will completely override any kind of model animation frame setting. + ent.renderfx|=RF_SETANIMINDEX; + ent.skinNum = s1->frame; + } + else + { + ent.frame = s1->frame; + } + ent.oldframe = ent.frame; + ent.backlerp = 0; + +/* +Ghoul2 Insert Start +*/ + CG_SetGhoul2Info(&ent, cent); + +/* +Ghoul2 Insert End +*/ + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + if (cent->currentState.modelGhoul2) + { //If the game says this guy uses a ghoul2 model and the g2 instance handle is null, then initialize it + if (!cent->ghoul2 && !cent->currentState.bolt1) + { + char skinName[MAX_QPATH]; + const char *modelName = CG_ConfigString( CS_MODELS+cent->currentState.modelindex ); + int l; + int skin = 0; + + trap_G2API_InitGhoul2Model(¢->ghoul2, modelName, 0, 0, 0, 0, 0); + if (cent->ghoul2 && trap_G2API_SkinlessModel(cent->ghoul2, 0)) + { //well, you'd never want a skinless model, so try to get his skin... + Q_strncpyz(skinName, modelName, MAX_QPATH); + l = strlen(skinName); + while (l > 0 && skinName[l] != '/') + { //parse back to first / + l--; + } + if (skinName[l] == '/') + { //got it + l++; + skinName[l] = 0; + Q_strcat(skinName, MAX_QPATH, "model_default.skin"); + + skin = trap_R_RegisterSkin(skinName); + } + trap_G2API_SetSkin(cent->ghoul2, 0, skin, skin); + } + } + else if (cent->currentState.bolt1) + { + TurretClientRun(cent); + } + + if (cent->ghoul2) + { //give us a proper radius + ent.radius = cent->currentState.g2radius; + } + } + + if (s1->eType == ET_BODY) + { //bodies should have a radius as well + ent.radius = cent->currentState.g2radius; + + if (cent->ghoul2) + { //all bodies should already have a ghoul2 instance. Use it to set the torso/head angles to 0. + cent->lerpAngles[PITCH] = 0; + cent->lerpAngles[ROLL] = 0; + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "pelvis", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg->time); + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg->time); + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg->time); + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg->time); + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, cgs.gameModels, 100, cg->time); + } + } + + if (s1->eType == ET_HOLOCRON && s1->modelindex < -100) + { //special render, it's a holocron + //Using actual models now: + ent.hModel = trap_R_RegisterModel(forceHolocronModels[s1->modelindex+128]); + + //Rotate them + VectorCopy( cg->autoAngles, cent->lerpAngles ); + AxisCopy( cg->autoAxis, ent.axis ); + } + else if (!doNotSetModel) + { + ent.hModel = cgs.gameModels[s1->modelindex]; + } + + // player model + if (s1->number == cg->snap->ps.clientNum) { + ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors + } + + // convert angles to axis + AnglesToAxis( cent->lerpAngles, ent.axis ); + + if (cent->currentState.iModelScale) + { //if the server says we have a custom scale then set it now. + cent->modelScale[0] = cent->modelScale[1] = cent->modelScale[2] = cent->currentState.iModelScale/100.0f; + VectorCopy(cent->modelScale, ent.modelScale); + ScaleModelAxis(&ent); + } + else + { + VectorClear(cent->modelScale); + } + + if ( cent->currentState.time > cg->time && cent->currentState.weapon == WP_EMPLACED_GUN ) + { + // make the gun pulse red to warn about it exploding + val = (1.0f - (float)(cent->currentState.time - cg->time) / 3200.0f ) * 0.3f; + + ent.customShader = trap_R_RegisterShader( "gfx/effects/turretflashdie" ); + ent.shaderRGBA[0] = (sin( cg->time * 0.04f ) * val * 0.4f + val) * 255; + ent.shaderRGBA[1] = ent.shaderRGBA[2] = 0; + + ent.shaderRGBA[3] = 100; + trap_R_AddRefEntityToScene( &ent ); + ent.customShader = 0; + } + else if ( cent->currentState.time == -1 && cent->currentState.weapon == WP_EMPLACED_GUN) + { + ent.customShader = trap_R_RegisterShader( "models/map_objects/imp_mine/turret_chair_dmg.tga" ); + //trap_R_AddRefEntityToScene( &ent ); + } + + if ((cent->currentState.eFlags & EF_DISINTEGRATION) && cent->currentState.eType == ET_BODY) + { + if (!cent->dustTrailTime) + { + cent->dustTrailTime = cg->time; + } + + CG_Disintegration(cent, &ent); + return; + } + else if (cent->currentState.eType == ET_BODY) + { + if (cent->bodyFadeTime > cg->time) + { + qboolean lightSide = cent->teamPowerType; + vec3_t hitLoc, tempAng; + float tempLength; + int curTimeDif = ((cg->time + 60000) - cent->bodyFadeTime); + int tMult = curTimeDif*0.08; + + ent.renderfx |= RF_FORCE_ENT_ALPHA; + + /* + if (!cent->bodyHeight) + { + cent->bodyHeight = ent.origin[2]; + } + */ + + if (curTimeDif*0.1 > 254) + { + ent.shaderRGBA[3] = 0; + } + else + { + ent.shaderRGBA[3] = (254 - tMult); + } + + if (ent.shaderRGBA[3] >= 1) + { //add the transparent body section + trap_R_AddRefEntityToScene (&ent); + } + + ent.renderfx &= ~RF_FORCE_ENT_ALPHA; + ent.renderfx |= RF_RGB_TINT; + + if (tMult > 200) + { //begin the disintegration effect + ent.shaderRGBA[3] = 200; + if (!cent->dustTrailTime) + { + cent->dustTrailTime = cg->time; + if (lightSide) + { + trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/see.wav") ); + } + else + { + trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/lightning") ); + } + } + ent.endTime = cent->dustTrailTime; + ent.renderfx |= RF_DISINTEGRATE2; + } + else + { //set the alpha on the to-be-disintegrated layer + ent.shaderRGBA[3] = tMult; + if (ent.shaderRGBA[3] < 1) + { + ent.shaderRGBA[3] = 1; + } + } + //Set everything up on the disint ref + ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = ent.shaderRGBA[3]; + VectorCopy(cent->lerpOrigin, hitLoc); + + VectorSubtract( hitLoc, ent.origin, ent.oldorigin ); + + tempLength = VectorNormalize( ent.oldorigin ); + vectoangles( ent.oldorigin, tempAng ); + tempAng[YAW] -= cent->lerpAngles[YAW]; + AngleVectors( tempAng, ent.oldorigin, NULL, NULL ); + VectorScale( ent.oldorigin, tempLength, ent.oldorigin ); + + if (lightSide) + { //might be temporary, dunno. + ent.customShader = cgs.media.playerShieldDamage; + } + else + { + ent.customShader = cgs.media.redSaberGlowShader; + } + + //slowly move the glowing part upward, out of the fading body + /* + cent->bodyHeight += 0.4f; + ent.origin[2] = cent->bodyHeight; + */ + + trap_R_AddRefEntityToScene( &ent ); + ent.renderfx &= ~RF_DISINTEGRATE2; + ent.customShader = 0; + + if (curTimeDif < 3400) + { + if (lightSide) + { + if (curTimeDif < 2200) + { //probably temporary + trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ) ); + } + } + else + { //probably temporary as well + ent.renderfx |= RF_RGB_TINT; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = ent.shaderRGBA[2] = 0; + ent.shaderRGBA[3] = 255; + if ( rand() & 1 ) + { + ent.customShader = cgs.media.electricBodyShader; + } + else + { + ent.customShader = cgs.media.electricBody2Shader; + } + if ( random() > 0.9f ) + { + trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, cgs.media.crackleSound ); + } + trap_R_AddRefEntityToScene( &ent ); + } + } + + return; + } + else + { + cent->dustTrailTime = 0; + } + } + + if (cent->currentState.modelGhoul2 && + !ent.ghoul2 && + !ent.hModel) + { + return; + } + + // add to refresh list + trap_R_AddRefEntityToScene (&ent); + + if (cent->bolt3 == 999) + { //this is an in-flight saber being rendered manually + vec3_t org; + float wv; + int i; + addspriteArgStruct_t fxSArgs; + //refEntity_t sRef; + //memcpy( &sRef, &ent, sizeof( sRef ) ); + + ent.customShader = cgs.media.solidWhite; + ent.renderfx = RF_RGB_TINT; + wv = sin( cg->time * 0.003f ) * 0.08f + 0.1f; + ent.shaderRGBA[0] = wv * 255; + ent.shaderRGBA[1] = wv * 255; + ent.shaderRGBA[2] = wv * 0; + trap_R_AddRefEntityToScene (&ent); + + for ( i = -4; i < 10; i += 1 ) + { + VectorMA( ent.origin, -i, ent.axis[2], org ); + + VectorCopy(org, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = 5.5f; + fxSArgs.dscale = 5.5f; + fxSArgs.sAlpha = wv; + fxSArgs.eAlpha = wv; + fxSArgs.rotation = 0.0f; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + fxSArgs.shader = cgs.media.yellowDroppedSaberShader; + fxSArgs.flags = 0x08000000; + + //trap_FX_AddSprite( org, NULL, NULL, 5.5f, 5.5f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowSaberGlowShader, 0x08000000 ); + trap_FX_AddSprite(&fxSArgs); + } + } + else if (cent->currentState.trickedentindex3) + { //holocron special effects + vec3_t org; + float wv; + addspriteArgStruct_t fxSArgs; + //refEntity_t sRef; + //memcpy( &sRef, &ent, sizeof( sRef ) ); + + ent.customShader = cgs.media.solidWhite; + ent.renderfx = RF_RGB_TINT; + wv = sin( cg->time * 0.005f ) * 0.08f + 0.1f; //* 0.08f + 0.1f; + + if (cent->currentState.trickedentindex3 == 1) + { //dark + ent.shaderRGBA[0] = wv*255; + ent.shaderRGBA[1] = 0; + ent.shaderRGBA[2] = 0; + } + else if (cent->currentState.trickedentindex3 == 2) + { //light + ent.shaderRGBA[0] = wv*255; + ent.shaderRGBA[1] = wv*255; + ent.shaderRGBA[2] = wv*255; + } + else + { //neutral + if ((s1->modelindex+128) == FP_SABER_OFFENSE || + (s1->modelindex+128) == FP_SABER_DEFENSE || + (s1->modelindex+128) == FP_SABERTHROW) + { //saber power + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = wv*255; + ent.shaderRGBA[2] = 0; + } + else + { + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = wv*255; + ent.shaderRGBA[2] = wv*255; + } + } + + ent.modelScale[0] = 1.1; + ent.modelScale[1] = 1.1; + ent.modelScale[2] = 1.1; + + ent.origin[2] -= 2; + ScaleModelAxis(&ent); + + trap_R_AddRefEntityToScene (&ent); + + VectorMA( ent.origin, 1, ent.axis[2], org ); + + org[2] += 18; + + wv = sin( cg->time * 0.002f ) * 0.08f + 0.1f; //* 0.08f + 0.1f; + + VectorCopy(org, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = wv*120;//16.0f; + fxSArgs.dscale = wv*120;//16.0f; + fxSArgs.sAlpha = wv*12; + fxSArgs.eAlpha = wv*12; + fxSArgs.rotation = 0.0f; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + + fxSArgs.flags = 0x08000000|0x00000001; + + if (cent->currentState.trickedentindex3 == 1) + { //dark + fxSArgs.sAlpha *= 3; + fxSArgs.eAlpha *= 3; + fxSArgs.shader = cgs.media.redSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else if (cent->currentState.trickedentindex3 == 2) + { //light + fxSArgs.sAlpha *= 1.5; + fxSArgs.eAlpha *= 1.5; + fxSArgs.shader = cgs.media.redSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.blueSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else + { //neutral + if ((s1->modelindex+128) == FP_SABER_OFFENSE || + (s1->modelindex+128) == FP_SABER_DEFENSE || + (s1->modelindex+128) == FP_SABERTHROW) + { //saber power + fxSArgs.sAlpha *= 1.5; + fxSArgs.eAlpha *= 1.5; + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else + { + fxSArgs.sAlpha *= 0.5; + fxSArgs.eAlpha *= 0.5; + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.blueSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + } + } + + if ( cent->currentState.time == -1 && cent->currentState.weapon == WP_TRIP_MINE && (cent->currentState.eFlags & EF_FIRING) ) + { //if force sight is active, render the laser multiple times up to the force sight level to increase visibility + if (cent->currentState.bolt2 == 1) + { + VectorMA( ent.origin, 6.6f, ent.axis[0], beamOrg );// forward + beamID = cgs.effects.tripmineGlowFX; + trap_FX_PlayEffectID( beamID, beamOrg, cent->currentState.pos.trDelta, -1, -1 ); + } + else + { + int i = 0; + + VectorMA( ent.origin, 6.6f, ent.axis[0], beamOrg );// forward + beamID = cgs.effects.tripmineLaserFX; + + if (cg->snap->ps.fd.forcePowersActive & (1 << FP_SEE)) + { + i = cg->snap->ps.fd.forcePowerLevel[FP_SEE]; + + while (i > 0) + { + trap_FX_PlayEffectID( beamID, beamOrg, cent->currentState.pos.trDelta, -1, -1 ); + trap_FX_PlayEffectID( beamID, beamOrg, cent->currentState.pos.trDelta, -1, -1 ); + i--; + } + } + + trap_FX_PlayEffectID( beamID, beamOrg, cent->currentState.pos.trDelta, -1, -1 ); + } + } +/* +Ghoul2 Insert Start +*/ + + if (cg_debugBB.integer) + { + CG_CreateBBRefEnts(s1, cent->lerpOrigin); + } +/* +Ghoul2 Insert End +*/ +} + +/* +================== +CG_Speaker + +Speaker entities can automatically play sounds +================== +*/ +static void CG_Speaker( centity_t *cent ) { + if (cent->currentState.trickedentindex) + { + CG_S_StopLoopingSound(cent->currentState.number, -1); + } + + if ( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum... + return; // not auto triggering + } + + if ( cg->time < cent->miscTime ) { + return; + } + + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); + + // ent->s.frame = ent->wait * 10; + // ent->s.clientNum = ent->random * 10; + cent->miscTime = cg->time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); +} + +qboolean CG_GreyItem(int type, int tag, int plSide) +{ + if (type == IT_POWERUP && + (tag == PW_FORCE_ENLIGHTENED_LIGHT || tag == PW_FORCE_ENLIGHTENED_DARK)) + { + if (plSide == FORCE_LIGHTSIDE) + { + if (tag == PW_FORCE_ENLIGHTENED_DARK) + { + return qtrue; + } + } + else if (plSide == FORCE_DARKSIDE) + { + if (tag == PW_FORCE_ENLIGHTENED_LIGHT) + { + return qtrue; + } + } + } + + return qfalse; +} + +/* +================== +CG_Item +================== +*/ +static void CG_Item( centity_t *cent ) { + refEntity_t ent; + entityState_t *es; + gitem_t *item; + int msec; + float scale; + weaponInfo_t *wi; + + es = ¢->currentState; + if ( es->modelindex >= bg_numItems ) { + CG_Error( "Bad item index %i on entity", es->modelindex ); + } + +/* +Ghoul2 Insert Start +*/ + + if ((es->eFlags & EF_NODRAW) && (es->eFlags & EF_ITEMPLACEHOLDER)) + { + es->eFlags &= ~EF_NODRAW; + } + + if ( !es->modelindex ) + { + return; + } + + item = &bg_itemlist[ es->modelindex ]; + + if ((item->giType == IT_WEAPON || item->giType == IT_POWERUP) && + !(cent->currentState.eFlags & EF_DROPPEDWEAPON) && + !cg_simpleItems.integer) + { + vec3_t uNorm; + qboolean doGrey; + + VectorClear(uNorm); + + uNorm[2] = 1; + + memset( &ent, 0, sizeof( ent ) ); + + ent.customShader = 0; + VectorCopy(cent->lerpOrigin, ent.origin); + VectorCopy( cent->currentState.angles, cent->lerpAngles ); + AnglesToAxis(cent->lerpAngles, ent.axis); + ent.hModel = cgs.media.itemHoloModel; + + doGrey = CG_GreyItem(item->giType, item->giTag, cg->snap->ps.fd.forceSide); + + if (doGrey) + { + ent.renderfx |= RF_RGB_TINT; + + ent.shaderRGBA[0] = 150; + ent.shaderRGBA[1] = 150; + ent.shaderRGBA[2] = 150; + } + + trap_R_AddRefEntityToScene(&ent); + + if (!doGrey) + { + trap_FX_PlayEffectID(cgs.effects.itemCone, ent.origin, uNorm, -1, -1); + } + } + + // if set to invisible, skip + if ( ( es->eFlags & EF_NODRAW ) ) + { + return; + } +/* +Ghoul2 Insert End +*/ + + if ( cg_simpleItems.integer && item->giType != IT_TEAM ) { + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.radius = 14; + ent.customShader = cg_items[es->modelindex].icon; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + + ent.origin[2] += 16; + + if (item->giType != IT_POWERUP || item->giTag != PW_FORCE_BOON) + { + ent.renderfx |= RF_FORCE_ENT_ALPHA; + } + + if ( es->eFlags & EF_ITEMPLACEHOLDER ) + { + if (item->giType == IT_POWERUP && item->giTag == PW_FORCE_BOON) + { + return; + } + ent.shaderRGBA[0] = 200; + ent.shaderRGBA[1] = 200; + ent.shaderRGBA[2] = 200; + ent.shaderRGBA[3] = 150 + sin(cg->time*0.01)*30; + } + else + { + ent.shaderRGBA[3] = 255; + } + + if (CG_GreyItem(item->giType, item->giTag, cg->snap->ps.fd.forceSide)) + { + ent.shaderRGBA[0] = 100; + ent.shaderRGBA[1] = 100; + ent.shaderRGBA[2] = 100; + + ent.shaderRGBA[3] = 200; + + if (item->giTag == PW_FORCE_ENLIGHTENED_LIGHT) + { + ent.customShader = trap_R_RegisterShader("gfx/misc/mp_light_enlight_disable"); + } + else + { + ent.customShader = trap_R_RegisterShader("gfx/misc/mp_dark_enlight_disable"); + } + } + trap_R_AddRefEntityToScene(&ent); + return; + } + + if ((item->giType == IT_WEAPON || item->giType == IT_POWERUP) && + !(cent->currentState.eFlags & EF_DROPPEDWEAPON)) + { + cent->lerpOrigin[2] += 16; + } + + if ((!(cent->currentState.eFlags & EF_DROPPEDWEAPON) || item->giType == IT_POWERUP) && + (item->giType == IT_WEAPON || item->giType == IT_POWERUP)) + { + // items bob up and down continuously + scale = 0.005 + cent->currentState.number * 0.00001; + cent->lerpOrigin[2] += 4 + cos( ( cg->time + 1000 ) * scale ) * 4; + } + else + { + if (item->giType == IT_HOLDABLE) + { + if (item->giTag == HI_SEEKER) + { + cent->lerpOrigin[2] += 5; + } + if (item->giTag == HI_SHIELD) + { + cent->lerpOrigin[2] += 2; + } + if (item->giTag == HI_BINOCULARS) + { + cent->lerpOrigin[2] += 2; + } + } + if (item->giType == IT_HEALTH) + { + cent->lerpOrigin[2] += 2; + } + if (item->giType == IT_ARMOR) + { + if (item->quantity == 100) + { + cent->lerpOrigin[2] += 7; + } + } + } + + memset (&ent, 0, sizeof(ent)); + + if ( (!(cent->currentState.eFlags & EF_DROPPEDWEAPON) || item->giType == IT_POWERUP) && + (item->giType == IT_WEAPON || item->giType == IT_POWERUP) ) + { //only weapons and powerups rotate now + // autorotate at one of two speeds + VectorCopy( cg->autoAngles, cent->lerpAngles ); + AxisCopy( cg->autoAxis, ent.axis ); + } + else + { + VectorCopy( cent->currentState.angles, cent->lerpAngles ); + AnglesToAxis(cent->lerpAngles, ent.axis); + } + + wi = NULL; + // the weapons have their origin where they attatch to player + // models, so we need to offset them or they will rotate + // eccentricly + if (!(cent->currentState.eFlags & EF_DROPPEDWEAPON)) + { + if ( item->giType == IT_WEAPON ) { + wi = &cg_weapons[item->giTag]; + cent->lerpOrigin[0] -= + wi->weaponMidpoint[0] * ent.axis[0][0] + + wi->weaponMidpoint[1] * ent.axis[1][0] + + wi->weaponMidpoint[2] * ent.axis[2][0]; + cent->lerpOrigin[1] -= + wi->weaponMidpoint[0] * ent.axis[0][1] + + wi->weaponMidpoint[1] * ent.axis[1][1] + + wi->weaponMidpoint[2] * ent.axis[2][1]; + cent->lerpOrigin[2] -= + wi->weaponMidpoint[0] * ent.axis[0][2] + + wi->weaponMidpoint[1] * ent.axis[1][2] + + wi->weaponMidpoint[2] * ent.axis[2][2]; + + cent->lerpOrigin[2] += 8; // an extra height boost + } + } + else + { + wi = &cg_weapons[item->giTag]; + + switch(item->giTag) + { + case WP_BLASTER: + cent->lerpOrigin[2] -= 12; + break; + case WP_DISRUPTOR: + cent->lerpOrigin[2] -= 13; + break; + case WP_BOWCASTER: + cent->lerpOrigin[2] -= 16; + break; + case WP_REPEATER: + cent->lerpOrigin[2] -= 12; + break; + case WP_DEMP2: + cent->lerpOrigin[2] -= 10; + break; + case WP_FLECHETTE: + cent->lerpOrigin[2] -= 6; + break; + case WP_ROCKET_LAUNCHER: + cent->lerpOrigin[2] -= 11; + break; + case WP_THERMAL: + cent->lerpOrigin[2] -= 12; + break; + case WP_TRIP_MINE: + cent->lerpOrigin[2] -= 16; + break; + case WP_DET_PACK: + cent->lerpOrigin[2] -= 16; + break; + default: + cent->lerpOrigin[2] -= 8; + break; + } + } + + ent.hModel = cg_items[es->modelindex].models[0]; +/* +Ghoul2 Insert Start +*/ + ent.ghoul2 = cg_items[es->modelindex].g2Models[0]; + ent.radius = cg_items[es->modelindex].radius[0]; + VectorCopy (cent->lerpAngles, ent.angles); +/* +Ghoul2 Insert End +*/ + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.nonNormalizedAxes = qfalse; + + // if just respawned, slowly scale up + + msec = cg->time - cent->miscTime; + + if (CG_GreyItem(item->giType, item->giTag, cg->snap->ps.fd.forceSide)) + { + ent.renderfx |= RF_RGB_TINT; + + ent.shaderRGBA[0] = 150; + ent.shaderRGBA[1] = 150; + ent.shaderRGBA[2] = 150; + + ent.renderfx |= RF_FORCE_ENT_ALPHA; + + ent.shaderRGBA[3] = 200; + + if (item->giTag == PW_FORCE_ENLIGHTENED_LIGHT) + { + ent.customShader = trap_R_RegisterShader("gfx/misc/mp_light_enlight_disable"); + } + else + { + ent.customShader = trap_R_RegisterShader("gfx/misc/mp_dark_enlight_disable"); + } + + trap_R_AddRefEntityToScene( &ent ); + return; + } + + if ( es->eFlags & EF_ITEMPLACEHOLDER ) // item has been picked up + { + if ( es->eFlags & EF_DEAD ) // if item had been droped, don't show at all + return; + + ent.renderfx |= RF_RGB_TINT; + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = 200; + ent.shaderRGBA[2] = 85; + ent.customShader = cgs.media.itemRespawningPlaceholder; + } + + // increase the size of the weapons when they are presented as items + if ( item->giType == IT_WEAPON ) { + VectorScale( ent.axis[0], 1.5, ent.axis[0] ); + VectorScale( ent.axis[1], 1.5, ent.axis[1] ); + VectorScale( ent.axis[2], 1.5, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + //trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); + } + + if (!(cent->currentState.eFlags & EF_DROPPEDWEAPON) && + (item->giType == IT_WEAPON || item->giType == IT_POWERUP)) + { + ent.renderfx |= RF_MINLIGHT; + } + + if (item->giType != IT_TEAM && msec >= 0 && msec < ITEM_SCALEUP_TIME && !(es->eFlags & EF_ITEMPLACEHOLDER) && !(es->eFlags & EF_DROPPEDWEAPON)) + { // if just respawned, fade in, but don't do this for flags. + float alpha; + int a; + + alpha = (float)msec / ITEM_SCALEUP_TIME; + a = alpha * 255.0; + if (a <= 0) + a=1; + + ent.shaderRGBA[3] = a; + if (item->giType != IT_POWERUP || item->giTag != PW_FORCE_BOON) + { //boon model uses a different blending mode for the sprite inside and doesn't look proper with this method + ent.renderfx |= RF_FORCE_ENT_ALPHA; + } + trap_R_AddRefEntityToScene(&ent); + + ent.renderfx &= ~RF_FORCE_ENT_ALPHA; + + // Now draw the static shader over it. + // Alpha in over half the time, out over half. + + //alpha = sin(M_PI*alpha); + a = alpha * 255.0; + + a = 255 - a; + + if (a <= 0) + a=1; + if (a > 255) + a=255; + + ent.customShader = cgs.media.itemRespawningRezOut; + + /* + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = a; + ent.shaderRGBA[2] = a-100; + + if (ent.shaderRGBA[2] < 0) + { + ent.shaderRGBA[2] = 0; + } + */ + + /* + ent.shaderRGBA[0] = + ent.shaderRGBA[1] = + ent.shaderRGBA[2] = a; + */ + + ent.renderfx |= RF_RGB_TINT; + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = 200; + ent.shaderRGBA[2] = 85; + + trap_R_AddRefEntityToScene( &ent ); + } + else + { // add to refresh list -- normal item + if (item->giType == IT_TEAM && + (item->giTag == PW_REDFLAG || item->giTag == PW_BLUEFLAG)) + { + ent.modelScale[0] = 0.7; + ent.modelScale[1] = 0.7; + ent.modelScale[2] = 0.7; + ScaleModelAxis(&ent); + } + trap_R_AddRefEntityToScene(&ent); + } + + //rww - As far as I can see, this is useless. + /* + if ( item->giType == IT_WEAPON && wi->barrelModel ) { + refEntity_t barrel; + + memset( &barrel, 0, sizeof( barrel ) ); + + barrel.hModel = wi->barrelModel; + + VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = ent.shadowPlane; + barrel.renderfx = ent.renderfx; + + barrel.customShader = ent.customShader; + + CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); + + AxisCopy( ent.axis, barrel.axis ); + barrel.nonNormalizedAxes = ent.nonNormalizedAxes; + + trap_R_AddRefEntityToScene( &barrel ); + } + */ + + // accompanying rings / spheres for powerups + if ( !cg_simpleItems.integer ) + { + vec3_t spinAngles; + + VectorClear( spinAngles ); + + if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP ) + { + if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 ) + { + if ( item->giType == IT_POWERUP ) + { + ent.origin[2] += 12; + spinAngles[1] = ( cg->time & 1023 ) * 360 / -1024.0f; + } + AnglesToAxis( spinAngles, ent.axis ); + + trap_R_AddRefEntityToScene( &ent ); + } + } + } +} + +//============================================================================ + +void CG_CreateDistortionTrailPart(centity_t *cent, float scale, vec3_t pos) +{ + refEntity_t ent; + vec3_t ang; + float vLen; + + if (!cg_renderToTextureFX.integer) + { + return; + } + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( pos, ent.origin ); + + VectorSubtract(ent.origin, cg->refdef.vieworg, ent.axis[0]); + vLen = VectorLength(ent.axis[0]); + if (VectorNormalize(ent.axis[0]) <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + + VectorCopy(cent->lerpAngles, ang); + ang[PITCH] += 90.0f; + AnglesToAxis(ang, ent.axis); + + //radius must be a power of 2, and is the actual captured texture size + if (vLen < 512) + { + ent.radius = 256; + } + else if (vLen < 1024) + { + ent.radius = 128; + } + else if (vLen < 2048) + { + ent.radius = 64; + } + else + { + ent.radius = 32; + } + + ent.modelScale[0] = scale; + ent.modelScale[1] = scale; + ent.modelScale[2] = scale*16.0f; + ScaleModelAxis(&ent); + + ent.hModel = trap_R_RegisterModel("models/weapons2/merr_sonn/trailmodel.md3"); + ent.customShader = cgs.media.itemRespawningRezOut;//cgs.media.cloakedShader;//cgs.media.halfShieldShader; + +#if 1 + ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); + ent.shaderRGBA[0] = 255.0f; + ent.shaderRGBA[1] = 255.0f; + ent.shaderRGBA[2] = 255.0f; + ent.shaderRGBA[3] = 100.0f; +#else //no alpha + ent.renderfx = RF_DISTORTION; +#endif + + trap_R_AddRefEntityToScene( &ent ); +} + +//distortion trail effect for rockets -rww +/* +static void CG_DistortionTrail( centity_t *cent ) +{ + vec3_t fwd; + vec3_t pos; + float overallScale = 4.0f; + + VectorCopy(cent->currentState.pos.trDelta, fwd); + VectorNormalize(fwd); + + VectorMA(cent->lerpOrigin, -8.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 0.5f*overallScale, pos); + VectorMA(cent->lerpOrigin, -12.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 0.6f*overallScale, pos); + VectorMA(cent->lerpOrigin, -16.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 0.7f*overallScale, pos); + VectorMA(cent->lerpOrigin, -20.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 0.8f*overallScale, pos); + VectorMA(cent->lerpOrigin, -30.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 0.9f*overallScale, pos); + VectorMA(cent->lerpOrigin, -40.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 1.0f*overallScale, pos); +} +*/ + +/* +=============== +CG_Missile +=============== +*/ +static void CG_Missile( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; +// int col; + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS && s1->weapon != G2_MODEL_PART ) { + s1->weapon = 0; + } + + if (cent->ghoul2 && s1->weapon == G2_MODEL_PART) + { + weapon = &cg_weapons[WP_SABER]; + } + else + { + weapon = &cg_weapons[s1->weapon]; + } + + if (cent->currentState.eFlags & EF_RADAROBJECT) + { + CG_AddRadarEnt(cent); + } + + if (s1->weapon == WP_SABER) + { + if ((cent->currentState.modelindex != cent->serverSaberHitIndex || !cent->ghoul2) && !(s1->eFlags & EF_NODRAW)) + { //no g2, or server changed the model we are using + const char *saberModel = CG_ConfigString( CS_MODELS+cent->currentState.modelindex ); + + cent->serverSaberHitIndex = cent->currentState.modelindex; + + if (cent->ghoul2) + { //clean if we already have one (because server changed model string index) + trap_G2API_CleanGhoul2Models(&(cent->ghoul2)); + cent->ghoul2 = 0; + } + + if (saberModel && saberModel[0]) + { + trap_G2API_InitGhoul2Model(¢->ghoul2, saberModel, 0, 0, 0, 0, 0); + } + else + { + trap_G2API_InitGhoul2Model(¢->ghoul2, "models/weapons2/saber/saber_w.glm", 0, 0, 0, 0, 0); + } + return; + } + else if (s1->eFlags & EF_NODRAW) + { + return; + } + } + + if (cent->ghoul2) + { //give us a proper radius + ent.radius = cent->currentState.g2radius; + } + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + + if ( s1->otherEntityNum2 && s1->weapon != WP_SABER ) + {//using an over-ridden trail effect! + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + if ((s1->eFlags&EF_JETPACK_ACTIVE)//hack so we know we're a vehicle Weapon shot + && (g_vehWeaponInfo[s1->otherEntityNum2].iShotFX + || g_vehWeaponInfo[s1->otherEntityNum2].iModel != NULL_HANDLE) ) + { //a vehicle with an override for the weapon trail fx or model + trap_FX_PlayEffectID( g_vehWeaponInfo[s1->otherEntityNum2].iShotFX, cent->lerpOrigin, forward, -1, -1 ); + if ( g_vehWeaponInfo[s1->otherEntityNum2].iLoopSound ) + { + vec3_t velocity; + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg->time, velocity ); + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, g_vehWeaponInfo[s1->otherEntityNum2].iLoopSound ); + } + //add custom model + if ( g_vehWeaponInfo[s1->otherEntityNum2].iModel == NULL_HANDLE ) + { + return; + } + } + else + {//a regular missile + trap_FX_PlayEffectID( cgs.gameEffects[s1->otherEntityNum2], cent->lerpOrigin, forward, -1, -1 ); + if ( s1->loopSound ) + { + vec3_t velocity; + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg->time, velocity ); + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, s1->loopSound ); + } + //FIXME: if has a custom model, too, then set it and do rest of code below? + return; + } + } + else if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + // add trails + if ( weapon->altMissileTrailFunc ) + { + weapon->altMissileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->altMissileDlight ) + { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->altMissileDlight, + weapon->altMissileDlightColor[0], weapon->altMissileDlightColor[1], weapon->altMissileDlightColor[2] ); + } + + // add missile sound + if ( weapon->altMissileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg->time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->altMissileSound ); + } + + //Don't draw something without a model + if ( weapon->altMissileModel == NULL_HANDLE ) + return; + } + else + { + // add trails + if ( weapon->missileTrailFunc ) + { + weapon->missileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->missileDlight ) + { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound + if ( weapon->missileSound ) + { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg->time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); + } + + //Don't draw something without a model + if ( weapon->missileModel == NULL_HANDLE && s1->weapon != WP_SABER && s1->weapon != G2_MODEL_PART ) //saber uses ghoul2 model, doesn't matter + return; + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); +/* +Ghoul2 Insert Start +*/ + CG_SetGhoul2Info(&ent, cent); + +/* +Ghoul2 Insert End +*/ + + // flicker between two skins + ent.skinNum = cg->clientFrame & 1; + ent.renderfx = /*weapon->missileRenderfx | */RF_NOSHADOW; + + if ( !(s1->eFlags&EF_JETPACK_ACTIVE) ) + { + if (s1->weapon != WP_SABER && s1->weapon != G2_MODEL_PART) + { + //if ( cent->currentState.eFlags | EF_ALT_FIRING ) + //rww - why was this like this? + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + ent.hModel = weapon->altMissileModel; + } + else + { + ent.hModel = weapon->missileModel; + } + } + } + //add custom model + else + { + if ( g_vehWeaponInfo[s1->otherEntityNum2].iModel != NULL_HANDLE ) + { + ent.hModel = g_vehWeaponInfo[s1->otherEntityNum2].iModel; + } + else + {//wtf? how did we get here? + return; + } + } + + // spin as it moves + if ( s1->apos.trType != TR_INTERPOLATE ) + { + // convert direction of travel into axis + if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { + ent.axis[0][2] = 1; + } + + // spin as it moves + if ( s1->pos.trType != TR_STATIONARY ) + { + if ( s1->eFlags & EF_MISSILE_STICK ) + { + RotateAroundDirection( ent.axis, cg->time * 0.5f );//Did this so regular missiles don't get broken + } + else + { + RotateAroundDirection( ent.axis, cg->time * 0.25f );//JFM:FLOAT FIX + } + } + else + { + if ( s1->eFlags & EF_MISSILE_STICK ) + { + RotateAroundDirection( ent.axis, (float)s1->pos.trTime * 0.5f ); + } + else + { + RotateAroundDirection( ent.axis, (float)s1->time ); + } + } + } + else + { + AnglesToAxis( cent->lerpAngles, ent.axis ); + } + + if (s1->weapon == WP_SABER) + { + ent.radius = s1->g2radius; + } + + // add to refresh list, possibly with quad glow + CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); + + if (s1->weapon == WP_SABER && cgs.gametype == GT_JEDIMASTER) + { //in jedimaster always make the saber glow when on the ground + vec3_t org; + float wv; + int i; + addspriteArgStruct_t fxSArgs; + //refEntity_t sRef; + //memcpy( &sRef, &ent, sizeof( sRef ) ); + + ent.customShader = cgs.media.solidWhite; + ent.renderfx = RF_RGB_TINT; + wv = sin( cg->time * 0.003f ) * 0.08f + 0.1f; + ent.shaderRGBA[0] = wv * 255; + ent.shaderRGBA[1] = wv * 255; + ent.shaderRGBA[2] = wv * 0; + trap_R_AddRefEntityToScene (&ent); + + for ( i = -4; i < 10; i += 1 ) + { + VectorMA( ent.origin, -i, ent.axis[2], org ); + + VectorCopy(org, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = 5.5f; + fxSArgs.dscale = 5.5f; + fxSArgs.sAlpha = wv; + fxSArgs.eAlpha = wv; + fxSArgs.rotation = 0.0f; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + fxSArgs.shader = cgs.media.yellowDroppedSaberShader; + fxSArgs.flags = 0x08000000; + + //trap_FX_AddSprite( org, NULL, NULL, 5.5f, 5.5f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowSaberGlowShader, 0x08000000 ); + trap_FX_AddSprite(&fxSArgs); + } + + if (cgs.gametype == GT_JEDIMASTER) + { + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 0; + + ent.renderfx |= RF_DEPTHHACK; + ent.customShader = cgs.media.forceSightBubble; + + trap_R_AddRefEntityToScene( &ent ); + } + } + + if ( s1->eFlags & EF_FIRING ) + {//special code for adding the beam to the attached tripwire mine + vec3_t beamOrg; + + VectorMA( ent.origin, 8, ent.axis[0], beamOrg );// forward + trap_FX_PlayEffectID( cgs.effects.mTripMineLaster, beamOrg, ent.axis[0], -1, -1 ); + } +} + +int CG_BMS_START = 0; +int CG_BMS_MID = 1; +int CG_BMS_END = 2; + +/* +------------------------- +CG_PlayDoorLoopSound +------------------------- +*/ + +void CG_PlayDoorLoopSound( centity_t *cent ) +{ + sfxHandle_t sfx; + const char *soundSet; + vec3_t origin; + float *v; + + if ( !cent->currentState.soundSetIndex ) + { + return; + } + + soundSet = CG_ConfigString( CS_AMBIENT_SET + cent->currentState.soundSetIndex ); + + if (!soundSet || !soundSet[0]) + { + return; + } + + sfx = trap_AS_GetBModelSound( soundSet, CG_BMS_MID ); + + if ( sfx == -1 ) + { + return; + } + + if (cent->currentState.eType == ET_MOVER) //shouldn't be in here otherwise, but just in case. + { + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + } + else + { + VectorCopy(cent->lerpOrigin, origin); + } + + //ent->s.loopSound = sfx; + CG_S_AddRealLoopingSound(cent->currentState.number, origin, vec3_origin, sfx); +} + +/* +------------------------- +CG_PlayDoorSound +------------------------- +*/ + +void CG_PlayDoorSound( centity_t *cent, int type ) +{ + sfxHandle_t sfx; + const char *soundSet; + + if ( !cent->currentState.soundSetIndex ) + { + return; + } + + soundSet = CG_ConfigString( CS_AMBIENT_SET + cent->currentState.soundSetIndex ); + + if (!soundSet || !soundSet[0]) + { + return; + } + + sfx = trap_AS_GetBModelSound( soundSet, type ); + + if ( sfx == -1 ) + { + return; + } + + trap_S_StartSound( NULL, cent->currentState.number, CHAN_AUTO, sfx ); +} + +/* +=============== +CG_Mover +=============== +*/ +static void CG_Mover( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + + if ( (cent->currentState.eFlags2&EF2_HYPERSPACE) ) + {//I'm the hyperspace brush + qboolean drawMe = qfalse; + if ( cg->predictedPlayerState.m_iVehicleNum + && cg->predictedVehicleState.hyperSpaceTime + && (cg->time-cg->predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME + && (cg->time-cg->predictedVehicleState.hyperSpaceTime) > 1000 ) + { + if ( (cg->predictedVehicleState.eFlags2&EF2_HYPERSPACE) ) + {//actually hyperspacing now + float timeFrac = ((float)(cg->time-cg->predictedVehicleState.hyperSpaceTime-1000))/(HYPERSPACE_TIME-1000); + if ( timeFrac < (HYPERSPACE_TELEPORT_FRAC+0.1f) ) + {//still in hyperspace or just popped out + const float alpha = timeFrac<0.5f?timeFrac/0.5f:1.0f; + drawMe = qtrue; + VectorMA( cg->refdef.vieworg, 1000.0f+((1.0f-timeFrac)*1000.0f), cg->refdef.viewaxis[0], cent->lerpOrigin ); + VectorSet( cent->lerpAngles, cg->refdef.viewangles[PITCH], cg->refdef.viewangles[YAW]-90.0f, 0 );//cos( ( cg->time + 1000 ) * scale ) * 4 ); + ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = alpha*255; + } + } + } + if ( !drawMe ) + {//else, never draw + return; + } + } + + if (cent->currentState.eFlags & EF_RADAROBJECT) + { + CG_AddRadarEnt(cent); + } + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; +/* +Ghoul2 Insert Start +*/ + + CG_SetGhoul2Info(&ent, cent); +/* +Ghoul2 Insert End +*/ + // flicker between two skins (FIXME?) + ent.skinNum = ( cg->time >> 6 ) & 1; + + // get the model, either as a bmodel or a modelindex + if ( s1->solid == SOLID_BMODEL ) + { + ent.hModel = cgs.inlineDrawModel[s1->modelindex]; + } + else + { + ent.hModel = cgs.gameModels[s1->modelindex]; + } + + if ( s1->eFlags & EF_SHADER_ANIM ) + { + ent.renderfx|=RF_SETANIMINDEX; + ent.skinNum = s1->frame; + //ent.shaderTime = cg->time*0.001f - s1->frame/s1->time;//NOTE: s1->time is number of frames + } + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + + // add the secondary model + if ( s1->modelindex2 ) + { + ent.skinNum = 0; + ent.hModel = cgs.gameModels[s1->modelindex2]; + if (s1->iModelScale) + { //custom model2 scale + ent.modelScale[0] = ent.modelScale[1] = ent.modelScale[2] = s1->iModelScale/100.0f; + ScaleModelAxis(&ent); + } + trap_R_AddRefEntityToScene(&ent); + } + +} + +/* +=============== +CG_Beam + +Also called as an event +=============== +*/ +void CG_Beam( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( s1->pos.trBase, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + AxisClear( ent.axis ); + ent.reType = RT_BEAM; + + ent.renderfx = RF_NOSHADOW; +/* +Ghoul2 Insert Start +*/ + CG_SetGhoul2Info(&ent, cent); + +/* +Ghoul2 Insert End +*/ + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +=============== +CG_Portal +=============== +*/ +static void CG_Portal( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + ByteToDir( s1->eventParm, ent.axis[0] ); + PerpendicularVector( ent.axis[1], ent.axis[0] ); + + // negating this tends to get the directions like they want + // we really should have a camera roll value + VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); + + CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); + ent.reType = RT_PORTALSURFACE; + ent.oldframe = s1->powerups; + ent.frame = s1->frame; // rotation speed + ent.skinNum = s1->clientNum/256.0 * 360; // roll offset +/* +Ghoul2 Insert Start +*/ + CG_SetGhoul2Info(&ent, cent); +/* +Ghoul2 Insert End +*/ + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +========================= +CG_AdjustPositionForMover + +Also called by client movement prediction code +========================= +*/ +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) { + centity_t *cent; + vec3_t oldOrigin, origin, deltaOrigin; + vec3_t oldAngles, angles, deltaAngles; + + if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { + VectorCopy( in, out ); + return; + } + + cent = &cg_entities[ moverNum ]; + if ( cent->currentState.eType != ET_MOVER ) { + VectorCopy( in, out ); + return; + } + + BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); + + BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); + BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); + + VectorSubtract( origin, oldOrigin, deltaOrigin ); + VectorSubtract( angles, oldAngles, deltaAngles ); + + VectorAdd( in, deltaOrigin, out ); + + // FIXME: origin change when on a rotating object +} + +/* +============================= +CG_InterpolateEntityPosition +============================= +*/ +static void CG_InterpolateEntityPosition( centity_t *cent ) { + vec3_t current, next; + float f; + + // it would be an internal error to find an entity that interpolates without + // a snapshot ahead of the current one + if ( cg->nextSnap == NULL ) { + CG_Error( "CG_InterpoateEntityPosition: cg->nextSnap == NULL" ); + } + + f = cg->frameInterpolation; + + // this will linearize a sine or parabolic curve, but it is important + // to not extrapolate player positions if more recent data is available + BG_EvaluateTrajectory( ¢->currentState.pos, cg->snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.pos, cg->nextSnap->serverTime, next ); + + cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); + cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); + cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); + + BG_EvaluateTrajectory( ¢->currentState.apos, cg->snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.apos, cg->nextSnap->serverTime, next ); + + cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); + cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); + cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); +} + +/* +=============== +CG_CalcEntityLerpPositions + +=============== +*/ +void CG_CalcEntityLerpPositions( centity_t *cent ) { + qboolean goAway = qfalse; + + // if this player does not want to see extrapolated players + if ( !cg_smoothClients.integer ) { + // make sure the clients use TR_INTERPOLATE + if ( cent->currentState.number < MAX_CLIENTS ) { + cent->currentState.pos.trType = TR_INTERPOLATE; + cent->nextState.pos.trType = TR_INTERPOLATE; + } + } + + if (cg->predictedPlayerState.m_iVehicleNum && + cg->predictedPlayerState.m_iVehicleNum == cent->currentState.number && + cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE) + { //special case for vehicle we are riding + centity_t *veh = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + + if (veh->currentState.owner == cg->predictedPlayerState.clientNum) + { //only do this if the vehicle is pilotted by this client and predicting properly + BG_EvaluateTrajectory( ¢->currentState.pos, cg->time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg->time, cent->lerpAngles ); + return; + } + } + +#ifdef _XBOX + if ( cent->interpolate[ClientManager::ActiveClientNum()] && cent->currentState.pos.trType == TR_INTERPOLATE ) { +#else + if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { +#endif + CG_InterpolateEntityPosition( cent ); + return; + } + + // first see if we can interpolate between two snaps for + // linear extrapolated clients +#ifdef _XBOX + if ( cent->interpolate[ClientManager::ActiveClientNum()] && cent->currentState.pos.trType == TR_LINEAR_STOP && + cent->currentState.number < MAX_CLIENTS) { +#else + if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && + cent->currentState.number < MAX_CLIENTS) { +#endif + CG_InterpolateEntityPosition( cent ); + goAway = qtrue; + } +#ifdef _XBOX + else if (cent->interpolate[ClientManager::ActiveClientNum()] && cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE) +#else + else if (cent->interpolate && + cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE) +#endif + { + CG_InterpolateEntityPosition( cent ); + goAway = qtrue; + } + else + { + // just use the current frame and evaluate as best we can + BG_EvaluateTrajectory( ¢->currentState.pos, cg->time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg->time, cent->lerpAngles ); + } + +#if 0 + if (cent->hasRagOffset && cent->ragOffsetTime < cg->time) + { //take all of the offsets from last frame and normalize the total direction and add it in + vec3_t slideDir; + vec3_t preOffset; + vec3_t addedOffset; + vec3_t playerMins = {-15, -15, DEFAULT_MINS_2}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + trace_t tr; + + //VectorSubtract(cent->lerpOrigin, callData->bonePos, slideDir); + VectorCopy(cent->ragOffsets, slideDir); + VectorNormalize(slideDir); + + //Store it in case we want to go back + VectorCopy(cent->lerpOriginOffset, preOffset); + + //just add a little at a time + VectorMA(cent->lerpOriginOffset, 0.4f, slideDir, cent->lerpOriginOffset); + + if (VectorLength(cent->lerpOriginOffset) > 10.0f) + { //don't go too far away + VectorCopy(preOffset, cent->lerpOriginOffset); + } + else + { + //Let's trace there to make sure we can make it + VectorAdd(cent->lerpOrigin, cent->lerpOriginOffset, addedOffset); + CG_Trace(&tr, cent->lerpOrigin, playerMins, playerMaxs, addedOffset, cent->currentState.number, MASK_PLAYERSOLID); + + if (tr.startsolid || tr.allsolid || tr.fraction != 1.0f) + { //can't get there + VectorCopy(preOffset, cent->lerpOriginOffset); + } + else + { + /* + if (cent->lerpOriginOffset[2] > 4.0f) + { //don't go too far off the ground + cent->lerpOriginOffset[2] = 4.0f; + } + */ + //I guess I just don't want this happening. + cent->lerpOriginOffset[2] = 0.0f; + } + } + + //done with this bit + cent->hasRagOffset = qfalse; + VectorClear(cent->ragOffsets); + cent->ragOffsetTime = cg->time + 50; + } + + //See if we should add in the offset for ragdoll + if (cent->isRagging && ((cent->currentState.eFlags & EF_DEAD) || (cent->currentState.eFlags & EF_RAG))) + { + VectorAdd(cent->lerpOrigin, cent->lerpOriginOffset, cent->lerpOrigin); + } +#endif + + if (goAway) + { + return; + } + + // adjust for riding a mover if it wasn't rolled into the predicted + // player state + if ( cent->currentState.number != cg->predictedPlayerState.clientNum ) { + CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, + cg->snap->serverTime, cg->time, cent->lerpOrigin ); + } +} + +/* +=============== +CG_TeamBase +=============== +*/ +static void CG_TeamBase( centity_t *cent ) { + refEntity_t model; + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { + // show the flag base + memset(&model, 0, sizeof(model)); + model.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, model.lightingOrigin ); + VectorCopy( cent->lerpOrigin, model.origin ); + AnglesToAxis( cent->currentState.angles, model.axis ); + if ( cent->currentState.modelindex == TEAM_RED ) { + model.hModel = cgs.media.redFlagBaseModel; + } + else if ( cent->currentState.modelindex == TEAM_BLUE ) { + model.hModel = cgs.media.blueFlagBaseModel; + } + else { + model.hModel = cgs.media.neutralFlagBaseModel; + } + + if (cent->currentState.eType != ET_NPC) + { //do not do this for g2animents + trap_R_AddRefEntityToScene( &model ); + } + } +} + +void CG_G2Animated( centity_t *cent ); + +static void CG_FX( centity_t *cent ) +{ + vec3_t fxDir; + int efxIndex = 0; + entityState_t *s1; + const char *s; + + if (cent->miscTime > cg->time) + { + return; + } + + s1 = ¢->currentState; + + if (!s1) + { + return; + } + + if (s1->modelindex2 == FX_STATE_OFF) + { // fx not active + return; + } + + if (s1->modelindex2 < FX_STATE_ONE_SHOT_LIMIT) + { // fx is single shot + if (cent->muzzleFlashTime == s1->modelindex2) + { + return; + } + + cent->muzzleFlashTime = s1->modelindex2; + } + + cent->miscTime = cg->time + s1->speed + random() * s1->time; + + AngleVectors(s1->angles, fxDir, 0, 0); + + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + + if ( cgs.gameEffects[ s1->modelindex ] ) + { + efxIndex = cgs.gameEffects[s1->modelindex]; + } + else + { + s = CG_ConfigString( CS_EFFECTS + s1->modelindex ); + if (s && s[0]) + { + efxIndex = trap_FX_RegisterEffect(s); + cgs.gameEffects[s1->modelindex] = efxIndex; + } + } + + if (efxIndex) + { + if (s1->isPortalEnt) + { + trap_FX_PlayPortalEffectID(efxIndex, cent->lerpOrigin, fxDir, -1, -1 ); + } + else + { + trap_FX_PlayEffectID(efxIndex, cent->lerpOrigin, fxDir, -1, -1 ); + } + } + + +} + + +/* +=============== +CG_AddCEntity + +=============== +*/ +static void CG_AddCEntity( centity_t *cent ) { + // event-only entities will have been dealt with already + if ( cent->currentState.eType >= ET_EVENTS ) { + return; + } + + if (cg->predictedPlayerState.pm_type == PM_INTERMISSION) + { //don't render anything then + if (cent->currentState.eType == ET_GENERAL || + cent->currentState.eType == ET_PLAYER || + cent->currentState.eType == ET_NPC || + cent->currentState.eType == ET_INVISIBLE) + { + return; + } + } + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if(cent->updatedThisFrame) { + if(cent->currentState.eType != ET_PLAYER && cent->currentState.eType != ET_MISSILE && + cent->currentState.weapon != WP_TRIP_MINE) + return; + } + } +#endif + + // calculate the current origin + CG_CalcEntityLerpPositions( cent ); + + // add automatic effects + CG_EntityEffects( cent ); +/* +Ghoul2 Insert Start +*/ + + // add local sound set if any + if ( cent->currentState.soundSetIndex && cent->currentState.eType != ET_MOVER ) + { + const char *soundSet = CG_ConfigString( CS_AMBIENT_SET + cent->currentState.soundSetIndex ); + + if (soundSet && soundSet[0]) + { + trap_S_AddLocalSet(soundSet, cg->refdef.vieworg, cent->lerpOrigin, cent->currentState.number, cg->time); + } + } +/* +Ghoul2 Insert End +*/ + switch ( cent->currentState.eType ) { + default: + CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); + break; + + case ET_FX: + CG_FX( cent ); + break; + + case ET_INVISIBLE: + case ET_PUSH_TRIGGER: + case ET_TELEPORT_TRIGGER: + case ET_TERRAIN: + break; + case ET_GENERAL: + CG_General( cent ); + break; + case ET_PLAYER: + CG_Player( cent ); + break; + case ET_ITEM: + CG_Item( cent ); + break; + case ET_MISSILE: + CG_Missile( cent ); + break; + case ET_SPECIAL: + CG_Special( cent ); + break; + case ET_HOLOCRON: + CG_General( cent ); + break; + case ET_MOVER: + CG_Mover( cent ); + break; + case ET_BEAM: + CG_Beam( cent ); + break; + case ET_PORTAL: + CG_Portal( cent ); + break; + case ET_SPEAKER: + CG_Speaker( cent ); + break; + case ET_NPC: //An entity that wants to be able to use ghoul2 humanoid (and other) anims. Like a player, but not. + CG_G2Animated( cent ); + break; + case ET_TEAM: + CG_TeamBase( cent ); + break; + case ET_BODY: + CG_General( cent ); + break; + } + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + cent->updatedThisFrame = true; +#endif +} + +void CG_ManualEntityRender(centity_t *cent) +{ + CG_AddCEntity(cent); +} + +/* +=============== +CG_AddPacketEntities + +=============== +*/ +void CG_AddPacketEntities( qboolean isPortal ) { + int num; + centity_t *cent; + playerState_t *ps; + + if (isPortal) + { + for ( num = 0 ; num < cg->snap->numEntities ; num++ ) + { + cent = &cg_entities[ cg->snap->entities[ num ].number ]; + + if (cent->currentState.isPortalEnt) + { + CG_AddCEntity( cent ); + } + } + return; + } + + // set cg->frameInterpolation + if ( cg->nextSnap ) { + int delta; + + delta = (cg->nextSnap->serverTime - cg->snap->serverTime); + if ( delta == 0 ) { + cg->frameInterpolation = 0; + } else { + cg->frameInterpolation = (float)( cg->time - cg->snap->serverTime ) / delta; + } + } else { + cg->frameInterpolation = 0; // actually, it should never be used, because + // no entities should be marked as interpolating + } + + // the auto-rotating items will all have the same axis + cg->autoAngles[0] = 0; + cg->autoAngles[1] = ( cg->time & 2047 ) * 360 / 2048.0; + cg->autoAngles[2] = 0; + + cg->autoAnglesFast[0] = 0; + cg->autoAnglesFast[1] = ( cg->time & 1023 ) * 360 / 1024.0f; + cg->autoAnglesFast[2] = 0; + + AnglesToAxis( cg->autoAngles, cg->autoAxis ); + AnglesToAxis( cg->autoAnglesFast, cg->autoAxisFast ); + + // Reset radar entities + cg->radarEntityCount = 0; + cg->bracketedEntityCount = 0; + + // generate and add the entity from the playerstate + ps = &cg->predictedPlayerState; + + CG_CheckPlayerG2Weapons(ps, &cg_entities[cg->predictedPlayerState.clientNum]); + BG_PlayerStateToEntityState( ps, &cg_entities[cg->predictedPlayerState.clientNum].currentState, qfalse ); + + if (cg->predictedPlayerState.m_iVehicleNum) + { //add the vehicle I'm riding first + //BG_PlayerStateToEntityState( &cg->predictedVehicleState, &cg_entities[cg->predictedPlayerState.m_iVehicleNum].currentState, qfalse ); + //cg_entities[cg->predictedPlayerState.m_iVehicleNum].currentState.eType = ET_NPC; + centity_t *veh = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + + if (veh->currentState.owner == cg->predictedPlayerState.clientNum) + { + BG_PlayerStateToEntityState( &cg->predictedVehicleState, &veh->currentState, qfalse ); + veh->currentState.eType = ET_NPC; + + veh->currentState.pos.trType = TR_INTERPOLATE; + } + CG_AddCEntity(veh); + veh->bodyHeight = cg->time; //indicate we have already been added + } + + CG_AddCEntity( &cg_entities[cg->predictedPlayerState.clientNum] ); + + /* + // lerp the non-predicted value for lightning gun origins + CG_CalcEntityLerpPositions( &cg_entities[ cg->snap->ps.clientNum ] ); + */ + //No longer have to do this. + + // add each entity sent over by the server + for ( num = 0 ; num < cg->snap->numEntities ; num++ ) { + // Don't re-add ents that have been predicted. + if (cg->snap->entities[ num ].number != cg->snap->ps.clientNum) + { + cent = &cg_entities[ cg->snap->entities[ num ].number ]; + if (cent->currentState.eType == ET_PLAYER && + cent->currentState.m_iVehicleNum) + { //add his veh first + int j = 0; + + while (j < cg->snap->numEntities) + { + if (cg->snap->entities[j].number == cent->currentState.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg->snap->entities[j].number]; + + CG_AddCEntity(veh); + veh->bodyHeight = cg->time; //indicate we have already been added + break; + } + + j++; + } + } + else if (cent->currentState.eType == ET_NPC && + cent->currentState.m_iVehicleNum && + cent->bodyHeight == cg->time) + { //never add a vehicle with a pilot, his pilot entity will get him added first. + //if we were to add the vehicle after the pilot, the pilot's bolt would lag a frame behind. + continue; + } + CG_AddCEntity( cent ); + } + } + + for(num=0;numcurrentValid[ClientManager::ActiveClientNum()]) +#else + if (cent->currentValid) +#endif + { + CG_AddCEntity( cent ); + } + } +} + +void CG_ROFF_NotetrackCallback( centity_t *cent, const char *notetrack) +{ + int i = 0, r = 0, objectID = 0, anglesGathered = 0, posoffsetGathered = 0; + char type[256]; + char argument[512]; + char addlArg[512]; + char errMsg[256]; + char t[64]; + int addlArgs = 0; + vec3_t parsedAngles, parsedOffset, useAngles, useOrigin, forward, right, up; + + if (!cent || !notetrack) + { + return; + } + + //notetrack = "effect effects/explosion1.efx 0+0+64 0-0-1"; + + while (notetrack[i] && notetrack[i] != ' ') + { + type[i] = notetrack[i]; + i++; + } + + type[i] = '\0'; + + if (notetrack[i] != ' ') + { //didn't pass in a valid notetrack type, or forgot the argument for it + return; + } + + i++; + + while (notetrack[i] && notetrack[i] != ' ') + { + argument[r] = notetrack[i]; + r++; + i++; + } + argument[r] = '\0'; + + if (!r) + { + return; + } + + if (notetrack[i] == ' ') + { //additional arguments... + addlArgs = 1; + + i++; + r = 0; + while (notetrack[i]) + { + addlArg[r] = notetrack[i]; + r++; + i++; + } + addlArg[r] = '\0'; + } + + if (strcmp(type, "effect") == 0) + { + if (!addlArgs) + { + //sprintf(errMsg, "Offset position argument for 'effect' type is invalid."); + //goto functionend; + VectorClear(parsedOffset); + goto defaultoffsetposition; + } + + i = 0; + + while (posoffsetGathered < 3) + { + r = 0; + while (addlArg[i] && addlArg[i] != '+' && addlArg[i] != ' ') + { + t[r] = addlArg[i]; + r++; + i++; + } + t[r] = '\0'; + i++; + if (!r) + { //failure.. + //sprintf(errMsg, "Offset position argument for 'effect' type is invalid."); + //goto functionend; + VectorClear(parsedOffset); + i = 0; + goto defaultoffsetposition; + } + parsedOffset[posoffsetGathered] = atof(t); + posoffsetGathered++; + } + + if (posoffsetGathered < 3) + { + Com_sprintf(errMsg, sizeof(errMsg), "Offset position argument for 'effect' type is invalid."); + goto functionend; + } + + i--; + + if (addlArg[i] != ' ') + { + addlArgs = 0; + } + +defaultoffsetposition: + + objectID = trap_FX_RegisterEffect(argument); + + if (objectID) + { + if (addlArgs) + { //if there is an additional argument for an effect it is expected to be XANGLE-YANGLE-ZANGLE + i++; + while (anglesGathered < 3) + { + r = 0; + while (addlArg[i] && addlArg[i] != '-') + { + t[r] = addlArg[i]; + r++; + i++; + } + t[r] = '\0'; + i++; + + if (!r) + { //failed to get a new part of the vector + anglesGathered = 0; + break; + } + + parsedAngles[anglesGathered] = atof(t); + anglesGathered++; + } + + if (anglesGathered) + { + VectorCopy(parsedAngles, useAngles); + } + else + { //failed to parse angles from the extra argument provided.. + VectorCopy(cent->lerpAngles, useAngles); + } + } + else + { //if no constant angles, play in direction entity is facing + VectorCopy(cent->lerpAngles, useAngles); + } + + AngleVectors(useAngles, forward, right, up); + + VectorCopy(cent->lerpOrigin, useOrigin); + + //forward + useOrigin[0] += forward[0]*parsedOffset[0]; + useOrigin[1] += forward[1]*parsedOffset[0]; + useOrigin[2] += forward[2]*parsedOffset[0]; + + //right + useOrigin[0] += right[0]*parsedOffset[1]; + useOrigin[1] += right[1]*parsedOffset[1]; + useOrigin[2] += right[2]*parsedOffset[1]; + + //up + useOrigin[0] += up[0]*parsedOffset[2]; + useOrigin[1] += up[1]*parsedOffset[2]; + useOrigin[2] += up[2]*parsedOffset[2]; + + trap_FX_PlayEffectID(objectID, useOrigin, useAngles, -1, -1); + } + } + else if (strcmp(type, "sound") == 0) + { + objectID = trap_S_RegisterSound(argument); + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_BODY, objectID); + } + else if (strcmp(type, "loop") == 0) + { //handled server-side + return; + } + //else if ... + else + { + if (type[0]) + { + Com_Printf("^3Warning: \"%s\" is an invalid ROFF notetrack function\n", type); + } + else + { + Com_Printf("^3Warning: Notetrack is missing function and/or arguments\n"); + } + } + + return; + +functionend: + Com_Printf("^3Type-specific notetrack error: %s\n", errMsg); + return; +} + +void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ) +{ + vec3_t rot={0,0,0}; + int vec[3]; + int axis, i; + addpolyArgStruct_t apArgs; + + memset (&apArgs, 0, sizeof(apArgs)); + + for ( axis = 0, vec[0] = 0, vec[1] = 1, vec[2] = 2; axis < 3; axis++, vec[0]++, vec[1]++, vec[2]++ ) + { + for ( i = 0; i < 3; i++ ) + { + if ( vec[i] > 2 ) + { + vec[i] = 0; + } + } + + apArgs.p[0][vec[1]] = mins[vec[1]]; + apArgs.p[0][vec[2]] = mins[vec[2]]; + + apArgs.p[1][vec[1]] = mins[vec[1]]; + apArgs.p[1][vec[2]] = maxs[vec[2]]; + + apArgs.p[2][vec[1]] = maxs[vec[1]]; + apArgs.p[2][vec[2]] = maxs[vec[2]]; + + apArgs.p[3][vec[1]] = maxs[vec[1]]; + apArgs.p[3][vec[2]] = mins[vec[2]]; + + //- face + apArgs.p[0][vec[0]] = apArgs.p[1][vec[0]] = apArgs.p[2][vec[0]] = apArgs.p[3][vec[0]] = mins[vec[0]]; + + apArgs.numVerts = 4; + apArgs.alpha1 = apArgs.alpha2 = alpha; + VectorCopy( color, apArgs.rgb1 ); + VectorCopy( color, apArgs.rgb2 ); + VectorCopy( rot, apArgs.rotationDelta ); + apArgs.killTime = cg->frametime; + apArgs.shader = cgs.media.solidWhite; + + trap_FX_AddPoly( &apArgs ); + + //+ face + apArgs.p[0][vec[0]] = apArgs.p[1][vec[0]] = apArgs.p[2][vec[0]] = apArgs.p[3][vec[0]] = maxs[vec[0]]; + + trap_FX_AddPoly( &apArgs ); + } +} diff --git a/codemp/cgame/cg_event.c b/codemp/cgame/cg_event.c new file mode 100644 index 0000000..5ad1351 --- /dev/null +++ b/codemp/cgame/cg_event.c @@ -0,0 +1,3572 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_event.c -- handle entity events at snapshot or playerstate transitions + +#include "cg_local.h" +#include "fx_local.h" +#include "../ui/ui_shared.h" +#include "../ui/ui_public.h" + +// for the voice chats +#include "../../ui/menudef.h" + +#include "../ghoul2/G2.h" + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif +//========================================================================== + +extern qboolean CG_VehicleWeaponImpact( centity_t *cent ); +extern qboolean CG_InFighter( void ); +extern qboolean CG_InATST( void ); +extern int cg_saberFlashTime; +extern vec3_t cg_saberFlashPos; +extern char *showPowersName[]; + +extern int cg_siegeDeathTime; +extern int cg_siegeDeathDelay; +extern int cg_vehicleAmmoWarning; +extern int cg_vehicleAmmoWarningTime; + +//I know, not siege, but... +typedef enum +{ + TAUNT_TAUNT = 0, + TAUNT_BOW, + TAUNT_MEDITATE, + TAUNT_FLOURISH, + TAUNT_GLOAT +}; +/* +=================== +CG_PlaceString + +Also called by scoreboard drawing +=================== +*/ +const char *CG_PlaceString( int rank ) { + static char str[64]; + char *s, *t; + // number extenstions, eg 1st, 2nd, 3rd, 4th etc. + // note that the rules are different for french, but by changing the required strip strings they seem to work + char sST[10]; + char sND[10]; + char sRD[10]; + char sTH[10]; + char sTiedFor[64]; // german is much longer, super safe... + + trap_SP_GetStringTextString("MP_INGAME_NUMBER_ST",sST, sizeof(sST) ); + trap_SP_GetStringTextString("MP_INGAME_NUMBER_ND",sND, sizeof(sND) ); + trap_SP_GetStringTextString("MP_INGAME_NUMBER_RD",sRD, sizeof(sRD) ); + trap_SP_GetStringTextString("MP_INGAME_NUMBER_TH",sTH, sizeof(sTH) ); + trap_SP_GetStringTextString("MP_INGAME_TIED_FOR" ,sTiedFor,sizeof(sTiedFor) ); + strcat(sTiedFor," "); // save worrying about translators adding spaces or not + + if ( rank & RANK_TIED_FLAG ) { + rank &= ~RANK_TIED_FLAG; + t = sTiedFor;//"Tied for "; + } else { + t = ""; + } + + if ( rank == 1 ) { + s = va("1%s",sST);//S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue + } else if ( rank == 2 ) { + s = va("2%s",sND);//S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red + } else if ( rank == 3 ) { + s = va("3%s",sRD);//S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow + } else if ( rank == 11 ) { + s = va("11%s",sTH); + } else if ( rank == 12 ) { + s = va("12%s",sTH); + } else if ( rank == 13 ) { + s = va("13%s",sTH); + } else if ( rank % 10 == 1 ) { + s = va("%i%s", rank,sST); + } else if ( rank % 10 == 2 ) { + s = va("%i%s", rank,sND); + } else if ( rank % 10 == 3 ) { + s = va("%i%s", rank,sRD); + } else { + s = va("%i%s", rank,sTH); + } + + Com_sprintf( str, sizeof( str ), "%s%s", t, s ); + return str; +} + +qboolean CG_ThereIsAMaster(void); + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) { + int mod; + int target, attacker; + char *message; + const char *targetInfo; + const char *attackerInfo; + char targetName[32]; + char attackerName[32]; + gender_t gender; + clientInfo_t *ci; + + + target = ent->otherEntityNum; + attacker = ent->otherEntityNum2; + mod = ent->eventParm; + + if ( target < 0 || target >= MAX_CLIENTS ) { + CG_Error( "CG_Obituary: target out of range" ); + } + ci = &cgs.clientinfo[target]; + + if ( attacker < 0 || attacker >= MAX_CLIENTS ) { + attacker = ENTITYNUM_WORLD; + attackerInfo = NULL; + } else { + attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); + } + + targetInfo = CG_ConfigString( CS_PLAYERS + target ); + if ( !targetInfo ) { + return; + } + Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof(targetName) - 2); + strcat( targetName, S_COLOR_WHITE ); + + // check for single client messages + + switch( mod ) { + case MOD_SUICIDE: + case MOD_FALLING: + case MOD_CRUSH: + case MOD_WATER: + case MOD_SLIME: + case MOD_LAVA: + case MOD_TRIGGER_HURT: + message = "DIED_GENERIC"; + break; + case MOD_TARGET_LASER: + message = "DIED_LASER"; + break; + default: + message = NULL; + break; + } + + // Attacker killed themselves. Ridicule them for it. + if (attacker == target) { + gender = ci->gender; + switch (mod) { + case MOD_BRYAR_PISTOL: + case MOD_BRYAR_PISTOL_ALT: + case MOD_BLASTER: + case MOD_TURBLAST: + case MOD_DISRUPTOR: + case MOD_DISRUPTOR_SPLASH: + case MOD_DISRUPTOR_SNIPER: + case MOD_BOWCASTER: + case MOD_REPEATER: + case MOD_REPEATER_ALT: + case MOD_FLECHETTE: + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_SHOT_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_SHOT_GENDERLESS"; + else + message = "SUICIDE_SHOT_MALE"; + break; + case MOD_REPEATER_ALT_SPLASH: + case MOD_FLECHETTE_ALT_SPLASH: + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: + case MOD_ROCKET_HOMING: + case MOD_ROCKET_HOMING_SPLASH: + case MOD_THERMAL: + case MOD_THERMAL_SPLASH: + case MOD_TRIP_MINE_SPLASH: + case MOD_TIMED_MINE_SPLASH: + case MOD_DET_PACK_SPLASH: + case MOD_VEHICLE: + case MOD_CONC: + case MOD_CONC_ALT: + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_EXPLOSIVES_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_EXPLOSIVES_GENDERLESS"; + else + message = "SUICIDE_EXPLOSIVES_MALE"; + break; + case MOD_DEMP2: + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_ELECTROCUTED_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_ELECTROCUTED_GENDERLESS"; + else + message = "SUICIDE_ELECTROCUTED_MALE"; + break; + case MOD_FALLING: + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_FALLDEATH_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_FALLDEATH_GENDERLESS"; + else + message = "SUICIDE_FALLDEATH_MALE"; + break; + default: + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_GENERICDEATH_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_GENERICDEATH_GENDERLESS"; + else + message = "SUICIDE_GENERICDEATH_MALE"; + break; + } + } + + if (target != attacker && target < MAX_CLIENTS && attacker < MAX_CLIENTS) + { + goto clientkilled; + } + + if (message) { + gender = ci->gender; + + if (!message[0]) + { + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_GENERICDEATH_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_GENERICDEATH_GENDERLESS"; + else + message = "SUICIDE_GENERICDEATH_MALE"; + } + message = (char *)CG_GetStringEdString("MP_INGAME", message); + + CG_PrintfAlways( "%s %s\n", targetName, message); + return; + } + +clientkilled: + + // check for kill messages from the current clientNum + if ( attacker == cg->snap->ps.clientNum ) { + char *s; + + if ( cgs.gametype < GT_TEAM && cgs.gametype != GT_DUEL && cgs.gametype != GT_POWERDUEL ) { +/* + if (cgs.gametype == GT_JEDIMASTER && + attacker < MAX_CLIENTS && + !ent->isJediMaster && + !cg->snap->ps.isJediMaster && + CG_ThereIsAMaster()) + { + char part1[512]; + char part2[512]; + trap_SP_GetStringTextString("MP_INGAME_KILLED_MESSAGE", part1, sizeof(part1)); + trap_SP_GetStringTextString("MP_INGAME_JMKILLED_NOTJM", part2, sizeof(part2)); + s = va("%s %s\n%s\n", part1, targetName, part2); + } + else if (cgs.gametype == GT_JEDIMASTER && + attacker < MAX_CLIENTS && + !ent->isJediMaster && + !cg->snap->ps.isJediMaster) + { //no JM, saber must be out + char part1[512]; + trap_SP_GetStringTextString("MP_INGAME_KILLED_MESSAGE", part1, sizeof(part1)); + +// kmsg1 = "for 0 points.\nGo for the saber!"; +// strcpy(part2, kmsg1); + +// s = va("%s %s %s\n", part1, targetName, part2); + + s = va("%s %s\n", part1, targetName); + } + else +*/ + if (cgs.gametype == GT_POWERDUEL) + { + s = ""; + } + else + { + char sPlaceWith[256]; + char sKilledStr[256]; + trap_SP_GetStringTextString("MP_INGAME_PLACE_WITH", sPlaceWith, sizeof(sPlaceWith)); + trap_SP_GetStringTextString("MP_INGAME_KILLED_MESSAGE", sKilledStr, sizeof(sKilledStr)); + + s = va("%s %s.\n%s %s %i.", sKilledStr, targetName, + CG_PlaceString( cg->snap->ps.persistant[PERS_RANK] + 1 ), + sPlaceWith, + cg->snap->ps.persistant[PERS_SCORE] ); + } + } else { + char sKilledStr[256]; + trap_SP_GetStringTextString("MP_INGAME_KILLED_MESSAGE", sKilledStr, sizeof(sKilledStr)); + s = va("%s %s", sKilledStr, targetName ); + } + //if (!(cg_singlePlayerActive.integer && cg_cameraOrbit.integer)) { +#ifdef _XBOX + int wh = SCREEN_HEIGHT; + if(ClientManager::splitScreenMode == qtrue) + wh = SCREEN_HEIGHT / 2; + + CG_CenterPrint( s, wh * 0.30, BIGCHAR_WIDTH ); +#else + CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); +#endif + //} + // print the text message as well + } + + // check for double client messages + if ( !attackerInfo ) { + attacker = ENTITYNUM_WORLD; + strcpy( attackerName, "noname" ); + } else { + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2); + strcat( attackerName, S_COLOR_WHITE ); + // check for kill messages about the current clientNum + if ( target == cg->snap->ps.clientNum ) { + Q_strncpyz( cg->killerName, attackerName, sizeof( cg->killerName ) ); + } + } + + if ( attacker != ENTITYNUM_WORLD ) { + switch (mod) { + case MOD_STUN_BATON: + message = "KILLED_STUN"; + break; + case MOD_MELEE: + message = "KILLED_MELEE"; + break; + case MOD_SABER: + message = "KILLED_SABER"; + break; + case MOD_BRYAR_PISTOL: + case MOD_BRYAR_PISTOL_ALT: + message = "KILLED_BRYAR"; + break; + case MOD_BLASTER: + message = "KILLED_BLASTER"; + break; + case MOD_TURBLAST: + message = "KILLED_BLASTER"; + break; + case MOD_DISRUPTOR: + case MOD_DISRUPTOR_SPLASH: + message = "KILLED_DISRUPTOR"; + break; + case MOD_DISRUPTOR_SNIPER: + message = "KILLED_DISRUPTORSNIPE"; + break; + case MOD_BOWCASTER: + message = "KILLED_BOWCASTER"; + break; + case MOD_REPEATER: + message = "KILLED_REPEATER"; + break; + case MOD_REPEATER_ALT: + case MOD_REPEATER_ALT_SPLASH: + message = "KILLED_REPEATERALT"; + break; + case MOD_DEMP2: + case MOD_DEMP2_ALT: + message = "KILLED_DEMP2"; + break; + case MOD_FLECHETTE: + message = "KILLED_FLECHETTE"; + break; + case MOD_FLECHETTE_ALT_SPLASH: + message = "KILLED_FLECHETTE_MINE"; + break; + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: + message = "KILLED_ROCKET"; + break; + case MOD_ROCKET_HOMING: + case MOD_ROCKET_HOMING_SPLASH: + message = "KILLED_ROCKET_HOMING"; + break; + case MOD_THERMAL: + case MOD_THERMAL_SPLASH: + message = "KILLED_THERMAL"; + break; + case MOD_TRIP_MINE_SPLASH: + message = "KILLED_TRIPMINE"; + break; + case MOD_TIMED_MINE_SPLASH: + message = "KILLED_TRIPMINE_TIMED"; + break; + case MOD_DET_PACK_SPLASH: + message = "KILLED_DETPACK"; + break; + case MOD_VEHICLE: + case MOD_CONC: + case MOD_CONC_ALT: + message = "KILLED_GENERIC"; + break; + case MOD_FORCE_DARK: + message = "KILLED_DARKFORCE"; + break; + case MOD_SENTRY: + message = "KILLED_SENTRY"; + break; + case MOD_TELEFRAG: + message = "KILLED_TELEFRAG"; + break; + case MOD_CRUSH: + message = "KILLED_GENERIC";//"KILLED_FORCETOSS"; + break; + case MOD_FALLING: + message = "KILLED_FORCETOSS"; + break; + case MOD_TRIGGER_HURT: + message = "KILLED_GENERIC";//"KILLED_FORCETOSS"; + break; + default: + message = "KILLED_GENERIC"; + break; + } + + if (message) { + message = (char *)CG_GetStringEdString("MP_INGAME", message); + CG_PrintfAlways( "%s %s %s\n", + targetName, message, attackerName); + return; + } + } + + // we don't know what it was + CG_PrintfAlways( "%s %s\n", targetName, (char *)CG_GetStringEdString("MP_INGAME", "DIED_GENERIC") ); +} + +//========================================================================== + +void CG_ToggleBinoculars(centity_t *cent, int forceZoom) +{ + if (cent->currentState.number != cg->snap->ps.clientNum) + { + return; + } + + if (cg->snap->ps.weaponstate != WEAPON_READY) + { //So we can't fool it and reactivate while switching to the saber or something. + return; + } + + /* + if (cg->snap->ps.weapon == WP_SABER) + { //No. + return; + } + */ + + if (forceZoom) + { + if (forceZoom == 2) + { + cg->snap->ps.zoomMode = 0; + } + else if (forceZoom == 1) + { + cg->snap->ps.zoomMode = 2; + } + } + + if (cg->snap->ps.zoomMode == 0) + { + trap_S_StartSound( NULL, cg->snap->ps.clientNum, CHAN_AUTO, cgs.media.zoomStart ); + } + else if (cg->snap->ps.zoomMode == 2) + { + trap_S_StartSound( NULL, cg->snap->ps.clientNum, CHAN_AUTO, cgs.media.zoomEnd ); + } +} + +//set the local timing bar +extern int cg_genericTimerBar; +extern int cg_genericTimerDur; +extern vec4_t cg_genericTimerColor; +void CG_LocalTimingBar(int startTime, int duration) +{ + cg_genericTimerBar = startTime + duration; + cg_genericTimerDur = duration; + + cg_genericTimerColor[0] = 1.0f; + cg_genericTimerColor[1] = 1.0f; + cg_genericTimerColor[2] = 0.0f; + cg_genericTimerColor[3] = 1.0f; +} + +/* +=============== +CG_UseItem +=============== +*/ +static void CG_UseItem( centity_t *cent ) { + clientInfo_t *ci; + int itemNum, clientNum; + gitem_t *item; + entityState_t *es; + + es = ¢->currentState; + + itemNum = (es->event & ~EV_EVENT_BITS) - EV_USE_ITEM0; + if ( itemNum < 0 || itemNum > HI_NUM_HOLDABLE ) { + itemNum = 0; + } + + // print a message if the local player + if ( es->number == cg->snap->ps.clientNum ) { + if ( !itemNum ) { + //CG_CenterPrint( "No item to use", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } else { + item = BG_FindItemForHoldable( itemNum ); + } + } + + switch ( itemNum ) { + default: + case HI_NONE: + //trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); + break; + + case HI_BINOCULARS: + CG_ToggleBinoculars(cent, es->eventParm); + break; + + case HI_SEEKER: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.deploySeeker ); + break; + + case HI_SHIELD: + case HI_SENTRY_GUN: + break; + +// case HI_MEDKIT: + case HI_MEDPAC: + case HI_MEDPAC_BIG: + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + ci->medkitUsageTime = cg->time; + } + //Different sound for big bacta? + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.medkitSound ); + break; + case HI_JETPACK: + break; //Do something? + case HI_HEALTHDISP: + //CG_LocalTimingBar(cg->time, TOSS_DEBOUNCE_TIME); + break; + case HI_AMMODISP: + //CG_LocalTimingBar(cg->time, TOSS_DEBOUNCE_TIME); + break; + case HI_EWEB: + break; + case HI_CLOAK: + break; //Do something? + } + + if (cg->snap && cg->snap->ps.clientNum == cent->currentState.number && itemNum != HI_BINOCULARS && + itemNum != HI_JETPACK && itemNum != HI_HEALTHDISP && itemNum != HI_AMMODISP && itemNum != HI_CLOAK && itemNum != HI_EWEB) + { //if not using binoculars/jetpack/dispensers/cloak, we just used that item up, so switch + BG_CycleInven(&cg->snap->ps, 1); + cg->itemSelect = -1; //update the client-side selection display + } +} + + +/* +================ +CG_ItemPickup + +A new item was picked up this frame +================ +*/ +static void CG_ItemPickup( int itemNum ) { +#ifdef _XBOX + short autoswitch = ClientManager::ActiveClient().cg_autoswitch; +#else + short autoswitch = cg_autoswitch.integer; +#endif //_XBOX + cg->itemPickup = itemNum; + cg->itemPickupTime = cg->time; + cg->itemPickupBlendTime = cg->time; + // see if it should be the grabbed weapon + if ( cg->snap && bg_itemlist[itemNum].giType == IT_WEAPON ) { + + // 0 == no switching + // 1 == automatically switch to best SAFE weapon + // 2 == automatically switch to best weapon, safe or otherwise + // 3 == if not saber, automatically switch to best weapon, safe or otherwise + + if (0 == autoswitch) + { + // don't switch + } + else if ( autoswitch == 1) + { //only autoselect if not explosive ("safe") + if (bg_itemlist[itemNum].giTag != WP_TRIP_MINE && + bg_itemlist[itemNum].giTag != WP_DET_PACK && + bg_itemlist[itemNum].giTag != WP_THERMAL && + bg_itemlist[itemNum].giTag != WP_ROCKET_LAUNCHER && + bg_itemlist[itemNum].giTag > cg->snap->ps.weapon && + cg->snap->ps.weapon != WP_SABER) + { + if (!cg->snap->ps.emplacedIndex) + { + cg->weaponSelectTime = cg->time; + } + cg->weaponSelect = bg_itemlist[itemNum].giTag; + } + } + else if ( autoswitch == 2) + { //autoselect if better + if (bg_itemlist[itemNum].giTag > cg->snap->ps.weapon && + cg->snap->ps.weapon != WP_SABER) + { + if (!cg->snap->ps.emplacedIndex) + { + cg->weaponSelectTime = cg->time; + } + cg->weaponSelect = bg_itemlist[itemNum].giTag; + } + } + /* + else if ( cg_autoswitch.integer == 3) + { //autoselect if better and not using the saber as a weapon + if (bg_itemlist[itemNum].giTag > cg->snap->ps.weapon && + cg->snap->ps.weapon != WP_SABER) + { + if (!cg->snap->ps.emplacedIndex) + { + cg->weaponSelectTime = cg->time; + } + cg->weaponSelect = bg_itemlist[itemNum].giTag; + } + } + */ + //No longer required - just not switching ever if using saber + } + + //rww - print pickup messages + if (bg_itemlist[itemNum].classname && bg_itemlist[itemNum].classname[0] && + (bg_itemlist[itemNum].giType != IT_TEAM || (bg_itemlist[itemNum].giTag != PW_REDFLAG && bg_itemlist[itemNum].giTag != PW_BLUEFLAG)) ) + { //don't print messages for flags, they have their own pickup event broadcasts + char text[1024]; + char upperKey[1024]; + + strcpy(upperKey, bg_itemlist[itemNum].classname); + + if ( trap_SP_GetStringTextString( va("SP_INGAME_%s",Q_strupr(upperKey)), text, sizeof( text ))) + { + Com_PrintfAlways("%s %s\n", CG_GetStringEdString("MP_INGAME", "PICKUPLINE"), text); + } + else + { + Com_PrintfAlways("%s %s\n", CG_GetStringEdString("MP_INGAME", "PICKUPLINE"), bg_itemlist[itemNum].classname); + } + } +} + + +/* +================ +CG_PainEvent + +Also called by playerstate transition +================ +*/ +void CG_PainEvent( centity_t *cent, int health ) { + char *snd; + + // don't do more than two pain sounds a second + if ( cg->time - cent->pe.painTime < 500 ) { + return; + } + + if ( health < 25 ) { + snd = "*pain25.wav"; + } else if ( health < 50 ) { + snd = "*pain50.wav"; + } else if ( health < 75 ) { + snd = "*pain75.wav"; + } else { + snd = "*pain100.wav"; + } + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, snd ) ); + + // save pain time for programitic twitch animation + cent->pe.painTime = cg->time; + cent->pe.painDirection ^= 1; +} + +extern qboolean BG_GetRootSurfNameWithVariant( void *ghoul2, const char *rootSurfName, char *returnSurfName, int returnSize ); +void CG_ReattachLimb(centity_t *source) +{ + clientInfo_t *ci = NULL; + + if ( source->currentState.number >= MAX_CLIENTS ) + { + ci = source->npcClient; + } + else + { + ci = &cgs.clientinfo[source->currentState.number]; + } + if ( ci ) + {//re-apply the skin + if ( ci->torsoSkin > 0 ) + { + trap_G2API_SetSkin(source->ghoul2,0,ci->torsoSkin,ci->torsoSkin); + } + } + + /* + char *limbName; + char *stubCapName; + int i = G2_MODELPART_HEAD; + + //rww NOTE: Assumes G2_MODELPART_HEAD is first and G2_MODELPART_RLEG is last + while (i <= G2_MODELPART_RLEG) + { + if (source->torsoBolt & (1 << (i-10))) + { + switch (i) + { + case G2_MODELPART_HEAD: + limbName = "head"; + stubCapName = "torso_cap_head"; + break; + case G2_MODELPART_WAIST: + limbName = "torso"; + stubCapName = "hips_cap_torso"; + break; + case G2_MODELPART_LARM: + limbName = "l_arm"; + stubCapName = "torso_cap_l_arm"; + break; + case G2_MODELPART_RARM: + limbName = "r_arm"; + stubCapName = "torso_cap_r_arm"; + break; + case G2_MODELPART_RHAND: + limbName = "r_hand"; + stubCapName = "r_arm_cap_r_hand"; + break; + case G2_MODELPART_LLEG: + limbName = "l_leg"; + stubCapName = "hips_cap_l_leg"; + break; + case G2_MODELPART_RLEG: + limbName = "r_leg"; + stubCapName = "hips_cap_r_leg"; + break; + default: + source->torsoBolt = 0; + source->ghoul2weapon = NULL; + return; + } + + trap_G2API_SetSurfaceOnOff(source->ghoul2, limbName, 0); + trap_G2API_SetSurfaceOnOff(source->ghoul2, stubCapName, 0x00000100); + } + i++; + } + */ + source->torsoBolt = 0; + + source->ghoul2weapon = NULL; +} + +const char *CG_TeamName(int team) +{ + if (team==TEAM_RED) + return "RED"; + else if (team==TEAM_BLUE) + return "BLUE"; + else if (team==TEAM_SPECTATOR) + return "SPECTATOR"; + return "FREE"; +} + +void CG_PrintCTFMessage(clientInfo_t *ci, const char *teamName, int ctfMessage) +{ + char printMsg[1024]; + char *refName = NULL; + const char *psStringEDString = NULL; + + switch (ctfMessage) + { + case CTFMESSAGE_FRAGGED_FLAG_CARRIER: + refName = "FRAGGED_FLAG_CARRIER"; + break; + case CTFMESSAGE_FLAG_RETURNED: + refName = "FLAG_RETURNED"; + break; + case CTFMESSAGE_PLAYER_RETURNED_FLAG: + refName = "PLAYER_RETURNED_FLAG"; + break; + case CTFMESSAGE_PLAYER_CAPTURED_FLAG: + refName = "PLAYER_CAPTURED_FLAG"; + break; + case CTFMESSAGE_PLAYER_GOT_FLAG: + refName = "PLAYER_GOT_FLAG"; + break; + default: + return; + } + + psStringEDString = CG_GetStringEdString("MP_INGAME", refName); + + if (!psStringEDString || !psStringEDString[0]) + { + return; + } + + if (teamName && teamName[0]) + { + const char *f = strstr(psStringEDString, "%s"); + + if (f) + { + int strLen = 0; + int i = 0; + + if (ci) + { + Com_sprintf(printMsg, sizeof(printMsg), "%s ", ci->name); + strLen = strlen(printMsg); + } + + while (psStringEDString[i] && i < 512) + { + if (psStringEDString[i] == '%' && + psStringEDString[i+1] == 's') + { + printMsg[strLen] = '\0'; + Q_strcat(printMsg, sizeof(printMsg), teamName); + strLen = strlen(printMsg); + + i++; + } + else + { + printMsg[strLen] = psStringEDString[i]; + strLen++; + } + + i++; + } + + printMsg[strLen] = '\0'; + + goto doPrint; + } + } + + if (ci) + { + Com_sprintf(printMsg, sizeof(printMsg), "%s %s", ci->name, psStringEDString); + } + else + { + Com_sprintf(printMsg, sizeof(printMsg), "%s", psStringEDString); + } + +doPrint: + Com_Printf("%s\n", printMsg); +} + +void CG_GetCTFMessageEvent(entityState_t *es) +{ + int clIndex = es->trickedentindex; + int teamIndex = es->trickedentindex2; + clientInfo_t *ci = NULL; + const char *teamName = NULL; + + if (clIndex < MAX_CLIENTS) + { + ci = &cgs.clientinfo[clIndex]; + } + + if (teamIndex < 50) + { + teamName = CG_TeamName(teamIndex); + } + + if (!ci) + { + return; + } + + CG_PrintCTFMessage(ci, teamName, es->eventParm); +} + +#include "../namespace_begin.h" +qboolean BG_InKnockDownOnly( int anim ); +#include "../namespace_end.h" + +#include "../client/fffx.h" + +void DoFall(centity_t *cent, entityState_t *es, int clientNum) +{ + int delta = es->eventParm; + + if (cent->currentState.eFlags & EF_DEAD) + { //corpses crack into the ground ^_^ + if (delta > 25) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.fallSound ); + } + else + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( "sound/movers/objects/objectHit.wav" ) ); + } + } + else if (BG_InKnockDownOnly(es->legsAnim)) + { + if (delta > 14) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.fallSound ); + } + else + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( "sound/movers/objects/objectHit.wav" ) ); + } + } + else if (delta > 50) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.fallSound ); + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, "*land1.wav" ) ); + cent->pe.painTime = cg->time; // don't play a pain sound right after this + } + else if (delta > 44) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.fallSound ); + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, "*land1.wav" ) ); + cent->pe.painTime = cg->time; // don't play a pain sound right after this + } + else + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound ); + } + + if ( clientNum == cg->predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg->landChange = -delta; + if (cg->landChange > 32) + { + cg->landChange = 32; + } + if (cg->landChange < -32) + { + cg->landChange = -32; + } + cg->landTime = cg->time; + } +//JLFRUMBLE +#ifdef _XBOX + + if ( cent->playerState && clientNum == clc->clientNum) + { + if ( delta >= 50) + FF_Play(fffx_FallingMedium); + else if( delta >= 75) + FF_Play(fffx_FallingFar); + } +#endif +} + +int CG_InClientBitflags(entityState_t *ent, int client) +{ + int checkIn; + int sub = 0; + + if (client > 47) + { + checkIn = ent->trickedentindex4; + sub = 48; + } + else if (client > 31) + { + checkIn = ent->trickedentindex3; + sub = 32; + } + else if (client > 15) + { + checkIn = ent->trickedentindex2; + sub = 16; + } + else + { + checkIn = ent->trickedentindex; + } + + if (checkIn & (1 << (client-sub))) + { + return 1; + } + + return 0; +} + +void CG_PlayDoorLoopSound( centity_t *cent ); +void CG_PlayDoorSound( centity_t *cent, int type ); + +void CG_TryPlayCustomSound( vec3_t origin, int entityNum, int channel, const char *soundName ) +{ + sfxHandle_t cSound = CG_CustomSound(entityNum, soundName); + + if (cSound <= 0) + { + return; + } + + trap_S_StartSound(origin, entityNum, channel, cSound); +} + +void CG_G2MarkEvent(entityState_t *es) +{ + //es->origin should be the hit location of the projectile, + //whereas es->origin2 is the predicted position of the + //projectile. (based on the trajectory upon impact) -rww + centity_t *pOwner = &cg_entities[es->otherEntityNum]; + vec3_t startPoint; + float size = 0.0f; + qhandle_t shader = 0; + + if (!pOwner->ghoul2) + { //can't do anything then... + return; + } + + //es->eventParm being non-0 means to do a special trace check + //first. This will give us an impact right at the surface to + //project the mark on. Typically this is used for radius + //explosions and such, where the source position could be + //way outside of model space. + if (es->eventParm) + { + trace_t tr; + int ignore = ENTITYNUM_NONE; + + CG_G2Trace(&tr, es->origin, NULL, NULL, es->origin2, ignore, MASK_PLAYERSOLID); + + if (tr.entityNum != es->otherEntityNum) + { //try again if we hit an ent but not the one we wanted. + //CG_TestLine(es->origin, es->origin2, 2000, 0x0000ff, 1); + if (tr.entityNum < ENTITYNUM_WORLD) + { + ignore = tr.entityNum; + CG_G2Trace(&tr, es->origin, NULL, NULL, es->origin2, ignore, MASK_PLAYERSOLID); + if (tr.entityNum != es->otherEntityNum) + { //try extending the trace a bit.. or not + /* + vec3_t v; + + VectorSubtract(es->origin2, es->origin, v); + VectorScale(v, 64.0f, v); + VectorAdd(es->origin2, v, es->origin2); + + CG_G2Trace(&tr, es->origin, NULL, NULL, es->origin2, ignore, MASK_PLAYERSOLID); + if (tr.entityNum != es->otherEntityNum) + { + return; + } + */ + //didn't manage to collide with the desired person. No mark will be placed then. + return; + } + } + } + + //otherwise we now have a valid starting point. + VectorCopy(tr.endpos, startPoint); + } + else + { + VectorCopy(es->origin, startPoint); + } + + if ( (es->eFlags&EF_JETPACK_ACTIVE) ) + {// a vehicle weapon, make it a larger size mark + //OR base this on the size of the thing you hit? + if ( g_vehWeaponInfo[es->otherEntityNum2].fG2MarkSize ) + { + size = flrand( 0.6f, 1.4f )*g_vehWeaponInfo[es->otherEntityNum2].fG2MarkSize; + } + else + { + size = flrand( 32.0f, 72.0f ); + } + //specify mark shader in vehWeapon file + if ( g_vehWeaponInfo[es->otherEntityNum2].iG2MarkShaderHandle ) + {//have one we want to use instead of defaults + shader = g_vehWeaponInfo[es->otherEntityNum2].iG2MarkShaderHandle; + } + } + switch(es->weapon) + { + case WP_BRYAR_PISTOL: + case WP_CONCUSSION: + case WP_BRYAR_OLD: + case WP_BLASTER: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_REPEATER: + case WP_TURRET: + if ( !size ) + { + size = 4.0f; + } + if ( !shader ) + { + shader = cgs.media.bdecal_bodyburn1; + } + CG_AddGhoul2Mark(shader, size, + startPoint, es->origin2, es->owner, pOwner->lerpOrigin, + pOwner->lerpAngles[YAW], pOwner->ghoul2, + pOwner->modelScale, Q_irand(10000, 20000)); + break; + case WP_ROCKET_LAUNCHER: + case WP_THERMAL: + if ( !size ) + { + size = 24.0f; + } + if ( !shader ) + { + shader = cgs.media.bdecal_burn1; + } + CG_AddGhoul2Mark(shader, size, + startPoint, es->origin2, es->owner, pOwner->lerpOrigin, + pOwner->lerpAngles[YAW], pOwner->ghoul2, + pOwner->modelScale, Q_irand(10000, 20000)); + break; + /* + case WP_FLECHETTE: + CG_AddGhoul2Mark(cgs.media.bdecal_bodyburn1, flrand(0.5f, 1.0f), + startPoint, es->origin2, es->owner, pOwner->lerpOrigin, + pOwner->lerpAngles[YAW], pOwner->ghoul2, + pOwner->modelScale); + break; + */ + //Issues with small scale? + default: + break; + } +} + +void CG_CalcVehMuzzle(Vehicle_t *pVeh, centity_t *ent, int muzzleNum) +{ + mdxaBone_t boltMatrix; + vec3_t vehAngles; + + assert(pVeh); + + if (pVeh->m_iMuzzleTime[muzzleNum] == cg->time) + { //already done for this frame, don't need to do it again + return; + } + //Uh... how about we set this, hunh...? :) + pVeh->m_iMuzzleTime[muzzleNum] = cg->time; + + VectorCopy( ent->lerpAngles, vehAngles ); + if ( pVeh->m_pVehicleInfo ) + { + if (pVeh->m_pVehicleInfo->type == VH_ANIMAL + ||pVeh->m_pVehicleInfo->type == VH_WALKER) + { + vehAngles[PITCH] = vehAngles[ROLL] = 0.0f; + } + else if (pVeh->m_pVehicleInfo->type == VH_SPEEDER) + { + vehAngles[PITCH] = 0.0f; + } + } + trap_G2API_GetBoltMatrix_NoRecNoRot(ent->ghoul2, 0, pVeh->m_iMuzzleTag[muzzleNum], &boltMatrix, vehAngles, + ent->lerpOrigin, cg->time, NULL, ent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, pVeh->m_vMuzzlePos[muzzleNum]); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, pVeh->m_vMuzzleDir[muzzleNum]); +} + +//corresponds to G_VehMuzzleFireFX -rww +void CG_VehMuzzleFireFX(centity_t *veh, entityState_t *broadcaster) +{ + Vehicle_t *pVeh = veh->m_pVehicle; + int curMuz = 0, muzFX = 0; + + if (!pVeh || !veh->ghoul2) + { + return; + } + + for ( curMuz = 0; curMuz < MAX_VEHICLE_MUZZLES; curMuz++ ) + {//go through all muzzles and + if ( pVeh->m_iMuzzleTag[curMuz] != -1//valid muzzle bolt + && (broadcaster->trickedentindex&(1<m_pVehicleInfo->weapMuzzle[curMuz] == 0 ) + {//no weaopon for this muzzle? check turrets + int i, j; + for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ ) + { + for ( j = 0; j < MAX_VEHICLE_TURRETS; j++ ) + { + if ( pVeh->m_pVehicleInfo->turret[i].iMuzzle[j]-1 == curMuz ) + {//this muzzle belongs to this turret + muzFX = g_vehWeaponInfo[pVeh->m_pVehicleInfo->turret[i].iWeapon].iMuzzleFX; + break; + } + } + } + } + else + { + muzFX = g_vehWeaponInfo[pVeh->m_pVehicleInfo->weapMuzzle[curMuz]].iMuzzleFX; + } + if ( muzFX ) + { + //CG_CalcVehMuzzle(pVeh, veh, curMuz); + //trap_FX_PlayEffectID(muzFX, pVeh->m_vMuzzlePos[curMuz], pVeh->m_vMuzzleDir[curMuz], -1, -1); + trap_FX_PlayBoltedEffectID(muzFX, veh->currentState.origin, veh->ghoul2, pVeh->m_iMuzzleTag[curMuz], veh->currentState.number, 0, 0, qtrue); + } + } + } +} + +const char *cg_stringEdVoiceChatTable[MAX_CUSTOM_SIEGE_SOUNDS] = +{ + "VC_ATT",//"*att_attack", + "VC_ATT_PRIMARY",//"*att_primary", + "VC_ATT_SECONDARY",//"*att_second", + "VC_DEF_GUNS",//"*def_guns", + "VC_DEF_POSITION",//"*def_position", + "VC_DEF_PRIMARY",//"*def_primary", + "VC_DEF_SECONDARY",//"*def_second", + "VC_REPLY_COMING",//"*reply_coming", + "VC_REPLY_GO",//"*reply_go", + "VC_REPLY_NO",//"*reply_no", + "VC_REPLY_STAY",//"*reply_stay", + "VC_REPLY_YES",//"*reply_yes", + "VC_REQ_ASSIST",//"*req_assist", + "VC_REQ_DEMO",//"*req_demo", + "VC_REQ_HVY",//"*req_hvy", + "VC_REQ_MEDIC",//"*req_medic", + "VC_REQ_SUPPLY",//"*req_sup", + "VC_REQ_TECH",//"*req_tech", + "VC_SPOT_AIR",//"*spot_air", + "VC_SPOT_DEF",//"*spot_defenses", + "VC_SPOT_EMPLACED",//"*spot_emplaced", + "VC_SPOT_SNIPER",//"*spot_sniper", + "VC_SPOT_TROOP",//"*spot_troops", + "VC_TAC_COVER",//"*tac_cover", + "VC_TAC_FALLBACK",//"*tac_fallback", + "VC_TAC_FOLLOW",//"*tac_follow", + "VC_TAC_HOLD",//"*tac_hold", + "VC_TAC_SPLIT",//"*tac_split", + "VC_TAC_TOGETHER",//"*tac_together", + NULL +}; + +//stupid way of figuring out what string to use for voice chats +const char *CG_GetStringForVoiceSound(const char *s) +{ + int i = 0; + while (i < MAX_CUSTOM_SIEGE_SOUNDS) + { + if (bg_customSiegeSoundNames[i] && + !Q_stricmp(bg_customSiegeSoundNames[i], s)) + { //get the matching reference name + assert(cg_stringEdVoiceChatTable[i]); + return CG_GetStringEdString("MENUS", (char *)cg_stringEdVoiceChatTable[i]); + } + i++; + } + + return "voice chat"; +} + +/* +============== +CG_EntityEvent + +An entity has an event value +also called by CG_CheckPlayerstateEvents +============== +*/ +#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} +extern void CG_ChatBox_AddString(char *chatStr); //cg_draw.c +void CG_EntityEvent( centity_t *cent, vec3_t position ) { + entityState_t *es; + int event; + vec3_t dir; + const char *s; + int clientNum; + clientInfo_t *ci; + int eID = 0; + int isnd = 0; + centity_t *cl_ent; + + es = ¢->currentState; + event = es->event & ~EV_EVENT_BITS; + + if ( cg_debugEvents.integer ) { + CG_Printf( "ent:%3i event:%3i ", es->number, event ); + } + + if ( !event ) { + DEBUGNAME("ZEROEVENT"); + return; + } + + clientNum = es->clientNum; +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if ( clientNum != ClientManager::ActiveClientNum() && + event != EV_GENERAL_SOUND && + event != EV_MISSILE_MISS && + event != EV_OBITUARY) { + return; + } + } +#endif + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + + if (es->eType == ET_NPC) + { + clientNum = es->number; + + if (!cent->npcClient) + { + CG_CreateNPCClient(¢->npcClient); //allocate memory for it + + if (!cent->npcClient) + { + assert(0); + return; + } + + memset(cent->npcClient, 0, sizeof(clientInfo_t)); + cent->npcClient->ghoul2Model = NULL; + } + + ci = cent->npcClient; + + assert(ci); + } + else + { + ci = &cgs.clientinfo[ clientNum ]; + } + + switch ( event ) { + // + // movement generated events + // + case EV_CLIENTJOIN: + DEBUGNAME("EV_CLIENTJOIN"); + + //Slight hack to force a local reinit of client entity on join. + cl_ent = &cg_entities[es->eventParm]; + + if (cl_ent) + { + //cl_ent->torsoBolt = 0; + cl_ent->bolt1 = 0; + cl_ent->bolt2 = 0; + cl_ent->bolt3 = 0; + cl_ent->bolt4 = 0; + cl_ent->bodyHeight = 0;//SABER_LENGTH_MAX; + //cl_ent->saberExtendTime = 0; + cl_ent->boltInfo = 0; + cl_ent->frame_minus1_refreshed = 0; + cl_ent->frame_minus2_refreshed = 0; + cl_ent->frame_hold_time = 0; + cl_ent->frame_hold_refreshed = 0; +#ifdef _XBOX + cl_ent->trickAlpha[0] = 0; + cl_ent->trickAlpha[1] = 0; + cl_ent->trickAlphaTime[0] = 0; + cl_ent->trickAlphaTime[1] = 0; +#else + cl_ent->trickAlpha = 0; + cl_ent->trickAlphaTime = 0; +#endif + cl_ent->ghoul2weapon = NULL; + cl_ent->weapon = WP_NONE; + cl_ent->teamPowerEffectTime = 0; + cl_ent->teamPowerType = 0; + cl_ent->numLoopingSounds = 0; + //cl_ent->localAnimIndex = 0; + } + break; + + case EV_FOOTSTEP: + DEBUGNAME("EV_FOOTSTEP"); + if (cg_footsteps.integer) { + footstep_t soundType; + switch( es->eventParm ) + { + case MATERIAL_MUD: + soundType = FOOTSTEP_MUDWALK; + break; + case MATERIAL_DIRT: + soundType = FOOTSTEP_DIRTWALK; + break; + case MATERIAL_SAND: + soundType = FOOTSTEP_SANDWALK; + break; + case MATERIAL_SNOW: + soundType = FOOTSTEP_SNOWWALK; + break; + case MATERIAL_SHORTGRASS: + case MATERIAL_LONGGRASS: + soundType = FOOTSTEP_GRASSWALK; + break; + case MATERIAL_SOLIDMETAL: + soundType = FOOTSTEP_METALWALK; + break; + case MATERIAL_HOLLOWMETAL: + soundType = FOOTSTEP_PIPEWALK; + break; + case MATERIAL_GRAVEL: + soundType = FOOTSTEP_GRAVELWALK; + break; + case MATERIAL_CARPET: + case MATERIAL_FABRIC: + case MATERIAL_CANVAS: + case MATERIAL_RUBBER: + case MATERIAL_PLASTIC: + soundType = FOOTSTEP_RUGWALK; + break; + case MATERIAL_SOLIDWOOD: + case MATERIAL_HOLLOWWOOD: + soundType = FOOTSTEP_WOODWALK; + break; + + default: + soundType = FOOTSTEP_STONEWALK; + break; + } + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == clientNum) +#endif + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ soundType ][rand()&3] ); + } + break; + case EV_FOOTSTEP_METAL: + DEBUGNAME("EV_FOOTSTEP_METAL"); + if (cg_footsteps.integer) { +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == clientNum) +#endif + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_METALWALK ][rand()&3] ); + } + break; + case EV_FOOTSPLASH: + DEBUGNAME("EV_FOOTSPLASH"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + case EV_FOOTWADE: + DEBUGNAME("EV_FOOTWADE"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + case EV_SWIM: + DEBUGNAME("EV_SWIM"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + + + case EV_FALL: + DEBUGNAME("EV_FALL"); + if (es->number == cg->snap->ps.clientNum && cg->snap->ps.fallingToDeath) + { + break; + } + DoFall(cent, es, clientNum); + break; + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: // smooth out step up transitions + DEBUGNAME("EV_STEP"); + { + float oldStep; + int delta; + int step; + + if ( clientNum != cg->predictedPlayerState.clientNum ) { + break; + } + // if we are interpolating, we don't need to smooth steps +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + break; + } + else +#endif + if ( cg->demoPlayback || (cg->snap->ps.pm_flags & PMF_FOLLOW) || + cg_nopredict.integer || cg_synchronousClients.integer ) { + break; + } + // check for stepping up before a previous step is completed + delta = cg->time - cg->stepTime; + if (delta < STEP_TIME) { + oldStep = cg->stepChange * (STEP_TIME - delta) / STEP_TIME; + } else { + oldStep = 0; + } + + // add this amount + step = 4 * (event - EV_STEP_4 + 1 ); + cg->stepChange = oldStep + step; + if ( cg->stepChange > MAX_STEP_CHANGE ) { + cg->stepChange = MAX_STEP_CHANGE; + } + cg->stepTime = cg->time; + break; + } + + case EV_JUMP_PAD: + DEBUGNAME("EV_JUMP_PAD"); + break; + + case EV_GHOUL2_MARK: + DEBUGNAME("EV_GHOUL2_MARK"); + + if (cg_ghoul2Marks.integer) + { //Can we put a burn mark on him? + CG_G2MarkEvent(es); + } + break; + + case EV_GLOBAL_DUEL: + DEBUGNAME("EV_GLOBAL_DUEL"); + //used for beginning of power duels + //if (cg->predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR) + if (es->otherEntityNum == cg->predictedPlayerState.clientNum || + es->otherEntityNum2 == cg->predictedPlayerState.clientNum || + es->groundEntityNum == cg->predictedPlayerState.clientNum) + { + CG_CenterPrint( CG_GetStringEdString("MP_SVGAME", "BEGIN_DUEL"), 120, GIANTCHAR_WIDTH*2 ); + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + } + break; + + case EV_PRIVATE_DUEL: + DEBUGNAME("EV_PRIVATE_DUEL"); + + if (cg->snap->ps.clientNum != es->number) + { + break; + } + + if (es->eventParm) + { //starting the duel + if (es->eventParm == 2) + { + CG_CenterPrint( CG_GetStringEdString("MP_SVGAME", "BEGIN_DUEL"), 120, GIANTCHAR_WIDTH*2 ); + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + } + else + { + trap_S_StartBackgroundTrack( "music/mp/duel.mp3", "music/mp/duel.mp3", qfalse ); + } + } + else + { //ending the duel + CG_StartMusic(qtrue); + } + break; + + case EV_JUMP: + DEBUGNAME("EV_JUMP"); + if (cg_jumpSounds.integer) + { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + } + break; + case EV_ROLL: + DEBUGNAME("EV_ROLL"); + if (es->number == cg->snap->ps.clientNum && cg->snap->ps.fallingToDeath) + { + break; + } + if (es->eventParm) + { //fall-roll-in-one event + DoFall(cent, es, clientNum); + } + + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.rollSound ); + + //FIXME: need some sort of body impact on ground sound and maybe kick up some dust? + break; + + case EV_TAUNT: + DEBUGNAME("EV_TAUNT"); + { + int soundIndex = 0; + if ( cgs.gametype != GT_DUEL + && cgs.gametype != GT_POWERDUEL + && es->eventParm == TAUNT_TAUNT ) + {//normal taunt + soundIndex = CG_CustomSound( es->number, "*taunt.wav" ); + } + else + { + switch ( es->eventParm ) + { + case TAUNT_TAUNT: + default: + if ( Q_irand( 0, 1 ) ) + { + soundIndex = CG_CustomSound( es->number, va("*anger%d.wav", Q_irand(1,3)) ); + } + else + { + soundIndex = CG_CustomSound( es->number, va("*taunt%d.wav", Q_irand(1,3)) ); + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, va("*anger%d.wav", Q_irand(1,3)) ); + } + } + break; + case TAUNT_BOW: + //soundIndex = CG_CustomSound( es->number, va("*respect%d.wav", Q_irand(1,3)) ); + break; + case TAUNT_MEDITATE: + //soundIndex = CG_CustomSound( es->number, va("*meditate%d.wav", Q_irand(1,3)) ); + break; + case TAUNT_FLOURISH: + if ( Q_irand( 0, 1 ) ) + { + soundIndex = CG_CustomSound( es->number, va("*deflect%d.wav", Q_irand(1,3)) ); + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, va("*gloat%d.wav", Q_irand(1,3)) ); + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, va("*anger%d.wav", Q_irand(1,3)) ); + } + } + } + else + { + soundIndex = CG_CustomSound( es->number, va("*gloat%d.wav", Q_irand(1,3)) ); + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, va("*deflect%d.wav", Q_irand(1,3)) ); + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, va("*anger%d.wav", Q_irand(1,3)) ); + } + } + } + break; + case TAUNT_GLOAT: + soundIndex = CG_CustomSound( es->number, va("*victory%d.wav", Q_irand(1,3)) ); + break; + } + } + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, "*taunt.wav" ); + } + if ( soundIndex ) + { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, soundIndex ); + } + } + break; + + //Begin NPC sounds + case EV_ANGER1: //Say when acquire an enemy when didn't have one before + case EV_ANGER2: + case EV_ANGER3: + DEBUGNAME("EV_ANGERx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*anger%i.wav", event - EV_ANGER1 + 1) ); + break; + + case EV_VICTORY1: //Say when killed an enemy + case EV_VICTORY2: + case EV_VICTORY3: + DEBUGNAME("EV_VICTORYx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*victory%i.wav", event - EV_VICTORY1 + 1) ); + break; + + case EV_CONFUSE1: //Say when confused + case EV_CONFUSE2: + case EV_CONFUSE3: + DEBUGNAME("EV_CONFUSEDx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*confuse%i.wav", event - EV_CONFUSE1 + 1) ); + break; + + case EV_PUSHED1: //Say when pushed + case EV_PUSHED2: + case EV_PUSHED3: + DEBUGNAME("EV_PUSHEDx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*pushed%i.wav", event - EV_PUSHED1 + 1) ); + break; + + case EV_CHOKE1: //Say when choking + case EV_CHOKE2: + case EV_CHOKE3: + DEBUGNAME("EV_CHOKEx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*choke%i.wav", event - EV_CHOKE1 + 1) ); + break; + + case EV_FFWARN: //Warn ally to stop shooting you + DEBUGNAME("EV_FFWARN"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*ffwarn.wav" ); + break; + + case EV_FFTURN: //Turn on ally after being shot by them + DEBUGNAME("EV_FFTURN"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*ffturn.wav" ); + break; + + //extra sounds for ST + case EV_CHASE1: + case EV_CHASE2: + case EV_CHASE3: + DEBUGNAME("EV_CHASEx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*chase%i.wav", event - EV_CHASE1 + 1) ); + break; + case EV_COVER1: + case EV_COVER2: + case EV_COVER3: + case EV_COVER4: + case EV_COVER5: + DEBUGNAME("EV_COVERx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*cover%i.wav", event - EV_COVER1 + 1) ); + break; + case EV_DETECTED1: + case EV_DETECTED2: + case EV_DETECTED3: + case EV_DETECTED4: + case EV_DETECTED5: + DEBUGNAME("EV_DETECTEDx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*detected%i.wav", event - EV_DETECTED1 + 1) ); + break; + case EV_GIVEUP1: + case EV_GIVEUP2: + case EV_GIVEUP3: + case EV_GIVEUP4: + DEBUGNAME("EV_GIVEUPx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*giveup%i.wav", event - EV_GIVEUP1 + 1) ); + break; + case EV_LOOK1: + case EV_LOOK2: + DEBUGNAME("EV_LOOKx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*look%i.wav", event - EV_LOOK1 + 1) ); + break; + case EV_LOST1: + DEBUGNAME("EV_LOST1"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*lost1.wav" ); + break; + case EV_OUTFLANK1: + case EV_OUTFLANK2: + DEBUGNAME("EV_OUTFLANKx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*outflank%i.wav", event - EV_OUTFLANK1 + 1) ); + break; + case EV_ESCAPING1: + case EV_ESCAPING2: + case EV_ESCAPING3: + DEBUGNAME("EV_ESCAPINGx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*escaping%i.wav", event - EV_ESCAPING1 + 1) ); + break; + case EV_SIGHT1: + case EV_SIGHT2: + case EV_SIGHT3: + DEBUGNAME("EV_SIGHTx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*sight%i.wav", event - EV_SIGHT1 + 1) ); + break; + case EV_SOUND1: + case EV_SOUND2: + case EV_SOUND3: + DEBUGNAME("EV_SOUNDx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*sound%i.wav", event - EV_SOUND1 + 1) ); + break; + case EV_SUSPICIOUS1: + case EV_SUSPICIOUS2: + case EV_SUSPICIOUS3: + case EV_SUSPICIOUS4: + case EV_SUSPICIOUS5: + DEBUGNAME("EV_SUSPICIOUSx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*suspicious%i.wav", event - EV_SUSPICIOUS1 + 1) ); + break; + //extra sounds for Jedi + case EV_COMBAT1: + case EV_COMBAT2: + case EV_COMBAT3: + DEBUGNAME("EV_COMBATx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*combat%i.wav", event - EV_COMBAT1 + 1) ); + break; + case EV_JDETECTED1: + case EV_JDETECTED2: + case EV_JDETECTED3: + DEBUGNAME("EV_JDETECTEDx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*jdetected%i.wav", event - EV_JDETECTED1 + 1) ); + break; + case EV_TAUNT1: + case EV_TAUNT2: + case EV_TAUNT3: + DEBUGNAME("EV_TAUNTx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*taunt%i.wav", event - EV_TAUNT1 + 1) ); + break; + case EV_JCHASE1: + case EV_JCHASE2: + case EV_JCHASE3: + DEBUGNAME("EV_JCHASEx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*jchase%i.wav", event - EV_JCHASE1 + 1) ); + break; + case EV_JLOST1: + case EV_JLOST2: + case EV_JLOST3: + DEBUGNAME("EV_JLOSTx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*jlost%i.wav", event - EV_JLOST1 + 1) ); + break; + case EV_DEFLECT1: + case EV_DEFLECT2: + case EV_DEFLECT3: + DEBUGNAME("EV_DEFLECTx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*deflect%i.wav", event - EV_DEFLECT1 + 1) ); + break; + case EV_GLOAT1: + case EV_GLOAT2: + case EV_GLOAT3: + DEBUGNAME("EV_GLOATx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*gloat%i.wav", event - EV_GLOAT1 + 1) ); + break; + case EV_PUSHFAIL: + DEBUGNAME("EV_PUSHFAIL"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*pushfail.wav" ); + break; + //End NPC sounds + + case EV_SIEGESPEC: + DEBUGNAME("EV_SIEGESPEC"); + if ( es->owner == cg->predictedPlayerState.clientNum ) + { + cg_siegeDeathTime = es->time; + } + + break; + + case EV_WATER_TOUCH: + DEBUGNAME("EV_WATER_TOUCH"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); + break; + case EV_WATER_LEAVE: + DEBUGNAME("EV_WATER_LEAVE"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); + break; + case EV_WATER_UNDER: + DEBUGNAME("EV_WATER_UNDER"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); + break; + case EV_WATER_CLEAR: + DEBUGNAME("EV_WATER_CLEAR"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); + break; + + case EV_ITEM_PICKUP: + DEBUGNAME("EV_ITEM_PICKUP"); + { + gitem_t *item; + int index; + qboolean newindex = qfalse; + + index = cg_entities[es->eventParm].currentState.modelindex; // player predicted + +/* + if (index < 1 && cg_entities[es->eventParm].currentState.isJediMaster) + { //a holocron most likely + index = cg_entities[es->eventParm].currentState.trickedentindex4; + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.holocronPickup ); + + if (es->number == cg->snap->ps.clientNum && showPowersName[index]) + { + const char *strText = CG_GetStringEdString("MP_INGAME", "PICKUPLINE"); + + //Com_Printf("%s %s\n", strText, showPowersName[index]); +#ifdef _XBOX + int wh = SCREEN_HEIGHT; + if(ClientManager::splitScreenMode == qtrue) + wh = SCREEN_HEIGHT / 2; + + CG_CenterPrint( va("%s %s\n", strText, CG_GetStringEdString("SP_INGAME",showPowersName[index])), wh * 0.30, BIGCHAR_WIDTH ); +#else + CG_CenterPrint( va("%s %s\n", strText, CG_GetStringEdString("SP_INGAME",showPowersName[index])), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); +#endif + } + + //Show the player their force selection bar in case picking the holocron up changed the current selection + if (index != FP_SABER_OFFENSE && index != FP_SABER_DEFENSE && index != FP_SABERTHROW && + index != FP_LEVITATION && + es->number == cg->snap->ps.clientNum && + (index == cg->snap->ps.fd.forcePowerSelected || !(cg->snap->ps.fd.forcePowersActive & (1 << cg->snap->ps.fd.forcePowerSelected)))) + { + if (cg->forceSelect != index) + { + cg->forceSelect = index; + newindex = qtrue; + } + } + + if (es->number == cg->snap->ps.clientNum && newindex) + { + if (cg->forceSelectTime < cg->time) + { + cg->forceSelectTime = cg->time; + } + } + + break; + } +*/ + + if (cg_entities[es->eventParm].weapon >= cg->time) + { //rww - an unfortunately necessary hack to prevent double item pickups + break; + } + + //Hopefully even if this entity is somehow removed and replaced with, say, another + //item, this time will have expired by the time that item needs to be picked up. + //Of course, it's quite possible this will fail miserably, so if you've got a better + //solution then please do use it. + cg_entities[es->eventParm].weapon = cg->time+500; + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + + if ( /*item->giType != IT_POWERUP && */item->giType != IT_TEAM) { + if (item->pickup_sound && item->pickup_sound[0]) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound ) ); + } + } + + // show icon and name on status bar + if ( es->number == cg->snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + case EV_GLOBAL_ITEM_PICKUP: + DEBUGNAME("EV_GLOBAL_ITEM_PICKUP"); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + // powerup pickups are global + if( item->pickup_sound && item->pickup_sound[0] ) { + trap_S_StartSound (NULL, cg->snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound) ); + } + + // show icon and name on status bar + if ( es->number == cg->snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + case EV_VEH_FIRE: + DEBUGNAME("EV_VEH_FIRE"); + { + centity_t *veh = &cg_entities[es->owner]; + CG_VehMuzzleFireFX(veh, es); + } + break; + + // + // weapon events + // + case EV_NOAMMO: + DEBUGNAME("EV_NOAMMO"); +// trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); + if ( es->number == cg->snap->ps.clientNum ) + { + if ( CG_InFighter() || CG_InATST() || cg->snap->ps.weapon == WP_NONE ) + {//just letting us know our vehicle is out of ammo + //FIXME: flash something on HUD or give some message so we know we have no ammo + centity_t *localCent = &cg_entities[cg->snap->ps.clientNum]; + if ( localCent->m_pVehicle + && localCent->m_pVehicle->m_pVehicleInfo + && localCent->m_pVehicle->m_pVehicleInfo->weapon[es->eventParm].soundNoAmmo ) + {//play the "no Ammo" sound for this weapon + trap_S_StartSound (NULL, cg->snap->ps.clientNum, CHAN_AUTO, localCent->m_pVehicle->m_pVehicleInfo->weapon[es->eventParm].soundNoAmmo ); + } + else + {//play the default "no ammo" sound + trap_S_StartSound (NULL, cg->snap->ps.clientNum, CHAN_AUTO, cgs.media.noAmmoSound ); + } + //flash the HUD so they associate the sound with the visual indicator that they don't have enough ammo + if ( cg_vehicleAmmoWarningTime < cg->time + || cg_vehicleAmmoWarning != es->eventParm ) + {//if there's already one going, don't interrupt it (unless they tried to fire another weapon that's out of ammo) + cg_vehicleAmmoWarning = es->eventParm; + cg_vehicleAmmoWarningTime = cg->time+500; + } + } + else if ( cg->snap->ps.weapon == WP_SABER ) + { + cg->forceHUDTotalFlashTime = cg->time + 1000; + } + else + { + int weap = 0; + + if (es->eventParm && es->eventParm < WP_NUM_WEAPONS) + { + cg->snap->ps.stats[STAT_WEAPONS] &= ~(1 << es->eventParm); + weap = cg->snap->ps.weapon; + } + else if (es->eventParm) + { + weap = (es->eventParm-WP_NUM_WEAPONS); + } + CG_OutOfAmmoChange(weap); + } + } + break; + case EV_CHANGE_WEAPON: + DEBUGNAME("EV_CHANGE_WEAPON"); + { + int weapon = es->eventParm; + weaponInfo_t *weaponInfo; + + assert(weapon >= 0 && weapon < MAX_WEAPONS); + + weaponInfo = &cg_weapons[weapon]; + + assert(weaponInfo); + + if (weaponInfo->selectSound) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, weaponInfo->selectSound ); + } + else if (weapon != WP_SABER) + { //not sure what SP is doing for this but I don't want a select sound for saber (it has the saber-turn-on) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + } + } + break; + case EV_FIRE_WEAPON: + DEBUGNAME("EV_FIRE_WEAPON"); + if (cent->currentState.number >= MAX_CLIENTS && cent->currentState.eType != ET_NPC) + { //special case for turret firing + vec3_t gunpoint, gunangle; + mdxaBone_t matrix; + + weaponInfo_t *weaponInfo = &cg_weapons[WP_TURRET]; + + if ( !weaponInfo->registered ) + { + CG_RegisterWeapon(WP_TURRET); + } + + if (cent->ghoul2) + { + if (!cent->bolt1) + { + cent->bolt1 = trap_G2API_AddBolt(cent->ghoul2, 0, "*flash01"); + } + if (!cent->bolt2) + { + cent->bolt2 = trap_G2API_AddBolt(cent->ghoul2, 0, "*flash02"); + } + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Bone02", 1, 4, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f, cg->time, -1, 300); + } + else + { + break; + } + + if (cent->currentState.eventParm) + { + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cent->bolt2, &matrix, cent->currentState.angles, cent->currentState.origin, cg->time, cgs.gameModels, cent->modelScale); + } + else + { + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cent->bolt1, &matrix, cent->currentState.angles, cent->currentState.origin, cg->time, cgs.gameModels, cent->modelScale); + } + + gunpoint[0] = matrix.matrix[0][3]; + gunpoint[1] = matrix.matrix[1][3]; + gunpoint[2] = matrix.matrix[2][3]; + + gunangle[0] = -matrix.matrix[0][0]; + gunangle[1] = -matrix.matrix[1][0]; + gunangle[2] = -matrix.matrix[2][0]; + + trap_FX_PlayEffectID(cgs.effects.mEmplacedMuzzleFlash, gunpoint, gunangle, -1, -1); + } + else if (cent->currentState.weapon != WP_EMPLACED_GUN || cent->currentState.eType == ET_NPC) + { + if (cent->currentState.eType == ET_NPC && + cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle) + { //vehicles do nothing for clientside weapon fire events.. at least for now. + break; + } + CG_FireWeapon( cent, qfalse ); + } + break; + + case EV_ALT_FIRE: + DEBUGNAME("EV_ALT_FIRE"); + + if (cent->currentState.weapon == WP_EMPLACED_GUN) + { //don't do anything for emplaced stuff + break; + } + + if (cent->currentState.eType == ET_NPC && + cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle) + { //vehicles do nothing for clientside weapon fire events.. at least for now. + break; + } + + CG_FireWeapon( cent, qtrue ); + + //if you just exploded your detpacks and you have no ammo left for them, autoswitch + if ( cg->snap->ps.clientNum == cent->currentState.number && + cg->snap->ps.weapon == WP_DET_PACK ) + { + if (cg->snap->ps.ammo[weaponData[WP_DET_PACK].ammoIndex] == 0) + { + CG_OutOfAmmoChange(WP_DET_PACK); + } + } + + break; + + case EV_SABER_ATTACK: + DEBUGNAME("EV_SABER_ATTACK"); + { + clientInfo_t *client = &cgs.clientinfo[es->number]; + qhandle_t swingSound = trap_S_RegisterSound(va("sound/weapons/saber/saberhup%i.wav", Q_irand(1, 8))); + if ( client && client->infoValid && client->saber[0].swingSound[0] ) + {//custom swing sound + swingSound = client->saber[0].swingSound[Q_irand(0,2)]; + } + trap_S_StartSound(es->pos.trBase, es->number, CHAN_WEAPON, swingSound ); + } + break; + + case EV_SABER_HIT: + DEBUGNAME("EV_SABER_HIT"); + { + int hitPersonFxID = cgs.effects.mSaberBloodSparks; + int hitPersonSmallFxID = cgs.effects.mSaberBloodSparksSmall; + int hitPersonMidFxID = cgs.effects.mSaberBloodSparksMid; + int hitOtherFxID = cgs.effects.mSaberCut; + int hitSound = trap_S_RegisterSound(va("sound/weapons/saber/saberhit%i.wav", Q_irand(1, 3))); + + if ( es->otherEntityNum2 >= 0 + && es->otherEntityNum2 < MAX_CLIENTS ) + {//we have a specific person who is causing this effect, see if we should override it with any custom saber effects/sounds + clientInfo_t *client = &cgs.clientinfo[es->otherEntityNum2]; + if ( client && client->infoValid ) + { + if ( client->saber[0].hitPersonEffect ) + {//custom hit person effect + hitPersonFxID = hitPersonSmallFxID = hitPersonMidFxID = client->saber[0].hitPersonEffect; + } + if ( client->saber[0].hitOtherEffect ) + {//custom hit other effect + hitOtherFxID = client->saber[0].hitOtherEffect; + } + if ( client->saber[0].hitSound[0] ) + {//custom hit sound + hitSound = client->saber[0].hitSound[Q_irand(0,2)]; + } + } + } + + if (es->eventParm == 16) + { //Make lots of sparks, something special happened + vec3_t fxDir; + VectorCopy(es->angles, fxDir); + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + trap_S_StartSound(es->origin, es->number, CHAN_AUTO, hitSound ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + + if(es->otherEntityNum2 == clc->clientNum) + FF_Play(fffx_Laser2); + } + else if (es->eventParm) + { //hit a person + vec3_t fxDir; + VectorCopy(es->angles, fxDir); + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + trap_S_StartSound(es->origin, es->number, CHAN_AUTO, hitSound ); + if ( es->eventParm == 3 ) + { // moderate or big hits. + trap_FX_PlayEffectID( hitPersonSmallFxID, es->origin, fxDir, -1, -1 ); + } + else if ( es->eventParm == 2 ) + { // this is for really big hits. + trap_FX_PlayEffectID( hitPersonMidFxID, es->origin, fxDir, -1, -1 ); + } + else + { // this should really just be done in the effect itself, no? + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + } + + if(es->otherEntityNum2 == clc->clientNum) + FF_Play(fffx_Laser1); + } + else + { //hit something else + vec3_t fxDir; + VectorCopy(es->angles, fxDir); + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + //old jk2mp method + /* + trap_S_StartSound(es->origin, es->number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/saber/saberhit.wav")); + trap_FX_PlayEffectID( trap_FX_RegisterEffect("saber/spark.efx"), es->origin, fxDir, -1, -1 ); + */ + + if(es->otherEntityNum2 == clc->clientNum) + FF_Play(fffx_Laser1); + + trap_FX_PlayEffectID( hitOtherFxID, es->origin, fxDir, -1, -1 ); + } + + //rww - this means we have the number of the ent being hit and the ent that owns the saber doing + //the hit. This being the case, we can store these indecies and the current time in order to do + //some visual tricks on the client between frames to make it look like we're actually continuing + //to hit between server frames. + if (es->otherEntityNum != ENTITYNUM_NONE && es->otherEntityNum2 != ENTITYNUM_NONE) + { + centity_t *saberOwner; + + saberOwner = &cg_entities[es->otherEntityNum2]; + + saberOwner->serverSaberHitIndex = es->otherEntityNum; + saberOwner->serverSaberHitTime = cg->time; + + if (es->eventParm) + { + saberOwner->serverSaberFleshImpact = qtrue; + } + else + { + saberOwner->serverSaberFleshImpact = qfalse; + } + } + } + break; + + case EV_SABER_BLOCK: + DEBUGNAME("EV_SABER_BLOCK"); + { + if (es->eventParm) + { //saber block + qboolean cullPass = qfalse; + int blockFXID = cgs.effects.mSaberBlock; + qhandle_t blockSound = trap_S_RegisterSound(va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) )); + qboolean noFlare = qfalse; + + if ( es->otherEntityNum2 >= 0 + && es->otherEntityNum2 < MAX_CLIENTS ) + {//we have a specific person who is causing this effect, see if we should override it with any custom saber effects/sounds + clientInfo_t *client = &cgs.clientinfo[es->otherEntityNum2]; + if ( client && client->infoValid ) + { + if ( client->saber[0].blockEffect ) + {//custom saber block effect + blockFXID = client->saber[0].blockEffect; + } + if ( client->saber[0].blockSound[0] ) + {//custom hit sound + blockSound = client->saber[0].blockSound[Q_irand(0,2)]; + } + if ( client->saber[0].noClashFlare ) + { + noFlare = qtrue; + } + } + } + if (cg->mInRMG) + { + trace_t tr; + vec3_t vecSub; + + VectorSubtract(cg->refdef.vieworg, es->origin, vecSub); + + if (VectorLength(vecSub) < 5000) + { + CG_Trace(&tr, cg->refdef.vieworg, NULL, NULL, es->origin, ENTITYNUM_NONE, CONTENTS_TERRAIN|CONTENTS_SOLID); + + if (tr.fraction == 1.0 || tr.entityNum < MAX_CLIENTS) + { + cullPass = qtrue; + } + } + } + else + { + cullPass = qtrue; + } + + if (cullPass) + { + vec3_t fxDir; + + VectorCopy(es->angles, fxDir); + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + trap_S_StartSound(es->origin, es->number, CHAN_AUTO, blockSound ); + trap_FX_PlayEffectID( blockFXID, es->origin, fxDir, -1, -1 ); + + if ( !noFlare ) + { + cg_saberFlashTime = cg->time-50; + VectorCopy( es->origin, cg_saberFlashPos ); + } + } + } + else + { //projectile block + vec3_t fxDir; + VectorCopy(es->angles, fxDir); + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + trap_FX_PlayEffectID(cgs.effects.mBlasterDeflect, es->origin, fxDir, -1, -1); + } + } + break; + + case EV_SABER_CLASHFLARE: + DEBUGNAME("EV_SABER_CLASHFLARE"); + { + qboolean cullPass = qfalse; + + if (cg->mInRMG) + { + trace_t tr; + vec3_t vecSub; + + VectorSubtract(cg->refdef.vieworg, es->origin, vecSub); + + if (VectorLength(vecSub) < 5000) + { + CG_Trace(&tr, cg->refdef.vieworg, NULL, NULL, es->origin, ENTITYNUM_NONE, CONTENTS_TERRAIN|CONTENTS_SOLID); + + if (tr.fraction == 1.0 || tr.entityNum < MAX_CLIENTS) + { + cullPass = qtrue; + } + } + } + else + { + cullPass = qtrue; + } + + if (cullPass) + { + cg_saberFlashTime = cg->time-50; + VectorCopy( es->origin, cg_saberFlashPos ); + } + trap_S_StartSound ( es->origin, -1, CHAN_WEAPON, trap_S_RegisterSound( va("sound/weapons/saber/saberhitwall%i", Q_irand(1, 3)) ) ); + } + break; + + case EV_SABER_UNHOLSTER: + DEBUGNAME("EV_SABER_UNHOLSTER"); + { + clientInfo_t *ci = NULL; + + if (es->eType == ET_NPC) + { + ci = cg_entities[es->number].npcClient; + } + else if (es->number < MAX_CLIENTS) + { + ci = &cgs.clientinfo[es->number]; + } + + if (ci) + { + if (ci->saber[0].soundOn) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, ci->saber[0].soundOn ); + } + if (ci->saber[1].soundOn) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, ci->saber[1].soundOn ); + } + } + } + break; + + case EV_BECOME_JEDIMASTER: + DEBUGNAME("EV_SABER_UNHOLSTER"); + { + trace_t tr; + vec3_t playerMins = {-15, -15, DEFAULT_MINS_2+8}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + vec3_t ang, pos, dpos; + + VectorClear(ang); + ang[ROLL] = 1; + + VectorCopy(position, dpos); + dpos[2] -= 4096; + + CG_Trace(&tr, position, playerMins, playerMaxs, dpos, es->number, MASK_SOLID); + VectorCopy(tr.endpos, pos); + + if (tr.fraction == 1) + { + break; + } + trap_FX_PlayEffectID(cgs.effects.mJediSpawn, pos, ang, -1, -1); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" ) ); + + if (cg->snap->ps.clientNum == es->number) + { + trap_S_StartLocalSound(cgs.media.happyMusic, CHAN_LOCAL); + CGCam_SetMusicMult(0.3, 5000); + } + } + break; + + case EV_DISRUPTOR_MAIN_SHOT: + DEBUGNAME("EV_DISRUPTOR_MAIN_SHOT"); + if (cent->currentState.eventParm != cg->snap->ps.clientNum || + cg->renderingThirdPerson) + { //h4q3ry + CG_GetClientWeaponMuzzleBoltPoint(cent->currentState.eventParm, cent->currentState.origin2); + } + else + { + if (cg->lastFPFlashPoint[0] ||cg->lastFPFlashPoint[1] || cg->lastFPFlashPoint[2]) + { //get the position of the muzzle flash for the first person weapon model from the last frame + VectorCopy(cg->lastFPFlashPoint, cent->currentState.origin2); + } + } + FX_DisruptorMainShot( cent->currentState.origin2, cent->lerpOrigin ); + break; + + case EV_DISRUPTOR_SNIPER_SHOT: + DEBUGNAME("EV_DISRUPTOR_SNIPER_SHOT"); + if (cent->currentState.eventParm != cg->snap->ps.clientNum || + cg->renderingThirdPerson) + { //h4q3ry + CG_GetClientWeaponMuzzleBoltPoint(cent->currentState.eventParm, cent->currentState.origin2); + } + else + { + if (cg->lastFPFlashPoint[0] ||cg->lastFPFlashPoint[1] || cg->lastFPFlashPoint[2]) + { //get the position of the muzzle flash for the first person weapon model from the last frame + VectorCopy(cg->lastFPFlashPoint, cent->currentState.origin2); + } + } + FX_DisruptorAltShot( cent->currentState.origin2, cent->lerpOrigin, cent->currentState.shouldtarget ); + break; + + case EV_DISRUPTOR_SNIPER_MISS: + DEBUGNAME("EV_DISRUPTOR_SNIPER_MISS"); + ByteToDir( es->eventParm, dir ); + if (es->weapon) + { //primary + FX_DisruptorHitWall( cent->lerpOrigin, dir ); + } + else + { //secondary + FX_DisruptorAltMiss( cent->lerpOrigin, dir ); + } + break; + + case EV_DISRUPTOR_HIT: + DEBUGNAME("EV_DISRUPTOR_HIT"); + ByteToDir( es->eventParm, dir ); + if (es->weapon) + { //client + FX_DisruptorHitPlayer( cent->lerpOrigin, dir, qtrue ); + } + else + { //non-client + FX_DisruptorHitWall( cent->lerpOrigin, dir ); + } + break; + + case EV_DISRUPTOR_ZOOMSOUND: + DEBUGNAME("EV_DISRUPTOR_ZOOMSOUND"); + if (es->number == cg->snap->ps.clientNum) + { + if (cg->snap->ps.zoomMode) + { + trap_S_StartLocalSound(trap_S_RegisterSound("sound/weapons/disruptor/zoomstart.wav"), CHAN_AUTO); + } + else + { + trap_S_StartLocalSound(trap_S_RegisterSound("sound/weapons/disruptor/zoomend.wav"), CHAN_AUTO); + } + } + break; + case EV_PREDEFSOUND: + DEBUGNAME("EV_PREDEFSOUND"); + { + int sID = -1; + + switch (es->eventParm) + { + case PDSOUND_PROTECTHIT: + sID = trap_S_RegisterSound("sound/weapons/force/protecthit.mp3"); + break; + case PDSOUND_PROTECT: + sID = trap_S_RegisterSound("sound/weapons/force/protect.mp3"); + break; + case PDSOUND_ABSORBHIT: + sID = trap_S_RegisterSound("sound/weapons/force/absorbhit.mp3"); + if (es->trickedentindex >= 0 && es->trickedentindex < MAX_CLIENTS) + { + int clnum = es->trickedentindex; + + cg_entities[clnum].teamPowerEffectTime = cg->time + 1000; + cg_entities[clnum].teamPowerType = 3; + } + break; + case PDSOUND_ABSORB: + sID = trap_S_RegisterSound("sound/weapons/force/absorb.mp3"); + break; + case PDSOUND_FORCEJUMP: + sID = trap_S_RegisterSound("sound/weapons/force/jump.mp3"); + break; + case PDSOUND_FORCEGRIP: + sID = trap_S_RegisterSound("sound/weapons/force/grip.mp3"); + break; + default: + break; + } + + if (sID != 1) + { + trap_S_StartSound(es->origin, es->number, CHAN_AUTO, sID); + } + } + break; + + case EV_TEAM_POWER: + DEBUGNAME("EV_TEAM_POWER"); + { + int clnum = 0; + + while (clnum < MAX_CLIENTS) + { + if (CG_InClientBitflags(es, clnum)) + { + if (es->eventParm == 1) + { //eventParm 1 is heal + trap_S_StartSound (NULL, clnum, CHAN_AUTO, cgs.media.teamHealSound ); + cg_entities[clnum].teamPowerEffectTime = cg->time + 1000; + cg_entities[clnum].teamPowerType = 1; + } + else + { //eventParm 2 is force regen + trap_S_StartSound (NULL, clnum, CHAN_AUTO, cgs.media.teamRegenSound ); + cg_entities[clnum].teamPowerEffectTime = cg->time + 1000; + cg_entities[clnum].teamPowerType = 0; + } + } + clnum++; + } + } + break; + + case EV_SCREENSHAKE: + DEBUGNAME("EV_SCREENSHAKE"); + if (!es->modelindex || cg->predictedPlayerState.clientNum == es->modelindex-1) + { + CGCam_Shake(es->angles[0], es->time); + } + break; + case EV_LOCALTIMER: + DEBUGNAME("EV_LOCALTIMER"); + if (es->owner == cg->predictedPlayerState.clientNum) + { + CG_LocalTimingBar(es->time, es->time2); + } + break; + case EV_USE_ITEM0: + DEBUGNAME("EV_USE_ITEM0"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM1: + DEBUGNAME("EV_USE_ITEM1"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM2: + DEBUGNAME("EV_USE_ITEM2"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM3: + DEBUGNAME("EV_USE_ITEM3"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM4: + DEBUGNAME("EV_USE_ITEM4"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM5: + DEBUGNAME("EV_USE_ITEM5"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM6: + DEBUGNAME("EV_USE_ITEM6"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM7: + DEBUGNAME("EV_USE_ITEM7"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM8: + DEBUGNAME("EV_USE_ITEM8"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM9: + DEBUGNAME("EV_USE_ITEM9"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM10: + DEBUGNAME("EV_USE_ITEM10"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM11: + DEBUGNAME("EV_USE_ITEM11"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM12: + DEBUGNAME("EV_USE_ITEM12"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM13: + DEBUGNAME("EV_USE_ITEM13"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM14: + DEBUGNAME("EV_USE_ITEM14"); + CG_UseItem( cent ); + break; + + case EV_ITEMUSEFAIL: + DEBUGNAME("EV_ITEMUSEFAIL"); + if (cg->snap->ps.clientNum == es->number) + { + char *psStringEDRef = NULL; + + switch(es->eventParm) + { + case SENTRY_NOROOM: + psStringEDRef = (char *)CG_GetStringEdString("MP_INGAME", "SENTRY_NOROOM"); + break; + case SENTRY_ALREADYPLACED: + psStringEDRef = (char *)CG_GetStringEdString("MP_INGAME", "SENTRY_ALREADYPLACED"); + break; + case SHIELD_NOROOM: + psStringEDRef = (char *)CG_GetStringEdString("MP_INGAME", "SHIELD_NOROOM"); + break; + case SEEKER_ALREADYDEPLOYED: + psStringEDRef = (char *)CG_GetStringEdString("MP_INGAME", "SEEKER_ALREADYDEPLOYED"); + break; + default: + break; + } + + if (!psStringEDRef) + { + break; + } + + Com_Printf("%s\n", psStringEDRef); + } + break; + + //================================================================= + + // + // other events + // + case EV_PLAYER_TELEPORT_IN: + DEBUGNAME("EV_PLAYER_TELEPORT_IN"); + { + trace_t tr; + vec3_t playerMins = {-15, -15, DEFAULT_MINS_2+8}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + vec3_t ang, pos, dpos; + + VectorClear(ang); + ang[ROLL] = 1; + + VectorCopy(position, dpos); + dpos[2] -= 4096; + + CG_Trace(&tr, position, playerMins, playerMaxs, dpos, es->number, MASK_SOLID); + VectorCopy(tr.endpos, pos); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); + + if (tr.fraction == 1) + { + break; + } + trap_FX_PlayEffectID(cgs.effects.mSpawn, pos, ang, -1, -1); + } + break; + + case EV_PLAYER_TELEPORT_OUT: + DEBUGNAME("EV_PLAYER_TELEPORT_OUT"); + { + trace_t tr; + vec3_t playerMins = {-15, -15, DEFAULT_MINS_2+8}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + vec3_t ang, pos, dpos; + + VectorClear(ang); + ang[ROLL] = 1; + + VectorCopy(position, dpos); + dpos[2] -= 4096; + + CG_Trace(&tr, position, playerMins, playerMaxs, dpos, es->number, MASK_SOLID); + VectorCopy(tr.endpos, pos); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); + + if (tr.fraction == 1) + { + break; + } + trap_FX_PlayEffectID(cgs.effects.mSpawn, pos, ang, -1, -1); + } + break; + + case EV_ITEM_POP: + DEBUGNAME("EV_ITEM_POP"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + case EV_ITEM_RESPAWN: + DEBUGNAME("EV_ITEM_RESPAWN"); + cent->miscTime = cg->time; // scale up from this + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + + case EV_GRENADE_BOUNCE: + DEBUGNAME("EV_GRENADE_BOUNCE"); + //Do something here? + break; + + case EV_SCOREPLUM: + DEBUGNAME("EV_SCOREPLUM"); + CG_ScorePlum( cent->currentState.otherEntityNum, cent->lerpOrigin, cent->currentState.time ); + break; + + case EV_CTFMESSAGE: + DEBUGNAME("EV_CTFMESSAGE"); + CG_GetCTFMessageEvent(es); + break; + + case EV_BODYFADE: + if (es->eType != ET_BODY) + { + assert(!"EV_BODYFADE event from a non-corpse"); + break; + } + + //If no room for body model, don't do the fade. + if (cent->ghoul2 && CG_ModelAllowed(cent->ghoul2) && + trap_G2_HaveWeGhoul2Models(cent->ghoul2)) + { + //turn the inside of the face off, to avoid showing the mouth when we start alpha fading the corpse + trap_G2API_SetSurfaceOnOff( cent->ghoul2, "head_eyes_mouth", 0x00000002/*G2SURFACEFLAG_OFF*/ ); + } + + cent->bodyFadeTime = cg->time + 60000; + break; + + // + // siege gameplay events + // + case EV_SIEGE_ROUNDOVER: + DEBUGNAME("EV_SIEGE_ROUNDOVER"); + CG_SiegeRoundOver(&cg_entities[cent->currentState.weapon], cent->currentState.eventParm); + break; + case EV_SIEGE_OBJECTIVECOMPLETE: + DEBUGNAME("EV_SIEGE_OBJECTIVECOMPLETE"); + CG_SiegeObjectiveCompleted(&cg_entities[cent->currentState.weapon], cent->currentState.eventParm, cent->currentState.trickedentindex); + break; + + case EV_DESTROY_GHOUL2_INSTANCE: + DEBUGNAME("EV_DESTROY_GHOUL2_INSTANCE"); + if (cg_entities[es->eventParm].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[es->eventParm].ghoul2)) + { + if (es->eventParm < MAX_CLIENTS) + { //You try to do very bad thing! +#ifdef _DEBUG + Com_Printf("WARNING: Tried to kill a client ghoul2 instance with a server event!\n"); +#endif + break; + } + trap_G2API_CleanGhoul2Models(&(cg_entities[es->eventParm].ghoul2)); + } + break; + + case EV_DESTROY_WEAPON_MODEL: + DEBUGNAME("EV_DESTROY_WEAPON_MODEL"); + if (cg_entities[es->eventParm].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[es->eventParm].ghoul2) && + trap_G2API_HasGhoul2ModelOnIndex(&(cg_entities[es->eventParm].ghoul2), 1)) + { + trap_G2API_RemoveGhoul2Model(&(cg_entities[es->eventParm].ghoul2), 1); + } + break; + + case EV_GIVE_NEW_RANK: + DEBUGNAME("EV_GIVE_NEW_RANK"); + if (es->trickedentindex == cg->snap->ps.clientNum) + { + trap_Cvar_Set("ui_rankChange", va("%i", es->eventParm)); + + ClientManager::ActiveClient().myTeam = es->bolt2; + + if (!( trap_Key_GetCatcher() & KEYCATCH_UI ) && !es->bolt1) + { + trap_OpenUIMenu(UIMENU_PLAYERCONFIG); + } + } + break; + + case EV_SET_FREE_SABER: + DEBUGNAME("EV_SET_FREE_SABER"); + + trap_Cvar_Set("ui_freeSaber", va("%i", es->eventParm)); + break; + + case EV_SET_FORCE_DISABLE: + DEBUGNAME("EV_SET_FORCE_DISABLE"); + + trap_Cvar_Set("ui_forcePowerDisable", va("%i", es->eventParm)); + break; + + // + // missile impacts + // + case EV_CONC_ALT_IMPACT: + DEBUGNAME("EV_CONC_ALT_IMPACT"); + { + float dist; + float shotDist = VectorNormalize(es->angles); + vec3_t spot; + + for (dist = 0.0f; dist < shotDist; dist += 64.0f) + { //one effect would be.. a whole lot better + VectorMA( es->origin2, dist, es->angles, spot ); + trap_FX_PlayEffectID(cgs.effects.mConcussionAltRing, spot, es->angles2, -1, -1); + } + + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall(WP_CONCUSSION, es->owner, position, dir, IMPACTSOUND_DEFAULT, qtrue, 0); + + FX_ConcAltShot(es->origin2, spot); + + //steal the bezier effect from the disruptor + FX_DisruptorAltMiss(position, dir); + } + break; + + case EV_MISSILE_STICK: + DEBUGNAME("EV_MISSILE_STICK"); +// trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.missileStick ); + break; + + case EV_MISSILE_HIT: + DEBUGNAME("EV_MISSILE_HIT"); + ByteToDir( es->eventParm, dir ); + if ( es->emplacedOwner ) + {//hack: this is an index to a custom effect to use + trap_FX_PlayEffectID(cgs.gameEffects[es->emplacedOwner], position, dir, -1, -1); + } + else if ( CG_VehicleWeaponImpact( cent ) ) + {//a vehicle missile that uses an overridden impact effect... + } + else if (cent->currentState.eFlags & EF_ALT_FIRING) + { + CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum, qtrue); + } + else + { + CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum, qfalse); + } + + if (cg_ghoul2Marks.integer && + es->trickedentindex) + { //flag to place a ghoul2 mark + CG_G2MarkEvent(es); + } + break; + + case EV_MISSILE_MISS: + DEBUGNAME("EV_MISSILE_MISS"); + ByteToDir( es->eventParm, dir ); + if ( es->emplacedOwner ) + {//hack: this is an index to a custom effect to use + trap_FX_PlayEffectID(cgs.gameEffects[es->emplacedOwner], position, dir, -1, -1); + } + else if ( CG_VehicleWeaponImpact( cent ) ) + {//a vehicle missile that used an overridden impact effect... + } + else if (cent->currentState.eFlags & EF_ALT_FIRING) + { + CG_MissileHitWall(es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT, qtrue, es->generic1); + } + else + { + CG_MissileHitWall(es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT, qfalse, 0); + } + + if (cg_ghoul2Marks.integer && + es->trickedentindex) + { //flag to place a ghoul2 mark + CG_G2MarkEvent(es); + } + break; + + case EV_MISSILE_MISS_METAL: + DEBUGNAME("EV_MISSILE_MISS_METAL"); + ByteToDir( es->eventParm, dir ); + if ( es->emplacedOwner ) + {//hack: this is an index to a custom effect to use + trap_FX_PlayEffectID(cgs.gameEffects[es->emplacedOwner], position, dir, -1, -1); + } + else if ( CG_VehicleWeaponImpact( cent ) ) + {//a vehicle missile that used an overridden impact effect... + } + else if (cent->currentState.eFlags & EF_ALT_FIRING) + { + CG_MissileHitWall(es->weapon, 0, position, dir, IMPACTSOUND_METAL, qtrue, es->generic1); + } + else + { + CG_MissileHitWall(es->weapon, 0, position, dir, IMPACTSOUND_METAL, qfalse, 0); + } + break; + + case EV_PLAY_EFFECT: + DEBUGNAME("EV_PLAY_EFFECT"); + switch(es->eventParm) + { //it isn't a hack, it's ingenuity! + case EFFECT_SMOKE: + eID = cgs.effects.mEmplacedDeadSmoke; + break; + case EFFECT_EXPLOSION: + eID = cgs.effects.mEmplacedExplode; + break; + case EFFECT_EXPLOSION_PAS: + eID = cgs.effects.mTurretExplode; + break; + case EFFECT_SPARK_EXPLOSION: + eID = cgs.effects.mSparkExplosion; + break; + case EFFECT_EXPLOSION_TRIPMINE: + eID = cgs.effects.mTripmineExplosion; + break; + case EFFECT_EXPLOSION_DETPACK: + eID = cgs.effects.mDetpackExplosion; + break; + case EFFECT_EXPLOSION_FLECHETTE: + eID = cgs.effects.mFlechetteAltBlow; + break; + case EFFECT_STUNHIT: + eID = cgs.effects.mStunBatonFleshImpact; + break; + case EFFECT_EXPLOSION_DEMP2ALT: + FX_DEMP2_AltDetonate( cent->lerpOrigin, es->weapon ); + eID = cgs.effects.mAltDetonate; + break; + case EFFECT_EXPLOSION_TURRET: + eID = cgs.effects.mTurretExplode; + break; + case EFFECT_SPARKS: + eID = cgs.effects.mSparksExplodeNoSound; + break; + case EFFECT_WATER_SPLASH: + eID = cgs.effects.waterSplash; + break; + case EFFECT_ACID_SPLASH: + eID = cgs.effects.acidSplash; + break; + case EFFECT_LAVA_SPLASH: + eID = cgs.effects.lavaSplash; + break; + case EFFECT_LANDING_MUD: + eID = cgs.effects.landingMud; + break; + case EFFECT_LANDING_SAND: + eID = cgs.effects.landingSand; + break; + case EFFECT_LANDING_DIRT: + eID = cgs.effects.landingDirt; + break; + case EFFECT_LANDING_SNOW: + eID = cgs.effects.landingSnow; + break; + case EFFECT_LANDING_GRAVEL: + eID = cgs.effects.landingGravel; + break; + default: + eID = -1; + break; + } + + if (eID != -1) + { + vec3_t fxDir; + + VectorCopy(es->angles, fxDir); + + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + + trap_FX_PlayEffectID(eID, es->origin, fxDir, -1, -1); + } + break; + + case EV_PLAY_EFFECT_ID: + case EV_PLAY_PORTAL_EFFECT_ID: + DEBUGNAME("EV_PLAY_EFFECT_ID"); + { + vec3_t fxDir; + qboolean portalEffect = qfalse; + int efxIndex = 0; + + if (event == EV_PLAY_PORTAL_EFFECT_ID) + { //This effect should only be played inside sky portals. + portalEffect = qtrue; + } + + AngleVectors(es->angles, fxDir, 0, 0); + + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + + if ( cgs.gameEffects[ es->eventParm ] ) + { + efxIndex = cgs.gameEffects[es->eventParm]; + } + else + { + s = CG_ConfigString( CS_EFFECTS + es->eventParm ); + if (s && s[0]) + { + efxIndex = trap_FX_RegisterEffect(s); + } + } + + if (efxIndex) + { + if (portalEffect) + { + trap_FX_PlayPortalEffectID(efxIndex, position, fxDir, -1, -1 ); + } + else + { + trap_FX_PlayEffectID(efxIndex, position, fxDir, -1, -1 ); + } + } + } + break; + + case EV_PLAYDOORSOUND: + CG_PlayDoorSound(cent, es->eventParm); + break; + case EV_PLAYDOORLOOPSOUND: + CG_PlayDoorLoopSound(cent); + break; + case EV_BMODEL_SOUND: + DEBUGNAME("EV_BMODEL_SOUND"); + { + sfxHandle_t sfx; + const char *soundSet; + + soundSet = CG_ConfigString( CS_AMBIENT_SET + es->soundSetIndex ); + + if (!soundSet || !soundSet[0]) + { + break; + } + + sfx = trap_AS_GetBModelSound(soundSet, es->eventParm); + + if (sfx == -1) + { + break; + } + + trap_S_StartSound( NULL, es->number, CHAN_AUTO, sfx ); + } + break; + + + case EV_MUTE_SOUND: + DEBUGNAME("EV_MUTE_SOUND"); + if (cg_entities[es->trickedentindex2].currentState.eFlags & EF_SOUNDTRACKER) + { + cg_entities[es->trickedentindex2].currentState.eFlags -= EF_SOUNDTRACKER; + } + trap_S_MuteSound(es->trickedentindex2, es->trickedentindex); + CG_S_StopLoopingSound(es->trickedentindex2, -1); + break; + + case EV_VOICECMD_SOUND: + DEBUGNAME("EV_VOICECMD_SOUND"); + if (es->groundEntityNum >= MAX_CLIENTS) + { //don't ever use this unless it is being used on a real client + break; + } + { + sfxHandle_t sfx = cgs.gameSounds[ es->eventParm ]; + clientInfo_t *ci = &cgs.clientinfo[es->groundEntityNum]; + centity_t *vChatEnt = &cg_entities[es->groundEntityNum]; + char descr[1024]; + + strcpy(descr, CG_GetStringForVoiceSound(CG_ConfigString( CS_SOUNDS + es->eventParm ))); + + if (!sfx) + { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + sfx = CG_CustomSound( es->groundEntityNum, s ); + } + + if (sfx) + { + if (es->groundEntityNum != cg->predictedPlayerState.clientNum) + { //play on the head as well to simulate hearing in radio and in world + if (ci->team == cg->predictedPlayerState.persistant[PERS_TEAM]) + { //don't hear it if this person is on the other team, but they can still + //hear it in the world spot. + trap_S_StartSound (NULL, cg->snap->ps.clientNum, CHAN_MENU1, sfx); + } + } + if (ci->team == cg->predictedPlayerState.persistant[PERS_TEAM]) + { //add to the chat box + //hear it in the world spot. + char vchatstr[1024]; + strcpy(vchatstr, va("<%s: %s>\n", ci->name, descr)); + CG_Printf(vchatstr); + CG_ChatBox_AddString(vchatstr); + } + + //and play in world for everyone + trap_S_StartSound (NULL, es->groundEntityNum, CHAN_VOICE, sfx); + vChatEnt->vChatTime = cg->time + 1000; + } + } + break; + + case EV_GENERAL_SOUND: + DEBUGNAME("EV_GENERAL_SOUND"); + if (es->saberEntityNum == TRACK_CHANNEL_2 || es->saberEntityNum == TRACK_CHANNEL_3 || + es->saberEntityNum == TRACK_CHANNEL_5) + { //channels 2 and 3 are for speed and rage, 5 for sight + if ( cgs.gameSounds[ es->eventParm ] ) + { + CG_S_AddRealLoopingSound(es->number, es->pos.trBase, vec3_origin, cgs.gameSounds[ es->eventParm ] ); + } + } + else + { + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, es->number, es->saberEntityNum, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, es->number, es->saberEntityNum, CG_CustomSound( es->number, s ) ); + } + } + break; + + case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes + DEBUGNAME("EV_GLOBAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, cg->snap->ps.clientNum, CHAN_MENU1, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, cg->snap->ps.clientNum, CHAN_MENU1, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_TEAM_SOUND: // play from the player's head so it never diminishes + { + DEBUGNAME("EV_GLOBAL_TEAM_SOUND"); + switch( es->eventParm ) { + case GTS_RED_CAPTURE: // CTF: red team captured the blue flag, 1FCTF: red team captured the neutral flag + //CG_AddBufferedSound( cgs.media.redScoredSound ); + break; + case GTS_BLUE_CAPTURE: // CTF: blue team captured the red flag, 1FCTF: blue team captured the neutral flag + //CG_AddBufferedSound( cgs.media.blueScoredSound ); + break; + case GTS_RED_RETURN: // CTF: blue flag returned, 1FCTF: never used + if (cgs.gametype == GT_CTY) + { + CG_AddBufferedSound( cgs.media.blueYsalReturnedSound ); + } + else + { + CG_AddBufferedSound( cgs.media.blueFlagReturnedSound ); + } + break; + case GTS_BLUE_RETURN: // CTF red flag returned, 1FCTF: neutral flag returned + if (cgs.gametype == GT_CTY) + { + CG_AddBufferedSound( cgs.media.redYsalReturnedSound ); + } + else + { + CG_AddBufferedSound( cgs.media.redFlagReturnedSound ); + } + break; + + case GTS_RED_TAKEN: // CTF: red team took blue flag, 1FCTF: blue team took the neutral flag + // if this player picked up the flag then a sound is played in CG_CheckLocalSounds + if (cgs.gametype == GT_CTY) + { + CG_AddBufferedSound( cgs.media.redTookYsalSound ); + } + else + { + CG_AddBufferedSound( cgs.media.redTookFlagSound ); + } + break; + case GTS_BLUE_TAKEN: // CTF: blue team took the red flag, 1FCTF red team took the neutral flag + // if this player picked up the flag then a sound is played in CG_CheckLocalSounds + if (cgs.gametype == GT_CTY) + { + CG_AddBufferedSound( cgs.media.blueTookYsalSound ); + } + else + { + CG_AddBufferedSound( cgs.media.blueTookFlagSound ); + } + break; + case GTS_REDTEAM_SCORED: + CG_AddBufferedSound(cgs.media.redScoredSound); + break; + case GTS_BLUETEAM_SCORED: + CG_AddBufferedSound(cgs.media.blueScoredSound); + break; + case GTS_REDTEAM_TOOK_LEAD: + CG_AddBufferedSound(cgs.media.redLeadsSound); + break; + case GTS_BLUETEAM_TOOK_LEAD: + CG_AddBufferedSound(cgs.media.blueLeadsSound); + break; + case GTS_TEAMS_ARE_TIED: + CG_AddBufferedSound( cgs.media.teamsTiedSound ); + break; + default: + break; + } + break; + } + + case EV_ENTITY_SOUND: + DEBUGNAME("EV_ENTITY_SOUND"); + //somewhat of a hack - weapon is the caller entity's index, trickedentindex is the proper sound channel + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, es->clientNum, es->trickedentindex, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, es->clientNum, es->trickedentindex, CG_CustomSound( es->clientNum, s ) ); + } + break; + + case EV_PLAY_ROFF: + DEBUGNAME("EV_PLAY_ROFF"); + trap_ROFF_Play(es->weapon, es->eventParm, es->trickedentindex); + break; + + case EV_GLASS_SHATTER: + DEBUGNAME("EV_GLASS_SHATTER"); + CG_GlassShatter(es->genericenemyindex, es->origin, es->angles, es->trickedentindex, es->pos.trTime); + break; + + case EV_DEBRIS: + DEBUGNAME("EV_DEBRIS"); + CG_Chunks(es->owner, es->origin, es->angles, es->origin2, es->angles2, es->speed, + es->eventParm, es->trickedentindex, es->modelindex, es->apos.trBase[0]); + break; + + case EV_MISC_MODEL_EXP: + DEBUGNAME("EV_MISC_MODEL_EXP"); + CG_MiscModelExplosion(es->origin2, es->angles2, es->time, es->eventParm); + break; + + case EV_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + DEBUGNAME("EV_PAIN"); + + if ( !cg_oldPainSounds.integer || (cent->currentState.number != cg->snap->ps.clientNum) ) + { + CG_PainEvent( cent, es->eventParm ); + } + break; + + case EV_DEATH1: + case EV_DEATH2: + case EV_DEATH3: + DEBUGNAME("EV_DEATHx"); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, + CG_CustomSound( es->number, va("*death%i.wav", event - EV_DEATH1 + 1) ) ); + if (es->eventParm && es->number == cg->snap->ps.clientNum) + { + trap_S_StartLocalSound(cgs.media.dramaticFailure, CHAN_LOCAL); + CGCam_SetMusicMult(0.3, 5000); + } + break; + + + case EV_OBITUARY: + DEBUGNAME("EV_OBITUARY"); + CG_Obituary( es ); + break; + + // + // powerup events + // + case EV_POWERUP_QUAD: + DEBUGNAME("EV_POWERUP_QUAD"); + if ( es->number == cg->snap->ps.clientNum ) { + cg->powerupActive = PW_QUAD; + cg->powerupTime = cg->time; + } + //trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.quadSound ); + break; + case EV_POWERUP_BATTLESUIT: + DEBUGNAME("EV_POWERUP_BATTLESUIT"); + if ( es->number == cg->snap->ps.clientNum ) { + cg->powerupActive = PW_BATTLESUIT; + cg->powerupTime = cg->time; + } + //trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.protectSound ); + break; + + case EV_FORCE_DRAINED: + DEBUGNAME("EV_FORCE_DRAINED"); + ByteToDir( es->eventParm, dir ); + //FX_ForceDrained(position, dir); + trap_S_StartSound (NULL, es->owner, CHAN_AUTO, cgs.media.drainSound ); + cg_entities[es->owner].teamPowerEffectTime = cg->time + 1000; + cg_entities[es->owner].teamPowerType = 2; + break; + + case EV_GIB_PLAYER: + DEBUGNAME("EV_GIB_PLAYER"); + //trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); + //CG_GibPlayer( cent->lerpOrigin ); + break; + + case EV_STARTLOOPINGSOUND: + DEBUGNAME("EV_STARTLOOPINGSOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) + { + isnd = cgs.gameSounds[es->eventParm]; + } + else + { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + isnd = CG_CustomSound(es->number, s); + } + + CG_S_AddRealLoopingSound( es->number, es->pos.trBase, vec3_origin, isnd ); + es->loopSound = isnd; + break; + + case EV_STOPLOOPINGSOUND: + DEBUGNAME("EV_STOPLOOPINGSOUND"); + CG_S_StopLoopingSound( es->number, -1 ); + es->loopSound = 0; + break; + + case EV_WEAPON_CHARGE: + DEBUGNAME("EV_WEAPON_CHARGE"); + assert(es->eventParm > WP_NONE && es->eventParm < WP_NUM_WEAPONS); + if (cg_weapons[es->eventParm].chargeSound) + { + trap_S_StartSound(NULL, es->number, CHAN_WEAPON, cg_weapons[es->eventParm].chargeSound); + } + else if (es->eventParm == WP_DISRUPTOR) + { + trap_S_StartSound(NULL, es->number, CHAN_WEAPON, cgs.media.disruptorZoomLoop); + } + break; + + case EV_WEAPON_CHARGE_ALT: + DEBUGNAME("EV_WEAPON_CHARGE_ALT"); + assert(es->eventParm > WP_NONE && es->eventParm < WP_NUM_WEAPONS); + if (cg_weapons[es->eventParm].altChargeSound) + { + trap_S_StartSound(NULL, es->number, CHAN_WEAPON, cg_weapons[es->eventParm].altChargeSound); + } + break; + + case EV_SHIELD_HIT: + DEBUGNAME("EV_SHIELD_HIT"); + ByteToDir(es->eventParm, dir); + CG_PlayerShieldHit(es->otherEntityNum, dir, es->time2); + break; + + case EV_DEBUG_LINE: + DEBUGNAME("EV_DEBUG_LINE"); + CG_Beam( cent ); + break; + + case EV_TESTLINE: + DEBUGNAME("EV_TESTLINE"); + CG_TestLine(es->origin, es->origin2, es->time2, es->weapon, 1); + break; + + default: + DEBUGNAME("UNKNOWN"); + CG_Error( "Unknown event: %i", event ); + break; + } + +} + + +/* +============== +CG_CheckEvents + +============== +*/ +void CG_CheckEvents( centity_t *cent ) { +#ifdef _XBOX + // check for event-only entities + if ( cent->currentState.eType > ET_EVENTS ) { + if ( cent->previousEvent[ClientManager::ActiveClientNum()] ) { + return; // already fired + } + // if this is a player event set the entity number of the client entity number + if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) { + cent->currentState.number = cent->currentState.otherEntityNum; + } + + cent->previousEvent[ClientManager::ActiveClientNum()] = 1; + + cent->currentState.event = cent->currentState.eType - ET_EVENTS; + } else { + // check for events riding with another entity + if ( cent->currentState.event == cent->previousEvent[ClientManager::ActiveClientNum()] ) { + return; + } + cent->previousEvent[ClientManager::ActiveClientNum()] = cent->currentState.event; + if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) { + return; + } + } + +#else // _XBOX + + // check for event-only entities + if ( cent->currentState.eType > ET_EVENTS ) { + if ( cent->previousEvent ) { + return; // already fired + } + // if this is a player event set the entity number of the client entity number + if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) { + cent->currentState.number = cent->currentState.otherEntityNum; + } + + cent->previousEvent = 1; + + cent->currentState.event = cent->currentState.eType - ET_EVENTS; + } else { + // check for events riding with another entity + if ( cent->currentState.event == cent->previousEvent ) { + return; + } + cent->previousEvent = cent->currentState.event; + if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) { + return; + } + } + +#endif // _XBOX + + // calculate the position at exactly the frame time + BG_EvaluateTrajectory( ¢->currentState.pos, cg->snap->serverTime, cent->lerpOrigin ); + CG_SetEntitySoundPosition( cent ); + + CG_EntityEvent( cent, cent->lerpOrigin ); +} + diff --git a/codemp/cgame/cg_info.c b/codemp/cgame/cg_info.c new file mode 100644 index 0000000..abf1144 --- /dev/null +++ b/codemp/cgame/cg_info.c @@ -0,0 +1,412 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_info.c -- display information while data is being loading + +#include "cg_local.h" + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + +#define MAX_LOADING_PLAYER_ICONS 16 +#define MAX_LOADING_ITEM_ICONS 26 + +//static int loadingPlayerIconCount; +//static qhandle_t loadingPlayerIcons[MAX_LOADING_PLAYER_ICONS]; + +void CG_LoadBar(void); + +/* +====================== +CG_LoadingString + +====================== +*/ +void CG_LoadingString( const char *s ) { +#ifdef _XBOX + if (ClientManager::ActiveClientNum() == 1) + return; +#endif + + Q_strncpyz( cg->infoScreenText, s, sizeof( cg->infoScreenText ) ); + + trap_UpdateScreen(); +} + +/* +=================== +CG_LoadingItem +=================== +*/ +void CG_LoadingItem( int itemNum ) { + gitem_t *item; + char upperKey[1024]; + + item = &bg_itemlist[itemNum]; + + if (!item->classname || !item->classname[0]) + { + // CG_LoadingString( "Unknown item" ); + return; + } + + strcpy(upperKey, item->classname); + CG_LoadingString( CG_GetStringEdString("SP_INGAME",Q_strupr(upperKey)) ); +} + +/* +=================== +CG_LoadingClient +=================== +*/ +void CG_LoadingClient( int clientNum ) { + const char *info; + char personality[MAX_QPATH]; + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + +/* + char model[MAX_QPATH]; + char iconName[MAX_QPATH]; + char *skin; + if ( loadingPlayerIconCount < MAX_LOADING_PLAYER_ICONS ) { + Q_strncpyz( model, Info_ValueForKey( info, "model" ), sizeof( model ) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } else { + skin = "default"; + } + + Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", model, skin ); + + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { + Com_sprintf( iconName, MAX_QPATH, "models/players/characters/%s/icon_%s.tga", model, skin ); + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + } + if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { + Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", DEFAULT_MODEL, "default" ); + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + } + if ( loadingPlayerIcons[loadingPlayerIconCount] ) { + loadingPlayerIconCount++; + } + } +*/ + Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof(personality) ); + Q_CleanStr( personality ); + + /* + if( cgs.gametype == GT_SINGLE_PLAYER ) { + trap_S_RegisterSound( va( "sound/player/announce/%s.wav", personality )); + } + */ + + CG_LoadingString( personality ); +} + + +/* +==================== +CG_DrawInformation + +Draw all the status / pacifier stuff during level loading +==================== +overlays UI_DrawConnectScreen +*/ +#define UI_INFOFONT (UI_BIGFONT) +void CG_DrawInformation( void ) { + const char *s; + const char *info; + const char *sysInfo; + int y; + int value, valueNOFP; + qhandle_t levelshot; + char buf[1024]; + int iPropHeight = 18; // I know, this is total crap, but as a post release asian-hack.... -Ste + + info = CG_ConfigString( CS_SERVERINFO ); + sysInfo = CG_ConfigString( CS_SYSTEMINFO ); + + s = Info_ValueForKey( info, "mapname" ); + levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s", s ) ); + if ( !levelshot ) { + levelshot = trap_R_RegisterShaderNoMip( "menu/art/unknownmap_mp" ); + } + trap_R_SetColor( NULL ); + + // Levelshot in bottom-right frame + CG_DrawPic( 371, 279, 189, 141, levelshot ); + + switch( cgs.gametype ) + { + case GT_FFA: + levelshot = trap_R_RegisterShaderNoMip( "levelshots/mp_ffa" ); break; + case GT_DUEL: + levelshot = trap_R_RegisterShaderNoMip( "levelshots/mp_duel" ); break; + case GT_POWERDUEL: + levelshot = trap_R_RegisterShaderNoMip( "levelshots/mp_pduel" ); break; + case GT_TEAM: + levelshot = trap_R_RegisterShaderNoMip( "levelshots/mp_tffa" ); break; + case GT_SIEGE: + levelshot = trap_R_RegisterShaderNoMip( "levelshots/mp_siege" ); break; + case GT_CTF: + levelshot = trap_R_RegisterShaderNoMip( "levelshots/mp_ctf" ); break; + default: + levelshot = trap_R_RegisterShaderNoMip( "levelshots/mp_ffa" ); break; + } + + CG_DrawPic( 75, 279, 189, 141, levelshot ); + + CG_LoadBar(); + + // draw the icons of things as they are loaded +// CG_DrawLoadingIcons(); + + // the first 150 rows are reserved for the client connection + // screen to write into +// if ( cg->infoScreenText[0] ) { +// const char *psLoading = CG_GetStringEdString("MENUS", "LOADING_MAPNAME"); +// UI_DrawProportionalString( 320, 128-32, va(/*"Loading... %s"*/ psLoading, cg->infoScreenText), +// UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); +// } else { +// const char *psAwaitingSnapshot = CG_GetStringEdString("MENUS", "AWAITING_SNAPSHOT"); +// UI_DrawProportionalString( 320, 128-32, /*"Awaiting snapshot..."*/psAwaitingSnapshot, +// UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); +// } + + // draw info string information + + y = 60; + + // don't print server lines if playing a local game + trap_Cvar_VariableStringBuffer( "sv_running", buf, sizeof( buf ) ); +/* + if ( !atoi( buf ) ) { + // server hostname + Q_strncpyz(buf, Info_ValueForKey( info, "sv_hostname" ), 1024); + Q_CleanStr(buf); + UI_DrawProportionalString( 320, y, buf, + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + + // some extra space after hostname and motd + y += 10; + } +*/ + // Long map name + s = CG_ConfigString( CS_MESSAGE ); + if ( s[0] ) { + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + // Game type + switch ( cgs.gametype ) { + case GT_FFA: + s = CG_GetStringEdString("MENUS", "FREE_FOR_ALL"); break; + case GT_DUEL: + s = CG_GetStringEdString("MENUS", "DUEL"); break; + case GT_POWERDUEL: + s = CG_GetStringEdString("MENUS", "POWERDUEL"); break; + case GT_TEAM: + s = CG_GetStringEdString("MENUS", "TEAM_FFA"); break; + case GT_SIEGE: + s = CG_GetStringEdString("MENUS", "SIEGE"); break; + case GT_CTF: + s = CG_GetStringEdString("MENUS", "CAPTURE_THE_FLAG"); break; + default: + s = CG_GetStringEdString("MENUS", "FREE_FOR_ALL"); break; + } + UI_DrawProportionalString( 320, y, s, UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + + // Rules + if (cgs.gametype != GT_SIEGE) + { + value = atoi( Info_ValueForKey( info, "timelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "%s %i", CG_GetStringEdString("MP_INGAME", "TIMELIMIT"), value ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + if (cgs.gametype < GT_CTF ) { + value = atoi( Info_ValueForKey( info, "fraglimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "%s %i", CG_GetStringEdString("MP_INGAME", "FRAGLIMIT"), value ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) + { + value = atoi( Info_ValueForKey( info, "duel_fraglimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "%s %i", CG_GetStringEdString("MP_INGAME", "WINLIMIT"), value ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + } + } + } + + if (cgs.gametype >= GT_CTF) { + value = atoi( Info_ValueForKey( info, "capturelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "%s %i", CG_GetStringEdString("MP_INGAME", "CAPTURELIMIT"), value ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + } + + if (cgs.gametype >= GT_TEAM) + { + value = atoi( Info_ValueForKey( info, "g_forceBasedTeams" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, CG_GetStringEdString("MP_INGAME", "FORCEBASEDTEAMS"), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + } + + if (cgs.gametype != GT_SIEGE) + { + valueNOFP = atoi( Info_ValueForKey( info, "g_forcePowerDisable" ) ); + + value = atoi( Info_ValueForKey( info, "g_maxForceRank" ) ); + if ( value && !valueNOFP ) { + char fmStr[1024]; + + trap_SP_GetStringTextString("MP_INGAME_MAXFORCERANK",fmStr, sizeof(fmStr)); + + UI_DrawProportionalString( 320, y, va( "%s %s", fmStr, CG_GetStringEdString("MP_INGAME", forceMasteryLevels[value]) ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + else if (!valueNOFP) + { + char fmStr[1024]; + trap_SP_GetStringTextString("MP_INGAME_MAXFORCERANK",fmStr, sizeof(fmStr)); + + UI_DrawProportionalString( 320, y, va( "%s %s", fmStr, (char *)CG_GetStringEdString("MP_INGAME", forceMasteryLevels[7]) ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) + { + value = atoi( Info_ValueForKey( info, "g_duelWeaponDisable" ) ); + } + else + { + value = atoi( Info_ValueForKey( info, "g_weaponDisable" ) ); + } + if ( cgs.gametype != GT_JEDIMASTER && value ) { + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "SABERONLYSET") ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + if ( valueNOFP ) { + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "NOFPSET") ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + } + + // Display the rules based on type + y += iPropHeight; + switch ( cgs.gametype ) { + case GT_FFA: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_FFA_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_DUEL: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_DUEL_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_DUEL_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_POWERDUEL: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_POWERDUEL_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_POWERDUEL_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_POWERDUEL_3")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_TEAM: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_TEAM_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_TEAM_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_SIEGE: + break; + case GT_CTF: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_CTF_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_CTF_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_CTF_3")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + default: + break; + } +} + +/* +=================== +CG_LoadBar +=================== +*/ +void CG_LoadBar(void) +{ + trap_R_SetColor( colorTable[CT_WHITE] ); + + int glowHeight = (cg->loadLCARSStage / 9.0f) * 147; + int glowTop = (280 + 147) - glowHeight; + + // Draw glow: + CG_DrawPic(280, glowTop, 73, glowHeight, cgs.media.loadTick); + + // Draw saber: + CG_DrawPic(280, 265, 73, 147, cgs.media.levelLoad); +/* + const int numticks = 9, tickwidth = 40, tickheight = 8; + const int tickpadx = 20, tickpady = 12; + const int capwidth = 8; + const int barwidth = numticks*tickwidth+tickpadx*2+capwidth*2, barleft = ((640-barwidth)/2); + const int barheight = tickheight + tickpady*2, bartop = 480-barheight; + const int capleft = barleft+tickpadx, tickleft = capleft+capwidth, ticktop = bartop+tickpady; + + trap_R_SetColor( colorWhite ); + // Draw background + CG_DrawPic(barleft, bartop, barwidth, barheight, cgs.media.loadBarLEDSurround); + + // Draw left cap (backwards) + CG_DrawPic(tickleft, ticktop, -capwidth, tickheight, cgs.media.loadBarLEDCap); + + // Draw bar + CG_DrawPic(tickleft, ticktop, tickwidth*cg->loadLCARSStage, tickheight, cgs.media.loadBarLED); + + // Draw right cap + CG_DrawPic(tickleft+tickwidth*cg->loadLCARSStage, ticktop, capwidth, tickheight, cgs.media.loadBarLEDCap); +*/ +} + diff --git a/codemp/cgame/cg_light.c b/codemp/cgame/cg_light.c new file mode 100644 index 0000000..64bb112 --- /dev/null +++ b/codemp/cgame/cg_light.c @@ -0,0 +1,85 @@ +#include "cg_local.h" + +#if !defined(CG_LIGHTS_H_INC) + #include "cg_lights.h" +#endif + +static clightstyle_t cl_lightstyle[MAX_LIGHT_STYLES]; +static int lastofs; + +/* +================ +FX_ClearLightStyles +================ +*/ +void CG_ClearLightStyles (void) +{ + int i; + + memset (cl_lightstyle, 0, sizeof(cl_lightstyle)); + lastofs = -1; + + for(i=0;itime / 50; +// if (ofs == lastofs) +// return; + lastofs = ofs; + + for (i=0,ls=cl_lightstyle ; ilength) + { + ls->value[0] = ls->value[1] = ls->value[2] = ls->value[3] = 255; + } + else if (ls->length == 1) + { + ls->value[0] = ls->map[0][0]; + ls->value[1] = ls->map[0][1]; + ls->value[2] = ls->map[0][2]; + ls->value[3] = 255; //ls->map[0][3]; + } + else + { + ls->value[0] = ls->map[ofs%ls->length][0]; + ls->value[1] = ls->map[ofs%ls->length][1]; + ls->value[2] = ls->map[ofs%ls->length][2]; + ls->value[3] = 255; //ls->map[ofs%ls->length][3]; + } + trap_R_SetLightStyle(i, *(int*)ls->value); + } +} + +void CG_SetLightstyle (int i) +{ + const char *s; + int j, k; + + s = CG_ConfigString( i+CS_LIGHT_STYLES ); + j = strlen (s); + if (j >= MAX_QPATH) + { + Com_Error (ERR_DROP, "svc_lightstyle length=%i", j); + } + + cl_lightstyle[(i/3)].length = j; + for (k=0 ; koldFrame was exactly on + + int frame; + int frameTime; // time when ->frame will be exactly on + + float backlerp; + + qboolean lastFlip; //if does not match torsoFlip/legsFlip, restart the anim. + + int lastForcedFrame; + + float yawAngle; + qboolean yawing; + float pitchAngle; + qboolean pitching; + + float yawSwingDif; + + int animationNumber; + animation_t *animation; + int animationTime; // time when the first frame of the animation will be exact + + float animationSpeed; // scale the animation speed + float animationTorsoSpeed; + + qboolean torsoYawing; +} lerpFrame_t; + +typedef struct { + lerpFrame_t legs, torso, flag; + int painTime; + int painDirection; // flip from 0 to 1 + int lightningFiring; + + // machinegun spinning + float barrelAngle; + int barrelTime; + qboolean barrelSpinning; +} playerEntity_t; + +//================================================= + +// each client has an associated clientInfo_t +// that contains media references necessary to present the +// client model and other color coded effects +// this is regenerated each time a client's configstring changes, +// usually as a result of a userinfo (name, model, etc) change +#define MAX_CUSTOM_COMBAT_SOUNDS 40 +#define MAX_CUSTOM_EXTRA_SOUNDS 40 +#define MAX_CUSTOM_JEDI_SOUNDS 40 +//#define MAX_CUSTOM_SIEGE_SOUNDS..defined in bg_public.h +#define MAX_CUSTOM_DUEL_SOUNDS 40 + +#define MAX_CUSTOM_SOUNDS 40 //rww - Note that for now these must all be the same, because of the way I am + //cycling through them and comparing for custom sounds. + +typedef struct { + qboolean infoValid; + + float colorOverride[3]; + + saberInfo_t saber[MAX_SABERS]; + void *ghoul2Weapons[MAX_SABERS]; + + char saberName[64]; + char saber2Name[64]; + + char name[MAX_QPATH]; + team_t team; + + int duelTeam; + + int botSkill; // 0 = not bot, 1-5 = bot + + int frame; + + vec3_t color1; + vec3_t color2; + + int icolor1; + int icolor2; + + int score; // updated by score servercmds + int location; // location index for team mode + int health; // you only get this info about your teammates + int armor; + int curWeapon; + + int handicap; + int wins, losses; // in tourney mode + + int teamTask; // task in teamplay (offence/defence) + qboolean teamLeader; // true when this is a team leader + + int powerups; // so can display quad/flag status + + int medkitUsageTime; + + int breathPuffTime; + + // when clientinfo is changed, the loading of models/skins/sounds + // can be deferred until you are dead, to prevent hitches in + // gameplay + char modelName[MAX_QPATH]; + char skinName[MAX_QPATH]; +// char headModelName[MAX_QPATH]; +// char headSkinName[MAX_QPATH]; + char forcePowers[MAX_QPATH]; + char redTeam[MAX_TEAMNAME]; + char blueTeam[MAX_TEAMNAME]; + + char teamName[MAX_TEAMNAME]; + + int corrTime; + + vec3_t lastHeadAngles; + int lookTime; + + int brokenLimbs; + + qboolean deferred; + + qboolean newAnims; // true if using the new mission pack animations + qboolean fixedlegs; // true if legs yaw is always the same as torso yaw + qboolean fixedtorso; // true if torso never changes yaw + + vec3_t headOffset; // move head in icon views + //footstep_t footsteps; + gender_t gender; // from model + + qhandle_t legsModel; + qhandle_t legsSkin; + + qhandle_t torsoModel; + qhandle_t torsoSkin; + + //qhandle_t headModel; + //qhandle_t headSkin; + + void *ghoul2Model; + + qhandle_t modelIcon; + + qhandle_t bolt_rhand; + qhandle_t bolt_lhand; + + qhandle_t bolt_head; + + qhandle_t bolt_motion; + + qhandle_t bolt_llumbar; + + int siegeIndex; + int siegeDesiredTeam; + + sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; + sfxHandle_t combatSounds[MAX_CUSTOM_COMBAT_SOUNDS]; + sfxHandle_t extraSounds[MAX_CUSTOM_EXTRA_SOUNDS]; + sfxHandle_t jediSounds[MAX_CUSTOM_JEDI_SOUNDS]; + sfxHandle_t siegeSounds[MAX_CUSTOM_SIEGE_SOUNDS]; + sfxHandle_t duelSounds[MAX_CUSTOM_DUEL_SOUNDS]; + + int legsAnim; + int torsoAnim; + + float facial_blink; // time before next blink. If a minus value, we are in blink mode + float facial_frown; // time before next frown. If a minus value, we are in frown mode + float facial_aux; // time before next aux. If a minus value, we are in aux mode + + int superSmoothTime; //do crazy amount of smoothing + +} clientInfo_t; + +//rww - cheap looping sound struct +#ifdef _XBOX +#define MAX_CG_LOOPSOUNDS 2 +#else +#define MAX_CG_LOOPSOUNDS 8 +#endif + +typedef struct cgLoopSound_s { + int entityNum; + vec3_t origin; + vec3_t velocity; + sfxHandle_t sfx; +} cgLoopSound_t; + +// centity_t have a direct corespondence with gentity_t in the game, but +// only the entityState_t is directly communicated to the cgame +typedef struct centity_s { + // This comment below is correct, but now m_pVehicle is the first thing in bg shared entity, so it goes first. - AReis + //rww - entstate must be first, to correspond with the bg shared entity structure + entityState_t currentState; // from cg.frame + playerState_t *playerState; //ptr to playerstate if applicable (for bg ents) + Vehicle_t *m_pVehicle; //vehicle data + void *ghoul2; //g2 instance + int localAnimIndex; //index locally (game/cgame) to anim data for this skel + vec3_t modelScale; //needed for g2 collision + + //from here up must be unified with bgEntity_t -rww + + entityState_t nextState; // from cg.nextFrame, if available +#ifdef _XBOX + qboolean interpolate[2]; // true if next is valid to interpolate to + qboolean currentValid[2]; // true if cg.frame holds this entity +#else + qboolean interpolate; // true if next is valid to interpolate to + qboolean currentValid; // true if cg.frame holds this entity +#endif + + int muzzleFlashTime; // move to playerEntity? +#ifdef _XBOX + int previousEvent[2]; +#else + int previousEvent; +#endif +// int teleportFlag; + + int trailTime; // so missile trails can handle dropped initial packets + int dustTrailTime; + int miscTime; + + vec3_t damageAngles; + int damageTime; + + int snapShotTime; // last time this entity was found in a snapshot + + playerEntity_t pe; + +// int errorTime; // decay the error from this time +// vec3_t errorOrigin; +// vec3_t errorAngles; + +// qboolean extrapolated; // false if origin / angles is an interpolation +// vec3_t rawOrigin; + vec3_t rawAngles; + + vec3_t beamEnd; + + // exact interpolated position of entity on this frame + vec3_t lerpOrigin; + vec3_t lerpAngles; + +#if 0 + //add up bone offsets until next client frame before adding them in + qboolean hasRagOffset; + vec3_t ragOffsets; + int ragOffsetTime; +#endif + + vec3_t ragLastOrigin; + int ragLastOriginTime; + + qboolean noLumbar; //if true only do anims and things on model_root instead of lower_lumbar, this will be the case for some NPCs. + qboolean noFace; + + //For keeping track of the current surface status in relation to the entitystate surface fields. + int npcLocalSurfOn; + int npcLocalSurfOff; + + int eventAnimIndex; + + clientInfo_t *npcClient; //dynamically allocated - always free it, and never stomp over it. + + int weapon; + + void *ghoul2weapon; //rww - pointer to ghoul2 instance of the current 3rd person weapon + + float radius; + int boltInfo; + + //sometimes used as a bolt index, but these values are also used as generic values for clientside entities + //at times + int bolt1; + int bolt2; + int bolt3; + int bolt4; + + float bodyHeight; + + int torsoBolt; + + vec3_t turAngles; + + vec3_t frame_minus1; + vec3_t frame_minus2; + + int frame_minus1_refreshed; + int frame_minus2_refreshed; + + void *frame_hold; //pointer to a ghoul2 instance + + int frame_hold_time; + int frame_hold_refreshed; + + void *grip_arm; //pointer to a ghoul2 instance + +#ifdef _XBOX + int trickAlpha[2]; + int trickAlphaTime[2]; +#else + int trickAlpha; + int trickAlphaTime; +#endif + + int teamPowerEffectTime; + qboolean teamPowerType; //0 regen, 1 heal, 2 drain, 3 absorb + + qboolean isRagging; + qboolean ownerRagging; + int overridingBones; + + int bodyFadeTime; + vec3_t pushEffectOrigin; + + cgLoopSound_t loopingSound[MAX_CG_LOOPSOUNDS]; + int numLoopingSounds; + + int serverSaberHitIndex; + int serverSaberHitTime; + qboolean serverSaberFleshImpact; //true if flesh, false if anything else. + + qboolean ikStatus; + + qboolean saberWasInFlight; + + float smoothYaw; + + int uncloaking; + qboolean cloaked; + + int vChatTime; + +#ifdef _XBOX + bool updatedThisFrame; +#endif +} centity_t; + + +//====================================================================== + +// local entities are created as a result of events or predicted actions, +// and live independently from all server transmitted entities + +typedef struct markPoly_s { + struct markPoly_s *prevMark, *nextMark; + int time; + qhandle_t markShader; + qboolean alphaFade; // fade alpha instead of rgb + float color[4]; + poly_t poly; + polyVert_t verts[MAX_VERTS_ON_POLY]; +} markPoly_t; + + +typedef enum { + LE_MARK, + LE_EXPLOSION, + LE_SPRITE_EXPLOSION, + LE_FADE_SCALE_MODEL, // currently only for Demp2 shock sphere + LE_FRAGMENT, + LE_PUFF, + LE_MOVE_SCALE_FADE, + LE_FALL_SCALE_FADE, + LE_FADE_RGB, + LE_SCALE_FADE, + LE_SCOREPLUM, + LE_OLINE, + LE_SHOWREFENTITY, + LE_LINE +} leType_t; + +typedef enum { + LEF_PUFF_DONT_SCALE = 0x0001, // do not scale size over time + LEF_TUMBLE = 0x0002, // tumble over time, used for ejecting shells + LEF_FADE_RGB = 0x0004, // explicitly fade + LEF_NO_RANDOM_ROTATE= 0x0008 // MakeExplosion adds random rotate which could be bad in some cases +} leFlag_t; + +typedef enum { + LEMT_NONE, + LEMT_BURN, + LEMT_BLOOD +} leMarkType_t; // fragment local entities can leave marks on walls + +typedef enum { + LEBS_NONE, + LEBS_BLOOD, + LEBS_BRASS, + LEBS_METAL, + LEBS_ROCK +} leBounceSoundType_t; // fragment local entities can make sounds on impacts + +typedef struct localEntity_s { + struct localEntity_s *prev, *next; + leType_t leType; + int leFlags; + + int startTime; + int endTime; + int fadeInTime; + + float lifeRate; // 1.0 / (endTime - startTime) + + trajectory_t pos; + trajectory_t angles; + + float bounceFactor; // 0.0 = no bounce, 1.0 = perfect + int bounceSound; // optional sound index to play upon bounce + + float alpha; + float dalpha; + + int forceAlpha; + + float color[4]; + + float radius; + + float light; + vec3_t lightColor; + + leMarkType_t leMarkType; // mark to leave on fragment impact + leBounceSoundType_t leBounceSoundType; + + union { + struct { + float radius; + float dradius; + vec3_t startRGB; + vec3_t dRGB; + } sprite; + struct { + float width; + float dwidth; + float length; + float dlength; + vec3_t startRGB; + vec3_t dRGB; + } trail; + struct { + float width; + float dwidth; + // Below are bezier specific. + vec3_t control1; // initial position of control points + vec3_t control2; + vec3_t control1_velocity; // initial velocity of control points + vec3_t control2_velocity; + vec3_t control1_acceleration; // constant acceleration of control points + vec3_t control2_acceleration; + } line; + struct { + float width; + float dwidth; + float width2; + float dwidth2; + vec3_t startRGB; + vec3_t dRGB; + } line2; + struct { + float width; + float dwidth; + float width2; + float dwidth2; + float height; + float dheight; + } cylinder; + struct { + float width; + float dwidth; + } electricity; + struct + { + // fight the power! open and close brackets in the same column! + float radius; + float dradius; + qboolean (*thinkFn)(struct localEntity_s *le); + vec3_t dir; // magnitude is 1, but this is oldpos - newpos right before the + //particle is sent to the renderer + // may want to add something like particle::localEntity_s *le (for the particle's think fn) + } particle; + struct + { + qboolean dontDie; + vec3_t dir; + float variance; + int delay; + int nextthink; + qboolean (*thinkFn)(struct localEntity_s *le); + int data1; + int data2; + } spawner; + struct + { + float radius; + } fragment; + } data; + + refEntity_t refEntity; +} localEntity_t; + +//====================================================================== + + +typedef struct { + int client; + int score; + int ping; + int time; + int scoreFlags; + int powerUps; + int accuracy; + int impressiveCount; + int excellentCount; + int guantletCount; + int defendCount; + int assistCount; + int captures; + qboolean perfect; + int team; +} score_t; + + +// each WP_* weapon enum has an associated weaponInfo_t +// that contains media references necessary to present the +// weapon and its effects +typedef struct weaponInfo_s { + qboolean registered; + gitem_t *item; + + qhandle_t handsModel; // the hands don't actually draw, they just position the weapon + qhandle_t weaponModel; // this is the pickup model + qhandle_t viewModel; // this is the in-view model used by the player + qhandle_t barrelModel; + qhandle_t flashModel; + + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag + + float flashDlight; + vec3_t flashDlightColor; + + qhandle_t weaponIcon; + qhandle_t ammoIcon; + + qhandle_t ammoModel; + + sfxHandle_t flashSound[4]; // fast firing weapons randomly choose + sfxHandle_t firingSound; + sfxHandle_t chargeSound; + fxHandle_t muzzleEffect; + qhandle_t missileModel; + sfxHandle_t missileSound; + void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + float missileDlight; + vec3_t missileDlightColor; + int missileRenderfx; + sfxHandle_t missileHitSound; + + sfxHandle_t altFlashSound[4]; + sfxHandle_t altFiringSound; + sfxHandle_t altChargeSound; + fxHandle_t altMuzzleEffect; + qhandle_t altMissileModel; + sfxHandle_t altMissileSound; + void (*altMissileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + float altMissileDlight; + vec3_t altMissileDlightColor; + int altMissileRenderfx; + sfxHandle_t altMissileHitSound; + + sfxHandle_t selectSound; + + sfxHandle_t readySound; + float trailRadius; + float wiTrailTime; + +} weaponInfo_t; + + +// each IT_* item has an associated itemInfo_t +// that constains media references necessary to present the +// item and its effects +typedef struct { + qboolean registered; + qhandle_t models[MAX_ITEM_MODELS]; + qhandle_t icon; +/* +Ghoul2 Insert Start +*/ + void *g2Models[MAX_ITEM_MODELS]; + float radius[MAX_ITEM_MODELS]; +/* +Ghoul2 Insert End +*/ +} itemInfo_t; + + +typedef struct { + int itemNum; +} powerupInfo_t; + + +#define MAX_SKULLTRAIL 10 + +typedef struct { + vec3_t positions[MAX_SKULLTRAIL]; + int numpositions; +} skulltrail_t; + + +#define MAX_REWARDSTACK 10 +#define MAX_SOUNDBUFFER 20 + +//====================================================================== + +// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action +// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after + +#define MAX_PREDICTED_EVENTS 16 + + +#define MAX_CHATBOX_ITEMS 5 +typedef struct chatBoxItem_s +{ + char string[MAX_SAY_TEXT]; + int time; + int lines; +} chatBoxItem_t; + +typedef struct { + int clientFrame; // incremented each frame + + int clientNum; + + qboolean demoPlayback; + qboolean levelShot; // taking a level menu screenshot + int deferredPlayerLoading; + qboolean loading; // don't defer players at initial startup + qboolean intermissionStarted; // don't play voice rewards, because game will end shortly + + // there are only one or two snapshot_t that are relevent at a time + int latestSnapshotNum; // the number of snapshots the client system has received + int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet + + snapshot_t *snap; // cg.snap->serverTime <= cg.time + snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL +// snapshot_t activeSnapshots[2]; + + float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / (cg.nextFrame->serverTime - cg.frame->serverTime) + + qboolean mMapChange; + + qboolean thisFrameTeleport; + qboolean nextFrameTeleport; + + int frametime; // cg.time - cg.oldTime + + int time; // this is the time value that the client + // is rendering at. + int oldTime; // time at last frame, used for missile trails and prediction checking + + int physicsTime; // either cg.snap->time or cg.nextSnap->time + + int timelimitWarnings; // 5 min, 1 min, overtime + int fraglimitWarnings; + + qboolean mapRestart; // set on a map restart to set back the weapon + + qboolean mInRMG; //rwwRMG - added + qboolean mRMGWeather; //rwwRMG - added + + qboolean renderingThirdPerson; // during deaths, chasecams, etc + + // prediction state + qboolean hyperspace; // true if prediction has hit a trigger_teleport + playerState_t predictedPlayerState; + playerState_t predictedVehicleState; + + //centity_t predictedPlayerEntity; + //rww - I removed this and made it use cg_entities[clnum] directly. + + qboolean validPPS; // clear until the first call to CG_PredictPlayerState + int predictedErrorTime; + vec3_t predictedError; + + int eventSequence; + int predictableEvents[MAX_PREDICTED_EVENTS]; + + float stepChange; // for stair up smoothing + int stepTime; + + float duckChange; // for duck viewheight smoothing + int duckTime; + + float landChange; // for landing hard + int landTime; + + // input state sent to server + int weaponSelect; + + int forceSelect; + int itemSelect; + + // auto rotating items + vec3_t autoAngles; + vec3_t autoAxis[3]; + vec3_t autoAnglesFast; + vec3_t autoAxisFast[3]; + + // view rendering + refdef_t refdef; + +#ifdef _XBOX + qboolean widescreen; +#endif + + // zoom key + qboolean zoomed; + int zoomTime; + float zoomSensitivity; + + // information screen text during loading + char infoScreenText[MAX_STRING_CHARS]; + + // scoreboard + int scoresRequestTime; + int numScores; + int selectedScore; + int teamScores[2]; + score_t scores[MAX_CLIENTS]; + qboolean showScores; + qboolean scoreBoardShowing; + int scoreFadeTime; + char killerName[MAX_NAME_LENGTH]; + char spectatorList[MAX_STRING_CHARS]; // list of names + int spectatorLen; // length of list + float spectatorWidth; // width in device units + int spectatorTime; // next time to offset + int spectatorPaintX; // current paint x + int spectatorPaintX2; // current paint x + int spectatorOffset; // current offset from start + int spectatorPaintLen; // current offset from start + + // skull trails + skulltrail_t skulltrails[MAX_CLIENTS]; + + // centerprinting + int centerPrintTime; + int centerPrintCharWidth; + int centerPrintY; + char centerPrint[1024]; + int centerPrintLines; + + // low ammo warning state + int lowAmmoWarning; // 1 = low, 2 = empty + + // kill timers for carnage reward + int lastKillTime; + + // crosshair client ID + int crosshairClientNum; + int crosshairClientTime; + + int crosshairVehNum; + int crosshairVehTime; + + // powerup active flashing + int powerupActive; + int powerupTime; + + // attacking player + int attackerTime; + int voiceTime; + + // reward medals + int rewardStack; + int rewardTime; + int rewardCount[MAX_REWARDSTACK]; + qhandle_t rewardShader[MAX_REWARDSTACK]; + qhandle_t rewardSound[MAX_REWARDSTACK]; + + // sound buffer mainly for announcer sounds + int soundBufferIn; + int soundBufferOut; + int soundTime; + qhandle_t soundBuffer[MAX_SOUNDBUFFER]; + + // for voice chat buffer + int voiceChatTime; + int voiceChatBufferIn; + int voiceChatBufferOut; + + // warmup countdown + int warmup; + int warmupCount; + + //========================== + + int itemPickup; + int itemPickupTime; + int itemPickupBlendTime; // the pulse around the crosshair is timed seperately + + int weaponSelectTime; + int weaponAnimation; + int weaponAnimationTime; + + // blend blobs + float damageTime; + float damageX, damageY, damageValue; + + // status bar head + float headYaw; + float headEndPitch; + float headEndYaw; + int headEndTime; + float headStartPitch; + float headStartYaw; + int headStartTime; + + // view movement + float v_dmg_time; + float v_dmg_pitch; + float v_dmg_roll; + + vec3_t kick_angles; // weapon kicks + int kick_time; + vec3_t kick_origin; + + // temp working variables for player view + float bobfracsin; + int bobcycle; + float xyspeed; + int nextOrbitTime; + + //qboolean cameraMode; // if rendering from a loaded camera + int loadLCARSStage; + + int forceHUDTotalFlashTime; + int forceHUDNextFlashTime; + qboolean forceHUDActive; // Flag to show force hud is off/on + + // development tool + refEntity_t testModelEntity; + char testModelName[MAX_QPATH]; + qboolean testGun; + + int VHUDFlashTime; + qboolean VHUDTurboFlag; + + // HUD stuff + float HUDTickFlashTime; + qboolean HUDArmorFlag; + qboolean HUDHealthFlag; + qboolean iconHUDActive; + float iconHUDPercent; + float iconSelectTime; + float invenSelectTime; + float forceSelectTime; + + vec3_t lastFPFlashPoint; + +/* +Ghoul2 Insert Start +*/ + int testModel; + // had to be moved so we wouldn't wipe these out with the memset - these have STL in them and shouldn't be cleared that way + snapshot_t activeSnapshots[2]; +/* +Ghoul2 Insert End +*/ + + char sharedBuffer[MAX_CG_SHARED_BUFFER_SIZE]; + + short radarEntityCount; + short radarEntities[MAX_RADAR_ENTITIES]; + + short bracketedEntityCount; + short bracketedEntities[MAX_CLIENTS+16]; + + float distanceCull; + + chatBoxItem_t chatItems[MAX_CHATBOX_ITEMS]; + int chatItemActive; + +#if 0 + int snapshotTimeoutTime; +#endif + +} cg_t; + +#define MAX_TICS 14 + +typedef struct forceTicPos_s +{ + int x; + int y; + int width; + int height; + char *file; + qhandle_t tic; +} forceTicPos_t; +extern forceTicPos_t forceTicPos[]; +extern forceTicPos_t ammoTicPos[]; + +typedef struct cgscreffects_s +{ + float FOV; + float FOV2; + + float shake_intensity; + int shake_duration; + int shake_start; + + float music_volume_multiplier; + int music_volume_time; + qboolean music_volume_set; +} cgscreffects_t; + +extern cgscreffects_t cgScreenEffects; + +void CGCam_Shake( float intensity, int duration ); +void CGCam_SetMusicMult( float multiplier, int duration ); + +typedef enum +{ + CHUNK_METAL1 = 0, + CHUNK_METAL2, + CHUNK_ROCK1, + CHUNK_ROCK2, + CHUNK_ROCK3, + CHUNK_CRATE1, + CHUNK_CRATE2, + CHUNK_WHITE_METAL, + NUM_CHUNK_TYPES +}; +#define NUM_CHUNK_MODELS 4 + +// all of the model, shader, and sound references that are +// loaded at gamestate time are stored in cgMedia_t +// Other media that can be tied to clients, weapons, or items are +// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t +typedef struct { +// qhandle_t charsetShader; + qhandle_t whiteShader; + + qhandle_t loadTick; + qhandle_t levelLoad; + + qhandle_t bryarFrontFlash; + qhandle_t greenFrontFlash; + qhandle_t lightningFlash; + + qhandle_t itemHoloModel; + qhandle_t redFlagModel; + qhandle_t blueFlagModel; + + qhandle_t flagPoleModel; + qhandle_t flagFlapModel; + + qhandle_t redFlagBaseModel; + qhandle_t blueFlagBaseModel; + qhandle_t neutralFlagBaseModel; + + qhandle_t teamStatusBar; + + qhandle_t deferShader; + + qhandle_t radarShader; + qhandle_t siegeItemShader; + qhandle_t mAutomapPlayerIcon; + qhandle_t mAutomapRocketIcon; + + qhandle_t wireframeAutomapFrame_left; + qhandle_t wireframeAutomapFrame_right; + qhandle_t wireframeAutomapFrame_top; + qhandle_t wireframeAutomapFrame_bottom; + +//Chunks + qhandle_t chunkModels[NUM_CHUNK_TYPES][4]; + sfxHandle_t chunkSound; + sfxHandle_t grateSound; + sfxHandle_t rockBreakSound; + sfxHandle_t rockBounceSound[2]; + sfxHandle_t metalBounceSound[2]; + sfxHandle_t glassChunkSound; + sfxHandle_t crateBreakSound[2]; + + qhandle_t hackerIconShader; + + // Saber shaders + //----------------------------- + qhandle_t forceCoronaShader; + + qhandle_t redSaberGlowShader; + qhandle_t redSaberCoreShader; + qhandle_t orangeSaberGlowShader; + qhandle_t orangeSaberCoreShader; + qhandle_t yellowSaberGlowShader; + qhandle_t yellowSaberCoreShader; + qhandle_t greenSaberGlowShader; + qhandle_t greenSaberCoreShader; + qhandle_t blueSaberGlowShader; + qhandle_t blueSaberCoreShader; + qhandle_t purpleSaberGlowShader; + qhandle_t purpleSaberCoreShader; + qhandle_t saberBlurShader; + qhandle_t swordTrailShader; + + qhandle_t yellowDroppedSaberShader; + + qhandle_t rivetMarkShader; + + qhandle_t teamRedShader; + qhandle_t teamBlueShader; + + qhandle_t powerDuelAllyShader; + + qhandle_t balloonShader; + qhandle_t vchatShader; + qhandle_t connectionShader; + + qhandle_t crosshairShader[NUM_CROSSHAIRS]; +// qhandle_t lagometerShader; +// qhandle_t backTileShader; + + qhandle_t numberShaders[11]; + qhandle_t smallnumberShaders[11]; +// qhandle_t chunkyNumberShaders[11]; + + qhandle_t electricBodyShader; + qhandle_t electricBody2Shader; + + qhandle_t fsrMarkShader; + qhandle_t fslMarkShader; + qhandle_t fshrMarkShader; + qhandle_t fshlMarkShader; + + qhandle_t refractionShader; + + qhandle_t cloakedShader; + + qhandle_t boltShader; + + qhandle_t shadowMarkShader; + + //glass shard shader + qhandle_t glassShardShader; + + // wall mark shaders + qhandle_t wakeMarkShader; + + // Pain view shader + qhandle_t viewPainShader; + qhandle_t viewPainShader_Shields; + qhandle_t viewPainShader_ShieldsAndHealth; + + qhandle_t itemRespawningPlaceholder; + qhandle_t itemRespawningRezOut; + + qhandle_t playerShieldDamage; + qhandle_t protectShader; + qhandle_t forceSightBubble; + qhandle_t forceShell; + qhandle_t sightShell; + + // Disruptor zoom graphics + qhandle_t disruptorMask; + qhandle_t disruptorInsert; + qhandle_t disruptorLight; + qhandle_t disruptorInsertTick; + qhandle_t disruptorChargeShader; + + // Binocular graphics +// qhandle_t binocularCircle; +// qhandle_t binocularMask; +// qhandle_t binocularArrow; +// qhandle_t binocularTri; +// qhandle_t binocularStatic; +// qhandle_t binocularOverlay; + + // weapon effect models + qhandle_t lightningExplosionModel; + + // explosion assets + qhandle_t explosionModel; + qhandle_t surfaceExplosionShader; + + qhandle_t disruptorShader; + + qhandle_t solidWhite; + + qhandle_t heartShader; + + // All the player shells + qhandle_t ysaliredShader; + qhandle_t ysaliblueShader; + qhandle_t ysalimariShader; + qhandle_t boonShader; + qhandle_t endarkenmentShader; + qhandle_t enlightenmentShader; + qhandle_t invulnerabilityShader; + +#ifdef JK2AWARDS + // medals shown during gameplay + qhandle_t medalImpressive; + qhandle_t medalExcellent; + qhandle_t medalGauntlet; + qhandle_t medalDefend; + qhandle_t medalAssist; + qhandle_t medalCapture; +#endif + + // sounds + sfxHandle_t selectSound; + sfxHandle_t footsteps[FOOTSTEP_TOTAL][4]; + + sfxHandle_t winnerSound; + sfxHandle_t loserSound; + + sfxHandle_t crackleSound; + + sfxHandle_t grenadeBounce1; + sfxHandle_t grenadeBounce2; + + sfxHandle_t teamHealSound; + sfxHandle_t teamRegenSound; + + sfxHandle_t teleInSound; + sfxHandle_t teleOutSound; + sfxHandle_t respawnSound; + sfxHandle_t talkSound; + sfxHandle_t landSound; + sfxHandle_t fallSound; + + sfxHandle_t oneMinuteSound; + sfxHandle_t fiveMinuteSound; + + sfxHandle_t threeFragSound; + sfxHandle_t twoFragSound; + sfxHandle_t oneFragSound; + +#ifdef JK2AWARDS + sfxHandle_t impressiveSound; + sfxHandle_t excellentSound; + sfxHandle_t deniedSound; + sfxHandle_t humiliationSound; + sfxHandle_t defendSound; +#endif + + /* + sfxHandle_t takenLeadSound; + sfxHandle_t tiedLeadSound; + sfxHandle_t lostLeadSound; + */ + + sfxHandle_t rollSound; + + sfxHandle_t watrInSound; + sfxHandle_t watrOutSound; + sfxHandle_t watrUnSound; + + sfxHandle_t noforceSound; + + sfxHandle_t deploySeeker; + sfxHandle_t medkitSound; + + // teamplay sounds +#ifdef JK2AWARDS + sfxHandle_t captureAwardSound; +#endif + sfxHandle_t redScoredSound; + sfxHandle_t blueScoredSound; + sfxHandle_t redLeadsSound; + sfxHandle_t blueLeadsSound; + sfxHandle_t teamsTiedSound; + + sfxHandle_t redFlagReturnedSound; + sfxHandle_t blueFlagReturnedSound; + sfxHandle_t redTookFlagSound; + sfxHandle_t blueTookFlagSound; + + sfxHandle_t redYsalReturnedSound; + sfxHandle_t blueYsalReturnedSound; + sfxHandle_t redTookYsalSound; + sfxHandle_t blueTookYsalSound; + + sfxHandle_t drainSound; + + //music blips + sfxHandle_t happyMusic; + sfxHandle_t dramaticFailure; + + // tournament sounds + sfxHandle_t count3Sound; + sfxHandle_t count2Sound; + sfxHandle_t count1Sound; + sfxHandle_t countFightSound; + + // new stuff + qhandle_t patrolShader; + qhandle_t assaultShader; + qhandle_t campShader; + qhandle_t followShader; + qhandle_t defendShader; + qhandle_t teamLeaderShader; + qhandle_t retrieveShader; + qhandle_t escortShader; + qhandle_t flagShaders[3]; + + qhandle_t halfShieldModel; + qhandle_t halfShieldShader; + + qhandle_t demp2Shell; + qhandle_t demp2ShellShader; + +// qhandle_t cursor; + qhandle_t selectCursor; + qhandle_t sizeCursor; + + //weapon icons + qhandle_t weaponIcons[WP_NUM_WEAPONS]; + qhandle_t weaponIcons_NA[WP_NUM_WEAPONS]; + + //holdable inventory item icons + qhandle_t invenIcons[HI_NUM_HOLDABLE]; + + //force power icons + qhandle_t forcePowerIcons[NUM_FORCE_POWERS]; + + qhandle_t rageRecShader; + + //other HUD parts +// int currentBackground; +// qhandle_t weaponIconBackground; +// qhandle_t forceIconBackground; +// qhandle_t inventoryIconBackground; + + sfxHandle_t holocronPickup; + + // Zoom + sfxHandle_t zoomStart; + sfxHandle_t zoomLoop; + sfxHandle_t zoomEnd; + sfxHandle_t disruptorZoomLoop; + + qhandle_t bdecal_bodyburn1; + qhandle_t bdecal_saberglow; + qhandle_t bdecal_burn1; + qhandle_t mSaberDamageGlow; + + // For vehicles only now + sfxHandle_t noAmmoSound; + +} cgMedia_t; + + +// Stored FX handles +//-------------------- +typedef struct +{ + //concussion + fxHandle_t concussionShotEffect; + fxHandle_t concussionImpactEffect; + + // BRYAR PISTOL + fxHandle_t bryarShotEffect; + fxHandle_t bryarPowerupShotEffect; + fxHandle_t bryarWallImpactEffect; + fxHandle_t bryarWallImpactEffect2; + fxHandle_t bryarWallImpactEffect3; + fxHandle_t bryarFleshImpactEffect; + fxHandle_t bryarDroidImpactEffect; + + // BLASTER + fxHandle_t blasterShotEffect; + fxHandle_t blasterWallImpactEffect; + fxHandle_t blasterFleshImpactEffect; + fxHandle_t blasterDroidImpactEffect; + + // DISRUPTOR + fxHandle_t disruptorRingsEffect; + fxHandle_t disruptorProjectileEffect; + fxHandle_t disruptorWallImpactEffect; + fxHandle_t disruptorFleshImpactEffect; + fxHandle_t disruptorAltMissEffect; + fxHandle_t disruptorAltHitEffect; + + // BOWCASTER + fxHandle_t bowcasterShotEffect; + fxHandle_t bowcasterImpactEffect; + + // REPEATER + fxHandle_t repeaterProjectileEffect; + fxHandle_t repeaterAltProjectileEffect; + fxHandle_t repeaterWallImpactEffect; + fxHandle_t repeaterFleshImpactEffect; + fxHandle_t repeaterAltWallImpactEffect; + + // DEMP2 + fxHandle_t demp2ProjectileEffect; + fxHandle_t demp2WallImpactEffect; + fxHandle_t demp2FleshImpactEffect; + + // FLECHETTE + fxHandle_t flechetteShotEffect; + fxHandle_t flechetteAltShotEffect; + fxHandle_t flechetteWallImpactEffect; + fxHandle_t flechetteFleshImpactEffect; + + // ROCKET + fxHandle_t rocketShotEffect; + fxHandle_t rocketExplosionEffect; + + // THERMAL + fxHandle_t thermalExplosionEffect; + fxHandle_t thermalShockwaveEffect; + + // TRIPMINE + fxHandle_t tripmineLaserFX; + fxHandle_t tripmineGlowFX; + + //FORCE + fxHandle_t forceLightning; + fxHandle_t forceLightningWide; + + fxHandle_t forceDrain; + fxHandle_t forceDrainWide; + fxHandle_t forceDrained; + + //TURRET + fxHandle_t turretShotEffect; + + //Whatever + fxHandle_t itemCone; + + fxHandle_t mSparks; + fxHandle_t mSaberCut; + fxHandle_t mTurretMuzzleFlash; + fxHandle_t mSaberBlock; + fxHandle_t mSaberBloodSparks; + fxHandle_t mSaberBloodSparksSmall; + fxHandle_t mSaberBloodSparksMid; + fxHandle_t mSpawn; + fxHandle_t mJediSpawn; + fxHandle_t mBlasterDeflect; + fxHandle_t mBlasterSmoke; + fxHandle_t mForceConfustionOld; + fxHandle_t mDisruptorDeathSmoke; + fxHandle_t mSparkExplosion; + fxHandle_t mTurretExplode; + fxHandle_t mEmplacedExplode; + fxHandle_t mEmplacedDeadSmoke; + fxHandle_t mTripmineExplosion; + fxHandle_t mDetpackExplosion; + fxHandle_t mFlechetteAltBlow; + fxHandle_t mStunBatonFleshImpact; + fxHandle_t mAltDetonate; + fxHandle_t mSparksExplodeNoSound; + fxHandle_t mTripMineLaster; + fxHandle_t mEmplacedMuzzleFlash; + fxHandle_t mConcussionAltRing; + fxHandle_t mHyperspaceStars; + fxHandle_t mBlackSmoke; + fxHandle_t mShipDestDestroyed; + fxHandle_t mShipDestBurning; + fxHandle_t mBobaJet; + + //footstep effects + fxHandle_t footstepMud; + fxHandle_t footstepSand; + fxHandle_t footstepSnow; + fxHandle_t footstepGravel; + //landing effects + fxHandle_t landingMud; + fxHandle_t landingSand; + fxHandle_t landingDirt; + fxHandle_t landingSnow; + fxHandle_t landingGravel; + //splashes + fxHandle_t waterSplash; + fxHandle_t lavaSplash; + fxHandle_t acidSplash; +} cgEffects_t; + + +// The client game static (cgs) structure hold everything +// loaded or calculated from the gamestate. It will NOT +// be cleared when a tournement restart is done, allowing +// all clients to begin playing instantly +typedef struct { + gameState_t gameState; // gamestate from server + glconfig_t glconfig; // rendering configuration + float screenXScale; // derived from glconfig + float screenYScale; + float screenXBias; + + int serverCommandSequence; // reliable command stream counter +#ifdef _XBOX + int processedSnapshotNum[2]; +#else + int processedSnapshotNum;// the number of snapshots cgame has requested +#endif + + qboolean localServer; // detected on startup by checking sv_running + + // parsed from serverinfo + int siegeTeamSwitch; + int showDuelHealths; + gametype_t gametype; + int debugMelee; + int stepSlideFix; + int noSpecMove; + int dmflags; + int teamflags; + int fraglimit; + int duel_fraglimit; + int capturelimit; + int timelimit; + int maxclients; + qboolean needpass; + qboolean jediVmerc; + int wDisable; + int fDisable; + + char mapname[MAX_QPATH]; + char redTeam[MAX_QPATH]; + char blueTeam[MAX_QPATH]; + + int voteTime; + int voteYes; + int voteNo; + qboolean voteModified; // beep whenever changed + char voteString[MAX_STRING_TOKENS]; + char voteCaller[32]; // Change this length???? + qboolean votePlaced; + + int teamVoteTime[2]; + int teamVoteYes[2]; + int teamVoteNo[2]; + qboolean teamVoteModified[2]; // beep whenever changed + char teamVoteString[2][MAX_STRING_TOKENS]; + + int levelStartTime; + + int scores1, scores2; // from configstrings + int jediMaster; + int duelWinner; + int duelist1; + int duelist2; + int duelist3; +// nmckenzie: DUEL_HEALTH. hmm. + int duelist1health; + int duelist2health; + int duelist3health; + + int redflag, blueflag; // flag status from configstrings + int flagStatus; + + qboolean newHud; + + // + // locally derived information from gamestate + // + qhandle_t gameModels[MAX_MODELS]; + sfxHandle_t gameSounds[MAX_SOUNDS]; + fxHandle_t gameEffects[MAX_FX]; + qhandle_t gameIcons[MAX_ICONS]; + + int numInlineModels; + qhandle_t inlineDrawModel[MAX_MODELS]; + vec3_t inlineModelMidpoints[MAX_MODELS]; + + clientInfo_t clientinfo[MAX_CLIENTS]; + + int cursorX; + int cursorY; + qboolean eventHandling; + qboolean mouseCaptured; + qboolean sizingHud; + void *capturedItem; + qhandle_t activeCursor; + + // media + cgMedia_t media; + + // effects + cgEffects_t effects; + +} cgs_t; + +typedef struct siegeExtended_s +{ + int health; + int maxhealth; + int ammo; + int weapon; + int lastUpdated; +} siegeExtended_t; + +//keep an entry available for each client +extern siegeExtended_t cg_siegeExtendedData[MAX_CLIENTS]; + +//============================================================================== + +extern cgs_t cgs; +extern cg_t g_cg; +extern cg_t *cg; + +//extern centity_t cg_entities[MAX_GENTITIES]; +extern centity_t *cg_entities; + +extern centity_t *cg_permanents[MAX_GENTITIES]; +extern int cg_numpermanents; + +extern weaponInfo_t cg_weapons[MAX_WEAPONS]; +extern itemInfo_t cg_items[MAX_ITEMS]; +extern markPoly_t cg_markPolys[MAX_MARK_POLYS]; + +extern vmCvar_t cg_centertime; +extern vmCvar_t cg_runpitch; +extern vmCvar_t cg_runroll; +extern vmCvar_t cg_bobup; +extern vmCvar_t cg_bobpitch; +extern vmCvar_t cg_bobroll; +//extern vmCvar_t cg_swingSpeed; +extern vmCvar_t cg_shadows; +extern vmCvar_t cg_renderToTextureFX; +extern vmCvar_t cg_drawTimer; +extern vmCvar_t cg_drawFPS; +//extern vmCvar_t cg_drawSnapshot; +extern vmCvar_t cg_draw3dIcons; +extern vmCvar_t cg_drawIcons; +extern vmCvar_t cg_drawAmmoWarning; +extern vmCvar_t cg_drawCrosshair; +extern vmCvar_t cg_drawCrosshairNames; +extern vmCvar_t cg_drawRadar; +extern vmCvar_t cg_drawAutomap; +extern vmCvar_t cg_drawScores; +extern vmCvar_t cg_dynamicCrosshair; +extern vmCvar_t cg_dynamicCrosshairPrecision; +extern vmCvar_t cg_drawRewards; +//extern vmCvar_t cg_drawTeamOverlay; +extern vmCvar_t cg_teamOverlayUserinfo; +extern vmCvar_t cg_crosshairX; +extern vmCvar_t cg_crosshairY; +extern vmCvar_t cg_crosshairSize; +extern vmCvar_t cg_crosshairHealth; +extern vmCvar_t cg_drawStatus; +extern vmCvar_t cg_draw2D; +extern vmCvar_t cg_animSpeed; +extern vmCvar_t cg_debugAnim; +extern vmCvar_t cg_debugPosition; +extern vmCvar_t cg_debugEvents; +extern vmCvar_t cg_errorDecay; +extern vmCvar_t cg_nopredict; +extern vmCvar_t cg_noPlayerAnims; +extern vmCvar_t cg_showmiss; +extern vmCvar_t cg_showVehMiss; +extern vmCvar_t cg_footsteps; +extern vmCvar_t cg_addMarks; +extern vmCvar_t cg_gun_frame; +extern vmCvar_t cg_gun_x; +extern vmCvar_t cg_gun_y; +extern vmCvar_t cg_gun_z; +extern vmCvar_t cg_drawGun; +extern vmCvar_t cg_viewsize; +extern vmCvar_t cg_autoswitch; +extern vmCvar_t cg_ignore; +extern vmCvar_t cg_simpleItems; +extern vmCvar_t cg_fov; +extern vmCvar_t cg_zoomFov; + +extern vmCvar_t cg_swingAngles; + +extern vmCvar_t cg_oldPainSounds; + +extern vmCvar_t cg_ragDoll; + +extern vmCvar_t cg_jumpSounds; + +extern vmCvar_t cg_autoMap; +extern vmCvar_t cg_autoMapX; +extern vmCvar_t cg_autoMapY; +extern vmCvar_t cg_autoMapW; +extern vmCvar_t cg_autoMapH; + +extern vmCvar_t bg_fighterAltControl; + +extern vmCvar_t cg_chatBox; +extern vmCvar_t cg_chatBoxHeight; + +extern vmCvar_t cg_saberModelTraceEffect; + +extern vmCvar_t cg_saberClientVisualCompensation; + +extern vmCvar_t cg_g2TraceLod; + +extern vmCvar_t cg_fpls; + +extern vmCvar_t cg_ghoul2Marks; + +extern vmCvar_t cg_saberDynamicMarks; +extern vmCvar_t cg_saberDynamicMarkTime; + +extern vmCvar_t cg_saberContact; +extern vmCvar_t cg_saberTrail; + +extern vmCvar_t cg_duelHeadAngles; + +extern vmCvar_t cg_speedTrail; +extern vmCvar_t cg_auraShell; + +extern vmCvar_t cg_repeaterOrb; + +extern vmCvar_t cg_animBlend; + +extern vmCvar_t cg_dismember; + +extern vmCvar_t cg_thirdPersonSpecialCam; + +extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_thirdPersonRange; +extern vmCvar_t cg_thirdPersonAngle; +extern vmCvar_t cg_thirdPersonPitchOffset; +extern vmCvar_t cg_thirdPersonVertOffset; +extern vmCvar_t cg_thirdPersonCameraDamp; +extern vmCvar_t cg_thirdPersonTargetDamp; + +extern vmCvar_t cg_thirdPersonAlpha; +extern vmCvar_t cg_thirdPersonHorzOffset; + +extern vmCvar_t cg_stereoSeparation; +//extern vmCvar_t cg_lagometer; +extern vmCvar_t cg_drawEnemyInfo; +extern vmCvar_t cg_synchronousClients; +extern vmCvar_t cg_stats; +extern vmCvar_t cg_forceModel; +extern vmCvar_t cg_buildScript; +extern vmCvar_t cg_paused; +extern vmCvar_t cg_blood; +extern vmCvar_t cg_predictItems; +extern vmCvar_t cg_deferPlayers; +extern vmCvar_t cg_drawFriend; +extern vmCvar_t cg_teamChatsOnly; +extern vmCvar_t cg_noVoiceChats; +extern vmCvar_t cg_noVoiceText; +extern vmCvar_t cg_scorePlum; +extern vmCvar_t cg_hudFiles; +extern vmCvar_t cg_smoothClients; + +#include "../namespace_begin.h" +extern vmCvar_t pmove_fixed; +extern vmCvar_t pmove_msec; +#include "../namespace_end.h" + +//extern vmCvar_t cg_pmove_fixed; +extern vmCvar_t cg_cameraOrbit; +extern vmCvar_t cg_cameraOrbitDelay; +extern vmCvar_t cg_timescaleFadeEnd; +extern vmCvar_t cg_timescaleFadeSpeed; +extern vmCvar_t cg_timescale; +extern vmCvar_t cg_cameraMode; +extern vmCvar_t cg_smallFont; +extern vmCvar_t cg_bigFont; +extern vmCvar_t cg_noTaunt; +extern vmCvar_t cg_noProjectileTrail; +extern vmCvar_t cg_trueLightning; + +extern vmCvar_t cg_redTeamName; +extern vmCvar_t cg_blueTeamName; +extern vmCvar_t cg_currentSelectedPlayer; +extern vmCvar_t cg_currentSelectedPlayerName; +extern vmCvar_t cg_singlePlayer; +extern vmCvar_t cg_enableDust; +extern vmCvar_t cg_enableBreath; +extern vmCvar_t cg_singlePlayerActive; +extern vmCvar_t cg_recordSPDemo; +extern vmCvar_t cg_recordSPDemoName; + +extern vmCvar_t cg_snapshotTimeout; +/* +Ghoul2 Insert Start +*/ + +extern vmCvar_t cg_debugBB; + +/* +Ghoul2 Insert End +*/ + +// +// cg_main.c +// +void CG_DrawMiscEnts(void); + +const char *CG_ConfigString( int index ); +const char *CG_Argv( int arg ); + +void QDECL CG_Printf( const char *msg, ... ); +void QDECL CG_PrintfAlways( const char *msg, ... ); +void QDECL CG_Error( const char *msg, ... ); + +void CG_StartMusic( qboolean bForceStart ); + +void CG_UpdateCvars( void ); + +int CG_CrosshairPlayer( void ); +int CG_LastAttacker( void ); +void CG_LoadMenus(const char *menuFile); +void CG_KeyEvent(int key, qboolean down); +void CG_MouseEvent(int x, int y); +void CG_EventHandling(int type); +void CG_RankRunFrame( void ); +void CG_SetScoreSelection(void *menu); +void CG_BuildSpectatorString(void); +void CG_NextInventory_f(void); +void CG_PrevInventory_f(void); +void CG_NextForcePower_f(void); +void CG_PrevForcePower_f(void); + +// +// cg_view.c +// +void CG_TestModel_f (void); +void CG_TestGun_f (void); +void CG_TestModelNextFrame_f (void); +void CG_TestModelPrevFrame_f (void); +void CG_TestModelNextSkin_f (void); +void CG_TestModelPrevSkin_f (void); +void CG_ZoomDown_f( void ); +void CG_ZoomUp_f( void ); +void CG_AddBufferedSound( sfxHandle_t sfx); + +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); +/* +Ghoul2 Insert Start +*/ + +void CG_TestG2Model_f (void); +void CG_TestModelSurfaceOnOff_f(void); +void CG_ListModelSurfaces_f (void); +void CG_ListModelBones_f (void); +void CG_TestModelSetAnglespre_f(void); +void CG_TestModelSetAnglespost_f(void); +void CG_TestModelAnimate_f(void); +/* +Ghoul2 Insert End +*/ + +// +// cg_drawtools.c +// +void CG_FillRect( float x, float y, float width, float height, const float *color ); +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void CG_DrawRotatePic( float x, float y, float width, float height,float angle, qhandle_t hShader ); +void CG_DrawRotatePic2( float x, float y, float width, float height,float angle, qhandle_t hShader ); +void CG_DrawString( float x, float y, const char *string, + float charWidth, float charHeight, const float *modulate ); + +void CG_DrawNumField (int x, int y, int width, int value,int charWidth,int charHeight,int style,qboolean zeroFill); + +float *CG_FadeColor( int startMsec, int totalMsec ); +float *CG_TeamColor( int team ); +void CG_TileClear( void ); +void CG_ColorForHealth( vec4_t hcolor ); +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); + +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); +void UI_DrawScaledProportionalString( int x, int y, const char* str, int style, vec4_t color, float scale); +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); +void CG_DrawSides(float x, float y, float w, float h, float size); +void CG_DrawTopBottom(float x, float y, float w, float h, float size); + +// +// cg_draw.c, cg_newDraw.c +// +extern int sortedTeamPlayers[TEAM_MAXOVERLAY]; +extern int numSortedTeamPlayers; +extern char systemChat[256]; + +//void CG_AddLagometerFrameInfo( void ); +//void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); +void CG_CenterPrint( const char *str, int y, int charWidth ); +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ); +void CG_DrawActive( stereoFrame_t stereoView ); +void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ); +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ); +void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle,int font); +void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style, int iMenuFont); +int CG_Text_Width(const char *text, float scale, int iMenuFont); +int CG_Text_Height(const char *text, float scale, int iMenuFont); +float CG_GetValue(int ownerDraw); +qboolean CG_OwnerDrawVisible(int flags); +void CG_RunMenuScript(char **args); +qboolean CG_DeferMenuScript(char **args); +void CG_ShowResponseHead(void); +void CG_GetTeamColor(vec4_t *color); +const char *CG_GetGameStatusText(void); +const char *CG_GetKillerText(void); +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, void *ghoul2, int g2radius, qhandle_t skin, vec3_t origin, vec3_t angles ); +void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader); +const char *CG_GameTypeString(void); +qboolean CG_YourTeamHasFlag(void); +qboolean CG_OtherTeamHasFlag(void); +qhandle_t CG_StatusHandle(int task); + + + +// +// cg_player.c +// +qboolean CG_RagDoll(centity_t *cent, vec3_t forcedAngles); +qboolean CG_G2TraceCollide(trace_t *tr, const vec3_t mins, const vec3_t maxs, const vec3_t lastValidStart, const vec3_t lastValidEnd); +void CG_AddGhoul2Mark(int shader, float size, vec3_t start, vec3_t end, int entnum, + vec3_t entposition, float entangle, void *ghoul2, vec3_t scale, int lifeTime); + +void CG_CreateNPCClient(clientInfo_t **ci); +void CG_DestroyNPCClient(clientInfo_t **ci); + +void CG_Player( centity_t *cent ); +void CG_ResetPlayerEntity( centity_t *cent ); +void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ); +void CG_NewClientInfo( int clientNum, qboolean entitiesInitialized ); +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); +void CG_PlayerShieldHit(int entitynum, vec3_t angles, int amount); + + +// +// cg_predict.c +// +void CG_BuildSolidList( void ); +int CG_PointContents( const vec3_t point, int passEntityNum ); +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ); +void CG_G2Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ); +void CG_PredictPlayerState( void ); +void CG_LoadDeferredPlayers( void ); + + +// +// cg_events.c +// +void CG_CheckEvents( centity_t *cent ); +const char *CG_PlaceString( int rank ); +void CG_EntityEvent( centity_t *cent, vec3_t position ); +void CG_PainEvent( centity_t *cent, int health ); +void CG_ReattachLimb(centity_t *source); + + +// +// cg_ents.c +// + +void CG_S_AddLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx); +void CG_S_AddRealLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx); +void CG_S_StopLoopingSound(int entityNum, sfxHandle_t sfx); +void CG_S_UpdateLoopingSounds(int entityNum); + +void CG_SetEntitySoundPosition( centity_t *cent ); +void CG_AddPacketEntities( qboolean isPortal ); +void CG_ManualEntityRender(centity_t *cent); +void CG_Beam( centity_t *cent ); +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); + +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); + +/* +Ghoul2 Insert Start +*/ +void ScaleModelAxis(refEntity_t *ent); +/* +Ghoul2 Insert End +*/ + +// +// cg_turret.c +// +void TurretClientRun(centity_t *ent); + +// +// cg_weapons.c +// +void CG_GetClientWeaponMuzzleBoltPoint(int clIndex, vec3_t to); + +void CG_NextWeapon_f( void ); +void CG_PrevWeapon_f( void ); +void CG_Weapon_f( void ); +void CG_WeaponClean_f( void ); + +void CG_RegisterWeapon( int weaponNum); +void CG_RegisterItemVisuals( int itemNum ); + +void CG_FireWeapon( centity_t *cent, qboolean alt_fire ); +void CG_MissileHitWall(int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType, qboolean alt_fire, int charge); +void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum, qboolean alt_fire); + +void CG_AddViewWeapon (playerState_t *ps); +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team, vec3_t newAngles, qboolean thirdPerson ); +void CG_DrawWeaponSelect( void ); +void CG_DrawIconBackground(void); + +void CG_OutOfAmmoChange( int oldWeapon ); // should this be in pmove? + +// +// cg_marks.c +// +void CG_InitMarkPolys( void ); +void CG_AddMarks( void ); +void CG_ImpactMark( qhandle_t markShader, + const vec3_t origin, const vec3_t dir, + float orientation, + float r, float g, float b, float a, + qboolean alphaFade, + float radius, qboolean temporary ); + +// +// cg_localents.c +// +void CG_InitLocalEntities( void ); +localEntity_t *CG_AllocLocalEntity( void ); +void CG_AddLocalEntities( void ); + +// +// cg_effects.c +// +localEntity_t *CG_SmokePuff( const vec3_t p, + const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ); +void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ); +void CG_GlassShatter(int entnum, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius, int maxShards); +void CG_ScorePlum( int client, vec3_t org, int score ); + +void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs, + float speed, int numChunks, material_t chunkType, int customChunk, float baseScale ); +void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType ); + +void CG_Bleed( vec3_t origin, int entityNum ); + +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, int numframes, qhandle_t shader, int msec, + qboolean isSprite, float scale, int flags );// Overloaded in single player + +void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke ); + +void CG_TestLine( vec3_t start, vec3_t end, int time, unsigned int color, int radius); + +void CG_InitGlass( void ); + +// +// cg_snapshot.c +// +void CG_ProcessSnapshots( void ); + +// +// cg_info.c +// +void CG_LoadingString( const char *s ); +void CG_LoadingItem( int itemNum ); +void CG_LoadingClient( int clientNum ); +void CG_DrawInformation( void ); + +// +// cg_scoreboard.c +// +qboolean CG_DrawOldScoreboard( void ); +void CG_DrawOldTourneyScoreboard( void ); + +// +// cg_consolecmds.c +// +qboolean CG_ConsoleCommand( void ); +void CG_InitConsoleCommands( void ); + +// +// cg_servercmds.c +// +void CG_ExecuteNewServerCommands( int latestSequence ); +void CG_ParseServerinfo( void ); +void CG_SetConfigValues( void ); +void CG_ShaderStateChanged(void); + +// +// cg_playerstate.c +// +int CG_IsMindTricked(int trickIndex1, int trickIndex2, int trickIndex3, int trickIndex4, int client); +void CG_Respawn( void ); +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); +void CG_CheckChangedPredictableEvents( playerState_t *ps ); + + +// +// cg_siege.c +// +void CG_InitSiegeMode(void); +void CG_SiegeRoundOver(centity_t *ent, int won); +void CG_SiegeObjectiveCompleted(centity_t *ent, int won, int objectivenum); + + + +//=============================================== + +// +// system traps +// These functions are how the cgame communicates with the main game system +// + +#include "../namespace_begin.h" + +// print message on the local console +void trap_Print( const char *fmt ); +void trap_PrintAlways( const char *fmt ); + +// abort the game +void trap_Error( const char *fmt ); + +// milliseconds should only be used for performance tuning, never +// for anything game related. Get time from the CG_DrawActiveFrame parameter +int trap_Milliseconds( void ); + +//rww - precision timer funcs... -ALWAYS- call end after start with supplied ptr, or you'll get a nasty memory leak. +//not that you should be using these outside of debug anyway.. because you shouldn't be. So don't. +void trap_PrecisionTimer_Start(void **theNewTimer); +int trap_PrecisionTimer_End(void *theTimer); + +// console variable interaction +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void trap_Cvar_Update( vmCvar_t *vmCvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +int trap_Cvar_GetHiddenVarValue(const char *name); + +// ServerCommand and ConsoleCommand parameter access +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Args( char *buffer, int bufferLength ); + +// filesystem access +// returns length of file +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); + +// add commands to the local console as if they were typed in +// for map changing, etc. The command is not executed immediately, +// but will be executed in order the next time console commands +// are processed +void trap_SendConsoleCommand( const char *text ); + +// register a command name so the console can perform command completion. +// FIXME: replace this with a normal console command "defineCommand"? +void trap_AddCommand( const char *cmdName ); + +// send a string to the server over the network +void trap_SendClientCommand( const char *s ); + +// force a screen update, only used during gamestate load +void trap_UpdateScreen( void ); + +// model collision +void trap_CM_LoadMap( const char *mapname, qboolean SubBSP ); +int trap_CM_NumInlineModels( void ); +clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ); +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ); + +// Returns the projection of a polygon onto the solid brushes in the world +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ); + +// normal sounds will have their volume dynamically changed as their entity +// moves and the listener moves +int trap_S_GetVoiceVolume( int entityNum ); +void trap_S_MuteSound( int entityNum, int entchannel ); +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +void trap_S_StopLoopingSound(int entnum); + +// a local sound is always played full volume +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +void trap_S_ClearLoopingSounds( void ); +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +// repatialize recalculates the volumes of sound as they should be heard by the +// given entityNum and position +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); +void trap_S_ShutUp(qboolean shutUpFactor); +sfxHandle_t trap_S_RegisterSound( const char *sample); // returns buzz if not found +void trap_S_StartBackgroundTrack( const char *intro, const char *loop, qboolean bReturnWithoutStarting); // empty name stops music +void trap_S_StopBackgroundTrack( void ); + +void trap_S_UpdateAmbientSet( const char *name, vec3_t origin ); +void trap_AS_ParseSets( void ); +void trap_AS_AddPrecacheEntry( const char *name ); +int trap_S_AddLocalSet( const char *name, vec3_t listener_origin, vec3_t origin, int entID, int time ); +sfxHandle_t trap_AS_GetBModelSound( const char *name, int stage ); + +void trap_R_LoadWorldMap( const char *mapname ); + +// all media should be registered during level startup to prevent +// hitches during gameplay +qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterFont( const char *name ); +int trap_R_Font_StrLenPixels(const char *text, const int iFontIndex, const float scale); +int trap_R_Font_StrLenChars(const char *text); +int trap_R_Font_HeightPixels(const int iFontIndex, const float scale); +void trap_R_Font_DrawString(int ox, int oy, const char *text, const float *rgba, const int setIndex, int iCharLimit, const float scale); +qboolean trap_Language_IsAsian(void); +qboolean trap_Language_UsesSpaces(void); +unsigned trap_AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation/* = NULL*/ ); + + +// a scene is built up by calls to R_ClearScene and the various R_Add functions. +// Nothing is drawn until R_RenderScene is called. +void trap_R_ClearScene( void ); +void trap_R_ClearDecals ( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); + +// polys are intended for simple wall marks, not really for doing +// significant construction +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); +void trap_R_AddDecalToScene ( qhandle_t shader, const vec3_t origin, const vec3_t dir, float orientation, float r, float g, float b, float a, qboolean alphaFade, float radius, qboolean temporary ); +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ); +// Does weird, barely controllable rotation behaviour +void trap_R_DrawRotatePic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ); +// rotates image around exact center point of passed in coords +void trap_R_DrawRotatePic2( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ); + +void trap_R_SetRangeFog(float range); + +void trap_R_SetRefractProp(float alpha, float stretch, qboolean prepost, qboolean negate); + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +void trap_R_GetLightStyle(int style, color4ub_t color); +void trap_R_SetLightStyle(int style, int color); + +void trap_R_GetBModelVerts(int bmodelIndex, vec3_t *verts, vec3_t normal ); + +void trap_R_GetDistanceCull(float *f); + +void trap_R_GetRealRes(int *w, int *h); //get screen resolution -rww +void trap_R_AutomapElevAdj(float newHeight); //automap elevation setting -rww +qboolean trap_R_InitWireframeAutomap(void); //initialize automap -rww + + +void trap_FX_AddLine( const vec3_t start, const vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + const vec3_t sRGB, const vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags); + +// The glconfig_t will not change during the life of a cgame. +// If it needs to change, the entire cgame will be restarted, because +// all the qhandle_t are then invalid. +void trap_GetGlconfig( glconfig_t *glconfig ); + +// the gamestate should be grabbed at startup, and whenever a +// configstring changes +void trap_GetGameState( gameState_t *gamestate ); + +// cgame will poll each frame to see if a newer snapshot has arrived +// that it is interested in. The time is returned seperately so that +// snapshot latency can be calculated. +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); + +// a snapshot get can fail if the snapshot (or the entties it holds) is so +// old that it has fallen out of the client system queue +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); + + +qboolean trap_GetDefaultState(int entityIndex, entityState_t *state ); + +// retrieve a text command from the server stream +// the current snapshot will hold the number of the most recent command +// qfalse can be returned if the client system handled the command +// argc() / argv() can be used to examine the parameters of the command +qboolean trap_GetServerCommand( int serverCommandNumber ); + +// returns the most recent command number that can be passed to GetUserCmd +// this will always be at least one higher than the number in the current +// snapshot, and it may be quite a few higher if it is a fast computer on +// a lagged connection +int trap_GetCurrentCmdNumber( void ); + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); + +// used for the weapon select and zoom +void trap_SetUserCmdValue( int stateValue, float sensitivityScale, float mPitchOverride, float mYawOverride, float mSensitivityOverride, int fpSel, int invenSel, qboolean fighterControls ); + +void trap_SetClientForceAngle(int time, vec3_t angle); +void trap_SetClientTurnExtent(float turnAdd, float turnSub, int turnTime); + +void trap_OpenUIMenu(int menuID); + +// aids for VM testing +void testPrintInt( char *string, int i ); +void testPrintFloat( char *string, float f ); + +int trap_MemoryRemaining( void ); +qboolean trap_Key_IsDown( int keynum ); +int trap_Key_GetCatcher( void ); +void trap_Key_SetCatcher( int catcher ); +int trap_Key_GetKey( const char *binding ); + +void BG_CycleInven(playerState_t *ps, int direction); +int BG_ProperForceIndex(int power); +void BG_CycleForce(playerState_t *ps, int direction); + +#include "../namespace_end.h" + + +typedef enum { + SYSTEM_PRINT, + CHAT_PRINT, + TEAMCHAT_PRINT +} q3print_t; // bk001201 - warning: useless keyword or type name in empty declaration + +#include "../namespace_begin.h" +/* +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status trap_CIN_StopCinematic(int handle); +e_status trap_CIN_RunCinematic (int handle); +void trap_CIN_DrawCinematic (int handle); +void trap_CIN_SetExtents (int handle, int x, int y, int w, int h); +*/ + +void trap_SnapVector( float *v ); + +qboolean trap_loadCamera(const char *name); +void trap_startCamera(int time); +qboolean trap_getCameraInfo(int time, vec3_t *origin, vec3_t *angles); + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ); +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2, byte *mask ); + +int trap_FX_InitSystem ( refdef_t* ); +void trap_FX_SetRefDef ( refdef_t* refdef ); +int trap_FX_RegisterEffect ( const char *file); +void trap_FX_PlayEffect ( const char *file, vec3_t org, vec3_t fwd, int vol, int rad ); // builds arbitrary perp. right vector, does a cross product to define up +void trap_FX_PlayEntityEffect ( const char *file, vec3_t org, vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ); +void trap_FX_PlayEffectID ( int id, vec3_t org, vec3_t fwd, int vol, int rad ); // builds arbitrary perp. right vector, does a cross product to define up +void trap_FX_PlayPortalEffectID ( int id, vec3_t org, vec3_t fwd, int vol, int rad ); // builds arbitrary perp. right vector, does a cross product to define up +void trap_FX_PlayEntityEffectID ( int id, vec3_t org, vec3_t axis[3], const int boltInfo, const int pGhoul2, int vol, int rad ); +void trap_FX_PlayBoltedEffectID ( int id, vec3_t org, void *pGhoul2, const int boltNum, const int entNum, const int modelNum, int iLooptime, qboolean isRelative ); +void trap_FX_AddScheduledEffects ( qboolean skyPortal ); +void trap_FX_Draw2DEffects ( float screenXScale, float screenYScale ); +qboolean trap_FX_FreeSystem ( void ); +void trap_FX_AdjustTime ( int time ); +void trap_FX_Reset ( void ); + +//rww - additional funcs for adding custom incode stuff +void trap_FX_AddPoly( addpolyArgStruct_t *p ); +void trap_FX_AddBezier( addbezierArgStruct_t *p ); +void trap_FX_AddPrimitive( effectTrailArgStruct_t *p ); +void trap_FX_AddSprite( addspriteArgStruct_t *p ); +void trap_FX_AddElectricity( addElectricityArgStruct_t *p ); + +//void trap_SP_Print(const unsigned ID, byte *Data); +int trap_SP_GetStringTextString(const char *text, char *buffer, int bufferLength); + +void trap_CG_RegisterSharedMemory(char *memory); + +int trap_CM_RegisterTerrain(const char *config); +void trap_RMG_Init(int terrainID, const char *terrainInfo); +void trap_RE_InitRendererTerrain( const char *info ); +void trap_R_WeatherContentsOverride( int contents ); +void trap_R_WorldEffectCommand(const char *cmd); +void trap_WE_AddWeatherZone( const vec3_t mins, const vec3_t maxs ); + +qboolean trap_ROFF_Clean( void ); +void trap_ROFF_UpdateEntities( void ); + int trap_ROFF_Cache( char *file ); +qboolean trap_ROFF_Play( int entID, int roffID, qboolean doTranslation ); +qboolean trap_ROFF_Purge_Ent( int entID ); + +//rww - dynamic vm memory allocation! +void trap_TrueMalloc(void **ptr, int size); +void trap_TrueFree(void **ptr); + +#include "../namespace_end.h" + +void CG_ClearParticles (void); +void CG_AddParticles (void); +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum); +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent); +void CG_AddParticleShrapnel (localEntity_t *le); +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent); +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration); +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed); +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir); +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha); +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd); +const char *CG_GetStringEdString(char *refSection, char *refName); +extern qboolean initparticles; +int CG_NewParticleArea ( int num ); + +void FX_TurretProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_TurretHitWall( vec3_t origin, vec3_t normal ); +void FX_TurretHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +void FX_ConcussionHitWall( vec3_t origin, vec3_t normal ); +void FX_ConcussionHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_ConcussionProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_ConcAltShot( vec3_t start, vec3_t end ); + +//----------------------------- +// Effects related prototypes +//----------------------------- + +// Environmental effects +void CG_Spark( vec3_t origin, vec3_t dir ); + +// Weapon prototypes +void FX_BryarHitWall( vec3_t origin, vec3_t normal ); +void FX_BryarAltHitWall( vec3_t origin, vec3_t normal, int power ); +void FX_BryarHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_BryarAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +void FX_BlasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterWeaponHitWall( vec3_t origin, vec3_t normal ); +void FX_BlasterWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + + +void FX_ForceDrained(vec3_t origin, vec3_t dir); + + +//----------------------------- +// Effects related prototypes +//----------------------------- + +// Environmental effects +void CG_Spark( vec3_t origin, vec3_t dir ); + +// Weapon prototypes +void FX_BryarHitWall( vec3_t origin, vec3_t normal ); +void FX_BryarAltHitWall( vec3_t origin, vec3_t normal, int power ); +void FX_BryarHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_BryarAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +void FX_BlasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterWeaponHitWall( vec3_t origin, vec3_t normal ); +void FX_BlasterWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +#include "../namespace_begin.h" + +void trap_G2API_GetModelName(void *ghoul2, const int modelIndex, + const char **modelName); + +void trap_G2API_CollisionDetect ( CollisionRecord_t *collRecMap, void* ghoul2, const vec3_t angles, const vec3_t position,int frameNumber, int entNum, const vec3_t rayStart, const vec3_t rayEnd, const vec3_t scale, int traceFlags, int useLod, float fRadius ); + +/* +Ghoul2 Insert Start +*/ +// CG specific API access +void trap_G2_ListModelSurfaces(void *ghlInfo); +void trap_G2_ListModelBones(void *ghlInfo, int frame); +void trap_G2_SetGhoul2ModelIndexes(void *ghoul2, qhandle_t *modelList, qhandle_t *skinList); +qboolean trap_G2_HaveWeGhoul2Models(void *ghoul2); +qboolean trap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); +qboolean trap_G2API_GetBoltMatrix_NoReconstruct(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); +qboolean trap_G2API_GetBoltMatrix_NoRecNoRot(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); +int trap_G2API_InitGhoul2Model(void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, + qhandle_t customShader, int modelFlags, int lodBias); +qboolean trap_G2API_SetSkin(void *ghoul2, int modelIndex, qhandle_t customSkin, qhandle_t renderSkin); + +int trap_G2API_CopyGhoul2Instance(void *g2From, void *g2To, int modelIndex); +void trap_G2API_CopySpecificGhoul2Model(void *g2From, int modelFrom, void *g2To, int modelTo); +void trap_G2API_DuplicateGhoul2Instance(void *g2From, void **g2To); +qboolean trap_G2API_HasGhoul2ModelOnIndex(void *ghlInfo, int modelIndex); +qboolean trap_G2API_RemoveGhoul2Model(void *ghlInfo, int modelIndex); + +qboolean trap_G2API_SkinlessModel(void *ghlInfo, int modelIndex); + +//rww - for adding gore (or whatever) shaders to the g2 model +int trap_G2API_GetNumGoreMarks(void *ghlInfo, int modelIndex); +void trap_G2API_AddSkinGore(void *ghlInfo,SSkinGoreData *gore); +void trap_G2API_ClearSkinGore ( void* ghlInfo ); + +int trap_G2API_Ghoul2Size ( void* ghlInfo ); + +int trap_G2API_AddBolt(void *ghoul2, int modelIndex, const char *boneName); +void trap_G2API_SetBoltInfo(void *ghoul2, int modelIndex, int boltInfo); +qboolean trap_G2API_AttachEnt(int *boltInfo, void *ghlInfoTo, int toBoltIndex, int entNum, int toModelNum); + +void trap_G2API_CleanGhoul2Models(void **ghoul2Ptr); +qboolean trap_G2API_SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ); +void trap_G2API_GetGLAName(void *ghoul2, int modelIndex, char *fillBuf); +qboolean trap_G2API_SetBoneAnim(void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame , const int blendTime ); +qboolean trap_G2API_GetBoneAnim(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *startFrame, + int *endFrame, int *flags, float *animSpeed, int *modelList, const int modelIndex); +qboolean trap_G2API_GetBoneFrame(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *modelList, const int modelIndex); + +qboolean trap_G2API_SetRootSurface(void *ghoul2, const int modelIndex, const char *surfaceName); +qboolean trap_G2API_SetSurfaceOnOff(void *ghoul2, const char *surfaceName, const int flags); +qboolean trap_G2API_SetNewOrigin(void *ghoul2, const int boltIndex); +qboolean trap_G2API_DoesBoneExist(void *ghoul2, int modelIndex, const char *boneName); +int trap_G2API_GetSurfaceRenderStatus(void *ghoul2, const int modelIndex, const char *surfaceName); + +int trap_G2API_GetTime(void); +void trap_G2API_SetTime(int time, int clock); + +void trap_G2API_AbsurdSmoothing(void *ghoul2, qboolean status); + +void trap_G2API_SetRagDoll(void *ghoul2, sharedRagDollParams_t *params); +void trap_G2API_AnimateG2Models(void *ghoul2, int time, sharedRagDollUpdateParams_t *params); + +//additional ragdoll options -rww +qboolean trap_G2API_RagPCJConstraint(void *ghoul2, const char *boneName, vec3_t min, vec3_t max); //override default pcj bonee constraints +qboolean trap_G2API_RagPCJGradientSpeed(void *ghoul2, const char *boneName, const float speed); //override the default gradient movespeed for a pcj bone +qboolean trap_G2API_RagEffectorGoal(void *ghoul2, const char *boneName, vec3_t pos); //override an effector bone's goal position (world coordinates) +qboolean trap_G2API_GetRagBonePos(void *ghoul2, const char *boneName, vec3_t pos, vec3_t entAngles, vec3_t entPos, vec3_t entScale); //current position of said bone is put into pos (world coordinates) +qboolean trap_G2API_RagEffectorKick(void *ghoul2, const char *boneName, vec3_t velocity); //add velocity to a rag bone +qboolean trap_G2API_RagForceSolve(void *ghoul2, qboolean force); //make sure we are actively performing solve/settle routines, if desired + +qboolean trap_G2API_SetBoneIKState(void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params); +qboolean trap_G2API_IKMove(void *ghoul2, int time, sharedIKMoveParams_t *params); + +//for removing bones so they no longer have their own seperate animation hierarchy. Or whatever reason you may have. -rww +qboolean trap_G2API_RemoveBone(void *ghoul2, const char *boneName, int modelIndex); + +void trap_G2API_AttachInstanceToEntNum(void *ghoul2, int entityNum, qboolean server); +void trap_G2API_ClearAttachedInstance(int entityNum); +void trap_G2API_CleanEntAttachments(void); +qboolean trap_G2API_OverrideServer(void *serverInstance); + +void trap_G2API_GetSurfaceName(void *ghoul2, int surfNumber, int modelIndex, char *fillBuf); + +#include "../namespace_end.h" + +void CG_Init_CG(void); +void CG_Init_CGents(void); + + +void CG_SetGhoul2Info( refEntity_t *ent, centity_t *cent); +void CG_CreateBBRefEnts(entityState_t *s1, vec3_t origin ); + +void CG_InitG2Weapons(void); +void CG_ShutDownG2Weapons(void); +void CG_CopyG2WeaponInstance(centity_t *cent, int weaponNum, void *toGhoul2); +void *CG_G2WeaponInstance(centity_t *cent, int weapon); +void CG_CheckPlayerG2Weapons(playerState_t *ps, centity_t *cent); + +void CG_SetSiegeTimerCvar( int msec ); + +bool CG_ModelAllowed(void *ghoul2); + +/* +Ghoul2 Insert End +*/ diff --git a/codemp/cgame/cg_localents.c b/codemp/cgame/cg_localents.c new file mode 100644 index 0000000..10259ff --- /dev/null +++ b/codemp/cgame/cg_localents.c @@ -0,0 +1,869 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +// cg_localents.c -- every frame, generate renderer commands for locally +// processed entities, like smoke puffs, gibs, shells, etc. + +#include "cg_local.h" + +#define MAX_LOCAL_ENTITIES 512 +localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; +localEntity_t cg_activeLocalEntities; // double linked list +localEntity_t *cg_freeLocalEntities; // single linked list + +/* +=================== +CG_InitLocalEntities + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitLocalEntities( void ) { + int i; + + memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); + cg_activeLocalEntities.next = &cg_activeLocalEntities; + cg_activeLocalEntities.prev = &cg_activeLocalEntities; + cg_freeLocalEntities = cg_localEntities; + for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) { + cg_localEntities[i].next = &cg_localEntities[i+1]; + } +} + + +/* +================== +CG_FreeLocalEntity +================== +*/ +void CG_FreeLocalEntity( localEntity_t *le ) { + if ( !le->prev ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // remove from the doubly linked active list + le->prev->next = le->next; + le->next->prev = le->prev; + + // the free list is only singly linked + le->next = cg_freeLocalEntities; + cg_freeLocalEntities = le; +} + +/* +=================== +CG_AllocLocalEntity + +Will allways succeed, even if it requires freeing an old active entity +=================== +*/ +localEntity_t *CG_AllocLocalEntity( void ) { + localEntity_t *le; + + if ( !cg_freeLocalEntities ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + CG_FreeLocalEntity( cg_activeLocalEntities.prev ); + } + + le = cg_freeLocalEntities; + cg_freeLocalEntities = cg_freeLocalEntities->next; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->next = cg_activeLocalEntities.next; + le->prev = &cg_activeLocalEntities; + cg_activeLocalEntities.next->prev = le; + cg_activeLocalEntities.next = le; + return le; +} + + +/* +==================================================================================== + +FRAGMENT PROCESSING + +A fragment localentity interacts with the environment in some way (hitting walls), +or generates more localentities along a trail. + +==================================================================================== +*/ + +/* +================ +CG_BloodTrail + +Leave expanding blood puffs behind gibs +================ +*/ +void CG_BloodTrail( localEntity_t *le ) { + int t; + int t2; + int step; + vec3_t newOrigin; + localEntity_t *blood; + + step = 150; + t = step * ( (cg->time - cg->frametime + step ) / step ); + t2 = step * ( cg->time / step ); + + for ( ; t <= t2; t += step ) { + BG_EvaluateTrajectory( &le->pos, t, newOrigin ); + + blood = CG_SmokePuff( newOrigin, vec3_origin, + 20, // radius + 1, 1, 1, 1, // color + 2000, // trailTime + t, // startTime + 0, // fadeInTime + 0, // flags + /*cgs.media.bloodTrailShader*/0 ); + // use the optimized version + blood->leType = LE_FALL_SCALE_FADE; + // drop a total of 40 units over its lifetime + blood->pos.trDelta[2] = 40; + } +} + + +/* +================ +CG_FragmentBounceMark +================ +*/ +void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { + int radius; + + if ( le->leMarkType == LEMT_BLOOD ) { + + radius = 16 + (rand()&31); +// CG_ImpactMark( cgs.media.bloodMarkShader, trace->endpos, trace->plane.normal, random()*360, +// 1,1,1,1, qtrue, radius, qfalse ); + } else if ( le->leMarkType == LEMT_BURN ) { + + radius = 8 + (rand()&15); +// CG_ImpactMark( cgs.media.burnMarkShader, trace->endpos, trace->plane.normal, random()*360, +// 1,1,1,1, qtrue, radius, qfalse ); + } + + + // don't allow a fragment to make multiple marks, or they + // pile up while settling + le->leMarkType = LEMT_NONE; +} + +/* +================ +CG_FragmentBounceSound +================ +*/ +void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { + // half the fragments will make a bounce sounds + if ( rand() & 1 ) + { + sfxHandle_t s = 0; + + switch( le->leBounceSoundType ) + { + case LEBS_ROCK: + s = cgs.media.rockBounceSound[Q_irand(0,1)]; + break; + case LEBS_METAL: + s = cgs.media.metalBounceSound[Q_irand(0,1)];// FIXME: make sure that this sound is registered properly...might still be rock bounce sound.... + break; + default: + return; + } + + if ( s ) + { + trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s ); + } + + // bouncers only make the sound once... + // FIXME: arbitrary...change if it bugs you + le->leBounceSoundType = LEBS_NONE; + } + else if ( rand() & 1 ) + { + // we may end up bouncing again, but each bounce reduces the chance of playing the sound again or they may make a lot of noise when they settle + // FIXME: maybe just always do this?? + le->leBounceSoundType = LEBS_NONE; + } +} + + +/* +================ +CG_ReflectVelocity +================ +*/ +void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = cg->time - cg->frametime + cg->frametime * trace->fraction; + BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2*dot, trace->plane.normal, le->pos.trDelta ); + + VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); + + VectorCopy( trace->endpos, le->pos.trBase ); + le->pos.trTime = cg->time; + + // check for stop, making sure that even on low FPS systems it doesn't bobble + if ( trace->allsolid || + ( trace->plane.normal[2] > 0 && + ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg->frametime * le->pos.trDelta[2] ) ) ) { + le->pos.trType = TR_STATIONARY; + } else { + + } +} + +/* +================ +CG_AddFragment +================ +*/ +void CG_AddFragment( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + + if (le->forceAlpha) + { + le->refEntity.renderfx |= RF_FORCE_ENT_ALPHA; + le->refEntity.shaderRGBA[3] = le->forceAlpha; + } + + if ( le->pos.trType == TR_STATIONARY ) { + // sink into the ground if near the removal time + int t; + float t_e; + + t = le->endTime - cg->time; + if ( t < (SINK_TIME*2) ) { + le->refEntity.renderfx |= RF_FORCE_ENT_ALPHA; + t_e = (float)((float)(le->endTime - cg->time)/(SINK_TIME*2)); + t_e = (int)((t_e)*255); + + if (t_e > 255) + { + t_e = 255; + } + if (t_e < 1) + { + t_e = 1; + } + + if (le->refEntity.shaderRGBA[3] && t_e > le->refEntity.shaderRGBA[3]) + { + t_e = le->refEntity.shaderRGBA[3]; + } + + le->refEntity.shaderRGBA[3] = t_e; + + trap_R_AddRefEntityToScene( &le->refEntity ); + } else { + trap_R_AddRefEntityToScene( &le->refEntity ); + } + + return; + } + + // calculate new position + BG_EvaluateTrajectory( &le->pos, cg->time, newOrigin ); + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); + if ( trace.fraction == 1.0 ) { + // still in free fall + VectorCopy( newOrigin, le->refEntity.origin ); + + if ( le->leFlags & LEF_TUMBLE ) { + vec3_t angles; + + BG_EvaluateTrajectory( &le->angles, cg->time, angles ); + AnglesToAxis( angles, le->refEntity.axis ); + ScaleModelAxis(&le->refEntity); + } + + trap_R_AddRefEntityToScene( &le->refEntity ); + + // add a blood trail + if ( le->leBounceSoundType == LEBS_BLOOD ) { + CG_BloodTrail( le ); + } + + return; + } + + // if it is in a nodrop zone, remove it + // this keeps gibs from waiting at the bottom of pits of death + // and floating levels + if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { + CG_FreeLocalEntity( le ); + return; + } + + if (!trace.startsolid) + { + // leave a mark + CG_FragmentBounceMark( le, &trace ); + + // do a bouncy sound + CG_FragmentBounceSound( le, &trace ); + + if (le->bounceSound) + { //specified bounce sound (debris) + trap_S_StartSound(le->pos.trBase, ENTITYNUM_WORLD, CHAN_AUTO, le->bounceSound); + } + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + + trap_R_AddRefEntityToScene( &le->refEntity ); + } +} + +/* +===================================================================== + +TRIVIAL LOCAL ENTITIES + +These only do simple scaling or modulation before passing to the renderer +===================================================================== +*/ + +/* +==================== +CG_AddFadeRGB +==================== +*/ +void CG_AddFadeRGB( localEntity_t *le ) { + refEntity_t *re; + float c; + + re = &le->refEntity; + + c = ( le->endTime - cg->time ) * le->lifeRate; + c *= 0xff; + + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + re->shaderRGBA[3] = le->color[3] * c; + + trap_R_AddRefEntityToScene( re ); +} + +static void CG_AddFadeScaleModel( localEntity_t *le ) +{ + refEntity_t *ent = &le->refEntity; + + float frac = ( cg->time - le->startTime )/((float)( le->endTime - le->startTime )); + + frac *= frac * frac; // yes, this is completely ridiculous...but it causes the shell to grow slowly then "explode" at the end + + ent->nonNormalizedAxes = qtrue; + + AxisCopy( axisDefault, ent->axis ); + + VectorScale( ent->axis[0], le->radius * frac, ent->axis[0] ); + VectorScale( ent->axis[1], le->radius * frac, ent->axis[1] ); + VectorScale( ent->axis[2], le->radius * 0.5f * frac, ent->axis[2] ); + + frac = 1.0f - frac; + + ent->shaderRGBA[0] = le->color[0] * frac; + ent->shaderRGBA[1] = le->color[1] * frac; + ent->shaderRGBA[2] = le->color[2] * frac; + ent->shaderRGBA[3] = le->color[3] * frac; + + // add the entity + trap_R_AddRefEntityToScene( ent ); +} + +/* +================== +CG_AddMoveScaleFade +================== +*/ +static void CG_AddMoveScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + if ( le->fadeInTime > le->startTime && cg->time < le->fadeInTime ) { + // fade / grow time + c = 1.0 - (float) ( le->fadeInTime - cg->time ) / ( le->fadeInTime - le->startTime ); + } + else { + // fade / grow time + c = ( le->endTime - cg->time ) * le->lifeRate; + } + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { + re->radius = le->radius * ( 1.0 - c ) + 8; + } + + BG_EvaluateTrajectory( &le->pos, cg->time, re->origin ); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg->refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + +/* +================== +CG_AddPuff +================== +*/ +static void CG_AddPuff( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade / grow time + c = ( le->endTime - cg->time ) / (float)( le->endTime - le->startTime ); + + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + + if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { + re->radius = le->radius * ( 1.0 - c ) + 8; + } + + BG_EvaluateTrajectory( &le->pos, cg->time, re->origin ); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg->refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + +/* +=================== +CG_AddScaleFade + +For rocket smokes that hang in place, fade out, and are +removed if the view passes through them. +There are often many of these, so it needs to be simple. +=================== +*/ +static void CG_AddScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade / grow time + c = ( le->endTime - cg->time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + re->radius = le->radius * ( 1.0 - c ) + 8; + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg->refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + +/* +================= +CG_AddFallScaleFade + +This is just an optimized CG_AddMoveScaleFade +For blood mists that drift down, fade out, and are +removed if the view passes through them. +There are often 100+ of these, so it needs to be simple. +================= +*/ +static void CG_AddFallScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade time + c = ( le->endTime - cg->time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2]; + + re->radius = le->radius * ( 1.0 - c ) + 16; + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg->refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + + +/* +================ +CG_AddExplosion +================ +*/ +static void CG_AddExplosion( localEntity_t *ex ) { + refEntity_t *ent; + + ent = &ex->refEntity; + + // add the entity + trap_R_AddRefEntityToScene(ent); + + // add the dlight + if ( ex->light ) { + float light; + + light = (float)( cg->time - ex->startTime ) / ( ex->endTime - ex->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = ex->light * light; + trap_R_AddLightToScene(ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2] ); + } +} + +/* +================ +CG_AddSpriteExplosion +================ +*/ +static void CG_AddSpriteExplosion( localEntity_t *le ) { + refEntity_t re; + float c; + + re = le->refEntity; + + c = ( le->endTime - cg->time ) / ( float ) ( le->endTime - le->startTime ); + if ( c > 1 ) { + c = 1.0; // can happen during connection problems + } + + re.shaderRGBA[0] = 0xff; + re.shaderRGBA[1] = 0xff; + re.shaderRGBA[2] = 0xff; + re.shaderRGBA[3] = 0xff * c * 0.33; + + re.reType = RT_SPRITE; + re.radius = 42 * ( 1.0 - c ) + 30; + + trap_R_AddRefEntityToScene( &re ); + + // add the dlight + if ( le->light ) { + float light; + + light = (float)( cg->time - le->startTime ) / ( le->endTime - le->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = le->light * light; + trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] ); + } +} + + +/* +=================== +CG_AddRefEntity +=================== +*/ +void CG_AddRefEntity( localEntity_t *le ) { + if (le->endTime < cg->time) { + CG_FreeLocalEntity( le ); + return; + } + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +/* +=================== +CG_AddScorePlum +=================== +*/ +#define NUMBER_SIZE 8 + +void CG_AddScorePlum( localEntity_t *le ) { + refEntity_t *re; + vec3_t origin, delta, dir, vec, up = {0, 0, 1}; + float c, len; + int i, score, digits[10], numdigits, negative; + + re = &le->refEntity; + + c = ( le->endTime - cg->time ) * le->lifeRate; + + score = le->radius; + if (score < 0) { + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0x11; + re->shaderRGBA[2] = 0x11; + } + else { + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + if (score >= 50) { + re->shaderRGBA[1] = 0; + } else if (score >= 20) { + re->shaderRGBA[0] = re->shaderRGBA[1] = 0; + } else if (score >= 10) { + re->shaderRGBA[2] = 0; + } else if (score >= 2) { + re->shaderRGBA[0] = re->shaderRGBA[2] = 0; + } + + } + if (c < 0.25) + re->shaderRGBA[3] = 0xff * 4 * c; + else + re->shaderRGBA[3] = 0xff; + + re->radius = NUMBER_SIZE / 2; + + VectorCopy(le->pos.trBase, origin); + origin[2] += 110 - c * 100; + + VectorSubtract(cg->refdef.vieworg, origin, dir); + CrossProduct(dir, up, vec); + VectorNormalize(vec); + + VectorMA(origin, -10 + 20 * sin(c * 2 * M_PI), vec, origin); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( origin, cg->refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < 20 ) { + CG_FreeLocalEntity( le ); + return; + } + + negative = qfalse; + if (score < 0) { + negative = qtrue; + score = -score; + } + + for (numdigits = 0; !(numdigits && !score); numdigits++) { + digits[numdigits] = score % 10; + score = score / 10; + } + + if (negative) { + digits[numdigits] = 10; + numdigits++; + } + + for (i = 0; i < numdigits; i++) { + VectorMA(origin, (float) (((float) numdigits / 2) - i) * NUMBER_SIZE, vec, re->origin); + re->customShader = cgs.media.numberShaders[digits[numdigits-1-i]]; + trap_R_AddRefEntityToScene( re ); + } +} + +/* +=================== +CG_AddOLine + +For forcefields/other rectangular things +=================== +*/ +void CG_AddOLine( localEntity_t *le ) +{ + refEntity_t *re; + float frac, alpha; + + re = &le->refEntity; + + frac = (cg->time - le->startTime) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + // Use the liferate to set the scale over time. + re->data.line.width = le->data.line.width + (le->data.line.dwidth * frac); + if (re->data.line.width <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + // We will assume here that we want additive transparency effects. + alpha = le->alpha + (le->dalpha * frac); + re->shaderRGBA[0] = 0xff * alpha; + re->shaderRGBA[1] = 0xff * alpha; + re->shaderRGBA[2] = 0xff * alpha; + re->shaderRGBA[3] = 0xff * alpha; // Yes, we could apply c to this too, but fading the color is better for lines. + + re->shaderTexCoord[0] = 1; + re->shaderTexCoord[1] = 1; + + re->rotation = 90; + + re->reType = RT_ORIENTEDLINE; + + trap_R_AddRefEntityToScene( re ); +} + +/* +=================== +CG_AddLine + +for beams and the like. +=================== +*/ +void CG_AddLine( localEntity_t *le ) +{ + refEntity_t *re; + + re = &le->refEntity; + + re->reType = RT_LINE; + + trap_R_AddRefEntityToScene( re ); +} + +//============================================================================== + +/* +=================== +CG_AddLocalEntities + +=================== +*/ +void CG_AddLocalEntities( void ) { + localEntity_t *le, *next; + + // walk the list backwards, so any new local entities generated + // (trails, marks, etc) will be present this frame + le = cg_activeLocalEntities.prev; + for ( ; le != &cg_activeLocalEntities ; le = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = le->prev; + + if ( cg->time >= le->endTime ) { + CG_FreeLocalEntity( le ); + continue; + } + switch ( le->leType ) { + default: + CG_Error( "Bad leType: %i", le->leType ); + break; + + case LE_MARK: + break; + + case LE_SPRITE_EXPLOSION: + CG_AddSpriteExplosion( le ); + break; + + case LE_EXPLOSION: + CG_AddExplosion( le ); + break; + + case LE_FADE_SCALE_MODEL: + CG_AddFadeScaleModel( le ); + break; + + case LE_FRAGMENT: // gibs and brass + CG_AddFragment( le ); + break; + + case LE_PUFF: + CG_AddPuff( le ); + break; + + case LE_MOVE_SCALE_FADE: // water bubbles + CG_AddMoveScaleFade( le ); + break; + + case LE_FADE_RGB: // teleporters, railtrails + CG_AddFadeRGB( le ); + break; + + case LE_FALL_SCALE_FADE: // gib blood trails + CG_AddFallScaleFade( le ); + break; + + case LE_SCALE_FADE: // rocket trails + CG_AddScaleFade( le ); + break; + + case LE_SCOREPLUM: + CG_AddScorePlum( le ); + break; + + case LE_OLINE: + CG_AddOLine( le ); + break; + + case LE_SHOWREFENTITY: + CG_AddRefEntity( le ); + break; + + case LE_LINE: // oriented lines for FX + CG_AddLine( le ); + break; + } + } +} + + + + diff --git a/codemp/cgame/cg_main.c b/codemp/cgame/cg_main.c new file mode 100644 index 0000000..17d7569 --- /dev/null +++ b/codemp/cgame/cg_main.c @@ -0,0 +1,4319 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_main.c -- initialization and primary entry point for cgame +#include "cg_local.h" + +#include "../ui/ui_shared.h" +// display context for new ui stuff +displayContextDef_t cgDC; + +#if !defined(CL_LIGHT_H_INC) + #include "cg_lights.h" +#endif + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + +extern int cgSiegeRoundState; +extern int cgSiegeRoundTime; +/* +Ghoul2 Insert Start +*/ +void CG_InitItems(void); +/* +Ghoul2 Insert End +*/ + +void CG_InitJetpackGhoul2(void); +void CG_CleanJetpackGhoul2(void); + +vec4_t colorTable[CT_MAX] = +{ +{0, 0, 0, 0}, // CT_NONE +{0, 0, 0, 1}, // CT_BLACK +{1, 0, 0, 1}, // CT_RED +{0, 1, 0, 1}, // CT_GREEN +{0, 0, 1, 1}, // CT_BLUE +{1, 1, 0, 1}, // CT_YELLOW +{1, 0, 1, 1}, // CT_MAGENTA +{0, 1, 1, 1}, // CT_CYAN +{1, 1, 1, 1}, // CT_WHITE +{0.75f, 0.75f, 0.75f, 1}, // CT_LTGREY +{0.50f, 0.50f, 0.50f, 1}, // CT_MDGREY +{0.25f, 0.25f, 0.25f, 1}, // CT_DKGREY +{0.15f, 0.15f, 0.15f, 1}, // CT_DKGREY2 + +{0.810f, 0.530f, 0.0f, 1}, // CT_VLTORANGE -- needs values +{0.810f, 0.530f, 0.0f, 1}, // CT_LTORANGE +{0.610f, 0.330f, 0.0f, 1}, // CT_DKORANGE +{0.402f, 0.265f, 0.0f, 1}, // CT_VDKORANGE + +{0.503f, 0.375f, 0.996f, 1}, // CT_VLTBLUE1 +{0.367f, 0.261f, 0.722f, 1}, // CT_LTBLUE1 +{0.199f, 0.0f, 0.398f, 1}, // CT_DKBLUE1 +{0.160f, 0.117f, 0.324f, 1}, // CT_VDKBLUE1 + +{0.300f, 0.628f, 0.816f, 1}, // CT_VLTBLUE2 -- needs values +{0.300f, 0.628f, 0.816f, 1}, // CT_LTBLUE2 +{0.191f, 0.289f, 0.457f, 1}, // CT_DKBLUE2 +{0.125f, 0.250f, 0.324f, 1}, // CT_VDKBLUE2 + +{0.796f, 0.398f, 0.199f, 1}, // CT_VLTBROWN1 -- needs values +{0.796f, 0.398f, 0.199f, 1}, // CT_LTBROWN1 +{0.558f, 0.207f, 0.027f, 1}, // CT_DKBROWN1 +{0.328f, 0.125f, 0.035f, 1}, // CT_VDKBROWN1 + +{0.996f, 0.796f, 0.398f, 1}, // CT_VLTGOLD1 -- needs values +{0.996f, 0.796f, 0.398f, 1}, // CT_LTGOLD1 +{0.605f, 0.441f, 0.113f, 1}, // CT_DKGOLD1 +{0.386f, 0.308f, 0.148f, 1}, // CT_VDKGOLD1 + +{0.648f, 0.562f, 0.784f, 1}, // CT_VLTPURPLE1 -- needs values +{0.648f, 0.562f, 0.784f, 1}, // CT_LTPURPLE1 +{0.437f, 0.335f, 0.597f, 1}, // CT_DKPURPLE1 +{0.308f, 0.269f, 0.375f, 1}, // CT_VDKPURPLE1 + +{0.816f, 0.531f, 0.710f, 1}, // CT_VLTPURPLE2 -- needs values +{0.816f, 0.531f, 0.710f, 1}, // CT_LTPURPLE2 +{0.566f, 0.269f, 0.457f, 1}, // CT_DKPURPLE2 +{0.343f, 0.226f, 0.316f, 1}, // CT_VDKPURPLE2 + +{0.929f, 0.597f, 0.929f, 1}, // CT_VLTPURPLE3 +{0.570f, 0.371f, 0.570f, 1}, // CT_LTPURPLE3 +{0.355f, 0.199f, 0.355f, 1}, // CT_DKPURPLE3 +{0.285f, 0.136f, 0.230f, 1}, // CT_VDKPURPLE3 + +{0.953f, 0.378f, 0.250f, 1}, // CT_VLTRED1 +{0.953f, 0.378f, 0.250f, 1}, // CT_LTRED1 +{0.593f, 0.121f, 0.109f, 1}, // CT_DKRED1 +{0.429f, 0.171f, 0.113f, 1}, // CT_VDKRED1 +{.25f, 0, 0, 1}, // CT_VDKRED +{.70f, 0, 0, 1}, // CT_DKRED + +{0.717f, 0.902f, 1.0f, 1}, // CT_VLTAQUA +{0.574f, 0.722f, 0.804f, 1}, // CT_LTAQUA +{0.287f, 0.361f, 0.402f, 1}, // CT_DKAQUA +{0.143f, 0.180f, 0.201f, 1}, // CT_VDKAQUA + +{0.871f, 0.386f, 0.375f, 1}, // CT_LTPINK +{0.435f, 0.193f, 0.187f, 1}, // CT_DKPINK +{ 0, .5f, .5f, 1}, // CT_LTCYAN +{ 0, .25f, .25f, 1}, // CT_DKCYAN +{ .179f, .51f, .92f, 1}, // CT_LTBLUE3 +{ .199f, .71f, .92f, 1}, // CT_LTBLUE3 +{ .5f, .05f, .4f, 1}, // CT_DKBLUE3 + +{ 0.0f, .613f, .097f, 1}, // CT_HUD_GREEN +{ 0.835f, .015f, .015f, 1}, // CT_HUD_RED +{ .567f, .685f, 1.0f, .75f}, // CT_ICON_BLUE +{ .515f, .406f, .507f, 1}, // CT_NO_AMMO_RED +{ 1.0f, .658f, .062f, 1}, // CT_HUD_ORANGE + +}; + +#include "holocronicons.h" + +int cgWeatherOverride = 0; + +int forceModelModificationCount = -1; + +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); +void CG_Shutdown( void ); + +void CG_CalcEntityLerpPositions( centity_t *cent ); +void CG_ROFF_NotetrackCallback( centity_t *cent, const char *notetrack); + +#include "../namespace_begin.h" +void UI_CleanupGhoul2(void); +#include "../namespace_end.h" + +static int C_PointContents(void); +static void C_GetLerpOrigin(void); +static void C_GetLerpData(void); +static void C_Trace(void); +static void C_G2Trace(void); +static void C_G2Mark(void); +static int CG_RagCallback(int callType); +static void C_GetBoltPos(void); +static void C_ImpactMark(void); + +#ifdef _XBOX +#define MAX_MISC_ENTS 200 +#else +#define MAX_MISC_ENTS 4000 +#endif + +//static refEntity_t *MiscEnts = 0; +//static float *Radius = 0; +static refEntity_t MiscEnts[MAX_MISC_ENTS]; //statically allocated for now. +static float Radius[MAX_MISC_ENTS]; +static float zOffset[MAX_MISC_ENTS]; //some models need a z offset for culling, because of stupid wrong model origins + +int NumMiscEnts = 0; + +extern autoMapInput_t cg_autoMapInput; //cg_view.c +extern int cg_autoMapInputTime; +extern vec3_t cg_autoMapAngle; + +void CG_MiscEnt(void); + +//do we have any force powers that we would normally need to cycle to? +qboolean CG_NoUseableForce(void) +{ + int i = FP_HEAL; + while (i < NUM_FORCE_POWERS) + { + if (i != FP_SABERTHROW && + i != FP_SABER_OFFENSE && + i != FP_SABER_DEFENSE && + i != FP_LEVITATION) + { //valid selectable power + if (cg->predictedPlayerState.fd.forcePowersKnown & (1 << i)) + { //we have it + return qfalse; + } + } + i++; + } + + //no useable force powers, I guess. + return qtrue; +} + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +#include "../namespace_begin.h" +int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) { + + switch ( command ) { + case CG_INIT: + CG_Init( arg0, arg1, arg2 ); + return 0; + case CG_SHUTDOWN: + CG_Shutdown(); + return 0; + case CG_CONSOLE_COMMAND: + return CG_ConsoleCommand(); + case CG_DRAW_ACTIVE_FRAME: + CG_DrawActiveFrame( arg0, arg1, arg2 ); + return 0; + case CG_CROSSHAIR_PLAYER: + return CG_CrosshairPlayer(); + case CG_LAST_ATTACKER: + return CG_LastAttacker(); + case CG_KEY_EVENT: + CG_KeyEvent(arg0, arg1); + return 0; + case CG_MOUSE_EVENT: + cgDC.cursorx = cgs.cursorX; + cgDC.cursory = cgs.cursorY; + CG_MouseEvent(arg0, arg1); + return 0; + case CG_EVENT_HANDLING: + CG_EventHandling(arg0); + return 0; + + case CG_POINT_CONTENTS: + return C_PointContents(); + + case CG_GET_LERP_ORIGIN: + C_GetLerpOrigin(); + return 0; + + case CG_GET_LERP_DATA: + C_GetLerpData(); + return 0; + + case CG_GET_GHOUL2: + return (int)cg_entities[arg0].ghoul2; //NOTE: This is used by the effect bolting which is actually not used at all. + //I'm fairly sure if you try to use it with vm's it will just give you total + //garbage. In other words, use at your own risk. + + case CG_GET_MODEL_LIST: + return (int)cgs.gameModels; + + case CG_CALC_LERP_POSITIONS: + CG_CalcEntityLerpPositions( &cg_entities[arg0] ); + return 0; + + case CG_TRACE: + C_Trace(); + return 0; + case CG_GET_SORTED_FORCE_POWER: + return forcePowerSorted[arg0]; + case CG_G2TRACE: + C_G2Trace(); + return 0; + + case CG_G2MARK: + C_G2Mark(); + return 0; + + case CG_RAG_CALLBACK: + return CG_RagCallback(arg0); + + case CG_INCOMING_CONSOLE_COMMAND: + //rww - let mod authors filter client console messages so they can cut them off if they want. + //return 1 if the command is ok. Otherwise, you can set char 0 on the command str to 0 and return + //0 to not execute anything, or you can fill conCommand in with something valid and return 0 + //in order to have that string executed in place. Some example code: +#if 0 + { + TCGIncomingConsoleCommand *icc = (TCGIncomingConsoleCommand *)cg->sharedBuffer; + if (strstr(icc->conCommand, "wait")) + { //filter out commands contaning wait + Com_Printf("You can't use commands containing the string wait with MyMod v1.0\n"); + icc->conCommand[0] = 0; + return 0; + } + else if (strstr(icc->conCommand, "blah")) + { //any command containing the string "blah" is redirected to "quit" + strcpy(icc->conCommand, "quit"); + return 0; + } + } +#endif + return 1; + + case CG_GET_USEABLE_FORCE: + return CG_NoUseableForce(); + + case CG_GET_ORIGIN: + VectorCopy(cg_entities[arg0].currentState.pos.trBase, (float *)arg1); + return 0; + + case CG_GET_ANGLES: + VectorCopy(cg_entities[arg0].currentState.apos.trBase, (float *)arg1); + return 0; + + case CG_GET_ORIGIN_TRAJECTORY: + return (int)&cg_entities[arg0].nextState.pos; + + case CG_GET_ANGLE_TRAJECTORY: + return (int)&cg_entities[arg0].nextState.apos; + + case CG_ROFF_NOTETRACK_CALLBACK: + CG_ROFF_NotetrackCallback( &cg_entities[arg0], (const char *)arg1 ); + return 0; + + case CG_IMPACT_MARK: + C_ImpactMark(); + return 0; + + case CG_MAP_CHANGE: + // this trap map be called more than once for a given map change, as the + // server is going to attempt to send out multiple broadcasts in hopes that + // the client will receive one of them + cg->mMapChange = qtrue; + return 0; + + case CG_AUTOMAP_INPUT: + //special input during automap mode -rww + { + autoMapInput_t *autoInput = (autoMapInput_t *)cg->sharedBuffer; + + memcpy(&cg_autoMapInput, autoInput, sizeof(autoMapInput_t)); + + if (!arg0) + { //if this is non-0, it's actually a one-frame mouse event + cg_autoMapInputTime = cg->time + 1000; + } + else + { + if (cg_autoMapInput.yaw) + { + cg_autoMapAngle[YAW] += cg_autoMapInput.yaw; + } + + if (cg_autoMapInput.pitch) + { + cg_autoMapAngle[PITCH] += cg_autoMapInput.pitch; + } + cg_autoMapInput.yaw = 0.0f; + cg_autoMapInput.pitch = 0.0f; + } + } + return 0; + + case CG_MISC_ENT: + CG_MiscEnt(); + return 0; + + default: + CG_Error( "vmMain: unknown command %i", command ); + break; + } + return -1; +} +#include "../namespace_end.h" + +static int C_PointContents(void) +{ + TCGPointContents *data = (TCGPointContents *)cg->sharedBuffer; + + return CG_PointContents( data->mPoint, data->mPassEntityNum ); +} + +static void C_GetLerpOrigin(void) +{ + TCGVectorData *data = (TCGVectorData *)cg->sharedBuffer; + + VectorCopy(cg_entities[data->mEntityNum].lerpOrigin, data->mPoint); +} + +static void C_GetLerpData(void) +{//only used by FX system to pass to getboltmat + TCGGetBoltData *data = (TCGGetBoltData *)cg->sharedBuffer; + + VectorCopy(cg_entities[data->mEntityNum].lerpOrigin, data->mOrigin); + VectorCopy(cg_entities[data->mEntityNum].modelScale, data->mScale); + VectorCopy(cg_entities[data->mEntityNum].lerpAngles, data->mAngles); + if (cg_entities[data->mEntityNum].currentState.eType == ET_PLAYER) + { //normal player + data->mAngles[PITCH] = 0.0f; + data->mAngles[ROLL] = 0.0f; + } + else if (cg_entities[data->mEntityNum].currentState.eType == ET_NPC) + { //an NPC + Vehicle_t *pVeh = cg_entities[data->mEntityNum].m_pVehicle; + if (!pVeh) + { //for vehicles, we may or may not want to 0 out pitch and roll + data->mAngles[PITCH] = 0.0f; + data->mAngles[ROLL] = 0.0f; + } + else if (pVeh->m_pVehicleInfo->type == VH_SPEEDER) + { //speeder wants no pitch but a roll + data->mAngles[PITCH] = 0.0f; + } + else if (pVeh->m_pVehicleInfo->type != VH_FIGHTER) + { //fighters want all angles + data->mAngles[PITCH] = 0.0f; + data->mAngles[ROLL] = 0.0f; + } + } +} + +static void C_Trace(void) +{ + TCGTrace *td = (TCGTrace *)cg->sharedBuffer; + + CG_Trace(&td->mResult, td->mStart, td->mMins, td->mMaxs, td->mEnd, td->mSkipNumber, td->mMask); +} + +static void C_G2Trace(void) +{ + TCGTrace *td = (TCGTrace *)cg->sharedBuffer; + + CG_G2Trace(&td->mResult, td->mStart, td->mMins, td->mMaxs, td->mEnd, td->mSkipNumber, td->mMask); +} + +static void C_G2Mark(void) +{ + TCGG2Mark *td = (TCGG2Mark *)cg->sharedBuffer; + trace_t tr; + vec3_t end; + + VectorMA(td->start, 64, td->dir, end); + CG_G2Trace(&tr, td->start, NULL, NULL, end, ENTITYNUM_NONE, MASK_PLAYERSOLID); + + if (tr.entityNum < ENTITYNUM_WORLD && + cg_entities[tr.entityNum].ghoul2) + { //hit someone with a ghoul2 instance, let's project the decal on them then. + centity_t *cent = &cg_entities[tr.entityNum]; + + //CG_TestLine(tr.endpos, end, 2000, 0x0000ff, 1); + + CG_AddGhoul2Mark(td->shader, td->size, tr.endpos, end, tr.entityNum, + cent->lerpOrigin, cent->lerpAngles[YAW], cent->ghoul2, cent->modelScale, + Q_irand(2000, 4000)); + //I'm making fx system decals have a very short lifetime. + } +} + +static void CG_DebugBoxLines(vec3_t mins, vec3_t maxs, int duration) +{ + vec3_t start; + vec3_t end; + vec3_t vert; + + float x = maxs[0] - mins[0]; + float y = maxs[1] - mins[1]; + + start[2] = maxs[2]; + vert[2] = mins[2]; + + vert[0] = mins[0]; + vert[1] = mins[1]; + start[0] = vert[0]; + start[1] = vert[1]; + CG_TestLine(start, vert, duration, 0x00000ff, 1); + + vert[0] = mins[0]; + vert[1] = maxs[1]; + start[0] = vert[0]; + start[1] = vert[1]; + CG_TestLine(start, vert, duration, 0x00000ff, 1); + + vert[0] = maxs[0]; + vert[1] = mins[1]; + start[0] = vert[0]; + start[1] = vert[1]; + CG_TestLine(start, vert, duration, 0x00000ff, 1); + + vert[0] = maxs[0]; + vert[1] = maxs[1]; + start[0] = vert[0]; + start[1] = vert[1]; + CG_TestLine(start, vert, duration, 0x00000ff, 1); + + // top of box + VectorCopy(maxs, start); + VectorCopy(maxs, end); + start[0] -= x; + CG_TestLine(start, end, duration, 0x00000ff, 1); + end[0] = start[0]; + end[1] -= y; + CG_TestLine(start, end, duration, 0x00000ff, 1); + start[1] = end[1]; + start[0] += x; + CG_TestLine(start, end, duration, 0x00000ff, 1); + CG_TestLine(start, maxs, duration, 0x00000ff, 1); + // bottom of box + VectorCopy(mins, start); + VectorCopy(mins, end); + start[0] += x; + CG_TestLine(start, end, duration, 0x00000ff, 1); + end[0] = start[0]; + end[1] += y; + CG_TestLine(start, end, duration, 0x00000ff, 1); + start[1] = end[1]; + start[0] -= x; + CG_TestLine(start, end, duration, 0x00000ff, 1); + CG_TestLine(start, mins, duration, 0x00000ff, 1); +} + +//handle ragdoll callbacks, for events and debugging -rww +static int CG_RagCallback(int callType) +{ + switch(callType) + { + case RAG_CALLBACK_DEBUGBOX: + { + ragCallbackDebugBox_t *callData = (ragCallbackDebugBox_t *)cg->sharedBuffer; + + CG_DebugBoxLines(callData->mins, callData->maxs, callData->duration); + } + break; + case RAG_CALLBACK_DEBUGLINE: + { + ragCallbackDebugLine_t *callData = (ragCallbackDebugLine_t *)cg->sharedBuffer; + + CG_TestLine(callData->start, callData->end, callData->time, callData->color, callData->radius); + } + break; + case RAG_CALLBACK_BONESNAP: + { + ragCallbackBoneSnap_t *callData = (ragCallbackBoneSnap_t *)cg->sharedBuffer; + centity_t *cent = &cg_entities[callData->entNum]; + int snapSound = trap_S_RegisterSound(va("sound/player/bodyfall_human%i.wav", Q_irand(1, 3))); + + trap_S_StartSound(cent->lerpOrigin, callData->entNum, CHAN_AUTO, snapSound); + } + case RAG_CALLBACK_BONEIMPACT: + break; + case RAG_CALLBACK_BONEINSOLID: +#if 0 + { + ragCallbackBoneInSolid_t *callData = (ragCallbackBoneInSolid_t *)cg->sharedBuffer; + + if (callData->solidCount > 16) + { //don't bother if we're just tapping into solidity, we'll probably recover on our own + centity_t *cent = &cg_entities[callData->entNum]; + vec3_t slideDir; + + VectorSubtract(cent->lerpOrigin, callData->bonePos, slideDir); + VectorAdd(cent->ragOffsets, slideDir, cent->ragOffsets); + + cent->hasRagOffset = qtrue; + } + } +#endif + break; + case RAG_CALLBACK_TRACELINE: + { + ragCallbackTraceLine_t *callData = (ragCallbackTraceLine_t *)cg->sharedBuffer; + + CG_Trace(&callData->tr, callData->start, callData->mins, callData->maxs, + callData->end, callData->ignore, callData->mask); + } + break; + default: + Com_Error(ERR_DROP, "Invalid callType in CG_RagCallback"); + break; + } + + return 0; +} + +static void C_ImpactMark(void) +{ + TCGImpactMark *data = (TCGImpactMark *)cg->sharedBuffer; + + /* + CG_ImpactMark((int)arg0, (const float *)arg1, (const float *)arg2, (float)arg3, + (float)arg4, (float)arg5, (float)arg6, (float)arg7, qtrue, (float)arg8, qfalse); + */ + CG_ImpactMark(data->mHandle, data->mPoint, data->mAngle, data->mRotation, + data->mRed, data->mGreen, data->mBlue, data->mAlphaStart, qtrue, data->mSizeStart, qfalse); +} + +void CG_MiscEnt(void) +{ + int modelIndex; + refEntity_t *RefEnt; + TCGMiscEnt *data = (TCGMiscEnt *)cg->sharedBuffer; + vec3_t mins, maxs; + float *radius, *zOff; + + if (NumMiscEnts >= MAX_MISC_ENTS) + { + return; + } + + radius = &Radius[NumMiscEnts]; + zOff = &zOffset[NumMiscEnts]; + RefEnt = &MiscEnts[NumMiscEnts++]; + + modelIndex = trap_R_RegisterModel(data->mModel); + if (modelIndex == 0) + { + Com_Error(ERR_DROP, "client_model has invalid model definition"); + return; + } + + *zOff = 0; + + memset(RefEnt, 0, sizeof(refEntity_t)); + RefEnt->reType = RT_MODEL; + RefEnt->hModel = modelIndex; + RefEnt->frame = 0; + trap_R_ModelBounds(modelIndex, mins, maxs); + VectorCopy(data->mScale, RefEnt->modelScale); + VectorCopy(data->mOrigin, RefEnt->origin); + + VectorScaleVector(mins, data->mScale, mins); + VectorScaleVector(maxs, data->mScale, maxs); + *radius = Distance(mins, maxs); + + AnglesToAxis( data->mAngles, RefEnt->axis ); + ScaleModelAxis(RefEnt); +} + +void CG_DrawMiscEnts(void) +{ + int i; + refEntity_t *RefEnt; + float *radius, *zOff; + vec3_t difference; + vec3_t cullOrigin; + + RefEnt = MiscEnts; + radius = Radius; + zOff = zOffset; + for(i=0;iorigin, cullOrigin); + cullOrigin[2] += 1.0f; + + if (*zOff) + { + cullOrigin[2] += *zOff; + } + + if (cg->snap && trap_R_inPVS(cg->refdef.vieworg, cullOrigin, cg->snap->areamask)) + { + VectorSubtract(RefEnt->origin, cg->refdef.vieworg, difference); + if (VectorLength(difference)-(*radius) <= cg->distanceCull) + { + trap_R_AddRefEntityToScene(RefEnt); + } + } + RefEnt++; + radius++; + zOff++; + } +} + +/* +Ghoul2 Insert Start +*/ +/* +void CG_ResizeG2Bolt(boltInfo_v *bolt, int newCount) +{ + bolt->resize(newCount); +} + +void CG_ResizeG2Surface(surfaceInfo_v *surface, int newCount) +{ + surface->resize(newCount); +} + +void CG_ResizeG2Bone(boneInfo_v *bone, int newCount) +{ + bone->resize(newCount); +} + +void CG_ResizeG2(CGhoul2Info_v *ghoul2, int newCount) +{ + ghoul2->resize(newCount); +} + +void CG_ResizeG2TempBone(mdxaBone_v *tempBone, int newCount) +{ + tempBone->resize(newCount); +} +*/ +/* +Ghoul2 Insert End +*/ +// MATT - Changed this to a pointer for splitscreen client swapping purposes +//cg_t g_cg; +cg_t *cg = NULL; //&g_cg; + +cgs_t cgs; +//centity_t cg_entities[MAX_GENTITIES]; +centity_t *cg_entities = NULL; + +centity_t *cg_permanents[MAX_GENTITIES]; //rwwRMG - added +int cg_numpermanents = 0; + +weaponInfo_t cg_weapons[MAX_WEAPONS]; +itemInfo_t cg_items[MAX_ITEMS]; + + +vmCvar_t cg_centertime; +vmCvar_t cg_runpitch; +vmCvar_t cg_runroll; +vmCvar_t cg_bobup; +vmCvar_t cg_bobpitch; +vmCvar_t cg_bobroll; +//vmCvar_t cg_swingSpeed; +vmCvar_t cg_shadows; +vmCvar_t cg_renderToTextureFX; +vmCvar_t cg_drawTimer; +vmCvar_t cg_drawFPS; +//vmCvar_t cg_drawSnapshot; +vmCvar_t cg_draw3dIcons; +vmCvar_t cg_drawIcons; +vmCvar_t cg_drawAmmoWarning; +vmCvar_t cg_drawCrosshair; +vmCvar_t cg_drawCrosshairNames; +vmCvar_t cg_drawRadar; +vmCvar_t cg_dynamicCrosshair; +vmCvar_t cg_dynamicCrosshairPrecision; +vmCvar_t cg_drawRewards; +vmCvar_t cg_drawScores; +vmCvar_t cg_crosshairSize; +vmCvar_t cg_crosshairX; +vmCvar_t cg_crosshairY; +vmCvar_t cg_crosshairHealth; +vmCvar_t cg_draw2D; +vmCvar_t cg_drawStatus; +vmCvar_t cg_animSpeed; +vmCvar_t cg_debugAnim; +vmCvar_t cg_debugSaber; +vmCvar_t cg_debugPosition; +vmCvar_t cg_debugEvents; +vmCvar_t cg_errorDecay; +vmCvar_t cg_nopredict; +vmCvar_t cg_noPlayerAnims; +vmCvar_t cg_showmiss; +vmCvar_t cg_showVehMiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_addMarks; +vmCvar_t cg_viewsize; +vmCvar_t cg_drawGun; +vmCvar_t cg_gun_frame; +vmCvar_t cg_gun_x; +vmCvar_t cg_gun_y; +vmCvar_t cg_gun_z; +vmCvar_t cg_autoswitch; +vmCvar_t cg_ignore; +vmCvar_t cg_simpleItems; +vmCvar_t cg_fov; +vmCvar_t cg_zoomFov; + +vmCvar_t cg_swingAngles; + +vmCvar_t cg_oldPainSounds; + +vmCvar_t cg_ragDoll; + +vmCvar_t cg_jumpSounds; + +vmCvar_t cg_autoMap; +vmCvar_t cg_autoMapX; +vmCvar_t cg_autoMapY; +vmCvar_t cg_autoMapW; +vmCvar_t cg_autoMapH; + +#ifndef _XBOX // Hmmm. This is also in game. I think this is safe. +vmCvar_t bg_fighterAltControl; +#endif + +vmCvar_t cg_chatBox; +vmCvar_t cg_chatBoxHeight; + +vmCvar_t cg_saberModelTraceEffect; + +vmCvar_t cg_saberClientVisualCompensation; + +vmCvar_t cg_g2TraceLod; + +vmCvar_t cg_fpls; + +vmCvar_t cg_ghoul2Marks; + +vmCvar_t cg_saberDynamicMarks; +vmCvar_t cg_saberDynamicMarkTime; + +vmCvar_t cg_saberContact; +vmCvar_t cg_saberTrail; + +vmCvar_t cg_duelHeadAngles; + +vmCvar_t cg_speedTrail; +vmCvar_t cg_auraShell; + +vmCvar_t cg_repeaterOrb; + +vmCvar_t cg_animBlend; + +vmCvar_t cg_dismember; + +vmCvar_t cg_thirdPersonSpecialCam; + +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_thirdPersonAngle; +vmCvar_t cg_thirdPersonPitchOffset; +vmCvar_t cg_thirdPersonVertOffset; +vmCvar_t cg_thirdPersonCameraDamp; +vmCvar_t cg_thirdPersonTargetDamp; + +vmCvar_t cg_thirdPersonAlpha; +vmCvar_t cg_thirdPersonHorzOffset; + +vmCvar_t cg_stereoSeparation; +//vmCvar_t cg_lagometer; +vmCvar_t cg_drawEnemyInfo; +vmCvar_t cg_synchronousClients; +vmCvar_t cg_stats; +vmCvar_t cg_buildScript; +vmCvar_t cg_forceModel; +vmCvar_t cg_paused; +vmCvar_t cg_blood; +vmCvar_t cg_predictItems; +vmCvar_t cg_deferPlayers; +//vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlayUserinfo; +vmCvar_t cg_drawFriend; +vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_hudFiles; +vmCvar_t cg_scorePlum; +vmCvar_t cg_smoothClients; + +#include "../namespace_begin.h" +vmCvar_t pmove_fixed; +//vmCvar_t cg_pmove_fixed; +vmCvar_t pmove_msec; +// nmckenzie: DUEL_HEALTH +vmCvar_t g_showDuelHealths; +#include "../namespace_end.h" + +vmCvar_t cg_pmove_msec; +vmCvar_t cg_cameraMode; +vmCvar_t cg_cameraOrbit; +vmCvar_t cg_cameraOrbitDelay; +vmCvar_t cg_timescaleFadeEnd; +vmCvar_t cg_timescaleFadeSpeed; +vmCvar_t cg_timescale; +vmCvar_t cg_noTaunt; +vmCvar_t cg_noProjectileTrail; +vmCvar_t cg_trueLightning; +/* +Ghoul2 Insert Start +*/ +vmCvar_t cg_debugBB; +/* +Ghoul2 Insert End +*/ +vmCvar_t cg_redTeamName; +vmCvar_t cg_blueTeamName; +vmCvar_t cg_currentSelectedPlayer; +vmCvar_t cg_currentSelectedPlayerName; +vmCvar_t cg_singlePlayer; +vmCvar_t cg_enableDust; +vmCvar_t cg_enableBreath; +vmCvar_t cg_singlePlayerActive; +vmCvar_t cg_recordSPDemo; +vmCvar_t cg_recordSPDemoName; +vmCvar_t cg_showVehBounds; + +vmCvar_t cg_snapshotTimeout; + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +static cvarTable_t cvarTable[] = { // bk001129 + { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging + { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, + { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, + { &cg_zoomFov, "cg_zoomfov", "40.0", CVAR_ARCHIVE }, + { &cg_fov, "cg_fov", "80", CVAR_ARCHIVE }, + { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, + { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, + { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, + { &cg_renderToTextureFX, "cg_renderToTextureFX", "1", CVAR_ARCHIVE }, + { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, + { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, + { &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE }, + { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, +// { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, + { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, + { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, + { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "0", CVAR_ARCHIVE }, + { &cg_drawEnemyInfo, "cg_drawEnemyInfo", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_drawRadar, "cg_drawRadar", "1", CVAR_ARCHIVE }, + { &cg_drawScores, "cg_drawScores", "1", CVAR_ARCHIVE }, + { &cg_dynamicCrosshair, "cg_dynamicCrosshair", "1", CVAR_ARCHIVE }, + //Enables ghoul2 traces for crosshair traces.. more precise when pointing at others, but slower. + //And if the server doesn't have g2 col enabled, it won't match up the same. + { &cg_dynamicCrosshairPrecision, "cg_dynamicCrosshairPrecision", "1", CVAR_ARCHIVE }, + { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, + { &cg_crosshairHealth, "cg_crosshairHealth", "0", CVAR_ARCHIVE }, + { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, + { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, + { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, +// { &cg_lagometer, "cg_lagometer", "0", CVAR_ARCHIVE }, + { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, + { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, + { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, + { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, + { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, + { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, + { &cg_bobup , "cg_bobup", "0.005", CVAR_ARCHIVE }, + { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, + { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, + //{ &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, + { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, + { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, + { &cg_debugSaber, "cg_debugsaber", "0", CVAR_CHEAT }, + { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, + { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, + { &cg_errorDecay, "cg_errordecay", "100", 0 }, + { &cg_nopredict, "cg_nopredict", "0", 0 }, + { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, + { &cg_showmiss, "cg_showmiss", "0", 0 }, + { &cg_showVehMiss, "cg_showVehMiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "3", CVAR_ARCHIVE }, + { &cg_swingAngles, "cg_swingAngles", "1", 0 }, + + { &cg_oldPainSounds, "cg_oldPainSounds", "0", 0 }, + + { &cg_ragDoll, "broadsword", "0", 0 }, + + { &cg_jumpSounds, "cg_jumpSounds", "0", 0 }, + + { &cg_autoMap, "r_autoMap", "0", CVAR_ARCHIVE }, + { &cg_autoMapX, "r_autoMapX", "496", CVAR_ARCHIVE }, + { &cg_autoMapY, "r_autoMapY", "32", CVAR_ARCHIVE }, + { &cg_autoMapW, "r_autoMapW", "128", CVAR_ARCHIVE }, + { &cg_autoMapH, "r_autoMapH", "128", CVAR_ARCHIVE }, + + { &bg_fighterAltControl, "bg_fighterAltControl", "0", CVAR_SERVERINFO }, + + { &cg_chatBox, "cg_chatBox", "10000", CVAR_ARCHIVE }, + { &cg_chatBoxHeight, "cg_chatBoxHeight", "350", CVAR_ARCHIVE }, + + { &cg_saberModelTraceEffect, "cg_saberModelTraceEffect", "0", 0 }, + + //allows us to trace between server frames on the client to see if we're visually + //hitting the last entity we detected a hit on from the server. + { &cg_saberClientVisualCompensation, "cg_saberClientVisualCompensation", "1", 0 }, + + { &cg_g2TraceLod, "cg_g2TraceLod", "2", 0 }, + + { &cg_fpls, "cg_fpls", "0", 0 }, + + { &cg_ghoul2Marks, "cg_ghoul2Marks", "16", 0 }, + + { &cg_saberDynamicMarks, "cg_saberDynamicMarks", "0", 0 }, + { &cg_saberDynamicMarkTime, "cg_saberDynamicMarkTime", "60000", 0 }, + + { &cg_saberContact, "cg_saberContact", "1", 0 }, + { &cg_saberTrail, "cg_saberTrail", "1", 0 }, + + { &cg_duelHeadAngles, "cg_duelHeadAngles", "0", 0 }, + + { &cg_speedTrail, "cg_speedTrail", "1", 0 }, + { &cg_auraShell, "cg_auraShell", "1", 0 }, + + { &cg_repeaterOrb, "cg_repeaterOrb", "0", 0 }, + + { &cg_animBlend, "cg_animBlend", "1", 0 }, + + { &cg_dismember, "cg_dismember", "0", CVAR_ARCHIVE }, + + { &cg_thirdPersonSpecialCam, "cg_thirdPersonSpecialCam", "0", 0 }, + + { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_ARCHIVE }, +// { &cg_thirdPersonRange, "cg_thirdPersonRange", "80", CVAR_CHEAT }, // Pulled back for Xbox - also in cl_data + { &cg_thirdPersonRange, "cg_thirdPersonRange", "90", CVAR_CHEAT }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPersonPitchOffset, "cg_thirdPersonPitchOffset", "0", CVAR_CHEAT }, + { &cg_thirdPersonVertOffset, "cg_thirdPersonVertOffset", "16", CVAR_CHEAT }, + { &cg_thirdPersonCameraDamp, "cg_thirdPersonCameraDamp", "0.3", 0 }, + { &cg_thirdPersonTargetDamp, "cg_thirdPersonTargetDamp", "0.5", CVAR_CHEAT }, + + { &cg_thirdPersonHorzOffset, "cg_thirdPersonHorzOffset", "0", CVAR_CHEAT }, + { &cg_thirdPersonAlpha, "cg_thirdPersonAlpha", "1.0", CVAR_CHEAT }, + + { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, + { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, + { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, +// { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, + { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, + { &cg_stats, "cg_stats", "0", 0 }, + { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, + { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, + // the following variables are created in other parts of the system, + // but we also reference them here + { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures + { &cg_paused, "cl_paused", "0", CVAR_ROM }, + { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, + { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo + + { &cg_redTeamName, "g_redteam", DEFAULT_REDTEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, + { &cg_blueTeamName, "g_blueteam", DEFAULT_BLUETEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, + { &cg_currentSelectedPlayer, "cg_currentSelectedPlayer", "0", CVAR_ARCHIVE}, + { &cg_currentSelectedPlayerName, "cg_currentSelectedPlayerName", "", CVAR_ARCHIVE}, + { &cg_singlePlayer, "ui_singlePlayerActive", "0", CVAR_USERINFO}, + { &cg_enableDust, "g_enableDust", "0", 0}, + { &cg_enableBreath, "g_enableBreath", "0", 0}, + { &cg_singlePlayerActive, "ui_singlePlayerActive", "0", CVAR_USERINFO}, + { &cg_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, + { &cg_recordSPDemoName, "ui_recordSPDemoName", "", CVAR_ARCHIVE}, + + { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, + { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, + { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0}, + { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0}, + { &cg_timescale, "timescale", "1", 0}, + { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_hudFiles, "cg_hudFiles", "ui/jahud.txt", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_smoothClients, "cg_smoothClients", "1", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, + + { &pmove_fixed, "pmove_fixed", "0", 0}, + { &pmove_msec, "pmove_msec", "8", 0}, + { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, + { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, + { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE}, + { &cg_showVehBounds, "cg_showVehBounds", "0", 0}, + + { &cg_snapshotTimeout, "cg_snapshotTimeout", "10", CVAR_ARCHIVE }, + +// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE } +/* +Ghoul2 Insert Start +*/ + { &cg_debugBB, "debugBB", "0", 0}, +/* +Ghoul2 Insert End +*/ +}; + +static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +/* +================= +CG_RegisterCvars +================= +*/ +void CG_RegisterCvars( void ) { + int i; + cvarTable_t *cv; + char var[MAX_TOKEN_CHARS]; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, + cv->defaultString, cv->cvarFlags ); + } + + // see if we are also running the server on this machine + trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); + cgs.localServer = atoi( var ); + + forceModelModificationCount = cg_forceModel.modificationCount; + + trap_Cvar_Register(NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "forcepowers", DEFAULT_FORCEPOWERS, CVAR_USERINFO | CVAR_ARCHIVE ); +//JLF forcepowers +#ifdef _XBOX + trap_Cvar_Register(NULL, "forcePowersProfile", DEFAULT_FORCEPOWERS, CVAR_PROFILE ); + +#endif + + // Cvars uses for transferring data between client and server + trap_Cvar_Register(NULL, "ui_about_gametype", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_fraglimit", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_capturelimit", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_duellimit", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_timelimit", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_maxclients", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_dmflags", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_mapname", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_hostname", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_needpass", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_botminplayers", "0", CVAR_ROM|CVAR_INTERNAL ); + + trap_Cvar_Register(NULL, "ui_tm1_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm3_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + + trap_Cvar_Register(NULL, "ui_tm1_c0_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm1_c1_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm1_c2_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm1_c3_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm1_c4_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm1_c5_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + + trap_Cvar_Register(NULL, "ui_tm2_c0_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_c1_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_c2_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_c3_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_c4_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_c5_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + +} + +/* +=================== +CG_SetWeatherOverride +=================== +*/ +#if 0 +void CG_SetWeatherOverride(int contents) +{ + if (contents != cgWeatherOverride) + { //only do the trap call if we aren't already set to this + trap_R_WeatherContentsOverride(contents); + } + cgWeatherOverride = contents; //keep track of it +} +#endif + +/* +=================== +CG_ForceModelChange +=================== +*/ +static void CG_ForceModelChange( void ) { + int i; + + for (i=0 ; ivmCvar ); + } + + // check for modications here + + // If team overlay is on, ask for updates from the server. If its off, + // let the server know so we don't receive it + trap_Cvar_Set( "teamoverlay", "0" ); +/* + if ( drawTeamOverlayModificationCount != cg_drawTeamOverlay.modificationCount ) { + drawTeamOverlayModificationCount = cg_drawTeamOverlay.modificationCount; + + if ( cg_drawTeamOverlay.integer > 0 ) { + trap_Cvar_Set( "teamoverlay", "1" ); + } else { + trap_Cvar_Set( "teamoverlay", "0" ); + } + // FIXME E3 HACK + trap_Cvar_Set( "teamoverlay", "1" ); + } +*/ + + // if force model changed + if ( forceModelModificationCount != cg_forceModel.modificationCount ) { + forceModelModificationCount = cg_forceModel.modificationCount; + CG_ForceModelChange(); + } +} + +int CG_CrosshairPlayer( void ) { + if ( cg->time > ( cg->crosshairClientTime + 1000 ) ) { + return -1; + } + + if (cg->crosshairClientNum >= MAX_CLIENTS) + { + return -1; + } + + return cg->crosshairClientNum; +} + +int CG_LastAttacker( void ) { + if ( !cg->attackerTime ) { + return -1; + } + return cg->snap->ps.persistant[PERS_ATTACKER]; +} + +void QDECL CG_PrintfAlways( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_PrintAlways( text ); +} + +void QDECL CG_Printf( const char *msg, ... ) { +#ifdef _DEBUG + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Print( text ); +#endif +} + +void QDECL CG_Error( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Error( text ); +} + +#ifndef CGAME_HARD_LINKED +// this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) + +void QDECL Com_Error( int level, const char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + CG_Error( "%s", text); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + CG_Printf ("%s", text); +} + +#endif + +/* +================ +CG_Argv +================ +*/ +const char *CG_Argv( int arg ) { + static char buffer[MAX_STRING_CHARS]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +//======================================================================== + +//so shared code can get the local time depending on the side it's executed on +#include "../namespace_begin.h" +int BG_GetTime(void) +{ + return cg->time; +} +#include "../namespace_end.h" + +/* +================= +CG_RegisterItemSounds + +The server says this item is used on this level +================= +*/ +static void CG_RegisterItemSounds( int itemNum ) { + gitem_t *item; + char data[MAX_QPATH]; + char *s, *start; + int len; + + item = &bg_itemlist[ itemNum ]; + + if( item->pickup_sound ) { + trap_S_RegisterSound( item->pickup_sound ); + } + + // parse the space seperated precache string for other media + s = item->sounds; + if (!s || !s[0]) + return; + + while (*s) { + start = s; + while (*s && *s != ' ') { + s++; + } + + len = s-start; + if (len >= MAX_QPATH || len < 5) { + CG_Error( "PrecacheItem: %s has bad precache string", + item->classname); + return; + } + memcpy (data, start, len); + data[len] = 0; + if ( *s ) { + s++; + } + + trap_S_RegisterSound( data ); + } + + // parse the space seperated precache string for other media + s = item->precaches; + if (!s || !s[0]) + return; + + while (*s) { + start = s; + while (*s && *s != ' ') { + s++; + } + + len = s-start; + if (len >= MAX_QPATH || len < 5) { + CG_Error( "PrecacheItem: %s has bad precache string", + item->classname); + return; + } + memcpy (data, start, len); + data[len] = 0; + if ( *s ) { + s++; + } + + if ( !strcmp(data+len-3, "efx" )) { + trap_FX_RegisterEffect( data ); + } + } +} + +static void CG_AS_Register(void) +{ + const char *soundName; + int i; + +// CG_LoadingString( "ambient sound sets" ); + + //Load the ambient sets +#if 0 //as_preCacheMap was game-side.. that is evil. + trap_AS_AddPrecacheEntry( "#clear" ); // ;-) + //FIXME: Don't ask... I had to get around a really nasty MS error in the templates with this... + namePrecache_m::iterator pi; + STL_ITERATE( pi, as_preCacheMap ) + { + cgi_AS_AddPrecacheEntry( ((*pi).first).c_str() ); + } +#else + trap_AS_AddPrecacheEntry( "#clear" ); + + for ( i = 1 ; i < MAX_AMBIENT_SETS ; i++ ) { + soundName = CG_ConfigString( CS_AMBIENT_SET+i ); + if ( !soundName || !soundName[0] ) + { + break; + } + + trap_AS_AddPrecacheEntry(soundName); + } + soundName = CG_ConfigString( CS_GLOBAL_AMBIENT_SET ); + if (soundName && soundName[0] && Q_stricmp(soundName, "default")) + { //global soundset + trap_AS_AddPrecacheEntry(soundName); + } +#endif + + trap_AS_ParseSets(); +} + +//a global weather effect (rain, snow, etc) +void CG_ParseWeatherEffect(const char *str) +{ + char *sptr = (char *)str; + sptr++; //pass the '*' + trap_R_WorldEffectCommand(sptr); +} + +extern int cgSiegeRoundBeganTime; +void CG_ParseSiegeState(const char *str) +{ + int i = 0; + int j = 0; +// int prevState = cgSiegeRoundState; + char b[1024]; + + while (str[i] && str[i] != '|') + { + b[j] = str[i]; + i++; + j++; + } + b[j] = 0; + cgSiegeRoundState = atoi(b); + + if (str[i] == '|') + { + j = 0; + i++; + while (str[i]) + { + b[j] = str[i]; + i++; + j++; + } + b[j] = 0; +// if (cgSiegeRoundState != prevState) + { //it changed + cgSiegeRoundTime = atoi(b); + if (cgSiegeRoundState == 0 || cgSiegeRoundState == 2) + { + cgSiegeRoundBeganTime = cgSiegeRoundTime; + } + } + } + else + { + cgSiegeRoundTime = cg->time; + } +} + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +void CG_PrecacheNPCSounds(const char *str); +void CG_ParseSiegeObjectiveStatus(const char *str); +extern int cg_beatingSiegeTime; +extern int cg_siegeWinTeam; +static void CG_RegisterSounds( void ) { + int i; + char items[MAX_ITEMS+1]; + char name[MAX_QPATH]; + const char *soundName; + + CG_AS_Register(); + +// CG_LoadingString( "sounds" ); + + trap_S_RegisterSound( "sound/weapons/melee/punch1.mp3" ); + trap_S_RegisterSound( "sound/weapons/melee/punch2.mp3" ); + trap_S_RegisterSound( "sound/weapons/melee/punch3.mp3" ); + trap_S_RegisterSound( "sound/weapons/melee/punch4.mp3" ); + trap_S_RegisterSound("sound/movers/objects/saber_slam"); + + trap_S_RegisterSound("sound/player/bodyfall_human1.wav"); + trap_S_RegisterSound("sound/player/bodyfall_human2.wav"); + trap_S_RegisterSound("sound/player/bodyfall_human3.wav"); + + //test effects + trap_FX_RegisterEffect("effects/mp/test_sparks.efx"); + trap_FX_RegisterEffect("effects/mp/test_wall_impact.efx"); + + cgs.media.oneMinuteSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM004" ); + cgs.media.fiveMinuteSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM005" ); + cgs.media.oneFragSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM001" ); + cgs.media.twoFragSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM002" ); + cgs.media.threeFragSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM003"); + cgs.media.count3Sound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM035" ); + cgs.media.count2Sound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM036" ); + cgs.media.count1Sound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM037" ); + cgs.media.countFightSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM038" ); + + cgs.media.hackerIconShader = trap_R_RegisterShaderNoMip("gfx/mp/c_icon_tech"); + + cgs.media.redSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/red_glow" ); + cgs.media.redSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/red_line" ); + cgs.media.orangeSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/orange_glow" ); + cgs.media.orangeSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/orange_line" ); + cgs.media.yellowSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/yellow_glow" ); + cgs.media.yellowSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/yellow_line" ); + cgs.media.greenSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/green_glow" ); + cgs.media.greenSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/green_line" ); + cgs.media.blueSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/blue_glow" ); + cgs.media.blueSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/blue_line" ); + cgs.media.purpleSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/purple_glow" ); + cgs.media.purpleSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/purple_line" ); + cgs.media.saberBlurShader = trap_R_RegisterShader( "gfx/effects/sabers/saberBlur" ); + cgs.media.swordTrailShader = trap_R_RegisterShader( "gfx/effects/sabers/swordTrail" ); + + cgs.media.forceCoronaShader = trap_R_RegisterShaderNoMip( "gfx/hud/force_swirl" ); + + cgs.media.yellowDroppedSaberShader = trap_R_RegisterShader("gfx/effects/yellow_glow"); + + cgs.media.rivetMarkShader = trap_R_RegisterShader( "gfx/damage/rivetmark" ); + + trap_R_RegisterShader( "gfx/effects/saberFlare" ); + + trap_R_RegisterShader( "powerups/ysalimarishell" ); + + trap_R_RegisterShader( "gfx/effects/forcePush" ); + + trap_R_RegisterShader( "gfx/misc/red_dmgshield" ); + trap_R_RegisterShader( "gfx/misc/red_portashield" ); + trap_R_RegisterShader( "gfx/misc/blue_dmgshield" ); + trap_R_RegisterShader( "gfx/misc/blue_portashield" ); + + trap_R_RegisterShader( "models/map_objects/imp_mine/turret_chair_dmg.tga" ); + + for (i=1 ; i<9 ; i++) + { + trap_S_RegisterSound(va("sound/weapons/saber/saberhup%i.wav", i)); + } + + for (i=1 ; i<10 ; i++) + { + trap_S_RegisterSound(va("sound/weapons/saber/saberblock%i.wav", i)); + } + + for (i=1 ; i<4 ; i++) + { + trap_S_RegisterSound(va("sound/weapons/saber/bounce%i.wav", i)); + } + + trap_S_RegisterSound( "sound/weapons/saber/enemy_saber_on.wav" ); + trap_S_RegisterSound( "sound/weapons/saber/enemy_saber_off.wav" ); + + trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ); + trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" ); + trap_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" ); + trap_S_RegisterSound( "sound/weapons/saber/saberhitwall1" ); + trap_S_RegisterSound( "sound/weapons/saber/saberhitwall2" ); + trap_S_RegisterSound( "sound/weapons/saber/saberhitwall3" ); + trap_S_RegisterSound("sound/weapons/saber/saberhit.wav"); + trap_S_RegisterSound("sound/weapons/saber/saberhit1.wav"); + trap_S_RegisterSound("sound/weapons/saber/saberhit2.wav"); + trap_S_RegisterSound("sound/weapons/saber/saberhit3.wav"); + + trap_S_RegisterSound("sound/weapons/saber/saber_catch.wav"); + + cgs.media.teamHealSound = trap_S_RegisterSound("sound/weapons/force/teamheal.wav"); + cgs.media.teamRegenSound = trap_S_RegisterSound("sound/weapons/force/teamforce.wav"); + + trap_S_RegisterSound("sound/weapons/force/heal.wav"); + trap_S_RegisterSound("sound/weapons/force/speed.wav"); + trap_S_RegisterSound("sound/weapons/force/see.wav"); + trap_S_RegisterSound("sound/weapons/force/rage.wav"); + trap_S_RegisterSound("sound/weapons/force/lightning"); + trap_S_RegisterSound("sound/weapons/force/lightninghit1"); + trap_S_RegisterSound("sound/weapons/force/lightninghit2"); + trap_S_RegisterSound("sound/weapons/force/lightninghit3"); + trap_S_RegisterSound("sound/weapons/force/drain.wav"); + trap_S_RegisterSound("sound/weapons/force/jumpbuild.wav"); + trap_S_RegisterSound("sound/weapons/force/distract.wav"); + trap_S_RegisterSound("sound/weapons/force/distractstop.wav"); + trap_S_RegisterSound("sound/weapons/force/pull.wav"); + trap_S_RegisterSound("sound/weapons/force/push.wav"); + + for (i=1 ; i<3 ; i++) + { + trap_S_RegisterSound(va("sound/weapons/thermal/bounce%i.wav", i)); + } + + trap_S_RegisterSound("sound/movers/switches/switch2.wav"); + trap_S_RegisterSound("sound/movers/switches/switch3.wav"); + trap_S_RegisterSound("sound/ambience/spark5.wav"); + trap_S_RegisterSound("sound/chars/turret/ping.wav"); + trap_S_RegisterSound("sound/chars/turret/startup.wav"); + trap_S_RegisterSound("sound/chars/turret/shutdown.wav"); + trap_S_RegisterSound("sound/chars/turret/move.wav"); + trap_S_RegisterSound("sound/player/pickuphealth.wav"); + trap_S_RegisterSound("sound/player/pickupshield.wav"); + + trap_S_RegisterSound("sound/effects/glassbreak1.wav"); + + trap_S_RegisterSound( "sound/weapons/rocket/tick.wav" ); + trap_S_RegisterSound( "sound/weapons/rocket/lock.wav" ); + + trap_S_RegisterSound("sound/weapons/force/speedloop.wav"); + + trap_S_RegisterSound("sound/weapons/force/protecthit.mp3"); //PDSOUND_PROTECTHIT + trap_S_RegisterSound("sound/weapons/force/protect.mp3"); //PDSOUND_PROTECT + trap_S_RegisterSound("sound/weapons/force/absorbhit.mp3"); //PDSOUND_ABSORBHIT + trap_S_RegisterSound("sound/weapons/force/absorb.mp3"); //PDSOUND_ABSORB + trap_S_RegisterSound("sound/weapons/force/jump.mp3"); //PDSOUND_FORCEJUMP + trap_S_RegisterSound("sound/weapons/force/grip.mp3"); //PDSOUND_FORCEGRIP + + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + +#ifdef JK2AWARDS + cgs.media.captureAwardSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav" ); +#endif + cgs.media.redLeadsSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM046"); + cgs.media.blueLeadsSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM045"); + cgs.media.teamsTiedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM032" ); + + cgs.media.redScoredSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM044"); + cgs.media.blueScoredSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM043" ); + + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { + cgs.media.redFlagReturnedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM042" ); + cgs.media.blueFlagReturnedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM041" ); + cgs.media.redTookFlagSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM040" ); + cgs.media.blueTookFlagSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM039" ); + } + if ( cgs.gametype == GT_CTY /*|| cg_buildScript.integer*/ ) { + cgs.media.redYsalReturnedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM050" ); + cgs.media.blueYsalReturnedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM049" ); + cgs.media.redTookYsalSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM048" ); + cgs.media.blueTookYsalSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM047" ); + } + } + + cgs.media.drainSound = trap_S_RegisterSound("sound/weapons/force/drained.mp3"); + + cgs.media.happyMusic = trap_S_RegisterSound("music/goodsmall.mp3"); + cgs.media.dramaticFailure = trap_S_RegisterSound("music/badsmall.mp3"); + + //PRECACHE ALL MUSIC HERE (don't need to precache normally because it's streamed off the disk) + if (cg_buildScript.integer) + { + trap_S_StartBackgroundTrack( "music/mp/duel.mp3", "music/mp/duel.mp3", qfalse ); + } + + cg->loadLCARSStage = 1; + + cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav" ); + + cgs.media.teleInSound = trap_S_RegisterSound( "sound/player/telein.wav" ); + cgs.media.teleOutSound = trap_S_RegisterSound( "sound/player/teleout.wav" ); + cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav" ); + + trap_S_RegisterSound( "sound/movers/objects/objectHit.wav" ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/player/talk.wav" ); + cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav"); + cgs.media.fallSound = trap_S_RegisterSound( "sound/player/fallsplat.wav"); + + cgs.media.crackleSound = trap_S_RegisterSound( "sound/effects/energy_crackle.wav" ); +#ifdef JK2AWARDS + cgs.media.impressiveSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM025" ); + cgs.media.excellentSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM053" ); + cgs.media.deniedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM017" ); + cgs.media.humiliationSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM019" ); + cgs.media.defendSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM024" ); +#endif + + /* + cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM051"); + cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM032"); + cgs.media.lostLeadSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM052"); + */ + + cgs.media.rollSound = trap_S_RegisterSound( "sound/player/roll1.wav"); + + cgs.media.noforceSound = trap_S_RegisterSound( "sound/weapons/force/noforce" ); + + cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav"); + cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav"); + cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav"); + + cgs.media.explosionModel = trap_R_RegisterModel ( "models/map_objects/mp/sphere.md3" ); + cgs.media.surfaceExplosionShader = trap_R_RegisterShader( "surfaceExplosion" ); + + cgs.media.disruptorShader = trap_R_RegisterShader( "gfx/effects/burn"); + + if (cg_buildScript.integer) + { + trap_R_RegisterShader( "gfx/effects/turretflashdie" ); + } + + cgs.media.solidWhite = trap_R_RegisterShader( "gfx/effects/solidWhite_cull" ); + + trap_R_RegisterShader("gfx/misc/mp_light_enlight_disable"); + trap_R_RegisterShader("gfx/misc/mp_dark_enlight_disable"); + + trap_R_RegisterModel ( "models/map_objects/mp/sphere.md3" ); + trap_R_RegisterModel("models/items/remote.md3"); + + cgs.media.holocronPickup = trap_S_RegisterSound( "sound/player/holocron.wav" ); + + // Zoom + cgs.media.zoomStart = trap_S_RegisterSound( "sound/interface/zoomstart.wav" ); + cgs.media.zoomLoop = trap_S_RegisterSound( "sound/interface/zoomloop.wav" ); + cgs.media.zoomEnd = trap_S_RegisterSound( "sound/interface/zoomend.wav" ); + + for (i=0 ; i<4 ; i++) { + Com_sprintf (name, sizeof(name), "sound/player/footsteps/stone_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_STONEWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/stone_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_STONERUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/metal_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_METALWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/metal_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_METALRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/pipe_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_PIPEWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/pipe_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_PIPERUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/water_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SPLASH][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/water_walk%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_WADE][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/water_wade_0%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SWIM][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/snow_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SNOWWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/snow_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SNOWRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/sand_walk%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SANDWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/sand_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SANDRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/grass_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_GRASSWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/grass_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_GRASSRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/dirt_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_DIRTWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/dirt_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_DIRTRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/mud_walk%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_MUDWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/mud_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_MUDRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/gravel_walk%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_GRAVELWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/gravel_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_GRAVELRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/rug_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_RUGWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/rug_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_RUGRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/wood_walk%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_WOODWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/wood_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_WOODRUN][i] = trap_S_RegisterSound (name); + } + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS ) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_RegisterItemSounds( i ); + } + } + + memset(&cgs.gameSounds, -1, MAX_SOUNDS); + for ( i = 1 ; i < MAX_SOUNDS ; i++ ) { + soundName = CG_ConfigString( CS_SOUNDS+i ); + if ( !soundName[0] ) { + break; + } + if ( soundName[0] == '*' ) + { + if (soundName[1] == '$') + { //an NPC soundset + CG_PrecacheNPCSounds(soundName); + } + continue; // custom sound + } + cgs.gameSounds[i] = trap_S_RegisterSound( soundName ); + } + + for ( i = 1 ; i < MAX_FX ; i++ ) { + soundName = CG_ConfigString( CS_EFFECTS+i ); + if ( !soundName[0] ) { + break; + } + + if (soundName[0] == '*') + { //it's a special global weather effect + CG_ParseWeatherEffect(soundName); + cgs.gameEffects[i] = 0; + } + else + { + cgs.gameEffects[i] = trap_FX_RegisterEffect( soundName ); + } + } + + // register all the server specified icons + for ( i = 1; i < MAX_ICONS; i ++ ) + { + const char* iconName; + + iconName = CG_ConfigString ( CS_ICONS + i ); + if ( !iconName[0] ) + { + break; + } + + cgs.gameIcons[i] = trap_R_RegisterShaderNoMip ( iconName ); + } + + soundName = CG_ConfigString(CS_SIEGE_STATE); + + if (soundName[0]) + { + CG_ParseSiegeState(soundName); + } + + soundName = CG_ConfigString(CS_SIEGE_WINTEAM); + + if (soundName[0]) + { + cg_siegeWinTeam = atoi(soundName); + } + + if (cgs.gametype == GT_SIEGE) + { + CG_ParseSiegeObjectiveStatus(CG_ConfigString(CS_SIEGE_OBJECTIVES)); + cg_beatingSiegeTime = atoi(CG_ConfigString(CS_SIEGE_TIMEOVERRIDE)); + if ( cg_beatingSiegeTime ) + { + CG_SetSiegeTimerCvar ( cg_beatingSiegeTime ); + } + } + + cg->loadLCARSStage = 2; + + // FIXME: only needed with item + cgs.media.deploySeeker = trap_S_RegisterSound ("sound/chars/seeker/misc/hiss"); + cgs.media.medkitSound = trap_S_RegisterSound ("sound/items/use_bacta.wav"); + + cgs.media.winnerSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM006" ); + cgs.media.loserSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM010" ); +} + + +//------------------------------------- +// CG_RegisterEffects +// +// Handles precaching all effect files +// and any shader, model, or sound +// files an effect may use. +//------------------------------------- +static void CG_RegisterEffects( void ) +{ + /* + const char *effectName; + int i; + + for ( i = 1 ; i < MAX_FX ; i++ ) + { + effectName = CG_ConfigString( CS_EFFECTS + i ); + + if ( !effectName[0] ) + { + break; + } + + trap_FX_RegisterEffect( effectName ); + } + */ + //the above was redundant as it's being done in CG_RegisterSounds + + // Set up the glass effects mini-system. + CG_InitGlass(); + + //footstep effects + cgs.effects.footstepMud = trap_FX_RegisterEffect( "materials/mud" ); + cgs.effects.footstepSand = trap_FX_RegisterEffect( "materials/sand" ); + cgs.effects.footstepSnow = trap_FX_RegisterEffect( "materials/snow" ); + cgs.effects.footstepGravel = trap_FX_RegisterEffect( "materials/gravel" ); + //landing effects + cgs.effects.landingMud = trap_FX_RegisterEffect( "materials/mud_large" ); + cgs.effects.landingSand = trap_FX_RegisterEffect( "materials/sand_large" ); + cgs.effects.landingDirt = trap_FX_RegisterEffect( "materials/dirt_large" ); + cgs.effects.landingSnow = trap_FX_RegisterEffect( "materials/snow_large" ); + cgs.effects.landingGravel = trap_FX_RegisterEffect( "materials/gravel_large" ); + //splashes + cgs.effects.waterSplash = trap_FX_RegisterEffect( "env/water_impact" ); + cgs.effects.lavaSplash = trap_FX_RegisterEffect( "env/lava_splash" ); + cgs.effects.acidSplash = trap_FX_RegisterEffect( "env/acid_splash" ); +} + +//=================================================================================== + +extern char *forceHolocronModels[]; +int CG_HandleAppendedSkin(char *modelName); +void CG_CacheG2AnimInfo(char *modelName); +/* +================= +CG_RegisterGraphics + +This function may execute for a couple of minutes with a slow disk. +================= +*/ +static void CG_RegisterGraphics( void ) { + int i; + int breakPoint; + char items[MAX_ITEMS+1]; + const char *terrainInfo; + int terrainID; + + static char *sb_nums[11] = { + "gfx/2d/numbers/zero", + "gfx/2d/numbers/one", + "gfx/2d/numbers/two", + "gfx/2d/numbers/three", + "gfx/2d/numbers/four", + "gfx/2d/numbers/five", + "gfx/2d/numbers/six", + "gfx/2d/numbers/seven", + "gfx/2d/numbers/eight", + "gfx/2d/numbers/nine", + "gfx/2d/numbers/minus", + }; + + static char *sb_t_nums[11] = { + "gfx/2d/numbers/t_zero", + "gfx/2d/numbers/t_one", + "gfx/2d/numbers/t_two", + "gfx/2d/numbers/t_three", + "gfx/2d/numbers/t_four", + "gfx/2d/numbers/t_five", + "gfx/2d/numbers/t_six", + "gfx/2d/numbers/t_seven", + "gfx/2d/numbers/t_eight", + "gfx/2d/numbers/t_nine", + "gfx/2d/numbers/t_minus", + }; + +/* + static char *sb_c_nums[11] = { + "gfx/2d/numbers/c_zero", + "gfx/2d/numbers/c_one", + "gfx/2d/numbers/c_two", + "gfx/2d/numbers/c_three", + "gfx/2d/numbers/c_four", + "gfx/2d/numbers/c_five", + "gfx/2d/numbers/c_six", + "gfx/2d/numbers/c_seven", + "gfx/2d/numbers/c_eight", + "gfx/2d/numbers/c_nine", + "gfx/2d/numbers/t_minus", //????? + }; +*/ + + // clear any references to old media + memset( &cg->refdef, 0, sizeof( cg->refdef ) ); + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() != 1) { +#endif + trap_R_ClearScene(); + + CG_LoadingString( cgs.mapname ); + + trap_R_LoadWorldMap( cgs.mapname ); + + // precache status bar pics +// CG_LoadingString( "game media" ); + + for ( i=0 ; i<11 ; i++) { + cgs.media.numberShaders[i] = trap_R_RegisterShader( sb_nums[i] ); + } + + cg->loadLCARSStage = 3; + + for ( i=0; i < 11; i++ ) + { + cgs.media.numberShaders[i] = trap_R_RegisterShaderNoMip( sb_nums[i] ); + cgs.media.smallnumberShaders[i] = trap_R_RegisterShaderNoMip( sb_t_nums[i] ); +// cgs.media.chunkyNumberShaders[i] = trap_R_RegisterShaderNoMip( sb_c_nums[i] ); + } + + trap_R_RegisterShaderNoMip ( "gfx/mp/pduel_icon_lone" ); + trap_R_RegisterShaderNoMip ( "gfx/mp/pduel_icon_double" ); + + cgs.media.balloonShader = trap_R_RegisterShader( "gfx/mp/chat_icon" ); + cgs.media.vchatShader = trap_R_RegisterShader( "gfx/mp/vchat_icon" ); + + cgs.media.deferShader = trap_R_RegisterShaderNoMip( "gfx/2d/defer.tga" ); + + cgs.media.radarShader = trap_R_RegisterShaderNoMip ( "gfx/menus/radar/radar.png" ); + cgs.media.siegeItemShader = trap_R_RegisterShaderNoMip ( "gfx/menus/radar/goalitem" ); + cgs.media.mAutomapPlayerIcon = trap_R_RegisterShader( "gfx/menus/radar/arrow_w" ); + cgs.media.mAutomapRocketIcon = trap_R_RegisterShader( "gfx/menus/radar/rocket" ); + + cgs.media.wireframeAutomapFrame_left = trap_R_RegisterShader( "gfx/mp_automap/mpauto_frame_left" ); + cgs.media.wireframeAutomapFrame_right = trap_R_RegisterShader( "gfx/mp_automap/mpauto_frame_right" ); + cgs.media.wireframeAutomapFrame_top = trap_R_RegisterShader( "gfx/mp_automap/mpauto_frame_top" ); + cgs.media.wireframeAutomapFrame_bottom = trap_R_RegisterShader( "gfx/mp_automap/mpauto_frame_bottom" ); + +// cgs.media.lagometerShader = trap_R_RegisterShaderNoMip("gfx/2d/lag" ); + cgs.media.connectionShader = trap_R_RegisterShaderNoMip( "gfx/2d/net" ); + +#ifdef _XBOX +} +#endif + + trap_FX_InitSystem(&cg->refdef); + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() != 1) { +#endif + CG_RegisterEffects(); + + cgs.media.boltShader = trap_R_RegisterShader( "gfx/misc/blueLine" ); + + cgs.effects.turretShotEffect = trap_FX_RegisterEffect( "turret/shot" ); + cgs.effects.mEmplacedDeadSmoke = trap_FX_RegisterEffect("emplaced/dead_smoke.efx"); + cgs.effects.mEmplacedExplode = trap_FX_RegisterEffect("emplaced/explode.efx"); + cgs.effects.mTurretExplode = trap_FX_RegisterEffect("turret/explode.efx"); + cgs.effects.mSparkExplosion = trap_FX_RegisterEffect("sparks/spark_explosion.efx"); + cgs.effects.mTripmineExplosion = trap_FX_RegisterEffect("tripMine/explosion.efx"); + cgs.effects.mDetpackExplosion = trap_FX_RegisterEffect("detpack/explosion.efx"); + cgs.effects.mFlechetteAltBlow = trap_FX_RegisterEffect("flechette/alt_blow.efx"); + cgs.effects.mStunBatonFleshImpact = trap_FX_RegisterEffect("stunBaton/flesh_impact.efx"); + cgs.effects.mAltDetonate = trap_FX_RegisterEffect("demp2/altDetonate.efx"); + cgs.effects.mSparksExplodeNoSound = trap_FX_RegisterEffect("sparks/spark_exp_nosnd"); + cgs.effects.mTripMineLaster = trap_FX_RegisterEffect("tripMine/laser.efx"); + cgs.effects.mEmplacedMuzzleFlash = trap_FX_RegisterEffect( "effects/emplaced/muzzle_flash" ); + cgs.effects.mConcussionAltRing = trap_FX_RegisterEffect("concussion/alt_ring"); + + cgs.effects.mHyperspaceStars = trap_FX_RegisterEffect("ships/hyperspace_stars"); + cgs.effects.mBlackSmoke = trap_FX_RegisterEffect( "volumetric/black_smoke" ); + cgs.effects.mShipDestDestroyed = trap_FX_RegisterEffect("effects/ships/dest_destroyed.efx"); + cgs.effects.mShipDestBurning = trap_FX_RegisterEffect("effects/ships/dest_burning.efx"); + cgs.effects.mBobaJet = trap_FX_RegisterEffect("effects/boba/jet.efx"); + + + cgs.effects.itemCone = trap_FX_RegisterEffect("mp/itemcone.efx"); + cgs.effects.mTurretMuzzleFlash = trap_FX_RegisterEffect("effects/turret/muzzle_flash.efx"); + cgs.effects.mSparks = trap_FX_RegisterEffect("sparks/spark_nosnd.efx"); //sparks/spark.efx + cgs.effects.mSaberCut = trap_FX_RegisterEffect("saber/saber_cut.efx"); + cgs.effects.mSaberBlock = trap_FX_RegisterEffect("saber/saber_block.efx"); + cgs.effects.mSaberBloodSparks = trap_FX_RegisterEffect("saber/blood_sparks_mp.efx"); + cgs.effects.mSaberBloodSparksSmall = trap_FX_RegisterEffect("saber/blood_sparks_25_mp.efx"); + cgs.effects.mSaberBloodSparksMid = trap_FX_RegisterEffect("saber/blood_sparks_50_mp.efx"); + cgs.effects.mSpawn = trap_FX_RegisterEffect("mp/spawn.efx"); + cgs.effects.mJediSpawn = trap_FX_RegisterEffect("mp/jedispawn.efx"); + cgs.effects.mBlasterDeflect = trap_FX_RegisterEffect("blaster/deflect.efx"); + cgs.effects.mBlasterSmoke = trap_FX_RegisterEffect("blaster/smoke_bolton"); + cgs.effects.mForceConfustionOld = trap_FX_RegisterEffect("force/confusion_old.efx"); + + cgs.effects.forceLightning = trap_FX_RegisterEffect( "effects/force/lightning.efx" ); + cgs.effects.forceLightningWide = trap_FX_RegisterEffect( "effects/force/lightningwide.efx" ); + cgs.effects.forceDrain = trap_FX_RegisterEffect( "effects/mp/drain.efx" ); + cgs.effects.forceDrainWide = trap_FX_RegisterEffect( "effects/mp/drainwide.efx" ); + cgs.effects.forceDrained = trap_FX_RegisterEffect( "effects/mp/drainhit.efx"); + + cgs.effects.mDisruptorDeathSmoke = trap_FX_RegisterEffect("disruptor/death_smoke"); + + for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { + cgs.media.crosshairShader[i] = trap_R_RegisterShaderNoMip( va("gfx/2d/crosshair%c", 'a'+i) ); + } + + cg->loadLCARSStage = 4; + +// cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); + + //precache the fpls skin + //trap_R_RegisterSkin("models/players/kyle/model_fpls2.skin"); + + cgs.media.itemRespawningPlaceholder = trap_R_RegisterShader("powerups/placeholder"); + cgs.media.itemRespawningRezOut = trap_R_RegisterShader("powerups/rezout"); + + cgs.media.playerShieldDamage = trap_R_RegisterShader("gfx/misc/personalshield"); + cgs.media.protectShader = trap_R_RegisterShader("gfx/misc/forceprotect"); + cgs.media.forceSightBubble = trap_R_RegisterShader("gfx/misc/sightbubble"); + cgs.media.forceShell = trap_R_RegisterShader("powerups/forceshell"); + cgs.media.sightShell = trap_R_RegisterShader("powerups/sightshell"); + + cgs.media.itemHoloModel = trap_R_RegisterModel("models/map_objects/mp/holo.md3"); + + if (cgs.gametype == GT_HOLOCRON || cg_buildScript.integer) + { + for ( i=0; i < NUM_FORCE_POWERS; i++ ) + { + if (forceHolocronModels[i] && + forceHolocronModels[i][0]) + { + trap_R_RegisterModel(forceHolocronModels[i]); + } + } + } + + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY || cg_buildScript.integer ) { + if (cg_buildScript.integer) + { + trap_R_RegisterModel( "models/flags/r_flag.md3" ); + trap_R_RegisterModel( "models/flags/b_flag.md3" ); + trap_R_RegisterModel( "models/flags/r_flag_ysal.md3" ); + trap_R_RegisterModel( "models/flags/b_flag_ysal.md3" ); + } + + if (cgs.gametype == GT_CTF) + { + cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag.md3" ); + cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag.md3" ); + } + else + { + cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag_ysal.md3" ); + cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag_ysal.md3" ); + } + + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_x" ); + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_x" ); + + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_ys" ); + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_ys" ); + + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag" ); + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag" ); + +// trap_R_RegisterShaderNoMip("gfx/2d/net.tga"); + + cgs.media.flagPoleModel = trap_R_RegisterModel( "models/flag2/flagpole.md3" ); + cgs.media.flagFlapModel = trap_R_RegisterModel( "models/flag2/flagflap3.md3" ); + + cgs.media.redFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/red_base.md3" ); + cgs.media.blueFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/blue_base.md3" ); + cgs.media.neutralFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/ntrl_base.md3" ); + } + + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + cgs.media.teamRedShader = trap_R_RegisterShader( "sprites/team_red" ); + cgs.media.teamBlueShader = trap_R_RegisterShader( "sprites/team_blue" ); + //cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); + cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); + } + else if ( cgs.gametype == GT_JEDIMASTER ) + { + cgs.media.teamRedShader = trap_R_RegisterShader( "sprites/team_red" ); + } + + if (cgs.gametype == GT_POWERDUEL || cg_buildScript.integer) + { + cgs.media.powerDuelAllyShader = trap_R_RegisterShader("gfx/mp/pduel_icon_double");//trap_R_RegisterShader("gfx/mp/pduel_gameicon_ally"); + } + + cgs.media.heartShader = trap_R_RegisterShaderNoMip( "ui/assets/statusbar/selectedhealth.tga" ); + + cgs.media.ysaliredShader = trap_R_RegisterShader( "powerups/ysaliredshell"); + cgs.media.ysaliblueShader = trap_R_RegisterShader( "powerups/ysaliblueshell"); + cgs.media.ysalimariShader = trap_R_RegisterShader( "powerups/ysalimarishell"); + cgs.media.boonShader = trap_R_RegisterShader( "powerups/boonshell"); + cgs.media.endarkenmentShader = trap_R_RegisterShader( "powerups/endarkenmentshell"); + cgs.media.enlightenmentShader = trap_R_RegisterShader( "powerups/enlightenmentshell"); + cgs.media.invulnerabilityShader = trap_R_RegisterShader( "powerups/invulnerabilityshell"); + +#ifdef JK2AWARDS + cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); + cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); + cgs.media.medalGauntlet = trap_R_RegisterShaderNoMip( "medal_gauntlet" ); + cgs.media.medalDefend = trap_R_RegisterShaderNoMip( "medal_defend" ); + cgs.media.medalAssist = trap_R_RegisterShaderNoMip( "medal_assist" ); + cgs.media.medalCapture = trap_R_RegisterShaderNoMip( "medal_capture" ); +#endif + + // Binocular interface +// cgs.media.binocularCircle = trap_R_RegisterShader( "gfx/2d/binCircle" ); +// cgs.media.binocularMask = trap_R_RegisterShader( "gfx/2d/binMask" ); +// cgs.media.binocularArrow = trap_R_RegisterShader( "gfx/2d/binSideArrow" ); +// cgs.media.binocularTri = trap_R_RegisterShader( "gfx/2d/binTopTri" ); +// cgs.media.binocularStatic = trap_R_RegisterShader( "gfx/2d/binocularWindow" ); +// cgs.media.binocularOverlay = trap_R_RegisterShader( "gfx/2d/binocularNumOverlay" ); + + cg->loadLCARSStage = 5; + + // Chunk models + //FIXME: jfm:? bother to conditionally load these if an ent has this material type? + for ( i = 0; i < NUM_CHUNK_MODELS; i++ ) + { + cgs.media.chunkModels[CHUNK_METAL2][i] = trap_R_RegisterModel( va( "models/chunks/metal/metal1_%i.md3", i+1 ) ); //_ /switched\ _ + cgs.media.chunkModels[CHUNK_METAL1][i] = trap_R_RegisterModel( va( "models/chunks/metal/metal2_%i.md3", i+1 ) ); // \switched/ + cgs.media.chunkModels[CHUNK_ROCK1][i] = trap_R_RegisterModel( va( "models/chunks/rock/rock1_%i.md3", i+1 ) ); + cgs.media.chunkModels[CHUNK_ROCK2][i] = trap_R_RegisterModel( va( "models/chunks/rock/rock2_%i.md3", i+1 ) ); + cgs.media.chunkModels[CHUNK_ROCK3][i] = trap_R_RegisterModel( va( "models/chunks/rock/rock3_%i.md3", i+1 ) ); + cgs.media.chunkModels[CHUNK_CRATE1][i] = trap_R_RegisterModel( va( "models/chunks/crate/crate1_%i.md3", i+1 ) ); + cgs.media.chunkModels[CHUNK_CRATE2][i] = trap_R_RegisterModel( va( "models/chunks/crate/crate2_%i.md3", i+1 ) ); + cgs.media.chunkModels[CHUNK_WHITE_METAL][i] = trap_R_RegisterModel( va( "models/chunks/metal/wmetal1_%i.md3", i+1 ) ); + } + + cgs.media.chunkSound = trap_S_RegisterSound("sound/weapons/explosions/glasslcar"); + cgs.media.grateSound = trap_S_RegisterSound( "sound/effects/grate_destroy" ); + cgs.media.rockBreakSound = trap_S_RegisterSound("sound/effects/wall_smash"); + cgs.media.rockBounceSound[0] = trap_S_RegisterSound("sound/effects/stone_bounce"); + cgs.media.rockBounceSound[1] = trap_S_RegisterSound("sound/effects/stone_bounce2"); + cgs.media.metalBounceSound[0] = trap_S_RegisterSound("sound/effects/metal_bounce"); + cgs.media.metalBounceSound[1] = trap_S_RegisterSound("sound/effects/metal_bounce2"); + cgs.media.glassChunkSound = trap_S_RegisterSound("sound/weapons/explosions/glassbreak1"); + cgs.media.crateBreakSound[0] = trap_S_RegisterSound("sound/weapons/explosions/crateBust1" ); + cgs.media.crateBreakSound[1] = trap_S_RegisterSound("sound/weapons/explosions/crateBust2" ); + +/* +Ghoul2 Insert Start +*/ + CG_InitItems(); +/* +Ghoul2 Insert End +*/ + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_LoadingItem( i ); + CG_RegisterItemVisuals( i ); + } + } + + cg->loadLCARSStage = 6; + + if( Q_stricmp(Cvar_VariableString( "mapname" ), "mp/ctf5") == 0) + cgs.media.glassShardShader = trap_R_RegisterShader( "gfx/misc/test_crackle" ); + else + cgs.media.glassShardShader = 0; + + // doing one shader just makes it look like a shell. By using two shaders with different bulge offsets and different texture scales, it has a much more chaotic look + cgs.media.electricBodyShader = trap_R_RegisterShader( "gfx/misc/electric" ); + cgs.media.electricBody2Shader = trap_R_RegisterShader( "gfx/misc/fullbodyelectric2" ); + + cgs.media.fsrMarkShader = trap_R_RegisterShader( "footstep_r" ); + cgs.media.fslMarkShader = trap_R_RegisterShader( "footstep_l" ); + cgs.media.fshrMarkShader = trap_R_RegisterShader( "footstep_heavy_r" ); + cgs.media.fshlMarkShader = trap_R_RegisterShader( "footstep_heavy_l" ); + + cgs.media.refractionShader = trap_R_RegisterShader("effects/refraction"); + + cgs.media.cloakedShader = trap_R_RegisterShader( "gfx/effects/cloakedShader" ); + + // wall marks + cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); + + cgs.media.viewPainShader = trap_R_RegisterShader( "gfx/misc/borgeyeflare" ); + cgs.media.viewPainShader_Shields = trap_R_RegisterShader( "gfx/mp/dmgshader_shields" ); + cgs.media.viewPainShader_ShieldsAndHealth = trap_R_RegisterShader( "gfx/mp/dmgshader_shieldsandhealth" ); + + // register the inline models + breakPoint = cgs.numInlineModels = trap_CM_NumInlineModels(); + for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { + char name[10]; + vec3_t mins, maxs; + int j; + + Com_sprintf( name, sizeof(name), "*%i", i ); + cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); + if (!cgs.inlineDrawModel[i]) + { + breakPoint = i; + break; + } + + trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); + for ( j = 0 ; j < 3 ; j++ ) { + cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); + } + } + + cg->loadLCARSStage = 7; + + // register all the server specified models + for (i=1 ; iloadLCARSStage = 8; +/* +Ghoul2 Insert Start +*/ + + +// CG_LoadingString( "BSP instances" ); + +/* + for(i = 1; i < MAX_SUB_BSP; i++) + { + const char *bspName = 0; + vec3_t mins, maxs; + int j; + int sub = 0; + char temp[MAX_QPATH]; + + bspName = CG_ConfigString( CS_BSP_MODELS+i ); + if ( !bspName[0] ) + { + break; + } + + trap_CM_LoadMap( bspName, qtrue ); + cgs.inlineDrawModel[breakPoint] = trap_R_RegisterModel( bspName ); + trap_R_ModelBounds( cgs.inlineDrawModel[breakPoint], mins, maxs ); + for ( j = 0 ; j < 3 ; j++ ) + { + cgs.inlineModelMidpoints[breakPoint][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); + } + breakPoint++; + for(sub=1;subloadLCARSStage = 9; + + + // new stuff + cgs.media.patrolShader = trap_R_RegisterShaderNoMip("ui/assets/statusbar/patrol.tga"); + cgs.media.assaultShader = trap_R_RegisterShaderNoMip("ui/assets/statusbar/assault.tga"); + cgs.media.campShader = trap_R_RegisterShaderNoMip("ui/assets/statusbar/camp.tga"); + cgs.media.followShader = trap_R_RegisterShaderNoMip("ui/assets/statusbar/follow.tga"); + cgs.media.defendShader = trap_R_RegisterShaderNoMip("ui/assets/statusbar/defend.tga"); + cgs.media.teamLeaderShader = trap_R_RegisterShaderNoMip("ui/assets/statusbar/team_leader.tga"); + cgs.media.retrieveShader = trap_R_RegisterShaderNoMip("ui/assets/statusbar/retrieve.tga"); + cgs.media.escortShader = trap_R_RegisterShaderNoMip("ui/assets/statusbar/escort.tga"); +// cgs.media.cursor = trap_R_RegisterShaderNoMip( "menu/art/3_cursor2" ); + cgs.media.sizeCursor = trap_R_RegisterShaderNoMip( "ui/assets/sizecursor.tga" ); + cgs.media.selectCursor = trap_R_RegisterShaderNoMip( "ui/assets/selectcursor.tga" ); + cgs.media.flagShaders[0] = trap_R_RegisterShaderNoMip("ui/assets/statusbar/flag_in_base.tga"); + cgs.media.flagShaders[1] = trap_R_RegisterShaderNoMip("ui/assets/statusbar/flag_capture.tga"); + cgs.media.flagShaders[2] = trap_R_RegisterShaderNoMip("ui/assets/statusbar/flag_missing.tga"); + + cgs.media.halfShieldModel = trap_R_RegisterModel ( "models/weaphits/testboom.md3" ); + cgs.media.halfShieldShader = trap_R_RegisterShader( "halfShieldShell" ); + + trap_FX_RegisterEffect("force/force_touch"); + + CG_ClearParticles (); +/* + for (i=1; ispectatorList[0] = 0; + + // Count up the number of players per team and per class + CG_SiegeCountCvars(); + + for (i = 0; i < MAX_CLIENTS; i++) { + if (cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_SPECTATOR ) { + Q_strcat(cg->spectatorList, sizeof(cg->spectatorList), va("%s ", cgs.clientinfo[i].name)); + } + } + i = strlen(cg->spectatorList); + if (i != cg->spectatorLen) { + cg->spectatorLen = i; + cg->spectatorWidth = -1; + } +} + + +/* +=================== +CG_RegisterClients +=================== +*/ +static void CG_RegisterClients( void ) { + int i; + + CG_LoadingClient(cg->clientNum); + CG_NewClientInfo(cg->clientNum, qfalse); + + for (i=0 ; iclientNum == i) { + continue; + } + + clientInfo = CG_ConfigString( CS_PLAYERS+i ); + if ( !clientInfo[0]) { + continue; + } + CG_LoadingClient( i ); + CG_NewClientInfo( i, qfalse); + } + CG_BuildSpectatorString(); +} + +//=========================================================================== + +/* +================= +CG_ConfigString +================= +*/ +const char *CG_ConfigString( int index ) { + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + CG_Error( "CG_ConfigString: bad index: %i", index ); + } + return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( qboolean bForceStart ) { + char *s; + char parm1[MAX_QPATH], parm2[MAX_QPATH]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( (const char **)&s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( (const char **)&s ), sizeof( parm2 ) ); + + trap_S_StartBackgroundTrack( parm1, parm2, !bForceStart ); +} + +#ifndef _XBOX +char *CG_GetMenuBuffer(const char *filename) { + int len; + fileHandle_t f; + static char buf[MAX_MENUFILE]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); + return NULL; + } + if ( len >= MAX_MENUFILE ) { + trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); + trap_FS_FCloseFile( f ); + return NULL; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + return buf; +} +#endif + +// +// ============================== +// new hud stuff ( mission pack ) +// ============================== +// +qboolean CG_Asset_Parse(int handle) { + pc_token_t token; + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + if (Q_stricmp(token.string, "{") != 0) { + return qfalse; + } + + while ( 1 ) { + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + + if (Q_stricmp(token.string, "}") == 0) { + return qtrue; + } + + // font + if (Q_stricmp(token.string, "font") == 0) { + int pointSize; + if (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + +// cgDC.registerFont(token.string, pointSize, &cgDC.Assets.textFont); + cgDC.Assets.qhMediumFont = cgDC.RegisterFont(token.string); + continue; + } + + // smallFont + if (Q_stricmp(token.string, "smallFont") == 0) { + int pointSize; + if (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } +// cgDC.registerFont(token.string, pointSize, &cgDC.Assets.smallFont); + cgDC.Assets.qhSmallFont = cgDC.RegisterFont(token.string); + continue; + } + + // smallFont + if (Q_stricmp(token.string, "small2Font") == 0) { + int pointSize; + if (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } +// cgDC.registerFont(token.string, pointSize, &cgDC.Assets.smallFont); + cgDC.Assets.qhSmall2Font = cgDC.RegisterFont(token.string); + continue; + } + + // font + if (Q_stricmp(token.string, "bigfont") == 0) { + int pointSize; + if (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } +// cgDC.registerFont(token.string, pointSize, &cgDC.Assets.bigFont); + cgDC.Assets.qhBigFont = cgDC.RegisterFont(token.string); + continue; + } + + // gradientbar +// if (Q_stricmp(token.string, "gradientbar") == 0) { +// if (!trap_PC_ReadToken(handle, &token)) { +// return qfalse; +// } +// cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(token.string); +// continue; +// } + + // enterMenuSound + if (Q_stricmp(token.string, "menuEnterSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + cgDC.Assets.menuEnterSound = trap_S_RegisterSound( token.string ); + continue; + } + + // exitMenuSound + if (Q_stricmp(token.string, "menuExitSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + cgDC.Assets.menuExitSound = trap_S_RegisterSound( token.string ); + continue; + } + + // itemFocusSound + if (Q_stricmp(token.string, "itemFocusSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + cgDC.Assets.itemFocusSound = trap_S_RegisterSound( token.string ); + continue; + } + + // menuBuzzSound + if (Q_stricmp(token.string, "menuBuzzSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( token.string ); + continue; + } + + if (Q_stricmp(token.string, "cursor") == 0) { + if (!PC_String_Parse(handle, &cgDC.Assets.cursorStr)) { + return qfalse; + } +// cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr); + continue; + } + + if (Q_stricmp(token.string, "fadeClamp") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.fadeClamp)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeCycle") == 0) { + if (!PC_Int_Parse(handle, &cgDC.Assets.fadeCycle)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeAmount") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.fadeAmount)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowX") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.shadowX)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowY") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.shadowY)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowColor") == 0) { + if (!PC_Color_Parse(handle, &cgDC.Assets.shadowColor)) { + return qfalse; + } + cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[3]; + continue; + } + } + return qfalse; // bk001204 - why not? +} + +void CG_ParseMenu(const char *menuFile) { + pc_token_t token; + int handle; + + handle = trap_PC_LoadSource(menuFile); + if (!handle) + handle = trap_PC_LoadSource("ui/testhud.menu"); + if (!handle) + return; + + while ( 1 ) { + if (!trap_PC_ReadToken( handle, &token )) { + break; + } + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if ( token.string[0] == '}' ) { + break; + } + + if (Q_stricmp(token.string, "assetGlobalDef") == 0) { + if (CG_Asset_Parse(handle)) { + continue; + } else { + break; + } + } + + + if (Q_stricmp(token.string, "menudef") == 0) { + // start a new menu + Menu_New(handle); + } + } + trap_PC_FreeSource(handle); +} + + +qboolean CG_Load_Menu(const char **p) +{ + + char *token; + + token = COM_ParseExt((const char **)p, qtrue); + + if (token[0] != '{') { + return qfalse; + } + + while ( 1 ) { + + token = COM_ParseExt((const char **)p, qtrue); + + if (Q_stricmp(token, "}") == 0) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + CG_ParseMenu(token); + } + return qfalse; +} + + +static qboolean CG_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) { + return qfalse; +} + + +static int CG_FeederCount(float feederID) { + int i, count; + count = 0; + if (feederID == FEEDER_REDTEAM_LIST) { + for (i = 0; i < cg->numScores; i++) { + if (cg->scores[i].team == TEAM_RED) { + count++; + } + } + } else if (feederID == FEEDER_BLUETEAM_LIST) { + for (i = 0; i < cg->numScores; i++) { + if (cg->scores[i].team == TEAM_BLUE) { + count++; + } + } + } else if (feederID == FEEDER_SCOREBOARD) { + return cg->numScores; + } + return count; +} + + +void CG_SetScoreSelection(void *p) { + menuDef_t *menu = (menuDef_t*)p; + playerState_t *ps = &cg->snap->ps; + int i, red, blue; + red = blue = 0; + for (i = 0; i < cg->numScores; i++) { + if (cg->scores[i].team == TEAM_RED) { + red++; + } else if (cg->scores[i].team == TEAM_BLUE) { + blue++; + } + if (ps->clientNum == cg->scores[i].client) { + cg->selectedScore = i; + } + } + + if (menu == NULL) { + // just interested in setting the selected score + return; + } + + if ( cgs.gametype >= GT_TEAM ) { + int feeder = FEEDER_REDTEAM_LIST; + i = red; + if (cg->scores[cg->selectedScore].team == TEAM_BLUE) { + feeder = FEEDER_BLUETEAM_LIST; + i = blue; + } + Menu_SetFeederSelection(menu, feeder, i, NULL); + } else { + Menu_SetFeederSelection(menu, FEEDER_SCOREBOARD, cg->selectedScore, NULL); + } +} + +// FIXME: might need to cache this info +static clientInfo_t * CG_InfoFromScoreIndex(int index, int team, int *scoreIndex) { + int i, count; + if ( cgs.gametype >= GT_TEAM ) { + count = 0; + for (i = 0; i < cg->numScores; i++) { + if (cg->scores[i].team == team) { + if (count == index) { + *scoreIndex = i; + return &cgs.clientinfo[cg->scores[i].client]; + } + count++; + } + } + } + *scoreIndex = index; + return &cgs.clientinfo[ cg->scores[index].client ]; +} + +static const char *CG_FeederItemText(float feederID, int index, int column, + qhandle_t *handle1, qhandle_t *handle2, qhandle_t *handle3) { + gitem_t *item; + int scoreIndex = 0; + clientInfo_t *info = NULL; + int team = -1; + score_t *sp = NULL; + + *handle1 = *handle2 = *handle3 = -1; + + if (feederID == FEEDER_REDTEAM_LIST) { + team = TEAM_RED; + } else if (feederID == FEEDER_BLUETEAM_LIST) { + team = TEAM_BLUE; + } + + info = CG_InfoFromScoreIndex(index, team, &scoreIndex); + sp = &cg->scores[scoreIndex]; + + if (info && info->infoValid) { + switch (column) { + case 0: + if ( info->powerups & ( 1 << PW_NEUTRALFLAG ) ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + *handle1 = cg_items[ ITEM_INDEX(item) ].icon; + } else if ( info->powerups & ( 1 << PW_REDFLAG ) ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + *handle1 = cg_items[ ITEM_INDEX(item) ].icon; + } else if ( info->powerups & ( 1 << PW_BLUEFLAG ) ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + *handle1 = cg_items[ ITEM_INDEX(item) ].icon; + } else { + /* + if ( info->botSkill > 0 && info->botSkill <= 5 ) { + *handle1 = cgs.media.botSkillShaders[ info->botSkill - 1 ]; + } else if ( info->handicap < 100 ) { + return va("%i", info->handicap ); + } + */ + } + break; + case 1: + if (team == -1) { + return ""; + } else { + *handle1 = CG_StatusHandle(info->teamTask); + } + break; + case 2: + if ( cg->snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << sp->client ) ) { + return "Ready"; + } + if (team == -1) { + if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) { + return va("%i/%i", info->wins, info->losses); + } else if (info->infoValid && info->team == TEAM_SPECTATOR ) { + return "Spectator"; + } else { + return ""; + } + } else { + if (info->teamLeader) { + return "Leader"; + } + } + break; + case 3: + return info->name; + break; + case 4: + return va("%i", info->score); + break; + case 5: + return va("%4i", sp->time); + break; + case 6: + if ( sp->ping == -1 ) { + return "connecting"; + } + return va("%4i", sp->ping); + break; + } + } + + return ""; +} + +static qhandle_t CG_FeederItemImage(float feederID, int index) { + return 0; +} + +static qboolean CG_FeederSelection(float feederID, int index, itemDef_t *item) { + if ( cgs.gametype >= GT_TEAM ) { + int i, count; + int team = (feederID == FEEDER_REDTEAM_LIST) ? TEAM_RED : TEAM_BLUE; + count = 0; + for (i = 0; i < cg->numScores; i++) { + if (cg->scores[i].team == team) { + if (index == count) { + cg->selectedScore = i; + } + count++; + } + } + } else { + cg->selectedScore = index; + } + + return qtrue; +} + +static float CG_Cvar_Get(const char *cvar) { + char buff[128]; + memset(buff, 0, sizeof(buff)); + trap_Cvar_VariableStringBuffer(cvar, buff, sizeof(buff)); + return atof(buff); +} + +void CG_Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style, int iMenuFont) { + CG_Text_Paint(x, y, scale, color, text, 0, limit, style, iMenuFont); +} + +static int CG_OwnerDrawWidth(int ownerDraw, float scale) { + switch (ownerDraw) { + case CG_GAME_TYPE: + return CG_Text_Width(CG_GameTypeString(), scale, FONT_MEDIUM); + case CG_GAME_STATUS: + return CG_Text_Width(CG_GetGameStatusText(), scale, FONT_MEDIUM); + break; + case CG_KILLER: + return CG_Text_Width(CG_GetKillerText(), scale, FONT_MEDIUM); + break; + case CG_RED_NAME: + return CG_Text_Width(cg_redTeamName.string, scale, FONT_MEDIUM); + break; + case CG_BLUE_NAME: + return CG_Text_Width(cg_blueTeamName.string, scale, FONT_MEDIUM); + break; + + + } + return 0; +} +/* +static int CG_PlayCinematic(const char *name, float x, float y, float w, float h) { + return trap_CIN_PlayCinematic(name, x, y, w, h, CIN_loop); +} + +static void CG_StopCinematic(int handle) { + trap_CIN_StopCinematic(handle); +} + +static void CG_DrawCinematic(int handle, float x, float y, float w, float h) { + trap_CIN_SetExtents(handle, x, y, w, h); + trap_CIN_DrawCinematic(handle); +} + +static void CG_RunCinematicFrame(int handle) { + trap_CIN_RunCinematic(handle); +} +*/ + +/* +================= +CG_LoadMenus(); + +================= +*/ +void CG_LoadMenus(const char *menuFile) +{ + const char *token; + const char *p; + int len; + fileHandle_t f; + char buf[MAX_MENUDEFFILE]; + + len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); + + if ( !f ) + { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", menuFile ) ); + + len = trap_FS_FOpenFile( "ui/jahud.txt", &f, FS_READ ); + if (!f) + { + trap_Print( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) ); + } + } + + if ( len >= MAX_MENUDEFFILE ) + { + trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", menuFile, len, MAX_MENUDEFFILE ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + p = buf; + + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if( !token || token[0] == 0 || token[0] == '}') + { + break; + } + + if ( Q_stricmp( token, "}" ) == 0 ) + { + break; + } + + if (Q_stricmp(token, "loadmenu") == 0) + { + if (CG_Load_Menu(&p)) + { + continue; + } + else + { + break; + } + } + } + + //Com_Printf("UI menu load time = %d milli seconds\n", cgi_Milliseconds() - start); +} + +/* +================= +CG_LoadHudMenu(); + +================= +*/ +void CG_LoadHudMenu() +{ + const char *hudSet; + + cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + cgDC.setColor = &trap_R_SetColor; + cgDC.drawHandlePic = &CG_DrawPic; + cgDC.drawStretchPic = &trap_R_DrawStretchPic; + cgDC.drawText = &CG_Text_Paint; + cgDC.textWidth = &CG_Text_Width; + cgDC.textHeight = &CG_Text_Height; + cgDC.registerModel = &trap_R_RegisterModel; + cgDC.modelBounds = &trap_R_ModelBounds; + cgDC.fillRect = &CG_FillRect; + cgDC.drawRect = &CG_DrawRect; + cgDC.drawSides = &CG_DrawSides; + cgDC.drawTopBottom = &CG_DrawTopBottom; + cgDC.clearScene = &trap_R_ClearScene; + cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + cgDC.renderScene = &trap_R_RenderScene; + cgDC.RegisterFont = &trap_R_RegisterFont; + cgDC.Font_StrLenPixels = &trap_R_Font_StrLenPixels; + cgDC.Font_StrLenChars = &trap_R_Font_StrLenChars; + cgDC.Font_HeightPixels = &trap_R_Font_HeightPixels; + cgDC.Font_DrawString = &trap_R_Font_DrawString; + cgDC.Language_IsAsian = &trap_Language_IsAsian; + cgDC.Language_UsesSpaces = &trap_Language_UsesSpaces; + cgDC.AnyLanguage_ReadCharFromString = &trap_AnyLanguage_ReadCharFromString; + cgDC.ownerDrawItem = &CG_OwnerDraw; + cgDC.getValue = &CG_GetValue; + cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; + cgDC.runScript = &CG_RunMenuScript; + cgDC.deferScript = &CG_DeferMenuScript; + cgDC.getTeamColor = &CG_GetTeamColor; + cgDC.setCVar = trap_Cvar_Set; + cgDC.getCVarString = trap_Cvar_VariableStringBuffer; + cgDC.getCVarValue = CG_Cvar_Get; + cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor; + //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + cgDC.startLocalSound = &trap_S_StartLocalSound; + cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; + cgDC.feederCount = &CG_FeederCount; + cgDC.feederItemImage = &CG_FeederItemImage; + cgDC.feederItemText = &CG_FeederItemText; + cgDC.feederSelection = &CG_FeederSelection; + //cgDC.setBinding = &trap_Key_SetBinding; + //cgDC.getBindingBuf = &trap_Key_GetBindingBuf; + //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; + //cgDC.executeText = &trap_Cmd_ExecuteText; + cgDC.Error = &Com_Error; + cgDC.Print = &Com_Printf; + cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; + //cgDC.Pause = &CG_Pause; + cgDC.registerSound = &trap_S_RegisterSound; + cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; +// cgDC.playCinematic = &CG_PlayCinematic; +// cgDC.stopCinematic = &CG_StopCinematic; +// cgDC.drawCinematic = &CG_DrawCinematic; +// cgDC.runCinematicFrame = &CG_RunCinematicFrame; + + Init_Display(&cgDC); + + Menu_Reset(); + + hudSet = cg_hudFiles.string; + if (hudSet[0] == '\0') + { + hudSet = "ui/jahud.txt"; + } + + CG_LoadMenus(hudSet); + +} + +void CG_AssetCache() { + //if (Assets.textFont == NULL) { + // trap_R_RegisterFont("fonts/arial.ttf", 72, &Assets.textFont); + //} + //Assets.background = trap_R_RegisterShaderNoMip( ASSET_BACKGROUND ); + //Com_Printf("Menu Size: %i bytes\n", sizeof(Menus)); +// cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); +// cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); +// cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); +// cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); +// cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); +// cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); +// cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); +// cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); +} + +/* + + +/* +Ghoul2 Insert Start +*/ + +// initialise the cg_entities structure - take into account the ghoul2 stl stuff in the active snap shots +void CG_Init_CG(void) +{ +#ifdef _XBOX + qboolean widescreen = cg->widescreen; +#endif + memset( cg, 0, sizeof(*cg)); +#ifdef _XBOX + cg->widescreen = widescreen; +#endif +} + +#ifdef _XBOX +void CG_SetWidescreen(qboolean widescreen) +{ + cg->widescreen = widescreen; +} +#endif + + +// initialise the cg_entities structure - take into account the ghoul2 stl stuff +void CG_Init_CGents(void) +{ + +// memset(&cg_entities, 0, sizeof(cg_entities)); + memset(cg_entities, 0, MAX_GENTITIES * sizeof(centity_t)); +} + + +void CG_InitItems(void) +{ + memset( cg_items, 0, sizeof( cg_items ) ); +} + +void CG_TransitionPermanent(void) +{ + centity_t *cent = cg_entities; + int i; + + cg_numpermanents = 0; + for(i=0;icurrentState)) + { + cent->nextState = cent->currentState; + VectorCopy (cent->currentState.origin, cent->lerpOrigin); + VectorCopy (cent->currentState.angles, cent->lerpAngles); +#ifdef _XBOX + cent->currentValid[ClientManager::ActiveClientNum()] = qtrue; +#else + cent->currentValid = qtrue; +#endif + + cg_permanents[cg_numpermanents++] = cent; + } + } +} + + +//this is a 32k custom pool for parsing ents, it can get reset between ent parsing +//so we don't need a whole lot of memory -rww +#define MAX_CGSTRPOOL_SIZE 32768 +static int cg_strPoolSize = 0; +static byte cg_strPool[MAX_CGSTRPOOL_SIZE]; + +char *CG_StrPool_Alloc(int size) +{ + char *giveThemThis; + + if (cg_strPoolSize+size >= MAX_CGSTRPOOL_SIZE) + { + Com_Error(ERR_DROP, "You exceeded the cgame string pool size. Bad programmer!\n"); + } + + giveThemThis = (char *) &cg_strPool[cg_strPoolSize]; + cg_strPoolSize += size; + + //memset it for them, just to be nice. + memset(giveThemThis, 0, size); + + return giveThemThis; +} + +void CG_StrPool_Reset(void) +{ + cg_strPoolSize = 0; +} + +/* +============= +CG_NewString + +Builds a copy of the string, translating \n to real linefeeds +so message texts can be multi-line +============= +*/ +char *CG_NewString( const char *string ) +{ + char *newb, *new_p; + int i,l; + + l = strlen(string) + 1; + + newb = CG_StrPool_Alloc( l ); + + new_p = newb; + + // turn \n into a real linefeed + 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; +} + +//data to grab our spawn info into +typedef struct cgSpawnEnt_s +{ + char *classname; + vec3_t origin; + vec3_t angles; + float angle; + vec3_t scale; + float fScale; + vec3_t mins; + vec3_t maxs; + char *model; + float zoffset; + int onlyFogHere; + float fogstart; + float radarrange; +} cgSpawnEnt_t; + +#define CGFOFS(x) ((int)&(((cgSpawnEnt_t *)0)->x)) + +//spawn fields for our cgame "entity" +BG_field_t cg_spawnFields[] = +{ + {"classname", CGFOFS(classname), F_LSTRING}, + {"origin", CGFOFS(origin), F_VECTOR}, + {"angles", CGFOFS(angles), F_VECTOR}, + {"angle", CGFOFS(angle), F_FLOAT}, + {"modelscale", CGFOFS(fScale), F_FLOAT}, + {"modelscale_vec", CGFOFS(scale), F_VECTOR}, + {"model", CGFOFS(model), F_LSTRING}, + {"mins", CGFOFS(mins), F_VECTOR}, + {"maxs", CGFOFS(maxs), F_VECTOR}, + {"zoffset", CGFOFS(zoffset), F_FLOAT}, + {"onlyfoghere", CGFOFS(onlyFogHere), F_INT}, + {"fogstart", CGFOFS(fogstart), F_FLOAT}, + {"radarrange", CGFOFS(radarrange), F_FLOAT}, + {NULL} +}; + +static int cg_numSpawnVars; +static int cg_numSpawnVarChars; +static char *cg_spawnVars[MAX_SPAWN_VARS][2]; +static char cg_spawnVarChars[MAX_SPAWN_VARS_CHARS]; + +//get some info from the skyportal ent on the map +qboolean cg_noFogOutsidePortal = qfalse; +void CG_CreateSkyPortalFromSpawnEnt(cgSpawnEnt_t *ent) +{ + if (ent->onlyFogHere) + { //only globally fog INSIDE the sky portal + cg_noFogOutsidePortal = qtrue; + } +} + +//create a skybox portal orientation entity. there -should- only +//be one of these things per level. if there's more than one the +//next will just stomp over the last. -rww +qboolean cg_skyOri = qfalse; +vec3_t cg_skyOriPos; +float cg_skyOriScale = 0.0f; +void CG_CreateSkyOriFromSpawnEnt(cgSpawnEnt_t *ent) +{ + cg_skyOri = qtrue; + VectorCopy(ent->origin, cg_skyOriPos); + cg_skyOriScale = ent->fScale; +} + +//get brush box extents, note this does not care about bsp instances. +void CG_CreateBrushEntData(cgSpawnEnt_t *ent) +{ + trap_R_ModelBounds(trap_R_RegisterModel(ent->model), ent->mins, ent->maxs); +} + +void CG_CreateWeatherZoneFromSpawnEnt(cgSpawnEnt_t *ent) +{ + CG_CreateBrushEntData(ent); + trap_WE_AddWeatherZone(ent->mins, ent->maxs); +} + +//create a new cgame-only model +void CG_CreateModelFromSpawnEnt(cgSpawnEnt_t *ent) +{ + int modelIndex; + refEntity_t *RefEnt; + vec3_t mins, maxs; + float *radius; + float *zOff; + + if (NumMiscEnts >= MAX_MISC_ENTS) + { + Com_Error(ERR_DROP, "Too many misc_model_static's on level, ask a programmer to raise the limit (currently %i), or take some out.", MAX_MISC_ENTS); + return; + } + + if (!ent || !ent->model || !ent->model[0]) + { + Com_Error(ERR_DROP, "misc_model_static with no model."); + return; + } + + radius = &Radius[NumMiscEnts]; + zOff = &zOffset[NumMiscEnts]; + RefEnt = &MiscEnts[NumMiscEnts++]; + + modelIndex = trap_R_RegisterModel(ent->model); + if (modelIndex == 0) + { + Com_Error(ERR_DROP, "misc_model_static failed to load model '%s'",ent->model); + return; + } + + memset(RefEnt, 0, sizeof(refEntity_t)); + RefEnt->reType = RT_MODEL; + RefEnt->hModel = modelIndex; + RefEnt->frame = 0; + trap_R_ModelBounds(modelIndex, mins, maxs); + VectorCopy(ent->scale, RefEnt->modelScale); + if (ent->fScale) + { //use same scale on each axis then + RefEnt->modelScale[0] = RefEnt->modelScale[1] = RefEnt->modelScale[2] = ent->fScale; + } + VectorCopy(ent->origin, RefEnt->origin); + VectorCopy(ent->origin, RefEnt->lightingOrigin); + + VectorScaleVector(mins, ent->scale, mins); + VectorScaleVector(maxs, ent->scale, maxs); + *radius = Distance(mins, maxs); + *zOff = ent->zoffset; + + if (ent->angle) + { //only yaw supplied... + ent->angles[YAW] = ent->angle; + } + + AnglesToAxis( ent->angles, RefEnt->axis ); + ScaleModelAxis(RefEnt); +} + +/* +==================== +CG_AddSpawnVarToken +==================== +*/ +char *CG_AddSpawnVarToken( const char *string ) +{ + int l; + char *dest; + + l = strlen( string ); + if ( cg_numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS ) { + CG_Error( "CG_AddSpawnVarToken: MAX_SPAWN_VARS" ); + } + + dest = cg_spawnVarChars + cg_numSpawnVarChars; + memcpy( dest, string, l+1 ); + + cg_numSpawnVarChars += l + 1; + + return dest; +} + +/* +==================== +CG_ParseSpawnVars + +cgame version of G_ParseSpawnVars, for ents that don't really +need to take up an entity slot (e.g. static models) -rww +==================== +*/ +qboolean CG_ParseSpawnVars( void ) +{ + char keyname[MAX_TOKEN_CHARS]; + char com_token[MAX_TOKEN_CHARS]; + + cg_numSpawnVars = 0; + cg_numSpawnVarChars = 0; + + // parse the opening brace + if ( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) { + // end of spawn string + return qfalse; + } + if ( com_token[0] != '{' ) { + CG_Error( "CG_ParseSpawnVars: found %s when expecting {",com_token ); + } + + // go through all the key / value pairs + while ( 1 ) + { + // parse key + if ( !trap_GetEntityToken( keyname, sizeof( keyname ) ) ) + { + CG_Error( "CG_ParseSpawnVars: EOF without closing brace" ); + } + + if ( keyname[0] == '}' ) + { + break; + } + + // parse value + if ( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) + { //this happens on mike's test level, I don't know why. Fixme? + //CG_Error( "CG_ParseSpawnVars: EOF without closing brace" ); + break; + } + + if ( com_token[0] == '}' ) + { + CG_Error( "CG_ParseSpawnVars: closing brace without data" ); + } + if ( cg_numSpawnVars == MAX_SPAWN_VARS ) + { + CG_Error( "CG_ParseSpawnVars: MAX_SPAWN_VARS" ); + } + cg_spawnVars[ cg_numSpawnVars ][0] = CG_AddSpawnVarToken( keyname ); + cg_spawnVars[ cg_numSpawnVars ][1] = CG_AddSpawnVarToken( com_token ); + cg_numSpawnVars++; + } + + return qtrue; +} + +/* +============== +CG_SpawnCGameEntFromVars + +See if we should do something for this ent cgame-side -rww +============== +*/ +#include "../namespace_begin.h" +void BG_ParseField( BG_field_t *l_fields, const char *key, const char *value, byte *ent ); +#include "../namespace_end.h" + +extern float cg_linearFogOverride; //cg_view.c +extern float cg_radarRange;//cg_draw.c +void CG_SpawnCGameEntFromVars(void) +{ + int i; + cgSpawnEnt_t ent; + + memset(&ent, 0, sizeof(cgSpawnEnt_t)); + + for (i = 0; i < cg_numSpawnVars; i++) + { //shove all this stuff into our data structure used specifically for getting spawn info + BG_ParseField( cg_spawnFields, cg_spawnVars[i][0], cg_spawnVars[i][1], (byte *)&ent ); + } + + if (ent.classname && ent.classname[0]) + { //we'll just stricmp this bastard, since there aren't all that many cgame-only things, and they all have special handling + if (!Q_stricmp(ent.classname, "worldspawn")) + { //I'd like some info off this guy + if (ent.fogstart) + { //linear fog method + cg_linearFogOverride = ent.fogstart; + } + //get radarRange off of worldspawn + if (ent.radarrange) + { //linear fog method + cg_radarRange = ent.radarrange; + } + } + else if (!Q_stricmp(ent.classname, "misc_model_static")) + { //we've got us a static model + CG_CreateModelFromSpawnEnt(&ent); + } + else if (!Q_stricmp(ent.classname, "misc_skyportal_orient")) + { //a sky portal orientation point + CG_CreateSkyOriFromSpawnEnt(&ent); + } + else if (!Q_stricmp(ent.classname, "misc_skyportal")) + { //might as well parse this thing cgame side for the extra info I want out of it + CG_CreateSkyPortalFromSpawnEnt(&ent); + } + else if (!Q_stricmp(ent.classname, "misc_weather_zone")) + { //might as well parse this thing cgame side for the extra info I want out of it + CG_CreateWeatherZoneFromSpawnEnt(&ent); + } + } + + //reset the string pool for the next entity, if there is one + CG_StrPool_Reset(); +} + +/* +============== +CG_SpawnCGameOnlyEnts + +Parses entity string data for cgame-only entities, that we can throw away on +the server and never even bother sending. -rww +============== +*/ +void CG_SpawnCGameOnlyEnts(void) +{ + //make sure it is reset + trap_GetEntityToken(NULL, -1); + + if (!CG_ParseSpawnVars()) + { //first one is gonna be the world spawn + CG_Error("no entities for cgame parse"); + } + else + { //parse the world spawn info we want + CG_SpawnCGameEntFromVars(); + } + + while(CG_ParseSpawnVars()) + { //now run through the whole list, and look for things we care about cgame-side + CG_SpawnCGameEntFromVars(); + } +} + +/* +Ghoul2 Insert End +*/ + +extern playerState_t *cgSendPS[MAX_GENTITIES]; //is not MAX_CLIENTS because NPCs exceed MAX_CLIENTS +void CG_PmoveClientPointerUpdate(); + +#include "../namespace_begin.h" +void WP_SaberLoadParms( void ); +void BG_VehicleLoadParms( void ); +#include "../namespace_end.h" + +/* +================= +CG_Init + +Called after every level change or subsystem restart +Will perform callbacks to make the loading info screen update. +================= +*/ +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) +{ + static gitem_t *item; + char buf[64]; + const char *s; + int i = 0; + + if( !cg_entities ) + cg_entities = new centity_t[MAX_GENTITIES]; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() != 1) +#endif + BG_InitAnimsets(); //clear it out + + trap_CG_RegisterSharedMemory(cg->sharedBuffer); + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() != 1) +#endif + //Load external vehicle data + BG_VehicleLoadParms(); + + // clear everything +/* +Ghoul2 Insert Start +*/ + +// memset( cg_entities, 0, sizeof( cg_entities ) ); + CG_Init_CGents(); +// this is a No-No now we have stl vector classes in here. +// memset( &cg, 0, sizeof( cg ) ); + CG_Init_CG(); + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() != 1) { +#endif + CG_InitItems(); + + //create the global jetpack instance + CG_InitJetpackGhoul2(); + + CG_PmoveClientPointerUpdate(); + +/* +Ghoul2 Insert End +*/ + + //Load sabers.cfg data + WP_SaberLoadParms(); + + // this is kinda dumb as well, but I need to pre-load some fonts in order to have the text available + // to say I'm loading the assets.... which includes loading the fonts. So I'll set these up as reasonable + // defaults, then let the menu asset parser (which actually specifies the ingame fonts) load over them + // if desired during parse. Dunno how legal it is to store in these cgDC things, but it causes no harm + // and even if/when they get overwritten they'll be legalised by the menu asset parser :-) +// CG_LoadFonts(); +// cgDC.Assets.qhSmallFont = trap_R_RegisterFont("ocr_a"); + cgDC.Assets.qhSmallFont = trap_R_RegisterFont("ergoec"); // Xbox - use ergoec here too! + cgDC.Assets.qhMediumFont = trap_R_RegisterFont("ergoec"); + cgDC.Assets.qhBigFont = cgDC.Assets.qhMediumFont; + + memset( &cgs, 0, sizeof( cgs ) ); + memset( cg_weapons, 0, sizeof(cg_weapons) ); + +#ifdef _XBOX + } +#endif + + cg->clientNum = clientNum; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + cgs.processedSnapshotNum[ClientManager::ActiveClientNum()] = serverCommandSequence; + else + cgs.processedSnapshotNum[0] = serverMessageNum; +#else + cgs.processedSnapshotNum = serverMessageNum; +#endif + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + ClientManager::ActiveClient().serverCommandSequence = serverCommandSequence; + else +#endif + cgs.serverCommandSequence = serverCommandSequence; + + + cg->loadLCARSStage = 0; + + cg->itemSelect = -1; + cg->forceSelect = -1; + + // load a few needed things before we do any screen updates +// cgs.media.charsetShader = trap_R_RegisterShaderNoMip( "gfx/2d/charsgrid_med" ); + cgs.media.whiteShader = trap_R_RegisterShader( "white" ); + + cgs.media.loadTick = trap_R_RegisterShaderNoMip( "gfx/menus/newFront/GlowLoad" ); + cgs.media.levelLoad = trap_R_RegisterShaderNoMip( "gfx/menus/newFront/SaberLoad" ); + + // Force HUD set up + cg->forceHUDActive = qtrue; + cg->forceHUDTotalFlashTime = 0; + cg->forceHUDNextFlashTime = 0; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() != 1) { +#endif + i = WP_NONE+1; + while (i <= LAST_USEABLE_WEAPON) + { + item = BG_FindItemForWeapon(i); + + if (item && item->icon && item->icon[0]) + { + cgs.media.weaponIcons[i] = trap_R_RegisterShaderNoMip(item->icon); + cgs.media.weaponIcons_NA[i] = trap_R_RegisterShaderNoMip(va("%s_na", item->icon)); + } + else + { //make sure it is zero'd (default shader) + cgs.media.weaponIcons[i] = 0; + cgs.media.weaponIcons_NA[i] = 0; + } + i++; + } + trap_Cvar_VariableStringBuffer("com_buildscript", buf, sizeof(buf)); + if (atoi(buf)) + { + trap_R_RegisterShaderNoMip("gfx/hud/w_icon_saberstaff"); + trap_R_RegisterShaderNoMip("gfx/hud/w_icon_duallightsaber"); + } + i = 0; + + // HUD artwork for cycling inventory,weapons and force powers +// cgs.media.weaponIconBackground = trap_R_RegisterShaderNoMip( "gfx/hud/background"); +// cgs.media.forceIconBackground = trap_R_RegisterShaderNoMip( "gfx/hud/background_f"); +// cgs.media.inventoryIconBackground = trap_R_RegisterShaderNoMip( "gfx/hud/background_i"); + + //rww - precache holdable item icons here + while (i < bg_numItems) + { + if (bg_itemlist[i].giType == IT_HOLDABLE) + { + if (bg_itemlist[i].icon) + { + cgs.media.invenIcons[bg_itemlist[i].giTag] = trap_R_RegisterShaderNoMip(bg_itemlist[i].icon); + } + else + { + cgs.media.invenIcons[bg_itemlist[i].giTag] = 0; + } + } + + i++; + } + + //rww - precache force power icons here + i = 0; + + while (i < NUM_FORCE_POWERS) + { + cgs.media.forcePowerIcons[i] = trap_R_RegisterShaderNoMip(HolocronIcons[i]); + + i++; + } + cgs.media.rageRecShader = trap_R_RegisterShaderNoMip("gfx/mp/f_icon_ragerec"); + + + //body decal shaders -rww + cgs.media.bdecal_bodyburn1 = trap_R_RegisterShader("gfx/damage/bodyburnmark1"); + cgs.media.bdecal_saberglow = trap_R_RegisterShader("gfx/damage/saberglowmark"); + cgs.media.bdecal_burn1 = trap_R_RegisterShader("gfx/damage/bodybigburnmark1"); + cgs.media.mSaberDamageGlow = trap_R_RegisterShader("gfx/effects/saberDamageGlow"); + + CG_RegisterCvars(); + + CG_InitConsoleCommands(); + +#ifdef _XBOX + } +#endif + + cg->weaponSelect = WP_BRYAR_PISTOL; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() != 1) { +#endif + cgs.redflag = cgs.blueflag = -1; // For compatibily, default to unset for + cgs.flagStatus = -1; + // old servers + + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); +#ifdef _XBOX + if(cg->widescreen) + cgs.screenXScale = cgs.glconfig.vidWidth / 720.0; + else +#endif + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; + + // get the gamestate from the client system + trap_GetGameState( &cgs.gameState ); + + CG_TransitionPermanent(); //rwwRMG - added + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + if ( strcmp( s, GAME_VERSION ) ) { + CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + } + + s = CG_ConfigString( CS_LEVEL_START_TIME ); + cgs.levelStartTime = atoi( s ); + + CG_ParseServerinfo(); + + // load the new map +// CG_LoadingString( "collision map" ); + + trap_CM_LoadMap( cgs.mapname, qfalse ); + + String_Init(); + + cg->loading = qtrue; // force players to load instead of defer + + //make sure saber data is loaded before this! (so we can precache the appropriate hilts) + CG_InitSiegeMode(); + + CG_RegisterSounds(); + +// CG_LoadingString( "graphics" ); + +#ifdef _XBOX + } +#endif + + CG_RegisterGraphics(); + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() != 1) { +#endif +// CG_LoadingString( "clients" ); + + CG_RegisterClients(); // if low on memory, some clients will be deferred + + CG_AssetCache(); + CG_LoadHudMenu(); // load new hud stuff + + cg->loading = qfalse; // future players will be deferred + + CG_InitLocalEntities(); + + CG_InitMarkPolys(); + + // remove the last loading update + cg->infoScreenText[0] = 0; + + // Make sure we have update values (scores) + CG_SetConfigValues(); + + CG_StartMusic(qfalse); + +// CG_LoadingString( "Clearing light styles" ); + CG_ClearLightStyles(); + +// CG_LoadingString( "Creating automap data" ); + //init automap +#ifndef _XBOX + trap_R_InitWireframeAutomap(); +#endif + + CG_LoadingString( "" ); + + CG_ShaderStateChanged(); + + trap_S_ClearLoopingSounds(); + +#ifdef _XBOX + } +#endif + + trap_R_GetDistanceCull(&cg->distanceCull); + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() != 1) +#endif + //now get all the cgame only cents + CG_SpawnCGameOnlyEnts(); +} + +//makes sure returned string is in localized format +const char *CG_GetLocationString(const char *loc) +{ +// static char text[1024]={0}; + + if (!loc || loc[0] != '@') + { //just a raw string + return loc; + } + + extern const char *SE_GetString( const char *psPackageAndStringReference ); + return SE_GetString( loc+1 ); +// trap_SP_GetStringTextString(loc+1, text, sizeof(text)); +// return text; +} + +//clean up all the ghoul2 allocations, the nice and non-hackly way -rww +void CG_KillCEntityG2(int entNum); +void CG_DestroyAllGhoul2(void) +{ + int i = 0; + int j; + +// Com_Printf("... CGameside GHOUL2 Cleanup\n"); + while (i < MAX_GENTITIES) + { //free all dynamically allocated npc client info structs and ghoul2 instances + CG_KillCEntityG2(i); + i++; + } + + //Clean the weapon instances + CG_ShutDownG2Weapons(); + + i = 0; + while (i < MAX_ITEMS) + { //and now for items + j = 0; + while (j < MAX_ITEM_MODELS) + { + if (cg_items[i].g2Models[j] && trap_G2_HaveWeGhoul2Models(cg_items[i].g2Models[j])) + { + trap_G2API_CleanGhoul2Models(&cg_items[i].g2Models[j]); + cg_items[i].g2Models[j] = NULL; + } + j++; + } + i++; + } + + //Clean the global jetpack instance + CG_CleanJetpackGhoul2(); +} + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) +{ + BG_ClearAnimsets(); //free all dynamic allocations made through the engine + + CG_DestroyAllGhoul2(); + +// Com_Printf("... FX System Cleanup\n"); + trap_FX_FreeSystem(); + trap_ROFF_Clean(); + + if (cgWeatherOverride) + { + trap_R_WeatherContentsOverride(0); //rwwRMG - reset it engine-side + } + + //reset weather + trap_R_WorldEffectCommand("die"); + + UI_CleanupGhoul2(); + //If there was any ghoul2 stuff in our side of the shared ui code, then remove it now. + + // some mods may need to do cleanup work here, + // like closing files or archiving session data + + // De-allocate cg_entities (if this is a map change, it just gets re-created above) + delete [] cg_entities; + cg_entities = NULL; +} + +#ifdef _XBOX // include this so that we have access to the player class here ( ie jedi, infantry, etc...) +#include "bg_saga.h" +#endif + +/* +=============== +CG_NextForcePower_f +=============== +*/ +void CG_NextForcePower_f( void ) +{ + int current; + usercmd_t cmd; + + if ( !cg->snap ) + { + return; + } + + if (cg->predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + // SIEGE + // if we have inventory, then we don't have force powers + // remap nextforcepower to nextinventory +#ifdef _XBOX +// short cg_siegeClassIndex = cgs.clientinfo[cg->snap->ps.clientNum].siegeIndex; +// if(cgs.gametype == GT_SIEGE && bgSiegeClasses[cg_siegeClassIndex].invenItems ) +// { +// CG_NextInventory_f(); +// return; +// } +#endif + + ClientManager::ActiveClient().swapMan1.SetUp(); + ClientManager::ActiveClient().swapMan2.SetUp(); + + current = trap_GetCurrentCmdNumber(); + trap_GetUserCmd(current, &cmd); + if ((cmd.buttons & BUTTON_USE) || CG_NoUseableForce()) + { + CG_NextInventory_f(); + return; + } + + if (cg->snap->ps.pm_flags & PMF_FOLLOW) + { + return; + } + +// BG_CycleForce(&cg->snap->ps, 1); + if (cg->forceSelect != -1) + { + cg->snap->ps.fd.forcePowerSelected = cg->forceSelect; + } + + BG_CycleForce(&cg->snap->ps, 1); + + if (cg->snap->ps.fd.forcePowersKnown & (1 << cg->snap->ps.fd.forcePowerSelected)) + { + cg->forceSelect = cg->snap->ps.fd.forcePowerSelected; + cg->forceSelectTime = cg->time; + } +} + +/* +=============== +CG_PrevForcePower_f +=============== +*/ +void CG_PrevForcePower_f( void ) +{ + int current; + usercmd_t cmd; + + if ( !cg->snap ) + { + return; + } + + if (cg->predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + // SIEGE + // if we have inventory, then we don't have force powers + // remap prevforcepower to previnventory +#ifdef _XBOX +// short cg_siegeClassIndex = cgs.clientinfo[cg->snap->ps.clientNum].siegeIndex; +// if(cgs.gametype == GT_SIEGE && bgSiegeClasses[cg_siegeClassIndex].invenItems ) +// { +// CG_PrevInventory_f(); +// return; +// } +#endif + + ClientManager::ActiveClient().swapMan1.SetUp(); + ClientManager::ActiveClient().swapMan2.SetUp(); + + current = trap_GetCurrentCmdNumber(); + trap_GetUserCmd(current, &cmd); + if ((cmd.buttons & BUTTON_USE) || CG_NoUseableForce()) + { + CG_PrevInventory_f(); + return; + } + + if (cg->snap->ps.pm_flags & PMF_FOLLOW) + { + return; + } + +// BG_CycleForce(&cg->snap->ps, -1); + if (cg->forceSelect != -1) + { + cg->snap->ps.fd.forcePowerSelected = cg->forceSelect; + } + + BG_CycleForce(&cg->snap->ps, -1); + + if (cg->snap->ps.fd.forcePowersKnown & (1 << cg->snap->ps.fd.forcePowerSelected)) + { + cg->forceSelect = cg->snap->ps.fd.forcePowerSelected; + cg->forceSelectTime = cg->time; + } +} + +void CG_NextInventory_f(void) +{ + if ( !cg->snap ) + { + return; + } + + if (cg->snap->ps.pm_flags & PMF_FOLLOW) + { + return; + } + + if (cg->predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + ClientManager::ActiveClient().swapMan1.SetUp(); + ClientManager::ActiveClient().swapMan2.SetUp(); + + if (cg->itemSelect != -1) + { + cg->snap->ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(cg->itemSelect, IT_HOLDABLE); + } + BG_CycleInven(&cg->snap->ps, 1); + + if (cg->snap->ps.stats[STAT_HOLDABLE_ITEM]) + { + cg->itemSelect = bg_itemlist[cg->snap->ps.stats[STAT_HOLDABLE_ITEM]].giTag; + cg->invenSelectTime = cg->time; + } +} + +void CG_PrevInventory_f(void) +{ + if ( !cg->snap ) + { + return; + } + + if (cg->snap->ps.pm_flags & PMF_FOLLOW) + { + return; + } + + if (cg->predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + ClientManager::ActiveClient().swapMan1.SetUp(); + ClientManager::ActiveClient().swapMan2.SetUp(); + + if (cg->itemSelect != -1) + { + cg->snap->ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(cg->itemSelect, IT_HOLDABLE); + } + BG_CycleInven(&cg->snap->ps, -1); + + if (cg->snap->ps.stats[STAT_HOLDABLE_ITEM]) + { + cg->itemSelect = bg_itemlist[cg->snap->ps.stats[STAT_HOLDABLE_ITEM]].giTag; + cg->invenSelectTime = cg->time; + } +} diff --git a/codemp/cgame/cg_marks.c b/codemp/cgame/cg_marks.c new file mode 100644 index 0000000..96380a4 --- /dev/null +++ b/codemp/cgame/cg_marks.c @@ -0,0 +1,2279 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_marks.c -- wall marks + +#include "cg_local.h" + +/* +=================================================================== + +MARK POLYS + +=================================================================== +*/ + + +markPoly_t cg_activeMarkPolys; // double linked list +markPoly_t *cg_freeMarkPolys; // single linked list +markPoly_t cg_markPolys[MAX_MARK_POLYS]; +static int markTotal; + +/* +=================== +CG_InitMarkPolys + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitMarkPolys( void ) { + int i; + + memset( cg_markPolys, 0, sizeof(cg_markPolys) ); + + cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; + cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; + cg_freeMarkPolys = cg_markPolys; + for ( i = 0 ; i < MAX_MARK_POLYS - 1 ; i++ ) { + cg_markPolys[i].nextMark = &cg_markPolys[i+1]; + } +} + + +/* +================== +CG_FreeMarkPoly +================== +*/ +void CG_FreeMarkPoly( markPoly_t *le ) { + if ( !le->prevMark ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // remove from the doubly linked active list + le->prevMark->nextMark = le->nextMark; + le->nextMark->prevMark = le->prevMark; + + // the free list is only singly linked + le->nextMark = cg_freeMarkPolys; + cg_freeMarkPolys = le; +} + +/* +=================== +CG_AllocMark + +Will allways succeed, even if it requires freeing an old active mark +=================== +*/ +markPoly_t *CG_AllocMark( void ) { + markPoly_t *le; + int time; + + if ( !cg_freeMarkPolys ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + time = cg_activeMarkPolys.prevMark->time; + while (cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time) { + CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); + } + } + + le = cg_freeMarkPolys; + cg_freeMarkPolys = cg_freeMarkPolys->nextMark; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->nextMark = cg_activeMarkPolys.nextMark; + le->prevMark = &cg_activeMarkPolys; + cg_activeMarkPolys.nextMark->prevMark = le; + cg_activeMarkPolys.nextMark = le; + return le; +} + + + +/* +================= +CG_ImpactMark + +origin should be a point within a unit of the plane +dir should be the plane normal + +temporary marks will not be stored or randomly oriented, but immediately +passed to the renderer. +================= +*/ +#define MAX_MARK_FRAGMENTS 128 +#define MAX_MARK_POINTS 384 + +void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, + float orientation, float red, float green, float blue, float alpha, + qboolean alphaFade, float radius, qboolean temporary ) { + vec3_t axis[3]; + float texCoordScale; + vec3_t originalPoints[4]; + byte colors[4]; + int i, j; + int numFragments; + markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; + vec3_t markPoints[MAX_MARK_POINTS]; + vec3_t projection; + + assert(markShader); + + if ( !cg_addMarks.integer ) { + return; + } + else if (cg_addMarks.integer == 2) + { + trap_R_AddDecalToScene(markShader, origin, dir, orientation, red, green, blue, alpha, + alphaFade, radius, temporary); + return; + } + + if ( radius <= 0 ) { + CG_Error( "CG_ImpactMark called with <= 0 radius" ); + } + + //if ( markTotal >= MAX_MARK_POLYS ) { + // return; + //} + + // create the texture axis + VectorNormalize2( dir, axis[0] ); + PerpendicularVector( axis[1], axis[0] ); + RotatePointAroundVector( axis[2], axis[0], axis[1], orientation ); + CrossProduct( axis[0], axis[2], axis[1] ); + + texCoordScale = 0.5 * 1.0 / radius; + + // create the full polygon + for ( i = 0 ; i < 3 ; i++ ) { + originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; + originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; + originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; + originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; + } + + // get the fragments + VectorScale( dir, -20, projection ); + numFragments = trap_CM_MarkFragments( 4, (const vec3_t *) originalPoints, + projection, MAX_MARK_POINTS, markPoints[0], + MAX_MARK_FRAGMENTS, markFragments ); + + colors[0] = red * 255; + colors[1] = green * 255; + colors[2] = blue * 255; + colors[3] = alpha * 255; + + for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) { + polyVert_t *v; + polyVert_t verts[MAX_VERTS_ON_POLY]; + markPoly_t *mark; + + // we have an upper limit on the complexity of polygons + // that we store persistantly + if ( mf->numPoints > MAX_VERTS_ON_POLY ) { + mf->numPoints = MAX_VERTS_ON_POLY; + } + for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) { + vec3_t delta; + + VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); + + VectorSubtract( v->xyz, origin, delta ); + v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; + v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; + *(int *)v->modulate = *(int *)colors; + } + + // if it is a temporary (shadow) mark, add it immediately and forget about it + if ( temporary ) { + trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); + continue; + } + + // otherwise save it persistantly + mark = CG_AllocMark(); + mark->time = cg->time; + mark->alphaFade = alphaFade; + mark->markShader = markShader; + mark->poly.numVerts = mf->numPoints; + mark->color[0] = red; + mark->color[1] = green; + mark->color[2] = blue; + mark->color[3] = alpha; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + markTotal++; + } +} + + +/* +=============== +CG_AddMarks +=============== +*/ +#define MARK_TOTAL_TIME 10000 +#define MARK_FADE_TIME 1000 + +void CG_AddMarks( void ) { + int j; + markPoly_t *mp, *next; + int t; + int fade; + + if ( !cg_addMarks.integer ) { + return; + } + + mp = cg_activeMarkPolys.nextMark; + for ( ; mp != &cg_activeMarkPolys ; mp = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = mp->nextMark; + + // see if it is time to completely remove it + if ( cg->time > mp->time + MARK_TOTAL_TIME ) { + CG_FreeMarkPoly( mp ); + continue; + } + + // fade out the energy bursts + //if ( mp->markShader == cgs.media.energyMarkShader ) { + if (0) { + + fade = 450 - 450 * ( (cg->time - mp->time ) / 3000.0 ); + if ( fade < 255 ) { + if ( fade < 0 ) { + fade = 0; + } + if ( mp->verts[0].modulate[0] != 0 ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + } + + // fade all marks out with time + t = mp->time + MARK_TOTAL_TIME - cg->time; + if ( t < MARK_FADE_TIME ) { + fade = 255 * t / MARK_FADE_TIME; + if ( mp->alphaFade ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[3] = fade; + } + } + else + { + float f = (float)t / MARK_FADE_TIME; + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * f; + mp->verts[j].modulate[1] = mp->color[1] * f; + mp->verts[j].modulate[2] = mp->color[2] * f; + } + } + } + else + { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0]; + mp->verts[j].modulate[1] = mp->color[1]; + mp->verts[j].modulate[2] = mp->color[2]; + } + } + + trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); + } +} + +// cg_particles.c + +#define BLOODRED 2 +#define EMISIVEFADE 3 +#define GREY75 4 + +typedef struct particle_s +{ + struct particle_s *next; + + float time; + float endtime; + + vec3_t org; + vec3_t vel; + vec3_t accel; + int color; + float colorvel; + float alpha; + float alphavel; + int type; + qhandle_t pshader; + + float height; + float width; + + float endheight; + float endwidth; + + float start; + float end; + + float startfade; + qboolean rotate; + int snum; + + qboolean link; + + // Ridah + int shaderAnim; + int roll; + + int accumroll; + +} cparticle_t; + +typedef enum +{ + P_NONE, + P_WEATHER, + P_FLAT, + P_SMOKE, + P_ROTATE, + P_WEATHER_TURBULENT, + P_ANIM, // Ridah + P_BAT, + P_BLEED, + P_FLAT_SCALEUP, + P_FLAT_SCALEUP_FADE, + P_WEATHER_FLURRY, + P_SMOKE_IMPACT, + P_BUBBLE, + P_BUBBLE_TURBULENT, + P_SPRITE +} particle_type_t; + +#define MAX_SHADER_ANIMS 32 +#define MAX_SHADER_ANIM_FRAMES 64 + +/* +static char *shaderAnimNames[MAX_SHADER_ANIMS] = { + "explode1", + NULL +}; +static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; +static int shaderAnimCounts[MAX_SHADER_ANIMS] = { + 23 +}; +static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { + 1.0f +}; +static int numShaderAnims; +*/ +// done. + +#define PARTICLE_GRAVITY 40 +#define MAX_PARTICLES 1024 + +cparticle_t *active_particles, *free_particles; +cparticle_t particles[MAX_PARTICLES]; +int cl_numparticles = MAX_PARTICLES; + +qboolean initparticles = qfalse; +vec3_t pvforward, pvright, pvup; +vec3_t rforward, rright, rup; + +float oldtime; + +/* +=============== +CL_ClearParticles +=============== +*/ +void CG_ClearParticles (void) +{ + int i; + + memset( particles, 0, sizeof(particles) ); + + free_particles = &particles[0]; + active_particles = NULL; + + for (i=0 ;itime; + + /* + // Ridah, init the shaderAnims + for (i=0; shaderAnimNames[i]; i++) { + int j; + + for (j=0; jtype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY + || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + {// create a front facing polygon + + if (p->type != P_WEATHER_FLURRY) + { + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + if (org[2] > p->end) + { + p->time = cg->time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + p->org[2] = ( p->start + crandom () * 4 ); + + + if (p->type == P_BUBBLE_TURBULENT) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + } + } + else + { + if (org[2] < p->end) + { + p->time = cg->time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + while (p->org[2] < p->end) + { + p->org[2] += (p->start - p->end); + } + + + if (p->type == P_WEATHER_TURBULENT) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + } + } + + + // Rafael snow pvs check + if (!p->link) + return; + + p->alpha = 1; + } + + // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp + if (Distance( cg->snap->ps.origin, org ) > 1024) { + return; + } + // done. + + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, -p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255 * p->alpha; + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy( point, TRIverts[0].xyz ); + TRIverts[0].st[0] = 1; + TRIverts[0].st[1] = 0; + TRIverts[0].modulate[0] = 255; + TRIverts[0].modulate[1] = 255; + TRIverts[0].modulate[2] = 255; + TRIverts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, TRIverts[1].xyz); + TRIverts[1].st[0] = 0; + TRIverts[1].st[1] = 0; + TRIverts[1].modulate[0] = 255; + TRIverts[1].modulate[1] = 255; + TRIverts[1].modulate[2] = 255; + TRIverts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, TRIverts[2].xyz); + TRIverts[2].st[0] = 0; + TRIverts[2].st[1] = 1; + TRIverts[2].modulate[0] = 255; + TRIverts[2].modulate[1] = 255; + TRIverts[2].modulate[2] = 255; + TRIverts[2].modulate[3] = 255 * p->alpha; + } + + } + else if (p->type == P_SPRITE) + { + vec3_t rr, ru; + vec3_t rotate_ang; + + VectorSet (color, 1.0, 1.0, 0.5); + time = cg->time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->roll) { + vectoangles( cg->refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, pvup, point); + VectorMA (point, -width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, pvup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, pvup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) + {// create a front rotating facing polygon + + if ( p->type == P_SMOKE_IMPACT && Distance( cg->snap->ps.origin, org ) > 1024) { + return; + } + + if (p->color == BLOODRED) + VectorSet (color, 0.22f, 0.0f, 0.0f); + else if (p->color == GREY75) + { + float len; + float greyit; + float val; + len = Distance (cg->snap->ps.origin, org); + if (!len) + len = 1; + + val = 4096/len; + greyit = 0.25 * val; + if (greyit > 0.5) + greyit = 0.5; + + VectorSet (color, greyit, greyit, greyit); + } + else + VectorSet (color, 1.0, 1.0, 1.0); + + time = cg->time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + if (cg->time > p->startfade) + { + invratio = 1 - ( (cg->time - p->startfade) / (p->endtime - p->startfade) ); + + if (p->color == EMISIVEFADE) + { + float fval; + fval = (invratio * invratio); + if (fval < 0) + fval = 0; + VectorSet (color, fval , fval , fval ); + } + invratio *= p->alpha; + } + else + invratio = 1 * p->alpha; + + if (invratio > 1) + invratio = 1; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->type != P_SMOKE_IMPACT) + { + vec3_t temp; + + vectoangles (rforward, temp); + p->accumroll += p->roll; + temp[ROLL] += p->accumroll * 0.1; + AngleVectors ( temp, NULL, rright2, rup2); + } + else + { + VectorCopy (rright, rright2); + VectorCopy (rup, rup2); + } + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255 * invratio; + + } + else if (p->type == P_BLEED) + { + vec3_t rr, ru; + vec3_t rotate_ang; + float alpha; + + alpha = p->alpha; + + if (p->roll) + { + vectoangles( cg->refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + else + { + VectorCopy (pvup, ru); + VectorCopy (pvright, rr); + } + + VectorMA (org, -p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 111; + verts[0].modulate[1] = 19; + verts[0].modulate[2] = 9; + verts[0].modulate[3] = 255 * alpha; + + VectorMA (org, -p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 111; + verts[1].modulate[1] = 19; + verts[1].modulate[2] = 9; + verts[1].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 111; + verts[2].modulate[1] = 19; + verts[2].modulate[2] = 9; + verts[2].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 111; + verts[3].modulate[1] = 19; + verts[3].modulate[2] = 9; + verts[3].modulate[3] = 255 * alpha; + + } + else if (p->type == P_FLAT_SCALEUP) + { + float width, height; + float sinR, cosR; + + if (p->color == BLOODRED) + VectorSet (color, 1, 1, 1); + else + VectorSet (color, 0.5, 0.5, 0.5); + + time = cg->time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (width > p->endwidth) + width = p->endwidth; + + if (height > p->endheight) + height = p->endheight; + + sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2.0f); + cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2.0f); + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= sinR; + verts[0].xyz[1] -= cosR; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= cosR; + verts[1].xyz[1] += sinR; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += sinR; + verts[2].xyz[1] += cosR; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += cosR; + verts[3].xyz[1] -= sinR; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255; + } + else if (p->type == P_FLAT) + { + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= p->height; + verts[0].xyz[1] -= p->width; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= p->height; + verts[1].xyz[1] += p->width; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += p->height; + verts[2].xyz[1] += p->width; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += p->height; + verts[3].xyz[1] -= p->width; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + } + // Ridah + else if (p->type == P_ANIM) { + p->pshader = 0; +/* + vec3_t rr, ru; + vec3_t rotate_ang; + int i, j; + + time = cg->time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + if (ratio >= 1.0f) { + ratio = 0.9999f; + } + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + // if we are "inside" this sprite, don't draw + if (Distance( cg->snap->ps.origin, org ) < width/1.5) { + return; + } + + i = p->shaderAnim; + j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); + p->pshader = shaderAnims[i][j]; + + if (p->roll) { + vectoangles( cg->refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, pvup, point); + VectorMA (point, -width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, pvup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, pvup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; +*/ + } + // done. + + if (!p->pshader) { +// (SA) temp commented out for DM +// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); + return; + } + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) + trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); + else + trap_R_AddPolyToScene( p->pshader, 4, verts ); + +} + +// Ridah, made this static so it doesn't interfere with other files +static float roll = 0.0; + +/* +=============== +CG_AddParticles +=============== +*/ +void CG_AddParticles (void) +{ + cparticle_t *p, *next; + float alpha; + float time, time2; + vec3_t org; + int color; + cparticle_t *active, *tail; + int type; + vec3_t rotate_ang; + + if (!initparticles) + CG_ClearParticles (); + + VectorCopy( cg->refdef.viewaxis[0], pvforward ); + VectorCopy( cg->refdef.viewaxis[1], pvright ); + VectorCopy( cg->refdef.viewaxis[2], pvup ); + + vectoangles( cg->refdef.viewaxis[0], rotate_ang ); + roll += ((cg->time - oldtime) * 0.1) ; + rotate_ang[ROLL] += (roll*0.9); + AngleVectors ( rotate_ang, rforward, rright, rup); + + oldtime = cg->time; + + active = NULL; + tail = NULL; + + for (p=active_particles ; p ; p=next) + { + + next = p->next; + + time = (cg->time - p->time)*0.001; + + alpha = p->alpha + time*p->alphavel; + if (alpha <= 0) + { // faded out + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) + { + if (cg->time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + + } + + if (p->type == P_WEATHER_FLURRY) + { + if (cg->time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + } + + + if (p->type == P_FLAT_SCALEUP_FADE) + { + if (cg->time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + } + + if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { + // temporary sprite + CG_AddParticleToScene (p, p->org, alpha); + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + p->next = NULL; + if (!tail) + active = tail = p; + else + { + tail->next = p; + tail = p; + } + + if (alpha > 1.0) + alpha = 1; + + color = p->color; + + time2 = time*time; + + org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; + org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; + org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; + + type = p->type; + + CG_AddParticleToScene (p, org, alpha); + } + + active_particles = active; +} + +/* +====================== +CG_AddParticles +====================== +*/ +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + qboolean turb = qtrue; + + if (!pshader) + CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + p->color = 0; + p->alpha = 0.90f; + p->alphavel = 0; + + p->start = cent->currentState.origin2[0]; + p->end = cent->currentState.origin2[1]; + + p->endtime = cg->time + cent->currentState.time; + p->startfade = cg->time + cent->currentState.time2; + + p->pshader = pshader; + + if (rand()%100 > 90) + { + p->height = 32; + p->width = 32; + p->alpha = 0.10f; + } + else + { + p->height = 1; + p->width = 1; + } + + p->vel[2] = -20; + + p->type = P_WEATHER_FLURRY; + + if (turb) + p->vel[2] = -10; + + VectorCopy(cent->currentState.origin, p->org); + + p->org[0] = p->org[0]; + p->org[1] = p->org[1]; + p->org[2] = p->org[2]; + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); + p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); + p->vel[2] += cent->currentState.angles[2]; + + if (turb) + { + p->accel[0] = crandom () * 16; + p->accel[1] = crandom () * 16; + } + +} + +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + p->height = 1; + p->width = 1; + + p->vel[2] = -50; + + if (turb) + { + p->type = P_WEATHER_TURBULENT; + p->vel[2] = -50 * 1.3; + } + else + { + p->type = P_WEATHER; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + float randsize; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + + randsize = 1 + (crandom() * 0.5); + + p->height = randsize; + p->width = randsize; + + p->vel[2] = 50 + ( crandom() * 10 ); + + if (turb) + { + p->type = P_BUBBLE_TURBULENT; + p->vel[2] = 50 * 1.3; + } + else + { + p->type = P_BUBBLE; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) +{ + + // using cent->density = enttime + // cent->frame = startfade + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSmoke == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + + p->endtime = cg->time + cent->currentState.time; + p->startfade = cg->time + cent->currentState.time2; + + p->color = 0; + p->alpha = 1.0; + p->alphavel = 0; + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; + p->pshader = pshader; + p->rotate = qfalse; + p->height = 8; + p->width = 8; + p->endheight = 32; + p->endwidth = 32; + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org); + + p->vel[0] = p->vel[1] = 0; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[2] = 5; + + if (cent->currentState.frame == 1)// reverse gravity + p->vel[2] *= -1; + + p->roll = 8 + (crandom() * 4); +} + + +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) +{ + + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + + p->endtime = cg->time + duration; + p->startfade = cg->time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 1.0; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = 0;//cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->accel[2] = -60; + p->vel[2] += -20; + +} + +/* +====================== +CG_ParticleExplosion +====================== +*/ + +/* +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) +{ + cparticle_t *p; + int anim; + + if (animStr < (char *)10) + CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); + + // find the animation string + for (anim=0; shaderAnimNames[anim]; anim++) { + if (!Q_stricmp( animStr, shaderAnimNames[anim] )) + break; + } + if (!shaderAnimNames[anim]) { + CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); + return; + } + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + p->alpha = 0.5; + p->alphavel = 0; + + if (duration < 0) { + duration *= -1; + p->roll = 0; + } else { + p->roll = crandom()*179; + } + + p->shaderAnim = anim; + + p->width = sizeStart; + p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction + + p->endheight = sizeEnd; + p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; + + p->endtime = cg->time + duration; + + p->type = P_ANIM; + + VectorCopy( origin, p->org ); + VectorCopy( vel, p->vel ); + VectorClear( p->accel ); + +} +*/ + +// Rafael Shrapnel +void CG_AddParticleShrapnel (localEntity_t *le) +{ + return; +} +// done. + +int CG_NewParticleArea (int num) +{ + // const char *str; + char *str; + char *token; + int type; + vec3_t origin, origin2; + int i; + float range = 0; + int turb; + int numparticles; + int snum; + + str = (char *) CG_ConfigString (num); + if (!str[0]) + return (0); + + // returns type 128 64 or 32 + token = COM_Parse ((const char **)&str); + type = atoi (token); + + if (type == 1) + range = 128; + else if (type == 2) + range = 64; + else if (type == 3) + range = 32; + else if (type == 0) + range = 256; + else if (type == 4) + range = 8; + else if (type == 5) + range = 16; + else if (type == 6) + range = 32; + else if (type == 7) + range = 64; + + + for (i=0; i<3; i++) + { + token = COM_Parse ((const char **)&str); + origin[i] = atof (token); + } + + for (i=0; i<3; i++) + { + token = COM_Parse ((const char **)&str); + origin2[i] = atof (token); + } + + token = COM_Parse ((const char **)&str); + numparticles = atoi (token); + + token = COM_Parse ((const char **)&str); + turb = atoi (token); + + token = COM_Parse ((const char **)&str); + snum = atoi (token); + + /* + for (i=0; i= 4) + CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + else + CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + } + */ + + return (1); +} + +void CG_SnowLink (centity_t *cent, qboolean particleOn) +{ + cparticle_t *p, *next; + int id; + + id = cent->currentState.frame; + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) + { + if (p->snum == id) + { + if (particleOn) + p->link = qtrue; + else + p->link = qfalse; + } + } + + } +} + +void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + p->alpha = 0.25; + p->alphavel = 0; + p->roll = crandom()*179; + + p->pshader = pshader; + + p->endtime = cg->time + 1000; + p->startfade = cg->time + 100; + + p->width = rand()%4 + 8; + p->height = rand()%4 + 8; + + p->endheight = p->height *2; + p->endwidth = p->width * 2; + + p->endtime = cg->time + 500; + + p->type = P_SMOKE_IMPACT; + + VectorCopy( origin, p->org ); + VectorSet(p->vel, 0, 0, 20); + VectorSet(p->accel, 0, 0, 20); + + p->rotate = qtrue; +} + +void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg->time + duration; + + if (fleshEntityNum) + p->startfade = cg->time; + else + p->startfade = cg->time + 100; + + p->width = 4; + p->height = 4; + + p->endheight = 4+rand()%3; + p->endwidth = p->endheight; + + p->type = P_SMOKE; + + VectorCopy( start, p->org ); + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -20; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + p->alpha = 0.75; + +} + +void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + int time; + int time2; + float ratio; + + float duration = 1500; + + time = cg->time; + time2 = cg->time + cent->currentState.time; + + ratio =(float)1 - ((float)time / (float)time2); + + if (!pshader) + CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg->time + duration; + + p->startfade = p->endtime; + + p->width = 1; + p->height = 3; + + p->endheight = 3; + p->endwidth = 1; + + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org ); + + p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); + p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); + p->vel[2] = (cent->currentState.origin2[2]); + + p->snum = 1.0f; + + VectorClear( p->accel ); + + p->accel[2] = -20; + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + + +void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + + if (cent->currentState.angles2[2]) + p->endtime = cg->time + cent->currentState.angles2[2]; + else + p->endtime = cg->time + 60000; + + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) + { + p->width = cent->currentState.angles2[0]; + p->height = cent->currentState.angles2[0]; + + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + } + else + { + p->width = 8; + p->height = 8; + + p->endheight = 16; + p->endwidth = 16; + } + + p->type = P_FLAT_SCALEUP; + + p->snum = 1.0; + + VectorCopy(cent->currentState.origin, p->org ); + + p->org[2]+= 0.55 + (crandom() * 0.5); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + +void CG_OilSlickRemove (centity_t *cent) +{ + cparticle_t *p, *next; + int id; + + id = 1.0f; + + if (!id) + CG_Printf ("CG_OilSlickRevove NULL id\n"); + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_FLAT_SCALEUP) + { + if (p->snum == id) + { + p->endtime = cg->time + 100; + p->startfade = p->endtime; + p->type = P_FLAT_SCALEUP_FADE; + + } + } + + } +} + +qboolean ValidBloodPool (vec3_t start) +{ +#define EXTRUDE_DIST 0.5 + + vec3_t angles; + vec3_t right, up; + vec3_t this_pos, x_pos, center_pos, end_pos; + float x, y; + float fwidth, fheight; + trace_t trace; + vec3_t normal; + + fwidth = 16; + fheight = 16; + + VectorSet (normal, 0, 0, 1); + + vectoangles (normal, angles); + AngleVectors (angles, NULL, right, up); + + VectorMA (start, EXTRUDE_DIST, normal, center_pos); + + for (x= -fwidth/2; xendpos, start); + legit = ValidBloodPool (start); + + if (!legit) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + + p->endtime = cg->time + 3000; + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + rndSize = 0.4 + random()*0.6; + + p->width = 8*rndSize; + p->height = 8*rndSize; + + p->endheight = 16*rndSize; + p->endwidth = 16*rndSize; + + p->type = P_FLAT_SCALEUP; + + VectorCopy(start, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + p->color = BLOODRED; +} + +#define NORMALSIZE 16 +#define LARGESIZE 32 + +void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg->time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = 0;//cgs.media.smokePuffShader; + + p->endtime = cg->time + 350 + (crandom() * 100); + + p->startfade = cg->time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + p->endheight = LARGESIZE; + p->endwidth = LARGESIZE; + + p->type = P_SMOKE; + + VectorCopy( origin, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -1; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) +{ + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + + p->endtime = cg->time + duration; + p->startfade = cg->time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 0.4f; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = 0;//cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->org[0] += (crandom() * x); + p->org[1] += (crandom() * y); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += (crandom() * 4); + p->vel[1] += (crandom() * 4); + p->vel[2] += (20 + (crandom() * 10)) * speed; + + p->accel[0] = crandom () * 4; + p->accel[1] = crandom () * 4; + +} + +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + VectorNegate (dir, dir); + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg->time; + p->alpha = 5.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = 0;//cgs.media.smokePuffShader; + + // RF, stay around for long enough to expand and dissipate naturally + if (length) + p->endtime = cg->time + 4500 + (crandom() * 3500); + else + p->endtime = cg->time + 750 + (crandom() * 500); + + p->startfade = cg->time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + + // RF, expand while falling + p->endheight = LARGESIZE*3.0; + p->endwidth = LARGESIZE*3.0; + + if (!length) + { + p->width *= 0.2f; + p->height *= 0.2f; + + p->endheight = NORMALSIZE; + p->endwidth = NORMALSIZE; + } + + p->type = P_SMOKE; + + VectorCopy( point, p->org ); + + p->vel[0] = crandom()*6; + p->vel[1] = crandom()*6; + p->vel[2] = random()*20; + + // RF, add some gravity/randomness + p->accel[0] = crandom()*3; + p->accel[1] = crandom()*3; + p->accel[2] = -PARTICLE_GRAVITY*0.4; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg->time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = rand()%179; + + p->pshader = pshader; + + if (duration > 0) + p->endtime = cg->time + duration; + else + p->endtime = duration; + + p->startfade = cg->time; + + p->width = size; + p->height = size; + + p->endheight = size; + p->endwidth = size; + + p->type = P_SPRITE; + + VectorCopy( origin, p->org ); + + p->rotate = qfalse; +} + diff --git a/codemp/cgame/cg_media.h b/codemp/cgame/cg_media.h new file mode 100644 index 0000000..e69de29 diff --git a/codemp/cgame/cg_newDraw.c b/codemp/cgame/cg_newDraw.c new file mode 100644 index 0000000..886ae3d --- /dev/null +++ b/codemp/cgame/cg_newDraw.c @@ -0,0 +1,893 @@ +#include "cg_local.h" +#include "../ui/ui_shared.h" + +extern displayContextDef_t cgDC; + + +int CG_GetSelectedPlayer() { + if (cg_currentSelectedPlayer.integer < 0 || cg_currentSelectedPlayer.integer >= numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer = 0; + } + return cg_currentSelectedPlayer.integer; +} + +qhandle_t CG_StatusHandle(int task) { + qhandle_t h = cgs.media.assaultShader; + switch (task) { + case TEAMTASK_OFFENSE : + h = cgs.media.assaultShader; + break; + case TEAMTASK_DEFENSE : + h = cgs.media.defendShader; + break; + case TEAMTASK_PATROL : + h = cgs.media.patrolShader; + break; + case TEAMTASK_FOLLOW : + h = cgs.media.followShader; + break; + case TEAMTASK_CAMP : + h = cgs.media.campShader; + break; + case TEAMTASK_RETRIEVE : + h = cgs.media.retrieveShader; + break; + case TEAMTASK_ESCORT : + h = cgs.media.escortShader; + break; + default : + h = cgs.media.assaultShader; + break; + } + return h; +} + + +float CG_GetValue(int ownerDraw) { + centity_t *cent; + clientInfo_t *ci; + playerState_t *ps; + + cent = &cg_entities[cg->snap->ps.clientNum]; + ps = &cg->snap->ps; + + switch (ownerDraw) { + case CG_SELECTEDPLAYER_ARMOR: + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + return ci->armor; + break; + case CG_SELECTEDPLAYER_HEALTH: + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + return ci->health; + break; + case CG_PLAYER_ARMOR_VALUE: + return ps->stats[STAT_ARMOR]; + break; + case CG_PLAYER_AMMO_VALUE: + if ( cent->currentState.weapon ) + { + return ps->ammo[weaponData[cent->currentState.weapon].ammoIndex]; + } + break; + case CG_PLAYER_SCORE: + return cg->snap->ps.persistant[PERS_SCORE]; + break; + case CG_PLAYER_HEALTH: + return ps->stats[STAT_HEALTH]; + break; + case CG_RED_SCORE: + return cgs.scores1; + break; + case CG_BLUE_SCORE: + return cgs.scores2; + break; + case CG_PLAYER_FORCE_VALUE: + return ps->fd.forcePower; + break; + default: + break; + } + return -1; +} + +qboolean CG_OtherTeamHasFlag(void) { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_CTY) { + int team = cg->snap->ps.persistant[PERS_TEAM]; + if (team == TEAM_RED && cgs.redflag == FLAG_TAKEN) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.blueflag == FLAG_TAKEN) { + return qtrue; + } else { + return qfalse; + } + } + return qfalse; +} + +qboolean CG_YourTeamHasFlag(void) { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_CTY) { + int team = cg->snap->ps.persistant[PERS_TEAM]; + if (team == TEAM_RED && cgs.blueflag == FLAG_TAKEN) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.redflag == FLAG_TAKEN) { + return qtrue; + } else { + return qfalse; + } + } + return qfalse; +} + +// THINKABOUTME: should these be exclusive or inclusive.. +// +qboolean CG_OwnerDrawVisible(int flags) { + + if (flags & CG_SHOW_TEAMINFO) { + return (cg_currentSelectedPlayer.integer == numSortedTeamPlayers); + } + + if (flags & CG_SHOW_NOTEAMINFO) { + return !(cg_currentSelectedPlayer.integer == numSortedTeamPlayers); + } + + if (flags & CG_SHOW_OTHERTEAMHASFLAG) { + return CG_OtherTeamHasFlag(); + } + + if (flags & CG_SHOW_YOURTEAMHASENEMYFLAG) { + return CG_YourTeamHasFlag(); + } + + if (flags & (CG_SHOW_BLUE_TEAM_HAS_REDFLAG | CG_SHOW_RED_TEAM_HAS_BLUEFLAG)) { + if (flags & CG_SHOW_BLUE_TEAM_HAS_REDFLAG && (cgs.redflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_RED)) { + return qtrue; + } else if (flags & CG_SHOW_RED_TEAM_HAS_BLUEFLAG && (cgs.blueflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_BLUE)) { + return qtrue; + } + return qfalse; + } + + if (flags & CG_SHOW_ANYTEAMGAME) { + if( cgs.gametype >= GT_TEAM) { + return qtrue; + } + } + + if (flags & CG_SHOW_ANYNONTEAMGAME) { + if( cgs.gametype < GT_TEAM) { + return qtrue; + } + } + + if (flags & CG_SHOW_CTF) { + if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { + return qtrue; + } + } + + if (flags & CG_SHOW_HEALTHCRITICAL) { + if (cg->snap->ps.stats[STAT_HEALTH] < 25) { + return qtrue; + } + } + + if (flags & CG_SHOW_HEALTHOK) { + if (cg->snap->ps.stats[STAT_HEALTH] >= 25) { + return qtrue; + } + } + + if (flags & CG_SHOW_SINGLEPLAYER) { + if( cgs.gametype == GT_SINGLE_PLAYER ) { + return qtrue; + } + } + + if (flags & CG_SHOW_TOURNAMENT) { + if( cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL ) { + return qtrue; + } + } + + if (flags & CG_SHOW_DURINGINCOMINGVOICE) { + } + + if (flags & CG_SHOW_IF_PLAYER_HAS_FLAG) { + if (cg->snap->ps.powerups[PW_REDFLAG] || cg->snap->ps.powerups[PW_BLUEFLAG] || cg->snap->ps.powerups[PW_NEUTRALFLAG]) { + return qtrue; + } + } + return qfalse; +} + + +const char *CG_GetKillerText(void) { + static const char *s = ""; + if ( cg->killerName[0] ) { + s = va("%s %s", CG_GetStringEdString("MP_INGAME", "KILLEDBY"), cg->killerName ); + } + return s; +} + + +const char *CG_GetGameStatusText(void) { + static const char *s = ""; + if (cgs.gametype == GT_POWERDUEL) + { + s = ""; + } + else if ( cgs.gametype < GT_TEAM) + { + if (cg->snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) + { + char sPlaceWith[256]; + trap_SP_GetStringTextString("MP_INGAME_PLACE_WITH", sPlaceWith, sizeof(sPlaceWith)); + + s = va("%s %s %i",CG_PlaceString( cg->snap->ps.persistant[PERS_RANK] + 1 ), sPlaceWith, cg->snap->ps.persistant[PERS_SCORE] ); + } + } + else + { + if ( cg->teamScores[0] == cg->teamScores[1] ) { + s = va("%s %i", CG_GetStringEdString("MP_INGAME", "TIEDAT"), cg->teamScores[0] ); + } else if ( cg->teamScores[0] >= cg->teamScores[1] ) { + s = va("%s, %i / %i", CG_GetStringEdString("MP_INGAME", "RED_LEADS"), cg->teamScores[0], cg->teamScores[1] ); + } else { + s = va("%s, %i / %i", CG_GetStringEdString("MP_INGAME", "BLUE_LEADS"), cg->teamScores[1], cg->teamScores[0] ); + } + } + return s; +} + +const char *CG_GameTypeString(void) { + if ( cgs.gametype == GT_FFA ) { + return "Free For All"; + } else if ( cgs.gametype == GT_HOLOCRON ) { + return "Holocron FFA"; + } else if ( cgs.gametype == GT_JEDIMASTER ) { + return "Jedi Master"; + } else if ( cgs.gametype == GT_TEAM ) { + return "Team FFA"; + } else if ( cgs.gametype == GT_SIEGE ) { + return "Siege"; + } else if ( cgs.gametype == GT_CTF ) { + return "Capture the Flag"; + } else if ( cgs.gametype == GT_CTY ) { + return "Capture the Ysalamiri"; + } + return ""; +} + +#include "../namespace_begin.h" +extern int MenuFontToHandle(int iMenuFont); +#include "../namespace_end.h" + +// maxX param is initially an X limit, but is also used as feedback. 0 = text was clipped to fit within, else maxX = next pos +// +static void CG_Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit, int iMenuFont) +{ + qboolean bIsTrailingPunctuation; + + // this is kinda dirty, but... + // + int iFontIndex = MenuFontToHandle(iMenuFont); + + //float fMax = *maxX; + int iPixelLen = trap_R_Font_StrLenPixels(text, iFontIndex, scale); + if (x + iPixelLen > *maxX) + { + // whole text won't fit, so we need to print just the amount that does... + // Ok, this is slow and tacky, but only called occasionally, and it works... + // + char sTemp[4096]={0}; // lazy assumption + const char *psText = text; + char *psOut = &sTemp[0]; + char *psOutLastGood = psOut; + unsigned int uiLetter; + + while (*psText && (x + trap_R_Font_StrLenPixels(sTemp, iFontIndex, scale)<=*maxX) + && psOut < &sTemp[sizeof(sTemp)-1] // sanity + ) + { + int iAdvanceCount; + psOutLastGood = psOut; + + uiLetter = trap_AnyLanguage_ReadCharFromString(psText, &iAdvanceCount, &bIsTrailingPunctuation); + psText += iAdvanceCount; + + if (uiLetter > 255) + { + *psOut++ = uiLetter>>8; + *psOut++ = uiLetter&0xFF; + } + else + { + *psOut++ = uiLetter&0xFF; + } + } + *psOutLastGood = '\0'; + + *maxX = 0; // feedback + CG_Text_Paint(x, y, scale, color, sTemp, adjust, limit, ITEM_TEXTSTYLE_NORMAL, iMenuFont); + } + else + { + // whole text fits fine, so print it all... + // + *maxX = x + iPixelLen; // feedback the next position, as the caller expects + CG_Text_Paint(x, y, scale, color, text, adjust, limit, ITEM_TEXTSTYLE_NORMAL, iMenuFont); + } +} + + + +#define PIC_WIDTH 12 + +extern const char *CG_GetLocationString(const char *loc); //cg_main.c +void CG_DrawNewTeamInfo(rectDef_t *rect, float text_x, float text_y, float scale, vec4_t color, qhandle_t shader) { + int xx; + float y; + int i, j, len, count; + const char *p; + vec4_t hcolor; + float pwidth, lwidth, maxx, leftOver; + clientInfo_t *ci; + gitem_t *item; + qhandle_t h; + + // max player name width + pwidth = 0; + count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg->snap->ps.persistant[PERS_TEAM]) { + len = CG_Text_Width( ci->name, scale, 0); + if (len > pwidth) + pwidth = len; + } + } + + // max location name width + lwidth = 0; + for (i = 1; i < MAX_LOCATIONS; i++) { + p = CG_GetLocationString(CG_ConfigString(CS_LOCATIONS+i)); + if (p && *p) { + len = CG_Text_Width(p, scale, 0); + if (len > lwidth) + lwidth = len; + } + } + + y = rect->y; + + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg->snap->ps.persistant[PERS_TEAM]) { + + xx = rect->x + 1; + for (j = 0; j <= PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + + item = BG_FindItemForPowerup( j ); + + if (item) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, trap_R_RegisterShader( item->icon ) ); + xx += PIC_WIDTH; + } + } + } + + // FIXME: max of 3 powerups shown properly + xx = rect->x + (PIC_WIDTH * 3) + 2; + + CG_GetColorForHealth( ci->health, ci->armor, hcolor ); + trap_R_SetColor(hcolor); + CG_DrawPic( xx, y + 1, PIC_WIDTH - 2, PIC_WIDTH - 2, cgs.media.heartShader ); + + //Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); + //CG_Text_Paint(xx, y + text_y, scale, hcolor, st, 0, 0); + + // draw weapon icon + xx += PIC_WIDTH + 1; + +// weapon used is not that useful, use the space for task +#if 0 + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cgs.media.deferShader ); + } +#endif + + trap_R_SetColor(NULL); + h = CG_StatusHandle(ci->teamTask); + + if (h) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, h); + } + + xx += PIC_WIDTH + 1; + + leftOver = rect->w - xx; + maxx = xx + leftOver / 3; + + + + CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, ci->name, 0, 0, FONT_MEDIUM); + + p = CG_GetLocationString(CG_ConfigString(CS_LOCATIONS+ci->location)); + if (!p || !*p) { + p = "unknown"; + } + + xx += leftOver / 3 + 2; + maxx = rect->w - 4; + + CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, p, 0, 0, FONT_MEDIUM); + y += text_y + 2; + if ( y + text_y + 2 > rect->y + rect->h ) { + break; + } + + } + } +} + + +void CG_DrawTeamSpectators(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + if (cg->spectatorLen) { + float maxX; + + if (cg->spectatorWidth == -1) { + cg->spectatorWidth = 0; + cg->spectatorPaintX = rect->x + 1; + cg->spectatorPaintX2 = -1; + } + + if (cg->spectatorOffset > cg->spectatorLen) { + cg->spectatorOffset = 0; + cg->spectatorPaintX = rect->x + 1; + cg->spectatorPaintX2 = -1; + } + + if (cg->time > cg->spectatorTime) { + cg->spectatorTime = cg->time + 10; + if (cg->spectatorPaintX <= rect->x + 2) { + if (cg->spectatorOffset < cg->spectatorLen) { + cg->spectatorPaintX += CG_Text_Width(&cg->spectatorList[cg->spectatorOffset], scale, 1) - 1; + cg->spectatorOffset++; + } else { + cg->spectatorOffset = 0; + if (cg->spectatorPaintX2 >= 0) { + cg->spectatorPaintX = cg->spectatorPaintX2; + } else { + cg->spectatorPaintX = rect->x + rect->w - 2; + } + cg->spectatorPaintX2 = -1; + } + } else { + cg->spectatorPaintX--; + if (cg->spectatorPaintX2 >= 0) { + cg->spectatorPaintX2--; + } + } + } + + maxX = rect->x + rect->w - 2; + CG_Text_Paint_Limit(&maxX, cg->spectatorPaintX, rect->y + rect->h - 3, scale, color, &cg->spectatorList[cg->spectatorOffset], 0, 0, FONT_MEDIUM); + if (cg->spectatorPaintX2 >= 0) { + float maxX2 = rect->x + rect->w - 2; + CG_Text_Paint_Limit(&maxX2, cg->spectatorPaintX2, rect->y + rect->h - 3, scale, color, cg->spectatorList, 0, cg->spectatorOffset, FONT_MEDIUM); + } + if (cg->spectatorOffset && maxX > 0) { + // if we have an offset ( we are skipping the first part of the string ) and we fit the string + if (cg->spectatorPaintX2 == -1) { + cg->spectatorPaintX2 = rect->x + rect->w - 2; + } + } else { + cg->spectatorPaintX2 = -1; + } + + } +} + + + +void CG_DrawMedal(int ownerDraw, rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + score_t *score = &cg->scores[cg->selectedScore]; + float value = 0; + char *text = NULL; + color[3] = 0.25; + + switch (ownerDraw) { + case CG_ACCURACY: + value = score->accuracy; + break; + case CG_ASSISTS: + value = score->assistCount; + break; + case CG_DEFEND: + value = score->defendCount; + break; + case CG_EXCELLENT: + value = score->excellentCount; + break; + case CG_IMPRESSIVE: + value = score->impressiveCount; + break; + case CG_PERFECT: + value = score->perfect; + break; + case CG_GAUNTLET: + value = score->guantletCount; + break; + case CG_CAPTURES: + value = score->captures; + break; + } + + if (value > 0) { + if (ownerDraw != CG_PERFECT) { + if (ownerDraw == CG_ACCURACY) { + text = va("%i%%", (int)value); + if (value > 50) { + color[3] = 1.0; + } + } else { + text = va("%i", (int)value); + color[3] = 1.0; + } + } else { + if (value) { + color[3] = 1.0; + } + text = "Wow"; + } + } + + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + + if (text) { + color[3] = 1.0; + value = CG_Text_Width(text, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h + 10 , scale, color, text, 0, 0, 0, FONT_MEDIUM); + } + trap_R_SetColor(NULL); + +} + + +// +void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle,int font) { + +//Ignore all this, at least for now. May put some stat stuff back in menu files later. +#if 0 + rectDef_t rect; + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + //if (ownerDrawFlags != 0 && !CG_OwnerDrawVisible(ownerDrawFlags)) { + // return; + //} + + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + switch (ownerDraw) { + case CG_PLAYER_ARMOR_ICON: + CG_DrawPlayerArmorIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_ARMOR_ICON2D: + CG_DrawPlayerArmorIcon(&rect, qtrue); + break; + case CG_PLAYER_ARMOR_VALUE: + CG_DrawPlayerArmorValue(&rect, scale, color, shader, textStyle); + break; + case CG_PLAYER_FORCE_VALUE: + CG_DrawPlayerForceValue(&rect, scale, color, shader, textStyle); + return ; + case CG_PLAYER_AMMO_ICON: + CG_DrawPlayerAmmoIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_AMMO_ICON2D: + CG_DrawPlayerAmmoIcon(&rect, qtrue); + break; + case CG_PLAYER_AMMO_VALUE: + CG_DrawPlayerAmmoValue(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_HEAD: + CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qfalse); + break; + case CG_VOICE_HEAD: + CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qtrue); + break; + case CG_VOICE_NAME: + CG_DrawSelectedPlayerName(&rect, scale, color, qtrue, textStyle); + break; + case CG_SELECTEDPLAYER_STATUS: + CG_DrawSelectedPlayerStatus(&rect); + break; + case CG_SELECTEDPLAYER_ARMOR: + CG_DrawSelectedPlayerArmor(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_HEALTH: + CG_DrawSelectedPlayerHealth(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_NAME: + CG_DrawSelectedPlayerName(&rect, scale, color, qfalse, textStyle); + break; + case CG_SELECTEDPLAYER_LOCATION: + CG_DrawSelectedPlayerLocation(&rect, scale, color, textStyle); + break; + case CG_SELECTEDPLAYER_WEAPON: + CG_DrawSelectedPlayerWeapon(&rect); + break; + case CG_SELECTEDPLAYER_POWERUP: + CG_DrawSelectedPlayerPowerup(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_HEAD: + CG_DrawPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_ITEM: + CG_DrawPlayerItem(&rect, scale, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_SCORE: + CG_DrawPlayerScore(&rect, scale, color, shader, textStyle); + break; + case CG_PLAYER_HEALTH: + CG_DrawPlayerHealth(&rect, scale, color, shader, textStyle); + break; + case CG_RED_SCORE: + CG_DrawRedScore(&rect, scale, color, shader, textStyle); + break; + case CG_BLUE_SCORE: + CG_DrawBlueScore(&rect, scale, color, shader, textStyle); + break; + case CG_RED_NAME: + CG_DrawRedName(&rect, scale, color, textStyle); + break; + case CG_BLUE_NAME: + CG_DrawBlueName(&rect, scale, color, textStyle); + break; + case CG_BLUE_FLAGHEAD: + CG_DrawBlueFlagHead(&rect); + break; + case CG_BLUE_FLAGSTATUS: + CG_DrawBlueFlagStatus(&rect, shader); + break; + case CG_BLUE_FLAGNAME: + CG_DrawBlueFlagName(&rect, scale, color, textStyle); + break; + case CG_RED_FLAGHEAD: + CG_DrawRedFlagHead(&rect); + break; + case CG_RED_FLAGSTATUS: + CG_DrawRedFlagStatus(&rect, shader); + break; + case CG_RED_FLAGNAME: + CG_DrawRedFlagName(&rect, scale, color, textStyle); + break; + case CG_PLAYER_LOCATION: + CG_DrawPlayerLocation(&rect, scale, color, textStyle); + break; + case CG_TEAM_COLOR: + CG_DrawTeamColor(&rect, color); + break; + case CG_CTF_POWERUP: + CG_DrawCTFPowerUp(&rect); + break; + case CG_AREA_POWERUP: + CG_DrawAreaPowerUp(&rect, align, special, scale, color); + break; + case CG_PLAYER_STATUS: + CG_DrawPlayerStatus(&rect); + break; + case CG_PLAYER_HASFLAG: + CG_DrawPlayerHasFlag(&rect, qfalse); + break; + case CG_PLAYER_HASFLAG2D: + CG_DrawPlayerHasFlag(&rect, qtrue); + break; + case CG_AREA_SYSTEMCHAT: + CG_DrawAreaSystemChat(&rect, scale, color, shader); + break; + case CG_AREA_TEAMCHAT: + CG_DrawAreaTeamChat(&rect, scale, color, shader); + break; + case CG_AREA_CHAT: + CG_DrawAreaChat(&rect, scale, color, shader); + break; + case CG_GAME_TYPE: + CG_DrawGameType(&rect, scale, color, shader, textStyle); + break; + case CG_GAME_STATUS: + CG_DrawGameStatus(&rect, scale, color, shader, textStyle); + break; + case CG_KILLER: + CG_DrawKiller(&rect, scale, color, shader, textStyle); + break; + case CG_ACCURACY: + case CG_ASSISTS: + case CG_DEFEND: + case CG_EXCELLENT: + case CG_IMPRESSIVE: + case CG_PERFECT: + case CG_GAUNTLET: + case CG_CAPTURES: + CG_DrawMedal(ownerDraw, &rect, scale, color, shader); + break; + case CG_SPECTATORS: + CG_DrawTeamSpectators(&rect, scale, color, shader); + break; + case CG_TEAMINFO: + if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { + CG_DrawNewTeamInfo(&rect, text_x, text_y, scale, color, shader); + } + break; + case CG_CAPFRAGLIMIT: + CG_DrawCapFragLimit(&rect, scale, color, shader, textStyle); + break; + case CG_1STPLACE: + CG_Draw1stPlace(&rect, scale, color, shader, textStyle); + break; + case CG_2NDPLACE: + CG_Draw2ndPlace(&rect, scale, color, shader, textStyle); + break; + default: + break; + } +#endif +} + +void CG_MouseEvent(int x, int y) { + int n; + + if ( (cg->predictedPlayerState.pm_type == PM_NORMAL || cg->predictedPlayerState.pm_type == PM_JETPACK || cg->predictedPlayerState.pm_type == PM_FLOAT || cg->predictedPlayerState.pm_type == PM_SPECTATOR) && cg->showScores == qfalse) { + trap_Key_SetCatcher(0); + return; + } + + cgs.cursorX+= x; + if (cgs.cursorX < 0) + cgs.cursorX = 0; + else if (cgs.cursorX > 640) + cgs.cursorX = 640; + + cgs.cursorY += y; + if (cgs.cursorY < 0) + cgs.cursorY = 0; + else if (cgs.cursorY > 480) + cgs.cursorY = 480; + + n = Display_CursorType(cgs.cursorX, cgs.cursorY); + cgs.activeCursor = 0; + if (n == CURSOR_ARROW) { + cgs.activeCursor = cgs.media.selectCursor; + } else if (n == CURSOR_SIZER) { + cgs.activeCursor = cgs.media.sizeCursor; + } + + if (cgs.capturedItem) { + Display_MouseMove(cgs.capturedItem, x, y); + } else { + Display_MouseMove(NULL, cgs.cursorX, cgs.cursorY); + } + +} + +/* +================== +CG_HideTeamMenus +================== + +*/ +void CG_HideTeamMenu() { + Menus_CloseByName("teamMenu"); + Menus_CloseByName("getMenu"); +} + +/* +================== +CG_ShowTeamMenus +================== + +*/ +void CG_ShowTeamMenu() { + Menus_OpenByName("teamMenu"); +} + + + + +/* +================== +CG_EventHandling +================== + type 0 - no event handling + 1 - team menu + 2 - hud editor + +*/ +void CG_EventHandling(int type) { + cgs.eventHandling = type; + if (type == CGAME_EVENT_NONE) { + CG_HideTeamMenu(); + } else if (type == CGAME_EVENT_TEAMMENU) { + //CG_ShowTeamMenu(); + } else if (type == CGAME_EVENT_SCOREBOARD) { + } + +} + + + +void CG_KeyEvent(int key, qboolean down) { + + if (!down) { + return; + } + + if ( cg->predictedPlayerState.pm_type == PM_NORMAL || cg->predictedPlayerState.pm_type == PM_JETPACK || cg->predictedPlayerState.pm_type == PM_NORMAL || (cg->predictedPlayerState.pm_type == PM_SPECTATOR && cg->showScores == qfalse)) { + CG_EventHandling(CGAME_EVENT_NONE); + trap_Key_SetCatcher(0); + return; + } + + //if (key == trap_Key_GetKey("teamMenu") || !Display_CaptureItem(cgs.cursorX, cgs.cursorY)) { + // if we see this then we should always be visible + // CG_EventHandling(CGAME_EVENT_NONE); + // trap_Key_SetCatcher(0); + //} + + + + Display_HandleKey(key, down, cgs.cursorX, cgs.cursorY); + + if (cgs.capturedItem) { + cgs.capturedItem = NULL; + } else { + if (key == A_MOUSE2 && down) { + cgs.capturedItem = Display_CaptureItem(cgs.cursorX, cgs.cursorY); + } + } +} + +int CG_ClientNumFromName(const char *p) { + int i; + for (i = 0; i < cgs.maxclients; i++) { + if (cgs.clientinfo[i].infoValid && Q_stricmp(cgs.clientinfo[i].name, p) == 0) { + return i; + } + } + return -1; +} + +void CG_RunMenuScript(char **args) { +} + +qboolean CG_DeferMenuScript (char **args) +{ + return qfalse; +} + +void CG_GetTeamColor(vec4_t *color) { + if (cg->snap->ps.persistant[PERS_TEAM] == TEAM_RED) { + (*color)[0] = 1.0f; + (*color)[3] = 0.25f; + (*color)[1] = (*color)[2] = 0.0f; + } else if (cg->snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { + (*color)[0] = (*color)[1] = 0.0f; + (*color)[2] = 1.0f; + (*color)[3] = 0.25f; + } else { + (*color)[0] = (*color)[2] = 0.0f; + (*color)[1] = 0.17f; + (*color)[3] = 0.25f; + } +} + diff --git a/codemp/cgame/cg_playeranimate.c b/codemp/cgame/cg_playeranimate.c new file mode 100644 index 0000000..e69de29 diff --git a/codemp/cgame/cg_players.c b/codemp/cgame/cg_players.c new file mode 100644 index 0000000..c22676f --- /dev/null +++ b/codemp/cgame/cg_players.c @@ -0,0 +1,11435 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_players.c -- handle the media and animation for player entities +#include "cg_local.h" +#include "..\ghoul2\g2.h" +#include "bg_saga.h" + +#ifdef _XBOX +#include "../client/cl_data.h" +#include "../ghoul2/ghoul2_shared.h" +#include "../renderer/tr_local.h" +#include "../renderer/modelmem.h" +#endif +#include "../xbox/XBLive.h" + +extern vmCvar_t cg_thirdPersonAlpha; + +extern int cgSiegeTeam1PlShader; +extern int cgSiegeTeam2PlShader; + +extern void CG_AddRadarEnt(centity_t *cent); //cg_ents.c +extern void CG_AddBracketedEnt(centity_t *cent); //cg_ents.c +extern qboolean CG_InFighter( void ); + + +//for g2 surface routines +#define TURN_ON 0x00000000 +#define TURN_OFF 0x00000100 + +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { + "*death1", + "*death2", + "*death3", + "*jump1", + "*pain25", + "*pain50", + "*pain75", + "*pain100", + "*falling1", + "*choke1", + "*choke2", + "*choke3", + "*gasp", + "*land1", + "*taunt", + NULL +}; + +//NPC sounds: +//Used as a supplement to the basic set for enemies and hazard team +// (keep numbers in ascending order in order for variant-capping to work) +const char *cg_customCombatSoundNames[MAX_CUSTOM_COMBAT_SOUNDS] = +{ + "*anger1", //Say when acquire an enemy when didn't have one before + "*anger2", + "*anger3", + "*victory1", //Say when killed an enemy + "*victory2", + "*victory3", + "*confuse1", //Say when confused + "*confuse2", + "*confuse3", + "*pushed1", //Say when force-pushed + "*pushed2", + "*pushed3", + "*choke1", + "*choke2", + "*choke3", + "*ffwarn", + "*ffturn", + NULL +}; + +//Used as a supplement to the basic set for stormtroopers +// (keep numbers in ascending order in order for variant-capping to work) +const char *cg_customExtraSoundNames[MAX_CUSTOM_EXTRA_SOUNDS] = +{ + "*chase1", + "*chase2", + "*chase3", + "*cover1", + "*cover2", + "*cover3", + "*cover4", + "*cover5", + "*detected1", + "*detected2", + "*detected3", + "*detected4", + "*detected5", + "*lost1", + "*outflank1", + "*outflank2", + "*escaping1", + "*escaping2", + "*escaping3", + "*giveup1", + "*giveup2", + "*giveup3", + "*giveup4", + "*look1", + "*look2", + "*sight1", + "*sight2", + "*sight3", + "*sound1", + "*sound2", + "*sound3", + "*suspicious1", + "*suspicious2", + "*suspicious3", + "*suspicious4", + "*suspicious5", + NULL +}; + +//Used as a supplement to the basic set for jedi +// (keep numbers in ascending order in order for variant-capping to work) +const char *cg_customJediSoundNames[MAX_CUSTOM_JEDI_SOUNDS] = +{ + "*combat1", + "*combat2", + "*combat3", + "*jdetected1", + "*jdetected2", + "*jdetected3", + "*taunt1", + "*taunt2", + "*taunt3", + "*jchase1", + "*jchase2", + "*jchase3", + "*jlost1", + "*jlost2", + "*jlost3", + "*deflect1", + "*deflect2", + "*deflect3", + "*gloat1", + "*gloat2", + "*gloat3", + "*pushfail", + NULL +}; + +//Used for DUEL taunts +const char *cg_customDuelSoundNames[MAX_CUSTOM_DUEL_SOUNDS] = +{ + "*anger1", //Say when acquire an enemy when didn't have one before + "*anger2", + "*anger3", + "*victory1", //Say when killed an enemy + "*victory2", + "*victory3", + "*taunt1", + "*taunt2", + "*taunt3", + "*deflect1", + "*deflect2", + "*deflect3", + "*gloat1", + "*gloat2", + "*gloat3", + NULL +}; + +void CG_Disintegration(centity_t *cent, refEntity_t *ent); + +/* +================ +CG_CustomSound + +================ +*/ +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { + clientInfo_t *ci; + int i; + int numCSounds = 0; + int numCComSounds = 0; + int numCExSounds = 0; + int numCJediSounds = 0; + int numCSiegeSounds = 0; + int numCDuelSounds = 0; + char lSoundName[MAX_QPATH]; + + if ( soundName[0] != '*' ) { + return trap_S_RegisterSound( soundName ); + } + + COM_StripExtension(soundName, lSoundName); + + if ( clientNum < 0 ) + { + clientNum = 0; + } + + if (clientNum >= MAX_CLIENTS) + { + ci = cg_entities[clientNum].npcClient; + } + else + { + ci = &cgs.clientinfo[ clientNum ]; + } + + if (!ci) + { + return 0; + } + + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!cg_customSoundNames[i]) + { + numCSounds = i; + break; + } + } + + if (clientNum >= MAX_CLIENTS) + { //these are only for npc's + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!cg_customCombatSoundNames[i]) + { + numCComSounds = i; + break; + } + } + + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!cg_customExtraSoundNames[i]) + { + numCExSounds = i; + break; + } + } + + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!cg_customJediSoundNames[i]) + { + numCJediSounds = i; + break; + } + } + } + + if (cgs.gametype >= GT_TEAM || cg_buildScript.integer) + { //siege only + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!bg_customSiegeSoundNames[i]) + { + numCSiegeSounds = i; + break; + } + } + } + + if (cgs.gametype == GT_DUEL + || cgs.gametype == GT_POWERDUEL + || cg_buildScript.integer) + { //Duel only + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!cg_customDuelSoundNames[i]) + { + numCDuelSounds = i; + break; + } + } + } + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) + { + if ( i < numCSounds && !strcmp( lSoundName, cg_customSoundNames[i] ) ) + { + return ci->sounds[i]; + } + else if ( (cgs.gametype >= GT_TEAM || cg_buildScript.integer) && i < numCSiegeSounds && !strcmp( lSoundName, bg_customSiegeSoundNames[i] ) ) + { //siege only + return ci->siegeSounds[i]; + } + else if ( (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL || cg_buildScript.integer) && i < numCDuelSounds && !strcmp( lSoundName, cg_customDuelSoundNames[i] ) ) + { //siege only + return ci->duelSounds[i]; + } + else if ( clientNum >= MAX_CLIENTS && i < numCComSounds && !strcmp( lSoundName, cg_customCombatSoundNames[i] ) ) + { //npc only + return ci->combatSounds[i]; + } + else if ( clientNum >= MAX_CLIENTS && i < numCExSounds && !strcmp( lSoundName, cg_customExtraSoundNames[i] ) ) + { //npc only + return ci->extraSounds[i]; + } + else if ( clientNum >= MAX_CLIENTS && i < numCJediSounds && !strcmp( lSoundName, cg_customJediSoundNames[i] ) ) + { //npc only + return ci->jediSounds[i]; + } + } + + //CG_Error( "Unknown custom sound: %s", lSoundName ); +#ifndef FINAL_BUILD + Com_Printf( "Unknown custom sound: %s", lSoundName ); +#endif + return 0; +} + +/* +============================================================================= + +CLIENT INFO + +============================================================================= +*/ +#define MAX_SURF_LIST_SIZE 1024 +qboolean CG_ParseSurfsFile( const char *modelName, const char *skinName, char *surfOff, char *surfOn ) +{ + const char *text_p; + int len; + const char *token; + const char *value; + char text[20000]; + char sfilename[MAX_QPATH]; + fileHandle_t f; + int i = 0; + + while (skinName && skinName[i]) + { + if (skinName[i] == '|') + { //this is a multi-part skin, said skins do not support .surf files + return qfalse; + } + + i++; + } + + + // Load and parse .surf file + Com_sprintf( sfilename, sizeof( sfilename ), "models/players/%s/model_%s.surf", modelName, skinName ); + + // load the file + len = trap_FS_FOpenFile( sfilename, &f, FS_READ ); + if ( len <= 0 ) + {//no file + return qfalse; + } + if ( len >= sizeof( text ) - 1 ) + { + Com_Printf( "File %s too long\n", sfilename ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + memset( (char *)surfOff, 0, sizeof(surfOff) ); + memset( (char *)surfOn, 0, sizeof(surfOn) ); + + // read information for surfOff and surfOn + while ( 1 ) + { + token = COM_ParseExt( &text_p, qtrue ); + if ( !token || !token[0] ) + { + break; + } + + // surfOff + if ( !Q_stricmp( token, "surfOff" ) ) + { + if ( COM_ParseString( &text_p, &value ) ) + { + continue; + } + if ( surfOff && surfOff[0] ) + { + Q_strcat( surfOff, MAX_SURF_LIST_SIZE, "," ); + Q_strcat( surfOff, MAX_SURF_LIST_SIZE, value ); + } + else + { + Q_strncpyz( surfOff, value, MAX_SURF_LIST_SIZE ); + } + continue; + } + + // surfOn + if ( !Q_stricmp( token, "surfOn" ) ) + { + if ( COM_ParseString( &text_p, &value ) ) + { + continue; + } + if ( surfOn && surfOn[0] ) + { + Q_strcat( surfOn, MAX_SURF_LIST_SIZE, ","); + Q_strcat( surfOn, MAX_SURF_LIST_SIZE, value ); + } + else + { + Q_strncpyz( surfOn, value, MAX_SURF_LIST_SIZE ); + } + continue; + } + } + return qtrue; +} + +/* +========================== +CG_RegisterClientModelname +========================== +*/ +#include "../namespace_begin.h" +qboolean BG_IsValidCharacterModel(const char *modelName, const char *skinName); +qboolean BG_ValidateSkinForTeam( const char *modelName, char *skinName, int team, float *colors ); +#include "../namespace_end.h" + +static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *teamName, int clientNum ) { + int handle; + char afilename[MAX_QPATH]; + char /**GLAName,*/ *slash; + char GLAName[MAX_QPATH]; + vec3_t tempVec = {0,0,0}; + qboolean badModel = qfalse; + char surfOff[MAX_SURF_LIST_SIZE]; + char surfOn[MAX_SURF_LIST_SIZE]; + int checkSkin; + char *useSkinName; + +retryModel: + if (badModel) + { + if (modelName && modelName[0]) + { + Com_Printf("WARNING: Attempted to load an unsupported multiplayer model %s! (bad or missing bone, or missing animation sequence)\n", modelName); + } + + modelName = "kyle"; + skinName = "default"; + + badModel = qfalse; + } + + // First things first. If this is a ghoul2 model, then let's make sure we demolish this first. + if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { + trap_G2API_CleanGhoul2Models(&(ci->ghoul2Model)); + } + + if (!BG_IsValidCharacterModel(modelName, skinName)) + { + modelName = "kyle"; + skinName = "default"; + } + + if ( cgs.gametype >= GT_TEAM && !cgs.jediVmerc && cgs.gametype != GT_SIEGE ) + { //We won't force colors for siege. + BG_ValidateSkinForTeam( ci->modelName, ci->skinName, ci->team, ci->colorOverride ); + skinName = ci->skinName; + } + else + { + ci->colorOverride[0] = ci->colorOverride[1] = ci->colorOverride[2] = 0.0f; + } + + if (strchr(skinName, '|')) + {//three part skin + useSkinName = va("models/players/%s/|%s", modelName, skinName); + } + else + { + useSkinName = va("models/players/%s/model_%s.skin", modelName, skinName); + } + + checkSkin = trap_R_RegisterSkin(useSkinName); + + if (checkSkin) + { + ci->torsoSkin = checkSkin; + } + else + { //fallback to the default skin + ci->torsoSkin = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelName, skinName)); + } + Com_sprintf( afilename, sizeof( afilename ), "models/players/%s/model.glm", modelName ); + handle = trap_G2API_InitGhoul2Model(&ci->ghoul2Model, afilename, 0, ci->torsoSkin, 0, 0, 0); + + if (handle<0) + { + return qfalse; + } + + // The model is now loaded. + + trap_G2API_SetSkin(ci->ghoul2Model, 0, ci->torsoSkin, ci->torsoSkin); + + GLAName[0] = 0; + + trap_G2API_GetGLAName( ci->ghoul2Model, 0, GLAName); + if (GLAName[0] != 0) + { + if (!strstr(GLAName, "players/_humanoid/") /*&& + (!strstr(GLAName, "players/rockettrooper/") || cgs.gametype != GT_SIEGE)*/) //only allow rockettrooper in siege + { //Bad! + badModel = qtrue; + goto retryModel; + } + } + + if (!BGPAFtextLoaded) + { + if (GLAName[0] == 0/*GLAName == NULL*/) + { + badModel = qtrue; + goto retryModel; + } + Q_strncpyz( afilename, GLAName, sizeof( afilename )); + slash = Q_strrchr( afilename, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + } // Now afilename holds just the path to the animation.cfg + else + { // Didn't find any slashes, this is a raw filename right in base (whish isn't a good thing) + return qfalse; + } + + //rww - All player models must use humanoid, no matter what. + if (Q_stricmp(afilename, "models/players/_humanoid/animation.cfg") /*&& + Q_stricmp(afilename, "models/players/rockettrooper/animation.cfg")*/) + { + Com_Printf( "Model does not use supported animation config.\n"); + return qfalse; + } + else if (BG_ParseAnimationFile("models/players/_humanoid/animation.cfg", bgHumanoidAnimations, qtrue) == -1) + { + Com_Printf( "Failed to load animation file models/players/_humanoid/animation.cfg\n" ); + return qfalse; + } + + BG_ParseAnimationEvtFile( "models/players/_humanoid/", 0, -1 ); //get the sounds for the humanoid anims +// if (cgs.gametype == GT_SIEGE) +// { +// BG_ParseAnimationEvtFile( "models/players/rockettrooper/", 1, 1 ); //parse rockettrooper too +// } + //For the time being, we're going to have all real players use the generic humanoid soundset and that's it. + //Only npc's will use model-specific soundsets. + + // BG_ParseAnimationSndFile(va("models/players/%s/", modelName), 0, -1); + } + else if (!bgAllEvents[0].eventsParsed) + { //make sure the player anim sounds are loaded even if the anims already are + BG_ParseAnimationEvtFile( "models/players/_humanoid/", 0, -1 ); +// if (cgs.gametype == GT_SIEGE) +// { +// BG_ParseAnimationEvtFile( "models/players/rockettrooper/", 1, 1 ); +// } + } + + if ( CG_ParseSurfsFile( modelName, skinName, surfOff, surfOn ) ) + {//turn on/off any surfs + const char *token; + const char *p; + + //Now turn on/off any surfaces + if ( surfOff && surfOff[0] ) + { + p = surfOff; + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + {//reached end of list + break; + } + //turn off this surf + trap_G2API_SetSurfaceOnOff( ci->ghoul2Model, token, 0x00000002/*G2SURFACEFLAG_OFF*/ ); + } + } + if ( surfOn && surfOn[0] ) + { + p = surfOn; + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + {//reached end of list + break; + } + //turn on this surf + trap_G2API_SetSurfaceOnOff( ci->ghoul2Model, token, 0 ); + } + } + } + + + ci->bolt_rhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand"); + + if (!trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, cg->time, -1, -1)) + { + badModel = qtrue; + } + + if (!trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "upper_lumbar", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, cg->time)) + { + badModel = qtrue; + } + + if (!trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "cranium", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, cg->time)) + { + badModel = qtrue; + } + + ci->bolt_lhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand"); + + //rhand must always be first bolt. lhand always second. Whichever you want the + //jetpack bolted to must always be third. + trap_G2API_AddBolt(ci->ghoul2Model, 0, "*chestg"); + + ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*head_top"); + if (ci->bolt_head == -1) + { + ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "ceyebrow"); + } + + ci->bolt_motion = trap_G2API_AddBolt(ci->ghoul2Model, 0, "Motion"); + + //We need a lower lumbar bolt for footsteps + ci->bolt_llumbar = trap_G2API_AddBolt(ci->ghoul2Model, 0, "lower_lumbar"); + + if (ci->bolt_rhand == -1 || ci->bolt_lhand == -1 || ci->bolt_head == -1 || ci->bolt_motion == -1 || ci->bolt_llumbar == -1) + { + badModel = qtrue; + } + + if (badModel) + { + goto retryModel; + } + + if (!Q_stricmp(modelName, "boba_fett")) + { //special case, turn off the jetpack surfs + trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_rjet", TURN_OFF); + trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_cjet", TURN_OFF); + trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_ljet", TURN_OFF); + } + +// ent->s.radius = 90; + + if (clientNum != -1) + { + /* + if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2)) + { + trap_G2API_CleanGhoul2Models(&(cg_entities[clientNum].ghoul2)); + } + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2); + */ + + cg_entities[clientNum].ghoul2weapon = NULL; + } + + Q_strncpyz (ci->teamName, teamName, sizeof(ci->teamName)); + + // Model icon for drawing the portrait on screen + ci->modelIcon = trap_R_RegisterShaderNoMip ( va ( "models/players/%s/icon_%s", modelName, skinName ) ); + if (!ci->modelIcon) + { + int i = 0; + int j; + char iconName[1024]; + strcpy(iconName, "icon_"); + j = strlen(iconName); + while (skinName[i] && skinName[i] != '|' && j < 1024) + { + iconName[j] = skinName[i]; + j++; + i++; + } + iconName[j] = 0; + if (skinName[i] == '|') + { //looks like it actually may be a custom model skin, let's try getting the icon... + ci->modelIcon = trap_R_RegisterShaderNoMip ( va ( "models/players/%s/%s", modelName, iconName ) ); + } + } + return qtrue; +} + +/* +==================== +CG_ColorFromString +==================== +*/ +static void CG_ColorFromString( const char *v, vec3_t color ) { + int val; + + VectorClear( color ); + + val = atoi( v ); + + if ( val < 1 || val > 7 ) { + VectorSet( color, 1, 1, 1 ); + return; + } + + if ( val & 1 ) { + color[2] = 1.0f; + } + if ( val & 2 ) { + color[1] = 1.0f; + } + if ( val & 4 ) { + color[0] = 1.0f; + } +} + +/* +==================== +CG_ColorFromInt +==================== +*/ +static void CG_ColorFromInt( int val, vec3_t color ) { + VectorClear( color ); + + if ( val < 1 || val > 7 ) { + VectorSet( color, 1, 1, 1 ); + return; + } + + if ( val & 1 ) { + color[2] = 1.0f; + } + if ( val & 2 ) { + color[1] = 1.0f; + } + if ( val & 4 ) { + color[0] = 1.0f; + } +} + +//load anim info +int CG_G2SkelForModel(void *g2) +{ + int animIndex = -1; + char GLAName[MAX_QPATH]; + char *slash; + + GLAName[0] = 0; + trap_G2API_GetGLAName(g2, 0, GLAName); + + slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + + animIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); + } + + return animIndex; +} + +//get the appropriate anim events file index +int CG_G2EvIndexForModel(void *g2, int animIndex) +{ + int evtIndex = -1; + char GLAName[MAX_QPATH]; + char *slash; + + if (animIndex == -1) + { + assert(!"shouldn't happen, bad animIndex"); + return -1; + } + + GLAName[0] = 0; + trap_G2API_GetGLAName(g2, 0, GLAName); + + slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + slash++; + *slash = 0; + + evtIndex = BG_ParseAnimationEvtFile(GLAName, animIndex, bgNumAnimEvents); + } + + return evtIndex; +} + +#define DEFAULT_FEMALE_SOUNDPATH "chars/mp_generic_female/misc"//"chars/tavion/misc" +#define DEFAULT_MALE_SOUNDPATH "chars/mp_generic_male/misc"//"chars/kyle/misc" +void CG_LoadCISounds(clientInfo_t *ci, qboolean modelloaded) +{ + fileHandle_t f; + qboolean isFemale = qfalse; + int i = 0; + int fLen = 0; + const char *dir; + char soundpath[MAX_QPATH]; + char soundName[1024]; + const char *s; + + dir = ci->modelName; + + if ( !ci->skinName || !Q_stricmp( "default", ci->skinName ) ) + {//try default sounds.cfg first + fLen = trap_FS_FOpenFile(va("models/players/%s/sounds.cfg", dir), &f, FS_READ); + if ( !f ) + {//no? Look for _default sounds.cfg + fLen = trap_FS_FOpenFile(va("models/players/%s/sounds_default.cfg", dir), &f, FS_READ); + } + } + else + {//use the .skin associated with this skin + fLen = trap_FS_FOpenFile(va("models/players/%s/sounds_%s.cfg", dir, ci->skinName), &f, FS_READ); + if ( !f ) + {//fall back to default sounds + fLen = trap_FS_FOpenFile(va("models/players/%s/sounds.cfg", dir), &f, FS_READ); + } + } + + soundpath[0] = 0; + + if (f) + { + trap_FS_Read(soundpath, fLen, f); + soundpath[fLen] = 0; + + i = fLen; + + while (i >= 0 && soundpath[i] != '\n') + { + if (soundpath[i] == 'f') + { + isFemale = qtrue; + soundpath[i] = 0; + } + + i--; + } + + i = 0; + + while (soundpath[i] && soundpath[i] != '\r' && soundpath[i] != '\n') + { + i++; + } + soundpath[i] = 0; + + trap_FS_FCloseFile(f); + } + + if (isFemale) + { + ci->gender = GENDER_FEMALE; + } + else + { + ci->gender = GENDER_MALE; + } + + trap_S_ShutUp(qtrue); + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) + { + s = cg_customSoundNames[i]; + if ( !s ) { + break; + } + + Com_sprintf(soundName, sizeof(soundName), "%s", s+1); + COM_StripExtension(soundName, soundName); + //strip the extension because we might want .mp3's + + ci->sounds[i] = 0; + // if the model didn't load use the sounds of the default model + if (soundpath[0]) + { + ci->sounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", soundpath, soundName) ); + } + else + { + if (modelloaded) + { + ci->sounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) ); + } + } + + if (!ci->sounds[i]) + { //failed the load, try one out of the generic path + if (isFemale) + { + ci->sounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) ); + } + else + { + ci->sounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) ); + } + } + } + + if (cgs.gametype >= GT_TEAM || cg_buildScript.integer) + { //load the siege sounds then + for ( i = 0 ; i < MAX_CUSTOM_SIEGE_SOUNDS; i++ ) + { + s = bg_customSiegeSoundNames[i]; + if ( !s ) + { + break; + } + + Com_sprintf(soundName, sizeof(soundName), "%s", s+1); + COM_StripExtension(soundName, soundName); + //strip the extension because we might want .mp3's + + ci->siegeSounds[i] = 0; + // if the model didn't load use the sounds of the default model + if (soundpath[0]) + { + ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", soundpath, soundName) ); + } + else + { + if (modelloaded) + { + ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) ); + } + } + + if (!ci->siegeSounds[i]) + { //failed the load, try one out of the generic path + if (isFemale) + { + ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) ); + } + else + { + ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) ); + } + } + } + } + + if (cgs.gametype == GT_DUEL + ||cgs.gametype == GT_POWERDUEL + || cg_buildScript.integer) + { //load the Duel sounds then + for ( i = 0 ; i < MAX_CUSTOM_DUEL_SOUNDS; i++ ) + { + s = cg_customDuelSoundNames[i]; + if ( !s ) + { + break; + } + + Com_sprintf(soundName, sizeof(soundName), "%s", s+1); + COM_StripExtension(soundName, soundName); + //strip the extension because we might want .mp3's + + ci->duelSounds[i] = 0; + // if the model didn't load use the sounds of the default model + if (soundpath[0]) + { + ci->duelSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", soundpath, soundName) ); + } + else + { + if (modelloaded) + { + ci->duelSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) ); + } + } + + if (!ci->duelSounds[i]) + { //failed the load, try one out of the generic path + if (isFemale) + { + ci->duelSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) ); + } + else + { + ci->duelSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) ); + } + } + } + } + + trap_S_ShutUp(qfalse); +} + +/* +=================== +CG_LoadClientInfo + +Load it now, taking the disk hits. +This will usually be deferred to a safe time +=================== +*/ +void CG_LoadClientInfo( clientInfo_t *ci ) { + qboolean modelloaded; + int clientNum; + int i; + char teamname[MAX_QPATH]; + + clientNum = ci - cgs.clientinfo; + + if (clientNum < 0 || clientNum >= MAX_CLIENTS) + { + clientNum = -1; + } + + ci->deferred = qfalse; + + /* + if (ci->team == TEAM_SPECTATOR) + { + // reset any existing players and bodies, because they might be in bad + // frames for this new model + clientNum = ci - cgs.clientinfo; + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { + if ( cg_entities[i].currentState.clientNum == clientNum + && cg_entities[i].currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( &cg_entities[i] ); + } + } + + if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + } + + return; + } + */ + + teamname[0] = 0; + if( cgs.gametype >= GT_TEAM) { + if( ci->team == TEAM_BLUE ) { + Q_strncpyz(teamname, cg_blueTeamName.string, sizeof(teamname) ); + } else { + Q_strncpyz(teamname, cg_redTeamName.string, sizeof(teamname) ); + } + } + if( teamname[0] ) { + strcat( teamname, "/" ); + } + modelloaded = qtrue; + if (cgs.gametype == GT_SIEGE && + (ci->team == TEAM_SPECTATOR || ci->siegeIndex == -1)) + { //yeah.. kind of a hack I guess. Don't care until they are actually ingame with a valid class. + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", teamname, -1 ) ) + { + CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); + } + } + else + { + if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, teamname, clientNum ) ) { + //CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); + //rww - DO NOT error out here! Someone could just type in a nonsense model name and crash everyone's client. + //Give it a chance to load default model for this client instead. + + // fall back to default team name + if( cgs.gametype >= GT_TEAM) { + // keep skin name + if( ci->team == TEAM_BLUE ) { + Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) ); + } else { + Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) ); + } + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, ci->skinName, teamname, -1 ) ) { + CG_Error( "DEFAULT_MODEL / skin (%s/%s) failed to register", DEFAULT_MODEL, ci->skinName ); + } + } else { + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", teamname, -1 ) ) { + CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); + } + } + modelloaded = qfalse; + } + } + + if (clientNum != -1) + { + trap_G2API_ClearAttachedInstance(clientNum); + } + + if (clientNum != -1 && ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { + if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2)) + { + trap_G2API_CleanGhoul2Models(&cg_entities[clientNum].ghoul2); + } + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2); + + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(cg_entities[clientNum].ghoul2, clientNum, qfalse); + + + if (trap_G2API_AddBolt(cg_entities[clientNum].ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cg_entities[clientNum].noFace = qtrue; + } + + cg_entities[clientNum].localAnimIndex = CG_G2SkelForModel(cg_entities[clientNum].ghoul2); + cg_entities[clientNum].eventAnimIndex = CG_G2EvIndexForModel(cg_entities[clientNum].ghoul2, cg_entities[clientNum].localAnimIndex); + } + + ci->newAnims = qfalse; + if ( ci->torsoModel ) { + orientation_t tag; + // if the torso model has the "tag_flag" + if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) { + ci->newAnims = qtrue; + } + } + + // sounds + if (cgs.gametype == GT_SIEGE && + (ci->team == TEAM_SPECTATOR || ci->siegeIndex == -1)) + { //don't need to load sounds + } + else + { + CG_LoadCISounds(ci, modelloaded); + } + + ci->deferred = qfalse; + + // reset any existing players and bodies, because they might be in bad + // frames for this new model + clientNum = ci - cgs.clientinfo; + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { + if ( cg_entities[i].currentState.clientNum == clientNum + && cg_entities[i].currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( &cg_entities[i] ); + } + } +} + + +//Take care of initializing all the ghoul2 saber stuff based on clientinfo data. -rww +static void CG_InitG2SaberData(int saberNum, clientInfo_t *ci) +{ + trap_G2API_InitGhoul2Model(&ci->ghoul2Weapons[saberNum], ci->saber[saberNum].model, 0, ci->saber[saberNum].skin, 0, 0, 0); + + if (ci->ghoul2Weapons[saberNum]) + { + int k = 0; + int tagBolt; + char *tagName; + + if (ci->saber[saberNum].skin) + { + trap_G2API_SetSkin(ci->ghoul2Weapons[saberNum], 0, ci->saber[saberNum].skin, ci->saber[saberNum].skin); + } + + trap_G2API_SetBoltInfo(ci->ghoul2Weapons[saberNum], 0, saberNum); + + while (k < ci->saber[saberNum].numBlades) + { + tagName = va("*blade%i", k+1); + tagBolt = trap_G2API_AddBolt(ci->ghoul2Weapons[saberNum], 0, tagName); + + if (tagBolt == -1) + { + if (k == 0) + { //guess this is an 0ldsk3wl saber + tagBolt = trap_G2API_AddBolt(ci->ghoul2Weapons[saberNum], 0, "*flash"); + + if (tagBolt == -1) + { + assert(0); + } + break; + } + + if (tagBolt == -1) + { + assert(0); + break; + } + } + + k++; + } + } +} + + +/* +====================== +CG_CopyClientInfoModel +====================== +*/ +static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) +{ + VectorCopy( from->headOffset, to->headOffset ); +// to->footsteps = from->footsteps; + to->gender = from->gender; + + to->legsModel = from->legsModel; + to->legsSkin = from->legsSkin; + to->torsoModel = from->torsoModel; + to->torsoSkin = from->torsoSkin; + //to->headModel = from->headModel; + //to->headSkin = from->headSkin; + to->modelIcon = from->modelIcon; + + to->newAnims = from->newAnims; + + //to->ghoul2Model = from->ghoul2Model; + //rww - Trying to use the same ghoul2 pointer for two seperate clients == DISASTER + assert(to->ghoul2Model != from->ghoul2Model); + + if (to->ghoul2Model && trap_G2_HaveWeGhoul2Models(to->ghoul2Model)) + { + trap_G2API_CleanGhoul2Models(&to->ghoul2Model); + } + if (from->ghoul2Model && trap_G2_HaveWeGhoul2Models(from->ghoul2Model)) + { + trap_G2API_DuplicateGhoul2Instance(from->ghoul2Model, &to->ghoul2Model); + } + + //Don't do this, I guess. Just leave the saber info in the original, so it will be + //properly initialized. + /* + strcpy(to->saberName, from->saberName); + strcpy(to->saber2Name, from->saber2Name); + + while (i < MAX_SABERS) + { + if (to->ghoul2Weapons[i] && trap_G2_HaveWeGhoul2Models(to->ghoul2Weapons[i])) + { + trap_G2API_CleanGhoul2Models(&to->ghoul2Weapons[i]); + } + + WP_SetSaber(to->saber, 0, to->saberName); + WP_SetSaber(to->saber, 1, to->saber2Name); + + j = 0; + + while (j < MAX_SABERS) + { + if (to->saber[j].model[0]) + { + CG_InitG2SaberData(j, to); + } + j++; + } + i++; + } + */ + + to->bolt_head = from->bolt_head; + to->bolt_lhand = from->bolt_lhand; + to->bolt_rhand = from->bolt_rhand; + to->bolt_motion = from->bolt_motion; + to->bolt_llumbar = from->bolt_llumbar; + + to->siegeIndex = from->siegeIndex; + + memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); + memcpy( to->siegeSounds, from->siegeSounds, sizeof( to->siegeSounds ) ); + memcpy( to->duelSounds, from->duelSounds, sizeof( to->duelSounds ) ); +} + +/* +====================== +CG_ScanForExistingClientInfo +====================== +*/ +static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci, int clientNum ) { + int i; + clientInfo_t *match; + + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + if ( match->deferred ) { + continue; + } + if ( !Q_stricmp( ci->modelName, match->modelName ) + && !Q_stricmp( ci->skinName, match->skinName ) + && !Q_stricmp( ci->saberName, match->saberName) + && !Q_stricmp( ci->saber2Name, match->saber2Name) +// && !Q_stricmp( ci->headModelName, match->headModelName ) +// && !Q_stricmp( ci->headSkinName, match->headSkinName ) + && !Q_stricmp( ci->blueTeam, match->blueTeam ) + && !Q_stricmp( ci->redTeam, match->redTeam ) + && (cgs.gametype < GT_TEAM || ci->team == match->team) + && ci->siegeIndex == match->siegeIndex + && match->ghoul2Model + && match->bolt_head) //if the bolts haven't been initialized, this "match" is useless to us + { + // this clientinfo is identical, so use it's handles + + ci->deferred = qfalse; + + //rww - Filthy hack. If this is actually the info already belonging to us, just reassign the pointer. + //Switching instances when not necessary produces small animation glitches. + //Actually, before, were we even freeing the instance attached to the old clientinfo before copying + //this new clientinfo over it? Could be a nasty leak possibility. (though this should remedy it in theory) + if (clientNum == i) + { + if (match->ghoul2Model && trap_G2_HaveWeGhoul2Models(match->ghoul2Model)) + { //The match has a valid instance (if it didn't, we'd probably already be fudged (^_^) at this state) + if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { //First kill the copy we have if we have one. (but it should be null) + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + } + + VectorCopy( match->headOffset, ci->headOffset ); +// ci->footsteps = match->footsteps; + ci->gender = match->gender; + + ci->legsModel = match->legsModel; + ci->legsSkin = match->legsSkin; + ci->torsoModel = match->torsoModel; + ci->torsoSkin = match->torsoSkin; + ci->modelIcon = match->modelIcon; + + ci->newAnims = match->newAnims; + + ci->bolt_head = match->bolt_head; + ci->bolt_lhand = match->bolt_lhand; + ci->bolt_rhand = match->bolt_rhand; + ci->bolt_motion = match->bolt_motion; + ci->bolt_llumbar = match->bolt_llumbar; + ci->siegeIndex = match->siegeIndex; + + memcpy( ci->sounds, match->sounds, sizeof( ci->sounds ) ); + memcpy( ci->siegeSounds, match->siegeSounds, sizeof( ci->siegeSounds ) ); + memcpy( ci->duelSounds, match->duelSounds, sizeof( ci->duelSounds ) ); + + //We can share this pointer, because it already belongs to this client. + //The pointer itself and the ghoul2 instance is never actually changed, just passed between + //clientinfo structures. + ci->ghoul2Model = match->ghoul2Model; + + //Don't need to do this I guess, whenever this function is called the saber stuff should + //already be taken care of in the new info. + /* + while (k < MAX_SABERS) + { + if (match->ghoul2Weapons[k] && match->ghoul2Weapons[k] != ci->ghoul2Weapons[k]) + { + if (ci->ghoul2Weapons[k]) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[k]); + } + ci->ghoul2Weapons[k] = match->ghoul2Weapons[k]; + } + k++; + } + */ + } + } + else + { + CG_CopyClientInfoModel( match, ci ); + } + + return qtrue; + } + } + + // nothing matches, so defer the load + return qfalse; +} + +/* +====================== +CG_SetDeferredClientInfo + +We aren't going to load it now, so grab some other +client's info to use until we have some spare time. +====================== +*/ +static void CG_SetDeferredClientInfo( clientInfo_t *ci ) { + int i; + clientInfo_t *match; + + // if someone else is already the same models and skins we + // can just load the client info + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid || match->deferred ) { + continue; + } + if ( Q_stricmp( ci->skinName, match->skinName ) || + Q_stricmp( ci->modelName, match->modelName ) || +// Q_stricmp( ci->headModelName, match->headModelName ) || +// Q_stricmp( ci->headSkinName, match->headSkinName ) || + (cgs.gametype >= GT_TEAM && ci->team != match->team && ci->team != TEAM_SPECTATOR) ) { + continue; + } + + /* + if (Q_stricmp(ci->saberName, match->saberName) || + Q_stricmp(ci->saber2Name, match->saber2Name)) + { + continue; + } + */ + + // just load the real info cause it uses the same models and skins + CG_LoadClientInfo( ci ); + return; + } + + // if we are in teamplay, only grab a model if the skin is correct + if ( cgs.gametype >= GT_TEAM ) { + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid || match->deferred ) { + continue; + } + if ( ci->team != TEAM_SPECTATOR && + (Q_stricmp( ci->skinName, match->skinName ) || + (cgs.gametype >= GT_TEAM && ci->team != match->team)) ) { + continue; + } + + /* + if (Q_stricmp(ci->saberName, match->saberName) || + Q_stricmp(ci->saber2Name, match->saber2Name)) + { + continue; + } + */ + + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + // load the full model, because we don't ever want to show + // an improper team skin. This will cause a hitch for the first + // player, when the second enters. Combat shouldn't be going on + // yet, so it shouldn't matter + CG_LoadClientInfo( ci ); + return; + } + + // find the first valid clientinfo and grab its stuff + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + + /* + if (Q_stricmp(ci->saberName, match->saberName) || + Q_stricmp(ci->saber2Name, match->saber2Name)) + { + continue; + } + */ + + if (match->deferred) + { //no deferring off of deferred info. Because I said so. + continue; + } + + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + + // we should never get here... + //CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); + //Actually it is possible now because of the unique sabers. + + CG_LoadClientInfo( ci ); +} + +/* +====================== +CG_NewClientInfo +====================== +*/ +#include "../namespace_begin.h" +void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName ); +#include "../namespace_end.h" + +void CG_NewClientInfo( int clientNum, qboolean entitiesInitialized ) { + clientInfo_t *ci; + clientInfo_t newInfo; + const char *configstring; + const char *v; + char *slash; + void *oldGhoul2; + void *oldG2Weapons[MAX_SABERS]; + int i = 0; + int k = 0; + qboolean saberUpdate[MAX_SABERS]; + +#ifdef _XBOX + if (ClientManager::ActiveClientNum() == 1) + return; +#endif + + ci = &cgs.clientinfo[clientNum]; + + oldGhoul2 = ci->ghoul2Model; + + while (k < MAX_SABERS) + { + oldG2Weapons[k] = ci->ghoul2Weapons[k]; + k++; + } + + configstring = CG_ConfigString( clientNum + CS_PLAYERS ); + if ( !configstring[0] ) { + if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { //clean this stuff up first + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + } + k = 0; + while (k < MAX_SABERS) + { + if (ci->ghoul2Weapons[k] && trap_G2_HaveWeGhoul2Models(ci->ghoul2Weapons[k])) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[k]); + } + k++; + } + +#ifdef _XBOX + // To the best of my knowledge, we only get here if a client that has + // left the game is getting it's ghoul2 model cleaned up for good + // Kill our model slot + char afilename[MAX_QPATH]; + + Com_sprintf( afilename, sizeof( afilename ), "models/players/%s/model.glm", ci->modelName ); + ModelMem.FreeModelMemory(afilename); +#endif + + memset( ci, 0, sizeof( *ci ) ); + return; // player just left + } + + // build into a temp buffer so the defer checks can use + // the old value + memset( &newInfo, 0, sizeof( newInfo ) ); + + // isolate the player's name + v = Info_ValueForKey(configstring, "n"); + Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); + + // Update the player's name in the player list, if we need to: + XBL_PL_UpdatePlayerName( clientNum, v ); + + // colors + v = Info_ValueForKey( configstring, "c1" ); + CG_ColorFromString( v, newInfo.color1 ); + + newInfo.icolor1 = atoi(v); + + v = Info_ValueForKey( configstring, "c2" ); + CG_ColorFromString( v, newInfo.color2 ); + + newInfo.icolor2 = atoi(v); + + // bot skill + v = Info_ValueForKey( configstring, "skill" ); + newInfo.botSkill = atoi( v ); + + // handicap + v = Info_ValueForKey( configstring, "hc" ); + newInfo.handicap = atoi( v ); + + // wins + v = Info_ValueForKey( configstring, "w" ); + newInfo.wins = atoi( v ); + + // losses + v = Info_ValueForKey( configstring, "l" ); + newInfo.losses = atoi( v ); + + // team + v = Info_ValueForKey( configstring, "t" ); + newInfo.team = atoi( v ); + +//copy team info out to menu + if ( clientNum == cg->clientNum) //this is me + { + trap_Cvar_Set("ui_team", v); + } + + // team task + v = Info_ValueForKey( configstring, "tt" ); + newInfo.teamTask = atoi(v); + + // team leader + v = Info_ValueForKey( configstring, "tl" ); + newInfo.teamLeader = atoi(v); + + v = Info_ValueForKey( configstring, "g_redteam" ); + Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME); + + v = Info_ValueForKey( configstring, "g_blueteam" ); + Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME); + + // model + v = Info_ValueForKey( configstring, "model" ); + if ( cg_forceModel.integer ) { + // forcemodel makes everyone use a single model + // to prevent load hitches + char modelStr[MAX_QPATH]; + char *skin; + + trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) ); + if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { + skin = "default"; + } else { + *skin++ = 0; + } + Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); + Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) ); + + if ( cgs.gametype >= GT_TEAM ) { + // keep skin name + slash = strchr( v, '/' ); + if ( slash ) { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + } + } + } else { + Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); + + slash = strchr( newInfo.modelName, '/' ); + if ( !slash ) { + // modelName didn not include a skin name + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + } else { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + // truncate modelName + *slash = 0; + } + } + + if (cgs.gametype == GT_SIEGE) + { //entries only sent in siege mode + //siege desired team + v = Info_ValueForKey( configstring, "sdt" ); + if (v && v[0]) + { + newInfo.siegeDesiredTeam = atoi(v); + } + else + { + newInfo.siegeDesiredTeam = 0; + } + + //siege classname + v = Info_ValueForKey( configstring, "siegeclass" ); + newInfo.siegeIndex = -1; + + if (v) + { + siegeClass_t *siegeClass = BG_SiegeFindClassByName(v); + + if (siegeClass) + { //See if this class forces a model, if so, then use it. Same for skin. + newInfo.siegeIndex = BG_SiegeFindClassIndexByName(v); + + if (siegeClass->forcedModel[0]) + { + Q_strncpyz( newInfo.modelName, siegeClass->forcedModel, sizeof( newInfo.modelName ) ); + } + + if (siegeClass->forcedSkin[0]) + { + Q_strncpyz( newInfo.skinName, siegeClass->forcedSkin, sizeof( newInfo.skinName ) ); + } + + if (siegeClass->hasForcedSaberColor) + { + newInfo.icolor1 = siegeClass->forcedSaberColor; + + CG_ColorFromInt( newInfo.icolor1, newInfo.color1 ); + } + if (siegeClass->hasForcedSaber2Color) + { + newInfo.icolor2 = siegeClass->forcedSaber2Color; + + CG_ColorFromInt( newInfo.icolor2, newInfo.color2 ); + } + } + } + } + + saberUpdate[0] = qfalse; + saberUpdate[1] = qfalse; + + //saber being used + v = Info_ValueForKey( configstring, "st" ); + + if (v && Q_stricmp(v, ci->saberName)) + { + Q_strncpyz( newInfo.saberName, v, 64 ); + WP_SetSaber(clientNum, newInfo.saber, 0, newInfo.saberName); + saberUpdate[0] = qtrue; + } + else + { + Q_strncpyz( newInfo.saberName, ci->saberName, 64 ); + memcpy(&newInfo.saber[0], &ci->saber[0], sizeof(newInfo.saber[0])); + newInfo.ghoul2Weapons[0] = ci->ghoul2Weapons[0]; + } + + v = Info_ValueForKey( configstring, "st2" ); + + if (v && Q_stricmp(v, ci->saber2Name)) + { + Q_strncpyz( newInfo.saber2Name, v, 64 ); + WP_SetSaber(clientNum, newInfo.saber, 1, newInfo.saber2Name); + saberUpdate[1] = qtrue; + } + else + { + Q_strncpyz( newInfo.saber2Name, ci->saber2Name, 64 ); + memcpy(&newInfo.saber[1], &ci->saber[1], sizeof(newInfo.saber[1])); + newInfo.ghoul2Weapons[1] = ci->ghoul2Weapons[1]; + } + + if (saberUpdate[0] || saberUpdate[1]) + { + int j = 0; + + while (j < MAX_SABERS) + { + if (saberUpdate[j]) + { + if (newInfo.saber[j].model[0]) + { + if (oldG2Weapons[j]) + { //free the old instance(s) + trap_G2API_CleanGhoul2Models(&oldG2Weapons[j]); + oldG2Weapons[j] = 0; + } + + CG_InitG2SaberData(j, &newInfo); + } + else + { + if (oldG2Weapons[j]) + { //free the old instance(s) + trap_G2API_CleanGhoul2Models(&oldG2Weapons[j]); + oldG2Weapons[j] = 0; + } + } + + cg_entities[clientNum].weapon = 0; + cg_entities[clientNum].ghoul2weapon = NULL; //force a refresh + } + j++; + } + } + + //Check for any sabers that didn't get set again, if they didn't, then reassign the pointers for the new ci + k = 0; + while (k < MAX_SABERS) + { + if (oldG2Weapons[k]) + { + newInfo.ghoul2Weapons[k] = oldG2Weapons[k]; + } + k++; + } + + //duel team + v = Info_ValueForKey( configstring, "dt" ); + + if (v) + { + newInfo.duelTeam = atoi(v); + } + else + { + newInfo.duelTeam = 0; + } + + // force powers + v = Info_ValueForKey( configstring, "forcepowers" ); + Q_strncpyz( newInfo.forcePowers, v, sizeof( newInfo.forcePowers ) ); + + if (cgs.gametype >= GT_TEAM && !cgs.jediVmerc && cgs.gametype != GT_SIEGE ) + { //We won't force colors for siege. + BG_ValidateSkinForTeam( newInfo.modelName, newInfo.skinName, newInfo.team, newInfo.colorOverride ); + } + else + { + newInfo.colorOverride[0] = newInfo.colorOverride[1] = newInfo.colorOverride[2] = 0.0f; + } + +#ifdef _XBOX + char afilename[MAX_QPATH]; + char bfilename[MAX_QPATH]; + + if(strcmp(ci->modelName, newInfo.modelName) != 0) + { + Com_sprintf( afilename, sizeof( afilename ), "models/players/%s/model.glm", ci->modelName ); + Com_sprintf( bfilename, sizeof( bfilename ), "models/players/%s/model.glm", newInfo.modelName ); + +// ModelMem.inNewClient = true; + + if(strlen(ci->modelName)) + ModelMem.FreeModelMemory(afilename);; + +// ModelMem.ModelAddRef(bfilename); + +// ModelMem.inNewClient = false; + } +#endif + + // scan for an existing clientinfo that matches this modelname + // so we can avoid loading checks if possible + if ( !CG_ScanForExistingClientInfo( &newInfo, clientNum ) ) { + // if we are defering loads, just have it pick the first valid + if (cg->snap && cg->snap->ps.clientNum == clientNum) + { //rww - don't defer your own client info ever + CG_LoadClientInfo( &newInfo ); + } + else if ( cg_deferPlayers.integer && cgs.gametype != GT_SIEGE && !cg_buildScript.integer && !cg->loading ) { + // keep whatever they had if it won't violate team skins + CG_SetDeferredClientInfo( &newInfo ); + } else { +#ifdef _XBOX +// ModelMem.inNewClient = true; +#endif + CG_LoadClientInfo( &newInfo ); +#ifdef _XBOX +// ModelMem.inNewClient = false; +#endif + } + } + + // replace whatever was there with the new one + newInfo.infoValid = qtrue; + if (ci->ghoul2Model && + ci->ghoul2Model != newInfo.ghoul2Model && + trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { //We must kill this instance before we remove our only pointer to it from the cgame. + //Otherwise we will end up with extra instances all over the place, I think. + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + } + *ci = newInfo; + + //force a weapon change anyway, for all clients being rendered to the current client + while (i < MAX_CLIENTS) + { + cg_entities[i].ghoul2weapon = NULL; + i++; + } + + if (clientNum != -1) + { //don't want it using an invalid pointer to share + trap_G2API_ClearAttachedInstance(clientNum); + } + + // Check if the ghoul2 model changed in any way. This is safer than assuming we have a legal cent shile loading info. + if (entitiesInitialized && ci->ghoul2Model && (oldGhoul2 != ci->ghoul2Model)) + { + // Copy the new ghoul2 model to the centity. + animation_t *anim; + centity_t *cent = &cg_entities[clientNum]; + + anim = &bgHumanoidAnimations[ (cg_entities[clientNum].currentState.legsAnim) ]; + + if (anim) + { + int flags = BONE_ANIM_OVERRIDE_FREEZE; + int firstFrame = anim->firstFrame; + int setFrame = -1; + float animSpeed = 50.0f / anim->frameLerp; + + if (anim->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (cent->pe.legs.frame >= anim->firstFrame && cent->pe.legs.frame <= (anim->firstFrame + anim->numFrames)) + { + setFrame = cent->pe.legs.frame; + } + + //rww - Set the animation again because it just got reset due to the model change + trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg->time, setFrame, 150); + + cg_entities[clientNum].currentState.legsAnim = 0; + } + + anim = &bgHumanoidAnimations[ (cg_entities[clientNum].currentState.torsoAnim) ]; + + if (anim) + { + int flags = BONE_ANIM_OVERRIDE_FREEZE; + int firstFrame = anim->firstFrame; + int setFrame = -1; + float animSpeed = 50.0f / anim->frameLerp; + + if (anim->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (cent->pe.torso.frame >= anim->firstFrame && cent->pe.torso.frame <= (anim->firstFrame + anim->numFrames)) + { + setFrame = cent->pe.torso.frame; + } + + //rww - Set the animation again because it just got reset due to the model change + trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "lower_lumbar", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg->time, setFrame, 150); + + cg_entities[clientNum].currentState.torsoAnim = 0; + } + + if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2)) + { + trap_G2API_CleanGhoul2Models(&cg_entities[clientNum].ghoul2); + } + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2); + + if (clientNum != -1) + { + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(cg_entities[clientNum].ghoul2, clientNum, qfalse); + } + + if (trap_G2API_AddBolt(cg_entities[clientNum].ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cg_entities[clientNum].noFace = qtrue; + } + + cg_entities[clientNum].localAnimIndex = CG_G2SkelForModel(cg_entities[clientNum].ghoul2); + cg_entities[clientNum].eventAnimIndex = CG_G2EvIndexForModel(cg_entities[clientNum].ghoul2, cg_entities[clientNum].localAnimIndex); + + if (cg_entities[clientNum].currentState.number != cg->predictedPlayerState.clientNum && + cg_entities[clientNum].currentState.weapon == WP_SABER) + { + cg_entities[clientNum].weapon = cg_entities[clientNum].currentState.weapon; + if (cg_entities[clientNum].ghoul2 && ci->ghoul2Model) + { + CG_CopyG2WeaponInstance(&cg_entities[clientNum], cg_entities[clientNum].currentState.weapon, cg_entities[clientNum].ghoul2); + cg_entities[clientNum].ghoul2weapon = CG_G2WeaponInstance(&cg_entities[clientNum], cg_entities[clientNum].currentState.weapon); + } + if (!cg_entities[clientNum].currentState.saberHolstered) + { //if not holstered set length and desired length for both blades to full right now. + int j; + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + i = 0; + while (i < MAX_SABERS) + { + j = 0; + while (j < ci->saber[i].numBlades) + { + ci->saber[i].blade[j].length = ci->saber[i].blade[j].lengthMax; + j++; + } + i++; + } + } + } + } +} + + +qboolean cgQueueLoad = qfalse; +/* +====================== +CG_ActualLoadDeferredPlayers + +Called at the beginning of CG_Player if cgQueueLoad is set. +====================== +*/ +void CG_ActualLoadDeferredPlayers( void ) +{ + int i; + clientInfo_t *ci; + + // scan for a deferred player to load + for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) { + if ( ci->infoValid && ci->deferred ) { + CG_LoadClientInfo( ci ); +// break; + } + } +} + +/* +====================== +CG_LoadDeferredPlayers + +Called each frame when a player is dead +and the scoreboard is up +so deferred players can be loaded +====================== +*/ +void CG_LoadDeferredPlayers( void ) { + cgQueueLoad = qtrue; +} + +/* +============================================================================= + +PLAYER ANIMATION + +============================================================================= +*/ + +#define FOOTSTEP_DISTANCE 32 +static void _PlayerFootStep( const vec3_t origin, + const float orientation, + const float radius, + centity_t *const cent, footstepType_t footStepType ) +{ + vec3_t end, mins = {-7, -7, 0}, maxs = {7, 7, 2}; + trace_t trace; + footstep_t soundType = FOOTSTEP_TOTAL; + qboolean bMark = qfalse; + qhandle_t footMarkShader; + int effectID = -1; + //float alpha; + + // send a trace down from the player to the ground + VectorCopy( origin, end ); + end[2] -= FOOTSTEP_DISTANCE; + + trap_CM_BoxTrace( &trace, origin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + // no shadow if too high + if ( trace.fraction >= 1.0f ) + { + return; + } + + //check for foot-steppable surface flag + switch( trace.surfaceFlags & MATERIAL_MASK ) + { + case MATERIAL_MUD: + bMark = qtrue; + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_MUDRUN; + } else { + soundType = FOOTSTEP_MUDWALK; + } + effectID = cgs.effects.footstepMud; + break; + case MATERIAL_DIRT: + bMark = qtrue; + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_DIRTRUN; + } else { + soundType = FOOTSTEP_DIRTWALK; + } + effectID = cgs.effects.footstepSand; + break; + case MATERIAL_SAND: + bMark = qtrue; + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_SANDRUN; + } else { + soundType = FOOTSTEP_SANDWALK; + } + effectID = cgs.effects.footstepSand; + break; + case MATERIAL_SNOW: + bMark = qtrue; + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_SNOWRUN; + } else { + soundType = FOOTSTEP_SNOWWALK; + } + effectID = cgs.effects.footstepSnow; + break; + case MATERIAL_SHORTGRASS: + case MATERIAL_LONGGRASS: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_GRASSRUN; + } else { + soundType = FOOTSTEP_GRASSWALK; + } + break; + case MATERIAL_SOLIDMETAL: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_METALRUN; + } else { + soundType = FOOTSTEP_METALWALK; + } + break; + case MATERIAL_HOLLOWMETAL: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_PIPERUN; + } else { + soundType = FOOTSTEP_PIPEWALK; + } + break; + case MATERIAL_GRAVEL: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_GRAVELRUN; + } else { + soundType = FOOTSTEP_GRAVELWALK; + } + effectID = cgs.effects.footstepGravel; + break; + case MATERIAL_CARPET: + case MATERIAL_FABRIC: + case MATERIAL_CANVAS: + case MATERIAL_RUBBER: + case MATERIAL_PLASTIC: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_RUGRUN; + } else { + soundType = FOOTSTEP_RUGWALK; + } + break; + case MATERIAL_SOLIDWOOD: + case MATERIAL_HOLLOWWOOD: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_WOODRUN; + } else { + soundType = FOOTSTEP_WOODWALK; + } + break; + + default: + //fall through + case MATERIAL_GLASS: + case MATERIAL_WATER: + case MATERIAL_FLESH: + case MATERIAL_BPGLASS: + case MATERIAL_DRYLEAVES: + case MATERIAL_GREENLEAVES: + case MATERIAL_TILES: + case MATERIAL_PLASTER: + case MATERIAL_SHATTERGLASS: + case MATERIAL_ARMOR: + case MATERIAL_COMPUTER: + + case MATERIAL_CONCRETE: + case MATERIAL_ROCK: + case MATERIAL_ICE: + case MATERIAL_MARBLE: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_STONERUN; + } else { + soundType = FOOTSTEP_STONEWALK; + } + break; + } + if (soundType < FOOTSTEP_TOTAL) + { + trap_S_StartSound( NULL, cent->currentState.clientNum, CHAN_BODY, cgs.media.footsteps[soundType][rand()&3] ); + } + + if ( cg_footsteps.integer < 4 ) + {//debugging - 4 always does footstep effect + if ( cg_footsteps.integer < 2 ) //1 for sounds, 2 for effects, 3 for marks + { + return; + } + } + + if ( effectID != -1 ) + { + trap_FX_PlayEffectID( effectID, trace.endpos, trace.plane.normal, -1, -1 ); + } + + if ( cg_footsteps.integer < 4 ) + {//debugging - 4 always does footstep effect + if ( !bMark || cg_footsteps.integer < 3 ) //1 for sounds, 2 for effects, 3 for marks + { + return; + } + } + + switch ( footStepType ) + { + case FOOTSTEP_HEAVY_R: + footMarkShader = cgs.media.fshrMarkShader; + break; + case FOOTSTEP_HEAVY_L: + footMarkShader = cgs.media.fshlMarkShader; + break; + case FOOTSTEP_R: + footMarkShader = cgs.media.fsrMarkShader; + break; + default: + case FOOTSTEP_L: + footMarkShader = cgs.media.fslMarkShader; + break; + } + + // fade the shadow out with height +// alpha = 1.0 - trace.fraction; + + // add the mark as a temporary, so it goes directly to the renderer + // without taking a spot in the cg_marks array + if (trace.plane.normal[0] || trace.plane.normal[1] || trace.plane.normal[2]) + { + CG_ImpactMark( footMarkShader, trace.endpos, trace.plane.normal, + orientation, 1,1,1, 1.0f, qfalse, radius, qfalse ); + } +} + +static void CG_PlayerFootsteps( centity_t *cent, footstepType_t footStepType ) +{ + if ( !cg_footsteps.integer ) + { + return; + } + + //FIXME: make this a feature of NPCs in the NPCs.cfg? Specify a footstep shader, if any? + if ( cent->currentState.NPC_class != CLASS_ATST + && cent->currentState.NPC_class != CLASS_CLAW + && cent->currentState.NPC_class != CLASS_FISH + && cent->currentState.NPC_class != CLASS_FLIER2 + && cent->currentState.NPC_class != CLASS_GLIDER + && cent->currentState.NPC_class != CLASS_INTERROGATOR + && cent->currentState.NPC_class != CLASS_MURJJ + && cent->currentState.NPC_class != CLASS_PROBE + && cent->currentState.NPC_class != CLASS_R2D2 + && cent->currentState.NPC_class != CLASS_R5D2 + && cent->currentState.NPC_class != CLASS_REMOTE + && cent->currentState.NPC_class != CLASS_SEEKER + && cent->currentState.NPC_class != CLASS_SENTRY + && cent->currentState.NPC_class != CLASS_SWAMP ) + { + mdxaBone_t boltMatrix; + vec3_t tempAngles, sideOrigin; + int footBolt = -1; + + tempAngles[PITCH] = 0; + tempAngles[YAW] = cent->pe.legs.yawAngle; + tempAngles[ROLL] = 0; + + switch ( footStepType ) + { + case FOOTSTEP_R: + case FOOTSTEP_HEAVY_R: + footBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_leg_foot");//cent->gent->footRBolt; + break; + case FOOTSTEP_L: + case FOOTSTEP_HEAVY_L: + default: + footBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_leg_foot");//cent->gent->footLBolt; + break; + } + + + //FIXME: get yaw orientation of the foot and use on decal + trap_G2API_GetBoltMatrix( cent->ghoul2, 0, footBolt, &boltMatrix, tempAngles, cent->lerpOrigin, + cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, sideOrigin ); + sideOrigin[2] += 15; //fudge up a bit for coplanar + _PlayerFootStep( sideOrigin, cent->pe.legs.yawAngle, 6, cent, footStepType ); + } +} + +void CG_PlayerAnimEventDo( centity_t *cent, animevent_t *animEvent ) +{ + soundChannel_t channel = CHAN_AUTO; + if ( !cent || !animEvent ) + { + return; + } + + switch ( animEvent->eventType ) + { + case AEV_SOUNDCHAN: + channel = (soundChannel_t)animEvent->eventData[AED_SOUNDCHANNEL]; + case AEV_SOUND: + { // are there variations on the sound? + const int holdSnd = animEvent->eventData[ AED_SOUNDINDEX_START+Q_irand( 0, animEvent->eventData[AED_SOUND_NUMRANDOMSNDS] ) ]; + if ( holdSnd > 0 ) + { + trap_S_StartSound( NULL, cent->currentState.number, channel, holdSnd ); + } + } + break; + case AEV_FOOTSTEP: + CG_PlayerFootsteps( cent, (footstepType_t)animEvent->eventData[AED_FOOTSTEP_TYPE] ); + break; + case AEV_EFFECT: +#if 0 //SP method + //add bolt, play effect + if ( animEvent->stringData != NULL && cent && cent->gent && cent->gent->ghoul2.size() ) + {//have a bolt name we want to use + animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], animEvent->stringData ); + animEvent->stringData = NULL;//so we don't try to do this again + } + if ( animEvent->eventData[AED_BOLTINDEX] != -1 ) + {//have a bolt we want to play the effect on + G_PlayEffect( animEvent->eventData[AED_EFFECTINDEX], cent->gent->playerModel, animEvent->eventData[AED_BOLTINDEX], cent->currentState.clientNum ); + } + else + {//play at origin? FIXME: maybe allow a fwd/rt/up offset? + theFxScheduler.PlayEffect( animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, qfalse ); + } +#else //my method + if (animEvent->stringData && animEvent->stringData[0] && cent && cent->ghoul2) + { + animEvent->eventData[AED_MODELINDEX] = 0; + if ( ( Q_stricmpn( "*blade", animEvent->stringData, 6 ) == 0 + || Q_stricmp( "*flash", animEvent->stringData ) == 0 ) ) + {//must be a weapon, try weapon 0? + animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 1, animEvent->stringData ); + if ( animEvent->eventData[AED_BOLTINDEX] != -1 ) + {//found it! + animEvent->eventData[AED_MODELINDEX] = 1; + } + else + {//hmm, just try on the player model, then? + animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 0, animEvent->stringData ); + } + } + else + { + animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 0, animEvent->stringData ); + } + animEvent->stringData[0] = 0; + } + if ( animEvent->eventData[AED_BOLTINDEX] != -1 ) + { + vec3_t lAngles; + vec3_t bPoint, bAngle; + mdxaBone_t matrix; + + VectorSet(lAngles, 0, cent->lerpAngles[YAW], 0); + + trap_G2API_GetBoltMatrix(cent->ghoul2, animEvent->eventData[AED_MODELINDEX], animEvent->eventData[AED_BOLTINDEX], &matrix, lAngles, + cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, bPoint); + VectorSet(bAngle, 0, 1, 0); + + trap_FX_PlayEffectID(animEvent->eventData[AED_EFFECTINDEX], bPoint, bAngle, -1, -1); + } + else + { + vec3_t bAngle; + + VectorSet(bAngle, 0, 1, 0); + trap_FX_PlayEffectID(animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, bAngle, -1, -1); + } +#endif + break; + //Would have to keep track of this on server to for these, it's not worth it. + case AEV_FIRE: + case AEV_MOVE: + break; + /* + case AEV_FIRE: + //add fire event + if ( animEvent->eventData[AED_FIRE_ALT] ) + { + G_AddEvent( cent->gent, EV_ALT_FIRE, 0 ); + } + else + { + G_AddEvent( cent->gent, EV_FIRE_WEAPON, 0 ); + } + break; + case AEV_MOVE: + //make him jump + if ( cent && cent->gent && cent->gent->client ) + { + if ( cent->gent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//on something + vec3_t fwd, rt, up, angles = {0, cent->gent->client->ps.viewangles[YAW], 0}; + AngleVectors( angles, fwd, rt, up ); + //FIXME: set or add to velocity? + VectorScale( fwd, animEvent->eventData[AED_MOVE_FWD], cent->gent->client->ps.velocity ); + VectorMA( cent->gent->client->ps.velocity, animEvent->eventData[AED_MOVE_RT], rt, cent->gent->client->ps.velocity ); + VectorMA( cent->gent->client->ps.velocity, animEvent->eventData[AED_MOVE_UP], up, cent->gent->client->ps.velocity ); + + if ( animEvent->eventData[AED_MOVE_UP] > 0 ) + {//a jump + cent->gent->client->ps.pm_flags |= PMF_JUMPING; + + G_AddEvent( cent->gent, EV_JUMP, 0 ); + //FIXME: if have force jump, do this? or specify sound in the event data? + //cent->gent->client->ps.forceJumpZStart = cent->gent->client->ps.origin[2];//so we don't take damage if we land at same height + //G_SoundOnEnt( cent->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + } + } + break; + */ + default: + return; + break; + } +} + +/* +void CG_PlayerAnimEvents( int animFileIndex, int eventFileIndex, qboolean torso, int oldFrame, int frame, const vec3_t org, int entNum ) + +play any keyframed sounds - only when start a new frame +This func is called once for legs and once for torso +*/ +void CG_PlayerAnimEvents( int animFileIndex, int eventFileIndex, qboolean torso, int oldFrame, int frame, int entNum ) +{ + int i; + int firstFrame = 0, lastFrame = 0; + qboolean doEvent = qfalse, inSameAnim = qfalse, loopAnim = qfalse, match = qfalse, animBackward = qfalse; + animevent_t *animEvents = NULL; + + if ( torso ) + { + animEvents = bgAllEvents[eventFileIndex].torsoAnimEvents; + } + else + { + animEvents = bgAllEvents[eventFileIndex].legsAnimEvents; + } + if ( fabs((float)(oldFrame-frame)) > 1 ) + {//given a range, see if keyFrame falls in that range + int oldAnim, anim; + if ( torso ) + { + /* + if ( cg_reliableAnimSounds.integer > 1 ) + {//more precise, slower + oldAnim = PM_TorsoAnimForFrame( &g_entities[entNum], oldFrame ); + anim = PM_TorsoAnimForFrame( &g_entities[entNum], frame ); + } + else + */ + {//less precise, but faster + oldAnim = cg_entities[entNum].currentState.torsoAnim; + anim = cg_entities[entNum].nextState.torsoAnim; + } + } + else + { + /* + if ( cg_reliableAnimSounds.integer > 1 ) + {//more precise, slower + oldAnim = PM_LegsAnimForFrame( &g_entities[entNum], oldFrame ); + anim = PM_TorsoAnimForFrame( &g_entities[entNum], frame ); + } + else + */ + {//less precise, but faster + oldAnim = cg_entities[entNum].currentState.legsAnim; + anim = cg_entities[entNum].nextState.legsAnim; + } + } + if ( anim != oldAnim ) + {//not in same anim + inSameAnim = qfalse; + //FIXME: we *could* see if the oldFrame was *just about* to play the keyframed sound... + } + else + {//still in same anim, check for looping anim + animation_t *animation; + + inSameAnim = qtrue; + animation = &bgAllAnims[animFileIndex].anims[anim]; + animBackward = (animation->frameLerp<0); + if ( animation->loopFrames != -1 ) + {//a looping anim! + loopAnim = qtrue; + firstFrame = animation->firstFrame; + lastFrame = animation->firstFrame+animation->numFrames; + } + } + } + + // Check for anim sound + for (i=0;i 1 /*&& cg_reliableAnimSounds.integer*/ ) + {//given a range, see if keyFrame falls in that range + if ( inSameAnim ) + {//if changed anims altogether, sorry, the sound is lost + if ( fabs((float)(oldFrame-animEvents[i].keyFrame)) <= 3 + || fabs((float)(frame-animEvents[i].keyFrame)) <= 3 ) + {//must be at least close to the keyframe + if ( animBackward ) + {//animation plays backwards + if ( oldFrame > animEvents[i].keyFrame && frame < animEvents[i].keyFrame ) + {//old to new passed through keyframe + match = qtrue; + } + else if ( loopAnim ) + {//hmm, didn't pass through it linearally, see if we looped + if ( animEvents[i].keyFrame >= firstFrame && animEvents[i].keyFrame < lastFrame ) + {//keyframe is in this anim + if ( oldFrame > animEvents[i].keyFrame + && frame > oldFrame ) + {//old to new passed through keyframe + match = qtrue; + } + } + } + } + else + {//anim plays forwards + if ( oldFrame < animEvents[i].keyFrame && frame > animEvents[i].keyFrame ) + {//old to new passed through keyframe + match = qtrue; + } + else if ( loopAnim ) + {//hmm, didn't pass through it linearally, see if we looped + if ( animEvents[i].keyFrame >= firstFrame && animEvents[i].keyFrame < lastFrame ) + {//keyframe is in this anim + if ( oldFrame < animEvents[i].keyFrame + && frame < oldFrame ) + {//old to new passed through keyframe + match = qtrue; + } + } + } + } + } + } + } + if ( match ) + { + switch ( animEvents[i].eventType ) + { + case AEV_SOUND: + case AEV_SOUNDCHAN: + // Determine probability of playing sound + if (!animEvents[i].eventData[AED_SOUND_PROBABILITY]) // 100% + { + doEvent = qtrue; + } + else if (animEvents[i].eventData[AED_SOUND_PROBABILITY] > Q_irand(0, 99) ) + { + doEvent = qtrue; + } + break; + case AEV_FOOTSTEP: + // Determine probability of playing sound + if (!animEvents[i].eventData[AED_FOOTSTEP_PROBABILITY]) // 100% + { + doEvent = qtrue; + } + else if (animEvents[i].eventData[AED_FOOTSTEP_PROBABILITY] > Q_irand(0, 99) ) + { + doEvent = qtrue; + } + break; + case AEV_EFFECT: + // Determine probability of playing sound + if (!animEvents[i].eventData[AED_EFFECT_PROBABILITY]) // 100% + { + doEvent = qtrue; + } + else if (animEvents[i].eventData[AED_EFFECT_PROBABILITY] > Q_irand(0, 99) ) + { + doEvent = qtrue; + } + break; + case AEV_FIRE: + // Determine probability of playing sound + if (!animEvents[i].eventData[AED_FIRE_PROBABILITY]) // 100% + { + doEvent = qtrue; + } + else if (animEvents[i].eventData[AED_FIRE_PROBABILITY] > Q_irand(0, 99) ) + { + doEvent = qtrue; + } + break; + case AEV_MOVE: + doEvent = qtrue; + break; + default: + //doEvent = qfalse;//implicit + break; + } + // do event + if ( doEvent ) + { + CG_PlayerAnimEventDo( &cg_entities[entNum], &animEvents[i] ); + } + } + } +} + +void CG_TriggerAnimSounds( centity_t *cent ) +{ //this also sets the lerp frames, so I suggest you keep calling it regardless of if you want anim sounds. + int curFrame = 0; + float currentFrame = 0; + int sFileIndex; + + assert(cent->localAnimIndex >= 0); + + sFileIndex = cent->eventAnimIndex; + + if (trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg->time, ¤tFrame, cgs.gameModels, 0)) + { + // the above may have failed, not sure what to do about it, current frame will be zero in that case + curFrame = floor( currentFrame ); + } + if ( curFrame != cent->pe.legs.frame ) + { + CG_PlayerAnimEvents( cent->localAnimIndex, sFileIndex, qfalse, cent->pe.legs.frame, curFrame, cent->currentState.number ); + } + cent->pe.legs.oldFrame = cent->pe.torso.frame; + cent->pe.legs.frame = curFrame; + + if (cent->noLumbar) + { //probably a droid or something. + cent->pe.torso.oldFrame = cent->pe.legs.oldFrame; + cent->pe.torso.frame = cent->pe.legs.frame; + return; + } + + if (trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg->time, ¤tFrame, cgs.gameModels, 0)) + { + curFrame = floor( currentFrame ); + } + if ( curFrame != cent->pe.torso.frame ) + { + CG_PlayerAnimEvents( cent->localAnimIndex, sFileIndex, qtrue, cent->pe.torso.frame, curFrame, cent->currentState.number ); + } + cent->pe.torso.oldFrame = cent->pe.torso.frame; + cent->pe.torso.frame = curFrame; + cent->pe.torso.backlerp = 1.0f - (currentFrame - (float)curFrame); +} + + +static qboolean CG_FirstAnimFrame(lerpFrame_t *lf, qboolean torsoOnly, float speedScale); + +qboolean CG_InRoll( centity_t *cent ) +{ + switch ( (cent->currentState.legsAnim) ) + { + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + if ( cent->pe.legs.animationTime > cg->time ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean CG_InRollAnim( centity_t *cent ) +{ + switch ( (cent->currentState.legsAnim) ) + { + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + return qtrue; + } + return qfalse; +} + +/* +=============== +CG_SetLerpFrameAnimation +=============== +*/ +#include "../namespace_begin.h" +qboolean BG_SaberStanceAnim( int anim ); +qboolean PM_RunningAnim( int anim ); +#include "../namespace_end.h" +static void CG_SetLerpFrameAnimation( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float animSpeedMult, qboolean torsoOnly, qboolean flipState) { + animation_t *anim; + float animSpeed; + int flags=BONE_ANIM_OVERRIDE_FREEZE; + int oldAnim = -1; + int blendTime = 100; + float oldSpeed = lf->animationSpeed; + + if (cent->localAnimIndex > 0) + { //rockettroopers can't have broken arms, nor can anything else but humanoids + ci->brokenLimbs = cent->currentState.brokenLimbs; + } + + oldAnim = lf->animationNumber; + + lf->animationNumber = newAnimation; + + if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) { + CG_Error( "Bad animation number: %i", newAnimation ); + } + + anim = &bgAllAnims[cent->localAnimIndex].anims[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + abs(anim->frameLerp); + + if (cent->localAnimIndex > 1 && + anim->firstFrame == 0 && + anim->numFrames == 0) + { //We'll allow this for non-humanoids. + return; + } + + if ( cg_debugAnim.integer && (cg_debugAnim.integer < 0 || cg_debugAnim.integer == cent->currentState.clientNum) ) { + if (lf == ¢->pe.legs) + { + CG_Printf( "%d: %d TORSO Anim: %i, '%s'\n", cg->time, cent->currentState.clientNum, newAnimation, GetStringForID(animTable, newAnimation)); + } + else + { + CG_Printf( "%d: %d LEGS Anim: %i, '%s'\n", cg->time, cent->currentState.clientNum, newAnimation, GetStringForID(animTable, newAnimation)); + } + } + + if (cent->ghoul2) + { + qboolean resumeFrame = qfalse; + int beginFrame = -1; + int firstFrame; + int lastFrame; +#if 0 //disabled for now + float unused; +#endif + + animSpeed = 50.0f / anim->frameLerp; + if (lf->animation->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (animSpeed < 0) + { + lastFrame = anim->firstFrame; + firstFrame = anim->firstFrame + anim->numFrames; + } + else + { + firstFrame = anim->firstFrame; + lastFrame = anim->firstFrame + anim->numFrames; + } + + if (cg_animBlend.integer) + { + flags |= BONE_ANIM_BLEND; + } + + if (BG_InDeathAnim(newAnimation)) + { + flags &= ~BONE_ANIM_BLEND; + } + else if ( oldAnim != -1 && + BG_InDeathAnim(oldAnim)) + { + flags &= ~BONE_ANIM_BLEND; + } + + if (flags & BONE_ANIM_BLEND) + { + if (BG_FlippingAnim(newAnimation)) + { + blendTime = 200; + } + else if ( oldAnim != -1 && + (BG_FlippingAnim(oldAnim)) ) + { + blendTime = 200; + } + } + + animSpeed *= animSpeedMult; + + BG_SaberStartTransAnim(cent->currentState.fireflag, newAnimation, &animSpeed, cent->currentState.brokenLimbs); + + if (torsoOnly) + { + if (lf->animationTorsoSpeed != animSpeedMult && newAnimation == oldAnim && + flipState == lf->lastFlip) + { //same animation, but changing speed, so we will want to resume off the frame we're on. + resumeFrame = qtrue; + } + lf->animationTorsoSpeed = animSpeedMult; + } + else + { + if (lf->animationSpeed != animSpeedMult && newAnimation == oldAnim && + flipState == lf->lastFlip) + { //same animation, but changing speed, so we will want to resume off the frame we're on. + resumeFrame = qtrue; + } + lf->animationSpeed = animSpeedMult; + } + + //vehicles may have torso etc but we only want to animate the root bone + if ( cent->currentState.NPC_class == CLASS_VEHICLE ) + { + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", firstFrame, lastFrame, flags, animSpeed,cg->time, beginFrame, blendTime); + return; + } + + if (torsoOnly && !cent->noLumbar) + { //rww - The guesswork based on the lerp frame figures is usually BS, so I've resorted to a call to get the frame of the bone directly. + float GBAcFrame = 0; + if (resumeFrame) + { //we already checked, and this is the same anim, same flip state, but different speed, so we want to resume with the new speed off of the same frame. + trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg->time, &GBAcFrame, NULL, 0); + beginFrame = GBAcFrame; + } + + //even if resuming, also be sure to check if we are running the same frame on the legs. If so, we want to use their frame no matter what. + trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg->time, &GBAcFrame, NULL, 0); + + if ((cent->currentState.torsoAnim) == (cent->currentState.legsAnim) && GBAcFrame >= anim->firstFrame && GBAcFrame <= (anim->firstFrame + anim->numFrames)) + { //if the legs are already running this anim, pick up on the exact same frame to avoid the "wobbly spine" problem. + beginFrame = GBAcFrame; + } + + if (firstFrame > lastFrame || ci->torsoAnim == newAnimation) + { //don't resume on backwards playing animations.. I guess. + beginFrame = -1; + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", firstFrame, lastFrame, flags, animSpeed,cg->time, beginFrame, blendTime); + + // Update the torso frame with the new animation + cent->pe.torso.frame = firstFrame; + + if (ci) + { + ci->torsoAnim = newAnimation; + } + } + else + { + if (resumeFrame) + { //we already checked, and this is the same anim, same flip state, but different speed, so we want to resume with the new speed off of the same frame. + float GBAcFrame = 0; + trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg->time, &GBAcFrame, NULL, 0); + beginFrame = GBAcFrame; + } + + if ((beginFrame < firstFrame) || (beginFrame > lastFrame)) + { //out of range, don't use it then. + beginFrame = -1; + } + + if (cent->currentState.torsoAnim == cent->currentState.legsAnim && + (ci->legsAnim != newAnimation || oldSpeed != animSpeed)) + { //alright, we are starting an anim on the legs, and that same anim is already playing on the toro, so pick up the frame. + float GBAcFrame = 0; + int oldBeginFrame = beginFrame; + + trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg->time, &GBAcFrame, NULL, 0); + beginFrame = GBAcFrame; + if ((beginFrame < firstFrame) || (beginFrame > lastFrame)) + { //out of range, don't use it then. + beginFrame = oldBeginFrame; + } + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", firstFrame, lastFrame, flags, animSpeed, cg->time, beginFrame, blendTime); + + if (ci) + { + ci->legsAnim = newAnimation; + } + } + + if (cent->localAnimIndex <= 1 && (cent->currentState.torsoAnim) == newAnimation && !cent->noLumbar) + { //make sure we're humanoid before we access the motion bone + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", firstFrame, lastFrame, flags, animSpeed, cg->time, beginFrame, blendTime); + } + +#if 0 //disabled for now + if (cent->localAnimIndex <= 1 && cent->currentState.brokenLimbs && + (cent->currentState.brokenLimbs & (1 << BROKENLIMB_LARM))) + { //broken left arm + char *brokenBone = "lhumerus"; + animation_t *armAnim; + int armFirstFrame; + int armLastFrame; + int armFlags = 0; + float armAnimSpeed; + + armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_DEAD21 ]; + ci->brokenLimbs = cent->currentState.brokenLimbs; + + armFirstFrame = armAnim->firstFrame; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = BONE_ANIM_OVERRIDE_LOOP; + + if (cg_animBlend.integer) + { + armFlags |= BONE_ANIM_BLEND; + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg->time, -1, blendTime); + } + else if (cent->localAnimIndex <= 1 && cent->currentState.brokenLimbs && + (cent->currentState.brokenLimbs & (1 << BROKENLIMB_RARM))) + { //broken right arm + char *brokenBone = "rhumerus"; + char *supportBone = "lhumerus"; + + ci->brokenLimbs = cent->currentState.brokenLimbs; + + //Only put the arm in a broken pose if the anim is such that we + //want to allow it. + if ((//cent->currentState.weapon == WP_MELEE || + cent->currentState.weapon != WP_SABER || + BG_SaberStanceAnim(newAnimation) || + PM_RunningAnim(newAnimation)) && + cent->currentState.torsoAnim == newAnimation && + (!ci->saber[1].model[0] || cent->currentState.weapon != WP_SABER)) + { + int armFirstFrame; + int armLastFrame; + int armFlags = 0; + float armAnimSpeed; + animation_t *armAnim; + + if (cent->currentState.weapon == WP_MELEE || + cent->currentState.weapon == WP_SABER || + cent->currentState.weapon == WP_BRYAR_PISTOL) + { //don't affect this arm if holding a gun, just make the other arm support it + armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_ATTACK2 ]; + + //armFirstFrame = armAnim->firstFrame; + armFirstFrame = armAnim->firstFrame+armAnim->numFrames; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = BONE_ANIM_OVERRIDE_LOOP; + + /* + if (cg_animBlend.integer) + { + armFlags |= BONE_ANIM_BLEND; + } + */ + //No blend on the broken arm + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg->time, -1, 0); + } + else + { //we want to keep the broken bone updated for some cases + trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, firstFrame, lastFrame, flags, animSpeed, cg->time, beginFrame, blendTime); + } + + if (newAnimation != BOTH_MELEE1 && + newAnimation != BOTH_MELEE2 && + (newAnimation == TORSO_WEAPONREADY2 || newAnimation == BOTH_ATTACK2 || cent->currentState.weapon < WP_BRYAR_PISTOL)) + { + //Now set the left arm to "support" the right one + armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_STAND2 ]; + armFirstFrame = armAnim->firstFrame; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = BONE_ANIM_OVERRIDE_LOOP; + + if (cg_animBlend.integer) + { + armFlags |= BONE_ANIM_BLEND; + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg->time, -1, 150); + } + else + { //we want to keep the support bone updated for some cases + trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, firstFrame, lastFrame, flags, animSpeed, cg->time, beginFrame, blendTime); + } + } + else if (cent->currentState.torsoAnim == newAnimation) + { //otherwise, keep it set to the same as the torso + trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, firstFrame, lastFrame, flags, animSpeed, cg->time, beginFrame, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, firstFrame, lastFrame, flags, animSpeed, cg->time, beginFrame, blendTime); + } + } + else if (ci && + (ci->brokenLimbs || + trap_G2API_GetBoneFrame(cent->ghoul2, "lhumerus", cg->time, &unused, cgs.gameModels, 0) || + trap_G2API_GetBoneFrame(cent->ghoul2, "rhumerus", cg->time, &unused, cgs.gameModels, 0))) + //rwwFIXMEFIXME: brokenLimbs gets stomped sometimes, but it shouldn't. + { //remove the bone now so it can be set again + char *brokenBone = NULL; + int broken = 0; + + //Warning: Don't remove bones that you've added as bolts unless you want to invalidate your bolt index + //(well, in theory, I haven't actually run into the problem) + if (ci->brokenLimbs & (1<brokenLimbs & (1<ghoul2, 0, "lhumerus", 0, 1, 0, 0, cg->time, -1, 0); + if (!trap_G2API_RemoveBone(cent->ghoul2, "lhumerus", 0)) + { + assert(0); + Com_Printf("WARNING: Failed to remove lhumerus\n"); + } + } + + if (!brokenBone) + { + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lhumerus", 0, 1, 0, 0, cg->time, -1, 0); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "rhumerus", 0, 1, 0, 0, cg->time, -1, 0); + trap_G2API_RemoveBone(cent->ghoul2, "lhumerus", 0); + trap_G2API_RemoveBone(cent->ghoul2, "rhumerus", 0); + ci->brokenLimbs = 0; + } + else + { + //Set the flags and stuff to 0, so that the remove will succeed + trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, 0, 1, 0, 0, cg->time, -1, 0); + + //Now remove it + if (!trap_G2API_RemoveBone(cent->ghoul2, brokenBone, 0)) + { + assert(0); + Com_Printf("WARNING: Failed to remove %s\n", brokenBone); + } + ci->brokenLimbs &= ~broken; + } + } +#endif + } +} + + +/* +=============== +CG_FirstAnimFrame + +Returns true if the lerpframe is on its first frame of animation. +Otherwise false. + +This is used to scale an animation into higher-speed without restarting +the animation before it completes at normal speed, in the case of a looping +animation (such as the leg running anim). +=============== +*/ +static qboolean CG_FirstAnimFrame(lerpFrame_t *lf, qboolean torsoOnly, float speedScale) +{ + if (torsoOnly) + { + if (lf->animationTorsoSpeed == speedScale) + { + return qfalse; + } + } + else + { + if (lf->animationSpeed == speedScale) + { + return qfalse; + } + } + + //I don't care where it is in the anim now, I am going to pick up from the same bone frame. +/* + if (lf->animation->numFrames < 2) + { + return qtrue; + } + + if (lf->animation->firstFrame == lf->frame) + { + return qtrue; + } +*/ + + return qtrue; +} + +/* +=============== +CG_RunLerpFrame + +Sets cg->snap, cg->oldFrame, and cg->backlerp +cg->time should be between oldFrameTime and frameTime after exit +=============== +*/ +static void CG_RunLerpFrame( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, qboolean flipState, int newAnimation, float speedScale, qboolean torsoOnly) +{ + // debugging tool to get no animations + if ( cg_animSpeed.integer == 0 ) { + lf->oldFrame = lf->frame = lf->backlerp = 0; + return; + } + + // see if the animation sequence is switching + if (cent->currentState.forceFrame) + { + if (lf->lastForcedFrame != cent->currentState.forceFrame) + { + int flags = BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND; + float animSpeed = 1.0f; + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg->time, -1, 150); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg->time, -1, 150); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg->time, -1, 150); + } + + lf->lastForcedFrame = cent->currentState.forceFrame; + + lf->animationNumber = 0; + } + else + { + lf->lastForcedFrame = -1; + + if ( (newAnimation != lf->animationNumber || + cent->currentState.brokenLimbs != ci->brokenLimbs || + lf->lastFlip != flipState || + !lf->animation || + //Sometimes ghoul2 ends up playing an animation it + //shouldn't and this function doesn't realize it. This + //next check makes sure that the currently playing + //frame is somewhat within the boundaries of the + //current animation. + lf->frame < lf->animation->firstFrame) || + (CG_FirstAnimFrame(lf, torsoOnly, speedScale)) ) + { + CG_SetLerpFrameAnimation( cent, ci, lf, newAnimation, speedScale, torsoOnly, flipState); + } + } + + lf->lastFlip = flipState; + + if ( lf->frameTime > cg->time + 200 ) { + lf->frameTime = cg->time; + } + + if ( lf->oldFrameTime > cg->time ) { + lf->oldFrameTime = cg->time; + } + + // calculate current lerp value + if ( lf->frameTime == lf->oldFrameTime ) { + lf->backlerp = 0; + } else { + lf->backlerp = 1.0 - (float)( cg->time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + } +} + + +/* +=============== +CG_ClearLerpFrame +=============== +*/ +static void CG_ClearLerpFrame( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int animationNumber, qboolean torsoOnly) { + lf->frameTime = lf->oldFrameTime = cg->time; + CG_SetLerpFrameAnimation( cent, ci, lf, animationNumber, 1, torsoOnly, qfalse ); + + if ( lf->animation->frameLerp < 0 ) + {//Plays backwards + lf->oldFrame = lf->frame = (lf->animation->firstFrame + lf->animation->numFrames); + } + else + { + lf->oldFrame = lf->frame = lf->animation->firstFrame; + } +} + + +/* +=============== +CG_PlayerAnimation +=============== +*/ +#include "../namespace_begin.h" +qboolean PM_WalkingAnim( int anim ); +#include "../namespace_end.h" + +static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, + int *torsoOld, int *torso, float *torsoBackLerp ) { + clientInfo_t *ci; + int clientNum; + float speedScale; + + clientNum = cent->currentState.clientNum; + + if ( cg_noPlayerAnims.integer ) { + *legsOld = *legs = *torsoOld = *torso = 0; + return; + } + + if (!PM_RunningAnim(cent->currentState.legsAnim) && + !PM_WalkingAnim(cent->currentState.legsAnim)) + { //if legs are not in a walking/running anim then just animate at standard speed + speedScale = 1.0f; + } + else if (cent->currentState.forcePowersActive & (1 << FP_RAGE)) + { + speedScale = 1.3f; + } + else if (cent->currentState.forcePowersActive & (1 << FP_SPEED)) + { + speedScale = 1.7f; + } + else + { + speedScale = 1.0f; + } + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + assert(ci); + } + else + { + ci = &cgs.clientinfo[ clientNum ]; + } + + CG_RunLerpFrame( cent, ci, ¢->pe.legs, cent->currentState.legsFlip, cent->currentState.legsAnim, speedScale, qfalse); + + if (!(cent->currentState.forcePowersActive & (1 << FP_RAGE))) + { //don't affect torso anim speed unless raged + speedScale = 1.0f; + } + else + { + speedScale = 1.7f; + } + + *legsOld = cent->pe.legs.oldFrame; + *legs = cent->pe.legs.frame; + *legsBackLerp = cent->pe.legs.backlerp; + + // If this is not a vehicle, you may lerm the frame (since vehicles never have a torso anim). -AReis + if ( cent->currentState.NPC_class != CLASS_VEHICLE ) + { + CG_RunLerpFrame( cent, ci, ¢->pe.torso, cent->currentState.torsoFlip, cent->currentState.torsoAnim, speedScale, qtrue ); + + *torsoOld = cent->pe.torso.oldFrame; + *torso = cent->pe.torso.frame; + *torsoBackLerp = cent->pe.torso.backlerp; + } +} + + + + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +#if 0 +typedef struct boneAngleParms_s { + void *ghoul2; + int modelIndex; + char *boneName; + vec3_t angles; + int flags; + int up; + int right; + int forward; + qhandle_t *modelList; + int blendTime; + int currentTime; + + qboolean refreshSet; +} boneAngleParms_t; + +boneAngleParms_t cgBoneAnglePostSet; +#endif + +void CG_G2SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ) +{ //we want to hold off on setting the bone angles until the end of the frame, because every time we set + //them the entire skeleton has to be reconstructed. +#if 0 + //This function should ONLY be called from CG_Player() or a function that is called only within CG_Player(). + //At the end of the frame we will check to use this information to call SetBoneAngles + memset(&cgBoneAnglePostSet, 0, sizeof(cgBoneAnglePostSet)); + cgBoneAnglePostSet.ghoul2 = ghoul2; + cgBoneAnglePostSet.modelIndex = modelIndex; + cgBoneAnglePostSet.boneName = (char *)boneName; + + cgBoneAnglePostSet.angles[0] = angles[0]; + cgBoneAnglePostSet.angles[1] = angles[1]; + cgBoneAnglePostSet.angles[2] = angles[2]; + + cgBoneAnglePostSet.flags = flags; + cgBoneAnglePostSet.up = up; + cgBoneAnglePostSet.right = right; + cgBoneAnglePostSet.forward = forward; + cgBoneAnglePostSet.modelList = modelList; + cgBoneAnglePostSet.blendTime = blendTime; + cgBoneAnglePostSet.currentTime = currentTime; + + cgBoneAnglePostSet.refreshSet = qtrue; +#endif + //We don't want to go with the delayed approach, we want out bolt points and everything to be updated in realtime. + //We'll just take the reconstructs and live with them. + trap_G2API_SetBoneAngles(ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList, + blendTime, currentTime); +} + +/* +================ +CG_Rag_Trace + +Variant on CG_Trace. Doesn't trace for ents because ragdoll engine trace code has no entity +trace access. Maybe correct this sometime, so bmodel col. at least works with ragdoll. +But I don't want to slow it down.. +================ +*/ +void CG_Rag_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) { + trap_CM_BoxTrace ( result, start, end, mins, maxs, 0, mask); + result->entityNum = result->fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; +} + +//#define _RAG_BOLT_TESTING + +#ifdef _RAG_BOLT_TESTING +void CG_TempTestFunction(centity_t *cent, vec3_t forcedAngles) +{ + mdxaBone_t boltMatrix; + vec3_t tAngles; + vec3_t bOrg; + vec3_t bDir; + vec3_t uOrg; + + VectorSet(tAngles, 0, cent->lerpAngles[YAW], 0); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, tAngles, cent->lerpOrigin, + cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, bOrg); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, bDir); + + VectorMA(bOrg, 40, bDir, uOrg); + + CG_TestLine(bOrg, uOrg, 50, 0x0000ff, 1); + + cent->turAngles[YAW] = forcedAngles[YAW]; +} +#endif + +//list of valid ragdoll effectors +static const char *cg_effectorStringTable[] = +{ //commented out the ones I don't want dragging to affect +// "thoracic", +// "rhand", + "lhand", + "rtibia", + "ltibia", + "rtalus", + "ltalus", +// "rradiusX", + "lradiusX", + "rfemurX", + "lfemurX", +// "ceyebrow", + NULL //always terminate +}; + +//we want to see which way the pelvis is facing to get a relatively oriented base settling frame +//this is to avoid the arms stretching in opposite directions on the body trying to reach the base +//pose if the pelvis is flipped opposite of the base pose or something -rww +static int CG_RagAnimForPositioning(centity_t *cent) +{ + int bolt; + vec3_t dir; + mdxaBone_t matrix; + + assert(cent->ghoul2); + bolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis"); + assert(bolt > -1); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &matrix, cent->turAngles, cent->lerpOrigin, + cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Z, dir); + + if (dir[2] > 0.0f) + { //facing up + return BOTH_DEADFLOP2; + } + else + { //facing down + return BOTH_DEADFLOP1; + } +} + +//rww - cgame interface for the ragdoll stuff. +//Returns qtrue if the entity is now in a ragdoll state, otherwise qfalse. +qboolean CG_RagDoll(centity_t *cent, vec3_t forcedAngles) +{ + vec3_t usedOrg; + qboolean inSomething = qfalse; + int ragAnim;//BOTH_DEAD1; //BOTH_DEATH1; + + if (!cg_ragDoll.integer) + { + return qfalse; + } + + if (cent->localAnimIndex) + { //don't rag non-humanoids + return qfalse; + } + + VectorCopy(cent->lerpOrigin, usedOrg); + + if (!cent->isRagging) + { //If we're not in a ragdoll state, perform the checks. + if (cent->currentState.eFlags & EF_RAG) + { //want to go into it no matter what then + inSomething = qtrue; + } + else if (cent->currentState.groundEntityNum == ENTITYNUM_NONE) + { + vec3_t cVel; + + VectorCopy(cent->currentState.pos.trDelta, cVel); + + if (VectorNormalize(cVel) > 400) + { //if he's flying through the air at a good enough speed, switch into ragdoll + inSomething = qtrue; + } + } + + if (cent->currentState.eType == ET_BODY) + { //just rag bodies immediately if their own was ragging on respawn + if (cent->ownerRagging) + { + cent->isRagging = qtrue; + return qfalse; + } + } + + if (cg_ragDoll.integer > 1) + { + inSomething = qtrue; + } + + if (!inSomething) + { + int anim = (cent->currentState.legsAnim); + int dur = (bgAllAnims[cent->localAnimIndex].anims[anim].numFrames-1) * fabs((float)(bgAllAnims[cent->localAnimIndex].anims[anim].frameLerp)); + int i = 0; + int boltChecks[5]; + vec3_t boltPoints[5]; + vec3_t trStart, trEnd; + vec3_t tAng; + qboolean deathDone = qfalse; + trace_t tr; + mdxaBone_t boltMatrix; + + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + if (cent->pe.legs.animationTime > 50 && (cg->time - cent->pe.legs.animationTime) > dur) + { //Looks like the death anim is done playing + deathDone = qtrue; + } + + if (deathDone) + { //only trace from the hands if the death anim is already done. + boltChecks[0] = trap_G2API_AddBolt(cent->ghoul2, 0, "rhand"); + boltChecks[1] = trap_G2API_AddBolt(cent->ghoul2, 0, "lhand"); + } + else + { //otherwise start the trace loop at the cranium. + i = 2; + } + boltChecks[2] = trap_G2API_AddBolt(cent->ghoul2, 0, "cranium"); + //boltChecks[3] = trap_G2API_AddBolt(cent->ghoul2, 0, "rtarsal"); + //boltChecks[4] = trap_G2API_AddBolt(cent->ghoul2, 0, "ltarsal"); + boltChecks[3] = trap_G2API_AddBolt(cent->ghoul2, 0, "rtalus"); + boltChecks[4] = trap_G2API_AddBolt(cent->ghoul2, 0, "ltalus"); + + //This may seem bad, but since we have a bone cache now it should manage to not be too disgustingly slow. + //Do the head first, because the hands reference it anyway. + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[2], &boltMatrix, tAng, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[2]); + + while (i < 5) + { + if (i < 2) + { //when doing hands, trace to the head instead of origin + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[i], &boltMatrix, tAng, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[i]); + VectorCopy(boltPoints[i], trStart); + VectorCopy(boltPoints[2], trEnd); + } + else + { + if (i > 2) + { //2 is the head, which already has the bolt point. + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[i], &boltMatrix, tAng, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[i]); + } + VectorCopy(boltPoints[i], trStart); + VectorCopy(cent->lerpOrigin, trEnd); + } + + //Now that we have all that sorted out, trace between the two points we desire. + CG_Rag_Trace(&tr, trStart, NULL, NULL, trEnd, cent->currentState.number, MASK_SOLID); + + if (tr.fraction != 1.0 || tr.startsolid || tr.allsolid) + { //Hit something or start in solid, so flag it and break. + //This is a slight hack, but if we aren't done with the death anim, we don't really want to + //go into ragdoll unless our body has a relatively "flat" pitch. +#if 0 + vec3_t vSub; + + //Check the pitch from the head to the right foot (should be reasonable) + VectorSubtract(boltPoints[2], boltPoints[3], vSub); + VectorNormalize(vSub); + vectoangles(vSub, vSub); + + if (deathDone || (vSub[PITCH] < 50 && vSub[PITCH] > -50)) + { + inSomething = qtrue; + } +#else + inSomething = qtrue; +#endif + break; + } + + i++; + } + } + + if (inSomething) + { + cent->isRagging = qtrue; +#if 0 + VectorClear(cent->lerpOriginOffset); +#endif + } + } + + if (cent->isRagging) + { //We're in a ragdoll state, so make the call to keep our positions updated and whatnot. + sharedRagDollParams_t tParms; + sharedRagDollUpdateParams_t tuParms; + + ragAnim = CG_RagAnimForPositioning(cent); + + if (cent->ikStatus) + { //ik must be reset before ragdoll is started, or you'll get some interesting results. + trap_G2API_SetBoneIKState(cent->ghoul2, cg->time, NULL, IKS_NONE, NULL); + cent->ikStatus = qfalse; + } + + //these will be used as "base" frames for the ragoll settling. + tParms.startFrame = bgAllAnims[cent->localAnimIndex].anims[ragAnim].firstFrame;// + bgAllAnims[cent->localAnimIndex].anims[ragAnim].numFrames; + tParms.endFrame = bgAllAnims[cent->localAnimIndex].anims[ragAnim].firstFrame + bgAllAnims[cent->localAnimIndex].anims[ragAnim].numFrames; +#if 0 + { + float animSpeed = 0; + int blendTime = 600; + int flags = 0;//BONE_ANIM_OVERRIDE_FREEZE; + + if (bgAllAnims[cent->localAnimIndex].anims[ragAnim].loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + /* + if (cg_animBlend.integer) + { + flags |= BONE_ANIM_BLEND; + } + */ + + animSpeed = 50.0f / bgAllAnims[cent->localAnimIndex].anims[ragAnim].frameLerp; + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", tParms.startFrame, tParms.endFrame, flags, animSpeed,cg->time, -1, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", tParms.startFrame, tParms.endFrame, flags, animSpeed, cg->time, -1, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", tParms.startFrame, tParms.endFrame, flags, animSpeed, cg->time, -1, blendTime); + } +#elif 1 //with my new method of doing things I want it to continue the anim + { + float currentFrame; + int startFrame, endFrame; + int flags; + float animSpeed; + + if (trap_G2API_GetBoneAnim(cent->ghoul2, "model_root", cg->time, ¤tFrame, &startFrame, &endFrame, &flags, &animSpeed, cgs.gameModels, 0)) + { //lock the anim on the current frame. + int blendTime = 500; + animation_t *curAnim = &bgAllAnims[cent->localAnimIndex].anims[cent->currentState.legsAnim]; + + if (currentFrame >= (curAnim->firstFrame + curAnim->numFrames-1)) + { //this is sort of silly but it works for now. + currentFrame = (curAnim->firstFrame + curAnim->numFrames-2); + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", currentFrame, currentFrame+1, flags, animSpeed,cg->time, currentFrame, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", currentFrame, currentFrame+1, flags, animSpeed, cg->time, currentFrame, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", currentFrame, currentFrame+1, flags, animSpeed, cg->time, currentFrame, blendTime); + } + } +#endif + CG_G2SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg->time); + CG_G2SetBoneAngles(cent->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg->time); + CG_G2SetBoneAngles(cent->ghoul2, 0, "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg->time); + CG_G2SetBoneAngles(cent->ghoul2, 0, "cervical", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg->time); + + VectorCopy(forcedAngles, tParms.angles); + VectorCopy(usedOrg, tParms.position); + VectorCopy(cent->modelScale, tParms.scale); + tParms.me = cent->currentState.number; + + tParms.collisionType = 1; + tParms.RagPhase = RP_DEATH_COLLISION; + tParms.fShotStrength = 4; + + trap_G2API_SetRagDoll(cent->ghoul2, &tParms); + + VectorCopy(forcedAngles, tuParms.angles); + VectorCopy(usedOrg, tuParms.position); + VectorCopy(cent->modelScale, tuParms.scale); + tuParms.me = cent->currentState.number; + tuParms.settleFrame = tParms.endFrame-1; + + if (cent->currentState.groundEntityNum != ENTITYNUM_NONE) + { + VectorClear(tuParms.velocity); + } + else + { + VectorScale(cent->currentState.pos.trDelta, 2.0f, tuParms.velocity); + } + + trap_G2API_AnimateG2Models(cent->ghoul2, cg->time, &tuParms); + + //So if we try to get a bolt point it's still correct + cent->turAngles[YAW] = + cent->lerpAngles[YAW] = + cent->pe.torso.yawAngle = + cent->pe.legs.yawAngle = forcedAngles[YAW]; + + if (cent->currentState.ragAttach && + (cent->currentState.eType != ET_NPC || cent->currentState.NPC_class != CLASS_VEHICLE)) + { + centity_t *grabEnt; + + if (cent->currentState.ragAttach == ENTITYNUM_NONE) + { //switch cl 0 and entitynum_none, so we can operate on the "if non-0" concept + grabEnt = &cg_entities[0]; + } + else + { + grabEnt = &cg_entities[cent->currentState.ragAttach]; + } + + if (grabEnt->ghoul2) + { + mdxaBone_t matrix; + vec3_t bOrg; + vec3_t thisHand; + vec3_t hands; + vec3_t pcjMin, pcjMax; + vec3_t pDif; + vec3_t thorPoint; + float difLen; + int thorBolt; + + //Get the person who is holding our hand's hand location + trap_G2API_GetBoltMatrix(grabEnt->ghoul2, 0, 0, &matrix, grabEnt->turAngles, grabEnt->lerpOrigin, + cg->time, cgs.gameModels, grabEnt->modelScale); + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, bOrg); + + //Get our hand's location + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, 0, &matrix, cent->turAngles, cent->lerpOrigin, + cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thisHand); + + //Get the position of the thoracic bone for hinting its velocity later on + thorBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "thoracic"); + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, thorBolt, &matrix, cent->turAngles, cent->lerpOrigin, + cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thorPoint); + + VectorSubtract(bOrg, thisHand, hands); + + if (VectorLength(hands) < 3.0f) + { + trap_G2API_RagForceSolve(cent->ghoul2, qfalse); + } + else + { + trap_G2API_RagForceSolve(cent->ghoul2, qtrue); + } + + //got the hand pos of him, now we want to make our hand go to it + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhand", bOrg); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rradius", bOrg); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rradiusX", bOrg); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerusX", bOrg); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerus", bOrg); + + //Make these two solve quickly so we can update decently + trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rhumerus", 1.5f); + trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rradius", 1.5f); + + //Break the constraints on them I suppose + VectorSet(pcjMin, -999, -999, -999); + VectorSet(pcjMax, 999, 999, 999); + trap_G2API_RagPCJConstraint(cent->ghoul2, "rhumerus", pcjMin, pcjMax); + trap_G2API_RagPCJConstraint(cent->ghoul2, "rradius", pcjMin, pcjMax); + + cent->overridingBones = cg->time + 2000; + + //hit the thoracic velocity to the hand point + VectorSubtract(bOrg, thorPoint, hands); + VectorNormalize(hands); + VectorScale(hands, 2048.0f, hands); + trap_G2API_RagEffectorKick(cent->ghoul2, "thoracic", hands); + trap_G2API_RagEffectorKick(cent->ghoul2, "ceyebrow", hands); + + VectorSubtract(cent->ragLastOrigin, cent->lerpOrigin, pDif); + VectorCopy(cent->lerpOrigin, cent->ragLastOrigin); + + if (cent->ragLastOriginTime >= cg->time && cent->currentState.groundEntityNum != ENTITYNUM_NONE) + { //make sure it's reasonably updated + difLen = VectorLength(pDif); + if (difLen > 0.0f) + { //if we're being dragged, then kick all the bones around a bit + vec3_t dVel; + vec3_t rVel; + int i = 0; + + if (difLen < 12.0f) + { + VectorScale(pDif, 12.0f/difLen, pDif); + difLen = 12.0f; + } + + while (cg_effectorStringTable[i]) + { + VectorCopy(pDif, dVel); + dVel[2] = 0; + + //Factor in a random velocity + VectorSet(rVel, flrand(-0.1f, 0.1f), flrand(-0.1f, 0.1f), flrand(0.1f, 0.5)); + VectorScale(rVel, 8.0f, rVel); + + VectorAdd(dVel, rVel, dVel); + VectorScale(dVel, 10.0f, dVel); + + trap_G2API_RagEffectorKick(cent->ghoul2, cg_effectorStringTable[i], dVel); + +#if 0 + { + mdxaBone_t bm; + vec3_t borg; + vec3_t vorg; + int b = trap_G2API_AddBolt(cent->ghoul2, 0, cg_effectorStringTable[i]); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &bm, cent->turAngles, cent->lerpOrigin, cg->time, + cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&bm, ORIGIN, borg); + + VectorMA(borg, 1.0f, dVel, vorg); + + CG_TestLine(borg, vorg, 50, 0x0000ff, 1); + } +#endif + + i++; + } + } + } + cent->ragLastOriginTime = cg->time + 1000; + } + } + else if (cent->overridingBones) + { //reset things to their normal rag state + vec3_t pcjMin, pcjMax; + vec3_t dVel; + + //got the hand pos of him, now we want to make our hand go to it + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhand", NULL); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rradius", NULL); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rradiusX", NULL); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerusX", NULL); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerus", NULL); + + VectorSet(dVel, 0.0f, 0.0f, -64.0f); + trap_G2API_RagEffectorKick(cent->ghoul2, "rhand", dVel); + + trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rhumerus", 0.0f); + trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rradius", 0.0f); + + VectorSet(pcjMin,-100.0f,-40.0f,-15.0f); + VectorSet(pcjMax,-15.0f,80.0f,15.0f); + trap_G2API_RagPCJConstraint(cent->ghoul2, "rhumerus", pcjMin, pcjMax); + + VectorSet(pcjMin,-25.0f,-20.0f,-20.0f); + VectorSet(pcjMax,90.0f,20.0f,-20.0f); + trap_G2API_RagPCJConstraint(cent->ghoul2, "rradius", pcjMin, pcjMax); + + if (cent->overridingBones < cg->time) + { + trap_G2API_RagForceSolve(cent->ghoul2, qfalse); + cent->overridingBones = 0; + } + else + { + trap_G2API_RagForceSolve(cent->ghoul2, qtrue); + } + } + + return qtrue; + } + + return qfalse; +} + +//set the bone angles of this client entity based on data from the server -rww +void CG_G2ServerBoneAngles(centity_t *cent) +{ + int i = 0; + int bone = cent->currentState.boneIndex1; + int flags, up, right, forward; + vec3_t boneAngles; + + VectorCopy(cent->currentState.boneAngles1, boneAngles); + + while (i < 4) + { //cycle through the 4 bone index values on the entstate + if (bone) + { //if it's non-0 then it could have something in it. + const char *boneName = CG_ConfigString(CS_G2BONES+bone); + + if (boneName && boneName[0]) + { //got the bone, now set the angles from the corresponding entitystate boneangles value. + flags = BONE_ANGLES_POSTMULT; + + //get the orientation out of our bit field + forward = (cent->currentState.boneOrient)&7; //3 bits from bit 0 + right = (cent->currentState.boneOrient>>3)&7; //3 bits from bit 3 + up = (cent->currentState.boneOrient>>6)&7; //3 bits from bit 6 + + trap_G2API_SetBoneAngles(cent->ghoul2, 0, boneName, boneAngles, flags, up, right, forward, cgs.gameModels, 100, cg->time); + } + } + + switch (i) + { + case 0: + bone = cent->currentState.boneIndex2; + VectorCopy(cent->currentState.boneAngles2, boneAngles); + break; +/* + case 1: + bone = cent->currentState.boneIndex3; + VectorCopy(cent->currentState.boneAngles3, boneAngles); + break; + case 2: + bone = cent->currentState.boneIndex4; + VectorCopy(cent->currentState.boneAngles4, boneAngles); + break; +*/ + default: + break; + } + + i++; + } +} + +/* +------------------------- +CG_G2SetHeadBlink +------------------------- +*/ +static void CG_G2SetHeadBlink( centity_t *cent, qboolean bStart ) +{ + vec3_t desiredAngles; + int blendTime = 80; + qboolean bWink = qfalse; + const int hReye = trap_G2API_AddBolt( cent->ghoul2, 0, "reye" ); + const int hLeye = trap_G2API_AddBolt( cent->ghoul2, 0, "leye" ); + + if (hLeye == -1) + { + return; + } + + VectorClear(desiredAngles); + + if (bStart) + { + desiredAngles[YAW] = -50; + if ( random() > 0.95f ) + { + bWink = qtrue; + blendTime /=3; + } + } + trap_G2API_SetBoneAngles( cent->ghoul2, 0, "leye", desiredAngles, + BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg->time ); + + if (hReye == -1) + { + return; + } + + if (!bWink) + { + trap_G2API_SetBoneAngles( cent->ghoul2, 0, "reye", desiredAngles, + BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg->time ); + } +} + +/* +------------------------- +CG_G2SetHeadAnims +------------------------- +*/ +static void CG_G2SetHeadAnim( centity_t *cent, int anim ) +{ + const int blendTime = 50; + const animation_t *animations = bgAllAnims[cent->localAnimIndex].anims; + int animFlags = BONE_ANIM_OVERRIDE ;//| BONE_ANIM_BLEND; + // animSpeed is 1.0 if the frameLerp (ms/frame) is 50 (20 fps). +// float timeScaleMod = (cg_timescale.value&&gent&&gent->s.clientNum==0&&!player_locked&&!MatrixMode&&gent->client->ps.forcePowersActive&(1<ghoul2[gent->playerModel], cent->gent->faceBone, &startFrame, &endFrame); + +// if (!animatingHead || ( animations[anim].firstFrame != startFrame ) )// only set the anim if we aren't going to do the same animation again + { + // gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], cent->gent->faceBone, + // firstFrame, lastFrame, animFlags, animSpeed, cg->time, -1, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "face", firstFrame, lastFrame, animFlags, animSpeed, + cg->time, -1, blendTime); + } +} + +qboolean CG_G2PlayerHeadAnims( centity_t *cent ) +{ + clientInfo_t *ci = NULL; + int anim = -1; + int voiceVolume = 0; + + if(cent->localAnimIndex > 1) + { //only do this for humanoids + return qfalse; + } + + if (cent->noFace) + { // i don't have a face + return qfalse; + } + + if (cent->currentState.number < MAX_CLIENTS) + { + ci = &cgs.clientinfo[cent->currentState.number]; + } + else + { + ci = cent->npcClient; + } + + if (!ci) + { + return qfalse; + } + + if ( cent->currentState.eFlags & EF_DEAD ) + {//Dead people close their eyes and don't make faces! + anim = FACE_DEAD; + ci->facial_blink = -1; + } + else + { + if (!ci->facial_blink) + { // set the timers + ci->facial_blink = cg->time + flrand(4000.0, 8000.0); + ci->facial_frown = cg->time + flrand(6000.0, 10000.0); + ci->facial_aux = cg->time + flrand(6000.0, 10000.0); + } + + //are we blinking? + if (ci->facial_blink < 0) + { // yes, check if we are we done blinking ? + if (-(ci->facial_blink) < cg->time) + { // yes, so reset blink timer + ci->facial_blink = cg->time + flrand(4000.0, 8000.0); + CG_G2SetHeadBlink( cent, qfalse ); //stop the blink + } + } + else // no we aren't blinking + { + if (ci->facial_blink < cg->time)// but should we start ? + { + CG_G2SetHeadBlink( cent, qtrue ); + if (ci->facial_blink == 1) + {//requested to stay shut by SET_FACEEYESCLOSED + ci->facial_blink = -(cg->time + 99999999.0f);// set blink timer + } + else + { + ci->facial_blink = -(cg->time + 300.0f);// set blink timer + } + } + } + + voiceVolume = trap_S_GetVoiceVolume(cent->currentState.number); + + if (voiceVolume > 0) // if we aren't talking, then it will be 0, -1 for talking but paused + { + anim = FACE_TALK1 + voiceVolume -1; + } + else if (voiceVolume == 0) //don't do aux if in a slient part of speech + {//not talking + if (ci->facial_aux < 0) // are we auxing ? + { //yes + if (-(ci->facial_aux) < cg->time)// are we done auxing ? + { // yes, reset aux timer + ci->facial_aux = cg->time + flrand(7000.0, 10000.0); + } + else + { // not yet, so choose aux + anim = FACE_ALERT; + } + } + else // no we aren't auxing + { // but should we start ? + if (ci->facial_aux < cg->time) + {//yes + anim = FACE_ALERT; + // set aux timer + ci->facial_aux = -(cg->time + 2000.0); + } + } + + if (anim != -1) //we we are auxing, see if we should override with a frown + { + if (ci->facial_frown < 0)// are we frowning ? + { // yes, + if (-(ci->facial_frown) < cg->time)//are we done frowning ? + { // yes, reset frown timer + ci->facial_frown = cg->time + flrand(7000.0, 10000.0); + } + else + { // not yet, so choose frown + anim = FACE_FROWN; + } + } + else// no we aren't frowning + { // but should we start ? + if (ci->facial_frown < cg->time) + { + anim = FACE_FROWN; + // set frown timer + ci->facial_frown = -(cg->time + 2000.0); + } + } + } + + }//talking + }//dead + if (anim != -1) + { + CG_G2SetHeadAnim( cent, anim ); + return qtrue; + } + return qfalse; +} + + +static void CG_G2PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t legsAngles) +{ + clientInfo_t *ci; + + //rww - now do ragdoll stuff + if ((cent->currentState.eFlags & EF_DEAD) || (cent->currentState.eFlags & EF_RAG)) + { + vec3_t forcedAngles; + + VectorClear(forcedAngles); + forcedAngles[YAW] = cent->lerpAngles[YAW]; + + if (CG_RagDoll(cent, forcedAngles)) + { //if we managed to go into the rag state, give our ent axis the forced angles and return. + AnglesToAxis( forcedAngles, legs ); + VectorCopy(forcedAngles, legsAngles); + return; + } + } + else if (cent->isRagging) + { + cent->isRagging = qfalse; + trap_G2API_SetRagDoll(cent->ghoul2, NULL); //calling with null parms resets to no ragdoll. + } + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + assert(ci); + } + else + { + ci = &cgs.clientinfo[cent->currentState.number]; + } + + //rww - Quite possibly the most arguments for a function ever. + if (cent->localAnimIndex <= 1) + { //don't do these things on non-humanoids + vec3_t lookAngles; + entityState_t *emplaced = NULL; + + if (cent->currentState.hasLookTarget) + { + VectorSubtract(cg_entities[cent->currentState.lookTarget].lerpOrigin, cent->lerpOrigin, lookAngles); + vectoangles(lookAngles, lookAngles); + ci->lookTime = cg->time + 1000; + } + else + { + VectorCopy(cent->lerpAngles, lookAngles); + } + lookAngles[PITCH] = 0; + + if (cent->currentState.otherEntityNum2) + { + emplaced = &cg_entities[cent->currentState.otherEntityNum2].currentState; + } + + BG_G2PlayerAngles(cent->ghoul2, ci->bolt_motion, ¢->currentState, cg->time, + cent->lerpOrigin, cent->lerpAngles, legs, legsAngles, ¢->pe.torso.yawing, ¢->pe.torso.pitching, + ¢->pe.legs.yawing, ¢->pe.torso.yawAngle, ¢->pe.torso.pitchAngle, ¢->pe.legs.yawAngle, + cg->frametime, cent->turAngles, cent->modelScale, ci->legsAnim, ci->torsoAnim, &ci->corrTime, + lookAngles, ci->lastHeadAngles, ci->lookTime, emplaced, &ci->superSmoothTime); + + if (cent->currentState.heldByClient && cent->currentState.heldByClient <= MAX_CLIENTS) + { //then put our arm in this client's hand + //is index+1 because index 0 is valid. + int heldByIndex = cent->currentState.heldByClient-1; + centity_t *other = &cg_entities[heldByIndex]; + + if (other && other->ghoul2 && ci->bolt_lhand) + { + mdxaBone_t boltMatrix; + vec3_t boltOrg; + + trap_G2API_GetBoltMatrix(other->ghoul2, 0, ci->bolt_lhand, &boltMatrix, other->turAngles, other->lerpOrigin, cg->time, cgs.gameModels, other->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + + BG_IK_MoveArm(cent->ghoul2, ci->bolt_lhand, cg->time, ¢->currentState, + cent->currentState.torsoAnim/*BOTH_DEAD1*/, boltOrg, ¢->ikStatus, cent->lerpOrigin, cent->lerpAngles, cent->modelScale, 500, qfalse); + } + } + else if (cent->ikStatus) + { //make sure we aren't IKing if we don't have anyone to hold onto us. + BG_IK_MoveArm(cent->ghoul2, ci->bolt_lhand, cg->time, ¢->currentState, + cent->currentState.torsoAnim/*BOTH_DEAD1*/, vec3_origin, ¢->ikStatus, cent->lerpOrigin, cent->lerpAngles, cent->modelScale, 500, qtrue); + } + } + else if ( cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) + { + vec3_t lookAngles; + + VectorCopy(cent->lerpAngles, legsAngles); + legsAngles[PITCH] = 0; + AnglesToAxis( legsAngles, legs ); + + VectorCopy(cent->lerpAngles, lookAngles); + lookAngles[YAW] = lookAngles[ROLL] = 0; + + BG_G2ATSTAngles( cent->ghoul2, cg->time, lookAngles ); + } + else + { + if (cent->currentState.eType == ET_NPC && + cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle && + cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { //fighters actually want to take pitch and roll into account for the axial angles + VectorCopy(cent->lerpAngles, legsAngles); + AnglesToAxis( legsAngles, legs ); + } + else if (cent->currentState.eType == ET_NPC && + cent->currentState.m_iVehicleNum && + cent->currentState.NPC_class != CLASS_VEHICLE ) + { //an NPC bolted to a vehicle should use the full angles + VectorCopy(cent->lerpAngles, legsAngles); + AnglesToAxis( legsAngles, legs ); + } + else + { + vec3_t nhAngles; + + if (cent->currentState.eType == ET_NPC && + cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle && + cent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER) + { //yeah, a hack, sorry. + VectorSet(nhAngles, 0, cent->lerpAngles[YAW], cent->lerpAngles[ROLL]); + } + else + { + VectorSet(nhAngles, 0, cent->lerpAngles[YAW], 0); + } + AnglesToAxis( nhAngles, legs ); + } + } + + //See if we have any bone angles sent from the server + CG_G2ServerBoneAngles(cent); +} +//========================================================================== + +/* +=============== +CG_TrailItem +=============== +*/ +#if 0 +static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { + refEntity_t ent; + vec3_t angles; + vec3_t axis[3]; + + VectorCopy( cent->lerpAngles, angles ); + angles[PITCH] = 0; + angles[ROLL] = 0; + AnglesToAxis( angles, axis ); + + memset( &ent, 0, sizeof( ent ) ); + VectorMA( cent->lerpOrigin, -16, axis[0], ent.origin ); + ent.origin[2] += 16; + angles[YAW] += 90; + AnglesToAxis( angles, ent.axis ); + + ent.hModel = hModel; + trap_R_AddRefEntityToScene( &ent ); +} +#endif + + +/* +=============== +CG_PlayerFlag +=============== +*/ +static void CG_PlayerFlag( centity_t *cent, qhandle_t hModel ) { + refEntity_t ent; + vec3_t angles; + vec3_t axis[3]; + vec3_t boltOrg, tAng, getAng, right; + mdxaBone_t boltMatrix; + clientInfo_t *ci; + + if (cent->currentState.number == cg->snap->ps.clientNum && + !cg->renderingThirdPerson) + { + return; + } + + if (!cent->ghoul2) + { + return; + } + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + assert(ci); + } + else + { + ci = &cgs.clientinfo[cent->currentState.number]; + } + + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_llumbar, &boltMatrix, tAng, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, tAng); + vectoangles(tAng, tAng); + + VectorCopy(cent->lerpAngles, angles); + + boltOrg[2] -= 12; + VectorSet(getAng, 0, cent->lerpAngles[1], 0); + AngleVectors(getAng, 0, right, 0); + boltOrg[0] += right[0]*8; + boltOrg[1] += right[1]*8; + boltOrg[2] += right[2]*8; + + angles[PITCH] = -cent->lerpAngles[PITCH]/2-30; + angles[YAW] = tAng[YAW]+270; + + AnglesToAxis(angles, axis); + + memset( &ent, 0, sizeof( ent ) ); + VectorMA( boltOrg, 24, axis[0], ent.origin ); + + angles[ROLL] += 20; + AnglesToAxis( angles, ent.axis ); + + ent.hModel = hModel; + + ent.modelScale[0] = 0.5; + ent.modelScale[1] = 0.5; + ent.modelScale[2] = 0.5; + ScaleModelAxis(&ent); + + /* + if (cent->currentState.number == cg->snap->ps.clientNum) + { //If we're the current client (in third person), render the flag on our back transparently + ent.renderfx |= RF_FORCE_ENT_ALPHA; + ent.shaderRGBA[3] = 100; + } + */ + //FIXME: Not doing this at the moment because sorting totally messes up + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + ent.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_PlayerPowerups +=============== +*/ +static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { + int powerups; + clientInfo_t *ci; + + powerups = cent->currentState.powerups; + if ( !powerups ) { + return; + } + + // quad gives a dlight + if ( powerups & ( 1 << PW_QUAD ) ) { + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); + } + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + assert(ci); + } + else + { + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + } + // redflag + if ( powerups & ( 1 << PW_REDFLAG ) ) { + CG_PlayerFlag( cent, cgs.media.redFlagModel ); + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f ); + } + + // blueflag + if ( powerups & ( 1 << PW_BLUEFLAG ) ) { + CG_PlayerFlag( cent, cgs.media.blueFlagModel ); + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); + } + + // neutralflag + if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 ); + } + + // haste leaves smoke trails + /* + if ( powerups & ( 1 << PW_HASTE ) ) { + CG_HasteTrail( cent ); + } + */ +} + + +/* +=============== +CG_PlayerFloatSprite + +Float a sprite over the player's head +=============== +*/ +static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { + int rf; + refEntity_t ent; + + if ( cent->currentState.number == cg->snap->ps.clientNum && !cg->renderingThirdPerson ) { + rf = RF_THIRD_PERSON; // only show in mirrors + } else { + rf = 0; + } + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.origin[2] += 48; + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.radius = 10; + ent.renderfx = rf; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + ent.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &ent ); +} + + + +/* +=============== +CG_PlayerFloatSprite + +Same as above but allows custom RGBA values +=============== +*/ +#if 0 +static void CG_PlayerFloatSpriteRGBA( centity_t *cent, qhandle_t shader, vec4_t rgba ) { + int rf; + refEntity_t ent; + + if ( cent->currentState.number == cg->snap->ps.clientNum && !cg->renderingThirdPerson ) { + rf = RF_THIRD_PERSON; // only show in mirrors + } else { + rf = 0; + } + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.origin[2] += 48; + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.radius = 10; + ent.renderfx = rf; + ent.shaderRGBA[0] = rgba[0]; + ent.shaderRGBA[1] = rgba[1]; + ent.shaderRGBA[2] = rgba[2]; + ent.shaderRGBA[3] = rgba[3]; + trap_R_AddRefEntityToScene( &ent ); +} +#endif + + +/* +=============== +CG_PlayerSprites + +Float sprites over the player's head +=============== +*/ +static void CG_PlayerSprites( centity_t *cent ) { +// int team; + + if (cg->snap && + CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg->snap->ps.clientNum)) + { + return; //this entity is mind-tricking the current client, so don't render it + } + + if ( cent->currentState.eFlags & EF_CONNECTION ) { + CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); + return; + } + + if (cent->vChatTime > cg->time) + { + CG_PlayerFloatSprite( cent, cgs.media.vchatShader ); + } + else if ( cent->currentState.eType != ET_NPC && //don't draw talk balloons on NPCs + (cent->currentState.eFlags & EF_TALK) ) + { + CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); + return; + } +} + +/* +=============== +CG_PlayerShadow + +Returns the Z component of the surface being shadowed + + should it return a full plane instead of a Z? +=============== +*/ +#define SHADOW_DISTANCE 128 +static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { + vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; + trace_t trace; + float alpha; + float radius = 24.0f; + + *shadowPlane = 0; + + if ( cg_shadows.integer == 0 ) { + return qfalse; + } + + // no shadows when cloaked + if ( cent->currentState.powerups & ( 1 << PW_CLOAKED )) + { + return qfalse; + } + + if (cent->currentState.eFlags & EF_DEAD) + { + return qfalse; + } + + if (CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg->snap->ps.clientNum)) + { + return qfalse; //this entity is mind-tricking the current client, so don't render it + } + + if ( cg_shadows.integer == 1 ) + {//dropshadow + if (cent->currentState.m_iVehicleNum && + cent->currentState.NPC_class != CLASS_VEHICLE ) + {//riding a vehicle, no dropshadow + return qfalse; + } + } + // send a trace down from the player to the ground + VectorCopy( cent->lerpOrigin, end ); + if (cg_shadows.integer == 2) + { //stencil + end[2] -= 4096.0f; + + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) + { + trace.endpos[2] = cent->lerpOrigin[2]-25.0f; + } + } + else + { + end[2] -= SHADOW_DISTANCE; + + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + // no shadow if too high + if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { + return qfalse; + } + } + + if (cg_shadows.integer == 2) + { //stencil shadows need plane to be on ground + *shadowPlane = trace.endpos[2]; + } + else + { + *shadowPlane = trace.endpos[2] + 1; + } + + if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows + return qtrue; + } + + // fade the shadow out with height + alpha = 1.0 - trace.fraction; + + // bk0101022 - hack / FPE - bogus planes? + //assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f ) + + // add the mark as a temporary, so it goes directly to the renderer + // without taking a spot in the cg_marks array + if ( cent->currentState.NPC_class == CLASS_REMOTE + || cent->currentState.NPC_class == CLASS_SEEKER ) + { + radius = 8.0f; + } + CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, + cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, radius, qtrue ); + + return qtrue; +} + + +/* +=============== +CG_PlayerSplash + +Draw a mark at the water surface +=============== +*/ +static void CG_PlayerSplash( centity_t *cent ) { + vec3_t start, end; + trace_t trace; + int contents; + polyVert_t verts[4]; + + if ( !cg_shadows.integer ) { + return; + } + + VectorCopy( cent->lerpOrigin, end ); + end[2] -= 24; + + // if the feet aren't in liquid, don't make a mark + // this won't handle moving water brushes, but they wouldn't draw right anyway... + contents = trap_CM_PointContents( end, 0 ); + if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { + return; + } + + VectorCopy( cent->lerpOrigin, start ); + start[2] += 32; + + // if the head isn't out of liquid, don't make a mark + contents = trap_CM_PointContents( start, 0 ); + if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + + // trace down to find the surface + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); + + if ( trace.fraction == 1.0 ) { + return; + } + + // create a mark polygon + VectorCopy( trace.endpos, verts[0].xyz ); + verts[0].xyz[0] -= 32; + verts[0].xyz[1] -= 32; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[1].xyz ); + verts[1].xyz[0] -= 32; + verts[1].xyz[1] += 32; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[2].xyz ); + verts[2].xyz[0] += 32; + verts[2].xyz[1] += 32; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[3].xyz ); + verts[3].xyz[0] += 32; + verts[3].xyz[1] -= 32; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); +} + +#define REFRACT_EFFECT_DURATION 500 +static void CG_ForcePushBlur( vec3_t org, centity_t *cent ) +{ + if (!cent || !cg_renderToTextureFX.integer) + { + localEntity_t *ex; + + ex = CG_AllocLocalEntity(); + ex->leType = LE_PUFF; + ex->refEntity.reType = RT_SPRITE; + ex->radius = 2.0f; + ex->startTime = cg->time; + ex->endTime = ex->startTime + 120; + VectorCopy( org, ex->pos.trBase ); + ex->pos.trTime = cg->time; + ex->pos.trType = TR_LINEAR; + VectorScale( cg->refdef.viewaxis[1], 55, ex->pos.trDelta ); + + ex->color[0] = 24; + ex->color[1] = 32; + ex->color[2] = 40; + ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" ); + + ex = CG_AllocLocalEntity(); + ex->leType = LE_PUFF; + ex->refEntity.reType = RT_SPRITE; + ex->refEntity.rotation = 180.0f; + ex->radius = 2.0f; + ex->startTime = cg->time; + ex->endTime = ex->startTime + 120; + VectorCopy( org, ex->pos.trBase ); + ex->pos.trTime = cg->time; + ex->pos.trType = TR_LINEAR; + VectorScale( cg->refdef.viewaxis[1], -55, ex->pos.trDelta ); + + ex->color[0] = 24; + ex->color[1] = 32; + ex->color[2] = 40; + ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" ); + } + else + { //superkewl "refraction" (well sort of) effect -rww + refEntity_t ent; + vec3_t ang; + float scale; + float vLen; + float alpha; + int tDif; + + if (!cent->bodyFadeTime) + { //the duration for the expansion and fade + cent->bodyFadeTime = cg->time + REFRACT_EFFECT_DURATION; + } + + //closer tDif is to 0, the closer we are to + //being "done" + tDif = (cent->bodyFadeTime - cg->time); + + if ((REFRACT_EFFECT_DURATION-tDif) < 200) + { //stop following the hand after a little and stay in a fixed spot + //save the initial spot of the effect + VectorCopy(org, cent->pushEffectOrigin); + } + + //scale from 1.0f to 0.1f then hold at 0.1 for the rest of the duration + if (cent->currentState.powerups & (1 << PW_PULL)) + { + scale = (float)(REFRACT_EFFECT_DURATION-tDif)*0.003f; + } + else + { + scale = (float)(tDif)*0.003f; + } + + if (scale > 1.0f) + { + scale = 1.0f; + } + else if (scale < 0.2f) + { + scale = 0.2f; + } + + //start alpha at 244, fade to 10 + alpha = (float)tDif*0.488f; + + if (alpha > 244.0f) + { + alpha = 244.0f; + } + else if (alpha < 10.0f) + { + alpha = 10.0f; + } + + memset( &ent, 0, sizeof( ent ) ); + ent.shaderTime = (cent->bodyFadeTime-REFRACT_EFFECT_DURATION) / 1000.0f; + + VectorCopy( cent->pushEffectOrigin, ent.origin ); + + VectorSubtract(ent.origin, cg->refdef.vieworg, ent.axis[0]); + vLen = VectorLength(ent.axis[0]); + if (vLen <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + + vectoangles(ent.axis[0], ang); + ang[ROLL] += 180.0f; + AnglesToAxis(ang, ent.axis); + + //radius must be a power of 2, and is the actual captured texture size + if (vLen < 128) + { + ent.radius = 256; + } + else if (vLen < 256) + { + ent.radius = 128; + } + else if (vLen < 512) + { + ent.radius = 64; + } + else + { + ent.radius = 32; + } + + VectorScale(ent.axis[0], scale, ent.axis[0]); + VectorScale(ent.axis[1], scale, ent.axis[1]); + VectorScale(ent.axis[2], scale, ent.axis[2]); + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = cgs.media.refractionShader; //cgs.media.cloakedShader; + ent.nonNormalizedAxes = qtrue; + + //make it partially transparent so it blends with the background + ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); + ent.shaderRGBA[0] = 255.0f; + ent.shaderRGBA[1] = 255.0f; + ent.shaderRGBA[2] = 255.0f; + ent.shaderRGBA[3] = alpha; + + trap_R_AddRefEntityToScene( &ent ); + } +} + +static const char *cg_pushBoneNames[] = +{ + "cranium", + "lower_lumbar", + "rhand", + "lhand", + "ltibia", + "rtibia", + "lradius", + "rradius", + NULL +}; + +static void CG_ForcePushBodyBlur( centity_t *cent ) +{ + vec3_t fxOrg; + mdxaBone_t boltMatrix; + int bolt; + int i; + + if (cent->localAnimIndex > 1) + { //Sorry, the humanoid IS IN ANOTHER CASTLE. + return; + } + + if (cg->snap && + CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg->snap->ps.clientNum)) + { + return; //this entity is mind-tricking the current client, so don't render it + } + + assert(cent->ghoul2); + + for (i = 0; cg_pushBoneNames[i]; i++) + { //go through all the bones we want to put a blur effect on + bolt = trap_G2API_AddBolt(cent->ghoul2, 0, cg_pushBoneNames[i]); + + if (bolt == -1) + { + assert(!"You've got an invalid bone/bolt name in cg_pushBoneNames"); + continue; + } + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &boltMatrix, cent->turAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, fxOrg); + + //standard effect, don't be refractive (for now) + CG_ForcePushBlur(fxOrg, NULL); + } +} + +static void CG_ForceGripEffect( vec3_t org ) +{ + localEntity_t *ex; + float wv = sin( cg->time * 0.004f ) * 0.08f + 0.1f; + + ex = CG_AllocLocalEntity(); + ex->leType = LE_PUFF; + ex->refEntity.reType = RT_SPRITE; + ex->radius = 2.0f; + ex->startTime = cg->time; + ex->endTime = ex->startTime + 120; + VectorCopy( org, ex->pos.trBase ); + ex->pos.trTime = cg->time; + ex->pos.trType = TR_LINEAR; + VectorScale( cg->refdef.viewaxis[1], 55, ex->pos.trDelta ); + + ex->color[0] = 200+((wv*255)); + if (ex->color[0] > 255) + { + ex->color[0] = 255; + } + ex->color[1] = 0; + ex->color[2] = 0; + ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" ); + + ex = CG_AllocLocalEntity(); + ex->leType = LE_PUFF; + ex->refEntity.reType = RT_SPRITE; + ex->refEntity.rotation = 180.0f; + ex->radius = 2.0f; + ex->startTime = cg->time; + ex->endTime = ex->startTime + 120; + VectorCopy( org, ex->pos.trBase ); + ex->pos.trTime = cg->time; + ex->pos.trType = TR_LINEAR; + VectorScale( cg->refdef.viewaxis[1], -55, ex->pos.trDelta ); + + /* + ex->color[0] = 200+((wv*255)); + if (ex->color[0] > 255) + { + ex->color[0] = 255; + } + */ + ex->color[0] = 255; + ex->color[1] = 255; + ex->color[2] = 255; + ex->refEntity.customShader = cgs.media.redSaberGlowShader;//trap_R_RegisterShader( "gfx/effects/forcePush" ); +} + + +/* +=============== +CG_AddRefEntityWithPowerups + +Adds a piece with modifications or duplications for powerups +Also called by CG_Missile for quad rockets, but nobody can tell... +=============== +*/ +void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) { + + if (CG_IsMindTricked(state->trickedentindex, + state->trickedentindex2, + state->trickedentindex3, + state->trickedentindex4, + cg->snap->ps.clientNum)) + { + return; //this entity is mind-tricking the current client, so don't render it + } + + trap_R_AddRefEntityToScene( ent ); +} + +#define MAX_SHIELD_TIME 2000.0 +#define MIN_SHIELD_TIME 2000.0 + + +void CG_PlayerShieldHit(int entitynum, vec3_t dir, int amount) +{ + centity_t *cent; + int time; + + if (entitynum<0 || entitynum >= MAX_ENTITIES) + { + return; + } + + cent = &cg_entities[entitynum]; + + if (amount > 100) + { + time = cg->time + MAX_SHIELD_TIME; // 2 sec. + } + else + { + time = cg->time + 500 + amount*15; + } + + if (time > cent->damageTime) + { + cent->damageTime = time; + VectorScale(dir, -1, dir); + vectoangles(dir, cent->damageAngles); + } +} + + +void CG_DrawPlayerShield(centity_t *cent, vec3_t origin) +{ + refEntity_t ent; + int alpha; + float scale; + + // Don't draw the shield when the player is dead. + if (cent->currentState.eFlags & EF_DEAD) + { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( origin, ent.origin ); + ent.origin[2] += 10.0; + AnglesToAxis( cent->damageAngles, ent.axis ); + + alpha = 255.0 * ((cent->damageTime - cg->time) / MIN_SHIELD_TIME) + random()*16; + if (alpha>255) + alpha=255; + + // Make it bigger, but tighter if more solid + scale = 1.4 - ((float)alpha*(0.4/255.0)); // Range from 1.0 to 1.4 + VectorScale( ent.axis[0], scale, ent.axis[0] ); + VectorScale( ent.axis[1], scale, ent.axis[1] ); + VectorScale( ent.axis[2], scale, ent.axis[2] ); + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = cgs.media.halfShieldShader; + ent.shaderRGBA[0] = alpha; + ent.shaderRGBA[1] = alpha; + ent.shaderRGBA[2] = alpha; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); +} + + +void CG_PlayerHitFX(centity_t *cent) +{ + // only do the below fx if the cent in question is...uh...me, and it's first person. + if (cent->currentState.clientNum != cg->predictedPlayerState.clientNum || cg->renderingThirdPerson) + { + if (cent->damageTime > cg->time + && cent->currentState.NPC_class != CLASS_VEHICLE ) + { + CG_DrawPlayerShield(cent, cent->lerpOrigin); + } + + return; + } +} + + + +/* +================= +CG_LightVerts +================= +*/ +int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) +{ + int i, j; + float incoming; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + + trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir ); + + for (i = 0; i < numVerts; i++) { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + verts[i].modulate[0] = ambientLight[0]; + verts[i].modulate[1] = ambientLight[1]; + verts[i].modulate[2] = ambientLight[2]; + verts[i].modulate[3] = 255; + continue; + } + j = ( ambientLight[0] + incoming * directedLight[0] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[0] = j; + + j = ( ambientLight[1] + incoming * directedLight[1] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[1] = j; + + j = ( ambientLight[2] + incoming * directedLight[2] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[2] = j; + + verts[i].modulate[3] = 255; + } + return qtrue; +} + +static void CG_RGBForSaberColor( saber_colors_t color, vec3_t rgb ) +{ + switch( color ) + { + case SABER_RED: + VectorSet( rgb, 1.0f, 0.2f, 0.2f ); + break; + case SABER_ORANGE: + VectorSet( rgb, 1.0f, 0.5f, 0.1f ); + break; + case SABER_YELLOW: + VectorSet( rgb, 1.0f, 1.0f, 0.2f ); + break; + case SABER_GREEN: + VectorSet( rgb, 0.2f, 1.0f, 0.2f ); + break; + case SABER_BLUE: + VectorSet( rgb, 0.2f, 0.4f, 1.0f ); + break; + case SABER_PURPLE: + VectorSet( rgb, 0.9f, 0.2f, 1.0f ); + break; + } +} + +static void CG_DoSaberLight( saberInfo_t *saber ) +{ + vec3_t positions[MAX_BLADES*2], mid={0}, rgbs[MAX_BLADES*2], rgb={0}; + float lengths[MAX_BLADES*2]={0}, totallength = 0, numpositions = 0, dist, diameter = 0; + int i, j; + + //RGB combine all the colors of the sabers you're using into one averaged color! + if ( !saber ) + { + return; + } + + if ( saber->noDlight ) + {//no dlight! + return; + } + + for ( i = 0; i < saber->numBlades; i++ ) + { + if ( saber->blade[i].length >= 0.5f ) + { + //FIXME: make RGB sabers + CG_RGBForSaberColor( saber->blade[i].color, rgbs[i] ); + lengths[i] = saber->blade[i].length; + if ( saber->blade[i].length*2.0f > diameter ) + { + diameter = saber->blade[i].length*2.0f; + } + totallength += saber->blade[i].length; + VectorMA( saber->blade[i].muzzlePoint, saber->blade[i].length, saber->blade[i].muzzleDir, positions[i] ); + if ( !numpositions ) + {//first blade, store middle of that as midpoint + VectorMA( saber->blade[i].muzzlePoint, saber->blade[i].length*0.5, saber->blade[i].muzzleDir, mid ); + VectorCopy( rgbs[i], rgb ); + } + numpositions++; + } + } + + if ( totallength ) + {//actually have something to do + if ( numpositions == 1 ) + {//only 1 blade, midpoint is already set (halfway between the start and end of that blade), rgb is already set, so it diameter + } + else + {//multiple blades, calc averages + VectorClear( mid ); + VectorClear( rgb ); + //now go through all the data and get the average RGB and middle position and the radius + for ( i = 0; i < MAX_BLADES*2; i++ ) + { + if ( lengths[i] ) + { + VectorMA( rgb, lengths[i], rgbs[i], rgb ); + VectorAdd( mid, positions[i], mid ); + } + } + + //get middle rgb + VectorScale( rgb, 1/totallength, rgb );//get the average, normalized RGB + //get mid position + VectorScale( mid, 1/numpositions, mid ); + //find the farthest distance between the blade tips, this will be our diameter + for ( i = 0; i < MAX_BLADES*2; i++ ) + { + if ( lengths[i] ) + { + for ( j = 0; j < MAX_BLADES*2; j++ ) + { + if ( lengths[j] ) + { + dist = Distance( positions[i], positions[j] ); + if ( dist > diameter ) + { + diameter = dist; + } + } + } + } + } + } + + trap_R_AddLightToScene( mid, diameter + (random()*8.0f), rgb[0], rgb[1], rgb[2] ); + } +} + +void CG_DoSaber( vec3_t origin, vec3_t dir, float length, float lengthMax, float radius, saber_colors_t color, int rfx, qboolean doLight ) +{ + vec3_t mid; + qhandle_t blade = 0, glow = 0; + refEntity_t saber; + float radiusmult; + float radiusRange; + float radiusStart; + + if ( length < 0.5f ) + { + // if the thing is so short, just forget even adding me. + return; + } + + // Find the midpoint of the saber for lighting purposes + VectorMA( origin, length * 0.5f, dir, mid ); + + switch( color ) + { + case SABER_RED: + glow = cgs.media.redSaberGlowShader; + blade = cgs.media.redSaberCoreShader; + break; + case SABER_ORANGE: + glow = cgs.media.orangeSaberGlowShader; + blade = cgs.media.orangeSaberCoreShader; + break; + case SABER_YELLOW: + glow = cgs.media.yellowSaberGlowShader; + blade = cgs.media.yellowSaberCoreShader; + break; + case SABER_GREEN: + glow = cgs.media.greenSaberGlowShader; + blade = cgs.media.greenSaberCoreShader; + break; + case SABER_BLUE: + glow = cgs.media.blueSaberGlowShader; + blade = cgs.media.blueSaberCoreShader; + break; + case SABER_PURPLE: + glow = cgs.media.purpleSaberGlowShader; + blade = cgs.media.purpleSaberCoreShader; + break; + default: + glow = cgs.media.blueSaberGlowShader; + blade = cgs.media.blueSaberCoreShader; + break; + } + + if (doLight) + { // always add a light because sabers cast a nice glow before they slice you in half!! or something... + vec3_t rgb={1,1,1}; + CG_RGBForSaberColor( color, rgb ); + trap_R_AddLightToScene( mid, (length*1.4f) + (random()*3.0f), rgb[0], rgb[1], rgb[2] ); + } + + memset( &saber, 0, sizeof( refEntity_t )); + + // Saber glow is it's own ref type because it uses a ton of sprites, otherwise it would eat up too many + // refEnts to do each glow blob individually + saber.saberLength = length; + + // Jeff, I did this because I foolishly wished to have a bright halo as the saber is unleashed. + // It's not quite what I'd hoped tho. If you have any ideas, go for it! --Pat + if (length < lengthMax) + { + radiusmult = 1.0 + (2.0 / length); // Note this creates a curve, and length cannot be < 0.5. + } + else + { + radiusmult = 1.0; + } + + if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) + { //draw the blade as a post-render so it doesn't get in the cap... + rfx |= RF_FORCEPOST; + } + + radiusRange = radius * 0.075f; + radiusStart = radius-radiusRange; + + saber.radius = (radiusStart + crandom() * radiusRange)*radiusmult; + //saber.radius = (2.8f + crandom() * 0.2f)*radiusmult; + + VectorCopy( origin, saber.origin ); + VectorCopy( dir, saber.axis[0] ); + saber.reType = RT_SABER_GLOW; + saber.customShader = glow; + saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff; + saber.renderfx = rfx; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + saber.skipForPlayer2 = true; +#endif + + trap_R_AddRefEntityToScene( &saber ); + + // Do the hot core + VectorMA( origin, length, dir, saber.origin ); + VectorMA( origin, -1, dir, saber.oldorigin ); + + +// CG_TestLine(saber.origin, saber.oldorigin, 50, 0x000000ff, 3); + saber.customShader = blade; + saber.reType = RT_LINE; + radiusStart = radius/3.0f; + saber.radius = (radiusStart + crandom() * radiusRange)*radiusmult; +// saber.radius = (1.0 + crandom() * 0.2f)*radiusmult; + + saber.shaderTexCoord[0] = saber.shaderTexCoord[1] = 1.0f; + saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff; + + trap_R_AddRefEntityToScene( &saber ); +} + +//-------------------------------------------------------------- +// CG_GetTagWorldPosition +// +// Can pass in NULL for the axis +//-------------------------------------------------------------- +void CG_GetTagWorldPosition( refEntity_t *model, char *tag, vec3_t pos, vec3_t axis[3] ) +{ + orientation_t orientation; + int i = 0; + + // Get the requested tag + trap_R_LerpTag( &orientation, model->hModel, model->oldframe, model->frame, + 1.0f - model->backlerp, tag ); + + VectorCopy( model->origin, pos ); + for ( i = 0 ; i < 3 ; i++ ) + { + VectorMA( pos, orientation.origin[i], model->axis[i], pos ); + } + + if ( axis ) + { + MatrixMultiply( orientation.axis, model->axis, axis ); + } +} + +#define MAX_MARK_FRAGMENTS 128 +#define MAX_MARK_POINTS 384 +extern markPoly_t *CG_AllocMark(); + +void CG_CreateSaberMarks( vec3_t start, vec3_t end, vec3_t normal ) +{ +// byte colors[4]; + int i, j; + int numFragments; + vec3_t axis[3], originalPoints[4], mid; + vec3_t markPoints[MAX_MARK_POINTS], projection; + polyVert_t *v, verts[MAX_VERTS_ON_POLY]; + markPoly_t *mark; + markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; + + float radius = 0.65f; + + if ( !cg_addMarks.integer ) + { + return; + } + + VectorSubtract( end, start, axis[1] ); + VectorNormalize( axis[1] ); + + // create the texture axis + VectorCopy( normal, axis[0] ); + CrossProduct( axis[1], axis[0], axis[2] ); + + // create the full polygon that we'll project + for ( i = 0 ; i < 3 ; i++ ) + { // stretch a bit more in the direction that we are traveling in... debateable as to whether this makes things better or worse + originalPoints[0][i] = start[i] - radius * axis[1][i] - radius * axis[2][i]; + originalPoints[1][i] = end[i] + radius * axis[1][i] - radius * axis[2][i]; + originalPoints[2][i] = end[i] + radius * axis[1][i] + radius * axis[2][i]; + originalPoints[3][i] = start[i] - radius * axis[1][i] + radius * axis[2][i]; + } + + VectorScale( normal, -1, projection ); + + // get the fragments + numFragments = trap_CM_MarkFragments( 4, (const float (*)[3])originalPoints, + projection, MAX_MARK_POINTS, markPoints[0], MAX_MARK_FRAGMENTS, markFragments ); + + for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) + { + // we have an upper limit on the complexity of polygons that we store persistantly + if ( mf->numPoints > MAX_VERTS_ON_POLY ) + { + mf->numPoints = MAX_VERTS_ON_POLY; + } + + for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) + { + vec3_t delta; + + // Set up our texture coords, this may need some work + VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); + VectorAdd( end, start, mid ); + VectorScale( mid, 0.5f, mid ); + VectorSubtract( v->xyz, mid, delta ); + + v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * (0.05f + random() * 0.03f); + v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * (0.15f + random() * 0.05f); + } + + if (cg_saberDynamicMarks.integer) + { + int i = 0; + int i_2 = 0; + addpolyArgStruct_t apArgs; + vec3_t x; + + memset (&apArgs, 0, sizeof(apArgs)); + + while (i < 4) + { + while (i_2 < 3) + { + apArgs.p[i][i_2] = verts[i].xyz[i_2]; + + i_2++; + } + + i_2 = 0; + i++; + } + + i = 0; + i_2 = 0; + + while (i < 4) + { + while (i_2 < 2) + { + apArgs.ev[i][i_2] = verts[i].st[i_2]; + + i_2++; + } + + i_2 = 0; + i++; + } + + //When using addpoly, having a situation like this tends to cause bad results. + //(I assume it doesn't like trying to draw a polygon over two planes and extends + //the vertex out to some odd value) + VectorSubtract(apArgs.p[0], apArgs.p[3], x); + if (VectorLength(x) > 3.0f) + { + return; + } + + apArgs.numVerts = mf->numPoints; + VectorCopy(vec3_origin, apArgs.vel); + VectorCopy(vec3_origin, apArgs.accel); + + apArgs.alpha1 = 1.0f; + apArgs.alpha2 = 0.0f; + apArgs.alphaParm = 255.0f; + + VectorSet(apArgs.rgb1, 0.0f, 0.0f, 0.0f); + VectorSet(apArgs.rgb2, 0.0f, 0.0f, 0.0f); + + apArgs.rgbParm = 0.0f; + + apArgs.bounce = 0; + apArgs.motionDelay = 0; + apArgs.killTime = cg_saberDynamicMarkTime.integer; + apArgs.shader = cgs.media.rivetMarkShader; + apArgs.flags = 0x08000000|0x00000004; + + trap_FX_AddPoly(&apArgs); + + apArgs.shader = cgs.media.mSaberDamageGlow; + apArgs.rgb1[0] = 215 + random() * 40.0f; + apArgs.rgb1[1] = 96 + random() * 32.0f; + apArgs.rgb1[2] = apArgs.alphaParm = random()*15.0f; + + apArgs.rgb1[0] /= 255; + apArgs.rgb1[1] /= 255; + apArgs.rgb1[2] /= 255; + VectorCopy(apArgs.rgb1, apArgs.rgb2); + + apArgs.killTime = 100; + + trap_FX_AddPoly(&apArgs); + } + else + { + // save it persistantly, do burn first + mark = CG_AllocMark(); + mark->time = cg->time; + mark->alphaFade = qtrue; + mark->markShader = cgs.media.rivetMarkShader; + mark->poly.numVerts = mf->numPoints; + mark->color[0] = mark->color[1] = mark->color[2] = mark->color[3] = 255; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + + // And now do a glow pass + // by moving the start time back, we can hack it to fade out way before the burn does + mark = CG_AllocMark(); + mark->time = cg->time - 8500; + mark->alphaFade = qfalse; + mark->markShader = cgs.media.mSaberDamageGlow; + mark->poly.numVerts = mf->numPoints; + mark->color[0] = 215 + random() * 40.0f; + mark->color[1] = 96 + random() * 32.0f; + mark->color[2] = mark->color[3] = random()*15.0f; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + } + } +} + +qboolean CG_G2TraceCollide(trace_t *tr, vec3_t const mins, vec3_t const maxs, const vec3_t lastValidStart, const vec3_t lastValidEnd) +{ + G2Trace_t G2Trace; + centity_t *g2Hit; + vec3_t angles; + int tN = 0; + float fRadius = 0.0f; + + if (mins && maxs && + (mins[0] || maxs[0])) + { + fRadius=(maxs[0]-mins[0])/2.0f; + } + + memset (&G2Trace, 0, sizeof(G2Trace)); + + while (tN < MAX_G2_COLLISIONS) + { + G2Trace[tN].mEntityNum = -1; + tN++; + } + g2Hit = &cg_entities[tr->entityNum]; + + if (g2Hit && g2Hit->ghoul2) + { + angles[ROLL] = angles[PITCH] = 0; + angles[YAW] = g2Hit->lerpAngles[YAW]; + + trap_G2API_CollisionDetect ( G2Trace, g2Hit->ghoul2, angles, g2Hit->lerpOrigin, cg->time, g2Hit->currentState.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, cg_g2TraceLod.integer, fRadius ); + + if (G2Trace[0].mEntityNum != g2Hit->currentState.number) + { + tr->fraction = 1.0f; + tr->entityNum = ENTITYNUM_NONE; + tr->startsolid = 0; + tr->allsolid = 0; + return qfalse; + } + else + { //Yay! + VectorCopy(G2Trace[0].mCollisionPosition, tr->endpos); + VectorCopy(G2Trace[0].mCollisionNormal, tr->plane.normal); + return qtrue; + } + } + + return qfalse; +} + +void CG_G2SaberEffects(vec3_t start, vec3_t end, centity_t *owner) +{ + trace_t trace; + vec3_t startTr; + vec3_t endTr; + qboolean backWards = qfalse; + qboolean doneWithTraces = qfalse; + + while (!doneWithTraces) + { + if (!backWards) + { + VectorCopy(start, startTr); + VectorCopy(end, endTr); + } + else + { + VectorCopy(end, startTr); + VectorCopy(start, endTr); + } + + CG_Trace( &trace, startTr, NULL, NULL, endTr, owner->currentState.number, MASK_PLAYERSOLID ); + + if (trace.entityNum < MAX_CLIENTS) + { //hit a client.. + CG_G2TraceCollide(&trace, NULL, NULL, startTr, endTr); + + if (trace.entityNum != ENTITYNUM_NONE) + { //it succeeded with the ghoul2 trace + trap_FX_PlayEffectID( cgs.effects.mSaberBloodSparks, trace.endpos, trace.plane.normal, -1, -1 ); + trap_S_StartSound(trace.endpos, trace.entityNum, CHAN_AUTO, trap_S_RegisterSound(va("sound/weapons/saber/saberhit%i.wav", Q_irand(1, 3)))); + } + } + + if (!backWards) + { + backWards = qtrue; + } + else + { + doneWithTraces = qtrue; + } + } +} + +#define CG_MAX_SABER_COMP_TIME 400 //last registered saber entity hit must match within this many ms for the client effect to take place. + +void CG_AddGhoul2Mark(int shader, float size, vec3_t start, vec3_t end, int entnum, + vec3_t entposition, float entangle, void *ghoul2, vec3_t scale, int lifeTime) +{ + SSkinGoreData goreSkin; + + assert(ghoul2); + + memset ( &goreSkin, 0, sizeof(goreSkin) ); + + if (trap_G2API_GetNumGoreMarks(ghoul2, 0) >= cg_ghoul2Marks.integer) + { //you've got too many marks already + return; + } + + goreSkin.growDuration = -1; // default expandy time + goreSkin.goreScaleStartFraction = 1.0; // default start scale + goreSkin.frontFaces = qtrue; + goreSkin.backFaces = qtrue; + goreSkin.lifeTime = lifeTime; //last randomly 10-20 seconds + /* + if (lifeTime) + { + goreSkin.fadeOutTime = lifeTime*0.1; //default fade duration is relative to lifetime. + } + goreSkin.fadeRGB = qtrue; //fade on RGB instead of alpha (this depends on the shader really, modify if needed) + */ + //rwwFIXMEFIXME: fade has sorting issues with other non-fading decals, disabled until fixed + + goreSkin.baseModelOnly = qfalse; + + goreSkin.currentTime = cg->time; + goreSkin.entNum = entnum; + goreSkin.SSize = size; + goreSkin.TSize = size; + goreSkin.theta = flrand(0.0f,6.28f); + goreSkin.shader = shader; + + if (!scale[0] && !scale[1] && !scale[2]) + { + VectorSet(goreSkin.scale, 1.0f, 1.0f, 1.0f); + } + else + { + VectorCopy(goreSkin.scale, scale); + } + + VectorCopy (start, goreSkin.hitLocation); + + VectorSubtract(end, start, goreSkin.rayDirection); + if (VectorNormalize(goreSkin.rayDirection)<.1f) + { + return; + } + + VectorCopy ( entposition, goreSkin.position ); + goreSkin.angles[YAW] = entangle; + + trap_G2API_AddSkinGore(ghoul2, &goreSkin); +} + +void CG_SaberCompWork(vec3_t start, vec3_t end, centity_t *owner, int saberNum) +{ + trace_t trace; + vec3_t startTr; + vec3_t endTr; + qboolean backWards = qfalse; + qboolean doneWithTraces = qfalse; + qboolean doEffect = qfalse; + clientInfo_t *client = NULL; + + if ((cg->time - owner->serverSaberHitTime) > CG_MAX_SABER_COMP_TIME) + { + return; + } + + if (cg->time == owner->serverSaberHitTime) + { //don't want to do it the same frame as the server hit, to avoid burst effect concentrations every x ms. + return; + } + + while (!doneWithTraces) + { + if (!backWards) + { + VectorCopy(start, startTr); + VectorCopy(end, endTr); + } + else + { + VectorCopy(end, startTr); + VectorCopy(start, endTr); + } + + CG_Trace( &trace, startTr, NULL, NULL, endTr, owner->currentState.number, MASK_PLAYERSOLID ); + + if (trace.entityNum == owner->serverSaberHitIndex) + { //this is the guy the server says we last hit, so continue. + if (cg_entities[trace.entityNum].ghoul2) + { //If it has a g2 instance, do the proper ghoul2 checks + CG_G2TraceCollide(&trace, NULL, NULL, startTr, endTr); + + if (trace.entityNum != ENTITYNUM_NONE) + { //it succeeded with the ghoul2 trace + doEffect = qtrue; + + if (cg_ghoul2Marks.integer) + { + vec3_t ePos; + centity_t *trEnt = &cg_entities[trace.entityNum]; + + if (trEnt->ghoul2) + { + if (trEnt->currentState.eType != ET_NPC || + trEnt->currentState.NPC_class != CLASS_VEHICLE || + !trEnt->m_pVehicle || + trEnt->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) + { //don't do on fighters cause they have crazy full axial angles + int markShader = cgs.media.bdecal_saberglow; + + + VectorSubtract(endTr, trace.endpos, ePos); + VectorNormalize(ePos); + VectorMA(trace.endpos, 4.0f, ePos, ePos); + + client = &cgs.clientinfo[owner->currentState.number]; + if ( client->infoValid + && client->saber[saberNum].g2MarksShader ) + {//we have a shader to use instead of the standard mark shader + markShader = client->saber[saberNum].g2MarksShader; + } + CG_AddGhoul2Mark(markShader, flrand(3.0f, 4.0f), + trace.endpos, ePos, trace.entityNum, trEnt->lerpOrigin, trEnt->lerpAngles[YAW], + trEnt->ghoul2, trEnt->modelScale, Q_irand(5000, 10000)); + } + } + } + } + } + else + { //otherwise, we're all set. + doEffect = qtrue; + } + + if (doEffect) + { + int hitPersonFxID = cgs.effects.mSaberBloodSparks; + int hitOtherFxID = cgs.effects.mSaberCut; + + client = &cgs.clientinfo[owner->currentState.number]; + if ( client && client->infoValid ) + { + if ( client->saber[0].hitPersonEffect ) + { + hitPersonFxID = client->saber[0].hitPersonEffect; + } + if ( client->saber[0].hitOtherEffect ) + {//custom hit other effect + hitOtherFxID = client->saber[0].hitOtherEffect; + } + } + if (!trace.plane.normal[0] && !trace.plane.normal[1] && !trace.plane.normal[2]) + { //who cares, just shoot it somewhere. + trace.plane.normal[1] = 1; + } + + if (owner->serverSaberFleshImpact) + { //do standard player/live ent hit sparks + trap_FX_PlayEffectID( hitPersonFxID, trace.endpos, trace.plane.normal, -1, -1 ); + //trap_S_StartSound(trace.endpos, trace.entityNum, CHAN_AUTO, trap_S_RegisterSound(va("sound/weapons/saber/saberhit%i.wav", Q_irand(1, 3)))); + } + else + { //do the cut effect + trap_FX_PlayEffectID( hitOtherFxID, trace.endpos, trace.plane.normal, -1, -1 ); + } + doEffect = qfalse; + } + } + + /* + if (!backWards) + { + backWards = qtrue; + } + else + { + doneWithTraces = qtrue; + } + */ + doneWithTraces = qtrue; //disabling backwards tr for now, sometimes it just makes too many effects. + } +} + +#define SABER_TRAIL_TIME 40.0f +#define FX_USE_ALPHA 0x08000000 + +#include "../namespace_begin.h" +qboolean BG_SuperBreakWinAnim( int anim ); +#include "../namespace_end.h" + +void CG_AddSaberBlade( centity_t *cent, centity_t *scent, refEntity_t *saber, int renderfx, int modelIndex, int saberNum, int bladeNum, vec3_t origin, vec3_t angles, qboolean fromSaber, qboolean dontDraw) +{ + vec3_t org_, end, v, + axis_[3] = {0,0,0, 0,0,0, 0,0,0}; // shut the compiler up + trace_t trace; + int i = 0; + int trailDur; + float saberLen; + float diff; + clientInfo_t *client; + centity_t *saberEnt; + saberTrail_t *saberTrail; + mdxaBone_t boltMatrix; + vec3_t futureAngles; + effectTrailArgStruct_t fx; + int scolor = 0; + + if (cent->currentState.eType == ET_NPC) + { + client = cent->npcClient; + assert(client); + } + else + { + client = &cgs.clientinfo[cent->currentState.number]; + } + + saberEnt = &cg_entities[cent->currentState.saberEntityNum]; + saberLen = client->saber[saberNum].blade[bladeNum].length; + + if (saberLen <= 0 && !dontDraw) + { //don't bother then. + return; + } + + futureAngles[YAW] = angles[YAW]; + futureAngles[PITCH] = angles[PITCH]; + futureAngles[ROLL] = angles[ROLL]; + + //Assume bladeNum is equal to the bolt index because bolts should be added in order of the blades. + if (fromSaber) + { + trap_G2API_GetBoltMatrix(scent->ghoul2, 0, bladeNum, &boltMatrix, futureAngles, origin, cg->time, cgs.gameModels, scent->modelScale); + } + else + { + trap_G2API_GetBoltMatrix(scent->ghoul2, saberNum+1, bladeNum, &boltMatrix, futureAngles, origin, cg->time, cgs.gameModels, scent->modelScale); + } + // work the matrix axis stuff into the original axis and origins used. + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, org_); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, axis_[0]); + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (!fromSaber && saberEnt && !cent->currentState.saberInFlight) + { + VectorCopy(org_, saberEnt->currentState.pos.trBase); + + VectorCopy(axis_[0], saberEnt->currentState.apos.trBase); + } +#ifdef _XBOX + } +#endif + + VectorMA( org_, saberLen, axis_[0], end ); + + VectorAdd( end, axis_[0], end ); + + if (cent->currentState.eType == ET_NPC) + { + scolor = client->saber[saberNum].blade[bladeNum].color; + } + else + { + if (saberNum == 0) + { + scolor = client->icolor1; + } + else + { + scolor = client->icolor2; + } + } + + if (cgs.gametype >= GT_TEAM && + cgs.gametype != GT_SIEGE && + !cgs.jediVmerc && + cent->currentState.eType != ET_NPC) + { + if (client->team == TEAM_RED) + { + scolor = SABER_RED; + } + else if (client->team == TEAM_BLUE) + { + scolor = SABER_BLUE; + } + } + + if (!cg_saberContact.integer) + { //if we don't have saber contact enabled, just add the blade and don't care what it's touching + goto CheckTrail; + } + + if (!dontDraw) + { + if (cg_saberModelTraceEffect.integer) + { + CG_G2SaberEffects(org_, end, cent); + } + else if (cg_saberClientVisualCompensation.integer) + { + CG_Trace( &trace, org_, NULL, NULL, end, ENTITYNUM_NONE, MASK_SOLID ); + + if (trace.fraction != 1) + { //nudge the endpos a very small amount from the beginning to the end, so the comp trace hits at the end. + //I'm only bothering with this because I want to do a backwards trace too in the comp trace, so if the + //blade is sticking through a player or something the standard trace doesn't it, it will make sparks + //on each side. + vec3_t seDif; + + VectorSubtract(trace.endpos, org_, seDif); + VectorNormalize(seDif); + trace.endpos[0] += seDif[0]*0.1f; + trace.endpos[1] += seDif[1]*0.1f; + trace.endpos[2] += seDif[2]*0.1f; + } + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (client->saber[saberNum].blade[bladeNum].storageTime < cg->time) + { //debounce it in case our framerate is absurdly high. Using storageTime since it's not used for anything else in the client. + CG_SaberCompWork(org_, trace.endpos, cent, saberNum); + + client->saber[saberNum].blade[bladeNum].storageTime = cg->time + 5; + } +#ifdef _XBOX + } +#endif + } + + for ( i = 0; i < 1; i++ )//was 2 because it would go through architecture and leave saber trails on either side of the brush - but still looks bad if we hit a corner, blade is still 8 longer than hit + { + if ( i ) + {//tracing from end to base + CG_Trace( &trace, end, NULL, NULL, org_, ENTITYNUM_NONE, MASK_SOLID ); + } + else + {//tracing from base to end + CG_Trace( &trace, org_, NULL, NULL, end, ENTITYNUM_NONE, MASK_SOLID ); + } + + if ( trace.fraction < 1.0f ) + { + vec3_t trDir; + VectorCopy(trace.plane.normal, trDir); + if (!trDir[0] && !trDir[1] && !trDir[2]) + { + trDir[1] = 1; + } + + if ( client->saber[saberNum].noWallMarks ) + {//don't actually draw the marks/impact effects + } + else + { + if (!(trace.surfaceFlags & SURF_NOIMPACT) ) // never spark on sky + { + trap_FX_PlayEffectID( cgs.effects.mSparks, trace.endpos, trDir, -1, -1 ); + } + } + + //Stop saber? (it wouldn't look right if it was stuck through a thin wall and unable to hurt players on the other side) + VectorSubtract(org_, trace.endpos, v); + saberLen = VectorLength(v); + + VectorCopy(trace.endpos, end); + + if ( client->saber[saberNum].noWallMarks ) + {//don't actually draw the marks + } + else + {//draw marks if we hit a wall + // All I need is a bool to mark whether I have a previous point to work with. + //....come up with something better.. + if ( client->saber[saberNum].blade[bladeNum].trail[ClientManager::ActiveClientNum()].haveOldPos[i] ) + { + if ( trace.entityNum == ENTITYNUM_WORLD || cg_entities[trace.entityNum].currentState.eType == ET_TERRAIN || (cg_entities[trace.entityNum].currentState.eFlags & EF_PERMANENT) ) + {//only put marks on architecture + // Let's do some cool burn/glowing mark bits!!! + CG_CreateSaberMarks( client->saber[saberNum].blade[bladeNum].trail[ClientManager::ActiveClientNum()].oldPos[i], trace.endpos, trace.plane.normal ); + + //make a sound + if ( cg->time - client->saber[saberNum].blade[bladeNum].hitWallDebounceTime >= 100 ) + {//ugh, need to have a real sound debouncer... or do this game-side + client->saber[saberNum].blade[bladeNum].hitWallDebounceTime = cg->time; + trap_S_StartSound ( trace.endpos, -1, CHAN_WEAPON, trap_S_RegisterSound( va("sound/weapons/saber/saberhitwall%i", Q_irand(1, 3)) ) ); + } + } + } + else + { + // if we impact next frame, we'll mark a slash mark + client->saber[saberNum].blade[bladeNum].trail[ClientManager::ActiveClientNum()].haveOldPos[i] = qtrue; + // CG_ImpactMark( cgs.media.rivetMarkShader, client->saber[saberNum].blade[bladeNum].trail.oldPos[i], client->saber[saberNum].blade[bladeNum].trail.oldNormal[i], + // 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse ); + } + } + + // stash point so we can connect-the-dots later + VectorCopy( trace.endpos, client->saber[saberNum].blade[bladeNum].trail[ClientManager::ActiveClientNum()].oldPos[i] ); + VectorCopy( trace.plane.normal, client->saber[saberNum].blade[bladeNum].trail[ClientManager::ActiveClientNum()].oldNormal[i] ); + } + else + { + if ( client->saber[saberNum].blade[bladeNum].trail[ClientManager::ActiveClientNum()].haveOldPos[i] ) + { + // Hmmm, no impact this frame, but we have an old point + // Let's put the mark there, we should use an endcap mark to close the line, but we + // can probably just get away with a round mark + // CG_ImpactMark( cgs.media.rivetMarkShader, client->saber[saberNum].blade[bladeNum].trail.oldPos[i], client->saber[saberNum].blade[bladeNum].trail.oldNormal[i], + // 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse ); + } + + // we aren't impacting, so turn off our mark tracking mechanism + client->saber[saberNum].blade[bladeNum].trail[ClientManager::ActiveClientNum()].haveOldPos[i] = qfalse; + } + } + } + +CheckTrail: + + if (!cg_saberTrail.integer) + { //don't do the trail in this case + goto JustDoIt; + } + + if ( client->saber[saberNum].trailStyle > 1 ) + {//don't actually draw the trail at all + goto JustDoIt; + } + + //FIXME: if trailStyle is 1, use the motion blur instead + + saberTrail = &client->saber[saberNum].blade[bladeNum].trail[ClientManager::ActiveClientNum()]; + saberTrail->duration = saberMoveData[cent->currentState.saberMove].trailLength; + + trailDur = (saberTrail->duration/5.0f); + if (!trailDur) + { //hmm.. ok, default + if ( BG_SuperBreakWinAnim(cent->currentState.torsoAnim) ) + { + trailDur = 150; + } + else + { + trailDur = SABER_TRAIL_TIME; + } + } + + // if we happen to be timescaled or running in a high framerate situation, we don't want to flood + // the system with very small trail slices...but perhaps doing it by distance would yield better results? + if ( cg->time > saberTrail->lastTime + 2 || cg_saberTrail.integer == 2 ) // 2ms + { + if (!dontDraw) + { + if ( (BG_SuperBreakWinAnim(cent->currentState.torsoAnim) || saberMoveData[cent->currentState.saberMove].trailLength > 0 || ((cent->currentState.powerups & (1 << PW_SPEED) && cg_speedTrail.integer)) || (cent->currentState.saberInFlight && saberNum == 0)) && cg->time < saberTrail->lastTime + 2000 ) // if we have a stale segment, don't draw until we have a fresh one + { + #if 0 + if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) + { + polyVert_t verts[4]; + + VectorCopy( org_, verts[0].xyz ); + VectorMA( end, 3.0f, axis_[0], verts[1].xyz ); + VectorCopy( saberTrail->tip, verts[2].xyz ); + VectorCopy( saberTrail->base, verts[3].xyz ); + + //tc doesn't even matter since we're just gonna stencil an outline, but whatever. + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + //don't capture postrender objects (now we'll postrender the saber so it doesn't get in the capture) + trap_R_SetRefractProp(1.0f, 0.0f, qtrue, qtrue); + + //shader 2 is always the crazy refractive shader. + trap_R_AddPolyToScene( 2, 4, verts ); + } + else + #endif + { + vec3_t rgb1={255.0f,255.0f,255.0f}; + + switch( scolor ) + { + case SABER_RED: + VectorSet( rgb1, 255.0f, 0.0f, 0.0f ); + break; + case SABER_ORANGE: + VectorSet( rgb1, 255.0f, 64.0f, 0.0f ); + break; + case SABER_YELLOW: + VectorSet( rgb1, 255.0f, 255.0f, 0.0f ); + break; + case SABER_GREEN: + VectorSet( rgb1, 0.0f, 255.0f, 0.0f ); + break; + case SABER_BLUE: + VectorSet( rgb1, 0.0f, 64.0f, 255.0f ); + break; + case SABER_PURPLE: + VectorSet( rgb1, 220.0f, 0.0f, 255.0f ); + break; + default: + VectorSet( rgb1, 0.0f, 64.0f, 255.0f ); + break; + } + + //Here we will use the happy process of filling a struct in with arguments and passing it to a trap function + //so that we can take the struct and fill in an actual CTrail type using the data within it once we get it + //into the effects area + + // Go from new muzzle to new end...then to old end...back down to old muzzle...finally + // connect back to the new muzzle...this is our trail quad + VectorCopy( org_, fx.mVerts[0].origin ); + VectorMA( end, 3.0f, axis_[0], fx.mVerts[1].origin ); + + VectorCopy( saberTrail->tip, fx.mVerts[2].origin ); + VectorCopy( saberTrail->base, fx.mVerts[3].origin ); + + diff = cg->time - saberTrail->lastTime; + + // I'm not sure that clipping this is really the best idea + //This prevents the trail from showing at all in low framerate situations. + //if ( diff <= SABER_TRAIL_TIME * 2 ) + if ( diff <= 10000 ) + { //don't draw it if the last time is way out of date + float oldAlpha = 1.0f - ( diff / trailDur ); + + if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) + {//does other stuff below + } + else + { + if ( client->saber[saberNum].trailStyle == 1 ) + {//motion trail + fx.mShader = cgs.media.swordTrailShader; + VectorSet( rgb1, 32.0f, 32.0f, 32.0f ); // make the sith sword trail pretty faint + trailDur *= 2.0f; // stay around twice as long? + } + else + { + fx.mShader = cgs.media.saberBlurShader; + } + fx.mKillTime = trailDur; + fx.mSetFlags = FX_USE_ALPHA; + } + + // New muzzle + VectorCopy( rgb1, fx.mVerts[0].rgb ); + fx.mVerts[0].alpha = 255.0f; + + fx.mVerts[0].ST[0] = 0.0f; + fx.mVerts[0].ST[1] = 1.0f; + fx.mVerts[0].destST[0] = 1.0f; + fx.mVerts[0].destST[1] = 1.0f; + + // new tip + VectorCopy( rgb1, fx.mVerts[1].rgb ); + fx.mVerts[1].alpha = 255.0f; + + fx.mVerts[1].ST[0] = 0.0f; + fx.mVerts[1].ST[1] = 0.0f; + fx.mVerts[1].destST[0] = 1.0f; + fx.mVerts[1].destST[1] = 0.0f; + + // old tip + VectorCopy( rgb1, fx.mVerts[2].rgb ); + fx.mVerts[2].alpha = 255.0f; + + fx.mVerts[2].ST[0] = 1.0f - oldAlpha; // NOTE: this just happens to contain the value I want + fx.mVerts[2].ST[1] = 0.0f; + fx.mVerts[2].destST[0] = 1.0f + fx.mVerts[2].ST[0]; + fx.mVerts[2].destST[1] = 0.0f; + + // old muzzle + VectorCopy( rgb1, fx.mVerts[3].rgb ); + fx.mVerts[3].alpha = 255.0f; + + fx.mVerts[3].ST[0] = 1.0f - oldAlpha; // NOTE: this just happens to contain the value I want + fx.mVerts[3].ST[1] = 1.0f; + fx.mVerts[3].destST[0] = 1.0f + fx.mVerts[2].ST[0]; + fx.mVerts[3].destST[1] = 1.0f; + + if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) + { + trap_R_SetRefractProp(1.0f, 0.0f, qtrue, qtrue); //don't need to do this every frame.. but.. + + if (BG_SaberInAttack(cent->currentState.saberMove) + ||BG_SuperBreakWinAnim(cent->currentState.torsoAnim)) + { //in attack, strong trail + fx.mKillTime = 300; + } + else + { //faded trail + fx.mKillTime = 40; + } + fx.mShader = 2; //2 is always refractive shader + fx.mSetFlags = FX_USE_ALPHA; + } + /* + else + { + fx.mShader = cgs.media.saberBlurShader; + fx.mKillTime = trailDur; + fx.mSetFlags = FX_USE_ALPHA; + } + */ + + trap_FX_AddPrimitive(&fx); + } + } + } + } + + // we must always do this, even if we aren't active..otherwise we won't know where to pick up from + VectorCopy( org_, saberTrail->base ); + VectorMA( end, 3.0f, axis_[0], saberTrail->tip ); + saberTrail->lastTime = cg->time; + } + +JustDoIt: + + if (dontDraw) + { + return; + } + + if ( client->saber[saberNum].noBlade ) + {//don't actually draw the blade at all + if ( !client->saber[saberNum].noDlight ) + {//hmm, but still add the dlight + CG_DoSaberLight( &client->saber[saberNum] ); + } + return; + } + // Pass in the renderfx flags attached to the saber weapon model...this is done so that saber glows + // will get rendered properly in a mirror...not sure if this is necessary?? + //CG_DoSaber( org_, axis_[0], saberLen, client->saber[saberNum].blade[bladeNum].lengthMax, client->saber[saberNum].blade[bladeNum].radius, + // scolor, renderfx, (qboolean)(saberNum==0&&bladeNum==0) ); + CG_DoSaber( org_, axis_[0], saberLen, client->saber[saberNum].blade[bladeNum].lengthMax, client->saber[saberNum].blade[bladeNum].radius, + scolor, renderfx, (qboolean)(client->saber[saberNum].numBlades < 3 && !client->saber[saberNum].noDlight) ); +} + +int CG_IsMindTricked(int trickIndex1, int trickIndex2, int trickIndex3, int trickIndex4, int client) +{ + int checkIn; + int sub = 0; + + if (cg_entities[client].currentState.forcePowersActive & (1 << FP_SEE)) + { + return 0; + } + + if (client > 47) + { + checkIn = trickIndex4; + sub = 48; + } + else if (client > 31) + { + checkIn = trickIndex3; + sub = 32; + } + else if (client > 15) + { + checkIn = trickIndex2; + sub = 16; + } + else + { + checkIn = trickIndex1; + } + + if (checkIn & (1 << (client-sub))) + { + return 1; + } + + return 0; +} + +#define SPEED_TRAIL_DISTANCE 6 + +void CG_DrawPlayerSphere(centity_t *cent, vec3_t origin, float scale, int shader) +{ + refEntity_t ent; + vec3_t ang; + float vLen; + vec3_t viewDir; + + // Don't draw the shield when the player is dead. + if (cent->currentState.eFlags & EF_DEAD) + { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( origin, ent.origin ); + ent.origin[2] += 9.0; + + VectorSubtract(ent.origin, cg->refdef.vieworg, ent.axis[0]); + vLen = VectorLength(ent.axis[0]); + if (vLen <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + + VectorCopy(ent.axis[0], viewDir); + VectorInverse(viewDir); + VectorNormalize(viewDir); + + vectoangles(ent.axis[0], ang); + ang[ROLL] += 180.0f; + ang[PITCH] += 180.0f; + AnglesToAxis(ang, ent.axis); + + VectorScale(ent.axis[0], scale, ent.axis[0]); + VectorScale(ent.axis[1], scale, ent.axis[1]); + VectorScale(ent.axis[2], scale, ent.axis[2]); + + ent.nonNormalizedAxes = qtrue; + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = shader; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + ent.skipForPlayer2 = true; +#endif + + trap_R_AddRefEntityToScene( &ent ); + + if (!cg->renderingThirdPerson && cent->currentState.number == cg->predictedPlayerState.clientNum) + { //don't do the rest then + return; + } + if (!cg_renderToTextureFX.integer) + { + return; + } + + ang[PITCH] -= 180.0f; + AnglesToAxis(ang, ent.axis); + + VectorScale(ent.axis[0], scale*0.5f, ent.axis[0]); + VectorScale(ent.axis[1], scale*0.5f, ent.axis[1]); + VectorScale(ent.axis[2], scale*0.5f, ent.axis[2]); + + ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); + if (shader == cgs.media.invulnerabilityShader) + { //ok, ok, this is a little hacky. sorry! + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 0; + ent.shaderRGBA[3] = 100; + } + else if (shader == cgs.media.ysalimariShader) + { + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 0; + ent.shaderRGBA[3] = 100; + } + else if (shader == cgs.media.endarkenmentShader) + { + ent.shaderRGBA[0] = 100; + ent.shaderRGBA[1] = 0; + ent.shaderRGBA[2] = 0; + ent.shaderRGBA[3] = 20; + } + else if (shader == cgs.media.enlightenmentShader) + { + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 20; + } + else + { //ysal red/blue, boon + ent.shaderRGBA[0] = 255.0f; + ent.shaderRGBA[1] = 255.0f; + ent.shaderRGBA[2] = 255.0f; + ent.shaderRGBA[3] = 20; + } + + ent.radius = 256; + + VectorMA(ent.origin, 40.0f, viewDir, ent.origin); + + ent.customShader = trap_R_RegisterShader("effects/refract_2"); + trap_R_AddRefEntityToScene( &ent ); +} + +void CG_AddLightningBeam(vec3_t start, vec3_t end) +{ + vec3_t dir, chaos, + c1, c2, + v1, v2; + float len, + s1, s2, s3; + + addbezierArgStruct_t b; + + VectorCopy(start, b.start); + VectorCopy(end, b.end); + + VectorSubtract( b.end, b.start, dir ); + len = VectorNormalize( dir ); + + // Get the base control points, we'll work from there + VectorMA( b.start, 0.3333f * len, dir, c1 ); + VectorMA( b.start, 0.6666f * len, dir, c2 ); + + // get some chaos values that really aren't very chaotic :) + s1 = sin( cg->time * 0.005f ) * 2 + crandom() * 0.2f; + s2 = sin( cg->time * 0.001f ); + s3 = sin( cg->time * 0.011f ); + + VectorSet( chaos, len * 0.01f * s1, + len * 0.02f * s2, + len * 0.04f * (s1 + s2 + s3)); + + VectorAdd( c1, chaos, c1 ); + VectorScale( chaos, 4.0f, v1 ); + + VectorSet( chaos, -len * 0.02f * s3, + len * 0.01f * (s1 * s2), + -len * 0.02f * (s1 + s2 * s3)); + + VectorAdd( c2, chaos, c2 ); + VectorScale( chaos, 2.0f, v2 ); + + VectorSet( chaos, 1.0f, 1.0f, 1.0f ); + + VectorCopy(c1, b.control1); + VectorCopy(vec3_origin, b.control1Vel); + VectorCopy(c2, b.control2); + VectorCopy(vec3_origin, b.control2Vel); + + b.size1 = 6.0f; + b.size2 = 6.0f; + b.sizeParm = 0.0f; + b.alpha1 = 0.0f; + b.alpha2 = 0.2f; + b.alphaParm = 0.5f; + + /* + VectorCopy(WHITE, b.sRGB); + VectorCopy(WHITE, b.eRGB); + */ + + b.sRGB[0] = 255; + b.sRGB[1] = 255; + b.sRGB[2] = 255; + VectorCopy(b.sRGB, b.eRGB); + + b.rgbParm = 0.0f; + b.killTime = 50; + b.shader = trap_R_RegisterShader( "gfx/misc/electric2" ); + b.flags = 0x00000001; //FX_ALPHA_LINEAR + + trap_FX_AddBezier(&b); +} + +void CG_AddRandomLightning(vec3_t start, vec3_t end) +{ + vec3_t inOrg, outOrg; + + VectorCopy(start, inOrg); + VectorCopy(end, outOrg); + + if ( rand() & 1 ) + { + outOrg[0] += Q_irand(0, 24); + inOrg[0] += Q_irand(0, 8); + } + else + { + outOrg[0] -= Q_irand(0, 24); + inOrg[0] -= Q_irand(0, 8); + } + + if ( rand() & 1 ) + { + outOrg[1] += Q_irand(0, 24); + inOrg[1] += Q_irand(0, 8); + } + else + { + outOrg[1] -= Q_irand(0, 24); + inOrg[1] -= Q_irand(0, 8); + } + + if ( rand() & 1 ) + { + outOrg[2] += Q_irand(0, 50); + inOrg[2] += Q_irand(0, 40); + } + else + { + outOrg[2] -= Q_irand(0, 64); + inOrg[2] -= Q_irand(0, 40); + } + + CG_AddLightningBeam(inOrg, outOrg); +} + +extern char *forceHolocronModels[]; + +qboolean CG_ThereIsAMaster(void) +{ + int i = 0; + centity_t *cent; + + while (i < MAX_CLIENTS) + { + cent = &cg_entities[i]; +/* + if (cent && cent->currentState.isJediMaster) + { + return qtrue; + } +*/ + i++; + } + + return qfalse; +} + +#if 0 +void CG_DrawNoForceSphere(centity_t *cent, vec3_t origin, float scale, int shader) +{ + refEntity_t ent; + + // Don't draw the shield when the player is dead. + if (cent->currentState.eFlags & EF_DEAD) + { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( origin, ent.origin ); + ent.origin[2] += 9.0; + + VectorSubtract(cg->refdef.vieworg, ent.origin, ent.axis[0]); + if (VectorNormalize(ent.axis[0]) <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + + VectorCopy(cg->refdef.viewaxis[2], ent.axis[2]); + CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); + + VectorScale(ent.axis[0], scale, ent.axis[0]); + VectorScale(ent.axis[1], scale, ent.axis[1]); + VectorScale(ent.axis[2], -scale, ent.axis[2]); + + ent.shaderRGBA[3] = (cent->currentState.genericenemyindex - cg->time)/8; + ent.renderfx |= RF_RGB_TINT; + if (ent.shaderRGBA[3] > 200) + { + ent.shaderRGBA[3] = 200; + } + if (ent.shaderRGBA[3] < 1) + { + ent.shaderRGBA[3] = 1; + } + + ent.shaderRGBA[2] = 0; + ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[3]; + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = shader; + + trap_R_AddRefEntityToScene( &ent ); +} +#endif + +//Checks to see if the model string has a * appended with a custom skin name after. +//If so, it terminates the model string correctly, parses the skin name out, and returns +//the handle of the registered skin. +int CG_HandleAppendedSkin(char *modelName) +{ + char skinName[MAX_QPATH]; + char *p; + qhandle_t skinID = 0; + int i = 0; + + //see if it has a skin name + p = Q_strrchr(modelName, '*'); + + if (p) + { //found a *, we should have a model name before it and a skin name after it. + *p = 0; //terminate the modelName string at this point, then go ahead and parse to the next 0 for the skin. + p++; + + while (p && *p) + { + skinName[i] = *p; + i++; + p++; + } + skinName[i] = 0; + + if (skinName[0]) + { //got it, register the skin under the model path. + char baseFolder[MAX_QPATH]; + + strcpy(baseFolder, modelName); + p = Q_strrchr(baseFolder, '/'); //go back to the first /, should be the path point + + if (p) + { //got it.. terminate at the slash and register. + char *useSkinName; + + *p = 0; + + if (strchr(skinName, '|')) + {//three part skin + useSkinName = va("%s/|%s", baseFolder, skinName); + } + else + { + useSkinName = va("%s/model_%s.skin", baseFolder, skinName); + } + + skinID = trap_R_RegisterSkin(useSkinName); + } + } + } + + return skinID; +} + +//Create a temporary ghoul2 instance and get the gla name so we can try loading animation data and sounds. +#include "../namespace_begin.h" +void BG_GetVehicleModelName(char *modelname); +void BG_GetVehicleSkinName(char *skinname); +#include "../namespace_end.h" + +void CG_CacheG2AnimInfo(char *modelName) +{ + void *g2 = NULL; + char *slash; + char useModel[MAX_QPATH]; + char useSkin[MAX_QPATH]; + int animIndex; + + strcpy(useModel, modelName); + strcpy(useSkin, modelName); + + if (modelName[0] == '$') + { //it's a vehicle name actually, let's precache the whole vehicle + BG_GetVehicleModelName(useModel); + BG_GetVehicleSkinName(useSkin); + if ( useSkin[0] ) + { //use a custom skin + trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", useModel, useSkin)); + } + else + { + trap_R_RegisterSkin(va("models/players/%s/model_default.skin", useModel)); + } + strcpy(useModel, va("models/players/%s/model.glm", useModel)); + } + + trap_G2API_InitGhoul2Model(&g2, useModel, 0, 0, 0, 0, 0); + + if (g2) + { + char GLAName[MAX_QPATH]; + char originalModelName[MAX_QPATH]; + + animIndex = -1; + + GLAName[0] = 0; + trap_G2API_GetGLAName(g2, 0, GLAName); + + strcpy(originalModelName, useModel); + + slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + + animIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); + } + + if (animIndex != -1) + { + slash = Q_strrchr( originalModelName, '/' ); + if ( slash ) + { + slash++; + *slash = 0; + } + + BG_ParseAnimationEvtFile(originalModelName, animIndex, bgNumAnimEvents); + } + + //Now free the temp instance + trap_G2API_CleanGhoul2Models(&g2); + } +} + +static void CG_RegisterVehicleAssets( Vehicle_t *pVeh ) +{ + /* + if ( pVeh->m_pVehicleInfo->exhaustFX ) + { + pVeh->m_pVehicleInfo->iExhaustFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->exhaustFX ); + } + if ( pVeh->m_pVehicleInfo->trailFX ) + { + pVeh->m_pVehicleInfo->iTrailFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->trailFX ); + } + if ( pVeh->m_pVehicleInfo->impactFX ) + { + pVeh->m_pVehicleInfo->iImpactFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->impactFX ); + } + if ( pVeh->m_pVehicleInfo->explodeFX ) + { + pVeh->m_pVehicleInfo->iExplodeFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->explodeFX ); + } + if ( pVeh->m_pVehicleInfo->wakeFX ) + { + pVeh->m_pVehicleInfo->iWakeFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wakeFX ); + } + + if ( pVeh->m_pVehicleInfo->dmgFX ) + { + pVeh->m_pVehicleInfo->iDmgFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->dmgFX ); + } + if ( pVeh->m_pVehicleInfo->wpn1FX ) + { + pVeh->m_pVehicleInfo->iWpn1FX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn1FX ); + } + if ( pVeh->m_pVehicleInfo->wpn2FX ) + { + pVeh->m_pVehicleInfo->iWpn2FX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn2FX ); + } + if ( pVeh->m_pVehicleInfo->wpn1FireFX ) + { + pVeh->m_pVehicleInfo->iWpn1FireFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn1FireFX ); + } + if ( pVeh->m_pVehicleInfo->wpn2FireFX ) + { + pVeh->m_pVehicleInfo->iWpn2FireFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn2FireFX ); + } + */ +} + +extern void CG_HandleNPCSounds(centity_t *cent); + +#include "../namespace_begin.h" +extern void G_CreateAnimalNPC( Vehicle_t **pVeh, const char *strAnimalType ); +extern void G_CreateSpeederNPC( Vehicle_t **pVeh, const char *strType ); +extern void G_CreateWalkerNPC( Vehicle_t **pVeh, const char *strAnimalType ); +extern void G_CreateFighterNPC( Vehicle_t **pVeh, const char *strType ); +#include "../namespace_end.h" + +extern playerState_t *cgSendPS[MAX_GENTITIES]; +void CG_G2AnimEntModelLoad(centity_t *cent) +{ + const char *cModelName = CG_ConfigString( CS_MODELS+cent->currentState.modelindex ); + + if (!cent->npcClient) + { //have not init'd client yet + return; + } + + if (cModelName && cModelName[0]) + { + char modelName[MAX_QPATH]; + int skinID; + char *slash; + + strcpy(modelName, cModelName); + + if (cent->currentState.NPC_class == CLASS_VEHICLE && modelName[0] == '$') + { //vehicles pass their veh names over as model names, then we get the model name from the veh type + //create a vehicle object clientside for this type + char *vehType = &modelName[1]; + int iVehIndex = BG_VehicleGetIndex( vehType ); + + switch( g_vehicleInfo[iVehIndex].type ) + { + case VH_ANIMAL: + // Create the animal (making sure all it's data is initialized). + G_CreateAnimalNPC( ¢->m_pVehicle, vehType ); + break; + case VH_SPEEDER: + // Create the speeder (making sure all it's data is initialized). + G_CreateSpeederNPC( ¢->m_pVehicle, vehType ); + break; + case VH_FIGHTER: + // Create the fighter (making sure all it's data is initialized). + G_CreateFighterNPC( ¢->m_pVehicle, vehType ); + break; + case VH_WALKER: + // Create the walker (making sure all it's data is initialized). + G_CreateWalkerNPC( ¢->m_pVehicle, vehType ); + break; + + default: + assert(!"vehicle with an unknown type - couldn't create vehicle_t"); + break; + } + + //set up my happy prediction hack + cent->m_pVehicle->m_vOrientation = &cgSendPS[cent->currentState.number]->vehOrientation[0]; + + cent->m_pVehicle->m_pParentEntity = (bgEntity_t *)cent; + + //attach the handles for fx cgame-side + CG_RegisterVehicleAssets(cent->m_pVehicle); + + BG_GetVehicleModelName(modelName); + if (cent->m_pVehicle->m_pVehicleInfo->skin && + cent->m_pVehicle->m_pVehicleInfo->skin[0]) + { //use a custom skin + skinID = trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", modelName, cent->m_pVehicle->m_pVehicleInfo->skin)); + } + else + { + skinID = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelName)); + } + strcpy(modelName, va("models/players/%s/model.glm", modelName)); + + //this sound is *only* used for vehicles now + cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav" ); + } + else + { + skinID = CG_HandleAppendedSkin(modelName); //get the skin if there is one. + } + + if (cent->ghoul2) + { //clean it first! + trap_G2API_CleanGhoul2Models(¢->ghoul2); + } + + trap_G2API_InitGhoul2Model(¢->ghoul2, modelName, 0, skinID, 0, 0, 0); + + if (cent->ghoul2) + { + char GLAName[MAX_QPATH]; + char originalModelName[MAX_QPATH]; + char *saber; + int j = 0; + + if (cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle) + { //do special vehicle stuff + char strTemp[128]; + int i; + + // Setup the default first bolt + i = trap_G2API_AddBolt( cent->ghoul2, 0, "model_root" ); + + // Setup the droid unit. + cent->m_pVehicle->m_iDroidUnitTag = trap_G2API_AddBolt( cent->ghoul2, 0, "*droidunit" ); + + // Setup the Exhausts. + for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ ) + { + Com_sprintf( strTemp, 128, "*exhaust%i", i + 1 ); + cent->m_pVehicle->m_iExhaustTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp ); + } + + // Setup the Muzzles. + for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) + { + Com_sprintf( strTemp, 128, "*muzzle%i", i + 1 ); + cent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp ); + if ( cent->m_pVehicle->m_iMuzzleTag[i] == -1 ) + {//ergh, try *flash? + Com_sprintf( strTemp, 128, "*flash%i", i + 1 ); + cent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp ); + } + } + + // Setup the Turrets. + for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ ) + { + if ( cent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag ) + { + cent->m_pVehicle->m_iGunnerViewTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, cent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag ); + } + else + { + cent->m_pVehicle->m_iGunnerViewTag[i] = -1; + } + } + } + + if (cent->currentState.npcSaber1) + { + saber = (char *)CG_ConfigString(CS_MODELS+cent->currentState.npcSaber1); + assert(!saber || !saber[0] || saber[0] == '@'); + //valid saber names should always start with '@' for NPCs + + if (saber && saber[0]) + { + saber++; //skip over the @ + WP_SetSaber(cent->currentState.number, cent->npcClient->saber, 0, saber); + } + } + if (cent->currentState.npcSaber2) + { + saber = (char *)CG_ConfigString(CS_MODELS+cent->currentState.npcSaber2); + assert(!saber || !saber[0] || saber[0] == '@'); + //valid saber names should always start with '@' for NPCs + + if (saber && saber[0]) + { + saber++; //skip over the @ + WP_SetSaber(cent->currentState.number, cent->npcClient->saber, 1, saber); + } + } + + // If this is a not vehicle, give it saber stuff... + if ( cent->currentState.NPC_class != CLASS_VEHICLE ) + { + while (j < MAX_SABERS) + { + if (cent->npcClient->saber[j].model[0]) + { + if (cent->npcClient->ghoul2Weapons[j]) + { //free the old instance(s) + trap_G2API_CleanGhoul2Models(¢->npcClient->ghoul2Weapons[j]); + cent->npcClient->ghoul2Weapons[j] = 0; + } + + CG_InitG2SaberData(j, cent->npcClient); + } + j++; + } + } + + trap_G2API_SetSkin(cent->ghoul2, 0, skinID, skinID); + + cent->localAnimIndex = -1; + + GLAName[0] = 0; + trap_G2API_GetGLAName(cent->ghoul2, 0, GLAName); + + strcpy(originalModelName, modelName); + + if (GLAName[0] && + !strstr(GLAName, "players/_humanoid/") /*&& + !strstr(GLAName, "players/rockettrooper/")*/) + { //it doesn't use humanoid anims. + slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + + cent->localAnimIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); + } + } + else + { //humanoid index. + trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand"); + trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand"); + + //rhand must always be first bolt. lhand always second. Whichever you want the + //jetpack bolted to must always be third. + trap_G2API_AddBolt(cent->ghoul2, 0, "*chestg"); + + + if (strstr(GLAName, "players/rockettrooper/")) + { + cent->localAnimIndex = 1; + } + else + { + cent->localAnimIndex = 0; + } + + if (trap_G2API_AddBolt(cent->ghoul2, 0, "*head_top") == -1) + { + trap_G2API_AddBolt(cent->ghoul2, 0, "ceyebrow"); + } + trap_G2API_AddBolt(cent->ghoul2, 0, "Motion"); + } + + // If this is a not vehicle... + if ( cent->currentState.NPC_class != CLASS_VEHICLE ) + { + if (trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar") == -1) + { //check now to see if we have this bone for setting anims and such + cent->noLumbar = qtrue; + } + + if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cent->noFace = qtrue; + } + } + else + { + cent->noLumbar = qtrue; + cent->noFace = qtrue; + } + + if (cent->localAnimIndex != -1) + { + slash = Q_strrchr( originalModelName, '/' ); + if ( slash ) + { + slash++; + *slash = 0; + } + + cent->eventAnimIndex = BG_ParseAnimationEvtFile(originalModelName, cent->localAnimIndex, bgNumAnimEvents); + } + } + } + + trap_S_ShutUp(qtrue); + CG_HandleNPCSounds(cent); //handle sound loading here as well. + trap_S_ShutUp(qfalse); +} + +//for now this is just gonna create a big explosion on the area of the surface, +//because I am lazy. +static void CG_CreateSurfaceDebris(centity_t *cent, int surfNum, int fxID, qboolean throwPart) +{ + int lostPartFX = 0; + int b; + vec3_t v, d; + mdxaBone_t boltMatrix; + const char *surfName = bgToggleableSurfaces[surfNum]; + + if (!cent->ghoul2) + { //oh no + return; + } + + //let's add the surface as a bolt so we can get the base point of it + if (bgToggleableSurfaceDebris[surfNum] == 3) + { //right wing flame + b = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_wingdamage"); + if ( throwPart + && cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo ) + { + lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iRWingFX; + } + } + else if (bgToggleableSurfaceDebris[surfNum] == 4) + { //left wing flame + b = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_wingdamage"); + if ( throwPart + && cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo ) + { + lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iLWingFX; + } + } + else if (bgToggleableSurfaceDebris[surfNum] == 5) + { //right wing flame 2 + b = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_wingdamage"); + if ( throwPart + && cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo ) + { + lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iRWingFX; + } + } + else if (bgToggleableSurfaceDebris[surfNum] == 6) + { //left wing flame 2 + b = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_wingdamage"); + if ( throwPart + && cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo ) + { + lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iLWingFX; + } + } + else if (bgToggleableSurfaceDebris[surfNum] == 7) + { //nose flame + b = trap_G2API_AddBolt(cent->ghoul2, 0, "*nosedamage"); + if ( cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo ) + { + lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iNoseFX; + } + } + else + { + b = trap_G2API_AddBolt(cent->ghoul2, 0, surfName); + } + + if (b == -1) + { //couldn't find this surface apparently + return; + } + + //now let's get the position and direction of this surface and make a big explosion + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg->time, + cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, v); + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_Z, d); + + trap_FX_PlayEffectID(fxID, v, d, -1, -1); + if ( throwPart && lostPartFX ) + {//throw off a ship part, too + vec3_t fxFwd; + AngleVectors( cent->lerpAngles, fxFwd, NULL, NULL ); + trap_FX_PlayEffectID(lostPartFX, v, fxFwd, -1, -1); + } +} + +//for now this is just gonna create a big explosion on the area of the surface, +//because I am lazy. +static void CG_CreateSurfaceSmoke(centity_t *cent, int shipSurf, int fxID) +{ + int b = -1; + vec3_t v, d; + mdxaBone_t boltMatrix; + const char *surfName = NULL; + + if (!cent->ghoul2) + { //oh no + return; + } + + //let's add the surface as a bolt so we can get the base point of it + if ( shipSurf == SHIPSURF_FRONT ) + { //front flame/smoke + surfName = "*nosedamage"; + } + else if (shipSurf == SHIPSURF_BACK ) + { //back flame/smoke + surfName = "*exhaust1";//FIXME: random? Some point in-between? + } + else if (shipSurf == SHIPSURF_RIGHT ) + { //right wing flame/smoke + surfName = "*r_wingdamage"; + } + else if (shipSurf == SHIPSURF_LEFT ) + { //left wing flame/smoke + surfName = "*l_wingdamage"; + } + else + {//unknown surf! + return; + } + b = trap_G2API_AddBolt(cent->ghoul2, 0, surfName); + if (b == -1) + { //couldn't find this surface apparently + return; + } + + //now let's get the position and direction of this surface and make a big explosion + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg->time, + cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, v); + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_Z, d); + + trap_FX_PlayEffectID(fxID, v, d, -1, -1); +} + +#define SMOOTH_G2ANIM_LERPANGLES + +qboolean CG_VehicleShouldDrawShields( centity_t *vehCent ) +{ + if ( vehCent->damageTime > cg->time //ship shields currently taking damage + && vehCent->currentState.NPC_class == CLASS_VEHICLE + && vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo ) + { + return qtrue; + } + return qfalse; +} + +/* +extern vmCvar_t cg_showVehBounds; +extern void BG_VehicleAdjustBBoxForOrientation( Vehicle_t *veh, vec3_t origin, vec3_t mins, vec3_t maxs, + int clientNum, int tracemask, + void (*localTrace)(trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask)); // bg_pmove.c +*/ +qboolean CG_VehicleAttachDroidUnit( centity_t *droidCent, refEntity_t *legs ) +{ + if ( droidCent + && droidCent->currentState.owner + && droidCent->currentState.clientNum >= MAX_CLIENTS ) + {//the only NPCs that can ride a vehicle are droids...??? + centity_t *vehCent = &cg_entities[droidCent->currentState.owner]; + if ( vehCent + && vehCent->m_pVehicle + && vehCent->ghoul2 + && vehCent->m_pVehicle->m_iDroidUnitTag != -1 ) + { + mdxaBone_t boltMatrix; + vec3_t fwd, rt, tempAng; + + trap_G2API_GetBoltMatrix(vehCent->ghoul2, 0, vehCent->m_pVehicle->m_iDroidUnitTag, &boltMatrix, vehCent->lerpAngles, vehCent->lerpOrigin, cg->time, + cgs.gameModels, vehCent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, droidCent->lerpOrigin); + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, fwd);//WTF??? + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, rt);//WTF??? + vectoangles( fwd, droidCent->lerpAngles ); + vectoangles( rt, tempAng ); + droidCent->lerpAngles[ROLL] = tempAng[PITCH]; + + return qtrue; + } + } + return qfalse; +} + +void CG_G2Animated( centity_t *cent ) +{ +#ifdef SMOOTH_G2ANIM_LERPANGLES + float angSmoothFactor = 0.7f; +#endif + + + if (!cent->ghoul2) + { //Initialize this g2 anim ent, then return (will start rendering next frame) + CG_G2AnimEntModelLoad(cent); + cent->npcLocalSurfOff = 0; + cent->npcLocalSurfOn = 0; + return; + } + + if (cent->npcLocalSurfOff != cent->currentState.surfacesOff || + cent->npcLocalSurfOn != cent->currentState.surfacesOn) + { //looks like it's time for an update. + int i = 0; + + while (i < BG_NUM_TOGGLEABLE_SURFACES && bgToggleableSurfaces[i]) + { + if (!(cent->npcLocalSurfOff & (1 << i)) && + (cent->currentState.surfacesOff & (1 << i))) + { //it wasn't off before but it's off now, so reflect this change in the g2 instance. + if (bgToggleableSurfaceDebris[i] > 0) + { //make some local debris of this thing? + //FIXME: throw off the proper model effect, too + CG_CreateSurfaceDebris(cent, i, cgs.effects.mShipDestDestroyed, qtrue); + } + + trap_G2API_SetSurfaceOnOff(cent->ghoul2, bgToggleableSurfaces[i], TURN_OFF); + } + + if (!(cent->npcLocalSurfOn & (1 << i)) && + (cent->currentState.surfacesOn & (1 << i))) + { //same as above, but on instead of off. + trap_G2API_SetSurfaceOnOff(cent->ghoul2, bgToggleableSurfaces[i], TURN_ON); + } + + i++; + } + + cent->npcLocalSurfOff = cent->currentState.surfacesOff; + cent->npcLocalSurfOn = cent->currentState.surfacesOn; + } + + + /* + if (cent->currentState.weapon && + !trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1) && + !(cent->currentState.eFlags & EF_DEAD)) + { //if the server says we have a weapon and we haven't copied one onto ourselves yet, then do so. + trap_G2API_CopySpecificGhoul2Model(g2WeaponInstances[cent->currentState.weapon], 0, cent->ghoul2, 1); + + if (cent->currentState.weapon == WP_SABER) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" )); + } + } + */ + + if (cent->torsoBolt && !(cent->currentState.eFlags & EF_DEAD)) + { //he's alive and has a limb missing still, reattach it and reset the weapon + CG_ReattachLimb(cent); + } + + if (((cent->currentState.eFlags & EF_DEAD) || (cent->currentState.eFlags & EF_RAG)) && !cent->localAnimIndex) + { + vec3_t forcedAngles; + + VectorClear(forcedAngles); + forcedAngles[YAW] = cent->lerpAngles[YAW]; + + CG_RagDoll(cent, forcedAngles); + } + +#ifdef SMOOTH_G2ANIM_LERPANGLES + if ((cent->lerpAngles[YAW] > 0 && cent->smoothYaw < 0) || + (cent->lerpAngles[YAW] < 0 && cent->smoothYaw > 0)) + { //keep it from snapping around on the threshold + cent->smoothYaw = -cent->smoothYaw; + } + cent->lerpAngles[YAW] = cent->smoothYaw+(cent->lerpAngles[YAW]-cent->smoothYaw)*angSmoothFactor; + cent->smoothYaw = cent->lerpAngles[YAW]; +#endif + + //now just render as a player + CG_Player(cent); + + /* + if ( cg_showVehBounds.integer ) + {//show vehicle bboxes + if ( cent->currentState.clientNum >= MAX_CLIENTS + && cent->currentState.NPC_class == CLASS_VEHICLE + && cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo + && cent->currentState.clientNum != cg->predictedVehicleState.clientNum ) + {//not the predicted vehicle + vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0}; + vec3_t absmin, absmax; + vec3_t bmins, bmaxs; + float *old = cent->m_pVehicle->m_vOrientation; + cent->m_pVehicle->m_vOrientation = ¢->lerpAngles[0]; + + BG_VehicleAdjustBBoxForOrientation( cent->m_pVehicle, cent->lerpOrigin, bmins, bmaxs, + cent->currentState.number, MASK_PLAYERSOLID, NULL ); + cent->m_pVehicle->m_vOrientation = old; + + VectorAdd( cent->lerpOrigin, bmins, absmin ); + VectorAdd( cent->lerpOrigin, bmaxs, absmax ); + CG_Cube( absmin, absmax, NPCDEBUG_RED, 0.25 ); + } + } + */ +} +//rww - here ends the majority of my g2animent stuff. + +//Disabled for now, I'm too lazy to keep it working with all the stuff changing around. +#if 0 +int cgFPLSState = 0; + +void CG_ForceFPLSPlayerModel(centity_t *cent, clientInfo_t *ci) +{ + animation_t *anim; + + if (cg_fpls.integer && !cg->renderingThirdPerson) + { + int skinHandle; + + skinHandle = trap_R_RegisterSkin("models/players/kyle/model_fpls2.skin"); + + trap_G2API_CleanGhoul2Models(&(ci->ghoul2Model)); + + ci->torsoSkin = skinHandle; + trap_G2API_InitGhoul2Model(&ci->ghoul2Model, "models/players/kyle/model.glm", 0, ci->torsoSkin, 0, 0, 0); + + ci->bolt_rhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand"); + + trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, cg->time, -1, -1); + trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, cg->time); + trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, cg->time); + + ci->bolt_lhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand"); + + //rhand must always be first bolt. lhand always second. Whichever you want the + //jetpack bolted to must always be third. + trap_G2API_AddBolt(ci->ghoul2Model, 0, "*chestg"); + + ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*head_top"); + if (ci->bolt_head == -1) + { + ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "ceyebrow"); + } + + ci->bolt_motion = trap_G2API_AddBolt(ci->ghoul2Model, 0, "Motion"); + + //We need a lower lumbar bolt for footsteps + ci->bolt_llumbar = trap_G2API_AddBolt(ci->ghoul2Model, 0, "lower_lumbar"); + + CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, ci->ghoul2Model); + } + else + { + CG_RegisterClientModelname(ci, ci->modelName, ci->skinName, ci->teamName, cent->currentState.number); + } + + anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.legsAnim ]; + + if (anim) + { + int flags = BONE_ANIM_OVERRIDE_FREEZE; + int firstFrame = anim->firstFrame; + int setFrame = -1; + float animSpeed = 50.0f / anim->frameLerp; + + if (anim->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (cent->pe.legs.frame >= anim->firstFrame && cent->pe.legs.frame <= (anim->firstFrame + anim->numFrames)) + { + setFrame = cent->pe.legs.frame; + } + + trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg->time, setFrame, 150); + + cent->currentState.legsAnim = 0; + } + + anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.torsoAnim ]; + + if (anim) + { + int flags = BONE_ANIM_OVERRIDE_FREEZE; + int firstFrame = anim->firstFrame; + int setFrame = -1; + float animSpeed = 50.0f / anim->frameLerp; + + if (anim->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (cent->pe.torso.frame >= anim->firstFrame && cent->pe.torso.frame <= (anim->firstFrame + anim->numFrames)) + { + setFrame = cent->pe.torso.frame; + } + + trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "lower_lumbar", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg->time, setFrame, 150); + + cent->currentState.torsoAnim = 0; + } + + trap_G2API_CleanGhoul2Models(&(cent->ghoul2)); + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, ¢->ghoul2); + + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse); +} +#endif + +//for allocating and freeing npc clientinfo structures. +//Remember to free this before game shutdown no matter what +//and don't stomp over it, as it is dynamic memory from the +//exe. +void CG_CreateNPCClient(clientInfo_t **ci) +{ + //trap_TrueMalloc((void **)ci, sizeof(clientInfo_t)); + *ci = (clientInfo_t *) BG_Alloc(sizeof(clientInfo_t)); +} + +void CG_DestroyNPCClient(clientInfo_t **ci) +{ + memset(*ci, 0, sizeof(clientInfo_t)); + //trap_TrueFree((void **)ci); +} + +static void CG_ForceElectrocution( centity_t *cent, const vec3_t origin, vec3_t tempAngles, qhandle_t shader, qboolean alwaysDo ) +{ + // Undoing for now, at least this code should compile if I ( or anyone else ) decides to work on this effect + qboolean found = qfalse; + vec3_t fxOrg, fxOrg2, dir; + vec3_t rgb; + mdxaBone_t boltMatrix; + trace_t tr; + int bolt=-1; + int iter=0; + int torsoBolt = -1; + int crotchBolt = -1; + int elbowLBolt = -1; + int elbowRBolt = -1; + int handLBolt = -1; + int handRBolt = -1; + int kneeLBolt = -1; + int kneeRBolt = -1; + int footLBolt = -1; + int footRBolt = -1; + + VectorSet(rgb, 1, 1, 1); + + if (cent->localAnimIndex <= 1) + { //humanoid + torsoBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar"); + crotchBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis"); + elbowLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_arm_elbow"); + elbowRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_arm_elbow"); + handLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand"); + handRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand"); + kneeLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hips_l_knee"); + kneeRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hips_r_knee"); + footLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_leg_foot"); + footRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_leg_foot"); + } + else if (cent->currentState.NPC_class == CLASS_PROTOCOL) + { //any others that can use these bolts too? + torsoBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar"); + crotchBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis"); + elbowLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*bicep_lg"); + elbowRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*bicep_rg"); + handLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hand_l"); + handRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*weapon"); + kneeLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*thigh_lg"); + kneeRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*thigh_rg"); + footLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*foot_lg"); + footRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*foot_rg"); + } + + // Pick a random start point + while (bolt<0) + { + int test; + if (iter>5) + { + test=iter-5; + } + else + { + test=Q_irand(0,6); + } + switch(test) + { + case 0: + // Right Elbow + bolt=elbowRBolt; + break; + case 1: + // Left Hand + bolt=handLBolt; + break; + case 2: + // Right hand + bolt=handRBolt; + break; + case 3: + // Left Foot + bolt=footLBolt; + break; + case 4: + // Right foot + bolt=footRBolt; + break; + case 5: + // Torso + bolt=torsoBolt; + break; + case 6: + default: + // Left Elbow + bolt=elbowLBolt; + break; + } + if (++iter==20) + break; + } + if (bolt>=0) + { + found = trap_G2API_GetBoltMatrix( cent->ghoul2, 0, bolt, + &boltMatrix, tempAngles, origin, cg->time, + cgs.gameModels, cent->modelScale); + } + + // Make sure that it's safe to even try and get these values out of the Matrix, otherwise the values could be garbage + if ( found ) + { + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, fxOrg ); + if ( random() > 0.5f ) + { + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, dir ); + } + else + { + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + } + + // Add some fudge, makes us not normalized, but that isn't really important + dir[0] += crandom() * 0.4f; + dir[1] += crandom() * 0.4f; + dir[2] += crandom() * 0.4f; + } + else + { + // Just use the lerp Origin and a random direction + VectorCopy( cent->lerpOrigin, fxOrg ); + VectorSet( dir, crandom(), crandom(), crandom() ); // Not normalized, but who cares. + switch ( cent->currentState.NPC_class ) + { + case CLASS_PROBE: + fxOrg[2] += 50; + break; + case CLASS_MARK1: + fxOrg[2] += 50; + break; + case CLASS_ATST: + fxOrg[2] += 120; + break; + default: + break; + } + } + + VectorMA( fxOrg, random() * 40 + 40, dir, fxOrg2 ); + + CG_Trace( &tr, fxOrg, NULL, NULL, fxOrg2, -1, CONTENTS_SOLID ); + + if ( tr.fraction < 1.0f || random() > 0.94f || alwaysDo ) + { + addElectricityArgStruct_t p; + + VectorCopy(fxOrg, p.start); + VectorCopy(tr.endpos, p.end); + p.size1 = 1.5f; + p.size2 = 4.0f; + p.sizeParm = 0.0f; + p.alpha1 = 1.0f; + p.alpha2 = 0.5f; + p.alphaParm = 0.0f; + VectorCopy(rgb, p.sRGB); + VectorCopy(rgb, p.eRGB); + p.rgbParm = 0.0f; + p.chaos = 5.0f; + p.killTime = (random() * 50 + 100); + p.shader = shader; + p.flags = (0x00000001 | 0x00000100 | 0x02000000 | 0x04000000 | 0x01000000); + + trap_FX_AddElectricity(&p); + + //In other words: + /* + FX_AddElectricity( fxOrg, tr.endpos, + 1.5f, 4.0f, 0.0f, + 1.0f, 0.5f, 0.0f, + rgb, rgb, 0.0f, + 5.5f, random() * 50 + 100, shader, FX_ALPHA_LINEAR | FX_SIZE_LINEAR | FX_BRANCH | FX_GROW | FX_TAPER ); + */ + } +} + +void *cg_g2JetpackInstance = NULL; + +#define JETPACK_MODEL "models/weapons2/jetpack/model.glm" + +void CG_InitJetpackGhoul2(void) +{ + if (cg_g2JetpackInstance) + { + assert(!"Tried to init jetpack inst, already init'd"); + return; + } + + trap_G2API_InitGhoul2Model(&cg_g2JetpackInstance, JETPACK_MODEL, 0, 0, 0, 0, 0); + + assert(cg_g2JetpackInstance); + + //Indicate which bolt on the player we will be attached to + //In this case bolt 0 is rhand, 1 is lhand, and 2 is the bolt + //for the jetpack (*chestg) + trap_G2API_SetBoltInfo(cg_g2JetpackInstance, 0, 2); + + //Add the bolts jet effects will be played from + trap_G2API_AddBolt(cg_g2JetpackInstance, 0, "torso_ljet"); + trap_G2API_AddBolt(cg_g2JetpackInstance, 0, "torso_rjet"); +} + +void CG_CleanJetpackGhoul2(void) +{ + if (cg_g2JetpackInstance) + { + trap_G2API_CleanGhoul2Models(&cg_g2JetpackInstance); + cg_g2JetpackInstance = NULL; + } +} + +#define RARMBIT (1 << (G2_MODELPART_RARM-10)) +#define RHANDBIT (1 << (G2_MODELPART_RHAND-10)) +#define WAISTBIT (1 << (G2_MODELPART_WAIST-10)) + +#if 0 +static void CG_VehicleHeatEffect( vec3_t org, centity_t *cent ) +{ + refEntity_t ent; + vec3_t ang; + float scale; + float vLen; + float alpha; + + if (!cg_renderToTextureFX.integer) + { + return; + } + scale = 0.1f; + + alpha = 200.0f; + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( org, ent.origin ); + + VectorSubtract(ent.origin, cg->refdef.vieworg, ent.axis[0]); + vLen = VectorLength(ent.axis[0]); + if (VectorNormalize(ent.axis[0]) <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + + vectoangles(ent.axis[0], ang); + AnglesToAxis(ang, ent.axis); + + //radius must be a power of 2, and is the actual captured texture size + ent.radius = 32; + + VectorScale(ent.axis[0], scale, ent.axis[0]); + VectorScale(ent.axis[1], scale, ent.axis[1]); + VectorScale(ent.axis[2], -scale, ent.axis[2]); + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = cgs.media.cloakedShader; + + //make it partially transparent so it blends with the background + ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); + ent.shaderRGBA[0] = 255.0f; + ent.shaderRGBA[1] = 255.0f; + ent.shaderRGBA[2] = 255.0f; + ent.shaderRGBA[3] = alpha; + + trap_R_AddRefEntityToScene( &ent ); +} +#endif + +static int lastFlyBySound[MAX_GENTITIES] = {0}; +#define FLYBYSOUNDTIME 2000 +int cg_lastHyperSpaceEffectTime = 0; +static CGAME_INLINE void CG_VehicleEffects(centity_t *cent) +{ + Vehicle_t *pVehNPC; + + if (cent->currentState.eType != ET_NPC || + cent->currentState.NPC_class != CLASS_VEHICLE || + !cent->m_pVehicle) + { + return; + } + + pVehNPC = cent->m_pVehicle; + + if ( cent->currentState.clientNum == cg->predictedPlayerState.m_iVehicleNum//my vehicle + && (cent->currentState.eFlags2&EF2_HYPERSPACE) )//hyperspacing + {//in hyperspace! + if ( cg->predictedVehicleState.hyperSpaceTime + && (cg->time-cg->predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME ) + { + if ( !cg_lastHyperSpaceEffectTime + || (cg->time - cg_lastHyperSpaceEffectTime) > HYPERSPACE_TIME+500 ) + {//can't be from the last time we were in hyperspace, so play the effect! + trap_FX_PlayBoltedEffectID( cgs.effects.mHyperspaceStars, cent->lerpOrigin, cent->ghoul2, 0, + cent->currentState.number, 0, 0, qtrue ); + cg_lastHyperSpaceEffectTime = cg->time; + } + } + } + + //FLYBY sound + if ( cent->currentState.clientNum != cg->predictedPlayerState.m_iVehicleNum + && (pVehNPC->m_pVehicleInfo->soundFlyBy||pVehNPC->m_pVehicleInfo->soundFlyBy2) ) + {//not my vehicle + if ( cent->currentState.speed && cg->predictedPlayerState.speed+cent->currentState.speed > 500 ) + {//he's moving and between the two of us, we're moving fast + vec3_t diff; + VectorSubtract( cent->lerpOrigin, cg->predictedPlayerState.origin, diff ); + if ( VectorLength( diff ) < 2048 ) + {//close + vec3_t myFwd, theirFwd; + AngleVectors( cg->predictedPlayerState.viewangles, myFwd, NULL, NULL ); + VectorScale( myFwd, cg->predictedPlayerState.speed, myFwd ); + AngleVectors( cent->lerpAngles, theirFwd, NULL, NULL ); + VectorScale( theirFwd, cent->currentState.speed, theirFwd ); + if ( lastFlyBySound[cent->currentState.clientNum]+FLYBYSOUNDTIME < cg->time ) + {//okay to do a flyby sound on this vehicle + if ( DotProduct( myFwd, theirFwd ) < 500 ) + { + int flyBySound = 0; + if ( pVehNPC->m_pVehicleInfo->soundFlyBy && pVehNPC->m_pVehicleInfo->soundFlyBy2 ) + { + flyBySound = Q_irand(0,1)?pVehNPC->m_pVehicleInfo->soundFlyBy:pVehNPC->m_pVehicleInfo->soundFlyBy2; + } + else if ( pVehNPC->m_pVehicleInfo->soundFlyBy ) + { + flyBySound = pVehNPC->m_pVehicleInfo->soundFlyBy; + } + else //if ( pVehNPC->m_pVehicleInfo->soundFlyBy2 ) + { + flyBySound = pVehNPC->m_pVehicleInfo->soundFlyBy2; + } + trap_S_StartSound(NULL, cent->currentState.clientNum, CHAN_LESS_ATTEN, flyBySound ); + lastFlyBySound[cent->currentState.clientNum] = cg->time; + } + } + } + } + } + + if ( !cent->currentState.speed//was stopped + && cent->nextState.speed > 0//now moving forward + && cent->m_pVehicle->m_pVehicleInfo->soundEngineStart ) + {//engines rev up for the first time + trap_S_StartSound(NULL, cent->currentState.clientNum, CHAN_LESS_ATTEN, cent->m_pVehicle->m_pVehicleInfo->soundEngineStart ); + } + // Animals don't exude any effects... + if ( pVehNPC->m_pVehicleInfo->type != VH_ANIMAL ) + { + if (pVehNPC->m_pVehicleInfo->surfDestruction && cent->ghoul2) + { //see if anything has been blown off + int i = 0; + qboolean surfDmg = qfalse; + + while (i < BG_NUM_TOGGLEABLE_SURFACES) + { + if (bgToggleableSurfaceDebris[i] > 1) + { //this is decidedly a destroyable surface, let's check its status + int surfTest = trap_G2API_GetSurfaceRenderStatus(cent->ghoul2, 0, bgToggleableSurfaces[i]); + + if ( surfTest != -1 + && (surfTest&TURN_OFF) ) + { //it exists, but it's off... + surfDmg = qtrue; + + //create some flames + CG_CreateSurfaceDebris(cent, i, cgs.effects.mShipDestBurning, qfalse); + } + } + + i++; + } + + if (surfDmg) + { //if any surface are damaged, neglect exhaust etc effects (so we don't have exhaust trails coming out of invisible surfaces) + return; + } + } + + if ( pVehNPC->m_iLastFXTime <= cg->time ) + {//until we attach it, we need to debounce this + vec3_t fwd, rt, up; + vec3_t flat; + float nextFXDelay = 50; + VectorSet(flat, 0, cent->lerpAngles[1], cent->lerpAngles[2]); + AngleVectors( flat, fwd, rt, up ); + if ( cent->currentState.speed > 0 ) + {//FIXME: only do this when accelerator is being pressed! (must have a driver?) + vec3_t org; + qboolean doExhaust = qfalse; + VectorMA( cent->lerpOrigin, -16, up, org ); + VectorMA( org, -42, fwd, org ); + // Play damage effects. + //if ( pVehNPC->m_iArmor <= 75 ) + if (0) + {//hurt + trap_FX_PlayEffectID( cgs.effects.mBlackSmoke, org, fwd, -1, -1 ); + } + else if ( pVehNPC->m_pVehicleInfo->iTrailFX ) + {//okay, do normal trail + trap_FX_PlayEffectID( pVehNPC->m_pVehicleInfo->iTrailFX, org, fwd, -1, -1 ); + } + //===================================================================== + //EXHAUST FX + //===================================================================== + //do exhaust + if ( (cent->currentState.eFlags&EF_JETPACK_ACTIVE) ) + {//cheap way of telling us the vehicle is in "turbo" mode + doExhaust = (pVehNPC->m_pVehicleInfo->iTurboFX!=0); + } + else + { + doExhaust = (pVehNPC->m_pVehicleInfo->iExhaustFX!=0); + } + if ( doExhaust && cent->ghoul2 ) + { + int i; + int fx; + + for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ ) + { + // We hit an invalid tag, we quit (they should be created in order so tough luck if not). + if ( pVehNPC->m_iExhaustTag[i] == -1 ) + { + break; + } + + if ( (cent->currentState.brokenLimbs&(1<currentState.brokenLimbs&(1<currentState.eFlags&EF_JETPACK_ACTIVE) //cheap way of telling us the vehicle is in "turbo" mode + && pVehNPC->m_pVehicleInfo->iTurboFX )//they have a valid turbo exhaust effect to play + { + fx = pVehNPC->m_pVehicleInfo->iTurboFX; + } + else + {//play the normal one + fx = pVehNPC->m_pVehicleInfo->iExhaustFX; + } + + if (pVehNPC->m_pVehicleInfo->type == VH_FIGHTER) + { + trap_FX_PlayBoltedEffectID(fx, cent->lerpOrigin, cent->ghoul2, pVehNPC->m_iExhaustTag[i], + cent->currentState.number, 0, 0, qtrue); + } + else + { //fixme: bolt these too + mdxaBone_t boltMatrix; + vec3_t boltOrg, boltDir; + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, pVehNPC->m_iExhaustTag[i], &boltMatrix, flat, + cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + VectorCopy(fwd, boltDir); //fixme? + + trap_FX_PlayEffectID( fx, boltOrg, boltDir, -1, -1 ); + } + } + } + //===================================================================== + //WING TRAIL FX + //===================================================================== + //do trail + //FIXME: not in space!!! + if ( pVehNPC->m_pVehicleInfo->iTrailFX != 0 && cent->ghoul2 ) + { + int i; + vec3_t boltOrg, boltDir; + mdxaBone_t boltMatrix; + vec3_t getBoltAngles; + + VectorCopy(cent->lerpAngles, getBoltAngles); + if (pVehNPC->m_pVehicleInfo->type != VH_FIGHTER) + { //only fighters use pitch/roll in refent axis + getBoltAngles[PITCH] = getBoltAngles[ROLL] = 0.0f; + } + + for ( i = 1; i < 5; i++ ) + { + int trailBolt = trap_G2API_AddBolt(cent->ghoul2, 0, va("*trail%d",i) ); + // We hit an invalid tag, we quit (they should be created in order so tough luck if not). + if ( trailBolt == -1 ) + { + break; + } + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, trailBolt, &boltMatrix, getBoltAngles, + cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + VectorCopy(fwd, boltDir); //fixme? + + trap_FX_PlayEffectID( pVehNPC->m_pVehicleInfo->iTrailFX, boltOrg, boltDir, -1, -1 ); + } + } + } + //FIXME armor needs to be sent over network + { + if ( (cent->currentState.eFlags&EF_DEAD) ) + {//just plain dead, use flames + vec3_t up ={0,0,1}; + vec3_t boltOrg; + + //if ( pVehNPC->m_iDriverTag == -1 ) + {//doh! no tag + VectorCopy( cent->lerpOrigin, boltOrg ); + } + //else + //{ + // mdxaBone_t boltMatrix; + // vec3_t getBoltAngles; + + // VectorCopy(cent->lerpAngles, getBoltAngles); + // if (pVehNPC->m_pVehicleInfo->type != VH_FIGHTER) + // { //only fighters use pitch/roll in refent axis + // getBoltAngles[PITCH] = getBoltAngles[ROLL] = 0.0f; + // } + + // trap_G2API_GetBoltMatrix(cent->ghoul2, 0, pVehNPC->m_iDriverTag, &boltMatrix, getBoltAngles, + // cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + + // BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + //} + trap_FX_PlayEffectID( cgs.effects.mShipDestBurning, boltOrg, up, -1, -1 ); + } + } + if ( cent->currentState.brokenLimbs ) + { + int i; + if ( !Q_irand( 0, 5 ) ) + { + for ( i = SHIPSURF_FRONT; i <= SHIPSURF_LEFT; i++ ) + { + if ( (cent->currentState.brokenLimbs&(1<<((i-SHIPSURF_FRONT)+SHIPSURF_DAMAGE_FRONT_HEAVY))) ) + {//heavy damage, do both effects + if ( pVehNPC->m_pVehicleInfo->iInjureFX ) + { + CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iInjureFX ); + } + if ( pVehNPC->m_pVehicleInfo->iDmgFX ) + { + CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iDmgFX ); + } + } + else if ( (cent->currentState.brokenLimbs&(1<<((i-SHIPSURF_FRONT)+SHIPSURF_DAMAGE_FRONT_LIGHT))) ) + {//only light damage + if ( pVehNPC->m_pVehicleInfo->iInjureFX ) + { + CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iInjureFX ); + } + } + } + } + } + /* + if ( pVehNPC->m_iArmor <= 50 ) + {//FIXME: use as a proportion of max armor? + VectorMA( cent->lerpOrigin, 64, fwd, org ); + VectorScale( fwd, -1, fwd ); + + trap_FX_PlayEffectID( cgs.effects.mBlackSmoke, org, fwd, -1, -1 ); + } + if ( pVehNPC->m_iArmor <= 0 ) + {//FIXME: should use something attached.. but want it to build up over time, so... + if ( flrand( 0, cg->time - pVehNPC->m_iDieTime ) < 1000 ) + {//flaming! + VectorMA( cent->lerpOrigin, flrand(-64, 64), fwd, org ); + VectorScale( fwd, -1, fwd ); + trap_FX_PlayEffectID( trap_FX_RegisterEffect("ships/fire"), org, fwd, -1, -1 ); + nextFXDelay = 50; + } + } + */ + pVehNPC->m_iLastFXTime = cg->time + nextFXDelay; + } + } +} + +/* +=============== +CG_Player +=============== +*/ +#include "../namespace_begin.h" +int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); +#include "../namespace_end.h" + +float CG_RadiusForCent( centity_t *cent ) +{ + if ( cent->currentState.eType == ET_NPC ) + { + if (cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle && + cent->m_pVehicle->m_pVehicleInfo->g2radius) + { //has override + return cent->m_pVehicle->m_pVehicleInfo->g2radius; + } + else if ( cent->currentState.g2radius ) + { + return cent->currentState.g2radius; + } + } + else if ( cent->currentState.g2radius ) + { + return cent->currentState.g2radius; + } + return 64.0f; +} + +static float cg_vehThirdPersonAlpha = 1.0f; +extern vec3_t cg_crosshairPos; +extern vec3_t cameraCurLoc; +void CG_CheckThirdPersonAlpha( centity_t *cent, refEntity_t *legs ) +{ + float alpha = 1.0f; + int setFlags = 0; + + if ( cent->m_pVehicle ) + {//a vehicle + if ( cg->predictedPlayerState.m_iVehicleNum != cent->currentState.clientNum//not mine + && cent->m_pVehicle->m_pVehicleInfo + && cent->m_pVehicle->m_pVehicleInfo->cameraOverride + && cent->m_pVehicle->m_pVehicleInfo->cameraAlpha )//it has alpha + {//make sure it's not using any alpha + legs->renderfx |= RF_FORCE_ENT_ALPHA; + legs->shaderRGBA[3] = 255; + return; + } + } + + if ( !cg->renderingThirdPerson ) + { + return; + } + + if ( cg->predictedPlayerState.m_iVehicleNum ) + {//in a vehicle + if ( cg->predictedPlayerState.m_iVehicleNum == cent->currentState.clientNum ) + {//this is my vehicle + if ( cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo + && cent->m_pVehicle->m_pVehicleInfo->cameraOverride + && cent->m_pVehicle->m_pVehicleInfo->cameraAlpha ) + {//vehicle has auto third-person alpha on + trace_t trace; + vec3_t dir2Crosshair, end; +#ifdef _XBOX + VectorSubtract( cg_crosshairPos, ClientManager::ActiveClient().cameraCurLoc, dir2Crosshair ); +#else + VectorSubtract( cg_crosshairPos, cameraCurLoc, dir2Crosshair ); +#endif + VectorNormalize( dir2Crosshair ); +#ifdef _XBOX + VectorMA( ClientManager::ActiveClient().cameraCurLoc, cent->m_pVehicle->m_pVehicleInfo->cameraRange*2.0f, dir2Crosshair, end ); + CG_G2Trace( &trace, ClientManager::ActiveClient().cameraCurLoc, vec3_origin, vec3_origin, end, ENTITYNUM_NONE, CONTENTS_BODY ); +#else + VectorMA( cameraCurLoc, cent->m_pVehicle->m_pVehicleInfo->cameraRange*2.0f, dir2Crosshair, end ); + CG_G2Trace( &trace, cameraCurLoc, vec3_origin, vec3_origin, end, ENTITYNUM_NONE, CONTENTS_BODY ); +#endif + if ( trace.entityNum == cent->currentState.clientNum + || trace.entityNum == cg->predictedPlayerState.clientNum) + {//hit me or the vehicle I'm in + cg_vehThirdPersonAlpha -= 0.1f*cg->frametime/50.0f; + if ( cg_vehThirdPersonAlpha < cent->m_pVehicle->m_pVehicleInfo->cameraAlpha ) + { + cg_vehThirdPersonAlpha = cent->m_pVehicle->m_pVehicleInfo->cameraAlpha; + } + } + else + { + cg_vehThirdPersonAlpha += 0.1f*cg->frametime/50.0f; + if ( cg_vehThirdPersonAlpha > 1.0f ) + { + cg_vehThirdPersonAlpha = 1.0f; + } + } + alpha = cg_vehThirdPersonAlpha; + } + else + {//use the cvar + //reset this + cg_vehThirdPersonAlpha = 1.0f; + //use the cvar + alpha = cg_thirdPersonAlpha.value; +#ifdef _XBOX + alpha = ClientManager::ActiveClient().cg_thirdPersonAlpha; +#endif + } + } + } + else if ( cg->predictedPlayerState.clientNum == cent->currentState.clientNum ) + {//it's me + //reset this + cg_vehThirdPersonAlpha = 1.0f; + //use the cvar + setFlags = RF_FORCE_ENT_ALPHA; + alpha = cg_thirdPersonAlpha.value; +#ifdef _XBOX + alpha = ClientManager::ActiveClient().cg_thirdPersonAlpha; +#endif + } + + if ( alpha < 1.0f ) + { + legs->renderfx |= setFlags; + legs->shaderRGBA[3] = (unsigned char)(alpha * 255.0f); + } +} + +void CG_Player( centity_t *cent ) { + clientInfo_t *ci; + refEntity_t legs; + refEntity_t torso; + int clientNum; + int renderfx; + qboolean shadow = qfalse; + float shadowPlane = 0; + qboolean dead = qfalse; + vec3_t rootAngles; + float angle; + vec3_t angles, dir, elevated, enang, seekorg; + int iwantout = 0, successchange = 0; + int team; + mdxaBone_t boltMatrix, lHandMatrix; + int doAlpha = 0; + qboolean gotLHandMatrix = qfalse; + qboolean g2HasWeapon = qfalse; + qboolean drawPlayerSaber = qfalse; + qboolean checkDroidShields = qfalse; + + //first if we are not an npc and we are using an emplaced gun then make sure our + //angles are visually capped to the constraints (otherwise it's possible to lerp + //a little outside and look kind of twitchy) +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (cent->currentState.weapon == WP_EMPLACED_GUN && + cent->currentState.otherEntityNum2) + { + float empYaw; + + if (BG_EmplacedView(cent->lerpAngles, cg_entities[cent->currentState.otherEntityNum2].currentState.angles, &empYaw, cg_entities[cent->currentState.otherEntityNum2].currentState.origin2[0])) + { + cent->lerpAngles[YAW] = empYaw; + } + } +#ifdef _XBOX + } +#endif + + if (cent->currentState.iModelScale) + { //if the server says we have a custom scale then set it now. + cent->modelScale[0] = cent->modelScale[1] = cent->modelScale[2] = cent->currentState.iModelScale/100.0f; + } + else + { + VectorClear(cent->modelScale); + } + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if ((cg_smoothClients.integer || cent->currentState.heldByClient) && (cent->currentState.groundEntityNum >= ENTITYNUM_WORLD || cent->currentState.eType == ET_TERRAIN) && + !(cent->currentState.eFlags2 & EF2_HYPERSPACE) && cg->predictedPlayerState.m_iVehicleNum != cent->currentState.number) + { //always smooth when being thrown + vec3_t posDif; + float smoothFactor; + int k = 0; + float fTolerance = 20000.0f; + + if (cent->currentState.heldByClient) + { //smooth the origin more when in this state, because movement is origin-based on server. + smoothFactor = 0.2f; + } + else if ( (cent->currentState.powerups & (1 << PW_SPEED)) || + (cent->currentState.forcePowersActive & (1 << FP_RAGE)) ) + { //we're moving fast so don't smooth as much + smoothFactor = 0.6f; + } + else if (cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { //greater smoothing for flying vehicles, since they move so fast + fTolerance = 6000000.0f;//500000.0f; //yeah, this is so wrong..but.. + smoothFactor = 0.5f; + } + else + { + smoothFactor = 0.5f; + } + + if (DistanceSquared(cent->beamEnd,cent->lerpOrigin) > smoothFactor*fTolerance) //10000 + { + VectorCopy(cent->lerpOrigin, cent->beamEnd); + } + + VectorSubtract(cent->lerpOrigin, cent->beamEnd, posDif); + + for (k=0;k<3;k++) + { + cent->beamEnd[k]=(cent->beamEnd[k]+posDif[k]*smoothFactor); + cent->lerpOrigin[k]=cent->beamEnd[k]; + } + } + else + { + VectorCopy(cent->lerpOrigin, cent->beamEnd); + } +#ifdef _XBOX + } +#endif + + if (cent->currentState.m_iVehicleNum && + cent->currentState.NPC_class != CLASS_VEHICLE) + { //this player is riding a vehicle + centity_t *veh = &cg_entities[cent->currentState.m_iVehicleNum]; + + cent->lerpAngles[YAW] = veh->lerpAngles[YAW]; + + //Attach ourself to the vehicle + if (veh->m_pVehicle && + cent->playerState && + veh->playerState && + cent->ghoul2 && + veh->ghoul2 ) + { + if ( veh->currentState.owner != cent->currentState.clientNum ) + {//FIXME: what about visible passengers? + if ( CG_VehicleAttachDroidUnit( cent, &legs ) ) + { + checkDroidShields = qtrue; + } + } + else if ( veh->currentState.owner != ENTITYNUM_NONE) + {//has a pilot...??? + vec3_t oldPSOrg; + + //make sure it has its pilot and parent set + veh->m_pVehicle->m_pPilot = (bgEntity_t *)&cg_entities[veh->currentState.owner]; + veh->m_pVehicle->m_pParentEntity = (bgEntity_t *)veh; + + VectorCopy(veh->playerState->origin, oldPSOrg); + + //update the veh's playerstate org for getting the bolt + VectorCopy(veh->lerpOrigin, veh->playerState->origin); + VectorCopy(cent->lerpOrigin, cent->playerState->origin); + + //Now do the attach + VectorCopy(veh->lerpAngles, veh->playerState->viewangles); + veh->m_pVehicle->m_pVehicleInfo->AttachRiders(veh->m_pVehicle); + + //copy the "playerstate origin" to the lerpOrigin since that's what we use to display + VectorCopy(cent->playerState->origin, cent->lerpOrigin); + + VectorCopy(oldPSOrg, veh->playerState->origin); + } + } + } + + // the client number is stored in clientNum. It can't be derived + // from the entity number, because a single client may have + // multiple corpses on the level using the same clientinfo + if (cent->currentState.eType != ET_NPC) + { + clientNum = cent->currentState.clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + CG_Error( "Bad clientNum on player entity"); + } + ci = &cgs.clientinfo[ clientNum ]; + } + else + { + if (!cent->npcClient) + { + CG_CreateNPCClient(¢->npcClient); //allocate memory for it + + if (!cent->npcClient) + { + assert(0); + return; + } + + memset(cent->npcClient, 0, sizeof(clientInfo_t)); + cent->npcClient->ghoul2Model = NULL; + } + + assert(cent->npcClient); + + if (cent->npcClient->ghoul2Model != cent->ghoul2 && cent->ghoul2) + { + cent->npcClient->ghoul2Model = cent->ghoul2; + if (cent->localAnimIndex <= 1) + { + cent->npcClient->bolt_rhand = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*r_hand"); + cent->npcClient->bolt_lhand = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*l_hand"); + + //rhand must always be first bolt. lhand always second. Whichever you want the + //jetpack bolted to must always be third. + trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*chestg"); + + cent->npcClient->bolt_head = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*head_top"); + if (cent->npcClient->bolt_head == -1) + { + cent->npcClient->bolt_head = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "ceyebrow"); + } + cent->npcClient->bolt_motion = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "Motion"); + cent->npcClient->bolt_llumbar = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "lower_lumbar"); + } + else + { + cent->npcClient->bolt_rhand = -1; + cent->npcClient->bolt_lhand = -1; + cent->npcClient->bolt_head = -1; + cent->npcClient->bolt_motion = -1; + cent->npcClient->bolt_llumbar = -1; + } + cent->npcClient->team = TEAM_FREE; + cent->npcClient->infoValid = qtrue; + } + ci = cent->npcClient; + } + + // it is possible to see corpses from disconnected players that may + // not have valid clientinfo + if ( !ci->infoValid ) { + return; + } + + // Add the player to the radar if on the same team and its a team game +#ifdef _XBOX + if(cent->updatedThisFrame == false || (ClientManager::splitScreenMode && ClientManager::ActiveClientNum() == 1)) { +#endif + if (cgs.gametype >= GT_TEAM) + { + if ( cent->currentState.eType != ET_NPC && + cg->snap->ps.clientNum != cent->currentState.number && + ci->team == cg->snap->ps.persistant[PERS_TEAM] ) + { + CG_AddRadarEnt(cent); + } + } + + if (cent->currentState.eType == ET_NPC && + cent->currentState.NPC_class == CLASS_VEHICLE) + { //add vehicles + CG_AddRadarEnt(cent); + if ( CG_InFighter() ) + {//this is a vehicle, bracket it + if ( cg->predictedPlayerState.m_iVehicleNum != cent->currentState.clientNum ) + {//don't add the vehicle I'm in... :) + CG_AddBracketedEnt(cent); + } + } + + } +#ifdef _XBOX + } +#endif + + if (!cent->ghoul2) + { //not ready yet? +#ifdef _DEBUG + Com_Printf("WARNING: Client %i has a null ghoul2 instance\n", cent->currentState.number); +#endif + trap_G2API_ClearAttachedInstance(cent->currentState.number); + + if (ci->ghoul2Model && + trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { +#ifdef _DEBUG + Com_Printf("Clientinfo instance was valid, duplicating for cent\n"); +#endif + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, ¢->ghoul2); + + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse); + + if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cent->noFace = qtrue; + } + + cent->localAnimIndex = CG_G2SkelForModel(cent->ghoul2); + cent->eventAnimIndex = CG_G2EvIndexForModel(cent->ghoul2, cent->localAnimIndex); + } + return; + } + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (ci->superSmoothTime) + { //do crazy smoothing + if (ci->superSmoothTime > cg->time) + { //do it + trap_G2API_AbsurdSmoothing(cent->ghoul2, qtrue); + } + else + { //turn it off + ci->superSmoothTime = 0; + trap_G2API_AbsurdSmoothing(cent->ghoul2, qfalse); + } + } +#ifdef _XBOX + } +#endif + + if (cg->predictedPlayerState.pm_type == PM_INTERMISSION) + { //don't show all this shit during intermission + return; + } + + CG_VehicleEffects(cent); + + if ((cent->currentState.eFlags & EF_JETPACK) && !(cent->currentState.eFlags & EF_DEAD) && + cg_g2JetpackInstance) + { //should have a jetpack attached + //1 is rhand weap, 2 is lhand weap (akimbo sabs), 3 is jetpack + if (!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 3)) + { + trap_G2API_CopySpecificGhoul2Model(cg_g2JetpackInstance, 0, cent->ghoul2, 3); + } + + if (cent->currentState.eFlags & EF_JETPACK_ACTIVE) + { + mdxaBone_t mat; + vec3_t flamePos, flameDir; + int n = 0; + + while (n < 2) + { + //Get the position/dir of the flame bolt on the jetpack model bolted to the player + trap_G2API_GetBoltMatrix(cent->ghoul2, 3, n, &mat, cent->turAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&mat, ORIGIN, flamePos); + + if (n == 0) + { + BG_GiveMeVectorFromMatrix(&mat, NEGATIVE_Y, flameDir); + VectorMA(flamePos, -9.5f, flameDir, flamePos); + BG_GiveMeVectorFromMatrix(&mat, POSITIVE_X, flameDir); + VectorMA(flamePos, -13.5f, flameDir, flamePos); + } + else + { + BG_GiveMeVectorFromMatrix(&mat, POSITIVE_X, flameDir); + VectorMA(flamePos, -9.5f, flameDir, flamePos); + BG_GiveMeVectorFromMatrix(&mat, NEGATIVE_Y, flameDir); + VectorMA(flamePos, -13.5f, flameDir, flamePos); + } + + if (cent->currentState.eFlags & EF_JETPACK_FLAMING) + { //create effects + //FIXME: Just one big effect + //Play the effect + trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1); + trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1); + + //Keep the jet fire sound looping + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + trap_S_RegisterSound( "sound/effects/fire_lp" ) ); + } + else + { //just idling + //FIXME: Different smaller effect for idle + //Play the effect + trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1); + } + + n++; + } + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + trap_S_RegisterSound( "sound/boba/JETHOVER" ) ); + } + } + else if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 3)) + { //fixme: would be good if this could be done not every frame +#ifdef _XBOX + if(cent->updatedThisFrame == false) +#endif + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 3); + } + + g2HasWeapon = trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1); + + if (!g2HasWeapon) + { //force a redup of the weapon instance onto the client instance + cent->ghoul2weapon = NULL; + cent->weapon = 0; + } + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (cent->torsoBolt && !(cent->currentState.eFlags & EF_DEAD)) + { //he's alive and has a limb missing still, reattach it and reset the weapon + CG_ReattachLimb(cent); + } + + if (cent->isRagging && !(cent->currentState.eFlags & EF_DEAD) && !(cent->currentState.eFlags & EF_RAG)) + { //make sure we don't ragdoll ever while alive unless directly told to with eFlags + cent->isRagging = qfalse; + trap_G2API_SetRagDoll(cent->ghoul2, NULL); //calling with null parms resets to no ragdoll. + } + + if (cent->ghoul2 && cent->torsoBolt && ((cent->torsoBolt & RARMBIT) || (cent->torsoBolt & RHANDBIT) || (cent->torsoBolt & WAISTBIT)) && g2HasWeapon) + { //kill the weapon if the limb holding it is no longer on the model + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + g2HasWeapon = qfalse; + } +#ifdef _XBOX + } +#endif + +#ifdef _XBOX + if (!cent->trickAlphaTime[ClientManager::ActiveClientNum()] || (cg->time - cent->trickAlphaTime[ClientManager::ActiveClientNum()]) > 1000) + { //things got out of sync, perhaps a new client is trying to fill in this slot + cent->trickAlpha[ClientManager::ActiveClientNum()] = 255; + cent->trickAlphaTime[ClientManager::ActiveClientNum()] = cg->time; + } +#else + if (!cent->trickAlphaTime || (cg->time - cent->trickAlphaTime) > 1000) + { //things got out of sync, perhaps a new client is trying to fill in this slot + cent->trickAlpha = 255; + cent->trickAlphaTime = cg->time; + } +#endif + + if (cent->currentState.eFlags & EF_NODRAW) + { //If nodraw, return here + return; + } + else if (cent->currentState.eFlags2 & EF2_SHIP_DEATH) + { //died in ship, don't draw, we were "obliterated" + return; + } + + //If this client has tricked you. +#ifdef _XBOX + if (CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg->snap->ps.clientNum)) + { + if (cent->trickAlpha[ClientManager::ActiveClientNum()] > 1) + { + cent->trickAlpha[ClientManager::ActiveClientNum()] -= (cg->time - cent->trickAlphaTime[ClientManager::ActiveClientNum()])*0.5; + cent->trickAlphaTime[ClientManager::ActiveClientNum()] = cg->time; + + if (cent->trickAlpha[ClientManager::ActiveClientNum()] < 0) + { + cent->trickAlpha[ClientManager::ActiveClientNum()] = 0; + } + + doAlpha = 1; + } + else + { + doAlpha = 1; + cent->trickAlpha[ClientManager::ActiveClientNum()] = 1; + cent->trickAlphaTime[ClientManager::ActiveClientNum()] = cg->time; + iwantout = 1; + } + } + else + { + if (cent->trickAlpha[ClientManager::ActiveClientNum()] < 255) + { + cent->trickAlpha[ClientManager::ActiveClientNum()] += (cg->time - cent->trickAlphaTime[ClientManager::ActiveClientNum()]); + cent->trickAlphaTime[ClientManager::ActiveClientNum()] = cg->time; + + if (cent->trickAlpha[ClientManager::ActiveClientNum()] > 255) + { + cent->trickAlpha[ClientManager::ActiveClientNum()] = 255; + } + + doAlpha = 1; + } + else + { + cent->trickAlpha[ClientManager::ActiveClientNum()] = 255; + cent->trickAlphaTime[ClientManager::ActiveClientNum()] = cg->time; + } + } +#else + if (CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg->snap->ps.clientNum)) + { + if (cent->trickAlpha > 1) + { + cent->trickAlpha -= (cg->time - cent->trickAlphaTime)*0.5; + cent->trickAlphaTime = cg->time; + + if (cent->trickAlpha < 0) + { + cent->trickAlpha = 0; + } + + doAlpha = 1; + } + else + { + doAlpha = 1; + cent->trickAlpha = 1; + cent->trickAlphaTime = cg->time; + iwantout = 1; + } + } + else + { + if (cent->trickAlpha < 255) + { + cent->trickAlpha += (cg->time - cent->trickAlphaTime); + cent->trickAlphaTime = cg->time; + + if (cent->trickAlpha > 255) + { + cent->trickAlpha = 255; + } + + doAlpha = 1; + } + else + { + cent->trickAlpha = 255; + cent->trickAlphaTime = cg->time; + } + } +#endif + + // get the player model information + renderfx = 0; + if ( cent->currentState.number == cg->snap->ps.clientNum) { + if (!cg->renderingThirdPerson) { +#if 0 + if (!cg_fpls.integer || cent->currentState.weapon != WP_SABER) +#else + if (cent->currentState.weapon != WP_SABER) +#endif + { + renderfx = RF_THIRD_PERSON; // only draw in mirrors + } + } else { + if (cg_cameraMode.integer) { + iwantout = 1; + + + // goto minimal_add; + + // NOTENOTE Temporary + return; + } + } + } + + // Update the player's client entity information regarding weapons. + // Explanation: The entitystate has a weapond defined on it. The cliententity does as well. + // The cliententity's weapon tells us what the ghoul2 instance on the cliententity has bolted to it. + // If the entitystate and cliententity weapons differ, then the state's needs to be copied to the client. + // Save the old weapon, to verify that it is or is not the same as the new weapon. + // rww - Make sure weapons don't get set BEFORE cent->ghoul2 is initialized or else we'll have no + // weapon bolted on +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (cent->currentState.saberInFlight) + { + cent->ghoul2weapon = CG_G2WeaponInstance(cent, WP_SABER); + } + + if (cent->ghoul2 && + (cent->currentState.eType != ET_NPC || (cent->currentState.NPC_class != CLASS_VEHICLE&¢->currentState.NPC_class != CLASS_REMOTE&¢->currentState.NPC_class != CLASS_SEEKER)) && //don't add weapon models to NPCs that have no bolt for them! + cent->ghoul2weapon != CG_G2WeaponInstance(cent, cent->currentState.weapon) && + !(cent->currentState.eFlags & EF_DEAD) && !cent->torsoBolt && + cg->snap && (cent->currentState.number != cg->snap->ps.clientNum || (cg->snap->ps.pm_flags & PMF_FOLLOW))) + { + if (ci->team == TEAM_SPECTATOR) + { + cent->ghoul2weapon = NULL; + cent->weapon = 0; + } + else + { + CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, cent->ghoul2); + + if (cent->currentState.eType != ET_NPC) + { + if (cent->weapon == WP_SABER + && cent->weapon != cent->currentState.weapon + && !cent->currentState.saberHolstered) + { //switching away from the saber + //trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" )); + if (ci->saber[0].soundOff + && !cent->currentState.saberHolstered) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[0].soundOff); + } + + if (ci->saber[1].soundOff && + ci->saber[1].model[0] && + !cent->currentState.saberHolstered) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[1].soundOff); + } + + } + else if (cent->currentState.weapon == WP_SABER + && cent->weapon != cent->currentState.weapon + && !cent->saberWasInFlight) + { //switching to the saber + //trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" )); + if (ci->saber[0].soundOn) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[0].soundOn); + } + + if (ci->saber[1].soundOn) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[1].soundOn); + } + + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + } + + cent->weapon = cent->currentState.weapon; + cent->ghoul2weapon = CG_G2WeaponInstance(cent, cent->currentState.weapon); + } + } + else if ((cent->currentState.eFlags & EF_DEAD) || cent->torsoBolt) + { + cent->ghoul2weapon = NULL; //be sure to update after respawning/getting limb regrown + } + + if (cent->saberWasInFlight && g2HasWeapon) + { + cent->saberWasInFlight = qfalse; + } +#ifdef _XBOX + } +#endif + + memset (&legs, 0, sizeof(legs)); + + CG_SetGhoul2Info(&legs, cent); + + VectorCopy(cent->modelScale, legs.modelScale); + legs.radius = CG_RadiusForCent( cent ); + VectorClear(legs.angles); + + if (ci->colorOverride[0] != 0.0f || + ci->colorOverride[1] != 0.0f || + ci->colorOverride[2] != 0.0f) + { + legs.shaderRGBA[0] = ci->colorOverride[0]*255.0f; + legs.shaderRGBA[1] = ci->colorOverride[1]*255.0f; + legs.shaderRGBA[2] = ci->colorOverride[2]*255.0f; + legs.shaderRGBA[3] = cent->currentState.customRGBA[3]; + } + else + { + legs.shaderRGBA[0] = cent->currentState.customRGBA[0]; + legs.shaderRGBA[1] = cent->currentState.customRGBA[1]; + legs.shaderRGBA[2] = cent->currentState.customRGBA[2]; + legs.shaderRGBA[3] = cent->currentState.customRGBA[3]; + } + +// minimal_add: + + team = ci->team; + + if (cgs.gametype >= GT_TEAM && cg_drawFriend.integer && + cent->currentState.number != cg->snap->ps.clientNum && + cent->currentState.eType != ET_NPC) + { // If the view is either a spectator or on the same team as this character, show a symbol above their head. + if ((cg->snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cg->snap->ps.persistant[PERS_TEAM] == team) && + !(cent->currentState.eFlags & EF_DEAD)) + { + if (cgs.gametype == GT_SIEGE) + { //check for per-map team shaders + if (team == SIEGETEAM_TEAM1) + { + if (cgSiegeTeam1PlShader) + { + CG_PlayerFloatSprite( cent, cgSiegeTeam1PlShader); + } + else + { //if there isn't one fallback to default + CG_PlayerFloatSprite( cent, cgs.media.teamRedShader); + } + } + else + { + if (cgSiegeTeam2PlShader) + { + CG_PlayerFloatSprite( cent, cgSiegeTeam2PlShader); + } + else + { //if there isn't one fallback to default + CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader); + } + } + } + else + { //generic teamplay + if (team == TEAM_RED) + { + CG_PlayerFloatSprite( cent, cgs.media.teamRedShader); + } + else // if (team == TEAM_BLUE) + { + CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader); + } + } + } + } + else if (cgs.gametype == GT_POWERDUEL && cg_drawFriend.integer && + cent->currentState.number != cg->snap->ps.clientNum) + { + if (cg->predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR && + cent->currentState.number < MAX_CLIENTS && + !(cent->currentState.eFlags & EF_DEAD) && + ci && + cgs.clientinfo[cg->snap->ps.clientNum].duelTeam == ci->duelTeam) + { //ally in powerduel, so draw the icon + CG_PlayerFloatSprite( cent, cgs.media.powerDuelAllyShader); + } + else if (cg->predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR && + cent->currentState.number < MAX_CLIENTS && + !(cent->currentState.eFlags & EF_DEAD) && + ci->duelTeam == DUELTEAM_DOUBLE) + { + CG_PlayerFloatSprite( cent, cgs.media.powerDuelAllyShader); + } + } + + if (cgs.gametype == GT_JEDIMASTER && cg_drawFriend.integer && + cent->currentState.number != cg->snap->ps.clientNum) // Don't show a sprite above a player's own head in 3rd person. + { // If the view is either a spectator or on the same team as this character, show a symbol above their head. + if ((cg->snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cg->snap->ps.persistant[PERS_TEAM] == team) && + !(cent->currentState.eFlags & EF_DEAD)) + { + if (CG_ThereIsAMaster()) + { +/* + if (!cg->snap->ps.isJediMaster) + { + if (!cent->currentState.isJediMaster) + { + CG_PlayerFloatSprite( cent, cgs.media.teamRedShader); + } + } +*/ + } + } + } + + // add the shadow + shadow = CG_PlayerShadow( cent, &shadowPlane ); + + if ( ((cent->currentState.eFlags & EF_SEEKERDRONE) || cent->currentState.genericenemyindex != -1) && cent->currentState.eType != ET_NPC ) + { + refEntity_t seeker; + + memset( &seeker, 0, sizeof(seeker) ); + + VectorCopy(cent->lerpOrigin, elevated); + elevated[2] += 40; + + VectorCopy( elevated, seeker.lightingOrigin ); + seeker.shadowPlane = shadowPlane; + seeker.renderfx = 0; //renderfx; + //don't show in first person? + + angle = ((cg->time / 12) & 255) * (M_PI * 2) / 255; + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 5; + VectorAdd(elevated, dir, seeker.origin); + + VectorCopy(seeker.origin, seekorg); + + if (cent->currentState.genericenemyindex > MAX_GENTITIES) + { + float prefig = (cent->currentState.genericenemyindex-cg->time)/80; + + if (prefig > 55) + { + prefig = 55; + } + else if (prefig < 1) + { + prefig = 1; + } + + elevated[2] -= 55-prefig; + + angle = ((cg->time / 12) & 255) * (M_PI * 2) / 255; + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 5; + VectorAdd(elevated, dir, seeker.origin); + } + else if (cent->currentState.genericenemyindex != ENTITYNUM_NONE && cent->currentState.genericenemyindex != -1) + { + centity_t *enent = &cg_entities[cent->currentState.genericenemyindex]; + + if (enent) + { + VectorSubtract(enent->lerpOrigin, seekorg, enang); + VectorNormalize(enang); + vectoangles(enang, angles); + successchange = 1; + } + } + + if (!successchange) + { + angles[0] = sin(angle) * 30; + angles[1] = (angle * 180 / M_PI) + 90; + if (angles[1] > 360) + angles[1] -= 360; + angles[2] = 0; + } + + AnglesToAxis( angles, seeker.axis ); + + seeker.hModel = trap_R_RegisterModel("models/items/remote.md3"); +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + seeker.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &seeker ); + } + + // add a water splash if partially in and out of water + CG_PlayerSplash( cent ); + + if ( (cg_shadows.integer == 3 || cg_shadows.integer == 2) && shadow ) { + renderfx |= RF_SHADOW_PLANE; + } + renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all + + // if we've been hit, display proper fullscreen fx + CG_PlayerHitFX(cent); + + VectorCopy( cent->lerpOrigin, legs.origin ); + + VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); + legs.shadowPlane = shadowPlane; + legs.renderfx = renderfx; + if (cg_shadows.integer == 2 && (renderfx & RF_THIRD_PERSON)) + { //can see own shadow + legs.renderfx |= RF_SHADOW_ONLY; + } + VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all + + CG_G2PlayerAngles( cent, legs.axis, rootAngles ); + CG_G2PlayerHeadAnims( cent ); + + if ( (cent->currentState.eFlags2&EF2_HELD_BY_MONSTER) + && cent->currentState.hasLookTarget )//NOTE: lookTarget is an entity number, so this presumes that client 0 is NOT a Rancor... + { + centity_t *rancor = &cg_entities[cent->currentState.lookTarget]; + if ( rancor->currentValid[ClientManager::ActiveClientNum()] && rancor->ghoul2/*rancor*/ ) + { + BG_AttachToRancor( rancor->ghoul2, //ghoul2 info + rancor->lerpAngles[YAW], + rancor->lerpOrigin, + cg->time, + cgs.gameModels, + rancor->modelScale, + (rancor->currentState.eFlags2&EF2_GENERIC_NPC_FLAG), + legs.origin, + legs.angles, + NULL ); + + if ( cent->isRagging ) + {//hack, ragdoll has you way at bottom of bounding box + VectorMA( legs.origin, 32, legs.axis[2], legs.origin ); + } + VectorCopy( legs.origin, legs.oldorigin ); + VectorCopy( legs.origin, legs.lightingOrigin ); + + VectorCopy( legs.angles, cent->lerpAngles ); + VectorCopy( cent->lerpAngles, rootAngles );//??? tempAngles );//tempAngles is needed a lot below + VectorCopy( cent->lerpAngles, cent->turAngles ); + VectorCopy( legs.origin, cent->lerpOrigin ); + } + } + //This call is mainly just to reconstruct the skeleton. But we'll get the left hand matrix while we're at it. + //If we don't reconstruct the skeleton after setting the bone angles, we will get bad bolt points on the model + //(e.g. the weapon model bolt will look "lagged") if there's no other GetBoltMatrix call for the rest of the + //frame. Yes, this is stupid and needs to be fixed properly. + //The current solution is to force it not to reconstruct the skeleton for the first GBM call in G2PlayerAngles. + //It works and we end up only reconstructing it once, but it doesn't seem like the best solution. + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + gotLHandMatrix = qtrue; + +#if 0 + if (cg->renderingThirdPerson) + { + if (cgFPLSState != 0) + { + CG_ForceFPLSPlayerModel(cent, ci); + cgFPLSState = 0; + return; + } + } + else if (ci->team == TEAM_SPECTATOR || (cg->snap && (cg->snap->ps.pm_flags & PMF_FOLLOW))) + { //don't allow this when spectating + if (cgFPLSState != 0) + { + trap_Cvar_Set("cg_fpls", "0"); + cg_fpls.integer = 0; + + CG_ForceFPLSPlayerModel(cent, ci); + cgFPLSState = 0; + return; + } + + if (cg_fpls.integer) + { + trap_Cvar_Set("cg_fpls", "0"); + } + } + else + { + if (cg_fpls.integer && cent->currentState.weapon == WP_SABER && cg->snap && cent->currentState.number == cg->snap->ps.clientNum) + { + + if (cgFPLSState != cg_fpls.integer) + { + CG_ForceFPLSPlayerModel(cent, ci); + cgFPLSState = cg_fpls.integer; + return; + } + + /* + mdxaBone_t headMatrix; + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_head, &headMatrix, cent->turAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&headMatrix, ORIGIN, cg->refdef.vieworg); + */ + } + else if (!cg_fpls.integer && cgFPLSState) + { + if (cgFPLSState != cg_fpls.integer) + { + CG_ForceFPLSPlayerModel(cent, ci); + cgFPLSState = cg_fpls.integer; + return; + } + } + } +#endif + + if (cent->currentState.eFlags & EF_DEAD) + { + dead = qtrue; + //rww - since our angles are fixed when we're dead this shouldn't be an issue anyway + //we need to render the dying/dead player because we are now spawning the body on respawn instead of death + //return; + } + + ScaleModelAxis(&legs); + + memset( &torso, 0, sizeof(torso) ); + + //rww - force speed "trail" effect + if (!(cent->currentState.powerups & (1 << PW_SPEED)) || doAlpha || !cg_speedTrail.integer) + { + cent->frame_minus1_refreshed = 0; + cent->frame_minus2_refreshed = 0; + } + + if (cent->frame_minus1_refreshed || + cent->frame_minus2_refreshed) + { + vec3_t tDir; + int distVelBase; + + VectorCopy(cent->currentState.pos.trDelta, tDir); + distVelBase = SPEED_TRAIL_DISTANCE*(VectorNormalize(tDir)*0.004); + + if (cent->frame_minus1_refreshed) + { + refEntity_t reframe_minus1 = legs; + reframe_minus1.renderfx |= RF_FORCE_ENT_ALPHA; + reframe_minus1.shaderRGBA[0] = legs.shaderRGBA[0]; + reframe_minus1.shaderRGBA[1] = legs.shaderRGBA[1]; + reframe_minus1.shaderRGBA[2] = legs.shaderRGBA[2]; + reframe_minus1.shaderRGBA[3] = 100; + + //rww - if the client gets a bad framerate we will only receive frame positions + //once per frame anyway, so we might end up with speed trails very spread out. + //in order to avoid that, we'll get the direction of the last trail from the player + //and place the trail refent a set distance from the player location this frame + VectorSubtract(cent->frame_minus1, legs.origin, tDir); + VectorNormalize(tDir); + + cent->frame_minus1[0] = legs.origin[0]+tDir[0]*distVelBase; + cent->frame_minus1[1] = legs.origin[1]+tDir[1]*distVelBase; + cent->frame_minus1[2] = legs.origin[2]+tDir[2]*distVelBase; + + VectorCopy(cent->frame_minus1, reframe_minus1.origin); + + //reframe_minus1.customShader = 2; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + reframe_minus1.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene(&reframe_minus1); + } + + if (cent->frame_minus2_refreshed) + { + refEntity_t reframe_minus2 = legs; + + reframe_minus2.renderfx |= RF_FORCE_ENT_ALPHA; + reframe_minus2.shaderRGBA[0] = legs.shaderRGBA[0]; + reframe_minus2.shaderRGBA[1] = legs.shaderRGBA[1]; + reframe_minus2.shaderRGBA[2] = legs.shaderRGBA[2]; + reframe_minus2.shaderRGBA[3] = 50; + + //Same as above but do it between trail points instead of the player and first trail entry + VectorSubtract(cent->frame_minus2, cent->frame_minus1, tDir); + VectorNormalize(tDir); + + cent->frame_minus2[0] = cent->frame_minus1[0]+tDir[0]*distVelBase; + cent->frame_minus2[1] = cent->frame_minus1[1]+tDir[1]*distVelBase; + cent->frame_minus2[2] = cent->frame_minus1[2]+tDir[2]*distVelBase; + + VectorCopy(cent->frame_minus2, reframe_minus2.origin); + + //reframe_minus2.customShader = 2; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + reframe_minus2.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene(&reframe_minus2); + } + } + + //trigger animation-based sounds, done before next lerp frame. +#ifdef _XBOX + if(cent->updatedThisFrame == false) +#endif + CG_TriggerAnimSounds(cent); + + // get the animation state (after rotation, to allow feet shuffle) + CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, + &torso.oldframe, &torso.frame, &torso.backlerp ); + + // add the talk baloon or disconnect icon + CG_PlayerSprites( cent ); + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (cent->currentState.eFlags & EF_DEAD) + { //keep track of death anim frame for when we copy off the bodyqueue + ci->frame = cent->pe.torso.frame; + } +#ifdef _XBOX + } +#endif + + if (cent->currentState.activeForcePass > FORCE_LEVEL_3 + && cent->currentState.NPC_class != CLASS_VEHICLE) + { + vec3_t axis[3]; + vec3_t tAng, fAng, fxDir; + vec3_t efOrg; + + int realForceLev = (cent->currentState.activeForcePass - FORCE_LEVEL_3); + + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + VectorSet( fAng, cent->pe.torso.pitchAngle, cent->pe.torso.yawAngle, 0 ); + + AngleVectors( fAng, fxDir, NULL, NULL ); + + if ( cent->currentState.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD + && Q_irand( 0, 1 ) ) + {//alternate back and forth between left and right + mdxaBone_t rHandMatrix; + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &rHandMatrix, cent->turAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + efOrg[0] = rHandMatrix.matrix[0][3]; + efOrg[1] = rHandMatrix.matrix[1][3]; + efOrg[2] = rHandMatrix.matrix[2][3]; + } + else + { + //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + if (!gotLHandMatrix) + { + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + gotLHandMatrix = qtrue; + } + efOrg[0] = lHandMatrix.matrix[0][3]; + efOrg[1] = lHandMatrix.matrix[1][3]; + efOrg[2] = lHandMatrix.matrix[2][3]; + } + + AnglesToAxis( fAng, axis ); + + if ( realForceLev > FORCE_LEVEL_2 ) + {//arc + //trap_FX_PlayEffectID( cgs.effects.forceLightningWide, efOrg, fxDir ); + //trap_FX_PlayEntityEffectID(cgs.effects.forceDrainWide, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); + trap_FX_PlayEntityEffectID(cgs.effects.forceDrainWide, efOrg, axis, -1, -1, -1, -1); + } + else + {//line + //trap_FX_PlayEffectID( cgs.effects.forceLightning, efOrg, fxDir ); + //trap_FX_PlayEntityEffectID(cgs.effects.forceDrain, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); + trap_FX_PlayEntityEffectID(cgs.effects.forceDrain, efOrg, axis, -1, -1, -1, -1); + } + + /* + if (cent->bolt4 < cg->time) + { + cent->bolt4 = cg->time + 100; + trap_S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/drain.wav") ); + } + */ + } + else if ( cent->currentState.activeForcePass + && cent->currentState.NPC_class != CLASS_VEHICLE) + {//doing the electrocuting + vec3_t axis[3]; + vec3_t tAng, fAng, fxDir; + vec3_t efOrg; + + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + VectorSet( fAng, cent->pe.torso.pitchAngle, cent->pe.torso.yawAngle, 0 ); + + AngleVectors( fAng, fxDir, NULL, NULL ); + + //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + if (!gotLHandMatrix) + { + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + gotLHandMatrix = qtrue; + } + + efOrg[0] = lHandMatrix.matrix[0][3]; + efOrg[1] = lHandMatrix.matrix[1][3]; + efOrg[2] = lHandMatrix.matrix[2][3]; + + AnglesToAxis( fAng, axis ); + + if ( cent->currentState.activeForcePass > FORCE_LEVEL_2 ) + {//arc + //trap_FX_PlayEffectID( cgs.effects.forceLightningWide, efOrg, fxDir ); + //trap_FX_PlayEntityEffectID(cgs.effects.forceLightningWide, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); + trap_FX_PlayEntityEffectID(cgs.effects.forceLightningWide, efOrg, axis, -1, -1, -1, -1); + } + else + {//line + //trap_FX_PlayEffectID( cgs.effects.forceLightning, efOrg, fxDir ); + //trap_FX_PlayEntityEffectID(cgs.effects.forceLightning, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); + trap_FX_PlayEntityEffectID(cgs.effects.forceLightning, efOrg, axis, -1, -1, -1, -1); + } + + /* + if (cent->bolt4 < cg->time) + { + cent->bolt4 = cg->time + 100; + trap_S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/lightning.wav") ); + } + */ + } + + //fullbody push effect + if (cent->currentState.eFlags & EF_BODYPUSH) + { + CG_ForcePushBodyBlur(cent); + } + + if ( cent->currentState.powerups & (1 << PW_DISINT_4) ) + { + vec3_t tAng; + vec3_t efOrg; + + //VectorSet( tAng, 0, cent->pe.torso.yawAngle, 0 ); + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + if (!gotLHandMatrix) + { + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + gotLHandMatrix = qtrue; + } + + efOrg[0] = lHandMatrix.matrix[0][3]; + efOrg[1] = lHandMatrix.matrix[1][3]; + efOrg[2] = lHandMatrix.matrix[2][3]; + + if ( (cent->currentState.forcePowersActive & (1 << FP_GRIP)) && + (cg->renderingThirdPerson || cent->currentState.number != cg->snap->ps.clientNum) ) + { + vec3_t boltDir; + vec3_t origBolt; + VectorCopy(efOrg, origBolt); + BG_GiveMeVectorFromMatrix( &lHandMatrix, NEGATIVE_Y, boltDir ); + + CG_ForceGripEffect( efOrg ); + CG_ForceGripEffect( efOrg ); + + /* + //Render a scaled version of the model's hand with a n337 looking shader + { + const char *rotateBone; + char *limbName; + char *limbCapName; + vec3_t armAng; + refEntity_t regrip_arm; + float wv = sin( cg->time * 0.003f ) * 0.08f + 0.1f; + + //rotateBone = "lradius"; + rotateBone = "lradiusX"; + limbName = "l_arm"; + limbCapName = "l_arm_cap_torso"; + + if (cent->grip_arm && trap_G2_HaveWeGhoul2Models(cent->grip_arm)) + { + trap_G2API_CleanGhoul2Models(&(cent->grip_arm)); + } + + memset( ®rip_arm, 0, sizeof(regrip_arm) ); + + VectorCopy(origBolt, efOrg); + + + //efOrg[2] += 8; + efOrg[2] -= 4; + + VectorCopy(efOrg, regrip_arm.origin); + VectorCopy(regrip_arm.origin, regrip_arm.lightingOrigin); + + //VectorCopy(cent->lerpAngles, armAng); + VectorAdd(vec3_origin, rootAngles, armAng); + //armAng[ROLL] = -90; + armAng[ROLL] = 0; + armAng[PITCH] = 0; + AnglesToAxis(armAng, regrip_arm.axis); + + trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, ¢->grip_arm); + + //remove all other models + if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 1)) + { //weapon right + trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 1); + } + if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 2)) + { //weapon left + trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 2); + } + if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 3)) + { //jetpack + trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 3); + } + + trap_G2API_SetRootSurface(cent->grip_arm, 0, limbName); + trap_G2API_SetNewOrigin(cent->grip_arm, trap_G2API_AddBolt(cent->grip_arm, 0, rotateBone)); + trap_G2API_SetSurfaceOnOff(cent->grip_arm, limbCapName, 0); + + regrip_arm.modelScale[0] = 1;//+(wv*6); + regrip_arm.modelScale[1] = 1;//+(wv*6); + regrip_arm.modelScale[2] = 1;//+(wv*6); + ScaleModelAxis(®rip_arm); + + regrip_arm.radius = 64; + + regrip_arm.customShader = trap_R_RegisterShader( "gfx/misc/red_portashield" ); + + regrip_arm.renderfx |= RF_RGB_TINT; + regrip_arm.shaderRGBA[0] = 255 - (wv*900); + if (regrip_arm.shaderRGBA[0] < 30) + { + regrip_arm.shaderRGBA[0] = 30; + } + if (regrip_arm.shaderRGBA[0] > 255) + { + regrip_arm.shaderRGBA[0] = 255; + } + regrip_arm.shaderRGBA[1] = regrip_arm.shaderRGBA[2] = regrip_arm.shaderRGBA[0]; + + regrip_arm.ghoul2 = cent->grip_arm; + trap_R_AddRefEntityToScene( ®rip_arm ); + } + */ + } + else if (!(cent->currentState.forcePowersActive & (1 << FP_GRIP))) + { + //use refractive effect + CG_ForcePushBlur( efOrg, cent ); + } + } + else if (cent->bodyFadeTime) + { //reset the counter for keeping track of push refraction effect state + cent->bodyFadeTime = 0; + } + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (cent->currentState.weapon == WP_STUN_BATON && cent->currentState.number == cg->snap->ps.clientNum) + { + trap_S_AddLoopingSound( cent->currentState.number, cg->refdef.vieworg, vec3_origin, + trap_S_RegisterSound( "sound/weapons/baton/idle.wav" ) ); + } +#ifdef _XBOX + } +#endif + + //NOTE: All effects that should be visible during mindtrick should go above here + + if (iwantout) + { + goto stillDoSaber; + //return; + } + else if (doAlpha) + { + legs.renderfx |= RF_FORCE_ENT_ALPHA; +#ifdef _XBOX + legs.shaderRGBA[3] = cent->trickAlpha[ClientManager::ActiveClientNum()]; +#else + legs.shaderRGBA[3] = cent->trickAlpha; +#endif + + if (legs.shaderRGBA[3] < 1) + { //don't cancel it out even if it's < 1 + legs.shaderRGBA[3] = 1; + } + } + + if (cent->teamPowerEffectTime > cg->time) + { + if (cent->teamPowerType == 3) + { //absorb is a somewhat different effect entirely + //Guess I'll take care of it where it's always been, just checking these values instead. + } + else + { + vec4_t preCol; + int preRFX; + + preRFX = legs.renderfx; + + legs.renderfx |= RF_RGB_TINT; + legs.renderfx |= RF_FORCE_ENT_ALPHA; + + preCol[0] = legs.shaderRGBA[0]; + preCol[1] = legs.shaderRGBA[1]; + preCol[2] = legs.shaderRGBA[2]; + preCol[3] = legs.shaderRGBA[3]; + + if (cent->teamPowerType == 1) + { //heal + legs.shaderRGBA[0] = 0; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 0; + } + else if (cent->teamPowerType == 0) + { //regen + legs.shaderRGBA[0] = 0; + legs.shaderRGBA[1] = 0; + legs.shaderRGBA[2] = 255; + } + else + { //drain + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 0; + legs.shaderRGBA[2] = 0; + } + + legs.shaderRGBA[3] = ((cent->teamPowerEffectTime - cg->time)/8); + + legs.customShader = trap_R_RegisterShader( "powerups/ysalimarishell" ); +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene(&legs); + + legs.customShader = 0; + legs.renderfx = preRFX; + legs.shaderRGBA[0] = preCol[0]; + legs.shaderRGBA[1] = preCol[1]; + legs.shaderRGBA[2] = preCol[2]; + legs.shaderRGBA[3] = preCol[3]; + } + } + + //If you've tricked this client. + if (CG_IsMindTricked(cg->snap->ps.fd.forceMindtrickTargetIndex, + cg->snap->ps.fd.forceMindtrickTargetIndex2, + cg->snap->ps.fd.forceMindtrickTargetIndex3, + cg->snap->ps.fd.forceMindtrickTargetIndex4, + cent->currentState.number)) + { + if (cent->ghoul2) + { + vec3_t efOrg; + vec3_t tAng, fxAng; + vec3_t axis[3]; + + //VectorSet( tAng, 0, cent->pe.torso.yawAngle, 0 ); + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_head, &boltMatrix, tAng, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, efOrg); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, fxAng); + + axis[0][0] = boltMatrix.matrix[0][0]; + axis[0][1] = boltMatrix.matrix[1][0]; + axis[0][2] = boltMatrix.matrix[2][0]; + + axis[1][0] = boltMatrix.matrix[0][1]; + axis[1][1] = boltMatrix.matrix[1][1]; + axis[1][2] = boltMatrix.matrix[2][1]; + + axis[2][0] = boltMatrix.matrix[0][2]; + axis[2][1] = boltMatrix.matrix[1][2]; + axis[2][2] = boltMatrix.matrix[2][2]; + + //trap_FX_PlayEntityEffectID(trap_FX_RegisterEffect("force/confusion.efx"), efOrg, axis, cent->boltInfo, cent->currentState.number); + trap_FX_PlayEntityEffectID(cgs.effects.mForceConfustionOld, efOrg, axis, -1, -1, -1, -1); + } + } + +/* + if (cgs.gametype == GT_HOLOCRON && cent->currentState.time2 && (cg->renderingThirdPerson || cg->snap->ps.clientNum != cent->currentState.number)) + { + int i = 0; + int renderedHolos = 0; + refEntity_t holoRef; + + while (i < NUM_FORCE_POWERS && renderedHolos < 3) + { + if (cent->currentState.time2 & (1 << i)) + { + memset( &holoRef, 0, sizeof(holoRef) ); + + VectorCopy(cent->lerpOrigin, elevated); + elevated[2] += 8; + + VectorCopy( elevated, holoRef.lightingOrigin ); + holoRef.shadowPlane = shadowPlane; + holoRef.renderfx = 0;//RF_THIRD_PERSON; + + if (renderedHolos == 0) + { + angle = ((cg->time / 8) & 255) * (M_PI * 2) / 255; + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 20; + VectorAdd(elevated, dir, holoRef.origin); + + angles[0] = sin(angle) * 30; + angles[1] = (angle * 180 / M_PI) + 90; + if (angles[1] > 360) + angles[1] -= 360; + angles[2] = 0; + AnglesToAxis( angles, holoRef.axis ); + } + else if (renderedHolos == 1) + { + angle = ((cg->time / 8) & 255) * (M_PI * 2) / 255 + M_PI; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + dir[2] = cos(angle) * 20; + VectorAdd(elevated, dir, holoRef.origin); + + angles[0] = cos(angle - 0.5 * M_PI) * 30; + angles[1] = 360 - (angle * 180 / M_PI); + if (angles[1] > 360) + angles[1] -= 360; + angles[2] = 0; + AnglesToAxis( angles, holoRef.axis ); + } + else + { + angle = ((cg->time / 6) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + dir[2] = 0; + VectorAdd(elevated, dir, holoRef.origin); + + VectorCopy(dir, holoRef.axis[1]); + VectorNormalize(holoRef.axis[1]); + VectorSet(holoRef.axis[2], 0, 0, 1); + CrossProduct(holoRef.axis[1], holoRef.axis[2], holoRef.axis[0]); + } + + holoRef.modelScale[0] = 0.5; + holoRef.modelScale[1] = 0.5; + holoRef.modelScale[2] = 0.5; + ScaleModelAxis(&holoRef); + + { + float wv; + addspriteArgStruct_t fxSArgs; + vec3_t holoCenter; + + holoCenter[0] = holoRef.origin[0] + holoRef.axis[2][0]*18; + holoCenter[1] = holoRef.origin[1] + holoRef.axis[2][1]*18; + holoCenter[2] = holoRef.origin[2] + holoRef.axis[2][2]*18; + + wv = sin( cg->time * 0.004f ) * 0.08f + 0.1f; + + VectorCopy(holoCenter, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = wv*60; + fxSArgs.dscale = wv*60; + fxSArgs.sAlpha = wv*12; + fxSArgs.eAlpha = wv*12; + fxSArgs.rotation = 0.0f; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + + fxSArgs.flags = 0x08000000|0x00000001; + + if (forcePowerDarkLight[i] == FORCE_DARKSIDE) + { //dark + fxSArgs.sAlpha *= 3; + fxSArgs.eAlpha *= 3; + fxSArgs.shader = cgs.media.redSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else if (forcePowerDarkLight[i] == FORCE_LIGHTSIDE) + { //light + fxSArgs.sAlpha *= 1.5; + fxSArgs.eAlpha *= 1.5; + fxSArgs.shader = cgs.media.redSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.blueSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else + { //neutral + if (i == FP_SABER_OFFENSE || + i == FP_SABER_DEFENSE || + i == FP_SABERTHROW) + { //saber power + fxSArgs.sAlpha *= 1.5; + fxSArgs.eAlpha *= 1.5; + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else + { + fxSArgs.sAlpha *= 0.5; + fxSArgs.eAlpha *= 0.5; + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.blueSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + } + } + + holoRef.hModel = trap_R_RegisterModel(forceHolocronModels[i]); + trap_R_AddRefEntityToScene( &holoRef ); + + renderedHolos++; + } + i++; + } + } +*/ + + if ((cent->currentState.powerups & (1 << PW_YSALAMIRI)) || + (cgs.gametype == GT_CTY && ((cent->currentState.powerups & (1 << PW_REDFLAG)) || (cent->currentState.powerups & (1 << PW_BLUEFLAG)))) ) + { + if (cgs.gametype == GT_CTY && (cent->currentState.powerups & (1 << PW_REDFLAG))) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysaliredShader ); + } + else if (cgs.gametype == GT_CTY && (cent->currentState.powerups & (1 << PW_BLUEFLAG))) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysaliblueShader ); + } + else + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysalimariShader ); + } + } + + if (cent->currentState.powerups & (1 << PW_FORCE_BOON)) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.boonShader ); + } + + if (cent->currentState.powerups & (1 << PW_FORCE_ENLIGHTENED_DARK)) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.endarkenmentShader ); + } + else if (cent->currentState.powerups & (1 << PW_FORCE_ENLIGHTENED_LIGHT)) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.enlightenmentShader ); + } + + if (cent->currentState.eFlags & EF_INVULNERABLE) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.0f, cgs.media.invulnerabilityShader ); + } +stillDoSaber: + +//#ifdef _XBOX +// if(ClientManager::ActiveClientNum() == 0) { +//#endif + + if ((cent->currentState.eFlags & EF_DEAD) && cent->currentState.weapon == WP_SABER) + { + //cent->saberLength = 0; + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + drawPlayerSaber = qtrue; + } + else if (cent->currentState.weapon == WP_SABER + && cent->currentState.saberHolstered < 2 ) + { + if ( (!cent->currentState.saberInFlight //saber not in flight + || ci->saber[1].soundLoop) //??? + && !(cent->currentState.eFlags & EF_DEAD))//still alive + { + vec3_t soundSpot; + qboolean didFirstSound = qfalse; + + if (cg->snap->ps.clientNum == cent->currentState.number) + { + //trap_S_AddLoopingSound( cent->currentState.number, cg->refdef.vieworg, vec3_origin, + // trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ) ); + VectorCopy(cg->refdef.vieworg, soundSpot); + } + else + { + //trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + // trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ) ); + VectorCopy(cent->lerpOrigin, soundSpot); + } + + if (ci->saber[0].model[0] + && ci->saber[0].soundLoop + && !cent->currentState.saberInFlight) + { + int i = 0; + qboolean hasLen = qfalse; + + while (i < ci->saber[0].numBlades) + { + if (ci->saber[0].blade[i].length) + { + hasLen = qtrue; + break; + } + i++; + } + if (hasLen) + { + trap_S_AddLoopingSound( cent->currentState.number, soundSpot, vec3_origin, + ci->saber[0].soundLoop ); + didFirstSound = qtrue; + } + } + if (ci->saber[1].model[0] + && ci->saber[1].soundLoop + && (!didFirstSound || ci->saber[0].soundLoop != ci->saber[1].soundLoop)) + { + int i = 0; + qboolean hasLen = qfalse; + + while (i < ci->saber[1].numBlades) + { + if (ci->saber[1].blade[i].length) + { + hasLen = qtrue; + break; + } + i++; + } + + if (hasLen) + { + trap_S_AddLoopingSound( cent->currentState.number, soundSpot, vec3_origin, + ci->saber[1].soundLoop ); + } + } + } + + if (iwantout + && !cent->currentState.saberInFlight) + { +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (cent->currentState.eFlags & EF_DEAD) + { + if (cent->ghoul2 + && cent->currentState.saberInFlight + && g2HasWeapon) + { //special case, kill the saber on a freshly dead player if another source says to. + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + g2HasWeapon = qfalse; + } + } +#ifdef _XBOX + } +#endif + return; + //goto endOfCall; + } + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (g2HasWeapon + && cent->currentState.saberInFlight) + { //keep this set, so we don't re-unholster the thing when we get it back, even if it's knocked away. + cent->saberWasInFlight = qtrue; + } +#ifdef _XBOX + } +#endif + + if (cent->currentState.saberInFlight + && cent->currentState.saberEntityNum) + { + centity_t *saberEnt; + + saberEnt = &cg_entities[cent->currentState.saberEntityNum]; + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + + if (/*!cent->bolt4 &&*/ g2HasWeapon || !cent->bolt3 || + saberEnt->serverSaberHitIndex != saberEnt->currentState.modelindex/*|| !cent->saberLength*/) + { //saber is in flight, do not have it as a standard weapon model + qboolean addBolts = qfalse; + mdxaBone_t boltMat; + + if (g2HasWeapon) + { + //ah well, just stick it over the right hand right now. + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &boltMat, cent->turAngles, cent->lerpOrigin, + cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMat, ORIGIN, saberEnt->currentState.pos.trBase); + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + g2HasWeapon = qfalse; + } + + //cent->bolt4 = 1; + + saberEnt->currentState.pos.trTime = cg->time; + saberEnt->currentState.apos.trTime = cg->time; + + VectorCopy(saberEnt->currentState.pos.trBase, saberEnt->lerpOrigin); + VectorCopy(saberEnt->currentState.apos.trBase, saberEnt->lerpAngles); + + cent->bolt3 = saberEnt->currentState.apos.trBase[0]; + if (!cent->bolt3) + { + cent->bolt3 = 1; + } + cent->bolt2 = 0; + + saberEnt->currentState.bolt2 = 123; + + if (saberEnt->ghoul2 && + saberEnt->serverSaberHitIndex == saberEnt->currentState.modelindex) + { + // now set up the gun bolt on it + addBolts = qtrue; + } + else + { + const char *saberModel = CG_ConfigString( CS_MODELS+saberEnt->currentState.modelindex ); + + saberEnt->serverSaberHitIndex = saberEnt->currentState.modelindex; + + if (saberEnt->ghoul2) + { //clean if we already have one (because server changed model string index) + trap_G2API_CleanGhoul2Models(&(saberEnt->ghoul2)); + saberEnt->ghoul2 = 0; + } + if (saberModel && saberModel[0]) + { + trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, saberModel, 0, 0, 0, 0, 0); + } + else if (ci->saber[0].model[0]) + { + trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, ci->saber[0].model, 0, 0, 0, 0, 0); + } + else + { + trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, "models/weapons2/saber/saber_w.glm", 0, 0, 0, 0, 0); + } + //trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, &saberEnt->ghoul2); + + if (saberEnt->ghoul2) + { + addBolts = qtrue; + //cent->bolt4 = 2; + + VectorCopy(saberEnt->currentState.pos.trBase, saberEnt->lerpOrigin); + VectorCopy(saberEnt->currentState.apos.trBase, saberEnt->lerpAngles); + saberEnt->currentState.pos.trTime = cg->time; + saberEnt->currentState.apos.trTime = cg->time; + } + } + + if (addBolts) + { + int m = 0; + int tagBolt; + char *tagName; + + while (m < ci->saber[0].numBlades) + { + tagName = va("*blade%i", m+1); + tagBolt = trap_G2API_AddBolt(saberEnt->ghoul2, 0, tagName); + + if (tagBolt == -1) + { + if (m == 0) + { //guess this is an 0ldsk3wl saber + tagBolt = trap_G2API_AddBolt(saberEnt->ghoul2, 0, "*flash"); + + if (tagBolt == -1) + { + assert(0); + } + break; + } + + if (tagBolt == -1) + { + assert(0); + break; + } + } + + m++; + } + } + } + /*else if (cent->bolt4 != 2) + { + if (saberEnt->ghoul2) + { + trap_G2API_AddBolt(saberEnt->ghoul2, 0, "*flash"); + cent->bolt4 = 2; + } + }*/ + + if (saberEnt && saberEnt->ghoul2 /*&& cent->bolt4 == 2*/) + { + vec3_t bladeAngles; + vec3_t tAng; + vec3_t efOrg; + float wv; + int k = 0; + int l = 0; + addspriteArgStruct_t fxSArgs; + + if (!cent->bolt2) + { + cent->bolt2 = cg->time; + } + + if (cent->bolt3 != 90) + { + if (cent->bolt3 < 90) + { + cent->bolt3 += (cg->time - cent->bolt2)*0.5; + + if (cent->bolt3 > 90) + { + cent->bolt3 = 90; + } + } + else if (cent->bolt3 > 90) + { + cent->bolt3 -= (cg->time - cent->bolt2)*0.5; + + if (cent->bolt3 < 90) + { + cent->bolt3 = 90; + } + } + } + + cent->bolt2 = cg->time; + + saberEnt->currentState.apos.trBase[0] = cent->bolt3; + saberEnt->lerpAngles[0] = cent->bolt3; + + if (!saberEnt->currentState.saberInFlight && saberEnt->currentState.bolt2 != 123) + { //owner is pulling is back + if ( !ci->saber[0].returnDamage + || cent->currentState.saberHolstered ) + { + vec3_t owndir; + + VectorSubtract(saberEnt->lerpOrigin, cent->lerpOrigin, owndir); + VectorNormalize(owndir); + + vectoangles(owndir, owndir); + + owndir[0] += 90; + + VectorCopy(owndir, saberEnt->currentState.apos.trBase); + VectorCopy(owndir, saberEnt->lerpAngles); + VectorClear(saberEnt->currentState.apos.trDelta); + } + } + + //We don't actually want to rely entirely on server updates to render the position of the saber, because we actually know generally where + //it's going to be before the first position update even gets here, and it needs to start getting rendered the instant the saber model is + //removed from the player hand. So we'll just render it manually and let normal rendering for the entity be ignored. + if (!saberEnt->currentState.saberInFlight && saberEnt->currentState.bolt2 != 123) + { //tell it that we're a saber and to render the glow around our handle because we're being pulled back + saberEnt->bolt3 = 999; + } + + saberEnt->currentState.modelGhoul2 = 1; + CG_ManualEntityRender(saberEnt); + saberEnt->bolt3 = 0; + saberEnt->currentState.modelGhoul2 = 127; + + VectorCopy(saberEnt->lerpAngles, bladeAngles); + bladeAngles[ROLL] = 0; + + if ( ci->saber[0].numBlades > 1//staff + && cent->currentState.saberHolstered == 1 )//extra blades off + {//only first blade should be on + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[0], -1, 0); + } + else + { + BG_SI_SetDesiredLength(&ci->saber[0], -1, -1); + } + if ( ci->saber[1].model //dual sabers + && cent->currentState.saberHolstered == 1 )//second one off + { + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + else + { + BG_SI_SetDesiredLength(&ci->saber[1], -1, -1); + } + + //while (l < MAX_SABERS) + //Only want to do for the first saber actually, it's the one in flight. + while (l < 1) + { + if (!ci->saber[l].model[0]) + { + break; + } + + k = 0; + while (k < ci->saber[l].numBlades) + { + if ( //cent->currentState.fireflag == SS_STAFF&& //in saberstaff style + l == 0//first saber + && cent->currentState.saberHolstered == 1 //extra blades should be off + && k > 0 )//this is an extra blade + {//extra blades off + //don't draw them + CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, l, k, saberEnt->lerpOrigin, bladeAngles, qtrue, qtrue); + } + else + { + CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, l, k, saberEnt->lerpOrigin, bladeAngles, qtrue, qfalse); + } + + k++; + } + if ( ci->saber[l].numBlades > 2 ) + {//add a single glow for the saber based on all the blade colors combined + CG_DoSaberLight( &ci->saber[l] ); + } + + l++; + } + + //Make the player's hand glow while guiding the saber + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &boltMatrix, tAng, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + + efOrg[0] = boltMatrix.matrix[0][3]; + efOrg[1] = boltMatrix.matrix[1][3]; + efOrg[2] = boltMatrix.matrix[2][3]; + + wv = sin( cg->time * 0.003f ) * 0.08f + 0.1f; + + //trap_FX_AddSprite( NULL, efOrg, NULL, NULL, 8.0f, 8.0f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowSaberGlowShader, 0x08000000 ); + VectorCopy(efOrg, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = 8.0f; + fxSArgs.dscale = 8.0f; + fxSArgs.sAlpha = wv; + fxSArgs.eAlpha = wv; + fxSArgs.rotation = 0.0f; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + fxSArgs.shader = cgs.media.yellowDroppedSaberShader; + fxSArgs.flags = 0x08000000; + trap_FX_AddSprite(&fxSArgs); + } +#ifdef _XBOX + } + else { // Already updated this frame, saber ent has already been updated - just render it + if(saberEnt && saberEnt->ghoul2) { + CG_ManualEntityRender(saberEnt); + + if(ci->saber[0].model[0]) { + vec3_t bladeAngles; + + VectorCopy(saberEnt->lerpAngles, bladeAngles); + bladeAngles[ROLL] = 0; + + int k = 0; + while (k < ci->saber[0].numBlades) + { + if (cent->currentState.saberHolstered == 1 //extra blades should be off + && k > 0 )//this is an extra blade + {//extra blades off + //don't draw them + CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, 0, k, saberEnt->lerpOrigin, bladeAngles, qtrue, qtrue); + } + else + { + CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, 0, k, saberEnt->lerpOrigin, bladeAngles, qtrue, qfalse); + } + + k++; + } + } + } + } +#endif + } + else + { +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if ( ci->saber[0].numBlades > 1//staff + && cent->currentState.saberHolstered == 1 )//extra blades off + {//only first blade should be on + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[0], -1, 0); + } + else + { + BG_SI_SetDesiredLength(&ci->saber[0], -1, -1); + } + if ( ci->saber[1].model //dual sabers + && cent->currentState.saberHolstered == 1 )//second one off + { + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + else + { + BG_SI_SetDesiredLength(&ci->saber[1], -1, -1); + } +#ifdef _XBOX + } +#endif + } + + //If the arm the saber is in is broken, turn it off. + /* + if (cent->currentState.brokenLimbs & (1 << BROKENLIMB_RARM)) + { + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + } + */ + //Leaving right arm on, at least for now. +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (cent->currentState.brokenLimbs & (1 << BROKENLIMB_LARM)) + { + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + + if (!cent->currentState.saberEntityNum) + { + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + //BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } +#ifdef _XBOX + } +#endif + /* + else + { + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + */ + drawPlayerSaber = qtrue; + } + else if (cent->currentState.weapon == WP_SABER) + { + //cent->saberLength = 0; +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); +#ifdef _XBOX + } +#endif + drawPlayerSaber = qtrue; + } + else + { + //cent->saberLength = 0; +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + BG_SI_SetLength(&ci->saber[0], 0); + BG_SI_SetLength(&ci->saber[1], 0); +#ifdef _XBOX + } +#endif + } + +#ifdef _RAG_BOLT_TESTING + if (cent->currentState.eFlags & EF_RAG) + { + CG_TempTestFunction(cent, cent->turAngles); + } +#endif + + if (cent->currentState.weapon == WP_SABER) + { +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + BG_SI_SetLengthGradual(&ci->saber[0], cg->time); + BG_SI_SetLengthGradual(&ci->saber[1], cg->time); +#ifdef _XBOX + } +#endif + } + + if (drawPlayerSaber) + { + centity_t *saberEnt; + int k = 0; + int l = 0; + + if (!cent->currentState.saberEntityNum) + { + l = 1; //The "primary" saber is missing or in flight or something, so only try to draw in the second one + } + else if (!cent->currentState.saberInFlight) + { + saberEnt = &cg_entities[cent->currentState.saberEntityNum]; + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (/*cent->bolt4 && */!g2HasWeapon) + { + trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, WP_SABER), 0, cent->ghoul2, 1); + + if (saberEnt && saberEnt->ghoul2) + { + trap_G2API_CleanGhoul2Models(&(saberEnt->ghoul2)); + } + + saberEnt->currentState.modelindex = 0; + saberEnt->ghoul2 = NULL; + VectorClear(saberEnt->currentState.pos.trBase); + } + + cent->bolt3 = 0; + cent->bolt2 = 0; +#ifdef _XBOX + } +#endif + } + else + { + l = 1; //The "primary" saber is missing or in flight or something, so only try to draw in the second one + } + + while (l < MAX_SABERS) + { + k = 0; + + if (!ci->saber[l].model[0]) + { + break; + } + + if (cent->currentState.eFlags2&EF2_HELD_BY_MONSTER) + { + //vectoangles(legs.axis[0], rootAngles); +#if 0 + if ( cent->currentState.hasLookTarget )//NOTE: lookTarget is an entity number, so this presumes that client 0 is NOT a Rancor... + { + centity_t *rancor = &cg_entities[cent->currentState.lookTarget]; + if ( rancor && rancor->ghoul2 ) + { + BG_AttachToRancor( rancor->ghoul2, //ghoul2 info + rancor->lerpAngles[YAW], + rancor->lerpOrigin, + cg.time, + cgs.gameModels, + rancor->modelScale, + (rancor->currentState.eFlags2&EF2_GENERIC_NPC_FLAG), + legs.origin, + rootAngles, + NULL ); + } + } +#else + vectoangles(legs.axis[0], rootAngles); +#endif + } + + while (k < ci->saber[l].numBlades) + { + if ( //cent->currentState.fireflag == SS_STAFF&& //in saberstaff style + cent->currentState.saberHolstered == 1 //extra blades should be off + && k > 0 //this is an extra blade + && ci->saber[l].blade[k].length <= 0 )//it's completely off + {//extra blades off + //don't draw them + CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qtrue); + } + else if ( ci->saber[1].model[0]//we have a second saber + && cent->currentState.saberHolstered == 1 //it should be off + && l > 0//and this is the second one + && ci->saber[l].blade[k].length <= 0 )//it's completely off + {//second saber is turned off and this blade is done with turning off + CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qtrue); + } + else + { + CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qfalse); + } + + k++; + } + if ( ci->saber[l].numBlades > 2 ) + {//add a single glow for the saber based on all the blade colors combined + CG_DoSaberLight( &ci->saber[l] ); + } + + l++; + } + } + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (cent->currentState.saberInFlight && !cent->currentState.saberEntityNum) + { //reset the length if the saber is knocked away + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + if (g2HasWeapon) + { //and remember to kill the bolton model in case we didn't get a thrown saber update first + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + g2HasWeapon = qfalse; + } + cent->bolt3 = 0; + cent->bolt2 = 0; + } + + if (cent->currentState.eFlags & EF_DEAD) + { + if (cent->ghoul2 && cent->currentState.saberInFlight && g2HasWeapon) + { //special case, kill the saber on a freshly dead player if another source says to. + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + g2HasWeapon = qfalse; + } + } +#ifdef _XBOX + } +#endif + + if (iwantout) + { + return; + //goto endOfCall; + } + + if ((cg->snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg->snap->ps.clientNum != cent->currentState.number) + { + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 0; + legs.renderfx |= RF_MINLIGHT; + } + + if (cg->snap->ps.duelInProgress /*&& cent->currentState.number != cg->snap->ps.clientNum*/) + { //I guess go ahead and glow your own client too in a duel + if (cent->currentState.number != cg->snap->ps.duelIndex && + cent->currentState.number != cg->snap->ps.clientNum) + { //everyone not involved in the duel is drawn very dark + legs.shaderRGBA[0] = 50; + legs.shaderRGBA[1] = 50; + legs.shaderRGBA[2] = 50; + legs.renderfx |= RF_RGB_TINT; + } + else + { //adjust the glow by how far away you are from your dueling partner + centity_t *duelEnt; + + duelEnt = &cg_entities[cg->snap->ps.duelIndex]; + + if (duelEnt) + { + vec3_t vecSub; + float subLen = 0; + + VectorSubtract(duelEnt->lerpOrigin, cg->snap->ps.origin, vecSub); + subLen = VectorLength(vecSub); + + if (subLen < 1) + { + subLen = 1; + } + + if (subLen > 1020) + { + subLen = 1020; + } + + legs.shaderRGBA[0] = 255 - subLen/4; + legs.shaderRGBA[1] = 255 - subLen/4; + legs.shaderRGBA[2] = 255 - subLen/4; + + if (legs.shaderRGBA[2] < 1) legs.shaderRGBA[2] = 1; + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.customShader = cgs.media.forceShell; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + + legs.customShader = 0; + + legs.shaderRGBA[0] = 255 - subLen/8; + legs.shaderRGBA[1] = 255 - subLen/8; + legs.shaderRGBA[2] = 255 - subLen/8; + + if (legs.shaderRGBA[2] < 1) + { + legs.shaderRGBA[2] = 1; + } + if (legs.shaderRGBA[2] > 255) + { + legs.shaderRGBA[2] = 255; + } + + if (subLen <= 1024) + { + legs.renderfx |= RF_RGB_TINT; + } + } + } + } + else + { + if (cent->currentState.bolt1 && !(cent->currentState.eFlags & EF_DEAD) && cent->currentState.number != cg->snap->ps.clientNum && (!cg->snap->ps.duelInProgress || cg->snap->ps.duelIndex != cent->currentState.number)) + { + legs.shaderRGBA[0] = 50; + legs.shaderRGBA[1] = 50; + legs.shaderRGBA[2] = 50; + legs.renderfx |= RF_RGB_TINT; + } + } + + if (cent->currentState.eFlags & EF_DISINTEGRATION) + { + if (!cent->dustTrailTime) + { + cent->dustTrailTime = cg->time; + cent->miscTime = legs.frame; + } + + if ((cg->time - cent->dustTrailTime) > 1500) + { //avoid rendering the entity after disintegration has finished anyway + //goto endOfCall; + return; + } + + trap_G2API_SetBoneAnim(legs.ghoul2, 0, "model_root", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg->time, cent->miscTime, -1); + + if (!cent->noLumbar) + { + trap_G2API_SetBoneAnim(legs.ghoul2, 0, "lower_lumbar", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg->time, cent->miscTime, -1); + + if (cent->localAnimIndex <= 1) + { + trap_G2API_SetBoneAnim(legs.ghoul2, 0, "Motion", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg->time, cent->miscTime, -1); + } + } + + CG_Disintegration(cent, &legs); + + //goto endOfCall; + return; + } + else + { + cent->dustTrailTime = 0; + cent->miscTime = 0; + } + + if (cent->currentState.powerups & (1 << PW_CLOAKED)) + { + if (!cent->cloaked) + { + cent->cloaked = qtrue; + cent->uncloaking = cg->time + 2000; + } + } + else if (cent->cloaked) + { + cent->cloaked = qfalse; + cent->uncloaking = cg->time + 2000; + } + + if (cent->uncloaking > cg->time) + {//in the middle of cloaking + if ((cg->snap->ps.fd.forcePowersActive & (1 << FP_SEE)) + && cg->snap->ps.clientNum != cent->currentState.number) + {//just draw him +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + } + else + { + float perc = (float)(cent->uncloaking - cg->time) / 2000.0f; + if (( cent->currentState.powerups & ( 1 << PW_CLOAKED ))) + {//actually cloaking, so reverse it + perc = 1.0f - perc; + } + + if ( perc >= 0.0f && perc <= 1.0f ) + { + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.renderfx |= RF_RGB_TINT; + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255.0f * perc; + legs.shaderRGBA[3] = 0; + legs.customShader = cgs.media.cloakedShader; +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255; + legs.shaderRGBA[3] = 255 * (1.0f - perc); // let model alpha in + legs.customShader = 0; // use regular skin + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx |= RF_FORCE_ENT_ALPHA; + trap_R_AddRefEntityToScene( &legs ); + } + } + } + else if (( cent->currentState.powerups & ( 1 << PW_CLOAKED ))) + {//fully cloaked + if ((cg->snap->ps.fd.forcePowersActive & (1 << FP_SEE)) + && cg->snap->ps.clientNum != cent->currentState.number) + {//just draw him +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + } + else + { + if (cg->renderingThirdPerson || cent->currentState.number != cg->predictedPlayerState.clientNum) + { + /* + legs.renderfx = 0;//&= ~(RF_RGB_TINT|RF_ALPHA_FADE); + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = legs.shaderRGBA[3] = 255; + legs.customShader = cgs.media.cloakedShader; + + legs.nonNormalizedAxes = qtrue; + + legs.modelScale[0] = 1.02f; + legs.modelScale[1] = 1.02f; + legs.modelScale[2] = 1.02f; + VectorScale( legs.axis[0], legs.modelScale[0], legs.axis[0] ); + VectorScale( legs.axis[1], legs.modelScale[1], legs.axis[1] ); + VectorScale( legs.axis[2], legs.modelScale[2], legs.axis[2] ); + + ScaleModelAxis(&legs); + + trap_R_AddRefEntityToScene( &legs ); + + legs.modelScale[0] = 0.98f; + legs.modelScale[1] = 0.98f; + legs.modelScale[2] = 0.98f; + VectorScale( legs.axis[0], legs.modelScale[0], legs.axis[0] ); + VectorScale( legs.axis[1], legs.modelScale[1], legs.axis[1] ); + VectorScale( legs.axis[2], legs.modelScale[2], legs.axis[2] ); + + ScaleModelAxis(&legs); + */ + + if (cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4 && cg_renderToTextureFX.integer) + { + trap_R_SetRefractProp(1.0f, 0.0f, qfalse, qfalse); //don't need to do this every frame.. but.. + legs.customShader = 2; //crazy "refractive" shader +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + legs.customShader = 0; + } + else + { //stencil buffer's in use, sorry + legs.renderfx = 0;//&= ~(RF_RGB_TINT|RF_ALPHA_FADE); + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = legs.shaderRGBA[3] = 255; + legs.customShader = cgs.media.cloakedShader; +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + legs.customShader = 0; + } + } + } + } + + if (!(cent->currentState.powerups & (1 << PW_CLOAKED))) + { //don't add the normal model if cloaked + CG_CheckThirdPersonAlpha( cent, &legs ); +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene(&legs); + } + + //cent->frame_minus2 = cent->frame_minus1; + VectorCopy(cent->frame_minus1, cent->frame_minus2); + + if (cent->frame_minus1_refreshed) + { + cent->frame_minus2_refreshed = 1; + } + + //cent->frame_minus1 = legs; + VectorCopy(legs.origin, cent->frame_minus1); + + cent->frame_minus1_refreshed = 1; + + if (!cent->frame_hold_refreshed && (cent->currentState.powerups & (1 << PW_SPEEDBURST))) + { + cent->frame_hold_time = cg->time + 254; + } + +#ifdef _XBOX + if(cent->updatedThisFrame == false) { +#endif + if (cent->frame_hold_time >= cg->time) + { + refEntity_t reframe_hold; + + if (!cent->frame_hold_refreshed) + { //We're taking the ghoul2 instance from the original refent and duplicating it onto our refent alias so that we can then freeze the frame and fade it for the effect + if (cent->frame_hold && trap_G2_HaveWeGhoul2Models(cent->frame_hold) && + cent->frame_hold != cent->ghoul2) + { + trap_G2API_CleanGhoul2Models(&(cent->frame_hold)); + } + reframe_hold = legs; + cent->frame_hold_refreshed = 1; + reframe_hold.ghoul2 = NULL; + + trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, ¢->frame_hold); + + //Set the animation to the current frame and freeze on end + //trap_G2API_SetBoneAnim(cent->frame_hold.ghoul2, 0, "model_root", cent->frame_hold.frame, cent->frame_hold.frame, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg->time, cent->frame_hold.frame, -1); + trap_G2API_SetBoneAnim(cent->frame_hold, 0, "model_root", legs.frame, legs.frame, 0, 1.0f, cg->time, legs.frame, -1); + } + else + { + reframe_hold = legs; + reframe_hold.ghoul2 = cent->frame_hold; + } + + reframe_hold.renderfx |= RF_FORCE_ENT_ALPHA; + reframe_hold.shaderRGBA[3] = (cent->frame_hold_time - cg->time); + if (reframe_hold.shaderRGBA[3] > 254) + { + reframe_hold.shaderRGBA[3] = 254; + } + if (reframe_hold.shaderRGBA[3] < 1) + { + reframe_hold.shaderRGBA[3] = 1; + } + + reframe_hold.ghoul2 = cent->frame_hold; +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + reframe_hold.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene(&reframe_hold); + } + else + { + cent->frame_hold_refreshed = 0; + } +#ifdef _XBOX + } +#endif + + // + // add the gun / barrel / flash + // + if (cent->currentState.weapon != WP_EMPLACED_GUN) + { + CG_AddPlayerWeapon( &legs, NULL, cent, ci->team, rootAngles, qtrue ); + } + // add powerups floating behind the player + CG_PlayerPowerups( cent, &legs ); + + if ((cent->currentState.forcePowersActive & (1 << FP_RAGE)) && + (cg->renderingThirdPerson || cent->currentState.number != cg->snap->ps.clientNum)) + { + //legs.customShader = cgs.media.rageShader; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.renderfx &= ~RF_MINLIGHT; + + legs.renderfx |= RF_RGB_TINT; + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = legs.shaderRGBA[2] = 0; + legs.shaderRGBA[3] = 255; + + if ( rand() & 1 ) + { + legs.customShader = cgs.media.electricBodyShader; + } + else + { + legs.customShader = cgs.media.electricBody2Shader; + } + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene(&legs); + } + + if (!cg->snap->ps.duelInProgress && cent->currentState.bolt1 && !(cent->currentState.eFlags & EF_DEAD) && cent->currentState.number != cg->snap->ps.clientNum && (!cg->snap->ps.duelInProgress || cg->snap->ps.duelIndex != cent->currentState.number)) + { + legs.shaderRGBA[0] = 50; + legs.shaderRGBA[1] = 50; + legs.shaderRGBA[2] = 255; + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.customShader = cgs.media.forceSightBubble; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + } + + if ( CG_VehicleShouldDrawShields( cent ) //vehicle + || (checkDroidShields && CG_VehicleShouldDrawShields( &cg_entities[cent->currentState.m_iVehicleNum] )) )//droid in vehicle + {//Vehicles have form-fitting shields + Vehicle_t *pVeh = cent->m_pVehicle; + if ( checkDroidShields ) + { + pVeh = cg_entities[cent->currentState.m_iVehicleNum].m_pVehicle; + } + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 255; + legs.shaderRGBA[3] = 10.0f+(sin((float)(cg->time/4))*128.0f);//112.0 * ((cent->damageTime - cg->time) / MIN_SHIELD_TIME) + random()*16; + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + + if ( pVeh + && pVeh->m_pVehicleInfo + && pVeh->m_pVehicleInfo->shieldShaderHandle ) + {//use the vehicle-specific shader + legs.customShader = pVeh->m_pVehicleInfo->shieldShaderHandle; + } + else + { + legs.customShader = cgs.media.playerShieldDamage; + } + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + } + //For now, these two are using the old shield shader. This is just so that you + //can tell it apart from the JM/duel shaders, but it's still very obvious. + if (cent->currentState.forcePowersActive & (1 << FP_PROTECT)) + { //aborb is represented by green.. + refEntity_t prot; + + memcpy(&prot, &legs, sizeof(prot)); + + prot.shaderRGBA[0] = 0; + prot.shaderRGBA[1] = 128; + prot.shaderRGBA[2] = 0; + prot.shaderRGBA[3] = 254; + + prot.renderfx &= ~RF_RGB_TINT; + prot.renderfx &= ~RF_FORCE_ENT_ALPHA; + prot.customShader = cgs.media.protectShader; + + /* + if (!prot.modelScale[0] && !prot.modelScale[1] && !prot.modelScale[2]) + { + prot.modelScale[0] = prot.modelScale[1] = prot.modelScale[2] = 1.0f; + } + VectorScale(prot.modelScale, 1.1f, prot.modelScale); + prot.origin[2] -= 2.0f; + ScaleModelAxis(&prot); + */ + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + prot.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &prot ); + } + //if (cent->currentState.forcePowersActive & (1 << FP_ABSORB)) + //Showing only when the power has been active (absorbed something) recently now, instead of always. + //AND + //always show if it is you with the absorb on + if ((cent->currentState.number == cg->predictedPlayerState.clientNum && (cg->predictedPlayerState.fd.forcePowersActive & (1<teamPowerEffectTime > cg->time && cent->teamPowerType == 3)) + { //aborb is represented by blue.. + legs.shaderRGBA[0] = 0; + legs.shaderRGBA[1] = 0; + legs.shaderRGBA[2] = 255; + legs.shaderRGBA[3] = 254; + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.customShader = cgs.media.playerShieldDamage; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + } +/* + if (cent->currentState.isJediMaster && cg->snap->ps.clientNum != cent->currentState.number) + { + legs.shaderRGBA[0] = 100; + legs.shaderRGBA[1] = 100; + legs.shaderRGBA[2] = 255; + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.renderfx |= RF_NODEPTH; + legs.customShader = cgs.media.forceShell; + + trap_R_AddRefEntityToScene( &legs ); + + legs.renderfx &= ~RF_NODEPTH; + } +*/ + if ((cg->snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg->snap->ps.clientNum != cent->currentState.number && cg_auraShell.integer) + { + if (cgs.gametype == GT_SIEGE) + { // A team game + if ( ci->team == TEAM_SPECTATOR || ci->team == TEAM_FREE ) + {//yellow + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 0; + } + else if ( ci->team != cgs.clientinfo[cg->snap->ps.clientNum].team ) + {//red + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 50; + legs.shaderRGBA[2] = 50; + } + else + {//green + legs.shaderRGBA[0] = 50; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 50; + } + } + else if (cgs.gametype >= GT_TEAM) + { // A team game + switch(ci->team) + { + case TEAM_RED: + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 50; + legs.shaderRGBA[2] = 50; + break; + case TEAM_BLUE: + legs.shaderRGBA[0] = 75; + legs.shaderRGBA[1] = 75; + legs.shaderRGBA[2] = 255; + break; + + default: + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 0; + break; + } + } + else + { // Not a team game + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 0; + } + +/* if (cg->snap->ps.fd.forcePowerLevel[FP_SEE] <= FORCE_LEVEL_1) + { + legs.renderfx |= RF_MINLIGHT; + } + else +*/ { // See through walls. + legs.renderfx |= RF_MINLIGHT | RF_NODEPTH; + + if (cg->snap->ps.fd.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2) + { //only level 2+ can see players through walls + legs.renderfx &= ~RF_NODEPTH; + } + } + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.customShader = cgs.media.sightShell; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + } + + // Electricity + //------------------------------------------------ + if ( cent->currentState.emplacedOwner > cg->time ) + { + int dif = cent->currentState.emplacedOwner - cg->time; + vec3_t tempAngles; + + if ( dif > 0 && random() > 0.4f ) + { + // fade out over the last 500 ms + int brightness = 255; + + if ( dif < 500 ) + { + brightness = floor((dif - 500.0f) / 500.0f * 255.0f ); + } + + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.renderfx &= ~RF_MINLIGHT; + + legs.renderfx |= RF_RGB_TINT; + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = brightness; + legs.shaderRGBA[3] = 255; + + if ( rand() & 1 ) + { + legs.customShader = cgs.media.electricBodyShader; + } + else + { + legs.customShader = cgs.media.electricBody2Shader; + } + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + + if ( random() > 0.9f ) + trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, cgs.media.crackleSound ); + } + + VectorSet(tempAngles, 0, cent->lerpAngles[YAW], 0); + CG_ForceElectrocution( cent, legs.origin, tempAngles, cgs.media.boltShader, qfalse ); + } + + if (cent->currentState.powerups & (1 << PW_SHIELDHIT)) + { + /* + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255.0f * 0.5f;//t; + legs.shaderRGBA[3] = 255; + legs.renderfx &= ~RF_ALPHA_FADE; + legs.renderfx |= RF_RGB_TINT; + */ + + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = Q_irand(1, 255); + + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.renderfx &= ~RF_MINLIGHT; + legs.renderfx &= ~RF_RGB_TINT; + legs.customShader = cgs.media.playerShieldDamage; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + legs.skipForPlayer2 = true; +#endif + trap_R_AddRefEntityToScene( &legs ); + } +#if 0 +endOfCall: + + if (cgBoneAnglePostSet.refreshSet) + { + trap_G2API_SetBoneAngles(cgBoneAnglePostSet.ghoul2, cgBoneAnglePostSet.modelIndex, cgBoneAnglePostSet.boneName, + cgBoneAnglePostSet.angles, cgBoneAnglePostSet.flags, cgBoneAnglePostSet.up, cgBoneAnglePostSet.right, + cgBoneAnglePostSet.forward, cgBoneAnglePostSet.modelList, cgBoneAnglePostSet.blendTime, cgBoneAnglePostSet.currentTime); + + cgBoneAnglePostSet.refreshSet = qfalse; + } +#endif +} + + +//===================================================================== + +/* +=============== +CG_ResetPlayerEntity + +A player just came into view or teleported, so reset all animation info +=============== +*/ +void CG_ResetPlayerEntity( centity_t *cent ) +{ + clientInfo_t *ci; + int i = 0; + int j = 0; + +// cent->errorTime = -99999; // guarantee no error decay added +// cent->extrapolated = qfalse; + + if (cent->currentState.eType == ET_NPC) + { + if (!cent->npcClient) + { + CG_CreateNPCClient(¢->npcClient); //allocate memory for it + + if (!cent->npcClient) + { + assert(0); + return; + } + + memset(cent->npcClient, 0, sizeof(clientInfo_t)); + cent->npcClient->ghoul2Model = NULL; + } + + ci = cent->npcClient; + + assert(ci); + + //just force these guys to be set again, it won't hurt anything if they're + //already set. + cent->npcLocalSurfOff = 0; + cent->npcLocalSurfOn = 0; + } + else + { + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + } + + while (i < MAX_SABERS) + { + j = 0; + while (j < ci->saber[i].numBlades) + { + ci->saber[i].blade[j].trail[ClientManager::ActiveClientNum()].lastTime = -20000; + j++; + } + i++; + } + + ci->facial_blink = -1; + ci->facial_frown = 0; + ci->facial_aux = 0; + ci->superSmoothTime = 0; + + //reset lerp origin smooth point + VectorCopy(cent->lerpOrigin, cent->beamEnd); + + if (cent->currentState.eType != ET_NPC || + !(cent->currentState.eFlags & EF_DEAD)) + { + CG_ClearLerpFrame( cent, ci, ¢->pe.legs, cent->currentState.legsAnim, qfalse); + CG_ClearLerpFrame( cent, ci, ¢->pe.torso, cent->currentState.torsoAnim, qtrue); + + BG_EvaluateTrajectory( ¢->currentState.pos, cg->time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg->time, cent->lerpAngles ); + +// VectorCopy( cent->lerpOrigin, cent->rawOrigin ); + VectorCopy( cent->lerpAngles, cent->rawAngles ); + + memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); + cent->pe.legs.yawAngle = cent->rawAngles[YAW]; + cent->pe.legs.yawing = qfalse; + cent->pe.legs.pitchAngle = 0; + cent->pe.legs.pitching = qfalse; + + memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); + cent->pe.torso.yawAngle = cent->rawAngles[YAW]; + cent->pe.torso.yawing = qfalse; + cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; + cent->pe.torso.pitching = qfalse; + + if (cent->currentState.eType == ET_NPC) + { //just start them off at 0 pitch + cent->pe.torso.pitchAngle = 0; + } + + if ((cent->ghoul2 == NULL) && ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, ¢->ghoul2); + cent->weapon = 0; + cent->ghoul2weapon = NULL; + + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse); + + if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cent->noFace = qtrue; + } + + cent->localAnimIndex = CG_G2SkelForModel(cent->ghoul2); + cent->eventAnimIndex = CG_G2EvIndexForModel(cent->ghoul2, cent->localAnimIndex); + + //CG_CopyG2WeaponInstance(cent->currentState.weapon, ci->ghoul2Model); + //cent->weapon = cent->currentState.weapon; + } + } + + //do this to prevent us from making a saber unholster sound the first time we enter the pvs + if (cent->currentState.number != cg->predictedPlayerState.clientNum && + cent->currentState.weapon == WP_SABER && + cent->weapon != cent->currentState.weapon) + { + cent->weapon = cent->currentState.weapon; + if (cent->ghoul2 && ci->ghoul2Model) + { + CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, cent->ghoul2); + cent->ghoul2weapon = CG_G2WeaponInstance(cent, cent->currentState.weapon); + } + if (!cent->currentState.saberHolstered) + { //if not holstered set length and desired length for both blades to full right now. + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + i = 0; + while (i < MAX_SABERS) + { + j = 0; + while (j < ci->saber[i].numBlades) + { + ci->saber[i].blade[j].length = ci->saber[i].blade[j].lengthMax; + j++; + } + i++; + } + } + } + + + if ( cg_debugPosition.integer ) { + CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); + } +} + diff --git a/codemp/cgame/cg_playerstate.c b/codemp/cgame/cg_playerstate.c new file mode 100644 index 0000000..5592a92 --- /dev/null +++ b/codemp/cgame/cg_playerstate.c @@ -0,0 +1,537 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_playerstate.c -- this file acts on changes in a new playerState_t +// With normal play, this will be done after local prediction, but when +// following another player or playing back a demo, it will be checked +// when the snapshot transitions like all the other entities + +#include "cg_local.h" + +/* +============== +CG_CheckAmmo + +If the ammo has gone low enough to generate the warning, play a sound +============== +*/ +void CG_CheckAmmo( void ) { +#if 0 + int i; + int total; + int previous; + int weapons; + + // see about how many seconds of ammo we have remaining + weapons = cg->snap->ps.stats[ STAT_WEAPONS ]; + total = 0; + for ( i = WP_BRYAR_PISTOL; i < WP_NUM_WEAPONS ; i++ ) { + if ( ! ( weapons & ( 1 << i ) ) ) { + continue; + } + switch ( i ) + { + case WP_BRYAR_PISTOL: + case WP_CONCUSSION: + case WP_BRYAR_OLD: + case WP_BLASTER: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_REPEATER: + case WP_DEMP2: + case WP_FLECHETTE: + case WP_ROCKET_LAUNCHER: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + case WP_EMPLACED_GUN: + total += cg->snap->ps.ammo[weaponData[i].ammoIndex] * 1000; + break; + default: + total += cg->snap->ps.ammo[weaponData[i].ammoIndex] * 200; + break; + } + if ( total >= 5000 ) { + cg->lowAmmoWarning = 0; + return; + } + } + + previous = cg->lowAmmoWarning; + + if ( total == 0 ) { + cg->lowAmmoWarning = 2; + } else { + cg->lowAmmoWarning = 1; + } + + if (cg->snap->ps.weapon == WP_SABER) + { + cg->lowAmmoWarning = 0; + } + + // play a sound on transitions + if ( cg->lowAmmoWarning != previous ) { + trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); + } +#endif + //disabled silly ammo warning stuff for now +} + +/* +============== +CG_DamageFeedback +============== +*/ +void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) { + float left = 0.0f, front, up; + float kick; + int health; + float scale; + vec3_t dir; + vec3_t angles; + float dist; + float yaw, pitch; + + // show the attacking player's head and name in corner + cg->attackerTime = cg->time; + + // the lower on health you are, the greater the view kick will be + health = cg->snap->ps.stats[STAT_HEALTH]; + if ( health < 40 ) { + scale = 1; + } else { + scale = 40.0 / health; + } + kick = damage * scale; + + if (kick < 5) + kick = 5; + if (kick > 10) + kick = 10; + + // if yaw and pitch are both 255, make the damage always centered (falling, etc) + if ( yawByte == 255 && pitchByte == 255 ) { + cg->damageX = 0; + cg->damageY = 0; + cg->v_dmg_roll = 0; + cg->v_dmg_pitch = -kick; + } else { + // positional + pitch = pitchByte / 255.0 * 360; + yaw = yawByte / 255.0 * 360; + + angles[PITCH] = pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; + + AngleVectors( angles, dir, NULL, NULL ); + VectorSubtract( vec3_origin, dir, dir ); + + front = DotProduct (dir, cg->refdef.viewaxis[0] ); + left = DotProduct (dir, cg->refdef.viewaxis[1] ); + up = DotProduct (dir, cg->refdef.viewaxis[2] ); + + dir[0] = front; + dir[1] = left; + dir[2] = 0; + dist = VectorLength( dir ); + if ( dist < 0.1 ) { + dist = 0.1f; + } + + cg->v_dmg_roll = kick * left; + + cg->v_dmg_pitch = -kick * front; + + if ( front <= 0.1 ) { + front = 0.1f; + } + cg->damageX = -left / front; + cg->damageY = up / dist; + } + + // clamp the position + if ( cg->damageX > 1.0 ) { + cg->damageX = 1.0; + } + if ( cg->damageX < - 1.0 ) { + cg->damageX = -1.0; + } + + if ( cg->damageY > 1.0 ) { + cg->damageY = 1.0; + } + if ( cg->damageY < - 1.0 ) { + cg->damageY = -1.0; + } + + // don't let the screen flashes vary as much + if ( kick > 10 ) { + kick = 10; + } + cg->damageValue = kick; + cg->v_dmg_time = cg->time + DAMAGE_TIME; + cg->damageTime = cg->snap->serverTime; + +//JLFRUMBLE +#ifdef _XBOX +extern void FF_XboxShake(float intensity, int duration); +extern void FF_XboxDamage(int damage, float xpos); + +//FF_XboxShake(kick, 500); +FF_XboxDamage(damage, -left); + + +#endif + +} + + + + +/* +================ +CG_Respawn + +A respawn happened this snapshot +================ +*/ +void CG_Respawn( void ) { + // no error decay on player movement + cg->thisFrameTeleport = qtrue; + + // display weapons available + cg->weaponSelectTime = cg->time; + + // select the weapon the server says we are using + cg->weaponSelect = cg->snap->ps.weapon; +} + +extern char *eventnames[]; + +/* +============== +CG_CheckPlayerstateEvents +============== +*/ +void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { + int i; + int event; + centity_t *cent; + + if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { + cent = &cg_entities[ ps->clientNum ]; + cent->currentState.event = ps->externalEvent; + cent->currentState.eventParm = ps->externalEventParm; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + + cent = &cg_entities[ ps->clientNum ]; + // go through the predictable events buffer + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { + // if we have a new predictable event + if ( i >= ops->eventSequence + // or the server told us to play another event instead of a predicted event we already issued + // or something the server told us changed our prediction causing a different event + || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) { + + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; +//JLF ADDED to hopefully mark events as player event + cent->playerState = ps; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg->predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + + cg->eventSequence++; + } + } +} + +/* +================== +CG_CheckChangedPredictableEvents +================== +*/ +void CG_CheckChangedPredictableEvents( playerState_t *ps ) { + int i; + int event; + centity_t *cent; + + cent = &cg_entities[ps->clientNum]; + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { + // + if (i >= cg->eventSequence) { + continue; + } + // if this event is not further back in than the maximum predictable events we remember + if (i > cg->eventSequence - MAX_PREDICTED_EVENTS) { + // if the new playerstate event is different from a previously predicted one + if ( ps->events[i & (MAX_PS_EVENTS-1)] != cg->predictableEvents[i & (MAX_PREDICTED_EVENTS-1) ] ) { + + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg->predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + + if ( cg_showmiss.integer ) { +#ifndef FINAL_BUILD + CG_Printf("WARNING: changed predicted event\n"); +#endif + } + } + } + } +} + +/* +================== +pushReward +================== +*/ +#ifdef JK2AWARDS +static void pushReward(sfxHandle_t sfx, qhandle_t shader, int rewardCount) { + if (cg->rewardStack < (MAX_REWARDSTACK-1)) { + cg->rewardStack++; + cg->rewardSound[cg->rewardStack] = sfx; + cg->rewardShader[cg->rewardStack] = shader; + cg->rewardCount[cg->rewardStack] = rewardCount; + } +} +#endif + +int cgAnnouncerTime = 0; //to prevent announce sounds from playing on top of each other + +/* +================== +CG_CheckLocalSounds +================== +*/ +void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { + int highScore, health, armor, reward; +#ifdef JK2AWARDS + sfxHandle_t sfx; +#endif + + // don't play the sounds if the player just changed teams + if ( ps->persistant[PERS_TEAM] != ops->persistant[PERS_TEAM] ) { + return; + } + + // hit changes + if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) { + armor = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff; + health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8; + + if (armor > health/2) + { // We also hit shields along the way, so consider them "pierced". +// trap_S_StartLocalSound( cgs.media.shieldPierceSound, CHAN_LOCAL_SOUND ); + } + else + { // Shields didn't really stand in our way. +// trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); + } + + //FIXME: Hit sounds? + /* + if (armor > 50 ) { + trap_S_StartLocalSound( cgs.media.hitSoundHighArmor, CHAN_LOCAL_SOUND ); + } else if (armor || health > 100) { + trap_S_StartLocalSound( cgs.media.hitSoundLowArmor, CHAN_LOCAL_SOUND ); + } else { + trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); + } + */ + } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) { + //trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND ); + } + + // health changes of more than -3 should make pain sounds + if (cg_oldPainSounds.integer) + { + if ( ps->stats[STAT_HEALTH] < (ops->stats[STAT_HEALTH] - 3)) + { + if ( ps->stats[STAT_HEALTH] > 0 ) + { + CG_PainEvent( &cg_entities[cg->predictedPlayerState.clientNum], ps->stats[STAT_HEALTH] ); + } + } + } + + // if we are going into the intermission, don't start any voices + if ( cg->intermissionStarted ) { + return; + } + +#ifdef JK2AWARDS + // reward sounds + reward = qfalse; + if (ps->persistant[PERS_CAPTURES] != ops->persistant[PERS_CAPTURES]) { + pushReward(cgs.media.captureAwardSound, cgs.media.medalCapture, ps->persistant[PERS_CAPTURES]); + reward = qtrue; + //Com_Printf("capture\n"); + } + if (ps->persistant[PERS_IMPRESSIVE_COUNT] != ops->persistant[PERS_IMPRESSIVE_COUNT]) { + sfx = cgs.media.impressiveSound; + + pushReward(sfx, cgs.media.medalImpressive, ps->persistant[PERS_IMPRESSIVE_COUNT]); + reward = qtrue; + //Com_Printf("impressive\n"); + } + if (ps->persistant[PERS_EXCELLENT_COUNT] != ops->persistant[PERS_EXCELLENT_COUNT]) { + sfx = cgs.media.excellentSound; + pushReward(sfx, cgs.media.medalExcellent, ps->persistant[PERS_EXCELLENT_COUNT]); + reward = qtrue; + //Com_Printf("excellent\n"); + } + if (ps->persistant[PERS_GAUNTLET_FRAG_COUNT] != ops->persistant[PERS_GAUNTLET_FRAG_COUNT]) { + sfx = cgs.media.humiliationSound; + pushReward(sfx, cgs.media.medalGauntlet, ps->persistant[PERS_GAUNTLET_FRAG_COUNT]); + reward = qtrue; + //Com_Printf("guantlet frag\n"); + } + if (ps->persistant[PERS_DEFEND_COUNT] != ops->persistant[PERS_DEFEND_COUNT]) { + pushReward(cgs.media.defendSound, cgs.media.medalDefend, ps->persistant[PERS_DEFEND_COUNT]); + reward = qtrue; + //Com_Printf("defend\n"); + } + if (ps->persistant[PERS_ASSIST_COUNT] != ops->persistant[PERS_ASSIST_COUNT]) { + //pushReward(cgs.media.assistSound, cgs.media.medalAssist, ps->persistant[PERS_ASSIST_COUNT]); + //reward = qtrue; + //Com_Printf("assist\n"); + } + // if any of the player event bits changed + if (ps->persistant[PERS_PLAYEREVENTS] != ops->persistant[PERS_PLAYEREVENTS]) { + if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD)) { + trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER ); + } + else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD)) { + trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER ); + } + reward = qtrue; + } +#else + reward = qfalse; +#endif + // lead changes + if (!reward && cgAnnouncerTime < cg->time) { + // + if ( !cg->warmup && cgs.gametype != GT_POWERDUEL ) { + // never play lead changes during warmup and powerduel + if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) { + if ( cgs.gametype < GT_TEAM) { + /* + if ( ps->persistant[PERS_RANK] == 0 ) { + CG_AddBufferedSound(cgs.media.takenLeadSound); + cgAnnouncerTime = cg->time + 3000; + } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) { + //CG_AddBufferedSound(cgs.media.tiedLeadSound); + } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) { + //rww - only bother saying this if you have more than 1 kill already. + //joining the server and hearing "the force is not with you" is silly. + if (ps->persistant[PERS_SCORE] > 0) + { + CG_AddBufferedSound(cgs.media.lostLeadSound); + cgAnnouncerTime = cg->time + 3000; + } + } + */ + } + } + } + } + + // timelimit warnings + if ( cgs.timelimit > 0 && cgAnnouncerTime < cg->time ) { + int msec; + + msec = cg->time - cgs.levelStartTime; + if ( !( cg->timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) { + cg->timelimitWarnings |= 1 | 2 | 4; + //trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER ); + } + else if ( !( cg->timelimitWarnings & 2 ) && msec > (cgs.timelimit - 1) * 60 * 1000 ) { + cg->timelimitWarnings |= 1 | 2; + trap_S_StartLocalSound( cgs.media.oneMinuteSound, CHAN_ANNOUNCER ); + cgAnnouncerTime = cg->time + 3000; + } + else if ( cgs.timelimit > 5 && !( cg->timelimitWarnings & 1 ) && msec > (cgs.timelimit - 5) * 60 * 1000 ) { + cg->timelimitWarnings |= 1; + trap_S_StartLocalSound( cgs.media.fiveMinuteSound, CHAN_ANNOUNCER ); + cgAnnouncerTime = cg->time + 3000; + } + } + + // fraglimit warnings + if ( cgs.fraglimit > 0 && cgs.gametype < GT_CTF && cgs.gametype != GT_DUEL && cgs.gametype != GT_POWERDUEL && cgs.gametype != GT_SIEGE && cgAnnouncerTime < cg->time) { + highScore = cgs.scores1; + if ( !( cg->fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) { + cg->fraglimitWarnings |= 1 | 2 | 4; + CG_AddBufferedSound(cgs.media.oneFragSound); + cgAnnouncerTime = cg->time + 3000; + } + else if ( cgs.fraglimit > 2 && !( cg->fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) { + cg->fraglimitWarnings |= 1 | 2; + CG_AddBufferedSound(cgs.media.twoFragSound); + cgAnnouncerTime = cg->time + 3000; + } + else if ( cgs.fraglimit > 3 && !( cg->fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) { + cg->fraglimitWarnings |= 1; + CG_AddBufferedSound(cgs.media.threeFragSound); + cgAnnouncerTime = cg->time + 3000; + } + } +} + +/* +=============== +CG_TransitionPlayerState + +=============== +*/ +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { + // check for changing follow mode + if ( ps->clientNum != ops->clientNum ) { + cg->thisFrameTeleport = qtrue; + // make sure we don't get any unwanted transition effects + *ops = *ps; + } + + // damage events (player is getting wounded) + if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) { + CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); + } + + // respawning + if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) { + CG_Respawn(); + } + + if ( cg->mapRestart ) { + CG_Respawn(); + cg->mapRestart = qfalse; + } + + if ( cg->snap->ps.pm_type != PM_INTERMISSION + && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + CG_CheckLocalSounds( ps, ops ); + } + + // check for going low on ammo + CG_CheckAmmo(); + + // run events + CG_CheckPlayerstateEvents( ps, ops ); + + // smooth the ducking viewheight change + if ( ps->viewheight != ops->viewheight ) { + cg->duckChange = ps->viewheight - ops->viewheight; + cg->duckTime = cg->time; + } +} + diff --git a/codemp/cgame/cg_predict.c b/codemp/cgame/cg_predict.c new file mode 100644 index 0000000..2377b71 --- /dev/null +++ b/codemp/cgame/cg_predict.c @@ -0,0 +1,1539 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_predict.c -- this file generates cg->predictedPlayerState by either +// interpolating between snapshots from the server or locally predicting +// ahead the client's movement. +// It also handles local physics interaction, like fragments bouncing off walls + +#include "cg_local.h" + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + +static pmove_t cg_pmove; + +static int cg_numSolidEntities; +static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; +static int cg_numTriggerEntities; +static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; + +//is this client piloting this veh? +static CGAME_INLINE qboolean CG_Piloting(int vehNum) +{ + centity_t *veh; + + if (!vehNum) + { + return qfalse; + } + + veh = &cg_entities[vehNum]; + + if (veh->currentState.owner != cg->predictedPlayerState.clientNum) + { //the owner should be the current pilot + return qfalse; + } + + return qtrue; +} + +/* +==================== +CG_BuildSolidList + +When a new cg->snap has been set, this function builds a sublist +of the entities that are actually solid, to make for more +efficient collision detection +==================== +*/ +void CG_BuildSolidList( void ) { + int i; + centity_t *cent; + snapshot_t *snap; + entityState_t *ent; + vec3_t difference; + float dsquared; + + cg_numSolidEntities = 0; + cg_numTriggerEntities = 0; + + if ( cg->nextSnap && !cg->nextFrameTeleport && !cg->thisFrameTeleport ) { + snap = cg->nextSnap; + } else { + snap = cg->snap; + } + + for ( i = 0 ; i < snap->numEntities ; i++ ) { + cent = &cg_entities[ snap->entities[ i ].number ]; + ent = ¢->currentState; + + if ( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER ) { + cg_triggerEntities[cg_numTriggerEntities] = cent; + cg_numTriggerEntities++; + continue; + } + + if ( cent->nextState.solid ) { + cg_solidEntities[cg_numSolidEntities] = cent; + cg_numSolidEntities++; + continue; + } + } + + //rww - Horrible, terrible, awful hack. + //We don't send your client entity from the server, + //so it isn't added into the solid list from the snapshot, + //and in addition, it has no solid data. So we will force + //adding it in based on a hardcoded player bbox size. + //This will cause issues if the player box size is ever + //changed.. + if (cg_numSolidEntities < MAX_ENTITIES_IN_SNAPSHOT) + { + vec3_t playerMins = {-15, -15, DEFAULT_MINS_2}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + int i, j, k; + + i = playerMaxs[0]; + if (i<1) + i = 1; + if (i>255) + i = 255; + + // z is not symetric + j = (-playerMins[2]); + if (j<1) + j = 1; + if (j>255) + j = 255; + + // and z playerMaxs can be negative... + k = (playerMaxs[2]+32); + if (k<1) + k = 1; + if (k>255) + k = 255; + + cg_solidEntities[cg_numSolidEntities] = &cg_entities[cg->predictedPlayerState.clientNum]; + cg_solidEntities[cg_numSolidEntities]->currentState.solid = (k<<16) | (j<<8) | i; + + cg_numSolidEntities++; + } + + dsquared = /*RMG_distancecull.value*/5000+500; + dsquared *= dsquared; + + for(i=0;ilerpOrigin, snap->ps.origin, difference); + if (cent->currentState.eType == ET_TERRAIN || + ((difference[0]*difference[0]) + (difference[1]*difference[1]) + (difference[2]*difference[2])) <= dsquared) + { +#ifdef _XBOX + cent->currentValid[ClientManager::ActiveClientNum()] = qtrue; +#else + cent->currentValid = qtrue; +#endif + if ( cent->nextState.solid ) + { + cg_solidEntities[cg_numSolidEntities] = cent; + cg_numSolidEntities++; + } + } + else + { +#ifdef _XBOX + cent->currentValid[ClientManager::ActiveClientNum()] = qfalse; +#else + cent->currentValid = qfalse; +#endif + } + } +} + +static CGAME_INLINE qboolean CG_VehicleClipCheck(centity_t *ignored, trace_t *trace) +{ + if (!trace || trace->entityNum < 0 || trace->entityNum >= ENTITYNUM_WORLD) + { //it's alright then + return qtrue; + } + + if (ignored->currentState.eType != ET_PLAYER && + ignored->currentState.eType != ET_NPC) + { //can't possibly be valid then + return qtrue; + } + + if (ignored->currentState.m_iVehicleNum) + { //see if the ignore ent is a vehicle/rider - if so, see if the ent we supposedly hit is a vehicle/rider. + //if they belong to each other, we don't want to collide them. + centity_t *otherguy = &cg_entities[trace->entityNum]; + + if (otherguy->currentState.eType != ET_PLAYER && + otherguy->currentState.eType != ET_NPC) + { //can't possibly be valid then + return qtrue; + } + + if (otherguy->currentState.m_iVehicleNum) + { //alright, both of these are either a vehicle or a player who is on a vehicle + int index; + + if (ignored->currentState.eType == ET_PLAYER + || (ignored->currentState.eType == ET_NPC && ignored->currentState.NPC_class != CLASS_VEHICLE) ) + { //must be a player or NPC riding a vehicle + index = ignored->currentState.m_iVehicleNum; + } + else + { //a vehicle + index = ignored->currentState.m_iVehicleNum-1; + } + + if (index == otherguy->currentState.number) + { //this means we're riding or being ridden by this guy, so don't collide + return qfalse; + } + else + {//see if I'm hitting one of my own passengers + if (otherguy->currentState.eType == ET_PLAYER + || (otherguy->currentState.eType == ET_NPC && otherguy->currentState.NPC_class != CLASS_VEHICLE) ) + { //must be a player or NPC riding a vehicle + if (otherguy->currentState.m_iVehicleNum==ignored->currentState.number) + { //this means we're other guy is riding the ignored ent + return qfalse; + } + } + } + } + } + + return qtrue; +} + +//rww - I'm disabling this warning for this function. It complains about oldTrace but as you can see it +//always gets set before use, and I am not wasting CPU memsetting it to shut the compiler up. +#pragma warning(disable : 4701) //local variable may be used without having been initialized +/* +==================== +CG_ClipMoveToEntities + +==================== +*/ +#include "../namespace_begin.h" +extern void BG_VehicleAdjustBBoxForOrientation( Vehicle_t *veh, vec3_t origin, vec3_t mins, vec3_t maxs, + int clientNum, int tracemask, + void (*localTrace)(trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask)); // bg_pmove.c +#include "../namespace_end.h" +static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask, trace_t *tr, qboolean g2Check ) { + int i, x, zd, zu; + trace_t trace, oldTrace; + entityState_t *ent; + clipHandle_t cmodel; + vec3_t bmins, bmaxs; + vec3_t origin, angles; + centity_t *cent; + centity_t *ignored = NULL; + + if (skipNumber != -1 && skipNumber != ENTITYNUM_NONE) + { + ignored = &cg_entities[skipNumber]; + } + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + ent = ¢->currentState; + + if ( ent->number == skipNumber ) { + continue; + } + + if ( ent->number > MAX_CLIENTS && + (ent->genericenemyindex-MAX_GENTITIES==cg->predictedPlayerState.clientNum || ent->genericenemyindex-MAX_GENTITIES==cg->predictedVehicleState.clientNum) ) +// if (ent->number > MAX_CLIENTS && cg->snap && ent->genericenemyindex && (ent->genericenemyindex-MAX_GENTITIES) == cg->snap->ps.clientNum) + { //rww - method of keeping objects from colliding in client-prediction (in case of ownership) + continue; + } + + if ( ent->solid == SOLID_BMODEL ) { + // special value for bmodel + cmodel = trap_CM_InlineModel( ent->modelindex ); + VectorCopy( cent->lerpAngles, angles ); + BG_EvaluateTrajectory( ¢->currentState.pos, cg->physicsTime, origin ); + } else { + // encoded bbox + x = (ent->solid & 255); + zd = ((ent->solid>>8) & 255); + zu = ((ent->solid>>16) & 255) - 32; + + bmins[0] = bmins[1] = -x; + bmaxs[0] = bmaxs[1] = x; + bmins[2] = -zd; + bmaxs[2] = zu; + + if (ent->eType == ET_NPC && ent->NPC_class == CLASS_VEHICLE && + cent->m_pVehicle) + { //try to dynamically adjust his bbox dynamically, if possible + float *old = cent->m_pVehicle->m_vOrientation; + cent->m_pVehicle->m_vOrientation = ¢->lerpAngles[0]; + BG_VehicleAdjustBBoxForOrientation(cent->m_pVehicle, cent->lerpOrigin, bmins, bmaxs, + cent->currentState.number, MASK_PLAYERSOLID, NULL); + cent->m_pVehicle->m_vOrientation = old; + } + + cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); + VectorCopy( vec3_origin, angles ); + + VectorCopy( cent->lerpOrigin, origin ); + } + + + trap_CM_TransformedBoxTrace ( &trace, start, end, + mins, maxs, cmodel, mask, origin, angles); + trace.entityNum = trace.fraction != 1.0 ? ent->number : ENTITYNUM_NONE; + + if (g2Check || (ignored && ignored->currentState.m_iVehicleNum)) + { + //keep these older variables around for a bit, incase we need to replace them in the Ghoul2 Collision check + //or in the vehicle owner trace check + oldTrace = *tr; + } + + if (trace.allsolid || trace.fraction < tr->fraction) { + trace.entityNum = ent->number; + *tr = trace; + } else if (trace.startsolid) { + tr->startsolid = qtrue; + + //rww 12-02-02 + tr->entityNum = trace.entityNum = ent->number; + } + if ( tr->allsolid ) + { + if (ignored && ignored->currentState.m_iVehicleNum) + { + trace.entityNum = ent->number; + if (CG_VehicleClipCheck(ignored, &trace)) + { //this isn't our vehicle, we're really stuck + return; + } + else + { //it's alright, keep going + trace = oldTrace; + *tr = trace; + } + } + else + { + return; + } + } + + if (g2Check) + { + if (trace.entityNum == ent->number && cent->ghoul2) + { + CG_G2TraceCollide(&trace, mins, maxs, start, end); + + if (trace.entityNum == ENTITYNUM_NONE) + { //g2 trace failed, so put it back where it was. + trace = oldTrace; + *tr = trace; + } + } + } + + if (ignored && ignored->currentState.m_iVehicleNum) + { //see if this is the vehicle we hit + centity_t *hit = &cg_entities[trace.entityNum]; + if (!CG_VehicleClipCheck(ignored, &trace)) + { //looks like it + trace = oldTrace; + *tr = trace; + } + else if (hit->currentState.eType == ET_MISSILE && + hit->currentState.owner == ignored->currentState.number) + { //hack, don't hit own missiles + trace = oldTrace; + *tr = trace; + } + } + } +} +#pragma warning(default : 4701) //local variable may be used without having been initialized + +/* +================ +CG_Trace +================ +*/ +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) { + trace_t t; + + trap_CM_BoxTrace ( &t, start, end, mins, maxs, 0, mask); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities (start, mins, maxs, end, skipNumber, mask, &t, qfalse); + + *result = t; +} + +/* +================ +CG_G2Trace +================ +*/ +void CG_G2Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) { + trace_t t; + + trap_CM_BoxTrace ( &t, start, end, mins, maxs, 0, mask); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities (start, mins, maxs, end, skipNumber, mask, &t, qtrue); + + *result = t; +} + +/* +================ +CG_PointContents +================ +*/ +int CG_PointContents( const vec3_t point, int passEntityNum ) { + int i; + entityState_t *ent; + centity_t *cent; + clipHandle_t cmodel; + int contents; + + contents = trap_CM_PointContents (point, 0); + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + + ent = ¢->currentState; + + if ( ent->number == passEntityNum ) { + continue; + } + + if (ent->solid != SOLID_BMODEL) { // special value for bmodel + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); + } + + return contents; +} + + +/* +======================== +CG_InterpolatePlayerState + +Generates cg->predictedPlayerState by interpolating between +cg->snap->player_state and cg->nextFrame->player_state +======================== +*/ +static void CG_InterpolatePlayerState( qboolean grabAngles ) { + float f; + int i; + playerState_t *out; + snapshot_t *prev, *next; + + out = &cg->predictedPlayerState; + prev = cg->snap; + next = cg->nextSnap; + + *out = cg->snap->ps; + + // if we are still allowing local input, short circuit the view angles + if ( grabAngles ) { + usercmd_t cmd; + int cmdNum; + + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + + PM_UpdateViewAngles( out, &cmd ); + } + + // if the next frame is a teleport, we can't lerp to it + if ( cg->nextFrameTeleport ) { + return; + } + + if ( !next || next->serverTime <= prev->serverTime ) { + return; + } + + f = (float)( cg->time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); + + i = next->ps.bobCycle; + if ( i < prev->ps.bobCycle ) { + i += 256; // handle wraparound + } + out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); + + for ( i = 0 ; i < 3 ; i++ ) { + out->origin[i] = prev->ps.origin[i] + f * (next->ps.origin[i] - prev->ps.origin[i] ); + if ( !grabAngles ) { + out->viewangles[i] = LerpAngle( + prev->ps.viewangles[i], next->ps.viewangles[i], f ); + } + out->velocity[i] = prev->ps.velocity[i] + + f * (next->ps.velocity[i] - prev->ps.velocity[i] ); + } + +} + +static void CG_InterpolateVehiclePlayerState( qboolean grabAngles ) { + float f; + int i; + playerState_t *out; + snapshot_t *prev, *next; + + out = &cg->predictedVehicleState; + prev = cg->snap; + next = cg->nextSnap; + + *out = cg->snap->vps; + + // if we are still allowing local input, short circuit the view angles + if ( grabAngles ) { + usercmd_t cmd; + int cmdNum; + + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + + PM_UpdateViewAngles( out, &cmd ); + } + + // if the next frame is a teleport, we can't lerp to it + if ( cg->nextFrameTeleport ) { + return; + } + + if ( !next || next->serverTime <= prev->serverTime ) { + return; + } + + f = (float)( cg->time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); + + i = next->vps.bobCycle; + if ( i < prev->vps.bobCycle ) { + i += 256; // handle wraparound + } + out->bobCycle = prev->vps.bobCycle + f * ( i - prev->vps.bobCycle ); + + for ( i = 0 ; i < 3 ; i++ ) { + out->origin[i] = prev->vps.origin[i] + f * (next->vps.origin[i] - prev->vps.origin[i] ); + if ( !grabAngles ) { + out->viewangles[i] = LerpAngle( + prev->vps.viewangles[i], next->vps.viewangles[i], f ); + } + out->velocity[i] = prev->vps.velocity[i] + + f * (next->vps.velocity[i] - prev->vps.velocity[i] ); + } + +} + +/* +=================== +CG_TouchItem +=================== +*/ +static void CG_TouchItem( centity_t *cent ) { + gitem_t *item; + + if ( !cg_predictItems.integer ) { + return; + } + if ( !BG_PlayerTouchesItem( &cg->predictedPlayerState, ¢->currentState, cg->time ) ) { + return; + } + + if (cent->currentState.brokenLimbs) + { //dropped item + return; + } + + if (cent->currentState.eFlags & EF_ITEMPLACEHOLDER) + { + return; + } + + if (cent->currentState.eFlags & EF_NODRAW) + { + return; + } + + // never pick an item up twice in a prediction + if ( cent->miscTime == cg->time ) { + return; + } + + if ( !BG_CanItemBeGrabbed( cgs.gametype, ¢->currentState, &cg->predictedPlayerState ) ) { + return; // can't hold it + } + + item = &bg_itemlist[ cent->currentState.modelindex ]; + + //Currently there is no reliable way of knowing if the client has touched a certain item before another if they are next to each other, or rather + //if the server has touched them in the same order. This results often in grabbing an item in the prediction and the server giving you the other + //item. So for now prediction of armor, health, and ammo is disabled. +/* + if (item->giType == IT_ARMOR) + { //rww - this will be stomped next update, but it's set so that we don't try to pick up two shields in one prediction and have the server cancel one + // cg->predictedPlayerState.stats[STAT_ARMOR] += item->quantity; + + //FIXME: This can't be predicted properly at the moment + return; + } + + if (item->giType == IT_HEALTH) + { //same as above, for health + // cg->predictedPlayerState.stats[STAT_HEALTH] += item->quantity; + + //FIXME: This can't be predicted properly at the moment + return; + } + + if (item->giType == IT_AMMO) + { //same as above, for ammo + // cg->predictedPlayerState.ammo[item->giTag] += item->quantity; + + //FIXME: This can't be predicted properly at the moment + return; + } + + if (item->giType == IT_HOLDABLE) + { //same as above, for holdables + // cg->predictedPlayerState.stats[STAT_HOLDABLE_ITEMS] |= (1 << item->giTag); + } +*/ + // Special case for flags. + // We don't predict touching our own flag + if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { + if (cg->predictedPlayerState.persistant[PERS_TEAM] == TEAM_RED && + item->giTag == PW_REDFLAG) + return; + if (cg->predictedPlayerState.persistant[PERS_TEAM] == TEAM_BLUE && + item->giTag == PW_BLUEFLAG) + return; + } + + if (item->giType == IT_POWERUP && + (item->giTag == PW_FORCE_ENLIGHTENED_LIGHT || item->giTag == PW_FORCE_ENLIGHTENED_DARK)) + { + if (item->giTag == PW_FORCE_ENLIGHTENED_LIGHT) + { + if (cg->predictedPlayerState.fd.forceSide != FORCE_LIGHTSIDE) + { + return; + } + } + else + { + if (cg->predictedPlayerState.fd.forceSide != FORCE_DARKSIDE) + { + return; + } + } + } + + + // grab it + BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.number , &cg->predictedPlayerState); + + // remove it from the frame so it won't be drawn + cent->currentState.eFlags |= EF_NODRAW; + + // don't touch it again this prediction + cent->miscTime = cg->time; + + // if its a weapon, give them some predicted ammo so the autoswitch will work + if ( item->giType == IT_WEAPON ) { + cg->predictedPlayerState.stats[ STAT_WEAPONS ] |= 1 << item->giTag; + if ( !cg->predictedPlayerState.ammo[ item->giTag ] ) { + cg->predictedPlayerState.ammo[ item->giTag ] = 1; + } + } +} + + +/* +========================= +CG_TouchTriggerPrediction + +Predict push triggers and items +========================= +*/ +static void CG_TouchTriggerPrediction( void ) { + int i; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + centity_t *cent; + qboolean spectator; + + // dead clients don't activate triggers + if ( cg->predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + spectator = ( cg->predictedPlayerState.pm_type == PM_SPECTATOR ); + + if ( cg->predictedPlayerState.pm_type != PM_NORMAL && cg->predictedPlayerState.pm_type != PM_JETPACK && cg->predictedPlayerState.pm_type != PM_FLOAT && !spectator ) { + return; + } + + for ( i = 0 ; i < cg_numTriggerEntities ; i++ ) { + cent = cg_triggerEntities[ i ]; + ent = ¢->currentState; + + if ( ent->eType == ET_ITEM && !spectator ) { + CG_TouchItem( cent ); + continue; + } + + if ( ent->solid != SOLID_BMODEL ) { + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + trap_CM_BoxTrace( &trace, cg->predictedPlayerState.origin, cg->predictedPlayerState.origin, + cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); + + if ( !trace.startsolid ) { + continue; + } + + if ( ent->eType == ET_TELEPORT_TRIGGER ) { + cg->hyperspace = qtrue; + } else if ( ent->eType == ET_PUSH_TRIGGER ) { + BG_TouchJumpPad( &cg->predictedPlayerState, ent ); + } + } + + // if we didn't touch a jump pad this pmove frame + if ( cg->predictedPlayerState.jumppad_frame != cg->predictedPlayerState.pmove_framecount ) { + cg->predictedPlayerState.jumppad_frame = 0; + cg->predictedPlayerState.jumppad_ent = 0; + } +} + +#if 0 +static ID_INLINE void CG_EntityStateToPlayerState( entityState_t *s, playerState_t *ps ) +{ + //currently unused vars commented out for speed.. only uncomment if you need them. + ps->clientNum = s->number; + VectorCopy( s->pos.trBase, ps->origin ); + VectorCopy( s->pos.trDelta, ps->velocity ); + ps->saberLockFrame = s->forceFrame; + ps->legsAnim = s->legsAnim; + ps->torsoAnim = s->torsoAnim; + ps->legsFlip = s->legsFlip; + ps->torsoFlip = s->torsoFlip; + ps->clientNum = s->clientNum; + ps->saberMove = s->saberMove; + + /* + VectorCopy( s->apos.trBase, ps->viewangles ); + + ps->fd.forceMindtrickTargetIndex = s->trickedentindex; + ps->fd.forceMindtrickTargetIndex2 = s->trickedentindex2; + ps->fd.forceMindtrickTargetIndex3 = s->trickedentindex3; + ps->fd.forceMindtrickTargetIndex4 = s->trickedentindex4; + + ps->electrifyTime = s->emplacedOwner; + + ps->speed = s->speed; + + ps->genericEnemyIndex = s->genericenemyindex; + + ps->activeForcePass = s->activeForcePass; + + ps->movementDir = s->angles2[YAW]; + + ps->eFlags = s->eFlags; + + ps->saberInFlight = s->saberInFlight; + ps->saberEntityNum = s->saberEntityNum; + + ps->fd.forcePowersActive = s->forcePowersActive; + + if (s->bolt1) + { + ps->duelInProgress = qtrue; + } + else + { + ps->duelInProgress = qfalse; + } + + if (s->bolt2) + { + ps->dualBlade = qtrue; + } + else + { + ps->dualBlade = qfalse; + } + + ps->emplacedIndex = s->otherEntityNum2; + + ps->saberHolstered = s->saberHolstered; //reuse bool in entitystate for players differently + + ps->genericEnemyIndex = -1; //no real option for this + + //The client has no knowledge of health levels (except for the client entity) + if (s->eFlags & EF_DEAD) + { + ps->stats[STAT_HEALTH] = 0; + } + else + { + ps->stats[STAT_HEALTH] = 100; + } + + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + ps->weapon = s->weapon; + ps->groundEntityNum = s->groundEntityNum; + + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if (s->powerups & (1 << i)) + { + ps->powerups[i] = 30; + } + else + { + ps->powerups[i] = 0; + } + } + + ps->loopSound = s->loopSound; + ps->generic1 = s->generic1; + */ +} +#endif + +// This many playerState_t structures is painfully large. And we +// don't need that many. So we just use a small pool of them. +// PC gets to keep one per entity, just in case. +#ifdef _XBOX + +struct psLinkedNode_t +{ + playerState_t ps; + psLinkedNode_t *next; +}; + +#define CG_SEND_PS_POOL_SIZE 64 +psLinkedNode_t cgSendPSPool[ CG_SEND_PS_POOL_SIZE ]; +psLinkedNode_t *cgSendPSFreeList; + +#else +playerState_t cgSendPSPool[ MAX_GENTITIES ]; +#endif + +playerState_t *cgSendPS[MAX_GENTITIES]; + +#ifdef _XBOX +void AllocSendPlayerstate(int entNum) +{ + if (cgSendPS[entNum]) + { + //Com_Printf( S_COLOR_RED "ERROR: Entity %d already has a playerstate!\n", entNum ); + return; + } + + if (!cgSendPSFreeList) + Com_Error( ERR_DROP, "ERROR: No free playerstates! Increase CG_SEND_PS_POOL_SIZE\n" ); + + cgSendPS[entNum] = &cgSendPSFreeList->ps; + cgSendPSFreeList = cgSendPSFreeList->next; +} +#endif + +//#define _PROFILE_ES_TO_PS + +#ifdef _PROFILE_ES_TO_PS +int g_cgEStoPSTime = 0; +#endif + +//Assign all the entity playerstate pointers to the corresponding one +//so that we can access playerstate stuff in bg code (and then translate +//it back to entitystate data) +void CG_PmoveClientPointerUpdate() +{ + int i; + + memset(&cgSendPSPool[0], 0, sizeof(cgSendPSPool)); + + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) + { +#ifdef _XBOX + cgSendPS[i] = NULL; +#else + cgSendPS[i] = &cgSendPSPool[i]; +#endif + + // These will be invalid at this point on Xbox + cg_entities[i].playerState = cgSendPS[i]; + } + +#ifdef _XBOX + for ( i = 0; i < CG_SEND_PS_POOL_SIZE - 1; i++ ) + { + cgSendPSPool[i].next = &cgSendPSPool[i+1]; + } + + // Last .next is already NULL from memset above + cgSendPSFreeList = &cgSendPSPool[0]; +#endif + + //Set up bg entity data + cg_pmove.baseEnt = (bgEntity_t *)cg_entities; + cg_pmove.entSize = sizeof(centity_t); + + cg_pmove.ghoul2 = NULL; +} + +//check if local client is on an eweb +qboolean CG_UsingEWeb(void) +{ + if (cg->predictedPlayerState.weapon == WP_EMPLACED_GUN && cg->predictedPlayerState.emplacedIndex && + cg_entities[cg->predictedPlayerState.emplacedIndex].currentState.weapon == WP_NONE) + { + return qtrue; + } + + return qfalse; +} + +/* +================= +CG_PredictPlayerState + +Generates cg->predictedPlayerState for the current cg->time +cg->predictedPlayerState is guaranteed to be valid after exiting. + +For demo playback, this will be an interpolation between two valid +playerState_t. + +For normal gameplay, it will be the result of predicted usercmd_t on +top of the most recent playerState_t received from the server. + +Each new snapshot will usually have one or more new usercmd over the last, +but we simulate all unacknowledged commands each time, not just the new ones. +This means that on an internet connection, quite a few pmoves may be issued +each frame. + +OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t +differs from the predicted one. Would require saving all intermediate +playerState_t during prediction. + +We detect prediction errors and allow them to be decayed off over several frames +to ease the jerk. +================= +*/ +extern void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ); +extern vmCvar_t cg_showVehBounds; +pmove_t cg_vehPmove; +qboolean cg_vehPmoveSet = qfalse; + +#pragma warning(disable : 4701) //local variable may be used without having been initialized +void CG_PredictPlayerState( void ) { + int cmdNum, current, i; + playerState_t oldPlayerState; + playerState_t oldVehicleState; + qboolean moved; + usercmd_t oldestCmd; + usercmd_t latestCmd; + centity_t *pEnt; + clientInfo_t *ci; + + cg->hyperspace = qfalse; // will be set if touching a trigger_teleport + + // if this is the first frame we must guarantee + // predictedPlayerState is valid even if there is some + // other error condition + if ( !cg->validPPS ) { + cg->validPPS = qtrue; + cg->predictedPlayerState = cg->snap->ps; + if (CG_Piloting(cg->snap->ps.m_iVehicleNum)) + { + cg->predictedVehicleState = cg->snap->vps; + } + } + + // demo playback just copies the moves + if ( cg->demoPlayback || (cg->snap->ps.pm_flags & PMF_FOLLOW) ) { + CG_InterpolatePlayerState( qfalse ); + if (CG_Piloting(cg->predictedPlayerState.m_iVehicleNum)) + { + CG_InterpolateVehiclePlayerState(qfalse); + } + return; + } + + // non-predicting local movement will grab the latest angles +#ifdef _XBOX + if ( ClientManager::splitScreenMode == qtrue) { + CG_InterpolatePlayerState( qtrue ); + if (CG_Piloting(cg->predictedPlayerState.m_iVehicleNum)) + { + CG_InterpolateVehiclePlayerState(qtrue); + } + return; + } +#endif + + if ( cg_nopredict.integer || cg_synchronousClients.integer || CG_UsingEWeb() ) { + CG_InterpolatePlayerState( qtrue ); + if (CG_Piloting(cg->predictedPlayerState.m_iVehicleNum)) + { + CG_InterpolateVehiclePlayerState(qtrue); + } + return; + } + + // prepare for pmove + cg_pmove.ps = &cg->predictedPlayerState; + cg_pmove.trace = CG_Trace; + cg_pmove.pointcontents = CG_PointContents; + + pEnt = &cg_entities[cg->predictedPlayerState.clientNum]; + //rww - bgghoul2 + if (cg_pmove.ghoul2 != pEnt->ghoul2) //only update it if the g2 instance has changed + { + if (cg->snap && + pEnt->ghoul2 && + !(cg->snap->ps.pm_flags & PMF_FOLLOW) && + cg->snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR) + { + cg_pmove.ghoul2 = pEnt->ghoul2; + cg_pmove.g2Bolts_LFoot = trap_G2API_AddBolt(pEnt->ghoul2, 0, "*l_leg_foot"); + cg_pmove.g2Bolts_RFoot = trap_G2API_AddBolt(pEnt->ghoul2, 0, "*r_leg_foot"); + } + else + { + cg_pmove.ghoul2 = NULL; + } + } + + ci = &cgs.clientinfo[cg->predictedPlayerState.clientNum]; + + //I'll just do this every frame in case the scale changes in realtime (don't need to update the g2 inst for that) + VectorCopy(pEnt->modelScale, cg_pmove.modelScale); + //rww end bgghoul2 + + if ( cg_pmove.ps->pm_type == PM_DEAD ) { + cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + else { + cg_pmove.tracemask = MASK_PLAYERSOLID; + } + if ( cg->snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies + } + cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; + + // save the state before the pmove so we can detect transitions + oldPlayerState = cg->predictedPlayerState; + if (CG_Piloting(cg->predictedPlayerState.m_iVehicleNum)) + { + oldVehicleState = cg->predictedVehicleState; + } + + current = trap_GetCurrentCmdNumber(); + + // if we don't have the commands right after the snapshot, we + // can't accurately predict a current position, so just freeze at + // the last good position we had + cmdNum = current - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &oldestCmd ); + if ( oldestCmd.serverTime > cg->snap->ps.commandTime + && oldestCmd.serverTime < cg->time ) { // special check for map_restart + if ( cg_showmiss.integer ) { + CG_Printf ("exceeded PACKET_BACKUP on commands\n"); + } + return; + } + + // get the latest command so we can know which commands are from previous map_restarts + trap_GetUserCmd( current, &latestCmd ); + + // get the most recent information we have, even if + // the server time is beyond our current cg->time, + // because predicted player positions are going to + // be ahead of everything else anyway + if ( cg->nextSnap && !cg->nextFrameTeleport && !cg->thisFrameTeleport ) { + cg->nextSnap->ps.slopeRecalcTime = cg->predictedPlayerState.slopeRecalcTime; //this is the only value we want to maintain seperately on server/client + cg->predictedPlayerState = cg->nextSnap->ps; + if (CG_Piloting(cg->nextSnap->ps.m_iVehicleNum)) + { + cg->predictedVehicleState = cg->nextSnap->vps; + } + cg->physicsTime = cg->nextSnap->serverTime; + } else { + cg->snap->ps.slopeRecalcTime = cg->predictedPlayerState.slopeRecalcTime; //this is the only value we want to maintain seperately on server/client + cg->predictedPlayerState = cg->snap->ps; + if (CG_Piloting(cg->snap->ps.m_iVehicleNum)) + { + cg->predictedVehicleState = cg->snap->vps; + } + cg->physicsTime = cg->snap->serverTime; + } + + if ( pmove_msec.integer < 8 ) { + trap_Cvar_Set("pmove_msec", "8"); + } + else if (pmove_msec.integer > 33) { + trap_Cvar_Set("pmove_msec", "33"); + } + + cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; + cg_pmove.pmove_msec = pmove_msec.integer; + + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) + { + //Written this way for optimal speed, even though it doesn't look pretty. + //(we don't want to spend the time assigning pointers as it does take + //a small precious fraction of time and adds up in the loop.. so says + //the precision timer!) + + if (cg_entities[i].currentState.eType == ET_PLAYER || + cg_entities[i].currentState.eType == ET_NPC) + { + // Need a new playerState_t on Xbox +#ifdef _XBOX + AllocSendPlayerstate(i); +#endif + VectorCopy( cg_entities[i].currentState.pos.trBase, cgSendPS[i]->origin ); + VectorCopy( cg_entities[i].currentState.pos.trDelta, cgSendPS[i]->velocity ); + cgSendPS[i]->saberLockFrame = cg_entities[i].currentState.forceFrame; + cgSendPS[i]->legsAnim = cg_entities[i].currentState.legsAnim; + cgSendPS[i]->torsoAnim = cg_entities[i].currentState.torsoAnim; + cgSendPS[i]->legsFlip = cg_entities[i].currentState.legsFlip; + cgSendPS[i]->torsoFlip = cg_entities[i].currentState.torsoFlip; + cgSendPS[i]->clientNum = cg_entities[i].currentState.clientNum; + cgSendPS[i]->saberMove = cg_entities[i].currentState.saberMove; + } + } + + if (CG_Piloting(cg->predictedPlayerState.m_iVehicleNum)) + { + cg_entities[cg->predictedPlayerState.clientNum].playerState = &cg->predictedPlayerState; + cg_entities[cg->predictedPlayerState.m_iVehicleNum].playerState = &cg->predictedVehicleState; + + //use the player command time, because we are running with the player cmds (this is even the case + //on the server) + cg->predictedVehicleState.commandTime = cg->predictedPlayerState.commandTime; + } + + // run cmds + moved = qfalse; + for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { + // get the command + trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); + + if ( cg_pmove.pmove_fixed ) { + PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); + } + + // don't do anything if the time is before the snapshot player time + if ( cg_pmove.cmd.serverTime <= cg->predictedPlayerState.commandTime ) + { + continue; + } + + // don't do anything if the command was from a previous map_restart + if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) { + continue; + } + + // check for a prediction error from last frame + // on a lan, this will often be the exact value + // from the snapshot, but on a wan we will have + // to predict several commands to get to the point + // we want to compare + if ( CG_Piloting(oldPlayerState.m_iVehicleNum) && + cg->predictedVehicleState.commandTime == oldVehicleState.commandTime ) + { + vec3_t delta; + float len; + + if ( cg->thisFrameTeleport ) { + // a teleport will not cause an error decay + VectorClear( cg->predictedError ); + if ( cg_showVehMiss.integer ) { + CG_Printf( "VEH PredictionTeleport\n" ); + } + cg->thisFrameTeleport = qfalse; + } else { + vec3_t adjusted; + CG_AdjustPositionForMover( cg->predictedVehicleState.origin, + cg->predictedVehicleState.groundEntityNum, cg->physicsTime, cg->oldTime, adjusted ); + + if ( cg_showVehMiss.integer ) { + if (!VectorCompare( oldVehicleState.origin, adjusted )) { + CG_Printf("VEH prediction error\n"); + } + } + VectorSubtract( oldVehicleState.origin, adjusted, delta ); + len = VectorLength( delta ); + if ( len > 0.1 ) { + if ( cg_showVehMiss.integer ) { + CG_Printf("VEH Prediction miss: %f\n", len); + } + if ( cg_errorDecay.integer ) { + int t; + float f; + + t = cg->time - cg->predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f < 0 ) { + f = 0; + } + if ( f > 0 && cg_showVehMiss.integer ) { + CG_Printf("VEH Double prediction decay: %f\n", f); + } + VectorScale( cg->predictedError, f, cg->predictedError ); + } else { + VectorClear( cg->predictedError ); + } + VectorAdd( delta, cg->predictedError, cg->predictedError ); + cg->predictedErrorTime = cg->oldTime; + } + // + if ( cg_showVehMiss.integer ) { + if (!VectorCompare( oldVehicleState.vehOrientation, cg->predictedVehicleState.vehOrientation )) { + CG_Printf("VEH orient prediction error\n"); + CG_Printf("VEH pitch prediction miss: %f\n", AngleSubtract( oldVehicleState.vehOrientation[0], cg->predictedVehicleState.vehOrientation[0] ) ); + CG_Printf("VEH yaw prediction miss: %f\n", AngleSubtract( oldVehicleState.vehOrientation[1], cg->predictedVehicleState.vehOrientation[1] ) ); + CG_Printf("VEH roll prediction miss: %f\n", AngleSubtract( oldVehicleState.vehOrientation[2], cg->predictedVehicleState.vehOrientation[2] ) ); + } + } + } + } + else if ( !oldPlayerState.m_iVehicleNum && //don't do pred err on ps while riding veh + cg->predictedPlayerState.commandTime == oldPlayerState.commandTime ) + { + vec3_t delta; + float len; + + if ( cg->thisFrameTeleport ) { + // a teleport will not cause an error decay + VectorClear( cg->predictedError ); + if ( cg_showmiss.integer ) { + CG_Printf( "PredictionTeleport\n" ); + } + cg->thisFrameTeleport = qfalse; + } else { + vec3_t adjusted; + CG_AdjustPositionForMover( cg->predictedPlayerState.origin, + cg->predictedPlayerState.groundEntityNum, cg->physicsTime, cg->oldTime, adjusted ); + + if ( cg_showmiss.integer ) { + if (!VectorCompare( oldPlayerState.origin, adjusted )) { + CG_Printf("prediction error\n"); + } + } + VectorSubtract( oldPlayerState.origin, adjusted, delta ); + len = VectorLength( delta ); + if ( len > 0.1 ) { + if ( cg_showmiss.integer ) { + CG_Printf("Prediction miss: %f\n", len); + } + if ( cg_errorDecay.integer ) { + int t; + float f; + + t = cg->time - cg->predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f < 0 ) { + f = 0; + } + if ( f > 0 && cg_showmiss.integer ) { + CG_Printf("Double prediction decay: %f\n", f); + } + VectorScale( cg->predictedError, f, cg->predictedError ); + } else { + VectorClear( cg->predictedError ); + } + VectorAdd( delta, cg->predictedError, cg->predictedError ); + cg->predictedErrorTime = cg->oldTime; + } + } + } + + if ( cg_pmove.pmove_fixed ) { + cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + } + + cg_pmove.animations = bgAllAnims[pEnt->localAnimIndex].anims; + cg_pmove.gametype = cgs.gametype; + + cg_pmove.debugMelee = cgs.debugMelee; + cg_pmove.stepSlideFix = cgs.stepSlideFix; + cg_pmove.noSpecMove = cgs.noSpecMove; + + cg_pmove.nonHumanoid = (pEnt->localAnimIndex > 0); + + if (cg->snap && cg->snap->ps.saberLockTime > cg->time) + { + centity_t *blockOpp = &cg_entities[cg->snap->ps.saberLockEnemy]; + + if (blockOpp) + { + vec3_t lockDir, lockAng; + + VectorSubtract( blockOpp->lerpOrigin, cg->snap->ps.origin, lockDir ); + vectoangles(lockDir, lockAng); + + VectorCopy(lockAng, cg_pmove.ps->viewangles); + } + } + + //THIS is pretty much bad, but... + cg_pmove.ps->fd.saberAnimLevelBase = cg_pmove.ps->fd.saberAnimLevel; + if ( cg_pmove.ps->saberHolstered == 1 ) + { + if ( ci->saber[0].numBlades > 0 ) + { + cg_pmove.ps->fd.saberAnimLevelBase = SS_STAFF; + } + else if ( ci->saber[1].model[0] ) + { + cg_pmove.ps->fd.saberAnimLevelBase = SS_DUAL; + } + } + + Pmove (&cg_pmove); + + if (CG_Piloting(cg->predictedPlayerState.m_iVehicleNum) && + cg->predictedPlayerState.pm_type != PM_INTERMISSION) + { //we're riding a vehicle, let's predict it + centity_t *veh = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + int x, zd, zu; + + if (veh->m_pVehicle) + { //make sure pointer is set up to go to our predicted state + veh->m_pVehicle->m_vOrientation = &cg->predictedVehicleState.vehOrientation[0]; + + //keep this updated based on what the playerstate says + veh->m_pVehicle->m_iRemovedSurfaces = cg->predictedVehicleState.vehSurfaces; + + trap_GetUserCmd( cmdNum, &veh->m_pVehicle->m_ucmd ); + + if ( veh->m_pVehicle->m_ucmd.buttons & BUTTON_TALK ) + { //forced input if "chat bubble" is up + veh->m_pVehicle->m_ucmd.buttons = BUTTON_TALK; + veh->m_pVehicle->m_ucmd.forwardmove = 0; + veh->m_pVehicle->m_ucmd.rightmove = 0; + veh->m_pVehicle->m_ucmd.upmove = 0; + } + cg_vehPmove.ps = &cg->predictedVehicleState; + cg_vehPmove.animations = bgAllAnims[veh->localAnimIndex].anims; + + memcpy(&cg_vehPmove.cmd, &veh->m_pVehicle->m_ucmd, sizeof(usercmd_t)); + /* + cg_vehPmove.cmd.rightmove = 0; //no vehicle can move right/left + cg_vehPmove.cmd.upmove = 0; //no vehicle can move up/down + */ + + cg_vehPmove.gametype = cgs.gametype; + cg_vehPmove.ghoul2 = veh->ghoul2; + + cg_vehPmove.nonHumanoid = (veh->localAnimIndex > 0); + + /* + x = (veh->currentState.solid & 255); + zd = (veh->currentState.solid & 255); + zu = (veh->currentState.solid & 255) - 32; + + cg_vehPmove.mins[0] = cg_vehPmove.mins[1] = -x; + cg_vehPmove.maxs[0] = cg_vehPmove.maxs[1] = x; + cg_vehPmove.mins[2] = -zd; + cg_vehPmove.maxs[2] = zu; + */ + //I think this was actually wrong.. just copy-pasted from id code. Oh well. + x = (veh->currentState.solid)&255; + zd = (veh->currentState.solid>>8)&255; + zu = (veh->currentState.solid>>15)&255; + + zu -= 32; //I don't quite get the reason for this. + zd = -zd; + + //z/y must be symmetrical (blah) + cg_vehPmove.mins[0] = cg_vehPmove.mins[1] = -x; + cg_vehPmove.maxs[0] = cg_vehPmove.maxs[1] = x; + cg_vehPmove.mins[2] = zd; + cg_vehPmove.maxs[2] = zu; + + VectorCopy(veh->modelScale, cg_vehPmove.modelScale); + + if (!cg_vehPmoveSet) + { //do all the one-time things + cg_vehPmove.trace = CG_Trace; + cg_vehPmove.pointcontents = CG_PointContents; + cg_vehPmove.tracemask = MASK_PLAYERSOLID; + cg_vehPmove.debugLevel = 0; + cg_vehPmove.g2Bolts_LFoot = -1; + cg_vehPmove.g2Bolts_RFoot = -1; + + cg_vehPmove.baseEnt = (bgEntity_t *)cg_entities; + cg_vehPmove.entSize = sizeof(centity_t); + + cg_vehPmoveSet = qtrue; + } + + cg_vehPmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; + cg_vehPmove.pmove_fixed = pmove_fixed.integer; + cg_vehPmove.pmove_msec = pmove_msec.integer; + + cg_entities[cg->predictedPlayerState.clientNum].playerState = &cg->predictedPlayerState; + veh->playerState = &cg->predictedVehicleState; + + //update boarding value sent from server. boarding is not predicted, but no big deal + veh->m_pVehicle->m_iBoarding = cg->predictedVehicleState.vehBoarding; + + Pmove(&cg_vehPmove); + /* + if ( !cg_paused.integer ) + { + Com_Printf( "%d - PITCH change %4.2f\n", cg->time, AngleSubtract(veh->m_pVehicle->m_vOrientation[0],veh->m_pVehicle->m_vPrevOrientation[0]) ); + } + */ + if ( cg_showVehBounds.integer ) + { + vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0}; + vec3_t absmin, absmax; + VectorAdd( cg_vehPmove.ps->origin, cg_vehPmove.mins, absmin ); + VectorAdd( cg_vehPmove.ps->origin, cg_vehPmove.maxs, absmax ); + CG_Cube( absmin, absmax, NPCDEBUG_RED, 0.25 ); + } + } + } + + moved = qtrue; + + // add push trigger movement effects + CG_TouchTriggerPrediction(); + + // check for predictable events that changed from previous predictions + //CG_CheckChangedPredictableEvents(&cg->predictedPlayerState); + } + + if ( cg_showmiss.integer > 1 ) { + CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg->time ); + } + + if ( !moved ) { + if ( cg_showmiss.integer ) { + CG_Printf( "not moved\n" ); + } + goto revertES; + } + + if (CG_Piloting(cg->predictedPlayerState.m_iVehicleNum)) + { + CG_AdjustPositionForMover( cg->predictedVehicleState.origin, + cg->predictedVehicleState.groundEntityNum, + cg->physicsTime, cg->time, cg->predictedVehicleState.origin ); + } + else + { + // adjust for the movement of the groundentity + CG_AdjustPositionForMover( cg->predictedPlayerState.origin, + cg->predictedPlayerState.groundEntityNum, + cg->physicsTime, cg->time, cg->predictedPlayerState.origin ); + } + + if ( cg_showmiss.integer ) { + if (cg->predictedPlayerState.eventSequence > oldPlayerState.eventSequence + MAX_PS_EVENTS) { +#ifndef FINAL_BUILD + CG_Printf("WARNING: dropped event\n"); +#endif + } + } + + // fire events and other transition triggered things + CG_TransitionPlayerState( &cg->predictedPlayerState, &oldPlayerState ); + + if ( cg_showmiss.integer ) { + if (cg->eventSequence > cg->predictedPlayerState.eventSequence) { +#ifndef FINAL_BUILD + CG_Printf("WARNING: double event\n"); +#endif + cg->eventSequence = cg->predictedPlayerState.eventSequence; + } + } + + if (cg->predictedPlayerState.m_iVehicleNum && + !CG_Piloting(cg->predictedPlayerState.m_iVehicleNum)) + { //a passenger on this vehicle, bolt them in + centity_t *veh = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + VectorCopy(veh->lerpAngles, cg->predictedPlayerState.viewangles); + VectorCopy(veh->lerpOrigin, cg->predictedPlayerState.origin); + } + +revertES: + if (CG_Piloting(cg->predictedPlayerState.m_iVehicleNum)) + { + centity_t *veh = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + + if (veh->m_pVehicle) + { + //switch ptr back for this ent in case we stop riding it + veh->m_pVehicle->m_vOrientation = &cgSendPS[veh->currentState.number]->vehOrientation[0]; + } + + cg_entities[cg->predictedPlayerState.clientNum].playerState = cgSendPS[cg->predictedPlayerState.clientNum]; + veh->playerState = cgSendPS[veh->currentState.number]; + } + + //copy some stuff back into the entstates to help actually "predict" them if applicable + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) + { + if (cg_entities[i].currentState.eType == ET_PLAYER || + cg_entities[i].currentState.eType == ET_NPC) + { + cg_entities[i].currentState.torsoAnim = cgSendPS[i]->torsoAnim; + cg_entities[i].currentState.legsAnim = cgSendPS[i]->legsAnim; + cg_entities[i].currentState.forceFrame = cgSendPS[i]->saberLockFrame; + cg_entities[i].currentState.saberMove = cgSendPS[i]->saberMove; + } + } +} +#pragma warning(default : 4701) //local variable may be used without having been initialized diff --git a/codemp/cgame/cg_public.h b/codemp/cgame/cg_public.h new file mode 100644 index 0000000..e812cf9 --- /dev/null +++ b/codemp/cgame/cg_public.h @@ -0,0 +1,596 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#ifndef __CG_PUBLIC_H +#define __CG_PUBLIC_H + +#define CMD_BACKUP 64 +#define CMD_MASK (CMD_BACKUP - 1) +// allow a lot of command backups for very fast systems +// multiple commands may be combined into a single packet, so this +// needs to be larger than PACKET_BACKUP + + +#define MAX_ENTITIES_IN_SNAPSHOT 256 + +// snapshots are a view of the server at a given time + +// Snapshots are generated at regular time intervals by the server, +// but they may not be sent if a client's rate level is exceeded, or +// they may be dropped by the network. +typedef struct { + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; + + int serverTime; // server time the message is valid for (in msec) + + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + playerState_t ps; // complete information about the current player at this time + playerState_t vps; //vehicle I'm riding's playerstate (if applicable) -rww + + int numEntities; // all of the entities that need to be presented + entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot + + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current +} snapshot_t; + +enum { + CGAME_EVENT_NONE, + CGAME_EVENT_TEAMMENU, + CGAME_EVENT_SCOREBOARD, + CGAME_EVENT_EDITHUD +}; + + +/* +================================================================== + +functions imported from the main executable + +================================================================== +*/ + +#define CGAME_IMPORT_API_VERSION 5 + +typedef enum { + CG_PRINT = 0, + CG_ERROR, + CG_MILLISECONDS, + + //Also for profiling.. do not use for game related tasks. + CG_PRECISIONTIMER_START, + CG_PRECISIONTIMER_END, + + CG_PRINTALWAYS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_CVAR_GETHIDDENVALUE, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_FS_GETFILELIST, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_REMOVECOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_TEMPCAPSULEMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_CAPSULETRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_TRANSFORMEDCAPSULETRACE, + CG_CM_MARKFRAGMENTS, + CG_S_GETVOICEVOLUME, + CG_S_MUTESOUND, + CG_S_STARTSOUND, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_ADDREALLOOPINGSOUND, + CG_S_STOPLOOPINGSOUND, + CG_S_RESPATIALIZE, + CG_S_SHUTUP, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + + //rww - AS trap implem + CG_S_UPDATEAMBIENTSET, + CG_AS_PARSESETS, + CG_AS_ADDPRECACHEENTRY, + CG_S_ADDLOCALSET, + CG_AS_GETBMODELSOUND, + + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_REGISTERSHADERNOMIP, + CG_R_REGISTERFONT, + CG_R_FONT_STRLENPIXELS, + CG_R_FONT_STRLENCHARS, + CG_R_FONT_STRHEIGHTPIXELS, + CG_R_FONT_DRAWSTRING, + CG_LANGUAGE_ISASIAN, + CG_LANGUAGE_USESSPACES, + CG_ANYLANGUAGE_READCHARFROMSTRING, + + CGAME_MEMSET = 100, + CGAME_MEMCPY, + CGAME_STRNCPY, + CGAME_SIN, + CGAME_COS, + CGAME_ATAN2, + CGAME_SQRT, + CGAME_MATRIXMULTIPLY, + CGAME_ANGLEVECTORS, + CGAME_PERPENDICULARVECTOR, + CGAME_FLOOR, + CGAME_CEIL, + + CGAME_TESTPRINTINT, + CGAME_TESTPRINTFLOAT, + + CGAME_ACOS, + CGAME_ASIN, + + CG_R_CLEARSCENE = 200, + CG_R_CLEARDECALS, + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDPOLYSTOSCENE, + CG_R_ADDDECALTOSCENE, + CG_R_LIGHTFORPOINT, + CG_R_ADDLIGHTTOSCENE, + CG_R_ADDADDITIVELIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_SETCOLOR, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_R_DRAWROTATEPIC, + CG_R_DRAWROTATEPIC2, + CG_R_SETRANGEFOG, //linear fogging, with settable range -rww + CG_R_SETREFRACTIONPROP, //set some properties for the draw layer for my refractive effect (here primarily for mod authors) -rww + CG_R_REMAP_SHADER, + CG_R_GET_LIGHT_STYLE, + CG_R_SET_LIGHT_STYLE, + CG_R_GET_BMODEL_VERTS, + CG_R_GETDISTANCECULL, + + CG_R_GETREALRES, + CG_R_AUTOMAPELEVADJ, + CG_R_INITWIREFRAMEAUTO, + + CG_FX_ADDLINE, + + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETDEFAULTSTATE, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_SETCLIENTFORCEANGLE, + CG_SETCLIENTTURNEXTENT, + CG_OPENUIMENU, + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_MEMORY_REMAINING, + CG_KEY_ISDOWN, + CG_KEY_GETCATCHER, + CG_KEY_SETCATCHER, + CG_KEY_GETKEY, + + CG_PC_ADD_GLOBAL_DEFINE, + CG_PC_LOAD_SOURCE, + CG_PC_FREE_SOURCE, + CG_PC_READ_TOKEN, + CG_PC_SOURCE_FILE_AND_LINE, + CG_PC_LOAD_GLOBAL_DEFINES, + CG_PC_REMOVE_ALL_GLOBAL_DEFINES, + + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, + CG_SNAPVECTOR, + CG_CIN_PLAYCINEMATIC, + CG_CIN_STOPCINEMATIC, + CG_CIN_RUNCINEMATIC, + CG_CIN_DRAWCINEMATIC, + CG_CIN_SETEXTENTS, + + CG_GET_ENTITY_TOKEN, + CG_R_INPVS, + + CG_FX_REGISTER_EFFECT, + CG_FX_PLAY_EFFECT, + CG_FX_PLAY_ENTITY_EFFECT, + CG_FX_PLAY_EFFECT_ID, + CG_FX_PLAY_PORTAL_EFFECT_ID, + CG_FX_PLAY_ENTITY_EFFECT_ID, + CG_FX_PLAY_BOLTED_EFFECT_ID, + CG_FX_ADD_SCHEDULED_EFFECTS, + CG_FX_INIT_SYSTEM, + CG_FX_SET_REFDEF, + CG_FX_FREE_SYSTEM, + CG_FX_ADJUST_TIME, + CG_FX_DRAW_2D_EFFECTS, + CG_FX_RESET, + CG_FX_ADDPOLY, + CG_FX_ADDBEZIER, + CG_FX_ADDPRIMITIVE, + CG_FX_ADDSPRITE, + CG_FX_ADDELECTRICITY, + +// CG_SP_PRINT, + CG_SP_GETSTRINGTEXTSTRING, + + CG_ROFF_CLEAN, + CG_ROFF_UPDATE_ENTITIES, + CG_ROFF_CACHE, + CG_ROFF_PLAY, + CG_ROFF_PURGE_ENT, + + + //rww - dynamic vm memory allocation! + CG_TRUEMALLOC, + CG_TRUEFREE, + +/* +Ghoul2 Insert Start +*/ + CG_G2_GETMODELNAME, + CG_G2_LISTSURFACES, + CG_G2_LISTBONES, + CG_G2_SETMODELS, + CG_G2_HAVEWEGHOULMODELS, + CG_G2_GETBOLT, + CG_G2_GETBOLT_NOREC, + CG_G2_GETBOLT_NOREC_NOROT, + CG_G2_INITGHOUL2MODEL, + CG_G2_SETSKIN, + CG_G2_COLLISIONDETECT, + CG_G2_CLEANMODELS, + CG_G2_ANGLEOVERRIDE, + CG_G2_PLAYANIM, + CG_G2_GETBONEANIM, + CG_G2_GETBONEFRAME, //trimmed down version of GBA, so I don't have to pass all those unused args across the VM-exe border + CG_G2_GETGLANAME, + CG_G2_COPYGHOUL2INSTANCE, + CG_G2_COPYSPECIFICGHOUL2MODEL, + CG_G2_DUPLICATEGHOUL2INSTANCE, + CG_G2_HASGHOUL2MODELONINDEX, + CG_G2_REMOVEGHOUL2MODEL, + CG_G2_SKINLESSMODEL, + CG_G2_GETNUMGOREMARKS, + CG_G2_ADDSKINGORE, + CG_G2_CLEARSKINGORE, + CG_G2_SIZE, + CG_G2_ADDBOLT, + CG_G2_ATTACHENT, + CG_G2_SETBOLTON, + CG_G2_SETROOTSURFACE, + CG_G2_SETSURFACEONOFF, + CG_G2_SETNEWORIGIN, + CG_G2_DOESBONEEXIST, + CG_G2_GETSURFACERENDERSTATUS, + + CG_G2_GETTIME, + CG_G2_SETTIME, + + CG_G2_ABSURDSMOOTHING, + +/* + //rww - RAGDOLL_BEGIN +*/ + CG_G2_SETRAGDOLL, + CG_G2_ANIMATEG2MODELS, +/* + //rww - RAGDOLL_END +*/ + + //additional ragdoll options -rww + CG_G2_RAGPCJCONSTRAINT, + CG_G2_RAGPCJGRADIENTSPEED, + CG_G2_RAGEFFECTORGOAL, + CG_G2_GETRAGBONEPOS, + CG_G2_RAGEFFECTORKICK, + CG_G2_RAGFORCESOLVE, + + //rww - ik move method, allows you to specify a bone and move it to a world point (within joint constraints) + //by using the majority of gil's existing bone angling stuff from the ragdoll code. + CG_G2_SETBONEIKSTATE, + CG_G2_IKMOVE, + + CG_G2_REMOVEBONE, + + CG_G2_ATTACHINSTANCETOENTNUM, + CG_G2_CLEARATTACHEDINSTANCE, + CG_G2_CLEANENTATTACHMENTS, + CG_G2_OVERRIDESERVER, + + CG_G2_GETSURFACENAME, + + CG_SET_SHARED_BUFFER, + + CG_CM_REGISTER_TERRAIN, + CG_RMG_INIT, + CG_RE_INIT_RENDERER_TERRAIN, + CG_R_WEATHER_CONTENTS_OVERRIDE, + CG_R_WORLDEFFECTCOMMAND, + //Adding trap to get weather working + CG_WE_ADDWEATHERZONE + +/* +Ghoul2 Insert End +*/ +} cgameImport_t; + + +/* +================================================================== + +functions exported to the main executable + +================================================================== +*/ + +typedef enum { + CG_INIT, +// void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, tourney restarts, or vid_restarts + + CG_SHUTDOWN, +// void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, +// qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, +// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, +// int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER, +// int (*CG_LastAttacker)( void ); + + CG_KEY_EVENT, +// void (*CG_KeyEvent)( int key, qboolean down ); + + CG_MOUSE_EVENT, +// void (*CG_MouseEvent)( int dx, int dy ); + CG_EVENT_HANDLING, +// void (*CG_EventHandling)(int type); + + CG_POINT_CONTENTS, +// int CG_PointContents( const vec3_t point, int passEntityNum ); + + CG_GET_LERP_ORIGIN, +// void CG_LerpOrigin(int num, vec3_t result); + + CG_GET_LERP_DATA, + CG_GET_GHOUL2, + CG_GET_MODEL_LIST, + + CG_CALC_LERP_POSITIONS, +// void CG_CalcEntityLerpPositions(int num); + + CG_TRACE, + CG_G2TRACE, +//void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, +// int skipNumber, int mask ); + + CG_G2MARK, + + CG_RAG_CALLBACK, + + CG_INCOMING_CONSOLE_COMMAND, + + CG_GET_USEABLE_FORCE, + + CG_GET_ORIGIN, // int entnum, vec3_t origin + CG_GET_ANGLES, // int entnum, vec3_t angle + + CG_GET_ORIGIN_TRAJECTORY, // int entnum + CG_GET_ANGLE_TRAJECTORY, // int entnum + + CG_ROFF_NOTETRACK_CALLBACK, // int entnum, char *notetrack + + CG_IMPACT_MARK, +//void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, +// float orientation, float red, float green, float blue, float alpha, +// qboolean alphaFade, float radius, qboolean temporary ) + + CG_MAP_CHANGE, + + CG_AUTOMAP_INPUT, + + CG_MISC_ENT, //rwwRMG - added + + CG_GET_SORTED_FORCE_POWER, +} cgameExport_t; + +typedef struct +{ + float up; + float down; + float yaw; + float pitch; + qboolean goToDefaults; +} autoMapInput_t; + +// CG_POINT_CONTENTS +typedef struct +{ + vec3_t mPoint; // input + int mPassEntityNum; // input +} TCGPointContents; + +// CG_GET_BOLT_POS +typedef struct +{ + vec3_t mOrigin; // output + vec3_t mAngles; // output + vec3_t mScale; // output + int mEntityNum; // input +} TCGGetBoltData; + +// CG_IMPACT_MARK +typedef struct +{ + int mHandle; + vec3_t mPoint; + vec3_t mAngle; + float mRotation; + float mRed; + float mGreen; + float mBlue; + float mAlphaStart; + float mSizeStart; +} TCGImpactMark; + +// CG_GET_LERP_ORIGIN +// CG_GET_LERP_ANGLES +// CG_GET_MODEL_SCALE +typedef struct +{ + int mEntityNum; // input + vec3_t mPoint; // output +} TCGVectorData; + +// CG_TRACE/CG_G2TRACE +typedef struct +{ + trace_t mResult; // output + vec3_t mStart, mMins, mMaxs, mEnd; // input + int mSkipNumber, mMask; // input +} TCGTrace; + +// CG_G2MARK +typedef struct +{ + int shader; + float size; + vec3_t start, dir; +} TCGG2Mark; + +// CG_INCOMING_CONSOLE_COMMAND +typedef struct +{ + char conCommand[1024]; +} TCGIncomingConsoleCommand; + +// CG_FX_CAMERASHAKE +typedef struct +{ + vec3_t mOrigin; // input + float mIntensity; // input + int mRadius; // input + int mTime; // input +} TCGCameraShake; + +// CG_MISC_ENT +typedef struct +{ + char mModel[MAX_QPATH]; // input + vec3_t mOrigin, mAngles, mScale; // input +} TCGMiscEnt; + +typedef struct +{ + refEntity_t ent; // output + void *ghoul2; // input + int modelIndex; // input + int boltIndex; // input + vec3_t origin; // input + vec3_t angles; // input + vec3_t modelScale; // input +} TCGPositionOnBolt; + +//ragdoll callback structs -rww +#define RAG_CALLBACK_NONE 0 +#define RAG_CALLBACK_DEBUGBOX 1 +typedef struct +{ + vec3_t mins; + vec3_t maxs; + int duration; +} ragCallbackDebugBox_t; + +#define RAG_CALLBACK_DEBUGLINE 2 +typedef struct +{ + vec3_t start; + vec3_t end; + int time; + int color; + int radius; +} ragCallbackDebugLine_t; + +#define RAG_CALLBACK_BONESNAP 3 +typedef struct +{ + char boneName[128]; //name of the bone in question + int entNum; //index of entity who owns the bone in question +} ragCallbackBoneSnap_t; + +#define RAG_CALLBACK_BONEIMPACT 4 +typedef struct +{ + char boneName[128]; //name of the bone in question + int entNum; //index of entity who owns the bone in question +} ragCallbackBoneImpact_t; + +#define RAG_CALLBACK_BONEINSOLID 5 +typedef struct +{ + vec3_t bonePos; //world coordinate position of the bone + int entNum; //index of entity who owns the bone in question + int solidCount; //higher the count, the longer we've been in solid (the worse off we are) +} ragCallbackBoneInSolid_t; + +#define RAG_CALLBACK_TRACELINE 6 +typedef struct +{ + trace_t tr; + vec3_t start; + vec3_t end; + vec3_t mins; + vec3_t maxs; + int ignore; + int mask; +} ragCallbackTraceLine_t; + +#define MAX_CG_SHARED_BUFFER_SIZE 2048 + +//---------------------------------------------- + +#endif // __CG_PUBLIC_H diff --git a/codemp/cgame/cg_saga.c b/codemp/cgame/cg_saga.c new file mode 100644 index 0000000..0aac16f --- /dev/null +++ b/codemp/cgame/cg_saga.c @@ -0,0 +1,1106 @@ +// Copyright (C) 2000-2002 Raven Software, Inc. +// +/***************************************************************************** + * name: cg_siege.c + * + * desc: Clientgame-side module for Siege gametype. + * + * $Author: mccloskey $ + * $Revision: 1.19 $ + * + *****************************************************************************/ +#include "cg_local.h" +#include "bg_saga.h" + +int cgSiegeRoundState = 0; +int cgSiegeRoundTime = 0; + +static char team1[512]; +static char team2[512]; + +int team1Timed = 0; +int team2Timed = 0; + +int cgSiegeTeam1PlShader = 0; +int cgSiegeTeam2PlShader = 0; + +static char cgParseObjectives[MAX_SIEGE_INFO_SIZE]; + +extern void CG_LoadCISounds(clientInfo_t *ci, qboolean modelloaded); //cg_players.c + +void CG_DrawSiegeMessage( const char *str, int objectiveScreen ); +void CG_DrawSiegeMessageNonMenu( const char *str ); +void CG_SiegeBriefingDisplay(int team, int dontshow); + +void CG_PrecacheSiegeObjectiveAssetsForTeam(int myTeam) +{ + char teamstr[64]; + char objstr[256]; + char foundobjective[MAX_SIEGE_INFO_SIZE]; + + if (!siege_valid) + { + CG_Error("Siege data does not exist on client!\n"); + return; + } + + if (myTeam == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { + int i = 1; + while (i < 32) + { //eh, just try 32 I guess + Com_sprintf(objstr, sizeof(objstr), "Objective%i", i); + + if (BG_SiegeGetValueGroup(cgParseObjectives, objstr, foundobjective)) + { + char str[MAX_QPATH]; + + if (BG_SiegeGetPairedValue(foundobjective, "sound_team1", str)) + { + trap_S_RegisterSound(str); + } + if (BG_SiegeGetPairedValue(foundobjective, "sound_team2", str)) + { + trap_S_RegisterSound(str); + } + if (BG_SiegeGetPairedValue(foundobjective, "objgfx", str)) + { + trap_R_RegisterShaderNoMip(str); + } + if (BG_SiegeGetPairedValue(foundobjective, "mapicon", str)) + { + trap_R_RegisterShaderNoMip(str); + } + if (BG_SiegeGetPairedValue(foundobjective, "litmapicon", str)) + { + trap_R_RegisterShaderNoMip(str); + } + if (BG_SiegeGetPairedValue(foundobjective, "donemapicon", str)) + { + trap_R_RegisterShaderNoMip(str); + } + } + else + { //no more + break; + } + i++; + } + } +} + +void CG_PrecachePlayersForSiegeTeam(int team) +{ +#ifdef _XBOX + // Dont want to precache players on Xbox + return; +#endif + + siegeTeam_t *stm; + int i = 0; + + stm = BG_SiegeFindThemeForTeam(team); + + if (!stm) + { //invalid team/no theme for team? + return; + } + + while (i < stm->numClasses) + { + siegeClass_t *scl = stm->classes[i]; + + if (scl->forcedModel[0]) + { + clientInfo_t fake; + + memset(&fake, 0, sizeof(fake)); + strcpy(fake.modelName, scl->forcedModel); + + trap_R_RegisterModel(va("models/players/%s/model.glm", scl->forcedModel)); + if (scl->forcedSkin[0]) + { + trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", scl->forcedModel, scl->forcedSkin)); + strcpy(fake.skinName, scl->forcedSkin); + } + else + { + strcpy(fake.skinName, "default"); + } + + //precache the sounds for the model... + CG_LoadCISounds(&fake, qtrue); + } + + i++; + } +} + +void CG_InitSiegeMode(void) +{ + char levelname[MAX_QPATH]; + char btime[1024]; + char teams[2048]; + char teamInfo[MAX_SIEGE_INFO_SIZE]; + int len = 0; + int i = 0; + int j = 0; + siegeClass_t *cl; + siegeTeam_t *sTeam; + fileHandle_t f; + char teamIcon[128]; + + if (cgs.gametype != GT_SIEGE) + { + goto failure; + } + + Com_sprintf(levelname, sizeof(levelname), "%s\0", cgs.mapname); + + i = strlen(levelname)-1; + + while (i > 0 && levelname[i] && levelname[i] != '.') + { + i--; + } + + if (!i) + { + goto failure; + } + + levelname[i] = '\0'; //kill the ".bsp" + + Com_sprintf(levelname, sizeof(levelname), "%s.siege\0", levelname); + + if (!levelname || !levelname[0]) + { + goto failure; + } + + len = trap_FS_FOpenFile(levelname, &f, FS_READ); + + if (!f || len >= MAX_SIEGE_INFO_SIZE) + { + goto failure; + } + + trap_FS_Read(siege_info, len, f); + + trap_FS_FCloseFile(f); + + siege_valid = 1; + + if (BG_SiegeGetValueGroup(siege_info, "Teams", teams)) + { + char buf[1024]; + + trap_Cvar_VariableStringBuffer("cg_siegeTeam1", buf, 1024); + if (buf[0] && Q_stricmp(buf, "none")) + { + strcpy(team1, buf); + } + else + { + BG_SiegeGetPairedValue(teams, "team1", team1); + } + + if (team1[0] == '@') + { //it's a damn stringed reference. + char b[256]; + trap_SP_GetStringTextString(team1+1, b, 256); + trap_Cvar_Set("cg_siegeTeam1Name", b); + } + else + { + trap_Cvar_Set("cg_siegeTeam1Name", team1); + } + + trap_Cvar_VariableStringBuffer("cg_siegeTeam2", buf, 1024); + if (buf[0] && Q_stricmp(buf, "none")) + { + strcpy(team2, buf); + } + else + { + BG_SiegeGetPairedValue(teams, "team2", team2); + } + + if (team2[0] == '@') + { //it's a damn stringed reference. + char b[256]; + trap_SP_GetStringTextString(team2+1, b, 256); + trap_Cvar_Set("cg_siegeTeam2Name", b); + } + else + { + trap_Cvar_Set("cg_siegeTeam2Name", team2); + } + } + else + { + CG_Error("Siege teams not defined"); + } + + if (BG_SiegeGetValueGroup(siege_info, team1, teamInfo)) + { + if (BG_SiegeGetPairedValue(teamInfo, "TeamIcon", teamIcon)) + { + trap_Cvar_Set( "team1_icon", teamIcon); + } + + if (BG_SiegeGetPairedValue(teamInfo, "Timed", btime)) + { + team1Timed = atoi(btime)*1000; + CG_SetSiegeTimerCvar ( team1Timed ); + } + else + { + team1Timed = 0; + } + } + else + { + CG_Error("No team entry for '%s'\n", team1); + } + + if (BG_SiegeGetPairedValue(siege_info, "mapgraphic", teamInfo)) + { + trap_Cvar_Set("siege_mapgraphic", teamInfo); + } + else + { + trap_Cvar_Set("siege_mapgraphic", "gfx/mplevels/siege1_hoth"); + } + + if (BG_SiegeGetPairedValue(siege_info, "missionname", teamInfo)) + { + trap_Cvar_Set("siege_missionname", teamInfo); + } + else + { + trap_Cvar_Set("siege_missionname", " "); + } + + if (BG_SiegeGetValueGroup(siege_info, team2, teamInfo)) + { + if (BG_SiegeGetPairedValue(teamInfo, "TeamIcon", teamIcon)) + { + trap_Cvar_Set( "team2_icon", teamIcon); + } + + if (BG_SiegeGetPairedValue(teamInfo, "Timed", btime)) + { + team2Timed = atoi(btime)*1000; + CG_SetSiegeTimerCvar ( team2Timed ); + } + else + { + team2Timed = 0; + } + } + else + { + CG_Error("No team entry for '%s'\n", team2); + } + + //Load the player class types + BG_SiegeLoadClasses(NULL); + + if (!bgNumSiegeClasses) + { //We didn't find any?! + CG_Error("Couldn't find any player classes for Siege"); + } + + //Now load the teams since we have class data. + BG_SiegeLoadTeams(); + + if (!bgNumSiegeTeams) + { //React same as with classes. + CG_Error("Couldn't find any player teams for Siege"); + } + + //Get and set the team themes for each team. This will control which classes can be + //used on each team. + if (BG_SiegeGetValueGroup(siege_info, team1, teamInfo)) + { + if (BG_SiegeGetPairedValue(teamInfo, "UseTeam", btime)) + { + BG_SiegeSetTeamTheme(SIEGETEAM_TEAM1, btime); + } + if (BG_SiegeGetPairedValue(teamInfo, "FriendlyShader", btime)) + { + cgSiegeTeam1PlShader = trap_R_RegisterShaderNoMip(btime); + } + else + { + cgSiegeTeam1PlShader = 0; + } + } + if (BG_SiegeGetValueGroup(siege_info, team2, teamInfo)) + { + if (BG_SiegeGetPairedValue(teamInfo, "UseTeam", btime)) + { + BG_SiegeSetTeamTheme(SIEGETEAM_TEAM2, btime); + } + if (BG_SiegeGetPairedValue(teamInfo, "FriendlyShader", btime)) + { + cgSiegeTeam2PlShader = trap_R_RegisterShaderNoMip(btime); + } + else + { + cgSiegeTeam2PlShader = 0; + } + } + + //Now go through the classes used by the loaded teams and try to precache + //any forced models or forced skins. + i = SIEGETEAM_TEAM1; + + while (i <= SIEGETEAM_TEAM2) + { + j = 0; + sTeam = BG_SiegeFindThemeForTeam(i); + + if (!sTeam) + { + i++; + continue; + } + + //Get custom team shaders while we're at it. + if (i == SIEGETEAM_TEAM1) + { + cgSiegeTeam1PlShader = sTeam->friendlyShader; + } + else if (i == SIEGETEAM_TEAM2) + { + cgSiegeTeam2PlShader = sTeam->friendlyShader; + } + + while (j < sTeam->numClasses) + { + cl = sTeam->classes[j]; + + if (cl->forcedModel[0]) + { //This class has a forced model, so precache it. +#ifndef _XBOX + // Don't precache on Xbox + trap_R_RegisterModel(va("models/players/%s/model.glm", cl->forcedModel)); +#endif + + if (cl->forcedSkin[0]) + { //also has a forced skin, precache it. + char *useSkinName; + + if (strchr(cl->forcedSkin, '|')) + {//three part skin + useSkinName = va("models/players/%s/|%s", cl->forcedModel, cl->forcedSkin); + } + else + { + useSkinName = va("models/players/%s/model_%s.skin", cl->forcedModel, cl->forcedSkin); + } + + trap_R_RegisterSkin(useSkinName); + } + } + + j++; + } + i++; + } + + //precache saber data for classes that use sabers on both teams + BG_PrecacheSabersForSiegeTeam(SIEGETEAM_TEAM1); + BG_PrecacheSabersForSiegeTeam(SIEGETEAM_TEAM2); + + CG_PrecachePlayersForSiegeTeam(SIEGETEAM_TEAM1); + CG_PrecachePlayersForSiegeTeam(SIEGETEAM_TEAM2); + + CG_PrecachePlayersForSiegeTeam(SIEGETEAM_TEAM1); + CG_PrecachePlayersForSiegeTeam(SIEGETEAM_TEAM2); + + CG_PrecacheSiegeObjectiveAssetsForTeam(SIEGETEAM_TEAM1); + CG_PrecacheSiegeObjectiveAssetsForTeam(SIEGETEAM_TEAM2); + + return; +failure: + siege_valid = 0; +} + +static char CGAME_INLINE *CG_SiegeObjectiveBuffer(int team, int objective) +{ + static char buf[8192]; + char teamstr[1024]; + + if (team == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { //found the team group + if (BG_SiegeGetValueGroup(cgParseObjectives, va("Objective%i", objective), buf)) + { //found the objective group + return buf; + } + } + + return NULL; +} + +void CG_ParseSiegeObjectiveStatus(const char *str) +{ + int i = 0; + int team = SIEGETEAM_TEAM1; + char *cvarName; + char *s; + int objectiveNum = 0; + + if (!str || !str[0]) + { + return; + } + + while (str[i]) + { + if (str[i] == '|') + { //switch over to team2, this is the next section + team = SIEGETEAM_TEAM2; + objectiveNum = 0; + } + else if (str[i] == '-') + { + objectiveNum++; + i++; + + cvarName = va("team%i_objective%i", team, objectiveNum); + if (str[i] == '1') + { //it's completed + trap_Cvar_Set(cvarName, "1"); + } + else + { //otherwise assume it is not + trap_Cvar_Set(cvarName, "0"); + } + + s = CG_SiegeObjectiveBuffer(team, objectiveNum); + if (s && s[0]) + { //now set the description and graphic cvars to by read by the menu + char buffer[8192]; + + cvarName = va("team%i_objective%i_longdesc", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "objdesc", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "UNSPECIFIED"); + } + + cvarName = va("team%i_objective%i_gfx", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "objgfx", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "UNSPECIFIED"); + } + + cvarName = va("team%i_objective%i_mapicon", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "mapicon", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "UNSPECIFIED"); + } + + cvarName = va("team%i_objective%i_litmapicon", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "litmapicon", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "UNSPECIFIED"); + } + + cvarName = va("team%i_objective%i_donemapicon", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "donemapicon", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "UNSPECIFIED"); + } + + cvarName = va("team%i_objective%i_mappos", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "mappos", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "0 0 32 32"); + } + } + } + i++; + } + + if (cg->predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR) + { //update menu cvars + CG_SiegeBriefingDisplay(cg->predictedPlayerState.persistant[PERS_TEAM], 1); + } +} + +void CG_SiegeRoundOver(centity_t *ent, int won) +{ + int myTeam; + char teamstr[64]; + char appstring[1024]; + char soundstr[1024]; + int success = 0; + playerState_t *ps = NULL; + + if (!siege_valid) + { + CG_Error("ERROR: Siege data does not exist on client!\n"); + return; + } + + if (cg->snap) + { //this should always be true, if it isn't though use the predicted ps as a fallback + ps = &cg->snap->ps; + } + else + { + ps = &cg->predictedPlayerState; + } + + if (!ps) + { + assert(0); + return; + } + + myTeam = ps->persistant[PERS_TEAM]; + + if (myTeam == TEAM_SPECTATOR) + { + return; + } + + if (myTeam == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { + if (won == myTeam) + { + success = BG_SiegeGetPairedValue(cgParseObjectives, "wonround", appstring); + } + else + { + success = BG_SiegeGetPairedValue(cgParseObjectives, "lostround", appstring); + } + + if (success) + { + CG_DrawSiegeMessage(appstring, 0); + } + + appstring[0] = 0; + soundstr[0] = 0; + + if (myTeam == won) + { + Com_sprintf(teamstr, sizeof(teamstr), "roundover_sound_wewon"); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), "roundover_sound_welost"); + } + + if (BG_SiegeGetPairedValue(cgParseObjectives, teamstr, appstring)) + { + Com_sprintf(soundstr, sizeof(soundstr), appstring); + } + /* + else + { + if (myTeam != won) + { + Com_sprintf(soundstr, sizeof(soundstr), DEFAULT_LOSE_ROUND); + } + else + { + Com_sprintf(soundstr, sizeof(soundstr), DEFAULT_WIN_ROUND); + } + } + */ + + if (soundstr[0]) + { + trap_S_StartLocalSound(trap_S_RegisterSound(soundstr), CHAN_ANNOUNCER); + } + } +} + +void CG_SiegeGetObjectiveDescription(int team, int objective, char *buffer) +{ + char teamstr[1024]; + char objectiveStr[8192]; + + buffer[0] = 0; //set to 0 ahead of time in case we fail to find the objective group/name + + if (team == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { //found the team group + if (BG_SiegeGetValueGroup(cgParseObjectives, va("Objective%i", objective), objectiveStr)) + { //found the objective group + //Parse the name right into the buffer. + BG_SiegeGetPairedValue(objectiveStr, "goalname", buffer); + } + } +} + +int CG_SiegeGetObjectiveFinal(int team, int objective ) +{ + char finalStr[64]; + char teamstr[1024]; + char objectiveStr[8192]; + + if (team == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { //found the team group + if (BG_SiegeGetValueGroup(cgParseObjectives, va("Objective%i", objective), objectiveStr)) + { //found the objective group + //Parse the name right into the buffer. + BG_SiegeGetPairedValue(objectiveStr, "final", finalStr); + return (atoi( finalStr )); + } + } + return 0; +} + +void CG_SiegeBriefingDisplay(int team, int dontshow) +{ + char teamstr[64]; + char briefing[8192]; + char properValue[1024]; + char objectiveDesc[1024]; + int i = 1; + int useTeam = team; + qboolean primary = qfalse; + + if (!siege_valid) + { + return; + } + + if (team == TEAM_SPECTATOR) + { + return; + } + + if (team == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (useTeam != SIEGETEAM_TEAM1 && useTeam != SIEGETEAM_TEAM2) + { //This shouldn't be happening. But just fall back to team 2 anyway. + useTeam = SIEGETEAM_TEAM2; + } + + trap_Cvar_Set(va("siege_primobj_inuse"), "0"); + +#ifdef _XBOX + while (i < 8) // Dear god this was un-necessary ... and making tons of cvars +#else + while (i < 16) +#endif + { //do up to 16 objectives I suppose + //Get the value for this objective on this team + //Now set the cvar for the menu to display. + + //primary = (CG_SiegeGetObjectiveFinal(useTeam, i)>-1)?qtrue:qfalse; + primary = (CG_SiegeGetObjectiveFinal(useTeam, i)>0)?qtrue:qfalse; + + properValue[0] = 0; + trap_Cvar_VariableStringBuffer(va("team%i_objective%i", useTeam, i), properValue, 1024); + if (primary) + { + trap_Cvar_Set(va("siege_primobj"), properValue); + } + else + { + trap_Cvar_Set(va("siege_objective%i", i), properValue); + } + + //Now set the long desc cvar for the menu to display. + properValue[0] = 0; + trap_Cvar_VariableStringBuffer(va("team%i_objective%i_longdesc", useTeam, i), properValue, 1024); + if (primary) + { + trap_Cvar_Set(va("siege_primobj_longdesc"), properValue); + } + else + { + trap_Cvar_Set(va("siege_objective%i_longdesc", i), properValue); + } + + //Now set the gfx cvar for the menu to display. + properValue[0] = 0; + trap_Cvar_VariableStringBuffer(va("team%i_objective%i_gfx", useTeam, i), properValue, 1024); + if (primary) + { + trap_Cvar_Set(va("siege_primobj_gfx"), properValue); + } + else + { + trap_Cvar_Set(va("siege_objective%i_gfx", i), properValue); + } + + //Now set the mapicon cvar for the menu to display. + properValue[0] = 0; + trap_Cvar_VariableStringBuffer(va("team%i_objective%i_mapicon", useTeam, i), properValue, 1024); + if (primary) + { + trap_Cvar_Set(va("siege_primobj_mapicon"), properValue); + } + else + { + trap_Cvar_Set(va("siege_objective%i_mapicon", i), properValue); + } + + //Now set the mappos cvar for the menu to display. + properValue[0] = 0; + trap_Cvar_VariableStringBuffer(va("team%i_objective%i_mappos", useTeam, i), properValue, 1024); + if (primary) + { + trap_Cvar_Set(va("siege_primobj_mappos"), properValue); + } + else + { + trap_Cvar_Set(va("siege_objective%i_mappos", i), properValue); + } + + //Now set the description cvar for the objective + CG_SiegeGetObjectiveDescription(useTeam, i, objectiveDesc); + + if (objectiveDesc[0]) + { //found a valid objective description + if ( primary ) + { + trap_Cvar_Set(va("siege_primobj_desc"), objectiveDesc); + //this one is marked not in use because it gets primobj + trap_Cvar_Set(va("siege_objective%i_inuse", i), "0"); + trap_Cvar_Set(va("siege_primobj_inuse"), "1"); + + trap_Cvar_Set(va("team%i_objective%i_inuse", useTeam, i), "1"); + + } + else + { + trap_Cvar_Set(va("siege_objective%i_desc", i), objectiveDesc); + trap_Cvar_Set(va("siege_objective%i_inuse", i), "2"); + trap_Cvar_Set(va("team%i_objective%i_inuse", useTeam, i), "2"); + + } + } + else + { //didn't find one, so set the "inuse" cvar to 0 for the objective and mark it non-complete. + trap_Cvar_Set(va("siege_objective%i_inuse", i), "0"); + trap_Cvar_Set(va("siege_objective%i", i), "0"); + trap_Cvar_Set(va("team%i_objective%i_inuse", useTeam, i), "0"); + trap_Cvar_Set(va("team%i_objective%i", useTeam, i), "0"); + + trap_Cvar_Set(va("siege_objective%i_mappos", i), ""); + trap_Cvar_Set(va("team%i_objective%i_mappos", useTeam, i), ""); + trap_Cvar_Set(va("siege_objective%i_gfx", i), ""); + trap_Cvar_Set(va("team%i_objective%i_gfx", useTeam, i), ""); + trap_Cvar_Set(va("siege_objective%i_mapicon", i), ""); + trap_Cvar_Set(va("team%i_objective%i_mapicon", useTeam, i), ""); + } + + i++; + } + + if (dontshow) + { + return; + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { + if (BG_SiegeGetPairedValue(cgParseObjectives, "briefing", briefing)) + { + CG_DrawSiegeMessage(briefing, 1); + } + } +} + +void CG_SiegeObjectiveCompleted(centity_t *ent, int won, int objectivenum) +{ + int myTeam; + char teamstr[64]; + char objstr[256]; + char foundobjective[MAX_SIEGE_INFO_SIZE]; + char appstring[1024]; + char soundstr[1024]; + int success = 0; + playerState_t *ps = NULL; + + if (!siege_valid) + { + CG_Error("Siege data does not exist on client!\n"); + return; + } + + if (cg->snap) + { //this should always be true, if it isn't though use the predicted ps as a fallback + ps = &cg->snap->ps; + } + else + { + ps = &cg->predictedPlayerState; + } + + if (!ps) + { + assert(0); + return; + } + + myTeam = ps->persistant[PERS_TEAM]; + + if (myTeam == TEAM_SPECTATOR) + { + return; + } + + if (won == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { + Com_sprintf(objstr, sizeof(objstr), "Objective%i", objectivenum); + + if (BG_SiegeGetValueGroup(cgParseObjectives, objstr, foundobjective)) + { + if (myTeam == SIEGETEAM_TEAM1) + { + success = BG_SiegeGetPairedValue(foundobjective, "message_team1", appstring); + } + else + { + success = BG_SiegeGetPairedValue(foundobjective, "message_team2", appstring); + } + + if (success) + { + CG_DrawSiegeMessageNonMenu(appstring); + } + + appstring[0] = 0; + soundstr[0] = 0; + + if (myTeam == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), "sound_team1"); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), "sound_team2"); + } + + if (BG_SiegeGetPairedValue(foundobjective, teamstr, appstring)) + { + Com_sprintf(soundstr, sizeof(soundstr), appstring); + } + /* + else + { + if (myTeam != won) + { + Com_sprintf(soundstr, sizeof(soundstr), DEFAULT_LOSE_OBJECTIVE); + } + else + { + Com_sprintf(soundstr, sizeof(soundstr), DEFAULT_WIN_OBJECTIVE); + } + } + */ + + if (soundstr[0]) + { + trap_S_StartLocalSound(trap_S_RegisterSound(soundstr), CHAN_ANNOUNCER); + } + } + } +} + +siegeExtended_t cg_siegeExtendedData[MAX_CLIENTS]; + +//parse a single extended siege data entry +void CG_ParseSiegeExtendedDataEntry(const char *conStr) +{ + char s[MAX_STRING_CHARS]; + char *str = (char *)conStr; + int argParses = 0; + int i; + int maxAmmo = 0, clNum = -1, health = 1, maxhealth = 1, ammo = 1; + centity_t *cent; + + if (!conStr || !conStr[0]) + { + return; + } + + while (*str && argParses < 4) + { + i = 0; + while (*str && *str != '|') + { + s[i] = *str; + i++; + *str++; + } + s[i] = 0; + switch (argParses) + { + case 0: + clNum = atoi(s); + break; + case 1: + health = atoi(s); + break; + case 2: + maxhealth = atoi(s); + break; + case 3: + ammo = atoi(s); + break; + default: + break; + } + argParses++; + str++; + } + + if (clNum < 0 || clNum >= MAX_CLIENTS) + { + return; + } + + cg_siegeExtendedData[clNum].health = health; + cg_siegeExtendedData[clNum].maxhealth = maxhealth; + cg_siegeExtendedData[clNum].ammo = ammo; + + cent = &cg_entities[clNum]; + + maxAmmo = ammoData[weaponData[cent->currentState.weapon].ammoIndex].max; + if ( (cent->currentState.eFlags & EF_DOUBLE_AMMO) ) + { + maxAmmo *= 2.0f; + } + if (ammo >= 0 && ammo <= maxAmmo ) + { //assure the weapon number is valid and not over max + //keep the weapon so if it changes before our next ext data update we'll know + //that the ammo is not applicable. + cg_siegeExtendedData[clNum].weapon = cent->currentState.weapon; + } + else + { //not valid? Oh well, just invalidate the weapon too then so we don't display ammo + cg_siegeExtendedData[clNum].weapon = -1; + } + + cg_siegeExtendedData[clNum].lastUpdated = cg->time; +} + +//parse incoming siege data, see counterpart in g_saga.c +void CG_ParseSiegeExtendedData(void) +{ + int numEntries = trap_Argc(); + int i = 0; + + if (numEntries < 1) + { + assert(!"Bad numEntries for sxd"); + return; + } + + while (i < numEntries) + { + CG_ParseSiegeExtendedDataEntry(CG_Argv(i+1)); + i++; + } +} + +void CG_SetSiegeTimerCvar ( int msec ) +{ + int seconds; + int mins; + int tens; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + trap_Cvar_Set("ui_siegeTimer", va( "%i:%i%i", mins, tens, seconds ) ); +} diff --git a/codemp/cgame/cg_scoreboard.c b/codemp/cgame/cg_scoreboard.c new file mode 100644 index 0000000..f519ef7 --- /dev/null +++ b/codemp/cgame/cg_scoreboard.c @@ -0,0 +1,854 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_scoreboard -- draw the scoreboard on top of the game screen +#include "cg_local.h" +#include "../ui/ui_shared.h" +#include "../game/bg_saga.h" + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + +#define SCOREBOARD_X (0) + +#define SB_HEADER 86 +#define SB_TOP (SB_HEADER+32) + +// Where the status bar starts, so we don't overwrite it +#define SB_STATUSBAR 420 + +#define SB_NORMAL_HEIGHT 25 +#define SB_INTER_HEIGHT 15 // interleaved height + +#define SB_MAXCLIENTS_NORMAL ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT) +#define SB_MAXCLIENTS_INTER ((SB_STATUSBAR - SB_TOP) / SB_INTER_HEIGHT - 1) + +// Used when interleaved + + + +#define SB_LEFT_BOTICON_X (SCOREBOARD_X+0) +#define SB_LEFT_HEAD_X (SCOREBOARD_X+32) +#define SB_RIGHT_BOTICON_X (SCOREBOARD_X+64) +#define SB_RIGHT_HEAD_X (SCOREBOARD_X+96) +// Normal +#define SB_BOTICON_X (SCOREBOARD_X+32) +#define SB_HEAD_X (SCOREBOARD_X+64) + +#define SB_SCORELINE_X 100 +#define SB_SCORELINE_WIDTH (640 - SB_SCORELINE_X * 2) + +#define SB_RATING_WIDTH 0 // (6 * BIGCHAR_WIDTH) +#define SB_NAME_X (SB_SCORELINE_X) +#define SB_SCORE_X (SB_SCORELINE_X + .55 * SB_SCORELINE_WIDTH) +//#define SB_PING_X (SB_SCORELINE_X + .70 * SB_SCORELINE_WIDTH) +#define SB_TIME_X (SB_SCORELINE_X + .85 * SB_SCORELINE_WIDTH) + +//JLF +#define TITLE_SAFE_SHIFT 30 + +// The new and improved score board +// +// In cases where the number of clients is high, the score board heads are interleaved +// here's the layout + +// +// 0 32 80 112 144 240 320 400 <-- pixel position +// bot head bot head score ping time name +// +// wins/losses are drawn on bot icon now + +static qboolean localClient; // true if local client has been displayed + + + /* +================= +CG_DrawScoreboard +================= +*/ +static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) +{ + //vec3_t headAngles; + clientInfo_t *ci; + int iconx, headx; + float scale; + + if ( largeFormat ) + { + scale = 1.0f; + } + else + { + scale = 0.75f; + } + + if ( score->client < 0 || score->client >= cgs.maxclients ) { + Com_Printf( "Bad score->client: %i\n", score->client ); + return; + } + + ci = &cgs.clientinfo[score->client]; + + iconx = SB_BOTICON_X + (SB_RATING_WIDTH / 2)+/*JLF*/TITLE_SAFE_SHIFT; + headx = SB_HEAD_X + (SB_RATING_WIDTH / 2); + + // draw the handicap or bot skill marker (unless player has flag) + if ( ci->powerups & ( 1 << PW_NEUTRALFLAG ) ) + { + if( largeFormat ) + { + CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse ); + } + else + { + CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse ); + } + } + else if ( ci->powerups & ( 1 << PW_REDFLAG ) ) + { + if( largeFormat ) + { + CG_DrawFlagModel( iconx*cgs.screenXScale, y*cgs.screenYScale, 32*cgs.screenXScale, 32*cgs.screenYScale, TEAM_RED, qfalse ); + } + else + { + CG_DrawFlagModel( iconx*cgs.screenXScale, y*cgs.screenYScale, 32*cgs.screenXScale, 32*cgs.screenYScale, TEAM_RED, qfalse ); + } + } + else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) + { + if( largeFormat ) + { + CG_DrawFlagModel( iconx*cgs.screenXScale, y*cgs.screenYScale, 32*cgs.screenXScale, 32*cgs.screenYScale, TEAM_BLUE, qfalse ); + } + else + { + CG_DrawFlagModel( iconx*cgs.screenXScale, y*cgs.screenYScale, 32*cgs.screenXScale, 32*cgs.screenYScale, TEAM_BLUE, qfalse ); + } + } + else if (cgs.gametype == GT_POWERDUEL && + (ci->duelTeam == DUELTEAM_LONE || ci->duelTeam == DUELTEAM_DOUBLE)) + { + if (ci->duelTeam == DUELTEAM_LONE) + { +#ifdef _XBOX + if(cg->widescreen) + CG_DrawPic ( iconx + 40, y, 32, 32, trap_R_RegisterShaderNoMip ( "gfx/mp/pduel_icon_lone" ) ); + else +#endif + CG_DrawPic ( iconx, y, 32, 32, trap_R_RegisterShaderNoMip ( "gfx/mp/pduel_icon_lone" ) ); + } + else + { +#ifdef _XBOX + if(cg->widescreen) + CG_DrawPic ( iconx + 40, y, 32, 32, trap_R_RegisterShaderNoMip ( "gfx/mp/pduel_icon_double" ) ); + else +#endif + CG_DrawPic ( iconx, y, 32, 32, trap_R_RegisterShaderNoMip ( "gfx/mp/pduel_icon_double" ) ); + } + } + else if (cgs.gametype == GT_SIEGE) + { //try to draw the shader for this class on the scoreboard + if (ci->siegeIndex != -1) + { + siegeClass_t *scl = &bgSiegeClasses[ci->siegeIndex]; + + if (scl->classShader) + { +#ifdef _XBOX + if(cg->widescreen) + CG_DrawPic (iconx + 40, y, largeFormat?24:12, largeFormat?24:12, scl->classShader); + else +#endif + CG_DrawPic (iconx, y, largeFormat?24:12, largeFormat?24:12, scl->classShader); + } + } + } + else + { + //rww - in duel, we now show wins/losses in place of "frags". This is because duel now defaults to 1 kill per round. + } + + // highlight your position + if ( score->client == cg->snap->ps.clientNum ) + { + float hcolor[4]; + int rank; + + localClient = qtrue; + + if ( cg->snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR + || cgs.gametype >= GT_TEAM ) { + rank = -1; + } else { + rank = cg->snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; + } + if ( rank == 0 ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7f; + } else if ( rank == 1 ) { + hcolor[0] = 0.7f; + hcolor[1] = 0; + hcolor[2] = 0; + } else if ( rank == 2 ) { + hcolor[0] = 0.7f; + hcolor[1] = 0.7f; + hcolor[2] = 0; + } else { + hcolor[0] = 0.7f; + hcolor[1] = 0.7f; + hcolor[2] = 0.7f; + } + + hcolor[3] = fade * 0.7; +#ifdef _XBOX + if(cg->widescreen) + CG_FillRect( SB_SCORELINE_X - 5 + 40, y + 2, 640 - SB_SCORELINE_X * 2 + 10, largeFormat?SB_NORMAL_HEIGHT:SB_INTER_HEIGHT, hcolor ); + else +#endif + CG_FillRect( SB_SCORELINE_X - 5, y + 2, 640 - SB_SCORELINE_X * 2 + 10, largeFormat?SB_NORMAL_HEIGHT:SB_INTER_HEIGHT, hcolor ); + } + +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint (SB_NAME_X + 40, y, 0.9f * scale, colorWhite, ci->name,0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else +#endif + CG_Text_Paint (SB_NAME_X, y, 0.9f * scale, colorWhite, ci->name,0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + + if ( score->ping != -1 ) + { + if ( ci->team != TEAM_SPECTATOR || cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL ) + { + if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) + { +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint (SB_SCORE_X + 40, y, 1.0f * scale, colorWhite, va("%i/%i", ci->wins, ci->losses),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + else +#endif + CG_Text_Paint (SB_SCORE_X, y, 1.0f * scale, colorWhite, va("%i/%i", ci->wins, ci->losses),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + } + else + { +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint (SB_SCORE_X + 40, y, 1.0f * scale, colorWhite, va("%i", score->score),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + else +#endif + CG_Text_Paint (SB_SCORE_X, y, 1.0f * scale, colorWhite, va("%i", score->score),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + } + } + +// CG_Text_Paint (SB_PING_X, y, 1.0f * scale, colorWhite, va("%i", score->ping),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint (SB_TIME_X + 40, y, 1.0f * scale, colorWhite, va("%i", score->time),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + else +#endif + CG_Text_Paint (SB_TIME_X, y, 1.0f * scale, colorWhite, va("%i", score->time),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + } + else + { +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint (SB_SCORE_X + 40, y, 1.0f * scale, colorWhite, "-",0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + else +#endif + CG_Text_Paint (SB_SCORE_X, y, 1.0f * scale, colorWhite, "-",0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); +// CG_Text_Paint (SB_PING_X, y, 1.0f * scale, colorWhite, "-",0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint (SB_TIME_X + 40, y, 1.0f * scale, colorWhite, "-",0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + else +#endif + CG_Text_Paint (SB_TIME_X, y, 1.0f * scale, colorWhite, "-",0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + } + + // add the "ready" marker for intermission exiting + if ( cg->snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) + { +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint (SB_NAME_X - 64 + 40, y + 2, 0.7f * scale, colorWhite, CG_GetStringEdString("MP_INGAME", "READY"),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else +#endif + CG_Text_Paint (SB_NAME_X - 64 + 15, y + 2, 0.7f * scale, colorWhite, CG_GetStringEdString("MP_INGAME", "READY"),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } +} + +/* +================= +CG_TeamScoreboard +================= +*/ +static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight, qboolean countOnly ) +{ + int i; + score_t *score; + float color[4]; + int count; + clientInfo_t *ci; + + color[0] = color[1] = color[2] = 1.0; + color[3] = fade; + + count = 0; + for ( i = 0 ; i < cg->numScores && count < maxClients ; i++ ) { + score = &cg->scores[i]; + ci = &cgs.clientinfo[ score->client ]; + + if ( team != ci->team ) { + continue; + } + + if ( !countOnly ) + { + CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT ); + } + + count++; + } + + return count; +} + +int CG_GetClassCount(team_t team,int siegeClass ) +{ + int i = 0; + int count = 0; + clientInfo_t *ci; + siegeClass_t *scl; + + for ( i = 0 ; i < cgs.maxclients ; i++ ) + { + ci = &cgs.clientinfo[ i ]; + + if ((!ci->infoValid) || ( team != ci->team )) + { + continue; + } + + scl = &bgSiegeClasses[ci->siegeIndex]; + + // Correct class? + if ( siegeClass != scl->classShader ) + { + continue; + } + + count++; + } + + return count; + +} + +int CG_GetTeamNonScoreCount(team_t team) +{ + int i = 0,count=0; + clientInfo_t *ci; + + for ( i = 0 ; i < cgs.maxclients ; i++ ) + { + ci = &cgs.clientinfo[ i ]; + + if ( (!ci->infoValid) || (team != ci->team && team != ci->siegeDesiredTeam) ) + { + continue; + } + + count++; + } + + return count; +} + +int CG_GetTeamCount(team_t team, int maxClients) +{ + int i = 0; + int count = 0; + clientInfo_t *ci; + score_t *score; + + for ( i = 0 ; i < cg->numScores && count < maxClients ; i++ ) + { + score = &cg->scores[i]; + ci = &cgs.clientinfo[ score->client ]; + + if ( team != ci->team ) + { + continue; + } + + count++; + } + + return count; + +} +/* +================= +CG_DrawScoreboard + +Draw the normal in-game scoreboard +================= +*/ +int cg_siegeWinTeam = 0; +qboolean CG_DrawOldScoreboard( void ) { + int x, y, w, i, n1, n2; + float fade; + float *fadeColor; + char *s; + int maxClients; + int lineHeight; + int topBorderSize, bottomBorderSize; + + // don't draw amuthing if the menu or console is up + if ( cg_paused.integer ) { + cg->deferredPlayerLoading = 0; + return qfalse; + } + + // don't draw scoreboard during death while warmup up + if ( cg->warmup && !cg->showScores ) { + return qfalse; + } + + if ( cg->showScores || cg->predictedPlayerState.pm_type == PM_DEAD || + cg->predictedPlayerState.pm_type == PM_INTERMISSION ) { + fade = 1.0; + fadeColor = colorWhite; + } else { + fadeColor = CG_FadeColor( cg->scoreFadeTime, FADE_TIME ); + + if ( !fadeColor ) { + // next time scoreboard comes up, don't print killer + cg->deferredPlayerLoading = 0; + cg->killerName[0] = 0; + return qfalse; + } + fade = *fadeColor; + } + + // fragged by ... line + // or if in intermission and duel, prints the winner of the duel round + if ((cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) && cgs.duelWinner != -1 && + cg->predictedPlayerState.pm_type == PM_INTERMISSION) + { + s = va("%s^7 %s", cgs.clientinfo[cgs.duelWinner].name, CG_GetStringEdString("MP_INGAME", "DUEL_WINS") ); + /*w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 40; + CG_DrawBigString( x, y, s, fade ); + */ + x = ( SCREEN_WIDTH ) / 2; + if(ClientManager::splitScreenMode == qtrue) { + y = 20; + + if(ClientManager::ActiveClientNum() == 1) + y = 240; + } + else + y = 40; + + if(cg->widescreen) + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2 + 40, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + else if ((cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) && cgs.duelist1 != -1 && cgs.duelist2 != -1 && + cg->predictedPlayerState.pm_type == PM_INTERMISSION) + { + if (cgs.gametype == GT_POWERDUEL && cgs.duelist3 != -1) + { + s = va("%s^7 %s %s^7 %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStringEdString("MP_INGAME", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name, CG_GetStringEdString("MP_INGAME", "AND"), cgs.clientinfo[cgs.duelist3].name ); + } + else + { + s = va("%s^7 %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStringEdString("MP_INGAME", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name ); + } + /*w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 40; + CG_DrawBigString( x, y, s, fade ); + */ + x = ( SCREEN_WIDTH ) / 2; + if(ClientManager::splitScreenMode == qtrue) { + y = 20; + + if(ClientManager::ActiveClientNum() == 1) + y = 240; + } + else + y = 40; + + if(cg->widescreen) + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2 + 40, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + else if ( cg->killerName[0] ) { + s = " "; //va("%s %s", CG_GetStringEdString("MP_INGAME", "KILLEDBY"), cg->killerName ); + /*w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 40; + CG_DrawBigString( x, y, s, fade ); + */ + x = ( SCREEN_WIDTH ) / 2; + if(ClientManager::splitScreenMode == qtrue) { + y = 20; + + if(ClientManager::ActiveClientNum() == 1) + y = 240; + + // If not displaying the scoreboard in splitscreen, move the text down + // so that it doesn't interfere with console messages + if(!cg->showScores) + y += 80; + } + else + y = 40; + + if(cg->widescreen) + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2 + 40, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + + // current rank + if (cgs.gametype == GT_POWERDUEL) + { //do nothing? + } + else if ( cgs.gametype < GT_TEAM) { +#ifdef _XBOX + // Don't want place ranking text in splitscreen Duel + if(ClientManager::NumClients() == 2 && cgs.gametype == GT_DUEL) { + } + else +#endif + if (cg->snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) + { + char sPlace[256]; + char sOf[256]; + char sWith[256]; + + trap_SP_GetStringTextString("MP_INGAME_PLACE", sPlace, sizeof(sPlace)); + trap_SP_GetStringTextString("MP_INGAME_OF", sOf, sizeof(sOf)); + trap_SP_GetStringTextString("MP_INGAME_WITH", sWith, sizeof(sWith)); + + s = va("%s %s (%s %i) %s %i", + CG_PlaceString( cg->snap->ps.persistant[PERS_RANK] + 1 ), + sPlace, + sOf, + cg->numScores, + sWith, + cg->snap->ps.persistant[PERS_SCORE] ); +// w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH ) / 2; + + if(ClientManager::splitScreenMode == qtrue) { + y = 40; + + if(ClientManager::ActiveClientNum() == 1) + y = 260; + + // If not displaying the scoreboard in splitscreen, move the text down + // so that it doesn't interfere with console messages + if(!cg->showScores) + y += 80; + } + else + y = 60; + + if(cg->widescreen) + UI_DrawProportionalString(x + 40, y, s, UI_CENTER|UI_DROPSHADOW, colorTable[CT_WHITE]); + else + UI_DrawProportionalString(x, y, s, UI_CENTER|UI_DROPSHADOW, colorTable[CT_WHITE]); + } + } + else if (cgs.gametype != GT_SIEGE) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && cg->showScores == qfalse) { + } + else { +#endif + if ( cg->teamScores[0] == cg->teamScores[1] ) { + s = va("%s %i", CG_GetStringEdString("MP_INGAME", "TIEDAT"), cg->teamScores[0] ); + } else if ( cg->teamScores[0] >= cg->teamScores[1] ) { + s = va("%s, %i / %i", CG_GetStringEdString("MP_INGAME", "RED_LEADS"), cg->teamScores[0], cg->teamScores[1] ); + } else { + s = va("%s, %i / %i", CG_GetStringEdString("MP_INGAME", "BLUE_LEADS"), cg->teamScores[1], cg->teamScores[0] ); + } + + x = ( SCREEN_WIDTH ) / 2; + if(ClientManager::splitScreenMode == qtrue) { + y = 40; + + if(ClientManager::ActiveClientNum() == 1) + y = 260; + } + else + y = 60; + + if(cg->widescreen) + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2 + 40, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); +#ifdef _XBOX + } +#endif + } + else if (cgs.gametype == GT_SIEGE && (cg_siegeWinTeam == 1 || cg_siegeWinTeam == 2)) + { + if (cg_siegeWinTeam == 1) + { + s = va("%s", CG_GetStringEdString("MP_INGAME", "SIEGETEAM1WIN") ); + } + else + { + s = va("%s", CG_GetStringEdString("MP_INGAME", "SIEGETEAM2WIN") ); + } + + x = ( SCREEN_WIDTH ) / 2; + if(ClientManager::splitScreenMode == qtrue) { + y = 40; + + if(ClientManager::ActiveClientNum() == 1) + y = 260; + } + else + y = 60; + + if(cg->widescreen) + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2 + 40, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + +#ifdef _XBOX + // In splitscreen mode, the scoreboard should only come up when requested (back button) + // or at the end of a round + if(ClientManager::splitScreenMode == qtrue && cg->showScores == qfalse && + cg->predictedPlayerState.pm_type != PM_INTERMISSION) + return qfalse; +#endif + + // scoreboard + if(ClientManager::splitScreenMode == qtrue) { + y = SB_HEADER - 20; + + if(ClientManager::ActiveClientNum() == 1) + y = SB_HEADER - 20 + 220; + } + else + y = SB_HEADER; + + if(cg->widescreen) + CG_DrawPic ( SB_SCORELINE_X - 40 + 40, y - 5, SB_SCORELINE_WIDTH + 80, 40, trap_R_RegisterShaderNoMip ( "gfx/menus/menu_buttonback.tga" ) ); + else + CG_DrawPic ( SB_SCORELINE_X - 40, y - 5, SB_SCORELINE_WIDTH + 80, 40, trap_R_RegisterShaderNoMip ( "gfx/menus/menu_buttonback.tga" ) ); + + if(cg->widescreen) + CG_Text_Paint ( SB_NAME_X + 40, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "NAME"),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else + CG_Text_Paint ( SB_NAME_X, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "NAME"),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) + { + char sWL[100]; + trap_SP_GetStringTextString("MP_INGAME_W_L", sWL, sizeof(sWL)); + + if(cg->widescreen) + CG_Text_Paint ( SB_SCORE_X + 40, y, 1.0f, colorWhite, sWL, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else + CG_Text_Paint ( SB_SCORE_X, y, 1.0f, colorWhite, sWL, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + else + { +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint ( SB_SCORE_X + 40, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "SCORE"), 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else +#endif + CG_Text_Paint ( SB_SCORE_X, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "SCORE"), 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } +// CG_Text_Paint ( SB_PING_X, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "PING"), 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint ( SB_TIME_X + 40, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "TIME"), 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else +#endif + CG_Text_Paint ( SB_TIME_X, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "TIME"), 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + + if(ClientManager::splitScreenMode == qtrue) { + y = SB_TOP - 20; + + if(ClientManager::ActiveClientNum() == 1) + y = SB_TOP - 20 + 220; + } + else + y = SB_TOP; + + // If there are more than SB_MAXCLIENTS_NORMAL, use the interleaved scores + if ( cg->numScores > SB_MAXCLIENTS_NORMAL || ClientManager::splitScreenMode == qtrue) { + maxClients = SB_MAXCLIENTS_INTER; + lineHeight = SB_INTER_HEIGHT; + topBorderSize = 8; + bottomBorderSize = 16; + } else { + maxClients = SB_MAXCLIENTS_NORMAL; + lineHeight = SB_NORMAL_HEIGHT; + topBorderSize = 8; + bottomBorderSize = 8; + } + + localClient = qfalse; + + + //I guess this should end up being able to display 19 clients at once. + //In a team game, if there are 9 or more clients on the team not in the lead, + //we only want to show 10 of the clients on the team in the lead, so that we + //have room to display the clients in the lead on the losing team. + + //I guess this can be accomplished simply by printing the first teams score with a maxClients + //value passed in related to how many players are on both teams. + if ( cgs.gametype >= GT_TEAM ) { + // + // teamplay scoreboard + // + y += lineHeight/2; + + if ( cg->teamScores[0] >= cg->teamScores[1] ) { + int team1MaxCl = CG_GetTeamCount(TEAM_RED, maxClients); + int team2MaxCl = CG_GetTeamCount(TEAM_BLUE, maxClients); + + if (team1MaxCl > 10 && (team1MaxCl+team2MaxCl) > maxClients) + { + team1MaxCl -= team2MaxCl; + //subtract as many as you have to down to 10, once we get there + //we just set it to 10 + + if (team1MaxCl < 10) + { + team1MaxCl = 10; + } + } + + team2MaxCl = (maxClients-team1MaxCl); //team2 can display however many is left over after team1's display + + n1 = CG_TeamScoreboard( y, TEAM_RED, fade, team1MaxCl, lineHeight, qtrue ); +#ifdef _XBOX + if(cg->widescreen) + CG_DrawTeamBackground( SB_SCORELINE_X - 5 + 40, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + else +#endif + CG_DrawTeamBackground( SB_SCORELINE_X - 5, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + CG_TeamScoreboard( y, TEAM_RED, fade, team1MaxCl, lineHeight, qfalse ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + + //maxClients -= n1; + + n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, team2MaxCl, lineHeight, qtrue ); +#ifdef _XBOX + if(cg->widescreen) + CG_DrawTeamBackground( SB_SCORELINE_X - 5 + 40, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + else +#endif + CG_DrawTeamBackground( SB_SCORELINE_X - 5, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + CG_TeamScoreboard( y, TEAM_BLUE, fade, team2MaxCl, lineHeight, qfalse ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + + //maxClients -= n2; + + maxClients -= (team1MaxCl+team2MaxCl); + } else { + int team1MaxCl = CG_GetTeamCount(TEAM_BLUE, maxClients); + int team2MaxCl = CG_GetTeamCount(TEAM_RED, maxClients); + + if (team1MaxCl > 10 && (team1MaxCl+team2MaxCl) > maxClients) + { + team1MaxCl -= team2MaxCl; + //subtract as many as you have to down to 10, once we get there + //we just set it to 10 + + if (team1MaxCl < 10) + { + team1MaxCl = 10; + } + } + + team2MaxCl = (maxClients-team1MaxCl); //team2 can display however many is left over after team1's display + + n1 = CG_TeamScoreboard( y, TEAM_BLUE, fade, team1MaxCl, lineHeight, qtrue ); +#ifdef _XBOX + if(cg->widescreen) + CG_DrawTeamBackground( SB_SCORELINE_X - 5 + 40, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + else +#endif + CG_DrawTeamBackground( SB_SCORELINE_X - 5, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + CG_TeamScoreboard( y, TEAM_BLUE, fade, team1MaxCl, lineHeight, qfalse ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + + //maxClients -= n1; + + n2 = CG_TeamScoreboard( y, TEAM_RED, fade, team2MaxCl, lineHeight, qtrue ); +#ifdef _XBOX + if(cg->widescreen) + CG_DrawTeamBackground( SB_SCORELINE_X - 5 + 40, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + else +#endif + CG_DrawTeamBackground( SB_SCORELINE_X - 5, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + CG_TeamScoreboard( y, TEAM_RED, fade, team2MaxCl, lineHeight, qfalse ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + + //maxClients -= n2; + + maxClients -= (team1MaxCl+team2MaxCl); + } + n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight, qfalse ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + + } else { + // + // free for all scoreboard + // + n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight, qfalse ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight, qfalse ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + } + + if (!localClient && ClientManager::splitScreenMode == qfalse) { + // draw local client at the bottom + for ( i = 0 ; i < cg->numScores ; i++ ) { + if ( cg->scores[i].client == cg->snap->ps.clientNum ) { + CG_DrawClientScore( y, &cg->scores[i], fadeColor, fade, lineHeight == SB_NORMAL_HEIGHT ); + break; + } + } + } + + //If in intermission, draw "press fire to continue" string. + if(cg->predictedPlayerState.pm_type == PM_INTERMISSION) { + const char *s = CG_GetStringEdString("SP_INGAME", "CONTINUE"); +#ifdef _XBOX + if(cg->widescreen) + CG_Text_Paint ( (720 - CG_Text_Width(s, 1.0f, FONT_MEDIUM)) / 2 , y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + else +#endif + CG_Text_Paint ( (640 - CG_Text_Width(s, 1.0f, FONT_MEDIUM)) / 2 , y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + + // load any models that have been deferred + if ( ++cg->deferredPlayerLoading > 10 ) { + CG_LoadDeferredPlayers(); + } + + return qtrue; +} + +//================================================================================ + diff --git a/codemp/cgame/cg_servercmds.c b/codemp/cgame/cg_servercmds.c new file mode 100644 index 0000000..74a4c42 --- /dev/null +++ b/codemp/cgame/cg_servercmds.c @@ -0,0 +1,1764 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_servercmds.c -- reliably sequenced text commands sent by the server +// these are processed at snapshot transition time, so there will definately +// be a valid snapshot this frame + +#include "cg_local.h" +#include "../../ui/menudef.h" +#if !defined(CL_LIGHT_H_INC) + #include "cg_lights.h" +#endif +#include "..\ghoul2\g2.h" +#include "../ui/ui_public.h" + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + +/* +================= +CG_ParseScores + +================= +*/ +static void CG_ParseScores( void ) { + int i, powerups, readScores; + + cg->numScores = atoi( CG_Argv( 1 ) ); + + readScores = cg->numScores; + + if (readScores > MAX_CLIENT_SCORE_SEND) + { + readScores = MAX_CLIENT_SCORE_SEND; + } + + if ( cg->numScores > MAX_CLIENTS ) { + cg->numScores = MAX_CLIENTS; + } + + cg->numScores = readScores; + + cg->teamScores[0] = atoi( CG_Argv( 2 ) ); + cg->teamScores[1] = atoi( CG_Argv( 3 ) ); + + memset( cg->scores, 0, sizeof( cg->scores ) ); + for ( i = 0 ; i < readScores ; i++ ) { + // + cg->scores[i].client = atoi( CG_Argv( i * 14 + 4 ) ); + cg->scores[i].score = atoi( CG_Argv( i * 14 + 5 ) ); + cg->scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) ); + cg->scores[i].time = atoi( CG_Argv( i * 14 + 7 ) ); + cg->scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) ); + powerups = atoi( CG_Argv( i * 14 + 9 ) ); + cg->scores[i].accuracy = atoi(CG_Argv(i * 14 + 10)); + cg->scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11)); + cg->scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12)); + cg->scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13)); + cg->scores[i].defendCount = atoi(CG_Argv(i * 14 + 14)); + cg->scores[i].assistCount = atoi(CG_Argv(i * 14 + 15)); + cg->scores[i].perfect = atoi(CG_Argv(i * 14 + 16)); + cg->scores[i].captures = atoi(CG_Argv(i * 14 + 17)); + + if ( cg->scores[i].client < 0 || cg->scores[i].client >= MAX_CLIENTS ) { + cg->scores[i].client = 0; + } + cgs.clientinfo[ cg->scores[i].client ].score = cg->scores[i].score; + cgs.clientinfo[ cg->scores[i].client ].powerups = powerups; + + cg->scores[i].team = cgs.clientinfo[cg->scores[i].client].team; + } + CG_SetScoreSelection(NULL); +} + +/* +================= +CG_ParseTeamInfo + +================= +*/ +static void CG_ParseTeamInfo( void ) { + int i; + int client; + + numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); + + for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) { + client = atoi( CG_Argv( i * 6 + 2 ) ); + + sortedTeamPlayers[i] = client; + + cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) ); + cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); + cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); + cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); + cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) ); + } +} + + +/* +================ +CG_ParseServerinfo + +This is called explicitly when the gamestate is first received, +and whenever the server updates any serverinfo flagged cvars +================ +*/ +void CG_ParseServerinfo( void ) { + const char *info; + const char *tinfo; + char *mapname; + + info = CG_ConfigString( CS_SERVERINFO ); + + cgs.debugMelee = atoi( Info_ValueForKey( info, "g_debugMelee" ) ); //trap_Cvar_GetHiddenVarValue("g_iknowkungfu"); + cgs.stepSlideFix = atoi( Info_ValueForKey( info, "g_stepSlideFix" ) ); + + cgs.noSpecMove = atoi( Info_ValueForKey( info, "g_noSpecMove" ) ); + + trap_Cvar_Set("g_WeaponDisable", Info_ValueForKey( info, "g_WeaponDisable")); + trap_Cvar_Set("g_allowVote", Info_ValueForKey( info, "g_allowVote" )); + trap_Cvar_Set("mapname", Info_ValueForKey( info, "mapname")); + + trap_Cvar_Set("bg_fighterAltControl", Info_ValueForKey( info, "bg_fighterAltControl" )); + + cgs.siegeTeamSwitch = atoi( Info_ValueForKey( info, "g_siegeTeamSwitch" ) ); + + cgs.showDuelHealths = atoi( Info_ValueForKey( info, "g_showDuelHealths" ) ); + + cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); + trap_Cvar_Set("g_gametype", va("%i", cgs.gametype)); + cgs.needpass = atoi( Info_ValueForKey( info, "needpass" ) ); + cgs.jediVmerc = atoi( Info_ValueForKey( info, "g_jediVmerc" ) ); + cgs.wDisable = atoi( Info_ValueForKey( info, "wdisable" ) ); + cgs.fDisable = atoi( Info_ValueForKey( info, "fdisable" ) ); + cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); + cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); + cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) ); + cgs.duel_fraglimit = atoi( Info_ValueForKey( info, "duel_fraglimit" ) ); + cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); + cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); + cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + mapname = Info_ValueForKey( info, "mapname" ); + + //rww - You must do this one here, Info_ValueForKey always uses the same memory pointer. + trap_Cvar_Set ( "ui_about_mapname", mapname ); + + Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); + Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) ); + trap_Cvar_Set("g_redTeam", cgs.redTeam); + Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) ); + trap_Cvar_Set("g_blueTeam", cgs.blueTeam); + + trap_Cvar_Set ( "ui_about_gametype", va("%i", cgs.gametype ) ); + trap_Cvar_Set ( "ui_about_fraglimit", va("%i", cgs.fraglimit ) ); + trap_Cvar_Set ( "ui_about_duellimit", va("%i", cgs.duel_fraglimit ) ); + trap_Cvar_Set ( "ui_about_capturelimit", va("%i", cgs.capturelimit ) ); + trap_Cvar_Set ( "ui_about_timelimit", va("%i", cgs.timelimit ) ); + trap_Cvar_Set ( "ui_about_maxclients", va("%i", cgs.maxclients ) ); + trap_Cvar_Set ( "ui_about_dmflags", va("%i", cgs.dmflags ) ); + trap_Cvar_Set ( "ui_about_hostname", Info_ValueForKey( info, "sv_hostname" ) ); + trap_Cvar_Set ( "ui_about_needpass", Info_ValueForKey( info, "g_needpass" ) ); + trap_Cvar_Set ( "ui_about_botminplayers", Info_ValueForKey ( info, "bot_minplayers" ) ); + + //Set the siege teams based on what the server has for overrides. + trap_Cvar_Set("cg_siegeTeam1", Info_ValueForKey(info, "g_siegeTeam1")); + trap_Cvar_Set("cg_siegeTeam2", Info_ValueForKey(info, "g_siegeTeam2")); + + tinfo = CG_ConfigString( CS_TERRAINS + 1 ); + if ( !tinfo || !*tinfo ) + { + cg->mInRMG = qfalse; + } + else + { + int weather = 0; + + cg->mInRMG = qtrue; + trap_Cvar_Set("RMG", "1"); + + weather = atoi( Info_ValueForKey( info, "RMG_weather" ) ); + + trap_Cvar_Set("RMG_weather", va("%i", weather)); + + if (weather == 1 || weather == 2) + { + cg->mRMGWeather = qtrue; + } + else + { + cg->mRMGWeather = qfalse; + } + } +} + +/* +================== +CG_ParseWarmup +================== +*/ +static void CG_ParseWarmup( void ) { + const char *info; + int warmup; + + info = CG_ConfigString( CS_WARMUP ); + + warmup = atoi( info ); + cg->warmupCount = -1; + + cg->warmup = warmup; +} + +/* +================ +CG_SetConfigValues + +Called on load to set the initial values from configure strings +================ +*/ +void CG_SetConfigValues( void ) +{ + const char *s; + const char *str; + + cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) ); + cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) ); + cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); + if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.redflag = s[0] - '0'; + cgs.blueflag = s[1] - '0'; + } + cg->warmup = atoi( CG_ConfigString( CS_WARMUP ) ); + + // Track who the jedi master is + cgs.jediMaster = atoi ( CG_ConfigString ( CS_CLIENT_JEDIMASTER ) ); + cgs.duelWinner = atoi ( CG_ConfigString ( CS_CLIENT_DUELWINNER ) ); + + str = CG_ConfigString(CS_CLIENT_DUELISTS); + + if (str && str[0]) + { + char buf[64]; + int c = 0; + int i = 0; + + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist1 = atoi ( buf ); + c = 0; + + i++; + while (str[i]) + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist2 = atoi ( buf ); + } +} + +/* +===================== +CG_ShaderStateChanged +===================== +*/ +void CG_ShaderStateChanged(void) { + char originalShader[MAX_QPATH]; + char newShader[MAX_QPATH]; + char timeOffset[16]; + const char *o; + char *n,*t; + + o = CG_ConfigString( CS_SHADERSTATE ); + while (o && *o) { + n = strstr(o, "="); + if (n && *n) { + strncpy(originalShader, o, n-o); + originalShader[n-o] = 0; + n++; + t = strstr(n, ":"); + if (t && *t) { + strncpy(newShader, n, t-n); + newShader[t-n] = 0; + } else { + break; + } + t++; + o = strstr(t, "@"); + if (o) { + strncpy(timeOffset, t, o-t); + timeOffset[o-t] = 0; + o++; + trap_R_RemapShader( originalShader, newShader, timeOffset ); + } + } else { + break; + } + } +} + +extern char *cg_customSoundNames[MAX_CUSTOM_SOUNDS]; +extern const char *cg_customCombatSoundNames[MAX_CUSTOM_COMBAT_SOUNDS]; +extern const char *cg_customExtraSoundNames[MAX_CUSTOM_EXTRA_SOUNDS]; +extern const char *cg_customJediSoundNames[MAX_CUSTOM_JEDI_SOUNDS]; +extern const char *cg_customDuelSoundNames[MAX_CUSTOM_DUEL_SOUNDS]; + +static const char *GetCustomSoundForType(int setType, int index) +{ + switch (setType) + { + case 1: + return cg_customSoundNames[index]; + case 2: + return cg_customCombatSoundNames[index]; + case 3: + return cg_customExtraSoundNames[index]; + case 4: + return cg_customJediSoundNames[index]; + case 5: + return bg_customSiegeSoundNames[index]; + case 6: + return cg_customDuelSoundNames[index]; + default: + assert(0); + return NULL; + } +} + +void SetCustomSoundForType(clientInfo_t *ci, int setType, int index, sfxHandle_t sfx) +{ + switch (setType) + { + case 1: + ci->sounds[index] = sfx; + break; + case 2: + ci->combatSounds[index] = sfx; + break; + case 3: + ci->extraSounds[index] = sfx; + break; + case 4: + ci->jediSounds[index] = sfx; + break; + case 5: + ci->siegeSounds[index] = sfx; + break; + case 6: + ci->duelSounds[index] = sfx; + break; + default: + assert(0); + break; + } +} + +static void CG_RegisterCustomSounds(clientInfo_t *ci, int setType, const char *psDir) +{ + int iTableEntries = 0; + int i; + + switch (setType) + { + case 1: + iTableEntries = MAX_CUSTOM_SOUNDS; + break; + case 2: + iTableEntries = MAX_CUSTOM_COMBAT_SOUNDS; + break; + case 3: + iTableEntries = MAX_CUSTOM_EXTRA_SOUNDS; + break; + case 4: + iTableEntries = MAX_CUSTOM_JEDI_SOUNDS; + break; + case 5: + iTableEntries = MAX_CUSTOM_SIEGE_SOUNDS; + default: + assert(0); + return; + } + + for ( i = 0 ; inpcClient) + { + return; + } + + //standard + if (cent->currentState.csSounds_Std) + { + const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Std ); + + if (s && s[0]) + { + char sEnd[MAX_QPATH]; + int i = 2; + int j = 0; + + //Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates + //it is an NPC custom sound dir. + while (s[i]) + { + sEnd[j] = s[i]; + j++; + i++; + } + sEnd[j] = 0; + + CG_RegisterCustomSounds(cent->npcClient, 1, sEnd); + } + } + else + { + memset(¢->npcClient->sounds, 0, sizeof(cent->npcClient->sounds)); + } + + //combat + if (cent->currentState.csSounds_Combat) + { + const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Combat ); + + if (s && s[0]) + { + char sEnd[MAX_QPATH]; + int i = 2; + int j = 0; + + //Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates + //it is an NPC custom sound dir. + while (s[i]) + { + sEnd[j] = s[i]; + j++; + i++; + } + sEnd[j] = 0; + + CG_RegisterCustomSounds(cent->npcClient, 2, sEnd); + } + } + else + { + memset(¢->npcClient->combatSounds, 0, sizeof(cent->npcClient->combatSounds)); + } + + //extra + if (cent->currentState.csSounds_Extra) + { + const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Extra ); + + if (s && s[0]) + { + char sEnd[MAX_QPATH]; + int i = 2; + int j = 0; + + //Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates + //it is an NPC custom sound dir. + while (s[i]) + { + sEnd[j] = s[i]; + j++; + i++; + } + sEnd[j] = 0; + + CG_RegisterCustomSounds(cent->npcClient, 3, sEnd); + } + } + else + { + memset(¢->npcClient->extraSounds, 0, sizeof(cent->npcClient->extraSounds)); + } + + //jedi + if (cent->currentState.csSounds_Jedi) + { + const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Jedi ); + + if (s && s[0]) + { + char sEnd[MAX_QPATH]; + int i = 2; + int j = 0; + + //Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates + //it is an NPC custom sound dir. + while (s[i]) + { + sEnd[j] = s[i]; + j++; + i++; + } + sEnd[j] = 0; + + CG_RegisterCustomSounds(cent->npcClient, 4, sEnd); + } + } + else + { + memset(¢->npcClient->jediSounds, 0, sizeof(cent->npcClient->jediSounds)); + } +} + +int CG_HandleAppendedSkin(char *modelName); +void CG_CacheG2AnimInfo(char *modelName); + +// nmckenzie: DUEL_HEALTH - fixme - we could really clean this up immensely with some helper functions. +void SetDuelistHealthsFromConfigString ( const char *str ) { + char buf[64]; + int c = 0; + int i = 0; + + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist1health = atoi ( buf ); + + c = 0; + i++; + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist2health = atoi ( buf ); + + c = 0; + i++; + if ( str[i] == '!' ) + { // we only have 2 duelists, apparently. + cgs.duelist3health = -1; + return; + } + + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist3health = atoi ( buf ); +} + + +static void CG_SetUIVoteCvar(void) +{ + const char *str; + + if(!strncmp(cgs.voteString, "g_gametype", 10)) { + str = CG_GetStringEdString("MP_SVGAME", "VOTE_GAMETYPE"); + Cvar_Set("vote_strref", va(str, cgs.voteCaller, cgs.voteString + 11)); + } else if(!strncmp(cgs.voteString, "map_restart", 11)) { + str = CG_GetStringEdString("MP_SVGAME", "VOTE_MAP_RESTART"); + Cvar_Set("vote_strref", va(str, cgs.voteCaller)); + } else if(!strncmp(cgs.voteString, "vstr nextmap", 12)) { + str = CG_GetStringEdString("MP_SVGAME", "VOTE_NEXT_MAP"); + Cvar_Set("vote_strref", va(str, cgs.voteCaller)); + } else if(!strncmp(cgs.voteString, "g_doWarmup", 10)) { + str = CG_GetStringEdString("MP_SVGAME", "VOTE_DO_WARMUP"); + Cvar_Set("vote_strref", va(str, cgs.voteCaller)); + } else if(!strncmp(cgs.voteString, "map", 3)) { + str = CG_GetStringEdString("MP_SVGAME", "VOTE_MAP"); + Cvar_Set("vote_strref", va(str, cgs.voteCaller, cgs.voteString + 4)); + } else if(!strncmp(cgs.voteString, "kick", 4)) { + str = CG_GetStringEdString("MP_SVGAME", "VOTE_KICK"); + Cvar_Set("vote_strref", va(str, cgs.voteCaller, cgs.voteString + 5)); + } +} + +/* +================ +CG_ConfigStringModified + +================ +*/ +extern int cgSiegeRoundState; +extern int cgSiegeRoundTime; +void CG_ParseSiegeObjectiveStatus(const char *str); +void CG_ParseWeatherEffect(const char *str); +extern void CG_ParseSiegeState(const char *str); //cg_main.c +extern int cg_beatingSiegeTime; +extern int cg_siegeWinTeam; +static void CG_ConfigStringModified( void ) { + const char *str; + int num; + + num = atoi( CG_Argv( 1 ) ); + + // get the gamestate from the client system, which will have the + // new configstring already integrated + trap_GetGameState( &cgs.gameState ); + + // look up the individual string that was modified + str = CG_ConfigString( num ); + + // do something with it if necessary + if ( num == CS_MUSIC ) { + CG_StartMusic( qtrue ); + } else if ( num == CS_SERVERINFO ) { + CG_ParseServerinfo(); + } else if ( num == CS_WARMUP ) { + CG_ParseWarmup(); + } else if ( num == CS_SCORES1 ) { + cgs.scores1 = atoi( str ); + } else if ( num == CS_SCORES2 ) { + cgs.scores2 = atoi( str ); + } else if ( num == CS_CLIENT_JEDIMASTER ) { + cgs.jediMaster = atoi ( str ); + } + else if ( num == CS_CLIENT_DUELWINNER ) + { + cgs.duelWinner = atoi ( str ); + } + else if ( num == CS_CLIENT_DUELISTS ) + { + char buf[64]; + int c = 0; + int i = 0; + + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist1 = atoi ( buf ); + c = 0; + + i++; + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist2 = atoi ( buf ); + + if (str[i]) + { + c = 0; + i++; + + while (str[i]) + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist3 = atoi(buf); + } + } + else if ( num == CS_CLIENT_DUELHEALTHS ) { // nmckenzie: DUEL_HEALTH + SetDuelistHealthsFromConfigString(str); + } + else if ( num == CS_LEVEL_START_TIME ) { + cgs.levelStartTime = atoi( str ); + } else if ( num == CS_VOTE_TIME ) { + cgs.voteTime = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_YES ) { + cgs.voteYes = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_NO ) { + cgs.voteNo = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_STRING ) { + Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); + CG_SetUIVoteCvar(); + } else if ( num == CS_VOTE_CALLER ) { + Q_strncpyz( cgs.voteCaller, str, sizeof( cgs.voteCaller ) ); + CG_SetUIVoteCvar(); + } else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1) { + cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue; + } else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1) { + cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue; + } else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1) { + cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue; + } else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1) { + Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) ); + } else if ( num == CS_INTERMISSION ) { + cg->intermissionStarted = atoi( str ); + } else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) { + char modelName[MAX_QPATH]; + strcpy(modelName, str); + if (strstr(modelName, ".glm") || modelName[0] == '$') + { //Check to see if it has a custom skin attached. + CG_HandleAppendedSkin(modelName); + CG_CacheG2AnimInfo(modelName); + } + + if (modelName[0] != '$' && modelName[0] != '@') + { //don't register vehicle names and saber names as models. + cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( modelName ); + } + else + { + cgs.gameModels[ num-CS_MODELS ] = 0; + } +// GHOUL2 Insert start + /* + } else if ( num >= CS_CHARSKINS && num < CS_CHARSKINS+MAX_CHARSKINS ) { + cgs.skins[ num-CS_CHARSKINS ] = trap_R_RegisterSkin( str ); + */ + //rww - removed and replaced with CS_G2BONES +// Ghoul2 Insert end + } else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_SOUNDS ) { + if ( str[0] != '*' ) { // player specific sounds don't register here + cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str ); + } + else if (str[1] == '$') + { //an NPC soundset + CG_PrecacheNPCSounds(str); + } + } else if ( num >= CS_EFFECTS && num < CS_EFFECTS+MAX_FX ) { + if (str[0] == '*') + { //it's a special global weather effect + CG_ParseWeatherEffect(str); + cgs.gameEffects[ num-CS_EFFECTS] = 0; + } + else + { + cgs.gameEffects[ num-CS_EFFECTS] = trap_FX_RegisterEffect( str ); + } + } + else if ( num >= CS_SIEGE_STATE && num < CS_SIEGE_STATE+1 ) + { + if (str[0]) + { + CG_ParseSiegeState(str); + } + } + else if ( num >= CS_SIEGE_WINTEAM && num < CS_SIEGE_WINTEAM+1 ) + { + if (str[0]) + { + cg_siegeWinTeam = atoi(str); + } + } + else if ( num >= CS_SIEGE_OBJECTIVES && num < CS_SIEGE_OBJECTIVES+1 ) + { + CG_ParseSiegeObjectiveStatus(str); + } + else if (num >= CS_SIEGE_TIMEOVERRIDE && num < CS_SIEGE_TIMEOVERRIDE+1) + { + cg_beatingSiegeTime = atoi(str); + CG_SetSiegeTimerCvar ( cg_beatingSiegeTime ); + } + else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) + { + CG_NewClientInfo( num - CS_PLAYERS, qtrue); + CG_BuildSpectatorString(); + } else if ( num == CS_FLAGSTATUS ) { + if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { + // format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped + cgs.redflag = str[0] - '0'; + cgs.blueflag = str[1] - '0'; + } + } + else if ( num == CS_SHADERSTATE ) { + CG_ShaderStateChanged(); + } + else if ( num >= CS_LIGHT_STYLES && num < CS_LIGHT_STYLES + (MAX_LIGHT_STYLES * 3)) + { + CG_SetLightstyle(num - CS_LIGHT_STYLES); + } + +} + +//frees all ghoul2 stuff and npc stuff from a centity -rww +void CG_KillCEntityG2(int entNum) +{ + int j; + clientInfo_t *ci = NULL; + centity_t *cent = &cg_entities[entNum]; + + if (entNum < MAX_CLIENTS) + { + ci = &cgs.clientinfo[entNum]; + } + else + { + ci = cent->npcClient; + } + + if (ci) + { + if (ci == cent->npcClient) + { //never going to be != cent->ghoul2, unless cent->ghoul2 has already been removed (and then this ptr is not valid) + ci->ghoul2Model = NULL; + } + else if (ci->ghoul2Model == cent->ghoul2) + { + ci->ghoul2Model = NULL; + } + else if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + ci->ghoul2Model = NULL; + } + + //Clean up any weapon instances for custom saber stuff + j = 0; + while (j < MAX_SABERS) + { + if (ci->ghoul2Weapons[j] && trap_G2_HaveWeGhoul2Models(ci->ghoul2Weapons[j])) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[j]); + ci->ghoul2Weapons[j] = NULL; + } + + j++; + } + } + + if (cent->ghoul2 && trap_G2_HaveWeGhoul2Models(cent->ghoul2)) + { + trap_G2API_CleanGhoul2Models(¢->ghoul2); + cent->ghoul2 = NULL; + } + + if (cent->grip_arm && trap_G2_HaveWeGhoul2Models(cent->grip_arm)) + { + trap_G2API_CleanGhoul2Models(¢->grip_arm); + cent->grip_arm = NULL; + } + + if (cent->frame_hold && trap_G2_HaveWeGhoul2Models(cent->frame_hold)) + { + trap_G2API_CleanGhoul2Models(¢->frame_hold); + cent->frame_hold = NULL; + } + + if (cent->npcClient) + { + CG_DestroyNPCClient(¢->npcClient); + } + + cent->isRagging = qfalse; //just in case. + cent->ikStatus = qfalse; + + cent->localAnimIndex = 0; +} + +void CG_KillCEntityInstances(void) +{ + int i = 0; + centity_t *cent; + + while (i < MAX_GENTITIES) + { + cent = &cg_entities[i]; + + if (i >= MAX_CLIENTS && cent->currentState.number == i) + { //do not clear G2 instances on client ents, they are constant + CG_KillCEntityG2(i); + } + + cent->bolt1 = 0; + cent->bolt2 = 0; + cent->bolt3 = 0; + cent->bolt4 = 0; + + cent->bodyHeight = 0;//SABER_LENGTH_MAX; + //cent->saberExtendTime = 0; + + cent->boltInfo = 0; + + cent->frame_minus1_refreshed = 0; + cent->frame_minus2_refreshed = 0; + cent->dustTrailTime = 0; + cent->ghoul2weapon = NULL; + //cent->torsoBolt = 0; + cent->trailTime = 0; + cent->frame_hold_time = 0; + cent->frame_hold_refreshed = 0; +#ifdef _XBOX + cent->trickAlpha[0] = 0; + cent->trickAlpha[1] = 0; + cent->trickAlphaTime[0] = 0; + cent->trickAlphaTime[1] = 0; +#else + cent->trickAlpha = 0; + cent->trickAlphaTime = 0; +#endif + VectorClear(cent->turAngles); + cent->weapon = 0; + cent->teamPowerEffectTime = 0; + cent->teamPowerType = 0; + cent->numLoopingSounds = 0; + + cent->localAnimIndex = 0; + + i++; + } +} + +/* +=============== +CG_MapRestart + +The server has issued a map_restart, so the next snapshot +is completely new and should not be interpolated to. + +A tournement restart will clear everything, but doesn't +require a reload of all the media +=============== +*/ +static void CG_MapRestart( void ) { + if ( cg_showmiss.integer ) { + CG_Printf( "CG_MapRestart\n" ); + } + + trap_R_ClearDecals ( ); + //FIXME: trap_FX_Reset? + + CG_InitLocalEntities(); + CG_InitMarkPolys(); + CG_ClearParticles (); + CG_KillCEntityInstances(); + + // make sure the "3 frags left" warnings play again + cg->fraglimitWarnings = 0; + + cg->timelimitWarnings = 0; + + cg->intermissionStarted = qfalse; + + cgs.voteTime = 0; + + cg->mapRestart = qtrue; + + CG_StartMusic(qtrue); + + trap_S_ClearLoopingSounds(); + + // we really should clear more parts of cg here and stop sounds + + // play the "fight" sound if this is a restart without warmup + if ( cg->warmup == 0 && cgs.gametype != GT_SIEGE && cgs.gametype != GT_POWERDUEL/* && cgs.gametype == GT_DUEL */) { + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + CG_CenterPrint( CG_GetStringEdString("MP_SVGAME", "BEGIN_DUEL"), 120, GIANTCHAR_WIDTH*2 ); + } + /* + if (cg_singlePlayerActive.integer) { + trap_Cvar_Set("ui_matchStartTime", va("%i", cg->time)); + if (cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string) { + trap_SendConsoleCommand(va("set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string)); + } + } + */ + trap_Cvar_Set("cg_thirdPerson", "0"); +#ifdef _XBOX + ClientManager::ActiveClient().cg_thirdPerson = 0; +#endif +} + +/* +================= +CG_RemoveChatEscapeChar +================= +*/ +static void CG_RemoveChatEscapeChar( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (text[i] == '\x19') + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + +#define MAX_STRINGED_SV_STRING 1024 // this is an quake-engine limit, not a StringEd limit + +void CG_CheckSVStringEdRef(char *buf, const char *str) +{ //I don't really like doing this. But it utilizes the system that was already in place. + int i = 0; + int b = 0; + int strLen = 0; + qboolean gotStrip = qfalse; + + if (!str || !str[0]) + { + if (str) + { + strcpy(buf, str); + } + return; + } + + strcpy(buf, str); + + strLen = strlen(str); + + if (strLen >= MAX_STRINGED_SV_STRING) + { + return; + } + + while (i < strLen && str[i]) + { + gotStrip = qfalse; + + if (str[i] == '@' && (i+1) < strLen) + { + if (str[i+1] == '@' && (i+2) < strLen) + { + if (str[i+2] == '@' && (i+3) < strLen) + { //@@@ should mean to insert a StringEd reference here, so insert it into buf at the current place + char stringRef[MAX_STRINGED_SV_STRING]; + int r = 0; + + while (i < strLen && str[i] == '@') + { + i++; + } + + while (i < strLen && str[i] && str[i] != ' ' && str[i] != ':' && str[i] != '.' && str[i] != '\n') + { + stringRef[r] = str[i]; + r++; + i++; + } + stringRef[r] = 0; + + buf[b] = 0; + Q_strcat(buf, MAX_STRINGED_SV_STRING, CG_GetStringEdString("MP_SVGAME", stringRef)); + b = strlen(buf); + } + } + } + + if (!gotStrip) + { + buf[b] = str[i]; + b++; + } + i++; + } + + buf[b] = 0; +} + +static void CG_BodyQueueCopy(centity_t *cent, int clientNum, int knownWeapon) +{ + centity_t *source; + animation_t *anim; + float animSpeed; + int flags=BONE_ANIM_OVERRIDE_FREEZE; + clientInfo_t *ci; + + if (cent->ghoul2) + { + trap_G2API_CleanGhoul2Models(¢->ghoul2); + } + + if (clientNum < 0 || clientNum >= MAX_CLIENTS) + { + return; + } + + source = &cg_entities[ clientNum ]; + ci = &cgs.clientinfo[ clientNum ]; + + if (!source) + { + return; + } + + if (!source->ghoul2) + { + return; + } + + cent->isRagging = qfalse; //reset in case it's still set from another body that was in this cent slot. + cent->ownerRagging = source->isRagging; //if the owner was in ragdoll state, then we want to go into it too right away. + +#if 0 + VectorCopy(source->lerpOriginOffset, cent->lerpOriginOffset); +#endif + + cent->bodyFadeTime = 0; + cent->bodyHeight = 0; + + cent->dustTrailTime = source->dustTrailTime; + + trap_G2API_DuplicateGhoul2Instance(source->ghoul2, ¢->ghoul2); + + if (source->isRagging) + { //just reset it now. + source->isRagging = qfalse; + trap_G2API_SetRagDoll(source->ghoul2, NULL); //calling with null parms resets to no ragdoll. + } + + //either force the weapon from when we died or remove it if it was a dropped weapon + if (knownWeapon > WP_BRYAR_PISTOL && trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) + { + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + } + else if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) + { +//#ifdef _XBOX +// if(ClientManager::ActiveClientNum() == 0) +//#endif + trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, knownWeapon), 0, cent->ghoul2, 1); + } + + if (!cent->ownerRagging) + { + int aNum; + int eFrame; + qboolean fallBack = qfalse; + + //anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.torsoAnim ]; + if (!BG_InDeathAnim(source->currentState.torsoAnim)) + { //then just snap the corpse into a default + anim = &bgAllAnims[source->localAnimIndex].anims[ BOTH_DEAD1 ]; + fallBack = qtrue; + } + else + { + anim = &bgAllAnims[source->localAnimIndex].anims[ source->currentState.torsoAnim ]; + } + animSpeed = 50.0f / anim->frameLerp; + + if (!fallBack) + { + //this will just set us to the last frame of the animation, in theory + aNum = cgs.clientinfo[source->currentState.number].frame+1; + + while (aNum >= anim->firstFrame+anim->numFrames) + { + aNum--; + } + + if (aNum < anim->firstFrame-1) + { //wrong animation...? + aNum = (anim->firstFrame+anim->numFrames)-1; + } + } + else + { + aNum = anim->firstFrame; + } + + eFrame = anim->firstFrame + anim->numFrames; + + //if (!cgs.clientinfo[source->currentState.number].frame || (cent->currentState.torsoAnim) != (source->currentState.torsoAnim) ) + //{ + // aNum = (anim->firstFrame+anim->numFrames)-1; + //} + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "upper_lumbar", aNum, eFrame, flags, animSpeed, cg->time, -1, 150); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", aNum, eFrame, flags, animSpeed, cg->time, -1, 150); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", aNum, eFrame, flags, animSpeed, cg->time, -1, 150); + } + + //After we create the bodyqueue, regenerate any limbs on the real instance + if (source->torsoBolt) + { + CG_ReattachLimb(source); + } +} + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +void CG_SiegeBriefingDisplay(int team, int dontshow); +void CG_ParseSiegeExtendedData(void); +extern void CG_ChatBox_AddString(char *chatStr); //cg_draw.c +static void CG_ServerCommand( void ) { + const char *cmd; + char text[MAX_SAY_TEXT]; + qboolean IRCG = qfalse; + + cmd = CG_Argv(0); + + if ( !cmd[0] ) { + // server claimed the command + return; + } + +#if 0 + // never seems to get used -Ste + if ( !strcmp( cmd, "spd" ) ) + { + const char *ID; + int holdInt,count,i; + char string[1204]; + + count = trap_Argc(); + + ID = CG_Argv(1); + holdInt = atoi(ID); + + memset( &string, 0, sizeof( string ) ); + + Com_sprintf( string,sizeof(string)," \"%s\"", (const char *) CG_Argv(2)); + + for (i=3;i= 2) + { + indexNum = atoi(CG_Argv(2)); + + if (indexNum != -1) + { + trackerent = &cg_entities[indexNum]; + } + } + + if (clent) + { + CG_S_StopLoopingSound(clent->currentState.number, -1); + } + if (trackerent) + { + CG_S_StopLoopingSound(trackerent->currentState.number, -1); + } + + return; + } + + if (!strcmp(cmd, "ircg")) + { //this means param 2 is the body index and we want to copy to bodyqueue on it + IRCG = qtrue; + } + + if (!strcmp(cmd, "rcg") || IRCG) + { //rcg - Restore Client Ghoul (make sure limbs are reattached and ragdoll state is reset - this must be done reliably) + int indexNum = 0; + int argNum = trap_Argc(); + centity_t *clent; + + if (argNum < 1) + { + assert(0); + return; + } + + indexNum = atoi(CG_Argv(1)); + if (indexNum < 0 || indexNum >= MAX_CLIENTS) + { + assert(0); + return; + } + + clent = &cg_entities[indexNum]; + + //assert(clent->ghoul2); + if (!clent->ghoul2) + { //this can happen while connecting as a client + return; + } + +#ifdef _DEBUG + if (!trap_G2_HaveWeGhoul2Models(clent->ghoul2)) + { + assert(!"Tried to reset state on a bad instance. Crash is inevitable."); + } +#endif + + if (IRCG) + { + int bodyIndex = 0; + int weaponIndex = 0; + int side = 0; + centity_t *body; + + assert(argNum >= 3); + bodyIndex = atoi(CG_Argv(2)); + weaponIndex = atoi(CG_Argv(3)); + side = atoi(CG_Argv(4)); + + body = &cg_entities[bodyIndex]; + + if (side) + { + body->teamPowerType = qtrue; //light side + } + else + { + body->teamPowerType = qfalse; //dark side + } + + CG_BodyQueueCopy(body, clent->currentState.number, weaponIndex); + } + + //reattach any missing limbs + if (clent->torsoBolt) + { + CG_ReattachLimb(clent); + } + + //make sure ragdoll state is reset + if (clent->isRagging) + { + clent->isRagging = qfalse; + trap_G2API_SetRagDoll(clent->ghoul2, NULL); //calling with null parms resets to no ragdoll. + } + + //clear all the decals as well + trap_G2API_ClearSkinGore(clent->ghoul2); + + clent->weapon = 0; + clent->ghoul2weapon = NULL; //force a weapon reinit + + return; + } + + if ( !strcmp( cmd, "cp" ) ) { + char strEd[MAX_STRINGED_SV_STRING]; + CG_CheckSVStringEdRef(strEd, CG_Argv(1)); + CG_CenterPrint( strEd, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + return; + } + + if ( !strcmp( cmd, "cps" ) ) { + char strEd[MAX_STRINGED_SV_STRING]; + char *x = (char *)CG_Argv(1); + if (x[0] == '@') + { + x++; + } + trap_SP_GetStringTextString(x, strEd, MAX_STRINGED_SV_STRING); + CG_CenterPrint( strEd, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + return; + } + + if ( !strcmp( cmd, "cs" ) ) { + CG_ConfigStringModified(); + return; + } + + if ( !strcmp( cmd, "print" ) ) { + char strEd[MAX_STRINGED_SV_STRING]; + CG_CheckSVStringEdRef(strEd, CG_Argv(1)); + + //Hack! No other way to differentiate important print commands from + //annoying ones? + if(strstr(CG_Argv(1), "VOTE")) { + CG_PrintfAlways( "%s", strEd ); + } else { + CG_Printf( "%s", strEd ); + } + return; + } + + if ( !strcmp( cmd, "chat" ) ) { + if ( !cg_teamChatsOnly.integer ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_ChatBox_AddString(text); + CG_Printf( "*%s\n", text ); + } + return; + } + + if ( !strcmp( cmd, "tchat" ) ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_ChatBox_AddString(text); + CG_Printf( "*%s\n", text ); + + return; + } + + //chat with location, possibly localized. + if ( !strcmp( cmd, "lchat" ) ) { + if ( !cg_teamChatsOnly.integer ) { + char name[MAX_STRING_CHARS]; + char loc[MAX_STRING_CHARS]; + char color[8]; + char message[MAX_STRING_CHARS]; + + if (trap_Argc() < 4) + { + return; + } + + strcpy(name, CG_Argv(1)); + strcpy(loc, CG_Argv(2)); + strcpy(color, CG_Argv(3)); + strcpy(message, CG_Argv(4)); + + if (loc[0] == '@') + { //get localized text + trap_SP_GetStringTextString(loc+1, loc, MAX_STRING_CHARS); + } + + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + //Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + Com_sprintf(text, MAX_SAY_TEXT, "%s<%s>^%s%s", name, loc, color, message); + CG_RemoveChatEscapeChar( text ); + CG_ChatBox_AddString(text); + CG_Printf( "*%s\n", text ); + } + return; + } + if ( !strcmp( cmd, "ltchat" ) ) { + char name[MAX_STRING_CHARS]; + char loc[MAX_STRING_CHARS]; + char color[8]; + char message[MAX_STRING_CHARS]; + + if (trap_Argc() < 4) + { + return; + } + + strcpy(name, CG_Argv(1)); + strcpy(loc, CG_Argv(2)); + strcpy(color, CG_Argv(3)); + strcpy(message, CG_Argv(4)); + + if (loc[0] == '@') + { //get localized text + trap_SP_GetStringTextString(loc+1, loc, MAX_STRING_CHARS); + } + + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + //Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + Com_sprintf(text, MAX_SAY_TEXT, "%s<%s> ^%s%s", name, loc, color, message); + CG_RemoveChatEscapeChar( text ); + CG_ChatBox_AddString(text); + CG_Printf( "*%s\n", text ); + + return; + } + + if ( !strcmp( cmd, "scores" ) ) { + CG_ParseScores(); + return; + } + + if ( !strcmp( cmd, "tinfo" ) ) { + CG_ParseTeamInfo(); + return; + } + + if ( !strcmp( cmd, "map_restart" ) ) { + CG_MapRestart(); + return; + } + + if ( Q_stricmp (cmd, "remapShader") == 0 ) { + if (trap_Argc() == 4) { + trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3)); + } + } + + // loaddeferred can be both a servercmd and a consolecmd + if ( !strcmp( cmd, "loaddefered" ) ) { // FIXME: spelled wrong, but not changing for demo + CG_LoadDeferredPlayers(); + return; + } + + // clientLevelShot is sent before taking a special screenshot for + // the menu system during development + if ( !strcmp( cmd, "clientLevelShot" ) ) { + cg->levelShot = qtrue; + return; + } + + CG_Printf( "Unknown client game command: %s\n", cmd ); +} + + +/* +==================== +CG_ExecuteNewServerCommands + +Execute all of the server commands that were received along +with this this snapshot. +==================== +*/ +void CG_ExecuteNewServerCommands( int latestSequence ) { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + while ( ClientManager::ActiveClient().serverCommandSequence < latestSequence ) { + if ( trap_GetServerCommand( ++ClientManager::ActiveClient().serverCommandSequence ) ) { + CG_ServerCommand(); + } + } + } + else + { +#endif + while ( cgs.serverCommandSequence < latestSequence ) { + if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { + CG_ServerCommand(); + } + } +#ifdef _XBOX + } +#endif +} diff --git a/codemp/cgame/cg_snapshot.c b/codemp/cgame/cg_snapshot.c new file mode 100644 index 0000000..3128273 --- /dev/null +++ b/codemp/cgame/cg_snapshot.c @@ -0,0 +1,497 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_snapshot.c -- things that happen on snapshot transition, +// not necessarily every single rendered frame + +#include "cg_local.h" + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + +/* +================== +CG_ResetEntity +================== +*/ +static void CG_ResetEntity( centity_t *cent ) { + // if the previous snapshot this entity was updated in is at least + // an event window back in time then we can reset the previous event + if ( cent->snapShotTime < cg->time - EVENT_VALID_MSEC ) { +#ifdef _XBOX + cent->previousEvent[0] = 0; + cent->previousEvent[1] = 0; +#else + cent->previousEvent = 0; +#endif + } + + cent->trailTime = cg->snap->serverTime; + + VectorCopy (cent->currentState.origin, cent->lerpOrigin); + VectorCopy (cent->currentState.angles, cent->lerpAngles); + + if (cent->currentState.eFlags & EF_G2ANIMATING) + { //reset the animation state + cent->pe.torso.animationNumber = -1; + cent->pe.legs.animationNumber = -1; + } + +#if 0 + if (cent->isRagging && (cent->currentState.eFlags & EF_DEAD)) + { + VectorAdd(cent->lerpOrigin, cent->lerpOriginOffset, cent->lerpOrigin); + } +#endif + + if ( cent->currentState.eType == ET_PLAYER || cent->currentState.eType == ET_NPC ) { + CG_ResetPlayerEntity( cent ); + } +} + +/* +=============== +CG_TransitionEntity + +cent->nextState is moved to cent->currentState and events are fired +=============== +*/ +static void CG_TransitionEntity( centity_t *cent ) { + cent->currentState = cent->nextState; +#ifdef _XBOX + cent->currentValid[ClientManager::ActiveClientNum()] = qtrue; + + // reset if the entity wasn't in the last frame or was teleported + if ( !cent->interpolate[ClientManager::ActiveClientNum()] ) { + CG_ResetEntity( cent ); + } + + // clear the next state. if will be set by the next CG_SetNextSnap + cent->interpolate[ClientManager::ActiveClientNum()] = qfalse; +#else + cent->currentValid = qtrue; + + // reset if the entity wasn't in the last frame or was teleported + if ( !cent->interpolate ) { + CG_ResetEntity( cent ); + } + + // clear the next state. if will be set by the next CG_SetNextSnap + cent->interpolate = qfalse; +#endif + + // check for events + CG_CheckEvents( cent ); +} + + +/* +================== +CG_SetInitialSnapshot + +This will only happen on the very first snapshot, or +on tourney restarts. All other times will use +CG_TransitionSnapshot instead. + +FIXME: Also called by map_restart? +================== +*/ +void CG_SetInitialSnapshot( snapshot_t *snap ) { + int i; + centity_t *cent; + entityState_t *state; + + cg->snap = snap; + + if ((cg_entities[snap->ps.clientNum].ghoul2 == NULL) && trap_G2_HaveWeGhoul2Models(cgs.clientinfo[snap->ps.clientNum].ghoul2Model)) + { + trap_G2API_DuplicateGhoul2Instance(cgs.clientinfo[snap->ps.clientNum].ghoul2Model, &cg_entities[snap->ps.clientNum].ghoul2); + CG_CopyG2WeaponInstance(&cg_entities[snap->ps.clientNum], FIRST_WEAPON, cg_entities[snap->ps.clientNum].ghoul2); + + if (trap_G2API_AddBolt(cg_entities[snap->ps.clientNum].ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cg_entities[snap->ps.clientNum].noFace = qtrue; + } + } + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); + + // sort out solid entities + CG_BuildSolidList(); + + CG_ExecuteNewServerCommands( snap->serverCommandSequence ); + + // set our local weapon selection pointer to + // what the server has indicated the current weapon is + CG_Respawn(); + + for ( i = 0 ; i < cg->snap->numEntities ; i++ ) { + state = &cg->snap->entities[ i ]; + cent = &cg_entities[ state->number ]; + + memcpy(¢->currentState, state, sizeof(entityState_t)); + //cent->currentState = *state; + +#ifdef _XBOX + cent->interpolate[ClientManager::ActiveClientNum()] = qfalse; + cent->currentValid[ClientManager::ActiveClientNum()] = qtrue; +#else + cent->interpolate = qfalse; + cent->currentValid = qtrue; +#endif + + CG_ResetEntity( cent ); + + // check for events + CG_CheckEvents( cent ); + } +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +extern qboolean CG_UsingEWeb(void); //cg_predict.c +static void CG_TransitionSnapshot( void ) { + centity_t *cent; + snapshot_t *oldFrame; + int i; + + if ( !cg->snap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg->snap" ); + } + if ( !cg->nextSnap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg->nextSnap" ); + } + + // execute any server string commands before transitioning entities + CG_ExecuteNewServerCommands( cg->nextSnap->serverCommandSequence ); + + // if we had a map_restart, set everthing with initial + if ( !cg->snap ) { + } + + // clear the currentValid flag for all entities in the existing snapshot + for ( i = 0 ; i < cg->snap->numEntities ; i++ ) { + cent = &cg_entities[ cg->snap->entities[ i ].number ]; +#ifdef _XBOX + cent->currentValid[ClientManager::ActiveClientNum()] = qfalse; +#else + cent->currentValid = qfalse; +#endif + } + + // move nextSnap to snap and do the transitions + oldFrame = cg->snap; + cg->snap = cg->nextSnap; + + //CG_CheckPlayerG2Weapons(&cg->snap->ps, &cg_entities[cg->snap->ps.clientNum]); + //CG_CheckPlayerG2Weapons(&cg->snap->ps, &cg->predictedPlayerEntity); + BG_PlayerStateToEntityState( &cg->snap->ps, &cg_entities[ cg->snap->ps.clientNum ].currentState, qfalse ); +#ifdef _XBOX + cg_entities[ cg->snap->ps.clientNum ].interpolate[ClientManager::ActiveClientNum()] = qfalse; +#else + cg_entities[ cg->snap->ps.clientNum ].interpolate = qfalse; +#endif + + for ( i = 0 ; i < cg->snap->numEntities ; i++ ) { + cent = &cg_entities[ cg->snap->entities[ i ].number ]; + CG_TransitionEntity( cent ); + + // remember time of snapshot this entity was last updated in + cent->snapShotTime = cg->snap->serverTime; + } + + cg->nextSnap = NULL; + + // check for playerstate transition events + if ( oldFrame ) { + playerState_t *ops, *ps; + + ops = &oldFrame->ps; + ps = &cg->snap->ps; + // teleporting checks are irrespective of prediction + if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) { + cg->thisFrameTeleport = qtrue; // will be cleared by prediction code + } + +#ifdef _XBOX + if ( ClientManager::splitScreenMode == qtrue ) { + CG_TransitionPlayerState( ps, ops ); + } + else +#endif + // if we are not doing client side movement prediction for any + // reason, then the client events and view changes will be issued now + if ( cg->demoPlayback || (cg->snap->ps.pm_flags & PMF_FOLLOW) + || cg_nopredict.integer || cg_synchronousClients.integer || CG_UsingEWeb() ) { + CG_TransitionPlayerState( ps, ops ); + } + } + +} + + +/* +=================== +CG_SetNextSnap + +A new snapshot has just been read in from the client system. +=================== +*/ +static void CG_SetNextSnap( snapshot_t *snap ) { + int num; + entityState_t *es; + centity_t *cent; + + cg->nextSnap = snap; + + //CG_CheckPlayerG2Weapons(&cg->snap->ps, &cg_entities[cg->snap->ps.clientNum]); + //CG_CheckPlayerG2Weapons(&cg->snap->ps, &cg->predictedPlayerEntity); + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); + //cg_entities[ cg->snap->ps.clientNum ].interpolate = qtrue; + //No longer want to do this, as the cg_entities[clnum] and cg->predictedPlayerEntity are one in the same. + + // check for extrapolation errors + for ( num = 0 ; num < snap->numEntities ; num++ ) + { + es = &snap->entities[num]; + cent = &cg_entities[ es->number ]; + + memcpy(¢->nextState, es, sizeof(entityState_t)); + //cent->nextState = *es; + + // if this frame is a teleport, or the entity wasn't in the + // previous frame, don't interpolate +#ifdef _XBOX + if ( !cent->currentValid[ClientManager::ActiveClientNum()] || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) { + cent->interpolate[ClientManager::ActiveClientNum()] = qfalse; + } else { + cent->interpolate[ClientManager::ActiveClientNum()] = qtrue; + } +#else + if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) { + cent->interpolate = qfalse; + } else { + cent->interpolate = qtrue; + } +#endif + } + + // if the next frame is a teleport for the playerstate, we + // can't interpolate during demos + if ( cg->snap && ( ( snap->ps.eFlags ^ cg->snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) { + cg->nextFrameTeleport = qtrue; + } else { + cg->nextFrameTeleport = qfalse; + } + + // if changing follow mode, don't interpolate + if ( cg->nextSnap->ps.clientNum != cg->snap->ps.clientNum ) { + cg->nextFrameTeleport = qtrue; + } + + // if changing server restarts, don't interpolate + if ( ( cg->nextSnap->snapFlags ^ cg->snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) { + cg->nextFrameTeleport = qtrue; + } + + // sort out solid entities + CG_BuildSolidList(); +} + + +/* +======================== +CG_ReadNextSnapshot + +This is the only place new snapshots are requested +This may increment cgs.processedSnapshotNum multiple +times if the client system fails to return a +valid snapshot. +======================== +*/ +static snapshot_t *CG_ReadNextSnapshot( void ) { + qboolean r; + snapshot_t *dest; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + if ( cg->latestSnapshotNum > cgs.processedSnapshotNum[ClientManager::ActiveClientNum()] + 1000 ) { +#ifndef FINAL_BUILD + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + cg->latestSnapshotNum, cgs.processedSnapshotNum[ClientManager::ActiveClientNum()] ); +#endif + } + } + else + { + if ( cg->latestSnapshotNum > cgs.processedSnapshotNum[0] + 1000 ) { +#ifndef FINAL_BUILD + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + cg->latestSnapshotNum, cgs.processedSnapshotNum[0] ); +#endif + } + } +#else + if ( cg->latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { +#ifndef FINAL_BUILD + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + cg->latestSnapshotNum, cgs.processedSnapshotNum ); +#endif + } +#endif + +#ifdef _XBOX + int clnum = 0; + if(ClientManager::splitScreenMode == qtrue) + clnum = ClientManager::ActiveClientNum(); + + while ( cgs.processedSnapshotNum[clnum] < cg->latestSnapshotNum ) { +#else + while ( cgs.processedSnapshotNum < cg->latestSnapshotNum ) { +#endif + // decide which of the two slots to load it into + if ( cg->snap == &cg->activeSnapshots[0] ) { + dest = &cg->activeSnapshots[1]; + } else { + dest = &cg->activeSnapshots[0]; + } + + // try to read the snapshot from the client system +#ifdef _XBOX + cgs.processedSnapshotNum[clnum]++; + r = trap_GetSnapshot( cgs.processedSnapshotNum[clnum], dest ); +#else + cgs.processedSnapshotNum++; + r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); +#endif + + // FIXME: why would trap_GetSnapshot return a snapshot with the same server time + if ( cg->snap && r && dest->serverTime == cg->snap->serverTime ) { + //continue; + } + + // if it succeeded, return + if ( r ) { +// CG_AddLagometerSnapshotInfo( dest ); + return dest; + } + + // a GetSnapshot will return failure if the snapshot + // never arrived, or is so old that its entities + // have been shoved off the end of the circular + // buffer in the client system. + + // record as a dropped packet +// CG_AddLagometerSnapshotInfo( NULL ); + + // If there are additional snapshots, continue trying to + // read them. + } + + // nothing left to read + return NULL; +} + + +/* +============ +CG_ProcessSnapshots + +We are trying to set up a renderable view, so determine +what the simulated time is, and try to get snapshots +both before and after that time if available. + +If we don't have a valid cg->snap after exiting this function, +then a 3D game view cannot be rendered. This should only happen +right after the initial connection. After cg->snap has been valid +once, it will never turn invalid. + +Even if cg->snap is valid, cg->nextSnap may not be, if the snapshot +hasn't arrived yet (it becomes an extrapolating situation instead +of an interpolating one) + +============ +*/ +void CG_ProcessSnapshots( void ) { + snapshot_t *snap; + int n; + + // see what the latest snapshot the client system has is + trap_GetCurrentSnapshotNumber( &n, &cg->latestSnapshotTime ); + if ( n != cg->latestSnapshotNum ) { + if ( n < cg->latestSnapshotNum ) { + // this should never happen + CG_Error( "CG_ProcessSnapshots: n < cg->latestSnapshotNum" ); + } + cg->latestSnapshotNum = n; + } + + // If we have yet to receive a snapshot, check for it. + // Once we have gotten the first snapshot, cg->snap will + // always have valid data for the rest of the game + while ( !cg->snap ) { + snap = CG_ReadNextSnapshot(); + if ( !snap ) { + // we can't continue until we get a snapshot + return; + } + + // set our weapon selection to what + // the playerstate is currently using + if ( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_SetInitialSnapshot( snap ); + } + } + + // loop until we either have a valid nextSnap with a serverTime + // greater than cg->time to interpolate towards, or we run + // out of available snapshots + do { + // if we don't have a nextframe, try and read a new one in + if ( !cg->nextSnap ) { + snap = CG_ReadNextSnapshot(); + + // if we still don't have a nextframe, we will just have to + // extrapolate + if ( !snap ) { + break; + } + + CG_SetNextSnap( snap ); + + + // if time went backwards, we have a level restart + if ( cg->nextSnap->serverTime < cg->snap->serverTime ) { + CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); + } + } + + // if our time is < nextFrame's, we have a nice interpolating state + if ( cg->time >= cg->snap->serverTime && cg->time < cg->nextSnap->serverTime ) { + break; + } + + // we have passed the transition from nextFrame to frame + CG_TransitionSnapshot(); + } while ( 1 ); + + // assert our valid conditions upon exiting + if ( cg->snap == NULL ) { + CG_Error( "CG_ProcessSnapshots: cg->snap == NULL" ); + } + if ( cg->time < cg->snap->serverTime ) { + // this can happen right after a vid_restart + cg->time = cg->snap->serverTime; + } + if ( cg->nextSnap != NULL && cg->nextSnap->serverTime <= cg->time ) { + CG_Error( "CG_ProcessSnapshots: cg->nextSnap->serverTime <= cg->time" ); + } + +} + diff --git a/codemp/cgame/cg_strap.c b/codemp/cgame/cg_strap.c new file mode 100644 index 0000000..ba635a8 --- /dev/null +++ b/codemp/cgame/cg_strap.c @@ -0,0 +1,73 @@ +//rww - shared trap call system +#include "cg_local.h" + +#include "../namespace_begin.h" + +qboolean strap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ + return trap_G2API_GetBoltMatrix(ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale); +} + +qboolean strap_G2API_GetBoltMatrix_NoReconstruct(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ + return trap_G2API_GetBoltMatrix_NoReconstruct(ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale); +} + +qboolean strap_G2API_GetBoltMatrix_NoRecNoRot(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ + return trap_G2API_GetBoltMatrix_NoRecNoRot(ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale); +} + +qboolean strap_G2API_SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ) +{ + return trap_G2API_SetBoneAngles(ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList, blendTime, currentTime); +} + +qboolean strap_G2API_SetBoneAnim(void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame , const int blendTime ) +{ + return trap_G2API_SetBoneAnim(ghoul2, modelIndex, boneName, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime); +} + +qboolean strap_G2API_GetBoneAnim(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList, const int modelIndex) +{ + return trap_G2API_GetBoneAnim(ghoul2, boneName, currentTime, currentFrame, startFrame, endFrame, flags, animSpeed, modelList, modelIndex); +} + +void strap_G2API_SetRagDoll(void *ghoul2, sharedRagDollParams_t *params) +{ + trap_G2API_SetRagDoll(ghoul2, params); +} + +void strap_G2API_AnimateG2Models(void *ghoul2, int time, sharedRagDollUpdateParams_t *params) +{ + trap_G2API_AnimateG2Models(ghoul2, time, params); +} + +qboolean strap_G2API_SetBoneIKState(void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params) +{ + return trap_G2API_SetBoneIKState(ghoul2, time, boneName, ikState, params); +} + +qboolean strap_G2API_IKMove(void *ghoul2, int time, sharedIKMoveParams_t *params) +{ + return trap_G2API_IKMove(ghoul2, time, params); +} + +void strap_TrueMalloc(void **ptr, int size) +{ + trap_TrueMalloc(ptr, size); +} + +void strap_TrueFree(void **ptr) +{ + trap_TrueFree(ptr); +} + +#include "../namespace_end.h" diff --git a/codemp/cgame/cg_syscalls.asm b/codemp/cgame/cg_syscalls.asm new file mode 100644 index 0000000..f182848 --- /dev/null +++ b/codemp/cgame/cg_syscalls.asm @@ -0,0 +1,203 @@ +code + +equ trap_Print -1 ; CG_PRINT +equ trap_Error -2 ; CG_ERROR +equ trap_Milliseconds -3 ; CG_MILLISECONDS +equ trap_PrecisionTimer_Start -4 ; CG_PRECISIONTIMER_START +equ trap_PrecisionTimer_End -5 ; CG_PRECISIONTIMER_END +equ trap_Cvar_Register -6 ; CG_CVAR_REGISTER +equ trap_Cvar_Update -7 ; CG_CVAR_UPDATE +equ trap_Cvar_Set -8 ; CG_CVAR_SET +equ trap_Cvar_VariableStringBuffer -9 ; CG_CVAR_VARIABLESTRINGBUFFER +equ trap_Argc -10 ; CG_ARGC +equ trap_Argv -11 ; CG_ARGV +equ trap_Args -12 ; CG_ARGS +equ trap_FS_FOpenFile -13 ; CG_FS_FOPENFILE +equ trap_FS_Read -14 ; CG_FS_READ +equ trap_FS_Write -15 ; CG_FS_WRITE +equ trap_FS_FCloseFile -16 ; CG_FS_FCLOSEFILE +equ trap_FS_GetFileList -17 ; CG_FS_GETFILELIST +equ trap_SendConsoleCommand -18 ; CG_SENDCONSOLECOMMAND +equ trap_AddCommand -19 ; CG_ADDCOMMAND +equ trap_RemoveCommand -20 ; CG_REMOVECOMMAND +equ trap_SendClientCommand -21 ; CG_SENDCLIENTCOMMAND +equ trap_UpdateScreen -22 ; CG_UPDATESCREEN +equ trap_CM_LoadMap -23 ; CG_CM_LOADMAP +equ trap_CM_NumInlineModels -24 ; CG_CM_NUMINLINEMODELS +equ trap_CM_InlineModel -25 ; CG_CM_INLINEMODEL +equ trap_CM_TempBoxModel -26 ; CG_CM_TEMPBOXMODEL +equ trap_CM_TempCapsuleModel -27 ; CG_CM_TEMPCAPSULEMODEL +equ trap_CM_PointContents -28 ; CG_CM_POINTCONTENTS +equ trap_CM_TransformedPointContents -29 ; CG_CM_TRANSFORMEDPOINTCONTENTS +equ trap_CM_BoxTrace -30 ; CG_CM_BOXTRACE +equ trap_CM_CapsuleTrace -31 ; CG_CM_CAPSULETRACE +equ trap_CM_TransformedBoxTrace -32 ; CG_CM_TRANSFORMEDBOXTRACE +equ trap_CM_TransformedCapsuleTrace -33 ; CG_CM_TRANSFORMEDCAPSULETRACE +equ trap_CM_MarkFragments -34 ; CG_CM_MARKFRAGMENTS +equ trap_S_GetVoiceVolume -35 ; CG_S_GETVOICEVOLUME +equ trap_S_MuteSound -36 ; CG_S_MUTESOUND +equ trap_S_StartSound -37 ; CG_S_STARTSOUND +equ trap_S_StartLocalSound -38 ; CG_S_STARTLOCALSOUND +equ trap_S_ClearLoopingSounds -39 ; CG_S_CLEARLOOPINGSOUNDS +equ trap_S_AddLoopingSound -40 ; CG_S_ADDLOOPINGSOUND +equ trap_S_UpdateEntityPosition -41 ; CG_S_UPDATEENTITYPOSITION +equ trap_S_AddRealLoopingSound -42 ; CG_S_ADDREALLOOPINGSOUND +equ trap_S_StopLoopingSound -43 ; CG_S_STOPLOOPINGSOUND +equ trap_S_Respatialize -44 ; CG_S_RESPATIALIZE +equ trap_S_RegisterSound -45 ; CG_S_REGISTERSOUND +equ trap_S_StartBackgroundTrack -46 ; CG_S_STARTBACKGROUNDTRACK +equ trap_S_UpdateAmbientSet -47 ; CG_S_UPDATEAMBIENTSET +equ trap_AS_ParseSets -48 ; CG_AS_PARSESETS +equ trap_AS_AddPrecacheEntry -49 ; CG_AS_ADDPRECACHEENTRY +equ trap_S_AddLocalSet -50 ; CG_S_ADDLOCALSET +equ trap_AS_GetBModelSound -51 ; CG_AS_GETBMODELSOUND +equ trap_R_LoadWorldMap -52 ; CG_R_LOADWORLDMAP +equ trap_R_RegisterModel -53 ; CG_R_REGISTERMODEL +equ trap_R_RegisterSkin -54 ; CG_R_REGISTERSKIN +equ trap_R_RegisterShader -55 ; CG_R_REGISTERSHADER +equ trap_R_RegisterShaderNoMip -56 ; CG_R_REGISTERSHADERNOMIP +equ trap_R_RegisterFont -57 ; CG_R_REGISTERFONT +equ trap_R_Font_StrLenPixels -58 ; CG_R_FONT_STRLENPIXELS +equ trap_R_Font_StrLenChars -59 ; CG_R_FONT_STRLENCHARS +equ trap_R_Font_HeightPixels -60 ; CG_R_FONT_STRHEIGHTPIXELS +equ trap_R_Font_DrawString -61 ; CG_R_FONT_DRAWSTRING +equ trap_Language_IsAsian -62 ; CG_LANGUAGE_ISASIAN +equ trap_Language_UsesSpaces -63 ; CG_LANGUAGE_USESSPACES +equ trap_AnyLanguage_ReadCharFromString -64 ; CG_ANYLANGUAGE_READCHARFROMSTRING +equ trap_R_ClearScene -201 ; CG_R_CLEARSCENE +equ trap_R_AddRefEntityToScene -202 ; CG_R_ADDREFENTITYTOSCENE +equ trap_R_AddPolyToScene -203 ; CG_R_ADDPOLYTOSCENE +equ trap_R_AddPolysToScene -204 ; CG_R_ADDPOLYSTOSCENE +equ trap_R_LightForPoint -205 ; CG_R_LIGHTFORPOINT +equ trap_R_AddLightToScene -206 ; CG_R_ADDLIGHTTOSCENE +equ trap_R_AddAdditiveLightToScene -207 ; CG_R_ADDADDITIVELIGHTTOSCENE +equ trap_R_RenderScene -208 ; CG_R_RENDERSCENE +equ trap_R_SetColor -209 ; CG_R_SETCOLOR +equ trap_R_DrawStretchPic -210 ; CG_R_DRAWSTRETCHPIC +equ trap_R_ModelBounds -211 ; CG_R_MODELBOUNDS +equ trap_R_LerpTag -212 ; CG_R_LERPTAG +equ trap_R_DrawRotatePic -213 ; CG_R_DRAWROTATEPIC +equ trap_R_DrawRotatePic2 -214 ; CG_R_DRAWROTATEPIC2 +equ trap_R_RemapShader -215 ; CG_R_REMAP_SHADER +equ trap_R_GetLightStyle -216 ; CG_R_GET_LIGHT_STYLE +equ trap_R_SetLightStyle -217 ; CG_R_SET_LIGHT_STYLE +equ trap_R_GetBModelVerts -218 ; CG_R_GET_BMODEL_VERTS +equ trap_FX_AddLine -219 ; CG_FX_ADDLINE +equ trap_GetGlconfig -220 ; CG_GETGLCONFIG +equ trap_GetGameState -221 ; CG_GETGAMESTATE +equ trap_GetCurrentSnapshotNumber -222 ; CG_GETCURRENTSNAPSHOTNUMBER +equ trap_GetSnapshot -223 ; CG_GETSNAPSHOT +equ trap_GetDefaultState -224 ; CG_GETDEFAULTSTATE +equ trap_GetServerCommand -225 ; CG_GETSERVERCOMMAND +equ trap_GetCurrentCmdNumber -226 ; CG_GETCURRENTCMDNUMBER +equ trap_GetUserCmd -227 ; CG_GETUSERCMD +equ trap_SetUserCmdValue -228 ; CG_SETUSERCMDVALUE +equ trap_SetClientForceAngle -229 ; CG_SETCLIENTFORCEANGLE +equ trap_SetClientTurnExtent -230 ; CG_SETCLIENTTURNEXTENT +equ trap_OpenUIMenu -231 ; CG_OPENUIMENU +equ trap_MemoryRemaining -234 ; CG_MEMORY_REMAINING +equ trap_Key_IsDown -235 ; CG_KEY_ISDOWN +equ trap_Key_GetCatcher -236 ; CG_KEY_GETCATCHER +equ trap_Key_SetCatcher -237 ; CG_KEY_SETCATCHER +equ trap_Key_GetKey -238 ; CG_KEY_GETKEY +equ trap_PC_AddGlobalDefine -239 ; CG_PC_ADD_GLOBAL_DEFINE +equ trap_PC_LoadSource -240 ; CG_PC_LOAD_SOURCE +equ trap_PC_FreeSource -241 ; CG_PC_FREE_SOURCE +equ trap_PC_ReadToken -242 ; CG_PC_READ_TOKEN +equ trap_PC_SourceFileAndLine -243 ; CG_PC_SOURCE_FILE_AND_LINE +equ trap_PC_LoadGlobalDefines -244 ; CG_PC_LOAD_GLOBAL_DEFINES +equ trap_PC_RemoveAllGlobalDefines -245 ; CG_PC_REMOVE_ALL_GLOBAL_DEFINES +equ trap_S_StopBackgroundTrack -246 ; CG_S_STOPBACKGROUNDTRACK +equ trap_RealTime -247 ; CG_REAL_TIME +equ trap_SnapVector -248 ; CG_SNAPVECTOR +equ trap_CIN_PlayCinematic -249 ; CG_CIN_PLAYCINEMATIC +equ trap_CIN_StopCinematic -250 ; CG_CIN_STOPCINEMATIC +equ trap_CIN_RunCinematic -251 ; CG_CIN_RUNCINEMATIC +equ trap_CIN_DrawCinematic -252 ; CG_CIN_DRAWCINEMATIC +equ trap_CIN_SetExtents -253 ; CG_CIN_SETEXTENTS +equ trap_GetEntityToken -254 ; CG_GET_ENTITY_TOKEN +equ trap_R_inPVS -255 ; CG_R_INPVS +equ trap_FX_RegisterEffect -256 ; CG_FX_REGISTER_EFFECT +equ trap_FX_PlaySimpleEffect -257 ; CG_FX_PLAY_SIMPLE_EFFECT +equ trap_FX_PlayEffect -258 ; CG_FX_PLAY_EFFECT +equ trap_FX_PlayEntityEffect -259 ; CG_FX_PLAY_ENTITY_EFFECT +equ trap_FX_PlaySimpleEffectID -260 ; CG_FX_PLAY_SIMPLE_EFFECT_ID +equ trap_FX_PlayEffectID -261 ; CG_FX_PLAY_EFFECT_ID +equ trap_FX_PlayPortalEffectID -262 ; CG_FX_PLAY_PORTAL_EFFECT_ID +equ trap_FX_PlayEntityEffectID -263 ; CG_FX_PLAY_ENTITY_EFFECT_ID +equ trap_FX_PlayBoltedEffectID -264 ; CG_FX_PLAY_BOLTED_EFFECT_ID +equ trap_FX_AddScheduledEffects -265 ; CG_FX_ADD_SCHEDULED_EFFECTS +equ trap_FX_InitSystem -266 ; CG_FX_INIT_SYSTEM +equ trap_FX_FreeSystem -267 ; CG_FX_FREE_SYSTEM +equ trap_FX_AdjustTime -268 ; CG_FX_ADJUST_TIME +equ trap_FX_AddPoly -269 ; CG_FX_ADDPOLY +equ trap_FX_AddBezier -270 ; CG_FX_ADDBEZIER +equ trap_FX_AddPrimitive -271 ; CG_FX_ADDPRIMITIVE +equ trap_FX_AddSprite -272 ; CG_FX_ADDSPRITE +equ trap_FX_AddElectricity -273 ; CG_FX_ADDELECTRICITY +equ trap_SP_GetStringTextString -274 ; CG_SP_GETSTRINGTEXTSTRING +equ trap_SP_Register -275 ; CG_SP_REGISTER +equ trap_ROFF_Clean -276 ; CG_ROFF_CLEAN +equ trap_ROFF_UpdateEntities -277 ; CG_ROFF_UPDATE_ENTITIES +equ trap_ROFF_Cache -278 ; CG_ROFF_CACHE +equ trap_ROFF_Play -279 ; CG_ROFF_PLAY +equ trap_ROFF_Purge_Ent -280 ; CG_ROFF_PURGE_ENT +equ trap_TrueMalloc -281 ; CG_TRUEMALLOC +equ trap_TrueFree -282 ; CG_TRUEFREE +equ trap_G2_ListModelSurfaces -283 ; CG_G2_LISTSURFACES +equ trap_G2_ListModelBones -284 ; CG_G2_LISTBONES +equ trap_G2_SetGhoul2ModelIndexes -285 ; CG_G2_SETMODELS +equ trap_G2_HaveWeGhoul2Models -286 ; CG_G2_HAVEWEGHOULMODELS +equ trap_G2API_GiveMeVectorFromMatrix -287 ; CG_G2_GIVEMEVECTORFROMMATRIX +equ trap_G2API_GetBoltMatrix -288 ; CG_G2_GETBOLT +equ trap_G2API_GetBoltMatrix_NoReconstruct -289 ; CG_G2_GETBOLT_NOREC +equ trap_G2API_GetBoltMatrix_NoRecNoRot -290 ; CG_G2_GETBOLT_NOREC_NOROT +equ trap_G2API_InitGhoul2Model -291 ; CG_G2_INITGHOUL2MODEL +equ trap_G2API_SetSkin -292 ; CG_G2_SETSKIN +equ trap_G2API_CollisionDetect -293 ; CG_G2_COLLISIONDETECT +equ trap_G2API_CleanGhoul2Models -294 ; CG_G2_CLEANMODELS +equ trap_G2API_SetBoneAngles -295 ; CG_G2_ANGLEOVERRIDE +equ trap_G2API_SetBoneAnim -296 ; CG_G2_PLAYANIM +equ trap_G2API_GetBoneAnim -297 ; CG_G2_GETBONEANIM +equ trap_G2API_GetBoneFrame -298 ; CG_G2_GETBONEFRAME +equ trap_G2API_GetGLAName -299 ; CG_G2_GETGLANAME +equ trap_G2API_CopyGhoul2Instance -300 ; CG_G2_COPYGHOUL2INSTANCE +equ trap_G2API_CopySpecificGhoul2Model -301 ; CG_G2_COPYSPECIFICGHOUL2MODEL +equ trap_G2API_DuplicateGhoul2Instance -302 ; CG_G2_DUPLICATEGHOUL2INSTANCE +equ trap_G2API_HasGhoul2ModelOnIndex -303 ; CG_G2_HASGHOUL2MODELONINDEX +equ trap_G2API_RemoveGhoul2Model -304 ; CG_G2_REMOVEGHOUL2MODEL +equ trap_G2API_AddBolt -305 ; CG_G2_ADDBOLT +equ trap_G2API_SetBoltInfo -306 ; CG_G2_SETBOLTON +equ trap_G2API_SetRootSurface -307 ; CG_G2_SETROOTSURFACE +equ trap_G2API_SetSurfaceOnOff -308 ; CG_G2_SETSURFACEONOFF +equ trap_G2API_SetNewOrigin -309 ; CG_G2_SETNEWORIGIN +equ trap_G2API_GetSurfaceRenderStatus -310 ; CG_G2_GETSURFACERENDERSTATUS +equ trap_G2API_GetTime -311 ; CG_G2_GETTIME +equ trap_G2API_SetTime -312 ; CG_G2_SETTIME +equ trap_G2API_SetRagDoll -313 ; CG_G2_SETRAGDOLL +equ trap_G2API_AnimateG2Models -314 ; CG_G2_ANIMATEG2MODELS +equ trap_G2API_SetBoneIKState -315 ; CG_G2_SETBONEIKSTATE +equ trap_G2API_IKMove -316 ; CG_G2_IKMOVE +equ trap_G2API_GetSurfaceName -317 ; CG_G2_GETSURFACENAME +equ trap_CG_RegisterSharedMemory -318 ; CG_SET_SHARED_BUFFER +equ trap_CM_RegisterTerrain -319 ; CG_CM_REGISTER_TERRAIN +equ trap_RMG_Init -320 ; CG_RMG_INIT +equ trap_RE_InitRendererTerrain -321 ; CG_RE_INIT_RENDERER_TERRAIN +equ trap_R_WeatherContentsOverride -322 ; CG_R_WEATHER_CONTENTS_OVERRIDE + + +; hardcoded functions +equ memset -101 ; CGAME_MEMSET +equ memcpy -102 ; CGAME_MEMCPY +equ strncpy -103 ; CGAME_STRNCPY +equ sin -104 ; CGAME_SIN +equ cos -105 ; CGAME_COS +equ atan2 -106 ; CGAME_ATAN2 +equ sqrt -107 ; CGAME_SQRT +equ matrixmultiply -108 ; CGAME_MATRIXMULTIPLY +equ anglevectors -109 ; CGAME_ANGLEVECTORS +equ perpendicularvector -110 ; CGAME_PERPENDICULARVECTOR +equ floor -111 ; CGAME_FLOOR +equ ceil -112 ; CGAME_CEIL +equ acos -115 ; CGAME_ACOS +equ asin -116 ; CGAME_ASIN diff --git a/codemp/cgame/cg_syscalls.c b/codemp/cgame/cg_syscalls.c new file mode 100644 index 0000000..bea6ecb --- /dev/null +++ b/codemp/cgame/cg_syscalls.c @@ -0,0 +1,1118 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_syscalls.c -- this file is only included when building a dll +// cg_syscalls.asm is included instead when building a qvm +#include "cg_local.h" + +static int (QDECL *syscall)( int arg, ... ) = (int (QDECL *)( int, ...))-1; + +#include "../namespace_begin.h" +void dllEntry( int (QDECL *syscallptr)( int arg,... ) ) { + syscall = syscallptr; +} + + +int PASSFLOAT( float x ) { + float floatTemp; + floatTemp = x; + return *(int *)&floatTemp; +} + +void trap_PrintAlways( const char *fmt ) { + syscall( CG_PRINTALWAYS, fmt ); +} + +void trap_Print( const char *fmt ) { + syscall( CG_PRINT, fmt ); +} + +void trap_Error( const char *fmt ) { + syscall( CG_ERROR, fmt ); +} + +int trap_Milliseconds( void ) { + return syscall( CG_MILLISECONDS ); +} + +//rww - precision timer funcs... -ALWAYS- call end after start with supplied ptr, or you'll get a nasty memory leak. +//not that you should be using these outside of debug anyway.. because you shouldn't be. So don't. + +//Start should be suppled with a pointer to an empty pointer (e.g. void *blah; trap_PrecisionTimer_Start(&blah);), +//the empty pointer will be filled with an exe address to our timer (this address means nothing in vm land however). +//You must pass this pointer back unmodified to the timer end func. +void trap_PrecisionTimer_Start(void **theNewTimer) +{ + syscall(CG_PRECISIONTIMER_START, theNewTimer); +} + +//If you're using the above example, the appropriate call for this is int result = trap_PrecisionTimer_End(blah); +int trap_PrecisionTimer_End(void *theTimer) +{ + return syscall(CG_PRECISIONTIMER_END, theTimer); +} + +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { + syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); +} + +void trap_Cvar_Update( vmCvar_t *vmCvar ) { + syscall( CG_CVAR_UPDATE, vmCvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { + syscall( CG_CVAR_SET, var_name, value ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +int trap_Cvar_GetHiddenVarValue(const char *name) +{ + return syscall(CG_CVAR_GETHIDDENVALUE, name); +} + +int trap_Argc( void ) { + return syscall( CG_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { + syscall( CG_ARGV, n, buffer, bufferLength ); +} + +void trap_Args( char *buffer, int bufferLength ) { + syscall( CG_ARGS, buffer, bufferLength ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + return syscall( CG_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { + syscall( CG_FS_FCLOSEFILE, f ); +} + +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { + return syscall( CG_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +void trap_SendConsoleCommand( const char *text ) { + syscall( CG_SENDCONSOLECOMMAND, text ); +} + +void trap_AddCommand( const char *cmdName ) { + syscall( CG_ADDCOMMAND, cmdName ); +} + +void trap_RemoveCommand( const char *cmdName ) { + syscall( CG_REMOVECOMMAND, cmdName ); +} + +void trap_SendClientCommand( const char *s ) { + syscall( CG_SENDCLIENTCOMMAND, s ); +} + +void trap_UpdateScreen( void ) { + syscall( CG_UPDATESCREEN ); +} + +void trap_CM_LoadMap( const char *mapname, qboolean SubBSP ) { + syscall( CG_CM_LOADMAP, mapname, SubBSP ); +} + +int trap_CM_NumInlineModels( void ) { + return syscall( CG_CM_NUMINLINEMODELS ); +} + +clipHandle_t trap_CM_InlineModel( int index ) { + return syscall( CG_CM_INLINEMODEL, index ); +} + +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); +} + +clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); +} + +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) { + return syscall( CG_CM_POINTCONTENTS, p, model ); +} + +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { + return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); +} + +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ) { + return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); +} + +int trap_S_GetVoiceVolume( int entityNum ) { + return syscall( CG_S_GETVOICEVOLUME, entityNum ); +} + +void trap_S_MuteSound( int entityNum, int entchannel ) { + syscall( CG_S_MUTESOUND, entityNum, entchannel ); +} + +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) { + syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { + syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); +} + +void trap_S_ClearLoopingSounds(void) { + syscall( CG_S_CLEARLOOPINGSOUNDS ); +} + +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { + syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); +} + +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { + syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_StopLoopingSound( int entityNum ) { + syscall( CG_S_STOPLOOPINGSOUND, entityNum ); +} + +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { + syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); +} + +void trap_S_ShutUp(qboolean shutUpFactor) +{ + syscall(CG_S_SHUTUP, shutUpFactor); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample ) { + return syscall( CG_S_REGISTERSOUND, sample ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop, qboolean bReturnWithoutStarting ) { + syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop, bReturnWithoutStarting ); +} + +void trap_S_UpdateAmbientSet( const char *name, vec3_t origin ) +{ + syscall(CG_S_UPDATEAMBIENTSET, name, origin); +} + +void trap_AS_ParseSets( void ) +{ + syscall(CG_AS_PARSESETS); +} + +void trap_AS_AddPrecacheEntry( const char *name ) +{ + syscall(CG_AS_ADDPRECACHEENTRY, name); +} + +int trap_S_AddLocalSet( const char *name, vec3_t listener_origin, vec3_t origin, int entID, int time ) +{ + return syscall(CG_S_ADDLOCALSET, name, listener_origin, origin, entID, time); +} + +sfxHandle_t trap_AS_GetBModelSound( const char *name, int stage ) +{ + return syscall(CG_AS_GETBMODELSOUND, name, stage); +} + +void trap_R_LoadWorldMap( const char *mapname ) { + syscall( CG_R_LOADWORLDMAP, mapname ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) { + return syscall( CG_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) { + return syscall( CG_R_REGISTERSKIN, name ); +} + +qhandle_t trap_R_RegisterShader( const char *name ) { + return syscall( CG_R_REGISTERSHADER, name ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { + return syscall( CG_R_REGISTERSHADERNOMIP, name ); +} + +qhandle_t trap_R_RegisterFont( const char *fontName ) +{ + return syscall( CG_R_REGISTERFONT, fontName); +} + +int trap_R_Font_StrLenPixels(const char *text, const int iFontIndex, const float scale) +{ + return syscall( CG_R_FONT_STRLENPIXELS, text, iFontIndex, PASSFLOAT(scale)); +} + +int trap_R_Font_StrLenChars(const char *text) +{ + return syscall( CG_R_FONT_STRLENCHARS, text); +} + +int trap_R_Font_HeightPixels(const int iFontIndex, const float scale) +{ + return syscall( CG_R_FONT_STRHEIGHTPIXELS, iFontIndex, PASSFLOAT(scale)); +} + +void trap_R_Font_DrawString(int ox, int oy, const char *text, const float *rgba, const int setIndex, int iCharLimit, const float scale) +{ + syscall( CG_R_FONT_DRAWSTRING, ox, oy, text, rgba, setIndex, iCharLimit, PASSFLOAT(scale)); +} + +qboolean trap_Language_IsAsian(void) +{ + return syscall( CG_LANGUAGE_ISASIAN ); +} + +qboolean trap_Language_UsesSpaces(void) +{ + return syscall( CG_LANGUAGE_USESSPACES ); +} + +unsigned int trap_AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation/* = NULL*/ ) +{ + return syscall( CG_ANYLANGUAGE_READCHARFROMSTRING, psText, piAdvanceCount, pbIsTrailingPunctuation); +} + +void trap_R_ClearScene( void ) { + syscall( CG_R_CLEARSCENE ); +} + +void trap_R_ClearDecals ( void ) +{ + syscall ( CG_R_CLEARDECALS ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { + syscall( CG_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { + syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) { + syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); +} + +void trap_R_AddDecalToScene ( qhandle_t shader, const vec3_t origin, const vec3_t dir, float orientation, float r, float g, float b, float a, qboolean alphaFade, float radius, qboolean temporary ) +{ + syscall( CG_R_ADDDECALTOSCENE, shader, origin, dir, PASSFLOAT(orientation), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b), PASSFLOAT(a), alphaFade, PASSFLOAT(radius), temporary ); +} + +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) { + return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir ); +} + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_RenderScene( const refdef_t *fd ) { + syscall( CG_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) { + syscall( CG_R_SETCOLOR, rgba ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + syscall( CG_R_MODELBOUNDS, model, mins, maxs ); +} + +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ) { + return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); +} + +void trap_R_DrawRotatePic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ) +{ + syscall( CG_R_DRAWROTATEPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), PASSFLOAT(a), hShader ); +} + +void trap_R_DrawRotatePic2( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ) +{ + syscall( CG_R_DRAWROTATEPIC2, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), PASSFLOAT(a), hShader ); +} + +//linear fogging, with settable range -rww +void trap_R_SetRangeFog(float range) +{ + syscall(CG_R_SETRANGEFOG, PASSFLOAT(range)); +} + +//set some properties for the draw layer for my refractive effect (here primarily for mod authors) -rww +void trap_R_SetRefractProp(float alpha, float stretch, qboolean prepost, qboolean negate) +{ + syscall(CG_R_SETREFRACTIONPROP, PASSFLOAT(alpha), PASSFLOAT(stretch), prepost, negate); +} + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) +{ + syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_R_GetLightStyle(int style, color4ub_t color) +{ + syscall( CG_R_GET_LIGHT_STYLE, style, color ); +} + +void trap_R_SetLightStyle(int style, int color) +{ + syscall( CG_R_SET_LIGHT_STYLE, style, color ); +} + +void trap_R_GetBModelVerts(int bmodelIndex, vec3_t *verts, vec3_t normal ) +{ + syscall( CG_R_GET_BMODEL_VERTS, bmodelIndex, verts, normal ); +} + +void trap_R_GetDistanceCull(float *f) +{ + syscall(CG_R_GETDISTANCECULL, f); +} + +//get screen resolution -rww +void trap_R_GetRealRes(int *w, int *h) +{ + syscall( CG_R_GETREALRES, w, h ); +} + + +//automap elevation setting -rww +void trap_R_AutomapElevAdj(float newHeight) +{ + syscall( CG_R_AUTOMAPELEVADJ, PASSFLOAT(newHeight) ); +} + +//initialize automap -rww +qboolean trap_R_InitWireframeAutomap(void) +{ + return syscall( CG_R_INITWIREFRAMEAUTO ); +} + +void trap_FX_AddLine( const vec3_t start, const vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + const vec3_t sRGB, const vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags) +{ + syscall( CG_FX_ADDLINE, start, end, PASSFLOAT(size1), PASSFLOAT(size2), PASSFLOAT(sizeParm), + PASSFLOAT(alpha1), PASSFLOAT(alpha2), PASSFLOAT(alphaParm), + sRGB, eRGB, PASSFLOAT(rgbParm), + killTime, shader, flags); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) { + syscall( CG_GETGLCONFIG, glconfig ); +} + +void trap_GetGameState( gameState_t *gamestate ) { + syscall( CG_GETGAMESTATE, gamestate ); +} + +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); +} + +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); +} + +qboolean trap_GetDefaultState(int entityIndex, entityState_t *state ) +{ //rwwRMG - added [NEWTRAP] + return syscall( CG_GETDEFAULTSTATE, entityIndex, state ); +} + +qboolean trap_GetServerCommand( int serverCommandNumber ) { + return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); +} + +int trap_GetCurrentCmdNumber( void ) { + return syscall( CG_GETCURRENTCMDNUMBER ); +} + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); +} + +void trap_SetUserCmdValue( int stateValue, float sensitivityScale, float mPitchOverride, float mYawOverride, float mSensitivityOverride, int fpSel, int invenSel, qboolean fighterControls ) { + syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale), PASSFLOAT(mPitchOverride), PASSFLOAT(mYawOverride), PASSFLOAT(mSensitivityOverride), fpSel, invenSel, fighterControls ); +} + +void trap_SetClientForceAngle(int time, vec3_t angle) +{ + syscall( CG_SETCLIENTFORCEANGLE, time, angle ); +} + +void trap_SetClientTurnExtent(float turnAdd, float turnSub, int turnTime) +{ + syscall( CG_SETCLIENTTURNEXTENT, PASSFLOAT(turnAdd), PASSFLOAT(turnSub), turnTime ); +} + +void trap_OpenUIMenu(int menuID) +{ + syscall( CG_OPENUIMENU, menuID ); +} + +void testPrintInt( char *string, int i ) { + syscall( CG_TESTPRINTINT, string, i ); +} + +void testPrintFloat( char *string, float f ) { + syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); +} + +int trap_MemoryRemaining( void ) { + return syscall( CG_MEMORY_REMAINING ); +} + +qboolean trap_Key_IsDown( int keynum ) { + return syscall( CG_KEY_ISDOWN, keynum ); +} + +int trap_Key_GetCatcher( void ) { + return syscall( CG_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) { + syscall( CG_KEY_SETCATCHER, catcher ); +} + +int trap_Key_GetKey( const char *binding ) { + return syscall( CG_KEY_GETKEY, binding ); +} + +int trap_PC_AddGlobalDefine( char *define ) { + return syscall( CG_PC_ADD_GLOBAL_DEFINE, define ); +} + +int trap_PC_LoadSource( const char *filename ) { + return syscall( CG_PC_LOAD_SOURCE, filename ); +} + +int trap_PC_FreeSource( int handle ) { + return syscall( CG_PC_FREE_SOURCE, handle ); +} + +int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { + return syscall( CG_PC_READ_TOKEN, handle, pc_token ); +} + +int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { + return syscall( CG_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +int trap_PC_LoadGlobalDefines ( const char* filename ) +{ + return syscall ( CG_PC_LOAD_GLOBAL_DEFINES, filename ); +} + +void trap_PC_RemoveAllGlobalDefines ( void ) +{ + syscall ( CG_PC_REMOVE_ALL_GLOBAL_DEFINES ); +} + +void trap_S_StopBackgroundTrack( void ) { + syscall( CG_S_STOPBACKGROUNDTRACK ); +} + +int trap_RealTime(qtime_t *qtime) { + return syscall( CG_REAL_TIME, qtime ); +} + +void trap_SnapVector( float *v ) { + syscall( CG_SNAPVECTOR, v ); +} +/* +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) { + return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); +} + +// stops playing the cinematic and ends it. should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic(int handle) { + return syscall(CG_CIN_STOPCINEMATIC, handle); +} + + +// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic (int handle) { + return syscall(CG_CIN_RUNCINEMATIC, handle); +} + + +// draws the current frame +void trap_CIN_DrawCinematic (int handle) { + syscall(CG_CIN_DRAWCINEMATIC, handle); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) { + syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h); +} +*/ + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ) { + return syscall( CG_GET_ENTITY_TOKEN, buffer, bufferSize ); +} + +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2, byte *mask ) { + return syscall( CG_R_INPVS, p1, p2, mask ); +} + + +int trap_FX_RegisterEffect(const char *file) +{ + return syscall( CG_FX_REGISTER_EFFECT, file); +} + +void trap_FX_PlayEffect( const char *file, vec3_t org, vec3_t fwd, int vol, int rad ) +{ + syscall( CG_FX_PLAY_EFFECT, file, org, fwd, vol, rad); +} + +void trap_FX_PlayEntityEffect( const char *file, vec3_t org, + vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ) +{ + syscall( CG_FX_PLAY_ENTITY_EFFECT, file, org, axis, boltInfo, entNum, vol, rad ); +} + +void trap_FX_PlayEffectID( int id, vec3_t org, vec3_t fwd, int vol, int rad ) +{ + syscall( CG_FX_PLAY_EFFECT_ID, id, org, fwd, vol, rad ); +} + +void trap_FX_PlayPortalEffectID( int id, vec3_t org, vec3_t fwd, int vol, int rad ) +{ + syscall( CG_FX_PLAY_PORTAL_EFFECT_ID, id, org, fwd); +} + +void trap_FX_PlayEntityEffectID( int id, vec3_t org, + vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ) +{ + syscall( CG_FX_PLAY_ENTITY_EFFECT_ID, id, org, axis, boltInfo, entNum, vol, rad ); +} + +void trap_FX_PlayBoltedEffectID( int id, vec3_t org, + void *ghoul2, const int boltNum, const int entNum, const int modelNum, int iLooptime, qboolean isRelative ) +{ + syscall( CG_FX_PLAY_BOLTED_EFFECT_ID, id, org, ghoul2, boltNum, entNum, modelNum, iLooptime, isRelative ); +} + +void trap_FX_AddScheduledEffects( qboolean skyPortal ) +{ + syscall( CG_FX_ADD_SCHEDULED_EFFECTS, skyPortal ); +} + +void trap_FX_Draw2DEffects ( float screenXScale, float screenYScale ) +{ + syscall( CG_FX_DRAW_2D_EFFECTS, PASSFLOAT(screenXScale), PASSFLOAT(screenYScale) ); +} + +int trap_FX_InitSystem( refdef_t* refdef ) +{ + return syscall( CG_FX_INIT_SYSTEM, refdef ); +} + +void trap_FX_SetRefDef( refdef_t* refdef ) +{ + syscall( CG_FX_SET_REFDEF, refdef ); +} + +qboolean trap_FX_FreeSystem( void ) +{ + return syscall( CG_FX_FREE_SYSTEM ); +} + +void trap_FX_Reset ( void ) +{ + syscall ( CG_FX_RESET ); +} + +void trap_FX_AdjustTime( int time ) +{ + syscall( CG_FX_ADJUST_TIME, time ); +} + + +void trap_FX_AddPoly( addpolyArgStruct_t *p ) +{ + syscall( CG_FX_ADDPOLY, p ); +} + +void trap_FX_AddBezier( addbezierArgStruct_t *p ) +{ + syscall( CG_FX_ADDBEZIER, p ); +} + +void trap_FX_AddPrimitive( effectTrailArgStruct_t *p ) +{ + syscall( CG_FX_ADDPRIMITIVE, p ); +} + +void trap_FX_AddSprite( addspriteArgStruct_t *p ) +{ + syscall( CG_FX_ADDSPRITE, p ); +} + +void trap_FX_AddElectricity( addElectricityArgStruct_t *p ) +{ + syscall( CG_FX_ADDELECTRICITY, p ); +} + +//void trap_SP_Print(const unsigned ID, byte *Data) +//{ +// syscall( CG_SP_PRINT, ID, Data); +//} + +int trap_SP_GetStringTextString(const char *text, char *buffer, int bufferLength) +{ + return syscall( CG_SP_GETSTRINGTEXTSTRING, text, buffer, bufferLength ); +} + +qboolean trap_ROFF_Clean( void ) +{ + return syscall( CG_ROFF_CLEAN ); +} + +void trap_ROFF_UpdateEntities( void ) +{ + syscall( CG_ROFF_UPDATE_ENTITIES ); +} + +int trap_ROFF_Cache( char *file ) +{ + return syscall( CG_ROFF_CACHE, file ); +} + +qboolean trap_ROFF_Play( int entID, int roffID, qboolean doTranslation ) +{ + return syscall( CG_ROFF_PLAY, entID, roffID, doTranslation ); +} + +qboolean trap_ROFF_Purge_Ent( int entID ) +{ + return syscall( CG_ROFF_PURGE_ENT, entID ); +} + + +//rww - dynamic vm memory allocation! +void trap_TrueMalloc(void **ptr, int size) +{ + syscall(CG_TRUEMALLOC, ptr, size); +} + +void trap_TrueFree(void **ptr) +{ + syscall(CG_TRUEFREE, ptr); +} + +/* +Ghoul2 Insert Start +*/ +// CG Specific API calls +void trap_G2_ListModelSurfaces(void *ghlInfo) +{ + syscall( CG_G2_LISTSURFACES, ghlInfo); +} + +void trap_G2_ListModelBones(void *ghlInfo, int frame) +{ + syscall( CG_G2_LISTBONES, ghlInfo, frame); +} + +void trap_G2_SetGhoul2ModelIndexes(void *ghoul2, qhandle_t *modelList, qhandle_t *skinList) +{ + syscall( CG_G2_SETMODELS, ghoul2, modelList, skinList); +} + +qboolean trap_G2_HaveWeGhoul2Models(void *ghoul2) +{ + return (qboolean)(syscall(CG_G2_HAVEWEGHOULMODELS, ghoul2)); +} + +qboolean trap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ + return (qboolean)(syscall(CG_G2_GETBOLT, ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale)); +} + +qboolean trap_G2API_GetBoltMatrix_NoReconstruct(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ //Same as above but force it to not reconstruct the skeleton before getting the bolt position + return (qboolean)(syscall(CG_G2_GETBOLT_NOREC, ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale)); +} + +qboolean trap_G2API_GetBoltMatrix_NoRecNoRot(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ //Same as above but force it to not reconstruct the skeleton before getting the bolt position + return (qboolean)(syscall(CG_G2_GETBOLT_NOREC_NOROT, ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale)); +} + +int trap_G2API_InitGhoul2Model(void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, + qhandle_t customShader, int modelFlags, int lodBias) +{ + return syscall(CG_G2_INITGHOUL2MODEL, ghoul2Ptr, fileName, modelIndex, customSkin, customShader, modelFlags, lodBias); +} + +qboolean trap_G2API_SetSkin(void *ghoul2, int modelIndex, qhandle_t customSkin, qhandle_t renderSkin) +{ + return syscall(CG_G2_SETSKIN, ghoul2, modelIndex, customSkin, renderSkin); +} + +void trap_G2API_CollisionDetect ( + CollisionRecord_t *collRecMap, + void* ghoul2, + const vec3_t angles, + const vec3_t position, + int frameNumber, + int entNum, + const vec3_t rayStart, + const vec3_t rayEnd, + const vec3_t scale, + int traceFlags, + int useLod, + float fRadius + ) +{ + syscall ( CG_G2_COLLISIONDETECT, collRecMap, ghoul2, angles, position, frameNumber, entNum, rayStart, rayEnd, scale, traceFlags, useLod, PASSFLOAT(fRadius) ); +} + +void trap_G2API_CleanGhoul2Models(void **ghoul2Ptr) +{ + syscall(CG_G2_CLEANMODELS, ghoul2Ptr); +} + +qboolean trap_G2API_SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ) +{ + return (syscall(CG_G2_ANGLEOVERRIDE, ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList, blendTime, currentTime)); +} + + +void trap_G2API_GetModelName(void *ghoul2, const int modelIndex, + const char **modelName) +{ + syscall(CG_G2_GETMODELNAME, ghoul2, modelIndex, modelName); +} + +qboolean trap_G2API_SetBoneAnim(void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame , const int blendTime ) +{ + return syscall(CG_G2_PLAYANIM, ghoul2, modelIndex, boneName, startFrame, endFrame, flags, PASSFLOAT(animSpeed), currentTime, PASSFLOAT(setFrame), blendTime); +} + +qboolean trap_G2API_GetBoneAnim(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList, const int modelIndex) +{ + return syscall(CG_G2_GETBONEANIM, ghoul2, boneName, currentTime, currentFrame, startFrame, endFrame, flags, animSpeed, modelList, modelIndex); +} + +qboolean trap_G2API_GetBoneFrame(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *modelList, const int modelIndex) +{ + return syscall(CG_G2_GETBONEFRAME, ghoul2, boneName, currentTime, currentFrame, modelList, modelIndex); +} + +void trap_G2API_GetGLAName(void *ghoul2, int modelIndex, char *fillBuf) +{ + syscall(CG_G2_GETGLANAME, ghoul2, modelIndex, fillBuf); +} + +int trap_G2API_CopyGhoul2Instance(void *g2From, void *g2To, int modelIndex) +{ + return syscall(CG_G2_COPYGHOUL2INSTANCE, g2From, g2To, modelIndex); +} + +void trap_G2API_CopySpecificGhoul2Model(void *g2From, int modelFrom, void *g2To, int modelTo) +{ + syscall(CG_G2_COPYSPECIFICGHOUL2MODEL, g2From, modelFrom, g2To, modelTo); +} + +void trap_G2API_DuplicateGhoul2Instance(void *g2From, void **g2To) +{ + syscall(CG_G2_DUPLICATEGHOUL2INSTANCE, g2From, g2To); +} + +qboolean trap_G2API_HasGhoul2ModelOnIndex(void *ghlInfo, int modelIndex) +{ + return syscall(CG_G2_HASGHOUL2MODELONINDEX, ghlInfo, modelIndex); +} + +qboolean trap_G2API_RemoveGhoul2Model(void *ghlInfo, int modelIndex) +{ + return syscall(CG_G2_REMOVEGHOUL2MODEL, ghlInfo, modelIndex); +} + +qboolean trap_G2API_SkinlessModel(void *ghlInfo, int modelIndex) +{ + return syscall(CG_G2_SKINLESSMODEL, ghlInfo, modelIndex); +} + +int trap_G2API_GetNumGoreMarks(void *ghlInfo, int modelIndex) +{ + return syscall(CG_G2_GETNUMGOREMARKS, ghlInfo, modelIndex); +} + +void trap_G2API_AddSkinGore(void *ghlInfo,SSkinGoreData *gore) +{ + syscall(CG_G2_ADDSKINGORE, ghlInfo, gore); +} + +void trap_G2API_ClearSkinGore ( void* ghlInfo ) +{ + syscall(CG_G2_CLEARSKINGORE, ghlInfo ); +} + +int trap_G2API_Ghoul2Size ( void* ghlInfo ) +{ + return syscall(CG_G2_SIZE, ghlInfo ); +} + +int trap_G2API_AddBolt(void *ghoul2, int modelIndex, const char *boneName) +{ + return syscall(CG_G2_ADDBOLT, ghoul2, modelIndex, boneName); +} + +qboolean trap_G2API_AttachEnt(int *boltInfo, void *ghlInfoTo, int toBoltIndex, int entNum, int toModelNum) +{ + return syscall(CG_G2_ATTACHENT, boltInfo, ghlInfoTo, toBoltIndex, entNum, toModelNum); +} + +void trap_G2API_SetBoltInfo(void *ghoul2, int modelIndex, int boltInfo) +{ + syscall(CG_G2_SETBOLTON, ghoul2, modelIndex, boltInfo); +} + +qboolean trap_G2API_SetRootSurface(void *ghoul2, const int modelIndex, const char *surfaceName) +{ + return syscall(CG_G2_SETROOTSURFACE, ghoul2, modelIndex, surfaceName); +} + +qboolean trap_G2API_SetSurfaceOnOff(void *ghoul2, const char *surfaceName, const int flags) +{ + return syscall(CG_G2_SETSURFACEONOFF, ghoul2, surfaceName, flags); +} + +qboolean trap_G2API_SetNewOrigin(void *ghoul2, const int boltIndex) +{ + return syscall(CG_G2_SETNEWORIGIN, ghoul2, boltIndex); +} + +//check if a bone exists on skeleton without actually adding to the bone list -rww +qboolean trap_G2API_DoesBoneExist(void *ghoul2, int modelIndex, const char *boneName) +{ + return syscall(CG_G2_DOESBONEEXIST, ghoul2, modelIndex, boneName); +} + +int trap_G2API_GetSurfaceRenderStatus(void *ghoul2, const int modelIndex, const char *surfaceName) +{ + return syscall(CG_G2_GETSURFACERENDERSTATUS, ghoul2, modelIndex, surfaceName); +} + +int trap_G2API_GetTime(void) +{ + return syscall(CG_G2_GETTIME); +} + +void trap_G2API_SetTime(int time, int clock) +{ + syscall(CG_G2_SETTIME, time, clock); +} + +//hack for smoothing during ugly situations. forgive me. +void trap_G2API_AbsurdSmoothing(void *ghoul2, qboolean status) +{ + syscall(CG_G2_ABSURDSMOOTHING, ghoul2, status); +} + +//rww - RAGDOLL_BEGIN +void trap_G2API_SetRagDoll(void *ghoul2, sharedRagDollParams_t *params) +{ + syscall(CG_G2_SETRAGDOLL, ghoul2, params); +} + +void trap_G2API_AnimateG2Models(void *ghoul2, int time, sharedRagDollUpdateParams_t *params) +{ + syscall(CG_G2_ANIMATEG2MODELS, ghoul2, time, params); +} +//rww - RAGDOLL_END + +//additional ragdoll options -rww +qboolean trap_G2API_RagPCJConstraint(void *ghoul2, const char *boneName, vec3_t min, vec3_t max) //override default pcj bonee constraints +{ + return syscall(CG_G2_RAGPCJCONSTRAINT, ghoul2, boneName, min, max); +} + +qboolean trap_G2API_RagPCJGradientSpeed(void *ghoul2, const char *boneName, const float speed) //override the default gradient movespeed for a pcj bone +{ + return syscall(CG_G2_RAGPCJGRADIENTSPEED, ghoul2, boneName, PASSFLOAT(speed)); +} + +qboolean trap_G2API_RagEffectorGoal(void *ghoul2, const char *boneName, vec3_t pos) //override an effector bone's goal position (world coordinates) +{ + return syscall(CG_G2_RAGEFFECTORGOAL, ghoul2, boneName, pos); +} + +qboolean trap_G2API_GetRagBonePos(void *ghoul2, const char *boneName, vec3_t pos, vec3_t entAngles, vec3_t entPos, vec3_t entScale) //current position of said bone is put into pos (world coordinates) +{ + return syscall(CG_G2_GETRAGBONEPOS, ghoul2, boneName, pos, entAngles, entPos, entScale); +} + +qboolean trap_G2API_RagEffectorKick(void *ghoul2, const char *boneName, vec3_t velocity) //add velocity to a rag bone +{ + return syscall(CG_G2_RAGEFFECTORKICK, ghoul2, boneName, velocity); +} + +qboolean trap_G2API_RagForceSolve(void *ghoul2, qboolean force) //make sure we are actively performing solve/settle routines, if desired +{ + return syscall(CG_G2_RAGFORCESOLVE, ghoul2, force); +} + +qboolean trap_G2API_SetBoneIKState(void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params) +{ + return syscall(CG_G2_SETBONEIKSTATE, ghoul2, time, boneName, ikState, params); +} + +qboolean trap_G2API_IKMove(void *ghoul2, int time, sharedIKMoveParams_t *params) +{ + return syscall(CG_G2_IKMOVE, ghoul2, time, params); +} + +qboolean trap_G2API_RemoveBone(void *ghoul2, const char *boneName, int modelIndex) +{ + return syscall(CG_G2_REMOVEBONE, ghoul2, boneName, modelIndex); +} + +//rww - Stuff to allow association of ghoul2 instances to entity numbers. +//This way, on listen servers when both the client and server are doing +//ghoul2 operations, we can copy relevant data off the client instance +//directly onto the server instance and slash the transforms and whatnot +//right in half. +void trap_G2API_AttachInstanceToEntNum(void *ghoul2, int entityNum, qboolean server) +{ + syscall(CG_G2_ATTACHINSTANCETOENTNUM, ghoul2, entityNum, server); +} + +void trap_G2API_ClearAttachedInstance(int entityNum) +{ + syscall(CG_G2_CLEARATTACHEDINSTANCE, entityNum); +} + +void trap_G2API_CleanEntAttachments(void) +{ + syscall(CG_G2_CLEANENTATTACHMENTS); +} + +qboolean trap_G2API_OverrideServer(void *serverInstance) +{ + return syscall(CG_G2_OVERRIDESERVER, serverInstance); +} + +void trap_G2API_GetSurfaceName(void *ghoul2, int surfNumber, int modelIndex, char *fillBuf) +{ + syscall(CG_G2_GETSURFACENAME, ghoul2, surfNumber, modelIndex, fillBuf); +} + +void trap_CG_RegisterSharedMemory(char *memory) +{ + syscall(CG_SET_SHARED_BUFFER, memory); +} + +int trap_CM_RegisterTerrain(const char *config) +{ //rwwRMG - added [NEWTRAP] + return syscall(CG_CM_REGISTER_TERRAIN, config); +} + +void trap_RMG_Init(int terrainID, const char *terrainInfo) +{ //rwwRMG - added [NEWTRAP] + syscall(CG_RMG_INIT, terrainID, terrainInfo); +} + +void trap_RE_InitRendererTerrain( const char *info ) +{ //rwwRMG - added [NEWTRAP] + syscall(CG_RE_INIT_RENDERER_TERRAIN, info); +} + +void trap_R_WeatherContentsOverride( int contents ) +{ //rwwRMG - added [NEWTRAP] + syscall(CG_R_WEATHER_CONTENTS_OVERRIDE, contents); +} + +void trap_R_WorldEffectCommand(const char *cmd) +{ + syscall(CG_R_WORLDEFFECTCOMMAND, cmd); +} + +void trap_WE_AddWeatherZone( const vec3_t mins, const vec3_t maxs ) +{ + syscall( CG_WE_ADDWEATHERZONE, mins, maxs ); +} + +/* +Ghoul2 Insert End +*/ + +#include "../namespace_end.h" diff --git a/codemp/cgame/cg_turret.c b/codemp/cgame/cg_turret.c new file mode 100644 index 0000000..88f3fef --- /dev/null +++ b/codemp/cgame/cg_turret.c @@ -0,0 +1,242 @@ +#include "cg_local.h" +#include "..\game\q_shared.h" +#include "..\ghoul2\g2.h" + +//rww - The turret is heavily dependant on bone angles. We can't happily set that on the server, so it is done client-only. + +void CreepToPosition(vec3_t ideal, vec3_t current) +{ + float max_degree_switch = 90; + int degrees_negative = 0; + int degrees_positive = 0; + int doNegative = 0; + + int angle_ideal; + int angle_current; + + angle_ideal = (int)ideal[YAW]; + angle_current = (int)current[YAW]; + + if (angle_ideal <= angle_current) + { + degrees_negative = (angle_current - angle_ideal); + + degrees_positive = (360 - angle_current) + angle_ideal; + } + else + { + degrees_negative = angle_current + (360 - angle_ideal); + + degrees_positive = (angle_ideal - angle_current); + } + + if (degrees_negative < degrees_positive) + { + doNegative = 1; + } + + if (doNegative) + { + current[YAW] -= max_degree_switch; + + if (current[YAW] < ideal[YAW] && (current[YAW]+(max_degree_switch*2)) >= ideal[YAW]) + { + current[YAW] = ideal[YAW]; + } + + if (current[YAW] < 0) + { + current[YAW] += 361; + } + } + else + { + current[YAW] += max_degree_switch; + + if (current[YAW] > ideal[YAW] && (current[YAW]-(max_degree_switch*2)) <= ideal[YAW]) + { + current[YAW] = ideal[YAW]; + } + + if (current[YAW] > 360) + { + current[YAW] -= 361; + } + } + + if (ideal[PITCH] < 0) + { + ideal[PITCH] += 360; + } + + angle_ideal = (int)ideal[PITCH]; + angle_current = (int)current[PITCH]; + + doNegative = 0; + + if (angle_ideal <= angle_current) + { + degrees_negative = (angle_current - angle_ideal); + + degrees_positive = (360 - angle_current) + angle_ideal; + } + else + { + degrees_negative = angle_current + (360 - angle_ideal); + + degrees_positive = (angle_ideal - angle_current); + } + + if (degrees_negative < degrees_positive) + { + doNegative = 1; + } + + if (doNegative) + { + current[PITCH] -= max_degree_switch; + + if (current[PITCH] < ideal[PITCH] && (current[PITCH]+(max_degree_switch*2)) >= ideal[PITCH]) + { + current[PITCH] = ideal[PITCH]; + } + + if (current[PITCH] < 0) + { + current[PITCH] += 361; + } + } + else + { + current[PITCH] += max_degree_switch; + + if (current[PITCH] > ideal[PITCH] && (current[PITCH]-(max_degree_switch*2)) <= ideal[PITCH]) + { + current[PITCH] = ideal[PITCH]; + } + + if (current[PITCH] > 360) + { + current[PITCH] -= 361; + } + } +} + +void TurretClientRun(centity_t *ent) +{ + if (!ent->ghoul2) + { + weaponInfo_t *weaponInfo; + + trap_G2API_InitGhoul2Model(&ent->ghoul2, CG_ConfigString( CS_MODELS+ent->currentState.modelindex ), 0, 0, 0, 0, 0); + + if (!ent->ghoul2) + { //bad + return; + } + + ent->torsoBolt = trap_G2API_AddBolt( ent->ghoul2, 0, "*flash02" ); + + trap_G2API_SetBoneAngles( ent->ghoul2, 0, "bone_hinge", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, cg->time ); + trap_G2API_SetBoneAngles( ent->ghoul2, 0, "bone_gback", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, cg->time ); + trap_G2API_SetBoneAngles( ent->ghoul2, 0, "bone_barrel", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, cg->time ); + + trap_G2API_SetBoneAnim( ent->ghoul2, 0, "model_root", 0, 11, BONE_ANIM_OVERRIDE_FREEZE, 0.8f, cg->time, 0, 0 ); + + ent->turAngles[ROLL] = 0; + ent->turAngles[PITCH] = 90; + ent->turAngles[YAW] = 0; + + weaponInfo = &cg_weapons[WP_TURRET]; + + if ( !weaponInfo->registered ) + { + CG_RegisterWeapon(WP_TURRET); + } + } + + if (ent->currentState.fireflag == 2) + { //I'm about to blow + if (ent->turAngles) + { + trap_G2API_SetBoneAngles( ent->ghoul2, 0, "bone_hinge", ent->turAngles, BONE_ANGLES_REPLACE, NEGATIVE_Y, NEGATIVE_Z, NEGATIVE_X, NULL, 100, cg->time ); + } + return; + } + else if (ent->currentState.fireflag && ent->bolt4 != ent->currentState.fireflag) + { + vec3_t muzzleOrg, muzzleDir; + mdxaBone_t boltMatrix; + + trap_G2API_GetBoltMatrix(ent->ghoul2, 0, ent->torsoBolt, &boltMatrix, /*ent->lerpAngles*/vec3_origin, ent->lerpOrigin, cg->time, cgs.gameModels, ent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, muzzleOrg); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, muzzleDir); + + trap_FX_PlayEffectID(cgs.effects.mTurretMuzzleFlash, muzzleOrg, muzzleDir, -1, -1); + + ent->bolt4 = ent->currentState.fireflag; + } + else if (!ent->currentState.fireflag) + { + ent->bolt4 = 0; + } + + if (ent->currentState.bolt2 != ENTITYNUM_NONE) + { //turn toward the enemy + centity_t *enemy = &cg_entities[ent->currentState.bolt2]; + + if (enemy) + { + vec3_t enAng; + vec3_t enPos; + + VectorCopy(enemy->currentState.pos.trBase, enPos); + + VectorSubtract(enPos, ent->lerpOrigin, enAng); + VectorNormalize(enAng); + vectoangles(enAng, enAng); + enAng[ROLL] = 0; + enAng[PITCH] += 90; + + CreepToPosition(enAng, ent->turAngles); + } + } + else + { + vec3_t idleAng; + float turnAmount; + + if (ent->turAngles[YAW] > 360) + { + ent->turAngles[YAW] -= 361; + } + + if (!ent->dustTrailTime) + { + ent->dustTrailTime = cg->time; + } + + turnAmount = (cg->time-ent->dustTrailTime)*0.03; + + if (turnAmount > 360) + { + turnAmount = 360; + } + + idleAng[PITCH] = 90; + idleAng[ROLL] = 0; + idleAng[YAW] = ent->turAngles[YAW] + turnAmount; + ent->dustTrailTime = cg->time; + + CreepToPosition(idleAng, ent->turAngles); + } + + if (cg->time < ent->frame_minus1_refreshed) + { + ent->frame_minus1_refreshed = cg->time; + return; + } + + ent->frame_minus1_refreshed = cg->time; + trap_G2API_SetBoneAngles( ent->ghoul2, 0, "bone_hinge", ent->turAngles, BONE_ANGLES_REPLACE, NEGATIVE_Y, NEGATIVE_Z, NEGATIVE_X, NULL, 100, cg->time ); +} diff --git a/codemp/cgame/cg_view.c b/codemp/cgame/cg_view.c new file mode 100644 index 0000000..a88d191 --- /dev/null +++ b/codemp/cgame/cg_view.c @@ -0,0 +1,3454 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_view.c -- setup all the parameters (position, angle, etc) +// for a 3D rendering +#include "cg_local.h" + +#include "bg_saga.h" + +#if !defined(CL_LIGHT_H_INC) + #include "cg_lights.h" +#endif + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + +#define MASK_CAMERACLIP (MASK_SOLID|CONTENTS_PLAYERCLIP) +#define CAMERA_SIZE 4 + + +/* +============================================================================= + + MODEL TESTING + +The viewthing and gun positioning tools from Q2 have been integrated and +enhanced into a single model testing facility. + +Model viewing can begin with either "testmodel " or "testgun ". + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + +Testgun will cause the model to follow the player around and supress the real +view weapon model. The default frame 0 of most guns is completely off screen, +so you will probably have to cycle a couple frames to see it. + +"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the +frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in +q3default.cfg. + +If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let +you adjust the positioning. + +Note that none of the model testing features update while the game is paused, so +it may be convenient to test with deathmatch set to 1 so that bringing down the +console doesn't pause the game. + +============================================================================= +*/ + +/* +================= +CG_TestModel_f + +Creates an entity in front of the current position, which +can then be moved around +================= +*/ +void CG_TestModel_f (void) { + vec3_t angles; + + memset( &cg->testModelEntity, 0, sizeof(cg->testModelEntity) ); + if ( trap_Argc() < 2 ) { + return; + } + + Q_strncpyz (cg->testModelName, CG_Argv( 1 ), MAX_QPATH ); + cg->testModelEntity.hModel = trap_R_RegisterModel( cg->testModelName ); + + if ( trap_Argc() == 3 ) { + cg->testModelEntity.backlerp = atof( CG_Argv( 2 ) ); + cg->testModelEntity.frame = 1; + cg->testModelEntity.oldframe = 0; + } + if (! cg->testModelEntity.hModel ) { + CG_Printf( "Can't register model\n" ); + return; + } + + VectorMA( cg->refdef.vieworg, 100, cg->refdef.viewaxis[0], cg->testModelEntity.origin ); + + angles[PITCH] = 0; + angles[YAW] = 180 + cg->refdef.viewangles[1]; + angles[ROLL] = 0; + + AnglesToAxis( angles, cg->testModelEntity.axis ); + cg->testGun = qfalse; +} + +/* +================= +CG_TestGun_f + +Replaces the current view weapon with the given model +================= +*/ +void CG_TestGun_f (void) { + CG_TestModel_f(); + cg->testGun = qtrue; + //cg->testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON; + + // rww - 9-13-01 [1-26-01-sof2] + cg->testModelEntity.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON; +} + + +void CG_TestModelNextFrame_f (void) { + cg->testModelEntity.frame++; + CG_Printf( "frame %i\n", cg->testModelEntity.frame ); +} + +void CG_TestModelPrevFrame_f (void) { + cg->testModelEntity.frame--; + if ( cg->testModelEntity.frame < 0 ) { + cg->testModelEntity.frame = 0; + } + CG_Printf( "frame %i\n", cg->testModelEntity.frame ); +} + +void CG_TestModelNextSkin_f (void) { + cg->testModelEntity.skinNum++; + CG_Printf( "skin %i\n", cg->testModelEntity.skinNum ); +} + +void CG_TestModelPrevSkin_f (void) { + cg->testModelEntity.skinNum--; + if ( cg->testModelEntity.skinNum < 0 ) { + cg->testModelEntity.skinNum = 0; + } + CG_Printf( "skin %i\n", cg->testModelEntity.skinNum ); +} + +static void CG_AddTestModel (void) { + int i; + + // re-register the model, because the level may have changed + cg->testModelEntity.hModel = trap_R_RegisterModel( cg->testModelName ); + if (! cg->testModelEntity.hModel ) { + CG_Printf ("Can't register model\n"); + return; + } + + // if testing a gun, set the origin reletive to the view origin + if ( cg->testGun ) { + VectorCopy( cg->refdef.vieworg, cg->testModelEntity.origin ); + VectorCopy( cg->refdef.viewaxis[0], cg->testModelEntity.axis[0] ); + VectorCopy( cg->refdef.viewaxis[1], cg->testModelEntity.axis[1] ); + VectorCopy( cg->refdef.viewaxis[2], cg->testModelEntity.axis[2] ); + + // allow the position to be adjusted + for (i=0 ; i<3 ; i++) { + cg->testModelEntity.origin[i] += cg->refdef.viewaxis[0][i] * cg_gun_x.value; + cg->testModelEntity.origin[i] += cg->refdef.viewaxis[1][i] * cg_gun_y.value; + cg->testModelEntity.origin[i] += cg->refdef.viewaxis[2][i] * cg_gun_z.value; + } + } + + trap_R_AddRefEntityToScene( &cg->testModelEntity ); +} + + + +//============================================================================ + + +/* +================= +CG_CalcVrect + +Sets the coordinates of the rendered window +================= +*/ +#ifdef _XBOX +extern int glcfgX, glcfgY; +#endif +static void CG_CalcVrect (void) { + int size; + + // the intermission should allways be full screen + if ( cg->snap->ps.pm_type == PM_INTERMISSION ) { + size = 100; + } else { + // bound normal viewsize + if (cg_viewsize.integer < 30) { + trap_Cvar_Set ("cg_viewsize","30"); + size = 30; + } else if (cg_viewsize.integer > 100) { + trap_Cvar_Set ("cg_viewsize","100"); + size = 100; + } else { + size = cg_viewsize.integer; + } + + } + cg->refdef.width = cgs.glconfig.vidWidth*size/100; + cg->refdef.width &= ~1; + + cg->refdef.height = cgs.glconfig.vidHeight*size/100; + cg->refdef.height &= ~1; + +#ifdef _XBOX + cg->refdef.x = glcfgX + (cgs.glconfig.vidWidth - cg->refdef.width)/2; + cg->refdef.y = glcfgY + (cgs.glconfig.vidHeight - cg->refdef.height)/2; +#else + cg->refdef.x = (cgs.glconfig.vidWidth - cg->refdef.width)/2; + cg->refdef.y = (cgs.glconfig.vidHeight - cg->refdef.height)/2; +#endif +} + +//============================================================================== + +//============================================================================== +//============================================================================== +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) { + int timeDelta; + + // smooth out stair climbing + timeDelta = cg->time - cg->stepTime; + if ( timeDelta < STEP_TIME ) { + cg->refdef.vieworg[2] -= cg->stepChange + * (STEP_TIME - timeDelta) / STEP_TIME; + } +} + +#define CAMERA_DAMP_INTERVAL 50 + +static vec3_t cameramins = { -CAMERA_SIZE, -CAMERA_SIZE, -CAMERA_SIZE }; +static vec3_t cameramaxs = { CAMERA_SIZE, CAMERA_SIZE, CAMERA_SIZE }; +vec3_t camerafwd, cameraup; + +#ifndef _XBOX +vec3_t cameraFocusAngles, cameraFocusLoc; +vec3_t cameraIdealTarget, cameraIdealLoc; +vec3_t cameraCurTarget={0,0,0}, cameraCurLoc={0,0,0}; +vec3_t cameraOldLoc={0,0,0}, cameraNewLoc={0,0,0}; +int cameraLastFrame=0; + +float cameraLastYaw=0; +float cameraStiffFactor=0.0f; +#endif + +/* +=============== +Notes on the camera viewpoint in and out... + +cg->refdef.vieworg +--at the start of the function holds the player actor's origin (center of player model). +--it is set to the final view location of the camera at the end of the camera code. +cg->refdef.viewangles +--at the start holds the client's view angles +--it is set to the final view angle of the camera at the end of the camera code. + +=============== +*/ + +#ifdef _XBOX +extern qboolean gCGHasFallVector[2]; +extern vec3_t gCGFallVector[2]; +#else +extern qboolean gCGHasFallVector; +extern vec3_t gCGFallVector; +#endif + +/* +=============== +CG_CalcTargetThirdPersonViewLocation + +=============== +*/ +static void CG_CalcIdealThirdPersonViewTarget(void) +{ + // Initialize IdealTarget +#ifdef _XBOX + if (gCGHasFallVector[ClientManager::ActiveClientNum()]) + { + VectorCopy(gCGFallVector[ClientManager::ActiveClientNum()], ClientManager::ActiveClient().cameraFocusLoc); + } + else + { + VectorCopy(cg->refdef.vieworg, ClientManager::ActiveClient().cameraFocusLoc); + } + + // Add in the new viewheight + ClientManager::ActiveClient().cameraFocusLoc[2] += cg->snap->ps.viewheight; + + // Add in a vertical offset from the viewpoint, which puts the actual target above the head, regardless of angle. + VectorCopy( ClientManager::ActiveClient().cameraFocusLoc, ClientManager::ActiveClient().cameraIdealTarget ); +#else + if (gCGHasFallVector) + { + VectorCopy(gCGFallVector, cameraFocusLoc); + } + else + { + VectorCopy(cg->refdef.vieworg, cameraFocusLoc); + } + + // Add in the new viewheight + cameraFocusLoc[2] += cg->snap->ps.viewheight; + + // Add in a vertical offset from the viewpoint, which puts the actual target above the head, regardless of angle. +// VectorMA(cameraFocusLoc, thirdPersonVertOffset, cameraup, cameraIdealTarget); + + // Add in a vertical offset from the viewpoint, which puts the actual target above the head, regardless of angle. + VectorCopy( cameraFocusLoc, cameraIdealTarget ); +#endif + + { + float vertOffset = cg_thirdPersonVertOffset.value; +#ifdef _XBOX + vertOffset = ClientManager::ActiveClient().cg_thirdPersonVertOffset; +#endif + + if (cg->snap && cg->snap->ps.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg->snap->ps.m_iVehicleNum]; + if (veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride) + { //override the range with what the vehicle wants it to be + if ( veh->m_pVehicle->m_pVehicleInfo->cameraPitchDependantVertOffset ) + { + if ( cg->snap->ps.viewangles[PITCH] > 0 ) + { + vertOffset = 130+cg->predictedPlayerState.viewangles[PITCH]*-10; + if ( vertOffset < -170 ) + { + vertOffset = -170; + } + } + else if ( cg->snap->ps.viewangles[PITCH] < 0 ) + { + vertOffset = 130+cg->predictedPlayerState.viewangles[PITCH]*-5; + if ( vertOffset > 130 ) + { + vertOffset = 130; + } + } + else + { + vertOffset = 30; + } + } + else + { + vertOffset = veh->m_pVehicle->m_pVehicleInfo->cameraVertOffset; + } + } + else if ( veh->m_pVehicle + && veh->m_pVehicle->m_pVehicleInfo + && veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL ) + { + vertOffset = 0; + } + } +#ifdef _XBOX + ClientManager::ActiveClient().cameraIdealTarget[2] += vertOffset; +#else + cameraIdealTarget[2] += vertOffset; +#endif + } + //VectorMA(cameraFocusLoc, cg_thirdPersonVertOffset.value, cameraup, cameraIdealTarget); +} + + + +/* +=============== +CG_CalcTargetThirdPersonViewLocation + +=============== +*/ +static void CG_CalcIdealThirdPersonViewLocation(void) +{ + float thirdPersonRange = cg_thirdPersonRange.value; + +#ifdef _XBOX + thirdPersonRange = (float)ClientManager::ActiveClient().cg_thirdPersonRange; +#endif + + if (cg->snap && cg->snap->ps.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg->snap->ps.m_iVehicleNum]; + if (veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride) + { //override the range with what the vehicle wants it to be + thirdPersonRange = veh->m_pVehicle->m_pVehicleInfo->cameraRange; + if ( veh->playerState->hackingTime ) + { + thirdPersonRange += fabs(((float)veh->playerState->hackingTime)/MAX_STRAFE_TIME) * 100.0f; + } + } + } + + if ( cg->snap + && (cg->snap->ps.eFlags2&EF2_HELD_BY_MONSTER) + && cg->snap->ps.hasLookTarget + && cg_entities[cg->snap->ps.lookTarget].currentState.NPC_class == CLASS_RANCOR )//only possibility for now, may add Wampa and sand creature later + {//stay back + //thirdPersonRange = 180.0f; + thirdPersonRange = 120.0f; + } + +#ifdef _XBOX + VectorMA(ClientManager::ActiveClient().cameraIdealTarget, -(thirdPersonRange), camerafwd, ClientManager::ActiveClient().cameraIdealLoc); +#else + VectorMA(cameraIdealTarget, -(thirdPersonRange), camerafwd, cameraIdealLoc); +#endif +} + + +#ifdef _XBOX +static void CG_ResetThirdPersonViewDamp(void) +{ + trace_t trace; + + // Cap the pitch within reasonable limits + if (ClientManager::ActiveClient().cameraFocusAngles[PITCH] > 89.0) + { + ClientManager::ActiveClient().cameraFocusAngles[PITCH] = 89.0; + } + else if (ClientManager::ActiveClient().cameraFocusAngles[PITCH] < -89.0) + { + ClientManager::ActiveClient().cameraFocusAngles[PITCH] = -89.0; + } + + AngleVectors(ClientManager::ActiveClient().cameraFocusAngles, camerafwd, NULL, cameraup); + + // Set the cameraIdealTarget + CG_CalcIdealThirdPersonViewTarget(); + + // Set the cameraIdealLoc + CG_CalcIdealThirdPersonViewLocation(); + + // Now, we just set everything to the new positions. + VectorCopy(ClientManager::ActiveClient().cameraIdealTarget, ClientManager::ActiveClient().cameraCurTarget); + VectorCopy(ClientManager::ActiveClient().cameraIdealLoc, ClientManager::ActiveClient().cameraCurLoc); + + // First thing we do is trace from the first person viewpoint out to the new target location. + CG_Trace(&trace, ClientManager::ActiveClient().cameraFocusLoc, cameramins, cameramaxs, ClientManager::ActiveClient().cameraCurTarget, cg->snap->ps.clientNum, MASK_CAMERACLIP); + if (trace.fraction <= 1.0) + { + VectorCopy(trace.endpos, ClientManager::ActiveClient().cameraCurTarget); + } + + // Now we trace from the new target location to the new view location, to make sure there is nothing in the way. + CG_Trace(&trace, ClientManager::ActiveClient().cameraCurTarget, cameramins, cameramaxs, ClientManager::ActiveClient().cameraCurLoc, cg->snap->ps.clientNum, MASK_CAMERACLIP); + if (trace.fraction <= 1.0) + { + VectorCopy(trace.endpos, ClientManager::ActiveClient().cameraCurLoc); + } + + ClientManager::ActiveClient().cameraLastFrame = cg->time; + ClientManager::ActiveClient().cameraLastYaw = ClientManager::ActiveClient().cameraFocusAngles[YAW]; + ClientManager::ActiveClient().cameraStiffFactor = 0.0f; +} + +#else // _XBOX + +static void CG_ResetThirdPersonViewDamp(void) +{ + trace_t trace; + + // Cap the pitch within reasonable limits + if (cameraFocusAngles[PITCH] > 89.0) + { + cameraFocusAngles[PITCH] = 89.0; + } + else if (cameraFocusAngles[PITCH] < -89.0) + { + cameraFocusAngles[PITCH] = -89.0; + } + + AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup); + + // Set the cameraIdealTarget + CG_CalcIdealThirdPersonViewTarget(); + + // Set the cameraIdealLoc + CG_CalcIdealThirdPersonViewLocation(); + + // Now, we just set everything to the new positions. + VectorCopy(cameraIdealTarget, cameraCurTarget); + VectorCopy(cameraIdealLoc, cameraCurLoc); + + // First thing we do is trace from the first person viewpoint out to the new target location. + CG_Trace(&trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, cg->snap->ps.clientNum, MASK_CAMERACLIP); + if (trace.fraction <= 1.0) + { + VectorCopy(trace.endpos, cameraCurTarget); + } + + // Now we trace from the new target location to the new view location, to make sure there is nothing in the way. + CG_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, cg->snap->ps.clientNum, MASK_CAMERACLIP); + if (trace.fraction <= 1.0) + { + VectorCopy(trace.endpos, cameraCurLoc); + } + + cameraLastYaw = cameraFocusAngles[YAW]; + cameraLastFrame = cg->time; + cameraStiffFactor = 0.0f; +} +#endif // _XBOX + +#ifdef _XBOX +static void CG_UpdateThirdPersonTargetDamp(void) +{ + trace_t trace; + vec3_t targetdiff; + float dampfactor, dtime, ratio; + + // Set the cameraIdealTarget + // Automatically get the ideal target, to avoid jittering. + CG_CalcIdealThirdPersonViewTarget(); + + if ( cg->predictedVehicleState.hyperSpaceTime + && (cg->time-cg->predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME ) + {//hyperspacing, no damp + VectorCopy(ClientManager::ActiveClient().cameraIdealTarget, ClientManager::ActiveClient().cameraCurTarget); + } + else if (ClientManager::ActiveClient().cg_thirdPersonTargetDamp >=1.0||cg->thisFrameTeleport||cg->predictedPlayerState.m_iVehicleNum) + { // No damping. + VectorCopy(ClientManager::ActiveClient().cameraIdealTarget, ClientManager::ActiveClient().cameraCurTarget); + } + else if (ClientManager::ActiveClient().cg_thirdPersonTargetDamp >= 0.0) + { + // Calculate the difference from the current position to the new one. + VectorSubtract(ClientManager::ActiveClient().cameraIdealTarget, ClientManager::ActiveClient().cameraCurTarget, targetdiff); + + // Now we calculate how much of the difference we cover in the time allotted. + // The equation is (Damp)^(time) + dampfactor = 1.0 - ClientManager::ActiveClient().cg_thirdPersonTargetDamp; + dtime = (float)(cg->time-ClientManager::ActiveClient().cameraLastFrame) * (1.0/(float)CAMERA_DAMP_INTERVAL); + + // Note that since there are a finite number of "practical" delta millisecond values possible, + // the ratio should be initialized into a chart ultimately. + ratio = powf(dampfactor, dtime); + + // This value is how much distance is "left" from the ideal. + VectorMA(ClientManager::ActiveClient().cameraIdealTarget, -ratio, targetdiff, ClientManager::ActiveClient().cameraCurTarget); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + } + + // Now we trace to see if the new location is cool or not. + + // First thing we do is trace from the first person viewpoint out to the new target location. + CG_Trace(&trace, ClientManager::ActiveClient().cameraFocusLoc, cameramins, cameramaxs, ClientManager::ActiveClient().cameraCurTarget, cg->snap->ps.clientNum, MASK_CAMERACLIP); + if (trace.fraction < 1.0) + { + VectorCopy(trace.endpos, ClientManager::ActiveClient().cameraCurTarget); + } + + // Note that previously there was an upper limit to the number of physics traces that are done through the world + // for the sake of camera collision, since it wasn't calced per frame. Now it is calculated every frame. + // This has the benefit that the camera is a lot smoother now (before it lerped between tested points), + // however two full volume traces each frame is a bit scary to think about. +} + +#else // _XBOX + +// This is called every frame. +static void CG_UpdateThirdPersonTargetDamp(void) +{ + trace_t trace; + vec3_t targetdiff; + float dampfactor, dtime, ratio; + + // Set the cameraIdealTarget + // Automatically get the ideal target, to avoid jittering. + CG_CalcIdealThirdPersonViewTarget(); + + if ( cg->predictedVehicleState.hyperSpaceTime + && (cg->time-cg->predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME ) + {//hyperspacing, no damp + VectorCopy(cameraIdealTarget, cameraCurTarget); + } + else if (cg_thirdPersonTargetDamp.value>=1.0||cg->thisFrameTeleport||cg->predictedPlayerState.m_iVehicleNum) + { // No damping. + VectorCopy(cameraIdealTarget, cameraCurTarget); + } + else if (cg_thirdPersonTargetDamp.value>=0.0) + { + // Calculate the difference from the current position to the new one. + VectorSubtract(cameraIdealTarget, cameraCurTarget, targetdiff); + + // Now we calculate how much of the difference we cover in the time allotted. + // The equation is (Damp)^(time) + dampfactor = 1.0-cg_thirdPersonTargetDamp.value; // We must exponent the amount LEFT rather than the amount bled off + dtime = (float)(cg->time-cameraLastFrame) * (1.0/(float)CAMERA_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1". + + // Note that since there are a finite number of "practical" delta millisecond values possible, + // the ratio should be initialized into a chart ultimately. + ratio = powf(dampfactor, dtime); + + // This value is how much distance is "left" from the ideal. + VectorMA(cameraIdealTarget, -ratio, targetdiff, cameraCurTarget); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + } + + // Now we trace to see if the new location is cool or not. + + // First thing we do is trace from the first person viewpoint out to the new target location. + CG_Trace(&trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, cg->snap->ps.clientNum, MASK_CAMERACLIP); + if (trace.fraction < 1.0) + { + VectorCopy(trace.endpos, cameraCurTarget); + } + + // Note that previously there was an upper limit to the number of physics traces that are done through the world + // for the sake of camera collision, since it wasn't calced per frame. Now it is calculated every frame. + // This has the benefit that the camera is a lot smoother now (before it lerped between tested points), + // however two full volume traces each frame is a bit scary to think about. +} + +#endif // _XBOX + +// This can be called every interval, at the user's discretion. +extern void CG_CalcEntityLerpPositions( centity_t *cent ); //cg_ents.c + +#ifdef _XBOX +static void CG_UpdateThirdPersonCameraDamp(void) +{ + trace_t trace; + vec3_t locdiff; + float dampfactor, dtime, ratio; + + // Set the cameraIdealLoc + CG_CalcIdealThirdPersonViewLocation(); + + + // First thing we do is calculate the appropriate damping factor for the camera. + dampfactor=0.0; + if ( cg->predictedVehicleState.hyperSpaceTime + && (cg->time-cg->predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME ) + {//hyperspacing - don't damp camera + dampfactor = 1.0f; + } + else if (ClientManager::ActiveClient().cg_thirdPersonCameraDamp != 0.0) + { + float pitch; + float dFactor; + + if (!cg->predictedPlayerState.m_iVehicleNum) + { + dFactor = ClientManager::ActiveClient().cg_thirdPersonCameraDamp; + } + else + { + dFactor = 1.0f; + } + + // Note that the camera pitch has already been capped off to 89. + pitch = Q_fabs(ClientManager::ActiveClient().cameraFocusAngles[PITCH]); + + // The higher the pitch, the larger the factor, so as you look up, it damps a lot less. + pitch /= 115.0; + dampfactor = (1.0-dFactor)*(pitch*pitch); + + dampfactor += dFactor; + + // Now we also multiply in the stiff factor, so that faster yaw changes are stiffer. + if (ClientManager::ActiveClient().cameraStiffFactor > 0.0f) + { // The cameraStiffFactor is how much of the remaining damp below 1 should be shaved off, i.e. approach 1 as stiffening increases. + dampfactor += (1.0-dampfactor)*ClientManager::ActiveClient().cameraStiffFactor; + } + } + + if (dampfactor>=1.0||cg->thisFrameTeleport) + { // No damping. + VectorCopy(ClientManager::ActiveClient().cameraIdealLoc, ClientManager::ActiveClient().cameraCurLoc); + } + else if (dampfactor>=0.0) + { + // Calculate the difference from the current position to the new one. + VectorSubtract(ClientManager::ActiveClient().cameraIdealLoc, ClientManager::ActiveClient().cameraCurLoc, locdiff); + + // Now we calculate how much of the difference we cover in the time allotted. + // The equation is (Damp)^(time) + dampfactor = 1.0-dampfactor; // We must exponent the amount LEFT rather than the amount bled off + + dtime = (float)(cg->time-ClientManager::ActiveClient().cameraLastFrame) * (1.0/(float)CAMERA_DAMP_INTERVAL); + + // Note that since there are a finite number of "practical" delta millisecond values possible, + // the ratio should be initialized into a chart ultimately. + ratio = powf(dampfactor, dtime); + + // This value is how much distance is "left" from the ideal. + VectorMA(ClientManager::ActiveClient().cameraIdealLoc, -ratio, locdiff, ClientManager::ActiveClient().cameraCurLoc); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + } + + // Now we trace from the new target location to the new view location, to make sure there is nothing in the way. + CG_Trace(&trace, ClientManager::ActiveClient().cameraCurTarget, cameramins, cameramaxs, ClientManager::ActiveClient().cameraCurLoc, cg->snap->ps.clientNum, MASK_CAMERACLIP); + + if (trace.fraction < 1.0) + { + if (trace.entityNum < ENTITYNUM_WORLD && + cg_entities[trace.entityNum].currentState.solid == SOLID_BMODEL && + cg_entities[trace.entityNum].currentState.eType == ET_MOVER) + { //get a different position for movers -rww + centity_t *mover = &cg_entities[trace.entityNum]; + + //this is absolutely hackiful, since we calc view values before we add packet ents and lerp, + //if we hit a mover we want to update its lerp pos and force it when we do the trace against + //it. + if (mover->currentState.pos.trType != TR_STATIONARY && + mover->currentState.pos.trType != TR_LINEAR) + { + int curTr = mover->currentState.pos.trType; + vec3_t curTrB; + + VectorCopy(mover->currentState.pos.trBase, curTrB); + + //calc lerporigin for this client frame + CG_CalcEntityLerpPositions(mover); + + //force the calc'd lerp to be the base and say we are stationary so we don't try to extrapolate + //out further. + mover->currentState.pos.trType = TR_STATIONARY; + VectorCopy(mover->lerpOrigin, mover->currentState.pos.trBase); + + //retrace + CG_Trace(&trace, ClientManager::ActiveClient().cameraCurTarget, cameramins, cameramaxs, ClientManager::ActiveClient().cameraCurLoc, cg->snap->ps.clientNum, MASK_CAMERACLIP); + + //copy old data back in + mover->currentState.pos.trType = (trType_t) curTr; + VectorCopy(curTrB, mover->currentState.pos.trBase); + } + if (trace.fraction < 1.0f) + { //still hit it, so take the proper trace endpos and use that. + VectorCopy(trace.endpos, ClientManager::ActiveClient().cameraCurLoc); + } + } + else + { + VectorCopy(trace.endpos, ClientManager::ActiveClient().cameraCurLoc); + } + } + + // Note that previously there was an upper limit to the number of physics traces that are done through the world + // for the sake of camera collision, since it wasn't calced per frame. Now it is calculated every frame. + // This has the benefit that the camera is a lot smoother now (before it lerped between tested points), + // however two full volume traces each frame is a bit scary to think about. +} + +#else // _XBOX + +static void CG_UpdateThirdPersonCameraDamp(void) +{ + trace_t trace; + vec3_t locdiff; + float dampfactor, dtime, ratio; + + // Set the cameraIdealLoc + CG_CalcIdealThirdPersonViewLocation(); + + + // First thing we do is calculate the appropriate damping factor for the camera. + dampfactor=0.0; + if ( cg->predictedVehicleState.hyperSpaceTime + && (cg->time-cg->predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME ) + {//hyperspacing - don't damp camera + dampfactor = 1.0f; + } + else if (cg_thirdPersonCameraDamp.value != 0.0) + { + float pitch; + float dFactor; + + if (!cg->predictedPlayerState.m_iVehicleNum) + { + dFactor = cg_thirdPersonCameraDamp.value; + } + else + { + dFactor = 1.0f; + } + + // Note that the camera pitch has already been capped off to 89. + pitch = Q_fabs(cameraFocusAngles[PITCH]); + + // The higher the pitch, the larger the factor, so as you look up, it damps a lot less. + pitch /= 115.0; + dampfactor = (1.0-dFactor)*(pitch*pitch); + + dampfactor += dFactor; + + // Now we also multiply in the stiff factor, so that faster yaw changes are stiffer. + if (cameraStiffFactor > 0.0f) + { // The cameraStiffFactor is how much of the remaining damp below 1 should be shaved off, i.e. approach 1 as stiffening increases. + dampfactor += (1.0-dampfactor)*cameraStiffFactor; + } + } + + if (dampfactor>=1.0||cg->thisFrameTeleport) + { // No damping. + VectorCopy(cameraIdealLoc, cameraCurLoc); + } + else if (dampfactor>=0.0) + { + // Calculate the difference from the current position to the new one. + VectorSubtract(cameraIdealLoc, cameraCurLoc, locdiff); + + // Now we calculate how much of the difference we cover in the time allotted. + // The equation is (Damp)^(time) + dampfactor = 1.0-dampfactor; // We must exponent the amount LEFT rather than the amount bled off + dtime = (float)(cg->time-cameraLastFrame) * (1.0/(float)CAMERA_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1". + + // Note that since there are a finite number of "practical" delta millisecond values possible, + // the ratio should be initialized into a chart ultimately. + ratio = powf(dampfactor, dtime); + + // This value is how much distance is "left" from the ideal. + VectorMA(cameraIdealLoc, -ratio, locdiff, cameraCurLoc); + + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + } + + // Now we trace from the new target location to the new view location, to make sure there is nothing in the way. + CG_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, cg->snap->ps.clientNum, MASK_CAMERACLIP); + + if (trace.fraction < 1.0) + { + if (trace.entityNum < ENTITYNUM_WORLD && + cg_entities[trace.entityNum].currentState.solid == SOLID_BMODEL && + cg_entities[trace.entityNum].currentState.eType == ET_MOVER) + { //get a different position for movers -rww + centity_t *mover = &cg_entities[trace.entityNum]; + + //this is absolutely hackiful, since we calc view values before we add packet ents and lerp, + //if we hit a mover we want to update its lerp pos and force it when we do the trace against + //it. + if (mover->currentState.pos.trType != TR_STATIONARY && + mover->currentState.pos.trType != TR_LINEAR) + { + int curTr = mover->currentState.pos.trType; + vec3_t curTrB; + + VectorCopy(mover->currentState.pos.trBase, curTrB); + + //calc lerporigin for this client frame + CG_CalcEntityLerpPositions(mover); + + //force the calc'd lerp to be the base and say we are stationary so we don't try to extrapolate + //out further. + mover->currentState.pos.trType = TR_STATIONARY; + VectorCopy(mover->lerpOrigin, mover->currentState.pos.trBase); + + //retrace + CG_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, cg->snap->ps.clientNum, MASK_CAMERACLIP); + + //copy old data back in + mover->currentState.pos.trType = (trType_t) curTr; + VectorCopy(curTrB, mover->currentState.pos.trBase); + } + if (trace.fraction < 1.0f) + { //still hit it, so take the proper trace endpos and use that. + VectorCopy(trace.endpos, cameraCurLoc); + } + } + else + { + VectorCopy( trace.endpos, cameraCurLoc ); + } + } + + // Note that previously there was an upper limit to the number of physics traces that are done through the world + // for the sake of camera collision, since it wasn't calced per frame. Now it is calculated every frame. + // This has the benefit that the camera is a lot smoother now (before it lerped between tested points), + // however two full volume traces each frame is a bit scary to think about. +} + +#endif // _XBOX + +#ifdef _XBOX +static void CG_OffsetThirdPersonHack(void) +{ + // only in splitscreen mode + if(ClientManager::splitScreenMode == qfalse) + return; + + //Hacks to mess with vertical camera position in multiplayer. Both + //players needed to have their camera moved down, but player 2 needed + //to be moved further. + static int offset = -10; + cg->refdef.vieworg[2] += offset; + if(ClientManager::ActiveClientNum() == 1) { + static int p2offset = -6; + cg->refdef.vieworg[2] += p2offset; + } + + //Make sure we don't poke through some geometry + trace_t trace; + CG_Trace(&trace, ClientManager::ActiveClient().cameraCurTarget, + cameramins, cameramaxs, + cg->refdef.vieworg, + cg->predictedPlayerState.clientNum, + MASK_CAMERACLIP); + if (trace.fraction <= 1.0) + { + VectorCopy(trace.endpos, cg->refdef.vieworg); + } +} +#endif + + +/* +===============` +CG_OffsetThirdPersonView + +=============== +*/ +extern vmCvar_t cg_thirdPersonHorzOffset; +extern qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ); + +#ifdef _XBOX +static void CG_OffsetThirdPersonView( void ) +{ + vec3_t diff; + float thirdPersonHorzOffset = (float)ClientManager::ActiveClient().cg_thirdPersonHorzOffset; + + float deltayaw; + + if (cg->snap && cg->snap->ps.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg->snap->ps.m_iVehicleNum]; + if (veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride) + { //override the range with what the vehicle wants it to be + thirdPersonHorzOffset = veh->m_pVehicle->m_pVehicleInfo->cameraHorzOffset; + if ( veh->playerState->hackingTime ) + { + thirdPersonHorzOffset += (((float)veh->playerState->hackingTime)/MAX_STRAFE_TIME) * -80.0f; + } + } + } + + ClientManager::ActiveClient().cameraStiffFactor = 0.0; + + // Set camera viewing direction. + VectorCopy( cg->refdef.viewangles, ClientManager::ActiveClient().cameraFocusAngles ); + + // if dead, look at killer + if ( cg->snap + && (cg->snap->ps.eFlags2&EF2_HELD_BY_MONSTER) + && cg->snap->ps.hasLookTarget + && cg_entities[cg->snap->ps.lookTarget].currentState.NPC_class == CLASS_RANCOR )//only possibility for now, may add Wampa and sand creature later + {//being held + //vec3_t monsterPos, dir2Me; + centity_t *monster = &cg_entities[cg->snap->ps.lookTarget]; + VectorSet( ClientManager::ActiveClient().cameraFocusAngles, 0, AngleNormalize180(monster->lerpAngles[YAW]+180), 0); + } + else if ( cg->snap->ps.stats[STAT_HEALTH] <= 0 ) + { + ClientManager::ActiveClient().cameraFocusAngles[YAW] = cg->snap->ps.stats[STAT_DEAD_YAW]; + } + else + { // Add in the third Person Angle. + ClientManager::ActiveClient().cameraFocusAngles[YAW] += ClientManager::ActiveClient().cg_thirdPersonAngle; + { + float pitchOffset = ClientManager::ActiveClient().cg_thirdPersonPitchOffset; + + if (cg->snap && cg->snap->ps.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg->snap->ps.m_iVehicleNum]; + if (veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride) + { //override the range with what the vehicle wants it to be + if ( veh->m_pVehicle->m_pVehicleInfo->cameraPitchDependantVertOffset ) + { + if ( cg->snap->ps.viewangles[PITCH] > 0 ) + { + pitchOffset = cg->predictedPlayerState.viewangles[PITCH]*-0.75; + } + else if ( cg->snap->ps.viewangles[PITCH] < 0 ) + { + pitchOffset = cg->predictedPlayerState.viewangles[PITCH]*-0.75; + } + else + { + pitchOffset = 0; + } + } + else + { + pitchOffset = veh->m_pVehicle->m_pVehicleInfo->cameraPitchOffset; + } + } + } + if ( 0 && cg->predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg->predictedPlayerState, cg_entities[cg->predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + { + float pitchPerc = ((90.0f-fabs(ClientManager::ActiveClient().cameraFocusAngles[ROLL]))/90.0f); + ClientManager::ActiveClient().cameraFocusAngles[PITCH] += pitchOffset*pitchPerc; + if ( ClientManager::ActiveClient().cameraFocusAngles[ROLL] > 0 ) + { + ClientManager::ActiveClient().cameraFocusAngles[YAW] -= pitchOffset-(pitchOffset*pitchPerc); + } + else + { + ClientManager::ActiveClient().cameraFocusAngles[YAW] += pitchOffset-(pitchOffset*pitchPerc); + } + } + else + { + ClientManager::ActiveClient().cameraFocusAngles[PITCH] += pitchOffset; + } + } + } + + // The next thing to do is to see if we need to calculate a new camera target location. + + // If we went back in time for some reason, or if we just started, reset the sample. + if (ClientManager::ActiveClient().cameraLastFrame == 0 || ClientManager::ActiveClient().cameraLastFrame > cg->time) + { + CG_ResetThirdPersonViewDamp(); + } + else + { + // Cap the pitch within reasonable limits + if ( cg->predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg->predictedPlayerState, cg_entities[cg->predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + {//no clamp on pitch + //FIXME: when pitch >= 90 or <= -90, camera rotates oddly... need to CrossProduct not just vectoangles + } + else + { + if (ClientManager::ActiveClient().cameraFocusAngles[PITCH] > 80.0) + { + ClientManager::ActiveClient().cameraFocusAngles[PITCH] = 80.0; + } + else if (ClientManager::ActiveClient().cameraFocusAngles[PITCH] < -80.0) + { + ClientManager::ActiveClient().cameraFocusAngles[PITCH] = -80.0; + } + } + + AngleVectors(ClientManager::ActiveClient().cameraFocusAngles, camerafwd, NULL, cameraup); + + deltayaw = fabs(ClientManager::ActiveClient().cameraFocusAngles[YAW] - ClientManager::ActiveClient().cameraLastYaw); + if (deltayaw > 180.0f) + { // Normalize this angle so that it is between 0 and 180. + deltayaw = fabs(deltayaw - 360.0f); + } + ClientManager::ActiveClient().cameraStiffFactor = deltayaw / (float)(cg->time-ClientManager::ActiveClient().cameraLastFrame); + if (ClientManager::ActiveClient().cameraStiffFactor < 1.0) + { + ClientManager::ActiveClient().cameraStiffFactor = 0.0; + } + else if (ClientManager::ActiveClient().cameraStiffFactor > 2.5) + { + ClientManager::ActiveClient().cameraStiffFactor = 0.75; + } + else + { // 1 to 2 scales from 0.0 to 0.5 + ClientManager::ActiveClient().cameraStiffFactor = (ClientManager::ActiveClient().cameraStiffFactor-1.0f)*0.5f; + } + ClientManager::ActiveClient().cameraLastYaw = ClientManager::ActiveClient().cameraFocusAngles[YAW]; + + // Move the target to the new location. + CG_UpdateThirdPersonTargetDamp(); + CG_UpdateThirdPersonCameraDamp(); + } + + // Now interestingly, the Quake method is to calculate a target focus point above the player, and point the camera at it. + // We won't do that for now. + + // We must now take the angle taken from the camera target and location. + /*VectorSubtract(cameraCurTarget, cameraCurLoc, diff); + VectorNormalize(diff); + vectoangles(diff, cg->refdef.viewangles);*/ + VectorSubtract(ClientManager::ActiveClient().cameraCurTarget, ClientManager::ActiveClient().cameraCurLoc, diff); + { + float dist = VectorNormalize(diff); + //under normal circumstances, should never be 0.00000 and so on. + if ( !dist || (diff[0] == 0 || diff[1] == 0) ) + {//must be hitting something, need some value to calc angles, so use cam forward + VectorCopy( camerafwd, diff ); + } + } + if ( 0 && cg->predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg->predictedPlayerState, cg_entities[cg->predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + {//FIXME: this causes camera jerkiness, need to blend the roll? + float sav_Roll = cg->refdef.viewangles[ROLL]; + vectoangles(diff, cg->refdef.viewangles); + cg->refdef.viewangles[ROLL] = sav_Roll; + } + else + { + vectoangles(diff, cg->refdef.viewangles); + } + + // Temp: just move the camera to the side a bit + if ( thirdPersonHorzOffset != 0.0f ) + { + AnglesToAxis( cg->refdef.viewangles, cg->refdef.viewaxis ); + VectorMA( ClientManager::ActiveClient().cameraCurLoc, thirdPersonHorzOffset, cg->refdef.viewaxis[1], ClientManager::ActiveClient().cameraCurLoc ); + } + + // ...and of course we should copy the new view location to the proper spot too. + VectorCopy(ClientManager::ActiveClient().cameraCurLoc, cg->refdef.vieworg); + + CG_OffsetThirdPersonHack(); + + ClientManager::ActiveClient().cameraLastFrame=cg->time; +} + +#else // _XBOX + +static void CG_OffsetThirdPersonView( void ) +{ + vec3_t diff; + float thirdPersonHorzOffset = cg_thirdPersonHorzOffset.value; + float deltayaw; + + if (cg->snap && cg->snap->ps.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg->snap->ps.m_iVehicleNum]; + if (veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride) + { //override the range with what the vehicle wants it to be + thirdPersonHorzOffset = veh->m_pVehicle->m_pVehicleInfo->cameraHorzOffset; + if ( veh->playerState->hackingTime ) + { + thirdPersonHorzOffset += (((float)veh->playerState->hackingTime)/MAX_STRAFE_TIME) * -80.0f; + } + } + } + + cameraStiffFactor = 0.0; + + // Set camera viewing direction. + VectorCopy( cg->refdef.viewangles, cameraFocusAngles ); + + // if dead, look at killer + if ( cg->snap + && (cg->snap->ps.eFlags2&EF2_HELD_BY_MONSTER) + && cg->snap->ps.hasLookTarget + && cg_entities[cg->snap->ps.lookTarget].currentState.NPC_class == CLASS_RANCOR )//only possibility for now, may add Wampa and sand creature later + {//being held + //vec3_t monsterPos, dir2Me; + centity_t *monster = &cg_entities[cg->snap->ps.lookTarget]; + VectorSet( cameraFocusAngles, 0, AngleNormalize180(monster->lerpAngles[YAW]+180), 0 ); + + //make the look angle the vector from his mouth to me + /* + VectorCopy( monster->lerpOrigin, monsterPos ); + monsterPos[2] = cg->snap->ps.origin[2]; + VectorSubtract( monsterPos, cg->snap->ps.origin, dir2Me ); + vectoangles( dir2Me, cameraFocusAngles ); + */ + } + else if ( cg->snap->ps.stats[STAT_HEALTH] <= 0 ) + { + cameraFocusAngles[YAW] = cg->snap->ps.stats[STAT_DEAD_YAW]; + } + else + { // Add in the third Person Angle. + cameraFocusAngles[YAW] += cg_thirdPersonAngle.value; + { + float pitchOffset = cg_thirdPersonPitchOffset.value; + if (cg->snap && cg->snap->ps.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg->snap->ps.m_iVehicleNum]; + if (veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride) + { //override the range with what the vehicle wants it to be + if ( veh->m_pVehicle->m_pVehicleInfo->cameraPitchDependantVertOffset ) + { + if ( cg->snap->ps.viewangles[PITCH] > 0 ) + { + pitchOffset = cg->predictedPlayerState.viewangles[PITCH]*-0.75; + } + else if ( cg->snap->ps.viewangles[PITCH] < 0 ) + { + pitchOffset = cg->predictedPlayerState.viewangles[PITCH]*-0.75; + } + else + { + pitchOffset = 0; + } + } + else + { + pitchOffset = veh->m_pVehicle->m_pVehicleInfo->cameraPitchOffset; + } + } + } + if ( 0 && cg->predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg->predictedPlayerState, cg_entities[cg->predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + { + float pitchPerc = ((90.0f-fabs(cameraFocusAngles[ROLL]))/90.0f); + cameraFocusAngles[PITCH] += pitchOffset*pitchPerc; + if ( cameraFocusAngles[ROLL] > 0 ) + { + cameraFocusAngles[YAW] -= pitchOffset-(pitchOffset*pitchPerc); + } + else + { + cameraFocusAngles[YAW] += pitchOffset-(pitchOffset*pitchPerc); + } + } + else + { + cameraFocusAngles[PITCH] += pitchOffset; + } + } + } + + // The next thing to do is to see if we need to calculate a new camera target location. + + // If we went back in time for some reason, or if we just started, reset the sample. + if (cameraLastFrame == 0 || cameraLastFrame > cg->time) + { + CG_ResetThirdPersonViewDamp(); + } + else + { + // Cap the pitch within reasonable limits + if ( cg->predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg->predictedPlayerState, cg_entities[cg->predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + {//no clamp on pitch + //FIXME: when pitch >= 90 or <= -90, camera rotates oddly... need to CrossProduct not just vectoangles + } + else + { + if (cameraFocusAngles[PITCH] > 80.0) + { + cameraFocusAngles[PITCH] = 80.0; + } + else if (cameraFocusAngles[PITCH] < -80.0) + { + cameraFocusAngles[PITCH] = -80.0; + } + } + + AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup); + + deltayaw = fabs(cameraFocusAngles[YAW] - cameraLastYaw); + if (deltayaw > 180.0f) + { // Normalize this angle so that it is between 0 and 180. + deltayaw = fabs(deltayaw - 360.0f); + } + cameraStiffFactor = deltayaw / (float)(cg->time-cameraLastFrame); + if (cameraStiffFactor < 1.0) + { + cameraStiffFactor = 0.0; + } + else if (cameraStiffFactor > 2.5) + { + cameraStiffFactor = 0.75; + } + else + { // 1 to 2 scales from 0.0 to 0.5 + cameraStiffFactor = (cameraStiffFactor-1.0f)*0.5f; + } + cameraLastYaw = cameraFocusAngles[YAW]; + + // Move the target to the new location. + CG_UpdateThirdPersonTargetDamp(); + CG_UpdateThirdPersonCameraDamp(); + } + + // Now interestingly, the Quake method is to calculate a target focus point above the player, and point the camera at it. + // We won't do that for now. + + // We must now take the angle taken from the camera target and location. + /*VectorSubtract(cameraCurTarget, cameraCurLoc, diff); + VectorNormalize(diff); + vectoangles(diff, cg->refdef.viewangles);*/ + VectorSubtract(cameraCurTarget, cameraCurLoc, diff); + { + float dist = VectorNormalize(diff); + //under normal circumstances, should never be 0.00000 and so on. + if ( !dist || (diff[0] == 0 || diff[1] == 0) ) + {//must be hitting something, need some value to calc angles, so use cam forward + VectorCopy( camerafwd, diff ); + } + } + if ( 0 && cg->predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg->predictedPlayerState, cg_entities[cg->predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + {//FIXME: this causes camera jerkiness, need to blend the roll? + float sav_Roll = cg->refdef.viewangles[ROLL]; + vectoangles(diff, cg->refdef.viewangles); + cg->refdef.viewangles[ROLL] = sav_Roll; + } + else + { + vectoangles(diff, cg->refdef.viewangles); + } + + // Temp: just move the camera to the side a bit + if ( thirdPersonHorzOffset != 0.0f ) + { + AnglesToAxis( cg->refdef.viewangles, cg->refdef.viewaxis ); + VectorMA( cameraCurLoc, thirdPersonHorzOffset, cg->refdef.viewaxis[1], cameraCurLoc ); + } + + // ...and of course we should copy the new view location to the proper spot too. + VectorCopy(cameraCurLoc, cg->refdef.vieworg); + cameraLastFrame=cg->time; +} + +#endif // _XBOX + +/* +=============== +CG_OffsetThirdPersonView + +=============== +*//* +#define FOCUS_DISTANCE 512 +static void CG_OffsetThirdPersonView( void ) { + vec3_t forward, right, up; + vec3_t view; + vec3_t focusAngles; + trace_t trace; + static vec3_t mins = { -4, -4, -4 }; + static vec3_t maxs = { 4, 4, 4 }; + vec3_t focusPoint; + float focusDist; + float forwardScale, sideScale; + + cg->refdef.vieworg[2] += cg->predictedPlayerState.viewheight; + + VectorCopy( cg->refdef.viewangles, focusAngles ); + + // if dead, look at killer + if ( cg->predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + focusAngles[YAW] = cg->predictedPlayerState.stats[STAT_DEAD_YAW]; + cg->refdef.viewangles[YAW] = cg->predictedPlayerState.stats[STAT_DEAD_YAW]; + } + + if ( focusAngles[PITCH] > 45 ) { + focusAngles[PITCH] = 45; // don't go too far overhead + } + AngleVectors( focusAngles, forward, NULL, NULL ); + + VectorMA( cg->refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + + VectorCopy( cg->refdef.vieworg, view ); + + view[2] += 8; + + cg->refdef.viewangles[PITCH] *= 0.5; + + AngleVectors( cg->refdef.viewangles, forward, right, up ); + + forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); + sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); + VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); + VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); + + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + + if (!cg_cameraMode.integer) { + CG_Trace( &trace, cg->refdef.vieworg, mins, maxs, view, cg->predictedPlayerState.clientNum, MASK_CAMERACLIP); + + if ( trace.fraction != 1.0 ) { + VectorCopy( trace.endpos, view ); + view[2] += (1.0 - trace.fraction) * 32; + // try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out + + CG_Trace( &trace, cg->refdef.vieworg, mins, maxs, view, cg->predictedPlayerState.clientNum, MASK_CAMERACLIP); + VectorCopy( trace.endpos, view ); + } + } + + + VectorCopy( view, cg->refdef.vieworg ); + + // select pitch to look at focus point from vieword + VectorSubtract( focusPoint, cg->refdef.vieworg, focusPoint ); + focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) { + focusDist = 1; // should never happen + } + cg->refdef.viewangles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); + cg->refdef.viewangles[YAW] -= cg_thirdPersonAngle.value; +} + + +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) { + int timeDelta; + + // smooth out stair climbing + timeDelta = cg->time - cg->stepTime; + if ( timeDelta < STEP_TIME ) { + cg->refdef.vieworg[2] -= cg->stepChange + * (STEP_TIME - timeDelta) / STEP_TIME; + } +}*/ + +/* +=============== +CG_OffsetFirstPersonView + +=============== +*/ +static void CG_OffsetFirstPersonView( void ) { + float *origin; + float *angles; + float bob; + float ratio; + float delta; + float speed; + float f; + vec3_t predictedVelocity; + int timeDelta; + int kickTime; + + if ( cg->snap->ps.pm_type == PM_INTERMISSION ) { + return; + } + + origin = cg->refdef.vieworg; + angles = cg->refdef.viewangles; + + // if dead, fix the angle and don't add any kick + if ( cg->snap->ps.stats[STAT_HEALTH] <= 0 ) { + angles[ROLL] = 40; + angles[PITCH] = -15; + angles[YAW] = cg->snap->ps.stats[STAT_DEAD_YAW]; + origin[2] += cg->predictedPlayerState.viewheight; + return; + } + + // add angles based on weapon kick + kickTime = (cg->time - cg->kick_time); + if ( kickTime < 800 ) + {//kicks are always 1 second long. Deal with it. + float kickPerc = 0.0f; + if ( kickTime <= 200 ) + {//winding up + kickPerc = kickTime/200.0f; + } + else + {//returning to normal + kickTime = 800 - kickTime; + kickPerc = kickTime/600.0f; + } + VectorMA( angles, kickPerc, cg->kick_angles, angles ); + } + // add angles based on damage kick + if ( cg->damageTime ) { + ratio = cg->time - cg->damageTime; + if ( ratio < DAMAGE_DEFLECT_TIME ) { + ratio /= DAMAGE_DEFLECT_TIME; + angles[PITCH] += ratio * cg->v_dmg_pitch; + angles[ROLL] += ratio * cg->v_dmg_roll; + } else { + ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; + if ( ratio > 0 ) { + angles[PITCH] += ratio * cg->v_dmg_pitch; + angles[ROLL] += ratio * cg->v_dmg_roll; + } + } + } + + // add pitch based on fall kick +#if 0 + ratio = ( cg->time - cg->landTime) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * cg->fall_value; +#endif + + // add angles based on velocity + VectorCopy( cg->predictedPlayerState.velocity, predictedVelocity ); + + delta = DotProduct ( predictedVelocity, cg->refdef.viewaxis[0]); + angles[PITCH] += delta * cg_runpitch.value; + + delta = DotProduct ( predictedVelocity, cg->refdef.viewaxis[1]); + angles[ROLL] -= delta * cg_runroll.value; + + // add angles based on bob + + // make sure the bob is visible even at low speeds + speed = cg->xyspeed > 200 ? cg->xyspeed : 200; + + delta = cg->bobfracsin * cg_bobpitch.value * speed; + if (cg->predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching + angles[PITCH] += delta; + delta = cg->bobfracsin * cg_bobroll.value * speed; + if (cg->predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching accentuates roll + if (cg->bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + +//=================================== + + // add view height + origin[2] += cg->predictedPlayerState.viewheight; + + // smooth out duck height changes + timeDelta = cg->time - cg->duckTime; + if ( timeDelta < DUCK_TIME) { + cg->refdef.vieworg[2] -= cg->duckChange + * (DUCK_TIME - timeDelta) / DUCK_TIME; + } + + // add bob height + bob = cg->bobfracsin * cg->xyspeed * cg_bobup.value; + if (bob > 6) { + bob = 6; + } + + origin[2] += bob; + + + // add fall height + delta = cg->time - cg->landTime; + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + cg->refdef.vieworg[2] += cg->landChange * f; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + cg->refdef.vieworg[2] += cg->landChange * f; + } + + // add step offset + CG_StepOffset(); + + // add kick offset + + VectorAdd (origin, cg->kick_origin, origin); + + // pivot the eye based on a neck length +#if 0 + { +#define NECK_LENGTH 8 + vec3_t forward, up; + + cg->refdef.vieworg[2] -= NECK_LENGTH; + AngleVectors( cg->refdef.viewangles, forward, NULL, up ); + VectorMA( cg->refdef.vieworg, 3, forward, cg->refdef.vieworg ); + VectorMA( cg->refdef.vieworg, NECK_LENGTH, up, cg->refdef.vieworg ); + } +#endif +} + +static void CG_OffsetFighterView( void ) +{ + vec3_t vehFwd, vehRight, vehUp, backDir; + vec3_t camOrg, camBackOrg; + float horzOffset = cg_thirdPersonHorzOffset.value; + float vertOffset = cg_thirdPersonVertOffset.value; + float pitchOffset = cg_thirdPersonPitchOffset.value; + float yawOffset = cg_thirdPersonAngle.value; + float range = cg_thirdPersonRange.value; +#ifdef _XBOX + range = (float)ClientManager::ActiveClient().cg_thirdPersonRange; + yawOffset = (float)ClientManager::ActiveClient().cg_thirdPersonAngle; + pitchOffset = (float)ClientManager::ActiveClient().cg_thirdPersonPitchOffset; + vertOffset = (float)ClientManager::ActiveClient().cg_thirdPersonVertOffset; + horzOffset = (float)ClientManager::ActiveClient().cg_thirdPersonHorzOffset; +#endif + trace_t trace; + centity_t *veh = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + + AngleVectors( cg->refdef.viewangles, vehFwd, vehRight, vehUp ); + + if ( veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride ) + { //override the horizontal offset with what the vehicle wants it to be + horzOffset = veh->m_pVehicle->m_pVehicleInfo->cameraHorzOffset; + vertOffset = veh->m_pVehicle->m_pVehicleInfo->cameraVertOffset; + //NOTE: no yaw offset? + pitchOffset = veh->m_pVehicle->m_pVehicleInfo->cameraPitchOffset; + range = veh->m_pVehicle->m_pVehicleInfo->cameraRange; + if ( veh->playerState->hackingTime ) + { + horzOffset += (((float)veh->playerState->hackingTime)/MAX_STRAFE_TIME) * -80.0f; + range += fabs(((float)veh->playerState->hackingTime)/MAX_STRAFE_TIME) * 100.0f; + } + } + + //Set camera viewing position + VectorMA( cg->refdef.vieworg, horzOffset, vehRight, camOrg ); + VectorMA( camOrg, vertOffset, vehUp, camOrg ); + + //trace to that pos + CG_Trace(&trace, cg->refdef.vieworg, cameramins, cameramaxs, camOrg, cg->snap->ps.clientNum, MASK_CAMERACLIP); + if ( trace.fraction < 1.0 ) + { + VectorCopy( trace.endpos, camOrg ); + } + + // Set camera viewing direction. + cg->refdef.viewangles[YAW] += yawOffset; + cg->refdef.viewangles[PITCH] += pitchOffset; + + //Now bring the cam back from that pos and angles at range + AngleVectors( cg->refdef.viewangles, backDir, NULL, NULL ); + VectorScale( backDir, -1, backDir ); + + VectorMA( camOrg, range, backDir, camBackOrg ); + + //trace to that pos + CG_Trace(&trace, camOrg, cameramins, cameramaxs, camBackOrg, cg->snap->ps.clientNum, MASK_CAMERACLIP); + VectorCopy( trace.endpos, camOrg ); + + //FIXME: do we need to smooth the org? + // ...and of course we should copy the new view location to the proper spot too. + VectorCopy(camOrg, cg->refdef.vieworg); +} +//====================================================================== + +void CG_ZoomDown_f( void ) { + if ( cg->zoomed ) { + return; + } + cg->zoomed = qtrue; + cg->zoomTime = cg->time; +} + +void CG_ZoomUp_f( void ) { + if ( !cg->zoomed ) { + return; + } + cg->zoomed = qfalse; + cg->zoomTime = cg->time; +} + + + +/* +==================== +CG_CalcFovFromX + +Calcs Y FOV from given X FOV +==================== +*/ +qboolean CG_CalcFOVFromX( float fov_x ) +{ + float x; +// float phase; +// float v; +// int contents; + float fov_y; + qboolean inwater; + + x = cg->refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg->refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + // there's a problem with this, it only takes the leafbrushes into account, not the entity brushes, + // so if you give slime/water etc properties to a func_door area brush in order to move the whole water + // level up/down this doesn't take into account the door position, so warps the view the whole time + // whether the water is up or not. Fortunately there's only one slime area in Trek that you can be under, + // so lose it... +#if 0 +/* + // warp if underwater + contents = CG_PointContents( cg->refdef.vieworg, -1 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ){ + phase = cg->time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + v = WAVE_AMPLITUDE * sin( phase ); + fov_x += v; + fov_y -= v; + inwater = qtrue; + } + else { + inwater = qfalse; + } +*/ +#else + inwater = qfalse; +#endif + + + // set it + cg->refdef.fov_x = fov_x; + cg->refdef.fov_y = fov_y; + +#ifdef _XBOX + if(cg->widescreen) + cg->refdef.fov_x *= 1.125f; +#endif + + return (inwater); +} + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE 1 +#define WAVE_FREQUENCY 0.4 + +#ifdef _XBOX +float zoomFov[2]; +#else +float zoomFov; //this has to be global client-side +#endif + +static int CG_CalcFov( void ) { + float x; + float phase; + float v; + float fov_x, fov_y; + float f; + int inwater; + float cgFov = cg_fov.value; + + if (cgFov < 1) + { + cgFov = 1; + } + if (cgFov > 97) + { + cgFov = 97; + } + + if ( cg->predictedPlayerState.pm_type == PM_INTERMISSION ) { + // if in intermission, use a fixed value + fov_x = 80;//90; + } else { + // user selectable + if ( cgs.dmflags & DF_FIXED_FOV ) { + // dmflag to prevent wide fov for all clients + fov_x = 80;//90; + } else { + fov_x = cgFov; + if ( fov_x < 1 ) { + fov_x = 1; + } else if ( fov_x > 160 ) { + fov_x = 160; + } + } + +#ifdef _XBOX + if (cg->predictedPlayerState.zoomMode == 2) + { //binoculars + if (zoomFov[ClientManager::ActiveClientNum()] > 40.0f) + { + zoomFov[ClientManager::ActiveClientNum()] -= cg->frametime * 0.075f; + + if (zoomFov[ClientManager::ActiveClientNum()] < 40.0f) + { + zoomFov[ClientManager::ActiveClientNum()] = 40.0f; + } + else if (zoomFov[ClientManager::ActiveClientNum()] > cgFov) + { + zoomFov[ClientManager::ActiveClientNum()] = cgFov; + } + } + + fov_x = zoomFov[ClientManager::ActiveClientNum()]; + } + else if (cg->predictedPlayerState.zoomMode) + { + if (!cg->predictedPlayerState.zoomLocked) + { + if (zoomFov[ClientManager::ActiveClientNum()] > 50) + { //Now starting out at nearly half zoomed in + zoomFov[ClientManager::ActiveClientNum()] = 50; + } + zoomFov[ClientManager::ActiveClientNum()] -= cg->frametime * 0.035f;//0.075f; + + if (zoomFov[ClientManager::ActiveClientNum()] < MAX_ZOOM_FOV) + { + zoomFov[ClientManager::ActiveClientNum()] = MAX_ZOOM_FOV; + } + else if (zoomFov[ClientManager::ActiveClientNum()] > cgFov) + { + zoomFov[ClientManager::ActiveClientNum()] = cgFov; + } + else + { // Still zooming +#ifdef _XBOX + static int zoomSoundTime[2] = { 0, 0 }; + + if (zoomSoundTime[ClientManager::ActiveClientNum()] < cg->time || zoomSoundTime[ClientManager::ActiveClientNum()] > cg->time + 10000) + { + trap_S_StartSound(cg->refdef.vieworg, ENTITYNUM_WORLD, CHAN_LOCAL, cgs.media.disruptorZoomLoop); + zoomSoundTime[ClientManager::ActiveClientNum()] = cg->time + 300; + } +#else + static int zoomSoundTime = 0; + + if (zoomSoundTime < cg->time || zoomSoundTime > cg->time + 10000) + { + trap_S_StartSound(cg->refdef.vieworg, ENTITYNUM_WORLD, CHAN_LOCAL, cgs.media.disruptorZoomLoop); + zoomSoundTime = cg->time + 300; + } +#endif + } + } + + if (zoomFov[ClientManager::ActiveClientNum()] < MAX_ZOOM_FOV) + { + zoomFov[ClientManager::ActiveClientNum()] = 50; // hack to fix zoom during vid restart + } + fov_x = zoomFov[ClientManager::ActiveClientNum()]; + } + else + { + zoomFov[ClientManager::ActiveClientNum()] = 80; + + f = ( cg->time - cg->predictedPlayerState.zoomTime ) / ZOOM_OUT_TIME; + if ( f > 1.0 ) + { + fov_x = fov_x; + } + else + { + fov_x = cg->predictedPlayerState.zoomFov + f * ( fov_x - cg->predictedPlayerState.zoomFov ); + } + } + +#else // _XBOX + + if (cg->predictedPlayerState.zoomMode == 2) + { //binoculars + if (zoomFov > 40.0f) + { + zoomFov -= cg->frametime * 0.075f; + + if (zoomFov < 40.0f) + { + zoomFov = 40.0f; + } + else if (zoomFov > cgFov) + { + zoomFov = cgFov; + } + } + + fov_x = zoomFov; + } + else if (cg->predictedPlayerState.zoomMode) + { + if (!cg->predictedPlayerState.zoomLocked) + { + if (zoomFov > 50) + { //Now starting out at nearly half zoomed in + zoomFov = 50; + } + zoomFov -= cg->frametime * 0.035f;//0.075f; + + if (zoomFov < MAX_ZOOM_FOV) + { + zoomFov = MAX_ZOOM_FOV; + } + else if (zoomFov > cgFov) + { + zoomFov = cgFov; + } + else + { // Still zooming + static int zoomSoundTime = 0; + + if (zoomSoundTime < cg->time || zoomSoundTime > cg->time + 10000) + { + trap_S_StartSound(cg->refdef.vieworg, ENTITYNUM_WORLD, CHAN_LOCAL, cgs.media.disruptorZoomLoop); + zoomSoundTime = cg->time + 300; + } + } + } + + if (zoomFov < MAX_ZOOM_FOV) + { + zoomFov = 50; // hack to fix zoom during vid restart + } + fov_x = zoomFov; + } + else + { + zoomFov = 80; + + f = ( cg->time - cg->predictedPlayerState.zoomTime ) / ZOOM_OUT_TIME; + if ( f > 1.0 ) + { + fov_x = fov_x; + } + else + { + fov_x = cg->predictedPlayerState.zoomFov + f * ( fov_x - cg->predictedPlayerState.zoomFov ); + } + } +#endif + } + + x = cg->refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg->refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + // warp if underwater + cg->refdef.viewContents = CG_PointContents( cg->refdef.vieworg, -1 ); + if ( cg->refdef.viewContents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ){ + phase = cg->time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + v = WAVE_AMPLITUDE * sin( phase ); + fov_x += v; + fov_y -= v; + inwater = qtrue; + } + else { + inwater = qfalse; + } + +#ifdef _XBOX + if(cg->widescreen) + fov_x *= 1.125f; +#endif + + + // set it + cg->refdef.fov_x = fov_x; + cg->refdef.fov_y = fov_y; + + if (cg->predictedPlayerState.zoomMode) + { +#ifdef _XBOX + cg->zoomSensitivity = zoomFov[ClientManager::ActiveClientNum()]/cgFov; +#else + cg->zoomSensitivity = zoomFov/cgFov; +#endif + } + else if ( !cg->zoomed ) { + cg->zoomSensitivity = 1; + } else { + cg->zoomSensitivity = cg->refdef.fov_y / 75.0; + } + + return inwater; +} + + +/* +=============== +CG_DamageBlendBlob + +=============== +*/ +static void CG_DamageBlendBlob( void ) +{ + int t; + int maxTime; + refEntity_t ent; + + if ( !cg->damageValue ) { + return; + } + + maxTime = DAMAGE_TIME; + t = cg->time - cg->damageTime; + if ( t <= 0 || t >= maxTime ) { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + ent.renderfx = RF_FIRST_PERSON; + + VectorMA( cg->refdef.vieworg, 8, cg->refdef.viewaxis[0], ent.origin ); + VectorMA( ent.origin, cg->damageX * -8, cg->refdef.viewaxis[1], ent.origin ); + VectorMA( ent.origin, cg->damageY * 8, cg->refdef.viewaxis[2], ent.origin ); + + ent.radius = cg->damageValue * 3 * ( 1.0 - ((float)t / maxTime) ); + + if (cg->snap->ps.damageType == 0) + { //pure health + ent.customShader = cgs.media.viewPainShader; + ent.shaderRGBA[0] = 180 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[1] = 50 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[2] = 50 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[3] = 255; + } + else if (cg->snap->ps.damageType == 1) + { //pure shields + ent.customShader = cgs.media.viewPainShader_Shields; + ent.shaderRGBA[0] = 50 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[1] = 180 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[2] = 50 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[3] = 255; + } + else + { //shields and health + ent.customShader = cgs.media.viewPainShader_ShieldsAndHealth; + ent.shaderRGBA[0] = 180 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[1] = 180 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[2] = 50 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[3] = 255; + } + trap_R_AddRefEntityToScene( &ent ); +} + +int cg_actionCamLastTime = 0; +vec3_t cg_actionCamLastPos; + +//action cam routine -rww +static qboolean CG_ThirdPersonActionCam(void) +{ + centity_t *cent = &cg_entities[cg->snap->ps.clientNum]; + clientInfo_t *ci = &cgs.clientinfo[cg->snap->ps.clientNum]; + trace_t tr; + vec3_t positionDir; + vec3_t desiredAngles; + vec3_t desiredPos; + vec3_t v; + const float smoothFactor = 0.1f*cg_timescale.value; + int i; + + if (!cent->ghoul2) + { //if we don't have a g2 instance this frame for whatever reason then do nothing + return qfalse; + } + + if (cent->currentState.weapon != WP_SABER) + { //just being safe, should not ever happen + return qfalse; + } + + if ((cg->time - ci->saber[0].blade[0].trail[ClientManager::ActiveClientNum()].lastTime) > 300) + { //too long since we last got the blade position + return qfalse; + } + + //get direction from base to ent origin + VectorSubtract(ci->saber[0].blade[0].trail[ClientManager::ActiveClientNum()].base, cent->lerpOrigin, positionDir); + VectorNormalize(positionDir); + + //position the cam based on the direction and saber position +#ifdef _XBOX + VectorMA(cent->lerpOrigin, ClientManager::ActiveClient().cg_thirdPersonRange*2, positionDir, desiredPos); +#else + VectorMA(cent->lerpOrigin, cg_thirdPersonRange.value*2, positionDir, desiredPos); +#endif + + //trace to the desired pos to see how far that way we can actually go before we hit something + //the endpos will be valid for our desiredpos no matter what + CG_Trace(&tr, cent->lerpOrigin, NULL, NULL, desiredPos, cent->currentState.number, MASK_SOLID); + VectorCopy(tr.endpos, desiredPos); + + if ((cg->time - cg_actionCamLastTime) > 300) + { + //do a third person offset first and grab the initial point from that + CG_OffsetThirdPersonView(); + VectorCopy(cg->refdef.vieworg, cg_actionCamLastPos); + } + + cg_actionCamLastTime = cg->time; + + //lerp the vieworg to the desired pos from the last valid + VectorSubtract(desiredPos, cg_actionCamLastPos, v); + + if (VectorLength(v) > 64.0f) + { //don't bother moving yet if not far from the last pos + for (i = 0; i < 3; i++) + { + cg_actionCamLastPos[i] = (cg_actionCamLastPos[i] + (v[i]*smoothFactor)); + cg->refdef.vieworg[i] = cg_actionCamLastPos[i]; + } + } + else + { + VectorCopy(cg_actionCamLastPos, cg->refdef.vieworg); + } + + //Make sure the point is alright + CG_Trace(&tr, cent->lerpOrigin, NULL, NULL, cg->refdef.vieworg, cent->currentState.number, MASK_SOLID); + VectorCopy(tr.endpos, cg->refdef.vieworg); + + VectorSubtract(cent->lerpOrigin, cg->refdef.vieworg, positionDir); + vectoangles(positionDir, desiredAngles); + + //just set the angles for now + VectorCopy(desiredAngles, cg->refdef.viewangles); + return qtrue; +} + +vec3_t cg_lastTurretViewAngles={0}; +qboolean CG_CheckPassengerTurretView( void ) +{ + if ( cg->predictedPlayerState.m_iVehicleNum //in a vehicle + && cg->predictedPlayerState.generic1 )//as a passenger + {//passenger in a vehicle + centity_t *vehCent = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + if ( vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo + && vehCent->m_pVehicle->m_pVehicleInfo->maxPassengers ) + {//a vehicle capable of carrying passengers + int turretNum; + for ( turretNum = 0; turretNum < MAX_VEHICLE_TURRETS; turretNum++ ) + { + if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].iAmmoMax ) + {// valid turret + if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].passengerNum == cg->predictedPlayerState.generic1 ) + {//I control this turret + int boltIndex = -1; + qboolean hackPosAndAngle = qfalse; + if ( vehCent->m_pVehicle->m_iGunnerViewTag[turretNum] != -1 ) + { + boltIndex = vehCent->m_pVehicle->m_iGunnerViewTag[turretNum]; + } + else + {//crap... guess? + hackPosAndAngle = qtrue; + if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].yawBone ) + { + boltIndex = trap_G2API_AddBolt( vehCent->ghoul2, 0, vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].yawBone ); + } + else if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].pitchBone ) + { + boltIndex = trap_G2API_AddBolt( vehCent->ghoul2, 0, vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].pitchBone ); + } + else + {//well, no way of knowing, so screw it + return qfalse; + } + } + if ( boltIndex != -1 ) + { + mdxaBone_t boltMatrix; + vec3_t fwd, up; + trap_G2API_GetBoltMatrix_NoRecNoRot(vehCent->ghoul2, 0, boltIndex, &boltMatrix, vehCent->lerpAngles, + vehCent->lerpOrigin, cg->time, NULL, vehCent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, cg->refdef.vieworg); + if ( hackPosAndAngle ) + { + //FIXME: these are assumptions, externalize? BETTER YET: give me a controller view bolt/tag for each turret + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, fwd); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, up); + VectorMA( cg->refdef.vieworg, 8.0f, fwd, cg->refdef.vieworg ); + VectorMA( cg->refdef.vieworg, 4.0f, up, cg->refdef.vieworg ); + } + else + { + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, fwd); + } + { + vec3_t newAngles, deltaAngles; + vectoangles( fwd, newAngles ); + AnglesSubtract( newAngles, cg_lastTurretViewAngles, deltaAngles ); + VectorMA( cg_lastTurretViewAngles, 0.5f*(float)cg->frametime/100.0f, deltaAngles, cg->refdef.viewangles ); + } + return qtrue; + } + } + } + } + } + } + return qfalse; +} +/* +=============== +CG_CalcViewValues + +Sets cg->refdef view values +=============== +*/ +void CG_EmplacedView(vec3_t angles); +static int CG_CalcViewValues( void ) { + qboolean manningTurret = qfalse; + playerState_t *ps; + + memset( &cg->refdef, 0, sizeof( cg->refdef ) ); + + // strings for in game rendering + // Q_strncpyz( cg->refdef.text[0], "Park Ranger", sizeof(cg->refdef.text[0]) ); + // Q_strncpyz( cg->refdef.text[1], "19", sizeof(cg->refdef.text[1]) ); + + // calculate size of 3D view + CG_CalcVrect(); + + ps = &cg->predictedPlayerState; +/* + if (cg->cameraMode) { + vec3_t origin, angles; + if (trap_getCameraInfo(cg->time, &origin, &angles)) { + VectorCopy(origin, cg->refdef.vieworg); + angles[ROLL] = 0; + VectorCopy(angles, cg->refdef.viewangles); + AnglesToAxis( cg->refdef.viewangles, cg->refdef.viewaxis ); + return CG_CalcFov(); + } else { + cg->cameraMode = qfalse; + } + } +*/ + // intermission view + if ( ps->pm_type == PM_INTERMISSION ) { + VectorCopy( ps->origin, cg->refdef.vieworg ); + VectorCopy( ps->viewangles, cg->refdef.viewangles ); + AnglesToAxis( cg->refdef.viewangles, cg->refdef.viewaxis ); + return CG_CalcFov(); + } + + cg->bobcycle = ( ps->bobCycle & 128 ) >> 7; + cg->bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); + cg->xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + + ps->velocity[1] * ps->velocity[1] ); + + if (cg->xyspeed > 270) + { + cg->xyspeed = 270; + } + + manningTurret = CG_CheckPassengerTurretView(); + if ( !manningTurret ) + {//not manning a turret on a vehicle + VectorCopy( ps->origin, cg->refdef.vieworg ); + if ( cg->predictedPlayerState.m_iVehicleNum )//in a vehicle + { + Vehicle_t *pVeh = cg_entities[cg->predictedPlayerState.m_iVehicleNum].m_pVehicle; + if ( BG_UnrestrainedPitchRoll( &cg->predictedPlayerState, pVeh ) )//can roll/pitch without restriction + {//use the vehicle's viewangles to render view! + VectorCopy( cg->predictedVehicleState.viewangles, cg->refdef.viewangles ); + } + else if ( pVeh //valid vehicle data pointer + && pVeh->m_pVehicleInfo//valid vehicle info + && pVeh->m_pVehicleInfo->type == VH_FIGHTER )//fighter + { + VectorCopy( cg->predictedVehicleState.viewangles, cg->refdef.viewangles ); + cg->refdef.viewangles[PITCH] = AngleNormalize180( cg->refdef.viewangles[PITCH] ); + } + else + { + VectorCopy( ps->viewangles, cg->refdef.viewangles ); + } + /* + if ( cg.predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg.predictedPlayerState, cg_entities[cg.predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + {//use the vehicle's viewangles to render view! + VectorCopy( cg.predictedVehicleState.viewangles, cg.refdef.viewangles ); + */ + } + else + { + VectorCopy( ps->viewangles, cg->refdef.viewangles ); + } + } + VectorCopy( cg->refdef.viewangles, cg_lastTurretViewAngles ); + + if (cg_cameraOrbit.integer) { + if (cg->time > cg->nextOrbitTime) { + cg->nextOrbitTime = cg->time + cg_cameraOrbitDelay.integer; +#ifdef _XBOX + ClientManager::ActiveClient().cg_thirdPersonAngle += cg_cameraOrbit.value; +#else + cg_thirdPersonAngle.value += cg_cameraOrbit.value; +#endif + } + } + // add error decay + if ( cg_errorDecay.value > 0 ) { + int t; + float f; + + t = cg->time - cg->predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f > 0 && f < 1 ) { + VectorMA( cg->refdef.vieworg, f, cg->predictedError, cg->refdef.vieworg ); + } else { + cg->predictedErrorTime = 0; + } + } + + if (cg->snap->ps.weapon == WP_EMPLACED_GUN && + cg->snap->ps.emplacedIndex) + { //constrain the view properly for emplaced guns + CG_EmplacedView(cg_entities[cg->snap->ps.emplacedIndex].currentState.angles); + } + + if ( !manningTurret ) + { + if ( cg->predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg->predictedPlayerState, cg_entities[cg->predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + {//use the vehicle's viewangles to render view! + CG_OffsetFighterView(); + } + else if ( cg->renderingThirdPerson ) { + // back away from character +#ifdef _XBOX + if(ClientManager::ActiveClient().cg_thirdPersonSpecialCam && +#else + if (cg_thirdPersonSpecialCam.integer && +#endif + BG_SaberInSpecial(cg->snap->ps.saberMove)) + { //the action cam + if (!CG_ThirdPersonActionCam()) + { //couldn't do it for whatever reason, resort back to third person then + CG_OffsetThirdPersonView(); + } + } + else + { + CG_OffsetThirdPersonView(); + } + } else { + // offset for local bobbing and kicks + CG_OffsetFirstPersonView(); + } + } + + // position eye relative to origin + AnglesToAxis( cg->refdef.viewangles, cg->refdef.viewaxis ); + + if ( cg->hyperspace ) { + cg->refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; + } + + // field of view + return CG_CalcFov(); +} + + +/* +===================== +CG_PowerupTimerSounds +===================== +*/ +static void CG_PowerupTimerSounds( void ) { + int i; + int t; + + // powerup timers going away + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + t = cg->snap->ps.powerups[i]; + if ( t <= cg->time ) { + continue; + } + if ( t - cg->time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + continue; + } + if ( ( t - cg->time ) / POWERUP_BLINK_TIME != ( t - cg->oldTime ) / POWERUP_BLINK_TIME ) { + //trap_S_StartSound( NULL, cg->snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound ); + } + } +} + +/* +============== +CG_DrawSkyBoxPortal +============== +*/ +extern qboolean cg_skyOri; +extern vec3_t cg_skyOriPos; +extern float cg_skyOriScale; +extern qboolean cg_noFogOutsidePortal; +void CG_DrawSkyBoxPortal(const char *cstr) +{ + static float lastfov; + refdef_t backuprefdef; + float fov_x; + float fov_y; + float x; + char *token; + float f = 0; + +#ifdef _XBOX + lastfov = zoomFov[ClientManager::ActiveClientNum()]; +#else + lastfov = zoomFov; // for transitions back from zoomed in modes +#endif + + backuprefdef = cg->refdef; + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n"); + } + cg->refdef.vieworg[0] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n"); + } + cg->refdef.vieworg[1] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n"); + } + cg->refdef.vieworg[2] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n"); + } + fov_x = atoi(token); + + if (!fov_x) + { + fov_x = cg_fov.value; + } + + // setup fog the first time, ignore this part of the configstring after that + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog state\n"); + } + else + { + vec4_t fogColor; + int fogStart, fogEnd; + + if(atoi(token)) + { // this camera has fog + token = COM_ParseExt(&cstr, qfalse); + + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[0]\n"); + } + fogColor[0] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[1]\n"); + } + fogColor[1] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[2]\n"); + } + fogColor[2] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + fogStart = 0; + } + else + { + fogStart = atoi(token); + } + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + fogEnd = 0; + } + else + { + fogEnd = atoi(token); + } + } + } + + if ( cg->predictedPlayerState.pm_type == PM_INTERMISSION ) + { + // if in intermission, use a fixed value + fov_x = cg_fov.value; + } + else + { + fov_x = cg_fov.value; + if ( fov_x < 1 ) + { + fov_x = 1; + } + else if ( fov_x > 160 ) + { + fov_x = 160; + } + +#ifdef _XBOX + if (cg->predictedPlayerState.zoomMode) + { + fov_x = zoomFov[ClientManager::ActiveClientNum()]; + } + + // do smooth transitions for zooming + if (cg->predictedPlayerState.zoomMode) + { //zoomed/zooming in + f = ( cg->time - cg->zoomTime ) / (float)ZOOM_OUT_TIME; + if ( f > 1.0 ) { + fov_x = zoomFov[ClientManager::ActiveClientNum()]; + } else { + fov_x = fov_x + f * ( zoomFov[ClientManager::ActiveClientNum()] - fov_x ); + } + lastfov = fov_x; + } + else + { //zooming out + f = ( cg->time - cg->zoomTime ) / (float)ZOOM_OUT_TIME; + if ( f > 1.0 ) { + fov_x = fov_x; + } else { + fov_x = zoomFov[ClientManager::ActiveClientNum()] + f * ( fov_x - zoomFov[ClientManager::ActiveClientNum()]); + } + } + +#else // _XBOX + + if (cg->predictedPlayerState.zoomMode) + { + fov_x = zoomFov; + } + + // do smooth transitions for zooming + if (cg->predictedPlayerState.zoomMode) + { //zoomed/zooming in + f = ( cg->time - cg->zoomTime ) / (float)ZOOM_OUT_TIME; + if ( f > 1.0 ) { + fov_x = zoomFov; + } else { + fov_x = fov_x + f * ( zoomFov - fov_x ); + } + lastfov = fov_x; + } + else + { //zooming out + f = ( cg->time - cg->zoomTime ) / (float)ZOOM_OUT_TIME; + if ( f > 1.0 ) { + fov_x = fov_x; + } else { + fov_x = zoomFov + f * ( fov_x - zoomFov); + } + } +#endif // _XBOX + } + + x = cg->refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg->refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + cg->refdef.fov_x = fov_x; + cg->refdef.fov_y = fov_y; + + cg->refdef.rdflags |= RDF_SKYBOXPORTAL; + cg->refdef.rdflags |= RDF_DRAWSKYBOX; + + cg->refdef.time = cg->time; + + if ( !cg->hyperspace) + { //rww - also had to add this to add effects being rendered in portal sky areas properly. + trap_FX_AddScheduledEffects(qtrue); + } + + CG_AddPacketEntities(qtrue); //rww - There was no proper way to put real entities inside the portal view before. + //This will put specially flagged entities in the render. + + if (cg_skyOri) + { //ok, we want to orient the sky refdef vieworg based on the normal vieworg's relation to the ori pos + vec3_t dif; + + VectorSubtract(backuprefdef.vieworg, cg_skyOriPos, dif); + VectorScale(dif, cg_skyOriScale, dif); + VectorAdd(cg->refdef.vieworg, dif, cg->refdef.vieworg); + } + + if (cg_noFogOutsidePortal) + { //make sure no fog flag is stripped first, and make sure it is set on the normal refdef + cg->refdef.rdflags &= ~RDF_NOFOG; + backuprefdef.rdflags |= RDF_NOFOG; + } + + // draw the skybox + trap_R_RenderScene( &cg->refdef ); + + cg->refdef = backuprefdef; +} + +/* +===================== +CG_AddBufferedSound +===================== +*/ +void CG_AddBufferedSound( sfxHandle_t sfx ) { + if ( !sfx ) + return; + cg->soundBuffer[cg->soundBufferIn] = sfx; + cg->soundBufferIn = (cg->soundBufferIn + 1) % MAX_SOUNDBUFFER; + if (cg->soundBufferIn == cg->soundBufferOut) { + cg->soundBufferOut++; + } +} + +/* +===================== +CG_PlayBufferedSounds +===================== +*/ +static void CG_PlayBufferedSounds( void ) { + if ( cg->soundTime < cg->time ) { + if (cg->soundBufferOut != cg->soundBufferIn && cg->soundBuffer[cg->soundBufferOut]) { + trap_S_StartLocalSound(cg->soundBuffer[cg->soundBufferOut], CHAN_ANNOUNCER); + cg->soundBuffer[cg->soundBufferOut] = 0; + cg->soundBufferOut = (cg->soundBufferOut + 1) % MAX_SOUNDBUFFER; + cg->soundTime = cg->time + 750; + } + } +} + +void CG_UpdateSoundTrackers() +{ + int num; + centity_t *cent; + + for ( num = 0 ; num < ENTITYNUM_NONE ; num++ ) + { + cent = &cg_entities[num]; + + if (cent && (cent->currentState.eFlags & EF_SOUNDTRACKER) && cent->currentState.number == num) + //make sure the thing is valid at least. + { //keep sound for this entity updated in accordance with its attached entity at all times + if (cg->snap && cent->currentState.trickedentindex == cg->snap->ps.clientNum) + { //this is actually the player, so center the sound origin right on top of us + VectorCopy(cg->refdef.vieworg, cent->lerpOrigin); + trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); + } + else + { + trap_S_UpdateEntityPosition( cent->currentState.number, cg_entities[cent->currentState.trickedentindex].lerpOrigin ); + } + } + + if (cent->currentState.number == num) + { + //update all looping sounds.. + CG_S_UpdateLoopingSounds(num); + } + } +} + +//========================================================================= + +/* +================================ +Screen Effect stuff starts here +================================ +*/ +#define CAMERA_DEFAULT_FOV 90.0f +#define MAX_SHAKE_INTENSITY 16.0f + +cgscreffects_t cgScreenEffects; + +void CG_SE_UpdateShake( vec3_t origin, vec3_t angles ) +{ + vec3_t moveDir; + float intensity_scale, intensity; + int i; + +#ifdef _XBOX + if ( ClientManager::ActiveClient().shake_duration <= 0 ) +#else + if ( cgScreenEffects.shake_duration <= 0 ) +#endif + return; + +#ifdef _XBOX + if ( cg->time > ( ClientManager::ActiveClient().shake_start + ClientManager::ActiveClient().shake_duration ) ) +#else + if ( cg->time > ( cgScreenEffects.shake_start + cgScreenEffects.shake_duration ) ) +#endif + { +#ifdef _XBOX + ClientManager::ActiveClient().shake_intensity = 0; + ClientManager::ActiveClient().shake_duration = 0; + ClientManager::ActiveClient().shake_start = 0; + return; +#else + cgScreenEffects.shake_intensity = 0; + cgScreenEffects.shake_duration = 0; + cgScreenEffects.shake_start = 0; + return; +#endif + } + + cgScreenEffects.FOV = CAMERA_DEFAULT_FOV; + cgScreenEffects.FOV2 = CAMERA_DEFAULT_FOV; + + //intensity_scale now also takes into account FOV with 90.0 as normal +#ifdef _XBOX + intensity_scale = 1.0f - ( (float) ( cg->time - ClientManager::ActiveClient().shake_start ) / (float) ClientManager::ActiveClient().shake_duration ) * (((cgScreenEffects.FOV+cgScreenEffects.FOV2)/2.0f)/90.0f); +#else + intensity_scale = 1.0f - ( (float) ( cg->time - cgScreenEffects.shake_start ) / (float) cgScreenEffects.shake_duration ) * (((cgScreenEffects.FOV+cgScreenEffects.FOV2)/2.0f)/90.0f); +#endif + +#ifdef _XBOX + intensity = ClientManager::ActiveClient().shake_intensity * intensity_scale; +#else + intensity = cgScreenEffects.shake_intensity * intensity_scale; +#endif + + for ( i = 0; i < 3; i++ ) + { + moveDir[i] = ( crandom() * intensity ); + } + + //Move the camera + VectorAdd( origin, moveDir, origin ); + + for ( i=0; i < 2; i++ ) // Don't do ROLL + moveDir[i] = ( crandom() * intensity ); + + //Move the angles + VectorAdd( angles, moveDir, angles ); +} + +void CG_SE_UpdateMusic(void) +{ + if (cgScreenEffects.music_volume_multiplier < 0.1) + { + cgScreenEffects.music_volume_multiplier = 1.0; + return; + } + + if (cgScreenEffects.music_volume_time < cg->time) + { + if (cgScreenEffects.music_volume_multiplier != 1.0 || cgScreenEffects.music_volume_set) + { + char musMultStr[512]; + + cgScreenEffects.music_volume_multiplier += 0.1; + if (cgScreenEffects.music_volume_multiplier > 1.0) + { + cgScreenEffects.music_volume_multiplier = 1.0; + } + + Com_sprintf(musMultStr, sizeof(musMultStr), "%f", cgScreenEffects.music_volume_multiplier); + trap_Cvar_Set("s_musicMult", musMultStr); + + if (cgScreenEffects.music_volume_multiplier == 1.0) + { + cgScreenEffects.music_volume_set = qfalse; + } + else + { + cgScreenEffects.music_volume_time = cg->time + 200; + } + } + + return; + } + + if (!cgScreenEffects.music_volume_set) + { //if the volume_time is >= cg->time, we should have a volume multiplier set + char musMultStr[512]; + + Com_sprintf(musMultStr, sizeof(musMultStr), "%f", cgScreenEffects.music_volume_multiplier); + trap_Cvar_Set("s_musicMult", musMultStr); + cgScreenEffects.music_volume_set = qtrue; + } +} + +/* +================= +CG_CalcScreenEffects + +Currently just for screen shaking (and music volume management) +================= +*/ +void CG_CalcScreenEffects(void) +{ + CG_SE_UpdateShake(cg->refdef.vieworg, cg->refdef.viewangles); +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) +#endif + CG_SE_UpdateMusic(); +} + +void CGCam_Shake( float intensity, int duration ) +{ + if ( intensity > MAX_SHAKE_INTENSITY ) + intensity = MAX_SHAKE_INTENSITY; + +#ifdef _XBOX + ClientManager::ActiveClient().shake_intensity = intensity; + ClientManager::ActiveClient().shake_duration = duration; + ClientManager::ActiveClient().shake_start = cg->time; +#else + cgScreenEffects.shake_intensity = intensity; + cgScreenEffects.shake_duration = duration; + cgScreenEffects.shake_start = cg->time; +#endif +//JLFRUMBLE +#ifdef _XBOX +extern void FF_XboxShake(float intensity, int duration); + +FF_XboxShake(intensity, duration); + +#endif +} + +void CGCam_SetMusicMult( float multiplier, int duration ) +{ + if (multiplier < 0.1f) + { + multiplier = 0.1f; + } + + if (multiplier > 1.0f) + { + multiplier = 1.0f; + } + + cgScreenEffects.music_volume_multiplier = multiplier; + cgScreenEffects.music_volume_time = cg->time + duration; + cgScreenEffects.music_volume_set = qfalse; +} + +/* +================================ +Screen Effect stuff ends here +================================ +*/ + +/* +================= +CG_EmplacedView + +Keep view reasonably constrained in relation to gun -rww +================= +*/ +#include "../namespace_begin.h" +int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); +#include "../namespace_end.h" + +void CG_EmplacedView(vec3_t angles) +{ + float yaw; + int override; + + override = BG_EmplacedView(cg->refdef.viewangles, angles, &yaw, + cg_entities[cg->snap->ps.emplacedIndex].currentState.origin2[0]); + + if (override) + { + cg->refdef.viewangles[YAW] = yaw; + AnglesToAxis(cg->refdef.viewangles, cg->refdef.viewaxis); + + if (override == 2) + { + trap_SetClientForceAngle(cg->time + 5000, cg->refdef.viewangles); + } + } + + //we want to constrain the predicted player state viewangles as well + override = BG_EmplacedView(cg->predictedPlayerState.viewangles, angles, &yaw, + cg_entities[cg->snap->ps.emplacedIndex].currentState.origin2[0]); + if (override) + { + cg->predictedPlayerState.viewangles[YAW] = yaw; + } +} + +//specially add cent's for automap +static void CG_AddRefentForAutoMap(centity_t *cent) +{ + refEntity_t ent; + vec3_t flat; + + if (cent->currentState.eFlags & EF_NODRAW) + { + return; + } + + memset(&ent, 0, sizeof(refEntity_t)); + ent.reType = RT_MODEL; + + VectorCopy(cent->lerpAngles, flat); + flat[PITCH] = flat[ROLL] = 0.0f; + + VectorCopy(cent->lerpOrigin, ent.origin); + VectorCopy(flat, ent.angles); + AnglesToAxis(flat, ent.axis); + + if (cent->ghoul2 && + (cent->currentState.eType == ET_PLAYER || + cent->currentState.eType == ET_NPC || + cent->currentState.modelGhoul2)) + { //using a ghoul2 model + ent.ghoul2 = cent->ghoul2; + ent.radius = cent->currentState.g2radius; + + if (!ent.radius) + { + ent.radius = 64.0f; + } + } + else + { //then assume a standard indexed model + ent.hModel = cgs.gameModels[cent->currentState.modelindex]; + } + + trap_R_AddRefEntityToScene(&ent); +} + +//add all entities that would be on the radar +void CG_AddRadarAutomapEnts(void) +{ + int i = 0; + + //first add yourself + CG_AddRefentForAutoMap(&cg_entities[cg->predictedPlayerState.clientNum]); + + while (i < cg->radarEntityCount) + { + CG_AddRefentForAutoMap(&cg_entities[cg->radarEntities[i]]); + i++; + } +} + +/* +================ +CG_DrawAutoMap + +Draws the automap scene. -rww +================ +*/ +float cg_autoMapZoom = 512.0f; +float cg_autoMapZoomMainOffset = 0.0f; +vec3_t cg_autoMapAngle = {90.0f, 0.0f, 0.0f}; +autoMapInput_t cg_autoMapInput; +int cg_autoMapInputTime = 0; +#define SIDEFRAME_WIDTH 16 +#define SIDEFRAME_HEIGHT 32 +void CG_DrawAutoMap(void) +{ + clientInfo_t *local; + refdef_t refdef; + trace_t tr; + vec3_t fwd; + vec3_t playerMins, playerMaxs; + int vWidth, vHeight; + float hScale, vScale; + float x, y, w, h; + + if (!cg_autoMap.integer) + { //don't do anything then + return; + } + + if ( cg->snap->ps.stats[STAT_HEALTH] <= 0 ) + { //don't show when dead + return; + } + + if ( (cg->predictedPlayerState.pm_flags & PMF_FOLLOW) || cg->predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR ) + { //don't show when spec + return; + } + + local = &cgs.clientinfo[ cg->predictedPlayerState.clientNum ]; + if ( !local->infoValid ) + { //don't show if bad ci + return; + } + + if (cgs.gametype < GT_TEAM) + { //don't show in non-team gametypes + return; + } + + if (cg_autoMapInputTime >= cg->time) + { + if (cg_autoMapInput.up) + { + cg_autoMapZoom -= cg_autoMapInput.up; + if (cg_autoMapZoom < cg_autoMapZoomMainOffset+64.0f) + { + cg_autoMapZoom = cg_autoMapZoomMainOffset+64.0f; + } + } + + if (cg_autoMapInput.down) + { + cg_autoMapZoom += cg_autoMapInput.down; + if (cg_autoMapZoom > cg_autoMapZoomMainOffset+4096.0f) + { + cg_autoMapZoom = cg_autoMapZoomMainOffset+4096.0f; + } + } + + if (cg_autoMapInput.yaw) + { + cg_autoMapAngle[YAW] += cg_autoMapInput.yaw; + } + + if (cg_autoMapInput.pitch) + { + cg_autoMapAngle[PITCH] += cg_autoMapInput.pitch; + } + + if (cg_autoMapInput.goToDefaults) + { + cg_autoMapZoom = 512.0f; + VectorSet(cg_autoMapAngle, 90.0f, 0.0f, 0.0f); + } + } + + memset( &refdef, 0, sizeof( refdef ) ); + + refdef.rdflags = (RDF_NOWORLDMODEL|RDF_AUTOMAP); + + VectorCopy(cg->predictedPlayerState.origin, refdef.vieworg); + VectorCopy(cg_autoMapAngle, refdef.viewangles); + + //scale out in the direction of the view angles base on the zoom factor + AngleVectors(refdef.viewangles, fwd, 0, 0); + VectorMA(refdef.vieworg, -cg_autoMapZoom, fwd, refdef.vieworg); + + AnglesToAxis(refdef.viewangles, refdef.viewaxis); + + refdef.fov_x = 50; + refdef.fov_y = 50; + + //guess this doesn't need to be done every frame, but eh + trap_R_GetRealRes(&vWidth, &vHeight); + + //set scaling values so that the 640x480 will result at 1.0/1.0 +#ifdef _XBOX + if(cg->widescreen) + hScale = vWidth/720.0f; + else +#endif + hScale = vWidth/640.0f; + vScale = vHeight/480.0f; + + x = cg_autoMapX.value; + y = cg_autoMapY.value; + w = cg_autoMapW.value; + h = cg_autoMapH.value; + + refdef.x = x*hScale; + refdef.y = y*vScale; + refdef.width = w*hScale; + refdef.height = h*vScale; + + CG_DrawPic(x-SIDEFRAME_WIDTH, y, SIDEFRAME_WIDTH, h, cgs.media.wireframeAutomapFrame_left); + CG_DrawPic(x+w, y, SIDEFRAME_WIDTH, h, cgs.media.wireframeAutomapFrame_right); + CG_DrawPic(x-SIDEFRAME_WIDTH, y-SIDEFRAME_HEIGHT, w+(SIDEFRAME_WIDTH*2), SIDEFRAME_HEIGHT, cgs.media.wireframeAutomapFrame_top); + CG_DrawPic(x-SIDEFRAME_WIDTH, y+h, w+(SIDEFRAME_WIDTH*2), SIDEFRAME_HEIGHT, cgs.media.wireframeAutomapFrame_bottom); + + refdef.time = cg->time; + + trap_R_ClearScene(); + CG_AddRadarAutomapEnts(); + + if (cg->predictedPlayerState.m_iVehicleNum && + cg_entities[cg->predictedPlayerState.m_iVehicleNum].currentState.eType == ET_NPC && + cg_entities[cg->predictedPlayerState.m_iVehicleNum].currentState.NPC_class == CLASS_VEHICLE && + cg_entities[cg->predictedPlayerState.m_iVehicleNum].m_pVehicle && + cg_entities[cg->predictedPlayerState.m_iVehicleNum].m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { //constantly adjust to current height + trap_R_AutomapElevAdj(cg->predictedPlayerState.origin[2]); + } + else + { + //Trace down and set the ground elevation as the main automap elevation point + VectorSet(playerMins, -15, -15, DEFAULT_MINS_2); + VectorSet(playerMaxs, 15, 15, DEFAULT_MAXS_2); + + VectorCopy(cg->predictedPlayerState.origin, fwd); + fwd[2] -= 4096.0f; + CG_Trace(&tr, cg->predictedPlayerState.origin, playerMins, playerMaxs, fwd, cg->predictedPlayerState.clientNum, MASK_SOLID); + + if (!tr.startsolid && !tr.allsolid) + { + trap_R_AutomapElevAdj(tr.endpos[2]); + } + } + trap_R_RenderScene( &refdef ); +} + +/* +================= +CG_DrawActiveFrame + +Generates and draws a game scene and status information at the given time. +================= +*/ +static qboolean cg_rangedFogging = qfalse; //so we know if we should go back to normal fog +float cg_linearFogOverride = 0.0f; //designer-specified override for linear fogging style + +#include "../namespace_begin.h" +extern void BG_VehicleTurnRateForSpeed( Vehicle_t *pVeh, float speed, float *mPitchOverride, float *mYawOverride ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +#include "../namespace_end.h" + +extern qboolean cgQueueLoad; +extern void CG_ActualLoadDeferredPlayers( void ); + +static int cg_siegeClassIndex = -2; + +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { + int inwater; + const char *cstr; + float mSensitivity = cg->zoomSensitivity; + float mPitchOverride = 0.0f; + float mYawOverride = 0.0f; + float mSensitivityOverride = 0.0f; + qboolean bUseFighterPitch = qfalse; + static centity_t *veh = NULL; + qboolean isFighter = qfalse; + + if (cgQueueLoad) + { //do this before you start messing around with adding ghoul2 refents and crap + CG_ActualLoadDeferredPlayers(); + cgQueueLoad = qfalse; + } + + cg->time = serverTime; + cg->demoPlayback = demoPlayback; + + if (cg->snap && ClientManager::ActiveClient().myTeam != cg->snap->ps.persistant[PERS_TEAM]) + { + ClientManager::ActiveClient().myTeam = cg->snap->ps.persistant[PERS_TEAM]; + } + if (cgs.gametype == GT_SIEGE && + cg->snap && + cg_siegeClassIndex != cgs.clientinfo[cg->snap->ps.clientNum].siegeIndex) + { + cg_siegeClassIndex = cgs.clientinfo[cg->snap->ps.clientNum].siegeIndex; + if (cg_siegeClassIndex == -1) + { + trap_Cvar_Set("ui_mySiegeClass", ""); + } + else + { + trap_Cvar_Set("ui_mySiegeClass", bgSiegeClasses[cg_siegeClassIndex].name); + } + } + + // update cvars + CG_UpdateCvars(); + + // if we are only updating the screen as a loading + // pacifier, don't even try to read snapshots + if ( cg->infoScreenText[0] != 0 ) { + CG_DrawInformation(); + return; + } + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) { +#endif + trap_FX_AdjustTime( cg->time ); + + CG_RunLightStyles(); + + // any looped sounds will be respecified as entities + // are added to the render list + trap_S_ClearLoopingSounds(); + + // clear all the render lists + trap_R_ClearScene(); +#ifdef _XBOX + } +#endif + + // set up cg->snap and possibly cg->nextSnap + CG_ProcessSnapshots(); + + trap_ROFF_UpdateEntities(); + + // if we haven't received any snapshots yet, all + // we can draw is the information screen + if ( !cg->snap || ( cg->snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) && + cls.state != CA_ACTIVE ) + { +#if 0 + // Transition from zero to negative one on the snapshot timeout. + // The reason we do this is because the first client frame is responsible for + // some farily slow processing (such as weather) and we dont want to include + // that processing time into our calculations + if ( !cg->snapshotTimeoutTime ) + { + cg->snapshotTimeoutTime = -1; + } + // Transition the snapshot timeout time from -1 to the current time in + // milliseconds which will start the timeout. + else if ( cg->snapshotTimeoutTime == -1 ) + { + cg->snapshotTimeoutTime = trap_Milliseconds ( ); + } + + // If we have been waiting too long then just error out + if ( cg->snapshotTimeoutTime > 0 && (trap_Milliseconds ( ) - cg->snapshotTimeoutTime > cg_snapshotTimeout.integer * 1000) ) + { + Com_Error ( ERR_DROP, CG_GetStringEdString("MP_SVGAME", "SNAPSHOT_TIMEOUT")); + return; + } +#endif + CG_DrawInformation(); + return; + } + + // let the client system know what our weapon and zoom settings are + if (cg->snap && cg->snap->ps.saberLockTime > cg->time) + { + mSensitivity = 0.01f; + } + else if (cg->predictedPlayerState.weapon == WP_EMPLACED_GUN) + { //lower sens for emplaced guns and vehicles + mSensitivity = 0.2f; + } + else if (cg->predictedPlayerState.m_iVehicleNum//in a vehicle + && !cg->predictedPlayerState.generic1 )//not as a passenger + { + centity_t *cent = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + if ( cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo + && cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + { + BG_VehicleTurnRateForSpeed( cent->m_pVehicle, cent->currentState.speed, &mPitchOverride, &mYawOverride ); + //mSensitivityOverride = 5.0f;//old default value + mSensitivityOverride = 0.0f; + bUseFighterPitch = qtrue; + trap_SetUserCmdValue( cg->weaponSelect, mSensitivity, mPitchOverride, mYawOverride, mSensitivityOverride, cg->forceSelect, cg->itemSelect, bUseFighterPitch ); + isFighter = qtrue; + } + } + + if ( !isFighter ) + { + if (cg->predictedPlayerState.m_iVehicleNum) + { + veh = &cg_entities[cg->predictedPlayerState.m_iVehicleNum]; + } + if (veh && + veh->currentState.eType == ET_NPC && + veh->currentState.NPC_class == CLASS_VEHICLE && + veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER && + bg_fighterAltControl.integer) + { + trap_SetUserCmdValue( cg->weaponSelect, mSensitivity, mPitchOverride, mYawOverride, 0.0f, cg->forceSelect, cg->itemSelect, qtrue ); + veh = NULL; //this is done because I don't want an extra assign each frame because I am so perfect and super efficient. + } + else + { + trap_SetUserCmdValue( cg->weaponSelect, mSensitivity, mPitchOverride, mYawOverride, 0.0f, cg->forceSelect, cg->itemSelect, qfalse ); + } + } + + // this counter will be bumped for every valid scene we generate + cg->clientFrame++; + + // update cg->predictedPlayerState + CG_PredictPlayerState(); + + // decide on third person view +#ifdef _XBOX + cg->renderingThirdPerson = ClientManager::ActiveClient().cg_thirdPerson || (cg->snap->ps.stats[STAT_HEALTH] <= 0); +#else + cg->renderingThirdPerson = cg_thirdPerson.integer || (cg->snap->ps.stats[STAT_HEALTH] <= 0); +#endif + + if (cg->snap->ps.stats[STAT_HEALTH] > 0) + { + if (cg->predictedPlayerState.weapon == WP_EMPLACED_GUN && cg->predictedPlayerState.emplacedIndex /*&& + cg_entities[cg->predictedPlayerState.emplacedIndex].currentState.weapon == WP_NONE*/) + { //force third person for e-web and emplaced use + cg->renderingThirdPerson = 1; + } + else if (cg->predictedPlayerState.weapon == WP_SABER || cg->predictedPlayerState.weapon == WP_MELEE || + BG_InGrappleMove(cg->predictedPlayerState.torsoAnim) || BG_InGrappleMove(cg->predictedPlayerState.legsAnim) || + cg->predictedPlayerState.forceHandExtend == HANDEXTEND_KNOCKDOWN || cg->predictedPlayerState.fallingToDeath || + cg->predictedPlayerState.m_iVehicleNum || PM_InKnockDown(&cg->predictedPlayerState)) + { + if (cg_fpls.integer && cg->predictedPlayerState.weapon == WP_SABER) + { //force to first person for fpls + cg->renderingThirdPerson = 0; + } + else + { + cg->renderingThirdPerson = 1; + } + } + else if (cg->snap->ps.zoomMode) + { //always force first person when zoomed + cg->renderingThirdPerson = 0; + } + } + + if (cg->predictedPlayerState.pm_type == PM_SPECTATOR) + { //always first person for spec + cg->renderingThirdPerson = 0; + } + + + if (cg->snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR) + { + cg->renderingThirdPerson = 0; + } + + // build cg->refdef + inwater = CG_CalcViewValues(); + + if (cg_linearFogOverride) + { + trap_R_SetRangeFog(-cg_linearFogOverride); + } + else if (cg->predictedPlayerState.zoomMode) + { //zooming with binoculars or sniper, set the fog range based on the zoom level -rww + cg_rangedFogging = qtrue; + //smaller the fov the less fog we have between the view and cull dist + trap_R_SetRangeFog(cg->refdef.fov_x*64.0f); + } + else if (cg_rangedFogging) + { //disable it + cg_rangedFogging = qfalse; + trap_R_SetRangeFog(0.0f); + } + + cstr = CG_ConfigString(CS_SKYBOXORG); + + if (cstr && cstr[0]) + { //we have a skyportal + CG_DrawSkyBoxPortal(cstr); + } + + CG_CalcScreenEffects(); + + // first person blend blobs, done after AnglesToAxis + if ( !cg->renderingThirdPerson && cg->predictedPlayerState.pm_type != PM_SPECTATOR ) { + CG_DamageBlendBlob(); + } + +#ifdef _XBOX + // Clear the cent->updated list, so that entities will only get updated once per frame + if((ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) + || (ClientManager::splitScreenMode == qfalse)) { + for(int i = 0; i < MAX_GENTITIES; i++) + cg_entities[i].updatedThisFrame = false; + } +#endif + + // build the render lists + if ( !cg->hyperspace ) { + CG_AddPacketEntities(qfalse); // adter calcViewValues, so predicted player state is correct +//#ifdef _XBOX +// if(ClientManager::ActiveClientNum() == 0) +//#endif + CG_AddMarks(); + CG_AddParticles (); +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) +#endif + CG_AddLocalEntities(); + CG_DrawMiscEnts(); + } + CG_AddViewWeapon( &cg->predictedPlayerState ); + + if ( !cg->hyperspace) + { + trap_FX_AddScheduledEffects(qfalse); + } + + // add buffered sounds + CG_PlayBufferedSounds(); + + // finish up the rest of the refdef + if ( cg->testModelEntity.hModel ) { + CG_AddTestModel(); + } + cg->refdef.time = cg->time; + memcpy( cg->refdef.areamask, cg->snap->areamask, sizeof( cg->refdef.areamask ) ); + + // warning sounds when powerup is wearing off + CG_PowerupTimerSounds(); + + // if there are any entities flagged as sound trackers and attached to other entities, update their sound pos + CG_UpdateSoundTrackers(); + +#ifdef _XBOX + if (gCGHasFallVector[ClientManager::ActiveClientNum()]) +#else + if (gCGHasFallVector) +#endif + { + vec3_t lookAng; + + VectorSubtract(cg->snap->ps.origin, cg->refdef.vieworg, lookAng); + VectorNormalize(lookAng); + vectoangles(lookAng, lookAng); + +#ifdef _XBOX + VectorCopy(gCGFallVector[ClientManager::ActiveClientNum()], cg->refdef.vieworg); +#else + VectorCopy(gCGFallVector, cg->refdef.vieworg); +#endif + AnglesToAxis(lookAng, cg->refdef.viewaxis); + } + + //This is done from the vieworg to get origin for non-attenuated sounds + cstr = CG_ConfigString( CS_GLOBAL_AMBIENT_SET ); + + if (cstr && cstr[0]) + { + trap_S_UpdateAmbientSet( cstr, cg->refdef.vieworg ); + } + + // update audio positions + trap_S_Respatialize( cg->snap->ps.clientNum, cg->refdef.vieworg, cg->refdef.viewaxis, inwater ); + + // make sure the lagometerSample and frame timing isn't done twice when in stereo + if ( stereoView != STEREO_RIGHT ) { + cg->frametime = cg->time - cg->oldTime; + if ( cg->frametime < 0 ) { + cg->frametime = 0; + } + cg->oldTime = cg->time; +// CG_AddLagometerFrameInfo(); + } + if (cg_timescale.value != cg_timescaleFadeEnd.value) { + if (cg_timescale.value < cg_timescaleFadeEnd.value) { + cg_timescale.value += cg_timescaleFadeSpeed.value * ((float)cg->frametime) / 1000; + if (cg_timescale.value > cg_timescaleFadeEnd.value) + cg_timescale.value = cg_timescaleFadeEnd.value; + } + else { + cg_timescale.value -= cg_timescaleFadeSpeed.value * ((float)cg->frametime) / 1000; + if (cg_timescale.value < cg_timescaleFadeEnd.value) + cg_timescale.value = cg_timescaleFadeEnd.value; + } + if (cg_timescaleFadeSpeed.value) { + trap_Cvar_Set("timescale", va("%f", cg_timescale.value)); + } + } + + // actually issue the rendering calls + CG_DrawActive( stereoView ); + + CG_DrawAutoMap(); + + if ( cg_stats.integer ) { + CG_Printf( "cg->clientFrame:%i\n", cg->clientFrame ); + } +} + diff --git a/codemp/cgame/cg_weaponinit.c b/codemp/cgame/cg_weaponinit.c new file mode 100644 index 0000000..294f9d5 --- /dev/null +++ b/codemp/cgame/cg_weaponinit.c @@ -0,0 +1,592 @@ +// +// cg_weaponinit.c -- events and effects dealing with weapons +#include "cg_local.h" +#include "fx_local.h" + + +/* +================= +CG_RegisterWeapon + +The server says this item is used on this level +================= +*/ +void CG_RegisterWeapon( int weaponNum) { + weaponInfo_t *weaponInfo; + gitem_t *item, *ammo; + char path[MAX_QPATH]; + vec3_t mins, maxs; + int i; + + weaponInfo = &cg_weapons[weaponNum]; + + if ( weaponNum == 0 ) { + return; + } + + if ( weaponInfo->registered ) { + return; + } + + memset( weaponInfo, 0, sizeof( *weaponInfo ) ); + weaponInfo->registered = qtrue; + + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { + if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { + weaponInfo->item = item; + break; + } + } + if ( !item->classname ) { + CG_Error( "Couldn't find weapon %i", weaponNum ); + } + CG_RegisterItemVisuals( item - bg_itemlist ); + + // load cmodel before model so filecache works + weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model[0] ); + // load in-view model also + weaponInfo->viewModel = trap_R_RegisterModel(item->view_model); + + // calc midpoint for rotation + trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); + for ( i = 0 ; i < 3 ; i++ ) { + weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); + } + + weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); + weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon ); + + for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { + if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { + break; + } + } + if ( ammo->classname && ammo->world_model[0] ) { + weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] ); + } + +// strcpy( path, item->view_model ); +// COM_StripExtension( path, path ); +// strcat( path, "_flash.md3" ); + weaponInfo->flashModel = 0;//trap_R_RegisterModel( path ); + + if (weaponNum == WP_DISRUPTOR || + weaponNum == WP_FLECHETTE || + weaponNum == WP_REPEATER || + weaponNum == WP_ROCKET_LAUNCHER) + { + strcpy( path, item->view_model ); + COM_StripExtension( path, path ); + strcat( path, "_barrel.md3" ); + weaponInfo->barrelModel = trap_R_RegisterModel( path ); + } + else if (weaponNum == WP_STUN_BATON) + { //only weapon with more than 1 barrel.. + trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel.md3"); + trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel2.md3"); + trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel3.md3"); + } + else + { + weaponInfo->barrelModel = 0; + } + + if (weaponNum != WP_SABER) + { + strcpy( path, item->view_model ); + COM_StripExtension( path, path ); + strcat( path, "_hand.md3" ); + weaponInfo->handsModel = trap_R_RegisterModel( path ); + } + else + { + weaponInfo->handsModel = 0; + } + +// if ( !weaponInfo->handsModel ) { +// weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); +// } + + switch ( weaponNum ) { + case WP_STUN_BATON: + case WP_MELEE: +/* MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/saber/saberhum.wav" ); +// weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav" ); +*/ + //trap_R_RegisterShader( "gfx/effects/stunPass" ); + trap_FX_RegisterEffect( "stunBaton/flesh_impact" ); + + if (weaponNum == WP_STUN_BATON) + { + trap_S_RegisterSound( "sound/weapons/baton/idle.wav" ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/baton/fire.mp3" ); + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/baton/fire.mp3" ); + } + else + { + /* + int j = 0; + + while (j < 4) + { + weaponInfo->flashSound[j] = trap_S_RegisterSound( va("sound/weapons/melee/swing%i", j+1) ); + weaponInfo->altFlashSound[j] = weaponInfo->flashSound[j]; + j++; + } + */ + //No longer needed, animsound config plays them for us + } + break; + case WP_SABER: + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ); + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/saber/saber_w.glm" ); + break; + + case WP_CONCUSSION: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/concussion/select.wav"); + + weaponInfo->flashSound[0] = NULL_SOUND; + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "concussion/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; + //weaponInfo->missileDlightColor= {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_ConcussionProjectileThink; + + weaponInfo->altFlashSound[0] = NULL_SOUND; + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = trap_S_RegisterSound( "sound/weapons/bryar/altcharge.wav"); + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "concussion/altmuzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; + //weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_ConcussionProjectileThink; + + cgs.effects.disruptorAltMissEffect = trap_FX_RegisterEffect( "disruptor/alt_miss" ); + + cgs.effects.concussionShotEffect = trap_FX_RegisterEffect( "concussion/shot" ); + cgs.effects.concussionImpactEffect = trap_FX_RegisterEffect( "concussion/explosion" ); + trap_R_RegisterShader("gfx/effects/blueLine"); + trap_R_RegisterShader("gfx/misc/whiteline2"); + break; + + case WP_BRYAR_PISTOL: + case WP_BRYAR_OLD: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/bryar/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bryar/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "bryar/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; + //weaponInfo->missileDlightColor= {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_BryarProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/bryar/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = trap_S_RegisterSound( "sound/weapons/bryar/altcharge.wav"); + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "bryar/muzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; + //weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_BryarAltProjectileThink; + + cgs.effects.bryarShotEffect = trap_FX_RegisterEffect( "bryar/shot" ); + cgs.effects.bryarPowerupShotEffect = trap_FX_RegisterEffect( "bryar/crackleShot" ); + cgs.effects.bryarWallImpactEffect = trap_FX_RegisterEffect( "bryar/wall_impact" ); + cgs.effects.bryarWallImpactEffect2 = trap_FX_RegisterEffect( "bryar/wall_impact2" ); + cgs.effects.bryarWallImpactEffect3 = trap_FX_RegisterEffect( "bryar/wall_impact3" ); + cgs.effects.bryarFleshImpactEffect = trap_FX_RegisterEffect( "bryar/flesh_impact" ); + cgs.effects.bryarDroidImpactEffect = trap_FX_RegisterEffect( "bryar/droid_impact" ); + + cgs.media.bryarFrontFlash = trap_R_RegisterShader( "gfx/effects/bryarFrontFlash" ); + + // Note these are temp shared effects + trap_FX_RegisterEffect("blaster/wall_impact.efx"); + trap_FX_RegisterEffect("blaster/flesh_impact.efx"); + + break; + + case WP_BLASTER: + case WP_EMPLACED_GUN: //rww - just use the same as this for now.. + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/blaster/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/blaster/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "blaster/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_BlasterProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/blaster/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "blaster/muzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_BlasterProjectileThink; + + trap_FX_RegisterEffect( "blaster/deflect" ); + cgs.effects.blasterShotEffect = trap_FX_RegisterEffect( "blaster/shot" ); + cgs.effects.blasterWallImpactEffect = trap_FX_RegisterEffect( "blaster/wall_impact" ); + cgs.effects.blasterFleshImpactEffect = trap_FX_RegisterEffect( "blaster/flesh_impact" ); + cgs.effects.blasterDroidImpactEffect = trap_FX_RegisterEffect( "blaster/droid_impact" ); + break; + + case WP_DISRUPTOR: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/disruptor/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/disruptor/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "disruptor/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = 0; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/disruptor/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = trap_S_RegisterSound("sound/weapons/disruptor/altCharge.wav"); + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "disruptor/muzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = 0; + + cgs.effects.disruptorRingsEffect = trap_FX_RegisterEffect( "disruptor/rings" ); + cgs.effects.disruptorProjectileEffect = trap_FX_RegisterEffect( "disruptor/projectile" ); + cgs.effects.disruptorWallImpactEffect = trap_FX_RegisterEffect( "disruptor/wall_impact" ); + cgs.effects.disruptorFleshImpactEffect = trap_FX_RegisterEffect( "disruptor/flesh_impact" ); + cgs.effects.disruptorAltMissEffect = trap_FX_RegisterEffect( "disruptor/alt_miss" ); + cgs.effects.disruptorAltHitEffect = trap_FX_RegisterEffect( "disruptor/alt_hit" ); + + trap_R_RegisterShader( "gfx/effects/redLine" ); + trap_R_RegisterShader( "gfx/misc/whiteline2" ); + trap_R_RegisterShader( "gfx/effects/smokeTrail" ); + + trap_S_RegisterSound("sound/weapons/disruptor/zoomstart.wav"); + trap_S_RegisterSound("sound/weapons/disruptor/zoomend.wav"); + + // Disruptor gun zoom interface + cgs.media.disruptorMask = trap_R_RegisterShader( "gfx/2d/cropCircle2"); + cgs.media.disruptorInsert = trap_R_RegisterShader( "gfx/2d/cropCircle"); + cgs.media.disruptorLight = trap_R_RegisterShader( "gfx/2d/cropCircleGlow" ); + cgs.media.disruptorInsertTick = trap_R_RegisterShader( "gfx/2d/insertTick" ); + cgs.media.disruptorChargeShader = trap_R_RegisterShaderNoMip("gfx/2d/crop_charge"); + + cgs.media.disruptorZoomLoop = trap_S_RegisterSound( "sound/weapons/disruptor/zoomloop.wav" ); + break; + + case WP_BOWCASTER: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/bowcaster/select.wav"); + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/bowcaster/fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "bowcaster/muzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor = {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_BowcasterProjectileThink; + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bowcaster/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = trap_S_RegisterSound( "sound/weapons/bowcaster/altcharge.wav"); + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "bowcaster/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor= {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_BowcasterAltProjectileThink; + + cgs.effects.bowcasterShotEffect = trap_FX_RegisterEffect( "bowcaster/shot" ); + cgs.effects.bowcasterImpactEffect = trap_FX_RegisterEffect( "bowcaster/explosion" ); + + trap_FX_RegisterEffect( "bowcaster/deflect" ); + + cgs.media.greenFrontFlash = trap_R_RegisterShader( "gfx/effects/greenFrontFlash" ); + break; + + case WP_REPEATER: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/repeater/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/repeater/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "repeater/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_RepeaterProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/repeater/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "repeater/muzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_RepeaterAltProjectileThink; + + cgs.effects.repeaterProjectileEffect = trap_FX_RegisterEffect( "repeater/projectile" ); + cgs.effects.repeaterAltProjectileEffect = trap_FX_RegisterEffect( "repeater/alt_projectile" ); + cgs.effects.repeaterWallImpactEffect = trap_FX_RegisterEffect( "repeater/wall_impact" ); + cgs.effects.repeaterFleshImpactEffect = trap_FX_RegisterEffect( "repeater/flesh_impact" ); + //cgs.effects.repeaterAltWallImpactEffect = trap_FX_RegisterEffect( "repeater/alt_wall_impact" ); + cgs.effects.repeaterAltWallImpactEffect = trap_FX_RegisterEffect( "repeater/concussion" ); + break; + + case WP_DEMP2: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/demp2/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound("sound/weapons/demp2/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect("demp2/muzzle_flash"); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_DEMP2_ProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound("sound/weapons/demp2/altfire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = trap_S_RegisterSound("sound/weapons/demp2/altCharge.wav"); + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect("demp2/muzzle_flash"); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = 0; + + cgs.effects.demp2ProjectileEffect = trap_FX_RegisterEffect( "demp2/projectile" ); + cgs.effects.demp2WallImpactEffect = trap_FX_RegisterEffect( "demp2/wall_impact" ); + cgs.effects.demp2FleshImpactEffect = trap_FX_RegisterEffect( "demp2/flesh_impact" ); + + cgs.media.demp2Shell = trap_R_RegisterModel( "models/items/sphere.md3" ); + cgs.media.demp2ShellShader = trap_R_RegisterShader( "gfx/effects/demp2shell" ); + + cgs.media.lightningFlash = trap_R_RegisterShader("gfx/misc/lightningFlash"); + break; + + case WP_FLECHETTE: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/flechette/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/flechette/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "flechette/muzzle_flash" ); + weaponInfo->missileModel = trap_R_RegisterModel("models/weapons2/golan_arms/projectileMain.md3"); + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_FlechetteProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/flechette/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "flechette/muzzle_flash" ); + weaponInfo->altMissileModel = trap_R_RegisterModel( "models/weapons2/golan_arms/projectile.md3" ); + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_FlechetteAltProjectileThink; + + cgs.effects.flechetteShotEffect = trap_FX_RegisterEffect( "flechette/shot" ); + cgs.effects.flechetteAltShotEffect = trap_FX_RegisterEffect( "flechette/alt_shot" ); + cgs.effects.flechetteWallImpactEffect = trap_FX_RegisterEffect( "flechette/wall_impact" ); + cgs.effects.flechetteFleshImpactEffect = trap_FX_RegisterEffect( "flechette/flesh_impact" ); + break; + + case WP_ROCKET_LAUNCHER: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/rocket/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "rocket/muzzle_flash" ); //trap_FX_RegisterEffect( "rocket/muzzle_flash2" ); + //flash2 still looks crappy with the fx bolt stuff. Because the fx bolt stuff doesn't work entirely right. + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/merr_sonn/projectile.md3" ); + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/missleloop.wav"); + weaponInfo->missileDlight = 125; + VectorSet(weaponInfo->missileDlightColor, 1.0, 1.0, 0.5); + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_RocketProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "rocket/altmuzzle_flash" ); + weaponInfo->altMissileModel = trap_R_RegisterModel( "models/weapons2/merr_sonn/projectile.md3" ); + weaponInfo->altMissileSound = trap_S_RegisterSound( "sound/weapons/rocket/missleloop.wav"); + weaponInfo->altMissileDlight = 125; + VectorSet(weaponInfo->altMissileDlightColor, 1.0, 1.0, 0.5); + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_RocketAltProjectileThink; + + cgs.effects.rocketShotEffect = trap_FX_RegisterEffect( "rocket/shot" ); + cgs.effects.rocketExplosionEffect = trap_FX_RegisterEffect( "rocket/explosion" ); + + trap_R_RegisterShaderNoMip( "gfx/2d/wedge" ); + trap_R_RegisterShaderNoMip( "gfx/2d/lock" ); + + trap_S_RegisterSound( "sound/weapons/rocket/lock.wav" ); + trap_S_RegisterSound( "sound/weapons/rocket/tick.wav" ); + break; + + case WP_THERMAL: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/thermal/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/thermal/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = trap_S_RegisterSound( "sound/weapons/thermal/charge.wav"); + weaponInfo->muzzleEffect = NULL_FX; + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/thermal/thermal_proj.md3" ); + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = 0; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/thermal/fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = trap_S_RegisterSound( "sound/weapons/thermal/charge.wav"); + weaponInfo->altMuzzleEffect = NULL_FX; + weaponInfo->altMissileModel = trap_R_RegisterModel( "models/weapons2/thermal/thermal_proj.md3" ); + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = 0; + + cgs.effects.thermalExplosionEffect = trap_FX_RegisterEffect( "thermal/explosion" ); + cgs.effects.thermalShockwaveEffect = trap_FX_RegisterEffect( "thermal/shockwave" ); + + cgs.media.grenadeBounce1 = trap_S_RegisterSound( "sound/weapons/thermal/bounce1.wav" ); + cgs.media.grenadeBounce2 = trap_S_RegisterSound( "sound/weapons/thermal/bounce2.wav" ); + + trap_S_RegisterSound( "sound/weapons/thermal/thermloop.wav" ); + trap_S_RegisterSound( "sound/weapons/thermal/warning.wav" ); + + break; + + case WP_TRIP_MINE: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/detpack/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/laser_trap/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = NULL_FX; + weaponInfo->missileModel = 0;//trap_R_RegisterModel( "models/weapons2/laser_trap/laser_trap_w.md3" ); + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = 0; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/laser_trap/fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = NULL_FX; + weaponInfo->altMissileModel = 0;//trap_R_RegisterModel( "models/weapons2/laser_trap/laser_trap_w.md3" ); + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = 0; + + cgs.effects.tripmineLaserFX = trap_FX_RegisterEffect("tripMine/laserMP.efx"); + cgs.effects.tripmineGlowFX = trap_FX_RegisterEffect("tripMine/glowbit.efx"); + + trap_FX_RegisterEffect( "tripMine/explosion" ); + // NOTENOTE temp stuff + trap_S_RegisterSound( "sound/weapons/laser_trap/stick.wav" ); + trap_S_RegisterSound( "sound/weapons/laser_trap/warning.wav" ); + break; + + case WP_DET_PACK: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/detpack/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/detpack/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = NULL_FX; + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/detpack/det_pack.md3" ); + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = 0; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/detpack/fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = NULL_FX; + weaponInfo->altMissileModel = trap_R_RegisterModel( "models/weapons2/detpack/det_pack.md3" ); + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = 0; + + trap_R_RegisterModel( "models/weapons2/detpack/det_pack.md3" ); + trap_S_RegisterSound( "sound/weapons/detpack/stick.wav" ); + trap_S_RegisterSound( "sound/weapons/detpack/warning.wav" ); + trap_S_RegisterSound( "sound/weapons/explosions/explode5.wav" ); + break; + case WP_TURRET: + weaponInfo->flashSound[0] = NULL_SOUND; + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = NULL_HANDLE; + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_TurretProjectileThink; + + trap_FX_RegisterEffect("effects/blaster/wall_impact.efx"); + trap_FX_RegisterEffect("effects/blaster/flesh_impact.efx"); + break; + + default: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav" ); + break; + } +} diff --git a/codemp/cgame/cg_weapons.c b/codemp/cgame/cg_weapons.c new file mode 100644 index 0000000..3450c88 --- /dev/null +++ b/codemp/cgame/cg_weapons.c @@ -0,0 +1,2750 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_weapons.c -- events and effects dealing with weapons +#include "cg_local.h" +#include "fx_local.h" + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + +extern vec4_t bluehudtint; +extern vec4_t redhudtint; +extern float *hudTintColor; + +/* +Ghoul2 Insert Start +*/ +// set up the appropriate ghoul2 info to a refent +void CG_SetGhoul2InfoRef( refEntity_t *ent, refEntity_t *s1) +{ + ent->ghoul2 = s1->ghoul2; + VectorCopy( s1->modelScale, ent->modelScale); + ent->radius = s1->radius; + VectorCopy( s1->angles, ent->angles); +} + +/* +Ghoul2 Insert End +*/ + +/* +================= +CG_RegisterItemVisuals + +The server says this item is used on this level +================= +*/ +void CG_RegisterItemVisuals( int itemNum ) { + itemInfo_t *itemInfo; + gitem_t *item; + int handle; + + if ( itemNum < 0 || itemNum >= bg_numItems ) { + CG_Error( "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 ); + } + + itemInfo = &cg_items[ itemNum ]; + if ( itemInfo->registered ) { + return; + } + + item = &bg_itemlist[ itemNum ]; + + memset( itemInfo, 0, sizeof( &itemInfo ) ); + itemInfo->registered = qtrue; + + if (item->giType == IT_TEAM && + (item->giTag == PW_REDFLAG || item->giTag == PW_BLUEFLAG) && + cgs.gametype == GT_CTY) + { //in CTY the flag model is different + itemInfo->models[0] = trap_R_RegisterModel( item->world_model[1] ); + } + else if (item->giType == IT_WEAPON && + (item->giTag == WP_THERMAL || item->giTag == WP_TRIP_MINE || item->giTag == WP_DET_PACK)) + { + itemInfo->models[0] = trap_R_RegisterModel( item->world_model[1] ); + } + else + { + itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); + } +/* +Ghoul2 Insert Start +*/ + if (!Q_stricmp(&item->world_model[0][strlen(item->world_model[0]) - 4], ".glm")) + { + handle = trap_G2API_InitGhoul2Model(&itemInfo->g2Models[0], item->world_model[0], 0 , 0, 0, 0, 0); + if (handle<0) + { + itemInfo->g2Models[0] = NULL; + } + else + { + itemInfo->radius[0] = 60; + } + } +/* +Ghoul2 Insert End +*/ + if (item->icon) + { + if (item->giType == IT_HEALTH) + { //medpack gets nomip'd by the ui or something I guess. + itemInfo->icon = trap_R_RegisterShaderNoMip( item->icon ); + } + else + { + itemInfo->icon = trap_R_RegisterShader( item->icon ); + } + } + else + { + itemInfo->icon = 0; + } + + if ( item->giType == IT_WEAPON ) { + CG_RegisterWeapon( item->giTag ); + } + + // + // powerups have an accompanying ring or sphere + // + if ( item->giType == IT_POWERUP || item->giType == IT_HEALTH || + item->giType == IT_ARMOR || item->giType == IT_HOLDABLE ) { + if ( item->world_model[1] ) { + itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] ); + } + } +} + + +/* +======================================================================================== + +VIEW WEAPON + +======================================================================================== +*/ + +#define WEAPON_FORCE_BUSY_HOLSTER + +#ifdef WEAPON_FORCE_BUSY_HOLSTER +//rww - this was done as a last resort. Forgive me. +static int cgWeapFrame = 0; +static int cgWeapFrameTime = 0; +#endif + +/* +================= +CG_MapTorsoToWeaponFrame + +================= +*/ +static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame, int animNum ) { + animation_t *animations = bgHumanoidAnimations; +#ifdef WEAPON_FORCE_BUSY_HOLSTER + if (cg->snap->ps.forceHandExtend != HANDEXTEND_NONE || cgWeapFrameTime > cg->time) + { //the reason for the after delay is so that it doesn't snap the weapon frame to the "idle" (0) frame + //for a very quick moment + if (cgWeapFrame < 6) + { + cgWeapFrame = 6; + cgWeapFrameTime = cg->time + 10; + } + + if (cgWeapFrameTime < cg->time && cgWeapFrame < 10) + { + cgWeapFrame++; + cgWeapFrameTime = cg->time + 10; + } + + if (cg->snap->ps.forceHandExtend != HANDEXTEND_NONE && + cgWeapFrame == 10) + { + cgWeapFrameTime = cg->time + 100; + } + + return cgWeapFrame; + } + else + { + cgWeapFrame = 0; + cgWeapFrameTime = 0; + } +#endif + + switch( animNum ) + { + case TORSO_DROPWEAP1: + if ( frame >= animations[animNum].firstFrame && frame < animations[animNum].firstFrame + 5 ) + { + return frame - animations[animNum].firstFrame + 6; + } + break; + + case TORSO_RAISEWEAP1: + if ( frame >= animations[animNum].firstFrame && frame < animations[animNum].firstFrame + 4 ) + { + return frame - animations[animNum].firstFrame + 6 + 4; + } + break; + case BOTH_ATTACK1: + case BOTH_ATTACK2: + case BOTH_ATTACK3: + case BOTH_ATTACK4: + case BOTH_ATTACK10: + case BOTH_THERMAL_THROW: + if ( frame >= animations[animNum].firstFrame && frame < animations[animNum].firstFrame + 6 ) + { + return 1 + ( frame - animations[animNum].firstFrame ); + } + + break; + } + return -1; +} + + +/* +============== +CG_CalculateWeaponPosition +============== +*/ +static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { + float scale; + int delta; + float fracsin; + + VectorCopy( cg->refdef.vieworg, origin ); + VectorCopy( cg->refdef.viewangles, angles ); + + // on odd legs, invert some angles + if ( cg->bobcycle & 1 ) { + scale = -cg->xyspeed; + } else { + scale = cg->xyspeed; + } + + // gun angles from bobbing + angles[ROLL] += scale * cg->bobfracsin * 0.005; + angles[YAW] += scale * cg->bobfracsin * 0.01; + angles[PITCH] += cg->xyspeed * cg->bobfracsin * 0.005; + + // drop the weapon when landing + delta = cg->time - cg->landTime; + if ( delta < LAND_DEFLECT_TIME ) { + origin[2] += cg->landChange*0.25 * delta / LAND_DEFLECT_TIME; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin[2] += cg->landChange*0.25 * + (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; + } + +#if 0 + // drop the weapon when stair climbing + delta = cg->time - cg->stepTime; + if ( delta < STEP_TIME/2 ) { + origin[2] -= cg->stepChange*0.25 * delta / (STEP_TIME/2); + } else if ( delta < STEP_TIME ) { + origin[2] -= cg->stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); + } +#endif + +#ifdef _XBOX + if(ClientManager::NumClients() > 1) { + static float offset = 1.5f; + origin[2] += offset; + if(ClientManager::ActiveClientNum() == 1) { + static float pitchoffset = -4.5f; + angles[PITCH] += pitchoffset; + } + } +#endif + + // idle drift + scale = cg->xyspeed + 40; + fracsin = sin( cg->time * 0.001 ); + angles[ROLL] += scale * fracsin * 0.01; + angles[YAW] += scale * fracsin * 0.01; + angles[PITCH] += scale * fracsin * 0.01; +} + + +/* +=============== +CG_LightningBolt + +Origin will be the exact tag point, which is slightly +different than the muzzle point used for determining hits. +The cent should be the non-predicted cent if it is from the player, +so the endpoint will reflect the simulated strike (lagging the predicted +angle) +=============== +*/ +static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { +// trace_t trace; + refEntity_t beam; +// vec3_t forward; +// vec3_t muzzlePoint, endPoint; + + //Must be a durational weapon that continuously generates an effect. + if ( cent->currentState.weapon == WP_DEMP2 && cent->currentState.eFlags & EF_ALT_FIRING ) + { /*nothing*/ } + else + { + return; + } + + memset( &beam, 0, sizeof( beam ) ); + + // NOTENOTE No lightning gun-ish stuff yet. +/* + // CPMA "true" lightning + if ((cent->currentState.number == cg->predictedPlayerState.clientNum) && (cg_trueLightning.value != 0)) { + vec3_t angle; + int i; + + for (i = 0; i < 3; i++) { + float a = cent->lerpAngles[i] - cg->refdef.viewangles[i]; + if (a > 180) { + a -= 360; + } + if (a < -180) { + a += 360; + } + + angle[i] = cg->refdef.viewangles[i] + a * (1.0 - cg_trueLightning.value); + if (angle[i] < 0) { + angle[i] += 360; + } + if (angle[i] > 360) { + angle[i] -= 360; + } + } + + AngleVectors(angle, forward, NULL, NULL ); + VectorCopy(cent->lerpOrigin, muzzlePoint ); +// VectorCopy(cg->refdef.vieworg, muzzlePoint ); + } else { + // !CPMA + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + VectorCopy(cent->lerpOrigin, muzzlePoint ); + } + + // FIXME: crouch + muzzlePoint[2] += DEFAULT_VIEWHEIGHT; + + VectorMA( muzzlePoint, 14, forward, muzzlePoint ); + + // project forward by the lightning range + VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); + + // see if it hit a wall + CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, + cent->currentState.number, MASK_SHOT ); + + // this is the endpoint + VectorCopy( trace.endpos, beam.oldorigin ); + + // use the provided origin, even though it may be slightly + // different than the muzzle origin + VectorCopy( origin, beam.origin ); + + beam.reType = RT_LIGHTNING; + beam.customShader = cgs.media.lightningShader; + trap_R_AddRefEntityToScene( &beam ); +*/ + + // NOTENOTE No lightning gun-ish stuff yet. +/* + // add the impact flare if it hit something + if ( trace.fraction < 1.0 ) { + vec3_t angles; + vec3_t dir; + + VectorSubtract( beam.oldorigin, beam.origin, dir ); + VectorNormalize( dir ); + + memset( &beam, 0, sizeof( beam ) ); + beam.hModel = cgs.media.lightningExplosionModel; + + VectorMA( trace.endpos, -16, dir, beam.origin ); + + // make a random orientation + angles[0] = rand() % 360; + angles[1] = rand() % 360; + angles[2] = rand() % 360; + AnglesToAxis( angles, beam.axis ); + trap_R_AddRefEntityToScene( &beam ); + } +*/ +} + + +/* +======================== +CG_AddWeaponWithPowerups +======================== +*/ +static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { + // add powerup effects + trap_R_AddRefEntityToScene( gun ); + + if (cg->predictedPlayerState.electrifyTime > cg->time) + { //add electrocution shell + int preShader = gun->customShader; + if ( rand() & 1 ) + { + gun->customShader = cgs.media.electricBodyShader; + } + else + { + gun->customShader = cgs.media.electricBody2Shader; + } + trap_R_AddRefEntityToScene( gun ); + gun->customShader = preShader; //set back just to be safe + } +} + + +/* +============= +CG_AddPlayerWeapon + +Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) +The main player will have this called for BOTH cases, so effects like light and +sound should only be done on the world model case. +============= +*/ +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team, vec3_t newAngles, qboolean thirdPerson ) { + refEntity_t gun; + refEntity_t barrel; + vec3_t angles; + weapon_t weaponNum; + weaponInfo_t *weapon; + centity_t *nonPredictedCent; + refEntity_t flash; + + weaponNum = cent->currentState.weapon; + + if (cent->currentState.weapon == WP_EMPLACED_GUN) + { + return; + } + + if (cg->predictedPlayerState.pm_type == PM_SPECTATOR && + cent->currentState.number == cg->predictedPlayerState.clientNum) + { //spectator mode, don't draw it... + return; + } + + CG_RegisterWeapon( weaponNum ); + weapon = &cg_weapons[weaponNum]; +/* +Ghoul2 Insert Start +*/ + + memset( &gun, 0, sizeof( gun ) ); + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + gun.skipForPlayer2 = true; +#endif + + // only do this if we are in first person, since world weapons are now handled on the server by Ghoul2 + if (!thirdPerson) + { + + // add the weapon + VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); + gun.shadowPlane = parent->shadowPlane; + gun.renderfx = parent->renderfx; + + if (ps) + { // this player, in first person view + gun.hModel = weapon->viewModel; + } + else + { + gun.hModel = weapon->weaponModel; + } + if (!gun.hModel) { + return; + } + + if ( !ps ) { + // add weapon ready sound + cent->pe.lightningFiring = qfalse; + if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { + // lightning gun and guantlet make a different sound when fire is held down + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); + cent->pe.lightningFiring = qtrue; + } else if ( weapon->readySound ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); + } + } + + CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); + + if (!CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg->snap->ps.clientNum)) + { + CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); //don't draw the weapon if the player is invisible + /* + if ( weaponNum == WP_STUN_BATON ) + { + gun.shaderRGBA[0] = gun.shaderRGBA[1] = gun.shaderRGBA[2] = 25; + + gun.customShader = trap_R_RegisterShader( "gfx/effects/stunPass" ); + gun.renderfx = RF_RGB_TINT | RF_FIRST_PERSON | RF_DEPTHHACK; + trap_R_AddRefEntityToScene( &gun ); + } + */ + } + + if (weaponNum == WP_STUN_BATON) + { + int i = 0; + + while (i < 3) + { + memset( &barrel, 0, sizeof( barrel ) ); +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + barrel.skipForPlayer2 = true; +#endif + VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = parent->shadowPlane; + barrel.renderfx = parent->renderfx; + + if (i == 0) + { + barrel.hModel = trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel.md3"); + } + else if (i == 1) + { + barrel.hModel = trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel2.md3"); + } + else + { + barrel.hModel = trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel3.md3"); + } + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = 0; + + AnglesToAxis( angles, barrel.axis ); + + if (i == 0) + { + CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel" ); + } + else if (i == 1) + { + CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel2" ); + } + else + { + CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel3" ); + } + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); + + i++; + } + } + else + { + // add the spinning barrel + if ( weapon->barrelModel ) { + memset( &barrel, 0, sizeof( barrel ) ); +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + barrel.skipForPlayer2 = true; +#endif + VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = parent->shadowPlane; + barrel.renderfx = parent->renderfx; + + barrel.hModel = weapon->barrelModel; + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = 0; + + AnglesToAxis( angles, barrel.axis ); + + CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel" ); + + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); + } + } + } +/* +Ghoul2 Insert End +*/ + + memset (&flash, 0, sizeof(flash)); +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + flash.skipForPlayer2 = true; +#endif + CG_PositionEntityOnTag( &flash, &gun, gun.hModel, "tag_flash"); + + VectorCopy(flash.origin, cg->lastFPFlashPoint); + + // Do special charge bits + //----------------------- + if ( (ps || cg->renderingThirdPerson || cg->predictedPlayerState.clientNum != cent->currentState.number) && + ( ( cent->currentState.modelindex2 == WEAPON_CHARGING_ALT && cent->currentState.weapon == WP_BRYAR_PISTOL ) || + ( cent->currentState.modelindex2 == WEAPON_CHARGING_ALT && cent->currentState.weapon == WP_BRYAR_OLD ) || + ( cent->currentState.weapon == WP_BOWCASTER && cent->currentState.modelindex2 == WEAPON_CHARGING ) || + ( cent->currentState.weapon == WP_DEMP2 && cent->currentState.modelindex2 == WEAPON_CHARGING_ALT) ) ) + { + int shader = 0; + float val = 0.0f; + float scale = 1.0f; + addspriteArgStruct_t fxSArgs; + vec3_t flashorigin, flashdir; + + if (!thirdPerson) + { + VectorCopy(flash.origin, flashorigin); + VectorCopy(flash.axis[0], flashdir); + } + else + { + mdxaBone_t boltMatrix; + + if (!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) + { //it's quite possible that we may have have no weapon model and be in a valid state, so return here if this is the case + return; + } + + // go away and get me the bolt position for this frame please + if (!(trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, newAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale))) + { // Couldn't find bolt point. + return; + } + + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, flashorigin); + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, flashdir); + } + + if ( cent->currentState.weapon == WP_BRYAR_PISTOL || + cent->currentState.weapon == WP_BRYAR_OLD) + { + // Hardcoded max charge time of 1 second + val = ( cg->time - cent->currentState.constantLight ) * 0.001f; + shader = cgs.media.bryarFrontFlash; + } + else if ( cent->currentState.weapon == WP_BOWCASTER ) + { + // Hardcoded max charge time of 1 second + val = ( cg->time - cent->currentState.constantLight ) * 0.001f; + shader = cgs.media.greenFrontFlash; + } + else if ( cent->currentState.weapon == WP_DEMP2 ) + { + val = ( cg->time - cent->currentState.constantLight ) * 0.001f; + shader = cgs.media.lightningFlash; + scale = 1.75f; + } + + if ( val < 0.0f ) + { + val = 0.0f; + } + else if ( val > 1.0f ) + { + val = 1.0f; + if (ps && cent->currentState.number == ps->clientNum) + { + CGCam_Shake( /*0.1f*/0.2f, 100 ); + } + } + else + { + if (ps && cent->currentState.number == ps->clientNum) + { + CGCam_Shake( val * val * /*0.3f*/0.6f, 100 ); + } + } + + val += random() * 0.5f; + + VectorCopy(flashorigin, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = 3.0f*val*scale; + fxSArgs.dscale = 0.0f; + fxSArgs.sAlpha = 0.7f; + fxSArgs.eAlpha = 0.7f; + fxSArgs.rotation = random()*360; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + fxSArgs.shader = shader; + fxSArgs.flags = 0x08000000; + + //FX_AddSprite( flash.origin, NULL, NULL, 3.0f * val, 0.0f, 0.7f, 0.7f, WHITE, WHITE, random() * 360, 0.0f, 1.0f, shader, FX_USE_ALPHA ); + trap_FX_AddSprite(&fxSArgs); + } + + // make sure we aren't looking at cg->predictedPlayerEntity for LG + nonPredictedCent = &cg_entities[cent->currentState.clientNum]; + + // if the index of the nonPredictedCent is not the same as the clientNum + // then this is a fake player (like on teh single player podiums), so + // go ahead and use the cent + if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { + nonPredictedCent = cent; + } + + // add the flash + if ( ( weaponNum == WP_DEMP2) + && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) + { + // continuous flash + } else { + // impulse flash + if ( cg->time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME) { + return; + } + } + + if ( ps || cg->renderingThirdPerson || + cent->currentState.number != cg->predictedPlayerState.clientNum ) + { // Make sure we don't do the thirdperson model effects for the local player if we're in first person + vec3_t flashorigin, flashdir; + refEntity_t flash; + + memset (&flash, 0, sizeof(flash)); + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + flash.skipForPlayer2 = true; +#endif + + if (!thirdPerson) + { + CG_PositionEntityOnTag( &flash, &gun, gun.hModel, "tag_flash"); + VectorCopy(flash.origin, flashorigin); + VectorCopy(flash.axis[0], flashdir); + } + else + { + mdxaBone_t boltMatrix; + + if (!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) + { //it's quite possible that we may have have no weapon model and be in a valid state, so return here if this is the case + return; + } + + // go away and get me the bolt position for this frame please + if (!(trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, newAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale))) + { // Couldn't find bolt point. + return; + } + + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, flashorigin); + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, flashdir); + } + + if ( cg->time - cent->muzzleFlashTime <= MUZZLE_FLASH_TIME + 10 ) + { // Handle muzzle flashes + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { // Check the alt firing first. + if (weapon->altMuzzleEffect) + { + if (!thirdPerson) + { + trap_FX_PlayEntityEffectID(weapon->altMuzzleEffect, flashorigin, flash.axis, -1, -1, -1, -1 ); + } + else + { + trap_FX_PlayEffectID(weapon->altMuzzleEffect, flashorigin, flashdir, -1, -1); + } + } + } + else + { // Regular firing + if (weapon->muzzleEffect) + { + if (!thirdPerson) + { + trap_FX_PlayEntityEffectID(weapon->muzzleEffect, flashorigin, flash.axis, -1, -1, -1, -1 ); + } + else + { + trap_FX_PlayEffectID(weapon->muzzleEffect, flashorigin, flashdir, -1, -1); + } + } + } + } + + // add lightning bolt + CG_LightningBolt( nonPredictedCent, flashorigin ); + + if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { + trap_R_AddLightToScene( flashorigin, 300 + (rand()&31), weapon->flashDlightColor[0], + weapon->flashDlightColor[1], weapon->flashDlightColor[2] ); + } + } +} + +/* +============== +CG_AddViewWeapon + +Add the weapon, and flash for the player's view +============== +*/ +void CG_AddViewWeapon( playerState_t *ps ) { + refEntity_t hand; + centity_t *cent; + clientInfo_t *ci; + float fovOffset; + vec3_t angles; + weaponInfo_t *weapon; + float cgFov = cg_fov.value; + + if (cgFov < 1) + { + cgFov = 1; + } + if (cgFov > 97) + { + cgFov = 97; + } + + if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + if ( ps->pm_type == PM_INTERMISSION ) { + return; + } + + // no gun if in third person view or a camera is active + //if ( cg->renderingThirdPerson || cg->cameraMode) { + if ( cg->renderingThirdPerson ) { + return; + } + + // allow the gun to be completely removed + if ( !cg_drawGun.integer || cg->predictedPlayerState.zoomMode) { + vec3_t origin; + + if ( cg->predictedPlayerState.eFlags & EF_FIRING ) { + // special hack for lightning gun... + VectorCopy( cg->refdef.vieworg, origin ); + VectorMA( origin, -8, cg->refdef.viewaxis[2], origin ); + CG_LightningBolt( &cg_entities[ps->clientNum], origin ); + } + return; + } + + // don't draw if testing a gun model + if ( cg->testGun ) { + return; + } + + // drop gun lower at higher fov + if ( cgFov > 90 ) { + fovOffset = -0.2 * ( cgFov - 90 ); + } else { + fovOffset = 0; + } + + cent = &cg_entities[cg->predictedPlayerState.clientNum]; + CG_RegisterWeapon( ps->weapon ); + weapon = &cg_weapons[ ps->weapon ]; + + memset (&hand, 0, sizeof(hand)); + + // set up gun position + CG_CalculateWeaponPosition( hand.origin, angles ); + + VectorMA( hand.origin, cg_gun_x.value, cg->refdef.viewaxis[0], hand.origin ); + VectorMA( hand.origin, cg_gun_y.value, cg->refdef.viewaxis[1], hand.origin ); + VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg->refdef.viewaxis[2], hand.origin ); + + AnglesToAxis( angles, hand.axis ); + + // map torso animations to weapon animations + if ( cg_gun_frame.integer ) { + // development tool + hand.frame = hand.oldframe = cg_gun_frame.integer; + hand.backlerp = 0; + } else { + // get clientinfo for animation map + if (cent->currentState.eType == ET_NPC) + { + if (!cent->npcClient) + { + return; + } + + ci = cent->npcClient; + } + else + { + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + } + hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame, cent->currentState.torsoAnim ); + hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame, cent->currentState.torsoAnim ); + hand.backlerp = cent->pe.torso.backlerp; + + // Handle the fringe situation where oldframe is invalid + if ( hand.frame == -1 ) + { + hand.frame = 0; + hand.oldframe = 0; + hand.backlerp = 0; + } + else if ( hand.oldframe == -1 ) + { + hand.oldframe = hand.frame; + hand.backlerp = 0; + } + } + + hand.hModel = weapon->handsModel; + hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON;// | RF_MINLIGHT; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) + hand.skipForPlayer2 = true; +#endif + + // add everything onto the hand + CG_AddPlayerWeapon( &hand, ps, &cg_entities[cg->predictedPlayerState.clientNum], ps->persistant[PERS_TEAM], angles, qfalse ); +} + +/* +============================================================================== + +WEAPON SELECTION + +============================================================================== +*/ +#define ICON_WEAPONS 0 +#define ICON_FORCE 1 +#define ICON_INVENTORY 2 + + +void CG_DrawIconBackground(void) +{ + int height,xAdd,x2,y2,t; +// int prongLeftX,prongRightX; + float inTime = cg->invenSelectTime+WEAPON_SELECT_TIME; + float wpTime = cg->weaponSelectTime+WEAPON_SELECT_TIME; + float fpTime = cg->forceSelectTime+WEAPON_SELECT_TIME; +// int drawType = cgs.media.weaponIconBackground; +// int yOffset = 0; + +#ifdef _XBOX +// yOffset = -50; +#endif + + // don't display if dead + if ( cg->snap->ps.stats[STAT_HEALTH] <= 0 ) + { + return; + } + + if (cg_hudFiles.integer) + { //simple hud + return; + } + + x2 = 30; + y2 = SCREEN_HEIGHT-70; + + //prongLeftX =x2+37; + //prongRightX =x2+544; + + if (inTime > wpTime) + { +// drawType = cgs.media.inventoryIconBackground; + cg->iconSelectTime = cg->invenSelectTime; + } + else + { +// drawType = cgs.media.weaponIconBackground; + cg->iconSelectTime = cg->weaponSelectTime; + } + + if (fpTime > inTime && fpTime > wpTime) + { +// drawType = cgs.media.forceIconBackground; + cg->iconSelectTime = cg->forceSelectTime; + } + + if ((cg->iconSelectTime+WEAPON_SELECT_TIME)time) // Time is up for the HUD to display + { + if (cg->iconHUDActive) // The time is up, but we still need to move the prongs back to their original position + { + t = cg->time - (cg->iconSelectTime+WEAPON_SELECT_TIME); + cg->iconHUDPercent = t/ 130.0f; + cg->iconHUDPercent = 1 - cg->iconHUDPercent; + + if (cg->iconHUDPercent<0) + { + cg->iconHUDActive = qfalse; + cg->iconHUDPercent=0; + } + + xAdd = (int) 8*cg->iconHUDPercent; + + height = (int) (60.0f*cg->iconHUDPercent); + //CG_DrawPic( x2+60, y2+30+yOffset, 460, -height, drawType); // Top half + //CG_DrawPic( x2+60, y2+30-2+yOffset, 460, height, drawType); // Bottom half + + } + else + { + xAdd = 0; + } + + return; + } + //prongLeftX =x2+37; + //prongRightX =x2+544; + + if (!cg->iconHUDActive) + { + t = cg->time - cg->iconSelectTime; + cg->iconHUDPercent = t/ 130.0f; + + // Calc how far into opening sequence we are + if (cg->iconHUDPercent>1) + { + cg->iconHUDActive = qtrue; + cg->iconHUDPercent=1; + } + else if (cg->iconHUDPercent<0) + { + cg->iconHUDPercent=0; + } + } + else + { + cg->iconHUDPercent=1; + } + + //trap_R_SetColor( colorTable[CT_WHITE] ); + //height = (int) (60.0f*cg->iconHUDPercent); + //CG_DrawPic( x2+60, y2+30+yOffset, 460, -height, drawType); // Top half + //CG_DrawPic( x2+60, y2+30-2+yOffset, 460, height, drawType); // Bottom half + + // And now for the prongs +/* if ((cg->inventorySelectTime+WEAPON_SELECT_TIME)>cg->time) + { + cgs.media.currentBackground = ICON_INVENTORY; + background = &cgs.media.inventoryProngsOn; + } + else if ((cg->weaponSelectTime+WEAPON_SELECT_TIME)>cg->time) + { + cgs.media.currentBackground = ICON_WEAPONS; + } + else + { + cgs.media.currentBackground = ICON_FORCE; + background = &cgs.media.forceProngsOn; + } +*/ + // Side Prongs +// trap_R_SetColor( colorTable[CT_WHITE]); +// xAdd = (int) 8*cg->iconHUDPercent; +// CG_DrawPic( prongLeftX+xAdd, y2-10, 40, 80, background); +// CG_DrawPic( prongRightX-xAdd, y2-10, -40, 80, background); + +} + +qboolean CG_WeaponCheck(int weap) +{ + if (cg->snap->ps.ammo[weaponData[weap].ammoIndex] < weaponData[weap].energyPerShot && + cg->snap->ps.ammo[weaponData[weap].ammoIndex] < weaponData[weap].altEnergyPerShot) + { + return qfalse; + } + + return qtrue; +} + +/* +=============== +CG_WeaponSelectable +=============== +*/ +static qboolean CG_WeaponSelectable( int i ) { + /*if ( !cg->snap->ps.ammo[weaponData[i].ammoIndex] ) { + return qfalse; + }*/ + if (!i) + { + return qfalse; + } + + if (cg->predictedPlayerState.ammo[weaponData[i].ammoIndex] < weaponData[i].energyPerShot && + cg->predictedPlayerState.ammo[weaponData[i].ammoIndex] < weaponData[i].altEnergyPerShot) + { + return qfalse; + } + + if (i == WP_DET_PACK && cg->predictedPlayerState.ammo[weaponData[i].ammoIndex] < 1 && + !cg->predictedPlayerState.hasDetPackPlanted) + { + return qfalse; + } + + if ( ! (cg->predictedPlayerState.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { + return qfalse; + } + + return qtrue; +} + +/* +=================== +CG_DrawWeaponSelect +=================== +*/ +#ifdef _XBOX +extern bool CL_ExtendSelectTime(void); +#endif +void CG_DrawWeaponSelect( void ) { + int i; + int bits; + int count; + int smallIconSize,bigIconSize; + int holdX,x,y,pad; + int sideLeftIconCnt,sideRightIconCnt; + int sideMax,holdCount,iconCnt; + int height; + int yOffset = 0; + int xOffset = 0; + qboolean drewConc = qfalse; + + if (cg->predictedPlayerState.emplacedIndex) + { //can't cycle when on a weapon + cg->weaponSelectTime = 0; + } + + if ((cg->weaponSelectTime+WEAPON_SELECT_TIME)time) // Time is up for the HUD to display + { + return; + } + + // don't display if dead + if ( cg->predictedPlayerState.stats[STAT_HEALTH] <= 0 ) + { + return; + } + +#ifdef _XBOX + if(CL_ExtendSelectTime()) { + cg->weaponSelectTime = cg->time; + } + + if(cg->widescreen) + xOffset = 40; + + yOffset = -50; +#endif + + // showing weapon select clears pickup item display, but not the blend blob + cg->itemPickupTime = 0; + + bits = cg->predictedPlayerState.stats[ STAT_WEAPONS ]; + + // count the number of weapons owned + count = 0; + + if ( !CG_WeaponSelectable(cg->weaponSelect) && + (cg->weaponSelect == WP_THERMAL || cg->weaponSelect == WP_TRIP_MINE) ) + { //display this weapon that we don't actually "have" as unhighlighted until it's deselected + //since it's selected we must increase the count to display the proper number of valid selectable weapons + count++; + } + + for ( i = 1 ; i < WP_NUM_WEAPONS ; i++ ) + { + if ( bits & ( 1 << i ) ) + { + if ( CG_WeaponSelectable(i) || + (i != WP_THERMAL && i != WP_TRIP_MINE) ) + { + count++; + } + } + } + + if (count == 0) // If no weapons, don't display + { + return; + } + + sideMax = 3; // Max number of icons on the side + + // Calculate how many icons will appear to either side of the center one + holdCount = count - 1; // -1 for the center icon + if (holdCount == 0) // No icons to either side + { + sideLeftIconCnt = 0; + sideRightIconCnt = 0; + } + else if (count > (2*sideMax)) // Go to the max on each side + { + sideLeftIconCnt = sideMax; + sideRightIconCnt = sideMax; + } + else // Less than max, so do the calc + { + sideLeftIconCnt = holdCount/2; + sideRightIconCnt = holdCount - sideLeftIconCnt; + } + + if ( cg->weaponSelect == WP_CONCUSSION ) + { + i = WP_FLECHETTE; + } + else + { + i = cg->weaponSelect - 1; + } + + if (i<1) + { + i = LAST_USEABLE_WEAPON; + } + + smallIconSize = 40; + bigIconSize = 80; + pad = 12; + + x = 320 + xOffset; + y = 410; + + // Background +// memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); +// calcColor[3] = .35f; +// trap_R_SetColor( calcColor); + + // Left side ICONS + trap_R_SetColor(colorTable[CT_WHITE]); + // Work backwards from current icon + holdX = x - ((bigIconSize/2) + pad + smallIconSize); + height = smallIconSize * 1;//cg->iconHUDPercent; + drewConc = qfalse; + + for (iconCnt=1;iconCnt<(sideLeftIconCnt+1);i--) + { + if ( i == WP_CONCUSSION ) + { + i--; + } + else if ( i == WP_FLECHETTE && !drewConc && cg->weaponSelect != WP_CONCUSSION ) + { + i = WP_CONCUSSION; + } + if (i<1) + { + //i = 13; + //...don't ever do this. + i = LAST_USEABLE_WEAPON; + } + + if ( !(bits & ( 1 << i ))) // Does he have this weapon? + { + if ( i == WP_CONCUSSION ) + { + drewConc = qtrue; + i = WP_ROCKET_LAUNCHER; + } + continue; + } + + if ( !CG_WeaponSelectable(i) && + (i == WP_THERMAL || i == WP_TRIP_MINE) ) + { //Don't show thermal and tripmine when out of them + continue; + } + + ++iconCnt; // Good icon + + if (cgs.media.weaponIcons[i]) + { + weaponInfo_t *weaponInfo; + CG_RegisterWeapon( i ); + weaponInfo = &cg_weapons[i]; + + trap_R_SetColor(colorTable[CT_WHITE]); + if (!CG_WeaponCheck(i)) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) + CG_DrawPic( holdX, y+10+yOffset - 220, smallIconSize, smallIconSize, /*weaponInfo->weaponIconNoAmmo*/cgs.media.weaponIcons_NA[i] ); + else +#endif + CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, /*weaponInfo->weaponIconNoAmmo*/cgs.media.weaponIcons_NA[i] ); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) + CG_DrawPic( holdX, y+10+yOffset - 220, smallIconSize, smallIconSize, /*weaponInfo->weaponIcon*/cgs.media.weaponIcons[i] ); + else +#endif + CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, /*weaponInfo->weaponIcon*/cgs.media.weaponIcons[i] ); + } + + holdX -= (smallIconSize+pad); + } + if ( i == WP_CONCUSSION ) + { + drewConc = qtrue; + i = WP_ROCKET_LAUNCHER; + } + } + + // Current Center Icon + height = bigIconSize * cg->iconHUDPercent; + if (cgs.media.weaponIcons[cg->weaponSelect]) + { + weaponInfo_t *weaponInfo; + CG_RegisterWeapon( cg->weaponSelect ); + weaponInfo = &cg_weapons[cg->weaponSelect]; + + trap_R_SetColor( colorTable[CT_WHITE]); + if (!CG_WeaponCheck(cg->weaponSelect)) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10+yOffset - 220, bigIconSize, bigIconSize, cgs.media.weaponIcons_NA[cg->weaponSelect] ); + else +#endif + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10+yOffset, bigIconSize, bigIconSize, cgs.media.weaponIcons_NA[cg->weaponSelect] ); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10+yOffset - 220, bigIconSize, bigIconSize, cgs.media.weaponIcons[cg->weaponSelect] ); + else +#endif + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10+yOffset, bigIconSize, bigIconSize, cgs.media.weaponIcons[cg->weaponSelect] ); + } + } + + if ( cg->weaponSelect == WP_CONCUSSION ) + { + i = WP_ROCKET_LAUNCHER; + } + else + { + i = cg->weaponSelect + 1; + } + + if (i> LAST_USEABLE_WEAPON) + { + i = 1; + } + + // Right side ICONS + // Work forwards from current icon + holdX = x + (bigIconSize/2) + pad; + height = smallIconSize * cg->iconHUDPercent; + for (iconCnt=1;iconCnt<(sideRightIconCnt+1);i++) + { + if ( i == WP_CONCUSSION ) + { + i++; + } + else if ( i == WP_ROCKET_LAUNCHER && !drewConc && cg->weaponSelect != WP_CONCUSSION ) + { + i = WP_CONCUSSION; + } + if (i>LAST_USEABLE_WEAPON) + { + i = 1; + } + + if ( !(bits & ( 1 << i ))) // Does he have this weapon? + { + if ( i == WP_CONCUSSION ) + { + drewConc = qtrue; + i = WP_FLECHETTE; + } + continue; + } + + if ( !CG_WeaponSelectable(i) && + (i == WP_THERMAL || i == WP_TRIP_MINE) ) + { //Don't show thermal and tripmine when out of them + continue; + } + + ++iconCnt; // Good icon + + if (/*weaponData[i].weaponIcon[0]*/cgs.media.weaponIcons[i]) + { + weaponInfo_t *weaponInfo; + CG_RegisterWeapon( i ); + weaponInfo = &cg_weapons[i]; + // No ammo for this weapon? + trap_R_SetColor( colorTable[CT_WHITE]); + if (!CG_WeaponCheck(i)) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) + CG_DrawPic( holdX, y+10+yOffset - 220, smallIconSize, smallIconSize, cgs.media.weaponIcons_NA[i] ); + else +#endif + CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, cgs.media.weaponIcons_NA[i] ); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) + CG_DrawPic( holdX, y+10+yOffset - 220, smallIconSize, smallIconSize, cgs.media.weaponIcons[i] ); + else +#endif + CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, cgs.media.weaponIcons[i] ); + } + + + holdX += (smallIconSize+pad); + } + if ( i == WP_CONCUSSION ) + { + drewConc = qtrue; + i = WP_FLECHETTE; + } + } + + // draw the selected name + if ( cg_weapons[ cg->weaponSelect ].item ) + { + vec4_t textColor = { .875f, .718f, .121f, 1.0f }; + char text[1024]; + char upperKey[1024]; + + strcpy(upperKey, cg_weapons[ cg->weaponSelect ].item->classname); + + if ( trap_SP_GetStringTextString( va("SP_INGAME_%s",Q_strupr(upperKey)), text, sizeof( text ))) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) + UI_DrawProportionalString(320 + xOffset, y+45+yOffset - 220, text, UI_CENTER|UI_SMALLFONT, textColor); + else +#endif + UI_DrawProportionalString(320 + xOffset, y+45+yOffset, text, UI_CENTER|UI_SMALLFONT, textColor); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) + UI_DrawProportionalString(320 + xOffset, y+45+yOffset - 220, cg_weapons[ cg->weaponSelect ].item->classname, UI_CENTER|UI_SMALLFONT, textColor); + else +#endif + UI_DrawProportionalString(320 + xOffset, y+45+yOffset, cg_weapons[ cg->weaponSelect ].item->classname, UI_CENTER|UI_SMALLFONT, textColor); + } + } + + trap_R_SetColor( NULL ); +} + + +/* +=============== +CG_NextWeapon_f +=============== +*/ +void CG_NextWeapon_f( void ) { + int i; + int original; + + if ( !cg->snap ) { + return; + } + if ( cg->snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + if (cg->predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + if (cg->snap->ps.emplacedIndex) + { + return; + } + + ClientManager::ActiveClient().swapMan1.SetUp(); + ClientManager::ActiveClient().swapMan2.SetUp(); + + cg->weaponSelectTime = cg->time; + original = cg->weaponSelect; + + for ( i = 0 ; i < WP_NUM_WEAPONS ; i++ ) { + //*SIGH*... Hack to put concussion rifle before rocketlauncher + if ( cg->weaponSelect == WP_FLECHETTE ) + { + cg->weaponSelect = WP_CONCUSSION; + } + else if ( cg->weaponSelect == WP_CONCUSSION ) + { + cg->weaponSelect = WP_ROCKET_LAUNCHER; + } + else if ( cg->weaponSelect == WP_DET_PACK ) + { + cg->weaponSelect = WP_BRYAR_OLD; + } + else + { + cg->weaponSelect++; + } + if ( cg->weaponSelect == WP_NUM_WEAPONS ) { + cg->weaponSelect = 0; + } + // if ( cg->weaponSelect == WP_STUN_BATON ) { + // continue; // never cycle to gauntlet + // } + if ( CG_WeaponSelectable( cg->weaponSelect ) ) { + break; + } + } + if ( i == WP_NUM_WEAPONS ) { + cg->weaponSelect = original; + } + else + { + trap_S_MuteSound(cg->snap->ps.clientNum, CHAN_WEAPON); + } +} + +/* +=============== +CG_PrevWeapon_f +=============== +*/ +void CG_PrevWeapon_f( void ) { + int i; + int original; + + if ( !cg->snap ) { + return; + } + if ( cg->snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + if (cg->predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + if (cg->snap->ps.emplacedIndex) + { + return; + } + + ClientManager::ActiveClient().swapMan1.SetUp(); + ClientManager::ActiveClient().swapMan2.SetUp(); + + cg->weaponSelectTime = cg->time; + original = cg->weaponSelect; + + for ( i = 0 ; i < WP_NUM_WEAPONS ; i++ ) { + //*SIGH*... Hack to put concussion rifle before rocketlauncher + if ( cg->weaponSelect == WP_ROCKET_LAUNCHER ) + { + cg->weaponSelect = WP_CONCUSSION; + } + else if ( cg->weaponSelect == WP_CONCUSSION ) + { + cg->weaponSelect = WP_FLECHETTE; + } + else if ( cg->weaponSelect == WP_BRYAR_OLD ) + { + cg->weaponSelect = WP_DET_PACK; + } + else + { + cg->weaponSelect--; + } + if ( cg->weaponSelect == -1 ) { + cg->weaponSelect = WP_NUM_WEAPONS-1; + } + // if ( cg->weaponSelect == WP_STUN_BATON ) { + // continue; // never cycle to gauntlet + // } + if ( CG_WeaponSelectable( cg->weaponSelect ) ) { + break; + } + } + if ( i == WP_NUM_WEAPONS ) { + cg->weaponSelect = original; + } + else + { + trap_S_MuteSound(cg->snap->ps.clientNum, CHAN_WEAPON); + } +} + +/* +=============== +CG_Weapon_f +=============== +*/ +void CG_Weapon_f( void ) { + int num; + + if ( !cg->snap ) { + return; + } + if ( cg->snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + if (cg->snap->ps.emplacedIndex) + { + return; + } + + num = atoi( CG_Argv( 1 ) ); + + if ( num < 1 || num > LAST_USEABLE_WEAPON ) { + return; + } + + if (num == 1 && cg->snap->ps.weapon == WP_SABER) + { + if (cg->snap->ps.weaponTime < 1) + { + trap_SendConsoleCommand("sv_saberswitch\n"); + } + return; + } + + //rww - hack to make weapon numbers same as single player + if (num > WP_STUN_BATON) + { + //num++; + num += 2; //I suppose this is getting kind of crazy, what with the wp_melee in there too now. + } + else + { + if (cg->snap->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) + { + num = WP_SABER; + } + else + { + num = WP_MELEE; + } + } + + if (num > LAST_USEABLE_WEAPON+1) + { //other weapons are off limits due to not actually being weapon weapons + return; + } + + if (num >= WP_THERMAL && num <= WP_DET_PACK) + { + int weap, i = 0; + + if (cg->snap->ps.weapon >= WP_THERMAL && + cg->snap->ps.weapon <= WP_DET_PACK) + { + // already in cycle range so start with next cycle item + weap = cg->snap->ps.weapon + 1; + } + else + { + // not in cycle range, so start with thermal detonator + weap = WP_THERMAL; + } + + // prevent an endless loop + while ( i <= 4 ) + { + if (weap > WP_DET_PACK) + { + weap = WP_THERMAL; + } + + if (CG_WeaponSelectable(weap)) + { + num = weap; + break; + } + + weap++; + i++; + } + } + + if (!CG_WeaponSelectable(num)) + { + return; + } + + cg->weaponSelectTime = cg->time; + + if ( ! ( cg->snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) + { + if (num == WP_SABER) + { //don't have saber, try melee on the same slot + num = WP_MELEE; + + if ( ! ( cg->snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) + { + return; + } + } + else + { + return; // don't have the weapon + } + } + + if (cg->weaponSelect != num) + { + trap_S_MuteSound(cg->snap->ps.clientNum, CHAN_WEAPON); + } + + cg->weaponSelect = num; +} + + +//Version of the above which doesn't add +2 to a weapon. The above can't +//triger WP_MELEE or WP_STUN_BATON. Derogatory comments go here. +void CG_WeaponClean_f( void ) { + int num; + + if ( !cg->snap ) { + return; + } + if ( cg->snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + if (cg->snap->ps.emplacedIndex) + { + return; + } + + num = atoi( CG_Argv( 1 ) ); + + if ( num < 1 || num > LAST_USEABLE_WEAPON ) { + return; + } + + if (num == 1 && cg->snap->ps.weapon == WP_SABER) + { + if (cg->snap->ps.weaponTime < 1) + { + trap_SendConsoleCommand("sv_saberswitch\n"); + } + return; + } + + if(num == WP_STUN_BATON) { + if (cg->snap->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) + { + num = WP_SABER; + } + else + { + num = WP_MELEE; + } + } + + if (num > LAST_USEABLE_WEAPON+1) + { //other weapons are off limits due to not actually being weapon weapons + return; + } + + if (num >= WP_THERMAL && num <= WP_DET_PACK) + { + int weap, i = 0; + + if (cg->snap->ps.weapon >= WP_THERMAL && + cg->snap->ps.weapon <= WP_DET_PACK) + { + // already in cycle range so start with next cycle item + weap = cg->snap->ps.weapon + 1; + } + else + { + // not in cycle range, so start with thermal detonator + weap = WP_THERMAL; + } + + // prevent an endless loop + while ( i <= 4 ) + { + if (weap > WP_DET_PACK) + { + weap = WP_THERMAL; + } + + if (CG_WeaponSelectable(weap)) + { + num = weap; + break; + } + + weap++; + i++; + } + } + + if (!CG_WeaponSelectable(num)) + { + return; + } + + cg->weaponSelectTime = cg->time; + + if ( ! ( cg->snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) + { + if (num == WP_SABER) + { //don't have saber, try melee on the same slot + num = WP_MELEE; + + if ( ! ( cg->snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) + { + return; + } + } + else + { + return; // don't have the weapon + } + } + + if (cg->weaponSelect != num) + { + trap_S_MuteSound(cg->snap->ps.clientNum, CHAN_WEAPON); + } + + cg->weaponSelect = num; +} + + + +/* +=================== +CG_OutOfAmmoChange + +The current weapon has just run out of ammo +=================== +*/ +void CG_OutOfAmmoChange( int oldWeapon ) +{ + int i; + +#ifdef _XBOX + short autoswitch = ClientManager::ActiveClient().cg_autoswitch; +#else + short autoswitch = cg_autoswitch.integer; +#endif //_XBOX + + cg->weaponSelectTime = cg->time; + + for ( i = LAST_USEABLE_WEAPON ; i > 0 ; i-- ) //We don't want the emplaced or turret + { + if ( CG_WeaponSelectable( i ) ) + { + /* + if ( 1 == cg_autoswitch.integer && + ( i == WP_TRIP_MINE || i == WP_DET_PACK || i == WP_THERMAL || i == WP_ROCKET_LAUNCHER) ) // safe weapon switch + */ + //rww - Don't we want to make sure i != one of these if autoswitch is 1 (safe)? + if (autoswitch != 1 || (i != WP_TRIP_MINE && i != WP_DET_PACK && i != WP_THERMAL && i != WP_ROCKET_LAUNCHER)) + { + if (i != oldWeapon) + { //don't even do anything if we're just selecting the weapon we already have/had + cg->weaponSelect = i; + break; + } + } + } + } + + trap_S_MuteSound(cg->snap->ps.clientNum, CHAN_WEAPON); +} + + + +/* +=================================================================================================== + +WEAPON EVENTS + +=================================================================================================== +*/ + +void CG_GetClientWeaponMuzzleBoltPoint(int clIndex, vec3_t to) +{ + centity_t *cent; + mdxaBone_t boltMatrix; + + if (clIndex < 0 || clIndex >= MAX_CLIENTS) + { + return; + } + + cent = &cg_entities[clIndex]; + + if (!cent || !cent->ghoul2 || !trap_G2_HaveWeGhoul2Models(cent->ghoul2) || + !trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) + { + return; + } + + trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, cent->turAngles, cent->lerpOrigin, cg->time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, to); +} + +/* +================ +CG_FireWeapon + +Caused by an EV_FIRE_WEAPON event +================ +*/ +#include "..\client\fffx.h" + +void CG_FireWeapon( centity_t *cent, qboolean altFire ) { + entityState_t *ent; + int c; + weaponInfo_t *weap; + + ent = ¢->currentState; + if ( ent->weapon == WP_NONE ) { + return; + } + if ( ent->weapon >= WP_NUM_WEAPONS ) { + CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); + return; + } + weap = &cg_weapons[ ent->weapon ]; + + // mark the entity as muzzle flashing, so when it is added it will + // append the flash to the weapon model + cent->muzzleFlashTime = cg->time; + + if (cg->predictedPlayerState.clientNum == cent->currentState.number) + { + switch (ent->weapon) + { + + case WP_SABER: + // no rumble + break; + + case WP_DISRUPTOR: + case WP_BRYAR_PISTOL: +// case WP_BLASTER_PISTOL: +// case WP_JAWA: + case WP_THERMAL: + FF_Play( altFire ? fffx_Shotgun : fffx_Pistol); + break; + case WP_DET_PACK: + // rumble + break; + + case WP_FLECHETTE: + FF_Play( altFire ? fffx_Shotgun : fffx_ShortPlasma); + break; + + case WP_REPEATER: + FF_Play( altFire ? fffx_MachineGun : fffx_GatlingGun); + break; + + case WP_BLASTER: + FF_Play( altFire ? fffx_Shotgun : fffx_Pistol); + break; + + case WP_ROCKET_LAUNCHER: + case WP_CONCUSSION: + if (!cg->renderingThirdPerson ) + { + //kick the view back + cg->kick_angles[PITCH] = flrand( -10, -15 ); + cg->kick_time = cg->time; + } + FF_Play( fffx_RocketLaunch ); + break; + + case WP_BOWCASTER: + FF_Play( altFire ? fffx_Land : fffx_Jump); + break; + + case WP_DEMP2: + FF_Play( altFire ? fffx_Shotgun : fffx_Pistol); + break; + + case WP_STUN_BATON: + FF_Play( fffx_Laser1 ); + break; + + case WP_TRIP_MINE: + FF_Play( fffx_OutOfAmmo ); + break; + } + + /* + if ((ent->weapon == WP_BRYAR_PISTOL && altFire) || + (ent->weapon == WP_BRYAR_OLD && altFire) || + (ent->weapon == WP_BOWCASTER && !altFire) || + (ent->weapon == WP_DEMP2 && altFire)) + { + float val = ( cg->time - cent->currentState.constantLight ) * 0.001f; + + if (val > 3) + { + val = 3; + } + if (val < 0.2) + { + val = 0.2; + } + + val *= 2; + + CGCam_Shake( val, 250 ); + } + else if (ent->weapon == WP_ROCKET_LAUNCHER || + (ent->weapon == WP_REPEATER && altFire) || + ent->weapon == WP_FLECHETTE || + (ent->weapon == WP_CONCUSSION && !altFire)) + { + if (ent->weapon == WP_CONCUSSION) + { + if (!cg->renderingThirdPerson )//gives an advantage to being in 3rd person, but would look silly otherwise + {//kick the view back + cg->kick_angles[PITCH] = flrand( -10, -15 ); + cg->kick_time = cg->time; + } + } + else if (ent->weapon == WP_ROCKET_LAUNCHER) + { + CGCam_Shake(flrand(2, 3), 350); + } + else if (ent->weapon == WP_REPEATER) + { + CGCam_Shake(flrand(2, 3), 350); + } + else if (ent->weapon == WP_FLECHETTE) + { + if (altFire) + { + CGCam_Shake(flrand(2, 3), 350); + } + else + { + CGCam_Shake(1.5, 250); + } + } + } + */ + } + // lightning gun only does this this on initial press + if ( ent->weapon == WP_DEMP2 ) { + if ( cent->pe.lightningFiring ) { + return; + } + } + + // play quad sound if needed + if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { + //trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); + } + + + // play a sound + if (altFire) + { + // play a sound + for ( c = 0 ; c < 4 ; c++ ) { + if ( !weap->altFlashSound[c] ) { + break; + } + } + if ( c > 0 ) { + c = rand() % c; + if ( weap->altFlashSound[c] ) + { + if(!ClientManager::splitScreenMode || ClientManager::IsActiveClient(ent->clientNum )) + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSound[c] ); + } + } +// if ( weap->altFlashSnd ) +// { +// trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSnd ); +// } + } + else + { + // play a sound + for ( c = 0 ; c < 4 ; c++ ) { + if ( !weap->flashSound[c] ) { + break; + } + } + if ( c > 0 ) { + c = rand() % c; + if ( weap->flashSound[c] ) + { + if(!ClientManager::splitScreenMode || ClientManager::IsActiveClient(ent->clientNum)) + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] ); + } + } + } +} + +qboolean CG_VehicleWeaponImpact( centity_t *cent ) +{//see if this is a missile entity that's owned by a vehicle and should do a special, overridden impact effect + if ((cent->currentState.eFlags&EF_JETPACK_ACTIVE)//hack so we know we're a vehicle Weapon shot + && cent->currentState.otherEntityNum2 + && g_vehWeaponInfo[cent->currentState.otherEntityNum2].iImpactFX) + {//missile is from a special vehWeapon + vec3_t normal; + ByteToDir( cent->currentState.eventParm, normal ); + + trap_FX_PlayEffectID( g_vehWeaponInfo[cent->currentState.otherEntityNum2].iImpactFX, cent->lerpOrigin, normal, -1, -1 ); + return qtrue; + } + return qfalse; +} + +/* +================= +CG_MissileHitWall + +Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing +================= +*/ +void CG_MissileHitWall(int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType, qboolean altFire, int charge) +{ + int parm; + vec3_t up={0,0,1}; + + switch( weapon ) + { + case WP_BRYAR_PISTOL: + if ( altFire ) + { + parm = charge; + FX_BryarAltHitWall( origin, dir, parm ); + } + else + { + FX_BryarHitWall( origin, dir ); + } + break; + + case WP_CONCUSSION: + FX_ConcussionHitWall( origin, dir ); + break; + + case WP_BRYAR_OLD: + if ( altFire ) + { + parm = charge; + FX_BryarAltHitWall( origin, dir, parm ); + } + else + { + FX_BryarHitWall( origin, dir ); + } + break; + + case WP_TURRET: + FX_TurretHitWall( origin, dir ); + break; + + case WP_BLASTER: + FX_BlasterWeaponHitWall( origin, dir ); + break; + + case WP_DISRUPTOR: + FX_DisruptorAltMiss( origin, dir ); + break; + + case WP_BOWCASTER: + FX_BowcasterHitWall( origin, dir ); + break; + + case WP_REPEATER: + if ( altFire ) + { + FX_RepeaterAltHitWall( origin, dir ); + } + else + { + FX_RepeaterHitWall( origin, dir ); + } + break; + + case WP_DEMP2: + if (altFire) + { + trap_FX_PlayEffectID(cgs.effects.mAltDetonate, origin, dir, -1, -1); + } + else + { + FX_DEMP2_HitWall( origin, dir ); + } + break; + + case WP_FLECHETTE: + /*if (altFire) + { + CG_SurfaceExplosion(origin, dir, 20.0f, 12.0f, qtrue); + } + else + */ + if (!altFire) + { + FX_FlechetteWeaponHitWall( origin, dir ); + } + break; + + case WP_ROCKET_LAUNCHER: + FX_RocketHitWall( origin, dir ); + break; + + case WP_THERMAL: + trap_FX_PlayEffectID( cgs.effects.thermalExplosionEffect, origin, dir, -1, -1 ); + trap_FX_PlayEffectID( cgs.effects.thermalShockwaveEffect, origin, up, -1, -1 ); + break; + + case WP_EMPLACED_GUN: + FX_BlasterWeaponHitWall( origin, dir ); + //FIXME: Give it its own hit wall effect + break; + } +} + + +/* +================= +CG_MissileHitPlayer +================= +*/ +void CG_MissileHitPlayer(int weapon, vec3_t origin, vec3_t dir, int entityNum, qboolean altFire) +{ + qboolean humanoid = qtrue; + vec3_t up={0,0,1}; + + /* + // NOTENOTE Non-portable code from single player + if ( cent->gent ) + { + other = &g_entities[cent->gent->s.otherEntityNum]; + + if ( other->client && other->client->playerTeam == TEAM_BOTS ) + { + humanoid = qfalse; + } + } + */ + + // NOTENOTE No bleeding in this game +// CG_Bleed( origin, entityNum ); + + // some weapons will make an explosion with the blood, while + // others will just make the blood + switch ( weapon ) { + case WP_BRYAR_PISTOL: + if ( altFire ) + { + FX_BryarAltHitPlayer( origin, dir, humanoid ); + } + else + { + FX_BryarHitPlayer( origin, dir, humanoid ); + } + break; + + case WP_CONCUSSION: + FX_ConcussionHitPlayer( origin, dir, humanoid ); + break; + + case WP_BRYAR_OLD: + if ( altFire ) + { + FX_BryarAltHitPlayer( origin, dir, humanoid ); + } + else + { + FX_BryarHitPlayer( origin, dir, humanoid ); + } + break; + + case WP_TURRET: + FX_TurretHitPlayer( origin, dir, humanoid ); + break; + + case WP_BLASTER: + FX_BlasterWeaponHitPlayer( origin, dir, humanoid ); + break; + + case WP_DISRUPTOR: + FX_DisruptorAltHit( origin, dir); + break; + + case WP_BOWCASTER: + FX_BowcasterHitPlayer( origin, dir, humanoid ); + break; + + case WP_REPEATER: + if ( altFire ) + { + FX_RepeaterAltHitPlayer( origin, dir, humanoid ); + } + else + { + FX_RepeaterHitPlayer( origin, dir, humanoid ); + } + break; + + case WP_DEMP2: + // Do a full body effect here for some more feedback + // NOTENOTE The chaining of the demp2 is not yet implemented. + /* + if ( other ) + { + other->s.powerups |= ( 1 << PW_DISINT_1 ); + other->client->ps.powerups[PW_DISINT_1] = cg->time + 650; + } + */ + if (altFire) + { + trap_FX_PlayEffectID(cgs.effects.mAltDetonate, origin, dir, -1, -1); + } + else + { + FX_DEMP2_HitPlayer( origin, dir, humanoid ); + } + break; + + case WP_FLECHETTE: + FX_FlechetteWeaponHitPlayer( origin, dir, humanoid ); + break; + + case WP_ROCKET_LAUNCHER: + FX_RocketHitPlayer( origin, dir, humanoid ); + break; + + case WP_THERMAL: + trap_FX_PlayEffectID( cgs.effects.thermalExplosionEffect, origin, dir, -1, -1 ); + trap_FX_PlayEffectID( cgs.effects.thermalShockwaveEffect, origin, up, -1, -1 ); + break; + case WP_EMPLACED_GUN: + //FIXME: Its own effect? + FX_BlasterWeaponHitPlayer( origin, dir, humanoid ); + break; + + default: + break; + } +} + + +/* +============================================================================ + +BULLETS + +============================================================================ +*/ + + +/* +====================== +CG_CalcMuzzlePoint +====================== +*/ +qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { + vec3_t forward, right; + vec3_t gunpoint; + centity_t *cent; + int anim; + + if ( entityNum == cg->snap->ps.clientNum ) + { //I'm not exactly sure why we'd be rendering someone else's crosshair, but hey. + int weapontype = cg->snap->ps.weapon; + vec3_t weaponMuzzle; + centity_t *pEnt = &cg_entities[cg->predictedPlayerState.clientNum]; + + VectorCopy(WP_MuzzlePoint[weapontype], weaponMuzzle); + + if (weapontype == WP_DISRUPTOR || weapontype == WP_STUN_BATON || weapontype == WP_MELEE || weapontype == WP_SABER) + { + VectorClear(weaponMuzzle); + } + + if (cg->renderingThirdPerson) + { + VectorCopy( pEnt->lerpOrigin, gunpoint ); + AngleVectors( pEnt->lerpAngles, forward, right, NULL ); + } + else + { + VectorCopy( cg->refdef.vieworg, gunpoint ); + AngleVectors( cg->refdef.viewangles, forward, right, NULL ); + } + + if (weapontype == WP_EMPLACED_GUN && cg->snap->ps.emplacedIndex) + { + centity_t *gunEnt = &cg_entities[cg->snap->ps.emplacedIndex]; + + if (gunEnt) + { + vec3_t pitchConstraint; + + VectorCopy(gunEnt->lerpOrigin, gunpoint); + gunpoint[2] += 46; + + if (cg->renderingThirdPerson) + { + VectorCopy(pEnt->lerpAngles, pitchConstraint); + } + else + { + VectorCopy(cg->refdef.viewangles, pitchConstraint); + } + + if (pitchConstraint[PITCH] > 40) + { + pitchConstraint[PITCH] = 40; + } + AngleVectors( pitchConstraint, forward, right, NULL ); + } + } + + VectorCopy(gunpoint, muzzle); + + VectorMA(muzzle, weaponMuzzle[0], forward, muzzle); + VectorMA(muzzle, weaponMuzzle[1], right, muzzle); + + if (weapontype == WP_EMPLACED_GUN && cg->snap->ps.emplacedIndex) + { + //Do nothing + } + else if (cg->renderingThirdPerson) + { + muzzle[2] += cg->snap->ps.viewheight + weaponMuzzle[2]; + } + else + { + muzzle[2] += weaponMuzzle[2]; + } + + return qtrue; + } + + cent = &cg_entities[entityNum]; +#ifdef _XBOX + if ( !cent->currentValid[ClientManager::ActiveClientNum()] ) { +#else + if ( !cent->currentValid ) { +#endif + return qfalse; + } + + VectorCopy( cent->currentState.pos.trBase, muzzle ); + + AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); + anim = cent->currentState.legsAnim; + if ( anim == BOTH_CROUCH1WALK || anim == BOTH_CROUCH1IDLE ) { + muzzle[2] += CROUCH_VIEWHEIGHT; + } else { + muzzle[2] += DEFAULT_VIEWHEIGHT; + } + + VectorMA( muzzle, 14, forward, muzzle ); + + return qtrue; + +} + + + +/* +Ghoul2 Insert Start +*/ + +// create one instance of all the weapons we are going to use so we can just copy this info into each clients gun ghoul2 object in fast way +static void *g2WeaponInstances[MAX_WEAPONS]; + +void CG_InitG2Weapons(void) +{ + int i = 0; + gitem_t *item; + memset(g2WeaponInstances, 0, sizeof(g2WeaponInstances)); + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) + { + if ( item->giType == IT_WEAPON ) + { + assert(item->giTag < MAX_WEAPONS); + + // initialise model + trap_G2API_InitGhoul2Model(&g2WeaponInstances[/*i*/item->giTag], item->world_model[0], 0, 0, 0, 0, 0); +// trap_G2API_InitGhoul2Model(&g2WeaponInstances[i], item->world_model[0],G_ModelIndex( item->world_model[0] ) , 0, 0, 0, 0); + if (g2WeaponInstances[/*i*/item->giTag]) + { + // indicate we will be bolted to model 0 (ie the player) on bolt 0 (always the right hand) when we get copied + trap_G2API_SetBoltInfo(g2WeaponInstances[/*i*/item->giTag], 0, 0); + // now set up the gun bolt on it + if (item->giTag == WP_SABER) + { + trap_G2API_AddBolt(g2WeaponInstances[/*i*/item->giTag], 0, "*blade1"); + } + else + { + trap_G2API_AddBolt(g2WeaponInstances[/*i*/item->giTag], 0, "*flash"); + } + i++; + } + if (i == MAX_WEAPONS) + { + assert(0); + break; + } + + } + } +} + +// clean out any g2 models we instanciated for copying purposes +void CG_ShutDownG2Weapons(void) +{ + int i; + for (i=0; icurrentState.eType != ET_PLAYER && + cent->currentState.eType != ET_NPC) + { + return g2WeaponInstances[weapon]; + } + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + } + else + { + ci = &cgs.clientinfo[cent->currentState.number]; + } + + if (!ci) + { + return g2WeaponInstances[weapon]; + } + + //Try to return the custom saber instance if we can. + if (ci->saber[0].model[0] && + ci->ghoul2Weapons[0]) + { + return ci->ghoul2Weapons[0]; + } + + //If no custom then just use the default. + return g2WeaponInstances[weapon]; +} + +// what ghoul2 model do we want to copy ? +void CG_CopyG2WeaponInstance(centity_t *cent, int weaponNum, void *toGhoul2) +{ + //rww - the -1 is because there is no "weapon" for WP_NONE + assert(weaponNum < MAX_WEAPONS); + if (CG_G2WeaponInstance(cent, weaponNum/*-1*/)) + { + if (weaponNum == WP_SABER) + { + clientInfo_t *ci = NULL; + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + } + else + { + ci = &cgs.clientinfo[cent->currentState.number]; + } + + if (!ci) + { + trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, weaponNum/*-1*/), 0, toGhoul2, 1); + } + else + { //Try both the left hand saber and the right hand saber + int i = 0; + + while (i < MAX_SABERS) + { + if (ci->saber[i].model[0] && + ci->ghoul2Weapons[i]) + { + trap_G2API_CopySpecificGhoul2Model(ci->ghoul2Weapons[i], 0, toGhoul2, i+1); + } + else if (ci->ghoul2Weapons[i]) + { //if the second saber has been removed, then be sure to remove it and free the instance. + qboolean g2HasSecondSaber = trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 2); + + if (g2HasSecondSaber) + { //remove it now since we're switching away from sabers + trap_G2API_RemoveGhoul2Model(&(toGhoul2), 2); + } + trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[i]); + } + + i++; + } + } + } + else + { + qboolean g2HasSecondSaber = trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 2); + + if (g2HasSecondSaber) + { //remove it now since we're switching away from sabers + trap_G2API_RemoveGhoul2Model(&(toGhoul2), 2); + } + + if (weaponNum == WP_EMPLACED_GUN) + { //a bit of a hack to remove gun model when using an emplaced weap + if (trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 1)) + { + trap_G2API_RemoveGhoul2Model(&(toGhoul2), 1); + } + } + else if (weaponNum == WP_MELEE) + { //don't want a weapon on the model for this one + if (trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 1)) + { + trap_G2API_RemoveGhoul2Model(&(toGhoul2), 1); + } + } + else + { + trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, weaponNum/*-1*/), 0, toGhoul2, 1); + } + } + } +} + +void CG_CheckPlayerG2Weapons(playerState_t *ps, centity_t *cent) +{ + if (!ps) + { + assert(0); + return; + } + + if (ps->pm_flags & PMF_FOLLOW) + { + return; + } + + if (cent->currentState.eType == ET_NPC) + { + assert(0); + return; + } + + // should we change the gun model on this player? + if (cent->currentState.saberInFlight) + { + cent->ghoul2weapon = CG_G2WeaponInstance(cent, WP_SABER); + } + + if (cent->currentState.eFlags & EF_DEAD) + { //no updating weapons when dead + cent->ghoul2weapon = NULL; + return; + } + + if (cent->torsoBolt) + { //got our limb cut off, no updating weapons until it's restored + cent->ghoul2weapon = NULL; + return; + } + + if (cgs.clientinfo[ps->clientNum].team == TEAM_SPECTATOR || + ps->persistant[PERS_TEAM] == TEAM_SPECTATOR) + { + cent->ghoul2weapon = cg_entities[ps->clientNum].ghoul2weapon = NULL; + cent->weapon = cg_entities[ps->clientNum].weapon = 0; + return; + } + + if (cent->ghoul2 && cent->ghoul2weapon != CG_G2WeaponInstance(cent, ps->weapon) && + ps->clientNum == cent->currentState.number) //don't want spectator mode forcing one client's weapon instance over another's + { + CG_CopyG2WeaponInstance(cent, ps->weapon, cent->ghoul2); + cent->ghoul2weapon = CG_G2WeaponInstance(cent, ps->weapon); + if (cent->weapon == WP_SABER && cent->weapon != ps->weapon && !ps->saberHolstered) + { //switching away from the saber + //trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" )); + if (cgs.clientinfo[ps->clientNum].saber[0].soundOff && !ps->saberHolstered) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[0].soundOff); + } + + if (cgs.clientinfo[ps->clientNum].saber[1].soundOff && + cgs.clientinfo[ps->clientNum].saber[1].model[0] && + !ps->saberHolstered) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[1].soundOff); + } + } + else if (ps->weapon == WP_SABER && cent->weapon != ps->weapon && !cent->saberWasInFlight) + { //switching to the saber + //trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" )); + if (cgs.clientinfo[ps->clientNum].saber[0].soundOn) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[0].soundOn); + } + + if (cgs.clientinfo[ps->clientNum].saber[1].soundOn) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[1].soundOn); + } + + BG_SI_SetDesiredLength(&cgs.clientinfo[ps->clientNum].saber[0], 0, -1); + BG_SI_SetDesiredLength(&cgs.clientinfo[ps->clientNum].saber[1], 0, -1); + } + cent->weapon = ps->weapon; + } +} + + +/* +Ghoul2 Insert End +*/ diff --git a/codemp/cgame/cgame.bat b/codemp/cgame/cgame.bat new file mode 100644 index 0000000..6059cbc --- /dev/null +++ b/codemp/cgame/cgame.bat @@ -0,0 +1,19 @@ +echo off + +REM del /q vm +REM rww - removed this.. point of makefile is not have to rebuild all of it + +mkdir vm + +del vm\bg_lib.asm +asm2mak cgame makefile +make cgame + +cd vm + +mkdir "..\..\base\vm" +copy *.map "..\..\base\vm" +copy *.qvm "..\..\base\vm" + +:quit +cd .. diff --git a/codemp/cgame/fx_blaster.c b/codemp/cgame/fx_blaster.c new file mode 100644 index 0000000..05c1fb1 --- /dev/null +++ b/codemp/cgame/fx_blaster.c @@ -0,0 +1,65 @@ +// Blaster Weapon + +#include "cg_local.h" + +/* +------------------------- +FX_BlasterProjectileThink +------------------------- +*/ + +void FX_BlasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.blasterShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_BlasterAltFireThink +------------------------- +*/ +void FX_BlasterAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.blasterShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_BlasterWeaponHitWall +------------------------- +*/ +void FX_BlasterWeaponHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.blasterWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_BlasterWeaponHitPlayer +------------------------- +*/ +void FX_BlasterWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + if ( humanoid ) + { + trap_FX_PlayEffectID( cgs.effects.blasterFleshImpactEffect, origin, normal, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( cgs.effects.blasterDroidImpactEffect, origin, normal, -1, -1 ); + } +} diff --git a/codemp/cgame/fx_bowcaster.c b/codemp/cgame/fx_bowcaster.c new file mode 100644 index 0000000..0cf11f3 --- /dev/null +++ b/codemp/cgame/fx_bowcaster.c @@ -0,0 +1,62 @@ +// Bowcaster Weapon + +#include "cg_local.h" + +/* +--------------------------- +FX_BowcasterProjectileThink +--------------------------- +*/ + +void FX_BowcasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.bowcasterShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +--------------------------- +FX_BowcasterHitWall +--------------------------- +*/ + +void FX_BowcasterHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.bowcasterImpactEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_BowcasterHitPlayer +--------------------------- +*/ + +void FX_BowcasterHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.bowcasterImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------------ +FX_BowcasterAltProjectileThink +------------------------------ +*/ + +void FX_BowcasterAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.bowcasterShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + diff --git a/codemp/cgame/fx_bryarpistol.c b/codemp/cgame/fx_bryarpistol.c new file mode 100644 index 0000000..89b131a --- /dev/null +++ b/codemp/cgame/fx_bryarpistol.c @@ -0,0 +1,237 @@ +// Bryar Pistol Weapon Effects + +#include "cg_local.h" +#include "fx_local.h" + +/* +------------------------- + + MAIN FIRE + +------------------------- +FX_BryarProjectileThink +------------------------- +*/ +void FX_BryarProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.bryarShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_BryarHitWall +------------------------- +*/ +void FX_BryarHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.bryarWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_BryarHitPlayer +------------------------- +*/ +void FX_BryarHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + if ( humanoid ) + { + trap_FX_PlayEffectID( cgs.effects.bryarFleshImpactEffect, origin, normal, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( cgs.effects.bryarDroidImpactEffect, origin, normal, -1, -1 ); + } +} + + +/* +------------------------- + + ALT FIRE + +------------------------- +FX_BryarAltProjectileThink +------------------------- +*/ +void FX_BryarAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + int t; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + // see if we have some sort of extra charge going on + for (t = 1; t < cent->currentState.generic1; t++ ) + { + // just add ourselves over, and over, and over when we are charged + trap_FX_PlayEffectID( cgs.effects.bryarPowerupShotEffect, cent->lerpOrigin, forward, -1, -1 ); + } + + // for ( int t = 1; t < cent->gent->count; t++ ) // The single player stores the charge in count, which isn't accessible on the client + + trap_FX_PlayEffectID( cgs.effects.bryarShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_BryarAltHitWall +------------------------- +*/ +void FX_BryarAltHitWall( vec3_t origin, vec3_t normal, int power ) +{ + switch( power ) + { + case 4: + case 5: + trap_FX_PlayEffectID( cgs.effects.bryarWallImpactEffect3, origin, normal, -1, -1 ); + break; + + case 2: + case 3: + trap_FX_PlayEffectID( cgs.effects.bryarWallImpactEffect2, origin, normal, -1, -1 ); + break; + + default: + trap_FX_PlayEffectID( cgs.effects.bryarWallImpactEffect, origin, normal, -1, -1 ); + break; + } +} + +/* +------------------------- +FX_BryarAltHitPlayer +------------------------- +*/ +void FX_BryarAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + if ( humanoid ) + { + trap_FX_PlayEffectID( cgs.effects.bryarFleshImpactEffect, origin, normal, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( cgs.effects.bryarDroidImpactEffect, origin, normal, -1, -1 ); + } +} + + +//TURRET +/* +------------------------- +FX_TurretProjectileThink +------------------------- +*/ +void FX_TurretProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.turretShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_TurretHitWall +------------------------- +*/ +void FX_TurretHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.bryarWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_TurretHitPlayer +------------------------- +*/ +void FX_TurretHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + if ( humanoid ) + { + trap_FX_PlayEffectID( cgs.effects.bryarFleshImpactEffect, origin, normal, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( cgs.effects.bryarDroidImpactEffect, origin, normal, -1, -1 ); + } +} + + + +//CONCUSSION (yeah, should probably make a new file for this.. or maybe just move all these stupid semi-redundant fx_ functions into one file) +/* +------------------------- +FX_ConcussionHitWall +------------------------- +*/ +void FX_ConcussionHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.concussionImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_ConcussionHitPlayer +------------------------- +*/ +void FX_ConcussionHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.concussionImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_ConcussionProjectileThink +------------------------- +*/ +void FX_ConcussionProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.concussionShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +--------------------------- +FX_ConcAltShot +--------------------------- +*/ +static vec3_t WHITE ={1.0f,1.0f,1.0f}; +static vec3_t BRIGHT={0.75f,0.5f,1.0f}; + +void FX_ConcAltShot( vec3_t start, vec3_t end ) +{ + //"concussion/beam" + trap_FX_AddLine( start, end, 0.1f, 10.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + WHITE, WHITE, 0.0f, + 175, trap_R_RegisterShader( "gfx/effects/blueLine" ), + FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + + // add some beef + trap_FX_AddLine( start, end, 0.1f, 7.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + BRIGHT, BRIGHT, 0.0f, + 150, trap_R_RegisterShader( "gfx/misc/whiteline2" ), + FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); +} diff --git a/codemp/cgame/fx_demp2.c b/codemp/cgame/fx_demp2.c new file mode 100644 index 0000000..9546d8b --- /dev/null +++ b/codemp/cgame/fx_demp2.c @@ -0,0 +1,259 @@ +// DEMP2 Weapon + +#include "cg_local.h" + +/* +--------------------------- +FX_DEMP2_ProjectileThink +--------------------------- +*/ + +void FX_DEMP2_ProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.demp2ProjectileEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +--------------------------- +FX_DEMP2_HitWall +--------------------------- +*/ + +void FX_DEMP2_HitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.demp2WallImpactEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_DEMP2_HitPlayer +--------------------------- +*/ + +void FX_DEMP2_HitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.demp2FleshImpactEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_DEMP2_AltBeam +--------------------------- +*/ +void FX_DEMP2_AltBeam( vec3_t start, vec3_t end, vec3_t normal, //qboolean spark, + vec3_t targ1, vec3_t targ2 ) +{ +//NOTENOTE Fix this after trap calls for all primitives are created. +/* + vec3_t dir, chaos, + c1, c2, + v1, v2; + float len, + s1, s2, s3; + + VectorSubtract( end, start, dir ); + len = VectorNormalize( dir ); + + // Get the base control points, we'll work from there + VectorMA( start, 0.3333f * len, dir, c1 ); + VectorMA( start, 0.6666f * len, dir, c2 ); + + // get some chaos values that really aren't very chaotic :) + s1 = sin( cg->time * 0.005f ) * 2 + crandom() * 0.2f; + s2 = sin( cg->time * 0.001f ); + s3 = sin( cg->time * 0.011f ); + + VectorSet( chaos, len * 0.01f * s1, + len * 0.02f * s2, + len * 0.04f * (s1 + s2 + s3)); + + VectorAdd( c1, chaos, c1 ); + VectorScale( chaos, 4.0f, v1 ); + + VectorSet( chaos, -len * 0.02f * s3, + len * 0.01f * (s1 * s2), + -len * 0.02f * (s1 + s2 * s3)); + + VectorAdd( c2, chaos, c2 ); + VectorScale( chaos, 2.0f, v2 ); + + VectorSet( chaos, 1.0f, 1.0f, 1.0f ); + + FX_AddBezier( start, targ1, + c1, v1, c2, v2, + 5.0f + s1 * 2, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + FX_AddBezier( start, targ1, + c2, v2, c1, v1, + 3.0f + s3, 3.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + s1 = sin( cg->time * 0.0005f ) + crandom() * 0.1f; + s2 = sin( cg->time * 0.0025f ); + float cc2 = cos( cg->time * 0.0025f ); + s3 = sin( cg->time * 0.01f ) + crandom() * 0.1f; + + VectorSet( chaos, len * 0.08f * s2, + len * 0.04f * cc2,//s1 * -s3, + len * 0.06f * s3 ); + + VectorAdd( c1, chaos, c1 ); + VectorScale( chaos, 4.0f, v1 ); + + VectorSet( chaos, len * 0.02f * s1 * s3, + len * 0.04f * s2, + len * 0.03f * s1 * s2 ); + + VectorAdd( c2, chaos, c2 ); + VectorScale( chaos, 3.0f, v2 ); + + VectorSet( chaos, 1.0f, 1.0f, 1.0f ); + + FX_AddBezier( start, targ1, + c1, v1, c2, v2, + 4.0f + s3, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + FX_AddBezier( start, targ1, + c2, v1, c1, v2, + 5.0f + s1 * 2, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + + VectorMA( start, 14.0f, dir, c1 ); + + FX_AddSprite( c1, NULL, NULL, 12.0f + crandom() * 4, 0.0f, 1.0f, 1.0f, random() * 360, 0.0f, 1.0f, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); + FX_AddSprite( c1, NULL, NULL, 6.0f + crandom() * 2, 0.0f, 1.0f, 1.0f, random() * 360, 0.0f, 1.0f, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); + + FX_AddSprite( targ1, NULL, NULL, 4.0f + crandom(), 0.0f, 1.0f, 0.0f, chaos, chaos, random() * 360, 0.0f, 10, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); + FX_AddSprite( targ1, NULL, NULL, 8.0f + crandom() * 2, 0.0f, 1.0f, 0.0f, chaos, chaos, random() * 360, 0.0f, 10, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); + + + //-------------------------------------------- + + VectorSubtract( targ2, targ1, dir ); + len = VectorNormalize( dir ); + + // Get the base control points, we'll work from there + VectorMA( targ1, 0.3333f * len, dir, c1 ); + VectorMA( targ1, 0.6666f * len, dir, c2 ); + + // get some chaos values that really aren't very chaotic :) + s1 = sin( cg->time * 0.005f ) * 2 + crandom() * 0.2f; + s2 = sin( cg->time * 0.001f ); + s3 = sin( cg->time * 0.011f ); + + VectorSet( chaos, len * 0.01f * s1, + len * 0.02f * s2, + len * 0.04f * (s1 + s2 + s3)); + + VectorAdd( c1, chaos, c1 ); + VectorScale( chaos, 4.0f, v1 ); + + VectorSet( chaos, -len * 0.02f * s3, + len * 0.01f * (s1 * s2), + -len * 0.02f * (s1 + s2 * s3)); + + VectorAdd( c2, chaos, c2 ); + VectorScale( chaos, 2.0f, v2 ); + + VectorSet( chaos, 1.0f, 1.0f, 1.0f ); + + FX_AddBezier( targ1, targ2, + c1, v1, c2, v2, + 5.0f + s1 * 2, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + FX_AddBezier( targ1, targ2, + c2, v2, c1, v1, + 3.0f + s3, 3.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + s1 = sin( cg->time * 0.0005f ) + crandom() * 0.1f; + s2 = sin( cg->time * 0.0025f ); + cc2 = cos( cg->time * 0.0025f ); + s3 = sin( cg->time * 0.01f ) + crandom() * 0.1f; + + VectorSet( chaos, len * 0.08f * s2, + len * 0.04f * cc2,//s1 * -s3, + len * 0.06f * s3 ); + + VectorAdd( c1, chaos, c1 ); + VectorScale( chaos, 4.0f, v1 ); + + VectorSet( chaos, len * 0.02f * s1 * s3, + len * 0.04f * s2, + len * 0.03f * s1 * s2 ); + + VectorAdd( c2, chaos, c2 ); + VectorScale( chaos, 3.0f, v2 ); + + VectorSet( chaos, 1.0f, 1.0f, 1.0f ); + + FX_AddBezier( targ1, targ2, + c1, v1, c2, v2, + 4.0f + s3, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + FX_AddBezier( targ1, targ2, + c2, v1, c1, v2, + 5.0f + s1 * 2, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + + FX_AddSprite( targ2, NULL, NULL, 4.0f + crandom(), 0.0f, 1.0f, 0.0f, chaos, chaos, random() * 360, 0.0f, 10, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); + FX_AddSprite( targ2, NULL, NULL, 8.0f + crandom() * 2, 0.0f, 1.0f, 0.0f, chaos, chaos, random() * 360, 0.0f, 10, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); +*/ +} + +//--------------------------------------------- +void FX_DEMP2_AltDetonate( vec3_t org, float size ) +{ + localEntity_t *ex; + + ex = CG_AllocLocalEntity(); + ex->leType = LE_FADE_SCALE_MODEL; + memset( &ex->refEntity, 0, sizeof( refEntity_t )); + + ex->refEntity.renderfx |= RF_VOLUMETRIC; + + ex->startTime = cg->time; + ex->endTime = ex->startTime + 800;//1600; + + ex->radius = size; + ex->refEntity.customShader = cgs.media.demp2ShellShader; + ex->refEntity.hModel = cgs.media.demp2Shell; + VectorCopy( org, ex->refEntity.origin ); + + ex->color[0] = ex->color[1] = ex->color[2] = 255.0f; +} diff --git a/codemp/cgame/fx_disruptor.c b/codemp/cgame/fx_disruptor.c new file mode 100644 index 0000000..ac66bb6 --- /dev/null +++ b/codemp/cgame/fx_disruptor.c @@ -0,0 +1,148 @@ +// Disruptor Weapon + +#include "cg_local.h" +#include "fx_local.h" + +/* +--------------------------- +FX_DisruptorMainShot +--------------------------- +*/ +static vec3_t WHITE={1.0f,1.0f,1.0f}; + +void FX_DisruptorMainShot( vec3_t start, vec3_t end ) +{ +// vec3_t dir; +// float len; + + trap_FX_AddLine( start, end, 0.1f, 6.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + WHITE, WHITE, 0.0f, + 150, trap_R_RegisterShader( "gfx/effects/redLine" ), + FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + +// VectorSubtract( end, start, dir ); +// len = VectorNormalize( dir ); + +// FX_AddCylinder( start, dir, 5.0f, 5.0f, 0.0f, +// 5.0f, 5.0f, 0.0f, +// len, len, 0.0f, +// 1.0f, 1.0f, 0.0f, +// WHITE, WHITE, 0.0f, +// 400, cgi_R_RegisterShader( "gfx/effects/spiral" ), 0 ); +} + + +/* +--------------------------- +FX_DisruptorAltShot +--------------------------- +*/ +void FX_DisruptorAltShot( vec3_t start, vec3_t end, qboolean fullCharge ) +{ + trap_FX_AddLine( start, end, 0.1f, 10.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + WHITE, WHITE, 0.0f, + 175, trap_R_RegisterShader( "gfx/effects/redLine" ), + FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + + if ( fullCharge ) + { + vec3_t YELLER={0.8f,0.7f,0.0f}; + + // add some beef + trap_FX_AddLine( start, end, 0.1f, 7.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + YELLER, YELLER, 0.0f, + 150, trap_R_RegisterShader( "gfx/misc/whiteline2" ), + FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + } +} + + +/* +--------------------------- +FX_DisruptorAltMiss +--------------------------- +*/ +#define FX_ALPHA_WAVE 0x00000008 + +void FX_DisruptorAltMiss( vec3_t origin, vec3_t normal ) +{ + vec3_t pos, c1, c2; + addbezierArgStruct_t b; + + VectorMA( origin, 4.0f, normal, c1 ); + VectorCopy( c1, c2 ); + c1[2] += 4; + c2[2] += 12; + + VectorAdd( origin, normal, pos ); + pos[2] += 28; + + /* + FX_AddBezier( origin, pos, c1, vec3_origin, c2, vec3_origin, 6.0f, 6.0f, 0.0f, 0.0f, 0.2f, 0.5f, + WHITE, WHITE, 0.0f, 4000, trap_R_RegisterShader( "gfx/effects/smokeTrail" ), FX_ALPHA_WAVE ); + */ + + VectorCopy(origin, b.start); + VectorCopy(pos, b.end); + VectorCopy(c1, b.control1); + VectorCopy(vec3_origin, b.control1Vel); + VectorCopy(c2, b.control2); + VectorCopy(vec3_origin, b.control2Vel); + + b.size1 = 6.0f; + b.size2 = 6.0f; + b.sizeParm = 0.0f; + b.alpha1 = 0.0f; + b.alpha2 = 0.2f; + b.alphaParm = 0.5f; + + VectorCopy(WHITE, b.sRGB); + VectorCopy(WHITE, b.eRGB); + + b.rgbParm = 0.0f; + b.killTime = 4000; + b.shader = trap_R_RegisterShader( "gfx/effects/smokeTrail" ); + b.flags = FX_ALPHA_WAVE; + + trap_FX_AddBezier(&b); + + trap_FX_PlayEffectID( cgs.effects.disruptorAltMissEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_DisruptorAltHit +--------------------------- +*/ + +void FX_DisruptorAltHit( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.disruptorAltHitEffect, origin, normal, -1, -1 ); +} + + + +/* +--------------------------- +FX_DisruptorHitWall +--------------------------- +*/ + +void FX_DisruptorHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.disruptorWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_DisruptorHitPlayer +--------------------------- +*/ + +void FX_DisruptorHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.disruptorFleshImpactEffect, origin, normal, -1, -1 ); +} diff --git a/codemp/cgame/fx_flechette.c b/codemp/cgame/fx_flechette.c new file mode 100644 index 0000000..46f2ceb --- /dev/null +++ b/codemp/cgame/fx_flechette.c @@ -0,0 +1,67 @@ +// Golan Arms Flechette Weapon + +#include "cg_local.h" + +/* +------------------------- +FX_FlechetteProjectileThink +------------------------- +*/ + +void FX_FlechetteProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.flechetteShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_FlechetteWeaponHitWall +------------------------- +*/ +void FX_FlechetteWeaponHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.flechetteWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_FlechetteWeaponHitPlayer +------------------------- +*/ +void FX_FlechetteWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ +// if ( humanoid ) +// { + trap_FX_PlayEffectID( cgs.effects.flechetteFleshImpactEffect, origin, normal, -1, -1 ); +// } +// else +// { +// trap_FX_PlayEffect( "blaster/droid_impact", origin, normal ); +// } +} + + +/* +------------------------- +FX_FlechetteProjectileThink +------------------------- +*/ + +void FX_FlechetteAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.flechetteAltShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} diff --git a/codemp/cgame/fx_force.c b/codemp/cgame/fx_force.c new file mode 100644 index 0000000..55e7c36 --- /dev/null +++ b/codemp/cgame/fx_force.c @@ -0,0 +1,16 @@ +// Any dedicated force oriented effects + +#include "cg_local.h" + +/* +------------------------- +FX_ForceDrained +------------------------- +*/ +// This effect is not generic because of possible enhancements +void FX_ForceDrained(vec3_t origin, vec3_t dir) +{ + VectorScale(dir, -1.0, dir); + trap_FX_PlayEffectID(cgs.effects.forceDrained, origin, dir, -1, -1); +} + diff --git a/codemp/cgame/fx_heavyrepeater.c b/codemp/cgame/fx_heavyrepeater.c new file mode 100644 index 0000000..87ddae0 --- /dev/null +++ b/codemp/cgame/fx_heavyrepeater.c @@ -0,0 +1,165 @@ +// Heavy Repeater Weapon + +#include "cg_local.h" + +#ifdef _XBOX +#include "../client/cl_data.h" +#endif + +/* +--------------------------- +FX_RepeaterProjectileThink +--------------------------- +*/ + +void FX_RepeaterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.repeaterProjectileEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------ +FX_RepeaterHitWall +------------------------ +*/ + +void FX_RepeaterHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.repeaterWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------ +FX_RepeaterHitPlayer +------------------------ +*/ + +void FX_RepeaterHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.repeaterFleshImpactEffect, origin, normal, -1, -1 ); +} + +static void CG_DistortionOrb( centity_t *cent ) +{ + refEntity_t ent; + vec3_t ang; + float scale = 0.5f; + float vLen; + + if (!cg_renderToTextureFX.integer) + { + return; + } + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( cent->lerpOrigin, ent.origin ); + + VectorSubtract(ent.origin, cg->refdef.vieworg, ent.axis[0]); + vLen = VectorLength(ent.axis[0]); + if (VectorNormalize(ent.axis[0]) <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + +// VectorCopy(cg->refdef.viewaxis[2], ent.axis[2]); +// CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); + vectoangles(ent.axis[0], ang); +#ifdef _XBOX + ang[ROLL] = cent->trickAlpha[ClientManager::ActiveClientNum()]; + cent->trickAlpha[ClientManager::ActiveClientNum()] += 16; //spin the half-sphere to give a "screwdriver" effect +#else + ang[ROLL] = cent->trickAlpha; + cent->trickAlpha += 16; //spin the half-sphere to give a "screwdriver" effect +#endif + + AnglesToAxis(ang, ent.axis); + + //radius must be a power of 2, and is the actual captured texture size + if (vLen < 128) + { + ent.radius = 256; + } + else if (vLen < 256) + { + ent.radius = 128; + } + else if (vLen < 512) + { + ent.radius = 64; + } + else + { + ent.radius = 32; + } + + VectorScale(ent.axis[0], scale, ent.axis[0]); + VectorScale(ent.axis[1], scale, ent.axis[1]); + VectorScale(ent.axis[2], -scale, ent.axis[2]); + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = 0;//cgs.media.halfShieldShader; + +#if 1 + ent.renderfx = (RF_DISTORTION|RF_RGB_TINT); + + //tint the whole thing a shade of blue + ent.shaderRGBA[0] = 200.0f; + ent.shaderRGBA[1] = 200.0f; + ent.shaderRGBA[2] = 255.0f; +#else //no tint + ent.renderfx = RF_DISTORTION; +#endif + + trap_R_AddRefEntityToScene( &ent ); +} + +/* +------------------------------ +FX_RepeaterAltProjectileThink +----------------------------- +*/ + +void FX_RepeaterAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + if (cg_repeaterOrb.integer) + { + CG_DistortionOrb(cent); + } + trap_FX_PlayEffectID( cgs.effects.repeaterAltProjectileEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------ +FX_RepeaterAltHitWall +------------------------ +*/ + +void FX_RepeaterAltHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.repeaterAltWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------ +FX_RepeaterAltHitPlayer +------------------------ +*/ + +void FX_RepeaterAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.repeaterAltWallImpactEffect, origin, normal, -1, -1 ); +} diff --git a/codemp/cgame/fx_local.h b/codemp/cgame/fx_local.h new file mode 100644 index 0000000..fa7bae9 --- /dev/null +++ b/codemp/cgame/fx_local.h @@ -0,0 +1,63 @@ +// +// fx_*.c +// + +// NOTENOTE This is not the best, DO NOT CHANGE THESE! +#define FX_ALPHA_LINEAR 0x00000001 +#define FX_SIZE_LINEAR 0x00000100 + + + +// Bryar +void FX_BryarProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BryarAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BryarHitWall( vec3_t origin, vec3_t normal ); +void FX_BryarAltHitWall( vec3_t origin, vec3_t normal, int power ); +void FX_BryarHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_BryarAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +// Blaster +void FX_BlasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterWeaponHitWall( vec3_t origin, vec3_t normal ); +void FX_BlasterWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +// Disruptor +void FX_DisruptorMainShot( vec3_t start, vec3_t end ); +void FX_DisruptorAltShot( vec3_t start, vec3_t end, qboolean fullCharge ); +void FX_DisruptorAltMiss( vec3_t origin, vec3_t normal ); +void FX_DisruptorAltHit( vec3_t origin, vec3_t normal ); +void FX_DisruptorHitWall( vec3_t origin, vec3_t normal ); +void FX_DisruptorHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +// Bowcaster +void FX_BowcasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BowcasterAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BowcasterHitWall( vec3_t origin, vec3_t normal ); +void FX_BowcasterHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +// Heavy Repeater +void FX_RepeaterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_RepeaterAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_RepeaterHitWall( vec3_t origin, vec3_t normal ); +void FX_RepeaterAltHitWall( vec3_t origin, vec3_t normal ); +void FX_RepeaterHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_RepeaterAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +// DEMP2 +void FX_DEMP2_ProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_DEMP2_HitWall( vec3_t origin, vec3_t normal ); +void FX_DEMP2_HitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_DEMP2_AltDetonate( vec3_t org, float size ); + +// Golan Arms Flechette +void FX_FlechetteProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_FlechetteWeaponHitWall( vec3_t origin, vec3_t normal ); +void FX_FlechetteWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_FlechetteAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Personal Rocket Launcher +void FX_RocketProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_RocketAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_RocketHitWall( vec3_t origin, vec3_t normal ); +void FX_RocketHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); diff --git a/codemp/cgame/fx_rocketlauncher.c b/codemp/cgame/fx_rocketlauncher.c new file mode 100644 index 0000000..2d3e3ec --- /dev/null +++ b/codemp/cgame/fx_rocketlauncher.c @@ -0,0 +1,61 @@ +// Rocket Launcher Weapon + +#include "cg_local.h" + +/* +--------------------------- +FX_RocketProjectileThink +--------------------------- +*/ + +void FX_RocketProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.rocketShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +--------------------------- +FX_RocketHitWall +--------------------------- +*/ + +void FX_RocketHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.rocketExplosionEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_RocketHitPlayer +--------------------------- +*/ + +void FX_RocketHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.rocketExplosionEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_RocketAltProjectileThink +--------------------------- +*/ + +void FX_RocketAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.rocketShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} diff --git a/codemp/cgame/holocronicons.h b/codemp/cgame/holocronicons.h new file mode 100644 index 0000000..3806e6c --- /dev/null +++ b/codemp/cgame/holocronicons.h @@ -0,0 +1,24 @@ +#if defined(_XBOX) && defined(_UI) +extern char *HolocronIcons[]; +#else +char *HolocronIcons[] = { + "gfx/mp/f_icon_lt_heal", //FP_HEAL, + "gfx/mp/f_icon_levitation", //FP_LEVITATION, + "gfx/mp/f_icon_speed", //FP_SPEED, + "gfx/mp/f_icon_push", //FP_PUSH, + "gfx/mp/f_icon_pull", //FP_PULL, + "gfx/mp/f_icon_lt_telepathy", //FP_TELEPATHY, + "gfx/mp/f_icon_dk_grip", //FP_GRIP, + "gfx/mp/f_icon_dk_l1", //FP_LIGHTNING, + "gfx/mp/f_icon_dk_rage", //FP_RAGE, + "gfx/mp/f_icon_lt_protect", //FP_PROTECT, + "gfx/mp/f_icon_lt_absorb", //FP_ABSORB, + "gfx/mp/f_icon_lt_healother", //FP_TEAM_HEAL, + "gfx/mp/f_icon_dk_forceother", //FP_TEAM_FORCE, + "gfx/mp/f_icon_dk_drain", //FP_DRAIN, + "gfx/mp/f_icon_sight", //FP_SEE, + "gfx/mp/f_icon_saber_attack", //FP_SABER_OFFENSE, + "gfx/mp/f_icon_saber_defend", //FP_SABER_DEFENSE, + "gfx/mp/f_icon_saber_throw" //FP_SABERTHROW +}; +#endif \ No newline at end of file diff --git a/codemp/cgame/tr_types.h b/codemp/cgame/tr_types.h new file mode 100644 index 0000000..dc2a3df --- /dev/null +++ b/codemp/cgame/tr_types.h @@ -0,0 +1,352 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + + +#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces +#ifdef _XBOX +#define MAX_ENTITIES 1024 // 11 bits, can't be increased without changing drawsurf bit packing (QSORT_ENTITYNUM_SHIFT) +#else +#define MAX_ENTITIES 2048 // 11 bits, can't be increased without changing drawsurf bit packing (QSORT_ENTITYNUM_SHIFT) +#endif +#define MAX_MINI_ENTITIES 1024 + +#define TR_WORLDENT (MAX_ENTITIES-1) + +// renderfx flags +#define RF_MINLIGHT 0x00001 // allways have some light (viewmodel, some items) +#define RF_THIRD_PERSON 0x00002 // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON 0x00004 // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK 0x00008 // for view weapon Z crunching +#define RF_NODEPTH 0x00010 // No depth at all (seeing through walls) + +#define RF_VOLUMETRIC 0x00020 // fake volumetric shading + +#define RF_NOSHADOW 0x00040 // don't add stencil shadows + +#define RF_LIGHTING_ORIGIN 0x00080 // use refEntity->lightingOrigin instead of refEntity->origin + // for lighting. This allows entities to sink into the floor + // with their origin going solid, and allows all parts of a + // player to get the same lighting +#define RF_SHADOW_PLANE 0x00100 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 0x00200 // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count + +#define RF_FORCE_ENT_ALPHA 0x00400 // override shader alpha settings +#define RF_RGB_TINT 0x00800 // override shader rgb settings + +#define RF_SHADOW_ONLY 0x01000 //add surfs for shadowing but don't draw them -rww + +#define RF_DISTORTION 0x02000 //area distortion effect -rww + +#define RF_FORKED 0x04000 // override lightning to have forks +#define RF_TAPERED 0x08000 // lightning tapers +#define RF_GROW 0x10000 // lightning grows from start to end during its life + +#define RF_DISINTEGRATE1 0x20000 // does a procedural hole-ripping thing. +#define RF_DISINTEGRATE2 0x40000 // does a procedural hole-ripping thing with scaling at the ripping point + +#define RF_SETANIMINDEX 0x80000 //use backEnd.currentEntity->e.skinNum for R_BindAnimatedImage + +#define RF_ALPHA_DEPTH 0x100000 //depth write on alpha model + +#define RF_FORCEPOST 0x200000 //force it to post-render -rww + +// refdef flags +#define RDF_NOWORLDMODEL 1 // used for player configuration screen +#define RDF_HYPERSPACE 4 // teleportation effect + +#define RDF_SKYBOXPORTAL 8 +#define RDF_DRAWSKYBOX 16 // the above marks a scene as being a 'portal sky'. this flag says to draw it or not + +#define RDF_AUTOMAP 32 //means this scene is to draw the automap -rww +#define RDF_NOFOG 64 //no global fog in this scene (but still brush fog) -rww + +extern int skyboxportal; +extern int drawskyboxportal; + +typedef byte color4ub_t[4]; + +typedef struct { + vec3_t xyz; + float st[2]; + byte modulate[4]; +} polyVert_t; + +typedef struct poly_s { + qhandle_t hShader; + int numVerts; + polyVert_t *verts; +} poly_t; + +typedef enum { + RT_MODEL, + RT_POLY, + RT_SPRITE, + RT_ORIENTED_QUAD, + RT_BEAM, + RT_SABER_GLOW, + RT_ELECTRICITY, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + RT_LINE, + RT_ORIENTEDLINE, + RT_CYLINDER, + RT_ENT_CHAIN, + + RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + +typedef struct miniRefEntity_s +{ + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + vec3_t origin; // also used as MODEL_BEAM's "from" + + // previous data for frame interpolation + vec3_t oldorigin; // also used as MODEL_BEAM's "to" + + // texturing + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + vec2_t shaderTexCoord; // texture coordinates used by tcMod entity modifiers + + // extra sprite information + float radius; + float rotation; // size 2 for RT_CYLINDER or number of verts in RT_ELECTRICITY + + // misc + float shaderTime; // subtracted from refdef time to control effect start times + int frame; // also used as MODEL_BEAM's diameter + +} miniRefEntity_t; + +#pragma warning (disable : 4201 ) +typedef struct { + // this stucture must remain identical as the miniRefEntity_t + // + // + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + vec3_t origin; // also used as MODEL_BEAM's "from" + + // previous data for frame interpolation + vec3_t oldorigin; // also used as MODEL_BEAM's "to" + + // texturing + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + vec2_t shaderTexCoord; // texture coordinates used by tcMod entity modifiers + + // extra sprite information + float radius; + float rotation; + + // misc + float shaderTime; // subtracted from refdef time to control effect start times + int frame; // also used as MODEL_BEAM's diameter + // + // + // end miniRefEntity_t + + // + // + // specific full refEntity_t data + // + // + + // most recent data + vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) + float shadowPlane; // projection shadows go here, stencils go slightly lower + + // previous data for frame interpolation + int oldframe; + float backlerp; // 0.0 = current, 1.0 = old + + // texturing + int skinNum; // inline skin index + qhandle_t customSkin; // NULL for default skin + + // texturing +/* + union + { +// int skinNum; // inline skin index +// ivec3_t terxelCoords; // coords of patch for RT_TERXELS + struct + { + int miniStart; + int miniCount; + } uMini; + } uRefEnt; +*/ + + // extra sprite information + union { +/* + struct + { + float rotation; + float radius; + byte vertRGBA[4][4]; + } sprite; +*/ + struct + { + float width; + float width2; + float stscale; + } line; +/* + struct // that whole put-the-opening-brace-on-the-same-line-as-the-beginning-of-the-definition coding style is fecal + { + float width; + vec3_t control1; + vec3_t control2; + } bezier; +*/ +/* + struct + { + float width; + float width2; + float stscale; + float height; + float bias; + qboolean wrap; + } cylinder; +*/ +/* + struct + { + float width; + float deviation; + float stscale; + qboolean wrap; + qboolean taper; + } electricity; +*/ + } data; + + float endTime; + float saberLength; + +/* +Ghoul2 Insert Start +*/ + vec3_t angles; // rotation angles - used for Ghoul2 + + vec3_t modelScale; // axis scale for models +// CGhoul2Info_v *ghoul2; // has to be at the end of the ref-ent in order for it to be created properly + void *ghoul2; // has to be at the end of the ref-ent in order for it to be created properly +/* +Ghoul2 Insert End +*/ +#ifdef _XBOX + bool skipForPlayer2; +#endif +} refEntity_t; + + +#define MAX_RENDER_STRINGS 8 +#define MAX_RENDER_STRING_LENGTH 32 + +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewangles; + vec3_t viewaxis[3]; // transformation matrix + int viewContents; // world contents at vieworg + + // time in milliseconds for shader effects and other time dependent rendering issues + int time; + + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; +} refdef_t; + + +typedef enum { + STEREO_CENTER, + STEREO_LEFT, + STEREO_RIGHT +}; +typedef int stereoFrame_t; + + +/* +** glconfig_t +** +** Contains variables specific to the OpenGL configuration +** being run right now. These are constant once the OpenGL +** subsystem is initialized. +*/ +typedef enum { + TC_NONE, + TC_S3TC, + TC_S3TC_DXT +} textureCompression_t; + +typedef struct { + const char *renderer_string; + const char *vendor_string; + const char *version_string; + const char *extensions_string; + + int maxTextureSize; // queried from GL + int maxActiveTextures; // multitexture ability + float maxTextureFilterAnisotropy; + + int colorBits, depthBits, stencilBits; + + qboolean deviceSupportsGamma; + textureCompression_t textureCompression; + qboolean textureEnvAddAvailable; + qboolean clampToEdgeAvailable; + + int vidWidth, vidHeight; + + int displayFrequency; + + // synonymous with "does rendering consume the entire screen?", therefore + // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that + // used CDS. + qboolean isFullscreen; + qboolean stereoEnabled; +} glconfig_t; + + +#if !defined _WIN32 + +#define OPENGL_DRIVER_NAME "libGL.so" + +#else + +#define OPENGL_DRIVER_NAME "opengl32" + +#endif // !defined _WIN32 + + +#endif // __TR_TYPES_H diff --git a/codemp/client/0_sh_leak.cpp b/codemp/client/0_sh_leak.cpp new file mode 100644 index 0000000..dcb9873 --- /dev/null +++ b/codemp/client/0_sh_leak.cpp @@ -0,0 +1,412 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#pragma warning( disable : 4786) +#include "client.h" + +#include +#include "..\smartheap\smrtheap.h" +#if !defined(__Q_SHARED_H) + #include "../game/q_shared.h" +#endif +#if !defined(_QCOMMON_H_) + #include "../qcommon/qcommon.h" +#endif +#include +#include + +using namespace std; + +#if MEM_DEBUG +#include "..\smartheap\heapagnt.h" + +static const int maxStack=2048; +static int TotalMem; +static int TotalBlocks; +static int nStack; +static char StackNames[maxStack][256]; +static int StackSize[maxStack]; +static int StackCount[maxStack]; +static int StackCache[48]; +static int StackCacheAt=0; +static int CheckpointSize[1000]; +static int CheckpointCount[1000]; + +#define _FASTRPT_ + +cvar_t *mem_leakfile; +cvar_t *mem_leakreport; + +MEM_BOOL MEM_CALLBACK MyMemReporter2(MEM_ERROR_INFO *info) +{ + static char buffer[10000]; + if (!info->objectCreationInfo) + return 1; + info=info->objectCreationInfo; + int idx=info->checkpoint; + if (idx<0||idx>=1000) + { + idx=0; + } + CheckpointCount[idx]++; + CheckpointSize[idx]+=info->argSize; +//return 1; + dbgMemFormatCall(info,buffer,9999); + if (strstr(buffer,"ntdll")) + return 1; + if (strstr(buffer,"CLBCATQ")) + return 1; + int i; + TotalBlocks++; + if (TotalBlocks%1000==0) + { + char mess[1000]; + sprintf(mess,"%d blocks processed\n",TotalBlocks); + OutputDebugString(mess); + } + for (i=strlen(buffer);i>0;i--) + { + if (buffer[i]=='\n') + break; + } + if (!i) + return 1; + buffer[i]=0; + char *buf=buffer; + while (*buf) + { + if (*buf=='\n') + { + buf++; + break; + } + buf++; + } + char *start=0; + while (*buf) + { + while (*buf==' ') + buf++; + start=buf; + while (*buf!=0&&*buf!='\n') + buf++; + if (*start) + { + if (*buf) + { + *buf=0; + buf++; + } + if (strlen(start)>255) + start[255]=0; + if (strstr(start,"std::")) + { +// start=0; + continue; + } + if (strstr(start,"Malloc")) + { + start=0; + continue; + } + if (strstr(start,"FS_LoadFile")) + { + start=0; + continue; + } + if (strstr(start,"CopyString")) + { + start=0; + continue; + } + break; + } + } + if (!start||!*start) + { + start="UNKNOWN"; + } + + for (i=0;i<48;i++) + { + if (StackCache[i]<0||StackCache[i]>=nStack) + continue; + if (!strcmpi(start,StackNames[StackCache[i]])) + break; + } + if (i<48) + { + StackSize[StackCache[i]]+=info->argSize; + StackCount[StackCache[i]]++; + } + else + { + for (i=0;iargSize; + StackCount[i]++; + StackCache[StackCacheAt]=i; + StackCacheAt++; + if (StackCacheAt>=48) + StackCacheAt=0; + } + else if (iargSize; + StackCount[i]=1; + nStack++; + } + else if (nStackargSize; + StackCount[maxStack-1]=1; + } + else + { + StackSize[maxStack-1]+=info->argSize; + StackCount[maxStack-1]++; + } + } + TotalMem+=info->argSize; + return 1; +} + +void SH_Checking_f(void); +#endif + +class Leakage +{ + MEM_POOL MyPool; + +public: + Leakage() + { + MyPool = MemInitDefaultPool(); +// MemPoolSetSmallBlockSize(MyPool, 16); + MemPoolSetSmallBlockAllocator(MyPool,MEM_SMALL_BLOCK_SH3); +#if MEM_DEBUG + dbgMemSetGuardSize(2); +#endif + EnableChecking(100000); + } + + void LeakReport(void) + { +#if MEM_DEBUG + + int i; + char mess[1000]; + int blocks=dbgMemTotalCount(); + int mem=dbgMemTotalSize()/1024; + sprintf(mess,"Final Memory Summary %d blocks %d K\n",blocks,mem); + OutputDebugString(mess); + for (i=0;i<1000;i++) + { + CheckpointSize[i]=0; + CheckpointCount[i]=0; + } + + TotalMem=0; + TotalBlocks=0; + nStack=0; + MemSetErrorHandler(MyMemReporter2); + dbgMemReportLeakage(NULL,1,1000); + MemSetErrorHandler(MemDefaultErrorHandler); + if (TotalBlocks) + { + // Sort by size. + Sleep(100); + OutputDebugString("**************************************\n"); + OutputDebugString("**********Memory Leak Report**********\n"); + OutputDebugString("*************** By Size **************\n"); + OutputDebugString("**************************************\n"); + sprintf(mess,"Actual leakage %d blocks %d K\n",TotalBlocks,TotalMem/1024); + OutputDebugString(mess); + multimap > sortit; + for (i=0;i >(-StackSize[i],pair(StackCount[i],StackNames[i]))); + multimap >::iterator j; + Sleep(5); + for (j=sortit.begin();j!=sortit.end();j++) + { + sprintf(mess,"%5d KB %6d cnt %s\n",-(*j).first/1024,(*j).second.first,(*j).second.second); + // if (!(-(*j).first/1024)) + // break; + Sleep(5); + OutputDebugString(mess); + } + + // Sort by count. + Sleep(100); + OutputDebugString("**************************************\n"); + OutputDebugString("**********Memory Leak Report**********\n"); + OutputDebugString("************** By Count **************\n"); + OutputDebugString("**************************************\n"); + sprintf(mess,"Actual leakage %d blocks %d K\n",TotalBlocks,TotalMem/1024); + OutputDebugString(mess); + sortit.clear(); + for (i=0;i >(-StackCount[i],pair(StackSize[i],StackNames[i]))); + Sleep(5); + for (j=sortit.begin();j!=sortit.end();j++) + { + sprintf(mess,"%5d KB %6d cnt %s\n",(*j).second.first/1024,-(*j).first,(*j).second.second); + // if (!(-(*j).first/1024)) + // break; + Sleep(5); + OutputDebugString(mess); + } + } + else + { + OutputDebugString("No Memory Leaks\n"); + } + + // Sort by size. + Sleep(5); + OutputDebugString("***************************************\n"); + OutputDebugString("By Tag, sort: size ********************\n"); + OutputDebugString("size(K) count name \n"); + OutputDebugString("-----------------------\n"); + Sleep(5); + multimap sorted; + for (i=0;i<1000;i++) + { + if (CheckpointCount[i]) + { + sorted.insert(pair(-CheckpointSize[i],i)); + } + } + multimap::iterator k; + for (k=sorted.begin();k!=sorted.end();k++) + { +// sprintf(mess,"%8d %8d %s\n",CheckpointSize[(*k).second]/1024,CheckpointCount[(*k).second],(*k).second>=2?tagDefs[(*k).second-2]:"unknown"); + sprintf(mess,"%8d %8d %s\n",CheckpointSize[(*k).second]/1024,CheckpointCount[(*k).second],"unknown"); + Sleep(5); + OutputDebugString(mess); + } + + // Sort by count. + Sleep(5); + OutputDebugString("***************************************\n"); + OutputDebugString("By Tag, sort: count *******************\n"); + OutputDebugString("size(K) count name \n"); + OutputDebugString("-----------------------\n"); + Sleep(5); + sorted.clear(); + for (i=0;i<1000;i++) + { + if (CheckpointCount[i]) + { + sorted.insert(pair(-CheckpointCount[i],i)); + } + } + for (k=sorted.begin();k!=sorted.end();k++) + { +// sprintf(mess,"%8d %8d %s\n",CheckpointSize[(*k).second]/1024,CheckpointCount[(*k).second],(*k).second>=2?tagDefs[(*k).second-2]:"unknown"); + sprintf(mess,"%8d %8d %s\n",CheckpointSize[(*k).second]/1024,CheckpointCount[(*k).second],"unknown"); + Sleep(5); + OutputDebugString(mess); + } +#endif + } + + ~Leakage() + { +#if MEM_DEBUG +#if 0 + if (mem_leakfile && mem_leakfile->integer) + { + dbgMemSetDefaultErrorOutput(DBGMEM_OUTPUT_FILE,"leakage.out"); + dbgMemReportLeakage(NULL,1,1); + dbgMemSetDefaultErrorOutput(DBGMEM_OUTPUT_PROMPT,NULL); + } +#endif + if (mem_leakreport && mem_leakreport->integer) + { + LeakReport(); + } +#endif + } +#if MEM_DEBUG + + void EnableChecking(int x) + { + if (x) + { + dbgMemSetSafetyLevel(MEM_SAFETY_DEBUG); + dbgMemPoolSetCheckFrequency(MyPool, x); + dbgMemSetCheckFrequency(x); + dbgMemDeferFreeing(TRUE); + if (x>50000) + { + dbgMemSetDeferQueueLen(x+5000); + } + else + { + dbgMemSetDeferQueueLen(50000); + } + } + else + { + dbgMemSetSafetyLevel(MEM_SAFETY_SOME); + dbgMemDeferFreeing(FALSE); + } + + } +#endif + +}; + +static Leakage TheLeakage; + +#if MEM_DEBUG + +void MEM_Checking_f(void) +{ + if (Cmd_Argc() != 2) + { + Com_Printf ("mem_checking \n"); + return; + } + + if (atol(Cmd_Argv(1)) > 0 && atol(Cmd_Argv(1)) < 100) + { + Com_Printf ("mem_checking frequency is too low ( < 100 )\n"); + return; + } + + TheLeakage.EnableChecking(atol(Cmd_Argv(1))); +} + +void MEM_Report_f(void) +{ + TheLeakage.LeakReport(); +} + +/* +void myexit(void) +{ + TheLeakage.LeakReport(); +} +*/ + +void SH_Register(void) +{ + Cmd_AddCommand ("mem_checking", MEM_Checking_f); + Cmd_AddCommand ("mem_report", MEM_Report_f); + + mem_leakfile = Cvar_Get( "mem_leakfile", "1", 0 ); + mem_leakreport = Cvar_Get( "mem_leakreport", "1", 0 ); +// atexit(myexit); +} + +#endif diff --git a/codemp/client/BinkVideo.cpp b/codemp/client/BinkVideo.cpp new file mode 100644 index 0000000..debd8f9 --- /dev/null +++ b/codemp/client/BinkVideo.cpp @@ -0,0 +1,524 @@ +#include "snd_local_console.h" +#include "../renderer/tr_local.h" +#include "BinkVideo.h" +#include "RAD.h" + + +bool bvUseGCTexMem = true; + +#ifdef _XBOX +int memMarker = 0; +char* binkXboxStartAddr = NULL; +char* binkXboxCurrentAddr = NULL; +char* binkXboxNextAddr = NULL; +#endif + +static void PTR4* RADEXPLINK AllocWrapper(U32 size) +{ + +// Give bink pre-initialized mem on xbox +#ifdef _XBOX + switch(memMarker) + { + case 0: + memMarker++; + binkXboxCurrentAddr = binkXboxStartAddr; + binkXboxNextAddr = binkXboxCurrentAddr + size; + return (void *)binkXboxStartAddr; + case 1: case 2: case 3: case 4: case 5: case 6: case 7: + memMarker++; + binkXboxCurrentAddr = binkXboxNextAddr; + binkXboxNextAddr = binkXboxCurrentAddr + size; + return (void *)binkXboxCurrentAddr; + case 8: + memMarker = -1; + binkXboxCurrentAddr = binkXboxNextAddr; + binkXboxNextAddr = binkXboxStartAddr; + return (void *)binkXboxCurrentAddr; + default: + return BinkVideo::Allocate(size); + } +#endif + + return BinkVideo::Allocate(size); +} + +static void RADEXPLINK FreeWrapper(void PTR4* ptr) +{ + +// Don't free the preinitialized mem +#ifdef _XBOX + if(memMarker < 6) + { + memMarker++; + return; + } + else if(memMarker == 6) + { + memMarker = 1; + binkXboxNextAddr = binkXboxStartAddr + XBOX_MEM_STAGE_1; + return; + } +#endif + BinkVideo::Free(ptr); +} + + +/********* +BinkVideo +*********/ +BinkVideo::BinkVideo() +{ + bink = NULL; + buffer = NULL; + texture = 0; + x1 = 0.0f; + y1 = 0.0f; + x2 = 0.0f; + y2 = 0.0f; + w = 0.0f; + h = 0.0f; + status = NS_BV_STOPPED; + looping = false; +#ifdef _XBOX + initialized = false; +#endif +} + +/********* +~BinkVideo +*********/ +BinkVideo::~BinkVideo() +{ + Free(buffer); + BinkClose(bink); +} + +/********* +AllocateXboxMem +Pre-Allocates memory for xbox +*********/ +#ifdef _XBOX +void BinkVideo::AllocateXboxMem(void) +{ + u32 memToAllocate = XBOX_MEM_STAGE_1 + + XBOX_MEM_STAGE_2 + + XBOX_MEM_STAGE_3 + + XBOX_MEM_STAGE_4 + + XBOX_MEM_STAGE_5 + + XBOX_MEM_STAGE_6 + + XBOX_MEM_STAGE_7 + + XBOX_MEM_STAGE_8 + + XBOX_BUFFER_SIZE; + binkXboxStartAddr = (char*)Allocate(memToAllocate); + memMarker = 0; + initialized = true; +} + +/********* +FreeXboxMem +*********/ +void BinkVideo::FreeXboxMem(void) +{ + initialized = false; + Z_Free(binkXboxStartAddr); + memMarker = 0; +} +#endif + + +/********* +Start +Opens a bink file and gets it ready to play +*********/ +bool BinkVideo::Start(const char *filename, float xOrigin, float yOrigin, float width, float height) +{ + +#ifdef _XBOX + assert(initialized); +#endif + + // Check to see if a video is being played. + if(status == NS_BV_PLAYING) + { + // stop + this->Stop(); + } + + // Set memory allocation wrapper + RADSetMemory(AllocWrapper,FreeWrapper); + + // Set up sound for consoles +#if defined(_XBOX) + // If we are on XBox, tell Bink to play all of the 5.1 tracks + U32 TrackIDsToPlay[ 4 ] = { 0, 1, 2, 3 }; + BinkSetSoundTrack( 4, TrackIDsToPlay ); + + // Now route the sound tracks to the correct speaker + U32 bins[ 2 ]; + + bins[ 0 ] = DSMIXBIN_FRONT_LEFT; + bins[ 1 ] = DSMIXBIN_FRONT_RIGHT; + BinkSetMixBins( bink, 0, bins, 2 ); + bins[ 0 ] = DSMIXBIN_FRONT_CENTER; + BinkSetMixBins( bink, 1, bins, 1 ); + bins[ 0 ] = DSMIXBIN_LOW_FREQUENCY; + BinkSetMixBins( bink, 2, bins, 1 ); + bins[ 0 ] = DSMIXBIN_BACK_LEFT; + bins[ 1 ] = DSMIXBIN_BACK_RIGHT; + BinkSetMixBins( bink, 3, bins, 2 ); + +#elif defined(_GAMECUBE) + BinkSoundUseNGCSound(); + + RADMEMALLOC a; + alGeti(AL_MEMORY_ALLOCATOR, (ALint*)&a); + + RADMEMFREE f; + alGeti(AL_MEMORY_DEALLOCATOR, (ALint*)&f); + + RADSetAudioMemory(a, f); +#endif + + + // Try to open the Bink file. +#ifdef _XBOX + bink = BinkOpen( filename, BINKSNDTRACK ); + if(!bink) + { + return false; + } +#elif defined _GAMECUBE + if(bvUseGCTexMem) + { + extern void GLW_TexCacheLock(void); + GLW_TexCacheLock(); + } + + bink = BinkOpen( filename, 0); + + if(!bink) + { + extern void GLW_TexCacheUnlock(void); + GLW_TexCacheUnlock(); + return false; + } +#endif + + + assert(bink->Width <= MAX_WIDTH && bink->Height <=MAX_HEIGHT); + + // allocate memory for the frame buffer +#ifdef _XBOX + buffer = AllocWrapper(XBOX_BUFFER_SIZE); +#elif _GAMECUBE + buffer = Allocate(XBOX_BUFFER_SIZE); +#endif + + // set the height, width, etc... + x1 = xOrigin; + y1 = yOrigin; + x2 = x1 + width; + y2 = y1 + height; + w = width; + h = height; + + // flush any background sound reads +#if defined (_XBOX)|| (_GAMECUBE) + extern void S_DrainRawSoundData(void); + S_DrainRawSoundData(); +#endif + + // Create the video texture + GLuint tex = (GLuint)texture; + if (tex != 0) + qglDeleteTextures(1, &tex); + + qglGenTextures(1, &tex); + qglBindTexture(GL_TEXTURE_2D, tex); + glState.currenttextures[glState.currenttmu] = tex; + + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB5, bink->Width, bink->Height, 0, + GL_RGB_SWIZZLE_EXT, GL_UNSIGNED_BYTE, buffer ); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + + texture = (int)tex; + + status = NS_BV_PLAYING; + + return true; +} + +/********* +Run +Decompresses a frame, renders it to the screen, and advances to +the next frame. +*********/ +bool BinkVideo::Run(void) +{ + if(status == NS_BV_STOPPED) // A movie can't be run if it's not started first + { + return false; + } + + while(BinkWait(bink)); // Wait + + DecompressFrame(); // Decompress + Draw(); // Render + + if(status != NS_BV_PAUSED) // Only advance the frame is not paused + { + BinkNextFrame( bink ); + } + + + if(bink->FrameNum == (bink->Frames - 1) && !looping) // The movie is done + { + Stop(); + return false; + } + + return true; +} + +/********* +GetBinkData +Returns the buffer data for the next frame of the video +*********/ +void* BinkVideo::GetBinkData(void) +{ + while(BinkWait(bink)); + DecompressFrame(); + BinkNextFrame(bink); + return buffer; +} + +/******** +Draw +Copies the decompressed frame to a texture to be rendered on +the screen. +********/ +void BinkVideo::Draw(void) +{ + if(buffer) + { + qglFlush(); + + extern void RB_SetGL2D (void); + RB_SetGL2D(); + + GL_SelectTexture(0); + + // Update the video texture + qglBindTexture(GL_TEXTURE_2D, (GLuint)texture); + glState.currenttextures[glState.currenttmu] = texture; + + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, bink->Width, bink->Height, + GL_RGB_SWIZZLE_EXT, GL_UNSIGNED_BYTE, buffer ); + + // Clear the screen. We use triangles here (instead + // of glClear) because we want the back buffer to stick + // around... so we can get a nice, cheap fade on Gamecube + // reset. + qglColor3f(0.f, 0.f, 0.f); +#if defined (_XBOX) || (_GAMECUBE) + qglBeginEXT (GL_TRIANGLE_STRIP, 4, 0, 0, 4, 0); +#else + qglBegin(GL_TRIANGLE_STRIP); +#endif + qglTexCoord2f ( 0, 0 ); + qglVertex2f (-10, -10); + qglTexCoord2f ( 1 , 0 ); + qglVertex2f (650, -10); + qglTexCoord2f ( 0, 1 ); + qglVertex2f (-10, 490); + qglTexCoord2f ( 1, 1 ); + qglVertex2f (650, 490); + qglEnd (); + + // Draw the video + qglColor3f(1.f, 1.f, 1.f); +#if defined (_XBOX) || (_GAMECUBE) + qglBeginEXT (GL_TRIANGLE_STRIP, 4, 0, 0, 4, 0); +#else + qglBegin(GL_TRIANGLE_STRIP); +#endif + qglTexCoord2f ( 0, 0 ); + qglVertex2f (x1, y1); + qglTexCoord2f ( 1 , 0 ); + qglVertex2f (x2, y1); + qglTexCoord2f ( 0, 1 ); + qglVertex2f (x1, y2); + qglTexCoord2f ( 1, 1 ); + qglVertex2f (x2, y2); + qglEnd (); + } +} + +/********* +Stop +Stops the current movie, and clears it from memory +*********/ +void BinkVideo::Stop(void) +{ + BinkClose(bink); + bink = NULL; +#ifdef _XBOX + FreeWrapper(buffer); +#elif _GAMECUBE + Free(buffer); +#endif + buffer = NULL; + + GLuint tex = (GLuint)texture; + if (tex != 0) + qglDeleteTextures(1, &tex); + + texture = 0; + x1 = 0.0f; + y1 = 0.0f; + x2 = 0.0f; + y2 = 0.0f; + w = 0.0f; + h = 0.0f; + status = NS_BV_STOPPED; +#ifdef _XBOX + memMarker = 0; +#endif + +#ifdef _GAMECUBE + extern void GLW_TexCacheUnlock(void); + GLW_TexCacheUnlock(); +#endif +} + +/********* +Pause +Pauses the current movie. Only the current frame is rendered +*********/ +void BinkVideo::Pause(void) +{ + status = NS_BV_PAUSED; +} + +/********* +SetExtends +Sets dimmension variables +*********/ + +void BinkVideo::SetExtents(float xOrigin, float yOrigin, float width, float height) +{ + x1 = xOrigin; + y1 = yOrigin; + x2 = x1 + width; + y2 = y1 + height; + w = width; + h = height; +} + +/********* +SetMasterVolume +Sets the volume of the specified track +*********/ +void BinkVideo::SetMasterVolume(s32 volume) +{ +#ifdef _XBOX + int i; + for(i = 0; i < 4; i++) + { + BinkSetVolume(bink,i,volume); + } +#else + BinkSetVolume(bink,0,volume); +#endif +} + +/********* +DecompressFrame +Decompresses current frame and copies the data to +the buffer +*********/ +S32 BinkVideo::DecompressFrame() +{ + BinkDoFrame(bink); + + S32 skip; + skip = BinkCopyToBuffer( + bink, + (void *)buffer, + NS_BV_DEFAULT_CIN_BPS * bink->Width, //pitch + bink->Height, + 0, + 0, + BINKCOPYALL | BINKSURFACE565); + return skip; +} + +/********* +Allocate +Allocates memory for the frame buffer +*********/ +void *BinkVideo::Allocate(U32 size) +{ + + size = RoundUp(size + 32, 32); + char* ptr = NULL; + +#ifdef _GAMECUBE + if (bvUseGCTexMem) + { + // Try allocating from texture cache + extern void* GLW_TexCacheAllocRaw(int size); + ptr = (char*)GLW_TexCacheAllocRaw(size); + } +#endif + + if (!ptr) + { + // Did not allocate texture cache memory, fall + // back to main memory.. + ptr = (char*)Z_Malloc(size, TAG_BINK, qfalse, 32); + ptr[0] = 'z'; + } + else + { + // Allocated memory from the texture cache + ptr[0] = 't'; + } + + return (void*)(ptr + 32); +} + +/********* +FreeBuffer +Releases the frame buffer memory +*********/ +void BinkVideo::Free(void* ptr) +{ + char* base = (char*)ptr - 32; + + switch (*base) + { +#ifdef _GAMECUBE + case 't': + { + // Free texture cache memory + extern void GLW_TexCacheFreeRaw(void* ptr); + GLW_TexCacheFreeRaw(base); + break; + } +#endif + + case 'z': + // Free main memory + Z_Free(base); + break; + + default: + assert(false); + } +} diff --git a/codemp/client/BinkVideo.h b/codemp/client/BinkVideo.h new file mode 100644 index 0000000..7cc0445 --- /dev/null +++ b/codemp/client/BinkVideo.h @@ -0,0 +1,73 @@ +#ifndef NS_BINKVIDEO +#define NS_BINKVIDEO + +#include "bink.h" +#define NS_BV_DEFAULT_CIN_BPS (2) +#define MAX_WIDTH 512 +#define MAX_HEIGHT 512 + +#define XBOX_MEM_STAGE_1 32640 +#define XBOX_MEM_STAGE_2 786528 +#define XBOX_MEM_STAGE_3 557152 +#define XBOX_MEM_STAGE_4 106560 +#define XBOX_MEM_STAGE_5 138304 +#define XBOX_MEM_STAGE_6 25696 +#define XBOX_MEM_STAGE_7 100 +#define XBOX_MEM_STAGE_8 100 + +#define XBOX_BUFFER_SIZE NS_BV_DEFAULT_CIN_BPS * MAX_WIDTH * MAX_HEIGHT + +typedef enum { + NS_BV_PLAYING, // Movie is playing + NS_BV_STOPPED, // Movie is stopped + NS_BV_PAUSED // Movie is paused +}; + +class BinkVideo +{ +private: + HBINK bink; + void* buffer; + int texture; + int status; + bool looping; + float x1; + float y1; + float x2; + float y2; + float w; + float h; + +#ifdef _XBOX + bool initialized; +#endif + + void Draw(void); + S32 DecompressFrame(); + + +public: + + BinkVideo(); + ~BinkVideo(); + bool Start(const char *filename, float xOrigin, float yOrigin, float width, float height); + bool Run(void); + void Stop(void); + void Pause(void); + void SetExtents(float xOrigin, float yOrigin, float width, float height); + int GetStatus(void) { return status; } + void SetLooping(bool loop) { looping = loop; } + void* GetBinkData(void); + int GetBinkWidth(void) { return this->bink->Width; } + int GetBinkHeight(void) { return this->bink->Height; } + void SetMasterVolume(s32 volume); +#ifdef _XBOX + void AllocateXboxMem(void); + void FreeXboxMem(void); +#endif + static void* Allocate(U32 size); + static void Free(void* ptr); + bool Ready(void) { return this->bink != NULL; } +}; + +#endif diff --git a/codemp/client/cl_cgame.cpp b/codemp/client/cl_cgame.cpp new file mode 100644 index 0000000..65eff58 --- /dev/null +++ b/codemp/client/cl_cgame.cpp @@ -0,0 +1,2147 @@ +// cl_cgame.c -- client system interaction with client game +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "../RMG/RM_Headers.h" + +#include "client.h" + +#include "../game/botlib.h" + +#include "../RMG/RM_Headers.h" + +#if !defined(FX_EXPORT_H_INC) + #include "FXExport.h" +#endif + +#include "FXutil.h" + +#if !defined(CROFFSYSTEM_H_INC) + #include "../qcommon/ROFFSystem.h" +#endif + +#ifdef _DONETPROFILE_ +#include "../qcommon/INetProfile.h" +#endif + +#include "../renderer/tr_worldeffects.h" + +/* +Ghoul2 Insert Start +*/ + +#if !defined(G2_H_INC) + #include "../ghoul2/G2_local.h" +#endif + +#include "../qcommon/stringed_ingame.h" + +#include "../ghoul2/G2_gore.h" + +extern CMiniHeap *G2VertSpaceClient; + +#include "snd_ambient.h" + +#include "../qcommon/timing.h" + +#include "../renderer/tr_local.h" + +//extern int contentOverride; + +/* +Ghoul2 Insert End +*/ + +#include "../cgame/cg_local.h" + +#ifdef _XBOX +#include "cl_data.h" +#endif + +extern botlib_export_t *botlib_export; + +extern qboolean loadCamera(const char *name); +extern void startCamera(int time); +extern qboolean getCameraInfo(int time, vec3_t *origin, vec3_t *angles); + +void FX_FeedTrail(effectTrailArgStruct_t *a); + +int CM_LoadSubBSP(const char *name, qboolean clientload); +void RE_InitRendererTerrain( const char *info ); + + +/* +==================== +CL_GetGameState +==================== +*/ +void CL_GetGameState( gameState_t *gs ) { + *gs = cl->gameState; +} + +/* +==================== +CL_GetGlconfig +==================== +*/ +void CL_GetGlconfig( glconfig_t *glconfig ) { + *glconfig = cls.glconfig; +} + + +/* +==================== +CL_GetUserCmd +==================== +*/ +qboolean CL_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + // cmds[cmdNumber] is the last properly generated command + + // can't return anything that we haven't created yet + if ( cmdNumber > cl->cmdNumber ) { + Com_Error( ERR_DROP, "CL_GetUserCmd: %i >= %i", cmdNumber, cl->cmdNumber ); + } + + // the usercmd has been overwritten in the wrapping + // buffer because it is too far out of date + if ( cmdNumber <= cl->cmdNumber - CMD_BACKUP ) { + return qfalse; + } + + *ucmd = cl->cmds[ cmdNumber & CMD_MASK ]; + + return qtrue; +} + +int CL_GetCurrentCmdNumber( void ) { + return cl->cmdNumber; +} + + +/* +==================== +CL_GetParseEntityState +==================== +*/ +qboolean CL_GetParseEntityState( int parseEntityNumber, entityState_t *state ) { + // can't return anything that hasn't been parsed yet + if ( parseEntityNumber >= cl->parseEntitiesNum ) { + Com_Error( ERR_DROP, "CL_GetParseEntityState: %i >= %i", + parseEntityNumber, cl->parseEntitiesNum ); + } + + // can't return anything that has been overwritten in the circular buffer + if ( parseEntityNumber <= cl->parseEntitiesNum - MAX_PARSE_ENTITIES ) { + return qfalse; + } + + *state = cl->parseEntities[ parseEntityNumber & ( MAX_PARSE_ENTITIES - 1 ) ]; + return qtrue; +} + +/* +==================== +CL_GetCurrentSnapshotNumber +==================== +*/ +void CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + *snapshotNumber = cl->snap.messageNum; + *serverTime = cl->snap.serverTime; +} + +/* +==================== +CL_GetSnapshot +==================== +*/ +qboolean CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + clSnapshot_t *clSnap; + int i, count; + + if ( snapshotNumber > cl->snap.messageNum ) { + Com_Error( ERR_DROP, "CL_GetSnapshot: snapshotNumber > cl->snapshot.messageNum" ); + } + + // if the frame has fallen out of the circular buffer, we can't return it + if ( cl->snap.messageNum - snapshotNumber >= PACKET_BACKUP ) { + return qfalse; + } + + // if the frame is not valid, we can't return it + clSnap = &cl->snapshots[snapshotNumber & PACKET_MASK]; + if ( !clSnap->valid ) { + return qfalse; + } + + // if the entities in the frame have fallen out of their + // circular buffer, we can't return it + if ( cl->parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES ) { + return qfalse; + } + + // write the snapshot + snapshot->snapFlags = clSnap->snapFlags; + snapshot->serverCommandSequence = clSnap->serverCommandNum; + snapshot->ping = clSnap->ping; + snapshot->serverTime = clSnap->serverTime; + Com_Memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) ); + snapshot->ps = clSnap->ps; + snapshot->vps = clSnap->vps; //get the vehicle ps + count = clSnap->numEntities; + if ( count > MAX_ENTITIES_IN_SNAPSHOT ) { + Com_DPrintf( "CL_GetSnapshot: truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT ); + count = MAX_ENTITIES_IN_SNAPSHOT; + } + snapshot->numEntities = count; + + for ( i = 0 ; i < count ; i++ ) { + + int entNum = ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ; + + // copy everything but the ghoul2 pointer + memcpy(&snapshot->entities[i], &cl->parseEntities[ entNum ], sizeof(entityState_t)); + } + + // FIXME: configstring changes and server commands!!! + + return qtrue; +} + +qboolean CL_GetDefaultState(int index, entityState_t *state) +{ + if (index < 0 || index >= MAX_GENTITIES) + { + return qfalse; + } + + if (!(cl->entityBaselines[index].eFlags & EF_PERMANENT)) + { + return qfalse; + } + + *state = cl->entityBaselines[index]; + + return qtrue; +} + +/* +===================== +CL_SetUserCmdValue +===================== +*/ +extern float cl_mPitchOverride; +extern float cl_mYawOverride; +extern float cl_mSensitivityOverride; +void CL_SetUserCmdValue( int userCmdValue, float sensitivityScale, float mPitchOverride, float mYawOverride, float mSensitivityOverride, int fpSel, int invenSel ) { + cl->cgameUserCmdValue = userCmdValue; + cl->cgameSensitivity = sensitivityScale; + cl_mPitchOverride = mPitchOverride; + cl_mYawOverride = mYawOverride; + cl_mSensitivityOverride = mSensitivityOverride; + cl->cgameForceSelection = fpSel; + cl->cgameInvenSelection = invenSel; +} + +/* +===================== +CL_SetClientForceAngle +===================== +*/ +void CL_SetClientForceAngle(int time, vec3_t angle) +{ + cl->cgameViewAngleForceTime = time; + VectorCopy(angle, cl->cgameViewAngleForce); +} + +/* +===================== +CL_AddCgameCommand +===================== +*/ +void CL_AddCgameCommand( const char *cmdName ) { + Cmd_AddCommand( cmdName, NULL ); +} + +/* +===================== +CL_CgameError +===================== +*/ +void CL_CgameError( const char *string ) { + Com_Error( ERR_DROP, "%s", string ); +} + + +int gCLTotalClientNum = 0; +//keep track of the total number of clients +extern cvar_t *cl_autolodscale; +//if we want to do autolodscaling + +void CL_DoAutoLODScale(void) +{ + float finalLODScaleFactor = 0; + + if ( gCLTotalClientNum >= 8 ) + { + finalLODScaleFactor = (gCLTotalClientNum/-8.0f); + } + + + Cvar_Set( "r_autolodscalevalue", va("%f", finalLODScaleFactor) ); +} + +/* +===================== +CL_ConfigstringModified +===================== +*/ +void CL_ConfigstringModified( void ) { + char *old, *s; + int i, index; + char *dup; + gameState_t oldGs; + int len; + + index = atoi( Cmd_Argv(1) ); + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } + // get everything after "cs " + s = Cmd_ArgsFrom(2); + + old = cl->gameState.stringData + cl->gameState.stringOffsets[ index ]; + if ( !strcmp( old, s ) ) { + return; // unchanged + } + + // build the new gameState_t + oldGs = cl->gameState; + + Com_Memset( &cl->gameState, 0, sizeof( cl->gameState ) ); + + // leave the first 0 for uninitialized strings + cl->gameState.dataCount = 1; + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( i == index ) { + dup = s; + } else { + dup = oldGs.stringData + oldGs.stringOffsets[ i ]; + } + if ( !dup[0] ) { + continue; // leave with the default empty string + } + + len = strlen( dup ); + + if ( len + 1 + cl->gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl->gameState.stringOffsets[ i ] = cl->gameState.dataCount; + Com_Memcpy( cl->gameState.stringData + cl->gameState.dataCount, dup, len + 1 ); + cl->gameState.dataCount += len + 1; + } + + if (cl_autolodscale && cl_autolodscale->integer) + { + if (index >= CS_PLAYERS && + index < CS_G2BONES) + { //this means that a client was updated in some way. Go through and count the clients. + int clientCount = 0; + i = CS_PLAYERS; + + while (i < CS_G2BONES) + { + s = cl->gameState.stringData + cl->gameState.stringOffsets[ i ]; + + if (s && s[0]) + { + clientCount++; + } + + i++; + } + + gCLTotalClientNum = clientCount; + +#ifdef _DEBUG + Com_DPrintf("%i clients\n", gCLTotalClientNum); +#endif + + CL_DoAutoLODScale(); + } + } + + if ( index == CS_SYSTEMINFO ) { + // parse serverId and other cvars + CL_SystemInfoChanged(); + } + +} +#ifndef MAX_STRINGED_SV_STRING + #define MAX_STRINGED_SV_STRING 1024 +#endif +// just copied it from CG_CheckSVStringEdRef( +void CL_CheckSVStringEdRef(char *buf, const char *str) +{ //I don't really like doing this. But it utilizes the system that was already in place. + int i = 0; + int b = 0; + int strLen = 0; + qboolean gotStrip = qfalse; + + if (!str || !str[0]) + { + if (str) + { + strcpy(buf, str); + } + return; + } + + strcpy(buf, str); + + strLen = strlen(str); + + if (strLen >= MAX_STRINGED_SV_STRING) + { + return; + } + + while (i < strLen && str[i]) + { + gotStrip = qfalse; + + if (str[i] == '@' && (i+1) < strLen) + { + if (str[i+1] == '@' && (i+2) < strLen) + { + if (str[i+2] == '@' && (i+3) < strLen) + { //@@@ should mean to insert a StringEd reference here, so insert it into buf at the current place + char stringRef[MAX_STRINGED_SV_STRING]; + int r = 0; + + while (i < strLen && str[i] == '@') + { + i++; + } + + while (i < strLen && str[i] && str[i] != ' ' && str[i] != ':' && str[i] != '.' && str[i] != '\n') + { + stringRef[r] = str[i]; + r++; + i++; + } + stringRef[r] = 0; + + buf[b] = 0; + Q_strcat(buf, MAX_STRINGED_SV_STRING, SE_GetString("MP_SVGAME", stringRef)); + b = strlen(buf); + } + } + } + + if (!gotStrip) + { + buf[b] = str[i]; + b++; + } + i++; + } + + buf[b] = 0; +} +/* +=================== +CL_GetServerCommand + +Set up argc/argv for the given command +=================== +*/ +qboolean CL_GetServerCommand( int serverCommandNumber ) { + char *s; + char *cmd; + static char bigConfigString[BIG_INFO_STRING]; + + // if we have irretrievably lost a reliable command, drop the connection + if ( serverCommandNumber <= clc->serverCommandSequence - MAX_RELIABLE_COMMANDS ) + { + int i = 0; + + // when a demo record was started after the client got a whole bunch of + // reliable commands then the client never got those first reliable commands +#ifndef _XBOX // No demos on Xbox + if ( clc->demoplaying ) + return qfalse; +#endif + while (i < MAX_RELIABLE_COMMANDS) + { //spew out the reliable command buffer + if (clc->reliableCommands[i][0]) + { + Com_Printf("%i: %s\n", i, clc->reliableCommands[i]); + } + i++; + } + Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" ); + return qfalse; + } + + if ( serverCommandNumber > clc->serverCommandSequence ) { + Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" ); + return qfalse; + } + + s = clc->serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ]; + clc->lastExecutedServerCommand = serverCommandNumber; + + Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s ); + +rescan: + Cmd_TokenizeString( s ); + cmd = Cmd_Argv(0); + + if ( !strcmp( cmd, "disconnect" ) ) { +// char strEd[MAX_STRINGED_SV_STRING]; +// CL_CheckSVStringEdRef(strEd, Cmd_Argv(1)); + // These now always JUST send a string_ref: + Com_Error ( ERR_SERVERDISCONNECT, Cmd_Argv(1) ); + } + + if ( !strcmp( cmd, "bcs0" ) ) { + Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) ); + return qfalse; + } + + if ( !strcmp( cmd, "bcs1" ) ) { + s = Cmd_Argv(2); + if( strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + } + strcat( bigConfigString, s ); + return qfalse; + } + + if ( !strcmp( cmd, "bcs2" ) ) { + s = Cmd_Argv(2); + if( strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + } + strcat( bigConfigString, s ); + strcat( bigConfigString, "\"" ); + s = bigConfigString; + goto rescan; + } + + if ( !strcmp( cmd, "cs" ) ) { + CL_ConfigstringModified(); + // reparse the string, because CL_ConfigstringModified may have done another Cmd_TokenizeString() + Cmd_TokenizeString( s ); + return qtrue; + } + + if ( !strcmp( cmd, "map_restart" ) ) { + // clear notify lines and outgoing commands before passing + // the restart to the cgame + Con_ClearNotify(); + Com_Memset( cl->cmds, 0, sizeof( cl->cmds ) ); + return qtrue; + } + + // the clientLevelShot command is used during development + // to generate 128*128 screenshots from the intermission + // point of levels for the menu system to use + // we pass it along to the cgame to make apropriate adjustments, + // but we also clear the console and notify lines here + if ( !strcmp( cmd, "clientLevelShot" ) ) { + // don't do it if we aren't running the server locally, + // otherwise malicious remote servers could overwrite + // the existing thumbnails + if ( !com_sv_running->integer ) { + return qfalse; + } + // close the console + Con_Close(); + // take a special screenshot next frame + Cbuf_AddText( "wait ; wait ; wait ; wait ; screenshot levelshot\n" ); + return qtrue; + } + + // we may want to put a "connect to other server" command here + + // cgame can now act on the command + return qtrue; +} + + +/* +==================== +CL_CM_LoadMap + +Just adds default parameters that cgame doesn't need to know about +==================== +*/ +void CL_CM_LoadMap( const char *mapname ) { + int checksum; + + CM_LoadMap( mapname, qtrue, &checksum ); +} + +/* +==================== +CL_ShutdonwCGame + +==================== +*/ +void CL_ShutdownCGame( void ) { + cls.keyCatchers &= ~KEYCATCH_CGAME; + cls.cgameStarted = qfalse; + if ( !cgvm ) { + return; + } + VM_Call( cgvm, CG_SHUTDOWN ); + VM_Free( cgvm ); + cgvm = NULL; +#ifdef _DONETPROFILE_ + ClReadProf().ShowTotals(); +#endif +} + +static int FloatAsInt( float f ) { + int temp; + + *(float *)&temp = f; + + return temp; +} + +/* +==================== +CL_CgameSystemCalls + +The cgame module is making a system call +==================== +*/ +#define VMA(x) VM_ArgPtr(args[x]) +#define VMF(x) ((float *)args)[x] +extern int s_entityWavVol[MAX_GENTITIES]; +void R_WorldEffectCommand(const char *command); + +extern int CL_GetValueForHidden(const char *s); //cl_parse.cpp + +extern void R_AutomapElevationAdjustment(float newHeight); //tr_world.cpp +extern qboolean R_InitializeWireframeAutomap(void); //tr_world.cpp + +/* +extern float tr_distortionAlpha; //tr_shadows.cpp +extern float tr_distortionStretch; //tr_shadows.cpp +extern qboolean tr_distortionPrePost; //tr_shadows.cpp +extern qboolean tr_distortionNegate; //tr_shadows.cpp +*/ +extern qboolean cl_bUseFighterPitch; //cl_input.cpp + +int CL_CgameSystemCalls( int *args ) { + switch( args[0] ) { + //rww - alright, DO NOT EVER add a GAME/CGAME/UI generic call without adding a trap to match, and + //all of these traps must be shared and have cases in sv_game, cl_cgame, and cl_ui. They must also + //all be in the same order, and start at 100. + case TRAP_MEMSET: + Com_Memset( VMA(1), args[2], args[3] ); + return 0; + case TRAP_MEMCPY: + Com_Memcpy( VMA(1), VMA(2), args[3] ); + return 0; + case TRAP_STRNCPY: + return (int)strncpy( (char *)VMA(1), (const char *)VMA(2), args[3] ); + case TRAP_SIN: + return FloatAsInt( sin( VMF(1) ) ); + case TRAP_COS: + return FloatAsInt( cos( VMF(1) ) ); + case TRAP_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + case TRAP_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + case TRAP_MATRIXMULTIPLY: + MatrixMultiply( (vec3_t *)VMA(1), (vec3_t *)VMA(2), (vec3_t *)VMA(3) ); + return 0; + case TRAP_ANGLEVECTORS: + AngleVectors( (const float *)VMA(1), (float *)VMA(2), (float *)VMA(3), (float *)VMA(4) ); + return 0; + case TRAP_PERPENDICULARVECTOR: + PerpendicularVector( (float *)VMA(1), (const float *)VMA(2) ); + return 0; + case TRAP_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + case TRAP_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + case TRAP_TESTPRINTINT: + return 0; + case TRAP_TESTPRINTFLOAT: + return 0; + case TRAP_ACOS: + return FloatAsInt( Q_acos( VMF(1) ) ); + case TRAP_ASIN: + return FloatAsInt( Q_asin( VMF(1) ) ); + + + case CG_PRINTALWAYS: + Com_PrintfAlways( "%s", VMA(1) ); + return 0; + case CG_PRINT: + Com_Printf( "%s", VMA(1) ); + return 0; + case CG_ERROR: + Com_Error( ERR_DROP, "%s", VMA(1) ); + return 0; + case CG_MILLISECONDS: + return Sys_Milliseconds(); + //rww - precision timer funcs... -ALWAYS- call end after start with supplied ptr, or you'll get a nasty memory leak. + //not that you should be using these outside of debug anyway.. because you shouldn't be. So don't. + case CG_PRECISIONTIMER_START: + { + void **suppliedPtr =(void **)VMA(1); //we passed in a pointer to a point + timing_c *newTimer = new timing_c; //create the new timer + *suppliedPtr = newTimer; //assign the pointer within the pointer to point at the mem addr of our new timer + newTimer->Start(); //start the timer + } + return 0; + case CG_PRECISIONTIMER_END: + { + int r; + timing_c *timer = (timing_c *)args[1]; //this is the pointer we assigned in start, so we can directly cast it back + r = timer->End(); //get the result + delete timer; //delete the timer since we're done with it + return r; //return the result + } + case CG_CVAR_REGISTER: + Cvar_Register( (vmCvar_t *)VMA(1), (const char *)VMA(2), (const char *)VMA(3), args[4] ); + return 0; + case CG_CVAR_UPDATE: + Cvar_Update( (vmCvar_t *)VMA(1) ); + return 0; + case CG_CVAR_SET: + Cvar_Set( (const char *)VMA(1), (const char *)VMA(2) ); + return 0; + case CG_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( (const char *)VMA(1), (char *)VMA(2), args[3] ); + return 0; +/* + case CG_CVAR_GETHIDDENVALUE: + return CL_GetValueForHidden((const char *)VMA(1)); +*/ + case CG_ARGC: + return Cmd_Argc(); + case CG_ARGV: + Cmd_ArgvBuffer( args[1], (char *)VMA(2), args[3] ); + return 0; + case CG_ARGS: + Cmd_ArgsBuffer( (char *)VMA(1), args[2] ); + return 0; + + case CG_FS_FOPENFILE: + return FS_FOpenFileByMode( (const char *)VMA(1), (int *)VMA(2), (fsMode_t)args[3] ); + case CG_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + case CG_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + case CG_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; + case CG_FS_GETFILELIST: + return FS_GetFileList( (const char *)VMA(1), (const char *)VMA(2), (char *)VMA(3), args[4] ); + + case CG_SENDCONSOLECOMMAND: + Cbuf_AddText( (const char *)VMA(1) ); + return 0; + case CG_ADDCOMMAND: + CL_AddCgameCommand( (const char *)VMA(1) ); + return 0; + case CG_REMOVECOMMAND: + Cmd_RemoveCommand( (const char *)VMA(1) ); + return 0; + case CG_SENDCLIENTCOMMAND: + CL_AddReliableCommand( (const char *)VMA(1) ); + return 0; + case CG_UPDATESCREEN: + // this is used during lengthy level loading, so pump message loop +// Com_EventLoop(); // FIXME: if a server restarts here, BAD THINGS HAPPEN! +// We can't call Com_EventLoop here, a restart will crash and this _does_ happen +// if there is a map change while we are downloading at pk3. +// ZOID + SCR_UpdateScreen(); + return 0; + case CG_CM_LOADMAP: +/* + if (args[2]) + { + CM_LoadSubBSP(va("maps/%s.bsp", ((const char *)VMA(1)) + 1), qfalse); + } + else +*/ + { + CL_CM_LoadMap( (const char *)VMA(1) ); + } + return 0; + case CG_CM_NUMINLINEMODELS: + return CM_NumInlineModels(); + case CG_CM_INLINEMODEL: + return CM_InlineModel( args[1] ); + case CG_CM_TEMPBOXMODEL: + return CM_TempBoxModel( (const float *)VMA(1), (const float *)VMA(2), /*int capsule*/ qfalse ); + case CG_CM_TEMPCAPSULEMODEL: + return CM_TempBoxModel( (const float *)VMA(1), (const float *)VMA(2), /*int capsule*/ qtrue ); + case CG_CM_POINTCONTENTS: + return CM_PointContents( (const float *)VMA(1), args[2] ); + case CG_CM_TRANSFORMEDPOINTCONTENTS: + return CM_TransformedPointContents( (const float *)VMA(1), args[2], (const float *)VMA(3), (const float *)VMA(4) ); + case CG_CM_BOXTRACE: + CM_BoxTrace( (trace_t *)VMA(1), (const float *)VMA(2), (const float *)VMA(3), (const float *)VMA(4), (const float *)VMA(5), args[6], args[7], /*int capsule*/ qfalse ); + return 0; + case CG_CM_CAPSULETRACE: + CM_BoxTrace( (trace_t *)VMA(1), (const float *)VMA(2), (const float *)VMA(3), (const float *)VMA(4), (const float *)VMA(5), args[6], args[7], /*int capsule*/ qtrue ); + return 0; + case CG_CM_TRANSFORMEDBOXTRACE: + CM_TransformedBoxTrace( (trace_t *)VMA(1), (const float *)VMA(2), (const float *)VMA(3), (const float *)VMA(4), (const float *)VMA(5), args[6], args[7], (const float *)VMA(8), (const float *)VMA(9), /*int capsule*/ qfalse ); + return 0; + case CG_CM_TRANSFORMEDCAPSULETRACE: + CM_TransformedBoxTrace( (trace_t *)VMA(1), (const float *)VMA(2), (const float *)VMA(3), (const float *)VMA(4), (const float *)VMA(5), args[6], args[7], (const float *)VMA(8), (const float *)VMA(9), /*int capsule*/ qtrue ); + return 0; + case CG_CM_MARKFRAGMENTS: + return re.MarkFragments( args[1], (const vec3_t *)VMA(2), (const float *)VMA(3), args[4], (float *)VMA(5), args[6], (markFragment_t *)VMA(7) ); + case CG_S_GETVOICEVOLUME: + return s_entityWavVol[args[1]]; + case CG_S_MUTESOUND: + S_MuteSound( args[1], args[2] ); + return 0; + case CG_S_STARTSOUND: + S_StartSound( (float *)VMA(1), args[2], args[3], args[4] ); + return 0; + case CG_S_STARTLOCALSOUND: + S_StartLocalSound( args[1], args[2] ); + return 0; + case CG_S_CLEARLOOPINGSOUNDS: + S_ClearLoopingSounds(); + return 0; + case CG_S_ADDLOOPINGSOUND: + S_AddLoopingSound( args[1], (const float *)VMA(2), (const float *)VMA(3), args[4] ); + return 0; + case CG_S_ADDREALLOOPINGSOUND: + //S_AddRealLoopingSound( args[1], (const float *)VMA(2), (const float *)VMA(3), args[4] ); + S_AddLoopingSound( args[1], (const float *)VMA(2), (const float *)VMA(3), args[4] ); + return 0; + case CG_S_STOPLOOPINGSOUND: + S_StopLoopingSound( args[1] ); + return 0; + case CG_S_UPDATEENTITYPOSITION: + S_UpdateEntityPosition( args[1], (const float *)VMA(2) ); + return 0; + case CG_S_RESPATIALIZE: + S_Respatialize( args[1], (const float *)VMA(2), (vec3_t *)VMA(3), args[4] ); + return 0; + case CG_S_SHUTUP: + s_shutUp = (qboolean)args[1]; + return 0; + case CG_S_REGISTERSOUND: + return S_RegisterSound( (const char *)VMA(1) ); + case CG_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( (const char *)VMA(1), (const char *)VMA(2), args[3]?qtrue:qfalse ); + return 0; + + case CG_S_UPDATEAMBIENTSET: + S_UpdateAmbientSet((const char *)VMA(1), (float *)VMA(2)); + return 0; + case CG_AS_PARSESETS: + AS_ParseSets(); + return 0; + case CG_AS_ADDPRECACHEENTRY: + AS_AddPrecacheEntry((const char *)VMA(1)); + return 0; + case CG_S_ADDLOCALSET: + return S_AddLocalSet((const char *)VMA(1), (float *)VMA(2), (float *)VMA(3), args[4], args[5]); + case CG_AS_GETBMODELSOUND: + return AS_GetBModelSound((const char *)VMA(1), args[2]); + + case CG_R_LOADWORLDMAP: + re.LoadWorld( (const char *)VMA(1) ); + return 0; + case CG_R_REGISTERMODEL: + return re.RegisterModel( (const char *)VMA(1) ); + case CG_R_REGISTERSKIN: + return re.RegisterSkin( (const char *)VMA(1) ); + case CG_R_REGISTERSHADER: + return re.RegisterShader( (const char *)VMA(1) ); + case CG_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( (const char *)VMA(1) ); + case CG_R_REGISTERFONT: + return re.RegisterFont( (const char *)VMA(1) ); + case CG_R_FONT_STRLENPIXELS: + return re.Font_StrLenPixels( (const char *)VMA(1), args[2], VMF(3) ); + case CG_R_FONT_STRLENCHARS: + return re.Font_StrLenChars( (const char *)VMA(1) ); + case CG_R_FONT_STRHEIGHTPIXELS: + return re.Font_HeightPixels( args[1], VMF(2) ); + case CG_R_FONT_DRAWSTRING: + re.Font_DrawString( args[1], args[2], (const char *)VMA(3), (const float *) VMA(4), args[5], args[6], VMF(7) ); + return 0; + case CG_LANGUAGE_ISASIAN: + return re.Language_IsAsian(); + case CG_LANGUAGE_USESSPACES: + return re.Language_UsesSpaces(); + case CG_ANYLANGUAGE_READCHARFROMSTRING: + return re.AnyLanguage_ReadCharFromString( (const char *) VMA(1), (int *) VMA(2), (qboolean *) VMA(3) ); + case CG_R_CLEARSCENE: + re.ClearScene(); + return 0; + case CG_R_CLEARDECALS: + re.ClearDecals(); + return 0; + case CG_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( (const refEntity_t *)VMA(1) ); + return 0; + case CG_R_ADDPOLYTOSCENE: + re.AddPolyToScene( args[1], args[2], (const polyVert_t *)VMA(3), 1 ); + return 0; + case CG_R_ADDPOLYSTOSCENE: + re.AddPolyToScene( args[1], args[2], (const polyVert_t *)VMA(3), args[4] ); + return 0; + case CG_R_ADDDECALTOSCENE: + re.AddDecalToScene( (qhandle_t)args[1], (const float*)VMA(2), (const float*)VMA(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), (qboolean)args[9], VMF(10), (qboolean)args[11] ); + return 0; + case CG_R_LIGHTFORPOINT: + return re.LightForPoint( (float *)VMA(1), (float *)VMA(2), (float *)VMA(3), (float *)VMA(4) ); + case CG_R_ADDLIGHTTOSCENE: + re.AddLightToScene( (const float *)VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + case CG_R_ADDADDITIVELIGHTTOSCENE: + re.AddAdditiveLightToScene( (const float *)VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + case CG_R_RENDERSCENE: + re.RenderScene( (const refdef_t *)VMA(1) ); + return 0; + case CG_R_SETCOLOR: + re.SetColor( (const float *)VMA(1) ); + return 0; + case CG_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); + return 0; + case CG_R_MODELBOUNDS: + re.ModelBounds( args[1], (float *)VMA(2), (float *)VMA(3) ); + return 0; + case CG_R_LERPTAG: + return re.LerpTag( (orientation_t *)VMA(1), args[2], args[3], args[4], VMF(5), (const char *)VMA(6) ); + case CG_R_DRAWROTATEPIC: + re.DrawRotatePic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), VMF(9), args[10] ); + return 0; + case CG_R_DRAWROTATEPIC2: + re.DrawRotatePic2( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), VMF(9), args[10] ); + return 0; + + case CG_R_SETRANGEFOG: + tr.rangedFog = VMF(1); + return 0; + + case CG_R_SETREFRACTIONPROP: +/* + tr_distortionAlpha = VMF(1); + tr_distortionStretch = VMF(2); + tr_distortionPrePost = (qboolean)args[3]; + tr_distortionNegate = (qboolean)args[4]; +*/ + return 0; + + case CG_GETGLCONFIG: + CL_GetGlconfig( (glconfig_t *)VMA(1) ); + return 0; + case CG_GETGAMESTATE: + CL_GetGameState( (gameState_t *)VMA(1) ); + return 0; + case CG_GETCURRENTSNAPSHOTNUMBER: + CL_GetCurrentSnapshotNumber( (int *)VMA(1), (int *)VMA(2) ); + return 0; + case CG_GETSNAPSHOT: + return CL_GetSnapshot( args[1], (snapshot_t *)VMA(2) ); + case CG_GETDEFAULTSTATE: + return CL_GetDefaultState(args[1], (entityState_t *)VMA(2)); + case CG_GETSERVERCOMMAND: + return CL_GetServerCommand( args[1] ); + case CG_GETCURRENTCMDNUMBER: + return CL_GetCurrentCmdNumber(); + case CG_GETUSERCMD: + return CL_GetUserCmd( args[1], (struct usercmd_s *)VMA(2) ); + case CG_SETUSERCMDVALUE: + cl_bUseFighterPitch = (qboolean)args[8]; + CL_SetUserCmdValue( args[1], VMF(2), VMF(3), VMF(4), VMF(5), args[6], args[7] ); + return 0; + case CG_SETCLIENTFORCEANGLE: + CL_SetClientForceAngle(args[1], (float *)VMA(2)); + return 0; + case CG_SETCLIENTTURNEXTENT: + return 0; + + case CG_OPENUIMENU: + VM_Call( uivm, UI_SET_ACTIVE_MENU, args[1] ); + return 0; + + case CG_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + case CG_KEY_ISDOWN: + return Key_IsDown( args[1] ); + case CG_KEY_GETCATCHER: + return Key_GetCatcher(); + case CG_KEY_SETCATCHER: + Key_SetCatcher( args[1] ); + return 0; + case CG_KEY_GETKEY: + return Key_GetKey( (const char *)VMA(1) ); + + case CG_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( (char *)VMA(1) ); + case CG_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( (const char *)VMA(1) ); + case CG_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case CG_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], (struct pc_token_s *)VMA(2) ); + case CG_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], (char *)VMA(2), (int *)VMA(3) ); + case CG_PC_LOAD_GLOBAL_DEFINES: + return botlib_export->PC_LoadGlobalDefines ( (char *)VMA(1) ); + case CG_PC_REMOVE_ALL_GLOBAL_DEFINES: + botlib_export->PC_RemoveAllGlobalDefines ( ); + return 0; + + case CG_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + + case CG_REAL_TIME: + return Com_RealTime( (struct qtime_s *)VMA(1) ); + case CG_SNAPVECTOR: + Sys_SnapVector( (float *)VMA(1) ); + return 0; + + case CG_CIN_PLAYCINEMATIC: + return CIN_PlayCinematic((const char *)VMA(1), args[2], args[3], args[4], args[5], args[6]); + + case CG_CIN_STOPCINEMATIC: + return CIN_StopCinematic(args[1]); + + case CG_CIN_RUNCINEMATIC: + return CIN_RunCinematic(args[1]); + + case CG_CIN_DRAWCINEMATIC: + CIN_DrawCinematic(args[1]); + return 0; + + case CG_CIN_SETEXTENTS: + CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); + return 0; + + case CG_R_REMAP_SHADER: + re.RemapShader( (const char *)VMA(1), (const char *)VMA(2), (const char *)VMA(3) ); + return 0; + + case CG_R_GET_LIGHT_STYLE: + re.GetLightStyle(args[1], (unsigned char *)VMA(2)); + return 0; + + case CG_R_SET_LIGHT_STYLE: + re.SetLightStyle(args[1], args[2]); + return 0; + + case CG_R_GET_BMODEL_VERTS: + re.GetBModelVerts( args[1], (float (*)[3])VMA(2), (float *)VMA(3) ); + return 0; + + case CG_R_GETDISTANCECULL: + { + float *f; + f = (float *)VMA(1); + *f = tr.distanceCull; + } + return 0; + + case CG_R_GETREALRES: + { + int *w = (int *)VMA(1); + int *h = (int *)VMA(2); + *w = glConfig.vidWidth; + *h = glConfig.vidHeight; + } + return 0; + + case CG_R_AUTOMAPELEVADJ: + R_AutomapElevationAdjustment(VMF(1)); + return 0; + + case CG_R_INITWIREFRAMEAUTO: + return R_InitializeWireframeAutomap(); + +/* + case CG_LOADCAMERA: + return loadCamera(VMA(1)); + + case CG_STARTCAMERA: + startCamera(args[1]); + return 0; + + case CG_GETCAMERAINFO: + return getCameraInfo(args[1], VMA(2), VMA(3)); +*/ + case CG_GET_ENTITY_TOKEN: + return re.GetEntityToken( (char *)VMA(1), args[2] ); + case CG_R_INPVS: + return re.inPVS( (const float *)VMA(1), (const float *)VMA(2), (byte *)VMA(3) ); + +#ifndef DEBUG_DISABLEFXCALLS + case CG_FX_ADDLINE: + FX_AddLine( (float *)VMA(1), (float *)VMA(2), VMF(3), VMF(4), VMF(5), + VMF(6), VMF(7), VMF(8), + (float *)VMA(9), (float *)VMA(10), VMF(11), + args[12], args[13], args[14]); + return 0; + case CG_FX_REGISTER_EFFECT: + return FX_RegisterEffect((const char *)VMA(1)); + + case CG_FX_PLAY_EFFECT: + FX_PlayEffect((const char *)VMA(1), (float *)VMA(2), (float *)VMA(3), args[4], args[5] ); + return 0; + + case CG_FX_PLAY_ENTITY_EFFECT: + assert(0);//gone! + //FX_PlayEntityEffect((const char *)VMA(1), (float *)VMA(2), (vec3_t *)VMA(3), args[4], args[5], args[6], args[7] ); + return 0; + + case CG_FX_PLAY_EFFECT_ID: + FX_PlayEffectID(args[1], (float *)VMA(2), (float *)VMA(3), args[4], args[5] ); + return 0; + + case CG_FX_PLAY_PORTAL_EFFECT_ID: + FX_PlayEffectID(args[1], (float *)VMA(2), (float *)VMA(3), args[4], args[5], qtrue ); + return 0; + + case CG_FX_PLAY_ENTITY_EFFECT_ID: + FX_PlayEntityEffectID(args[1], (float *)VMA(2), (vec3_t *)VMA(3), args[4], args[5], args[6], args[7] ); + return 0; + + case CG_FX_PLAY_BOLTED_EFFECT_ID: + { + //( int id, vec3_t org, void *pGhoul2, const int boltNum, const int entNum, const int modelNum, int iLooptime, qboolean isRelative ); + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[3]); + int boltInfo=0; + if ( G2API_AttachEnt( &boltInfo, &g2[args[6]], args[4], args[5], args[6] ) ) + { + FX_PlayBoltedEffectID(args[1], (float *)VMA(2), boltInfo, g2.mItem, args[7], (qboolean)args[8] ); + return 1; + } + return 0; + } + case CG_FX_ADD_SCHEDULED_EFFECTS: + FX_AddScheduledEffects((qboolean)args[1]); + return 0; + + case CG_FX_DRAW_2D_EFFECTS: + FX_Draw2DEffects ( VMF(1), VMF(2) ); + return 0; + + case CG_FX_INIT_SYSTEM: + return FX_InitSystem( (refdef_t*)VMA(1) ); + + case CG_FX_SET_REFDEF: + FX_SetRefDefFromCGame( (refdef_t*)VMA(1) ); + return 0; + + case CG_FX_FREE_SYSTEM: + return FX_FreeSystem(); + + case CG_FX_ADJUST_TIME: + FX_AdjustTime(args[1]); + return 0; + + case CG_FX_RESET: + FX_Free ( false ); + return 0; + + case CG_FX_ADDPOLY: + { + addpolyArgStruct_t *p; + + p = (addpolyArgStruct_t *)VMA(1);//args[1]; + + if (p) + { + FX_AddPoly(p->p, p->ev, p->numVerts, p->vel, p->accel, p->alpha1, p->alpha2, + p->alphaParm, p->rgb1, p->rgb2, p->rgbParm, p->rotationDelta, p->bounce, p->motionDelay, + p->killTime, p->shader, p->flags); + } + } + return 0; + + case CG_FX_ADDBEZIER: + { + addbezierArgStruct_t *b; + + b = (addbezierArgStruct_t *)VMA(1);//args[1]; + + if (b) + { + FX_AddBezier(b->start, b->end, b->control1, b->control1Vel, b->control2, b->control2Vel, + b->size1, b->size2, b->sizeParm, b->alpha1, b->alpha2, b->alphaParm, b->sRGB, + b->eRGB, b->rgbParm, b->killTime, b->shader, b->flags); + } + } + return 0; + + case CG_FX_ADDPRIMITIVE: + { + effectTrailArgStruct_t *a; + + a = (effectTrailArgStruct_t *)VMA(1);//args[1]; + + if (a) + { + FX_FeedTrail(a); + } + } + return 0; + + case CG_FX_ADDSPRITE: + { + addspriteArgStruct_t *s; + + s = (addspriteArgStruct_t *)VMA(1);//args[1]; + + if (s) + { + vec3_t rgb; + rgb[0] = 1; + rgb[1] = 1; + rgb[2] = 1; + //FX_AddSprite(NULL, s->origin, s->vel, s->accel, s->scale, s->dscale, s->sAlpha, s->eAlpha, + // s->rotation, s->bounce, s->life, s->shader, s->flags); + FX_AddParticle(s->origin, s->vel, s->accel, s->scale, s->dscale, 0, s->sAlpha, s->eAlpha, 0, + rgb, rgb, 0, s->rotation, 0, vec3_origin, vec3_origin, s->bounce, 0, 0, s->life, + s->shader, s->flags); + } + } + return 0; + case CG_FX_ADDELECTRICITY: + { + addElectricityArgStruct_t *p; + + p = (addElectricityArgStruct_t *)VMA(1); + + if (p) + { + + FX_AddElectricity(p->start, p->end, p->size1, p->size2, p->sizeParm, p->alpha1, p->alpha2, + p->alphaParm, p->sRGB, p->eRGB, p->rgbParm, p->chaos, p->killTime, p->shader, p->flags); + } + } + return 0; +#else + case CG_FX_REGISTER_EFFECT: + case CG_FX_PLAY_EFFECT: + case CG_FX_PLAY_ENTITY_EFFECT: + case CG_FX_PLAY_EFFECT_ID: + case CG_FX_PLAY_PORTAL_EFFECT_ID: + case CG_FX_PLAY_ENTITY_EFFECT_ID: + case CG_FX_PLAY_BOLTED_EFFECT_ID: + case CG_FX_ADD_SCHEDULED_EFFECTS: + case CG_FX_INIT_SYSTEM: + case CG_FX_FREE_SYSTEM: + case CG_FX_ADJUST_TIME: + case CG_FX_ADDPOLY: + case CG_FX_ADDBEZIER: + case CG_FX_ADDPRIMITIVE: + case CG_FX_ADDSPRITE: + case CG_FX_ADDELECTRICITY: + return 0; +#endif + +// case CG_SP_PRINT: +// CL_SP_Print(args[1], (byte *)VMA(2)); +// return 0; + + case CG_ROFF_CLEAN: + return theROFFSystem.Clean(qtrue); + + case CG_ROFF_UPDATE_ENTITIES: + theROFFSystem.UpdateEntities(qtrue); + return 0; + + case CG_ROFF_CACHE: + return theROFFSystem.Cache( (char *)VMA(1), qtrue ); + + case CG_ROFF_PLAY: + return theROFFSystem.Play(args[1], args[2], (qboolean)args[3], qtrue ); + + case CG_ROFF_PURGE_ENT: + return theROFFSystem.PurgeEnt( args[1], qtrue ); + + //rww - dynamic vm memory allocation! + case CG_TRUEMALLOC: + VM_Shifted_Alloc((void **)VMA(1), args[2]); + return 0; + case CG_TRUEFREE: + VM_Shifted_Free((void **)VMA(1)); + return 0; + +/* +Ghoul2 Insert Start +*/ + + case CG_G2_GETMODELNAME: + G2API_GetModelName(*(CGhoul2Info_v*)args[1], args[2], + (const char**)VMA(3)); + break; + + case CG_G2_LISTSURFACES: + G2API_ListSurfaces( (CGhoul2Info *) args[1] ); + return 0; + + case CG_G2_LISTBONES: + G2API_ListBones( (CGhoul2Info *) args[1], args[2]); + return 0; + + case CG_G2_HAVEWEGHOULMODELS: + return G2API_HaveWeGhoul2Models( *((CGhoul2Info_v *)args[1]) ); + + case CG_G2_SETMODELS: + G2API_SetGhoul2ModelIndexes( *((CGhoul2Info_v *)args[1]),(qhandle_t *)VMA(2),(qhandle_t *)VMA(3)); + return 0; + + case CG_G2_GETBOLT: + return G2API_GetBoltMatrix(*((CGhoul2Info_v *)args[1]), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + + case CG_G2_GETBOLT_NOREC: + gG2_GBMNoReconstruct = qtrue; + return G2API_GetBoltMatrix(*((CGhoul2Info_v *)args[1]), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + + case CG_G2_GETBOLT_NOREC_NOROT: + //gG2_GBMNoReconstruct = qtrue; + //Yeah, this was probably BAD. + gG2_GBMUseSPMethod = qtrue; + return G2API_GetBoltMatrix(*((CGhoul2Info_v *)args[1]), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + + case CG_G2_INITGHOUL2MODEL: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 0; +#endif + return G2API_InitGhoul2Model((CGhoul2Info_v **)VMA(1), (const char *)VMA(2), args[3], (qhandle_t) args[4], + (qhandle_t) args[5], args[6], args[7]); + + case CG_G2_SETSKIN: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + int modelIndex = args[2]; + + return G2API_SetSkin(&g2[modelIndex], args[3], args[4]); + } + + case CG_G2_COLLISIONDETECT: + G2API_CollisionDetect ( (CollisionRecord_t*)VMA(1), *((CGhoul2Info_v *)args[2]), + (const float*)VMA(3), + (const float*)VMA(4), + args[5], + args[6], + (float*)VMA(7), + (float*)VMA(8), + (float*)VMA(9), + G2VertSpaceClient, + args[10], + args[11], + VMF(12) ); + return 0; + + case CG_G2_ANGLEOVERRIDE: + return G2API_SetBoneAngles(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3), (float *)VMA(4), args[5], + (const Eorientations) args[6], (const Eorientations) args[7], (const Eorientations) args[8], + (qhandle_t *)VMA(9), args[10], args[11] ); + + case CG_G2_CLEANMODELS: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 0; +#endif + G2API_CleanGhoul2Models((CGhoul2Info_v **)VMA(1)); + // G2API_CleanGhoul2Models((CGhoul2Info_v **)args[1]); + return 0; + + case CG_G2_PLAYANIM: + return G2API_SetBoneAnim(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3), args[4], args[5], + args[6], VMF(7), args[8], VMF(9), args[10]); + + case CG_G2_GETBONEANIM: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + int modelIndex = args[10]; + + return G2API_GetBoneAnim(&g2[modelIndex], (const char*)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), + (int *)VMA(6), (int *)VMA(7), (float *)VMA(8), (int *)VMA(9)); + } + + case CG_G2_GETBONEFRAME: + { //rwwFIXMEFIXME: Just make a G2API_GetBoneFrame func too. This is dirty. + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + int modelIndex = args[6]; + int iDontCare1 = 0, iDontCare2 = 0, iDontCare3 = 0; + float fDontCare1 = 0; + + return G2API_GetBoneAnim(&g2[modelIndex], (const char*)VMA(2), args[3], (float *)VMA(4), &iDontCare1, + &iDontCare2, &iDontCare3, &fDontCare1, (int *)VMA(5)); + } + + case CG_G2_GETGLANAME: + // return (int)G2API_GetGLAName(*((CGhoul2Info_v *)VMA(1)), args[2]); + { + char *point = ((char *)VMA(3)); + char *local; + local = G2API_GetGLAName(*((CGhoul2Info_v *)args[1]), args[2]); + if (local) + { + strcpy(point, local); + } + } + return 0; + + case CG_G2_COPYGHOUL2INSTANCE: + return (int)G2API_CopyGhoul2Instance(*((CGhoul2Info_v *)args[1]), *((CGhoul2Info_v *)args[2]), args[3]); + + case CG_G2_COPYSPECIFICGHOUL2MODEL: + G2API_CopySpecificG2Model(*((CGhoul2Info_v *)args[1]), args[2], *((CGhoul2Info_v *)args[3]), args[4]); + return 0; + + case CG_G2_DUPLICATEGHOUL2INSTANCE: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 0; +#endif + G2API_DuplicateGhoul2Instance(*((CGhoul2Info_v *)args[1]), (CGhoul2Info_v **)VMA(2)); + return 0; + + case CG_G2_HASGHOUL2MODELONINDEX: + return (int)G2API_HasGhoul2ModelOnIndex((CGhoul2Info_v **)VMA(1), args[2]); + //return (int)G2API_HasGhoul2ModelOnIndex((CGhoul2Info_v **)args[1], args[2]); + + case CG_G2_REMOVEGHOUL2MODEL: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 0; +#endif + return (int)G2API_RemoveGhoul2Model((CGhoul2Info_v **)VMA(1), args[2]); + //return (int)G2API_RemoveGhoul2Model((CGhoul2Info_v **)args[1], args[2]); + + case CG_G2_SKINLESSMODEL: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + return G2API_SkinlessModel(&g2[args[2]]); + } + + case CG_G2_GETNUMGOREMARKS: +#ifdef _G2_GORE + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + return G2API_GetNumGoreMarks(&g2[args[2]]); + } +#endif + return 0; + + case CG_G2_ADDSKINGORE: +#ifdef _G2_GORE + G2API_AddSkinGore(*((CGhoul2Info_v *)args[1]),*(SSkinGoreData *)VMA(2)); +#endif + return 0; + + case CG_G2_CLEARSKINGORE: +#ifdef _G2_GORE + G2API_ClearSkinGore ( *((CGhoul2Info_v *)args[1]) ); +#endif + return 0; + + case CG_G2_SIZE: + return G2API_Ghoul2Size ( *((CGhoul2Info_v *)args[1]) ); + break; + + case CG_G2_ADDBOLT: + return G2API_AddBolt(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3)); + + + case CG_G2_ATTACHENT: +// G2API_AttachEnt(int *boltInfo, CGhoul2Info *ghlInfoTo, int toBoltIndex, int entNum, int toModelNum) + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[2]); + return G2API_AttachEnt( (int*)VMA(1), &g2[0], args[3], args[4], args[5] ); + } + + case CG_G2_SETBOLTON: + G2API_SetBoltInfo(*((CGhoul2Info_v *)args[1]), args[2], args[3]); + return 0; + +#ifdef _SOF2 + case CG_G2_ADDSKINGORE: + G2API_AddSkinGore(*((CGhoul2Info_v *)args[1]),*(SSkinGoreData *)VMA(2)); + return 0; +#endif // _SOF2 +/* +Ghoul2 Insert End +*/ + case CG_G2_SETROOTSURFACE: + return G2API_SetRootSurface(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3)); + + case CG_G2_SETSURFACEONOFF: + return G2API_SetSurfaceOnOff(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), /*(const int)VMA(3)*/args[3]); + + case CG_G2_SETNEWORIGIN: + return G2API_SetNewOrigin(*((CGhoul2Info_v *)args[1]), /*(const int)VMA(2)*/args[2]); + + case CG_G2_DOESBONEEXIST: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + return G2API_DoesBoneExist(&g2[args[2]], (const char *)VMA(3)); + } + + case CG_G2_GETSURFACERENDERSTATUS: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + + return G2API_GetSurfaceRenderStatus(&g2[args[2]], (const char *)VMA(3)); + } + + case CG_G2_GETTIME: + return G2API_GetTime(0); + + case CG_G2_SETTIME: + G2API_SetTime(args[1], args[2]); + return 0; + + case CG_G2_ABSURDSMOOTHING: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + + G2API_AbsurdSmoothing(g2, (qboolean)args[2]); + } + return 0; + + + case CG_G2_SETRAGDOLL: + { + //Convert the info in the shared structure over to the class-based version. + sharedRagDollParams_t *rdParamst = (sharedRagDollParams_t *)VMA(2); + CRagDollParams rdParams; + + if (!rdParamst) + { + G2API_ResetRagDoll(*((CGhoul2Info_v *)args[1])); + return 0; + } + + VectorCopy(rdParamst->angles, rdParams.angles); + VectorCopy(rdParamst->position, rdParams.position); + VectorCopy(rdParamst->scale, rdParams.scale); + VectorCopy(rdParamst->pelvisAnglesOffset, rdParams.pelvisAnglesOffset); + VectorCopy(rdParamst->pelvisPositionOffset, rdParams.pelvisPositionOffset); + + rdParams.fImpactStrength = rdParamst->fImpactStrength; + rdParams.fShotStrength = rdParamst->fShotStrength; + rdParams.me = rdParamst->me; + + rdParams.startFrame = rdParamst->startFrame; + rdParams.endFrame = rdParamst->endFrame; + + rdParams.collisionType = rdParamst->collisionType; + rdParams.CallRagDollBegin = rdParamst->CallRagDollBegin; + + rdParams.RagPhase = (CRagDollParams::ERagPhase)rdParamst->RagPhase; + rdParams.effectorsToTurnOff = (CRagDollParams::ERagEffector)rdParamst->effectorsToTurnOff; + + G2API_SetRagDoll(*((CGhoul2Info_v *)args[1]), &rdParams); + } + return 0; + break; + case CG_G2_ANIMATEG2MODELS: + { + sharedRagDollUpdateParams_t *rduParamst = (sharedRagDollUpdateParams_t *)VMA(3); + CRagDollUpdateParams rduParams; + + if (!rduParamst) + { + return 0; + } + + VectorCopy(rduParamst->angles, rduParams.angles); + VectorCopy(rduParamst->position, rduParams.position); + VectorCopy(rduParamst->scale, rduParams.scale); + VectorCopy(rduParamst->velocity, rduParams.velocity); + + rduParams.me = rduParamst->me; + rduParams.settleFrame = rduParamst->settleFrame; + + G2API_AnimateG2Models(*((CGhoul2Info_v *)args[1]), args[2], &rduParams); + } + return 0; + break; + + //additional ragdoll options -rww + case CG_G2_RAGPCJCONSTRAINT: + return G2API_RagPCJConstraint(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4)); + case CG_G2_RAGPCJGRADIENTSPEED: + return G2API_RagPCJGradientSpeed(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), VMF(3)); + case CG_G2_RAGEFFECTORGOAL: + return G2API_RagEffectorGoal(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), (float *)VMA(3)); + case CG_G2_GETRAGBONEPOS: + return G2API_GetRagBonePos(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4), (float *)VMA(5), (float *)VMA(6)); + case CG_G2_RAGEFFECTORKICK: + return G2API_RagEffectorKick(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), (float *)VMA(3)); + case CG_G2_RAGFORCESOLVE: + return G2API_RagForceSolve(*((CGhoul2Info_v *)args[1]), (qboolean)args[2]); + + case CG_G2_SETBONEIKSTATE: + return G2API_SetBoneIKState(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3), args[4], (sharedSetBoneIKStateParams_t *)VMA(5)); + case CG_G2_IKMOVE: + return G2API_IKMove(*((CGhoul2Info_v *)args[1]), args[2], (sharedIKMoveParams_t *)VMA(3)); + + case CG_G2_REMOVEBONE: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + + return G2API_RemoveBone(&g2[args[3]], (const char *)VMA(2)); + } + + case CG_G2_ATTACHINSTANCETOENTNUM: + { + G2API_AttachInstanceToEntNum(*((CGhoul2Info_v *)args[1]), args[2], (qboolean)args[3]); + } + return 0; + case CG_G2_CLEARATTACHEDINSTANCE: + G2API_ClearAttachedInstance(args[1]); + return 0; + case CG_G2_CLEANENTATTACHMENTS: + G2API_CleanEntAttachments(); + return 0; + case CG_G2_OVERRIDESERVER: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + return G2API_OverrideServerWithClientData(&g2[0]); + } + + case CG_G2_GETSURFACENAME: + { //Since returning a pointer in such a way to a VM seems to cause MASSIVE FAILURE, we will shove data into the pointer the vm passes instead + char *point = ((char *)VMA(4)); + char *local; + int modelindex = args[3]; + + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + + local = G2API_GetSurfaceName(&g2[modelindex], args[2]); + if (local) + { + strcpy(point, local); + } + } + + return 0; + + case CG_SP_GETSTRINGTEXTSTRING: +// case CG_SP_GETSTRINGTEXT: + const char* text; + + assert(VMA(1)); + assert(VMA(2)); + +// if (args[0] == CG_SP_GETSTRINGTEXT) +// { +// text = SP_GetStringText( args[1] ); +// } +// else + { + text = SE_GetString( (const char *) VMA(1) ); + } + + if ( text[0] ) + { + Q_strncpyz( (char *) VMA(2), text, args[3] ); + return qtrue; + } + else + { + Com_sprintf( (char *) VMA(2), args[3], "??%s", VMA(1) ); + return qfalse; + } + break; + + case CG_SET_SHARED_BUFFER: + cl->mSharedMemory = ((char *)VMA(1)); + return 0; + +/* + case CG_CM_REGISTER_TERRAIN: + return CM_RegisterTerrain((const char *)VMA(1), false)->GetTerrainId(); +*/ + +/* + case CG_RMG_INIT: +#ifndef PRE_RELEASE_DEMO + if (!com_sv_running->integer) + { // don't do this if we are connected locally + if (!TheRandomMissionManager) + { + TheRandomMissionManager = new CRMManager; + } + TheRandomMissionManager->SetLandScape( cmg.landScape ); + if (TheRandomMissionManager->LoadMission(qfalse)) + { + if (!TheRandomMissionManager->SpawnMission(qfalse)) + { + Com_Error(ERR_DROP, "Error spawning mission for terrain"); + } + } + cmg.landScape->UpdatePatches(); + } + RM_CreateRandomModels(args[1], (const char *)VMA(2)); +// TheRandomMissionManager->CreateMap(); +#endif // PRE_RELEASE_DEMO + return 0; +*/ +/* + case CG_RE_INIT_RENDERER_TERRAIN: + RE_InitRendererTerrain((const char *)VMA(1)); + return 0; +*/ + + case CG_R_WEATHER_CONTENTS_OVERRIDE: + //contentOverride = args[1]; + return 0; + + case CG_R_WORLDEFFECTCOMMAND: + R_WorldEffectCommand((const char *)VMA(1)); + return 0; + + case CG_WE_ADDWEATHERZONE: + R_AddWeatherZone( (vec_t *)VMA(1), (vec_t *)VMA(2) ); + return 0; + + default: + assert(0); // bk010102 + Com_Error( ERR_DROP, "Bad cgame system trap: %i", args[0] ); + } + return 0; +} + + +/* +==================== +CL_InitCGame + +Should only be called by CL_StartHunkUsers +==================== +*/ +void CL_InitCGame( void ) { + const char *info; + const char *mapname; +// int t1, t2; + vmInterpret_t interpret; + +// t1 = Sys_Milliseconds(); + + // put away the console + Con_Close(); + + // find the current mapname + info = cl->gameState.stringData + cl->gameState.stringOffsets[ CS_SERVERINFO ]; + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cl->mapname, sizeof( cl->mapname ), "maps/%s.bsp", mapname ); + + // load the dll or bytecode + if ( cl_connectedToPureServer != 0 ) { +#if 0 + // if sv_pure is set we only allow qvms to be loaded + interpret = VMI_COMPILED; +#else //load the module type based on what the server is doing -rww + interpret = (vmInterpret_t)cl_connectedCGAME; +#endif + } + else { + interpret = (vmInterpret_t)(int)Cvar_VariableValue( "vm_cgame" ); + } + cgvm = VM_Create( "cgame", CL_CgameSystemCalls, interpret ); + if ( !cgvm ) { + Com_Error( ERR_DROP, "VM_Create on cgame failed" ); + } + +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// ClientManager::ActiveClient().state = CA_LOADING; +// else +#endif + cls.state = CA_LOADING; + + // init for this gamestate + // use the lastExecutedServerCommand instead of the serverCommandSequence + // otherwise server commands sent just before a gamestate are dropped + VM_Call( cgvm, CG_INIT, clc->serverMessageSequence, clc->lastExecutedServerCommand, clc->clientNum ); + + // we will send a usercmd this frame, which + // will cause the server to send us the first snapshot +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// ClientManager::ActiveClient().state = CA_PRIMED; +// else +#endif + cls.state = CA_PRIMED; + +// t2 = Sys_Milliseconds(); + +// Com_Printf( "CL_InitCGame: %5.2f seconds\n", (t2-t1)/1000.0 ); + + // have the renderer touch all its images, so they are present + // on the card even if the driver does deferred loading + re.EndRegistration(); + + // make sure everything is paged in +// if (!Sys_LowPhysicalMemory()) + { + Com_TouchMemory(); + } + + // clear anything that got printed + Con_ClearNotify (); +#ifdef _DONETPROFILE_ + ClReadProf().Reset(); +#endif +} + + +/* +==================== +CL_GameCommand + +See if the current console command is claimed by the cgame +==================== +*/ +qboolean CL_GameCommand( void ) { + if ( !cgvm ) { + return qfalse; + } + + return (qboolean)VM_Call( cgvm, CG_CONSOLE_COMMAND ); +} + + + +/* +===================== +CL_CGameRendering +===================== +*/ +void CL_CGameRendering( stereoFrame_t stereo ) { + //rww - RAGDOLL_BEGIN +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 0) { +#endif + if (!com_sv_running->integer) + { //set the server time to match the client time, if we don't have a server going. + G2API_SetTime(cl->serverTime, 0); + } + G2API_SetTime(cl->serverTime, 1); + //rww - RAGDOLL_END +#ifdef _XBOX + } +#endif + +#ifdef _XBOX // No demos on Xbox + VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl->serverTime, stereo, 0 ); +#else + VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl->serverTime, stereo, clc->demoplaying ); +#endif + VM_Debug( 0 ); +} + + +/* +================= +CL_AdjustTimeDelta + +Adjust the clients view of server time. + +We attempt to have cl->serverTime exactly equal the server's view +of time plus the timeNudge, but with variable latencies over +the internet it will often need to drift a bit to match conditions. + +Our ideal time would be to have the adjusted time approach, but not pass, +the very latest snapshot. + +Adjustments are only made when a new snapshot arrives with a rational +latency, which keeps the adjustment process framerate independent and +prevents massive overadjustment during times of significant packet loss +or bursted delayed packets. +================= +*/ + +#define RESET_TIME 500 + +void CL_AdjustTimeDelta( void ) { + int resetTime; + int newDelta; + int deltaDelta; + + cl->newSnapshots = qfalse; + + // the delta never drifts when replaying a demo +#ifndef _XBOX // No demos on Xbox + if ( clc->demoplaying ) { + return; + } +#endif + + // if the current time is WAY off, just correct to the current value + if ( com_sv_running->integer ) { + resetTime = 100; + } else { + resetTime = RESET_TIME; + } + + newDelta = cl->snap.serverTime - cls.realtime; + deltaDelta = abs( newDelta - cl->serverTimeDelta ); + + if ( deltaDelta > RESET_TIME ) { + cl->serverTimeDelta = newDelta; + cl->oldServerTime = cl->snap.serverTime; // FIXME: is this a problem for cgame? + cl->serverTime = cl->snap.serverTime; + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + } else if ( deltaDelta > 100 ) { + // fast adjust, cut the difference in half + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + cl->serverTimeDelta = ( cl->serverTimeDelta + newDelta ) >> 1; + } else { + // slow drift adjust, only move 1 or 2 msec + + // if any of the frames between this and the previous snapshot + // had to be extrapolated, nudge our sense of time back a little + // the granularity of +1 / -2 is too high for timescale modified frametimes + if ( com_timescale->value == 0 || com_timescale->value == 1 ) { + if ( cl->extrapolatedSnapshot ) { + cl->extrapolatedSnapshot = qfalse; + cl->serverTimeDelta -= 2; + } else { + // otherwise, move our sense of time forward to minimize total latency + cl->serverTimeDelta++; + } + } + } + + if ( cl_showTimeDelta->integer ) { + Com_Printf( "%i ", cl->serverTimeDelta ); + } +} + + +/* +================== +CL_FirstSnapshot +================== +*/ +extern void RE_RegisterMedia_LevelLoadEnd(void); +void CL_FirstSnapshot( void ) { + // ignore snapshots that don't have entities + if ( cl->snap.snapFlags & SNAPFLAG_NOT_ACTIVE ) { + return; + } + + RE_RegisterMedia_LevelLoadEnd(); + +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// ClientManager::ActiveClient().state = CA_ACTIVE; +// else +#endif + cls.state = CA_ACTIVE; + + // set the timedelta so we are exactly on this first frame + cl->serverTimeDelta = cl->snap.serverTime - cls.realtime; + cl->oldServerTime = cl->snap.serverTime; + +#ifndef _XBOX // No demos on Xbox + clc->timeDemoBaseTime = cl->snap.serverTime; +#endif + + // if this is the first frame of active play, + // execute the contents of activeAction now + // this is to allow scripting a timedemo to start right + // after loading + if ( cl_activeAction->string[0] ) { + Cbuf_AddText( cl_activeAction->string ); + Cvar_Set( "activeAction", "" ); + } + + Sys_BeginProfiling(); + +#ifdef _XBOX + // turn vsync back on - tearing is ugly + qglEnable(GL_VSYNC); + + // Start (or update) advertising on MM + if ( com_sv_running->integer ) + XBL_MM_Advertise(); +#endif +} + +/* +================== +CL_SetCGameTime +================== +*/ +void CL_SetCGameTime( void ) { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + CM_START_LOOP(); +#endif + +#ifdef _XBOX +/* + if(ClientManager::splitScreenMode == qtrue) + { + // getting a valid frame message ends the connection process + if ( ClientManager::ActiveClient().state != CA_ACTIVE ) { + if ( ClientManager::ActiveClient().state != CA_PRIMED ) { + return; + } + if ( cl->newSnapshots ) { + cl->newSnapshots = qfalse; + CL_FirstSnapshot(); + } + if ( ClientManager::ActiveClient().state != CA_ACTIVE ) { + return; + } + } + } + else + { +*/ +#endif + // getting a valid frame message ends the connection process + if ( cls.state != CA_ACTIVE ) { + if ( cls.state != CA_PRIMED ) { + return; + } + if ( cl->newSnapshots ) { + cl->newSnapshots = qfalse; + CL_FirstSnapshot(); + } + if ( cls.state != CA_ACTIVE ) { + return; + } + } +#ifdef _XBOX +// } +#endif + + // if we have gotten to this point, cl->snap is guaranteed to be valid + if ( !cl->snap.valid ) { + Com_Error( ERR_DROP, "CL_SetCGameTime: !cl->snap.valid" ); + } + + // allow pause in single player + if ( sv_paused->integer && cl_paused->integer && com_sv_running->integer ) { + // paused + return; + } + + if ( cl->snap.serverTime < cl->oldFrameServerTime ) { + Com_Error( ERR_DROP, "cl->snap.serverTime < cl->oldFrameServerTime" ); + } + cl->oldFrameServerTime = cl->snap.serverTime; + + + // get our current view of time + +#ifndef _XBOX // No demos on Xbox + if ( clc->demoplaying && cl_freezeDemo->integer ) { + // cl_freezeDemo is used to lock a demo in place for single frame advances + + } else +#endif + { + // cl_timeNudge is a user adjustable cvar that allows more + // or less latency to be added in the interest of better + // smoothness or better responsiveness. + int tn; + + tn = cl_timeNudge->integer; +#ifdef _DEBUG + if (tn<-900) { + tn = -900; + } else if (tn>900) { + tn = 900; + } +#else + if (tn<-30) { + tn = -30; + } else if (tn>30) { + tn = 30; + } +#endif + + cl->serverTime = cls.realtime + cl->serverTimeDelta - tn; + + // guarantee that time will never flow backwards, even if + // serverTimeDelta made an adjustment or cl_timeNudge was changed + if ( cl->serverTime < cl->oldServerTime ) { + cl->serverTime = cl->oldServerTime; + } + cl->oldServerTime = cl->serverTime; + + // note if we are almost past the latest frame (without timeNudge), + // so we will try and adjust back a bit when the next snapshot arrives + if ( cls.realtime + cl->serverTimeDelta >= cl->snap.serverTime - 5 ) { + cl->extrapolatedSnapshot = qtrue; + } + } + + // if we have gotten new snapshots, drift serverTimeDelta + // don't do this every frame, or a period of packet loss would + // make a huge adjustment + if ( cl->newSnapshots ) { + CL_AdjustTimeDelta(); + } +#ifdef _XBOX + CM_END_LOOP(); +#endif + +#ifndef _XBOX // No demos on Xbox + if ( !clc->demoplaying ) { + return; + } + + // if we are playing a demo back, we can just keep reading + // messages from the demo file until the cgame definately + // has valid snapshots to interpolate between + + // a timedemo will always use a deterministic set of time samples + // no matter what speed machine it is run on, + // while a normal demo may have different time samples + // each time it is played back + if ( cl_timedemo->integer ) { + if (!clc->timeDemoStart) { + clc->timeDemoStart = Sys_Milliseconds(); + } + clc->timeDemoFrames++; + cl->serverTime = clc->timeDemoBaseTime + clc->timeDemoFrames * 50; + } + + while ( cl->serverTime >= cl->snap.serverTime ) { + // feed another messag, which should change + // the contents of cl->snap + CL_ReadDemoMessage(); + if ( cls.state != CA_ACTIVE ) { + return; // end of demo + } + } +#endif // _XBOX +} + + + diff --git a/codemp/client/cl_cin.cpp b/codemp/client/cl_cin.cpp new file mode 100644 index 0000000..0757b84 --- /dev/null +++ b/codemp/client/cl_cin.cpp @@ -0,0 +1,1499 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/***************************************************************************** + * name: cl_cin.c + * + * desc: video and cinematic playback + * + * $Archive: /MissionPack/code/client/cl_cin.c $ + * $Author: mccloskey $ + * $Revision: 1.16 $ + * $Modtime: 6/12/01 10:36a $ + * $Date: 2003/09/17 01:10:20 $ + * + * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256 + * + *****************************************************************************/ + +#include "client.h" +#include "snd_local.h" + +#define MAXSIZE 8 +#define MINSIZE 4 + +#define DEFAULT_CIN_WIDTH 512 +#define DEFAULT_CIN_HEIGHT 512 + +#define ROQ_QUAD 0x1000 +#define ROQ_QUAD_INFO 0x1001 +#define ROQ_CODEBOOK 0x1002 +#define ROQ_QUAD_VQ 0x1011 +#define ROQ_QUAD_JPEG 0x1012 +#define ROQ_QUAD_HANG 0x1013 +#define ROQ_PACKET 0x1030 +#define ZA_SOUND_MONO 0x1020 +#define ZA_SOUND_STEREO 0x1021 + +#define MAX_VIDEO_HANDLES 16 + +extern glconfig_t glConfig; +extern int s_paintedtime; +extern int s_rawend; + + +static void RoQ_init( void ); + +/****************************************************************************** +* +* Class: trFMV +* +* Description: RoQ/RnR manipulation routines +* not entirely complete for first run +* +******************************************************************************/ + +static long ROQ_YY_tab[256]; +static long ROQ_UB_tab[256]; +static long ROQ_UG_tab[256]; +static long ROQ_VG_tab[256]; +static long ROQ_VR_tab[256]; +static unsigned short vq2[256*16*4]; +static unsigned short vq4[256*64*4]; +static unsigned short vq8[256*256*4]; + + +typedef struct { + byte linbuf[DEFAULT_CIN_WIDTH*DEFAULT_CIN_HEIGHT*4*2]; + byte file[65536]; + short sqrTable[256]; + + unsigned int mcomp[256]; + byte *qStatus[2][32768]; + + long oldXOff, oldYOff; + unsigned int oldysize, oldxsize; +} cinematics_t; + +typedef struct { + char fileName[MAX_OSPATH]; + int CIN_WIDTH, CIN_HEIGHT; + int xpos, ypos, width, height; + qboolean looping, holdAtEnd, dirty, alterGameState, silent, shader; + fileHandle_t iFile; + e_status status; + unsigned int startTime; + unsigned int lastTime; + long tfps; + long RoQPlayed; + long ROQSize; + unsigned int RoQFrameSize; + long onQuad; + long numQuads; + long samplesPerLine; + unsigned int roq_id; + long screenDelta; + + void ( *VQ0)(byte *status, void *qdata ); + void ( *VQ1)(byte *status, void *qdata ); + void ( *VQNormal)(byte *status, void *qdata ); + void ( *VQBuffer)(byte *status, void *qdata ); + + byte* gray; + unsigned int xsize, ysize, maxsize, minsize; + + qboolean inMemory; + long normalBuffer0; + long roq_flags; + long roqF0; + long roqF1; + long t[2]; + long roqFPS; + int playonwalls; + byte* buf; + long drawX, drawY; +} cin_cache; + +static cinematics_t cin; +static cin_cache cinTable[MAX_VIDEO_HANDLES]; +static int currentHandle = -1; +static int CL_handle = -1; + +extern int s_soundtime; // sample PAIRS +extern int s_paintedtime; // sample PAIRS + + +void CIN_CloseAllVideos(void) { + int i; + + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if (cinTable[i].fileName[0] != 0 ) { + CIN_StopCinematic(i); + } + } +} + + +static int CIN_HandleForVideo(void) { + int i; + + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if ( cinTable[i].fileName[0] == 0 ) { + return i; + } + } + Com_Error( ERR_DROP, "CIN_HandleForVideo: none free" ); + return -1; +} + + +//----------------------------------------------------------------------------- +// RllSetupTable +// +// Allocates and initializes the square table. +// +// Parameters: None +// +// Returns: Nothing +//----------------------------------------------------------------------------- +static void RllSetupTable() +{ + int z; + + for (z=0;z<128;z++) { + cin.sqrTable[z] = (short)(z*z); + cin.sqrTable[z+128] = (short)(-cin.sqrTable[z]); + } +} + + + +//----------------------------------------------------------------------------- +// RllDecodeMonoToMono +// +// Decode mono source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of shorts of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput ,unsigned short flag) +{ + unsigned int z; + int prev; + + if (signedOutput) + prev = flag - 0x8000; + else + prev = flag; + + for (z=0;z buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/4 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput,unsigned short flag) +{ + unsigned int z; + int prev; + + if (signedOutput) + prev = flag - 0x8000; + else + prev = flag; + + for (z = 0; z < size; z++) { + prev = (short)(prev + cin.sqrTable[from[z]]); + to[z*2+0] = to[z*2+1] = (short)(prev); + } + + return size; // * 2 * sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeStereoToStereo +// +// Decode stereo source data into a stereo buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/2 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) +{ + unsigned int z; + unsigned char *zz = from; + int prevL, prevR; + + if (signedOutput) { + prevL = (flag & 0xff00) - 0x8000; + prevR = ((flag & 0x00ff) << 8) - 0x8000; + } else { + prevL = flag & 0xff00; + prevR = (flag & 0x00ff) << 8; + } + + for (z=0;z>1); //*sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeStereoToMono +// +// Decode stereo source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) +{ + unsigned int z; + int prevL,prevR; + + if (signedOutput) { + prevL = (flag & 0xff00) - 0x8000; + prevR = ((flag & 0x00ff) << 8) -0x8000; + } else { + prevL = flag & 0xff00; + prevR = (flag & 0x00ff) << 8; + } + + for (z=0;z>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void move4_32( byte *src, byte *dst, int spl ) +{ + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += dspl; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blit8_32( byte *src, byte *dst, int spl ) +{ + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; + dsrc += 4; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +#define movs double +static void blit4_32( byte *src, byte *dst, int spl ) +{ + movs *dsrc, *ddst; + int dspl; + + dsrc = (movs *)src; + ddst = (movs *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; + dsrc += 2; ddst += dspl; + ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blit2_32( byte *src, byte *dst, int spl ) +{ + double *dsrc, *ddst; + int dspl; + + dsrc = (double *)src; + ddst = (double *)dst; + dspl = spl>>3; + + ddst[0] = dsrc[0]; + ddst[dspl] = dsrc[1]; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void blitVQQuad32fs( byte **status, unsigned char *data ) +{ +unsigned short newd, celdata, code; +unsigned int index, i; +int spl; + + newd = 0; + celdata = 0; + index = 0; + + spl = cinTable[currentHandle].samplesPerLine; + + do { + if (!newd) { + newd = 7; + celdata = data[0] + data[1]*256; + data += 2; + } else { + newd--; + } + + code = (unsigned short)(celdata&0xc000); + celdata <<= 2; + + switch (code) { + case 0x8000: // vq code + blit8_32( (byte *)&vq8[(*data)*128], status[index], spl ); + data++; + index += 5; + break; + case 0xc000: // drop + index++; // skip 8x8 + for(i=0;i<4;i++) { + if (!newd) { + newd = 7; + celdata = data[0] + data[1]*256; + data += 2; + } else { + newd--; + } + + code = (unsigned short)(celdata&0xc000); celdata <<= 2; + + switch (code) { // code in top two bits of code + case 0x8000: // 4x4 vq code + blit4_32( (byte *)&vq4[(*data)*32], status[index], spl ); + data++; + break; + case 0xc000: // 2x2 vq code + blit2_32( (byte *)&vq2[(*data)*8], status[index], spl ); + data++; + blit2_32( (byte *)&vq2[(*data)*8], status[index]+8, spl ); + data++; + blit2_32( (byte *)&vq2[(*data)*8], status[index]+spl*2, spl ); + data++; + blit2_32( (byte *)&vq2[(*data)*8], status[index]+spl*2+8, spl ); + data++; + break; + case 0x4000: // motion compensation + move4_32( status[index] + cin.mcomp[(*data)], status[index], spl ); + data++; + break; + } + index++; + } + break; + case 0x4000: // motion compensation + move8_32( status[index] + cin.mcomp[(*data)], status[index], spl ); + data++; + index += 5; + break; + case 0x0000: + index += 5; + break; + } + } while ( status[index] != NULL ); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void ROQ_GenYUVTables( void ) +{ + float t_ub,t_vr,t_ug,t_vg; + long i; + + t_ub = (1.77200f/2.0f) * (float)(1<<6) + 0.5f; + t_vr = (1.40200f/2.0f) * (float)(1<<6) + 0.5f; + t_ug = (0.34414f/2.0f) * (float)(1<<6) + 0.5f; + t_vg = (0.71414f/2.0f) * (float)(1<<6) + 0.5f; + for(i=0;i<256;i++) { + float x = (float)(2 * i - 255); + + ROQ_UB_tab[i] = (long)( ( t_ub * x) + (1<<5)); + ROQ_VR_tab[i] = (long)( ( t_vr * x) + (1<<5)); + ROQ_UG_tab[i] = (long)( (-t_ug * x) ); + ROQ_VG_tab[i] = (long)( (-t_vg * x) + (1<<5)); + ROQ_YY_tab[i] = (long)( (i << 6) | (i >> 2) ); + } +} + +#define VQ2TO4(a,b,c,d) { \ + *c++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *c++ = a[1]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *c++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *c++ = b[1]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + a += 2; b += 2; } + +#define VQ2TO2(a,b,c,d) { \ + *c++ = *a; \ + *d++ = *a; \ + *d++ = *a; \ + *c++ = *b; \ + *d++ = *b; \ + *d++ = *b; \ + *d++ = *a; \ + *d++ = *a; \ + *d++ = *b; \ + *d++ = *b; \ + a++; b++; } + + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +#if defined(MACOS_X) + +static inline unsigned int yuv_to_rgb24( long y, long u, long v ) +{ + long r,g,b,YY; + + YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 6; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; + b = (YY + ROQ_UB_tab[u]) >> 6; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; + + return ((r<<24)|(g<<16)|(b<<8))|(255); //+(255<<24)); +} + +#else +static unsigned int yuv_to_rgb24( long y, long u, long v ) +{ + long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 6; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; + b = (YY + ROQ_UB_tab[u]) >> 6; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; + + return LittleLong ((r)|(g<<8)|(b<<16)|(255<<24)); +} +#endif + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void decodeCodeBook( byte *input, unsigned short roq_flags ) +{ + long i, j, two, four; + unsigned short *bptr; + long y0,y1,y2,y3,cr,cb; + unsigned int *iaptr, *ibptr, *icptr, *idptr; + + if (!roq_flags) { + two = four = 256; + } else { + two = roq_flags>>8; + if (!two) two = 256; + four = roq_flags&0xff; + } + + four *= 2; + + bptr = (unsigned short *)vq2; + + // + // normal height + // + ibptr = (unsigned int *)bptr; + for(i=0;i cinTable[currentHandle].CIN_WIDTH) bigx = cinTable[currentHandle].CIN_WIDTH; + if (bigy > cinTable[currentHandle].CIN_HEIGHT) bigy = cinTable[currentHandle].CIN_HEIGHT; + + if ( (startX >= lowx) && (startX+quadSize) <= (bigx) && (startY+quadSize) <= (bigy) && (startY >= lowy) && quadSize <= MAXSIZE) { + useY = startY; + scroff = cin.linbuf + (useY+((cinTable[currentHandle].CIN_HEIGHT-bigy)>>1)+yOff)*(cinTable[currentHandle].samplesPerLine) + (((startX+xOff))*4); + + cin.qStatus[0][cinTable[currentHandle].onQuad ] = scroff; + cin.qStatus[1][cinTable[currentHandle].onQuad++] = scroff+offset; + } + + if ( quadSize != MINSIZE ) { + quadSize >>= 1; + recurseQuad( startX, startY , quadSize, xOff, yOff ); + recurseQuad( startX+quadSize, startY , quadSize, xOff, yOff ); + recurseQuad( startX, startY+quadSize , quadSize, xOff, yOff ); + recurseQuad( startX+quadSize, startY+quadSize , quadSize, xOff, yOff ); + } +} + + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void setupQuad( long xOff, long yOff ) +{ + long numQuadCels, i,x,y; + byte *temp; + + if (xOff == cin.oldXOff && yOff == cin.oldYOff && cinTable[currentHandle].ysize == cin.oldysize && cinTable[currentHandle].xsize == cin.oldxsize) { + return; + } + + cin.oldXOff = xOff; + cin.oldYOff = yOff; + cin.oldysize = cinTable[currentHandle].ysize; + cin.oldxsize = cinTable[currentHandle].xsize; + + numQuadCels = (cinTable[currentHandle].CIN_WIDTH*cinTable[currentHandle].CIN_HEIGHT) / (16); + numQuadCels += numQuadCels/4 + numQuadCels/16; + numQuadCels += 64; // for overflow + + numQuadCels = (cinTable[currentHandle].xsize*cinTable[currentHandle].ysize) / (16); + numQuadCels += numQuadCels/4; + numQuadCels += 64; // for overflow + + cinTable[currentHandle].onQuad = 0; + + for(y=0;y<(long)cinTable[currentHandle].ysize;y+=16) + for(x=0;x<(long)cinTable[currentHandle].xsize;x+=16) + recurseQuad( x, y, 16, xOff, yOff ); + + temp = NULL; + + for(i=(numQuadCels-64);i256) { + cinTable[currentHandle].drawX = 256; + } + if (cinTable[currentHandle].drawY>256) { + cinTable[currentHandle].drawY = 256; + } + if (cinTable[currentHandle].CIN_WIDTH != 256 || cinTable[currentHandle].CIN_HEIGHT != 256) { + Com_Printf("HACK: approxmimating cinematic for Rage Pro or Voodoo\n"); + } + } +#if defined(MACOS_X) + cinTable[currentHandle].drawX = 256; + cinTable[currentHandle].drawX = 256; +#endif +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQPrepMcomp( long xoff, long yoff ) +{ + long i, j, x, y, temp, temp2; + + i=cinTable[currentHandle].samplesPerLine; + j=4; + if ( cinTable[currentHandle].xsize == (cinTable[currentHandle].ysize*4) ) + { + j = j+j; + i = i+i; + } + + for(y=0;y<16;y++) { + temp2 = (y+yoff-8)*i; + for(x=0;x<16;x++) { + temp = (x+xoff-8)*j; + cin.mcomp[(x*16)+y] = cinTable[currentHandle].normalBuffer0-(temp2+temp); + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void initRoQ() +{ + if (currentHandle < 0) return; + + cinTable[currentHandle].VQNormal = (void (*)(byte *, void *))blitVQQuad32fs; + cinTable[currentHandle].VQBuffer = (void (*)(byte *, void *))blitVQQuad32fs; + ROQ_GenYUVTables(); + RllSetupTable(); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +/* +static byte* RoQFetchInterlaced( byte *source ) { + int x, *src, *dst; + + if (currentHandle < 0) return NULL; + + src = (int *)source; + dst = (int *)cinTable[currentHandle].buf2; + + for(x=0;x<256*256;x++) { + *dst = *src; + dst++; src += 2; + } + return cinTable[currentHandle].buf2; +} +*/ +static void RoQReset() { + + if (currentHandle < 0) return; + + if (cinTable[currentHandle].iFile) { + Sys_EndStreamedFile(cinTable[currentHandle].iFile); + FS_Seek(cinTable[currentHandle].iFile, 0, FS_SEEK_SET); + FS_Read (cin.file, 16, cinTable[currentHandle].iFile); + RoQ_init(); + // let the background thread start reading ahead + Sys_BeginStreamedFile( cinTable[currentHandle].iFile, 0x10000 ); + cinTable[currentHandle].status = FMV_LOOPED; + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQInterrupt(void) +{ + byte *framedata; + short sbuf[32768]; + int ssize; + + if (currentHandle < 0) return; + + Sys_StreamedRead( cin.file, cinTable[currentHandle].RoQFrameSize+8, 1, cinTable[currentHandle].iFile ); + if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { + if (cinTable[currentHandle].holdAtEnd==qfalse) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + cinTable[currentHandle].status = FMV_EOF; + } + } else { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata = cin.file; +// +// new frame is ready +// +redump: + switch(cinTable[currentHandle].roq_id) + { + case ROQ_QUAD_VQ: + if ((cinTable[currentHandle].numQuads&1)) { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[1]; + RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); + cinTable[currentHandle].VQ1( (byte *)cin.qStatus[1], framedata); + cinTable[currentHandle].buf = cin.linbuf + cinTable[currentHandle].screenDelta; + } else { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[0]; + RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); + cinTable[currentHandle].VQ0( (byte *)cin.qStatus[0], framedata ); + cinTable[currentHandle].buf = cin.linbuf; + } + if (cinTable[currentHandle].numQuads == 0) { // first frame + Com_Memcpy(cin.linbuf+cinTable[currentHandle].screenDelta, cin.linbuf, cinTable[currentHandle].samplesPerLine*cinTable[currentHandle].ysize); + } + cinTable[currentHandle].numQuads++; + cinTable[currentHandle].dirty = qtrue; + break; + case ROQ_CODEBOOK: + decodeCodeBook( framedata, (unsigned short)cinTable[currentHandle].roq_flags ); + break; + case ZA_SOUND_MONO: + if (!cinTable[currentHandle].silent) { + ssize = RllDecodeMonoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); + S_RawSamples( ssize, 22050, 2, 1, (byte *)sbuf, s_volume->value, 1 ); + } + break; + case ZA_SOUND_STEREO: + if (!cinTable[currentHandle].silent) { + if (cinTable[currentHandle].numQuads == -1) { + S_Update(); + s_rawend = s_soundtime; + } + ssize = RllDecodeStereoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); + S_RawSamples( ssize, 22050, 2, 2, (byte *)sbuf, s_volume->value, 1 ); + } + break; + case ROQ_QUAD_INFO: + if (cinTable[currentHandle].numQuads == -1) { + readQuadInfo( framedata ); + setupQuad( 0, 0 ); + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = Sys_Milliseconds()*com_timescale->value; + } + if (cinTable[currentHandle].numQuads != 1) cinTable[currentHandle].numQuads = 0; + break; + case ROQ_PACKET: + cinTable[currentHandle].inMemory = (qboolean)cinTable[currentHandle].roq_flags; + cinTable[currentHandle].RoQFrameSize = 0; // for header + break; + case ROQ_QUAD_HANG: + cinTable[currentHandle].RoQFrameSize = 0; + break; + case ROQ_QUAD_JPEG: + break; + default: + cinTable[currentHandle].status = FMV_EOF; + break; + } +// +// read in next frame data +// + if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { + if (cinTable[currentHandle].holdAtEnd==qfalse) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + cinTable[currentHandle].status = FMV_EOF; + } + } else { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata += cinTable[currentHandle].RoQFrameSize; + cinTable[currentHandle].roq_id = framedata[0] + framedata[1]*256; + cinTable[currentHandle].RoQFrameSize = framedata[2] + framedata[3]*256 + framedata[4]*65536; + cinTable[currentHandle].roq_flags = framedata[6] + framedata[7]*256; + cinTable[currentHandle].roqF0 = (char)framedata[7]; + cinTable[currentHandle].roqF1 = (char)framedata[6]; + + if (cinTable[currentHandle].RoQFrameSize>65536||cinTable[currentHandle].roq_id==0x1084) { + Com_DPrintf("roq_size>65536||roq_id==0x1084\n"); + cinTable[currentHandle].status = FMV_EOF; + if (cinTable[currentHandle].looping) { + RoQReset(); + } + return; + } + if (cinTable[currentHandle].inMemory && (cinTable[currentHandle].status != FMV_EOF)) + { + cinTable[currentHandle].inMemory = (qboolean)(((int)cinTable[currentHandle].inMemory)-1); + framedata += 8; + goto redump; + } +// +// one more frame hits the dust +// +// assert(cinTable[currentHandle].RoQFrameSize <= 65536); +// r = Sys_StreamedRead( cin.file, cinTable[currentHandle].RoQFrameSize+8, 1, cinTable[currentHandle].iFile ); + cinTable[currentHandle].RoQPlayed += cinTable[currentHandle].RoQFrameSize+8; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQ_init( void ) +{ + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = Sys_Milliseconds()*com_timescale->value; + + cinTable[currentHandle].RoQPlayed = 24; + +/* get frame rate */ + cinTable[currentHandle].roqFPS = cin.file[ 6] + cin.file[ 7]*256; + + if (!cinTable[currentHandle].roqFPS) cinTable[currentHandle].roqFPS = 30; + + cinTable[currentHandle].numQuads = -1; + + cinTable[currentHandle].roq_id = cin.file[ 8] + cin.file[ 9]*256; + cinTable[currentHandle].RoQFrameSize = cin.file[10] + cin.file[11]*256 + cin.file[12]*65536; + cinTable[currentHandle].roq_flags = cin.file[14] + cin.file[15]*256; + + if (cinTable[currentHandle].RoQFrameSize > 65536 || !cinTable[currentHandle].RoQFrameSize) { + return; + } + +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQShutdown( void ) { + const char *s; + + if (!cinTable[currentHandle].buf) { + return; + } + + if ( cinTable[currentHandle].status == FMV_IDLE ) { + return; + } + Com_DPrintf("finished cinematic\n"); + cinTable[currentHandle].status = FMV_IDLE; + + if (cinTable[currentHandle].iFile) { + Sys_EndStreamedFile( cinTable[currentHandle].iFile ); + FS_FCloseFile( cinTable[currentHandle].iFile ); + cinTable[currentHandle].iFile = 0; + } + + if (cinTable[currentHandle].alterGameState) { + cls.state = CA_DISCONNECTED; + // we can't just do a vstr nextmap, because + // if we are aborting the intro cinematic with + // a devmap command, nextmap would be valid by + // the time it was referenced + s = Cvar_VariableString( "nextmap" ); + if ( s[0] ) { + Cbuf_ExecuteText( EXEC_APPEND, va("%s\n", s) ); + Cvar_Set( "nextmap", "" ); + } + CL_handle = -1; + } + cinTable[currentHandle].fileName[0] = 0; + currentHandle = -1; +} + +/* +================== +SCR_StopCinematic +================== +*/ +e_status CIN_StopCinematic(int handle) { + + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; + currentHandle = handle; + + Com_DPrintf("trFMV::stop(), closing %s\n", cinTable[currentHandle].fileName); + + if (!cinTable[currentHandle].buf) { + return FMV_EOF; + } + + if (cinTable[currentHandle].alterGameState) { + if ( cls.state != CA_CINEMATIC ) { + return cinTable[currentHandle].status; + } + } + cinTable[currentHandle].status = FMV_EOF; + RoQShutdown(); + + return FMV_EOF; +} + +/* +================== +SCR_RunCinematic + +Fetch and decompress the pending frame +================== +*/ + + +e_status CIN_RunCinematic (int handle) +{ + // bk001204 - init + int start = 0; + int thisTime = 0; + + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; + + if (currentHandle != handle) { + currentHandle = handle; + cinTable[currentHandle].status = FMV_EOF; + RoQReset(); + } + + if (cinTable[handle].playonwalls < -1) + { + return cinTable[handle].status; + } + + currentHandle = handle; + + if (cinTable[currentHandle].alterGameState) { + if ( cls.state != CA_CINEMATIC ) { + return cinTable[currentHandle].status; + } + } + + if (cinTable[currentHandle].status == FMV_IDLE) { + return cinTable[currentHandle].status; + } + + thisTime = Sys_Milliseconds()*com_timescale->value; + if (cinTable[currentHandle].shader && (abs(thisTime - cinTable[currentHandle].lastTime))>100) { + cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime; + } + cinTable[currentHandle].tfps = ((((Sys_Milliseconds()*com_timescale->value) - cinTable[currentHandle].startTime)*cinTable[currentHandle].roqFPS)/1000); + + start = cinTable[currentHandle].startTime; + while( (cinTable[currentHandle].tfps != cinTable[currentHandle].numQuads) + && (cinTable[currentHandle].status == FMV_PLAY) ) + { + RoQInterrupt(); + if (start != cinTable[currentHandle].startTime) { + cinTable[currentHandle].tfps = ((((Sys_Milliseconds()*com_timescale->value) + - cinTable[currentHandle].startTime)*cinTable[currentHandle].roqFPS)/1000); + start = cinTable[currentHandle].startTime; + } + } + + cinTable[currentHandle].lastTime = thisTime; + + if (cinTable[currentHandle].status == FMV_LOOPED) { + cinTable[currentHandle].status = FMV_PLAY; + } + + if (cinTable[currentHandle].status == FMV_EOF) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + RoQShutdown(); + } + } + + return cinTable[currentHandle].status; +} + +/* +================== +CL_PlayCinematic + +================== +*/ +int CIN_PlayCinematic( const char *arg, int x, int y, int w, int h, int systemBits ) { + unsigned short RoQID; + char name[MAX_OSPATH]; + int i; + + if (strstr(arg, "/") == NULL && strstr(arg, "\\") == NULL) { + Com_sprintf (name, sizeof(name), "video/%s", arg); + } else { + Com_sprintf (name, sizeof(name), "%s", arg); + } + COM_DefaultExtension(name,sizeof(name),".roq"); + + if (!(systemBits & CIN_system)) { + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if (!strcmp(cinTable[i].fileName, name) ) { + return i; + } + } + } + + Com_DPrintf("SCR_PlayCinematic( %s )\n", arg); + + Com_Memset(&cin, 0, sizeof(cinematics_t) ); + currentHandle = CIN_HandleForVideo(); + + strcpy(cinTable[currentHandle].fileName, name); + + cinTable[currentHandle].ROQSize = 0; + cinTable[currentHandle].ROQSize = FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); + + if (cinTable[currentHandle].ROQSize<=0) { + Com_DPrintf("cinematic failed to open %s\n", arg); + cinTable[currentHandle].fileName[0] = 0; + return -1; + } + + CIN_SetExtents(currentHandle, x, y, w, h); + CIN_SetLooping(currentHandle, (qboolean)((systemBits & CIN_loop)!=0)); + + cinTable[currentHandle].CIN_HEIGHT = DEFAULT_CIN_HEIGHT; + cinTable[currentHandle].CIN_WIDTH = DEFAULT_CIN_WIDTH; + cinTable[currentHandle].holdAtEnd = (qboolean)((systemBits & CIN_hold) != 0); + cinTable[currentHandle].alterGameState = (qboolean)((systemBits & CIN_system) != 0); + cinTable[currentHandle].playonwalls = 1; + cinTable[currentHandle].silent = (qboolean)((systemBits & CIN_silent) != 0); + cinTable[currentHandle].shader = (qboolean)((systemBits & CIN_shader) != 0); + + if (cinTable[currentHandle].alterGameState) { + // close the menu + if ( uivm ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + } else { + cinTable[currentHandle].playonwalls = cl_inGameVideo->integer; + } + + initRoQ(); + + FS_Read (cin.file, 16, cinTable[currentHandle].iFile); + + RoQID = (unsigned short)(cin.file[0]) + (unsigned short)(cin.file[1])*256; + if (RoQID == 0x1084) + { + RoQ_init(); +// FS_Read (cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile); + // let the background thread start reading ahead + Sys_BeginStreamedFile( cinTable[currentHandle].iFile, 0x10000 ); + + cinTable[currentHandle].status = FMV_PLAY; + Com_DPrintf("trFMV::play(), playing %s\n", arg); + + if (cinTable[currentHandle].alterGameState) { + cls.state = CA_CINEMATIC; + } + + Con_Close(); + + s_rawend = s_soundtime; + + return currentHandle; + } + Com_DPrintf("trFMV::play(), invalid RoQ ID\n"); + + RoQShutdown(); + return -1; +} + +void CIN_SetExtents (int handle, int x, int y, int w, int h) { + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + cinTable[handle].xpos = x; + cinTable[handle].ypos = y; + cinTable[handle].width = w; + cinTable[handle].height = h; + cinTable[handle].dirty = qtrue; +} + +void CIN_SetLooping(int handle, qboolean loop) { + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + cinTable[handle].looping = loop; +} + +/* +================== +SCR_DrawCinematic + +================== +*/ +void CIN_DrawCinematic (int handle) { + float x, y, w, h; + + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + + if (!cinTable[handle].buf) { + return; + } + + x = cinTable[handle].xpos; + y = cinTable[handle].ypos; + w = cinTable[handle].width; + h = cinTable[handle].height; + + if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) { + int ix, iy, *buf2, *buf3, xm, ym, ll; + + xm = cinTable[handle].CIN_WIDTH/256; + ym = cinTable[handle].CIN_HEIGHT/256; + ll = 8; + if (cinTable[handle].CIN_WIDTH==512) { + ll = 9; + } + + buf3 = (int*)cinTable[handle].buf; + buf2 = (int *)Hunk_AllocateTempMemory( 256*256*4 ); + if (xm==2 && ym==2) { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for (iy = 0; iy<256; iy++) { + iiy = iy<<12; + for (ix = 0; ix<2048; ix+=8) { + for(ic = ix;ic<(ix+4);ic++) { + *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic]+bc3[iiy+2048+ic]+bc3[iiy+2048+4+ic])>>2; + bc2++; + } + } + } + } else if (xm==2 && ym==1) { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for (iy = 0; iy<256; iy++) { + iiy = iy<<11; + for (ix = 0; ix<2048; ix+=8) { + for(ic = ix;ic<(ix+4);ic++) { + *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic])>>1; + bc2++; + } + } + } + } else { + for (iy = 0; iy<256; iy++) { + for (ix = 0; ix<256; ix++) { + buf2[(iy<<8)+ix] = buf3[((iy*ym)<widescreen) + CL_handle = CIN_PlayCinematic( arg, 0, 0, 720, SCREEN_HEIGHT, bits ); + else +#endif + CL_handle = CIN_PlayCinematic( arg, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, bits ); + if (CL_handle >= 0) { + do { + SCR_RunCinematic(); + } while (cinTable[currentHandle].buf == NULL && cinTable[currentHandle].status == FMV_PLAY); // wait for first frame (load codebook and sound) + } + else + { + Com_Printf(va(S_COLOR_RED "PlayCinematic(): Failed to open \"%s\"\n",arg)); + } +} + + +void SCR_DrawCinematic (void) { + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_DrawCinematic(CL_handle); + } +} + +void SCR_RunCinematic (void) +{ + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_RunCinematic(CL_handle); + } +} + +void SCR_StopCinematic(void) { + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_StopCinematic(CL_handle); + S_StopAllSounds (); + CL_handle = -1; + } +} + +void CIN_UploadCinematic(int handle) { + if (handle >= 0 && handle < MAX_VIDEO_HANDLES) { + if (!cinTable[handle].buf) { + return; + } + if (cinTable[handle].playonwalls <= 0 && cinTable[handle].dirty) { + if (cinTable[handle].playonwalls == 0) { + cinTable[handle].playonwalls = -1; + } else { + if (cinTable[handle].playonwalls == -1) { + cinTable[handle].playonwalls = -2; + } else { + cinTable[handle].dirty = qfalse; + } + } + } + re.UploadCinematic( cinTable[handle].drawX, cinTable[handle].drawY, cinTable[handle].buf, handle, cinTable[handle].dirty); + if (cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1) { + cinTable[handle].playonwalls--; + } + } +} + diff --git a/codemp/client/cl_cin_console.cpp b/codemp/client/cl_cin_console.cpp new file mode 100644 index 0000000..f7f9132 --- /dev/null +++ b/codemp/client/cl_cin_console.cpp @@ -0,0 +1,79 @@ + +/***************************************************************************** + * name: cl_cin_stubs.cpp + * + * desc: video and cinematic playback stubs to avoid link errors + * + * + * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256 + * + *****************************************************************************/ + +#include "client.h" +//#include "../win32/win_local.h" +//#include "../win32/win_input.h" + +//#ifdef _XBOX +//#include "../cgame/cg_local.h" +//#include "cl_data.h" +//#endif + +void CIN_CloseAllVideos(void) +{ + return; +} + +e_status CIN_StopCinematic(int handle) +{ + return FMV_EOF; +} + +e_status CIN_RunCinematic(int handle) +{ + return FMV_EOF; +} + +int CIN_PlayCinematic(const char *arg0, int xpos, int ypos, int width, int height, int bits) +{ + return 0; +} + +void CIN_SetExtents(int handle, int x, int y, int w, int h) +{ + return; +} + +void CIN_DrawCinematic(int handle) +{ + return; +} + +void SCR_DrawCinematic(void) +{ + return; +} + +void SCR_RunCinematic(void) +{ + return; +} + +void SCR_StopCinematic(void) +{ + return; +} + +void CIN_UploadCinematic(int handle) +{ + return; +} + +void CIN_Init(void) +{ + return; +} + +void CL_PlayCinematic_f(void) +{ + return; +} diff --git a/codemp/client/cl_console.cpp b/codemp/client/cl_console.cpp new file mode 100644 index 0000000..c26ae1b --- /dev/null +++ b/codemp/client/cl_console.cpp @@ -0,0 +1,467 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// console.c + +#include "client.h" +#include "../qcommon/stringed_ingame.h" +#include "../qcommon/game_version.h" +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#include "../renderer/tr_font.h" + +int g_console_field_width = 78; + +console_t con; + +cvar_t *con_conspeed; +cvar_t *con_notifytime; + +#define DEFAULT_CONSOLE_WIDTH 78 + +/* +================ +Con_Clear_f +================ +*/ +void Con_Clear_f (void) { + int i; + + for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { + con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; + } + + Con_Bottom(); // go to end +} + + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify( void ) { + int i; + + for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) { + con.times[i] = 0; + } +} + + + +/* +================ +Con_CheckResize + +If the line width has changed, reformat the buffer. +================ +*/ +void Con_CheckResize (void) +{ + int i, j, width, oldwidth, oldtotallines, numlines, numchars; + MAC_STATIC short tbuf[CON_TEXTSIZE]; + +// width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; + width = (cls.glconfig.vidWidth / SMALLCHAR_WIDTH) - 2; + + if (width == con.linewidth) + return; + + + if (width < 1) // video hasn't been initialized yet + { + con.xadjust = 1; + con.yadjust = 1; + width = DEFAULT_CONSOLE_WIDTH; + con.linewidth = width; + con.totallines = CON_TEXTSIZE / con.linewidth; + for(i=0; i= 0 && !silent ) + { + con.times[con.current % NUM_CON_TIMES] = cls.realtime; + } + else + { + con.times[con.current % NUM_CON_TIMES] = 0; + } + + con.x = 0; + if (con.display == con.current) + con.display++; + con.current++; + for(i=0; iinteger ) { + return; + } + + if (!con.initialized) { + con.color[0] = + con.color[1] = + con.color[2] = + con.color[3] = 1.0f; + con.linewidth = -1; + Con_CheckResize (); + con.initialized = qtrue; + } + + color = ColorIndex(COLOR_WHITE); + +#ifdef _XBOX + // client to use + if(ClientManager::splitScreenMode == qtrue) { + y = con.current % con.totallines; + con.text[y*con.linewidth+con.x] = (ClientManager::ActiveClientNum() << 8) | '@'; + con.x++; + } +#endif + + while ( (c = (unsigned char) *txt) != 0 ) { + if ( Q_IsColorString( (unsigned char*) txt ) ) { + color = ColorIndex( *(txt+1) ); + txt += 2; + continue; + } + + // count word length + for (l=0 ; l< con.linewidth ; l++) { + if ( txt[l] <= ' ') { + break; + } + + } + + // word wrap + if (l != con.linewidth && (con.x + l >= con.linewidth) ) { + Con_Linefeed(silent); + + } + + txt++; + + switch (c) + { + case '\n': + Con_Linefeed (silent); + break; + case '\r': + con.x = 0; + break; + default: // display character and advance + y = con.current % con.totallines; + con.text[y*con.linewidth+con.x] = (short) ((color << 8) | c); + con.x++; + if (con.x >= con.linewidth) { + + Con_Linefeed(silent); + con.x = 0; + } + break; + } + } + + + // mark time for transparent overlay + + if (con.current >= 0 && !silent ) + { + con.times[con.current % NUM_CON_TIMES] = cls.realtime; + } + else + { + con.times[con.current % NUM_CON_TIMES] = 0; + } +} + + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + + +/* +================ +Con_DrawInput + +Draw the editline after a ] prompt +================ +*/ +void Con_DrawInput (void) { +/* + int y; + + if ( cls.state != CA_DISCONNECTED && !(cls.keyCatchers & KEYCATCH_CONSOLE ) ) { + return; + } + + y = con.vislines - ( SMALLCHAR_HEIGHT * (re.Language_IsAsian() ? 1.5 : 2) ); + + re.SetColor( con.color ); + + SCR_DrawSmallChar( (int)(con.xadjust + 1 * SMALLCHAR_WIDTH), y, ']' ); + + Field_Draw( &kg.g_consoleField, (int)(con.xadjust + 2 * SMALLCHAR_WIDTH), y, + SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, qtrue ); +*/ +} + + + + +/* +================ +Con_DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void Con_DrawNotify (void) +{ + int x, y; + short *text; + int i; + int time; + int currentColor; + int xStart; + char lineBuf[256]; + int lineLen; + + currentColor = 7; + +#ifdef _XBOX + int curClient = ClientManager::ActiveClientNum(); +#endif + + // Implicitly checks for split screen mode: + if( ClientManager::ActiveClientNum() == 1 ) + y = 250; + else + y = 40; + + for ( i = con.current-NUM_CON_TIMES+1; i <= con.current; i++ ) + { + if (/*cl->snap.ps.pm_type != PM_INTERMISSION &&*/ cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME) ) + continue; + if(cg->scoreBoardShowing) + continue; + if (i < 0) + continue; + time = con.times[i % NUM_CON_TIMES]; + if (time == 0) + continue; + time = cls.realtime - time; + if (time > con_notifytime->value*1000) + continue; + text = con.text + (i % con.totallines)*con.linewidth; + + xStart = cl_conXOffset->integer + con.xadjust + 58; // Some arbitrary nonsense + lineBuf[0] = 0; + lineLen = 0; + + // Scan through characters: + for (x = 0 ; x < con.linewidth ; x++) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if ( ( ((char)text[x]) == '@') ) + { + curClient = (text[x] >> 8)&7; + if (curClient < 0 || curClient >= ClientManager::NumClients()) // Safety check + curClient = 0; + //st--; + continue; + } + } +#endif + // New color: + if ( ( (text[x]>>8)&7 ) != currentColor ) + { + // Draw whatever we've copied thus far: + lineBuf[lineLen] = 0; + RE_Font_DrawString( xStart, y, lineBuf, g_color_table[currentColor], 1, -1, 1.0f ); + xStart += RE_Font_StrLenPixels( lineBuf, 1, 1.0f ); + + // Switch colors, and reset lineBuf: + currentColor = (text[x]>>8)&7; + lineBuf[0] = 0; + lineLen = 0; + } + + // All other characters just get copied into lineBuf: + lineBuf[lineLen++] = (text[x] & 0xFF); + } + + if (curClient != ClientManager::ActiveClientNum()) + continue; + + if(ClientManager::splitScreenMode == qtrue && + cg->showScores == qtrue) + continue; + + // Draw whatever we left in lineBuf from the last iteration: + lineBuf[lineLen] = 0; + RE_Font_DrawString( xStart, y, lineBuf, g_color_table[currentColor], 1, -1, 1.0f ); + + y += RE_Font_HeightPixels( 1, 1.0f ); + } +} + +/* +================== +Con_DrawConsole +================== +*/ +void Con_DrawConsole( void ) { + // check for console width changes from a vid mode change + Con_CheckResize (); + + // Xbox - always just run in notify mode + Con_DrawNotify(); +} + +//================================================================ + +void Con_PageUp( void ) { + con.display -= 2; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_PageDown( void ) { + con.display += 2; + if (con.display > con.current) { + con.display = con.current; + } +} + +void Con_Top( void ) { + con.display = con.totallines; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_Bottom( void ) { + con.display = con.current; +} + + +void Con_Close( void ) { + if ( !com_cl_running->integer ) { + return; + } + Field_Clear( &kg.g_consoleField ); + Con_ClearNotify (); + cls.keyCatchers &= ~KEYCATCH_CONSOLE; + con.finalFrac = 0; // none visible + con.displayFrac = 0; +} diff --git a/codemp/client/cl_data.cpp b/codemp/client/cl_data.cpp new file mode 100644 index 0000000..1aeaf28 --- /dev/null +++ b/codemp/client/cl_data.cpp @@ -0,0 +1,317 @@ +#include "../cgame/cg_local.h" +#include "cl_data.h" + + +//tvector(ClientData*) ClientManager::clientData; +ClientData* ClientManager::clientData[2] = { NULL }; +int ClientManager::curClient = 0; +ClientData* ClientManager::activeClient = 0; +int ClientManager::mainClient = 0; +qboolean ClientManager::splitScreenMode = qfalse; +int ClientManager::numClients = 0; +bool ClientManager::bClOutside = false; + +ClientData::ClientData() +: in_mlooking(qfalse), cmd_wait(0), loopbacks(0), eventHead(0), eventTail(0), serverCommandSequence(0), +// cg_thirdPersonSpecialCam(0), cg_thirdPerson(0), cg_thirdPersonRange(80), cg_thirdPersonAngle(0), // Pulled back for Xbox - also in cg_main + cg_thirdPersonSpecialCam(0), cg_thirdPerson(0), cg_thirdPersonRange(90), cg_thirdPersonAngle(0), + cg_thirdPersonPitchOffset(0), cg_thirdPersonVertOffset(16), cg_thirdPersonCameraDamp(0.3), + cg_thirdPersonTargetDamp(0.5), cg_thirdPersonAlpha(1.0), cg_thirdPersonHorzOffset(0), + key_overstrikeMode(qfalse), anykeydown(qfalse), cg_crossHairStatus(0), cg_autolevel(0), cg_pitch(-0.022), + cg_sensitivity(2), cg_autoswitch(1), controller(-1), shake_intensity(0), + shake_duration(0), shake_start(0), cg_sensitivityY(2), lastFireTime(0), cvar_modifiedFlags(0),forceConfig(0), + modelIndex(0), forceSide(1), voiceTogglePressTime(0), swapMan1(HOTSWAP_ID_WHITE), swapMan2(HOTSWAP_ID_BLACK), myTeam(0), isSpectating(qfalse) + +{ + state = CA_DISCONNECTED; +// memset( &state, 0, sizeof(connstate_t)); + + memset( &mv_clc, 0, sizeof( mv_clc ) ); + m_clc = &mv_clc; + + memset( &mv_cg , 0, sizeof( mv_cg ) ); + m_cg = &mv_cg; + +// memset( &mv_cl, 0, sizeof( mv_cl ) ); +// m_cl = &mv_cl; + + memset(&in_left, 0 , sizeof(kbutton_t)); + memset(&in_right, 0 , sizeof(kbutton_t)); + memset(&in_forward, 0 , sizeof(kbutton_t)); + memset(&in_back, 0 , sizeof(kbutton_t)); + + memset(&in_lookup, 0 , sizeof(kbutton_t)); + memset(&in_lookdown, 0 , sizeof(kbutton_t)); + memset(&in_moveleft, 0 , sizeof(kbutton_t)); + memset(&in_moveright, 0 , sizeof(kbutton_t)); + + memset(&in_strafe, 0 , sizeof(kbutton_t)); + memset(&in_speed, 0 , sizeof(kbutton_t)); + memset(&in_up, 0 , sizeof(kbutton_t)); + memset(&in_down, 0 , sizeof(kbutton_t)); + + memset(in_buttons, 0, sizeof(kbutton_t) * 16); + + // Command Stuff + memset(&cmd_text, 0, sizeof(msg_t)); + memset(cmd_text_buf, 0, sizeof(byte) *MAX_CMD_BUFFER); + memset(cmd_defer_text_buf, 0, sizeof(char) * MAX_CMD_BUFFER); + MSG_Init (&cmd_text, cmd_text_buf, sizeof(cmd_text_buf)); + + memset( keys, 0 , sizeof(qkey_t) *MAX_KEYS); + + memset(eventQue, 0, sizeof(sysEvent_t) * MAX_QUED_EVENTS); + + memset( joyInfo, 0, sizeof(JoystickInfo) * IN_MAX_JOYSTICKS); + + strcpy(autoName, "Padawan"); + + + model[0] = 0; + + strcpy(saber_color1, "blue"); + strcpy(saber_color2, "blue"); + + forcePowers[0] = 0; + //forcePowersCustom[0] = 0; + + strcpy(forcePowers, "7-1-032330000000001333"); + //strcpy(forcePowersCustom, "7-1-032330000000001333"); + + strcpy(model,"kyle/default"); + strcpy(char_color_red,"255"); + strcpy(char_color_green,"255"); + strcpy(char_color_blue,"255"); + strcpy(saber1,"single_1"); + strcpy(saber2,"none"); + strcpy(color1,"4"); + strcpy(color2,"4"); +} + +ClientData::~ClientData() +{ + // Really nasty hack to prevent HeapFree() being called here + Z_PushNewDeleteTag( TAG_NEWDEL ); + + // If we got any bindings via S_Malloc, make sure to kill them here + for( int i = 0; i < MAX_KEYS; ++i ) + if( keys[i].binding ) + Z_Free( keys[i].binding ); + + Z_PopNewDeleteTag(); +} + +bool ClientManager::ActivateByControllerId(int id) +{ + for (int i = 0; i < ClientManager::NumClients(); i++) { + if (clientData[i]->controller == id) { + ActivateClient(i); +// SetMainClient(i); + return true; + } + } + return false; +} + + +void ClientManager::ClientForceInit ( int clientNum ) +{ + int oldClientNum = curClient; + ActivateClient(clientNum); + SetMainClient(clientNum); + + ClientForceInit(); + + ActivateClient(oldClientNum); + SetMainClient(oldClientNum); +} + +void ClientManager::ClientForceInit( void ) +{ + MSG_Init (&activeClient->cmd_text, activeClient->cmd_text_buf, sizeof(activeClient->cmd_text_buf)); + //exec control01.cfg + if (!activeClient->loopbacks) + { + Z_PushNewDeleteTag( TAG_CLIENT_MANAGER ); + + activeClient->loopbacks = new loopback_t[2]; + memset(activeClient->loopbacks, 0, sizeof(loopback_t) * 2); + + Z_PopNewDeleteTag( ); + } +// if (!activeClient->m_cl) activeClient->m_cl = new clientActive_t; + +// memset( activeClient->m_cl, 0, sizeof( clientActive_t ) ); + + activeClient->state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED + + Cbuf_ExecuteText( EXEC_APPEND, "exec control01.cfg\n" ); + +} + +extern void IN_KeyUp( kbutton_t *b ); +void ClientManager::ClearAllClientInputs(void) +{ + int i,j; + + for ( i = 0 ; i < numClients; i++) + { + for ( j = 0 ; j < 16; j++) + { + if (clientData[i]->in_buttons[j].active) + { + // IN_KeyUp(&clientData[i]->in_buttons[j]); + clientData[i]->in_buttons[j].down[0] = clientData[i]->in_buttons[j].down[1] = 0; + clientData[i]->in_buttons[j].active = qfalse; + } + } + if ( clientData[i]->in_down.active) + { + // IN_KeyUp(&clientData[i]->in_down); + clientData[i]->in_down.down[0] = clientData[i]->in_down.down[1] = 0; + clientData[i]->in_down.active = qfalse; + } + } +} + +void ClientManager::ClientForceAllInit( void ) +{ + for (int i = 0; i < numClients; i++) { + ClientForceInit(i); + } +} + +void ClientManager::Init(int _numClients) +{ + Shutdown(); + + Z_PushNewDeleteTag( TAG_CLIENT_MANAGER ); + + numClients = _numClients; + for (int i = 0; i < _numClients; i++) + { + clientData[i] = new ClientData(); + clientData[i]->m_cl = new clientActive_t; + memset( clientData[i]->m_cl, 0, sizeof(clientActive_t) ); + } + + ActivateClient(0); + + mainClient = 0; + + Z_PopNewDeleteTag(); +} + +void ClientManager::Shutdown( void ) +{ +// tvector(ClientData*)::iterator begin = clientData.begin(), end = clientData.end(); + for ( int i = 0; i < numClients; ++i ) + delete clientData[i]; +// clientData.clear(); + numClients = 0; + curClient = 0; + activeClient = 0; +} + +void ClientManager::Resize( int n ) +{ + Z_PushNewDeleteTag( TAG_CLIENT_MANAGER ); + + if (n < numClients ) + { + for (int i = n; i < numClients; i++) + { + if (clientData[i]->loopbacks) + delete clientData[i]->loopbacks; +// if (clientData[i]->m_cl) delete clientData[i]->m_cl; + + Z_PushNewDeleteTag( TAG_CLIENT_MANAGER_SPECIAL ); + delete clientData[n]->m_cl; + delete clientData[n]; + Z_PopNewDeleteTag( ); + } +// clientData.resize(n); + numClients = n; + } + else if (n > numClients ) + { + for( int i = numClients; i < n; ++i ) + { + clientData[i] = new ClientData(); + clientData[i]->m_cl = new clientActive_t; + memset( clientData[i]->m_cl, 0, sizeof(clientActive_t) ); + } + numClients = n; +// int diff = n - numClients; +// clientData.reserve(diff); +// for ( ; diff > 0; diff--) +// clientData.push_back(new ClientData()); + } + + Z_PopNewDeleteTag( ); +} + +int ClientManager::AddClient( void ) +{ + // Special tag that tells Z_Malloc to use HeapAlloc() instead - and thus + // use the same memory as ModelMem. + Z_PushNewDeleteTag( TAG_CLIENT_MANAGER_SPECIAL ); //TAG_CLIENT_MANAGER ); + +// clientData.push_back(new ClientData()); +// return static_cast(clientData.size()) - 1; + clientData[numClients] = new ClientData(); + clientData[numClients]->m_cl = new clientActive_t; + memset( clientData[numClients]->m_cl, 0, sizeof(clientActive_t) ); + clientData[numClients]->m_cg->widescreen = cg->widescreen; + numClients++; + + Z_PopNewDeleteTag( ); + + return numClients - 1; +} + +bool ClientManager::IsAClientControlller ( int id ) +{ + for (int i = 0; i < ClientManager::NumClients(); i++) { + if (clientData[i]->controller == id) + return true; + } + return false; +} + +void ClientManager::ClientFeedDeferedScript(void ) +{ + if ( *(activeClient->cmd_defer_text_buf) != 0) + { + Cbuf_AddText( activeClient->cmd_defer_text_buf ); + *(activeClient->cmd_defer_text_buf) = 0; + } +} + +void ClientManager::ClientActiveRelocate( bool bOutside ) +{ + if( bOutside == bClOutside ) + return; + + Z_PushNewDeleteTag( bOutside ? TAG_CLIENT_MANAGER_SPECIAL : TAG_CLIENT_MANAGER ); + clientActive_t *newCl = new clientActive_t; + Z_PopNewDeleteTag(); + + memcpy( newCl, clientData[0]->m_cl, sizeof(clientActive_t) ); + memset( clientData[0]->m_cl, 0, sizeof(clientActive_t) ); + + Z_PushNewDeleteTag( !bOutside ? TAG_CLIENT_MANAGER_SPECIAL : TAG_CLIENT_MANAGER ); + delete clientData[0]->m_cl; + Z_PopNewDeleteTag(); + + clientData[0]->m_cl = newCl; + bClOutside = bOutside; + if( curClient == 0 ) + cl = newCl; +} + +// Hacky function for server code that can't seem to include cl_data properly: +bool SplitScreenModeActive( void ) +{ + return ClientManager::splitScreenMode; +} diff --git a/codemp/client/cl_data.h b/codemp/client/cl_data.h new file mode 100644 index 0000000..25387bf --- /dev/null +++ b/codemp/client/cl_data.h @@ -0,0 +1,272 @@ +#ifndef _CL_DATA_H_ +#define _CL_DATA_H_ + +//#include "../cgame/cg_local.h" +//#include "../game/g_shared.h" +//#include "../game/g_local.h" +#include "client.h" +#include "cl_input_hotswap.h" + +#include "../win32/win_input.h" + +#include +#define MAX_CMD_BUFFER 4096 +#define MAX_LOOPBACK 16 + +#define MAX_QUED_EVENTS 256 +#define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 ) +#define STRING_SIZE 64 + + +typedef struct { + byte data[1359]; + int datalen; +} loopmsg_t; + +typedef struct { + loopmsg_t msgs[MAX_LOOPBACK]; + int get, send; +} loopback_t; + +#define CM_START_LOOP() ClientManager::StartIt(); do { +#define CM_END_LOOP() } while (ClientManager::NextIt()); + + +#define IN_MAX_JOYSTICKS 2 + +class ClientData +{ +public: + ClientData(); + ~ClientData(); + +public: + connstate_t state; // connection status + +// clientActive_t mv_cl; + clientActive_t* m_cl; + + clientConnection_t mv_clc; + clientConnection_t *m_clc; + + cg_t mv_cg; + cg_t *m_cg; + + // Button Data + kbutton_t in_left, in_right, in_forward, in_back; + kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; + kbutton_t in_strafe, in_speed; + kbutton_t in_up, in_down; + kbutton_t in_buttons[16]; + qboolean in_mlooking; + + // Hot-swap button data + HotSwapManager swapMan1; + HotSwapManager swapMan2; + + // Command Text Data + int cmd_wait; + msg_t cmd_text; + byte cmd_text_buf[MAX_CMD_BUFFER]; + char cmd_defer_text_buf[MAX_CMD_BUFFER]; + + // NET stuff + loopback_t *loopbacks; + + // event stuff + sysEvent_t eventQue[MAX_QUED_EVENTS]; + int eventHead, eventTail; + + // key stuff + qboolean key_overstrikeMode; + qboolean anykeydown; + qkey_t keys[MAX_KEYS]; + + // Used for autolevel + unsigned long lastFireTime; + // Used for taunting + unsigned long voiceTogglePressTime; + + // controller stuff + int controller; + + JoystickInfo joyInfo[IN_MAX_JOYSTICKS]; + + int serverCommandSequence; + + int cg_thirdPersonSpecialCam; + + int cg_thirdPerson; + int cg_thirdPersonRange; + int cg_thirdPersonAngle; + int cg_thirdPersonPitchOffset; + int cg_thirdPersonVertOffset; + float cg_thirdPersonCameraDamp; + float cg_thirdPersonTargetDamp; + + float cg_thirdPersonAlpha; + int cg_thirdPersonHorzOffset; + + vec3_t cameraFocusAngles, cameraFocusLoc; + vec3_t cameraIdealTarget, cameraIdealLoc; + vec3_t cameraCurTarget, cameraCurLoc; + int cameraLastFrame; + + float cameraLastYaw; + float cameraStiffFactor; + + int cg_crossHairStatus; + + short cg_autolevel; + float cg_pitch; + float cg_sensitivity; +#ifdef _XBOX + float cg_sensitivityY; +#endif + short cg_autoswitch; + + int cvar_modifiedFlags; + + char autoName[STRING_SIZE]; + + //character Model and saber info + char model[STRING_SIZE]; + int modelIndex; + //colors + char char_color_red[STRING_SIZE]; + char char_color_green[STRING_SIZE]; + char char_color_blue[STRING_SIZE]; + + char saber1[STRING_SIZE]; + char saber2[STRING_SIZE]; + char color1[STRING_SIZE]; + char color2[STRING_SIZE]; + char saber_color1[STRING_SIZE]; + char saber_color2[STRING_SIZE]; + char forcePowers[STRING_SIZE]; +// char forcePowersCustom[STRING_SIZE]; + int forceSide; + int forceConfig; + int myTeam; + qboolean isSpectating; + + +// char profileName[STRING_SIZE]; + + + + // Camera shake stuff + float shake_intensity; + int shake_duration; + int shake_start; + + int cgRageTime; + int cgRageFadeTime; + float cgRageFadeVal; + + int cgRageRecTime; + int cgRageRecFadeTime; + float cgRageRecFadeVal; + + int cgAbsorbTime; + int cgAbsorbFadeTime; + float cgAbsorbFadeVal; + + int cgProtectTime; + int cgProtectFadeTime; + float cgProtectFadeVal; + + int cgYsalTime; + int cgYsalFadeTime; + float cgYsalFadeVal; +}; + + +class ClientManager +{ +public: + + __inline static void StartIt( void ) { ActivateClient(0); } + __inline static bool NextIt( void ) + { +// if ( curClient + 1 >= static_cast(clientData.size()) ) { + if ( curClient + 1 >= numClients ) { + ActivateClient(mainClient); + return false; + } + ActivateClient( curClient + 1); + return true; + } + + __inline static int ActiveController( void ) { return activeClient->controller; } + + __inline static void SetActiveController( int ctl ) { activeClient->controller = ctl; } + __inline static void SetMainClient( int client) { mainClient = client; } + __inline static void ActivateMainClient( void ) { ActivateClient(mainClient); } + __inline static ClientData& ActiveClient( void ) { return *activeClient; } + __inline static ClientData& GetClient( int i ) { return *clientData[i] ; } + __inline static int ActiveClientNum( void ) { return curClient; } + __inline static int ActivePort ( void ) { return Cvar_VariableIntegerValue("qport") + curClient; } + + __inline static bool IsActiveClient (int n) { return (n == curClient); } + __inline static int NumClients( void ) { return numClients; } + + __inline static int Controller(int clientNum) { return clientData[clientNum]->controller; } + __inline static void ActivateClient( int client ) + { + if (client >= numClients) return; + +// clientData[curClient]->m_cg = cg; +// if(clientData[curClient]->m_cl) +// clientData[curClient]->m_cl = cl; +// clientData[curClient]->m_clc = clc; + // Need to check when removing second client: + if( curClient < numClients ) + clientData[curClient]->state = cls.state; + + curClient = client; + activeClient = clientData[curClient]; + + cg = clientData[curClient]->m_cg; +// if(clientData[curClient]->m_cl) + cl = clientData[curClient]->m_cl; + clc = clientData[curClient]->m_clc; + cls.state = clientData[curClient]->state; + } + + static int AddClient( void ); + static void Resize( int n ); + static void Shutdown( void ); + static void Init( int _numClients ); + + static void ClientForceInit (int client); + static void ClientForceInit ( void ); + static void ClearAllClientInputs(void); + static void ClientForceAllInit ( void ); + + static bool ActivateByControllerId( int id); + static bool IsAClientControlller ( int id ); + static void ClientFeedDeferedScript(void ); + + static void ClientActiveRelocate( bool bOutside ); + +private: + ClientManager() {} + ~ClientManager() {} + operator =(const ClientManager&); + +private: +// static tvector(ClientData*) clientData; + static ClientData* clientData[2]; + static int curClient; + static int mainClient; + static ClientData* activeClient; + static int numClients; + + static bool bClOutside; + +public: + static qboolean splitScreenMode; +}; + +#endif diff --git a/codemp/client/cl_input.cpp b/codemp/client/cl_input.cpp new file mode 100644 index 0000000..c192e07 --- /dev/null +++ b/codemp/client/cl_input.cpp @@ -0,0 +1,2757 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// cl->input.c -- builds an intended movement command to send to the server + +#include "client.h" +#ifdef _XBOX +#include "cl_input_hotswap.h" +#include "../xbox/XBVoice.h" +#include "../cgame/cg_local.h" +#include "cl_data.h" +#endif +unsigned frame_msec; +int old_com_frameTime; + +float cl_mPitchOverride = 0.0f; +float cl_mYawOverride = 0.0f; +float cl_mSensitivityOverride = 0.0f; +qboolean cl_bUseFighterPitch = qfalse; +qboolean cl_crazyShipControls = qfalse; +#define OVERRIDE_MOUSE_SENSITIVITY 5.0f//20.0f = 180 degree turn in one mouse swipe across keyboard +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as argv(1) so it can be matched up with the release. + +argv(2) will be set to the time the event happened, which allows exact +control even at low framerates when the down and up events may both get qued +at the same time. + +=============================================================================== +*/ + + +kbutton_t in_left, in_right, in_forward, in_back; +kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; +kbutton_t in_strafe, in_speed; +kbutton_t in_up, in_down; + +kbutton_t in_buttons[16]; + + +qboolean in_mlooking; + + +#ifdef _XBOX +//HotSwapManager swapMan1(HOTSWAP_ID_WHITE); +//HotSwapManager swapMan2(HOTSWAP_ID_BLACK); + + +void IN_HotSwap1On(void) +{ + ClientManager::ActiveClient().swapMan1.SetDown(); +} + + +void IN_HotSwap2On(void) +{ + ClientManager::ActiveClient().swapMan2.SetDown(); +} + + +void IN_HotSwap1Off(void) +{ + ClientManager::ActiveClient().swapMan1.SetUp(); +} + + +void IN_HotSwap2Off(void) +{ + ClientManager::ActiveClient().swapMan2.SetUp(); +} + + +void CL_UpdateHotSwap(void) +{ + CM_START_LOOP(); + ClientManager::ActiveClient().swapMan1.Update(); + ClientManager::ActiveClient().swapMan2.Update(); + CM_END_LOOP(); +} + + +bool CL_ExtendSelectTime(void) +{ + return ClientManager::ActiveClient().swapMan1.ButtonDown() || + ClientManager::ActiveClient().swapMan2.ButtonDown(); +} + + +#endif + + +void IN_Button11Down(void); +void IN_Button11Up(void); +void IN_Button10Down(void); +void IN_Button10Up(void); +void IN_Button6Down(void); +void IN_Button6Up(void); +void IN_UseGivenForce(void) +{ + char *c = Cmd_Argv(1); + int forceNum =-1; + int genCmdNum = 0; + + if(c) { + forceNum = atoi(c); + } else { + return; + } + + switch(forceNum) { + case FP_DRAIN: + IN_Button11Down(); + IN_Button11Up(); + break; + case FP_PUSH: + genCmdNum = GENCMD_FORCE_THROW; + break; + case FP_SPEED: + genCmdNum = GENCMD_FORCE_SPEED; + break; + case FP_PULL: + genCmdNum = GENCMD_FORCE_PULL; + break; + case FP_TELEPATHY: + genCmdNum = GENCMD_FORCE_DISTRACT; + break; + case FP_GRIP: + IN_Button6Down(); + IN_Button6Up(); + break; + case FP_LIGHTNING: + IN_Button10Down(); + IN_Button10Up(); + break; + case FP_RAGE: + genCmdNum = GENCMD_FORCE_RAGE; + break; + case FP_PROTECT: + genCmdNum = GENCMD_FORCE_PROTECT; + break; + case FP_ABSORB: + genCmdNum = GENCMD_FORCE_ABSORB; + break; + case FP_SEE: + genCmdNum = GENCMD_FORCE_SEEING; + break; + case FP_HEAL: + genCmdNum = GENCMD_FORCE_HEAL; + break; + case FP_TEAM_HEAL: + genCmdNum = GENCMD_FORCE_HEALOTHER; + break; + case FP_TEAM_FORCE: + genCmdNum = GENCMD_FORCE_FORCEPOWEROTHER; + break; + default: + assert(0); + break; + } + + if(genCmdNum != 0) { + cl->gcmdSendValue = qtrue; + cl->gcmdValue = genCmdNum; + } +} + +void IN_MLookDown( void ) { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + ClientManager::ActiveClient().in_mlooking = qtrue; + } + else +#endif + + in_mlooking = qtrue; +} + +void IN_MLookUp( void ) { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + ClientManager::ActiveClient().in_mlooking = qfalse; + } + else +#endif + + in_mlooking = qfalse; + if ( !cl_freelook->integer ) { + IN_CenterView (); + } +} + +void IN_GenCMD1( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_SABERSWITCH; +} + +void IN_GenCMD2( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_ENGAGE_DUEL; +} + +void IN_GenCMD3( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_HEAL; +} + +void IN_GenCMD4( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_SPEED; +} + +void IN_GenCMD5( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_PULL; +} + +void IN_GenCMD6( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_DISTRACT; +} + +void IN_GenCMD7( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_RAGE; +} + +void IN_GenCMD8( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_PROTECT; +} + +void IN_GenCMD9( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_ABSORB; +} + +void IN_GenCMD10( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_HEALOTHER; +} + +void IN_GenCMD11( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_FORCEPOWEROTHER; +} + +void IN_GenCMD12( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_SEEING; +} + +void IN_GenCMD13( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_SEEKER; +} + +void IN_GenCMD14( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_FIELD; +} + +void IN_GenCMD15( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_BACTA; +} + +void IN_GenCMD16( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_ELECTROBINOCULARS; +} + +void IN_GenCMD17( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_ZOOM; +} + +void IN_GenCMD18( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_SENTRY; +} + +void IN_GenCMD19( void ) +{ +#ifdef _XBOX + if (cl->snap.ps.weapon != WP_SABER) + { + Cbuf_ExecuteText(EXEC_APPEND, "weapon 1"); + return; + } +#endif + + if (Cvar_VariableIntegerValue("d_saberStanceDebug")) + { + Com_Printf("SABERSTANCEDEBUG: Gencmd on client set successfully.\n"); + } + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_SABERATTACKCYCLE; +} + +void IN_GenCMD20( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FORCE_THROW; +} + +void IN_GenCMD21( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_JETPACK; +} + +void IN_GenCMD22( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_BACTABIG; +} + +void IN_GenCMD23( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_HEALTHDISP; +} + +void IN_GenCMD24( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_AMMODISP; +} + +void IN_GenCMD25( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_EWEB; +} + +void IN_GenCMD26( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_USE_CLOAK; +} + +void IN_GenCMD27( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_TAUNT; +} + +void IN_GenCMD28( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_BOW; +} + +void IN_GenCMD29( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_MEDITATE; +} + +void IN_GenCMD30( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_FLOURISH; +} + +void IN_GenCMD31( void ) +{ + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_GLOAT; +} + + +//toggle automap view mode +static bool g_clAutoMapMode = false; +void IN_AutoMapButton(void) +{ + g_clAutoMapMode = !g_clAutoMapMode; +} + +//toggle between automap, radar, nothing +extern cvar_t *r_autoMap; +void IN_AutoMapToggle(void) +{ + + if (Cvar_VariableIntegerValue("cg_drawRadar")) + { + Cvar_Set("cg_drawRadar", "0"); + } + else + { + Cvar_Set("cg_drawRadar", "1"); + } + /* + if (r_autoMap && r_autoMap->integer) + { //automap off, radar on + Cvar_Set("r_autoMap", "0"); + Cvar_Set("cg_drawRadar", "1"); + } + else if (Cvar_VariableIntegerValue("cg_drawRadar")) + { //radar off, automap should be off too + Cvar_Set("cg_drawRadar", "0"); + } + else + { //turn automap on + Cvar_Set("r_autoMap", "1"); + } + */ +} + +void IN_VoiceChatButton(void) +{ + if (!uivm) + { //ui not loaded so this command is useless + return; + } + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_VOICECHAT ); +} + +void IN_KeyDown( kbutton_t *b ) { + int k; + char *c; + + c = Cmd_Argv(1); + if ( c[0] ) { + k = atoi(c); + } else { + k = -1; // typed manually at the console for continuous down + } + + if ( k == b->down[0] || k == b->down[1] ) { + return; // repeating key + } + + if ( !b->down[0] ) { + b->down[0] = k; + } else if ( !b->down[1] ) { + b->down[1] = k; + } else { + Com_Printf ("Three keys down for a button!\n"); + return; + } + + if ( b->active ) { + return; // still down + } + + // save timestamp for partial frame summing + c = Cmd_Argv(2); + b->downtime = atoi(c); + + b->active = qtrue; + b->wasPressed = qtrue; +} + +void IN_KeyUp( kbutton_t *b ) { + int k; + char *c; + unsigned uptime; + + c = Cmd_Argv(1); + if ( c[0] ) { + k = atoi(c); + } else { + // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->active = qfalse; + return; + } + + if ( b->down[0] == k ) { + b->down[0] = 0; + } else if ( b->down[1] == k ) { + b->down[1] = 0; + } else { + return; // key up without coresponding down (menu pass through) + } + if ( b->down[0] || b->down[1] ) { + return; // some other key is still holding it down + } + + b->active = qfalse; + + // save timestamp for partial frame summing + c = Cmd_Argv(2); + uptime = atoi(c); + if ( uptime ) { + b->msec += uptime - b->downtime; + } else { + b->msec += frame_msec / 2; + } + + b->active = qfalse; +} + + + +/* +=============== +CL_KeyState + +Returns the fraction of the frame that the key was down +=============== +*/ +float CL_KeyState( kbutton_t *key ) { + float val; + int msec; + + msec = key->msec; + key->msec = 0; + + if ( key->active ) { + // still down + if ( !key->downtime ) { + msec = com_frameTime; + } else { + msec += com_frameTime - key->downtime; + } + key->downtime = com_frameTime; + } + +#if 0 + if (msec) { + Com_Printf ("%i ", msec); + } +#endif + + val = (float)msec / frame_msec; + if ( val < 0 ) { + val = 0; + } + if ( val > 1 ) { + val = 1; + } + + return val; +} + +#define AUTOMAP_KEY_FORWARD 1 +#define AUTOMAP_KEY_BACK 2 +#define AUTOMAP_KEY_YAWLEFT 3 +#define AUTOMAP_KEY_YAWRIGHT 4 +#define AUTOMAP_KEY_PITCHUP 5 +#define AUTOMAP_KEY_PITCHDOWN 6 +#define AUTOMAP_KEY_DEFAULTVIEW 7 +static autoMapInput_t g_clAutoMapInput; +//intercept certain keys during automap mode +static void CL_AutoMapKey(int autoMapKey, qboolean up) +{ + autoMapInput_t *data = (autoMapInput_t *)cl->mSharedMemory; + + switch (autoMapKey) + { + case AUTOMAP_KEY_FORWARD: + if (up) + { + g_clAutoMapInput.up = 0.0f; + } + else + { + g_clAutoMapInput.up = 16.0f; + } + break; + case AUTOMAP_KEY_BACK: + if (up) + { + g_clAutoMapInput.down = 0.0f; + } + else + { + g_clAutoMapInput.down = 16.0f; + } + break; + case AUTOMAP_KEY_YAWLEFT: + if (up) + { + g_clAutoMapInput.yaw = 0.0f; + } + else + { + g_clAutoMapInput.yaw = -4.0f; + } + break; + case AUTOMAP_KEY_YAWRIGHT: + if (up) + { + g_clAutoMapInput.yaw = 0.0f; + } + else + { + g_clAutoMapInput.yaw = 4.0f; + } + break; + case AUTOMAP_KEY_PITCHUP: + if (up) + { + g_clAutoMapInput.pitch = 0.0f; + } + else + { + g_clAutoMapInput.pitch = -4.0f; + } + break; + case AUTOMAP_KEY_PITCHDOWN: + if (up) + { + g_clAutoMapInput.pitch = 0.0f; + } + else + { + g_clAutoMapInput.pitch = 4.0f; + } + break; + case AUTOMAP_KEY_DEFAULTVIEW: + memset(&g_clAutoMapInput, 0, sizeof(autoMapInput_t)); + g_clAutoMapInput.goToDefaults = qtrue; + break; + default: + break; + } + + memcpy(data, &g_clAutoMapInput, sizeof(autoMapInput_t)); + + if (cgvm) + { + VM_Call(cgvm, CG_AUTOMAP_INPUT, 0); + } + + g_clAutoMapInput.goToDefaults = qfalse; +} + + +void IN_UpDown(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_PITCHUP, qfalse); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + IN_KeyDown(&ClientManager::ActiveClient().in_up); + else +#endif + IN_KeyDown(&in_up); + } +} +void IN_UpUp(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_PITCHUP, qtrue); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + IN_KeyUp(&ClientManager::ActiveClient().in_up); + else +#endif + IN_KeyUp(&in_up); + } +} +void IN_DownDown(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_PITCHDOWN, qfalse); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + IN_KeyDown(&ClientManager::ActiveClient().in_down); + else +#endif + IN_KeyDown(&in_down); + } +} +void IN_DownUp(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_PITCHDOWN, qtrue); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + IN_KeyUp(&ClientManager::ActiveClient().in_down); + else +#endif + IN_KeyUp(&in_down); + } +} + +void IN_LeftDown(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_left); + } + else +#endif + IN_KeyDown(&in_left); +} + +void IN_LeftUp(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_left); + } + else +#endif + IN_KeyUp(&in_left); +} + +void IN_RightDown(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_right); + } + else +#endif + IN_KeyDown(&in_right); +} + + +void IN_RightUp(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_right); + } + else +#endif + IN_KeyUp(&in_right); +} + + +void IN_ForwardDown(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_FORWARD, qfalse); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_forward); + } + else +#endif + IN_KeyDown(&in_forward); + } +} + + +void IN_ForwardUp(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_FORWARD, qtrue); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_forward); + } + else +#endif + IN_KeyUp(&in_forward); + } +} +void IN_BackDown(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_BACK, qfalse); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_back); + } + else +#endif + IN_KeyDown(&in_back); + } +} +void IN_BackUp(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_BACK, qtrue); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_back); + } + else +#endif + IN_KeyUp(&in_back); + } +} + +void IN_LookupDown(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_lookup); + } + else +#endif + IN_KeyDown(&in_lookup); +} + +void IN_LookupUp(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_lookup); + } + else +#endif + IN_KeyUp(&in_lookup); +} + +void IN_LookdownDown(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_lookdown); + } + else +#endif + IN_KeyDown(&in_lookdown); +} + +void IN_LookdownUp(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_lookdown); + } + else +#endif + IN_KeyUp(&in_lookdown); +} + +void IN_MoveleftDown(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_YAWLEFT, qfalse); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_moveleft); + } + else +#endif + IN_KeyDown(&in_moveleft); + } +} + +void IN_MoveleftUp(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_YAWLEFT, qtrue); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_moveleft); + } + else +#endif + IN_KeyUp(&in_moveleft); + } +} + +void IN_MoverightDown(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_YAWRIGHT, qfalse); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_moveright); + } + else +#endif + IN_KeyDown(&in_moveright); + } +} + +void IN_MoverightUp(void) +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_YAWRIGHT, qtrue); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_moveright); + } + else +#endif + IN_KeyUp(&in_moveright); + } +} + +#ifdef _XBOX +// BTO - State for auto-level usage. This REALLY ought to be in cl[sc ]. +// Honestly, I don't want to figure out which one, especially anticipating +// split-screen. All I want for Christmas is to not do split-screen. + +// MATT - looks like you got coal in your stocking this year.... +//static unsigned long sLastFireTime = 0; +#endif + +void IN_SpeedDown(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_speed); + } + else +#endif + IN_KeyDown(&in_speed); +} + +void IN_SpeedUp(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_speed); + } + else +#endif + IN_KeyUp(&in_speed); +} + +void IN_StrafeDown(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_strafe); + } + else +#endif + IN_KeyDown(&in_strafe); +} + +void IN_StrafeUp(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_strafe); + } + else +#endif + IN_KeyUp(&in_strafe); +} + +void IN_Button0Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[0]); + } + else +#endif + IN_KeyDown(&in_buttons[0]); +} + +void IN_Button0Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[0]); + } + else +#endif + IN_KeyUp(&in_buttons[0]); + +#ifdef _XBOX // Auto-level. Thought this was nasty, but now I sort of like it. + ClientManager::ActiveClient().lastFireTime = Sys_Milliseconds(); +#endif +} + +void IN_Button1Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[1]); + } + else +#endif + IN_KeyDown(&in_buttons[1]); +} + +void IN_Button1Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[1]); + } + else +#endif + IN_KeyUp(&in_buttons[1]); +} + +void IN_Button2Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[2]); + } + else +#endif + IN_KeyDown(&in_buttons[2]); +} + +void IN_Button2Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[2]); + } + else +#endif + IN_KeyUp(&in_buttons[2]); +} + +void IN_Button3Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[3]); + } + else +#endif + IN_KeyDown(&in_buttons[3]); +} + +void IN_Button3Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[3]); + } + else +#endif + IN_KeyUp(&in_buttons[3]); +} + +void IN_Button4Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[4]); + } + else +#endif + IN_KeyDown(&in_buttons[4]); +} + +void IN_Button4Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[4]); + } + else +#endif + IN_KeyUp(&in_buttons[4]); +} + +void IN_Button5Down(void) //use key +{ + if (g_clAutoMapMode) + { + CL_AutoMapKey(AUTOMAP_KEY_DEFAULTVIEW, qfalse); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[5]); + } + else +#endif + IN_KeyDown(&in_buttons[5]); + } +} + +void IN_Button5Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[5]); + } + else +#endif + IN_KeyUp(&in_buttons[5]); +} + +void IN_Button6Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[6]); + } + else +#endif + IN_KeyDown(&in_buttons[6]); +} + +void IN_Button6Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[6]); + } + else +#endif + IN_KeyUp(&in_buttons[6]); +} + +void IN_Button7Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[7]); + } + else +#endif + IN_KeyDown(&in_buttons[7]); +} + +void IN_Button7Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[7]); + } + else +#endif + IN_KeyUp(&in_buttons[7]); + +#ifdef _XBOX // Auto-level. Thought this was nasty, but now I sort of like it. + ClientManager::ActiveClient().lastFireTime = Sys_Milliseconds(); +#endif +} + +void IN_Button8Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[8]); + } + else +#endif + IN_KeyDown(&in_buttons[8]); +} + + +void IN_Button8Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[8]); + } + else +#endif + IN_KeyUp(&in_buttons[8]); +} + +void IN_Button9Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[9]); + } + else +#endif + IN_KeyDown(&in_buttons[9]); +} + +void IN_Button9Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[9]); + } + else +#endif + IN_KeyUp(&in_buttons[9]); +} + +void IN_Button10Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[10]); + } + else +#endif + IN_KeyDown(&in_buttons[10]); +} + +void IN_Button10Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[10]); + } + else +#endif + IN_KeyUp(&in_buttons[10]); +} + +void IN_Button11Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[11]); + } + else +#endif + IN_KeyDown(&in_buttons[11]); +} + +void IN_Button11Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[11]); + } + else +#endif + IN_KeyUp(&in_buttons[11]); +} + +void IN_Button12Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[12]); + } + else +#endif + IN_KeyDown(&in_buttons[12]); +} + +void IN_Button12Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[12]); + } + else +#endif + IN_KeyUp(&in_buttons[12]); +} + +void IN_Button13Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[13]); + } + else +#endif + IN_KeyDown(&in_buttons[13]); +} + +void IN_Button13Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[13]); + } + else +#endif + IN_KeyUp(&in_buttons[13]); +} + +void IN_Button14Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[14]); + } + else +#endif + IN_KeyDown(&in_buttons[14]); +} + +void IN_Button14Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[14]); + } + else +#endif + IN_KeyUp(&in_buttons[14]); +} + +void IN_Button15Down(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[15]); + } + else +#endif + IN_KeyDown(&in_buttons[15]); +} + +void IN_Button15Up(void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[15]); + } + else +#endif + IN_KeyUp(&in_buttons[15]); +} + +void IN_ButtonDown (void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyDown(&ClientManager::ActiveClient().in_buttons[1]); + } + else +#endif + IN_KeyDown(&in_buttons[1]); +} + +void IN_ButtonUp (void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + IN_KeyUp(&ClientManager::ActiveClient().in_buttons[1]); + } + else +#endif + IN_KeyUp(&in_buttons[1]); +} + +void IN_CenterView (void) { + cl->viewangles[PITCH] = -SHORT2ANGLE(cl->snap.ps.delta_angles[PITCH]); +} + +#ifdef _XBOX + +// If the voice toggle button (default: Y) is held for less than TAUNT_PRESS_TIME +// milliseconds - we play the character's taunt animation +#define TAUNT_PRESS_TIME 500 + +void IN_VoiceToggleDown(void) +{ + g_Voice.SetChannel( CHAN_ALT ); + // Remember when we pressed this. If it's held for a short amount of time, we taunt. =) + ClientManager::ActiveClient().voiceTogglePressTime = Sys_Milliseconds(); +} + +void IN_VoiceToggleUp(void) +{ + g_Voice.SetChannel( CHAN_PRIMARY ); + // Check duration of hold + if( Sys_Milliseconds() - ClientManager::ActiveClient().voiceTogglePressTime < TAUNT_PRESS_TIME ) + { + cl->gcmdSendValue = qtrue; + cl->gcmdValue = GENCMD_TAUNT; + } +} +#endif + + +//========================================================================== + +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_sidespeed; + +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; + +cvar_t *cl_run; + +cvar_t *cl_anglespeedkey; + + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles( void ) { + float speed; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + if ( ClientManager::ActiveClient().in_speed.active ) + { + speed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + speed = 0.001 * cls.frametime; + } + + if ( !ClientManager::ActiveClient().in_strafe.active ) + { + if ( cl_mYawOverride ) + { + cl->viewangles[YAW] -= cl_mYawOverride*OVERRIDE_MOUSE_SENSITIVITY*speed*cl_yawspeed->value*CL_KeyState (&ClientManager::ActiveClient().in_right); + cl->viewangles[YAW] += cl_mYawOverride*OVERRIDE_MOUSE_SENSITIVITY*speed*cl_yawspeed->value*CL_KeyState (&ClientManager::ActiveClient().in_left); + } + else + { + cl->viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&ClientManager::ActiveClient().in_right); + cl->viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&ClientManager::ActiveClient().in_left); + } + } + + if ( cl_mPitchOverride ) + { + cl->viewangles[PITCH] -= cl_mPitchOverride*OVERRIDE_MOUSE_SENSITIVITY*speed*cl_pitchspeed->value * CL_KeyState (&ClientManager::ActiveClient().in_lookup); + cl->viewangles[PITCH] += cl_mPitchOverride*OVERRIDE_MOUSE_SENSITIVITY*speed*cl_pitchspeed->value * CL_KeyState (&ClientManager::ActiveClient().in_lookdown); + } + else + { + cl->viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&ClientManager::ActiveClient().in_lookup); + cl->viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&ClientManager::ActiveClient().in_lookdown); + } + } + else + { +#endif // _XBOX + + if ( in_speed.active ) { + speed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + speed = 0.001 * cls.frametime; + } + + if ( !in_strafe.active ) { + if ( cl_mYawOverride ) + { + cl->viewangles[YAW] -= cl_mYawOverride*OVERRIDE_MOUSE_SENSITIVITY*speed*cl_yawspeed->value*CL_KeyState (&in_right); + cl->viewangles[YAW] += cl_mYawOverride*OVERRIDE_MOUSE_SENSITIVITY*speed*cl_yawspeed->value*CL_KeyState (&in_left); + } + else + { + cl->viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + cl->viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + } + } + + if ( cl_mPitchOverride ) + { + cl->viewangles[PITCH] -= cl_mPitchOverride*OVERRIDE_MOUSE_SENSITIVITY*speed*cl_pitchspeed->value * CL_KeyState (&in_lookup); + cl->viewangles[PITCH] += cl_mPitchOverride*OVERRIDE_MOUSE_SENSITIVITY*speed*cl_pitchspeed->value * CL_KeyState (&in_lookdown); + } + else + { + cl->viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_lookup); + cl->viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_lookdown); + } + +#ifdef _XBOX + } +#endif +} + +/* +================ +CL_KeyMove + +Sets the usercmd_t based on key states +================ +*/ +void CL_KeyMove( usercmd_t *cmd ) { + int movespeed; + int forward, side, up; + + // + // adjust for speed key / running + // the walking flag is to keep animations consistant + // even during acceleration and develeration + // + + forward = 0; + side = 0; + up = 0; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + if ( ClientManager::ActiveClient().in_speed.active ^ cl_run->integer ) { + movespeed = 127; + cmd->buttons &= ~BUTTON_WALKING; + } else { + cmd->buttons |= BUTTON_WALKING; + movespeed = 46; + } + + if ( ClientManager::ActiveClient().in_strafe.active ) + { + side += movespeed * CL_KeyState (&ClientManager::ActiveClient().in_right); + side -= movespeed * CL_KeyState (&ClientManager::ActiveClient().in_left); + } + + side += movespeed * CL_KeyState (&ClientManager::ActiveClient().in_moveright); + side -= movespeed * CL_KeyState (&ClientManager::ActiveClient().in_moveleft); + + + up += movespeed * CL_KeyState (&ClientManager::ActiveClient().in_up); + up -= movespeed * CL_KeyState (&ClientManager::ActiveClient().in_down); + + forward += movespeed * CL_KeyState (&ClientManager::ActiveClient().in_forward); + forward -= movespeed * CL_KeyState (&ClientManager::ActiveClient().in_back); + } + else + { +#endif // _XBOX + + if ( in_speed.active ^ cl_run->integer ) { + movespeed = 127; + cmd->buttons &= ~BUTTON_WALKING; + } else { + cmd->buttons |= BUTTON_WALKING; + movespeed = 46; + } + + if ( in_strafe.active ) { + side += movespeed * CL_KeyState (&in_right); + side -= movespeed * CL_KeyState (&in_left); + } + + side += movespeed * CL_KeyState (&in_moveright); + side -= movespeed * CL_KeyState (&in_moveleft); + + + up += movespeed * CL_KeyState (&in_up); + up -= movespeed * CL_KeyState (&in_down); + + forward += movespeed * CL_KeyState (&in_forward); + forward -= movespeed * CL_KeyState (&in_back); + +#ifdef _XBOX + } +#endif + + cmd->forwardmove = ClampChar( forward ); + cmd->rightmove = ClampChar( side ); + cmd->upmove = ClampChar( up ); +} + +/* +================= +CL_MouseEvent +================= +*/ +void CL_MouseEvent( int dx, int dy, int time ) { + +#ifdef _XBOX + // No mouse movement if the player is taunting + if(ClientManager::splitScreenMode == qtrue && cg->predictedPlayerState.forceHandExtend == HANDEXTEND_TAUNT) + return; +#endif + if (g_clAutoMapMode && cgvm) + { //automap input + autoMapInput_t *data = (autoMapInput_t *)cl->mSharedMemory; + + g_clAutoMapInput.yaw = dx; + g_clAutoMapInput.pitch = dy; + memcpy(data, &g_clAutoMapInput, sizeof(autoMapInput_t)); + VM_Call(cgvm, CG_AUTOMAP_INPUT, 1); + + g_clAutoMapInput.yaw = 0.0f; + g_clAutoMapInput.pitch = 0.0f; + } + else if ( cls.keyCatchers & KEYCATCH_UI ) { + VM_Call( uivm, UI_MOUSE_EVENT, dx, dy ); + } else if (cls.keyCatchers & KEYCATCH_CGAME) { + VM_Call (cgvm, CG_MOUSE_EVENT, dx, dy); + } else { + cl->mouseDx[cl->mouseIndex] += dx; + cl->mouseDy[cl->mouseIndex] += dy; + } +} + +/* +================= +CL_JoystickEvent + +Joystick values stay set until changed +================= +*/ +void CL_JoystickEvent( int axis, int value, int time ) { + if ( axis < 0 || axis >= MAX_JOYSTICK_AXIS ) { + Com_Error( ERR_DROP, "CL_JoystickEvent: bad axis %i", axis ); + } + cl->joystickAxis[axis] = value; +} + +/* +================= +CL_JoystickMove +================= +*/ +extern cvar_t *in_joystick; +void CL_JoystickMove( usercmd_t *cmd ) { +/* + if ( !in_joystick->integer ) + { + return; + } +*/ + +// int movespeed; +// float anglespeed; + +/* + if(ClientManager::splitScreenMode == qtrue) + { + if ( ClientManager::ActiveClient().in_speed.active ^ cl_run->integer ) { + movespeed = 2; + } else { + movespeed = 1; + cmd->buttons |= BUTTON_WALKING; + } + + if ( ClientManager::ActiveClient().in_speed.active ) { + anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + anglespeed = 0.001 * cls.frametime; + } + } + else + { + if ( in_speed.active ^ cl_run->integer ) { + movespeed = 2; + } else { + movespeed = 1; + cmd->buttons |= BUTTON_WALKING; + } + + if ( in_speed.active ) { + anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + anglespeed = 0.001 * cls.frametime; + } + } +*/ + +/* + if ( !in_strafe.active ) { + if ( cl_mYawOverride ) + { + cl->viewangles[YAW] += cl_mYawOverride * OVERRIDE_MOUSE_SENSITIVITY * cl->joystickAxis[AXIS_SIDE]/2.0f; + } + else + { + cl->viewangles[YAW] += anglespeed * (cl_yawspeed->value / 100.0f) * cl->joystickAxis[AXIS_SIDE]; + } + } else +*/ + { + cmd->rightmove = ClampChar( cmd->rightmove + cl->joystickAxis[AXIS_SIDE] ); + } + +/* + if ( in_mlooking || cl_freelook->integer ) { + if ( cl_mPitchOverride ) + { + cl->viewangles[PITCH] += cl_mPitchOverride * OVERRIDE_MOUSE_SENSITIVITY * cl->joystickAxis[AXIS_FORWARD]/2.0f; + } + else + { + cl->viewangles[PITCH] += anglespeed * (cl_pitchspeed->value / 100.0f) * cl->joystickAxis[AXIS_FORWARD]; + } + } else +*/ + { + cmd->forwardmove = ClampChar( cmd->forwardmove + cl->joystickAxis[AXIS_FORWARD] ); + } + +// if( abs( cmd->forwardmove ) < MOVE_RUN && +// abs( cmd->rightmove ) < MOVE_RUN ) +// cmd->buttons |= BUTTON_WALKING; + + // Smarter run-speed detection, taking diagonals into account! + if( (cmd->forwardmove * cmd->forwardmove + cmd->rightmove * cmd->rightmove) < (MOVE_RUN * MOVE_RUN) ) + cmd->buttons |= BUTTON_WALKING; + + cmd->upmove = ClampChar( cmd->upmove + cl->joystickAxis[AXIS_UP] ); +} + +/* +================= +CL_MouseMove +================= +*/ +#ifdef _XBOX +void CL_MouseClamp(int *x, int *y) +{ + float ax = Q_fabs(*x); + float ay = Q_fabs(*y); + + ax = (ax-10)*(3.0f/45.0f) * (ax-10) * (Q_fabs(*x) > 10); + ay = (ay-10)*(3.0f/45.0f) * (ay-10) * (Q_fabs(*y) > 10); + if (*x < 0) + *x = -ax; + else + *x = ax; + if (*y < 0) + *y = -ay; + else + *y = ay; +} +#endif + +void CL_MouseMove( usercmd_t *cmd ) { + float mx, my; + float accelSensitivity; +#ifdef _XBOX + float accelSensitivityY; +#endif + + float rate; + const float speed = static_cast(frame_msec); + const float pitch = ClientManager::ActiveClient().cg_pitch; + +#ifdef _XBOX + const float mouseSpeedX = 0.06f; + const float mouseSpeedY = 0.05f; + + // allow mouse smoothing + if ( m_filter->integer ) { + mx = ( cl->mouseDx[0] + cl->mouseDx[1] ) * 0.5f * frame_msec * mouseSpeedX; + my = ( cl->mouseDy[0] + cl->mouseDy[1] ) * 0.5f * frame_msec * mouseSpeedY; + } else { + int ax = cl->mouseDx[cl->mouseIndex]; + int ay = cl->mouseDy[cl->mouseIndex]; + CL_MouseClamp(&ax, &ay); + + mx = ax * speed * mouseSpeedX; + my = ay * speed * mouseSpeedY; + } + +// extern int cg_crossHairStatus; + const float m_hoverSensitivity = 0.4f; + if (ClientManager::ActiveClient().cg_crossHairStatus) + { + mx *= m_hoverSensitivity; + my *= m_hoverSensitivity; + } +#else + // allow mouse smoothing + if ( m_filter->integer ) { + mx = ( cl->mouseDx[0] + cl->mouseDx[1] ) * 0.5; + my = ( cl->mouseDy[0] + cl->mouseDy[1] ) * 0.5; + } else { + mx = cl->mouseDx[cl->mouseIndex]; + my = cl->mouseDy[cl->mouseIndex]; + } +#endif + + cl->mouseIndex ^= 1; + cl->mouseDx[cl->mouseIndex] = 0; + cl->mouseDy[cl->mouseIndex] = 0; + + rate = SQRTFAST( mx * mx + my * my ) / speed; + + if ( cl_mYawOverride || cl_mPitchOverride ) + {//FIXME: different people have different speed mouses, + if ( cl_mSensitivityOverride ) + { + //this will fuck things up for them, need to clamp + //max input? + accelSensitivity = cl_mSensitivityOverride; + accelSensitivityY = cl_mSensitivityOverride; + } + else + { +#ifdef _XBOX + accelSensitivity = ClientManager::ActiveClient().cg_sensitivity + rate * cl_mouseAccel->value; + accelSensitivityY = ClientManager::ActiveClient().cg_sensitivityY + rate * cl_mouseAccel->value; +#else + accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value; +#endif //_XBOX + // scale by FOV + accelSensitivity *= cl->cgameSensitivity; + accelSensitivityY *= cl->cgameSensitivity; + } + } + else + { +#ifdef _XBOX + accelSensitivity = ClientManager::ActiveClient().cg_sensitivity + rate * cl_mouseAccel->value; + accelSensitivityY = ClientManager::ActiveClient().cg_sensitivityY + rate * cl_mouseAccel->value; +#else + accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value; +#endif + // scale by FOV + accelSensitivity *= cl->cgameSensitivity; + accelSensitivityY *= cl->cgameSensitivity; + } + + + if ( rate && cl_showMouseRate->integer ) { + Com_Printf( "%f : %f\n", rate, accelSensitivity ); + } + + mx *= accelSensitivity; + +#ifdef _XBOX + my *= accelSensitivityY; +#else + my *= accelSensitivity; +#endif + + + + if (!mx && !my) { +#ifdef _XBOX + // If there was a movement but no change in angles then start auto-leveling the camera + float autolevelSpeed = 0.03f; + + // Get the current client + ClientData &ac(ClientManager::ActiveClient()); + + // Moved tests that differ in split screen up here, to make things easier to read: + bool firingNow = (ClientManager::splitScreenMode) ? + (ac.in_buttons[0].active || ac.in_buttons[7].active) : + (in_buttons[0].active || in_buttons[7].active); + + if (!ac.cg_crossHairStatus && // Not looking at an enemy + cl->joystickAxis[AXIS_FORWARD] && // Moving forward/backward + cl->snap.ps.groundEntityNum != ENTITYNUM_NONE && // Not in the air + ac.cg_autolevel && // Autolevel is turned on + !firingNow && // Not firing a weapon + ac.lastFireTime < Sys_Milliseconds() - 1000) // Haven't fired recently + { + float normAngle = -SHORT2ANGLE(cl->snap.ps.delta_angles[PITCH]); + // The adjustment to normAngle below is meant to add or remove some multiple + // of 360, so that normAngle is within 180 of viewangles[PITCH]. It should + // be correct. + int diff = (int)(cl->viewangles[PITCH] - normAngle); + if (diff > 180) + normAngle += 360.0f * ((diff+180) / 360); + else if (diff < -180) + normAngle -= 360.0f * ((-diff+180) / 360); + + if (ac.cg_thirdPerson == 1) + { +// normAngle += 10; // Removed by BTO, 2003/05/14, I hate it + autolevelSpeed *= 1.5f; + } + if (cl->viewangles[PITCH] > normAngle) + { + cl->viewangles[PITCH] -= autolevelSpeed * speed; + if (cl->viewangles[PITCH] < normAngle) cl->viewangles[PITCH] = normAngle; + } + else if (cl->viewangles[PITCH] < normAngle) + { + cl->viewangles[PITCH] += autolevelSpeed * speed; + if (cl->viewangles[PITCH] > normAngle) cl->viewangles[PITCH] = normAngle; + } + } +#endif + return; + } + + // add mouse X/Y movement to cmd +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + in_strafe.active = ClientManager::ActiveClient().in_strafe.active; + in_mlooking = ClientManager::ActiveClient().in_mlooking; + } +#endif + + if ( in_strafe.active ) { + cmd->rightmove = ClampChar( cmd->rightmove + m_side->value * mx ); + } else { + if ( cl_mYawOverride ) + { + cl->viewangles[YAW] -= cl_mYawOverride * mx; + } + else + { + cl->viewangles[YAW] -= m_yaw->value * mx; + } + } + + if ( (in_mlooking || cl_freelook->integer) && !in_strafe.active ) { + // VVFIXME - This is supposed to be a CVAR +#ifdef _XBOX + const float cl_pitchSensitivity = 0.5f; +#else + const float cl_pitchSensitivity = 1.0f; +#endif + if ( cl_mPitchOverride ) + { + if ( pitch > 0 ) + { + cl->viewangles[PITCH] += cl_mPitchOverride * my * cl_pitchSensitivity; + } + else + { + cl->viewangles[PITCH] -= cl_mPitchOverride * my * cl_pitchSensitivity; + } + } + else + { + cl->viewangles[PITCH] += pitch * my * cl_pitchSensitivity; + } + } else { + cmd->forwardmove = ClampChar( cmd->forwardmove - m_forward->value * my ); + } +} + +qboolean CL_NoUseableForce(void) +{ + if (!cgvm) + { //ahh, no cgame loaded + return qfalse; + } + + return (qboolean)VM_Call(cgvm, CG_GET_USEABLE_FORCE); +} + +/* +============== +CL_CmdButtons +============== +*/ +void CL_CmdButtons( usercmd_t *cmd ) { + int i; + + // + // figure button bits + // send a button bit even if the key was pressed and released in + // less than a frame + // +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + for (i = 0 ; i < 15 ; i++) { + if ( ClientManager::ActiveClient().in_buttons[i].active || ClientManager::ActiveClient().in_buttons[i].wasPressed ) { + cmd->buttons |= 1 << i; + } + ClientManager::ActiveClient().in_buttons[i].wasPressed = qfalse; + } + } + else + { +#endif + for (i = 0 ; i < 15 ; i++) { + if ( in_buttons[i].active || in_buttons[i].wasPressed ) { + cmd->buttons |= 1 << i; + } + in_buttons[i].wasPressed = qfalse; + } +#ifdef _XBOX + } +#endif + + if (cmd->buttons & BUTTON_FORCEPOWER) + { //check for transferring a use force to a use inventory... + if ((cmd->buttons & BUTTON_USE) || CL_NoUseableForce()) + { //it's pushed, remap it! + cmd->buttons &= ~BUTTON_FORCEPOWER; + cmd->buttons |= BUTTON_USE_HOLDABLE; + } + } + + // if this is siege make BUTTON_FORCEPOWER trigger BUTTON_USE_HOLDABLE as well + // this assumes that no player can have both items and force powers +#ifdef _XBOX + if( (cmd->buttons & BUTTON_FORCEPOWER ) && // is a force power use + (cgs.gametype == GT_SIEGE) ) // is in siege mode + { + cmd->buttons |= BUTTON_USE_HOLDABLE; // set BUTTON_USE_HOLDABLE + } +#endif + + if ( cls.keyCatchers ) { + cmd->buttons |= BUTTON_TALK; + } + + // allow the game to know if any key at all is + // currently pressed, even if it isn't bound to anything +#ifdef _XBOX + if ( ClientManager::ActiveClient().anykeydown && !cls.keyCatchers ) { +#else + if ( kg.anykeydown && !cls.keyCatchers ) { +#endif + cmd->buttons |= BUTTON_ANY; + } +} + + +/* +============== +CL_FinishMove +============== +*/ +vec3_t cl_sendAngles={0}; +vec3_t cl_lastViewAngles={0}; +void CL_FinishMove( usercmd_t *cmd ) { + int i; + + // copy the state that the cgame is currently sending + cmd->weapon = cl->cgameUserCmdValue; + cmd->forcesel = cl->cgameForceSelection; + cmd->invensel = cl->cgameInvenSelection; + + if (cl->gcmdSendValue) + { + cmd->generic_cmd = cl->gcmdValue; + //cl->gcmdSendValue = qfalse; + cl->gcmdSentValue = qtrue; + } + else + { + cmd->generic_cmd = 0; + } + + // send the current server time so the amount of movement + // can be determined without allowing cheating + cmd->serverTime = cl->serverTime; + + if (cl->cgameViewAngleForceTime > cl->serverTime) + { + cl->cgameViewAngleForce[YAW] -= SHORT2ANGLE(cl->snap.ps.delta_angles[YAW]); + + cl->viewangles[YAW] = cl->cgameViewAngleForce[YAW]; + cl->cgameViewAngleForceTime = 0; + } + + if ( cl_crazyShipControls ) + { + float pitchSubtract, pitchDelta, yawDelta; + + yawDelta = AngleSubtract(cl->viewangles[YAW],cl_lastViewAngles[YAW]); + //yawDelta *= (4.0f*pVeh->m_fTimeModifier); + cl_sendAngles[ROLL] -= yawDelta; + + float nRoll = fabs(cl_sendAngles[ROLL]); + + pitchDelta = AngleSubtract(cl->viewangles[PITCH],cl_lastViewAngles[PITCH]); + //pitchDelta *= (2.0f*pVeh->m_fTimeModifier); + pitchSubtract = pitchDelta * (nRoll/90.0f); + cl_sendAngles[PITCH] += pitchDelta-pitchSubtract; + + //yaw-roll calc should be different + if (nRoll > 90.0f) + { + nRoll -= 180.0f; + } + if (nRoll < 0.0f) + { + nRoll = -nRoll; + } + pitchSubtract = pitchDelta * (nRoll/90.0f); + if ( cl_sendAngles[ROLL] > 0.0f ) + { + cl_sendAngles[YAW] += pitchSubtract; + } + else + { + cl_sendAngles[YAW] -= pitchSubtract; + } + + cl_sendAngles[PITCH] = AngleNormalize180( cl_sendAngles[PITCH] ); + cl_sendAngles[YAW] = AngleNormalize360( cl_sendAngles[YAW] ); + cl_sendAngles[ROLL] = AngleNormalize180( cl_sendAngles[ROLL] ); + + for (i=0 ; i<3 ; i++) { + cmd->angles[i] = ANGLE2SHORT(cl_sendAngles[i]); + } + } + else + { + for (i=0 ; i<3 ; i++) { + cmd->angles[i] = ANGLE2SHORT(cl->viewangles[i]); + } + //in case we switch to the cl_crazyShipControls + VectorCopy( cl->viewangles, cl_sendAngles ); + } + //always needed in for the cl_crazyShipControls + VectorCopy( cl->viewangles, cl_lastViewAngles ); +} + +/* +================= +CL_CreateCmd +================= +*/ +usercmd_t CL_CreateCmd( void ) { + usercmd_t cmd; + vec3_t oldAngles; + + VectorCopy( cl->viewangles, oldAngles ); + + // keyboard angle adjustment + CL_AdjustAngles (); + + Com_Memset( &cmd, 0, sizeof( cmd ) ); + + CL_CmdButtons( &cmd ); + + // get basic movement from keyboard + CL_KeyMove( &cmd ); + + // get basic movement from mouse + CL_MouseMove( &cmd ); + + // get basic movement from joystick + CL_JoystickMove( &cmd ); + + // check to make sure the angles haven't wrapped + if ( cl->viewangles[PITCH] - oldAngles[PITCH] > 90 ) { + cl->viewangles[PITCH] = oldAngles[PITCH] + 90; + } else if ( oldAngles[PITCH] - cl->viewangles[PITCH] > 90 ) { + cl->viewangles[PITCH] = oldAngles[PITCH] - 90; + } + + // store out the final values + CL_FinishMove( &cmd ); + + // draw debug graphs of turning for mouse testing + if ( cl_debugMove->integer ) { + if ( cl_debugMove->integer == 1 ) { + SCR_DebugGraph( abs(cl->viewangles[YAW] - oldAngles[YAW]), 0 ); + } + if ( cl_debugMove->integer == 2 ) { + SCR_DebugGraph( abs(cl->viewangles[PITCH] - oldAngles[PITCH]), 0 ); + } + } + + return cmd; +} + + +/* +================= +CL_CreateNewCommands + +Create a new usercmd_t structure for this frame +================= +*/ +void CL_CreateNewCommands( void ) { + usercmd_t *cmd; + int cmdNum; + +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// cls.state = ClientManager::ActiveClient().state; +#endif + + // no need to create usercmds until we have a gamestate + if ( cls.state < CA_PRIMED ) { + return; + } + + frame_msec = com_frameTime - old_com_frameTime; + + // if running less than 5fps, truncate the extra time to prevent + // unexpected moves after a hitch + if ( frame_msec > 200 ) { + frame_msec = 200; + } + old_com_frameTime = com_frameTime; + + + // generate a command for this frame +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + CM_START_LOOP(); + cl->cmdNumber++; + cmdNum = cl->cmdNumber & CMD_MASK; + cl->cmds[cmdNum] = CL_CreateCmd (); + cmd = &cl->cmds[cmdNum]; + CM_END_LOOP(); + } + else + { +#endif + + cl->cmdNumber++; + cmdNum = cl->cmdNumber & CMD_MASK; + cl->cmds[cmdNum] = CL_CreateCmd (); + cmd = &cl->cmds[cmdNum]; + +#ifdef _XBOX + } +#endif +} + +/* +================= +CL_ReadyToSendPacket + +Returns qfalse if we are over the maxpackets limit +and should choke back the bandwidth a bit by not sending +a packet this frame. All the commands will still get +delivered in the next packet, but saving a header and +getting more delta compression will reduce total bandwidth. +================= +*/ +qboolean CL_ReadyToSendPacket( void ) { + int oldPacketNum; + int delta; + + // don't send anything if playing back a demo +#ifdef _XBOX // No demos on Xbox +// if(ClientManager::splitScreenMode == qtrue) +// { +// if (ClientManager::ActiveClient().state == CA_CINEMATIC ) { +// return qfalse; +// } +// } +// else + if ( cls.state == CA_CINEMATIC ) { +#else + if ( clc->demoplaying || cls.state == CA_CINEMATIC ) { +#endif + return qfalse; + } + + // If we are downloading, we send no less than 50ms between packets +#ifndef _XBOX // No downloads on Xbox + if ( *clc->downloadTempName && + cls.realtime - clc->lastPacketSentTime < 50 ) { + return qfalse; + } +#endif + + // if we don't have a valid gamestate yet, only send + // one packet a second +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// { +// if ( ClientManager::ActiveClient().state != CA_ACTIVE && +// ClientManager::ActiveClient().state != CA_PRIMED && +// cls.realtime - clc->lastPacketSentTime < 1000 ) { +// return qfalse; +// } +// } +// else +// { +#endif + if ( cls.state != CA_ACTIVE && + cls.state != CA_PRIMED && + cls.realtime - clc->lastPacketSentTime < 1000 ) { + return qfalse; + } +#ifdef _XBOX +// } +#endif + + // send every frame for loopbacks + if ( clc->netchan.remoteAddress.type == NA_LOOPBACK ) { + return qtrue; + } + + // send every frame for LAN + if ( Sys_IsLANAddress( clc->netchan.remoteAddress ) ) { + return qtrue; + } + + // check for exceeding cl_maxpackets + if ( cl_maxpackets->integer < 15 ) { + Cvar_Set( "cl_maxpackets", "15" ); + } else if ( cl_maxpackets->integer > 100 ) { + Cvar_Set( "cl_maxpackets", "100" ); + } + oldPacketNum = (clc->netchan.outgoingSequence - 1) & PACKET_MASK; + delta = cls.realtime - cl->outPackets[ oldPacketNum ].p_realtime; + if ( delta < 1000 / cl_maxpackets->integer ) { + // the accumulated commands will go out in the next packet + return qfalse; + } + + return qtrue; +} + +/* +=================== +CL_WritePacket + +Create and send the command packet to the server +Including both the reliable commands and the usercmds + +During normal gameplay, a client packet will contain something like: + +4 sequence number +2 qport +4 serverid +4 acknowledged sequence number +4 clc->serverCommandSequence + +1 clc_move or clc_moveNoDelta +1 command count + + +=================== +*/ +void CL_WritePacket( void ) { + msg_t buf; + byte data[MAX_MSGLEN]; + int i, j; + usercmd_t *cmd, *oldcmd; + usercmd_t nullcmd; + int packetNum; + int oldPacketNum; + int count, key; + + // don't send anything if playing back a demo +#ifdef _XBOX // No demos on Xbox +// if(ClientManager::splitScreenMode == qtrue) +// { +// if(ClientManager::ActiveClient().state == CA_CINEMATIC) +// { +// return; +// } +// } +// else + if ( cls.state == CA_CINEMATIC ) { +#else + if ( clc->demoplaying || cls.state == CA_CINEMATIC ) { +#endif + return; + } + + Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); + oldcmd = &nullcmd; + + MSG_Init( &buf, data, sizeof(data) ); + + MSG_Bitstream( &buf ); + // write the current serverId so the server + // can tell if this is from the current gameState + MSG_WriteLong( &buf, cl->serverId ); + + // write the last message we received, which can + // be used for delta compression, and is also used + // to tell if we dropped a gamestate + MSG_WriteLong( &buf, clc->serverMessageSequence ); + + // write the last reliable message we received + MSG_WriteLong( &buf, clc->serverCommandSequence ); + + // write any unacknowledged clientCommands + for ( i = clc->reliableAcknowledge + 1 ; i <= clc->reliableSequence ; i++ ) { + MSG_WriteByte( &buf, clc_clientCommand ); + MSG_WriteLong( &buf, i ); + MSG_WriteString( &buf, clc->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); + } + + // we want to send all the usercmds that were generated in the last + // few packet, so even if a couple packets are dropped in a row, + // all the cmds will make it to the server + if ( cl_packetdup->integer < 0 ) { + Cvar_Set( "cl_packetdup", "0" ); + } else if ( cl_packetdup->integer > 5 ) { + Cvar_Set( "cl_packetdup", "5" ); + } + oldPacketNum = (clc->netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; + count = cl->cmdNumber - cl->outPackets[ oldPacketNum ].p_cmdNumber; + if ( count > MAX_PACKET_USERCMDS ) { + count = MAX_PACKET_USERCMDS; + Com_Printf("MAX_PACKET_USERCMDS\n"); + } + if ( count >= 1 ) { + if ( cl_showSend->integer ) { + Com_Printf( "(%i)", count ); + } + + // begin a client move command + if ( cl_nodelta->integer || !cl->snap.valid +#ifndef _XBOX // No demos on Xbox + || clc->demowaiting +#endif + || clc->serverMessageSequence != cl->snap.messageNum ) { + MSG_WriteByte (&buf, clc_moveNoDelta); + } else { + MSG_WriteByte (&buf, clc_move); + } + + // write the command count + MSG_WriteByte( &buf, count ); + + // use the checksum feed in the key + key = clc->checksumFeed; + // also use the message acknowledge + key ^= clc->serverMessageSequence; + // also use the last acknowledged server command in the key + key ^= Com_HashKey(clc->serverCommands[ clc->serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32); + + // write all the commands, including the predicted command + for ( i = 0 ; i < count ; i++ ) { + j = (cl->cmdNumber - count + i + 1) & CMD_MASK; + cmd = &cl->cmds[j]; + MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd); + oldcmd = cmd; + } + + if (cl->gcmdSentValue) + { //hmm, just clear here, I guess.. hoping it will resolve issues with gencmd values sometimes not going through. + cl->gcmdSendValue = qfalse; + cl->gcmdSentValue = qfalse; + cl->gcmdValue = 0; + } + } + + // + // deliver the message + // + packetNum = clc->netchan.outgoingSequence & PACKET_MASK; + cl->outPackets[ packetNum ].p_realtime = cls.realtime; + cl->outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; + cl->outPackets[ packetNum ].p_cmdNumber = cl->cmdNumber; + clc->lastPacketSentTime = cls.realtime; + + if ( cl_showSend->integer ) { + Com_Printf( "%i ", buf.cursize ); + } + + CL_Netchan_Transmit (&clc->netchan, &buf); + + // clients never really should have messages large enough + // to fragment, but in case they do, fire them all off + // at once + while ( clc->netchan.unsentFragments ) { + CL_Netchan_TransmitNextFragment( &clc->netchan ); + } +} + +/* +================= +CL_SendCmd + +Called every frame to builds and sends a command packet to the server. +================= +*/ +void CL_SendCmd( void ) { + // don't send any message if not connected +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// { +// if(ClientManager::ActiveClient().state < CA_CONNECTED) +// { +// return; +// } +// } +// else +// { +#endif + if ( cls.state < CA_CONNECTED ) { + return; + } +#ifdef _XBOX +// } +#endif + + // don't send commands if paused + if ( com_sv_running->integer && sv_paused->integer && cl_paused->integer ) { + return; + } + + // we create commands even if a demo is playing, + CL_CreateNewCommands(); + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + CM_START_LOOP(); + // don't send a packet if the last packet was sent too recently + if ( !CL_ReadyToSendPacket() ) { + if ( cl_showSend->integer ) { + Com_Printf( ". " ); + } + continue; + } + + CL_WritePacket(); + CM_END_LOOP(); + } + else + { +#endif + // don't send a packet if the last packet was sent too recently + if ( !CL_ReadyToSendPacket() ) { + if ( cl_showSend->integer ) { + Com_Printf( ". " ); + } + return; + } + + CL_WritePacket(); + +#ifdef _XBOX + } +#endif +} + + +/* +============ +CL_InitInput +============ +*/ +void CL_InitInput( void ) { + Cmd_AddCommand ("centerview",IN_CenterView); + + //Cmd_AddCommand ("+taunt", IN_Button3Down);//gesture + //Cmd_AddCommand ("-taunt", IN_Button3Up); + Cmd_AddCommand ("+moveup",IN_UpDown); + Cmd_AddCommand ("-moveup",IN_UpUp); + Cmd_AddCommand ("+movedown",IN_DownDown); + Cmd_AddCommand ("-movedown",IN_DownUp); + Cmd_AddCommand ("+left",IN_LeftDown); + Cmd_AddCommand ("-left",IN_LeftUp); + Cmd_AddCommand ("+right",IN_RightDown); + Cmd_AddCommand ("-right",IN_RightUp); + Cmd_AddCommand ("+forward",IN_ForwardDown); + Cmd_AddCommand ("-forward",IN_ForwardUp); + Cmd_AddCommand ("+back",IN_BackDown); + Cmd_AddCommand ("-back",IN_BackUp); + Cmd_AddCommand ("+lookup", IN_LookupDown); + Cmd_AddCommand ("-lookup", IN_LookupUp); + Cmd_AddCommand ("+lookdown", IN_LookdownDown); + Cmd_AddCommand ("-lookdown", IN_LookdownUp); + Cmd_AddCommand ("+strafe", IN_StrafeDown); + Cmd_AddCommand ("-strafe", IN_StrafeUp); + Cmd_AddCommand ("+moveleft", IN_MoveleftDown); + Cmd_AddCommand ("-moveleft", IN_MoveleftUp); + Cmd_AddCommand ("+moveright", IN_MoverightDown); + Cmd_AddCommand ("-moveright", IN_MoverightUp); + Cmd_AddCommand ("+speed", IN_SpeedDown); + Cmd_AddCommand ("-speed", IN_SpeedUp); + Cmd_AddCommand ("+attack", IN_Button0Down); + Cmd_AddCommand ("-attack", IN_Button0Up); + //Cmd_AddCommand ("+force_jump", IN_Button1Down);//force jump + //Cmd_AddCommand ("-force_jump", IN_Button1Up); + Cmd_AddCommand ("+use", IN_Button5Down); + Cmd_AddCommand ("-use", IN_Button5Up); + Cmd_AddCommand ("+force_grip", IN_Button6Down);//force grip + Cmd_AddCommand ("-force_grip", IN_Button6Up); + Cmd_AddCommand ("+altattack", IN_Button7Down);//altattack + Cmd_AddCommand ("-altattack", IN_Button7Up); + Cmd_AddCommand ("+useforce", IN_Button9Down);//active force power + Cmd_AddCommand ("-useforce", IN_Button9Up); + Cmd_AddCommand ("+force_lightning", IN_Button10Down);//active force power + Cmd_AddCommand ("-force_lightning", IN_Button10Up); + Cmd_AddCommand ("+force_drain", IN_Button11Down);//active force power + Cmd_AddCommand ("-force_drain", IN_Button11Up); + //buttons + Cmd_AddCommand ("+button0", IN_Button0Down);//attack + Cmd_AddCommand ("-button0", IN_Button0Up); + Cmd_AddCommand ("+button1", IN_Button1Down);//force jump + Cmd_AddCommand ("-button1", IN_Button1Up); + Cmd_AddCommand ("+button2", IN_Button2Down);//use holdable (not used - change to use jedi power?) + Cmd_AddCommand ("-button2", IN_Button2Up); + Cmd_AddCommand ("+button3", IN_Button3Down);//gesture + Cmd_AddCommand ("-button3", IN_Button3Up); + Cmd_AddCommand ("+button4", IN_Button4Down);//walking + Cmd_AddCommand ("-button4", IN_Button4Up); + Cmd_AddCommand ("+button5", IN_Button5Down);//use object + Cmd_AddCommand ("-button5", IN_Button5Up); + Cmd_AddCommand ("+button6", IN_Button6Down);//force grip + Cmd_AddCommand ("-button6", IN_Button6Up); + Cmd_AddCommand ("+button7", IN_Button7Down);//altattack + Cmd_AddCommand ("-button7", IN_Button7Up); + Cmd_AddCommand ("+button8", IN_Button8Down); + Cmd_AddCommand ("-button8", IN_Button8Up); + Cmd_AddCommand ("+button9", IN_Button9Down);//active force power + Cmd_AddCommand ("-button9", IN_Button9Up); + Cmd_AddCommand ("+button10", IN_Button10Down);//force lightning + Cmd_AddCommand ("-button10", IN_Button10Up); + Cmd_AddCommand ("+button11", IN_Button11Down);//force drain + Cmd_AddCommand ("-button11", IN_Button11Up); + Cmd_AddCommand ("+button12", IN_Button12Down); + Cmd_AddCommand ("-button12", IN_Button12Up); + Cmd_AddCommand ("+button13", IN_Button13Down); + Cmd_AddCommand ("-button13", IN_Button13Up); + Cmd_AddCommand ("+button14", IN_Button14Down); + Cmd_AddCommand ("-button14", IN_Button14Up); + Cmd_AddCommand ("+mlook", IN_MLookDown); + Cmd_AddCommand ("-mlook", IN_MLookUp); + + Cmd_AddCommand ("sv_saberswitch", IN_GenCMD1); + Cmd_AddCommand ("engage_duel", IN_GenCMD2); + Cmd_AddCommand ("force_heal", IN_GenCMD3); + Cmd_AddCommand ("force_speed", IN_GenCMD4); + Cmd_AddCommand ("force_pull", IN_GenCMD5); + Cmd_AddCommand ("force_distract", IN_GenCMD6); + Cmd_AddCommand ("force_rage", IN_GenCMD7); + Cmd_AddCommand ("force_protect", IN_GenCMD8); + Cmd_AddCommand ("force_absorb", IN_GenCMD9); + Cmd_AddCommand ("force_healother", IN_GenCMD10); + Cmd_AddCommand ("force_forcepowerother", IN_GenCMD11); + Cmd_AddCommand ("force_seeing", IN_GenCMD12); + Cmd_AddCommand ("use_seeker", IN_GenCMD13); + Cmd_AddCommand ("use_field", IN_GenCMD14); + Cmd_AddCommand ("use_bacta", IN_GenCMD15); + Cmd_AddCommand ("use_electrobinoculars", IN_GenCMD16); + Cmd_AddCommand ("zoom", IN_GenCMD17); + Cmd_AddCommand ("use_sentry", IN_GenCMD18); + Cmd_AddCommand ("use_jetpack", IN_GenCMD21); + Cmd_AddCommand ("use_bactabig", IN_GenCMD22); + Cmd_AddCommand ("use_healthdisp", IN_GenCMD23); + Cmd_AddCommand ("use_ammodisp", IN_GenCMD24); + Cmd_AddCommand ("use_eweb", IN_GenCMD25); + Cmd_AddCommand ("use_cloak", IN_GenCMD26); + Cmd_AddCommand ("taunt", IN_GenCMD27); + Cmd_AddCommand ("bow", IN_GenCMD28); + Cmd_AddCommand ("meditate", IN_GenCMD29); + Cmd_AddCommand ("flourish", IN_GenCMD30); + Cmd_AddCommand ("gloat", IN_GenCMD31); + Cmd_AddCommand ("saberAttackCycle", IN_GenCMD19); + Cmd_AddCommand ("force_throw", IN_GenCMD20); +#ifdef _XBOX + Cmd_AddCommand ("+hotswap1", IN_HotSwap1On); + Cmd_AddCommand ("+hotswap2", IN_HotSwap2On); + Cmd_AddCommand ("-hotswap1", IN_HotSwap1Off); + Cmd_AddCommand ("-hotswap2", IN_HotSwap2Off); + + Cmd_AddCommand ("+voicetoggle", IN_VoiceToggleDown); + Cmd_AddCommand ("-voicetoggle", IN_VoiceToggleUp); +#endif + Cmd_AddCommand ("useGivenForce", IN_UseGivenForce); + + + Cmd_AddCommand("automap_button", IN_AutoMapButton); + Cmd_AddCommand("automap_toggle", IN_AutoMapToggle); + Cmd_AddCommand("voicechat", IN_VoiceChatButton); + + cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0); + cl_debugMove = Cvar_Get ("cl_debugMove", "0", 0); +} diff --git a/codemp/client/cl_input_hotswap.cpp b/codemp/client/cl_input_hotswap.cpp new file mode 100644 index 0000000..6af613b --- /dev/null +++ b/codemp/client/cl_input_hotswap.cpp @@ -0,0 +1,258 @@ +/* + TODO: finalize item support + + 1) Make ItemSelectUp() work. + 2) Change cg->itemSelect to whatever var is used to store selected item. + 3) Make sure commands in itemCommands work in both multi & single player. +*/ + +#include "client.h" +#include "../cgame/cg_local.h" + +#include "cl_input_hotswap.h" +#include "cl_data.h" +#include "../qcommon/xb_settings.h" + +#define FORCESELECTTIME forceSelectTime +#define FORCESELECT forceSelect +#define INVSELECTTIME invenSelectTime +#define INVSELECT itemSelect +#define REGISTERSOUND S_RegisterSound +#define STARTSOUND S_StartLocalSound +#define WEAPONBINDSTR "weaponclean" + +#define BIND_TIME 2000 //number of milliseconds button is held before binding +#define EXEC_TIME 500 //max ms button can be held to execute in bind mode + + +const char *itemCommands[HI_NUM_HOLDABLE] = { + NULL, //HI_NONE + "use_seeker\n", + "use_field\n", + "use_bacta\n", + "use_bactabig\n", + "use_electrobinoculars\n", + "use_sentry\n", + "use_jetpack\n", + NULL, //ammo dispenser + NULL, //health dispenser + "use_eweb\n", + "use_cloak\n", +}; + +// Commands to issue when user presses a force-bound button +const char *forceDownCommands[NUM_FORCE_POWERS] = { + "force_heal\n", // FP_HEAL + NULL, // FP_LEVITATION + "force_speed\n", // FP_SPEED + "force_throw\n", // FP_PUSH + "force_pull\n", // FP_PULL + "force_distract\n", // FP_TELEPATHY + "+force_grip\n", // FP_GRIP + "+force_lightning\n", // FP_LIGHTNING + "force_rage\n", // FP_RAGE + "force_protect\n", // FP_PROTECT + "force_absorb\n", // FP_ABSORB + "force_healother\n", // FP_TEAM_HEAL + "force_forcepowerother\n", // FP_TEAM_FORCE + "+force_drain\n", // FP_DRAIN + "force_seeing\n", // FP_SEE + NULL, // FP_SABER_OFFENSE + NULL, // FP_SABER_DEFENSE + NULL, // FP_SABERTHROW +}; + +// Commands to issue when user releases a force-bound button +const char *forceUpCommands[NUM_FORCE_POWERS] = { + NULL, // FP_HEAL + NULL, // FP_LEVITATION + NULL, // FP_SPEED + NULL, // FP_PUSH + NULL, // FP_PULL + NULL, // FP_TELEPATHY + "-force_grip\n", // FP_GRIP + "-force_lightning\n", // FP_LIGHTNING + NULL, // FP_RAGE + NULL, // FP_PROTECT + NULL, // FP_ABSORB + NULL, // FP_TEAM_HEAL + NULL, // FP_TEAM_FORCE + "-force_drain\n", // FP_DRAIN + NULL, // FP_SEE + NULL, // FP_SABER_OFFENSE + NULL, // FP_SABER_DEFENSE + NULL, // FP_SABERTHROW +}; + + +HotSwapManager::HotSwapManager(int uniqueID) : + uniqueID(uniqueID) +{ + Reset(); +} + + +const char *HotSwapManager::GetBinding(void) +{ + char buf[64]; + + // Need to use unique variables for each client in split screen: + sprintf(buf, "hotswap%d", uniqueID+(ClientManager::ActiveClientNum()*4)); + cvar_t *cvar = Cvar_Get(buf, "", CVAR_ARCHIVE); + + if(!cvar || !cvar->string[0]) + return NULL; + + if (cvar->integer < HOTSWAP_CAT_ITEM) { // Weapon + return va("weaponclean %d", cvar->integer); + } else if (cvar->integer < HOTSWAP_CAT_FORCE) { // Item + return itemCommands[cvar->integer - HOTSWAP_CAT_ITEM]; + } else { // Force power + return forceDownCommands[cvar->integer - HOTSWAP_CAT_FORCE]; + } +} + +const char *HotSwapManager::GetBindingUp(void) +{ + char buf[64]; + + sprintf(buf, "hotswap%d", uniqueID+(ClientManager::ActiveClientNum()*4)); + cvar_t *cvar = Cvar_Get(buf, "", CVAR_ARCHIVE); + + if(!cvar || !cvar->string[0]) + return NULL; + + // Only force powers have release-commands + if (cvar->integer < HOTSWAP_CAT_FORCE) { + return NULL; + } else { + return forceUpCommands[cvar->integer - HOTSWAP_CAT_FORCE]; + } +} + + +void HotSwapManager::Bind(void) +{ + if(WeaponSelectUp()) { + HotSwapBind(uniqueID, HOTSWAP_CAT_WEAPON, cg->weaponSelect); + } else if(ForceSelectUp()) { + HotSwapBind(uniqueID, HOTSWAP_CAT_FORCE, cg->FORCESELECT); + } else if(ItemSelectUp()) { + HotSwapBind(uniqueID, HOTSWAP_CAT_ITEM, cg->INVSELECT); + } else{ + assert(0); + } + + noBind = true; + STARTSOUND(REGISTERSOUND("sound/interface/update"), 0); +} + + +bool HotSwapManager::ForceSelectUp(void) +{ + return cg->FORCESELECTTIME != 0 && + (cg->FORCESELECTTIME + WEAPON_SELECT_TIME >= cg->time); +} + + +bool HotSwapManager::WeaponSelectUp(void) +{ + return cg->weaponSelectTime != 0 && + (cg->weaponSelectTime + WEAPON_SELECT_TIME >= cg->time); +} + + +bool HotSwapManager::ItemSelectUp(void) +{ + return cg->INVSELECTTIME != 0 && + (cg->INVSELECTTIME + WEAPON_SELECT_TIME >= cg->time); +} + + +bool HotSwapManager::HUDInBindState(void) +{ + return ForceSelectUp() || WeaponSelectUp() || ItemSelectUp(); +} + + +void HotSwapManager::Update(void) +{ + if(down) { + //Increment bindTime only if HUD is in select mode. + if(HUDInBindState()) { + bindTime += cls.frametime; + } else { + + //Clear bind time. + bindTime = 0; + + } + } + + //Down long enough, bind button. + if(!noBind && bindTime >= BIND_TIME) { + Bind(); + } +} + + +void HotSwapManager::Execute(void) +{ + const char *binding = GetBinding(); + if(binding) { + Cbuf_ExecuteText(EXEC_APPEND, binding); + } +} + +void HotSwapManager::ExecuteUp(void) +{ + const char *binding = GetBindingUp(); + if(binding) { + Cbuf_ExecuteText(EXEC_APPEND, binding); + } +} + + +void HotSwapManager::SetDown(void) +{ + //Set the down flag. + down = true; + + //Execute the bind if the HUD isn't up. Also, prevent re-binding! + if(!HUDInBindState()) { + Execute(); + noBind = true; + } +} + + +void HotSwapManager::SetUp(void) +{ + // Execute the tail of the command if the HUD isn't up. + if(!HUDInBindState() || noBind) { + ExecuteUp(); + } + + Reset(); +} + + +void HotSwapManager::Reset(void) +{ + down = false; + bindTime = 0; + noBind = false; +} + +void HotSwapBind(int buttonID, int category, int value) +{ + char buf[64]; + sprintf(buf, "hotswap%d", buttonID+(ClientManager::ActiveClientNum()*4)); + + // Add category as an offset for when we retrieve it + Cvar_SetValue( buf, value+category ); + Settings.hotswapMP[buttonID+(ClientManager::ActiveClientNum()*2)] = value+category; + + Settings.Save(); +} + diff --git a/codemp/client/cl_input_hotswap.h b/codemp/client/cl_input_hotswap.h new file mode 100644 index 0000000..6dc2c1d --- /dev/null +++ b/codemp/client/cl_input_hotswap.h @@ -0,0 +1,63 @@ +#ifndef __CL_INPUT_HOTSWAP_H +#define __CL_INPUT_HOTSWAP_H + + +#define HOTSWAP_ID_WHITE 0 +#define HOTSWAP_ID_BLACK 1 + +#define HOTSWAP_CAT_WEAPON 0 +#define HOTSWAP_CAT_ITEM 1024 +#define HOTSWAP_CAT_FORCE 2048 + + +class HotSwapManager +{ +private: + bool down; //Is the button down? + bool noBind; //Don't bind the button. + int bindTime; //How long the button has been down with the selection up. + int uniqueID; //Unique ID for this button. + + //Return the binding for the button, or NULL if none. + const char *GetBinding(void); + const char *GetBindingUp(void); + + //Returns true if the weapon/force/item select screen is up. + bool HUDInBindState(void); + + //Returns true if the weapon/force/item select screen is up. + bool ForceSelectUp(void); + bool WeaponSelectUp(void); + bool ItemSelectUp(void); + + //Binds the button based on the current HUD selection. + void Bind(void); + + //Execute the current bind, if there is one. + void Execute(void); + void ExecuteUp(void); + + //Reset the object to the default state. + void Reset(void); + +public: + HotSwapManager(int uniqueID); + + //Call every frame. Uses cg.frametime to increment timers. + void Update(void); + + //Set the button down or up. + void SetDown(void); + void SetUp(void); + + //Returns true if the button is currently down. + bool ButtonDown(void) { return down; } +}; + + +//External bind function for sharing with UI. +extern void HotSwapBind(int buttonID, int category, int value); + + + +#endif diff --git a/codemp/client/cl_keys.cpp b/codemp/client/cl_keys.cpp new file mode 100644 index 0000000..8da7ec5 --- /dev/null +++ b/codemp/client/cl_keys.cpp @@ -0,0 +1,1738 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "client.h" +#include "../qcommon/stringed_ingame.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "cl_data.h" +#endif +/* + +key up events are sent even if in console mode + +*/ + +field_t chatField; +qboolean chat_team; + +int chat_playerNum; + +keyGlobals_t kg; + +// do NOT blithely change any of the key names (3rd field) here, since they have to match the key binds +// in the CFG files, they're also prepended with "KEYNAME_" when looking up StringEd references +// +keyname_t keynames[MAX_KEYS] = +{ + { 0x00, 0x00, NULL, A_NULL, false }, + { 0x01, 0x01, "SHIFT", A_SHIFT, false }, + { 0x02, 0x02, "CTRL", A_CTRL, false }, + { 0x03, 0x03, "ALT", A_ALT, false }, + { 0x04, 0x04, "CAPSLOCK", A_CAPSLOCK, false }, + { 0x05, 0x05, "KP_NUMLOCK", A_NUMLOCK, false }, + { 0x06, 0x06, "SCROLLLOCK", A_SCROLLLOCK, false }, + { 0x07, 0x07, "PAUSE", A_PAUSE, false }, + { 0x08, 0x08, "BACKSPACE", A_BACKSPACE, false }, + { 0x09, 0x09, "TAB", A_TAB, false }, + { 0x0a, 0x0a, "ENTER", A_ENTER, false }, + { 0x0b, 0x0b, "KP_PLUS", A_KP_PLUS, false }, + { 0x0c, 0x0c, "KP_MINUS", A_KP_MINUS, false }, + { 0x0d, 0x0d, "KP_ENTER", A_KP_ENTER, false }, + { 0x0e, 0x0e, "KP_DEL", A_KP_PERIOD, false }, + { 0x0f, 0x0f, NULL, A_PRINTSCREEN, false }, + { 0x10, 0x10, "KP_INS", A_KP_0, false }, + { 0x11, 0x11, "KP_END", A_KP_1, false }, + { 0x12, 0x12, "KP_DOWNARROW", A_KP_2, false }, + { 0x13, 0x13, "KP_PGDN", A_KP_3, false }, + { 0x14, 0x14, "KP_LEFTARROW", A_KP_4, false }, + { 0x15, 0x15, "KP_5", A_KP_5, false }, + { 0x16, 0x16, "KP_RIGHTARROW", A_KP_6, false }, + { 0x17, 0x17, "KP_HOME", A_KP_7, false }, + { 0x18, 0x18, "KP_UPARROW", A_KP_8, false }, + { 0x19, 0x19, "KP_PGUP", A_KP_9, false }, + { 0x1a, 0x1a, "CONSOLE", A_CONSOLE, false }, + { 0x1b, 0x1b, "ESCAPE", A_ESCAPE, false }, + { 0x1c, 0x1c, "F1", A_F1, true }, + { 0x1d, 0x1d, "F2", A_F2, true }, + { 0x1e, 0x1e, "F3", A_F3, true }, + { 0x1f, 0x1f, "F4", A_F4, true }, + + { 0x20, 0x20, "SPACE", A_SPACE, false }, + { (word)'!', (word)'!', NULL, A_PLING, false }, + { (word)'"', (word)'"', NULL, A_DOUBLE_QUOTE, false }, + { (word)'#', (word)'#', NULL, A_HASH, false }, + { (word)'$', (word)'$', NULL, A_STRING, false }, + { (word)'%', (word)'%', NULL, A_PERCENT, false }, + { (word)'&', (word)'&', NULL, A_AND, false }, + { 0x27, 0x27, NULL, A_SINGLE_QUOTE, false }, + { (word)'(', (word)'(', NULL, A_OPEN_BRACKET, false }, + { (word)')', (word)')', NULL, A_CLOSE_BRACKET, false }, + { (word)'*', (word)'*', NULL, A_STAR, false }, + { (word)'+', (word)'+', NULL, A_PLUS, false }, + { (word)',', (word)',', NULL, A_COMMA, false }, + { (word)'-', (word)'-', NULL, A_MINUS, false }, + { (word)'.', (word)'.', NULL, A_PERIOD, false }, + { (word)'/', (word)'/', NULL, A_FORWARD_SLASH, false }, + { (word)'0', (word)'0', NULL, A_0, false }, + { (word)'1', (word)'1', NULL, A_1, false }, + { (word)'2', (word)'2', NULL, A_2, false }, + { (word)'3', (word)'3', NULL, A_3, false }, + { (word)'4', (word)'4', NULL, A_4, false }, + { (word)'5', (word)'5', NULL, A_5, false }, + { (word)'6', (word)'6', NULL, A_6, false }, + { (word)'7', (word)'7', NULL, A_7, false }, + { (word)'8', (word)'8', NULL, A_8, false }, + { (word)'9', (word)'9', NULL, A_9, false }, + { (word)':', (word)':', NULL, A_COLON, false }, + { (word)';', (word)';', "SEMICOLON", A_SEMICOLON, false }, + { (word)'<', (word)'<', NULL, A_LESSTHAN, false }, + { (word)'=', (word)'=', NULL, A_EQUALS, false }, + { (word)'>', (word)'>', NULL, A_GREATERTHAN, false }, + { (word)'?', (word)'?', NULL, A_QUESTION, false }, + + { (word)'@', (word)'@', NULL, A_AT, false }, + { (word)'A', (word)'a', NULL, A_CAP_A, false }, + { (word)'B', (word)'b', NULL, A_CAP_B, false }, + { (word)'C', (word)'c', NULL, A_CAP_C, false }, + { (word)'D', (word)'d', NULL, A_CAP_D, false }, + { (word)'E', (word)'e', NULL, A_CAP_E, false }, + { (word)'F', (word)'f', NULL, A_CAP_F, false }, + { (word)'G', (word)'g', NULL, A_CAP_G, false }, + { (word)'H', (word)'h', NULL, A_CAP_H, false }, + { (word)'I', (word)'i', NULL, A_CAP_I, false }, + { (word)'J', (word)'j', NULL, A_CAP_J, false }, + { (word)'K', (word)'k', NULL, A_CAP_K, false }, + { (word)'L', (word)'l', NULL, A_CAP_L, false }, + { (word)'M', (word)'m', NULL, A_CAP_M, false }, + { (word)'N', (word)'n', NULL, A_CAP_N, false }, + { (word)'O', (word)'o', NULL, A_CAP_O, false }, + { (word)'P', (word)'p', NULL, A_CAP_P, false }, + { (word)'Q', (word)'q', NULL, A_CAP_Q, false }, + { (word)'R', (word)'r', NULL, A_CAP_R, false }, + { (word)'S', (word)'s', NULL, A_CAP_S, false }, + { (word)'T', (word)'t', NULL, A_CAP_T, false }, + { (word)'U', (word)'u', NULL, A_CAP_U, false }, + { (word)'V', (word)'v', NULL, A_CAP_V, false }, + { (word)'W', (word)'w', NULL, A_CAP_W, false }, + { (word)'X', (word)'x', NULL, A_CAP_X, false }, + { (word)'Y', (word)'y', NULL, A_CAP_Y, false }, + { (word)'Z', (word)'z', NULL, A_CAP_Z, false }, + { (word)'[', (word)'[', NULL, A_OPEN_SQUARE, false }, + { 0x5c, 0x5c, NULL, A_BACKSLASH, false }, + { (word)']', (word)']', NULL, A_CLOSE_SQUARE, false }, + { (word)'^', (word)'^', NULL, A_CARET, false }, + { (word)'_', (word)'_', NULL, A_UNDERSCORE, false }, + + { 0x60, 0x60, NULL, A_LEFT_SINGLE_QUOTE, false }, + { (word)'A', (word)'a', NULL, A_LOW_A, false }, + { (word)'B', (word)'b', NULL, A_LOW_B, false }, + { (word)'C', (word)'c', NULL, A_LOW_C, false }, + { (word)'D', (word)'d', NULL, A_LOW_D, false }, + { (word)'E', (word)'e', NULL, A_LOW_E, false }, + { (word)'F', (word)'f', NULL, A_LOW_F, false }, + { (word)'G', (word)'g', NULL, A_LOW_G, false }, + { (word)'H', (word)'h', NULL, A_LOW_H, false }, + { (word)'I', (word)'i', NULL, A_LOW_I, false }, + { (word)'J', (word)'j', NULL, A_LOW_J, false }, + { (word)'K', (word)'k', NULL, A_LOW_K, false }, + { (word)'L', (word)'l', NULL, A_LOW_L, false }, + { (word)'M', (word)'m', NULL, A_LOW_M, false }, + { (word)'N', (word)'n', NULL, A_LOW_N, false }, + { (word)'O', (word)'o', NULL, A_LOW_O, false }, + { (word)'P', (word)'p', NULL, A_LOW_P, false }, + { (word)'Q', (word)'q', NULL, A_LOW_Q, false }, + { (word)'R', (word)'r', NULL, A_LOW_R, false }, + { (word)'S', (word)'s', NULL, A_LOW_S, false }, + { (word)'T', (word)'t', NULL, A_LOW_T, false }, + { (word)'U', (word)'u', NULL, A_LOW_U, false }, + { (word)'V', (word)'v', NULL, A_LOW_V, false }, + { (word)'W', (word)'w', NULL, A_LOW_W, false }, + { (word)'X', (word)'x', NULL, A_LOW_X, false }, + { (word)'Y', (word)'y', NULL, A_LOW_Y, false }, + { (word)'Z', (word)'z', NULL, A_LOW_Z, false }, + { (word)'{', (word)'{', NULL, A_OPEN_BRACE, false }, + { (word)'|', (word)'|', NULL, A_BAR, false }, + { (word)'}', (word)'}', NULL, A_CLOSE_BRACE, false }, + { (word)'~', (word)'~', NULL, A_TILDE, false }, + { 0x7f, 0x7f, "DEL", A_DELETE, false }, + + { 0x80, 0x80, "EURO", A_EURO, false }, + { 0x81, 0x81, "SHIFT", A_SHIFT2, false }, + { 0x82, 0x82, "CTRL", A_CTRL2, false }, + { 0x83, 0x83, "ALT", A_ALT2, false }, + { 0x84, 0x84, "F5", A_F5, true }, + { 0x85, 0x85, "F6", A_F6, true }, + { 0x86, 0x86, "F7", A_F7, true }, + { 0x87, 0x87, "F8", A_F8, true }, + { 0x88, 0x88, "CIRCUMFLEX", A_CIRCUMFLEX, false }, + { 0x89, 0x89, "MWHEELUP", A_MWHEELUP, false }, + { 0x8a, 0x9a, NULL, A_CAP_SCARON, false }, // ****** + { 0x8b, 0x8b, "MWHEELDOWN", A_MWHEELDOWN, false }, + { 0x8c, 0x9c, NULL, A_CAP_OE, false }, // ****** + { 0x8d, 0x8d, "MOUSE1", A_MOUSE1, false }, + { 0x8e, 0x8e, "MOUSE2", A_MOUSE2, false }, + { 0x8f, 0x8f, "INS", A_INSERT, false }, + { 0x90, 0x90, "HOME", A_HOME, false }, + { 0x91, 0x91, "PGUP", A_PAGE_UP, false }, + { 0x92, 0x92, NULL, A_RIGHT_SINGLE_QUOTE, false }, + { 0x93, 0x93, NULL, A_LEFT_DOUBLE_QUOTE, false }, + { 0x94, 0x94, NULL, A_RIGHT_DOUBLE_QUOTE, false }, + { 0x95, 0x95, "F9", A_F9, true }, + { 0x96, 0x96, "F10", A_F10, true }, + { 0x97, 0x97, "F11", A_F11, true }, + { 0x98, 0x98, "F12", A_F12, true }, + { 0x99, 0x99, NULL, A_TRADEMARK, false }, + { 0x8a, 0x9a, NULL, A_LOW_SCARON, false }, // ****** + { 0x9b, 0x9b, "SHIFT_ENTER", A_ENTER, false }, + { 0x8c, 0x9c, NULL, A_LOW_OE, false }, // ****** + { 0x9d, 0x9d, "END", A_END, false }, + { 0x9e, 0x9e, "PGDN", A_PAGE_DOWN, false }, + { 0x9f, 0xff, NULL, A_CAP_YDIERESIS, false }, // ****** + + { 0xa0, 0, "SHIFT_SPACE", A_SPACE, false }, + { 0xa1, 0xa1, NULL, A_EXCLAMDOWN, false }, // upside down '!' - undisplayable + { (word)(byte)'¢', (word)(byte)'¢', NULL, A_CENT, false }, + { (word)(byte)'£', (word)(byte)'£', NULL, A_POUND, false }, + { 0xa4, 0, "SHIFT_KP_ENTER", A_KP_ENTER, false }, + { (word)(byte)'¥', (word)(byte)'¥', NULL, A_YEN, false }, + { 0xa6, 0xa6, "MOUSE3", A_MOUSE3, false }, + { 0xa7, 0xa7, "MOUSE4", A_MOUSE4, false }, + { 0xa8, 0xa8, "MOUSE5", A_MOUSE5, false }, + { (word)(byte)'©', (word)(byte)'©', NULL, A_COPYRIGHT, false }, + { 0xaa, 0xaa, "UPARROW", A_CURSOR_UP, false }, + { 0xab, 0xab, "DOWNARROW", A_CURSOR_DOWN, false }, + { 0xac, 0xac, "LEFTARROW", A_CURSOR_LEFT, false }, + { 0xad, 0xad, "RIGHTARROW", A_CURSOR_RIGHT, false }, + { (word)(byte)'®', (word)(byte)'®', NULL, A_REGISTERED, false }, + { 0xaf, 0, NULL, A_UNDEFINED_7, false }, + { 0xb0, 0, NULL, A_UNDEFINED_8, false }, + { 0xb1, 0, NULL, A_UNDEFINED_9, false }, + { 0xb2, 0, NULL, A_UNDEFINED_10, false }, + { 0xb3, 0, NULL, A_UNDEFINED_11, false }, + { 0xb4, 0, NULL, A_UNDEFINED_12, false }, + { 0xb5, 0, NULL, A_UNDEFINED_13, false }, + { 0xb6, 0, NULL, A_UNDEFINED_14, false }, + { 0xb7, 0, NULL, A_UNDEFINED_15, false }, + { 0xb8, 0, NULL, A_UNDEFINED_16, false }, + { 0xb9, 0, NULL, A_UNDEFINED_17, false }, + { 0xba, 0, NULL, A_UNDEFINED_18, false }, + { 0xbb, 0, NULL, A_UNDEFINED_19, false }, + { 0xbc, 0, NULL, A_UNDEFINED_20, false }, + { 0xbd, 0, NULL, A_UNDEFINED_21, false }, + { 0xbe, 0, NULL, A_UNDEFINED_22, false }, + { (word)(byte)'¿', (word)(byte)'¿', NULL, A_QUESTION_DOWN, false }, + + { (word)(byte)'À', (word)(byte)'à', NULL, A_CAP_AGRAVE, false }, + { (word)(byte)'Á', (word)(byte)'á', NULL, A_CAP_AACUTE, false }, + { (word)(byte)'Â', (word)(byte)'â', NULL, A_CAP_ACIRCUMFLEX, false }, + { (word)(byte)'Ã', (word)(byte)'ã', NULL, A_CAP_ATILDE, false }, + { (word)(byte)'Ä', (word)(byte)'ä', NULL, A_CAP_ADIERESIS, false }, + { (word)(byte)'Å', (word)(byte)'å', NULL, A_CAP_ARING, false }, + { (word)(byte)'Æ', (word)(byte)'æ', NULL, A_CAP_AE, false }, + { (word)(byte)'Ç', (word)(byte)'ç', NULL, A_CAP_CCEDILLA, false }, + { (word)(byte)'È', (word)(byte)'è', NULL, A_CAP_EGRAVE, false }, + { (word)(byte)'É', (word)(byte)'é', NULL, A_CAP_EACUTE, false }, + { (word)(byte)'Ê', (word)(byte)'ê', NULL, A_CAP_ECIRCUMFLEX, false }, + { (word)(byte)'Ë', (word)(byte)'ë', NULL, A_CAP_EDIERESIS, false }, + { (word)(byte)'Ì', (word)(byte)'ì', NULL, A_CAP_IGRAVE, false }, + { (word)(byte)'Í', (word)(byte)'í', NULL, A_CAP_IACUTE, false }, + { (word)(byte)'Î', (word)(byte)'î', NULL, A_CAP_ICIRCUMFLEX, false }, + { (word)(byte)'Ï', (word)(byte)'ï', NULL, A_CAP_IDIERESIS, false }, + { (word)(byte)'Ð', (word)(byte)'ð', NULL, A_CAP_ETH, false }, + { (word)(byte)'Ñ', (word)(byte)'ñ', NULL, A_CAP_NTILDE, false }, + { (word)(byte)'Ò', (word)(byte)'ò', NULL, A_CAP_OGRAVE, false }, + { (word)(byte)'Ó', (word)(byte)'ó', NULL, A_CAP_OACUTE, false }, + { (word)(byte)'Ô', (word)(byte)'ô', NULL, A_CAP_OCIRCUMFLEX, false }, + { (word)(byte)'Õ', (word)(byte)'õ', NULL, A_CAP_OTILDE, false }, + { (word)(byte)'Ö', (word)(byte)'ö', NULL, A_CAP_ODIERESIS, false }, + { (word)(byte)'×', (word)(byte)'×', "KP_STAR", A_MULTIPLY, false }, + { (word)(byte)'Ø', (word)(byte)'ø', NULL, A_CAP_OSLASH, false }, + { (word)(byte)'Ù', (word)(byte)'ù', NULL, A_CAP_UGRAVE, false }, + { (word)(byte)'Ú', (word)(byte)'ú', NULL, A_CAP_UACUTE, false }, + { (word)(byte)'Û', (word)(byte)'û', NULL, A_CAP_UCIRCUMFLEX, false }, + { (word)(byte)'Ü', (word)(byte)'ü', NULL, A_CAP_UDIERESIS, false }, + { (word)(byte)'Ý', (word)(byte)'ý', NULL, A_CAP_YACUTE, false }, + { (word)(byte)'Þ', (word)(byte)'þ', NULL, A_CAP_THORN, false }, + { (word)(byte)'ß', (word)(byte)'ß', NULL, A_GERMANDBLS, false }, + + { (word)(byte)'À', (word)(byte)'à', NULL, A_LOW_AGRAVE, false }, + { (word)(byte)'Á', (word)(byte)'á', NULL, A_LOW_AACUTE, false }, + { (word)(byte)'Â', (word)(byte)'â', NULL, A_LOW_ACIRCUMFLEX, false }, + { (word)(byte)'Ã', (word)(byte)'ã', NULL, A_LOW_ATILDE, false }, + { (word)(byte)'Ä', (word)(byte)'ä', NULL, A_LOW_ADIERESIS, false }, + { (word)(byte)'Å', (word)(byte)'å', NULL, A_LOW_ARING, false }, + { (word)(byte)'Æ', (word)(byte)'æ', NULL, A_LOW_AE, false }, + { (word)(byte)'Ç', (word)(byte)'ç', NULL, A_LOW_CCEDILLA, false }, + { (word)(byte)'È', (word)(byte)'è', NULL, A_LOW_EGRAVE, false }, + { (word)(byte)'É', (word)(byte)'é', NULL, A_LOW_EACUTE, false }, + { (word)(byte)'Ê', (word)(byte)'ê', NULL, A_LOW_ECIRCUMFLEX, false }, + { (word)(byte)'Ë', (word)(byte)'ë', NULL, A_LOW_EDIERESIS, false }, + { (word)(byte)'Ì', (word)(byte)'ì', NULL, A_LOW_IGRAVE, false }, + { (word)(byte)'Í', (word)(byte)'í', NULL, A_LOW_IACUTE, false }, + { (word)(byte)'Î', (word)(byte)'î', NULL, A_LOW_ICIRCUMFLEX, false }, + { (word)(byte)'Ï', (word)(byte)'ï', NULL, A_LOW_IDIERESIS, false }, + { (word)(byte)'Ð', (word)(byte)'ð', NULL, A_LOW_ETH, false }, + { (word)(byte)'Ñ', (word)(byte)'ñ', NULL, A_LOW_NTILDE, false }, + { (word)(byte)'Ò', (word)(byte)'ò', NULL, A_LOW_OGRAVE, false }, + { (word)(byte)'Ó', (word)(byte)'ó', NULL, A_LOW_OACUTE, false }, + { (word)(byte)'Ô', (word)(byte)'ô', NULL, A_LOW_OCIRCUMFLEX, false }, + { (word)(byte)'Õ', (word)(byte)'õ', NULL, A_LOW_OTILDE, false }, + { (word)(byte)'Ö', (word)(byte)'ö', NULL, A_LOW_ODIERESIS, false }, + { (word)(byte)'÷', (word)(byte)'÷', "KP_SLASH", A_DIVIDE, false }, + { (word)(byte)'Ø', (word)(byte)'ø', NULL, A_LOW_OSLASH, false }, + { (word)(byte)'Ù', (word)(byte)'ù', NULL, A_LOW_UGRAVE, false }, + { (word)(byte)'Ú', (word)(byte)'ú', NULL, A_LOW_UACUTE, false }, + { (word)(byte)'Û', (word)(byte)'û', NULL, A_LOW_UCIRCUMFLEX, false }, + { (word)(byte)'Ü', (word)(byte)'ü', NULL, A_LOW_UDIERESIS, false }, + { (word)(byte)'Ý', (word)(byte)'ý', NULL, A_LOW_YACUTE, false }, + { (word)(byte)'Þ', (word)(byte)'þ', NULL, A_LOW_THORN, false }, + { 0x9f, 0xff, NULL, A_LOW_YDIERESIS, false }, // ******* + + { 0x100, 0x100, "JOY0", A_JOY0, false }, + { 0x101, 0x101, "JOY1", A_JOY1, false }, + { 0x102, 0x102, "JOY2", A_JOY2, false }, + { 0x103, 0x103, "JOY3", A_JOY3, false }, + { 0x104, 0x104, "JOY4", A_JOY4, false }, + { 0x105, 0x105, "JOY5", A_JOY5, false }, + { 0x106, 0x106, "JOY6", A_JOY6, false }, + { 0x107, 0x107, "JOY7", A_JOY7, false }, + { 0x108, 0x108, "JOY8", A_JOY8, false }, + { 0x109, 0x109, "JOY9", A_JOY9, false }, + { 0x10a, 0x10a, "JOY10", A_JOY10, false }, + { 0x10b, 0x10b, "JOY11", A_JOY11, false }, + { 0x10c, 0x10c, "JOY12", A_JOY12, false }, + { 0x10d, 0x10d, "JOY13", A_JOY13, false }, + { 0x10e, 0x10e, "JOY14", A_JOY14, false }, + { 0x10f, 0x10f, "JOY15", A_JOY15, false }, + { 0x110, 0x110, "JOY16", A_JOY16, false }, + { 0x111, 0x111, "JOY17", A_JOY17, false }, + { 0x112, 0x112, "JOY18", A_JOY18, false }, + { 0x113, 0x113, "JOY19", A_JOY19, false }, + { 0x114, 0x114, "JOY20", A_JOY20, false }, + { 0x115, 0x115, "JOY21", A_JOY21, false }, + { 0x116, 0x116, "JOY22", A_JOY22, false }, + { 0x117, 0x117, "JOY23", A_JOY23, false }, + { 0x118, 0x118, "JOY24", A_JOY24, false }, + { 0x119, 0x119, "JOY25", A_JOY25, false }, + { 0x11a, 0x11a, "JOY26", A_JOY26, false }, + { 0x11b, 0x11b, "JOY27", A_JOY27, false }, + { 0x11c, 0x11c, "JOY28", A_JOY28, false }, + { 0x11d, 0x11d, "JOY29", A_JOY29, false }, + { 0x11e, 0x11e, "JOY30", A_JOY30, false }, + { 0x11f, 0x11f, "JOY31", A_JOY31, false }, + + { 0x120, 0x120, "AUX0", A_AUX0, false }, + { 0x121, 0x121, "AUX1", A_AUX1, false }, + { 0x122, 0x122, "AUX2", A_AUX2, false }, + { 0x123, 0x123, "AUX3", A_AUX3, false }, + { 0x124, 0x124, "AUX4", A_AUX4, false }, + { 0x125, 0x125, "AUX5", A_AUX5, false }, + { 0x126, 0x126, "AUX6", A_AUX6, false }, + { 0x127, 0x127, "AUX7", A_AUX7, false }, + { 0x128, 0x128, "AUX8", A_AUX8, false }, + { 0x129, 0x129, "AUX9", A_AUX9, false }, + { 0x12a, 0x12a, "AUX10", A_AUX10, false }, + { 0x12b, 0x12b, "AUX11", A_AUX11, false }, + { 0x12c, 0x12c, "AUX12", A_AUX12, false }, + { 0x12d, 0x12d, "AUX13", A_AUX13, false }, + { 0x12e, 0x12e, "AUX14", A_AUX14, false }, + { 0x12f, 0x12f, "AUX15", A_AUX15, false }, + { 0x130, 0x130, "AUX16", A_AUX16, false }, + { 0x131, 0x131, "AUX17", A_AUX17, false }, + { 0x132, 0x132, "AUX18", A_AUX18, false }, + { 0x133, 0x133, "AUX19", A_AUX19, false }, + { 0x134, 0x134, "AUX20", A_AUX20, false }, + { 0x135, 0x135, "AUX21", A_AUX21, false }, + { 0x136, 0x136, "AUX22", A_AUX22, false }, + { 0x137, 0x137, "AUX23", A_AUX23, false }, + { 0x138, 0x138, "AUX24", A_AUX24, false }, + { 0x139, 0x139, "AUX25", A_AUX25, false }, + { 0x13a, 0x13a, "AUX26", A_AUX26, false }, + { 0x13b, 0x13b, "AUX27", A_AUX27, false }, + { 0x13c, 0x13c, "AUX28", A_AUX28, false }, + { 0x13d, 0x13d, "AUX29", A_AUX29, false }, + { 0x13e, 0x13e, "AUX30", A_AUX30, false }, + { 0x13f, 0x13f, "AUX31", A_AUX31, false } +}; + + + +/* +============================================================================= + +EDIT FIELDS + +============================================================================= +*/ + + +/* +================ +Field_Paste +================ +*/ +void Field_Paste( field_t *edit ) { + char *cbd; + int pasteLen, i; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + return; + } + + // send as if typed, so insert / overstrike works properly + pasteLen = strlen( cbd ); + for ( i = 0 ; i < pasteLen ; i++ ) { + Field_CharEvent( edit, cbd[i] ); + } + + Z_Free( cbd ); +} + +/* +================= +Field_KeyDownEvent + +Performs the basic line editing functions for the console, +in-game talk, and menu fields + +Key events are used for non-printable characters, others are gotten from char events. +================= +*/ +void Field_KeyDownEvent( field_t *edit, int key ) { + int len; + + // shift-insert is paste +#ifdef _XBOX + if ( ( ( key == A_INSERT ) || ( key == A_KP_0 ) ) && ClientManager::ActiveClient().keys[A_SHIFT].down ) { +#else + if ( ( ( key == A_INSERT ) || ( key == A_KP_0 ) ) && kg.keys[A_SHIFT].down ) { +#endif + Field_Paste( edit ); + return; + } + + len = strlen( edit->buffer ); + + if ( key == A_DELETE ) { + if ( edit->cursor < len ) { + memmove( edit->buffer + edit->cursor, + edit->buffer + edit->cursor + 1, len - edit->cursor ); + } + return; + } + + if ( key == A_CURSOR_RIGHT ) + { + if ( edit->cursor < len ) { + edit->cursor++; + } + + if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) + { + edit->scroll++; + } + return; + } + + if ( key == A_CURSOR_LEFT ) + { + if ( edit->cursor > 0 ) { + edit->cursor--; + } + if ( edit->cursor < edit->scroll ) + { + edit->scroll--; + } + return; + } + +#ifdef _XBOX + if ( key == A_HOME || ( keynames[key].lower == 'a' && ClientManager::ActiveClient().keys[A_CTRL].down ) ) +#else + if ( key == A_HOME || ( keynames[key].lower == 'a' && kg.keys[A_CTRL].down ) ) +#endif + { + edit->cursor = 0; + return; + } + +#ifdef _XBOX + if ( key == A_END || ( keynames[key].lower == 'e' && ClientManager::ActiveClient().keys[A_CTRL].down ) ) +#else + if ( key == A_END || ( keynames[key].lower == 'e' && kg.keys[A_CTRL].down ) ) +#endif + { + edit->cursor = len; + return; + } + + if ( key == A_INSERT ) { +#ifdef _XBOX + ClientManager::ActiveClient().key_overstrikeMode = (qboolean)!ClientManager::ActiveClient().key_overstrikeMode; +#else + kg.key_overstrikeMode = (qboolean)!kg.key_overstrikeMode; +#endif + return; + } +} + +/* +================== +Field_CharEvent +================== +*/ +void Field_CharEvent( field_t *edit, int ch ) { + int len; + + if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste + Field_Paste( edit ); + return; + } + + if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field + Field_Clear( edit ); + return; + } + + len = strlen( edit->buffer ); + + if ( ch == 'h' - 'a' + 1 ) { // ctrl-h is backspace + if ( edit->cursor > 0 ) { + memmove( edit->buffer + edit->cursor - 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->cursor--; + if ( edit->cursor < edit->scroll ) + { + edit->scroll--; + } + } + return; + } + + if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home + edit->cursor = 0; + edit->scroll = 0; + return; + } + + if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end + edit->cursor = len; + edit->scroll = edit->cursor - edit->widthInChars; + return; + } + + // + // ignore any other non printable chars + // + if ( ch < 32 ) { + return; + } + +#ifdef _XBOX + if ( ClientManager::ActiveClient().key_overstrikeMode ) { +#else + if ( kg.key_overstrikeMode ) { +#endif + if ( edit->cursor == MAX_EDIT_LINE - 1 ) + return; + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } else { // insert mode + if ( len == MAX_EDIT_LINE - 1 ) { + return; // all full + } + memmove( edit->buffer + edit->cursor + 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } + + + if ( edit->cursor >= edit->widthInChars ) { + edit->scroll++; + } + + if ( edit->cursor == len + 1) { + edit->buffer[edit->cursor] = 0; + } +} + +/* +================== +Field_Clear +================== +*/ +void Field_Clear( field_t *edit ) { + edit->buffer[0] = 0; + edit->cursor = 0; + edit->scroll = 0; +} + +/* +============================================================================= + +CONSOLE LINE EDITING + +============================================================================== +*/ + +static const char *completionString; +static char shortestMatch[MAX_TOKEN_CHARS]; +static int matchCount; + +/* +=============== +FindMatches + +=============== +*/ +static void FindMatches( const char *s ) { + int i; + + if ( Q_stricmpn( s, completionString, strlen( completionString ) ) ) { + return; + } + matchCount++; + if ( matchCount == 1 ) { + Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) ); + return; + } + + // cut shortestMatch to the amount common with s + for ( i = 0 ; s[i] ; i++ ) { + if ( tolower(shortestMatch[i]) != tolower(s[i]) ) { + shortestMatch[i] = 0; + break; + } + } + if (!s[i]) + { + shortestMatch[i] = 0; + } +} + +/* +=============== +PrintMatches + +=============== +*/ +static void PrintMatches( const char *s ) { + if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) { + Com_Printf( " %s\n", s ); + } +} + +static void keyConcatArgs( void ) { + int i; + char *arg; + + for ( i = 1 ; i < Cmd_Argc() ; i++ ) { + Q_strcat( kg.g_consoleField.buffer, sizeof( kg.g_consoleField.buffer ), " " ); + arg = Cmd_Argv( i ); + while (*arg) { + if (*arg == ' ') { + Q_strcat( kg.g_consoleField.buffer, sizeof( kg.g_consoleField.buffer ), "\""); + break; + } + arg++; + } + Q_strcat( kg.g_consoleField.buffer, sizeof( kg.g_consoleField.buffer ), Cmd_Argv( i ) ); + if (*arg == ' ') { + Q_strcat( kg.g_consoleField.buffer, sizeof( kg.g_consoleField.buffer ), "\""); + } + } +} + +static void ConcatRemaining( const char *src, const char *start ) { + char *str; + + str = strstr(src, start); + if (!str) { + keyConcatArgs(); + return; + } + + str += strlen(start); + Q_strcat( kg.g_consoleField.buffer, sizeof( kg.g_consoleField.buffer ), str); +} + + +/* +=============== +CompleteCommand + +Tab expansion +=============== +*/ +void CompleteCommand( void ) +{ +#ifndef _XBOX + field_t *edit; + field_t temp; + + edit = &kg.g_consoleField; + + // only look at the first token for completion purposes + Cmd_TokenizeString( edit->buffer ); + + completionString = Cmd_Argv(0); + if ( completionString[0] == '\\' || completionString[0] == '/' ) { + completionString++; + } + matchCount = 0; + shortestMatch[0] = 0; + + if ( strlen( completionString ) == 0 ) { + return; + } + + Cmd_CommandCompletion( FindMatches ); + Cvar_CommandCompletion( FindMatches ); + + if ( matchCount == 0 ) { + return; // no matches + } + + Com_Memcpy(&temp, edit, sizeof(field_t)); + + if ( matchCount == 1 ) { + Com_sprintf( edit->buffer, sizeof( edit->buffer ), "\\%s", shortestMatch ); + if ( Cmd_Argc() == 1 ) { + Q_strcat( kg.g_consoleField.buffer, sizeof( kg.g_consoleField.buffer ), " " ); + } else { + ConcatRemaining( temp.buffer, completionString ); + } + edit->cursor = strlen( edit->buffer ); + return; + } + + // multiple matches, complete to shortest + Com_sprintf( edit->buffer, sizeof( edit->buffer ), "\\%s", shortestMatch ); + edit->cursor = strlen( edit->buffer ); + ConcatRemaining( temp.buffer, completionString ); + + Com_Printf( "]%s\n", edit->buffer ); + + // run through again, printing matches + Cmd_CommandCompletion( PrintMatches ); + Cvar_CommandCompletion( PrintMatches ); +#endif +} + + +/* +==================== +Console_Key + +Handles history and console scrollback +==================== +*/ +void Console_Key (int key) { + // ctrl-L clears screen + if ( keynames[ key ].lower == 'l' && kg.keys[A_CTRL].down ) { + Cbuf_AddText ("clear\n"); + return; + } + + // enter finishes the line + if ( key == A_ENTER || key == A_KP_ENTER ) { + // if not in the game explicitly prepent a slash if needed + if ( cls.state != CA_ACTIVE && kg.g_consoleField.buffer[0] != '\\' + && kg.g_consoleField.buffer[0] != '/' ) { + char temp[MAX_STRING_CHARS]; + + Q_strncpyz( temp, kg.g_consoleField.buffer, sizeof( temp ) ); + Com_sprintf( kg.g_consoleField.buffer, sizeof( kg.g_consoleField.buffer ), "\\%s", temp ); + kg.g_consoleField.cursor++; + } + else + { // Added this to automatically make explicit commands not need slashes. + CompleteCommand(); + } + + Com_Printf ( "]%s\n", kg.g_consoleField.buffer ); + + // leading slash is an explicit command + if ( kg.g_consoleField.buffer[0] == '\\' || kg.g_consoleField.buffer[0] == '/' ) { + if (cgvm && cl->mSharedMemory) + { //don't do this unless cgame is inited and shared memory is valid + const char *buf = (kg.g_consoleField.buffer+1); + TCGIncomingConsoleCommand *icc = (TCGIncomingConsoleCommand *)cl->mSharedMemory; + + strcpy(icc->conCommand, buf); + + if (VM_Call(cgvm, CG_INCOMING_CONSOLE_COMMAND)) + { //rww - let mod authors filter client console messages so they can cut them off if they want. + Cbuf_AddText( kg.g_consoleField.buffer+1 ); // valid command + Cbuf_AddText ("\n"); + } + else if (icc->conCommand[0]) + { //the vm call says to execute this command in place + Cbuf_AddText( icc->conCommand ); + Cbuf_AddText ("\n"); + } + } + else + { //just exec it then + Cbuf_AddText( kg.g_consoleField.buffer+1 ); // valid command + Cbuf_AddText ("\n"); + } + } else { + // other text will be chat messages + if ( !kg.g_consoleField.buffer[0] ) { + return; // empty lines just scroll the console without adding to history + } else { + Cbuf_AddText ("cmd say "); + Cbuf_AddText( kg.g_consoleField.buffer ); + Cbuf_AddText ("\n"); + } + } + + // copy line to history buffer + kg.historyEditLines[kg.nextHistoryLine % COMMAND_HISTORY] = kg.g_consoleField; + kg.nextHistoryLine++; + kg.historyLine = kg.nextHistoryLine; + + Field_Clear( &kg.g_consoleField ); + + kg.g_consoleField.widthInChars = g_console_field_width; + + if ( cls.state == CA_DISCONNECTED ) { + SCR_UpdateScreen (); // force an update, because the command + } // may take some time + return; + } + + // command completion + + if (key == A_TAB) { + CompleteCommand(); + return; + } + + // command history (ctrl-p ctrl-n for unix style) + + if ( ( key == A_CURSOR_UP ) || ( ( keynames[ key ].lower == 'p' ) && kg.keys[A_CTRL].down ) ) + { + if ( kg.nextHistoryLine - kg.historyLine < COMMAND_HISTORY && kg.historyLine > 0 ) + { + kg.historyLine--; + } + kg.g_consoleField = kg.historyEditLines[ kg.historyLine % COMMAND_HISTORY ]; + return; + } + + if ( ( key == A_CURSOR_DOWN ) || ( ( keynames[ key ].lower == 'n' ) && kg.keys[A_CTRL].down ) ) + { + if (kg.historyLine == kg.nextHistoryLine) + return; + kg.historyLine++; + kg.g_consoleField = kg.historyEditLines[ kg.historyLine % COMMAND_HISTORY ]; + return; + } + + // console scrolling + if ( key == A_PAGE_UP ) { + Con_PageUp(); + return; + } + + if ( key == A_PAGE_DOWN ) { + Con_PageDown(); + return; + } + + // ctrl-home = top of console + if ( key == A_HOME && kg.keys[A_CTRL].down ) { + Con_Top(); + return; + } + + // ctrl-end = bottom of console + if ( key == A_END && kg.keys[A_CTRL].down ) { + Con_Bottom(); + return; + } + + // pass to the normal editline routine + Field_KeyDownEvent( &kg.g_consoleField, key ); +} + +//============================================================================ + + +/* +================ +Message_Key + +In game talk message +================ +*/ +void Message_Key( int key ) { + + char buffer[MAX_STRING_CHARS]; + + + if (key == A_ESCAPE) { + cls.keyCatchers &= ~KEYCATCH_MESSAGE; + Field_Clear( &chatField ); + return; + } + + if ( key == A_ENTER || key == A_KP_ENTER ) + { + if ( chatField.buffer[0] && cls.state == CA_ACTIVE ) { + if (chat_playerNum != -1 ) + + Com_sprintf( buffer, sizeof( buffer ), "tell %i \"%s\"\n", chat_playerNum, chatField.buffer ); + + else if (chat_team) + + Com_sprintf( buffer, sizeof( buffer ), "say_team \"%s\"\n", chatField.buffer ); + else + Com_sprintf( buffer, sizeof( buffer ), "say \"%s\"\n", chatField.buffer ); + + + + CL_AddReliableCommand( buffer ); + } + cls.keyCatchers &= ~KEYCATCH_MESSAGE; + Field_Clear( &chatField ); + return; + } + + Field_KeyDownEvent( &chatField, key ); +} + +//============================================================================ + + +qboolean Key_GetOverstrikeMode( void ) { +#ifdef _XBOX + return ClientManager::ActiveClient().key_overstrikeMode; +#else + return kg.key_overstrikeMode; +#endif +} + + +void Key_SetOverstrikeMode( qboolean state ) { +#ifdef _XBOX + ClientManager::ActiveClient().key_overstrikeMode = state; +#else + kg.key_overstrikeMode = state; +#endif +} + + +/* +=================== +Key_IsDown +=================== +*/ +qboolean Key_IsDown( int keynum ) { + if ( keynum == -1 ) { + return qfalse; + } + +#ifdef _XBOX + return ClientManager::ActiveClient().keys[ keynames[keynum].upper ].down; +#else + return kg.keys[ keynames[keynum].upper ].down; +#endif +} + + +/* +=================== +Key_StringToKeynum + +Returns a key number to be used to index keys[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. + +0x11 will be interpreted as raw hex, which will allow new controlers +to be configured even if they don't have defined names. +=================== +*/ +int Key_StringToKeynum( char *str ) { + int i; + + if ( !str || !str[0] ) + { + return -1; + } + // If single char bind, presume ascii char bind + if ( !str[1] ) + { + return keynames[ (unsigned char)str[0] ].upper; + } + + // scan for a text match + for ( i = 0 ; i < MAX_KEYS ; i++ ) + { + if ( keynames[i].name && !stricmp( str, keynames[i].name ) ) + { + return keynames[i].keynum; + } + } + + // check for hex code + if ( str[0] == '0' && str[1] == 'x' && strlen( str ) == 4) + { + int n1, n2; + + n1 = str[2]; + if ( n1 >= '0' && n1 <= '9' ) + { + n1 -= '0'; + } + else if ( n1 >= 'A' && n1 <= 'F' ) + { + n1 = n1 - 'A' + 10; + } + else + { + n1 = 0; + } + + n2 = str[3]; + if ( n2 >= '0' && n2 <= '9' ) + { + n2 -= '0'; + } + else if ( n2 >= 'A' && n2 <= 'F' ) + { + n2 = n2 - 'A' + 10; + } + else + { + n2 = 0; + } + return n1 * 16 + n2; + } + + return -1; +} + + +static char tinyString[16]; +static const char *Key_KeynumValid( int keynum ) +{ + if ( keynum == -1 ) + { + return ""; + } + if ( keynum < 0 || keynum >= MAX_KEYS ) + { + return ""; + } + return NULL; +} + +static const char *Key_KeyToName( int keynum ) +{ + return keynames[keynum].name; +} + + +static const char *Key_KeyToAscii( int keynum ) +{ + if(!keynames[keynum].lower) + { + return(NULL); + } + if(keynum == A_SPACE) + { + tinyString[0] = (char)A_SHIFT_SPACE; + } + else if(keynum == A_ENTER) + { + tinyString[0] = (char)A_SHIFT_ENTER; + } + else if(keynum == A_KP_ENTER) + { + tinyString[0] = (char)A_SHIFT_KP_ENTER; + } + else + { + tinyString[0] = keynames[keynum].upper; + } + tinyString[1] = 0; + return tinyString; +} + +static const char *Key_KeyToHex( int keynum ) +{ + int i, j; + + i = keynum >> 4; + j = keynum & 15; + + tinyString[0] = '0'; + tinyString[1] = 'x'; + tinyString[2] = i > 9 ? i - 10 + 'A' : i + '0'; + tinyString[3] = j > 9 ? j - 10 + 'A' : j + '0'; + tinyString[4] = 0; + + return tinyString; +} + +// Returns the ascii code of the keynum +const char *Key_KeynumToAscii( int keynum ) +{ + const char *name; + + name = Key_KeynumValid(keynum); + + // check for printable ascii + if ( !name && keynum > 0 && keynum < 256 ) + { + name = Key_KeyToAscii(keynum); + } + // Check for name (for JOYx and AUXx buttons) + if ( !name ) + { + name = Key_KeyToName(keynum); + } + // Fallback to hex number + if ( !name ) + { + name = Key_KeyToHex(keynum); + } + return name; +} + + +/* +=================== +Key_KeynumToString + +Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the +given keynum. +=================== +*/ +// Returns a console/config file friendly name for the key +const char *Key_KeynumToString( int keynum ) +{ + const char *name; + + name = Key_KeynumValid(keynum); + + // Check for friendly name + if ( !name ) + { + name = Key_KeyToName(keynum); + } + // check for printable ascii + if ( !name && keynum > 0 && keynum < 256) + { + name = Key_KeyToAscii(keynum); + } + // Fallback to hex number + if ( !name ) + { + name = Key_KeyToHex(keynum); + } + return name; +} + + + +/* +=================== +Key_SetBinding +=================== +*/ +void Key_SetBinding( int keynum, const char *binding ) { + if ( keynum == -1 ) { + return; + } + + // free old bindings +#ifdef _XBOX + if ( ClientManager::ActiveClient().keys[ keynames[keynum].upper ].binding ) { + Z_Free( ClientManager::ActiveClient().keys[ keynames[keynum].upper ].binding ); + ClientManager::ActiveClient().keys[ keynames[keynum].upper ].binding = NULL; + } + + // allocate memory for new binding + if (binding) + { + ClientManager::ActiveClient().keys[ keynames[keynum].upper ].binding = CopyString( binding ); + } +#else + if ( kg.keys[ keynames[keynum].upper ].binding ) { + Z_Free( kg.keys[ keynames[keynum].upper ].binding ); + kg.keys[ keynames[keynum].upper ].binding = NULL; + } + + // allocate memory for new binding + if (binding) + { + kg.keys[ keynames[keynum].upper ].binding = CopyString( binding ); + } +#endif + + // consider this like modifying an archived cvar, so the + // file write will be triggered at the next oportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; +} + + +/* +=================== +Key_GetBinding +=================== +*/ +char *Key_GetBinding( int keynum ) { + if ( keynum == -1 ) { + return ""; + } + +#ifdef _XBOX + return ClientManager::ActiveClient().keys[ keynum ].binding; +#else + return kg.keys[ keynum ].binding; +#endif +} + +/* +=================== +Key_GetKey +=================== +*/ + +int Key_GetKey(const char *binding) { + int i; + + if (binding) { + for (i=0 ; i<256 ; i++) { +#ifdef _XBOX + if (ClientManager::ActiveClient().keys[i].binding && Q_stricmp(binding, ClientManager::ActiveClient().keys[i].binding) == 0) { +#else + if (kg.keys[i].binding && Q_stricmp(binding, kg.keys[i].binding) == 0) { +#endif + return i; + } + } + } + return -1; +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f (void) +{ + int b; + + if (Cmd_Argc() != 2) + { + Com_Printf ("unbind : remove commands from a key\n"); + return; + } + + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b==-1) + { + Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + Key_SetBinding (b, ""); +} + +/* +=================== +Key_Unbindall_f +=================== +*/ +void Key_Unbindall_f (void) +{ + int i; + + for (i = 0; i < MAX_KEYS ; i++) + { +#ifdef _XBOX + if (ClientManager::ActiveClient().keys[i].binding) +#else + if (kg.keys[i].binding) +#endif + { + Key_SetBinding (i, ""); + } + } +} + + + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f (void) +{ + int i, c, b; + char cmd[1024]; + + c = Cmd_Argc(); + + if (c < 2) + { + Com_Printf ("bind [command] : attach a command to a key\n"); + return; + } + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b==-1) + { + Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + if (c == 2) + { +#ifdef _XBOX + if (ClientManager::ActiveClient().keys[b].binding) + Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), ClientManager::ActiveClient().keys[b].binding ); +#else + if (kg.keys[b].binding) + Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), kg.keys[b].binding ); +#endif + else + Com_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) ); + return; + } + +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + for (i=2 ; i< c ; i++) + { + strcat (cmd, Cmd_Argv(i)); + if (i != (c-1)) + strcat (cmd, " "); + } + + Key_SetBinding (b, cmd); +} + +/* +============ +Key_WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void Key_WriteBindings( fileHandle_t f ) { + int i; + + FS_Printf (f, "unbindall\n" ); + for (i=0 ; idemoplaying || cls.state == CA_CINEMATIC ) && +#endif + !cls.keyCatchers) { + + if (Cvar_VariableValue ("com_cameraMode") == 0) { + Cvar_Set ("nextdemo",""); + key = A_ESCAPE; + } + } + + + // escape is always handled special + if ( key == A_ESCAPE && down ) { + if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { + // clear message mode + Message_Key( key ); + return; + } + + // escape always gets out of CGAME stuff + if (cls.keyCatchers & KEYCATCH_CGAME) { + cls.keyCatchers &= ~KEYCATCH_CGAME; + VM_Call (cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE); + return; + } + + if ( !( cls.keyCatchers & KEYCATCH_UI ) ) { +#ifdef _XBOX // No demos on Xbox + if ( cls.state == CA_ACTIVE ) { +#else + if ( cls.state == CA_ACTIVE && !clc->demoplaying ) { +#endif + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME ); + } + // This just causes crashes and such if people hit BACK during loading: +// else +// { +// CL_Disconnect_f(); +// S_StopAllSounds(); +// VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); +// } + return; + } + + VM_Call( uivm, UI_KEY_EVENT, key, down ); + return; + } + + // + // key up events only perform actions if the game key binding is + // a button command (leading + sign). These will be processed even in + // console mode and menu mode, to keep the character from continuing + // an action started before a mode switch. + // + if (!down) { +#ifdef _XBOX + kb = ClientManager::ActiveClient().keys[ keynames[key].upper ].binding; +#else + kb = kg.keys[ keynames[key].upper ].binding; +#endif + + CL_AddKeyUpCommands( key, kb ); + + if ( cls.keyCatchers & KEYCATCH_UI && uivm ) { + VM_Call( uivm, UI_KEY_EVENT, key, down ); + } else if ( cls.keyCatchers & KEYCATCH_CGAME && cgvm ) { + VM_Call( cgvm, CG_KEY_EVENT, key, down ); + } + + return; + } + + + // distribute the key down event to the apropriate handler + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { + Console_Key( key ); + } else if ( cls.keyCatchers & KEYCATCH_UI ) { + if ( uivm ) { + VM_Call( uivm, UI_KEY_EVENT, key, down ); + } + } else if ( cls.keyCatchers & KEYCATCH_CGAME ) { + if ( cgvm ) { + VM_Call( cgvm, CG_KEY_EVENT, key, down ); + } + } else if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { + Message_Key( key ); + } else if ( cls.state == CA_DISCONNECTED ) { + Console_Key( key ); + } else { + // send the bound action +#ifdef _XBOX + kb = ClientManager::ActiveClient().keys[ keynames[key].upper ].binding; +#else + kb = kg.keys[ keynames[key].upper ].binding; +#endif + if (kb) + { + if (kb[0] == '+') { + int i; + char button[1024], *buttonPtr; + buttonPtr = button; + for ( i = 0; ; i++ ) { + if ( kb[i] == ';' || !kb[i] ) { + *buttonPtr = '\0'; + if ( button[0] == '+') { + // button commands add keynum and time as parms so that multiple + // sources can be discriminated and subframe corrected + Com_sprintf (cmd, sizeof(cmd), "%s %i %i\n", button, key, time); + Cbuf_AddText (cmd); + } else { + // down-only command + Cbuf_AddText (button); + Cbuf_AddText ("\n"); + } + buttonPtr = button; + while ( (kb[i] <= ' ' || kb[i] == ';') && kb[i] != 0 ) { + i++; + } + } + *buttonPtr++ = kb[i]; + if ( !kb[i] ) { + break; + } + } + } else { + // down-only command + if (cgvm && cl->mSharedMemory) + { //don't do this unless cgame is inited and shared memory is valid + TCGIncomingConsoleCommand *icc = (TCGIncomingConsoleCommand *)cl->mSharedMemory; + + strcpy(icc->conCommand, kb); + + if (VM_Call(cgvm, CG_INCOMING_CONSOLE_COMMAND)) + { //rww - let mod authors filter client console messages so they can cut them off if they want. + Cbuf_AddText (kb); + Cbuf_AddText ("\n"); + } + else if (icc->conCommand[0]) + { //the vm call says to execute this command in place + Cbuf_AddText( icc->conCommand ); + Cbuf_AddText ("\n"); + } + } + else + { //otherwise just add it + Cbuf_AddText (kb); + Cbuf_AddText ("\n"); + } + } + } + } +} + + +/* +=================== +CL_CharEvent + +Normal keyboard characters, already shifted / capslocked / etc +=================== +*/ +void CL_CharEvent( int key ) { + // the console key should never be used as a char + if ( key == '`' || key == '~' ) { + return; + } + + // distribute the key down event to the apropriate handler + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) + { + Field_CharEvent( &kg.g_consoleField, key ); + } + else if ( cls.keyCatchers & KEYCATCH_UI ) + { + VM_Call( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue ); + } + else if ( cls.keyCatchers & KEYCATCH_MESSAGE ) + { + Field_CharEvent( &chatField, key ); + } + else if ( cls.state == CA_DISCONNECTED ) + { + Field_CharEvent( &kg.g_consoleField, key ); + } +} + + +/* +=================== +Key_ClearStates +=================== +*/ +void Key_ClearStates (void) +{ + int i; + +#ifdef _XBOX + ClientManager::ActiveClient().anykeydown = qfalse; + + for ( i=0 ; i < MAX_KEYS ; i++ ) { + if ( ClientManager::ActiveClient().keys[i].down ) { + CL_KeyEvent( i, qfalse, 0 ); + + } + ClientManager::ActiveClient().keys[i].down = (qboolean)0; + ClientManager::ActiveClient().keys[i].repeats = 0; + } +#else + kg.anykeydown = qfalse; + + for ( i=0 ; i < MAX_KEYS ; i++ ) { + if ( kg.keys[i].down ) { + CL_KeyEvent( i, qfalse, 0 ); + + } + kg.keys[i].down = (qboolean)0; + kg.keys[i].repeats = 0; + } +#endif +} + diff --git a/codemp/client/cl_main.cpp b/codemp/client/cl_main.cpp new file mode 100644 index 0000000..26feabf --- /dev/null +++ b/codemp/client/cl_main.cpp @@ -0,0 +1,2337 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// cl_main.c -- client main loop + +#include "client.h" +#include "../qcommon/stringed_ingame.h" +#include +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "cl_data.h" +#include "snd_local_console.h" +#include "../xbox/XBLive.h" +#include "../xbox/XBoxCommon.h" +#else +#include "snd_local.h" +#endif + +//rwwRMG - added: +//#include "..\qcommon\cm_local.h" +//#include "..\qcommon\cm_landscape.h" + +#if !defined(G2_H_INC) + #include "..\ghoul2\G2_local.h" +#endif + +#if !defined (MINIHEAP_H_INC) +#include "../qcommon/miniheap.h" +#endif + +#ifdef _DONETPROFILE_ +#include "../qcommon/INetProfile.h" +#endif + +#if 0 //rwwFIXMEFIXME: Disable this before release!!!!!! I am just trying to find a crash bug. +#include "../renderer/tr_local.h" +#endif + +#ifdef _XBOX +#include "../renderer/tr_local.h" +#endif + +cvar_t *cl_nodelta; +cvar_t *cl_debugMove; + +cvar_t *cl_noprint; +cvar_t *cl_motd; + +cvar_t *rcon_client_password; +cvar_t *rconAddress; + +cvar_t *cl_timeout; +cvar_t *cl_maxpackets; +cvar_t *cl_packetdup; +cvar_t *cl_timeNudge; +cvar_t *cl_showTimeDelta; +cvar_t *cl_freezeDemo; + +cvar_t *cl_shownet; +cvar_t *cl_showSend; +cvar_t *cl_timedemo; +cvar_t *cl_avidemo; +cvar_t *cl_forceavidemo; + +cvar_t *cl_freelook; +cvar_t *cl_sensitivity; + +cvar_t *cl_mouseAccel; +cvar_t *cl_showMouseRate; + +cvar_t *m_pitchVeh; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; +cvar_t *m_filter; + +#ifdef _XBOX +//MAP HACK +cvar_t *cl_mapname; +#endif + +cvar_t *cl_activeAction; + +cvar_t *cl_motdString; + +cvar_t *cl_allowDownload; +cvar_t *cl_allowAltEnter; +cvar_t *cl_conXOffset; +cvar_t *cl_inGameVideo; + +cvar_t *cl_serverStatusResendTime; +cvar_t *cl_trn; +cvar_t *cl_framerate; + +cvar_t *cl_autolodscale; + +vec3_t cl_windVec; + +#ifdef USE_CD_KEY +char cl_cdkey[34] = " "; + + +#endif // USE_CD_KEY + +// MATT - changed this to a pointer for splitscreen client swapping +//clientActive_t g_cl; +clientActive_t *cl = NULL; //&g_cl; + +//clientConnection_t g_clc; +clientConnection_t *clc = NULL; //&g_clc; + +clientStatic_t cls; +vm_t *cgvm; + +bool preserveTextures = false; + +// Structure containing functions exported from refresh DLL +refexport_t re; + +CMiniHeap *G2VertSpaceClient = 0; + +#if defined __USEA3D && defined __A3D_GEOM + void hA3Dg_ExportRenderGeom (refexport_t *incoming_re); +#endif + +extern void SV_BotFrame( int time ); +void CL_CheckForResend( void ); +void CL_ShowIP_f(void); +//void CL_ServerStatus_f(void); +//void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); + +/* +======================================================================= + +CLIENT RELIABLE COMMAND COMMUNICATION + +======================================================================= +*/ + +/* +====================== +CL_AddReliableCommand + +The given command will be transmitted to the server, and is gauranteed to +not have future usercmd_t executed before it is executed +====================== +*/ +void CL_AddReliableCommand( const char *cmd ) { + int index; + + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + if ( clc->reliableSequence - clc->reliableAcknowledge > MAX_RELIABLE_COMMANDS ) { + Com_Error( ERR_DROP, "Client command overflow" ); + } + clc->reliableSequence++; + index = clc->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + Q_strncpyz( clc->reliableCommands[ index ], cmd, sizeof( clc->reliableCommands[ index ] ) ); +} + +/* +====================== +CL_ChangeReliableCommand +====================== +*/ +void CL_ChangeReliableCommand( void ) { + int r, index, l; + + r = clc->reliableSequence - ((int)(random()) * 5); + index = clc->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + l = strlen(clc->reliableCommands[ index ]); + if ( l >= MAX_STRING_CHARS - 1 ) { + l = MAX_STRING_CHARS - 2; + } + clc->reliableCommands[ index ][ l ] = '\n'; + clc->reliableCommands[ index ][ l+1 ] = '\0'; +} + +/* +====================== +CL_MakeMonkeyDoLaundry +====================== +*/ +void CL_MakeMonkeyDoLaundry( void ) { + if ( Sys_MonkeyShouldBeSpanked() ) { + if ( !(cls.framecount & 255) ) { + if ( random() < 0.1 ) { + CL_ChangeReliableCommand(); + } + } + } +} + +//====================================================================== + +/* +===================== +CL_ShutdownAll +===================== +*/ +void CL_ShutdownAll(void) { + +#if 0 //rwwFIXMEFIXME: Disable this before release!!!!!! I am just trying to find a crash bug. + //so it doesn't barf on shutdown saying refentities belong to each other + tr.refdef.num_entities = 0; +#endif + + // clear sounds + S_DisableSounds(); + // shutdown CGame + CL_ShutdownCGame(); + + if ( !com_dedicated->integer ) + { + // shutdown UI + CL_ShutdownUI(); + + // shutdown the renderer + if ( re.Shutdown ) { + re.Shutdown( qfalse ); // don't destroy window or context + } + } + else + { + VM_Call( uivm, UI_SHUTDOWN ); + } + +#ifndef _XBOX + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; +#endif + cls.rendererStarted = qfalse; + cls.soundRegistered = qfalse; +} + + +#ifdef _XBOX +//To avoid fragmentation, we want everything free by this point. +//Much of this probably violates DLL boundaries, so it's done on +//Xbox only. +extern void R_DestroyWireframeMap(void); +extern void Sys_IORequestQueueClear(void); +extern void AS_FreePartial(void); +extern void Cvar_Defrag(void); +extern void R_ModelFree(void); +extern void Ghoul2InfoArray_Free(void); +extern void CM_Free(void); +extern void G_ClPtrClear(void); +extern void NPC_NPCPtrsClear(void); +extern void Sys_StreamRequestQueueClear(void); +extern void RemoveAllWP(void); +extern void BG_ClearVehicleParseParms(void); +extern void NAV_ClearStoredWaypoints(void); +extern void ClearTheBonePool(void); +extern void IN_KillRumbleScripts(void); +extern int NumMiscEnts; + +// Various client-side siege state: +extern int cgSiegeRoundState; +extern int cgSiegeRoundTime; +extern int cgSiegeRoundBeganTime; +extern int cgSiegeRoundCountTime; +extern int cg_siegeDeathTime; +extern int g_siegeRespawnCheck; +extern qboolean cg_vehPmoveSet; + +namespace cgame +{ + extern int bgNumAnimEvents; + extern void BG_ClearVehicleLoadInfo(void); +}; + +void CL_ClearLastLevel(void) +{ + Z_TagFree(TAG_UI_ALLOC); + Z_TagFree(TAG_CG_UI_ALLOC); + Z_TagFree(TAG_BG_ALLOC); + CM_Free(); + R_DestroyWireframeMap(); + Ghoul2InfoArray_Free(); + CM_FreeShaderText(); + R_ModelFree(); + Sys_IORequestQueueClear(); + Sys_StreamRequestQueueClear(); + AS_FreePartial(); + Cvar_Defrag(); + G_ClPtrClear(); + NPC_NPCPtrsClear(); + RemoveAllWP(); + BG_ClearVehicleParseParms(); + cgame::BG_ClearVehicleLoadInfo(); + NAV_ClearStoredWaypoints(); + R_DeleteTextures(); + ClearTheBonePool(); + IN_KillRumbleScripts(); // Trying to fix rumble when quitting - wrong place? + + NumMiscEnts = 0; + cgame::bgNumAnimEvents = 1; + cg_vehPmoveSet = qfalse; + + cgSiegeRoundState = 0; + cgSiegeRoundTime = 0; + cgSiegeRoundBeganTime = 0; + cgSiegeRoundCountTime = 0; + cg_siegeDeathTime = 0; + g_siegeRespawnCheck = 0; +} +#endif + + +/* +================= +CL_FlushMemory + +Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only +ways a client gets into a game +Also called by Com_Error +================= +*/ +void CL_FlushMemory( void ) { + + // shutdown all the client stuff + CL_ShutdownAll(); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear collision map data + CM_ClearMap(); + // clear the whole hunk + Hunk_Clear(); + + //clear everything else to avoid fragmentation +#ifdef _XBOX + CL_ClearLastLevel(); + + ClientManager::ClientActiveRelocate( false ); + +#ifdef _DEBUG + //Useful for memory debugging. Please don't delete. Comment out if + //necessary. + extern void Z_DisplayLevelMemory(int, int, int); + extern void Z_Details_f(void); + extern void Z_TagPointers(memtag_t); + Z_DisplayLevelMemory(0, 0, 0); + Z_Details_f(); + #endif +#endif + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + CL_StartHunkUsers(); +} + +/* +===================== +CL_MapLoading + +A local server is starting to load a map, so update the +screen to let the user know about it, then dump all client +memory on the hunk from cgame, ui, and renderer +===================== +*/ +void CL_MapLoading( void ) { + if ( !com_cl_running->integer ) { + return; + } + + // Set this to localhost. + Cvar_Set( "cl_currentServerAddress", "Localhost"); + + Con_Close(); + cls.keyCatchers = 0; + + // if we are already connected to the local host, stay connected + if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) + { + CM_START_LOOP(); + + cls.state = CA_CONNECTED; // so the connect screen is drawn + Com_Memset( clc->serverMessage, 0, sizeof( clc->serverMessage ) ); + Com_Memset( &cl->gameState, 0, sizeof( cl->gameState ) ); + clc->lastPacketSentTime = -9999; + CM_END_LOOP(); + + SCR_UpdateScreen(); + } + else + { + // clear nextmap so the cinematic shutdown doesn't execute it + Cvar_Set( "nextmap", "" ); + CL_Disconnect( qtrue, qfalse ); // Special flag to not delete textures - we need them to draw the connect screen! + Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) ); + + CM_START_LOOP(); + + cls.state = CA_CHALLENGING; // so the connect screen is drawn + cls.keyCatchers = 0; + SCR_UpdateScreen(); + clc->connectTime = -RETRANSMIT_TIMEOUT; + NET_StringToAdr( cls.servername, &clc->serverAddress); + + CM_END_LOOP(); + + // we don't need a challenge on the localhost + + CL_CheckForResend(); + } +} + +/* +===================== +CL_ClearState + +Called before parsing a gamestate +===================== +*/ +void CL_ClearState (void) { + +// S_StopAllSounds(); + + Com_Memset( cl, 0, sizeof( *cl ) ); +} + + +/* +===================== +CL_Disconnect + +Called when a connection, demo, or cinematic is being terminated. +Goes from a connected state to either a menu state or a console state +Sends a disconnect message to the server +This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect( qboolean showMainMenu, qboolean deleteTextures ) { + if ( !com_cl_running || !com_cl_running->integer ) { + return; + } + +#ifdef _XBOX + Cvar_Set("r_norefresh", "0"); +#endif + + // shutting down the client so enter full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + if ( uivm && showMainMenu ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + + SCR_StopCinematic (); + S_ClearSoundBuffer(); + +#ifdef _XBOX +// extern qboolean RE_RegisterImages_LevelLoadEnd(void); +// RE_RegisterImages_LevelLoadEnd(); + if( deleteTextures && !preserveTextures ) + R_DeleteTextures(); +#endif + + // send a disconnect message to the server + // send it a few times in case one is dropped +#ifdef _XBOX + CM_START_LOOP(); +// if(ClientManager::splitScreenMode == qtrue) +// cls.state = ClientManager::ActiveClient().state; +#endif + if ( cls.state >= CA_CONNECTED ) { + CL_AddReliableCommand( "disconnect" ); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + } + + CL_ClearState (); + + // wipe the client connection + Com_Memset( clc, 0, sizeof( *clc ) ); + + cls.state = CA_DISCONNECTED; +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// ClientManager::ActiveClient().state = cls.state; + CM_END_LOOP(); +#endif + + // not connected to a pure server anymore + cl_connectedToPureServer = qfalse; + cl_connectedGAME = 0; + cl_connectedCGAME = 0; + cl_connectedUI = 0; +} + + +/* +=================== +CL_ForwardCommandToServer + +adds the current command line as a clientCommand +things like godmode, noclip, etc, are commands directed to the server, +so when they are typed in at the console, they will need to be forwarded. +=================== +*/ +void CL_ForwardCommandToServer( const char *string ) { + char *cmd; + + cmd = Cmd_Argv(0); + + // ignore key up commands + if ( cmd[0] == '-' ) { + return; + } + +#ifdef _XBOX // No demos on Xbox +// if(ClientManager::splitScreenMode == qtrue) +// { +// if(ClientManager::ActiveClient().state < CA_CONNECTED || cmd[0] == '+' ) { +// return; +// } +// } +// else + if (cls.state < CA_CONNECTED || cmd[0] == '+' ) { +#else + if (clc->demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) { +#endif + Com_Printf ("Unknown command \"%s\"\n", cmd); + return; + } + + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand( string ); + } else { + CL_AddReliableCommand( cmd ); + } +} + +/* +====================================================================== + +CONSOLE COMMANDS + +====================================================================== +*/ + +/* +================== +CL_Disconnect_f +================== +*/ +void CL_Disconnect_f( void ) { + SCR_StopCinematic(); + Cvar_Set("ui_singlePlayerActive", "0"); + +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// { +// if ( ClientManager::ActiveClient().state != CA_DISCONNECTED && ClientManager::ActiveClient().state != CA_CINEMATIC ) { +// Com_Error (ERR_DISCONNECT, "Disconnected from server"); +// } +// } +// else +#endif + if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) { + Com_Error (ERR_DISCONNECT, "Disconnected from server"); + } +} + + +/* +================ +CL_Reconnect_f + +================ +*/ +void CL_Reconnect_f( void ) { + if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) { + Com_Printf( "Can't reconnect to localhost.\n" ); + return; + } + Cvar_Set("ui_singlePlayerActive", "0"); + Cbuf_AddText( va("connect %s\n", cls.servername ) ); +} + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f( void ) { + char *server; + + if ( !Cvar_VariableValue("fs_restrict") && !Sys_CheckCD() ) + { + Com_Error( ERR_NEED_CD, SE_GetString("CON_TEXT_NEED_CD") ); //"Game CD not in drive" ); + } + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: connect [server]\n"); + return; + } + + Cvar_Set("ui_singlePlayerActive", "0"); + + // clear any previous "server full" type messages + clc->serverMessage[0] = 0; + + server = Cmd_Argv (1); + + if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { + // if running a local server, kill it + SV_Shutdown( "Server quit\n" ); + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + preserveTextures = true; //Don't allow textures to get trashed by CL_Disconnect + SV_Frame( 0 ); + preserveTextures = false; + + // Hmm. Don't delete textures here either. They should get thrown out when + // we finally connect in CL_DownloadsComplete? Hope so. + CL_Disconnect( qtrue, qfalse ); + Con_Close(); + + /* MrE: 2000-09-13: now called in CL_DownloadsComplete + CL_FlushMemory( ); + */ + + Q_strncpyz( cls.servername, server, sizeof(cls.servername) ); + + if (!NET_StringToAdr( cls.servername, &clc->serverAddress) ) { + Com_Printf ("Bad server address\n"); + cls.state = CA_DISCONNECTED; + return; + } + if (clc->serverAddress.port == 0) { + clc->serverAddress.port = BigShort( PORT_SERVER ); + } +#ifndef FINAL_BUILD + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", cls.servername, + clc->serverAddress.ip[0], clc->serverAddress.ip[1], + clc->serverAddress.ip[2], clc->serverAddress.ip[3], + BigShort( clc->serverAddress.port ) ); +#endif + + // if we aren't playing on a lan, we need to authenticate + // with the cd key + if ( NET_IsLocalAddress( clc->serverAddress ) ) { + cls.state = CA_CHALLENGING; + } else { + cls.state = CA_CONNECTING; + } + + cls.keyCatchers = 0; + clc->connectTime = -99999; // CL_CheckForResend() will fire immediately + clc->connectPacketCount = 0; + + // server connection string + Cvar_Set( "cl_currentServerAddress", server ); +} + + +/* +===================== +CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void CL_Rcon_f( void ) { + char message[1024]; + int i; + netadr_t to; + + if ( !rcon_client_password->string ) { + Com_Printf ("You must set 'rcon_password' before\n" + "issuing an rcon command.\n"); + return; + } + + message[0] = -1; + message[1] = -1; + message[2] = -1; + message[3] = -1; + message[4] = 0; + + strcat (message, "rcon "); + + strcat (message, rcon_client_password->string); + strcat (message, " "); + + for (i=1 ; i= CA_CONNECTED ) { + to = clc->netchan.remoteAddress; + } else { + if (!strlen(rconAddress->string)) { + Com_Printf ("You must either be connected,\n" + "or set the 'rconAddress' cvar\n" + "to issue rcon commands\n"); + + return; + } + NET_StringToAdr (rconAddress->string, &to); + if (to.port == 0) { + to.port = BigShort (PORT_SERVER); + } + } + + NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); +} + +/* +================= +CL_SendPureChecksums +================= +*/ +void CL_SendPureChecksums( void ) { +#ifndef _XBOX + const char *pChecksums; + char cMsg[MAX_INFO_VALUE]; + int i; + + // if we are pure we need to send back a command with our referenced pk3 checksums + pChecksums = FS_ReferencedPakPureChecksums(); + + // "cp" + // "Yf" + Com_sprintf(cMsg, sizeof(cMsg), "Yf "); + Q_strcat(cMsg, sizeof(cMsg), pChecksums); + for (i = 0; i < 2; i++) { + cMsg[i] += 10; + } + CL_AddReliableCommand( cMsg ); +#endif +} + +/* +================= +CL_ResetPureClientAtServer +================= +*/ +void CL_ResetPureClientAtServer( void ) { + CL_AddReliableCommand( va("vdr") ); +} + +/* +================= +CL_Vid_Restart_f + +Restart the video subsystem + +we also have to reload the UI and CGame because the renderer +doesn't know what graphics to reload +================= +*/ +extern bool g_nOverrideChecked; +void CL_Vid_Restart_f( void ) { + //rww - sort of nasty, but when a user selects a mod + //from the menu all it does is a vid_restart, so we + //have to check for new net overrides for the mod then. +#ifndef _XBOX // No mods on Xbox + g_nOverrideChecked = false; +#endif + + // don't let them loop during the restart + S_StopAllSounds(); + // shutdown the UI + CL_ShutdownUI(); + // shutdown the CGame + CL_ShutdownCGame(); + // shutdown the renderer and clear the renderer interface + CL_ShutdownRef(); + // client is no longer pure untill new checksums are sent + CL_ResetPureClientAtServer(); + // clear pak references + FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); + // reinitialize the filesystem if the game directory or checksum has changed + FS_ConditionalRestart( clc->checksumFeed ); + + cls.rendererStarted = qfalse; + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.soundRegistered = qfalse; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + CM_ClearMap(); + // clear the whole hunk + Hunk_Clear(); + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + // initialize the renderer interface + CL_InitRef(); + + // startup all the client stuff + CL_StartHunkUsers(); + +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// cls.state = ClientManager::ActiveClient().state; +#endif + + // start the cgame if connected + if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { + cls.cgameStarted = qtrue; + CL_InitCGame(); + // send pure checksums + CL_SendPureChecksums(); + } +} + +/* +================= +CL_Snd_Restart_f + +Restart the sound subsystem +The cgame and game must also be forced to restart because +handles will be invalid +================= +*/ +// extern void S_UnCacheDynamicMusic( void ); +void CL_Snd_Restart_f( void ) { + S_Shutdown(); + S_Init(); + +// S_FreeAllSFXMem(); // These two removed by BTO (VV) +// S_UnCacheDynamicMusic(); // S_Shutdown() already does this! + +// CL_Vid_Restart_f(); + + extern qboolean s_soundMuted; + s_soundMuted = qfalse; // we can play again + + extern void S_RestartMusic( void ); + S_RestartMusic(); +} + + +/* +================== +CL_PK3List_f +================== +*/ +void CL_OpenedPK3List_f( void ) { + Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames()); +} + +/* +================== +CL_PureList_f +================== +*/ +void CL_ReferencedPK3List_f( void ) { + Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames()); +} + +/* +================== +CL_Configstrings_f +================== +*/ +void CL_Configstrings_f( void ) { + int i; + int ofs; + +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// { +// if ( ClientManager::ActiveClient().state != CA_ACTIVE ) { +// Com_Printf( "Not connected to a server.\n"); +// return; +// } +// } +// else +#endif + if ( cls.state != CA_ACTIVE ) { + Com_Printf( "Not connected to a server.\n"); + return; + } + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + ofs = cl->gameState.stringOffsets[ i ]; + if ( !ofs ) { + continue; + } + Com_Printf( "%4i: %s\n", i, cl->gameState.stringData + ofs ); + } +} + +/* +============== +CL_Clientinfo_f +============== +*/ +void CL_Clientinfo_f( void ) { + Com_Printf( "--------- Client Information ---------\n" ); +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// Com_Printf( "state: %i\n", ClientManager::ActiveClient().state ); +// else +#endif + Com_Printf( "state: %i\n", cls.state ); + Com_Printf( "Server: %s\n", cls.servername ); + Com_Printf ("User info settings:\n"); + Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); + Com_Printf( "--------------------------------------\n" ); +} + + +//==================================================================== + +/* +================= +CL_DownloadsComplete + +Called when all downloading has been completed +================= +*/ +void CL_DownloadsComplete( void ) { + + // if we downloaded files we need to restart the file system +#ifndef _XBOX // No downloads on Xbox + if (clc->downloadRestart) { + clc->downloadRestart = qfalse; + + FS_Restart(clc->checksumFeed); // We possibly downloaded a pak, restart the file system to load it + + // inform the server so we get new gamestate info + CL_AddReliableCommand( "donedl" ); + + // by sending the donenl command we request a new gamestate + // so we don't want to load stuff yet + return; + } +#endif + + // let the client game init and load data + cls.state = CA_LOADING; + +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// ClientManager::ActiveClient().state = cls.state; +#endif + + // Pump the loop, this may change gamestate! + Com_EventLoop(); + + // if the gamestate was changed by calling Com_EventLoop + // then we loaded everything already and we don't want to do it again. +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// { +// if(ClientManager::ActiveClient().state != CA_LOADING) { +// return; +// } +// } +// else +#endif + if ( cls.state != CA_LOADING ) { + return; + } + + // starting to load a map so we get out of full screen ui mode + Cvar_Set("r_uiFullScreen", "0"); + + // flush client memory and start loading stuff + // this will also (re)load the UI + // if this is a local client then only the client part of the hunk + // will be cleared, note that this is done after the hunk mark has been set +#ifdef _XBOX + if((ClientManager::ActiveClientNum() == 0) || (ClientManager::splitScreenMode == false)) +#endif + CL_FlushMemory(); + + // initialize the CGame + cls.cgameStarted = qtrue; + CL_InitCGame(); + + // set pure checksums + CL_SendPureChecksums(); + + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); +} + +/* +================= +CL_BeginDownload + +Requests a file to download from the server. Stores it in the current +game directory. +================= +*/ + +#ifndef _XBOX // No downloads on Xbox + +void CL_BeginDownload( const char *localName, const char *remoteName ) { + + Com_DPrintf("***** CL_BeginDownload *****\n" + "Localname: %s\n" + "Remotename: %s\n" + "****************************\n", localName, remoteName); + + Q_strncpyz ( clc->downloadName, localName, sizeof(clc->downloadName) ); + Com_sprintf( clc->downloadTempName, sizeof(clc->downloadTempName), "%s.tmp", localName ); + + // Set so UI gets access to it + Cvar_Set( "cl_downloadName", remoteName ); + Cvar_Set( "cl_downloadSize", "0" ); + Cvar_Set( "cl_downloadCount", "0" ); + Cvar_SetValue( "cl_downloadTime", (float) cls.realtime ); + + clc->downloadBlock = 0; // Starting new file + clc->downloadCount = 0; + + CL_AddReliableCommand( va("download %s", remoteName) ); +} + +/* +================= +CL_NextDownload + +A download completed or failed +================= +*/ +void CL_NextDownload(void) { + char *s; + char *remoteName, *localName; + + // We are looking to start a download here + if (*clc->downloadList) { + s = clc->downloadList; + + // format is: + // @remotename@localname@remotename@localname, etc. + + if (*s == '@') + s++; + remoteName = s; + + if ( (s = strchr(s, '@')) == NULL ) { + CL_DownloadsComplete(); + return; + } + + *s++ = 0; + localName = s; + if ( (s = strchr(s, '@')) != NULL ) + *s++ = 0; + else + s = localName + strlen(localName); // point at the nul byte + + CL_BeginDownload( localName, remoteName ); + + clc->downloadRestart = qtrue; + + // move over the rest + memmove( clc->downloadList, s, strlen(s) + 1); + + return; + } + + CL_DownloadsComplete(); +} + +#endif // XBOX - No downloads on Xbox + +/* +================= +CL_InitDownloads + +After receiving a valid game state, we valid the cgame and local zip files here +and determine if we need to download them +================= +*/ +void CL_InitDownloads(void) { +#ifndef _XBOX + char missingfiles[1024]; + + if ( !cl_allowDownload->integer ) + { + // autodownload is disabled on the client + // but it's possible that some referenced files on the server are missing + if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) ) + { + // NOTE TTimo I would rather have that printed as a modal message box + // but at this point while joining the game we don't know wether we will successfully join or not + Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s" + "You might not be able to join the game\n" + "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles ); + } + } + else if ( FS_ComparePaks( clc->downloadList, sizeof( clc->downloadList ) , qtrue ) ) { + + Com_Printf("Need paks: %s\n", clc->downloadList ); + + if ( *clc->downloadList ) { + // if autodownloading is not enabled on the server + cls.state = CA_CONNECTED; +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// ClientManager::ActiveClient().state = cls.state; +#endif + CL_NextDownload(); + return; + } + + } +#endif + CL_DownloadsComplete(); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +#define MAX_CONNECT_RETRIES 5 + +void CL_CheckForResend( void ) { + int port; + char info[MAX_INFO_STRING]; + char data[MAX_INFO_STRING]; + + CM_START_LOOP(); + + // resend if we haven't gotten a reply yet + if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) { + return; + } + + if ( cls.realtime - clc->connectTime < RETRANSMIT_TIMEOUT ) { + return; + } + + clc->connectTime = cls.realtime; // for retransmit requests + clc->connectPacketCount++; + + if( clc->connectPacketCount > MAX_CONNECT_RETRIES ) + { + Com_Error( ERR_DROP, "@MENUS_LOST_CONNECTION" ); + return; + } + + switch ( cls.state ) { + case CA_CONNECTING: + // requesting a challenge + NET_OutOfBandPrint(NS_CLIENT, clc->serverAddress, "getchallenge"); + break; + + case CA_CHALLENGING: + // sending back the challenge + port = (int) Cvar_VariableValue ("net_qport"); + + Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); + Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) ); + if(ClientManager::splitScreenMode == qtrue) + Info_SetValueForKey( info, "qport", va("%i", ClientManager::ActivePort()) ); + else + Info_SetValueForKey( info, "qport", va("%i", port ) ); + Info_SetValueForKey( info, "challenge", va("%i", clc->challenge ) ); + + // Send Xbox stuff to host + // This stuff needs to be parsed in SV_DirectConnect(). SOF2 sent a raw + // XBPlayerInfo, which changed net traffic type. I'm just sending what I + // need to, and doing text encode to avoid other changes. + + // Send our Xbox Address + char sxnaddr[XNADDR_STRING_LEN]; + XnAddrToString(Net_GetXNADDR(), sxnaddr); + Info_SetValueForKey(info, "xnaddr", sxnaddr); + + // Send our XUID if we're logged on and it's good + if (logged_on) + { + XONLINE_USER *pUser = &XBLLoggedOnUsers[ IN_GetMainController() ]; + if (pUser && pUser->hr == S_OK) + { + char sxuid[XUID_STRING_LEN]; + XUIDToString(&pUser->xuid, sxuid); + Info_SetValueForKey(info, "xuid", sxuid); + } + } + + // If we're allowed to take a private slot (ie, we joined a friend or got invited): + if (XBL_MM_CanUsePrivateSlot()) + Info_SetValueForKey(info, "xbps", "1"); + + sprintf(data, "connect \"%s\"", info ); + NET_OutOfBandData( NS_CLIENT, clc->serverAddress, (unsigned char *)data, strlen(data) ); + + // the most current userinfo has been sent, so watch for any + // newer changes to userinfo variables + ClientManager::ActiveClient().cvar_modifiedFlags &= ~CVAR_USERINFO; + break; + + default: + Com_Error( ERR_FATAL, "CL_CheckForResend: bad cls.state" ); + } + + CM_END_LOOP(); +} + + +/* +=================== +CL_DisconnectPacket + +Sometimes the server can drop the client and the netchan based +disconnect can be lost. If the client continues to send packets +to the server, the server will send out of band disconnect packets +to the client so it doesn't have to wait for the full timeout period. +=================== +*/ +void CL_DisconnectPacket( netadr_t from ) { +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) { +// if(ClientManager::ActiveClient().state < CA_AUTHORIZING ) { +// return; +// } +// } +// else +#endif + if ( cls.state < CA_AUTHORIZING ) { + return; + } + + // if not from our server, ignore it + if ( !NET_CompareAdr( from, clc->netchan.remoteAddress ) ) { + return; + } + + // if we have received packets within three seconds, ignore it + // (it might be a malicious spoof) + if ( cls.realtime - clc->lastPacketTime < 3000 ) { + return; + } + + // drop the connection (FIXME: connection dropped dialog) + Com_Printf( "Server disconnected for unknown reason\n" ); + +#ifdef _XBOX + Net_XboxDisconnect(); +#endif + + CL_Disconnect( qtrue ); +} + + +#ifndef MAX_STRINGED_SV_STRING +#define MAX_STRINGED_SV_STRING 1024 +#endif +static void CL_CheckSVStringEdRef(char *buf, const char *str) +{ //I don't really like doing this. But it utilizes the system that was already in place. + int i = 0; + int b = 0; + int strLen = 0; + qboolean gotStrip = qfalse; + + if (!str || !str[0]) + { + if (str) + { + strcpy(buf, str); + } + return; + } + + strcpy(buf, str); + + strLen = strlen(str); + + if (strLen >= MAX_STRINGED_SV_STRING) + { + return; + } + + while (i < strLen && str[i]) + { + gotStrip = qfalse; + + if (str[i] == '@' && (i+1) < strLen) + { + if (str[i+1] == '@' && (i+2) < strLen) + { + if (str[i+2] == '@' && (i+3) < strLen) + { //@@@ should mean to insert a stringed reference here, so insert it into buf at the current place + char stripRef[MAX_STRINGED_SV_STRING]; + int r = 0; + + while (i < strLen && str[i] == '@') + { + i++; + } + + while (i < strLen && str[i] && str[i] != ' ' && str[i] != ':' && str[i] != '.' && str[i] != '\n') + { + stripRef[r] = str[i]; + r++; + i++; + } + stripRef[r] = 0; + + buf[b] = 0; + Q_strcat(buf, MAX_STRINGED_SV_STRING, SE_GetString(va("MP_SVGAME_%s", stripRef))); + b = strlen(buf); + } + } + } + + if (!gotStrip) + { + buf[b] = str[i]; + b++; + } + i++; + } + + buf[b] = 0; +} + + +/* +================= +CL_ConnectionlessPacket + +Responses to broadcasts, etc +================= +*/ +void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 + + s = MSG_ReadStringLine( msg ); + + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + + Com_DPrintf ("CL packet %s: %s\n", NET_AdrToString(from), c); + + // challenge from the server we are connecting to + if ( !Q_stricmp(c, "challengeResponse") ) { + if ( cls.state != CA_CONNECTING ) { + Com_Printf( "Unwanted challenge response received. Ignored.\n" ); + } else { + // start sending challenge repsonse instead of challenge request packets + clc->challenge = atoi(Cmd_Argv(1)); + cls.state = CA_CHALLENGING; + clc->connectPacketCount = 0; + clc->connectTime = -99999; + + // take this address as the new server address. This allows + // a server proxy to hand off connections to multiple servers + clc->serverAddress = from; + Com_DPrintf ("challengeResponse: %d\n", clc->challenge); + } + return; + } + + // server connection + if ( !Q_stricmp(c, "connectResponse") ) { + if ( cls.state >= CA_CONNECTED ) { + Com_Printf ("Dup connect received. Ignored.\n"); + return; + } + if ( cls.state != CA_CHALLENGING ) { + Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); + return; + } + if ( !NET_CompareBaseAdr( from, clc->serverAddress ) ) { + Com_Printf( "connectResponse from a different address. Ignored.\n" ); + Com_Printf( "%s should have been %s\n", NET_AdrToString( from ), + NET_AdrToString( clc->serverAddress ) ); + return; + } + + if(ClientManager::splitScreenMode == qtrue) + Netchan_Setup (NS_CLIENT, &clc->netchan, from, ClientManager::ActivePort() ); + else + Netchan_Setup (NS_CLIENT, &clc->netchan, from, Cvar_VariableValue( "net_qport" ) ); + + cls.state = CA_CONNECTED; + clc->lastPacketSentTime = -9999; // send first packet immediately + return; + } + + // server responding to a get playerlist + if ( !Q_stricmp(c, "statusResponse") ) { + assert( 0 ); +// CL_ServerStatusResponse( from, msg ); + return; + } + + // a disconnect message from the server, which will happen if the server + // dropped the connection but it is still getting packets from us + if (!Q_stricmp(c, "disconnect")) { + CL_DisconnectPacket( from ); + return; + } + + // echo request from server + if ( !Q_stricmp(c, "echo") ) { + NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); + return; + } + + // cd check + if ( !Q_stricmp(c, "keyAuthorize") ) { + // we don't use these now, so dump them on the floor + return; + } + + // echo request from server + if ( !Q_stricmp(c, "print") ) + { + char sTemp[MAX_STRINGED_SV_STRING]; + + s = MSG_ReadString( msg ); + CL_CheckSVStringEdRef(sTemp, s); + Q_strncpyz( clc->serverMessage, sTemp, sizeof( clc->serverMessage ) ); + Com_Printf( "%s", sTemp ); + return; + } + + // New for Xbox - server responds with various xCommands to tell us to shut up and go away: + if ( !Q_stricmp(c, "xFull") ) + { + Com_Error( ERR_DROP, "@MENUS_SERVER_FULL" ); + return; + } + + if ( !Q_stricmp(c, "xProt") || !Q_stricmp(c, "xToosoon") ) + { + Com_Error( ERR_DROP, "@MENUS_LOST_CONNECTION" ); + return; + } + + Com_DPrintf ("Unknown connectionless packet command.\n"); +} + + +/* +================= +CL_PacketEvent + +A packet has arrived from the main event loop +================= +*/ +void CL_PacketEvent( netadr_t from, msg_t *msg ) { + int headerBytes; + + clc->lastPacketTime = cls.realtime; + + if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { + CL_ConnectionlessPacket( from, msg ); + return; + } + +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// cls.state = ClientManager::ActiveClient().state; +#endif + + if ( cls.state < CA_CONNECTED ) { + return; // can't be a valid sequenced packet + } + + if ( msg->cursize < 4 ) { + Com_Printf ("%s: Runt packet\n",NET_AdrToString( from )); + return; + } + + // + // packet from server + // + if ( !NET_CompareAdr( from, clc->netchan.remoteAddress ) ) { + Com_DPrintf ("%s:sequenced packet without connection\n" + ,NET_AdrToString( from ) ); + // FIXME: send a client disconnect? + return; + } + + if (!CL_Netchan_Process( &clc->netchan, msg) ) { + return; // out of order, duplicated, etc + } + + // the header is different lengths for reliable and unreliable messages + headerBytes = msg->readcount; + + // track the last message received so it can be returned in + // client messages, allowing the server to detect a dropped + // gamestate + clc->serverMessageSequence = LittleLong( *(int *)msg->data ); + + clc->lastPacketTime = cls.realtime; + CL_ParseServerMessage( msg ); + + // + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + // +#ifndef _XBOX // No demos on Xbox + if ( clc->demorecording && !clc->demowaiting ) { + CL_WriteDemoMessage( msg, headerBytes ); + } +#endif +} + +/* +================== +CL_CheckTimeout + +================== +*/ +void CL_CheckTimeout( void ) { + // + // check timeout + // +#ifdef _XBOX +/* + if(ClientManager::splitScreenMode == qtrue) + { + if ( ( !cl_paused->integer || !sv_paused->integer ) + && ClientManager::ActiveClient().state >= CA_CONNECTED && ClientManager::ActiveClient().state != CA_CINEMATIC + && cls.realtime - clc->lastPacketTime > cl_timeout->value*1000) { + if (++cl->timeoutcount > 5) { // timeoutcount saves debugger + const char *psTimedOut = SE_GetString("MP_SVGAME_SERVER_CONNECTION_TIMED_OUT"); + Com_Printf ("\n%s\n",psTimedOut); + Com_Error(ERR_DROP, psTimedOut); + //CL_Disconnect( qtrue ); + return; + } + } else { + cl->timeoutcount = 0; + } + } + else + { +*/ +#endif + if ( ( !cl_paused->integer || !sv_paused->integer ) + && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC + && cls.realtime - clc->lastPacketTime > cl_timeout->value*1000) { + if (++cl->timeoutcount > 5) { // timeoutcount saves debugger +// const char *psTimedOut = SE_GetString("MP_SVGAME_SERVER_CONNECTION_TIMED_OUT"); +// Com_Printf ("\n%s\n",psTimedOut); + Com_Error(ERR_DROP, "@MENUS_LOST_CONNECTION"); + //CL_Disconnect( qtrue ); + return; + } + } else { + cl->timeoutcount = 0; + } +#ifdef _XBOX +// } +#endif +} + + +//============================================================================ + +void CL_PrepareUserInfoClientData() +{ + int clientnum = ClientManager::ActiveClientNum(); + Cvar_Set ( "model", ClientManager::ActiveClient().model ); + Cvar_Set ( "char_color_red", ClientManager::ActiveClient().char_color_red ); + Cvar_Set ( "char_color_green", ClientManager::ActiveClient().char_color_green ); + Cvar_Set ( "char_color_blue", ClientManager::ActiveClient().char_color_blue ); + + Cvar_Set ( "saber1", ClientManager::ActiveClient().saber1 ); + Cvar_Set ( "saber2", ClientManager::ActiveClient().saber2 ); + + Cvar_Set ( "color1", ClientManager::ActiveClient().color1 ); + Cvar_Set ( "color2", ClientManager::ActiveClient().color2 ); + + Cvar_Set ( "g_saber_color", ClientManager::ActiveClient().saber_color1 ); + Cvar_Set ( "g_saber2_color", ClientManager::ActiveClient().saber_color2 ); + Cvar_Set ( "forcePowers", ClientManager::ActiveClient().forcePowers); + + Cvar_Set ( "name", ClientManager::ActiveClient().autoName); +} + +/* +================== +CL_CheckUserinfo + +================== +*/ +void CL_CheckUserinfo( void ) { + // don't add reliable commands when not yet connected + + if ( cls.state < CA_CHALLENGING ) { + return; + } + // don't overflow the reliable command buffer when paused + if ( cl_paused->integer ) { + return; + } + + CM_START_LOOP(); + + // send a reliable userinfo update if needed + if ( ClientManager::ActiveClient().cvar_modifiedFlags & CVAR_USERINFO ) { + CL_PrepareUserInfoClientData(); + ClientManager::ActiveClient().cvar_modifiedFlags &= ~CVAR_USERINFO; + + CL_AddReliableCommand( va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ) ); + } + + CM_END_LOOP(); +} + + +void CL_CheckDeferedCmds() +{ + // don't add reliable commands when not yet connected + + if ( cls.state < CA_CHALLENGING ) { + return; + } + // don't overflow the reliable command buffer when paused + if ( cl_paused->integer ) { + return; + } + + CM_START_LOOP(); + ClientManager::ClientFeedDeferedScript(); + CM_END_LOOP(); +} + + +static void CL_UpdateTeamCount( void ) +{ + int red = 0; + int blue = 0; + int i = 0; + + for( ; i < cgs.maxclients; i++) + { + if(cgs.clientinfo[i].infoValid) + { + if(cgs.clientinfo[i].team == 3 ) // spectator + { + continue; + } + if(cgs.clientinfo[i].team == 1 || cgs.clientinfo[i].duelTeam == 2) + { + red++; + } + else if(cgs.clientinfo[i].team == 2 || cgs.clientinfo[i].duelTeam == 1) + { + blue++; + } + } + } + + Cvar_Set("blueTeamCount", va("(%d)",blue)); + Cvar_Set("redTeamCount", va("(%d)",red)); +} + +extern CMiniHeap *G2VertSpaceServer; + +/* +================== +CL_Frame + +================== +*/ +static unsigned int frameCount; +static float avgFrametime=0.0; +extern void SE_CheckForLanguageUpdates(void); +void CL_Frame ( int msec ) { + + if ( !com_cl_running->integer ) { + // If a client isn't running, then we're running a dedicated + // server - we still need the UI. + S_Update(); + + SCR_UpdateScreen(); + return; + } + + SE_CheckForLanguageUpdates(); // will take zero time to execute unless language changes, then will reload strings. + // of course this still doesn't work for menus... + +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// cls.state = ClientManager::ActiveClient().state; +#endif + + if ( cls.state == CA_DISCONNECTED && !( cls.keyCatchers & KEYCATCH_UI ) + && !com_sv_running->integer ) { + // if disconnected, bring up the menu + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + S_StartBackgroundTrack("music/mp/MP_action4.mp3","",0); + } + + // if recording an avi, lock to a fixed fps +#ifndef _XBOX + if ( cl_avidemo->integer && msec) { + // save the current screen + if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) { + if (cl_avidemo->integer > 0) { + Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" ); + } else { + Cbuf_ExecuteText( EXEC_NOW, "screenshot_tga silent\n" ); + } + } + // fixed time for next frame' + msec = (1000 / abs(cl_avidemo->integer)) * com_timescale->value; + if (msec == 0) { + msec = 1; + } + } +#endif + + CL_MakeMonkeyDoLaundry(); + + // save the msec before checking pause + cls.realFrametime = msec; + + // decide the simulation time + cls.frametime = msec; + + // Always calculate framerate, bias the LOD if low + avgFrametime+=msec; + float framerate = 1000.0f*(1.0/(avgFrametime/32.0f)); + static int lodFrameCount = 0; + int bias = Cvar_VariableIntegerValue("r_lodbias"); + if(!(frameCount&0x1f)) + { + if(cl_framerate->integer) + { + char mess[256]; + sprintf(mess,"Frame rate=%f LOD=%d\n\n",framerate,bias); + Com_PrintfAlways(mess); + } + avgFrametime=0.0f; + + if(ClientManager::splitScreenMode == qtrue) + { + // If splitscreen mode drops below 20FPS, pull down the LOD bias + // below 15FPS, drop it again + if(framerate < 20.0f && bias == 0) + { + bias++; + Cvar_SetValue("r_lodbias", bias); + lodFrameCount = -1; + } + if(framerate < 15.0f) + { + bias++; + if(bias > 2) + bias = 2; + Cvar_SetValue("r_lodbias", bias); + lodFrameCount = -1; + } + } + else + { + // If non-splitscreen drops below 30FPS, pull down the LOD bias + if(framerate < 30.0f && bias == 0) + { + bias++; + Cvar_SetValue("r_lodbias", bias); + lodFrameCount = -1; + } + if(framerate < 20.0f) + { + bias++; + if(bias > 2) + bias = 2; + Cvar_SetValue("r_lodbias", bias); + lodFrameCount = -1; + } + } + + lodFrameCount++; + if(lodFrameCount==5 && bias > 0) + { + bias--; + Cvar_SetValue("r_lodBias", bias); + lodFrameCount = 0; + } + } + frameCount++; + + cls.realtime += cls.frametime; + +#ifdef _DONETPROFILE_ + if(cls.state==CA_ACTIVE) + { + ClReadProf().IncTime(cls.frametime); + } +#endif + + if ( cl_timegraph->integer ) { + SCR_DebugGraph ( cls.realFrametime * 0.25, 0 ); + } + +#ifdef _XBOX + //Check on the hot swappable button states. + CL_UpdateHotSwap(); +#endif + + // see if we need to update any userinfo + CL_CheckUserinfo(); + + //JLF + CL_CheckDeferedCmds(); + + // if we haven't gotten a packet in a long time, + // drop the connection + CL_CheckTimeout(); + + // send intentions now + CL_SendCmd(); + + // resend a connection request if necessary + CL_CheckForResend(); + + // decide on the serverTime to render + CL_SetCGameTime(); + + // update the screen + SCR_UpdateScreen(); + + // update audio + S_Update(); + + // advance local effects for next frame + SCR_RunCinematic(); + +// Con_RunConsole(); + + // reset the heap for Ghoul2 vert transform space gameside + if (G2VertSpaceServer) + { + G2VertSpaceServer->ResetHeap(); + } + + CL_UpdateTeamCount(); + + cls.framecount++; +} + + +//============================================================================ + +/* +================ +CL_RefPrintf + +DLL glue +================ +*/ +#define MAXPRINTMSG 4096 +void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + if ( print_level == PRINT_ALL ) { + Com_Printf ("%s", msg); + } else if ( print_level == PRINT_WARNING ) { + Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow + } else if ( print_level == PRINT_DEVELOPER ) { + Com_DPrintf (S_COLOR_RED "%s", msg); // red + } +} + + + +/* +============ +CL_ShutdownRef +============ +*/ +void CL_ShutdownRef( void ) { + if ( !re.Shutdown ) { + return; + } + re.Shutdown( qtrue ); + Com_Memset( &re, 0, sizeof( re ) ); +} + +/* +============ +CL_InitRenderer +============ +*/ +void CL_InitRenderer( void ) { + // this sets up the renderer and calls R_Init + re.BeginRegistration( &cls.glconfig ); + + // load character sets +// cls.charSetShader = re.RegisterShaderNoMip("gfx/2d/charsgrid_med"); + + cls.whiteShader = re.RegisterShader( "white" ); +// cls.consoleShader = re.RegisterShader( "console" ); + g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; + kg.g_consoleField.widthInChars = g_console_field_width; +} + +/* +============================ +CL_StartHunkUsers + +After the server has cleared the hunk, these will need to be restarted +This is the only place that any of these functions are called from +============================ +*/ +void CL_StartHunkUsers( void ) { + if (!com_cl_running) { + return; + } + + if ( !com_cl_running->integer ) { + return; + } + + if ( !cls.rendererStarted ) { + cls.rendererStarted = qtrue; + CL_InitRenderer(); + } + + if ( !cls.soundStarted ) { + cls.soundStarted = qtrue; + S_Init(); + } + + if ( !cls.soundRegistered ) { + cls.soundRegistered = qtrue; +#ifdef _XBOX + S_BeginRegistration(ClientManager::NumClients()); +#else + S_BeginRegistration(); +#endif + } + + if ( !cls.uiStarted ) { + cls.uiStarted = qtrue; + CL_InitUI(); + } +} + +/* +============ +CL_InitRef +============ +*/ +void CL_InitRef( void ) { + refexport_t *ret; + + ret = GetRefAPI( REF_API_VERSION ); + +#if defined __USEA3D && defined __A3D_GEOM + hA3Dg_ExportRenderGeom (ret); +#endif + +// Com_Printf( "-------------------------------\n"); + + if ( !ret ) { + Com_Error (ERR_FATAL, "Couldn't initialize refresh" ); + } + + re = *ret; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); +} + + +//=========================================================================================== + +#define MODEL_CHANGE_DELAY 5000 +int gCLModelDelay = 0; + +void CL_SetModel_f( void ) { + char *arg; + char name[256]; + + arg = Cmd_Argv( 1 ); + if (arg[0]) + { + /* + //If you wanted to be foolproof you would put this on the server I guess. But that + //tends to put things out of sync regarding cvar status. And I sort of doubt someone + //is going to write a client and figure out the protocol so that they can annoy people + //by changing models real fast. + int curTime = Com_Milliseconds(); + if (gCLModelDelay > curTime) + { + Com_Printf("You can only change your model every %i seconds.\n", (MODEL_CHANGE_DELAY/1000)); + return; + } + + gCLModelDelay = curTime + MODEL_CHANGE_DELAY; + */ + //rwwFIXMEFIXME: This is currently broken and doesn't seem to work for connecting clients + Cvar_Set( "model", arg ); + } + else + { + Cvar_VariableStringBuffer( "model", name, sizeof(name) ); + Com_Printf("model is set to %s\n", name); + } +} + +void CL_SetForcePowers_f( void ) { + return; +} + +#define G2_VERT_SPACE_CLIENT_SIZE 256 + +/* +==================== +CL_Init +==================== +*/ +void CL_Init( void ) { +// Com_Printf( "----- Client Initialization -----\n" ); + + Con_Init (); + +#ifdef _XBOX + CM_START_LOOP(); + +// if (!ClientManager::ActiveClient().m_cl) ClientManager::ActiveClient().m_cl = new clientActive_t; + + CL_ClearState (); + +// if(ClientManager::splitScreenMode == qtrue) +// ClientManager::ActiveClient().state = CA_DISCONNECTED; +// else + cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED + + cls.realtime = 0; + CM_END_LOOP(); +#else + CL_ClearState (); + + cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED + + cls.realtime = 0; +#endif + CL_InitInput (); + + // + // register our variables + // + cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); + cl_motd = Cvar_Get ("cl_motd", "1", 0); + +// cl_timeout = Cvar_Get ("cl_timeout", "200", 0); + cl_timeout = Cvar_Get ("cl_timeout", "20", 0); + + cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP ); + cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); + cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP ); + cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP ); + cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP ); + rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP ); + cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); + + cl_timedemo = Cvar_Get ("timedemo", "0", 0); + cl_avidemo = Cvar_Get ("cl_avidemo", "0", 0); + cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0); + + rconAddress = Cvar_Get ("rconAddress", "", 0); + + cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE); + cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE); + cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", CVAR_ARCHIVE); + + cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE ); + cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE ); + + cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE); + cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE); +#ifdef _XBOX + cl_sensitivity = Cvar_Get ("sensitivity", "2", CVAR_ARCHIVE); + + Cvar_Get ("sensitivityY", "2", CVAR_ARCHIVE); + + CM_START_LOOP(); + + ClientManager::ActiveClient().cg_sensitivity = cl_sensitivity->value; + ClientManager::ActiveClient().cg_sensitivityY = Cvar_VariableValue ("sensitivityY"); + + CM_END_LOOP(); + +#endif//_XBOX + cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE); + cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); + + cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); + cl_framerate = Cvar_Get ("cl_framerate", "0", CVAR_TEMP); + cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); + cl_allowAltEnter = Cvar_Get ("cl_allowAltEnter", "0", CVAR_ARCHIVE); + + cl_autolodscale = Cvar_Get( "cl_autolodscale", "1", CVAR_ARCHIVE ); + + cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0); +#ifdef MACOS_X + // In game video is REALLY slow in Mac OS X right now due to driver slowness + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE); +#else + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE); +#endif + + cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0); + + m_pitchVeh = Cvar_Get ("m_pitchVeh", "-0.022", CVAR_ARCHIVE); + + m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE); + m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE); + m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE); +#ifdef MACOS_X + // Input is jittery on OS X w/o this + m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE); +#else + m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE); +#endif + + cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); + + Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); + + + // userinfo + Cvar_Get ("name", "Padawan", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_PROFILE ); + Cvar_Get ("rate", "4000", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("model", "kyle/default", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_PROFILE ); + +#ifdef _XBOX + // Temp CVar to store UI selected models +// Cvar_Get ("UImodel", "kyle/default", CVAR_ARCHIVE | CVAR_PROFILE ); +#endif + +// Cvar_Get ("model2", "kyle/default", CVAR_USERINFO2 | CVAR_ARCHIVE | CVAR_PROFILE ); + Cvar_Get ("forcepowers", "7-1-032330000000001333", CVAR_USERINFO | CVAR_ARCHIVE ); + + + +//JLF forcepowers +#ifdef _XBOX +// Cvar_Get ("forcepowers2", "7-1-032330000000001333", CVAR_USERINFO2 | CVAR_ARCHIVE ); + Cvar_Get ("forcePowersProfile", "7-1-032330000000001333", CVAR_USERINFO | CVAR_ARCHIVE ); +#endif + Cvar_Get ("g_redTeam", "Empire", CVAR_SERVERINFO | CVAR_ARCHIVE); + Cvar_Get ("g_blueTeam", "Rebellion", CVAR_SERVERINFO | CVAR_ARCHIVE); + + Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("color2", "4", CVAR_USERINFO | CVAR_ARCHIVE ); + + + Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("teamtask", "0", CVAR_USERINFO ); + Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE ); + + Cvar_Get ("password", "", CVAR_USERINFO); + Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE ); + + //default sabers + Cvar_Get ("saber1", "single_1", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_PROFILE ); + + Cvar_Get ("saber2", "none", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_PROFILE ); + +// Cvar_Get ("saber12", "single_1", CVAR_USERINFO2 | CVAR_ARCHIVE | CVAR_PROFILE2 ); +// Cvar_Get ("saber22", "none", CVAR_USERINFO2 | CVAR_ARCHIVE | CVAR_PROFILE2 ); + + //skin color + Cvar_Get ("char_color_red", "255", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_PROFILE ); + + Cvar_Get ("char_color_green", "255", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_PROFILE ); + + Cvar_Get ("char_color_blue", "255", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_PROFILE ); + + + + //skin color2 +// Cvar_Get ("char_color_red2", "255", CVAR_USERINFO2 | CVAR_ARCHIVE | CVAR_PROFILE2 ); +// Cvar_Get ("char_color_green2", "255", CVAR_USERINFO2 | CVAR_ARCHIVE | CVAR_PROFILE2 ); +// Cvar_Get ("char_color_blue2", "255", CVAR_USERINFO2 | CVAR_ARCHIVE | CVAR_PROFILE2 ); + + // cgame might not be initialized before menu is used + Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE ); + + // + // register our commands + // +#ifndef _XBOX + Cmd_AddCommand ("cmd", CL_ForwardToServer_f); + Cmd_AddCommand ("globalservers", CL_GlobalServers_f); + Cmd_AddCommand ("record", CL_Record_f); + Cmd_AddCommand ("demo", CL_PlayDemo_f); + Cmd_AddCommand ("stoprecord", CL_StopRecord_f); +#endif + Cmd_AddCommand ("configstrings", CL_Configstrings_f); + Cmd_AddCommand ("clientinfo", CL_Clientinfo_f); + Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); + Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); + Cmd_AddCommand ("disconnect", CL_Disconnect_f); + Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); + Cmd_AddCommand ("connect", CL_Connect_f); + Cmd_AddCommand ("reconnect", CL_Reconnect_f); + Cmd_AddCommand ("rcon", CL_Rcon_f); + Cmd_AddCommand ("showip", CL_ShowIP_f ); + Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f ); + Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f ); + Cmd_AddCommand ("model", CL_SetModel_f ); + Cmd_AddCommand ("forcepowers", CL_SetForcePowers_f ); + + CL_InitRef(); + + SCR_Init (); + + Cbuf_Execute (); + + Cvar_Set( "cl_running", "1" ); + + G2VertSpaceClient = new CMiniHeap(G2_VERT_SPACE_CLIENT_SIZE * 1024); + +#ifdef _XBOX + extern void CIN_Init(void); + Com_Printf( "Initializing Cinematics...\n"); + CIN_Init(); +#endif +} + + +/* +=============== +CL_Shutdown + +=============== +*/ +void CL_Shutdown( void ) { + static qboolean recursive = qfalse; + + //Com_Printf( "----- CL_Shutdown -----\n" ); + + if ( recursive ) { + printf ("recursive CL_Shutdown shutdown\n"); + return; + } + recursive = qtrue; + + if (G2VertSpaceClient) + { + delete G2VertSpaceClient; + G2VertSpaceClient = 0; + } + + CL_Disconnect( qtrue ); + + CL_ShutdownRef(); //must be before shutdown all so the images get dumped in RE_Shutdown + + // RJ: added the shutdown all to close down the cgame (to free up some memory, such as in the fx system) + CL_ShutdownAll(); + + S_Shutdown(); + //CL_ShutdownUI(); + + Cmd_RemoveCommand ("cmd"); + Cmd_RemoveCommand ("configstrings"); + Cmd_RemoveCommand ("userinfo"); + Cmd_RemoveCommand ("snd_restart"); + Cmd_RemoveCommand ("vid_restart"); + Cmd_RemoveCommand ("disconnect"); + Cmd_RemoveCommand ("record"); + Cmd_RemoveCommand ("demo"); + Cmd_RemoveCommand ("cinematic"); + Cmd_RemoveCommand ("stoprecord"); + Cmd_RemoveCommand ("connect"); + Cmd_RemoveCommand ("localservers"); + Cmd_RemoveCommand ("globalservers"); + Cmd_RemoveCommand ("rcon"); + Cmd_RemoveCommand ("ping"); + Cmd_RemoveCommand ("serverstatus"); + Cmd_RemoveCommand ("showip"); + Cmd_RemoveCommand ("model"); + Cmd_RemoveCommand ("forcepowers"); + + Cvar_Set( "cl_running", "0" ); + + recursive = qfalse; + + Com_Memset( &cls, 0, sizeof( cls ) ); + + //Com_Printf( "-----------------------\n" ); + +} + +/* +================== +CL_ShowIP_f +================== +*/ +void CL_ShowIP_f(void) { + Sys_ShowIP(); +} diff --git a/codemp/client/cl_net_chan.cpp b/codemp/client/cl_net_chan.cpp new file mode 100644 index 0000000..6efbc03 --- /dev/null +++ b/codemp/client/cl_net_chan.cpp @@ -0,0 +1,179 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "client.h" + +// TTimo: unused, commenting out to make gcc happy +#if 1 +/* +============== +CL_Netchan_Encode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Encode( msg_t *msg ) { +#ifdef _XBOX + return; +#endif + int serverId, messageAcknowledge, reliableAcknowledge; + int i, index, srdc, sbit, soob; + byte key, *string; + + if ( msg->cursize <= CL_ENCODE_START ) { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = (qboolean)0; + + serverId = MSG_ReadLong(msg); + messageAcknowledge = MSG_ReadLong(msg); + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = (qboolean)soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)clc->serverCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // + key = clc->challenge ^ serverId ^ messageAcknowledge; + for (i = CL_ENCODE_START; i < msg->cursize; i++) { + // modify the key with the last received now acknowledged server command + if (!string[index]) + index = 0; + if (/*string[index] > 127 || */ // eurofix: remove this so we can chat in european languages... -ste + string[index] == '%') + { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // encode the data with this key + *(msg->data + i) = (*(msg->data + i)) ^ key; + } +} + +/* +============== +CL_Netchan_Decode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Decode( msg_t *msg ) { +#ifdef _XBOX + return; +#endif + long reliableAcknowledge, i, index; + byte key, *string; + int srdc, sbit, soob; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->oob = (qboolean)0; + + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = (qboolean)soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (unsigned char *)clc->reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // xor the client challenge with the netchan sequence number (need something that changes every message) + key = clc->challenge ^ LittleLong( *(unsigned *)msg->data ); + for (i = msg->readcount + CL_DECODE_START; i < msg->cursize; i++) { + // modify the key with the last sent and with this message acknowledged client command + if (!string[index]) + index = 0; + if (/*string[index] > 127 || */ // eurofix: remove this so we can chat in european languages... -ste + string[index] == '%') + { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // decode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} +#endif + +/* +================= +CL_Netchan_TransmitNextFragment +================= +*/ +void CL_Netchan_TransmitNextFragment( netchan_t *chan ) { + Netchan_TransmitNextFragment( chan ); +} + +//byte chksum[65536]; + +/* +=============== +CL_Netchan_Transmit +================ +*/ +void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) +{ + MSG_WriteByte( msg, clc_EOF ); + + CL_Netchan_Encode( msg ); + if( !Netchan_Transmit( chan, msg->cursize, msg->data ) ) + { + // Don't call Com_Error if we're already disconnecting! + if( com_errorEntered ) + return; + + // Quickly detect dead connections: (may want to put in some relaxation here?) + Com_Error( ERR_DROP, "@MENUS_LOST_CONNECTION" ); + } +} + +extern int oldsize; +int newsize = 0; + +/* +================= +CL_Netchan_Process +================= +*/ +qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) { + int ret; +// int i; +// static int newsize = 0; + + ret = Netchan_Process( chan, msg ); + if (!ret) + return qfalse; + CL_Netchan_Decode( msg ); +// Huff_Decompress( msg, CL_DECODE_START ); +// for(i=CL_DECODE_START+msg->readcount;icursize;i++) { +// if (msg->data[i] != chksum[i-(CL_DECODE_START+msg->readcount)]) { +// Com_Error(ERR_DROP,"bad %d v %d\n", msg->data[i], chksum[i-(CL_DECODE_START+msg->readcount)]); +// } +// } + newsize += msg->cursize; +// Com_Printf("saved %d to %d (%d%%)\n", (oldsize>>3), newsize, 100-(newsize*100/(oldsize>>3))); + return qtrue; +} diff --git a/codemp/client/cl_parse.cpp b/codemp/client/cl_parse.cpp new file mode 100644 index 0000000..7f22ed3 --- /dev/null +++ b/codemp/client/cl_parse.cpp @@ -0,0 +1,1064 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// cl_parse.c -- parse a message received from the server + +#include "client.h" +#include "../qcommon/stringed_ingame.h" +#include "../ghoul2/g2_local.h" +#ifdef _DONETPROFILE_ +#include "../qcommon/INetProfile.h" +#endif +#include "../zlib32/zip.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "cl_data.h" + +#include "../xbox/XBLive.h" +#include "../xbox/XBoxCommon.h" +#include "../xbox/XBVoice.h" +#endif + +//static char hiddenCvarVal[128]; + +char *svc_strings[256] = { + "svc_bad", + + "svc_nop", + "svc_gamestate", + "svc_configstring", + "svc_baseline", + "svc_serverCommand", + "svc_download", + "svc_snapshot", + "svc_setgame", + "svc_mapchange", +#ifdef _XBOX + "svc_newpeer", + "svc_removepeer", + "svc_xbInfo", +#endif +}; + +void SHOWNET( msg_t *msg, char *s) { + if ( cl_shownet->integer >= 2) { + Com_Printf ("%3i:%s\n", msg->readcount-1, s); + } +} + +//void CL_SP_Print(const word ID, byte *Data); //, char* color) + +/* +========================================================================= + +MESSAGE PARSING + +========================================================================= +*/ + +/* +================== +CL_DeltaEntity + +Parses deltas from the given base and adds the resulting entity +to the current frame +================== +*/ +void CL_DeltaEntity (msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, + qboolean unchanged) { + entityState_t *state; + + // save the parsed entity state into the big circular buffer so + // it can be used as the source for a later delta + state = &cl->parseEntities[cl->parseEntitiesNum & (MAX_PARSE_ENTITIES-1)]; + + if ( unchanged ) + { + *state = *old; + } + else + { + MSG_ReadDeltaEntity( msg, old, state, newnum ); + } + + if ( state->number == (MAX_GENTITIES-1) ) { + return; // entity was delta removed + } + cl->parseEntitiesNum++; + frame->numEntities++; +} + +/* +================== +CL_ParsePacketEntities + +================== +*/ +void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) { + int newnum; + entityState_t *oldstate; + int oldindex, oldnum; + + newframe->parseEntitiesNum = cl->parseEntitiesNum; + newframe->numEntities = 0; + + // delta from the entities present in oldframe + oldindex = 0; + oldstate = NULL; + if (!oldframe) { + oldnum = 99999; + } else { + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl->parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + + while ( 1 ) { + // read the entity index number + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + + if ( newnum == (MAX_GENTITIES-1) ) { + break; + } + + if ( msg->readcount > msg->cursize ) { + Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message"); + } + + while ( oldnum < newnum ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl->parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + if (oldnum == newnum) { + // delta from previous state + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: delta: %i\n", msg->readcount, newnum); + } + CL_DeltaEntity( msg, newframe, newnum, oldstate, qfalse ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl->parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + continue; + } + + if ( oldnum > newnum ) { + // delta from baseline + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: baseline: %i\n", msg->readcount, newnum); + } + CL_DeltaEntity( msg, newframe, newnum, &cl->entityBaselines[newnum], qfalse ); + continue; + } + + } + + // any remaining entities in the old frame are copied over + while ( oldnum != 99999 ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl->parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } +} + + +/* +================ +CL_ParseSnapshot + +If the snapshot is parsed properly, it will be copied to +cl->snap and saved in cl->snapshots[]. If the snapshot is invalid +for any reason, no changes to the state will be made at all. +================ +*/ +void CL_ParseSnapshot( msg_t *msg ) { + int len; + clSnapshot_t *old; + clSnapshot_t newSnap; + int deltaNum; + int oldMessageNum; + int i, packetNum; + + // get the reliable sequence acknowledge number + // NOTE: now sent with all server to client messages + //clc->reliableAcknowledge = MSG_ReadLong( msg ); + + // read in the new snapshot to a temporary buffer + // we will only copy to cl->snap if it is valid + Com_Memset (&newSnap, 0, sizeof(newSnap)); + + // we will have read any new server commands in this + // message before we got to svc_snapshot + newSnap.serverCommandNum = clc->serverCommandSequence; + + newSnap.serverTime = MSG_ReadLong( msg ); + + newSnap.messageNum = clc->serverMessageSequence; + + deltaNum = MSG_ReadByte( msg ); + if ( !deltaNum ) { + newSnap.deltaNum = -1; + } else { + newSnap.deltaNum = newSnap.messageNum - deltaNum; + } + newSnap.snapFlags = MSG_ReadByte( msg ); + + // If the frame is delta compressed from data that we + // no longer have available, we must suck up the rest of + // the frame, but not use it, then ask for a non-compressed + // message + if ( newSnap.deltaNum <= 0 ) { + newSnap.valid = qtrue; // uncompressed frame + old = NULL; +#ifndef _XBOX // No demos on Xbox + clc->demowaiting = qfalse; // we can start recording now +#endif + } else { + old = &cl->snapshots[newSnap.deltaNum & PACKET_MASK]; + if ( !old->valid ) { + // should never happen + Com_Printf ("Delta from invalid frame (not supposed to happen!).\n"); + } else if ( old->messageNum != newSnap.deltaNum ) { + // The frame that the server did the delta from + // is too old, so we can't reconstruct it properly. + Com_Printf ("Delta frame too old.\n"); + } else if ( cl->parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) { + Com_DPrintf ("Delta parseEntitiesNum too old.\n"); + } else { + newSnap.valid = qtrue; // valid delta parse + } + } + + // read areamask + len = MSG_ReadByte( msg ); + MSG_ReadData( msg, &newSnap.areamask, len); + + // read playerinfo + SHOWNET( msg, "playerstate" ); + if ( old ) { + MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps ); + if (newSnap.ps.m_iVehicleNum) + { //this means we must have written our vehicle's ps too + MSG_ReadDeltaPlayerstate( msg, &old->vps, &newSnap.vps, qtrue ); + } + } else { + MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps ); + if (newSnap.ps.m_iVehicleNum) + { //this means we must have written our vehicle's ps too + MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.vps, qtrue ); + } + } + + // read packet entities + SHOWNET( msg, "packet entities" ); + CL_ParsePacketEntities( msg, old, &newSnap ); + + // if not valid, dump the entire thing now that it has + // been properly read + if ( !newSnap.valid ) { + return; + } + + // clear the valid flags of any snapshots between the last + // received and this one, so if there was a dropped packet + // it won't look like something valid to delta from next + // time we wrap around in the buffer + oldMessageNum = cl->snap.messageNum + 1; + + if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) { + oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 ); + } + for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) { + cl->snapshots[oldMessageNum & PACKET_MASK].valid = qfalse; + } + + // copy to the current good spot + cl->snap = newSnap; + cl->snap.ping = 999; + // calculate ping time + for ( i = 0 ; i < PACKET_BACKUP ; i++ ) { + packetNum = ( clc->netchan.outgoingSequence - 1 - i ) & PACKET_MASK; + if ( cl->snap.ps.commandTime >= cl->outPackets[ packetNum ].p_serverTime ) { + cl->snap.ping = cls.realtime - cl->outPackets[ packetNum ].p_realtime; + break; + } + } + // save the frame off in the backup array for later delta comparisons + cl->snapshots[cl->snap.messageNum & PACKET_MASK] = cl->snap; + + if (cl_shownet->integer == 3) { + Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl->snap.messageNum, + cl->snap.deltaNum, cl->snap.ping ); + } + + cl->newSnapshots = qtrue; +} + + +/* +================ +CL_ParseSetGame + +rww - Update fs_game, this message is so we can use the ext_data +*_overrides.txt files for mods. +================ +*/ +void MSG_CheckNETFPSFOverrides(qboolean psfOverrides); +void FS_UpdateGamedir(void); +void CL_ParseSetGame( msg_t *msg ) +{ + char newGameDir[MAX_QPATH]; + int i = 0; + char next; + + while (i < MAX_QPATH) + { + next = MSG_ReadByte( msg ); + + if (next) + { //if next is 0 then we have finished reading to the end of the message + newGameDir[i] = next; + } + else + { + break; + } + i++; + } + newGameDir[i] = 0; + + Cvar_Set("fs_game", newGameDir); + + //Update the search path for the mod dir + FS_UpdateGamedir(); + + //Now update the overrides manually +#ifndef _XBOX // No mods on Xbox + MSG_CheckNETFPSFOverrides(qfalse); + MSG_CheckNETFPSFOverrides(qtrue); +#endif +} + + +//===================================================================== + +int cl_connectedToPureServer; +int cl_connectedGAME; +int cl_connectedCGAME; +int cl_connectedUI; + +/* +================== +CL_SystemInfoChanged + +The systeminfo configstring has been changed, so parse +new information out of it. This will happen at every +gamestate, and possibly during gameplay. +================== +*/ +void CL_SystemInfoChanged( void ) { + char *systemInfo; + const char *s, *t; + char key[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + qboolean gameSet; + + systemInfo = cl->gameState.stringData + cl->gameState.stringOffsets[ CS_SYSTEMINFO ]; + cl->serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) ); + + // don't set any vars when playing a demo +#ifndef _XBOX // No demos on Xbox + if ( clc->demoplaying ) { + return; + } +#endif + + s = Info_ValueForKey( systemInfo, "sv_cheats" ); + if ( atoi(s) == 0 ) + { + Cvar_SetCheatState(); + } + + // check pure server string + s = Info_ValueForKey( systemInfo, "sv_paks" ); + t = Info_ValueForKey( systemInfo, "sv_pakNames" ); + FS_PureServerSetLoadedPaks( s, t ); + + s = Info_ValueForKey( systemInfo, "sv_referencedPaks" ); + t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" ); + FS_PureServerSetReferencedPaks( s, t ); + + gameSet = qfalse; + // scan through all the variables in the systeminfo and locally set cvars to match + s = systemInfo; + while ( s ) { + Info_NextPair( &s, key, value ); + if ( !key[0] ) { + break; + } + // ehw! + if ( !Q_stricmp( key, "fs_game" ) ) { + gameSet = qtrue; + } + + Cvar_Set( key, value ); + } + // if game folder should not be set and it is set at the client side + if ( !gameSet && *Cvar_VariableString("fs_game") ) { + Cvar_Set( "fs_game", "" ); + } + cl_connectedToPureServer = Cvar_VariableValue( "sv_pure" ); + + cl_connectedGAME = atoi(Info_ValueForKey( systemInfo, "vm_game" )); + cl_connectedCGAME = atoi(Info_ValueForKey( systemInfo, "vm_cgame" )); + cl_connectedUI = atoi(Info_ValueForKey( systemInfo, "vm_ui" )); +} + +/* +void CL_ParseAutomapSymbols ( msg_t* msg ) +{ + int i; + + clc->rmgAutomapSymbolCount = (unsigned short) MSG_ReadShort ( msg ); + + for ( i = 0; i < clc->rmgAutomapSymbolCount; i ++ ) + { + clc->rmgAutomapSymbols[i].mType = (int)MSG_ReadByte ( msg ); + clc->rmgAutomapSymbols[i].mSide = (int)MSG_ReadByte ( msg ); + clc->rmgAutomapSymbols[i].mOrigin[0] = (float)MSG_ReadLong ( msg ); + clc->rmgAutomapSymbols[i].mOrigin[1] = (float)MSG_ReadLong ( msg ); + } +} + +void CL_ParseRMG ( msg_t* msg ) +{ + clc->rmgHeightMapSize = (unsigned short)MSG_ReadShort ( msg ); + if ( !clc->rmgHeightMapSize ) + { + return; + } + + z_stream zdata; + int size; + unsigned char heightmap1[15000]; + + if ( MSG_ReadBits ( msg, 1 ) ) + { + // Read the heightmap + memset(&zdata, 0, sizeof(z_stream)); + inflateInit ( &zdata, Z_SYNC_FLUSH ); + + MSG_ReadData ( msg, heightmap1, clc->rmgHeightMapSize ); + + zdata.next_in = heightmap1; + zdata.avail_in = clc->rmgHeightMapSize; + zdata.next_out = (unsigned char*)clc->rmgHeightMap; + zdata.avail_out = MAX_HEIGHTMAP_SIZE; + inflate (&zdata); + + clc->rmgHeightMapSize = zdata.total_out; + + inflateEnd(&zdata); + } + else + { + MSG_ReadData ( msg, (unsigned char*)clc->rmgHeightMap, clc->rmgHeightMapSize ); + } + + size = (unsigned short)MSG_ReadShort ( msg ); + + if ( MSG_ReadBits ( msg, 1 ) ) + { + // Read the flatten map + memset(&zdata, 0, sizeof(z_stream)); + inflateInit ( &zdata, Z_SYNC_FLUSH ); + + MSG_ReadData ( msg, heightmap1, size ); + + zdata.next_in = heightmap1; + zdata.avail_in = clc->rmgHeightMapSize; + zdata.next_out = (unsigned char*)clc->rmgFlattenMap; + zdata.avail_out = MAX_HEIGHTMAP_SIZE; + inflate (&zdata ); + inflateEnd(&zdata); + } + else + { + MSG_ReadData ( msg, (unsigned char*)clc->rmgFlattenMap, size ); + } + + // Read the seed + clc->rmgSeed = MSG_ReadLong ( msg ); + + CL_ParseAutomapSymbols ( msg ); +} +*/ + +/* +================== +CL_ParseGamestate +================== +*/ +void CL_ParseGamestate( msg_t *msg ) { + int i; + entityState_t *es; + int newnum; + entityState_t nullstate; + int cmd; + char *s; + + Con_Close(); + + clc->connectPacketCount = 0; + + // wipe local client state + CL_ClearState(); + +#ifdef _DONETPROFILE_ + int startBytes,endBytes; + startBytes=msg->readcount; +#endif + + // a gamestate always marks a server command sequence + clc->serverCommandSequence = MSG_ReadLong( msg ); + + // parse all the configstrings and baselines + cl->gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings + while ( 1 ) { + cmd = MSG_ReadByte( msg ); + + if ( cmd == svc_EOF ) { + break; + } + + if ( cmd == svc_configstring ) { + int len, start; + + start = msg->readcount; + + i = MSG_ReadShort( msg ); + if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } + s = MSG_ReadBigString( msg ); + + if (cl_shownet->integer >= 2) + { + Com_Printf("%3i: %d: %s\n", start, i, s); + } + + /* + if (i == CS_SERVERINFO) + { //get the special value here + char *f = strstr(s, "g_debugMelee"); + if (f) + { + while (*f && *f != '\\') + { //find the \ after it + f++; + } + if (*f == '\\') + { //got it + int i = 0; + + f++; + while (*f && *f != '\\' && i < 128) + { + hiddenCvarVal[i] = *f; + i++; + f++; + } + hiddenCvarVal[i] = 0; + + //resume here + s = f; + } + } + } + */ + + len = strlen( s ); + + if ( len + 1 + cl->gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl->gameState.stringOffsets[ i ] = cl->gameState.dataCount; + Com_Memcpy( cl->gameState.stringData + cl->gameState.dataCount, s, len + 1 ); + cl->gameState.dataCount += len + 1; + } else if ( cmd == svc_baseline ) { + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + if ( newnum < 0 || newnum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); + } + Com_Memset (&nullstate, 0, sizeof(nullstate)); + es = &cl->entityBaselines[ newnum ]; + MSG_ReadDeltaEntity( msg, &nullstate, es, newnum ); + } else { + Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); + } + } + + clc->clientNum = MSG_ReadLong(msg); + // read the checksum feed + clc->checksumFeed = MSG_ReadLong( msg ); + +/* + CL_ParseRMG ( msg ); //rwwRMG - get info for it from the server +*/ + +#ifdef _DONETPROFILE_ + endBytes=msg->readcount; +// ClReadProf().AddField("svc_gamestate",endBytes-startBytes); +#endif + + // parse serverId and other cvars + CL_SystemInfoChanged(); + + // reinitialize the filesystem if the game directory has changed + if( FS_ConditionalRestart( clc->checksumFeed ) ) { + // don't set to true because we yet have to start downloading + // enabling this can cause double loading of a map when connecting to + // a server which has a different game directory set + //clc->downloadRestart = qtrue; + } + + // This used to call CL_StartHunkUsers, but now we enter the download state before loading the + // cgame + CL_InitDownloads(); + + // make sure the game starts + Cvar_Set( "cl_paused", "0" ); +} + + +//===================================================================== + +/* +===================== +CL_ParseDownload + +A download message has been received from the server +===================== +*/ +void CL_ParseDownload ( msg_t *msg ) { +#ifdef _XBOX + assert(!"Xbox received a download message. Unsupported!"); +#else + int size; + unsigned char data[MAX_MSGLEN]; + int block; + + // read the data + block = (unsigned short)MSG_ReadShort ( msg ); + + if ( !block ) + { + // block zero is special, contains file size + clc->downloadSize = MSG_ReadLong ( msg ); + + Cvar_SetValue( "cl_downloadSize", clc->downloadSize ); + + if (clc->downloadSize < 0) + { + Com_Error(ERR_DROP, MSG_ReadString( msg ) ); + return; + } + } + + size = (unsigned short)MSG_ReadShort ( msg ); + if (size > 0) + MSG_ReadData( msg, data, size ); + + if (clc->downloadBlock != block) { + Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", clc->downloadBlock, block); + return; + } + + // open the file if not opened yet + if (!clc->download) + { + if (!*clc->downloadTempName) { + Com_Printf("Server sending download, but no download was requested\n"); + CL_AddReliableCommand( "stopdl" ); + return; + } + + clc->download = FS_SV_FOpenFileWrite( clc->downloadTempName ); + + if (!clc->download) { + Com_Printf( "Could not create %s\n", clc->downloadTempName ); + CL_AddReliableCommand( "stopdl" ); + CL_NextDownload(); + return; + } + } + + if (size) + FS_Write( data, size, clc->download ); + + CL_AddReliableCommand( va("nextdl %d", clc->downloadBlock) ); + clc->downloadBlock++; + + clc->downloadCount += size; + + // So UI gets access to it + Cvar_SetValue( "cl_downloadCount", clc->downloadCount ); + + if (!size) { // A zero length block means EOF + if (clc->download) { + FS_FCloseFile( clc->download ); + clc->download = 0; + + // rename the file + FS_SV_Rename ( clc->downloadTempName, clc->downloadName ); + } + *clc->downloadTempName = *clc->downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + + // send intentions now + // We need this because without it, we would hold the last nextdl and then start + // loading right away. If we take a while to load, the server is happily trying + // to send us that last block over and over. + // Write it twice to help make sure we acknowledge the download + CL_WritePacket(); + CL_WritePacket(); + + // get another file if needed + CL_NextDownload (); + } +#endif +} + +/* +int CL_GetValueForHidden(const char *s) +{ //string arg here just in case I want to add more sometime and make a lookup table + return atoi(hiddenCvarVal); +} +*/ + +/* +===================== +CL_ParseCommandString + +Command strings are just saved off until cgame asks for them +when it transitions a snapshot +===================== +*/ +void CL_ParseCommandString( msg_t *msg ) { + char *s; + int seq; + int index; + +#ifdef _DONETPROFILE_ + int startBytes,endBytes; + startBytes=msg->readcount; +#endif + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); +#ifdef _DONETPROFILE_ + endBytes=msg->readcount; + ClReadProf().AddField("svc_serverCommand",endBytes-startBytes); +#endif + // see if we have already executed stored it off + if ( clc->serverCommandSequence >= seq ) { + return; + } + clc->serverCommandSequence = seq; + + index = seq & (MAX_RELIABLE_COMMANDS-1); + /* + if (s[0] == 'c' && s[1] == 's' && s[2] == ' ' && s[3] == '0' && s[4] == ' ') + { //yes.. we seem to have an incoming server info. + char *f = strstr(s, "g_debugMelee"); + if (f) + { + while (*f && *f != '\\') + { //find the \ after it + f++; + } + if (*f == '\\') + { //got it + int i = 0; + + f++; + while (*f && *f != '\\' && i < 128) + { + hiddenCvarVal[i] = *f; + i++; + f++; + } + hiddenCvarVal[i] = 0; + + //don't worry about backing over beginning of string I guess, + //we already know we successfully strstr'd the initial string + //which exceeds this length. + //MSG_ReadString appears to just return a static buffer so I + //can stomp over its contents safely. + f--; + *f = '\"'; + f--; + *f = ' '; + f--; + *f = '0'; + f--; + *f = ' '; + f--; + *f = 's'; + f--; + *f = 'c'; + + //the normal configstring gets to start here... + s = f; + } + } + } + */ + Q_strncpyz( clc->serverCommands[ index ], s, sizeof( clc->serverCommands[ index ] ) ); +} + + +/* +===================== +CL_ParseServerMessage +===================== +*/ +void CL_ParseServerMessage( msg_t *msg ) { + int cmd; + + if ( cl_shownet->integer == 1 ) { + Com_Printf ("%i ",msg->cursize); + } else if ( cl_shownet->integer >= 2 ) { + Com_Printf ("------------------\n"); + } + + MSG_Bitstream(msg); + + // get the reliable sequence acknowledge number + clc->reliableAcknowledge = MSG_ReadLong( msg ); + // + if ( clc->reliableAcknowledge < clc->reliableSequence - MAX_RELIABLE_COMMANDS ) { + clc->reliableAcknowledge = clc->reliableSequence; + } + + // + // parse the message + // + while ( 1 ) { + if ( msg->readcount > msg->cursize ) { + Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message"); + break; + } + + cmd = MSG_ReadByte( msg ); + + if ( cmd == svc_EOF) { + SHOWNET( msg, "END OF MESSAGE" ); + break; + } + + if ( cl_shownet->integer >= 2 ) { + if ( !svc_strings[cmd] ) { + Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd ); + } else { + SHOWNET( msg, svc_strings[cmd] ); + } + } + + // other commands + switch ( cmd ) { + default: + Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n"); + break; + case svc_nop: + break; + case svc_serverCommand: + CL_ParseCommandString( msg ); + break; + case svc_gamestate: + CL_ParseGamestate( msg ); + break; + case svc_snapshot: + CL_ParseSnapshot( msg ); + break; + case svc_setgame: + CL_ParseSetGame( msg ); + break; + case svc_download: + CL_ParseDownload( msg ); + break; + case svc_mapchange: + if (cgvm) + { + VM_Call( cgvm, CG_MAP_CHANGE ); + } + break; +#ifdef _XBOX + case svc_newpeer: + { //jsw// new client to add to our XBonlineInfo + // We now get the index that we should use to store this from the server + // That ensures that our playerlist and clientinfo stay in sync! + int index = MSG_ReadLong(msg); + + // Sanity check - server shouldn't have us overwriting an active player + // Unless, we're the server, in which case it will be active. Doh. + assert( com_sv_running->integer || + !xbOnlineInfo.xbPlayerList[index].isActive ); + + // OK. Read directly into the right place + MSG_ReadData(msg, &xbOnlineInfo.xbPlayerList[index], sizeof(XBPlayerInfo)); + + // Need to do address conversion here, voice needs it + XNetXnAddrToInAddr( &xbOnlineInfo.xbPlayerList[index].xbAddr, + Net_GetXNKID(), + &xbOnlineInfo.xbPlayerList[index].inAddr ); + + // VVFIXME - Search for g_Voice here - lots of stuff we're not doing + // g_Voice.OnPlayerJoined( index ); + //if(g_Voice.IsPlayerMuted(&xbOnlineInfo.xbPlayerList[index].xuid)) + //{ + // g_Voice.MutePlayer(&xbOnlineInfo.xbPlayerList[index].xbAddr, &xbOnlineInfo.xbPlayerList[index].xuid, false); + // xbOnlineInfo.xbPlayerList[index].flags |= MUTED_PLAYER; + //} + //else if(Cvar_Get(CVAR_XBL_WT_ACTIVE)->integer) + //{ //reset w/t in case he's on the channel + // g_Voice.EnableWalkieTalkie(); + //} + + XBL_PL_CheckHistoryList(&xbOnlineInfo.xbPlayerList[index]); + break; + } + case svc_removepeer: + { + // Remove a client from our xbOnlineInfo. Our ordering is the same + // as the server, so we just get an index. + int index = MSG_ReadLong(msg); + + // Sanity check + assert( xbOnlineInfo.xbPlayerList[index].isActive && + index != xbOnlineInfo.localIndex ); + + // Clear out important information + xbOnlineInfo.xbPlayerList[index].isActive = false; + g_Voice.OnPlayerDisconnect( &xbOnlineInfo.xbPlayerList[index] ); + XBL_PL_RemoveActivePeer(&xbOnlineInfo.xbPlayerList[index].xuid, index); + break; + } + case svc_xbInfo: + { //jsw//get XNADDR list from server + MSG_ReadData(msg, &xbOnlineInfo, sizeof(XBOnlineInfo)); + + // Immediately convert everyone's XNADDR to an IN_ADDR + for( int i = 0; i < MAX_ONLINE_PLAYERS; ++i ) + { + if( xbOnlineInfo.xbPlayerList[i].isActive ) + XNetXnAddrToInAddr( &xbOnlineInfo.xbPlayerList[i].xbAddr, + Net_GetXNKID(), + &xbOnlineInfo.xbPlayerList[i].inAddr ); + } + + //now find which entry we are + if( logged_on ) + { + PXONLINE_USER pToUser = &XBLLoggedOnUsers[ IN_GetMainController() ]; + BOOL found = false; + for( int i = 0; i < MAX_ONLINE_PLAYERS && !found; i++ ) + { + if( xbOnlineInfo.xbPlayerList[i].isActive && XOnlineAreUsersIdentical(&pToUser->xuid, &xbOnlineInfo.xbPlayerList[i].xuid)) + { + xbOnlineInfo.localIndex = i; + found = true; + } + } + } + else + { + DWORD dwStatus; + IN_ADDR localAddr; + XNetXnAddrToInAddr(Net_GetXNADDR( &dwStatus ), Net_GetXNKID(), &localAddr); + + if( dwStatus != XNET_GET_XNADDR_NONE ) + { + int j; + for( j = 0; j < MAX_ONLINE_PLAYERS; ++j ) + { + if( xbOnlineInfo.xbPlayerList[j].isActive && + localAddr.s_addr == xbOnlineInfo.xbPlayerList[j].inAddr.s_addr ) + break; + } + xbOnlineInfo.localIndex = (j == MAX_ONLINE_PLAYERS) ? -1 : j; + } + } + + // OK. Time to join the chat if we're not in split screen + if (ClientManager::splitScreenMode == false) + g_Voice.JoinSession(); + + // OK. We're playing now. + if (logged_on) + XBL_F_SetState( XONLINE_FRIENDSTATE_FLAG_PLAYING, true ); + + break; + } + case svc_plyrPos0: + case svc_plyrPos1: + case svc_plyrPos2: + case svc_plyrPos3: + case svc_plyrPos4: + case svc_plyrPos5: + case svc_plyrPos6: + case svc_plyrPos7: + case svc_plyrPos8: + case svc_plyrPos9: + { + short pos[3]; + MSG_ReadData( msg, pos, sizeof(pos) ); + + g_Voice.SetClientPosition( cmd - svc_plyrPos0, pos ); + break; + } +#endif + } + } +} + + +extern int scr_center_y; +void SCR_CenterPrint (char *str);//, PalIdx_t colour) diff --git a/codemp/client/cl_scrn.cpp b/codemp/client/cl_scrn.cpp new file mode 100644 index 0000000..06e29d6 --- /dev/null +++ b/codemp/client/cl_scrn.cpp @@ -0,0 +1,486 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc + +#include "client.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "cl_data.h" +#endif + +#include "../xbox/XBVoice.h" + +extern console_t con; +qboolean scr_initialized; // ready to draw + +cvar_t *cl_timegraph; +cvar_t *cl_debuggraph; +cvar_t *cl_graphheight; +cvar_t *cl_graphscale; +cvar_t *cl_graphshift; +#ifndef FINAL_BUILD +cvar_t *cl_debugVoice; +#endif + +/* +================ +SCR_DrawNamedPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { + qhandle_t hShader; + + assert( width != 0 ); + + hShader = re.RegisterShader( picname ); + re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + +/* +================ +SCR_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_FillRect( float x, float y, float width, float height, const float *color ) { + re.SetColor( color ); + + re.DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cls.whiteShader ); + + re.SetColor( NULL ); +} + + +/* +================ +SCR_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + + +//=============================================================================== + +/* +=============================================================================== + +DEBUG GRAPH + +=============================================================================== +*/ +#ifndef _XBOX + +typedef struct +{ + float value; + int color; +} graphsamp_t; + +static int current; +static graphsamp_t values[1024]; + +#endif + +/* +============== +SCR_DebugGraph +============== +*/ +void SCR_DebugGraph (float value, int color) +{ +#ifndef _XBOX + values[current&1023].value = value; + values[current&1023].color = color; + current++; +#endif +} + +/* +============== +SCR_DrawDebugGraph +============== +*/ +void SCR_DrawDebugGraph (void) +{ +#ifndef _XBOX + int a, x, y, w, i, h; + float v; + int color; + + // + // draw the graph + // + w = cls.glconfig.vidWidth; + x = 0; + y = cls.glconfig.vidHeight; + re.SetColor( g_color_table[0] ); + re.DrawStretchPic(x, y - cl_graphheight->integer, + w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader ); + re.SetColor( NULL ); + + for (a=0 ; ainteger + cl_graphshift->integer; + + if (v < 0) + v += cl_graphheight->integer * (1+(int)(-v / cl_graphheight->integer)); + h = (int)v % cl_graphheight->integer; + re.DrawStretchPic( x+w-1-a, y - h, 1, h, 0, 0, 0, 0, cls.whiteShader ); + } +#endif +} + +//============================================================================= + +/* +================== +SCR_Init +================== +*/ +void SCR_Init( void ) { + cl_timegraph = Cvar_Get ("timegraph", "0", CVAR_CHEAT); + cl_debuggraph = Cvar_Get ("debuggraph", "0", CVAR_CHEAT); + cl_graphheight = Cvar_Get ("graphheight", "32", CVAR_CHEAT); + cl_graphscale = Cvar_Get ("graphscale", "1", CVAR_CHEAT); + cl_graphshift = Cvar_Get ("graphshift", "0", CVAR_CHEAT); +#ifndef FINAL_BUILD + cl_debugVoice = Cvar_Get ("debugVoice", "1", 0); +#endif + scr_initialized = qtrue; +} + + +//======================================================= + +/* +================== +SCR_DrawScreenField + +This will be called twice if rendering in stereo mode +================== +*/ + +void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { + re.BeginFrame( stereoFrame ); + +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// cls.state = ClientManager::ActiveClient().state; +#endif + + // wide aspect ratio screens need to have the sides cleared + // unless they are displaying game renderings +#ifndef _XBOX + // Xbox no likey + if ( cls.state != CA_ACTIVE ) { + if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { + re.SetColor( g_color_table[0] ); + re.DrawStretchPic( 0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader ); + re.SetColor( NULL ); + } + } +#endif + + if ( !uivm ) { + Com_DPrintf("draw screen without UI loaded\n"); + return; + } + + // if the menu is going to cover the entire screen, we + // don't need to render anything under it + //actually, yes you do, unless you want clients to cycle out their reliable + //commands from sitting in the menu. -rww + if ( !VM_Call( uivm, UI_IS_FULLSCREEN ) || (!(cls.framecount&7) && cls.state == CA_ACTIVE)) { + + switch( cls.state ) { + default: +#ifdef _XBOX + if(ClientManager::splitScreenMode == qfalse) +#endif + Com_Error( ERR_FATAL, "SCR_DrawScreenField: bad cls.state" ); + break; + case CA_CINEMATIC: + SCR_DrawCinematic(); + break; + case CA_DISCONNECTED: + // force menu up + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + break; + case CA_CONNECTING: + case CA_CHALLENGING: + case CA_CONNECTED: + // connecting clients will only show the connection dialog + // refresh to update the time + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse ); + break; + case CA_LOADING: + case CA_PRIMED: + // also draw the connection information, so it doesn't + // flash away too briefly on local or lan games + // refresh to update the time + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qtrue ); + + // draw the game information screen and loading progress + CL_CGameRendering( stereoFrame ); + break; + case CA_ACTIVE: + CL_CGameRendering( stereoFrame ); +// SCR_DrawDemoRecording(); + break; + } + } + + // the menu draws next + if ( cls.keyCatchers & KEYCATCH_UI && uivm ) { + VM_Call( uivm, UI_REFRESH, cls.realtime ); + } + + // console draws next + Con_DrawConsole (); + +#ifndef FINAL_BUILD + // Debugging output for voice system + if( cl_debugVoice->integer ) + g_Voice.DrawVoiceStats(); +#endif + +#ifdef _DEBUG + // debug graph can be drawn on top of anything + if ( cl_debuggraph->integer || cl_timegraph->integer || cl_debugMove->integer ) { + SCR_DrawDebugGraph (); + } +#endif +} + +/* +================== +SCR_UpdateScreen + +This is called every frame, and can also be called explicitly to flush +text to the screen. +================== +*/ +#ifdef _XBOX +int glcfgWidth = 640, glcfgHeight = 480, glcfgX = 0, glcfgY = 0; +extern glconfig_t glConfig; +#endif + +void SCR_UpdateScreen( void ) { + static int recursive; + + if ( !scr_initialized ) { + return; // not initialized yet + } + + if ( ++recursive > 2 ) { + Com_Error( ERR_FATAL, "SCR_UpdateScreen: recursively called" ); + } + recursive = 1; + +#ifdef _XBOX + qboolean rendered = qfalse; + if (ClientManager::splitScreenMode == qtrue) + { +// cls.state = ClientManager::ActiveClient().state; + + if (cls.state == CA_ACTIVE) + { + int lastClient = ClientManager::ActiveClientNum(); + ClientManager::SetMainClient(0); + ClientManager::ActivateClient(0); + + glcfgHeight = cgs.glconfig.vidHeight = 240;// glConfig.vidHeight = 240; + ClientManager::SetMainClient(0); + ClientManager::ActivateClient(0); + SCR_DrawScreenField( STEREO_CENTER ); + + // Only draw the other screen if there are two clients + if (ClientManager::NumClients() == 2) + { + glcfgY = 240; + ClientManager::SetMainClient(1); + ClientManager::ActivateClient(1); + SCR_DrawScreenField( STEREO_CENTER ); + } + + glcfgY = 0; + glcfgHeight = cgs.glconfig.vidHeight = glConfig.vidHeight = 480; + + ClientManager::SetMainClient(lastClient); + ClientManager::ActivateClient(lastClient); + + rendered = qtrue; + } + else + rendered = qfalse; + } + + if(rendered == qfalse) + { +#endif + + // if running in stereo, we need to draw the frame twice + if ( cls.glconfig.stereoEnabled ) { + SCR_DrawScreenField( STEREO_LEFT ); + SCR_DrawScreenField( STEREO_RIGHT ); + } else { + SCR_DrawScreenField( STEREO_CENTER ); + } + +#ifdef _XBOX + } +#endif + + if ( com_speeds->integer ) { + re.EndFrame( &time_frontend, &time_backend ); + } else { + re.EndFrame( NULL, NULL ); + } + + recursive = 0; +} + +#define MAX_SCR_LINES 10 + +static float scr_centertime_off; +int scr_center_y; +//static string scr_font; +static char scr_centerstring[1024]; +static int scr_center_lines; +static int scr_center_widths[MAX_SCR_LINES]; + +cvar_t *scr_centertime; + +void SCR_CenterPrint (char *str)//, PalIdx_t colour) +{ + char *s, *last, *start, *write_pos, *save_pos; + int num_chars; + int num_lines; + int width; + bool done = false; + bool spaced; + + if (!str) + { + scr_centertime_off = 0; + return; + } + +// scr_font = string("medium"); + + // RWL - commented out +// width = viddef.width / 8; // rjr hardcoded yuckiness +#ifdef _XBOX + if(cg->widescreen) + width = 720 / 8; + else +#endif + width = 640 / 8; // rjr hardcoded yuckiness + width -= 4; + + // RWL - commented out +/* + if (cl.frame.playerstate.remote_type != REMOTE_TYPE_LETTERBOX) + { + width -= 30; + } +*/ + + scr_centertime_off = scr_centertime->value; + + Com_Printf("\n"); + + num_lines = 0; + write_pos = scr_centerstring; + scr_center_lines = 0; + spaced = false; + for(s = start = str, last=NULL, num_chars = 0; !done ; s++) + { + num_chars++; + if ((*s) == ' ') + { + spaced = true; + last = s; + scr_centertime_off += 0.2;//give them an extra 0.05 second for each character + } + + if ((*s) == '\n' || (*s) == 0) + { + last = s; + num_chars = width; + spaced = true; + } + + if (num_chars >= width) + { + scr_centertime_off += 0.8;//give them an extra half second for each newline + if (!last) + { + last = s; + } + if (!spaced) + { + last++; + } + + save_pos = write_pos; + strncpy(write_pos, start, last-start); + write_pos += last-start; + *write_pos = 0; + write_pos++; + + Com_Printf ("%s\n", save_pos); + + // RWL - commented out +// scr_center_widths[scr_center_lines] = re.StrlenFont(save_pos, scr_font);; +#ifdef _XBOX + if(cg->widescreen) + scr_center_widths[scr_center_lines] = 720; + else +#endif + scr_center_widths[scr_center_lines] = 640; + + + scr_center_lines++; + + if ((*s) == NULL || scr_center_lines >= MAX_SCR_LINES) + { + done = true; + } + else + { + s = last; + if (spaced) + { + last++; + } + start = last; + last = NULL; + num_chars = 0; + spaced = false; + } + continue; + } + } + + // echo it to the console + Com_Printf("\n\n"); + Con_ClearNotify (); +} diff --git a/codemp/client/cl_ui.cpp b/codemp/client/cl_ui.cpp new file mode 100644 index 0000000..b8990bf --- /dev/null +++ b/codemp/client/cl_ui.cpp @@ -0,0 +1,882 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "client.h" + +#include "../game/botlib.h" +#include "../qcommon/stringed_ingame.h" + +/* +Ghoul2 Insert Start +*/ + +#if !defined(G2_H_INC) + #include "../ghoul2/G2_local.h" +#endif + +/* +Ghoul2 Insert End +*/ + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#include "../renderer/modelmem.h" +#endif + +extern botlib_export_t *botlib_export; +void SP_Register(const char *Package); + +vm_t *uivm; + +#ifdef USE_CD_KEY + +extern char cl_cdkey[34]; + +#endif // USE_CD_KEY + +/* +==================== +GetClientState +==================== +*/ +static void GetClientState( uiClientState_t *state ) { + state->connectPacketCount = clc->connectPacketCount; +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// cls.state = ClientManager::ActiveClient().state; +#endif + state->connState = cls.state; + Q_strncpyz( state->servername, cls.servername, sizeof( state->servername ) ); + Q_strncpyz( state->messageString, clc->serverMessage, sizeof( state->messageString ) ); + state->clientNum = cl->snap.ps.clientNum; +} + +/* +==================== +CL_GetGlConfig +==================== +*/ +static void CL_GetGlconfig( glconfig_t *config ) { + *config = cls.glconfig; +} + +/* +==================== +GetClipboardData +==================== +*/ +static void GetClipboardData( char *buf, int buflen ) { + char *cbd; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + *buf = 0; + return; + } + + Q_strncpyz( buf, cbd, buflen ); + + Z_Free( cbd ); +} + +/* +==================== +Key_KeynumToStringBuf +==================== +*/ +// only ever called by binding-display code, therefore returns non-technical "friendly" names +// in any language that don't necessarily match those in the config file... +// +void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) +{ + const char *psKeyName = Key_KeynumToString( keynum/*, qtrue */); + + // see if there's a more friendly (or localised) name... + // + const char *psKeyNameFriendly = SE_GetString( va("KEYNAMES_KEYNAME_%s",psKeyName) ); + + Q_strncpyz( buf, (psKeyNameFriendly && psKeyNameFriendly[0]) ? psKeyNameFriendly : psKeyName, buflen ); +} + + +/* +==================== +Key_GetBindingBuf +==================== +*/ +static void Key_GetBindingBuf( int keynum, char *buf, int buflen ) { + char *value; + + value = Key_GetBinding( keynum ); + if ( value ) { + Q_strncpyz( buf, value, buflen ); + } + else { + *buf = 0; + } +} + +/* +==================== +Key_GetCatcher +==================== +*/ +int Key_GetCatcher( void ) { + return cls.keyCatchers; +} + +/* +==================== +Ket_SetCatcher +==================== +*/ +void Key_SetCatcher( int catcher ) { + cls.keyCatchers = catcher; + +#ifdef _XBOX + if(catcher == KEYCATCH_UI) + { +// ModelMem.EnterUI(); + int i; + + for(i=0; istring[0] != 0) { + Com_Memcpy( buf, &cl_cdkey[16], 16); + buf[16] = 0; + } else { + Com_Memcpy( buf, cl_cdkey, 16); + buf[16] = 0; + } +} + + +/* +==================== +CLUI_SetCDKey +==================== +*/ +static void CLUI_SetCDKey( char *buf ) { + cvar_t *fs; + fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) { + Com_Memcpy( &cl_cdkey[16], buf, 16 ); + cl_cdkey[32] = 0; + // set the flag so the fle will be written at the next opportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; + } else { + Com_Memcpy( cl_cdkey, buf, 16 ); + // set the flag so the fle will be written at the next opportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; + } +} + +#endif // USE_CD_KEY + +/* +==================== +GetConfigString +==================== +*/ +static int GetConfigString(int index, char *buf, int size) +{ + int offset; + + if (index < 0 || index >= MAX_CONFIGSTRINGS) + return qfalse; + + offset = cl->gameState.stringOffsets[index]; + if (!offset) { + if( size ) { + buf[0] = 0; + } + return qfalse; + } + + Q_strncpyz( buf, cl->gameState.stringData+offset, size); + + return qtrue; +} + +/* +==================== +FloatAsInt +==================== +*/ +static int FloatAsInt( float f ) { + int temp; + + *(float *)&temp = f; + + return temp; +} + +void *VM_ArgPtr( int intValue ); +#define VMA(x) VM_ArgPtr(args[x]) +#define VMF(x) ((float *)args)[x] + +/* +==================== +CL_UISystemCalls + +The ui module is making a system call +==================== +*/ +int CL_UISystemCalls( int *args ) { + switch( args[0] ) { + //rww - alright, DO NOT EVER add a GAME/CGAME/UI generic call without adding a trap to match, and + //all of these traps must be shared and have cases in sv_game, cl_cgame, and cl_ui. They must also + //all be in the same order, and start at 100. + case TRAP_MEMSET: + Com_Memset( VMA(1), args[2], args[3] ); + return 0; + case TRAP_MEMCPY: + Com_Memcpy( VMA(1), VMA(2), args[3] ); + return 0; + case TRAP_STRNCPY: + return (int)strncpy( (char *)VMA(1), (const char *)VMA(2), args[3] ); + case TRAP_SIN: + return FloatAsInt( sin( VMF(1) ) ); + case TRAP_COS: + return FloatAsInt( cos( VMF(1) ) ); + case TRAP_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + case TRAP_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + case TRAP_MATRIXMULTIPLY: + MatrixMultiply( (vec3_t *)VMA(1), (vec3_t *)VMA(2), (vec3_t *)VMA(3) ); + return 0; + case TRAP_ANGLEVECTORS: + AngleVectors( (const float *)VMA(1), (float *)VMA(2), (float *)VMA(3), (float *)VMA(4) ); + return 0; + case TRAP_PERPENDICULARVECTOR: + PerpendicularVector( (float *)VMA(1), (const float *)VMA(2) ); + return 0; + case TRAP_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + case TRAP_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + case TRAP_TESTPRINTINT: + return 0; + case TRAP_TESTPRINTFLOAT: + return 0; + case TRAP_ACOS: + return FloatAsInt( Q_acos( VMF(1) ) ); + case TRAP_ASIN: + return FloatAsInt( Q_asin( VMF(1) ) ); + + + case UI_ERROR: + Com_Error( ERR_DROP, "%s", VMA(1) ); + return 0; + + case UI_PRINT: + Com_Printf( "%s", VMA(1) ); + return 0; + + case UI_MILLISECONDS: + return Sys_Milliseconds(); + + case UI_CVAR_REGISTER: + Cvar_Register( (vmCvar_t *)VMA(1), (const char *)VMA(2), (const char *)VMA(3), args[4] ); + return 0; + + case UI_CVAR_UPDATE: + Cvar_Update( (vmCvar_t *)VMA(1) ); + return 0; + + case UI_CVAR_SET: + Cvar_Set( (const char *)VMA(1), (const char *)VMA(2) ); + return 0; + + case UI_CVAR_VARIABLEVALUE: + return FloatAsInt( Cvar_VariableValue( (const char *)VMA(1) ) ); + + case UI_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( (const char *)VMA(1), (char *)VMA(2), args[3] ); + return 0; + + case UI_CVAR_SETVALUE: + Cvar_SetValue( (const char *)VMA(1), VMF(2) ); + return 0; + + case UI_CVAR_RESET: + Cvar_Reset( (const char *)VMA(1) ); + return 0; + + case UI_CVAR_CREATE: + Cvar_Get( (const char *)VMA(1), (const char *)VMA(2), args[3] ); + return 0; + + case UI_CVAR_INFOSTRINGBUFFER: + Cvar_InfoStringBuffer( args[1], (char *)VMA(2), args[3] ); + return 0; + + case UI_ARGC: + return Cmd_Argc(); + + case UI_ARGV: + Cmd_ArgvBuffer( args[1], (char *)VMA(2), args[3] ); + return 0; + + case UI_CMD_EXECUTETEXT: + Cbuf_ExecuteText( args[1], (const char *)VMA(2) ); + return 0; + + case UI_FS_FOPENFILE: + return FS_FOpenFileByMode( (const char *)VMA(1), (int *)VMA(2), (fsMode_t)args[3] ); + + case UI_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + + case UI_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + + case UI_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; + + case UI_FS_GETFILELIST: + return FS_GetFileList( (const char *)VMA(1), (const char *)VMA(2), (char *)VMA(3), args[4] ); + + case UI_R_REGISTERMODEL: + return re.RegisterModel( (const char *)VMA(1) ); + + case UI_R_REGISTERSKIN: + return re.RegisterSkin( (const char *)VMA(1) ); + + case UI_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( (const char *)VMA(1) ); + + case UI_R_SHADERNAMEFROMINDEX: + { + char *gameMem = (char *)VMA(1); + const char *retMem = re.ShaderNameFromIndex(args[2]); + if (retMem) + { + strcpy(gameMem, retMem); + } + else + { + gameMem[0] = 0; + } + } + return 0; + + case UI_R_CLEARSCENE: + re.ClearScene(); + return 0; + + case UI_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( (const refEntity_t *)VMA(1) ); + return 0; + + case UI_R_ADDPOLYTOSCENE: + re.AddPolyToScene( args[1], args[2], (const polyVert_t *)VMA(3), 1 ); + return 0; + + case UI_R_ADDLIGHTTOSCENE: + re.AddLightToScene( (const float *)VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + + case UI_R_RENDERSCENE: + re.RenderScene( (const refdef_t *)VMA(1) ); + return 0; + + case UI_R_SETCOLOR: + re.SetColor( (const float *)VMA(1) ); + return 0; + + case UI_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); + return 0; + + case UI_R_MODELBOUNDS: + re.ModelBounds( args[1], (float *)VMA(2), (float *)VMA(3) ); + return 0; + + case UI_UPDATESCREEN: + SCR_UpdateScreen(); + return 0; + + case UI_CM_LERPTAG: + re.LerpTag( (orientation_t *)VMA(1), args[2], args[3], args[4], VMF(5), (const char *)VMA(6) ); + return 0; + + case UI_S_REGISTERSOUND: + return S_RegisterSound( (const char *)VMA(1) ); + + case UI_S_STARTLOCALSOUND: + S_StartLocalSound( args[1], args[2] ); + return 0; + + case UI_KEY_KEYNUMTOSTRINGBUF: + Key_KeynumToStringBuf( args[1], (char *)VMA(2), args[3] ); + return 0; + + case UI_KEY_GETBINDINGBUF: + Key_GetBindingBuf( args[1], (char *)VMA(2), args[3] ); + return 0; + + case UI_KEY_SETBINDING: + Key_SetBinding( args[1], (const char *)VMA(2) ); + return 0; + + case UI_KEY_ISDOWN: + return Key_IsDown( args[1] ); + + case UI_KEY_GETOVERSTRIKEMODE: + return Key_GetOverstrikeMode(); + + case UI_KEY_SETOVERSTRIKEMODE: + Key_SetOverstrikeMode( (qboolean)args[1] ); + return 0; + + case UI_KEY_CLEARSTATES: + Key_ClearStates(); + return 0; + + case UI_KEY_GETCATCHER: + return Key_GetCatcher(); + + case UI_KEY_SETCATCHER: + Key_SetCatcher( args[1] ); + return 0; + + case UI_GETCLIPBOARDDATA: + GetClipboardData( (char *)VMA(1), args[2] ); + return 0; + + case UI_GETCLIENTSTATE: + GetClientState( (uiClientState_t *)VMA(1) ); + return 0; + + case UI_GETGLCONFIG: + CL_GetGlconfig( (glconfig_t *)VMA(1) ); + return 0; + + case UI_GETCONFIGSTRING: + return GetConfigString( args[1], (char *)VMA(2), args[3] ); + + case UI_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + +#ifdef USE_CD_KEY + case UI_GET_CDKEY: + CLUI_GetCDKey( (char *)VMA(1), args[2] ); + return 0; + + case UI_SET_CDKEY: + CLUI_SetCDKey( (char *)VMA(1) ); + return 0; +#endif // USE_CD_KEY + + case UI_R_REGISTERFONT: + return re.RegisterFont( (const char *)VMA(1) ); + + case UI_R_FONT_STRLENPIXELS: + return re.Font_StrLenPixels( (const char *)VMA(1), args[2], VMF(3) ); + + case UI_R_FONT_STRLENCHARS: + return re.Font_StrLenChars( (const char *)VMA(1) ); + + case UI_R_FONT_STRHEIGHTPIXELS: + return re.Font_HeightPixels( args[1], VMF(2) ); + + case UI_R_FONT_DRAWSTRING: + re.Font_DrawString( args[1], args[2], (const char *)VMA(3), (const float *) VMA(4), args[5], args[6], VMF(7) ); + return 0; + + case UI_LANGUAGE_ISASIAN: + return re.Language_IsAsian(); + + case UI_LANGUAGE_USESSPACES: + return re.Language_UsesSpaces(); + + case UI_ANYLANGUAGE_READCHARFROMSTRING: + return re.AnyLanguage_ReadCharFromString( (const char *)VMA(1), (int *) VMA(2), (qboolean *) VMA(3) ); + + case UI_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( (char *)VMA(1) ); + case UI_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( (const char *)VMA(1) ); + case UI_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case UI_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], (struct pc_token_s *)VMA(2) ); + case UI_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], (char *)VMA(2), (int *)VMA(3) ); + case UI_PC_LOAD_GLOBAL_DEFINES: + return botlib_export->PC_LoadGlobalDefines ( (char *)VMA(1) ); + case UI_PC_REMOVE_ALL_GLOBAL_DEFINES: + botlib_export->PC_RemoveAllGlobalDefines ( ); + return 0; + + case UI_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + case UI_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( (const char *)VMA(1), (const char *)VMA(2), qfalse); + return 0; + + case UI_REAL_TIME: + return Com_RealTime( (struct qtime_s *)VMA(1) ); + + case UI_R_REMAP_SHADER: + re.RemapShader( (const char *)VMA(1), (const char *)VMA(2), (const char *)VMA(3) ); + return 0; + +#ifdef USE_CD_KEY + case UI_VERIFY_CDKEY: + return CL_CDKeyValidate((const char *)VMA(1), (const char *)VMA(2)); +#endif // USE_CD_KEY + + case UI_SP_GETNUMLANGUAGES: + assert( 0 ); + return 0; +// return SE_GetNumLanguages(); + + case UI_SP_GETLANGUAGENAME: + assert( 0 ); + return 0; + + case UI_SP_GETSTRINGTEXTSTRING: + const char* text; + + assert(VMA(1)); + assert(VMA(2)); + text = SE_GetString((const char *) VMA(1)); + Q_strncpyz( (char *) VMA(2), text, args[3] ); + return qtrue; + +/* +Ghoul2 Insert Start +*/ +/* +Ghoul2 Insert Start +*/ + + case UI_G2_LISTSURFACES: + G2API_ListSurfaces( (CGhoul2Info *) args[1] ); + return 0; + + case UI_G2_LISTBONES: + G2API_ListBones( (CGhoul2Info *) args[1], args[2]); + return 0; + + case UI_G2_HAVEWEGHOULMODELS: + return G2API_HaveWeGhoul2Models( *((CGhoul2Info_v *)args[1]) ); + + case UI_G2_SETMODELS: + G2API_SetGhoul2ModelIndexes( *((CGhoul2Info_v *)args[1]),(qhandle_t *)VMA(2),(qhandle_t *)VMA(3)); + return 0; + + case UI_G2_GETBOLT: + return G2API_GetBoltMatrix(*((CGhoul2Info_v *)args[1]), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + + case UI_G2_GETBOLT_NOREC: + gG2_GBMNoReconstruct = qtrue; + return G2API_GetBoltMatrix(*((CGhoul2Info_v *)args[1]), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + + case UI_G2_GETBOLT_NOREC_NOROT: + gG2_GBMNoReconstruct = qtrue; + gG2_GBMUseSPMethod = qtrue; + return G2API_GetBoltMatrix(*((CGhoul2Info_v *)args[1]), args[2], args[3], (mdxaBone_t *)VMA(4), (const float *)VMA(5),(const float *)VMA(6), args[7], (qhandle_t *)VMA(8), (float *)VMA(9)); + + case UI_G2_INITGHOUL2MODEL: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 0; +#endif + return G2API_InitGhoul2Model((CGhoul2Info_v **)VMA(1), (const char *)VMA(2), args[3], (qhandle_t) args[4], + (qhandle_t) args[5], args[6], args[7]); + + + case UI_G2_COLLISIONDETECT: + return 0; //not supported for ui + + case UI_G2_ANGLEOVERRIDE: + return G2API_SetBoneAngles(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3), (float *)VMA(4), args[5], + (const Eorientations) args[6], (const Eorientations) args[7], (const Eorientations) args[8], + (qhandle_t *)VMA(9), args[10], args[11] ); + + case UI_G2_CLEANMODELS: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 0; +#endif + G2API_CleanGhoul2Models((CGhoul2Info_v **)VMA(1)); + // G2API_CleanGhoul2Models((CGhoul2Info_v **)args[1]); + return 0; + + case UI_G2_PLAYANIM: + return G2API_SetBoneAnim(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3), args[4], args[5], + args[6], VMF(7), args[8], VMF(9), args[10]); + + case UI_G2_GETBONEANIM: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + int modelIndex = args[10]; + + return G2API_GetBoneAnim(&g2[modelIndex], (const char*)VMA(2), args[3], (float *)VMA(4), (int *)VMA(5), + (int *)VMA(6), (int *)VMA(7), (float *)VMA(8), (int *)VMA(9)); + } + + case UI_G2_GETBONEFRAME: + { //rwwFIXMEFIXME: Just make a G2API_GetBoneFrame func too. This is dirty. + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + int modelIndex = args[6]; + int iDontCare1 = 0, iDontCare2 = 0, iDontCare3 = 0; + float fDontCare1 = 0; + + return G2API_GetBoneAnim(&g2[modelIndex], (const char*)VMA(2), args[3], (float *)VMA(4), &iDontCare1, + &iDontCare2, &iDontCare3, &fDontCare1, (int *)VMA(5)); + } + + case UI_G2_GETGLANAME: + // return (int)G2API_GetGLAName(*((CGhoul2Info_v *)VMA(1)), args[2]); + { + char *point = ((char *)VMA(3)); + char *local; + local = G2API_GetGLAName(*((CGhoul2Info_v *)args[1]), args[2]); + if (local) + { + strcpy(point, local); + } + } + return 0; + + case UI_G2_COPYGHOUL2INSTANCE: + return (int)G2API_CopyGhoul2Instance(*((CGhoul2Info_v *)args[1]), *((CGhoul2Info_v *)args[2]), args[3]); + + case UI_G2_COPYSPECIFICGHOUL2MODEL: + G2API_CopySpecificG2Model(*((CGhoul2Info_v *)args[1]), args[2], *((CGhoul2Info_v *)args[3]), args[4]); + return 0; + + case UI_G2_DUPLICATEGHOUL2INSTANCE: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 0; +#endif + G2API_DuplicateGhoul2Instance(*((CGhoul2Info_v *)args[1]), (CGhoul2Info_v **)VMA(2)); + return 0; + + case UI_G2_HASGHOUL2MODELONINDEX: + return (int)G2API_HasGhoul2ModelOnIndex((CGhoul2Info_v **)VMA(1), args[2]); + //return (int)G2API_HasGhoul2ModelOnIndex((CGhoul2Info_v **)args[1], args[2]); + + case UI_G2_REMOVEGHOUL2MODEL: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 0; +#endif + return (int)G2API_RemoveGhoul2Model((CGhoul2Info_v **)VMA(1), args[2]); + //return (int)G2API_RemoveGhoul2Model((CGhoul2Info_v **)args[1], args[2]); + + case UI_G2_ADDBOLT: + return G2API_AddBolt(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3)); + +// case UI_G2_REMOVEBOLT: +// return G2API_RemoveBolt(*((CGhoul2Info_v *)VMA(1)), args[2]); + + case UI_G2_SETBOLTON: + G2API_SetBoltInfo(*((CGhoul2Info_v *)args[1]), args[2], args[3]); + return 0; + +#ifdef _SOF2 + case UI_G2_ADDSKINGORE: + G2API_AddSkinGore(*((CGhoul2Info_v *)args[1]),*(SSkinGoreData *)VMA(2)); + return 0; +#endif // _SOF2 +/* +Ghoul2 Insert End +*/ + case UI_G2_SETROOTSURFACE: + return G2API_SetRootSurface(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3)); + + case UI_G2_SETSURFACEONOFF: + return G2API_SetSurfaceOnOff(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), /*(const int)VMA(3)*/args[3]); + + case UI_G2_SETNEWORIGIN: + return G2API_SetNewOrigin(*((CGhoul2Info_v *)args[1]), /*(const int)VMA(2)*/args[2]); + + case UI_G2_GETTIME: + return G2API_GetTime(0); + + case UI_G2_SETTIME: + G2API_SetTime(args[1], args[2]); + return 0; + + case UI_G2_SETRAGDOLL: + return 0; //not supported for ui + break; + case UI_G2_ANIMATEG2MODELS: + return 0; //not supported for ui + break; + + case UI_G2_SETBONEIKSTATE: + return G2API_SetBoneIKState(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3), args[4], (sharedSetBoneIKStateParams_t *)VMA(5)); + case UI_G2_IKMOVE: + return G2API_IKMove(*((CGhoul2Info_v *)args[1]), args[2], (sharedIKMoveParams_t *)VMA(3)); + + case UI_G2_GETSURFACENAME: + { //Since returning a pointer in such a way to a VM seems to cause MASSIVE FAILURE, we will shove data into the pointer the vm passes instead + char *point = ((char *)VMA(4)); + char *local; + int modelindex = args[3]; + + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + + local = G2API_GetSurfaceName(&g2[modelindex], args[2]); + if (local) + { + strcpy(point, local); + } + } + + return 0; + case UI_G2_SETSKIN: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + int modelIndex = args[2]; + + return G2API_SetSkin(&g2[modelIndex], args[3], args[4]); + } + + case UI_G2_ATTACHG2MODEL: + { + CGhoul2Info_v *g2From = ((CGhoul2Info_v *)args[1]); + CGhoul2Info_v *g2To = ((CGhoul2Info_v *)args[3]); + + return G2API_AttachG2Model(g2From[0], args[2], g2To[0], args[4], args[5]); + } +/* +Ghoul2 Insert End +*/ + default: + Com_Error( ERR_DROP, "Bad UI system trap: %i", args[0] ); + + } + + return 0; +} + +/* +==================== +CL_ShutdownUI +==================== +*/ +void CL_ShutdownUI( void ) { + cls.keyCatchers &= ~KEYCATCH_UI; + cls.uiStarted = qfalse; + if ( !uivm ) { + return; + } + VM_Call( uivm, UI_SHUTDOWN ); + VM_Call( uivm, UI_MENU_RESET ); + VM_Free( uivm ); + uivm = NULL; +} + +/* +==================== +CL_InitUI +==================== +*/ + +void CL_InitUI( void ) { + int v; + vmInterpret_t interpret; + + // load the dll or bytecode + if ( cl_connectedToPureServer != 0 ) { +#if 0 + // if sv_pure is set we only allow qvms to be loaded + interpret = VMI_COMPILED; +#else //load the module type based on what the server is doing -rww + interpret = (vmInterpret_t)cl_connectedUI; +#endif + } + else { + interpret = (vmInterpret_t)(int)Cvar_VariableValue( "vm_ui" ); + } + uivm = VM_Create( "ui", CL_UISystemCalls, interpret ); + if ( !uivm ) { + Com_Error( ERR_FATAL, "VM_Create on UI failed" ); + } + + // sanity check + v = VM_Call( uivm, UI_GETAPIVERSION ); + if (v != UI_API_VERSION) { + Com_Error( ERR_DROP, "User Interface is version %d, expected %d", v, UI_API_VERSION ); + cls.uiStarted = qfalse; + } + else { + // init for this gamestate + //rww - changed to <= CA_ACTIVE, because that is the state when we did a vid_restart + //ingame (was just < CA_ACTIVE before, resulting in ingame menus getting wiped and + //not reloaded on vid restart from ingame menu) +#ifdef _XBOX +// if(ClientManager::splitScreenMode == qtrue) +// cls.state = ClientManager::ActiveClient().state; +#endif + +#ifdef _XBOX + // This is kinda a hack to get the UI models that are ALWAYS loaded + // forced into the UI model memory slot +// ModelMem.EnterUI(); +#endif + VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state <= CA_ACTIVE) ); + +#ifdef _XBOX +// ModelMem.ExitUI(); +#endif + } +} + +qboolean UI_usesUniqueCDKey() { + if (uivm) { + return (qboolean)(VM_Call( uivm, UI_HASUNIQUECDKEY) == qtrue); + } else { + return qfalse; + } +} + +/* +==================== +UI_GameCommand + +See if the current console command is claimed by the ui +==================== +*/ +qboolean UI_GameCommand( void ) { + if ( !uivm ) { + return qfalse; + } + + return (qboolean)VM_Call( uivm, UI_CONSOLE_COMMAND, cls.realtime ); +} diff --git a/codemp/client/client.h b/codemp/client/client.h new file mode 100644 index 0000000..4238353 --- /dev/null +++ b/codemp/client/client.h @@ -0,0 +1,575 @@ +// client.h -- primary header for client +#pragma once +#if !defined(CLIENT_H_INC) +#define CLIENT_H_INC + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +#include "../ui/ui_public.h" +#include "keys.h" +#include "snd_public.h" +#include "../cgame/cg_public.h" +#include "../game/bg_public.h" + +#ifdef _XBOX +#include +#endif + +#define RETRANSMIT_TIMEOUT 3000 // time between connection packet retransmits + +// Wind +extern vec3_t cl_windVec; + +// snapshots are a view of the server at a given time +typedef struct { + qboolean valid; // cleared if delta parsing was invalid + int snapFlags; // rate delayed and dropped commands + + int serverTime; // server time the message is valid for (in msec) + + int messageNum; // copied from netchan->incoming_sequence + int deltaNum; // messageNum the delta is from + int ping; // time from when cmdNum-1 was sent to time packet was reeceived + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + int cmdNum; // the next cmdNum the server is expecting + playerState_t ps; // complete information about the current player at this time + playerState_t vps; //vehicle I'm riding's playerstate (if applicable) -rww + + int numEntities; // all of the entities that need to be presented + int parseEntitiesNum; // at the time of this snapshot + + int serverCommandNum; // execute all commands up to this before + // making the snapshot current +} clSnapshot_t; + + + +/* +============================================================================= + +the clientActive_t structure is wiped completely at every +new gamestate_t, potentially several times during an established connection + +============================================================================= +*/ + +typedef struct { + int p_cmdNumber; // cl.cmdNumber when packet was sent + int p_serverTime; // usercmd->serverTime when packet was sent + int p_realtime; // cls.realtime when packet was sent +} outPacket_t; + +// the parseEntities array must be large enough to hold PACKET_BACKUP frames of +// entities, so that when a delta compressed message arives from the server +// it can be un-deltad from the original +#ifdef _XBOX +#define MAX_PARSE_ENTITIES 1024 +#else +#define MAX_PARSE_ENTITIES 2048 +#endif + +extern int g_console_field_width; + +typedef struct { + int timeoutcount; // it requres several frames in a timeout condition + // to disconnect, preventing debugging breaks from + // causing immediate disconnects on continue + clSnapshot_t snap; // latest received from server + + int serverTime; // may be paused during play + int oldServerTime; // to prevent time from flowing bakcwards + int oldFrameServerTime; // to check tournament restarts + int serverTimeDelta; // cl.serverTime = cls.realtime + cl.serverTimeDelta + // this value changes as net lag varies + qboolean extrapolatedSnapshot; // set if any cgame frame has been forced to extrapolate + // cleared when CL_AdjustTimeDelta looks at it + qboolean newSnapshots; // set on parse of any valid packet + + gameState_t gameState; // configstrings + char mapname[MAX_QPATH]; // extracted from CS_SERVERINFO + + int parseEntitiesNum; // index (not anded off) into cl_parse_entities[] + + int mouseDx[2], mouseDy[2]; // added to by mouse events + int mouseIndex; + int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events + + // cgame communicates a few values to the client system + int cgameUserCmdValue; // current weapon to add to usercmd_t + vec3_t cgameViewAngleForce; + int cgameViewAngleForceTime; + float cgameSensitivity; + + int cgameForceSelection; + int cgameInvenSelection; + + qboolean gcmdSendValue; + qboolean gcmdSentValue; + byte gcmdValue; + + // cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last + // properly generated command + usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds + int cmdNumber; // incremented each frame, because multiple + // frames may need to be packed into a single packet + + outPacket_t outPackets[PACKET_BACKUP]; // information about each packet we have sent out + + // the client maintains its own idea of view angles, which are + // sent to the server each frame. It is cleared to 0 upon entering each level. + // the server sends a delta each frame which is added to the locally + // tracked view angles to account for standing on rotating objects, + // and teleport direction changes + vec3_t viewangles; + + int serverId; // included in each client message so the server + // can tell if it is for a prior map_restart + // big stuff at end of structure so most offsets are 15 bits or less + clSnapshot_t snapshots[PACKET_BACKUP]; + + entityState_t entityBaselines[MAX_GENTITIES]; // for delta compression when not in previous frame + + entityState_t parseEntities[MAX_PARSE_ENTITIES]; + + char *mSharedMemory; +} clientActive_t; + +//extern clientActive_t g_cl; +extern clientActive_t *cl; + +#define MAX_HEIGHTMAP_SIZE 16000 + +typedef struct +{ + int mType; + int mSide; + vec3_t mOrigin; + +} rmAutomapSymbol_t; + +#define MAX_AUTOMAP_SYMBOLS 512 + +/* +============================================================================= + +the clientConnection_t structure is wiped when disconnecting from a server, +either to go to a full screen console, play a demo, or connect to a different server + +A connection can be to either a server through the network layer or a +demo through a file. + +============================================================================= +*/ + + +typedef struct { + + int clientNum; + int lastPacketSentTime; // for retransmits during connection + int lastPacketTime; // for timeouts + + netadr_t serverAddress; + int connectTime; // for connection retransmits + int connectPacketCount; // for display on connection dialog + char serverMessage[MAX_STRING_TOKENS]; // for display on connection dialog + + int challenge; // from the server to use for connecting + int checksumFeed; // from the server for checksum calculations + + // these are our reliable messages that go to the server + int reliableSequence; + int reliableAcknowledge; // the last one the server has executed + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + + // server message (unreliable) and command (reliable) sequence + // numbers are NOT cleared at level changes, but continue to + // increase as long as the connection is valid + + // message sequence is used by both the network layer and the + // delta compression layer + int serverMessageSequence; + + // reliable messages received from server + int serverCommandSequence; + int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand + char serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + +#ifndef _XBOX // No downloading or demos on Xbox + // file transfer from server + fileHandle_t download; + char downloadTempName[MAX_OSPATH]; + char downloadName[MAX_OSPATH]; + int downloadNumber; + int downloadBlock; // block we are waiting for + int downloadCount; // how many bytes we got + int downloadSize; // how many bytes we got + char downloadList[MAX_INFO_STRING]; // list of paks we need to download + qboolean downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak + + // demo information + char demoName[MAX_QPATH]; + qboolean spDemoRecording; + qboolean demorecording; + qboolean demoplaying; + qboolean demowaiting; // don't record until a non-delta message is received + qboolean firstDemoFrameSkipped; + fileHandle_t demofile; + + int timeDemoFrames; // counter of rendered frames + int timeDemoStart; // cls.realtime before first frame + int timeDemoBaseTime; // each frame will be at this time + frameNum * 50 +#endif + + // big stuff at end of structure so most offsets are 15 bits or less + netchan_t netchan; + + //rwwRMG - added: +/* + int rmgSeed; + int rmgHeightMapSize; + unsigned char rmgHeightMap[MAX_HEIGHTMAP_SIZE]; + unsigned char rmgFlattenMap[MAX_HEIGHTMAP_SIZE]; + rmAutomapSymbol_t rmgAutomapSymbols[MAX_AUTOMAP_SYMBOLS]; + int rmgAutomapSymbolCount; +*/ +} clientConnection_t; + +extern clientConnection_t g_clc; +extern clientConnection_t *clc; + +/* +================================================================== + +the clientStatic_t structure is never wiped, and is used even when +no client connection is active at all + +================================================================== +*/ +/* +typedef struct { + netadr_t adr; + int start; + int time; + char info[MAX_INFO_STRING]; + XNADDR xnaddr; +} ping_t; +*/ + +typedef struct { +// char hostName[MAX_NAME_LENGTH]; + char mapName[MAX_NAME_LENGTH]; + int gameType; + int clients; + int maxClients; + + qboolean saberOnly; + qboolean forceDisable; + + XNKID SessionID; + XNKEY KeyExchangeKey; + XNADDR HostAddress; + + int lastUpdate; +} serverInfo_t; + +typedef struct { + byte ip[4]; + unsigned short port; +} serverAddress_t; + +typedef struct { + connstate_t state; // connection status + int keyCatchers; // bit flags + + char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect) + + // when the server clears the hunk, all of these must be restarted + qboolean rendererStarted; + qboolean soundStarted; + qboolean soundRegistered; + qboolean uiStarted; + qboolean cgameStarted; + + int framecount; + int frametime; // msec since last frame + + int realtime; // ignores pause + int realFrametime; // ignoring pause, so console always works + + int numlocalservers; + serverInfo_t localServers[MAX_OTHER_SERVERS]; + + int pingUpdateSource; // source currently pinging or updating + + // rendering info + glconfig_t glconfig; +// qhandle_t charSetShader; + qhandle_t whiteShader; +// qhandle_t consoleShader; + +#ifdef _XBOX + short mainGamepad; +#endif +} clientStatic_t; + +#ifdef _XBOX +#define CON_TEXTSIZE 256 +#else +#define CON_TEXTSIZE 32768 +#endif +#define NUM_CON_TIMES 4 + +typedef struct { + qboolean initialized; + + short text[CON_TEXTSIZE]; + int current; // line where next message will be printed + int x; // offset in current line for next print + int display; // bottom of console displays this line + + int linewidth; // characters across screen + int totallines; // total lines in console scrollback + + float xadjust; // for wide aspect screens + float yadjust; // for wide aspect screens + + float displayFrac; // aproaches finalFrac at scr_conspeed + float finalFrac; // 0.0 to 1.0 lines of console to display + + int vislines; // in scanlines + + int times[NUM_CON_TIMES]; // cls.realtime time the line was generated + // for transparent notify lines + vec4_t color; +} console_t; + +extern clientStatic_t cls; + +//============================================================================= + +extern vm_t *cgvm; // interface to cgame dll or vm +extern vm_t *uivm; // interface to ui dll or vm +extern refexport_t re; // interface to refresh .dll + + +// +// cvars +// +extern cvar_t *cl_nodelta; +extern cvar_t *cl_debugMove; +extern cvar_t *cl_noprint; +extern cvar_t *cl_timegraph; +extern cvar_t *cl_maxpackets; +extern cvar_t *cl_packetdup; +extern cvar_t *cl_shownet; +extern cvar_t *cl_showSend; +extern cvar_t *cl_timeNudge; +extern cvar_t *cl_showTimeDelta; +extern cvar_t *cl_freezeDemo; + +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_run; +extern cvar_t *cl_anglespeedkey; + +extern cvar_t *cl_sensitivity; +extern cvar_t *cl_freelook; + +extern cvar_t *cl_mouseAccel; +extern cvar_t *cl_showMouseRate; + +extern cvar_t *m_pitchVeh; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; +extern cvar_t *m_filter; + +extern cvar_t *cl_timedemo; + +extern cvar_t *cl_activeAction; + +#ifndef _XBOX +extern cvar_t *cl_allowDownload; +extern cvar_t *cl_allowAltEnter; +#endif +extern cvar_t *cl_conXOffset; +extern cvar_t *cl_inGameVideo; + +//================================================= + +// +// cl_main +// + +void CL_Init (void); +void CL_FlushMemory(void); +void CL_ShutdownAll(void); +void CL_AddReliableCommand( const char *cmd ); + +void CL_StartHunkUsers( void ); + +void CL_Disconnect_f (void); +void CL_GetChallengePacket (void); +void CL_Vid_Restart_f( void ); +void CL_Snd_Restart_f (void); +void CL_StartDemoLoop( void ); +void CL_NextDemo( void ); +void CL_ReadDemoMessage( void ); + +void CL_InitDownloads(void); +void CL_NextDownload(void); + +//void CL_GetPing( int n, char *buf, int buflen, int *pingtime ); +void CL_GetPingInfo( int n, char *buf, int buflen ); +void CL_ClearPing( int n ); +int CL_GetPingQueueCount( void ); + +void CL_ShutdownRef( void ); +void CL_InitRef( void ); + +#ifdef USE_CD_KEY + +qboolean CL_CDKeyValidate( const char *key, const char *checksum ); + +#endif // USE_CD_KEY + +//int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ); + + +// +// cl_input +// +typedef struct { + int down[2]; // key nums holding it down + unsigned downtime; // msec timestamp + unsigned msec; // msec down this frame if both a down and up happened + qboolean active; // current state + qboolean wasPressed; // set when down, not cleared when up +} kbutton_t; + +extern kbutton_t in_mlook, in_klook; +extern kbutton_t in_strafe; +extern kbutton_t in_speed; + +void CL_InitInput (void); +void CL_SendCmd (void); +void CL_ClearState (void); +void CL_ReadPackets (void); + +void CL_WritePacket( void ); +void IN_CenterView (void); + +void CL_VerifyCode( void ); + +float CL_KeyState (kbutton_t *key); +const char *Key_KeynumToString( int keynum/*, qboolean bTranslate */ ); //note: translate is only called for menu display not configs + +// +// cl_parse.c +// +extern int cl_connectedToPureServer; +extern int cl_connectedGAME; +extern int cl_connectedCGAME; +extern int cl_connectedUI; + +void CL_SystemInfoChanged( void ); +void CL_ParseServerMessage( msg_t *msg ); +//void CL_SP_Print(const word ID, byte *Data); + +//==================================================================== + +//void CL_ServerInfoPacket( netadr_t from, msg_t *msg ); +//void CL_LocalServers_f( void ); +//void CL_Ping_f( void ); +qboolean CL_UpdateVisiblePings_f( int source ); + + +// +// console +// +void Con_DrawCharacter (int cx, int line, int num); + +void Con_CheckResize (void); +void Con_Init (void); +void Con_Clear_f (void); +void Con_ToggleConsole_f (void); +void Con_DrawNotify (void); +void Con_ClearNotify (void); +void Con_RunConsole (void); +void Con_DrawConsole (void); +void Con_PageUp( void ); +void Con_PageDown( void ); +void Con_Top( void ); +void Con_Bottom( void ); +void Con_Close( void ); + + +// +// cl_scrn.c +// +void SCR_Init (void); +void SCR_UpdateScreen (void); + +void SCR_DebugGraph (float value, int color); + +void SCR_FillRect( float x, float y, float width, float height, + const float *color ); +void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ); + +// +// cl_cin.c +// + +void CL_PlayCinematic_f( void ); +void SCR_DrawCinematic (void); +void SCR_RunCinematic (void); +void SCR_StopCinematic (void); +int CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status CIN_StopCinematic(int handle); +e_status CIN_RunCinematic (int handle); +void CIN_DrawCinematic (int handle); +void CIN_SetExtents (int handle, int x, int y, int w, int h); +void CIN_SetLooping (int handle, qboolean loop); +void CIN_UploadCinematic(int handle); +void CIN_CloseAllVideos(void); + +void CL_UpdateHotSwap(void); + +#ifdef _XBOX +void CIN_Init(void); +bool CIN_PlayAllFrames( const char *arg, int x, int y, int w, int h, int systemBits, bool keyBreakAllowed ); +#endif + + +// +// cl_cgame.c +// +void CL_InitCGame( void ); +void CL_ShutdownCGame( void ); +qboolean CL_GameCommand( void ); +void CL_CGameRendering( stereoFrame_t stereo ); +void CL_SetCGameTime( void ); +void CL_FirstSnapshot( void ); +void CL_ShaderStateChanged(void); + +// +// cl_ui.c +// +void CL_InitUI( void ); +void CL_ShutdownUI( void ); +int Key_GetCatcher( void ); +void Key_SetCatcher( int catcher ); +void LAN_LoadCachedServers(); +void LAN_SaveServersToCache(); + + +// +// cl_net_chan.c +// +void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg); //int length, const byte *data ); +void CL_Netchan_TransmitNextFragment( netchan_t *chan ); +qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ); +#endif diff --git a/codemp/client/eax/eax.h b/codemp/client/eax/eax.h new file mode 100644 index 0000000..606df88 --- /dev/null +++ b/codemp/client/eax/eax.h @@ -0,0 +1,1562 @@ +/*******************************************************************\ +* * +* EAX.H - Environmental Audio Extensions version 4.0 * +* for OpenAL and DirectSound3D * +* * +* File revision 1.6.3 (21 February 2003) * +* EAX 4.0 API Spec version 1.6 * +* * +\*******************************************************************/ + +#ifndef EAX_H_INCLUDED +#define EAX_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#ifndef OPENAL + #include + + /* + * EAX Unified Interface (using Direct X 7) {4FF53B81-1CE0-11d3-AAB8-00A0C95949D5} + */ + DEFINE_GUID(CLSID_EAXDirectSound, + 0x4ff53b81, + 0x1ce0, + 0x11d3, + 0xaa, 0xb8, 0x0, 0xa0, 0xc9, 0x59, 0x49, 0xd5); + + /* + * EAX Unified Interface (using Direct X 8) {CA503B60-B176-11d4-A094-D0C0BF3A560C} + */ + DEFINE_GUID(CLSID_EAXDirectSound8, + 0xca503b60, + 0xb176, + 0x11d4, + 0xa0, 0x94, 0xd0, 0xc0, 0xbf, 0x3a, 0x56, 0xc); + + + +#ifdef DIRECTSOUND_VERSION +#if DIRECTSOUND_VERSION >= 0x0800 + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate8(GUID*, LPDIRECTSOUND8*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE8)(GUID*, LPDIRECTSOUND8*, IUnknown FAR*); +#endif +#endif + + __declspec(dllimport) HRESULT WINAPI EAXDirectSoundCreate(GUID*, LPDIRECTSOUND*, IUnknown FAR *); + typedef HRESULT (FAR PASCAL *LPEAXDIRECTSOUNDCREATE)(GUID*, LPDIRECTSOUND*, IUnknown FAR*); + +#else // OPENAL + #include "../OpenAl/al.h" + + #ifndef GUID_DEFINED + #define GUID_DEFINED + typedef struct _GUID + { + unsigned long Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; + } GUID; + #endif // GUID_DEFINED + + #ifndef DEFINE_GUID + #ifndef INITGUID + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID /*FAR*/ name + #else + #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + extern const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } + #endif // INITGUID + #endif // DEFINE_GUID + + /* + * EAX OpenAL Extensions + */ + typedef ALenum (*EAXSet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); + typedef ALenum (*EAXGet)(const GUID*, ALuint, ALuint, ALvoid*, ALuint); +#endif + +#pragma pack(push, 4) + + + + +//////////////////////////////////////////////////////////////////////////// +// Constants + +#define EAX_MAX_FXSLOTS 4 +#define EAX_MAX_ACTIVE_FXSLOTS 2 + +// The EAX_NULL_GUID is used by EAXFXSLOT_LOADEFFECT, EAXCONTEXT_PRIMARYFXSLOTID +// and EAXSOURCE_ACTIVEFXSLOTID + +// {00000000-0000-0000-0000-000000000000} +DEFINE_GUID(EAX_NULL_GUID, + 0x00000000, + 0x0000, + 0x0000, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + +// The EAX_PrimaryFXSlotID GUID is used by EAXSOURCE_ACTIVEFXSLOTID + +// {F317866D-924C-450C-861B-E6DAA25E7C20} +DEFINE_GUID(EAX_PrimaryFXSlotID, + 0xf317866d, + 0x924c, + 0x450c, + 0x86, 0x1b, 0xe6, 0xda, 0xa2, 0x5e, 0x7c, 0x20); + + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Structures + +// Use this structure for EAXCONTEXT_ALL property. +typedef struct _EAXCONTEXTPROPERTIES +{ + GUID guidPrimaryFXSlotID; + float flDistanceFactor; + float flAirAbsorptionHF; + float flHFReference; +} EAXCONTEXTPROPERTIES, *LPEAXCONTEXTPROPERTIES; + +// Use this structure for EAXSOURCE_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myBuffer.lDirect = 0; +// myBuffer.lDirectHF = -200; +// ... +// myBuffer.dwFlags = myFlags /* see EAXSOURCEFLAGS below */ ; +// instead of: +// myBuffer = { 0, -200, ... , 0x00000003 }; +// +typedef struct _EAXSOURCEPROPERTIES +{ + long lDirect; // direct path level (at low and mid frequencies) + long lDirectHF; // relative direct path level at high frequencies + long lRoom; // room effect level (at low and mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lObstruction; // main obstruction control (attenuation at high frequencies) + float flObstructionLFRatio; // obstruction low-frequency level re. main control + long lOcclusion; // main occlusion control (attenuation at high frequencies) + float flOcclusionLFRatio; // occlusion low-frequency level re. main control + float flOcclusionRoomRatio; // relative occlusion control for room effect + float flOcclusionDirectRatio; // relative occlusion control for direct path + long lExclusion; // main exlusion control (attenuation at high frequencies) + float flExclusionLFRatio; // exclusion low-frequency level re. main control + long lOutsideVolumeHF; // outside sound cone level at high frequencies + float flDopplerFactor; // like DS3D flDopplerFactor but per source + float flRolloffFactor; // like DS3D flRolloffFactor but per source + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + float flAirAbsorptionFactor; // multiplies EAXREVERB_AIRABSORPTIONHF + unsigned long ulFlags; // modifies the behavior of properties +} EAXSOURCEPROPERTIES, *LPEAXSOURCEPROPERTIES; + +// Use this structure for EAXSOURCE_ALLSENDPARAMETERS +// - all levels are hundredths of decibels +// +typedef struct _EAXSOURCEALLSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lSend; // send level (at low and mid frequencies) + long lSendHF; // relative send level at high frequencies + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; + long lExclusion; + float flExclusionLFRatio; +} EAXSOURCEALLSENDPROPERTIES, *LPEAXSOURCEALLSENDPROPERTIES; + +// Use this structure for EAXSOURCE_ACTIVEFXSLOTID +typedef struct _EAXACTIVEFXSLOTS +{ + GUID guidActiveFXSlots[EAX_MAX_ACTIVE_FXSLOTS]; +} EAXACTIVEFXSLOTS, *LPEAXACTIVEFXSLOTS; + +// Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. +typedef struct _EAXOBSTRUCTIONPROPERTIES +{ + long lObstruction; + float flObstructionLFRatio; +} EAXOBSTRUCTIONPROPERTIES, *LPEAXOBSTRUCTIONPROPERTIES; + +// Use this structure for EAXSOURCE_OCCLUSIONPARAMETERS property. +typedef struct _EAXOCCLUSIONPROPERTIES +{ + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +} EAXOCCLUSIONPROPERTIES, *LPEAXOCCLUSIONPROPERTIES; + +// Use this structure for EAXSOURCE_EXCLUSIONPARAMETERS property. +typedef struct _EAXEXCLUSIONPROPERTIES +{ + long lExclusion; + float flExclusionLFRatio; +} EAXEXCLUSIONPROPERTIES, *LPEAXEXCLUSIONPROPERTIES; + +// Use this structure for EAXSOURCE_SENDPARAMETERS properties. +typedef struct _EAXSOURCESENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lSend; + long lSendHF; +} EAXSOURCESENDPROPERTIES, *LPEAXSOURCESENDPROPERTIES; + +// Use this structure for EAXSOURCE_OCCLUSIONSENDPARAMETERS +typedef struct _EAXSOURCEOCCLUSIONSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lOcclusion; + float flOcclusionLFRatio; + float flOcclusionRoomRatio; + float flOcclusionDirectRatio; +} EAXSOURCEOCCLUSIONSENDPROPERTIES, *LPEAXSOURCEOCCLUSIONSENDPROPERTIES; + +// Use this structure for EAXSOURCE_EXCLUSIONSENDPARAMETERS +typedef struct _EAXSOURCEEXCLUSIONSENDPROPERTIES +{ + GUID guidReceivingFXSlotID; + long lExclusion; + float flExclusionLFRatio; +} EAXSOURCEEXCLUSIONSENDPROPERTIES, *LPEAXSOURCEEXCLUSIONSENDPROPERTIES; + +// Use this structure for EAXFXSLOT_ALLPARAMETERS +// - all levels are hundredths of decibels +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myFXSlot.guidLoadEffect = EAX_REVERB_EFFECT; +// myFXSlot.lVolume = 0; +// myFXSlot.lLock = 1; +// myFXSlot.ulFlags = myFlags /* see EAXFXSLOTFLAGS below */ ; +// instead of: +// myFXSlot = { EAX_REVERB_EFFECT, 0, 1, 0x00000001 }; +// +typedef struct _EAXFXSLOTPROPERTIES +{ + GUID guidLoadEffect; + long lVolume; + long lLock; + unsigned long ulFlags; +} EAXFXSLOTPROPERTIES, *LPEAXFXSLOTPROPERTIES; + +// Use this structure for EAXREVERB_REFLECTIONSPAN and EAXREVERB_REVERBPAN properties. +#ifndef EAXVECTOR_DEFINED +#define EAXVECTOR_DEFINED +typedef struct _EAXVECTOR { + float x; + float y; + float z; +} EAXVECTOR; +#endif + + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Error Codes + +#define EAX_OK 0 +#define EAXERR_INVALID_OPERATION (-1) +#define EAXERR_INVALID_VALUE (-2) +#define EAXERR_NO_EFFECT_LOADED (-3) +#define EAXERR_UNKNOWN_EFFECT (-4) +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Context Object + +// {1D4870AD-0DEF-43c0-A40C-523632296342} +DEFINE_GUID(EAXPROPERTYID_EAX40_Context, + 0x1d4870ad, + 0xdef, + 0x43c0, + 0xa4, 0xc, 0x52, 0x36, 0x32, 0x29, 0x63, 0x42); + +// For compatibility with future EAX versions: +#define EAXPROPERTYID_EAX_Context EAXPROPERTYID_EAX40_Context + +typedef enum +{ + EAXCONTEXT_NONE = 0, + EAXCONTEXT_ALLPARAMETERS, + EAXCONTEXT_PRIMARYFXSLOTID, + EAXCONTEXT_DISTANCEFACTOR, + EAXCONTEXT_AIRABSORPTIONHF, + EAXCONTEXT_HFREFERENCE, + EAXCONTEXT_LASTERROR +} EAXCONTEXT_PROPERTY; + +// OR these flags with property id +#define EAXCONTEXT_PARAMETER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXCONTEXT_PARAMETER_DEFER 0x80000000 // changes take effect later +#define EAXCONTEXT_PARAMETER_COMMITDEFERREDSETTINGS (EAXCONTEXT_NONE | \ + EAXCONTEXT_PARAMETER_IMMEDIATE) + +// EAX Context property ranges and defaults: +#define EAXCONTEXT_DEFAULTPRIMARYFXSLOTID EAXPROPERTYID_EAX40_FXSlot0 + +#define EAXCONTEXT_MINDISTANCEFACTOR FLT_MIN //minimum positive value +#define EAXCONTEXT_MAXDISTANCEFACTOR FLT_MAX +#define EAXCONTEXT_DEFAULTDISTANCEFACTOR 1.0f + +#define EAXCONTEXT_MINAIRABSORPTIONHF (-100.0f) +#define EAXCONTEXT_MAXAIRABSORPTIONHF 0.0f +#define EAXCONTEXT_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAXCONTEXT_MINHFREFERENCE 1000.0f +#define EAXCONTEXT_MAXHFREFERENCE 20000.0f +#define EAXCONTEXT_DEFAULTHFREFERENCE 5000.0f + +#define EAXCONTEXT_DEFAULTLASTERROR EAX_OK + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Effect Slot Objects + +// {C4D79F1E-F1AC-436b-A81D-A738E7045469} +DEFINE_GUID(EAXPROPERTYID_EAX40_FXSlot0, + 0xc4d79f1e, + 0xf1ac, + 0x436b, + 0xa8, 0x1d, 0xa7, 0x38, 0xe7, 0x4, 0x54, 0x69); + +// {08C00E96-74BE-4491-93AA-E8AD35A49117} +DEFINE_GUID(EAXPROPERTYID_EAX40_FXSlot1, + 0x8c00e96, + 0x74be, + 0x4491, + 0x93, 0xaa, 0xe8, 0xad, 0x35, 0xa4, 0x91, 0x17); + +// {1D433B88-F0F6-4637-919F-60E7E06B5EDD} +DEFINE_GUID(EAXPROPERTYID_EAX40_FXSlot2, + 0x1d433b88, + 0xf0f6, + 0x4637, + 0x91, 0x9f, 0x60, 0xe7, 0xe0, 0x6b, 0x5e, 0xdd); + +// {EFFF08EA-C7D8-44ab-93AD-6DBD5F910064} +DEFINE_GUID(EAXPROPERTYID_EAX40_FXSlot3, + 0xefff08ea, + 0xc7d8, + 0x44ab, + 0x93, 0xad, 0x6d, 0xbd, 0x5f, 0x91, 0x0, 0x64); + +// For compatibility with future EAX versions: +#define EAXPROPERTYID_EAX_FXSlot0 EAXPROPERTYID_EAX40_FXSlot0 +#define EAXPROPERTYID_EAX_FXSlot1 EAXPROPERTYID_EAX40_FXSlot1 +#define EAXPROPERTYID_EAX_FXSlot2 EAXPROPERTYID_EAX40_FXSlot2 +#define EAXPROPERTYID_EAX_FXSlot3 EAXPROPERTYID_EAX40_FXSlot3 + +// FXSlot object properties +typedef enum +{ + EAXFXSLOT_PARAMETER = 0, // range 0-0x40 reserved for loaded effect parameters + EAXFXSLOT_NONE = 0x10000, + EAXFXSLOT_ALLPARAMETERS, + EAXFXSLOT_LOADEFFECT, + EAXFXSLOT_VOLUME, + EAXFXSLOT_LOCK, + EAXFXSLOT_FLAGS +} EAXFXSLOT_PROPERTY; + +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXFXSLOTFLAGS_ENVIRONMENT; +// instead of: +// myFlags = 0x00000001; +// +#define EAXFXSLOTFLAGS_ENVIRONMENT 0x00000001 +#define EAXFXSLOTFLAGS_RESERVED 0xFFFFFFFE // reserved future use + +// EAX Effect Slot property ranges and defaults: +#define EAXFXSLOT_MINVOLUME (-10000) +#define EAXFXSLOT_MAXVOLUME 0 +#define EAXFXSLOT_DEFAULTVOLUME 0 + +#define EAXFXSLOT_MINLOCK 0 +#define EAXFXSLOT_MAXLOCK 1 + +enum +{ + EAXFXSLOT_UNLOCKED = 0, + EAXFXSLOT_LOCKED = 1 +}; + +#define EAXFXSLOT_DEFAULTFLAGS (EAXFXSLOTFLAGS_ENVIRONMENT) +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Source Object + +// {1B86B823-22DF-4eae-8B3C-1278CE544227} +DEFINE_GUID(EAXPROPERTYID_EAX40_Source, + 0x1b86b823, + 0x22df, + 0x4eae, + 0x8b, 0x3c, 0x12, 0x78, 0xce, 0x54, 0x42, 0x27); + +// For compatibility with future EAX versions: +#define EAXPROPERTYID_EAX_Source EAXPROPERTYID_EAX40_Source + +// Source object properties +typedef enum +{ + EAXSOURCE_NONE, + EAXSOURCE_ALLPARAMETERS, + EAXSOURCE_OBSTRUCTIONPARAMETERS, + EAXSOURCE_OCCLUSIONPARAMETERS, + EAXSOURCE_EXCLUSIONPARAMETERS, + EAXSOURCE_DIRECT, + EAXSOURCE_DIRECTHF, + EAXSOURCE_ROOM, + EAXSOURCE_ROOMHF, + EAXSOURCE_OBSTRUCTION, + EAXSOURCE_OBSTRUCTIONLFRATIO, + EAXSOURCE_OCCLUSION, + EAXSOURCE_OCCLUSIONLFRATIO, + EAXSOURCE_OCCLUSIONROOMRATIO, + EAXSOURCE_OCCLUSIONDIRECTRATIO, + EAXSOURCE_EXCLUSION, + EAXSOURCE_EXCLUSIONLFRATIO, + EAXSOURCE_OUTSIDEVOLUMEHF, + EAXSOURCE_DOPPLERFACTOR, + EAXSOURCE_ROLLOFFFACTOR, + EAXSOURCE_ROOMROLLOFFFACTOR, + EAXSOURCE_AIRABSORPTIONFACTOR, + EAXSOURCE_FLAGS, + EAXSOURCE_SENDPARAMETERS, + EAXSOURCE_ALLSENDPARAMETERS, + EAXSOURCE_OCCLUSIONSENDPARAMETERS, + EAXSOURCE_EXCLUSIONSENDPARAMETERS, + EAXSOURCE_ACTIVEFXSLOTID, +} EAXSOURCE_PROPERTY; + +// OR these flags with property id +#define EAXSOURCE_PARAMETER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXSOURCE_PARAMETER_DEFERRED 0x80000000 // changes take effect later +#define EAXSOURCE_PARAMETER_COMMITDEFERREDSETTINGS (EAXSOURCE_NONE | \ + EAXSOURCE_PARAMETER_IMMEDIATE) +// Used by EAXSOURCE_FLAGS for EAXSOURCEFLAGS_xxxAUTO +// TRUE: value is computed automatically - property is an offset +// FALSE: value is used directly +// +// Note: The number and order of flags may change in future EAX versions. +// To insure future compatibility, use flag defines as follows: +// myFlags = EAXSOURCE_DIRECTHFAUTO | EAXSOURCE_ROOMAUTO; +// instead of: +// myFlags = 0x00000003; +// +#define EAXSOURCEFLAGS_DIRECTHFAUTO 0x00000001 // relates to EAXSOURCE_DIRECTHF +#define EAXSOURCEFLAGS_ROOMAUTO 0x00000002 // relates to EAXSOURCE_ROOM +#define EAXSOURCEFLAGS_ROOMHFAUTO 0x00000004 // relates to EAXSOURCE_ROOMHF +#define EAXSOURCEFLAGS_RESERVED 0xFFFFFFF8 // reserved future use + +// EAX Source property ranges and defaults: +#define EAXSOURCE_MINSEND (-10000) +#define EAXSOURCE_MAXSEND 0 +#define EAXSOURCE_DEFAULTSEND 0 + +#define EAXSOURCE_MINSENDHF (-10000) +#define EAXSOURCE_MAXSENDHF 0 +#define EAXSOURCE_DEFAULTSENDHF 0 + +#define EAXSOURCE_MINDIRECT (-10000) +#define EAXSOURCE_MAXDIRECT 1000 +#define EAXSOURCE_DEFAULTDIRECT 0 + +#define EAXSOURCE_MINDIRECTHF (-10000) +#define EAXSOURCE_MAXDIRECTHF 0 +#define EAXSOURCE_DEFAULTDIRECTHF 0 + +#define EAXSOURCE_MINROOM (-10000) +#define EAXSOURCE_MAXROOM 1000 +#define EAXSOURCE_DEFAULTROOM 0 + +#define EAXSOURCE_MINROOMHF (-10000) +#define EAXSOURCE_MAXROOMHF 0 +#define EAXSOURCE_DEFAULTROOMHF 0 + +#define EAXSOURCE_MINOBSTRUCTION (-10000) +#define EAXSOURCE_MAXOBSTRUCTION 0 +#define EAXSOURCE_DEFAULTOBSTRUCTION 0 + +#define EAXSOURCE_MINOBSTRUCTIONLFRATIO 0.0f +#define EAXSOURCE_MAXOBSTRUCTIONLFRATIO 1.0f +#define EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO 0.0f + +#define EAXSOURCE_MINOCCLUSION (-10000) +#define EAXSOURCE_MAXOCCLUSION 0 +#define EAXSOURCE_DEFAULTOCCLUSION 0 + +#define EAXSOURCE_MINOCCLUSIONLFRATIO 0.0f +#define EAXSOURCE_MAXOCCLUSIONLFRATIO 1.0f +#define EAXSOURCE_DEFAULTOCCLUSIONLFRATIO 0.25f + +#define EAXSOURCE_MINOCCLUSIONROOMRATIO 0.0f +#define EAXSOURCE_MAXOCCLUSIONROOMRATIO 10.0f +#define EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO 1.5f + +#define EAXSOURCE_MINOCCLUSIONDIRECTRATIO 0.0f +#define EAXSOURCE_MAXOCCLUSIONDIRECTRATIO 10.0f +#define EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO 1.0f + +#define EAXSOURCE_MINEXCLUSION (-10000) +#define EAXSOURCE_MAXEXCLUSION 0 +#define EAXSOURCE_DEFAULTEXCLUSION 0 + +#define EAXSOURCE_MINEXCLUSIONLFRATIO 0.0f +#define EAXSOURCE_MAXEXCLUSIONLFRATIO 1.0f +#define EAXSOURCE_DEFAULTEXCLUSIONLFRATIO 1.0f + +#define EAXSOURCE_MINOUTSIDEVOLUMEHF (-10000) +#define EAXSOURCE_MAXOUTSIDEVOLUMEHF 0 +#define EAXSOURCE_DEFAULTOUTSIDEVOLUMEHF 0 + +#define EAXSOURCE_MINDOPPLERFACTOR 0.0f +#define EAXSOURCE_MAXDOPPLERFACTOR 10.f +#define EAXSOURCE_DEFAULTDOPPLERFACTOR 1.0f + +#define EAXSOURCE_MINROLLOFFFACTOR 0.0f +#define EAXSOURCE_MAXROLLOFFFACTOR 10.f +#define EAXSOURCE_DEFAULTROLLOFFFACTOR 0.0f + +#define EAXSOURCE_MINROOMROLLOFFFACTOR 0.0f +#define EAXSOURCE_MAXROOMROLLOFFFACTOR 10.f +#define EAXSOURCE_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXSOURCE_MINAIRABSORPTIONFACTOR 0.0f +#define EAXSOURCE_MAXAIRABSORPTIONFACTOR 10.0f +#define EAXSOURCE_DEFAULTAIRABSORPTIONFACTOR 0.0f + +#define EAXSOURCE_DEFAULTFLAGS (EAXSOURCEFLAGS_DIRECTHFAUTO | \ + EAXSOURCEFLAGS_ROOMAUTO | \ + EAXSOURCEFLAGS_ROOMHFAUTO ) + +#define EAXSOURCE_DEFAULTACTIVEFXSLOTID {{ EAX_NULL_GUID.Data1, EAX_NULL_GUID.Data2, EAX_NULL_GUID.Data3, \ + EAX_NULL_GUID.Data4[0],EAX_NULL_GUID.Data4[1],EAX_NULL_GUID.Data4[2],\ + EAX_NULL_GUID.Data4[3],EAX_NULL_GUID.Data4[4],EAX_NULL_GUID.Data4[5],\ + EAX_NULL_GUID.Data4[6],EAX_NULL_GUID.Data4[7] }, \ + { EAX_PrimaryFXSlotID.Data1, EAX_PrimaryFXSlotID.Data2, \ + EAX_PrimaryFXSlotID.Data3, EAX_PrimaryFXSlotID.Data4[0],\ + EAX_PrimaryFXSlotID.Data4[1],EAX_PrimaryFXSlotID.Data4[2],\ + EAX_PrimaryFXSlotID.Data4[3],EAX_PrimaryFXSlotID.Data4[4],\ + EAX_PrimaryFXSlotID.Data4[5],EAX_PrimaryFXSlotID.Data4[6],\ + EAX_PrimaryFXSlotID.Data4[7] }} + + +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// +// Reverb Effect + +// EAX REVERB {0CF95C8F-A3CC-4849-B0B6-832ECC1822DF} +DEFINE_GUID(EAX_REVERB_EFFECT, + 0xcf95c8f, + 0xa3cc, + 0x4849, + 0xb0, 0xb6, 0x83, 0x2e, 0xcc, 0x18, 0x22, 0xdf); + +// Reverb effect properties +typedef enum +{ + EAXREVERB_NONE, + EAXREVERB_ALLPARAMETERS, + EAXREVERB_ENVIRONMENT, + EAXREVERB_ENVIRONMENTSIZE, + EAXREVERB_ENVIRONMENTDIFFUSION, + EAXREVERB_ROOM, + EAXREVERB_ROOMHF, + EAXREVERB_ROOMLF, + EAXREVERB_DECAYTIME, + EAXREVERB_DECAYHFRATIO, + EAXREVERB_DECAYLFRATIO, + EAXREVERB_REFLECTIONS, + EAXREVERB_REFLECTIONSDELAY, + EAXREVERB_REFLECTIONSPAN, + EAXREVERB_REVERB, + EAXREVERB_REVERBDELAY, + EAXREVERB_REVERBPAN, + EAXREVERB_ECHOTIME, + EAXREVERB_ECHODEPTH, + EAXREVERB_MODULATIONTIME, + EAXREVERB_MODULATIONDEPTH, + EAXREVERB_AIRABSORPTIONHF, + EAXREVERB_HFREFERENCE, + EAXREVERB_LFREFERENCE, + EAXREVERB_ROOMROLLOFFFACTOR, + EAXREVERB_FLAGS, +} EAXREVERB_PROPERTY; + +// OR these flags with property id +#define EAXREVERB_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXREVERB_DEFERRED 0x80000000 // changes take effect later +#define EAXREVERB_COMMITDEFERREDSETTINGS (EAXREVERB_NONE | \ + EAXREVERB_IMMEDIATE) + +// used by EAXREVERB_ENVIRONMENT +enum +{ + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX_ENVIRONMENT_UNDEFINED, + + EAX_ENVIRONMENT_COUNT +}; + +// Used by EAXREVERB_FLAGS +// +// Note: The number and order of flags may change in future EAX versions. +// It is recommended to use the flag defines as follows: +// myFlags = EAXREVERBFLAGS_DECAYTIMESCALE | EAXREVERBFLAGS_REVERBSCALE; +// instead of: +// myFlags = 0x00000009; +// +// These flags determine what properties are affected by environment size. +#define EAXREVERBFLAGS_DECAYTIMESCALE 0x00000001 // reverberation decay time +#define EAXREVERBFLAGS_REFLECTIONSSCALE 0x00000002 // reflection level +#define EAXREVERBFLAGS_REFLECTIONSDELAYSCALE 0x00000004 // initial reflection delay time +#define EAXREVERBFLAGS_REVERBSCALE 0x00000008 // reflections level +#define EAXREVERBFLAGS_REVERBDELAYSCALE 0x00000010 // late reverberation delay time +#define EAXREVERBFLAGS_ECHOTIMESCALE 0x00000040 // echo time +#define EAXREVERBFLAGS_MODULATIONTIMESCALE 0x00000080 // modulation time +// This flag limits high-frequency decay time according to air absorption. +#define EAXREVERBFLAGS_DECAYHFLIMIT 0x00000020 +#define EAXREVERBFLAGS_RESERVED 0xFFFFFF00 // reserved future use + +// Use this structure for EAXREVERB_ALLPARAMETERS +// - all levels are hundredths of decibels +// - all times and delays are in seconds +// +// NOTE: This structure may change in future EAX versions. +// It is recommended to initialize fields by name: +// myReverb.lRoom = -1000; +// myReverb.lRoomHF = -100; +// ... +// myReverb.dwFlags = myFlags /* see EAXREVERBFLAGS below */ ; +// instead of: +// myReverb = { -1000, -100, ... , 0x00000009 }; +// If you want to save and load presets in binary form, you +// should define your own structure to insure future compatibility. +// +typedef struct _EAXREVERBPROPERTIES +{ + unsigned long ulEnvironment; // sets all reverb properties + float flEnvironmentSize; // environment size in meters + float flEnvironmentDiffusion; // environment diffusion + long lRoom; // room effect level (at mid frequencies) + long lRoomHF; // relative room effect level at high frequencies + long lRoomLF; // relative room effect level at low frequencies + float flDecayTime; // reverberation decay time at mid frequencies + float flDecayHFRatio; // high-frequency to mid-frequency decay time ratio + float flDecayLFRatio; // low-frequency to mid-frequency decay time ratio + long lReflections; // early reflections level relative to room effect + float flReflectionsDelay; // initial reflection delay time + EAXVECTOR vReflectionsPan; // early reflections panning vector + long lReverb; // late reverberation level relative to room effect + float flReverbDelay; // late reverberation delay time relative to initial reflection + EAXVECTOR vReverbPan; // late reverberation panning vector + float flEchoTime; // echo time + float flEchoDepth; // echo depth + float flModulationTime; // modulation time + float flModulationDepth; // modulation depth + float flAirAbsorptionHF; // change in level per meter at high frequencies + float flHFReference; // reference high frequency + float flLFReference; // reference low frequency + float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect + unsigned long ulFlags; // modifies the behavior of properties +} EAXREVERBPROPERTIES, *LPEAXREVERBPROPERTIES; + +// Property ranges and defaults: +#define EAXREVERB_MINENVIRONMENT 0 +#define EAXREVERB_MAXENVIRONMENT (EAX_ENVIRONMENT_COUNT-1) +#define EAXREVERB_DEFAULTENVIRONMENT EAX_ENVIRONMENT_GENERIC + +#define EAXREVERB_MINENVIRONMENTSIZE 1.0f +#define EAXREVERB_MAXENVIRONMENTSIZE 100.0f +#define EAXREVERB_DEFAULTENVIRONMENTSIZE 7.5f + +#define EAXREVERB_MINENVIRONMENTDIFFUSION 0.0f +#define EAXREVERB_MAXENVIRONMENTDIFFUSION 1.0f +#define EAXREVERB_DEFAULTENVIRONMENTDIFFUSION 1.0f + +#define EAXREVERB_MINROOM (-10000) +#define EAXREVERB_MAXROOM 0 +#define EAXREVERB_DEFAULTROOM (-1000) + +#define EAXREVERB_MINROOMHF (-10000) +#define EAXREVERB_MAXROOMHF 0 +#define EAXREVERB_DEFAULTROOMHF (-100) + +#define EAXREVERB_MINROOMLF (-10000) +#define EAXREVERB_MAXROOMLF 0 +#define EAXREVERB_DEFAULTROOMLF 0 + +#define EAXREVERB_MINDECAYTIME 0.1f +#define EAXREVERB_MAXDECAYTIME 20.0f +#define EAXREVERB_DEFAULTDECAYTIME 1.49f + +#define EAXREVERB_MINDECAYHFRATIO 0.1f +#define EAXREVERB_MAXDECAYHFRATIO 2.0f +#define EAXREVERB_DEFAULTDECAYHFRATIO 0.83f + +#define EAXREVERB_MINDECAYLFRATIO 0.1f +#define EAXREVERB_MAXDECAYLFRATIO 2.0f +#define EAXREVERB_DEFAULTDECAYLFRATIO 1.00f + +#define EAXREVERB_MINREFLECTIONS (-10000) +#define EAXREVERB_MAXREFLECTIONS 1000 +#define EAXREVERB_DEFAULTREFLECTIONS (-2602) + +#define EAXREVERB_MINREFLECTIONSDELAY 0.0f +#define EAXREVERB_MAXREFLECTIONSDELAY 0.3f +#define EAXREVERB_DEFAULTREFLECTIONSDELAY 0.007f + +#define EAXREVERB_DEFAULTREFLECTIONSPAN {0.0f, 0.0f, 0.0f} + +#define EAXREVERB_MINREVERB (-10000) +#define EAXREVERB_MAXREVERB 2000 +#define EAXREVERB_DEFAULTREVERB 200 + +#define EAXREVERB_MINREVERBDELAY 0.0f +#define EAXREVERB_MAXREVERBDELAY 0.1f +#define EAXREVERB_DEFAULTREVERBDELAY 0.011f + +#define EAXREVERB_DEFAULTREVERBPAN {0.0f, 0.0f, 0.0f} + +#define EAXREVERB_MINECHOTIME 0.075f +#define EAXREVERB_MAXECHOTIME 0.25f +#define EAXREVERB_DEFAULTECHOTIME 0.25f + +#define EAXREVERB_MINECHODEPTH 0.0f +#define EAXREVERB_MAXECHODEPTH 1.0f +#define EAXREVERB_DEFAULTECHODEPTH 0.0f + +#define EAXREVERB_MINMODULATIONTIME 0.04f +#define EAXREVERB_MAXMODULATIONTIME 4.0f +#define EAXREVERB_DEFAULTMODULATIONTIME 0.25f + +#define EAXREVERB_MINMODULATIONDEPTH 0.0f +#define EAXREVERB_MAXMODULATIONDEPTH 1.0f +#define EAXREVERB_DEFAULTMODULATIONDEPTH 0.0f + +#define EAXREVERB_MINAIRABSORPTIONHF (-100.0f) +#define EAXREVERB_MAXAIRABSORPTIONHF 0.0f +#define EAXREVERB_DEFAULTAIRABSORPTIONHF (-5.0f) + +#define EAXREVERB_MINHFREFERENCE 1000.0f +#define EAXREVERB_MAXHFREFERENCE 20000.0f +#define EAXREVERB_DEFAULTHFREFERENCE 5000.0f + +#define EAXREVERB_MINLFREFERENCE 20.0f +#define EAXREVERB_MAXLFREFERENCE 1000.0f +#define EAXREVERB_DEFAULTLFREFERENCE 250.0f + +#define EAXREVERB_MINROOMROLLOFFFACTOR 0.0f +#define EAXREVERB_MAXROOMROLLOFFFACTOR 10.0f +#define EAXREVERB_DEFAULTROOMROLLOFFFACTOR 0.0f + +#define EAXREVERB_DEFAULTFLAGS (EAXREVERBFLAGS_DECAYTIMESCALE | \ + EAXREVERBFLAGS_REFLECTIONSSCALE | \ + EAXREVERBFLAGS_REFLECTIONSDELAYSCALE | \ + EAXREVERBFLAGS_REVERBSCALE | \ + EAXREVERBFLAGS_REVERBDELAYSCALE | \ + EAXREVERBFLAGS_DECAYHFLIMIT) +//////////////////////////////////////////////////////////////////////////// + + + + +//////////////////////////////////////////////////////////////////////////// + +// New Effect Types + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// AGC Compressor Effect + +// EAX AGC COMPRESSOR {BFB7A01E-7825-4039-927F-3AABDA0C560} + +DEFINE_GUID(EAX_AGCCOMPRESSOR_EFFECT, + 0xbfb7a01e, + 0x7825, + 0x4039, + 0x92, 0x7f, 0x3, 0xaa, 0xbd, 0xa0, 0xc5, 0x60); + +// AGC Compressor properties +typedef enum +{ + EAXAGCCOMPRESSOR_NONE, + EAXAGCCOMPRESSOR_ALLPARAMETERS, + EAXAGCCOMPRESSOR_ONOFF +} EAXAGCCOMPRESSOR_PROPERTY; + +// OR these flags with property id +#define EAXAGCCOMPRESSOR_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXAGCCOMPRESSOR_DEFERRED 0x80000000 // changes take effect later +#define EAXAGCCOMPRESSOR_COMMITDEFERREDSETTINGS (EAXAGCCOMPRESSOR_NONE | \ + EAXAGCCOMPRESSOR_IMMEDIATE) + +// Use this structure for EAXAGCCOMPRESSOR_ALLPARAMETERS +typedef struct _EAXAGCCOMPRESSORPROPERTIES +{ + unsigned long ulOnOff; // Switch Compressor on or off +} EAXAGCCOMPRESSORPROPERTIES, *LPEAXAGCCOMPRESSORPROPERTIES; + +// Property ranges and defaults: + +#define EAXAGCCOMPRESSOR_MINONOFF 0 +#define EAXAGCCOMPRESSOR_MAXONOFF 1 +#define EAXAGCCOMPRESSOR_DEFAULTONOFF 1 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Autowah Effect + +// EAX AUTOWAH {EC3130C0-AC7A-11D2-88DD-A024D13CE1} +DEFINE_GUID(EAX_AUTOWAH_EFFECT, + 0xec3130c0, + 0xac7a, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Autowah properties +typedef enum +{ + EAXAUTOWAH_NONE, + EAXAUTOWAH_ALLPARAMETERS, + EAXAUTOWAH_ATTACKTIME, + EAXAUTOWAH_RELEASETIME, + EAXAUTOWAH_RESONANCE, + EAXAUTOWAH_PEAKLEVEL +} EAXAUTOWAH_PROPERTY; + +// OR these flags with property id +#define EAXAUTOWAH_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXAUTOWAH_DEFERRED 0x80000000 // changes take effect later +#define EAXAUTOWAH_COMMITDEFERREDSETTINGS (EAXAUTOWAH_NONE | \ + EAXAUTOWAH_IMMEDIATE) + +// Use this structure for EAXAUTOWAH_ALLPARAMETERS +typedef struct _EAXAUTOWAHPROPERTIES +{ + float flAttackTime; // Attack time (seconds) + float flReleaseTime; // Release time (seconds) + long lResonance; // Resonance (mB) + long lPeakLevel; // Peak level (mB) +} EAXAUTOWAHPROPERTIES, *LPEAXAUTOWAHPROPERTIES; + +// Property ranges and defaults: + +#define EAXAUTOWAH_MINATTACKTIME 0.0001f +#define EAXAUTOWAH_MAXATTACKTIME 1.0f +#define EAXAUTOWAH_DEFAULTATTACKTIME 0.06f + +#define EAXAUTOWAH_MINRELEASETIME 0.0001f +#define EAXAUTOWAH_MAXRELEASETIME 1.0f +#define EAXAUTOWAH_DEFAULTRELEASETIME 0.06f + +#define EAXAUTOWAH_MINRESONANCE 600 +#define EAXAUTOWAH_MAXRESONANCE 6000 +#define EAXAUTOWAH_DEFAULTRESONANCE 6000 + +#define EAXAUTOWAH_MINPEAKLEVEL (-9000) +#define EAXAUTOWAH_MAXPEAKLEVEL 9000 +#define EAXAUTOWAH_DEFAULTPEAKLEVEL 2100 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Chorus Effect + +// EAX CHORUS {DE6D6FE0-AC79-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_CHORUS_EFFECT, + 0xde6d6fe0, + 0xac79, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + + +// Chorus properties +typedef enum +{ + EAXCHORUS_NONE, + EAXCHORUS_ALLPARAMETERS, + EAXCHORUS_WAVEFORM, + EAXCHORUS_PHASE, + EAXCHORUS_RATE, + EAXCHORUS_DEPTH, + EAXCHORUS_FEEDBACK, + EAXCHORUS_DELAY +} EAXCHORUS_PROPERTY; + +// OR these flags with property id +#define EAXCHORUS_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXCHORUS_DEFERRED 0x80000000 // changes take effect later +#define EAXCHORUS_COMMITDEFERREDSETTINGS (EAXCHORUS_NONE | \ + EAXCHORUS_IMMEDIATE) + +// used by EAXCHORUS_WAVEFORM +enum +{ + EAX_CHORUS_SINUSOID, + EAX_CHORUS_TRIANGLE +}; + +// Use this structure for EAXCHORUS_ALLPARAMETERS +typedef struct _EAXCHORUSPROPERTIES +{ + unsigned long ulWaveform; // Waveform selector - see enum above + long lPhase; // Phase (Degrees) + float flRate; // Rate (Hz) + float flDepth; // Depth (0 to 1) + float flFeedback; // Feedback (-1 to 1) + float flDelay; // Delay (seconds) +} EAXCHORUSPROPERTIES, *LPEAXCHORUSPROPERTIES; + +// Property ranges and defaults: + +#define EAXCHORUS_MINWAVEFORM 0 +#define EAXCHORUS_MAXWAVEFORM 1 +#define EAXCHORUS_DEFAULTWAVEFORM 1 + +#define EAXCHORUS_MINPHASE (-180) +#define EAXCHORUS_MAXPHASE 180 +#define EAXCHORUS_DEFAULTPHASE 90 + +#define EAXCHORUS_MINRATE 0.0f +#define EAXCHORUS_MAXRATE 10.0f +#define EAXCHORUS_DEFAULTRATE 1.1f + +#define EAXCHORUS_MINDEPTH 0.0f +#define EAXCHORUS_MAXDEPTH 1.0f +#define EAXCHORUS_DEFAULTDEPTH 0.1f + +#define EAXCHORUS_MINFEEDBACK (-1.0f) +#define EAXCHORUS_MAXFEEDBACK 1.0f +#define EAXCHORUS_DEFAULTFEEDBACK 0.25f + +#define EAXCHORUS_MINDELAY 0.0f +#define EAXCHORUS_MAXDELAY 0.016f +#define EAXCHORUS_DEFAULTDELAY 0.016f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Distortion Effect + +// EAX DISTORTION {975A4CE0-AC7E-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_DISTORTION_EFFECT, + 0x975a4ce0, + 0xac7e, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Distortion properties +typedef enum +{ + EAXDISTORTION_NONE, + EAXDISTORTION_ALLPARAMETERS, + EAXDISTORTION_EDGE, + EAXDISTORTION_GAIN, + EAXDISTORTION_LOWPASSCUTOFF, + EAXDISTORTION_EQCENTER, + EAXDISTORTION_EQBANDWIDTH +} EAXDISTORTION_PROPERTY; + +// OR these flags with property id +#define EAXDISTORTION_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXDISTORTION_DEFERRED 0x80000000 // changes take effect later +#define EAXDISTORTION_COMMITDEFERREDSETTINGS (EAXDISTORTION_NONE | \ + EAXDISTORTION_IMMEDIATE) + +// Use this structure for EAXDISTORTION_ALLPARAMETERS +typedef struct _EAXDISTORTIONPROPERTIES +{ + float flEdge; // Controls the shape of the distortion (0 to 1) + long lGain; // Controls the post distortion gain (mB) + float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz) + float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz) + float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz) +} EAXDISTORTIONPROPERTIES, *LPEAXDISTORTIONPROPERTIES; + +// Property ranges and defaults: + +#define EAXDISTORTION_MINEDGE 0.0f +#define EAXDISTORTION_MAXEDGE 1.0f +#define EAXDISTORTION_DEFAULTEDGE 0.2f + +#define EAXDISTORTION_MINGAIN (-6000) +#define EAXDISTORTION_MAXGAIN 0 +#define EAXDISTORTION_DEFAULTGAIN (-2600) + +#define EAXDISTORTION_MINLOWPASSCUTOFF 80.0f +#define EAXDISTORTION_MAXLOWPASSCUTOFF 24000.0f +#define EAXDISTORTION_DEFAULTLOWPASSCUTOFF 8000.0f + +#define EAXDISTORTION_MINEQCENTER 80.0f +#define EAXDISTORTION_MAXEQCENTER 24000.0f +#define EAXDISTORTION_DEFAULTEQCENTER 3600.0f + +#define EAXDISTORTION_MINEQBANDWIDTH 80.0f +#define EAXDISTORTION_MAXEQBANDWIDTH 24000.0f +#define EAXDISTORTION_DEFAULTEQBANDWIDTH 3600.0f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Echo Effect + +// EAX ECHO {E9F1BC0-AC82-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_ECHO_EFFECT, + 0xe9f1bc0, + 0xac82, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Echo properties +typedef enum +{ + EAXECHO_NONE, + EAXECHO_ALLPARAMETERS, + EAXECHO_DELAY, + EAXECHO_LRDELAY, + EAXECHO_DAMPING, + EAXECHO_FEEDBACK, + EAXECHO_SPREAD +} EAXECHO_PROPERTY; + +// OR these flags with property id +#define EAXECHO_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXECHO_DEFERRED 0x80000000 // changes take effect later +#define EAXECHO_COMMITDEFERREDSETTINGS (EAXECHO_NONE | \ + EAXECHO_IMMEDIATE) + +// Use this structure for EAXECHO_ALLPARAMETERS +typedef struct _EAXECHOPROPERTIES +{ + float flDelay; // Controls the initial delay time (seconds) + float flLRDelay; // Controls the delay time between the first and second taps (seconds) + float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1) + float flFeedback; // Controls the duration of echo repetition (0 to 1) + float flSpread; // Controls the left-right spread of the echoes +} EAXECHOPROPERTIES, *LPEAXECHOPROPERTIES; + +// Property ranges and defaults: + +#define EAXECHO_MINDAMPING 0.0f +#define EAXECHO_MAXDAMPING 0.99f +#define EAXECHO_DEFAULTDAMPING 0.5f + +#define EAXECHO_MINDELAY 0.0f +#define EAXECHO_MAXDELAY 0.207f +#define EAXECHO_DEFAULTDELAY 0.1f + +#define EAXECHO_MINLRDELAY 0.0f +#define EAXECHO_MAXLRDELAY 0.404f +#define EAXECHO_DEFAULTLRDELAY 0.1f + +#define EAXECHO_MINFEEDBACK 0.0f +#define EAXECHO_MAXFEEDBACK 1.0f +#define EAXECHO_DEFAULTFEEDBACK 0.5f + +#define EAXECHO_MINSPREAD (-1.0f) +#define EAXECHO_MAXSPREAD 1.0f +#define EAXECHO_DEFAULTSPREAD (-1.0f) + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Equalizer Effect + +// EAX EQUALIZER {65F94CE0-9793-11D3-939D-C0F02DD6F0} + +DEFINE_GUID(EAX_EQUALIZER_EFFECT, + 0x65f94ce0, + 0x9793, + 0x11d3, + 0x93, 0x9d, 0x0, 0xc0, 0xf0, 0x2d, 0xd6, 0xf0); + + +// Equalizer properties +typedef enum +{ + EAXEQUALIZER_NONE, + EAXEQUALIZER_ALLPARAMETERS, + EAXEQUALIZER_LOWGAIN, + EAXEQUALIZER_LOWCUTOFF, + EAXEQUALIZER_MID1GAIN, + EAXEQUALIZER_MID1CENTER, + EAXEQUALIZER_MID1WIDTH, + EAXEQUALIZER_MID2GAIN, + EAXEQUALIZER_MID2CENTER, + EAXEQUALIZER_MID2WIDTH, + EAXEQUALIZER_HIGHGAIN, + EAXEQUALIZER_HIGHCUTOFF +} EAXEQUALIZER_PROPERTY; + +// OR these flags with property id +#define EAXEQUALIZER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXEQUALIZER_DEFERRED 0x80000000 // changes take effect later +#define EAXEQUALIZER_COMMITDEFERREDSETTINGS (EAXEQUALIZER_NONE | \ + EAXEQUALIZER_IMMEDIATE) + +// Use this structure for EAXEQUALIZER_ALLPARAMETERS +typedef struct _EAXEQUALIZERPROPERTIES +{ + long lLowGain; // (mB) + float flLowCutOff; // (Hz) + long lMid1Gain; // (mB) + float flMid1Center; // (Hz) + float flMid1Width; // (octaves) + long lMid2Gain; // (mB) + float flMid2Center; // (Hz) + float flMid2Width; // (octaves) + long lHighGain; // (mB) + float flHighCutOff; // (Hz) +} EAXEQUALIZERPROPERTIES, *LPEAXEQUALIZERPROPERTIES; + +// Property ranges and defaults: + +#define EAXEQUALIZER_MINLOWGAIN (-1800) +#define EAXEQUALIZER_MAXLOWGAIN 1800 +#define EAXEQUALIZER_DEFAULTLOWGAIN 0 + +#define EAXEQUALIZER_MINLOWCUTOFF 50.0f +#define EAXEQUALIZER_MAXLOWCUTOFF 800.0f +#define EAXEQUALIZER_DEFAULTLOWCUTOFF 200.0f + +#define EAXEQUALIZER_MINMID1GAIN (-1800) +#define EAXEQUALIZER_MAXMID1GAIN 1800 +#define EAXEQUALIZER_DEFAULTMID1GAIN 0 + +#define EAXEQUALIZER_MINMID1CENTER 200.0f +#define EAXEQUALIZER_MAXMID1CENTER 3000.0f +#define EAXEQUALIZER_DEFAULTMID1CENTER 500.0f + +#define EAXEQUALIZER_MINMID1WIDTH 0.01f +#define EAXEQUALIZER_MAXMID1WIDTH 1.0f +#define EAXEQUALIZER_DEFAULTMID1WIDTH 1.0f + +#define EAXEQUALIZER_MINMID2GAIN (-1800) +#define EAXEQUALIZER_MAXMID2GAIN 1800 +#define EAXEQUALIZER_DEFAULTMID2GAIN 0 + +#define EAXEQUALIZER_MINMID2CENTER 1000.0f +#define EAXEQUALIZER_MAXMID2CENTER 8000.0f +#define EAXEQUALIZER_DEFAULTMID2CENTER 3000.0f + +#define EAXEQUALIZER_MINMID2WIDTH 0.01f +#define EAXEQUALIZER_MAXMID2WIDTH 1.0f +#define EAXEQUALIZER_DEFAULTMID2WIDTH 1.0f + +#define EAXEQUALIZER_MINHIGHGAIN (-1800) +#define EAXEQUALIZER_MAXHIGHGAIN 1800 +#define EAXEQUALIZER_DEFAULTHIGHGAIN 0 + +#define EAXEQUALIZER_MINHIGHCUTOFF 4000.0f +#define EAXEQUALIZER_MAXHIGHCUTOFF 16000.0f +#define EAXEQUALIZER_DEFAULTHIGHCUTOFF 6000.0f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Flanger Effect + +// EAX FLANGER {A70007C0-7D2-11D3-9B1E-A024D13CE1} + +DEFINE_GUID(EAX_FLANGER_EFFECT, + 0xa70007c0, + 0x7d2, + 0x11d3, + 0x9b, 0x1e, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Flanger properties +typedef enum +{ + EAXFLANGER_NONE, + EAXFLANGER_ALLPARAMETERS, + EAXFLANGER_WAVEFORM, + EAXFLANGER_PHASE, + EAXFLANGER_RATE, + EAXFLANGER_DEPTH, + EAXFLANGER_FEEDBACK, + EAXFLANGER_DELAY +} EAXFLANGER_PROPERTY; + +// OR these flags with property id +#define EAXFLANGER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXFLANGER_DEFERRED 0x80000000 // changes take effect later +#define EAXFLANGER_COMMITDEFERREDSETTINGS (EAXFLANGER_NONE | \ + EAXFLANGER_IMMEDIATE) + +// used by EAXFLANGER_WAVEFORM +enum +{ + EAX_FLANGER_SINUSOID, + EAX_FLANGER_TRIANGLE +}; + +// Use this structure for EAXFLANGER_ALLPARAMETERS +typedef struct _EAXFLANGERPROPERTIES +{ + unsigned long ulWaveform; // Waveform selector - see enum above + long lPhase; // Phase (Degrees) + float flRate; // Rate (Hz) + float flDepth; // Depth (0 to 1) + float flFeedback; // Feedback (0 to 1) + float flDelay; // Delay (seconds) +} EAXFLANGERPROPERTIES, *LPEAXFLANGERPROPERTIES; + +// Property ranges and defaults: + +#define EAXFLANGER_MINWAVEFORM 0 +#define EAXFLANGER_MAXWAVEFORM 1 +#define EAXFLANGER_DEFAULTWAVEFORM 1 + +#define EAXFLANGER_MINPHASE (-180) +#define EAXFLANGER_MAXPHASE 180 +#define EAXFLANGER_DEFAULTPHASE 0 + +#define EAXFLANGER_MINRATE 0.0f +#define EAXFLANGER_MAXRATE 10.0f +#define EAXFLANGER_DEFAULTRATE 0.27f + +#define EAXFLANGER_MINDEPTH 0.0f +#define EAXFLANGER_MAXDEPTH 1.0f +#define EAXFLANGER_DEFAULTDEPTH 1.0f + +#define EAXFLANGER_MINFEEDBACK (-1.0f) +#define EAXFLANGER_MAXFEEDBACK 1.0f +#define EAXFLANGER_DEFAULTFEEDBACK (-0.5f) + +#define EAXFLANGER_MINDELAY 0.0f +#define EAXFLANGER_MAXDELAY 0.004f +#define EAXFLANGER_DEFAULTDELAY 0.002f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Frequency Shifter Effect + +// EAX FREQUENCY SHIFTER {DC3E1880-9212-11D3-939D-C0F02DD6F0} + +DEFINE_GUID(EAX_FREQUENCYSHIFTER_EFFECT, + 0xdc3e1880, + 0x9212, + 0x11d3, + 0x93, 0x9d, 0x0, 0xc0, 0xf0, 0x2d, 0xd6, 0xf0); + +// Frequency Shifter properties +typedef enum +{ + EAXFREQUENCYSHIFTER_NONE, + EAXFREQUENCYSHIFTER_ALLPARAMETERS, + EAXFREQUENCYSHIFTER_FREQUENCY, + EAXFREQUENCYSHIFTER_LEFTDIRECTION, + EAXFREQUENCYSHIFTER_RIGHTDIRECTION +} EAXFREQUENCYSHIFTER_PROPERTY; + +// OR these flags with property id +#define EAXFREQUENCYSHIFTER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXFREQUENCYSHIFTER_DEFERRED 0x80000000 // changes take effect later +#define EAXFREQUENCYSHIFTER_COMMITDEFERREDSETTINGS (EAXFREQUENCYSHIFTER_NONE | \ + EAXFREQUENCYSHIFTER_IMMEDIATE) + +// used by EAXFREQUENCYSHIFTER_LEFTDIRECTION and EAXFREQUENCYSHIFTER_RIGHTDIRECTION +enum +{ + EAX_FREQUENCYSHIFTER_DOWN, + EAX_FREQUENCYSHIFTER_UP, + EAX_FREQUENCYSHIFTER_OFF +}; + +// Use this structure for EAXFREQUENCYSHIFTER_ALLPARAMETERS +typedef struct _EAXFREQUENCYSHIFTERPROPERTIES +{ + float flFrequency; // (Hz) + unsigned long ulLeftDirection; // see enum above + unsigned long ulRightDirection; // see enum above +} EAXFREQUENCYSHIFTERPROPERTIES, *LPEAXFREQUENCYSHIFTERPROPERTIES; + +// Property ranges and defaults: + +#define EAXFREQUENCYSHIFTER_MINFREQUENCY 0.0f +#define EAXFREQUENCYSHIFTER_MAXFREQUENCY 24000.0f +#define EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY 0.0f + +#define EAXFREQUENCYSHIFTER_MINLEFTDIRECTION 0 +#define EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION 2 +#define EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION 0 + +#define EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION 0 +#define EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION 2 +#define EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION 0 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Vocal Morpher Effect + +// EAX VOCAL MORPHER {E41CF10C-3383-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_VOCALMORPHER_EFFECT, + 0xe41cf10c, + 0x3383, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Vocal Morpher properties +typedef enum +{ + EAXVOCALMORPHER_NONE, + EAXVOCALMORPHER_ALLPARAMETERS, + EAXVOCALMORPHER_PHONEMEA, + EAXVOCALMORPHER_PHONEMEACOARSETUNING, + EAXVOCALMORPHER_PHONEMEB, + EAXVOCALMORPHER_PHONEMEBCOARSETUNING, + EAXVOCALMORPHER_WAVEFORM, + EAXVOCALMORPHER_RATE +} EAXVOCALMORPHER_PROPERTY; + +// OR these flags with property id +#define EAXVOCALMORPHER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXVOCALMORPHER_DEFERRED 0x80000000 // changes take effect later +#define EAXVOCALMORPHER_COMMITDEFERREDSETTINGS (EAXVOCALMORPHER_NONE | \ + EAXVOCALMORPHER_IMMEDIATE) + +// used by EAXVOCALMORPHER_PHONEMEA and EAXVOCALMORPHER_PHONEMEB +enum +{ + A, E, I, O, U, AA, AE, AH, AO, EH, ER, IH, IY, UH, UW, B, D, F, G, + J, K, L, M, N, P, R, S, T, V, Z +}; + +// used by EAXVOCALMORPHER_WAVEFORM +enum +{ + EAX_VOCALMORPHER_SINUSOID, + EAX_VOCALMORPHER_TRIANGLE, + EAX_VOCALMORPHER_SAWTOOTH +}; + +// Use this structure for EAXVOCALMORPHER_ALLPARAMETERS +typedef struct _EAXVOCALMORPHERPROPERTIES +{ + unsigned long ulPhonemeA; // see enum above + long lPhonemeACoarseTuning; // (semitones) + unsigned long ulPhonemeB; // see enum above + long lPhonemeBCoarseTuning; // (semitones) + unsigned long ulWaveform; // Waveform selector - see enum above + float flRate; // (Hz) +} EAXVOCALMORPHERPROPERTIES, *LPEAXVOCALMORPHERPROPERTIES; + +// Property ranges and defaults: + +#define EAXVOCALMORPHER_MINPHONEMEA 0 +#define EAXVOCALMORPHER_MAXPHONEMEA 29 +#define EAXVOCALMORPHER_DEFAULTPHONEMEA 0 + +#define EAXVOCALMORPHER_MINPHONEMEACOARSETUNING (-24) +#define EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING 24 +#define EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING 0 + +#define EAXVOCALMORPHER_MINPHONEMEB 0 +#define EAXVOCALMORPHER_MAXPHONEMEB 29 +#define EAXVOCALMORPHER_DEFAULTPHONEMEB 10 + +#define EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING (-24) +#define EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING 24 +#define EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING 0 + +#define EAXVOCALMORPHER_MINWAVEFORM 0 +#define EAXVOCALMORPHER_MAXWAVEFORM 2 +#define EAXVOCALMORPHER_DEFAULTWAVEFORM 0 + +#define EAXVOCALMORPHER_MINRATE 0.0f +#define EAXVOCALMORPHER_MAXRATE 10.0f +#define EAXVOCALMORPHER_DEFAULTRATE 1.41f + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Pitch Shifter Effect + +// EAX PITCH SHIFTER {E7905100-AFB2-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_PITCHSHIFTER_EFFECT, + 0xe7905100, + 0xafb2, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Pitch Shifter properties +typedef enum +{ + EAXPITCHSHIFTER_NONE, + EAXPITCHSHIFTER_ALLPARAMETERS, + EAXPITCHSHIFTER_COARSETUNE, + EAXPITCHSHIFTER_FINETUNE +} EAXPITCHSHIFTER_PROPERTY; + +// OR these flags with property id +#define EAXPITCHSHIFTER_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXPITCHSHIFTER_DEFERRED 0x80000000 // changes take effect later +#define EAXPITCHSHIFTER_COMMITDEFERREDSETTINGS (EAXPITCHSHIFTER_NONE | \ + EAXPITCHSHIFTER_IMMEDIATE) + +// Use this structure for EAXPITCHSHIFTER_ALLPARAMETERS +typedef struct _EAXPITCHSHIFTERPROPERTIES +{ + long lCoarseTune; // Amount of pitch shift (semitones) + long lFineTune; // Amount of pitch shift (cents) +} EAXPITCHSHIFTERPROPERTIES, *LPEAXPITCHSHIFTERPROPERTIES; + +// Property ranges and defaults: + +#define EAXPITCHSHIFTER_MINCOARSETUNE (-12) +#define EAXPITCHSHIFTER_MAXCOARSETUNE 12 +#define EAXPITCHSHIFTER_DEFAULTCOARSETUNE 12 + +#define EAXPITCHSHIFTER_MINFINETUNE (-50) +#define EAXPITCHSHIFTER_MAXFINETUNE 50 +#define EAXPITCHSHIFTER_DEFAULTFINETUNE 0 + +//////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// +// Ring Modulator Effect + +// EAX RING MODULATOR {B89FE60-AFB5-11D2-88DD-A024D13CE1} + +DEFINE_GUID(EAX_RINGMODULATOR_EFFECT, + 0xb89fe60, + 0xafb5, + 0x11d2, + 0x88, 0xdd, 0x0, 0xa0, 0x24, 0xd1, 0x3c, 0xe1); + +// Ring Modulator properties +typedef enum +{ + EAXRINGMODULATOR_NONE, + EAXRINGMODULATOR_ALLPARAMETERS, + EAXRINGMODULATOR_FREQUENCY, + EAXRINGMODULATOR_HIGHPASSCUTOFF, + EAXRINGMODULATOR_WAVEFORM +} EAXRINGMODULATOR_PROPERTY; + +// OR these flags with property id +#define EAXRINGMODULATOR_IMMEDIATE 0x00000000 // changes take effect immediately +#define EAXRINGMODULATOR_DEFERRED 0x80000000 // changes take effect later +#define EAXRINGMODULATOR_COMMITDEFERREDSETTINGS (EAXRINGMODULATOR_NONE | \ + EAXRINGMODULATOR_IMMEDIATE) + +// used by EAXRINGMODULATOR_WAVEFORM +enum +{ + EAX_RINGMODULATOR_SINUSOID, + EAX_RINGMODULATOR_SAWTOOTH, + EAX_RINGMODULATOR_SQUARE +}; + +// Use this structure for EAXRINGMODULATOR_ALLPARAMETERS +typedef struct _EAXRINGMODULATORPROPERTIES +{ + float flFrequency; // Frequency of modulation (Hz) + float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz) + unsigned long ulWaveform; // Waveform selector - see enum above +} EAXRINGMODULATORPROPERTIES, *LPEAXRINGMODULATORPROPERTIES; + +// Property ranges and defaults: + +#define EAXRINGMODULATOR_MINFREQUENCY 0.0f +#define EAXRINGMODULATOR_MAXFREQUENCY 8000.0f +#define EAXRINGMODULATOR_DEFAULTFREQUENCY 440.0f + +#define EAXRINGMODULATOR_MINHIGHPASSCUTOFF 0.0f +#define EAXRINGMODULATOR_MAXHIGHPASSCUTOFF 24000.0f +#define EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF 800.0f + +#define EAXRINGMODULATOR_MINWAVEFORM 0 +#define EAXRINGMODULATOR_MAXWAVEFORM 2 +#define EAXRINGMODULATOR_DEFAULTWAVEFORM 0 + +//////////////////////////////////////////////////////////////////////////// + +#pragma pack(pop) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif diff --git a/codemp/client/eax/eaxman.h b/codemp/client/eax/eaxman.h new file mode 100644 index 0000000..25e1c9c --- /dev/null +++ b/codemp/client/eax/eaxman.h @@ -0,0 +1,171 @@ +/* +*/ +#ifndef __EAXMANH +#define __EAXMANH + +#define COM_NO_WINDOWS_H +#include +#include "eax.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +//#define CLSID_EAXMANAGER CLSID_EAX20_Manager +//#define IID_IEaxManager IID_EAX20_Manager +#define EM_MAX_NAME 32 + +#define EMFLAG_IDDEFAULT (-1) +#define EMFLAG_IDNONE (-2) +#define EMFLAG_LOCKPOSITION 1 +#define EMFLAG_LOADFROMMEMORY 2 +#define EMFLAG_NODIFFRACTION 4 + +typedef struct _EMPOINT { + float fX; + float fY; + float fZ; +} EMPOINT; +typedef EMPOINT FAR *LPEMPOINT; + +typedef struct _LISTENERATTRIBUTES { + float fDistanceFactor; + float fRolloffFactor; + float fDopplerFactor; +} LISTENERATTRIBUTES; +typedef LISTENERATTRIBUTES FAR *LPLISTENERATTRIBUTES; + +typedef struct _SOURCEATTRIBUTES { + EAXSOURCEPROPERTIES eaxAttributes; + unsigned long ulInsideConeAngle; + unsigned long ulOutsideConeAngle; + long lConeOutsideVolume; + float fConeXdir; + float fConeYdir; + float fConeZdir; + float fMinDistance; + float fMaxDistance; + long lDupCount; + long lPriority; +} SOURCEATTRIBUTES; +typedef SOURCEATTRIBUTES FAR *LPSOURCEATTRIBUTES; + +typedef struct _MATERIALATTRIBUTES { + long lLevel; + float fLFRatio; + float fRoomRatio; + DWORD dwFlags; +} MATERIALATTRIBUTES; +typedef MATERIALATTRIBUTES FAR *LPMATERIALATTRIBUTES; + +#define EMMATERIAL_OBSTRUCTS 1 +#define EMMATERIAL_OCCLUDES 3 + +typedef struct _DIFFRACTIONBOX { + long lSubspaceID; + EMPOINT empMin; + EMPOINT empMax; +} DIFFRACTIONBOX; +typedef DIFFRACTIONBOX FAR *LPDIFFRACTIONBOX; + +// {7CE4D6E6-562F-11d3-8812-005004062F83} +DEFINE_GUID(CLSID_EAXMANAGER, 0x60b721a1, 0xf7c8, 0x11d2, 0xa0, 0x2e, 0x0, 0x50, 0x4, 0x6, 0x18, 0xb8); + +#ifdef __cplusplus +struct IEaxManager; +#endif // __cplusplus + +typedef struct IEaxManager *LPEAXMANAGER; + +// {7CE4D6E8-562F-11d3-8812-005004062F83} +DEFINE_GUID(IID_IEaxManager, 0x60b721a2, 0xf7c8, 0x11d2, 0xa0, 0x2e, 0x0, 0x50, 0x4, 0x6, 0x18, 0xb8); + +#undef INTERFACE +#define INTERFACE IEaxManager + +extern HRESULT __stdcall EaxManagerCreate(LPEAXMANAGER*); +typedef HRESULT (__stdcall *LPEAXMANAGERCREATE)(LPEAXMANAGER*); + +DECLARE_INTERFACE_(IEaxManager, IUnknown) +{ + // IUnknown methods + STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID *) PURE; + STDMETHOD_(ULONG,AddRef) (THIS) PURE; + STDMETHOD_(ULONG,Release) (THIS) PURE; + + STDMETHOD(GetDataSetSize) (THIS_ unsigned long*, DWORD) PURE; + STDMETHOD(LoadDataSet) (THIS_ char*, DWORD) PURE; + STDMETHOD(FreeDataSet) (THIS_ DWORD) PURE; + STDMETHOD(GetListenerAttributes) (THIS_ LPLISTENERATTRIBUTES) PURE; + STDMETHOD(GetSourceID) (THIS_ char*, long*) PURE; + STDMETHOD(GetSourceAttributes) (THIS_ long, LPSOURCEATTRIBUTES) PURE; + STDMETHOD(GetSourceNumInstances) (THIS_ long, long*) PURE; + STDMETHOD(GetSourceInstancePos) (THIS_ long, long, LPEMPOINT) PURE; + STDMETHOD(GetEnvironmentID) (THIS_ char*, long*) PURE; + STDMETHOD(GetEnvironmentAttributes) (THIS_ long, LPEAXREVERBPROPERTIES) PURE; + STDMETHOD(GetMaterialID) (THIS_ char*, long*) PURE; + STDMETHOD(GetMaterialAttributes) (THIS_ long, LPMATERIALATTRIBUTES) PURE; + STDMETHOD(GetGeometrySetID) (THIS_ char*, long*) PURE; + STDMETHOD(GetListenerDynamicAttributes) (THIS_ long, LPEMPOINT, long*, DWORD) PURE; + STDMETHOD(GetSourceDynamicAttributes) (THIS_ long, LPEMPOINT, long*, float*, long*, float*, float*, LPEMPOINT, DWORD) PURE; +// STDMETHOD(GetSubSpaceID) (THIS_ long, LPEMPOINT, long *) PURE; + STDMETHOD(GetEnvironmentName) (THIS_ long, char *szString, long lStrlen) PURE; +}; + +#if !defined(__cplusplus) || defined(CINTERFACE) +#define IEaxManager_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) +#define IEaxManager_AddRef(p) (p)->lpVtbl->AddRef(p) +#define IEaxManager_Release(p) (p)->lpVtbl->Release(p) +#define IEaxManager_GetDataSetSize(p,a,b) (p)->lpVtbl->GetDataSetSize(p,a,b) +#define IEaxManager_LoadDataSet(p,a,b) (p)->lpVtbl->LoadDataSet(p,a,b) +#define IEaxManager_FreeDataSet(p,a) (p)->lpVtbl->FreeDataSet(p,a) +#define IEaxManager_GetListenerAttributes(p,a) (p)->lpVtbl->GetListenerAttributes(p,a) +#define IEaxManager_GetSourceID(p,a,b) (p)->lpVtbl->GetSourceID(p,a,b) +#define IEaxManager_GetSourceAttributes(p,a,b) (p)->lpVtbl->GetSourceAttributes(p,a,b) +#define IEaxManager_GetSourceNumInstances(p,a,b) (p)->lpVtbl->GetSourceNumInstances(p,a,b) +#define IEaxManager_GetSourceInstancePos(p,a,b,c) (p)->lpVtbl->GetSourceInstancePos(p,a,b,c) +#define IEaxManager_GetEnvironmentID(p,a,b) (p)->lpVtbl->GetEnvironmentID(p,a,b) +#define IEaxManager_GetEnvironmentAttributes(p,a,b) (p)->lpVtbl->GetEnvironmentAttributes(p,a,b) +#define IEaxManager_GetMaterialID(p,a,b) (p)->lpVtbl->GetMaterialID(p,a,b) +#define IEaxManager_GetMaterialAttributes(p,a,b) (p)->lpVtbl->GetMaterialAttributes(p,a,b) +#define IEaxManager_GetGeometrySetID(p,a,b) (p)->lpVtbl->GetGeometrySetID(p,a,b) +#define IEaxManager_GetListenerDynamicAttributes(p,a,b,c,d) (p)->lpVtbl->GetListenerDynamicAttributes(p,a,b,c,d) +#define IEaxManager_GetSourceDynamicAttributes(p,a,b,c,d,e,f,g,h,i) (p)->lpVtbl->GetSourceDynamicAttributes(p,a,b,c,d,e,f,g,h,i) +//#define IEaxManager_GetSubSpaceID(p,a,b,c) (p)->lpVtbl->GetSubSpaceID(p,a,b,c) +#define IEaxManager_GetEnvironmentName(p,a,b,c) (p)->lpVtbl->GetEnvironmentName(p,a,b,c) +#else +#define IEaxManager_QueryInterface(p,a,b) (p)->QueryInterface(a,b) +#define IEaxManager_AddRef(p) (p)->AddRef() +#define IEaxManager_Release(p) (p)->Release() +#define IEaxManager_GetDataSetSize(p,a,b) (p)->GetDataSetSize(a,b) +#define IEaxManager_LoadDataSet(p,a,b) (p)->LoadDataSet(a,b) +#define IEaxManager_FreeDataSet(p,a) (p)->FreeDataSet(a) +#define IEaxManager_GetListenerAttributes(p,a) (p)->GetListenerAttributes(a) +#define IEaxManager_GetSourceID(p,a,b) (p)->GetSourceID(a,b) +#define IEaxManager_GetSourceAttributes(p,a,b) (p)->GetSourceAttributes(a,b) +#define IEaxManager_GetSourceNumInstances(p,a,b) (p)->GetSourceNumInstances(a,b) +#define IEaxManager_GetSourceInstancePos(p,a,b,c) (p)->GetSourceInstancePos(a,b,c) +#define IEaxManager_GetEnvironmentID(p,a,b) (p)->GetEnvironmentID(a,b) +#define IEaxManager_GetEnvironmentAttributes(p,a,b) (p)->GetEnvironmentAttributes(a,b) +#define IEaxManager_GetMaterialID(p,a,b) (p)->GetMaterialID(a,b) +#define IEaxManager_GetMaterialAttributes(p,a,b) (p)->GetMaterialAttributes(a,b) +#define IEaxManager_GetGeometrySetID(p,a,b) (p)->GetGeometrySetID(a,b) +#define IEaxManager_GetListenerDynamicAttributes(p,a,b,c,d) (p)->GetListenerDynamicAttributes(a,b,c,d) +#define IEaxManager_GetSourceDynamicAttributes(p,a,b,c,d,e,f,g,h,i) (p)->GetSourceDynamicAttributes(a,b,c,d,e,f,g,h,i) +//#define IEaxManager_GetSubSpaceID(p,a,b,c) (p)->GetSubSpaceID(a,b,c) +#define IEaxManager_GetEnvironmentName(p,a,b,c) (p)->GetEnvironmentName(a,b,c) +#endif + +#define EM_OK 0 +#define EM_INVALIDID MAKE_HRESULT(1, FACILITY_ITF, 1) +#define EM_IDNOTFOUND MAKE_HRESULT(1, FACILITY_ITF, 2) +#define EM_FILENOTFOUND MAKE_HRESULT(1, FACILITY_ITF, 3) +#define EM_FILEINVALID MAKE_HRESULT(1, FACILITY_ITF, 4) +#define EM_VERSIONINVALID MAKE_HRESULT(1, FACILITY_ITF, 5) +#define EM_INSTANCENOTFOUND MAKE_HRESULT(1, FACILITY_ITF, 6) + +#ifdef __cplusplus +}; +#endif // __cplusplus + +#endif diff --git a/codemp/client/fffx.h b/codemp/client/fffx.h new file mode 100644 index 0000000..f66f191 --- /dev/null +++ b/codemp/client/fffx.h @@ -0,0 +1,129 @@ +// Filename:- fffx.h (Force Feedback FX) + +#ifndef FFFX_H +#define FFFX_H + +// this part can be seen by the CGAME as well... + +// These enums match the generic ones built into the effects ROM in the MS SideWinder FF Joystick, +// so blame MS for anything you don't like (like that aircraft carrier one - jeez!)... +// +// (Judging from the names of most of these, the MS FF guys appear to be rather fond of ID-type games...) +// +typedef enum + { + fffx_RandomNoise=0, + fffx_AircraftCarrierTakeOff, // this one is pointless / dumb + fffx_BasketballDribble, + fffx_CarEngineIdle, + fffx_ChainsawIdle, + fffx_ChainsawInAction, + fffx_DieselEngineIdle, + fffx_Jump, + fffx_Land, + fffx_MachineGun, + fffx_Punched, + fffx_RocketLaunch, + fffx_SecretDoor, + fffx_SwitchClick, + fffx_WindGust, + fffx_WindShear, // also pretty crap + fffx_Pistol, + fffx_Shotgun, + fffx_Laser1, + fffx_Laser2, + fffx_Laser3, + fffx_Laser4, + fffx_Laser5, + fffx_Laser6, + fffx_OutOfAmmo, + fffx_LightningGun, + fffx_Missile, + fffx_GatlingGun, + fffx_ShortPlasma, + fffx_PlasmaCannon1, + fffx_PlasmaCannon2, + fffx_Cannon, +#ifdef _XBOX + fffx_FallingShort, + fffx_FallingMedium, + fffx_FallingFar, + fffx_StartConst, + fffx_StopConst, +#endif + // + fffx_NUMBEROF, + fffx_NULL // special use, ignore during array mallocs etc, use fffx_NUMBEROF instead + } ffFX_e; + + +#ifndef CGAME_ONLY + +/////////////////////////// START of functions to call ///////////////////////////////// +// +// +// +// Once the game is running you should *only* access FF functions through these 4 macros. Savvy? +// +// Usage note: In practice, you can have about 4 FF FX running concurrently, though I defy anyone to make sense +// of that amount of white-noise vibration. MS guidelines say you shouldn't leave vibration on for long periods +// of time (eg engine rumble) because of various nerve-damage/lawsuit issues, so for this reason there is no API +// support for setting durations etc. All FF FX stuff here is designed for things like firing, hit damage, driving +// over bumps, etc that can be played as one-off events, though you *can* do things like FFFX_ENSURE(fffx_ChainsawIdle) +// if you really want to. +// +// Combining small numbers of effects such as having a laser firing (MS photons have higher mass apparently ), +// and then firing a machine gun in bursts as well are no problem, and easily felt, hence the ability to stop playing +// individual FF FX. +// +#define FFFX_START(f) FF_Play(f) +#define FFFX_ENSURE(f) FF_EnsurePlaying(f) +#define FFFX_STOP(f) FF_Stop(f) // some effects (eg. gatling, chainsaw), need this, or they play too long after trigger-off. +#define FFFX_STOPALL FF_StopAll() + + +// +// These 2 are called at app start/stop, but you can call FF_Init to change FF devices anytime (takes a couple of seconds) +// +void FF_Init(qboolean bTryMouseFirst=true); +void FF_Shutdown(void); +// +// other stuff you may want to call but don't have to... +// +qboolean FF_IsAvailable(void); +qboolean FF_IsMouse(void); +qboolean FF_SetTension(int iTension); // tension setting 0..3 (0=none) +qboolean FF_SetSpring(long lSpring); // precision version of above, 0..n..10000 + // (only provided for command line fiddling with + // weird hardware. FF_SetTension(1) = default + // = FF_SetSpring(2000) (internal lookup table)) + +// +// +// +/////////////////////////// END of functions to call ///////////////////////////////// + + + + +// do *not* call this functions directly (or else!), use the macros above +// +void FF_Play (ffFX_e fffx); +void FF_EnsurePlaying (ffFX_e fffx); +void FF_Stop (ffFX_e fffx); +void FF_StopAll (void); + +#ifdef _XBOX +void FF_XboxShake (float intensity, int duration); +void FF_XboxDamage (int damage, float xpos); +#endif + +#define MAX_CONCURRENT_FFFXs 4 // only for my code to use/read, do NOT alter! + + + +#endif // #ifndef CGAME_ONLY +#endif // #ifndef FFFX_H + +//////////////////////// eof ////////////////////// + diff --git a/codemp/client/fxexport.cpp b/codemp/client/fxexport.cpp new file mode 100644 index 0000000..79252d7 --- /dev/null +++ b/codemp/client/fxexport.cpp @@ -0,0 +1,105 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "client.h" +#include "FXScheduler.h" + +//#define __FXCHECKER + +#ifdef __FXCHECKER + #include +#endif // __FXCHECKER + +int FX_RegisterEffect(const char *file) +{ + return theFxScheduler.RegisterEffect(file, true); +} + +void FX_PlayEffect( const char *file, vec3_t org, vec3_t fwd, int vol, int rad ) +{ +#ifdef __FXCHECKER + if (_isnan(org[0]) || _isnan(org[1]) || _isnan(org[2])) + { + assert(0); + } + if (_isnan(fwd[0]) || _isnan(fwd[1]) || _isnan(fwd[2])) + { + assert(0); + } + if (fabs(fwd[0]) < 0.1 && fabs(fwd[1]) < 0.1 && fabs(fwd[2]) < 0.1) + { + assert(0); + } +#endif // __FXCHECKER + + theFxScheduler.PlayEffect(file, org, fwd, vol, rad); +} + +void FX_PlayEffectID( int id, vec3_t org, vec3_t fwd, int vol, int rad, qboolean isPortal ) +{ +#ifdef __FXCHECKER + if (_isnan(org[0]) || _isnan(org[1]) || _isnan(org[2])) + { + assert(0); + } + if (_isnan(fwd[0]) || _isnan(fwd[1]) || _isnan(fwd[2])) + { + assert(0); + } + if (fabs(fwd[0]) < 0.1 && fabs(fwd[1]) < 0.1 && fabs(fwd[2]) < 0.1) + { + assert(0); + } +#endif // __FXCHECKER + + theFxScheduler.PlayEffect(id, org, fwd, vol, rad, !!isPortal ); +} + +void FX_PlayBoltedEffectID( int id, vec3_t org, + const int boltInfo, int iGhoul2, int iLooptime, qboolean isRelative ) +{ + theFxScheduler.PlayEffect(id, org, 0, boltInfo, iGhoul2, -1, -1, -1, qfalse, iLooptime, !!isRelative ); +} + +void FX_PlayEntityEffectID( int id, vec3_t org, + vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ) +{ +#ifdef __FXCHECKER + if (_isnan(org[0]) || _isnan(org[1]) || _isnan(org[2])) + { + assert(0); + } +#endif // __FXCHECKER + + theFxScheduler.PlayEffect(id, org, axis, boltInfo, NULL, -1, vol, rad ); +} + +void FX_AddScheduledEffects( qboolean portal ) +{ + theFxScheduler.AddScheduledEffects(!!portal); +} + +void FX_Draw2DEffects( float screenXScale, float screenYScale ) +{ + theFxScheduler.Draw2DEffects( screenXScale, screenYScale ); +} + +int FX_InitSystem( refdef_t* refdef ) +{ + return FX_Init( refdef ); +} + +void FX_SetRefDefFromCGame( refdef_t* refdef ) +{ + FX_SetRefDef( refdef ); +} + +qboolean FX_FreeSystem( void ) +{ + return (qboolean)FX_Free( true ); +} + +void FX_AdjustTime( int time ) +{ + theFxHelper.AdjustTime(time); +} diff --git a/codemp/client/fxexport.h b/codemp/client/fxexport.h new file mode 100644 index 0000000..aca9217 --- /dev/null +++ b/codemp/client/fxexport.h @@ -0,0 +1,23 @@ +#ifndef FX_EXPORT_H_INC +#define FX_EXPORT_H_INC + +int FX_RegisterEffect(const char *file); + +void FX_PlayEffect( const char *file, vec3_t org, vec3_t fwd, int vol, int rad ); // builds arbitrary perp. right vector, does a cross product to define up + +void FX_PlayEffectID( int id, vec3_t org, vec3_t fwd, int vol, int rad, qboolean isPortal = qfalse ); // builds arbitrary perp. right vector, does a cross product to define up +void FX_PlayEntityEffectID( int id, vec3_t org, + vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ); +void FX_PlayBoltedEffectID( int id, vec3_t org, + const int boltInfo, int iGhoul2, int iLooptime, qboolean isRelative ); + +void FX_AddScheduledEffects( qboolean portal ); +void FX_Draw2DEffects ( float screenXScale, float screenYScale ); + +int FX_InitSystem( refdef_t* refdef ); // called in CG_Init to purge the fx system. +void FX_SetRefDefFromCGame( refdef_t* refdef ); +qboolean FX_FreeSystem( void ); // ditches all active effects; +void FX_AdjustTime( int time ); + + +#endif // FX_EXPORT_H_INC diff --git a/codemp/client/fxprimitives.cpp b/codemp/client/fxprimitives.cpp new file mode 100644 index 0000000..3ac8154 --- /dev/null +++ b/codemp/client/fxprimitives.cpp @@ -0,0 +1,2497 @@ +// this include must remain at the top of every CPP file +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "client.h" + +#if !defined(FX_SCHEDULER_H_INC) + #include "FxScheduler.h" +#endif + +#if !defined(G2_H_INC) + #include "../ghoul2/G2.h" + #include "../ghoul2/G2_local.h" +#endif + +#ifdef VV_LIGHTING +#include "../win32/glw_win_dx8.h" +#endif + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +extern int drawnFx; + +//-------------------------- +// +// Base Effect Class +// +//-------------------------- +CEffect::CEffect(void) : + mMatImpactFX(MATIMPACTFX_NONE), + mMatImpactParm(-1), + mSoundVolume(-1), + mSoundRadius(-1), + mFlags(0) +{ + memset( &mRefEnt, 0, sizeof( mRefEnt )); +} + +//-------------------------- +// +// Derived Particle Class +// +//-------------------------- + +//---------------------------- +void CParticle::Init(void) +{ + mRefEnt.radius = 0.0f; + if (mFlags & FX_PLAYER_VIEW) + { + mOrigin1[0] = irand(0, 639); + mOrigin1[1] = irand(0, 479); + } +} + +//---------------------------- +void CParticle::Die(void) +{ + if ( mFlags & FX_DEATH_RUNS_FX && !(mFlags & FX_KILL_ON_IMPACT) ) + { + vec3_t norm; + + // Man, this just seems so, like, uncool and stuff... + VectorSet( norm, flrand(-1.0f, 1.0f), flrand(-1.0f, 1.0f), flrand(-1.0f, 1.0f)); + VectorNormalize( norm ); + + theFxScheduler.PlayEffect( mDeathFxID, mOrigin1, norm ); + } +} + +//---------------------------- +bool CParticle::Cull(void) +{ + vec3_t dir; + + if (mFlags & FX_PLAYER_VIEW) + { + // this will be drawn as a 2D effect so don't cull it + return false; + } + + // Get the direction to the view +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + VectorSubtract( mOrigin1, cg->refdef.vieworg, dir ); + else +#endif + VectorSubtract( mOrigin1, theFxHelper.refdef->vieworg, dir ); + + // Check if it's behind the viewer +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if ( (DotProduct( cg->refdef.viewaxis[0], dir )) < 0) // cg.cosHalfFOV * (len - mRadius) ) + { + return true; + } + } + else { +#endif + if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) < 0) // cg.cosHalfFOV * (len - mRadius) ) + { + return true; + } +#ifdef _XBOX + } +#endif + + // don't cull if this is hacked to show up close to the inview wpn + if (mFlags & FX_DEPTH_HACK) + { + return false; + } + // Can't be too close + float len = VectorLengthSquared( dir ); + if ( len < fx_nearCull->value ) + { + return true; + } + + return false; +} + +//---------------------------- +void CParticle::Draw(void) +{ + if ( mFlags & FX_DEPTH_HACK ) + { + // Not sure if first person needs to be set, but it can't hurt? + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + if (mFlags & FX_PLAYER_VIEW) + { + vec4_t color; + + color[0] = mRefEnt.shaderRGBA[0] / 255.0; + color[1] = mRefEnt.shaderRGBA[1] / 255.0; + color[2] = mRefEnt.shaderRGBA[2] / 255.0; + color[3] = mRefEnt.shaderRGBA[3] / 255.0; + + // add this 2D effect to the proper list. it will get drawn after the cgi.RenderScene call + theFxScheduler.Add2DEffect(mOrigin1[0], mOrigin1[1], mRefEnt.radius, mRefEnt.radius, color, mRefEnt.customShader); + } + else + { + // Add our refEntity to the scene + VectorCopy( mOrigin1, mRefEnt.origin ); + + theFxHelper.AddFxToScene(&mRefEnt); + } + drawnFx++; +} + +//---------------------------- +// Update +//---------------------------- +bool CParticle::Update(void) +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( !mGhoul2.IsValid()) + { // the thing we are bolted to is no longer valid, so we may as well just die. + return false; + } + + vec3_t org; + vec3_t ax[3]; + + // Get our current position and direction + if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, org, ax)) + { //could not get bolt + return false; + } + vec3_t realVel, realAccel; + + VectorMA( org, mOrgOffset[0], ax[0], org ); + VectorMA( org, mOrgOffset[1], ax[1], org ); + VectorMA( org, mOrgOffset[2], ax[2], org ); + + const float time = (theFxHelper.mTime - mTimeStart) * 0.001f; + // calc the real velocity and accel vectors + VectorScale( ax[0], mVel[0], realVel ); + VectorMA( realVel, mVel[1], ax[1], realVel ); + VectorMA( realVel, mVel[2], ax[2], realVel ); + //realVel[2] += 0.5f * mGravity * time; + + VectorScale( ax[0], mAccel[0], realAccel ); + VectorMA( realAccel, mAccel[1], ax[1], realAccel ); + VectorMA( realAccel, mAccel[2], ax[2], realAccel ); + + // Get our real velocity at the current time, taking into account the effects of acceleartion. NOTE: not sure if this is even 100% correct math-wise + VectorMA( realVel, time, realAccel, realVel ); + + // Now move us to where we should be at the given time + VectorMA( org, time, realVel, mOrigin1 ); + + } + else if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) + { + // we are marked for death + return false; + } + + + if ( !Cull() ) + { + // Only update these if the thing is visible. + UpdateSize(); + UpdateRGB(); + UpdateAlpha(); + UpdateRotation(); + + Draw(); + } + + return true; +} + +//---------------------------- +// Update Origin +//---------------------------- +bool CParticle::UpdateOrigin(void) +{ + vec3_t new_origin; + + VectorMA( mVel, theFxHelper.mRealTime, mAccel, mVel ); + + // Predict the new position + new_origin[0] = mOrigin1[0] + (theFxHelper.mRealTime * mVel[0]);// + (theFxHelper.mHalfRealTimeSq * mVel[0]); + new_origin[1] = mOrigin1[1] + (theFxHelper.mRealTime * mVel[1]);// + (theFxHelper.mHalfRealTimeSq * mVel[1]); + new_origin[2] = mOrigin1[2] + (theFxHelper.mRealTime * mVel[2]);// + (theFxHelper.mHalfRealTimeSq * mVel[2]); + + // Only perform physics if this object is tagged to do so + if ( (mFlags & FX_APPLY_PHYSICS) && !(mFlags & FX_PLAYER_VIEW) ) + { + bool solid; + + if ( mFlags & FX_EXPENSIVE_PHYSICS ) + { + solid = true; // by setting this to true, we force a real trace to happen + } + else + { + // if this returns solid, we need to do a trace + if (!com_RMG || com_RMG->integer) + { // don't do this call for RMG maps + TCGPointContents *data = (TCGPointContents *)cl->mSharedMemory; + + VectorCopy(new_origin, data->mPoint); + data->mPassEntityNum = ENTITYNUM_WORLD; + + // if this returns solid, we need to do a trace + solid = !!(VM_Call( cgvm, CG_POINT_CONTENTS ) & MASK_SOLID); + } + else + { + solid = false; + } + } + + if ( solid ) + { + trace_t trace; + float dot; + + if ( mFlags & FX_USE_BBOX ) + { + if (mFlags & FX_GHOUL2_TRACE) + { + theFxHelper.G2Trace( trace, mOrigin1, mMin, mMax, new_origin, -1, MASK_SOLID ); + } + else + { + theFxHelper.Trace( trace, mOrigin1, mMin, mMax, new_origin, -1, MASK_SOLID ); + } + } + else + { + if (mFlags & FX_GHOUL2_TRACE) + { + theFxHelper.G2Trace( trace, mOrigin1, NULL, NULL, new_origin, -1, MASK_PLAYERSOLID ); + } + else + { + theFxHelper.Trace( trace, mOrigin1, NULL, NULL, new_origin, -1, MASK_SOLID ); + } + } + + // Hit something + if (trace.startsolid || trace.allsolid) + { + VectorClear( mVel ); + VectorClear( mAccel ); + + if ((mFlags & FX_GHOUL2_TRACE) && (mFlags & FX_IMPACT_RUNS_FX)) + { + static vec3_t bsNormal = {0, 1, 0}; + + theFxScheduler.PlayEffect( mImpactFxID, trace.endpos, bsNormal ); + } + + mFlags &= ~(FX_APPLY_PHYSICS | FX_IMPACT_RUNS_FX); + + return true; + } + else if ( trace.fraction < 1.0f )//&& !trace.startsolid && !trace.allsolid ) + { + if ( mFlags & FX_IMPACT_RUNS_FX && !(trace.surfaceFlags & SURF_NOIMPACT )) + { + theFxScheduler.PlayEffect( mImpactFxID, trace.endpos, trace.plane.normal ); + } + + // may need to interact with the material type we hit + theFxScheduler.MaterialImpact(&trace, (CEffect*)this); + + if ( mFlags & FX_KILL_ON_IMPACT ) + { + // time to die + return false; + } + + VectorMA( mVel, theFxHelper.mRealTime * trace.fraction, mAccel, mVel ); + + dot = DotProduct( mVel, trace.plane.normal ); + + VectorMA( mVel, -2.0f * dot, trace.plane.normal, mVel ); + + VectorScale( mVel, mElasticity, mVel ); + mElasticity *= 0.5f; + + // If the velocity is too low, make it stop moving, rotating, and turn off physics to avoid + // doing expensive operations when they aren't needed + //if ( trace.plane.normal[2] > 0.33f && mVel[2] < 10.0f ) + if (VectorLengthSquared(mVel) < 100.0f) + { + VectorClear( mVel ); + VectorClear( mAccel ); + + mFlags &= ~(FX_APPLY_PHYSICS | FX_IMPACT_RUNS_FX); + } + + // Set the origin to the exact impact point + VectorMA( trace.endpos, 1.0f, trace.plane.normal, mOrigin1 ); + return true; + } + } + } + + // No physics were done to this object, move it + VectorCopy( new_origin, mOrigin1 ); + + if (mFlags & FX_PLAYER_VIEW) + { + if (mOrigin1[0] < 0 || mOrigin1[0] > 639 || mOrigin1[1] < 0 || mOrigin1[1] > 479) + { + return false; + } + } + + return true; +} + +//---------------------------- +// Update Size +//---------------------------- +void CParticle::UpdateSize(void) +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + + if ( (mFlags & FX_SIZE_LINEAR) ) + { + // calculate element biasing + perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) / (float)(mTimeEnd - mTimeStart); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR, FX_WAVE, or FX_CLAMP + if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_NONLINEAR ) + { + if ( theFxHelper.mTime > mSizeParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)(theFxHelper.mTime - mSizeParm) / (float)(mTimeEnd - mSizeParm); + } + + if ( mFlags & FX_SIZE_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * cosf( (theFxHelper.mTime - mTimeStart) * mSizeParm ); + } + else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_CLAMP ) + { + if ( theFxHelper.mTime < mSizeParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mSizeParm - theFxHelper.mTime) / (float)(mSizeParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( (mFlags & FX_SIZE_LINEAR) ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_SIZE_RAND ) + { + // Random simply modulates the existing value + perc1 = flrand(0.0f, perc1); + } + + mRefEnt.radius = (mSizeStart * perc1) + (mSizeEnd * (1.0f - perc1)); +} + +inline int VectorToInt(vec3_t vec) +{ + int tmp, retval; + + _asm + { + push edx + mov edx, [vec] + fld dword ptr[edx + 0] + fld dword ptr[edx + 4] + fld dword ptr[edx + 8] + + mov eax, 0xff00 + + fistp tmp + mov al, byte ptr [tmp] + shl eax, 16 + + fistp tmp + mov ah, byte ptr [tmp] + + fistp tmp + mov al, byte ptr [tmp] + + mov [retval], eax + pop edx + } + return(retval); +} + +//---------------------------- +// Update RGB +//---------------------------- +void CParticle::UpdateRGB(void) +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + vec3_t res; + + if ( (mFlags & FX_RGB_LINEAR) ) + { + // calculate element biasing + perc1 = 1.0f - (float)( theFxHelper.mTime - mTimeStart ) / (float)( mTimeEnd - mTimeStart ); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR, FX_WAVE, or FX_CLAMP + if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_NONLINEAR ) + { + if ( theFxHelper.mTime > mRGBParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)( theFxHelper.mTime - mRGBParm ) / (float)( mTimeEnd - mRGBParm ); + } + + if ( (mFlags & FX_RGB_LINEAR) ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * cosf(( theFxHelper.mTime - mTimeStart ) * mRGBParm ); + } + else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_CLAMP ) + { + if ( theFxHelper.mTime < mRGBParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mRGBParm - theFxHelper.mTime) / (float)(mRGBParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if (( mFlags & FX_RGB_LINEAR )) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_RGB_RAND ) + { + // Random simply modulates the existing value + perc1 = flrand(0.0f, perc1); + } + + // Now get the correct color + VectorScale( mRGBStart, perc1, res ); + VectorMA( res, 1.0f - perc1, mRGBEnd, res ); + + res[0] = Com_Clamp(0.0f, 1.0f, res[0]) * 255.0f; + res[1] = Com_Clamp(0.0f, 1.0f, res[1]) * 255.0f; + res[2] = Com_Clamp(0.0f, 1.0f, res[2]) * 255.0f; + + *(int *)mRefEnt.shaderRGBA = VectorToInt(res); +} + +//---------------------------- +// Update Alpha +//---------------------------- +void CParticle::UpdateAlpha(void) +{ + int alpha; + + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + + if ( (mFlags & FX_ALPHA_LINEAR) ) + { + // calculate element biasing + perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) / (float)(mTimeEnd - mTimeStart); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR, FX_WAVE, or FX_CLAMP + if (( mFlags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_NONLINEAR ) + { + if ( theFxHelper.mTime > mAlphaParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)(theFxHelper.mTime - mAlphaParm) / (float)(mTimeEnd - mAlphaParm); + } + + if (( mFlags & FX_ALPHA_LINEAR )) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * cosf( (theFxHelper.mTime - mTimeStart) * mAlphaParm ); + } + else if (( mFlags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_CLAMP ) + { + if ( theFxHelper.mTime < mAlphaParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mAlphaParm - theFxHelper.mTime) / (float)(mAlphaParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if (( mFlags & FX_ALPHA_LINEAR )) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + perc1 = (mAlphaStart * perc1) + (mAlphaEnd * (1.0f - perc1)); + + // We should be in the right range, but clamp to ensure + perc1 = Com_Clamp(0.0f, 1.0f, perc1); + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_ALPHA_RAND ) + { + // Random simply modulates the existing value + perc1 = flrand(0.0f, perc1); + } + + alpha = Com_Clamp(0, 255, perc1 * 255.0f); + if ( mFlags & FX_USE_ALPHA ) + { + // should use this when using art that has an alpha channel + mRefEnt.shaderRGBA[3] = (byte)alpha; + } + else + { + // Modulate the rgb fields by the alpha value to do the fade, works fine for additive blending + mRefEnt.shaderRGBA[0] = ((int)mRefEnt.shaderRGBA[0] * alpha) >> 8; + mRefEnt.shaderRGBA[1] = ((int)mRefEnt.shaderRGBA[1] * alpha) >> 8; + mRefEnt.shaderRGBA[2] = ((int)mRefEnt.shaderRGBA[2] * alpha) >> 8; + } +} + +//-------------------------- +void CParticle::UpdateRotation(void) +{ + mRefEnt.rotation += theFxHelper.mFrameTime * 0.01f * mRotationDelta; + mRotationDelta *= ( 1.0f - ( theFxHelper.mFrameTime * 0.0007f )); // decay rotationDelta +} + + +//-------------------------------- +// +// Derived Oriented Particle Class +// +//-------------------------------- +COrientedParticle::COrientedParticle(void) +{ + mRefEnt.reType = RT_ORIENTED_QUAD; +} + +//---------------------------- +bool COrientedParticle::Cull(void) +{ + vec3_t dir; +// float len; + + // Get the direction to the view +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + VectorSubtract( mOrigin1, cg->refdef.vieworg, dir ); + else +#endif + VectorSubtract( mOrigin1, theFxHelper.refdef->vieworg, dir ); + + // Check if it's behind the viewer +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if ( (DotProduct( cg->refdef.viewaxis[0], dir )) < 0 ) + { + return true; + } + } + else { +#endif + if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) < 0 ) + { + return true; + } +#ifdef _XBOX + } +#endif + +// len = VectorLengthSquared( dir ); + + // don't cull stuff that's associated with inview wpns + if ( mFlags & FX_DEPTH_HACK ) + { + return false; + } + // Can't be too close +// if ( len < fx_nearCull->value * fx_nearCull->value) +// { +// return true; +// } + + return false; +} + +//---------------------------- +void COrientedParticle::Draw(void) +{ + if ( mFlags & FX_DEPTH_HACK ) + { + // Not sure if first person needs to be set + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + // Add our refEntity to the scene + VectorCopy( mOrigin1, mRefEnt.origin ); + if ( !(mFlags&FX_RELATIVE) ) + { + VectorCopy( mNormal, mRefEnt.axis[0] ); + MakeNormalVectors( mRefEnt.axis[0], mRefEnt.axis[1], mRefEnt.axis[2] ); + } + + theFxHelper.AddFxToScene( &mRefEnt ); + drawnFx++; +} + +//---------------------------- +// Update +//---------------------------- +bool COrientedParticle::Update(void) +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( !mGhoul2.IsValid()) + { // the thing we are bolted to is no longer valid, so we may as well just die. + return false; + } + vec3_t org; + vec3_t ax[3]; + + // Get our current position and direction + if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, org, ax)) + { //could not get bolt + return false; + } + vec3_t realVel, realAccel; + + VectorMA( org, mOrgOffset[0], ax[0], org ); + VectorMA( org, mOrgOffset[1], ax[1], org ); + VectorMA( org, mOrgOffset[2], ax[2], org ); + + const float time = (theFxHelper.mTime - mTimeStart) * 0.001f; + // calc the real velocity and accel vectors + VectorScale( ax[0], mVel[0], realVel ); + VectorMA( realVel, mVel[1], ax[1], realVel ); + VectorMA( realVel, mVel[2], ax[2], realVel ); +// realVel[2] += 0.5f * mGravity * time; + + VectorScale( ax[0], mAccel[0], realAccel ); + VectorMA( realAccel, mAccel[1], ax[1], realAccel ); + VectorMA( realAccel, mAccel[2], ax[2], realAccel ); + + // Get our real velocity at the current time, taking into account the effects of acceleartion. NOTE: not sure if this is even 100% correct math-wise + VectorMA( realVel, time, realAccel, realVel ); + + // Now move us to where we should be at the given time + VectorMA( org, time, realVel, mOrigin1 ); + + //use the normalOffset and add that to the actual normal of the bolt + //NOTE: not tested!!! + VectorCopy( ax[0], mRefEnt.axis[0] ); + VectorCopy( ax[1], mRefEnt.axis[1] ); + VectorCopy( ax[2], mRefEnt.axis[2] ); + + //vec3_t offsetAngles; + //VectorSet( offsetAngles, 0, 90, 90 ); + + vec3_t offsetAxis[3]; + //NOTE: mNormal is actually PITCH YAW and ROLL offsets + AnglesToAxis( mNormal, offsetAxis ); + MatrixMultiply( offsetAxis, ax, mRefEnt.axis ); + } + else if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) + { + // we are marked for death + return false; + } + + if ( !Cull() ) + { // Only update these if the thing is visible. + UpdateSize(); + UpdateRGB(); + UpdateAlpha(); + UpdateRotation(); + + Draw(); + } + + return true; +} + + +//---------------------------- +// +// Derived Line Class +// +//---------------------------- +CLine::CLine(void) +{ + mRefEnt.reType = RT_LINE; +} + +//---------------------------- +void CLine::Draw(void) +{ + if ( mFlags & FX_DEPTH_HACK ) + { + // Not sure if first person needs to be set, but it can't hurt? + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + VectorCopy( mOrigin1, mRefEnt.origin ); + VectorCopy( mOrigin2, mRefEnt.oldorigin ); + + theFxHelper.AddFxToScene(&mRefEnt); + drawnFx++; +} + +//---------------------------- +bool CLine::Update(void) +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( !mGhoul2.IsValid()) + { // the thing we are bolted to is no longer valid, so we may as well just die. + return false; + } + + vec3_t ax[3]; + // Get our current position and direction + if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, mOrigin1, ax)) + { //could not get bolt + return false; + } + + VectorAdd(mOrigin1, mOrgOffset, mOrigin1); //add the offset to the bolt point + + VectorMA( mOrigin1, mVel[0], ax[0], mOrigin2 ); + VectorMA( mOrigin2, mVel[1], ax[1], mOrigin2 ); + VectorMA( mOrigin2, mVel[2], ax[2], mOrigin2 ); + } + + if ( !Cull()) + { + // Only update these if the thing is visible. + UpdateSize(); + UpdateRGB(); + UpdateAlpha(); + + Draw(); + } + + return true; +} + +//---------------------------- +// +// Derived Electricity Class +// +//---------------------------- +CElectricity::CElectricity(void) +{ + mRefEnt.reType = RT_ELECTRICITY; +} + +//---------------------------- +void CElectricity::Initialize(void) +{ + mRefEnt.frame = flrand(0.0f, 1.0f) * 1265536.0f; + mRefEnt.axis[0][2] = theFxHelper.mTime + (mTimeEnd - mTimeStart); // endtime + + if ( mFlags & FX_DEPTH_HACK ) + { + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + if ( mFlags & FX_BRANCH ) + { + mRefEnt.renderfx |= RF_FORKED; + } + + if ( mFlags & FX_TAPER ) + { + mRefEnt.renderfx |= RF_TAPERED; + } + + if ( mFlags & FX_GROW ) + { + mRefEnt.renderfx |= RF_GROW; + } +} + +//---------------------------- +void CElectricity::Draw(void) +{ + VectorCopy( mOrigin1, mRefEnt.origin ); + VectorCopy( mOrigin2, mRefEnt.oldorigin ); + mRefEnt.axis[0][0] = mChaos; + mRefEnt.axis[0][1] = mTimeEnd - mTimeStart; + + theFxHelper.AddFxToScene( &mRefEnt ); + drawnFx++; +} + +//---------------------------- +bool CElectricity::Update(void) +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( !mGhoul2.IsValid()) + { // the thing we are bolted to is no longer valid, so we may as well just die. + return false; + } + + vec3_t ax[3]; + // Get our current position and direction + if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, mOrigin1, ax)) + { //could not get bolt + return false; + } + + VectorAdd(mOrigin1, mOrgOffset, mOrigin1); //add the offset to the bolt point + + VectorMA( mOrigin1, mVel[0], ax[0], mOrigin2 ); + VectorMA( mOrigin2, mVel[1], ax[1], mOrigin2 ); + VectorMA( mOrigin2, mVel[2], ax[2], mOrigin2 ); + } + + if ( !Cull()) + { + // Only update these if the thing is visible. + UpdateSize(); + UpdateRGB(); + UpdateAlpha(); + + Draw(); + } + + return true; +} + +//---------------------------- +// +// Derived Tail Class +// +//---------------------------- +CTail::CTail(void) +{ + mRefEnt.reType = RT_LINE; +} + +//---------------------------- +void CTail::Draw(void) +{ + if ( mFlags & FX_DEPTH_HACK ) + { + // Not sure if first person needs to be set + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + VectorCopy( mOrigin1, mRefEnt.origin ); + + theFxHelper.AddFxToScene(&mRefEnt); + drawnFx++; +} + +//---------------------------- +bool CTail::Update(void) +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( !mGhoul2.IsValid()) + { // the thing we are bolted to is no longer valid, so we may as well just die. + return false; + } + vec3_t org; + vec3_t ax[3]; + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, org, ax)) + { //could not get bolt + return false; + } + } + + vec3_t realVel, realAccel; + + VectorMA( org, mOrgOffset[0], ax[0], org ); + VectorMA( org, mOrgOffset[1], ax[1], org ); + VectorMA( org, mOrgOffset[2], ax[2], org ); + + // calc the real velocity and accel vectors + // FIXME: if you want right and up movement in addition to the forward movement, you'll have to convert dir into a set of perp. axes and do some extra work + VectorScale( ax[0], mVel[0], realVel ); + VectorMA( realVel, mVel[1], ax[1], realVel ); + VectorMA( realVel, mVel[2], ax[2], realVel ); + + VectorScale( ax[0], mAccel[0], realAccel ); + VectorMA( realAccel, mAccel[1], ax[1], realAccel ); + VectorMA( realAccel, mAccel[2], ax[2], realAccel ); + + const float time = (theFxHelper.mTime - mTimeStart) * 0.001f; + + // Get our real velocity at the current time, taking into account the effects of acceleration. NOTE: not sure if this is even 100% correct math-wise + VectorMA( realVel, time, realAccel, realVel ); + + // Now move us to where we should be at the given time + VectorMA( org, time, realVel, mOrigin1 ); + + // Just calc an old point some time in the past, doesn't really matter when + VectorMA( org, (time - 0.003f), realVel, mOldOrigin ); + } +#ifdef _SOF2DEV_ + else if ( !fx_freeze->integer ) +#else + else +#endif + { + VectorCopy( mOrigin1, mOldOrigin ); + } + + if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) + { + // we are marked for death + return false; + } + + if ( !Cull() ) + { + // Only update these if the thing is visible. + UpdateSize(); + UpdateLength(); + UpdateRGB(); + UpdateAlpha(); + + CalcNewEndpoint(); + Draw(); + } + + return true; +} + +//---------------------------- +void CTail::UpdateLength(void) +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + + if ( mFlags & FX_LENGTH_LINEAR ) + { + // calculate element biasing + perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) / (float)(mTimeEnd - mTimeStart); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE + if (( mFlags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_NONLINEAR ) + { + if ( theFxHelper.mTime > mLengthParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)(theFxHelper.mTime - mLengthParm) / (float)(mTimeEnd - mLengthParm); + } + + if ( mFlags & FX_LENGTH_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * cosf( (theFxHelper.mTime - mTimeStart) * mLengthParm ); + } + else if (( mFlags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_CLAMP ) + { + if ( theFxHelper.mTime < mLengthParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mLengthParm - theFxHelper.mTime) / (float)(mLengthParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( mFlags & FX_LENGTH_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_LENGTH_RAND ) + { + // Random simply modulates the existing value + perc1 = flrand(0.0f, perc1); + } + + mLength = (mLengthStart * perc1) + (mLengthEnd * (1.0f - perc1)); +} + + +//---------------------------- +void CTail::CalcNewEndpoint(void) +{ + vec3_t temp; + + // FIXME: Hmmm, this looks dumb when physics are on and a bounce happens + VectorSubtract( mOldOrigin, mOrigin1, temp ); + + // I wish we didn't have to do a VectorNormalize every frame..... + VectorNormalize( temp ); + + VectorMA( mOrigin1, mLength, temp, mRefEnt.oldorigin ); +} + + +//---------------------------- +// +// Derived Cylinder Class +// +//---------------------------- +CCylinder::CCylinder(void) +{ + mRefEnt.reType = RT_CYLINDER; + mTraceEnd = qfalse; +} + +bool CCylinder::Cull(void) +{ + if (mTraceEnd) + { //eh, don't cull variable-length cylinders + return false; + } + + return CTail::Cull(); +} + +void CCylinder::UpdateLength(void) +{ + if (mTraceEnd) + { + vec3_t temp; + trace_t tr; + + VectorMA( mOrigin1, FX_MAX_TRACE_DIST, mRefEnt.axis[0], temp ); + theFxHelper.Trace( tr, mOrigin1, NULL, NULL, temp, -1, MASK_SOLID ); + VectorSubtract( tr.endpos, mOrigin1, temp ); + mLength = VectorLength(temp); + } + else + { + CTail::UpdateLength(); + } +} + +//---------------------------- +void CCylinder::Draw(void) +{ + if ( mFlags & FX_DEPTH_HACK ) + { + // Not sure if first person needs to be set, but it can't hurt? + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + VectorCopy( mOrigin1, mRefEnt.origin ); + VectorMA( mOrigin1, mLength, mRefEnt.axis[0], mRefEnt.oldorigin ); + + theFxHelper.AddFxToScene(&mRefEnt); + drawnFx++; +} + +//---------------------------- +// Update Size2 +//---------------------------- +void CCylinder::UpdateSize2(void) +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + + if ( mFlags & FX_SIZE2_LINEAR ) + { + // calculate element biasing + perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) / (float)(mTimeEnd - mTimeStart); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE + if (( mFlags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_NONLINEAR ) + { + if ( theFxHelper.mTime > mSize2Parm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)(theFxHelper.mTime - mSize2Parm) / (float)(mTimeEnd - mSize2Parm); + } + + if ( (mFlags & FX_SIZE2_LINEAR) ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * cosf( (theFxHelper.mTime - mTimeStart) * mSize2Parm ); + } + else if (( mFlags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_CLAMP ) + { + if ( theFxHelper.mTime < mSize2Parm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mSize2Parm - theFxHelper.mTime) / (float)(mSize2Parm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( mFlags & FX_SIZE2_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_SIZE2_RAND ) + { + // Random simply modulates the existing value + perc1 = flrand(0.0f, perc1); + } + + mRefEnt.rotation = (mSize2Start * perc1) + (mSize2End * (1.0f - perc1)); +} + +//---------------------------- +bool CCylinder::Update(void) +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( !mGhoul2.IsValid()) + { // the thing we are bolted to is no longer valid, so we may as well just die. + return false; + } + + vec3_t ax[3]; + // Get our current position and direction + if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, mOrigin1, ax)) + { //could not get bolt + return false; + } + + VectorAdd(mOrigin1, mOrgOffset, mOrigin1); //add the offset to the bolt point + + VectorCopy( ax[0], mRefEnt.axis[0] ); + //FIXME: should mNormal be a modifier on the forward axis? + /* + VectorMA( mOrigin1, mNormal[0], ax[0], mOrigin2 ); + VectorMA( mOrigin2, mNormal[1], ax[1], mOrigin2 ); + VectorMA( mOrigin2, mNormal[2], ax[2], mOrigin2 ); + */ + } + + if ( !Cull() ) + { + // Only update these if the thing is visible. + UpdateSize(); + UpdateSize2(); + UpdateLength(); + UpdateRGB(); + UpdateAlpha(); + + Draw(); + } + + return true; +} + + +//---------------------------- +// +// Derived Emitter Class +// +//---------------------------- +CEmitter::CEmitter(void) +{ + // There may or may not be a model, but if there isn't one, + // we just won't bother adding the refEnt in our Draw func + mRefEnt.reType = RT_MODEL; +} + +//---------------------------- +CEmitter::~CEmitter(void) +{ +} + +//---------------------------- +// Draw +//---------------------------- +void CEmitter::Draw(void) +{ + // Emitters don't draw themselves, but they may need to add an attached model + if ( mFlags & FX_ATTACHED_MODEL ) + { + mRefEnt.nonNormalizedAxes = qtrue; + + VectorCopy( mOrigin1, mRefEnt.origin ); + + VectorScale( mRefEnt.axis[0], mRefEnt.radius, mRefEnt.axis[0] ); + VectorScale( mRefEnt.axis[1], mRefEnt.radius, mRefEnt.axis[1] ); + VectorScale( mRefEnt.axis[2], mRefEnt.radius, mRefEnt.axis[2] ); + + theFxHelper.AddFxToScene((miniRefEntity_t*)0);// I hate having to do this, but this needs to get added as a regular refEntity + theFxHelper.AddFxToScene(&mRefEnt); + } + + // If we are emitting effects, we had better be careful because just calling it every cgame frame could + // either choke up the effects system on a fast machine, or look really nasty on a low end one. + if ( mFlags & FX_EMIT_FX ) + { + vec3_t org, v; + float ftime, time2, step; + int t, dif; + +#define TRAIL_RATE 12 // we "think" at about a 60hz rate + + // Pick a target step distance and square it + step = mDensity + flrand(-mVariance, mVariance); + step *= step; + + dif = 0; + + for ( t = mOldTime; t <= theFxHelper.mTime; t += TRAIL_RATE ) + { + dif += TRAIL_RATE; + + // ?Not sure if it's better to update this before or after updating the origin + VectorMA( mOldVelocity, dif * 0.001f, mAccel, v ); + + // Calc the time differences + ftime = dif * 0.001f; + time2 = ftime * ftime * 0.5f; + + // Predict the new position + org[0] = mOldOrigin[0] + (ftime * v[0]) + (time2 * v[0]); + org[1] = mOldOrigin[1] + (ftime * v[1]) + (time2 * v[1]); + org[2] = mOldOrigin[2] + (ftime * v[2]) + (time2 * v[2]); + + // Is it time to draw an effect? + if ( DistanceSquared( org, mOldOrigin ) >= step ) + { + // Pick a new target step distance and square it + step = mDensity + flrand(-mVariance, mVariance); + step *= step; + + // We met the step criteria so, we should add in the effect + theFxScheduler.PlayEffect( mEmitterFxID, org, mRefEnt.axis ); + + VectorCopy( org, mOldOrigin ); + VectorCopy( v, mOldVelocity ); + dif = 0; + mOldTime = t; + } + } + } + drawnFx++; +} + +//---------------------------- +bool CEmitter::Update(void) +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + // Use this to track if we've stopped moving + VectorCopy( mOrigin1, mOldOrigin ); + VectorCopy( mVel, mOldVelocity ); + + if ( mFlags & FX_RELATIVE ) + { + if ( !mGhoul2.IsValid()) + { // the thing we are bolted to is no longer valid, so we may as well just die. + return false; + } + assert(0);//need this? + + } + if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) + { + // we are marked for death + return false; + } + + bool moving = false; + + // If the thing is no longer moving, kill the angle delta, but don't do it too quickly or it will + // look very artificial. Don't do it too slowly or it will look like there is no friction. + if ( VectorCompare( mOldOrigin, mOrigin1 )) + { + VectorScale( mAngleDelta, 0.7f, mAngleDelta ); + } + else + { + moving = true; + } + + if ( mFlags & FX_PAPER_PHYSICS ) + { + // do this in a more framerate independant manner + float sc = ( 20.0f / theFxHelper.mFrameTime); + + // bah, evil clamping + if ( sc >= 1.0f ) + { + sc = 1.0f; + } + + if ( moving ) + { + // scale the velocity down, basically inducing drag. Acceleration ( gravity ) should keep it pulling down, which is what we want. + VectorScale( mVel, (sc * 0.8f + 0.2f ) * 0.92f, mVel ); + + // add some chaotic motion based on the way we are oriented + VectorMA( mVel, (1.5f - sc) * 4.0f, mRefEnt.axis[0], mVel ); + VectorMA( mVel, (1.5f - sc) * 4.0f, mRefEnt.axis[1], mVel ); + } + + // make us settle so we can lay flat + mAngles[0] *= (0.97f * (sc * 0.4f + 0.6f )); + mAngles[2] *= (0.97f * (sc * 0.4f + 0.6f )); + + // decay our angle delta so we don't rotate as quickly + VectorScale( mAngleDelta, (0.96f * (sc * 0.1f + 0.9f )), mAngleDelta ); + } + + UpdateAngles(); + UpdateSize(); + + Draw(); + + return true; +} + +//---------------------------- +void CEmitter::UpdateAngles(void) +{ + VectorMA( mAngles, theFxHelper.mFrameTime * 0.01f, mAngleDelta, mAngles ); // was 0.001f, but then you really have to jack up the delta to even notice anything + AnglesToAxis( mAngles, mRefEnt.axis ); +} + + +//-------------------------- +// +// Derived Light Class +// +//-------------------------- + +//---------------------------- +void CLight::Draw(void) +{ + theFxHelper.AddLightToScene( mOrigin1, mRefEnt.radius, mRefEnt.origin[0], mRefEnt.origin[1], mRefEnt.origin[2] ); + drawnFx++; +} + +//---------------------------- +// Update +//---------------------------- +bool CLight::Update(void) +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( !mGhoul2.IsValid()) + { // the thing we are bolted to is no longer valid, so we may as well just die. + return false; + } + + vec3_t ax[3]; + // Get our current position and direction + if (!theFxHelper.GetOriginAxisFromBolt(&mGhoul2, mEntNum, mModelNum, mBoltNum, mOrigin1, ax)) + { //could not get bolt + return false; + } + + VectorMA( mOrigin1, mOrgOffset[0], ax[0], mOrigin1 ); + VectorMA( mOrigin1, mOrgOffset[1], ax[1], mOrigin1 ); + VectorMA( mOrigin1, mOrgOffset[2], ax[2], mOrigin1 ); + } + + UpdateSize(); + UpdateRGB(); + + Draw(); + + return true; +} + +//---------------------------- +// Update Size +//---------------------------- +void CLight::UpdateSize(void) +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + + if ( mFlags & FX_SIZE_LINEAR ) + { + // calculate element biasing + perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) / (float)(mTimeEnd - mTimeStart); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE + if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_NONLINEAR ) + { + if ( theFxHelper.mTime > mSizeParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)(theFxHelper.mTime - mSizeParm) / (float)(mTimeEnd - mSizeParm); + } + + if ( (mFlags & FX_SIZE_LINEAR) ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * cosf( (theFxHelper.mTime - mTimeStart) * mSizeParm ); + } + else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_CLAMP ) + { + if ( theFxHelper.mTime < mSizeParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mSizeParm - theFxHelper.mTime) / (float)(mSizeParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( mFlags & FX_SIZE_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_SIZE_RAND ) + { + // Random simply modulates the existing value + perc1 = flrand(0.0f, perc1); + } + + mRefEnt.radius = (mSizeStart * perc1) + (mSizeEnd * (1.0f - perc1)); +} + +//---------------------------- +// Update RGB +//---------------------------- +void CLight::UpdateRGB(void) +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + vec3_t res; + + if ( mFlags & FX_RGB_LINEAR ) + { + // calculate element biasing + perc1 = 1.0f - (float)( theFxHelper.mTime - mTimeStart ) / (float)( mTimeEnd - mTimeStart ); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE + if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_NONLINEAR ) + { + if ( theFxHelper.mTime > mRGBParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)( theFxHelper.mTime - mRGBParm ) / (float)( mTimeEnd - mRGBParm ); + } + + if ( mFlags & FX_RGB_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * cosf(( theFxHelper.mTime - mTimeStart ) * mRGBParm ); + } + else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_CLAMP ) + { + if ( theFxHelper.mTime < mRGBParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mRGBParm - theFxHelper.mTime) / (float)(mRGBParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( mFlags & FX_RGB_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_RGB_RAND ) + { + // Random simply modulates the existing value + perc1 = flrand(0.0f, perc1); + } + + // Now get the correct color + VectorScale( mRGBStart, perc1, res ); + VectorMA(res, ( 1.0f - perc1 ), mRGBEnd, mRefEnt.origin); +} + +//-------------------------- +// +// Derived Trail Class +// +//-------------------------- +#define NEW_MUZZLE 0 +#define NEW_TIP 1 +#define OLD_TIP 2 +#define OLD_MUZZLE 3 + +//---------------------------- +void CTrail::Draw() +{ + polyVert_t verts[3]; +// vec3_t color; + + // build the first tri out of the new muzzle...new tip...old muzzle + VectorCopy( mVerts[NEW_MUZZLE].origin, verts[0].xyz ); + VectorCopy( mVerts[NEW_TIP].origin, verts[1].xyz ); + VectorCopy( mVerts[OLD_MUZZLE].origin, verts[2].xyz ); + +// VectorScale( mVerts[NEW_MUZZLE].curRGB, mVerts[NEW_MUZZLE].curAlpha, color ); + verts[0].modulate[0] = mVerts[NEW_MUZZLE].rgb[0]; + verts[0].modulate[1] = mVerts[NEW_MUZZLE].rgb[1]; + verts[0].modulate[2] = mVerts[NEW_MUZZLE].rgb[2]; + verts[0].modulate[3] = mVerts[NEW_MUZZLE].alpha; + +// VectorScale( mVerts[NEW_TIP].curRGB, mVerts[NEW_TIP].curAlpha, color ); + verts[1].modulate[0] = mVerts[NEW_TIP].rgb[0]; + verts[1].modulate[1] = mVerts[NEW_TIP].rgb[1]; + verts[1].modulate[2] = mVerts[NEW_TIP].rgb[2]; + verts[1].modulate[3] = mVerts[NEW_TIP].alpha; + +// VectorScale( mVerts[OLD_MUZZLE].curRGB, mVerts[OLD_MUZZLE].curAlpha, color ); + verts[2].modulate[0] = mVerts[OLD_MUZZLE].rgb[0]; + verts[2].modulate[1] = mVerts[OLD_MUZZLE].rgb[1]; + verts[2].modulate[2] = mVerts[OLD_MUZZLE].rgb[2]; + verts[2].modulate[3] = mVerts[OLD_MUZZLE].alpha; + + verts[0].st[0] = mVerts[NEW_MUZZLE].curST[0]; + verts[0].st[1] = mVerts[NEW_MUZZLE].curST[1]; + verts[1].st[0] = mVerts[NEW_TIP].curST[0]; + verts[1].st[1] = mVerts[NEW_TIP].curST[1]; + verts[2].st[0] = mVerts[OLD_MUZZLE].curST[0]; + verts[2].st[1] = mVerts[OLD_MUZZLE].curST[1]; + + // Add this tri + theFxHelper.AddPolyToScene( mShader, 3, verts ); + + // build the second tri out of the old muzzle...old tip...new tip + VectorCopy( mVerts[OLD_MUZZLE].origin, verts[0].xyz ); + VectorCopy( mVerts[OLD_TIP].origin, verts[1].xyz ); + VectorCopy( mVerts[NEW_TIP].origin, verts[2].xyz ); + +// VectorScale( mVerts[OLD_MUZZLE].curRGB, mVerts[OLD_MUZZLE].curAlpha, color ); + verts[0].modulate[0] = mVerts[OLD_MUZZLE].rgb[0]; + verts[0].modulate[1] = mVerts[OLD_MUZZLE].rgb[1]; + verts[0].modulate[2] = mVerts[OLD_MUZZLE].rgb[2]; + verts[0].modulate[3] = mVerts[OLD_MUZZLE].alpha; + +// VectorScale( mVerts[OLD_TIP].curRGB, mVerts[OLD_TIP].curAlpha, color ); + verts[1].modulate[0] = mVerts[OLD_TIP].rgb[0]; + verts[1].modulate[1] = mVerts[OLD_TIP].rgb[1]; + verts[1].modulate[2] = mVerts[OLD_TIP].rgb[2]; + verts[0].modulate[3] = mVerts[OLD_TIP].alpha; + +// VectorScale( mVerts[NEW_TIP].curRGB, mVerts[NEW_TIP].curAlpha, color ); + verts[2].modulate[0] = mVerts[NEW_TIP].rgb[0]; + verts[2].modulate[1] = mVerts[NEW_TIP].rgb[1]; + verts[2].modulate[2] = mVerts[NEW_TIP].rgb[2]; + verts[0].modulate[3] = mVerts[NEW_TIP].alpha; + + verts[0].st[0] = mVerts[OLD_MUZZLE].curST[0]; + verts[0].st[1] = mVerts[OLD_MUZZLE].curST[1]; + verts[1].st[0] = mVerts[OLD_TIP].curST[0]; + verts[1].st[1] = mVerts[OLD_TIP].curST[1]; + verts[2].st[0] = mVerts[NEW_TIP].curST[0]; + verts[2].st[1] = mVerts[NEW_TIP].curST[1]; + + // Add this tri + theFxHelper.AddPolyToScene( mShader, 3, verts ); + + drawnFx++; +} + +//---------------------------- +// Update +//---------------------------- +bool CTrail::Update() +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + float perc = (float)(mTimeEnd - theFxHelper.mTime) / (float)(mTimeEnd - mTimeStart); + + for ( int t = 0; t < 4; t++ ) + { +// mVerts[t].curAlpha = mVerts[t].alpha * perc + mVerts[t].destAlpha * ( 1.0f - perc ); +// if ( mVerts[t].curAlpha < 0.0f ) +// { +// mVerts[t].curAlpha = 0.0f; +// } + +// VectorScale( mVerts[t].rgb, perc, mVerts[t].curRGB ); +// VectorMA( mVerts[t].curRGB, ( 1.0f - perc ), mVerts[t].destrgb, mVerts[t].curRGB ); + mVerts[t].curST[0] = mVerts[t].ST[0] * perc + mVerts[t].destST[0] * ( 1.0f - perc ); + if ( mVerts[t].curST[0] > 1.0f ) + { + mVerts[t].curST[0] = 1.0f; + } + mVerts[t].curST[1] = mVerts[t].ST[1] * perc + mVerts[t].destST[1] * ( 1.0f - perc ); + } + + Draw(); + + return true; +} + +//-------------------------- +// +// Derived Poly Class +// +//-------------------------- + +//---------------------------- +bool CPoly::Cull(void) +{ + vec3_t dir; + + // Get the direction to the view +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + VectorSubtract( mOrigin1, cg->refdef.vieworg, dir ); + else +#endif + VectorSubtract( mOrigin1, theFxHelper.refdef->vieworg, dir ); + + // Check if it's behind the viewer +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if ( (DotProduct( cg->refdef.viewaxis[0], dir )) < 0 ) + { + return true; + } + } + else { +#endif + if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) < 0 ) + { + return true; + } +#ifdef _XBOX + } +#endif + + float len = VectorLengthSquared( dir ); + + // Can't be too close + if ( len < fx_nearCull->value * fx_nearCull->value) + { + return true; + } + + return false; +} + +//---------------------------- +void CPoly::Draw(void) +{ + polyVert_t verts[MAX_CPOLY_VERTS]; + + for ( int i = 0; i < mCount; i++ ) + { + // Add our midpoint and vert offset to get the actual vertex + VectorAdd( mOrigin1, mOrg[i], verts[i].xyz ); + + // Assign the same color to each vert + *(int *)verts[i].modulate = *(int *)mRefEnt.shaderRGBA; + + // Copy the ST coords + Vector2Copy( mST[i], verts[i].st ); + } + + // Add this poly + theFxHelper.AddPolyToScene( mRefEnt.customShader, mCount, verts ); + drawnFx++; +} + +//---------------------------- +void CPoly::CalcRotateMatrix(void) +{ + float cosX, cosZ; + float sinX, sinZ; + float rad; + + // rotate around Z + rad = DEG2RAD( mRotDelta[YAW] * theFxHelper.mFrameTime * 0.01f ); + cosZ = cosf( rad ); + sinZ = sinf( rad ); + // rotate around X + rad = DEG2RAD( mRotDelta[PITCH] * theFxHelper.mFrameTime * 0.01f ); + cosX = cosf( rad ); + sinX = sinf( rad ); + +/*Pitch - aroundx Yaw - around z +1 0 0 c -s 0 +0 c -s s c 0 +0 s c 0 0 1 +*/ + mRot[0][0] = cosZ; + mRot[1][0] = -sinZ; + mRot[2][0] = 0; + mRot[0][1] = cosX * sinZ; + mRot[1][1] = cosX * cosZ; + mRot[2][1] = -sinX; + mRot[0][2] = sinX * sinZ; + mRot[1][2] = sinX * cosZ; + mRot[2][2] = cosX; +/* +// ROLL is not supported unless anyone complains, if it needs to be added, use this format +Roll + + c 0 s + 0 1 0 +-s 0 c +*/ + mLastFrameTime = theFxHelper.mFrameTime; +} + +//-------------------------------- +void CPoly::Rotate(void) +{ + vec3_t temp[MAX_CPOLY_VERTS]; + float dif = fabs( (float)(mLastFrameTime - theFxHelper.mFrameTime) ); + + if ( dif > 0.1f * mLastFrameTime ) + { + CalcRotateMatrix(); + } + + // Multiply our rotation matrix by each of the offset verts to get their new position + for ( int i = 0; i < mCount; i++ ) + { + VectorRotate( mOrg[i], mRot, temp[i] ); + VectorCopy( temp[i], mOrg[i] ); + } +} + +//---------------------------- +// Update +//---------------------------- +bool CPoly::Update(void) +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + // If our timestamp hasn't exired yet, we won't even consider doing any kind of motion + if ( theFxHelper.mTime > mTimeStamp ) + { + vec3_t mOldOrigin; + + VectorCopy( mOrigin1, mOldOrigin ); + + if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) + { + // we are marked for death + return false; + } + + // Only rotate whilst moving + if ( !VectorCompare( mOldOrigin, mOrigin1 )) + { + Rotate(); + } + } + + if ( !Cull()) + { + // Only update these if the thing is visible. + UpdateRGB(); + UpdateAlpha(); + + Draw(); + } + + return true; +} + +//---------------------------- +void CPoly::PolyInit(void) +{ + if ( mCount < 3 ) + { + return; + } + + int i; + vec3_t org = {0.0f, 0.0f ,0.0f}; + + // Find our midpoint + for ( i = 0; i < mCount; i++ ) + { + VectorAdd( org, mOrg[i], org ); + } + + VectorScale( org, (float)(1.0f / mCount), org ); + + // now store our midpoint for physics purposes + VectorCopy( org, mOrigin1 ); + + // Now we process the passed in points and make it so that they aren't actually the point... + // rather, they are the offset from mOrigin1. + for ( i = 0; i < mCount; i++ ) + { + VectorSubtract( mOrg[i], mOrigin1, mOrg[i] ); + } + + CalcRotateMatrix(); +} + +/* +------------------------- +CBezier + +Bezier curve line +------------------------- +*/ +bool CBezier::Cull( void ) +{ + vec3_t dir; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + VectorSubtract( mOrigin1, cg->refdef.vieworg, dir ); + else +#endif + VectorSubtract( mOrigin1, theFxHelper.refdef->vieworg, dir ); + + //Check if it's in front of the viewer +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if ( (DotProduct( cg->refdef.viewaxis[0], dir )) >= 0 ) + { + return false; //don't cull + } + } + else { +#endif + if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) >= 0 ) + { + return false; //don't cull + } +#ifdef _XBOX + } +#endif + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + VectorSubtract( mOrigin2, cg->refdef.vieworg, dir ); + else +#endif + VectorSubtract( mOrigin2, theFxHelper.refdef->vieworg, dir ); + + //Check if it's in front of the viewer +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if ( (DotProduct( cg->refdef.viewaxis[0], dir )) >= 0 ) + { + return false; + } + } + else { +#endif + if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) >= 0 ) + { + return false; + } +#ifdef _XBOX + } +#endif + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + VectorSubtract( mControl1, cg->refdef.vieworg, dir ); + else +#endif + VectorSubtract( mControl1, theFxHelper.refdef->vieworg, dir ); + + //Check if it's in front of the viewer +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if ( (DotProduct( cg->refdef.viewaxis[0], dir )) >= 0 ) + { + return false; + } + } + else { +#endif + if ( (DotProduct( theFxHelper.refdef->viewaxis[0], dir )) >= 0 ) + { + return false; + } +#ifdef _XBOX + } +#endif + + return true; //all points behind viewer +} + +//---------------------------- +bool CBezier::Update( void ) +{ + float ftime, time2; + + ftime = theFxHelper.mFrameTime * 0.001f; + time2 = ftime * ftime * 0.5f; + + mControl1[0] = mControl1[0] + (ftime * mControl1Vel[0]) + (time2 * mControl1Vel[0]); + mControl2[0] = mControl2[0] + (ftime * mControl2Vel[0]) + (time2 * mControl2Vel[0]); + mControl1[1] = mControl1[1] + (ftime * mControl1Vel[1]) + (time2 * mControl1Vel[1]); + mControl2[1] = mControl2[1] + (ftime * mControl2Vel[1]) + (time2 * mControl2Vel[1]); + mControl1[2] = mControl1[2] + (ftime * mControl1Vel[2]) + (time2 * mControl1Vel[2]); + mControl2[2] = mControl2[2] + (ftime * mControl2Vel[2]) + (time2 * mControl2Vel[2]); + + if ( Cull() == false ) + { + // Only update these if the thing is visible. + UpdateSize(); + UpdateRGB(); + UpdateAlpha(); + + Draw(); + } + return true; +} + +//---------------------------- +inline void CBezier::DrawSegment( vec3_t start, vec3_t end, float texcoord1, float texcoord2, float segPercent, float lastSegPercent ) +{ + vec3_t lineDir, cross, viewDir; + static vec3_t lastEnd[2]; + polyVert_t verts[4]; + float scaleBottom = 0.0f, scaleTop = 0.0f; + + VectorSubtract( end, start, lineDir ); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue ) + VectorSubtract( end, cg->refdef.vieworg, viewDir ); + else +#endif + VectorSubtract( end, theFxHelper.refdef->vieworg, viewDir ); + CrossProduct( lineDir, viewDir, cross ); + VectorNormalize( cross ); + + // scaleBottom is the width of the bottom edge of the quad, scaleTop is the width of the top edge + scaleBottom = (mSizeStart + ((mSizeEnd - mSizeStart) * lastSegPercent)) * 0.5f; + scaleTop = (mSizeStart + ((mSizeEnd - mSizeStart) * segPercent)) * 0.5f; + + //Construct the oriented quad + if ( mInit ) + { + VectorCopy( lastEnd[0], verts[0].xyz ); + VectorCopy( lastEnd[1], verts[1].xyz ); + } + else + { + VectorMA( start, -scaleBottom, cross, verts[0].xyz ); + VectorMA( start, scaleBottom, cross, verts[1].xyz ); + } + + verts[0].st[0] = 0.0f; + verts[0].st[1] = texcoord1; + + verts[0].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord1 ); + verts[0].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord1 ); + verts[0].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord1 ); + verts[0].modulate[3] = mRefEnt.shaderRGBA[3]; + + verts[1].st[0] = 1.0f; + verts[1].st[1] = texcoord1; + + verts[1].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord1 ); + verts[1].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord1 ); + verts[1].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord1 ); + verts[1].modulate[3] = mRefEnt.shaderRGBA[3]; + + if ( texcoord1 == 0.0f ) + { + *(int *)verts[0].modulate = 0; + *(int *)verts[1].modulate = 0; + } + + VectorMA( end, scaleTop, cross, verts[2].xyz ); + verts[2].st[0] = 1.0f; + verts[2].st[1] = texcoord2; + + verts[2].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord2 ); + verts[2].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord2 ); + verts[2].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord2 ); + verts[2].modulate[3] = mRefEnt.shaderRGBA[3]; + + VectorMA( end, -scaleTop, cross, verts[3].xyz ); + verts[3].st[0] = 0.0f; + verts[3].st[1] = texcoord2; + + verts[3].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord2 ); + verts[3].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord2 ); + verts[3].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord2 ); + verts[3].modulate[3] = mRefEnt.shaderRGBA[3]; + + theFxHelper.AddPolyToScene( mRefEnt.customShader, 4, verts ); + + VectorCopy( verts[2].xyz, lastEnd[1] ); + VectorCopy( verts[3].xyz, lastEnd[0] ); + + mInit = true; +} + +const float BEZIER_RESOLUTION = 16.0f; + +//---------------------------- +void CBezier::Draw( void ) +{ + vec3_t pos, old_pos; + float mu, mum1; + float incr = 1.0f / BEZIER_RESOLUTION, tex = 1.0f, tc1, tc2; + int i = 0; + + VectorCopy( mOrigin1, old_pos ); + + mInit = false; //Signify a new batch for vert gluing + + float mum13, mu3, group1, group2; + + tc1 = 0.0f; + + for ( mu = incr; mu <= 1.0f; mu += incr) + { + //Four point curve + mum1 = 1 - mu; + mum13 = mum1 * mum1 * mum1; + mu3 = mu * mu * mu; + group1 = 3 * mu * mum1 * mum1; + group2 = 3 * mu * mu *mum1; + + for ( i = 0; i < 3; i++ ) + { + pos[i] = mum13 * mOrigin1[i] + group1 * mControl1[i] + group2 * mControl2[i] + mu3 * mOrigin2[i]; + } + + tc2 = mu * tex; + + //Draw it + DrawSegment( old_pos, pos, tc1, tc2, mu, mu - incr ); + + VectorCopy( pos, old_pos ); + tc1 = tc2; + } + drawnFx++; +} + +/* +------------------------- +CFlash + +Full screen flash +------------------------- +*/ + +//---------------------------- +bool CFlash::Update( void ) +{ + if ( UpdateOrigin() == false ) + { + // we are marked for death + return false; + } + + UpdateSize(); + mRefEnt.radius *= mRadiusModifier; + UpdateRGB(); + UpdateAlpha(); + + Draw(); + return true; +} + +bool FX_WorldToScreen(vec3_t worldCoord, float *x, float *y) +{ + int xcenter, ycenter; + vec3_t local, transformed; + vec3_t vfwd, vright, vup; + + //NOTE: did it this way because most draw functions expect virtual 640x480 coords + // and adjust them for current resolution +#ifdef _XBOX + if(glw_state->isWidescreen) + xcenter = 720 / 2; + else +#endif + xcenter = 640 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn + ycenter = 480 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + VectorSubtract (worldCoord, cg->refdef.vieworg, local); + AngleVectors (cg->refdef.viewangles, vfwd, vright, vup); + } + else { +#endif + VectorSubtract (worldCoord, theFxHelper.refdef->vieworg, local); + AngleVectors (theFxHelper.refdef->viewangles, vfwd, vright, vup); +#ifdef _XBOX + } +#endif + + transformed[0] = DotProduct(local,vright); + transformed[1] = DotProduct(local,vup); + transformed[2] = DotProduct(local,vfwd); + + // Make sure Z is not negative. + if(transformed[2] < 0.01) + { + return false; + } + // Simple convert to screen coords. + float xzi, yzi; +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + xzi = xcenter / transformed[2] * (90.0/cg->refdef.fov_x); + yzi = ycenter / transformed[2] * (90.0/cg->refdef.fov_y); + } + else { +#endif + xzi = xcenter / transformed[2] * (90.0/theFxHelper.refdef->fov_x); + yzi = ycenter / transformed[2] * (90.0/theFxHelper.refdef->fov_y); +#ifdef _XBOX + } +#endif + + *x = (xcenter + xzi * transformed[0]); + *y = (ycenter - yzi * transformed[1]); + + return true; +} + +//---------------------------- +void CFlash::Init( void ) +{ + // 10/19/01 kef -- maybe we want to do something different here for localized flashes, but right + //now I want to be sure that whatever RGB changes occur to a non-localized flash will also occur + //to a localized flash (so I'll have the same initial RGBA values for both...I need them sync'd for an effect) + + vec3_t dif; + float mod = 1.0f, dis = 0.0f, maxRange = 900; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + VectorSubtract( mOrigin1, cg->refdef.vieworg, dif ); + else +#endif + VectorSubtract( mOrigin1, theFxHelper.refdef->vieworg, dif ); + dis = VectorNormalize( dif ); + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + mod = DotProduct( dif, cg->refdef.viewaxis[0] ); + else +#endif + mod = DotProduct( dif, theFxHelper.refdef->viewaxis[0] ); + + if ( dis > maxRange || ( mod < 0.5f && dis > 100 )) + { + mod = 0.0f; + } + else if ( mod < 0.5f && dis <= 100 ) + { + mod += 1.1f; + } + + mod *= (1.0f - ((dis * dis) / (maxRange * maxRange))); + + VectorScale( mRGBStart, mod, mRGBStart ); + VectorScale( mRGBEnd, mod, mRGBEnd ); + + if ( mFlags & FX_LOCALIZED_FLASH ) + { + FX_WorldToScreen(mOrigin1, &mScreenX, &mScreenY); + + // modify size of localized flash based on distance to effect (but not orientation) + if (dis > 100 && dis < maxRange) + { + mRadiusModifier = (1.0f - ((dis * dis) / (maxRange * maxRange))); + } + } +} + +//---------------------------- +void CFlash::Draw( void ) +{ + mRefEnt.reType = RT_SPRITE; + + if ( mFlags & FX_LOCALIZED_FLASH ) + { + vec4_t color; + + color[0] = mRefEnt.shaderRGBA[0] / 255.0; + color[1] = mRefEnt.shaderRGBA[1] / 255.0; + color[2] = mRefEnt.shaderRGBA[2] / 255.0; + color[3] = mRefEnt.shaderRGBA[3] / 255.0; + + // add this 2D effect to the proper list. it will get drawn after the cgi.RenderScene call + theFxScheduler.Add2DEffect(mScreenX, mScreenY, mRefEnt.radius, mRefEnt.radius, color, mRefEnt.customShader); + } + else + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + VectorCopy( cg->refdef.vieworg, mRefEnt.origin ); + VectorMA( mRefEnt.origin, 12, cg->refdef.viewaxis[0], mRefEnt.origin ); + } + else { +#endif + VectorCopy( theFxHelper.refdef->vieworg, mRefEnt.origin ); + VectorMA( mRefEnt.origin, 12, theFxHelper.refdef->viewaxis[0], mRefEnt.origin ); +#ifdef _XBOX + } +#endif + mRefEnt.radius = 11.0f; + + theFxHelper.AddFxToScene( &mRefEnt ); + } + drawnFx++; +} + +void FX_AddPrimitive( CEffect **pEffect, int killTime ); +void FX_FeedTrail(effectTrailArgStruct_t *a) +{ + CTrail *fx = new CTrail; + int i = 0; + + while (i < 4) + { + VectorCopy(a->mVerts[i].origin, fx->mVerts[i].origin); + VectorCopy(a->mVerts[i].rgb, fx->mVerts[i].rgb); + VectorCopy(a->mVerts[i].destrgb, fx->mVerts[i].destrgb); + VectorCopy(a->mVerts[i].curRGB, fx->mVerts[i].curRGB); + fx->mVerts[i].alpha = a->mVerts[i].alpha; + fx->mVerts[i].destAlpha = a->mVerts[i].destAlpha; + fx->mVerts[i].curAlpha = a->mVerts[i].curAlpha; + fx->mVerts[i].ST[0] = a->mVerts[i].ST[0]; + fx->mVerts[i].ST[1] = a->mVerts[i].ST[1]; + fx->mVerts[i].destST[0] = a->mVerts[i].destST[0]; + fx->mVerts[i].destST[1] = a->mVerts[i].destST[1]; + fx->mVerts[i].curST[0] = a->mVerts[i].curST[0]; + fx->mVerts[i].curST[1] = a->mVerts[i].curST[1]; + i++; + } + + fx->SetFlags(a->mSetFlags); + + fx->mShader = a->mShader; + + FX_AddPrimitive((CEffect **)&fx, a->mKillTime); +} +// end diff --git a/codemp/client/fxprimitives.h b/codemp/client/fxprimitives.h new file mode 100644 index 0000000..d2dac53 --- /dev/null +++ b/codemp/client/fxprimitives.h @@ -0,0 +1,611 @@ + +#if !defined(FX_SYSTEM_H_INC) + #include "FxSystem.h" +#endif + +#ifndef FX_PRIMITIVES_H_INC +#define FX_PRIMITIVES_H_INC + + +#define MAX_EFFECTS 1800 + +// Generic group flags, used by parser, then get converted to the appropriate specific flags +#define FX_PARM_MASK 0xC // use this to mask off any transition types that use a parm +#define FX_GENERIC_MASK 0xF +#define FX_LINEAR 0x1 +#define FX_RAND 0x2 +#define FX_NONLINEAR 0x4 +#define FX_WAVE 0x8 +#define FX_CLAMP 0xC + +// Group flags +#define FX_ALPHA_SHIFT 0 +#define FX_ALPHA_PARM_MASK 0x0000000C +#define FX_ALPHA_LINEAR 0x00000001 +#define FX_ALPHA_RAND 0x00000002 +#define FX_ALPHA_NONLINEAR 0x00000004 +#define FX_ALPHA_WAVE 0x00000008 +#define FX_ALPHA_CLAMP 0x0000000C + +#define FX_RGB_SHIFT 4 +#define FX_RGB_PARM_MASK 0x000000C0 +#define FX_RGB_LINEAR 0x00000010 +#define FX_RGB_RAND 0x00000020 +#define FX_RGB_NONLINEAR 0x00000040 +#define FX_RGB_WAVE 0x00000080 +#define FX_RGB_CLAMP 0x000000C0 + +#define FX_SIZE_SHIFT 8 +#define FX_SIZE_PARM_MASK 0x00000C00 +#define FX_SIZE_LINEAR 0x00000100 +#define FX_SIZE_RAND 0x00000200 +#define FX_SIZE_NONLINEAR 0x00000400 +#define FX_SIZE_WAVE 0x00000800 +#define FX_SIZE_CLAMP 0x00000C00 + +#define FX_LENGTH_SHIFT 12 +#define FX_LENGTH_PARM_MASK 0x0000C000 +#define FX_LENGTH_LINEAR 0x00001000 +#define FX_LENGTH_RAND 0x00002000 +#define FX_LENGTH_NONLINEAR 0x00004000 +#define FX_LENGTH_WAVE 0x00008000 +#define FX_LENGTH_CLAMP 0x0000C000 + +#define FX_SIZE2_SHIFT 16 +#define FX_SIZE2_PARM_MASK 0x000C0000 +#define FX_SIZE2_LINEAR 0x00010000 +#define FX_SIZE2_RAND 0x00020000 +#define FX_SIZE2_NONLINEAR 0x00040000 +#define FX_SIZE2_WAVE 0x00080000 +#define FX_SIZE2_CLAMP 0x000C0000 + +// Shared flag--these flags, at first glance would appear to be shared, but are safe. I'd rather not do this, but as you can see, features flags are currently all accounted for +#define FX_PAPER_PHYSICS 0x00010000 // emitters ONLY. shared with FX_SIZE_2_LINEAR +#define FX_LOCALIZED_FLASH 0x00010000 // full screen flashes ONLY. shared with FX_SIZE_2_LINEAR +#define FX_PLAYER_VIEW 0x00010000 // player view effects ONLY. shared with FX_SIZE_2_LINEAR + +// Feature flags +#define FX_DEPTH_HACK 0x00100000 +#define FX_RELATIVE 0x00200000 +#define FX_SET_SHADER_TIME 0x00400000 +#define FX_EXPENSIVE_PHYSICS 0x00800000 + +//rww - g2-related flags (these can slow things down significantly, use sparingly) +//These should be used only with particles/decals as they steal flags used by cylinders. +#define FX_GHOUL2_TRACE 0x00020000 //use in conjunction with particles - actually do full ghoul2 traces for physics collision against entities with a ghoul2 instance + //shared FX_SIZE2_RAND (used only with cylinders) +#define FX_GHOUL2_DECALS 0x00040000 //use in conjunction with decals - can project decal as a ghoul2 gore skin object onto ghoul2 models + //shared FX_SIZE2_NONLINEAR (used only with cylinders) + +#define FX_ATTACHED_MODEL 0x01000000 + +#define FX_APPLY_PHYSICS 0x02000000 +#define FX_USE_BBOX 0x04000000 // can make physics more accurate at the expense of speed + +#define FX_USE_ALPHA 0x08000000 // the FX system actually uses RGB to do fades, but this will override that + // and cause it to fill in the alpha. + +#define FX_EMIT_FX 0x10000000 // emitters technically don't have to emit stuff, but when they do + // this flag needs to be set +#define FX_DEATH_RUNS_FX 0x20000000 // Normal death triggers effect, but not kill_on_impact +#define FX_KILL_ON_IMPACT 0x40000000 // works just like it says, but only when physics are on. +#define FX_IMPACT_RUNS_FX 0x80000000 // an effect can call another effect when it hits something. + +// Lightning flags, duplicates of existing flags, but lightning doesn't use those flags in that context...and nothing will ever use these in this context..so we are safe. +#define FX_TAPER 0x01000000 // tapers as it moves towards its endpoint +#define FX_BRANCH 0x02000000 // enables lightning branching +#define FX_GROW 0x04000000 // lightning grows from start point to end point over the course of its life + + +// stuff that can occur when an effect is flagged with "materialImpact" and it hits something +enum EMatImpactEffect +{ + MATIMPACTFX_NONE = 0, + MATIMPACTFX_SHELLSOUND +}; + +//------------------------------ +class CEffect +{ +protected: + + CEffect *mNext; + vec3_t mOrigin1; + + int mTimeStart; + int mTimeEnd; + + unsigned int mFlags; + + EMatImpactEffect mMatImpactFX; + int mMatImpactParm; + + // Size of our object, useful for things that have physics + vec3_t mMin; + vec3_t mMax; + + int mImpactFxID; // if we have an impact event, we may have to call an effect + int mDeathFxID; // if we have a death event, we may have to call an effect + + miniRefEntity_t mRefEnt; + + int mSoundRadius; + int mSoundVolume; + +public: + + CEffect(); + virtual ~CEffect() {} + + virtual void Die() {} + virtual bool Update() { return true; } + virtual void Draw(void) {} + + inline miniRefEntity_t &GetRefEnt(void) { return mRefEnt; } + + inline void SetNext(CEffect *Next) { mNext = Next; } + inline CEffect *GetNext(void) { return mNext; } + inline void GetOrigin(vec3_t dest) {VectorCopy( mOrigin1, dest); } + + inline void SetSTScale(float s,float t) { mRefEnt.shaderTexCoord[0]=s;mRefEnt.shaderTexCoord[1]=t;} + + inline void SetSound ( int vol, int rad) { mSoundRadius = rad; mSoundVolume = vol; } + inline void SetMin( vec3_t min ) { if(min){VectorCopy(min,mMin);}else{VectorClear(mMin);} } + inline void SetMax( vec3_t max ) { if(max){VectorCopy(max,mMax);}else{VectorClear(mMax);} } + inline void SetFlags( int flags ) { mFlags = flags; } + inline void AddFlags( int flags ) { mFlags |= flags; } + inline void ClearFlags( int flags ) { mFlags &= ~flags; } + inline void SetOrigin1( vec3_t org ) { if(org){VectorCopy(org,mOrigin1);}else{VectorClear(mOrigin1);} } + inline void SetTimeStart( int time ) { mTimeStart = time; if (mFlags&FX_SET_SHADER_TIME) { mRefEnt.shaderTime = time * 0.001f; }} + inline void SetTimeEnd( int time ) { mTimeEnd = time; } + inline void SetImpactFxID( int id ) { mImpactFxID = id; } + inline void SetDeathFxID( int id ) { mDeathFxID = id; } + inline EMatImpactEffect GetMatImpactFX() { return mMatImpactFX; } + inline int GetMatImpactParm() { return mMatImpactParm; } + inline void SetMatImpactFX(EMatImpactEffect matFX) { mMatImpactFX = matFX; } + inline void SetMatImpactParm(int matParm) { mMatImpactParm = matParm; } +}; + +//--------------------------------------------------- +// This class is kind of an exception to the "rule". +// For now it exists only for allowing an easy way +// to get the saber slash trails rendered. +//--------------------------------------------------- +class CTrail : public CEffect +{ +// This is such a specific case thing, just grant public access to the goods. +protected: + + void Draw(); + +public: + + typedef struct + { + vec3_t origin; + + // very specifc case, we can modulate the color and the alpha + vec3_t rgb; + vec3_t destrgb; + vec3_t curRGB; + + float alpha; + float destAlpha; + float curAlpha; + + // this is a very specific case thing...allow interpolating the st coords so we can map the texture + // properly as this segement progresses through it's life + float ST[2]; + float destST[2]; + float curST[2]; + + } TVert; + + TVert mVerts[4]; + qhandle_t mShader; + + + CTrail() {}; + virtual ~CTrail() {}; + + virtual bool Update(); +}; + +//------------------------------ +class CLight : public CEffect +{ +protected: + + float mSizeStart; + float mSizeEnd; + float mSizeParm; + + vec3_t mOrgOffset; + vec3_t mRGBStart; + vec3_t mRGBEnd; + float mRGBParm; + + CGhoul2Info_v mGhoul2; + short mEntNum; + char mModelNum; + char mBoltNum; + + void UpdateSize(); + void UpdateRGB(); + + virtual void Draw(void); +public: + + inline CLight(void) + { + mEntNum = -1; mModelNum = -1; mBoltNum = -1; + } + + virtual ~CLight(void) + { + mGhoul2.kill(); //remove my model ref without actually deleting + } + + inline void SetBoltinfo( int iGhoul2, int entNum, int modelNum = -1, int boltNum = -1 ) + { + mGhoul2 = iGhoul2; mEntNum = entNum; mModelNum = modelNum; mBoltNum = boltNum; + } + + virtual bool Update(); + + inline void SetSizeStart( float sz ) { mSizeStart = sz; } + inline void SetSizeEnd( float sz ) { mSizeEnd = sz; } + inline void SetSizeParm( float parm ) { mSizeParm = parm; } + + inline void SetOrgOffset( const vec3_t o ) { if(o){VectorCopy(o,mOrgOffset);}else{VectorClear(mOrgOffset);}} + inline void SetRGBStart( vec3_t rgb ) { if(rgb){VectorCopy(rgb,mRGBStart);}else{VectorClear(mRGBStart);} } + inline void SetRGBEnd( vec3_t rgb ) { if(rgb){VectorCopy(rgb,mRGBEnd);}else{VectorClear(mRGBEnd);} } + inline void SetRGBParm( float parm ) { mRGBParm = parm; } +}; + +//------------------------------ +class CParticle : public CEffect +{ +protected: + + vec3_t mOrgOffset; + + vec3_t mVel; + vec3_t mAccel; + + float mSizeStart; + float mSizeEnd; + float mSizeParm; + + vec3_t mRGBStart; + vec3_t mRGBEnd; + float mRGBParm; + + float mAlphaStart; + float mAlphaEnd; + float mAlphaParm; + + float mRotationDelta; + float mElasticity; + + CGhoul2Info_v mGhoul2; + short mEntNum; + char mModelNum; + char mBoltNum; + + bool UpdateOrigin(); + void UpdateSize(); + void UpdateRGB(); + void UpdateAlpha(); + void UpdateRotation(); + + +public: + + inline void SetBoltinfo( int iGhoul2, int entNum, int modelNum = -1, int boltNum = -1 ) + { + mGhoul2 = iGhoul2; mEntNum = entNum; mModelNum = modelNum; mBoltNum = boltNum; + } + + inline CParticle::CParticle(void) + { + mRefEnt.reType = RT_SPRITE; mEntNum = -1; mModelNum = -1; mBoltNum = -1; + } + + virtual CParticle::~CParticle(void) + { + mGhoul2.kill(); //remove my model ref without actually deleting + } + + + virtual void Init(); + virtual void Die(); + virtual bool Update(); + virtual bool Cull(void); + virtual void Draw(void); + + inline void SetShader( qhandle_t sh ) { mRefEnt.customShader = sh; } + + inline void SetOrgOffset( const vec3_t o ) { if(o){VectorCopy(o,mOrgOffset);}else{VectorClear(mOrgOffset);}} + inline void SetVel( vec3_t vel ) { if(vel){VectorCopy(vel,mVel);}else{VectorClear(mVel);} } + inline void SetAccel( vec3_t ac ) { if(ac){VectorCopy(ac,mAccel);}else{VectorClear(mAccel);} } + + inline void SetSizeStart( float sz ) { mSizeStart = sz; mRefEnt.radius = sz; } + inline void SetSizeEnd( float sz ) { mSizeEnd = sz; } + inline void SetSizeParm( float parm ) { mSizeParm = parm; } + + inline void SetRGBStart( vec3_t rgb ) { if(rgb){VectorCopy(rgb,mRGBStart);}else{VectorClear(mRGBStart);} } + inline void SetRGBEnd( vec3_t rgb ) { if(rgb){VectorCopy(rgb,mRGBEnd);}else{VectorClear(mRGBEnd);} } + inline void SetRGBParm( float parm ) { mRGBParm = parm; } + + inline void SetAlphaStart( float al ) { mAlphaStart = al; } + inline void SetAlphaEnd( float al ) { mAlphaEnd = al; } + inline void SetAlphaParm( float parm ) { mAlphaParm = parm; } + + inline void SetRotation( float rot ) { mRefEnt.rotation = rot; } + inline void SetRotationDelta( float rot ) { mRotationDelta = rot; } + inline void SetElasticity( float el ) { mElasticity = el; } +}; + +//------------------------------ +class CFlash : public CParticle +{ +public: + + CFlash(): + mScreenX(0), + mScreenY(0), + mRadiusModifier(1) + {} + + virtual ~CFlash() {} + + virtual bool Update(); + virtual void Draw(void); + virtual bool Cull(void) { return false; } + + void Init( void ); + +protected: + // kef -- mScreenX and mScreenY are used for flashes that are FX_LOCALIZED_FLASH + float mScreenX; + float mScreenY; + float mRadiusModifier; +}; + +//------------------------------ +class CLine : public CParticle +{ +protected: + + vec3_t mOrigin2; + + virtual void Draw(void); +public: + + CLine(); + virtual ~CLine() {} + + virtual void Die() {} + + virtual bool Update(); + + inline void SetOrigin2( vec3_t org2 ) { VectorCopy( org2, mOrigin2 ); } +}; + +//------------------------------ +class CBezier : public CLine +{ +protected: + + vec3_t mControl1; + vec3_t mControl1Vel; + + vec3_t mControl2; + vec3_t mControl2Vel; + + bool mInit; + +public: + + CBezier(){ mInit = false; } + virtual ~CBezier() {} + + virtual void Die() {} + virtual bool Update(); + virtual bool Cull(void); + virtual void Draw(void); + + void DrawSegment( vec3_t start, vec3_t end, float texcoord1, float texcoord2, float segPercent, float lastSegPercent ); + + inline void SetControlPoints( vec3_t ctrl1, vec3_t ctrl2 ) { VectorCopy( ctrl1, mControl1 ); VectorCopy( ctrl2, mControl2 ); } + inline void SetControlVel( vec3_t ctrl1v, vec3_t ctrl2v ) { VectorCopy( ctrl1v, mControl1Vel ); VectorCopy( ctrl2v, mControl2Vel ); } +}; + +//------------------------------ +class CElectricity : public CLine +{ +protected: + + float mChaos; + + virtual void Draw(void); +public: + + CElectricity(); + virtual ~CElectricity() {} + + virtual void Die() {} + + virtual bool Update(); + + void Initialize(); + + inline void SetChaos( float chaos ) { mChaos = chaos; } +}; + + +// Oriented quad +//------------------------------ +class COrientedParticle : public CParticle +{ +protected: + + vec3_t mNormal; + +public: + + COrientedParticle(); + virtual ~COrientedParticle() {} + + virtual bool Update(); + virtual bool Cull(void); + virtual void Draw(void); + + inline void SetNormal( vec3_t norm ) { VectorCopy( norm, mNormal ); } +}; + +//------------------------------ +class CTail : public CParticle +{ +protected: + + vec3_t mOldOrigin; + + float mLengthStart; + float mLengthEnd; + float mLengthParm; + + float mLength; + + void UpdateLength(); + void CalcNewEndpoint(); + + virtual void Draw(void); +public: + + CTail(); + virtual ~CTail() {} + + virtual bool Update(); + + inline void SetLengthStart( float len ) { mLengthStart = len; } + inline void SetLengthEnd( float len ) { mLengthEnd = len; } + inline void SetLengthParm( float len ) { mLengthParm = len; } +}; + + +//------------------------------ +class CCylinder : public CTail +{ +protected: + + float mSize2Start; + float mSize2End; + float mSize2Parm; + qboolean mTraceEnd; + + void UpdateSize2(); + + virtual void Draw(void); +public: + + CCylinder(); + virtual ~CCylinder() {} + + virtual bool Cull(void); + virtual void UpdateLength(void); + virtual bool Update(); + + inline void SetSize2Start( float sz ) { mSize2Start = sz; } + inline void SetSize2End( float sz ) { mSize2End = sz; } + inline void SetSize2Parm( float parm ) { mSize2Parm = parm; } + inline void SetTraceEnd(qboolean traceEnd) { mTraceEnd = traceEnd; } + + inline void SetNormal( vec3_t norm ) { VectorCopy( norm, mRefEnt.axis[0] ); } +}; + + +//------------------------------ +// Emitters are derived from particles because, although they don't draw, any effect called +// from them can borrow an initial or ending value from the emitters current alpha, rgb, etc.. +class CEmitter : public CParticle +{ +protected: + + vec3_t mOldOrigin; // we use these to do some nice + vec3_t mLastOrigin; // tricks... + vec3_t mOldVelocity; // + int mOldTime; + + vec3_t mAngles; // for a rotating thing, using a delta + vec3_t mAngleDelta; // as opposed to an end angle is probably much easier + + int mEmitterFxID; // if we have emitter fx, this is our id + + float mDensity; // controls how often emitter chucks an effect + float mVariance; // density sloppiness + + void UpdateAngles(); + + virtual void Draw(void); +public: + + CEmitter(); + virtual ~CEmitter(); + + virtual bool Cull(void) {return false;} + virtual bool Update(); + + inline void SetModel( qhandle_t model ) { mRefEnt.hModel = model; } + inline void SetAngles( vec3_t ang ) { if(ang){VectorCopy(ang,mAngles);}else{VectorClear(mAngles);} } + inline void SetAngleDelta( vec3_t ang ) { if(ang){VectorCopy(ang,mAngleDelta);}else{VectorClear(mAngleDelta);} } + inline void SetEmitterFxID( int id ) { mEmitterFxID = id; } + inline void SetDensity( float density ) { mDensity = density; } + inline void SetVariance( float var ) { mVariance = var; } + inline void SetOldTime( int time ) { mOldTime = time; } + inline void SetLastOrg( vec3_t org ) { if(org){VectorCopy(org,mLastOrigin);}else{VectorClear(mLastOrigin);} } + inline void SetLastVel( vec3_t vel ) { if(vel){VectorCopy(vel,mOldVelocity);}else{VectorClear(mOldVelocity);} } +}; + +// We're getting pretty low level here, not the kind of thing to abuse considering how much overhead this +// adds to a SINGLE triangle or quad.... +//------------------------------ +#define MAX_CPOLY_VERTS 5 + +class CPoly : public CParticle +{ +protected: + + int mCount; + vec3_t mRotDelta; + int mTimeStamp; + +public: + + vec3_t mOrg[MAX_CPOLY_VERTS]; + vec2_t mST[MAX_CPOLY_VERTS]; + + float mRot[3][3]; + int mLastFrameTime; + + + CPoly() {} + virtual ~CPoly() {} + + virtual bool Update(); + virtual bool Cull(void); + virtual void Draw(void); + + void PolyInit(); + void CalcRotateMatrix(); + void Rotate(); + + inline void SetNumVerts( int c ) { mCount = c; } + inline void SetRot( vec3_t r ) { if(r){VectorCopy(r,mRotDelta);}else{VectorClear(mRotDelta);}} + inline void SetMotionTimeStamp( int t ) { mTimeStamp = theFxHelper.GetTime() + t; } + inline int GetMotionTimeStamp() { return mTimeStamp; } +}; + + +#endif //FX_PRIMITIVES_H_INC \ No newline at end of file diff --git a/codemp/client/fxscheduler.cpp b/codemp/client/fxscheduler.cpp new file mode 100644 index 0000000..979a939 --- /dev/null +++ b/codemp/client/fxscheduler.cpp @@ -0,0 +1,1774 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "client.h" + +#if !defined(FX_SCHEDULER_H_INC) + #include "FxScheduler.h" +#endif + +#if !defined(G2_H_INC) + #include "../ghoul2/G2.h" + #include "../ghoul2/G2_local.h" +#endif + +#if !defined(__Q_SHARED_H) + #include "../game/q_shared.h" +#endif + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +CFxScheduler theFxScheduler; + +//----------------------------------------------------------- +void CMediaHandles::operator=(const CMediaHandles &that ) +{ + mMediaList.clear(); + + for ( int i = 0; i < that.mMediaList.size(); i++ ) + { + mMediaList.push_back( that.mMediaList[i] ); + } +} + +//------------------------------------------------------ +CFxScheduler::CFxScheduler() +{ + mNextFree2DEffect = 0; + memset( &mEffectTemplates, 0, sizeof( mEffectTemplates )); + memset( &mLoopedEffectArray, 0, sizeof( mLoopedEffectArray )); +} + +int CFxScheduler::ScheduleLoopedEffect( int id, int boltInfo, int iGhoul2, bool isPortal, int iLoopTime, bool isRelative ) +{ + int i; + + assert(id); + assert(boltInfo!=-1); + + for (i=0;i> ENTITY_SHIFT ) & ENTITY_AND; + // Find out where the entity currently is + TCGVectorData *data = (TCGVectorData*)cl->mSharedMemory; + + data->mEntityNum = entNum; + VM_Call( cgvm, CG_GET_LERP_ORIGIN ); + + PlayEffect( mLoopedEffectArray[i].mId, data->mPoint, 0, mLoopedEffectArray[i].mBoltInfo, mLoopedEffectArray[i].mGhoul2.mItem, -1, mLoopedEffectArray[i].mPortalEffect, false, mLoopedEffectArray[i].mIsRelative ); //very important to send FALSE to not recursively add me! + mLoopedEffectArray[i].mNextTime = theFxHelper.mTime + mEffectTemplates[mLoopedEffectArray[i].mId].mRepeatDelay; + if (mLoopedEffectArray[i].mLoopStopTime && mLoopedEffectArray[i].mLoopStopTime < theFxHelper.mTime) //time's up + {//kill this entry + memset( &mLoopedEffectArray[i], 0, sizeof(mLoopedEffectArray[i]) ); + } + } + } + +} + +//----------------------------------------------------------- +void SEffectTemplate::operator=(const SEffectTemplate &that) +{ + mCopy = true; + + strcpy( mEffectName, that.mEffectName ); + + mPrimitiveCount = that.mPrimitiveCount; + + for( int i = 0; i < mPrimitiveCount; i++ ) + { + mPrimitives[i] = new CPrimitiveTemplate; + *(mPrimitives[i]) = *(that.mPrimitives[i]); + // Mark use as a copy so that we know that we should be chucked when used up + mPrimitives[i]->mCopy = true; + } +} + +//------------------------------------------------------ +// Clean +// Free up any memory we've allocated so we aren't leaking memory +// +// Input: +// Whether to clean everything or just stop the playing (active) effects +// +// Return: +// None +// +//------------------------------------------------------ +void CFxScheduler::Clean(bool bRemoveTemplates /*= true*/, int idToPreserve /*= 0*/) +{ + int i, j; + TScheduledEffect::iterator itr, next; + + // Ditch any scheduled effects + itr = mFxSchedule.begin(); + + while ( itr != mFxSchedule.end() ) + { + next = itr; + next++; + + delete *itr; + mFxSchedule.erase(itr); + + itr = next; + } + + if (bRemoveTemplates) + { + // Ditch any effect templates + for ( i = 1; i < FX_MAX_EFFECTS; i++ ) + { + if ( i == idToPreserve) + { + continue; + } + + if ( mEffectTemplates[i].mInUse ) + { + // Ditch the primitives + for (j = 0; j < mEffectTemplates[i].mPrimitiveCount; j++) + { + delete mEffectTemplates[i].mPrimitives[j]; + } + } + + mEffectTemplates[i].mInUse = false; + } + + if (idToPreserve == 0) + { + mEffectIDs.clear(); + } + else + { + // Clear the effect names, but first get the name of the effect to preserve, + // and restore it after clearing. + string str; + TEffectID::iterator iter; + + for (iter = mEffectIDs.begin(); iter != mEffectIDs.end(); ++iter) + { + if ((*iter).second == idToPreserve) + { + str = (*iter).first; + break; + } + } + + mEffectIDs.clear(); + + mEffectIDs[str] = idToPreserve; + } + } +} + +//------------------------------------------------------ +// RegisterEffect +// Attempt to open the specified effect file, if +// file read succeeds, parse the file. +// +// Input: +// path or filename to open +// +// Return: +// int handle to the effect +//------------------------------------------------------ +int CFxScheduler::RegisterEffect( const char *file, bool bHasCorrectPath /*= false*/ ) +{ + // Dealing with file names: + // File names can come from two places - the editor, in which case we should use the given + // path as is, and the effect file, in which case we should add the correct path and extension. + // In either case we create a stripped file name to use for naming effects. + // + + char sfile[MAX_QPATH]; + + COM_StripExtension( file, sfile ); + strlwr(sfile); + + Com_DPrintf("Registering effect : %s\n", sfile); + + // see if the specified file is already registered. If it is, just return the id of that file + TEffectID::iterator itr; + + itr = mEffectIDs.find( sfile ); + + if ( itr != mEffectIDs.end() ) + { + return (*itr).second; + } + + CGenericParser2 parser; + int len = 0; + fileHandle_t fh; + //char data[65536]; + char *bufParse = 0; + + // if our file doesn't have an extension, add one + string finalFilename = file; + string effectsSubstr = finalFilename.substr(0, 7); + + if (finalFilename.find('.') == string::npos) + { + // didn't find an extension so add one + finalFilename += ".efx"; + } + + // kef - grr. this angers me. every filename everywhere should start from the base dir + if (effectsSubstr.compare("effects") != 0) + { + //theFxHelper.Print("Hey!!! '%s' should be pathed from the base directory!!!\n", finalFilename.c_str()); + string strTemp = finalFilename; + finalFilename = "effects/"; + finalFilename += strTemp; + } + + len = theFxHelper.OpenFile( finalFilename.c_str(), &fh, FS_READ ); + + /* + if (bHasCorrectPath) + { + pfile = file; + } + else + { + // Add on our extension and prepend the file with the default path + sprintf( temp, "%s/%s.efx", FX_FILE_PATH, sfile ); + pfile = temp; + } + len = theFxHelper.OpenFile( pfile, &fh, FS_READ ); + */ + + + if ( len < 0 ) + { + theFxHelper.Print( "Effect file load failed: %s\n", finalFilename.c_str() ); + return 0; + } + + if (len == 0) + { + theFxHelper.Print( "INVALID Effect file: %s\n", finalFilename.c_str() ); + theFxHelper.CloseFile( fh ); + return 0; + } + + char *data = (char *) Z_Malloc( len+1, TAG_TEMP_WORKSPACE, qfalse, 4 ); + + // If we'll overflow our buffer, bail out--not a particularly elegant solution +/* + if (len >= sizeof(data) - 1 ) + { + theFxHelper.CloseFile( fh ); + return 0; + } +*/ + + // Get the goods and ensure Null termination + theFxHelper.ReadFile( data, len, fh ); + data[len] = '\0'; + bufParse = data; + + // Let the generic parser process the whole file + parser.Parse( &bufParse ); + + theFxHelper.CloseFile( fh ); + + // Lets convert the effect file into something that we can work with + int retVal = ParseEffect( sfile, parser.GetBaseParseGroup() ); + Z_Free(data); + return retVal; +} + + +//------------------------------------------------------ +// ParseEffect +// Starts at ground zero, using each group header to +// determine which kind of effect we are working with. +// Then we call the appropriate function to parse the +// specified effect group. +// +// Input: +// base group, essentially the whole files contents +// +// Return: +// int handle of the effect +//------------------------------------------------------ +int CFxScheduler::ParseEffect( const char *file, CGPGroup *base ) +{ + CGPGroup *primitiveGroup; + CPrimitiveTemplate *prim; + const char *grpName; + SEffectTemplate *effect = 0; + EPrimType type; + int handle; + CGPValue *pair; + + effect = GetNewEffectTemplate( &handle, file ); + + if ( !handle || !effect ) + { + // failure + return 0; + } + if ((pair = base->GetPairs())!=0) + { + grpName = pair->GetName(); + if ( !stricmp( grpName, "repeatDelay" )) + { + effect->mRepeatDelay = atoi(pair->GetTopValue()); + } + else + {//unknown + + } + } + + primitiveGroup = base->GetSubGroups(); + + while ( primitiveGroup ) + { + grpName = primitiveGroup->GetName(); + + // Huge stricmp lists suxor + if ( !stricmp( grpName, "particle" )) + { + type = Particle; + } + else if ( !stricmp( grpName, "line" )) + { + type = Line; + } + else if ( !stricmp( grpName, "tail" )) + { + type = Tail; + } + else if ( !stricmp( grpName, "sound" )) + { + type = Sound; + } + else if ( !stricmp( grpName, "cylinder" )) + { + type = Cylinder; + } + else if ( !stricmp( grpName, "electricity" )) + { + type = Electricity; + } + else if ( !stricmp( grpName, "emitter" )) + { + type = Emitter; + } + else if ( !stricmp( grpName, "decal" )) + { + type = Decal; + } + else if ( !stricmp( grpName, "orientedparticle" )) + { + type = OrientedParticle; + } + else if ( !stricmp( grpName, "fxrunner" )) + { + type = FxRunner; + } + else if ( !stricmp( grpName, "light" )) + { + type = Light; + } + else if ( !stricmp( grpName, "cameraShake" )) + { + type = CameraShake; + } + else if ( !stricmp( grpName, "flash" )) + { + type = ScreenFlash; + } + else + { + type = None; + } + + if ( type != None ) + { + prim = new CPrimitiveTemplate; + + prim->mType = type; + prim->ParsePrimitive( primitiveGroup ); + + // Add our primitive template to the effect list + AddPrimitiveToEffect( effect, prim ); + } + + primitiveGroup = (CGPGroup *)primitiveGroup->GetNext(); + } + + return handle; +} + + +//------------------------------------------------------ +// AddPrimitiveToEffect +// Takes a primitive and attaches it to the effect. +// +// Input: +// Effect template that we tack the primitive on to +// Primitive to add to the effect template +// +// Return: +// None +//------------------------------------------------------ +void CFxScheduler::AddPrimitiveToEffect( SEffectTemplate *fx, CPrimitiveTemplate *prim ) +{ + int ct = fx->mPrimitiveCount; + + if ( ct >= FX_MAX_EFFECT_COMPONENTS ) + { + theFxHelper.Print( "FxScheduler: Error--too many primitives in an effect\n" ); + } + else + { + fx->mPrimitives[ct] = prim; + fx->mPrimitiveCount++; + } +} + +//------------------------------------------------------ +// GetNewEffectTemplate +// Finds an unused effect template and returns it to the +// caller. +// +// Input: +// pointer to an id that will be filled in, +// file name-- should be NULL when requesting a copy +// +// Return: +// the id of the added effect template +//------------------------------------------------------ +SEffectTemplate *CFxScheduler::GetNewEffectTemplate( int *id, const char *file ) +{ + SEffectTemplate *effect; + + // wanted zero to be a bogus effect ID, so we just skip it. + for ( int i = 1; i < FX_MAX_EFFECTS; i++ ) + { + effect = &mEffectTemplates[i]; + + if ( !effect->mInUse ) + { + *id = i; + memset( effect, 0, sizeof( SEffectTemplate )); + + // If we are a copy, we really won't have a name that we care about saving for later + if ( file ) + { + mEffectIDs[file] = i; + strcpy( effect->mEffectName, file ); + } + + effect->mInUse = true; + effect->mRepeatDelay = 300; + return effect; + } + } + + theFxHelper.Print( "FxScheduler: Error--reached max effects\n" ); + *id = 0; + return 0; +} + +//------------------------------------------------------ +// GetEffectCopy +// Returns a copy of the desired effect so that it can +// easily be modified run-time. +// +// Input: +// file-- the name of the effect file that you want a copy of +// newHandle-- will actually be the returned handle to the new effect +// you have to hold onto this if you intend to call it again +// +// Return: +// the pointer to the copy +//------------------------------------------------------ +SEffectTemplate *CFxScheduler::GetEffectCopy( const char *file, int *newHandle ) +{ + return ( GetEffectCopy( mEffectIDs[file], newHandle ) ); +} + +//------------------------------------------------------ +// GetEffectCopy +// Returns a copy of the desired effect so that it can +// easily be modified run-time. +// +// Input: +// fxHandle-- the handle to the effect that you want a copy of +// newHandle-- will actually be the returned handle to the new effect +// you have to hold onto this if you intend to call it again +// +// Return: +// the pointer to the copy +//------------------------------------------------------ +SEffectTemplate *CFxScheduler::GetEffectCopy( int fxHandle, int *newHandle ) +{ + if ( fxHandle < 1 || fxHandle >= FX_MAX_EFFECTS) + { + // Didn't even request a valid effect to copy!!! + theFxHelper.Print( "FxScheduler: Bad effect file copy request: id = %d\n", fxHandle ); + + *newHandle = 0; + return 0; + } + + if (!mEffectTemplates[fxHandle].mInUse ) + { + // Didn't even request a valid effect to copy!!! + theFxHelper.Print( "FxScheduler: Bad effect file copy request: id %d not inuse\n", fxHandle ); + + *newHandle = 0; + return 0; + } + +#ifdef _SOF2DEV_ + // never get a copy when time is frozen + if ( fx_freeze->integer ) + { + return 0; + } +#endif + + // Copies shouldn't have names, otherwise they could trash our stl map used for getting ID from name + SEffectTemplate *copy = GetNewEffectTemplate( newHandle, NULL ); + + if ( copy && *newHandle ) + { + // do the effect copy and mark us as what we are + *copy = mEffectTemplates[fxHandle]; + copy->mCopy = true; + + // the user had better hold onto this handle if they ever hope to call this effect. + return copy; + } + + // No space left to return an effect + *newHandle = 0; + return 0; +} + +//------------------------------------------------------ +// GetPrimitiveCopy +// Helper function that returns a copy of the desired primitive +// +// Input: +// fxHandle - the pointer to the effect copy you want to override +// componentName - name of the component to find +// +// Return: +// the pointer to the desired primitive +//------------------------------------------------------ +CPrimitiveTemplate *CFxScheduler::GetPrimitiveCopy( SEffectTemplate *effectCopy, const char *componentName ) +{ + if ( !effectCopy || !effectCopy->mInUse ) + { + return NULL; + } + + for ( int i = 0; i < effectCopy->mPrimitiveCount; i++ ) + { + if ( !stricmp( effectCopy->mPrimitives[i]->mName, componentName )) + { + // we found a match, so return it + return effectCopy->mPrimitives[i]; + } + } + + // bah, no good. + return NULL; +} + + +void CFxScheduler::MaterialImpact(trace_t *tr, CEffect *effect) +{ +/* EMatImpactEffect matImpactEffect = effect->GetMatImpactFX(); + int impactParm = effect->GetMatImpactParm(); + + if (matImpactEffect == MATIMPACTFX_NONE) + { + return; + } + else if (matImpactEffect == MATIMPACTFX_SHELLSOUND) + { + // only want to play this for the first impact + effect->SetMatImpactFX(MATIMPACTFX_NONE); + + int material = tr->surfaceFlags & MATERIAL_MASK; + const char *ammoName = CWeaponSystem::GetAmmoName(impactParm); + + if(ammoName && materials[material].HasShellSound(ammoName)) + { + theFxHelper.PlaySound( tr->endpos, ENTITYNUM_NONE, CHAN_AUTO, materials[material].GetShellSoundHandle(ammoName) ); + } + }*/ +} + + +//------------------------------------------------------ +static void ReportPlayEffectError(int id) +{ + theFxHelper.Print( "CFxScheduler::PlayEffect called with invalid effect ID: %i\n", id ); +} + + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Applies a default up +// axis. +// +// Input: +// Effect file id and the origin +// +// Return: +// none +//------------------------------------------------------ +/*void CFxScheduler::PlayEffect( int id, vec3_t origin, int vol, int rad ) +{ + vec3_t axis[3]; + + VectorSet( axis[0], 0, 0, 1 ); + VectorSet( axis[1], 1, 0, 0 ); + VectorSet( axis[2], 0, 1, 0 ); + + PlayEffect( id, origin, axis, vol, rad ); +} +*/ +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Takes a fwd vector +// and builds a right and up vector +// +// Input: +// Effect file id, the origin, and a fwd vector +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( int id, vec3_t origin, vec3_t forward, int vol, int rad, bool isPortal ) +{ + vec3_t axis[3]; + + // Take the forward vector and create two arbitrary but perpendicular vectors + VectorCopy( forward, axis[0] ); + MakeNormalVectors( forward, axis[1], axis[2] ); + + PlayEffect( id, origin, axis, -1, 0, -1, vol, rad, isPortal ); +} + + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Uses the specified axis +// +// Input: +// Effect file name, the origin, and axis. +// Optional boltInfo (defaults to -1) +// and iGhoul2 used by boltInfo +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( const char *file, vec3_t origin, vec3_t axis[3], const int boltInfo, int iGhoul2, + int fxParm /*-1*/, int vol, int rad, int iLoopTime, bool isRelative ) +{ + char sfile[MAX_QPATH]; + + // Get an extenstion stripped version of the file + COM_StripExtension( file, sfile ); + +#ifndef FINAL_BUILD + if ( mEffectIDs[sfile] == 0 ) + { + theFxHelper.Print( "CFxScheduler::PlayEffect unregistered/non-existent effect: %s\n", sfile ); + return; + } +#endif + + PlayEffect( mEffectIDs[sfile], origin, axis, boltInfo, iGhoul2, fxParm, vol, rad, qfalse, iLoopTime, isRelative ); +} + +int totalPrimitives = 0; +int totalEffects = 0; + +void GetRGB_Colors( CPrimitiveTemplate *fx, vec3_t outStartRGB, vec3_t outEndRGB ) +{ + float percent; + + if ( fx->mSpawnFlags & FX_RGB_COMPONENT_INTERP ) + { + percent = flrand(0.0f, 1.0f); + + VectorSet( outStartRGB, fx->mRedStart.GetVal(percent), fx->mGreenStart.GetVal(percent), fx->mBlueStart.GetVal(percent) ); + VectorSet( outEndRGB, fx->mRedEnd.GetVal(percent), fx->mGreenEnd.GetVal(percent), fx->mBlueEnd.GetVal(percent) ); + } + else + { + VectorSet( outStartRGB, fx->mRedStart.GetVal(), fx->mGreenStart.GetVal(), fx->mBlueStart.GetVal() ); + VectorSet( outEndRGB, fx->mRedEnd.GetVal(), fx->mGreenEnd.GetVal(), fx->mBlueEnd.GetVal() ); + } +} + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Uses the specified axis +// +// Input: +// Effect id, the origin, and axis. +// Optional boltInfo (defaults to -1) +// Optional entity number to be used by a cheap entity origin bolt (defaults to -1) +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( int id, vec3_t origin, vec3_t axis[3], const int boltInfo, int iGhoul2, int fxParm /*-1*/, int vol, int rad, bool isPortal/*false*/, int iLoopTime/*0*/, bool isRelative ) +{ + SEffectTemplate *fx; + CPrimitiveTemplate *prim; + int i = 0; + int count = 0, delay = 0; + float factor = 0.0f, fxscale; + bool forceScheduling = false; + + if ( id < 1 || id >= FX_MAX_EFFECTS || !mEffectTemplates[id].mInUse ) + { + // Now you've done it! + ReportPlayEffectError(id); + return; + } + +#ifdef _SOF2DEV_ + // Don't bother scheduling the effect if the system is currently frozen + if ( fx_freeze->integer ) + { + return; + } +#endif + + int modelNum = 0, boltNum = -1; + int entityNum = -1; + + if ( boltInfo > 0 ) + { + // extract the wraith ID from the bolt info + modelNum = ( boltInfo >> MODEL_SHIFT ) & MODEL_AND; + boltNum = ( boltInfo >> BOLT_SHIFT ) & BOLT_AND; + entityNum = ( boltInfo >> ENTITY_SHIFT ) & ENTITY_AND; + + // We always force ghoul bolted objects to be scheduled so that they don't play right away. + forceScheduling = true; + + if (iLoopTime)//0 = not looping, 1 for infinite, else duration + {//store off the id to reschedule every frame + ScheduleLoopedEffect(id, boltInfo, iGhoul2, !!isPortal, iLoopTime, isRelative); + } + } + + // Get the effect. + fx = &mEffectTemplates[id]; + +#ifndef FINAL_BUILD + if ( fx_debug->integer == 2 ) + { + Com_Printf( "> %s\n", fx->mEffectName); + } +#endif + + // Loop through the primitives and schedule each bit + for ( i = 0; i < fx->mPrimitiveCount; i++ ) + { + totalPrimitives++; + prim = fx->mPrimitives[i]; + + prim->mSoundRadius = rad; + prim->mSoundVolume = vol; + + if ( prim->mCullRange ) + { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + if ( DistanceSquared( origin, cg->refdef.vieworg ) > prim->mCullRange ) // cullrange gets squared on load + { + // is too far away + continue; + } + } + else { +#endif + if ( DistanceSquared( origin, theFxHelper.refdef->vieworg ) > prim->mCullRange ) // cullrange gets squared on load + { + // is too far away + continue; + } +#ifdef _XBOX + } +#endif + } + + // Scale the particles based on the countscale factor. Never, ever scale the particles upwards, however. + fxscale = fx_countScale->value; + if (fxscale > 1.0) + { + fxscale = 1.0; + } + // Only use scalability if there is a range + // Temp fix until I have time to reweight all the scalability files + if(fabsf(prim->mSpawnCount.GetMax() - prim->mSpawnCount.GetMin()) > 1.0f) + { + count = Round(prim->mSpawnCount.GetVal() * fxscale); + } + else + { + count = Round(prim->mSpawnCount.GetVal()); + } + // Make sure we have at least one particle after scaling + if(prim->mSpawnCount.GetMin() >= 1.0f && count < 1) + { + count = 1; + } + + if ( prim->mCopy ) + { + // If we are a copy, we need to store a "how many references count" so that we + // can keep the primitive template around for the correct amount of time. + prim->mRefCount = count; + } + + if ( prim->mSpawnFlags & FX_EVEN_DISTRIBUTION ) + { + factor = abs(prim->mSpawnDelay.GetMax() - prim->mSpawnDelay.GetMin()) / (float)count; + } + + // Schedule the random number of bits + for ( int t = 0; t < count; t++ ) + { + totalEffects++; + if ( prim->mSpawnFlags & FX_EVEN_DISTRIBUTION ) + { + delay = t * factor; + } + else + { + delay = prim->mSpawnDelay.GetVal(); + } + + // if the delay is so small, we may as well just create this bit right now + if ( delay < 1 && !forceScheduling && !isPortal ) + { + if ( boltInfo == -1 && entityNum != -1 ) + { + // Find out where the entity currently is + TCGVectorData *data = (TCGVectorData*)cl->mSharedMemory; + + data->mEntityNum = entityNum; + VM_Call( cgvm, CG_GET_LERP_ORIGIN ); + CreateEffect( prim, data->mPoint, axis, -delay, fxParm ); + } + else + { + CreateEffect( prim, origin, axis, -delay, fxParm ); + } + } + else + { + // We have to create a new scheduled effect so that we can create it at a later point + // you should avoid this because it's much more expensive + SScheduledEffect *sfx; + sfx = new SScheduledEffect; + sfx->mStartTime = theFxHelper.mTime + delay; + sfx->mpTemplate = prim; + sfx->mIsRelative = isRelative; + sfx->mPortalEffect = isPortal; + + if ( boltInfo == -1 ) + { + sfx->iGhoul2 = 0; + if ( entityNum == -1 ) + { + // we aren't bolting, so make sure the spawn system knows this by putting -1's in these fields + sfx->mBoltNum = -1; + sfx->mEntNum = ENTITYNUM_NONE; + sfx->mModelNum = 0; + + if ( origin ) + { + VectorCopy( origin, sfx->mOrigin ); + } + else + { + VectorClear( sfx->mOrigin ); + } + + AxisCopy( axis, sfx->mAxis ); + } + else + { + // we are doing bolting onto the origin of the entity, so use a cheaper method + sfx->mBoltNum = -1; + sfx->mEntNum = entityNum; + sfx->mModelNum = 0; + + AxisCopy( axis, sfx->mAxis ); + } + } + else + { + // we are bolting, so store the extra info + sfx->mBoltNum = boltNum; + sfx->mEntNum = entityNum; + sfx->mModelNum = modelNum; + sfx->iGhoul2 = iGhoul2; + + // Also, the ghoul bolt may not be around yet, so delay the creation one frame + sfx->mStartTime++; + } + + mFxSchedule.push_front( sfx ); + } + } + } + + // We track effect templates and primitive templates separately. + if ( fx->mCopy ) + { + // We don't use dynamic memory allocation, so just mark us as dead + fx->mInUse = false; + } +} + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Applies a default up +// axis. +// +// Input: +// Effect file name and the origin +// +// Return: +// none +//------------------------------------------------------ +/* +void CFxScheduler::PlayEffect( const char *file, vec3_t origin, int vol, int rad ) +{ + char sfile[MAX_QPATH]; + + // Get an extenstion stripped version of the file + COM_StripExtension( file, sfile ); + + PlayEffect( mEffectIDs[sfile], origin, vol, rad ); +} +*/ +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Takes a forward vector +// and uses this to complete the axis field. +// +// Input: +// Effect file name, the origin, and a forward vector +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( const char *file, vec3_t origin, vec3_t forward, int vol, int rad ) +{ + char sfile[MAX_QPATH]; + + // Get an extenstion stripped version of the file + COM_StripExtension( file, sfile ); + + PlayEffect( mEffectIDs[sfile], origin, forward, vol, rad ); +} + +//------------------------------------------------------ +// AddScheduledEffects +// Handles determining if a scheduled effect should +// be created or not. If it should it handles converting +// the template effect into a real one. +// +// Input: +// none +// +// Return: +// none +//------------------------------------------------------ +bool gEffectsInPortal = qfalse; //this is just because I don't want to have to add an mPortalEffect field to every actual effect. + +void CFxScheduler::AddScheduledEffects( bool portal ) +{ + TScheduledEffect::iterator itr, next; + SScheduledEffect *schedEffect = 0; + vec3_t origin; + vec3_t axis[3]; + int oldEntNum = -1, oldBoltIndex = -1, oldModelNum = -1; + qboolean doesBoltExist = qfalse; + + if (portal) + { + gEffectsInPortal = true; + } + else + { + AddLoopedEffects(); + } + + itr = mFxSchedule.begin(); + + while ( itr != mFxSchedule.end() ) + { + next = itr; + next++; + schedEffect = (*itr); + + if (portal == (*itr)->mPortalEffect) + { //only render portal fx on the skyportal pass and vice versa + if ( *(*itr) <= theFxHelper.mTime ) + { + if ((*itr)->mBoltNum == -1) + {// ok, are we spawning a bolt on effect or a normal one? + if ( (*itr)->mEntNum != ENTITYNUM_NONE ) + { + // Find out where the entity currently is + TCGVectorData *data = (TCGVectorData*)cl->mSharedMemory; + + data->mEntityNum = (*itr)->mEntNum; + VM_Call( cgvm, CG_GET_LERP_ORIGIN ); + CreateEffect( (*itr)->mpTemplate, + data->mPoint, (*itr)->mAxis, + theFxHelper.mTime - (*itr)->mStartTime ); + } + else + { + CreateEffect( (*itr)->mpTemplate, + (*itr)->mOrigin, (*itr)->mAxis, + theFxHelper.mTime - (*itr)->mStartTime ); + } + } + else + { //bolted on effect + // do we need to go and re-get the bolt matrix again? Since it takes time lets try to do it only once + if (((*itr)->mModelNum != oldModelNum) || + ((*itr)->mEntNum != oldEntNum) || + ((*itr)->mBoltNum != oldBoltIndex)) + { + oldModelNum = (*itr)->mModelNum; + oldEntNum = (*itr)->mEntNum; + oldBoltIndex = (*itr)->mBoltNum; + CGhoul2Info_v Ghoul2((*itr)->iGhoul2); + doesBoltExist = theFxHelper.GetOriginAxisFromBolt(&Ghoul2, (*itr)->mEntNum, (*itr)->mModelNum, (*itr)->mBoltNum, origin, axis); + Ghoul2.kill(); //remove the model ref without actually deleting it + } + + // only do this if we found the bolt + if (doesBoltExist) + { + if ((*itr)->mIsRelative ) + { + CreateEffect( (*itr)->mpTemplate, + origin, axis, 0, -1, + (*itr)->iGhoul2, (*itr)->mEntNum, (*itr)->mModelNum, (*itr)->mBoltNum ); + } + else + { + CreateEffect( (*itr)->mpTemplate, + origin, axis, + theFxHelper.mTime - (*itr)->mStartTime ); + } + } + } + + delete *itr; + mFxSchedule.erase(itr); + } + } + + itr = next; + } + + // Add all active effects into the scene + FX_Add( !!portal ); + + gEffectsInPortal = false; +} + +bool CFxScheduler::Add2DEffect(float x, float y, float w, float h, vec4_t color, qhandle_t shaderHandle) +{ + // need some sort of scale here because the effect was created using world units, not pixels + float fxScale2D = 10.0f; + + if (mNextFree2DEffect < FX_MAX_2DEFFECTS) + { + m2DEffects[mNextFree2DEffect].mScreenX = x; + m2DEffects[mNextFree2DEffect].mScreenY = y; + m2DEffects[mNextFree2DEffect].mWidth = w*fxScale2D; + m2DEffects[mNextFree2DEffect].mHeight = h*fxScale2D; + VectorCopy4(color, m2DEffects[mNextFree2DEffect].mColor); + m2DEffects[mNextFree2DEffect].mShaderHandle = shaderHandle; + + mNextFree2DEffect++; + return true; + } + return false; +} + +void CFxScheduler::Draw2DEffects(float screenXScale, float screenYScale) +{ + float x = 0, y = 0, w = 0, h = 0; + + for (int i = 0; i < mNextFree2DEffect; i++) + { + x = m2DEffects[i].mScreenX; + y = m2DEffects[i].mScreenY; + w = m2DEffects[i].mWidth; + h = m2DEffects[i].mHeight; + + x *= screenXScale; + w *= screenXScale; + y *= screenYScale; + h *= screenYScale; + + //allow 2d effect coloring? + re.DrawStretchPic(x - (w*0.5f), y - (h*0.5f), w, h, 0, 0, 1, 1, /*m2DEffects[i].mColor,*/ m2DEffects[i].mShaderHandle); + } + // now that all 2D effects have been drawn we can consider the entire array to be free + mNextFree2DEffect = 0; +} + +//------------------------------------------------------ +// CreateEffect +// Creates the specified fx taking into account the +// multitude of different ways it could be spawned. +// +// Input: +// template used to build the effect, desired effect origin, +// desired orientation and how late the effect is so that +// it can be moved to the correct spot +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::CreateEffect( CPrimitiveTemplate *fx, const vec3_t origin, vec3_t axis[3], int lateTime, int fxParm /*-1*/, int iGhoul2, int entNum, int modelNum, int boltNum ) +{ + vec3_t org, org2, temp, + vel, accel, + sRGB, eRGB, + ang, angDelta, + ax[3]; + trace_t tr; + int emitterModel; + + // We may modify the axis, so make a work copy + AxisCopy( axis, ax ); + + int flags = fx->mFlags; + if (iGhoul2>0 && modelNum>=0 && boltNum>=0) + {//since you passed in these values, mark as relative to use them if it is supported + switch( fx->mType ) + { + case Particle: + case Line: + case Tail: + case Electricity: + case Cylinder: + case Emitter: + case OrientedParticle: + case Light: + flags |= FX_RELATIVE; + break; + case Decal: + case FxRunner: + case ScreenFlash: + //not supported yet + case Sound: + case CameraShake: + //does not work bolted + break; + } + } + + if( fx->mSpawnFlags & FX_RAND_ROT_AROUND_FWD ) + { + RotatePointAroundVector( ax[1], ax[0], axis[1], flrand(0.0f, 360.0f) ); + CrossProduct( ax[0], ax[1], ax[2] ); + } + + // Origin calculations + //------------------------------------- + if ( fx->mSpawnFlags & FX_CHEAP_ORG_CALC || flags & FX_RELATIVE ) + { // let's take the easy way out + VectorSet( org, fx->mOrigin1X.GetVal(), fx->mOrigin1Y.GetVal(), fx->mOrigin1Z.GetVal() ); + } + else + { // time for some extra work + VectorScale( ax[0], fx->mOrigin1X.GetVal(), org ); + VectorMA( org, fx->mOrigin1Y.GetVal(), ax[1], org ); + VectorMA( org, fx->mOrigin1Z.GetVal(), ax[2], org ); + } + + // We always add our calculated offset to the passed in origin, unless relative! + if( !(flags & FX_RELATIVE) ) + { + VectorAdd( org, origin, org ); + } + // Now, we may need to calc a point on a sphere/ellipsoid/cylinder/disk and add that to it + //---------------------------------------------------------------- + if ( fx->mSpawnFlags & FX_ORG_ON_SPHERE ) + { + float x, y; + float width, height; + + x = DEG2RAD( flrand(0.0f, 360.0f) ); + y = DEG2RAD( flrand(0.0f, 180.0f) ); + + width = fx->mRadius.GetVal(); + height = fx->mHeight.GetVal(); + + // calculate point on ellipse + VectorSet( temp, sin(x) * width * sin(y), cos(x) * width * sin(y), cos(y) * height ); // sinx * siny, cosx * siny, cosy + VectorAdd( org, temp, org ); + + if ( fx->mSpawnFlags & FX_AXIS_FROM_SPHERE ) + { + // well, we will now override the axis at the users request + VectorNormalize2( temp, ax[0] ); + MakeNormalVectors( ax[0], ax[1], ax[2] ); + } + } + else if ( fx->mSpawnFlags & FX_ORG_ON_CYLINDER ) + { + vec3_t pt; + + // set up our point, then rotate around the current direction to. Make unrotated cylinder centered around 0,0,0 + VectorScale( ax[1], fx->mRadius.GetVal(), pt ); + VectorMA( pt, flrand(-1.0f, 1.0f) * 0.5f * fx->mHeight.GetVal(), ax[0], pt ); + RotatePointAroundVector( temp, ax[0], pt, flrand(0.0f, 360.0f) ); + + VectorAdd( org, temp, org ); + + if ( fx->mSpawnFlags & FX_AXIS_FROM_SPHERE ) + { + vec3_t up={0,0,1}; + + // well, we will now override the axis at the users request + VectorNormalize2( temp, ax[0] ); + + if ( ax[0][2] == 1.0f ) + { + // readjust up + VectorSet( up, 0, 1, 0 ); + } + + CrossProduct( up, ax[0], ax[1] ); + CrossProduct( ax[0], ax[1], ax[2] ); + } + } + + if ( fx->mType == OrientedParticle ) + {//bolted oriented particles use origin2 as an angular rotation offset... + if ( flags & FX_RELATIVE ) + { + VectorSet( ax[0], fx->mOrigin2X.GetVal(), fx->mOrigin2Y.GetVal(), fx->mOrigin2Z.GetVal() ); + } + } + + // There are only a few types that really use velocity and acceleration, so do extra work for those types + //-------------------------------------------------------------------------------------------------------- + if ( fx->mType == Particle || fx->mType == OrientedParticle || fx->mType == Tail || fx->mType == Emitter ) + { + // Velocity calculations + //------------------------------------- + if ( fx->mSpawnFlags & FX_VEL_IS_ABSOLUTE || flags & FX_RELATIVE ) + { + VectorSet( vel, fx->mVelX.GetVal(), fx->mVelY.GetVal(), fx->mVelZ.GetVal() ); + } + else + { // bah, do some extra work to coerce it + VectorScale( ax[0], fx->mVelX.GetVal(), vel ); + VectorMA( vel, fx->mVelY.GetVal(), ax[1], vel ); + VectorMA( vel, fx->mVelZ.GetVal(), ax[2], vel ); + } + + //------------------------------------- + if ( fx->mSpawnFlags & FX_AFFECTED_BY_WIND ) + { +/*rjr vec3_t wind; + + // wind is affecting us, so modify our initial velocity. ideally, we would update throughout our lives, but this is easier + CL_GetWindVector( wind ); + VectorMA( vel, fx->mWindModifier.GetVal() * 0.01f, wind, vel ); + */ + } + + // Acceleration calculations + //------------------------------------- + if ( fx->mSpawnFlags & FX_ACCEL_IS_ABSOLUTE || flags & FX_RELATIVE ) + { + VectorSet( accel, fx->mAccelX.GetVal(), fx->mAccelY.GetVal(), fx->mAccelZ.GetVal() ); + } + else + { + VectorScale( ax[0], fx->mAccelX.GetVal(), accel ); + VectorMA( accel, fx->mAccelY.GetVal(), ax[1], accel ); + VectorMA( accel, fx->mAccelZ.GetVal(), ax[2], accel ); + } + + // Gravity is completely decoupled from acceleration since it is __always__ absolute + // NOTE: I only effect Z ( up/down in the Quake world ) + accel[2] += fx->mGravity.GetVal(); + + // There may be a lag between when the effect should be created and when it actually gets created. + // Since we know what the discrepancy is, we can attempt to compensate... + if ( lateTime > 0 ) + { + // Calc the time differences + float ftime = lateTime * 0.001f; + float time2 = ftime * ftime * 0.5f; + + VectorMA( vel, ftime, accel, vel ); + + // Predict the new position + for ( int i = 0 ; i < 3 ; i++ ) + { + org[i] = org[i] + ftime * vel[i] + time2 * vel[i]; + } + } + } // end moving types + + // Line type primitives work with an origin2, so do the extra work for them + //-------------------------------------------------------------------------- + if ( fx->mType == Line || fx->mType == Electricity ) + { + // We may have to do a trace to find our endpoint + if ( fx->mSpawnFlags & FX_ORG2_FROM_TRACE ) + { + VectorMA( org, FX_MAX_TRACE_DIST, ax[0], temp ); + + if ( fx->mSpawnFlags & FX_ORG2_IS_OFFSET ) + { // add a random flair to the endpoint...note: org2 will have to be pretty large to affect this much + // we also do this pre-trace as opposed to post trace since we may have to render an impact effect + // and we will want the normal at the exact endpos... + if ( fx->mSpawnFlags & FX_CHEAP_ORG2_CALC || flags & FX_RELATIVE ) + { + VectorSet( org2, fx->mOrigin2X.GetVal(), fx->mOrigin2Y.GetVal(), fx->mOrigin2Z.GetVal() ); + VectorAdd( org2, temp, temp ); + } + else + { // I can only imagine a few cases where you might want to do this... + VectorMA( temp, fx->mOrigin2X.GetVal(), ax[0], temp ); + VectorMA( temp, fx->mOrigin2Y.GetVal(), ax[1], temp ); + VectorMA( temp, fx->mOrigin2Z.GetVal(), ax[2], temp ); + } + } + + theFxHelper.Trace( tr, org, NULL, NULL, temp, -1, MASK_SOLID ); + + VectorCopy( tr.endpos, org2 ); + + if ( fx->mSpawnFlags & FX_TRACE_IMPACT_FX ) + { + PlayEffect( fx->mImpactFxHandles.GetHandle(), org2, tr.plane.normal ); + } + } + else + { + if ( fx->mSpawnFlags & FX_CHEAP_ORG2_CALC || flags & FX_RELATIVE ) + { + VectorSet( org2, fx->mOrigin2X.GetVal(), fx->mOrigin2Y.GetVal(), fx->mOrigin2Z.GetVal() ); + } + else + { + VectorScale( ax[0], fx->mOrigin2X.GetVal(), org2 ); + VectorMA( org2, fx->mOrigin2Y.GetVal(), ax[1], org2 ); + VectorMA( org2, fx->mOrigin2Z.GetVal(), ax[2], org2 ); + } + if( !(flags & FX_RELATIVE) ) + { + VectorAdd( org2, origin, org2 ); + } + } + } // end special org2 types + + // handle RGB color, but only for types that will use it + //--------------------------------------------------------------------------- + if ( fx->mType != Sound && fx->mType != FxRunner && fx->mType != CameraShake ) + { + GetRGB_Colors( fx, sRGB, eRGB ); + } + + // Now create the appropriate effect entity + //------------------------ + switch( fx->mType ) + { + //--------- + case Particle: + //--------- + + FX_AddParticle( org, vel, accel, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mRotation.GetVal(), fx->mRotationDelta.GetVal(), + fx->mMin, fx->mMax, fx->mElasticity.GetVal(), + fx->mDeathFxHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, fx->mMatImpactFX, fxParm, + iGhoul2, entNum, modelNum, boltNum ); + break; + + //--------- + case Line: + //--------- + + FX_AddLine( org, org2, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, fx->mMatImpactFX, fxParm, + iGhoul2, entNum, modelNum, boltNum); + break; + + //--------- + case Tail: + //--------- + + FX_AddTail( org, vel, accel, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mLengthStart.GetVal(), fx->mLengthEnd.GetVal(), fx->mLengthParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mMin, fx->mMax, fx->mElasticity.GetVal(), + fx->mDeathFxHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, fx->mMatImpactFX, fxParm, + iGhoul2, entNum, modelNum, boltNum); + break; + + //---------------- + case Electricity: + //---------------- + + FX_AddElectricity( org, org2, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mElasticity.GetVal(), fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, + fx->mMatImpactFX, fxParm, + iGhoul2, entNum, modelNum, boltNum); + break; + + //--------- + case Cylinder: + //--------- + + FX_AddCylinder( org, ax[0], + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mSize2Start.GetVal(), fx->mSize2End.GetVal(), fx->mSize2Parm.GetVal(), + fx->mLengthStart.GetVal(), fx->mLengthEnd.GetVal(), fx->mLengthParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, fx->mMatImpactFX, fxParm, + iGhoul2, entNum, modelNum, boltNum, + (qboolean)( fx->mSpawnFlags & FX_ORG2_FROM_TRACE )); + break; + + //--------- + case Emitter: + //--------- + + // for chunk angles, you don't really need much control over the end result...you just want variation.. + VectorSet( ang, + fx->mAngle1.GetVal(), + fx->mAngle2.GetVal(), + fx->mAngle3.GetVal() ); + + vectoangles( ax[0], temp ); + VectorAdd( ang, temp, ang ); + + VectorSet( angDelta, + fx->mAngle1Delta.GetVal(), + fx->mAngle2Delta.GetVal(), + fx->mAngle3Delta.GetVal() ); + + emitterModel = fx->mMediaHandles.GetHandle(); + + FX_AddEmitter( org, vel, accel, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + ang, angDelta, + fx->mMin, fx->mMax, fx->mElasticity.GetVal(), + fx->mDeathFxHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), + fx->mEmitterFxHandles.GetHandle(), + fx->mDensity.GetVal(), fx->mVariance.GetVal(), + fx->mLife.GetVal(), emitterModel, flags, fx->mMatImpactFX, fxParm ); + break; + + //--------- + case Decal: + //--------- + +/* rjr + // This function is a somewhat higher-cost function for projecting a decal mark onto a surface. + // We shouldn't always need this, only for big hits or when a decal is very close-up. + + // If the impact size is greater than 6, don't even try to use cheap sprites. Big marks need real decals. + if (fx->mSizeStart.GetVal()<6) + { + vec3_t dest, normal; + int findmarkret; + + findmarkret = CG_FindMark(org, ax[0], dest, normal); + + if (findmarkret) + { // Legal to put down a mark. + + if ( findmarkret == FINDMARK_CHEAP || // If we can't put down a decal OR if and distance > 200 + DistanceSquared( dest, cg.refdef.vieworg ) > 40000) + { // Use cheap oriented particle decals. + vec3_t zerovec={0,0,0}; + + FX_AddOrientedParticle( effectCloud, org, ax[0], + zerovec, // velocity + zerovec, // acceleration + fx->mSizeStart.GetVal(), fx->mSizeStart.GetVal(), 0, // size params + fx->mAlphaStart.GetVal(), 0, 0.75, // alpha params (start fading at 75% life) + sRGB, sRGB, 0, // rgb params + fx->mRotation.GetVal(), 0, // rotation delta + zerovec, // min + zerovec, // max + 0, // bounce + 0, // deathID + 0, // impactID + 20000, // lifetime + fx->mMediaHandles.GetHandle(), + (fx->mFlags&~FX_ALPHA_PARM_MASK)|FX_ALPHA_NONLINEAR, + MATIMPACTFX_NONE, -1); + } + else + { // Use the expensive kind. + color.rgba.r = sRGB[0] * 0xff; + color.rgba.g = sRGB[1] * 0xff; + color.rgba.b = sRGB[2] * 0xff; + color.rgba.a = fx->mAlphaStart.GetVal() * 0xff; + + CG_ImpactMark( fx->mMediaHandles.GetHandle(), org, ax[0], + fx->mRotation.GetVal(), color.c, 12000, 8000, + true, fx->mSizeStart.GetVal(), false ); + } + } + } + else + { // Use the expensive kind. + color.rgba.r = sRGB[0] * 0xff; + color.rgba.g = sRGB[1] * 0xff; + color.rgba.b = sRGB[2] * 0xff; + color.rgba.a = fx->mAlphaStart.GetVal() * 0xff; + + CG_ImpactMark( fx->mMediaHandles.GetHandle(), org, ax[0], + fx->mRotation.GetVal(), color.c, 12000, 8000, + true, fx->mSizeStart.GetVal(), false ); + } */ + + theFxHelper.AddDecalToScene ( fx->mMediaHandles.GetHandle(), org, ax[0], fx->mRotation.GetVal(), sRGB[0], sRGB[1], sRGB[2], fx->mAlphaStart.GetVal(), qtrue, fx->mSizeStart.GetVal(), qfalse ); + + if (fx->mFlags & FX_GHOUL2_DECALS) + { + theFxHelper.AddGhoul2Decal(fx->mMediaHandles.GetHandle(), org, ax[0], fx->mSizeStart.GetVal()); + } + + break; + + //------------------- + case OrientedParticle: + //------------------- + + FX_AddOrientedParticle( org, ax[0], vel, accel, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mRotation.GetVal(), fx->mRotationDelta.GetVal(), + fx->mMin, fx->mMax, fx->mElasticity.GetVal(), + fx->mDeathFxHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, fx->mMatImpactFX, fxParm, + iGhoul2, entNum, modelNum, boltNum); + break; + + //--------- + case Sound: + //--------- + if (gEffectsInPortal) + { //could orient this anyway for panning, but eh. It's going to appear to the player in the sky the same place no matter what, so just make it a local sound. + theFxHelper.PlayLocalSound( fx->mMediaHandles.GetHandle(), CHAN_AUTO ); + } + else + { + theFxHelper.PlaySound( org, ENTITYNUM_NONE, CHAN_AUTO, fx->mMediaHandles.GetHandle(), fx->mSoundVolume, fx->mSoundRadius ); + } + break; + + //--------- + case FxRunner: + //--------- + + PlayEffect( fx->mPlayFxHandles.GetHandle(), org, ax ); + break; + + //--------- + case Light: + //--------- + + FX_AddLight( org, fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mLife.GetVal(), flags, fx->mMatImpactFX, fxParm, + iGhoul2, entNum, modelNum, boltNum); + break; + + //--------- + case CameraShake: + //--------- + // It calculates how intense the shake should be based on how close you are to the origin you pass in here + // elasticity is actually the intensity...radius is the distance in which the shake will have some effect + // life is how long the effect lasts. + theFxHelper.CameraShake( org, fx->mElasticity.GetVal(), fx->mRadius.GetVal(), fx->mLife.GetVal() ); + break; + + //-------------- + case ScreenFlash: + //-------------- + + FX_AddFlash( org, fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, fx->mMatImpactFX, fxParm ); + break; + + default: + assert(0); + break; + } + + // Track when we need to clean ourselves up if we are a copy + if ( fx->mCopy ) + { + fx->mRefCount--; + + if ( fx->mRefCount <= 0 ) + { + delete fx; + } + } +} + +//------------------------------------------------------ +// CreateEffect +// Creates the fx_runner +// +// Input: +// template used to build the effect, and the scheduled effect we are based off of +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::CreateEffect( CPrimitiveTemplate *fx, SScheduledEffect *scheduledFx ) +{ + int boltInfo; + + // annoying bit....we have to pack the values back into an int before calling playEffect since there isn't the ideal overload we can already use. + boltInfo = (( scheduledFx->mModelNum & MODEL_AND ) << MODEL_SHIFT ); + boltInfo |= (( scheduledFx->mBoltNum & BOLT_AND ) << BOLT_SHIFT ); + boltInfo |= (( scheduledFx->mEntNum & ENTITY_AND ) << ENTITY_SHIFT ); + + PlayEffect( fx->mPlayFxHandles.GetHandle(), scheduledFx->mOrigin, scheduledFx->mAxis, boltInfo ); +} + diff --git a/codemp/client/fxscheduler.h b/codemp/client/fxscheduler.h new file mode 100644 index 0000000..0f8dfe2 --- /dev/null +++ b/codemp/client/fxscheduler.h @@ -0,0 +1,505 @@ + +#if !defined(FX_UTIL_H_INC) + #include "FxUtil.h" +#endif + +#if !defined(GENERICPARSER2_H_INC) + #include "../qcommon/GenericParser2.h" +#endif + +#ifndef FX_SCHEDULER_H_INC +#define FX_SCHEDULER_H_INC + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include +#include +#include +#include +#pragma warning (pop) + +using namespace std; + + +#define FX_FILE_PATH "effects" + +#define FX_MAX_TRACE_DIST 16384 // SOF2 uses a larger scale +#define FX_MAX_EFFECTS 256 // how many effects the system can store +#define FX_MAX_2DEFFECTS 64 // how many 2d effects the system can store +#define FX_MAX_EFFECT_COMPONENTS 24 // how many primitives an effect can hold, this should be plenty +#define FX_MAX_PRIM_NAME 32 + +//----------------------------------------------- +// These are spawn flags for primitiveTemplates +//----------------------------------------------- + +#define FX_ORG_ON_SPHERE 0x00001 // Pretty dang expensive, calculates a point on a sphere/ellipsoid +#define FX_AXIS_FROM_SPHERE 0x00002 // Can be used in conjunction with org_on_sphere to cause particles to move out + // from the center of the sphere +#define FX_ORG_ON_CYLINDER 0x00004 // calculate point on cylinder/disk + +#define FX_ORG2_FROM_TRACE 0x00010 +#define FX_TRACE_IMPACT_FX 0x00020 // if trace impacts, we should play one of the specified impact fx files +#define FX_ORG2_IS_OFFSET 0x00040 // template specified org2 should be the offset from a trace endpos or + // passed in org2. You might use this to lend a random flair to the endpos. + // Note: this is done pre-trace, so you may have to specify large numbers for this + +#define FX_CHEAP_ORG_CALC 0x00100 // Origin is calculated relative to passed in axis unless this is on. +#define FX_CHEAP_ORG2_CALC 0x00200 // Origin2 is calculated relative to passed in axis unless this is on. +#define FX_VEL_IS_ABSOLUTE 0x00400 // Velocity isn't relative to passed in axis with this flag on. +#define FX_ACCEL_IS_ABSOLUTE 0x00800 // Acceleration isn't relative to passed in axis with this flag on. + +#define FX_RAND_ROT_AROUND_FWD 0x01000 // Randomly rotates up and right around forward vector +#define FX_EVEN_DISTRIBUTION 0x02000 // When you have a delay, it normally picks a random time to play. When + // this flag is on, it generates an even time distribution +#define FX_RGB_COMPONENT_INTERP 0x04000 // Picks a color on the line defined by RGB min & max, default is to pick color in cube defined by min & max + +#define FX_AFFECTED_BY_WIND 0x10000 // this effect primitive needs to query wind + +//----------------------------------------------------------------- +// +// CMediaHandles +// +// Primitive templates might want to use a list of sounds, shaders +// or models to get a bit more variation in their effects. +// +//----------------------------------------------------------------- +class CMediaHandles +{ +private: + + vector mMediaList; + +public: + + void AddHandle( int item ) { mMediaList.push_back( item ); } + int GetHandle() { if (mMediaList.size()==0) {return 0;} + else {return mMediaList[irand(0,mMediaList.size()-1)];} } + + void operator=(const CMediaHandles &that ); +}; + + +//----------------------------------------------------------------- +// +// CFxRange +// +// Primitive templates typically use this class to define each of +// its members. This is done to make it easier to create effects +// with a desired range of characteristics. +// +//----------------------------------------------------------------- +class CFxRange +{ +private: + + float mMin; + float mMax; + +public: + + CFxRange(void) { mMin = 0.0f; mMax = 0.0f; } + + inline void SetRange(float min,float max) { mMin = min; mMax = max; } + + inline float GetMax(void) const { return mMax; } + inline float GetMin(void) const { return mMin; } + inline float GetVal(float fraction) const { if(mMin != mMax) { return mMin + fraction * (mMax - mMin); } else { return mMin; } } + inline float GetVal(void) const { if(mMin != mMax) { return flrand(mMin,mMax); } else { return mMin; } } + + inline int GetRoundedVal() const {if(mMin == mMax){return (int)mMin;} + return (int)(flrand(mMin, mMax) + 0.5f);} + + bool operator==(const CFxRange &rhs) const { return ((mMin == rhs.mMin) && (mMax == rhs.mMax)); } +}; + + +//---------------------------- +// Supported primitive types +//---------------------------- + +enum EPrimType +{ + None = 0, + Particle, // sprite + Line, + Tail, // comet-like tail thing + Cylinder, + Emitter, // emits effects as it moves, can also attach a chunk + Sound, + Decal, // projected onto architecture + OrientedParticle, + Electricity, + FxRunner, + Light, + CameraShake, + ScreenFlash +}; + + +//----------------------------------------------------------------- +// +// CPrimitiveTemplate +// +// The primitive template is used to spawn 1 or more fx primitives +// with the range of characteristics defined by the template. +// +// As such, I just made this one huge shared class knowing that +// there won't be many of them in memory at once, and we won't +// be dynamically creating and deleting them mid-game. Also, +// note that not every primitive type will use all of these fields. +// +//----------------------------------------------------------------- +class CPrimitiveTemplate +{ + +public: + + // These kinds of things should not even be allowed to be accessed publicly + bool mCopy; + int mRefCount; // For a copy of a primitive...when we figure out how many items we want to spawn, + // we'll store that here and then decrement us for each we actually spawn. When we + // hit zero, we are no longer used and so we can just free ourselves + + char mName[FX_MAX_PRIM_NAME]; + + EPrimType mType; + + CFxRange mSpawnDelay; + CFxRange mSpawnCount; + CFxRange mLife; + int mCullRange; + + CMediaHandles mMediaHandles; + CMediaHandles mImpactFxHandles; + CMediaHandles mDeathFxHandles; + CMediaHandles mEmitterFxHandles; + CMediaHandles mPlayFxHandles; + + int mFlags; // These need to get passed on to the primitive + int mSpawnFlags; // These are only used to control spawning, but never get passed to prims. + + EMatImpactEffect mMatImpactFX; + + vec3_t mMin; + vec3_t mMax; + + CFxRange mOrigin1X; + CFxRange mOrigin1Y; + CFxRange mOrigin1Z; + + CFxRange mOrigin2X; + CFxRange mOrigin2Y; + CFxRange mOrigin2Z; + + CFxRange mRadius; // spawn on sphere/ellipse/disk stuff. + CFxRange mHeight; + CFxRange mWindModifier; + + CFxRange mRotation; + CFxRange mRotationDelta; + + CFxRange mAngle1; + CFxRange mAngle2; + CFxRange mAngle3; + + CFxRange mAngle1Delta; + CFxRange mAngle2Delta; + CFxRange mAngle3Delta; + + CFxRange mVelX; + CFxRange mVelY; + CFxRange mVelZ; + + CFxRange mAccelX; + CFxRange mAccelY; + CFxRange mAccelZ; + + CFxRange mGravity; + + CFxRange mDensity; + CFxRange mVariance; + + CFxRange mRedStart; + CFxRange mGreenStart; + CFxRange mBlueStart; + + CFxRange mRedEnd; + CFxRange mGreenEnd; + CFxRange mBlueEnd; + + CFxRange mRGBParm; + + CFxRange mAlphaStart; + CFxRange mAlphaEnd; + CFxRange mAlphaParm; + + CFxRange mSizeStart; + CFxRange mSizeEnd; + CFxRange mSizeParm; + + CFxRange mSize2Start; + CFxRange mSize2End; + CFxRange mSize2Parm; + + CFxRange mLengthStart; + CFxRange mLengthEnd; + CFxRange mLengthParm; + + CFxRange mTexCoordS; + CFxRange mTexCoordT; + + CFxRange mElasticity; + + int mSoundRadius; + int mSoundVolume; + + // Lower level parsing utilities + bool ParseVector( const char *val, vec3_t min, vec3_t max ); + bool ParseFloat( const char *val, float *min, float *max ); + bool ParseGroupFlags( const char *val, int *flags ); + + // Base key processing + // Note that these all have their own parse functions in case it becomes important to do certain kinds + // of validation specific to that type. + bool ParseMin( const char *val ); + bool ParseMax( const char *val ); + bool ParseDelay( const char *val ); + bool ParseCount( const char *val ); + bool ParseLife( const char *val ); + bool ParseElasticity( const char *val ); + bool ParseFlags( const char *val ); + bool ParseSpawnFlags( const char *val ); + + bool ParseOrigin1( const char *val ); + bool ParseOrigin2( const char *val ); + bool ParseRadius( const char *val ); + bool ParseHeight( const char *val ); + bool ParseWindModifier( const char *val ); + bool ParseRotation( const char *val ); + bool ParseRotationDelta( const char *val ); + bool ParseAngle( const char *val ); + bool ParseAngleDelta( const char *val ); + bool ParseVelocity( const char *val ); + bool ParseAcceleration( const char *val ); + bool ParseGravity( const char *val ); + bool ParseDensity( const char *val ); + bool ParseVariance( const char *val ); + + // Group type processing + bool ParseRGB( CGPGroup *grp ); + bool ParseAlpha( CGPGroup *grp ); + bool ParseSize( CGPGroup *grp ); + bool ParseSize2( CGPGroup *grp ); + bool ParseLength( CGPGroup *grp ); + + bool ParseModels( CGPValue *grp ); + bool ParseShaders( CGPValue *grp ); + bool ParseSounds( CGPValue *grp ); + + bool ParseImpactFxStrings( CGPValue *grp ); + bool ParseDeathFxStrings( CGPValue *grp ); + bool ParseEmitterFxStrings( CGPValue *grp ); + bool ParsePlayFxStrings( CGPValue *grp ); + + // Group keys + bool ParseRGBStart( const char *val ); + bool ParseRGBEnd( const char *val ); + bool ParseRGBParm( const char *val ); + bool ParseRGBFlags( const char *val ); + + bool ParseAlphaStart( const char *val ); + bool ParseAlphaEnd( const char *val ); + bool ParseAlphaParm( const char *val ); + bool ParseAlphaFlags( const char *val ); + + bool ParseSizeStart( const char *val ); + bool ParseSizeEnd( const char *val ); + bool ParseSizeParm( const char *val ); + bool ParseSizeFlags( const char *val ); + + bool ParseSize2Start( const char *val ); + bool ParseSize2End( const char *val ); + bool ParseSize2Parm( const char *val ); + bool ParseSize2Flags( const char *val ); + + bool ParseLengthStart( const char *val ); + bool ParseLengthEnd( const char *val ); + bool ParseLengthParm( const char *val ); + bool ParseLengthFlags( const char *val ); + + bool ParseMaterialImpact(const char *val); + +public: + + CPrimitiveTemplate(); + ~CPrimitiveTemplate() {}; + + bool ParsePrimitive( CGPGroup *grp ); + + void operator=(const CPrimitiveTemplate &that); +}; + +// forward declaration +struct SEffectTemplate; + +// Effects are built of one or more primitives +struct SEffectTemplate +{ + bool mInUse; + bool mCopy; + char mEffectName[MAX_QPATH]; // is this extraneous?? + int mPrimitiveCount; + int mRepeatDelay; + CPrimitiveTemplate *mPrimitives[FX_MAX_EFFECT_COMPONENTS]; + + bool operator == (const char * name) const + { + return !stricmp( mEffectName, name ); + } + void operator=(const SEffectTemplate &that); +}; + + + +//----------------------------------------------------------------- +// +// CFxScheduler +// +// The scheduler not only handles requests to play an effect, it +// tracks the request throughout its life if necessary, creating +// any of the delayed components as needed. +// +//----------------------------------------------------------------- +class CFxScheduler +{ +private: + + // We hold a scheduled effect here + struct SScheduledEffect + { + CPrimitiveTemplate *mpTemplate; // primitive template + int mStartTime; + char mModelNum; // uset to determine which ghoul2 model we want to bolt this effect to + char mBoltNum; // used to determine which bolt on the ghoul2 model we should be attaching this effect to + short mEntNum; // used to determine which entity this ghoul model is attached to. + bool mPortalEffect; // rww - render this before skyportals, and not in the normal world view. + bool mIsRelative; // bolt this puppy on keep it updated + int iGhoul2; + vec3_t mOrigin; + vec3_t mAxis[3]; + + bool operator <= (const int time) const + { + return mStartTime <= time; + } + }; + +/* Looped Effects get stored and reschedule at mRepeatRate */ + #define MAX_LOOPED_FX 32 + // We hold a looped effect here + struct SLoopedEffect + { + int mId; // effect id + int mBoltInfo; // used to determine which bolt on the ghoul2 model we should be attaching this effect to + CGhoul2Info_v mGhoul2; + int mNextTime; //time to render again + int mLoopStopTime; //time to die + bool mPortalEffect; // rww - render this before skyportals, and not in the normal world view. + bool mIsRelative; // bolt this puppy on keep it updated + }; + + SLoopedEffect mLoopedEffectArray[MAX_LOOPED_FX]; + + int ScheduleLoopedEffect( int id, int boltInfo, int iGhoul2, bool isPortal, int iLoopTime, bool isRelative ); + void AddLoopedEffects( ); + + + class CScheduled2DEffect + { + public: + CScheduled2DEffect(): + mScreenX(0), + mScreenY(0), + mWidth(0), + mHeight(0), + mShaderHandle(0) + {mColor[0]=mColor[1]=mColor[2]=mColor[3]=1.0f;} + + public: + float mScreenX; + float mScreenY; + float mWidth; + float mHeight; + vec4_t mColor; // bytes A, G, B, R -- see class paletteRGBA_c + qhandle_t mShaderHandle; + }; + + // this makes looking up the index based on the string name much easier + typedef map TEffectID; + + typedef list TScheduledEffect; + + // Effects + SEffectTemplate mEffectTemplates[FX_MAX_EFFECTS]; + TEffectID mEffectIDs; // if you only have the unique effect name, you'll have to use this to get the ID. + + // 2D effects + CScheduled2DEffect m2DEffects[FX_MAX_2DEFFECTS]; + int mNextFree2DEffect; + + // List of scheduled effects that will need to be created at the correct time. + TScheduledEffect mFxSchedule; + + + // Private function prototypes + SEffectTemplate *GetNewEffectTemplate( int *id, const char *file ); + + void AddPrimitiveToEffect( SEffectTemplate *fx, CPrimitiveTemplate *prim ); + int ParseEffect( const char *file, CGPGroup *base ); + + void CreateEffect( CPrimitiveTemplate *fx, const vec3_t origin, vec3_t axis[3], int lateTime, int fxParm = -1, int iGhoul2 = 0, int entNum = -1, int modelNum = -1, int boltNum = -1); + void CreateEffect( CPrimitiveTemplate *fx, SScheduledEffect *schedFx ); + +public: + + CFxScheduler(); + + int RegisterEffect( const char *file, bool bHasCorrectPath = false ); // handles pre-caching + + // Nasty overloaded madness + //rww - maybe this should be done differently.. it's more than a bit confusing. + //Remind me when I don't have 50 files checked out. + void PlayEffect( int id, vec3_t org, vec3_t fwd, int vol = -1, int rad = -1, bool isPortal = false ); // builds arbitrary perp. right vector, does a cross product to define up + void PlayEffect( int id, vec3_t origin, vec3_t axis[3], const int boltInfo=-1, int iGhoul2 = 0, + int fxParm = -1, int vol = -1, int rad = -1, bool isPortal = false, int iLoopTime = false, bool isRelative = false ); + void PlayEffect( const char *file, vec3_t org, int vol = -1, int rad = -1 ); // uses a default up axis + void PlayEffect( const char *file, vec3_t org, vec3_t fwd, int vol = -1, int rad = -1 ); // builds arbitrary perp. right vector, does a cross product to define up + void PlayEffect( const char *file, vec3_t origin, + vec3_t axis[3], const int boltInfo = -1, int iGhoul2 = 0, int fxParm = -1, int vol = -1, int rad = -1, int iLoopTime = false, bool isRelative = false ); + + void StopEffect( const char *file, const int boltInfo, bool isPortal = false ); //find a scheduled Looping effect with these parms and kill it + void AddScheduledEffects( bool portal ); // call once per CGame frame + + // kef -- called for a 2D effect instead of addRefToScene + bool Add2DEffect(float x, float y, float w, float h, vec4_t color, qhandle_t shaderHandle); + // kef -- called once per cgame frame AFTER cgi.RenderScene + void Draw2DEffects(float screenXScale, float screenYScale); + + int NumScheduledFx() { return mFxSchedule.size(); } + void Clean(bool bRemoveTemplates = true, int idToPreserve = 0); // clean out the system + + // FX Override functions + SEffectTemplate *GetEffectCopy( int fxHandle, int *newHandle ); + SEffectTemplate *GetEffectCopy( const char *file, int *newHandle ); + + CPrimitiveTemplate *GetPrimitiveCopy( SEffectTemplate *effectCopy, const char *componentName ); + + void MaterialImpact(trace_t *tr, CEffect *effect); +}; + +//------------------- +// The one and only +//------------------- +extern CFxScheduler theFxScheduler; + +#endif // FX_SCHEDULER_H_INC diff --git a/codemp/client/fxsystem.cpp b/codemp/client/fxsystem.cpp new file mode 100644 index 0000000..875d799 --- /dev/null +++ b/codemp/client/fxsystem.cpp @@ -0,0 +1,130 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" +// this include must remain at the top of every CPP file +#include "client.h" + +#if !defined(FX_SCHEDULER_H_INC) + #include "FxScheduler.h" +#endif + +cvar_t *fx_debug; +#ifdef _SOF2DEV_ +cvar_t *fx_freeze; +#endif +cvar_t *fx_countScale; +cvar_t *fx_nearCull; + +#define DEFAULT_EXPLOSION_RADIUS 512 + +// Stuff for the FxHelper +//------------------------------------------------------ +SFxHelper::SFxHelper(void) : + mTime(0), + mOldTime(0), + mFrameTime(0), + mTimeFrozen(false), + refdef(0) +{ +} + +void SFxHelper::ReInit(refdef_t* pRefdef) +{ + mTime = 0; + mOldTime = 0; + mFrameTime = 0; + mTimeFrozen = false; + refdef = pRefdef; +} + +//------------------------------------------------------ +void SFxHelper::Print( const char *msg, ... ) +{ + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + vsprintf( text, msg, argptr ); + va_end( argptr ); + + Com_DPrintf( text ); +} + +//------------------------------------------------------ +void SFxHelper::AdjustTime( int frametime ) +{ +#ifdef _SOF2DEV_ + if ( fx_freeze->integer || ( frametime <= 0 )) +#else + if ( frametime <= 0 ) +#endif + { + // Allow no time progression when we are paused. + mFrameTime = 0; + mRealTime = 0.0f; + } + else + { + mOldTime = mTime; + mTime = frametime; + mFrameTime = mTime - mOldTime; + + mRealTime = mFrameTime * 0.001f; + + +/* mFrameTime = frametime; + mTime += mFrameTime; + mRealTime = mFrameTime * 0.001f;*/ + +// mHalfRealTimeSq = mRealTime * mRealTime * 0.5f; + } +} + +//------------------------------------------------------ +void SFxHelper::CameraShake( vec3_t origin, float intensity, int radius, int time ) +{ + TCGCameraShake *data = (TCGCameraShake *)cl->mSharedMemory; + + VectorCopy(origin, data->mOrigin); + data->mIntensity = intensity; + data->mRadius = radius; + data->mTime = time; + +// VM_Call( cgvm, CG_FX_CAMERASHAKE ); + //FIXME +} + +//------------------------------------------------------ +qboolean SFxHelper::GetOriginAxisFromBolt(CGhoul2Info_v *pGhoul2, int mEntNum, int modelNum, int boltNum, vec3_t /*out*/origin, vec3_t /*out*/axis[3]) +{ + qboolean doesBoltExist; + mdxaBone_t boltMatrix; + TCGGetBoltData *data = (TCGGetBoltData*)cl->mSharedMemory; + data->mEntityNum = mEntNum; + VM_Call( cgvm, CG_GET_LERP_DATA );//this func will zero out pitch and roll for players, and ridable vehicles + + //Fixme: optimize these VM calls away by storing + + // go away and get me the bolt position for this frame please + doesBoltExist = G2API_GetBoltMatrix(*pGhoul2, modelNum, boltNum, + &boltMatrix, data->mAngles, data->mOrigin, theFxHelper.mOldTime, 0, data->mScale); + + if (doesBoltExist) + { // set up the axis and origin we need for the actual effect spawning + origin[0] = boltMatrix.matrix[0][3]; + origin[1] = boltMatrix.matrix[1][3]; + origin[2] = boltMatrix.matrix[2][3]; + + axis[1][0] = boltMatrix.matrix[0][0]; + axis[1][1] = boltMatrix.matrix[1][0]; + axis[1][2] = boltMatrix.matrix[2][0]; + + axis[0][0] = boltMatrix.matrix[0][1]; + axis[0][1] = boltMatrix.matrix[1][1]; + axis[0][2] = boltMatrix.matrix[2][1]; + + axis[2][0] = boltMatrix.matrix[0][2]; + axis[2][1] = boltMatrix.matrix[1][2]; + axis[2][2] = boltMatrix.matrix[2][2]; + } + return doesBoltExist; +} \ No newline at end of file diff --git a/codemp/client/fxsystem.h b/codemp/client/fxsystem.h new file mode 100644 index 0000000..3154ffc --- /dev/null +++ b/codemp/client/fxsystem.h @@ -0,0 +1,226 @@ + +#ifndef FX_SYSTEM_H_INC +#define FX_SYSTEM_H_INC + +#if !defined(G2_H_INC) + #include "../ghoul2/G2.h" + #include "../ghoul2/G2_local.h" +#endif + +extern cvar_t *fx_debug; + +#ifdef _SOF2DEV_ +extern cvar_t *fx_freeze; +#endif + +extern cvar_t *fx_countScale; +extern cvar_t *fx_nearCull; + +inline void Vector2Clear(vec2_t a) +{ + a[0] = 0.0f; + a[1] = 0.0f; +} + +inline void Vector2Set(vec2_t a,float b,float c) +{ + a[0] = b; + a[1] = c; +} + +inline void Vector2Copy(vec2_t src,vec2_t dst) +{ + dst[0] = src[0]; + dst[1] = src[1]; +} + +inline void Vector2MA(vec2_t src, float m, vec2_t v, vec2_t dst) +{ + dst[0] = src[0] + (m*v[0]); + dst[1] = src[1] + (m*v[1]); +} + +inline void Vector2Scale(vec2_t src,float b,vec2_t dst) +{ + dst[0] = src[0] * b; + dst[1] = src[1] * b; +} + +class SFxHelper +{ +public: + int mTime; + int mOldTime; + int mFrameTime; + bool mTimeFrozen; + float mRealTime; + refdef_t* refdef; +#ifdef _DEBUG + int mMainRefs; + int mMiniRefs; +#endif + +public: + SFxHelper(void); + + inline int GetTime(void) { return mTime; } + inline int GetFrameTime(void) { return mFrameTime; } + + void ReInit(refdef_t* pRefdef); + void AdjustTime( int time ); + + // These functions are wrapped and used by the fx system in case it makes things a bit more portable + void Print( const char *msg, ... ); + + // File handling + inline int OpenFile( const char *path, fileHandle_t *fh, int mode ) + { + return FS_FOpenFileByMode( path, fh, FS_READ ); + } + inline int ReadFile( void *data, int len, fileHandle_t fh ) + { + FS_Read2( data, len, fh ); + return 1; + } + inline void CloseFile( fileHandle_t fh ) + { + FS_FCloseFile( fh ); + } + + // Sound + inline void PlaySound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle, int volume, int radius ) + { + //S_StartSound( origin, ENTITYNUM_NONE, CHAN_AUTO, sfxHandle, volume, radius ); + S_StartSound( origin, ENTITYNUM_NONE, CHAN_AUTO, sfxHandle ); + } + inline void PlayLocalSound(sfxHandle_t sfxHandle, int entchannel) + { + //S_StartSound( origin, ENTITYNUM_NONE, CHAN_AUTO, sfxHandle, volume, radius ); + S_StartLocalSound(sfxHandle, entchannel); + } + inline int RegisterSound( const char *sound ) + { + return S_RegisterSound( sound ); + } + + // Physics/collision + inline void Trace( trace_t &tr, vec3_t start, vec3_t min, vec3_t max, vec3_t end, int skipEntNum, int flags ) + { + TCGTrace *td = (TCGTrace *)cl->mSharedMemory; + + if ( !min ) + { + min = vec3_origin; + } + + if ( !max ) + { + max = vec3_origin; + } + + // MATT - what was this? +// memset(td, sizeof(*td), 0); + memset(td, 0, sizeof(*td)); + + VectorCopy(start, td->mStart); + VectorCopy(min, td->mMins); + VectorCopy(max, td->mMaxs); + VectorCopy(end, td->mEnd); + td->mSkipNumber = skipEntNum; + td->mMask = flags; + + VM_Call( cgvm, CG_TRACE ); + + tr = td->mResult; + } + + inline void G2Trace( trace_t &tr, vec3_t start, vec3_t min, vec3_t max, vec3_t end, int skipEntNum, int flags ) + { + TCGTrace *td = (TCGTrace *)cl->mSharedMemory; + + if ( !min ) + { + min = vec3_origin; + } + + if ( !max ) + { + max = vec3_origin; + } + + memset(td, sizeof(*td), 0); + VectorCopy(start, td->mStart); + VectorCopy(min, td->mMins); + VectorCopy(max, td->mMaxs); + VectorCopy(end, td->mEnd); + td->mSkipNumber = skipEntNum; + td->mMask = flags; + + VM_Call( cgvm, CG_G2TRACE ); + + tr = td->mResult; + } + + inline void AddGhoul2Decal(int shader, vec3_t start, vec3_t dir, float size) + { + TCGG2Mark *td = (TCGG2Mark *)cl->mSharedMemory; + + td->size = size; + td->shader = shader; + VectorCopy(start, td->start); + VectorCopy(dir, td->dir); + + VM_Call(cgvm, CG_G2MARK); + } + + inline void AddFxToScene( refEntity_t *ent ) + { +#ifdef _DEBUG + mMainRefs++; + + assert(!ent || ent->renderfx >= 0); +#endif + re.AddRefEntityToScene( ent ); + } + inline void AddFxToScene( miniRefEntity_t *ent ) + { +#ifdef _DEBUG + mMiniRefs++; + + assert(!ent || ent->renderfx >= 0); +#endif + re.AddMiniRefEntityToScene( ent ); + } + + inline void AddLightToScene( vec3_t org, float radius, float red, float green, float blue ) + { + re.AddLightToScene( org, radius, red, green, blue ); + } + + + inline int RegisterShader( const char *shader ) + { + return re.RegisterShader( shader ); + } + inline int RegisterModel( const char *model ) + { + return re.RegisterModel( model ); + } + + inline void AddPolyToScene( int shader, int count, polyVert_t *verts ) + { + re.AddPolyToScene( shader, count, verts, 1 ); + } + + inline void AddDecalToScene ( qhandle_t shader, const vec3_t origin, const vec3_t dir, float orientation, float r, float g, float b, float a, qboolean alphaFade, float radius, qboolean temporary ) + { + re.AddDecalToScene ( shader, origin, dir, orientation, r, g, b, a, alphaFade, radius, temporary ); + } + + void CameraShake( vec3_t origin, float intensity, int radius, int time ); + qboolean SFxHelper::GetOriginAxisFromBolt(CGhoul2Info_v *pGhoul2, int mEntNum, int modelNum, int boltNum, vec3_t /*out*/origin, vec3_t /*out*/axis[3]); +}; + +extern SFxHelper theFxHelper; + +#endif // FX_SYSTEM_H_INC diff --git a/codemp/client/fxtemplate.cpp b/codemp/client/fxtemplate.cpp new file mode 100644 index 0000000..7ee3a25 --- /dev/null +++ b/codemp/client/fxtemplate.cpp @@ -0,0 +1,2386 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" +// this include must remain at the top of every CPP file +#include "client.h" + +#if !defined(FX_SCHEDULER_H_INC) + #include "FxScheduler.h" +#endif + +//------------------------------------------------------ +// CPrimitiveTemplate +// Set up our minimal default values +// +// Input: +// none +// +// Return: +// none +//------------------------------------------------------ +CPrimitiveTemplate::CPrimitiveTemplate() +{ + // We never start out as a copy or with a name + mCopy = false; + mName[0] = 0; + mCullRange = 0; // no distance culling + + mFlags = mSpawnFlags = 0; + mElasticity.SetRange(0.1f, 0.1f); + + mSoundVolume = -1; + mSoundRadius = -1; + + mMatImpactFX = MATIMPACTFX_NONE; + + mLife.SetRange( 50.0f, 50.0f ); + mSpawnCount.SetRange( 1.0f, 1.0f ); + mRadius.SetRange( 10.0f, 10.0f ); + mHeight.SetRange( 10.0f, 10.0f ); + mWindModifier.SetRange( 1.0f, 1.0f ); + + VectorSet( mMin, 0.0f, 0.0f, 0.0f ); + VectorSet( mMax, 0.0f, 0.0f, 0.0f ); + + mRedStart.SetRange( 1.0f, 1.0f ); + mGreenStart.SetRange( 1.0f, 1.0f ); + mBlueStart.SetRange( 1.0f, 1.0f ); + + mRedEnd.SetRange( 1.0f, 1.0f ); + mGreenEnd.SetRange( 1.0f, 1.0f ); + mBlueEnd.SetRange( 1.0f, 1.0f ); + + mAlphaStart.SetRange( 1.0f, 1.0f ); + mAlphaEnd.SetRange( 1.0f, 1.0f ); + + mSizeStart.SetRange( 1.0f, 1.0f ); + mSizeEnd.SetRange( 1.0f, 1.0f ); + + mSize2Start.SetRange( 1.0f, 1.0f ); + mSize2End.SetRange( 1.0f, 1.0f ); + + mLengthStart.SetRange( 1.0f, 1.0f ); + mLengthEnd.SetRange( 1.0f, 1.0f ); + + mTexCoordS.SetRange( 1.0f, 1.0f ); + mTexCoordT.SetRange( 1.0f, 1.0f ); + + mVariance.SetRange( 1.0f, 1.0f ); + mDensity.SetRange( 10.0f, 10.0f );// default this high so it doesn't do bad things +} + +//----------------------------------------------------------- +void CPrimitiveTemplate::operator=(const CPrimitiveTemplate &that) +{ + // I'm assuming that doing a memcpy wouldn't work here + // If you are looking at this and know a better way to do this, please tell me. + strcpy( mName, that.mName ); + + mType = that.mType; + + mSpawnDelay = that.mSpawnDelay; + mSpawnCount = that.mSpawnCount; + mLife = that.mLife; + mCullRange = that.mCullRange; + + mMediaHandles = that.mMediaHandles; + mImpactFxHandles = that.mImpactFxHandles; + mDeathFxHandles = that.mDeathFxHandles; + mEmitterFxHandles = that.mEmitterFxHandles; + mPlayFxHandles = that.mPlayFxHandles; + + mFlags = that.mFlags; + mSpawnFlags = that.mSpawnFlags; + + VectorCopy( that.mMin, mMin ); + VectorCopy( that.mMax, mMax ); + + mOrigin1X = that.mOrigin1X; + mOrigin1Y = that.mOrigin1Y; + mOrigin1Z = that.mOrigin1Z; + + mOrigin2X = that.mOrigin2X; + mOrigin2Y = that.mOrigin2Y; + mOrigin2Z = that.mOrigin2Z; + + mRadius = that.mRadius; + mHeight = that.mHeight; + mWindModifier = that.mWindModifier; + + mRotation = that.mRotation; + mRotationDelta = that.mRotationDelta; + + mAngle1 = that.mAngle1; + mAngle2 = that.mAngle2; + mAngle3 = that.mAngle3; + + mAngle1Delta = that.mAngle1Delta; + mAngle2Delta = that.mAngle2Delta; + mAngle3Delta = that.mAngle3Delta; + + mVelX = that.mVelX; + mVelY = that.mVelY; + mVelZ = that.mVelZ; + + mAccelX = that.mAccelX; + mAccelY = that.mAccelY; + mAccelZ = that.mAccelZ; + + mGravity = that.mGravity; + + mDensity = that.mDensity; + mVariance = that.mVariance; + + mRedStart = that.mRedStart; + mGreenStart = that.mGreenStart; + mBlueStart = that.mBlueStart; + + mRedEnd = that.mRedEnd; + mGreenEnd = that.mGreenEnd; + mBlueEnd = that.mBlueEnd; + + mRGBParm = that.mRGBParm; + + mAlphaStart = that.mAlphaStart; + mAlphaEnd = that.mAlphaEnd; + mAlphaParm = that.mAlphaParm; + + mSizeStart = that.mSizeStart; + mSizeEnd = that.mSizeEnd; + mSizeParm = that.mSizeParm; + + mSize2Start = that.mSize2Start; + mSize2End = that.mSize2End; + mSize2Parm = that.mSize2Parm; + + mLengthStart = that.mLengthStart; + mLengthEnd = that.mLengthEnd; + mLengthParm = that.mLengthParm; + + mTexCoordS = that.mTexCoordS; + mTexCoordT = that.mTexCoordT; + + mElasticity = that.mElasticity; + + mSoundRadius = that.mSoundRadius; + mSoundVolume = that.mSoundVolume; +} + +//------------------------------------------------------ +// ParseFloat +// Removes up to two values from a passed in string and +// sets these values into the passed in min and max +// fields. if no max is present, min is copied into it. +// +// input: +// string that contains up to two float values +// min & max are used to return the parse values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseFloat( const char *val, float *min, float *max ) +{ + // We don't allow passing in a null for either of the fields + if ( min == 0 || max == 0 ) + { // failue + return false; + } + + // attempt to read out the values + int v = sscanf( val, "%f %f", min, max ); + + if ( v == 0 ) + { // nothing was there, failure + return false; + } + else if ( v == 1 ) + { // only one field entered, this is ok, but we should copy min into max + *max = *min; + } + + return true; +} + + +//------------------------------------------------------ +// ParseVector +// Removes up to six values from a passed in string and +// sets these values into the passed in min and max vector +// fields. if no max is present, min is copied into it. +// +// input: +// string that contains up to six float values +// min & max are used to return the parse values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseVector( const char *val, vec3_t min, vec3_t max ) +{ + // we don't allow passing in a null + if ( min == 0 || max == 0 ) + { + return false; + } + + // attempt to read out our values + int v = sscanf( val, "%f %f %f %f %f %f", &min[0], &min[1], &min[2], &max[0], &max[1], &max[2] ); + + // Check for completeness + if ( v < 3 || v == 4 || v == 5 ) + { // not a complete value + return false; + } + else if ( v == 3 ) + { // only a min was entered, so copy the result into max + VectorCopy( min, max ); + } + + return true; +} + +//------------------------------------------------------ +// ParseGroupFlags +// Group flags are generic in nature, so we can easily +// use a generic function to parse them in, then the +// caller can shift them into the appropriate range. +// +// input: +// string that contains the flag strings +// *flags returns the set bit flags +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseGroupFlags( const char *val, int *flags ) +{ + // Must pass in a non-null pointer + if ( flags == 0 ) + { + return false; + } + + char flag[][32] = {"\0","\0","\0","0"}; + bool ok = true; + + // For a sub group, really you probably only have one or two flags set + int v = sscanf( val, "%s %s %s %s", flag[0], flag[1], flag[2], flag[3] ); + + // Clear out the flags field, then convert the flag string to an actual value ( use generic flags ) + *flags = 0; + + for ( int i = 0; i < 4; i++ ) + { + if ( i + 1 > v ) + { + return true; + } + + if ( !stricmp( flag[i], "linear" )) + { + *flags |= FX_LINEAR; + } + else if ( !stricmp( flag[i], "nonlinear" )) + { + *flags |= FX_NONLINEAR; + } + else if ( !stricmp( flag[i], "wave" )) + { + *flags |= FX_WAVE; + } + else if ( !stricmp( flag[i], "random" )) + { + *flags |= FX_RAND; + } + else if ( !stricmp( flag[i], "clamp" )) + { + *flags |= FX_CLAMP; + } + else + { // we have badness going on, but continue on in case there are any valid fields in here + ok = false; + } + } + + return ok; +} + +//------------------------------------------------------ +// ParseMin +// Reads in a min bounding box field in vector format +// +// input: +// string that contains three float values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseMin( const char *val ) +{ + vec3_t min; + + if ( ParseVector( val, min, min ) == true ) + { + VectorCopy( min, mMin ); + + // We assume that if a min is being set that we are using physics and a bounding box + mFlags |= (FX_USE_BBOX | FX_APPLY_PHYSICS); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseMax +// Reads in a max bounding box field in vector format +// +// input: +// string that contains three float values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseMax( const char *val ) +{ + vec3_t max; + + if ( ParseVector( val, max, max ) == true ) + { + VectorCopy( max, mMax ); + + // We assume that if a max is being set that we are using physics and a bounding box + mFlags |= (FX_USE_BBOX | FX_APPLY_PHYSICS); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseLife +// Reads in a ranged life value +// +// input: +// string that contains a float range ( two vals ) +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLife( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mLife.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseDelay +// Reads in a ranged delay value +// +// input: +// string that contains a float range ( two vals ) +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseDelay( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSpawnDelay.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseCount +// Reads in a ranged count value +// +// input: +// string that contains a float range ( two vals ) +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseCount( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSpawnCount.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseElasticity +// Reads in a ranged elasticity value +// +// input: +// string that contains a float range ( two vals ) +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseElasticity( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mElasticity.SetRange( min, max ); + + // We assume that if elasticity is set that we are using physics, but don't assume we are + // using a bounding box unless a min/max are explicitly set + mFlags |= FX_APPLY_PHYSICS; + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseOrigin1 +// Reads in an origin field in vector format +// +// input: +// string that contains three float values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseOrigin1( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mOrigin1X.SetRange( min[0], max[0] ); + mOrigin1Y.SetRange( min[1], max[1] ); + mOrigin1Z.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseOrigin2 +// Reads in an origin field in vector format +// +// input: +// string that contains three float values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseOrigin2( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mOrigin2X.SetRange( min[0], max[0] ); + mOrigin2Y.SetRange( min[1], max[1] ); + mOrigin2Z.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRadius +// Reads in a ranged radius value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRadius( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mRadius.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseHeight +// Reads in a ranged height value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseHeight( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mHeight.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseWindModifier +// Reads in a ranged wind modifier value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseWindModifier( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mWindModifier.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRotation +// Reads in a ranged rotation value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRotation( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == qtrue ) + { + mRotation.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRotationDelta +// Reads in a ranged rotationDelta value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRotationDelta( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == qtrue ) + { + mRotationDelta.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAngle +// Reads in a ranged angle field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAngle( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mAngle1.SetRange( min[0], max[0] ); + mAngle2.SetRange( min[1], max[1] ); + mAngle3.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAngleDelta +// Reads in a ranged angleDelta field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAngleDelta( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mAngle1Delta.SetRange( min[0], max[0] ); + mAngle2Delta.SetRange( min[1], max[1] ); + mAngle3Delta.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseVelocity +// Reads in a ranged velocity field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseVelocity( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mVelX.SetRange( min[0], max[0] ); + mVelY.SetRange( min[1], max[1] ); + mVelZ.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseFlags +// These are flags that are not specific to a group, +// rather, they are specific to the whole primitive. +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseFlags( const char *val ) +{ + char flag[][32] = {"\0","\0","\0","\0","\0","\0","\0"}; + bool ok = true; + + // For a primitive, really you probably only have two or less flags set + int v = sscanf( val, "%s %s %s %s %s %s %s", flag[0], flag[1], flag[2], flag[3], flag[4], flag[5], flag[6] ); + + for ( int i = 0; i < 7; i++ ) + { + if ( i + 1 > v ) + { + return true; + } + + if ( !stricmp( flag[i], "useModel" )) + { + mFlags |= FX_ATTACHED_MODEL; + } + else if ( !stricmp( flag[i], "useBBox" )) + { + mFlags |= FX_USE_BBOX; + } + else if ( !stricmp( flag[i], "usePhysics" )) + { + mFlags |= FX_APPLY_PHYSICS; + } + else if ( !stricmp( flag[i], "expensivePhysics" )) + { + mFlags |= FX_EXPENSIVE_PHYSICS; + } + //rww - begin g2 stuff + else if ( !stricmp( flag[i], "ghoul2Collision" )) + { + mFlags |= (FX_GHOUL2_TRACE|FX_APPLY_PHYSICS|FX_EXPENSIVE_PHYSICS); + } + else if ( !stricmp( flag[i], "ghoul2Decals" )) + { + mFlags |= FX_GHOUL2_DECALS; + } + //rww - end + else if ( !stricmp( flag[i], "impactKills" )) + { + mFlags |= FX_KILL_ON_IMPACT; + } + else if ( !stricmp( flag[i], "impactFx" )) + { + mFlags |= FX_IMPACT_RUNS_FX; + } + else if ( !stricmp( flag[i], "deathFx" )) + { + mFlags |= FX_DEATH_RUNS_FX; + } + else if ( !stricmp( flag[i], "useAlpha" )) + { + mFlags |= FX_USE_ALPHA; + } + else if ( !stricmp( flag[i], "emitFx" )) + { + mFlags |= FX_EMIT_FX; + } + else if ( !stricmp( flag[i], "depthHack" )) + { + mFlags |= FX_DEPTH_HACK; + } + else if ( !stricmp( flag[i], "relative" )) + { + mFlags |= FX_RELATIVE; + } + else if ( !stricmp( flag[i], "setShaderTime" )) + { + mFlags |= FX_SET_SHADER_TIME; + } + else if ( !stricmp( flag[i], "paperPhysics" )) + { + mFlags |= FX_PAPER_PHYSICS; //warning! shared flag. You use this with a cylinder and you can expect evilness to ensue + } + else if ( !stricmp( flag[i], "localizedFlash" )) + { + mFlags |= FX_LOCALIZED_FLASH; //warning! shared flag. You use this with a cylinder and you can expect evilness to ensue + } + else if ( !stricmp( flag[i], "playerView" )) + { + mFlags |= FX_PLAYER_VIEW; //warning! shared flag. You use this with a cylinder and you can expect evilness to ensue + } + else + { // we have badness going on, but continue on in case there are any valid fields in here + ok = false; + } + } + + return ok; +} + +//------------------------------------------------------ +// ParseSpawnFlags +// These kinds of flags control how things spawn. They +// never get passed on to a primitive. +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSpawnFlags( const char *val ) +{ + char flag[][32] = {"\0","\0","\0","\0","\0","\0","\0"}; + bool ok = true; + + // For a primitive, really you probably only have two or less flags set + int v = sscanf( val, "%s %s %s %s %s %s %s", flag[0], flag[1], flag[2], flag[3], flag[4], flag[5], flag[6] ); + + for ( int i = 0; i < 7; i++ ) + { + if ( i + 1 > v ) + { + return true; + } + + if ( !stricmp( flag[i], "org2fromTrace" )) + { + mSpawnFlags |= FX_ORG2_FROM_TRACE; + } + else if ( !stricmp( flag[i], "traceImpactFx" )) + { + mSpawnFlags |= FX_TRACE_IMPACT_FX; + } + else if ( !stricmp( flag[i], "org2isOffset" )) + { + mSpawnFlags |= FX_ORG2_IS_OFFSET; + } + else if ( !stricmp( flag[i], "cheapOrgCalc" )) + { + mSpawnFlags |= FX_CHEAP_ORG_CALC; + } + else if ( !stricmp( flag[i], "cheapOrg2Calc" )) + { + mSpawnFlags |= FX_CHEAP_ORG2_CALC; + } + else if ( !stricmp( flag[i], "absoluteVel" )) + { + mSpawnFlags |= FX_VEL_IS_ABSOLUTE; + } + else if ( !stricmp( flag[i], "absoluteAccel" )) + { + mSpawnFlags |= FX_ACCEL_IS_ABSOLUTE; + } + else if ( !stricmp( flag[i], "orgOnSphere" )) // sphere/ellipsoid + { + mSpawnFlags |= FX_ORG_ON_SPHERE; + } + else if ( !stricmp( flag[i], "orgOnCylinder" )) // cylinder/disk + { + mSpawnFlags |= FX_ORG_ON_CYLINDER; + } + else if ( !stricmp( flag[i], "axisFromSphere" )) + { + mSpawnFlags |= FX_AXIS_FROM_SPHERE; + } + else if ( !stricmp( flag[i], "randrotaroundfwd" )) + { + mSpawnFlags |= FX_RAND_ROT_AROUND_FWD; + } + else if ( !stricmp( flag[i], "evenDistribution" )) + { + mSpawnFlags |= FX_EVEN_DISTRIBUTION; + } + else if ( !stricmp( flag[i], "rgbComponentInterpolation" )) + { + mSpawnFlags |= FX_RGB_COMPONENT_INTERP; + } + else if ( !stricmp( flag[i], "affectedByWind" )) + { + mSpawnFlags |= FX_AFFECTED_BY_WIND; + } + else + { // we have badness going on, but continue on in case there are any valid fields in here + ok = false; + } + } + + return ok; +} + + + +bool CPrimitiveTemplate::ParseMaterialImpact(const char *val) +{ + if (!stricmp(val, "shellsound")) + { + mMatImpactFX = MATIMPACTFX_SHELLSOUND; + } + else + { + mMatImpactFX = MATIMPACTFX_NONE; + theFxHelper.Print( "CPrimitiveTemplate::ParseMaterialImpact -- unknown materialImpact type!\n" ); + return false; + } + return true; +} + + +//------------------------------------------------------ +// ParseAcceleration +// Reads in a ranged acceleration field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAcceleration( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mAccelX.SetRange( min[0], max[0] ); + mAccelY.SetRange( min[1], max[1] ); + mAccelZ.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseGravity +// Reads in a ranged gravity value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseGravity( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mGravity.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseDensity +// Reads in a ranged density value. Density is only +// for emitters that are calling effects...it basically +// specifies how often the emitter should emit fx. +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseDensity( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mDensity.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseVariance +// Reads in a ranged variance value. Variance is only +// valid for emitters that are calling effects... +// it basically determines the amount of slop in the +// density calculations +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseVariance( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mVariance.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRGBStart +// Reads in a ranged rgbStart field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRGBStart( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mRedStart.SetRange( min[0], max[0] ); + mGreenStart.SetRange( min[1], max[1] ); + mBlueStart.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRGBEnd +// Reads in a ranged rgbEnd field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRGBEnd( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mRedEnd.SetRange( min[0], max[0] ); + mGreenEnd.SetRange( min[1], max[1] ); + mBlueEnd.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRGBParm +// Reads in a ranged rgbParm field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRGBParm( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mRGBParm.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRGBFlags +// Reads in a set of rgbFlags in string format +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRGBFlags( const char *val ) +{ + int flags; + + if ( ParseGroupFlags( val, &flags ) == true ) + { + // Convert our generic flag values into type specific ones + mFlags |= ( flags << FX_RGB_SHIFT ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAlphaStart +// Reads in a ranged alphaStart field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAlphaStart( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mAlphaStart.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAlphaEnd +// Reads in a ranged alphaEnd field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAlphaEnd( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mAlphaEnd.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAlphaParm +// Reads in a ranged alphaParm field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAlphaParm( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mAlphaParm.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAlphaFlags +// Reads in a set of alphaFlags in string format +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAlphaFlags( const char *val ) +{ + int flags; + + if ( ParseGroupFlags( val, &flags ) == true ) + { + // Convert our generic flag values into type specific ones + mFlags |= ( flags << FX_ALPHA_SHIFT ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSizeStart +// Reads in a ranged sizeStart field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSizeStart( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSizeStart.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSizeEnd +// Reads in a ranged sizeEnd field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSizeEnd( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSizeEnd.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSizeParm +// Reads in a ranged sizeParm field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSizeParm( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSizeParm.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSizeFlags +// Reads in a set of sizeFlags in string format +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSizeFlags( const char *val ) +{ + int flags; + + if ( ParseGroupFlags( val, &flags ) == true ) + { + // Convert our generic flag values into type specific ones + mFlags |= ( flags << FX_SIZE_SHIFT ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSize2Start +// Reads in a ranged Size2Start field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize2Start( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSize2Start.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSize2End +// Reads in a ranged Size2End field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize2End( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSize2End.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSize2Parm +// Reads in a ranged Size2Parm field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize2Parm( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSize2Parm.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSize2Flags +// Reads in a set of Size2Flags in string format +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize2Flags( const char *val ) +{ + int flags; + + if ( ParseGroupFlags( val, &flags ) == true ) + { + // Convert our generic flag values into type specific ones + mFlags |= ( flags << FX_SIZE2_SHIFT ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseLengthStart +// Reads in a ranged lengthStart field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLengthStart( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mLengthStart.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseLengthEnd +// Reads in a ranged lengthEnd field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLengthEnd( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mLengthEnd.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseLengthParm +// Reads in a ranged lengthParm field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLengthParm( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mLengthParm.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseLengthFlags +// Reads in a set of lengthFlags in string format +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLengthFlags( const char *val ) +{ + int flags; + + if ( ParseGroupFlags( val, &flags ) == true ) + { + // Convert our generic flag values into type specific ones + mFlags |= ( flags << FX_LENGTH_SHIFT ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseShaders +// Reads in a group of shaders and registers them +// +// input: +// Parse group that contains the list of shaders to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseShaders( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + + handle = theFxHelper.RegisterShader( val ); + mMediaHandles.AddHandle( handle ); + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxHelper.RegisterShader( val ); + mMediaHandles.AddHandle( handle ); + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseShaders called with an empty list!\n" ); + return false; + } + } + + return true; +} + +//------------------------------------------------------ +// ParseSounds +// Reads in a group of sounds and registers them +// +// input: +// Parse group that contains the list of sounds to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSounds( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + + handle = theFxHelper.RegisterSound( val ); + mMediaHandles.AddHandle( handle ); + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxHelper.RegisterSound( val ); + mMediaHandles.AddHandle( handle ); + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseSounds called with an empty list!\n" ); + return false; + } + } + + return true; +} + +//------------------------------------------------------ +// ParseModels +// Reads in a group of models and registers them +// +// input: +// Parse group that contains the list of models to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseModels( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + + handle = theFxHelper.RegisterModel( val ); + mMediaHandles.AddHandle( handle ); + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxHelper.RegisterModel( val ); + mMediaHandles.AddHandle( handle ); + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseModels called with an empty list!\n" ); + return false; + } + } + + mFlags |= FX_ATTACHED_MODEL; + + return true; +} + +//------------------------------------------------------ +// ParseImpactFxStrings +// Reads in a group of fx file names and registers them +// +// input: +// Parse group that contains the list of fx to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseImpactFxStrings( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mImpactFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Impact effect file not found.\n" ); + return false; + } + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mImpactFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Impact effect file not found.\n" ); + return false; + } + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseImpactFxStrings called with an empty list!\n" ); + return false; + } + } + + mFlags |= FX_IMPACT_RUNS_FX | FX_APPLY_PHYSICS; + + return true; +} + +//------------------------------------------------------ +// ParseDeathFxStrings +// Reads in a group of fx file names and registers them +// +// input: +// Parse group that contains the list of fx to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseDeathFxStrings( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mDeathFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Death effect file not found.\n" ); + return false; + } + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mDeathFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Death effect file not found.\n" ); + return false; + } + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseDeathFxStrings called with an empty list!\n" ); + return false; + } + } + + mFlags |= FX_DEATH_RUNS_FX; + + return true; +} + +//------------------------------------------------------ +// ParseEmitterFxStrings +// Reads in a group of fx file names and registers them +// +// input: +// Parse group that contains the list of fx to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseEmitterFxStrings( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mEmitterFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Emitter effect file not found.\n" ); + return false; + } + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mEmitterFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Emitter effect file not found.\n" ); + return false; + } + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseEmitterFxStrings called with an empty list!\n" ); + return false; + } + } + + mFlags |= FX_EMIT_FX; + + return true; +} + +//------------------------------------------------------ +// ParsePlayFxStrings +// Reads in a group of fx file names and registers them +// +// input: +// Parse group that contains the list of fx to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParsePlayFxStrings( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mPlayFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Effect file not found.\n" ); + return false; + } + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mPlayFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Effect file not found.\n" ); + return false; + } + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParsePlayFxStrings called with an empty list!\n" ); + return false; + } + } + + return true; +} + +//------------------------------------------------------ +// ParseRGB +// Takes an RGB group and chomps out any pairs contained +// in it. +// +// input: +// the parse group to process +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRGB( CGPGroup *grp ) +{ + CGPValue *pairs; + const char *key; + const char *val; + + // Inside of the group, we should have a series of pairs + pairs = grp->GetPairs(); + + while( pairs ) + { + // Let's get the key field + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "start" )) + { + ParseRGBStart( val ); + } + else if ( !stricmp( key, "end" )) + { + ParseRGBEnd( val ); + } + else if ( !stricmp( key, "parm" ) || !stricmp( key, "parms" )) + { + ParseRGBParm( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { + ParseRGBFlags( val ); + } + else + { + theFxHelper.Print( "Unknown key parsing an RGB group: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + return true; +} + +//------------------------------------------------------ +// ParseAlpha +// Takes an alpha group and chomps out any pairs contained +// in it. +// +// input: +// the parse group to process +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAlpha( CGPGroup *grp ) +{ + CGPValue *pairs; + const char *key; + const char *val; + + // Inside of the group, we should have a series of pairs + pairs = grp->GetPairs(); + + while( pairs ) + { + // Let's get the key field + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "start" )) + { + ParseAlphaStart( val ); + } + else if ( !stricmp( key, "end" )) + { + ParseAlphaEnd( val ); + } + else if ( !stricmp( key, "parm" ) || !stricmp( key, "parms" )) + { + ParseAlphaParm( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { + ParseAlphaFlags( val ); + } + else + { + theFxHelper.Print( "Unknown key parsing an Alpha group: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + return true; +} + +//------------------------------------------------------ +// ParseSize +// Takes a size group and chomps out any pairs contained +// in it. +// +// input: +// the parse group to process +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize( CGPGroup *grp ) +{ + CGPValue *pairs; + const char *key; + const char *val; + + // Inside of the group, we should have a series of pairs + pairs = grp->GetPairs(); + + while( pairs ) + { + // Let's get the key field + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "start" )) + { + ParseSizeStart( val ); + } + else if ( !stricmp( key, "end" )) + { + ParseSizeEnd( val ); + } + else if ( !stricmp( key, "parm" ) || !stricmp( key, "parms" )) + { + ParseSizeParm( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { + ParseSizeFlags( val ); + } + else + { + theFxHelper.Print( "Unknown key parsing a Size group: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + return true; +} + +//------------------------------------------------------ +// ParseSize2 +// Takes a Size2 group and chomps out any pairs contained +// in it. +// +// input: +// the parse group to process +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize2( CGPGroup *grp ) +{ + CGPValue *pairs; + const char *key; + const char *val; + + // Inside of the group, we should have a series of pairs + pairs = grp->GetPairs(); + + while( pairs ) + { + // Let's get the key field + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "start" )) + { + ParseSize2Start( val ); + } + else if ( !stricmp( key, "end" )) + { + ParseSize2End( val ); + } + else if ( !stricmp( key, "parm" ) || !stricmp( key, "parms" )) + { + ParseSize2Parm( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { + ParseSize2Flags( val ); + } + else + { + theFxHelper.Print( "Unknown key parsing a Size2 group: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + return true; +} + +//------------------------------------------------------ +// ParseLength +// Takes a length group and chomps out any pairs contained +// in it. +// +// input: +// the parse group to process +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLength( CGPGroup *grp ) +{ + CGPValue *pairs; + const char *key; + const char *val; + + // Inside of the group, we should have a series of pairs + pairs = grp->GetPairs(); + + while( pairs ) + { + // Let's get the key field + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "start" )) + { + ParseLengthStart( val ); + } + else if ( !stricmp( key, "end" )) + { + ParseLengthEnd( val ); + } + else if ( !stricmp( key, "parm" ) || !stricmp( key, "parms" )) + { + ParseLengthParm( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { + ParseLengthFlags( val ); + } + else + { + theFxHelper.Print( "Unknown key parsing a Length group: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + return true; +} + + +// Parse a primitive, apply defaults first, grab any base level +// key pairs, then process any sub groups we may contain. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParsePrimitive( CGPGroup *grp ) +{ + CGPGroup *subGrp; + CGPValue *pairs; + const char *key; + const char *val; + + // Lets work with the pairs first + pairs = grp->GetPairs(); + + while( pairs ) + { + // the fields + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "count" )) + { + ParseCount( val ); + } + else if ( !stricmp( key, "shaders" ) || !stricmp( key, "shader" )) + { + ParseShaders( pairs ); + } + else if ( !stricmp( key, "models" ) || !stricmp( key, "model" )) + { + ParseModels( pairs ); + } + else if ( !stricmp( key, "sounds" ) || !stricmp( key, "sound" )) + { + ParseSounds( pairs ); + } + else if ( !stricmp( key, "impactfx" )) + { + ParseImpactFxStrings( pairs ); + } + else if ( !stricmp( key, "deathfx" )) + { + ParseDeathFxStrings( pairs ); + } + else if ( !stricmp( key, "emitfx" )) + { + ParseEmitterFxStrings( pairs ); + } + else if ( !stricmp( key, "playfx" )) + { + ParsePlayFxStrings( pairs ); + } + else if ( !stricmp( key, "life" )) + { + ParseLife( val ); + } + else if ( !stricmp( key, "delay" )) + { + ParseDelay( val ); + } + else if ( !stricmp( key, "cullrange" )) + { +// mCullRange = atoi( val ); +// mCullRange *= mCullRange; // square it now so we don't have to square every time we compare + } + else if ( !stricmp( key, "bounce" ) || !stricmp( key, "intensity" )) // me==bad for reusing this...but it shouldn't hurt anything) + { + ParseElasticity( val ); + } + else if ( !stricmp( key, "min" )) + { + ParseMin( val ); + } + else if ( !stricmp( key, "max" )) + { + ParseMax( val ); + } + else if ( !stricmp( key, "angle" ) || !stricmp( key, "angles" )) + { + ParseAngle( val ); + } + else if ( !stricmp( key, "angleDelta" )) + { + ParseAngleDelta( val ); + } + else if ( !stricmp( key, "velocity" ) || !stricmp( key, "vel" )) + { + ParseVelocity( val ); + } + else if ( !stricmp( key, "acceleration" ) || !stricmp( key, "accel" )) + { + ParseAcceleration( val ); + } + else if ( !stricmp( key, "gravity" )) + { + ParseGravity( val ); + } + else if ( !stricmp( key, "density" )) + { + ParseDensity( val ); + } + else if ( !stricmp( key, "variance" )) + { + ParseVariance( val ); + } + else if ( !stricmp( key, "origin" )) + { + ParseOrigin1( val ); + } + else if ( !stricmp( key, "origin2" )) + { + ParseOrigin2( val ); + } + else if ( !stricmp( key, "radius" )) // part of ellipse/cylinder calcs. + { + ParseRadius( val ); + } + else if ( !stricmp( key, "height" )) // part of ellipse/cylinder calcs. + { + ParseHeight( val ); + } + else if ( !stricmp( key, "wind" )) + { + ParseWindModifier( val ); + } + else if ( !stricmp( key, "rotation" )) + { + ParseRotation( val ); + } + else if ( !stricmp( key, "rotationDelta" )) + { + ParseRotationDelta( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { // these need to get passed on to the primitive + ParseFlags( val ); + } + else if ( !stricmp( key, "spawnFlags" ) || !stricmp( key, "spawnFlag" )) + { // these are used to spawn things in cool ways, but don't ever get passed on to prims. + ParseSpawnFlags( val ); + } + else if ( !stricmp( key, "name" )) + { + if ( val ) + { + // just stash the descriptive name of the primitive + strcpy( mName, val ); + } + } + else if (!stricmp(key, "materialImpact")) + { + ParseMaterialImpact(val); + } + else + { + theFxHelper.Print( "Unknown key parsing an effect primitive: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + subGrp = grp->GetSubGroups(); + + // Lets chomp on the groups now + while ( subGrp ) + { + key = subGrp->GetName(); + + if ( !stricmp( key, "rgb" )) + { + ParseRGB( subGrp ); + } + else if ( !stricmp( key, "alpha" )) + { + ParseAlpha( subGrp ); + } + else if ( !stricmp( key, "size" ) || !stricmp( key, "width" )) + { + ParseSize( subGrp ); + } + else if ( !stricmp( key, "size2" ) || !stricmp( key, "width2" )) + { + ParseSize2( subGrp ); + } + else if ( !stricmp( key, "length" ) || !stricmp( key, "height" )) + { + ParseLength( subGrp ); + } + else + { + theFxHelper.Print( "Unknown group key parsing a particle: %s\n", key ); + } + + subGrp = (CGPGroup *)subGrp->GetNext(); + } + + return true; +} diff --git a/codemp/client/fxutil.cpp b/codemp/client/fxutil.cpp new file mode 100644 index 0000000..08a2876 --- /dev/null +++ b/codemp/client/fxutil.cpp @@ -0,0 +1,1247 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" +// this include must remain at the top of every CPP file +#include "client.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "cl_data.h" +#endif + + +#if !defined(FX_SCHEDULER_H_INC) + #include "FxScheduler.h" +#endif + +vec3_t WHITE = {1.0f, 1.0f, 1.0f}; + +struct SEffectList +{ + CEffect *mEffect; + int mKillTime; + bool mPortal; +#ifdef _XBOX + int mClient; +#endif +}; + +#define PI 3.14159f + +SEffectList effectList[MAX_EFFECTS]; +SEffectList *nextValidEffect; +SFxHelper theFxHelper; + +int activeFx = 0; +int drawnFx; +qboolean fxInitialized = qfalse; + +//------------------------- +// FX_Free +// +// Frees all FX +//------------------------- +bool FX_Free( bool templates ) +{ + for ( int i = 0; i < MAX_EFFECTS; i++ ) + { + if ( effectList[i].mEffect ) + { + delete effectList[i].mEffect; + } + + effectList[i].mEffect = 0; + } + + activeFx = 0; + + theFxScheduler.Clean( templates ); + return true; +} + +//------------------------- +// FX_Stop +// +// Frees all active FX but leaves the templates +//------------------------- +void FX_Stop( void ) +{ + for ( int i = 0; i < MAX_EFFECTS; i++ ) + { + if ( effectList[i].mEffect ) + { + delete effectList[i].mEffect; + } + + effectList[i].mEffect = 0; + } + + activeFx = 0; + + theFxScheduler.Clean(false); +} + +//------------------------- +// FX_Init +// +// Preps system for use +//------------------------- +int FX_Init( refdef_t* refdef ) +{ +// FX_Free( true ); + if ( fxInitialized == qfalse ) + { + fxInitialized = qtrue; + + for ( int i = 0; i < MAX_EFFECTS; i++ ) + { + effectList[i].mEffect = 0; + } + } + nextValidEffect = &effectList[0]; + +#ifdef _SOF2DEV_ + fx_freeze = Cvar_Get("fx_freeze", "0", CVAR_CHEAT); +#endif + fx_debug = Cvar_Get("fx_debug", "0", CVAR_TEMP); + fx_countScale = Cvar_Get("fx_countScale", "1", CVAR_ARCHIVE); + fx_nearCull = Cvar_Get("fx_nearCull", "16", CVAR_ARCHIVE); + + theFxHelper.ReInit(refdef); + + return true; +} + +void FX_SetRefDef(refdef_t *refdef) +{ + theFxHelper.refdef = refdef; +} + +//------------------------- +// FX_FreeMember +//------------------------- +static void FX_FreeMember( SEffectList *obj ) +{ + obj->mEffect->Die(); + delete obj->mEffect; + obj->mEffect = 0; + + // May as well mark this to be used next + nextValidEffect = obj; + + activeFx--; +} + + +//------------------------- +// FX_GetValidEffect +// +// Finds an unused effect slot +// +// Note - in the editor, this function may return NULL, indicating that all +// effects are being stopped. +//------------------------- +static SEffectList *FX_GetValidEffect() +{ + if ( nextValidEffect->mEffect == 0 ) + { + return nextValidEffect; + } + + int i; + SEffectList *ef; + + // Blah..plow through the list till we find something that is currently untainted + for ( i = 0, ef = effectList; i < MAX_EFFECTS; i++, ef++ ) + { + if ( ef->mEffect == 0 ) + { + return ef; + } + } + + // report the error. +#ifndef FINAL_BUILD + theFxHelper.Print( "FX system out of effects\n" ); +#endif + + // Hmmm.. just trashing the first effect in the list is a poor approach + FX_FreeMember( &effectList[0] ); + + // Recursive call + return nextValidEffect; +} + +//------------------------- +// FX_Add +// +// Adds all fx to the view +//------------------------- +void FX_Add( bool portal ) +{ + int i; + SEffectList *ef; + + drawnFx = 0; + + int numFx = activeFx; //but stop when there can't be any more left! + for ( i = 0, ef = effectList; i < MAX_EFFECTS && numFx; i++, ef++ ) + { +#ifdef _XBOX + if ( ef->mEffect != 0 )//&& ef->mClient == ClientManager::ActiveClientNum() ) +#else + if ( ef->mEffect != 0) +#endif + { + --numFx; + if (portal != ef->mPortal) + { + continue; //this one does not render in this scene + } + // Effect is active + if ( theFxHelper.mTime > ef->mKillTime ) + { + // Clean up old effects, calling any death effects as needed + // this flag just has to be cleared otherwise death effects might not happen correctly + ef->mEffect->ClearFlags( FX_KILL_ON_IMPACT ); + FX_FreeMember( ef ); + } + else + { + if ( ef->mEffect->Update() == false ) + { + // We've been marked for death + FX_FreeMember( ef ); + continue; + } + } + } + } + + + if ( fx_debug->integer && !portal) + { + theFxHelper.Print( "Active FX: %i\n", activeFx ); + theFxHelper.Print( "Drawn FX: %i\n", drawnFx ); + theFxHelper.Print( "Scheduled FX: %i\n", theFxScheduler.NumScheduledFx() ); + } +} + + + +//------------------------- +// FX_AddPrimitive +// +// Note - in the editor, this function may change *pEffect to NULL, indicating that +// all effects are being stopped. +//------------------------- +extern bool gEffectsInPortal; //from FXScheduler.cpp so i don't have to pass it in on EVERY FX_ADD* +void FX_AddPrimitive( CEffect **pEffect, int killTime ) +{ + SEffectList *item = FX_GetValidEffect(); + + item->mEffect = *pEffect; + item->mKillTime = theFxHelper.mTime + killTime; + item->mPortal = gEffectsInPortal; //global set in AddScheduledEffects +#ifdef _XBOX + item->mClient = ClientManager::ActiveClientNum(); +#endif + + activeFx++; + + // Stash these in the primitive so it has easy access to the vals + (*pEffect)->SetTimeStart( theFxHelper.mTime ); + (*pEffect)->SetTimeEnd( theFxHelper.mTime + killTime ); +} + +//------------------------- +// FX_AddParticle +//------------------------- +CParticle *FX_AddParticle( vec3_t org, vec3_t vel, vec3_t accel, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + float rotation, float rotationDelta, + vec3_t min, vec3_t max, float elasticity, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags = 0, + EMatImpactEffect matImpactFX /*MATIMPACTFX_NONE*/, int fxParm /*-1*/, + int iGhoul2/*0*/, int entNum/*-1*/, int modelNum/*-1*/, int boltNum/*-1*/ ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding effects when the system is paused + return 0; + } + + CParticle *fx = new CParticle; + + if ( fx ) + { + if (flags&FX_RELATIVE && iGhoul2>0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( org ); + fx->SetBoltinfo( iGhoul2, entNum, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( org ); + } + fx->SetOrigin1( org ); + fx->SetMatImpactFX(matImpactFX); + fx->SetMatImpactParm(fxParm); + fx->SetVel( vel ); + fx->SetAccel( accel ); + + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetFlags( flags ); + fx->SetShader( shader ); + fx->SetRotation( rotation ); + fx->SetRotationDelta( rotationDelta ); + fx->SetElasticity( elasticity ); + fx->SetMin( min ); + fx->SetMax( max ); + fx->SetDeathFxID( deathID ); + fx->SetImpactFxID( impactID ); + + fx->Init(); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + + + +//------------------------- +// FX_AddLine +//------------------------- +CLine *FX_AddLine( vec3_t start, vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags = 0, + EMatImpactEffect matImpactFX /*MATIMPACTFX_NONE*/, int fxParm /*-1*/, + int iGhoul2/*0*/, int entNum/*-1*/, int modelNum/*-1*/, int boltNum/*-1*/) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding new effects when the system is paused + return 0; + } + + CLine *fx = new CLine; + + if ( fx ) + { + if (flags&FX_RELATIVE && iGhoul2>0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( start ); //offset from bolt pos + fx->SetVel( end ); //vel is the vector offset from bolt+orgOffset + fx->SetBoltinfo( iGhoul2, entNum, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( start ); + fx->SetOrigin2( end ); + } + fx->SetMatImpactFX(matImpactFX); + fx->SetMatImpactParm(fxParm); + + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetShader( shader ); + fx->SetFlags( flags ); + + fx->SetSTScale( 1.0f, 1.0f ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + + +//------------------------- +// FX_AddElectricity +//------------------------- +CElectricity *FX_AddElectricity( vec3_t start, vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + float chaos, int killTime, qhandle_t shader, int flags = 0, + EMatImpactEffect matImpactFX /*MATIMPACTFX_NONE*/, int fxParm /*-1*/, + int iGhoul2/*0*/, int entNum/*-1*/, int modelNum/*-1*/, int boltNum/*-1*/ ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding new effects when the system is paused + return 0; + } + + CElectricity *fx = new CElectricity; + + if ( fx ) + { + if (flags&FX_RELATIVE && iGhoul2>0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( start );//offset + fx->SetVel( end ); //vel is the vector offset from bolt+orgOffset + fx->SetBoltinfo( iGhoul2, entNum, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( start ); + fx->SetOrigin2( end ); + } + fx->SetMatImpactFX(matImpactFX); + fx->SetMatImpactParm(fxParm); + + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetShader( shader ); + fx->SetFlags( flags ); + fx->SetChaos( chaos ); + + fx->SetSTScale( 1.0f, 1.0f ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + // in the editor, fx may now be NULL? + if ( fx ) + { + fx->Initialize(); + } + } + + return fx; +} + + +//------------------------- +// FX_AddTail +//------------------------- +CTail *FX_AddTail( vec3_t org, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float length1, float length2, float lengthParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + vec3_t min, vec3_t max, float elasticity, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags = 0, + EMatImpactEffect matImpactFX /*MATIMPACTFX_NONE*/, int fxParm /*-1*/, + int iGhoul2/*0*/, int entNum/*-1*/, int modelNum/*-1*/, int boltNum/*-1*/ ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding effects when the system is paused + return 0; + } + + CTail *fx = new CTail; + + if ( fx ) + { + if (flags&FX_RELATIVE && iGhoul2>0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( org ); + fx->SetBoltinfo( iGhoul2, entNum, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( org ); + } + fx->SetMatImpactFX(matImpactFX); + fx->SetMatImpactParm(fxParm); + fx->SetVel( vel ); + fx->SetAccel( accel ); + + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Length---------------- + fx->SetLengthStart( length1 ); + fx->SetLengthEnd( length2 ); + + if (( flags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_WAVE ) + { + fx->SetLengthParm( lengthParm * PI * 0.001f ); + } + else if ( flags & FX_LENGTH_PARM_MASK ) + { + fx->SetLengthParm( lengthParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetFlags( flags ); + fx->SetShader( shader ); + fx->SetElasticity( elasticity ); + fx->SetMin( min ); + fx->SetMax( max ); + fx->SetSTScale( 1.0f, 1.0f ); + fx->SetDeathFxID( deathID ); + fx->SetImpactFxID( impactID ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + + +//------------------------- +// FX_AddCylinder +//------------------------- +CCylinder *FX_AddCylinder( vec3_t start, vec3_t normal, + float size1s, float size1e, float size1Parm, + float size2s, float size2e, float size2Parm, + float length1, float length2, float lengthParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + int killTime, qhandle_t shader, int flags, + EMatImpactEffect matImpactFX /*MATIMPACTFX_NONE*/, int fxParm /*-1*/, + int iGhoul2/*0*/, int entNum/*-1*/, int modelNum/*-1*/, int boltNum/*-1*/, + qboolean traceEnd) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding new effects when the system is paused + return 0; + } + + CCylinder *fx = new CCylinder; + + if ( fx ) + { + if (flags&FX_RELATIVE && iGhoul2>0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( start );//offset + fx->SetBoltinfo( iGhoul2, entNum, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( start ); + } + fx->SetTraceEnd(traceEnd); + + fx->SetMatImpactFX(matImpactFX); + fx->SetMatImpactParm(fxParm); + fx->SetOrigin1( start ); + fx->SetNormal( normal ); + + // RGB---------------- + fx->SetRGBStart( rgb1 ); + fx->SetRGBEnd( rgb2 ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size1---------------- + fx->SetSizeStart( size1s ); + fx->SetSizeEnd( size1e ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( size1Parm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( size1Parm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size2---------------- + fx->SetSize2Start( size2s ); + fx->SetSize2End( size2e ); + + if (( flags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_WAVE ) + { + fx->SetSize2Parm( size2Parm * PI * 0.001f ); + } + else if ( flags & FX_SIZE2_PARM_MASK ) + { + fx->SetSize2Parm( size2Parm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Length1--------------- + fx->SetLengthStart( length1 ); + fx->SetLengthEnd( length2 ); + + if (( flags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_WAVE ) + { + fx->SetLengthParm( lengthParm * PI * 0.001f ); + } + else if ( flags & FX_LENGTH_PARM_MASK ) + { + fx->SetLengthParm( lengthParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetShader( shader ); + fx->SetFlags( flags ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + +//------------------------- +// FX_AddEmitter +//------------------------- +CEmitter *FX_AddEmitter( vec3_t org, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + vec3_t angs, vec3_t deltaAngs, + vec3_t min, vec3_t max, float elasticity, + int deathID, int impactID, int emitterID, + float density, float variance, + int killTime, qhandle_t model, int flags = 0, + EMatImpactEffect matImpactFX /*MATIMPACTFX_NONE*/, int fxParm /*-1*/, + int iGhoul2/*0*/, int entNum/*-1*/, int modelNum/*-1*/, int boltNum/*-1*/ ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding effects when the system is paused + return 0; + } + + CEmitter *fx = new CEmitter; + + if ( fx ) + { + if (flags&FX_RELATIVE && iGhoul2>0) + { + assert(0);//not done +// fx->SetBoltinfo( iGhoul2, entNum, modelNum, boltNum ); + } + fx->SetMatImpactFX(matImpactFX); + fx->SetMatImpactParm(fxParm); + fx->SetOrigin1( org ); + fx->SetVel( vel ); + fx->SetAccel( accel ); + + // RGB---------------- + fx->SetRGBStart( rgb1 ); + fx->SetRGBEnd( rgb2 ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetAngles( angs ); + fx->SetAngleDelta( deltaAngs ); + fx->SetFlags( flags ); + fx->SetModel( model ); + fx->SetElasticity( elasticity ); + fx->SetMin( min ); + fx->SetMax( max ); + fx->SetDeathFxID( deathID ); + fx->SetImpactFxID( impactID ); + fx->SetEmitterFxID( emitterID ); + fx->SetDensity( density ); + fx->SetVariance( variance ); + fx->SetOldTime( theFxHelper.mTime ); + + fx->SetLastOrg( org ); + fx->SetLastVel( vel ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + +//------------------------- +// FX_AddLight +//------------------------- +CLight *FX_AddLight( vec3_t org, float size1, float size2, float sizeParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + int killTime, int flags = 0, + EMatImpactEffect matImpactFX /*MATIMPACTFX_NONE*/, int fxParm /*-1*/, + int iGhoul2/*0*/, int entNum/*-1*/, int modelNum/*-1*/, int boltNum/*-1*/) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding effects when the system is paused + return 0; + } + + CLight *fx = new CLight; + + if ( fx ) + { + if (flags&FX_RELATIVE && iGhoul2>0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( org );//offset + fx->SetBoltinfo( iGhoul2, entNum, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( org ); + } + fx->SetMatImpactFX(matImpactFX); + fx->SetMatImpactParm(fxParm); + + // RGB---------------- + fx->SetRGBStart( rgb1 ); + fx->SetRGBEnd( rgb2 ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetFlags( flags ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; + +} + + +//------------------------- +// FX_AddOrientedParticle +//------------------------- +COrientedParticle *FX_AddOrientedParticle( vec3_t org, vec3_t norm, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + float rotation, float rotationDelta, + vec3_t min, vec3_t max, float bounce, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags = 0, + EMatImpactEffect matImpactFX /*MATIMPACTFX_NONE*/, int fxParm /*-1*/, + int iGhoul2/*0*/, int entNum/*-1*/, int modelNum/*-1*/, int boltNum/*-1*/ ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding effects when the system is paused + return 0; + } + + COrientedParticle *fx = new COrientedParticle; + + if ( fx ) + { + if (flags&FX_RELATIVE && iGhoul2>0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( org );//offset + fx->SetBoltinfo( iGhoul2, entNum, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( org ); + } + fx->SetMatImpactFX(matImpactFX); + fx->SetMatImpactParm(fxParm); + fx->SetOrigin1( org ); + fx->SetNormal( norm ); + fx->SetVel( vel ); + fx->SetAccel( accel ); + + // RGB---------------- + fx->SetRGBStart( rgb1 ); + fx->SetRGBEnd( rgb2 ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetFlags( flags ); + fx->SetShader( shader ); + fx->SetRotation( rotation ); + fx->SetRotationDelta( rotationDelta ); + fx->SetElasticity( bounce ); + fx->SetMin( min ); + fx->SetMax( max ); + fx->SetDeathFxID( deathID ); + fx->SetImpactFxID( impactID ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + + +//------------------------- +// FX_AddPoly +//------------------------- +CPoly *FX_AddPoly( vec3_t *verts, vec2_t *st, int numVerts, + vec3_t vel, vec3_t accel, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + vec3_t rotationDelta, float bounce, int motionDelay, + int killTime, qhandle_t shader, int flags ) +{ + if ( theFxHelper.mFrameTime < 1 || !verts ) + { // disallow adding effects when the system is paused or the user doesn't pass in a vert array + return 0; + } + + CPoly *fx = new CPoly; + + if ( fx ) + { + // Do a cheesy copy of the verts and texture coords into our own structure + for ( int i = 0; i < numVerts; i++ ) + { + VectorCopy( verts[i], fx->mOrg[i] ); + Vector2Copy( st[i], fx->mST[i] ); + } + + fx->SetVel( vel ); + fx->SetAccel( accel ); + + // RGB---------------- + fx->SetRGBStart( rgb1 ); + fx->SetRGBEnd( rgb2 ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetFlags( flags ); + fx->SetShader( shader ); + fx->SetRot( rotationDelta ); + fx->SetElasticity( bounce ); + fx->SetMotionTimeStamp( motionDelay ); + fx->SetNumVerts( numVerts ); + + // Now that we've set our data up, let's process it into a useful format + fx->PolyInit(); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + + +//------------------------- +// FX_AddFlash +//------------------------- +CFlash *FX_AddFlash( vec3_t origin, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags, + EMatImpactEffect matImpactFX /*MATIMPACTFX_NONE*/, int fxParm /*-1*/ ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding new effects when the system is paused + return 0; + } + + if (!shader) + { //yeah..this is bad, I guess, but SP seems to handle it by not drawing the flash, so I will too. + assert(shader); + return 0; + } + + CFlash *fx = new CFlash; + + if ( fx ) + { + fx->SetMatImpactFX(matImpactFX); + fx->SetMatImpactParm(fxParm); + fx->SetOrigin1( origin ); + + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetShader( shader ); + fx->SetFlags( flags ); + +// fx->SetSTScale( 1.0f, 1.0f ); + + fx->Init(); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + +//------------------------- +// FX_AddBezier +//------------------------- +CBezier *FX_AddBezier( vec3_t start, vec3_t end, + vec3_t control1, vec3_t control1Vel, + vec3_t control2, vec3_t control2Vel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding new effects when the system is paused + return 0; + } + + CBezier *fx = new CBezier; + + if ( fx ) + { + fx->SetOrigin1( start ); + fx->SetOrigin2( end ); + + fx->SetControlPoints( control1, control2 ); + fx->SetControlVel( control1Vel, control2Vel ); + + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetShader( shader ); + fx->SetFlags( flags ); + + fx->SetSTScale( 1.0f, 1.0f ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} diff --git a/codemp/client/fxutil.h b/codemp/client/fxutil.h new file mode 100644 index 0000000..d6a68dc --- /dev/null +++ b/codemp/client/fxutil.h @@ -0,0 +1,116 @@ + +#if !defined(FX_PRIMITIVES_H_INC) + #include "FxPrimitives.h" +#endif + +#ifndef FX_UTIL_H_INC +#define FX_UTIL_H_INC + + +bool FX_Free( bool templates ); // ditches all active effects; +int FX_Init( refdef_t* refdef ); // called in CG_Init to purge the fx system. +void FX_SetRefDef(refdef_t *refdef); +void FX_Add( bool portal ); // called every cgame frame to add all fx into the scene. +void FX_Stop( void ); // ditches all active effects without touching the templates. + + +CParticle *FX_AddParticle( vec3_t org, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + float rotation, float rotationDelta, + vec3_t min, vec3_t max, float elasticity, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags, + EMatImpactEffect matImpactFX = MATIMPACTFX_NONE, int fxParm = -1, + int iGhoul2=0, int entNum=-1, int modelNum=-1, int boltNum=-1); + +CLine *FX_AddLine( vec3_t start, vec3_t end, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + int killTime, qhandle_t shader, int flags, + EMatImpactEffect matImpactFX = MATIMPACTFX_NONE, int fxParm = -1, + int iGhoul2=0, int entNum=-1, int modelNum=-1, int boltNum=-1); + +CElectricity *FX_AddElectricity( vec3_t start, vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + float chaos, int killTime, qhandle_t shader, int flags, + EMatImpactEffect matImpactFX = MATIMPACTFX_NONE, int fxParm = -1, + int iGhoul2=0, int entNum=-1, int modelNum=-1, int boltNum=-1); + +CTail *FX_AddTail( vec3_t org, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float length1, float length2, float lengthParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + vec3_t min, vec3_t max, float elasticity, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags, + EMatImpactEffect matImpactFX = MATIMPACTFX_NONE, int fxParm = -1, + int iGhoul2=0, int entNum=-1, int modelNum=-1, int boltNum=-1); + +CCylinder *FX_AddCylinder( vec3_t start, vec3_t normal, + float size1s, float size1e, float size1Parm, + float size2s, float size2e, float size2Parm, + float length1, float length2, float lengthParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + int killTime, qhandle_t shader, int flags, + EMatImpactEffect matImpactFX = MATIMPACTFX_NONE, int fxParm = -1, + int iGhoul2=0, int entNum=-1, int modelNum=-1, int boltNum=-1, + qboolean traceEnd = qfalse); + +CEmitter *FX_AddEmitter( vec3_t org, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + vec3_t angs, vec3_t deltaAngs, + vec3_t min, vec3_t max, float elasticity, + int deathID, int impactID, int emitterID, + float density, float variance, + int killTime, qhandle_t model, int flags, + EMatImpactEffect matImpactFX = MATIMPACTFX_NONE, int fxParm = -1, + int iGhoul2=0, int entNum=-1, int modelNum=-1, int boltNum=-1); + +CLight *FX_AddLight( vec3_t org, float size1, float size2, float sizeParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + int killTime, int flags, + EMatImpactEffect matImpactFX = MATIMPACTFX_NONE, int fxParm = -1, + int iGhoul2=0, int entNum=-1, int modelNum=-1, int boltNum=-1); + +COrientedParticle *FX_AddOrientedParticle( vec3_t org, vec3_t norm, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + float rotation, float rotationDelta, + vec3_t min, vec3_t max, float bounce, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags, + EMatImpactEffect matImpactFX = MATIMPACTFX_NONE, int fxParm = -1, + int iGhoul2=0, int entNum=-1, int modelNum=-1, int boltNum=-1); + +CPoly *FX_AddPoly( vec3_t *verts, vec2_t *st, int numVerts, + vec3_t vel, vec3_t accel, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + vec3_t rotationDelta, float bounce, int motionDelay, + int killTime, qhandle_t shader, int flags ); + +CFlash *FX_AddFlash( vec3_t origin, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags = 0, + EMatImpactEffect matImpactFX = MATIMPACTFX_NONE, int fxParm = -1); + +CBezier *FX_AddBezier( vec3_t start, vec3_t end, + vec3_t control1, vec3_t control1Vel, + vec3_t control2, vec3_t control2Vel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags = 0 ); + +#endif //FX_UTIL_H_INC \ No newline at end of file diff --git a/codemp/client/keycodes.h b/codemp/client/keycodes.h new file mode 100644 index 0000000..0b362a4 --- /dev/null +++ b/codemp/client/keycodes.h @@ -0,0 +1,347 @@ +#ifndef __KEYCODES_H__ +#define __KEYCODES_H__ + +// these are the key numbers that should be passed to KeyEvent + +typedef enum +{ + A_NULL = 0, + A_SHIFT, + A_CTRL, + A_ALT, + A_CAPSLOCK, + A_NUMLOCK, + A_SCROLLLOCK, + A_PAUSE, + A_BACKSPACE, + A_TAB, + A_ENTER, + A_KP_PLUS, + A_KP_MINUS, + A_KP_ENTER, + A_KP_PERIOD, + A_PRINTSCREEN, + A_KP_0, + A_KP_1, + A_KP_2, + A_KP_3, + A_KP_4, + A_KP_5, + A_KP_6, + A_KP_7, + A_KP_8, + A_KP_9, + A_CONSOLE, + A_ESCAPE, + A_F1, + A_F2, + A_F3, + A_F4, + + A_SPACE, + A_PLING, + A_DOUBLE_QUOTE, + A_HASH, + A_STRING, + A_PERCENT, + A_AND, + A_SINGLE_QUOTE, + A_OPEN_BRACKET, + A_CLOSE_BRACKET, + A_STAR, + A_PLUS, + A_COMMA, + A_MINUS, + A_PERIOD, + A_FORWARD_SLASH, + A_0, + A_1, + A_2, + A_3, + A_4, + A_5, + A_6, + A_7, + A_8, + A_9, + A_COLON, + A_SEMICOLON, + A_LESSTHAN, + A_EQUALS, + A_GREATERTHAN, + A_QUESTION, + + A_AT, + A_CAP_A, + A_CAP_B, + A_CAP_C, + A_CAP_D, + A_CAP_E, + A_CAP_F, + A_CAP_G, + A_CAP_H, + A_CAP_I, + A_CAP_J, + A_CAP_K, + A_CAP_L, + A_CAP_M, + A_CAP_N, + A_CAP_O, + A_CAP_P, + A_CAP_Q, + A_CAP_R, + A_CAP_S, + A_CAP_T, + A_CAP_U, + A_CAP_V, + A_CAP_W, + A_CAP_X, + A_CAP_Y, + A_CAP_Z, + A_OPEN_SQUARE, + A_BACKSLASH, + A_CLOSE_SQUARE, + A_CARET, + A_UNDERSCORE, + + A_LEFT_SINGLE_QUOTE, + A_LOW_A, + A_LOW_B, + A_LOW_C, + A_LOW_D, + A_LOW_E, + A_LOW_F, + A_LOW_G, + A_LOW_H, + A_LOW_I, + A_LOW_J, + A_LOW_K, + A_LOW_L, + A_LOW_M, + A_LOW_N, + A_LOW_O, + A_LOW_P, + A_LOW_Q, + A_LOW_R, + A_LOW_S, + A_LOW_T, + A_LOW_U, + A_LOW_V, + A_LOW_W, + A_LOW_X, + A_LOW_Y, + A_LOW_Z, + A_OPEN_BRACE, + A_BAR, + A_CLOSE_BRACE, + A_TILDE, + A_DELETE, + + A_EURO, + A_SHIFT2, + A_CTRL2, + A_ALT2, + A_F5, + A_F6, + A_F7, + A_F8, + A_CIRCUMFLEX, + A_MWHEELUP, + A_CAP_SCARON, + A_MWHEELDOWN, + A_CAP_OE, + A_MOUSE1, + A_MOUSE2, + A_INSERT, + A_HOME, + A_PAGE_UP, + A_RIGHT_SINGLE_QUOTE, + A_LEFT_DOUBLE_QUOTE, + A_RIGHT_DOUBLE_QUOTE, + A_F9, + A_F10, + A_F11, + A_F12, + A_TRADEMARK, + A_LOW_SCARON, + A_SHIFT_ENTER, + A_LOW_OE, + A_END, + A_PAGE_DOWN, + A_CAP_YDIERESIS, + + A_SHIFT_SPACE, + A_EXCLAMDOWN, + A_CENT, + A_POUND, + A_SHIFT_KP_ENTER, + A_YEN, + A_MOUSE3, + A_MOUSE4, + A_MOUSE5, + A_COPYRIGHT, + A_CURSOR_UP, + A_CURSOR_DOWN, + A_CURSOR_LEFT, + A_CURSOR_RIGHT, + A_REGISTERED, + A_UNDEFINED_7, + A_UNDEFINED_8, + A_UNDEFINED_9, + A_UNDEFINED_10, + A_UNDEFINED_11, + A_UNDEFINED_12, + A_UNDEFINED_13, + A_UNDEFINED_14, + A_UNDEFINED_15, + A_UNDEFINED_16, + A_UNDEFINED_17, + A_UNDEFINED_18, + A_UNDEFINED_19, + A_UNDEFINED_20, + A_UNDEFINED_21, + A_UNDEFINED_22, + A_QUESTION_DOWN, + + A_CAP_AGRAVE, + A_CAP_AACUTE, + A_CAP_ACIRCUMFLEX, + A_CAP_ATILDE, + A_CAP_ADIERESIS, + A_CAP_ARING, + A_CAP_AE, + A_CAP_CCEDILLA, + A_CAP_EGRAVE, + A_CAP_EACUTE, + A_CAP_ECIRCUMFLEX, + A_CAP_EDIERESIS, + A_CAP_IGRAVE, + A_CAP_IACUTE, + A_CAP_ICIRCUMFLEX, + A_CAP_IDIERESIS, + A_CAP_ETH, + A_CAP_NTILDE, + A_CAP_OGRAVE, + A_CAP_OACUTE, + A_CAP_OCIRCUMFLEX, + A_CAP_OTILDE, + A_CAP_ODIERESIS, + A_MULTIPLY, + A_CAP_OSLASH, + A_CAP_UGRAVE, + A_CAP_UACUTE, + A_CAP_UCIRCUMFLEX, + A_CAP_UDIERESIS, + A_CAP_YACUTE, + A_CAP_THORN, + A_GERMANDBLS, + + A_LOW_AGRAVE, + A_LOW_AACUTE, + A_LOW_ACIRCUMFLEX, + A_LOW_ATILDE, + A_LOW_ADIERESIS, + A_LOW_ARING, + A_LOW_AE, + A_LOW_CCEDILLA, + A_LOW_EGRAVE, + A_LOW_EACUTE, + A_LOW_ECIRCUMFLEX, + A_LOW_EDIERESIS, + A_LOW_IGRAVE, + A_LOW_IACUTE, + A_LOW_ICIRCUMFLEX, + A_LOW_IDIERESIS, + A_LOW_ETH, + A_LOW_NTILDE, + A_LOW_OGRAVE, + A_LOW_OACUTE, + A_LOW_OCIRCUMFLEX, + A_LOW_OTILDE, + A_LOW_ODIERESIS, + A_DIVIDE, + A_LOW_OSLASH, + A_LOW_UGRAVE, + A_LOW_UACUTE, + A_LOW_UCIRCUMFLEX, + A_LOW_UDIERESIS, + A_LOW_YACUTE, + A_LOW_THORN, + A_LOW_YDIERESIS, + + A_JOY0, + A_JOY1, + A_JOY2, + A_JOY3, + A_JOY4, + A_JOY5, + A_JOY6, + A_JOY7, + A_JOY8, + A_JOY9, + A_JOY10, + A_JOY11, + A_JOY12, + A_JOY13, + A_JOY14, + A_JOY15, + A_JOY16, + A_JOY17, + A_JOY18, + A_JOY19, + A_JOY20, + A_JOY21, + A_JOY22, + A_JOY23, + A_JOY24, + A_JOY25, + A_JOY26, + A_JOY27, + A_JOY28, + A_JOY29, + A_JOY30, + A_JOY31, + + A_AUX0, + A_AUX1, + A_AUX2, + A_AUX3, + A_AUX4, + A_AUX5, + A_AUX6, + A_AUX7, + A_AUX8, + A_AUX9, + A_AUX10, + A_AUX11, + A_AUX12, + A_AUX13, + A_AUX14, + A_AUX15, + A_AUX16, + A_AUX17, + A_AUX18, + A_AUX19, + A_AUX20, + A_AUX21, + A_AUX22, + A_AUX23, + A_AUX24, + A_AUX25, + A_AUX26, + A_AUX27, + A_AUX28, + A_AUX29, + A_AUX30, + A_AUX31, + + MAX_KEYS +} fakeAscii_t; + + +// The menu code needs to get both key and char events, but +// to avoid duplicating the paths, the char events are just +// distinguished by or'ing in K_CHAR_FLAG (ugly) +#define K_CHAR_FLAG 1024 + +#endif diff --git a/codemp/client/keys.h b/codemp/client/keys.h new file mode 100644 index 0000000..5c5817a --- /dev/null +++ b/codemp/client/keys.h @@ -0,0 +1,64 @@ +#include "../ui/keycodes.h" + +typedef struct { + qboolean down; + int repeats; // if > 1, it is autorepeating + char *binding; +} qkey_t; + +#define MAX_EDIT_LINE 256 +#define COMMAND_HISTORY 32 + +typedef struct { + int cursor; + int scroll; + int widthInChars; + char buffer[MAX_EDIT_LINE]; +} field_t; + +typedef struct keyGlobals_s +{ + field_t historyEditLines[COMMAND_HISTORY]; + + int nextHistoryLine; // the last line in the history buffer, not masked + int historyLine; // the line being displayed from history buffer + // will be <= nextHistoryLine + field_t g_consoleField; + + qboolean anykeydown; + qboolean key_overstrikeMode; + int keyDownCount; + + qkey_t keys[MAX_KEYS]; +} keyGlobals_t; + + +typedef struct +{ + word upper; + word lower; + char *name; + int keynum; + bool menukey; +} keyname_t; + +extern keyGlobals_t kg; +extern keyname_t keynames[MAX_KEYS]; + + +void Field_Clear( field_t *edit ); +void Field_KeyDownEvent( field_t *edit, int key ); +void Field_CharEvent( field_t *edit, int ch ); + +extern field_t chatField; +extern qboolean chat_team; +extern int chat_playerNum; + +void Key_WriteBindings( fileHandle_t f ); +void Key_SetBinding( int keynum, const char *binding ); +char *Key_GetBinding( int keynum ); +qboolean Key_IsDown( int keynum ); +qboolean Key_GetOverstrikeMode( void ); +void Key_SetOverstrikeMode( qboolean state ); +void Key_ClearStates( void ); +int Key_GetKey(const char *binding); diff --git a/codemp/client/openal/al.h b/codemp/client/openal/al.h new file mode 100644 index 0000000..4f416db --- /dev/null +++ b/codemp/client/openal/al.h @@ -0,0 +1,564 @@ +#ifndef _AL_H_ +#define _AL_H_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ +#include "altypes.h" + +#ifdef _XBOX + #define ALAPI + #define ALAPIENTRY + #define AL_CALLBACK +#else + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 + #ifdef _OPENAL32LIB + #define ALAPI __declspec(dllexport) + #else + #define ALAPI __declspec(dllimport) + #endif + #define ALAPIENTRY __cdecl + #define AL_CALLBACK +#else + #ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export on + #endif + #endif + #define ALAPI + #define ALAPIENTRY __cdecl + #define AL_CALLBACK +#endif +#endif + +#define OPENAL + +#ifndef AL_NO_PROTOTYPES + +/** + * OpenAL Maintenance Functions + * Initialization and exiting. + * State Management and Query. + * Error Handling. + * Extension Support. + */ + +/** State management. */ +ALAPI ALvoid ALAPIENTRY alEnable( ALenum capability ); +ALAPI ALvoid ALAPIENTRY alDisable( ALenum capability ); +ALAPI ALboolean ALAPIENTRY alIsEnabled( ALenum capability ); + +/** Application preferences for driver performance choices. */ +ALAPI ALvoid ALAPIENTRY alHint( ALenum target, ALenum mode ); + +/** State retrieval. */ +ALAPI ALboolean ALAPIENTRY alGetBoolean( ALenum param ); +ALAPI ALint ALAPIENTRY alGetInteger( ALenum param ); +ALAPI ALfloat ALAPIENTRY alGetFloat( ALenum param ); +ALAPI ALdouble ALAPIENTRY alGetDouble( ALenum param ); +ALAPI ALvoid ALAPIENTRY alGetBooleanv( ALenum param, ALboolean* data ); +ALAPI ALvoid ALAPIENTRY alGetIntegerv( ALenum param, ALint* data ); +ALAPI ALvoid ALAPIENTRY alGetFloatv( ALenum param, ALfloat* data ); +ALAPI ALvoid ALAPIENTRY alGetDoublev( ALenum param, ALdouble* data ); +ALAPI ALubyte* ALAPIENTRY alGetString( ALenum param ); + +/** + * Error support. + * Obtain the most recent error generated in the AL state machine. + */ +ALAPI ALenum ALAPIENTRY alGetError( ALvoid ); + +#ifdef _XBOX +/** + * Update cycle. + */ +ALAPI ALvoid ALAPIENTRY alUpdate( ALvoid ); + +/** + * Returns a global state parameter. + */ +ALAPI ALvoid ALAPIENTRY alGeti( ALenum param, ALint* value ); + +/** + * Adjust the size of the sound buffer pool. + */ +ALAPI ALvoid ALAPIENTRY alResizePool( ALuint size ); +#endif + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALboolean ALAPIENTRY alIsExtensionPresent( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALvoid* ALAPIENTRY alGetProcAddress( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the integer value of an enumeration (usually an extension) with the name ename. + */ +ALAPI ALenum ALAPIENTRY alGetEnumValue( ALubyte* ename ); + + + + +/** + * LISTENER + * Listener is the sample position for a given context. + * The multi-channel (usually stereo) output stream generated + * by the mixer is parametrized by this Listener object: + * its position and velocity relative to Sources, within + * occluder and reflector geometry. + */ + + + +#ifdef _XBOX +/** + * Listener create and delete + */ +ALAPI ALvoid ALAPIENTRY alGenListeners( ALsizei n, ALuint* listeners ); +ALAPI ALvoid ALAPIENTRY alDeleteListeners( ALsizei n, ALuint* listeners ); +#endif + +// VV's Console version of openAL includes multiple listener support, +// as an extra first arg to the alListener functions. We wrap that up +// in a macro here, to make the following more concise. +#ifdef _XBOX +#define AL_VV_LISTENER ALuint listener, +#else +#define AL_VV_LISTENER +#endif + +/** + * + * Listener Environment: default 0. + */ +ALAPI ALvoid ALAPIENTRY alListeneri( AL_VV_LISTENER ALenum param, ALint value ); + + +/** + * + * Listener Gain: default 1.0f. + */ +ALAPI ALvoid ALAPIENTRY alListenerf( AL_VV_LISTENER ALenum param, ALfloat value ); + + +/** + * + * Listener Position. + * Listener Velocity. + */ +ALAPI ALvoid ALAPIENTRY alListener3f( AL_VV_LISTENER ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); + + +/** + * + * Listener Position: ALfloat[3] + * Listener Velocity: ALfloat[3] + * Listener Orientation: ALfloat[6] (forward and up vector). + */ +ALAPI ALvoid ALAPIENTRY alListenerfv( AL_VV_LISTENER ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY alGetListeneri( AL_VV_LISTENER ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY alGetListenerf( AL_VV_LISTENER ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY alGetListener3f( AL_VV_LISTENER ALenum param, ALfloat* v1, ALfloat* v2, ALfloat* v3 ); +ALAPI ALvoid ALAPIENTRY alGetListenerfv( AL_VV_LISTENER ALenum param, ALfloat* values ); + + +/** + * SOURCE + * Source objects are by default localized. Sources + * take the PCM data provided in the specified Buffer, + * apply Source-specific modifications, and then + * submit them to be mixed according to spatial + * arrangement etc. + */ + + + +/** Create Source objects. */ +#ifdef _XBOX +ALAPI ALvoid ALAPIENTRY alGenSources2D( ALsizei n, ALuint* sources ); +ALAPI ALvoid ALAPIENTRY alGenSources3D( ALsizei n, ALuint* sources ); +#else +ALAPI ALvoid ALAPIENTRY alGenSources( ALsizei n, ALuint* sources ); +#endif + +/** Delete Source objects. */ +ALAPI ALvoid ALAPIENTRY alDeleteSources( ALsizei n, ALuint* sources ); + +/** Verify a handle is a valid Source. */ +ALAPI ALboolean ALAPIENTRY alIsSource( ALuint id ); + +/** Set an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY alSourcei( ALuint source, ALenum param, ALint value ); +ALAPI ALvoid ALAPIENTRY alSourcef( ALuint source, ALenum param, ALfloat value ); +ALAPI ALvoid ALAPIENTRY alSource3f( ALuint source, ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); +ALAPI ALvoid ALAPIENTRY alSourcefv( ALuint source, ALenum param, ALfloat* values ); + +/** Get an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY alGetSourcei( ALuint source, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY alGetSourcef( ALuint source, ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY alGetSource3f( ALuint source, ALenum param, ALfloat* v1, ALfloat* v2, ALfloat* v3 ); +ALAPI ALvoid ALAPIENTRY alGetSourcefv( ALuint source, ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY alSourcePlayv( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY alSourcePausev( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY alSourceStopv( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY alSourceRewindv(ALsizei n,ALuint *sources); + +/** Activate a source, start replay. */ +ALAPI ALvoid ALAPIENTRY alSourcePlay( ALuint source ); + +/** + * Pause a source, + * temporarily remove it from the mixer list. + */ +ALAPI ALvoid ALAPIENTRY alSourcePause( ALuint source ); + +/** + * Stop a source, + * temporarily remove it from the mixer list, + * and reset its internal state to pre-Play. + * To remove a Source completely, it has to be + * deleted following Stop, or before Play. + */ +ALAPI ALvoid ALAPIENTRY alSourceStop( ALuint source ); + +/** + * Rewinds a source, + * temporarily remove it from the mixer list, + * and reset its internal state to pre-Play. + */ +ALAPI ALvoid ALAPIENTRY alSourceRewind( ALuint source ); + + + +/** + * BUFFER + * Buffer objects are storage space for sample data. + * Buffers are referred to by Sources. There can be more than + * one Source using the same Buffer data. If Buffers have + * to be duplicated on a per-Source basis, the driver has to + * take care of allocation, copying, and deallocation as well + * as propagating buffer data changes. + */ + + + + +/** Buffer object generation. */ +ALAPI ALvoid ALAPIENTRY alGenBuffers( ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY alDeleteBuffers( ALsizei n, ALuint* buffers ); +ALAPI ALboolean ALAPIENTRY alIsBuffer( ALuint buffer ); + +/** + * Specify the data to be filled into a buffer. + */ +ALAPI ALvoid ALAPIENTRY alBufferData( ALuint buffer, + ALenum format, + ALvoid* data, + ALsizei size, + ALsizei freq ); + + +ALAPI ALvoid ALAPIENTRY alGetBufferi( ALuint buffer, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY alGetBufferf( ALuint buffer, ALenum param, ALfloat* value ); + + + + +/** + * Queue stuff + */ + +ALAPI ALvoid ALAPIENTRY alSourceQueueBuffers( ALuint source, ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY alSourceUnqueueBuffers( ALuint source, ALsizei n, ALuint* buffers ); + + + + +#ifdef _XBOX +/** + * STREAM + * Stream objects encapsulate traditional sound stream and + * act as both sources and buffers. They emit sound and + * manage sound data. + */ + +ALAPI ALvoid ALAPIENTRY alGenStream( ALvoid ); +ALAPI ALvoid ALAPIENTRY alDeleteStream( ALvoid ); + +ALAPI ALvoid ALAPIENTRY alStreamStop( ALvoid ); +ALAPI ALvoid ALAPIENTRY alStreamPlay( ALsizei offset, ALint file, ALint loop ); + +ALAPI ALvoid ALAPIENTRY alStreamf( ALenum param, ALfloat value ); + +ALAPI ALvoid ALAPIENTRY alGetStreamf( ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY alGetStreami( ALenum param, ALint* value ); +#endif + + +/** + * Knobs and dials + */ +ALAPI ALvoid ALAPIENTRY alDistanceModel( ALenum value ); +ALAPI ALvoid ALAPIENTRY alDopplerFactor( ALfloat value ); +ALAPI ALvoid ALAPIENTRY alDopplerVelocity( ALfloat value ); +#ifdef _XBOX +ALAPI ALvoid ALAPIENTRY alGain( ALfloat value ); +#endif + +#else /* AL_NO_PROTOTYPES */ + +/** + * OpenAL Maintenance Functions + * Initialization and exiting. + * State Management and Query. + * Error Handling. + * Extension Support. + */ + +/** State management. */ +ALAPI ALvoid ALAPIENTRY (*alEnable)( ALenum capability ); +ALAPI ALvoid ALAPIENTRY (*alDisable)( ALenum capability ); +ALAPI ALboolean ALAPIENTRY (*alIsEnabled)( ALenum capability ); + +/** Application preferences for driver performance choices. */ +ALAPI ALvoid ALAPIENTRY (*alHint)( ALenum target, ALenum mode ); + +/** State retrieval. */ +ALAPI ALboolean ALAPIENTRY (*alGetBoolean)( ALenum param ); +ALAPI ALint ALAPIENTRY (*alGetInteger)( ALenum param ); +ALAPI ALfloat ALAPIENTRY (*alGetFloat)( ALenum param ); +ALAPI ALdouble ALAPIENTRY (*alGetDouble)( ALenum param ); +ALAPI ALvoid ALAPIENTRY (*alGetBooleanv)( ALenum param, ALboolean* data ); +ALAPI ALvoid ALAPIENTRY (*alGetIntegerv)( ALenum param, ALint* data ); +ALAPI ALvoid ALAPIENTRY (*alGetFloatv)( ALenum param, ALfloat* data ); +ALAPI ALvoid ALAPIENTRY (*alGetDoublev)( ALenum param, ALdouble* data ); +ALAPI ALubyte* ALAPIENTRY (*alGetString)( ALenum param ); + +/** + * Error support. + * Obtain the most recent error generated in the AL state machine. + */ +ALAPI ALenum ALAPIENTRY (*alGetError)( ALvoid ); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALboolean ALAPIENTRY (*alIsExtensionPresent)( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the address of a function (usually an extension) + * with the name fname. All addresses are context-independent. + */ +ALAPI ALvoid* ALAPIENTRY (*alGetProcAddress)( ALubyte* fname ); + + +/** + * Extension support. + * Obtain the integer value of an enumeration (usually an extension) with the name ename. + */ +ALAPI ALenum ALAPIENTRY (*alGetEnumValue)( ALubyte* ename ); + + + + +/** + * LISTENER + * Listener is the sample position for a given context. + * The multi-channel (usually stereo) output stream generated + * by the mixer is parametrized by this Listener object: + * its position and velocity relative to Sources, within + * occluder and reflector geometry. + */ + + + +/** + * + * Listener Environment: default 0. + */ +ALAPI ALvoid ALAPIENTRY (*alListeneri)( ALenum param, ALint value ); + + +/** + * + * Listener Gain: default 1.0f. + */ +ALAPI ALvoid ALAPIENTRY (*alListenerf)( ALenum param, ALfloat value ); + + +/** + * + * Listener Position. + * Listener Velocity. + */ +ALAPI ALvoid ALAPIENTRY (*alListener3f)( ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); + + +/** + * + * Listener Position: ALfloat[3] + * Listener Velocity: ALfloat[3] + * Listener Orientation: ALfloat[6] (forward and up vector). + */ +ALAPI ALvoid ALAPIENTRY (*alListenerfv)( ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY (*alGetListeneri)( ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY (*alGetListenerf)( ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY (*alGetListener3f)( ALenum param, ALfloat* v1, ALfloat* v2, ALfloat* v3 ); +ALAPI ALvoid ALAPIENTRY (*alGetListenerfv)( ALenum param, ALfloat* values ); + + +/** + * SOURCE + * Source objects are by default localized. Sources + * take the PCM data provided in the specified Buffer, + * apply Source-specific modifications, and then + * submit them to be mixed according to spatial + * arrangement etc. + */ + + + +/** Create Source objects. */ +ALAPI ALvoid ALAPIENTRY (*alGenSources)( ALsizei n, ALuint* sources ); + +/** Delete Source objects. */ +ALAPI ALvoid ALAPIENTRY (*alDeleteSources)( ALsizei n, ALuint* sources ); + +/** Verify a handle is a valid Source. */ +ALAPI ALboolean ALAPIENTRY (*alIsSource)( ALuint id ); + +/** Set an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY (*alSourcei)( ALuint source, ALenum param, ALint value ); +ALAPI ALvoid ALAPIENTRY (*alSourcef)( ALuint source, ALenum param, ALfloat value ); +ALAPI ALvoid ALAPIENTRY (*alSource3f)( ALuint source, ALenum param, ALfloat v1, ALfloat v2, ALfloat v3 ); +ALAPI ALvoid ALAPIENTRY (*alSourcefv)( ALuint source, ALenum param, ALfloat* values ); + +/** Get an integer parameter for a Source object. */ +ALAPI ALvoid ALAPIENTRY (*alGetSourcei)( ALuint source, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY (*alGetSourcef)( ALuint source, ALenum param, ALfloat* value ); +ALAPI ALvoid ALAPIENTRY (*alGetSourcefv)( ALuint source, ALenum param, ALfloat* values ); + +ALAPI ALvoid ALAPIENTRY (*alSourcePlayv)( ALsizei n, ALuint *sources ); +ALAPI ALvoid ALAPIENTRY (*alSourceStopv)( ALsizei n, ALuint *sources ); + +/** Activate a source, start replay. */ +ALAPI ALvoid ALAPIENTRY (*alSourcePlay)( ALuint source ); + +/** + * Pause a source, + * temporarily remove it from the mixer list. + */ +ALAPI ALvoid ALAPIENTRY (*alSourcePause)( ALuint source ); + +/** + * Stop a source, + * temporarily remove it from the mixer list, + * and reset its internal state to pre-Play. + * To remove a Source completely, it has to be + * deleted following Stop, or before Play. + */ +ALAPI ALvoid ALAPIENTRY (*alSourceStop)( ALuint source ); + + + +/** + * BUFFER + * Buffer objects are storage space for sample data. + * Buffers are referred to by Sources. There can be more than + * one Source using the same Buffer data. If Buffers have + * to be duplicated on a per-Source basis, the driver has to + * take care of allocation, copying, and deallocation as well + * as propagating buffer data changes. + */ + + + + +/** Buffer object generation. */ +ALAPI ALvoid ALAPIENTRY (*alGenBuffers)( ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY (*alDeleteBuffers)( ALsizei n, ALuint* buffers ); +ALAPI ALboolean ALAPIENTRY (*alIsBuffer)( ALuint buffer ); + +/** + * Specify the data to be filled into a buffer. + */ +ALAPI ALvoid ALAPIENTRY (*alBufferData)( ALuint buffer, + ALenum format, + ALvoid* data, + ALsizei size, + ALsizei freq ); + +ALAPI ALvoid ALAPIENTRY (*alGetBufferi)( ALuint buffer, ALenum param, ALint* value ); +ALAPI ALvoid ALAPIENTRY (*alGetBufferf)( ALuint buffer, ALenum param, ALfloat* value ); + + + + +/** + * Queue stuff + */ +ALAPI ALvoid ALAPIENTRY (*alSourceQueueBuffers)( ALuint source, ALsizei n, ALuint* buffers ); +ALAPI ALvoid ALAPIENTRY (*alSourceUnqueueBuffers)( ALuint source, ALsizei n, ALuint* buffers ); + +/** + * Knobs and dials + */ +ALAPI ALvoid ALAPIENTRY (*alDistanceModel)( ALenum value ); +ALAPI ALvoid ALAPIENTRY (*alDopplerFactor)( ALfloat value ); +ALAPI ALvoid ALAPIENTRY (*alDopplerVelocity)( ALfloat value ); + +#endif /* AL_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export off + #endif +#endif + +#ifndef _XBOX +#ifdef __cplusplus +} +#endif +#endif + +#endif diff --git a/codemp/client/openal/alc.h b/codemp/client/openal/alc.h new file mode 100644 index 0000000..0d52c77 --- /dev/null +++ b/codemp/client/openal/alc.h @@ -0,0 +1,97 @@ +#ifndef _ALC_H_ +#define _ALC_H_ + +#include "altypes.h" +#include "alctypes.h" + +#ifdef _XBOX + #define ALCAPI + #define ALCAPIENTRY +#else + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 + #ifdef _OPENAL32LIB + #define ALCAPI __declspec(dllexport) + #else + #define ALCAPI __declspec(dllimport) + #endif + + typedef struct ALCdevice_struct ALCdevice; + typedef struct ALCcontext_struct ALCcontext; + + #define ALCAPIENTRY __cdecl +#else + #ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export on + #endif + #endif + #define ALCAPI + #define ALCAPIENTRY __cdecl +#endif + +#endif + +#ifndef ALC_NO_PROTOTYPES + +ALCAPI ALCubyte* ALCAPIENTRY alcGetString(ALCdevice *device,ALCenum param); +ALCAPI ALCvoid ALCAPIENTRY alcGetIntegerv(ALCdevice *device,ALCenum param,ALCsizei size,ALCint *data); + +ALCAPI ALCdevice* ALCAPIENTRY alcOpenDevice(ALCubyte *deviceName); +ALCAPI ALCvoid ALCAPIENTRY alcCloseDevice(ALCdevice *device); + +ALCAPI ALCcontext*ALCAPIENTRY alcCreateContext(ALCdevice *device,ALCint *attrList); +ALCAPI ALCboolean ALCAPIENTRY alcMakeContextCurrent(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY alcProcessContext(ALCcontext *context); +ALCAPI ALCcontext*ALCAPIENTRY alcGetCurrentContext(ALCvoid); +ALCAPI ALCdevice* ALCAPIENTRY alcGetContextsDevice(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY alcSuspendContext(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY alcDestroyContext(ALCcontext *context); + +ALCAPI ALCenum ALCAPIENTRY alcGetError(ALCdevice *device); + +ALCAPI ALCboolean ALCAPIENTRY alcIsExtensionPresent(ALCdevice *device,ALCubyte *extName); +ALCAPI ALCvoid * ALCAPIENTRY alcGetProcAddress(ALCdevice *device,ALCubyte *funcName); +ALCAPI ALCenum ALCAPIENTRY alcGetEnumValue(ALCdevice *device,ALCubyte *enumName); + +#else /* AL_NO_PROTOTYPES */ + +ALCAPI ALCubyte* ALCAPIENTRY (*alcGetString)(ALCdevice *device,ALCenum param); +ALCAPI ALCvoid ALCAPIENTRY (*alcGetIntegerv)(ALCdevice * device,ALCenum param,ALCsizei size,ALCint *data); + +ALCAPI ALCdevice* ALCAPIENTRY (*alcOpenDevice)(ALubyte *deviceName); +ALCAPI ALCvoid ALCAPIENTRY (*alcCloseDevice)(ALCdevice *device); + +ALCAPI ALCcontext*ALCAPIENTRY (*alcCreateContext)(ALCdevice *device,ALCint *attrList); +ALCAPI ALCboolean ALCAPIENTRY (*alcMakeContextCurrent)(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY (*alcProcessContext)(ALCcontext *context); +ALCAPI ALCcontext*ALCAPIENTRY (*alcGetCurrentContext)(ALCvoid); +ALCAPI ALCdevice* ALCAPIENTRY (*alcGetContextsDevice)(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY (*alcSuspendContext)(ALCcontext *context); +ALCAPI ALCvoid ALCAPIENTRY (*alcDestroyContext)(ALCcontext *context); + +ALCAPI ALCenum ALCAPIENTRY (*alcGetError)(ALCdevice *device); + +ALCAPI ALCboolean ALCAPIENTRY (*alcIsExtensionPresent)(ALCdevice *device,ALCubyte *extName); +ALCAPI ALCvoid * ALCAPIENTRY (*alcGetProcAddress)(ALCdevice *device,ALCubyte *funcName); +ALCAPI ALCenum ALCAPIENTRY (*alcGetEnumValue)(ALCdevice *device,ALCubyte *enumName); + +#endif /* AL_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export off + #endif +#endif + +#ifndef _XBOX +#ifdef __cplusplus +} +#endif +#endif + +#endif diff --git a/codemp/client/openal/alctypes.h b/codemp/client/openal/alctypes.h new file mode 100644 index 0000000..a2b53ea --- /dev/null +++ b/codemp/client/openal/alctypes.h @@ -0,0 +1,133 @@ +#ifndef _ALCTYPES_H_ +#define _ALCTYPES_H_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _XBOX +/** ALC device type. */ +typedef int ALCdevice; + +/** ALC context type. */ +typedef int ALCcontext; +#endif + +/** ALC boolean type. */ +typedef char ALCboolean; + +/** ALC 8bit signed byte. */ +typedef char ALCbyte; + +/** ALC 8bit unsigned byte. */ +typedef unsigned char ALCubyte; + +/** ALC 16bit signed short integer type. */ +typedef short ALCshort; + +/** ALC 16bit unsigned short integer type. */ +typedef unsigned short ALCushort; + +/** ALC 32bit unsigned integer type. */ +typedef unsigned ALCuint; + +/** ALC 32bit signed integer type. */ +typedef int ALCint; + +/** ALC 32bit floating point type. */ +typedef float ALCfloat; + +/** ALC 64bit double point type. */ +typedef double ALCdouble; + +/** ALC 32bit type. */ +typedef unsigned int ALCsizei; + +/** ALC void type */ +typedef void ALCvoid; + +/** ALC enumerations. */ +typedef int ALCenum; + +/* Bad value. */ +#define ALC_INVALID (-1) + +/* Boolean False. */ +#define ALC_FALSE 0 + +/* Boolean True. */ +#define ALC_TRUE 1 + +/** Errors: No Error. */ +#define ALC_NO_ERROR ALC_FALSE + +#define ALC_MAJOR_VERSION 0x1000 +#define ALC_MINOR_VERSION 0x1001 +#define ALC_ATTRIBUTES_SIZE 0x1002 +#define ALC_ALL_ATTRIBUTES 0x1003 + +#define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 +#define ALC_DEVICE_SPECIFIER 0x1005 +#define ALC_EXTENSIONS 0x1006 + +#define ALC_FREQUENCY 0x1007 +#define ALC_REFRESH 0x1008 +#define ALC_SYNC 0x1009 + +/** + * The device argument does not name a valid dvice. + */ +#define ALC_INVALID_DEVICE 0xA001 + +/** + * The context argument does not name a valid context. + */ +#define ALC_INVALID_CONTEXT 0xA002 + +/** + * A function was called at inappropriate time, + * or in an inappropriate way, causing an illegal state. + * This can be an incompatible ALenum, object ID, + * and/or function. + */ +#define ALC_INVALID_ENUM 0xA003 + +/** + * Illegal value passed as an argument to an AL call. + * Applies to parameter values, but not to enumerations. + */ +#define ALC_INVALID_VALUE 0xA004 + +/** + * A function could not be completed, + * because there is not enough memory available. + */ +#define ALC_OUT_OF_MEMORY 0xA005 + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/codemp/client/openal/altypes.h b/codemp/client/openal/altypes.h new file mode 100644 index 0000000..a108bf9 --- /dev/null +++ b/codemp/client/openal/altypes.h @@ -0,0 +1,351 @@ +#ifndef _ALTYPES_H_ +#define _ALTYPES_H_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** OpenAL boolean type. */ +typedef char ALboolean; + +/** OpenAL 8bit signed byte. */ +typedef char ALbyte; + +/** OpenAL 8bit unsigned byte. */ +typedef unsigned char ALubyte; + +/** OpenAL 16bit signed short integer type. */ +typedef short ALshort; + +/** OpenAL 16bit unsigned short integer type. */ +typedef unsigned short ALushort; + +/** OpenAL 32bit unsigned integer type. */ +typedef unsigned ALuint; + +/** OpenAL 32bit signed integer type. */ +typedef int ALint; + +/** OpenAL 32bit floating point type. */ +typedef float ALfloat; + +/** OpenAL 64bit double point type. */ +typedef double ALdouble; + +/** OpenAL 32bit type. */ +typedef unsigned int ALsizei; + +/** OpenAL void type */ +typedef void ALvoid; + +/** OpenAL enumerations. */ +typedef int ALenum; + +/* Bad value. */ +#define AL_INVALID (-1) + +/* Disable value. */ +#define AL_NONE 0 + +/* Boolean False. */ +#define AL_FALSE 0 + +/* Boolean True. */ +#define AL_TRUE 1 + +/** + * Indicate the type of AL_SOURCE. + * Sources can be spatialized + */ +#define AL_SOURCE_TYPE 0x200 + +/** Indicate source has absolute coordinates. */ +#define AL_SOURCE_ABSOLUTE 0x201 + +/** Indicate Source has listener relative coordinates. */ +#define AL_SOURCE_RELATIVE 0x202 + +/** + * Directional source, inner cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_INNER_ANGLE 0x1001 + +/** + * Directional source, outer cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_OUTER_ANGLE 0x1002 + +/** + * Specify the pitch to be applied, either at source, + * or on mixer results, at listener. + * Range: [0.5-2.0] + * Default: 1.0 + */ +#define AL_PITCH 0x1003 + +/** + * Specify the current location in three dimensional space. + * OpenAL, like OpenGL, uses a right handed coordinate system, + * where in a frontal default view X (thumb) points right, + * Y points up (index finger), and Z points towards the + * viewer/camera (middle finger). + * To switch from a left handed coordinate system, flip the + * sign on the Z coordinate. + * Listener position is always in the world coordinate system. + */ +#define AL_POSITION 0x1004 + +/** Specify the current direction as forward vector. */ +#define AL_DIRECTION 0x1005 + +/** Specify the current velocity in three dimensional space. */ +#define AL_VELOCITY 0x1006 + +/** + * Indicate whether source has to loop infinite. + * Type: ALboolean + * Range: [AL_TRUE, AL_FALSE] + * Default: AL_FALSE + */ +#define AL_LOOPING 0x1007 + +/** + * Indicate the buffer to provide sound samples. + * Type: ALuint. + * Range: any valid Buffer id. + */ +#define AL_BUFFER 0x1009 + +/** + * Indicate the gain (volume amplification) applied. + * Type: ALfloat. + * Range: ]0.0- ] + * A value of 1.0 means un-attenuated/unchanged. + * Each division by 2 equals an attenuation of -6dB. + * Each multiplicaton with 2 equals an amplification of +6dB. + * A value of 0.0 is meaningless with respect to a logarithmic + * scale; it is interpreted as zero volume - the channel + * is effectively disabled. + */ +#define AL_GAIN 0x100A + +/** + * Indicate minimum source attenuation. + * Type: ALfloat + * Range: [0.0 - 1.0] + */ +#define AL_MIN_GAIN 0x100D + +/** + * Indicate maximum source attenuation. + * Type: ALfloat + * Range: [0.0 - 1.0] + */ +#define AL_MAX_GAIN 0x100E + +/** + * Specify the current orientation. + * Type: ALfv6 (at/up) + * Range: N/A + */ +#define AL_ORIENTATION 0x100F + +/* byte offset into source (in canon format). -1 if source + * is not playing. Don't set this, get this. + * + * Type: ALfloat + * Range: [0.0 - ] + * Default: 1.0 + */ +#define AL_REFERENCE_DISTANCE 0x1020 + + /** + * Indicate the rolloff factor for the source. + * Type: ALfloat + * Range: [0.0 - ] + * Default: 1.0 + */ +#define AL_ROLLOFF_FACTOR 0x1021 + +/** + * Indicate the gain (volume amplification) applied. + * Type: ALfloat. + * Range: ]0.0- ] + * A value of 1.0 means un-attenuated/unchanged. + * Each division by 2 equals an attenuation of -6dB. + * Each multiplicaton with 2 equals an amplification of +6dB. + * A value of 0.0 is meaningless with respect to a logarithmic + * scale; it is interpreted as zero volume - the channel + * is effectively disabled. + */ +#define AL_CONE_OUTER_GAIN 0x1022 + +/** + * Specify the maximum distance. + * Type: ALfloat + * Range: [0.0 - ] + */ +#define AL_MAX_DISTANCE 0x1023 + +/** + * Specify the panning to be applied (2D only.) + * Range: [-1.0 - 1.0] + * Default: 0.0 + */ +#define AL_PAN 0x1024 + +/** + * Get the playing time of a stream. + * Range: [0.0 - infinity] + */ +#define AL_TIME 0x1025 + +/** + * Specify the channel mask. (Creative) + * Type: ALuint + * Range: [0 - 255] + */ +#define AL_CHANNEL_MASK 0x3000 + +/** + * Source state information + */ +#define AL_SOURCE_STATE 0x1010 +#define AL_INITIAL 0x1011 +#define AL_PLAYING 0x1012 +#define AL_PAUSED 0x1013 +#define AL_STOPPED 0x1014 + +/** + * Buffer Queue params + */ +#define AL_BUFFERS_QUEUED 0x1015 +#define AL_BUFFERS_PROCESSED 0x1016 + +/** Sound buffers: format specifier. */ +#define AL_FORMAT_MONO8 0x1100 +#define AL_FORMAT_MONO16 0x1101 +#define AL_FORMAT_STEREO8 0x1102 +#define AL_FORMAT_STEREO16 0x1103 +#define AL_FORMAT_MONO4 0x1104 +#define AL_FORMAT_STEREO4 0x1105 + +/** + * Sound buffers: frequency, in units of Hertz [Hz]. + * This is the number of samples per second. Half of the + * sample frequency marks the maximum significant + * frequency component. + */ +#define AL_FREQUENCY 0x2001 +#define AL_BITS 0x2002 +#define AL_CHANNELS 0x2003 +#define AL_SIZE 0x2004 +#define AL_DATA 0x2005 + +/** + * Buffer state. + * + * Not supported for public use (yet). + */ +#define AL_UNUSED 0x2010 +#define AL_PENDING 0x2011 +#define AL_PROCESSED 0x2012 + +/** Errors: No Error. */ +#define AL_NO_ERROR AL_FALSE + +/** + * Illegal name passed as an argument to an AL call. + */ +#define AL_INVALID_NAME 0xA001 + +/** + * Illegal enum passed as an argument to an AL call. + */ +#define AL_INVALID_ENUM 0xA002 +/** + * Illegal value passed as an argument to an AL call. + * Applies to parameter values, but not to enumerations. + */ +#define AL_INVALID_VALUE 0xA003 + +/** + * A function was called at inappropriate time, + * or in an inappropriate way, causing an illegal state. + * This can be an incompatible ALenum, object ID, + * and/or function. + */ +#define AL_INVALID_OPERATION 0xA004 + +/** + * A function could not be completed, + * because there is not enough memory available. + */ +#define AL_OUT_OF_MEMORY 0xA005 + +/** Context strings: Vendor Name. */ +#define AL_VENDOR 0xB001 +#define AL_VERSION 0xB002 +#define AL_RENDERER 0xB003 +#define AL_EXTENSIONS 0xB004 +#define AL_MEMORY_USED 0xB005 +#define AL_MEMORY_ALLOCATOR 0xB006 +#define AL_MEMORY_DEALLOCATOR 0xB007 +#define AL_STEREO 0xB008 + +/** Global tweakage. */ + +/** + * Doppler scale. Default 1.0 + */ +#define AL_DOPPLER_FACTOR 0xC000 + +/** + * Doppler velocity. Default 1.0 + */ +#define AL_DOPPLER_VELOCITY 0xC001 + +/** + * Distance model. Default AL_INVERSE_DISTANCE_CLAMPED + */ +#define AL_DISTANCE_MODEL 0xD000 + +/** Distance models. */ + +#define AL_INVERSE_DISTANCE 0xD001 +#define AL_INVERSE_DISTANCE_CLAMPED 0xD002 + + /** + * enables + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/codemp/client/openal/alu.h b/codemp/client/openal/alu.h new file mode 100644 index 0000000..c6adff6 --- /dev/null +++ b/codemp/client/openal/alu.h @@ -0,0 +1,34 @@ +#ifndef _ALU_H_ +#define _ALU_H_ + +#define ALUAPI +#define ALUAPIENTRY __cdecl + +#define BUFFERSIZE 48000 +#define FRACTIONBITS 14 +#define FRACTIONMASK ((1L< namePrecache_m; +static namePrecache_m* pMap = NULL; + +// Used for enum / string matching +static const char *setNames[NUM_AS_SETS] = + { + "generalSet", + "localSet", + "bmodelSet", + }; + +// Used for enum / function matching +static const parseFunc_t parseFuncs[NUM_AS_SETS] = + { + AS_GetGeneralSet, + AS_GetLocalSet, + AS_GetBModelSet, + }; + +// Used for keyword / enum matching +static const char *keywordNames[NUM_AS_KEYWORDS]= + { + "timeBetweenWaves", + "subWaves", + "loopedWave", + "volRange", + "radius", + "type", + "amsdir", + "outdir", + "basedir", + }; + + +CSetGroup::CSetGroup(void) +{ + m_ambientSets = new vector; + m_setMap = new map; +} + + +CSetGroup::~CSetGroup(void) +{ + delete m_ambientSets; + delete m_setMap; +} + +/* +------------------------- +Free +------------------------- +*/ + +void CSetGroup::Free( void ) +{ + vector < ambientSet_t * >::iterator ai; + + for ( ai = m_ambientSets->begin(); ai != m_ambientSets->end(); ai++ ) + { + Z_Free ( (*ai) ); + } + + //Do this in place of clear() so it *really* frees the memory. + delete m_ambientSets; + delete m_setMap; + m_ambientSets = new vector; + m_setMap = new map; + + m_numSets = 0; +} + +/* +------------------------- +AddSet +------------------------- +*/ + +ambientSet_t *CSetGroup::AddSet( const char *name ) +{ + ambientSet_t *set; + + //Allocate the memory + set = (ambientSet_t *) Z_Malloc( sizeof( ambientSet_t ), TAG_AMBIENTSET, qtrue); + + //Set up some defaults + Q_strncpyz(set->name,name,sizeof(set->name)); + set->loopedVolume = MAX_SET_VOLUME; + set->masterVolume = MAX_SET_VOLUME; + set->radius = 250; + set->time_start = 10; + set->time_end = 25; + + set->volRange_start = MAX_SET_VOLUME; + set->volRange_end = MAX_SET_VOLUME; + + m_ambientSets->insert( m_ambientSets->end(), set ); + + set->id = m_numSets++; + + //Map the name to the pointer for reference later + (*m_setMap)[name] = set; + + return set; +} + +/* +------------------------- +GetSet +------------------------- +*/ + +ambientSet_t *CSetGroup::GetSet( const char *name ) +{ + map < sstring_t, ambientSet_t *>::iterator mi; + + if ( name == NULL ) + return NULL; + + mi = m_setMap->find( name ); + + if ( mi == m_setMap->end() ) + return NULL; + + return (*mi).second; +} + +ambientSet_t *CSetGroup::GetSet( int ID ) +{ + if ( m_ambientSets->empty() ) + return NULL; + + if ( ID < 0 ) + return NULL; + + if ( ID >= m_numSets ) + return NULL; + + return (*m_ambientSets)[ID]; +} + + +/* +=============================================== + +File Parsing + +=============================================== +*/ + +/* +------------------------- +AS_GetSetNameIDForString +------------------------- +*/ + +static int AS_GetSetNameIDForString( const char *name ) +{ + //Make sure it's valid + if ( name == NULL || name[0] == NULL ) + return -1; + + for ( int i = 0; i < NUM_AS_SETS; i++ ) + { + if ( stricmp( name, setNames[i] ) == 0 ) + return i; + } + + return -1; +} + +/* +------------------------- +AS_GetKeywordIDForString +------------------------- +*/ + +static int AS_GetKeywordIDForString( const char *name ) +{ + //Make sure it's valid + if ( name == NULL || name[0] == NULL ) + return -1; + + for ( int i = 0; i < NUM_AS_KEYWORDS; i++ ) + { + if ( stricmp( name, keywordNames[i] ) == 0 ) + return i; + } + + return -1; +} + +/* +------------------------- +AS_SkipLine + +Skips a line in the character buffer +------------------------- +*/ + +static void AS_SkipLine( void ) +{ + if ( parsePos > parseSize ) // needed to avoid a crash because of some OOR access that shouldn't be done + return; + + while ( (parseBuffer[parsePos] != '\n') && (parseBuffer[parsePos] != '\r') ) + { + parsePos++; + + if ( parsePos > parseSize ) + return; + } + + parsePos++; +} + +/* +------------------------- +AS_GetTimeBetweenWaves + +getTimeBetweenWaves +------------------------- +*/ + +static void AS_GetTimeBetweenWaves( ambientSet_t &set ) +{ + int startTime, endTime; + + //Get the data + sscanf( parseBuffer+parsePos, "%s %d %d", &tempBuffer, &startTime, &endTime ); + + //Check for swapped start / end + if ( startTime > endTime ) + { + #ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW"WARNING: Corrected swapped start / end times in a \"timeBetweenWaves\" keyword\n"); + #endif + + int swap = startTime; + startTime = endTime; + endTime = swap; + } + + //Store it + set.time_start = startTime; + set.time_end = endTime; + + AS_SkipLine(); +} + +/* +------------------------- +AS_GetSubWaves + +subWaves ... +------------------------- +*/ + +static void AS_GetSubWaves( ambientSet_t &set ) +{ + char dirBuffer[512], waveBuffer[256], waveName[1024]; + + //Get the directory for these sets + sscanf( parseBuffer+parsePos, "%s %s", &tempBuffer, &dirBuffer ); + + //Move the pointer past these two strings + parsePos += ((strlen(keywordNames[SET_KEYWORD_SUBWAVES])+1) + (strlen(dirBuffer)+1)); + + //Get all the subwaves + while ( parsePos <= parseSize ) + { + //Get the data + sscanf( parseBuffer+parsePos, "%s", &waveBuffer ); + + if ( set.numSubWaves > MAX_WAVES_PER_GROUP ) + { + #ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW"WARNING: Too many subwaves on set \"%s\"\n", set.name ); + #endif + } + else + { + //Construct the wave name (pretty, huh?) + strcpy( (char *) waveName, "sound/" ); + strncat( (char *) waveName, (const char *) dirBuffer, 1024 ); + strncat( (char *) waveName, "/", 512 ); + strncat( (char *) waveName, (const char *) waveBuffer, 512 ); + strncat( (char *) waveName, ".wav", 512 ); + + //Place this onto the sound directory name + + //Precache the file at this point and store off the ID instead of the name + if ( ( set.subWaves[set.numSubWaves++] = S_RegisterSound( waveName ) ) <= 0 ) + { + #ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW"WARNING: Unable to load ambient sound \"%s\"\n", waveName); + #endif + } + } + + //Move the pointer past this string + parsePos += strlen(waveBuffer)+1; + + if ( ( (parseBuffer+parsePos)[0] == '\n') || ( (parseBuffer+parsePos)[0] == '\r') ) + break; + } + + AS_SkipLine(); +} + +/* +------------------------- +AS_GetLoopedWave + +loopedWave +------------------------- +*/ + +static void AS_GetLoopedWave( ambientSet_t &set ) +{ + char waveBuffer[256], waveName[1024]; + + //Get the looped wave name + sscanf( parseBuffer+parsePos, "%s %s", &tempBuffer, &waveBuffer ); + + //Construct the wave name + strcpy( (char *) waveName, "sound/" ); + strncat( (char *) waveName, (const char *) waveBuffer, 1024 ); + strncat( (char *) waveName, ".wav", 1024 ); + + //Precache the file at this point and store off the ID instead of the name + if ( ( set.loopedWave = S_RegisterSound( waveName ) ) <= 0 ) + { + #ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW"WARNING: Unable to load ambient sound \"%s\"\n", waveName); + #endif + } + + AS_SkipLine(); +} + +/* +------------------------- +AS_GetVolumeRange +------------------------- +*/ + +static void AS_GetVolumeRange( ambientSet_t &set ) +{ + int min, max; + + //Get the data + sscanf( parseBuffer+parsePos, "%s %d %d", &tempBuffer, &min, &max ); + + //Check for swapped min / max + if ( min > max ) + { + #ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW"WARNING: Corrected swapped min / max range in a \"volRange\" keyword\n"); + #endif + + int swap = min; + min = max; + max = swap; + } + + //Store the data + set.volRange_start = min; + set.volRange_end = max; + + AS_SkipLine(); +} + +/* +------------------------- +AS_GetRadius +------------------------- +*/ + +static void AS_GetRadius( ambientSet_t &set ) +{ + //Get the data + sscanf( parseBuffer+parsePos, "%s %d", &tempBuffer, &set.radius ); + + AS_SkipLine(); +} + +/* +------------------------- +AS_GetGeneralSet +------------------------- +*/ + +static void AS_GetGeneralSet( ambientSet_t &set ) +{ + int keywordID; + + //The other parameters of the set come in a specific order + while ( parsePos <= parseSize ) + { + int iFieldsScanned = sscanf( parseBuffer+parsePos, "%s", &tempBuffer ); + if (iFieldsScanned <= 0) + return; + + keywordID = AS_GetKeywordIDForString( (const char *) &tempBuffer ); + + //Find and parse the keyword info + switch ( keywordID ) + { + case SET_KEYWORD_TIMEBETWEENWAVES: + AS_GetTimeBetweenWaves( set ); + break; + + case SET_KEYWORD_SUBWAVES: + AS_GetSubWaves( set ); + break; + + case SET_KEYWORD_LOOPEDWAVE: + AS_GetLoopedWave( set ); + break; + + case SET_KEYWORD_VOLRANGE: + AS_GetVolumeRange( set ); + break; + + default: + + //Check to see if we've finished this group + if ( AS_GetSetNameIDForString( (const char *) &tempBuffer ) == -1 ) + { + //Ignore comments + if ( tempBuffer[0] == ';' ) + return; + + //This wasn't a set name, so it's an error + #ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: Unknown ambient set keyword \"%s\"\n", tempBuffer ); + #endif + } + + return; + break; + } + } +} + +/* +------------------------- +AS_GetLocalSet +------------------------- +*/ + +static void AS_GetLocalSet( ambientSet_t &set ) +{ + int keywordID; + + //The other parameters of the set come in a specific order + while ( parsePos <= parseSize ) + { + int iFieldsScanned = sscanf( parseBuffer+parsePos, "%s", &tempBuffer ); + if (iFieldsScanned <= 0) + return; + + keywordID = AS_GetKeywordIDForString( (const char *) &tempBuffer ); + + //Find and parse the keyword info + switch ( keywordID ) + { + case SET_KEYWORD_TIMEBETWEENWAVES: + AS_GetTimeBetweenWaves( set ); + break; + + case SET_KEYWORD_SUBWAVES: + AS_GetSubWaves( set ); + break; + + case SET_KEYWORD_LOOPEDWAVE: + AS_GetLoopedWave( set ); + break; + + case SET_KEYWORD_VOLRANGE: + AS_GetVolumeRange( set ); + break; + + case SET_KEYWORD_RADIUS: + AS_GetRadius( set ); + break; + + default: + + //Check to see if we've finished this group + if ( AS_GetSetNameIDForString( (const char *) &tempBuffer ) == -1 ) + { + //Ignore comments + if ( tempBuffer[0] == ';' ) + return; + + //This wasn't a set name, so it's an error + #ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: Unknown ambient set keyword \"%s\"\n", tempBuffer ); + #endif + } + + return; + break; + } + } +} + +/* +------------------------- +AS_GetBModelSet +------------------------- +*/ + +static void AS_GetBModelSet( ambientSet_t &set ) +{ + int keywordID; + + //The other parameters of the set come in a specific order + while ( parsePos <= parseSize ) + { + int iFieldsScanned = sscanf( parseBuffer+parsePos, "%s", &tempBuffer ); + if (iFieldsScanned <= 0) + return; + + keywordID = AS_GetKeywordIDForString( (const char *) &tempBuffer ); + + //Find and parse the keyword info + switch ( keywordID ) + { + case SET_KEYWORD_SUBWAVES: + AS_GetSubWaves( set ); + break; + + default: + + //Check to see if we've finished this group + if ( AS_GetSetNameIDForString( (const char *) &tempBuffer ) == -1 ) + { + //Ignore comments + if ( tempBuffer[0] == ';' ) + return; + + //This wasn't a set name, so it's an error + #ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: Unknown ambient set keyword \"%s\"\n", tempBuffer ); + #endif + } + + return; + break; + } + } +} + +/* +------------------------- +AS_ParseSet + +Parses an individual set group out of a set file buffer +------------------------- +*/ + +static sboolean AS_ParseSet( int setID, CSetGroup *sg ) +{ + ambientSet_t *set; + const char *name; + + //Make sure we're not overstepping the name array + if ( setID > NUM_AS_SETS ) + return qfalse; + + //Reset the pointers for this run through + parsePos = 0; + + name = setNames[setID]; + + //Iterate through the whole file and find every occurance of a set + while ( parsePos <= parseSize ) + { + //Check for a valid set group + if ( strncmp( parseBuffer+parsePos, name, strlen(name) ) == 0 ) + { + //Update the debug info + numSets++; + + //Push past the set specifier and on to the name + parsePos+=strlen(name)+1; //Also take the following space out + + //Get the set name (this MUST be first) + sscanf( parseBuffer+parsePos, "%s", &tempBuffer ); + AS_SkipLine(); + + //Test the string against the precaches + if ( tempBuffer[0] ) + { + //Not in our precache listings, so skip it + if ( ( pMap->find( (const char *) &tempBuffer ) == pMap->end() ) ) + continue; + } + + //Create a new set + set = sg->AddSet( (const char *) &tempBuffer ); + + //Run the function to parse the data out + parseFuncs[setID]( *set ); + continue; + } + + //If not found on this line, go down another and check again + AS_SkipLine(); + } + + return qtrue; +} + +/* +------------------------- +AS_ParseHeader + +Parses the directory information out of the beginning of the file +------------------------- +*/ + +static void AS_ParseHeader( void ) +{ + char typeBuffer[128]; + int keywordID; + + while ( parsePos <= parseSize ) + { + sscanf( parseBuffer+parsePos, "%s", &tempBuffer ); + + keywordID = AS_GetKeywordIDForString( (const char *) &tempBuffer ); + + switch ( keywordID ) + { + case SET_KEYWORD_TYPE: + sscanf( parseBuffer+parsePos, "%s %s", &tempBuffer, &typeBuffer ); + + if ( !stricmp( (const char *) typeBuffer, "ambientSet" ) ) + { + return; + } + Com_Error( ERR_DROP, "AS_ParseHeader: Set type \"%s\" is not a valid set type!\n", typeBuffer ); + + break; + + case SET_KEYWORD_AMSDIR: + //TODO: Implement + break; + + case SET_KEYWORD_OUTDIR: + //TODO: Implement + break; + + case SET_KEYWORD_BASEDIR: + //TODO: Implement + break; + } + + AS_SkipLine(); + } +} + +/* +------------------------- +AS_ParseFile + +Opens and parses a sound set file +------------------------- +*/ + +static sboolean AS_ParseFile( const char *filename, CSetGroup *sg ) +{ + //Open the file and read the information from it + parseSize = FS_ReadFile( filename, (void **) &parseBuffer ); + + if ( parseSize <= 0 ) + return qfalse; + + //Parse the directory information out of the file + AS_ParseHeader(); + + //Parse all the relevent sets out of it + for ( int i = 0; i < NUM_AS_SETS; i++ ) + AS_ParseSet( i, sg ); + + //Free the memory and close the file + FS_FreeFile( parseBuffer ); + + return qtrue; +} + +/* +=============================================== + +Main code + +=============================================== +*/ + +/* +------------------------- +AS_Init + +Loads the ambient sound sets and prepares to play them when needed +------------------------- +*/ + +void AS_Init( void ) +{ + if (!aSets) + { + numSets = 0; + + pMap = new namePrecache_m; + + //Setup the structure + aSets = new CSetGroup(); + aSets->Init(); + } +} + +/* +------------------------- +AS_AddPrecacheEntry +------------------------- +*/ + +void AS_AddPrecacheEntry( const char *name ) +{ + if (!stricmp(name,"#clear")) + { + pMap->clear(); + } + else + { + (*pMap)[ name ] = 1; + } +} + +/* +------------------------- +AS_ParseSets + +Called on the client side to load and precache all the ambient sound sets +------------------------- +*/ + +void AS_ParseSets( void ) +{ + AS_Init(); + + //Parse all the sets + if ( AS_ParseFile( AMBIENT_SET_FILENAME, aSets ) == qfalse ) + { + Com_Error ( ERR_FATAL, S_COLOR_RED"ERROR: Couldn't load ambient sound sets from %s", AMBIENT_SET_FILENAME ); + } + +// Com_Printf( "AS_ParseFile: Loaded %d of %d ambient set(s)\n", pMap.size(), numSets ); + + int iErrorsOccured = 0; + for (namePrecache_m::iterator it = pMap->begin(); it != pMap->end(); ++it) + { + const char* str = (*it).first.c_str(); + ambientSet_t *aSet = aSets->GetSet( str ); + if (!aSet) + { + // I print these red instead of yellow because they're going to cause an ERR_DROP if they occur + Com_Printf( S_COLOR_RED"ERROR: AS_ParseSets: Unable to find ambient soundset \"%s\"!\n",str); + iErrorsOccured++; + } + } + + if (iErrorsOccured) + { + Com_Error( ERR_DROP, "....%d missing sound sets! (see above)\n", iErrorsOccured); + } + +// //Done with the precache info, it will be rebuilt on a restart +// pMap->clear(); // do NOT do this here now +} + +/* +------------------------- +AS_Free + +Frees up the ambient sound system +------------------------- +*/ + +void AS_Free( void ) +{ + if (aSets) + { + aSets->Free(); + delete aSets; + aSets = NULL; + + currentSet = -1; + oldSet = -1; + + currentSetTime = 0; + oldSetTime = 0; + + numSets = 0; + } +} + + +void AS_FreePartial(void) +{ + if (aSets) + { + aSets->Free(); + currentSet = -1; + oldSet = -1; + + currentSetTime = 0; + oldSetTime = 0; + + numSets = 0; + + delete pMap; + pMap = new namePrecache_m; + } +} + +/* +=============================================== + +Sound code + +=============================================== +*/ + +/* +------------------------- +AS_UpdateSetVolumes + +Fades volumes up or down depending on the action being taken on them. +------------------------- +*/ + +static void AS_UpdateSetVolumes( void ) +{ + ambientSet_t *old, *current; + float scale; + int deltaTime; + + //Get the sets and validate them + current = aSets->GetSet( currentSet ); + + if ( current == NULL ) + return; + + if ( current->masterVolume < MAX_SET_VOLUME ) + { + deltaTime = cls.realtime - current->fadeTime; + scale = ((float)(deltaTime)/(float)(crossDelay)); + current->masterVolume = (int)((scale) * (float)MAX_SET_VOLUME); + } + + if ( current->masterVolume > MAX_SET_VOLUME ) + current->masterVolume = MAX_SET_VOLUME; + + //Only update the old set if it's still valid + if ( oldSet == -1 ) + return; + + old = aSets->GetSet( oldSet ); + + if ( old == NULL ) + return; + + //Update the volumes + if ( old->masterVolume > 0 ) + { + deltaTime = cls.realtime - old->fadeTime; + scale = ((float)(deltaTime)/(float)(crossDelay)); + old->masterVolume = MAX_SET_VOLUME - (int)((scale) * (float)MAX_SET_VOLUME); + } + + if ( old->masterVolume <= 0 ) + { + old->masterVolume = 0; + oldSet = -1; + } +} + +/* +------------------------- +S_UpdateCurrentSet + +Does internal maintenance to keep track of changing sets. +------------------------- +*/ + +static void AS_UpdateCurrentSet( int id ) +{ + ambientSet_t *old, *current; + + //Check for a change + if ( id != currentSet ) + { + //This is new, so start the fading + oldSet = currentSet; + currentSet = id; + + old = aSets->GetSet( oldSet ); + current = aSets->GetSet( currentSet ); + // Ste, I just put this null check in for now, not sure if there's a more graceful way to exit this function - dmv + if( !current ) + { + return; + } + if ( old ) + { + old->masterVolume = MAX_SET_VOLUME; + old->fadeTime = cls.realtime; + } + + current->masterVolume = 0; + + //Set the fading starts + current->fadeTime = cls.realtime; + } + + //Update their volumes if fading + AS_UpdateSetVolumes(); +} + +/* +------------------------- +AS_PlayLocalSet + +Plays a local set taking volume and subwave playing into account. +Alters lastTime to reflect the time updates. +------------------------- +*/ + +static void AS_PlayLocalSet( vec3_t listener_origin, vec3_t origin, ambientSet_t *set, int entID, int *lastTime ) +{ + unsigned char volume; + vec3_t dir; + float volScale, dist, distScale; + int time = cls.realtime; + + //Make sure it's valid + if ( set == NULL ) + return; + + VectorSubtract( origin, listener_origin, dir ); + dist = VectorLength( dir ); + + //Determine the volume based on distance (NOTE: This sits on top of what SpatializeOrigin does) + distScale = ( dist < ( set->radius * 0.5f ) ) ? 1 : ( set->radius - dist ) / ( set->radius * 0.5f ); + volume = ( distScale > 1.0f || distScale < 0.0f ) ? 0 : (unsigned char) ( set->masterVolume * distScale ); + + //Add the looping sound + if ( set->loopedWave ) + S_AddAmbientLoopingSound( origin, volume, set->loopedWave ); + + //Check the time to start another one-shot subwave + if ( ( time - *lastTime ) < ( ( Q_irand( set->time_start, set->time_end ) ) * 1000 ) ) + return; + + //Update the time + *lastTime = time; + + //Scale the volume ranges for the subwaves based on the overall master volume + volScale = (float) volume / (float) MAX_SET_VOLUME; + volume = (unsigned char) Q_irand( (int)(volScale*set->volRange_start), (int)(volScale*set->volRange_end) ); + + //Add the random subwave + if ( set->numSubWaves ) + S_StartAmbientSound( origin, entID, volume, set->subWaves[Q_irand( 0, set->numSubWaves-1)] ); +} + +/* +------------------------- +AS_PlayAmbientSet + +Plays an ambient set taking volume and subwave playing into account. +Alters lastTime to reflect the time updates. +------------------------- +*/ + +static void AS_PlayAmbientSet( vec3_t origin, ambientSet_t *set, int *lastTime ) +{ + unsigned char volume; + float volScale; + int time = cls.realtime; + + //Make sure it's valid + if ( set == NULL ) + return; + + //Add the looping sound + if ( set->loopedWave ) + S_AddAmbientLoopingSound( origin, (unsigned char) set->masterVolume, set->loopedWave ); + + //Check the time to start another one-shot subwave + if ( ( time - *lastTime ) < ( ( Q_irand( set->time_start, set->time_end ) ) * 1000 ) ) + return; + + //Update the time + *lastTime = time; + + //Scale the volume ranges for the subwaves based on the overall master volume + volScale = (float) set->masterVolume / (float) MAX_SET_VOLUME; + volume = Q_irand( (int)(volScale*set->volRange_start), (int)(volScale*set->volRange_end) ); + + //Allow for softer noises than the masterVolume, but not louder + if ( volume > set->masterVolume ) + volume = set->masterVolume; + + //Add the random subwave + if ( set->numSubWaves ) + S_StartAmbientSound( origin, 0, volume, set->subWaves[Q_irand( 0, set->numSubWaves-1)] ); +} + +/* +------------------------- +S_UpdateAmbientSet + +Does maintenance and plays the ambient sets (two if crossfading) +------------------------- +*/ + +void S_UpdateAmbientSet ( const char *name, vec3_t origin ) +{ + ambientSet_t *current, *old; + ambientSet_t *set = aSets->GetSet( name ); + + if ( set == NULL ) + return; + + //Update the current and old set for crossfading + AS_UpdateCurrentSet( set->id ); + + current = aSets->GetSet( currentSet ); + old = aSets->GetSet( oldSet ); + + if ( current ) + AS_PlayAmbientSet( origin, set, ¤tSetTime ); + + if ( old ) + AS_PlayAmbientSet( origin, old, &oldSetTime ); +} + +/* +------------------------- +S_AddLocalSet +------------------------- +*/ + +int S_AddLocalSet( const char *name, vec3_t listener_origin, vec3_t origin, int entID, int time ) +{ + ambientSet_t *set; + int currentTime = 0; + + set = aSets->GetSet( name ); + + if ( set == NULL ) + return cls.realtime; + + currentTime = time; + + AS_PlayLocalSet( listener_origin, origin, set, entID, ¤tTime ); + + return currentTime; +} + +/* +------------------------- +AS_GetBModelSound +------------------------- +*/ + +sfxHandle_t AS_GetBModelSound( const char *name, int stage ) +{ + ambientSet_t *set; + + set = aSets->GetSet( name ); + + if ( set == NULL ) + return -1; + + //Stage must be within a valid range + if ( ( stage > ( set->numSubWaves - 1 ) ) || ( stage < 0 ) ) + return -1; + + return set->subWaves[ stage ]; +} diff --git a/codemp/client/snd_ambient.h b/codemp/client/snd_ambient.h new file mode 100644 index 0000000..3aaf560 --- /dev/null +++ b/codemp/client/snd_ambient.h @@ -0,0 +1,118 @@ +#ifndef __SND_AMBIENT__ +#define __SND_AMBIENT__ + +// Includes + +#pragma warning ( disable : 4786 ) +#pragma warning ( disable : 4511 ) //copy constructor could not be gen +#pragma warning ( disable : 4512 ) //assign constructor could not be gen + +//these don't work because stl re-sets them +//#pragma warning ( disable : 4663 ) //spcialize class +//#pragma warning ( disable : 4018 ) //signed/unsigned +#pragma warning (disable:4503) // decorated name length xceeded, name was truncated +#pragma warning (push, 3) //go back down to 3 for the stl include + +#include "../qcommon/sstring.h" // #include +#include +#include +#pragma warning (pop) +#pragma warning (disable:4503) // decorated name length xceeded, name was truncated + +using namespace std; + +// Defines + +#define AMBIENT_SET_FILENAME "sound/sound.txt" + +const int MAX_WAVES_PER_GROUP = 8; +const int MAX_SET_NAME_LENGTH = 64; + +// Enums + +enum set_e +{ + AS_SET_GENERAL, //General sets + AS_SET_LOCAL, //Local sets (regional) + AS_SET_BMODEL, //Brush model sets (doors, plats, etc.) + + NUM_AS_SETS +}; + +enum setKeyword_e +{ + SET_KEYWORD_TIMEBETWEENWAVES, + SET_KEYWORD_SUBWAVES, + SET_KEYWORD_LOOPEDWAVE, + SET_KEYWORD_VOLRANGE, + SET_KEYWORD_RADIUS, + SET_KEYWORD_TYPE, + SET_KEYWORD_AMSDIR, + SET_KEYWORD_OUTDIR, + SET_KEYWORD_BASEDIR, + + NUM_AS_KEYWORDS, +}; + +// Structures + +//NOTENOTE: Was going to make this a class, but don't want to muck around +typedef struct ambientSet_s +{ + char name[MAX_SET_NAME_LENGTH]; + unsigned char loopedVolume; + unsigned int time_start, time_end; + unsigned int volRange_start, volRange_end; + unsigned char numSubWaves; + int subWaves[MAX_WAVES_PER_GROUP]; + int loopedWave; + int radius; //NOTENOTE: -1 is global + int masterVolume; //Used for fading ambient sets (not a byte to prevent wrapping) + int id; //Used for easier referencing of sets + int fadeTime; //When the fade was started on this set +} ambientSet_t; + +typedef void (*parseFunc_t)( ambientSet_t & ); + +// Classes + +//NOTENOTE: But this one should be a class because of all the mapping and internal data handling +class CSetGroup +{ +public: + + CSetGroup(); + ~CSetGroup(); + + void Init( void ) + { + Free(); + } + + void Free( void ); + + ambientSet_t *AddSet( const char *name ); + + ambientSet_t *GetSet ( const char *name ); + ambientSet_t *GetSet ( int ID ); + +protected: + + int m_numSets; + vector < ambientSet_t * > *m_ambientSets; + map < sstring_t, ambientSet_t * > *m_setMap; +}; + +// Prototypes + +extern void AS_Init( void ); +extern void AS_Free( void ); +extern void AS_ParseSets( void ); +extern void AS_AddPrecacheEntry( const char *name ); + +extern void S_UpdateAmbientSet ( const char *name, vec3_t origin ); +extern int S_AddLocalSet( const char *name, vec3_t listener_origin, vec3_t origin, int entID, int time ); + +extern sfxHandle_t AS_GetBModelSound( const char *name, int stage ); + +#endif //__SND_AMBIENT__ \ No newline at end of file diff --git a/codemp/client/snd_dma.cpp b/codemp/client/snd_dma.cpp new file mode 100644 index 0000000..9fa2430 --- /dev/null +++ b/codemp/client/snd_dma.cpp @@ -0,0 +1,6332 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/***************************************************************************** + * name: snd_dma.c + * + * desc: main control for any streaming sound output device + * + * + *****************************************************************************/ +#include "snd_local.h" +#include "snd_mp3.h" +#include "snd_music.h" +#include "client.h" + +qboolean s_shutUp = qfalse; + +static void S_Play_f(void); +static void S_SoundList_f(void); +static void S_Music_f(void); +static void S_SetDynamicMusic_f(void); + +void S_Update_(); +void S_StopAllSounds(void); +static void S_UpdateBackgroundTrack( void ); +sfx_t *S_FindName( const char *name ); +static int SND_FreeSFXMem(sfx_t *sfx); + +extern qboolean Sys_LowPhysicalMemory(); + +////////////////////////// +// +// vars for bgrnd music track... +// +const int iMP3MusicStream_DiskBytesToRead = 10000;//4096; +const int iMP3MusicStream_DiskBufferSize = iMP3MusicStream_DiskBytesToRead*2; //*10; + +typedef struct +{ + sboolean bIsMP3; + // + // MP3 specific... + // + sfx_t sfxMP3_Bgrnd; + MP3STREAM streamMP3_Bgrnd; // this one is pointed at by the sfx_t's ptr, and is NOT the one the decoder uses every cycle + channel_t chMP3_Bgrnd; // ... but the one in this struct IS. + // + // MP3 disk streamer stuff... (if music is non-dynamic) + // + byte byMP3MusicStream_DiskBuffer[iMP3MusicStream_DiskBufferSize]; + int iMP3MusicStream_DiskReadPos; + int iMP3MusicStream_DiskWindowPos; + // + // MP3 disk-load stuff (for use during dynamic music, which is mem-resident) + // + byte *pLoadedData; // Z_Malloc, Z_Free // these two MUST be kept as valid/invalid together + char sLoadedDataName[MAX_QPATH]; // " " " " " + int iLoadedDataLen; + // + // remaining dynamic fields... + // + int iXFadeVolumeSeekTime; + int iXFadeVolumeSeekTo; // when changing this, set the above timer to Sys_Milliseconds(). + // Note that this should be thought of more as an up/down bool rather than as a + // number now, in other words set it only to 0 or 255. I'll probably change this + // to actually be a bool later. + int iXFadeVolume; // 0 = silent, 255 = max mixer vol, though still modulated via overall music_volume + float fSmoothedOutVolume; + sboolean bActive; // whether playing or not + sboolean bExists; // whether was even loaded for this level (ie don't try and start playing it) + // + // new dynamic fields... + // + sboolean bTrackSwitchPending; + MusicState_e eTS_NewState; + float fTS_NewTime; + // + // Generic... + // + fileHandle_t s_backgroundFile; // valid handle, else -1 if an MP3 (so that NZ compares still work) + wavinfo_t s_backgroundInfo; + int s_backgroundSamples; + + void Rewind() + { + MP3Stream_Rewind( &chMP3_Bgrnd ); + s_backgroundSamples = sfxMP3_Bgrnd.iSoundLengthInSamples; + } + + void SeekTo(float fTime) + { + chMP3_Bgrnd.iMP3SlidingDecodeWindowPos = 0; + chMP3_Bgrnd.iMP3SlidingDecodeWritePos = 0; + MP3Stream_SeekTo( &chMP3_Bgrnd, fTime ); + s_backgroundSamples = sfxMP3_Bgrnd.iSoundLengthInSamples; + } + +} MusicInfo_t; + +static void S_SetDynamicMusicState( MusicState_e musicState ); + +#define fDYNAMIC_XFADE_SECONDS (1.0f) + +static MusicInfo_t tMusic_Info[eBGRNDTRACK_NUMBEROF] = {0}; +static sboolean bMusic_IsDynamic = qfalse; +static MusicState_e eMusic_StateActual = eBGRNDTRACK_EXPLORE; // actual state, can be any enum +static MusicState_e eMusic_StateRequest = eBGRNDTRACK_EXPLORE; // requested state, can only be explore, action, boss, or silence +static char sMusic_BackgroundLoop[MAX_QPATH] = {0}; // only valid for non-dynamic music +static char sInfoOnly_CurrentDynamicMusicSet[64]; // any old reasonable size, only has to fit stuff like "kejim_post" +// +////////////////////////// + + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +// only begin attenuating sound volumes when outside the FULLVOLUME range +#define SOUND_FULLVOLUME 256 + +#define SOUND_ATTENUATE 0.0008f +#define VOICE_ATTENUATE 0.004f + +const float SOUND_FMAXVOL=0.75;//1.0; +const int SOUND_MAXVOL=255; + +channel_t s_channels[MAX_CHANNELS]; + +int s_soundStarted; +qboolean s_soundMuted; + +dma_t dma; + +int listener_number; +vec3_t listener_origin; +vec3_t listener_axis[3]; + +int s_soundtime; // sample PAIRS +int s_paintedtime; // sample PAIRS + +// MAX_SFX may be larger than MAX_SOUNDS because +// of custom player sounds +#define MAX_SFX 10000 //512 * 2 +sfx_t s_knownSfx[MAX_SFX]; +int s_numSfx; + +#define LOOP_HASH 128 +static sfx_t *sfxHash[LOOP_HASH]; + + +cvar_t *s_volume; +cvar_t *s_volumeVoice; +cvar_t *s_testsound; +cvar_t *s_khz; +cvar_t *s_allowDynamicMusic; +cvar_t *s_show; +cvar_t *s_mixahead; +cvar_t *s_mixPreStep; +cvar_t *s_musicVolume; +cvar_t *s_separation; +cvar_t *s_lip_threshold_1; +cvar_t *s_lip_threshold_2; +cvar_t *s_lip_threshold_3; +cvar_t *s_lip_threshold_4; +cvar_t *s_CPUType; +cvar_t *s_language; // note that this is distinct from "g_language" +cvar_t *s_dynamix; +cvar_t *s_debugdynamic; + +typedef struct +{ + unsigned char volume; + vec3_t origin; + vec3_t velocity; +/* const*/ sfx_t *sfx; + int mergeFrame; + int entnum; + + // For Open AL + bool bProcessed; + bool bRelative; +} loopSound_t; + +#define MAX_LOOP_SOUNDS 32 + +int numLoopSounds; +loopSound_t loopSounds[MAX_LOOP_SOUNDS]; + + +int s_rawend; +portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; +vec3_t s_entityPosition[MAX_GENTITIES]; +int s_entityWavVol[MAX_GENTITIES]; +int s_entityWavVol_back[MAX_GENTITIES]; + + +/**************************************************************************************************\ +* +* Open AL Specific +* +\**************************************************************************************************/ + +#define FLT_MAX 3.402823466e+38F +#define FLT_MIN 1.175494351e-38F +#define sqr(a) ((a)*(a)) +#define ENV_UPDATE_RATE 100 // Environmental audio update rate (in ms) + +//#define DISPLAY_CLOSEST_ENVS // Displays the closest env. zones (including the one the listener is in) + +#define DEFAULT_REF_DISTANCE 300.0f // Default reference distance +#define DEFAULT_VOICE_REF_DISTANCE 1500.0f // Default voice reference distance + +int s_UseOpenAL = false; // Determines if using Open AL or the default software mixer + +ALfloat listener_pos[3]; // Listener Position +ALfloat listener_ori[6]; // Listener Orientation +int s_numChannels; // Number of AL Sources == Num of Channels +short s_rawdata[MAX_RAW_SAMPLES*2]; // Used for Raw Samples (Music etc...) + +channel_t *S_OpenALPickChannel(int entnum, int entchannel); +int S_MP3PreProcessLipSync(channel_t *ch, short *data); +void UpdateSingleShotSounds(); +void UpdateLoopingSounds(); +void AL_UpdateRawSamples(); +void S_SetLipSyncs(); + +// EAX Related + +typedef struct +{ + ALuint ulNumApertures; + ALint lFXSlotID; + ALboolean bUsed; + struct + { + ALfloat vPos1[3]; + ALfloat vPos2[3]; + ALfloat vCenter[3]; + } Aperture[64]; +} ENVTABLE, *LPENVTABLE; + +typedef struct +{ + long lEnvID; + long lApertureNum; + float flDist; +} REVERBDATA, *LPREVERBDATA; + +typedef struct +{ + GUID FXSlotGuid; + ALint lEnvID; +} FXSLOTINFO, *LPFXSLOTINFO; + +ALboolean s_bEAX; // Is EAX 4.0 support available +bool s_bEALFileLoaded; // Has an .eal file been loaded for the current level +bool s_bInWater; // Underwater effect currently active +int s_EnvironmentID; // EAGLE ID of current environment +LPEAXMANAGER s_lpEAXManager; // Pointer to EAXManager object +HINSTANCE s_hEAXManInst; // Handle of EAXManager DLL +EAXSet s_eaxSet; // EAXSet() function +EAXGet s_eaxGet; // EAXGet() function +EAXREVERBPROPERTIES s_eaxLPCur; // Current EAX Parameters +LPENVTABLE s_lpEnvTable=NULL; // Stores information about each environment zone +long s_lLastEnvUpdate; // Time of last EAX update +long s_lNumEnvironments; // Number of environment zones +long s_NumFXSlots; // Number of EAX 4.0 FX Slots +FXSLOTINFO s_FXSlotInfo[EAX_MAX_FXSLOTS]; // Stores information about the EAX 4.0 FX Slots + +void InitEAXManager(); +void ReleaseEAXManager(); +bool LoadEALFile(char *szEALFilename); +void UnloadEALFile(); +void UpdateEAXListener(); +void UpdateEAXBuffer(channel_t *ch); +void EALFileInit(char *level); +float CalcDistance(EMPOINT A, EMPOINT B); + +void Normalize(EAXVECTOR *v) +{ + float flMagnitude; + + flMagnitude = (float)sqrt(sqr(v->x) + sqr(v->y) + sqr(v->z)); + + v->x = v->x / flMagnitude; + v->y = v->y / flMagnitude; + v->z = v->z / flMagnitude; +} + +// EAX 4.0 GUIDS ... confidential information ... + +const GUID EAXPROPERTYID_EAX40_FXSlot0 = { 0xc4d79f1e, 0xf1ac, 0x436b, { 0xa8, 0x1d, 0xa7, 0x38, 0xe7, 0x4, 0x54, 0x69} }; + +const GUID EAXPROPERTYID_EAX40_FXSlot1 = { 0x8c00e96, 0x74be, 0x4491, { 0x93, 0xaa, 0xe8, 0xad, 0x35, 0xa4, 0x91, 0x17} }; + +const GUID EAXPROPERTYID_EAX40_FXSlot2 = { 0x1d433b88, 0xf0f6, 0x4637, { 0x91, 0x9f, 0x60, 0xe7, 0xe0, 0x6b, 0x5e, 0xdd} }; + +const GUID EAXPROPERTYID_EAX40_FXSlot3 = { 0xefff08ea, 0xc7d8, 0x44ab, { 0x93, 0xad, 0x6d, 0xbd, 0x5f, 0x91, 0x0, 0x64} }; + +const GUID EAXPROPERTYID_EAX40_Context = { 0x1d4870ad, 0xdef, 0x43c0, { 0xa4, 0xc, 0x52, 0x36, 0x32, 0x29, 0x63, 0x42} }; + +const GUID EAXPROPERTYID_EAX40_Source = { 0x1b86b823, 0x22df, 0x4eae, { 0x8b, 0x3c, 0x12, 0x78, 0xce, 0x54, 0x42, 0x27} }; + +const GUID EAX_NULL_GUID = { 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }; + +const GUID EAX_PrimaryFXSlotID = { 0xf317866d, 0x924c, 0x450c, { 0x86, 0x1b, 0xe6, 0xda, 0xa2, 0x5e, 0x7c, 0x20} }; + +const GUID EAX_REVERB_EFFECT = { 0xcf95c8f, 0xa3cc, 0x4849, { 0xb0, 0xb6, 0x83, 0x2e, 0xcc, 0x18, 0x22, 0xdf} }; + +/**************************************************************************************************\ +* +* End of Open AL Specific +* +\**************************************************************************************************/ + +// instead of clearing a whole channel_t struct, we're going to skip the MP3SlidingDecodeBuffer[] buffer in the middle... +// +#ifndef offsetof +#include +#endif +static inline void Channel_Clear(channel_t *ch) +{ + // memset (ch, 0, sizeof(*ch)); + + memset(ch,0,offsetof(channel_t,MP3SlidingDecodeBuffer)); + + byte *const p = (byte *)ch + offsetof(channel_t,MP3SlidingDecodeBuffer) + sizeof(ch->MP3SlidingDecodeBuffer); + + memset(p,0,(sizeof(*ch) - offsetof(channel_t,MP3SlidingDecodeBuffer)) - sizeof(ch->MP3SlidingDecodeBuffer)); +} + +// ==================================================================== +// User-setable variables +// ==================================================================== +static void DynamicMusicInfoPrint(void) +{ + if (bMusic_IsDynamic) + { + // horribly lazy... ;-) + // + LPCSTR psRequestMusicState = Music_BaseStateToString( eMusic_StateRequest ); + LPCSTR psActualMusicState = Music_BaseStateToString( eMusic_StateActual, qtrue ); + if (psRequestMusicState == NULL) + { + psRequestMusicState = ""; + } + if (psActualMusicState == NULL) + { + psActualMusicState = ""; + } + + Com_Printf("( Dynamic music ON, request state: '%s'(%d), actual: '%s' (%d) )\n", psRequestMusicState, eMusic_StateRequest, psActualMusicState, eMusic_StateActual); + } + else + { + Com_Printf("( Dynamic music OFF )\n"); + } +} + +void S_SoundInfo_f(void) { + Com_Printf("----- Sound Info -----\n" ); + + if (!s_soundStarted) { + Com_Printf ("sound system not started\n"); + } else { + if ( s_soundMuted ) { + Com_Printf ("sound system is muted\n"); + } + + if (s_UseOpenAL) + { + Com_Printf("EAX 4.0 %s supported\n",s_bEAX?"is":"not"); + Com_Printf("Eal file %s loaded\n",s_bEALFileLoaded?"is":"not"); + Com_Printf("s_EnvironmentID = %d\n",s_EnvironmentID); + Com_Printf("s_bInWater = %s\n",s_bInWater?"true":"false"); + } + else + { + Com_Printf("%5d stereo\n", dma.channels - 1); + Com_Printf("%5d samples\n", dma.samples); + Com_Printf("%5d samplebits\n", dma.samplebits); + Com_Printf("%5d submission_chunk\n", dma.submission_chunk); + Com_Printf("%5d speed\n", dma.speed); + Com_Printf("0x%x dma buffer\n", dma.buffer); + } + + if (bMusic_IsDynamic) + { + DynamicMusicInfoPrint(); + Com_Printf("( Dynamic music set name: \"%s\" )\n",sInfoOnly_CurrentDynamicMusicSet); + } + else + { + if (!s_allowDynamicMusic->integer) + { + Com_Printf("( Dynamic music inhibited (s_allowDynamicMusic == 0) )\n", sMusic_BackgroundLoop ); + } + if ( tMusic_Info[eBGRNDTRACK_NONDYNAMIC].s_backgroundFile ) + { + Com_Printf("Background file: %s\n", sMusic_BackgroundLoop ); + } + else + { + Com_Printf("No background file.\n" ); + } + } + } + S_DisplayFreeMemory(); + Com_Printf("----------------------\n" ); +} + + + +/* +================ +S_Init +================ +*/ +void S_Init( void ) { + ALCcontext *ALCContext = NULL; + ALCdevice *ALCDevice = NULL; + ALfloat listenerPos[]={0.0,0.0,0.0}; + ALfloat listenerVel[]={0.0,0.0,0.0}; + ALfloat listenerOri[]={0.0,0.0,-1.0, 0.0,1.0,0.0}; + cvar_t *cv; + sboolean r; + int i, j; + channel_t *ch; + char *mapname; + + Com_Printf("\n------- sound initialization -------\n"); + + s_volume = Cvar_Get ("s_volume", "0.5", CVAR_ARCHIVE); + s_volumeVoice= Cvar_Get ("s_volumeVoice", "1.0", CVAR_ARCHIVE); + s_musicVolume = Cvar_Get ("s_musicvolume", "0.25", CVAR_ARCHIVE); + s_separation = Cvar_Get ("s_separation", "0.5", CVAR_ARCHIVE); + s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE|CVAR_LATCH); + s_allowDynamicMusic = Cvar_Get ("s_allowDynamicMusic", "1", CVAR_ARCHIVE); + s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); + + s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE); + s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); + s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); + s_debugdynamic = Cvar_Get("s_debugdynamic","0", CVAR_CHEAT); + s_lip_threshold_1 = Cvar_Get("s_threshold1" , "0.5",0); + s_lip_threshold_2 = Cvar_Get("s_threshold2" , "4.0",0); + s_lip_threshold_3 = Cvar_Get("s_threshold3" , "7.0",0); + s_lip_threshold_4 = Cvar_Get("s_threshold4" , "8.0",0); + + s_language = Cvar_Get("s_language","english",CVAR_ARCHIVE | CVAR_NORESTART); + + MP3_InitCvars(); + + s_CPUType = Cvar_Get("sys_cpuid","",0); + +#if !(defined __linux__ && defined __i386__) +#if !id386 +#else + extern unsigned int uiMMXAvailable; + uiMMXAvailable = !!(s_CPUType->integer >= CPUID_INTEL_MMX); +#endif +#endif + + cv = Cvar_Get ("s_initsound", "1", CVAR_ROM); + if ( !cv->integer ) { + s_soundStarted = 0; // needed in case you set s_initsound to 0 midgame then snd_restart (div0 err otherwise later) + Com_Printf ("not initializing.\n"); + Com_Printf("------------------------------------\n"); + return; + } + + Cmd_AddCommand("play", S_Play_f); + Cmd_AddCommand("music", S_Music_f); + Cmd_AddCommand("soundlist", S_SoundList_f); + Cmd_AddCommand("soundinfo", S_SoundInfo_f); + Cmd_AddCommand("soundstop", S_StopAllSounds); + Cmd_AddCommand("mp3_calcvols", S_MP3_CalcVols_f); + Cmd_AddCommand("s_dynamic", S_SetDynamicMusic_f); + + cv = Cvar_Get("s_UseOpenAL" , "0",CVAR_ARCHIVE|CVAR_LATCH); + s_UseOpenAL = !!(cv->integer); + + + if (s_UseOpenAL) + { + ALCDevice = alcOpenDevice((ALubyte*)"DirectSound3D"); + if (!ALCDevice) + return; + + //Create context(s) + ALCContext = alcCreateContext(ALCDevice, NULL); + if (!ALCContext) + return; + + //Set active context + alcMakeContextCurrent(ALCContext); + if (alcGetError(ALCDevice) != ALC_NO_ERROR) + return; + + s_soundStarted = 1; + s_soundMuted = qtrue; + s_soundtime = 0; + s_paintedtime = 0; + s_rawend = 0; + + S_StopAllSounds(); + + S_SoundInfo_f(); + + // Set Listener attributes + alListenerfv(AL_POSITION,listenerPos); + alListenerfv(AL_VELOCITY,listenerVel); + alListenerfv(AL_ORIENTATION,listenerOri); + + InitEAXManager(); + + memset(s_channels, 0, sizeof(s_channels)); + + s_numChannels = 0; + + // Create as many AL Sources (up to Max) as possible + for (i = 0; i < MAX_CHANNELS; i++) + { + alGenSources(1, &s_channels[i].alSource); // &g_Sources[i]); + if (alGetError() != AL_NO_ERROR) + { + // Reached limit of sources + break; + } + alSourcef(s_channels[i].alSource, AL_REFERENCE_DISTANCE, DEFAULT_REF_DISTANCE); + if (alGetError() != AL_NO_ERROR) + { + break; + } + + // Sources / Channels are not sending to any Slots (other than the Listener / Primary FX Slot) + s_channels[i].lSlotID = -1; + + s_numChannels++; + } + + // Generate AL Buffers for streaming audio playback (used for MP3s) + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + for (j = 0; j < NUM_STREAMING_BUFFERS; j++) + { + alGenBuffers(1, &(ch->buffers[j].BufferID)); + ch->buffers[j].Status = UNQUEUED; + ch->buffers[j].Data = (char *)Z_Malloc(STREAMING_BUFFER_SIZE, TAG_SND_RAWDATA, qfalse); + } + } + + // clear out the lip synching override array + memset(s_entityWavVol, 0, sizeof(s_entityWavVol)); + + // These aren't really relevant for Open AL, but for completeness ... + dma.speed = 22050; + dma.channels = 2; + dma.samplebits = 16; + dma.samples = 0; + dma.submission_chunk = 0; + dma.buffer = NULL; + + // Clamp sound volumes between 0.0f and 1.0f (just in case they aren't already) + if (s_volume->value < 0.f) + s_volume->value = 0.f; + if (s_volume->value > 1.f) + s_volume->value = 1.f; + + if (s_volumeVoice->value < 0.f) + s_volumeVoice->value = 0.f; + if (s_volumeVoice->value > 1.f) + s_volumeVoice->value = 1.f; + + if (s_musicVolume->value < 0.f) + s_musicVolume->value = 0.f; + if (s_musicVolume->value > 1.f) + s_musicVolume->value = 1.f; + + // s_init could be called in game, if so there may be an .eal file + // for this level + + mapname = Cvar_VariableString( "mapname" ); + EALFileInit(mapname); + + } + else + { + r = SNDDMA_Init(); + + if ( r ) { + s_soundStarted = 1; + s_soundMuted = qtrue; + // s_numSfx = 0; // do NOT do this here now!!! + + s_soundtime = 0; + s_paintedtime = 0; + + S_StopAllSounds (); + + S_SoundInfo_f(); + } + } + + Com_Printf("------------------------------------\n"); + + Com_Printf("\n--- ambient sound initialization ---\n"); + + AS_Init(); +} + +// only called from snd_restart. QA request... +// +int RE_RegisterMedia_GetLevel(void); +void S_ReloadAllUsedSounds(void) +{ + if (s_soundStarted && !s_soundMuted ) + { + // new bit, reload all soundsthat are used on the current level... + // + for (int i=1 ; i < s_numSfx ; i++) // start @ 1 to skip freeing default sound + { + sfx_t *sfx = &s_knownSfx[i]; + + if (!sfx->bInMemory && !sfx->bDefaultSound && sfx->iLastLevelUsedOn == RE_RegisterMedia_GetLevel()){ + S_memoryLoad(sfx); + } + } + } +} + +// ======================================================================= +// Shutdown sound engine +// ======================================================================= + +void S_Shutdown( void ) +{ + ALCcontext *ALCContext; + ALCdevice *ALCDevice; + channel_t *ch; + int i, j; + + if ( !s_soundStarted ) { + return; + } + + S_FreeAllSFXMem(); + S_UnCacheDynamicMusic(); + + if (s_UseOpenAL) + { + // Release all the AL Sources (including Music channel (Source 0)) + for (i = 0; i < s_numChannels; i++) + { + alDeleteSources(1, &(s_channels[i].alSource)); + } + + // Release Streaming AL Buffers + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + for (j = 0; j < NUM_STREAMING_BUFFERS; j++) + { + alDeleteBuffers(1, &(ch->buffers[j].BufferID)); + ch->buffers[j].BufferID = 0; + ch->buffers[j].Status = UNQUEUED; + if (ch->buffers[j].Data) + { + Z_Free(ch->buffers[j].Data); + ch->buffers[j].Data = NULL; + } + } + } + + // Get active context + ALCContext = alcGetCurrentContext(); + // Get device for active context + ALCDevice = alcGetContextsDevice(ALCContext); + // Release context(s) + alcDestroyContext(ALCContext); + // Close device + alcCloseDevice(ALCDevice); + + ReleaseEAXManager(); + + s_numChannels = 0; + + } + else + { + SNDDMA_Shutdown(); + } + + s_soundStarted = 0; + + Cmd_RemoveCommand("play"); + Cmd_RemoveCommand("music"); + Cmd_RemoveCommand("stopsound"); + Cmd_RemoveCommand("soundlist"); + Cmd_RemoveCommand("soundinfo"); + Cmd_RemoveCommand("soundstop"); + Cmd_RemoveCommand("mp3_calcvols"); + Cmd_RemoveCommand("s_dynamic"); + AS_Free(); +} + + + +/* + Mutes / Unmutes all OpenAL sound +*/ +void S_AL_MuteAllSounds(sboolean bMute) +{ + if (!s_soundStarted) + return; + + if (!s_UseOpenAL) + return; + + if (bMute) + { + alListenerf(AL_GAIN, 0.0f); + } + else + { + alListenerf(AL_GAIN, 1.0f); + } +} + + + + + +// ======================================================================= +// Load a sound +// ======================================================================= +/* +================ +return a hash value for the sfx name +================ +*/ +static long S_HashSFXName(const char *name) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (name[i] != '\0') { + letter = tolower(name[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (LOOP_HASH-1); + return hash; +} + +/* +================== +S_FindName + +Will allocate a new sfx if it isn't found +================== +*/ +sfx_t *S_FindName( const char *name ) { + int i; + int hash; + + sfx_t *sfx; + + if (!name) { + Com_Error (ERR_FATAL, "S_FindName: NULL\n"); + } + if (!name[0]) { + Com_Error (ERR_FATAL, "S_FindName: empty name\n"); + } + + if (strlen(name) >= MAX_QPATH) { + Com_Error (ERR_FATAL, "Sound name too long: %s", name); + } + + char sSoundNameNoExt[MAX_QPATH]; + COM_StripExtension(name,sSoundNameNoExt); + + hash = S_HashSFXName(sSoundNameNoExt); + + sfx = sfxHash[hash]; + // see if already loaded + while (sfx) { + if (!Q_stricmp(sfx->sSoundName, sSoundNameNoExt) ) { + return sfx; + } + sfx = sfx->next; + } +/* + // find a free sfx + for (i=0 ; i < s_numSfx ; i++) { + if (!s_knownSfx[i].soundName[0]) { + break; + } + } +*/ + i = s_numSfx; //we don't clear the soundName after failed loads any more, so it'll always be the last entry + + if (s_numSfx == MAX_SFX) + { + // ok, no sfx's free, but are there any with defaultSound set? (which the registering ent will never + // see because he gets zero returned if it's default...) + // + for (i=0 ; i < s_numSfx ; i++) { + if (s_knownSfx[i].bDefaultSound) { + break; + } + } + + if (i==s_numSfx) + { + // genuinely out of handles... + + // if we ever reach this, let me know and I'll either boost the array or put in a map-used-on + // reference to enable sfx_t recycling. TA codebase relies on being able to have structs for every sound + // used anywhere, ever, all at once (though audio bit-buffer gets recycled). SOF1 used about 1900 distinct + // events, so current MAX_SFX limit should do, or only need a small boost... -ste + // + + Com_Error (ERR_FATAL, "S_FindName: out of sfx_t"); + } + } + else + { + s_numSfx++; + } + + sfx = &s_knownSfx[i]; + memset (sfx, 0, sizeof(*sfx)); + Q_strncpyz(sfx->sSoundName, sSoundNameNoExt, sizeof(sfx->sSoundName)); + strlwr(sfx->sSoundName);//force it down low + + sfx->next = sfxHash[hash]; + sfxHash[hash] = sfx; + + return sfx; +} + +/* +================= +S_DefaultSound +================= +*/ +void S_DefaultSound( sfx_t *sfx ) { + + int i; + + sfx->iSoundLengthInSamples = 512; // #samples, ie shorts + sfx->pSoundData = (short *) SND_malloc(512*2, sfx); // ... so *2 for alloc bytes + sfx->bInMemory = qtrue; + + for ( i=0 ; i < sfx->iSoundLengthInSamples ; i++ ) + { + sfx->pSoundData[i] = i; + } +} + + +/* +=================== +S_DisableSounds + +Disables sounds until the next S_BeginRegistration. +This is called when the hunk is cleared and the sounds +are no longer valid. +=================== +*/ +void S_DisableSounds( void ) { + S_StopAllSounds(); + s_soundMuted = qtrue; +} + +/* +===================== +S_BeginRegistration + +===================== +*/ +void S_BeginRegistration( void ) +{ + char *mapname; + + s_soundMuted = qfalse; // we can play again + + // Find name of level so we can load in the appropriate EAL file + if (s_UseOpenAL) + { + mapname = Cvar_VariableString( "mapname" ); + EALFileInit(mapname); + // clear carry crap from previous map + for (int i = 0; i < EAX_MAX_FXSLOTS; i++) + { + s_FXSlotInfo[i].lEnvID = -1; + } + } + + if (s_numSfx == 0) { + SND_setup(); + + s_numSfx = 0; + memset( s_knownSfx, 0, sizeof( s_knownSfx ) ); + memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); + +#ifdef _DEBUG + sfx_t *sfx = S_FindName( "***DEFAULT***" ); + S_DefaultSound( sfx ); +#else + S_RegisterSound("sound/null.wav"); +#endif + } +} + + +void EALFileInit(char *level) +{ + long lRoom; + char name[MAX_QPATH]; + char szEALFilename[MAX_QPATH]; + int i; + + // If an EAL File is already unloaded, remove it + if (s_bEALFileLoaded) + { + UnloadEALFile(); + } + + // Reset variables + s_bInWater = false; + + // Try and load an EAL file for the new level + COM_StripExtension(level, name); + Com_sprintf(szEALFilename, MAX_QPATH, "eagle/%s.eal", name); + + s_bEALFileLoaded = LoadEALFile(szEALFilename); + + if (!s_bEALFileLoaded) + { + Com_sprintf(szEALFilename, MAX_QPATH, "base/eagle/%s.eal", name); + s_bEALFileLoaded = LoadEALFile(szEALFilename); + } + + if (s_bEALFileLoaded) + { + s_lLastEnvUpdate = timeGetTime(); + } + else + { + // Mute reverbs if no EAL file is found + if ((s_bEAX)&&(s_eaxSet)) + { + lRoom = -10000; + for (i = 0; i < s_NumFXSlots; i++) + { + s_eaxSet(&s_FXSlotInfo[i].FXSlotGuid, EAXREVERB_ROOM, NULL, + &lRoom, sizeof(long)); + } + } + } +} + + + +/* +================== +S_RegisterSound + +Creates a default buzz sound if the file can't be loaded +================== +*/ +sfxHandle_t S_RegisterSound( const char *name) +{ + sfx_t *sfx; + + if (!s_soundStarted) { + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( S_COLOR_RED"Sound name exceeds MAX_QPATH - %s\n", name ); + return 0; + } + + sfx = S_FindName( name ); + + SND_TouchSFX(sfx); + + if ( sfx->bDefaultSound ) + return 0; + + if (s_UseOpenAL) + { + if ((sfx->pSoundData) || (sfx->Buffer)) + return sfx - s_knownSfx; + } + else + { + if ( sfx->pSoundData ) + { + return sfx - s_knownSfx; + } + } + + sfx->bInMemory = qfalse; + + S_memoryLoad(sfx); + + if ( sfx->bDefaultSound ) { +#ifndef FINAL_BUILD + if (!s_shutUp) + { + Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->sSoundName ); + } +#endif + + + return 0; + } + + return sfx - s_knownSfx; +} + +void S_memoryLoad(sfx_t *sfx) +{ + // load the sound file... + // + if ( !S_LoadSound( sfx ) ) + { +// Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->sSoundName ); + sfx->bDefaultSound = qtrue; + } + sfx->bInMemory = qtrue; +} + + + +//============================================================================= +static sboolean S_CheckChannelStomp( int chan1, int chan2 ) +{ + if (!s_UseOpenAL) + { + if ( chan1 == chan2 ) + { + return qtrue; + } + } + + if ( ( chan1 == CHAN_VOICE || chan1 == CHAN_VOICE_ATTEN || chan1 == CHAN_VOICE_GLOBAL ) && ( chan2 == CHAN_VOICE || chan2 == CHAN_VOICE_ATTEN || chan2 == CHAN_VOICE_GLOBAL ) ) + { + return qtrue; + } + + return qfalse; +} + + +/* +================= +S_PickChannel +================= +*/ +// there were 2 versions of this, one for A3D and one normal, but the normal one wouldn't compile because +// it hadn't been updated for some time, so rather than risk anything weird/out of date, I just removed the +// A3D lines from this version and deleted the other one. +// +// If this really bothers you then feel free to play with it. -Ste. +// +channel_t *S_PickChannel(int entnum, int entchannel) +{ + int ch_idx; + channel_t *ch, *firstToDie; + sboolean foundChan = qfalse; + + if (s_UseOpenAL) + return S_OpenALPickChannel(entnum, entchannel); + + if ( entchannel<0 ) { + Com_Error (ERR_DROP, "S_PickChannel: entchannel<0"); + } + + // Check for replacement sound, or find the best one to replace + + firstToDie = &s_channels[0]; + + for ( int pass = 0; (pass < ((entchannel == CHAN_AUTO || entchannel == CHAN_LESS_ATTEN)?1:2)) && !foundChan; pass++ ) + { + for (ch_idx = 0, ch = &s_channels[0]; ch_idx < MAX_CHANNELS ; ch_idx++, ch++ ) + { + if ( entchannel == CHAN_AUTO || entchannel == CHAN_LESS_ATTEN || pass > 0 ) + {//if we're on the second pass, just find the first open chan + if ( !ch->thesfx ) + {//grab the first open channel + firstToDie = ch; + break; + } + + } + else if ( ch->entnum == entnum && S_CheckChannelStomp( ch->entchannel, entchannel ) ) + { + // always override sound from same entity + if ( s_show->integer == 1 && ch->thesfx ) { + Com_Printf( S_COLOR_YELLOW"...overrides %s\n", ch->thesfx->sSoundName ); + ch->thesfx = 0; //just to clear the next error msg + } + firstToDie = ch; + foundChan = qtrue; + break; + } + + // don't let anything else override local player sounds + if ( ch->entnum == listener_number && entnum != listener_number && ch->thesfx) { + continue; + } + + // don't override loop sounds + if ( ch->loopSound ) { + continue; + } + + if ( ch->startSample < firstToDie->startSample ) { + firstToDie = ch; + } + } + } + + if ( s_show->integer == 1 && firstToDie->thesfx ) { + Com_Printf( S_COLOR_RED"***kicking %s\n", firstToDie->thesfx->sSoundName ); + } + + Channel_Clear(firstToDie); // memset(firstToDie, 0, sizeof(*firstToDie)); + + return firstToDie; +} + + + +/* + For use with Open AL + + Allows more than one sound of the same type to emanate from the same entity - sounds much better + on hardware this way esp. rapid fire modes of weapons! +*/ +channel_t *S_OpenALPickChannel(int entnum, int entchannel) +{ + int ch_idx; + channel_t *ch, *ch_firstToDie; + bool foundChan = false; + float source_pos[3]; + + if ( entchannel < 0 ) + { + Com_Error (ERR_DROP, "S_PickChannel: entchannel<0"); + } + + // Check for replacement sound, or find the best one to replace + + ch_firstToDie = s_channels + 1; // channel 0 is reserved for Music + + for (ch_idx = 1, ch = s_channels + ch_idx; ch_idx < s_numChannels; ch_idx++, ch++) + { + if ( ch->entnum == entnum && S_CheckChannelStomp( ch->entchannel, entchannel ) ) + { + // always override sound from same entity + if ( s_show->integer == 1 && ch->thesfx ) { + Com_Printf( S_COLOR_YELLOW"...overrides %s\n", ch->thesfx->sSoundName ); + ch->thesfx = 0; //just to clear the next error msg + } + ch_firstToDie = ch; + foundChan = true; + break; + } + } + + if (!foundChan) + for (ch_idx = 1, ch = s_channels + ch_idx; ch_idx < s_numChannels; ch_idx++, ch++) + { + // See if the channel is free + if (!ch->thesfx) + { + ch_firstToDie = ch; + foundChan = true; + break; + } + } + + if (!foundChan) + { + for (ch_idx = 1, ch = s_channels + ch_idx; ch_idx < s_numChannels; ch_idx++, ch++) + { + if ( (ch->entnum == entnum) && (ch->entchannel == entchannel) && (ch->entchannel != CHAN_AMBIENT) + && (ch->entnum != listener_number) ) + { + // Same entity and same type of sound effect (entchannel) + ch_firstToDie = ch; + foundChan = true; + break; + } + } + } + + int longestDist; + int dist; + + if (!foundChan) + { + // Find sound effect furthest from listener + ch = s_channels + 1; + + if (ch->fixed_origin) + { + // Convert to Open AL co-ordinates + source_pos[0] = ch->origin[0]; + source_pos[1] = ch->origin[2]; + source_pos[2] = -ch->origin[1]; + + longestDist = ((listener_pos[0] - source_pos[0]) * (listener_pos[0] - source_pos[0])) + + ((listener_pos[1] - source_pos[1]) * (listener_pos[1] - source_pos[1])) + + ((listener_pos[2] - source_pos[2]) * (listener_pos[2] - source_pos[2])); + } + else + { + if (ch->entnum == listener_number) + longestDist = 0; + else + { + if (ch->bLooping) + { + // Convert to Open AL co-ordinates + source_pos[0] = loopSounds[ch->entnum].origin[0]; + source_pos[1] = loopSounds[ch->entnum].origin[2]; + source_pos[2] = -loopSounds[ch->entnum].origin[1]; + } + else + { + // Convert to Open AL co-ordinates + source_pos[0] = s_entityPosition[ch->entnum][0]; + source_pos[1] = s_entityPosition[ch->entnum][2]; + source_pos[2] = -s_entityPosition[ch->entnum][1]; + } + + longestDist = ((listener_pos[0] - source_pos[0]) * (listener_pos[0] - source_pos[0])) + + ((listener_pos[1] - source_pos[1]) * (listener_pos[1] - source_pos[1])) + + ((listener_pos[2] - source_pos[2]) * (listener_pos[2] - source_pos[2])); + } + } + + for (ch_idx = 2, ch = s_channels + ch_idx; ch_idx < s_numChannels; ch_idx++, ch++) + { + if (ch->fixed_origin) + { + // Convert to Open AL co-ordinates + source_pos[0] = ch->origin[0]; + source_pos[1] = ch->origin[2]; + source_pos[2] = -ch->origin[1]; + + dist = ((listener_pos[0] - source_pos[0]) * (listener_pos[0] - source_pos[0])) + + ((listener_pos[1] - source_pos[1]) * (listener_pos[1] - source_pos[1])) + + ((listener_pos[2] - source_pos[2]) * (listener_pos[2] - source_pos[2])); + } + else + { + if (ch->entnum == listener_number) + dist = 0; + else + { + if (ch->bLooping) + { + // Convert to Open AL co-ordinates + source_pos[0] = loopSounds[ch->entnum].origin[0]; + source_pos[1] = loopSounds[ch->entnum].origin[2]; + source_pos[2] = -loopSounds[ch->entnum].origin[1]; + } + else + { + // Convert to Open AL co-ordinates + source_pos[0] = s_entityPosition[ch->entnum][0]; + source_pos[1] = s_entityPosition[ch->entnum][2]; + source_pos[2] = -s_entityPosition[ch->entnum][1]; + } + + dist = ((listener_pos[0] - source_pos[0]) * (listener_pos[0] - source_pos[0])) + + ((listener_pos[1] - source_pos[1]) * (listener_pos[1] - source_pos[1])) + + ((listener_pos[2] - source_pos[2]) * (listener_pos[2] - source_pos[2])); + } + } + + if (dist > longestDist) + { + longestDist = dist; + ch_firstToDie = ch; + } + } + } + + if (ch_firstToDie->bPlaying) + { + if (s_show->integer == 1 && ch_firstToDie->thesfx ) + { + Com_Printf(S_COLOR_RED"***kicking %s\n", ch_firstToDie->thesfx->sSoundName ); + } + + // Stop sound + alSourceStop(ch_firstToDie->alSource); + ch_firstToDie->bPlaying = false; + } + + // Reset channel variables + memset(&ch_firstToDie->MP3StreamHeader, 0, sizeof(MP3STREAM)); + ch_firstToDie->bLooping = false; + ch_firstToDie->bProcessed = false; + ch_firstToDie->bStreaming = false; + + return ch_firstToDie; +} + + +/* +================= +S_SpatializeOrigin + +Used for spatializing s_channels +================= +*/ +void S_SpatializeOrigin (const vec3_t origin, float master_vol, int *left_vol, int *right_vol, int channel) +{ + vec_t dot; + vec_t dist; + vec_t lscale, rscale, scale; + vec3_t source_vec; + float dist_mult = SOUND_ATTENUATE; + + // calculate stereo seperation and distance attenuation + VectorSubtract(origin, listener_origin, source_vec); + + dist = VectorNormalize(source_vec); + if ( channel == CHAN_VOICE ) + { + dist -= SOUND_FULLVOLUME * 3.0f; +// dist_mult = VOICE_ATTENUATE; // tweak added (this fixes an NPC dialogue "in your ears" bug, but we're not sure if it'll make a bunch of others fade too early. Too close to shipping...) + } + else if ( channel == CHAN_LESS_ATTEN ) + { + dist -= SOUND_FULLVOLUME * 8.0f; // maybe is too large + } + else if ( channel == CHAN_VOICE_ATTEN ) + { + dist -= SOUND_FULLVOLUME * 1.35f; // used to be 0.15f, dropped off too sharply - dmv + dist_mult = VOICE_ATTENUATE; + } + else if ( channel == CHAN_VOICE_GLOBAL ) + { + dist = -1; + } + else // use normal attenuation. + { + dist -= SOUND_FULLVOLUME; + } + + if (dist < 0) + { + dist = 0; // close enough to be at full volume + } + dist *= dist_mult; // different attenuation levels + + dot = -DotProduct(listener_axis[1], source_vec); + + if (dma.channels == 1) // || !dist_mult) + { // no attenuation = no spatialization + rscale = SOUND_FMAXVOL; + lscale = SOUND_FMAXVOL; + } + else + { + //rscale = 0.5 * (1.0 + dot); + //lscale = 0.5 * (1.0 - dot); + rscale = s_separation->value + ( 1.0f - s_separation->value ) * dot; + lscale = s_separation->value - ( 1.0f - s_separation->value ) * dot; + if ( rscale < 0 ) + { + rscale = 0; + } + if ( lscale < 0 ) + { + lscale = 0; + } + } + + // add in distance effect + scale = (1.0f - dist) * rscale; + *right_vol = (int) (master_vol * scale); + if (*right_vol < 0) + { + *right_vol = 0; + } + + scale = (1.0f - dist) * lscale; + *left_vol = (int) (master_vol * scale); + if (*left_vol < 0) + { + *left_vol = 0; + } +} + + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +/* +==================== +S_StartAmbientSound + +Starts an ambient, 'one-shot" sound. +==================== +*/ + +void S_StartAmbientSound( const vec3_t origin, int entityNum, unsigned char volume, sfxHandle_t sfxHandle ) +{ + channel_t *ch; + /*const*/ sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) + Com_Error( ERR_DROP, "S_StartAmbientSound: bad entitynum %i", entityNum ); + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) + Com_Error( ERR_DROP, "S_StartAmbientSound: handle %i out of range", sfxHandle ); + + sfx = &s_knownSfx[ sfxHandle ]; + if (sfx->bInMemory == qfalse){ + S_memoryLoad(sfx); + } + SND_TouchSFX(sfx); + + if (s_UseOpenAL) + { + if (volume==0) + return; + } + + if ( s_show->integer == 1 ) { + Com_Printf( "%i : %s on (%d) Ambient\n", s_paintedtime, sfx->sSoundName, entityNum ); + } + + // pick a channel to play on + ch = S_PickChannel( entityNum, CHAN_AMBIENT ); + if (!ch) { + return; + } + + if (origin) + { + VectorCopy (origin, ch->origin); + ch->fixed_origin = qtrue; + } + else + { + ch->fixed_origin = qfalse; + } + + ch->master_vol = volume; + ch->entnum = entityNum; + ch->entchannel = CHAN_AMBIENT; + ch->thesfx = sfx; + ch->startSample = START_SAMPLE_IMMEDIATE; + + ch->leftvol = ch->master_vol; // these will get calced at next spatialize + ch->rightvol = ch->master_vol; // unless the game isn't running + + if (sfx->pMP3StreamHeader) + { + memcpy(&ch->MP3StreamHeader,sfx->pMP3StreamHeader, sizeof(ch->MP3StreamHeader)); + //ch->iMP3SlidingDecodeWritePos = 0; // These will be zero from the memset in S_PickChannel(), but keep them here for reference... + //ch->iMP3SlidingDecodeWindowPos= 0; // + } + else + { + memset(&ch->MP3StreamHeader,0, sizeof(ch->MP3StreamHeader)); + } +} + +/* +==================== +S_MuteSound + +Mutes sound on specified channel for specified entity. +==================== +*/ +void S_MuteSound(int entityNum, int entchannel) +{ + //I guess this works. + channel_t *ch = S_PickChannel( entityNum, entchannel ); + + if (!ch) + { + return; + } + + ch->master_vol = 0; + ch->entnum = 0; + ch->entchannel = 0; + ch->thesfx = 0; + ch->startSample = 0; + + ch->leftvol = 0; + ch->rightvol = 0; +} + +/* +==================== +S_StartSound + +Validates the parms and ques the sound up +if pos is NULL, the sound will be dynamically sourced from the entity +Entchannel 0 will never override a playing sound +==================== +*/ +void S_StartSound(const vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) +{ + int i; + channel_t *ch; + /*const*/ sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) { + Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Error( ERR_DROP, "S_StartSound: handle %i out of range", sfxHandle ); + } + + sfx = &s_knownSfx[ sfxHandle ]; + if (sfx->bInMemory == qfalse){ + S_memoryLoad(sfx); + } + SND_TouchSFX(sfx); + + if ( s_show->integer == 1 ) { + Com_Printf( "%i : %s on (%d)\n", s_paintedtime, sfx->sSoundName, entityNum ); + } + + if (s_UseOpenAL) + { + if (entchannel == CHAN_WEAPON) + { + // Check if we are playing a 'charging' sound, if so, stop it now .. + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + if ((ch->entnum == entityNum) && (ch->entchannel == CHAN_WEAPON) && (ch->thesfx) && (strstr(ch->thesfx->sSoundName, "altcharge") != NULL)) + { + // Stop this sound + alSourceStop(ch->alSource); + alSourcei(ch->alSource, AL_BUFFER, NULL); + ch->bPlaying = false; + ch->thesfx = NULL; + break; + } + } + } + else + { + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + if ((ch->entnum == entityNum) && (ch->thesfx) && (strstr(ch->thesfx->sSoundName, "falling") != NULL)) + { + // Stop this sound + alSourceStop(ch->alSource); + alSourcei(ch->alSource, AL_BUFFER, NULL); + ch->bPlaying = false; + ch->thesfx = NULL; + break; + } + } + } + } + + + // pick a channel to play on + + ch = S_PickChannel( entityNum, entchannel ); + if (!ch) { + return; + } + + if (origin) { + VectorCopy (origin, ch->origin); + ch->fixed_origin = qtrue; + } else { + ch->fixed_origin = qfalse; + } + + ch->master_vol = SOUND_MAXVOL; //FIXME: Um.. control? + ch->entnum = entityNum; + ch->entchannel = entchannel; + ch->thesfx = sfx; + ch->startSample = START_SAMPLE_IMMEDIATE; + + ch->leftvol = ch->master_vol; // these will get calced at next spatialize + ch->rightvol = ch->master_vol; // unless the game isn't running + + if (entchannel < CHAN_AMBIENT && entityNum == listener_number) { //only do it for body sounds not local sounds + ch->master_vol = SOUND_MAXVOL * SOUND_FMAXVOL; //this won't be attenuated so let it scale down + } + if ( entchannel == CHAN_VOICE || entchannel == CHAN_VOICE_ATTEN || entchannel == CHAN_VOICE_GLOBAL ) + { + s_entityWavVol[ ch->entnum ] = -1; //we've started the sound but it's silent for now + } + + if (sfx->pMP3StreamHeader) + { + memcpy(&ch->MP3StreamHeader,sfx->pMP3StreamHeader, sizeof(ch->MP3StreamHeader)); + //ch->iMP3SlidingDecodeWritePos = 0; // These will be zero from the memset in S_PickChannel(), but keep them here for reference... + //ch->iMP3SlidingDecodeWindowPos= 0; // + } + else + { + memset(&ch->MP3StreamHeader,0, sizeof(ch->MP3StreamHeader)); + } +} + +/* +================== +S_StartLocalSound +================== +*/ +void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Error( ERR_DROP, "S_StartLocalSound: handle %i out of range", sfxHandle ); + } + + S_StartSound (NULL, listener_number, channelNum, sfxHandle ); +} + + +/* +================== +S_StartLocalLoopingSound +================== +*/ +void S_StartLocalLoopingSound( sfxHandle_t sfxHandle) { + vec3_t nullVec = {0,0,0}; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Error( ERR_DROP, "S_StartLocalLoopingSound: handle %i out of range", sfxHandle ); + } + + S_AddLoopingSound( listener_number, nullVec, nullVec, sfxHandle ); + +} + +// returns length in milliseconds of supplied sound effect... (else 0 for bad handle now) +// +float S_GetSampleLengthInMilliSeconds( sfxHandle_t sfxHandle) +{ + sfx_t *sfx; + + if (!s_soundStarted) + { //we have no sound, so let's just make a reasonable guess + return 512 * 1000; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) + return 0.0f; + + sfx = &s_knownSfx[ sfxHandle ]; + + float f = (float)sfx->iSoundLengthInSamples / (float)dma.speed; + + return (f * 1000); +} + + +/* +================== +S_ClearSoundBuffer + +If we are about to perform file access, clear the buffer +so sound doesn't stutter. +================== +*/ +void S_ClearSoundBuffer( void ) { + int clear; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } +#if 0 //this causes scripts to freak when the sounds get cut... + // clear all the sounds so they don't + // start back up after the load finishes + memset( s_channels, 0, sizeof( s_channels ) ); + // clear out the lip synching override array + memset(s_entityWavVol, 0,sizeof(s_entityWavVol)); +#endif + s_rawend = 0; + + if (!s_UseOpenAL) + { + if (dma.samplebits == 8) + clear = 0x80; + else + clear = 0; + + SNDDMA_BeginPainting (); + if (dma.buffer) + memset(dma.buffer, clear, dma.samples * dma.samplebits/8); + SNDDMA_Submit (); + } + else + { + s_paintedtime = 0; + s_soundtime = 0; + } +} + + +// kinda kludgy way to stop a special-use sfx_t playing... +// +void S_CIN_StopSound(sfxHandle_t sfxHandle) +{ + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Error( ERR_DROP, "S_CIN_StopSound: handle %i out of range", sfxHandle ); + } + + sfx_t *sfx = &s_knownSfx[ sfxHandle ]; + channel_t *ch = s_channels; + int i; + + for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) + { + if ( !ch->thesfx || (ch->leftvol<0.25 && ch->rightvol<0.25 )) { + continue; + } + if (ch->thesfx == sfx) + { + if (s_UseOpenAL) + { + alSourceStop(s_channels[i].alSource); + } + SND_FreeSFXMem(ch->thesfx); // heh, may as well... + ch->thesfx = NULL; + memset(&ch->MP3StreamHeader, 0, sizeof(MP3STREAM)); + ch->bLooping = false; + ch->bProcessed = false; + ch->bPlaying = false; + ch->bStreaming = false; + break; + } + } +} + + +/* +================== +S_StopAllSounds +================== +*/ +void S_StopSounds(void) +{ + int i; //, j; + channel_t *ch; + + if ( !s_soundStarted ) { + return; + } + + // stop looping sounds + S_ClearLoopingSounds(); + + // clear all the s_channels + if (s_UseOpenAL) + { + ch = s_channels; + for (i = 0; i < s_numChannels; i++, ch++) + { + alSourceStop(s_channels[i].alSource); + alSourcei(s_channels[i].alSource, AL_BUFFER, NULL); + ch->thesfx = NULL; + memset(&ch->MP3StreamHeader, 0, sizeof(MP3STREAM)); + ch->bLooping = false; + ch->bProcessed = false; + ch->bPlaying = false; + ch->bStreaming = false; + } + } + else + { + memset(s_channels, 0, sizeof(s_channels)); + } + + // clear out the lip synching override array + memset(s_entityWavVol, 0,sizeof(s_entityWavVol)); + + S_ClearSoundBuffer (); +} + +/* +================== +S_StopAllSounds + and music +================== +*/ +void S_StopAllSounds(void) { + if ( !s_soundStarted ) { + return; + } + // stop the background music + S_StopBackgroundTrack(); + + S_StopSounds(); +} + +/* +============================================================== + +continuous looping sounds are added each frame + +============================================================== +*/ + +/* +================== +S_ClearLoopingSounds + +================== +*/ +void S_ClearLoopingSounds( void ) +{ + int i; + + if (s_UseOpenAL) + { + for (i = 0; i < MAX_LOOP_SOUNDS; i++) + loopSounds[i].bProcessed = false; + } + numLoopSounds = 0; + +} + +/* +================== +S_StopLoopingSound + +Stops all active looping sounds on a specified entity. +Sort of a slow method though, isn't there some better way? +================== +*/ +void S_StopLoopingSound( int entityNum ) +{ + int i = 0; + + while (i < numLoopSounds) + { + if (loopSounds[i].entnum == entityNum) + { + int x = i+1; + while (x < numLoopSounds) + { + memcpy(&loopSounds[x-1], &loopSounds[x], sizeof(loopSounds[x])); + x++; + } + numLoopSounds--; + } + i++; + } +} + +/* +================== +S_AddLoopingSound + +Called during entity generation for a frame +Include velocity in case I get around to doing doppler... +================== +*/ +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { + /*const*/ sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + if ( numLoopSounds >= MAX_LOOP_SOUNDS ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Error( ERR_DROP, "S_AddLoopingSound: handle %i out of range", sfxHandle ); + } + + sfx = &s_knownSfx[ sfxHandle ]; + if (sfx->bInMemory == qfalse){ + S_memoryLoad(sfx); + } + SND_TouchSFX(sfx); + + if ( !sfx->iSoundLengthInSamples ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->sSoundName ); + } + assert(!sfx->pMP3StreamHeader); + VectorCopy( origin, loopSounds[numLoopSounds].origin ); + VectorCopy( velocity, loopSounds[numLoopSounds].velocity ); + loopSounds[numLoopSounds].sfx = sfx; + loopSounds[numLoopSounds].volume = SOUND_MAXVOL; + loopSounds[numLoopSounds].entnum = entityNum; + numLoopSounds++; +} + + +/* +================== +S_AddAmbientLoopingSound +================== +*/ +void S_AddAmbientLoopingSound( const vec3_t origin, unsigned char volume, sfxHandle_t sfxHandle ) +{ + /*const*/ sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + if ( numLoopSounds >= MAX_LOOP_SOUNDS ) { + return; + } + + if (s_UseOpenAL) + { + if (volume == 0) + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Error( ERR_DROP, "S_StartSound: handle %i out of range", sfxHandle ); + } + + sfx = &s_knownSfx[ sfxHandle ]; + if (sfx->bInMemory == qfalse){ + S_memoryLoad(sfx); + } + SND_TouchSFX(sfx); + + if ( !sfx->iSoundLengthInSamples ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->sSoundName ); + } + VectorCopy( origin, loopSounds[numLoopSounds].origin ); + loopSounds[numLoopSounds].sfx = sfx; + assert(!sfx->pMP3StreamHeader); + + //TODO: Calculate the distance falloff + loopSounds[numLoopSounds].volume = volume; + numLoopSounds++; +} + + + +/* +================== +S_AddLoopSounds + +Spatialize all of the looping sounds. +All sounds are on the same cycle, so any duplicates can just +sum up the channel multipliers. +================== +*/ +void S_AddLoopSounds (void) +{ + int i, j; + int left, right, left_total, right_total; + channel_t *ch; + loopSound_t *loop, *loop2; + static int loopFrame; + + loopFrame++; + for ( i = 0 ; i < numLoopSounds ; i++) { + loop = &loopSounds[i]; + if ( loop->mergeFrame == loopFrame ) { + continue; // already merged into an earlier sound + } + + // find the total contribution of all sounds of this type + left_total = right_total = 0; + + for ( j = i ; j < numLoopSounds ; j++) { + loop2 = &loopSounds[j]; + if ( loop2->sfx != loop->sfx ) { + continue; + } + loop2->mergeFrame = loopFrame; // don't check this again later + + S_SpatializeOrigin( loop2->origin, loop2->volume, &left, &right, CHAN_AUTO); //FIXME: Allow for volume change!! + + left_total += left; + right_total += right; + } + + if (left_total == 0 && right_total == 0) + continue; // not audible + + // allocate a channel + ch = S_PickChannel(0, 0); + if (!ch) + return; + + if (left_total > SOUND_MAXVOL) + left_total = SOUND_MAXVOL; + if (right_total > SOUND_MAXVOL) + right_total = SOUND_MAXVOL; + ch->leftvol = left_total; + ch->rightvol = right_total; + ch->loopSound = qtrue; // remove next frame + ch->thesfx = loop->sfx; + + // you cannot use MP3 files here because they offer only streaming access, not random + // + if (loop->sfx->pMP3StreamHeader) + { + Com_Error( ERR_DROP, "S_AddLoopSounds(): Cannot use streamed MP3 files here for random access (%s)\n",loop->sfx->sSoundName ); + } + else + { + memset(&ch->MP3StreamHeader,0, sizeof(ch->MP3StreamHeader)); + } + } +} + +//============================================================================= + +/* +================= +S_ByteSwapRawSamples + +If raw data has been loaded in little endien binary form, this must be done. +If raw data was calculated, as with ADPCM, this should not be called. +================= +*/ +void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { + int i; + + if ( width != 2 ) { + return; + } + if ( LittleShort( 256 ) == 256 ) { + return; + } + + if ( s_channels == 2 ) { + samples <<= 1; + } + for ( i = 0 ; i < samples ; i++ ) { + ((short *)data)[i] = LittleShort( ((short *)data)[i] ); + } +} + + +portable_samplepair_t *S_GetRawSamplePointer() { + return s_rawsamples; +} + + +/* +============ +S_RawSamples + +Music streaming +============ +*/ +void S_RawSamples( int samples, int rate, int width, int s_channels, const byte *data, float volume, int bFirstOrOnlyUpdateThisFrame ) +{ + int i; + int src, dst; + float scale; + int intVolume; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + intVolume = 256 * volume; + + if ( s_rawend < s_soundtime ) { + Com_DPrintf( "S_RawSamples: resetting minimum: %i < %i\n", s_rawend, s_soundtime ); + s_rawend = s_soundtime; + } + + scale = (float)rate / dma.speed; + +//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend); + if (s_channels == 2 && width == 2) + { + if (scale == 1.0) + { // optimized case + if (bFirstOrOnlyUpdateThisFrame) + { + for (i=0 ; i= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = ((short *)data)[src*2] * intVolume; + s_rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume; + } + } + else + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left += ((short *)data)[src*2] * intVolume; + s_rawsamples[dst].right += ((short *)data)[src*2+1] * intVolume; + } + } + } + } + else if (s_channels == 1 && width == 2) + { + if (bFirstOrOnlyUpdateThisFrame) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = ((short *)data)[src] * intVolume; + s_rawsamples[dst].right = ((short *)data)[src] * intVolume; + } + } + else + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left += ((short *)data)[src] * intVolume; + s_rawsamples[dst].right += ((short *)data)[src] * intVolume; + } + } + } + else if (s_channels == 2 && width == 1) + { + intVolume *= 256; + + if (bFirstOrOnlyUpdateThisFrame) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = ((char *)data)[src*2] * intVolume; + s_rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume; + } + } + else + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left += ((char *)data)[src*2] * intVolume; + s_rawsamples[dst].right += ((char *)data)[src*2+1] * intVolume; + } + } + } + else if (s_channels == 1 && width == 1) + { + intVolume *= 256; + + if (bFirstOrOnlyUpdateThisFrame) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume; + s_rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume; + } + } + else + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left += (((byte *)data)[src]-128) * intVolume; + s_rawsamples[dst].right += (((byte *)data)[src]-128) * intVolume; + } + } + } + + if ( s_rawend > s_soundtime + MAX_RAW_SAMPLES ) { + Com_DPrintf( "S_RawSamples: overflowed %i > %i\n", s_rawend, s_soundtime ); + } +} + +//============================================================================= + +/* +===================== +S_UpdateEntityPosition + +let the sound system know where an entity currently is +====================== +*/ +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + ALfloat pos[3]; + channel_t *ch; + int i; + + if ( entityNum < 0 || entityNum > MAX_GENTITIES ) { + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + } + + if (s_UseOpenAL) + { + if (entityNum == 0) + return; + + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + if ((s_channels[i].bPlaying) & (s_channels[i].entnum == entityNum) & (!s_channels[i].bLooping)) + { + // Ignore position updates for CHAN_VOICE_GLOBAL + if (ch->entchannel != CHAN_VOICE_GLOBAL && ch->entchannel != CHAN_ANNOUNCER) + { + pos[0] = origin[0]; + pos[1] = origin[2]; + pos[2] = -origin[1]; + alSourcefv(s_channels[i].alSource, AL_POSITION, pos); + + UpdateEAXBuffer(ch); + } + +/* pos[0] = origin[0]; + pos[1] = origin[2]; + pos[2] = -origin[1]; + alSourcefv(s_channels[i].alSource, AL_POSITION, pos); + + if ((s_bEALFileLoaded) && !( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE_ATTEN || ch->entchannel == CHAN_VOICE_GLOBAL ) ) + { + UpdateEAXBuffer(ch); + } +*/ + } + } + } + + VectorCopy( origin, s_entityPosition[entityNum] ); +} + + +// Given a current wav we are playing, and our position within it, lets figure out its volume... +// +// (this is mostly Jake's code from EF1, which explains a lot...:-) +// +static int next_amplitude = 0; +static int S_CheckAmplitude(channel_t *ch, const unsigned int s_oldpaintedtime ) +{ + // now, is this a cycle - or have we just started a new sample - where we should update the backup table, and write this value + // into the new table? or should we just take the value FROM the back up table and feed it out. + assert( ch->startSample != START_SAMPLE_IMMEDIATE ); + if ( ch->startSample == s_oldpaintedtime || (next_amplitude < s_soundtime) )//(ch->startSample == START_SAMPLE_IMMEDIATE)//!s_entityWavVol_back[ch->entnum] + { + int sample; + int sample_total = 0; + int count = 0; + short *current_pos_s; +// char *current_pos_c; + int offset = 0; + + // if we haven't started the sample yet, we must be at the beginning + current_pos_s = ((short*)ch->thesfx->pSoundData); +// current_pos_c = ((char*)ch->thesfx->data); + + //if (ch->startSample != START_SAMPLE_IMMEDIATE) + //{ + // figure out where we are in the sample right now. + offset = s_oldpaintedtime - ch->startSample;//s_paintedtime + current_pos_s += offset; +// current_pos_c += offset; + //} + + // scan through 10 samples 100( at 11hz or 200 at 22hz) samples apart. + // + for (int i=0; i<10; i++) + { + // + // have we run off the end? + if ((offset + (i*100)) > ch->thesfx->iSoundLengthInSamples) + { + break; + } +// if (ch->thesfx->width == 1) +// { +// sample = current_pos_c[i*100]; +// } +// else + { + switch (ch->thesfx->eSoundCompressionMethod) + { + case ct_16: + { + sample = LittleShort(current_pos_s[i*100]); + } + break; + + case ct_MP3: + { + const int iIndex = (i*100) + ((offset * /*ch->thesfx->width*/2) - ch->iMP3SlidingDecodeWindowPos); + const short* pwSamples = (short*) (ch->MP3SlidingDecodeBuffer + iIndex); + + sample = LittleShort(*pwSamples); + } + break; + + default: + { + assert(0); + sample = 0; + } + break; + } + +// if (sample < 0) +// sample = -sample; + sample = sample>>8; + } + // square it for better accuracy + sample_total += (sample * sample); + count++; + } + + // if we are already done with this sample, then its silence + if (!count) + { + return(0); + } + sample_total /= count; + + // I hate doing this, but its the simplest way + if (sample_total < ch->thesfx->fVolRange * s_lip_threshold_1->value) + { + // tell the scripts that are relying on this that we are still going, but actually silent right now. + sample = -1; + } + else + if (sample_total < ch->thesfx->fVolRange * s_lip_threshold_2->value) + { + sample = 1; + } + else + if (sample_total < ch->thesfx->fVolRange * s_lip_threshold_3->value) + { + sample = 2; + } + else + if (sample_total < ch->thesfx->fVolRange * s_lip_threshold_4->value) + { + sample = 3; + } + else + { + sample = 4; + } + +// OutputDebugString(va("Returning sample %d\n",sample)); + + // store away the value we got into the back up table + s_entityWavVol_back[ ch->entnum ] = sample; + return (sample); + } + // no, just get last value calculated from backup table + assert( s_entityWavVol_back[ch->entnum] ); + return (s_entityWavVol_back[ ch->entnum]); +} +/* +============ +S_Respatialize + +Change the volumes of all the playing sounds for changes in their positions +============ +*/ +void S_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) +{ + EAXOCCLUSIONPROPERTIES eaxOCProp; + EAXACTIVEFXSLOTS eaxActiveSlots; + unsigned int ulEnvironment; + int i; + channel_t *ch; + vec3_t origin; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if (s_UseOpenAL) + { + listener_pos[0] = head[0]; + listener_pos[1] = head[2]; + listener_pos[2] = -head[1]; + alListenerfv(AL_POSITION, listener_pos); + + listener_ori[0] = axis[0][0]; + listener_ori[1] = axis[0][2]; + listener_ori[2] = -axis[0][1]; + listener_ori[3] = axis[2][0]; + listener_ori[4] = axis[2][2]; + listener_ori[5] = -axis[2][1]; + alListenerfv(AL_ORIENTATION, listener_ori); + + // Update EAX effects here + if (s_bEALFileLoaded) + { + // Check if the Listener is underwater + if (inwater) + { + // Check if we have already applied Underwater effect + if (!s_bInWater) + { + // New underwater fix + for (i = 0; i < EAX_MAX_FXSLOTS; i++) + { + s_FXSlotInfo[i].lEnvID = -1; + } + + // Load underwater reverb effect into FX Slot 0, and set this as the Primary FX Slot + ulEnvironment = EAX_ENVIRONMENT_UNDERWATER; + s_eaxSet(&EAXPROPERTYID_EAX40_FXSlot0, EAXREVERB_ENVIRONMENT, + NULL, &ulEnvironment, sizeof(unsigned int)); + s_EnvironmentID = 999; + + s_eaxSet(&EAXPROPERTYID_EAX40_Context, EAXCONTEXT_PRIMARYFXSLOTID, NULL, (ALvoid*)&EAXPROPERTYID_EAX40_FXSlot0, + sizeof(GUID)); + + // Occlude all sounds into this environment, and mute all their sends to other reverbs + eaxOCProp.lOcclusion = -3000; + eaxOCProp.flOcclusionLFRatio = 0.0f; + eaxOCProp.flOcclusionRoomRatio = 1.37f; + eaxOCProp.flOcclusionDirectRatio = 1.0f; + + eaxActiveSlots.guidActiveFXSlots[0] = EAX_NULL_GUID; + eaxActiveSlots.guidActiveFXSlots[1] = EAX_PrimaryFXSlotID; + + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + // New underwater fix + s_channels[i].lSlotID = -1; + + s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_OCCLUSIONPARAMETERS, + ch->alSource, &eaxOCProp, sizeof(EAXOCCLUSIONPROPERTIES)); + + s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_ACTIVEFXSLOTID, ch->alSource, + &eaxActiveSlots, 2*sizeof(GUID)); + } + + s_bInWater = true; + } + } + else + { + // Not underwater ... check if the underwater effect is still present + if (s_bInWater) + { + s_bInWater = false; + + // Remove underwater Reverb effect, and reset Occlusion / Obstruction amount on all Sources + UpdateEAXListener(); + + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + UpdateEAXBuffer(ch); + } + } + else + { + UpdateEAXListener(); + } + } + } + } + else + { + listener_number = entityNum; + VectorCopy(head, listener_origin); + VectorCopy(axis[0], listener_axis[0]); + VectorCopy(axis[1], listener_axis[1]); + VectorCopy(axis[2], listener_axis[2]); + + // update spatialization for dynamic sounds + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx ) { + continue; + } + if ( ch->loopSound ) { // loopSounds are regenerated fresh each frame + Channel_Clear(ch); // memset (ch, 0, sizeof(*ch)); + continue; + } + + // anything coming from the view entity will always be full volume + if (ch->entnum == listener_number) { + ch->leftvol = ch->master_vol; + ch->rightvol = ch->master_vol; + } else { + if (ch->fixed_origin) { + VectorCopy( ch->origin, origin ); + } else { + VectorCopy( s_entityPosition[ ch->entnum ], origin ); + } + + S_SpatializeOrigin (origin, (float)ch->master_vol, &ch->leftvol, &ch->rightvol, ch->entchannel); + } + + //NOTE: Made it so that voice sounds keep playing, even out of range + // so that tasks waiting for sound completion keep proper timing + if ( !( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE_ATTEN || ch->entchannel == CHAN_VOICE_GLOBAL ) && !ch->leftvol && !ch->rightvol ) { + Channel_Clear(ch); // memset (ch, 0, sizeof(*ch)); + continue; + } + } + + // add loopsounds + S_AddLoopSounds (); + } + + return; +} + + +/* +======================== +S_ScanChannelStarts + +Returns qtrue if any new sounds were started since the last mix +======================== +*/ +sboolean S_ScanChannelStarts( void ) { + channel_t *ch; + int i; + sboolean newSamples; + + newSamples = qfalse; + ch = s_channels; + for (i=0; ithesfx ) { + continue; + } + if ( ch->loopSound ) { + continue; + } + + // if this channel was just started this frame, + // set the sample count to it begins mixing + // into the very first sample + if ( ch->startSample == START_SAMPLE_IMMEDIATE ) { + ch->startSample = s_paintedtime; + newSamples = qtrue; + continue; + } + + // if it is completely finished by now, clear it + if ( ch->startSample + ch->thesfx->iSoundLengthInSamples <= s_paintedtime ) { + ch->thesfx = NULL; + continue; + } + } + + return newSamples; +} + +// this is now called AFTER the DMA painting, since it's only the painter calls that cause the MP3s to be unpacked, +// and therefore to have data readable by the lip-sync volume calc code. +// +void S_DoLipSynchs( const unsigned s_oldpaintedtime ) +{ + channel_t *ch; + int i; + sboolean newSamples; + + // clear out the lip synching override array for this frame + memset(s_entityWavVol, 0,(MAX_GENTITIES * 4)); + + newSamples = qfalse; + ch = s_channels; + for (i=0; ithesfx ) { + continue; + } + if ( ch->loopSound ) { + continue; + } + + // if we are playing a sample that should override the lip texture on its owning model, lets figure out + // what the amplitude is, stick it in a table, then return it + if ( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE_ATTEN || ch->entchannel == CHAN_VOICE_GLOBAL ) + { + // go away and work out amplitude for this sound we are playing right now. + s_entityWavVol[ ch->entnum ] = S_CheckAmplitude( ch, s_oldpaintedtime ); + if ( s_show->integer == 3 ) { + Com_Printf( "(%i)%i %s vol = %i\n", ch->entnum, i, ch->thesfx->sSoundName, s_entityWavVol[ ch->entnum ] ); + } + } + } + + if (next_amplitude < s_soundtime) { + next_amplitude = s_soundtime + 800; + } +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Update( void ) { + int i; + int total; + channel_t *ch; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + // + // debugging output + // + if ( s_show->integer == 2 ) { + total = 0; + int totalMeg =0; + ch = s_channels; + for (i=0 ; ithesfx && (ch->leftvol || ch->rightvol) ) { + Com_Printf ("(%i) %3i %3i %s\n", ch->entnum, ch->leftvol, ch->rightvol, ch->thesfx->sSoundName); + total++; + totalMeg += Z_Size(ch->thesfx->pSoundData); + if (ch->thesfx->pMP3StreamHeader) + { + totalMeg += sizeof(*ch->thesfx->pMP3StreamHeader); + } + } + } + + if (total) + Com_Printf ("----(%i)---- painted: %i, SND %.2fMB\n", total, s_paintedtime, totalMeg/1024.0f/1024.0f); + } + + // The Open AL code, handles background music in the S_UpdateRawSamples function + if (!s_UseOpenAL) + { + // add raw data from streamed samples + S_UpdateBackgroundTrack(); + } + + // mix some sound + S_Update_(); +} + +void S_GetSoundtime(void) +{ + int samplepos; + static int buffers; + static int oldsamplepos; + int fullsamples; + + fullsamples = dma.samples / dma.channels; + + // it is possible to miscount buffers if it has wrapped twice between + // calls to S_Update. Oh well. + samplepos = SNDDMA_GetDMAPos(); + if (samplepos < oldsamplepos) + { + buffers++; // buffer wrapped + + if (s_paintedtime > 0x40000000) + { // time to chop things off to avoid 32 bit limits + buffers = 0; + s_paintedtime = fullsamples; + S_StopAllSounds (); + } + } + oldsamplepos = samplepos; + + s_soundtime = buffers*fullsamples + samplepos/dma.channels; + +#if 0 +// check to make sure that we haven't overshot + if (s_paintedtime < s_soundtime) + { + Com_DPrintf ("S_Update_ : overflow\n"); + s_paintedtime = s_soundtime; + } +#endif + + if ( dma.submission_chunk < 256 ) { + s_paintedtime = (int)(s_soundtime + s_mixPreStep->value * dma.speed); + } else { + s_paintedtime = s_soundtime + dma.submission_chunk; + } +} + + +void S_Update_(void) { + unsigned endtime; + int samps; + channel_t *ch; + int i,j; + int source; + float pos[3]; +#ifdef _DEBUG + char szString[256]; +#endif + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if (s_UseOpenAL) + { + UpdateSingleShotSounds(); + + ch = s_channels + 1; + for ( i = 1; i < MAX_CHANNELS ; i++, ch++ ) + { + if ( !ch->thesfx || (ch->bPlaying)) + continue; + + source = ch - s_channels; + + if (ch->entchannel == CHAN_VOICE_GLOBAL || ch->entchannel == CHAN_ANNOUNCER) + { + // Always play these sounds at 0,0,-1 (in front of listener) + pos[0] = 0.0f; + pos[1] = 0.0f; + pos[2] = -1.0f; + + alSourcefv(s_channels[source].alSource, AL_POSITION, pos); + alSourcei(s_channels[source].alSource, AL_LOOPING, AL_FALSE); + alSourcei(s_channels[source].alSource, AL_SOURCE_RELATIVE, AL_TRUE); + if (ch->entchannel == CHAN_ANNOUNCER) + { + alSourcef(s_channels[source].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volume->value) / 255.0f); + } + else + { + alSourcef(s_channels[source].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volumeVoice->value) / 255.0f); + } + } + else + { + // Get position of source + if (ch->fixed_origin) + { + pos[0] = ch->origin[0]; + pos[1] = ch->origin[2]; + pos[2] = -ch->origin[1]; + alSourcei(s_channels[source].alSource, AL_SOURCE_RELATIVE, AL_FALSE); + } + else + { + if (ch->entnum == listener_number) + { + pos[0] = 0.0f; + pos[1] = 0.0f; + pos[2] = 0.0f; + alSourcei(s_channels[source].alSource, AL_SOURCE_RELATIVE, AL_TRUE); + } + else + { + // Get position of Entity + if (ch->bLooping) + { + pos[0] = loopSounds[ ch->entnum ].origin[0]; + pos[1] = loopSounds[ ch->entnum ].origin[2]; + pos[2] = -loopSounds[ ch->entnum ].origin[1]; + } + else + { + pos[0] = s_entityPosition[ch->entnum][0]; + pos[1] = s_entityPosition[ch->entnum][2]; + pos[2] = -s_entityPosition[ch->entnum][1]; + } + alSourcei(s_channels[source].alSource, AL_SOURCE_RELATIVE, AL_FALSE); + } + } + + alSourcefv(s_channels[source].alSource, AL_POSITION, pos); + alSourcei(s_channels[source].alSource, AL_LOOPING, AL_FALSE); + + if (ch->entchannel == CHAN_VOICE) + { + // Reduced fall-off (Large Reference Distance), affected by Voice Volume + alSourcef(s_channels[source].alSource, AL_REFERENCE_DISTANCE, DEFAULT_VOICE_REF_DISTANCE); + alSourcef(s_channels[source].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volumeVoice->value) / 255.0f); + } + else if (ch->entchannel == CHAN_VOICE_ATTEN) + { + // Normal fall-off, affected by Voice Volume + alSourcef(s_channels[source].alSource, AL_REFERENCE_DISTANCE, DEFAULT_REF_DISTANCE); + alSourcef(s_channels[source].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volumeVoice->value) / 255.0f); + } + else if (ch->entchannel == CHAN_LESS_ATTEN) + { + // Reduced fall-off, affected by Sound Effect Volume + alSourcef(s_channels[source].alSource, AL_REFERENCE_DISTANCE, DEFAULT_VOICE_REF_DISTANCE); + alSourcef(s_channels[source].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volume->value) / 255.f); + } + else + { + // Normal fall-off, affect by Sound Effect Volume + alSourcef(s_channels[source].alSource, AL_REFERENCE_DISTANCE, DEFAULT_REF_DISTANCE); + alSourcef(s_channels[source].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volume->value) / 255.f); + } + } + + if (s_bEALFileLoaded) + UpdateEAXBuffer(ch); + + int nBytesDecoded = 0; + int nTotalBytesDecoded = 0; + int nBuffersToAdd = 0; + + if (ch->thesfx->pMP3StreamHeader) + { + memcpy(&ch->MP3StreamHeader, ch->thesfx->pMP3StreamHeader, sizeof(ch->MP3StreamHeader)); + ch->iMP3SlidingDecodeWritePos = 0; + ch->iMP3SlidingDecodeWindowPos= 0; + + // Reset streaming buffers status's + for (i = 0; i < NUM_STREAMING_BUFFERS; i++) + ch->buffers[i].Status = UNQUEUED; + + // Decode (STREAMING_BUFFER_SIZE / 1152) MP3 frames for each of the NUM_STREAMING_BUFFERS AL Buffers + for (i = 0; i < NUM_STREAMING_BUFFERS; i++) + { + nTotalBytesDecoded = 0; + + for (j = 0; j < (STREAMING_BUFFER_SIZE / 1152); j++) + { + nBytesDecoded = C_MP3Stream_Decode(&ch->MP3StreamHeader, 0); // added ,0 ? + memcpy(ch->buffers[i].Data + nTotalBytesDecoded, ch->MP3StreamHeader.bDecodeBuffer, nBytesDecoded); + if (ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE_ATTEN || ch->entchannel == CHAN_VOICE_GLOBAL ) + { + if (ch->thesfx->lipSyncData) + { + ch->thesfx->lipSyncData[(i*NUM_STREAMING_BUFFERS)+j] = S_MP3PreProcessLipSync(ch, (short *)(ch->MP3StreamHeader.bDecodeBuffer)); + } + else + { +#ifdef _DEBUG + sprintf(szString, "Missing lip-sync info. for %s\n", ch->thesfx->sSoundName); + OutputDebugString(szString); +#endif + } + } + nTotalBytesDecoded += nBytesDecoded; + } + + if (nTotalBytesDecoded != STREAMING_BUFFER_SIZE) + { + memset(ch->buffers[i].Data + nTotalBytesDecoded, 0, (STREAMING_BUFFER_SIZE - nTotalBytesDecoded)); + break; + } + } + + if (i >= NUM_STREAMING_BUFFERS) + nBuffersToAdd = NUM_STREAMING_BUFFERS; + else + nBuffersToAdd = i + 1; + + // Make sure queue is empty first + alSourcei(s_channels[source].alSource, AL_BUFFER, NULL); + + for (i = 0; i < nBuffersToAdd; i++) + { + // Copy decoded data to AL Buffer + alBufferData(ch->buffers[i].BufferID, AL_FORMAT_MONO16, ch->buffers[i].Data, STREAMING_BUFFER_SIZE, 22050); + + // Queue AL Buffer on Source + alSourceQueueBuffers(s_channels[source].alSource, 1, &(ch->buffers[i].BufferID)); + if (alGetError() == AL_NO_ERROR) + { + ch->buffers[i].Status = QUEUED; + } + } + + // Clear error state, and check for successful Play call + alGetError(); + alSourcePlay(s_channels[source].alSource); + if (alGetError() == AL_NO_ERROR) + s_channels[source].bPlaying = true; + + ch->bStreaming = true; + + if ( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE_ATTEN || ch->entchannel == CHAN_VOICE_GLOBAL ) + { + if (ch->thesfx->lipSyncData) + { + // Record start time for Lip-syncing + s_channels[source].iStartTime = timeGetTime(); + + // Prepare lipsync value(s) + s_entityWavVol[ ch->entnum ] = ch->thesfx->lipSyncData[0]; + } + else + { +#ifdef _DEBUG + sprintf(szString, "Missing lip-sync info. for %s\n", ch->thesfx->sSoundName); + OutputDebugString(szString); +#endif + } + } + + return; + } + else + { + // Attach buffer to source + alSourcei(s_channels[source].alSource, AL_BUFFER, ch->thesfx->Buffer); + + ch->bStreaming = false; + + // Clear error state, and check for successful Play call + alGetError(); + alSourcePlay(s_channels[source].alSource); + if (alGetError() == AL_NO_ERROR) + s_channels[source].bPlaying = true; + + if ( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE_ATTEN || ch->entchannel == CHAN_VOICE_GLOBAL ) + { + if (ch->thesfx->lipSyncData) + { + // Record start time for Lip-syncing + s_channels[source].iStartTime = timeGetTime(); + + // Prepare lipsync value(s) + s_entityWavVol[ ch->entnum ] = ch->thesfx->lipSyncData[0]; + } + else + { +#ifdef _DEBUG + sprintf(szString, "Missing lip-sync info. for %s\n", ch->thesfx->sSoundName); + OutputDebugString(szString); +#endif + } + } + } + } + + S_SetLipSyncs(); + + UpdateLoopingSounds(); + + AL_UpdateRawSamples(); + } + else + { + // Updates s_soundtime + S_GetSoundtime(); + + const unsigned s_oldpaintedtime = s_paintedtime; + + // clear any sound effects that end before the current time, + // and start any new sounds + S_ScanChannelStarts(); + + // mix ahead of current position + endtime = (int)(s_soundtime + s_mixahead->value * dma.speed); + + // mix to an even submission block size + endtime = (endtime + dma.submission_chunk-1) + & ~(dma.submission_chunk-1); + + // never mix more than the complete buffer + samps = dma.samples >> (dma.channels-1); + if (endtime - s_soundtime > samps) + endtime = s_soundtime + samps; + + + SNDDMA_BeginPainting (); + + S_PaintChannels (endtime); + + SNDDMA_Submit (); + + S_DoLipSynchs( s_oldpaintedtime ); + } +} + + +void UpdateSingleShotSounds() +{ + int i, j, k; + ALint state; + ALint processed; + channel_t *ch; +#ifdef _DEBUG + char szString[256]; +#endif + + // Firstly, check if any single-shot sounds have completed, or if they need more data (for streaming Sources), + // and/or if any of the currently playing (non-Ambient) looping sounds need to be stopped + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + ch->bProcessed = false; + if ((s_channels[i].bPlaying) && (!ch->bLooping)) + { + // Single-shot + if (s_channels[i].bStreaming == false) + { + alGetSourcei(s_channels[i].alSource, AL_SOURCE_STATE, &state); + if (state == AL_STOPPED) + { + s_channels[i].thesfx = NULL; + s_channels[i].bPlaying = false; + } + } + else + { + // Process streaming sample + + // Procedure :- + // if more data to play + // if any UNQUEUED Buffers + // fill them with data + // (else ?) + // get number of buffers processed + // fill them with data + // restart playback if it has stopped (buffer underrun) + // else + // free channel + + int nBytesDecoded; + + if (ch->thesfx->pMP3StreamHeader) + { + if (ch->MP3StreamHeader.iSourceBytesRemaining == 0) + { + // Finished decoding data - if the source has finished playing then we're done + alGetSourcei(ch->alSource, AL_SOURCE_STATE, &state); + if (state == AL_STOPPED) + { + // Attach NULL buffer to Source to remove any buffers left in the queue + alSourcei(ch->alSource, AL_BUFFER, NULL); + ch->thesfx = NULL; + ch->bPlaying = false; + } + // Move on to next channel ... + continue; + } + + // Check to see if any Buffers have been processed + alGetSourcei(ch->alSource, AL_BUFFERS_PROCESSED, &processed); + + ALuint buffer; + while (processed) + { + alSourceUnqueueBuffers(ch->alSource, 1, &buffer); + for (j = 0; j < NUM_STREAMING_BUFFERS; j++) + { + if (ch->buffers[j].BufferID == buffer) + { + ch->buffers[j].Status = UNQUEUED; + break; + } + } + processed--; + } + + int nTotalBytesDecoded = 0; + + for (j = 0; j < NUM_STREAMING_BUFFERS; j++) + { + if ((ch->buffers[j].Status == UNQUEUED) & (ch->MP3StreamHeader.iSourceBytesRemaining > 0)) + { + nTotalBytesDecoded = 0; + + for (k = 0; k < (STREAMING_BUFFER_SIZE / 1152); k++) + { + nBytesDecoded = C_MP3Stream_Decode(&ch->MP3StreamHeader, 0); // added ,0 + + if (nBytesDecoded > 0) + { + memcpy(ch->buffers[j].Data + nTotalBytesDecoded, ch->MP3StreamHeader.bDecodeBuffer, nBytesDecoded); + + if (ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE_ATTEN || ch->entchannel == CHAN_VOICE_GLOBAL ) + { + if (ch->thesfx->lipSyncData) + { + ch->thesfx->lipSyncData[(j*4)+k] = S_MP3PreProcessLipSync(ch, (short *)(ch->buffers[j].Data)); + } + else + { +#ifdef _DEBUG + sprintf(szString, "Missing lip-sync info. for %s\n", ch->thesfx->sSoundName); + OutputDebugString(szString); +#endif + } + } + nTotalBytesDecoded += nBytesDecoded; + } + else + { + // Make sure that iSourceBytesRemaining is 0 + if (ch->MP3StreamHeader.iSourceBytesRemaining != 0) + { + ch->MP3StreamHeader.iSourceBytesRemaining = 0; + break; + } + } + } + + if (nTotalBytesDecoded != STREAMING_BUFFER_SIZE) + { + memset(ch->buffers[j].Data + nTotalBytesDecoded, 0, (STREAMING_BUFFER_SIZE - nTotalBytesDecoded)); + + // Move data to buffer + alBufferData(ch->buffers[j].BufferID, AL_FORMAT_MONO16, ch->buffers[j].Data, STREAMING_BUFFER_SIZE, 22050); + + // Queue Buffer on Source + alSourceQueueBuffers(ch->alSource, 1, &(ch->buffers[j].BufferID)); + + // Update status of Buffer + ch->buffers[j].Status = QUEUED; + + break; + } + else + { + // Move data to buffer + alBufferData(ch->buffers[j].BufferID, AL_FORMAT_MONO16, ch->buffers[j].Data, STREAMING_BUFFER_SIZE, 22050); + + // Queue Buffer on Source + alSourceQueueBuffers(ch->alSource, 1, &(ch->buffers[j].BufferID)); + + // Update status of Buffer + ch->buffers[j].Status = QUEUED; + } + } + } + + // Get state of Buffer + alGetSourcei(ch->alSource, AL_SOURCE_STATE, &state); + if (state != AL_PLAYING) + { + alSourcePlay(ch->alSource); +#ifdef _DEBUG + char szString[256]; + sprintf(szString,"[%d] Restarting playback of single-shot streaming MP3 sample - still have %d bytes to decode\n", i, ch->MP3StreamHeader.iSourceBytesRemaining); + OutputDebugString(szString); +#endif + } + } + } + } + } +} + + + + +void UpdateLoopingSounds() +{ + int i,j; + ALuint source; + channel_t *ch; + loopSound_t *loop; + float pos[3]; + + // First check to see if any of the looping sounds are already playing at the correct positions + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + if (ch->bLooping && s_channels[i].bPlaying) + { + for (j = 0; j < numLoopSounds; j++) + { + loop = &loopSounds[j]; + + // If this channel is playing the right sound effect at the right position then mark this channel and looping sound + // as processed + if ((loop->bProcessed == false) && (ch->thesfx == loop->sfx) ) + { + if ( (loop->origin[0] == listener_pos[0]) && (loop->origin[1] == -listener_pos[2]) + && (loop->origin[2] == listener_pos[1]) ) + { + // Assume that this sound is head relative + if (!loop->bRelative) + { + // Set position to 0,0,0 and turn on Head Relative Mode + float pos[3]; + pos[0] = 0.f; + pos[1] = 0.f; + pos[2] = 0.f; + + alSourcefv(s_channels[i].alSource, AL_POSITION, pos); + alSourcei(s_channels[i].alSource, AL_SOURCE_RELATIVE, AL_TRUE); + loop->bRelative = true; + } + + // Make sure Gain is set correctly + if (ch->master_vol != loop->volume) + { + ch->master_vol = loop->volume; + alSourcef(s_channels[i].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volume->value) / 255.f); + } + + ch->bProcessed = true; + loop->bProcessed = true; + } + else if ((loop->bProcessed == false) && (ch->thesfx == loop->sfx) && (!memcmp(ch->origin, loop->origin, sizeof(ch->origin)))) + { + // Match ! + ch->bProcessed = true; + loop->bProcessed = true; + + // Make sure Gain is set correctly + if (ch->master_vol != loop->volume) + { + ch->master_vol = loop->volume; + alSourcef(s_channels[i].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volume->value) / 255.f); + } + + break; + } + } + } + } + } + + // Next check if the correct looping sound is playing, but at the wrong position + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + if ((ch->bLooping) && (ch->bProcessed == false) && s_channels[i].bPlaying) + { + for (j = 0; j < numLoopSounds; j++) + { + loop = &loopSounds[j]; + + if ((loop->bProcessed == false) && (ch->thesfx == loop->sfx)) + { + // Same sound - wrong position + ch->origin[0] = loop->origin[0]; + ch->origin[1] = loop->origin[1]; + ch->origin[2] = loop->origin[2]; + + pos[0] = loop->origin[0]; + pos[1] = loop->origin[2]; + pos[2] = -loop->origin[1]; + alSourcefv(s_channels[i].alSource, AL_POSITION, pos); + + ch->master_vol = loop->volume; + alSourcef(s_channels[i].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volume->value) / 255.f); + + if (s_bEALFileLoaded) + UpdateEAXBuffer(ch); + + ch->bProcessed = true; + loop->bProcessed = true; + break; + } + } + } + } + + // If any non-procesed looping sounds are still playing on a channel, they can be removed as they are no longer + // required + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + if (s_channels[i].bPlaying && ch->bLooping && !ch->bProcessed) + { + // Sound no longer needed + alSourceStop(s_channels[i].alSource); + ch->thesfx = NULL; + ch->bPlaying = false; + } + } + +#ifdef _DEBUG + alGetError(); +#endif + // Finally if there are any non-processed sounds left, we need to try and play them + for (j = 0; j < numLoopSounds; j++) + { + loop = &loopSounds[j]; + + if (loop->bProcessed == false) + { + ch = S_PickChannel(0,0); + + ch->master_vol = loop->volume; + ch->entnum = loop->entnum; + ch->entchannel = CHAN_AMBIENT; // Make sure this gets set to something + ch->thesfx = loop->sfx; + ch->bLooping = true; + + // Check if the Source is positioned at exactly the same location as the listener + if ( (loop->origin[0] == listener_pos[0]) && (loop->origin[1] == -listener_pos[2]) + && (loop->origin[2] == listener_pos[1]) ) + { + // Assume that this sound is head relative + loop->bRelative = true; + ch->origin[0] = 0.f; + ch->origin[1] = 0.f; + ch->origin[2] = 0.f; + } + else + { + ch->origin[0] = loop->origin[0]; + ch->origin[1] = loop->origin[1]; + ch->origin[2] = loop->origin[2]; + loop->bRelative = false; + } + + ch->fixed_origin = loop->bRelative; + pos[0] = ch->origin[0]; + pos[1] = ch->origin[2]; + pos[2] = -ch->origin[1]; + + source = ch - s_channels; + alSourcei(s_channels[source].alSource, AL_BUFFER, ch->thesfx->Buffer); + alSourcefv(s_channels[source].alSource, AL_POSITION, pos); + alSourcei(s_channels[source].alSource, AL_LOOPING, AL_TRUE); + alSourcef(s_channels[source].alSource, AL_REFERENCE_DISTANCE, DEFAULT_REF_DISTANCE); + alSourcef(s_channels[source].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volume->value) / 255.0f); + alSourcei(s_channels[source].alSource, AL_SOURCE_RELATIVE, ch->fixed_origin ? AL_TRUE : AL_FALSE); + + if (s_bEALFileLoaded) + UpdateEAXBuffer(ch); + + alGetError(); + alSourcePlay(s_channels[source].alSource); + if (alGetError() == AL_NO_ERROR) + s_channels[source].bPlaying = true; + } + } +} + + + + + +void AL_UpdateRawSamples() +{ + ALuint buffer; + ALint size; + ALint processed; + ALint state; + int i,j,src; + +#ifdef _DEBUG + // Clear Open AL Error + alGetError(); +#endif + + S_UpdateBackgroundTrack(); + + // Find out how many buffers have been processed (played) by the Source + alGetSourcei(s_channels[0].alSource, AL_BUFFERS_PROCESSED, &processed); + + while (processed) + { + // Unqueue each buffer, determine the length of the buffer, and then delete it + alSourceUnqueueBuffers(s_channels[0].alSource, 1, &buffer); + alGetBufferi(buffer, AL_SIZE, &size); + alDeleteBuffers(1, &buffer); + + // Update sg.soundtime (+= number of samples played (number of bytes / 4)) + s_soundtime += (size >> 2); + + processed--; + } + + // Add new data to a new Buffer and queue it on the Source + if (s_rawend > s_paintedtime) + { + size = (s_rawend - s_paintedtime)<<2; + if (size > (MAX_RAW_SAMPLES<<2)) + { + OutputDebugString("UpdateRawSamples :- Raw Sample buffer has overflowed !!!\n"); + size = MAX_RAW_SAMPLES<<2; + s_paintedtime = s_rawend - MAX_RAW_SAMPLES; + } + + // Copy samples from RawSamples to audio buffer (sg.rawdata) + for (i = s_paintedtime, j = 0; i < s_rawend; i++, j+=2) + { + src = i & (MAX_RAW_SAMPLES - 1); + s_rawdata[j] = (short)(s_rawsamples[src].left>>8); + s_rawdata[j+1] = (short)(s_rawsamples[src].right>>8); + } + + // Need to generate more than 1 buffer for music playback + // iterations = 0; + // largestBufferSize = (MAX_RAW_SAMPLES / 4) * 4 + // while (size) + // generate a buffer + // if size > largestBufferSize + // copy sg.rawdata + ((iterations * largestBufferSize)>>1) to buffer + // size -= largestBufferSize + // else + // copy remainder + // size = 0 + // queue the buffer + // iterations++; + + int iterations = 0; + int largestBufferSize = MAX_RAW_SAMPLES; // in bytes (== quarter of Raw Samples data) + while (size) + { + alGenBuffers(1, &buffer); + + if (size > largestBufferSize) + { + alBufferData(buffer, AL_FORMAT_STEREO16, (char*)(s_rawdata + ((iterations * largestBufferSize)>>1)), largestBufferSize, 22050); + size -= largestBufferSize; + } + else + { + alBufferData(buffer, AL_FORMAT_STEREO16, (char*)(s_rawdata + ((iterations * largestBufferSize)>>1)), size, 22050); + size = 0; + } + + alSourceQueueBuffers(s_channels[0].alSource, 1, &buffer); + iterations++; + } + + // Update paintedtime + s_paintedtime = s_rawend; + + // Check that the Source is actually playing + alGetSourcei(s_channels[0].alSource, AL_SOURCE_STATE, &state); + if (state != AL_PLAYING) + { + // Stopped playing ... due to buffer underrun + // Unqueue any buffers still on the Source (they will be PROCESSED), and restart playback + alGetSourcei(s_channels[0].alSource, AL_BUFFERS_PROCESSED, &processed); + while (processed) + { + alSourceUnqueueBuffers(s_channels[0].alSource, 1, &buffer); + processed--; + alGetBufferi(buffer, AL_SIZE, &size); + alDeleteBuffers(1, &buffer); + + // Update sg.soundtime (+= number of samples played (number of bytes / 4)) + s_soundtime += (size >> 2); + } + +#ifdef _DEBUG + OutputDebugString("Restarting / Starting playback of Raw Samples\n"); +#endif + alSourcePlay(s_channels[0].alSource); + } + } + +#ifdef _DEBUG + if (alGetError() != AL_NO_ERROR) + OutputDebugString("OAL Error : UpdateRawSamples\n"); +#endif +} + + +int S_MP3PreProcessLipSync(channel_t *ch, short *data) +{ + int i; + int sample; + int sampleTotal = 0; + + for (i = 0; i < 576; i += 100) + { + sample = LittleShort(data[i]); + sample = sample >> 8; + sampleTotal += sample * sample; + } + + sampleTotal /= 6; + + if (sampleTotal < ch->thesfx->fVolRange * s_lip_threshold_1->value) + sample = -1; + else if (sampleTotal < ch->thesfx->fVolRange * s_lip_threshold_2->value) + sample = 1; + else if (sampleTotal < ch->thesfx->fVolRange * s_lip_threshold_3->value) + sample = 2; + else if (sampleTotal < ch->thesfx->fVolRange * s_lip_threshold_4->value) + sample = 3; + else + sample = 4; + + return sample; +} + + +void S_SetLipSyncs() +{ + int i; + unsigned int samples; + int currentTime, timePlayed; + channel_t *ch; +#ifdef _DEBUG + char szString[256]; +#endif + + currentTime = timeGetTime(); + + memset(s_entityWavVol, 0, sizeof(s_entityWavVol)); + + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + if ((!ch->thesfx)||(!ch->bPlaying)) + continue; + + if ( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE_ATTEN || ch->entchannel == CHAN_VOICE_GLOBAL ) + { + // Calculate how much time has passed since the sample was started + timePlayed = currentTime - ch->iStartTime; + + if (ch->thesfx->eSoundCompressionMethod==ct_16) + { + // There is a new computed lip-sync value every 1000 samples - so find out how many samples + // have been played and lookup the value in the lip-sync table + samples = (timePlayed * 22050) / 1000; + + if (ch->thesfx->lipSyncData == NULL) + { +#ifdef _DEBUG + sprintf(szString, "Missing lip-sync info. for %s\n", ch->thesfx->sSoundName); + OutputDebugString(szString); +#endif + } + + if ((ch->thesfx->lipSyncData) && (samples < ch->thesfx->iSoundLengthInSamples)) + { + s_entityWavVol[ ch->entnum ] = ch->thesfx->lipSyncData[samples / 1000]; + if ( s_show->integer == 3 ) + { + Com_Printf( "(%i)%i %s vol = %i\n", ch->entnum, i, ch->thesfx->sSoundName, s_entityWavVol[ ch->entnum ] ); + } + } + } + else + { + // MP3 + + // There is a new computed lip-sync value every 576 samples - so find out how many samples + // have been played and lookup the value in the lip-sync table + samples = (timePlayed * 22050) / 1000; + + if (ch->thesfx->lipSyncData == NULL) + { +#ifdef _DEBUG + sprintf(szString, "Missing lip-sync info. for %s\n", ch->thesfx->sSoundName); + OutputDebugString(szString); +#endif + } + + if ((ch->thesfx->lipSyncData) && (samples < ch->thesfx->iSoundLengthInSamples)) + { + s_entityWavVol[ ch->entnum ] = ch->thesfx->lipSyncData[(samples / 576) % 16]; + + if ( s_show->integer == 3 ) + { + Com_Printf( "(%i)%i %s vol = %i\n", ch->entnum, i, ch->thesfx->sSoundName, s_entityWavVol[ ch->entnum ] ); + } + } + } + } + } +} + + +/* +=============================================================================== + +console functions + +=============================================================================== +*/ + +static void S_Play_f( void ) { + int i; + sfxHandle_t h; + char name[256]; + + i = 1; + while ( i [loopfile]\n"); + return; + } +} + +// a debug function, but no harm to leave in... +// +static void S_SetDynamicMusic_f(void) +{ + int c = Cmd_Argc(); + + if ( c == 2 ) + { + if (bMusic_IsDynamic) + { + // don't need to check existance of 'explore' or 'action' music, since music wouldn't + // be counted as dynamic if either were missing, but other types are optional... + // + if (!Q_stricmp(Cmd_Argv(1),"explore")) + { + S_SetDynamicMusicState( eBGRNDTRACK_EXPLORE ); + return; + } + else + if (!Q_stricmp(Cmd_Argv(1),"action")) + { + S_SetDynamicMusicState( eBGRNDTRACK_ACTION ); + return; + } + else + if (!Q_stricmp(Cmd_Argv(1),"silence")) + { + S_SetDynamicMusicState( eBGRNDTRACK_SILENCE ); + return; + } + else + if (!Q_stricmp(Cmd_Argv(1),"boss")) + { + if (tMusic_Info[ eBGRNDTRACK_BOSS ].bExists) + { + S_SetDynamicMusicState( eBGRNDTRACK_BOSS ); + } + else + { + Com_Printf("No 'boss' music defined in current dynamic set\n"); + } + return; + } + else + if (!Q_stricmp(Cmd_Argv(1),"death")) + { + if (tMusic_Info[ eBGRNDTRACK_DEATH ].bExists) + { + S_SetDynamicMusicState( eBGRNDTRACK_DEATH ); + } + else + { + Com_Printf("No 'death' music defined in current dynamic set\n"); + } + return; + } + } + else + { + DynamicMusicInfoPrint(); // print "inactive" string + return; + } + } + + // show usage... + // + Com_Printf("Usage: s_dynamic \n"); + DynamicMusicInfoPrint(); +} + + +// this table needs to be in-sync with the typedef'd enum "SoundCompressionMethod_t"... -ste +// +static const char *sSoundCompressionMethodStrings[ct_NUMBEROF] = +{ + "16b", // ct_16 + "mp3" // ct_MP3 +}; +void S_SoundList_f( void ) { + int i; + sfx_t *sfx; + int size, total; + int iVariantCap = -1; // for %d-inquiry stuff + int iTotalBytes = 0; + + sboolean bWavOnly = qfalse; + sboolean bShouldBeMP3 = qfalse; + + if ( Cmd_Argc() == 2 ) + { + if (!stricmp(Cmd_Argv(1), "shouldbeMP3")) + { + bShouldBeMP3 = qtrue; + } + else + if (!stricmp(Cmd_Argv(1), "wavonly")) + { + bWavOnly = qtrue; + } + else + { + if (!stricmp(Cmd_Argv(1), "1")) + { + iVariantCap = 1; + } + else + if (!stricmp(Cmd_Argv(1), "2")) + { + iVariantCap = 2; + } + else + if (!stricmp(Cmd_Argv(1), "3")) + { + iVariantCap = 3; + } + } + } + else + { + Com_Printf("( additional (mutually exclusive) options available:\n'wavonly', 'ShouldBeMP3', '1'/'2'/'3' for %%d-variant capping )\n" ); + } + + + + total = 0; + + Com_Printf("\n"); + Com_Printf(" InMemory?\n"); + Com_Printf(" |\n"); + Com_Printf(" | LevelLastUsedOn\n"); + Com_Printf(" | |\n"); + Com_Printf(" | |\n"); + Com_Printf(" Slot Smpls Type | | Name\n"); +// Com_Printf(" Slot Smpls Type InMem? Name\n"); + + for (sfx=s_knownSfx, i=0 ; ibDefaultSound && !sfx->pMP3StreamHeader && sfx->pSoundData && (Z_Size(sfx->pSoundData) > cv_MP3overhead->integer); + + if (bMP3DumpOverride || (!bShouldBeMP3 && (!bWavOnly || sfx->eSoundCompressionMethod == ct_16))) + { + sboolean bDumpThisOne = qtrue; + if (iVariantCap >= 1 && iVariantCap <= 3) + { + int iStrLen = strlen(sfx->sSoundName); + if (iStrLen > 2) // crash-safety, jic. + { + char c = sfx->sSoundName[iStrLen-1]; + char c2 = sfx->sSoundName[iStrLen-2]; + if (!isdigit(c2) // quick-avoid of stuff like "pain75" + && isdigit(c) && atoi(va("%c",c)) > iVariantCap) + { + // need to see if this %d-variant should be omitted, in other words if there's a %1 version then skip this... + // + char sFindName[MAX_QPATH]; + Q_strncpyz(sFindName,sfx->sSoundName,sizeof(sFindName)); + sFindName[iStrLen-1] = '1'; + int i2; + sfx_t *sfx2; + for (sfx2 = s_knownSfx, i2=0 ; i2sSoundName)) + { + bDumpThisOne = qfalse; // found a %1-variant of this, so use variant capping and ignore this sfx_t + break; + } + } + } + } + } + + size = sfx->iSoundLengthInSamples; + if (sfx->bDefaultSound) + { + Com_Printf("%5d Missing file: \"%s\"\n", i, sfx->sSoundName ); + } + else + { + if (bDumpThisOne) + { + iTotalBytes += (sfx->bInMemory && sfx->pSoundData) ? Z_Size(sfx->pSoundData) : 0; + iTotalBytes += (sfx->bInMemory && sfx->pMP3StreamHeader) ? sizeof(*sfx->pMP3StreamHeader) : 0; + total += sfx->bInMemory ? size : 0; + } + Com_Printf("%5d %7i [%s] %s %2d %s", i, size, sSoundCompressionMethodStrings[sfx->eSoundCompressionMethod], sfx->bInMemory?"y":"n", sfx->iLastLevelUsedOn, sfx->sSoundName ); + + if (!bDumpThisOne) + { + Com_Printf(" ( Skipping, variant capped )"); + //OutputDebugString(va("Variant capped: %s\n",sfx->sSoundName)); + } + Com_Printf("\n"); + } + } + } + Com_Printf(" Slot Smpls Type In? Lev Name\n"); + + + Com_Printf ("Total resident samples: %i %s ( not mem usage, see 'meminfo' ).\n", total, bWavOnly?"(WAV only)":""); + Com_Printf ("%d out of %d sfx_t slots used\n", s_numSfx, MAX_SFX); + Com_Printf ("%.2fMB bytes used when counting sfx_t->pSoundData + MP3 headers (if any)\n", (float)iTotalBytes / 1024.0f / 1024.0f); + S_DisplayFreeMemory(); +} + + +/* +=============================================================================== + +background music functions + +=============================================================================== +*/ + +int FGetLittleLong( fileHandle_t f ) { + int v; + + FS_Read( &v, sizeof(v), f ); + + return LittleLong( v); +} + +int FGetLittleShort( fileHandle_t f ) { + short v; + + FS_Read( &v, sizeof(v), f ); + + return LittleShort( v); +} + +// returns the length of the data in the chunk, or 0 if not found +int S_FindWavChunk( fileHandle_t f, char *chunk ) { + char name[5]; + int len; + int r; + + name[4] = 0; + len = 0; + r = FS_Read( name, 4, f ); + if ( r != 4 ) { + return 0; + } + len = FGetLittleLong( f ); + if ( len < 0 || len > 0xfffffff ) { + len = 0; + return 0; + } + len = (len + 1 ) & ~1; // pad to word boundary +// s_nextWavChunk += len + 8; + + if ( strcmp( name, chunk ) ) { + return 0; + } + return len; +} + +// fixme: need to move this into qcommon sometime?, but too much stuff altered by other people and I won't be able +// to compile again for ages if I check that out... +// +// DO NOT replace this with a call to FS_FileExists, that's for checking about writing out, and doesn't work for this. +// +sboolean S_FileExists( const char *psFilename ) +{ + fileHandle_t fhTemp; + + FS_FOpenFileRead (psFilename, &fhTemp, qtrue); // qtrue so I can fclose the handle without closing a PAK + if (!fhTemp) + return qfalse; + + FS_FCloseFile(fhTemp); + return qtrue; +} + +// some stuff for streaming MP3 files from disk (not pleasant, but nothing about MP3 is, other than compression ratios...) +// +static void MP3MusicStream_Reset(MusicInfo_t *pMusicInfo) +{ + pMusicInfo->iMP3MusicStream_DiskReadPos = 0; + pMusicInfo->iMP3MusicStream_DiskWindowPos = 0; +} + +// +// return is where the decoder should read from... +// +static byte *MP3MusicStream_ReadFromDisk(MusicInfo_t *pMusicInfo, int iReadOffset, int iReadBytesNeeded) +{ + if (iReadOffset < pMusicInfo->iMP3MusicStream_DiskWindowPos) + { + assert(0); // should never happen + return pMusicInfo->byMP3MusicStream_DiskBuffer; // ...but return something safe anyway + } + + while (iReadOffset + iReadBytesNeeded > pMusicInfo->iMP3MusicStream_DiskReadPos) + { + int iBytesRead = FS_Read( pMusicInfo->byMP3MusicStream_DiskBuffer + (pMusicInfo->iMP3MusicStream_DiskReadPos - pMusicInfo->iMP3MusicStream_DiskWindowPos), iMP3MusicStream_DiskBytesToRead, pMusicInfo->s_backgroundFile ); + + pMusicInfo->iMP3MusicStream_DiskReadPos += iBytesRead; + + if (iBytesRead != iMP3MusicStream_DiskBytesToRead) // quietly ignore any requests to read past file end + { + break; // we need to do this because the disk read code can't know how much source data we need to + // read for a given number of requested output bytes, so we'll always be asking for too many + } + } + + // if reached halfway point in buffer (approx 20k), backscroll it... + // + if (pMusicInfo->iMP3MusicStream_DiskReadPos - pMusicInfo->iMP3MusicStream_DiskWindowPos > iMP3MusicStream_DiskBufferSize/2) + { + int iMoveSrcOffset = iReadOffset - pMusicInfo->iMP3MusicStream_DiskWindowPos; + int iMoveCount = (pMusicInfo->iMP3MusicStream_DiskReadPos - pMusicInfo->iMP3MusicStream_DiskWindowPos ) - iMoveSrcOffset; + memmove( &pMusicInfo->byMP3MusicStream_DiskBuffer, &pMusicInfo->byMP3MusicStream_DiskBuffer[iMoveSrcOffset], iMoveCount); + pMusicInfo->iMP3MusicStream_DiskWindowPos += iMoveSrcOffset; + } + + return pMusicInfo->byMP3MusicStream_DiskBuffer + (iReadOffset - pMusicInfo->iMP3MusicStream_DiskWindowPos); +} + + +// does NOT set s_rawend!... +// +static void S_StopBackgroundTrack_Actual( MusicInfo_t *pMusicInfo ) +{ + if ( pMusicInfo->s_backgroundFile ) + { + if ( pMusicInfo->s_backgroundFile != -1) + { + Sys_EndStreamedFile( pMusicInfo->s_backgroundFile ); + FS_FCloseFile( pMusicInfo->s_backgroundFile ); + } + pMusicInfo->s_backgroundFile = 0; + } +} + +static void FreeMusic( MusicInfo_t *pMusicInfo ) +{ + if (pMusicInfo->pLoadedData) + { + Z_Free(pMusicInfo->pLoadedData); + pMusicInfo->pLoadedData = NULL; // these two MUST be kept as valid/invalid together + pMusicInfo->sLoadedDataName[0]= '\0'; // + pMusicInfo->iLoadedDataLen = 0; + } +} + +// called only by snd_shutdown (from snd_restart or app exit) +// +void S_UnCacheDynamicMusic( void ) +{ + for (int i = eBGRNDTRACK_DATABEGIN; i != eBGRNDTRACK_DATAEND; i++) + { + FreeMusic( &tMusic_Info[i]); + } +} + +static sboolean S_StartBackgroundTrack_Actual( MusicInfo_t *pMusicInfo, sboolean qbDynamic, const char *intro, const char *loop ) +{ + int len; + char dump[16]; + char name[MAX_QPATH]; + + Q_strncpyz( sMusic_BackgroundLoop, loop, sizeof( sMusic_BackgroundLoop )); + + Q_strncpyz( name, intro, sizeof( name ) - 4 ); // this seems to be so that if the filename hasn't got an extension + // but doesn't have the room to append on either then you'll just + // get the "soft" fopen() error, rather than the ERR_DROP you'd get + // if COM_DefaultExtension didn't have room to add it on. + COM_DefaultExtension( name, sizeof( name ), ".mp3" ); + + // close the background track, but DON'T reset s_rawend (or remaining music bits that haven't been output yet will be cut off) + // + S_StopBackgroundTrack_Actual( pMusicInfo ); + + pMusicInfo->bIsMP3 = qfalse; + + if ( !intro[0] ) { + return qfalse; + } + + // new bit, if file requested is not same any loaded one (if prev was in-mem), ditch it... + // + if (Q_stricmp(name, pMusicInfo->sLoadedDataName)) + { + FreeMusic( pMusicInfo ); + } + + if (!Q_stricmpn(name+(strlen(name)-4),".mp3",4)) + { + if (pMusicInfo->pLoadedData) + { + pMusicInfo->s_backgroundFile = -1; + } + else + { + pMusicInfo->iLoadedDataLen = FS_FOpenFileRead( name, &pMusicInfo->s_backgroundFile, qtrue ); + } + + if (!pMusicInfo->s_backgroundFile) + { + Com_Printf( S_COLOR_RED"Couldn't open music file %s\n", name ); + return qfalse; + } + + MP3MusicStream_Reset( pMusicInfo ); + + byte *pbMP3DataSegment = NULL; + int iInitialMP3ReadSize = 8192; // fairly arbitrary, whatever size this is then the decoder is allowed to + // scan up to halfway of it to find floating headers, so don't make it + // too small. 8k works fine. + sboolean bMusicSucceeded = qfalse; + if (qbDynamic) + { + if (!pMusicInfo->pLoadedData) + { + pMusicInfo->pLoadedData = (byte *) Z_Malloc(pMusicInfo->iLoadedDataLen, TAG_SND_DYNAMICMUSIC, qfalse); + + S_ClearSoundBuffer(); + FS_Read(pMusicInfo->pLoadedData, pMusicInfo->iLoadedDataLen, pMusicInfo->s_backgroundFile); + Q_strncpyz(pMusicInfo->sLoadedDataName, name, sizeof(pMusicInfo->sLoadedDataName)); + } + + // enable the rest of the code to work as before... + // + pbMP3DataSegment = pMusicInfo->pLoadedData; + iInitialMP3ReadSize = pMusicInfo->iLoadedDataLen; + } + else + { + pbMP3DataSegment = MP3MusicStream_ReadFromDisk(pMusicInfo, 0, iInitialMP3ReadSize); + } + + if (MP3_IsValid(name, pbMP3DataSegment, iInitialMP3ReadSize, qtrue /*bStereoDesired*/)) + { + // init stream struct... + // + memset(&pMusicInfo->streamMP3_Bgrnd,0,sizeof(pMusicInfo->streamMP3_Bgrnd)); + char *psError = C_MP3Stream_DecodeInit( &pMusicInfo->streamMP3_Bgrnd, pbMP3DataSegment, pMusicInfo->iLoadedDataLen, + dma.speed, + 16, // sfx->width * 8, + qtrue // bStereoDesired + ); + + + if (psError == NULL) + { + // init sfx struct & setup the few fields I actually need... + // + memset( &pMusicInfo->sfxMP3_Bgrnd,0,sizeof(pMusicInfo->sfxMP3_Bgrnd)); + // pMusicInfo->sfxMP3_Bgrnd.width = 2; // read by MP3_GetSamples() + pMusicInfo->sfxMP3_Bgrnd.iSoundLengthInSamples = 0x7FFFFFFF; // max possible +ve int, since music finishes when decoder stops + pMusicInfo->sfxMP3_Bgrnd.pMP3StreamHeader = &pMusicInfo->streamMP3_Bgrnd; + Q_strncpyz( pMusicInfo->sfxMP3_Bgrnd.sSoundName, name, sizeof(pMusicInfo->sfxMP3_Bgrnd.sSoundName) ); + + if (qbDynamic) + { + MP3Stream_InitPlayingTimeFields ( &pMusicInfo->streamMP3_Bgrnd, name, pbMP3DataSegment, pMusicInfo->iLoadedDataLen, qtrue); + } + + pMusicInfo->s_backgroundInfo.format = WAV_FORMAT_MP3; // not actually used this way, but just ensures we don't match one of the legit formats + pMusicInfo->s_backgroundInfo.channels = 2; // always, for our MP3s when used for music (else 1 for FX) + pMusicInfo->s_backgroundInfo.rate = dma.speed; + pMusicInfo->s_backgroundInfo.width = 2; // always, for our MP3s + pMusicInfo->s_backgroundInfo.samples = pMusicInfo->sfxMP3_Bgrnd.iSoundLengthInSamples; + pMusicInfo->s_backgroundSamples = pMusicInfo->sfxMP3_Bgrnd.iSoundLengthInSamples; + + memset(&pMusicInfo->chMP3_Bgrnd,0,sizeof(pMusicInfo->chMP3_Bgrnd)); + pMusicInfo->chMP3_Bgrnd.thesfx = &pMusicInfo->sfxMP3_Bgrnd; + memcpy(&pMusicInfo->chMP3_Bgrnd.MP3StreamHeader, pMusicInfo->sfxMP3_Bgrnd.pMP3StreamHeader, sizeof(*pMusicInfo->sfxMP3_Bgrnd.pMP3StreamHeader)); + + if (qbDynamic) + { + if (pMusicInfo->s_backgroundFile != -1) + { + FS_FCloseFile( pMusicInfo->s_backgroundFile ); + pMusicInfo->s_backgroundFile = -1; // special mp3 value for "valid, but not a real file" + } + } + + pMusicInfo->bIsMP3 = qtrue; + bMusicSucceeded = qtrue; + } + else + { + Com_Printf(S_COLOR_RED"Error streaming file %s: %s\n", name, psError); + if (pMusicInfo->s_backgroundFile != -1) + { + FS_FCloseFile( pMusicInfo->s_backgroundFile ); + } + pMusicInfo->s_backgroundFile = 0; + } + } + else + { + // MP3_IsValid() will already have printed any errors via Com_Printf at this point... + // + if (pMusicInfo->s_backgroundFile != -1) + { + FS_FCloseFile( pMusicInfo->s_backgroundFile ); + } + pMusicInfo->s_backgroundFile = 0; + } + + return bMusicSucceeded; + } + else // not an mp3 file + { + // + // open up a wav file and get all the info + // + FS_FOpenFileRead( name, &pMusicInfo->s_backgroundFile, qtrue ); + if ( !pMusicInfo->s_backgroundFile ) { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", name ); +#endif + return qfalse; + } + + // skip the riff wav header + + FS_Read(dump, 12, pMusicInfo->s_backgroundFile); + + if ( !S_FindWavChunk( pMusicInfo->s_backgroundFile, "fmt " ) ) { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW "WARNING: No fmt chunk in %s\n", name ); +#endif + FS_FCloseFile( pMusicInfo->s_backgroundFile ); + pMusicInfo->s_backgroundFile = 0; + return qfalse; + } + + // save name for soundinfo + pMusicInfo->s_backgroundInfo.format = FGetLittleShort( pMusicInfo->s_backgroundFile ); + pMusicInfo->s_backgroundInfo.channels = FGetLittleShort( pMusicInfo->s_backgroundFile ); + pMusicInfo->s_backgroundInfo.rate = FGetLittleLong( pMusicInfo->s_backgroundFile ); + FGetLittleLong( pMusicInfo->s_backgroundFile ); + FGetLittleShort( pMusicInfo->s_backgroundFile ); + pMusicInfo->s_backgroundInfo.width = FGetLittleShort( pMusicInfo->s_backgroundFile ) / 8; + + if ( pMusicInfo->s_backgroundInfo.format != WAV_FORMAT_PCM ) { + FS_FCloseFile( pMusicInfo->s_backgroundFile ); + pMusicInfo->s_backgroundFile = 0; +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW "WARNING: Not a microsoft PCM format wav: %s\n", name); +#endif + return qfalse; + } + + if ( pMusicInfo->s_backgroundInfo.channels != 2 || pMusicInfo->s_backgroundInfo.rate != 22050 ) { +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", name ); +#endif + } + + if ( ( len = S_FindWavChunk( pMusicInfo->s_backgroundFile, "data" ) ) == 0 ) { + FS_FCloseFile( pMusicInfo->s_backgroundFile ); + pMusicInfo->s_backgroundFile = 0; +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW "WARNING: No data chunk in %s\n", name); +#endif + return qfalse; + } + + pMusicInfo->s_backgroundInfo.samples = len / (pMusicInfo->s_backgroundInfo.width * pMusicInfo->s_backgroundInfo.channels); + + pMusicInfo->s_backgroundSamples = pMusicInfo->s_backgroundInfo.samples; + + // + // start the background streaming + // + Sys_BeginStreamedFile( pMusicInfo->s_backgroundFile, 0x10000 ); + } + + return qtrue; +} + + +static void S_SwitchDynamicTracks( MusicState_e eOldState, MusicState_e eNewState, sboolean bNewTrackStartsFullVolume ) +{ + // copy old track into fader... + // + tMusic_Info[ eBGRNDTRACK_FADE ] = tMusic_Info[ eOldState ]; +// tMusic_Info[ eBGRNDTRACK_FADE ].bActive = qtrue; // inherent +// tMusic_Info[ eBGRNDTRACK_FADE ].bExists = qtrue; // inherent + tMusic_Info[ eBGRNDTRACK_FADE ].iXFadeVolumeSeekTime= Sys_Milliseconds(); + tMusic_Info[ eBGRNDTRACK_FADE ].iXFadeVolumeSeekTo = 0; + // + // ... and deactivate... + // + tMusic_Info[ eOldState ].bActive = qfalse; + // + // set new track to either full volume or fade up... + // + tMusic_Info[eNewState].bActive = qtrue; + tMusic_Info[eNewState].iXFadeVolumeSeekTime = Sys_Milliseconds(); + tMusic_Info[eNewState].iXFadeVolumeSeekTo = 255; + tMusic_Info[eNewState].iXFadeVolume = bNewTrackStartsFullVolume ? 255 : 0; + + eMusic_StateActual = eNewState; + + + if (s_debugdynamic->integer) + { + LPCSTR psNewStateString = Music_BaseStateToString( eNewState, qtrue ); + psNewStateString = psNewStateString?psNewStateString:""; + + Com_Printf( S_COLOR_MAGENTA "S_SwitchDynamicTracks( \"%s\" )\n", psNewStateString ); + } +} + +// called by both the config-string parser and the console-command state-changer... +// +// This either changes the music right now (copying track structures etc), or leaves the new state as pending +// so it gets picked up by the general music player if in a transition that can't be overridden... +// +static void S_SetDynamicMusicState( MusicState_e eNewState ) +{ + if (eMusic_StateRequest != eNewState) + { + eMusic_StateRequest = eNewState; + + if (s_debugdynamic->integer) + { + LPCSTR psNewStateString = Music_BaseStateToString( eNewState, qtrue ); + psNewStateString = psNewStateString?psNewStateString:""; + + Com_Printf( S_COLOR_MAGENTA "S_SetDynamicMusicState( Request: \"%s\" )\n", psNewStateString ); + } + } +} + + +static void S_HandleDynamicMusicStateChange( void ) +{ + if (eMusic_StateRequest != eMusic_StateActual) + { + // check whether or not the new request can be honoured, given what's currently playing... + // + if (Music_StateCanBeInterrupted( eMusic_StateActual, eMusic_StateRequest )) + { + LP_MP3STREAM pMP3StreamActual = &tMusic_Info[ eMusic_StateActual ].chMP3_Bgrnd.MP3StreamHeader; + + switch (eMusic_StateRequest) + { + case eBGRNDTRACK_EXPLORE: // ... from action or silence + { + switch (eMusic_StateActual) + { + case eBGRNDTRACK_ACTION: // action->explore + { + // find the transition track to play, and the entry point for explore when we get there, + // and also see if we're at a permitted exit point to switch at all... + // + float fPlayingTimeElapsed = MP3Stream_GetPlayingTimeInSeconds( pMP3StreamActual ) - MP3Stream_GetRemainingTimeInSeconds( pMP3StreamActual ); + + // supply: + // + // playing point in float seconds + // enum of track being queried + // + // get: + // + // enum of transition track to switch to + // float time of entry point of new track *after* transition + + MusicState_e eTransition; + float fNewTrackEntryTime = 0.0f; + if (Music_AllowedToTransition( fPlayingTimeElapsed, eBGRNDTRACK_ACTION, &eTransition, &fNewTrackEntryTime)) + { + S_SwitchDynamicTracks( eMusic_StateActual, eTransition, qfalse ); // sboolean bNewTrackStartsFullVolume + + tMusic_Info[eTransition].Rewind(); + tMusic_Info[eTransition].bTrackSwitchPending = qtrue; + tMusic_Info[eTransition].eTS_NewState = eMusic_StateRequest; + tMusic_Info[eTransition].fTS_NewTime = fNewTrackEntryTime; + } + } + break; + + case eBGRNDTRACK_SILENCE: // silence->explore + { + S_SwitchDynamicTracks( eMusic_StateActual, eMusic_StateRequest, qfalse ); // sboolean bNewTrackStartsFullVolume + +// float fEntryTime = Music_GetRandomEntryTime( eMusic_StateRequest ); +// tMusic_Info[ eMusic_StateRequest ].SeekTo(fEntryTime); + tMusic_Info[ eMusic_StateRequest ].Rewind(); + } + break; + + default: // trying to transition from some state I wasn't aware you could transition from (shouldn't happen), so ignore + { + assert(0); + S_SwitchDynamicTracks( eMusic_StateActual, eBGRNDTRACK_SILENCE, qfalse ); // sboolean bNewTrackStartsFullVolume + } + break; + } + } + break; + + case eBGRNDTRACK_SILENCE: // from explore or action + { + switch (eMusic_StateActual) + { + case eBGRNDTRACK_ACTION: // action->silence + case eBGRNDTRACK_EXPLORE: // explore->silence + { + // find the transition track to play, and the entry point for explore when we get there, + // and also see if we're at a permitted exit point to switch at all... + // + float fPlayingTimeElapsed = MP3Stream_GetPlayingTimeInSeconds( pMP3StreamActual ) - MP3Stream_GetRemainingTimeInSeconds( pMP3StreamActual ); + + MusicState_e eTransition; + float fNewTrackEntryTime = 0.0f; + if (Music_AllowedToTransition( fPlayingTimeElapsed, eMusic_StateActual, &eTransition, &fNewTrackEntryTime)) + { + S_SwitchDynamicTracks( eMusic_StateActual, eTransition, qfalse ); // sboolean bNewTrackStartsFullVolume + + tMusic_Info[eTransition].Rewind(); + tMusic_Info[eTransition].bTrackSwitchPending = qtrue; + tMusic_Info[eTransition].eTS_NewState = eMusic_StateRequest; + tMusic_Info[eTransition].fTS_NewTime = 0.0f; //fNewTrackEntryTime; irrelevant when switching to silence + } + } + break; + + default: // some unhandled type switching to silence + assert(0); // fall through since boss case just does silence->switch anyway + + case eBGRNDTRACK_BOSS: // boss->silence + { + S_SwitchDynamicTracks( eMusic_StateActual, eBGRNDTRACK_SILENCE, qfalse ); // sboolean bNewTrackStartsFullVolume + } + break; + } + } + break; + + case eBGRNDTRACK_ACTION: // anything->action + { + switch (eMusic_StateActual) + { + case eBGRNDTRACK_SILENCE: // silence->action + { + S_SwitchDynamicTracks( eMusic_StateActual, eMusic_StateRequest, qfalse ); // sboolean bNewTrackStartsFullVolume + tMusic_Info[ eMusic_StateRequest ].Rewind(); + } + break; + + default: // !silence->action + { + S_SwitchDynamicTracks( eMusic_StateActual, eMusic_StateRequest, qtrue ); // sboolean bNewTrackStartsFullVolume + float fEntryTime = Music_GetRandomEntryTime( eMusic_StateRequest ); + tMusic_Info[ eMusic_StateRequest ].SeekTo(fEntryTime); + } + break; + } + } + break; + + case eBGRNDTRACK_BOSS: + { + S_SwitchDynamicTracks( eMusic_StateActual, eMusic_StateRequest, qfalse ); // sboolean bNewTrackStartsFullVolume + // + // ( no need to fast forward or rewind, boss track is only entered into once, at start, and can't exit ) + // + } + break; + + case eBGRNDTRACK_DEATH: + { + S_SwitchDynamicTracks( eMusic_StateActual, eMusic_StateRequest, qtrue ); // sboolean bNewTrackStartsFullVolume + // + // ( no need to fast forward or rewind, death track is only entered into once, at start, and can't exit or loop) + // + } + break; + + default: assert(0); break; // unknown new mode request, so just ignore it + } + } + } +} + + + +static char gsIntroMusic[MAX_QPATH]={0}; +static char gsLoopMusic [MAX_QPATH]={0}; + +void S_RestartMusic( void ) +{ + if (s_soundStarted && !s_soundMuted ) + { + //if (gsIntroMusic[0] || gsLoopMusic[0]) // dont test this anymore (but still *use* them), they're blank for JK2 dynamic-music levels anyway + { + MusicState_e ePrevState = eMusic_StateRequest; + S_StartBackgroundTrack( gsIntroMusic, gsLoopMusic, qfalse ); // ( default music start will set the state to EXPLORE ) + S_SetDynamicMusicState( ePrevState ); // restore to prev state + } + } +} + +// Basic logic here is to see if the intro file specified actually exists, and if so, then it's not dynamic music, +// When called by the cgame start it loads up, then stops the playback (because of stutter issues), so that when the +// actual snapshot is received and the real play request is processed the data has already been loaded so will be quicker. +// +// to be honest, although the code still plays WAVs some of the file-check logic only works for MP3s, so if you ever want +// to use WAV music you'll have to do some tweaking below (but I've got other things to do so it'll have to wait - Ste) +// +void S_StartBackgroundTrack( const char *intro, const char *loop, int bCalledByCGameStart ) +{ + bMusic_IsDynamic = qfalse; + + if (!s_soundStarted) + { //we have no sound, so don't even bother trying + return; + } + + if ( !intro ) { + intro = ""; + } + if ( !loop || !loop[0] ) { + loop = intro; + } + + Q_strncpyz(gsIntroMusic,intro, sizeof(gsIntroMusic)); + Q_strncpyz(gsLoopMusic, loop, sizeof(gsLoopMusic)); + + char sNameIntro[MAX_QPATH]; + char sNameLoop [MAX_QPATH]; + Q_strncpyz(sNameIntro, intro, sizeof(sNameIntro)); + Q_strncpyz(sNameLoop, loop, sizeof(sNameLoop)); + + COM_DefaultExtension( sNameIntro, sizeof( sNameIntro ), ".mp3" ); + COM_DefaultExtension( sNameLoop, sizeof( sNameLoop), ".mp3" ); + + // if dynamic music not allowed, then just stream the explore music instead of playing dynamic... + // + if (!s_allowDynamicMusic->integer && Music_DynamicDataAvailable(intro)) // "intro", NOT "sName" (i.e. don't use version with ".mp3" extension) + { + LPCSTR psMusicName = Music_GetFileNameForState( eBGRNDTRACK_DATABEGIN ); + if (psMusicName && S_FileExists( psMusicName )) + { + Q_strncpyz(sNameIntro,psMusicName,sizeof(sNameIntro)); + Q_strncpyz(sNameLoop, psMusicName,sizeof(sNameLoop )); + } + } + + // conceptually we always play the 'intro'[/sName] track, intro-to-loop transition is handled in UpdateBackGroundTrack(). + // + if ( (strstr(sNameIntro,"/") && S_FileExists( sNameIntro )) ) // strstr() check avoids extra file-exists check at runtime if reverting from streamed music to dynamic since literal files all need at least one slash in their name (eg "music/blah") + { + LPCSTR psLoopName = S_FileExists( sNameLoop ) ? sNameLoop : sNameIntro; + Com_DPrintf("S_StartBackgroundTrack: Found/using non-dynamic music track '%s' (loop: '%s')\n", sNameIntro, psLoopName); + S_StartBackgroundTrack_Actual( &tMusic_Info[eBGRNDTRACK_NONDYNAMIC], bMusic_IsDynamic, sNameIntro, psLoopName ); + } + else + { + if (Music_DynamicDataAvailable(intro)) // "intro", NOT "sName" (i.e. don't use version with ".mp3" extension) + { + extern const char *Music_GetLevelSetName(void); + Q_strncpyz(sInfoOnly_CurrentDynamicMusicSet, Music_GetLevelSetName(), sizeof(sInfoOnly_CurrentDynamicMusicSet)); + for (int i = eBGRNDTRACK_DATABEGIN; i != eBGRNDTRACK_DATAEND; i++) + { + sboolean bOk = qfalse; + LPCSTR psMusicName = Music_GetFileNameForState( (MusicState_e) i); + if (psMusicName && (!Q_stricmp(tMusic_Info[i].sLoadedDataName, psMusicName) || S_FileExists( psMusicName )) ) + { + bOk = S_StartBackgroundTrack_Actual( &tMusic_Info[i], qtrue, psMusicName, loop ); + } + + tMusic_Info[i].bExists = bOk; + + if (!tMusic_Info[i].bExists) + { + FreeMusic( &tMusic_Info[i] ); + } + } + + // + // default all tracks to OFF first (and set any other vars) + // + for (i=0; ibActive = qtrue; + pMusicInfo->iXFadeVolumeSeekTime= Sys_Milliseconds(); + pMusicInfo->iXFadeVolumeSeekTo = 255; + pMusicInfo->iXFadeVolume = 0; + + //#ifdef _DEBUG + // float fRemaining = MP3Stream_GetPlayingTimeInSeconds( &pMusicInfo->chMP3_Bgrnd.MP3StreamHeader); + //#endif + } + else + { + Com_Printf( S_COLOR_RED "Dynamic music did not have both 'action' and 'explore' versions, inhibiting...\n"); + S_StopBackgroundTrack(); + } + } + else + { + if (sNameIntro[0]!='.') // blank name with ".mp3" or whatever attached - no error print out + { + Com_Printf( S_COLOR_RED "Unable to find music \"%s\" as explicit track or dynamic music entry!\n",sNameIntro); + S_StopBackgroundTrack(); + } + } + } + + if (bCalledByCGameStart) + { + S_StopBackgroundTrack(); + } +} + +void S_StopBackgroundTrack( void ) +{ + for (int i=0; ivalue; + + if (bMusic_IsDynamic) + { + // step xfade volume... + // + if ( pMusicInfo->iXFadeVolume != pMusicInfo->iXFadeVolumeSeekTo ) + { + int iFadeMillisecondsElapsed = Sys_Milliseconds() - pMusicInfo->iXFadeVolumeSeekTime; + + if (iFadeMillisecondsElapsed > (fDYNAMIC_XFADE_SECONDS * 1000)) + { + pMusicInfo->iXFadeVolume = pMusicInfo->iXFadeVolumeSeekTo; + } + else + { + pMusicInfo->iXFadeVolume = (int) (255.0f * ((float)iFadeMillisecondsElapsed/(fDYNAMIC_XFADE_SECONDS * 1000.0f))); + if (pMusicInfo->iXFadeVolumeSeekTo == 0) // bleurgh + pMusicInfo->iXFadeVolume = 255 - pMusicInfo->iXFadeVolume; + } + } + fMasterVol *= (float)((float)pMusicInfo->iXFadeVolume / 255.0f); + } + +// this is to work around an obscure issue to do with sliding decoder windows and amounts being requested, since the +// original MP3 stream-decoder wrapper was designed to work with audio-paintbuffer sized pieces... Basically 30000 +// is far too big for the window decoder to handle in one request because of the time-travel issue associated with +// normal sfx buffer painting, and allowing sufficient sliding room, even though the music file never goes back in time. +// +#define SIZEOF_RAW_BUFFER_FOR_MP3 4096 +#define RAWSIZE (pMusicInfo->bIsMP3?SIZEOF_RAW_BUFFER_FOR_MP3:sizeof(raw)) + + if ( !pMusicInfo->s_backgroundFile ) { + return qfalse; + } + + pMusicInfo->fSmoothedOutVolume = (pMusicInfo->fSmoothedOutVolume + fMasterVol)/2.0f; +// OutputDebugString(va("%f\n",pMusicInfo->fSmoothedOutVolume)); + + // don't bother playing anything if musicvolume is 0 + if ( pMusicInfo->fSmoothedOutVolume <= 0 ) { + return qfalse; + } + + // see how many samples should be copied into the raw buffer + if ( s_rawend < s_soundtime ) { + s_rawend = s_soundtime; + } + + while ( s_rawend < s_soundtime + MAX_RAW_SAMPLES ) + { + bufferSamples = MAX_RAW_SAMPLES - (s_rawend - s_soundtime); + + // decide how much data needs to be read from the file + fileSamples = bufferSamples * pMusicInfo->s_backgroundInfo.rate / dma.speed; + + // don't try and read past the end of the file + if ( fileSamples > pMusicInfo->s_backgroundSamples ) { + fileSamples = pMusicInfo->s_backgroundSamples; + } + + // our max buffer size + fileBytes = fileSamples * (pMusicInfo->s_backgroundInfo.width * pMusicInfo->s_backgroundInfo.channels); + if (fileBytes > RAWSIZE ) { + fileBytes = RAWSIZE; + fileSamples = fileBytes / (pMusicInfo->s_backgroundInfo.width * pMusicInfo->s_backgroundInfo.channels); + } + + sboolean qbForceFinish = qfalse; + if (pMusicInfo->bIsMP3) + { + int iStartingSampleNum = pMusicInfo->chMP3_Bgrnd.thesfx->iSoundLengthInSamples - pMusicInfo->s_backgroundSamples; // but this IS relevant + // Com_Printf(S_COLOR_YELLOW "Requesting MP3 samples: sample %d\n",iStartingSampleNum); + + + if (pMusicInfo->s_backgroundFile == -1) + { + // in-mem... + // + qbForceFinish = (MP3Stream_GetSamples( &pMusicInfo->chMP3_Bgrnd, iStartingSampleNum, fileBytes/2, (short*) raw, qtrue ))?qfalse:qtrue; + + //Com_Printf(S_COLOR_YELLOW "Music time remaining: %f seconds\n", MP3Stream_GetRemainingTimeInSeconds( &pMusicInfo->chMP3_Bgrnd.MP3StreamHeader )); + } + else + { + // streaming an MP3 file instead... (note that the 'fileBytes' request size isn't that relevant for MP3s, + // since code here can't know how much the MP3 needs to decompress) + // + byte *pbScrolledStreamData = MP3MusicStream_ReadFromDisk(pMusicInfo, pMusicInfo->chMP3_Bgrnd.MP3StreamHeader.iSourceReadIndex, fileBytes); + + pMusicInfo->chMP3_Bgrnd.MP3StreamHeader.pbSourceData = pbScrolledStreamData - pMusicInfo->chMP3_Bgrnd.MP3StreamHeader.iSourceReadIndex; + + qbForceFinish = (MP3Stream_GetSamples( &pMusicInfo->chMP3_Bgrnd, iStartingSampleNum, fileBytes/2, (short*) raw, qtrue ))?qfalse:qtrue; + } + } + else + { + // streaming a WAV off disk... + // + r = Sys_StreamedRead( raw, 1, fileBytes, pMusicInfo->s_backgroundFile ); + if ( r != fileBytes ) { + Com_Printf(S_COLOR_RED"StreamedRead failure on music track\n"); + S_StopBackgroundTrack(); + return qfalse; + } + + // byte swap if needed (do NOT do for MP3 decoder, that has an internal big/little endian handler) + // + S_ByteSwapRawSamples( fileSamples, pMusicInfo->s_backgroundInfo.width, pMusicInfo->s_backgroundInfo.channels, raw ); + } + + // add to raw buffer + S_RawSamples( fileSamples, pMusicInfo->s_backgroundInfo.rate, + pMusicInfo->s_backgroundInfo.width, pMusicInfo->s_backgroundInfo.channels, raw, pMusicInfo->fSmoothedOutVolume, + bFirstOrOnlyMusicTrack + ); + + pMusicInfo->s_backgroundSamples -= fileSamples; + if ( !pMusicInfo->s_backgroundSamples || qbForceFinish ) + { + // loop the music, or play the next piece if we were on the intro... + // (but not for dynamic, that can only be used for loop music) + // + if (bMusic_IsDynamic) // needs special logic for this, different call + { + pMusicInfo->Rewind(); + } + else + { + // for non-dynamic music we need to check if "sMusic_BackgroundLoop" is an actual filename, + // or if it's a dynamic music specifier (which can't literally exist), in which case it should set + // a return flag then exit... + // + char sTestName[MAX_QPATH*2];// *2 so COM_DefaultExtension doesn't do an ERR_DROP if there was no space + // for an extension, since this is a "soft" test + Q_strncpyz( sTestName, sMusic_BackgroundLoop, sizeof(sTestName)); + COM_DefaultExtension(sTestName, sizeof(sTestName), ".mp3"); + + if (S_FileExists( sTestName )) + { + S_StartBackgroundTrack_Actual( pMusicInfo, qfalse, sMusic_BackgroundLoop, sMusic_BackgroundLoop ); + } + else + { + // proposed file doesn't exist, but this may be a dynamic track we're wanting to loop, + // so exit with a special flag... + // + return qtrue; + } + } + if ( !pMusicInfo->s_backgroundFile ) + { + return qfalse; // loop failed to restart + } + } + } + +#undef SIZEOF_RAW_BUFFER_FOR_MP3 +#undef RAWSIZE + + return qfalse; +} + + +// used to be just for dynamic, but now even non-dynamic music has to know whether it should be silent or not... +// +static LPCSTR S_Music_GetRequestedState(void) +{ + /* + int iStringOffset = cl.gameState.stringOffsets[CS_DYNAMIC_MUSIC_STATE]; + if (iStringOffset) + { + LPCSTR psCommand = cl.gameState.stringData+iStringOffset; + + return psCommand; + } + */ + //rwwFIXMEFIXME: Maybe use the above for something in MP? + + return NULL; +} + + +// scan the configstring to see if there's been a state-change requested... +// (note that even if the state doesn't change it still gets here, so do a same-state check for applying) +// +// then go on to do transition handling etc... +// +static void S_CheckDynamicMusicState(void) +{ + LPCSTR psCommand = S_Music_GetRequestedState(); + + if (psCommand) + { + MusicState_e eNewState; + + if ( !Q_stricmpn( psCommand, "silence", 7) ) + { + eNewState = eBGRNDTRACK_SILENCE; + } + else if ( !Q_stricmpn( psCommand, "action", 6) ) + { + eNewState = eBGRNDTRACK_ACTION; + } + else if ( !Q_stricmpn( psCommand, "boss", 4) ) + { + // special case, boss music is optional and may not be defined... + // + if (tMusic_Info[ eBGRNDTRACK_BOSS ].bExists) + { + eNewState = eBGRNDTRACK_BOSS; + } + else + { + // ( leave it playing current track ) + // + eNewState = eMusic_StateActual; + } + } + else if ( !Q_stricmpn( psCommand, "death", 5) ) + { + // special case, death music is optional and may not be defined... + // + if (tMusic_Info[ eBGRNDTRACK_DEATH ].bExists) + { + eNewState = eBGRNDTRACK_DEATH; + } + else + { + // ( leave it playing current track, typically either boss or action ) + // + eNewState = eMusic_StateActual; + } + } + else + { + // seems a reasonable default... + // + eNewState = eBGRNDTRACK_EXPLORE; + } + + S_SetDynamicMusicState( eNewState ); + } + + S_HandleDynamicMusicStateChange(); +} + +static void S_UpdateBackgroundTrack( void ) +{ + if (bMusic_IsDynamic) + { + if (s_debugdynamic->integer == 2) + { + DynamicMusicInfoPrint(); + } + + S_CheckDynamicMusicState(); + + if (eMusic_StateActual != eBGRNDTRACK_SILENCE) + { + MusicInfo_t *pMusicInfoCurrent = &tMusic_Info[ (eMusic_StateActual == eBGRNDTRACK_FADE)?eBGRNDTRACK_EXPLORE:eMusic_StateActual ]; + MusicInfo_t *pMusicInfoFadeOut = &tMusic_Info[ eBGRNDTRACK_FADE ]; + + if ( pMusicInfoCurrent->s_backgroundFile == -1) + { + int iRawEnd = s_rawend; + S_UpdateBackgroundTrack_Actual( pMusicInfoCurrent, qtrue, s_musicVolume->value ); + + /* static int iPrevFrontVol = 0; + if (iPrevFrontVol != pMusicInfoCurrent->iXFadeVolume) + { + iPrevFrontVol = pMusicInfoCurrent->iXFadeVolume; + Com_Printf("front vol = %d\n",pMusicInfoCurrent->iXFadeVolume); + } + */ + if (pMusicInfoFadeOut->bActive) + { + s_rawend = iRawEnd; + S_UpdateBackgroundTrack_Actual( pMusicInfoFadeOut, qfalse, s_musicVolume->value ); // inactive-checked internally + /* + static int iPrevFadeVol = 0; + if (iPrevFadeVol != pMusicInfoFadeOut->iXFadeVolume) + { + iPrevFadeVol = pMusicInfoFadeOut->iXFadeVolume; + Com_Printf("fade vol = %d\n",pMusicInfoFadeOut->iXFadeVolume); + } + */ + // + // only do this for the fader!... + // + if (pMusicInfoFadeOut->iXFadeVolume == 0) + { + pMusicInfoFadeOut->bActive = qfalse; + } + } + + float fRemainingTimeInSeconds = MP3Stream_GetRemainingTimeInSeconds( &pMusicInfoCurrent->chMP3_Bgrnd.MP3StreamHeader ); + // Com_Printf("Remaining: %3.3f\n",fRemainingTimeInSeconds); + + if ( fRemainingTimeInSeconds < fDYNAMIC_XFADE_SECONDS*2 ) + { + // now either loop current track, switch if finishing a transition, or stop if finished a death... + // + if (pMusicInfoCurrent->bTrackSwitchPending) + { + pMusicInfoCurrent->bTrackSwitchPending = qfalse; // ack + S_SwitchDynamicTracks( eMusic_StateActual, pMusicInfoCurrent->eTS_NewState, qfalse); // sboolean bNewTrackStartsFullVolume + if (tMusic_Info[ pMusicInfoCurrent->eTS_NewState ].bExists) // don't do this if switching to silence + { + tMusic_Info[ pMusicInfoCurrent->eTS_NewState ].SeekTo(pMusicInfoCurrent->fTS_NewTime); + } + } + else + { + // normal looping, so set rewind current track, set volume to 0 and fade up to full (unless death track playing, then stays quiet) + // (while fader copy of end-section fades down) + // + // copy current track to fader... + // + *pMusicInfoFadeOut = *pMusicInfoCurrent; // struct copy + pMusicInfoFadeOut->iXFadeVolumeSeekTime = Sys_Milliseconds(); + pMusicInfoFadeOut->iXFadeVolumeSeekTo = 0; + // + pMusicInfoCurrent->Rewind(); + pMusicInfoCurrent->iXFadeVolumeSeekTime = Sys_Milliseconds(); + pMusicInfoCurrent->iXFadeVolumeSeekTo = (eMusic_StateActual == eBGRNDTRACK_DEATH) ? 0: 255; + pMusicInfoCurrent->iXFadeVolume = 0; + } + } + } + } + else + { + // special case, when foreground music is shut off but fader still running to fade off previous track... + // + MusicInfo_t *pMusicInfoFadeOut = &tMusic_Info[ eBGRNDTRACK_FADE ]; + if (pMusicInfoFadeOut->bActive) + { + S_UpdateBackgroundTrack_Actual( pMusicInfoFadeOut, qtrue, s_musicVolume->value ); + if (pMusicInfoFadeOut->iXFadeVolume == 0) + { + pMusicInfoFadeOut->bActive = qfalse; + } + } + } + } + else + { + // standard / non-dynamic one-track music... + // + LPCSTR psCommand = S_Music_GetRequestedState(); // special check just for "silence" case... + sboolean bShouldBeSilent = (psCommand && !stricmp(psCommand,"silence")); + float fDesiredVolume = bShouldBeSilent ? 0.0f : s_musicVolume->value; + // + // internal to this code is a volume-smoother... + // + sboolean bNewTrackDesired = S_UpdateBackgroundTrack_Actual(&tMusic_Info[eBGRNDTRACK_NONDYNAMIC], qtrue, fDesiredVolume); + + if (bNewTrackDesired) + { + S_StartBackgroundTrack( sMusic_BackgroundLoop, sMusic_BackgroundLoop, qfalse ); + } + } +} + + +cvar_t *s_soundpoolmegs = NULL; + + +// currently passing in sfx as a param in case I want to do something with it later. +// +byte *SND_malloc(int iSize, sfx_t *sfx) +{ + byte *pData = (byte *) Z_Malloc(iSize, TAG_SND_RAWDATA, qfalse); // don't bother asking for zeroed mem + + // if "s_soundpoolmegs" is < 0, then the -ve of the value is the maximum amount of sounds we're allowed to have loaded... + // + if (s_soundpoolmegs && s_soundpoolmegs->integer < 0) + { + while ( (Z_MemSize(TAG_SND_RAWDATA) + Z_MemSize(TAG_SND_MP3STREAMHDR)) > ((-s_soundpoolmegs->integer) * 1024 * 1024)) + { + int iBytesFreed = SND_FreeOldestSound(sfx); + if (iBytesFreed == 0) + break; // sanity + } + } + + return pData; +} + + +// called once-only in EXE lifetime... +// +void SND_setup() +{ + s_soundpoolmegs = Cvar_Get("s_soundpoolmegs", "25", CVAR_ARCHIVE); + if (Sys_LowPhysicalMemory() ) + { + Cvar_Set("s_soundpoolmegs", "0"); + } + + Com_Printf("Sound memory manager started\n"); +} + + +// ask how much mem an sfx has allocated... +// +static int SND_MemUsed(sfx_t *sfx) +{ + int iSize = 0; + if (sfx->pSoundData){ + iSize += Z_Size(sfx->pSoundData); + } + + if (sfx->pMP3StreamHeader) { + iSize += Z_Size(sfx->pMP3StreamHeader); + } + + return iSize; +} + +// free any allocated sfx mem... +// +// now returns # bytes freed to help with z_malloc()-fail recovery +// +static int SND_FreeSFXMem(sfx_t *sfx) +{ + int iBytesFreed = 0; + + if (s_UseOpenAL) + { + alGetError(); + if (sfx->Buffer) + { + alDeleteBuffers(1, &(sfx->Buffer)); +#ifdef _DEBUG + char szString[256]; + if (alGetError() != AL_NO_ERROR) + { + sprintf(szString, "Failed to delete AL Buffer (%s) ... !\n", sfx->sSoundName); + OutputDebugString(szString); + } +#endif + sfx->Buffer = 0; + } + + if (sfx->lipSyncData) + { + iBytesFreed += Z_Size( sfx->lipSyncData); + Z_Free( sfx->lipSyncData); + sfx->lipSyncData = NULL; + } + } + + if ( sfx->pSoundData) { + iBytesFreed += Z_Size( sfx->pSoundData); + Z_Free( sfx->pSoundData ); + sfx->pSoundData = NULL; + } + + sfx->bInMemory = qfalse; + + if ( sfx->pMP3StreamHeader) { + iBytesFreed += Z_Size( sfx->pMP3StreamHeader); + Z_Free( sfx->pMP3StreamHeader ); + sfx->pMP3StreamHeader = NULL; + } + + return iBytesFreed; +} + +void S_DisplayFreeMemory() +{ + int iSoundDataSize = Z_MemSize ( TAG_SND_RAWDATA ) + Z_MemSize( TAG_SND_MP3STREAMHDR ); + int iMusicDataSize = Z_MemSize ( TAG_SND_DYNAMICMUSIC ); + + if (iSoundDataSize || iMusicDataSize) + { + Com_Printf("\n%.2fMB audio data: ( %.2fMB WAV/MP3 ) + ( %.2fMB Music )\n", + ((float)(iSoundDataSize+iMusicDataSize))/1024.0f/1024.0f, + ((float)(iSoundDataSize))/1024.0f/1024.0f, + ((float)(iMusicDataSize))/1024.0f/1024.0f + ); + + // now count up amount used on this level... + // + iSoundDataSize = 0; + for (int i=1; iiLastLevelUsedOn == RE_RegisterMedia_GetLevel()){ + iSoundDataSize += SND_MemUsed(sfx); + } + } + + Com_Printf("%.2fMB in sfx_t alloc data (WAV/MP3) loaded this level\n",(float)iSoundDataSize/1024.0f/1024.0f); + } +} + +void SND_TouchSFX(sfx_t *sfx) +{ + sfx->iLastTimeUsed = Com_Milliseconds()+1; + sfx->iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); +} + + +// currently this is only called during snd_shutdown or snd_restart +// +void S_FreeAllSFXMem(void) +{ + for (int i=1 ; i < s_numSfx ; i++) // start @ 1 to skip freeing default sound + { + SND_FreeSFXMem(&s_knownSfx[i]); + } +} + +// returns number of bytes freed up... +// +// new param is so we can be usre of not freeing ourselves (without having to rely on possible uninitialised timers etc) +// +int SND_FreeOldestSound(sfx_t *pButNotThisOne /* = NULL */) +{ + int iBytesFreed = 0; + sfx_t *sfx; + + int iOldest = Com_Milliseconds(); + int iUsed = 0; + + // start on 1 so we never dump the default sound... + // + for (int i=1 ; i < s_numSfx ; i++) + { + sfx = &s_knownSfx[i]; + + if (sfx != pButNotThisOne) + { + if (!sfx->bDefaultSound && sfx->bInMemory && sfx->iLastTimeUsed < iOldest) + { + // new bit, we can't throw away any sfx_t struct in use by a channel, else the paint code will crash... + // + for (int iChannel=0; iChannelthesfx == sfx) + break; // damn, being used + } + if (iChannel == MAX_CHANNELS) + { + // this sfx_t struct wasn't used by any channels, so we can lose it... + // + iUsed = i; + iOldest = sfx->iLastTimeUsed; + } + } + } + } + + if (iUsed) + { + sfx = &s_knownSfx[ iUsed ]; + + Com_DPrintf("SND_FreeOldestSound: freeing sound %s\n", sfx->sSoundName); + + iBytesFreed = SND_FreeSFXMem(sfx); + } + + return iBytesFreed; +} +int SND_FreeOldestSound(void) +{ + return SND_FreeOldestSound(NULL); // I had to add a void-arg version of this because of link issues, sigh +} + + +// just before we drop into a level, ensure the audio pool is under whatever the maximum +// pool size is (but not by dropping out sounds used by the current level)... +// +// returns qtrue if at least one sound was dropped out, so z_malloc-fail recovery code knows if anything changed +// +extern qboolean gbInsideLoadSound; +qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel /* 99% qfalse */) +{ + qboolean bAtLeastOneSoundDropped = qfalse; + + Com_DPrintf( "SND_RegisterAudio_LevelLoadEnd():\n"); + + if (gbInsideLoadSound) + { + Com_DPrintf( "(Inside S_LoadSound (z_malloc recovery?), exiting...\n"); + } + else + { + int iLoadedAudioBytes = Z_MemSize ( TAG_SND_RAWDATA ) + Z_MemSize( TAG_SND_MP3STREAMHDR ); + const int iMaxAudioBytes = s_soundpoolmegs->integer * 1024 * 1024; + + for (int i=1; i iMaxAudioBytes || bDeleteEverythingNotUsedThisLevel) ; i++) // i=1 so we never page out default sound + { + sfx_t *sfx = &s_knownSfx[i]; + + if (sfx->bInMemory) + { + sboolean bDeleteThis = qfalse; + + if (bDeleteEverythingNotUsedThisLevel) + { + bDeleteThis = (sfx->iLastLevelUsedOn != RE_RegisterMedia_GetLevel()); + } + else + { + bDeleteThis = (sfx->iLastLevelUsedOn < RE_RegisterMedia_GetLevel()); + } + + if (bDeleteThis) + { + Com_DPrintf( "Dumping sfx_t \"%s\"\n",sfx->sSoundName); + + if (SND_FreeSFXMem(sfx)) + { + bAtLeastOneSoundDropped = qtrue; + } + + iLoadedAudioBytes = Z_MemSize ( TAG_SND_RAWDATA ) + Z_MemSize( TAG_SND_MP3STREAMHDR ); + } + } + } + } + + Com_DPrintf( "SND_RegisterAudio_LevelLoadEnd(): Ok\n"); + + return bAtLeastOneSoundDropped; +} + + +/****************************************************************************************************\ +* +* EAX Related +* +\****************************************************************************************************/ + +/* + Initialize the EAX Manager +*/ +void InitEAXManager() +{ + LPEAXMANAGERCREATE lpEAXManagerCreateFn; + EAXFXSLOTPROPERTIES FXSlotProp; + GUID Effect; + GUID FXSlotGuids[4]; + int i; + + s_bEALFileLoaded = false; + + // Check for EAX 4.0 support + s_bEAX = alIsExtensionPresent((ALubyte*)"EAX4.0"); + + if (s_bEAX) + { + Com_Printf("Found EAX 4.0 native support\n"); + } + else + { + // Support for EAXUnified (automatic translation of EAX 4.0 calls into EAX 3.0) + if ((alIsExtensionPresent((ALubyte*)"EAX3.0")) && (alIsExtensionPresent((ALubyte*)"EAX4.0Emulated"))) + { + s_bEAX = AL_TRUE; + Com_Printf("Found EAX 4.0 EMULATION support\n"); + } + } + + if (s_bEAX) + { + s_eaxSet = (EAXSet)alGetProcAddress((ALubyte*)"EAXSet"); + if (s_eaxSet == NULL) + s_bEAX = false; + s_eaxGet = (EAXGet)alGetProcAddress((ALubyte*)"EAXGet"); + if (s_eaxGet == NULL) + s_bEAX = false; + } + + // If we have detected EAX support, then try and load the EAX Manager DLL + if (s_bEAX) + { + s_hEAXManInst = LoadLibrary("EAXMan.dll"); + if (s_hEAXManInst) + { + lpEAXManagerCreateFn = (LPEAXMANAGERCREATE)GetProcAddress(s_hEAXManInst, "EaxManagerCreate"); + if (lpEAXManagerCreateFn) + { + if (lpEAXManagerCreateFn(&s_lpEAXManager)==EM_OK) + { + // Configure our EAX 4.0 Effect Slots + + s_NumFXSlots = 0; + for (i = 0; i < EAX_MAX_FXSLOTS; i++) + { + s_FXSlotInfo[i].FXSlotGuid = EAX_NULL_GUID; + s_FXSlotInfo[i].lEnvID = -1; + } + + FXSlotGuids[0] = EAXPROPERTYID_EAX40_FXSlot0; + FXSlotGuids[1] = EAXPROPERTYID_EAX40_FXSlot1; + FXSlotGuids[2] = EAXPROPERTYID_EAX40_FXSlot2; + FXSlotGuids[3] = EAXPROPERTYID_EAX40_FXSlot3; + + // For each effect slot, try and load a reverb and lock the slot + FXSlotProp.guidLoadEffect = EAX_REVERB_EFFECT; + FXSlotProp.lVolume = 0; + FXSlotProp.lLock = EAXFXSLOT_LOCKED; + FXSlotProp.ulFlags = EAXFXSLOTFLAGS_ENVIRONMENT; + + for (i = 0; i < EAX_MAX_FXSLOTS; i++) + { + if (s_eaxSet(&FXSlotGuids[i], EAXFXSLOT_ALLPARAMETERS, NULL, &FXSlotProp, sizeof(EAXFXSLOTPROPERTIES))==AL_NO_ERROR) + { + // We can use this slot + s_FXSlotInfo[s_NumFXSlots].FXSlotGuid = FXSlotGuids[i]; + s_NumFXSlots++; + } + else + { + // If this slot already contains a reverb, then we will use it anyway (Slot 0 will + // be in this category). (It probably means that Slot 0 is locked) + if (s_eaxGet(&FXSlotGuids[i], EAXFXSLOT_LOADEFFECT, NULL, &Effect, sizeof(GUID))==AL_NO_ERROR) + { + if (Effect == EAX_REVERB_EFFECT) + { + // We can use this slot + // Make sure the environment flag is on + s_eaxSet(&FXSlotGuids[i], EAXFXSLOT_FLAGS, NULL, &FXSlotProp.ulFlags, sizeof(unsigned long)); + s_FXSlotInfo[s_NumFXSlots].FXSlotGuid = FXSlotGuids[i]; + s_NumFXSlots++; + } + } + } + } + + return; + } + } + } + } + + // If the EAXManager library was loaded (and there was a problem), then unload it + if (s_hEAXManInst) + { + FreeLibrary(s_hEAXManInst); + s_hEAXManInst = NULL; + } + + s_lpEAXManager = NULL; + s_bEAX = false; + + return; +} + +/* + Release the EAX Manager +*/ +void ReleaseEAXManager() +{ + s_bEAX = false; + + UnloadEALFile(); + + if (s_lpEAXManager) + { + s_lpEAXManager->Release(); + s_lpEAXManager = NULL; + } + if (s_hEAXManInst) + { + FreeLibrary(s_hEAXManInst); + s_hEAXManInst = NULL; + } +} + + +/* + Try to load the given .eal file +*/ +bool LoadEALFile(char *szEALFilename) +{ + char *ealData = NULL; + HRESULT hr; + long i, j, lID, lEnvID; + EMPOINT EMPoint; + char szAperture[128]; + char szFullEALFilename[MAX_QPATH]; + long lNumInst, lNumInstA, lNumInstB; + bool bLoaded = false; + bool bValid = true; + int result; + char szString[256]; + + if ((!s_lpEAXManager) || (!s_bEAX)) + return false; + + if (strstr(szEALFilename, "nomap")) + return false; + + s_EnvironmentID = 0xFFFFFFFF; + + // Assume there is no aperture information in the .eal file + s_lpEnvTable = NULL; + + // Load EAL file from PAK file + result = FS_ReadFile(szEALFilename, (void **)&ealData); + + if ((ealData) && (result != -1)) + { + hr = s_lpEAXManager->LoadDataSet(ealData, EMFLAG_LOADFROMMEMORY); + + // Unload EAL file + FS_FreeFile (ealData); + + if (hr == EM_OK) + { + Com_DPrintf("Loaded %s by Quake loader\n", szEALFilename); + bLoaded = true; + } + } + else + { + // Failed to load via Quake loader, try manually + Com_sprintf(szFullEALFilename, MAX_QPATH, "base/%s", szEALFilename); + if (SUCCEEDED(s_lpEAXManager->LoadDataSet(szFullEALFilename, 0))) + { + Com_DPrintf("Loaded %s by EAXManager\n", szEALFilename); + bLoaded = true; + } + } + + if (bLoaded) + { + // For a valid eal file ... need to find 'Center' tag, record num of instances, and then find + // the right number of instances of 'Aperture0a' and 'Aperture0b'. + + if (s_lpEAXManager->GetSourceID("Center", &lID)==EM_OK) + { + if (s_lpEAXManager->GetSourceNumInstances(lID, &s_lNumEnvironments)==EM_OK) + { + if (s_lpEAXManager->GetSourceID("Aperture0a", &lID)==EM_OK) + { + if (s_lpEAXManager->GetSourceNumInstances(lID, &lNumInst)==EM_OK) + { + if (lNumInst == s_lNumEnvironments) + { + if (s_lpEAXManager->GetSourceID("Aperture0b", &lID)==EM_OK) + { + if (s_lpEAXManager->GetSourceNumInstances(lID, &lNumInst)==EM_OK) + { + if (lNumInst == s_lNumEnvironments) + { + // Check equal numbers of ApertureXa and ApertureXb + i = 1; + while (true) + { + lNumInstA = lNumInstB = 0; + + sprintf(szAperture,"Aperture%da",i); + if ((s_lpEAXManager->GetSourceID(szAperture, &lID)==EM_OK) && (s_lpEAXManager->GetSourceNumInstances(lID, &lNumInstA)==EM_OK)) + { + sprintf(szAperture,"Aperture%db",i); + s_lpEAXManager->GetSourceID(szAperture, &lID); + s_lpEAXManager->GetSourceNumInstances(lID, &lNumInstB); + + if (lNumInstA!=lNumInstB) + { + Com_DPrintf( S_COLOR_YELLOW "Invalid EAL file - %d Aperture%da tags, and %d Aperture%db tags\n", lNumInstA, i, lNumInstB, i); + bValid = false; + } + } + else + { + break; + } + + i++; + } + + if (bValid) + { + s_lpEnvTable = (LPENVTABLE)Z_Malloc(s_lNumEnvironments * sizeof(ENVTABLE), TAG_GENERAL, qtrue); + } + } + else + Com_DPrintf( S_COLOR_YELLOW "Invalid EAL File - expected %d instances of Aperture0b, found %d\n", s_lNumEnvironments, lNumInst); + } + else + Com_DPrintf( S_COLOR_YELLOW "EAXManager- failed GetSourceNumInstances()\n"); + } + else + Com_DPrintf( S_COLOR_YELLOW "Invalid EAL File - no instances of 'Aperture0b' source-tag\n"); + } + else + Com_DPrintf( S_COLOR_YELLOW "Invalid EAL File - found %d instances of the 'Center' tag, but only %d instances of 'Aperture0a'\n", s_lNumEnvironments, lNumInst); + } + else + Com_DPrintf( S_COLOR_YELLOW "EAXManager- failed GetSourceNumInstances()\n"); + } + else + Com_DPrintf( S_COLOR_YELLOW "Invalid EAL File - no instances of 'Aperture0a' source-tag\n"); + } + else + Com_DPrintf( S_COLOR_YELLOW "EAXManager- failed GetSourceNumInstances()\n"); + } + else + Com_DPrintf( S_COLOR_YELLOW "Invalid EAL File - no instances of 'Center' source-tag\n"); + + + if (s_lpEnvTable) + { + i = 0; + while (true) + { + sprintf(szAperture, "Aperture%da", i); + if (s_lpEAXManager->GetSourceID(szAperture, &lID)==EM_OK) + { + if (s_lpEAXManager->GetSourceNumInstances(lID, &lNumInst)==EM_OK) + { + for (j = 0; j < s_lNumEnvironments; j++) + { + s_lpEnvTable[j].bUsed = false; + } + + for (j = 0; j < lNumInst; j++) + { + if (s_lpEAXManager->GetSourceInstancePos(lID, j, &EMPoint)==EM_OK) + { + if (s_lpEAXManager->GetListenerDynamicAttributes(0, &EMPoint, &lEnvID, 0)==EM_OK) + { + if ((lEnvID >= 0) && (lEnvID < s_lNumEnvironments)) + { + if (!s_lpEnvTable[lEnvID].bUsed) + { + s_lpEnvTable[lEnvID].bUsed = true; + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos1[0] = EMPoint.fX; + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos1[1] = EMPoint.fY; + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos1[2] = EMPoint.fZ; + } + else + { + s_lpEAXManager->GetEnvironmentName(lEnvID, szString, 256); + Com_DPrintf( S_COLOR_YELLOW "Found more than one occurance of Aperture%da in %s sub-space\n", i, szString); + Com_DPrintf( S_COLOR_YELLOW "One tag at %.3f,%.3f,%.3f, other at %.3f,%.3f,%.3f\n", EMPoint.fX, EMPoint.fY, EMPoint.fZ, + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos1[0], s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos1[1], + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos1[2]); + bValid = false; + } + } + else + { + if (lEnvID==-1) + Com_DPrintf( S_COLOR_YELLOW "%s (%.3f,%.3f,%.3f) in Default Environment - please remove\n", szAperture, EMPoint.fX, EMPoint.fY, EMPoint.fZ); + else + Com_DPrintf( S_COLOR_YELLOW "Detected more reverb presets than zones - please delete unused presets\n"); + bValid = false; + } + } + } + } + } + } + else + { + break; + } + + if (bValid) + { + sprintf(szAperture, "Aperture%db", i); + if (s_lpEAXManager->GetSourceID(szAperture, &lID)==EM_OK) + { + if (s_lpEAXManager->GetSourceNumInstances(lID, &lNumInst)==EM_OK) + { + for (j = 0; j < s_lNumEnvironments; j++) + { + s_lpEnvTable[j].bUsed = false; + } + + for (j = 0; j < lNumInst; j++) + { + if (s_lpEAXManager->GetSourceInstancePos(lID, j, &EMPoint)==EM_OK) + { + if (s_lpEAXManager->GetListenerDynamicAttributes(0, &EMPoint, &lEnvID, 0)==EM_OK) + { + if ((lEnvID >= 0) && (lEnvID < s_lNumEnvironments)) + { + if (!s_lpEnvTable[lEnvID].bUsed) + { + s_lpEnvTable[lEnvID].bUsed = true; + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos2[0] = EMPoint.fX; + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos2[1] = EMPoint.fY; + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos2[2] = EMPoint.fZ; + } + else + { + s_lpEAXManager->GetEnvironmentName(lEnvID, szString, 256); + Com_DPrintf( S_COLOR_YELLOW "Found more than one occurance of Aperture%db in %s sub-space\n", i, szString); + bValid = false; + } + + // Calculate center position of aperture (average of 2 points) + + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vCenter[0] = + (s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos1[0] + + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos2[0]) / 2; + + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vCenter[1] = + (s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos1[1] + + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos2[1]) / 2; + + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vCenter[2] = + (s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos1[2] + + s_lpEnvTable[lEnvID].Aperture[s_lpEnvTable[lEnvID].ulNumApertures].vPos2[2]) / 2; + + s_lpEnvTable[lEnvID].ulNumApertures++; + } + else + { + if (lEnvID==-1) + Com_DPrintf( S_COLOR_YELLOW "%s (%.3f,%.3f,%.3f) in Default Environment - please remove\n", szAperture, EMPoint.fX, EMPoint.fY, EMPoint.fZ); + else + Com_DPrintf( S_COLOR_YELLOW "Detected more reverb presets than zones - please delete unused presets\n"); + bValid = false; + } + } + } + } + } + } + } + + if (!bValid) + { + // Found a problem + Com_DPrintf( S_COLOR_YELLOW "EAX legacy behaviour invoked (one reverb)\n"); + + Z_Free( s_lpEnvTable ); + s_lpEnvTable = NULL; + break; + } + + i++; + } + } + else + { + Com_DPrintf( S_COLOR_YELLOW "EAX legacy behaviour invoked (one reverb)\n"); + } + + return true; + } + + + Com_DPrintf( S_COLOR_YELLOW "Failed to load %s\n", szEALFilename); + return false; +} + +/* + Unload current .eal file +*/ +void UnloadEALFile() +{ + HRESULT hr; + + if ((!s_lpEAXManager) || (!s_bEAX)) + return; + + hr = s_lpEAXManager->FreeDataSet(0); + s_bEALFileLoaded = false; + + if (s_lpEnvTable) + { + Z_Free( s_lpEnvTable ); + s_lpEnvTable = NULL; + } + + return; +} + +/* + Updates the current EAX Reverb setting, based on the location of the listener +*/ +void UpdateEAXListener() +{ + EMPOINT ListPos, ListOri; + EMPOINT EMAperture; + EMPOINT EMSourcePoint; + long lID, lSourceID, lApertureNum; + int i, j, k; + float flDistance, flNearest; + EAXREVERBPROPERTIES Reverb; + bool bFound; + long lVolume; + long lCurTime; + channel_t *ch; + EAXVECTOR LR, LP1, LP2, Pan; + REVERBDATA ReverbData[3]; // Hardcoded to three (maximum no of reverbs) +#ifdef DISPLAY_CLOSEST_ENVS + char szEnvName[256]; +#endif + + if ((!s_lpEAXManager) || (!s_bEAX)) + return; + + lCurTime = timeGetTime(); + + if ((s_lLastEnvUpdate + ENV_UPDATE_RATE) < lCurTime) + { + // Update closest reverbs + s_lLastEnvUpdate = lCurTime; + + // No panning information in .eal file, or we only have 1 FX Slot to use, revert to legacy + // behaviour (i.e only one reverb) + if ((!s_lpEnvTable) || (s_NumFXSlots==1)) + { + // Convert Listener co-ordinate to left-handed system + ListPos.fX = listener_pos[0]; + ListPos.fY = listener_pos[1]; + ListPos.fZ = -listener_pos[2]; + + if (SUCCEEDED(s_lpEAXManager->GetListenerDynamicAttributes(0, &ListPos, &lID, EMFLAG_LOCKPOSITION))) + { + if (lID != s_EnvironmentID) + { +#ifdef DISPLAY_CLOSEST_ENVS + if (SUCCEEDED(s_lpEAXManager->GetEnvironmentName(lID, szEnvName, 256))) + Com_Printf("Changing to '%s' zone !\n", szEnvName); +#endif + // Get EAX Preset info. + if (SUCCEEDED(s_lpEAXManager->GetEnvironmentAttributes(lID, &s_eaxLPCur))) + { + // Override + s_eaxLPCur.flAirAbsorptionHF = 0.0f; + + // Set Environment + s_eaxSet(&EAXPROPERTYID_EAX40_FXSlot0, EAXREVERB_ALLPARAMETERS, + NULL, &s_eaxLPCur, sizeof(EAXREVERBPROPERTIES)); + + s_EnvironmentID = lID; + } + } + } + + return; + } + + // Convert Listener position and orientation to left-handed system + ListPos.fX = listener_pos[0]; + ListPos.fY = listener_pos[1]; + ListPos.fZ = -listener_pos[2]; + + ListOri.fX = listener_ori[0]; + ListOri.fY = listener_ori[1]; + ListOri.fZ = -listener_ori[2]; + + // Need to find closest s_NumFXSlots (including the Listener's slot) + + if (s_lpEAXManager->GetListenerDynamicAttributes(0, &ListPos, &lID, EMFLAG_LOCKPOSITION)==EM_OK) + { + if (lID == -1) + { + // Found default environment +// Com_Printf( S_COLOR_YELLOW "Listener in default environment - ignoring zone !\n"); + return; + } + + ReverbData[0].lEnvID = -1; + ReverbData[0].lApertureNum = -1; + ReverbData[0].flDist = FLT_MAX; + + ReverbData[1].lEnvID = -1; + ReverbData[1].lApertureNum = -1; + ReverbData[1].flDist = FLT_MAX; + + ReverbData[2].lEnvID = lID; + ReverbData[2].lApertureNum = -1; + ReverbData[2].flDist = 0.0f; + + for (i = 0; i < s_lNumEnvironments; i++) + { + // Ignore Environment id lID as this one will always be used + if (i != lID) + { + flNearest = FLT_MAX; + lApertureNum = 0; //shut up compile warning + + for (j = 0; j < s_lpEnvTable[i].ulNumApertures; j++) + { + EMAperture.fX = s_lpEnvTable[i].Aperture[j].vCenter[0]; + EMAperture.fY = s_lpEnvTable[i].Aperture[j].vCenter[1]; + EMAperture.fZ = s_lpEnvTable[i].Aperture[j].vCenter[2]; + + flDistance = CalcDistance(EMAperture, ListPos); + + if (flDistance < flNearest) + { + flNearest = flDistance; + lApertureNum = j; + } + } + + // Now have closest point for this Environment - see if this is closer than any others + + if (flNearest < ReverbData[1].flDist) + { + if (flNearest < ReverbData[0].flDist) + { + ReverbData[1] = ReverbData[0]; + ReverbData[0].flDist = flNearest; + ReverbData[0].lApertureNum = lApertureNum; + ReverbData[0].lEnvID = i; + } + else + { + ReverbData[1].flDist = flNearest; + ReverbData[1].lApertureNum = lApertureNum; + ReverbData[1].lEnvID = i; + } + } + } + } + + } + +#ifdef DISPLAY_CLOSEST_ENVS + char szEnvName1[256] = {0}; + char szEnvName2[256] = {0}; + char szEnvName3[256] = {0}; + + s_lpEAXManager->GetEnvironmentName(ReverbData[0].lEnvID, szEnvName1, 256); + s_lpEAXManager->GetEnvironmentName(ReverbData[1].lEnvID, szEnvName2, 256); + s_lpEAXManager->GetEnvironmentName(ReverbData[2].lEnvID, szEnvName3, 256); + + Com_Printf("Closest zones are %s, %s (Listener in %s)\n", szEnvName1, + szEnvName2, szEnvName3); +#endif + + // Mute any reverbs no longer required ... + + for (i = 0; i < s_NumFXSlots; i++) + { + if ((s_FXSlotInfo[i].lEnvID != -1) && (s_FXSlotInfo[i].lEnvID != ReverbData[0].lEnvID) && (s_FXSlotInfo[i].lEnvID != ReverbData[1].lEnvID) + && (s_FXSlotInfo[i].lEnvID != ReverbData[2].lEnvID)) + { + // This environment is no longer needed + + // Mute it + lVolume = -10000; + if (s_eaxSet(&s_FXSlotInfo[i].FXSlotGuid, EAXFXSLOT_VOLUME, NULL, &lVolume, sizeof(long))!=AL_NO_ERROR) + OutputDebugString("Failed to Mute FX Slot\n"); + + // If any source is sending to this Slot ID then we need to stop them sending to the slot + for (j = 1; j < s_numChannels; j++) + { + if (s_channels[j].lSlotID == i) + { + if (s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_ACTIVEFXSLOTID, s_channels[j].alSource, (void*)&EAX_NULL_GUID, sizeof(GUID))!=AL_NO_ERROR) + { + OutputDebugString("Failed to set Source ActiveFXSlotID to NULL\n"); + } + + s_channels[j].lSlotID = -1; + } + } + + assert(s_FXSlotInfo[i].lEnvID < s_lNumEnvironments && s_FXSlotInfo[i].lEnvID >= 0); + if (s_FXSlotInfo[i].lEnvID < s_lNumEnvironments && s_FXSlotInfo[i].lEnvID >= 0) + { + s_lpEnvTable[s_FXSlotInfo[i].lEnvID].lFXSlotID = -1; + } + s_FXSlotInfo[i].lEnvID = -1; + } + } + + + // Make sure all the reverbs we want are being rendered, if not, find an empty slot + // and apply appropriate reverb settings + for (j = 0; j < 3; j++) + { + bFound = false; + + for (i = 0; i < s_NumFXSlots; i++) + { + if (s_FXSlotInfo[i].lEnvID == ReverbData[j].lEnvID) + { + bFound = true; + break; + } + } + + if (!bFound) + { + // Find the first available slot and use that one + for (i = 0; i < s_NumFXSlots; i++) + { + if (s_FXSlotInfo[i].lEnvID == -1) + { + // Found slot + + // load reverb here + + // Retrieve reverb properties from EAX Manager + if (s_lpEAXManager->GetEnvironmentAttributes(ReverbData[j].lEnvID, &Reverb)==EM_OK) + { + // Override Air Absorption HF + Reverb.flAirAbsorptionHF = 0.0f; + + s_eaxSet(&s_FXSlotInfo[i].FXSlotGuid, EAXREVERB_ALLPARAMETERS, NULL, &Reverb, sizeof(EAXREVERBPROPERTIES)); + + // See if any Sources are in this environment, if they are, enable their sends + ch = s_channels + 1; + for (k = 1; k < s_numChannels; k++, ch++) + { + if (ch->fixed_origin) + { + // Converting from Quake -> DS3D (for EAGLE) ... swap Y and Z + EMSourcePoint.fX = ch->origin[0]; + EMSourcePoint.fY = ch->origin[2]; + EMSourcePoint.fZ = ch->origin[1]; + } + else + { + if (ch->entnum == listener_number) + { + // Source at same position as listener + // Probably won't be any Occlusion / Obstruction effect -- unless the listener is underwater + // Converting from Open AL -> DS3D (for EAGLE) ... invert Z + EMSourcePoint.fX = listener_pos[0]; + EMSourcePoint.fY = listener_pos[1]; + EMSourcePoint.fZ = -listener_pos[2]; + } + else + { + // Get position of Entity + // Converting from Quake -> DS3D (for EAGLE) ... swap Y and Z + EMSourcePoint.fX = loopSounds[ ch->entnum ].origin[0]; + EMSourcePoint.fY = loopSounds[ ch->entnum ].origin[2]; + EMSourcePoint.fZ = loopSounds[ ch->entnum ].origin[1]; + } + } + + // Get Source Environment point + if (s_lpEAXManager->GetListenerDynamicAttributes(0, &EMSourcePoint, &lSourceID, 0)!=EM_OK) + OutputDebugString("Failed to get environment zone for Source\n"); + + if (lSourceID == i) + { + if (s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_ACTIVEFXSLOTID, ch->alSource, (void*)&(s_FXSlotInfo[i].FXSlotGuid), sizeof(GUID))!=AL_NO_ERROR) + { + OutputDebugString("Failed to set Source ActiveFXSlotID to new environment\n"); + } + + ch->lSlotID = i; + } + } + + assert(ReverbData[j].lEnvID < s_lNumEnvironments && ReverbData[j].lEnvID >= 0); + if (ReverbData[j].lEnvID < s_lNumEnvironments && ReverbData[j].lEnvID >= 0) + { + s_FXSlotInfo[i].lEnvID = ReverbData[j].lEnvID; + s_lpEnvTable[ReverbData[j].lEnvID].lFXSlotID = i; + } + break; + } + } + } + } + } + + // Make sure Primary FX Slot ID is set correctly + if (s_EnvironmentID != ReverbData[2].lEnvID) + { + s_eaxSet(&EAXPROPERTYID_EAX40_Context, EAXCONTEXT_PRIMARYFXSLOTID, NULL, &(s_FXSlotInfo[s_lpEnvTable[ReverbData[2].lEnvID].lFXSlotID].FXSlotGuid), sizeof(GUID)); + s_EnvironmentID = ReverbData[2].lEnvID; + } + + // Have right reverbs loaded ... now to pan them and adjust volume + + + // We need to rotate the vector from the Listener to the reverb Aperture by minus the listener + // orientation + + // Need dot product of Listener Orientation and the straight ahead vector (0, 0, 1) + + // Since both vectors are already normalized, and two terms cancel out (0's), the angle + // is the arc cosine of the z component of the Listener Orientation + + float flTheta = (float)acos(ListOri.fZ); + + // If the Listener Orientation is to the left of straight ahead, then invert the angle + if (ListOri.fX < 0) + flTheta = -flTheta; + + float flSin = (float)sin(-flTheta); + float flCos = (float)cos(-flTheta); + + for (i = 0; i < min(s_NumFXSlots,s_lNumEnvironments); i++) + { + if (s_FXSlotInfo[i].lEnvID == s_EnvironmentID) + { + // Listener's environment + + // Find the closest Aperture in *this* environment + + flNearest = FLT_MAX; + lApertureNum = 0; //shut up compile warning + + for (j = 0; j < s_lpEnvTable[s_EnvironmentID].ulNumApertures; j++) + { + EMAperture.fX = s_lpEnvTable[s_EnvironmentID].Aperture[j].vCenter[0]; + EMAperture.fY = s_lpEnvTable[s_EnvironmentID].Aperture[j].vCenter[1]; + EMAperture.fZ = s_lpEnvTable[s_EnvironmentID].Aperture[j].vCenter[2]; + + flDistance = CalcDistance(EMAperture, ListPos); + + if (flDistance < flNearest) + { + flNearest = flDistance; + lApertureNum = j; + } + } + + // Have closest environment, work out pan vector direction + + LR.x = s_lpEnvTable[s_EnvironmentID].Aperture[lApertureNum].vCenter[0] - ListPos.fX; + LR.y = s_lpEnvTable[s_EnvironmentID].Aperture[lApertureNum].vCenter[1] - ListPos.fY; + LR.z = s_lpEnvTable[s_EnvironmentID].Aperture[lApertureNum].vCenter[2] - ListPos.fZ; + + Pan.x = (LR.x * flCos) + (LR.z * flSin); + Pan.y = 0.0f; + Pan.z = (LR.x * -flSin) + (LR.z * flCos); + + Normalize(&Pan); + + + // Adjust magnitude ... + + // Magnitude is based on the angle subtended by the aperture, so compute the angle between + // the vector from the Listener to Pos1 of the aperture, and the vector from the + // Listener to Pos2 of the aperture. + + + LP1.x = s_lpEnvTable[s_EnvironmentID].Aperture[lApertureNum].vPos1[0] - ListPos.fX; + LP1.y = s_lpEnvTable[s_EnvironmentID].Aperture[lApertureNum].vPos1[1] - ListPos.fY; + LP1.z = s_lpEnvTable[s_EnvironmentID].Aperture[lApertureNum].vPos1[2] - ListPos.fZ; + + LP2.x = s_lpEnvTable[s_EnvironmentID].Aperture[lApertureNum].vPos2[0] - ListPos.fX; + LP2.y = s_lpEnvTable[s_EnvironmentID].Aperture[lApertureNum].vPos2[1] - ListPos.fY; + LP2.z = s_lpEnvTable[s_EnvironmentID].Aperture[lApertureNum].vPos2[2] - ListPos.fZ; + + Normalize(&LP1); + Normalize(&LP2); + + float flGamma = acos((LP1.x * LP2.x) + (LP1.y * LP2.y) + (LP1.z * LP2.z)); + + // We want opposite magnitude (because we are 'in' this environment) + float flMagnitude = 1.0f - ((2.0f * (float)sin(flGamma/2.0f)) / flGamma); + + // Negative (because pan should be 180 degrees) + Pan.x *= -flMagnitude; + Pan.y *= -flMagnitude; + Pan.z *= -flMagnitude; + + if (s_eaxSet(&s_FXSlotInfo[i].FXSlotGuid, EAXREVERB_REVERBPAN, NULL, &Pan, sizeof(EAXVECTOR))!=AL_NO_ERROR) + OutputDebugString("Failed to set Listener Reverb Pan\n"); + + if (s_eaxSet(&s_FXSlotInfo[i].FXSlotGuid, EAXREVERB_REFLECTIONSPAN, NULL, &Pan, sizeof(EAXVECTOR))!=AL_NO_ERROR) + OutputDebugString("Failed to set Listener Reflections Pan\n"); + } + else + { + // Find out which Reverb this is + if (ReverbData[0].lEnvID == s_FXSlotInfo[i].lEnvID) + k = 0; + else + k = 1; + + LR.x = s_lpEnvTable[ReverbData[k].lEnvID].Aperture[ReverbData[k].lApertureNum].vCenter[0] - ListPos.fX; + LR.y = s_lpEnvTable[ReverbData[k].lEnvID].Aperture[ReverbData[k].lApertureNum].vCenter[1] - ListPos.fY; + LR.z = s_lpEnvTable[ReverbData[k].lEnvID].Aperture[ReverbData[k].lApertureNum].vCenter[2] - ListPos.fZ; + + // Rotate the vector + + Pan.x = (LR.x * flCos) + (LR.z * flSin); + Pan.y = 0.0f; + Pan.z = (LR.x * -flSin) + (LR.z * flCos); + + Normalize(&Pan); + + // Adjust magnitude ... + + // Magnitude is based on the angle subtended by the aperture, so compute the angle between + // the vector from the Listener to Pos1 of the aperture, and the vector from the + // Listener to Pos2 of the aperture. + + + LP1.x = s_lpEnvTable[ReverbData[k].lEnvID].Aperture[ReverbData[k].lApertureNum].vPos1[0] - ListPos.fX; + LP1.y = s_lpEnvTable[ReverbData[k].lEnvID].Aperture[ReverbData[k].lApertureNum].vPos1[1] - ListPos.fY; + LP1.z = s_lpEnvTable[ReverbData[k].lEnvID].Aperture[ReverbData[k].lApertureNum].vPos1[2] - ListPos.fZ; + + LP2.x = s_lpEnvTable[ReverbData[k].lEnvID].Aperture[ReverbData[k].lApertureNum].vPos2[0] - ListPos.fX; + LP2.y = s_lpEnvTable[ReverbData[k].lEnvID].Aperture[ReverbData[k].lApertureNum].vPos2[1] - ListPos.fY; + LP2.z = s_lpEnvTable[ReverbData[k].lEnvID].Aperture[ReverbData[k].lApertureNum].vPos2[2] - ListPos.fZ; + + Normalize(&LP1); + Normalize(&LP2); + + float flGamma = acos((LP1.x * LP2.x) + (LP1.y * LP2.y) + (LP1.z * LP2.z)); + float flMagnitude = (2.0f * (float)sin(flGamma/2.0f)) / flGamma; + + Pan.x *= flMagnitude; + Pan.y *= flMagnitude; + Pan.z *= flMagnitude; + + if (s_eaxSet(&s_FXSlotInfo[i].FXSlotGuid, EAXREVERB_REVERBPAN, NULL, &Pan, sizeof(EAXVECTOR))!=AL_NO_ERROR) + OutputDebugString("Failed to set Reverb Pan\n"); + + if (s_eaxSet(&s_FXSlotInfo[i].FXSlotGuid, EAXREVERB_REFLECTIONSPAN, NULL, &Pan, sizeof(EAXVECTOR))!=AL_NO_ERROR) + OutputDebugString("Failed to set Reflections Pan\n"); + } + } + + for (i = 0; i < s_NumFXSlots; i++) + { + if (s_FXSlotInfo[i].lEnvID == s_EnvironmentID) + { + lVolume = 0; + if (s_eaxSet(&s_FXSlotInfo[i].FXSlotGuid, EAXFXSLOT_VOLUME, NULL, &lVolume, sizeof(long))!=AL_NO_ERROR) + OutputDebugString("Failed to set Listener's FX Slot Volume to 0\n"); + } + else + { + // This will change to lower the volume based on distance from aperture ... + lVolume = -600; + if (s_eaxSet(&s_FXSlotInfo[i].FXSlotGuid, EAXFXSLOT_VOLUME, NULL, &lVolume, sizeof(long))!=AL_NO_ERROR) + OutputDebugString("Failed to set FX Slot Volume to 0\n"); + } + } + } + + return; +} + +/* + Updates the EAX Buffer related effects on the given Source +*/ +void UpdateEAXBuffer(channel_t *ch) +{ + HRESULT hr; + EMPOINT EMSourcePoint; + EMPOINT EMVirtualSourcePoint; + EAXOBSTRUCTIONPROPERTIES eaxOBProp; + EAXOCCLUSIONPROPERTIES eaxOCProp; + int i; + long lSourceID; + + // If EAX Manager is not initialized, or there is no EAX support, or the listener + // is underwater, return + if ((!s_lpEAXManager) || (!s_bEAX) || (s_bInWater)) + return; + + // Set Occlusion Direct Ratio to the default value (it won't get set by the current version of + // EAX Manager) + eaxOCProp.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; + + // Convert Source co-ordinate to left-handed system + if (ch->fixed_origin) + { + // Converting from Quake -> DS3D (for EAGLE) ... swap Y and Z + EMSourcePoint.fX = ch->origin[0]; + EMSourcePoint.fY = ch->origin[2]; + EMSourcePoint.fZ = ch->origin[1]; + } + else + { + if (ch->entnum == listener_number) + { + // Source at same position as listener + // Probably won't be any Occlusion / Obstruction effect -- unless the listener is underwater + // Converting from Open AL -> DS3D (for EAGLE) ... invert Z + EMSourcePoint.fX = listener_pos[0]; + EMSourcePoint.fY = listener_pos[1]; + EMSourcePoint.fZ = -listener_pos[2]; + } + else + { + // Get position of Entity + // Converting from Quake -> DS3D (for EAGLE) ... swap Y and Z + if (ch->bLooping) + { + EMSourcePoint.fX = loopSounds[ ch->entnum ].origin[0]; + EMSourcePoint.fY = loopSounds[ ch->entnum ].origin[2]; + EMSourcePoint.fZ = loopSounds[ ch->entnum ].origin[1]; + } + else + { + EMSourcePoint.fX = s_entityPosition[ch->entnum][0]; + EMSourcePoint.fY = s_entityPosition[ch->entnum][2]; + EMSourcePoint.fZ = s_entityPosition[ch->entnum][1]; + } + } + } + + long lExclusion; + + // Just determine what environment the source is in + if (s_lpEAXManager->GetListenerDynamicAttributes(0, &EMSourcePoint, &lSourceID, 0)==EM_OK) + { + // See if a Slot is rendering this environment + for (i = 0; i < s_NumFXSlots; i++) + { + if (s_FXSlotInfo[i].lEnvID == lSourceID) + { + // If the Source is not sending to this slot, then enable the send now + if (ch->lSlotID != i) + { + // Set this + if (s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_ACTIVEFXSLOTID, ch->alSource, &s_FXSlotInfo[i].FXSlotGuid, sizeof(GUID))!=AL_NO_ERROR) + OutputDebugString("UpdateEAXBuffer = failed to set ActiveFXSlotID\n"); + + ch->lSlotID = i; + } + + break; + } + } + } + else + { + OutputDebugString("UpdateEAXBuffer::Failed to get Source environment zone\n"); + } + + // Add some Exclusion to sounds that are not located in the Listener's environment + if (s_FXSlotInfo[ch->lSlotID].lEnvID == s_EnvironmentID) + { + lExclusion = 0; + if (s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_EXCLUSION, ch->alSource, &lExclusion, sizeof(long))!=AL_NO_ERROR) + OutputDebugString("UpdateEAXBuffer : Failed to set exclusion to 0\n"); + } + else + { + lExclusion = -1000; + if (s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_EXCLUSION, ch->alSource, &lExclusion, sizeof(long))!=AL_NO_ERROR) + OutputDebugString("UpdateEAXBuffer : Failed to set exclusion to -1000\n"); + } + + if ((ch->entchannel == CHAN_VOICE) || (ch->entchannel == CHAN_VOICE_ATTEN) || (ch->entchannel == CHAN_VOICE_GLOBAL)) + { + // Remove any Occlusion + Obstruction + eaxOBProp.lObstruction = EAXSOURCE_DEFAULTOBSTRUCTION; + eaxOBProp.flObstructionLFRatio = EAXSOURCE_DEFAULTOBSTRUCTIONLFRATIO; + + eaxOCProp.lOcclusion = EAXSOURCE_DEFAULTOCCLUSION; + eaxOCProp.flOcclusionLFRatio = EAXSOURCE_DEFAULTOCCLUSIONLFRATIO; + eaxOCProp.flOcclusionRoomRatio = EAXSOURCE_DEFAULTOCCLUSIONROOMRATIO; + eaxOCProp.flOcclusionDirectRatio = EAXSOURCE_DEFAULTOCCLUSIONDIRECTRATIO; + + s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_OBSTRUCTIONPARAMETERS, + ch->alSource, &eaxOBProp, sizeof(EAXOBSTRUCTIONPROPERTIES)); + + s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_OCCLUSIONPARAMETERS, + ch->alSource, &eaxOCProp, sizeof(EAXOCCLUSIONPROPERTIES)); + } + else + { + // Check for Occlusion + Obstruction + hr = s_lpEAXManager->GetSourceDynamicAttributes(0, &EMSourcePoint, &eaxOBProp.lObstruction, &eaxOBProp.flObstructionLFRatio, + &eaxOCProp.lOcclusion, &eaxOCProp.flOcclusionLFRatio, &eaxOCProp.flOcclusionRoomRatio, &EMVirtualSourcePoint, 0); + if (hr == EM_OK) + { + // Set EAX effect ! + s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_OBSTRUCTIONPARAMETERS, + ch->alSource, &eaxOBProp, sizeof(EAXOBSTRUCTIONPROPERTIES)); + + s_eaxSet(&EAXPROPERTYID_EAX40_Source, EAXSOURCE_OCCLUSIONPARAMETERS, + ch->alSource, &eaxOCProp, sizeof(EAXOCCLUSIONPROPERTIES)); + } + } + + return; +} + +float CalcDistance(EMPOINT A, EMPOINT B) +{ + return (float)sqrt(sqr(A.fX - B.fX)+sqr(A.fY - B.fY) + sqr(A.fZ - B.fZ)); +} diff --git a/codemp/client/snd_dma_console.cpp b/codemp/client/snd_dma_console.cpp new file mode 100644 index 0000000..bc0e9c5 --- /dev/null +++ b/codemp/client/snd_dma_console.cpp @@ -0,0 +1,2990 @@ +/***************************************************************************** + * name: snd_dma.c + * + * desc: main control for any streaming sound output device + * + * + *****************************************************************************/ +// leave this as first line for PCH reasons... +// +// #include "../server/exe_headers.h" + +#include "snd_local_console.h" +#include "snd_music.h" +#include "../zlib/zlib.h" + +// #include "../../toolbox/zlib/zlib.h" + +#include "../client/client.h" + +// Doesn't do anything on Xbox +qboolean s_shutUp = qfalse; + +#ifdef _XBOX +#include +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +#ifdef _GAMECUBE +typedef const char* LPCSTR; +#endif + +static void S_Play_f(void); +#ifndef _JK2MP +static void S_PlayEx_f(void); +#endif +static void S_SoundList_f(void); +static void S_Music_f(void); + +void S_Update_(); +void S_StopAllSounds(void); +static void S_UpdateBackgroundTrack( void ); +unsigned int S_HashName( const char *name ); +static int SND_FreeSFXMem(sfx_t *sfx); + +/*static void S_FreeAllSFXMem(void); +static void S_UnCacheDynamicMusic( void ); +*/ +extern unsigned long crc32(unsigned long crc, const unsigned char *buf, unsigned long len); + +extern int Sys_GetFileCodeSize(int code); + +extern void Sys_StreamInit(void); +extern void Sys_StreamShutdown(void); + +qboolean SND_RegisterAudio_Clean(void); +void S_KillEntityChannel(int entnum, int chan); + +////////////////////////// +// +// vars for bgrnd music track... +// +typedef struct +{ + // + // disk-load stuff + // + char sLoadedDataName[MAX_QPATH]; + int iFileCode; + int iFileSeekTo; + bool bLoaded; + // + // remaining dynamic fields... + // + int iXFadeVolumeSeekTime; + int iXFadeVolumeSeekTo; // when changing this, set the above timer to Sys_Milliseconds(). + // Note that this should be thought of more as an up/down bool rather than as a + // number now, in other words set it only to 0 or 255. I'll probably change this + // to actually be a bool later. + int iXFadeVolume; // 0 = silent, 255 = max mixer vol, though still modulated via overall music_volume + float fSmoothedOutVolume; + qboolean bActive; // whether playing or not + qboolean bExists; // whether was even loaded for this level (ie don't try and start playing it) + // + // new dynamic fields... + // + qboolean bTrackSwitchPending; + qboolean bLooping; + MusicState_e eTS_NewState; + float fTS_NewTime; + // + // Generic... + // + int s_backgroundSize; + int s_backgroundBPS; + + void Rewind() + { + iFileSeekTo = 0; + } + + void SeekTo(float fTime) + { + iFileSeekTo = (int)((float)(s_backgroundBPS) * fTime); + } + + float TotalTime(void) + { + return (float)(s_backgroundSize) / (float)(s_backgroundBPS); + } + + float PlayTime(void) + { + ALfloat playTime; + alGetStreamf(AL_TIME, &playTime); + return playTime; + } + + float ElapsedTime(void) + { + return fmod(PlayTime(), TotalTime()); + } +} MusicInfo_t; + +static void S_SetDynamicMusicState( MusicState_e musicState ); + +#define fDYNAMIC_XFADE_SECONDS (1.f) + +static MusicInfo_t tMusic_Info[eBGRNDTRACK_NUMBEROF] = {0}; +static qboolean bMusic_IsDynamic = qfalse; +static MusicState_e eMusic_StateActual = eBGRNDTRACK_EXPLORE; // actual state, can be any enum +static MusicState_e eMusic_StateRequest = eBGRNDTRACK_EXPLORE; // requested state, can only be explore, action, boss, or silence +static char sMusic_BackgroundLoop[MAX_QPATH] = {0}; // only valid for non-dynamic music +static char sInfoOnly_CurrentDynamicMusicSet[64]; // any old reasonable size, only has to fit stuff like "kejim_post" +// +////////////////////////// + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +// only begin attenuating sound volumes when outside the FULLVOLUME range +#define SOUND_FULLVOLUME 256 + +#define SOUND_ATTENUATE 0.0008f +#define VOICE_ATTENUATE 0.004f + +// This number has dramatic affects on volume. QA has +// determined that 100 is too quiet in some spots and +// 200 is too loud in others. Modify with care... +#define SOUND_REF_DIST_BASE 150.f + +#define SOUND_UPDATE_TIME 100 + +const float SOUND_FMAXVOL=1.0; +const int SOUND_MAXVOL=255; + +int s_soundStarted; +qboolean s_soundMuted; +int s_loopEnabled; +int s_updateTime; + +struct listener_t +{ + ALuint handle; + ALfloat pos[3]; + ALfloat orient[6]; + int entnum; +}; + +#define SND_MAX_LISTENERS 2 +static listener_t s_listeners[SND_MAX_LISTENERS]; +static int s_numListeners; + +static int s_numChannels; // Number of AL Sources == Num of Channels + +#ifdef _XBOX +# define MAX_CHANNELS_2D 64 +# define MAX_CHANNELS_3D 64 +#else +# define MAX_CHANNELS_2D 30 +# define MAX_CHANNELS_3D 30 +#endif + +#define MAX_CHANNELS (MAX_CHANNELS_2D + MAX_CHANNELS_3D) +static channel_t s_channels[MAX_CHANNELS]; + +#define MAX_SFX 3072 // 2048 +#define INVALID_CODE 0 +static sfx_t s_sfxBlock[MAX_SFX]; +static int s_sfxCodes[MAX_SFX]; + +static bool s_registered = false; +static int s_defaultSound = 0; + +typedef struct +{ + int volume; + vec3_t origin; + sfx_t *sfx; + int entnum; + int entchannel; + bool bProcessed; + bool bMarked; +} loopSound_t; + +#define MAX_LOOP_SOUNDS 32 +static int numLoopSounds; +static loopSound_t loopSounds[MAX_LOOP_SOUNDS]; + +int s_entityWavVol[MAX_GENTITIES]; + +cvar_t *s_effects_volume; +cvar_t *s_music_volume; +cvar_t *s_voice_volume; +cvar_t *s_testsound; +cvar_t *s_allowDynamicMusic; +cvar_t *s_show; +cvar_t *s_separation; +cvar_t *s_CPUType; +cvar_t *s_debugdynamic; +cvar_t *s_soundpoolmegs; +//cvar_t *s_language; // note that this is distinct from "g_language" + + + +void S_SoundInfo_f(void) { + Com_Printf("----- Sound Info -----\n" ); + + if (!s_soundStarted) { + Com_Printf ("sound system not started\n"); + } else { + if ( s_soundMuted ) { + Com_Printf ("sound system is muted\n"); + } + } + S_DisplayFreeMemory(); + Com_Printf("----------------------\n" ); +} + + + +/* +================ +S_Init +================ +*/ +void S_Init( void ) { + ALCcontext *ALCContext = NULL; + ALCdevice *ALCDevice = NULL; + cvar_t *cv; + + Com_Printf("\n------- sound initialization -------\n"); + + AS_Init(); + + s_effects_volume = Cvar_Get ("s_effects_volume", "1.0", CVAR_ARCHIVE); + s_voice_volume= Cvar_Get ("s_voice_volume", "1.0", CVAR_ARCHIVE); + s_music_volume = Cvar_Get ("s_music_volume", "0.25", CVAR_ARCHIVE); + s_separation = Cvar_Get ("s_separation", "0.5", CVAR_ARCHIVE); + s_allowDynamicMusic = Cvar_Get ("s_allowDynamicMusic", "1", CVAR_ARCHIVE); + + s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); + s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); + s_debugdynamic = Cvar_Get("s_debugdynamic","0", CVAR_CHEAT); + + s_CPUType = Cvar_Get("sys_cpuid","",0); + s_soundpoolmegs = Cvar_Get("s_soundpoolmegs", "6", CVAR_ARCHIVE); + +// s_language = Cvar_Get("s_language","english",CVAR_ARCHIVE | CVAR_NORESTART); + + cv = Cvar_Get ("s_initsound", "1", CVAR_ROM); + if ( !cv->integer ) { + s_soundStarted = 0; // needed in case you set s_initsound to 0 midgame then snd_restart (div0 err otherwise later) + Com_Printf ("not initializing.\n"); + Com_Printf("------------------------------------\n"); + return; + } + + Cmd_AddCommand("play", S_Play_f); +#ifndef _JK2MP + Cmd_AddCommand("playex", S_PlayEx_f); +#endif + Cmd_AddCommand("music", S_Music_f); + Cmd_AddCommand("soundlist", S_SoundList_f); + Cmd_AddCommand("soundinfo", S_SoundInfo_f); + Cmd_AddCommand("soundstop", S_StopAllSounds); + + // clear out the lip synching override array + memset(s_entityWavVol, 0, sizeof(int) * MAX_GENTITIES); + + ALCDevice = alcOpenDevice((ALubyte*)"DirectSound3D"); + if (!ALCDevice) + return; + + //Create context(s) + ALCContext = alcCreateContext(ALCDevice, NULL); + if (!ALCContext) + return; + + //Set active context + alcMakeContextCurrent(ALCContext); + if (alcGetError(ALCDevice) != ALC_NO_ERROR) + return; + + memset(s_sfxCodes, INVALID_CODE, sizeof(int) * MAX_SFX); + + S_StopAllSounds(); + + s_soundStarted = 1; + s_soundMuted = 1; + s_loopEnabled = 0; + s_updateTime = 0; + + S_SoundInfo_f(); + + memset(s_channels, 0, sizeof(channel_t) * MAX_CHANNELS); + s_numChannels = 0; + + // create music channel + alGenStream(); + + Com_Printf("------------------------------------\n"); + + S_InitLoad(); +} + +// only called from snd_restart. QA request... +// +void S_ReloadAllUsedSounds(void) +{ + if (s_soundStarted && !s_soundMuted ) + { + // new bit, reload all soundsthat are used on the current level->.. + // + for (int i = 0; i < MAX_SFX; ++i) + { + if (s_sfxCodes[i] == INVALID_CODE || s_sfxCodes[i] == s_defaultSound) continue; + + sfx_t *sfx = &s_sfxBlock[i]; + + if ((sfx->iFlags & SFX_FLAG_UNLOADED) && + !(sfx->iFlags & (SFX_FLAG_DEFAULT | SFX_FLAG_DEMAND))) + { + S_StartLoadSound(sfx); + } + } + } +} + +// ======================================================================= +// Shutdown sound engine +// ======================================================================= + +void S_Shutdown( void ) +{ + ALCcontext *ALCContext; + ALCdevice *ALCDevice; + int i; + + if ( !s_soundStarted ) { + return; + } + + alDeleteStream(); + + // Release all the AL Sources (including Music channel (Source 0)) + for (i = 0; i < s_numChannels; i++) + { + alDeleteSources(1, &(s_channels[i].alSource)); + } + + S_FreeAllSFXMem(); + S_UnCacheDynamicMusic(); + + // Release listeners + for (i = 0; i < s_numListeners; ++i) + { + alDeleteListeners(1, &s_listeners[i].handle); + } + s_numListeners = 0; + + // Get active context + ALCContext = alcGetCurrentContext(); + // Get device for active context + ALCDevice = alcGetContextsDevice(ALCContext); + // Release context(s) + alcDestroyContext(ALCContext); + // Close device + alcCloseDevice(ALCDevice); + + s_numChannels = 0; + s_soundStarted = 0; + + Cmd_RemoveCommand("play"); + Cmd_RemoveCommand("music"); + Cmd_RemoveCommand("stopsound"); + Cmd_RemoveCommand("soundlist"); + Cmd_RemoveCommand("soundinfo"); + AS_Free(); + S_CloseLoad(); +} + + + +/* + Mutes / Unmutes all OpenAL sound +*/ +void S_AL_MuteAllSounds(qboolean bMute) +{ + if (!s_soundStarted) return; + if (bMute) alGain(0.f); + else alGain(1.f); +} + +void S_SetVolume(float volume) +{ + if (!s_soundStarted) return; + alGain(volume); + alUpdate(); +} + + + + + +// ======================================================================= +// Load a sound +// ======================================================================= +/* +================== +S_FixMusicFileExtension +================== +*/ +char* S_FixMusicFileName(const char* name) +{ + static char xname[MAX_QPATH]; + +#if defined(_XBOX) + const char* ext = "wxb"; +#elif defined(_WINDOWS) + const char* ext = "wav"; +#elif defined(_GAMECUBE) + const char* ext = "adp"; +#endif + + Q_strncpyz(xname, name, sizeof(xname)); + if (xname[strlen(xname) - 4] != '.') + { + strcat(xname, "."); + strcat(xname, ext); + } + else + { + int len = strlen(xname); + xname[len-3] = ext[0]; + xname[len-2] = ext[1]; + xname[len-1] = ext[2]; + } + +#ifdef _GAMECUBE + if (!strncmp("music/", xname, 6) || + !strncmp("music\\", xname, 6)) + { + char chan_name[MAX_QPATH]; + + /* + ALint is_stereo; + alGeti(AL_STEREO, &is_stereo); + + sprintf(chan_name,"music-%s/%s", + is_stereo ? "stereo" : "mono", &xname[6]); + strcpy(xname, chan_name); + */ + + sprintf(chan_name,"music-stereo/%s", &xname[6]); + strcpy(xname, chan_name); + } +#endif + + return xname; +} + + +/* +================== +S_HashName +================== +*/ +unsigned int S_HashName( const char *name ) { + if (!name) { + Com_Error (ERR_FATAL, "S_HashName: NULL\n"); + } + if (!name[0]) { + Com_Error (ERR_FATAL, "S_HashName: empty name\n"); + } + + if (strlen(name) >= MAX_QPATH) { + Com_Error (ERR_FATAL, "Sound name too long: %s", name); + } + + char sSoundNameNoExt[MAX_QPATH]; + COM_StripExtension(name,sSoundNameNoExt); + + Q_strlwr(sSoundNameNoExt); + for (int i = 0; i < strlen(sSoundNameNoExt); ++i) + { + if (sSoundNameNoExt[i] == '\\') sSoundNameNoExt[i] = '/'; + } + + return crc32(0, (const byte *)sSoundNameNoExt, strlen(sSoundNameNoExt)); +} + +/* +=================== +S_DisableSounds + +Disables sounds until the next S_BeginRegistration. +This is called when the hunk is cleared and the sounds +are no longer valid. +=================== +*/ +void S_DisableSounds( void ) { + if (!s_soundStarted) return; + S_StopAllSounds(); + SND_RegisterAudio_Clean(); // unregister sounds + s_soundMuted = qtrue; +} + +void S_SetLoopState( qboolean s ) { + if (!s_soundStarted) return; + s_loopEnabled = s; +} + +void S_CreateSources( void ) { + int i; + + // Remove any old sources + for (i = 0; i < s_numChannels; ++i) + { + alDeleteSources(1, &s_channels[i].alSource); + } + s_numChannels = 0; + + // Create as many AL Sources (up to Max) as possible + int limit = MAX_CHANNELS_2D + MAX_CHANNELS_3D / s_numListeners; + for (i = 0; i < limit; i++) + { + if (i < MAX_CHANNELS_2D) + { + alGenSources2D(1, &s_channels[i].alSource); + s_channels[i].b2D = true; + } + else + { + alGenSources3D(1, &s_channels[i].alSource); + s_channels[i].b2D = false; + } + + if (alGetError() != AL_NO_ERROR) + { + // Reached limit of sources + break; + } + + if (!s_channels[i].b2D) + { + alSourcef(s_channels[i].alSource, AL_REFERENCE_DISTANCE, SOUND_REF_DIST_BASE); + } + + s_numChannels++; + } + + assert(s_numChannels > MAX_CHANNELS_2D); +} + +/* +===================== +S_BeginRegistration + +===================== +*/ +void S_BeginRegistration( int num_listeners ) +{ + if (!s_soundStarted) return; + + int i; + + s_soundMuted = qfalse; + + // Create listeners + assert(num_listeners <= SND_MAX_LISTENERS); + if (num_listeners < s_numListeners) + { + // remove some listeners + for (i = num_listeners; i < s_numListeners; ++i) + { + alDeleteListeners(1, &s_listeners[i].handle); + } + + s_numListeners = num_listeners; + + S_CreateSources(); + } + else if (num_listeners > s_numListeners) + { + // add some listeners + for (i = s_numListeners; i < num_listeners; ++i) + { + memset(&s_listeners[i], 0, sizeof(listener_t)); + s_listeners[i].entnum = i; + s_listeners[i].orient[2] = -1; + s_listeners[i].orient[4] = 1; + alGenListeners(1, &s_listeners[i].handle); + alListenerfv(s_listeners[i].handle, AL_POSITION, s_listeners[i].pos); + alListenerfv(s_listeners[i].handle, AL_ORIENTATION, s_listeners[i].orient); + } + + s_numListeners = num_listeners; + + S_CreateSources(); + } + + S_SetLoopState(qtrue); + + if (!s_registered) { + s_defaultSound = S_RegisterSound("sound/null.wav"); + S_LoadSound(s_defaultSound); + s_registered = true; + } +} + +/* +================== +S_LookupSfx +================== +*/ +sfxHandle_t S_LookupSfx(int hash) +{ + for (int i = 0; i < MAX_SFX; ++i) + { + if (s_sfxCodes[i] == hash) + { + return i; + } + } + return -1; +} + +/* +================== +S_AllocSfx +================== +*/ +sfxHandle_t S_AllocSfx(int hash) +{ + for (int i = 0; i < MAX_SFX; ++i) + { + if (s_sfxCodes[i] == INVALID_CODE) + { + s_sfxCodes[i] = hash; + return i; + } + } + return -1; +} + + +/* +================== +S_RegisterSound + +Creates a default buzz sound if the file can't be loaded +================== +*/ +sfxHandle_t S_RegisterSound(const char *name) +{ + sfx_t *sfx; + unsigned int hash; + sfxHandle_t handle; + + if (!s_soundStarted) { + return 0; + } + + if ( strlen( name ) >= MAX_QPATH || !name[0] ) { + Com_Printf( S_COLOR_RED"Sound name exceeds MAX_QPATH - %s\n", name ); + return s_defaultSound; + } + + hash = S_HashName( name ); + handle = S_LookupSfx(hash); + + if (handle < 0) + { + handle = S_AllocSfx(hash); + + if (handle < 0) + Com_Error (ERR_DROP, "No free sound channels"); + + sfx = &s_sfxBlock[handle]; + memset(sfx, 0, sizeof(sfx_t)); + + if (strlen(name) < 5 || name[0] == '*') sfx->iFileCode = -1; + else sfx->iFileCode = S_GetFileCode(name); + + sfx->iFlags |= SFX_FLAG_UNLOADED; + } + else + { + sfx = &s_sfxBlock[handle]; + } + + SND_TouchSFX(sfx); + + if ( sfx->iFileCode == -1 ) sfx->iFlags |= SFX_FLAG_DEFAULT; + + if (strstr(name, "chars") || + strstr(name, "chr_d") || + strstr(name, "chr_f")) + { + sfx->iFlags |= SFX_FLAG_VOICE; + sfx->iFlags |= SFX_FLAG_DEMAND; + } + + if ( sfx->iFlags & SFX_FLAG_DEFAULT ) + { + sfx->iFlags |= SFX_FLAG_RESIDENT; + return s_defaultSound; + } + + return handle; +} + + +//============================================================================= +channel_t *S_FindFurthestChannel(void) +{ + int ch_idx; + channel_t *ch; + channel_t *ch_firstToDie = NULL; + int li_idx; + listener_t *li; + int longestDist = -1; + int dist; + + for (li_idx = 0, li = s_listeners; li_idx < s_numListeners; ++li_idx, ++li) + { + for (ch_idx = MAX_CHANNELS_2D, ch = s_channels + ch_idx; + ch_idx < s_numChannels; ch_idx++, ch++) + { + dist = + ((li->pos[0] - ch->origin[0]) * (li->pos[0] - ch->origin[0])) + + ((li->pos[1] - ch->origin[1]) * (li->pos[1] - ch->origin[1])) + + ((li->pos[2] - ch->origin[2]) * (li->pos[2] - ch->origin[2])); + + if (dist > longestDist) + { + longestDist = dist; + ch_firstToDie = ch; + } + } + } + + return ch_firstToDie; +} + +static bool IsListenerEnt(int entnum) +{ + for (int i = 0; i < s_numListeners; ++i) + { + if (s_listeners[i].entnum == entnum) return true; + } + return false; +} + +/* +================= +S_PickChannel +================= +*/ +channel_t *S_PickChannel(int entnum, int entchannel, bool is2D, sfx_t* sfx) +{ + int ch_idx; + channel_t *ch, *ch_firstToDie; + bool foundChan = false; + + if ( entchannel < 0 ) + { + Com_Error (ERR_DROP, "S_PickChannel: entchannel<0"); + } + + // Check for replacement sound, or find the best one to replace + + ch_firstToDie = s_channels; + unsigned int age = 0xFFFFFFFF; + + for (ch_idx = 0, ch = s_channels + ch_idx; ch_idx < s_numChannels; ch_idx++, ch++) + { + // Special check to prevent 2d voices from being played + // twice in 2 player games... + if (is2D && ch->b2D && + sfx == ch->thesfx && + ch->bPlaying && + (sfx->iFlags & SFX_FLAG_VOICE)) + { + return NULL; + } + + // See if the channel is free + if (!ch->thesfx && is2D == ch->b2D && ch->iLastPlayTime < age) + { + ch_firstToDie = ch; + age = ch->iLastPlayTime; + foundChan = true; + } + } + + if (!foundChan) + { + for (ch_idx = 0, ch = s_channels + ch_idx; ch_idx < s_numChannels; ch_idx++, ch++) + { + if ( (ch->entnum == entnum) && + (ch->entchannel == entchannel) && + (ch->entchannel != CHAN_AMBIENT) && + (!IsListenerEnt(ch->entnum)) && + (ch->b2D == is2D) && + (!ch_firstToDie->thesfx || + !(ch_firstToDie->thesfx->iFlags & SFX_FLAG_LOADING)) ) + { + // Same entity and same type of sound effect (entchannel) + ch_firstToDie = ch; + foundChan = true; + break; + } + } + } + + if (!foundChan) + { + if (is2D) + { + // Find random sound effect + ch_firstToDie = s_channels + (rand() % MAX_CHANNELS_2D); + } + else + { + // Find sound effect furthest from listeners + ch_firstToDie = S_FindFurthestChannel(); + } + } + + assert(ch_firstToDie->b2D == is2D); + + if (ch_firstToDie->thesfx && ch_firstToDie->thesfx->iFlags & SFX_FLAG_LOADING) + { + // If the sound is loading, just give up... + return NULL; + } + + if (ch_firstToDie->bPlaying) + { +#ifdef _XBOX + // We have an insane amount of channels on the Xbox + // and stopping one is a blocking operation. Let's + // just assume that no one will care if a sound is + // dropped when over 100 are already playing... + return NULL; +#else + // Stop sound + alSourceStop(ch_firstToDie->alSource); + ch_firstToDie->bPlaying = false; +#endif + } + + // Reset channel variables + alSourcei(ch_firstToDie->alSource, AL_BUFFER, 0); + ch_firstToDie->thesfx = NULL; + ch_firstToDie->bLooping = false; + + return ch_firstToDie; +} + + + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +static void SetChannelOrigin(channel_t *ch, const vec3_t origin, int entityNum) +{ + if (origin) + { + ch->origin[0] = origin[0]; + ch->origin[1] = origin[1]; + ch->origin[2] = origin[2]; + } + else + { + vec3_t pos; + + extern void CG_EntityPosition( int i, vec3_t ret ); + CG_EntityPosition(entityNum, pos); + + ch->origin[0] = pos[0]; + ch->origin[1] = pos[1]; + ch->origin[2] = pos[2]; + } + + ch->bOriginDirty = true; +} + +/* +==================== +S_StartAmbientSound + +Starts an ambient, 'one-shot" sound. +==================== +*/ + +void S_StartAmbientSound( const vec3_t origin, int entityNum, unsigned char volume, sfxHandle_t sfxHandle ) +{ + channel_t *ch; + /*const*/ sfx_t *sfx; + + if( volume == 0) + return; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + if ( sfxHandle < 0 || sfxHandle > MAX_SFX || s_sfxCodes[sfxHandle] == INVALID_CODE ) { + return; + } + if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) { + Com_Error( ERR_DROP, "S_StartAmbientSound: bad entitynum %i", entityNum ); + } + + sfx = &s_sfxBlock[sfxHandle]; + if (sfx->iFlags & SFX_FLAG_UNLOADED){ + S_StartLoadSound(sfx); + } + SND_TouchSFX(sfx); + + // pick a channel to play on + bool is2D = false; + for (int i = 0; i < s_numListeners; ++i) + { + if ((entityNum == s_listeners[i].entnum && !origin) || + (origin && + origin[0] == s_listeners[i].pos[0] && + origin[1] == s_listeners[i].pos[1] && + origin[2] == s_listeners[i].pos[2])) + { + is2D = true; + break; + } + } + + ch = S_PickChannel( entityNum, CHAN_AMBIENT, is2D, NULL ); + if (!ch) { + return; + } + + if (!is2D) + { + SetChannelOrigin(ch, origin, entityNum); + } + + ch->master_vol = volume; + ch->fLastVolume = -1; + ch->entnum = entityNum; + ch->entchannel = CHAN_AMBIENT; + ch->thesfx = sfx; +} + +/* +==================== +S_MuteSound + +Mutes sound on specified channel for specified entity. +This seems to be implemented quite incorrectly on PC. I +think the following is what this function should do... + +Perhaps we should actually be changing the volume on all +the channels that meet our criteria, but for now we'll just +kill the sounds and hope it does what is expected. +==================== +*/ +void S_MuteSound(int entityNum, int entchannel) +{ + S_KillEntityChannel( entityNum, entchannel ); + +/* + if (!s_soundStarted) { + return; + } + + //I guess this works. + channel_t *ch = S_PickChannel( entityNum, entchannel ); + + if (!ch) + { + return; + } + + ch->master_vol = 0; + ch->entnum = 0; + ch->entchannel = 0; + ch->thesfx = 0; + ch->startSample = 0; + + ch->leftvol = 0; + ch->rightvol = 0; +*/ +} + +/* +==================== +S_StartSound + +Validates the parms and ques the sound up +if pos is NULL, the sound will be dynamically sourced from the entity +Entchannel 0 will never override a playing sound +==================== +*/ +extern unsigned int Sys_GetSoundFileCodeFlags(unsigned int code); +extern int Sys_GetSoundFileCodeSize(unsigned int code); +void S_StartSound(const vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) +{ + channel_t *ch; + /*const*/ sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle > MAX_SFX || s_sfxCodes[sfxHandle] == INVALID_CODE ) { + return; + } + + if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) { + Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); + } + + sfx = &s_sfxBlock[sfxHandle]; + + if (sfx->iFlags & SFX_FLAG_UNLOADED){ + S_StartLoadSound(sfx); + } + SND_TouchSFX(sfx); + + // pick a channel to play on + bool is2D = false; + for (int i = 0; i < ClientManager::NumClients(); ++i) + { + if ((entityNum == s_listeners[i].entnum && !origin) || + (origin && + origin[0] == s_listeners[i].pos[0] && + origin[1] == s_listeners[i].pos[1] && + origin[2] == s_listeners[i].pos[2])) + { + is2D = true; + break; + } + } + + if(entchannel == CHAN_VOICE_GLOBAL || entchannel == CHAN_ANNOUNCER) + is2D = true; + + if(Sys_GetSoundFileCodeSize(sfx->iFileCode) == -1) + return; + + unsigned int flags = Sys_GetSoundFileCodeFlags(sfx->iFileCode); + + // might as well add more hacks ;) + if(flags & (1 << SFF_WEAPONS_ATST)) + { + entchannel = CHAN_BODY; + } + + if (entchannel == CHAN_VOICE) + { + // Make howlers and sand_creature VOICE effects use the normal fall-off (they will still be affected + // by the Voice Volume) + if ((flags & (1 << SFF_SAND_CREATURE)) || + (flags & (1 << SFF_HOWLER))) + { + entchannel = CHAN_VOICE_ATTEN; + } + } + if (entchannel == CHAN_WEAPON) + { + // Check if we are playing a 'charging' sound, if so, stop it now .. + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + unsigned int weaponSoundFlags; + + if(ch->thesfx) + weaponSoundFlags = Sys_GetSoundFileCodeFlags(ch->thesfx->iFileCode); + + if ((ch->entnum == entityNum) && (ch->entchannel == CHAN_WEAPON) && (ch->thesfx) && (weaponSoundFlags & (1 << SFF_ALTCHARGE))) + { + // Stop this sound + alSourceStop(ch->alSource); + alSourcei(ch->alSource, AL_BUFFER, NULL); + ch->bPlaying = false; + ch->thesfx = NULL; + break; + } + } + } + else + { + ch = s_channels + 1; + for (i = 1; i < s_numChannels; i++, ch++) + { + unsigned int fallSoundFlags; + + if(ch->thesfx) + fallSoundFlags = Sys_GetSoundFileCodeFlags(ch->thesfx->iFileCode); + + if ((ch->entnum == entityNum) && (ch->thesfx) && + (fallSoundFlags & (1 << SFF_FALLING))) + { + // Stop this sound + alSourceStop(ch->alSource); + alSourcei(ch->alSource, AL_BUFFER, NULL); + ch->bPlaying = false; + ch->thesfx = NULL; + break; + } + } + } + + ch = S_PickChannel( entityNum, entchannel, is2D, sfx ); + if (!ch) { + return; + } + + if (!is2D) + { + SetChannelOrigin(ch, origin, entityNum); + } + + if (entchannel == CHAN_AUTO && (sfx->iFlags & SFX_FLAG_VOICE)) { + entchannel = CHAN_VOICE; // Compensate of the incompetance of others. Yeah. ;) +// entchannel = CHAN_VOICE_ATTEN; // Super hack to put Rancor noises on a different channel E3! + } + + ch->master_vol = SOUND_MAXVOL; //FIXME: Um.. control? + ch->fLastVolume = -1; + ch->entnum = entityNum; + ch->entchannel = entchannel; + ch->thesfx = sfx; + + if (entchannel < CHAN_AMBIENT && IsListenerEnt(ch->entnum)) + { + ch->master_vol = SOUND_MAXVOL * SOUND_FMAXVOL; //this won't be attenuated so let it scale down + } + if ( entchannel == CHAN_VOICE || entchannel == CHAN_VOICE_ATTEN || entchannel == CHAN_VOICE_GLOBAL ) + { + s_entityWavVol[ ch->entnum ] = -1; //we've started the sound but it's silent for now + } +} + +/* +================== +S_StartLocalSound +================== +*/ +void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + // Play a 2D sound -- doesn't matter which listener we use +// S_StartSound (NULL, 0, channelNum, sfxHandle ); + S_StartSound (NULL, s_listeners[ClientManager::ActiveClientNum()].entnum, channelNum, sfxHandle ); +} + + +/* +================== +S_StartLocalLoopingSound +================== +*/ +void S_StartLocalLoopingSound( sfxHandle_t sfxHandle) { + vec3_t nullVec = {0,0,0}; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( VM_Call( uivm, UI_IS_FULLSCREEN ) && cls.state == CA_ACTIVE) { + return; + } + + // Play a 2D sound -- doesn't matter which listener we use + S_AddLoopingSound( 0, nullVec, nullVec, sfxHandle, CHAN_AMBIENT ); + +} + +// Kill an voice sounds from an entity +void S_KillEntityChannel(int entnum, int chan) +{ + int i; + channel_t *ch; + + if ( !s_soundStarted ) { + return; + } + + if ( entnum < s_numListeners && chan == CHAN_VOICE ) { + // don't kill player death sounds + return; + } + + ch = s_channels; + for (i = 0; i < s_numChannels; i++, ch++) + { + if (ch->bPlaying && + ch->entnum == entnum && + ch->entchannel == chan) + { + alSourceStop(ch->alSource); + ch->bPlaying = false; + + alSourcei(ch->alSource, AL_BUFFER, 0); + ch->thesfx = NULL; + ch->bLooping = false; + } + } +} + +/* +================== +S_StopLoopingSound + +Stops all active looping sounds on a specified entity. +Sort of a slow method though, isn't there some better way? +================== +*/ +void S_StopLoopingSound( int entnum ) +{ + if (!s_soundStarted) { + return; + } + + int i = 0; + + while (i < numLoopSounds) + { + if (loopSounds[i].entnum == entnum) + { + int x = i+1; + while (x < numLoopSounds) + { + memcpy(&loopSounds[x-1], &loopSounds[x], sizeof(loopSounds[x])); + x++; + } + numLoopSounds--; + } + i++; + } +} + +// returns length in milliseconds of supplied sound effect... (else 0 for bad handle now) +// +extern int Sys_GetSoundFileCodeSize(unsigned int code); +float S_GetSampleLengthInMilliSeconds( sfxHandle_t sfxHandle) +{ + sfx_t *sfx; + + if (!s_soundStarted) + { //we have no sound, so let's just make a reasonable guess + return 512; + } + + if ( s_sfxCodes[sfxHandle] == INVALID_CODE ) { + return 0.0f; + } + + sfx = &s_sfxBlock[sfxHandle]; + + int size = Sys_GetSoundFileCodeSize(sfx->iFileCode); + if (size < 0) return 0; + + return 1000 * size / (22050 / 2); +} + +/* +================== +S_LoadSound +================== +*/ +void S_LoadSound( sfxHandle_t sfxHandle ) +{ + /*const*/ sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle > MAX_SFX || s_sfxCodes[sfxHandle] == INVALID_CODE ) { + return; + } + + sfx = &s_sfxBlock[sfxHandle]; + + if (sfx->iFlags & SFX_FLAG_UNLOADED){ + S_StartLoadSound(sfx); + + extern void S_DrainRawSoundData(void); + S_DrainRawSoundData(); + } +} + +/* +================== +S_ClearSoundBuffer + +If we are about to perform file access, clear the buffer +so sound doesn't stutter. +================== +*/ +void S_ClearSoundBuffer( void ) { + if ( !s_soundStarted || s_soundMuted ) { + return; + } +} + + +/* +================== +S_StopAllSounds +================== +*/ +void S_StopSounds(void) +{ + int i; //, j; + channel_t *ch; + + if ( !s_soundStarted ) { + return; + } + + // stop looping sounds + S_ClearLoopingSounds(); + + // clear all the s_channels + ch = s_channels; + for (i = 0; i < s_numChannels; i++, ch++) + { + if (ch->bPlaying) + { + alSourceStop(ch->alSource); + ch->bPlaying = false; + } + + alSourcei(ch->alSource, AL_BUFFER, 0); + ch->thesfx = NULL; + ch->bLooping = false; + } + + // clear out the lip synching override array + memset(s_entityWavVol, 0, sizeof(int) * MAX_GENTITIES); + + S_ClearSoundBuffer (); +} + +/* +================== +S_StopAllSounds + and music +================== +*/ +void S_StopAllSounds(void) { + if ( !s_soundStarted ) { + return; + } + // stop the background music + S_StopBackgroundTrack(); + + S_StopSounds(); +} + +/* +============================================================== + +continuous looping sounds are added each frame + +============================================================== +*/ + +/* +================== +S_ClearLoopingSounds + +================== +*/ +void S_ClearLoopingSounds( void ) +{ + if ( !s_soundStarted ) { + return; + } + + int i; + + for (i = 0; i < MAX_LOOP_SOUNDS; i++) + { + loopSounds[i].bProcessed = false; + loopSounds[i].bMarked = false; + loopSounds[i].sfx = NULL; + } + + numLoopSounds = 0; +} + +/* +================== +S_AddLoopingSound + +Called during entity generation for a frame +================== +*/ +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle, int chan ) { + /*const*/ sfx_t *sfx; + + if ( VM_Call( uivm, UI_IS_FULLSCREEN ) && cls.state == CA_ACTIVE) { + return; + } + + if ( !s_soundStarted || s_soundMuted || !s_loopEnabled ) { + return; + } + if ( numLoopSounds >= MAX_LOOP_SOUNDS ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle > MAX_SFX || s_sfxCodes[sfxHandle] == INVALID_CODE ) { + return; + } + + sfx = &s_sfxBlock[sfxHandle]; + if (sfx->iFlags & SFX_FLAG_UNLOADED){ + S_StartLoadSound(sfx); + } + SND_TouchSFX(sfx); + + loopSounds[numLoopSounds].origin[0] = origin[0]; + loopSounds[numLoopSounds].origin[1] = origin[1]; + loopSounds[numLoopSounds].origin[2] = origin[2]; + + loopSounds[numLoopSounds].sfx = sfx; + loopSounds[numLoopSounds].volume = SOUND_MAXVOL; + loopSounds[numLoopSounds].entnum = entityNum; + loopSounds[numLoopSounds].entchannel = chan; + numLoopSounds++; +} + + +/* +================== +S_AddAmbientLoopingSound +================== +*/ +void S_AddAmbientLoopingSound( const vec3_t origin, unsigned char volume, sfxHandle_t sfxHandle ) +{ + /*const*/ sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted || !s_loopEnabled ) { + return; + } + if ( numLoopSounds >= MAX_LOOP_SOUNDS ) { + return; + } + + if ( VM_Call( uivm, UI_IS_FULLSCREEN ) && cls.state == CA_ACTIVE) { + return; + } + + if (volume == 0) + return; + + if ( sfxHandle < 0 || sfxHandle > MAX_SFX || s_sfxCodes[sfxHandle] == INVALID_CODE ) { + return; + } + + sfx = &s_sfxBlock[sfxHandle]; + if (sfx->iFlags & SFX_FLAG_UNLOADED){ + S_StartLoadSound(sfx); + } + SND_TouchSFX(sfx); + + loopSounds[numLoopSounds].origin[0] = origin[0]; + loopSounds[numLoopSounds].origin[1] = origin[1]; + loopSounds[numLoopSounds].origin[2] = origin[2]; + + loopSounds[numLoopSounds].sfx = sfx; + loopSounds[numLoopSounds].volume = volume; + loopSounds[numLoopSounds].entnum = -1; + numLoopSounds++; +} + + + + +/* +===================== +S_UpdateEntityPosition + +let the sound system know where an entity currently is +====================== +*/ +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + if ( !s_soundStarted ) { + return; + } + + channel_t *ch; + int i; + + if ( entityNum < 0 || entityNum > MAX_GENTITIES ) { + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + } + + if (entityNum == 0) + return; + + ch = s_channels; + for (i = 0; i < s_numChannels; i++, ch++) + { + if ((ch->bPlaying) && + (ch->entnum == entityNum) && + (!ch->b2D)) + { + if (ch->origin[0] != origin[0] || + ch->origin[1] != origin[1] || + ch->origin[2] != origin[2]) + { + ch->origin[0] = origin[0]; + ch->origin[1] = origin[1]; + ch->origin[2] = origin[2]; + ch->bOriginDirty = true; + } + } + } +} + + +/* +============ +S_Respatialize + +Change the volumes of all the playing sounds for changes in their positions +============ +*/ +void S_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], qboolean inwater ) +{ + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + int index = 0; + +#ifdef _XBOX + if ( ClientManager::splitScreenMode == qtrue ) { + index = entityNum; + } +#endif + + if ( index >= s_numListeners ) { + return; + } + + listener_t *li = &s_listeners[index]; + + li->entnum = entityNum; + + li->pos[0] = head[0]; + li->pos[1] = head[1]; + li->pos[2] = head[2]; + alListenerfv(li->handle, AL_POSITION, li->pos); + + li->orient[0] = axis[0][0]; + li->orient[1] = axis[0][1]; + li->orient[2] = axis[0][2]; + li->orient[3] = axis[2][0]; + li->orient[4] = axis[2][1]; + li->orient[5] = axis[2][2]; + + alListenerfv(li->handle, AL_ORIENTATION, li->orient); +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Update( void ) { + if ( !s_soundStarted ) { + return; + } + + // don't update too often + int now = Sys_Milliseconds(); + if (now - s_updateTime < SOUND_UPDATE_TIME) { + return; + } + s_updateTime = now; + + if ( s_soundMuted ) { + alUpdate(); + return; + } + + // finish up any pending loads + S_UpdateLoading(); + + // update the music stream + S_UpdateBackgroundTrack(); + + // mix some sound + S_Update_(); + + alUpdate(); +} + +static void UpdatePosition(channel_t *ch) +{ + if ( !ch->b2D ) + { + if ( ch->bLooping && ch->bPlaying ) + { + loopSound_t *loop = &loopSounds[ch->loopChannel]; + if ( loop->origin[0] != ch->origin[0] || + loop->origin[1] != ch->origin[1] || + loop->origin[2] != ch->origin[2] ) + { + ch->origin[0] = loop->origin[0]; + ch->origin[1] = loop->origin[1]; + ch->origin[2] = loop->origin[2]; + ch->bOriginDirty = true; + } + } + + if ( ch->bOriginDirty ) + { + alSourcefv(ch->alSource, AL_POSITION, ch->origin); + ch->bOriginDirty = false; + } + } +} + +static void UpdateGain(channel_t *ch) +{ + float v = 0.f; + + if ( ch->bLooping && ch->bPlaying ) + { + loopSound_t *loop = &loopSounds[ch->loopChannel]; + if ( loop->volume != ch->master_vol ) + { + ch->master_vol = loop->volume; + } + } + + if ( ch->entchannel == CHAN_ANNOUNCER || + ch->entchannel == CHAN_VOICE || + ch->entchannel == CHAN_VOICE_ATTEN || + ch->entchannel == CHAN_VOICE_GLOBAL ) + { + v = ((float)(ch->master_vol) * s_voice_volume->value) / 255.0f; + } + else if ( ch->entchannel == CHAN_MUSIC ) + { + v = ((float)(ch->master_vol) * s_music_volume->value) / 255.f; + } + else + { + v = ((float)(ch->master_vol) * s_effects_volume->value) / 255.f; + } + + if ( ch->fLastVolume != v) + { + alSourcef(ch->alSource, AL_GAIN, v); + ch->fLastVolume = v; + } +} + +static void UpdatePlayState(channel_t *ch) +{ + if (!ch->bPlaying) return; + + if (ch->bLooping) + { + // Looping sound + loopSound_t *loop = &loopSounds[ch->loopChannel]; + + if (loop->bProcessed == false && loop->sfx != NULL && + (loop->sfx == ch->thesfx || + (loop->sfx->iFlags & SFX_FLAG_DEFAULT))) + { + // Playing + loop->bProcessed = true; + } + else + { + // Sound no longer needed + alSourceStop(ch->alSource); + alSourcei(ch->alSource, AL_BUFFER, 0); + ch->thesfx = NULL; + ch->bPlaying = false; + } + } + else + { + // Single shot sound + ALint state; + alGetSourcei(ch->alSource, AL_SOURCE_STATE, &state); + if (state == AL_STOPPED) + { + alSourcei(ch->alSource, AL_BUFFER, 0); + ch->thesfx = NULL; + ch->bPlaying = false; + } + } +} + +static void UpdateAttenuation(channel_t *ch) +{ + if (!ch->b2D) + { + /* + if ( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE_ATTEN || ch->entchannel == CHAN_VOICE_GLOBAL ) + { + alSourcef(ch->alSource, AL_REFERENCE_DISTANCE, 1500.0f); + } + else + { + alSourcef(ch->alSource, AL_REFERENCE_DISTANCE, 400.0f); + } + */ +#ifdef _XBOX + // if (ClientManager::splitScreenMode == qfalse) + // { +#endif + switch (ch->entchannel) + { + case CHAN_VOICE_ATTEN: + alSourcef(ch->alSource, AL_REFERENCE_DISTANCE, 300.0f); + break; + case CHAN_VOICE: + case CHAN_LESS_ATTEN: + case CHAN_ANNOUNCER: + case CHAN_LOCAL_SOUND: + case CHAN_BODY: + case CHAN_VOICE_GLOBAL: + case CHAN_LOCAL: + alSourcef(ch->alSource, AL_REFERENCE_DISTANCE, 1500.0f); + break; + default: + alSourcef(ch->alSource, AL_REFERENCE_DISTANCE, 300.0f); + break; + } +#ifdef _XBOX + // } + // else + // { + // alSourcef(ch->alSource, AL_REFERENCE_DISTANCE, 1500.0f); + // } +#endif + } +} + +static void PlaySingleShot(channel_t *ch) +{ + alSourcei(ch->alSource, AL_LOOPING, AL_FALSE); + + UpdateAttenuation(ch); + UpdatePosition(ch); + + // Attach buffer to source + alSourcei(ch->alSource, AL_BUFFER, ch->thesfx->Buffer); + + // Clear error state, and check for successful Play call + alGetError(); + alSourcePlay(ch->alSource); + if (alGetError() == AL_NO_ERROR) + { + ch->bPlaying = true; + ch->iLastPlayTime = Sys_Milliseconds(); + } +} + +void UpdateLoopingSounds() +{ + // Look for non-processed loops that are ready to play + for (int j = 0; j < numLoopSounds; j++) + { + loopSound_t *loop = &loopSounds[j]; + + { + // merge all loops with the same sfx into a single loop + float num = 1; + for (int k = j+1; k < numLoopSounds; ++k) + { + if (loopSounds[k].sfx == loop->sfx) + { + loop->origin[0] += loopSounds[k].origin[0]; + loop->origin[1] += loopSounds[k].origin[1]; + loop->origin[2] += loopSounds[k].origin[2]; + loop->volume += loopSounds[k].volume; + loopSounds[k].bProcessed = true; + num += 1; + } + } + + loop->origin[0] /= num; + loop->origin[1] /= num; + loop->origin[2] /= num; + loop->volume /= (int)num; + } + + if (loop->bProcessed == false && (loop->sfx->iFlags & SFX_FLAG_RESIDENT)) + { + // play the loop + bool is2D = false; + for (int i = 0; i < s_numListeners; ++i) + { + if (loop->entnum == s_listeners[i].entnum || + (loop->origin[0] == s_listeners[i].pos[0] && + loop->origin[1] == s_listeners[i].pos[1] && + loop->origin[2] == s_listeners[i].pos[2])) + { + is2D = true; + break; + } + } + + channel_t *ch = S_PickChannel(0, 0, is2D, NULL); + if (!ch) continue; + + ch->master_vol = loop->volume; + ch->fLastVolume = -1; + ch->entnum = loop->entnum; + ch->entchannel = loop->entchannel; + ch->thesfx = loop->sfx; + ch->loopChannel = j; + ch->bLooping = true; + + ch->origin[0] = loop->origin[0]; + ch->origin[1] = loop->origin[1]; + ch->origin[2] = loop->origin[2]; + ch->bOriginDirty = true; + + alSourcei(ch->alSource, AL_LOOPING, AL_TRUE); + alSourcei(ch->alSource, AL_BUFFER, ch->thesfx->Buffer); + UpdateAttenuation(ch); + UpdatePosition(ch); + UpdateGain(ch); + + alGetError(); + alSourcePlay(ch->alSource); + if (alGetError() == AL_NO_ERROR) + { + ch->bPlaying = true; + ch->iLastPlayTime = Sys_Milliseconds(); + } + } + } +} + +static void SyncChannelLoops(void) +{ + channel_t *ch; + int i, j; + + // Try to match up channels with looping sounds + // (The order of sounds in loopSounds can change + // frame to frame.) + ch = s_channels; + for ( i = 0; i < s_numChannels ; i++, ch++ ) + { + if ( ch->bPlaying && ch->bLooping ) + { + for ( j = 0; j < numLoopSounds; ++j ) + { + if ( ch->thesfx == loopSounds[j].sfx && + !loopSounds[j].bMarked ) + { + ch->loopChannel = j; + loopSounds[j].bMarked = true; + break; + } + } + } + } +} + +void S_Update_(void) +{ + channel_t *ch; + int i; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + memset(s_entityWavVol, 0, sizeof(int) * MAX_GENTITIES); + + SyncChannelLoops(); + + ch = s_channels; + for ( i = 0; i < s_numChannels ; i++, ch++ ) + { + if ( !ch->thesfx ) continue; + + if ( ch->thesfx->iFlags & SFX_FLAG_UNLOADED ) + { + // if the sound is not going to be loaded, force the + // playing flag high, stop the source, and hope that + // the update code cleans it up... + ch->bPlaying = true; + alSourceStop(ch->alSource); + continue; + } + + if ( ch->entchannel == CHAN_VOICE || + ch->entchannel == CHAN_VOICE_ATTEN || + ch->entchannel == CHAN_VOICE_GLOBAL ) + { + s_entityWavVol[ch->entnum] = ch->bPlaying ? 4 : -1; + } + + if ( !(ch->thesfx->iFlags & SFX_FLAG_RESIDENT) ) continue; + + UpdatePosition(ch); + UpdateGain(ch); + + if ( ch->bPlaying ) + { + UpdatePlayState(ch); + } + else + { + PlaySingleShot(ch); + } + } + + UpdateLoopingSounds(); +} + + + +/* +=============================================================================== + +console functions + +=============================================================================== +*/ + +static void S_Play_f( void ) { + int i; + sfxHandle_t h; + char name[256]; + + i = 1; + while ( i xOffset yOffset zOffset channel + */ +#ifndef _JK2MP +static void S_PlayEx_f( void ) { + sfxHandle_t h; + char name[256] = { 0 }; + vec3_t origin; + int entchannel; + + if (Cmd_Argc() < 6) + return; + + Q_strncpyz( name, Cmd_Argv(1), sizeof(name) ); + h = S_RegisterSound( name ); + if (!h) + return; + + extern void G_EntityPosition( int i, vec3_t ret ); + G_EntityPosition(0, origin); + + origin[0] += atof(Cmd_Argv(2)); + origin[1] += atof(Cmd_Argv(3)); + origin[2] += atof(Cmd_Argv(4)); + + entchannel = atoi(Cmd_Argv(5)); + + S_StartSound(origin, 0, entchannel, h); +} +#endif + +static void S_Music_f( void ) { + int c; + + c = Cmd_Argc(); + + if ( c == 2 ) { + S_StartBackgroundTrack( Cmd_Argv(1), Cmd_Argv(1), qfalse ); + } else if ( c == 3 ) { + S_StartBackgroundTrack( Cmd_Argv(1), Cmd_Argv(2), qfalse ); + } else { + Com_Printf ("music [loopfile]\n"); + return; + } +} + +void S_SoundList_f( void ) { +} + + +/* +=============================================================================== + +background music functions + +=============================================================================== +*/ + +// fixme: need to move this into qcommon sometime?, but too much stuff altered by other people and I won't be able +// to compile again for ages if I check that out... +// +// DO NOT replace this with a call to FS_FileExists, that's for checking about writing out, and doesn't work for this. +// +qboolean S_MusicFileExists( const char *psFilename ) +{ + fileHandle_t fhTemp; + + char* pLoadName = S_FixMusicFileName(psFilename); + + FS_FOpenFileRead (pLoadName, &fhTemp, qtrue); // qtrue so I can fclose the handle without closing a PAK + if (!fhTemp) + return qfalse; + + FS_FCloseFile(fhTemp); + return qtrue; +} + +static void S_StopBackgroundTrack_Actual( MusicInfo_t *pMusicInfo ) +{ + pMusicInfo->bLoaded = false; + pMusicInfo->Rewind(); + alStreamStop(); +} + +static void FreeMusic( MusicInfo_t *pMusicInfo ) +{ + pMusicInfo->sLoadedDataName[0] = '\0'; +} + +// called only by snd_shutdown (from snd_restart or app exit) +// +static void S_UnCacheDynamicMusic( void ) +{ + for (int i = eBGRNDTRACK_DATABEGIN; i != eBGRNDTRACK_DATAEND; i++) + { + FreeMusic( &tMusic_Info[i]); + } +} + +static qboolean S_StartBackgroundTrack_Actual( MusicInfo_t *pMusicInfo, qboolean qbDynamic, const char *intro, const char *loop ) +{ + Q_strncpyz( sMusic_BackgroundLoop, loop, sizeof( sMusic_BackgroundLoop )); + + char* name = S_FixMusicFileName(intro); + + if ( !intro[0] ) { + S_StopBackgroundTrack_Actual( pMusicInfo ); + return qfalse; + } + + // new bit, if file requested is not same any loaded one (if prev was in-mem), ditch it... + // + if (Q_stricmp(name, pMusicInfo->sLoadedDataName)) + { + FreeMusic( pMusicInfo ); + } + + // + // open up a wav file and get all the info + // + fileHandle_t handle; + int len = FS_FOpenFileRead( name, &handle, qtrue ); + if ( !handle ) { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", name ); +#endif + S_StopBackgroundTrack_Actual( pMusicInfo ); + return qfalse; + } + +#if defined(_XBOX) || defined(_WINDOWS) + // read enough of the file to get the header... + byte buffer[128]; + FS_Read(buffer, sizeof(buffer), handle); + FS_FCloseFile( handle ); + + wavinfo_t info = GetWavInfo(buffer); + if ( info.size == 0 ) { +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW "WARNING: Invalid format in %s\n", name); +#endif + S_StopBackgroundTrack_Actual( pMusicInfo ); + return qfalse; + } + + pMusicInfo->s_backgroundSize = info.size; + pMusicInfo->s_backgroundBPS = info.rate * info.width / 8; + if (info.format == AL_FORMAT_STEREO4) + { + pMusicInfo->s_backgroundBPS <<= 1; + } +#elif defined(_GAMECUBE) + FS_FCloseFile( handle ); + pMusicInfo->s_backgroundSize = len; + pMusicInfo->s_backgroundBPS = 48000 * 4 / 8 * 2; +#endif + + Q_strncpyz(pMusicInfo->sLoadedDataName, intro, sizeof(pMusicInfo->sLoadedDataName)); + pMusicInfo->iFileCode = Sys_GetFileCode(name); + pMusicInfo->bLoaded = true; + + return qtrue; +} + +static void S_SwitchDynamicTracks( MusicState_e eOldState, MusicState_e eNewState, qboolean bNewTrackStartsFullVolume ) +{ + // copy old track into fader... + // + tMusic_Info[ eBGRNDTRACK_FADE ] = tMusic_Info[ eOldState ]; + tMusic_Info[ eBGRNDTRACK_FADE ].iXFadeVolumeSeekTime= Sys_Milliseconds(); + tMusic_Info[ eBGRNDTRACK_FADE ].iXFadeVolumeSeekTo = 0; + // + // ... and deactivate... + // + tMusic_Info[ eOldState ].bActive = qfalse; + // + // set new track to either full volume or fade up... + // + tMusic_Info[eNewState].bActive = qtrue; + tMusic_Info[eNewState].iXFadeVolumeSeekTime = Sys_Milliseconds(); + tMusic_Info[eNewState].iXFadeVolumeSeekTo = 255; + tMusic_Info[eNewState].iXFadeVolume = bNewTrackStartsFullVolume ? 255 : 0; + + eMusic_StateActual = eNewState; + + // sanity check + if (tMusic_Info[eNewState].iFileSeekTo >= tMusic_Info[eNewState].s_backgroundSize) + { + tMusic_Info[eNewState].iFileSeekTo = 0; + } +} + +// called by both the config-string parser and the console-command state-changer... +// +// This either changes the music right now (copying track structures etc), or leaves the new state as pending +// so it gets picked up by the general music player if in a transition that can't be overridden... +// +static void S_SetDynamicMusicState( MusicState_e eNewState ) +{ + if (eMusic_StateRequest != eNewState) + { + eMusic_StateRequest = eNewState; + + if (s_debugdynamic->integer) + { + LPCSTR psNewStateString = Music_BaseStateToString( eNewState, qtrue ); + psNewStateString = psNewStateString?psNewStateString:""; + + Com_Printf( S_COLOR_MAGENTA "S_SetDynamicMusicState( Request: \"%s\" )\n", psNewStateString ); + } + } +} + + +static void S_HandleDynamicMusicStateChange( void ) +{ + if (eMusic_StateRequest != eMusic_StateActual) + { + // check whether or not the new request can be honoured, given what's currently playing... + // + if (Music_StateCanBeInterrupted( eMusic_StateActual, eMusic_StateRequest )) + { + switch (eMusic_StateRequest) + { + case eBGRNDTRACK_EXPLORE: // ... from action or silence + { + switch (eMusic_StateActual) + { + case eBGRNDTRACK_ACTION: // action->explore + { + // find the transition track to play, and the entry point for explore when we get there, + // and also see if we're at a permitted exit point to switch at all... + // + float fPlayingTimeElapsed = tMusic_Info[ eMusic_StateActual ].ElapsedTime(); + + // supply: + // + // playing point in float seconds + // enum of track being queried + // + // get: + // + // enum of transition track to switch to + // float time of entry point of new track *after* transition + + MusicState_e eTransition; + float fNewTrackEntryTime = 0.0f; + if (Music_AllowedToTransition( fPlayingTimeElapsed, eBGRNDTRACK_ACTION, &eTransition, &fNewTrackEntryTime)) + { + tMusic_Info[eTransition].Rewind(); + tMusic_Info[eTransition].bTrackSwitchPending = qtrue; + tMusic_Info[eTransition].bLooping = qfalse; + tMusic_Info[eTransition].eTS_NewState = eMusic_StateRequest; + tMusic_Info[eTransition].fTS_NewTime = fNewTrackEntryTime; + + S_SwitchDynamicTracks( eMusic_StateActual, eTransition, qfalse ); // qboolean bNewTrackStartsFullVolume + } + } + break; + + case eBGRNDTRACK_SILENCE: // silence->explore + { + tMusic_Info[ eMusic_StateRequest ].Rewind(); + S_SwitchDynamicTracks( eMusic_StateActual, eMusic_StateRequest, qfalse ); // qboolean bNewTrackStartsFullVolume + } + break; + + default: // trying to transition from some state I wasn't aware you could transition from (shouldn't happen), so ignore + { + assert(0); + S_SwitchDynamicTracks( eMusic_StateActual, eBGRNDTRACK_SILENCE, qfalse ); // qboolean bNewTrackStartsFullVolume + } + break; + } + } + break; + + case eBGRNDTRACK_SILENCE: // from explore or action + { + switch (eMusic_StateActual) + { + case eBGRNDTRACK_ACTION: // action->silence + case eBGRNDTRACK_EXPLORE: // explore->silence + { + // find the transition track to play, and the entry point for explore when we get there, + // and also see if we're at a permitted exit point to switch at all... + // + float fPlayingTimeElapsed = tMusic_Info[ eMusic_StateActual ].ElapsedTime(); + + MusicState_e eTransition; + float fNewTrackEntryTime = 0.0f; + if (Music_AllowedToTransition( fPlayingTimeElapsed, eMusic_StateActual, &eTransition, &fNewTrackEntryTime)) + { + tMusic_Info[eTransition].Rewind(); + tMusic_Info[eTransition].bTrackSwitchPending = qtrue; + tMusic_Info[eTransition].bLooping = qfalse; + tMusic_Info[eTransition].eTS_NewState = eMusic_StateRequest; + tMusic_Info[eTransition].fTS_NewTime = 0.0f; //fNewTrackEntryTime; irrelevant when switching to silence + + S_SwitchDynamicTracks( eMusic_StateActual, eTransition, qfalse ); // qboolean bNewTrackStartsFullVolume + } + } + break; + + default: // some unhandled type switching to silence + assert(0); // fall through since boss case just does silence->switch anyway + + case eBGRNDTRACK_BOSS: // boss->silence + { + tMusic_Info[eBGRNDTRACK_SILENCE].Rewind(); + S_SwitchDynamicTracks( eMusic_StateActual, eBGRNDTRACK_SILENCE, qfalse ); // qboolean bNewTrackStartsFullVolume + } + break; + } + } + break; + + case eBGRNDTRACK_ACTION: // anything->action + { + switch (eMusic_StateActual) + { + case eBGRNDTRACK_SILENCE: // silence->action + { + tMusic_Info[ eMusic_StateRequest ].Rewind(); + S_SwitchDynamicTracks( eMusic_StateActual, eMusic_StateRequest, qfalse ); // qboolean bNewTrackStartsFullVolume + } + break; + + default: // !silence->action + { + float fEntryTime = Music_GetRandomEntryTime( eMusic_StateRequest ); + tMusic_Info[ eMusic_StateRequest ].SeekTo(fEntryTime); + S_SwitchDynamicTracks( eMusic_StateActual, eMusic_StateRequest, qfalse ); // qboolean bNewTrackStartsFullVolume + } + break; + } + } + break; + + case eBGRNDTRACK_BOSS: + { + tMusic_Info[eMusic_StateRequest].Rewind(); + S_SwitchDynamicTracks( eMusic_StateActual, eMusic_StateRequest, qfalse ); // qboolean bNewTrackStartsFullVolume + } + break; + + case eBGRNDTRACK_DEATH: + { + tMusic_Info[eMusic_StateRequest].Rewind(); + S_SwitchDynamicTracks( eMusic_StateActual, eMusic_StateRequest, qtrue ); // qboolean bNewTrackStartsFullVolume + } + break; + + default: assert(0); break; // unknown new mode request, so just ignore it + } + } + } +} + + + +static char gsIntroMusic[MAX_QPATH]={0}; +static char gsLoopMusic [MAX_QPATH]={0}; + +void S_RestartMusic( void ) +{ + if (s_soundStarted && !s_soundMuted ) + { + S_StartBackgroundTrack( gsIntroMusic, gsLoopMusic, qfalse ); // ( default music start will set the state to EXPLORE ) + S_SetDynamicMusicState( eMusic_StateRequest ); // restore to prev state + } +} + + +// Basic logic here is to see if the intro file specified actually exists, and if so, then it's not dynamic music, +// When called by the cgame start it loads up, then stops the playback (because of stutter issues), so that when the +// actual snapshot is received and the real play request is processed the data has already been loaded so will be quicker. +// +void S_StartBackgroundTrack( const char *intro, const char *loop, qboolean bCalledByCGameStart ) +{ + bMusic_IsDynamic = qfalse; + + if (!s_soundStarted) + { //we have no sound, so don't even bother trying + return; + } + + if ( !intro ) { + intro = ""; + } + if ( !loop || !loop[0] ) { + loop = intro; + } + + Q_strncpyz(gsIntroMusic,intro, sizeof(gsIntroMusic)); + Q_strncpyz(gsLoopMusic, loop, sizeof(gsLoopMusic)); + + char sName[MAX_QPATH]; + Q_strncpyz(sName,intro,sizeof(sName)); + + COM_DefaultExtension( sName, sizeof( sName ), ".wxb" ); + + // if dynamic music not allowed, then just stream the explore music instead of playing dynamic... + // + if (!s_allowDynamicMusic->integer && Music_DynamicDataAvailable(intro)) // "intro", NOT "sName" (i.e. don't use version with ".wxb" extension) + { + LPCSTR psMusicName = Music_GetFileNameForState( eBGRNDTRACK_DATABEGIN ); + if (psMusicName && S_MusicFileExists( psMusicName )) + { + Q_strncpyz(sName,psMusicName,sizeof(sName)); + } + } + + // conceptually we always play the 'intro'[/sName] track, intro-to-loop transition is handled in UpdateBackGroundTrack(). + // + if ( (strstr(sName,"/") && S_MusicFileExists( sName )) ) // strstr() check avoids extra file-exists check at runtime if reverting from streamed music to dynamic since literal files all need at least one slash in their name (eg "music/blah") + { + Com_DPrintf("S_StartBackgroundTrack: Found/using non-dynamic music track '%s'\n", sName); + tMusic_Info[eBGRNDTRACK_NONDYNAMIC].bLooping = qtrue; + S_StartBackgroundTrack_Actual( &tMusic_Info[eBGRNDTRACK_NONDYNAMIC], bMusic_IsDynamic, sName, sName ); + } + else + { + if (Music_DynamicDataAvailable(intro)) // "intro", NOT "sName" (i.e. don't use version with ".wxb" extension) + { + int i; + extern const char *Music_GetLevelSetName(void); + Q_strncpyz(sInfoOnly_CurrentDynamicMusicSet, Music_GetLevelSetName(), sizeof(sInfoOnly_CurrentDynamicMusicSet)); + for (i = eBGRNDTRACK_DATABEGIN; i != eBGRNDTRACK_DATAEND; i++) + { + qboolean bOk = qfalse; + LPCSTR psMusicName = Music_GetFileNameForState( (MusicState_e) i); + if (psMusicName && (!Q_stricmp(tMusic_Info[i].sLoadedDataName, psMusicName) || S_MusicFileExists( psMusicName )) ) + { + bOk = S_StartBackgroundTrack_Actual( &tMusic_Info[i], qtrue, psMusicName, loop ); + } + + tMusic_Info[i].bExists = bOk; + + if (!tMusic_Info[i].bExists) + { + FreeMusic( &tMusic_Info[i] ); + } + } + + // + // default all tracks to OFF first (and set any other vars) + // + for (i=0; ibActive = qtrue; + pMusicInfo->iXFadeVolumeSeekTime= Sys_Milliseconds(); + pMusicInfo->iXFadeVolumeSeekTo = 255; + pMusicInfo->iXFadeVolume = 0; + } + else + { + Com_Printf( S_COLOR_RED "Dynamic music did not have both 'action' and 'explore' versions, inhibiting...\n"); + S_StopBackgroundTrack(); + } + } + else + { + if (sName[0]!='.') // blank name with ".wxb" or whatever attached - no error print out + { + Com_Printf( S_COLOR_RED "Unable to find music \"%s\" as explicit track or dynamic music entry!\n",sName); + S_StopBackgroundTrack(); + } + } + } + + if (bCalledByCGameStart) + { + S_StopBackgroundTrack(); + } +} + +void S_StopBackgroundTrack( void ) +{ + for (int i=0; ivalue; + + if (bMusic_IsDynamic) + { + // step xfade volume... + // + if ( pMusicInfo->iXFadeVolume != pMusicInfo->iXFadeVolumeSeekTo ) + { + int iFadeMillisecondsElapsed = Sys_Milliseconds() - pMusicInfo->iXFadeVolumeSeekTime; + + if (iFadeMillisecondsElapsed > (fDYNAMIC_XFADE_SECONDS * 1000)) + { + pMusicInfo->iXFadeVolume = pMusicInfo->iXFadeVolumeSeekTo; + } + else + { + pMusicInfo->iXFadeVolume = (int) (255.0f * ((float)iFadeMillisecondsElapsed/(fDYNAMIC_XFADE_SECONDS * 1000.0f))); + if (pMusicInfo->iXFadeVolumeSeekTo == 0) // bleurgh + pMusicInfo->iXFadeVolume = 255 - pMusicInfo->iXFadeVolume; + } + } + fMasterVol *= (float)((float)pMusicInfo->iXFadeVolume / 255.0f); + } + + if ( pMusicInfo->bLoaded == false ) { + return qfalse; + } + + pMusicInfo->fSmoothedOutVolume = (pMusicInfo->fSmoothedOutVolume + fMasterVol)/2.0f; + + alStreamf(AL_GAIN, pMusicInfo->fSmoothedOutVolume); + + // don't bother playing anything if musicvolume is 0 + if ( pMusicInfo->fSmoothedOutVolume <= 0 ) { + return qfalse; + } + + // start playing if necessary + if ( pMusicInfo->bLooping ) + { + ALint state; + alGetStreami(AL_SOURCE_STATE, &state); + if ( state != AL_PLAYING ) + { + alStreamPlay(pMusicInfo->iFileSeekTo, + pMusicInfo->iFileCode, + pMusicInfo->bLooping); + } + } + + if ( pMusicInfo->PlayTime() >= pMusicInfo->TotalTime() ) + { + // loop the music, or play the next piece if we were on the intro... + // (but not for dynamic, that can only be used for loop music) + // + if (bMusic_IsDynamic) // needs special logic for this, different call + { + pMusicInfo->Rewind(); + } + else + { + // for non-dynamic music we need to check if "sMusic_BackgroundLoop" is an actual filename, + // or if it's a dynamic music specifier (which can't literally exist), in which case it should set + // a return flag then exit... + // + char sTestName[MAX_QPATH*2];// *2 so COM_DefaultExtension doesn't do an ERR_DROP if there was no space + // for an extension, since this is a "soft" test + Q_strncpyz( sTestName, sMusic_BackgroundLoop, sizeof(sTestName)); + COM_DefaultExtension(sTestName, sizeof(sTestName), ".mp3"); + + if (S_MusicFileExists( sTestName )) + { + // Restart the music + alStreamStop(); + alStreamPlay(pMusicInfo->iFileSeekTo, + pMusicInfo->iFileCode, + pMusicInfo->bLooping); + } + else + { + // proposed file doesn't exist, but this may be a dynamic track we're wanting to loop, + // so exit with a special flag... + // + return qtrue; + } + } + } + + return qfalse; +} + + +// used to be just for dynamic, but now even non-dynamic music has to know whether it should be silent or not... +// +static LPCSTR S_Music_GetRequestedState(void) +{ +// This doesn't do anything in MP - just return NULL +#ifndef _JK2MP + int iStringOffset = cl.gameState.stringOffsets[CS_DYNAMIC_MUSIC_STATE]; + if (iStringOffset) + { + LPCSTR psCommand = cl.gameState.stringData+iStringOffset; + + return psCommand; + } +#endif + + return NULL; +} + + +// scan the configstring to see if there's been a state-change requested... +// (note that even if the state doesn't change it still gets here, so do a same-state check for applying) +// +// then go on to do transition handling etc... +// +static void S_CheckDynamicMusicState(void) +{ + LPCSTR psCommand = S_Music_GetRequestedState(); + + if (psCommand) + { + MusicState_e eNewState; + + if ( !Q_stricmpn( psCommand, "silence", 7) ) + { + eNewState = eBGRNDTRACK_SILENCE; + } + else if ( !Q_stricmpn( psCommand, "action", 6) ) + { + eNewState = eBGRNDTRACK_ACTION; + } + else if ( !Q_stricmpn( psCommand, "boss", 4) ) + { + // special case, boss music is optional and may not be defined... + // + if (tMusic_Info[ eBGRNDTRACK_BOSS ].bExists) + { + eNewState = eBGRNDTRACK_BOSS; + } + else + { + // ( leave it playing current track ) + // + eNewState = eMusic_StateActual; + } + } + else if ( !Q_stricmpn( psCommand, "death", 5) ) + { + // special case, death music is optional and may not be defined... + // + if (tMusic_Info[ eBGRNDTRACK_DEATH ].bExists) + { + eNewState = eBGRNDTRACK_DEATH; + } + else + { + // ( leave it playing current track, typically either boss or action ) + // + eNewState = eMusic_StateActual; + } + } + else + { + // seems a reasonable default... + // + eNewState = eBGRNDTRACK_EXPLORE; + } + + S_SetDynamicMusicState( eNewState ); + } + + S_HandleDynamicMusicStateChange(); +} + +static void S_UpdateBackgroundTrack( void ) +{ + if (bMusic_IsDynamic) + { + S_CheckDynamicMusicState(); + + if (eMusic_StateActual != eBGRNDTRACK_SILENCE) + { + MusicInfo_t *pMusicInfoCurrent = &tMusic_Info[ (eMusic_StateActual == eBGRNDTRACK_FADE)?eBGRNDTRACK_EXPLORE:eMusic_StateActual ]; + MusicInfo_t *pMusicInfoFadeOut = &tMusic_Info[ eBGRNDTRACK_FADE ]; + + if ( pMusicInfoCurrent->bLoaded ) + { + float fRemainingTimeInSeconds = 1000000; + + if (pMusicInfoFadeOut->bActive) + { + S_UpdateBackgroundTrack_Actual( pMusicInfoFadeOut, qfalse, s_music_volume->value ); // inactive-checked internally + + // + // only do this for the fader!... + // + if (pMusicInfoFadeOut->iXFadeVolume == 0) + { + pMusicInfoFadeOut->bActive = qfalse; + + // play if we have a file + if (pMusicInfoCurrent->iFileCode) + { + alStreamPlay(pMusicInfoCurrent->iFileSeekTo, + pMusicInfoCurrent->iFileCode, + pMusicInfoCurrent->bLooping); + + pMusicInfoCurrent->iXFadeVolumeSeekTime = Sys_Milliseconds(); + } + else + { + alStreamStop(); + } + } + } + else + { + S_UpdateBackgroundTrack_Actual( pMusicInfoCurrent, qtrue, s_music_volume->value ); + fRemainingTimeInSeconds = pMusicInfoCurrent->TotalTime() - pMusicInfoCurrent->ElapsedTime(); + } + + if ( fRemainingTimeInSeconds < fDYNAMIC_XFADE_SECONDS*2 ) + { + // now either loop current track, switch if finishing a transition, or stop if finished a death... + // + if (pMusicInfoCurrent->bTrackSwitchPending) + { + pMusicInfoCurrent->bTrackSwitchPending = qfalse; // ack + tMusic_Info[ pMusicInfoCurrent->eTS_NewState ].SeekTo(pMusicInfoCurrent->fTS_NewTime); + S_SwitchDynamicTracks( eMusic_StateActual, pMusicInfoCurrent->eTS_NewState, qfalse); // qboolean bNewTrackStartsFullVolume + } + else + { + // normal looping, so set rewind current track, set volume to 0 and fade up to full (unless death track playing, then stays quiet) + // (while fader copy of end-section fades down) + // + // copy current track to fader... + // + *pMusicInfoFadeOut = *pMusicInfoCurrent; // struct copy + pMusicInfoFadeOut->iXFadeVolumeSeekTime = Sys_Milliseconds(); + pMusicInfoFadeOut->iXFadeVolumeSeekTo = 0; + // + pMusicInfoCurrent->Rewind(); + pMusicInfoCurrent->iXFadeVolumeSeekTime = Sys_Milliseconds(); + pMusicInfoCurrent->iXFadeVolumeSeekTo = (eMusic_StateActual == eBGRNDTRACK_DEATH) ? 0: 255; + pMusicInfoCurrent->iXFadeVolume = 0; + } + } + } + } + else + { + // special case, when foreground music is shut off but fader still running to fade off previous track... + // + MusicInfo_t *pMusicInfoFadeOut = &tMusic_Info[ eBGRNDTRACK_FADE ]; + if (pMusicInfoFadeOut->bActive) + { + S_UpdateBackgroundTrack_Actual( pMusicInfoFadeOut, qtrue, s_music_volume->value ); + if (pMusicInfoFadeOut->iXFadeVolume == 0) + { + pMusicInfoFadeOut->bActive = qfalse; + alStreamStop(); + } + } + } + } + else + { + // standard / non-dynamic one-track music... + // + LPCSTR psCommand = S_Music_GetRequestedState(); // special check just for "silence" case... + qboolean bShouldBeSilent = (psCommand && !Q_stricmp(psCommand,"silence")); + float fDesiredVolume = bShouldBeSilent ? 0.0f : s_music_volume->value; + // + // internal to this code is a volume-smoother... + // + qboolean bNewTrackDesired = S_UpdateBackgroundTrack_Actual(&tMusic_Info[eBGRNDTRACK_NONDYNAMIC], qtrue, fDesiredVolume); + + if (bNewTrackDesired) + { + S_StartBackgroundTrack( sMusic_BackgroundLoop, sMusic_BackgroundLoop, qfalse ); + } + } +} + + + + +int SND_GetMemoryUsed(void) +{ + ALint used; + alGeti(AL_MEMORY_USED, &used); + return used; +} + +void SND_update(sfx_t *sfx) +{ + while ( SND_GetMemoryUsed() > (2 * 1024 * 1024)) // s_soundpoolmegs + { + int iBytesFreed = SND_FreeOldestSound(sfx); + if (iBytesFreed == 0) + break; // sanity + } +} + + +// free any allocated sfx mem... +// +// now returns # bytes freed to help with z_malloc()-fail recovery +// +static int SND_FreeSFXMem(sfx_t *sfx) +{ + int iOrgMem = SND_GetMemoryUsed(); + + alGetError(); + if (sfx->Buffer) + { + alDeleteBuffers(1, &(sfx->Buffer)); + sfx->Buffer = 0; + } + + sfx->iFlags &= ~(SFX_FLAG_RESIDENT | SFX_FLAG_LOADING); + sfx->iFlags |= SFX_FLAG_UNLOADED; + + return iOrgMem - SND_GetMemoryUsed(); +} + +void S_DisplayFreeMemory() +{ +} + +void SND_TouchSFX(sfx_t *sfx) +{ + sfx->iLastTimeUsed = Com_Milliseconds()+1; +} + + +// currently this is only called during snd_shutdown or snd_restart +// +static void S_FreeAllSFXMem(void) +{ + for (int i = 0; i < MAX_SFX; ++i) + { + if (s_sfxCodes[i] != INVALID_CODE && i != s_defaultSound) + { + SND_FreeSFXMem(&s_sfxBlock[i]); + } + } +} + +// returns number of bytes freed up... +// +// new param is so we can be usre of not freeing ourselves (without having to rely on possible uninitialised timers etc) +// Super new version just throws out ALL sound. Bwa ha ha! +int SND_FreeOldestSound(sfx_t *pButNotThisOne /* = NULL */) +{ + int iBytesFreed = 0; + sfx_t *sfx; + + // start on 1 so we never dump the default sound... + // + for (int i = 0; i < MAX_SFX; ++i) + { + if (s_sfxCodes[i] == INVALID_CODE || i == s_defaultSound) continue; + + sfx = &s_sfxBlock[i]; + + if (sfx != pButNotThisOne) + { + // Don't throw out the default sound, or sounds that are not in memory + // + if (!(sfx->iFlags & SFX_FLAG_DEFAULT) && + (sfx->iFlags & SFX_FLAG_RESIDENT)) + { + // new bit, we can't throw away any sfx_t struct in use by a channel, + // else the paint code will crash... + // + int iChannel; + for (iChannel=0; iChannelthesfx == sfx) + break; // damn, being used + } + if (iChannel == s_numChannels) + { + // this sfx_t struct wasn't used by any channels, so we can lose it... + // + iBytesFreed += SND_FreeSFXMem( &s_sfxBlock[i] ); + } + } + } + } + + return iBytesFreed; +} +int SND_FreeOldestSound(void) +{ + return SND_FreeOldestSound(NULL); // I had to add a void-arg version of this because of link issues, sigh +} + + +// just before we drop into a level, ensure the audio pool is under whatever the maximum +// pool size is (but not by dropping out sounds used by the current level)... +// +// returns qtrue if at least one sound was dropped out, so z_malloc-fail recovery code knows if anything changed +// +qboolean SND_RegisterAudio_Clean(void) +{ + if ( !s_soundStarted ) { + return qfalse; + } + + qboolean bAtLeastOneSoundDropped = qfalse; + + Com_DPrintf( "SND_RegisterAudio_Clean():\n"); + + extern void S_DrainRawSoundData(void); + S_DrainRawSoundData(); + + { + for (int i = 0; i < MAX_SFX; ++i) + { + if (s_sfxCodes[i] == INVALID_CODE || i == s_defaultSound) continue; + + sfx_t *sfx = &s_sfxBlock[i]; + + if (sfx->iFlags & (SFX_FLAG_RESIDENT | SFX_FLAG_DEMAND)) + { + qboolean bDeleteThis = qtrue; + //if (bDeleteThis) + { + int iChannel; + for (iChannel=0; iChanneliFlags & SFX_FLAG_DEFAULT) && + (sfx->iFlags & SFX_FLAG_RESIDENT) && + SND_FreeSFXMem(sfx)) + { + bAtLeastOneSoundDropped = qtrue; + } + if (sfx->iFlags & SFX_FLAG_DEMAND) + { + s_sfxCodes[i] = INVALID_CODE; + } + } + } + } + } + } + + Com_DPrintf( "SND_RegisterAudio_Clean(): Ok\n"); + + return bAtLeastOneSoundDropped; +} + +qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel /* 99% qfalse */) +{ + return qfalse; +} + +qboolean S_FileExists( const char *psFilename ) +{ + // This is only really used for music. Need to swap .mp3 with .wxb on Xbox + char *fixedName = S_FixMusicFileName(psFilename); + + // VVFIXME : This can be done better? + fileHandle_t fhTemp; + + FS_FOpenFileRead (fixedName, &fhTemp, qtrue); // qtrue so I can fclose the handle without closing a PAK + if (!fhTemp) + return qfalse; + + FS_FCloseFile(fhTemp); + return qtrue; +} + diff --git a/codemp/client/snd_local.h b/codemp/client/snd_local.h new file mode 100644 index 0000000..394469c --- /dev/null +++ b/codemp/client/snd_local.h @@ -0,0 +1,228 @@ +// snd_local.h -- private sound definations + +#ifndef SND_LOCAL_H +#define SND_LOCAL_H + +#define sboolean int //rww - argh (in SP qboolean type is merely #define'd as an int, but I do not want to do that for MP over the whole base) + +#include "snd_public.h" +#include "../mp3code/mp3struct.h" + +// Open AL Specific +#include "openal\al.h" +#include "openal\alc.h" +#include "eax\eax.h" +#include "eax\eaxman.h" + +// Added for Open AL to know when to mute all sounds (e.g when app. loses focus) +void S_AL_MuteAllSounds(sboolean bMute); + + +//from SND_AMBIENT +extern void AS_Init( void ); +extern void AS_Free( void ); + + +#define PAINTBUFFER_SIZE 1024 + + +// !!! if this is changed, the asm code must change !!! +typedef struct { + int left; // the final values will be clamped to +/- 0x00ffff00 and shifted down + int right; +} portable_samplepair_t; + + +// keep this enum in sync with the table "sSoundCompressionMethodStrings" -ste +// +typedef enum +{ + ct_16 = 0, // formerly ct_NONE in EF1, now indicates 16-bit samples (the default) + ct_MP3, + // + ct_NUMBEROF // used only for array sizing + +} SoundCompressionMethod_t; + + +typedef struct sfx_s { + short *pSoundData; + sboolean bDefaultSound; // couldn't be loaded, so use buzz + sboolean bInMemory; // not in Memory, set qtrue when loaded, and qfalse when its buffers are freed up because of being old, so can be reloaded + SoundCompressionMethod_t eSoundCompressionMethod; + MP3STREAM *pMP3StreamHeader; // NULL ptr unless this sfx_t is an MP3. Use Z_Malloc and Z_Free + int iSoundLengthInSamples; // length in samples, always kept as 16bit now so this is #shorts (watch for stereo later for music?) + char sSoundName[MAX_QPATH]; + int iLastTimeUsed; + float fVolRange; // used to set the highest volume this sample has at load time - used for lipsynching + int iLastLevelUsedOn; // used for cacheing purposes + + // Open AL + ALuint Buffer; + char *lipSyncData; + + struct sfx_s *next; // only used because of hash table when registering +} sfx_t; + +typedef struct { + int channels; + int samples; // mono samples in buffer + int submission_chunk; // don't mix less than this # + int samplebits; + int speed; + byte *buffer; +} dma_t; + + +#define START_SAMPLE_IMMEDIATE 0x7fffffff + +// Open AL specific +typedef struct +{ + ALuint BufferID; + ALuint Status; + char *Data; +} STREAMINGBUFFER; + +#define NUM_STREAMING_BUFFERS 4 +#define STREAMING_BUFFER_SIZE 4608 // 4 decoded MP3 frames + +#define QUEUED 1 +#define UNQUEUED 2 + + +typedef struct +{ +// back-indented fields new in TA codebase, will re-format when MP3 code finished -ste +// note: field missing in TA: sboolean loopSound; // from an S_AddLoopSound call, cleared each frame +// + unsigned int startSample; // START_SAMPLE_IMMEDIATE = set immediately on next mix + int entnum; // to allow overriding a specific sound + int entchannel; // to allow overriding a specific sound + int leftvol; // 0-255 volume after spatialization + int rightvol; // 0-255 volume after spatialization + int master_vol; // 0-255 volume before spatialization + + + vec3_t origin; // only use if fixed_origin is set + + sboolean fixed_origin; // use origin instead of fetching entnum's origin + sfx_t *thesfx; // sfx structure + sboolean loopSound; // from an S_AddLoopSound call, cleared each frame + // + MP3STREAM MP3StreamHeader; + byte MP3SlidingDecodeBuffer[50000/*12000*/]; // typical back-request = -3072, so roughly double is 6000 (safety), then doubled again so the 6K pos is in the middle of the buffer) + int iMP3SlidingDecodeWritePos; + int iMP3SlidingDecodeWindowPos; + + + // Open AL specific + bool bLooping; // Signifies if this channel / source is playing a looping sound +// bool bAmbient; // Signifies if this channel / source is playing a looping ambient sound + bool bProcessed; // Signifies if this channel / source has been processed + bool bStreaming; // Set to true if the data needs to be streamed (MP3 or dialogue) + STREAMINGBUFFER buffers[NUM_STREAMING_BUFFERS]; // AL Buffers for streaming + ALuint alSource; // Open AL Source + bool bPlaying; // Set to true when a sound is playing on this channel / source + int iStartTime; // Time playback of Source begins + int lSlotID; // ID of Slot rendering Source's environment (enables a send to this FXSlot) +} channel_t; + + +#define WAV_FORMAT_PCM 1 +#define WAV_FORMAT_ADPCM 2 // not actually implemented, but is the value that you get in a header +#define WAV_FORMAT_MP3 3 // not actually used this way, but just ensures we don't match one of the legit formats + + +typedef struct { + int format; + int rate; + int width; + int channels; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + + + +/* +==================================================================== + + SYSTEM SPECIFIC FUNCTIONS + +==================================================================== +*/ + +// initializes cycling through a DMA buffer and returns information on it +qboolean SNDDMA_Init(void); + +// gets the current DMA position +int SNDDMA_GetDMAPos(void); + +// shutdown the DMA xfer. +void SNDDMA_Shutdown(void); + +void SNDDMA_BeginPainting (void); + +void SNDDMA_Submit(void); + +//==================================================================== + +#define MAX_CHANNELS 32 +extern channel_t s_channels[MAX_CHANNELS]; + +extern int s_paintedtime; +extern int s_rawend; +extern vec3_t listener_origin; +extern vec3_t listener_forward; +extern vec3_t listener_right; +extern vec3_t listener_up; +extern dma_t dma; + +#define MAX_RAW_SAMPLES 16384 +extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; +portable_samplepair_t *S_GetRawSamplePointer(); // TA added this, but it just returns the s_rawsamples[] array above. Oh well... + +extern cvar_t *s_volume; +extern cvar_t *s_volumeVoice; +extern cvar_t *s_nosound; +extern cvar_t *s_khz; +extern cvar_t *s_allowDynamicMusic; +extern cvar_t *s_show; +extern cvar_t *s_mixahead; + +extern cvar_t *s_testsound; +extern cvar_t *s_separation; + +wavinfo_t GetWavinfo (const char *name, byte *wav, int wavlength); + +sboolean S_LoadSound( sfx_t *sfx ); + + +void S_PaintChannels(int endtime); + +// picks a channel based on priorities, empty slots, number of channels +channel_t *S_PickChannel(int entnum, int entchannel); + +// spatializes a channel +void S_Spatialize(channel_t *ch); + + +////////////////////////////////// +// +// new stuff from TA codebase + +byte *SND_malloc(int iSize, sfx_t *sfx); +void SND_setup(); +int SND_FreeOldestSound(sfx_t *pButNotThisOne = NULL); +void SND_TouchSFX(sfx_t *sfx); + +void S_DisplayFreeMemory(void); +void S_memoryLoad(sfx_t *sfx); +// +////////////////////////////////// + +#include "snd_mp3.h" + +#endif // #ifndef SND_LOCAL_H + diff --git a/codemp/client/snd_local_console.h b/codemp/client/snd_local_console.h new file mode 100644 index 0000000..541dfb5 --- /dev/null +++ b/codemp/client/snd_local_console.h @@ -0,0 +1,134 @@ +// snd_local.h -- private sound definations + +#ifndef SND_LOCAL_H +#define SND_LOCAL_H + +// Following #define is ONLY for MP JKA code. +// They want to keep qboolean pure enum in that code, so all +// sound code uses sboolean. +#define sboolean int + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "snd_public.h" +#include "../mp3code/mp3struct.h" + +#include "openal/al.h" +#include "openal/alc.h" + +typedef int streamHandle_t; + +//from SND_AMBIENT +extern void AS_Init( void ); +extern void AS_Free( void ); + +//Changing this? It needs to be synced with the create_soundbank util. +enum SoundFilenameFlags +{ + SFF_WEAPONS_ATST, + SFF_SAND_CREATURE, + SFF_HOWLER, + SFF_ALTCHARGE, + SFF_FALLING, + SFF_TIEEXPLODE, + //Can't have more than 8. +}; +//Changing this? It needs to be synced with the create_soundbank util. + +typedef struct { + int format; + int size; + int width; + int rate; +} wavinfo_t; + +extern wavinfo_t GetWavInfo(byte *data); + + +#define SFX_FLAG_UNLOADED (1 << 0) +#define SFX_FLAG_LOADING (1 << 1) +#define SFX_FLAG_RESIDENT (1 << 2) +#define SFX_FLAG_DEFAULT (1 << 3) +#define SFX_FLAG_DEMAND (1 << 4) +#define SFX_FLAG_VOICE (1 << 5) + +typedef struct sfx_s { + int iFlags; + int iSoundLength; // length in bytes + int iLastTimeUsed; // last time sound was played in ms + unsigned int iFileCode; // CRC of the file name + streamHandle_t iStreamHandle; // handle to the sound file when reading + void* pSoundData; // buffer to hold sound as we are loading it + ALuint Buffer; +} sfx_t; + +typedef struct +{ + int entnum; // to allow overriding a specific sound + int entchannel; // to allow overriding a specific sound + int master_vol; // 0-255 volume before spatialization + float fLastVolume; // 0-1 last volume sent to AL + + vec3_t origin; // sound location + bool bOriginDirty; // does the AL position need to be updated + + sfx_t *thesfx; // sfx structure + + int loopChannel; // index into loopSounds (if appropriate) + + bool bPlaying; // Set to true when a sound is playing on this channel / source + bool b2D; // Signifies a 2d sound + bool bLooping; // Signifies if this channel / source is playing a looping sound + ALuint alSource; // Open AL Source + + unsigned int iLastPlayTime; // Last time a sound was played on this channel +} channel_t; + +extern cvar_t *s_nosound; +extern cvar_t *s_allowDynamicMusic; +extern cvar_t *s_show; + +extern cvar_t *s_testsound; +extern cvar_t *s_separation; + +extern int s_entityWavVol[MAX_GENTITIES]; + +int Sys_GetFileCode( const char* sSoundName ); +int S_GetFileCode( const char* sSoundName ); + +qboolean S_StartLoadSound( sfx_t *sfx ); +qboolean S_EndLoadSound( sfx_t *sfx ); + +void S_InitLoad(void); +void S_CloseLoad(void); +void S_UpdateLoading(void); + +// New stuff from VV +void S_LoadSound( sfxHandle_t sfxHandle ); + +// picks a channel based on priorities, empty slots, number of channels +channel_t *S_PickChannel(int entnum, int entchannel); + +////////////////////////////////// +// +// new stuff from TA codebase + +void SND_update(sfx_t *sfx); +void SND_setup(); +int SND_FreeOldestSound(sfx_t *pButNotThisOne = NULL); +void SND_TouchSFX(sfx_t *sfx); + +void S_DisplayFreeMemory(void); +void S_memoryLoad(sfx_t *sfx); + +bool Sys_StreamIsReading(streamHandle_t handle); +int Sys_StreamOpen(int code, streamHandle_t *handle); +bool Sys_StreamRead(void* buffer, int size, int pos, streamHandle_t handle); +void Sys_StreamClose(streamHandle_t handle); +bool Sys_StreamIsError(streamHandle_t handle); + +// +////////////////////////////////// + +#endif // #ifndef SND_LOCAL_H + diff --git a/codemp/client/snd_mem.cpp b/codemp/client/snd_mem.cpp new file mode 100644 index 0000000..2736d5c --- /dev/null +++ b/codemp/client/snd_mem.cpp @@ -0,0 +1,1015 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// snd_mem.c: sound caching + +#include "snd_local.h" +#include "snd_mp3.h" +#include "snd_ambient.h" + +// Open AL +void S_PreProcessLipSync(sfx_t *sfx); +extern int s_UseOpenAL; +/* +=============================================================================== + +WAV loading + +=============================================================================== +*/ + +byte *data_p; +byte *iff_end; +byte *last_chunk; +byte *iff_data; +int iff_chunk_len; +extern sfx_t s_knownSfx[]; +extern int s_numSfx; + +extern cvar_t *s_lip_threshold_1; +extern cvar_t *s_lip_threshold_2; +extern cvar_t *s_lip_threshold_3; +extern cvar_t *s_lip_threshold_4; + +short GetLittleShort(void) +{ + short val = 0; + val = *data_p; + val = (short)(val + (*(data_p+1)<<8)); + data_p += 2; + return val; +} + +int GetLittleLong(void) +{ + int val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + val = val + (*(data_p+2)<<16); + val = val + (*(data_p+3)<<24); + data_p += 4; + return val; +} + +void FindNextChunk(char *name) +{ + while (1) + { + data_p=last_chunk; + + if (data_p >= iff_end) + { // didn't find the chunk + data_p = NULL; + return; + } + + data_p += 4; + iff_chunk_len = GetLittleLong(); + if (iff_chunk_len < 0) + { + data_p = NULL; + return; + } + data_p -= 8; + last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); + if (!strncmp((char *)data_p, name, 4)) + return; + } +} + +void FindChunk(char *name) +{ + last_chunk = iff_data; + FindNextChunk (name); +} + + +void DumpChunks(void) +{ + char str[5]; + + str[4] = 0; + data_p=iff_data; + do + { + memcpy (str, data_p, 4); + data_p += 4; + iff_chunk_len = GetLittleLong(); + Com_Printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len); + data_p += (iff_chunk_len + 1) & ~1; + } while (data_p < iff_end); +} + +/* +============ +GetWavinfo +============ +*/ +wavinfo_t GetWavinfo (const char *name, byte *wav, int wavlength) +{ + wavinfo_t info; + int samples; + + memset (&info, 0, sizeof(info)); + + if (!wav) + return info; + + iff_data = wav; + iff_end = wav + wavlength; + +// find "RIFF" chunk + FindChunk("RIFF"); + if (!(data_p && !strncmp((char *)data_p+8, "WAVE", 4))) + { + Com_Printf("Missing RIFF/WAVE chunks\n"); + return info; + } + +// get "fmt " chunk + iff_data = data_p + 12; +// DumpChunks (); + + FindChunk("fmt "); + if (!data_p) + { + Com_Printf("Missing fmt chunk\n"); + return info; + } + data_p += 8; + info.format = GetLittleShort(); + info.channels = GetLittleShort(); + info.rate = GetLittleLong(); + data_p += 4+2; + info.width = GetLittleShort() / 8; + + if (info.format != 1) + { + Com_Printf("Microsoft PCM format only\n"); + return info; + } + + +// find data chunk + FindChunk("data"); + if (!data_p) + { + Com_Printf("Missing data chunk\n"); + return info; + } + + data_p += 4; + samples = GetLittleLong () / info.width; + + if (info.samples) + { + if (samples < info.samples) + Com_Error (ERR_DROP, "Sound %s has a bad loop length", name); + } + else + info.samples = samples; + + info.dataofs = data_p - wav; + + + return info; +} + + +/* +================ +ResampleSfx + +resample / decimate to the current source rate +================ +*/ +void ResampleSfx (sfx_t *sfx, int iInRate, int iInWidth, byte *pData) +{ + int iOutCount; + int iSrcSample; + float fStepScale; + int i; + int iSample; + unsigned int uiSampleFrac, uiFracStep; // uiSampleFrac MUST be unsigned, or large samples (eg music tracks) crash + + fStepScale = (float)iInRate / dma.speed; // this is usually 0.5, 1, or 2 + + // When stepscale is > 1 (we're downsampling), we really ought to run a low pass filter on the samples + + iOutCount = (int)(sfx->iSoundLengthInSamples / fStepScale); + sfx->iSoundLengthInSamples = iOutCount; + + sfx->pSoundData = (short *) SND_malloc( sfx->iSoundLengthInSamples*2 ,sfx ); + + sfx->fVolRange = 0; + uiSampleFrac = 0; + uiFracStep = (int)(fStepScale*256); + + for (i=0 ; iiSoundLengthInSamples ; i++) + { + iSrcSample = uiSampleFrac >> 8; + uiSampleFrac += uiFracStep; + if (iInWidth == 2) { + iSample = LittleShort ( ((short *)pData)[iSrcSample] ); + } else { + iSample = (int)( (unsigned char)(pData[iSrcSample]) - 128) << 8; + } + + sfx->pSoundData[i] = (short)iSample; + + // work out max vol for this sample... + // + if (iSample < 0) + iSample = -iSample; + if (sfx->fVolRange < (iSample >> 8) ) + { + sfx->fVolRange = iSample >> 8; + } + } +} + + +//============================================================================= + + +void S_LoadSound_Finalize(wavinfo_t *info, sfx_t *sfx, byte *data) +{ + float stepscale = (float)info->rate / dma.speed; + int len = (int)(info->samples / stepscale); + + len *= info->width; + + sfx->eSoundCompressionMethod = ct_16; + sfx->iSoundLengthInSamples = info->samples; + ResampleSfx( sfx, info->rate, info->width, data + info->dataofs ); +} + + + + + +// maybe I'm re-inventing the wheel, here, but I can't see any functions that already do this, so... +// +char *Filename_WithoutPath(const char *psFilename) +{ + static char sString[MAX_QPATH]; // !! + const char *p = strrchr(psFilename,'\\'); + + if (!p++) + p=psFilename; + + strcpy(sString,p); + + return sString; + +} + +// returns (eg) "\dir\name" for "\dir\name.bmp" +// +char *Filename_WithoutExt(const char *psFilename) +{ + static char sString[MAX_QPATH]; // ! + + strcpy(sString,psFilename); + + char *p = strrchr(sString,'.'); + char *p2= strrchr(sString,'\\'); + + // special check, make sure the first suffix we found from the end wasn't just a directory suffix (eg on a path'd filename with no extension anyway) + // + if (p && (p2==0 || (p2 && p>p2))) + *p=0; + + return sString; + +} + + + +int iFilesFound; +int iFilesUpdated; +int iErrors; +sboolean qbForceRescan; +sboolean qbForceStereo; +string strErrors; + +void R_CheckMP3s( const char *psDir ) +{ +// Com_Printf(va("Scanning Dir: %s\n",psDir)); + Com_Printf("."); // stops useful info scrolling off screen + + char **sysFiles, **dirFiles; + int numSysFiles, i, numdirs; + + dirFiles = FS_ListFiles( psDir, "/", &numdirs); + if (numdirs > 2) + { + for (i=2;ifVolRange; + + // free sfx->data... + // + { + #ifndef INT_MIN + #define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ + #endif + // + pSFX->iLastTimeUsed = INT_MIN; // force this to be oldest sound file, therefore disposable... + pSFX->bInMemory = qtrue; + SND_FreeOldestSound(); // ... and do the disposal + + // now set our temp SFX struct back to default name so nothing else accidentally uses it... + // + strcpy(pSFX->sSoundName, sReservedSFXEntrynameForMP3); + pSFX->bDefaultSound = qfalse; + } + +// OutputDebugString(va("File: \"%s\" MaxVol %f\n",sFilename,pSFX->fVolRange)); + + // other stuff... + // + Z_Free(pbUnpackBuffer); + } + + // well, time to update the file now... + // + fileHandle_t f = FS_FOpenFileWrite( sFilename ); + if (f) + { + // write the file back out, but omitting the tag if there was one... + // + int iWritten = FS_Write(pbData, iSize-(pTAG?sizeof(*pTAG):0), f); + + if (iWritten) + { + // make up a new tag if we didn't find one in the original file... + // + id3v1_1 TAG; + if (!pTAG) + { + pTAG = &TAG; + memset(&TAG,0,sizeof(TAG)); + strncpy(pTAG->id,"TAG",3); + } + + strncpy(pTAG->title, Filename_WithoutPath(Filename_WithoutExt(sFilename)), sizeof(pTAG->title)); + strncpy(pTAG->artist, "Raven Software", sizeof(pTAG->artist) ); + strncpy(pTAG->year, "2002", sizeof(pTAG->year) ); + strncpy(pTAG->comment, va("%s %g",sKEY_MAXVOL,fMaxVol), sizeof(pTAG->comment) ); + strncpy(pTAG->album, va("%s %d",sKEY_UNCOMP,iActualUnpackedSize),sizeof(pTAG->album) ); + + if (FS_Write( pTAG, sizeof(*pTAG), f )) // NZ = success + { + iFilesUpdated++; + } + else + { + Com_Printf("*********** Failed write to file \"%s\"!\n",sFilename); + iErrors++; + strErrors += va("Failed to write: \"%s\"\n",sFilename); + } + } + else + { + Com_Printf("*********** Failed write to file \"%s\"!\n",sFilename); + iErrors++; + strErrors += va("Failed to write: \"%s\"\n",sFilename); + } + FS_FCloseFile( f ); + } + else + { + Com_Printf("*********** Failed to re-open for write \"%s\"!\n",sFilename); + iErrors++; + strErrors += va("Failed to re-open for write: \"%s\"\n",sFilename); + } + } + else + { + Com_Error(ERR_DROP, "******* This MP3 should be deleted: \"%s\"\n",sFilename); + } + } + else + { + Com_Printf("*********** File was not a valid MP3!: \"%s\"\n",sFilename); + iErrors++; + strErrors += va("Not game-legal MP3 format: \"%s\"\n",sFilename); + } + } + else + { + Com_Printf(" ( OK )\n"); + } + + FS_FreeFile( pbData ); + } + } + FS_FreeFileList( sysFiles ); + FS_FreeFileList( dirFiles ); +} + +// this console-function is for development purposes, and makes sure that sound/*.mp3 /s have tags in them +// specifying stuff like their max volume (and uncompressed size) etc... +// +void S_MP3_CalcVols_f( void ) +{ + char sStartDir[MAX_QPATH] = {"sound"}; + const char sUsage[] = "Usage: mp3_calcvols [-rescan] \ne.g. mp3_calcvols sound/chars"; + + if (Cmd_Argc() == 1 || Cmd_Argc()>4) // 3 optional arguments + { + Com_Printf(sUsage); + return; + } + + S_StopAllSounds(); + + + qbForceRescan = qfalse; + qbForceStereo = qfalse; + iFilesFound = 0; + iFilesUpdated = 0; + iErrors = 0; + strErrors = ""; + + for (int i=1; iinteger) + { + fileHandle_t hFile; + //German + strncpy(psVoice,"chr_d",5); // same number of letters as "chars" + FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the wav + if (!hFile) + { + strcpy(&psFilename[iNameStrlen-3],"mp3"); //not there try mp3 + FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the mp3 + } + if (hFile) + { + FS_FCloseFile(hFile); + } + strcpy(&psFilename[iNameStrlen-3],"wav"); //put it back to wav + + //French + strncpy(psVoice,"chr_f",5); // same number of letters as "chars" + FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the wav + if (!hFile) + { + strcpy(&psFilename[iNameStrlen-3],"mp3"); //not there try mp3 + FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the mp3 + } + if (hFile) + { + FS_FCloseFile(hFile); + } + strcpy(&psFilename[iNameStrlen-3],"wav"); //put it back to wav + + //Spanish + strncpy(psVoice,"chr_e",5); // same number of letters as "chars" + FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the wav + if (!hFile) + { + strcpy(&psFilename[iNameStrlen-3],"mp3"); //not there try mp3 + FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the mp3 + } + if (hFile) + { + FS_FCloseFile(hFile); + } + strcpy(&psFilename[iNameStrlen-3],"wav"); //put it back to wav + + strncpy(psVoice,"chars",5); //put it back to chars + } + + // account for foreign voices... + // + extern cvar_t* s_language; + if (s_language && stricmp("DEUTSCH",s_language->string)==0) + { + strncpy(psVoice,"chr_d",5); // same number of letters as "chars" + } + else if (s_language && stricmp("FRANCAIS",s_language->string)==0) + { + strncpy(psVoice,"chr_f",5); // same number of letters as "chars" + } + else if (s_language && stricmp("ESPANOL",s_language->string)==0) + { + strncpy(psVoice,"chr_e",5); // same number of letters as "chars" + } + else + { + psVoice = NULL; // use this ptr as a flag as to whether or not we substituted with a foreign version + } + } + + *piSize = FS_ReadFile( psFilename, (void **)pData ); // try WAV + if ( !*pData ) { + psFilename[iNameStrlen-3] = 'm'; + psFilename[iNameStrlen-2] = 'p'; + psFilename[iNameStrlen-1] = '3'; + *piSize = FS_ReadFile( psFilename, (void **)pData ); // try MP3 + + if ( !*pData ) + { + //hmmm, not found, ok, maybe we were trying a foreign noise ("arghhhhh.mp3" that doesn't matter?) but it + // was missing? Can't tell really, since both types are now in sound/chars. Oh well, fall back to English for now... + + if (psVoice) // were we trying to load foreign? + { + // yep, so fallback to re-try the english... + // +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW "Foreign file missing: \"%s\"! (using English...)\n",psFilename); +#endif + + strncpy(psVoice,"chars",5); + + psFilename[iNameStrlen-3] = 'w'; + psFilename[iNameStrlen-2] = 'a'; + psFilename[iNameStrlen-1] = 'v'; + *piSize = FS_ReadFile( psFilename, (void **)pData ); // try English WAV + if ( !*pData ) + { + psFilename[iNameStrlen-3] = 'm'; + psFilename[iNameStrlen-2] = 'p'; + psFilename[iNameStrlen-1] = '3'; + *piSize = FS_ReadFile( psFilename, (void **)pData ); // try English MP3 + } + } + + if (!*pData) + { + return qfalse; // sod it, give up... + } + } + } + + return qtrue; +} + + +// returns qtrue if this dir is allowed to keep loaded MP3s, else qfalse if they should be WAV'd instead... +// +// note that this is passed the original, un-language'd name +// +// (I was going to remove this, but on kejim_post I hit an assert because someone had got an ambient sound when the +// perimter fence goes online that was an MP3, then it tried to get added as looping. Presumably it sounded ok or +// they'd have noticed, but we therefore need to stop other levels using those. "sound/ambience" I can check for, +// but doors etc could be anything. Sigh...) +// +static sboolean S_LoadSound_DirIsAllowedToKeepMP3s(const char *psFilename) +{ + static const char *psAllowedDirs[] = + { + "sound/chars/", +// "sound/chr_d/" // no need for this now, or any other language, since we'll always compare against english + }; + + int i; + for (i=0; i< (sizeof(psAllowedDirs) / sizeof(psAllowedDirs[0])); i++) + { + if (strnicmp(psFilename, psAllowedDirs[i], strlen(psAllowedDirs[i]))==0) + return qtrue; // found a dir that's allowed to keep MP3s + } + + return qfalse; +} + + + +/* +============== +S_LoadSound + +The filename may be different than sfx->name in the case +of a forced fallback of a player specific sound (or of a wav/mp3 substitution now -Ste) +============== +*/ +qboolean gbInsideLoadSound = qfalse; +static sboolean S_LoadSound_Actual( sfx_t *sfx ) +{ + byte *data; + short *samples; + wavinfo_t info; + int size; + char *psExt; + char sLoadName[MAX_QPATH]; + ALuint Buffer; + + int len = strlen(sfx->sSoundName); + if (len<5) + { + return qfalse; + } + + // player specific sounds are never directly loaded... + // + if ( sfx->sSoundName[0] == '*') { + return qfalse; + } + // make up a local filename to try wav/mp3 substitutes... + // + Q_strncpyz(sLoadName, sfx->sSoundName, sizeof(sLoadName)); + strlwr( sLoadName ); + // + // Ensure name has an extension (which it must have, but you never know), and get ptr to it... + // + psExt = &sLoadName[strlen(sLoadName)-4]; + if (*psExt != '.') + { + //Com_Printf( "WARNING: soundname '%s' does not have 3-letter extension\n",sLoadName); + COM_DefaultExtension(sLoadName,sizeof(sLoadName),".wav"); // so psExt below is always valid + psExt = &sLoadName[strlen(sLoadName)-4]; + len = strlen(sLoadName); + } + + if (!S_LoadSound_FileLoadAndNameAdjuster(sLoadName, &data, &size, len)) + { + return qfalse; + } + + SND_TouchSFX(sfx); + +//========= + if (strnicmp(psExt,".mp3",4)==0) + { + // load MP3 file instead... + // + if (MP3_IsValid(sLoadName,data, size, qfalse)) + { + int iRawPCMDataSize = MP3_GetUnpackedSize(sLoadName,data,size,qfalse,qfalse); + + if (S_LoadSound_DirIsAllowedToKeepMP3s(sfx->sSoundName) // NOT sLoadName, this uses original un-languaged name + && + MP3Stream_InitFromFile(sfx, data, size, sLoadName, iRawPCMDataSize + 2304 /* + 1 MP3 frame size, jic */,qfalse) + ) + { +// Com_DPrintf("(Keeping file \"%s\" as MP3)\n",sLoadName); + + if (s_UseOpenAL) + { + // Create space for lipsync data (4 lip sync values per streaming AL buffer) + if ((strstr(sfx->sSoundName, "chars")) || (strstr(sfx->sSoundName, "CHARS"))) + sfx->lipSyncData = (char *)Z_Malloc(16, TAG_SND_RAWDATA, qfalse); + else + sfx->lipSyncData = NULL; + } + } + else + { + // small file, not worth keeping as MP3 since it would increase in size (with MP3 header etc)... + // + Com_DPrintf("S_LoadSound: Unpacking MP3 file(%i) \"%s\" to wav(%i).\n",size,sLoadName,iRawPCMDataSize); + // + // unpack and convert into WAV... + // + { + byte *pbUnpackBuffer = (byte *) Z_Malloc( iRawPCMDataSize+10 +2304 /* */, TAG_TEMP_WORKSPACE, qfalse ); // won't return if fails + + { + int iResultBytes = MP3_UnpackRawPCM( sLoadName, data, size, pbUnpackBuffer, qfalse ); + + if (iResultBytes!= iRawPCMDataSize){ + Com_Printf(S_COLOR_YELLOW"**** MP3 %s final unpack size %d different to previous value %d\n",sLoadName,iResultBytes,iRawPCMDataSize); + //assert (iResultBytes == iRawPCMDataSize); + } + + + // fake up a WAV structure so I can use the other post-load sound code such as volume calc for lip-synching + // + // (this is a bit crap really, but it lets me drop through into existing code)... + // + MP3_FakeUpWAVInfo( sLoadName, data, size, iResultBytes, + // these params are all references... + info.format, info.rate, info.width, info.channels, info.samples, info.dataofs, + qfalse + ); + + S_LoadSound_Finalize(&info,sfx,pbUnpackBuffer); + + // Open AL + if (s_UseOpenAL) + { + if ((strstr(sfx->sSoundName, "chars")) || (strstr(sfx->sSoundName, "CHARS"))) + { + sfx->lipSyncData = (char *)Z_Malloc((sfx->iSoundLengthInSamples / 1000) + 1, TAG_SND_RAWDATA, qfalse); + S_PreProcessLipSync(sfx); + } + else + sfx->lipSyncData = NULL; + + // Clear Open AL Error state + alGetError(); + + // Generate AL Buffer + alGenBuffers(1, &Buffer); + if (alGetError() == AL_NO_ERROR) + { + // Copy audio data to AL Buffer + alBufferData(Buffer, AL_FORMAT_MONO16, sfx->pSoundData, sfx->iSoundLengthInSamples*2, 22050); + if (alGetError() == AL_NO_ERROR) + { + sfx->Buffer = Buffer; + Z_Free(sfx->pSoundData); + sfx->pSoundData = NULL; + } + } + } + + Z_Free(pbUnpackBuffer); + } + } + } + } + else + { + // MP3_IsValid() will already have printed any errors via Com_Printf at this point... + // + FS_FreeFile (data); + return qfalse; + } + } + else + { + // loading a WAV, presumably... + +//========= + + info = GetWavinfo( sLoadName, data, size ); + if ( info.channels != 1 ) { + Com_Printf ("%s is a stereo wav file\n", sLoadName); + FS_FreeFile (data); + return qfalse; + } + +/* if ( info.width == 1 ) { + Com_Printf(S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sLoadName); + } + + if ( info.rate != 22050 ) { + Com_Printf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sLoadName); + } +*/ + samples = (short *)Z_Malloc(info.samples * sizeof(short) * 2, TAG_TEMP_WORKSPACE, qfalse); + + sfx->eSoundCompressionMethod = ct_16; + sfx->iSoundLengthInSamples = info.samples; + sfx->pSoundData = NULL; + ResampleSfx( sfx, info.rate, info.width, data + info.dataofs ); + + // Open AL + if (s_UseOpenAL) + { + if ((strstr(sfx->sSoundName, "chars")) || (strstr(sfx->sSoundName, "CHARS"))) + { + sfx->lipSyncData = (char *)Z_Malloc((sfx->iSoundLengthInSamples / 1000) + 1, TAG_SND_RAWDATA, qfalse); + S_PreProcessLipSync(sfx); + } + else + sfx->lipSyncData = NULL; + + // Clear Open AL Error State + alGetError(); + + // Generate AL Buffer + alGenBuffers(1, &Buffer); + if (alGetError() == AL_NO_ERROR) + { + // Copy audio data to AL Buffer + alBufferData(Buffer, AL_FORMAT_MONO16, sfx->pSoundData, sfx->iSoundLengthInSamples*2, 22050); + if (alGetError() == AL_NO_ERROR) + { + // Store AL Buffer in sfx struct, and release sample data + sfx->Buffer = Buffer; + Z_Free(sfx->pSoundData); + sfx->pSoundData = NULL; + } + } + } + + Z_Free(samples); + } + + FS_FreeFile( data ); + + return qtrue; +} + + +// wrapper function for above so I can guarantee that we don't attempt any audio-dumping during this call because +// of a z_malloc() fail recovery... +// +sboolean S_LoadSound( sfx_t *sfx ) +{ + gbInsideLoadSound = qtrue; // !!!!!!!!!!!!! + + sboolean bReturn = S_LoadSound_Actual( sfx ); + + gbInsideLoadSound = qfalse; // !!!!!!!!!!!!! + + return bReturn; +} + + +/* + Precalculate the lipsync values for the whole sample +*/ +void S_PreProcessLipSync(sfx_t *sfx) +{ + int i, j; + int sample; + int sampleTotal = 0; + + j = 0; + for (i = 0; i < sfx->iSoundLengthInSamples; i += 100) + { + sample = LittleShort(sfx->pSoundData[i]); + + sample = sample >> 8; + sampleTotal += sample * sample; + if (((i + 100) % 1000) == 0) + { + sampleTotal /= 10; + + if (sampleTotal < sfx->fVolRange * s_lip_threshold_1->value) + { + // tell the scripts that are relying on this that we are still going, but actually silent right now. + sample = -1; + } + else if (sampleTotal < sfx->fVolRange * s_lip_threshold_2->value) + sample = 1; + else if (sampleTotal < sfx->fVolRange * s_lip_threshold_3->value) + sample = 2; + else if (sampleTotal < sfx->fVolRange * s_lip_threshold_4->value) + sample = 3; + else + sample = 4; + + sfx->lipSyncData[j] = sample; + j++; + + sampleTotal = 0; + } + } + + if ((i % 1000) == 0) + return; + + i -= 100; + i = i % 1000; + i = i / 100; + // Process last < 1000 samples + if (i != 0) + sampleTotal /= i; + else + sampleTotal = 0; + + if (sampleTotal < sfx->fVolRange * s_lip_threshold_1->value) + { + // tell the scripts that are relying on this that we are still going, but actually silent right now. + sample = -1; + } + else if (sampleTotal < sfx->fVolRange * s_lip_threshold_2->value) + sample = 1; + else if (sampleTotal < sfx->fVolRange * s_lip_threshold_3->value) + sample = 2; + else if (sampleTotal < sfx->fVolRange * s_lip_threshold_4->value) + sample = 3; + else + sample = 4; + + sfx->lipSyncData[j] = sample; +} + diff --git a/codemp/client/snd_mem_console.cpp b/codemp/client/snd_mem_console.cpp new file mode 100644 index 0000000..88e5c6f --- /dev/null +++ b/codemp/client/snd_mem_console.cpp @@ -0,0 +1,353 @@ +// snd_mem.c: sound caching + + +// leave this as first line for PCH reasons... +// +// #include "../server/exe_headers.h" + +#include "snd_local_console.h" + +#ifdef _XBOX +#include +#endif + +#define SND_MAX_LOADS 48 +static sfx_t** s_LoadList = NULL; +static int s_LoadListSize = 0; +qboolean gbInsideLoadSound = qfalse; // Needed to link VVFIXME + +extern int Sys_GetFileCode(const char *name); + +//Drain sound main memory into ARAM. +void S_DrainRawSoundData(void) +{ + extern int s_soundStarted; + if (!s_soundStarted) return; + + do + { + S_UpdateLoading(); + +#ifdef _GAMECUBE + extern void ERR_DiscFail(bool); + ERR_DiscFail(true); +#endif + } + while (s_LoadListSize); +} + + + +/* +============ +GetWavInfo +============ +*/ +wavinfo_t GetWavInfo(byte *data) +{ + wavinfo_t info; + memset(&info, 0, sizeof(wavinfo_t)); + + if (!data) return info; + +#ifdef _GAMECUBE + if (*(short*)&data[14] != 0) + { + // invalid type, abort + return info; + } + + info.format = AL_FORMAT_MONO4; + info.width = 4; + info.size = ((*(int*)&data[20]) >> 1) + 96; + info.rate = *(int*)&data[8]; +#else + int dataofs = 0; + if (strncmp((char *)&data[dataofs + 0], "RIFF", 4) || + strncmp((char *)&data[dataofs + 8], "WAVE", 4)) + { + // invalid type, abort + return info; + } + dataofs += 12; // done with riff chunk + + WAVEFORMATEX* wav = (WAVEFORMATEX*)&data[dataofs + 8]; + info.format = wav->nChannels == 1 ? AL_FORMAT_MONO4 : AL_FORMAT_STEREO4; + info.rate = wav->nSamplesPerSec; + info.width = wav->wBitsPerSample; + dataofs += sizeof(WAVEFORMATEX) + wav->cbSize + 8; // done with fmt chunk + + info.size = *(int*)&data[dataofs + 4]; + + dataofs += 8; // done with data chunk +#endif + + return info; +} + +// adjust filename for foreign languages and WAV/MP3 issues. +// +unsigned int Sys_GetSoundFileCode(const char* name); +int Sys_GetSoundFileCodeSize(unsigned int code); +static qboolean S_LoadSound_FileNameAdjuster(char *psFilename) +{ + const char* ext = "wxb"; + + int len = strlen(psFilename); + + char *psVoice = strstr(psFilename,"chars"); + if (psVoice) + { + // account for foreign voices... + // + extern DWORD g_dwLanguage; + if (g_dwLanguage == XC_LANGUAGE_GERMAN) + strncpy(psVoice, "chr_d", 5); // Same number of letters as "chars" + else if (g_dwLanguage == XC_LANGUAGE_FRENCH) + strncpy(psVoice, "chr_f", 5); // Same number of letters as "chars" + else + psVoice = NULL; // Flag that we didn't substitute + } + + psFilename[len-3] = ext[0]; + psFilename[len-2] = ext[1]; + psFilename[len-1] = ext[2]; + int code = Sys_GetSoundFileCode( psFilename ); + + if(Sys_GetSoundFileCodeSize(code) == -1) + { + code = -1; + } + + if ( code == -1 ) + { + //hmmm, not found, ok, maybe we were trying a foreign noise ("arghhhhh.mp3" that doesn't matter?) but it + // was missing? Can't tell really, since both types are now in sound/chars. Oh well, fall back to English for now... + + if (psVoice) // were we trying to load foreign? + { + // yep, so fallback to re-try the english... + // + strncpy(psVoice,"chars",5); + + psFilename[len-3] = ext[0]; + psFilename[len-2] = ext[1]; + psFilename[len-1] = ext[2]; + code = Sys_GetSoundFileCode( psFilename ); + } + } + + if(Sys_GetSoundFileCodeSize(code) == -1) + { + code = -1; + } + + return code; +} + +/* +============== +S_GetFileCode +============== +*/ +int S_GetFileCode( const char* sSoundName ) +{ + char sLoadName[MAX_QPATH]; + + // make up a local filename to try wav/mp3 substitutes... + // + Q_strncpyz(sLoadName, sSoundName, sizeof(sLoadName)); + Q_strlwr( sLoadName ); + + // make sure we have an extension... + // + if (sLoadName[strlen(sLoadName) - 4] != '.') + { + strcat(sLoadName, ".xxx"); + } + + return S_LoadSound_FileNameAdjuster(sLoadName); +} + +/* +============ +S_UpdateLoading +============ +*/ +void S_UpdateLoading(void) { + for ( int i = 0; i < SND_MAX_LOADS; ++i ) + { + if ( s_LoadList[i] && + (s_LoadList[i]->iFlags & SFX_FLAG_LOADING) && + !Sys_StreamIsReading(s_LoadList[i]->iStreamHandle) ) + { + S_EndLoadSound(s_LoadList[i]); + s_LoadList[i] = NULL; + --s_LoadListSize; + } + } +} + +/* +============== +S_BeginLoadSound +============== +*/ +qboolean S_StartLoadSound( sfx_t *sfx ) +{ + assert(sfx->iFlags & SFX_FLAG_UNLOADED); + sfx->iFlags &= ~SFX_FLAG_UNLOADED; + + // Valid file? + if (sfx->iFileCode == -1) + { + sfx->iFlags |= SFX_FLAG_RESIDENT | SFX_FLAG_DEFAULT; + return qfalse; + } + + // Finish up any pending loads + do + { + S_UpdateLoading(); + } + while (s_LoadListSize >= SND_MAX_LOADS); + + // Open the file + sfx->iSoundLength = Sys_StreamOpen(sfx->iFileCode, &sfx->iStreamHandle); + if ( sfx->iSoundLength <= 0 ) + { + sfx->iFlags |= SFX_FLAG_RESIDENT | SFX_FLAG_DEFAULT; + return qfalse; + } + +#ifdef _GAMECUBE + // Allocate a buffer to read into... + sfx->pSoundData = Z_Malloc(sfx->iSoundLength + 64, TAG_SND_RAWDATA, + qtrue, 32); +#else + // Allocate a buffer to read into... + sfx->pSoundData = Z_Malloc(sfx->iSoundLength, TAG_SND_RAWDATA, qtrue, 32); +#endif + + // Setup the background read + if ( !sfx->pSoundData || + !Sys_StreamRead(sfx->pSoundData, sfx->iSoundLength, 0, + sfx->iStreamHandle) ) + { + if(sfx->pSoundData) { + Z_Free(sfx->pSoundData); + } + Sys_StreamClose(sfx->iStreamHandle); + sfx->iFlags |= SFX_FLAG_RESIDENT | SFX_FLAG_DEFAULT; + return qfalse; + } + sfx->iFlags |= SFX_FLAG_LOADING; + + // add sound to load list + for (int i = 0; i < SND_MAX_LOADS; ++i) + { + if (!s_LoadList[i]) + { + s_LoadList[i] = sfx; + ++s_LoadListSize; + break; + } + } + + return qtrue; +} + +/* +============== +S_EndLoadSound +============== +*/ +qboolean S_EndLoadSound( sfx_t *sfx ) +{ + wavinfo_t info; + byte* data; + ALuint Buffer; + + assert(sfx->iFlags & SFX_FLAG_LOADING); + sfx->iFlags &= ~SFX_FLAG_LOADING; + + // was the read successful? + if (Sys_StreamIsError(sfx->iStreamHandle)) + { +#if defined(FINAL_BUILD) + extern void ERR_DiscFail(bool); + ERR_DiscFail(false); +#endif + Sys_StreamClose(sfx->iStreamHandle); + Z_Free(sfx->pSoundData); + sfx->iFlags |= SFX_FLAG_RESIDENT | SFX_FLAG_DEFAULT; + return qfalse; + } + + Sys_StreamClose(sfx->iStreamHandle); + SND_TouchSFX(sfx); + + sfx->iLastTimeUsed = Com_Milliseconds()+1; // why +1? Hmmm, leave it for now I guess + + // loading a WAV, presumably... + data = (byte*)sfx->pSoundData; + info = GetWavInfo( data ); + + if (info.size == 0) + { + Z_Free(sfx->pSoundData); + sfx->iFlags |= SFX_FLAG_RESIDENT | SFX_FLAG_DEFAULT; + return qfalse; + } + + sfx->iSoundLength = info.size; + + // make sure we have enough space for the sound + SND_update(sfx); + + // Clear Open AL Error State + alGetError(); + + // Generate AL Buffer + alGenBuffers(1, &Buffer); + + // Copy audio data to AL Buffer + alBufferData(Buffer, info.format, data, + sfx->iSoundLength, info.rate); + if (alGetError() != AL_NO_ERROR) + { + Z_Free(sfx->pSoundData); + sfx->iFlags |= SFX_FLAG_UNLOADED; + return qfalse; + } + + sfx->Buffer = Buffer; + +#ifdef _GAMECUBE + Z_Free(sfx->pSoundData); +#endif + sfx->iFlags |= SFX_FLAG_RESIDENT; + + return qtrue; +} + +/* +============ +S_InitLoad +============ +*/ +void S_InitLoad(void) { + s_LoadList = new sfx_t*[SND_MAX_LOADS]; + memset(s_LoadList, 0, SND_MAX_LOADS * sizeof(sfx_t*)); + s_LoadListSize = 0; +} + +/* +============ +S_CloseLoad +============ +*/ +void S_CloseLoad(void) { + delete [] s_LoadList; +} + diff --git a/codemp/client/snd_mix.cpp b/codemp/client/snd_mix.cpp new file mode 100644 index 0000000..aebfdc1 --- /dev/null +++ b/codemp/client/snd_mix.cpp @@ -0,0 +1,470 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// snd_mix.c -- portable code to mix sounds for snd_dma.c + +// leave this as first line for PCH reasons... +// +#include "snd_local.h" + +portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; +int *snd_p, snd_linear_count, snd_vol; +short *snd_out; + + + + +#if !(defined __linux__ && defined __i386__) +#if !id386 + + +void S_WriteLinearBlastStereo16 (void) +{ + int i; + int val; + + for (i=0 ; i>8; + if (val > 0x7fff) + snd_out[i] = 0x7fff; + else if (val < (short)0x8000) + snd_out[i] = (short)0x8000; + else + snd_out[i] = val; + + val = snd_p[i+1]>>8; + if (val > 0x7fff) + snd_out[i+1] = 0x7fff; + else if (val < (short)0x8000) + snd_out[i+1] = (short)0x8000; + else + snd_out[i+1] = val; + } +} +#else +unsigned int uiMMXAvailable = 0; // leave as 32 bit +__declspec( naked ) void S_WriteLinearBlastStereo16 (void) +{ + __asm { + + push edi + push ebx + + mov ecx,ds:dword ptr[snd_linear_count] // snd_linear_count is always even at this point, but not nec. mult of 4 + mov ebx,ds:dword ptr[snd_p] + mov edi,ds:dword ptr[snd_out] + + cmp [uiMMXAvailable], dword ptr 0 + je NoMMX + +// writes 8 items (128 bits) per loop pass... +// + cmp ecx,8 + jb NoMMX + +LWLBLoopTop_MMX: + + movq mm1,[-8+ebx+ecx*4] + movq mm0,[-16+ebx+ecx*4] + movq mm3,[-24+ebx+ecx*4] + movq mm2,[-32+ebx+ecx*4] + psrad mm0,8 + psrad mm1,8 + psrad mm2,8 + psrad mm3,8 + packssdw mm0,mm1 + packssdw mm2,mm3 + movq [-8+edi+ecx*2],mm0 + movq [-16+edi+ecx*2],mm2 + + sub ecx,8 + cmp ecx,8 + jae LWLBLoopTop_MMX + + emms + + // now deal with any remaining count... + // + jecxz LExit + +NoMMX: + +// writes 2 items (32 bits) per loop pass... +// +LWLBLoopTop: + mov eax,ds:dword ptr[-8+ebx+ecx*4] + sar eax,8 + cmp eax,07FFFh + jg LClampHigh + cmp eax,0FFFF8000h + jnl LClampDone + mov eax,0FFFF8000h + jmp LClampDone +LClampHigh: + mov eax,07FFFh +LClampDone: + mov edx,ds:dword ptr[-4+ebx+ecx*4] + sar edx,8 + cmp edx,07FFFh + jg LClampHigh2 + cmp edx,0FFFF8000h + jnl LClampDone2 + mov edx,0FFFF8000h + jmp LClampDone2 +LClampHigh2: + mov edx,07FFFh +LClampDone2: + shl edx,16 + and eax,0FFFFh + or edx,eax + mov ds:dword ptr[-4+edi+ecx*2],edx + + sub ecx,2 + jnz LWLBLoopTop + +LExit: + pop ebx + pop edi + ret + } +} + +#endif +#endif + + +void S_TransferStereo16 (unsigned long *pbuf, int endtime) +{ + int lpos; + int ls_paintedtime; + + snd_p = (int *) paintbuffer; + ls_paintedtime = s_paintedtime; + + while (ls_paintedtime < endtime) + { + // handle recirculating buffer issues + lpos = ls_paintedtime & ((dma.samples>>1)-1); + + snd_out = (short *) pbuf + (lpos<<1); + + snd_linear_count = (dma.samples>>1) - lpos; + if (ls_paintedtime + snd_linear_count > endtime) + snd_linear_count = endtime - ls_paintedtime; + + snd_linear_count <<= 1; + + // write a linear blast of samples + S_WriteLinearBlastStereo16 (); + + snd_p += snd_linear_count; + ls_paintedtime += (snd_linear_count>>1); + } +} + +/* +=================== +S_TransferPaintBuffer + +=================== +*/ +void S_TransferPaintBuffer(int endtime) +{ + int out_idx; + int count; + int out_mask; + int *p; + int step; + int val; + unsigned long *pbuf; + + pbuf = (unsigned long *)dma.buffer; + + + if ( s_testsound->integer ) { + int i; + int count; + + // write a fixed sine wave + count = (endtime - s_paintedtime); + for (i=0 ; i> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = (short)val; + out_idx = (out_idx + 1) & out_mask; + } + } + else if (dma.samplebits == 8) + { + unsigned char *out = (unsigned char *) pbuf; + while (count--) + { + val = *p >> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = (short)((val>>8) + 128); + out_idx = (out_idx + 1) & out_mask; + } + } + } +} + + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ +static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sfx, int count, int sampleOffset, int bufferOffset ) +{ + portable_samplepair_t *pSamplesDest; + int iData; + + + int iLeftVol = ch->leftvol * snd_vol; + int iRightVol = ch->rightvol * snd_vol; + + pSamplesDest = &paintbuffer[ bufferOffset ]; + + for ( int i=0 ; ipSoundData[ sampleOffset++ ]; + + pSamplesDest[i].left += (iData * iLeftVol )>>8; + pSamplesDest[i].right += (iData * iRightVol)>>8; + } +} + + +void S_PaintChannelFromMP3( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) +{ + int data; + int leftvol, rightvol; + signed short *sfx; + int i; + portable_samplepair_t *samp; + static short tempMP3Buffer[PAINTBUFFER_SIZE]; + + MP3Stream_GetSamples( ch, sampleOffset, count, tempMP3Buffer, qfalse ); // qfalse = not stereo + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + sfx = tempMP3Buffer; + + samp = &paintbuffer[ bufferOffset ]; + + + while ( count & 3 ) { + data = *sfx; + samp->left += (data * leftvol)>>8; + samp->right += (data * rightvol)>>8; + + sfx++; + samp++; + count--; + } + + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + data = sfx[i+1]; + samp[i+1].left += (data * leftvol)>>8; + samp[i+1].right += (data * rightvol)>>8; + + data = sfx[i+2]; + samp[i+2].left += (data * leftvol)>>8; + samp[i+2].right += (data * rightvol)>>8; + + data = sfx[i+3]; + samp[i+3].left += (data * leftvol)>>8; + samp[i+3].right += (data * rightvol)>>8; + } +} + + +// subroutinised to save code dup (called twice) -ste +// +void ChannelPaint(channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset) +{ + switch (sc->eSoundCompressionMethod) + { + case ct_16: + + S_PaintChannelFrom16 (ch, sc, count, sampleOffset, bufferOffset); + break; + + case ct_MP3: + + S_PaintChannelFromMP3 (ch, sc, count, sampleOffset, bufferOffset); + break; + + default: + + assert(0); // debug aid, ignored in release. FIXME: Should we ERR_DROP here for badness-catch? + break; + } +} + + + +void S_PaintChannels( int endtime ) { + int i; + int end; + channel_t *ch; + sfx_t *sc; + int ltime, count; + int sampleOffset; + int normal_vol,voice_vol; + + snd_vol = normal_vol = s_volume->value*256; + voice_vol = (int)(s_volumeVoice->value*256); + +//Com_Printf ("%i to %i\n", s_paintedtime, endtime); + while ( s_paintedtime < endtime ) { + // if paintbuffer is smaller than DMA buffer + // we may need to fill it multiple times + end = endtime; + if ( endtime - s_paintedtime > PAINTBUFFER_SIZE ) { + end = s_paintedtime + PAINTBUFFER_SIZE; + } + + // clear the paint buffer to either music or zeros + if ( s_rawend < s_paintedtime ) { + if ( s_rawend ) { + //Com_DPrintf ("background sound underrun\n"); + } + memset(paintbuffer, 0, (end - s_paintedtime) * sizeof(portable_samplepair_t)); + } else { + // copy from the streaming sound source + int s; + int stop; + + stop = (end < s_rawend) ? end : s_rawend; + + for ( i = s_paintedtime ; i < stop ; i++ ) { + s = i&(MAX_RAW_SAMPLES-1); + paintbuffer[i-s_paintedtime] = s_rawsamples[s]; + } +// if (i != end) +// Com_Printf ("partial stream\n"); +// else +// Com_Printf ("full stream\n"); + for ( ; i < end ; i++ ) { + paintbuffer[i-s_paintedtime].left = + paintbuffer[i-s_paintedtime].right = 0; + } + } + + // paint in the channels. + ch = s_channels; + for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx || (ch->leftvol<0.25 && ch->rightvol<0.25 )) { + continue; + } + + if ( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE_ATTEN || ch->entchannel == CHAN_VOICE_GLOBAL ) + snd_vol = voice_vol; + else + snd_vol = normal_vol; + + ltime = s_paintedtime; + sc = ch->thesfx; + + // we might have to make 2 passes if it is + // a looping sound effect and the end of + // the sameple is hit... + // + do + { + if (ch->loopSound) { + sampleOffset = ltime % sc->iSoundLengthInSamples; + } else { + sampleOffset = ltime - ch->startSample; + } + + count = end - ltime; + if ( sampleOffset + count > sc->iSoundLengthInSamples ) { + count = sc->iSoundLengthInSamples - sampleOffset; + } + + if ( count > 0 ) { + ChannelPaint(ch, sc, count, sampleOffset, ltime - s_paintedtime); + ltime += count; + } + } while ( ltime < end && ch->loopSound ); + } +/* temprem + // paint in the looped channels. + ch = loop_channels; + for ( i = 0; i < numLoopChannels ; i++, ch++ ) { + if ( !ch->thesfx || (!ch->leftvol && !ch->rightvol )) { + continue; + } + + { + + ltime = s_paintedtime; + sc = ch->thesfx; + + if (sc->soundData==NULL || sc->soundLength==0) { + continue; + } + // we might have to make two passes if it + // is a looping sound effect and the end of + // the sample is hit + do { + sampleOffset = (ltime % sc->soundLength); + + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) + { + ChannelPaint(ch, sc, count, sampleOffset, ltime - s_paintedtime); + ltime += count; + } + + } while ( ltime < end); + } + } +*/ + // transfer out according to DMA format + S_TransferPaintBuffer( end ); + s_paintedtime = end; + } +} diff --git a/codemp/client/snd_mp3.cpp b/codemp/client/snd_mp3.cpp new file mode 100644 index 0000000..af90d0b --- /dev/null +++ b/codemp/client/snd_mp3.cpp @@ -0,0 +1,553 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// Filename:- cl_mp3.cpp +// +// (The interface module between all the MP3 stuff and the game) + + +#include "client.h" +#include "snd_mp3.h" // only included directly by a few snd_xxxx.cpp files plus this one +#include "../mp3code/mp3struct.h" // keep this rather awful file secret from the rest of the program +#include "../mp3code/copyright.h" + +// expects data already loaded, filename arg is for error printing only +// +// returns success/fail +// +sboolean MP3_IsValid( const char *psLocalFilename, void *pvData, int iDataLen, sboolean bStereoDesired /* = qfalse */) +{ + char *psError = C_MP3_IsValid(pvData, iDataLen, bStereoDesired); + + if (psError) + { + Com_Printf(va(S_COLOR_RED"%s(%s)\n",psError, psLocalFilename)); + } + + return !psError; +} + + + +// expects data already loaded, filename arg is for error printing only +// +// returns unpacked length, or 0 for errors (which will be printed internally) +// +int MP3_GetUnpackedSize( const char *psLocalFilename, void *pvData, int iDataLen, sboolean qbIgnoreID3Tag /* = qfalse */ + , sboolean bStereoDesired /* = qfalse */ + ) +{ + int iUnpackedSize = 0; + + // always do this now that we have fast-unpack code for measuring output size... (much safer than relying on tags that may have been edited, or if MP3 has been re-saved with same tag) + // + if (1)//qbIgnoreID3Tag || !MP3_ReadSpecialTagInfo((byte *)pvData, iDataLen, NULL, &iUnpackedSize)) + { + char *psError = C_MP3_GetUnpackedSize( pvData, iDataLen, &iUnpackedSize, bStereoDesired); + + if (psError) + { + Com_Printf(va(S_COLOR_RED"%s\n(File: %s)\n",psError, psLocalFilename)); + return 0; + } + } + + return iUnpackedSize; +} + + + +// expects data already loaded, filename arg is for error printing only +// +// returns byte count of unpacked data (effectively a success/fail bool) +// +int MP3_UnpackRawPCM( const char *psLocalFilename, void *pvData, int iDataLen, byte *pbUnpackBuffer, sboolean bStereoDesired /* = qfalse */) +{ + int iUnpackedSize; + char *psError = C_MP3_UnpackRawPCM( pvData, iDataLen, &iUnpackedSize, pbUnpackBuffer, bStereoDesired); + + if (psError) + { + Com_Printf(va(S_COLOR_RED"%s\n(File: %s)\n",psError, psLocalFilename)); + return 0; + } + + return iUnpackedSize; +} + + +// psLocalFilename is just for error reporting (if any)... +// +sboolean MP3Stream_InitPlayingTimeFields( LP_MP3STREAM lpMP3Stream, const char *psLocalFilename, void *pvData, int iDataLen, sboolean bStereoDesired /* = qfalse */) +{ + sboolean bRetval = qfalse; + + int iRate, iWidth, iChannels; + + char *psError = C_MP3_GetHeaderData(pvData, iDataLen, &iRate, &iWidth, &iChannels, bStereoDesired ); + if (psError) + { + Com_Printf(va(S_COLOR_RED"MP3Stream_InitPlayingTimeFields(): %s\n(File: %s)\n",psError, psLocalFilename)); + } + else + { + int iUnpackLength = MP3_GetUnpackedSize( psLocalFilename, pvData, iDataLen, qfalse, // sboolean qbIgnoreID3Tag + bStereoDesired); + if (iUnpackLength) + { + lpMP3Stream->iTimeQuery_UnpackedLength = iUnpackLength; + lpMP3Stream->iTimeQuery_SampleRate = iRate; + lpMP3Stream->iTimeQuery_Channels = iChannels; + lpMP3Stream->iTimeQuery_Width = iWidth; + + bRetval = qtrue; + } + } + + return bRetval; +} + +float MP3Stream_GetPlayingTimeInSeconds( LP_MP3STREAM lpMP3Stream ) +{ + if (lpMP3Stream->iTimeQuery_UnpackedLength) // fields initialised? + return (float)((((double)lpMP3Stream->iTimeQuery_UnpackedLength / (double)lpMP3Stream->iTimeQuery_SampleRate) / (double)lpMP3Stream->iTimeQuery_Channels) / (double)lpMP3Stream->iTimeQuery_Width); + + return 0.0f; +} + +float MP3Stream_GetRemainingTimeInSeconds( LP_MP3STREAM lpMP3Stream ) +{ + if (lpMP3Stream->iTimeQuery_UnpackedLength) // fields initialised? + return (float)(((((double)(lpMP3Stream->iTimeQuery_UnpackedLength - (lpMP3Stream->iBytesDecodedTotal * (lpMP3Stream->iTimeQuery_SampleRate / dma.speed)))) / (double)lpMP3Stream->iTimeQuery_SampleRate) / (double)lpMP3Stream->iTimeQuery_Channels) / (double)lpMP3Stream->iTimeQuery_Width); + + return 0.0f; +} + + + + +// expects data already loaded, filename arg is for error printing only +// +sboolean MP3_FakeUpWAVInfo( const char *psLocalFilename, void *pvData, int iDataLen, int iUnpackedDataLength, + int &format, int &rate, int &width, int &channels, int &samples, int &dataofs, + sboolean bStereoDesired /* = qfalse */ + ) +{ + // some things can be done instantly... + // + format = 1; // 1 for MS format + dataofs= 0; // will be 0 for me (since there's no header in the unpacked data) + + // some things need to be read... (though the whole stereo flag thing is crap) + // + char *psError = C_MP3_GetHeaderData(pvData, iDataLen, &rate, &width, &channels, bStereoDesired ); + if (psError) + { + Com_Printf(va(S_COLOR_RED"%s\n(File: %s)\n",psError, psLocalFilename)); + } + + // and some stuff needs calculating... + // + samples = iUnpackedDataLength / width; + + return !psError; +} + + + +const char sKEY_MAXVOL[]="#MAXVOL"; // formerly #defines +const char sKEY_UNCOMP[]="#UNCOMP"; // " " + +// returns qtrue for success... +// +sboolean MP3_ReadSpecialTagInfo(byte *pbLoadedFile, int iLoadedFileLen, + id3v1_1** ppTAG /* = NULL */, + int *piUncompressedSize /* = NULL */, + float *pfMaxVol /* = NULL */ + ) +{ + sboolean qbError = qfalse; + + id3v1_1* pTAG = (id3v1_1*) ((pbLoadedFile+iLoadedFileLen)-sizeof(id3v1_1)); // sizeof = 128 + + if (!strncmp(pTAG->id, "TAG", 3)) + { + // TAG found... + // + + // read MAXVOL key... + // + if (strncmp(pTAG->comment, sKEY_MAXVOL, strlen(sKEY_MAXVOL))) + { + qbError = qtrue; + } + else + { + if ( pfMaxVol) + { + *pfMaxVol = atof(pTAG->comment + strlen(sKEY_MAXVOL)); + } + } + + // + // read UNCOMP key... + // + if (strncmp(pTAG->album, sKEY_UNCOMP, strlen(sKEY_UNCOMP))) + { + qbError = qtrue; + } + else + { + if ( piUncompressedSize) + { + *piUncompressedSize = atoi(pTAG->album + strlen(sKEY_UNCOMP)); + } + } + } + else + { + pTAG = NULL; + } + + if (ppTAG) + { + *ppTAG = pTAG; + } + + return (pTAG && !qbError); +} + + + +#define FUZZY_AMOUNT (5*1024) // so it has to be significantly over, not just break even, because of + // the xtra CPU time versus memory saving + +cvar_t* cv_MP3overhead = NULL; +void MP3_InitCvars(void) +{ + cv_MP3overhead = Cvar_Get("s_mp3overhead", va("%d", sizeof(MP3STREAM) + FUZZY_AMOUNT), CVAR_ARCHIVE ); +} + + +// a file has been loaded in memory, see if we want to keep it as MP3, else as normal WAV... +// +// return = qtrue if keeping as MP3 +// +// (note: the reason I pass in the unpacked size rather than working it out here is simply because I already have it) +// +sboolean MP3Stream_InitFromFile( sfx_t* sfx, byte *pbSrcData, int iSrcDatalen, const char *psSrcDataFilename, + int iMP3UnPackedSize, sboolean bStereoDesired /* = qfalse */ + ) +{ + // first, make a decision based on size here as to whether or not it's worth it because of MP3 buffer space + // making small files much bigger (and therefore best left as WAV)... + // + + if (cv_MP3overhead && + ( + //iSrcDatalen + sizeof(MP3STREAM) + FUZZY_AMOUNT < iMP3UnPackedSize + iSrcDatalen + cv_MP3overhead->integer < iMP3UnPackedSize + ) + ) + { + // ok, let's keep it as MP3 then... + // + float fMaxVol = 128; // seems to be a reasonable typical default for maxvol (for lip synch). Naturally there's no #define I can use instead... + + MP3_ReadSpecialTagInfo(pbSrcData, iSrcDatalen, NULL, NULL, &fMaxVol ); // try and read a read maxvol from MP3 header + + // fill in some sfx_t fields... + // +// Q_strncpyz( sfx->name, psSrcDataFilename, sizeof(sfx->name) ); + sfx->eSoundCompressionMethod = ct_MP3; + sfx->fVolRange = fMaxVol; + //sfx->width = 2; + sfx->iSoundLengthInSamples = ((iMP3UnPackedSize / 2/*sfx->width*/) / (44100 / dma.speed)) / (bStereoDesired?2:1); + // + // alloc mem for data and store it (raw MP3 in this case)... + // + sfx->pSoundData = (short *) SND_malloc( iSrcDatalen, sfx ); + memcpy( sfx->pSoundData, pbSrcData, iSrcDatalen ); + + // now init the low-level MP3 stuff... + // + MP3STREAM SFX_MP3Stream = {0}; // important to init to all zeroes! + char *psError = C_MP3Stream_DecodeInit( &SFX_MP3Stream, /*sfx->data*/ /*sfx->soundData*/ pbSrcData, iSrcDatalen, + dma.speed,//(s_khz->value == 44)?44100:(s_khz->value == 22)?22050:11025, + 2/*sfx->width*/ * 8, + bStereoDesired + ); + SFX_MP3Stream.pbSourceData = (byte *) sfx->pSoundData; + if (psError) + { + // This should never happen, since any errors or problems with the MP3 file would have stopped us getting + // to this whole function, but just in case... + // + Com_Printf(va(S_COLOR_YELLOW"File \"%s\": %s\n",psSrcDataFilename,psError)); + + // This will leave iSrcDatalen bytes on the hunk stack (since you can't dealloc that), but MP3 files are + // usually small, and like I say, it should never happen. + // + // Strictly speaking, I should do a Z_Malloc above, then I could do a Z_Free if failed, else do a Hunk_Alloc + // to copy the Z_Malloc data into, then Z_Free, but for something that shouldn't happen it seemed bad to + // penalise the rest of the game with extra alloc demands. + // + return qfalse; + } + + // success ( ...on a plate). + // + // make a copy of the filled-in stream struct and attach to the sfx_t struct... + // + sfx->pMP3StreamHeader = (MP3STREAM *) Z_Malloc( sizeof(MP3STREAM), TAG_SND_MP3STREAMHDR, qfalse ); + memcpy( sfx->pMP3StreamHeader, &SFX_MP3Stream, sizeof(MP3STREAM) ); + // + return qtrue; + } + + return qfalse; +} + + + +// decode one packet of MP3 data only (typical output size is 2304, or 2304*2 for stereo, so input size is less +// +// return is decoded byte count, else 0 for finished +// +int MP3Stream_Decode( LP_MP3STREAM lpMP3Stream, sboolean bDoingMusic ) +{ + lpMP3Stream->iCopyOffset = 0; + + if (0)//!bDoingMusic) + { + /* + // SOF2: need to make a local buffer up so we can decode the piece we want from a contiguous bitstream rather than + // this linklist junk... + // + // since MP3 packets are generally 416 or 417 bytes in length it seems reasonable to just find which linked-chunk + // the current read offset lies within then grab the next one as well (since they're 2048 bytes) and make one + // buffer with just the two concat'd together. Shouldn't be much of a processor hit. + // + sndBuffer *pChunk = (sndBuffer *) lpMP3Stream->pbSourceData; + // + // may as well make this static to avoid cut down on stack-validation run-time... + // + static byte byRawBuffer[SND_CHUNK_SIZE_BYTE*2]; // *2 for byte->short // easily enough to decode one frame of MP3 data, most are 416 or 417 bytes + + // fast-forward to the correct chunk... + // + int iBytesToSkipPast = lpMP3Stream->iSourceReadIndex; + + while (iBytesToSkipPast >= SND_CHUNK_SIZE_BYTE) + { + pChunk = pChunk->next; + if (!pChunk) + { + // err.... reading off the end of the data stream guys... + // + // pChunk = (sndBuffer *) lpMP3Stream->pbSourceData; // restart + return 0; // ... 0 bytes decoded, so will just stop caller-decoder all nice and legal as EOS + } + iBytesToSkipPast -= SND_CHUNK_SIZE_BYTE; + } + + { + // ok, pChunk is now the 2k or so chunk we're in the middle of... + // + int iChunk1BytesToCopy = SND_CHUNK_SIZE_BYTE - iBytesToSkipPast; + memcpy(byRawBuffer,((byte *)pChunk->sndChunk) + iBytesToSkipPast, iChunk1BytesToCopy); + // + // concat next chunk on to this as well... + // + pChunk = pChunk->next; + if (pChunk) + { + memcpy(byRawBuffer + iChunk1BytesToCopy, pChunk->sndChunk, SND_CHUNK_SIZE_BYTE); + } + else + { + memset(byRawBuffer + iChunk1BytesToCopy, 0, SND_CHUNK_SIZE_BYTE); + } + } + + + { + // now we need to backup some struct fields, fake 'em, do the lo-level call, then restore 'em... + // + byte *pbSourceData_Old = lpMP3Stream->pbSourceData; + int iSourceReadIndex_Old= lpMP3Stream->iSourceReadIndex; + + lpMP3Stream->pbSourceData = &byRawBuffer[0]; + lpMP3Stream->iSourceReadIndex= 0; // since this is zero, not the buffer offset within a chunk, we can play tricks further down when restoring + + { + unsigned int uiBytesDecoded = C_MP3Stream_Decode( lpMP3Stream, qfalse ); + + lpMP3Stream->iSourceReadIndex += iSourceReadIndex_Old; // note '+=' rather than '=', to take account of movement. + lpMP3Stream->pbSourceData = pbSourceData_Old; + + return uiBytesDecoded; + } + } + */ + } + else + { + // SOF2 music, or EF1 anything... + // + return C_MP3Stream_Decode( lpMP3Stream, qfalse ); // bFastForwarding + } +} + + +sboolean MP3Stream_SeekTo( channel_t *ch, float fTimeToSeekTo ) +{ + const float fEpsilon = 0.05f; // accurate to 1/50 of a second, but plus or minus this gives 1/10 of second + + MP3Stream_Rewind( ch ); + // + // sanity... :-) + // + const float fTrackLengthInSeconds = MP3Stream_GetPlayingTimeInSeconds( &ch->MP3StreamHeader ); + if (fTimeToSeekTo > fTrackLengthInSeconds) + { + fTimeToSeekTo = fTrackLengthInSeconds; + } + + // now do the seek... + // + while (1) + { + float fPlayingTimeElapsed = MP3Stream_GetPlayingTimeInSeconds( &ch->MP3StreamHeader ) - MP3Stream_GetRemainingTimeInSeconds( &ch->MP3StreamHeader ); + float fAbsTimeDiff = fabs(fTimeToSeekTo - fPlayingTimeElapsed); + + if ( fAbsTimeDiff <= fEpsilon) + return qtrue; + + // when decoding, use fast-forward until within 3 seconds, then slow-decode (which should init stuff properly?)... + // + int iBytesDecodedThisPacket = C_MP3Stream_Decode( &ch->MP3StreamHeader, (fAbsTimeDiff > 3.0f) ); // bFastForwarding + if (iBytesDecodedThisPacket == 0) + break; // EOS + } + + return qfalse; +} + + +// returns qtrue for all ok +// +sboolean MP3Stream_Rewind( channel_t *ch ) +{ + ch->iMP3SlidingDecodeWritePos = 0; + ch->iMP3SlidingDecodeWindowPos= 0; + +/* + char *psError = C_MP3Stream_Rewind( &ch->MP3StreamHeader ); + + if (psError) + { + Com_Printf(S_COLOR_YELLOW"%s\n",psError); + return qfalse; + } + + return qtrue; +*/ + + // speed opt, since I know I already have the right data setup here... + // + memcpy(&ch->MP3StreamHeader, ch->thesfx->pMP3StreamHeader, sizeof(ch->MP3StreamHeader)); + return qtrue; + +} + + +// returns qtrue while still playing normally, else qfalse for either finished or request-offset-error +// +sboolean MP3Stream_GetSamples( channel_t *ch, int startingSampleNum, int count, short *buf, sboolean bStereo ) +{ + sboolean qbStreamStillGoing = qtrue; + + const int iQuarterOfSlidingBuffer = sizeof(ch->MP3SlidingDecodeBuffer)/4; + const int iThreeQuartersOfSlidingBuffer = (sizeof(ch->MP3SlidingDecodeBuffer)*3)/4; + +// Com_Printf("startingSampleNum %d\n",startingSampleNum); + + count *= 2/* <- = SOF2; ch->sfx->width*/; // count arg was for words, so double it for bytes; + + // convert sample number into a byte offset... (make new variable for clarity?) + // + startingSampleNum *= 2 /* <- = SOF2; ch->sfx->width*/ * (bStereo?2:1); + + if ( startingSampleNum < ch->iMP3SlidingDecodeWindowPos) + { + // what?!?!?! smegging time travel needed or something?, forget it + memset(buf,0,count); + return qfalse; + } + +// OutputDebugString(va("\nRequest: startingSampleNum %d, count %d\n",startingSampleNum,count)); +// OutputDebugString(va("WindowPos %d, WindowWritePos %d\n",ch->iMP3SlidingDecodeWindowPos,ch->iMP3SlidingDecodeWritePos)); + +// sboolean _bDecoded = qfalse; + + while (! + ( + (startingSampleNum >= ch->iMP3SlidingDecodeWindowPos) + && + (startingSampleNum + count < ch->iMP3SlidingDecodeWindowPos + ch->iMP3SlidingDecodeWritePos) + ) + ) + { +// if (!_bDecoded) +// { +// Com_Printf(S_COLOR_YELLOW"Decode needed!\n"); +// } +// _bDecoded = qtrue; +// OutputDebugString("Scrolling..."); + + int _iBytesDecoded = MP3Stream_Decode( (LP_MP3STREAM) &ch->MP3StreamHeader, bStereo ); // stereo only for music, so this is safe +// OutputDebugString(va("%d bytes decoded\n",_iBytesDecoded)); + if (_iBytesDecoded == 0) + { + // no more source data left so clear the remainder of the buffer... + // + memset(ch->MP3SlidingDecodeBuffer + ch->iMP3SlidingDecodeWritePos, 0, sizeof(ch->MP3SlidingDecodeBuffer)-ch->iMP3SlidingDecodeWritePos); +// OutputDebugString("Finished\n"); + qbStreamStillGoing = qfalse; + break; + } + else + { + memcpy(ch->MP3SlidingDecodeBuffer + ch->iMP3SlidingDecodeWritePos,ch->MP3StreamHeader.bDecodeBuffer,_iBytesDecoded); + + ch->iMP3SlidingDecodeWritePos += _iBytesDecoded; + + // if reached 3/4 of buffer pos, backscroll the decode window by one quarter... + // + if (ch->iMP3SlidingDecodeWritePos > iThreeQuartersOfSlidingBuffer) + { + memmove(ch->MP3SlidingDecodeBuffer, ((byte *)ch->MP3SlidingDecodeBuffer + iQuarterOfSlidingBuffer), iThreeQuartersOfSlidingBuffer); + ch->iMP3SlidingDecodeWritePos -= iQuarterOfSlidingBuffer; + ch->iMP3SlidingDecodeWindowPos+= iQuarterOfSlidingBuffer; + } + } +// OutputDebugString(va("WindowPos %d, WindowWritePos %d\n",ch->iMP3SlidingDecodeWindowPos,ch->iMP3SlidingDecodeWritePos)); + } + +// if (!_bDecoded) +// { +// Com_Printf(S_COLOR_YELLOW"No decode needed\n"); +// } + + assert(startingSampleNum >= ch->iMP3SlidingDecodeWindowPos); + memcpy( buf, ch->MP3SlidingDecodeBuffer + (startingSampleNum-ch->iMP3SlidingDecodeWindowPos), count); + +// OutputDebugString("OK\n\n"); + + return qbStreamStillGoing; +} + + +///////////// eof ///////////// + diff --git a/codemp/client/snd_mp3.h b/codemp/client/snd_mp3.h new file mode 100644 index 0000000..8e8e04d --- /dev/null +++ b/codemp/client/snd_mp3.h @@ -0,0 +1,87 @@ +// Filename:- cl_mp3.h +// +// (Interface to the rest of the game for the MP3 functions) +// +#ifndef CL_MP3_H +#define CL_MP3_H + + +#ifndef sfx_t +#include "snd_local.h" +#endif + + + +typedef struct id3v1_1 { + char id[3]; + char title[30]; // + char artist[30]; // "Raven Software" + char album[30]; // "#UNCOMP %d" // needed + char year[4]; // "2000" + char comment[28]; // "#MAXVOL %g" // needed + char zero; + char track; + char genre; +} id3v1_1; // 128 bytes in size + + +extern const char sKEY_MAXVOL[]; +extern const char sKEY_UNCOMP[]; + + +// (so far, all these functions are only called from one place in snd_mem.cpp) +// +// (filenames are used purely for error reporting, all files should already be loaded before you get here) +// +void MP3_InitCvars ( void ); +sboolean MP3_IsValid ( const char *psLocalFilename, void *pvData, int iDataLen, sboolean bStereoDesired = qfalse ); +int MP3_GetUnpackedSize ( const char *psLocalFilename, void *pvData, int iDataLen, sboolean qbIgnoreID3Tag = qfalse, sboolean bStereoDesired = qfalse ); +sboolean MP3_UnpackRawPCM ( const char *psLocalFilename, void *pvData, int iDataLen, byte *pbUnpackBuffer, sboolean bStereoDesired = qfalse ); +sboolean MP3Stream_InitPlayingTimeFields( LP_MP3STREAM lpMP3Stream, const char *psLocalFilename, void *pvData, int iDataLen, sboolean bStereoDesired = qfalse); +float MP3Stream_GetPlayingTimeInSeconds( LP_MP3STREAM lpMP3Stream ); +float MP3Stream_GetRemainingTimeInSeconds( LP_MP3STREAM lpMP3Stream ); +sboolean MP3_FakeUpWAVInfo ( const char *psLocalFilename, void *pvData, int iDataLen, int iUnpackedDataLength, int &format, int &rate, int &width, int &channels, int &samples, int &dataofs, sboolean bStereoDesired = qfalse ); +sboolean MP3_ReadSpecialTagInfo ( byte *pbLoadedFile, int iLoadedFileLen, + id3v1_1** ppTAG = NULL, int *piUncompressedSize = NULL, float *pfMaxVol = NULL); +sboolean MP3Stream_InitFromFile ( sfx_t* sfx, byte *pbSrcData, int iSrcDatalen, const char *psSrcDataFilename, int iMP3UnPackedSize, sboolean bStereoDesired = qfalse ); +int MP3Stream_Decode ( LP_MP3STREAM lpMP3Stream, sboolean bDoingMusic ); +sboolean MP3Stream_SeekTo ( channel_t *ch, float fTimeToSeekTo ); +sboolean MP3Stream_Rewind ( channel_t *ch ); +sboolean MP3Stream_GetSamples ( channel_t *ch, int startingSampleNum, int count, short *buf, sboolean bStereo ); + + + + + +/////////////////////////////////////// +// +// the real worker code deep down in the MP3 C code... (now externalised here so the music streamer can access one) +// +#ifdef __cplusplus +extern "C" +{ +#endif + + +char* C_MP3_IsValid (void *pvData, int iDataLen, int bStereoDesired); +char* C_MP3_GetUnpackedSize (void *pvData, int iDataLen, int *piUnpackedSize, int bStereoDesired); +char* C_MP3_UnpackRawPCM (void *pvData, int iDataLen, int *piUnpackedSize, void *pbUnpackBuffer, int bStereoDesired); +char* C_MP3_GetHeaderData (void *pvData, int iDataLen, int *piRate, int *piWidth, int *piChannels, int bStereoDesired); +char* C_MP3Stream_DecodeInit (LP_MP3STREAM pSFX_MP3Stream, void *pvSourceData, int iSourceBytesRemaining, + int iGameAudioSampleRate, int iGameAudioSampleBits, int bStereoDesired); +unsigned int C_MP3Stream_Decode( LP_MP3STREAM pSFX_MP3Stream, int bFastForwarding ); +char* C_MP3Stream_Rewind (LP_MP3STREAM pSFX_MP3Stream); + + +#ifdef __cplusplus +} +#endif +// +/////////////////////////////////////// + + + +#endif // #ifndef CL_MP3_H + +///////////////// eof ///////////////////// + diff --git a/codemp/client/snd_music.cpp b/codemp/client/snd_music.cpp new file mode 100644 index 0000000..90e9b86 --- /dev/null +++ b/codemp/client/snd_music.cpp @@ -0,0 +1,1153 @@ +// Filename:- snd_music.cpp +// +// Stuff to parse in special x-fade music format and handle blending etc + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + + +#include "../game/q_shared.h" +#include "../qcommon/sstring.h" + +#pragma warning ( disable : 4663 ) //spcialize class +#pragma warning( push, 3 ) +#include +#pragma warning (pop) + +#ifdef _XBOX +#include "snd_local_console.h" +#include +#else +#include "snd_local.h" +//#include "snd_mp3.h" +#endif + +// +#include "snd_music.h" +#include "snd_ambient.h" + +#include "../qcommon/GenericParser2.h" + +extern sboolean S_FileExists( const char *psFilename ); + +#ifdef _XBOX +extern void Z_SetNewDeleteTemporary(bool bTemp); +#endif + +#define sKEY_MUSICFILES "musicfiles" +#define sKEY_ENTRY "entry" +#define sKEY_EXIT "exit" +#define sKEY_MARKER "marker" +#define sKEY_TIME "time" +#define sKEY_NEXTFILE "nextfile" +#define sKEY_NEXTMARK "nextmark" +#define sKEY_LEVELMUSIC "levelmusic" +#define sKEY_EXPLORE "explore" +#define sKEY_ACTION "action" +#define sKEY_BOSS "boss" +#define sKEY_DEATH "death" +#define sKEY_USES "uses" +#define sKEY_USEBOSS "useboss" + +#define sKEY_PLACEHOLDER "placeholder" // ignore these + +#define sFILENAME_DMS "ext_data/dms.dat" + + +#define MUSIC_PARSE_ERROR(_string) Music_Parse_Error(_string) // only use during parse, not run-time use, and bear in mid that data is zapped after error message, so exit any loops immediately +#define MUSIC_PARSE_WARNING(_string) Music_Parse_Warning(_string) + +typedef struct +{ + sstring_t sNextFile; + sstring_t sNextMark; // blank if used for an explore piece, name of marker point to enter new file at + +} MusicExitPoint_t; + +struct MusicExitTime_t // need to declare this way for operator < below +{ + float fTime; + int iExitPoint; + + // I'm defining this '<' operator so STL's sort algorithm will work + // + bool operator < (const MusicExitTime_t& _X) const {return (fTime < _X.fTime);} +}; + +// it's possible for all 3 of these to be empty if it's boss or death music +// +typedef vector MusicExitPoints_t; +typedef vector MusicExitTimes_t; +typedef map MusicEntryTimes_t; // key eg "marker1" + +typedef struct +{ + sstring_t sFileNameBase; + MusicEntryTimes_t MusicEntryTimes; + MusicExitPoints_t MusicExitPoints; + MusicExitTimes_t MusicExitTimes; + +} MusicFile_t; + +typedef map MusicData_t; // string is "explore", "action", "boss" etc + MusicData_t* MusicData = NULL; +// there are now 2 of these, because of the new "uses" keyword... +// +sstring_t gsLevelNameForLoad; // eg "kejim_base", formed from literal BSP name, but also used as dir name for music paths +sstring_t gsLevelNameForCompare; // eg "kejim_base", formed from literal BSP name, but also used as dir name for music paths +sstring_t gsLevelNameForBossLoad; // eg "kejim_base', special case for enabling boss music to come from a different dir - sigh.... + +void Music_Free(void) +{ + delete MusicData; + MusicData = NULL; +} + +// some sort of error in the music data... +// +static void Music_Parse_Error(LPCSTR psError) +{ +#ifdef FINAL_BUILD + extern cvar_t *s_debugdynamic; + if (s_debugdynamic && s_debugdynamic->integer) + { +#endif + Com_Printf(S_COLOR_RED "Error parsing music data ( in \"%s\" ):\n%s\n",sFILENAME_DMS,psError); +#ifdef FINAL_BUILD + } +#endif + MusicData->clear(); +} + +// something to just mention if interested... +// +static void Music_Parse_Warning(LPCSTR psError) +{ +#ifdef FINAL_BUILD + extern cvar_t *s_debugdynamic; + if (s_debugdynamic && s_debugdynamic->integer) + { +#endif + Com_Printf(S_COLOR_YELLOW "%s", psError); +#ifdef FINAL_BUILD + } +#endif +} + +// the 2nd param here is pretty kludgy (sigh), and only used for testing for the "boss" type. +// Unfortunately two of the places that calls this doesn't have much other access to the state other than +// a string, not an enum, so for those cases they only pass in BOSS or EXPLORE, so don't rely on it totally. +// +static LPCSTR Music_BuildFileName(LPCSTR psFileNameBase, MusicState_e eMusicState ) +{ + static sstring_t sFileName; + + //HACK! + if (eMusicState == eBGRNDTRACK_DEATH) + { + return "music/death_music.mp3"; + } + + LPCSTR psDirName = (eMusicState == eBGRNDTRACK_BOSS) ? gsLevelNameForBossLoad.c_str() : gsLevelNameForLoad.c_str(); + + sFileName = va("music/%s/%s.mp3",psDirName,psFileNameBase); + return sFileName.c_str(); +} + +// this MUST return NULL for non-base states unless doing debug-query +const char *Music_BaseStateToString( MusicState_e eMusicState, sboolean bDebugPrintQuery /* = qfalse */ ) +{ + switch (eMusicState) + { + case eBGRNDTRACK_EXPLORE: return "explore"; + case eBGRNDTRACK_ACTION: return "action"; + case eBGRNDTRACK_BOSS: return "boss"; + case eBGRNDTRACK_SILENCE: return "silence"; // not used in this module, but snd_dma uses it now it's de-static'd + case eBGRNDTRACK_DEATH: return "death"; + + // info only, not map<> lookup keys (unlike above)... + // + case eBGRNDTRACK_ACTIONTRANS0: if (bDebugPrintQuery) return "action_tr0"; + case eBGRNDTRACK_ACTIONTRANS1: if (bDebugPrintQuery) return "action_tr1"; + case eBGRNDTRACK_ACTIONTRANS2: if (bDebugPrintQuery) return "action_tr2"; + case eBGRNDTRACK_ACTIONTRANS3: if (bDebugPrintQuery) return "action_tr3"; + case eBGRNDTRACK_EXPLORETRANS0: if (bDebugPrintQuery) return "explore_tr0"; + case eBGRNDTRACK_EXPLORETRANS1: if (bDebugPrintQuery) return "explore_tr1"; + case eBGRNDTRACK_EXPLORETRANS2: if (bDebugPrintQuery) return "explore_tr2"; + case eBGRNDTRACK_EXPLORETRANS3: if (bDebugPrintQuery) return "explore_tr3"; + case eBGRNDTRACK_FADE: if (bDebugPrintQuery) return "fade"; + } + + return NULL; +} + +static sboolean Music_ParseMusic(CGenericParser2 &Parser, MusicData_t *MusicData, CGPGroup *pgMusicFiles, LPCSTR psMusicName, LPCSTR psMusicNameKey, MusicState_e eMusicState) +{ + sboolean bReturn = qfalse; + +#ifdef _XBOX + Z_SetNewDeleteTemporary(true); +#endif + MusicFile_t MusicFile; +#ifdef _XBOX + Z_SetNewDeleteTemporary(false); +#endif + + CGPGroup *pgMusicFile = pgMusicFiles->FindSubGroup(psMusicName); + if (pgMusicFile) + { + // read subgroups... + // + sboolean bEntryFound = qfalse; + sboolean bExitFound = qfalse; + // + // (read entry points first, so I can check exit points aren't too close in time) + // + CGPGroup *pEntryGroup = pgMusicFile->FindSubGroup(sKEY_ENTRY); + if (pEntryGroup) + { + // read entry points... + // + for (CGPValue *pValue = pEntryGroup->GetPairs(); pValue; pValue = pValue->GetNext()) + { + LPCSTR psKey = pValue->GetName(); + LPCSTR psValue = pValue->GetTopValue(); + + //if (!strncmp(psKey,sKEY_MARKER,strlen(sKEY_MARKER))) // for now, assume anything is a marker + { + MusicFile.MusicEntryTimes[psKey] = atof(psValue); + bEntryFound = qtrue; // harmless to keep setting + } + } + } + + for (CGPGroup *pGroup = pgMusicFile->GetSubGroups(); pGroup; pGroup = pGroup->GetNext()) + { + LPCSTR psGroupName = pGroup->GetName(); + + if (!strcmp(psGroupName,sKEY_ENTRY)) + { + // skip entry points, I've already read them in above + // + } + else + if (!strcmp(psGroupName,sKEY_EXIT)) + { + int iThisExitPointIndex = MusicFile.MusicExitPoints.size(); // must eval this first, so unaffected by push_back etc + // + // read this set of exit points... + // + MusicExitPoint_t MusicExitPoint; + for (CGPValue *pValue = pGroup->GetPairs(); pValue; pValue = pValue->GetNext()) + { + LPCSTR psKey = pValue->GetName(); + LPCSTR psValue = pValue->GetTopValue(); + + if (!strcmp(psKey,sKEY_NEXTFILE)) + { + MusicExitPoint.sNextFile = psValue; + bExitFound = qtrue; // harmless to keep setting + } + else + if (!strcmp(psKey,sKEY_NEXTMARK)) + { + MusicExitPoint.sNextMark = psValue; + } + else + if (!strncmp(psKey,sKEY_TIME,strlen(sKEY_TIME))) + { + MusicExitTime_t MusicExitTime; + MusicExitTime.fTime = atof(psValue); + MusicExitTime.iExitPoint= iThisExitPointIndex; + + // new check, don't keep this this exit point if it's within 1.5 seconds either way of an entry point... + // + sboolean bTooCloseToEntryPoint = qfalse; + for (MusicEntryTimes_t::iterator itEntryTimes = MusicFile.MusicEntryTimes.begin(); itEntryTimes != MusicFile.MusicEntryTimes.end(); ++itEntryTimes) + { + float fThisEntryTime = (*itEntryTimes).second; + + if (Q_fabs(fThisEntryTime - MusicExitTime.fTime) < 1.5f) + { +// bTooCloseToEntryPoint = qtrue; // not sure about this, ignore for now + break; + } + } + if (!bTooCloseToEntryPoint) + { +#ifdef _XBOX + Z_SetNewDeleteTemporary(true); +#endif + MusicFile.MusicExitTimes.push_back(MusicExitTime); +#ifdef _XBOX + Z_SetNewDeleteTemporary(false); +#endif + } + } + } + +#ifdef _XBOX + Z_SetNewDeleteTemporary(true); +#endif + MusicFile.MusicExitPoints.push_back(MusicExitPoint); +#ifdef _XBOX + Z_SetNewDeleteTemporary(false); +#endif + int iNumExitPoints = MusicFile.MusicExitPoints.size(); + + // error checking... + // + switch (eMusicState) + { + case eBGRNDTRACK_EXPLORE: + if (iNumExitPoints > iMAX_EXPLORE_TRANSITIONS) + { + MUSIC_PARSE_ERROR( va("\"%s\" has > %d %s transitions defined!\n",psMusicName,iMAX_EXPLORE_TRANSITIONS,psMusicNameKey) ); + return qfalse; + } + break; + + case eBGRNDTRACK_ACTION: + if (iNumExitPoints > iMAX_ACTION_TRANSITIONS) + { + MUSIC_PARSE_ERROR( va("\"%s\" has > %d %s transitions defined!\n",psMusicName,iMAX_ACTION_TRANSITIONS,psMusicNameKey) ); + return qfalse; + } + break; + + case eBGRNDTRACK_BOSS: + case eBGRNDTRACK_DEATH: + + MUSIC_PARSE_ERROR( va("\"%s\" has %s transitions defined, this is not allowed!\n",psMusicName,psMusicNameKey) ); + break; + } + } + } + + // for now, assume everything was ok unless some obvious things are missing... + // + bReturn = qtrue; + + if (eMusicState != eBGRNDTRACK_BOSS && eMusicState != eBGRNDTRACK_DEATH) // boss & death pieces can omit entry/exit stuff + { + if (!bEntryFound) + { + MUSIC_PARSE_ERROR(va("Unable to find subgroup \"%s\" in group \"%s\"\n",sKEY_ENTRY,psMusicName)); + bReturn = qfalse; + } + if (!bExitFound) + { + MUSIC_PARSE_ERROR(va("Unable to find subgroup \"%s\" in group \"%s\"\n",sKEY_EXIT,psMusicName)); + bReturn = qfalse; + } + } + } + else + { + MUSIC_PARSE_ERROR(va("Unable to find musicfiles entry \"%s\"\n",psMusicName)); + } + + if (bReturn) + { + MusicFile.sFileNameBase = psMusicName; + (*MusicData)[ psMusicNameKey ] = MusicFile; + } + + return bReturn; +} + + + + +// I only need this because GP2 can't cope with trailing whitespace (for !@#$%^'s sake!!!!)... +// +// (output buffer will always be just '\n' seperated, regardless of possible "\r\n" pairs) +// +// (remember to Z_Free() the returned char * when done with it!!!) +// +static char *StripTrailingWhiteSpaceOnEveryLine(char *pText) +{ +#ifdef _XBOX + Z_SetNewDeleteTemporary(true); +#endif + string strNewText; + + while (*pText) + { + char sOneLine[1024]; // BTO: was 16k + + // find end of line... + // + char *pThisLineEnd = pText; + while (*pThisLineEnd && *pThisLineEnd != '\r' && ((pThisLineEnd-pText) < sizeof(sOneLine)-1)) + { + pThisLineEnd++; + } + + int iCharsToCopy = pThisLineEnd - pText; + strncpy(sOneLine, pText, iCharsToCopy); + sOneLine[iCharsToCopy]='\0'; + pText += iCharsToCopy; + while (*pText == '\n' || *pText == '\r') pText++; + + // trim trailing... + // + sboolean bTrimmed = qfalse; + do + { + bTrimmed = qfalse; + int iStrLen = strlen(sOneLine); + + if (iStrLen) + { + if (sOneLine[iStrLen-1] == '\t' || sOneLine[iStrLen-1] == ' ') + { + sOneLine[iStrLen-1] = '\0'; + bTrimmed = qtrue; + } + } + } + while (bTrimmed); + + strNewText += sOneLine; + strNewText += "\n"; + } + + char *pNewText = (char *) Z_Malloc( strlen(strNewText.c_str())+1, TAG_TEMP_WORKSPACE, qfalse); + strcpy(pNewText, strNewText.c_str()); +#ifdef _XBOX + Z_SetNewDeleteTemporary(false); +#endif + return pNewText; +} + + +// called from SV_SpawnServer, but before map load and music start etc. +// +// This just initialises the Lucas music structs so the background music player can interrogate them... +// +sstring_t gsLevelNameFromServer; +void Music_SetLevelName(const char *psLevelName) +{ + gsLevelNameFromServer = psLevelName; +} + +static sboolean Music_ParseLeveldata(const char *psLevelName) +{ + sboolean bReturn = qfalse; + + if (MusicData == NULL) + { + MusicData = new MusicData_t; + } + + // already got this data? + // + if (MusicData->size() && !Q_stricmp(psLevelName,gsLevelNameForCompare.c_str())) + { + return qtrue; + } + + MusicData->clear(); + + char sLevelName[MAX_QPATH]; + Q_strncpyz(sLevelName,psLevelName,sizeof(sLevelName)); + + gsLevelNameForLoad = sLevelName; // harmless to init here even if we fail to parse dms.dat file + gsLevelNameForCompare = sLevelName; // harmless to init here even if we fail to parse dms.dat file + gsLevelNameForBossLoad = sLevelName; // harmless to init here even if we fail to parse dms.dat file + + char *pText = NULL; + /*int iTotalBytesLoaded = */FS_ReadFile(sFILENAME_DMS, (void **)&pText ); + if (pText) + { + char *psStrippedText = StripTrailingWhiteSpaceOnEveryLine(pText); + CGenericParser2 Parser; + char *psDataPtr = psStrippedText; // because ptr gets advanced, so we supply a clone that GP can alter + if (Parser.Parse(&psDataPtr, true)) + { + CGPGroup *pFileGroup = Parser.GetBaseParseGroup(); + if (pFileGroup) + { + CGPGroup *pgMusicFiles = pFileGroup->FindSubGroup(sKEY_MUSICFILES); + if (pgMusicFiles) + { + CGPGroup *pgLevelMusic = pFileGroup->FindSubGroup(sKEY_LEVELMUSIC); + + if (pgLevelMusic) + { + CGPGroup *pgThisLevelMusic = NULL; + // + // check for new USE keyword... + // + int iSanityLimit = 0; + sstring_t sSearchName(sLevelName); + + while (sSearchName.c_str()[0] && iSanityLimit < 10) + { + gsLevelNameForLoad = sSearchName; + gsLevelNameForBossLoad = sSearchName; + pgThisLevelMusic = pgLevelMusic->FindSubGroup(sSearchName.c_str()); + + if (pgThisLevelMusic) + { + CGPValue *pValue = pgThisLevelMusic->FindPair(sKEY_USES); + if (pValue) + { + // re-search using the USE param... + // + sSearchName = pValue->GetTopValue(); + iSanityLimit++; +// Com_DPrintf("Using \"%s\"\n",sSearchName.c_str()); + } + else + { + // no new USE keyword found... + // + sSearchName = ""; + } + } + else + { + // level entry not found... + // + break; + } + } + + // now go ahead and use the final music set we've decided on... + // + if (pgThisLevelMusic && iSanityLimit < 10) + { + // these are optional fields, so see which ones we find... + // + LPCSTR psName_Explore = NULL; + LPCSTR psName_Action = NULL; + LPCSTR psName_Boss = NULL; + LPCSTR psName_Death = NULL; + // + LPCSTR psName_UseBoss = NULL; + + for (CGPValue *pValue = pgThisLevelMusic->GetPairs(); pValue; pValue = pValue->GetNext()) + { + LPCSTR psKey = pValue->GetName(); + LPCSTR psValue = pValue->GetTopValue(); + + if (Q_stricmp(psValue,sKEY_PLACEHOLDER)) // ignore "placeholder" items + { + if (!Q_stricmp(psKey,sKEY_EXPLORE)) + { + psName_Explore = psValue; + } + else + if (!Q_stricmp(psKey,sKEY_ACTION)) + { + psName_Action = psValue; + } + else + if (!Q_stricmp(psKey,sKEY_USEBOSS)) + { + psName_UseBoss = psValue; + } + else + if (!Q_stricmp(psKey,sKEY_BOSS)) + { + psName_Boss = psValue; + } + else + if (!Q_stricmp(psKey,sKEY_DEATH)) + { + psName_Death = psValue; + } + } + } + + bReturn = qtrue; // defualt to ON now, so I can turn it off if "useboss" fails + + if (psName_UseBoss) + { + CGPGroup *pgLevelMusicOfBoss = pgLevelMusic->FindSubGroup(psName_UseBoss); + if (pgLevelMusicOfBoss) + { + CGPValue *pValueBoss = pgLevelMusicOfBoss->FindPair(sKEY_BOSS); + if (pValueBoss) + { + psName_Boss = pValueBoss->GetTopValue(); + gsLevelNameForBossLoad = psName_UseBoss; + } + else + { + MUSIC_PARSE_ERROR(va("'useboss' \"%s\" has no \"boss\" entry!\n",psName_UseBoss)); + bReturn = qfalse; + } + } + else + { + MUSIC_PARSE_ERROR(va("Unable to find 'useboss' entry \"%s\"\n",psName_UseBoss)); + bReturn = qfalse; + } + } + + + // done this way in case I want to conditionally pass any bools depending on music type... + // + if (bReturn && psName_Explore) + { + bReturn = Music_ParseMusic(Parser, MusicData, pgMusicFiles, psName_Explore, sKEY_EXPLORE, eBGRNDTRACK_EXPLORE); + } + if (bReturn && psName_Action) + { + bReturn = Music_ParseMusic(Parser, MusicData, pgMusicFiles, psName_Action, sKEY_ACTION, eBGRNDTRACK_ACTION); + } + if (bReturn && psName_Boss) + { + bReturn = Music_ParseMusic(Parser, MusicData, pgMusicFiles, psName_Boss, sKEY_BOSS, eBGRNDTRACK_BOSS); + } + if (bReturn /*&& psName_Death*/) // LAST MINUTE HACK!!, always force in some death music!!!! + { + //bReturn = Music_ParseMusic(Parser, MusicData, pgMusicFiles, psName_Death, sKEY_DEATH, eBGRNDTRACK_DEATH); + + MusicFile_t m; + m.sFileNameBase = "death_music"; + (*MusicData)[ sKEY_DEATH ] = m; + } + } + else + { + MUSIC_PARSE_WARNING(va("Unable to find entry for \"%s\" in \"%s\"\n",sLevelName,sFILENAME_DMS)); + } + } + else + { + MUSIC_PARSE_ERROR(va("Unable to find subgroup \"%s\"\n",sKEY_LEVELMUSIC)); + } + } + else + { + MUSIC_PARSE_ERROR(va("Unable to find subgroup \"%s\"\n",sKEY_MUSICFILES)); + } + } + else + { + MUSIC_PARSE_ERROR( "Error calling GP2.GetBaseParseGroup()\n" ); + } + } + else + { + MUSIC_PARSE_ERROR( "Error using GP to parse file\n" ); + } + + Z_Free(psStrippedText); + FS_FreeFile( pText ); + } + else + { + MUSIC_PARSE_ERROR( "Unable to even read main file\n" ); // file name specified in error message + } + + if (bReturn) + { + // sort exit points, and do some error checking... + // + for (MusicData_t::iterator itMusicData = MusicData->begin(); itMusicData != MusicData->end(); ++itMusicData) + { + LPCSTR psMusicStateType = (*itMusicData).first.c_str(); + MusicFile_t &MusicFile = (*itMusicData).second; + + // kludge up an enum, only interested in boss or not at the moment, so... + // + MusicState_e eMusicState = !stricmp(psMusicStateType,"boss") ? eBGRNDTRACK_BOSS : !stricmp(psMusicStateType,"death") ? eBGRNDTRACK_DEATH : eBGRNDTRACK_EXPLORE; + + if (!MusicFile.MusicExitTimes.empty()) + { + sort(MusicFile.MusicExitTimes.begin(),MusicFile.MusicExitTimes.end()); + } + + // check music exists... + // + LPCSTR psMusicFileName = Music_BuildFileName( MusicFile.sFileNameBase.c_str(), eMusicState ); + if (!S_FileExists( psMusicFileName )) + { + MUSIC_PARSE_ERROR(va("Music file \"%s\" not found!\n",psMusicFileName)); + return qfalse; // have to return, because music data destroyed now + } + + // check all transition music pieces exist, and that entry points into new pieces after transitions also exist... + // + for (int iExitPoint=0; iExitPoint < MusicFile.MusicExitPoints.size(); iExitPoint++) + { + MusicExitPoint_t &MusicExitPoint = MusicFile.MusicExitPoints[ iExitPoint ]; + + LPCSTR psTransitionFileName = Music_BuildFileName( MusicExitPoint.sNextFile.c_str(), eMusicState ); + if (!S_FileExists( psTransitionFileName )) + { + MUSIC_PARSE_ERROR(va("Transition file \"%s\" (entry \"%s\" ) not found!\n",psTransitionFileName, MusicExitPoint.sNextFile.c_str())); + return qfalse; // have to return, because music data destroyed now + } + + LPCSTR psNextMark = MusicExitPoint.sNextMark.c_str(); + if (strlen(psNextMark)) // always NZ ptr + { + // then this must be "action" music under current rules... + // + assert( !strcmp(psMusicStateType, Music_BaseStateToString(eBGRNDTRACK_ACTION) ? Music_BaseStateToString(eBGRNDTRACK_ACTION):"") ); + // + // does this marker exist in the explore piece? + // + MusicData_t::iterator itExploreMusicData = MusicData->find( Music_BaseStateToString(eBGRNDTRACK_EXPLORE) ); + if (itExploreMusicData != MusicData->end()) + { + MusicFile_t &MusicFile_Explore = (*itExploreMusicData).second; + + if (!MusicFile_Explore.MusicEntryTimes.count(psNextMark)) + { + MUSIC_PARSE_ERROR( va("Unable to find entry point \"%s\" in description for \"%s\"\n",psNextMark,MusicFile_Explore.sFileNameBase.c_str()) ); + return qfalse; // have to return, because music data destroyed now + } + } + else + { + MUSIC_PARSE_ERROR( va("Unable to find %s piece to match \"%s\"\n", Music_BaseStateToString(eBGRNDTRACK_EXPLORE), MusicFile.sFileNameBase.c_str() ) ); + return qfalse; // have to return, because music data destroyed now + } + } + } + } + } + +#ifdef _DEBUG +/* + // dump the whole thing out to prove it was read in ok... + // + if (bReturn) + { + for (MusicData_t::iterator itMusicData = MusicData->begin(); itMusicData != MusicData->end(); ++itMusicData) + { + LPCSTR psMusicState = (*itMusicData).first.c_str(); + MusicFile_t &MusicFile = (*itMusicData).second; + + OutputDebugString(va("Music State: \"%s\", File: \"%s\"\n",psMusicState, MusicFile.sFileNameBase.c_str())); + + // entry times... + // + for (MusicEntryTimes_t::iterator itEntryTimes = MusicFile.MusicEntryTimes.begin(); itEntryTimes != MusicFile.MusicEntryTimes.end(); ++itEntryTimes) + { + LPCSTR psMarkerName = (*itEntryTimes).first.c_str(); + float fEntryTime = (*itEntryTimes).second; + + OutputDebugString(va("Entry time for \"%s\": %f\n", psMarkerName, fEntryTime)); + } + + // exit points... + // + for (int i=0; ifind( psMusicState ); + if (it != MusicData->end()) + { + MusicFile_t *pMusicFile = &(*it).second; + return pMusicFile; + } + + return NULL; +} + +static MusicFile_t *Music_GetBaseMusicFile( MusicState_e eMusicState ) +{ + LPCSTR psMusicStateString = Music_BaseStateToString( eMusicState ); + if ( psMusicStateString ) + { + return Music_GetBaseMusicFile( psMusicStateString ); + } + + return NULL; +} + + +// where label is (eg) "kejim_base"... +// +sboolean Music_DynamicDataAvailable(const char *psDynamicMusicLabel) +{ + char sLevelName[MAX_QPATH]; + Q_strncpyz(sLevelName,COM_SkipPath( const_cast( (psDynamicMusicLabel&&psDynamicMusicLabel[0])?psDynamicMusicLabel:gsLevelNameFromServer.c_str() ) ),sizeof(sLevelName)); + strlwr(sLevelName); + + if (strlen(sLevelName)) // avoid error messages when there's no music waiting to be played and we try and restart it... + { + if (Music_ParseLeveldata(sLevelName)) + { + return !!( Music_GetBaseMusicFile( eBGRNDTRACK_EXPLORE ) && + Music_GetBaseMusicFile( eBGRNDTRACK_ACTION ) + ); + } + } + + return qfalse; +} + +LPCSTR Music_GetFileNameForState( MusicState_e eMusicState) +{ + MusicFile_t *pMusicFile = NULL; + switch (eMusicState) + { + case eBGRNDTRACK_EXPLORE: + case eBGRNDTRACK_ACTION: + case eBGRNDTRACK_BOSS: + case eBGRNDTRACK_DEATH: + + pMusicFile = Music_GetBaseMusicFile( eMusicState ); + if (pMusicFile) + { + return Music_BuildFileName( pMusicFile->sFileNameBase.c_str(), eMusicState ); + } + break; + + case eBGRNDTRACK_ACTIONTRANS0: + case eBGRNDTRACK_ACTIONTRANS1: + case eBGRNDTRACK_ACTIONTRANS2: + case eBGRNDTRACK_ACTIONTRANS3: + + pMusicFile = Music_GetBaseMusicFile( eBGRNDTRACK_ACTION ); + if (pMusicFile) + { + int iTransNum = eMusicState - eBGRNDTRACK_ACTIONTRANS0; + if (iTransNum < pMusicFile->MusicExitPoints.size()) + { + return Music_BuildFileName( pMusicFile->MusicExitPoints[iTransNum].sNextFile.c_str(), eMusicState ); + } + } + break; + + case eBGRNDTRACK_EXPLORETRANS0: + case eBGRNDTRACK_EXPLORETRANS1: + case eBGRNDTRACK_EXPLORETRANS2: + case eBGRNDTRACK_EXPLORETRANS3: + + pMusicFile = Music_GetBaseMusicFile( eBGRNDTRACK_EXPLORE ); + if (pMusicFile) + { + int iTransNum = eMusicState - eBGRNDTRACK_EXPLORETRANS0; + if (iTransNum < pMusicFile->MusicExitPoints.size()) + { + return Music_BuildFileName( pMusicFile->MusicExitPoints[iTransNum].sNextFile.c_str(), eMusicState ); + } + } + break; + + default: + #ifndef FINAL_BUILD + assert(0); // duh....what state are they asking for? + Com_Printf( S_COLOR_RED "Music_GetFileNameForState( %d ) unhandled case!\n",eMusicState ); + #endif + break; + } + + return NULL; +} + + + +sboolean Music_StateIsTransition( MusicState_e eMusicState ) +{ + return (eMusicState >= eBGRNDTRACK_FIRSTTRANSITION && + eMusicState <= eBGRNDTRACK_LASTTRANSITION + ); +} + + +sboolean Music_StateCanBeInterrupted( MusicState_e eMusicState, MusicState_e eProposedMusicState ) +{ + // death music can interrupt anything... + // + if (eProposedMusicState == eBGRNDTRACK_DEATH) + return qtrue; + // + // ... and can't be interrupted once started...(though it will internally-switch to silence at the end, rather than loop) + // + if (eMusicState == eBGRNDTRACK_DEATH) + { + return qfalse; + } + + // boss music can interrupt anything (other than death, but that's already handled above)... + // + if (eProposedMusicState == eBGRNDTRACK_BOSS) + return qtrue; + // + // ... and can't be interrupted once started... + // + if (eMusicState == eBGRNDTRACK_BOSS) + { + // ...except by silence (or death, but again, that's already handled above) + // + if (eProposedMusicState == eBGRNDTRACK_SILENCE) + return qtrue; + + return qfalse; + } + + // action music can interrupt anything (after boss & death filters above)... + // + if (eProposedMusicState == eBGRNDTRACK_ACTION) + return qtrue; + + // nothing can interrupt a transition (after above filters)... + // + if (Music_StateIsTransition( eMusicState )) + return qfalse; + + // current state is therefore interruptable... + // + return qtrue; +} + + + +// returns qtrue if music is allowed to transition out of current state, based on current play position... +// (doesn't bother returning final state after transition (eg action->transition->explore) becuase it's fairly obvious) +// +// supply: +// +// playing point in float seconds +// enum of track being queried +// +// get: +// +// enum of transition track to switch to +// float time of entry point of new track *after* transition +// +sboolean Music_AllowedToTransition( float fPlayingTimeElapsed, + MusicState_e eMusicState, + // + MusicState_e *peTransition /* = NULL */, + float *pfNewTrackEntryTime /* = NULL */ + ) +{ + const float fTimeEpsilon = 0.3f; // arb., how close we have to be to an exit point to take it. + // if set too high then music change is sloppy + // if set too low[/precise] then we might miss an exit if client fps is poor + + + MusicFile_t *pMusicFile = Music_GetBaseMusicFile( eMusicState ); + if (pMusicFile && !pMusicFile->MusicExitTimes.empty()) + { + MusicExitTime_t T; + T.fTime = fPlayingTimeElapsed; + + // since a MusicExitTimes_t item is a sorted array, we can use the equal_range algorithm... + // + pair itp = equal_range( pMusicFile->MusicExitTimes.begin(), pMusicFile->MusicExitTimes.end(), T); + if (itp.first != pMusicFile->MusicExitTimes.begin()) + itp.first--; // encompass the one before, in case we've just missed an exit point by < fTimeEpsilon + if (itp.second!= pMusicFile->MusicExitTimes.end()) + itp.second++; // increase range to one beyond, so we can do normal STL being/end looping below + for (MusicExitTimes_t::iterator it = itp.first; it != itp.second; ++it) + { + MusicExitTimes_t::iterator pExitTime = it; + + if ( Q_fabs(pExitTime->fTime - fPlayingTimeElapsed) <= fTimeEpsilon ) + { + // got an exit point!, work out feedback params... + // + int iExitPoint = pExitTime->iExitPoint; + // + // the two params to give back... + // + MusicState_e eFeedBackTransition = eBGRNDTRACK_EXPLORETRANS0; // any old default + float fFeedBackNewTrackEntryTime = 0.0f; + // + // check legality in case of crap data... + // + if (iExitPoint < pMusicFile->MusicExitPoints.size()) + { + MusicExitPoint_t &ExitPoint = pMusicFile->MusicExitPoints[ iExitPoint ]; + + switch (eMusicState) + { + case eBGRNDTRACK_EXPLORE: + { + assert(iExitPoint < iMAX_EXPLORE_TRANSITIONS); // already been checked, but sanity + assert(!ExitPoint.sNextMark.c_str()[0]); // simple error checking, but harmless if tripped. explore transitions go to silence, hence no entry time for [silence] state after transition + + eFeedBackTransition = (MusicState_e) (eBGRNDTRACK_EXPLORETRANS0 + iExitPoint); + } + break; + + case eBGRNDTRACK_ACTION: + { + assert(iExitPoint < iMAX_ACTION_TRANSITIONS); // already been checked, but sanity + + // if there's an entry marker point defined... + // + if (ExitPoint.sNextMark.c_str()[0]) + { + MusicData_t::iterator itExploreMusicData = MusicData->find( Music_BaseStateToString(eBGRNDTRACK_EXPLORE) ); + // + // find "explore" music... + // + if (itExploreMusicData != MusicData->end()) + { + MusicFile_t &MusicFile_Explore = (*itExploreMusicData).second; + // + // find the entry marker within the music and read the time there... + // + MusicEntryTimes_t::iterator itEntryTime = MusicFile_Explore.MusicEntryTimes.find( ExitPoint.sNextMark.c_str() ); + if (itEntryTime != MusicFile_Explore.MusicEntryTimes.end()) + { + fFeedBackNewTrackEntryTime = (*itEntryTime).second; + eFeedBackTransition = (MusicState_e) (eBGRNDTRACK_ACTIONTRANS0 + iExitPoint); + } + else + { + #ifndef FINAL_BUILD + assert(0); // sanity, should have been caught elsewhere, but harmless to do this + Com_Printf( S_COLOR_RED "Music_AllowedToTransition() unable to find entry marker \"%s\" in \"%s\"",ExitPoint.sNextMark.c_str(), MusicFile_Explore.sFileNameBase.c_str()); + #endif + return qfalse; + } + } + else + { + #ifndef FINAL_BUILD + assert(0); // sanity, should have been caught elsewhere, but harmless to do this + Com_Printf( S_COLOR_RED "Music_AllowedToTransition() unable to find %s version of \"%s\"\n",Music_BaseStateToString(eBGRNDTRACK_EXPLORE), pMusicFile->sFileNameBase.c_str()); + #endif + return qfalse; + } + } + else + { + eFeedBackTransition = eBGRNDTRACK_ACTIONTRANS0; + fFeedBackNewTrackEntryTime = 0.0f; // already set to this, but FYI + } + } + break; + + default: + { + #ifndef FINAL_BUILD + assert(0); + Com_Printf( S_COLOR_RED "Music_AllowedToTransition(): No code to transition from music type %d\n",eMusicState); + #endif + return qfalse; + } + break; + } + } + else + { + #ifndef FINAL_BUILD + assert(0); + Com_Printf( S_COLOR_RED "Music_AllowedToTransition(): Illegal exit point %d, max = %d (music: \"%s\")\n",iExitPoint, pMusicFile->MusicExitPoints.size()-1, pMusicFile->sFileNameBase.c_str() ); + #endif + return qfalse; + } + + + // feed back answers... + // + if ( peTransition) + { + *peTransition = eFeedBackTransition; + } + + if ( pfNewTrackEntryTime ) + { + *pfNewTrackEntryTime = fFeedBackNewTrackEntryTime; + } + + return qtrue; + } + } + } + + return qfalse; +} + + +// typically used to get a (predefined) random entry point for the action music, but will work on any defined type with entry points, +// defaults safely to 0.0f if no info available... +// +float Music_GetRandomEntryTime( MusicState_e eMusicState ) +{ + MusicData_t::iterator itMusicData = MusicData->find( Music_BaseStateToString( eMusicState ) ); + if (itMusicData != MusicData->end()) + { + MusicFile_t &MusicFile = (*itMusicData).second; + + if (MusicFile.MusicEntryTimes.size()) // make sure at least one defined, else default to start + { + // Quake's random number generator isn't very good, so instead of this: + // + // int iRandomEntryNum = Q_irand(0, (MusicFile.MusicEntryTimes.size()-1) ); + // + // ... I'll do this (ensuring we don't get the same result on two consecutive calls, but without while-loop)... + // + static int iPrevRandomNumber = -1; + static int iCallCount = 0; + iCallCount++; + int iRandomEntryNum = (rand()+iCallCount) % (MusicFile.MusicEntryTimes.size()); // legal range + if (iRandomEntryNum == iPrevRandomNumber && MusicFile.MusicEntryTimes.size()>1) + { + iRandomEntryNum += 1; + iRandomEntryNum %= (MusicFile.MusicEntryTimes.size()); + } + iPrevRandomNumber = iRandomEntryNum; + +// OutputDebugString(va("Music_GetRandomEntryTime(): Entry %d\n",iRandomEntryNum)); + + for (MusicEntryTimes_t::iterator itEntryTime = MusicFile.MusicEntryTimes.begin(); itEntryTime != MusicFile.MusicEntryTimes.end(); ++itEntryTime) + { + if (!iRandomEntryNum--) + { + return (*itEntryTime).second; + } + } + } + } + + return 0.0f; +} + +// info only, used in "soundinfo" command... +// +const char *Music_GetLevelSetName(void) +{ + if (Q_stricmp(gsLevelNameForCompare.c_str(), gsLevelNameForLoad.c_str())) + { + // music remap via USES command... + // + return va("%s -> %s",gsLevelNameForCompare.c_str(), gsLevelNameForLoad.c_str()); + } + + return gsLevelNameForLoad.c_str(); +} + +///////////////// eof ///////////////////// + diff --git a/codemp/client/snd_music.h b/codemp/client/snd_music.h new file mode 100644 index 0000000..84f262b --- /dev/null +++ b/codemp/client/snd_music.h @@ -0,0 +1,65 @@ +// Filename:- snd_music.h +// +// + + + +#ifndef SND_MUSIC_H +#define SND_MUSIC_H + +// if you change this enum, you MUST update the #defines below +typedef enum +{ +//( eBGRNDTRACK_DATABEGIN ) // begin-label for FOR loops + // + eBGRNDTRACK_EXPLORE = 0, // for normal walking around + eBGRNDTRACK_ACTION, // for excitement + eBGRNDTRACK_BOSS, // (optional) for final encounter + eBGRNDTRACK_DEATH, // (optional) death "flourish" + eBGRNDTRACK_ACTIONTRANS0, // transition from action to explore + eBGRNDTRACK_ACTIONTRANS1, // " + eBGRNDTRACK_ACTIONTRANS2, // " + eBGRNDTRACK_ACTIONTRANS3, // " + eBGRNDTRACK_EXPLORETRANS0, // transition from explore to silence + eBGRNDTRACK_EXPLORETRANS1, // " + eBGRNDTRACK_EXPLORETRANS2, // " + eBGRNDTRACK_EXPLORETRANS3, // " + // +//( eBGRNDTRACK_DATAEND ), // tracks from this point on are for logic or copies, do NOT free them. + // + eBGRNDTRACK_NONDYNAMIC, // used for when music is just streaming, not part of dynamic stuff (used to be defined as same as explore entry, but this allows playing music in between 2 invokations of the same dynamic music without midleve reload, and also faster level transitioning if two consecutive dynamic sections use same DMS.DAT entries. Are you still reading this far? + eBGRNDTRACK_SILENCE, // silence (more of a logic thing than an actual track at the moment) + eBGRNDTRACK_FADE, // the xfade channel + // + eBGRNDTRACK_NUMBEROF + +} MusicState_e; + +#define iMAX_ACTION_TRANSITIONS 4 // these can be increased easily enough, I just need to know about them +#define iMAX_EXPLORE_TRANSITIONS 4 // + +#define eBGRNDTRACK_DATABEGIN eBGRNDTRACK_EXPLORE // label for FOR() loops (not in enum, else debugger shows in instead of the explore one unless I declare them backwards, which is gay) +#define eBGRNDTRACK_DATAEND eBGRNDTRACK_NONDYNAMIC // tracks from this point on are for logic or copies, do NOT free them. + +#define eBGRNDTRACK_FIRSTTRANSITION eBGRNDTRACK_ACTIONTRANS0 // used for "are we in transition mode" check +#define eBGRNDTRACK_LASTTRANSITION eBGRNDTRACK_EXPLORETRANS3 // + + +void Music_SetLevelName ( const char *psLevelName ); +sboolean Music_DynamicDataAvailable ( const char *psDynamicMusicLabel ); +const char *Music_GetFileNameForState ( MusicState_e eMusicState ); +sboolean Music_StateIsTransition ( MusicState_e eMusicState ); +sboolean Music_StateCanBeInterrupted ( MusicState_e eMusicState, MusicState_e eProposedMusicState ); +float Music_GetRandomEntryTime ( MusicState_e eMusicState ); + +sboolean Music_AllowedToTransition ( float fPlayingTimeElapsed, MusicState_e eMusicState, MusicState_e *peTransition = NULL, float *pfNewTrackEntryTime = NULL); + +const char *Music_BaseStateToString ( MusicState_e eMusicState, sboolean bDebugPrintQuery = qfalse); + + +#endif // #ifndef SND_MUSIC_H + + +//////////////// eof ///////////////// + + diff --git a/codemp/client/snd_public.h b/codemp/client/snd_public.h new file mode 100644 index 0000000..2e49dba --- /dev/null +++ b/codemp/client/snd_public.h @@ -0,0 +1,67 @@ +#ifndef _SND_PUBLIC_H +#define _SND_PUBLIC_H + +void S_Init( void ); +void S_Shutdown( void ); + +// if origin is NULL, the sound will be dynamically sourced from the entity +void S_AddAmbientLoopingSound( const vec3_t origin, unsigned char volume, sfxHandle_t sfxHandle ); +void S_StartAmbientSound( const vec3_t origin, int entityNum, unsigned char volume, sfxHandle_t sfxHandle ); +void S_MuteSound(int entityNum, int entchannel); +void S_StartSound( const vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); +void S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +void S_StartLocalLoopingSound( sfxHandle_t sfx); + +void S_UnCacheDynamicMusic( void ); +void S_RestartMusic( void ); +void S_StartBackgroundTrack( const char *intro, const char *loop, int bCalledByCGameStart ); +void S_StopBackgroundTrack( void ); +float S_GetSampleLengthInMilliSeconds( sfxHandle_t sfxHandle); + +// cinematics and voice-over-network will send raw samples +// 1.0 volume will be direct output of source samples +void S_RawSamples( int samples, int rate, int width, int s_channels, const byte *data, float volume, int bFirstOrOnlyUpdateThisFrame ); +// stop all sounds +void S_StopSounds(void); // from snd_dma.cpp +// stop all sounds and the background track +void S_StopAllSounds( void ); + +// scan all MP3s in the sound dir and add maxvol info if necessary. +void S_MP3_CalcVols_f( void ); + +// all continuous looping sounds must be added before calling S_Update +void S_ClearLoopingSounds( void ); +void S_StopLoopingSound( int entityNum ); +#ifdef _XBOX +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx, int chan = 0 ); +#else +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +#endif + +// recompute the reletive volumes for all running sounds +// relative to the given entityNum / orientation +void S_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ); + +// let the sound system know where an entity currently is +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +void S_Update( void ); + +void S_DisableSounds( void ); + +#ifdef _XBOX +void S_BeginRegistration( int num_listeners ); +#else +void S_BeginRegistration( void ); +#endif + +// RegisterSound will allways return a valid sample, even if it +// has to create a placeholder. This prevents continuous filesystem +// checks for missing files +sfxHandle_t S_RegisterSound( const char *sample ); + +extern qboolean s_shutUp; + +void S_FreeAllSFXMem(void); + +#endif diff --git a/codemp/encryption/buffer.cpp b/codemp/encryption/buffer.cpp new file mode 100644 index 0000000..e2c905c --- /dev/null +++ b/codemp/encryption/buffer.cpp @@ -0,0 +1,99 @@ +#include "encryption.h" + +#ifdef _ENCRYPTION_ + +#pragma warning (disable : 4514) //unref inline removed +#pragma warning (disable : 4711) //func selected for auto inline + +#include +#include +#include +#include "buffer.h" + +const int BufferIncrease = 1024; + +cBuffer::cBuffer(int InitIncrease) +:Buffer(NULL), + Size(0), + Pos(0), + ActualSize(0), + FreeNextAdd(false) +{ + Increase = InitIncrease; +} + +char *cBuffer::Add(char *Data, int Amount) +{ + if (FreeNextAdd) + { + Free(); + FreeNextAdd = false; + } + + if (Pos + Amount >= ActualSize) + { + ActualSize += Amount; + + ActualSize -= ActualSize % Increase; + ActualSize += Increase; + + Buffer = (char *)realloc(Buffer, ActualSize); + } + + if (Data) + { + memcpy(Buffer+Pos, Data, Amount); + } + + Pos += Amount; + Size += Amount; + + return Buffer + Pos - Amount; +} + +void cBuffer::Read(void) +{ + ResetPos(); +} + +char *cBuffer::Read(int Amount) +{ + Pos += Amount; + + if (Pos < Size) + { + return Buffer + Pos - Amount; + } + + return NULL; +} + +size_t cBuffer::strcspn(const char *notin) +{ + const char *s1, *s2; + + for (s1 = Buffer+Pos; *s1; s1++) + { + for(s2 = notin; *s2 != *s1 && *s2; s2++) + { + } + if (*s2) + { + break; + } + } + return s1 - Buffer + Pos; +} + +void cBuffer::Free(void) +{ + if (Buffer) + { + free(Buffer); + Buffer = NULL; + Size= 0; + Pos = 0; + ActualSize = 0; + } +} +#endif diff --git a/codemp/encryption/buffer.h b/codemp/encryption/buffer.h new file mode 100644 index 0000000..f4c4338 --- /dev/null +++ b/codemp/encryption/buffer.h @@ -0,0 +1,37 @@ +#ifndef __BUFFER_H +#define __BUFFER_H + +extern const int BufferIncrease; + +class cBuffer +{ +private: + char *Buffer; + int Size, ActualSize, Pos; + bool FreeNextAdd; + int Increase; + +public: + cBuffer(int InitIncrease = BufferIncrease); + ~cBuffer(void) { Free(); } + + const char *Get(void) { return Buffer; } + const char *GetWithPos(void) { return Buffer + Pos; } + const int GetSize(void) { return Size; } + const int GetRemaining(void) { return Size - Pos; } + + void FreeBeforeNextAdd(void) { FreeNextAdd = true; } + void ResetPos(void) { Pos = 0; } + void ResetSize(void) { Size = 0; } + + char *Add(char *Data, int Amount); + void Read(void); + char *Read(int Amount); + bool ValidPos(void) { return (Pos < Size); } + + size_t strcspn(const char *notin); + + void Free(void); +}; + +#endif // __BUFFER_H \ No newline at end of file diff --git a/codemp/encryption/cpp_interface.cpp b/codemp/encryption/cpp_interface.cpp new file mode 100644 index 0000000..0961e27 --- /dev/null +++ b/codemp/encryption/cpp_interface.cpp @@ -0,0 +1,419 @@ +#include "encryption.h" + +#ifdef _ENCRYPTION_ + +#pragma warning( disable : 4786) +#pragma warning( disable : 4100) +#pragma warning( disable : 4511) + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include +#include +#pragma warning (pop) + +#include "sockets.h" +#include "cpp_interface.h" + + +using namespace std; + + +extern "C" char *Cvar_VariableString( const char *var_name ); + + +const char *GOOD_REQUEST = "GRANTED"; +const char *BAD_REQUEST = "DENIED"; + + +static cWinsock Winsock; + + +class cAuto +{ +public: + cAuto(void); + ~cAuto(void); +}; + +cAuto::cAuto(void) +{ + Winsock.Init(); +}; + + +cAuto::~cAuto(void) +{ + Winsock.Shutdown(); +}; + + +static cAuto Auto; + +const int key_size = 4096; +const int user_size = 256; +const int buffer_size = 131072; +const unsigned short SERVER_PORT = 80; + + +unsigned char LastKey[key_size]; +bool LastKeyValid = false; + +class cEncryptedFile +{ +private: + unsigned char MainBuffer[buffer_size]; + int MainBufferPosition; + int MainBufferSize; + unsigned char Key[key_size]; + bool KeyValid; + string FileName; + long FileSize; + long CurrentPosition; + +public: + cEncryptedFile(char *InitFileName, long InitSize ); + + static cEncryptedFile *Create(char *FileName, unsigned char *InitKey = NULL); + + bool GetKey(char *KeyUser, char *KeyPassword); + void SetKey(unsigned char *NewKey); + + int Read(void *Buffer, int Position, int Amount, FILE *FH = NULL); + void SetPosition(long Offset, int origin); + long GetCurrentPosition(void) { return CurrentPosition; } +}; + +class cGameConnection : public cConnection +{ +private: + cEncryptedFile *File; + +public: + cGameConnection(cSocket *Init_Socket, cEncryptedFile *InitFile); + + virtual bool ReadCallback(void); + virtual bool WriteCallback(void); +}; + +cGameConnection::cGameConnection(cSocket *Init_Socket, cEncryptedFile *InitFile) +:cConnection(Init_Socket, NULL, false), + File(InitFile) +{ +} + +bool cGameConnection::ReadCallback(void) +{ + char *token; + bool GoodRequest = false; + bool ValidKey = false; + unsigned char *Key = NULL; + + token = strtok((char *)GetBuffer().Get(), " \n\r"); + while(token) + { + if (!strcmp(token, "Request:")) + { + token = strtok(NULL, "\n\r"); + if (token) + { + printf("Request: '%s'\n", token); + + if (!strcmp(token, GOOD_REQUEST)) + { + GoodRequest = true; + } + } + } + else if (!strcmp(token, "Key:")) + { + token = strtok(NULL, "\n\r"); + if (token) + { + if (token[0] == '\"' && strlen(token) == key_size+2 && token[key_size+1] == '\"') + { + ValidKey = true; + Key = (unsigned char *)token+1; + } + } + } + else + { + break; + } + token = strtok(NULL, " \n\r"); + } + + if (GoodRequest && ValidKey) + { + File->SetKey(Key); + } + + return true; +} + +bool cGameConnection::WriteCallback(void) +{ + Reading = true; + Buffer.FreeBeforeNextAdd(); + + return false; +} + + + + +cEncryptedFile::cEncryptedFile(char *InitFileName, long InitSize) +:FileName(InitFileName), FileSize(InitSize) +{ + MainBufferPosition = -1; + KeyValid = false; + CurrentPosition = 0; +} + +cEncryptedFile *cEncryptedFile::Create(char *FileName, unsigned char *InitKey) +{ + FILE *FH; + cEncryptedFile *new_file; + char userdata[user_size*2+2]; + char KeyUser[user_size], KeyPassword[user_size]; + long Size; + + FH = fopen(FileName, "rb"); + if (!FH) + { + return NULL; + } + + fseek(FH, -(long)sizeof(userdata), SEEK_END); + Size = ftell(FH); + + if (!InitKey) + { + fread(userdata, 1, sizeof(userdata), FH); + fclose(FH); + + strcpy(KeyUser, userdata); + strcpy(KeyPassword, &userdata[strlen(KeyUser)+1]); + + new_file = new cEncryptedFile(FileName, Size); + if (!new_file->GetKey(KeyUser, KeyPassword)) + { + delete new_file; + return NULL; + } + } + else + { + fclose(FH); + + new_file = new cEncryptedFile(FileName, Size); + new_file->SetKey(InitKey); + } + + return new_file; +} + +bool cEncryptedFile::GetKey(char *KeyUser, char *KeyPassword) +{ + cSocket *Socket; + cWinsock Winsock; + cGameConnection *Connection; + unsigned long size; + char temp[256]; + HKEY entry; + DWORD dwSize, dwType; + int value; + + Winsock.Init(); + + Socket = new cSocket; + Connection = new cGameConnection(Socket, this); + + if (Socket->Connect(204,97,248,145,SERVER_PORT)) + { + Connection->Print("User: %s\r\n", KeyUser); + Connection->Print("Password: %s\r\n", KeyPassword); + + size = sizeof(temp); + temp[0] = 0; + GetComputerName(temp, &size); + Connection->Print("Info: Computer Name '%s'\r\n", temp); + + size = sizeof(temp); + temp[0] = 0; + GetUserName(temp, &size); + Connection->Print("Info: Network User Name '%s'\r\n", temp); + + RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\MS Setup (ACME)\\User Info", NULL, KEY_READ, &entry); + dwSize = sizeof(temp); + temp[0] = 0; + value = RegQueryValueEx(entry, "DefCompany", NULL, &dwType, (unsigned char *)temp, &dwSize); + Connection->Print("Info: Company '%s'\r\n", temp); + + temp[0] = 0; + value = RegQueryValueEx(entry, "DefName", NULL, &dwType, (unsigned char *)temp, &dwSize); + Connection->Print("Info: Name '%s'\r\n", temp); + RegCloseKey(entry); + +extern int Sys_GetProcessorId( void ); + Connection->Print("Info: procId: 0x%x\r\n", Sys_GetProcessorId() ); + +#include "../qcommon/game_version.h" + Connection->Print("Info: Version '" Q3_VERSION " " __DATE__ "'\r\n"); + + Connection->Print("\r\n"); + while(!Connection->Handle()) + { + Sleep(50); + } + } + + delete Connection; // delete's Socket + + return KeyValid; +} + +void cEncryptedFile::SetKey(unsigned char *NewKey) +{ + if (!LastKeyValid) + { + memcpy(LastKey, NewKey, key_size); + LastKeyValid = true; + } + + memcpy(Key, NewKey, key_size); + KeyValid = true; +} + +int cEncryptedFile::Read(void *Buffer, int Position, int Amount, FILE *FH) +{ + int total; + unsigned char *pos; + int i; + int offset; + int size; + bool do_close = false; + + if (!KeyValid) + { + return -1; + } + + if (Position == -1) + { + Position = CurrentPosition; + } + else + { + CurrentPosition = Position; + } + + pos = (unsigned char *)Buffer; + total = 0; + offset = Position % key_size; + Position = Position - (Position % key_size); + while(Amount > 0) + { + if (MainBufferPosition == Position) + { + } + else + { + if (!FH) + { + FH = fopen(FileName.c_str(), "rb"); + if (!FH) + { + return -1; + } + + do_close = true; + } + fseek(FH, Position, SEEK_SET); + + MainBufferPosition = Position; + MainBufferSize = fread(MainBuffer, 1, buffer_size, FH); + for(i=0;i> 17) % key_size]; + } + } + + size = MainBufferSize - offset; + if (size < 0) + { + break; + } + if (size > Amount) + { + size = Amount; + } + + memcpy(pos, &MainBuffer[offset], size); + pos += size; + total += size; + Amount -= size; + + Position += MainBufferSize; + offset = 0; + } + + if (do_close) + { + fclose(FH); + } + + CurrentPosition += total; + + return total; +} + +void cEncryptedFile::SetPosition(long Offset, int origin) +{ + switch(origin) + { + case SEEK_SET: + CurrentPosition = Offset; + break; + case SEEK_CUR: + CurrentPosition += Offset; + break; + case SEEK_END: + CurrentPosition = FileSize - Offset; + break; + } +} + + +void *ENCRYPT_fopen(const char *Name, const char *Mode) +{ + cEncryptedFile *File; + + File = cEncryptedFile::Create((char *)Name, (LastKeyValid ? LastKey : NULL) ); + + return File; +} + +void ENCRYPT_fclose(void *File) +{ + delete (cEncryptedFile *)File; +} + +int ENCRYPT_fseek(void *File, long offset, int origin) +{ + ((cEncryptedFile *)File)->SetPosition(offset, origin); + + return 0; +} + +size_t ENCRYPT_fread(void *buffer, size_t size, size_t count, void *File) +{ + return ((cEncryptedFile *)File)->Read(buffer, -1, size*count) / size; +} + +long ENCRYPT_ftell(void *File) +{ + return ((cEncryptedFile *)File)->GetCurrentPosition(); +} +#endif diff --git a/codemp/encryption/cpp_interface.h b/codemp/encryption/cpp_interface.h new file mode 100644 index 0000000..523e547 --- /dev/null +++ b/codemp/encryption/cpp_interface.h @@ -0,0 +1,20 @@ +#ifndef __CPP_INTERFACE_H +#define __CPP_INTERFACE_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +void *ENCRYPT_fopen(const char *Name, const char *Mode); +void ENCRYPT_fclose(void *File); +int ENCRYPT_fseek(void *File, long offset, int origin); +size_t ENCRYPT_fread(void *buffer, size_t size, size_t count, void *File); +long ENCRYPT_ftell(void *File); + +#ifdef __cplusplus +} +#endif + + +#endif // __CPP_INTERFACE_H \ No newline at end of file diff --git a/codemp/encryption/encryption.h b/codemp/encryption/encryption.h new file mode 100644 index 0000000..0e92ffc --- /dev/null +++ b/codemp/encryption/encryption.h @@ -0,0 +1,6 @@ +//Pakinator Main Header + +//define this to enable all pak files encryption requirements +#ifdef FINAL_BUILD +//#define _ENCRYPTION_ +#endif \ No newline at end of file diff --git a/codemp/encryption/sockets.cpp b/codemp/encryption/sockets.cpp new file mode 100644 index 0000000..6d1a536 --- /dev/null +++ b/codemp/encryption/sockets.cpp @@ -0,0 +1,427 @@ +#include "encryption.h" + +#ifdef _ENCRYPTION_ + +#include +#include "sockets.h" + +class cWinsock Winsock; + + + +cSocket::cSocket(void) +{ + Socket = INVALID_SOCKET; +} + +cSocket::cSocket(SOCKET InitSocket) +:Socket(InitSocket) +{ +} + +cSocket::~cSocket(void) +{ + Free(); +} + +void cSocket::Free(void) +{ + if (Socket != INVALID_SOCKET) + { + shutdown(Socket, 2); // 2 = SD_BOTH which is defined for winsock2 + closesocket(Socket); + + Socket = INVALID_SOCKET; + } +} + +bool cSocket::socket(int af, int type, int protocol) +{ + Socket = ::socket(af, type, protocol); + + return (Socket != INVALID_SOCKET); +} + +bool cSocket::bind(const struct sockaddr FAR *name, int namelen) +{ + if (Socket == INVALID_SOCKET) + { + return false; + } + + return (::bind(Socket, name, namelen) != SOCKET_ERROR); +} + +bool cSocket::listen(int backlog) +{ + if (Socket == INVALID_SOCKET) + { + return false; + } + + return (::listen(Socket, backlog) != SOCKET_ERROR); +} + +SOCKET cSocket::accept(struct sockaddr FAR *addr, int FAR *addrlen) +{ + if (Socket == INVALID_SOCKET) + { + return INVALID_SOCKET; + } + + return ::accept(Socket, addr, addrlen); +} + +bool cSocket::connect(struct sockaddr FAR *addr, int FAR addrlen) +{ + if (Socket == INVALID_SOCKET) + { + return false; + } + + return (::connect(Socket, addr, addrlen) != SOCKET_ERROR); +} + +int cSocket::recv(char FAR *buf, int len, int flags) +{ + if (Socket == INVALID_SOCKET) + { + return INVALID_SOCKET; + } + + return ::recv(Socket, buf, len, flags); +} + +int cSocket::send(const char FAR *buf, int len, int flags) +{ + if (Socket == INVALID_SOCKET) + { + return INVALID_SOCKET; + } + + return ::send(Socket, buf, len, flags); +} + + + +bool cSocket::ioctlsocket(long cmd, u_long FAR *argp) +{ + if (Socket == INVALID_SOCKET) + { + return false; + } + + return (::ioctlsocket(Socket, cmd, argp) != SOCKET_ERROR); +} + +bool cSocket::setsockopt(int level, int optname, const char FAR *optval, int optlen) +{ + if (Socket == INVALID_SOCKET) + { + return false; + } + + return (::setsockopt(Socket, level, optname, optval, optlen) != SOCKET_ERROR); +} + +int cSocket::getsockopt(int level, int optname, char FAR *optval, int FAR *optlen) +{ + if (Socket == INVALID_SOCKET) + { + return false; + } + + return ::getsockopt(Socket, level, optname, optval, optlen); +} + +bool cSocket::getpeername(struct sockaddr FAR *name, int FAR *namelen) +{ + if (Socket == INVALID_SOCKET) + { + return false; + } + + return (::getpeername(Socket, name, namelen) != SOCKET_ERROR); +} + + +bool cSocket::Create(u_short Port) +{ + struct sockaddr_in addr; + + addr.sin_port = Winsock.htons(Port); + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_family = PF_INET; + + Free(); + + if (socket()) + { + if (bind((const struct sockaddr *)&addr, sizeof(addr))) + { + return true; + } + + Free(); + } + + return false; +} + +bool cSocket::Connect(unsigned char U1, unsigned char U2, unsigned char U3, unsigned char U4, unsigned short Port) +{ + struct sockaddr_in addr; + + addr.sin_port = Winsock.htons(Port); + addr.sin_addr.s_net = U1; + addr.sin_addr.s_host = U2; + addr.sin_addr.s_lh = U3; + addr.sin_addr.s_impno = U4; + addr.sin_family = PF_INET; + + Free(); + + if (socket()) + { + if (connect((struct sockaddr *)&addr, sizeof(addr))) + { + return true; + } + + Free(); + } + + return false; +} + +cSocket *cSocket::GetConnection(void) +{ + SOCKET new_socket; + + new_socket = accept(); + if (new_socket != INVALID_SOCKET) + { + return new cSocket(new_socket); + } + + return NULL; +} + +bool cSocket::SetBlocking(bool Enabled) +{ + unsigned long blockval = (Enabled != true); + + return ioctlsocket(FIONBIO, &blockval); +} + +bool cSocket::SetKeepAlive(bool Enabled) +{ + int keepaliveval = (Enabled == true); + + return setsockopt(SOL_SOCKET, SO_KEEPALIVE, (char*)&keepaliveval, sizeof(keepaliveval)); +} + +bool cSocket::SetLinger(bool Enabled, u_short TimeLimit) +{ + struct linger lingerval; + + lingerval.l_onoff = (Enabled == true); + lingerval.l_linger = TimeLimit; + + return setsockopt(SOL_SOCKET, SO_LINGER, (char*)&lingerval, sizeof(lingerval)); +} + +bool cSocket::SetSendBufferSize(int Size) +{ + return setsockopt(SOL_SOCKET, SO_SNDBUF, (char*)&Size, sizeof(Size)); +} + + + + + + + + + + + + + + + + + + +cConnection::cConnection(cSocket *Init_Socket, Connect_Callback InitCallback, bool InitReading) +:Socket(Init_Socket), + Callback(InitCallback), + Reading(InitReading) +{ + BufferReset = false; +} + +cConnection::~cConnection(void) +{ + if (Socket) + { + delete Socket; + } +} + +void cConnection::Print(char *Format, ...) +{ + char temp[1024]; + va_list argptr; + int size; + + va_start (argptr, Format); + size = _vsnprintf (temp, sizeof(temp), Format, argptr); + va_end (argptr); + + Buffer.Add(temp, size); +} + +bool cConnection::Read(void) +{ + char temp[8193]; + int size; + + size = Socket->recv(temp, sizeof(temp)-1); + if (size >= 0) + { + Buffer.Add(temp, size); + + temp[size] = 0; + } + if (Buffer.Get() && strstr(Buffer.Get(), "\r\n\r\n")) + { // found the blank line + temp[0] = 0; + Buffer.Add(temp, 1); + + Reading = false; + Buffer.FreeBeforeNextAdd(); + + if (Callback) + { + Callback(this); + } + return ReadCallback(); + } + + return false; +} + +bool cConnection::Write(void) +{ + int size; + + if (!BufferReset) + { + BufferReset = true; + Buffer.Read(); + } + + size = Socket->send(Buffer.GetWithPos(), Buffer.GetRemaining() ); + if (size != SOCKET_ERROR) + { + if (!Buffer.Read(size)) + { + return WriteCallback(); + } + } + + return false; +} + +bool cConnection::Handle(void) +{ + if (Reading) + { + return Read(); + } + else + { + return Write(); + } +} + + + + + + + + + + + + + + + + + + + + + + + + +cWinsock::cWinsock(void) +{ + WinsockStarted = false; +} + +cWinsock::~cWinsock(void) +{ + Shutdown(); +} + +void cWinsock::Init(void) +{ + WSADATA dat; + int rv; + + if (WinsockStarted) + { + return; + } + +#ifdef _USE_WINSOCK2_ + rv = WSAStartup(MAKEWORD(2,0), &dat); +#else + rv = WSAStartup(MAKEWORD(1,1), &dat); +#endif + + if (rv == 0) + { + WinsockStarted = true; + } +} + +void cWinsock::Shutdown(void) +{ + if (WinsockStarted) + { + WSACleanup(); + WinsockStarted = false; + } +} + +struct servent FAR *cWinsock::getservbyname(const char FAR *name, const char FAR *proto) +{ + return ::getservbyname(name, proto); +} + +u_short cWinsock::htons(u_short hostshort) +{ + return ::htons(hostshort); +} + +struct hostent FAR *cWinsock::gethostbyaddr(const char FAR *addr, int len, int type) +{ + return ::gethostbyaddr(addr, len, type); +} +#endif diff --git a/codemp/encryption/sockets.h b/codemp/encryption/sockets.h new file mode 100644 index 0000000..3249c11 --- /dev/null +++ b/codemp/encryption/sockets.h @@ -0,0 +1,95 @@ +#ifndef __SOCKETS_H +#define __SOCKETS_H + +#include +#include +#include "buffer.h" + +class cSocket +{ +private: + SOCKET Socket; + +public: + cSocket(void); + cSocket(SOCKET InitSocket); + ~cSocket(void); + + const SOCKET GetSocket(void) { return Socket; } + + void Free(void); + + // straight winsock commands + bool socket(int af = PF_INET, int type = SOCK_STREAM, int protocol = IPPROTO_TCP); + bool bind(const struct sockaddr FAR *name, int namelen); + bool listen(int backlog = 5); + SOCKET accept(struct sockaddr FAR *addr = NULL, int FAR *addrlen = 0); + bool connect(struct sockaddr FAR *addr, int FAR addrlen); + int recv(char FAR *buf, int len, int flags = 0); + int send(const char FAR *buf, int len, int flags = 0); + + bool ioctlsocket(long cmd, u_long FAR *argp); + bool setsockopt(int level, int optname, const char FAR *optval, int optlen); + int getsockopt(int level, int optname, char FAR *optval, int FAR *optlen); + bool getpeername(struct sockaddr FAR *name, int FAR *namelen); + bool getpeername(struct sockaddr_in FAR *name, int FAR *namelen) + { + return getpeername((struct sockaddr FAR *)name, namelen); + } + + // convience functions + bool Create(u_short Port); + bool Connect(unsigned char u1, unsigned char u2, unsigned char u3, unsigned char u4, unsigned short port); + cSocket *GetConnection(void); + + bool SetBlocking(bool Enabled = false); + bool SetKeepAlive(bool Enabled = false); + bool SetLinger(bool Enabled = true, u_short TimeLimit = 0); + bool SetSendBufferSize(int Size); +}; + +class cConnection; + +typedef void (*Connect_Callback)(cConnection *); + +class cConnection +{ +protected: + cSocket *Socket; + bool Reading; + bool BufferReset; + cBuffer Buffer; + Connect_Callback Callback; + +public: + cConnection(cSocket *Init_Socket, Connect_Callback InitCallback = NULL, bool InitReading = true); + ~cConnection(void); + + cBuffer &GetBuffer(void) { return Buffer; } + void Print(char *Format, ...); + bool Write(void); + bool Read(void); + bool Handle(void); + + virtual bool ReadCallback(void) { return true; } + virtual bool WriteCallback(void) { return true; } +}; + +class cWinsock +{ +private: + bool WinsockStarted; + +public: + cWinsock(void); + ~cWinsock(void); + + void Init(void); + void Shutdown(void); + + struct servent FAR *getservbyname(const char FAR *name, const char FAR *proto = "tcp"); + u_short htons(u_short hostshort); + struct hostent FAR *gethostbyaddr(const char FAR *addr, int len, int type = PF_INET); +}; + +#endif // __SOCKETS_H diff --git a/codemp/ff/ff_console.cpp b/codemp/ff/ff_console.cpp new file mode 100644 index 0000000..c6200d9 --- /dev/null +++ b/codemp/ff/ff_console.cpp @@ -0,0 +1,287 @@ +/* + * Stubs to allow linking with FF_ fnuctions declared. + * Brian Osman + */ + +//JLFRUMBLE includes modified to avoid typename collision field_t +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../client/keycodes.h" +//#include "../client/client.h" +#include "../client/fffx.h" +#include "../win32/win_input.h" +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" + +void FF_StopAll(void) +{ + Com_Printf("FF_StopAll: Please implement.\n"); + // Do nothing +} + +void FF_Stop(ffFX_e effect) +{ + Com_Printf("FF_Stop: Please implement fffx_id = %i\n",effect); + // Do nothing +} + +void FF_EnsurePlaying(ffFX_e effect) +{ + Com_Printf("FF_EnsurePlaying: Please implement fffx_id = %i\n",effect); + // Do nothing +} + +void FF_Play(ffFX_e effect) +{ + int s; // script id + static int const_rumble[2] = {-1, -1}; // script id for constant rumble + int client; + + // super huge switch for rumble effects + switch(effect) + { + case fffx_AircraftCarrierTakeOff: + case fffx_BasketballDribble: + case fffx_CarEngineIdle: + case fffx_ChainsawIdle: + case fffx_ChainsawInAction: + case fffx_DieselEngineIdle: + case fffx_Jump: + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 50000, 10000, 200); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Land: + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 50000, 10000, 200); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_MachineGun: + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 56000, 20000, 230); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Punched: + case fffx_RocketLaunch: + + case fffx_SecretDoor: + case fffx_SwitchClick: // used by saber + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 1, true); + if (s != -1) + { + IN_AddRumbleState(s, 30000, 10000, 120); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_WindGust: + case fffx_WindShear: + case fffx_Pistol: + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 50000, 10000, 200); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Shotgun: + case fffx_Laser1: + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 32000, 32000, 75); + IN_AddRumbleState(s, 0, 0, 15); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Laser2: + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 25000, 25000, 75); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Laser3: + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 35000, 35000, 100); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Laser4: + case fffx_Laser5: + case fffx_Laser6: + case fffx_OutOfAmmo: + case fffx_LightningGun: + case fffx_Missile: + case fffx_GatlingGun: + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 39000, 0, 220); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_ShortPlasma: + case fffx_PlasmaCannon1: + case fffx_PlasmaCannon2: + case fffx_Cannon: + case fffx_FallingShort: + case fffx_FallingMedium: + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 1, true); + if (s != -1) + { + IN_AddRumbleState(s, 25000,10000, 230); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_FallingFar: + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 1, true); + if (s != -1) + { + IN_AddRumbleState(s, 32000,10000, 230); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_StartConst: + client = ClientManager::ActiveClientNum(); + if(const_rumble[client] == -1) + { + const_rumble[client] = IN_CreateRumbleScript(ClientManager::ActiveController(), 4, true); + if (const_rumble[client] != -1) + { + IN_AddEffectFade4(const_rumble[client], 0,0, 50000, 50000, 1000); + //IN_AddRumbleState(const_rumble[client], 50000, 0, 300); + //IN_AddEffectFade4(const_rumble[client], 50000,50000, 0, 0, 1000); + IN_ExecuteRumbleScript(const_rumble[client]); + } + } + break; + case fffx_StopConst: + client = ClientManager::ActiveClientNum(); + if (const_rumble[client] == -1) + return; + IN_KillRumbleScript(const_rumble[client]); + const_rumble[client] = -1; + break; + default: + Com_Printf("No rumble script is defined for fffx_id = %i\n",effect); + break; + } +} + +/********* +FF_XboxShake + +intensity - speed of rumble +duration - length of rumble +*********/ +#define FF_SH_MIN_MOTOR_SPEED 20000 +#define FF_SH_MOTOR_SPEED_MODIFIER (65535 - FF_SH_MIN_MOTOR_SPEED) +void FF_XboxShake(float intensity, int duration) +{ + int s; + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 1, true); + if (s != -1) + { + int speed; + // figure out the speed + speed = (FF_SH_MIN_MOTOR_SPEED) + (FF_SH_MOTOR_SPEED_MODIFIER * intensity); + + // Add the state and execute + IN_AddRumbleState(s, speed, speed, duration); + IN_ExecuteRumbleScript(s); + } +} + +/********* +FF_XboxDamage + +damage - Amount of damage +xpos - x position for the damage ( -1.0 - 1.0 ) + +The following function various the rumble based upon +the amount of damage and the position of the damage. +*********/ +#define FF_DA_MIN_MOTOR_SPEED 20000 // use this to vary the minimum intensity +#define FF_DA_MOTOR_SPEED_MODIFIER (65535 - FF_DA_MIN_MOTOR_SPEED) +void FF_XboxDamage(int damage, float xpos) +{ + int s; + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 1, true); + if (s != -1) + { + int leftMotorSpeed; + int rightMotorSpeed; + int duration; + float per; + + duration = 175; + + // how much damage? + if(damage > 100) + { + per = 1.0; + } + else + { + per = damage/100; + } + + if(xpos >= -0.2 && xpos <= 0.2) // damge to center + { + leftMotorSpeed = rightMotorSpeed = (FF_DA_MIN_MOTOR_SPEED)+(FF_DA_MOTOR_SPEED_MODIFIER * per); + } + else if(xpos > 0.2) // damage to right + { + rightMotorSpeed = (FF_DA_MIN_MOTOR_SPEED)+(FF_DA_MOTOR_SPEED_MODIFIER * per); + leftMotorSpeed = 0; + } + else // damage to left + { + leftMotorSpeed = (FF_DA_MIN_MOTOR_SPEED)+(FF_DA_MOTOR_SPEED_MODIFIER * per);; + rightMotorSpeed = 0; + } + + // Add the state and execute + IN_AddRumbleState(s, leftMotorSpeed, rightMotorSpeed, duration); + IN_ExecuteRumbleScript(s); + } +} + +void FF_XboxSaberRumble( void ) +{ + + int s; + s = IN_CreateRumbleScript(ClientManager::ActiveController(), 1, true); + if (s != -1) + { + // Add the state and execute + IN_AddRumbleState(s, 55000, 55000, 100); + IN_ExecuteRumbleScript(s); + } +} + diff --git a/codemp/game/AnimalNPC.c b/codemp/game/AnimalNPC.c new file mode 100644 index 0000000..fdb6b68 --- /dev/null +++ b/codemp/game/AnimalNPC.c @@ -0,0 +1,946 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +#endif + +#ifdef QAGAME //we only want a few of these functions for BG + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifndef _JK2MP +extern void CG_ChangeWeapon( int num ); +#endif + +extern void Vehicle_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_VehicleTrace( trace_t *results, const vec3_t start, const vec3_t tMins, const vec3_t tMaxs, const vec3_t end, int passEntityNum, int contentmask ); + +// Update death sequence. +static void DeathUpdate( Vehicle_t *pVeh ) +{ + if ( level.time >= pVeh->m_iDieTime ) + { + // If the vehicle is not empty. + if ( pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) + { + pVeh->m_pVehicleInfo->EjectAll( pVeh ); + } + else + { + // Waste this sucker. + } + + // Die now... +/* else + { + vec3_t mins, maxs, bottom; + trace_t trace; + + if ( pVeh->m_pVehicleInfo->explodeFX ) + { + G_PlayEffect( pVeh->m_pVehicleInfo->explodeFX, parent->currentOrigin ); + //trace down and place mark + VectorCopy( parent->currentOrigin, bottom ); + bottom[2] -= 80; + gi.trace( &trace, parent->currentOrigin, vec3_origin, vec3_origin, bottom, parent->s.number, CONTENTS_SOLID ); + if ( trace.fraction < 1.0f ) + { + VectorCopy( trace.endpos, bottom ); + bottom[2] += 2; + G_PlayEffect( "ships/ship_explosion_mark", trace.endpos ); + } + } + + parent->takedamage = qfalse;//so we don't recursively damage ourselves + if ( pVeh->m_pVehicleInfo->explosionRadius > 0 && pVeh->m_pVehicleInfo->explosionDamage > 0 ) + { + VectorCopy( parent->mins, mins ); + mins[2] = -4;//to keep it off the ground a *little* + VectorCopy( parent->maxs, maxs ); + VectorCopy( parent->currentOrigin, bottom ); + bottom[2] += parent->mins[2] - 32; + gi.trace( &trace, parent->currentOrigin, mins, maxs, bottom, parent->s.number, CONTENTS_SOLID ); + G_RadiusDamage( trace.endpos, NULL, pVeh->m_pVehicleInfo->explosionDamage, pVeh->m_pVehicleInfo->explosionRadius, NULL, MOD_EXPLOSIVE );//FIXME: extern damage and radius or base on fuel + } + + parent->e_ThinkFunc = thinkF_G_FreeEntity; + parent->nextthink = level.time + FRAMETIME; + }*/ + } +} + +// Like a think or move command, this updates various vehicle properties. +static bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + return g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ); +} +#endif //QAGAME + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + float fWalkSpeedMax; + int curTime; + bgEntity_t *parent = pVeh->m_pParentEntity; +#ifdef _JK2MP + playerState_t *parentPS = parent->playerState; +#else + playerState_t *parentPS = &parent->client->ps; +#endif + +#ifndef _JK2MP//SP + curTime = level.time; +#elif QAGAME//MP GAME + curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + curTime = pm->cmd.serverTime; +#endif + + +#ifndef _JK2MP //bad for prediction - fixme + // Bucking so we can't do anything. + if ( pVeh->m_ulFlags & VEH_BUCKING || pVeh->m_ulFlags & VEH_FLYING || pVeh->m_ulFlags & VEH_CRASHING ) + { +//#ifdef QAGAME //this was in Update above +// ((gentity_t *)parent)->client->ps.speed = 0; +//#endif + parentPS->speed = 0; + return; + } +#endif + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + speedMax = pVeh->m_pVehicleInfo->speedMax; + + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + + + + if ( pVeh->m_pPilot /*&& (pilotPS->weapon == WP_NONE || pilotPS->weapon == WP_MELEE )*/ && + (pVeh->m_ucmd.buttons & BUTTON_ALT_ATTACK) && pVeh->m_pVehicleInfo->turboSpeed ) + { + if ((curTime - pVeh->m_iTurboTime)>pVeh->m_pVehicleInfo->turboRecharge) + { + pVeh->m_iTurboTime = (curTime + pVeh->m_pVehicleInfo->turboDuration); +#ifndef _JK2MP //kill me now + if (pVeh->m_pVehicleInfo->soundTurbo) + { + G_SoundIndexOnEnt(pVeh->m_pParentEntity, CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo); + } +#endif + parentPS->speed = pVeh->m_pVehicleInfo->turboSpeed; // Instantly Jump To Turbo Speed + } + } + + if ( curTime < pVeh->m_iTurboTime ) + { + speedMax = pVeh->m_pVehicleInfo->turboSpeed; + } + else + { + speedMax = pVeh->m_pVehicleInfo->speedMax; + } + +#ifdef _JK2MP + if ( !parentPS->m_iVehicleNum ) +#else + if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//drifts to a stop + speedInc = speedIdle * pVeh->m_fTimeModifier; + VectorClear( parentPS->moveDir ); + //m_ucmd.forwardmove = 127; + parentPS->speed = 0; + } + else + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + + if ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + parentPS->speed -= speedIdleDec; + } + } + // No input, so coast to stop. + else if ( parentPS->speed > 0.0f ) + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < 0.0f ) + { + parentPS->speed = 0.0f; + } + } + else if ( parentPS->speed < 0.0f ) + { + parentPS->speed += speedIdleDec; + if ( parentPS->speed > 0.0f ) + { + parentPS->speed = 0.0f; + } + } + } + else + { + if ( pVeh->m_ucmd.forwardmove < 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + if ( pVeh->m_ucmd.upmove < 0 ) + { + pVeh->m_ucmd.upmove = 0; + } + + //pVeh->m_ucmd.rightmove = 0; + + /*if ( !pVeh->m_pVehicleInfo->strafePerc + || (!g_speederControlScheme->value && !parent->s.number) ) + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + pVeh->m_ucmd.rightmove = 0; + }*/ + } + + fWalkSpeedMax = speedMax * 0.275f; + if ( curTime>pVeh->m_iTurboTime && (pVeh->m_ucmd.buttons & BUTTON_WALKING) && parentPS->speed > fWalkSpeedMax ) + { + parentPS->speed = fWalkSpeedMax; + } + else if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessOrientCommands the Vehicle. +static void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + bgEntity_t *parent = pVeh->m_pParentEntity; + playerState_t *parentPS, *riderPS; + +#ifdef _JK2MP + bgEntity_t *rider = NULL; + if (parent->s.owner != ENTITYNUM_NONE) + { + rider = PM_BGEntForNum(parent->s.owner); //&g_entities[parent->r.ownerNum]; + } +#else + gentity_t *rider = parent->owner; +#endif + + // Bucking so we can't do anything. +#ifndef _JK2MP //bad for prediction - fixme + if ( pVeh->m_ulFlags & VEH_BUCKING || pVeh->m_ulFlags & VEH_FLYING || pVeh->m_ulFlags & VEH_CRASHING ) + { + return; + } +#endif + +#ifdef _JK2MP + if ( !rider ) +#else + if ( !rider || !rider->client ) +#endif + { + rider = parent; + } + + + +#ifdef _JK2MP + parentPS = parent->playerState; + riderPS = rider->playerState; +#else + parentPS = &parent->client->ps; + riderPS = &rider->client->ps; +#endif + + if (rider) + { +#ifdef _JK2MP + float angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*4.0f; //magic number hackery + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; +#endif + } + + +/* speed = VectorLength( parentPS->velocity ); + + // If the player is the rider... + if ( rider->s.number < MAX_CLIENTS ) + {//FIXME: use the vehicle's turning stat in this calc + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + } + else + { + float turnSpeed = pVeh->m_pVehicleInfo->turningSpeed; + if ( !pVeh->m_pVehicleInfo->turnWhenStopped + && !parentPS->speed )//FIXME: or !pVeh->m_ucmd.forwardmove? + {//can't turn when not moving + //FIXME: or ramp up to max turnSpeed? + turnSpeed = 0.0f; + } +#ifdef _JK2MP + if (rider->s.eType == ET_NPC) +#else + if ( !rider || rider->NPC ) +#endif + {//help NPCs out some + turnSpeed *= 2.0f; +#ifdef _JK2MP + if (parentPS->speed > 200.0f) +#else + if ( parent->client->ps.speed > 200.0f ) +#endif + { + turnSpeed += turnSpeed * parentPS->speed/200.0f*0.05f; + } + } + turnSpeed *= pVeh->m_fTimeModifier; + + //default control scheme: strafing turns, mouselook aims + if ( pVeh->m_ucmd.rightmove < 0 ) + { + pVeh->m_vOrientation[YAW] += turnSpeed; + } + else if ( pVeh->m_ucmd.rightmove > 0 ) + { + pVeh->m_vOrientation[YAW] -= turnSpeed; + } + + if ( pVeh->m_pVehicleInfo->malfunctionArmorLevel && pVeh->m_iArmor <= pVeh->m_pVehicleInfo->malfunctionArmorLevel ) + {//damaged badly + } + }*/ + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef _JK2MP //temp hack til mp speeder controls are sorted -rww +void AnimalProcessOri(Vehicle_t *pVeh) +{ + ProcessOrientCommands(pVeh); +} +#endif + +#ifdef QAGAME //back to our game-only functions +static void AnimateVehicle( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_VT_IDLE; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + gentity_t * pilot = (gentity_t *)pVeh->m_pPilot; + gentity_t * parent = (gentity_t *)pVeh->m_pParentEntity; + playerState_t * pilotPS; + playerState_t * parentPS; + float fSpeedPercToMax; + +#ifdef _JK2MP + pilotPS = (pilot)?(pilot->playerState):(0); + parentPS = parent->playerState; +#else + pilotPS = (pilot)?(&pilot->client->ps):(0); + parentPS = &parent->client->ps; +#endif + + // We're dead (boarding is reused here so I don't have to make another variable :-). + if ( parent->health <= 0 ) + { + /* + if ( pVeh->m_iBoarding != -999 ) // Animate the death just once! + { + pVeh->m_iBoarding = -999; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + // FIXME! Why do you keep repeating over and over!!?!?!? Bastard! + //Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_DEATH1, iFlags, iBlend ); + } + */ + return; + } + + // If they're bucking, play the animation and leave... + if ( parent->client->ps.legsAnim == BOTH_VT_BUCK ) + { + // Done with animation? Erase the flag. + if ( parent->client->ps.legsAnimTimer <= 0 ) + { + pVeh->m_ulFlags &= ~VEH_BUCKING; + } + else + { + return; + } + } + else if ( pVeh->m_ulFlags & VEH_BUCKING ) + { + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + Anim = BOTH_VT_BUCK; + iBlend = 500; + Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_BUCK, iFlags, iBlend ); + return; + } + + // Boarding animation. + if ( pVeh->m_iBoarding != 0 ) + { + // We've just started boarding, set the amount of time it will take to finish boarding. + if ( pVeh->m_iBoarding < 0 ) + { + int iAnimLen; + + // Boarding from left... + if ( pVeh->m_iBoarding == -1 ) + { + Anim = BOTH_VT_MOUNT_L; + } + else if ( pVeh->m_iBoarding == -2 ) + { + Anim = BOTH_VT_MOUNT_R; + } + else if ( pVeh->m_iBoarding == -3 ) + { + Anim = BOTH_VT_MOUNT_B; + } + + // Set the delay time (which happens to be the time it takes for the animation to complete). + // NOTE: Here I made it so the delay is actually 70% (0.7f) of the animation time. +#ifdef _JK2MP + iAnimLen = BG_AnimLength( parent->localAnimIndex, Anim ) * 0.7f; +#else + iAnimLen = PM_AnimLength( parent->client->clientInfo.animFileIndex, Anim ) * 0.7f; +#endif + pVeh->m_iBoarding = level.time + iAnimLen; + + // Set the animation, which won't be interrupted until it's completed. + // TODO: But what if he's killed? Should the animation remain persistant??? + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); + if (pilot) + { + Vehicle_SetAnim(pilot, SETANIM_BOTH, Anim, iFlags, iBlend ); + } + return; + } + // Otherwise we're done. + else if ( pVeh->m_iBoarding <= level.time ) + { + pVeh->m_iBoarding = 0; + } + } + + // Percentage of maximum speed relative to current speed. + //float fSpeed = VectorLength( client->ps.velocity ); + fSpeedPercToMax = parent->client->ps.speed / pVeh->m_pVehicleInfo->speedMax; + + + // Going in reverse... + if ( fSpeedPercToMax < -0.01f ) + { + Anim = BOTH_VT_WALK_REV; + iBlend = 600; + } + else + { + bool Turbo = (fSpeedPercToMax>0.0f && level.timem_iTurboTime); + bool Walking = (fSpeedPercToMax>0.0f && ((pVeh->m_ucmd.buttons&BUTTON_WALKING) || fSpeedPercToMax<=0.275f)); + bool Running = (fSpeedPercToMax>0.275f); + + + // Remove Crashing Flag + //---------------------- + pVeh->m_ulFlags &= ~VEH_CRASHING; + + if (Turbo) + {// Kicked In Turbo + iBlend = 50; + iFlags = SETANIM_FLAG_OVERRIDE; + Anim = BOTH_VT_TURBO; + } + else + {// No Special Moves + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + Anim = (Walking)?(BOTH_VT_WALK_FWD ):((Running)?(BOTH_VT_RUN_FWD ):(BOTH_VT_IDLE1)); + } + } + Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); +} + +//rwwFIXMEFIXME: This is all going to have to be predicted I think, or it will feel awful +//and lagged +// This function makes sure that the rider's in this vehicle are properly animated. +static void AnimateRiders( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_VT_IDLE; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 500; + gentity_t *pilot = (gentity_t *)pVeh->m_pPilot; + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + playerState_t *pilotPS; + playerState_t *parentPS; + float fSpeedPercToMax; + +#ifdef _JK2MP + pilotPS = pVeh->m_pPilot->playerState; + parentPS = pVeh->m_pPilot->playerState; +#else + pilotPS = &pVeh->m_pPilot->client->ps; + parentPS = &pVeh->m_pParentEntity->client->ps; +#endif + + // Boarding animation. + if ( pVeh->m_iBoarding != 0 ) + { + return; + } + + // Percentage of maximum speed relative to current speed. + fSpeedPercToMax = parent->client->ps.speed / pVeh->m_pVehicleInfo->speedMax; + + // Going in reverse... +#ifdef _JK2MP //handled in pmove in mp + if (0) +#else + if ( fSpeedPercToMax < -0.01f ) +#endif + { + Anim = BOTH_VT_WALK_REV; + iBlend = 600; + } + else + { + bool HasWeapon = ((pilotPS->weapon != WP_NONE) && (pilotPS->weapon != WP_MELEE)); + bool Attacking = (HasWeapon && !!(pVeh->m_ucmd.buttons&BUTTON_ATTACK)); + bool Right = (pVeh->m_ucmd.rightmove>0); + bool Left = (pVeh->m_ucmd.rightmove<0); + bool Turbo = (fSpeedPercToMax>0.0f && level.timem_iTurboTime); + bool Walking = (fSpeedPercToMax>0.0f && ((pVeh->m_ucmd.buttons&BUTTON_WALKING) || fSpeedPercToMax<=0.275f)); + bool Running = (fSpeedPercToMax>0.275f); + EWeaponPose WeaponPose = WPOSE_NONE; + + + // Remove Crashing Flag + //---------------------- + pVeh->m_ulFlags &= ~VEH_CRASHING; + + + // Put Away Saber When It Is Not Active + //-------------------------------------- +#ifndef _JK2MP + if (HasWeapon && (Turbo || (pilotPS->weapon==WP_SABER && !pilotPS->SaberActive()))) + { + if (pVeh->m_pPilot->s.numberm_pPilot->client->ps.weapon = WP_NONE; + G_RemoveWeaponModels(pVeh->m_pPilot); + } +#endif + + // Don't Interrupt Attack Anims + //------------------------------ +#ifdef _JK2MP + if (pilotPS->weaponTime>0) + { + return; + } +#else + if (pilotPS->torsoAnim>=BOTH_VT_ATL_S && pilotPS->torsoAnim<=BOTH_VT_ATF_G) + { + float bodyCurrent = 0.0f; + int bodyEnd = 0; + if (!!gi.G2API_GetBoneAnimIndex(&pVeh->m_pPilot->ghoul2[pVeh->m_pPilot->playerModel], pVeh->m_pPilot->rootBone, level.time, &bodyCurrent, NULL, &bodyEnd, NULL, NULL, NULL)) + { + if (bodyCurrent<=((float)(bodyEnd)-1.5f)) + { + return; + } + } + } +#endif + + // Compute The Weapon Pose + //-------------------------- + if (pilotPS->weapon==WP_BLASTER) + { + WeaponPose = WPOSE_BLASTER; + } + else if (pilotPS->weapon==WP_SABER) + { + if ( (pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VT_ATL_TO_R_S) + { + pVeh->m_ulFlags &= ~VEH_SABERINLEFTHAND; + } + if (!(pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VT_ATR_TO_L_S) + { + pVeh->m_ulFlags |= VEH_SABERINLEFTHAND; + } + WeaponPose = (pVeh->m_ulFlags&VEH_SABERINLEFTHAND)?(WPOSE_SABERLEFT):(WPOSE_SABERRIGHT); + } + + + if (Attacking && WeaponPose) + {// Attack! + iBlend = 100; + iFlags = SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART; + + if (Turbo) + { + Right = true; + Left = false; + } + + // Auto Aiming + //=============================================== + if (!Left && !Right) // Allow player strafe keys to override + { +#ifndef _JK2MP + if (pVeh->m_pPilot->enemy) + { + vec3_t toEnemy; + float toEnemyDistance; + vec3_t actorRight; + float actorRightDot; + + VectorSubtract(pVeh->m_pPilot->currentOrigin, pVeh->m_pPilot->enemy->currentOrigin, toEnemy); + toEnemyDistance = VectorNormalize(toEnemy); + + AngleVectors(pVeh->m_pParentEntity->currentAngles, 0, actorRight, 0); + actorRightDot = DotProduct(toEnemy, actorRight); + + if (fabsf(actorRightDot)>0.5f || pilotPS->weapon==WP_SABER) + { + Left = (actorRightDot>0.0f); + Right = !Left; + } + else + { + Right = Left = false; + } + } + else +#endif + if (pilotPS->weapon==WP_SABER && !Left && !Right) + { + Left = (WeaponPose==WPOSE_SABERLEFT); + Right = !Left; + } + } + + + if (Left) + {// Attack Left + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_ATL_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VT_ATL_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VT_ATR_TO_L_S; break; + default: assert(0); + } + } + else if (Right) + {// Attack Right + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_ATR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VT_ATL_TO_R_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VT_ATR_S; break; + default: assert(0); + } + } + else + {// Attack Ahead + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_ATF_G; break; + default: assert(0); + } + } + } + else if (Turbo) + {// Kicked In Turbo + iBlend = 50; + iFlags = SETANIM_FLAG_OVERRIDE; + Anim = BOTH_VT_TURBO; + } + else + {// No Special Moves + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + + if (WeaponPose==WPOSE_NONE) + { + if (Walking) + { + Anim = BOTH_VT_WALK_FWD; + } + else if (Running) + { + Anim = BOTH_VT_RUN_FWD; + } + else + { + Anim = BOTH_VT_IDLE1;//(Q_irand(0,1)==0)?(BOTH_VT_IDLE):(BOTH_VT_IDLE1); + } + } + else + { + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_IDLE_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VT_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VT_IDLE_SR; break; + default: assert(0); + } + } + }// No Special Moves + } + + Vehicle_SetAnim( pilot, SETANIM_BOTH, Anim, iFlags, iBlend ); +} +#endif //QAGAME + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +//on the client this function will only set up the process command funcs +void G_SetAnimalVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + pVehInfo->AnimateVehicle = AnimateVehicle; + pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; +// pVehInfo->Board = Board; +// pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; + pVehInfo->DeathUpdate = DeathUpdate; +// pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; + pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif //QAGAME + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +//this is a BG function too in MP so don't un-bg-compatibilify it -rww +void G_CreateAnimalNPC( Vehicle_t **pVeh, const char *strAnimalType ) +{ + // Allocate the Vehicle. +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#else + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#endif +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/codemp/game/FighterNPC.c b/codemp/game/FighterNPC.c new file mode 100644 index 0000000..0f3f83c --- /dev/null +++ b/codemp/game/FighterNPC.c @@ -0,0 +1,1961 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +#endif + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +#ifdef QAGAME //SP or gameside MP +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern void G_VehicleTrace( trace_t *results, const vec3_t start, const vec3_t tMins, const vec3_t tMaxs, const vec3_t end, int passEntityNum, int contentmask ); +#endif + +extern qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ); + +#ifdef _JK2MP + +#include "../namespace_begin.h" + +extern void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int BG_GetTime(void); +#endif + +extern void BG_ExternThisSoICanRecompileInDebug( Vehicle_t *pVeh, playerState_t *riderPS ); + +//this stuff has got to be predicted, so.. +bool BG_FighterUpdate(Vehicle_t *pVeh, const usercmd_t *pUcmd, vec3_t trMins, vec3_t trMaxs, float gravity, + void (*traceFunc)( trace_t *results, const vec3_t start, const vec3_t lmins, const vec3_t lmaxs, const vec3_t end, int passEntityNum, int contentMask )) +{ + vec3_t bottom; + playerState_t *parentPS; + qboolean isDead = qfalse; +#ifdef QAGAME //don't do this on client + int i; + + // Make sure the riders are not visible or collidable. + pVeh->m_pVehicleInfo->Ghost( pVeh, pVeh->m_pPilot ); + for ( i = 0; i < pVeh->m_pVehicleInfo->maxPassengers; i++ ) + { + pVeh->m_pVehicleInfo->Ghost( pVeh, pVeh->m_ppPassengers[i] ); + } +#endif + + +#ifdef _JK2MP + parentPS = pVeh->m_pParentEntity->playerState; +#else + parentPS = &pVeh->m_pParentEntity->client->ps; +#endif + + if (!parentPS) + { + Com_Error(ERR_DROP, "NULL PS in BG_FighterUpdate (%s)", pVeh->m_pVehicleInfo->name); + return false; + } + + // If we have a pilot, take out gravity (it's a flying craft...). + if ( pVeh->m_pPilot ) + { + parentPS->gravity = 0; +#ifndef _JK2MP //don't need this flag in mp, I.. guess + pVeh->m_pParentEntity->svFlags |= SVF_CUSTOM_GRAVITY; +#endif + } + else + { +#ifndef _JK2MP //don't need this flag in mp, I.. guess + pVeh->m_pParentEntity->svFlags &= ~SVF_CUSTOM_GRAVITY; +#else //in MP set grav back to normal gravity + if (pVeh->m_pVehicleInfo->gravity) + { + parentPS->gravity = pVeh->m_pVehicleInfo->gravity; + } + else + { //it doesn't have gravity specified apparently + parentPS->gravity = gravity; + } +#endif + } + +#ifdef _JK2MP + isDead = (qboolean)((parentPS->eFlags&EF_DEAD)!=0); +#else + isDead = (parentPS->stats[STAT_HEALTH] <= 0 ); +#endif + + /* + if ( isDead || + (pVeh->m_pVehicleInfo->surfDestruction && + pVeh->m_iRemovedSurfaces ) ) + {//can't land if dead or spiralling out of control + pVeh->m_LandTrace.fraction = 1.0f; + pVeh->m_LandTrace.contents = pVeh->m_LandTrace.surfaceFlags = 0; + VectorClear( pVeh->m_LandTrace.plane.normal ); + pVeh->m_LandTrace.allsolid = qfalse; + pVeh->m_LandTrace.startsolid = qfalse; + } + else + { + */ + //argh, no, I need to have a way to see when they impact the ground while damaged. -rww + + // Check to see if the fighter has taken off yet (if it's a certain height above ground). + VectorCopy( parentPS->origin, bottom ); + bottom[2] -= pVeh->m_pVehicleInfo->landingHeight; + + traceFunc( &pVeh->m_LandTrace, parentPS->origin, trMins, trMaxs, bottom, pVeh->m_pParentEntity->s.number, (MASK_NPCSOLID&~CONTENTS_BODY) ); + //} + + return true; +} + +#ifdef QAGAME //ONLY in SP or on server, not cgame + +// Like a think or move command, this updates various vehicle properties. +static bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + assert(pVeh->m_pParentEntity); + if (!BG_FighterUpdate(pVeh, pUcmd, ((gentity_t *)pVeh->m_pParentEntity)->mins, + ((gentity_t *)pVeh->m_pParentEntity)->maxs, +#ifdef _JK2MP + g_gravity.value, +#else + g_gravity->value, +#endif + G_VehicleTrace)) + { + return false; + } + + if ( !g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ) ) + { + return false; + } + + return true; +} + +// Board this Vehicle (get on). The first entity to board an empty vehicle becomes the Pilot. +static bool Board( Vehicle_t *pVeh, bgEntity_t *pEnt ) +{ + if ( !g_vehicleInfo[VEHICLE_BASE].Board( pVeh, pEnt ) ) + return false; + + // Set the board wait time (they won't be able to do anything, including getting off, for this amount of time). + pVeh->m_iBoarding = level.time + 1500; + + return true; +} + +// Eject an entity from the vehicle. +static bool Eject( Vehicle_t *pVeh, bgEntity_t *pEnt, qboolean forceEject ) +{ + if ( g_vehicleInfo[VEHICLE_BASE].Eject( pVeh, pEnt, forceEject ) ) + { + return true; + } + + return false; +} + +#endif //end game-side only + +//method of decrementing the given angle based on the given taking variable frame times into account +static float PredictedAngularDecrement(float scale, float timeMod, float originalAngle) +{ + float fixedBaseDec = originalAngle*0.05f; + float r = 0.0f; + + if (fixedBaseDec < 0.0f) + { + fixedBaseDec = -fixedBaseDec; + } + + fixedBaseDec *= (1.0f+(1.0f-scale)); + + if (fixedBaseDec < 0.1f) + { //don't increment in incredibly small fractions, it would eat up unnecessary bandwidth. + fixedBaseDec = 0.1f; + } + + fixedBaseDec *= (timeMod*0.1f); + if (originalAngle > 0.0f) + { //subtract + r = (originalAngle-fixedBaseDec); + if (r < 0.0f) + { + r = 0.0f; + } + } + else if (originalAngle < 0.0f) + { //add + r = (originalAngle+fixedBaseDec); + if (r > 0.0f) + { + r = 0.0f; + } + } + + return r; +} + +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver +qboolean FighterIsInSpace( gentity_t *gParent ) +{ + if ( gParent + && gParent->client + && gParent->client->inSpaceIndex + && gParent->client->inSpaceIndex < ENTITYNUM_WORLD ) + { + return qtrue; + } + return qfalse; +} +#endif + +qboolean FighterOverValidLandingSurface( Vehicle_t *pVeh ) +{ + if ( pVeh->m_LandTrace.fraction < 1.0f //ground present + && pVeh->m_LandTrace.plane.normal[2] >= MIN_LANDING_SLOPE )//flat enough + //FIXME: also check for a certain surface flag ... "landing zones"? + { + return qtrue; + } + return qfalse; +} + +qboolean FighterIsLanded( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + if ( FighterOverValidLandingSurface( pVeh ) + && !parentPS->speed )//stopped + { + return qtrue; + } + return qfalse; +} + +qboolean FighterIsLanding( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + + if ( FighterOverValidLandingSurface( pVeh ) +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + && pVeh->m_pVehicleInfo->Inhabited( pVeh )//has to have a driver in order to be capable of landing +#endif + && (pVeh->m_ucmd.forwardmove < 0||pVeh->m_ucmd.upmove<0) //decelerating or holding crouch button + && parentPS->speed <= MIN_LANDING_SPEED )//going slow enough to start landing - was using pVeh->m_pVehicleInfo->speedIdle, but that's still too fast + { + return qtrue; + } + return qfalse; +} + +qboolean FighterIsLaunching( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + + if ( FighterOverValidLandingSurface( pVeh ) +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + && pVeh->m_pVehicleInfo->Inhabited( pVeh )//has to have a driver in order to be capable of landing +#endif + && pVeh->m_ucmd.upmove > 0 //trying to take off + && parentPS->speed <= 200.0f )//going slow enough to start landing - was using pVeh->m_pVehicleInfo->speedIdle, but that's still too fast + { + return qtrue; + } + return qfalse; +} + +qboolean FighterSuspended( Vehicle_t *pVeh, playerState_t *parentPS ) +{ +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + if (!pVeh->m_pPilot//empty + && !parentPS->speed//not moving + && pVeh->m_ucmd.forwardmove <= 0//not trying to go forward for whatever reason + && pVeh->m_pParentEntity != NULL + && (((gentity_t *)pVeh->m_pParentEntity)->spawnflags&2) )//SUSPENDED spawnflag is on + { + return qtrue; + } + return qfalse; +#elif CGAME + return qfalse; +#endif +} + +#ifdef CGAME +extern void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); //cg_syscalls.c +extern sfxHandle_t trap_S_RegisterSound( const char *sample); //cg_syscalls.c +#endif + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +#define FIGHTER_MIN_TAKEOFF_FRACTION 0.7f +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + bgEntity_t *parent = pVeh->m_pParentEntity; + qboolean isLandingOrLaunching = qfalse; +#ifndef _JK2MP//SP + int curTime = level.time; +#elif QAGAME//MP GAME + int curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + int curTime = pm->cmd.serverTime; +#endif + +#ifdef _JK2MP + playerState_t *parentPS = parent->playerState; +#else + playerState_t *parentPS = &parent->client->ps; +#endif + +#ifdef _JK2MP + if ( parentPS->hyperSpaceTime + && curTime - parentPS->hyperSpaceTime < HYPERSPACE_TIME ) + {//Going to Hyperspace + //totally override movement + float timeFrac = ((float)(curTime-parentPS->hyperSpaceTime))/HYPERSPACE_TIME; + if ( timeFrac < HYPERSPACE_TELEPORT_FRAC ) + {//for first half, instantly jump to top speed! + if ( !(parentPS->eFlags2&EF2_HYPERSPACE) ) + {//waiting to face the right direction, do nothing + parentPS->speed = 0.0f; + } + else + { + if ( parentPS->speed < HYPERSPACE_SPEED ) + {//just started hyperspace +//MIKE: This is going to play the sound twice for the predicting client, I suggest using +//a predicted event or only doing it game-side. -rich +#ifdef QAGAME//MP GAME-side + //G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_LOCAL, pVeh->m_pVehicleInfo->soundHyper ); +#elif CGAME//MP CGAME-side + trap_S_StartSound( NULL, pm->ps->clientNum, CHAN_LOCAL, pVeh->m_pVehicleInfo->soundHyper ); +#endif + } + + parentPS->speed = HYPERSPACE_SPEED; + } + } + else + {//slow from top speed to 200... + parentPS->speed = 200.0f + ((1.0f-timeFrac)*(1.0f/HYPERSPACE_TELEPORT_FRAC)*(HYPERSPACE_SPEED-200.0f)); + //don't mess with acceleration, just pop to the high velocity + if ( VectorLength( parentPS->velocity ) < parentPS->speed ) + { + VectorScale( parentPS->moveDir, parentPS->speed, parentPS->velocity ); + } + } + return; + } +#endif + + if ( pVeh->m_iDropTime >= curTime ) + {//no speed, just drop + parentPS->speed = 0.0f; + parentPS->gravity = 800; + return; + } + + isLandingOrLaunching = (FighterIsLanding( pVeh, parentPS )||FighterIsLaunching( pVeh, parentPS )); + + // If we are hitting the ground, just allow the fighter to go up and down. + if ( isLandingOrLaunching//going slow enough to start landing + && (pVeh->m_ucmd.forwardmove<=0||pVeh->m_LandTrace.fraction<=FIGHTER_MIN_TAKEOFF_FRACTION) )//not trying to accelerate away already (or: you are trying to, but not high enough off the ground yet) + {//FIXME: if start to move forward and fly over something low while still going relatively slow, you may try to land even though you don't mean to... + //float fInvFrac = 1.0f - pVeh->m_LandTrace.fraction; + + if ( pVeh->m_ucmd.upmove > 0 ) + { +#ifdef _JK2MP + if ( parentPS->velocity[2] <= 0 + && pVeh->m_pVehicleInfo->soundTakeOff ) + {//taking off for the first time +#ifdef QAGAME//MP GAME-side + G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_AUTO, pVeh->m_pVehicleInfo->soundTakeOff ); +#endif + } +#endif + parentPS->velocity[2] += pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier;// * ( /*fInvFrac **/ 1.5f ); + } + else if ( pVeh->m_ucmd.upmove < 0 ) + { + parentPS->velocity[2] -= pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier;// * ( /*fInvFrac **/ 1.8f ); + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( pVeh->m_LandTrace.fraction != 0.0f ) + { + parentPS->velocity[2] -= pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + + if ( pVeh->m_LandTrace.fraction <= FIGHTER_MIN_TAKEOFF_FRACTION ) + { + //pVeh->m_pParentEntity->client->ps.velocity[0] *= pVeh->m_LandTrace.fraction; + //pVeh->m_pParentEntity->client->ps.velocity[1] *= pVeh->m_LandTrace.fraction; + + //remember to always base this stuff on the time modifier! otherwise, you create + //framerate-dependancy issues and break prediction in MP -rww + //parentPS->velocity[2] *= pVeh->m_LandTrace.fraction; + //it's not an angle, but hey + parentPS->velocity[2] = PredictedAngularDecrement(pVeh->m_LandTrace.fraction, pVeh->m_fTimeModifier*5.0f, parentPS->velocity[2]); + + parentPS->speed = 0; + } + } + + // Make sure they don't pitch as they near the ground. + //pVeh->m_vOrientation[PITCH] *= 0.7f; + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.7f, pVeh->m_fTimeModifier*10.0f, pVeh->m_vOrientation[PITCH]); + + return; + } + + if ( (pVeh->m_ucmd.upmove > 0) && pVeh->m_pVehicleInfo->turboSpeed ) + { + if ((curTime - pVeh->m_iTurboTime)>pVeh->m_pVehicleInfo->turboRecharge) + { + pVeh->m_iTurboTime = (curTime + pVeh->m_pVehicleInfo->turboDuration); + if (pVeh->m_pVehicleInfo->iTurboStartFX) + { + int i; + for (i=0; im_iExhaustTag[i]==-1) + { + break; + } + #ifndef _JK2MP//SP + G_PlayEffect(pVeh->m_pVehicleInfo->iTurboStartFX, pVeh->m_pParentEntity->playerModel, pVeh->m_iExhaustTag[i], pVeh->m_pParentEntity->s.number, pVeh->m_pParentEntity->currentOrigin ); + #else + //TODO: MP Play Effect? + #endif + } + } + //NOTE: turbo sound can't be part of effect if effect is played on every muzzle! + if ( pVeh->m_pVehicleInfo->soundTurbo ) + { +#ifndef _JK2MP//SP + G_SoundIndexOnEnt( pVeh->m_pParentEntity, CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo ); +#elif QAGAME//MP GAME-side + G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo ); +#elif CGAME//MP CGAME-side + //trap_S_StartSound( NULL, pVeh->m_pParentEntity->s.number, CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo ); +#endif + } + } + } + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + if ( curTime < pVeh->m_iTurboTime ) + {//going turbo speed + speedMax = pVeh->m_pVehicleInfo->turboSpeed; + //double our acceleration + speedInc *= 2.0f; + //force us to move forward + pVeh->m_ucmd.forwardmove = 127; +#ifdef _JK2MP//SP can cheat and just check m_iTurboTime directly... :) + //add flag to let cgame know to draw the iTurboFX effect + parentPS->eFlags |= EF_JETPACK_ACTIVE; +#endif + } + /* + //FIXME: if turbotime is up and we're waiting for it to recharge, should our max speed drop while we recharge? + else if ( (curTime - pVeh->m_iTurboTime)<3000 ) + {//still waiting for the recharge + speedMax = pVeh->m_pVehicleInfo->speedMax*0.75; + } + */ + else + {//normal max speed + speedMax = pVeh->m_pVehicleInfo->speedMax; +#ifdef _JK2MP//SP can cheat and just check m_iTurboTime directly... :) + if ( (parentPS->eFlags&EF_JETPACK_ACTIVE) ) + {//stop cgame from playing the turbo exhaust effect + parentPS->eFlags &= ~EF_JETPACK_ACTIVE; + } +#endif + } + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + + if ( (parentPS->brokenLimbs&(1<brokenLimbs&(1<m_iRemovedSurfaces + || parentPS->electrifyTime>=curTime) + { //go out of control + parentPS->speed += speedInc; + //Why set forwardmove? PMove code doesn't use it... does it? + pVeh->m_ucmd.forwardmove = 127; + } +#ifdef QAGAME //well, the thing is always going to be inhabited if it's being predicted! + else if ( FighterSuspended( pVeh, parentPS ) ) + { + parentPS->speed = 0; + pVeh->m_ucmd.forwardmove = 0; + } + else if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) + && parentPS->speed > 0 ) + {//pilot jumped out while we were moving forward (not landing or landed) so just keep the throttle locked + //Why set forwardmove? PMove code doesn't use it... does it? + pVeh->m_ucmd.forwardmove = 127; + } +#endif + else if ( ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) && pVeh->m_LandTrace.fraction >= 0.05f ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + pVeh->m_ucmd.forwardmove = 127; + } + else if ( pVeh->m_ucmd.forwardmove < 0 + || pVeh->m_ucmd.upmove < 0 ) + {//decelerating or braking + if ( pVeh->m_ucmd.upmove < 0 ) + {//braking (trying to land?), slow down faster + if ( pVeh->m_ucmd.forwardmove ) + {//decelerator + brakes + speedInc += pVeh->m_pVehicleInfo->braking; + speedIdleDec += pVeh->m_pVehicleInfo->braking; + } + else + {//just brakes + speedInc = speedIdleDec = pVeh->m_pVehicleInfo->braking; + } + } + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + if ( FighterOverValidLandingSurface( pVeh ) ) + {//there's ground below us and we're trying to slow down, slow down faster + parentPS->speed -= speedInc; + } + else + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < MIN_LANDING_SPEED ) + {//unless you can land, don't drop below the landing speed!!! This way you can't come to a dead stop in mid-air + parentPS->speed = MIN_LANDING_SPEED; + } + } + } + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + pVeh->m_ucmd.forwardmove = 127; + } + else if ( speedMin >= 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + } + //else not accel, decel or braking + else if ( pVeh->m_pVehicleInfo->throttleSticks ) + {//we're using a throttle that sticks at current speed + if ( parentPS->speed <= MIN_LANDING_SPEED ) + {//going less than landing speed + if ( FighterOverValidLandingSurface( pVeh ) ) + {//close to ground and not going very fast + //slow to a stop if within landing height and not accel/decel/braking + if ( parentPS->speed > 0 ) + {//slow down + parentPS->speed -= speedIdleDec; + } + else if ( parentPS->speed < 0 ) + {//going backwards, slow down + parentPS->speed += speedIdleDec; + } + } + else + {//not over a valid landing surf, but going too slow + //speed up to idle speed if not over a valid landing surf and not accel/decel/braking + if ( parentPS->speed < speedIdle ) + { + parentPS->speed += speedIdleAccel; + if ( parentPS->speed > speedIdle ) + { + parentPS->speed = speedIdle; + } + } + } + } + } + else + {//then speed up or slow down to idle speed + //accelerate to cruising speed only, otherwise, just coast + // If they've launched, apply some constant motion. + if ( (pVeh->m_LandTrace.fraction >= 1.0f //no ground + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE )//or can't land on ground below us + && speedIdle > 0 ) + {//not above ground and have an idle speed + //float fSpeed = pVeh->m_pParentEntity->client->ps.speed; + if ( parentPS->speed < speedIdle ) + { + parentPS->speed += speedIdleAccel; + if ( parentPS->speed > speedIdle ) + { + parentPS->speed = speedIdle; + } + } + else if ( parentPS->speed > 0 ) + {//slow down + parentPS->speed -= speedIdleDec; + + if ( parentPS->speed < speedIdle ) + { + parentPS->speed = speedIdle; + } + } + } + else//either close to ground or no idle speed + {//slow to a stop if no idle speed or within landing height and not accel/decel/braking + if ( parentPS->speed > 0 ) + {//slow down + parentPS->speed -= speedIdleDec; + } + else if ( parentPS->speed < 0 ) + {//going backwards, slow down + parentPS->speed += speedIdleDec; + } + } + } + } + else + { + if ( pVeh->m_ucmd.forwardmove < 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + if ( pVeh->m_ucmd.upmove < 0 ) + { + pVeh->m_ucmd.upmove = 0; + } + +#ifndef _JK2MP + if ( !pVeh->m_pVehicleInfo->strafePerc || (!g_speederControlScheme->value && !pVeh->m_pParentEntity->s.number) ) + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + pVeh->m_ucmd.rightmove = 0; + } +#endif + } + +#if 1//This is working now, but there are some transitional jitters... Rich? +//STRAFING============================================================================== + if ( pVeh->m_pVehicleInfo->strafePerc +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + && pVeh->m_pVehicleInfo->Inhabited( pVeh )//has to have a driver in order to be capable of landing +#endif + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTimem_LandTrace.fraction >= 1.0f//no grounf + ||pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE//can't land here + ||parentPS->speed>MIN_LANDING_SPEED)//going too fast to land + && pVeh->m_ucmd.rightmove ) + {//strafe + vec3_t vAngles, vRight; + float strafeSpeed = (pVeh->m_pVehicleInfo->strafePerc*speedMax)*5.0f; + VectorCopy( pVeh->m_vOrientation, vAngles ); + vAngles[PITCH] = vAngles[ROLL] = 0; + AngleVectors( vAngles, NULL, vRight, NULL ); + + if ( pVeh->m_ucmd.rightmove > 0 ) + {//strafe right + //FIXME: this will probably make it possible to cheat and + // go faster than max speed if you keep turning and strafing... + if ( parentPS->hackingTime > -MAX_STRAFE_TIME ) + {//can strafe right for 2 seconds + float curStrafeSpeed = DotProduct( parentPS->velocity, vRight ); + if ( curStrafeSpeed > 0.0f ) + {//if > 0, already strafing right + strafeSpeed -= curStrafeSpeed;//so it doesn't add up + } + if ( strafeSpeed > 0 ) + { + VectorMA( parentPS->velocity, strafeSpeed*pVeh->m_fTimeModifier, vRight, parentPS->velocity ); + } + parentPS->hackingTime -= 50*pVeh->m_fTimeModifier; + } + } + else + {//strafe left + if ( parentPS->hackingTime < MAX_STRAFE_TIME ) + {//can strafe left for 2 seconds + float curStrafeSpeed = DotProduct( parentPS->velocity, vRight ); + if ( curStrafeSpeed < 0.0f ) + {//if < 0, already strafing left + strafeSpeed += curStrafeSpeed;//so it doesn't add up + } + if ( strafeSpeed > 0 ) + { + VectorMA( parentPS->velocity, -strafeSpeed*pVeh->m_fTimeModifier, vRight, parentPS->velocity ); + } + parentPS->hackingTime += 50*pVeh->m_fTimeModifier; + } + } + //strafing takes away from forward speed? If so, strafePerc above should use speedMax + //parentPS->speed *= (1.0f-pVeh->m_pVehicleInfo->strafePerc); + } + else//if ( parentPS->hackingTimef ) + { + if ( parentPS->hackingTime > 0 ) + { + parentPS->hackingTime -= 50*pVeh->m_fTimeModifier; + if ( parentPS->hackingTime < 0 ) + { + parentPS->hackingTime = 0.0f; + } + } + else if ( parentPS->hackingTime < 0 ) + { + parentPS->hackingTime += 50*pVeh->m_fTimeModifier; + if ( parentPS->hackingTime > 0 ) + { + parentPS->hackingTime = 0.0f; + } + } + } +//STRAFING============================================================================== +#endif + + if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + +#ifdef QAGAME//FIXME: get working in GAME and CGAME + if ((pVeh->m_vOrientation[PITCH]*0.1f) > 10.0f) + { //pitched downward, increase speed more and more based on our tilt + if ( FighterIsInSpace( (gentity_t *)parent ) ) + {//in space, do nothing with speed base on pitch... + } + else + { + //really should only do this when on a planet + float mult = pVeh->m_vOrientation[PITCH]*0.1f; + if (mult < 1.0f) + { + mult = 1.0f; + } + parentPS->speed = PredictedAngularDecrement(mult, pVeh->m_fTimeModifier*10.0f, parentPS->speed); + } + } + + if (pVeh->m_iRemovedSurfaces + || parentPS->electrifyTime>=curTime) + { //going down + if ( FighterIsInSpace( (gentity_t *)parent ) ) + {//we're in a valid trigger_space brush + //simulate randomness + if ( !(parent->s.number&3) ) + {//even multiple of 3, don't do anything + parentPS->gravity = 0; + } + else if ( !(parent->s.number&2) ) + {//even multiple of 2, go up + parentPS->gravity = -500.0f; + parentPS->velocity[2] = 80.0f; + } + else + {//odd number, go down + parentPS->gravity = 500.0f; + parentPS->velocity[2] = -80.0f; + } + } + else + {//over a planet + parentPS->gravity = 500.0f; + parentPS->velocity[2] = -80.0f; + } + } + else if ( FighterSuspended( pVeh, parentPS ) ) + { + parentPS->gravity = 0; + } + else if ( (!parentPS->speed||parentPS->speed < speedIdle) && pVeh->m_ucmd.upmove <= 0 ) + {//slowing down or stopped and not trying to take off + if ( FighterIsInSpace( (gentity_t *)parent ) ) + {//we're in space, stopping doesn't make us drift downward + if ( FighterOverValidLandingSurface( pVeh ) ) + {//well, there's something below us to land on, so go ahead and lower us down to it + parentPS->gravity = (speedIdle - parentPS->speed)/4; + } + } + else + {//over a planet + parentPS->gravity = (speedIdle - parentPS->speed)/4; + } + } + else + { + parentPS->gravity = 0; + } +#else//FIXME: get above checks working in GAME and CGAME + parentPS->gravity = 0; +#endif + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +extern void BG_VehicleTurnRateForSpeed( Vehicle_t *pVeh, float speed, float *mPitchOverride, float *mYawOverride ); +static void FighterWingMalfunctionCheck( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + float mPitchOverride = 1.0f; + float mYawOverride = 1.0f; + BG_VehicleTurnRateForSpeed( pVeh, parentPS->speed, &mPitchOverride, &mYawOverride ); + //check right wing damage + if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] += (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*50.0f; + } + else if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] += (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*12.5f; + } + + //check left wing damage + if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] -= (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*50.0f; + } + else if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] -= (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*12.5f; + } + +} + +static void FighterNoseMalfunctionCheck( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + float mPitchOverride = 1.0f; + float mYawOverride = 1.0f; + BG_VehicleTurnRateForSpeed( pVeh, parentPS->speed, &mPitchOverride, &mYawOverride ); + //check nose damage + if ( (parentPS->brokenLimbs&(1<m_vOrientation[PITCH] += sin( pVeh->m_ucmd.serverTime*0.001 )*pVeh->m_fTimeModifier*mPitchOverride*50.0f; + } + else if ( (parentPS->brokenLimbs&(1<m_vOrientation[PITCH] += sin( pVeh->m_ucmd.serverTime*0.001 )*pVeh->m_fTimeModifier*mPitchOverride*20.0f; + } +} + +static void FighterDamageRoutine( Vehicle_t *pVeh, bgEntity_t *parent, playerState_t *parentPS, playerState_t *riderPS, qboolean isDead ) +{ + if ( !pVeh->m_iRemovedSurfaces ) + {//still in one piece + if ( pVeh->m_pParentEntity && isDead ) + {//death spiral + pVeh->m_ucmd.upmove = 0; + //FIXME: don't bias toward pitching down when not in space + /* + if ( FighterIsInSpace( pVeh->m_pParentEntity ) ) + { + } + else + */ + if ( !(pVeh->m_pParentEntity->s.number%3) ) + {//NOT everyone should do this + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + } + } + else if ( !(pVeh->m_pParentEntity->s.number%2) ) + { + pVeh->m_vOrientation[PITCH] -= pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > -60.0f ) + { + pVeh->m_vOrientation[PITCH] = -60.0f; + } + } + } + if ( (pVeh->m_pParentEntity->s.number%2) ) + { + pVeh->m_vOrientation[YAW] += pVeh->m_fTimeModifier; + pVeh->m_vOrientation[ROLL] += pVeh->m_fTimeModifier*4.0f; + } + else + { + pVeh->m_vOrientation[YAW] -= pVeh->m_fTimeModifier; + pVeh->m_vOrientation[ROLL] -= pVeh->m_fTimeModifier*4.0f; + } + } + return; + } + + //if we get into here we have at least one broken piece + pVeh->m_ucmd.upmove = 0; + + //if you're off the ground and not suspended, pitch down + //FIXME: not in space! + if ( pVeh->m_LandTrace.fraction >= 0.1f ) + { + if ( !FighterSuspended( pVeh, parentPS ) ) + { + //pVeh->m_ucmd.forwardmove = 0; + //FIXME: don't bias towards pitching down when in space... + if ( !(pVeh->m_pParentEntity->s.number%2) ) + {//NOT everyone should do this + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + } + } + else if ( !(pVeh->m_pParentEntity->s.number%3) ) + { + pVeh->m_vOrientation[PITCH] -= pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > -60.0f ) + { + pVeh->m_vOrientation[PITCH] = -60.0f; + } + } + } + //else: just keep going forward + } + } +#ifdef QAGAME + if ( pVeh->m_LandTrace.fraction < 1.0f ) + { //if you land at all when pieces of your ship are missing, then die + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + gentity_t *killer = parent; +#ifdef _JK2MP//only have this info in MP... + if (parent->client->ps.otherKiller < ENTITYNUM_WORLD && + parent->client->ps.otherKillerTime > level.time) + { + gentity_t *potentialKiller = &g_entities[parent->client->ps.otherKiller]; + + if (potentialKiller->inuse && potentialKiller->client) + { //he's valid I guess + killer = potentialKiller; + } + } +#endif + G_Damage(parent, killer, killer, vec3_origin, parent->client->ps.origin, 99999, DAMAGE_NO_ARMOR, MOD_SUICIDE); + } +#endif + + if ( ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) && + ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) ) + { //wings on both side broken + float factor = 2.0f; + if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) + { //all wings broken + factor *= 2.0f; + } + + if ( !(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5) ) + {//won't yaw, so increase roll factor + factor *= 4.0f; + } + + pVeh->m_vOrientation[ROLL] += (pVeh->m_fTimeModifier*factor); //do some spiralling + } + else if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) + { //left wing broken + float factor = 2.0f; + if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) + { //if both are broken.. + factor *= 2.0f; + } + + if ( !(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5) ) + {//won't yaw, so increase roll factor + factor *= 4.0f; + } + + pVeh->m_vOrientation[ROLL] += factor*pVeh->m_fTimeModifier; + } + else if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) + { //right wing broken + float factor = 2.0f; + if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) + { //if both are broken.. + factor *= 2.0f; + } + + if ( !(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5) ) + {//won't yaw, so increase roll factor + factor *= 4.0f; + } + + pVeh->m_vOrientation[ROLL] -= factor*pVeh->m_fTimeModifier; + } +} + +#ifdef _JK2MP +#define FIGHTER_TURNING_MULTIPLIER 0.8f//was 1.6f //magic number hackery +#define FIGHTER_TURNING_DEADZONE 0.25f//no turning if offset is this much +void FighterRollAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ +/* + float angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); +*/ + float angDif = AngleSubtract(pVeh->m_vPrevRiderViewAngles[YAW],riderPS->viewangles[YAW]);///2.0f;//AngleSubtract(pVeh->m_vPrevRiderViewAngles[YAW], riderPS->viewangles[YAW]); + /* + if ( fabs( angDif ) < FIGHTER_TURNING_DEADZONE ) + { + angDif = 0.0f; + } + else if ( angDif >= FIGHTER_TURNING_DEADZONE ) + { + angDif -= FIGHTER_TURNING_DEADZONE; + } + else if ( angDif <= -FIGHTER_TURNING_DEADZONE ) + { + angDif += FIGHTER_TURNING_DEADZONE; + } + */ + + angDif *= 0.5f; + if ( angDif > 0.0f ) + { + angDif *= angDif; + } + else if ( angDif < 0.0f ) + { + angDif *= -angDif; + } + + if (parentPS && parentPS->speed) + { + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*FIGHTER_TURNING_MULTIPLIER; + + if ( pVeh->m_pVehicleInfo->speedDependantTurning ) + { + float speedFrac = 1.0f; + if ( pVeh->m_LandTrace.fraction >= 1.0f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + { + float s = parentPS->speed; + if (s < 0.0f) + { + s = -s; + } + speedFrac = (s/(pVeh->m_pVehicleInfo->speedMax*0.75f)); + if ( speedFrac < 0.25f ) + { + speedFrac = 0.25f; + } + else if ( speedFrac > 1.0f ) + { + speedFrac = 1.0f; + } + } + angDif *= speedFrac; + } + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[ROLL] = AngleNormalize180(pVeh->m_vOrientation[ROLL] + angDif*(pVeh->m_fTimeModifier*0.2f)); + } +} + +void FighterYawAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ +/* + float angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); +*/ + float angDif = AngleSubtract(pVeh->m_vPrevRiderViewAngles[YAW],riderPS->viewangles[YAW]);///2.0f;//AngleSubtract(pVeh->m_vPrevRiderViewAngles[YAW], riderPS->viewangles[YAW]); + if ( fabs( angDif ) < FIGHTER_TURNING_DEADZONE ) + { + angDif = 0.0f; + } + else if ( angDif >= FIGHTER_TURNING_DEADZONE ) + { + angDif -= FIGHTER_TURNING_DEADZONE; + } + else if ( angDif <= -FIGHTER_TURNING_DEADZONE ) + { + angDif += FIGHTER_TURNING_DEADZONE; + } + + angDif *= 0.5f; + if ( angDif > 0.0f ) + { + angDif *= angDif; + } + else if ( angDif < 0.0f ) + { + angDif *= -angDif; + } + + if (parentPS && parentPS->speed) + { + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*FIGHTER_TURNING_MULTIPLIER; + + if ( pVeh->m_pVehicleInfo->speedDependantTurning ) + { + float speedFrac = 1.0f; + if ( pVeh->m_LandTrace.fraction >= 1.0f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + { + float s = parentPS->speed; + if (s < 0.0f) + { + s = -s; + } + speedFrac = (s/(pVeh->m_pVehicleInfo->speedMax*0.75f)); + if ( speedFrac < 0.25f ) + { + speedFrac = 0.25f; + } + else if ( speedFrac > 1.0f ) + { + speedFrac = 1.0f; + } + } + angDif *= speedFrac; + } + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - (angDif*(pVeh->m_fTimeModifier*0.2f)) ); + } +} + +void FighterPitchAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ +/* + float angDif = AngleSubtract(pVeh->m_vOrientation[PITCH], riderPS->viewangles[PITCH]); +*/ + float angDif = AngleSubtract(0,riderPS->viewangles[PITCH]);//AngleSubtract(pVeh->m_vPrevRiderViewAngles[PITCH], riderPS->viewangles[PITCH]); + if ( fabs( angDif ) < FIGHTER_TURNING_DEADZONE ) + { + angDif = 0.0f; + } + else if ( angDif >= FIGHTER_TURNING_DEADZONE ) + { + angDif -= FIGHTER_TURNING_DEADZONE; + } + else if ( angDif <= -FIGHTER_TURNING_DEADZONE ) + { + angDif += FIGHTER_TURNING_DEADZONE; + } + + angDif *= 0.5f; + if ( angDif > 0.0f ) + { + angDif *= angDif; + } + else if ( angDif < 0.0f ) + { + angDif *= -angDif; + } + + if (parentPS && parentPS->speed) + { + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*FIGHTER_TURNING_MULTIPLIER; + + if ( pVeh->m_pVehicleInfo->speedDependantTurning ) + { + float speedFrac = 1.0f; + if ( pVeh->m_LandTrace.fraction >= 1.0f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + { + float s = parentPS->speed; + if (s < 0.0f) + { + s = -s; + } + speedFrac = (s/(pVeh->m_pVehicleInfo->speedMax*0.75f)); + if ( speedFrac < 0.25f ) + { + speedFrac = 0.25f; + } + else if ( speedFrac > 1.0f ) + { + speedFrac = 1.0f; + } + } + angDif *= speedFrac; + } + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[PITCH] = AngleNormalize180(pVeh->m_vOrientation[PITCH] - (angDif*(pVeh->m_fTimeModifier*0.2f)) ); + } +} + +void FighterPitchClamp(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS, int curTime ) +{ + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + {//cap pitch reasonably + if ( pVeh->m_pVehicleInfo->pitchLimit != -1 + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTime < curTime ) + { + if (pVeh->m_vOrientation[PITCH] > pVeh->m_pVehicleInfo->pitchLimit ) + { + pVeh->m_vOrientation[PITCH] = pVeh->m_pVehicleInfo->pitchLimit; + } + else if (pVeh->m_vOrientation[PITCH] < -pVeh->m_pVehicleInfo->pitchLimit) + { + pVeh->m_vOrientation[PITCH] = -pVeh->m_pVehicleInfo->pitchLimit; + } + } + } +} +#endif + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessOrientCommands the Vehicle. +static void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + + bgEntity_t *parent = pVeh->m_pParentEntity; + playerState_t *parentPS, *riderPS; + float angleTimeMod; +#ifdef QAGAME + const float groundFraction = 0.1f; +#endif + float curRoll = 0.0f; + qboolean isDead = qfalse; + qboolean isLandingOrLanded = qfalse; +#ifndef _JK2MP//SP + int curTime = level.time; +#elif QAGAME//MP GAME + int curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + int curTime = pm->cmd.serverTime; +#endif + +#ifdef _JK2MP + bgEntity_t *rider = NULL; + if (parent->s.owner != ENTITYNUM_NONE) + { + rider = PM_BGEntForNum(parent->s.owner); //&g_entities[parent->r.ownerNum]; + } +#else + gentity_t *rider = parent->owner; +#endif + +#ifdef _JK2MP + if ( !rider ) +#else + if ( !rider || !rider->client ) +#endif + { + rider = parent; + } + +#ifdef _JK2MP + parentPS = parent->playerState; + riderPS = rider->playerState; + isDead = (qboolean)((parentPS->eFlags&EF_DEAD)!=0); +#else + parentPS = &parent->client->ps; + riderPS = &rider->client->ps; + isDead = (parentPS->stats[STAT_HEALTH] <= 0 ); +#endif + +#ifdef _JK2MP + if ( parentPS->hyperSpaceTime + && (curTime - parentPS->hyperSpaceTime) < HYPERSPACE_TIME ) + {//Going to Hyperspace + //VectorCopy( riderPS->viewangles, pVeh->m_vOrientation ); + //VectorCopy( riderPS->viewangles, parentPS->viewangles ); + VectorCopy( parentPS->viewangles, pVeh->m_vOrientation ); + VectorClear( pVeh->m_vPrevRiderViewAngles ); + pVeh->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(riderPS->viewangles[YAW]); + FighterPitchClamp( pVeh, riderPS, parentPS, curTime ); + return; + } + else if ( parentPS->vehTurnaroundIndex + && parentPS->vehTurnaroundTime > curTime ) + {//in turn-around brush + //VectorCopy( riderPS->viewangles, pVeh->m_vOrientation ); + //VectorCopy( riderPS->viewangles, parentPS->viewangles ); + VectorCopy( parentPS->viewangles, pVeh->m_vOrientation ); + VectorClear( pVeh->m_vPrevRiderViewAngles ); + pVeh->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(riderPS->viewangles[YAW]); + FighterPitchClamp( pVeh, riderPS, parentPS, curTime ); + return; + } +#endif + + if ( pVeh->m_iDropTime >= curTime ) + {//you can only YAW during this + parentPS->viewangles[YAW] = pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + VectorClear( pVeh->m_vPrevRiderViewAngles ); + pVeh->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(riderPS->viewangles[YAW]); + return; + } + + angleTimeMod = pVeh->m_fTimeModifier; + + if ( isDead || parentPS->electrifyTime>=curTime || + (pVeh->m_pVehicleInfo->surfDestruction && + pVeh->m_iRemovedSurfaces && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) ) + { //do some special stuff for when all the wings are torn off + FighterDamageRoutine(pVeh, parent, parentPS, riderPS, isDead); + pVeh->m_vOrientation[ROLL] = AngleNormalize180( pVeh->m_vOrientation[ROLL] ); + return; + } + + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + pVeh->m_vOrientation[ROLL] = PredictedAngularDecrement(0.95f, angleTimeMod*2.0f, pVeh->m_vOrientation[ROLL]); + } + + isLandingOrLanded = (FighterIsLanding( pVeh, parentPS )||FighterIsLanded( pVeh, parentPS )); + + if (!isLandingOrLanded) + { //don't do this stuff while landed.. I guess. I don't want ships spinning in place, looks silly. + int m = 0; + float aVelDif; + float dForVel; + + FighterWingMalfunctionCheck( pVeh, parentPS ); + + while (m < 3) + { + aVelDif = pVeh->m_vFullAngleVelocity[m]; + + if (aVelDif != 0.0f) + { + dForVel = (aVelDif*0.1f)*pVeh->m_fTimeModifier; + if (dForVel > 1.0f || dForVel < -1.0f) + { + pVeh->m_vOrientation[m] += dForVel; + pVeh->m_vOrientation[m] = AngleNormalize180(pVeh->m_vOrientation[m]); + if (m == PITCH) + { //don't pitch downward into ground even more. + if (pVeh->m_vOrientation[m] > 90.0f && (pVeh->m_vOrientation[m]-dForVel) < 90.0f) + { + pVeh->m_vOrientation[m] = 90.0f; + pVeh->m_vFullAngleVelocity[m] = -pVeh->m_vFullAngleVelocity[m]; + } + } + pVeh->m_vFullAngleVelocity[m] -= dForVel; + } + else + { + pVeh->m_vFullAngleVelocity[m] = 0.0f; + } + } + + m++; + } + } + else + { //clear decr/incr angles once landed. + VectorClear(pVeh->m_vFullAngleVelocity); + } + + curRoll = pVeh->m_vOrientation[ROLL]; + + // If we're landed, we shouldn't be able to do anything but take off. + if ( isLandingOrLanded //going slow enough to start landing + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTimespeed > 0.0f ) + {//Uh... what? Why? + if ( pVeh->m_LandTrace.fraction < 0.3f ) + { + pVeh->m_vOrientation[PITCH] = 0.0f; + } + else + { + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.83f, angleTimeMod*10.0f, pVeh->m_vOrientation[PITCH]); + } + } + if ( pVeh->m_LandTrace.fraction > 0.1f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + {//off the ground, at least (or not on a valid landing surf) + // Dampen the turn rate based on the current height. +#ifdef _JK2MP + FighterYawAdjust(pVeh, riderPS, parentPS); +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW];//*pVeh->m_LandTrace.fraction; +#endif + } + else + { + VectorClear( pVeh->m_vPrevRiderViewAngles ); + pVeh->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(riderPS->viewangles[YAW]); + } + } + else if ( (pVeh->m_iRemovedSurfaces||parentPS->electrifyTime>=curTime)//spiralling out of control + && (!(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5)) ) + {//no yaw control + } + else if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS && parentPS->speed > 0.0f )//&& !( pVeh->m_ucmd.forwardmove > 0 && pVeh->m_LandTrace.fraction != 1.0f ) ) + { + if ( 0 && BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + VectorCopy( riderPS->viewangles, pVeh->m_vOrientation ); + VectorCopy( riderPS->viewangles, parentPS->viewangles ); +#ifdef _JK2MP + //BG_ExternThisSoICanRecompileInDebug( pVeh, riderPS ); +#endif + + curRoll = pVeh->m_vOrientation[ROLL]; + + FighterNoseMalfunctionCheck( pVeh, parentPS ); + + //VectorCopy( pVeh->m_vOrientation, parentPS->viewangles ); + } + else + { + /* + float fTurnAmt[3]; + //PITCH + fTurnAmt[PITCH] = riderPS->viewangles[PITCH] * 0.08f; + //YAW + fTurnAmt[YAW] = riderPS->viewangles[YAW] * 0.065f; + fTurnAmt[YAW] *= fTurnAmt[YAW]; + // Dampen the turn rate based on the current height. + if ( riderPS->viewangles[YAW] < 0 ) + {//must keep it negative because squaring a negative makes it positive + fTurnAmt[YAW] = -fTurnAmt[YAW]; + } + fTurnAmt[YAW] *= pVeh->m_LandTrace.fraction; + //ROLL + fTurnAmt[2] = 0.0f; + */ + + //Actal YAW +#ifdef _JK2MP + /* + pVeh->m_vOrientation[ROLL] = curRoll; + FighterRollAdjust(pVeh, riderPS, parentPS); + curRoll = pVeh->m_vOrientation[ROLL]; + */ + FighterYawAdjust(pVeh, riderPS, parentPS); +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; +#endif + + // If we are not hitting the ground, allow the fighter to pitch up and down. + if ( !FighterOverValidLandingSurface( pVeh ) + || parentPS->speed > MIN_LANDING_SPEED ) + //if ( ( pVeh->m_LandTrace.fraction >= 1.0f || pVeh->m_ucmd.forwardmove != 0 ) && pVeh->m_LandTrace.fraction >= 0.0f ) + { + float fYawDelta; + +#ifdef _JK2MP + FighterPitchAdjust(pVeh, riderPS, parentPS); + FighterPitchClamp( pVeh, riderPS, parentPS, curTime ); +#else + pVeh->m_vOrientation[PITCH] = riderPS->viewangles[PITCH]; +#endif + + FighterNoseMalfunctionCheck( pVeh, parentPS ); + + // Adjust the roll based on the turn amount and dampen it a little. + fYawDelta = AngleSubtract(pVeh->m_vOrientation[YAW], pVeh->m_vPrevOrientation[YAW]); //pVeh->m_vOrientation[YAW] - pVeh->m_vPrevOrientation[YAW]; + if ( fYawDelta > 8.0f ) + { + fYawDelta = 8.0f; + } + else if ( fYawDelta < -8.0f ) + { + fYawDelta = -8.0f; + } + curRoll -= fYawDelta; + curRoll = PredictedAngularDecrement(0.93f, angleTimeMod*2.0f, curRoll); + + //cap it reasonably + //NOTE: was hardcoded to 40.0f, now using extern data + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_pVehicleInfo->rollLimit != -1 ) + { + if (curRoll > pVeh->m_pVehicleInfo->rollLimit ) + { + curRoll = pVeh->m_pVehicleInfo->rollLimit; + } + else if (curRoll < -pVeh->m_pVehicleInfo->rollLimit) + { + curRoll = -pVeh->m_pVehicleInfo->rollLimit; + } + } + } + } + } + } + + // If you are directly impacting the ground, even out your pitch. + if ( isLandingOrLanded ) + {//only if capable of landing + if ( !isDead + && parentPS->electrifyTimem_pVehicleInfo->surfDestruction || !pVeh->m_iRemovedSurfaces ) ) + {//not crashing or spiralling out of control... + if ( pVeh->m_vOrientation[PITCH] > 0 ) + { + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.2f, angleTimeMod*10.0f, pVeh->m_vOrientation[PITCH]); + } + else + { + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.75f, angleTimeMod*10.0f, pVeh->m_vOrientation[PITCH]); + } + } + } + + +/* +//NOTE: all this is redundant now since we have the FighterDamageRoutine func... +#ifdef _JK2MP //...yeah. Need to send armor across net for prediction to work. + if ( isDead ) +#else + if ( pVeh->m_iArmor <= 0 ) +#endif + {//going to explode + //FIXME: maybe make it erratically jerk or spin or start and stop? +#ifndef _JK2MP + if ( g_speederControlScheme->value > 0 || !rider || rider->s.number ) +#else + if (1) +#endif + { + pVeh->m_ucmd.rightmove = Q_irand( -64, 64 ); + } + else + { + pVeh->m_ucmd.rightmove = 0; + } + pVeh->m_ucmd.forwardmove = Q_irand( -32, 127 ); + pVeh->m_ucmd.upmove = Q_irand( -127, 127 ); + pVeh->m_vOrientation[YAW] += Q_flrand( -10, 10 ); + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + if ( pVeh->m_LandTrace.fraction != 0.0f ) + { + parentPS->velocity[2] -= pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + } +*/ + // If no one is in this vehicle and it's up in the sky, pitch it forward as it comes tumbling down. +#ifdef QAGAME //never gonna happen on client anyway, we can't be getting predicted unless the predicting client is boarded + if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) + && pVeh->m_LandTrace.fraction >= groundFraction + && !FighterIsInSpace( (gentity_t *)parent ) + && !FighterSuspended( pVeh, parentPS ) ) + { + pVeh->m_ucmd.upmove = 0; + //pVeh->m_ucmd.forwardmove = 0; + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + } + } +#endif + + if ( !parentPS->hackingTime ) + {//use that roll + pVeh->m_vOrientation[ROLL] = curRoll; + //NOTE: this seems really backwards... + if ( pVeh->m_vOrientation[ROLL] ) + { //continually adjust the yaw based on the roll.. + if ( (pVeh->m_iRemovedSurfaces||parentPS->electrifyTime>=curTime)//spiralling out of control + && (!(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5)) ) + {//leave YAW alone + } + else + { + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + pVeh->m_vOrientation[YAW] -= ((pVeh->m_vOrientation[ROLL])*0.05f)*pVeh->m_fTimeModifier; + } + } + } + } + else + {//add in strafing roll + float strafeRoll = (parentPS->hackingTime/MAX_STRAFE_TIME)*pVeh->m_pVehicleInfo->rollLimit;//pVeh->m_pVehicleInfo->bankingSpeed* + float strafeDif = AngleSubtract(strafeRoll, pVeh->m_vOrientation[ROLL]); + pVeh->m_vOrientation[ROLL] += (strafeDif*0.1f)*pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + {//cap it reasonably + if ( pVeh->m_pVehicleInfo->rollLimit != -1 + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTimem_vOrientation[ROLL] > pVeh->m_pVehicleInfo->rollLimit ) + { + pVeh->m_vOrientation[ROLL] = pVeh->m_pVehicleInfo->rollLimit; + } + else if (pVeh->m_vOrientation[ROLL] < -pVeh->m_pVehicleInfo->rollLimit) + { + pVeh->m_vOrientation[ROLL] = -pVeh->m_pVehicleInfo->rollLimit; + } + } + } + } + + if (pVeh->m_pVehicleInfo->surfDestruction) + { + FighterDamageRoutine(pVeh, parent, parentPS, riderPS, isDead); + } + pVeh->m_vOrientation[ROLL] = AngleNormalize180( pVeh->m_vOrientation[ROLL] ); + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef QAGAME //ONLY in SP or on server, not cgame + +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); + +// This function makes sure that the vehicle is properly animated. +static void AnimateVehicle( Vehicle_t *pVeh ) +{ + int Anim = -1; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + qboolean isLanding = qfalse, isLanded = qfalse; +#ifdef _JK2MP + playerState_t *parentPS = pVeh->m_pParentEntity->playerState; +#else + playerState_t *parentPS = &pVeh->m_pParentEntity->client->ps; +#endif +#ifndef _JK2MP//SP + //nothing +#elif QAGAME//MP GAME + int curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + int curTime = pm->cmd.serverTime; +#endif + +#ifdef _JK2MP + if ( parentPS->hyperSpaceTime + && curTime - parentPS->hyperSpaceTime < HYPERSPACE_TIME ) + {//Going to Hyperspace + //close the wings (FIXME: makes sense on X-Wing, not Shuttle?) + if ( pVeh->m_ulFlags & VEH_WINGSOPEN ) + { + pVeh->m_ulFlags &= ~VEH_WINGSOPEN; + Anim = BOTH_WINGS_CLOSE; + } + } + else +#endif + { + isLanding = FighterIsLanding( pVeh, parentPS ); + isLanded = FighterIsLanded( pVeh, parentPS ); + + // if we're above launch height (way up in the air)... + if ( !isLanding && !isLanded ) + { + if ( !( pVeh->m_ulFlags & VEH_WINGSOPEN ) ) + { + pVeh->m_ulFlags |= VEH_WINGSOPEN; + pVeh->m_ulFlags &= ~VEH_GEARSOPEN; + Anim = BOTH_WINGS_OPEN; + } + } + // otherwise we're below launch height and still taking off. + else + { + if ( (pVeh->m_ucmd.forwardmove < 0 || pVeh->m_ucmd.upmove < 0||isLanded) + && pVeh->m_LandTrace.fraction <= 0.4f + && pVeh->m_LandTrace.plane.normal[2] >= MIN_LANDING_SLOPE ) + {//already landed or trying to land and close to ground + // Open gears. + if ( !( pVeh->m_ulFlags & VEH_GEARSOPEN ) ) + { +#ifdef _JK2MP + if ( pVeh->m_pVehicleInfo->soundLand ) + {//just landed? +#ifdef QAGAME//MP GAME-side + G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_AUTO, pVeh->m_pVehicleInfo->soundLand ); +#elif CGAME//MP CGAME-side + //trap_S_StartSound( NULL, pVeh->m_pParentEntity->s.number, CHAN_AUTO, pVeh->m_pVehicleInfo->soundLand ); +#endif + } +#endif + pVeh->m_ulFlags |= VEH_GEARSOPEN; + Anim = BOTH_GEARS_OPEN; + } + } + else + {//trying to take off and almost halfway off the ground + // Close gears (if they're open). + if ( pVeh->m_ulFlags & VEH_GEARSOPEN ) + { + pVeh->m_ulFlags &= ~VEH_GEARSOPEN; + Anim = BOTH_GEARS_CLOSE; + //iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + } + // If gears are closed, and we are below launch height, close the wings. + else + { + if ( pVeh->m_ulFlags & VEH_WINGSOPEN ) + { + pVeh->m_ulFlags &= ~VEH_WINGSOPEN; + Anim = BOTH_WINGS_CLOSE; + } + } + } + } + } + + if ( Anim != -1 ) + { + #ifdef _JK2MP + BG_SetAnim(pVeh->m_pParentEntity->playerState, bgAllAnims[pVeh->m_pParentEntity->localAnimIndex].anims, + SETANIM_BOTH, Anim, iFlags, iBlend); + #else + NPC_SetAnim( pVeh->m_pParentEntity, SETANIM_BOTH, Anim, iFlags, iBlend ); + #endif + } +} + +// This function makes sure that the rider's in this vehicle are properly animated. +static void AnimateRiders( Vehicle_t *pVeh ) +{ +} + +#endif //game-only + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +void G_SetFighterVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME //ONLY in SP or on server, not cgame + pVehInfo->AnimateVehicle = AnimateVehicle; + pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; + pVehInfo->Board = Board; + pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; +// pVehInfo->DeathUpdate = DeathUpdate; +// pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; + pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif //game-only + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +void G_CreateFighterNPC( Vehicle_t **pVeh, const char *strType ) +{ + // Allocate the Vehicle. +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); +#else + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); +#endif + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strType )]; +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/codemp/game/JK2_game.def b/codemp/game/JK2_game.def new file mode 100644 index 0000000..290f891 --- /dev/null +++ b/codemp/game/JK2_game.def @@ -0,0 +1,3 @@ +EXPORTS + dllEntry + vmMain diff --git a/codemp/game/JK2_game.dsp b/codemp/game/JK2_game.dsp new file mode 100644 index 0000000..6fbb686 --- /dev/null +++ b/codemp/game/JK2_game.dsp @@ -0,0 +1,650 @@ +# Microsoft Developer Studio Project File - Name="JK2game" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=JK2game - Win32 Release JK2 +!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 "JK2_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 "JK2_game.mak" CFG="JK2game - Win32 Release JK2" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "JK2game - Win32 Release JK2" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "JK2game - Win32 Debug JK2" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "JK2game - Win32 Final JK2" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/jedi/codemp/game", EGAAAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "JK2game - Win32 Release JK2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "JK2game___Win32_Release_TA" +# PROP BASE Intermediate_Dir "JK2game___Win32_Release_TA" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Release" +# PROP Intermediate_Dir "../Release/JK2game" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G6 /W4 /GX /O2 /D "WIN32" /D "NDebug" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /G6 /W4 /GX /Zi /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "MISSIONPACK" /D "QAGAME" /D "_JK2" /YX /FD /c +# SUBTRACT CPP /Fr +# ADD BASE MTL /nologo /D "NDebug" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDebug" /mktyplib203 /win32 +# 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 winmm.lib /nologo /base:"0x20000000" /subsystem:windows /dll /map /machine:I386 /out:"..\Release/qaJK2gamex86.dll" +# SUBTRACT BASE LINK32 /incremental:yes /debug +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /base:"0x20000000" /subsystem:windows /dll /map /debug /machine:I386 /def:".\JK2_game.def" /out:"../Release/jk2mpgamex86.dll" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "JK2game - Win32 Debug JK2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "JK2game___Win32_Debug_TA" +# PROP BASE Intermediate_Dir "JK2game___Win32_Debug_TA" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "../Debug" +# PROP Intermediate_Dir "../Debug/JK2game" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G5 /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_Debug" /D "_WINDOWS" /D "BUILDING_REF_GL" /D "Debug" /FR /YX /FD /c +# ADD CPP /nologo /G6 /MTd /W3 /Gm /GX /ZI /Od /D "_DEBUG" /D "BUILDING_REF_GL" /D "Debug" /D "WIN32" /D "_WINDOWS" /D "MISSIONPACK" /D "QAGAME" /D "_JK2" /D "JK2AWARDS" /FR /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_Debug" /mktyplib203 /win32 +# ADD MTL /nologo /D "_Debug" /mktyplib203 /win32 +# 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 winmm.lib /nologo /base:"0x20000000" /subsystem:windows /dll /map /debug /machine:I386 /out:"..\Debug/qaJK2gamex86.dll" +# SUBTRACT BASE LINK32 /incremental:no +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /base:"0x20000000" /subsystem:windows /dll /map /debug /machine:I386 /def:".\JK2_game.def" /out:"..\Debug\jk2mpgamex86.dll" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "JK2game - Win32 Final JK2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "../Final" +# PROP BASE Intermediate_Dir "../Final/JK2game" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Final" +# PROP Intermediate_Dir "../Final/JK2game" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G6 /W4 /GX /Zi /O2 /I ".." /I "../../jk2/game" /D "NDebug2" /D "WIN32" /D "_WINDOWS" /D "MISSIONPACK" /D "QAGAME" /D "_JK2" /YX /FD /c +# SUBTRACT BASE CPP /Fr +# ADD CPP /nologo /G6 /W4 /GX /O2 /D "NDEBUG" /D "_WINDOWS" /D "MISSIONPACK" /D "QAGAME" /D "WIN32" /D "_JK2" /D "FINAL_BUILD" /YX /FD /c +# ADD BASE MTL /nologo /D "NDebug" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDebug" /mktyplib203 /win32 +# 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 winmm.lib /nologo /base:"0x20000000" /subsystem:windows /dll /map /debug /machine:I386 /def:".\JK2_game.def" /out:"../../Release/jk2mpgamex86.dll" +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /base:"0x20000000" /subsystem:windows /dll /map /machine:I386 /def:".\JK2_game.def" /out:"../Final/jk2mpgamex86.dll" +# SUBTRACT LINK32 /pdb:none /debug + +!ENDIF + +# Begin Target + +# Name "JK2game - Win32 Release JK2" +# Name "JK2game - Win32 Debug JK2" +# Name "JK2game - Win32 Final JK2" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\ai_main.c +# End Source File +# Begin Source File + +SOURCE=.\ai_util.c +# End Source File +# Begin Source File + +SOURCE=.\ai_wpnav.c +# End Source File +# Begin Source File + +SOURCE=.\bg_lib.c +# PROP BASE Exclude_From_Build 1 +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=.\bg_misc.c +# End Source File +# Begin Source File + +SOURCE=.\bg_panimate.c +# End Source File +# Begin Source File + +SOURCE=.\bg_pmove.c +# End Source File +# Begin Source File + +SOURCE=.\bg_saber.c +# End Source File +# Begin Source File + +SOURCE=.\bg_slidemove.c +# End Source File +# Begin Source File + +SOURCE=.\bg_weapons.c +# End Source File +# Begin Source File + +SOURCE=.\g_active.c +# End Source File +# Begin Source File + +SOURCE=.\g_arenas.c +# End Source File +# Begin Source File + +SOURCE=.\g_bot.c +# End Source File +# Begin Source File + +SOURCE=.\g_client.c +# End Source File +# Begin Source File + +SOURCE=.\g_cmds.c +# End Source File +# Begin Source File + +SOURCE=.\g_combat.c +# End Source File +# Begin Source File + +SOURCE=.\g_exphysics.c +# End Source File +# Begin Source File + +SOURCE=.\g_ICARUScb.c +# End Source File +# Begin Source File + +SOURCE=.\g_items.c +# End Source File +# Begin Source File + +SOURCE=.\g_log.c +# End Source File +# Begin Source File + +SOURCE=.\g_main.c +# End Source File +# Begin Source File + +SOURCE=.\g_mem.c +# End Source File +# Begin Source File + +SOURCE=.\g_misc.c +# End Source File +# Begin Source File + +SOURCE=.\g_missile.c +# End Source File +# Begin Source File + +SOURCE=.\g_mover.c +# End Source File +# Begin Source File + +SOURCE=.\g_nav.c +# End Source File +# Begin Source File + +SOURCE=.\g_navnew.c +# End Source File +# Begin Source File + +SOURCE=.\g_object.c +# End Source File +# Begin Source File + +SOURCE=.\g_saga.c +# End Source File +# Begin Source File + +SOURCE=.\g_session.c +# End Source File +# Begin Source File + +SOURCE=.\g_spawn.c +# End Source File +# Begin Source File + +SOURCE=.\g_strap.c +# End Source File +# Begin Source File + +SOURCE=.\g_svcmds.c +# End Source File +# Begin Source File + +SOURCE=.\g_syscalls.c +# End Source File +# Begin Source File + +SOURCE=.\g_target.c +# End Source File +# Begin Source File + +SOURCE=.\g_team.c +# End Source File +# Begin Source File + +SOURCE=.\g_timer.c +# End Source File +# Begin Source File + +SOURCE=.\g_trigger.c +# End Source File +# Begin Source File + +SOURCE=.\g_utils.c +# End Source File +# Begin Source File + +SOURCE=.\g_weapon.c +# End Source File +# Begin Source File + +SOURCE=.\NPC.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Atst.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Default.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Droid.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_GalakMech.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Grenadier.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Howler.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_ImperialProbe.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Interrogator.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Jedi.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Mark1.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Mark2.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_MineMonster.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Remote.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Seeker.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Sentry.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Sniper.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Stormtrooper.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_AI_Utils.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_behavior.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_combat.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_goal.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_misc.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_move.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_reactions.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_senses.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_sounds.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_spawn.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_stats.c +# End Source File +# Begin Source File + +SOURCE=.\NPC_utils.c +# End Source File +# Begin Source File + +SOURCE=.\q_math.c +# End Source File +# Begin Source File + +SOURCE=.\q_shared.c +# End Source File +# Begin Source File + +SOURCE=.\w_force.c +# End Source File +# Begin Source File + +SOURCE=.\w_saber.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Group "ICARUS Headers" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\icarus\interpreter.h +# End Source File +# Begin Source File + +SOURCE=..\icarus\Q3_Interface.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\ai.h +# End Source File +# Begin Source File + +SOURCE=.\ai_main.h +# End Source File +# Begin Source File + +SOURCE=.\anims.h +# End Source File +# Begin Source File + +SOURCE=..\cgame\animtable.h +# End Source File +# Begin Source File + +SOURCE=.\b_local.h +# End Source File +# Begin Source File + +SOURCE=.\b_public.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_char.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_chat.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_gen.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_goal.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_move.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_weap.h +# End Source File +# Begin Source File + +SOURCE=.\be_ea.h +# End Source File +# Begin Source File + +SOURCE=.\bg_lib.h +# End Source File +# Begin Source File + +SOURCE=.\bg_local.h +# End Source File +# Begin Source File + +SOURCE=.\bg_public.h +# End Source File +# Begin Source File + +SOURCE=.\bg_saga.h +# End Source File +# Begin Source File + +SOURCE=.\bg_strap.h +# End Source File +# Begin Source File + +SOURCE=.\bg_weapons.h +# End Source File +# Begin Source File + +SOURCE=.\botlib.h +# End Source File +# Begin Source File + +SOURCE=..\cgame\cg_local.h +# End Source File +# Begin Source File + +SOURCE=..\cgame\cg_public.h +# End Source File +# Begin Source File + +SOURCE=.\chars.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\disablewarnings.h +# End Source File +# Begin Source File + +SOURCE=..\ghoul2\G2.h +# End Source File +# Begin Source File + +SOURCE=.\g_ICARUScb.h +# End Source File +# Begin Source File + +SOURCE=.\g_local.h +# End Source File +# Begin Source File + +SOURCE=.\g_nav.h +# End Source File +# Begin Source File + +SOURCE=.\g_public.h +# End Source File +# Begin Source File + +SOURCE=.\g_team.h +# End Source File +# Begin Source File + +SOURCE=.\inv.h +# End Source File +# Begin Source File + +SOURCE=.\JK2_game.def + +!IF "$(CFG)" == "JK2game - Win32 Release JK2" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "JK2game - Win32 Debug JK2" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "JK2game - Win32 Final JK2" + +# PROP BASE Exclude_From_Build 1 +# PROP Exclude_From_Build 1 + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\match.h +# End Source File +# Begin Source File + +SOURCE=..\..\ui\menudef.h +# End Source File +# Begin Source File + +SOURCE=.\npc_headers.h +# End Source File +# Begin Source File + +SOURCE=.\q_shared.h +# End Source File +# Begin Source File + +SOURCE=.\say.h +# End Source File +# Begin Source File + +SOURCE=.\surfaceflags.h +# End Source File +# Begin Source File + +SOURCE=.\syn.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\tags.h +# End Source File +# Begin Source File + +SOURCE=.\teams.h +# End Source File +# Begin Source File + +SOURCE=..\cgame\tr_types.h +# End Source File +# Begin Source File + +SOURCE=.\w_saber.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\game.bat +# PROP Exclude_From_Build 1 +# End Source File +# Begin Source File + +SOURCE=.\game.q3asm +# PROP Exclude_From_Build 1 +# End Source File +# End Target +# End Project diff --git a/codemp/game/JK2_game.vcproj b/codemp/game/JK2_game.vcproj new file mode 100644 index 0000000..0b01274 --- /dev/null +++ b/codemp/game/JK2_game.vcproj @@ -0,0 +1,744 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/game/NPC.c b/codemp/game/NPC.c new file mode 100644 index 0000000..2264531 --- /dev/null +++ b/codemp/game/NPC.c @@ -0,0 +1,2127 @@ +// +// NPC.cpp - generic functions +// +#include "b_local.h" +#include "anims.h" +#include "say.h" +#include "../icarus/Q3_Interface.h" + +extern vec3_t playerMins; +extern vec3_t playerMaxs; +//extern void PM_SetAnimFinal(int *torsoAnim,int *legsAnim,int type,int anim,int priority,int *torsoAnimTimer,int *legsAnimTimer,gentity_t *gent); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); +extern void NPC_BSNoClip ( void ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void NPC_ApplyRoff (void); +extern void NPC_TempLookTarget ( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern void NPC_CheckPlayerAim ( void ); +extern void NPC_CheckAllClear ( void ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern qboolean NPC_CheckLookTarget( gentity_t *self ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +//extern void Mark1_dying( gentity_t *self ); +extern void NPC_BSCinematic( void ); +extern int GetTime ( int lastTime ); +//extern void NPC_BSGM_Default( void ); +extern void NPC_CheckCharmed( void ); +extern qboolean Boba_Flying( gentity_t *self ); + +extern vmCvar_t g_saberRealisticCombat; + +//Local Variables +gentity_t *NPC; +gNPC_t *NPCInfo; +gclient_t *client; +usercmd_t ucmd; +visibility_t enemyVisibility; + +void NPC_SetAnim(gentity_t *ent,int type,int anim,int priority); +void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope ); +//extern void GM_Dying( gentity_t *self ); + +extern int eventClearTime; + +void CorpsePhysics( gentity_t *self ) +{ + // run the bot through the server like it was a real client + memset( &ucmd, 0, sizeof( ucmd ) ); + ClientThink( self->s.number, &ucmd ); + //VectorCopy( self->s.origin, self->s.origin2 ); + //rww - don't get why this is happening. + + if ( self->client->NPC_class == CLASS_GALAKMECH ) + { + assert( 0 ); +// GM_Dying( self ); + } + //FIXME: match my pitch and roll for the slope of my groundPlane + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !(self->s.eFlags&EF_DISINTEGRATION) ) + {//on the ground + //FIXME: check 4 corners + pitch_roll_for_slope( self, NULL ); + } + + if ( eventClearTime == level.time + ALERT_CLEAR_TIME ) + {//events were just cleared out so add me again + if ( !(self->client->ps.eFlags&EF_NODRAW) ) + { + AddSightEvent( self->enemy, self->r.currentOrigin, 384, AEL_DISCOVERED, 0.0f ); + } + } + + if ( level.time - self->s.time > 3000 ) + {//been dead for 3 seconds + if ( g_dismember.integer < 11381138 && !g_saberRealisticCombat.integer ) + {//can't be dismembered once dead + if ( self->client->NPC_class != CLASS_PROTOCOL ) + { + // self->client->dismembered = qtrue; + } + } + } + + //if ( level.time - self->s.time > 500 ) + if (self->client->respawnTime < (level.time+500)) + {//don't turn "nonsolid" until about 1 second after actual death + + if (self->client->ps.eFlags & EF_DISINTEGRATION) + { + self->r.contents = 0; + } + else if ((self->client->NPC_class != CLASS_MARK1) && (self->client->NPC_class != CLASS_INTERROGATOR)) // The Mark1 & Interrogator stays solid. + { + self->r.contents = CONTENTS_CORPSE; + //self->r.maxs[2] = -8; + } + + if ( self->message ) + { + self->r.contents |= CONTENTS_TRIGGER; + } + } +} + +/* +---------------------------------------- +NPC_RemoveBody + +Determines when it's ok to ditch the corpse +---------------------------------------- +*/ +#define REMOVE_DISTANCE 128 +#define REMOVE_DISTANCE_SQR (REMOVE_DISTANCE * REMOVE_DISTANCE) + +void NPC_RemoveBody( gentity_t *self ) +{ + CorpsePhysics( self ); + + self->nextthink = level.time + FRAMETIME; + + if ( self->NPC->nextBStateThink <= level.time ) + { + trap_ICARUS_MaintainTaskManager(self->s.number); + } + self->NPC->nextBStateThink = level.time + FRAMETIME; + + if ( self->message ) + {//I still have a key + return; + } + + // I don't consider this a hack, it's creative coding . . . + // I agree, very creative... need something like this for ATST and GALAKMECH too! + if (self->client->NPC_class == CLASS_MARK1) + { + assert( 0 ); +// Mark1_dying( self ); + } + + // Since these blow up, remove the bounding box. + if ( self->client->NPC_class == CLASS_REMOTE + || self->client->NPC_class == CLASS_SENTRY + || self->client->NPC_class == CLASS_PROBE + || self->client->NPC_class == CLASS_INTERROGATOR + || self->client->NPC_class == CLASS_PROBE + || self->client->NPC_class == CLASS_MARK2 ) + { + //if ( !self->taskManager || !self->taskManager->IsRunning() ) + if (!trap_ICARUS_IsRunning(self->s.number)) + { + if ( !self->activator || !self->activator->client || !(self->activator->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//not being held by a Rancor + G_FreeEntity( self ); + } + } + return; + } + + //FIXME: don't ever inflate back up? + self->r.maxs[2] = self->client->renderInfo.eyePoint[2] - self->r.currentOrigin[2] + 4; + if ( self->r.maxs[2] < -8 ) + { + self->r.maxs[2] = -8; + } + + if ( self->client->NPC_class == CLASS_GALAKMECH ) + {//never disappears + return; + } + if ( self->NPC && self->NPC->timeOfDeath <= level.time ) + { + self->NPC->timeOfDeath = level.time + 1000; + // Only do all of this nonsense for Scav boys ( and girls ) + /// if ( self->client->playerTeam == NPCTEAM_SCAVENGERS || self->client->playerTeam == NPCTEAM_KLINGON + // || self->client->playerTeam == NPCTEAM_HIROGEN || self->client->playerTeam == NPCTEAM_MALON ) + // should I check NPC_class here instead of TEAM ? - dmv + if( self->client->playerTeam == NPCTEAM_ENEMY || self->client->NPC_class == CLASS_PROTOCOL ) + { + self->nextthink = level.time + FRAMETIME; // try back in a second + + /* + if ( DistanceSquared( g_entities[0].r.currentOrigin, self->r.currentOrigin ) <= REMOVE_DISTANCE_SQR ) + { + return; + } + + if ( (InFOV( self, &g_entities[0], 110, 90 )) ) // generous FOV check + { + if ( (NPC_ClearLOS2( &g_entities[0], self->r.currentOrigin )) ) + { + return; + } + } + */ + //Don't care about this for MP I guess. + } + + //FIXME: there are some conditions - such as heavy combat - in which we want + // to remove the bodies... but in other cases it's just weird, like + // when they're right behind you in a closed room and when they've been + // placed as dead NPCs by a designer... + // For now we just assume that a corpse with no enemy was + // placed in the map as a corpse + if ( self->enemy ) + { + //if ( !self->taskManager || !self->taskManager->IsRunning() ) + if (!trap_ICARUS_IsRunning(self->s.number)) + { + if ( !self->activator || !self->activator->client || !(self->activator->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//not being held by a Rancor + if ( self->client && self->client->ps.saberEntityNum > 0 && self->client->ps.saberEntityNum < ENTITYNUM_WORLD ) + { + gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum]; + if ( saberent ) + { + G_FreeEntity( saberent ); + } + } + G_FreeEntity( self ); + } + } + } + } +} + +/* +---------------------------------------- +NPC_RemoveBody + +Determines when it's ok to ditch the corpse +---------------------------------------- +*/ + +int BodyRemovalPadTime( gentity_t *ent ) +{ + int time; + + if ( !ent || !ent->client ) + return 0; +/* + switch ( ent->client->playerTeam ) + { + case NPCTEAM_KLINGON: // no effect, we just remove them when the player isn't looking + case NPCTEAM_SCAVENGERS: + case NPCTEAM_HIROGEN: + case NPCTEAM_MALON: + case NPCTEAM_IMPERIAL: + case NPCTEAM_STARFLEET: + time = 10000; // 15 secs. + break; + + case NPCTEAM_BORG: + time = 2000; + break; + + case NPCTEAM_STASIS: + return qtrue; + break; + + case NPCTEAM_FORGE: + time = 1000; + break; + + case NPCTEAM_BOTS: +// if (!Q_stricmp( ent->NPC_type, "mouse" )) +// { + time = 0; +// } +// else +// { +// time = 10000; +// } + break; + + case NPCTEAM_8472: + time = 2000; + break; + + default: + // never go away + time = Q3_INFINITE; + break; + } +*/ + // team no longer indicates species/race, so in this case we'd use NPC_class, but + switch( ent->client->NPC_class ) + { + case CLASS_MOUSE: + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + //case CLASS_PROTOCOL: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_PROBE: + case CLASS_SEEKER: + case CLASS_REMOTE: + case CLASS_SENTRY: + case CLASS_INTERROGATOR: + time = 0; + break; + default: + // never go away + // time = Q3_INFINITE; + // for now I'm making default 10000 + time = 10000; + break; + + } + + + return time; +} + + +/* +---------------------------------------- +NPC_RemoveBodyEffect + +Effect to be applied when ditching the corpse +---------------------------------------- +*/ + +static void NPC_RemoveBodyEffect(void) +{ +// vec3_t org; +// gentity_t *tent; + + if ( !NPC || !NPC->client || (NPC->s.eFlags & EF_NODRAW) ) + return; +/* + switch(NPC->client->playerTeam) + { + case NPCTEAM_STARFLEET: + //FIXME: Starfleet beam out + break; + + case NPCTEAM_BOTS: +// VectorCopy( NPC->r.currentOrigin, org ); +// org[2] -= 16; +// tent = G_TempEntity( org, EV_BOT_EXPLODE ); +// tent->owner = NPC; + + break; + + default: + break; + } +*/ + + + // team no longer indicates species/race, so in this case we'd use NPC_class, but + + // stub code + switch(NPC->client->NPC_class) + { + case CLASS_PROBE: + case CLASS_SEEKER: + case CLASS_REMOTE: + case CLASS_SENTRY: + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + //case CLASS_PROTOCOL: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_INTERROGATOR: + case CLASS_ATST: // yeah, this is a little weird, but for now I'm listing all droids + // VectorCopy( NPC->r.currentOrigin, org ); + // org[2] -= 16; + // tent = G_TempEntity( org, EV_BOT_EXPLODE ); + // tent->owner = NPC; + break; + default: + break; + } + + +} + + +/* +==================================================================== +void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope ) + +MG + +This will adjust the pitch and roll of a monster to match +a given slope - if a non-'0 0 0' slope is passed, it will +use that value, otherwise it will use the ground underneath +the monster. If it doesn't find a surface, it does nothinh\g +and returns. +==================================================================== +*/ + +void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope ) +{ + vec3_t slope; + vec3_t nvf, ovf, ovr, startspot, endspot, new_angles = { 0, 0, 0 }; + float pitch, mod, dot; + + //if we don't have a slope, get one + if( !pass_slope || VectorCompare( vec3_origin, pass_slope ) ) + { + trace_t trace; + + VectorCopy( forwhom->r.currentOrigin, startspot ); + startspot[2] += forwhom->r.mins[2] + 4; + VectorCopy( startspot, endspot ); + endspot[2] -= 300; + trap_Trace( &trace, forwhom->r.currentOrigin, vec3_origin, vec3_origin, endspot, forwhom->s.number, MASK_SOLID ); +// if(trace_fraction>0.05&&forwhom.movetype==MOVETYPE_STEP) +// forwhom.flags(-)FL_ONGROUND; + + if ( trace.fraction >= 1.0 ) + return; + + if( !( &trace.plane ) ) + return; + + if ( VectorCompare( vec3_origin, trace.plane.normal ) ) + return; + + VectorCopy( trace.plane.normal, slope ); + } + else + { + VectorCopy( pass_slope, slope ); + } + + + AngleVectors( forwhom->r.currentAngles, ovf, ovr, NULL ); + + vectoangles( slope, new_angles ); + pitch = new_angles[PITCH] + 90; + new_angles[ROLL] = new_angles[PITCH] = 0; + + AngleVectors( new_angles, nvf, NULL, NULL ); + + mod = DotProduct( nvf, ovr ); + + if ( mod<0 ) + mod = -1; + else + mod = 1; + + dot = DotProduct( nvf, ovf ); + + if ( forwhom->client ) + { + float oldmins2; + + forwhom->client->ps.viewangles[PITCH] = dot * pitch; + forwhom->client->ps.viewangles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + oldmins2 = forwhom->r.mins[2]; + forwhom->r.mins[2] = -24 + 12 * fabs(forwhom->client->ps.viewangles[PITCH])/180.0f; + //FIXME: if it gets bigger, move up + if ( oldmins2 > forwhom->r.mins[2] ) + {//our mins is now lower, need to move up + //FIXME: trace? + forwhom->client->ps.origin[2] += (oldmins2 - forwhom->r.mins[2]); + forwhom->r.currentOrigin[2] = forwhom->client->ps.origin[2]; + trap_LinkEntity( forwhom ); + } + } + else + { + forwhom->r.currentAngles[PITCH] = dot * pitch; + forwhom->r.currentAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + } +} + + +/* +---------------------------------------- +DeadThink +---------------------------------------- +*/ +static void DeadThink ( void ) +{ + trace_t trace; + + //HACKHACKHACKHACKHACK + //We should really have a seperate G2 bounding box (seperate from the physics bbox) for G2 collisions only + //FIXME: don't ever inflate back up? + NPC->r.maxs[2] = NPC->client->renderInfo.eyePoint[2] - NPC->r.currentOrigin[2] + 4; + if ( NPC->r.maxs[2] < -8 ) + { + NPC->r.maxs[2] = -8; + } + if ( VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//not flying through the air + if ( NPC->r.mins[0] > -32 ) + { + NPC->r.mins[0] -= 1; + trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->r.mins[0] += 1; + } + } + if ( NPC->r.maxs[0] < 32 ) + { + NPC->r.maxs[0] += 1; + trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->r.maxs[0] -= 1; + } + } + if ( NPC->r.mins[1] > -32 ) + { + NPC->r.mins[1] -= 1; + trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->r.mins[1] += 1; + } + } + if ( NPC->r.maxs[1] < 32 ) + { + NPC->r.maxs[1] += 1; + trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->r.maxs[1] -= 1; + } + } + } + //HACKHACKHACKHACKHACK + + //FIXME: tilt and fall off of ledges? + //NPC_PostDeathThink(); + + /* + if ( !NPCInfo->timeOfDeath && NPC->client != NULL && NPCInfo != NULL ) + { + //haven't finished death anim yet and were NOT given a specific amount of time to wait before removal + int legsAnim = NPC->client->ps.legsAnim; + animation_t *animations = knownAnimFileSets[NPC->client->clientInfo.animFileIndex].animations; + + NPC->bounceCount = -1; // This is a cheap hack for optimizing the pointcontents check below + + //ghoul doesn't tell us this anymore + //if ( NPC->client->renderInfo.legsFrame == animations[legsAnim].firstFrame + (animations[legsAnim].numFrames - 1) ) + { + //reached the end of the death anim + NPCInfo->timeOfDeath = level.time + BodyRemovalPadTime( NPC ); + } + } + else + */ + { + //death anim done (or were given a specific amount of time to wait before removal), wait the requisite amount of time them remove + if ( level.time >= NPCInfo->timeOfDeath + BodyRemovalPadTime( NPC ) ) + { + if ( NPC->client->ps.eFlags & EF_NODRAW ) + { + if (!trap_ICARUS_IsRunning(NPC->s.number)) + //if ( !NPC->taskManager || !NPC->taskManager->IsRunning() ) + { + NPC->think = G_FreeEntity; + NPC->nextthink = level.time + FRAMETIME; + } + } + else + { + class_t npc_class; + + // Start the body effect first, then delay 400ms before ditching the corpse + NPC_RemoveBodyEffect(); + + //FIXME: keep it running through physics somehow? + NPC->think = NPC_RemoveBody; + NPC->nextthink = level.time + FRAMETIME; + // if ( NPC->client->playerTeam == NPCTEAM_FORGE ) + // NPCInfo->timeOfDeath = level.time + FRAMETIME * 8; + // else if ( NPC->client->playerTeam == NPCTEAM_BOTS ) + npc_class = NPC->client->NPC_class; + // check for droids + if ( npc_class == CLASS_SEEKER || npc_class == CLASS_REMOTE || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || + npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || + npc_class == CLASS_MARK2 || npc_class == CLASS_SENTRY )//npc_class == CLASS_PROTOCOL || + { + NPC->client->ps.eFlags |= EF_NODRAW; + NPCInfo->timeOfDeath = level.time + FRAMETIME * 8; + } + else + NPCInfo->timeOfDeath = level.time + FRAMETIME * 4; + } + return; + } + } + + // If the player is on the ground and the resting position contents haven't been set yet...(BounceCount tracks the contents) + if ( NPC->bounceCount < 0 && NPC->s.groundEntityNum >= 0 ) + { + // if client is in a nodrop area, make him/her nodraw + int contents = NPC->bounceCount = trap_PointContents( NPC->r.currentOrigin, -1 ); + + if ( ( contents & CONTENTS_NODROP ) ) + { + NPC->client->ps.eFlags |= EF_NODRAW; + } + } + + CorpsePhysics( NPC ); +} + + +/* +=============== +SetNPCGlobals + +local function to set globals used throughout the AI code +=============== +*/ +void SetNPCGlobals( gentity_t *ent ) +{ + NPC = ent; + NPCInfo = ent->NPC; + client = ent->client; + memset( &ucmd, 0, sizeof( usercmd_t ) ); +} + +gentity_t *_saved_NPC; +gNPC_t *_saved_NPCInfo; +gclient_t *_saved_client; +usercmd_t _saved_ucmd; + +void SaveNPCGlobals(void) +{ + _saved_NPC = NPC; + _saved_NPCInfo = NPCInfo; + _saved_client = client; + memcpy( &_saved_ucmd, &ucmd, sizeof( usercmd_t ) ); +} + +void RestoreNPCGlobals(void) +{ + NPC = _saved_NPC; + NPCInfo = _saved_NPCInfo; + client = _saved_client; + memcpy( &ucmd, &_saved_ucmd, sizeof( usercmd_t ) ); +} + +//We MUST do this, other funcs were using NPC illegally when "self" wasn't the global NPC +void ClearNPCGlobals( void ) +{ + NPC = NULL; + NPCInfo = NULL; + client = NULL; +} +//=============== + +extern qboolean showBBoxes; +vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0}; +vec3_t NPCDEBUG_GREEN = {0.0, 1.0, 0.0}; +vec3_t NPCDEBUG_BLUE = {0.0, 0.0, 1.0}; +vec3_t NPCDEBUG_LIGHT_BLUE = {0.3f, 0.7f, 1.0}; +extern void G_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ); +extern void G_Line( vec3_t start, vec3_t end, vec3_t color, float alpha ); +extern void G_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color ); + +void NPC_ShowDebugInfo (void) +{ + if ( showBBoxes ) + { + gentity_t *found = NULL; + vec3_t mins, maxs; + + while( (found = G_Find( found, FOFS(classname), "NPC" ) ) != NULL ) + { + if ( trap_InPVS( found->r.currentOrigin, g_entities[0].r.currentOrigin ) ) + { + VectorAdd( found->r.currentOrigin, found->r.mins, mins ); + VectorAdd( found->r.currentOrigin, found->r.maxs, maxs ); + G_Cube( mins, maxs, NPCDEBUG_RED, 0.25 ); + } + } + } +} + +void NPC_ApplyScriptFlags (void) +{ + if ( NPCInfo->scriptFlags & SCF_CROUCHED ) + { + if ( NPCInfo->charmedTime > level.time && (ucmd.forwardmove || ucmd.rightmove) ) + {//ugh, if charmed and moving, ignore the crouched command + } + else + { + ucmd.upmove = -127; + } + } + + if(NPCInfo->scriptFlags & SCF_RUNNING) + { + ucmd.buttons &= ~BUTTON_WALKING; + } + else if(NPCInfo->scriptFlags & SCF_WALKING) + { + if ( NPCInfo->charmedTime > level.time && (ucmd.forwardmove || ucmd.rightmove) ) + {//ugh, if charmed and moving, ignore the walking command + } + else + { + ucmd.buttons |= BUTTON_WALKING; + } + } +/* + if(NPCInfo->scriptFlags & SCF_CAREFUL) + { + ucmd.buttons |= BUTTON_CAREFUL; + } +*/ + if(NPCInfo->scriptFlags & SCF_LEAN_RIGHT) + { + ucmd.buttons |= BUTTON_USE; + ucmd.rightmove = 127; + ucmd.forwardmove = 0; + ucmd.upmove = 0; + } + else if(NPCInfo->scriptFlags & SCF_LEAN_LEFT) + { + ucmd.buttons |= BUTTON_USE; + ucmd.rightmove = -127; + ucmd.forwardmove = 0; + ucmd.upmove = 0; + } + + if ( (NPCInfo->scriptFlags & SCF_ALT_FIRE) && (ucmd.buttons & BUTTON_ATTACK) ) + {//Use altfire instead + ucmd.buttons |= BUTTON_ALT_ATTACK; + } +} + +void Q3_DebugPrint( int level, const char *format, ... ); +void NPC_HandleAIFlags (void) +{ + //FIXME: make these flags checks a function call like NPC_CheckAIFlagsAndTimers + if ( NPCInfo->aiFlags & NPCAI_LOST ) + {//Print that you need help! + //FIXME: shouldn't remove this just yet if cg_draw needs it + NPCInfo->aiFlags &= ~NPCAI_LOST; + + /* + if ( showWaypoints ) + { + Q3_DebugPrint(WL_WARNING, "%s can't navigate to target %s (my wp: %d, goal wp: %d)\n", NPC->targetname, NPCInfo->goalEntity->targetname, NPC->waypoint, NPCInfo->goalEntity->waypoint ); + } + */ + + if ( NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) + {//We can't nav to our enemy + //Drop enemy and see if we should search for him + NPC_LostEnemyDecideChase(); + } + } + + //MRJ Request: + /* + if ( NPCInfo->aiFlags & NPCAI_GREET_ALLIES && !NPC->enemy )//what if "enemy" is the greetEnt? + {//If no enemy, look for teammates to greet + //FIXME: don't say hi to the same guy over and over again. + if ( NPCInfo->greetingDebounceTime < level.time ) + {//Has been at least 2 seconds since we greeted last + if ( !NPCInfo->greetEnt ) + {//Find a teammate whom I'm facing and who is facing me and within 128 + NPCInfo->greetEnt = NPC_PickAlly( qtrue, 128, qtrue, qtrue ); + } + + if ( NPCInfo->greetEnt && !Q_irand(0, 5) ) + {//Start greeting someone + qboolean greeted = qfalse; + + //TODO: If have a greetscript, run that instead? + + //FIXME: make them greet back? + if( !Q_irand( 0, 2 ) ) + {//Play gesture anim (press gesture button?) + greeted = qtrue; + NPC_SetAnim( NPC, SETANIM_TORSO, Q_irand( BOTH_GESTURE1, BOTH_GESTURE3 ), SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD ); + //NOTE: play full-body gesture if not moving? + } + + if( !Q_irand( 0, 2 ) ) + {//Play random voice greeting sound + greeted = qtrue; + //FIXME: need NPC sound sets + + //G_AddVoiceEvent( NPC, Q_irand(EV_GREET1, EV_GREET3), 2000 ); + } + + if( !Q_irand( 0, 1 ) ) + {//set looktarget to them for a second or two + greeted = qtrue; + NPC_TempLookTarget( NPC, NPCInfo->greetEnt->s.number, 1000, 3000 ); + } + + if ( greeted ) + {//Did at least one of the things above + //Don't greet again for 2 - 4 seconds + NPCInfo->greetingDebounceTime = level.time + Q_irand( 2000, 4000 ); + NPCInfo->greetEnt = NULL; + } + } + } + } + */ + //been told to play a victory sound after a delay + if ( NPCInfo->greetingDebounceTime && NPCInfo->greetingDebounceTime < level.time ) + { + G_AddVoiceEvent( NPC, Q_irand(EV_VICTORY1, EV_VICTORY3), Q_irand( 2000, 4000 ) ); + NPCInfo->greetingDebounceTime = 0; + } + + if ( NPCInfo->ffireCount > 0 ) + { + if ( NPCInfo->ffireFadeDebounce < level.time ) + { + NPCInfo->ffireCount--; + //Com_Printf( "drop: %d < %d\n", NPCInfo->ffireCount, 3+((2-g_spskill.integer)*2) ); + NPCInfo->ffireFadeDebounce = level.time + 3000; + } + } + if ( d_patched.integer ) + {//use patch-style navigation + if ( NPCInfo->consecutiveBlockedMoves > 20 ) + {//been stuck for a while, try again? + NPCInfo->consecutiveBlockedMoves = 0; + } + } +} + +void NPC_AvoidWallsAndCliffs (void) +{ + //... +} + +void NPC_CheckAttackScript(void) +{ + if(!(ucmd.buttons & BUTTON_ATTACK)) + { + return; + } + + G_ActivateBehavior(NPC, BSET_ATTACK); +} + +float NPC_MaxDistSquaredForWeapon (void); +void NPC_CheckAttackHold(void) +{ + vec3_t vec; + + // If they don't have an enemy they shouldn't hold their attack anim. + if ( !NPC->enemy ) + { + NPCInfo->attackHoldTime = 0; + return; + } + +/* if ( ( NPC->client->ps.weapon == WP_BORG_ASSIMILATOR ) || ( NPC->client->ps.weapon == WP_BORG_DRILL ) ) + {//FIXME: don't keep holding this if can't hit enemy? + + // If they don't have shields ( been disabled) they shouldn't hold their attack anim. + if ( !(NPC->NPC->aiFlags & NPCAI_SHIELDS) ) + { + NPCInfo->attackHoldTime = 0; + return; + } + + VectorSubtract(NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec); + if( VectorLengthSquared(vec) > NPC_MaxDistSquaredForWeapon() ) + { + NPCInfo->attackHoldTime = 0; + PM_SetTorsoAnimTimer(NPC, &NPC->client->ps.torsoAnimTimer, 0); + } + else if( NPCInfo->attackHoldTime && NPCInfo->attackHoldTime > level.time ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + else if ( ( NPCInfo->attackHold ) && ( ucmd.buttons & BUTTON_ATTACK ) ) + { + NPCInfo->attackHoldTime = level.time + NPCInfo->attackHold; + PM_SetTorsoAnimTimer(NPC, &NPC->client->ps.torsoAnimTimer, NPCInfo->attackHold); + } + else + { + NPCInfo->attackHoldTime = 0; + PM_SetTorsoAnimTimer(NPC, &NPC->client->ps.torsoAnimTimer, 0); + } + } + else*/ + {//everyone else...? FIXME: need to tie this into AI somehow? + VectorSubtract(NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec); + if( VectorLengthSquared(vec) > NPC_MaxDistSquaredForWeapon() ) + { + NPCInfo->attackHoldTime = 0; + } + else if( NPCInfo->attackHoldTime && NPCInfo->attackHoldTime > level.time ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + else if ( ( NPCInfo->attackHold ) && ( ucmd.buttons & BUTTON_ATTACK ) ) + { + NPCInfo->attackHoldTime = level.time + NPCInfo->attackHold; + } + else + { + NPCInfo->attackHoldTime = 0; + } + } +} + +/* +void NPC_KeepCurrentFacing(void) + +Fills in a default ucmd to keep current angles facing +*/ +void NPC_KeepCurrentFacing(void) +{ + if(!ucmd.angles[YAW]) + { + ucmd.angles[YAW] = ANGLE2SHORT( client->ps.viewangles[YAW] ) - client->ps.delta_angles[YAW]; + } + + if(!ucmd.angles[PITCH]) + { + ucmd.angles[PITCH] = ANGLE2SHORT( client->ps.viewangles[PITCH] ) - client->ps.delta_angles[PITCH]; + } +} + +/* +------------------------- +NPC_BehaviorSet_Charmed +------------------------- +*/ + +void NPC_BehaviorSet_Charmed( int bState ) +{ + switch( bState ) + { + case BS_FOLLOW_LEADER://# 40: Follow your leader and shoot any enemies you come across + NPC_BSFollowLeader(); + break; + case BS_REMOVE: + NPC_BSRemove(); + break; + case BS_SEARCH: //# 43: Using current waypoint as a base, search the immediate branches of waypoints for enemies + NPC_BSSearch(); + break; + case BS_WANDER: //# 46: Wander down random waypoint paths + NPC_BSWander(); + break; + case BS_FLEE: + NPC_BSFlee(); + break; + default: + case BS_DEFAULT://whatever + NPC_BSDefault(); + break; + } +} +/* +------------------------- +NPC_BehaviorSet_Default +------------------------- +*/ + +void NPC_BehaviorSet_Default( int bState ) +{ + switch( bState ) + { + case BS_ADVANCE_FIGHT://head toward captureGoal, shoot anything that gets in the way + NPC_BSAdvanceFight (); + break; + case BS_SLEEP://Follow a path, looking for enemies + NPC_BSSleep (); + break; + case BS_FOLLOW_LEADER://# 40: Follow your leader and shoot any enemies you come across + NPC_BSFollowLeader(); + break; + case BS_JUMP: //41: Face navgoal and jump to it. + NPC_BSJump(); + break; + case BS_REMOVE: + NPC_BSRemove(); + break; + case BS_SEARCH: //# 43: Using current waypoint as a base, search the immediate branches of waypoints for enemies + NPC_BSSearch(); + break; + case BS_NOCLIP: + NPC_BSNoClip(); + break; + case BS_WANDER: //# 46: Wander down random waypoint paths + NPC_BSWander(); + break; + case BS_FLEE: + NPC_BSFlee(); + break; + case BS_WAIT: + NPC_BSWait(); + break; + case BS_CINEMATIC: + NPC_BSCinematic(); + break; + default: + case BS_DEFAULT://whatever + NPC_BSDefault(); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Interrogator +------------------------- +*/ +/* +void NPC_BehaviorSet_Interrogator( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSInterrogator_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} +*/ + +void NPC_BSImperialProbe_Attack( void ); +void NPC_BSImperialProbe_Patrol( void ); +void NPC_BSImperialProbe_Wait(void); + +/* +------------------------- +NPC_BehaviorSet_ImperialProbe +------------------------- +*/ +void NPC_BehaviorSet_ImperialProbe( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSImperialProbe_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + + +void NPC_BSSeeker_Default( void ); + +/* +------------------------- +NPC_BehaviorSet_Seeker +------------------------- +*/ +void NPC_BehaviorSet_Seeker( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSSeeker_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +void NPC_BSRemote_Default( void ); + +/* +------------------------- +NPC_BehaviorSet_Remote +------------------------- +*/ +void NPC_BehaviorSet_Remote( int bState ) +{ + NPC_BSRemote_Default(); +} + +void NPC_BSSentry_Default( void ); + +/* +------------------------- +NPC_BehaviorSet_Sentry +------------------------- +*/ +void NPC_BehaviorSet_Sentry( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSSentry_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Grenadier +------------------------- +*/ +void NPC_BehaviorSet_Grenadier( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSGrenadier_Default(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} +/* +------------------------- +NPC_BehaviorSet_Sniper +------------------------- +*/ +void NPC_BehaviorSet_Sniper( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSSniper_Default(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} +/* +------------------------- +NPC_BehaviorSet_Stormtrooper +------------------------- +*/ + +void NPC_BehaviorSet_Stormtrooper( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSST_Default(); + break; + + case BS_INVESTIGATE: + NPC_BSST_Investigate(); + break; + + case BS_SLEEP: + NPC_BSST_Sleep(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Jedi +------------------------- +*/ + +void NPC_BehaviorSet_Jedi( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSJedi_Default(); + break; + + case BS_FOLLOW_LEADER: + NPC_BSJedi_FollowLeader(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Droid +------------------------- +*/ +void NPC_BehaviorSet_Droid( int bState ) +{ + switch( bState ) + { + case BS_DEFAULT: + case BS_STAND_GUARD: + case BS_PATROL: + NPC_BSDroid_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Mark1 +------------------------- +*/ +void NPC_BehaviorSet_Mark1( int bState ) +{ + assert( 0 ); +/* + switch( bState ) + { + case BS_DEFAULT: + case BS_STAND_GUARD: + case BS_PATROL: + NPC_BSMark1_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +*/ +} + +/* +------------------------- +NPC_BehaviorSet_Mark2 +------------------------- +*/ +void NPC_BehaviorSet_Mark2( int bState ) +{ + assert( 0 ); +/* + switch( bState ) + { + case BS_DEFAULT: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + NPC_BSMark2_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +*/ +} + +/* +------------------------- +NPC_BehaviorSet_ATST +------------------------- +*/ +void NPC_BehaviorSet_ATST( int bState ) +{ + assert( 0 ); +/* + switch( bState ) + { + case BS_DEFAULT: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + NPC_BSATST_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +*/ +} + +/* +------------------------- +NPC_BehaviorSet_MineMonster +------------------------- +*/ +void NPC_BehaviorSet_MineMonster( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSMineMonster_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Howler +------------------------- +*/ +void NPC_BehaviorSet_Howler( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSHowler_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Rancor +------------------------- +*/ +void NPC_BehaviorSet_Rancor( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSRancor_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_RunBehavior +------------------------- +*/ +extern void NPC_BSEmplaced( void ); +extern qboolean NPC_CheckSurrender( void ); +extern void Boba_FlyStop( gentity_t *self ); +extern void NPC_BSWampa_Default( void ); +void NPC_RunBehavior( int team, int bState ) +{ + qboolean dontSetAim = qfalse; + + if (NPC->s.NPC_class == CLASS_VEHICLE && + NPC->m_pVehicle) + { //vehicles don't do AI! + return; + } + + if ( bState == BS_CINEMATIC ) + { + NPC_BSCinematic(); + } + else if ( NPC->client->ps.weapon == WP_EMPLACED_GUN ) + { + NPC_BSEmplaced(); + NPC_CheckCharmed(); + return; + } + else if ( NPC->client->ps.weapon == WP_SABER ) + {//jedi + NPC_BehaviorSet_Jedi( bState ); + dontSetAim = qtrue; + } + else if ( NPC->client->NPC_class == CLASS_WAMPA ) + {//wampa + NPC_BSWampa_Default(); + } + else if ( NPC->client->NPC_class == CLASS_RANCOR ) + {//rancor + NPC_BehaviorSet_Rancor( bState ); + } + else if ( NPC->client->NPC_class == CLASS_REMOTE ) + { + NPC_BehaviorSet_Remote( bState ); + } + else if ( NPC->client->NPC_class == CLASS_SEEKER ) + { + NPC_BehaviorSet_Seeker( bState ); + } + else if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + {//bounty hunter + if ( Boba_Flying( NPC ) ) + { + NPC_BehaviorSet_Seeker(bState); + } + else + { + NPC_BehaviorSet_Jedi( bState ); + } + dontSetAim = qtrue; + } + else if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) + {//being forced to march + NPC_BSDefault(); + } + else + { + switch( team ) + { + + // case NPCTEAM_SCAVENGERS: + // case NPCTEAM_IMPERIAL: + // case NPCTEAM_KLINGON: + // case NPCTEAM_HIROGEN: + // case NPCTEAM_MALON: + // not sure if TEAM_ENEMY is appropriate here, I think I should be using NPC_class to check for behavior - dmv + case NPCTEAM_ENEMY: + // special cases for enemy droids + switch( NPC->client->NPC_class) + { + case CLASS_ATST: + NPC_BehaviorSet_ATST( bState ); + return; + case CLASS_PROBE: + NPC_BehaviorSet_ImperialProbe(bState); + return; + case CLASS_REMOTE: + NPC_BehaviorSet_Remote( bState ); + return; + case CLASS_SENTRY: + NPC_BehaviorSet_Sentry(bState); + return; + case CLASS_INTERROGATOR: + assert( 0 ); +// NPC_BehaviorSet_Interrogator( bState ); + return; + case CLASS_MINEMONSTER: + NPC_BehaviorSet_MineMonster( bState ); + return; + case CLASS_HOWLER: + NPC_BehaviorSet_Howler( bState ); + return; + case CLASS_MARK1: + assert( 0 ); +// NPC_BehaviorSet_Mark1( bState ); + return; + case CLASS_MARK2: + assert( 0 ); +// NPC_BehaviorSet_Mark2( bState ); + return; + case CLASS_GALAKMECH: + assert( 0 ); +// NPC_BSGM_Default(); + return; + + } + + if ( NPC->enemy && NPC->s.weapon == WP_NONE && bState != BS_HUNT_AND_KILL && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//if in battle and have no weapon, run away, fixme: when in BS_HUNT_AND_KILL, they just stand there + if ( bState != BS_FLEE ) + { + NPC_StartFlee( NPC->enemy, NPC->enemy->r.currentOrigin, AEL_DANGER_GREAT, 5000, 10000 ); + } + else + { + NPC_BSFlee(); + } + return; + } + if ( NPC->client->ps.weapon == WP_SABER ) + {//special melee exception + NPC_BehaviorSet_Default( bState ); + return; + } + if ( NPC->client->ps.weapon == WP_DISRUPTOR && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//a sniper + NPC_BehaviorSet_Sniper( bState ); + return; + } + if ( NPC->client->ps.weapon == WP_THERMAL || NPC->client->ps.weapon == WP_STUN_BATON )//FIXME: separate AI for melee fighters + {//a grenadier + NPC_BehaviorSet_Grenadier( bState ); + return; + } + if ( NPC_CheckSurrender() ) + { + return; + } + NPC_BehaviorSet_Stormtrooper( bState ); + break; + + case NPCTEAM_NEUTRAL: + + // special cases for enemy droids + if ( NPC->client->NPC_class == CLASS_PROTOCOL || NPC->client->NPC_class == CLASS_UGNAUGHT || + NPC->client->NPC_class == CLASS_JAWA) + { + NPC_BehaviorSet_Default(bState); + } + else if ( NPC->client->NPC_class == CLASS_VEHICLE ) + { + // TODO: Add vehicle behaviors here. + NPC_UpdateAngles( qtrue, qtrue );//just face our spawn angles for now + } + else + { + // Just one of the average droids + NPC_BehaviorSet_Droid( bState ); + } + break; + + default: + if ( NPC->client->NPC_class == CLASS_SEEKER ) + { + NPC_BehaviorSet_Seeker(bState); + } + else + { + if ( NPCInfo->charmedTime > level.time ) + { + NPC_BehaviorSet_Charmed( bState ); + } + else + { + NPC_BehaviorSet_Default( bState ); + } + NPC_CheckCharmed(); + dontSetAim = qtrue; + } + break; + } + } +} + +/* +=============== +NPC_ExecuteBState + + MCG + +NPC Behavior state thinking + +=============== +*/ +void NPC_ExecuteBState ( gentity_t *self)//, int msec ) +{ + bState_t bState; + + NPC_HandleAIFlags(); + + //FIXME: these next three bits could be a function call, some sort of setup/cleanup func + //Lookmode must be reset every think cycle + if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time) + { + G_ActivateBehavior( NPC, BSET_DELAYED); + NPC->delayScriptTime = 0; + } + + //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func + NPCInfo->combatMove = qfalse; + + //Execute our bState + if(NPCInfo->tempBehavior) + {//Overrides normal behavior until cleared + bState = NPCInfo->tempBehavior; + } + else + { + if(!NPCInfo->behaviorState) + NPCInfo->behaviorState = NPCInfo->defaultBehavior; + + bState = NPCInfo->behaviorState; + } + + //Pick the proper bstate for us and run it + NPC_RunBehavior( self->client->playerTeam, bState ); + + +// if(bState != BS_POINT_COMBAT && NPCInfo->combatPoint != -1) +// { + //level.combatPoints[NPCInfo->combatPoint].occupied = qfalse; + //NPCInfo->combatPoint = -1; +// } + + //Here we need to see what the scripted stuff told us to do +//Only process snapshot if independant and in combat mode- this would pick enemies and go after needed items +// ProcessSnapshot(); + +//Ignore my needs if I'm under script control- this would set needs for items +// CheckSelf(); + + //Back to normal? All decisions made? + + //FIXME: don't walk off ledges unless we can get to our goal faster that way, or that's our goal's surface + //NPCPredict(); + + if ( NPC->enemy ) + { + if ( !NPC->enemy->inuse ) + {//just in case bState doesn't catch this + G_ClearEnemy( NPC ); + } + } + + if ( NPC->client->ps.saberLockTime && NPC->client->ps.saberLockEnemy != ENTITYNUM_NONE ) + { + NPC_SetLookTarget( NPC, NPC->client->ps.saberLockEnemy, level.time+1000 ); + } + else if ( !NPC_CheckLookTarget( NPC ) ) + { + if ( NPC->enemy ) + { + NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 ); + } + } + + if ( NPC->enemy ) + { + if(NPC->enemy->flags & FL_DONT_SHOOT) + { + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons &= ~BUTTON_ALT_ATTACK; + } + else if ( NPC->client->playerTeam != NPCTEAM_ENEMY && NPC->enemy->NPC && (NPC->enemy->NPC->surrenderTime > level.time || (NPC->enemy->NPC->scriptFlags&SCF_FORCED_MARCH)) ) + {//don't shoot someone who's surrendering if you're a good guy + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons &= ~BUTTON_ALT_ATTACK; + } + + if(client->ps.weaponstate == WEAPON_IDLE) + { + client->ps.weaponstate = WEAPON_READY; + } + } + else + { + if(client->ps.weaponstate == WEAPON_READY) + { + client->ps.weaponstate = WEAPON_IDLE; + } + } + + if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time) + {//We just shot but aren't still shooting, so hold the gun up for a while + if(client->ps.weapon == WP_SABER ) + {//One-handed + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); + } + else if(client->ps.weapon == WP_BRYAR_PISTOL) + {//Sniper pose + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + /*//FIXME: What's the proper solution here? + else + {//heavy weapon + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + */ + } + else if ( !NPC->enemy )//HACK! + { +// if(client->ps.weapon != WP_TRICORDER) + { + if( NPC->s.torsoAnim == TORSO_WEAPONREADY1 || NPC->s.torsoAnim == TORSO_WEAPONREADY3 ) + {//we look ready for action, using one of the first 2 weapon, let's rest our weapon on our shoulder + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); + } + } + } + + NPC_CheckAttackHold(); + NPC_ApplyScriptFlags(); + + //cliff and wall avoidance + NPC_AvoidWallsAndCliffs(); + + // run the bot through the server like it was a real client +//=== Save the ucmd for the second no-think Pmove ============================ + ucmd.serverTime = level.time - 50; + memcpy( &NPCInfo->last_ucmd, &ucmd, sizeof( usercmd_t ) ); + if ( !NPCInfo->attackHoldTime ) + { + NPCInfo->last_ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);//so we don't fire twice in one think + } +//============================================================================ + NPC_CheckAttackScript(); + NPC_KeepCurrentFacing(); + + if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) + {//If we were following a roff, we don't do normal pmoves. + ClientThink( NPC->s.number, &ucmd ); + } + else + { + NPC_ApplyRoff(); + } + + // end of thinking cleanup + NPCInfo->touchedByPlayer = NULL; + + NPC_CheckPlayerAim(); + NPC_CheckAllClear(); + + /*if( ucmd.forwardmove || ucmd.rightmove ) + { + int i, la = -1, ta = -1; + + for(i = 0; i < MAX_ANIMATIONS; i++) + { + if( NPC->client->ps.legsAnim == i ) + { + la = i; + } + + if( NPC->client->ps.torsoAnim == i ) + { + ta = i; + } + + if(la != -1 && ta != -1) + { + break; + } + } + + if(la != -1 && ta != -1) + {//FIXME: should never play same frame twice or restart an anim before finishing it + Com_Printf("LegsAnim: %s(%d) TorsoAnim: %s(%d)\n", animTable[la].name, NPC->renderInfo.legsFrame, animTable[ta].name, NPC->client->renderInfo.torsoFrame); + } + }*/ +} + +void NPC_CheckInSolid(void) +{ + trace_t trace; + vec3_t point; + VectorCopy(NPC->r.currentOrigin, point); + point[2] -= 0.25; + + trap_Trace(&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, point, NPC->s.number, NPC->clipmask); + if(!trace.startsolid && !trace.allsolid) + { + VectorCopy(NPC->r.currentOrigin, NPCInfo->lastClearOrigin); + } + else + { + if(VectorLengthSquared(NPCInfo->lastClearOrigin)) + { +// Com_Printf("%s stuck in solid at %s: fixing...\n", NPC->script_targetname, vtos(NPC->r.currentOrigin)); + G_SetOrigin(NPC, NPCInfo->lastClearOrigin); + trap_LinkEntity(NPC); + } + } +} + +void G_DroidSounds( gentity_t *self ) +{ + if ( self->client ) + {//make the noises + if ( TIMER_Done( self, "patrolNoise" ) && !Q_irand( 0, 20 ) ) + { + switch( self->client->NPC_class ) + { + case CLASS_R2D2: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3)) ); + break; + case CLASS_R5D2: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4)) ); + break; + case CLASS_PROBE: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d.wav",Q_irand(1, 3)) ); + break; + case CLASS_MOUSE: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3)) ); + break; + case CLASS_GONK: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2)) ); + break; + } + TIMER_Set( self, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } +} + +/* +=============== +NPC_Think + +Main NPC AI - called once per frame +=============== +*/ +#if AI_TIMERS +extern int AITime; +#endif// AI_TIMERS +void NPC_Think ( gentity_t *self)//, int msec ) +{ + vec3_t oldMoveDir; + int i = 0; + gentity_t *player; + + self->nextthink = level.time + FRAMETIME; + + SetNPCGlobals( self ); + + memset( &ucmd, 0, sizeof( ucmd ) ); + + VectorCopy( self->client->ps.moveDir, oldMoveDir ); + if (self->s.NPC_class != CLASS_VEHICLE) + { //YOU ARE BREAKING MY PREDICTION. Bad clear. + VectorClear( self->client->ps.moveDir ); + } + + if(!self || !self->NPC || !self->client) + { + return; + } + + // dead NPCs have a special think, don't run scripts (for now) + //FIXME: this breaks deathscripts + if ( self->health <= 0 ) + { + DeadThink(); + if ( NPCInfo->nextBStateThink <= level.time ) + { + trap_ICARUS_MaintainTaskManager(self->s.number); + } + VectorCopy(self->r.currentOrigin, self->client->ps.origin); + return; + } + + // see if NPC ai is frozen + if ( debugNPCFreeze.value || (NPC->r.svFlags&SVF_ICARUS_FREEZE) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + ClientThink(self->s.number, &ucmd); + //VectorCopy(self->s.origin, self->s.origin2 ); + VectorCopy(self->r.currentOrigin, self->client->ps.origin); + return; + } + + self->nextthink = level.time + FRAMETIME/2; + + + while (i < MAX_CLIENTS) + { + player = &g_entities[i]; + + if (player->inuse && player->client && player->client->sess.sessionTeam != TEAM_SPECTATOR && + !(player->client->ps.pm_flags & PMF_FOLLOW)) + { + //if ( player->client->ps.viewEntity == self->s.number ) + if (0) //rwwFIXMEFIXME: Allow controlling ents + {//being controlled by player + G_DroidSounds( self ); + //FIXME: might want to at least make sounds or something? + //NPC_UpdateAngles(qtrue, qtrue); + //Which ucmd should we send? Does it matter, since it gets overridden anyway? + NPCInfo->last_ucmd.serverTime = level.time - 50; + ClientThink( NPC->s.number, &ucmd ); + //VectorCopy(self->s.origin, self->s.origin2 ); + VectorCopy(self->r.currentOrigin, self->client->ps.origin); + return; + } + } + i++; + } + + if ( self->client->NPC_class == CLASS_VEHICLE) + { + if (self->client->ps.m_iVehicleNum) + {//we don't think on our own + //well, run scripts, though... + trap_ICARUS_MaintainTaskManager(self->s.number); + return; + } + else + { + VectorClear(self->client->ps.moveDir); + self->client->pers.cmd.forwardmove = 0; + self->client->pers.cmd.rightmove = 0; + self->client->pers.cmd.upmove = 0; + self->client->pers.cmd.buttons = 0; + memcpy(&self->m_pVehicle->m_ucmd, &self->client->pers.cmd, sizeof(usercmd_t)); + } + } + else if ( NPC->s.m_iVehicleNum ) + {//droid in a vehicle? + G_DroidSounds( self ); + } + + if ( NPCInfo->nextBStateThink <= level.time + && !NPC->s.m_iVehicleNum )//NPCs sitting in Vehicles do NOTHING + { +#if AI_TIMERS + int startTime = GetTime(0); +#endif// AI_TIMERS + if ( NPC->s.eType != ET_NPC ) + {//Something drastic happened in our script + return; + } + + if ( NPC->s.weapon == WP_SABER && g_spskill.integer >= 2 && NPCInfo->rank > RANK_LT_JG ) + {//Jedi think faster on hard difficulty, except low-rank (reborn) + NPCInfo->nextBStateThink = level.time + FRAMETIME/2; + } + else + {//Maybe even 200 ms? + NPCInfo->nextBStateThink = level.time + FRAMETIME; + } + + //nextthink is set before this so something in here can override it + if (self->s.NPC_class != CLASS_VEHICLE || + !self->m_pVehicle) + { //ok, let's not do this at all for vehicles. + NPC_ExecuteBState( self ); + } + +#if AI_TIMERS + int addTime = GetTime( startTime ); + if ( addTime > 50 ) + { + Com_Printf( S_COLOR_RED"ERROR: NPC number %d, %s %s at %s, weaponnum: %d, using %d of AI time!!!\n", NPC->s.number, NPC->NPC_type, NPC->targetname, vtos(NPC->r.currentOrigin), NPC->s.weapon, addTime ); + } + AITime += addTime; +#endif// AI_TIMERS + } + else + { + VectorCopy( oldMoveDir, self->client->ps.moveDir ); + //or use client->pers.lastCommand? + NPCInfo->last_ucmd.serverTime = level.time - 50; + if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) + {//If we were following a roff, we don't do normal pmoves. + //FIXME: firing angles (no aim offset) or regular angles? + NPC_UpdateAngles(qtrue, qtrue); + memcpy( &ucmd, &NPCInfo->last_ucmd, sizeof( usercmd_t ) ); + ClientThink(NPC->s.number, &ucmd); + } + else + { + NPC_ApplyRoff(); + } + //VectorCopy(self->s.origin, self->s.origin2 ); + } + //must update icarus *every* frame because of certain animation completions in the pmove stuff that can leave a 50ms gap between ICARUS animation commands + trap_ICARUS_MaintainTaskManager(self->s.number); + VectorCopy(self->r.currentOrigin, self->client->ps.origin); +} + +void NPC_InitAI ( void ) +{ + /* + trap_Cvar_Register(&g_saberRealisticCombat, "g_saberRealisticCombat", "0", CVAR_CHEAT); + + trap_Cvar_Register(&debugNoRoam, "d_noroam", "0", CVAR_CHEAT); + trap_Cvar_Register(&debugNPCAimingBeam, "d_npcaiming", "0", CVAR_CHEAT); + trap_Cvar_Register(&debugBreak, "d_break", "0", CVAR_CHEAT); + trap_Cvar_Register(&debugNPCAI, "d_npcai", "0", CVAR_CHEAT); + trap_Cvar_Register(&debugNPCFreeze, "d_npcfreeze", "0", CVAR_CHEAT); + trap_Cvar_Register(&d_JediAI, "d_JediAI", "0", CVAR_CHEAT); + trap_Cvar_Register(&d_noGroupAI, "d_noGroupAI", "0", CVAR_CHEAT); + trap_Cvar_Register(&d_asynchronousGroupAI, "d_asynchronousGroupAI", "0", CVAR_CHEAT); + + //0 = never (BORING) + //1 = kyle only + //2 = kyle and last enemy jedi + //3 = kyle and any enemy jedi + //4 = kyle and last enemy in a group + //5 = kyle and any enemy + //6 = also when kyle takes pain or enemy jedi dodges player saber swing or does an acrobatic evasion + + trap_Cvar_Register(&d_slowmodeath, "d_slowmodeath", "0", CVAR_CHEAT); + + trap_Cvar_Register(&d_saberCombat, "d_saberCombat", "0", CVAR_CHEAT); + + trap_Cvar_Register(&g_spskill, "g_npcspskill", "0", CVAR_ARCHIVE | CVAR_USERINFO); + */ +} + +/* +================================== +void NPC_InitAnimTable( void ) + + Need to initialize this table. + If someone tried to play an anim + before table is filled in with + values, causes tasks that wait for + anim completion to never finish. + (frameLerp of 0 * numFrames of 0 = 0) +================================== +*/ +/* +void NPC_InitAnimTable( void ) +{ + int i; + + for ( i = 0; i < MAX_ANIM_FILES; i++ ) + { + for ( int j = 0; j < MAX_ANIMATIONS; j++ ) + { + level.knownAnimFileSets[i].animations[j].firstFrame = 0; + level.knownAnimFileSets[i].animations[j].frameLerp = 100; + level.knownAnimFileSets[i].animations[j].initialLerp = 100; + level.knownAnimFileSets[i].animations[j].numFrames = 0; + } + } +} +*/ + +void NPC_InitGame( void ) +{ +// globals.NPCs = (gNPC_t *) gi.TagMalloc(game.maxclients * sizeof(game.bots[0]), TAG_GAME); +// trap_Cvar_Register(&debugNPCName, "d_npc", "0", CVAR_CHEAT); + + NPC_LoadParms(); + NPC_InitAI(); +// NPC_InitAnimTable(); + /* + ResetTeamCounters(); + for ( int team = NPCTEAM_FREE; team < NPCTEAM_NUM_TEAMS; team++ ) + { + teamLastEnemyTime[team] = -10000; + } + */ +} + +void NPC_SetAnim(gentity_t *ent, int setAnimParts, int anim, int setAnimFlags) +{ // FIXME : once torsoAnim and legsAnim are in the same structure for NCP and Players + // rename PM_SETAnimFinal to PM_SetAnim and have both NCP and Players call PM_SetAnim + G_SetAnim(ent, NULL, setAnimParts, anim, setAnimFlags, 0); +/* + if(ent->client) + {//Players, NPCs + if (setAnimFlags&SETANIM_FLAG_OVERRIDE) + { + if (setAnimParts & SETANIM_TORSO) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->client->ps.torsoAnim != anim ) + { + PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoTimer, 0 ); + } + } + if (setAnimParts & SETANIM_LEGS) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->client->ps.legsAnim != anim ) + { + PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, 0 ); + } + } + } + + PM_SetAnimFinal(&ent->client->ps.torsoAnim,&ent->client->ps.legsAnim,setAnimParts,anim,setAnimFlags, + &ent->client->ps.torsoAnimTimer,&ent->client->ps.legsAnimTimer,ent); + } + else + {//bodies, etc. + if (setAnimFlags&SETANIM_FLAG_OVERRIDE) + { + if (setAnimParts & SETANIM_TORSO) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->s.torsoAnim != anim ) + { + PM_SetTorsoAnimTimer( ent, &ent->s.torsoAnimTimer, 0 ); + } + } + if (setAnimParts & SETANIM_LEGS) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->s.legsAnim != anim ) + { + PM_SetLegsAnimTimer( ent, &ent->s.legsAnimTimer, 0 ); + } + } + } + + PM_SetAnimFinal(&ent->s.torsoAnim,&ent->s.legsAnim,setAnimParts,anim,setAnimFlags, + &ent->s.torsoAnimTimer,&ent->s.legsAnimTimer,ent); + } + */ +} diff --git a/codemp/game/NPC_AI_Atst.c b/codemp/game/NPC_AI_Atst.c new file mode 100644 index 0000000..e0e9714 --- /dev/null +++ b/codemp/game/NPC_AI_Atst.c @@ -0,0 +1,308 @@ +#include "b_local.h" + +#define MIN_MELEE_RANGE 640 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define TURN_OFF 0x00000100//G2SURFACEFLAG_NODESCENDANTS + +#define LEFT_ARM_HEALTH 40 +#define RIGHT_ARM_HEALTH 40 + +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +/* +------------------------- +NPC_ATST_Precache +------------------------- +*/ +#if 0 +void NPC_ATST_Precache(void) +{ + G_SoundIndex( "sound/chars/atst/atst_damaged1" ); + G_SoundIndex( "sound/chars/atst/atst_damaged2" ); + +// RegisterItem( BG_FindItemForWeapon( WP_ATST_MAIN )); //precache the weapon + //rwwFIXMEFIXME: add this weapon + RegisterItem( BG_FindItemForWeapon( WP_BOWCASTER )); //precache the weapon + RegisterItem( BG_FindItemForWeapon( WP_ROCKET_LAUNCHER )); //precache the weapon + + G_EffectIndex( "env/med_explode2" ); +// G_EffectIndex( "smaller_chunks" ); + G_EffectIndex( "blaster/smoke_bolton" ); + G_EffectIndex( "explosions/droidexplosion1" ); +} +#endif + +//----------------------------------------------------------------- +#if 0 +static void ATST_PlayEffect( gentity_t *self, const int boltID, const char *fx ) +{ + if ( boltID >=0 && fx && fx[0] ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir; + + trap_G2API_GetBoltMatrix( self->ghoul2, 0, + boltID, + &boltMatrix, self->r.currentAngles, self->r.currentOrigin, level.time, + NULL, self->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + + G_PlayEffectID( G_EffectIndex((char *)fx), org, dir ); + } +} +#endif + +/* +------------------------- +G_ATSTCheckPain + +Called by NPC's and player in an ATST +------------------------- +*/ + +void G_ATSTCheckPain( gentity_t *self, gentity_t *other, int damage ) +{ + //int newBolt; + //int hitLoc = gPainHitLoc; + + if ( rand() & 1 ) + { + G_SoundOnEnt( self, CHAN_LESS_ATTEN, "sound/chars/atst/atst_damaged1" ); + } + else + { + G_SoundOnEnt( self, CHAN_LESS_ATTEN, "sound/chars/atst/atst_damaged2" ); + } +} +/* +------------------------- +NPC_ATST_Pain +------------------------- +*/ +void NPC_ATST_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + G_ATSTCheckPain( self, attacker, damage ); + NPC_Pain( self, attacker, damage ); +} + +/* +------------------------- +ATST_Hunt +-------------------------` +*/ +/* +void ATST_Hunt( qboolean visible, qboolean advance ) +{ + + if ( NPCInfo->goalEntity == NULL ) + {//hunt + NPCInfo->goalEntity = NPC->enemy; + } + + NPCInfo->combatMove = qtrue; + + NPC_MoveToGoal( qtrue ); + +} +*/ + +/* +------------------------- +ATST_Ranged +------------------------- +*/ +/* +void ATST_Ranged( qboolean visible, qboolean advance, qboolean altAttack ) +{ + + if ( TIMER_Done( NPC, "atkDelay" ) && visible ) // Attack? + { + TIMER_Set( NPC, "atkDelay", Q_irand( 500, 3000 ) ); + + if (altAttack) + { + ucmd.buttons |= BUTTON_ATTACK|BUTTON_ALT_ATTACK; + } + else + { + ucmd.buttons |= BUTTON_ATTACK; + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ATST_Hunt( visible, advance ); + } +} +*/ + +/* +------------------------- +ATST_Attack +------------------------- +*/ +/* +void ATST_Attack( void ) +{ + qboolean altAttack=qfalse; + int blasterTest,chargerTest,weapon; + float distance; + distance_e distRate; + qboolean visible; + qboolean advance; + + if ( NPC_CheckEnemyExt(qfalse) == qfalse )//!NPC->enemy )// + { + NPC->enemy = NULL; + return; + } + + NPC_FaceEnemy( qtrue ); + + // Rate our distance to the target, and our visibilty + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ATST_Hunt( visible, advance ); + return; + } + } + + // Decide what type of attack to do + switch ( distRate ) + { + case DIST_MELEE: +// NPC_ChangeWeapon( WP_ATST_MAIN ); + break; + + case DIST_LONG: + +// NPC_ChangeWeapon( WP_ATST_SIDE ); + //rwwFIXMEFIXME: make atst weaps work. + + // See if the side weapons are there + blasterTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "head_light_blaster_cann" ); + chargerTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "head_concussion_charger" ); + + // It has both side weapons + if ( blasterTest != -1 + && !(blasterTest&TURN_OFF) + && chargerTest != -1 + && !(chargerTest&TURN_OFF)) + { + weapon = Q_irand( 0, 1); // 0 is blaster, 1 is charger (ALT SIDE) + + if (weapon) // Fire charger + { + altAttack = qtrue; + } + else + { + altAttack = qfalse; + } + + } + else if (blasterTest != -1 + && !(blasterTest & TURN_OFF)) // Blaster is on + { + altAttack = qfalse; + } + else if (chargerTest != -1 + &&!(chargerTest & TURN_OFF)) // Blaster is on + { + altAttack = qtrue; + } + else + { + NPC_ChangeWeapon( WP_NONE ); + } + break; + } + + NPC_FaceEnemy( qtrue ); + + ATST_Ranged( visible, advance,altAttack ); +} +*/ + +/* +------------------------- +ATST_Patrol +------------------------- +*/ +/* +void ATST_Patrol( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + } + } + +} +*/ + +/* +------------------------- +ATST_Idle +------------------------- +*/ +/* +void ATST_Idle( void ) +{ + + NPC_BSIdle(); + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_NORMAL ); +} +*/ + +/* +------------------------- +NPC_BSDroid_Default +------------------------- +*/ +/* +void NPC_BSATST_Default( void ) +{ + if ( NPC->enemy ) + { + if( (NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) ) + { + NPCInfo->goalEntity = NPC->enemy; + } + ATST_Attack(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + ATST_Patrol(); + } + else + { + ATST_Idle(); + } +} +*/ diff --git a/codemp/game/NPC_AI_Default.c b/codemp/game/NPC_AI_Default.c new file mode 100644 index 0000000..d65392d --- /dev/null +++ b/codemp/game/NPC_AI_Default.c @@ -0,0 +1,957 @@ +#include "b_local.h" +#include "g_nav.h" +#include "../icarus/Q3_Interface.h" + +//#include "anims.h" +//extern int PM_AnimLength( int index, animNumber_t anim ); +//extern int PM_AnimLength( int index, animNumber_t anim ); +//#define MAX_IDLE_ANIMS 8 + +extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent); + +/* +void NPC_LostEnemyDecideChase(void) + + We lost our enemy and want to drop him but see if we should chase him if we are in the proper bState +*/ + +void NPC_LostEnemyDecideChase(void) +{ + switch( NPCInfo->behaviorState ) + { + case BS_HUNT_AND_KILL: + //We were chasing him and lost him, so try to find him + if ( NPC->enemy == NPCInfo->goalEntity && NPC->enemy->lastWaypoint != WAYPOINT_NONE ) + {//Remember his last valid Wp, then check it out + //FIXME: Should we only do this if there's no other enemies or we've got LOCKED_ENEMY on? + NPC_BSSearchStart( NPC->enemy->lastWaypoint, BS_SEARCH ); + } + //If he's not our goalEntity, we're running somewhere else, so lose him + break; + default: + break; + } + G_ClearEnemy( NPC ); +} +/* +------------------------- +NPC_StandIdle +------------------------- +*/ + +void NPC_StandIdle( void ) +{ +/* + //Must be done with any other animations + if ( NPC->client->ps.legsAnimTimer != 0 ) + return; + + //Not ready to do another one + if ( TIMER_Done( NPC, "idleAnim" ) == false ) + return; + + int anim = NPC->client->ps.legsAnim; + + if ( anim != BOTH_STAND1 && anim != BOTH_STAND2 ) + return; + + //FIXME: Account for STAND1 or STAND2 here and set the base anim accordingly + int baseSeq = ( anim == BOTH_STAND1 ) ? BOTH_STAND1_RANDOM1 : BOTH_STAND2_RANDOM1; + + //Must have at least one random idle animation + //NOTENOTE: This relies on proper ordering of animations, which SHOULD be okay + if ( PM_HasAnimation( NPC, baseSeq ) == false ) + return; + + int newIdle = Q_irand( 0, MAX_IDLE_ANIMS-1 ); + + //FIXME: Technically this could never complete.. but that's not really too likely + while( 1 ) + { + if ( PM_HasAnimation( NPC, baseSeq + newIdle ) ) + break; + + newIdle = Q_irand( 0, MAX_IDLE_ANIMS ); + } + + //Start that animation going + NPC_SetAnim( NPC, SETANIM_BOTH, baseSeq + newIdle, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + int newTime = PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t) (baseSeq + newIdle) ); + + //Don't do this again for a random amount of time + TIMER_Set( NPC, "idleAnim", newTime + Q_irand( 2000, 10000 ) ); +*/ +} + +qboolean NPC_StandTrackAndShoot (gentity_t *NPC, qboolean canDuck) +{ + qboolean attack_ok = qfalse; + qboolean duck_ok = qfalse; + qboolean faced = qfalse; + float attack_scale = 1.0; + + //First see if we're hurt bad- if so, duck + //FIXME: if even when ducked, we can shoot someone, we should. + //Maybe is can be shot even when ducked, we should run away to the nearest cover? + if ( canDuck ) + { + if ( NPC->health < 20 ) + { + // if( NPC->svFlags&SVF_HEALING || random() ) + if( random() ) + { + duck_ok = qtrue; + } + } + else if ( NPC->health < 40 ) + { +// if ( NPC->svFlags&SVF_HEALING ) +// {//Medic is on the way, get down! +// duck_ok = qtrue; +// } + // no more borg +/// if ( NPC->client->playerTeam!= TEAM_BORG ) +// {//Borg don't care if they're about to die + //attack_scale will be a max of .66 +// attack_scale = NPC->health/60; +// } + } + } + + //NPC_CheckEnemy( qtrue, qfalse, qtrue ); + + if ( !duck_ok ) + {//made this whole part a function call + attack_ok = NPC_CheckCanAttack( attack_scale, qtrue ); + faced = qtrue; + } + + if ( canDuck && (duck_ok || (!attack_ok && client->ps.weaponTime <= 0)) && ucmd.upmove != -127 ) + {//if we didn't attack check to duck if we're not already + if( !duck_ok ) + { + if ( NPC->enemy->client ) + { + if ( NPC->enemy->enemy == NPC ) + { + if ( NPC->enemy->client->buttons & BUTTON_ATTACK ) + {//FIXME: determine if enemy fire angles would hit me or get close + if ( NPC_CheckDefend( 1.0 ) )//FIXME: Check self-preservation? Health? + { + duck_ok = qtrue; + } + } + } + } + } + + if ( duck_ok ) + {//duck and don't shoot + attack_ok = qfalse; + ucmd.upmove = -127; + NPCInfo->duckDebounceTime = level.time + 1000;//duck for a full second + } + } + + return faced; +} + + +void NPC_BSIdle( void ) +{ + //FIXME if there is no nav data, we need to do something else + // if we're stuck, try to move around it + if ( UpdateGoal() ) + { + NPC_MoveToGoal( qtrue ); + } + + if ( ( ucmd.forwardmove == 0 ) && ( ucmd.rightmove == 0 ) && ( ucmd.upmove == 0 ) ) + { +// NPC_StandIdle(); + } + + NPC_UpdateAngles( qtrue, qtrue ); + ucmd.buttons |= BUTTON_WALKING; +} + +void NPC_BSRun (void) +{ + //FIXME if there is no nav data, we need to do something else + // if we're stuck, try to move around it + if ( UpdateGoal() ) + { + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +void NPC_BSStandGuard (void) +{ + //FIXME: Use Snapshot info + if ( NPC->enemy == NULL ) + {//Possible to pick one up by being shot + if( random() < 0.5 ) + { + if(NPC->client->enemyTeam) + { + gentity_t *newenemy = NPC_PickEnemy(NPC, NPC->client->enemyTeam, (NPC->cantHitEnemyCounter < 10), (NPC->client->enemyTeam == NPCTEAM_PLAYER), qtrue); + //only checks for vis if couldn't hit last enemy + if(newenemy) + { + G_SetEnemy( NPC, newenemy ); + } + } + } + } + + if ( NPC->enemy != NULL ) + { + if( NPCInfo->tempBehavior == BS_STAND_GUARD ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + + if( NPCInfo->behaviorState == BS_STAND_GUARD ) + { + NPCInfo->behaviorState = BS_STAND_AND_SHOOT; + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSHuntAndKill +------------------------- +*/ + +void NPC_BSHuntAndKill( void ) +{ + qboolean turned = qfalse; + vec3_t vec; + float enemyDist; + visibility_t oEVis; + int curAnim; + + NPC_CheckEnemy( NPCInfo->tempBehavior != BS_HUNT_AND_KILL, qfalse, qtrue );//don't find new enemy if this is tempbehav + + if ( NPC->enemy ) + { + oEVis = enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT );//CHECK_360|//CHECK_PVS| + if(enemyVisibility > VIS_PVS) + { + if ( !NPC_EnemyTooFar( NPC->enemy, 0, qtrue ) ) + {//Enemy is close enough to shoot - FIXME: this next func does this also, but need to know here for info on whether ot not to turn later + NPC_CheckCanAttack( 1.0, qfalse ); + turned = qtrue; + } + } + + curAnim = NPC->client->ps.legsAnim; + if(curAnim != BOTH_ATTACK1 && curAnim != BOTH_ATTACK2 && curAnim != BOTH_ATTACK3 && curAnim != BOTH_MELEE1 && curAnim != BOTH_MELEE2 ) + {//Don't move toward enemy if we're in a full-body attack anim + //FIXME, use IdealDistance to determin if we need to close distance + VectorSubtract(NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec); + enemyDist = VectorLength(vec); + if( enemyDist > 48 && ((enemyDist*1.5)*(enemyDist*1.5) >= NPC_MaxDistSquaredForWeapon() || + oEVis != VIS_SHOOT || + //!(ucmd.buttons & BUTTON_ATTACK) || + enemyDist > IdealDistance(NPC)*3 ) ) + {//We should close in? + NPCInfo->goalEntity = NPC->enemy; + + NPC_MoveToGoal( qtrue ); + } + else if(enemyDist < IdealDistance(NPC)) + {//We should back off? + //if(ucmd.buttons & BUTTON_ATTACK) + { + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + NPC_MoveToGoal( qtrue ); + + ucmd.forwardmove *= -1; + ucmd.rightmove *= -1; + VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + + ucmd.buttons |= BUTTON_WALKING; + } + }//otherwise, stay where we are + } + } + else + {//ok, stand guard until we find an enemy + if( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + { + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + } + return; + } + + if(!turned) + { + NPC_UpdateAngles(qtrue, qtrue); + } +} + +void NPC_BSStandAndShoot (void) +{ + //FIXME: + //When our numbers outnumber enemies 3 to 1, or only one of them, + //go into hunt and kill mode + + //FIXME: + //When they're all dead, go to some script or wander off to sickbay? + + if(NPC->client->playerTeam && NPC->client->enemyTeam) + { + //FIXME: don't realize this right away- or else enemies show up and we're standing around + /* + if( teamNumbers[NPC->enemyTeam] == 0 ) + {//ok, stand guard until we find another enemy + //reset our rush counter + teamCounter[NPC->playerTeam] = 0; + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + return; + }*/ + /* + //FIXME: whether to do this or not should be settable + else if( NPC->playerTeam != TEAM_BORG )//Borg don't rush + { + //FIXME: In case reinforcements show up, we should wait a few seconds + //and keep checking before rushing! + //Also: what if not everyone on our team is going after playerTeam? + //Also: our team count includes medics! + if(NPC->health > 25) + {//Can we rush the enemy? + if(teamNumbers[NPC->enemyTeam] == 1 || + teamNumbers[NPC->playerTeam] >= teamNumbers[NPC->enemyTeam]*3) + {//Only one of them or we outnumber 3 to 1 + if(teamStrength[NPC->playerTeam] >= 75 || + (teamStrength[NPC->playerTeam] >= 50 && teamStrength[NPC->playerTeam] > teamStrength[NPC->enemyTeam])) + {//Our team is strong enough to rush + teamCounter[NPC->playerTeam]++; + if(teamNumbers[NPC->playerTeam] * 17 <= teamCounter[NPC->playerTeam]) + {//ok, we waited 1.7 think cycles on average and everyone is go, let's do it! + //FIXME: Should we do this to everyone on our team? + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + //FIXME: if the tide changes, we should retreat! + //FIXME: when do we reset the counter? + NPC_BSHuntAndKill (); + return; + } + } + else//Oops! Something's wrong, reset the counter to rush + teamCounter[NPC->playerTeam] = 0; + } + else//Oops! Something's wrong, reset the counter to rush + teamCounter[NPC->playerTeam] = 0; + } + } + */ + } + + NPC_CheckEnemy(qtrue, qfalse, qtrue); + + if(NPCInfo->duckDebounceTime > level.time && NPC->client->ps.weapon != WP_SABER ) + { + ucmd.upmove = -127; + if(NPC->enemy) + { + NPC_CheckCanAttack(1.0, qtrue); + } + return; + } + + if(NPC->enemy) + { + if(!NPC_StandTrackAndShoot( NPC, qtrue )) + {//That func didn't update our angles + NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH]; + NPC_UpdateAngles(qtrue, qtrue); + } + } + else + { + NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH]; + NPC_UpdateAngles(qtrue, qtrue); +// NPC_BSIdle();//only moves if we have a goal + } +} + +void NPC_BSRunAndShoot (void) +{ + /*if(NPC->playerTeam && NPC->enemyTeam) + { + //FIXME: don't realize this right away- or else enemies show up and we're standing around + if( teamNumbers[NPC->enemyTeam] == 0 ) + {//ok, stand guard until we find another enemy + //reset our rush counter + teamCounter[NPC->playerTeam] = 0; + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + return; + } + }*/ + + //NOTE: are we sure we want ALL run and shoot people to move this way? + //Shouldn't it check to see if we have an enemy and our enemy is our goal?! + //Moved that check into NPC_MoveToGoal + //NPCInfo->combatMove = qtrue; + + NPC_CheckEnemy( qtrue, qfalse, qtrue ); + + if ( NPCInfo->duckDebounceTime > level.time ) // && NPCInfo->hidingGoal ) + { + ucmd.upmove = -127; + if ( NPC->enemy ) + { + NPC_CheckCanAttack( 1.0, qfalse ); + } + return; + } + + if ( NPC->enemy ) + { + int monitor = NPC->cantHitEnemyCounter; + NPC_StandTrackAndShoot( NPC, qfalse );//(NPCInfo->hidingGoal != NULL) ); + + if ( !(ucmd.buttons & BUTTON_ATTACK) && ucmd.upmove >= 0 && NPC->cantHitEnemyCounter > monitor ) + {//not crouching and not firing + vec3_t vec; + + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec ); + vec[2] = 0; + if ( VectorLength( vec ) > 128 || NPC->cantHitEnemyCounter >= 10 ) + {//run at enemy if too far away + //The cantHitEnemyCounter getting high has other repercussions + //100 (10 seconds) will make you try to pick a new enemy... + //But we're chasing, so we clamp it at 50 here + if ( NPC->cantHitEnemyCounter > 60 ) + { + NPC->cantHitEnemyCounter = 60; + } + + if ( NPC->cantHitEnemyCounter >= (NPCInfo->stats.aggression+1) * 10 ) + { + NPC_LostEnemyDecideChase(); + } + + //chase and face + ucmd.angles[YAW] = 0; + ucmd.angles[PITCH] = 0; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + //NAV_ClearLastRoute(NPC); + NPC_MoveToGoal( qtrue ); + NPC_UpdateAngles(qtrue, qtrue); + } + else + { + //FIXME: this could happen if they're just on the other side + //of a thin wall or something else blocking out shot. That + //would make us just stand there and not go around it... + //but maybe it's okay- might look like we're waiting for + //him to come out...? + //Current solution: runs around if cantHitEnemyCounter gets + //to 10 (1 second). + } + } + else + {//Clear the can't hit enemy counter here + NPC->cantHitEnemyCounter = 0; + } + } + else + { + if ( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + {//lost him, go back to what we were doing before + NPCInfo->tempBehavior = BS_DEFAULT; + return; + } + +// NPC_BSRun();//only moves if we have a goal + } +} + +//Simply turn until facing desired angles +void NPC_BSFace (void) +{ + //FIXME: once you stop sending turning info, they reset to whatever their delta_angles was last???? + //Once this is over, it snaps back to what it was facing before- WHY??? + if( NPC_UpdateAngles ( qtrue, qtrue ) ) + { + trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE ); + + NPCInfo->desiredYaw = client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = client->ps.viewangles[PITCH]; + + NPCInfo->aimTime = 0;//ok to turn normally now + } +} + +void NPC_BSPointShoot (qboolean shoot) +{//FIXME: doesn't check for clear shot... + vec3_t muzzle, dir, angles, org; + + if ( !NPC->enemy || !NPC->enemy->inuse || (NPC->enemy->NPC && NPC->enemy->health <= 0) ) + {//FIXME: should still keep shooting for a second or two after they actually die... + trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE ); + goto finished; + return; + } + + CalcEntitySpot(NPC, SPOT_WEAPON, muzzle); + CalcEntitySpot(NPC->enemy, SPOT_HEAD, org);//Was spot_org + //Head is a little high, so let's aim for the chest: + if ( NPC->enemy->client ) + { + org[2] -= 12;//NOTE: is this enough? + } + + VectorSubtract(org, muzzle, dir); + vectoangles(dir, angles); + + switch( NPC->client->ps.weapon ) + { + case WP_NONE: +// case WP_TRICORDER: + case WP_STUN_BATON: + case WP_SABER: + //don't do any pitch change if not holding a firing weapon + break; + default: + NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]); + break; + } + + NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]); + + if ( NPC_UpdateAngles ( qtrue, qtrue ) ) + {//FIXME: if angles clamped, this may never work! + //NPCInfo->shotTime = NPC->attackDebounceTime = 0; + + if ( shoot ) + {//FIXME: needs to hold this down if using a weapon that requires it, like phaser... + ucmd.buttons |= BUTTON_ATTACK; + } + + //if ( !shoot || !(NPC->svFlags & SVF_LOCKEDENEMY) ) + if (1) + {//If locked_enemy is on, dont complete until it is destroyed... + trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE ); + goto finished; + } + } + //else if ( shoot && (NPC->svFlags & SVF_LOCKEDENEMY) ) + if (0) + {//shooting them till their dead, not aiming right at them yet... + /* + qboolean movingTarget = qfalse; + + if ( NPC->enemy->client ) + { + if ( VectorLengthSquared( NPC->enemy->client->ps.velocity ) ) + { + movingTarget = qtrue; + } + } + else if ( VectorLengthSquared( NPC->enemy->s.pos.trDelta ) ) + { + movingTarget = qtrue; + } + + if (movingTarget ) + */ + { + float dist = VectorLength( dir ); + float yawMiss, yawMissAllow = NPC->enemy->r.maxs[0]; + float pitchMiss, pitchMissAllow = (NPC->enemy->r.maxs[2] - NPC->enemy->r.mins[2])/2; + + if ( yawMissAllow < 8.0f ) + { + yawMissAllow = 8.0f; + } + + if ( pitchMissAllow < 8.0f ) + { + pitchMissAllow = 8.0f; + } + + yawMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw ))) * dist; + pitchMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[PITCH], NPCInfo->desiredPitch))) * dist; + + if ( yawMissAllow >= yawMiss && pitchMissAllow > pitchMiss ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + } + } + + return; + +finished: + NPCInfo->desiredYaw = client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = client->ps.viewangles[PITCH]; + + NPCInfo->aimTime = 0;//ok to turn normally now +} + +/* +void NPC_BSMove(void) +Move in a direction, face another +*/ +void NPC_BSMove(void) +{ + gentity_t *goal = NULL; + + NPC_CheckEnemy(qtrue, qfalse, qtrue); + if(NPC->enemy) + { + NPC_CheckCanAttack(1.0, qfalse); + } + else + { + NPC_UpdateAngles(qtrue, qtrue); + } + + goal = UpdateGoal(); + if(goal) + { +// NPCInfo->moveToGoalMod = 1.0; + + NPC_SlideMoveToGoal(); + } +} + +/* +void NPC_BSShoot(void) +Move in a direction, face another +*/ + +void NPC_BSShoot(void) +{ +// NPC_BSMove(); + + enemyVisibility = VIS_SHOOT; + + if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING ) + { + client->ps.weaponstate = WEAPON_READY; + } + + WeaponThink(qtrue); +} + +/* +void NPC_BSPatrol( void ) + + Same as idle, but you look for enemies every "vigilance" + using your angles, HFOV, VFOV and visrange, and listen for sounds within earshot... +*/ +void NPC_BSPatrol( void ) +{ + //int alertEventNum; + + if(level.time > NPCInfo->enemyCheckDebounceTime) + { + NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 1000); + NPC_CheckEnemy(qtrue, qfalse, qtrue); + if(NPC->enemy) + {//FIXME: do anger script + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + //NPC_AngerSound(); + return; + } + } + + //FIXME: Implement generic sound alerts + /* + alertEventNum = NPC_CheckAlertEvents( qtrue, qtrue ); + if( alertEventNum != -1 ) + {//If we heard something, see if we should check it out + if ( NPC_CheckInvestigate( alertEventNum ) ) + { + return; + } + } + */ + + NPCInfo->investigateSoundDebounceTime = 0; + //FIXME if there is no nav data, we need to do something else + // if we're stuck, try to move around it + if ( UpdateGoal() ) + { + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); + + ucmd.buttons |= BUTTON_WALKING; +} + +/* +void NPC_BSDefault(void) + uses various scriptflags to determine how an npc should behave +*/ +extern void NPC_CheckGetNewWeapon( void ); +extern void NPC_BSST_Attack( void ); + +void NPC_BSDefault( void ) +{ +// vec3_t enemyDir; +// float enemyDist; +// float shootDist; +// qboolean enemyFOV = qfalse; +// qboolean enemyShotFOV = qfalse; +// qboolean enemyPVS = qfalse; +// vec3_t enemyHead; +// vec3_t muzzle; +// qboolean enemyLOS = qfalse; +// qboolean enemyCS = qfalse; + qboolean move = qtrue; +// qboolean shoot = qfalse; + + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) + {//being forced to walk + if( NPC->client->ps.torsoAnim != TORSO_SURRENDER_START ) + { + NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD ); + } + } + //look for a new enemy if don't have one and are allowed to look, validate current enemy if have one + NPC_CheckEnemy( (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES), qfalse, qtrue ); + if ( !NPC->enemy ) + {//still don't have an enemy + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + {//check for alert events + //FIXME: Check Alert events, see if we should investigate or just look at it + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED ); + + //There is an event to look at + if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + {//heard/saw something + if ( level.alertEvents[alertEvent].level >= AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + {//was a big event + if ( level.alertEvents[alertEvent].owner && level.alertEvents[alertEvent].owner->client && level.alertEvents[alertEvent].owner->health >= 0 && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + } + } + else + {//FIXME: investigate lesser events + } + } + //FIXME: also check our allies' condition? + } + } + + if ( NPC->enemy && !(NPCInfo->scriptFlags&SCF_FORCED_MARCH) ) + { + // just use the stormtrooper attack AI... + NPC_CheckGetNewWeapon(); + if ( NPC->client->leader + && NPCInfo->goalEntity == NPC->client->leader + && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + { + NPC_ClearGoal(); + } + NPC_BSST_Attack(); + return; +/* + //have an enemy + //FIXME: if one of these fails, meaning we can't shoot, do we really need to do the rest? + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir ); + enemyDist = VectorNormalize( enemyDir ); + enemyDist *= enemyDist; + shootDist = NPC_MaxDistSquaredForWeapon(); + + enemyFOV = InFOV( NPC->enemy, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ); + enemyShotFOV = InFOV( NPC->enemy, NPC, 20, 20 ); + enemyPVS = gi.inPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ); + + if ( enemyPVS ) + {//in the pvs + trace_t tr; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemyHead ); + enemyHead[2] -= Q_flrand( 0.0f, NPC->enemy->maxs[2]*0.5f ); + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + enemyLOS = NPC_ClearLOS( muzzle, enemyHead ); + + gi.trace ( &tr, muzzle, vec3_origin, vec3_origin, enemyHead, NPC->s.number, MASK_SHOT ); + enemyCS = NPC_EvaluateShot( tr.entityNum, qtrue ); + } + else + {//skip thr 2 traces since they would have to fail + enemyLOS = qfalse; + enemyCS = qfalse; + } + + if ( enemyCS && enemyShotFOV ) + {//can hit enemy if we want + NPC->cantHitEnemyCounter = 0; + } + else + {//can't hit + NPC->cantHitEnemyCounter++; + } + + if ( enemyCS && enemyShotFOV && enemyDist < shootDist ) + {//can shoot + shoot = qtrue; + if ( NPCInfo->goalEntity == NPC->enemy ) + {//my goal is my enemy and I have a clear shot, no need to chase right now + move = qfalse; + } + } + else + {//don't shoot yet, keep chasing + shoot = qfalse; + move = qtrue; + } + + //shoot decision + if ( !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//try to shoot + if ( NPC->enemy ) + { + if ( shoot ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + } + } + } + + //chase decision + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + {//go after him + NPCInfo->goalEntity = NPC->enemy; + //FIXME: don't need to chase when have a clear shot and in range? + if ( !enemyCS && NPC->cantHitEnemyCounter > 60 ) + {//haven't been able to shoot enemy for about 6 seconds, need to do something + //FIXME: combat points? Just chase? + if ( enemyPVS ) + {//in my PVS, just pick a combat point + //FIXME: implement + } + else + {//just chase him + } + } + //FIXME: in normal behavior, should we use combat Points? Do we care? Is anyone actually going to ever use this AI? + } + else if ( NPC->cantHitEnemyCounter > 60 ) + {//pick a new one + NPC_CheckEnemy( qtrue, qfalse, qtrue ); + } + + if ( enemyPVS && enemyLOS )//&& !enemyShotFOV ) + {//have a clear LOS to him//, but not looking at him + //Find the desired angles + vec3_t angles; + + GetAnglesForDirection( muzzle, enemyHead, angles ); + + NPCInfo->desiredYaw = AngleNormalize180( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize180( angles[PITCH] ); + } + */ + } + + if ( UpdateGoal() ) + {//have a goal + if ( !NPC->enemy + && NPC->client->leader + && NPCInfo->goalEntity == NPC->client->leader + && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + { + NPC_BSFollowLeader(); + } + else + { + //set angles + if ( (NPCInfo->scriptFlags & SCF_FACE_MOVE_DIR) || NPCInfo->goalEntity != NPC->enemy ) + {//face direction of movement, NOTE: default behavior when not chasing enemy + NPCInfo->combatMove = qfalse; + } + else + {//face goal.. FIXME: what if have a navgoal but want to face enemy while moving? Will this do that? + vec3_t dir, angles; + + NPCInfo->combatMove = qfalse; + + VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir ); + vectoangles( dir, angles ); + NPCInfo->desiredYaw = angles[YAW]; + if ( NPCInfo->goalEntity == NPC->enemy ) + { + NPCInfo->desiredPitch = angles[PITCH]; + } + } + + //set movement + //override default walk/run behavior + //NOTE: redundant, done in NPC_ApplyScriptFlags + if ( NPCInfo->scriptFlags & SCF_RUNNING ) + { + ucmd.buttons &= ~BUTTON_WALKING; + } + else if ( NPCInfo->scriptFlags & SCF_WALKING ) + { + ucmd.buttons |= BUTTON_WALKING; + } + else if ( NPCInfo->goalEntity == NPC->enemy ) + { + ucmd.buttons &= ~BUTTON_WALKING; + } + else + { + ucmd.buttons |= BUTTON_WALKING; + } + + if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) + {//being forced to walk + //if ( g_crosshairEntNum != NPC->s.number ) + if (!NPC_SomeoneLookingAtMe(NPC)) + {//don't walk if player isn't aiming at me + move = qfalse; + } + } + + if ( move ) + { + //move toward goal + NPC_MoveToGoal( qtrue ); + } + } + } + else if ( !NPC->enemy && NPC->client->leader ) + { + NPC_BSFollowLeader(); + } + + //update angles + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/codemp/game/NPC_AI_Droid.c b/codemp/game/NPC_AI_Droid.c new file mode 100644 index 0000000..f738ed0 --- /dev/null +++ b/codemp/game/NPC_AI_Droid.c @@ -0,0 +1,621 @@ +#include "b_local.h" + +//static void R5D2_LookAround( void ); +float NPC_GetPainChance( gentity_t *self, int damage ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); + +#define TURN_OFF 0x00000100 + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_BACKINGUP, + LSTATE_SPINNING, + LSTATE_PAIN, + LSTATE_DROP +}; + +/* +------------------------- +R2D2_PartsMove +------------------------- +*/ +void R2D2_PartsMove(void) +{ + // Front 'eye' lense + if ( TIMER_Done(NPC,"eyeDelay") ) + { + NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); + + NPC->pos1[0]+=Q_irand( -20, 20 ); // Roll + NPC->pos1[1]=Q_irand( -20, 20 ); + NPC->pos1[2]=Q_irand( -20, 20 ); + + /* + if (NPC->genericBone1) + { + gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + */ + NPC_SetBoneAngles(NPC, "f_eye", NPC->pos1); + + + TIMER_Set( NPC, "eyeDelay", Q_irand( 100, 1000 ) ); + } +} + +/* +------------------------- +NPC_BSDroid_Idle +------------------------- +*/ +void Droid_Idle( void ) +{ +// VectorCopy( NPCInfo->investigateGoal, lookPos ); + +// NPC_FacePosition( lookPos ); +} + +/* +------------------------- +R2D2_TurnAnims +------------------------- +*/ +void R2D2_TurnAnims ( void ) +{ + float turndelta; + int anim; + + turndelta = AngleDelta(NPC->r.currentAngles[YAW], NPCInfo->desiredYaw); + + if ((fabs(turndelta) > 20) && ((NPC->client->NPC_class == CLASS_R2D2) || (NPC->client->NPC_class == CLASS_R5D2))) + { + anim = NPC->client->ps.legsAnim; + if (turndelta<0) + { + if (anim != BOTH_TURN_LEFT1) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_LEFT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + else + { + if (anim != BOTH_TURN_RIGHT1) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_RIGHT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + +} + +/* +------------------------- +Droid_Patrol +------------------------- +*/ +void Droid_Patrol( void ) +{ + + NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); + + if ( NPC->client && NPC->client->NPC_class != CLASS_GONK ) + { + if (NPC->client->NPC_class != CLASS_R5D2) + { //he doesn't have an eye. + R2D2_PartsMove(); // Get his eye moving. + } + R2D2_TurnAnims(); + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + + if( NPC->client && NPC->client->NPC_class == CLASS_MOUSE ) + { + NPCInfo->desiredYaw += sin(level.time*.5) * 25; // Weaves side to side a little + + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + else if( NPC->client && NPC->client->NPC_class == CLASS_R2D2 ) + { + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + else if( NPC->client && NPC->client->NPC_class == CLASS_R5D2 ) + { + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav", Q_irand(1, 4)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + if( NPC->client && NPC->client->NPC_class == CLASS_GONK ) + { + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav", Q_irand(1, 2)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } +// else +// { +// R5D2_LookAround(); +// } + } + + NPC_UpdateAngles( qtrue, qtrue ); + +} + +/* +------------------------- +Droid_Run +------------------------- +*/ +void Droid_Run( void ) +{ + R2D2_PartsMove(); + + if ( NPCInfo->localState == LSTATE_BACKINGUP ) + { + ucmd.forwardmove = -127; + NPCInfo->desiredYaw += 5; + + NPCInfo->localState = LSTATE_NONE; // So he doesn't constantly backup. + } + else + { + ucmd.forwardmove = 64; + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + if (NPC_MoveToGoal( qfalse )) + { + NPCInfo->desiredYaw += sin(level.time*.5) * 5; // Weaves side to side a little + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +void Droid_Spin( void ) +------------------------- +*/ +void Droid_Spin( void ) +{ + vec3_t dir = {0,0,1}; + + R2D2_TurnAnims(); + + + // Head is gone, spin and spark + if ( NPC->client->NPC_class == CLASS_R5D2 + || NPC->client->NPC_class == CLASS_R2D2 ) + { + // No head? + if (trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "head" )>0) + { + if (TIMER_Done(NPC,"smoke") && !TIMER_Done(NPC,"droidsmoketotal")) + { + TIMER_Set( NPC, "smoke", 100); + G_PlayEffectID( G_EffectIndex("volumetric/droid_smoke") , NPC->r.currentOrigin,dir); + } + + if (TIMER_Done(NPC,"droidspark")) + { + TIMER_Set( NPC, "droidspark", Q_irand(100,500)); + G_PlayEffectID( G_EffectIndex("sparks/spark"), NPC->r.currentOrigin,dir); + } + + ucmd.forwardmove = Q_irand( -64, 64); + + if (TIMER_Done(NPC,"roam")) + { + TIMER_Set( NPC, "roam", Q_irand( 250, 1000 ) ); + NPCInfo->desiredYaw = Q_irand( 0, 360 ); // Go in random directions + } + } + else + { + if (TIMER_Done(NPC,"roam")) + { + NPCInfo->localState = LSTATE_NONE; + } + else + { + NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around + } + } + } + else + { + if (TIMER_Done(NPC,"roam")) + { + NPCInfo->localState = LSTATE_NONE; + } + else + { + NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSDroid_Pain +------------------------- +*/ +void NPC_Droid_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + gentity_t *other = attacker; + int anim; + int mod = gPainMOD; + float pain_chance; + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( self->client->NPC_class == CLASS_R5D2 ) + { + pain_chance = NPC_GetPainChance( self, damage ); + + // Put it in pain + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this + { + // Health is between 0-30 or was hit by a DEMP2 so pop his head + if ( !self->s.m_iVehicleNum + && ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) ) + { + if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE + { + if ((self->NPC->localState != LSTATE_SPINNING) && + (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ))) + { + NPC_SetSurfaceOnOff( self, "head", TURN_OFF ); + + if ( self->client->ps.m_iVehicleNum ) + { + vec3_t up; + AngleVectors( self->r.currentAngles, NULL, NULL, up ); + G_PlayEffectID( G_EffectIndex("chunks/r5d2head_veh"), self->r.currentOrigin, up ); + } + else + { + G_PlayEffectID( G_EffectIndex("small_chunks") , self->r.currentOrigin, vec3_origin ); + G_PlayEffectID( G_EffectIndex("chunks/r5d2head"), self->r.currentOrigin, vec3_origin ); + } + + //self->s.powerups |= ( 1 << PW_SHOCKED ); + //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + self->client->ps.electrifyTime = level.time + 3000; + + TIMER_Set( self, "droidsmoketotal", 5000); + TIMER_Set( self, "droidspark", 100); + self->NPC->localState = LSTATE_SPINNING; + } + } + } + // Just give him normal pain for a little while + else + { + anim = self->client->ps.legsAnim; + + if ( anim == BOTH_STAND2 ) // On two legs? + { + anim = BOTH_PAIN1; + } + else // On three legs + { + anim = BOTH_PAIN2; + } + + NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + // Spin around in pain + self->NPC->localState = LSTATE_SPINNING; + TIMER_Set( self, "roam", Q_irand(1000,2000)); + } + } + } + else if (self->client->NPC_class == CLASS_MOUSE) + { + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) + { + self->NPC->localState = LSTATE_SPINNING; + //self->s.powerups |= ( 1 << PW_SHOCKED ); + //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + self->client->ps.electrifyTime = level.time + 3000; + } + else + { + self->NPC->localState = LSTATE_BACKINGUP; + } + + self->NPC->scriptFlags &= ~SCF_LOOK_FOR_ENEMIES; + } + else if ((self->client->NPC_class == CLASS_R2D2)) + { + + pain_chance = NPC_GetPainChance( self, damage ); + + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this + { + // Health is between 0-30 or was hit by a DEMP2 so pop his head + if ( !self->s.m_iVehicleNum + && ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) ) + { + if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE + { + if ((self->NPC->localState != LSTATE_SPINNING) && + (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ))) + { + NPC_SetSurfaceOnOff( self, "head", TURN_OFF ); + + if ( self->client->ps.m_iVehicleNum ) + { + vec3_t up; + AngleVectors( self->r.currentAngles, NULL, NULL, up ); + G_PlayEffectID( G_EffectIndex("chunks/r2d2head_veh"), self->r.currentOrigin, up ); + } + else + { + G_PlayEffectID( G_EffectIndex("small_chunks") , self->r.currentOrigin, vec3_origin ); + G_PlayEffectID( G_EffectIndex("chunks/r2d2head"), self->r.currentOrigin, vec3_origin ); + } + + //self->s.powerups |= ( 1 << PW_SHOCKED ); + //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + self->client->ps.electrifyTime = level.time + 3000; + + TIMER_Set( self, "droidsmoketotal", 5000); + TIMER_Set( self, "droidspark", 100); + self->NPC->localState = LSTATE_SPINNING; + } + } + } + // Just give him normal pain for a little while + else + { + anim = self->client->ps.legsAnim; + + if ( anim == BOTH_STAND2 ) // On two legs? + { + anim = BOTH_PAIN1; + } + else // On three legs + { + anim = BOTH_PAIN2; + } + + NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + // Spin around in pain + self->NPC->localState = LSTATE_SPINNING; + TIMER_Set( self, "roam", Q_irand(1000,2000)); + } + } + } + else if ( self->client->NPC_class == CLASS_INTERROGATOR && ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) && other ) + { + vec3_t dir; + + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir ); + VectorNormalize( dir ); + + VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); + self->client->ps.velocity[2] -= 127; + } + + NPC_Pain( self, attacker, damage); +} + + +/* +------------------------- +Droid_Pain +------------------------- +*/ +void Droid_Pain(void) +{ + if (TIMER_Done(NPC,"droidpain")) //He's done jumping around + { + NPCInfo->localState = LSTATE_NONE; + } +} + +/* +------------------------- +NPC_Mouse_Precache +------------------------- +*/ +void NPC_Mouse_Precache( void ) +{ + int i; + + for (i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/mouse/misc/mousego%d.wav", i ) ); + } + + G_EffectIndex( "env/small_explode" ); + G_SoundIndex( "sound/chars/mouse/misc/death1" ); + G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" ); +} + +/* +------------------------- +NPC_R5D2_Precache +------------------------- +*/ +void NPC_R5D2_Precache(void) +{ + int i; + + for ( i = 1; i < 5; i++) + { + G_SoundIndex( va( "sound/chars/r5d2/misc/r5talk%d.wav", i ) ); + } + //G_SoundIndex( "sound/chars/r5d2/misc/falling1.wav" ); + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); // ?? + G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" ); + G_EffectIndex( "env/med_explode"); + G_EffectIndex( "volumetric/droid_smoke" ); + G_EffectIndex("sparks/spark"); + G_EffectIndex( "chunks/r5d2head"); + G_EffectIndex( "chunks/r5d2head_veh"); +} + +/* +------------------------- +NPC_R2D2_Precache +------------------------- +*/ +void NPC_R2D2_Precache(void) +{ + int i; + + for ( i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/r2d2/misc/r2d2talk0%d.wav", i ) ); + } + //G_SoundIndex( "sound/chars/r2d2/misc/falling1.wav" ); + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); // ?? + G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" ); + G_EffectIndex( "env/med_explode"); + G_EffectIndex( "volumetric/droid_smoke" ); + G_EffectIndex("sparks/spark"); + G_EffectIndex( "chunks/r2d2head"); + G_EffectIndex( "chunks/r2d2head_veh"); +} + +/* +------------------------- +NPC_Gonk_Precache +------------------------- +*/ +void NPC_Gonk_Precache( void ) +{ + G_SoundIndex("sound/chars/gonk/misc/gonktalk1.wav"); + G_SoundIndex("sound/chars/gonk/misc/gonktalk2.wav"); + + G_SoundIndex("sound/chars/gonk/misc/death1.wav"); + G_SoundIndex("sound/chars/gonk/misc/death2.wav"); + G_SoundIndex("sound/chars/gonk/misc/death3.wav"); + + G_EffectIndex( "env/med_explode"); +} + +/* +------------------------- +NPC_Protocol_Precache +------------------------- +*/ +void NPC_Protocol_Precache( void ) +{ + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); + G_EffectIndex( "env/med_explode"); +} + +/* +static void R5D2_OffsetLook( float offset, vec3_t out ) +{ + vec3_t angles, forward, temp; + + GetAnglesForDirection( NPC->r.currentOrigin, NPCInfo->investigateGoal, angles ); + angles[YAW] += offset; + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( NPC->r.currentOrigin, 64, forward, out ); + + CalcEntitySpot( NPC, SPOT_HEAD, temp ); + out[2] = temp[2]; +} +*/ + +/* +------------------------- +R5D2_LookAround +------------------------- +*/ +/* +static void R5D2_LookAround( void ) +{ + vec3_t lookPos; + float perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime; + + //Keep looking at the spot + if ( perc < 0.25 ) + { + VectorCopy( NPCInfo->investigateGoal, lookPos ); + } + else if ( perc < 0.5f ) //Look up but straight ahead + { + R5D2_OffsetLook( 0.0f, lookPos ); + } + else if ( perc < 0.75f ) //Look right + { + R5D2_OffsetLook( 45.0f, lookPos ); + } + else //Look left + { + R5D2_OffsetLook( -45.0f, lookPos ); + } + + NPC_FacePosition( lookPos ); +} + +*/ + +/* +------------------------- +NPC_BSDroid_Default +------------------------- +*/ +void NPC_BSDroid_Default( void ) +{ + + if ( NPCInfo->localState == LSTATE_SPINNING ) + { + Droid_Spin(); + } + else if ( NPCInfo->localState == LSTATE_PAIN ) + { + Droid_Pain(); + } + else if ( NPCInfo->localState == LSTATE_DROP ) + { + NPC_UpdateAngles( qtrue, qtrue ); + ucmd.upmove = crandom() * 64; + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Droid_Patrol(); + } + else + { + Droid_Run(); + } +} diff --git a/codemp/game/NPC_AI_Grenadier.c b/codemp/game/NPC_AI_Grenadier.c new file mode 100644 index 0000000..e8147c7 --- /dev/null +++ b/codemp/game/NPC_AI_Grenadier.c @@ -0,0 +1,679 @@ +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +//#include "g_navigator.h" + +#include "../namespace_begin.h" +extern qboolean BG_SabersOff( playerState_t *ps ); +#include "../namespace_end.h" + +extern void CG_DrawAlert( vec3_t origin, float rating ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern void NPC_AimAdjust( int change ); +extern qboolean FlyingCreature( gentity_t *ent ); + +#define MAX_VIEW_DIST 1024 +#define MAX_VIEW_SPEED 250 +#define MAX_LIGHT_INTENSITY 255 +#define MIN_LIGHT_THRESHOLD 0.1 + +#define DISTANCE_SCALE 0.25f +#define DISTANCE_THRESHOLD 0.075f +#define SPEED_SCALE 0.25f +#define FOV_SCALE 0.5f +#define LIGHT_SCALE 0.25f + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 ) + +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS3; +static qboolean enemyCS3; +static qboolean faceEnemy3; +static qboolean move3; +static qboolean shoot3; +static float enemyDist3; + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void Grenadier_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "duck", 0 ); + TIMER_Set( ent, "stand", 0 ); + TIMER_Set( ent, "shuffleTime", 0 ); + TIMER_Set( ent, "sleepTime", 0 ); + TIMER_Set( ent, "enemyLastVisible", 0 ); + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "hideTime", 0 ); + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "stick", 0 ); + TIMER_Set( ent, "scoutTime", 0 ); + TIMER_Set( ent, "flee", 0 ); +} + +void NPC_Grenadier_PlayConfusionSound( gentity_t *self ) +{//FIXME: make this a custom sound in sound set + if ( self->health > 0 ) + { + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + TIMER_Set( self, "flee", 0 ); + self->NPC->squadState = SQUAD_IDLE; + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} + + +/* +------------------------- +NPC_ST_Pain +------------------------- +*/ + +void NPC_Grenadier_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + self->NPC->localState = LSTATE_UNDERFIRE; + + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stand", 2000 ); + + NPC_Pain( self, attacker, damage ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } +} + +/* +------------------------- +ST_HoldPosition +------------------------- +*/ + +static void Grenadier_HoldPosition( void ) +{ + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + NPCInfo->goalEntity = NULL; + + /*if ( TIMER_Done( NPC, "stand" ) ) + {//FIXME: what if can't shoot from this pos? + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +/* +------------------------- +ST_Move +------------------------- +*/ + +static qboolean Grenadier_Move( void ) +{ + qboolean moved; + navInfo_t info; + + NPCInfo->combatMove = qtrue;//always move straight toward our goal + moved = NPC_MoveToGoal( qtrue ); + + //Get the move info + NAV_GetLastMove( &info ); + + //FIXME: if we bump into another one of our guys and can't get around him, just stop! + //If we hit our target, then stop and fire! + if ( info.flags & NIF_COLLISION ) + { + if ( info.blocker == NPC->enemy ) + { + Grenadier_HoldPosition(); + } + } + + //If our move failed, then reset + if ( moved == qfalse ) + {//couldn't get to enemy + if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPC->client->ps.weapon == WP_THERMAL && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) + {//we were running after enemy + //Try to find a combat point that can hit the enemy + int cpFlags = (CP_CLEAR|CP_HAS_ROUTE); + int cp; + + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->r.currentOrigin, cpFlags, 32, -1 ); + if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + {//okay, try one by the enemy + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32, -1 ); + } + //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him... + if ( cp != -1 ) + {//found a combat point that has a clear shot to enemy + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL ); + return moved; + } + } + //just hang here + Grenadier_HoldPosition(); + } + + return moved; +} + +/* +------------------------- +NPC_BSGrenadier_Patrol +------------------------- +*/ + +void NPC_BSGrenadier_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be automatic now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + //Is there danger nearby + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); + if ( NPC_CheckForDanger( alertEvent ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + {//check for other alert events + //There is an event to look at + if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; + if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED ) + { + if ( level.alertEvents[alertEvent].owner && + level.alertEvents[alertEvent].owner->client && + level.alertEvents[alertEvent].owner->health >= 0 && + level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + //NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + } + } + else + {//FIXME: get more suspicious over time? + //Save the position for movement (if necessary) + VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); + NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); + if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) + {//suspicious looks longer + NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); + } + } + } + } + + if ( NPCInfo->investigateDebounceTime > level.time ) + {//FIXME: walk over to it, maybe? Not if not chase enemies + //NOTE: stops walking or doing anything else below + vec3_t dir, angles; + float o_yaw, o_pitch; + + VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); + vectoangles( dir, angles ); + + o_yaw = NPCInfo->desiredYaw; + o_pitch = NPCInfo->desiredPitch; + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + NPC_UpdateAngles( qtrue, qtrue ); + + NPCInfo->desiredYaw = o_yaw; + NPCInfo->desiredPitch = o_pitch; + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSGrenadier_Idle +------------------------- +*/ +/* +void NPC_BSGrenadier_Idle( void ) +{ + //FIXME: check for other alert events? + + //Is there danger nearby? + if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) ); + + NPC_UpdateAngles( qtrue, qtrue ); +} +*/ +/* +------------------------- +ST_CheckMoveState +------------------------- +*/ + +static void Grenadier_CheckMoveState( void ) +{ + //See if we're a scout + if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//behaviorState == BS_STAND_AND_SHOOT ) + { + if ( NPCInfo->goalEntity == NPC->enemy ) + { + move3 = qfalse; + return; + } + } + //See if we're running away + else if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + if ( TIMER_Done( NPC, "flee" ) ) + { + NPCInfo->squadState = SQUAD_IDLE; + } + else + { + faceEnemy3 = qfalse; + } + } + /* + else if ( NPCInfo->squadState == SQUAD_IDLE ) + { + if ( !NPCInfo->goalEntity ) + { + move3 = qfalse; + return; + } + //Should keep moving toward player when we're out of range... right? + } + */ + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) || + ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS3 && enemyDist3 <= 10000 ) ) + { + int newSquadState = SQUAD_STAND_AND_SHOOT; + //we got where we wanted to go, set timers based on why we were running + switch ( NPCInfo->squadState ) + { + case SQUAD_RETREAT://was running away + TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 ); + TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); + newSquadState = SQUAD_COVER; + break; + case SQUAD_TRANSITION://was heading for a combat point + TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); + break; + case SQUAD_SCOUT://was running after player + break; + default: + break; + } + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels + //don't do something else just yet + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); + //stop fleeing + if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + TIMER_Set( NPC, "flee", -level.time ); + NPCInfo->squadState = SQUAD_IDLE; + } + return; + } + + //keep going, hold of roamTimer until we get there + TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); + } + + if ( !NPCInfo->goalEntity ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + NPCInfo->goalEntity = NPC->enemy; + } + } +} + +/* +------------------------- +ST_CheckFireState +------------------------- +*/ + +static void Grenadier_CheckFireState( void ) +{ + if ( enemyCS3 ) + {//if have a clear shot, always try + return; + } + + if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT ) + {//runners never try to fire at the last pos + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + //continue to fire on their last position + /* + if ( !Q_irand( 0, 1 ) && NPCInfo->enemyLastSeenTime && level.time - NPCInfo->enemyLastSeenTime < 4000 ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + + VectorNormalize( dir ); + + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + //FIXME: they always throw toward enemy, so this will be very odd... + shoot3 = qtrue; + faceEnemy3 = qfalse; + + return; + } + */ +} + +qboolean Grenadier_EvaluateShot( int hit ) +{ + if ( !NPC->enemy ) + { + return qfalse; + } + + if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].r.svFlags&SVF_GLASS_BRUSH)) ) + {//can hit enemy or will hit glass, so shoot anyway + return qtrue; + } + return qfalse; +} + +/* +------------------------- +NPC_BSGrenadier_Attack +------------------------- +*/ + +void NPC_BSGrenadier_Attack( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //NPC_CheckEnemy( qtrue, qfalse ); + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse )//!NPC->enemy )// + { + NPC->enemy = NULL; + NPC_BSGrenadier_Patrol();//FIXME: or patrol? + return; + } + + if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//going to run + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( !NPC->enemy ) + {//WTF? somehow we lost our enemy? + NPC_BSGrenadier_Patrol();//FIXME: or patrol? + return; + } + + enemyLOS3 = enemyCS3 = qfalse; + move3 = qtrue; + faceEnemy3 = qfalse; + shoot3 = qfalse; + enemyDist3 = DistanceSquared( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ); + + //See if we should switch to melee attack + if ( enemyDist3 < 16384 //128 + && (!NPC->enemy->client + || NPC->enemy->client->ps.weapon != WP_SABER + || BG_SabersOff( &NPC->enemy->client->ps ) + ) + ) + {//enemy is close and not using saber + if ( NPC->client->ps.weapon == WP_THERMAL ) + {//grenadier + trace_t trace; + trap_Trace ( &trace, NPC->r.currentOrigin, NPC->enemy->r.mins, NPC->enemy->r.maxs, NPC->enemy->r.currentOrigin, NPC->s.number, NPC->enemy->clipmask ); + if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->enemy->s.number ) ) + {//I can get right to him + //reset fire-timing variables + NPC_ChangeWeapon( WP_STUN_BATON ); + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT ) + {//FIXME: should we be overriding scriptFlags? + NPCInfo->scriptFlags |= SCF_CHASE_ENEMIES;//NPCInfo->behaviorState = BS_HUNT_AND_KILL; + } + } + } + } + else if ( enemyDist3 > 65536 || (NPC->enemy->client && NPC->enemy->client->ps.weapon == WP_SABER && !NPC->enemy->client->ps.saberHolstered) )//256 + {//enemy is far or using saber + if ( NPC->client->ps.weapon == WP_STUN_BATON && (NPC->client->ps.stats[STAT_WEAPONS]&(1<enemy ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + enemyLOS3 = qtrue; + + if ( NPC->client->ps.weapon == WP_STUN_BATON ) + { + if ( enemyDist3 <= 4096 && InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 90, 45 ) )//within 64 & infront + { + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + enemyCS3 = qtrue; + } + } + else if ( InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 45, 90 ) ) + {//in front of me + //can we shoot our target? + //FIXME: how accurate/necessary is this check? + int hit = NPC_ShotEntity( NPC->enemy, NULL ); + gentity_t *hitEnt = &g_entities[hit]; + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) ) + { + float enemyHorzDist; + + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + enemyHorzDist = DistanceHorizontalSquared( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ); + if ( enemyHorzDist < 1048576 ) + {//within 1024 + enemyCS3 = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + } + else + { + NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + } + } + } + } + else + { + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + /* + else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy3 = qtrue; + } + */ + + if ( enemyLOS3 ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy3 = qtrue; + } + + if ( enemyCS3 ) + { + shoot3 = qtrue; + if ( NPC->client->ps.weapon == WP_THERMAL ) + {//don't chase and throw + move3 = qfalse; + } + else if ( NPC->client->ps.weapon == WP_STUN_BATON && enemyDist3 < (NPC->r.maxs[0]+NPC->enemy->r.maxs[0]+16)*(NPC->r.maxs[0]+NPC->enemy->r.maxs[0]+16) ) + {//close enough + move3 = qfalse; + } + }//this should make him chase enemy when out of range...? + + //Check for movement to take care of + Grenadier_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + Grenadier_CheckFireState(); + + if ( move3 ) + {//move toward goal + if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist3 > 10000 ) )//100 squared + { + move3 = Grenadier_Move(); + } + else + { + move3 = qfalse; + } + } + + if ( !move3 ) + { + if ( !TIMER_Done( NPC, "duck" ) ) + { + ucmd.upmove = -127; + } + //FIXME: what about leaning? + } + else + {//stop ducking! + TIMER_Set( NPC, "duck", -1 ); + } + + if ( !faceEnemy3 ) + {//we want to face in the dir we're running + if ( move3 ) + {//don't run away and shoot + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + shoot3 = qfalse; + } + NPC_UpdateAngles( qtrue, qtrue ); + } + else// if ( faceEnemy3 ) + {//face the enemy + NPC_FaceEnemy(qtrue); + } + + if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) + { + shoot3 = qfalse; + } + + //FIXME: don't shoot right away! + if ( shoot3 ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time ); + } + + } + } +} + +void NPC_BSGrenadier_Default( void ) +{ + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSGrenadier_Patrol(); + } + else//if ( NPC->enemy ) + {//have an enemy + NPC_BSGrenadier_Attack(); + } +} diff --git a/codemp/game/NPC_AI_Howler.c b/codemp/game/NPC_AI_Howler.c new file mode 100644 index 0000000..b25c3f0 --- /dev/null +++ b/codemp/game/NPC_AI_Howler.c @@ -0,0 +1,218 @@ +#include "b_local.h" + +// These define the working combat range for these suckers +#define MIN_DISTANCE 54 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 128 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +/* +------------------------- +NPC_Howler_Precache +------------------------- +*/ +void NPC_Howler_Precache( void ) +{ +} + + +/* +------------------------- +Howler_Idle +------------------------- +*/ +void Howler_Idle( void ) +{ +} + + +/* +------------------------- +Howler_Patrol +------------------------- +*/ +void Howler_Patrol( void ) +{ + vec3_t dif; + + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + else + { + if ( TIMER_Done( NPC, "patrolTime" )) + { + TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 ); + } + } + + //rwwFIXMEFIXME: Care about all clients, not just client 0 + VectorSubtract( g_entities[0].r.currentOrigin, NPC->r.currentOrigin, dif ); + + if ( VectorLengthSquared( dif ) < 256 * 256 ) + { + G_SetEnemy( NPC, &g_entities[0] ); + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Howler_Idle(); + return; + } +} + +/* +------------------------- +Howler_Move +------------------------- +*/ +void Howler_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + NPC_MoveToGoal( qtrue ); + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + } +} + +//--------------------------------------------------------- +void Howler_TryDamage( gentity_t *enemy, int damage ) +{ + vec3_t end, dir; + trace_t tr; + + if ( !enemy ) + { + return; + } + + AngleVectors( NPC->client->ps.viewangles, dir, NULL, NULL ); + VectorMA( NPC->r.currentOrigin, MIN_DISTANCE, dir, end ); + + // Should probably trace from the mouth, but, ah well. + trap_Trace( &tr, NPC->r.currentOrigin, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + + if ( tr.entityNum != ENTITYNUM_WORLD ) + { + G_Damage( &g_entities[tr.entityNum], NPC, NPC, dir, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } +} + +//------------------------------ +void Howler_Attack( void ) +{ + if ( !TIMER_Exists( NPC, "attacking" )) + { + // Going to do ATTACK1 + TIMER_Set( NPC, "attacking", 1700 + random() * 200 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack_dmg", 200 ); // level two damage + } + + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + if ( TIMER_Done2( NPC, "attack_dmg", qtrue )) + { + Howler_TryDamage( NPC->enemy, 5 ); + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); +} + +//---------------------------------- +void Howler_Combat( void ) +{ + float distance; + qboolean advance; + + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS4( NPC->enemy ) || UpdateGoal( )) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + + NPC_MoveToGoal( qtrue ); + return; + } + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + distance = DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + advance = (qboolean)( distance > MIN_DISTANCE_SQR ? qtrue : qfalse ); + + if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + Howler_Move( 1 ); + } + } + else + { + Howler_Attack(); + } +} + +/* +------------------------- +NPC_Howler_Pain +------------------------- +*/ +void NPC_Howler_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + if ( damage >= 10 ) + { + TIMER_Remove( self, "attacking" ); + TIMER_Set( self, "takingPain", 2900 ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } +} + + +/* +------------------------- +NPC_BSHowler_Default +------------------------- +*/ +void NPC_BSHowler_Default( void ) +{ + if ( NPC->enemy ) + { + Howler_Combat(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Howler_Patrol(); + } + else + { + Howler_Idle(); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/codemp/game/NPC_AI_ImperialProbe.c b/codemp/game/NPC_AI_ImperialProbe.c new file mode 100644 index 0000000..2b8d81c --- /dev/null +++ b/codemp/game/NPC_AI_ImperialProbe.c @@ -0,0 +1,609 @@ +#include "b_local.h" +#include "g_nav.h" + +#include "../namespace_begin.h" +gitem_t *BG_FindItemForAmmo( ammo_t ammo ); +#include "../namespace_end.h" +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_BACKINGUP, + LSTATE_SPINNING, + LSTATE_PAIN, + LSTATE_DROP +}; + +void ImperialProbe_Idle( void ); + +void NPC_Probe_Precache(void) +{ + int i; + + for ( i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/probe/misc/probetalk%d", i ) ); + } + G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); + G_SoundIndex("sound/chars/probe/misc/anger1"); + G_SoundIndex("sound/chars/probe/misc/fire"); + + G_EffectIndex( "chunks/probehead" ); + G_EffectIndex( "env/med_explode2" ); + G_EffectIndex( "explosions/probeexplosion1"); + G_EffectIndex( "bryar/muzzle_flash" ); + + RegisterItem( BG_FindItemForAmmo( AMMO_BLASTER )); + RegisterItem( BG_FindItemForWeapon( WP_BRYAR_PISTOL ) ); +} +/* +------------------------- +Hunter_MaintainHeight +------------------------- +*/ + +#define VELOCITY_DECAY 0.85f + +void ImperialProbe_MaintainHeight( void ) +{ + float dif; +// vec3_t endPos; +// trace_t trace; + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at about enemy eye level + if ( NPC->enemy ) + { + // Find the height difference + dif = NPC->enemy->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 8 ) + { + if ( fabs( dif ) > 16 ) + { + dif = ( dif < 0 ? -16 : 16 ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + // Apply friction + else if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + + // Stay at a given height until we take on an enemy +/* VectorSet( endPos, NPC->r.currentOrigin[0], NPC->r.currentOrigin[1], NPC->r.currentOrigin[2] - 512 ); + trap_Trace( &trace, NPC->r.currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID ); + + if ( trace.fraction != 1.0f ) + { + float length = ( trace.fraction * 512 ); + + if ( length < 80 ) + { + ucmd.upmove = 32; + } + else if ( length > 120 ) + { + ucmd.upmove = -32; + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } */ + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +/* +------------------------- +ImperialProbe_Strafe +------------------------- +*/ + +#define HUNTER_STRAFE_VEL 256 +#define HUNTER_STRAFE_DIS 200 +#define HUNTER_UPWARD_PUSH 32 + +void ImperialProbe_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->r.currentOrigin, HUNTER_STRAFE_DIS * dir, right, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, HUNTER_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + // Add a slight upward push + NPC->client->ps.velocity[2] += HUNTER_UPWARD_PUSH; + + // Set the strafe start time so we can do a controlled roll + //NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +/* +------------------------- +ImperialProbe_Hunt +-------------------------` +*/ + +#define HUNTER_FORWARD_BASE_SPEED 10 +#define HUNTER_FORWARD_MULTIPLIER 5 + +void ImperialProbe_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + ImperialProbe_Strafe(); + return; + } + } + + //If we don't want to advance, stop here + if ( advance == qfalse ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + //Get our direction from the navigator if we can't see our target + if ( NPC_GetMoveDirection( forward, &distance ) == qfalse ) + return; + } + else + { + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = HUNTER_FORWARD_BASE_SPEED + HUNTER_FORWARD_MULTIPLIER * g_spskill.integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +/* +------------------------- +ImperialProbe_FireBlaster +------------------------- +*/ +void ImperialProbe_FireBlaster(void) +{ + vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1; + static vec3_t forward, vright, up; + static vec3_t muzzle; + int genBolt1; + gentity_t *missile; + mdxaBone_t boltMatrix; + + genBolt1 = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash"); + + //FIXME: use {0, NPC->client->ps.legsYaw, 0} + trap_G2API_GetBoltMatrix( NPC->ghoul2, 0, + genBolt1, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time, + NULL, NPC->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle1 ); + + G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle1, vec3_origin ); + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/probe/misc/fire" )); + + if (NPC->health) + { + CalcEntitySpot( NPC->enemy, SPOT_CHEST, enemy_org1 ); + enemy_org1[0]+= Q_irand(0,10); + enemy_org1[1]+= Q_irand(0,10); + VectorSubtract (enemy_org1, muzzle1, delta1); + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + } + else + { + AngleVectors (NPC->r.currentAngles, forward, vright, up); + } + + missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC, qfalse ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + if ( g_spskill.integer <= 1 ) + { + missile->damage = 5; + } + else + { + missile->damage = 10; + } + + + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_UNKNOWN; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +ImperialProbe_Ranged +------------------------- +*/ +void ImperialProbe_Ranged( qboolean visible, qboolean advance ) +{ + int delay_min,delay_max; + + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + + if ( g_spskill.integer == 0 ) + { + delay_min = 500; + delay_max = 3000; + } + else if ( g_spskill.integer > 1 ) + { + delay_min = 500; + delay_max = 2000; + } + else + { + delay_min = 300; + delay_max = 1500; + } + + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) ); + ImperialProbe_FireBlaster(); +// ucmd.buttons |= BUTTON_ATTACK; + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ImperialProbe_Hunt( visible, advance ); + } +} + +/* +------------------------- +ImperialProbe_AttackDecision +------------------------- +*/ + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +void ImperialProbe_AttackDecision( void ) +{ + float distance; + qboolean visible; + qboolean advance; + + // Always keep a good height off the ground + ImperialProbe_MaintainHeight(); + + //randomly talk + if ( TIMER_Done(NPC,"patrolNoise") ) + { + if (TIMER_Done(NPC,"angerNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); + } + } + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse ) + { + ImperialProbe_Idle(); + return; + } + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL); + + // Rate our distance to the target, and our visibilty + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ImperialProbe_Hunt( visible, advance ); + return; + } + } + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + // Decide what type of attack to do + ImperialProbe_Ranged( visible, advance ); +} + +/* +------------------------- +NPC_BSDroid_Pain +------------------------- +*/ +void NPC_Probe_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + float pain_chance; + gentity_t *other = attacker; + int mod = gPainMOD; + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) // demp2 always messes them up real good + { + vec3_t endPos; + trace_t trace; + + VectorSet( endPos, self->r.currentOrigin[0], self->r.currentOrigin[1], self->r.currentOrigin[2] - 128 ); + trap_Trace( &trace, self->r.currentOrigin, NULL, NULL, endPos, self->s.number, MASK_SOLID ); + + if ( trace.fraction == 1.0f || mod == MOD_DEMP2 ) // demp2 always does this + { + /* + if (self->client->clientInfo.headModel != 0) + { + vec3_t origin; + + VectorCopy(self->r.currentOrigin,origin); + origin[2] +=50; +// G_PlayEffect( "small_chunks", origin ); + G_PlayEffect( "chunks/probehead", origin ); + G_PlayEffect( "env/med_explode2", origin ); + self->client->clientInfo.headModel = 0; + self->client->moveType = MT_RUNJUMP; + self->client->ps.gravity = g_gravity->value*.1; + } + */ + + if ( (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) && other ) + { + vec3_t dir; + + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir ); + VectorNormalize( dir ); + + VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); + self->client->ps.velocity[2] -= 127; + } + + //self->s.powerups |= ( 1 << PW_SHOCKED ); + //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + self->client->ps.electrifyTime = level.time + 3000; + + self->NPC->localState = LSTATE_DROP; + } + } + else + { + pain_chance = NPC_GetPainChance( self, damage ); + + if ( random() < pain_chance ) // Spin around in pain? + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE); + } + } + + NPC_Pain( self, attacker, damage ); +} + +/* +------------------------- +ImperialProbe_Idle +------------------------- +*/ + +void ImperialProbe_Idle( void ) +{ + ImperialProbe_MaintainHeight(); + + NPC_BSIdle(); +} + +/* +------------------------- +NPC_BSImperialProbe_Patrol +------------------------- +*/ +void ImperialProbe_Patrol( void ) +{ + ImperialProbe_MaintainHeight(); + + if ( NPC_CheckPlayerTeamStealth() ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL ); + + if ( UpdateGoal() ) + { + //start loop sound once we move + NPC->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + //randomly talk + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + else // He's got an enemy. Make him angry. + { + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/probe/misc/anger1" ); + TIMER_Set( NPC, "angerNoise", Q_irand( 2000, 4000 ) ); + //NPCInfo->behaviorState = BS_HUNT_AND_KILL; + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +ImperialProbe_Wait +------------------------- +*/ +void ImperialProbe_Wait(void) +{ + if ( NPCInfo->localState == LSTATE_DROP ) + { + vec3_t endPos; + trace_t trace; + + NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->desiredYaw + 25 ); + + VectorSet( endPos, NPC->r.currentOrigin[0], NPC->r.currentOrigin[1], NPC->r.currentOrigin[2] - 32 ); + trap_Trace( &trace, NPC->r.currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID ); + + if ( trace.fraction != 1.0f ) + { + G_Damage(NPC, NPC->enemy, NPC->enemy, NULL, NULL, 2000, 0,MOD_UNKNOWN); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSImperialProbe_Default +------------------------- +*/ +void NPC_BSImperialProbe_Default( void ) +{ + + if ( NPC->enemy ) + { + NPCInfo->goalEntity = NPC->enemy; + ImperialProbe_AttackDecision(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + ImperialProbe_Patrol(); + } + else if ( NPCInfo->localState == LSTATE_DROP ) + { + ImperialProbe_Wait(); + } + else + { + ImperialProbe_Idle(); + } +} diff --git a/codemp/game/NPC_AI_Jedi.c b/codemp/game/NPC_AI_Jedi.c new file mode 100644 index 0000000..effb433 --- /dev/null +++ b/codemp/game/NPC_AI_Jedi.c @@ -0,0 +1,6168 @@ +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +#include "w_saber.h" + +#include "../namespace_begin.h" +extern qboolean BG_SabersOff( playerState_t *ps ); +#include "../namespace_end.h" + +extern void CG_DrawAlert( vec3_t origin, float rating ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void ForceJump( gentity_t *self, usercmd_t *ucmd ); +extern vmCvar_t g_saberRealisticCombat; +extern vmCvar_t d_slowmodeath; + +void G_StartMatrixEffect( gentity_t *ent ) +{ //perhaps write this at some point? + +} + +#define MAX_VIEW_DIST 2048 +#define MAX_VIEW_SPEED 100 +#define JEDI_MAX_LIGHT_INTENSITY 64 +#define JEDI_MIN_LIGHT_THRESHOLD 10 +#define JEDI_MAX_LIGHT_THRESHOLD 50 + +#define DISTANCE_SCALE 0.25f +//#define DISTANCE_THRESHOLD 0.075f +#define SPEED_SCALE 0.25f +#define FOV_SCALE 0.5f +#define LIGHT_SCALE 0.25f + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.3 ) + +#define MAX_CHECK_THRESHOLD 1 + +extern void NPC_ClearLookTarget( gentity_t *self ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern qboolean NPC_CheckEnemyStealth( void ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); + +#include "../namespace_begin.h" +extern gitem_t *BG_FindItemForAmmo( ammo_t ammo ); +#include "../namespace_end.h" + +extern void ForceThrow( gentity_t *self, qboolean pull ); +extern void ForceLightning( gentity_t *self ); +extern void ForceHeal( gentity_t *self ); +extern void ForceRage( gentity_t *self ); +extern void ForceProtect( gentity_t *self ); +extern void ForceAbsorb( gentity_t *self ); +extern int WP_MissileBlockForBlock( int saberBlock ); +extern qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod ); +extern qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +extern qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +extern void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower ); +extern void WP_DeactivateSaber( gentity_t *self, qboolean clearLength ); //clearLength = qfalse +extern void WP_ActivateSaber( gentity_t *self ); +//extern void WP_SaberBlock(gentity_t *saber, vec3_t hitloc); + +#include "../namespace_begin.h" +extern qboolean PM_SaberInStart( int move ); +extern qboolean BG_SaberInSpecialAttack( int anim ); +extern qboolean BG_SaberInAttack( int move ); +extern qboolean PM_SaberInBounce( int move ); +extern qboolean PM_SaberInParry( int move ); +extern qboolean PM_SaberInKnockaway( int move ); +extern qboolean PM_SaberInBrokenParry( int move ); +extern qboolean PM_SaberInDeflect( int move ); +extern qboolean BG_SpinningSaberAnim( int anim ); +extern qboolean BG_FlippingAnim( int anim ); +extern qboolean PM_RollingAnim( int anim ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean BG_InRoll( playerState_t *ps, int anim ); +extern qboolean BG_CrouchAnim( int anim ); +#include "../namespace_end.h" + +extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent); + +extern int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd ); + +extern void G_TestLine(vec3_t start, vec3_t end, int color, int time); + +static void Jedi_Aggression( gentity_t *self, int change ); +qboolean Jedi_WaitingAmbush( gentity_t *self ); + +#include "../namespace_begin.h" +extern int bg_parryDebounce[]; +#include "../namespace_end.h" + +static int jediSpeechDebounceTime[TEAM_NUM_TEAMS];//used to stop several jedi from speaking all at once +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void NPC_ShadowTrooper_Precache( void ) +{ + RegisterItem( BG_FindItemForAmmo( AMMO_FORCE ) ); + G_SoundIndex( "sound/chars/shadowtrooper/cloak.wav" ); + G_SoundIndex( "sound/chars/shadowtrooper/decloak.wav" ); +} + +void Jedi_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "strafeLeft", 0 ); + TIMER_Set( ent, "strafeRight", 0 ); + TIMER_Set( ent, "noStrafe", 0 ); + TIMER_Set( ent, "walking", 0 ); + TIMER_Set( ent, "taunting", 0 ); + TIMER_Set( ent, "parryTime", 0 ); + TIMER_Set( ent, "parryReCalcTime", 0 ); + TIMER_Set( ent, "forceJumpChasing", 0 ); + TIMER_Set( ent, "jumpChaseDebounce", 0 ); + TIMER_Set( ent, "moveforward", 0 ); + TIMER_Set( ent, "moveback", 0 ); + TIMER_Set( ent, "movenone", 0 ); + TIMER_Set( ent, "moveright", 0 ); + TIMER_Set( ent, "moveleft", 0 ); + TIMER_Set( ent, "movecenter", 0 ); + TIMER_Set( ent, "saberLevelDebounce", 0 ); + TIMER_Set( ent, "noRetreat", 0 ); + TIMER_Set( ent, "holdLightning", 0 ); + TIMER_Set( ent, "gripping", 0 ); + TIMER_Set( ent, "draining", 0 ); + TIMER_Set( ent, "noturn", 0 ); +} + +void Jedi_PlayBlockedPushSound( gentity_t *self ) +{ + if ( !self->s.number ) + { + G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 ); + } + else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 ); + self->NPC->blockedSpeechDebounceTime = level.time + 3000; + } +} + +void Jedi_PlayDeflectSound( gentity_t *self ) +{ + if ( !self->s.number ) + { + G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 ); + } + else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 ); + self->NPC->blockedSpeechDebounceTime = level.time + 3000; + } +} + +void NPC_Jedi_PlayConfusionSound( gentity_t *self ) +{ + if ( self->health > 0 ) + { + if ( self->client && ( self->client->NPC_class == CLASS_TAVION || self->client->NPC_class == CLASS_DESANN ) ) + { + G_AddVoiceEvent( self, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), 2000 ); + } + else if ( Q_irand( 0, 1 ) ) + { + G_AddVoiceEvent( self, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 2000 ); + } + else + { + G_AddVoiceEvent( self, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 2000 ); + } + } +} + +void Boba_Precache( void ) +{ + G_SoundIndex( "sound/boba/jeton.wav" ); + G_SoundIndex( "sound/boba/jethover.wav" ); + G_SoundIndex( "sound/effects/combustfire.mp3" ); + G_EffectIndex( "boba/jet" ); + G_EffectIndex( "boba/fthrw" ); +} + +extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +void Boba_ChangeWeapon( int wp ) +{ + if ( NPC->s.weapon == wp ) + { + return; + } + NPC_ChangeWeapon( wp ); + G_AddEvent( NPC, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" )); +} + +void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty ) +{ + int parts; + qboolean runningResist = qfalse; + + if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client ) + { + return; + } + if ( (!self->s.number || self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) || self->client->NPC_class == CLASS_LUKE) + && (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.fd.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.fd.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) ) + { + runningResist = qtrue; + } + if ( !runningResist + && self->client->ps.groundEntityNum != ENTITYNUM_NONE + && !BG_SpinningSaberAnim( self->client->ps.legsAnim ) + && !BG_FlippingAnim( self->client->ps.legsAnim ) + && !PM_RollingAnim( self->client->ps.legsAnim ) + && !PM_InKnockDown( &self->client->ps ) + && !BG_CrouchAnim( self->client->ps.legsAnim )) + {//if on a surface and not in a spin or flip, play full body resist + parts = SETANIM_BOTH; + } + else + {//play resist just in torso + parts = SETANIM_TORSO; + } + NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( !noPenalty ) + { + char buf[128]; + float tFVal = 0; + + trap_Cvar_VariableStringBuffer("timescale", buf, sizeof(buf)); + + tFVal = atof(buf); + + if ( !runningResist ) + { + VectorClear( self->client->ps.velocity ); + //still stop them from attacking or moving for a bit, though + //FIXME: maybe push just a little (like, slide)? + self->client->ps.weaponTime = 1000; + if ( self->client->ps.fd.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * tFVal ); + } + self->client->ps.pm_time = self->client->ps.weaponTime; + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + //play the full body push effect on me + //self->forcePushTime = level.time + 600; // let the push effect last for 600 ms + //rwwFIXMEFIXME: Do this? + } + else + { + self->client->ps.weaponTime = 600; + if ( self->client->ps.fd.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * tFVal ); + } + } + } + //play my force push effect on my hand + self->client->ps.powerups[PW_DISINT_4] = level.time + self->client->ps.torsoTimer + 500; + self->client->ps.powerups[PW_PULL] = 0; + Jedi_PlayBlockedPushSound( self ); +} + +qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, vec3_t pushDir, qboolean forceKnockdown ) //forceKnockdown = qfalse +{ + vec3_t pDir, fwd, right, ang; + float fDot, rDot; + int strafeTime; + + if ( self->client->NPC_class != CLASS_BOBAFETT ) + { + return qfalse; + } + + if ( (self->client->ps.eFlags2&EF2_FLYING) )//moveType == MT_FLYSWIM ) + {//can't knock me down when I'm flying + return qtrue; + } + + VectorSet(ang, 0, self->r.currentAngles[YAW], 0); + strafeTime = Q_irand( 1000, 2000 ); + + AngleVectors( ang, fwd, right, NULL ); + VectorNormalize2( pushDir, pDir ); + fDot = DotProduct( pDir, fwd ); + rDot = DotProduct( pDir, right ); + + if ( Q_irand( 0, 2 ) ) + {//flip or roll with it + usercmd_t tempCmd; + if ( fDot >= 0.4f ) + { + tempCmd.forwardmove = 127; + TIMER_Set( self, "moveforward", strafeTime ); + } + else if ( fDot <= -0.4f ) + { + tempCmd.forwardmove = -127; + TIMER_Set( self, "moveback", strafeTime ); + } + else if ( rDot > 0 ) + { + tempCmd.rightmove = 127; + TIMER_Set( self, "strafeRight", strafeTime ); + TIMER_Set( self, "strafeLeft", -1 ); + } + else + { + tempCmd.rightmove = -127; + TIMER_Set( self, "strafeLeft", strafeTime ); + TIMER_Set( self, "strafeRight", -1 ); + } + G_AddEvent( self, EV_JUMP, 0 ); + if ( !Q_irand( 0, 1 ) ) + {//flip + self->client->ps.fd.forceJumpCharge = 280;//FIXME: calc this intelligently? + ForceJump( self, &tempCmd ); + } + else + {//roll + TIMER_Set( self, "duck", strafeTime ); + } + self->painDebounceTime = 0;//so we do something + } + else if ( !Q_irand( 0, 1 ) && forceKnockdown ) + {//resist + WP_ResistForcePush( self, pusher, qtrue ); + } + else + {//fall down + return qfalse; + } + + return qtrue; +} + +void Boba_FlyStart( gentity_t *self ) +{//switch to seeker AI for a while + if ( TIMER_Done( self, "jetRecharge" ) ) + { + self->client->ps.gravity = 0; + if ( self->NPC ) + { + self->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY; + } + self->client->ps.eFlags2 |= EF2_FLYING;//moveType = MT_FLYSWIM; + self->client->jetPackTime = level.time + Q_irand( 3000, 10000 ); + //take-off sound + G_SoundOnEnt( self, CHAN_ITEM, "sound/boba/jeton.wav" ); + //jet loop sound + self->s.loopSound = G_SoundIndex( "sound/boba/jethover.wav" ); + if ( self->NPC ) + { + self->count = Q3_INFINITE; // SEEKER shot ammo count + } + } +} + +void Boba_FlyStop( gentity_t *self ) +{ + self->client->ps.gravity = g_gravity.value; + if ( self->NPC ) + { + self->NPC->aiFlags &= ~NPCAI_CUSTOM_GRAVITY; + } + self->client->ps.eFlags2 &= ~EF2_FLYING; + self->client->jetPackTime = 0; + //stop jet loop sound + self->s.loopSound = 0; + if ( self->NPC ) + { + self->count = 0; // SEEKER shot ammo count + TIMER_Set( self, "jetRecharge", Q_irand( 1000, 5000 ) ); + TIMER_Set( self, "jumpChaseDebounce", Q_irand( 500, 2000 ) ); + } +} + +qboolean Boba_Flying( gentity_t *self ) +{ + return ((qboolean)(self->client->ps.eFlags2&EF2_FLYING));//moveType==MT_FLYSWIM)); +} + +void Boba_FireFlameThrower( gentity_t *self ) +{ + int damage = Q_irand( 20, 30 ); + trace_t tr; + gentity_t *traceEnt = NULL; + mdxaBone_t boltMatrix; + vec3_t start, end, dir, traceMins = {-4, -4, -4}, traceMaxs = {4, 4, 4}; + + trap_G2API_GetBoltMatrix( self->ghoul2, 0, self->client->renderInfo.handLBolt, + &boltMatrix, self->r.currentAngles, self->r.currentOrigin, level.time, + NULL, self->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, start ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + //G_PlayEffect( "boba/fthrw", start, dir ); + VectorMA( start, 128, dir, end ); + + trap_Trace( &tr, start, traceMins, traceMaxs, end, self->s.number, MASK_SHOT ); + + traceEnt = &g_entities[tr.entityNum]; + if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage ) + { + G_Damage( traceEnt, self, self, dir, tr.endpos, damage, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|/*DAMAGE_NO_HIT_LOC|*/DAMAGE_IGNORE_TEAM, MOD_LAVA ); + //rwwFIXMEFIXME: add DAMAGE_NO_HIT_LOC? + } +} + +//extern void SP_fx_explosion_trail( gentity_t *ent ); +void Boba_StartFlameThrower( gentity_t *self ) +{ + int flameTime = 4000;//Q_irand( 1000, 3000 ); + mdxaBone_t boltMatrix; + vec3_t org, dir; + + self->client->ps.torsoTimer = flameTime;//+1000; + if ( self->NPC ) + { + TIMER_Set( self, "nextAttackDelay", flameTime ); + TIMER_Set( self, "walking", 0 ); + } + TIMER_Set( self, "flameTime", flameTime ); + /* + gentity_t *fire = G_Spawn(); + if ( fire != NULL ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir, ang; + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, NPC->handRBolt, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir ); + vectoangles( dir, ang ); + + VectorCopy( org, fire->s.origin ); + VectorCopy( ang, fire->s.angles ); + + fire->targetname = "bobafire"; + SP_fx_explosion_trail( fire ); + fire->damage = 1; + fire->radius = 10; + fire->speed = 200; + fire->fxID = G_EffectIndex( "boba/fthrw" );//"env/exp_fire_trail" );//"env/small_fire" + fx_explosion_trail_link( fire ); + fx_explosion_trail_use( fire, NPC, NPC ); + + } + */ + G_SoundOnEnt( self, CHAN_WEAPON, "sound/effects/combustfire.mp3" ); + + trap_G2API_GetBoltMatrix(NPC->ghoul2, 0, NPC->client->renderInfo.handRBolt, &boltMatrix, NPC->r.currentAngles, + NPC->r.currentOrigin, level.time, NULL, NPC->modelScale); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + + G_PlayEffectID( G_EffectIndex("boba/fthrw"), org, dir); +} + +void Boba_DoFlameThrower( gentity_t *self ) +{ + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( TIMER_Done( self, "nextAttackDelay" ) && TIMER_Done( self, "flameTime" ) ) + { + Boba_StartFlameThrower( self ); + } + Boba_FireFlameThrower( self ); +} + +void Boba_FireDecide( void ) +{ + qboolean enemyLOS = qfalse; + qboolean enemyCS = qfalse; + qboolean enemyInFOV = qfalse; + //qboolean move = qtrue; + qboolean faceEnemy = qfalse; + qboolean shoot = qfalse; + qboolean hitAlly = qfalse; + vec3_t impactPos; + float enemyDist; + float dot; + vec3_t enemyDir, shootDir; + + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE + && NPC->client->ps.fd.forceJumpZStart + && !BG_FlippingAnim( NPC->client->ps.legsAnim ) + && !Q_irand( 0, 10 ) ) + {//take off + Boba_FlyStart( NPC ); + } + + if ( !NPC->enemy ) + { + return; + } + + /* + if ( NPC->enemy->enemy != NPC && NPC->health == NPC->client->pers.maxHealth ) + { + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + Boba_ChangeWeapon( WP_DISRUPTOR ); + } + else */if ( NPC->enemy->s.weapon == WP_SABER ) + { + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + Boba_ChangeWeapon( WP_ROCKET_LAUNCHER ); + } + else + { + if ( NPC->health < NPC->client->pers.maxHealth*0.5f ) + { + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + Boba_ChangeWeapon( WP_BLASTER ); + NPCInfo->burstMin = 3; + NPCInfo->burstMean = 12; + NPCInfo->burstMax = 20; + NPCInfo->burstSpacing = Q_irand( 300, 750 );//attack debounce + } + else + { + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + Boba_ChangeWeapon( WP_BLASTER ); + } + } + + VectorClear( impactPos ); + enemyDist = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir ); + VectorNormalize( enemyDir ); + AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL ); + dot = DotProduct( enemyDir, shootDir ); + if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 ) + {//enemy is in front of me or they're very close and not behind me + enemyInFOV = qtrue; + } + + if ( (enemyDist < (128*128)&&enemyInFOV) || !TIMER_Done( NPC, "flameTime" ) ) + {//flamethrower + Boba_DoFlameThrower( NPC ); + enemyCS = qfalse; + shoot = qfalse; + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); + } + else if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128 + {//enemy within 128 + if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) && + (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//shooting an explosive, but enemy too close, switch to primary fire + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not... + } + } + else if ( enemyDist > 65536 )//256 squared + { + if ( NPC->client->ps.weapon == WP_DISRUPTOR ) + {//sniping... should be assumed + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + {//use primary fire + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( WP_DISRUPTOR ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + //can we see our target? + if ( TIMER_Done( NPC, "nextAttackDelay" ) && TIMER_Done( NPC, "flameTime" ) ) + { + if ( NPC_ClearLOS4( NPC->enemy ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + enemyLOS = qtrue; + + if ( NPC->client->ps.weapon == WP_NONE ) + { + enemyCS = qfalse;//not true, but should stop us from firing + } + else + {//can we shoot our target? + if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128 + { + enemyCS = qfalse;//not true, but should stop us from firing + hitAlly = qtrue;//us! + //FIXME: if too close, run away! + } + else if ( enemyInFOV ) + {//if enemy is FOV, go ahead and check for shooting + int hit = NPC_ShotEntity( NPC->enemy, impactPos ); + gentity_t *hitEnt = &g_entities[hit]; + + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) ) + {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway + enemyCS = qtrue; + //NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + else + {//Hmm, have to get around this bastard + //NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) + {//would hit an ally, don't fire!!! + hitAlly = qtrue; + } + else + {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire + } + } + } + else + { + enemyCS = qfalse;//not true, but should stop us from firing + } + } + } + else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + //NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + + if ( NPC->client->ps.weapon == WP_NONE ) + { + faceEnemy = qfalse; + shoot = qfalse; + } + else + { + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + if ( enemyCS ) + { + shoot = qtrue; + } + } + + if ( !enemyCS ) + {//if have a clear shot, always try + //See if we should continue to fire on their last position + //!TIMER_Done( NPC, "stick" ) || + if ( !hitAlly //we're not going to hit an ally + && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS? + && NPCInfo->enemyLastSeenTime > 0 )//we've seen the enemy + { + if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )//we have seem the enemy in the last 10 seconds + { + if ( !Q_irand( 0, 10 ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + qboolean tooClose = qfalse; + qboolean tooFar = qfalse; + float distThreshold; + float dist; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + if ( VectorCompare( impactPos, vec3_origin ) ) + {//never checked ShotEntity this frame, so must do a trace... + trace_t tr; + //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; + vec3_t forward, end; + AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 8192, forward, end ); + trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + VectorCopy( tr.endpos, impactPos ); + } + + //see if impact would be too close to me + distThreshold = 16384/*128*128*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 65536/*256*256*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 65536/*256*256*/; + } + break; + default: + break; + } + + dist = DistanceSquared( impactPos, muzzle ); + + if ( dist < distThreshold ) + {//impact would be too close to me + tooClose = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 || + (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 )) + {//we've haven't seen them in the last 5 seconds + //see if it's too far from where he is + distThreshold = 65536/*256*256*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 262144/*512*512*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 262144/*512*512*/; + } + break; + default: + break; + } + dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation ); + if ( dist > distThreshold ) + {//impact would be too far from enemy + tooFar = qtrue; + } + } + + if ( !tooClose && !tooFar ) + {//okay too shoot at last pos + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + VectorNormalize( dir ); + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot = qtrue; + faceEnemy = qfalse; + } + } + } + } + } + + //FIXME: don't shoot right away! + if ( NPC->client->ps.weaponTime > 0 ) + { + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER ) + { + if ( !enemyLOS || !enemyCS ) + {//cancel it + NPC->client->ps.weaponTime = 0; + } + else + {//delay our next attempt + TIMER_Set( NPC, "nextAttackDelay", Q_irand( 500, 1000 ) ); + } + } + } + else if ( shoot ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "nextAttackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + //NASTY + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER + && (ucmd.buttons&BUTTON_ATTACK) + && !Q_irand( 0, 3 ) ) + {//every now and then, shoot a homing rocket + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + NPC->client->ps.weaponTime = Q_irand( 500, 1500 ); + } + } + } + } +} + +void Jedi_Cloak( gentity_t *self ) +{ + if ( self ) + { + self->flags |= FL_NOTARGET; + if ( self->client ) + { + if ( !self->client->ps.powerups[PW_CLOAKED] ) + {//cloak + self->client->ps.powerups[PW_CLOAKED] = Q3_INFINITE; + + //FIXME: debounce attacks? + //FIXME: temp sound + G_Sound( self, CHAN_ITEM, G_SoundIndex("sound/chars/shadowtrooper/cloak.wav") ); + } + } + } +} + +void Jedi_Decloak( gentity_t *self ) +{ + if ( self ) + { + self->flags &= ~FL_NOTARGET; + if ( self->client ) + { + if ( self->client->ps.powerups[PW_CLOAKED] ) + {//Uncloak + self->client->ps.powerups[PW_CLOAKED] = 0; + + G_Sound( self, CHAN_ITEM, G_SoundIndex("sound/chars/shadowtrooper/decloak.wav") ); + } + } + } +} + +void Jedi_CheckCloak( void ) +{ + if ( NPC && NPC->client && NPC->client->NPC_class == CLASS_SHADOWTROOPER ) + { + if ( !NPC->client->ps.saberHolstered || + NPC->health <= 0 || + NPC->client->ps.saberInFlight || + // (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) || + // (NPC->client->ps.eFlags&EF_FORCE_DRAINED) || + NPC->painDebounceTime > level.time ) + {//can't be cloaked if saber is on, or dead or saber in flight or taking pain or being gripped + Jedi_Decloak( NPC ); + } + else if ( NPC->health > 0 + && !NPC->client->ps.saberInFlight + // && !(NPC->client->ps.eFlags&EF_FORCE_GRIPPED) + // && !(NPC->client->ps.eFlags&EF_FORCE_DRAINED) + && NPC->painDebounceTime < level.time ) + {//still alive, have saber in hand, not taking pain and not being gripped + Jedi_Cloak( NPC ); + } + } +} +/* +========================================================================================== +AGGRESSION +========================================================================================== +*/ +static void Jedi_Aggression( gentity_t *self, int change ) +{ + int upper_threshold, lower_threshold; + + self->NPC->stats.aggression += change; + + //FIXME: base this on initial NPC stats + if ( self->client->playerTeam == NPCTEAM_PLAYER ) + {//good guys are less aggressive + upper_threshold = 7; + lower_threshold = 1; + } + else + {//bad guys are more aggressive + if ( self->client->NPC_class == CLASS_DESANN ) + { + upper_threshold = 20; + lower_threshold = 5; + } + else + { + upper_threshold = 10; + lower_threshold = 3; + } + } + + if ( self->NPC->stats.aggression > upper_threshold ) + { + self->NPC->stats.aggression = upper_threshold; + } + else if ( self->NPC->stats.aggression < lower_threshold ) + { + self->NPC->stats.aggression = lower_threshold; + } + //Com_Printf( "(%d) %s agg %d change: %d\n", level.time, self->NPC_type, self->NPC->stats.aggression, change ); +} + +static void Jedi_AggressionErosion( int amt ) +{ + if ( TIMER_Done( NPC, "roamTime" ) ) + {//the longer we're not alerted and have no enemy, the more our aggression goes down + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) ); + Jedi_Aggression( NPC, amt ); + } + + if ( NPCInfo->stats.aggression < 4 || (NPCInfo->stats.aggression < 6&&NPC->client->NPC_class == CLASS_DESANN)) + {//turn off the saber + WP_DeactivateSaber( NPC, qfalse ); + } +} + +void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy ) +{ + float healthAggression; + float weaponAggression; + int newAggression; + + switch( enemy->s.weapon ) + { + case WP_SABER: + healthAggression = (float)self->health/200.0f*6.0f; + weaponAggression = 7;//go after him + break; + case WP_BLASTER: + if ( DistanceSquared( self->r.currentOrigin, enemy->r.currentOrigin ) < 65536 )//256 squared + { + healthAggression = (float)self->health/200.0f*8.0f; + weaponAggression = 8;//go after him + } + else + { + healthAggression = 8.0f - ((float)self->health/200.0f*8.0f); + weaponAggression = 2;//hang back for a second + } + break; + default: + healthAggression = (float)self->health/200.0f*8.0f; + weaponAggression = 6;//approach + break; + } + //Average these with current aggression + newAggression = ceil( (healthAggression + weaponAggression + (float)self->NPC->stats.aggression )/3.0f); + //Com_Printf( "(%d) new agg %d - new enemy\n", level.time, newAggression ); + Jedi_Aggression( self, newAggression - self->NPC->stats.aggression ); + + //don't taunt right away + TIMER_Set( self, "chatter", Q_irand( 4000, 7000 ) ); +} + +static void Jedi_Rage( void ) +{ + Jedi_Aggression( NPC, 10 - NPCInfo->stats.aggression + Q_irand( -2, 2 ) ); + TIMER_Set( NPC, "roamTime", 0 ); + TIMER_Set( NPC, "chatter", 0 ); + TIMER_Set( NPC, "walking", 0 ); + TIMER_Set( NPC, "taunting", 0 ); + TIMER_Set( NPC, "jumpChaseDebounce", 0 ); + TIMER_Set( NPC, "movenone", 0 ); + TIMER_Set( NPC, "movecenter", 0 ); + TIMER_Set( NPC, "noturn", 0 ); + ForceRage( NPC ); +} + +void Jedi_RageStop( gentity_t *self ) +{ + if ( self->NPC ) + {//calm down and back off + TIMER_Set( self, "roamTime", 0 ); + Jedi_Aggression( self, Q_irand( -5, 0 ) ); + } +} +/* +========================================================================================== +SPEAKING +========================================================================================== +*/ + +static qboolean Jedi_BattleTaunt( void ) +{ + if ( TIMER_Done( NPC, "chatter" ) + && !Q_irand( 0, 3 ) + && NPCInfo->blockedSpeechDebounceTime < level.time + && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time ) + { + int event = -1; + if ( NPC->client->playerTeam == NPCTEAM_PLAYER + && NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_JEDI ) + {//a jedi fighting a jedi - training + if ( NPC->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) + {//only trainer taunts + event = EV_TAUNT1; + } + } + else + {//reborn or a jedi fighting an enemy + event = Q_irand( EV_TAUNT1, EV_TAUNT3 ); + } + if ( event != -1 ) + { + G_AddVoiceEvent( NPC, event, 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 6000; + TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) ); + + if ( NPC->enemy && NPC->enemy->NPC && NPC->enemy->s.weapon == WP_SABER && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_JEDI ) + {//Have the enemy jedi say something in response when I'm done? + } + return qtrue; + } + } + return qfalse; +} + +/* +========================================================================================== +MOVEMENT +========================================================================================== +*/ +static qboolean Jedi_ClearPathToSpot( vec3_t dest, int impactEntNum ) +{ + trace_t trace; + vec3_t mins, start, end, dir; + float dist, drop; + float i; + + //Offset the step height + VectorSet( mins, NPC->r.mins[0], NPC->r.mins[1], NPC->r.mins[2] + STEPSIZE ); + + trap_Trace( &trace, NPC->r.currentOrigin, mins, NPC->r.maxs, dest, NPC->s.number, NPC->clipmask ); + + //Do a simple check + if ( trace.allsolid || trace.startsolid ) + {//inside solid + return qfalse; + } + + if ( trace.fraction < 1.0f ) + {//hit something + if ( impactEntNum != ENTITYNUM_NONE && trace.entityNum == impactEntNum ) + {//hit what we're going after + return qtrue; + } + else + { + return qfalse; + } + } + + //otherwise, clear path in a straight line. + //Now at intervals of my size, go along the trace and trace down STEPSIZE to make sure there is a solid floor. + VectorSubtract( dest, NPC->r.currentOrigin, dir ); + dist = VectorNormalize( dir ); + if ( dest[2] > NPC->r.currentOrigin[2] ) + {//going up, check for steps + drop = STEPSIZE; + } + else + {//going down or level, check for moderate drops + drop = 64; + } + for ( i = NPC->r.maxs[0]*2; i < dist; i += NPC->r.maxs[0]*2 ) + {//FIXME: does this check the last spot, too? We're assuming that should be okay since the enemy is there? + VectorMA( NPC->r.currentOrigin, i, dir, start ); + VectorCopy( start, end ); + end[2] -= drop; + trap_Trace( &trace, start, mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask );//NPC->r.mins? + if ( trace.fraction < 1.0f || trace.allsolid || trace.startsolid ) + {//good to go + continue; + } + //no floor here! (or a long drop?) + return qfalse; + } + //we made it! + return qtrue; +} + +qboolean NPC_MoveDirClear( int forwardmove, int rightmove, qboolean reset ) +{ + vec3_t forward, right, testPos, angles, mins; + trace_t trace; + float fwdDist, rtDist; + float bottom_max = -STEPSIZE*4 - 1; + + if ( !forwardmove && !rightmove ) + {//not even moving + //Com_Printf( "%d skipping walk-cliff check (not moving)\n", level.time ); + return qtrue; + } + + if ( ucmd.upmove > 0 || NPC->client->ps.fd.forceJumpCharge ) + {//Going to jump + //Com_Printf( "%d skipping walk-cliff check (going to jump)\n", level.time ); + return qtrue; + } + + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//in the air + //Com_Printf( "%d skipping walk-cliff check (in air)\n", level.time ); + return qtrue; + } + /* + if ( fabs( AngleDelta( NPC->r.currentAngles[YAW], NPCInfo->desiredYaw ) ) < 5.0 )//!ucmd.angles[YAW] ) + {//Not turning much, don't do this + //NOTE: Should this not happen only if you're not turning AT ALL? + // You could be turning slowly but moving fast, so that would + // still let you walk right off a cliff... + //NOTE: Or maybe it is a good idea to ALWAYS do this, regardless + // of whether ot not we're turning? But why would we be walking + // straight into a wall or off a cliff unless we really wanted to? + return; + } + */ + + //FIXME: to really do this right, we'd have to actually do a pmove to predict where we're + //going to be... maybe this should be a flag and pmove handles it and sets a flag so AI knows + //NEXT frame? Or just incorporate current velocity, runspeed and possibly friction? + VectorCopy( NPC->r.mins, mins ); + mins[2] += STEPSIZE; + angles[PITCH] = angles[ROLL] = 0; + angles[YAW] = NPC->client->ps.viewangles[YAW];//Add ucmd.angles[YAW]? + AngleVectors( angles, forward, right, NULL ); + fwdDist = ((float)forwardmove)/2.0f; + rtDist = ((float)rightmove)/2.0f; + VectorMA( NPC->r.currentOrigin, fwdDist, forward, testPos ); + VectorMA( testPos, rtDist, right, testPos ); + trap_Trace( &trace, NPC->r.currentOrigin, mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + if ( trace.allsolid || trace.startsolid ) + {//hmm, trace started inside this brush... how do we decide if we should continue? + //FIXME: what do we do if we start INSIDE a CONTENTS_BOTCLIP? Try the trace again without that in the clipmask? + if ( reset ) + { + trace.fraction = 1.0f; + } + VectorCopy( testPos, trace.endpos ); + //return qtrue; + } + if ( trace.fraction < 0.6 ) + {//Going to bump into something very close, don't move, just turn + if ( (NPC->enemy && trace.entityNum == NPC->enemy->s.number) || (NPCInfo->goalEntity && trace.entityNum == NPCInfo->goalEntity->s.number) ) + {//okay to bump into enemy or goal + //Com_Printf( "%d bump into enemy/goal okay\n", level.time ); + return qtrue; + } + else if ( reset ) + {//actually want to screw with the ucmd + //Com_Printf( "%d avoiding walk into wall (entnum %d)\n", level.time, trace.entityNum ); + ucmd.forwardmove = 0; + ucmd.rightmove = 0; + VectorClear( NPC->client->ps.moveDir ); + } + return qfalse; + } + + if ( NPCInfo->goalEntity ) + { + if ( NPCInfo->goalEntity->r.currentOrigin[2] < NPC->r.currentOrigin[2] ) + {//goal is below me, okay to step off at least that far plus stepheight + bottom_max += NPCInfo->goalEntity->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + } + } + VectorCopy( trace.endpos, testPos ); + testPos[2] += bottom_max; + + trap_Trace( &trace, trace.endpos, mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask ); + + //FIXME:Should we try to see if we can still get to our goal using the waypoint network from this trace.endpos? + //OR: just put NPC clip brushes on these edges (still fall through when die) + + if ( trace.allsolid || trace.startsolid ) + {//Not going off a cliff + //Com_Printf( "%d walk off cliff okay (droptrace in solid)\n", level.time ); + return qtrue; + } + + if ( trace.fraction < 1.0 ) + {//Not going off a cliff + //FIXME: what if plane.normal is sloped? We'll slide off, not land... plus this doesn't account for slide-movement... + //Com_Printf( "%d walk off cliff okay will hit entnum %d at dropdist of %4.2f\n", level.time, trace.entityNum, (trace.fraction*bottom_max) ); + return qtrue; + } + + //going to fall at least bottom_max, don't move, just turn... is this bad, though? What if we want them to drop off? + if ( reset ) + {//actually want to screw with the ucmd + //Com_Printf( "%d avoiding walk off cliff\n", level.time ); + ucmd.forwardmove *= -1.0;//= 0; + ucmd.rightmove *= -1.0;//= 0; + VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + } + return qfalse; +} +/* +------------------------- +Jedi_HoldPosition +------------------------- +*/ + +static void Jedi_HoldPosition( void ) +{ + //NPCInfo->squadState = SQUAD_STAND_AND_SHOOT; + NPCInfo->goalEntity = NULL; + + /* + if ( TIMER_Done( NPC, "stand" ) ) + { + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +/* +------------------------- +Jedi_Move +------------------------- +*/ + +static void Jedi_Move( gentity_t *goal, qboolean retreat ) +{ + qboolean moved; + navInfo_t info; + + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = goal; + + moved = NPC_MoveToGoal( qtrue ); + + //FIXME: temp retreat behavior- should really make this toward a safe spot or maybe to outflank enemy + if ( retreat ) + {//FIXME: should we trace and make sure we can go this way? Or somehow let NPC_MoveToGoal know we want to retreat and have it handle it? + ucmd.forwardmove *= -1; + ucmd.rightmove *= -1; + VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + } + + //Get the move info + NAV_GetLastMove( &info ); + + //If we hit our target, then stop and fire! + if ( ( info.flags & NIF_COLLISION ) && ( info.blocker == NPC->enemy ) ) + { + Jedi_HoldPosition(); + } + + //If our move failed, then reset + if ( moved == qfalse ) + { + Jedi_HoldPosition(); + } +} + +static qboolean Jedi_Hunt( void ) +{ + //Com_Printf( "Hunting\n" ); + //if we're at all interested in fighting, go after him + if ( NPCInfo->stats.aggression > 1 ) + {//approach enemy + NPCInfo->combatMove = qtrue; + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + else + { + if ( NPCInfo->goalEntity == NULL ) + {//hunt + NPCInfo->goalEntity = NPC->enemy; + } + //Jedi_Move( NPC->enemy, qfalse ); + if ( NPC_MoveToGoal( qfalse ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + } + return qfalse; +} + +/* +static qboolean Jedi_Track( void ) +{ + //if we're at all interested in fighting, go after him + if ( NPCInfo->stats.aggression > 1 ) + {//approach enemy + NPCInfo->combatMove = qtrue; + NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 16, qtrue ); + if ( NPC_MoveToGoal( qfalse ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + return qfalse; +} +*/ + +static void Jedi_Retreat( void ) +{ + if ( !TIMER_Done( NPC, "noRetreat" ) ) + {//don't actually move + return; + } + //FIXME: when retreating, we should probably see if we can retreat + //in the direction we want. If not...? Evade? + //Com_Printf( "Retreating\n" ); + Jedi_Move( NPC->enemy, qtrue ); +} + +static void Jedi_Advance( void ) +{ + if ( !NPC->client->ps.saberInFlight ) + { + //NPC->client->ps.SaberActivate(); + WP_ActivateSaber(NPC); + } + //Com_Printf( "Advancing\n" ); + Jedi_Move( NPC->enemy, qfalse ); + + //TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) ); + //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); + //TIMER_Set( NPC, "duck", 0 ); +} + +static void Jedi_AdjustSaberAnimLevel( gentity_t *self, int newLevel ) +{ + if ( !self || !self->client ) + { + return; + } + //FIXME: each NPC shold have a unique pattern of behavior for the order in which they + if ( self->client->NPC_class == CLASS_TAVION ) + {//special attacks + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_5; + return; + } + else if ( self->client->NPC_class == CLASS_DESANN ) + {//special attacks + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_4; + return; + } + if ( self->client->playerTeam == NPCTEAM_ENEMY ) + { + if ( self->NPC->rank == RANK_CIVILIAN || self->NPC->rank == RANK_LT_JG ) + {//grunt and fencer always uses quick attacks + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1; + return; + } + if ( self->NPC->rank == RANK_CREWMAN + || self->NPC->rank == RANK_ENSIGN ) + {//acrobat & force-users always use medium attacks + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_2; + return; + } + /* + if ( self->NPC->rank == RANK_LT ) + {//boss always uses strong attacks + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_3; + return; + } + */ + } + //use the different attacks, how often they switch and under what circumstances + if ( newLevel > self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] ) + {//cap it + self->client->ps.fd.saberAnimLevel = self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]; + } + else if ( newLevel < FORCE_LEVEL_1 ) + { + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1; + } + else + {//go ahead and set it + self->client->ps.fd.saberAnimLevel = newLevel; + } + + if ( d_JediAI.integer ) + { + switch ( self->client->ps.fd.saberAnimLevel ) + { + case FORCE_LEVEL_1: + Com_Printf( S_COLOR_GREEN"%s Saber Attack Set: fast\n", self->NPC_type ); + break; + case FORCE_LEVEL_2: + Com_Printf( S_COLOR_YELLOW"%s Saber Attack Set: medium\n", self->NPC_type ); + break; + case FORCE_LEVEL_3: + Com_Printf( S_COLOR_RED"%s Saber Attack Set: strong\n", self->NPC_type ); + break; + } + } +} + +static void Jedi_CheckDecreaseSaberAnimLevel( void ) +{ + if ( !NPC->client->ps.weaponTime && !(ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) + {//not attacking + if ( TIMER_Done( NPC, "saberLevelDebounce" ) && !Q_irand( 0, 10 ) ) + { + //Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );//drop + Jedi_AdjustSaberAnimLevel( NPC, Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 ));//random + TIMER_Set( NPC, "saberLevelDebounce", Q_irand( 3000, 10000 ) ); + } + } + else + { + TIMER_Set( NPC, "saberLevelDebounce", Q_irand( 1000, 5000 ) ); + } +} + +static void Jedi_CombatDistance( int enemy_dist ) +{//FIXME: for many of these checks, what we really want is horizontal distance to enemy + if ( NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//when gripping, don't move + return; + } + else if ( !TIMER_Done( NPC, "gripping" ) ) + {//stopped gripping, clear timers just in case + TIMER_Set( NPC, "gripping", -level.time ); + TIMER_Set( NPC, "attackDelay", Q_irand( 0, 1000 ) ); + } + + if ( NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 ) + {//when draining, don't move + return; + } + else if ( !TIMER_Done( NPC, "draining" ) ) + {//stopped draining, clear timers just in case + TIMER_Set( NPC, "draining", -level.time ); + TIMER_Set( NPC, "attackDelay", Q_irand( 0, 1000 ) ); + } + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( !TIMER_Done( NPC, "flameTime" ) ) + { + if ( enemy_dist > 50 ) + { + Jedi_Advance(); + } + else if ( enemy_dist <= 0 ) + { + Jedi_Retreat(); + } + } + else if ( enemy_dist < 200 ) + { + Jedi_Retreat(); + } + else if ( enemy_dist > 1024 ) + { + Jedi_Advance(); + } + } + else if ( NPC->client->ps.saberInFlight && + !PM_SaberInBrokenParry( NPC->client->ps.saberMove ) + && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//maintain distance + if ( enemy_dist < NPC->client->ps.saberEntityDist ) + { + Jedi_Retreat(); + } + else if ( enemy_dist > NPC->client->ps.saberEntityDist && enemy_dist > 100 ) + { + Jedi_Advance(); + } + if ( NPC->client->ps.weapon == WP_SABER //using saber + && NPC->client->ps.saberEntityState == SES_LEAVING //not returning yet + && NPC->client->ps.fd.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1 //2nd or 3rd level lightsaber + && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//hold it out there + ucmd.buttons |= BUTTON_ALT_ATTACK; + //FIXME: time limit? + } + } + else if ( !TIMER_Done( NPC, "taunting" ) ) + { + if ( enemy_dist <= 64 ) + {//he's getting too close + ucmd.buttons |= BUTTON_ATTACK; + if ( !NPC->client->ps.saberInFlight ) + { + WP_ActivateSaber(NPC); + } + TIMER_Set( NPC, "taunting", -level.time ); + } + //else if ( NPC->client->ps.torsoAnim == BOTH_GESTURE1 && NPC->client->ps.torsoTimer < 2000 ) + else if (NPC->client->ps.forceHandExtend == HANDEXTEND_JEDITAUNT && (NPC->client->ps.forceHandExtendTime - level.time) < 200) + {//we're almost done with our special taunt + //FIXME: this doesn't always work, for some reason + if ( !NPC->client->ps.saberInFlight ) + { + WP_ActivateSaber(NPC); + } + } + } + else if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON ) + {//we won a saber lock, press the advantage + if ( enemy_dist > 0 ) + {//get closer so we can hit! + Jedi_Advance(); + } + if ( enemy_dist > 128 ) + {//lost 'em + NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON; + } + if ( NPC->enemy->painDebounceTime + 2000 < level.time ) + {//the window of opportunity is gone + NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON; + } + //don't strafe? + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + } + else if ( NPC->enemy->client + && NPC->enemy->s.weapon == WP_SABER + && NPC->enemy->client->ps.saberLockTime > level.time + && NPC->client->ps.saberLockTime < level.time ) + {//enemy is in a saberLock and we are not + if ( enemy_dist < 64 ) + {//FIXME: maybe just pick another enemy? + Jedi_Retreat(); + } + } + /* + else if ( NPC->enemy->s.weapon == WP_TURRET + && !Q_stricmp( "PAS", NPC->enemy->classname ) + && NPC->enemy->s.apos.trType == TR_STATIONARY ) + { + int testlevel; + if ( enemy_dist > forcePushPullRadius[FORCE_LEVEL_1] - 16 ) + { + Jedi_Advance(); + } + if ( NPC->client->ps.fd.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1 ) + {// + testlevel = FORCE_LEVEL_1; + } + else + { + testlevel = NPC->client->ps.fd.forcePowerLevel[FP_PUSH]; + } + if ( enemy_dist < forcePushPullRadius[testlevel] - 16 ) + {//close enough to push + if ( InFront( NPC->enemy->r.currentOrigin, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, 0.6f ) ) + {//knock it down + WP_KnockdownTurret( NPC, NPC->enemy ); + //do the forcethrow call just for effect + ForceThrow( NPC, qfalse ); + } + } + } + */ + //rwwFIXMEFIXME: Give them the ability to do this again (turret needs to be fixed up to allow it) + else if ( enemy_dist <= 64 + && ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||(!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,10))) ) + {//can't use saber and they're in striking range + if ( !Q_irand( 0, 5 ) && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.2f ) ) + { + if ( ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||NPC->client->pers.maxHealth - NPC->health > NPC->client->pers.maxHealth*0.25f)//lost over 1/4 of our health or not firing + && NPC->client->ps.fd.forcePowersKnown&(1<client->pers.maxHealth - NPC->health > NPC->client->pers.maxHealth*0.25f//lost over 1/4 of our health + && NPC->client->ps.fd.forcePowersKnown&(1<enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.2f ) ) + { + TIMER_Set( NPC, "draining", 3000 ); + TIMER_Set( NPC, "attackDelay", 3000 ); + Jedi_Advance(); + return; + } + else if ( enemy_dist <= -16 ) + {//we're too damn close! + Jedi_Retreat(); + } + else if ( enemy_dist <= 0 ) + {//we're within striking range + //if we are attacking, see if we should stop + if ( NPCInfo->stats.aggression < 4 ) + {//back off and defend + Jedi_Retreat(); + } + } + else if ( enemy_dist > 256 ) + {//we're way out of range + qboolean usedForce = qfalse; + if ( NPCInfo->stats.aggression < Q_irand( 0, 20 ) + && NPC->health < NPC->client->pers.maxHealth*0.75f + && !Q_irand( 0, 2 ) ) + { + if ( (NPC->client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1< 384 ) + {//FIXME: check for enemy facing away and/or moving away + if ( !Q_irand( 0, 10 ) && NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time ) + { + if ( NPC_ClearLOS4( NPC->enemy ) ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_JCHASE1, EV_JCHASE3 ), 3000 ); + } + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + } + //Unless we're totally hiding, go after him + if ( NPCInfo->stats.aggression > 0 ) + {//approach enemy + if ( !usedForce ) + { + Jedi_Advance(); + } + } + } + /* + else if ( enemy_dist < 96 && NPC->enemy && NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//too close and in air, so retreat + Jedi_Retreat(); + } + */ + //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade.... + else if ( enemy_dist > 50 )//FIXME: not hardcoded- base on our reach (modelScale?) and saberLengthMax + {//we're out of striking range and we are allowed to attack + //first, check some tactical force power decisions + if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.fd.forceGripBeingGripped > level.time) ) + {//They're being gripped, rush them! + if ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//far away or allowed to use saber + Jedi_Advance(); + } + } + } + if ( NPCInfo->rank >= RANK_LT_JG + && !Q_irand( 0, 5 ) + && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//throw saber + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + } + else if ( NPC->enemy && NPC->enemy->client && //valid enemy + NPC->enemy->client->ps.saberInFlight && /*NPC->enemy->client->ps.saber[0].Active()*/ NPC->enemy->client->ps.saberEntityNum && //enemy throwing saber + NPC->client->ps.weaponTime <= 0 && //I'm not busy + WP_ForcePowerAvailable( NPC, FP_GRIP, 0 ) && //I can use the power + !Q_irand( 0, 10 ) && //don't do it all the time, averages to 1 check a second + Q_irand( 0, 6 ) < g_spskill.integer && //more likely on harder diff + Q_irand( RANK_CIVILIAN, RANK_CAPTAIN ) < NPCInfo->rank )//more likely against harder enemies + {//They're throwing their saber, grip them! + //taunt + if ( TIMER_Done( NPC, "chatter" ) && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && NPCInfo->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + TIMER_Set( NPC, "chatter", 3000 ); + } + + //grip + TIMER_Set( NPC, "gripping", 3000 ); + TIMER_Set( NPC, "attackDelay", 3000 ); + } + else + { + int chanceScale; + + if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.fd.forcePowersActive&(1<enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//far away or allowed to use saber + Jedi_Advance(); + } + } + } + } + chanceScale = 0; + if ( NPC->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",NPC->NPC_type) ) + { + chanceScale = 1; + } + else if ( NPCInfo->rank == RANK_ENSIGN ) + { + chanceScale = 2; + } + else if ( NPCInfo->rank >= RANK_LT_JG ) + { + chanceScale = 5; + } + if ( chanceScale + && (enemy_dist > Q_irand( 100, 200 ) || (NPCInfo->scriptFlags&SCF_DONT_FIRE) || (!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,3)) ) + && enemy_dist < 500 + && (Q_irand( 0, chanceScale*10 )<5 || (NPC->enemy->client && NPC->enemy->client->ps.weapon != WP_SABER && !Q_irand( 0, chanceScale ) ) ) ) + {//else, randomly try some kind of attack every now and then + if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && !Q_irand( 0, 1 ) ) + { + if ( WP_ForcePowerAvailable( NPC, FP_PULL, 0 ) && !Q_irand( 0, 2 ) ) + { + //force pull the guy to me! + //FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]] + ForceThrow( NPC, qtrue ); + //maybe strafe too? + TIMER_Set( NPC, "duck", enemy_dist*3 ); + if ( Q_irand( 0, 1 ) ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + } + else if ( WP_ForcePowerAvailable( NPC, FP_LIGHTNING, 0 ) && Q_irand( 0, 1 ) ) + { + ForceLightning( NPC ); + if ( NPC->client->ps.fd.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 ) + { + NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_spskill.integer*500) ); + TIMER_Set( NPC, "holdLightning", NPC->client->ps.weaponTime ); + } + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + } + /*else if ( NPC->health < NPC->client->pers.maxHealth * 0.75f + && Q_irand( FORCE_LEVEL_0, NPC->client->ps.fd.forcePowerLevel[FP_DRAIN] ) > FORCE_LEVEL_1 + && WP_ForcePowerAvailable( NPC, FP_DRAIN, 0 ) + && Q_irand( 0, 1 ) ) + { + ForceDrain2( NPC ); + NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_spskill.integer*500) ); + TIMER_Set( NPC, "draining", NPC->client->ps.weaponTime ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + }*/ + //rwwFIXMEFIXME: After new drain stuff from SP is in re-enable this. + else if ( WP_ForcePowerAvailable( NPC, FP_GRIP, 0 ) ) + { + //taunt + if ( TIMER_Done( NPC, "chatter" ) && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && NPCInfo->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + TIMER_Set( NPC, "chatter", 3000 ); + } + + //grip + TIMER_Set( NPC, "gripping", 3000 ); + TIMER_Set( NPC, "attackDelay", 3000 ); + } + else + { + if ( WP_ForcePowerAvailable( NPC, FP_SABERTHROW, 0 ) + && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//throw saber + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + } + } + else + { + if ( NPCInfo->rank >= RANK_LT_JG + && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//throw saber + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + } + } + //see if we should advance now + else if ( NPCInfo->stats.aggression > 5 ) + {//approach enemy + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//far away or allowed to use saber + Jedi_Advance(); + } + } + } + } + else + {//maintain this distance? + //walk? + } + } + } + else + {//we're not close enough to attack, but not far enough away to be safe + if ( NPCInfo->stats.aggression < 4 ) + {//back off and defend + Jedi_Retreat(); + } + else if ( NPCInfo->stats.aggression > 5 ) + {//try to get closer + if ( enemy_dist > 0 && !(NPCInfo->scriptFlags&SCF_DONT_FIRE)) + {//we're allowed to use our lightsaber, get closer + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + Jedi_Advance(); + } + } + } + } + else + {//agression is 4 or 5... somewhere in the middle + //what do we do here? Nothing? + //Move forward and back? + } + } + //if really really mad, rage! + if ( NPCInfo->stats.aggression > Q_irand( 5, 15 ) + && NPC->health < NPC->client->pers.maxHealth*0.75f + && !Q_irand( 0, 2 ) ) + { + if ( (NPC->client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1<client->ps.saberEventFlags&SEF_LOCK_WON && NPC->enemy && NPC->enemy->painDebounceTime > level.time ) + {//don't strafe if pressing the advantage of winning a saberLock + return qfalse; + } + if ( TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) ) + { + qboolean strafed = qfalse; + //TODO: make left/right choice a tactical decision rather than random: + // try to keep own back away from walls and ledges, + // try to keep enemy's back to a ledge or wall + // Maybe try to strafe toward designer-placed "safe spots" or "goals"? + int strafeTime = Q_irand( strafeTimeMin, strafeTimeMax ); + + if ( Q_irand( 0, 1 ) ) + { + if ( NPC_MoveDirClear( ucmd.forwardmove, -127, qfalse ) ) + { + TIMER_Set( NPC, "strafeLeft", strafeTime ); + strafed = qtrue; + } + else if ( NPC_MoveDirClear( ucmd.forwardmove, 127, qfalse ) ) + { + TIMER_Set( NPC, "strafeRight", strafeTime ); + strafed = qtrue; + } + } + else + { + if ( NPC_MoveDirClear( ucmd.forwardmove, 127, qfalse ) ) + { + TIMER_Set( NPC, "strafeRight", strafeTime ); + strafed = qtrue; + } + else if ( NPC_MoveDirClear( ucmd.forwardmove, -127, qfalse ) ) + { + TIMER_Set( NPC, "strafeLeft", strafeTime ); + strafed = qtrue; + } + } + + if ( strafed ) + { + TIMER_Set( NPC, "noStrafe", strafeTime + Q_irand( nextStrafeTimeMin, nextStrafeTimeMax ) ); + if ( walking ) + {//should be a slow strafe + TIMER_Set( NPC, "walking", strafeTime ); + } + return qtrue; + } + } + return qfalse; +} + +/* +static void Jedi_FaceEntity( gentity_t *self, gentity_t *other, qboolean doPitch ) +{ + vec3_t entPos; + vec3_t muzzle; + + //Get the positions + CalcEntitySpot( other, SPOT_ORIGIN, entPos ); + + //Get the positions + CalcEntitySpot( self, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD + + //Find the desired angles + vec3_t angles; + + GetAnglesForDirection( muzzle, entPos, angles ); + + self->NPC->desiredYaw = AngleNormalize360( angles[YAW] ); + if ( doPitch ) + { + self->NPC->desiredPitch = AngleNormalize360( angles[PITCH] ); + } +} +*/ + +/* +qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc ) + +Jedi will play a dodge anim, blur, and make the force speed noise. + +Right now used to dodge instant-hit weapons. + +FIXME: possibly call this for saber melee evasion and/or missile evasion? +FIXME: possibly let player do this too? +*/ +//rwwFIXMEFIXME: Going to use qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc ) from +//w_saber.c.. maybe use seperate one for NPCs or add cases to that one? + +evasionType_t Jedi_CheckFlipEvasions( gentity_t *self, float rightdot, float zdiff ) +{ + if ( self->NPC && (self->NPC->scriptFlags&SCF_NO_ACROBATICS) ) + { + return EVASION_NONE; + } + if ( self->client + && (self->client->ps.fd.forceRageRecoveryTime > level.time || (self->client->ps.fd.forcePowersActive&(1<client->ps.legsAnim == BOTH_WALL_RUN_LEFT || self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT ) + {//already running on a wall + vec3_t right, fwdAngles; + int anim = -1; + float animLength; + + VectorSet(fwdAngles, 0, self->client->ps.viewangles[YAW], 0); + + AngleVectors( fwdAngles, NULL, right, NULL ); + + animLength = BG_AnimLength( self->localAnimIndex, (animNumber_t)self->client->ps.legsAnim ); + if ( self->client->ps.legsAnim == BOTH_WALL_RUN_LEFT && rightdot < 0 ) + {//I'm running on a wall to my left and the attack is on the left + if ( animLength - self->client->ps.legsTimer > 400 + && self->client->ps.legsTimer > 400 ) + {//not at the beginning or end of the anim + anim = BOTH_WALL_RUN_LEFT_FLIP; + } + } + else if ( self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT && rightdot > 0 ) + {//I'm running on a wall to my right and the attack is on the right + if ( animLength - self->client->ps.legsTimer > 400 + && self->client->ps.legsTimer > 400 ) + {//not at the beginning or end of the anim + anim = BOTH_WALL_RUN_RIGHT_FLIP; + } + } + if ( anim != -1 ) + {//flip off the wall! + int parts; + //FIXME: check the direction we will flip towards for do-not-enter/walls/drops? + //NOTE: we presume there is still a wall there! + if ( anim == BOTH_WALL_RUN_LEFT_FLIP ) + { + self->client->ps.velocity[0] *= 0.5f; + self->client->ps.velocity[1] *= 0.5f; + VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity ); + } + else if ( anim == BOTH_WALL_RUN_RIGHT_FLIP ) + { + self->client->ps.velocity[0] *= 0.5f; + self->client->ps.velocity[1] *= 0.5f; + VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity ); + } + parts = SETANIM_LEGS; + if ( !self->client->ps.weaponTime ) + { + parts = SETANIM_BOTH; + } + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL); + //rwwFIXMEFIXME: Add these pm flags? + G_AddEvent( self, EV_JUMP, 0 ); + return EVASION_OTHER; + } + } + else if ( self->client->NPC_class != CLASS_DESANN //desann doesn't do these kind of frilly acrobatics + && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT) + && Q_irand( 0, 1 ) + && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim ) + && !PM_InKnockDown( &self->client->ps ) + && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) ) + { + vec3_t fwd, right, traceto, mins, maxs, fwdAngles; + trace_t trace; + int parts, anim; + float speed, checkDist; + + VectorSet(mins, self->r.mins[0],self->r.mins[1],0); + VectorSet(maxs, self->r.maxs[0],self->r.maxs[1],24); + VectorSet(fwdAngles, 0, self->client->ps.viewangles[YAW], 0); + + AngleVectors( fwdAngles, fwd, right, NULL ); + + parts = SETANIM_BOTH; + + if ( BG_SaberInAttack( self->client->ps.saberMove ) + || PM_SaberInStart( self->client->ps.saberMove ) ) + { + parts = SETANIM_LEGS; + } + if ( rightdot >= 0 ) + { + if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_ARIAL_LEFT; + } + else + { + anim = BOTH_CARTWHEEL_LEFT; + } + checkDist = -128; + speed = -200; + } + else + { + if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_ARIAL_RIGHT; + } + else + { + anim = BOTH_CARTWHEEL_RIGHT; + } + checkDist = 128; + speed = 200; + } + //trace in the dir that we want to go + VectorMA( self->r.currentOrigin, checkDist, right, traceto ); + trap_Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP ); + if ( trace.fraction >= 1.0f ) + {//it's clear, let's do it + //FIXME: check for drops? + vec3_t fwdAngles, jumpRt; + + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.weaponTime = self->client->ps.legsTimer;//don't attack again until this anim is done + VectorCopy( self->client->ps.viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + //do the flip + AngleVectors( fwdAngles, NULL, jumpRt, NULL ); + VectorScale( jumpRt, speed, self->client->ps.velocity ); + self->client->ps.fd.forceJumpCharge = 0;//so we don't play the force flip anim + self->client->ps.velocity[2] = 200; + self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height + //self->client->ps.pm_flags |= PMF_JUMPING; + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + //ucmd.upmove = 0; + return EVASION_CARTWHEEL; + } + else if ( !(trace.contents&CONTENTS_BOTCLIP) ) + {//hit a wall, not a do-not-enter brush + //FIXME: before we check any of these jump-type evasions, we should check for headroom, right? + //Okay, see if we can flip *off* the wall and go the other way + vec3_t idealNormal; + gentity_t *traceEnt; + + VectorSubtract( self->r.currentOrigin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + traceEnt = &g_entities[trace.entityNum]; + if ( (trace.entityNums.solid!=SOLID_BMODEL) || DotProduct( trace.plane.normal, idealNormal ) > 0.7f ) + {//it's a ent of some sort or it's a wall roughly facing us + float bestCheckDist = 0; + //hmm, see if we're moving forward + if ( DotProduct( self->client->ps.velocity, fwd ) < 200 ) + {//not running forward very fast + //check to see if it's okay to move the other way + if ( (trace.fraction*checkDist) <= 32 ) + {//wall on that side is close enough to wall-flip off of or wall-run on + bestCheckDist = checkDist; + checkDist *= -1.0f; + VectorMA( self->r.currentOrigin, checkDist, right, traceto ); + //trace in the dir that we want to go + trap_Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP ); + if ( trace.fraction >= 1.0f ) + {//it's clear, let's do it + int parts; + + //FIXME: check for drops? + //turn the cartwheel into a wallflip in the other dir + if ( rightdot > 0 ) + { + anim = BOTH_WALL_FLIP_LEFT; + self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0; + VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity ); + } + else + { + anim = BOTH_WALL_FLIP_RIGHT; + self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0; + VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity ); + } + self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + //animate me + parts = SETANIM_LEGS; + if ( !self->client->ps.weaponTime ) + { + parts = SETANIM_BOTH; + } + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height + //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL); + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + return EVASION_OTHER; + } + else + {//boxed in on both sides + if ( DotProduct( self->client->ps.velocity, fwd ) < 0 ) + {//moving backwards + return EVASION_NONE; + } + if ( (trace.fraction*checkDist) <= 32 && (trace.fraction*checkDist) < bestCheckDist ) + { + bestCheckDist = checkDist; + } + } + } + else + {//too far from that wall to flip or run off it, check other side + checkDist *= -1.0f; + VectorMA( self->r.currentOrigin, checkDist, right, traceto ); + //trace in the dir that we want to go + trap_Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP ); + if ( (trace.fraction*checkDist) <= 32 ) + {//wall on this side is close enough + bestCheckDist = checkDist; + } + else + {//neither side has a wall within 32 + return EVASION_NONE; + } + } + } + //Try wall run? + if ( bestCheckDist ) + {//one of the walls was close enough to wall-run on + int parts; + + //FIXME: check for long enough wall and a drop at the end? + if ( bestCheckDist > 0 ) + {//it was to the right + anim = BOTH_WALL_RUN_RIGHT; + } + else + {//it was to the left + anim = BOTH_WALL_RUN_LEFT; + } + self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + //animate me + parts = SETANIM_LEGS; + if ( !self->client->ps.weaponTime ) + { + parts = SETANIM_BOTH; + } + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height + //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL); + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + return EVASION_OTHER; + } + //else check for wall in front, do backflip off wall + } + } + } + return EVASION_NONE; +} + +int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType ) +{ + if ( !self->client ) + { + return 0; + } + if ( !self->s.number ) + {//player + return bg_parryDebounce[self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]]; + } + else if ( self->NPC ) + { + if ( !g_saberRealisticCombat.integer + && ( g_spskill.integer == 2 || (g_spskill.integer == 1 && self->client->NPC_class == CLASS_TAVION) ) ) + { + if ( self->client->NPC_class == CLASS_TAVION ) + { + return 0; + } + else + { + return Q_irand( 0, 150 ); + } + } + else + { + int baseTime; + if ( evasionType == EVASION_DODGE ) + { + baseTime = self->client->ps.torsoTimer; + } + else if ( evasionType == EVASION_CARTWHEEL ) + { + baseTime = self->client->ps.torsoTimer; + } + else if ( self->client->ps.saberInFlight ) + { + baseTime = Q_irand( 1, 3 ) * 50; + } + else + { + if ( g_saberRealisticCombat.integer ) + { + baseTime = 500; + + switch ( g_spskill.integer ) + { + case 0: + baseTime = 500; + break; + case 1: + baseTime = 300; + break; + case 2: + default: + baseTime = 100; + break; + } + } + else + { + baseTime = 150;//500; + + switch ( g_spskill.integer ) + { + case 0: + baseTime = 200;//500; + break; + case 1: + baseTime = 100;//300; + break; + case 2: + default: + baseTime = 50;//100; + break; + } + } + + if ( self->client->NPC_class == CLASS_TAVION ) + {//Tavion is faster + baseTime = ceil(baseTime/2.0f); + } + else if ( self->NPC->rank >= RANK_LT_JG ) + {//fencers, bosses, shadowtroopers, luke, desann, et al use the norm + if ( Q_irand( 0, 2 ) ) + {//medium speed parry + baseTime = baseTime; + } + else + {//with the occasional fast parry + baseTime = ceil(baseTime/2.0f); + } + } + else if ( self->NPC->rank == RANK_CIVILIAN ) + {//grunts are slowest + baseTime = baseTime*Q_irand(1,3); + } + else if ( self->NPC->rank == RANK_CREWMAN ) + {//acrobats aren't so bad + if ( evasionType == EVASION_PARRY + || evasionType == EVASION_DUCK_PARRY + || evasionType == EVASION_JUMP_PARRY ) + {//slower with parries + baseTime = baseTime*Q_irand(1,2); + } + else + {//faster with acrobatics + //baseTime = baseTime; + } + } + else + {//force users are kinda slow + baseTime = baseTime*Q_irand(1,2); + } + if ( evasionType == EVASION_DUCK || evasionType == EVASION_DUCK_PARRY ) + { + baseTime += 100; + } + else if ( evasionType == EVASION_JUMP || evasionType == EVASION_JUMP_PARRY ) + { + baseTime += 50; + } + else if ( evasionType == EVASION_OTHER ) + { + baseTime += 100; + } + else if ( evasionType == EVASION_FJUMP ) + { + baseTime += 100; + } + } + + return baseTime; + } + } + return 0; +} + +qboolean Jedi_QuickReactions( gentity_t *self ) +{ + if ( ( self->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) || + self->client->NPC_class == CLASS_TAVION || + (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_1&&g_spskill.integer>1) || + (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&g_spskill.integer>0) ) + { + return qtrue; + } + return qfalse; +} + +qboolean Jedi_SaberBusy( gentity_t *self ) +{ + if ( self->client->ps.torsoTimer > 300 + && ( (BG_SaberInAttack( self->client->ps.saberMove )&&self->client->ps.fd.saberAnimLevel==FORCE_LEVEL_3) + || BG_SpinningSaberAnim( self->client->ps.torsoAnim ) + || BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) + //|| PM_SaberInBounce( self->client->ps.saberMove ) + || PM_SaberInBrokenParry( self->client->ps.saberMove ) + //|| PM_SaberInDeflect( self->client->ps.saberMove ) + || BG_FlippingAnim( self->client->ps.torsoAnim ) + || PM_RollingAnim( self->client->ps.torsoAnim ) ) ) + {//my saber is not in a parrying position + return qtrue; + } + return qfalse; +} + +/* +------------------------- +Jedi_SaberBlock + +Pick proper block anim + +FIXME: Based on difficulty level/enemy saber combat skill, make this decision-making more/less effective + +NOTE: always blocking projectiles in this func! + +------------------------- +*/ +extern qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result ); +evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist ) //dist = 0.0f +{ + vec3_t hitloc, hitdir, diff, fwdangles={0,0,0}, right; + float rightdot; + float zdiff; + int duckChance = 0; + int dodgeAnim = -1; + qboolean saberBusy = qfalse, evaded = qfalse, doDodge = qfalse; + evasionType_t evasionType = EVASION_NONE; + + //FIXME: if we don't have our saber in hand, pick the force throw option or a jump or strafe! + //FIXME: reborn don't block enough anymore + if ( !incoming ) + { + VectorCopy( pHitloc, hitloc ); + VectorCopy( phitDir, hitdir ); + //FIXME: maybe base this on rank some? And/or g_spskill? + if ( self->client->ps.saberInFlight ) + {//DOH! do non-saber evasion! + saberBusy = qtrue; + } + else if ( Jedi_QuickReactions( self ) ) + {//jedi trainer and tavion are must faster at parrying and can do it whenever they like + //Also, on medium, all level 3 people can parry any time and on hard, all level 2 or 3 people can parry any time + } + else + { + saberBusy = Jedi_SaberBusy( self ); + } + } + else + { + if ( incoming->s.weapon == WP_SABER ) + {//flying lightsaber, face it! + //FIXME: for this to actually work, we'd need to call update angles too? + //Jedi_FaceEntity( self, incoming, qtrue ); + } + VectorCopy( incoming->r.currentOrigin, hitloc ); + VectorNormalize2( incoming->s.pos.trDelta, hitdir ); + } + if ( self->client && self->client->NPC_class == CLASS_BOBAFETT ) + { + saberBusy = qtrue; + } + + VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff ); + diff[2] = 0; + //VectorNormalize( diff ); + fwdangles[1] = self->client->ps.viewangles[1]; + // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine. + AngleVectors( fwdangles, NULL, right, NULL ); + + rightdot = DotProduct(right, diff);// + flrand(-0.10f,0.10f); + //totalHeight = self->client->renderInfo.eyePoint[2] - self->r.absmin[2]; + zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];// + Q_irand(-6,6); + + //see if we can dodge if need-be + if ( (dist>16&&(Q_irand( 0, 2 )||saberBusy)) + || self->client->ps.saberInFlight + || BG_SabersOff( &self->client->ps ) + || self->client->NPC_class == CLASS_BOBAFETT ) + {//either it will miss by a bit (and 25% chance) OR our saber is not in-hand OR saber is off + if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT_JG) ) + {//acrobat or fencer or above + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE &&//on the ground + !(self->client->ps.pm_flags&PMF_DUCKED)&&cmd->upmove>=0&&TIMER_Done( self, "duck" )//not ducking + && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim )//not rolling + && !PM_InKnockDown( &self->client->ps )//not knocked down + && ( self->client->ps.saberInFlight || + self->client->NPC_class == CLASS_BOBAFETT || + (!BG_SaberInAttack( self->client->ps.saberMove )//not attacking + && !PM_SaberInStart( self->client->ps.saberMove )//not starting an attack + && !BG_SpinningSaberAnim( self->client->ps.torsoAnim )//not in a saber spin + && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ))//not in a special attack + ) + ) + {//need to check all these because it overrides both torso and legs with the dodge + doDodge = qtrue; + } + } + } + // Figure out what quadrant the block was in. + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) evading attack from height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, hitloc[2]-self->r.absmin[2],zdiff,rightdot); + } + + //UL = > -1//-6 + //UR = > -6//-9 + //TOP = > +6//+4 + //FIXME: take FP_SABER_DEFENSE into account here somehow? + if ( zdiff >= -5 )//was 0 + { + if ( incoming || !saberBusy ) + { + if ( rightdot > 12 + || (rightdot > 3 && zdiff < 5) + || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, 0.3 + {//coming from right + if ( doDodge ) + { + if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) ) + {//roll! + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + evasionType = EVASION_DUCK; + evaded = qtrue; + } + else if ( Q_irand( 0, 1 ) ) + { + dodgeAnim = BOTH_DODGE_FL; + } + else + { + dodgeAnim = BOTH_DODGE_BL; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT; + evasionType = EVASION_PARRY; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + if ( zdiff > 5 ) + { + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK_PARRY; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "duck " ); + } + } + else + { + duckChance = 6; + } + } + } + if ( d_JediAI.integer ) + { + Com_Printf( "UR block\n" ); + } + } + else if ( rightdot < -12 + || (rightdot < -3 && zdiff < 5) + || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, -0.3 + {//coming from left + if ( doDodge ) + { + if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) ) + {//roll! + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeLeft", 0 ); + evasionType = EVASION_DUCK; + evaded = qtrue; + } + else if ( Q_irand( 0, 1 ) ) + { + dodgeAnim = BOTH_DODGE_FR; + } + else + { + dodgeAnim = BOTH_DODGE_BR; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + evasionType = EVASION_PARRY; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + if ( zdiff > 5 ) + { + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK_PARRY; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "duck " ); + } + } + else + { + duckChance = 6; + } + } + } + if ( d_JediAI.integer ) + { + Com_Printf( "UL block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_TOP; + evasionType = EVASION_PARRY; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + duckChance = 4; + } + if ( d_JediAI.integer ) + { + Com_Printf( "TOP block\n" ); + } + } + evaded = qtrue; + } + else + { + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + //duckChance = 2; + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "duck " ); + } + } + } + } + //LL = -22//= -18 to -39 + //LR = -23//= -20 to -41 + else if ( zdiff > -22 )//was-15 ) + { + if ( 1 )//zdiff < -10 ) + {//hmm, pretty low, but not low enough to use the low block, so we need to duck + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + //duckChance = 2; + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "duck " ); + } + } + else + {//in air! Ducking does no good + } + } + if ( incoming || !saberBusy ) + { + if ( rightdot > 8 || (rightdot > 3 && zdiff < -11) )//was normalized, 0.2 + { + if ( doDodge ) + { + if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) ) + {//roll! + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + } + else + { + dodgeAnim = BOTH_DODGE_L; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT; + if ( evasionType == EVASION_DUCK ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_PARRY; + } + } + if ( d_JediAI.integer ) + { + Com_Printf( "mid-UR block\n" ); + } + } + else if ( rightdot < -8 || (rightdot < -3 && zdiff < -11) )//was normalized, -0.2 + { + if ( doDodge ) + { + if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) ) + {//roll! + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + } + else + { + dodgeAnim = BOTH_DODGE_R; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + if ( evasionType == EVASION_DUCK ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_PARRY; + } + } + if ( d_JediAI.integer ) + { + Com_Printf( "mid-UL block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_TOP; + if ( evasionType == EVASION_DUCK ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_PARRY; + } + if ( d_JediAI.integer ) + { + Com_Printf( "mid-TOP block\n" ); + } + } + evaded = qtrue; + } + } + else if ( saberBusy || (zdiff < -36 && ( zdiff < -44 || !Q_irand( 0, 2 ) ) ) )//was -30 and -40//2nd one was -46 + {//jump! + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//already in air, duck to pull up legs + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "legs up\n" ); + } + if ( incoming || !saberBusy ) + { + //since the jump may be cleared if not safe, set a lower block too + if ( rightdot >= 0 ) + { + self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + evasionType = EVASION_DUCK_PARRY; + if ( d_JediAI.integer ) + { + Com_Printf( "LR block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + evasionType = EVASION_DUCK_PARRY; + if ( d_JediAI.integer ) + { + Com_Printf( "LL block\n" ); + } + } + evaded = qtrue; + } + } + else + {//gotta jump! + if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) && + (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) ) + {//superjump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.fd.forceRageRecoveryTime < level.time + && !(self->client->ps.fd.forcePowersActive&(1<client->ps ) ) + { + self->client->ps.fd.forceJumpCharge = 320;//FIXME: calc this intelligently + evasionType = EVASION_FJUMP; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "force jump + " ); + } + } + } + else + {//normal jump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.fd.forceRageRecoveryTime < level.time + && !(self->client->ps.fd.forcePowersActive&(1<client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 1 ) ) + {//roll! + if ( rightdot > 0 ) + { + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + TIMER_Set( self, "walking", 0 ); + } + else + { + TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeLeft", 0 ); + TIMER_Set( self, "walking", 0 ); + } + } + else + { + if ( self == NPC ) + { + cmd->upmove = 127; + } + else + { + self->client->ps.velocity[2] = JUMP_VELOCITY; + } + } + evasionType = EVASION_JUMP; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "jump + " ); + } + } + if ( self->client->NPC_class == CLASS_TAVION ) + { + if ( !incoming + && self->client->ps.groundEntityNum < ENTITYNUM_NONE + && !Q_irand( 0, 2 ) ) + { + if ( !BG_SaberInAttack( self->client->ps.saberMove ) + && !PM_SaberInStart( self->client->ps.saberMove ) + && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim ) + && !PM_InKnockDown( &self->client->ps ) + && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) ) + {//do the butterfly! + int butterflyAnim; + if ( Q_irand( 0, 1 ) ) + { + butterflyAnim = BOTH_BUTTERFLY_LEFT; + } + else + { + butterflyAnim = BOTH_BUTTERFLY_RIGHT; + } + evasionType = EVASION_CARTWHEEL; + NPC_SetAnim( self, SETANIM_BOTH, butterflyAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.velocity[2] = 225; + self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height + // self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + // self->client->ps.SaberActivateTrail( 300 );//FIXME: reset this when done! + //Ah well. No hacking from the server for now. + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/jump.wav") ); + } + cmd->upmove = 0; + saberBusy = qtrue; + evaded = qtrue; + } + } + } + } + if ( ((evasionType = Jedi_CheckFlipEvasions( self, rightdot, zdiff ))!=EVASION_NONE) ) + { + if ( d_slowmodeath.integer > 5 && self->enemy && !self->enemy->s.number ) + { + G_StartMatrixEffect( self ); + } + saberBusy = qtrue; + evaded = qtrue; + } + else if ( incoming || !saberBusy ) + { + //since the jump may be cleared if not safe, set a lower block too + if ( rightdot >= 0 ) + { + self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + if ( evasionType == EVASION_JUMP ) + { + evasionType = EVASION_JUMP_PARRY; + } + else if ( evasionType == EVASION_NONE ) + { + evasionType = EVASION_PARRY; + } + if ( d_JediAI.integer ) + { + Com_Printf( "LR block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + if ( evasionType == EVASION_JUMP ) + { + evasionType = EVASION_JUMP_PARRY; + } + else if ( evasionType == EVASION_NONE ) + { + evasionType = EVASION_PARRY; + } + if ( d_JediAI.integer ) + { + Com_Printf( "LL block\n" ); + } + } + evaded = qtrue; + } + } + } + else + { + if ( incoming || !saberBusy ) + { + if ( rightdot >= 0 ) + { + self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + evasionType = EVASION_PARRY; + if ( d_JediAI.integer ) + { + Com_Printf( "LR block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + evasionType = EVASION_PARRY; + if ( d_JediAI.integer ) + { + Com_Printf( "LL block\n" ); + } + } + if ( incoming && incoming->s.weapon == WP_SABER ) + {//thrown saber! + if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) && + (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) ) + {//superjump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.fd.forceRageRecoveryTime < level.time + && !(self->client->ps.fd.forcePowersActive&(1<client->ps ) ) + { + self->client->ps.fd.forceJumpCharge = 320;//FIXME: calc this intelligently + evasionType = EVASION_FJUMP; + if ( d_JediAI.integer ) + { + Com_Printf( "force jump + " ); + } + } + } + else + {//normal jump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.fd.forceRageRecoveryTime < level.time + && !(self->client->ps.fd.forcePowersActive&(1<upmove = 127; + } + else + { + self->client->ps.velocity[2] = JUMP_VELOCITY; + } + evasionType = EVASION_JUMP_PARRY; + if ( d_JediAI.integer ) + { + Com_Printf( "jump + " ); + } + } + } + } + evaded = qtrue; + } + } + + if ( evasionType == EVASION_NONE ) + { + return EVASION_NONE; + } + //stop taunting + TIMER_Set( self, "taunting", 0 ); + //stop gripping + TIMER_Set( self, "gripping", -level.time ); + WP_ForcePowerStop( self, FP_GRIP ); + //stop draining + TIMER_Set( self, "draining", -level.time ); + WP_ForcePowerStop( self, FP_DRAIN ); + + if ( dodgeAnim != -1 ) + {//dodged + evasionType = EVASION_DODGE; + NPC_SetAnim( self, SETANIM_BOTH, dodgeAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.weaponTime = self->client->ps.torsoTimer; + //force them to stop moving in this case + self->client->ps.pm_time = self->client->ps.torsoTimer; + //FIXME: maybe make a sound? Like a grunt? EV_JUMP? + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + //dodged, not block + if ( d_slowmodeath.integer > 5 && self->enemy && !self->enemy->s.number ) + { + G_StartMatrixEffect( self ); + } + } + else + { + if ( duckChance ) + { + if ( !Q_irand( 0, duckChance ) ) + { + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + if ( evasionType == EVASION_PARRY ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_DUCK; + } + /* + if ( d_JediAI.integer ) + { + Com_Printf( "duck " ); + } + */ + } + } + + if ( incoming ) + { + self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked ); + } + + } + //if ( self->client->ps.saberBlocked != BLOCKED_NONE ) + { + int parryReCalcTime = Jedi_ReCalcParryTime( self, evasionType ); + if ( self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime ) + { + self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime; + } + } + return evasionType; +} + +extern float ShortestLineSegBewteen2LineSegs( vec3_t start1, vec3_t end1, vec3_t start2, vec3_t end2, vec3_t close_pnt1, vec3_t close_pnt2 ); +extern int WPDEBUG_SaberColor( saber_colors_t saberColor ); +static qboolean Jedi_SaberBlock( int saberNum, int bladeNum ) //saberNum = 0, bladeNum = 0 +{ + vec3_t hitloc, saberTipOld, saberTip, top, bottom, axisPoint, saberPoint, dir;//saberBase, + vec3_t pointDir, baseDir, tipDir, saberHitPoint, saberMins, saberMaxs; + float pointDist, baseDirPerc, dist; + float bladeLen = 0; + trace_t tr; + evasionType_t evasionType; + + //FIXME: reborn don't block enough anymore + /* + //maybe do this on easy only... or only on grunt-level reborn + if ( NPC->client->ps.weaponTime ) + {//i'm attacking right now + return qfalse; + } + */ + + if ( !TIMER_Done( NPC, "parryReCalcTime" ) ) + {//can't do our own re-think of which parry to use yet + return qfalse; + } + + if ( NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] > level.time ) + {//can't move the saber to another position yet + return qfalse; + } + + /* + if ( NPCInfo->rank < RANK_LT_JG && Q_irand( 0, (2 - g_spskill.integer) ) ) + {//lower rank reborn have a random chance of not doing it at all + NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 300; + return qfalse; + } + */ + + if ( NPC->enemy->health <= 0 || !NPC->enemy->client ) + {//don't keep blocking him once he's dead (or if not a client) + return qfalse; + } + /* + //VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip ); + //VectorMA( NPC->enemy->client->renderInfo.muzzlePointNext, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirNext, saberTipNext ); + VectorMA( NPC->enemy->client->renderInfo.muzzlePointOld, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirOld, saberTipOld ); + VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip ); + + VectorSubtract( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->renderInfo.muzzlePointOld, dir );//get the dir + VectorAdd( dir, NPC->enemy->client->renderInfo.muzzlePoint, saberBase );//extrapolate + + VectorSubtract( saberTip, saberTipOld, dir );//get the dir + VectorAdd( dir, saberTip, saberTipOld );//extrapolate + + VectorCopy( NPC->r.currentOrigin, top ); + top[2] = NPC->r.absmax[2]; + VectorCopy( NPC->r.currentOrigin, bottom ); + bottom[2] = NPC->r.absmin[2]; + + float dist = ShortestLineSegBewteen2LineSegs( saberBase, saberTipOld, bottom, top, saberPoint, axisPoint ); + if ( 0 )//dist > NPC->r.maxs[0]*4 )//was *3 + {//FIXME: sometimes he reacts when you're too far away to actually hit him + if ( d_JediAI.integer ) + { + Com_Printf( "enemy saber dist: %4.2f\n", dist ); + } + TIMER_Set( NPC, "parryTime", -1 ); + return qfalse; + } + + //get the actual point of impact + trace_t tr; + trap_Trace( &tr, saberPoint, vec3_origin, vec3_origin, axisPoint, NPC->enemy->s.number, MASK_SHOT, G2_RETURNONHIT, 10 ); + if ( tr.allsolid || tr.startsolid ) + {//estimate + VectorSubtract( saberPoint, axisPoint, dir ); + VectorNormalize( dir ); + VectorMA( axisPoint, NPC->r.maxs[0]*1.22, dir, hitloc ); + } + else + { + VectorCopy( tr.endpos, hitloc ); + } + */ + VectorSet(saberMins,-4,-4,-4); + VectorSet(saberMaxs,4,4,4); + + VectorMA( NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzlePointOld, NPC->enemy->client->saber[saberNum].blade[bladeNum].length, NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzleDirOld, saberTipOld ); + VectorMA( NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzlePoint, NPC->enemy->client->saber[saberNum].blade[bladeNum].length, NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzleDir, saberTip ); +// VectorCopy(NPC->enemy->client->lastSaberBase_Always, muzzlePoint); +// VectorMA(muzzlePoint, GAME_SABER_LENGTH, NPC->enemy->client->lastSaberDir_Always, saberTip); +// VectorCopy(saberTip, saberTipOld); + + VectorCopy( NPC->r.currentOrigin, top ); + top[2] = NPC->r.absmax[2]; + VectorCopy( NPC->r.currentOrigin, bottom ); + bottom[2] = NPC->r.absmin[2]; + + dist = ShortestLineSegBewteen2LineSegs( NPC->enemy->client->renderInfo.muzzlePoint, saberTip, bottom, top, saberPoint, axisPoint ); + if ( dist > NPC->r.maxs[0]*5 )//was *3 + {//FIXME: sometimes he reacts when you're too far away to actually hit him + if ( d_JediAI.integer ) + { + Com_Printf( S_COLOR_RED"enemy saber dist: %4.2f\n", dist ); + } + /* + if ( dist < 300 //close + && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves + && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || BG_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me + {//he's swinging at me and close enough to be a threat, don't start an attack right now + TIMER_Set( NPC, "parryTime", 100 ); + } + else + */ + { + TIMER_Set( NPC, "parryTime", -1 ); + } + return qfalse; + } + if ( d_JediAI.integer ) + { + Com_Printf( S_COLOR_GREEN"enemy saber dist: %4.2f\n", dist ); + } + + VectorSubtract( saberPoint, NPC->enemy->client->renderInfo.muzzlePoint, pointDir ); + pointDist = VectorLength( pointDir ); + + bladeLen = NPC->enemy->client->saber[saberNum].blade[bladeNum].length; + + if ( bladeLen <= 0 ) + { + baseDirPerc = 0.5f; + } + else + { + baseDirPerc = pointDist/bladeLen; + } + VectorSubtract( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->renderInfo.muzzlePointOld, baseDir ); + VectorSubtract( saberTip, saberTipOld, tipDir ); + VectorScale( baseDir, baseDirPerc, baseDir ); + VectorMA( baseDir, 1.0f-baseDirPerc, tipDir, dir ); + VectorMA( saberPoint, 200, dir, hitloc ); + + //get the actual point of impact + trap_Trace( &tr, saberPoint, saberMins, saberMaxs, hitloc, NPC->enemy->s.number, CONTENTS_BODY );//, G2_RETURNONHIT, 10 ); + if ( tr.allsolid || tr.startsolid || tr.fraction >= 1.0f ) + {//estimate + vec3_t dir2Me; + VectorSubtract( axisPoint, saberPoint, dir2Me ); + dist = VectorNormalize( dir2Me ); + if ( DotProduct( dir, dir2Me ) < 0.2f ) + {//saber is not swinging in my direction + /* + if ( dist < 300 //close + && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves + && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || BG_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me + {//he's swinging at me and close enough to be a threat, don't start an attack right now + TIMER_Set( NPC, "parryTime", 100 ); + } + else + */ + { + TIMER_Set( NPC, "parryTime", -1 ); + } + return qfalse; + } + ShortestLineSegBewteen2LineSegs( saberPoint, hitloc, bottom, top, saberHitPoint, hitloc ); + /* + VectorSubtract( saberPoint, axisPoint, dir ); + VectorNormalize( dir ); + VectorMA( axisPoint, NPC->r.maxs[0]*1.22, dir, hitloc ); + */ + } + else + { + VectorCopy( tr.endpos, hitloc ); + } + + if ( d_JediAI.integer ) + { + //G_DebugLine( saberPoint, hitloc, FRAMETIME, WPDEBUG_SaberColor( NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].color ), qtrue ); + G_TestLine(saberPoint, hitloc, 0x0000ff, FRAMETIME); + } + + //FIXME: if saber is off and/or we have force speed and want to be really cocky, + // and the swing misses by some amount, we can use the dodges here... :) + if ( (evasionType=Jedi_SaberBlockGo( NPC, &ucmd, hitloc, dir, NULL, dist )) != EVASION_DODGE ) + {//we did block (not dodge) + int parryReCalcTime; + + if ( !NPC->client->ps.saberInFlight ) + {//make sure saber is on + WP_ActivateSaber(NPC); + } + + //debounce our parry recalc time + parryReCalcTime = Jedi_ReCalcParryTime( NPC, evasionType ); + TIMER_Set( NPC, "parryReCalcTime", Q_irand( 0, parryReCalcTime ) ); + if ( d_JediAI.integer ) + { + Com_Printf( "Keep parry choice until: %d\n", level.time + parryReCalcTime ); + } + + //determine how long to hold this anim + if ( TIMER_Done( NPC, "parryTime" ) ) + { + if ( NPC->client->NPC_class == CLASS_TAVION ) + { + TIMER_Set( NPC, "parryTime", Q_irand( parryReCalcTime/2, parryReCalcTime*1.5 ) ); + } + else if ( NPCInfo->rank >= RANK_LT_JG ) + {//fencers and higher hold a parry less + TIMER_Set( NPC, "parryTime", parryReCalcTime ); + } + else + {//others hold it longer + TIMER_Set( NPC, "parryTime", Q_irand( 1, 2 )*parryReCalcTime ); + } + } + } + else + { + int dodgeTime = NPC->client->ps.torsoTimer; + if ( NPCInfo->rank > RANK_LT_COMM && NPC->client->NPC_class != CLASS_DESANN ) + {//higher-level guys can dodge faster + dodgeTime -= 200; + } + TIMER_Set( NPC, "parryReCalcTime", dodgeTime ); + TIMER_Set( NPC, "parryTime", dodgeTime ); + } + return qtrue; +} +/* +------------------------- +Jedi_EvasionSaber + +defend if other is using saber and attacking me! +------------------------- +*/ +static void Jedi_EvasionSaber( vec3_t enemy_movedir, float enemy_dist, vec3_t enemy_dir ) +{ + vec3_t dirEnemy2Me; + int evasionChance = 30;//only step aside 30% if he's moving at me but not attacking + qboolean enemy_attacking = qfalse; + qboolean throwing_saber = qfalse; + qboolean shooting_lightning = qfalse; + + if ( !NPC->enemy->client ) + { + return; + } + else if ( NPC->enemy->client + && NPC->enemy->s.weapon == WP_SABER + && NPC->enemy->client->ps.saberLockTime > level.time ) + {//don't try to block/evade an enemy who is in a saberLock + return; + } + else if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON && NPC->enemy->painDebounceTime > level.time ) + {//pressing the advantage of winning a saber lock + return; + } + + if ( NPC->enemy->client->ps.saberInFlight && !TIMER_Done( NPC, "taunting" ) ) + {//if he's throwing his saber, stop taunting + TIMER_Set( NPC, "taunting", -level.time ); + if ( !NPC->client->ps.saberInFlight ) + { + WP_ActivateSaber(NPC); + } + } + + if ( TIMER_Done( NPC, "parryTime" ) ) + { + if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE && + NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//wasn't blocked myself + NPC->client->ps.saberBlocked = BLOCKED_NONE; + } + } + + if ( NPC->enemy->client->ps.weaponTime && NPC->enemy->client->ps.weaponstate == WEAPON_FIRING ) + { + if ( !NPC->client->ps.saberInFlight && Jedi_SaberBlock(0, 0) ) + { + return; + } + } + + VectorSubtract( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, dirEnemy2Me ); + VectorNormalize( dirEnemy2Me ); + + if ( NPC->enemy->client->ps.weaponTime && NPC->enemy->client->ps.weaponstate == WEAPON_FIRING ) + {//enemy is attacking + enemy_attacking = qtrue; + evasionChance = 90; + } + + if ( (NPC->enemy->client->ps.fd.forcePowersActive&(1<enemy->client->ps.saberInFlight && NPC->enemy->client->ps.saberEntityNum != ENTITYNUM_NONE && NPC->enemy->client->ps.saberEntityState != SES_RETURNING ) + {//enemy is shooting lightning + enemy_attacking = qtrue; + throwing_saber = qtrue; + } + + //FIXME: this needs to take skill and rank(reborn type) into account much more + if ( Q_irand( 0, 100 ) < evasionChance ) + {//check to see if he's coming at me + float facingAmt; + if ( VectorCompare( enemy_movedir, vec3_origin ) || shooting_lightning || throwing_saber ) + {//he's not moving (or he's using a ranged attack), see if he's facing me + vec3_t enemy_fwd; + AngleVectors( NPC->enemy->client->ps.viewangles, enemy_fwd, NULL, NULL ); + facingAmt = DotProduct( enemy_fwd, dirEnemy2Me ); + } + else + {//he's moving + facingAmt = DotProduct( enemy_movedir, dirEnemy2Me ); + } + + if ( flrand( 0.25, 1 ) < facingAmt ) + {//coming at/facing me! + int whichDefense = 0; + if ( NPC->client->ps.weaponTime || NPC->client->ps.saberInFlight || NPC->client->NPC_class == CLASS_BOBAFETT ) + {//I'm attacking or recovering from a parry, can only try to strafe/jump right now + if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression ) + { + return; + } + whichDefense = 100; + } + else + { + if ( shooting_lightning ) + {//check for lightning attack + //only valid defense is strafe and/or jump + whichDefense = 100; + } + else if ( throwing_saber ) + {//he's thrown his saber! See if it's coming at me + float saberDist; + vec3_t saberDir2Me; + vec3_t saberMoveDir; + gentity_t *saber = &g_entities[NPC->enemy->client->ps.saberEntityNum]; + VectorSubtract( NPC->r.currentOrigin, saber->r.currentOrigin, saberDir2Me ); + saberDist = VectorNormalize( saberDir2Me ); + VectorCopy( saber->s.pos.trDelta, saberMoveDir ); + VectorNormalize( saberMoveDir ); + if ( !Q_irand( 0, 3 ) ) + { + //Com_Printf( "(%d) raise agg - enemy threw saber\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 ) + {//it's heading towards me + if ( saberDist < 100 ) + {//it's close + whichDefense = Q_irand( 3, 6 ); + } + else if ( saberDist < 200 ) + {//got some time, yet, try pushing + whichDefense = Q_irand( 0, 8 ); + } + } + } + if ( whichDefense ) + {//already chose one + } + else if ( enemy_dist > 80 || !enemy_attacking ) + {//he's pretty far, or not swinging, just strafe + if ( VectorCompare( enemy_movedir, vec3_origin ) ) + {//if he's not moving, not swinging and far enough away, no evasion necc. + return; + } + if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression ) + { + return; + } + whichDefense = 100; + } + else + {//he's getting close and swinging at me + vec3_t fwd; + //see if I'm facing him + AngleVectors( NPC->client->ps.viewangles, fwd, NULL, NULL ); + if ( DotProduct( enemy_dir, fwd ) < 0.5 ) + {//I'm not really facing him, best option is to strafe + whichDefense = Q_irand( 5, 16 ); + } + else if ( enemy_dist < 56 ) + {//he's very close, maybe we should be more inclined to block or throw + whichDefense = Q_irand( NPCInfo->stats.aggression, 12 ); + } + else + { + whichDefense = Q_irand( 2, 16 ); + } + } + } + + if ( whichDefense >= 4 && whichDefense <= 12 ) + {//would try to block + if ( NPC->client->ps.saberInFlight ) + {//can't, saber in not in hand, so fall back to strafe/jump + whichDefense = 100; + } + } + + switch( whichDefense ) + { + case 0: + case 1: + case 2: + case 3: + //use jedi force push? + //FIXME: try to do this if health low or enemy back to a cliff? + if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && TIMER_Done( NPC, "parryTime" ) ) + {//FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]] + ForceThrow( NPC, qfalse ); + } + break; + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + //try to parry the blow + //Com_Printf( "blocking\n" ); + Jedi_SaberBlock(0, 0); + break; + default: + //Evade! + //start a strafe left/right if not already + if ( !Q_irand( 0, 5 ) || !Jedi_Strafe( 300, 1000, 0, 1000, qfalse ) ) + {//certain chance they will pick an alternative evasion + //if couldn't strafe, try a different kind of evasion... + if ( shooting_lightning || throwing_saber || enemy_dist < 80 ) + { + //FIXME: force-jump+forward - jump over the guy! + if ( shooting_lightning || (!Q_irand( 0, 2 ) && NPCInfo->stats.aggression < 4 && TIMER_Done( NPC, "parryTime" ) ) ) + { + if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && !shooting_lightning && Q_irand( 0, 2 ) ) + {//FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]] + ForceThrow( NPC, qfalse ); + } + else if ( (NPCInfo->rank==RANK_CREWMAN||NPCInfo->rank>RANK_LT_JG) + && !(NPCInfo->scriptFlags&SCF_NO_ACROBATICS) + && NPC->client->ps.fd.forceRageRecoveryTime < level.time + && !(NPC->client->ps.fd.forcePowersActive&(1<client->ps ) ) + {//FIXME: make this a function call? + //FIXME: check for clearance, safety of landing spot? + NPC->client->ps.fd.forceJumpCharge = 480; + //Don't jump again for another 2 to 5 seconds + TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) ); + if ( Q_irand( 0, 2 ) ) + { + ucmd.forwardmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + else + { + ucmd.forwardmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + //FIXME: if this jump is cleared, we can't block... so pick a random lower block? + if ( Q_irand( 0, 1 ) )//FIXME: make intelligent + { + NPC->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + } + else + { + NPC->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + } + } + } + else if ( enemy_attacking ) + { + Jedi_SaberBlock(0, 0); + } + } + } + else + {//strafed + if ( d_JediAI.integer ) + { + Com_Printf( "def strafe\n" ); + } + if ( !(NPCInfo->scriptFlags&SCF_NO_ACROBATICS) + && NPC->client->ps.fd.forceRageRecoveryTime < level.time + && !(NPC->client->ps.fd.forcePowersActive&(1<rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG ) + && !PM_InKnockDown( &NPC->client->ps ) + && !Q_irand( 0, 5 ) ) + {//FIXME: make this a function call? + //FIXME: check for clearance, safety of landing spot? + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + NPC->client->ps.fd.forceJumpCharge = 280;//FIXME: calc this intelligently? + } + else + { + NPC->client->ps.fd.forceJumpCharge = 320; + } + //Don't jump again for another 2 to 5 seconds + TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) ); + } + } + break; + } + + //turn off slow walking no matter what + TIMER_Set( NPC, "walking", -level.time ); + TIMER_Set( NPC, "taunting", -level.time ); + } + } +} +/* +------------------------- +Jedi_Flee +------------------------- +*/ +/* + +static qboolean Jedi_Flee( void ) +{ + return qfalse; +} +*/ + + +/* +========================================================================================== +INTERNAL AI ROUTINES +========================================================================================== +*/ +gentity_t *Jedi_FindEnemyInCone( gentity_t *self, gentity_t *fallback, float minDot ) +{ + vec3_t forward, mins, maxs, dir; + float dist, bestDist = Q3_INFINITE; + gentity_t *enemy = fallback; + gentity_t *check = NULL; + int entityList[MAX_GENTITIES]; + int e, numListedEntities; + trace_t tr; + + if ( !self->client ) + { + return enemy; + } + + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + + for ( e = 0 ; e < 3 ; e++ ) + { + mins[e] = self->r.currentOrigin[e] - 1024; + maxs[e] = self->r.currentOrigin[e] + 1024; + } + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + check = &g_entities[entityList[e]]; + if ( check == self ) + {//me + continue; + } + if ( !(check->inuse) ) + {//freed + continue; + } + if ( !check->client ) + {//not a client - FIXME: what about turrets? + continue; + } + if ( check->client->playerTeam != self->client->enemyTeam ) + {//not an enemy - FIXME: what about turrets? + continue; + } + if ( check->health <= 0 ) + {//dead + continue; + } + + if ( !trap_InPVS( check->r.currentOrigin, self->r.currentOrigin ) ) + {//can't potentially see them + continue; + } + + VectorSubtract( check->r.currentOrigin, self->r.currentOrigin, dir ); + dist = VectorNormalize( dir ); + + if ( DotProduct( dir, forward ) < minDot ) + {//not in front + continue; + } + + //really should have a clear LOS to this thing... + trap_Trace( &tr, self->r.currentOrigin, vec3_origin, vec3_origin, check->r.currentOrigin, self->s.number, MASK_SHOT ); + if ( tr.fraction < 1.0f && tr.entityNum != check->s.number ) + {//must have clear shot + continue; + } + + if ( dist < bestDist ) + {//closer than our last best one + dist = bestDist; + enemy = check; + } + } + return enemy; +} + +static void Jedi_SetEnemyInfo( vec3_t enemy_dest, vec3_t enemy_dir, float *enemy_dist, vec3_t enemy_movedir, float *enemy_movespeed, int prediction ) +{ + if ( !NPC || !NPC->enemy ) + {//no valid enemy + return; + } + if ( !NPC->enemy->client ) + { + VectorClear( enemy_movedir ); + *enemy_movespeed = 0; + VectorCopy( NPC->enemy->r.currentOrigin, enemy_dest ); + enemy_dest[2] += NPC->enemy->r.mins[2] + 24;//get it's origin to a height I can work with + VectorSubtract( enemy_dest, NPC->r.currentOrigin, enemy_dir ); + //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade.... + *enemy_dist = VectorNormalize( enemy_dir );// - (NPC->client->ps.saberLengthMax + NPC->r.maxs[0]*1.5 + 16); + } + else + {//see where enemy is headed + VectorCopy( NPC->enemy->client->ps.velocity, enemy_movedir ); + *enemy_movespeed = VectorNormalize( enemy_movedir ); + //figure out where he'll be, say, 3 frames from now + VectorMA( NPC->enemy->r.currentOrigin, *enemy_movespeed * 0.001 * prediction, enemy_movedir, enemy_dest ); + //figure out what dir the enemy's estimated position is from me and how far from the tip of my saber he is + VectorSubtract( enemy_dest, NPC->r.currentOrigin, enemy_dir );//NPC->client->renderInfo.muzzlePoint + //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade.... + *enemy_dist = VectorNormalize( enemy_dir ) - (NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5 + 16); //just use the blade 0 len I guess + //FIXME: keep a group of enemies around me and use that info to make decisions... + // For instance, if there are multiple enemies, evade more, push them away + // and use medium attacks. If enemies are using blasters, switch to fast. + // If one jedi enemy, use strong attacks. Use grip when fighting one or + // two enemies, use lightning spread when fighting multiple enemies, etc. + // Also, when kill one, check rest of group instead of walking up to victim. + } +} + +extern float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire ); +static void Jedi_FaceEnemy( qboolean doPitch ) +{ + vec3_t enemy_eyes, eyes, angles; + + if ( NPC == NULL ) + return; + + if ( NPC->enemy == NULL ) + return; + + if ( NPC->client->ps.fd.forcePowersActive & (1<client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//don't update? + NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH]; + NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW]; + return; + } + CalcEntitySpot( NPC, SPOT_HEAD, eyes ); + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_eyes ); + + if ( NPC->client->NPC_class == CLASS_BOBAFETT + && TIMER_Done( NPC, "flameTime" ) + && NPC->s.weapon != WP_NONE + && NPC->s.weapon != WP_DISRUPTOR + && (NPC->s.weapon != WP_ROCKET_LAUNCHER||!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) + && NPC->s.weapon != WP_THERMAL + && NPC->s.weapon != WP_TRIP_MINE + && NPC->s.weapon != WP_DET_PACK + && NPC->s.weapon != WP_STUN_BATON + /*&& NPC->s.weapon != WP_MELEE*/ ) + {//boba leads his enemy + if ( NPC->health < NPC->client->pers.maxHealth*0.5f ) + {//lead + float missileSpeed = WP_SpeedOfMissileForWeapon( NPC->s.weapon, ((qboolean)(NPCInfo->scriptFlags&SCF_ALT_FIRE)) ); + if ( missileSpeed ) + { + float eDist = Distance( eyes, enemy_eyes ); + eDist /= missileSpeed;//How many seconds it will take to get to the enemy + VectorMA( enemy_eyes, eDist*flrand(0.95f,1.25f), NPC->enemy->client->ps.velocity, enemy_eyes ); + } + } + } + + //Find the desired angles + if ( !NPC->client->ps.saberInFlight + && (NPC->client->ps.legsAnim == BOTH_A2_STABBACK1 + || NPC->client->ps.legsAnim == BOTH_CROUCHATTACKBACK1 + || NPC->client->ps.legsAnim == BOTH_ATTACK_BACK) + ) + {//point *away* + GetAnglesForDirection( enemy_eyes, eyes, angles ); + } + else + {//point towards him + GetAnglesForDirection( eyes, enemy_eyes, angles ); + } + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + /* + if ( NPC->client->ps.saberBlocked == BLOCKED_UPPER_LEFT ) + {//temp hack- to make up for poor coverage on left side + NPCInfo->desiredYaw += 30; + } + */ + + if ( doPitch ) + { + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + if ( NPC->client->ps.saberInFlight ) + {//tilt down a little + NPCInfo->desiredPitch += 10; + } + } + //FIXME: else desiredPitch = 0? Or keep previous? +} + +static void Jedi_DebounceDirectionChanges( void ) +{ + //FIXME: check these before making fwd/back & right/left decisions? + //Time-debounce changes in forward/back dir + if ( ucmd.forwardmove > 0 ) + { + if ( !TIMER_Done( NPC, "moveback" ) || !TIMER_Done( NPC, "movenone" ) ) + { + ucmd.forwardmove = 0; + //now we have to normalize the total movement again + if ( ucmd.rightmove > 0 ) + { + ucmd.rightmove = 127; + } + else if ( ucmd.rightmove < 0 ) + { + ucmd.rightmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveback", -level.time ); + if ( TIMER_Done( NPC, "movenone" ) ) + { + TIMER_Set( NPC, "movenone", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveforward" ) ) + {//FIXME: should be if it's zero? + TIMER_Set( NPC, "moveforward", Q_irand( 500, 2000 ) ); + } + } + else if ( ucmd.forwardmove < 0 ) + { + if ( !TIMER_Done( NPC, "moveforward" ) || !TIMER_Done( NPC, "movenone" ) ) + { + ucmd.forwardmove = 0; + //now we have to normalize the total movement again + if ( ucmd.rightmove > 0 ) + { + ucmd.rightmove = 127; + } + else if ( ucmd.rightmove < 0 ) + { + ucmd.rightmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveforward", -level.time ); + if ( TIMER_Done( NPC, "movenone" ) ) + { + TIMER_Set( NPC, "movenone", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveback" ) ) + {//FIXME: should be if it's zero? + TIMER_Set( NPC, "moveback", Q_irand( 250, 1000 ) ); + } + } + else if ( !TIMER_Done( NPC, "moveforward" ) ) + {//NOTE: edge checking should stop me if this is bad... but what if it sends us colliding into the enemy? + ucmd.forwardmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + else if ( !TIMER_Done( NPC, "moveback" ) ) + {//NOTE: edge checking should stop me if this is bad... + ucmd.forwardmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + //Time-debounce changes in right/left dir + if ( ucmd.rightmove > 0 ) + { + if ( !TIMER_Done( NPC, "moveleft" ) || !TIMER_Done( NPC, "movecenter" ) ) + { + ucmd.rightmove = 0; + //now we have to normalize the total movement again + if ( ucmd.forwardmove > 0 ) + { + ucmd.forwardmove = 127; + } + else if ( ucmd.forwardmove < 0 ) + { + ucmd.forwardmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveleft", -level.time ); + if ( TIMER_Done( NPC, "movecenter" ) ) + { + TIMER_Set( NPC, "movecenter", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveright" ) ) + {//FIXME: should be if it's zero? + TIMER_Set( NPC, "moveright", Q_irand( 250, 1500 ) ); + } + } + else if ( ucmd.rightmove < 0 ) + { + if ( !TIMER_Done( NPC, "moveright" ) || !TIMER_Done( NPC, "movecenter" ) ) + { + ucmd.rightmove = 0; + //now we have to normalize the total movement again + if ( ucmd.forwardmove > 0 ) + { + ucmd.forwardmove = 127; + } + else if ( ucmd.forwardmove < 0 ) + { + ucmd.forwardmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveright", -level.time ); + if ( TIMER_Done( NPC, "movecenter" ) ) + { + TIMER_Set( NPC, "movecenter", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveleft" ) ) + {//FIXME: should be if it's zero? + TIMER_Set( NPC, "moveleft", Q_irand( 250, 1500 ) ); + } + } + else if ( !TIMER_Done( NPC, "moveright" ) ) + {//NOTE: edge checking should stop me if this is bad... + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + else if ( !TIMER_Done( NPC, "moveleft" ) ) + {//NOTE: edge checking should stop me if this is bad... + ucmd.rightmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } +} + +static void Jedi_TimersApply( void ) +{ + if ( !ucmd.rightmove ) + {//only if not already strafing + //FIXME: if enemy behind me and turning to face enemy, don't strafe in that direction, too + if ( !TIMER_Done( NPC, "strafeLeft" ) ) + { + if ( NPCInfo->desiredYaw > NPC->client->ps.viewangles[YAW] + 60 ) + {//we want to turn left, don't apply the strafing + } + else + {//go ahead and strafe left + ucmd.rightmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + } + else if ( !TIMER_Done( NPC, "strafeRight" ) ) + { + if ( NPCInfo->desiredYaw < NPC->client->ps.viewangles[YAW] - 60 ) + {//we want to turn right, don't apply the strafing + } + else + {//go ahead and strafe left + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + } + } + + Jedi_DebounceDirectionChanges(); + + //use careful anim/slower movement if not already moving + if ( !ucmd.forwardmove && !TIMER_Done( NPC, "walking" ) ) + { + ucmd.buttons |= (BUTTON_WALKING); + } + + if ( !TIMER_Done( NPC, "taunting" ) ) + { + ucmd.buttons |= (BUTTON_WALKING); + } + + if ( !TIMER_Done( NPC, "gripping" ) ) + {//FIXME: what do we do if we ran out of power? NPC's can't? + //FIXME: don't keep turning to face enemy or we'll end up spinning around + ucmd.buttons |= BUTTON_FORCEGRIP; + } + + if ( !TIMER_Done( NPC, "draining" ) ) + {//FIXME: what do we do if we ran out of power? NPC's can't? + //FIXME: don't keep turning to face enemy or we'll end up spinning around + ucmd.buttons |= BUTTON_FORCE_DRAIN; + } + + if ( !TIMER_Done( NPC, "holdLightning" ) ) + {//hold down the lightning key + ucmd.buttons |= BUTTON_FORCE_LIGHTNING; + } +} + +static void Jedi_CombatTimersUpdate( int enemy_dist ) +{ + if ( TIMER_Done( NPC, "roamTime" ) ) + { + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) ); + //okay, now mess with agression + if ( NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forceRageRecoveryTime > level.time ) + {//recovering + Jedi_Aggression( NPC, Q_irand( 0, -2 ) ); + } + if ( NPC->enemy && NPC->enemy->client ) + { + switch( NPC->enemy->client->ps.weapon ) + { + case WP_SABER: + //If enemy has a lightsaber, always close in + if ( BG_SabersOff( &NPC->enemy->client->ps ) ) + {//fool! Standing around unarmed, charge! + //Com_Printf( "(%d) raise agg - enemy saber off\n", level.time ); + Jedi_Aggression( NPC, 2 ); + } + else + { + //Com_Printf( "(%d) raise agg - enemy saber\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + break; + case WP_BLASTER: + case WP_BRYAR_PISTOL: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_REPEATER: + case WP_DEMP2: + case WP_FLECHETTE: + case WP_ROCKET_LAUNCHER: + //if he has a blaster, move in when: + //They're not shooting at me + if ( NPC->enemy->attackDebounceTime < level.time ) + {//does this apply to players? + //Com_Printf( "(%d) raise agg - enemy not shooting ranged weap\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + //He's closer than a dist that gives us time to deflect + if ( enemy_dist < 256 ) + { + //Com_Printf( "(%d) raise agg - enemy ranged weap- too close\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + break; + default: + break; + } + } + } + + if ( TIMER_Done( NPC, "noStrafe" ) && TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) ) + { + //FIXME: Maybe more likely to do this if aggression higher? Or some other stat? + if ( !Q_irand( 0, 4 ) ) + {//start a strafe + if ( Jedi_Strafe( 1000, 3000, 0, 4000, qtrue ) ) + { + if ( d_JediAI.integer ) + { + Com_Printf( "off strafe\n" ); + } + } + } + else + {//postpone any strafing for a while + TIMER_Set( NPC, "noStrafe", Q_irand( 1000, 3000 ) ); + } + } + + if ( NPC->client->ps.saberEventFlags ) + {//some kind of saber combat event is still pending + int newFlags = NPC->client->ps.saberEventFlags; + if ( NPC->client->ps.saberEventFlags&SEF_PARRIED ) + {//parried + TIMER_Set( NPC, "parryTime", -1 ); + /* + if ( NPCInfo->rank >= RANK_LT_JG ) + { + NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 100; + } + else + { + NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + } + */ + if ( NPC->enemy && PM_SaberInKnockaway( NPC->enemy->client->ps.saberMove ) ) + {//advance! + Jedi_Aggression( NPC, 1 );//get closer + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );//use a faster attack + } + else + { + if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff? + { + //Com_Printf( "(%d) drop agg - we parried\n", level.time ); + Jedi_Aggression( NPC, -1 ); + } + if ( !Q_irand( 0, 1 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) ); + } + } + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) PARRY: agg %d, no parry until %d\n", level.time, NPCInfo->stats.aggression, level.time + 100 ); + } + newFlags &= ~SEF_PARRIED; + } + if ( !NPC->client->ps.weaponTime && (NPC->client->ps.saberEventFlags&SEF_HITENEMY) )//hit enemy + {//we hit our enemy last time we swung, drop our aggression + if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff? + { + //Com_Printf( "(%d) drop agg - we hit enemy\n", level.time ); + Jedi_Aggression( NPC, -1 ); + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) HIT: agg %d\n", level.time, NPCInfo->stats.aggression ); + } + if ( !Q_irand( 0, 3 ) + && NPCInfo->blockedSpeechDebounceTime < level.time + && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time + && NPC->painDebounceTime < level.time - 1000 ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + } + if ( !Q_irand( 0, 2 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel+1) ); + } + newFlags &= ~SEF_HITENEMY; + } + if ( (NPC->client->ps.saberEventFlags&SEF_BLOCKED) ) + {//was blocked whilst attacking + if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove ) + || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN ) + { + //Com_Printf( "(%d) drop agg - we were knock-blocked\n", level.time ); + if ( NPC->client->ps.saberInFlight ) + {//lost our saber, too!!! + Jedi_Aggression( NPC, -5 );//really really really should back off!!! + } + else + { + Jedi_Aggression( NPC, -2 );//really should back off! + } + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel+1) );//use a stronger attack + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) KNOCK-BLOCKED: agg %d\n", level.time, NPCInfo->stats.aggression ); + } + } + else + { + if ( !Q_irand( 0, 2 ) )//FIXME: dependant on rank/diff? + { + //Com_Printf( "(%d) drop agg - we were blocked\n", level.time ); + Jedi_Aggression( NPC, -1 ); + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) BLOCKED: agg %d\n", level.time, NPCInfo->stats.aggression ); + } + } + if ( !Q_irand( 0, 1 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel+1) ); + } + } + newFlags &= ~SEF_BLOCKED; + //FIXME: based on the type of parry the enemy is doing and my skill, + // choose an attack that is likely to get around the parry? + // right now that's generic in the saber animation code, auto-picks + // a next anim for me, but really should be AI-controlled. + } + if ( NPC->client->ps.saberEventFlags&SEF_DEFLECTED ) + {//deflected a shot + newFlags &= ~SEF_DEFLECTED; + if ( !Q_irand( 0, 3 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) ); + } + } + if ( NPC->client->ps.saberEventFlags&SEF_HITWALL ) + {//hit a wall + newFlags &= ~SEF_HITWALL; + } + if ( NPC->client->ps.saberEventFlags&SEF_HITOBJECT ) + {//hit some other damagable object + if ( !Q_irand( 0, 3 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) ); + } + newFlags &= ~SEF_HITOBJECT; + } + NPC->client->ps.saberEventFlags = newFlags; + } +} + +static void Jedi_CombatIdle( int enemy_dist ) +{ + if ( !TIMER_Done( NPC, "parryTime" ) ) + { + return; + } + if ( NPC->client->ps.saberInFlight ) + {//don't do this idle stuff if throwing saber + return; + } + if ( NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forceRageRecoveryTime > level.time ) + {//never taunt while raging or recovering from rage + return; + } + //FIXME: make these distance numbers defines? + if ( enemy_dist >= 64 ) + {//FIXME: only do this if standing still? + //based on aggression, flaunt/taunt + int chance = 20; + if ( NPC->client->NPC_class == CLASS_SHADOWTROOPER ) + { + chance = 10; + } + //FIXME: possibly throw local objects at enemy? + if ( Q_irand( 2, chance ) < NPCInfo->stats.aggression ) + { + if ( TIMER_Done( NPC, "chatter" ) && NPC->client->ps.forceHandExtend == HANDEXTEND_NONE ) + {//FIXME: add more taunt behaviors + //FIXME: sometimes he turns it off, then turns it right back on again??? + if ( enemy_dist > 200 + && NPC->client->NPC_class != CLASS_BOBAFETT + && !NPC->client->ps.saberHolstered + && !Q_irand( 0, 5 ) ) + {//taunt even more, turn off the saber + //FIXME: don't do this if health low? + WP_DeactivateSaber( NPC, qfalse ); + //Don't attack for a bit + NPCInfo->stats.aggression = 3; + //FIXME: maybe start strafing? + //debounce this + if ( NPC->client->playerTeam != NPCTEAM_PLAYER && !Q_irand( 0, 1 )) + { + //NPC->client->ps.taunting = level.time + 100; + NPC->client->ps.forceHandExtend = HANDEXTEND_JEDITAUNT; + NPC->client->ps.forceHandExtendTime = level.time + 5000; + + TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) ); + TIMER_Set( NPC, "taunting", 5500 ); + } + else + { + Jedi_BattleTaunt(); + TIMER_Set( NPC, "taunting", Q_irand( 5000, 10000 ) ); + } + } + else if ( Jedi_BattleTaunt() ) + {//FIXME: pick some anims + } + } + } + } +} + +static qboolean Jedi_AttackDecide( int enemy_dist ) +{ + if ( NPC->enemy->client + && NPC->enemy->s.weapon == WP_SABER + && NPC->enemy->client->ps.saberLockTime > level.time + && NPC->client->ps.saberLockTime < level.time ) + {//enemy is in a saberLock and we are not + return qfalse; + } + + if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON ) + {//we won a saber lock, press the advantage with an attack! + int chance = 0; + if ( NPC->client->NPC_class == CLASS_DESANN || NPC->client->NPC_class == CLASS_LUKE || !Q_stricmp("Yoda",NPC->NPC_type) ) + {//desann and luke + chance = 20; + } + else if ( NPC->client->NPC_class == CLASS_TAVION ) + {//tavion + chance = 10; + } + else if ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG ) + {//fencer + chance = 5; + } + else + { + chance = NPCInfo->rank; + } + if ( Q_irand( 0, 30 ) < chance ) + {//based on skill with some randomness + NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;//clear this now that we are using the opportunity + TIMER_Set( NPC, "noRetreat", Q_irand( 500, 2000 ) ); + //FIXME: check enemy_dist? + NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0; + //NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + NPC->client->ps.saberBlocked = BLOCKED_NONE; + WeaponThink( qtrue ); + return qtrue; + } + } + + if ( NPC->client->NPC_class == CLASS_TAVION || + ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG ) || + ( NPC->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) ) + {//tavion, fencers, jedi trainer are all good at following up a parry with an attack + if ( ( PM_SaberInParry( NPC->client->ps.saberMove ) || PM_SaberInKnockaway( NPC->client->ps.saberMove ) ) + && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//try to attack straight from a parry + NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0; + //NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + NPC->client->ps.saberBlocked = BLOCKED_NONE; + Jedi_AdjustSaberAnimLevel( NPC, FORCE_LEVEL_1 );//try to follow-up with a quick attack + WeaponThink( qtrue ); + return qtrue; + } + } + + //try to hit them if we can + if ( enemy_dist >= 64 ) + { + return qfalse; + } + + if ( !TIMER_Done( NPC, "parryTime" ) ) + { + return qfalse; + } + + if ( (NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//not allowed to attack + return qfalse; + } + + if ( !(ucmd.buttons&BUTTON_ATTACK) && !(ucmd.buttons&BUTTON_ALT_ATTACK) ) + {//not already attacking + //Try to attack + WeaponThink( qtrue ); + } + + //FIXME: Maybe try to push enemy off a ledge? + + //close enough to step forward + + //FIXME: an attack debounce timer other than the phaser debounce time? + // or base it on aggression? + + if ( ucmd.buttons&BUTTON_ATTACK ) + {//attacking + /* + if ( enemy_dist > 32 && NPCInfo->stats.aggression >= 4 ) + {//move forward if we're too far away and we're chasing him + ucmd.forwardmove = 127; + } + else if ( enemy_dist < 0 ) + {//move back if we're too close + ucmd.forwardmove = -127; + } + */ + //FIXME: based on the type of parry/attack the enemy is doing and my skill, + // choose an attack that is likely to get around the parry? + // right now that's generic in the saber animation code, auto-picks + // a next anim for me, but really should be AI-controlled. + //FIXME: have this interact with/override above strafing code? + if ( !ucmd.rightmove ) + {//not already strafing + if ( !Q_irand( 0, 3 ) ) + {//25% chance of doing this + vec3_t right, dir2enemy; + + AngleVectors( NPC->r.currentAngles, NULL, right, NULL ); + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentAngles, dir2enemy ); + if ( DotProduct( right, dir2enemy ) > 0 ) + {//he's to my right, strafe left + ucmd.rightmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + else + {//he's to my left, strafe right + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + } + } + return qtrue; + } + + return qfalse; +} + +#define APEX_HEIGHT 200.0f +#define PARA_WIDTH (sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT)) +#define JUMP_SPEED 200.0f + +static qboolean Jedi_Jump( vec3_t dest, int goalEntNum ) +{//FIXME: if land on enemy, knock him down & jump off again + /* + if ( dest[2] - NPC->r.currentOrigin[2] < 64 && DistanceHorizontal( NPC->r.currentOrigin, dest ) > 256 ) + {//a pretty horizontal jump, easy to fake: + vec3_t enemy_diff; + + VectorSubtract( dest, NPC->r.currentOrigin, enemy_diff ); + float enemy_z_diff = enemy_diff[2]; + enemy_diff[2] = 0; + float enemy_xy_diff = VectorNormalize( enemy_diff ); + + VectorScale( enemy_diff, enemy_xy_diff*0.8, NPC->client->ps.velocity ); + if ( enemy_z_diff < 64 ) + { + NPC->client->ps.velocity[2] = enemy_xy_diff; + } + else + { + NPC->client->ps.velocity[2] = enemy_z_diff*2+enemy_xy_diff/2; + } + } + else + */ + if ( 1 ) + { + float targetDist, shotSpeed = 300, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed, + vec3_t targetDir, shotVel, failCase; + trace_t trace; + trajectory_t tr; + qboolean blocked; + int elapsedTime, timeStep = 500, hitCount = 0, maxHits = 7; + vec3_t lastPos, testPos, bottom; + + while ( hitCount < maxHits ) + { + VectorSubtract( dest, NPC->r.currentOrigin, targetDir ); + targetDist = VectorNormalize( targetDir ); + + VectorScale( targetDir, shotSpeed, shotVel ); + travelTime = targetDist/shotSpeed; + shotVel[2] += travelTime * 0.5 * NPC->client->ps.gravity; + + if ( !hitCount ) + {//save the first one as the worst case scenario + VectorCopy( shotVel, failCase ); + } + + if ( 1 )//tracePath ) + {//do a rough trace of the path + blocked = qfalse; + + VectorCopy( NPC->r.currentOrigin, tr.trBase ); + VectorCopy( shotVel, tr.trDelta ); + tr.trType = TR_GRAVITY; + tr.trTime = level.time; + travelTime *= 1000.0f; + VectorCopy( NPC->r.currentOrigin, lastPos ); + + //This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down? + for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep ) + { + if ( (float)elapsedTime > travelTime ) + {//cap it + elapsedTime = floor( travelTime ); + } + BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos ); + if ( testPos[2] < lastPos[2] ) + {//going down, ignore botclip + trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask ); + } + else + {//going up, check for botclip + trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + } + + if ( trace.allsolid || trace.startsolid ) + { + blocked = qtrue; + break; + } + if ( trace.fraction < 1.0f ) + {//hit something + if ( trace.entityNum == goalEntNum ) + {//hit the enemy, that's perfect! + //Hmm, don't want to land on him, though... + break; + } + else + { + if ( trace.contents & CONTENTS_BOTCLIP ) + {//hit a do-not-enter brush + blocked = qtrue; + break; + } + if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, dest ) < 4096 )//hit within 64 of desired location, should be okay + {//close enough! + break; + } + else + {//FIXME: maybe find the extents of this brush and go above or below it on next try somehow? + impactDist = DistanceSquared( trace.endpos, dest ); + if ( impactDist < bestImpactDist ) + { + bestImpactDist = impactDist; + VectorCopy( shotVel, failCase ); + } + blocked = qtrue; + break; + } + } + } + if ( elapsedTime == floor( travelTime ) ) + {//reached end, all clear + if ( trace.fraction >= 1.0f ) + {//hmm, make sure we'll land on the ground... + //FIXME: do we care how far below ourselves or our dest we'll land? + VectorCopy( trace.endpos, bottom ); + bottom[2] -= 128; + trap_Trace( &trace, trace.endpos, NPC->r.mins, NPC->r.maxs, bottom, NPC->s.number, NPC->clipmask ); + if ( trace.fraction >= 1.0f ) + {//would fall too far + blocked = qtrue; + } + } + break; + } + else + { + //all clear, try next slice + VectorCopy( testPos, lastPos ); + } + } + if ( blocked ) + {//hit something, adjust speed (which will change arc) + hitCount++; + shotSpeed = 300 + ((hitCount-2) * 100);//from 100 to 900 (skipping 300) + if ( hitCount >= 2 ) + {//skip 300 since that was the first value we tested + shotSpeed += 100; + } + } + else + {//made it! + break; + } + } + else + {//no need to check the path, go with first calc + break; + } + } + + if ( hitCount >= maxHits ) + {//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?) + //NOTE: or try failcase? + VectorCopy( failCase, NPC->client->ps.velocity ); + } + VectorCopy( shotVel, NPC->client->ps.velocity ); + } + else + {//a more complicated jump + vec3_t dir, p1, p2, apex; + float time, height, forward, z, xy, dist, apexHeight; + + if ( NPC->r.currentOrigin[2] > dest[2] )//NPCInfo->goalEntity->r.currentOrigin + { + VectorCopy( NPC->r.currentOrigin, p1 ); + VectorCopy( dest, p2 );//NPCInfo->goalEntity->r.currentOrigin + } + else if ( NPC->r.currentOrigin[2] < dest[2] )//NPCInfo->goalEntity->r.currentOrigin + { + VectorCopy( dest, p1 );//NPCInfo->goalEntity->r.currentOrigin + VectorCopy( NPC->r.currentOrigin, p2 ); + } + else + { + VectorCopy( NPC->r.currentOrigin, p1 ); + VectorCopy( dest, p2 );//NPCInfo->goalEntity->r.currentOrigin + } + + //z = xy*xy + VectorSubtract( p2, p1, dir ); + dir[2] = 0; + + //Get xy and z diffs + xy = VectorNormalize( dir ); + z = p1[2] - p2[2]; + + apexHeight = APEX_HEIGHT/2; + + //Determine most desirable apex height + //FIXME: length of xy will change curve of parabola, need to account for this + //somewhere... PARA_WIDTH + /* + apexHeight = (APEX_HEIGHT * PARA_WIDTH/xy) + (APEX_HEIGHT * z/128); + if ( apexHeight < APEX_HEIGHT * 0.5 ) + { + apexHeight = APEX_HEIGHT*0.5; + } + else if ( apexHeight > APEX_HEIGHT * 2 ) + { + apexHeight = APEX_HEIGHT*2; + } + */ + + z = (sqrt(apexHeight + z) - sqrt(apexHeight)); + + assert(z >= 0); + +// Com_Printf("apex is %4.2f percent from p1: ", (xy-z)*0.5/xy*100.0f); + + xy -= z; + xy *= 0.5; + + assert(xy > 0); + + VectorMA( p1, xy, dir, apex ); + apex[2] += apexHeight; + + VectorCopy(apex, NPC->pos1); + + //Now we have the apex, aim for it + height = apex[2] - NPC->r.currentOrigin[2]; + time = sqrt( height / ( .5 * NPC->client->ps.gravity ) );//was 0.5, but didn't work well for very long jumps + if ( !time ) + { + //Com_Printf( S_COLOR_RED"ERROR: no time in jump\n" ); + return qfalse; + } + + VectorSubtract ( apex, NPC->r.currentOrigin, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 0; + dist = VectorNormalize( NPC->client->ps.velocity ); + + forward = dist / time * 1.25;//er... probably bad, but... + VectorScale( NPC->client->ps.velocity, forward, NPC->client->ps.velocity ); + + //FIXME: Uh.... should we trace/EvaluateTrajectory this to make sure we have clearance and we land where we want? + NPC->client->ps.velocity[2] = time * NPC->client->ps.gravity; + + //Com_Printf("Jump Velocity: %4.2f, %4.2f, %4.2f\n", NPC->client->ps.velocity[0], NPC->client->ps.velocity[1], NPC->client->ps.velocity[2] ); + } + return qtrue; +} + +static qboolean Jedi_TryJump( gentity_t *goal ) +{//FIXME: never does a simple short, regular jump... + //FIXME: I need to be on ground too! + if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) ) + { + return qfalse; + } + if ( TIMER_Done( NPC, "jumpChaseDebounce" ) ) + { + if ( (!goal->client || goal->client->ps.groundEntityNum != ENTITYNUM_NONE) ) + { + if ( !PM_InKnockDown( &NPC->client->ps ) && !BG_InRoll( &NPC->client->ps, NPC->client->ps.legsAnim ) ) + {//enemy is on terra firma + vec3_t goal_diff; + float goal_z_diff; + float goal_xy_dist; + VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, goal_diff ); + goal_z_diff = goal_diff[2]; + goal_diff[2] = 0; + goal_xy_dist = VectorNormalize( goal_diff ); + if ( goal_xy_dist < 550 && goal_z_diff > -400/*was -256*/ )//for now, jedi don't take falling damage && (NPC->health > 20 || goal_z_diff > 0 ) && (NPC->health >= 100 || goal_z_diff > -128 ))//closer than @512 + { + qboolean debounce = qfalse; + if ( NPC->health < 150 && ((NPC->health < 30 && goal_z_diff < 0) || goal_z_diff < -128 ) ) + {//don't jump, just walk off... doesn't help with ledges, though + debounce = qtrue; + } + else if ( goal_z_diff < 32 && goal_xy_dist < 200 ) + {//what is their ideal jump height? + ucmd.upmove = 127; + debounce = qtrue; + } + else + { + /* + //NO! All Jedi can jump-navigate now... + if ( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG ) + {//can't do acrobatics + return qfalse; + } + */ + if ( goal_z_diff > 0 || goal_xy_dist > 128 ) + {//Fake a force-jump + //Screw it, just do my own calc & throw + vec3_t dest; + VectorCopy( goal->r.currentOrigin, dest ); + if ( goal == NPC->enemy ) + { + int sideTry = 0; + while( sideTry < 10 ) + {//FIXME: make it so it doesn't try the same spot again? + trace_t trace; + vec3_t bottom; + + if ( Q_irand( 0, 1 ) ) + { + dest[0] += NPC->enemy->r.maxs[0]*1.25; + } + else + { + dest[0] += NPC->enemy->r.mins[0]*1.25; + } + if ( Q_irand( 0, 1 ) ) + { + dest[1] += NPC->enemy->r.maxs[1]*1.25; + } + else + { + dest[1] += NPC->enemy->r.mins[1]*1.25; + } + VectorCopy( dest, bottom ); + bottom[2] -= 128; + trap_Trace( &trace, dest, NPC->r.mins, NPC->r.maxs, bottom, goal->s.number, NPC->clipmask ); + if ( trace.fraction < 1.0f ) + {//hit floor, okay to land here + break; + } + sideTry++; + } + if ( sideTry >= 10 ) + {//screw it, just jump right at him? + VectorCopy( goal->r.currentOrigin, dest ); + } + } + if ( Jedi_Jump( dest, goal->s.number ) ) + { + //Com_Printf( "(%d) pre-checked force jump\n", level.time ); + + //FIXME: store the dir we;re going in in case something gets in the way of the jump? + //? = vectoyaw( NPC->client->ps.velocity ); + /* + if ( NPC->client->ps.velocity[2] < 320 ) + { + NPC->client->ps.velocity[2] = 320; + } + else + */ + {//FIXME: make this a function call + int jumpAnim; + //FIXME: this should be more intelligent, like the normal force jump anim logic + if ( NPC->client->NPC_class == CLASS_BOBAFETT + ||( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG ) ) + {//can't do acrobatics + jumpAnim = BOTH_FORCEJUMP1; + } + else + { + jumpAnim = BOTH_FLIP_F; + } + NPC_SetAnim( NPC, SETANIM_BOTH, jumpAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + + NPC->client->ps.fd.forceJumpZStart = NPC->r.currentOrigin[2]; + //NPC->client->ps.pm_flags |= PMF_JUMPING; + + NPC->client->ps.weaponTime = NPC->client->ps.torsoTimer; + NPC->client->ps.fd.forcePowersActive |= ( 1 << FP_LEVITATION ); + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + G_SoundOnEnt( NPC, CHAN_ITEM, "sound/boba/jeton.wav" ); + NPC->client->jetPackTime = level.time + Q_irand( 1000, 3000 ); + } + else + { + G_SoundOnEnt( NPC, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + + TIMER_Set( NPC, "forceJumpChasing", Q_irand( 2000, 3000 ) ); + debounce = qtrue; + } + } + } + if ( debounce ) + { + //Don't jump again for another 2 to 5 seconds + TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) ); + ucmd.forwardmove = 127; + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "duck", -level.time ); + return qtrue; + } + } + } + } + } + return qfalse; +} + +static qboolean Jedi_Jumping( gentity_t *goal ) +{ + if ( !TIMER_Done( NPC, "forceJumpChasing" ) && goal ) + {//force-jumping at the enemy +// if ( !(NPC->client->ps.pm_flags & PMF_JUMPING )//forceJumpZStart ) +// && !(NPC->client->ps.pm_flags&PMF_TRIGGER_PUSHED)) + if (NPC->client->ps.groundEntityNum != ENTITYNUM_NONE) //rwwFIXMEFIXME: Not sure if this is gonna work, use the PM flags ideally. + {//landed + TIMER_Set( NPC, "forceJumpChasing", 0 ); + } + else + { + NPC_FaceEntity( goal, qtrue ); + //FIXME: push me torward where I was heading + //FIXME: if hit a ledge as soon as we jumped, we need to push toward our goal... must remember original jump dir and/or original jump dest + /* + vec3_t viewangles_xy={0,0,0}, goal_dir, goal_xy_dir, forward, right; + float goal_dist; + + //gert horz dir to goal + VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, goal_dir ); + VectorCopy( goal_dir, goal_xy_dir ); + goal_dist = VectorNormalize( goal_dir ); + goal_xy_dir[2] = 0; + VectorNormalize( goal_xy_dir ); + + //get horz facing + viewangles_xy[1] = NPC->client->ps.viewangles[1]; + AngleVectors( viewangles_xy, forward, right, NULL ); + + //get movement commands to push me toward enemy + float fDot = DotProduct( forward, goal_dir ) * 127; + float rDot = DotProduct( right, goal_dir ) * 127; + + ucmd.forwardmove = floor(fDot); + ucmd.rightmove = floor(rDot); + ucmd.upmove = 0;//don't duck + //Cheat: + if ( goal_dist < 128 && goal->r.currentOrigin[2] > NPC->r.currentOrigin[2] && NPC->client->ps.velocity[2] <= 0 ) + { + NPC->client->ps.velocity[2] += 320; + } + */ + return qtrue; + } + } + return qfalse; +} + +extern void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir ); +static void Jedi_CheckEnemyMovement( float enemy_dist ) +{ + if ( !NPC->enemy || !NPC->enemy->client ) + { + return; + } + + if ( NPC->client->NPC_class != CLASS_TAVION + && NPC->client->NPC_class != CLASS_DESANN + && NPC->client->NPC_class != CLASS_LUKE + && Q_stricmp("Yoda",NPC->NPC_type) ) + { + if ( NPC->enemy->enemy && NPC->enemy->enemy == NPC ) + {//enemy is mad at *me* + if ( NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSLASHDOWN1 || + NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSTABDOWN ) + {//enemy is flipping over me + if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT ) + {//be nice and stand still for him... + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.fd.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + } + } + else if ( NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_BACK1 + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_RIGHT + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_LEFT + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_LEFT_FLIP + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT_FLIP ) + {//he's flipping off a wall + if ( NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//still in air + if ( enemy_dist < 256 ) + {//close + if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT ) + {//be nice and stand still for him... + vec3_t enemyFwd, dest, dir; + + /* + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.fd.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "noturn", Q_irand( 200, 500 ) ); + */ + //stop current movement + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.fd.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "noturn", Q_irand( 250, 500 )*(3-g_spskill.integer) ); + + VectorCopy( NPC->enemy->client->ps.velocity, enemyFwd ); + VectorNormalize( enemyFwd ); + VectorMA( NPC->enemy->r.currentOrigin, -64, enemyFwd, dest ); + VectorSubtract( dest, NPC->r.currentOrigin, dir ); + if ( VectorNormalize( dir ) > 32 ) + { + G_UcmdMoveForDir( NPC, &ucmd, dir ); + } + else + { + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + } + } + } + } + } + else if ( NPC->enemy->client->ps.legsAnim == BOTH_A2_STABBACK1 ) + {//he's stabbing backwards + if ( enemy_dist < 256 && enemy_dist > 64 ) + {//close + if ( !InFront( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, NPC->enemy->r.currentAngles, 0.0f ) ) + {//behind him + if ( !Q_irand( 0, NPCInfo->rank ) ) + {//be nice and stand still for him... + vec3_t enemyFwd, dest, dir; + + //stop current movement + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.fd.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + + AngleVectors( NPC->enemy->r.currentAngles, enemyFwd, NULL, NULL ); + VectorMA( NPC->enemy->r.currentOrigin, -32, enemyFwd, dest ); + VectorSubtract( dest, NPC->r.currentOrigin, dir ); + if ( VectorNormalize( dir ) > 64 ) + { + G_UcmdMoveForDir( NPC, &ucmd, dir ); + } + else + { + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + } + } + } + } + } + } + } + //FIXME: also: + // If enemy doing wall flip, keep running forward + // If enemy doing back-attack and we're behind him keep running forward toward his back, don't strafe +} + +static void Jedi_CheckJumps( void ) +{ + vec3_t jumpVel; + trace_t trace; + trajectory_t tr; + vec3_t lastPos, testPos, bottom; + int elapsedTime; + + if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) ) + { + NPC->client->ps.fd.forceJumpCharge = 0; + ucmd.upmove = 0; + return; + } + //FIXME: should probably check this before AI decides that best move is to jump? Otherwise, they may end up just standing there and looking dumb + //FIXME: all this work and he still jumps off ledges... *sigh*... need CONTENTS_BOTCLIP do-not-enter brushes...? + VectorClear(jumpVel); + + if ( NPC->client->ps.fd.forceJumpCharge ) + { + //Com_Printf( "(%d) force jump\n", level.time ); + WP_GetVelocityForForceJump( NPC, jumpVel, &ucmd ); + } + else if ( ucmd.upmove > 0 ) + { + //Com_Printf( "(%d) regular jump\n", level.time ); + VectorCopy( NPC->client->ps.velocity, jumpVel ); + jumpVel[2] = JUMP_VELOCITY; + } + else + { + return; + } + + //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry... + if ( !jumpVel[0] && !jumpVel[1] )//FIXME: && !ucmd.forwardmove && !ucmd.rightmove? + {//we assume a jump straight up is safe + //Com_Printf( "(%d) jump straight up is safe\n", level.time ); + return; + } + //Now predict where this is going + //in steps, keep evaluating the trajectory until the new z pos is <= than current z pos, trace down from there + + VectorCopy( NPC->r.currentOrigin, tr.trBase ); + VectorCopy( jumpVel, tr.trDelta ); + tr.trType = TR_GRAVITY; + tr.trTime = level.time; + VectorCopy( NPC->r.currentOrigin, lastPos ); + + VectorClear(trace.endpos); //shut the compiler up + + //This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down? + for ( elapsedTime = 500; elapsedTime <= 4000; elapsedTime += 500 ) + { + BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos ); + //FIXME: account for PM_AirMove if ucmd.forwardmove and/or ucmd.rightmove is non-zero... + if ( testPos[2] < lastPos[2] ) + {//going down, don't check for BOTCLIP + trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask );//FIXME: include CONTENTS_BOTCLIP? + } + else + {//going up, check for BOTCLIP + trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + } + if ( trace.allsolid || trace.startsolid ) + {//WTF? + //FIXME: what do we do when we start INSIDE the CONTENTS_BOTCLIP? Do the trace again without that clipmask? + goto jump_unsafe; + return; + } + if ( trace.fraction < 1.0f ) + {//hit something + if ( trace.contents & CONTENTS_BOTCLIP ) + {//hit a do-not-enter brush + goto jump_unsafe; + return; + } + //FIXME: trace through func_glass? + break; + } + VectorCopy( testPos, lastPos ); + } + //okay, reached end of jump, now trace down from here for a floor + VectorCopy( trace.endpos, bottom ); + if ( bottom[2] > NPC->r.currentOrigin[2] ) + {//only care about dist down from current height or lower + bottom[2] = NPC->r.currentOrigin[2]; + } + else if ( NPC->r.currentOrigin[2] - bottom[2] > 400 ) + {//whoa, long drop, don't do it! + //probably no floor at end of jump, so don't jump + goto jump_unsafe; + return; + } + bottom[2] -= 128; + trap_Trace( &trace, trace.endpos, NPC->r.mins, NPC->r.maxs, bottom, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid || trace.startsolid || trace.fraction < 1.0f ) + {//hit ground! + if ( trace.entityNum < ENTITYNUM_WORLD ) + {//landed on an ent + gentity_t *groundEnt = &g_entities[trace.entityNum]; + if ( groundEnt->r.svFlags&SVF_GLASS_BRUSH ) + {//don't land on breakable glass! + goto jump_unsafe; + return; + } + } + //Com_Printf( "(%d) jump is safe\n", level.time ); + return; + } +jump_unsafe: + //probably no floor at end of jump, so don't jump + //Com_Printf( "(%d) unsafe jump cleared\n", level.time ); + NPC->client->ps.fd.forceJumpCharge = 0; + ucmd.upmove = 0; +} + +static void Jedi_Combat( void ) +{ + vec3_t enemy_dir, enemy_movedir, enemy_dest; + float enemy_dist, enemy_movespeed; + qboolean enemy_lost = qfalse; + + //See where enemy will be 300 ms from now + Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 ); + + if ( Jedi_Jumping( NPC->enemy ) ) + {//I'm in the middle of a jump, so just see if I should attack + Jedi_AttackDecide( enemy_dist ); + return; + } + + if ( !(NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 ) + {//not gripping + //If we can't get straight at him + if ( !Jedi_ClearPathToSpot( enemy_dest, NPC->enemy->s.number ) ) + {//hunt him down + //Com_Printf( "No Clear Path\n" ); + if ( (NPC_ClearLOS4( NPC->enemy )||NPCInfo->enemyLastSeenTime>level.time-500) && NPC_FaceEnemy( qtrue ) )//( NPCInfo->rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG ) && + { + //try to jump to him? + /* + vec3_t end; + VectorCopy( NPC->r.currentOrigin, end ); + end[2] += 36; + trap_Trace( &trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0 ) + { + vec3_t angles, forward; + VectorCopy( NPC->client->ps.viewangles, angles ); + angles[0] = 0; + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( end, 64, forward, end ); + trap_Trace( &trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + if ( !trace.allsolid && !trace.startsolid ) + { + if ( trace.fraction >= 1.0 || trace.plane.normal[2] > 0 ) + { + ucmd.upmove = 127; + ucmd.forwardmove = 127; + return; + } + } + } + */ + //FIXME: about every 1 second calc a velocity, + //run a loop of traces with evaluate trajectory + //for gravity with my size, see if it makes it... + //this will also catch misacalculations that send you off ledges! + //Com_Printf( "Considering Jump\n" ); + if ( Jedi_TryJump( NPC->enemy ) ) + {//FIXME: what about jumping to his enemyLastSeenLocation? + return; + } + } + + //Check for evasion + if ( TIMER_Done( NPC, "parryTime" ) ) + {//finished parrying + if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE && + NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//wasn't blocked myself + NPC->client->ps.saberBlocked = BLOCKED_NONE; + } + } + if ( Jedi_Hunt() && !(NPCInfo->aiFlags&NPCAI_BLOCKED) )//FIXME: have to do this because they can ping-pong forever + {//can macro-navigate to him + if ( enemy_dist < 384 && !Q_irand( 0, 10 ) && NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && !NPC_ClearLOS4( NPC->enemy ) ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_JLOST1, EV_JLOST3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + + return; + } + //well, try to head for his last seen location + /* + else if ( Jedi_Track() ) + { + return; + } + */ else + {//FIXME: try to find a waypoint that can see enemy, jump from there + if ( NPCInfo->aiFlags & NPCAI_BLOCKED ) + {//try to jump to the blockedDest + gentity_t *tempGoal = G_Spawn();//ugh, this is NOT good...? + G_SetOrigin( tempGoal, NPCInfo->blockedDest ); + trap_LinkEntity( tempGoal ); + if ( Jedi_TryJump( tempGoal ) ) + {//going to jump to the dest + G_FreeEntity( tempGoal ); + return; + } + G_FreeEntity( tempGoal ); + } + + enemy_lost = qtrue; + } + } + } + //else, we can see him or we can't track him at all + + //every few seconds, decide if we should we advance or retreat? + Jedi_CombatTimersUpdate( enemy_dist ); + + //We call this even if lost enemy to keep him moving and to update the taunting behavior + //maintain a distance from enemy appropriate for our aggression level + Jedi_CombatDistance( enemy_dist ); + + //if ( !enemy_lost ) + { + //Update our seen enemy position + if ( !NPC->enemy->client || ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) ) + { + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + NPCInfo->enemyLastSeenTime = level.time; + } + + //Turn to face the enemy + if ( TIMER_Done( NPC, "noturn" ) ) + { + Jedi_FaceEnemy( qtrue ); + } + NPC_UpdateAngles( qtrue, qtrue ); + + //Check for evasion + if ( TIMER_Done( NPC, "parryTime" ) ) + {//finished parrying + if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE && + NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//wasn't blocked myself + NPC->client->ps.saberBlocked = BLOCKED_NONE; + } + } + if ( NPC->enemy->s.weapon == WP_SABER ) + { + Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir ); + } + else + {//do we need to do any evasion for other kinds of enemies? + } + + //apply strafing/walking timers, etc. + Jedi_TimersApply(); + + if ( !NPC->client->ps.saberInFlight && (!(NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2) ) + {//not throwing saber or using force grip + //see if we can attack + if ( !Jedi_AttackDecide( enemy_dist ) ) + {//we're not attacking, decide what else to do + Jedi_CombatIdle( enemy_dist ); + //FIXME: lower aggression when actually strike offensively? Or just when do damage? + } + else + {//we are attacking + //stop taunting + TIMER_Set( NPC, "taunting", -level.time ); + } + } + else + { + } + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + Boba_FireDecide(); + } + + //Check for certain enemy special moves + Jedi_CheckEnemyMovement( enemy_dist ); + //Make sure that we don't jump off ledges over long drops + Jedi_CheckJumps(); + //Just make sure we don't strafe into walls or off cliffs + if ( !NPC_MoveDirClear( ucmd.forwardmove, ucmd.rightmove, qtrue ) ) + {//uh-oh, we are going to fall or hit something + navInfo_t info; + //Get the move info + NAV_GetLastMove( &info ); + if ( !(info.flags & NIF_MACRO_NAV) ) + {//micro-navigation told us to step off a ledge, try using macronav for now + NPC_MoveToGoal( qfalse ); + } + //reset the timers. + TIMER_Set( NPC, "strafeLeft", 0 ); + TIMER_Set( NPC, "strafeRight", 0 ); + } +} + +/* +========================================================================================== +EXTERNALLY CALLED BEHAVIOR STATES +========================================================================================== +*/ + +/* +------------------------- +NPC_Jedi_Pain +------------------------- +*/ + +void NPC_Jedi_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + gentity_t *other = attacker; + vec3_t point; + + VectorCopy(gPainPoint, point); + + //FIXME: base the actual aggression add/subtract on health? + //FIXME: don't do this more than once per frame? + //FIXME: when take pain, stop force gripping....? + if ( other->s.weapon == WP_SABER ) + {//back off + TIMER_Set( self, "parryTime", -1 ); + if ( self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) ) + {//less for Desann + self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill.integer)*50; + } + else if ( self->NPC->rank >= RANK_LT_JG ) + { + self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill.integer)*100;//300 + } + else + { + self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill.integer)*200;//500 + } + if ( !Q_irand( 0, 3 ) ) + {//ouch... maybe switch up which saber power level we're using + Jedi_AdjustSaberAnimLevel( self, Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 ) ); + } + if ( !Q_irand( 0, 1 ) )//damage > 20 || self->health < 40 || + { + //Com_Printf( "(%d) drop agg - hit by saber\n", level.time ); + Jedi_Aggression( self, -1 ); + } + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) PAIN: agg %d, no parry until %d\n", level.time, self->NPC->stats.aggression, level.time+500 ); + } + //for testing only + // Figure out what quadrant the hit was in. + if ( d_JediAI.integer ) + { + vec3_t diff, fwdangles, right; + float rightdot, zdiff; + + VectorSubtract( point, self->client->renderInfo.eyePoint, diff ); + diff[2] = 0; + fwdangles[1] = self->client->ps.viewangles[1]; + AngleVectors( fwdangles, NULL, right, NULL ); + rightdot = DotProduct(right, diff); + zdiff = point[2] - self->client->renderInfo.eyePoint[2]; + + Com_Printf( "(%d) saber hit at height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, point[2]-self->r.absmin[2],zdiff,rightdot); + } + } + else + {//attack + //Com_Printf( "(%d) raise agg - hit by ranged\n", level.time ); + Jedi_Aggression( self, 1 ); + } + + self->NPC->enemyCheckDebounceTime = 0; + + WP_ForcePowerStop( self, FP_GRIP ); + + //NPC_Pain( self, inflictor, other, point, damage, mod ); + NPC_Pain(self, attacker, damage); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } + + //drop me from the ceiling if I'm on it + if ( Jedi_WaitingAmbush( self ) ) + { + self->client->noclip = qfalse; + } + if ( self->client->ps.legsAnim == BOTH_CEILING_CLING ) + { + NPC_SetAnim( self, SETANIM_LEGS, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( self->client->ps.torsoAnim == BOTH_CEILING_CLING ) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } +} + +qboolean Jedi_CheckDanger( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR ); + if ( level.alertEvents[alertEvent].level >= AEL_DANGER ) + {//run away! + if ( !level.alertEvents[alertEvent].owner + || !level.alertEvents[alertEvent].owner->client + || (level.alertEvents[alertEvent].owner!=NPC&&level.alertEvents[alertEvent].owner->client->playerTeam!=NPC->client->playerTeam) ) + {//no owner + return qfalse; + } + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + return qfalse; +} + +qboolean Jedi_CheckAmbushPlayer( void ) +{ + int i = 0; + gentity_t *player; + float target_dist; + float zDiff; + + for ( i = 0; i < MAX_CLIENTS; i++ ) + { + player = &g_entities[i]; + + if ( !player || !player->client ) + { + continue; + } + + if ( !NPC_ValidEnemy( player ) ) + { + continue; + } + +// if ( NPC->client->ps.powerups[PW_CLOAKED] || g_crosshairEntNum != NPC->s.number ) + if (NPC->client->ps.powerups[PW_CLOAKED] || !NPC_SomeoneLookingAtMe(NPC)) //rwwFIXMEFIXME: Need to pay attention to who is under crosshair for each player or something. + {//if I'm not cloaked and the player's crosshair is on me, I will wake up, otherwise do this stuff down here... + if ( !trap_InPVS( player->r.currentOrigin, NPC->r.currentOrigin ) ) + {//must be in same room + continue; + } + else + { + if ( !NPC->client->ps.powerups[PW_CLOAKED] ) + { + NPC_SetLookTarget( NPC, 0, 0 ); + } + } + zDiff = NPC->r.currentOrigin[2]-player->r.currentOrigin[2]; + if ( zDiff <= 0 || zDiff > 512 ) + {//never ambush if they're above me or way way below me + continue; + } + + //If the target is this close, then wake up regardless + if ( (target_dist = DistanceHorizontalSquared( player->r.currentOrigin, NPC->r.currentOrigin )) > 4096 ) + {//closer than 64 - always ambush + if ( target_dist > 147456 ) + {//> 384, not close enough to ambush + continue; + } + //Check FOV first + if ( NPC->client->ps.powerups[PW_CLOAKED] ) + { + if ( InFOV( player, NPC, 30, 90 ) == qfalse ) + { + continue; + } + } + else + { + if ( InFOV( player, NPC, 45, 90 ) == qfalse ) + { + continue; + } + } + } + + if ( !NPC_ClearLOS4( player ) ) + { + continue; + } + } + + //Got him, return true; + G_SetEnemy( NPC, player ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + //Didn't get anyone. + return qfalse; +} + +void Jedi_Ambush( gentity_t *self ) +{ + self->client->noclip = qfalse; +// self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.weaponTime = self->client->ps.torsoTimer; //NPC->client->ps.torsoTimer; //what the? + if ( self->client->NPC_class != CLASS_BOBAFETT ) + { + WP_ActivateSaber(self); + } + Jedi_Decloak( self ); + G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 1000 ); +} + +qboolean Jedi_WaitingAmbush( gentity_t *self ) +{ + if ( (self->spawnflags&JSF_AMBUSH) && self->client->noclip ) + { + return qtrue; + } + return qfalse; +} +/* +------------------------- +Jedi_Patrol +------------------------- +*/ + +static void Jedi_Patrol( void ) +{ + NPC->client->ps.saberBlocked = BLOCKED_NONE; + + if ( Jedi_WaitingAmbush( NPC ) ) + {//hiding on the ceiling + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_CEILING_CLING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + {//look for enemies + if ( Jedi_CheckAmbushPlayer() || Jedi_CheckDanger() ) + {//found him! + Jedi_Ambush( NPC ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + else if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )//NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + {//look for enemies + gentity_t *best_enemy = NULL; + float best_enemy_dist = Q3_INFINITE; + int i; + for ( i = 0; i < ENTITYNUM_WORLD; i++ ) + { + gentity_t *enemy = &g_entities[i]; + float enemy_dist; + if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam ) + { + if ( trap_InPVS( NPC->r.currentOrigin, enemy->r.currentOrigin ) ) + {//we could potentially see him + enemy_dist = DistanceSquared( NPC->r.currentOrigin, enemy->r.currentOrigin ); + if ( enemy->s.eType == ET_PLAYER || enemy_dist < best_enemy_dist ) + { + //if the enemy is close enough, or threw his saber, take him as the enemy + //FIXME: what if he throws a thermal detonator? + if ( enemy_dist < (220*220) || ( NPCInfo->investigateCount>= 3 && !NPC->client->ps.saberHolstered ) ) + { + G_SetEnemy( NPC, enemy ); + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + NPCInfo->stats.aggression = 3; + break; + } + else if ( enemy->client->ps.saberInFlight && !enemy->client->ps.saberHolstered ) + {//threw his saber, see if it's heading toward me and close enough to consider a threat + float saberDist; + vec3_t saberDir2Me; + vec3_t saberMoveDir; + gentity_t *saber = &g_entities[enemy->client->ps.saberEntityNum]; + VectorSubtract( NPC->r.currentOrigin, saber->r.currentOrigin, saberDir2Me ); + saberDist = VectorNormalize( saberDir2Me ); + VectorCopy( saber->s.pos.trDelta, saberMoveDir ); + VectorNormalize( saberMoveDir ); + if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 ) + {//it's heading towards me + if ( saberDist < 200 ) + {//incoming! + G_SetEnemy( NPC, enemy ); + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + NPCInfo->stats.aggression = 3; + break; + } + } + } + best_enemy_dist = enemy_dist; + best_enemy = enemy; + } + } + } + } + if ( !NPC->enemy ) + {//still not mad + if ( !best_enemy ) + { + //Com_Printf( "(%d) drop agg - no enemy (patrol)\n", level.time ); + Jedi_AggressionErosion(-1); + //FIXME: what about alerts? But not if ignore alerts + } + else + {//have one to consider + if ( NPC_ClearLOS4( best_enemy ) ) + {//we have a clear (of architecture) LOS to him + if ( best_enemy->s.number ) + {//just attack + G_SetEnemy( NPC, best_enemy ); + NPCInfo->stats.aggression = 3; + } + else if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + {//the player, toy with him + //get progressively more interested over time + if ( TIMER_Done( NPC, "watchTime" ) ) + {//we want to pick him up in stages + if ( TIMER_Get( NPC, "watchTime" ) == -1 ) + {//this is the first time, we'll ignore him for a couple seconds + TIMER_Set( NPC, "watchTime", Q_irand( 3000, 5000 ) ); + goto finish; + } + else + {//okay, we've ignored him, now start to notice him + if ( !NPCInfo->investigateCount ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_JDETECTED1, EV_JDETECTED3 ), 3000 ); + } + NPCInfo->investigateCount++; + TIMER_Set( NPC, "watchTime", Q_irand( 4000, 10000 ) ); + } + } + //while we're waiting, do what we need to do + if ( best_enemy_dist < (440*440) || NPCInfo->investigateCount >= 2 ) + {//stage three: keep facing him + NPC_FaceEntity( best_enemy, qtrue ); + if ( best_enemy_dist < (330*330) ) + {//stage four: turn on the saber + if ( !NPC->client->ps.saberInFlight ) + { + WP_ActivateSaber(NPC); + } + } + } + else if ( best_enemy_dist < (550*550) || NPCInfo->investigateCount == 1 ) + {//stage two: stop and face him every now and then + if ( TIMER_Done( NPC, "watchTime" ) ) + { + NPC_FaceEntity( best_enemy, qtrue ); + } + } + else + {//stage one: look at him. + NPC_SetLookTarget( NPC, best_enemy->s.number, 0 ); + } + } + } + else if ( TIMER_Done( NPC, "watchTime" ) ) + {//haven't seen him in a bit, clear the lookTarget + NPC_ClearLookTarget( NPC ); + } + } + } + } +finish: + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + //Jedi_Move( NPCInfo->goalEntity ); + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); + + if ( NPC->enemy ) + {//just picked one up + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + } +} + +qboolean Jedi_CanPullBackSaber( gentity_t *self ) +{ + if ( self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN && !TIMER_Done( self, "parryTime" ) ) + { + return qfalse; + } + + if ( self->client->NPC_class == CLASS_SHADOWTROOPER + || self->client->NPC_class == CLASS_TAVION + || self->client->NPC_class == CLASS_LUKE + || self->client->NPC_class == CLASS_DESANN + || !Q_stricmp( "Yoda", self->NPC_type ) ) + { + return qtrue; + } + + if ( self->painDebounceTime > level.time )//|| self->client->ps.weaponTime > 0 ) + { + return qfalse; + } + + return qtrue; +} +/* +------------------------- +NPC_BSJedi_FollowLeader +------------------------- +*/ +void NPC_BSJedi_FollowLeader( void ) +{ + NPC->client->ps.saberBlocked = BLOCKED_NONE; + if ( !NPC->enemy ) + { + //Com_Printf( "(%d) drop agg - no enemy (follow)\n", level.time ); + Jedi_AggressionErosion(-1); + } + + //did we drop our saber? If so, go after it! + if ( NPC->client->ps.saberInFlight ) + {//saber is not in hand + if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0 + {// + if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY ) + {//fell to the ground, try to pick it up... + if ( Jedi_CanPullBackSaber( NPC ) ) + { + //FIXME: if it's on the ground and we just pulled it back to us, should we + // stand still for a bit to make sure it gets to us...? + // otherwise we could end up running away from it while it's on its + // way back to us and we could lose it again. + NPC->client->ps.saberBlocked = BLOCKED_NONE; + NPCInfo->goalEntity = &g_entities[NPC->client->ps.saberEntityNum]; + ucmd.buttons |= BUTTON_ATTACK; + if ( NPC->enemy && NPC->enemy->health > 0 ) + {//get our saber back NOW! + if ( !NPC_MoveToGoal( qtrue ) )//Jedi_Move( NPCInfo->goalEntity, qfalse ); + {//can't nav to it, try jumping to it + NPC_FaceEntity( NPCInfo->goalEntity, qtrue ); + Jedi_TryJump( NPCInfo->goalEntity ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + } + } + + if ( NPCInfo->goalEntity ) + { + trace_t trace; + + if ( Jedi_Jumping( NPCInfo->goalEntity ) ) + {//in mid-jump + return; + } + + if ( !NAV_CheckAhead( NPC, NPCInfo->goalEntity->r.currentOrigin, &trace, ( NPC->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) ) + {//can't get straight to him + if ( NPC_ClearLOS4( NPCInfo->goalEntity ) && NPC_FaceEntity( NPCInfo->goalEntity, qtrue ) ) + {//no line of sight + if ( Jedi_TryJump( NPCInfo->goalEntity ) ) + {//started a jump + return; + } + } + } + if ( NPCInfo->aiFlags & NPCAI_BLOCKED ) + {//try to jump to the blockedDest + if ( fabs(NPCInfo->blockedDest[2]-NPC->r.currentOrigin[2]) > 64 ) + { + gentity_t *tempGoal = G_Spawn();//ugh, this is NOT good...? + G_SetOrigin( tempGoal, NPCInfo->blockedDest ); + trap_LinkEntity( tempGoal ); + TIMER_Set( NPC, "jumpChaseDebounce", -1 ); + if ( Jedi_TryJump( tempGoal ) ) + {//going to jump to the dest + G_FreeEntity( tempGoal ); + return; + } + G_FreeEntity( tempGoal ); + } + } + } + //try normal movement + NPC_BSFollowLeader(); +} + + +/* +------------------------- +Jedi_Attack +------------------------- +*/ + +static void Jedi_Attack( void ) +{ + //Don't do anything if we're in a pain anim + if ( NPC->painDebounceTime > level.time ) + { + if ( Q_irand( 0, 1 ) ) + { + Jedi_FaceEnemy( qtrue ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( NPC->client->ps.saberLockTime > level.time ) + { + //FIXME: maybe if I'm losing I should try to force-push out of it? Very rarely, though... + if ( NPC->client->ps.fd.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 + && NPC->client->ps.saberLockTime < level.time + 5000 + && !Q_irand( 0, 10 )) + { + ForceThrow( NPC, qfalse ); + } + //based on my skill, hit attack button every other to every several frames in order to push enemy back + else + { + float chance; + + if ( NPC->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",NPC->NPC_type) ) + { + if ( g_spskill.integer ) + { + chance = 4.0f;//he pushes *hard* + } + else + { + chance = 3.0f;//he pushes *hard* + } + } + else if ( NPC->client->NPC_class == CLASS_TAVION ) + { + chance = 2.0f+g_spskill.value;//from 2 to 4 + } + else + {//the escalation in difficulty is nice, here, but cap it so it doesn't get *impossible* on hard + float maxChance = (float)(RANK_LT)/2.0f+3.0f;//5? + if ( !g_spskill.value ) + { + chance = (float)(NPCInfo->rank)/2.0f; + } + else + { + chance = (float)(NPCInfo->rank)/2.0f+1.0f; + } + if ( chance > maxChance ) + { + chance = maxChance; + } + } + // if ( flrand( -4.0f, chance ) >= 0.0f && !(NPC->client->ps.pm_flags&PMF_ATTACK_HELD) ) + // { + // ucmd.buttons |= BUTTON_ATTACK; + // } + if ( flrand( -4.0f, chance ) >= 0.0f ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + //rwwFIXMEFIXME: support for PMF_ATTACK_HELD + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + //did we drop our saber? If so, go after it! + if ( NPC->client->ps.saberInFlight ) + {//saber is not in hand + // if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0 + if (!NPC->client->ps.saberEntityNum && NPC->client->saberStoredIndex) //this is valid, it's 0 when our saber is gone -rww (mp-specific) + {// + //if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY ) + if (1) //no matter + {//fell to the ground, try to pick it up + // if ( Jedi_CanPullBackSaber( NPC ) ) + if (1) //no matter + { + NPC->client->ps.saberBlocked = BLOCKED_NONE; + NPCInfo->goalEntity = &g_entities[NPC->client->saberStoredIndex]; + ucmd.buttons |= BUTTON_ATTACK; + if ( NPC->enemy && NPC->enemy->health > 0 ) + {//get our saber back NOW! + Jedi_Move( NPCInfo->goalEntity, qfalse ); + NPC_UpdateAngles( qtrue, qtrue ); + if ( NPC->enemy->s.weapon == WP_SABER ) + {//be sure to continue evasion + vec3_t enemy_dir, enemy_movedir, enemy_dest; + float enemy_dist, enemy_movespeed; + Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 ); + Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir ); + } + return; + } + } + } + } + } + //see if our enemy was killed by us, gloat and turn off saber after cool down. + //FIXME: don't do this if we have other enemies to fight...? + if ( NPC->enemy ) + { + if ( NPC->enemy->health <= 0 && NPC->enemy->enemy == NPC && NPC->client->playerTeam != NPCTEAM_PLAYER )//good guys don't gloat + {//my enemy is dead and I killed him + NPCInfo->enemyCheckDebounceTime = 0;//keep looking for others + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( NPCInfo->walkDebounceTime < level.time && NPCInfo->walkDebounceTime >= 0 ) + { + TIMER_Set( NPC, "gloatTime", 10000 ); + NPCInfo->walkDebounceTime = -1; + } + if ( !TIMER_Done( NPC, "gloatTime" ) ) + { + if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared + { + NPCInfo->goalEntity = NPC->enemy; + Jedi_Move( NPC->enemy, qfalse ); + ucmd.buttons |= BUTTON_WALKING; + } + else + { + TIMER_Set( NPC, "gloatTime", 0 ); + } + } + else if ( NPCInfo->walkDebounceTime == -1 ) + { + NPCInfo->walkDebounceTime = -2; + G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = level.time + 3000; + NPCInfo->desiredPitch = 0; + NPCInfo->goalEntity = NULL; + } + Jedi_FaceEnemy( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + { + if ( !TIMER_Done( NPC, "parryTime" ) ) + { + TIMER_Set( NPC, "parryTime", -1 ); + NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + } + NPC->client->ps.saberBlocked = BLOCKED_NONE; + if ( !NPC->client->ps.saberHolstered && NPC->client->ps.saberInFlight ) + {//saber is still on (or we're trying to pull it back), count down erosion and keep facing the enemy + //FIXME: need to stop this from happening over and over again when they're blocking their victim's saber + //FIXME: turn off saber sooner so we get cool walk anim? + //Com_Printf( "(%d) drop agg - enemy dead\n", level.time ); + Jedi_AggressionErosion(-3); + if ( BG_SabersOff( &NPC->client->ps ) && !NPC->client->ps.saberInFlight ) + {//turned off saber (in hand), gloat + G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = level.time + 3000; + NPCInfo->desiredPitch = 0; + NPCInfo->goalEntity = NULL; + } + TIMER_Set( NPC, "gloatTime", 10000 ); + } + if ( !NPC->client->ps.saberHolstered || NPC->client->ps.saberInFlight || !TIMER_Done( NPC, "gloatTime" ) ) + {//keep walking + if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared + { + NPCInfo->goalEntity = NPC->enemy; + Jedi_Move( NPC->enemy, qfalse ); + ucmd.buttons |= BUTTON_WALKING; + } + else + {//got there + if ( NPC->health < NPC->client->pers.maxHealth + && (NPC->client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1<enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) + { + if ( NPC->enemy->count <= 0 ) + {//it's out of ammo + if ( NPC->enemy->activator && NPC_ValidEnemy( NPC->enemy->activator ) ) + { + gentity_t *turretOwner = NPC->enemy->activator; + G_ClearEnemy( NPC ); + G_SetEnemy( NPC, turretOwner ); + } + else + { + G_ClearEnemy( NPC ); + } + } + } + NPC_CheckEnemy( qtrue, qtrue, qtrue ); + + if ( !NPC->enemy ) + { + NPC->client->ps.saberBlocked = BLOCKED_NONE; + if ( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + {//lost him, go back to what we were doing before + NPCInfo->tempBehavior = BS_DEFAULT; + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + Jedi_Patrol();//was calling Idle... why? + return; + } + + //always face enemy if have one + NPCInfo->combatMove = qtrue; + + //Track the player and kill them if possible + Jedi_Combat(); + + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) + || ((NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_HEAL] 0 ) + { + ucmd.upmove = 0; + } + NPC->client->ps.fd.forceJumpCharge = 0; + VectorClear( NPC->client->ps.moveDir ); + } + + //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry... + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//don't push while in air, throws off jumps! + //FIXME: if we are in the air over a drop near a ledge, should we try to push back towards the ledge? + ucmd.forwardmove = 0; + ucmd.rightmove = 0; + VectorClear( NPC->client->ps.moveDir ); + } + + if ( !TIMER_Done( NPC, "duck" ) ) + { + ucmd.upmove = -127; + } + + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove ) || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN ) + {//just make sure they don't pull their saber to them if they're being blocked + ucmd.buttons &= ~BUTTON_ATTACK; + } + } + + if( (NPCInfo->scriptFlags&SCF_DONT_FIRE) //not allowed to attack + || ((NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_HEAL]client->ps.saberEventFlags&SEF_INWATER)&&!NPC->client->ps.saberInFlight) )//saber in water + { + ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); + } + + if ( NPCInfo->scriptFlags&SCF_NO_ACROBATICS ) + { + ucmd.upmove = 0; + NPC->client->ps.fd.forceJumpCharge = 0; + } + + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + Jedi_CheckDecreaseSaberAnimLevel(); + } + + if ( ucmd.buttons & BUTTON_ATTACK && NPC->client->playerTeam == NPCTEAM_ENEMY ) + { + if ( Q_irand( 0, NPC->client->ps.fd.saberAnimLevel ) > 0 + && Q_irand( 0, NPC->client->pers.maxHealth+10 ) > NPC->health + && !Q_irand( 0, 3 )) + {//the more we're hurt and the stronger the attack we're using, the more likely we are to make a anger noise when we swing + G_AddVoiceEvent( NPC, Q_irand( EV_COMBAT1, EV_COMBAT3 ), 1000 ); + } + } + + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + if ( NPC->client->NPC_class == CLASS_TAVION + || (g_spskill.integer && ( NPC->client->NPC_class == CLASS_DESANN || NPCInfo->rank >= Q_irand( RANK_CREWMAN, RANK_CAPTAIN )))) + {//Tavion will kick in force speed if the player does... + if ( NPC->enemy + && !NPC->enemy->s.number + && NPC->enemy->client + && (NPC->enemy->client->ps.fd.forcePowersActive & (1<client->ps.fd.forcePowersActive & (1<enemy ) + {//don't have an enemy, look for one + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + NPC_BSST_Patrol(); + } + else + { + Jedi_Patrol(); + } + } + else//if ( NPC->enemy ) + {//have an enemy + if ( Jedi_WaitingAmbush( NPC ) ) + {//we were still waiting to drop down - must have had enemy set on me outside my AI + Jedi_Ambush( NPC ); + } + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( NPC->enemy->enemy != NPC && NPC->health == NPC->client->pers.maxHealth && DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin )>(800*800) ) + { + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + Boba_ChangeWeapon( WP_DISRUPTOR ); + NPC_BSSniper_Default(); + return; + } + } + Jedi_Attack(); + //if we have multiple-jedi combat, probably need to keep checking (at certain debounce intervals) for a better (closer, more active) enemy and switch if needbe... + if ( ((!ucmd.buttons&&!NPC->client->ps.fd.forcePowersActive)||(NPC->enemy&&NPC->enemy->health<=0)) && NPCInfo->enemyCheckDebounceTime < level.time ) + {//not doing anything (or walking toward a vanquished enemy - fixme: always taunt the player?), not using force powers and it's time to look again + //FIXME: build a list of all local enemies (since we have to find best anyway) for other AI factors- like when to use group attacks, determine when to change tactics, when surrounded, when blocked by another in the enemy group, etc. Should we build this group list or let the enemies maintain their own list and we just access it? + gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? + gentity_t *newEnemy; + + NPC->enemy = NULL; + newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); + NPC->enemy = sav_enemy; + if ( newEnemy && newEnemy != sav_enemy ) + {//picked up a new enemy! + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, newEnemy ); + } + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 3000 ); + } + } +} diff --git a/codemp/game/NPC_AI_MineMonster.c b/codemp/game/NPC_AI_MineMonster.c new file mode 100644 index 0000000..edd1f22 --- /dev/null +++ b/codemp/game/NPC_AI_MineMonster.c @@ -0,0 +1,278 @@ +#include "b_local.h" + +// These define the working combat range for these suckers +#define MIN_DISTANCE 54 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 128 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +/* +------------------------- +NPC_MineMonster_Precache +------------------------- +*/ +void NPC_MineMonster_Precache( void ) +{ + int i; + + for ( i = 0; i < 4; i++ ) + { + G_SoundIndex( va("sound/chars/mine/misc/bite%i.wav", i+1 )); + G_SoundIndex( va("sound/chars/mine/misc/miss%i.wav", i+1 )); + } +} + + +/* +------------------------- +MineMonster_Idle +------------------------- +*/ +void MineMonster_Idle( void ) +{ + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } +} + + +/* +------------------------- +MineMonster_Patrol +------------------------- +*/ +void MineMonster_Patrol( void ) +{ + vec3_t dif; + + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + else + { + if ( TIMER_Done( NPC, "patrolTime" )) + { + TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 ); + } + } + + //rwwFIXMEFIXME: Care about all clients, not just client 0 + VectorSubtract( g_entities[0].r.currentOrigin, NPC->r.currentOrigin, dif ); + + if ( VectorLengthSquared( dif ) < 256 * 256 ) + { + G_SetEnemy( NPC, &g_entities[0] ); + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + MineMonster_Idle(); + return; + } +} + +/* +------------------------- +MineMonster_Move +------------------------- +*/ +void MineMonster_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + NPC_MoveToGoal( qtrue ); + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + } +} + +//--------------------------------------------------------- +void MineMonster_TryDamage( gentity_t *enemy, int damage ) +{ + vec3_t end, dir; + trace_t tr; + + if ( !enemy ) + { + return; + } + + AngleVectors( NPC->client->ps.viewangles, dir, NULL, NULL ); + VectorMA( NPC->r.currentOrigin, MIN_DISTANCE, dir, end ); + + // Should probably trace from the mouth, but, ah well. + trap_Trace( &tr, NPC->r.currentOrigin, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + + if ( tr.entityNum >= 0 && tr.entityNum < ENTITYNUM_NONE ) + { + G_Damage( &g_entities[tr.entityNum], NPC, NPC, dir, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + G_Sound( NPC, CHAN_AUTO, G_EffectIndex(va("sound/chars/mine/misc/bite%i.wav", Q_irand(1,4)))); + } + else + { + G_Sound( NPC, CHAN_AUTO, G_EffectIndex(va("sound/chars/mine/misc/miss%i.wav", Q_irand(1,4)))); + } +} + +//------------------------------ +void MineMonster_Attack( void ) +{ + if ( !TIMER_Exists( NPC, "attacking" )) + { + // usually try and play a jump attack if the player somehow got above them....or just really rarely + if ( NPC->enemy && ((NPC->enemy->r.currentOrigin[2] - NPC->r.currentOrigin[2] > 10 && random() > 0.1f ) + || random() > 0.8f )) + { + // Going to do ATTACK4 + TIMER_Set( NPC, "attacking", 1750 + random() * 200 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK4, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack2_dmg", 950 ); // level two damage + } + else if ( random() > 0.5f ) + { + if ( random() > 0.8f ) + { + // Going to do ATTACK3, (rare) + TIMER_Set( NPC, "attacking", 850 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack2_dmg", 400 ); // level two damage + } + else + { + // Going to do ATTACK1 + TIMER_Set( NPC, "attacking", 850 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack1_dmg", 450 ); // level one damage + } + } + else + { + // Going to do ATTACK2 + TIMER_Set( NPC, "attacking", 1250 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack1_dmg", 700 ); // level one damage + } + } + else + { + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + if ( TIMER_Done2( NPC, "attack1_dmg", qtrue )) + { + MineMonster_TryDamage( NPC->enemy, 5 ); + } + else if ( TIMER_Done2( NPC, "attack2_dmg", qtrue )) + { + MineMonster_TryDamage( NPC->enemy, 10 ); + } + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); +} + +//---------------------------------- +void MineMonster_Combat( void ) +{ + float distance; + qboolean advance; + + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS4( NPC->enemy ) || UpdateGoal( )) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + + NPC_MoveToGoal( qtrue ); + return; + } + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + distance = DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + + advance = (qboolean)( distance > MIN_DISTANCE_SQR ? qtrue : qfalse ); + + if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + MineMonster_Move( 1 ); + } + } + else + { + MineMonster_Attack(); + } +} + +/* +------------------------- +NPC_MineMonster_Pain +------------------------- +*/ +void NPC_MineMonster_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + G_AddEvent( self, EV_PAIN, floor((float)self->health/self->client->pers.maxHealth*100.0f) ); + + if ( damage >= 10 ) + { + TIMER_Remove( self, "attacking" ); + TIMER_Remove( self, "attacking1_dmg" ); + TIMER_Remove( self, "attacking2_dmg" ); + TIMER_Set( self, "takingPain", 1350 ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } +} + + +/* +------------------------- +NPC_BSMineMonster_Default +------------------------- +*/ +void NPC_BSMineMonster_Default( void ) +{ + if ( NPC->enemy ) + { + MineMonster_Combat(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + MineMonster_Patrol(); + } + else + { + MineMonster_Idle(); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/codemp/game/NPC_AI_Rancor.c b/codemp/game/NPC_AI_Rancor.c new file mode 100644 index 0000000..448e466 --- /dev/null +++ b/codemp/game/NPC_AI_Rancor.c @@ -0,0 +1,961 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" + +extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex ); + +// These define the working combat range for these suckers +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 1024 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +void Rancor_SetBolts( gentity_t *self ) +{ + if ( self && self->client ) + { + renderInfo_t *ri = &self->client->renderInfo; + ri->handRBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*r_hand" ); + ri->handLBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_hand" ); + ri->headBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*head_eyes" ); + ri->torsoBolt = trap_G2API_AddBolt( self->ghoul2, 0, "jaw_bone" ); + } +} + +/* +------------------------- +NPC_Rancor_Precache +------------------------- +*/ +void NPC_Rancor_Precache( void ) +{ + int i; + for ( i = 1; i < 3; i ++ ) + { + G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", i) ); + } + G_SoundIndex( "sound/chars/rancor/swipehit.wav" ); + G_SoundIndex( "sound/chars/rancor/chomp.wav" ); +} + + +/* +------------------------- +Rancor_Idle +------------------------- +*/ +void Rancor_Idle( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } +} + + +qboolean Rancor_CheckRoar( gentity_t *self ) +{ + if ( !self->wait ) + {//haven't ever gotten mad yet + self->wait = 1;//do this only once + self->client->ps.eFlags2 |= EF2_ALERTED; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( self, "rageTime", self->client->ps.legsTimer ); + return qtrue; + } + return qfalse; +} +/* +------------------------- +Rancor_Patrol +------------------------- +*/ +void Rancor_Patrol( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + else + { + if ( TIMER_Done( NPC, "patrolTime" )) + { + TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 ); + } + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Rancor_Idle(); + return; + } + Rancor_CheckRoar( NPC ); + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); +} + +/* +------------------------- +Rancor_Move +------------------------- +*/ +void Rancor_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + if ( !NPC_MoveToGoal( qtrue ) ) + { + NPCInfo->consecutiveBlockedMoves++; + } + else + { + NPCInfo->consecutiveBlockedMoves = 0; + } + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + } +} + +//--------------------------------------------------------- +//extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_Knockdown( gentity_t *victim ); +extern void G_Dismember( gentity_t *ent, gentity_t *enemy, vec3_t point, int limbType, float limbRollBase, float limbPitchBase, int deathAnim, qboolean postDeath ); +//extern qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force ); +extern float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex ); +extern int NPC_GetEntsNearBolt( int *radiusEnts, float radius, int boltIndex, vec3_t boltOrg ); + +void Rancor_DropVictim( gentity_t *self ) +{ + //FIXME: if Rancor dies, it should drop its victim. + //FIXME: if Rancor is removed, it must remove its victim. + if ( self->activator ) + { + if ( self->activator->client ) + { + self->activator->client->ps.eFlags2 &= ~EF2_HELD_BY_MONSTER; + self->activator->client->ps.hasLookTarget = qfalse; + self->activator->client->ps.lookTarget = ENTITYNUM_NONE; + self->activator->client->ps.viewangles[ROLL] = 0; + SetClientViewAngle( self->activator, self->activator->client->ps.viewangles ); + self->activator->r.currentAngles[PITCH] = self->activator->r.currentAngles[ROLL] = 0; + G_SetAngles( self->activator, self->activator->r.currentAngles ); + } + if ( self->activator->health <= 0 ) + { + //if ( self->activator->s.number ) + {//never free player + if ( self->count == 1 ) + {//in my hand, just drop them + if ( self->activator->client ) + { + self->activator->client->ps.legsTimer = self->activator->client->ps.torsoTimer = 0; + //FIXME: ragdoll? + } + } + else + { + if ( self->activator->client ) + { + self->activator->client->ps.eFlags |= EF_NODRAW;//so his corpse doesn't drop out of me... + } + //G_FreeEntity( self->activator ); + } + } + } + else + { + if ( self->activator->NPC ) + {//start thinking again + self->activator->NPC->nextBStateThink = level.time; + } + //clear their anim and let them fall + self->activator->client->ps.legsTimer = self->activator->client->ps.torsoTimer = 0; + } + if ( self->enemy == self->activator ) + { + self->enemy = NULL; + } + self->activator = NULL; + } + self->count = 0;//drop him +} + +void Rancor_Swing( qboolean tryGrab ) +{ + int radiusEntNums[128]; + int numEnts; + const float radius = 88; + const float radiusSquared = (radius*radius); + int i; + vec3_t boltOrg; + + numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.handRBolt, boltOrg ); + + for ( i = 0; i < numEnts; i++ ) + { + gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; + if ( !radiusEnt->inuse ) + { + continue; + } + + if ( radiusEnt == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnt->client == NULL ) + {//must be a client + continue; + } + + if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//can't be one already being held + continue; + } + + if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared ) + { + if ( tryGrab + && NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth! + && radiusEnt->client->NPC_class != CLASS_RANCOR + && radiusEnt->client->NPC_class != CLASS_GALAKMECH + && radiusEnt->client->NPC_class != CLASS_ATST + && radiusEnt->client->NPC_class != CLASS_GONK + && radiusEnt->client->NPC_class != CLASS_R2D2 + && radiusEnt->client->NPC_class != CLASS_R5D2 + && radiusEnt->client->NPC_class != CLASS_MARK1 + && radiusEnt->client->NPC_class != CLASS_MARK2 + && radiusEnt->client->NPC_class != CLASS_MOUSE + && radiusEnt->client->NPC_class != CLASS_PROBE + && radiusEnt->client->NPC_class != CLASS_SEEKER + && radiusEnt->client->NPC_class != CLASS_REMOTE + && radiusEnt->client->NPC_class != CLASS_SENTRY + && radiusEnt->client->NPC_class != CLASS_INTERROGATOR + && radiusEnt->client->NPC_class != CLASS_VEHICLE ) + {//grab + if ( NPC->count == 2 ) + {//have one in my mouth, remove him + TIMER_Remove( NPC, "clearGrabbed" ); + Rancor_DropVictim( NPC ); + } + NPC->enemy = radiusEnt;//make him my new best friend + radiusEnt->client->ps.eFlags2 |= EF2_HELD_BY_MONSTER; + //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something + radiusEnt->client->ps.hasLookTarget = qtrue; + radiusEnt->client->ps.lookTarget = NPC->s.number; + NPC->activator = radiusEnt;//remember him + NPC->count = 1;//in my hand + //wait to attack + TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + Q_irand(500, 2500) ); + if ( radiusEnt->health > 0 && radiusEnt->pain ) + {//do pain on enemy + radiusEnt->pain( radiusEnt, NPC, 100 ); + //GEntity_PainFunc( radiusEnt, NPC, NPC, radiusEnt->r.currentOrigin, 0, MOD_CRUSH ); + } + else if ( radiusEnt->client ) + { + radiusEnt->client->ps.forceHandExtend = HANDEXTEND_NONE; + radiusEnt->client->ps.forceHandExtendTime = 0; + NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + } + else + {//smack + vec3_t pushDir; + vec3_t angs; + + G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + //actually push the enemy + /* + //VectorSubtract( radiusEnt->r.currentOrigin, boltOrg, pushDir ); + VectorSubtract( radiusEnt->r.currentOrigin, NPC->r.currentOrigin, pushDir ); + pushDir[2] = Q_flrand( 100, 200 ); + VectorNormalize( pushDir ); + */ + VectorCopy( NPC->client->ps.viewangles, angs ); + angs[YAW] += flrand( 25, 50 ); + angs[PITCH] = flrand( -25, -15 ); + AngleVectors( angs, pushDir, NULL, NULL ); + if ( radiusEnt->client->NPC_class != CLASS_RANCOR + && radiusEnt->client->NPC_class != CLASS_ATST ) + { + G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + G_Throw( radiusEnt, pushDir, 250 ); + if ( radiusEnt->health > 0 ) + {//do pain on enemy + G_Knockdown( radiusEnt );//, NPC, pushDir, 100, qtrue ); + } + } + } + } + } +} + +void Rancor_Smash( void ) +{ + int radiusEntNums[128]; + int numEnts; + const float radius = 128; + const float halfRadSquared = ((radius/2)*(radius/2)); + const float radiusSquared = (radius*radius); + float distSq; + int i; + vec3_t boltOrg; + + AddSoundEvent( NPC, NPC->r.currentOrigin, 512, AEL_DANGER, qfalse );//, qtrue ); + + numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.handLBolt, boltOrg ); + + for ( i = 0; i < numEnts; i++ ) + { + gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; + if ( !radiusEnt->inuse ) + { + continue; + } + + if ( radiusEnt == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnt->client == NULL ) + {//must be a client + continue; + } + + if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//can't be one being held + continue; + } + + distSq = DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ); + if ( distSq <= radiusSquared ) + { + G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + if ( distSq < halfRadSquared ) + {//close enough to do damage, too + G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 10, 25 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + if ( radiusEnt->health > 0 + && radiusEnt->client + && radiusEnt->client->NPC_class != CLASS_RANCOR + && radiusEnt->client->NPC_class != CLASS_ATST ) + { + if ( distSq < halfRadSquared + || radiusEnt->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//within range of my fist or withing ground-shaking range and not in the air + G_Knockdown( radiusEnt );//, NPC, vec3_origin, 100, qtrue ); + } + } + } + } +} + +void Rancor_Bite( void ) +{ + int radiusEntNums[128]; + int numEnts; + const float radius = 100; + const float radiusSquared = (radius*radius); + int i; + vec3_t boltOrg; + + numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.crotchBolt, boltOrg );//was gutBolt? + + for ( i = 0; i < numEnts; i++ ) + { + gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; + if ( !radiusEnt->inuse ) + { + continue; + } + + if ( radiusEnt == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnt->client == NULL ) + {//must be a client + continue; + } + + if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//can't be one already being held + continue; + } + + if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared ) + { + G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 15, 30 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + if ( radiusEnt->health <= 0 && radiusEnt->client ) + {//killed them, chance of dismembering + if ( !Q_irand( 0, 1 ) ) + {//bite something off + int hitLoc = Q_irand( G2_MODELPART_HEAD, G2_MODELPART_RLEG ); + if ( hitLoc == G2_MODELPART_HEAD ) + { + NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else if ( hitLoc == G2_MODELPART_WAIST ) + { + NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + //radiusEnt->client->dismembered = qfalse; + //FIXME: the limb should just disappear, cuz I ate it + G_Dismember( radiusEnt, NPC, radiusEnt->r.currentOrigin, hitLoc, 90, 0, radiusEnt->client->ps.torsoAnim, qtrue); + //G_DoDismemberment( radiusEnt, radiusEnt->r.currentOrigin, MOD_SABER, 1000, hitLoc, qtrue ); + } + } + G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); + } + } +} +//------------------------------ +extern void TossClientItems( gentity_t *self ); +void Rancor_Attack( float distance, qboolean doCharge ) +{ + if ( !TIMER_Exists( NPC, "attacking" ) ) + { + if ( NPC->count == 2 && NPC->activator ) + { + } + else if ( NPC->count == 1 && NPC->activator ) + {//holding enemy + if ( NPC->activator->health > 0 && Q_irand( 0, 1 ) ) + {//quick bite + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 450 ); + } + else + {//full eat + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 900 ); + //Make victim scream in fright + if ( NPC->activator->health > 0 && NPC->activator->client ) + { + G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 ); + NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + if ( NPC->activator->NPC ) + {//no more thinking for you + TossClientItems( NPC ); + NPC->activator->NPC->nextBStateThink = Q3_INFINITE; + } + } + } + } + else if ( NPC->enemy->health > 0 && doCharge ) + {//charge + vec3_t fwd, yawAng; + VectorSet( yawAng, 0, NPC->client->ps.viewangles[YAW], 0 ); + AngleVectors( yawAng, fwd, NULL, NULL ); + VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 150; + NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 1250 ); + } + else if ( !Q_irand(0, 1) ) + {//smash + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 1000 ); + } + else + {//try to grab + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 1000 ); + } + + TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + random() * 200 ); + } + + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + + if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) + { + vec3_t shakePos; + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_MELEE1: + Rancor_Smash(); + G_GetBoltPosition( NPC, NPC->client->renderInfo.handLBolt, shakePos, 0 ); + G_ScreenShake( shakePos, NULL, 4.0f, 1000, qfalse ); + //CGCam_Shake( 1.0f*playerDist/128.0f, 1000 ); + break; + case BOTH_MELEE2: + Rancor_Bite(); + TIMER_Set( NPC, "attack_dmg2", 450 ); + break; + case BOTH_ATTACK1: + if ( NPC->count == 1 && NPC->activator ) + { + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + if ( NPC->activator->health <= 0 ) + {//killed him + //make it look like we bit his head off + //NPC->activator->client->dismembered = qfalse; + G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_HEAD, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue); + //G_DoDismemberment( NPC->activator, NPC->activator->r.currentOrigin, MOD_SABER, 1000, HL_HEAD, qtrue ); + NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; + NPC->activator->client->ps.forceHandExtendTime = 0; + NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); + } + break; + case BOTH_ATTACK2: + //try to grab + Rancor_Swing( qtrue ); + break; + case BOTH_ATTACK3: + if ( NPC->count == 1 && NPC->activator ) + { + //cut in half + if ( NPC->activator->client ) + { + //NPC->activator->client->dismembered = qfalse; + G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue); + //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); + } + //KILL + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, NPC->enemy->health+10, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE );// + if ( NPC->activator->client ) + { + NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; + NPC->activator->client->ps.forceHandExtendTime = 0; + NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + TIMER_Set( NPC, "attack_dmg2", 1350 ); + G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); + } + break; + default: //something broke! + Rancor_DropVictim(NPC); + break; + } + } + else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) ) + { + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_MELEE1: + break; + case BOTH_MELEE2: + Rancor_Bite(); + break; + case BOTH_ATTACK1: + break; + case BOTH_ATTACK2: + break; + case BOTH_ATTACK3: + if ( NPC->count == 1 && NPC->activator ) + {//swallow victim + G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); + //FIXME: sometimes end up with a live one in our mouths? + //just make sure they're dead + if ( NPC->activator->health > 0 ) + { + //cut in half + //NPC->activator->client->dismembered = qfalse; + G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue); + //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); + //KILL + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, NPC->enemy->health+10, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE ); + NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; + NPC->activator->client->ps.forceHandExtendTime = 0; + NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); + } + if ( NPC->activator->client ) + {//*sigh*, can't get tags right, just remove them? + NPC->activator->client->ps.eFlags |= EF_NODRAW; + } + NPC->count = 2; + TIMER_Set( NPC, "clearGrabbed", 2600 ); + } + break; + default: //something broke! + Rancor_DropVictim(NPC); + break; + } + } + else if ( NPC->client->ps.legsAnim == BOTH_ATTACK2 ) + { + if ( NPC->client->ps.legsTimer >= 1200 && NPC->client->ps.legsTimer <= 1350 ) + { + if ( Q_irand( 0, 2 ) ) + { + Rancor_Swing( qfalse ); + } + else + { + Rancor_Swing( qtrue ); + } + } + else if ( NPC->client->ps.legsTimer >= 1100 && NPC->client->ps.legsTimer <= 1550 ) + { + Rancor_Swing( qtrue ); + } + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); +} + +//---------------------------------- +void Rancor_Combat( void ) +{ + if ( NPC->count ) + {//holding my enemy + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + Rancor_Attack( 0, qfalse ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS4( NPC->enemy ) )//|| UpdateGoal( )) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE; // just get us within combat range + + if ( !NPC_MoveToGoal( qtrue ) ) + {//couldn't go after him? Look for a new one + TIMER_Set( NPC, "lookForNewEnemy", 0 ); + NPCInfo->consecutiveBlockedMoves++; + } + else + { + NPCInfo->consecutiveBlockedMoves = 0; + } + return; + } + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + { + float distance; + qboolean advance; + qboolean doCharge; + + distance = Distance( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + advance = (qboolean)( distance > (NPC->r.maxs[0]+MIN_DISTANCE) ? qtrue : qfalse ); + doCharge = qfalse; + + if ( advance ) + {//have to get closer + vec3_t yawOnlyAngles; + VectorSet( yawOnlyAngles, 0, NPC->r.currentAngles[YAW], 0 ); + if ( NPC->enemy->health > 0 + && fabs(distance-250) <= 80 + && InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, yawOnlyAngles, 30, 30 ) ) + { + if ( !Q_irand( 0, 9 ) ) + {//go for the charge + doCharge = qtrue; + advance = qfalse; + } + } + } + + if (( advance /*|| NPCInfo->localState == LSTATE_WAITING*/ ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + Rancor_Move( 1 ); + } + } + else + { + Rancor_Attack( distance, doCharge ); + } + } +} + +/* +------------------------- +NPC_Rancor_Pain +------------------------- +*/ +//void NPC_Rancor_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +void NPC_Rancor_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + qboolean hitByRancor = qfalse; + if ( attacker&&attacker->client&&attacker->client->NPC_class==CLASS_RANCOR ) + { + hitByRancor = qtrue; + } + if ( attacker + && attacker->inuse + && attacker != self->enemy + && !(attacker->flags&FL_NOTARGET) ) + { + if ( !self->count ) + { + if ( (!attacker->s.number&&!Q_irand(0,3)) + || !self->enemy + || self->enemy->health == 0 + || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR) + || (self->NPC && self->NPC->consecutiveBlockedMoves>=10 && DistanceSquared( attacker->r.currentOrigin, self->r.currentOrigin ) < DistanceSquared( self->enemy->r.currentOrigin, self->r.currentOrigin )) ) + {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker + //FIXME: if can't nav to my enemy, take this guy if I can nav to him + G_SetEnemy( self, attacker ); + TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + if ( hitByRancor ) + {//stay mad at this Rancor for 2-5 secs before looking for attacker enemies + TIMER_Set( self, "rancorInfight", Q_irand( 2000, 5000 ) ); + } + + } + } + } + if ( (hitByRancor|| (self->count==1&&self->activator&&!Q_irand(0,4)) || Q_irand( 0, 200 ) < damage )//hit by rancor, hit while holding live victim, or took a lot of damage + && self->client->ps.legsAnim != BOTH_STAND1TO2 + && TIMER_Done( self, "takingPain" ) ) + { + if ( !Rancor_CheckRoar( self ) ) + { + if ( self->client->ps.legsAnim != BOTH_MELEE1 + && self->client->ps.legsAnim != BOTH_MELEE2 + && self->client->ps.legsAnim != BOTH_ATTACK2 ) + {//cant interrupt one of the big attack anims + /* + if ( self->count != 1 + || attacker == self->activator + || (self->client->ps.legsAnim != BOTH_ATTACK1&&self->client->ps.legsAnim != BOTH_ATTACK3) ) + */ + {//if going to bite our victim, only victim can interrupt that anim + if ( self->health > 100 || hitByRancor ) + { + TIMER_Remove( self, "attacking" ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( self->count == 1 ) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + TIMER_Set( self, "takingPain", self->client->ps.legsTimer+Q_irand(0, 500) ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } + } + } + } + //let go + /* + if ( !Q_irand( 0, 3 ) && self->count == 1 ) + { + Rancor_DropVictim( self ); + } + */ + } +} + +void Rancor_CheckDropVictim( void ) +{ + vec3_t mins; + vec3_t maxs; + vec3_t start; + vec3_t end; + trace_t trace; + + VectorSet( mins, NPC->activator->r.mins[0]-1, NPC->activator->r.mins[1]-1, 0 ); + VectorSet( maxs, NPC->activator->r.maxs[0]+1, NPC->activator->r.maxs[1]+1, 1 ); + VectorSet( start, NPC->activator->r.currentOrigin[0], NPC->activator->r.currentOrigin[1], NPC->activator->r.absmin[2] ); + VectorSet( end, NPC->activator->r.currentOrigin[0], NPC->activator->r.currentOrigin[1], NPC->activator->r.absmax[2]-1 ); + + trap_Trace( &trace, start, mins, maxs, end, NPC->activator->s.number, NPC->activator->clipmask ); + if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0f ) + { + Rancor_DropVictim( NPC ); + } +} + +//if he's stepping on things then crush them -rww +void Rancor_Crush(void) +{ + gentity_t *crush; + + if (!NPC || + !NPC->client || + NPC->client->ps.groundEntityNum >= ENTITYNUM_WORLD) + { //nothing to crush + return; + } + + crush = &g_entities[NPC->client->ps.groundEntityNum]; + if (crush->inuse && crush->client && !crush->localAnimIndex) + { //a humanoid, smash them good. + G_Damage(crush, NPC, NPC, NULL, NPC->r.currentOrigin, 200, 0, MOD_CRUSH); + } +} + +/* +------------------------- +NPC_BSRancor_Default +------------------------- +*/ +void NPC_BSRancor_Default( void ) +{ + AddSightEvent( NPC, NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, 50 ); + + Rancor_Crush(); + + NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM|EF2_GENERIC_NPC_FLAG); + if ( NPC->count ) + {//holding someone + NPC->client->ps.eFlags2 |= EF2_USE_ALT_ANIM; + if ( NPC->count == 2 ) + {//in my mouth + NPC->client->ps.eFlags2 |= EF2_GENERIC_NPC_FLAG; + } + } + else + { + NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM|EF2_GENERIC_NPC_FLAG); + } + + if ( TIMER_Done2( NPC, "clearGrabbed", qtrue ) ) + { + Rancor_DropVictim( NPC ); + } + else if ( NPC->client->ps.legsAnim == BOTH_PAIN2 + && NPC->count == 1 + && NPC->activator ) + { + if ( !Q_irand( 0, 3 ) ) + { + Rancor_CheckDropVictim(); + } + } + if ( !TIMER_Done( NPC, "rageTime" ) ) + {//do nothing but roar first time we see an enemy + AddSoundEvent( NPC, NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, qfalse );//, qfalse ); + NPC_FaceEnemy( qtrue ); + return; + } + if ( NPC->enemy ) + { + /* + if ( NPC->enemy->client //enemy is a client + && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught + && NPC->enemy->enemy != NPC//enemy's enemy is not me + && (!NPC->enemy->enemy || !NPC->enemy->enemy->client || NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR) )//enemy's enemy is not a client or is not a rancor (which is as scary as me anyway) + {//they should be scared of ME and no-one else + G_SetEnemy( NPC->enemy, NPC ); + } + */ + if ( TIMER_Done(NPC,"angrynoise") ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( va("sound/chars/rancor/misc/anger%d.wav", Q_irand(1, 3))) ); + + TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); + } + else + { + AddSoundEvent( NPC, NPC->r.currentOrigin, 512, AEL_DANGER_GREAT, qfalse );//, qfalse ); + } + if ( NPC->count == 2 && NPC->client->ps.legsAnim == BOTH_ATTACK3 ) + {//we're still chewing our enemy up + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while + if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_RANCOR ) + {//got mad at another Rancor, look for a valid enemy + if ( TIMER_Done( NPC, "rancorInfight" ) ) + { + NPC_CheckEnemyExt( qtrue ); + } + } + else if ( !NPC->count ) + { + if ( ValidEnemy( NPC->enemy ) == qfalse ) + { + TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now + if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) + {//it's been a while since the enemy died, or enemy is completely gone, get bored with him + NPC->enemy = NULL; + Rancor_Patrol(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) + { + gentity_t *newEnemy, *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? + NPC->enemy = NULL; + newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); + NPC->enemy = sav_enemy; + if ( newEnemy && newEnemy != sav_enemy ) + {//picked up a new enemy! + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, newEnemy ); + //hold this one for at least 5-15 seconds + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + } + else + {//look again in 2-5 secs + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); + } + } + } + Rancor_Combat(); + } + else + { + if ( TIMER_Done(NPC,"idlenoise") ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", Q_irand(1, 2))) ); + + TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); + AddSoundEvent( NPC, NPC->r.currentOrigin, 384, AEL_DANGER, qfalse );//, qfalse ); + } + if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Rancor_Patrol(); + } + else + { + Rancor_Idle(); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/codemp/game/NPC_AI_Remote.c b/codemp/game/NPC_AI_Remote.c new file mode 100644 index 0000000..aa1383d --- /dev/null +++ b/codemp/game/NPC_AI_Remote.c @@ -0,0 +1,389 @@ +#include "b_local.h" +#include "g_nav.h" + +void Remote_Strafe( void ); + +#define VELOCITY_DECAY 0.85f + + +//Local state enums +enum +{ + LSTATE_NONE = 0, +}; + +void Remote_Idle( void ); + +void NPC_Remote_Precache(void) +{ + G_SoundIndex("sound/chars/remote/misc/fire.wav"); + G_SoundIndex( "sound/chars/remote/misc/hiss.wav"); + G_EffectIndex( "env/small_explode"); +} + +/* +------------------------- +NPC_Remote_Pain +------------------------- +*/ +void NPC_Remote_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + SaveNPCGlobals(); + SetNPCGlobals( self ); + Remote_Strafe(); + RestoreNPCGlobals(); + + NPC_Pain( self, attacker, damage ); +} + +/* +------------------------- +Remote_MaintainHeight +------------------------- +*/ +void Remote_MaintainHeight( void ) +{ + float dif; + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + // If we have an enemy, we should try to hover at or a little below enemy eye level + if ( NPC->enemy ) + { + if (TIMER_Done( NPC, "heightChange")) + { + TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); + + // Find the height difference + dif = (NPC->enemy->r.currentOrigin[2] + Q_irand( 0, NPC->enemy->r.maxs[2]+8 )) - NPC->r.currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 2 ) + { + if ( fabs( dif ) > 24 ) + { + dif = ( dif < 0 ? -24 : 24 ); + } + dif *= 10; + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + // NPC->fx_time = level.time; + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/remote/misc/hiss.wav")); + } + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + dif = ( dif < 0 ? -24 : 24 ); + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +#define REMOTE_STRAFE_VEL 256 +#define REMOTE_STRAFE_DIS 200 +#define REMOTE_UPWARD_PUSH 32 + +/* +------------------------- +Remote_Strafe +------------------------- +*/ +void Remote_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->r.currentOrigin, REMOTE_STRAFE_DIS * dir, right, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, REMOTE_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/remote/misc/hiss.wav")); + + // Add a slight upward push + NPC->client->ps.velocity[2] += REMOTE_UPWARD_PUSH; + + // Set the strafe start time so we can do a controlled roll + // NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +#define REMOTE_FORWARD_BASE_SPEED 10 +#define REMOTE_FORWARD_MULTIPLIER 5 + +/* +------------------------- +Remote_Hunt +------------------------- +*/ +void Remote_Hunt( qboolean visible, qboolean advance, qboolean retreat ) +{ + float distance, speed; + vec3_t forward; + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Remote_Strafe(); + return; + } + } + + //If we don't want to advance, stop here + if ( advance == qfalse && visible == qtrue ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + //Get our direction from the navigator if we can't see our target + if ( NPC_GetMoveDirection( forward, &distance ) == qfalse ) + return; + } + else + { + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = REMOTE_FORWARD_BASE_SPEED + REMOTE_FORWARD_MULTIPLIER * g_spskill.integer; + if ( retreat == qtrue ) + { + speed *= -1; + } + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + + +/* +------------------------- +Remote_Fire +------------------------- +*/ +void Remote_Fire (void) +{ + vec3_t delta1, enemy_org1, muzzle1; + vec3_t angleToEnemy1; + static vec3_t forward, vright, up; + static vec3_t muzzle; + gentity_t *missile; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 ); + VectorCopy( NPC->r.currentOrigin, muzzle1 ); + + VectorSubtract (enemy_org1, muzzle1, delta1); + + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + + missile = CreateMissile( NPC->r.currentOrigin, forward, 1000, 10000, NPC, qfalse ); + + G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), NPC->r.currentOrigin, forward ); + + missile->classname = "briar"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->damage = 10; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BRYAR_PISTOL; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +Remote_Ranged +------------------------- +*/ +void Remote_Ranged( qboolean visible, qboolean advance, qboolean retreat ) +{ + + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) ); + Remote_Fire(); + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Remote_Hunt( visible, advance, retreat ); + } +} + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 80 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +/* +------------------------- +Remote_Attack +------------------------- +*/ +void Remote_Attack( void ) +{ + float distance; + qboolean visible; + float idealDist; + qboolean advance; + qboolean retreat; + + if ( TIMER_Done(NPC,"spin") ) + { + TIMER_Set( NPC, "spin", Q_irand( 250, 1500 ) ); + NPCInfo->desiredYaw += Q_irand( -200, 200 ); + } + // Always keep a good height off the ground + Remote_MaintainHeight(); + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse ) + { + Remote_Idle(); + return; + } + + // Rate our distance to the target, and our visibilty + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + visible = NPC_ClearLOS4( NPC->enemy ); + idealDist = MIN_DISTANCE_SQR+(MIN_DISTANCE_SQR*flrand( 0, 1 )); + advance = (qboolean)(distance > idealDist*1.25); + retreat = (qboolean)(distance < idealDist*0.75); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Remote_Hunt( visible, advance, retreat ); + return; + } + } + + Remote_Ranged( visible, advance, retreat ); + +} + +/* +------------------------- +Remote_Idle +------------------------- +*/ +void Remote_Idle( void ) +{ + Remote_MaintainHeight(); + + NPC_BSIdle(); +} + +/* +------------------------- +Remote_Patrol +------------------------- +*/ +void Remote_Patrol( void ) +{ + Remote_MaintainHeight(); + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( UpdateGoal() ) + { + //start loop sound once we move + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + + +/* +------------------------- +NPC_BSRemote_Default +------------------------- +*/ +void NPC_BSRemote_Default( void ) +{ + if ( NPC->enemy ) + { + Remote_Attack(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Remote_Patrol(); + } + else + { + Remote_Idle(); + } +} diff --git a/codemp/game/NPC_AI_Seeker.c b/codemp/game/NPC_AI_Seeker.c new file mode 100644 index 0000000..bf624d5 --- /dev/null +++ b/codemp/game/NPC_AI_Seeker.c @@ -0,0 +1,574 @@ +#include "b_local.h" +#include "g_nav.h" + +extern void Boba_FireDecide( void ); + +void Seeker_Strafe( void ); + +#define VELOCITY_DECAY 0.7f + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 80 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define SEEKER_STRAFE_VEL 100 +#define SEEKER_STRAFE_DIS 200 +#define SEEKER_UPWARD_PUSH 32 + +#define SEEKER_FORWARD_BASE_SPEED 10 +#define SEEKER_FORWARD_MULTIPLIER 2 + +#define SEEKER_SEEK_RADIUS 1024 + +//------------------------------------ +void NPC_Seeker_Precache(void) +{ + G_SoundIndex("sound/chars/seeker/misc/fire.wav"); + G_SoundIndex( "sound/chars/seeker/misc/hiss.wav"); + G_EffectIndex( "env/small_explode"); +} + +//------------------------------------ +void NPC_Seeker_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + if ( !(self->NPC->aiFlags&NPCAI_CUSTOM_GRAVITY )) + {//void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod, int hitLoc=HL_NONE ); + G_Damage( self, NULL, NULL, (float*)vec3_origin, (float*)vec3_origin, 999, 0, MOD_FALLING ); + } + + SaveNPCGlobals(); + SetNPCGlobals( self ); + Seeker_Strafe(); + RestoreNPCGlobals(); + NPC_Pain( self, attacker, damage ); +} + +//------------------------------------ +void Seeker_MaintainHeight( void ) +{ + float dif; + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at or a little below enemy eye level + if ( NPC->enemy ) + { + if (TIMER_Done( NPC, "heightChange" )) + { + float difFactor; + + TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); + + // Find the height difference + dif = (NPC->enemy->r.currentOrigin[2] + flrand( NPC->enemy->r.maxs[2]/2, NPC->enemy->r.maxs[2]+8 )) - NPC->r.currentOrigin[2]; + + difFactor = 1.0f; + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( TIMER_Done( NPC, "flameTime" ) ) + { + difFactor = 10.0f; + } + } + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 2*difFactor ) + { + if ( fabs( dif ) > 24*difFactor ) + { + dif = ( dif < 0 ? -24*difFactor : 24*difFactor ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + NPC->client->ps.velocity[2] *= flrand( 0.85f, 3.0f ); + } + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +//------------------------------------ +void Seeker_Strafe( void ) +{ + int side; + vec3_t end, right, dir; + trace_t tr; + + if ( random() > 0.7f || !NPC->enemy || !NPC->enemy->client ) + { + // Do a regular style strafe + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonably valid + side = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->r.currentOrigin, SEEKER_STRAFE_DIS * side, right, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + float vel = SEEKER_STRAFE_VEL; + float upPush = SEEKER_UPWARD_PUSH; + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); + } + else + { + vel *= 3.0f; + upPush *= 4.0f; + } + VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity ); + // Add a slight upward push + NPC->client->ps.velocity[2] += upPush; + + NPCInfo->standTime = level.time + 1000 + random() * 500; + } + } + else + { + float stDis; + + // Do a strafe to try and keep on the side of their enemy + AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL ); + + // Pick a random side + side = ( rand() & 1 ) ? -1 : 1; + stDis = SEEKER_STRAFE_DIS; + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + stDis *= 2.0f; + } + VectorMA( NPC->enemy->r.currentOrigin, stDis * side, right, end ); + + // then add a very small bit of random in front of/behind the player action + VectorMA( end, crandom() * 25, dir, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + float dis, upPush; + + VectorSubtract( tr.endpos, NPC->r.currentOrigin, dir ); + dir[2] *= 0.25; // do less upward change + dis = VectorNormalize( dir ); + + // Try to move the desired enemy side + VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity ); + + upPush = SEEKER_UPWARD_PUSH; + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); + } + else + { + upPush *= 4.0f; + } + + // Add a slight upward push + NPC->client->ps.velocity[2] += upPush; + + NPCInfo->standTime = level.time + 2500 + random() * 500; + } + } +} + +//------------------------------------ +void Seeker_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + NPC_FaceEnemy( qtrue ); + + // If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Seeker_Strafe(); + return; + } + } + + // If we don't want to advance, stop here + if ( advance == qfalse ) + { + return; + } + + // Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 24; + + // Get our direction from the navigator if we can't see our target + if ( NPC_GetMoveDirection( forward, &distance ) == qfalse ) + { + return; + } + } + else + { + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = SEEKER_FORWARD_BASE_SPEED + SEEKER_FORWARD_MULTIPLIER * g_spskill.integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +//------------------------------------ +void Seeker_Fire( void ) +{ + vec3_t dir, enemy_org, muzzle; + gentity_t *missile; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org ); + VectorSubtract( enemy_org, NPC->r.currentOrigin, dir ); + VectorNormalize( dir ); + + // move a bit forward in the direction we shall shoot in so that the bolt doesn't poke out the other side of the seeker + VectorMA( NPC->r.currentOrigin, 15, dir, muzzle ); + + missile = CreateMissile( muzzle, dir, 1000, 10000, NPC, qfalse ); + + G_PlayEffectID( G_EffectIndex("blaster/muzzle_flash"), NPC->r.currentOrigin, dir ); + + missile->classname = "blaster"; + missile->s.weapon = WP_BLASTER; + + missile->damage = 5; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BLASTER; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + if ( NPC->r.ownerNum < ENTITYNUM_NONE ) + { + missile->r.ownerNum = NPC->r.ownerNum; + } +} + +//------------------------------------ +void Seeker_Ranged( qboolean visible, qboolean advance ) +{ + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + if ( NPC->count > 0 ) + { + if ( TIMER_Done( NPC, "attackDelay" )) // Attack? + { + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 2500 )); + Seeker_Fire(); + NPC->count--; + } + } + else + { + // out of ammo, so let it die...give it a push up so it can fall more and blow up on impact + // NPC->client->ps.gravity = 900; + // NPC->svFlags &= ~SVF_CUSTOM_GRAVITY; + // NPC->client->ps.velocity[2] += 16; + G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN ); + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Seeker_Hunt( visible, advance ); + } +} + +//------------------------------------ +void Seeker_Attack( void ) +{ + float distance; + qboolean visible; + qboolean advance; + + // Always keep a good height off the ground + Seeker_MaintainHeight(); + + // Rate our distance to the target, and our visibilty + distance = DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + advance = (qboolean)(distance>(200.0f*200.0f)); + } + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Seeker_Hunt( visible, advance ); + return; + } + } + + Seeker_Ranged( visible, advance ); +} + +//------------------------------------ +void Seeker_FindEnemy( void ) +{ + int numFound; + float dis, bestDis = SEEKER_SEEK_RADIUS * SEEKER_SEEK_RADIUS + 1; + vec3_t mins, maxs; + int entityList[MAX_GENTITIES]; + gentity_t *ent, *best = NULL; + int i; + + VectorSet( maxs, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS ); + VectorScale( maxs, -1, mins ); + + numFound = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( i = 0 ; i < numFound ; i++ ) + { + ent = &g_entities[entityList[i]]; + + if ( ent->s.number == NPC->s.number + || !ent->client //&& || !ent->NPC + || ent->health <= 0 + || !ent->inuse ) + { + continue; + } + + if ( ent->client->playerTeam == NPC->client->playerTeam || ent->client->playerTeam == NPCTEAM_NEUTRAL ) // don't attack same team or bots + { + continue; + } + + // try to find the closest visible one + if ( !NPC_ClearLOS4( ent )) + { + continue; + } + + dis = DistanceHorizontalSquared( NPC->r.currentOrigin, ent->r.currentOrigin ); + + if ( dis <= bestDis ) + { + bestDis = dis; + best = ent; + } + } + + if ( best ) + { + // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method. + NPC->random = random() * 6.3f; // roughly 2pi + + NPC->enemy = best; + } +} + +//------------------------------------ +void Seeker_FollowOwner( void ) +{ + float dis, minDistSqr; + vec3_t pt, dir; + gentity_t *owner = &g_entities[NPC->s.owner]; + + Seeker_MaintainHeight(); + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + owner = NPC->enemy; + } + if ( !owner || owner == NPC || !owner->client ) + { + return; + } + //rwwFIXMEFIXME: Care about all clients not just 0 + dis = DistanceHorizontalSquared( NPC->r.currentOrigin, owner->r.currentOrigin ); + + minDistSqr = MIN_DISTANCE_SQR; + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( TIMER_Done( NPC, "flameTime" ) ) + { + minDistSqr = 200*200; + } + } + + if ( dis < minDistSqr ) + { + // generally circle the player closely till we take an enemy..this is our target point + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + pt[0] = owner->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 250; + pt[1] = owner->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 250; + if ( NPC->client->jetPackTime < level.time ) + { + pt[2] = NPC->r.currentOrigin[2] - 64; + } + else + { + pt[2] = owner->r.currentOrigin[2] + 200; + } + } + else + { + pt[0] = owner->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 56; + pt[1] = owner->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 56; + pt[2] = owner->r.currentOrigin[2] + 40; + } + + VectorSubtract( pt, NPC->r.currentOrigin, dir ); + VectorMA( NPC->client->ps.velocity, 0.8f, dir, NPC->client->ps.velocity ); + } + else + { + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + if ( TIMER_Done( NPC, "seekerhiss" )) + { + TIMER_Set( NPC, "seekerhiss", 1000 + random() * 1000 ); + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); + } + } + + // Hey come back! + NPCInfo->goalEntity = owner; + NPCInfo->goalRadius = 32; + NPC_MoveToGoal( qtrue ); + NPC->parent = owner; + } + + if ( NPCInfo->enemyCheckDebounceTime < level.time ) + { + // check twice a second to find a new enemy + Seeker_FindEnemy(); + NPCInfo->enemyCheckDebounceTime = level.time + 500; + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +//------------------------------------ +void NPC_BSSeeker_Default( void ) +{ + /* + if ( in_camera ) + { + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + // cameras make me commit suicide.... + G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN ); + } + } + */ + //N/A for MP. + if ( NPC->r.ownerNum < ENTITYNUM_NONE ) + { + gentity_t *owner = &g_entities[0]; + if ( owner->health <= 0 + || (owner->client && owner->client->pers.connected == CON_DISCONNECTED) ) + {//owner is dead or gone + //remove me + G_Damage( NPC, NULL, NULL, NULL, NULL, 10000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG ); + return; + } + } + + if ( NPC->random == 0.0f ) + { + // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method. + NPC->random = random() * 6.3f; // roughly 2pi + } + + if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse ) + { + if ( NPC->client->NPC_class != CLASS_BOBAFETT && ( NPC->enemy->s.number == 0 || ( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_SEEKER )) ) + { + //hacked to never take the player as an enemy, even if the player shoots at it + NPC->enemy = NULL; + } + else + { + Seeker_Attack(); + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + Boba_FireDecide(); + } + return; + } + } + + // In all other cases, follow the player and look for enemies to take on + Seeker_FollowOwner(); +} diff --git a/codemp/game/NPC_AI_Sentry.c b/codemp/game/NPC_AI_Sentry.c new file mode 100644 index 0000000..c5f73dd --- /dev/null +++ b/codemp/game/NPC_AI_Sentry.c @@ -0,0 +1,577 @@ +#include "b_local.h" +#include "g_nav.h" + +#include "../namespace_begin.h" +extern gitem_t *BG_FindItemForAmmo( ammo_t ammo ); +#include "../namespace_end.h" +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); + +#define MIN_DISTANCE 256 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define SENTRY_FORWARD_BASE_SPEED 10 +#define SENTRY_FORWARD_MULTIPLIER 5 + +#define SENTRY_VELOCITY_DECAY 0.85f +#define SENTRY_STRAFE_VEL 256 +#define SENTRY_STRAFE_DIS 200 +#define SENTRY_UPWARD_PUSH 32 +#define SENTRY_HOVER_HEIGHT 24 + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_ASLEEP, + LSTATE_WAKEUP, + LSTATE_ACTIVE, + LSTATE_POWERING_UP, + LSTATE_ATTACKING, +}; + +/* +------------------------- +NPC_Sentry_Precache +------------------------- +*/ +void NPC_Sentry_Precache(void) +{ + int i; + + G_SoundIndex( "sound/chars/sentry/misc/sentry_explo" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_pain" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_open" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_close" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" ); + + for ( i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/sentry/misc/talk%d", i ) ); + } + + G_EffectIndex( "bryar/muzzle_flash"); + G_EffectIndex( "env/med_explode"); + + RegisterItem( BG_FindItemForAmmo( AMMO_BLASTER )); +} + +/* +================ +sentry_use +================ +*/ +void sentry_use( gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + self->flags &= ~FL_SHIELDED; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +// self->NPC->localState = LSTATE_WAKEUP; + self->NPC->localState = LSTATE_ACTIVE; +} + +/* +------------------------- +NPC_Sentry_Pain +------------------------- +*/ +void NPC_Sentry_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + int mod = gPainMOD; + + NPC_Pain( self, attacker, damage ); + + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) + { + self->NPC->burstCount = 0; + TIMER_Set( self, "attackDelay", Q_irand( 9000, 12000) ); + self->flags |= FL_SHIELDED; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_pain") ); + + self->NPC->localState = LSTATE_ACTIVE; + } + + // You got hit, go after the enemy +// if (self->NPC->localState == LSTATE_ASLEEP) +// { +// G_Sound( self, G_SoundIndex("sound/chars/sentry/misc/shieldsopen.wav")); +// +// self->flags &= ~FL_SHIELDED; +// NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +// self->NPC->localState = LSTATE_WAKEUP; +// } +} + +/* +------------------------- +Sentry_Fire +------------------------- +*/ +void Sentry_Fire (void) +{ + vec3_t muzzle; + static vec3_t forward, vright, up; + gentity_t *missile; + mdxaBone_t boltMatrix; + int bolt; + int which; + + NPC->flags &= ~FL_SHIELDED; + + if ( NPCInfo->localState == LSTATE_POWERING_UP ) + { + if ( TIMER_Done( NPC, "powerup" )) + { + NPCInfo->localState = LSTATE_ATTACKING; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + { + // can't do anything right now + return; + } + } + else if ( NPCInfo->localState == LSTATE_ACTIVE ) + { + NPCInfo->localState = LSTATE_POWERING_UP; + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_shield_open") ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "powerup", 250 ); + return; + } + else if ( NPCInfo->localState != LSTATE_ATTACKING ) + { + // bad because we are uninitialized + NPCInfo->localState = LSTATE_ACTIVE; + return; + } + + // Which muzzle to fire from? + which = NPCInfo->burstCount % 3; + switch( which ) + { + case 0: + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash1"); + break; + case 1: + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash2"); + break; + case 2: + default: + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash03"); + } + + trap_G2API_GetBoltMatrix( NPC->ghoul2, 0, + bolt, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time, + NULL, NPC->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle ); + + AngleVectors( NPC->r.currentAngles, forward, vright, up ); +// G_Sound( NPC, G_SoundIndex("sound/chars/sentry/misc/shoot.wav")); + + G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle, forward ); + + missile = CreateMissile( muzzle, forward, 1600, 10000, NPC, qfalse ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BRYAR_PISTOL; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + NPCInfo->burstCount++; + NPC->attackDebounceTime = level.time + 50; + missile->damage = 5; + + // now scale for difficulty + if ( g_spskill.integer == 0 ) + { + NPC->attackDebounceTime += 200; + missile->damage = 1; + } + else if ( g_spskill.integer == 1 ) + { + NPC->attackDebounceTime += 100; + missile->damage = 3; + } +} + +/* +------------------------- +Sentry_MaintainHeight +------------------------- +*/ +void Sentry_MaintainHeight( void ) +{ + float dif; + + NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" ); + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at about enemy eye level + if ( NPC->enemy ) + { + // Find the height difference + dif = (NPC->enemy->r.currentOrigin[2]+NPC->enemy->r.maxs[2]) - NPC->r.currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 8 ) + { + if ( fabs( dif ) > SENTRY_HOVER_HEIGHT ) + { + dif = ( dif < 0 ? -24 : 24 ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + + if (goal) + { + dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + if ( fabs( dif ) > SENTRY_HOVER_HEIGHT ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + // Apply friction to Z + else if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } + + NPC_FaceEnemy( qtrue ); +} + +/* +------------------------- +Sentry_Idle +------------------------- +*/ +void Sentry_Idle( void ) +{ + Sentry_MaintainHeight(); + + // Is he waking up? + if (NPCInfo->localState == LSTATE_WAKEUP) + { + if (NPC->client->ps.torsoTimer<=0) + { + NPCInfo->scriptFlags |= SCF_LOOK_FOR_ENEMIES; + NPCInfo->burstCount = 0; + } + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SLEEP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->flags |= FL_SHIELDED; + + NPC_BSIdle(); + } +} + +/* +------------------------- +Sentry_Strafe +------------------------- +*/ +void Sentry_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->r.currentOrigin, SENTRY_STRAFE_DIS * dir, right, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, SENTRY_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + // Add a slight upward push + NPC->client->ps.velocity[2] += SENTRY_UPWARD_PUSH; + + // Set the strafe start time so we can do a controlled roll + // NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +/* +------------------------- +Sentry_Hunt +------------------------- +*/ +void Sentry_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Sentry_Strafe(); + return; + } + } + + //If we don't want to advance, stop here + if ( !advance && visible ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + //Get our direction from the navigator if we can't see our target + if ( NPC_GetMoveDirection( forward, &distance ) == qfalse ) + return; + } + else + { + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = SENTRY_FORWARD_BASE_SPEED + SENTRY_FORWARD_MULTIPLIER * g_spskill.integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +/* +------------------------- +Sentry_RangedAttack +------------------------- +*/ +void Sentry_RangedAttack( qboolean visible, qboolean advance ) +{ + if ( TIMER_Done( NPC, "attackDelay" ) && NPC->attackDebounceTime < level.time && visible ) // Attack? + { + if ( NPCInfo->burstCount > 6 ) + { + if ( !NPC->fly_sound_debounce_time ) + {//delay closing down to give the player an opening + NPC->fly_sound_debounce_time = level.time + Q_irand( 500, 2000 ); + } + else if ( NPC->fly_sound_debounce_time < level.time ) + { + NPCInfo->localState = LSTATE_ACTIVE; + NPC->fly_sound_debounce_time = NPCInfo->burstCount = 0; + TIMER_Set( NPC, "attackDelay", Q_irand( 2000, 3500) ); + NPC->flags |= FL_SHIELDED; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/sentry/misc/sentry_shield_close" ); + } + } + else + { + Sentry_Fire(); + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Sentry_Hunt( visible, advance ); + } +} + +/* +------------------------- +Sentry_AttackDecision +------------------------- +*/ +void Sentry_AttackDecision( void ) +{ + float distance; + qboolean visible; + qboolean advance; + + // Always keep a good height off the ground + Sentry_MaintainHeight(); + + NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" ); + + //randomly talk + if ( TIMER_Done(NPC,"patrolNoise") ) + { + if (TIMER_Done(NPC,"angerNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); + } + } + + // He's dead. + if (NPC->enemy->health<1) + { + NPC->enemy = NULL; + Sentry_Idle(); + return; + } + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse ) + { + Sentry_Idle(); + return; + } + + // Rate our distance to the target and visibilty + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Sentry_Hunt( visible, advance ); + return; + } + } + + NPC_FaceEnemy( qtrue ); + + Sentry_RangedAttack( visible, advance ); +} + +qboolean NPC_CheckPlayerTeamStealth( void ); + +/* +------------------------- +NPC_Sentry_Patrol +------------------------- +*/ +void NPC_Sentry_Patrol( void ) +{ + Sentry_MaintainHeight(); + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( UpdateGoal() ) + { + //start loop sound once we move + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + //randomly talk + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSSentry_Default +------------------------- +*/ +void NPC_BSSentry_Default( void ) +{ + if ( NPC->targetname ) + { + NPC->use = sentry_use; + } + + if (( NPC->enemy ) && (NPCInfo->localState != LSTATE_WAKEUP)) + { + // Don't attack if waking up or if no enemy + Sentry_AttackDecision(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + NPC_Sentry_Patrol(); + } + else + { + Sentry_Idle(); + } +} diff --git a/codemp/game/NPC_AI_Sniper.c b/codemp/game/NPC_AI_Sniper.c new file mode 100644 index 0000000..bb3b965 --- /dev/null +++ b/codemp/game/NPC_AI_Sniper.c @@ -0,0 +1,864 @@ +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" + +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern qboolean FlyingCreature( gentity_t *ent ); + +#define SPF_NO_HIDE 2 + +#define MAX_VIEW_DIST 1024 +#define MAX_VIEW_SPEED 250 +#define MAX_LIGHT_INTENSITY 255 +#define MIN_LIGHT_THRESHOLD 0.1 + +#define DISTANCE_SCALE 0.25f +#define DISTANCE_THRESHOLD 0.075f +#define SPEED_SCALE 0.25f +#define FOV_SCALE 0.5f +#define LIGHT_SCALE 0.25f + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 ) + +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS2; +static qboolean enemyCS2; +static qboolean faceEnemy2; +static qboolean move2; +static qboolean shoot2; +static float enemyDist2; + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void Sniper_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "duck", 0 ); + TIMER_Set( ent, "stand", 0 ); + TIMER_Set( ent, "shuffleTime", 0 ); + TIMER_Set( ent, "sleepTime", 0 ); + TIMER_Set( ent, "enemyLastVisible", 0 ); + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "hideTime", 0 ); + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "stick", 0 ); + TIMER_Set( ent, "scoutTime", 0 ); + TIMER_Set( ent, "flee", 0 ); +} + +void NPC_Sniper_PlayConfusionSound( gentity_t *self ) +{//FIXME: make this a custom sound in sound set + if ( self->health > 0 ) + { + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + TIMER_Set( self, "flee", 0 ); + self->NPC->squadState = SQUAD_IDLE; + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} + + +/* +------------------------- +NPC_ST_Pain +------------------------- +*/ + +void NPC_Sniper_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + self->NPC->localState = LSTATE_UNDERFIRE; + + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stand", 2000 ); + + NPC_Pain( self, attacker, damage ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } +} + +/* +------------------------- +ST_HoldPosition +------------------------- +*/ + +static void Sniper_HoldPosition( void ) +{ + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + NPCInfo->goalEntity = NULL; + + /*if ( TIMER_Done( NPC, "stand" ) ) + {//FIXME: what if can't shoot from this pos? + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +/* +------------------------- +ST_Move +------------------------- +*/ + +static qboolean Sniper_Move( void ) +{ + qboolean moved; + navInfo_t info; + + NPCInfo->combatMove = qtrue;//always move straight toward our goal + + moved = NPC_MoveToGoal( qtrue ); + + //Get the move info + NAV_GetLastMove( &info ); + + //FIXME: if we bump into another one of our guys and can't get around him, just stop! + //If we hit our target, then stop and fire! + if ( info.flags & NIF_COLLISION ) + { + if ( info.blocker == NPC->enemy ) + { + Sniper_HoldPosition(); + } + } + + //If our move failed, then reset + if ( moved == qfalse ) + {//couldn't get to enemy + if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) + {//we were running after enemy + //Try to find a combat point that can hit the enemy + int cpFlags = (CP_CLEAR|CP_HAS_ROUTE); + int cp; + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->r.currentOrigin, cpFlags, 32, -1 ); + if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + {//okay, try one by the enemy + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32, -1 ); + } + //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him... + if ( cp != -1 ) + {//found a combat point that has a clear shot to enemy + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL ); + return moved; + } + } + //just hang here + Sniper_HoldPosition(); + } + + return moved; +} + +/* +------------------------- +NPC_BSSniper_Patrol +------------------------- +*/ + +void NPC_BSSniper_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + NPC->count = 0; + + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//Should be auto now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + //Is there danger nearby + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); + if ( NPC_CheckForDanger( alertEvent ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + {//check for other alert events + //There is an event to look at + if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; + if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED ) + { + if ( level.alertEvents[alertEvent].owner && + level.alertEvents[alertEvent].owner->client && + level.alertEvents[alertEvent].owner->health >= 0 && + level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + //NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*100, (6-NPCInfo->stats.aim)*500 ) ); + } + } + else + {//FIXME: get more suspicious over time? + //Save the position for movement (if necessary) + //FIXME: sound? + VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); + NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); + if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) + {//suspicious looks longer + NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); + } + } + } + } + + if ( NPCInfo->investigateDebounceTime > level.time ) + {//FIXME: walk over to it, maybe? Not if not chase enemies flag + //NOTE: stops walking or doing anything else below + vec3_t dir, angles; + float o_yaw, o_pitch; + + VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); + vectoangles( dir, angles ); + + o_yaw = NPCInfo->desiredYaw; + o_pitch = NPCInfo->desiredPitch; + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + NPC_UpdateAngles( qtrue, qtrue ); + + NPCInfo->desiredYaw = o_yaw; + NPCInfo->desiredPitch = o_pitch; + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSSniper_Idle +------------------------- +*/ +/* +void NPC_BSSniper_Idle( void ) +{ + //reset our shotcount + NPC->count = 0; + + //FIXME: check for other alert events? + + //Is there danger nearby? + if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue ) ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) ); + + NPC_UpdateAngles( qtrue, qtrue ); +} +*/ +/* +------------------------- +ST_CheckMoveState +------------------------- +*/ + +static void Sniper_CheckMoveState( void ) +{ + //See if we're a scout + if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT ) + { + if ( NPCInfo->goalEntity == NPC->enemy ) + { + move2 = qfalse; + return; + } + } + //See if we're running away + else if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + if ( TIMER_Done( NPC, "flee" ) ) + { + NPCInfo->squadState = SQUAD_IDLE; + } + else + { + faceEnemy2 = qfalse; + } + } + else if ( NPCInfo->squadState == SQUAD_IDLE ) + { + if ( !NPCInfo->goalEntity ) + { + move2 = qfalse; + return; + } + } + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) || + ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS2 && enemyDist2 <= 10000 ) ) + { + int newSquadState = SQUAD_STAND_AND_SHOOT; + //we got where we wanted to go, set timers based on why we were running + switch ( NPCInfo->squadState ) + { + case SQUAD_RETREAT://was running away + TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 ); + TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); + newSquadState = SQUAD_COVER; + break; + case SQUAD_TRANSITION://was heading for a combat point + TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); + break; + case SQUAD_SCOUT://was running after player + break; + default: + break; + } + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*50, (6-NPCInfo->stats.aim)*100 ) ); //FIXME: Slant for difficulty levels, too? + //don't do something else just yet + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); + //stop fleeing + if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + TIMER_Set( NPC, "flee", -level.time ); + NPCInfo->squadState = SQUAD_IDLE; + } + return; + } + + //keep going, hold of roamTimer until we get there + TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); + } +} + +static void Sniper_ResolveBlockedShot( void ) +{ + if ( TIMER_Done( NPC, "duck" ) ) + {//we're not ducking + if ( TIMER_Done( NPC, "roamTime" ) ) + {//not roaming + //FIXME: try to find another spot from which to hit the enemy + if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && (!NPCInfo->goalEntity || NPCInfo->goalEntity == NPC->enemy) ) + {//we were running after enemy + //Try to find a combat point that can hit the enemy + int cpFlags = (CP_CLEAR|CP_HAS_ROUTE); + int cp; + + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->r.currentOrigin, cpFlags, 32, -1 ); + if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + {//okay, try one by the enemy + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32, -1 ); + } + //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him... + if ( cp != -1 ) + {//found a combat point that has a clear shot to enemy + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL ); + TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) ); + return; + } + } + } + } + /* + else + {//maybe we should stand + if ( TIMER_Done( NPC, "stand" ) ) + {//stand for as long as we'll be here + TIMER_Set( NPC, "stand", Q_irand( 500, 2000 ) ); + return; + } + } + //Hmm, can't resolve this by telling them to duck or telling me to stand + //We need to move! + TIMER_Set( NPC, "roamTime", -1 ); + TIMER_Set( NPC, "stick", -1 ); + TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) ); + */ +} + +/* +------------------------- +ST_CheckFireState +------------------------- +*/ + +static void Sniper_CheckFireState( void ) +{ + if ( enemyCS2 ) + {//if have a clear shot, always try + return; + } + + if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT ) + {//runners never try to fire at the last pos + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + //continue to fire on their last position + if ( !Q_irand( 0, 1 ) && NPCInfo->enemyLastSeenTime && level.time - NPCInfo->enemyLastSeenTime < ((5-NPCInfo->stats.aim)*1000) )//FIXME: incorporate skill too? + { + if ( !VectorCompare( vec3_origin, NPCInfo->enemyLastSeenLocation ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + + VectorNormalize( dir ); + + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot2 = qtrue; + //faceEnemy2 = qfalse; + } + return; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 10000 ) + {//next time we see him, we'll miss few times first + NPC->count = 0; + } +} + +qboolean Sniper_EvaluateShot( int hit ) +{ + gentity_t *hitEnt; + + if ( !NPC->enemy ) + { + return qfalse; + } + + hitEnt = &g_entities[hit]; + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) + || ( hitEnt && (hitEnt->r.svFlags&SVF_GLASS_BRUSH)) ) + {//can hit enemy or will hit glass, so shoot anyway + return qtrue; + } + return qfalse; +} + +void Sniper_FaceEnemy( void ) +{ + //FIXME: the ones behind kill holes are facing some arbitrary direction and not firing + //FIXME: If actually trying to hit enemy, don't fire unless enemy is at least in front of me? + //FIXME: need to give designers option to make them not miss first few shots + if ( NPC->enemy ) + { + vec3_t muzzle, target, angles, forward, right, up; + //Get the positions + AngleVectors( NPC->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( NPC, forward, right, up, muzzle ); + //CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + CalcEntitySpot( NPC->enemy, SPOT_ORIGIN, target ); + + if ( enemyDist2 > 65536 && NPCInfo->stats.aim < 5 )//is 256 squared, was 16384 (128*128) + { + if ( NPC->count < (5-NPCInfo->stats.aim) ) + {//miss a few times first + if ( shoot2 && TIMER_Done( NPC, "attackDelay" ) && level.time >= NPCInfo->shotTime ) + {//ready to fire again + qboolean aimError = qfalse; + qboolean hit = qtrue; + int tryMissCount = 0; + trace_t trace; + + GetAnglesForDirection( muzzle, target, angles ); + AngleVectors( angles, forward, right, up ); + + while ( hit && tryMissCount < 10 ) + { + tryMissCount++; + if ( !Q_irand( 0, 1 ) ) + { + aimError = qtrue; + if ( !Q_irand( 0, 1 ) ) + { + VectorMA( target, NPC->enemy->r.maxs[2]*flrand(1.5, 4), right, target ); + } + else + { + VectorMA( target, NPC->enemy->r.mins[2]*flrand(1.5, 4), right, target ); + } + } + if ( !aimError || !Q_irand( 0, 1 ) ) + { + if ( !Q_irand( 0, 1 ) ) + { + VectorMA( target, NPC->enemy->r.maxs[2]*flrand(1.5, 4), up, target ); + } + else + { + VectorMA( target, NPC->enemy->r.mins[2]*flrand(1.5, 4), up, target ); + } + } + trap_Trace( &trace, muzzle, vec3_origin, vec3_origin, target, NPC->s.number, MASK_SHOT ); + hit = Sniper_EvaluateShot( trace.entityNum ); + } + NPC->count++; + } + else + { + if ( !enemyLOS2 ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + else + {//based on distance, aim value, difficulty and enemy movement, miss + //FIXME: incorporate distance as a factor? + int missFactor = 8-(NPCInfo->stats.aim+g_spskill.integer) * 3; + if ( missFactor > ENEMY_POS_LAG_STEPS ) + { + missFactor = ENEMY_POS_LAG_STEPS; + } + else if ( missFactor < 0 ) + {//??? + missFactor = 0 ; + } + VectorCopy( NPCInfo->enemyLaggedPos[missFactor], target ); + } + GetAnglesForDirection( muzzle, target, angles ); + } + else + { + target[2] += flrand( 0, NPC->enemy->r.maxs[2] ); + //CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, target ); + GetAnglesForDirection( muzzle, target, angles ); + } + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + } + NPC_UpdateAngles( qtrue, qtrue ); +} + +void Sniper_UpdateEnemyPos( void ) +{ + int index; + int i; + + for ( i = MAX_ENEMY_POS_LAG-ENEMY_POS_LAG_INTERVAL; i >= 0; i -= ENEMY_POS_LAG_INTERVAL ) + { + index = i/ENEMY_POS_LAG_INTERVAL; + if ( !index ) + { + CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, NPCInfo->enemyLaggedPos[index] ); + NPCInfo->enemyLaggedPos[index][2] -= flrand( 2, 16 ); + } + else + { + VectorCopy( NPCInfo->enemyLaggedPos[index-1], NPCInfo->enemyLaggedPos[index] ); + } + } +} + +/* +------------------------- +NPC_BSSniper_Attack +------------------------- +*/ + +void Sniper_StartHide( void ) +{ + int duckTime = Q_irand( 2000, 5000 ); + + TIMER_Set( NPC, "duck", duckTime ); + TIMER_Set( NPC, "watch", 500 ); + TIMER_Set( NPC, "attackDelay", duckTime + Q_irand( 500, 2000 ) ); +} + +void NPC_BSSniper_Attack( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //NPC_CheckEnemy( qtrue, qfalse ); + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse )//!NPC->enemy )// + { + NPC->enemy = NULL; + NPC_BSSniper_Patrol();//FIXME: or patrol? + return; + } + + if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//going to run + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( !NPC->enemy ) + {//WTF? somehow we lost our enemy? + NPC_BSSniper_Patrol();//FIXME: or patrol? + return; + } + + enemyLOS2 = enemyCS2 = qfalse; + move2 = qtrue; + faceEnemy2 = qfalse; + shoot2 = qfalse; + enemyDist2 = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + if ( enemyDist2 < 16384 )//128 squared + {//too close, so switch to primary fire + if ( NPC->client->ps.weapon == WP_DISRUPTOR ) + {//sniping... should be assumed + if ( NPCInfo->scriptFlags & SCF_ALT_FIRE ) + {//use primary fire + trace_t trace; + trap_Trace ( &trace, NPC->enemy->r.currentOrigin, NPC->enemy->r.mins, NPC->enemy->r.maxs, NPC->r.currentOrigin, NPC->enemy->s.number, NPC->enemy->clipmask ); + if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->s.number ) ) + {//he can get right to me + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( WP_DISRUPTOR ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + //FIXME: switch back if he gets far away again? + } + } + else if ( enemyDist2 > 65536 )//256 squared + { + if ( NPC->client->ps.weapon == WP_DISRUPTOR ) + {//sniping... should be assumed + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + {//use primary fire + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( WP_DISRUPTOR ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + Sniper_UpdateEnemyPos(); + //can we see our target? + if ( NPC_ClearLOS4( NPC->enemy ) )//|| (NPCInfo->stats.aim >= 5 && gi.inPVS( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin )) ) + { + float maxShootDist; + + NPCInfo->enemyLastSeenTime = level.time; + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + enemyLOS2 = qtrue; + maxShootDist = NPC_MaxDistSquaredForWeapon(); + if ( enemyDist2 < maxShootDist ) + { + vec3_t fwd, right, up, muzzle, end; + trace_t tr; + int hit; + + AngleVectors( NPC->client->ps.viewangles, fwd, right, up ); + CalcMuzzlePoint( NPC, fwd, right, up, muzzle ); + VectorMA( muzzle, 8192, fwd, end ); + trap_Trace ( &tr, muzzle, NULL, NULL, end, NPC->s.number, MASK_SHOT ); + + hit = tr.entityNum; + //can we shoot our target? + if ( Sniper_EvaluateShot( hit ) ) + { + enemyCS2 = qtrue; + } + } + } + /* + else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy2 = qtrue; + } + */ + + if ( enemyLOS2 ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy2 = qtrue; + } + if ( enemyCS2 ) + { + shoot2 = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 3000 ) + {//Hmm, have to get around this bastard... FIXME: this NPCInfo->enemyLastSeenTime builds up when ducked seems to make them want to run when they uncrouch + Sniper_ResolveBlockedShot(); + } + + //Check for movement to take care of + Sniper_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + Sniper_CheckFireState(); + + if ( move2 ) + {//move toward goal + if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist2 > 10000 ) )//100 squared + { + move2 = Sniper_Move(); + } + else + { + move2 = qfalse; + } + } + + if ( !move2 ) + { + if ( !TIMER_Done( NPC, "duck" ) ) + { + if ( TIMER_Done( NPC, "watch" ) ) + {//not while watching + ucmd.upmove = -127; + } + } + //FIXME: what about leaning? + //FIXME: also, when stop ducking, start looking, if enemy can see me, chance of ducking back down again + } + else + {//stop ducking! + TIMER_Set( NPC, "duck", -1 ); + } + + if ( TIMER_Done( NPC, "duck" ) + && TIMER_Done( NPC, "watch" ) + && (TIMER_Get( NPC, "attackDelay" )-level.time) > 1000 + && NPC->attackDebounceTime < level.time ) + { + if ( enemyLOS2 && (NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + { + if ( NPC->fly_sound_debounce_time < level.time ) + { + NPC->fly_sound_debounce_time = level.time + 2000; + } + } + } + + if ( !faceEnemy2 ) + {//we want to face in the dir we're running + if ( move2 ) + {//don't run away and shoot + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + shoot2 = qfalse; + } + NPC_UpdateAngles( qtrue, qtrue ); + } + else// if ( faceEnemy2 ) + {//face the enemy + Sniper_FaceEnemy(); + } + + if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) + { + shoot2 = qfalse; + } + + //FIXME: don't shoot right away! + if ( shoot2 ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + WeaponThink( qtrue ); + if ( ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK) ) + { + G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/null.wav" ); + } + + //took a shot, now hide + if ( !(NPC->spawnflags&SPF_NO_HIDE) && !Q_irand( 0, 1 ) ) + { + //FIXME: do this if in combat point and combat point has duck-type cover... also handle lean-type cover + Sniper_StartHide(); + } + else + { + TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time ); + } + } + } +} + +void NPC_BSSniper_Default( void ) +{ + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSSniper_Patrol(); + } + else//if ( NPC->enemy ) + {//have an enemy + NPC_BSSniper_Attack(); + } +} diff --git a/codemp/game/NPC_AI_Stormtrooper.c b/codemp/game/NPC_AI_Stormtrooper.c new file mode 100644 index 0000000..214c24a --- /dev/null +++ b/codemp/game/NPC_AI_Stormtrooper.c @@ -0,0 +1,2742 @@ +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" + +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState ); +extern qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum ); +extern void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot ); +extern void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void NPC_CheckGetNewWeapon( void ); +extern int GetTime ( int lastTime ); +extern void NPC_AimAdjust( int change ); +extern qboolean FlyingCreature( gentity_t *ent ); + +extern vmCvar_t d_asynchronousGroupAI; + +#define MAX_VIEW_DIST 1024 +#define MAX_VIEW_SPEED 250 +#define MAX_LIGHT_INTENSITY 255 +#define MIN_LIGHT_THRESHOLD 0.1 +#define ST_MIN_LIGHT_THRESHOLD 30 +#define ST_MAX_LIGHT_THRESHOLD 180 +#define DISTANCE_THRESHOLD 0.075f + +#define DISTANCE_SCALE 0.35f //These first three get your base detection rating, ideally add up to 1 +#define FOV_SCALE 0.40f // +#define LIGHT_SCALE 0.25f // + +#define SPEED_SCALE 0.25f //These next two are bonuses +#define TURNING_SCALE 0.25f // + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 ) + +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS; +static qboolean enemyCS; +static qboolean enemyInFOV; +static qboolean hitAlly; +static qboolean faceEnemy; +static qboolean move; +static qboolean shoot; +static float enemyDist; +static vec3_t impactPos; + +int groupSpeechDebounceTime[TEAM_NUM_TEAMS];//used to stop several group AI from speaking all at once + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void ST_AggressionAdjust( gentity_t *self, int change ) +{ + int upper_threshold, lower_threshold; + + self->NPC->stats.aggression += change; + + //FIXME: base this on initial NPC stats + if ( self->client->playerTeam == NPCTEAM_PLAYER ) + {//good guys are less aggressive + upper_threshold = 7; + lower_threshold = 1; + } + else + {//bad guys are more aggressive + upper_threshold = 10; + lower_threshold = 3; + } + + if ( self->NPC->stats.aggression > upper_threshold ) + { + self->NPC->stats.aggression = upper_threshold; + } + else if ( self->NPC->stats.aggression < lower_threshold ) + { + self->NPC->stats.aggression = lower_threshold; + } +} + +void ST_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "duck", 0 ); + TIMER_Set( ent, "stand", 0 ); + TIMER_Set( ent, "shuffleTime", 0 ); + TIMER_Set( ent, "sleepTime", 0 ); + TIMER_Set( ent, "enemyLastVisible", 0 ); + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "hideTime", 0 ); + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "stick", 0 ); + TIMER_Set( ent, "scoutTime", 0 ); + TIMER_Set( ent, "flee", 0 ); + TIMER_Set( ent, "interrogating", 0 ); + TIMER_Set( ent, "verifyCP", 0 ); +} + +enum +{ + SPEECH_CHASE, + SPEECH_CONFUSED, + SPEECH_COVER, + SPEECH_DETECTED, + SPEECH_GIVEUP, + SPEECH_LOOK, + SPEECH_LOST, + SPEECH_OUTFLANK, + SPEECH_ESCAPING, + SPEECH_SIGHT, + SPEECH_SOUND, + SPEECH_SUSPICIOUS, + SPEECH_YELL, + SPEECH_PUSHED +}; + +static void ST_Speech( gentity_t *self, int speechType, float failChance ) +{ + if ( random() < failChance ) + { + return; + } + + if ( failChance >= 0 ) + {//a negative failChance makes it always talk + if ( self->NPC->group ) + {//group AI speech debounce timer + if ( self->NPC->group->speechDebounceTime > level.time ) + { + return; + } + /* + else if ( !self->NPC->group->enemy ) + { + if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time ) + { + return; + } + } + */ + } + else if ( !TIMER_Done( self, "chatter" ) ) + {//personal timer + return; + } + else if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time ) + {//for those not in group AI + //FIXME: let certain speech types interrupt others? Let closer NPCs interrupt farther away ones? + return; + } + } + + if ( self->NPC->group ) + {//So they don't all speak at once... + //FIXME: if they're not yet mad, they have no group, so distracting a group of them makes them all speak! + self->NPC->group->speechDebounceTime = level.time + Q_irand( 2000, 4000 ); + } + else + { + TIMER_Set( self, "chatter", Q_irand( 2000, 4000 ) ); + } + groupSpeechDebounceTime[self->client->playerTeam] = level.time + Q_irand( 2000, 4000 ); + + if ( self->NPC->blockedSpeechDebounceTime > level.time ) + { + return; + } + + switch( speechType ) + { + case SPEECH_CHASE: + G_AddVoiceEvent( self, Q_irand(EV_CHASE1, EV_CHASE3), 2000 ); + break; + case SPEECH_CONFUSED: + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + break; + case SPEECH_COVER: + G_AddVoiceEvent( self, Q_irand(EV_COVER1, EV_COVER5), 2000 ); + break; + case SPEECH_DETECTED: + G_AddVoiceEvent( self, Q_irand(EV_DETECTED1, EV_DETECTED5), 2000 ); + break; + case SPEECH_GIVEUP: + G_AddVoiceEvent( self, Q_irand(EV_GIVEUP1, EV_GIVEUP4), 2000 ); + break; + case SPEECH_LOOK: + G_AddVoiceEvent( self, Q_irand(EV_LOOK1, EV_LOOK2), 2000 ); + break; + case SPEECH_LOST: + G_AddVoiceEvent( self, EV_LOST1, 2000 ); + break; + case SPEECH_OUTFLANK: + G_AddVoiceEvent( self, Q_irand(EV_OUTFLANK1, EV_OUTFLANK2), 2000 ); + break; + case SPEECH_ESCAPING: + G_AddVoiceEvent( self, Q_irand(EV_ESCAPING1, EV_ESCAPING3), 2000 ); + break; + case SPEECH_SIGHT: + G_AddVoiceEvent( self, Q_irand(EV_SIGHT1, EV_SIGHT3), 2000 ); + break; + case SPEECH_SOUND: + G_AddVoiceEvent( self, Q_irand(EV_SOUND1, EV_SOUND3), 2000 ); + break; + case SPEECH_SUSPICIOUS: + G_AddVoiceEvent( self, Q_irand(EV_SUSPICIOUS1, EV_SUSPICIOUS5), 2000 ); + break; + case SPEECH_YELL: + G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 2000 ); + break; + case SPEECH_PUSHED: + G_AddVoiceEvent( self, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 2000 ); + break; + default: + break; + } + + self->NPC->blockedSpeechDebounceTime = level.time + 2000; +} + +void ST_MarkToCover( gentity_t *self ) +{ + if ( !self || !self->NPC ) + { + return; + } + self->NPC->localState = LSTATE_UNDERFIRE; + TIMER_Set( self, "attackDelay", Q_irand( 500, 2500 ) ); + ST_AggressionAdjust( self, -3 ); + if ( self->NPC->group && self->NPC->group->numGroup > 1 ) + { + ST_Speech( self, SPEECH_COVER, 0 );//FIXME: flee sound? + } +} + +void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime ) +{ + if ( !self || !self->NPC ) + { + return; + } + G_StartFlee( self, enemy, dangerPoint, dangerLevel, minTime, maxTime ); + if ( self->NPC->group && self->NPC->group->numGroup > 1 ) + { + ST_Speech( self, SPEECH_COVER, 0 );//FIXME: flee sound? + } +} +/* +------------------------- +NPC_ST_Pain +------------------------- +*/ + +void NPC_ST_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + self->NPC->localState = LSTATE_UNDERFIRE; + + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "hideTime", -1 ); + TIMER_Set( self, "stand", 2000 ); + + NPC_Pain( self, attacker, damage ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } +} + +/* +------------------------- +ST_HoldPosition +------------------------- +*/ + +static void ST_HoldPosition( void ) +{ + if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + TIMER_Set( NPC, "flee", -level.time ); + } + TIMER_Set( NPC, "verifyCP", Q_irand( 1000, 3000 ) );//don't look for another one for a few seconds + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + //NPCInfo->combatPoint = -1;//??? + if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//don't have a script waiting for me to get to my point, okay to stop trying and stand + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + NPCInfo->goalEntity = NULL; + } + + /*if ( TIMER_Done( NPC, "stand" ) ) + {//FIXME: what if can't shoot from this pos? + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +void NPC_ST_SayMovementSpeech( void ) +{ + if ( !NPCInfo->movementSpeech ) + { + return; + } + if ( NPCInfo->group && + NPCInfo->group->commander && + NPCInfo->group->commander->client && + NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL && + !Q_irand( 0, 3 ) ) + {//imperial (commander) gives the order + ST_Speech( NPCInfo->group->commander, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance ); + } + else + {//really don't want to say this unless we can actually get there... + ST_Speech( NPC, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance ); + } + + NPCInfo->movementSpeech = 0; + NPCInfo->movementSpeechChance = 0.0f; +} + +void NPC_ST_StoreMovementSpeech( int speech, float chance ) +{ + NPCInfo->movementSpeech = speech; + NPCInfo->movementSpeechChance = chance; +} +/* +------------------------- +ST_Move +------------------------- +*/ +void ST_TransferMoveGoal( gentity_t *self, gentity_t *other ); +static qboolean ST_Move( void ) +{ + qboolean moved; + navInfo_t info; + + NPCInfo->combatMove = qtrue;//always move straight toward our goal + + moved = NPC_MoveToGoal( qtrue ); + + //Get the move info + NAV_GetLastMove( &info ); + + //FIXME: if we bump into another one of our guys and can't get around him, just stop! + //If we hit our target, then stop and fire! + if ( info.flags & NIF_COLLISION ) + { + if ( info.blocker == NPC->enemy ) + { + ST_HoldPosition(); + } + } + + //If our move failed, then reset + if ( moved == qfalse ) + {//FIXME: if we're going to a combat point, need to pick a different one + if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//can't transfer movegoal or stop when a script we're running is waiting to complete + if ( info.blocker && info.blocker->NPC && NPCInfo->group != NULL && info.blocker->NPC->group == NPCInfo->group )//(NPCInfo->aiFlags&NPCAI_BLOCKED) && NPCInfo->group != NULL ) + {//dammit, something is in our way + //see if it's one of ours + int j; + + for ( j = 0; j < NPCInfo->group->numGroup; j++ ) + { + if ( NPCInfo->group->member[j].number == NPCInfo->blockingEntNum ) + {//we're being blocked by one of our own, pass our goal onto them and I'll stand still + ST_TransferMoveGoal( NPC, &g_entities[NPCInfo->group->member[j].number] ); + break; + } + } + } + + ST_HoldPosition(); + } + } + else + { + //First time you successfully move, say what it is you're doing + NPC_ST_SayMovementSpeech(); + } + + return moved; +} + + +/* +------------------------- +NPC_ST_SleepShuffle +------------------------- +*/ + +static void NPC_ST_SleepShuffle( void ) +{ + //Play an awake script if we have one + if ( G_ActivateBehavior( NPC, BSET_AWAKE) ) + { + return; + } + + //Automate some movement and noise + if ( TIMER_Done( NPC, "shuffleTime" ) ) + { + + //TODO: Play sleeping shuffle animation + + //int soundIndex = Q_irand( 0, 1 ); + + /* + switch ( soundIndex ) + { + case 0: + G_Sound( NPC, G_SoundIndex("sound/chars/imperialsleeper1/scav4/hunh.mp3") ); + break; + + case 1: + G_Sound( NPC, G_SoundIndex("sound/chars/imperialsleeper3/scav4/tryingtosleep.wav") ); + break; + } + */ + + TIMER_Set( NPC, "shuffleTime", 4000 ); + TIMER_Set( NPC, "sleepTime", 2000 ); + return; + } + + //They made another noise while we were stirring, see if we can see them + if ( TIMER_Done( NPC, "sleepTime" ) ) + { + NPC_CheckPlayerTeamStealth(); + TIMER_Set( NPC, "sleepTime", 2000 ); + } +} + +/* +------------------------- +NPC_ST_Sleep +------------------------- +*/ + +void NPC_BSST_Sleep( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qfalse, qtrue, -1, qfalse, AEL_MINOR );//only check sounds since we're alseep! + + //There is an event we heard + if ( alertEvent >= 0 ) + { + //See if it was enough to wake us up + if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { //rwwFIXMEFIXME: Care about all clients not just 0 + if ( &g_entities[0] && g_entities[0].health > 0 ) + { + G_SetEnemy( NPC, &g_entities[0] ); + return; + } + } + + //Otherwise just stir a bit + NPC_ST_SleepShuffle(); + return; + } +} + +/* +------------------------- +NPC_CheckEnemyStealth +------------------------- +*/ + +qboolean NPC_CheckEnemyStealth( gentity_t *target ) +{ + float target_dist, minDist = 40;//any closer than 40 and we definitely notice + float maxViewDist; + qboolean clearLOS; + + //In case we aquired one some other way + if ( NPC->enemy != NULL ) + return qtrue; + + //Ignore notarget + if ( target->flags & FL_NOTARGET ) + return qfalse; + + if ( target->health <= 0 ) + { + return qfalse; + } + + if ( target->client->ps.weapon == WP_SABER && !target->client->ps.saberHolstered && !target->client->ps.saberInFlight ) + {//if target has saber in hand and activated, we wake up even sooner even if not facing him + minDist = 100; + } + + target_dist = DistanceSquared( target->r.currentOrigin, NPC->r.currentOrigin ); + + //If the target is this close, then wake up regardless + if ( !(target->client->ps.pm_flags&PMF_DUCKED) + && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) + && (target_dist) < (minDist*minDist) ) + { + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + maxViewDist = MAX_VIEW_DIST; + + if ( NPCInfo->stats.visrange > maxViewDist ) + {//FIXME: should we always just set maxViewDist to this? + maxViewDist = NPCInfo->stats.visrange; + } + + if ( target_dist > (maxViewDist*maxViewDist) ) + {//out of possible visRange + return qfalse; + } + + //Check FOV first + if ( InFOV( target, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) + return qfalse; + + //clearLOS = ( target->client->ps.leanofs ) ? NPC_ClearLOS5( target->client->renderInfo.eyePoint ) : NPC_ClearLOS4( target ); + clearLOS = NPC_ClearLOS4( target ); + + //Now check for clear line of vision + if ( clearLOS ) + { + vec3_t targ_org; + float hAngle_perc; + float vAngle_perc; + float target_speed; + int target_crouching; + float dist_rating; + float speed_rating; + float turning_rating; + float light_level; + float FOV_perc; + float vis_rating; + float dist_influence; + float fov_influence; + float light_influence; + float target_rating; + int contents; + float realize, cautious; + + if ( target->client->NPC_class == CLASS_ATST ) + {//can't miss 'em! + G_SetEnemy( NPC, target ); + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + VectorSet(targ_org, target->r.currentOrigin[0],target->r.currentOrigin[1],target->r.currentOrigin[2]+target->r.maxs[2]-4); + hAngle_perc = NPC_GetHFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.hfov ); + vAngle_perc = NPC_GetVFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.vfov ); + + //Scale them vertically some, and horizontally pretty harshly + vAngle_perc *= vAngle_perc;//( vAngle_perc * vAngle_perc ); + hAngle_perc *= ( hAngle_perc * hAngle_perc ); + + //Cap our vertical vision severely + //if ( vAngle_perc <= 0.3f ) // was 0.5f + // return qfalse; + + //Assess the player's current status + target_dist = Distance( target->r.currentOrigin, NPC->r.currentOrigin ); + + target_speed = VectorLength( target->client->ps.velocity ); + target_crouching = ( target->client->pers.cmd.upmove < 0 ); + dist_rating = ( target_dist / maxViewDist ); + speed_rating = ( target_speed / MAX_VIEW_SPEED ); + turning_rating = 5.0f;//AngleDelta( target->client->ps.viewangles[PITCH], target->lastAngles[PITCH] )/180.0f + AngleDelta( target->client->ps.viewangles[YAW], target->lastAngles[YAW] )/180.0f; + light_level = (255/MAX_LIGHT_INTENSITY); //( target->lightLevel / MAX_LIGHT_INTENSITY ); + FOV_perc = 1.0f - ( hAngle_perc + vAngle_perc ) * 0.5f; //FIXME: Dunno about the average... + vis_rating = 0.0f; + + //Too dark + if ( light_level < MIN_LIGHT_THRESHOLD ) + return qfalse; + + //Too close? + if ( dist_rating < DISTANCE_THRESHOLD ) + { + G_SetEnemy( NPC, target ); + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + //Out of range + if ( dist_rating > 1.0f ) + return qfalse; + + //Cap our speed checks + if ( speed_rating > 1.0f ) + speed_rating = 1.0f; + + + //Calculate the distance, fov and light influences + //...Visibilty linearly wanes over distance + dist_influence = DISTANCE_SCALE * ( ( 1.0f - dist_rating ) ); + //...As the percentage out of the FOV increases, straight perception suffers on an exponential scale + fov_influence = FOV_SCALE * ( 1.0f - FOV_perc ); + //...Lack of light hides, abundance of light exposes + light_influence = ( light_level - 0.5f ) * LIGHT_SCALE; + + //Calculate our base rating + target_rating = dist_influence + fov_influence + light_influence; + + //Now award any final bonuses to this number + contents = trap_PointContents( targ_org, target->s.number ); + if ( contents&CONTENTS_WATER ) + { + int myContents = trap_PointContents( NPC->client->renderInfo.eyePoint, NPC->s.number ); + if ( !(myContents&CONTENTS_WATER) ) + {//I'm not in water + if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER ) + {//these guys can see in in/through water pretty well + vis_rating = 0.10f;//10% bonus + } + else + { + vis_rating = 0.35f;//35% bonus + } + } + else + {//else, if we're both in water + if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER ) + {//I can see him just fine + } + else + { + vis_rating = 0.15f;//15% bonus + } + } + } + else + {//not in water + if ( contents&CONTENTS_FOG ) + { + vis_rating = 0.15f;//15% bonus + } + } + + target_rating *= (1.0f - vis_rating); + + //...Motion draws the eye quickly + target_rating += speed_rating * SPEED_SCALE; + target_rating += turning_rating * TURNING_SCALE; + //FIXME: check to see if they're animating, too? But can we do something as simple as frame != oldframe? + + //...Smaller targets are harder to indentify + if ( target_crouching ) + { + target_rating *= 0.9f; //10% bonus + } + + //If he's violated the threshold, then realize him + //float difficulty_scale = 1.0f + (2.0f-g_spskill.value);//if playing on easy, 20% harder to be seen...? + if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER ) + {//swamptroopers can see much better + realize = (float)CAUTIOUS_THRESHOLD/**difficulty_scale*/; + cautious = (float)CAUTIOUS_THRESHOLD * 0.75f/**difficulty_scale*/; + } + else + { + realize = (float)REALIZE_THRESHOLD/**difficulty_scale*/; + cautious = (float)CAUTIOUS_THRESHOLD * 0.75f/**difficulty_scale*/; + } + + if ( target_rating > realize && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + //If he's above the caution threshold, then realize him in a few seconds unless he moves to cover + if ( target_rating > cautious && !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + {//FIXME: ambushing guys should never talk + if ( TIMER_Done( NPC, "enemyLastVisible" ) ) + {//If we haven't already, start the counter + int lookTime = Q_irand( 4500, 8500 ); + //NPCInfo->timeEnemyLastVisible = level.time + 2000; + TIMER_Set( NPC, "enemyLastVisible", lookTime ); + //TODO: Play a sound along the lines of, "Huh? What was that?" + ST_Speech( NPC, SPEECH_SIGHT, 0 ); + NPC_TempLookTarget( NPC, target->s.number, lookTime, lookTime ); + //FIXME: set desired yaw and pitch towards this guy? + } + else if ( TIMER_Get( NPC, "enemyLastVisible" ) <= level.time + 500 && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) //FIXME: Is this reliable? + { + if ( NPCInfo->rank < RANK_LT && !Q_irand( 0, 2 ) ) + { + int interrogateTime = Q_irand( 2000, 4000 ); + ST_Speech( NPC, SPEECH_SUSPICIOUS, 0 ); + TIMER_Set( NPC, "interrogating", interrogateTime ); + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", interrogateTime ); + TIMER_Set( NPC, "stand", interrogateTime ); + } + else + { + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + //FIXME: ambush guys (like those popping out of water) shouldn't delay... + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + TIMER_Set( NPC, "stand", Q_irand( 500, 2500 ) ); + } + return qtrue; + } + + return qfalse; + } + } + + return qfalse; +} + +qboolean NPC_CheckPlayerTeamStealth( void ) +{ + /* + //NOTENOTE: For now, all stealh checks go against the player, since + // he is the main focus. Squad members and rivals do not + // fall into this category and will be ignored. + + NPC_CheckEnemyStealth( &g_entities[0] ); //Change this pointer to assess other entities + */ + gentity_t *enemy; + int i; + + for ( i = 0; i < ENTITYNUM_WORLD; i++ ) + { + enemy = &g_entities[i]; + + if (!enemy->inuse) + { + continue; + } + + if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam ) + { + if ( NPC_CheckEnemyStealth( enemy ) ) //Change this pointer to assess other entities + { + return qtrue; + } + } + } + return qfalse; +} +/* +------------------------- +NPC_ST_InvestigateEvent +------------------------- +*/ + +#define MAX_CHECK_THRESHOLD 1 + +static qboolean NPC_ST_InvestigateEvent( int eventID, qboolean extraSuspicious ) +{ + //If they've given themselves away, just take them as an enemy + if ( NPCInfo->confusionTime < level.time ) + { + if ( level.alertEvents[eventID].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { + NPCInfo->lastAlertID = level.alertEvents[eventID].ID; + if ( !level.alertEvents[eventID].owner || + !level.alertEvents[eventID].owner->client || + level.alertEvents[eventID].owner->health <= 0 || + level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam ) + {//not an enemy + return qfalse; + } + //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him? Or just let combat AI handle this... (act as if you lost him) + //ST_Speech( NPC, SPEECH_CHARGE, 0 ); + G_SetEnemy( NPC, level.alertEvents[eventID].owner ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + if ( level.alertEvents[eventID].type == AET_SOUND ) + {//heard him, didn't see him, stick for a bit + TIMER_Set( NPC, "roamTime", Q_irand( 500, 2500 ) ); + } + return qtrue; + } + } + + //don't look at the same alert twice + if ( level.alertEvents[eventID].ID == NPCInfo->lastAlertID ) + { + return qfalse; + } + NPCInfo->lastAlertID = level.alertEvents[eventID].ID; + + //Must be ready to take another sound event + /* + if ( NPCInfo->investigateSoundDebounceTime > level.time ) + { + return qfalse; + } + */ + + if ( level.alertEvents[eventID].type == AET_SIGHT ) + {//sight alert, check the light level + if ( level.alertEvents[eventID].light < Q_irand( ST_MIN_LIGHT_THRESHOLD, ST_MAX_LIGHT_THRESHOLD ) ) + {//below my threshhold of potentially seeing + return qfalse; + } + } + + //Save the position for movement (if necessary) + VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal ); + + //First awareness of it + NPCInfo->investigateCount += ( extraSuspicious ) ? 2 : 1; + + //Clamp the value + if ( NPCInfo->investigateCount > 4 ) + NPCInfo->investigateCount = 4; + + //See if we should walk over and investigate + if ( level.alertEvents[eventID].level > AEL_MINOR && NPCInfo->investigateCount > 1 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + { + //make it so they can walk right to this point and look at it rather than having to use combatPoints + if ( G_ExpandPointToBBox( NPCInfo->investigateGoal, NPC->r.mins, NPC->r.maxs, NPC->s.number, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) ) ) + {//we were able to move the investigateGoal to a point in which our bbox would fit + //drop the goal to the ground so we can get at it + vec3_t end; + trace_t trace; + VectorCopy( NPCInfo->investigateGoal, end ); + end[2] -= 512;//FIXME: not always right? What if it's even higher, somehow? + trap_Trace( &trace, NPCInfo->investigateGoal, NPC->r.mins, NPC->r.maxs, end, ENTITYNUM_NONE, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) ); + if ( trace.fraction >= 1.0f ) + {//too high to even bother + //FIXME: look at them??? + } + else + { + VectorCopy( trace.endpos, NPCInfo->investigateGoal ); + NPC_SetMoveGoal( NPC, NPCInfo->investigateGoal, 16, qtrue, -1, NULL ); + NPCInfo->localState = LSTATE_INVESTIGATE; + } + } + else + { + int id = NPC_FindCombatPoint( NPCInfo->investigateGoal, NPCInfo->investigateGoal, NPCInfo->investigateGoal, CP_INVESTIGATE|CP_HAS_ROUTE, 0, -1 ); + + if ( id != -1 ) + { + NPC_SetMoveGoal( NPC, level.combatPoints[id].origin, 16, qtrue, id, NULL ); + NPCInfo->localState = LSTATE_INVESTIGATE; + } + } + //Say something + //FIXME: only if have others in group... these should be responses? + if ( NPCInfo->investigateDebounceTime+NPCInfo->pauseTime > level.time ) + {//was already investigating + if ( NPCInfo->group && + NPCInfo->group->commander && + NPCInfo->group->commander->client && + NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL && + !Q_irand( 0, 3 ) ) + { + ST_Speech( NPCInfo->group->commander, SPEECH_LOOK, 0 );//FIXME: "I'll go check it out" type sounds + } + else + { + ST_Speech( NPC, SPEECH_LOOK, 0 );//FIXME: "I'll go check it out" type sounds + } + } + else + { + if ( level.alertEvents[eventID].type == AET_SIGHT ) + { + ST_Speech( NPC, SPEECH_SIGHT, 0 ); + } + else if ( level.alertEvents[eventID].type == AET_SOUND ) + { + ST_Speech( NPC, SPEECH_SOUND, 0 ); + } + } + //Setup the debounce info + NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000; + NPCInfo->investigateSoundDebounceTime = level.time + 2000; + NPCInfo->pauseTime = level.time; + } + else + {//just look? + //Say something + if ( level.alertEvents[eventID].type == AET_SIGHT ) + { + ST_Speech( NPC, SPEECH_SIGHT, 0 ); + } + else if ( level.alertEvents[eventID].type == AET_SOUND ) + { + ST_Speech( NPC, SPEECH_SOUND, 0 ); + } + //Setup the debounce info + NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 1000; + NPCInfo->investigateSoundDebounceTime = level.time + 1000; + NPCInfo->pauseTime = level.time; + VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal ); + } + + if ( level.alertEvents[eventID].level >= AEL_DANGER ) + { + NPCInfo->investigateDebounceTime = Q_irand( 500, 2500 ); + } + + //Start investigating + NPCInfo->tempBehavior = BS_INVESTIGATE; + return qtrue; +} + +/* +------------------------- +ST_OffsetLook +------------------------- +*/ + +static void ST_OffsetLook( float offset, vec3_t out ) +{ + vec3_t angles, forward, temp; + + GetAnglesForDirection( NPC->r.currentOrigin, NPCInfo->investigateGoal, angles ); + angles[YAW] += offset; + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( NPC->r.currentOrigin, 64, forward, out ); + + CalcEntitySpot( NPC, SPOT_HEAD, temp ); + out[2] = temp[2]; +} + +/* +------------------------- +ST_LookAround +------------------------- +*/ + +static void ST_LookAround( void ) +{ + vec3_t lookPos; + float perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime; + + //Keep looking at the spot + if ( perc < 0.25 ) + { + VectorCopy( NPCInfo->investigateGoal, lookPos ); + } + else if ( perc < 0.5f ) //Look up but straight ahead + { + ST_OffsetLook( 0.0f, lookPos ); + } + else if ( perc < 0.75f ) //Look right + { + ST_OffsetLook( 45.0f, lookPos ); + } + else //Look left + { + ST_OffsetLook( -45.0f, lookPos ); + } + + NPC_FacePosition( lookPos, qtrue ); +} + +/* +------------------------- +NPC_BSST_Investigate +------------------------- +*/ + +void NPC_BSST_Investigate( void ) +{ + //get group- mainly for group speech debouncing, but may use for group scouting/investigating AI, too + AI_GetGroup( NPC ); + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( NPCInfo->confusionTime < level.time ) + { + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + //Look for an enemy + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + ST_Speech( NPC, SPEECH_DETECTED, 0 ); + NPCInfo->tempBehavior = BS_DEFAULT; + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, NPCInfo->lastAlertID, qfalse, AEL_MINOR ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + if ( NPCInfo->confusionTime < level.time ) + { + if ( NPC_CheckForDanger( alertEvent ) ) + {//running like hell + ST_Speech( NPC, SPEECH_COVER, 0 );//FIXME: flee sound? + return; + } + } + + if ( level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + NPC_ST_InvestigateEvent( alertEvent, qtrue ); + } + } + } + + //If we're done looking, then just return to what we were doing + if ( ( NPCInfo->investigateDebounceTime + NPCInfo->pauseTime ) < level.time ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + NPCInfo->goalEntity = UpdateGoal(); + + NPC_UpdateAngles( qtrue, qtrue ); + //Say something + ST_Speech( NPC, SPEECH_GIVEUP, 0 ); + return; + } + + //FIXME: else, look for new alerts + + //See if we're searching for the noise's origin + if ( NPCInfo->localState == LSTATE_INVESTIGATE && (NPCInfo->goalEntity!=NULL) ) + { + //See if we're there + if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 32, FlyingCreature( NPC ) ) == qfalse ) + { + ucmd.buttons |= BUTTON_WALKING; + + //Try and move there + if ( NPC_MoveToGoal( qtrue ) ) + { + //Bump our times + NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000; + NPCInfo->pauseTime = level.time; + + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + //Otherwise we're done or have given up + //Say something + //ST_Speech( NPC, SPEECH_LOOK, 0.33f ); + NPCInfo->localState = LSTATE_NONE; + } + + //Look around + ST_LookAround(); +} + +/* +------------------------- +NPC_BSST_Patrol +------------------------- +*/ + +void NPC_BSST_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + + //get group- mainly for group speech debouncing, but may use for group scouting/investigating AI, too + AI_GetGroup( NPC ); + + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + if ( NPC_ST_InvestigateEvent( alertEvent, qfalse ) ) + {//actually going to investigate it + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + //ST_Move( NPCInfo->goalEntity ); + NPC_MoveToGoal( qtrue ); + } + else// if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + if ( NPC->client->NPC_class != CLASS_IMPERIAL && NPC->client->NPC_class != CLASS_IMPWORKER ) + {//imperials do not look around + if ( TIMER_Done( NPC, "enemyLastVisible" ) ) + {//nothing suspicious, look around + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredYaw = NPC->s.angles[1] + Q_irand( -90, 90 ); + } + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredPitch = Q_irand( -20, 20 ); + } + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); + //TEMP hack for Imperial stand anim + if ( NPC->client->NPC_class == CLASS_IMPERIAL || NPC->client->NPC_class == CLASS_IMPWORKER ) + {//hack + if ( ucmd.forwardmove || ucmd.rightmove || ucmd.upmove ) + {//moving + + if( (NPC->client->ps.torsoTimer <= 0) || (NPC->client->ps.torsoAnim == BOTH_STAND4) ) + { + if ( (ucmd.buttons&BUTTON_WALKING) && !(NPCInfo->scriptFlags&SCF_RUNNING) ) + {//not running, only set upper anim + // No longer overrides scripted anims + NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoTimer = 200; + } + } + } + else + {//standing still, set both torso and legs anim + // No longer overrides scripted anims + if( ( NPC->client->ps.torsoTimer <= 0 || (NPC->client->ps.torsoAnim == BOTH_STAND4) ) && + ( NPC->client->ps.legsTimer <= 0 || (NPC->client->ps.legsAnim == BOTH_STAND4) ) ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoTimer = NPC->client->ps.legsTimer = 200; + } + } + //FIXME: this is a disgusting hack that is supposed to make the Imperials start with their weapon holstered- need a better way + if ( NPC->client->ps.weapon != WP_NONE ) + { + ChangeWeapon( NPC, WP_NONE ); + NPC->client->ps.weapon = WP_NONE; + NPC->client->ps.weaponstate = WEAPON_READY; + /* + if ( NPC->weaponModel[0] > 0 ) + { + gi.G2API_RemoveGhoul2Model( NPC->ghoul2, NPC->weaponModel[0] ); + NPC->weaponModel[0] = -1; + } + */ + //rwwFIXMEFIXME: Do this? + } + } +} + +/* +------------------------- +NPC_BSST_Idle +------------------------- +*/ +/* +void NPC_BSST_Idle( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + NPC_ST_InvestigateEvent( alertEvent, qfalse ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) ); + + NPC_UpdateAngles( qtrue, qtrue ); +} +*/ +/* +------------------------- +ST_CheckMoveState +------------------------- +*/ + +static void ST_CheckMoveState( void ) +{ + if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//moving toward a goal that a script is waiting on, so don't stop for anything! + move = qtrue; + } + //See if we're a scout + else if ( NPCInfo->squadState == SQUAD_SCOUT ) + { + //If we're supposed to stay put, then stand there and fire + if ( TIMER_Done( NPC, "stick" ) == qfalse ) + { + move = qfalse; + return; + } + + //Otherwise, if we can see our target, just shoot + if ( enemyLOS ) + { + if ( enemyCS ) + { + //if we're going after our enemy, we can stop now + if ( NPCInfo->goalEntity == NPC->enemy ) + { + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + move = qfalse; + return; + } + } + } + else + { + //Move to find our target + faceEnemy = qfalse; + } + + /* + if ( TIMER_Done( NPC, "scoutTime" ) ) + {//we can't scout to him, someone else give it a try + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 2000 ) ); + move = qfalse; + return; + } + */ + + //ucmd.buttons |= BUTTON_CAREFUL; + } + //See if we're running away + else if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + if ( NPCInfo->goalEntity ) + { + faceEnemy = qfalse; + } + else + {//um, lost our goal? Just stand and shoot, then + NPCInfo->squadState = SQUAD_STAND_AND_SHOOT; + } + } + //see if we're heading to some other combatPoint + else if ( NPCInfo->squadState == SQUAD_TRANSITION ) + { + //ucmd.buttons |= BUTTON_CAREFUL; + if ( !NPCInfo->goalEntity ) + {//um, lost our goal? Just stand and shoot, then + NPCInfo->squadState = SQUAD_STAND_AND_SHOOT; + } + } + //see if we're at point, duck and fire + else if ( NPCInfo->squadState == SQUAD_POINT ) + { + if ( TIMER_Done( NPC, "stick" ) ) + { + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + return; + } + + move = qfalse; + return; + } + //see if we're just standing around + else if ( NPCInfo->squadState == SQUAD_STAND_AND_SHOOT ) + {//from this squadState we can transition to others? + move = qfalse; + return; + } + //see if we're hiding + else if ( NPCInfo->squadState == SQUAD_COVER ) + { + //Should we duck? + move = qfalse; + return; + } + //see if we're just standing around + else if ( NPCInfo->squadState == SQUAD_IDLE ) + { + if ( !NPCInfo->goalEntity ) + { + move = qfalse; + return; + } + } + //?? + else + {//invalid squadState! + } + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) || + ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) && NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) ) + {//either hit our navgoal or our navgoal was not a crucial (scripted) one (maybe a combat point) and we're scouting and found our enemy + int newSquadState = SQUAD_STAND_AND_SHOOT; + //we got where we wanted to go, set timers based on why we were running + switch ( NPCInfo->squadState ) + { + case SQUAD_RETREAT://was running away + //done fleeing, obviously + TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 ); + TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); + TIMER_Set( NPC, "flee", -level.time ); + newSquadState = SQUAD_COVER; + break; + case SQUAD_TRANSITION://was heading for a combat point + TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); + break; + case SQUAD_SCOUT://was running after player + break; + default: + break; + } + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, newSquadState ); + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels + //don't do something else just yet + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); + return; + } + + //keep going, hold of roamTimer until we get there + TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); + } +} + +void ST_ResolveBlockedShot( int hit ) +{ + int stuckTime; + //figure out how long we intend to stand here, max + if ( TIMER_Get( NPC, "roamTime" ) > TIMER_Get( NPC, "stick" ) ) + { + stuckTime = TIMER_Get( NPC, "roamTime" )-level.time; + } + else + { + stuckTime = TIMER_Get( NPC, "stick" )-level.time; + } + + if ( TIMER_Done( NPC, "duck" ) ) + {//we're not ducking + if ( AI_GroupContainsEntNum( NPCInfo->group, hit ) ) + { + gentity_t *member = &g_entities[hit]; + if ( TIMER_Done( member, "duck" ) ) + {//they aren't ducking + if ( TIMER_Done( member, "stand" ) ) + {//they're not being forced to stand + //tell them to duck at least as long as I'm not moving + TIMER_Set( member, "duck", stuckTime ); + return; + } + } + } + } + else + {//maybe we should stand + if ( TIMER_Done( NPC, "stand" ) ) + {//stand for as long as we'll be here + TIMER_Set( NPC, "stand", stuckTime ); + return; + } + } + //Hmm, can't resolve this by telling them to duck or telling me to stand + //We need to move! + TIMER_Set( NPC, "roamTime", -1 ); + TIMER_Set( NPC, "stick", -1 ); + TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "attakDelay", Q_irand( 1000, 3000 ) ); +} + +/* +------------------------- +ST_CheckFireState +------------------------- +*/ + +static void ST_CheckFireState( void ) +{ + if ( enemyCS ) + {//if have a clear shot, always try + return; + } + + if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT ) + {//runners never try to fire at the last pos + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + //See if we should continue to fire on their last position + //!TIMER_Done( NPC, "stick" ) || + if ( !hitAlly //we're not going to hit an ally + && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS? + && NPCInfo->enemyLastSeenTime > 0 //we've seen the enemy + && NPCInfo->group //have a group + && (NPCInfo->group->numState[SQUAD_RETREAT]>0||NPCInfo->group->numState[SQUAD_TRANSITION]>0||NPCInfo->group->numState[SQUAD_SCOUT]>0) )//laying down covering fire + { + if ( level.time - NPCInfo->enemyLastSeenTime < 10000 &&//we have seem the enemy in the last 10 seconds + (!NPCInfo->group || level.time - NPCInfo->group->lastSeenEnemyTime < 10000 ))//we are not in a group or the group has seen the enemy in the last 10 seconds + { + if ( !Q_irand( 0, 10 ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + qboolean tooClose = qfalse; + qboolean tooFar = qfalse; + float distThreshold; + float dist; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + if ( VectorCompare( impactPos, vec3_origin ) ) + {//never checked ShotEntity this frame, so must do a trace... + trace_t tr; + //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; + vec3_t forward, end; + AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 8192, forward, end ); + trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + VectorCopy( tr.endpos, impactPos ); + } + + //see if impact would be too close to me + distThreshold = 16384/*128*128*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 65536/*256*256*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 65536/*256*256*/; + } + break; + default: + break; + } + + dist = DistanceSquared( impactPos, muzzle ); + + if ( dist < distThreshold ) + {//impact would be too close to me + tooClose = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 || + (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 )) + {//we've haven't seen them in the last 5 seconds + //see if it's too far from where he is + distThreshold = 65536/*256*256*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 262144/*512*512*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 262144/*512*512*/; + } + break; + default: + break; + } + dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation ); + if ( dist > distThreshold ) + {//impact would be too far from enemy + tooFar = qtrue; + } + } + + if ( !tooClose && !tooFar ) + {//okay too shoot at last pos + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + VectorNormalize( dir ); + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot = qtrue; + faceEnemy = qfalse; + //AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + return; + } + } + } + } +} + +void ST_TrackEnemy( gentity_t *self, vec3_t enemyPos ) +{ + //clear timers + TIMER_Set( self, "attackDelay", Q_irand( 1000, 2000 ) ); + //TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stick", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "stand", -1 ); + TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) ); + //leave my combat point + NPC_FreeCombatPoint( self->NPC->combatPoint, qfalse ); + //go after his last seen pos + NPC_SetMoveGoal( self, enemyPos, 16, qfalse, -1, NULL ); +} + +int ST_ApproachEnemy( gentity_t *self ) +{ + TIMER_Set( self, "attackDelay", Q_irand( 250, 500 ) ); + //TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stick", Q_irand( 1000, 2000 ) ); + TIMER_Set( self, "stand", -1 ); + TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) ); + //leave my combat point + NPC_FreeCombatPoint( self->NPC->combatPoint, qfalse ); + //return the relevant combat point flags + return (CP_CLEAR|CP_CLOSEST); +} + +void ST_HuntEnemy( gentity_t *self ) +{ + //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );//Disabled this for now, guys who couldn't hunt would never attack + //TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "stick", Q_irand( 250, 1000 ) ); + TIMER_Set( NPC, "stand", -1 ); + TIMER_Set( NPC, "scoutTime", TIMER_Get( NPC, "stick" )-level.time+Q_irand(5000, 10000) ); + //leave my combat point + NPC_FreeCombatPoint( NPCInfo->combatPoint, qfalse ); + //go directly after the enemy + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + self->NPC->goalEntity = NPC->enemy; + } +} + +void ST_TransferTimers( gentity_t *self, gentity_t *other ) +{ + TIMER_Set( other, "attackDelay", TIMER_Get( self, "attackDelay" )-level.time ); + TIMER_Set( other, "duck", TIMER_Get( self, "duck" )-level.time ); + TIMER_Set( other, "stick", TIMER_Get( self, "stick" )-level.time ); + TIMER_Set( other, "scoutTime", TIMER_Get( self, "scout" )-level.time ); + TIMER_Set( other, "roamTime", TIMER_Get( self, "roamTime" )-level.time ); + TIMER_Set( other, "stand", TIMER_Get( self, "stand" )-level.time ); + TIMER_Set( self, "attackDelay", -1 ); + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stick", -1 ); + TIMER_Set( self, "scoutTime", -1 ); + TIMER_Set( self, "roamTime", -1 ); + TIMER_Set( self, "stand", -1 ); +} + +void ST_TransferMoveGoal( gentity_t *self, gentity_t *other ) +{ + if ( trap_ICARUS_TaskIDPending( self, TID_MOVE_NAV ) ) + {//can't transfer movegoal when a script we're running is waiting to complete + return; + } + if ( self->NPC->combatPoint != -1 ) + {//I've got a combatPoint I'm going to, give it to him + self->NPC->lastFailedCombatPoint = other->NPC->combatPoint = self->NPC->combatPoint; + self->NPC->combatPoint = -1; + } + else + {//I must be going for a goal, give that to him instead + if ( self->NPC->goalEntity == self->NPC->tempGoal ) + { + NPC_SetMoveGoal( other, self->NPC->tempGoal->r.currentOrigin, self->NPC->goalRadius, ((self->NPC->tempGoal->flags&FL_NAVGOAL)?qtrue:qfalse), -1, NULL ); + } + else + { + other->NPC->goalEntity = self->NPC->goalEntity; + } + } + //give him my squadstate + AI_GroupUpdateSquadstates( self->NPC->group, other, NPCInfo->squadState ); + + //give him my timers and clear mine + ST_TransferTimers( self, other ); + + //now make me stand around for a second or two at least + AI_GroupUpdateSquadstates( self->NPC->group, self, SQUAD_STAND_AND_SHOOT ); + TIMER_Set( self, "stand", Q_irand( 1000, 3000 ) ); +} + +int ST_GetCPFlags( void ) +{ + int cpFlags = 0; + if ( NPC && NPCInfo->group ) + { + if ( NPC == NPCInfo->group->commander && NPC->client->NPC_class == CLASS_IMPERIAL ) + {//imperials hang back and give orders + if ( NPCInfo->group->numGroup > 1 && Q_irand( -3, NPCInfo->group->numGroup ) > 1 ) + {//FIXME: make sure he;s giving orders with these lines + if ( Q_irand( 0, 1 ) ) + { + ST_Speech( NPC, SPEECH_CHASE, 0.5 ); + } + else + { + ST_Speech( NPC, SPEECH_YELL, 0.5 ); + } + } + cpFlags = (CP_CLEAR|CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT); + } + else if ( NPCInfo->group->morale < 0 ) + {//hide + cpFlags = (CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT); + } + else if ( NPCInfo->group->morale < NPCInfo->group->numGroup ) + {//morale is low for our size + int moraleDrop = NPCInfo->group->numGroup - NPCInfo->group->morale; + if ( moraleDrop < -6 ) + {//flee (no clear shot needed) + cpFlags = (CP_FLEE|CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE); + } + else if ( moraleDrop < -3 ) + {//retreat (no clear shot needed) + cpFlags = (CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE); + } + else if ( moraleDrop < 0 ) + {//cover (no clear shot needed) + cpFlags = (CP_COVER|CP_AVOID|CP_SAFE); + } + } + else + { + int moraleBoost = NPCInfo->group->morale - NPCInfo->group->numGroup; + if ( moraleBoost > 20 ) + {//charge to any one and outflank (no cover needed) + cpFlags = (CP_CLEAR|CP_FLANK|CP_APPROACH_ENEMY); + } + else if ( moraleBoost > 15 ) + {//charge to closest one (no cover needed) + cpFlags = (CP_CLEAR|CP_CLOSEST|CP_APPROACH_ENEMY); + } + else if ( moraleBoost > 10 ) + {//charge closer (no cover needed) + cpFlags = (CP_CLEAR|CP_APPROACH_ENEMY); + } + } + } + if ( !cpFlags ) + { + //at some medium level of morale + switch( Q_irand( 0, 3 ) ) + { + case 0://just take the nearest one + cpFlags = (CP_CLEAR|CP_COVER|CP_NEAREST); + break; + case 1://take one closer to the enemy + cpFlags = (CP_CLEAR|CP_COVER|CP_APPROACH_ENEMY); + break; + case 2://take the one closest to the enemy + cpFlags = (CP_CLEAR|CP_COVER|CP_CLOSEST|CP_APPROACH_ENEMY); + break; + case 3://take the one on the other side of the enemy + cpFlags = (CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY); + break; + } + } + if ( NPC && (NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + return cpFlags; +} +/* +------------------------- +ST_Commander + + Make decisions about who should go where, etc. + +FIXME: leader (group-decision-making) AI? +FIXME: need alternate routes! +FIXME: more group voice interaction +FIXME: work in pairs? + +------------------------- +*/ +void ST_Commander( void ) +{ + int i, j; + int cp, cpFlags_org, cpFlags; + AIGroupInfo_t *group = NPCInfo->group; + gentity_t *member;//, *buddy; + qboolean runner = qfalse; + qboolean enemyLost = qfalse; + qboolean enemyProtected = qfalse; + qboolean scouting = qfalse; + int squadState; + int curMemberNum, lastMemberNum; + float avoidDist; + + group->processed = qtrue; + + if ( group->enemy == NULL || group->enemy->client == NULL ) + {//hmm, no enemy...?! + return; + } + + //FIXME: have this group commander check the enemy group (if any) and see if they have + // superior numbers. If they do, fall back rather than advance. If you have + // superior numbers, advance on them. + //FIXME: find the group commander and have him occasionally give orders when there is speech + //FIXME: start fleeing when only a couple of you vs. a lightsaber, possibly give up if the only one left + + SaveNPCGlobals(); + + if ( group->lastSeenEnemyTime < level.time - 180000 ) + {//dissolve the group + ST_Speech( NPC, SPEECH_LOST, 0.0f ); + group->enemy->waypoint = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE ); + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + SetNPCGlobals( member ); + if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//running somewhere that a script requires us to go, don't break from that + continue; + } + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not allowed to move on my own + continue; + } + //Lost enemy for three minutes? go into search mode? + G_ClearEnemy( NPC ); + NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, group->enemy->waypoint ); + if ( NPC->waypoint == WAYPOINT_NONE ) + { + NPCInfo->behaviorState = BS_DEFAULT;//BS_PATROL; + } + else if ( group->enemy->waypoint == WAYPOINT_NONE || (trap_Nav_GetPathCost( NPC->waypoint, group->enemy->waypoint ) >= Q3_INFINITE) ) + { + NPC_BSSearchStart( NPC->waypoint, BS_SEARCH ); + } + else + { + NPC_BSSearchStart( group->enemy->waypoint, BS_SEARCH ); + } + } + group->enemy = NULL; + RestoreNPCGlobals(); + return; + } + + + //See if anyone in our group is not alerted and alert them + /* + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( !member->enemy ) + {//he's not mad, so get him mad + //Have his buddy tell him to get mad + if ( group->member[i].closestBuddy != ENTITYNUM_NONE ) + { + buddy = &g_entities[group->member[i].closestBuddy]; + if ( buddy->enemy == group->enemy ) + { + SetNPCGlobals( buddy ); + ST_Speech( NPC, SPEECH_CHARGE, 0.7f ); + } + } + SetNPCGlobals( member ); + G_SetEnemy( member, group->enemy ); + } + } + */ + //Okay, everyone is mad + + //see if anyone is running + if ( group->numState[SQUAD_SCOUT] > 0 || + group->numState[SQUAD_TRANSITION] > 0 || + group->numState[SQUAD_RETREAT] > 0 ) + {//someone is running + runner = qtrue; + } + + if ( /*!runner &&*/ group->lastSeenEnemyTime > level.time - 32000 && group->lastSeenEnemyTime < level.time - 30000 ) + {//no-one has seen the enemy for 30 seconds// and no-one is running after him + if ( group->commander && !Q_irand( 0, 1 ) ) + { + ST_Speech( group->commander, SPEECH_ESCAPING, 0.0f ); + } + else + { + ST_Speech( NPC, SPEECH_ESCAPING, 0.0f ); + } + //don't say this again + NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + + if ( group->lastSeenEnemyTime < level.time - 10000 ) + {//no-one has seen the enemy for at least 10 seconds! Should send a scout + enemyLost = qtrue; + } + + if ( group->lastClearShotTime < level.time - 5000 ) + {//no-one has had a clear shot for 5 seconds! + enemyProtected = qtrue; + } + + //Go through the list: + + //Everyone should try to get to a combat point if possible + if ( d_asynchronousGroupAI.integer ) + {//do one member a turn + group->activeMemberNum++; + if ( group->activeMemberNum >= group->numGroup ) + { + group->activeMemberNum = 0; + } + curMemberNum = group->activeMemberNum; + lastMemberNum = curMemberNum + 1; + } + else + { + curMemberNum = 0; + lastMemberNum = group->numGroup; + } + for ( i = curMemberNum; i < lastMemberNum; i++ ) + { + //reset combat point flags + cp = -1; + cpFlags = 0; + squadState = SQUAD_IDLE; + avoidDist = 0; + scouting = qfalse; + + //get the next guy + member = &g_entities[group->member[i].number]; + if ( !member->enemy ) + {//don't include guys that aren't angry + continue; + } + SetNPCGlobals( member ); + + if ( !TIMER_Done( NPC, "flee" ) ) + {//running away + continue; + } + + if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//running somewhere that a script requires us to go + continue; + } + + if ( NPC->s.weapon == WP_NONE + && NPCInfo->goalEntity + && NPCInfo->goalEntity == NPCInfo->tempGoal + && NPCInfo->goalEntity->enemy + && NPCInfo->goalEntity->enemy->s.eType == ET_ITEM ) + {//running to pick up a gun, don't do other logic + continue; + } + + //see if this member should start running (only if have no officer... FIXME: should always run from AEL_DANGER_GREAT?) + if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN ) + { + if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//going to run + ST_Speech( NPC, SPEECH_COVER, 0 ); + continue; + } + } + + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not allowed to do combat-movement + continue; + } + + //check the local state + if ( NPCInfo->squadState != SQUAD_RETREAT ) + {//not already retreating + if ( NPC->client->ps.weapon == WP_NONE ) + {//weaponless, should be hiding + if ( NPCInfo->goalEntity == NULL || NPCInfo->goalEntity->enemy == NULL || NPCInfo->goalEntity->enemy->s.eType != ET_ITEM ) + {//not running after a pickup + if ( TIMER_Done( NPC, "hideTime" ) || (DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 && NPC_ClearLOS4( NPC->enemy )) ) + {//done hiding or enemy near and can see us + //er, start another flee I guess? + NPC_StartFlee( NPC->enemy, NPC->enemy->r.currentOrigin, AEL_DANGER_GREAT, 5000, 10000 ); + }//else, just hang here + } + continue; + } + if ( TIMER_Done( NPC, "roamTime" ) && TIMER_Done( NPC, "hideTime" ) && NPC->health > 10 && !trap_InPVS( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + {//cant even see enemy + //better go after him + cpFlags |= (CP_CLEAR|CP_COVER); + } + else if ( NPCInfo->localState == LSTATE_UNDERFIRE ) + {//we've been shot + switch( group->enemy->client->ps.weapon ) + { + case WP_SABER: + if ( DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 )//256 squared + { + cpFlags |= (CP_AVOID_ENEMY|CP_COVER|CP_AVOID|CP_RETREAT); + if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN ) + { + squadState = SQUAD_RETREAT; + } + avoidDist = 256; + } + break; + default: + case WP_BLASTER: + cpFlags |= (CP_COVER); + break; + } + if ( NPC->health <= 10 ) + { + if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN ) + { + cpFlags |= (CP_FLEE|CP_AVOID|CP_RETREAT); + squadState = SQUAD_RETREAT; + } + } + } + else + {//not hit, see if there are other reasons we should run + if ( trap_InPVS( NPC->r.currentOrigin, group->enemy->r.currentOrigin ) ) + {//in the same room as enemy + if ( NPC->client->ps.weapon == WP_ROCKET_LAUNCHER && + DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < MIN_ROCKET_DIST_SQUARED && + NPCInfo->squadState != SQUAD_TRANSITION ) + {//too close for me to fire my weapon and I'm not already on the move + cpFlags |= (CP_AVOID_ENEMY|CP_CLEAR|CP_AVOID); + avoidDist = 256; + } + else + { + switch( group->enemy->client->ps.weapon ) + { + case WP_SABER: + //if ( group->enemy->client->ps.SaberLength() > 0 ) + if (!group->enemy->client->ps.saberHolstered) + { + if ( DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 ) + { + if ( TIMER_Done( NPC, "hideTime" ) ) + { + if ( NPCInfo->squadState != SQUAD_TRANSITION ) + {//not already moving: FIXME: we need to see if where we're going is good now? + cpFlags |= (CP_AVOID_ENEMY|CP_CLEAR|CP_AVOID); + avoidDist = 256; + } + } + } + } + default: + break; + } + } + } + } + } + + if ( !cpFlags ) + {//okay, we have no new enemy-driven reason to run... let's use tactics now + if ( runner && NPCInfo->combatPoint != -1 ) + {//someone is running and we have a combat point already + if ( NPCInfo->squadState != SQUAD_SCOUT && + NPCInfo->squadState != SQUAD_TRANSITION && + NPCInfo->squadState != SQUAD_RETREAT ) + {//it's not us + if ( TIMER_Done( NPC, "verifyCP" ) && DistanceSquared( NPC->r.currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin ) > 64*64 ) + {//1 - 3 seconds have passed since you chose a CP, see if you're there since, for some reason, you've stopped running... + //uh, WTF, we're not on our combat point? + //er, try again, I guess? + cp = NPCInfo->combatPoint; + cpFlags |= ST_GetCPFlags(); + } + else + {//cover them + //stop ducking + TIMER_Set( NPC, "duck", -1 ); + //start shooting + TIMER_Set( NPC, "attackDelay", -1 ); + //AI should take care of the rest - fire at enemy + } + } + else + {//we're running + //see if we're blocked + if ( NPCInfo->aiFlags & NPCAI_BLOCKED ) + {//dammit, something is in our way + //see if it's one of ours + for ( j = 0; j < group->numGroup; j++ ) + { + if ( group->member[j].number == NPCInfo->blockingEntNum ) + {//we're being blocked by one of our own, pass our goal onto them and I'll stand still + ST_TransferMoveGoal( NPC, &g_entities[group->member[j].number] ); + break; + } + } + } + //we don't need to do anything else + continue; + } + } + else + {//okay no-one is running, use some tactics + if ( NPCInfo->combatPoint != -1 ) + {//we have a combat point we're supposed to be running to + if ( NPCInfo->squadState != SQUAD_SCOUT && + NPCInfo->squadState != SQUAD_TRANSITION && + NPCInfo->squadState != SQUAD_RETREAT ) + {//but we're not running + if ( TIMER_Done( NPC, "verifyCP" ) ) + {//1 - 3 seconds have passed since you chose a CP, see if you're there since, for some reason, you've stopped running... + if ( DistanceSquared( NPC->r.currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin ) > 64*64 ) + {//uh, WTF, we're not on our combat point? + //er, try again, I guess? + cp = NPCInfo->combatPoint; + cpFlags |= ST_GetCPFlags(); + } + } + } + } + if ( enemyLost ) + {//if no-one has seen the enemy for a while, send a scout + //ask where he went + if ( group->numState[SQUAD_SCOUT] <= 0 ) + { + scouting = qtrue; + NPC_ST_StoreMovementSpeech( SPEECH_CHASE, 0.0f ); + } + //Since no-one else has done this, I should be the closest one, so go after him... + ST_TrackEnemy( NPC, group->enemyLastSeenPos ); + //set me into scout mode + AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT ); + //we're not using a cp, so we need to set runner to true right here + runner = qtrue; + } + else if ( enemyProtected ) + {//if no-one has a clear shot at the enemy, someone should go after him + //FIXME: if I'm in an area where no safe combat points have a clear shot at me, they don't come after me... they should anyway, though after some extra hesitation. + //ALSO: seem to give up when behind an area portal? + //since no-one else here has done this, I should be the closest one + if ( TIMER_Done( NPC, "roamTime" ) && !Q_irand( 0, group->numGroup) ) + {//only do this if we're ready to move again and we feel like it + cpFlags |= ST_ApproachEnemy( NPC ); + //set me into scout mode + AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT ); + } + } + else + {//group can see and has been shooting at the enemy + //see if we should do something fancy? + + {//we're ready to move + if ( NPCInfo->combatPoint == -1 ) + {//we're not on a combat point + if ( 1 )//!Q_irand( 0, 2 ) ) + {//we should go for a combat point + cpFlags |= ST_GetCPFlags(); + } + else + { + TIMER_Set( NPC, "stick", Q_irand( 2000, 4000 ) ); + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 3000 ) ); + } + } + else if ( TIMER_Done( NPC, "roamTime" ) ) + {//we are already on a combat point + if ( i == 0 ) + {//we're the closest + if ( (group->morale-group->numGroup>0) && !Q_irand( 0, 4 ) ) + {//try to outflank him + cpFlags |= (CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY); + } + else if ( (group->morale-group->numGroup<0) ) + {//better move! + cpFlags |= ST_GetCPFlags(); + } + else + {//If we're point, then get down + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) ); + TIMER_Set( NPC, "stick", Q_irand( 2000, 5000 ) ); + //FIXME: what if we can't shoot from a ducked pos? + TIMER_Set( NPC, "duck", Q_irand( 3000, 4000 ) ); + AI_GroupUpdateSquadstates( group, NPC, SQUAD_POINT ); + } + } + else if ( i == group->numGroup - 1 ) + {//farthest from the enemy + if ( (group->morale-group->numGroup<0) ) + {//low morale, just hang here + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) ); + TIMER_Set( NPC, "stick", Q_irand( 2000, 5000 ) ); + } + else if ( (group->morale-group->numGroup>0) ) + {//try to move in on the enemy + cpFlags |= ST_ApproachEnemy( NPC ); + //set me into scout mode + AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT ); + } + else + {//use normal decision making process + cpFlags |= ST_GetCPFlags(); + } + } + else + {//someone in-between + if ( (group->morale-group->numGroup<0) || !Q_irand( 0, 4 ) ) + {//do something + cpFlags |= ST_GetCPFlags(); + } + else + { + TIMER_Set( NPC, "stick", Q_irand( 2000, 4000 ) ); + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) ); + } + } + } + } + if ( !cpFlags ) + {//still not moving + //see if we should say something? + /* + if ( NPC->attackDebounceTime < level.time - 2000 ) + {//we, personally, haven't shot for 2 seconds + //maybe yell at the enemy? + ST_Speech( NPC, SPEECH_CHARGE, 0.9f ); + } + */ + + //see if we should do other fun stuff + //toy with ducking + if ( TIMER_Done( NPC, "duck" ) ) + {//not ducking + if ( TIMER_Done( NPC, "stand" ) ) + {//don't have to keep standing + if ( NPCInfo->combatPoint == -1 || (level.combatPoints[NPCInfo->combatPoint].flags&CPF_DUCK) ) + {//okay to duck here + if ( !Q_irand( 0, 3 ) ) + { + TIMER_Set( NPC, "duck", Q_irand( 1000, 3000 ) ); + } + } + } + } + //FIXME: what about CPF_LEAN? + } + } + } + } + + //clear the local state + NPCInfo->localState = LSTATE_NONE; + + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + //Assign combat points + if ( cpFlags ) + {//we want to run to a combat point + /* + if ( NPCInfo->combatPoint != -1 ) + {//if we're on a combat point, we obviously don't want the one we're closest to + cpFlags |= CP_AVOID; + } + */ + + if ( group->enemy->client->ps.weapon == WP_SABER && /*group->enemy->client->ps.SaberLength() > 0*/!group->enemy->client->ps.saberHolstered ) + {//we obviously want to avoid the enemy if he has a saber + cpFlags |= CP_AVOID_ENEMY; + avoidDist = 256; + } + + //remember what we *wanted* to do... + cpFlags_org = cpFlags; + + //now get a combat point + if ( cp == -1 ) + {//may have had sone set above + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, group->enemy->r.currentOrigin, cpFlags|CP_HAS_ROUTE, avoidDist, NPCInfo->lastFailedCombatPoint ); + } + while ( cp == -1 && cpFlags != CP_ANY ) + {//start "OR"ing out certain flags to see if we can find *any* point + if ( cpFlags & CP_INVESTIGATE ) + {//don't need to investigate + cpFlags &= ~CP_INVESTIGATE; + } + else if ( cpFlags & CP_SQUAD ) + {//don't need to stick to squads + cpFlags &= ~CP_SQUAD; + } + else if ( cpFlags & CP_DUCK ) + {//don't need to duck + cpFlags &= ~CP_DUCK; + } + else if ( cpFlags & CP_NEAREST ) + {//don't need closest one to me + cpFlags &= ~CP_NEAREST; + } + else if ( cpFlags & CP_FLANK ) + {//don't need to flank enemy + cpFlags &= ~CP_FLANK; + } + else if ( cpFlags & CP_SAFE ) + {//don't need one that hasn't been shot at recently + cpFlags &= ~CP_SAFE; + } + else if ( cpFlags & CP_CLOSEST ) + {//don't need to get closest to enemy + cpFlags &= ~CP_CLOSEST; + //but let's try to approach at least + cpFlags |= CP_APPROACH_ENEMY; + } + else if ( cpFlags & CP_APPROACH_ENEMY ) + {//don't need to approach enemy + cpFlags &= ~CP_APPROACH_ENEMY; + } + else if ( cpFlags & CP_COVER ) + {//don't need cover + cpFlags &= ~CP_COVER; + //but let's pick one that makes us duck + cpFlags |= CP_DUCK; + } + else if ( cpFlags & CP_CLEAR ) + {//don't need a clear shot to enemy + cpFlags &= ~CP_CLEAR; + } + else if ( cpFlags & CP_AVOID_ENEMY ) + {//don't need to avoid enemy + cpFlags &= ~CP_AVOID_ENEMY; + } + else if ( cpFlags & CP_RETREAT ) + {//don't need to retreat + cpFlags &= ~CP_RETREAT; + } + else if ( cpFlags &CP_FLEE ) + {//don't need to flee + cpFlags &= ~CP_FLEE; + //but at least avoid enemy and pick one that gives cover + cpFlags |= (CP_COVER|CP_AVOID_ENEMY); + } + else if ( cpFlags & CP_AVOID ) + {//okay, even pick one right by me + cpFlags &= ~CP_AVOID; + } + else + { + cpFlags = CP_ANY; + } + //now try again + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, group->enemy->r.currentOrigin, cpFlags|CP_HAS_ROUTE, avoidDist, -1 ); + } + //see if we got a valid one + if ( cp != -1 ) + {//found a combat point + //let others know that someone is now running + runner = qtrue; + //don't change course again until we get to where we're going + TIMER_Set( NPC, "roamTime", Q3_INFINITE ); + TIMER_Set( NPC, "verifyCP", Q_irand( 1000, 3000 ) );//don't make sure you're in your CP for 1 - 3 seconds + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL ); + //okay, try a move right now to see if we can even get there + + //if ( ST_Move() ) + {//we actually can get to it, so okay to say you're going there. + //FIXME: Hmm... any way we can store this move info so we don't have to do it again + // when our turn to think comes up? + + //set us up so others know we're on the move + if ( squadState != SQUAD_IDLE ) + { + AI_GroupUpdateSquadstates( group, NPC, squadState ); + } + else if ( cpFlags&CP_FLEE ) + {//outright running for your life + AI_GroupUpdateSquadstates( group, NPC, SQUAD_RETREAT ); + } + else + {//any other kind of transition between combat points + AI_GroupUpdateSquadstates( group, NPC, SQUAD_TRANSITION ); + } + + //unless we're trying to flee, walk slowly + if ( !(cpFlags_org&CP_FLEE) ) + { + //ucmd.buttons |= BUTTON_CAREFUL; + } + + /* + if ( scouting ) + {//successfully chasing enemy + ST_Speech( NPC, SPEECH_CHASE, 0.0f ); + //don't say this again + //group->speechDebounceTime = level.time + 5000; + } + //flanking: + else */if ( cpFlags & CP_FLANK ) + { + if ( group->numGroup > 1 ) + { + NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 ); + } + } + else + {//okay, let's cheat + if ( group->numGroup > 1 ) + { + float dot = 1.0f; + if ( !Q_irand( 0, 3 ) ) + {//25% of the time, see if we're flanking the enemy + vec3_t eDir2Me, eDir2CP; + + VectorSubtract( NPC->r.currentOrigin, group->enemy->r.currentOrigin, eDir2Me ); + VectorNormalize( eDir2Me ); + + VectorSubtract( level.combatPoints[NPCInfo->combatPoint].origin, group->enemy->r.currentOrigin, eDir2CP ); + VectorNormalize( eDir2CP ); + + dot = DotProduct( eDir2Me, eDir2CP ); + } + + if ( dot < 0.4 ) + {//flanking! + NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 ); + } + else if ( !Q_irand( 0, 10 ) ) + {//regular movement + NPC_ST_StoreMovementSpeech( SPEECH_YELL, 0.2f );//was SPEECH_COVER + } + } + } + /* + else if ( cpFlags & CP_CLOSEST || cpFlags & CP_APPROACH_ENEMY ) + { + if ( group->numGroup > 1 ) + { + NPC_ST_StoreMovementSpeech( SPEECH_CHASE, 0.4f ); + } + } + */ + }//else: nothing, a failed move should clear the combatPoint and you can try again next frame + } + else if ( NPCInfo->squadState == SQUAD_SCOUT ) + {//we couldn't find a combatPoint by the player, so just go after him directly + ST_HuntEnemy( NPC ); + //set me into scout mode + AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT ); + //AI should take care of rest + } + } + } + + RestoreNPCGlobals(); + return; +} + +/* +------------------------- +NPC_BSST_Attack +------------------------- +*/ + +void NPC_BSST_Attack( void ) +{ + vec3_t enemyDir, shootDir; + float dot; + + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //NPC_CheckEnemy( qtrue, qfalse ); + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse )//!NPC->enemy )// + { + NPC->enemy = NULL; + if( NPC->client->playerTeam == NPCTEAM_PLAYER ) + { + NPC_BSPatrol(); + } + else + { + NPC_BSST_Patrol();//FIXME: or patrol? + } + return; + } + + //FIXME: put some sort of delay into the guys depending on how they saw you...? + + //Get our group info + if ( TIMER_Done( NPC, "interrogating" ) ) + { + AI_GetGroup( NPC );//, 45, 512, NPC->enemy ); + } + else + { + //FIXME: when done interrogating, I should send out a team alert! + } + + if ( NPCInfo->group ) + {//I belong to a squad of guys - we should *always* have a group + if ( !NPCInfo->group->processed ) + {//I'm the first ent in my group, I'll make the command decisions +#if AI_TIMERS + int startTime = GetTime(0); +#endif// AI_TIMERS + ST_Commander(); +#if AI_TIMERS + int commTime = GetTime ( startTime ); + if ( commTime > 20 ) + { + gi.Printf( S_COLOR_RED"ERROR: Commander time: %d\n", commTime ); + } + else if ( commTime > 10 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: Commander time: %d\n", commTime ); + } + else if ( commTime > 2 ) + { + gi.Printf( S_COLOR_GREEN"Commander time: %d\n", commTime ); + } +#endif// AI_TIMERS + } + } + else if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//not already fleeing, and going to run + ST_Speech( NPC, SPEECH_COVER, 0 ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( !NPC->enemy ) + {//WTF? somehow we lost our enemy? + NPC_BSST_Patrol();//FIXME: or patrol? + return; + } + + enemyLOS = enemyCS = enemyInFOV = qfalse; + move = qtrue; + faceEnemy = qfalse; + shoot = qfalse; + hitAlly = qfalse; + VectorClear( impactPos ); + enemyDist = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir ); + VectorNormalize( enemyDir ); + AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL ); + dot = DotProduct( enemyDir, shootDir ); + if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 ) + {//enemy is in front of me or they're very close and not behind me + enemyInFOV = qtrue; + } + + if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128 + {//enemy within 128 + if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) && + (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//shooting an explosive, but enemy too close, switch to primary fire + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not... + } + } + else if ( enemyDist > 65536 )//256 squared + { + if ( NPC->client->ps.weapon == WP_DISRUPTOR ) + {//sniping... should be assumed + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + {//use primary fire + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( WP_DISRUPTOR ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + //can we see our target? + if ( NPC_ClearLOS4( NPC->enemy ) ) + { + AI_GroupUpdateEnemyLastSeen( NPCInfo->group, NPC->enemy->r.currentOrigin ); + NPCInfo->enemyLastSeenTime = level.time; + enemyLOS = qtrue; + + if ( NPC->client->ps.weapon == WP_NONE ) + { + enemyCS = qfalse;//not true, but should stop us from firing + NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon + } + else + {//can we shoot our target? + if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128 + { + enemyCS = qfalse;//not true, but should stop us from firing + hitAlly = qtrue;//us! + //FIXME: if too close, run away! + } + else if ( enemyInFOV ) + {//if enemy is FOV, go ahead and check for shooting + int hit = NPC_ShotEntity( NPC->enemy, impactPos ); + gentity_t *hitEnt = &g_entities[hit]; + + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) ) + {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway + AI_GroupUpdateClearShotTime( NPCInfo->group ); + enemyCS = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + else + {//Hmm, have to get around this bastard + NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + ST_ResolveBlockedShot( hit ); + if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) + {//would hit an ally, don't fire!!! + hitAlly = qtrue; + } + else + {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire + } + } + } + else + { + enemyCS = qfalse;//not true, but should stop us from firing + } + } + } + else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + + if ( NPC->client->ps.weapon == WP_NONE ) + { + faceEnemy = qfalse; + shoot = qfalse; + } + else + { + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + if ( enemyCS ) + { + shoot = qtrue; + } + } + + //Check for movement to take care of + ST_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + ST_CheckFireState(); + + if ( faceEnemy ) + {//face the enemy + NPC_FaceEnemy( qtrue ); + } + + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not supposed to chase my enemies + if ( NPCInfo->goalEntity == NPC->enemy ) + {//goal is my entity, so don't move + move = qfalse; + } + } + + if ( NPC->client->ps.weaponTime > 0 && NPC->s.weapon == WP_ROCKET_LAUNCHER ) + { + move = qfalse; + } + + if ( move ) + {//move toward goal + if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared + { + move = ST_Move(); + } + else + { + move = qfalse; + } + } + + if ( !move ) + { + if ( !TIMER_Done( NPC, "duck" ) ) + { + ucmd.upmove = -127; + } + //FIXME: what about leaning? + } + else + {//stop ducking! + TIMER_Set( NPC, "duck", -1 ); + } + + if ( !TIMER_Done( NPC, "flee" ) ) + {//running away + faceEnemy = qfalse; + } + + //FIXME: check scf_face_move_dir here? + + if ( !faceEnemy ) + {//we want to face in the dir we're running + if ( !move ) + {//if we haven't moved, we should look in the direction we last looked? + VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); + } + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + NPC_UpdateAngles( qtrue, qtrue ); + if ( move ) + {//don't run away and shoot + shoot = qfalse; + } + } + + if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + { + shoot = qfalse; + } + + if ( NPC->enemy && NPC->enemy->enemy ) + { + if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) + {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) + shoot = qfalse; + } + } + //FIXME: don't shoot right away! + if ( NPC->client->ps.weaponTime > 0 ) + { + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER ) + { + if ( !enemyLOS || !enemyCS ) + {//cancel it + NPC->client->ps.weaponTime = 0; + } + else + {//delay our next attempt + TIMER_Set( NPC, "attackDelay", Q_irand( 3000, 5000 ) ); + } + } + } + else if ( shoot ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + //NASTY + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER + && (ucmd.buttons&BUTTON_ATTACK) + && !move + && g_spskill.integer > 1 + && !Q_irand( 0, 3 ) ) + {//every now and then, shoot a homing rocket + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + NPC->client->ps.weaponTime = Q_irand( 1000, 2500 ); + } + } + } +} + +void NPC_BSST_Default( void ) +{ + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSST_Patrol(); + } + else //if ( NPC->enemy ) + {//have an enemy + NPC_CheckGetNewWeapon(); + NPC_BSST_Attack(); + } +} diff --git a/codemp/game/NPC_AI_Utils.c b/codemp/game/NPC_AI_Utils.c new file mode 100644 index 0000000..617bd92 --- /dev/null +++ b/codemp/game/NPC_AI_Utils.c @@ -0,0 +1,1139 @@ +// These utilities are meant for strictly non-player, non-team NPCs. +// These functions are in their own file because they are only intended +// for use with NPCs who's logic has been overriden from the original +// AI code, and who's code resides in files with the AI_ prefix. + +#include "b_local.h" +#include "g_nav.h" + +#define MAX_RADIUS_ENTS 128 +#define DEFAULT_RADIUS 45 + +extern vmCvar_t d_noGroupAI; +qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member ); + +extern void G_TestLine(vec3_t start, vec3_t end, int color, int time); + +/* +------------------------- +AI_GetGroupSize +------------------------- +*/ + +int AI_GetGroupSize( vec3_t origin, int radius, team_t playerTeam, gentity_t *avoid ) +{ + int radiusEnts[ MAX_RADIUS_ENTS ]; + gentity_t *check; + vec3_t mins, maxs; + int numEnts, realCount = 0; + int i; + int j; + + //Setup the bbox to search in + for ( i = 0; i < 3; i++ ) + { + mins[i] = origin[i] - radius; + maxs[i] = origin[i] + radius; + } + + //Get the number of entities in a given space + numEnts = trap_EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS ); + + //Cull this list + for ( j = 0; j < numEnts; j++ ) + { + check = &g_entities[radiusEnts[j]]; + + //Validate clients + if ( check->client == NULL ) + continue; + + //Skip the requested avoid ent if present + if ( ( avoid != NULL ) && ( check == avoid ) ) + continue; + + //Must be on the same team + if ( check->client->playerTeam != playerTeam ) + continue; + + //Must be alive + if ( check->health <= 0 ) + continue; + + realCount++; + } + + return realCount; +} + +//Overload + +int AI_GetGroupSize2( gentity_t *ent, int radius ) +{ + if ( ( ent == NULL ) || ( ent->client == NULL ) ) + return -1; + + return AI_GetGroupSize( ent->r.currentOrigin, radius, ent->client->playerTeam, ent ); +} + +extern int NAV_FindClosestWaypointForPoint( gentity_t *ent, vec3_t point ); +int AI_ClosestGroupEntityNumToPoint( AIGroupInfo_t *group, vec3_t point ) +{ + int markerWP = WAYPOINT_NONE; + int cost, bestCost = Q3_INFINITE; + int closest = ENTITYNUM_NONE; + int i; + + if ( group == NULL || group->numGroup <= 0 ) + { + return ENTITYNUM_NONE; + } + + markerWP = NAV_FindClosestWaypointForPoint( &g_entities[group->member[0].number], point ); + + if ( markerWP == WAYPOINT_NONE ) + { + return ENTITYNUM_NONE; + } + + for ( i = 0; i < group->numGroup; i++ ) + { + cost = trap_Nav_GetPathCost( group->member[i].waypoint, markerWP ); + if ( cost < bestCost ) + { + bestCost = cost; + closest = group->member[i].number; + } + } + + return closest; +} + +void AI_SetClosestBuddy( AIGroupInfo_t *group ) +{ + int i, j; + int dist, bestDist; + + for ( i = 0; i < group->numGroup; i++ ) + { + group->member[i].closestBuddy = ENTITYNUM_NONE; + + bestDist = Q3_INFINITE; + for ( j = 0; j < group->numGroup; j++ ) + { + dist = DistanceSquared( g_entities[group->member[i].number].r.currentOrigin, g_entities[group->member[j].number].r.currentOrigin ); + if ( dist < bestDist ) + { + bestDist = dist; + group->member[i].closestBuddy = group->member[j].number; + } + } + } +} + +void AI_SortGroupByPathCostToEnemy( AIGroupInfo_t *group ) +{ + AIGroupMember_t bestMembers[MAX_GROUP_MEMBERS]; + int i, j, k; + qboolean sort = qfalse; + + if ( group->enemy != NULL ) + {//FIXME: just use enemy->waypoint? + group->enemyWP = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE ); + } + else + { + group->enemyWP = WAYPOINT_NONE; + } + + for ( i = 0; i < group->numGroup; i++ ) + { + if ( group->enemyWP == WAYPOINT_NONE ) + {//FIXME: just use member->waypoint? + group->member[i].waypoint = WAYPOINT_NONE; + group->member[i].pathCostToEnemy = Q3_INFINITE; + } + else + {//FIXME: just use member->waypoint? + group->member[i].waypoint = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE ); + if ( group->member[i].waypoint != WAYPOINT_NONE ) + { + group->member[i].pathCostToEnemy = trap_Nav_GetPathCost( group->member[i].waypoint, group->enemyWP ); + //at least one of us has a path, so do sorting + sort = qtrue; + } + else + { + group->member[i].pathCostToEnemy = Q3_INFINITE; + } + } + } + //Now sort + if ( sort ) + { + //initialize bestMembers data + for ( j = 0; j < group->numGroup; j++ ) + { + bestMembers[j].number = ENTITYNUM_NONE; + } + + for ( i = 0; i < group->numGroup; i++ ) + { + for ( j = 0; j < group->numGroup; j++ ) + { + if ( bestMembers[j].number != ENTITYNUM_NONE ) + {//slot occupied + if ( group->member[i].pathCostToEnemy < bestMembers[j].pathCostToEnemy ) + {//this guy has a shorter path than the one currenly in this spot, bump him and put myself in here + for ( k = group->numGroup; k > j; k++ ) + { + memcpy( &bestMembers[k], &bestMembers[k-1], sizeof( bestMembers[k] ) ); + } + memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) ); + break; + } + } + else + {//slot unoccupied, reached end of list, throw self in here + memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) ); + break; + } + } + } + + //Okay, now bestMembers is a sorted list, just copy it into group->members + for ( i = 0; i < group->numGroup; i++ ) + { + memcpy( &group->member[i], &bestMembers[i], sizeof( group->member[i] ) ); + } + } +} + +qboolean AI_FindSelfInPreviousGroup( gentity_t *self ) +{//go through other groups made this frame and see if any of those contain me already + int i, j; + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( level.groups[i].numGroup )//&& level.groups[i].enemy != NULL ) + {//check this one + for ( j = 0; j < level.groups[i].numGroup; j++ ) + { + if ( level.groups[i].member[j].number == self->s.number ) + { + self->NPC->group = &level.groups[i]; + return qtrue; + } + } + } + } + return qfalse; +} + +void AI_InsertGroupMember( AIGroupInfo_t *group, gentity_t *member ) +{ + int i; + + //okay, you know what? Check this damn group and make sure we're not already in here! + for ( i = 0; i < group->numGroup; i++ ) + { + if ( group->member[i].number == member->s.number ) + {//already in here + break; + } + } + if ( i < group->numGroup ) + {//found him in group already + } + else + {//add him in + group->member[group->numGroup++].number = member->s.number; + group->numState[member->NPC->squadState]++; + } + if ( !group->commander || (member->NPC->rank > group->commander->NPC->rank) ) + {//keep track of highest rank + group->commander = member; + } + member->NPC->group = group; +} + +qboolean AI_TryJoinPreviousGroup( gentity_t *self ) +{//go through other groups made this frame and see if any of those have the same enemy as me... if so, add me in! + int i; + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( level.groups[i].numGroup + && level.groups[i].numGroup < (MAX_GROUP_MEMBERS - 1) + //&& level.groups[i].enemy != NULL + && level.groups[i].enemy == self->enemy ) + {//has members, not full and has my enemy + if ( AI_ValidateGroupMember( &level.groups[i], self ) ) + {//I am a valid member for this group + AI_InsertGroupMember( &level.groups[i], self ); + return qtrue; + } + } + } + return qfalse; +} + +qboolean AI_GetNextEmptyGroup( gentity_t *self ) +{ + int i; + + if ( AI_FindSelfInPreviousGroup( self ) ) + {//already in one, no need to make a new one + return qfalse; + } + + if ( AI_TryJoinPreviousGroup( self ) ) + {//try to just put us in one that already exists + return qfalse; + } + + //okay, make a whole new one, then + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( !level.groups[i].numGroup ) + {//make a new one + self->NPC->group = &level.groups[i]; + return qtrue; + } + } + + //if ( i >= MAX_FRAME_GROUPS ) + {//WTF? Out of groups! + self->NPC->group = NULL; + return qfalse; + } +} + +qboolean AI_ValidateNoEnemyGroupMember( AIGroupInfo_t *group, gentity_t *member ) +{ + vec3_t center; + + if ( !group ) + { + return qfalse; + } + if ( group->commander ) + { + VectorCopy( group->commander->r.currentOrigin, center ); + } + else + {//hmm, just pick the first member + if ( group->member[0].number < 0 || group->member[0].number >= ENTITYNUM_WORLD ) + { + return qfalse; + } + VectorCopy( g_entities[group->member[0].number].r.currentOrigin, center ); + } + //FIXME: maybe it should be based on the center of the mass of the group, not the commander? + if ( DistanceSquared( center, member->r.currentOrigin ) > 147456/*384*384*/ ) + { + return qfalse; + } + if ( !trap_InPVS( member->r.currentOrigin, center ) ) + {//not within PVS of the group enemy + return qfalse; + } + return qtrue; +} + +qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member ) +{ + //Validate ents + if ( member == NULL ) + return qfalse; + + //Validate clients + if ( member->client == NULL ) + return qfalse; + + //Validate NPCs + if ( member->NPC == NULL ) + return qfalse; + + //must be aware + if ( member->NPC->confusionTime > level.time ) + return qfalse; + + //must be allowed to join groups + if ( member->NPC->scriptFlags&SCF_NO_GROUPS ) + return qfalse; + + //Must not be in another group + if ( member->NPC->group != NULL && member->NPC->group != group ) + {//FIXME: if that group's enemy is mine, why not absorb that group into mine? + return qfalse; + } + + //Must be alive + if ( member->health <= 0 ) + return qfalse; + + //can't be in an emplaced gun +// if( member->s.eFlags & EF_LOCKED_TO_WEAPON ) +// return qfalse; + //rwwFIXMEFIXME: support this flag + + //Must be on the same team + if ( member->client->playerTeam != group->team ) + return qfalse; + + if ( member->client->ps.weapon == WP_SABER ||//!= self->s.weapon ) + member->client->ps.weapon == WP_THERMAL || + member->client->ps.weapon == WP_DISRUPTOR || + member->client->ps.weapon == WP_EMPLACED_GUN || +// member->client->ps.weapon == WP_BOT_LASER || // Probe droid - Laser blast + member->client->ps.weapon == WP_STUN_BATON || + member->client->ps.weapon == WP_TURRET /*|| // turret guns + member->client->ps.weapon == WP_ATST_MAIN || + member->client->ps.weapon == WP_ATST_SIDE || + member->client->ps.weapon == WP_TIE_FIGHTER*/ ) + {//not really a squad-type guy + return qfalse; + } + + if ( member->client->NPC_class == CLASS_ATST || + member->client->NPC_class == CLASS_PROBE || + member->client->NPC_class == CLASS_SEEKER || + member->client->NPC_class == CLASS_REMOTE || + member->client->NPC_class == CLASS_SENTRY || + member->client->NPC_class == CLASS_INTERROGATOR || + member->client->NPC_class == CLASS_MINEMONSTER || + member->client->NPC_class == CLASS_HOWLER || + member->client->NPC_class == CLASS_MARK1 || + member->client->NPC_class == CLASS_MARK2 ) + {//these kinds of enemies don't actually use this group AI + return qfalse; + } + + //should have same enemy + if ( member->enemy != group->enemy ) + { + if ( member->enemy != NULL ) + {//he's fighting someone else, leave him out + return qfalse; + } + if ( !trap_InPVS( member->r.currentOrigin, group->enemy->r.currentOrigin ) ) + {//not within PVS of the group enemy + return qfalse; + } + } + else if ( group->enemy == NULL ) + {//if the group is a patrol group, only take those within the room and radius + if ( !AI_ValidateNoEnemyGroupMember( group, member ) ) + { + return qfalse; + } + } + //must be actually in combat mode + if ( !TIMER_Done( member, "interrogating" ) ) + return qfalse; + //FIXME: need to have a route to enemy and/or clear shot? + return qtrue; +} + +/* +------------------------- +AI_GetGroup +------------------------- +*/ +//#define MAX_WAITERS 128 +void AI_GetGroup( gentity_t *self ) +{ + int i; + gentity_t *member;//, *waiter; + //int waiters[MAX_WAITERS]; + + if ( !self || !self->NPC ) + { + return; + } + + if ( d_noGroupAI.integer ) + { + self->NPC->group = NULL; + return; + } + + if ( !self->client ) + { + self->NPC->group = NULL; + return; + } + + if ( self->NPC->scriptFlags&SCF_NO_GROUPS ) + { + self->NPC->group = NULL; + return; + } + + if ( self->enemy && (!self->enemy->client || (level.time - self->NPC->enemyLastSeenTime > 7000 ))) + { + self->NPC->group = NULL; + return; + } + + if ( !AI_GetNextEmptyGroup( self ) ) + {//either no more groups left or we're already in a group built earlier + return; + } + + //create a new one + memset( self->NPC->group, 0, sizeof( AIGroupInfo_t ) ); + + self->NPC->group->enemy = self->enemy; + self->NPC->group->team = self->client->playerTeam; + self->NPC->group->processed = qfalse; + self->NPC->group->commander = self; + self->NPC->group->memberValidateTime = level.time + 2000; + self->NPC->group->activeMemberNum = 0; + + if ( self->NPC->group->enemy ) + { + self->NPC->group->lastSeenEnemyTime = level.time; + self->NPC->group->lastClearShotTime = level.time; + VectorCopy( self->NPC->group->enemy->r.currentOrigin, self->NPC->group->enemyLastSeenPos ); + } + +// for ( i = 0, member = &g_entities[0]; i < globals.num_entities ; i++, member++) + for ( i = 0; i < level.num_entities ; i++) + { + member = &g_entities[i]; + + if (!member->inuse) + { + continue; + } + + if ( !AI_ValidateGroupMember( self->NPC->group, member ) ) + {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group + continue; + } + + //store it + AI_InsertGroupMember( self->NPC->group, member ); + + if ( self->NPC->group->numGroup >= (MAX_GROUP_MEMBERS - 1) ) + {//full + break; + } + } + + /* + //now go through waiters and see if any should join the group + //NOTE: Some should hang back and probably not attack, so we can ambush + //NOTE: only do this if calling for reinforcements? + for ( i = 0; i < numWaiters; i++ ) + { + waiter = &g_entities[waiters[i]]; + + for ( j = 0; j < self->NPC->group->numGroup; j++ ) + { + member = &g_entities[self->NPC->group->member[j]; + + if ( trap_InPVS( waiter->r.currentOrigin, member->r.currentOrigin ) ) + {//this waiter is within PVS of a current member + } + } + } + */ + + if ( self->NPC->group->numGroup <= 0 ) + {//none in group + self->NPC->group = NULL; + return; + } + + AI_SortGroupByPathCostToEnemy( self->NPC->group ); + AI_SetClosestBuddy( self->NPC->group ); +} + +void AI_SetNewGroupCommander( AIGroupInfo_t *group ) +{ + gentity_t *member = NULL; + int i; + + group->commander = NULL; + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + + if ( !group->commander || (member && member->NPC && group->commander->NPC && member->NPC->rank > group->commander->NPC->rank) ) + {//keep track of highest rank + group->commander = member; + } + } +} + +void AI_DeleteGroupMember( AIGroupInfo_t *group, int memberNum ) +{ + int i; + + if ( group->commander && group->commander->s.number == group->member[memberNum].number ) + { + group->commander = NULL; + } + if ( g_entities[group->member[memberNum].number].NPC ) + { + g_entities[group->member[memberNum].number].NPC->group = NULL; + } + for ( i = memberNum; i < (group->numGroup-1); i++ ) + { + memcpy( &group->member[i], &group->member[i+1], sizeof( group->member[i] ) ); + } + if ( memberNum < group->activeMemberNum ) + { + group->activeMemberNum--; + if ( group->activeMemberNum < 0 ) + { + group->activeMemberNum = 0; + } + } + group->numGroup--; + if ( group->numGroup < 0 ) + { + group->numGroup = 0; + } + AI_SetNewGroupCommander( group ); +} + +void AI_DeleteSelfFromGroup( gentity_t *self ) +{ + int i; + + //FIXME: if killed, keep track of how many in group killed? To affect morale? + for ( i = 0; i < self->NPC->group->numGroup; i++ ) + { + if ( self->NPC->group->member[i].number == self->s.number ) + { + AI_DeleteGroupMember( self->NPC->group, i ); + return; + } + } +} + +extern void ST_AggressionAdjust( gentity_t *self, int change ); +extern void ST_MarkToCover( gentity_t *self ); +extern void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime ); +void AI_GroupMemberKilled( gentity_t *self ) +{ + AIGroupInfo_t *group = self->NPC->group; + gentity_t *member; + qboolean noflee = qfalse; + int i; + + if ( !group ) + {//what group? + return; + } + if ( !self || !self->NPC || self->NPC->rank < RANK_ENSIGN ) + {//I'm not an officer, let's not really care for now + return; + } + //temporarily drop group morale for a few seconds + group->moraleAdjust -= self->NPC->rank; + //go through and drop aggression on my teammates (more cover, worse aim) + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( member == self ) + { + continue; + } + if ( member->NPC->rank > RANK_ENSIGN ) + {//officers do not panic + noflee = qtrue; + } + else + { + ST_AggressionAdjust( member, -1 ); + member->NPC->currentAim -= Q_irand( 0, 10 );//Q_irand( 0, 2);//drop their aim accuracy + } + } + //okay, if I'm the group commander, make everyone else flee + if ( group->commander != self ) + {//I'm not the commander... hmm, should maybe a couple flee... maybe those near me? + return; + } + //now see if there is another of sufficient rank to keep them from fleeing + if ( !noflee ) + { + self->NPC->group->speechDebounceTime = 0; + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( member == self ) + { + continue; + } + if ( member->NPC->rank < RANK_ENSIGN ) + {//grunt + if ( group->enemy && DistanceSquared( member->r.currentOrigin, group->enemy->r.currentOrigin ) < 65536/*256*256*/ ) + {//those close to enemy run away! + ST_StartFlee( member, group->enemy, member->r.currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); + } + else if ( DistanceSquared( member->r.currentOrigin, self->r.currentOrigin ) < 65536/*256*256*/ ) + {//those close to me run away! + ST_StartFlee( member, group->enemy, member->r.currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); + } + else + {//else, maybe just a random chance + if ( Q_irand( 0, self->NPC->rank ) > member->NPC->rank ) + {//lower rank they are, higher rank I am, more likely they are to flee + ST_StartFlee( member, group->enemy, member->r.currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); + } + else + { + ST_MarkToCover( member ); + } + } + member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more + } + member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more + } + } +} + +void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot ) +{ + if ( !group ) + { + return; + } + group->lastSeenEnemyTime = level.time; + VectorCopy( spot, group->enemyLastSeenPos ); +} + +void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group ) +{ + if ( !group ) + { + return; + } + group->lastClearShotTime = level.time; +} + +void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState ) +{ + int i; + + if ( !group ) + { + member->NPC->squadState = newSquadState; + return; + } + + for ( i = 0; i < group->numGroup; i++ ) + { + if ( group->member[i].number == member->s.number ) + { + group->numState[member->NPC->squadState]--; + member->NPC->squadState = newSquadState; + group->numState[member->NPC->squadState]++; + return; + } + } +} + +qboolean AI_RefreshGroup( AIGroupInfo_t *group ) +{ + gentity_t *member; + int i;//, j; + + //see if we should merge with another group + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( &level.groups[i] == group ) + { + break; + } + else + { + if ( level.groups[i].enemy == group->enemy ) + {//2 groups with same enemy + if ( level.groups[i].numGroup+group->numGroup < (MAX_GROUP_MEMBERS - 1) ) + {//combining the members would fit in one group + qboolean deleteWhenDone = qtrue; + int j; + + //combine the members of mine into theirs + for ( j = 0; j < group->numGroup; j++ ) + { + member = &g_entities[group->member[j].number]; + if ( level.groups[i].enemy == NULL ) + {//special case for groups without enemies, must be in range + if ( !AI_ValidateNoEnemyGroupMember( &level.groups[i], member ) ) + { + deleteWhenDone = qfalse; + continue; + } + } + //remove this member from this group + AI_DeleteGroupMember( group, j ); + //keep marker at same place since we deleted this guy and shifted everyone up one + j--; + //add them to the earlier group + AI_InsertGroupMember( &level.groups[i], member ); + } + //return and delete this group + if ( deleteWhenDone ) + { + return qfalse; + } + } + } + } + } + //clear numStates + for ( i = 0; i < NUM_SQUAD_STATES; i++ ) + { + group->numState[i] = 0; + } + + //go through group and validate each membership + group->commander = NULL; + for ( i = 0; i < group->numGroup; i++ ) + { + /* + //this checks for duplicate copies of one member in a group + for ( j = 0; j < group->numGroup; j++ ) + { + if ( i != j ) + { + if ( group->member[i].number == group->member[j].number ) + { + break; + } + } + } + if ( j < group->numGroup ) + {//found a dupe! + gi.Printf( S_COLOR_RED"ERROR: member %s(%d) a duplicate group member!!!\n", g_entities[group->member[i].number].targetname, group->member[i].number ); + AI_DeleteGroupMember( group, i ); + i--; + continue; + } + */ + member = &g_entities[group->member[i].number]; + + //Must be alive + if ( member->health <= 0 ) + { + AI_DeleteGroupMember( group, i ); + //keep marker at same place since we deleted this guy and shifted everyone up one + i--; + } + else if ( group->memberValidateTime < level.time && !AI_ValidateGroupMember( group, member ) ) + { + //remove this one from the group + AI_DeleteGroupMember( group, i ); + //keep marker at same place since we deleted this guy and shifted everyone up one + i--; + } + else + {//membership is valid + //keep track of squadStates + group->numState[member->NPC->squadState]++; + if ( !group->commander || member->NPC->rank > group->commander->NPC->rank ) + {//keep track of highest rank + group->commander = member; + } + } + } + if ( group->memberValidateTime < level.time ) + { + group->memberValidateTime = level.time + Q_irand( 500, 2500 ); + } + //Now add any new guys as long as we're not full + /* + for ( i = 0, member = &g_entities[0]; i < globals.num_entities && group->numGroup < (MAX_GROUP_MEMBERS - 1); i++, member++) + { + if ( !AI_ValidateGroupMember( group, member ) ) + {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group + continue; + } + if ( member->NPC->group == group ) + {//DOH, already in our group + continue; + } + + //store it + AI_InsertGroupMember( group, member ); + } + */ + + //calc the morale of this group + group->morale = group->moraleAdjust; + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( member->NPC->rank < RANK_ENSIGN ) + {//grunts + group->morale++; + } + else + { + group->morale += member->NPC->rank; + } + if ( group->commander && debugNPCAI.integer ) + { + //G_DebugLine( group->commander->r.currentOrigin, member->r.currentOrigin, FRAMETIME, 0x00ff00ff, qtrue ); + G_TestLine(group->commander->r.currentOrigin, member->r.currentOrigin, 0x00000ff, FRAMETIME); + } + } + if ( group->enemy ) + {//modify morale based on enemy health and weapon + if ( group->enemy->health < 10 ) + { + group->morale += 10; + } + else if ( group->enemy->health < 25 ) + { + group->morale += 5; + } + else if ( group->enemy->health < 50 ) + { + group->morale += 2; + } + switch( group->enemy->s.weapon ) + { + case WP_SABER: + group->morale -= 5; + break; + case WP_BRYAR_PISTOL: + group->morale += 3; + break; + case WP_DISRUPTOR: + group->morale += 2; + break; + case WP_REPEATER: + group->morale -= 1; + break; + case WP_FLECHETTE: + group->morale -= 2; + break; + case WP_ROCKET_LAUNCHER: + group->morale -= 10; + break; + case WP_THERMAL: + group->morale -= 5; + break; + case WP_TRIP_MINE: + group->morale -= 3; + break; + case WP_DET_PACK: + group->morale -= 10; + break; +// case WP_MELEE: // Any ol' melee attack +// group->morale += 20; +// break; + case WP_STUN_BATON: + group->morale += 10; + break; + case WP_EMPLACED_GUN: + group->morale -= 8; + break; +// case WP_ATST_MAIN: +// group->morale -= 8; +// break; +// case WP_ATST_SIDE: +// group->morale -= 20; +// break; + } + } + if ( group->moraleDebounce < level.time ) + {//slowly degrade whatever moraleAdjusters we may have + if ( group->moraleAdjust > 0 ) + { + group->moraleAdjust--; + } + else if ( group->moraleAdjust < 0 ) + { + group->moraleAdjust++; + } + group->moraleDebounce = level.time + 1000;//FIXME: define? + } + //mark this group as not having been run this frame + group->processed = qfalse; + + return (group->numGroup>0); +} + +void AI_UpdateGroups( void ) +{ + int i; + + if ( d_noGroupAI.integer ) + { + return; + } + //Clear all Groups + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( !level.groups[i].numGroup || AI_RefreshGroup( &level.groups[i] ) == qfalse )//level.groups[i].enemy == NULL || + { + memset( &level.groups[i], 0, sizeof( level.groups[i] ) ); + } + } +} + +qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum ) +{ + int i; + + if ( !group ) + { + return qfalse; + } + for ( i = 0; i < group->numGroup; i++ ) + { + if ( group->member[i].number == entNum ) + { + return qtrue; + } + } + return qfalse; +} +//Overload + +/* +void AI_GetGroup( AIGroupInfo_t &group, gentity_t *ent, int radius ) +{ + if ( ent->client == NULL ) + return; + + vec3_t temp, angles; + + //FIXME: This is specialized code.. move? + if ( ent->enemy ) + { + VectorSubtract( ent->enemy->r.currentOrigin, ent->r.currentOrigin, temp ); + VectorNormalize( temp ); //FIXME: Needed? + vectoangles( temp, angles ); + } + else + { + VectorCopy( ent->currentAngles, angles ); + } + + AI_GetGroup( group, ent->r.currentOrigin, ent->currentAngles, DEFAULT_RADIUS, radius, ent->client->playerTeam, ent, ent->enemy ); +} +*/ +/* +------------------------- +AI_CheckEnemyCollision +------------------------- +*/ + +qboolean AI_CheckEnemyCollision( gentity_t *ent, qboolean takeEnemy ) +{ + navInfo_t info; + + if ( ent == NULL ) + return qfalse; + +// if ( ent->svFlags & SVF_LOCKEDENEMY ) +// return qfalse; + + NAV_GetLastMove( &info ); + + //See if we've hit something + if ( ( info.blocker ) && ( info.blocker != ent->enemy ) ) + { + if ( ( info.blocker->client ) && ( info.blocker->client->playerTeam == ent->client->enemyTeam ) ) + { + if ( takeEnemy ) + G_SetEnemy( ent, info.blocker ); + + return qtrue; + } + } + + return qfalse; +} + +/* +------------------------- +AI_DistributeAttack +------------------------- +*/ + +#define MAX_RADIUS_ENTS 128 + +gentity_t *AI_DistributeAttack( gentity_t *attacker, gentity_t *enemy, team_t team, int threshold ) +{ + int radiusEnts[ MAX_RADIUS_ENTS ]; + gentity_t *check; + int numEnts; + int numSurrounding; + int i; + int j; + vec3_t mins, maxs; + + //Don't take new targets +// if ( NPC->svFlags & SVF_LOCKEDENEMY ) +// return enemy; + + numSurrounding = AI_GetGroupSize( enemy->r.currentOrigin, 48, team, attacker ); + + //First, see if we should look for the player + if ( enemy != &g_entities[0] ) + { + //rwwFIXMEFIXME: care about all clients not just 0 + int aroundPlayer = AI_GetGroupSize( g_entities[0].r.currentOrigin, 48, team, attacker ); + + //See if we're above our threshold + if ( aroundPlayer < threshold ) + { + return &g_entities[0]; + } + } + + //See if our current enemy is still ok + if ( numSurrounding < threshold ) + return enemy; + + //Otherwise we need to take a new enemy if possible + + //Setup the bbox to search in + for ( i = 0; i < 3; i++ ) + { + mins[i] = enemy->r.currentOrigin[i] - 512; + maxs[i] = enemy->r.currentOrigin[i] + 512; + } + + //Get the number of entities in a given space + numEnts = trap_EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS ); + + //Cull this list + for ( j = 0; j < numEnts; j++ ) + { + check = &g_entities[radiusEnts[j]]; + + //Validate clients + if ( check->client == NULL ) + continue; + + //Skip the requested avoid ent if present + if ( ( check == enemy ) ) + continue; + + //Must be on the same team + if ( check->client->playerTeam != enemy->client->playerTeam ) + continue; + + //Must be alive + if ( check->health <= 0 ) + continue; + + //Must not be overwhelmed + if ( AI_GetGroupSize( check->r.currentOrigin, 48, team, attacker ) > threshold ) + continue; + + return check; + } + + return NULL; +} diff --git a/codemp/game/NPC_AI_Wampa.c b/codemp/game/NPC_AI_Wampa.c new file mode 100644 index 0000000..a1c279f --- /dev/null +++ b/codemp/game/NPC_AI_Wampa.c @@ -0,0 +1,654 @@ +#include "b_local.h" +#include "g_nav.h" + +// These define the working combat range for these suckers +#define MIN_DISTANCE 48 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 1024 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +float enemyDist = 0; + +void Wampa_SetBolts( gentity_t *self ) +{ + if ( self && self->client ) + { + renderInfo_t *ri = &self->client->renderInfo; + ri->headBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*head_eyes"); + //ri->cervicalBolt = trap_G2API_AddBolt(self->ghoul2, 0, "neck_bone" ); + //ri->chestBolt = trap_G2API_AddBolt(self->ghoul2, 0, "upper_spine"); + //ri->gutBolt = trap_G2API_AddBolt(self->ghoul2, 0, "mid_spine"); + ri->torsoBolt = trap_G2API_AddBolt(self->ghoul2, 0, "lower_spine"); + ri->crotchBolt = trap_G2API_AddBolt(self->ghoul2, 0, "rear_bone"); + //ri->elbowLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_arm_elbow"); + //ri->elbowRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_arm_elbow"); + ri->handLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_hand"); + ri->handRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_hand"); + //ri->kneeLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*hips_l_knee"); + //ri->kneeRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*hips_r_knee"); + ri->footLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_leg_foot"); + ri->footRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_leg_foot"); + } +} + +/* +------------------------- +NPC_Wampa_Precache +------------------------- +*/ +void NPC_Wampa_Precache( void ) +{ + /* + int i; + for ( i = 1; i < 4; i ++ ) + { + G_SoundIndex( va("sound/chars/wampa/growl%d.wav", i) ); + } + for ( i = 1; i < 3; i ++ ) + { + G_SoundIndex( va("sound/chars/wampa/snort%d.wav", i) ); + } + */ + G_SoundIndex( "sound/chars/rancor/swipehit.wav" ); + //G_SoundIndex( "sound/chars/wampa/chomp.wav" ); +} + + +/* +------------------------- +Wampa_Idle +------------------------- +*/ +void Wampa_Idle( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } +} + +qboolean Wampa_CheckRoar( gentity_t *self ) +{ + if ( self->wait < level.time ) + { + self->wait = level.time + Q_irand( 5000, 20000 ); + NPC_SetAnim( self, SETANIM_BOTH, Q_irand(BOTH_GESTURE1,BOTH_GESTURE2), (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD) ); + TIMER_Set( self, "rageTime", self->client->ps.legsTimer ); + return qtrue; + } + return qfalse; +} +/* +------------------------- +Wampa_Patrol +------------------------- +*/ +void Wampa_Patrol( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + else + { + if ( TIMER_Done( NPC, "patrolTime" )) + { + TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 ); + } + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Wampa_Idle(); + return; + } + Wampa_CheckRoar( NPC ); + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); +} + +/* +------------------------- +Wampa_Move +------------------------- +*/ +void Wampa_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + + if ( NPC->enemy ) + {//pick correct movement speed and anim + //run by default + ucmd.buttons &= ~BUTTON_WALKING; + if ( !TIMER_Done( NPC, "runfar" ) + || !TIMER_Done( NPC, "runclose" ) ) + {//keep running with this anim & speed for a bit + } + else if ( !TIMER_Done( NPC, "walk" ) ) + {//keep walking for a bit + ucmd.buttons |= BUTTON_WALKING; + } + else if ( visible && enemyDist > 384 && NPCInfo->stats.runSpeed == 180 ) + {//fast run, all fours + NPCInfo->stats.runSpeed = 300; + TIMER_Set( NPC, "runfar", Q_irand( 2000, 4000 ) ); + } + else if ( enemyDist > 256 && NPCInfo->stats.runSpeed == 300 ) + {//slow run, upright + NPCInfo->stats.runSpeed = 180; + TIMER_Set( NPC, "runclose", Q_irand( 3000, 5000 ) ); + } + else if ( enemyDist < 128 ) + {//walk + NPCInfo->stats.runSpeed = 180; + ucmd.buttons |= BUTTON_WALKING; + TIMER_Set( NPC, "walk", Q_irand( 4000, 6000 ) ); + } + } + + if ( NPCInfo->stats.runSpeed == 300 ) + {//need to use the alternate run - hunched over on all fours + NPC->client->ps.eFlags2 |= EF2_USE_ALT_ANIM; + } + NPC_MoveToGoal( qtrue ); + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + } +} + +//--------------------------------------------------------- +//extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_Knockdown( gentity_t *victim ); +extern void G_Dismember( gentity_t *ent, gentity_t *enemy, vec3_t point, int limbType, float limbRollBase, float limbPitchBase, int deathAnim, qboolean postDeath ); +extern int NPC_GetEntsNearBolt( int *radiusEnts, float radius, int boltIndex, vec3_t boltOrg ); + +void Wampa_Slash( int boltIndex, qboolean backhand ) +{ + int radiusEntNums[128]; + int numEnts; + const float radius = 88; + const float radiusSquared = (radius*radius); + int i; + vec3_t boltOrg; + int damage = (backhand)?Q_irand(10,15):Q_irand(20,30); + + numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, boltIndex, boltOrg ); + + for ( i = 0; i < numEnts; i++ ) + { + gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; + if ( !radiusEnt->inuse ) + { + continue; + } + + if ( radiusEnt == NPC ) + {//Skip the wampa ent + continue; + } + + if ( radiusEnt->client == NULL ) + {//must be a client + continue; + } + + if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared ) + { + //smack + G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, damage, ((backhand)?DAMAGE_NO_ARMOR:(DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK)), MOD_MELEE ); + if ( backhand ) + { + //actually push the enemy + vec3_t pushDir; + vec3_t angs; + VectorCopy( NPC->client->ps.viewangles, angs ); + angs[YAW] += flrand( 25, 50 ); + angs[PITCH] = flrand( -25, -15 ); + AngleVectors( angs, pushDir, NULL, NULL ); + if ( radiusEnt->client->NPC_class != CLASS_WAMPA + && radiusEnt->client->NPC_class != CLASS_RANCOR + && radiusEnt->client->NPC_class != CLASS_ATST ) + { + G_Throw( radiusEnt, pushDir, 65 ); + if ( BG_KnockDownable(&radiusEnt->client->ps) && + radiusEnt->health > 0 && Q_irand( 0, 1 ) ) + {//do pain on enemy + radiusEnt->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + radiusEnt->client->ps.forceDodgeAnim = 0; + radiusEnt->client->ps.forceHandExtendTime = level.time + 1100; + radiusEnt->client->ps.quickerGetup = qfalse; + } + } + } + else if ( radiusEnt->health <= 0 && radiusEnt->client ) + {//killed them, chance of dismembering + if ( !Q_irand( 0, 1 ) ) + {//bite something off + int hitLoc = Q_irand( G2_MODELPART_HEAD, G2_MODELPART_RLEG ); + if ( hitLoc == G2_MODELPART_HEAD ) + { + NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else if ( hitLoc == G2_MODELPART_WAIST ) + { + NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + G_Dismember( radiusEnt, NPC, radiusEnt->r.currentOrigin, hitLoc, 90, 0, radiusEnt->client->ps.torsoAnim, qtrue); + } + } + else if ( !Q_irand( 0, 3 ) && radiusEnt->health > 0 ) + {//one out of every 4 normal hits does a knockdown, too + vec3_t pushDir; + vec3_t angs; + VectorCopy( NPC->client->ps.viewangles, angs ); + angs[YAW] += flrand( 25, 50 ); + angs[PITCH] = flrand( -25, -15 ); + AngleVectors( angs, pushDir, NULL, NULL ); + G_Knockdown( radiusEnt ); + } + G_Sound( radiusEnt, CHAN_WEAPON, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + } + } +} + +//------------------------------ +void Wampa_Attack( float distance, qboolean doCharge ) +{ + if ( !TIMER_Exists( NPC, "attacking" ) ) + { + if ( Q_irand(0, 2) && !doCharge ) + {//double slash + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 750 ); + } + else if ( doCharge || (distance > 270 && distance < 430 && !Q_irand(0, 1)) ) + {//leap + vec3_t fwd, yawAng; + VectorSet( yawAng, 0, NPC->client->ps.viewangles[YAW], 0 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 500 ); + AngleVectors( yawAng, fwd, NULL, NULL ); + VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 150; + NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; + } + else + {//backhand + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 250 ); + } + + TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + random() * 200 ); + //allow us to re-evaluate our running speed/anim + TIMER_Set( NPC, "runfar", -1 ); + TIMER_Set( NPC, "runclose", -1 ); + TIMER_Set( NPC, "walk", -1 ); + } + + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + + if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) + { + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_ATTACK1: + Wampa_Slash( NPC->client->renderInfo.handRBolt, qfalse ); + //do second hit + TIMER_Set( NPC, "attack_dmg2", 100 ); + break; + case BOTH_ATTACK2: + Wampa_Slash( NPC->client->renderInfo.handRBolt, qfalse ); + TIMER_Set( NPC, "attack_dmg2", 100 ); + break; + case BOTH_ATTACK3: + Wampa_Slash( NPC->client->renderInfo.handLBolt, qtrue ); + break; + } + } + else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) ) + { + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_ATTACK1: + Wampa_Slash( NPC->client->renderInfo.handLBolt, qfalse ); + break; + case BOTH_ATTACK2: + Wampa_Slash( NPC->client->renderInfo.handLBolt, qfalse ); + break; + } + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); + + if ( NPC->client->ps.legsAnim == BOTH_ATTACK1 && distance > (NPC->r.maxs[0]+MIN_DISTANCE) ) + {//okay to keep moving + ucmd.buttons |= BUTTON_WALKING; + Wampa_Move( 1 ); + } +} + +//---------------------------------- +void Wampa_Combat( void ) +{ + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) ) + { + if ( !Q_irand( 0, 10 ) ) + { + if ( Wampa_CheckRoar( NPC ) ) + { + return; + } + } + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + + Wampa_Move( 0 ); + return; + } + else if ( UpdateGoal() ) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + + Wampa_Move( 1 ); + return; + } + else + { + float distance = enemyDist = Distance( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + qboolean advance = (qboolean)( distance > (NPC->r.maxs[0]+MIN_DISTANCE) ? qtrue : qfalse ); + qboolean doCharge = qfalse; + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + //FIXME: always seems to face off to the left or right?!!!! + NPC_FaceEnemy( qtrue ); + + + if ( advance ) + {//have to get closer + vec3_t yawOnlyAngles; + VectorSet( yawOnlyAngles, 0, NPC->r.currentAngles[YAW], 0 ); + if ( NPC->enemy->health > 0//enemy still alive + && fabs(distance-350) <= 80 //enemy anywhere from 270 to 430 away + && InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, yawOnlyAngles, 20, 20 ) )//enemy generally in front + {//10% chance of doing charge anim + if ( !Q_irand( 0, 9 ) ) + {//go for the charge + doCharge = qtrue; + advance = qfalse; + } + } + } + + if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + Wampa_Move( 1 ); + } + } + else + { + if ( !Q_irand( 0, 20 ) ) + {//FIXME: only do this if we just damaged them or vice-versa? + if ( Wampa_CheckRoar( NPC ) ) + { + return; + } + } + if ( !Q_irand( 0, 1 ) ) + {//FIXME: base on skill + Wampa_Attack( distance, doCharge ); + } + } + } +} + +/* +------------------------- +NPC_Wampa_Pain +------------------------- +*/ +//void NPC_Wampa_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +void NPC_Wampa_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + qboolean hitByWampa = qfalse; + if ( attacker&&attacker->client&&attacker->client->NPC_class==CLASS_WAMPA ) + { + hitByWampa = qtrue; + } + if ( attacker + && attacker->inuse + && attacker != self->enemy + && !(attacker->flags&FL_NOTARGET) ) + { + if ( (!attacker->s.number&&!Q_irand(0,3)) + || !self->enemy + || self->enemy->health == 0 + || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_WAMPA) + || (!Q_irand(0, 4 ) && DistanceSquared( attacker->r.currentOrigin, self->r.currentOrigin ) < DistanceSquared( self->enemy->r.currentOrigin, self->r.currentOrigin )) ) + {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker + //FIXME: if can't nav to my enemy, take this guy if I can nav to him + G_SetEnemy( self, attacker ); + TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + if ( hitByWampa ) + {//stay mad at this Wampa for 2-5 secs before looking for attacker enemies + TIMER_Set( self, "wampaInfight", Q_irand( 2000, 5000 ) ); + } + } + } + if ( (hitByWampa|| Q_irand( 0, 100 ) < damage )//hit by wampa, hit while holding live victim, or took a lot of damage + && self->client->ps.legsAnim != BOTH_GESTURE1 + && self->client->ps.legsAnim != BOTH_GESTURE2 + && TIMER_Done( self, "takingPain" ) ) + { + if ( !Wampa_CheckRoar( self ) ) + { + if ( self->client->ps.legsAnim != BOTH_ATTACK1 + && self->client->ps.legsAnim != BOTH_ATTACK2 + && self->client->ps.legsAnim != BOTH_ATTACK3 ) + {//cant interrupt one of the big attack anims + if ( self->health > 100 || hitByWampa ) + { + TIMER_Remove( self, "attacking" ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( !Q_irand( 0, 1 ) ) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + TIMER_Set( self, "takingPain", self->client->ps.legsTimer+Q_irand(0, 500) ); + //allow us to re-evaluate our running speed/anim + TIMER_Set( self, "runfar", -1 ); + TIMER_Set( self, "runclose", -1 ); + TIMER_Set( self, "walk", -1 ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } + } + } + } +} + +/* +------------------------- +NPC_BSWampa_Default +------------------------- +*/ +void NPC_BSWampa_Default( void ) +{ + NPC->client->ps.eFlags2 &= ~EF2_USE_ALT_ANIM; + //NORMAL ANIMS + // stand1 = normal stand + // walk1 = normal, non-angry walk + // walk2 = injured + // run1 = far away run + // run2 = close run + //VICTIM ANIMS + // grabswipe = melee1 - sweep out and grab + // stand2 attack = attack4 - while holding victim, swipe at him + // walk3_drag = walk5 - walk with drag + // stand2 = hold victim + // stand2to1 = drop victim + if ( !TIMER_Done( NPC, "rageTime" ) ) + {//do nothing but roar first time we see an enemy + NPC_FaceEnemy( qtrue ); + return; + } + if ( NPC->enemy ) + { + if ( !TIMER_Done(NPC,"attacking") ) + {//in middle of attack + //face enemy + NPC_FaceEnemy( qtrue ); + //continue attack logic + enemyDist = Distance( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + Wampa_Attack( enemyDist, qfalse ); + return; + } + else + { + if ( TIMER_Done(NPC,"angrynoise") ) + { + G_Sound( NPC, CHAN_VOICE, G_SoundIndex( va("sound/chars/wampa/misc/anger%d.wav", Q_irand(1, 2)) ) ); + + TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); + } + //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while + if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_WAMPA ) + {//got mad at another Wampa, look for a valid enemy + if ( TIMER_Done( NPC, "wampaInfight" ) ) + { + NPC_CheckEnemyExt( qtrue ); + } + } + else + { + if ( ValidEnemy( NPC->enemy ) == qfalse ) + { + TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now + if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) + {//it's been a while since the enemy died, or enemy is completely gone, get bored with him + NPC->enemy = NULL; + Wampa_Patrol(); + NPC_UpdateAngles( qtrue, qtrue ); + //just lost my enemy + if ( (NPC->spawnflags&2) ) + {//search around me if I don't have an enemy + NPC_BSSearchStart( NPC->waypoint, BS_SEARCH ); + NPCInfo->tempBehavior = BS_DEFAULT; + } + else if ( (NPC->spawnflags&1) ) + {//wander if I don't have an enemy + NPC_BSSearchStart( NPC->waypoint, BS_WANDER ); + NPCInfo->tempBehavior = BS_DEFAULT; + } + return; + } + } + if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) + { + gentity_t *newEnemy, *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? + NPC->enemy = NULL; + newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); + NPC->enemy = sav_enemy; + if ( newEnemy && newEnemy != sav_enemy ) + {//picked up a new enemy! + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, newEnemy ); + //hold this one for at least 5-15 seconds + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + } + else + {//look again in 2-5 secs + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); + } + } + } + Wampa_Combat(); + return; + } + } + else + { + if ( TIMER_Done(NPC,"idlenoise") ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/wampa/misc/anger3.wav" ) ); + + TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); + } + if ( (NPC->spawnflags&2) ) + {//search around me if I don't have an enemy + if ( NPCInfo->homeWp == WAYPOINT_NONE ) + {//no homewap, initialize the search behavior + NPC_BSSearchStart( WAYPOINT_NONE, BS_SEARCH ); + NPCInfo->tempBehavior = BS_DEFAULT; + } + ucmd.buttons |= BUTTON_WALKING; + NPC_BSSearch();//this automatically looks for enemies + } + else if ( (NPC->spawnflags&1) ) + {//wander if I don't have an enemy + if ( NPCInfo->homeWp == WAYPOINT_NONE ) + {//no homewap, initialize the wander behavior + NPC_BSSearchStart( WAYPOINT_NONE, BS_WANDER ); + NPCInfo->tempBehavior = BS_DEFAULT; + } + ucmd.buttons |= BUTTON_WALKING; + NPC_BSWander(); + if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Wampa_Idle(); + } + else + { + Wampa_CheckRoar( NPC ); + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + } + } + } + else + { + if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Wampa_Patrol(); + } + else + { + Wampa_Idle(); + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/codemp/game/NPC_behavior.c b/codemp/game/NPC_behavior.c new file mode 100644 index 0000000..a7e3369 --- /dev/null +++ b/codemp/game/NPC_behavior.c @@ -0,0 +1,1748 @@ +//NPC_behavior.cpp +/* +FIXME - MCG: +These all need to make use of the snapshots. Write something that can look for only specific +things in a snapshot or just go through the snapshot every frame and save the info in case +we need it... +*/ + +#include "b_local.h" +#include "g_nav.h" +#include "../icarus/Q3_Interface.h" + +extern qboolean showBBoxes; +extern vec3_t NPCDEBUG_BLUE; +extern void G_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ); +extern void NPC_CheckGetNewWeapon( void ); + +#include "../namespace_begin.h" +extern qboolean PM_InKnockDown( playerState_t *ps ); +#include "../namespace_end.h" + +extern void NPC_AimAdjust( int change ); +extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent); +/* + void NPC_BSAdvanceFight (void) + +Advance towards your captureGoal and shoot anyone you can along the way. +*/ +void NPC_BSAdvanceFight (void) +{//FIXME: IMPLEMENT +//Head to Goal if I can + + //Make sure we're still headed where we want to capture + if ( NPCInfo->captureGoal ) + {//FIXME: if no captureGoal, what do we do? + //VectorCopy( NPCInfo->captureGoal->r.currentOrigin, NPCInfo->tempGoal->r.currentOrigin ); + //NPCInfo->goalEntity = NPCInfo->tempGoal; + + NPC_SetMoveGoal( NPC, NPCInfo->captureGoal->r.currentOrigin, 16, qtrue, -1, NULL ); + +// NAV_ClearLastRoute(NPC); + NPCInfo->goalTime = level.time + 100000; + } + +// NPC_BSRun(); + + NPC_CheckEnemy(qtrue, qfalse, qtrue); + + //FIXME: Need melee code + if( NPC->enemy ) + {//See if we can shoot him + vec3_t delta, forward; + vec3_t angleToEnemy; + vec3_t hitspot, muzzle, diff, enemy_org, enemy_head; + float distanceToEnemy; + qboolean attack_ok = qfalse; + qboolean dead_on = qfalse; + float attack_scale = 1.0; + float aim_off; + float max_aim_off = 64; + + //Yaw to enemy + VectorMA(NPC->enemy->r.absmin, 0.5, NPC->enemy->r.maxs, enemy_org); + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorSubtract (enemy_org, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + distanceToEnemy = VectorNormalize(delta); + + if(!NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue)) + { + attack_ok = qtrue; + } + + if(attack_ok) + { + NPC_UpdateShootAngles(angleToEnemy, qfalse, qtrue); + + NPCInfo->enemyLastVisibility = enemyVisibility; + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV);//CHECK_360|//CHECK_PVS| + + if(enemyVisibility == VIS_FOV) + {//He's in our FOV + + attack_ok = qtrue; + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_head); + + if(attack_ok) + { + trace_t tr; + gentity_t *traceEnt; + //are we gonna hit him if we shoot at his center? + trap_Trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT ); + traceEnt = &g_entities[tr.entityNum]; + if( traceEnt != NPC->enemy && + (!traceEnt || !traceEnt->client || !NPC->client->enemyTeam || NPC->client->enemyTeam != traceEnt->client->playerTeam) ) + {//no, so shoot for the head + attack_scale *= 0.75; + trap_Trace ( &tr, muzzle, NULL, NULL, enemy_head, NPC->s.number, MASK_SHOT ); + traceEnt = &g_entities[tr.entityNum]; + } + + VectorCopy( tr.endpos, hitspot ); + + if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) ) + { + dead_on = qtrue; + } + else + { + attack_scale *= 0.5; + if(NPC->client->playerTeam) + { + if(traceEnt && traceEnt->client && traceEnt->client->playerTeam) + { + if(NPC->client->playerTeam == traceEnt->client->playerTeam) + {//Don't shoot our own team + attack_ok = qfalse; + } + } + } + } + } + + if( attack_ok ) + { + //ok, now adjust pitch aim + VectorSubtract (hitspot, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateShootAngles(angleToEnemy, qtrue, qfalse); + + if( !dead_on ) + {//We're not going to hit him directly, try a suppressing fire + //see if where we're going to shoot is too far from his origin + AngleVectors (NPCInfo->shootAngles, forward, NULL, NULL); + VectorMA ( muzzle, distanceToEnemy, forward, hitspot); + VectorSubtract(hitspot, enemy_org, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off)//FIXME: use aim value to allow poor aim? + { + attack_scale *= 0.75; + //see if where we're going to shoot is too far from his head + VectorSubtract(hitspot, enemy_head, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off) + { + attack_ok = qfalse; + } + } + attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off; + } + } + } + } + + if( attack_ok ) + { + if( NPC_CheckAttack( attack_scale )) + {//check aggression to decide if we should shoot + enemyVisibility = VIS_SHOOT; + WeaponThink(qtrue); + } + else + attack_ok = qfalse; + } +//Don't do this- only for when stationary and trying to shoot an enemy +// else +// NPC->cantHitEnemyCounter++; + } + else + {//FIXME: + NPC_UpdateShootAngles(NPC->client->ps.viewangles, qtrue, qtrue); + } + + if(!ucmd.forwardmove && !ucmd.rightmove) + {//We reached our captureGoal + if(trap_ICARUS_IsInitialized(NPC->s.number)) + { + trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE ); + } + } +} + +void Disappear(gentity_t *self) +{ +// ClientDisconnect(self); + self->s.eFlags |= EF_NODRAW; + self->think = 0; + self->nextthink = -1; +} + +void MakeOwnerInvis (gentity_t *self); +void BeamOut (gentity_t *self) +{ +// gentity_t *tent = G_Spawn(); + +/* + tent->owner = self; + tent->think = MakeOwnerInvis; + tent->nextthink = level.time + 1800; + //G_AddEvent( ent, EV_PLAYER_TELEPORT, 0 ); + tent = G_TempEntity( self->client->pcurrentOrigin, EV_PLAYER_TELEPORT ); +*/ + //fixme: doesn't actually go away! + self->nextthink = level.time + 1500; + self->think = Disappear; + self->client->squadname = NULL; + self->client->playerTeam = self->s.teamowner = TEAM_FREE; + //self->r.svFlags |= SVF_BEAMING; //this appears unused in SP as well +} + +void NPC_BSCinematic( void ) +{ + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( UpdateGoal() ) + {//have a goalEntity + //move toward goal, should also face that goal + NPC_MoveToGoal( qtrue ); + } + + if ( NPCInfo->watchTarget ) + {//have an entity which we want to keep facing + //NOTE: this will override any angles set by NPC_MoveToGoal + vec3_t eyes, viewSpot, viewvec, viewangles; + + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); + CalcEntitySpot( NPCInfo->watchTarget, SPOT_HEAD_LEAN, viewSpot ); + + VectorSubtract( viewSpot, eyes, viewvec ); + + vectoangles( viewvec, viewangles ); + + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw = viewangles[YAW]; + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch = viewangles[PITCH]; + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +void NPC_BSWait( void ) +{ + NPC_UpdateAngles( qtrue, qtrue ); +} + + +void NPC_BSInvestigate (void) +{ +/* + //FIXME: maybe allow this to be set as a tempBState in a script? Just specify the + //investigateGoal, investigateDebounceTime and investigateCount? (Needs a macro) + vec3_t invDir, invAngles, spot; + gentity_t *saveGoal; + //BS_INVESTIGATE would turn toward goal, maybe take a couple steps towards it, + //look for enemies, then turn away after your investigate counter was down- + //investigate counter goes up every time you set it... + + if(level.time > NPCInfo->enemyCheckDebounceTime) + { + NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 1000); + NPC_CheckEnemy(qtrue, qfalse); + if(NPC->enemy) + {//FIXME: do anger script + NPCInfo->goalEntity = NPC->enemy; +// NAV_ClearLastRoute(NPC); + NPCInfo->behaviorState = BS_RUN_AND_SHOOT; + NPCInfo->tempBehavior = BS_DEFAULT; + NPC_AngerSound(); + return; + } + } + + NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_WEAPONREADY3, SETANIM_FLAG_NORMAL ); + + if(NPCInfo->stats.vigilance <= 1.0 && NPCInfo->eventOwner) + { + VectorCopy(NPCInfo->eventOwner->r.currentOrigin, NPCInfo->investigateGoal); + } + + saveGoal = NPCInfo->goalEntity; + if( level.time > NPCInfo->walkDebounceTime ) + { + vec3_t vec; + + VectorSubtract(NPCInfo->investigateGoal, NPC->r.currentOrigin, vec); + vec[2] = 0; + if(VectorLength(vec) > 64) + { + if(Q_irand(0, 100) < NPCInfo->investigateCount) + {//take a full step + //NPCInfo->walkDebounceTime = level.time + 1400; + //actually finds length of my BOTH_WALK anim + NPCInfo->walkDebounceTime = PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_WALK1 ); + } + } + } + + if( level.time < NPCInfo->walkDebounceTime ) + {//walk toward investigateGoal + + /* + NPCInfo->goalEntity = NPCInfo->tempGoal; +// NAV_ClearLastRoute(NPC); + VectorCopy(NPCInfo->investigateGoal, NPCInfo->tempGoal->r.currentOrigin); + */ + +/* NPC_SetMoveGoal( NPC, NPCInfo->investigateGoal, 16, qtrue ); + + NPC_MoveToGoal( qtrue ); + + //FIXME: walk2? + NPC_SetAnim(NPC,SETANIM_LEGS,BOTH_WALK1,SETANIM_FLAG_NORMAL); + + ucmd.buttons |= BUTTON_WALKING; + } + else + { + + NPC_SetAnim(NPC,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + + if(NPCInfo->hlookCount > 30) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->hlookCount = 0; + } + } + else if(NPCInfo->hlookCount < -30) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->hlookCount = 0; + } + } + else if(NPCInfo->hlookCount == 0) + { + NPCInfo->hlookCount = Q_irand(-1, 1); + } + else if(Q_irand(0, 10) > 7) + { + if(NPCInfo->hlookCount > 0) + { + NPCInfo->hlookCount++; + } + else//lookCount < 0 + { + NPCInfo->hlookCount--; + } + } + + if(NPCInfo->vlookCount >= 15) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->vlookCount = 0; + } + } + else if(NPCInfo->vlookCount <= -15) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->vlookCount = 0; + } + } + else if(NPCInfo->vlookCount == 0) + { + NPCInfo->vlookCount = Q_irand(-1, 1); + } + else if(Q_irand(0, 10) > 8) + { + if(NPCInfo->vlookCount > 0) + { + NPCInfo->vlookCount++; + } + else//lookCount < 0 + { + NPCInfo->vlookCount--; + } + } + + //turn toward investigateGoal + CalcEntitySpot( NPC, SPOT_HEAD, spot ); + VectorSubtract(NPCInfo->investigateGoal, spot, invDir); + VectorNormalize(invDir); + vectoangles(invDir, invAngles); + NPCInfo->desiredYaw = AngleNormalize360(invAngles[YAW] + NPCInfo->hlookCount); + NPCInfo->desiredPitch = AngleNormalize360(invAngles[PITCH] + NPCInfo->hlookCount); + } + + NPC_UpdateAngles(qtrue, qtrue); + + NPCInfo->goalEntity = saveGoal; +// NAV_ClearLastRoute(NPC); + + if(level.time > NPCInfo->investigateDebounceTime) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + + NPC_CheckSoundEvents(); + */ +} + +qboolean NPC_CheckInvestigate( int alertEventNum ) +{ + gentity_t *owner = level.alertEvents[alertEventNum].owner; + int invAdd = level.alertEvents[alertEventNum].level; + vec3_t soundPos; + float soundRad = level.alertEvents[alertEventNum].radius; + float earshot = NPCInfo->stats.earshot; + + VectorCopy( level.alertEvents[alertEventNum].position, soundPos ); + + //NOTE: Trying to preserve previous investigation behavior + if ( !owner ) + { + return qfalse; + } + + if ( owner->s.eType != ET_PLAYER && owner->s.eType != ET_NPC && owner == NPCInfo->goalEntity ) + { + return qfalse; + } + + if ( owner->s.eFlags & EF_NODRAW ) + { + return qfalse; + } + + if ( owner->flags & FL_NOTARGET ) + { + return qfalse; + } + + if ( soundRad < earshot ) + { + return qfalse; + } + + //if(!trap_InPVSIgnorePortals(ent->r.currentOrigin, NPC->r.currentOrigin))//should we be able to hear through areaportals? + if ( !trap_InPVS( soundPos, NPC->r.currentOrigin ) ) + {//can hear through doors? + return qfalse; + } + + if ( owner->client && owner->client->playerTeam && NPC->client->playerTeam && owner->client->playerTeam != NPC->client->playerTeam ) + { + if( (float)NPCInfo->investigateCount >= (NPCInfo->stats.vigilance*200) && owner ) + {//If investigateCount == 10, just take it as enemy and go + if ( ValidEnemy( owner ) ) + {//FIXME: run angerscript + G_SetEnemy( NPC, owner ); + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + return qtrue; + } + } + else + { + NPCInfo->investigateCount += invAdd; + } + //run awakescript + G_ActivateBehavior(NPC, BSET_AWAKE); + + /* + if ( Q_irand(0, 10) > 7 ) + { + NPC_AngerSound(); + } + */ + + //NPCInfo->hlookCount = NPCInfo->vlookCount = 0; + NPCInfo->eventOwner = owner; + VectorCopy( soundPos, NPCInfo->investigateGoal ); + if ( NPCInfo->investigateCount > 20 ) + { + NPCInfo->investigateDebounceTime = level.time + 10000; + } + else + { + NPCInfo->investigateDebounceTime = level.time + (NPCInfo->investigateCount*500); + } + NPCInfo->tempBehavior = BS_INVESTIGATE; + return qtrue; + } + + return qfalse; +} + + +/* +void NPC_BSSleep( void ) +*/ +void NPC_BSSleep( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qtrue, qfalse, -1, qfalse, AEL_MINOR ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + G_ActivateBehavior(NPC, BSET_AWAKE); + return; + } + + /* + if ( level.time > NPCInfo->enemyCheckDebounceTime ) + { + if ( NPC_CheckSoundEvents() != -1 ) + {//only 1 alert per second per 0.1 of vigilance + NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 10000); + G_ActivateBehavior(NPC, BSET_AWAKE); + } + } + */ +} + +extern qboolean NPC_MoveDirClear( int forwardmove, int rightmove, qboolean reset ); +void NPC_BSFollowLeader (void) +{ + vec3_t vec; + float leaderDist; + visibility_t leaderVis; + int curAnim; + + if ( !NPC->client->leader ) + {//ok, stand guard until we find an enemy + if( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + { + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + } + return; + } + + if ( !NPC->enemy ) + {//no enemy, find one + NPC_CheckEnemy( NPCInfo->confusionTimeenemy ) + {//just found one + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + } + else + { + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + int eventID = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR ); + if ( level.alertEvents[eventID].level >= AEL_SUSPICIOUS && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { + NPCInfo->lastAlertID = level.alertEvents[eventID].ID; + if ( !level.alertEvents[eventID].owner || + !level.alertEvents[eventID].owner->client || + level.alertEvents[eventID].owner->health <= 0 || + level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam ) + {//not an enemy + } + else + { + //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him? Or just let combat AI handle this... (act as if you lost him) + G_SetEnemy( NPC, level.alertEvents[eventID].owner ); + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 1000 ) ); + } + } + + } + } + if ( !NPC->enemy ) + { + if ( NPC->client->leader + && NPC->client->leader->enemy + && NPC->client->leader->enemy != NPC + && ( (NPC->client->leader->enemy->client&&NPC->client->leader->enemy->client->playerTeam==NPC->client->enemyTeam) + ||(/*NPC->client->leader->enemy->r.svFlags&SVF_NONNPC_ENEMY*/0&&NPC->client->leader->enemy->alliedTeam==NPC->client->enemyTeam) ) + && NPC->client->leader->enemy->health > 0 ) + { //rwwFIXMEFIXME: use SVF_NONNPC_ENEMY? + G_SetEnemy( NPC, NPC->client->leader->enemy ); + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + NPCInfo->enemyLastSeenTime = level.time; + } + } + } + else + { + if ( NPC->enemy->health <= 0 || (NPC->enemy->flags&FL_NOTARGET) ) + { + G_ClearEnemy( NPC ); + if ( NPCInfo->enemyCheckDebounceTime > level.time + 1000 ) + { + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 2000 ); + } + } + else if ( NPC->client->ps.weapon && NPCInfo->enemyCheckDebounceTime < level.time ) + { + NPC_CheckEnemy( (NPCInfo->confusionTimetempBehavior!=BS_FOLLOW_LEADER), qfalse, qtrue );//don't find new enemy if this is tempbehav + } + } + + if ( NPC->enemy && NPC->client->ps.weapon ) + {//If have an enemy, face him and fire + if ( NPC->client->ps.weapon == WP_SABER )//|| NPCInfo->confusionTime>level.time ) + {//lightsaber user or charmed enemy + if ( NPCInfo->tempBehavior != BS_FOLLOW_LEADER ) + {//not already in a temp bState + //go after the guy + NPCInfo->tempBehavior = BS_HUNT_AND_KILL; + NPC_UpdateAngles(qtrue, qtrue); + return; + } + } + + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT );//CHECK_360|CHECK_PVS| + if ( enemyVisibility > VIS_PVS ) + {//face + vec3_t enemy_org, muzzle, delta, angleToEnemy; + float distanceToEnemy; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org ); + NPC_AimWiggle( enemy_org ); + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorSubtract( enemy_org, muzzle, delta); + vectoangles( delta, angleToEnemy ); + distanceToEnemy = VectorNormalize( delta ); + + NPCInfo->desiredYaw = angleToEnemy[YAW]; + NPCInfo->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles( qtrue, qtrue ); + + if ( enemyVisibility >= VIS_SHOOT ) + {//shoot + NPC_AimAdjust( 2 ); + if ( NPC_GetHFOVPercentage( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, NPCInfo->stats.hfov ) > 0.6f + && NPC_GetHFOVPercentage( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, NPCInfo->stats.vfov ) > 0.5f ) + {//actually withing our front cone + WeaponThink( qtrue ); + } + } + else + { + NPC_AimAdjust( 1 ); + } + + //NPC_CheckCanAttack(1.0, qfalse); + } + else + { + NPC_AimAdjust( -1 ); + } + } + else + {//FIXME: combine with vector calc below + vec3_t head, leaderHead, delta, angleToLeader; + + CalcEntitySpot( NPC->client->leader, SPOT_HEAD, leaderHead ); + CalcEntitySpot( NPC, SPOT_HEAD, head ); + VectorSubtract (leaderHead, head, delta); + vectoangles ( delta, angleToLeader ); + VectorNormalize(delta); + NPC->NPC->desiredYaw = angleToLeader[YAW]; + NPC->NPC->desiredPitch = angleToLeader[PITCH]; + + NPC_UpdateAngles(qtrue, qtrue); + } + + //leader visible? + leaderVis = NPC_CheckVisibility( NPC->client->leader, CHECK_PVS|CHECK_360|CHECK_SHOOT );// ent->e_UseFunc = useF_NULL; + + + //Follow leader, stay within visibility and a certain distance, maintain a distance from. + curAnim = NPC->client->ps.legsAnim; + if ( curAnim != BOTH_ATTACK1 && curAnim != BOTH_ATTACK2 && curAnim != BOTH_ATTACK3 && curAnim != BOTH_MELEE1 && curAnim != BOTH_MELEE2 ) + {//Don't move toward leader if we're in a full-body attack anim + //FIXME, use IdealDistance to determine if we need to close distance + float followDist = 96.0f;//FIXME: If there are enmies, make this larger? + float backupdist, walkdist, minrundist; + float leaderHDist; + + if ( NPCInfo->followDist ) + { + followDist = NPCInfo->followDist; + } + backupdist = followDist/2.0f; + walkdist = followDist*0.83; + minrundist = followDist*1.33; + + VectorSubtract(NPC->client->leader->r.currentOrigin, NPC->r.currentOrigin, vec); + leaderDist = VectorLength( vec );//FIXME: make this just nav distance? + //never get within their radius horizontally + vec[2] = 0; + leaderHDist = VectorLength( vec ); + if( leaderHDist > backupdist && (leaderVis != VIS_SHOOT || leaderDist > walkdist) ) + {//We should close in? + NPCInfo->goalEntity = NPC->client->leader; + + NPC_SlideMoveToGoal(); + if ( leaderVis == VIS_SHOOT && leaderDist < minrundist ) + { + ucmd.buttons |= BUTTON_WALKING; + } + } + else if ( leaderDist < backupdist ) + {//We should back off? + NPCInfo->goalEntity = NPC->client->leader; + NPC_SlideMoveToGoal(); + + //reversing direction + ucmd.forwardmove = -ucmd.forwardmove; + ucmd.rightmove = -ucmd.rightmove; + VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + }//otherwise, stay where we are + //check for do not enter and stop if there's one there... + if ( ucmd.forwardmove || ucmd.rightmove || VectorCompare( vec3_origin, NPC->client->ps.moveDir ) ) + { + NPC_MoveDirClear( ucmd.forwardmove, ucmd.rightmove, qtrue ); + } + } +} +#define APEX_HEIGHT 200.0f +#define PARA_WIDTH (sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT)) +#define JUMP_SPEED 200.0f +void NPC_BSJump (void) +{ + vec3_t dir, angles, p1, p2, apex; + float time, height, forward, z, xy, dist, yawError, apexHeight; + + if( !NPCInfo->goalEntity ) + {//Should have task completed the navgoal + return; + } + + if ( NPCInfo->jumpState != JS_JUMPING && NPCInfo->jumpState != JS_LANDING ) + { + //Face navgoal + VectorSubtract(NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir); + vectoangles(dir, angles); + NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]); + NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]); + } + + NPC_UpdateAngles ( qtrue, qtrue ); + yawError = AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw ); + //We don't really care about pitch here + + switch ( NPCInfo->jumpState ) + { + case JS_FACING: + if ( yawError < MIN_ANGLE_ERROR ) + {//Facing it, Start crouching + NPC_SetAnim(NPC, SETANIM_LEGS, BOTH_CROUCH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + NPCInfo->jumpState = JS_CROUCHING; + } + break; + case JS_CROUCHING: + if ( NPC->client->ps.legsTimer > 0 ) + {//Still playing crouching anim + return; + } + + //Create a parabola + + if ( NPC->r.currentOrigin[2] > NPCInfo->goalEntity->r.currentOrigin[2] ) + { + VectorCopy( NPC->r.currentOrigin, p1 ); + VectorCopy( NPCInfo->goalEntity->r.currentOrigin, p2 ); + } + else if ( NPC->r.currentOrigin[2] < NPCInfo->goalEntity->r.currentOrigin[2] ) + { + VectorCopy( NPCInfo->goalEntity->r.currentOrigin, p1 ); + VectorCopy( NPC->r.currentOrigin, p2 ); + } + else + { + VectorCopy( NPC->r.currentOrigin, p1 ); + VectorCopy( NPCInfo->goalEntity->r.currentOrigin, p2 ); + } + + //z = xy*xy + VectorSubtract( p2, p1, dir ); + dir[2] = 0; + + //Get xy and z diffs + xy = VectorNormalize( dir ); + z = p1[2] - p2[2]; + + apexHeight = APEX_HEIGHT/2; + /* + //Determine most desirable apex height + apexHeight = (APEX_HEIGHT * PARA_WIDTH/xy) + (APEX_HEIGHT * z/128); + if ( apexHeight < APEX_HEIGHT * 0.5 ) + { + apexHeight = APEX_HEIGHT*0.5; + } + else if ( apexHeight > APEX_HEIGHT * 2 ) + { + apexHeight = APEX_HEIGHT*2; + } + */ + + //FIXME: length of xy will change curve of parabola, need to account for this + //somewhere... PARA_WIDTH + + z = (sqrt(apexHeight + z) - sqrt(apexHeight)); + + assert(z >= 0); + +// Com_Printf("apex is %4.2f percent from p1: ", (xy-z)*0.5/xy*100.0f); + + xy -= z; + xy *= 0.5; + + assert(xy > 0); + + VectorMA( p1, xy, dir, apex ); + apex[2] += apexHeight; + + VectorCopy(apex, NPC->pos1); + + //Now we have the apex, aim for it + height = apex[2] - NPC->r.currentOrigin[2]; + time = sqrt( height / ( .5 * NPC->client->ps.gravity ) ); + if ( !time ) + { +// Com_Printf("ERROR no time in jump\n"); + return; + } + + // set s.origin2 to the push velocity + VectorSubtract ( apex, NPC->r.currentOrigin, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 0; + dist = VectorNormalize( NPC->client->ps.velocity ); + + forward = dist / time; + VectorScale( NPC->client->ps.velocity, forward, NPC->client->ps.velocity ); + + NPC->client->ps.velocity[2] = time * NPC->client->ps.gravity; + +// Com_Printf( "%s jumping %s, gravity at %4.0f percent\n", NPC->targetname, vtos(NPC->client->ps.velocity), NPC->client->ps.gravity/8.0f ); + + NPC->flags |= FL_NO_KNOCKBACK; + NPCInfo->jumpState = JS_JUMPING; + //FIXME: jumpsound? + break; + case JS_JUMPING: + + if ( showBBoxes ) + { + VectorAdd(NPC->r.mins, NPC->pos1, p1); + VectorAdd(NPC->r.maxs, NPC->pos1, p2); + G_Cube( p1, p2, NPCDEBUG_BLUE, 0.5 ); + } + + if ( NPC->s.groundEntityNum != ENTITYNUM_NONE) + {//Landed, start landing anim + //FIXME: if the + VectorClear(NPC->client->ps.velocity); + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_LAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + NPCInfo->jumpState = JS_LANDING; + //FIXME: landsound? + } + else if ( NPC->client->ps.legsTimer > 0 ) + {//Still playing jumping anim + //FIXME: apply jump velocity here, a couple frames after start, not right away + return; + } + else + {//still in air, but done with jump anim, play inair anim + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_INAIR1, SETANIM_FLAG_OVERRIDE); + } + break; + case JS_LANDING: + if ( NPC->client->ps.legsTimer > 0 ) + {//Still playing landing anim + return; + } + else + { + NPCInfo->jumpState = JS_WAITING; + + + //task complete no matter what... + NPC_ClearGoal(); + NPCInfo->goalTime = level.time; + NPCInfo->aiFlags &= ~NPCAI_MOVING; + ucmd.forwardmove = 0; + NPC->flags &= ~FL_NO_KNOCKBACK; + //Return that the goal was reached + trap_ICARUS_TaskIDComplete( NPC, TID_MOVE_NAV ); + + //Or should we keep jumping until reached goal? + + /* + NPCInfo->goalEntity = UpdateGoal(); + if ( !NPCInfo->goalEntity ) + { + NPC->flags &= ~FL_NO_KNOCKBACK; + Q3_TaskIDComplete( NPC, TID_MOVE_NAV ); + } + */ + + } + break; + case JS_WAITING: + default: + NPCInfo->jumpState = JS_FACING; + break; + } +} + +void NPC_BSRemove (void) +{ + NPC_UpdateAngles ( qtrue, qtrue ); + if( !trap_InPVS( NPC->r.currentOrigin, g_entities[0].r.currentOrigin ) )//FIXME: use cg.vieworg? + { //rwwFIXMEFIXME: Care about all clients instead of just 0? + G_UseTargets2( NPC, NPC, NPC->target3 ); + NPC->s.eFlags |= EF_NODRAW; + NPC->s.eType = ET_INVISIBLE; + NPC->r.contents = 0; + NPC->health = 0; + NPC->targetname = NULL; + + //Disappear in half a second + NPC->think = G_FreeEntity; + NPC->nextthink = level.time + FRAMETIME; + }//FIXME: else allow for out of FOV??? +} + +void NPC_BSSearch (void) +{ + NPC_CheckEnemy(qtrue, qfalse, qtrue); + //Look for enemies, if find one: + if ( NPC->enemy ) + { + if( NPCInfo->tempBehavior == BS_SEARCH ) + {//if tempbehavior, set tempbehavior to default + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + {//if bState, change to run and shoot + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + NPC_BSRunAndShoot(); + } + return; + } + + //FIXME: what if our goalEntity is not NULL and NOT our tempGoal - they must + //want us to do something else? If tempBehavior, just default, else set + //to run and shoot...? + + //FIXME: Reimplement + + if ( !NPCInfo->investigateDebounceTime ) + {//On our way to a tempGoal + float minGoalReachedDistSquared = 32*32; + vec3_t vec; + + //Keep moving toward our tempGoal + NPCInfo->goalEntity = NPCInfo->tempGoal; + + VectorSubtract ( NPCInfo->tempGoal->r.currentOrigin, NPC->r.currentOrigin, vec); + if ( vec[2] < 24 ) + { + vec[2] = 0; + } + + if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) + { + /* + //FIXME: can't get the radius... + float wpRadSq = waypoints[NPCInfo->tempGoal->waypoint].radius * waypoints[NPCInfo->tempGoal->waypoint].radius; + if ( minGoalReachedDistSquared > wpRadSq ) + { + minGoalReachedDistSquared = wpRadSq; + } + */ + + minGoalReachedDistSquared = 32*32;//12*12; + } + + if ( VectorLengthSquared( vec ) < minGoalReachedDistSquared ) + { + //Close enough, just got there + NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); + + if ( ( NPCInfo->homeWp == WAYPOINT_NONE ) || ( NPC->waypoint == WAYPOINT_NONE ) ) + { + //Heading for or at an invalid waypoint, get out of this bState + if( NPCInfo->tempBehavior == BS_SEARCH ) + {//if tempbehavior, set tempbehavior to default + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + {//if bState, change to stand guard + NPCInfo->behaviorState = BS_STAND_GUARD; + NPC_BSRunAndShoot(); + } + return; + } + + if ( NPC->waypoint == NPCInfo->homeWp ) + { + //Just Reached our homeWp, if this is the first time, run your lostenemyscript + if ( NPCInfo->aiFlags & NPCAI_ENROUTE_TO_HOMEWP ) + { + NPCInfo->aiFlags &= ~NPCAI_ENROUTE_TO_HOMEWP; + G_ActivateBehavior( NPC, BSET_LOSTENEMY ); + } + + } + + //Com_Printf("Got there.\n"); + //Com_Printf("Looking..."); + if( !Q_irand(0, 1) ) + { + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_NORMAL); + } + else + { + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL); + } + NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); + } + else + { + NPC_MoveToGoal( qtrue ); + } + } + else + { + //We're there + if ( NPCInfo->investigateDebounceTime > level.time ) + { + //Still waiting around for a bit + //Turn angles every now and then to look around + if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) + { + if ( !Q_irand( 0, 30 ) ) + { + int numEdges = trap_Nav_GetNodeNumEdges( NPCInfo->tempGoal->waypoint ); + + if ( numEdges != WAYPOINT_NONE ) + { + int branchNum = Q_irand( 0, numEdges - 1 ); + + vec3_t branchPos, lookDir; + + int nextWp = trap_Nav_GetNodeEdge( NPCInfo->tempGoal->waypoint, branchNum ); + trap_Nav_GetNodePosition( nextWp, branchPos ); + + VectorSubtract( branchPos, NPCInfo->tempGoal->r.currentOrigin, lookDir ); + NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + flrand( -45, 45 ) ); + } + + //pick an angle +-45 degrees off of the dir of a random branch + //from NPCInfo->tempGoal->waypoint + //int branch = Q_irand( 0, (waypoints[NPCInfo->tempGoal->waypoint].numNeighbors - 1) ); + //int nextWp = waypoints[NPCInfo->tempGoal->waypoint].nextWaypoint[branch][NPC->client->moveType]; + //vec3_t lookDir; + + //VectorSubtract( waypoints[nextWp].origin, NPCInfo->tempGoal->r.currentOrigin, lookDir ); + //Look in that direction +- 45 degrees + //NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + Q_flrand( -45, 45 ) ); + } + } + //Com_Printf("."); + } + else + {//Just finished waiting + NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); + + if ( NPC->waypoint == NPCInfo->homeWp ) + { + int numEdges = trap_Nav_GetNodeNumEdges( NPCInfo->tempGoal->waypoint ); + + if ( numEdges != WAYPOINT_NONE ) + { + int branchNum = Q_irand( 0, numEdges - 1 ); + + int nextWp = trap_Nav_GetNodeEdge( NPCInfo->homeWp, branchNum ); + trap_Nav_GetNodePosition( nextWp, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = nextWp; + } + + /* + //Pick a random branch + int branch = Q_irand( 0, (waypoints[NPCInfo->homeWp].numNeighbors - 1) ); + int nextWp = waypoints[NPCInfo->homeWp].nextWaypoint[branch][NPC->client->moveType]; + + VectorCopy( waypoints[nextWp].origin, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = nextWp; + //Com_Printf("\nHeading for wp %d...\n", waypoints[NPCInfo->homeWp].nextWaypoint[branch][NPC->client->moveType]); + */ + } + else + {//At a branch, so return home + trap_Nav_GetNodePosition( NPCInfo->homeWp, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = NPCInfo->homeWp; + /* + VectorCopy( waypoints[NPCInfo->homeWp].origin, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = NPCInfo->homeWp; + //Com_Printf("\nHeading for wp %d...\n", NPCInfo->homeWp); + */ + } + + NPCInfo->investigateDebounceTime = 0; + //Start moving toward our tempGoal + NPCInfo->goalEntity = NPCInfo->tempGoal; + NPC_MoveToGoal( qtrue ); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSSearchStart +------------------------- +*/ + +void NPC_BSSearchStart( int homeWp, bState_t bState ) +{ + //FIXME: Reimplement + if ( homeWp == WAYPOINT_NONE ) + { + homeWp = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); + if( NPC->waypoint == WAYPOINT_NONE ) + { + NPC->waypoint = homeWp; + } + } + NPCInfo->homeWp = homeWp; + NPCInfo->tempBehavior = bState; + NPCInfo->aiFlags |= NPCAI_ENROUTE_TO_HOMEWP; + NPCInfo->investigateDebounceTime = 0; + trap_Nav_GetNodePosition( homeWp, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = homeWp; + //Com_Printf("\nHeading for wp %d...\n", NPCInfo->homeWp); +} + +/* +------------------------- +NPC_BSNoClip + + Use in extreme circumstances only +------------------------- +*/ + +void NPC_BSNoClip ( void ) +{ + if ( UpdateGoal() ) + { + vec3_t dir, forward, right, angles, up = {0, 0, 1}; + float fDot, rDot, uDot; + + VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir ); + + vectoangles( dir, angles ); + NPCInfo->desiredYaw = angles[YAW]; + + AngleVectors( NPC->r.currentAngles, forward, right, NULL ); + + VectorNormalize( dir ); + + fDot = DotProduct(forward, dir) * 127; + rDot = DotProduct(right, dir) * 127; + uDot = DotProduct(up, dir) * 127; + + ucmd.forwardmove = floor(fDot); + ucmd.rightmove = floor(rDot); + ucmd.upmove = floor(uDot); + } + else + { + //Cut velocity? + VectorClear( NPC->client->ps.velocity ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +void NPC_BSWander (void) +{//FIXME: don't actually go all the way to the next waypoint, just move in fits and jerks...? + if ( !NPCInfo->investigateDebounceTime ) + {//Starting out + float minGoalReachedDistSquared = 64;//32*32; + vec3_t vec; + + //Keep moving toward our tempGoal + NPCInfo->goalEntity = NPCInfo->tempGoal; + + VectorSubtract ( NPCInfo->tempGoal->r.currentOrigin, NPC->r.currentOrigin, vec); + + if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) + { + minGoalReachedDistSquared = 64; + } + + if ( VectorLengthSquared( vec ) < minGoalReachedDistSquared ) + { + //Close enough, just got there + NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); + + if( !Q_irand(0, 1) ) + { + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_NORMAL); + } + else + { + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL); + } + //Just got here, so Look around for a while + NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); + } + else + { + //Keep moving toward goal + NPC_MoveToGoal( qtrue ); + } + } + else + { + //We're there + if ( NPCInfo->investigateDebounceTime > level.time ) + { + //Still waiting around for a bit + //Turn angles every now and then to look around + if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) + { + if ( !Q_irand( 0, 30 ) ) + { + int numEdges = trap_Nav_GetNodeNumEdges( NPCInfo->tempGoal->waypoint ); + + if ( numEdges != WAYPOINT_NONE ) + { + int branchNum = Q_irand( 0, numEdges - 1 ); + + vec3_t branchPos, lookDir; + + int nextWp = trap_Nav_GetNodeEdge( NPCInfo->tempGoal->waypoint, branchNum ); + trap_Nav_GetNodePosition( nextWp, branchPos ); + + VectorSubtract( branchPos, NPCInfo->tempGoal->r.currentOrigin, lookDir ); + NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + flrand( -45, 45 ) ); + } + } + } + } + else + {//Just finished waiting + NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); + + if ( NPC->waypoint != WAYPOINT_NONE ) + { + int numEdges = trap_Nav_GetNodeNumEdges( NPC->waypoint ); + + if ( numEdges != WAYPOINT_NONE ) + { + int branchNum = Q_irand( 0, numEdges - 1 ); + + int nextWp = trap_Nav_GetNodeEdge( NPC->waypoint, branchNum ); + trap_Nav_GetNodePosition( nextWp, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = nextWp; + } + + NPCInfo->investigateDebounceTime = 0; + //Start moving toward our tempGoal + NPCInfo->goalEntity = NPCInfo->tempGoal; + NPC_MoveToGoal( qtrue ); + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +void NPC_BSFaceLeader (void) +{ + vec3_t head, leaderHead, delta, angleToLeader; + + if ( !NPC->client->leader ) + {//uh.... okay. + return; + } + + CalcEntitySpot( NPC->client->leader, SPOT_HEAD, leaderHead ); + CalcEntitySpot( NPC, SPOT_HEAD, head ); + VectorSubtract( leaderHead, head, delta ); + vectoangles( delta, angleToLeader ); + VectorNormalize( delta ); + NPC->NPC->desiredYaw = angleToLeader[YAW]; + NPC->NPC->desiredPitch = angleToLeader[PITCH]; + + NPC_UpdateAngles(qtrue, qtrue); +} +*/ +/* +------------------------- +NPC_BSFlee +------------------------- +*/ +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void WP_DropWeapon( gentity_t *dropper, vec3_t velocity ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +void NPC_Surrender( void ) +{//FIXME: say "don't shoot!" if we weren't already surrendering + if ( NPC->client->ps.weaponTime || PM_InKnockDown( &NPC->client->ps ) ) + { + return; + } + if ( NPC->s.weapon != WP_NONE && + NPC->s.weapon != WP_STUN_BATON && + NPC->s.weapon != WP_SABER ) + { + //WP_DropWeapon( NPC, NULL ); //rwwFIXMEFIXME: Do this (gonna need a system for notifying client of removal) + } + if ( NPCInfo->surrenderTime < level.time - 5000 ) + {//haven't surrendered for at least 6 seconds, tell them what you're doing + //FIXME: need real dialogue EV_SURRENDER + NPCInfo->blockedSpeechDebounceTime = 0;//make sure we say this + G_AddVoiceEvent( NPC, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 3000 ); + } +// NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); +// NPC->client->ps.torsoTimer = 1000; + NPCInfo->surrenderTime = level.time + 1000;//stay surrendered for at least 1 second + //FIXME: while surrendering, make a big sight/sound alert? Or G_AlertTeam? +} + +qboolean NPC_CheckSurrender( void ) +{ + if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) + && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE + && !NPC->client->ps.weaponTime && !PM_InKnockDown( &NPC->client->ps ) + && NPC->enemy && NPC->enemy->client && NPC->enemy->enemy == NPC && NPC->enemy->s.weapon != WP_NONE && NPC->enemy->s.weapon != WP_STUN_BATON + && NPC->enemy->health > 20 && NPC->enemy->painDebounceTime < level.time - 3000 && NPC->enemy->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] < level.time - 1000 ) + {//don't surrender if scripted to run somewhere or if we're in the air or if we're busy or if we don't have an enemy or if the enemy is not mad at me or is hurt or not a threat or busy being attacked + //FIXME: even if not in a group, don't surrender if there are other enemies in the PVS and within a certain range? + if ( NPC->s.weapon != WP_ROCKET_LAUNCHER + && NPC->s.weapon != WP_REPEATER + && NPC->s.weapon != WP_FLECHETTE + && NPC->s.weapon != WP_SABER ) + {//jedi and heavy weapons guys never surrender + //FIXME: rework all this logic into some orderly fashion!!! + if ( NPC->s.weapon != WP_NONE ) + {//they have a weapon so they'd have to drop it to surrender + //don't give up unless low on health + if ( NPC->health > 25 /*|| NPC->health >= NPC->max_health*/ ) + { //rwwFIXMEFIXME: Keep max health not a ps state? + return qfalse; + } + //if ( g_crosshairEntNum == NPC->s.number && NPC->painDebounceTime > level.time ) + if (NPC_SomeoneLookingAtMe(NPC) && NPC->painDebounceTime > level.time) + {//if he just shot me, always give up + //fall through + } + else + {//don't give up unless facing enemy and he's very close + if ( !InFOV( NPC->enemy, NPC, 60, 30 ) ) + {//I'm not looking at them + return qfalse; + } + else if ( DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) < 65536/*256*256*/ ) + {//they're not close + return qfalse; + } + else if ( !trap_InPVS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) ) + {//they're not in the same room + return qfalse; + } + } + } + //fixme: this logic keeps making npc's randomly surrender + /* + if ( NPCInfo->group && NPCInfo->group->numGroup <= 1 ) + {//I'm alone but I was in a group//FIXME: surrender anyway if just melee or no weap? + if ( NPC->s.weapon == WP_NONE + //NPC has a weapon + || (NPC->enemy && NPC->enemy->s.number < MAX_CLIENTS) + || (NPC->enemy->s.weapon == WP_SABER&&NPC->enemy->client&&!NPC->enemy->client->ps.saberHolstered) + || (NPC->enemy->NPC && NPC->enemy->NPC->group && NPC->enemy->NPC->group->numGroup > 2) ) + {//surrender only if have no weapon or fighting a player or jedi or if we are outnumbered at least 3 to 1 + if ( (NPC->enemy && NPC->enemy->s.number < MAX_CLIENTS) ) + {//player is the guy I'm running from + //if ( g_crosshairEntNum == NPC->s.number ) + if (NPC_SomeoneLookingAtMe(NPC)) + {//give up if player is aiming at me + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + else if ( NPC->enemy->s.weapon == WP_SABER ) + {//player is using saber + if ( InFOV( NPC, NPC->enemy, 60, 30 ) ) + {//they're looking at me + if ( DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) < 16384 ) + {//they're close + if ( trap_InPVS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) ) + {//they're in the same room + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + } + } + } + else if ( NPC->enemy ) + {//??? + //should NPC's surrender to others? + if ( InFOV( NPC, NPC->enemy, 30, 30 ) ) + {//they're looking at me + if ( DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) < 4096 ) + {//they're close + if ( trap_InPVS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) ) + {//they're in the same room + //FIXME: should player-team NPCs not fire on surrendered NPCs? + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + } + } + } + } + */ + } + } + return qfalse; +} + +void NPC_BSFlee( void ) +{//FIXME: keep checking for danger + gentity_t *goal; + + if ( TIMER_Done( NPC, "flee" ) && NPCInfo->tempBehavior == BS_FLEE ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + NPCInfo->squadState = SQUAD_IDLE; + //FIXME: should we set some timer to make him stay in this spot for a bit, + //so he doesn't just suddenly turn around and come back at the enemy? + //OR, just stop running toward goal for last second or so of flee? + } + if ( NPC_CheckSurrender() ) + { + return; + } + goal = NPCInfo->goalEntity; + if ( !goal ) + { + goal = NPCInfo->lastGoalEntity; + if ( !goal ) + {//???!!! + goal = NPCInfo->tempGoal; + } + } + + if ( goal ) + { + qboolean moved; + qboolean reverseCourse = qtrue; + + //FIXME: if no weapon, find one and run to pick it up? + + //Let's try to find a waypoint that gets me away from this thing + if ( NPC->waypoint == WAYPOINT_NONE ) + { + NPC->waypoint = NAV_GetNearestNode( NPC, NPC->lastWaypoint ); + } + if ( NPC->waypoint != WAYPOINT_NONE ) + { + int numEdges = trap_Nav_GetNodeNumEdges( NPC->waypoint ); + + if ( numEdges != WAYPOINT_NONE ) + { + vec3_t dangerDir; + int nextWp; + int branchNum; + + VectorSubtract( NPCInfo->investigateGoal, NPC->r.currentOrigin, dangerDir ); + VectorNormalize( dangerDir ); + + for ( branchNum = 0; branchNum < numEdges; branchNum++ ) + { + vec3_t branchPos, runDir; + + nextWp = trap_Nav_GetNodeEdge( NPC->waypoint, branchNum ); + trap_Nav_GetNodePosition( nextWp, branchPos ); + + VectorSubtract( branchPos, NPC->r.currentOrigin, runDir ); + VectorNormalize( runDir ); + if ( DotProduct( runDir, dangerDir ) > flrand( 0, 0.5 ) ) + {//don't run toward danger + continue; + } + //FIXME: don't want to ping-pong back and forth + NPC_SetMoveGoal( NPC, branchPos, 0, qtrue, -1, NULL ); + reverseCourse = qfalse; + break; + } + } + } + + moved = NPC_MoveToGoal( qfalse );//qtrue? (do try to move straight to (away from) goal) + + if ( NPC->s.weapon == WP_NONE && (moved == qfalse || reverseCourse) ) + {//No weapon and no escape route... Just cower? Need anim. + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + //If our move failed, then just run straight away from our goal + //FIXME: We really shouldn't do this. + if ( moved == qfalse ) + { + vec3_t dir; + float dist; + if ( reverseCourse ) + { + VectorSubtract( NPC->r.currentOrigin, goal->r.currentOrigin, dir ); + } + else + { + VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, dir ); + } + NPCInfo->distToGoal = dist = VectorNormalize( dir ); + NPCInfo->desiredYaw = vectoyaw( dir ); + NPCInfo->desiredPitch = 0; + ucmd.forwardmove = 127; + } + else if ( reverseCourse ) + { + //ucmd.forwardmove *= -1; + //ucmd.rightmove *= -1; + //VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + NPCInfo->desiredYaw *= -1; + } + //FIXME: can stop after a safe distance? + //ucmd.upmove = 0; + ucmd.buttons &= ~BUTTON_WALKING; + //FIXME: what do we do once we've gotten to our goal? + } + NPC_UpdateAngles( qtrue, qtrue ); + + NPC_CheckGetNewWeapon(); +} + +void NPC_StartFlee( gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ) +{ + int cp = -1; + + if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//running somewhere that a script requires us to go, don't interrupt that! + return; + } + + //if have a fleescript, run that instead + if ( G_ActivateBehavior( NPC, BSET_FLEE ) ) + { + return; + } + //FIXME: play a flee sound? Appropriate to situation? + if ( enemy ) + { + G_SetEnemy( NPC, enemy ); + } + + //FIXME: if don't have a weapon, find nearest one we have a route to and run for it? + if ( dangerLevel > AEL_DANGER || NPC->s.weapon == WP_NONE || ((!NPCInfo->group || NPCInfo->group->numGroup <= 1) && NPC->health <= 10 ) ) + {//IF either great danger OR I have no weapon OR I'm alone and low on health, THEN try to find a combat point out of PVS + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_COVER|CP_AVOID|CP_HAS_ROUTE|CP_NO_PVS, 128, -1 ); + } + //FIXME: still happens too often... + if ( cp == -1 ) + {//okay give up on the no PVS thing + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_COVER|CP_AVOID|CP_HAS_ROUTE, 128, -1 ); + if ( cp == -1 ) + {//okay give up on the avoid + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_COVER|CP_HAS_ROUTE, 128, -1 ); + if ( cp == -1 ) + {//okay give up on the cover + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_HAS_ROUTE, 128, -1 ); + } + } + } + + //see if we got a valid one + if ( cp != -1 ) + {//found a combat point + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL ); + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + {//need to just run like hell! + if ( NPC->s.weapon != WP_NONE ) + { + return;//let's just not flee? + } + else + { + //FIXME: other evasion AI? Duck? Strafe? Dodge? + NPCInfo->tempBehavior = BS_FLEE; + //Run straight away from here... FIXME: really want to find farthest waypoint/navgoal from this pos... maybe based on alert event radius? + NPC_SetMoveGoal( NPC, dangerPoint, 0, qtrue, -1, NULL ); + //store the danger point + VectorCopy( dangerPoint, NPCInfo->investigateGoal );//FIXME: make a new field for this? + } + } + //FIXME: localize this Timer? + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + //FIXME: is this always applicable? + NPCInfo->squadState = SQUAD_RETREAT; + TIMER_Set( NPC, "flee", Q_irand( fleeTimeMin, fleeTimeMax ) ); + TIMER_Set( NPC, "panic", Q_irand( 1000, 4000 ) );//how long to wait before trying to nav to a dropped weapon + + if (NPC->client->NPC_class != CLASS_PROTOCOL) + { + TIMER_Set( NPC, "duck", 0 ); + } +} + +void G_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ) +{ + if ( !self->NPC ) + {//player + return; + } + SaveNPCGlobals(); + SetNPCGlobals( self ); + + NPC_StartFlee( enemy, dangerPoint, dangerLevel, fleeTimeMin, fleeTimeMax ); + + RestoreNPCGlobals(); +} + +void NPC_BSEmplaced( void ) +{ + qboolean enemyLOS = qfalse; + qboolean enemyCS = qfalse; + qboolean faceEnemy = qfalse; + qboolean shoot = qfalse; + vec3_t impactPos; + + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse ) + { + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredYaw = NPC->s.angles[1] + Q_irand( -90, 90 ); + } + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredPitch = Q_irand( -20, 20 ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( NPC_ClearLOS4( NPC->enemy ) ) + { + int hit; + gentity_t *hitEnt; + + enemyLOS = qtrue; + + hit = NPC_ShotEntity( NPC->enemy, impactPos ); + hitEnt = &g_entities[hit]; + + if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->takedamage ) ) + {//can hit enemy or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway + enemyCS = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + } +/* + else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } +*/ + + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + if ( enemyCS ) + { + shoot = qtrue; + } + + if ( faceEnemy ) + {//face the enemy + NPC_FaceEnemy( qtrue ); + } + else + {//we want to face in the dir we're running + NPC_UpdateAngles( qtrue, qtrue ); + } + + if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + { + shoot = qfalse; + } + + if ( NPC->enemy && NPC->enemy->enemy ) + { + if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) + {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) + shoot = qfalse; + } + } + if ( shoot ) + {//try to shoot if it's time + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + } +} diff --git a/codemp/game/NPC_combat.c b/codemp/game/NPC_combat.c new file mode 100644 index 0000000..6c1ee14 --- /dev/null +++ b/codemp/game/NPC_combat.c @@ -0,0 +1,3144 @@ +//NPC_combat.cpp +#include "b_local.h" +#include "g_nav.h" + +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void G_SetEnemy( gentity_t *self, gentity_t *enemy ); +extern qboolean NPC_CheckLookTarget( gentity_t *self ); +extern void NPC_ClearLookTarget( gentity_t *self ); +extern void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy ); +extern int NAV_FindClosestWaypointForPoint2( vec3_t point ); +extern int NAV_GetNearestNode( gentity_t *self, int lastNode ); +extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum ); +extern qboolean PM_DroidMelee( int npc_class ); + +void ChangeWeapon( gentity_t *ent, int newWeapon ); + +void G_ClearEnemy (gentity_t *self) +{ + NPC_CheckLookTarget( self ); + + if ( self->enemy ) + { + if( self->client && self->client->renderInfo.lookTarget == self->enemy->s.number ) + { + NPC_ClearLookTarget( self ); + } + + if ( self->NPC && self->enemy == self->NPC->goalEntity ) + { + self->NPC->goalEntity = NULL; + } + //FIXME: set last enemy? + } + + self->enemy = NULL; +} + +/* +------------------------- +NPC_AngerAlert +------------------------- +*/ + +#define ANGER_ALERT_RADIUS 512 +#define ANGER_ALERT_SOUND_RADIUS 256 + +void G_AngerAlert( gentity_t *self ) +{ + if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) ) + {//I'm not a team playa... + return; + } + if ( !TIMER_Done( self, "interrogating" ) ) + {//I'm interrogating, don't wake everyone else up yet... FIXME: this may never wake everyone else up, though! + return; + } + //FIXME: hmm.... with all the other new alerts now, is this still neccesary or even a good idea...? + G_AlertTeam( self, self->enemy, ANGER_ALERT_RADIUS, ANGER_ALERT_SOUND_RADIUS ); +} + +/* +------------------------- +G_TeamEnemy +------------------------- +*/ + +qboolean G_TeamEnemy( gentity_t *self ) +{//FIXME: Probably a better way to do this, is a linked list of your teammates already available? + int i; + gentity_t *ent; + + if ( !self->client || self->client->playerTeam == TEAM_FREE ) + { + return qfalse; + } + if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) ) + {//I'm not a team playa... + return qfalse; + } + + for( i = 1; i < level.num_entities; i++ ) + { + ent = &g_entities[i]; + + if ( ent == self ) + { + continue; + } + + if ( ent->health <= 0 ) + { + continue; + } + + if ( !ent->client ) + { + continue; + } + + if ( ent->client->playerTeam != self->client->playerTeam ) + {//ent is not on my team + continue; + } + + if ( ent->enemy ) + {//they have an enemy + if ( !ent->enemy->client || ent->enemy->client->playerTeam != self->client->playerTeam ) + {//the ent's enemy is either a normal ent or is a player/NPC that is not on my team + return qtrue; + } + } + } + + return qfalse; +} + +void G_AttackDelay( gentity_t *self, gentity_t *enemy ) +{ + if ( enemy && self->client && self->NPC ) + {//delay their attack based on how far away they're facing from enemy + vec3_t fwd, dir; + int attDelay; + + VectorSubtract( self->client->renderInfo.eyePoint, enemy->r.currentOrigin, dir );//purposely backwards + VectorNormalize( dir ); + AngleVectors( self->client->renderInfo.eyeAngles, fwd, NULL, NULL ); + //dir[2] = fwd[2] = 0;//ignore z diff? + + attDelay = (4-g_spskill.integer)*500;//initial: from 1000ms delay on hard to 2000ms delay on easy + if ( self->client->playerTeam == NPCTEAM_PLAYER ) + {//invert + attDelay = 2000-attDelay; + } + attDelay += floor( (DotProduct( fwd, dir )+1.0f) * 2000.0f );//add up to 4000ms delay if they're facing away + + //FIXME: should distance matter, too? + + //Now modify the delay based on NPC_class, weapon, and team + //NOTE: attDelay should be somewhere between 1000 to 6000 milliseconds + switch ( self->client->NPC_class ) + { + case CLASS_IMPERIAL://they give orders and hang back + attDelay += Q_irand( 500, 1500 ); + break; + case CLASS_STORMTROOPER://stormtroopers shoot sooner + if ( self->NPC->rank >= RANK_LT ) + {//officers shoot even sooner + attDelay -= Q_irand( 500, 1500 ); + } + else + {//normal stormtroopers don't have as fast reflexes as officers + attDelay -= Q_irand( 0, 1000 ); + } + break; + case CLASS_SWAMPTROOPER://shoot very quickly? What about guys in water? + attDelay -= Q_irand( 1000, 2000 ); + break; + case CLASS_IMPWORKER://they panic, don't fire right away + attDelay += Q_irand( 1000, 2500 ); + break; + case CLASS_TRANDOSHAN: + attDelay -= Q_irand( 500, 1500 ); + break; + case CLASS_JAN: + case CLASS_LANDO: + case CLASS_PRISONER: + case CLASS_REBEL: + attDelay -= Q_irand( 500, 1500 ); + break; + case CLASS_GALAKMECH: + case CLASS_ATST: + attDelay -= Q_irand( 1000, 2000 ); + break; + case CLASS_REELO: + case CLASS_UGNAUGHT: + case CLASS_JAWA: + return; + break; + case CLASS_MINEMONSTER: + case CLASS_MURJJ: + return; + break; + case CLASS_INTERROGATOR: + case CLASS_PROBE: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_SENTRY: + return; + break; + case CLASS_REMOTE: + case CLASS_SEEKER: + return; + break; + /* + case CLASS_GRAN: + case CLASS_RODIAN: + case CLASS_WEEQUAY: + break; + case CLASS_JEDI: + case CLASS_SHADOWTROOPER: + case CLASS_TAVION: + case CLASS_REBORN: + case CLASS_LUKE: + case CLASS_DESANN: + break; + */ + } + + switch ( self->s.weapon ) + { + case WP_NONE: + case WP_SABER: + return; + break; + case WP_BRYAR_PISTOL: + break; + case WP_BLASTER: + if ( self->NPC->scriptFlags & SCF_ALT_FIRE ) + {//rapid-fire blasters + attDelay += Q_irand( 0, 500 ); + } + else + {//regular blaster + attDelay -= Q_irand( 0, 500 ); + } + break; + case WP_BOWCASTER: + attDelay += Q_irand( 0, 500 ); + break; + case WP_REPEATER: + if ( !(self->NPC->scriptFlags&SCF_ALT_FIRE) ) + {//rapid-fire blasters + attDelay += Q_irand( 0, 500 ); + } + break; + case WP_FLECHETTE: + attDelay += Q_irand( 500, 1500 ); + break; + case WP_ROCKET_LAUNCHER: + attDelay += Q_irand( 500, 1500 ); + break; +// case WP_BLASTER_PISTOL: // apparently some enemy only version of the blaster +// attDelay -= Q_irand( 500, 1500 ); +// break; + //rwwFIXMEFIXME: Have this weapon for NPCs? + case WP_DISRUPTOR://sniper's don't delay? + return; + break; + case WP_THERMAL://grenade-throwing has a built-in delay + return; + break; + case WP_STUN_BATON: // Any ol' melee attack + return; + break; + case WP_EMPLACED_GUN: + return; + break; + case WP_TURRET: // turret guns + return; + break; +// case WP_BOT_LASER: // Probe droid - Laser blast +// return; //rwwFIXMEFIXME: Have this weapon for NPCs? + break; + /* + case WP_DEMP2: + break; + case WP_TRIP_MINE: + break; + case WP_DET_PACK: + break; + case WP_STUN_BATON: + break; + case WP_ATST_MAIN: + break; + case WP_ATST_SIDE: + break; + case WP_TIE_FIGHTER: + break; + case WP_RAPID_FIRE_CONC: + break; + */ + } + + if ( self->client->playerTeam == NPCTEAM_PLAYER ) + {//clamp it + if ( attDelay > 2000 ) + { + attDelay = 2000; + } + } + + //don't shoot right away + if ( attDelay > 4000+((2-g_spskill.integer)*3000) ) + { + attDelay = 4000+((2-g_spskill.integer)*3000); + } + TIMER_Set( self, "attackDelay", attDelay );//Q_irand( 1500, 4500 ) ); + //don't move right away either + if ( attDelay > 4000 ) + { + attDelay = 4000 - Q_irand(500, 1500); + } + else + { + attDelay -= Q_irand(500, 1500); + } + + TIMER_Set( self, "roamTime", attDelay );//was Q_irand( 1000, 3500 ); + } +} + +void G_ForceSaberOn(gentity_t *ent) +{ + if (ent->client->ps.saberInFlight) + { //alright, can't turn it on now in any case, so forget it. + return; + } + + if (!ent->client->ps.saberHolstered) + { //it's already on! + return; + } + + if (ent->client->ps.weapon != WP_SABER) + { //This probably should never happen. But if it does we'll just return without complaining. + return; + } + + //Well then, turn it on. + ent->client->ps.saberHolstered = 0; + + if (ent->client->saber[0].soundOn) + { + G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOn); + } + if (ent->client->saber[1].soundOn) + { + G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOn); + } +} + + +/* +------------------------- +G_SetEnemy +------------------------- +*/ +void G_AimSet( gentity_t *self, int aim ); +void G_SetEnemy( gentity_t *self, gentity_t *enemy ) +{ + int event = 0; + + //Must be valid + if ( enemy == NULL ) + return; + + //Must be valid + if ( enemy->inuse == 0 ) + { + return; + } + + //Don't take the enemy if in notarget + if ( enemy->flags & FL_NOTARGET ) + return; + + if ( !self->NPC ) + { + self->enemy = enemy; + return; + } + + if ( self->NPC->confusionTime > level.time ) + {//can't pick up enemies if confused + return; + } + +#ifdef _DEBUG + if ( self->s.number ) + { + assert( enemy != self ); + } +#endif// _DEBUG + +// if ( enemy->client && enemy->client->playerTeam == TEAM_DISGUISE ) +// {//unmask the player +// enemy->client->playerTeam = TEAM_PLAYER; +// } + + if ( self->client && self->NPC && enemy->client && enemy->client->playerTeam == self->client->playerTeam ) + {//Probably a damn script! + if ( self->NPC->charmedTime > level.time ) + {//Probably a damn script! + return; + } + } + + if ( self->NPC && self->client && self->client->ps.weapon == WP_SABER ) + { + //when get new enemy, set a base aggression based on what that enemy is using, how far they are, etc. + NPC_Jedi_RateNewEnemy( self, enemy ); + } + + //NOTE: this is not necessarily true! + //self->NPC->enemyLastSeenTime = level.time; + + if ( self->enemy == NULL ) + { + //TEMP HACK: turn on our saber + if ( self->health > 0 ) + { + G_ForceSaberOn(self); + } + + //FIXME: Have to do this to prevent alert cascading + G_ClearEnemy( self ); + self->enemy = enemy; + + //Special case- if player is being hunted by his own people, set their enemy team correctly + if ( self->client->playerTeam == NPCTEAM_PLAYER && enemy->s.number == 0 ) + { + self->client->enemyTeam = NPCTEAM_PLAYER; + } + + //If have an anger script, run that instead of yelling + if( G_ActivateBehavior( self, BSET_ANGER ) ) + { + } + else if ( self->client && enemy->client && self->client->playerTeam != enemy->client->playerTeam ) + { + //FIXME: Use anger when entire team has no enemy. + // Basically, you're first one to notice enemies + //if ( self->forcePushTime < level.time ) // not currently being pushed + if (1) //rwwFIXMEFIXME: Set forcePushTime + { + if ( !G_TeamEnemy( self ) ) + {//team did not have an enemy previously + event = Q_irand(EV_ANGER1, EV_ANGER3); + } + } + + if ( event ) + {//yell + G_AddVoiceEvent( self, event, 2000 ); + } + } + + if ( self->s.weapon == WP_BLASTER || self->s.weapon == WP_REPEATER || + self->s.weapon == WP_THERMAL /*|| self->s.weapon == WP_BLASTER_PISTOL */ //rwwFIXMEFIXME: Blaster pistol useable by npcs? + || self->s.weapon == WP_BOWCASTER ) + {//Hmm, how about sniper and bowcaster? + //When first get mad, aim is bad + //Hmm, base on game difficulty, too? Rank? + if ( self->client->playerTeam == NPCTEAM_PLAYER ) + { + G_AimSet( self, Q_irand( self->NPC->stats.aim - (5*(g_spskill.integer)), self->NPC->stats.aim - g_spskill.integer ) ); + } + else + { + int minErr = 3; + int maxErr = 12; + if ( self->client->NPC_class == CLASS_IMPWORKER ) + { + minErr = 15; + maxErr = 30; + } + else if ( self->client->NPC_class == CLASS_STORMTROOPER && self->NPC && self->NPC->rank <= RANK_CREWMAN ) + { + minErr = 5; + maxErr = 15; + } + + G_AimSet( self, Q_irand( self->NPC->stats.aim - (maxErr*(3-g_spskill.integer)), self->NPC->stats.aim - (minErr*(3-g_spskill.integer)) ) ); + } + } + + //Alert anyone else in the area + if ( Q_stricmp( "desperado", self->NPC_type ) != 0 && Q_stricmp( "paladin", self->NPC_type ) != 0 ) + {//special holodeck enemies exception + if ( self->client->ps.fd.forceGripBeingGripped < level.time ) + {//gripped people can't call for help + G_AngerAlert( self ); + } + } + + //Stormtroopers don't fire right away! + G_AttackDelay( self, enemy ); + + //rwwFIXMEFIXME: Deal with this some other way. + /* + //FIXME: this is a disgusting hack that is supposed to make the Imperials start with their weapon holstered- need a better way + if ( self->client->ps.weapon == WP_NONE && !Q_strncmp( self->NPC_type, "imp", 3 ) && !(self->NPC->scriptFlags & SCF_FORCED_MARCH) ) + { + if ( self->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER ) ) + { + ChangeWeapon( self, WP_BLASTER ); + self->client->ps.weapon = WP_BLASTER; + self->client->ps.weaponstate = WEAPON_READY; + G_CreateG2AttachedWeaponModel( self, weaponData[WP_BLASTER].weaponMdl, self->handRBolt, 0 ); + } + else if ( self->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER_PISTOL ) ) + { + ChangeWeapon( self, WP_BLASTER_PISTOL ); + self->client->ps.weapon = WP_BLASTER_PISTOL; + self->client->ps.weaponstate = WEAPON_READY; + G_CreateG2AttachedWeaponModel( self, weaponData[WP_BLASTER_PISTOL].weaponMdl, self->handRBolt, 0 ); + } + } + */ + return; + } + + //Otherwise, just picking up another enemy + + if ( event ) + { + G_AddVoiceEvent( self, event, 2000 ); + } + + //Take the enemy + G_ClearEnemy(self); + self->enemy = enemy; +} + +/* +int ChooseBestWeapon( void ) +{ + int n; + int weapon; + + // check weapons in the NPC's weapon preference order + for ( n = 0; n < MAX_WEAPONS; n++ ) + { + weapon = NPCInfo->weaponOrder[n]; + + if ( weapon == WP_NONE ) + { + break; + } + + if ( !HaveWeapon( weapon ) ) + { + continue; + } + + if ( client->ps.ammo[weaponData[weapon].ammoIndex] ) + { + return weapon; + } + } + + // check weapons serially (mainly in case a weapon is not on the NPC's list) + for ( weapon = 1; weapon < WP_NUM_WEAPONS; weapon++ ) + { + if ( !HaveWeapon( weapon ) ) + { + continue; + } + + if ( client->ps.ammo[weaponData[weapon].ammoIndex] ) + { + return weapon; + } + } + + return client->ps.weapon; +} +*/ + +void ChangeWeapon( gentity_t *ent, int newWeapon ) +{ + if ( !ent || !ent->client || !ent->NPC ) + { + return; + } + + ent->client->ps.weapon = newWeapon; + ent->NPC->shotTime = 0; + ent->NPC->burstCount = 0; + ent->NPC->attackHold = 0; + ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[newWeapon].ammoIndex]; + + switch ( newWeapon ) + { + case WP_BRYAR_PISTOL://prifle + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attackdebounce + break; + + /* + case WP_BLASTER_PISTOL: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 1000;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + break; + */ + //rwwFIXMEFIXME: support WP_BLASTER_PISTOL and WP_BOT_LASER + + /* + case WP_BOT_LASER://probe attack + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 600;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 600;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 400;//attack debounce + else + ent->NPC->burstSpacing = 200;//attack debounce + break; + */ + + case WP_SABER: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 0;//attackdebounce + break; + + case WP_DISRUPTOR: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + switch( g_spskill.integer ) + { + case 0: + ent->NPC->burstSpacing = 2500;//attackdebounce + break; + case 1: + ent->NPC->burstSpacing = 2000;//attackdebounce + break; + case 2: + ent->NPC->burstSpacing = 1500;//attackdebounce + break; + } + } + else + { + ent->NPC->burstSpacing = 1000;//attackdebounce + } + break; + + case WP_BOWCASTER: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 1000;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + break; + + case WP_REPEATER: + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 2000;//attackdebounce + } + else + { + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 3; + ent->NPC->burstMean = 6; + ent->NPC->burstMax = 10; + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1500;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + } + break; + + case WP_DEMP2: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attackdebounce + break; + + case WP_FLECHETTE: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + ent->NPC->burstSpacing = 2000;//attackdebounce + } + else + { + ent->NPC->burstSpacing = 1000;//attackdebounce + } + break; + + case WP_ROCKET_LAUNCHER: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 2500;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 2500;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 2000;//attack debounce + else + ent->NPC->burstSpacing = 1500;//attack debounce + break; + + case WP_THERMAL: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 3000;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 3000;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 2500;//attack debounce + else + ent->NPC->burstSpacing = 2000;//attack debounce + break; + + /* + case WP_SABER: + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 5;//0.5 sec + ent->NPC->burstMean = 10;//1 second + ent->NPC->burstMax = 20;//3 seconds + ent->NPC->burstSpacing = 2000;//2 seconds + ent->NPC->attackHold = 1000;//Hold attack button for a 1-second burst + break; + + case WP_TRICORDER: + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 5; + ent->NPC->burstMean = 10; + ent->NPC->burstMax = 30; + ent->NPC->burstSpacing = 1000; + break; + */ + + case WP_BLASTER: + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 3; + ent->NPC->burstMean = 3; + ent->NPC->burstMax = 3; + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1500;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + } + else + { + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + // ent->NPC->burstSpacing = 1000;//attackdebounce + } + break; + + case WP_STUN_BATON: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attackdebounce + break; + + /* + case WP_ATST_MAIN: + case WP_ATST_SIDE: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 1000;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + break; + */ + //rwwFIXMEFIXME: support for atst weaps + + case WP_EMPLACED_GUN: + //FIXME: give some designer-control over this? + if ( ent->client && ent->client->NPC_class == CLASS_REELO ) + { + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attack debounce + // if ( g_spskill.integer == 0 ) + // ent->NPC->burstSpacing = 300;//attack debounce + // else if ( g_spskill.integer == 1 ) + // ent->NPC->burstSpacing = 200;//attack debounce + // else + // ent->NPC->burstSpacing = 100;//attack debounce + } + else + { + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 2; // 3 shots, really + ent->NPC->burstMean = 2; + ent->NPC->burstMax = 2; + + if ( ent->parent ) // if we have an owner, it should be the chair at this point...so query the chair for its shot debounce times, etc. + { + if ( g_spskill.integer == 0 ) + { + ent->NPC->burstSpacing = ent->parent->wait + 400;//attack debounce + ent->NPC->burstMin = ent->NPC->burstMax = 1; // two shots + } + else if ( g_spskill.integer == 1 ) + { + ent->NPC->burstSpacing = ent->parent->wait + 200;//attack debounce + } + else + { + ent->NPC->burstSpacing = ent->parent->wait;//attack debounce + } + } + else + { + if ( g_spskill.integer == 0 ) + { + ent->NPC->burstSpacing = 1200;//attack debounce + ent->NPC->burstMin = ent->NPC->burstMax = 1; // two shots + } + else if ( g_spskill.integer == 1 ) + { + ent->NPC->burstSpacing = 1000;//attack debounce + } + else + { + ent->NPC->burstSpacing = 800;//attack debounce + } + } + } + break; + + default: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + break; + } +} + +void NPC_ChangeWeapon( int newWeapon ) +{ + /* + qboolean changing = qfalse; + if ( newWeapon != NPC->client->ps.weapon ) + { + changing = qtrue; + } + if ( changing && NPC->weaponModel[0] > 9 ) + { + trap_G2API_RemoveGhoul2Model( NPC->ghoul2, NPC->weaponModel[0] ); + } + ChangeWeapon( NPC, newWeapon ); + if ( changing && NPC->client->ps.weapon != WP_NONE ) + { + if ( NPC->client->ps.weapon == WP_SABER ) + { + G_CreateG2AttachedWeaponModel( NPC, NPC->client->ps.saber[0].model, NPC->handRBolt, 0 ); + if ( NPC->client->ps.dualSabers ) + { + G_CreateG2AttachedWeaponModel( NPC, NPC->client->ps.saber[1].model, NPC->handLBolt, 0 ); + } + } + else + { + G_CreateG2AttachedWeaponModel( NPC, weaponData[NPC->client->ps.weapon].weaponMdl, NPC->handRBolt, 0 ); + } + }*/ + //rwwFIXMEFIXME: Change the same way as players, all this stuff is just crazy. +} +/* +void NPC_ApplyWeaponFireDelay(void) +How long, if at all, in msec the actual fire should delay from the time the attack was started +*/ +void NPC_ApplyWeaponFireDelay(void) +{ + if ( NPC->attackDebounceTime > level.time ) + {//Just fired, if attacking again, must be a burst fire, so don't add delay + //NOTE: Borg AI uses attackDebounceTime "incorrectly", so this will always return for them! + return; + } + + switch(client->ps.weapon) + { + /* + case WP_BOT_LASER: + NPCInfo->burstCount = 0; + client->ps.weaponTime = 500; + break; + */ //rwwFIXMEFIXME: support for this + + case WP_THERMAL: + if ( client->ps.clientNum ) + {//NPCs delay... + //FIXME: player should, too, but would feel weird in 1st person, even though it + // would look right in 3rd person. Really should have a wind-up anim + // for player as he holds down the fire button to throw, then play + // the actual throw when he lets go... + client->ps.weaponTime = 700; + } + break; + + case WP_STUN_BATON: + //if ( !PM_DroidMelee( client->NPC_class ) ) + if (1) //rwwFIXMEFIXME: ... + {//FIXME: should be unique per melee anim + client->ps.weaponTime = 300; + } + break; + + default: + client->ps.weaponTime = 0; + break; + } +} + +/* +------------------------- +ShootThink +------------------------- +*/ +void ShootThink( void ) +{ + int delay; + + ucmd.buttons &= ~BUTTON_ATTACK; +/* + if ( enemyVisibility != VIS_SHOOT) + return; +*/ + + if ( client->ps.weapon == WP_NONE ) + return; + + if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING && client->ps.weaponstate != WEAPON_IDLE) + return; + + if ( level.time < NPCInfo->shotTime ) + { + return; + } + + ucmd.buttons |= BUTTON_ATTACK; + + NPCInfo->currentAmmo = client->ps.ammo[weaponData[client->ps.weapon].ammoIndex]; // checkme + + NPC_ApplyWeaponFireDelay(); + + if ( NPCInfo->aiFlags & NPCAI_BURST_WEAPON ) + { + if ( !NPCInfo->burstCount ) + { + NPCInfo->burstCount = Q_irand( NPCInfo->burstMin, NPCInfo->burstMax ); + /* + NPCInfo->burstCount = erandom( NPCInfo->burstMean ); + if ( NPCInfo->burstCount < NPCInfo->burstMin ) + { + NPCInfo->burstCount = NPCInfo->burstMin; + } + else if ( NPCInfo->burstCount > NPCInfo->burstMax ) + { + NPCInfo->burstCount = NPCInfo->burstMax; + } + */ + delay = 0; + } + else + { + NPCInfo->burstCount--; + if ( NPCInfo->burstCount == 0 ) + { + delay = NPCInfo->burstSpacing; + } + else + { + delay = 0; + } + } + + if ( !delay ) + { + // HACK: dirty little emplaced bits, but is done because it would otherwise require some sort of new variable... + if ( client->ps.weapon == WP_EMPLACED_GUN ) + { + if ( NPC->parent ) // try and get the debounce values from the chair if we can + { + if ( g_spskill.integer == 0 ) + { + delay = NPC->parent->random + 150; + } + else if ( g_spskill.integer == 1 ) + { + delay = NPC->parent->random + 100; + } + else + { + delay = NPC->parent->random; + } + } + else + { + if ( g_spskill.integer == 0 ) + { + delay = 350; + } + else if ( g_spskill.integer == 1 ) + { + delay = 300; + } + else + { + delay = 200; + } + } + } + } + } + else + { + delay = NPCInfo->burstSpacing; + } + + NPCInfo->shotTime = level.time + delay; + NPC->attackDebounceTime = level.time + NPC_AttackDebounceForWeapon(); +} + +/* +static void WeaponThink( qboolean inCombat ) +FIXME makes this so there's a delay from event that caused us to check to actually doing it + +Added: hacks for Borg +*/ +void WeaponThink( qboolean inCombat ) +{ + if ( client->ps.weaponstate == WEAPON_RAISING || client->ps.weaponstate == WEAPON_DROPPING ) + { + ucmd.weapon = client->ps.weapon; + ucmd.buttons &= ~BUTTON_ATTACK; + return; + } + +//MCG - Begin + //For now, no-one runs out of ammo + if(NPC->client->ps.ammo[ weaponData[client->ps.weapon].ammoIndex ] < 10) // checkme +// if(NPC->client->ps.ammo[ client->ps.weapon ] < 10) + { + Add_Ammo (NPC, client->ps.weapon, 100); + } + + /*if ( NPC->playerTeam == TEAM_BORG ) + {//HACK!!! + if(!(NPC->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BORG_WEAPON ))) + NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BORG_WEAPON ); + + if ( client->ps.weapon != WP_BORG_WEAPON ) + { + NPC_ChangeWeapon( WP_BORG_WEAPON ); + Add_Ammo (NPC, client->ps.weapon, 10); + NPCInfo->currentAmmo = client->ps.ammo[client->ps.weapon]; + } + } + else */ + + /*if ( NPC->client->playerTeam == TEAM_SCAVENGERS ) + {//HACK!!! + if(!(NPC->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER ))) + NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BLASTER ); + + if ( client->ps.weapon != WP_BLASTER ) + + { + NPC_ChangeWeapon( WP_BLASTER ); + Add_Ammo (NPC, client->ps.weapon, 10); +// NPCInfo->currentAmmo = client->ps.ammo[client->ps.weapon]; + NPCInfo->currentAmmo = client->ps.ammo[weaponData[client->ps.weapon].ammoIndex]; // checkme + } + } + else*/ +//MCG - End + { + // if the gun in our hands is out of ammo, we need to change + /*if ( client->ps.ammo[client->ps.weapon] == 0 ) + { + NPCInfo->aiFlags |= NPCAI_CHECK_WEAPON; + } + + if ( NPCInfo->aiFlags & NPCAI_CHECK_WEAPON ) + { + NPCInfo->aiFlags &= ~NPCAI_CHECK_WEAPON; + bestWeapon = ChooseBestWeapon(); + if ( bestWeapon != client->ps.weapon ) + { + NPC_ChangeWeapon( bestWeapon ); + } + }*/ + } + + ucmd.weapon = client->ps.weapon; + ShootThink(); +} + +/* +HaveWeapon +*/ + +qboolean HaveWeapon( int weapon ) +{ + return ( client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) ); +} + +qboolean EntIsGlass (gentity_t *check) +{ + if(check->classname && + !Q_stricmp("func_breakable", check->classname) && + check->count == 1 && check->health <= 100) + { + return qtrue; + } + + return qfalse; +} + +qboolean ShotThroughGlass (trace_t *tr, gentity_t *target, vec3_t spot, int mask) +{ + gentity_t *hit = &g_entities[ tr->entityNum ]; + if(hit != target && EntIsGlass(hit)) + {//ok to shoot through breakable glass + int skip = hit->s.number; + vec3_t muzzle; + + VectorCopy(tr->endpos, muzzle); + trap_Trace (tr, muzzle, NULL, NULL, spot, skip, mask ); + return qtrue; + } + + return qfalse; +} + +/* +CanShoot +determine if NPC can directly target enemy + +this function does not check teams, invulnerability, notarget, etc.... + +Added: If can't shoot center, try head, if not, see if it's close enough to try anyway. +*/ +qboolean CanShoot ( gentity_t *ent, gentity_t *shooter ) +{ + trace_t tr; + vec3_t muzzle; + vec3_t spot, diff; + gentity_t *traceEnt; + + CalcEntitySpot( shooter, SPOT_WEAPON, muzzle ); + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); //FIXME preferred target locations for some weapons (feet for R/L) + + trap_Trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT ); + traceEnt = &g_entities[ tr.entityNum ]; + + // point blank, baby! + if (tr.startsolid && (shooter->NPC) && (shooter->NPC->touchedByPlayer) ) + { + traceEnt = shooter->NPC->touchedByPlayer; + } + + if ( ShotThroughGlass( &tr, ent, spot, MASK_SHOT ) ) + { + traceEnt = &g_entities[ tr.entityNum ]; + } + + // shot is dead on + if ( traceEnt == ent ) + { + return qtrue; + } +//MCG - Begin + else + {//ok, can't hit them in center, try their head + CalcEntitySpot( ent, SPOT_HEAD, spot ); + trap_Trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT ); + traceEnt = &g_entities[ tr.entityNum ]; + if ( traceEnt == ent) + { + return qtrue; + } + } + + //Actually, we should just check to fire in dir we're facing and if it's close enough, + //and we didn't hit someone on our own team, shoot + VectorSubtract(spot, tr.endpos, diff); + if(VectorLength(diff) < random() * 32) + { + return qtrue; + } +//MCG - End + // shot would hit a non-client + if ( !traceEnt->client ) + { + return qfalse; + } + + // shot is blocked by another player + + // he's already dead, so go ahead + if ( traceEnt->health <= 0 ) + { + return qtrue; + } + + // don't deliberately shoot a teammate + if ( traceEnt->client && ( traceEnt->client->playerTeam == shooter->client->playerTeam ) ) + { + return qfalse; + } + + // he's just in the wrong place, go ahead + return qtrue; +} + + +/* +void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ) + +Added: hacks for scripted NPCs +*/ +void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ) +{ + // is he is already our enemy? + if ( other == NPC->enemy ) + return; + + if ( other->flags & FL_NOTARGET ) + return; + + // we already have an enemy and this guy is in our FOV, see if this guy would be better + if ( NPC->enemy && vis == VIS_FOV ) + { + if ( NPCInfo->enemyLastSeenTime - level.time < 2000 ) + { + return; + } + if ( enemyVisibility == VIS_UNKNOWN ) + { + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV ); + } + if ( enemyVisibility == VIS_FOV ) + { + return; + } + } + + if ( !NPC->enemy ) + {//only take an enemy if you don't have one yet + G_SetEnemy( NPC, other ); + } + + if ( vis == VIS_FOV ) + { + NPCInfo->enemyLastSeenTime = level.time; + VectorCopy( other->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + NPCInfo->enemyLastHeardTime = 0; + VectorClear( NPCInfo->enemyLastHeardLocation ); + } + else + { + NPCInfo->enemyLastSeenTime = 0; + VectorClear( NPCInfo->enemyLastSeenLocation ); + NPCInfo->enemyLastHeardTime = level.time; + VectorCopy( other->r.currentOrigin, NPCInfo->enemyLastHeardLocation ); + } +} + + +//========================================== +//MCG Added functions: +//========================================== + +/* +int NPC_AttackDebounceForWeapon (void) + +DOES NOT control how fast you can fire +Only makes you keep your weapon up after you fire + +*/ +int NPC_AttackDebounceForWeapon (void) +{ + switch ( NPC->client->ps.weapon ) + { +/* + case WP_BLASTER://scav rifle + return 1000; + break; + + case WP_BRYAR_PISTOL://prifle + return 3000; + break; + + case WP_SABER: + return 100; + break; + + + case WP_TRICORDER: + return 0;//tricorder + break; +*/ + case WP_SABER: + return 0; + break; + + /* + case WP_BOT_LASER: + + if ( g_spskill.integer == 0 ) + return 2000; + + if ( g_spskill.integer == 1 ) + return 1500; + + return 1000; + break; + */ + //rwwFIXMEFIXME: support + default: + return NPCInfo->burstSpacing;//was 100 by default + break; + } +} + +//FIXME: need a mindist for explosive weapons +float NPC_MaxDistSquaredForWeapon (void) +{ + if(NPCInfo->stats.shootDistance > 0) + {//overrides default weapon dist + return NPCInfo->stats.shootDistance * NPCInfo->stats.shootDistance; + } + + switch ( NPC->s.weapon ) + { + case WP_BLASTER://scav rifle + return 1024 * 1024;//should be shorter? + break; + + case WP_BRYAR_PISTOL://prifle + return 1024 * 1024; + break; + + /* + case WP_BLASTER_PISTOL://prifle + return 1024 * 1024; + break; + */ + + case WP_DISRUPTOR://disruptor + if ( NPCInfo->scriptFlags & SCF_ALT_FIRE ) + { + return ( 4096 * 4096 ); + } + else + { + return 1024 * 1024; + } + break; +/* + case WP_SABER: + return 1024 * 1024; + break; + + + case WP_TRICORDER: + return 0;//tricorder + break; +*/ + case WP_SABER: + if ( NPC->client && NPC->client->saber[0].blade[0].lengthMax ) + {//FIXME: account for whether enemy and I are heading towards each other! + return (NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5)*(NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5); + } + else + { + return 48*48; + } + break; + + default: + return 1024 * 1024;//was 0 + break; + } +} + +/* +------------------------- +ValidEnemy +------------------------- +*/ + +qboolean ValidEnemy(gentity_t *ent) +{ + if ( ent == NULL ) + return qfalse; + + if ( ent == NPC ) + return qfalse; + + //if team_free, maybe everyone is an enemy? + //if ( !NPC->client->enemyTeam ) + // return qfalse; + + if ( !(ent->flags & FL_NOTARGET) ) + { + if( ent->health > 0 ) + { + if( !ent->client ) + { + return qtrue; + } + else if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + {//don't go after spectators + return qfalse; + } + else + { + int entTeam = TEAM_FREE; + if ( ent->NPC && ent->client ) + { + entTeam = ent->client->playerTeam; + } + else if ( ent->client ) + { + if ( ent->client->sess.sessionTeam == TEAM_BLUE ) + { + entTeam = NPCTEAM_PLAYER; + } + else if ( ent->client->sess.sessionTeam == TEAM_RED ) + { + entTeam = NPCTEAM_ENEMY; + } + else + { + entTeam = NPCTEAM_NEUTRAL; + } + } + if( entTeam == NPCTEAM_FREE + || NPC->client->enemyTeam == NPCTEAM_FREE + || entTeam == NPC->client->enemyTeam ) + { + if ( entTeam != NPC->client->playerTeam ) + { + return qtrue; + } + } + } + } + } + + return qfalse; +} + +qboolean NPC_EnemyTooFar(gentity_t *enemy, float dist, qboolean toShoot) +{ + vec3_t vec; + + + if ( !toShoot ) + {//Not trying to actually press fire button with this check + if ( NPC->client->ps.weapon == WP_SABER ) + {//Just have to get to him + return qfalse; + } + } + + + if(!dist) + { + VectorSubtract(NPC->r.currentOrigin, enemy->r.currentOrigin, vec); + dist = VectorLengthSquared(vec); + } + + if(dist > NPC_MaxDistSquaredForWeapon()) + return qtrue; + + return qfalse; +} + +/* +NPC_PickEnemy + +Randomly picks a living enemy from the specified team and returns it + +FIXME: For now, you MUST specify an enemy team + +If you specify choose closest, it will find only the closest enemy + +If you specify checkVis, it will return and enemy that is visible + +If you specify findPlayersFirst, it will try to find players first + +You can mix and match any of those options (example: find closest visible players first) + +FIXME: this should go through the snapshot and find the closest enemy +*/ +gentity_t *NPC_PickEnemy( gentity_t *closestTo, int enemyTeam, qboolean checkVis, qboolean findPlayersFirst, qboolean findClosest ) +{ + int num_choices = 0; + int choice[128];//FIXME: need a different way to determine how many choices? + gentity_t *newenemy = NULL; + gentity_t *closestEnemy = NULL; + int entNum; + vec3_t diff; + float relDist; + float bestDist = Q3_INFINITE; + qboolean failed = qfalse; + int visChecks = (CHECK_360|CHECK_FOV|CHECK_VISRANGE); + int minVis = VIS_FOV; + + if ( enemyTeam == NPCTEAM_NEUTRAL ) + { + return NULL; + } + + if ( NPCInfo->behaviorState == BS_STAND_AND_SHOOT || + NPCInfo->behaviorState == BS_HUNT_AND_KILL ) + {//Formations guys don't require inFov to pick up a target + //These other behavior states are active battle states and should not + //use FOV. FOV checks are for enemies who are patrolling, guarding, etc. + visChecks &= ~CHECK_FOV; + minVis = VIS_360; + } + + if( findPlayersFirst ) + {//try to find a player first + newenemy = &g_entities[0]; + if( newenemy->client && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW)) + { + if( newenemy->health > 0 ) + { + if( NPC_ValidEnemy( newenemy) )//enemyTeam == TEAM_PLAYER || newenemy->client->playerTeam == enemyTeam || ( enemyTeam == TEAM_PLAYER ) ) + {//FIXME: check for range and FOV or vis? + if( newenemy != NPC->lastEnemy ) + {//Make sure we're not just going back and forth here + if ( trap_InPVS(newenemy->r.currentOrigin, NPC->r.currentOrigin) ) + { + if(NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL) + { + if(!NPC->enemy) + { + if(!InVisrange(newenemy)) + { + failed = qtrue; + } + else if(NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV) + { + failed = qtrue; + } + } + } + + if ( !failed ) + { + VectorSubtract( closestTo->r.currentOrigin, newenemy->r.currentOrigin, diff ); + relDist = VectorLengthSquared(diff); + if ( newenemy->client->hiddenDist > 0 ) + { + if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist ) + { + //out of hidden range + if ( VectorLengthSquared( newenemy->client->hiddenDir ) ) + {//They're only hidden from a certain direction, check + float dot; + VectorNormalize( diff ); + dot = DotProduct( newenemy->client->hiddenDir, diff ); + if ( dot > 0.5 ) + {//I'm not looking in the right dir toward them to see them + failed = qtrue; + } + else + { + Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot ); + } + } + else + { + failed = qtrue; + } + } + else + { + Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist ); + } + } + + if(!failed) + { + if(findClosest) + { + if(relDist < bestDist) + { + if(!NPC_EnemyTooFar(newenemy, relDist, qfalse)) + { + if(checkVis) + { + if( NPC_CheckVisibility ( newenemy, visChecks ) == minVis ) + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + else + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + } + } + else if(!NPC_EnemyTooFar(newenemy, 0, qfalse)) + { + if(checkVis) + { + if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV ) + { + choice[num_choices++] = newenemy->s.number; + } + } + else + { + choice[num_choices++] = newenemy->s.number; + } + } + } + } + } + } + } + } + } + } + + if (findClosest && closestEnemy) + { + return closestEnemy; + } + + if (num_choices) + { + return &g_entities[ choice[rand() % num_choices] ]; + } + + /* + //FIXME: used to have an option to look *only* for the player... now...? Still need it? + if ( enemyTeam == TEAM_PLAYER ) + {//couldn't find the player + return NULL; + } + */ + + num_choices = 0; + bestDist = Q3_INFINITE; + closestEnemy = NULL; + + for ( entNum = 0; entNum < level.num_entities; entNum++ ) + { + newenemy = &g_entities[entNum]; + + if ( newenemy != NPC && (newenemy->client /*|| newenemy->svFlags & SVF_NONNPC_ENEMY*/) && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW)) + { + if ( newenemy->health > 0 ) + { + if ( (newenemy->client && NPC_ValidEnemy( newenemy)) + || (!newenemy->client && newenemy->alliedTeam == enemyTeam) ) + {//FIXME: check for range and FOV or vis? + if ( NPC->client->playerTeam == NPCTEAM_PLAYER && enemyTeam == NPCTEAM_PLAYER ) + {//player allies turning on ourselves? How? + if ( newenemy->s.number ) + {//only turn on the player, not other player allies + continue; + } + } + + if ( newenemy != NPC->lastEnemy ) + {//Make sure we're not just going back and forth here + if(!trap_InPVS(newenemy->r.currentOrigin, NPC->r.currentOrigin)) + { + continue; + } + + if ( NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL ) + { + if ( !NPC->enemy ) + { + if ( !InVisrange( newenemy ) ) + { + continue; + } + else if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV ) + { + continue; + } + } + } + + VectorSubtract( closestTo->r.currentOrigin, newenemy->r.currentOrigin, diff ); + relDist = VectorLengthSquared(diff); + if ( newenemy->client && newenemy->client->hiddenDist > 0 ) + { + if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist ) + { + //out of hidden range + if ( VectorLengthSquared( newenemy->client->hiddenDir ) ) + {//They're only hidden from a certain direction, check + float dot; + + VectorNormalize( diff ); + dot = DotProduct( newenemy->client->hiddenDir, diff ); + if ( dot > 0.5 ) + {//I'm not looking in the right dir toward them to see them + continue; + } + else + { + Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot ); + } + } + else + { + continue; + } + } + else + { + Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist ); + } + } + + if ( findClosest ) + { + if ( relDist < bestDist ) + { + if ( !NPC_EnemyTooFar( newenemy, relDist, qfalse ) ) + { + if ( checkVis ) + { + //FIXME: NPCs need to be able to pick up other NPCs behind them, + //but for now, commented out because it was picking up enemies it shouldn't + //if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 ) + if ( NPC_CheckVisibility ( newenemy, visChecks ) == minVis ) + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + else + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + } + } + else if ( !NPC_EnemyTooFar( newenemy, 0, qfalse ) ) + { + if ( checkVis ) + { + //if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV ) + if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 ) + { + choice[num_choices++] = newenemy->s.number; + } + } + else + { + choice[num_choices++] = newenemy->s.number; + } + } + } + } + } + } + } + + + if (findClosest) + {//FIXME: you can pick up an enemy around a corner this way. + return closestEnemy; + } + + if (!num_choices) + { + return NULL; + } + + return &g_entities[ choice[rand() % num_choices] ]; +} + +/* +gentity_t *NPC_PickAlly ( void ) + + Simply returns closest visible ally +*/ + +gentity_t *NPC_PickAlly ( qboolean facingEachOther, float range, qboolean ignoreGroup, qboolean movingOnly ) +{ + gentity_t *ally = NULL; + gentity_t *closestAlly = NULL; + int entNum; + vec3_t diff; + float relDist; + float bestDist = range; + + for ( entNum = 0; entNum < level.num_entities; entNum++ ) + { + ally = &g_entities[entNum]; + + if ( ally->client ) + { + if ( ally->health > 0 ) + { + if ( ally->client && ( ally->client->playerTeam == NPC->client->playerTeam || + NPC->client->playerTeam == NPCTEAM_ENEMY ) )// && ally->client->playerTeam == TEAM_DISGUISE ) ) ) + {//if on same team or if player is disguised as your team + if ( ignoreGroup ) + { + if ( ally == NPC->client->leader ) + { + //reject + continue; + } + if ( ally->client && ally->client->leader && ally->client->leader == NPC ) + { + //reject + continue; + } + } + + if(!trap_InPVS(ally->r.currentOrigin, NPC->r.currentOrigin)) + { + continue; + } + + if ( movingOnly && ally->client && NPC->client ) + {//They have to be moving relative to each other + if ( !DistanceSquared( ally->client->ps.velocity, NPC->client->ps.velocity ) ) + { + continue; + } + } + + VectorSubtract( NPC->r.currentOrigin, ally->r.currentOrigin, diff ); + relDist = VectorNormalize( diff ); + if ( relDist < bestDist ) + { + if ( facingEachOther ) + { + vec3_t vf; + float dot; + + AngleVectors( ally->client->ps.viewangles, vf, NULL, NULL ); + VectorNormalize(vf); + dot = DotProduct(diff, vf); + + if ( dot < 0.5 ) + {//Not facing in dir to me + continue; + } + //He's facing me, am I facing him? + AngleVectors( NPC->client->ps.viewangles, vf, NULL, NULL ); + VectorNormalize(vf); + dot = DotProduct(diff, vf); + + if ( dot > -0.5 ) + {//I'm not facing opposite of dir to me + continue; + } + //I am facing him + } + + if ( NPC_CheckVisibility ( ally, CHECK_360|CHECK_VISRANGE ) >= VIS_360 ) + { + bestDist = relDist; + closestAlly = ally; + } + } + } + } + } + } + + + return closestAlly; +} + +gentity_t *NPC_CheckEnemy( qboolean findNew, qboolean tooFarOk, qboolean setEnemy ) +{ + qboolean forcefindNew = qfalse; + gentity_t *closestTo; + gentity_t *newEnemy = NULL; + //FIXME: have a "NPCInfo->persistance" we can set to determine how long to try to shoot + //someone we can't hit? Rather than hard-coded 10? + + //FIXME they shouldn't recognize enemy's death instantly + + //TEMP FIX: + //if(NPC->enemy->client) + //{ + // NPC->enemy->health = NPC->enemy->client->ps.stats[STAT_HEALTH]; + //} + + if ( NPC->enemy ) + { + if ( !NPC->enemy->inuse )//|| NPC->enemy == NPC )//wtf? NPCs should never get mad at themselves! + { + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + } + } + + //if ( NPC->svFlags & SVF_IGNORE_ENEMIES ) + if (0) //rwwFIXMEFIXME: support for this flag + {//We're ignoring all enemies for now + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + return NULL; + } + + //rwwFIXMEFIXME: support for this flag + /* + if ( NPC->svFlags & SVF_LOCKEDENEMY ) + {//keep this enemy until dead + if ( NPC->enemy ) + { + if ( (!NPC->NPC && !(NPC->svFlags & SVF_NONNPC_ENEMY) ) || NPC->enemy->health > 0 ) + {//Enemy never had health (a train or info_not_null, etc) or did and is now dead (NPCs, turrets, etc) + return NULL; + } + } + NPC->svFlags &= ~SVF_LOCKEDENEMY; + } + */ + + if ( NPC->enemy ) + { + if ( NPC_EnemyTooFar(NPC->enemy, 0, qfalse) ) + { + if(findNew) + {//See if there is a close one and take it if so, else keep this one + forcefindNew = qtrue; + } + else if(!tooFarOk)//FIXME: don't need this extra bool any more + { + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + } + } + else if ( !trap_InPVS(NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) ) + {//FIXME: should this be a line-of site check? + //FIXME: a lot of things check PVS AGAIN when deciding whether + //or not to shoot, redundant! + //Should we lose the enemy? + //FIXME: if lose enemy, run lostenemyscript + if ( NPC->enemy->client && NPC->enemy->client->hiddenDist ) + {//He ducked into shadow while we weren't looking + //Drop enemy and see if we should search for him + NPC_LostEnemyDecideChase(); + } + else + {//If we're not chasing him, we need to lose him + //NOTE: since we no longer have bStates, really, this logic doesn't work, so never give him up + + /* + switch( NPCInfo->behaviorState ) + { + case BS_HUNT_AND_KILL: + //Okay to lose PVS, we're chasing them + break; + case BS_RUN_AND_SHOOT: + //FIXME: only do this if !(NPCInfo->scriptFlags&SCF_CHASE_ENEMY) + //If he's not our goalEntity, we're running somewhere else, so lose him + if ( NPC->enemy != NPCInfo->goalEntity ) + { + G_ClearEnemy( NPC ); + } + break; + default: + //We're not chasing him, so lose him as an enemy + G_ClearEnemy( NPC ); + break; + } + */ + } + } + } + + if ( NPC->enemy ) + { + if ( NPC->enemy->health <= 0 || NPC->enemy->flags & FL_NOTARGET ) + { + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + } + } + + closestTo = NPC; + //FIXME: check your defendEnt, if you have one, see if their enemy is different + //than yours, or, if they don't have one, pick the closest enemy to THEM? + if ( NPCInfo->defendEnt ) + {//Trying to protect someone + if ( NPCInfo->defendEnt->health > 0 ) + {//Still alive, We presume we're close to them, navigation should handle this? + if ( NPCInfo->defendEnt->enemy ) + {//They were shot or acquired an enemy + if ( NPC->enemy != NPCInfo->defendEnt->enemy ) + {//They have a different enemy, take it! + newEnemy = NPCInfo->defendEnt->enemy; + if ( setEnemy ) + { + G_SetEnemy( NPC, NPCInfo->defendEnt->enemy ); + } + } + } + else if ( NPC->enemy == NULL ) + {//We don't have an enemy, so find closest to defendEnt + closestTo = NPCInfo->defendEnt; + } + } + } + + if (!NPC->enemy || ( NPC->enemy && NPC->enemy->health <= 0 ) || forcefindNew ) + {//FIXME: NPCs that are moving after an enemy should ignore the can't hit enemy counter- that should only be for NPCs that are standing still + //NOTE: cantHitEnemyCounter >= 100 means we couldn't hit enemy for a full + // 10 seconds, so give up. This means even if we're chasing him, we would + // try to find another enemy after 10 seconds (assuming the cantHitEnemyCounter + // is allowed to increment in a chasing bState) + qboolean foundenemy = qfalse; + + if(!findNew) + { + if ( setEnemy ) + { + NPC->lastEnemy = NPC->enemy; + G_ClearEnemy(NPC); + } + return NULL; + } + + //If enemy dead or unshootable, look for others on out enemy's team + if ( NPC->client->enemyTeam != NPCTEAM_NEUTRAL ) + { + //NOTE: this only checks vis if can't hit enemy for 10 tries, which I suppose + // means they need to find one that in more than just PVS + //newenemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, (NPC->cantHitEnemyCounter > 10), qfalse, qtrue );//3rd parm was (NPC->enemyTeam == TEAM_STARFLEET) + //For now, made it so you ALWAYS have to check VIS + newEnemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, qtrue, qfalse, qtrue );//3rd parm was (NPC->enemyTeam == TEAM_STARFLEET) + if ( newEnemy ) + { + foundenemy = qtrue; + if ( setEnemy ) + { + G_SetEnemy( NPC, newEnemy ); + } + } + } + + if ( !forcefindNew ) + { + if ( !foundenemy ) + { + if ( setEnemy ) + { + NPC->lastEnemy = NPC->enemy; + G_ClearEnemy(NPC); + } + } + + NPC->cantHitEnemyCounter = 0; + } + //FIXME: if we can't find any at all, go into INdependant NPC AI, pursue and kill + } + + if ( NPC->enemy && NPC->enemy->client ) + { + if(NPC->enemy->client->playerTeam) + { +// assert( NPC->client->playerTeam != NPC->enemy->client->playerTeam); + if( NPC->client->playerTeam != NPC->enemy->client->playerTeam ) + { + NPC->client->enemyTeam = NPC->enemy->client->playerTeam; + } + } + } + return newEnemy; +} + +/* +------------------------- +NPC_ClearShot +------------------------- +*/ + +qboolean NPC_ClearShot( gentity_t *ent ) +{ + vec3_t muzzle; + trace_t tr; + + if ( ( NPC == NULL ) || ( ent == NULL ) ) + return qfalse; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + // add aim error + // use weapon instead of specific npc types, although you could add certain npc classes if you wanted +// if ( NPC->client->playerTeam == TEAM_SCAVENGERS ) + if( NPC->s.weapon == WP_BLASTER /*|| NPC->s.weapon == WP_BLASTER_PISTOL*/ ) // any other guns to check for? + { + vec3_t mins = { -2, -2, -2 }; + vec3_t maxs = { 2, 2, 2 }; + + trap_Trace ( &tr, muzzle, mins, maxs, ent->r.currentOrigin, NPC->s.number, MASK_SHOT ); + } + else + { + trap_Trace ( &tr, muzzle, NULL, NULL, ent->r.currentOrigin, NPC->s.number, MASK_SHOT ); + } + + if ( tr.startsolid || tr.allsolid ) + { + return qfalse; + } + + if ( tr.entityNum == ent->s.number ) + return qtrue; + + return qfalse; +} + +/* +------------------------- +NPC_ShotEntity +------------------------- +*/ + +int NPC_ShotEntity( gentity_t *ent, vec3_t impactPos ) +{ + vec3_t muzzle; + vec3_t targ; + trace_t tr; + + if ( ( NPC == NULL ) || ( ent == NULL ) ) + return qfalse; + + if ( NPC->s.weapon == WP_THERMAL ) + {//thermal aims from slightly above head + //FIXME: what about low-angle shots, rolling the thermal under something? + vec3_t angles, forward, end; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + VectorSet( angles, 0, NPC->client->ps.viewangles[1], 0 ); + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( muzzle, 8, forward, end ); + end[2] += 24; + trap_Trace ( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + VectorCopy( tr.endpos, muzzle ); + } + else + { + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + } + CalcEntitySpot( ent, SPOT_CHEST, targ ); + + // add aim error + // use weapon instead of specific npc types, although you could add certain npc classes if you wanted +// if ( NPC->client->playerTeam == TEAM_SCAVENGERS ) + if( NPC->s.weapon == WP_BLASTER /*|| NPC->s.weapon == WP_BLASTER_PISTOL*/ ) // any other guns to check for? + { + vec3_t mins = { -2, -2, -2 }; + vec3_t maxs = { 2, 2, 2 }; + + trap_Trace ( &tr, muzzle, mins, maxs, targ, NPC->s.number, MASK_SHOT ); + } + else + { + trap_Trace ( &tr, muzzle, NULL, NULL, targ, NPC->s.number, MASK_SHOT ); + } + //FIXME: if using a bouncing weapon like the bowcaster, should we check the reflection of the wall, too? + if ( impactPos ) + {//they want to know *where* the hit would be, too + VectorCopy( tr.endpos, impactPos ); + } +/* // NPCs should be able to shoot even if the muzzle would be inside their target + if ( tr.startsolid || tr.allsolid ) + { + return ENTITYNUM_NONE; + } +*/ + return tr.entityNum; +} + +qboolean NPC_EvaluateShot( int hit, qboolean glassOK ) +{ + if ( !NPC->enemy ) + { + return qfalse; + } + + if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].r.svFlags&SVF_GLASS_BRUSH)) ) + {//can hit enemy or will hit glass, so shoot anyway + return qtrue; + } + return qfalse; +} + +/* +NPC_CheckAttack + +Simply checks aggression and returns true or false +*/ + +qboolean NPC_CheckAttack (float scale) +{ + if(!scale) + scale = 1.0; + + if(((float)NPCInfo->stats.aggression) * scale < flrand(0, 4)) + { + return qfalse; + } + + if(NPCInfo->shotTime > level.time) + return qfalse; + + return qtrue; +} + +/* +NPC_CheckDefend + +Simply checks evasion and returns true or false +*/ + +qboolean NPC_CheckDefend (float scale) +{ + if(!scale) + scale = 1.0; + + if((float)(NPCInfo->stats.evasion) > random() * 4 * scale) + return qtrue; + + return qfalse; +} + + +//NOTE: BE SURE TO CHECK PVS BEFORE THIS! +qboolean NPC_CheckCanAttack (float attack_scale, qboolean stationary) +{ + vec3_t delta, forward; + vec3_t angleToEnemy; + vec3_t hitspot, muzzle, diff, enemy_org;//, enemy_head; + float distanceToEnemy; + qboolean attack_ok = qfalse; +// qboolean duck_ok = qfalse; + qboolean dead_on = qfalse; + float aim_off; + float max_aim_off = 128 - (16 * (float)NPCInfo->stats.aim); + trace_t tr; + gentity_t *traceEnt = NULL; + + if(NPC->enemy->flags & FL_NOTARGET) + { + return qfalse; + } + + //FIXME: only check to see if should duck if that provides cover from the + //enemy!!! + if(!attack_scale) + { + attack_scale = 1.0; + } + //Yaw to enemy + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org ); + NPC_AimWiggle( enemy_org ); + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorSubtract (enemy_org, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + distanceToEnemy = VectorNormalize(delta); + + NPC->NPC->desiredYaw = angleToEnemy[YAW]; + NPC_UpdateFiringAngles(qfalse, qtrue); + + if( NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue) ) + {//Too far away? Do not attack + return qfalse; + } + + if(client->ps.weaponTime > 0) + {//already waiting for a shot to fire + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles(qtrue, qfalse); + return qfalse; + } + + if(NPCInfo->scriptFlags & SCF_DONT_FIRE) + { + return qfalse; + } + + NPCInfo->enemyLastVisibility = enemyVisibility; + //See if they're in our FOV and we have a clear shot to them + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV);////CHECK_PVS| + + if(enemyVisibility >= VIS_FOV) + {//He's in our FOV + + attack_ok = qtrue; + //CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_head); + + //Check to duck + if ( NPC->enemy->client ) + { + if ( NPC->enemy->enemy == NPC ) + { + if ( NPC->enemy->client->buttons & BUTTON_ATTACK ) + {//FIXME: determine if enemy fire angles would hit me or get close + if ( NPC_CheckDefend( 1.0 ) )//FIXME: Check self-preservation? Health? + {//duck and don't shoot + attack_ok = qfalse; + ucmd.upmove = -127; + } + } + } + } + + if(attack_ok) + { + //are we gonna hit him + //NEW: use actual forward facing + AngleVectors( client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, distanceToEnemy, forward, hitspot ); + trap_Trace( &tr, muzzle, NULL, NULL, hitspot, NPC->s.number, MASK_SHOT ); + ShotThroughGlass( &tr, NPC->enemy, hitspot, MASK_SHOT ); + /* + //OLD: trace regardless of facing + trap_Trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT ); + ShotThroughGlass(&tr, NPC->enemy, enemy_org, MASK_SHOT); + */ + + traceEnt = &g_entities[tr.entityNum]; + + /* + if( traceEnt != NPC->enemy &&//FIXME: if someone on our team is in the way, suggest that they duck if possible + (!traceEnt || !traceEnt->client || !NPC->client->enemyTeam || NPC->client->enemyTeam != traceEnt->client->playerTeam) ) + {//no, so shoot for somewhere between the head and torso + //NOTE: yes, I know this looks weird, but it works + enemy_org[0] += 0.3*Q_flrand(NPC->enemy->r.mins[0], NPC->enemy->r.maxs[0]); + enemy_org[1] += 0.3*Q_flrand(NPC->enemy->r.mins[1], NPC->enemy->r.maxs[1]); + enemy_org[2] -= NPC->enemy->r.maxs[2]*Q_flrand(0.0f, 1.0f); + + attack_scale *= 0.75; + trap_Trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT ); + ShotThroughGlass(&tr, NPC->enemy, enemy_org, MASK_SHOT); + traceEnt = &g_entities[tr.entityNum]; + } + */ + + VectorCopy( tr.endpos, hitspot ); + + if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) ) + { + dead_on = qtrue; + } + else + { + attack_scale *= 0.5; + if(NPC->client->playerTeam) + { + if(traceEnt && traceEnt->client && traceEnt->client->playerTeam) + { + if(NPC->client->playerTeam == traceEnt->client->playerTeam) + {//Don't shoot our own team + attack_ok = qfalse; + } + } + } + } + } + + if( attack_ok ) + { + //ok, now adjust pitch aim + VectorSubtract (hitspot, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles(qtrue, qfalse); + + if( !dead_on ) + {//We're not going to hit him directly, try a suppressing fire + //see if where we're going to shoot is too far from his origin + if(traceEnt && (traceEnt->health <= 30 || EntIsGlass(traceEnt))) + {//easy to kill - go for it + //if(traceEnt->die == ExplodeDeath_Wait && traceEnt->splashDamage) + if (0) //rwwFIXMEFIXME: ExplodeDeath_Wait? + {//going to explode, don't shoot if close to self + VectorSubtract(NPC->r.currentOrigin, traceEnt->r.currentOrigin, diff); + if(VectorLengthSquared(diff) < traceEnt->splashRadius*traceEnt->splashRadius) + {//Too close to shoot! + attack_ok = qfalse; + } + else + {//Hey, it might kill him, do it! + attack_scale *= 2;// + } + } + } + else + { + AngleVectors (client->ps.viewangles, forward, NULL, NULL); + VectorMA ( muzzle, distanceToEnemy, forward, hitspot); + VectorSubtract(hitspot, enemy_org, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off)//FIXME: use aim value to allow poor aim? + { + attack_scale *= 0.75; + //see if where we're going to shoot is too far from his head + VectorSubtract(hitspot, enemy_org, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off) + { + attack_ok = qfalse; + } + } + attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off; + } + } + } + } + else + {//Update pitch anyway + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles(qtrue, qfalse); + } + + if( attack_ok ) + { + if( NPC_CheckAttack( attack_scale )) + {//check aggression to decide if we should shoot + enemyVisibility = VIS_SHOOT; + WeaponThink(qtrue); + } + else + attack_ok = qfalse; + } + + return attack_ok; +} +//======================================================================================== +//OLD id-style hunt and kill +//======================================================================================== +/* +IdealDistance + +determines what the NPC's ideal distance from it's enemy should +be in the current situation +*/ +float IdealDistance ( gentity_t *self ) +{ + float ideal; + + ideal = 225 - 20 * NPCInfo->stats.aggression; + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + ideal += 200; + break; + + case WP_THERMAL: + ideal += 50; + break; + +/* case WP_TRICORDER: + ideal = 0; + break; +*/ + case WP_SABER: + case WP_BRYAR_PISTOL: +// case WP_BLASTER_PISTOL: + case WP_BLASTER: + default: + break; + } + + return ideal; +} + +/*QUAKED point_combat (0.7 0 0.7) (-16 -16 -24) (16 16 32) DUCK FLEE INVESTIGATE SQUAD LEAN SNIPE +NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point + +DUCK - NPC will duck and fire from this point, NOT IMPLEMENTED? +FLEE - Will choose this point when running +INVESTIGATE - Will look here if a sound is heard near it +SQUAD - NOT IMPLEMENTED +LEAN - Lean-type cover, NOT IMPLEMENTED +SNIPE - Snipers look for these first, NOT IMPLEMENTED +*/ + +void SP_point_combat( gentity_t *self ) +{ + if(level.numCombatPoints >= MAX_COMBAT_POINTS) + { +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_RED"ERROR: Too many combat points, limit is %d\n", MAX_COMBAT_POINTS); +#endif + G_FreeEntity(self); + return; + } + + self->s.origin[2] += 0.125; + G_SetOrigin(self, self->s.origin); + trap_LinkEntity(self); + + if ( G_CheckInSolid( self, qtrue ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: combat point at %s in solid!\n", vtos(self->r.currentOrigin) ); +#endif + } + + VectorCopy( self->r.currentOrigin, level.combatPoints[level.numCombatPoints].origin ); + + level.combatPoints[level.numCombatPoints].flags = self->spawnflags; + level.combatPoints[level.numCombatPoints].occupied = qfalse; + + level.numCombatPoints++; + + G_FreeEntity(self); +} + +void CP_FindCombatPointWaypoints( void ) +{ + int i; + + for ( i = 0; i < level.numCombatPoints; i++ ) + { + level.combatPoints[i].waypoint = NAV_FindClosestWaypointForPoint2( level.combatPoints[i].origin ); +#ifndef FINAL_BUILD + if ( level.combatPoints[i].waypoint == WAYPOINT_NONE ) + { + Com_Printf( S_COLOR_RED"ERROR: Combat Point at %s has no waypoint!\n", vtos(level.combatPoints[i].origin) ); + } +#endif + } +} + + +/* +------------------------- +NPC_CollectCombatPoints +------------------------- +*/ +typedef struct +{ + float dist; + int index; +} combatPt_t; +static int NPC_CollectCombatPoints( const vec3_t origin, const float radius, combatPt_t *points, const int flags ) +{ + float radiusSqr = (radius*radius); + float distance; + float bestDistance = Q3_INFINITE; + int bestPoint = 0; + int numPoints = 0; + int i; + + //Collect all nearest + for ( i = 0; i < level.numCombatPoints; i++ ) + { + if (numPoints >= MAX_COMBAT_POINTS) + { + break; + } + + //Must be vacant + if ( level.combatPoints[i].occupied == (int) qtrue ) + continue; + + //If we want a duck space, make sure this is one + if ( ( flags & CP_DUCK ) && ( level.combatPoints[i].flags & CPF_DUCK ) ) + continue; + + //If we want a duck space, make sure this is one + if ( ( flags & CP_FLEE ) && ( level.combatPoints[i].flags & CPF_FLEE ) ) + continue; + + ///Make sure this is an investigate combat point + if ( ( flags & CP_INVESTIGATE ) && ( level.combatPoints[i].flags & CPF_INVESTIGATE ) ) + continue; + + //Squad points are only valid if we're looking for them + if ( ( level.combatPoints[i].flags & CPF_SQUAD ) && ( ( flags & CP_SQUAD ) == qfalse ) ) + continue; + + if ( flags&CP_NO_PVS ) + {//must not be within PVS of mu current origin + if ( trap_InPVS( origin, level.combatPoints[i].origin ) ) + { + continue; + } + } + + if ( flags&CP_HORZ_DIST_COLL ) + { + distance = DistanceHorizontalSquared( origin, level.combatPoints[i].origin ); + } + else + { + distance = DistanceSquared( origin, level.combatPoints[i].origin ); + } + + if ( distance < radiusSqr ) + { + if (distance < bestDistance) + { + bestDistance = distance; + bestPoint = numPoints; + } + + points[numPoints].dist = distance; + points[numPoints].index = i; + numPoints++; + } + } + + return numPoints;//bestPoint; +} + +/* +------------------------- +NPC_FindCombatPoint +------------------------- +*/ + +#define MIN_AVOID_DOT 0.75f +#define MIN_AVOID_DISTANCE 128 +#define MIN_AVOID_DISTANCE_SQUARED ( MIN_AVOID_DISTANCE * MIN_AVOID_DISTANCE ) +#define CP_COLLECT_RADIUS 512.0f + +int NPC_FindCombatPoint( const vec3_t position, const vec3_t avoidPosition, vec3_t enemyPosition, const int flags, const float avoidDist, const int ignorePoint ) +{ + combatPt_t points[MAX_COMBAT_POINTS]; + int best = -1, cost, bestCost = Q3_INFINITE, waypoint = WAYPOINT_NONE; + float dist; + trace_t tr; + float collRad = CP_COLLECT_RADIUS; + int numPoints; + int j = 0; + float modifiedAvoidDist = avoidDist; + + if ( modifiedAvoidDist <= 0 ) + { + modifiedAvoidDist = MIN_AVOID_DISTANCE_SQUARED; + } + else + { + modifiedAvoidDist *= modifiedAvoidDist; + } + + if ( (flags & CP_HAS_ROUTE) || (flags & CP_NEAREST) ) + {//going to be doing macro nav tests + if ( NPC->waypoint == WAYPOINT_NONE ) + { + waypoint = NAV_GetNearestNode( NPC, NPC->lastWaypoint ); + } + else + { + waypoint = NPC->waypoint; + } + } + + //Collect our nearest points + if ( flags & CP_NO_PVS ) + {//much larger radius since most will be dropped? + collRad = CP_COLLECT_RADIUS*4; + } + numPoints = NPC_CollectCombatPoints( enemyPosition, collRad, points, flags );//position + + + for ( j = 0; j < numPoints; j++ ) + { + //const int i = (*cpi).second; + const int i = points[j].index; + const float pdist = points[j].dist; + + //Must not be one we want to ignore + if ( i == ignorePoint ) + continue; + + //FIXME: able to mark certain ones as too dangerous to go to for now? Like a tripmine/thermal/detpack is near or something? + //If we need a cover point, check this point + if ( ( flags & CP_COVER ) && ( NPC_ClearLOS( level.combatPoints[i].origin, enemyPosition ) == qtrue ) )//Used to use NPC->enemy + continue; + + //Need a clear LOS to our target... and be within shot range to enemy position (FIXME: make this a separate CS_ flag? and pass in a range?) + if ( flags & CP_CLEAR ) + { + if ( NPC_ClearLOS3( level.combatPoints[i].origin, NPC->enemy ) == qfalse ) + { + continue; + } + if ( NPC->s.weapon == WP_THERMAL ) + {//horizontal + dist = DistanceHorizontalSquared( level.combatPoints[i].origin, NPC->enemy->r.currentOrigin ); + } + else + {//actual + dist = DistanceSquared( level.combatPoints[i].origin, NPC->enemy->r.currentOrigin ); + } + if ( dist > (NPCInfo->stats.visrange*NPCInfo->stats.visrange) ) + { + continue; + } + } + + //Avoid this position? + if ( ( flags & CP_AVOID ) && ( DistanceSquared( level.combatPoints[i].origin, position ) < modifiedAvoidDist ) )//was using MIN_AVOID_DISTANCE_SQUARED, not passed in modifiedAvoidDist + continue; + + //Try to find a point closer to the enemy than where we are + if ( flags & CP_APPROACH_ENEMY ) + { + if ( flags&CP_HORZ_DIST_COLL ) + { + if ( pdist > DistanceHorizontalSquared( position, enemyPosition ) ) + { + continue; + } + } + else + { + if ( pdist > DistanceSquared( position, enemyPosition ) ) + { + continue; + } + } + } + //Try to find a point farther from the enemy than where we are + if ( flags & CP_RETREAT ) + { + if ( flags&CP_HORZ_DIST_COLL ) + { + if ( pdist < DistanceHorizontalSquared( position, enemyPosition ) ) + {//it's closer, don't use it + continue; + } + } + else + { + if ( pdist < DistanceSquared( position, enemyPosition ) ) + {//it's closer, don't use it + continue; + } + } + } + + //We want a point on other side of the enemy from current pos + if ( flags & CP_FLANK ) + { + vec3_t eDir2Me, eDir2CP; + float dot; + + VectorSubtract( position, enemyPosition, eDir2Me ); + VectorNormalize( eDir2Me ); + + VectorSubtract( level.combatPoints[i].origin, enemyPosition, eDir2CP ); + VectorNormalize( eDir2CP ); + + dot = DotProduct( eDir2Me, eDir2CP ); + + //Not far enough behind enemy from current pos + if ( dot >= 0.4 ) + continue; + } + + //See if we're trying to avoid our enemy + //FIXME: this needs to check for the waypoint you'll be taking to get to that combat point + if ( flags & CP_AVOID_ENEMY ) + { + vec3_t eDir, gDir; + vec3_t wpOrg; + float dot; + + VectorSubtract( position, enemyPosition, eDir ); + VectorNormalize( eDir ); + + /* + NAV_FindClosestWaypointForEnt( NPC, level.combatPoints[i].waypoint ); + if ( NPC->waypoint != WAYPOINT_NONE && NPC->waypoint != level.combatPoints[i].waypoint ) + { + trap_Nav_GetNodePosition( NPC->waypoint, wpOrg ); + } + else + */ + { + VectorCopy( level.combatPoints[i].origin, wpOrg ); + } + VectorSubtract( position, wpOrg, gDir ); + VectorNormalize( gDir ); + + dot = DotProduct( gDir, eDir ); + + //Don't want to run at enemy + if ( dot >= MIN_AVOID_DOT ) + continue; + + //Can't be too close to the enemy + if ( DistanceSquared( wpOrg, enemyPosition ) < modifiedAvoidDist ) + continue; + } + + //Okay, now make sure it's not blocked + trap_Trace( &tr, level.combatPoints[i].origin, NPC->r.mins, NPC->r.maxs, level.combatPoints[i].origin, NPC->s.number, NPC->clipmask ); + if ( tr.allsolid || tr.startsolid ) + { + continue; + } + + //we must have a route to the combat point + if ( flags & CP_HAS_ROUTE ) + { + /* + if ( level.combatPoints[i].waypoint == WAYPOINT_NONE ) + { + level.combatPoints[i].waypoint = NAV_FindClosestWaypointForPoint( level.combatPoints[i].origin ); + } + */ + + if ( waypoint == WAYPOINT_NONE || level.combatPoints[i].waypoint == WAYPOINT_NONE || trap_Nav_GetBestNodeAltRoute2( waypoint, level.combatPoints[i].waypoint, NODE_NONE ) == WAYPOINT_NONE ) + {//can't possibly have a route to any OR can't possibly have a route to this one OR don't have a route to this one + if ( !NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, level.combatPoints[i].origin, NPC->clipmask, ENTITYNUM_NONE ) ) + {//don't even have a clear straight path to this one + continue; + } + } + } + + //We want the one with the shortest path from current pos + if ( flags & CP_NEAREST && waypoint != WAYPOINT_NONE && level.combatPoints[i].waypoint != WAYPOINT_NONE ) + { + cost = trap_Nav_GetPathCost( waypoint, level.combatPoints[i].waypoint ); + if ( cost < bestCost ) + { + bestCost = cost; + best = i; + } + continue; + } + + //we want the combat point closest to the enemy + //if ( flags & CP_CLOSEST ) + //they are sorted by this distance, so the first one to get this far is the closest + return i; + } + + return best; +} + +/* +------------------------- +NPC_FindSquadPoint +------------------------- +*/ + +int NPC_FindSquadPoint( vec3_t position ) +{ + float dist, nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE; + int nearestPoint = -1; + int i; + + //float playerDist = DistanceSquared( g_entities[0].currentOrigin, NPC->r.currentOrigin ); + + for ( i = 0; i < level.numCombatPoints; i++ ) + { + //Squad points are only valid if we're looking for them + if ( ( level.combatPoints[i].flags & CPF_SQUAD ) == qfalse ) + continue; + + //Must be vacant + if ( level.combatPoints[i].occupied == qtrue ) + continue; + + dist = DistanceSquared( position, level.combatPoints[i].origin ); + + //The point cannot take us past the player + //if ( dist > ( playerDist * DotProduct( dirToPlayer, playerDir ) ) ) //FIXME: Retain this + // continue; + + //See if this is closer than the others + if ( dist < nearestDist ) + { + nearestPoint = i; + nearestDist = dist; + } + } + + return nearestPoint; +} + +/* +------------------------- +NPC_ReserveCombatPoint +------------------------- +*/ + +qboolean NPC_ReserveCombatPoint( int combatPointID ) +{ + //Make sure it's valid + if ( combatPointID > level.numCombatPoints ) + return qfalse; + + //Make sure it's not already occupied + if ( level.combatPoints[combatPointID].occupied ) + return qfalse; + + //Reserve it + level.combatPoints[combatPointID].occupied = qtrue; + + return qtrue; +} + +/* +------------------------- +NPC_FreeCombatPoint +------------------------- +*/ + +qboolean NPC_FreeCombatPoint( int combatPointID, qboolean failed ) +{ + if ( failed ) + {//remember that this one failed for us + NPCInfo->lastFailedCombatPoint = combatPointID; + } + //Make sure it's valid + if ( combatPointID > level.numCombatPoints ) + return qfalse; + + //Make sure it's currently occupied + if ( level.combatPoints[combatPointID].occupied == qfalse ) + return qfalse; + + //Free it + level.combatPoints[combatPointID].occupied = qfalse; + + return qtrue; +} + +/* +------------------------- +NPC_SetCombatPoint +------------------------- +*/ + +qboolean NPC_SetCombatPoint( int combatPointID ) +{ + //Free a combat point if we already have one + if ( NPCInfo->combatPoint != -1 ) + { + NPC_FreeCombatPoint( NPCInfo->combatPoint, qfalse ); + } + + if ( NPC_ReserveCombatPoint( combatPointID ) == qfalse ) + return qfalse; + + NPCInfo->combatPoint = combatPointID; + + return qtrue; +} + +extern qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper ); +gentity_t *NPC_SearchForWeapons( void ) +{ + gentity_t *found = g_entities, *bestFound = NULL; + float dist, bestDist = Q3_INFINITE; + int i; +// for ( found = g_entities; found < &g_entities[globals.num_entities] ; found++) + for ( i = 0; iinuse ) +// { +// continue; +// } + if(!g_entities[i].inuse) + continue; + + found=&g_entities[i]; + + //FIXME: Also look for ammo_racks that have weapons on them? + if ( found->s.eType != ET_ITEM ) + { + continue; + } + if ( found->item->giType != IT_WEAPON ) + { + continue; + } + if ( found->s.eFlags & EF_NODRAW ) + { + continue; + } + if ( CheckItemCanBePickedUpByNPC( found, NPC ) ) + { + if ( trap_InPVS( found->r.currentOrigin, NPC->r.currentOrigin ) ) + { + dist = DistanceSquared( found->r.currentOrigin, NPC->r.currentOrigin ); + if ( dist < bestDist ) + { + if ( !trap_Nav_GetBestPathBetweenEnts( NPC, found, NF_CLEAR_PATH ) + || trap_Nav_GetBestNodeAltRoute2( NPC->waypoint, found->waypoint, NODE_NONE ) == WAYPOINT_NONE ) + {//can't possibly have a route to any OR can't possibly have a route to this one OR don't have a route to this one + if ( NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, found->r.currentOrigin, NPC->clipmask, ENTITYNUM_NONE ) ) + {//have a clear straight path to this one + bestDist = dist; + bestFound = found; + } + } + else + {//can nav to it + bestDist = dist; + bestFound = found; + } + } + } + } + } + + return bestFound; +} + +void NPC_SetPickUpGoal( gentity_t *foundWeap ) +{ + vec3_t org; + + //NPCInfo->goalEntity = foundWeap; + VectorCopy( foundWeap->r.currentOrigin, org ); + org[2] += 24 - (foundWeap->r.mins[2]*-1);//adjust the origin so that I am on the ground + NPC_SetMoveGoal( NPC, org, foundWeap->r.maxs[0]*0.75, qfalse, -1, foundWeap ); + NPCInfo->tempGoal->waypoint = foundWeap->waypoint; + NPCInfo->tempBehavior = BS_DEFAULT; + NPCInfo->squadState = SQUAD_TRANSITION; +} + +void NPC_CheckGetNewWeapon( void ) +{ + if ( NPC->s.weapon == WP_NONE && NPC->enemy ) + {//if running away because dropped weapon... + if ( NPCInfo->goalEntity + && NPCInfo->goalEntity == NPCInfo->tempGoal + && NPCInfo->goalEntity->enemy + && !NPCInfo->goalEntity->enemy->inuse ) + {//maybe was running at a weapon that was picked up + NPCInfo->goalEntity = NULL; + } + if ( TIMER_Done( NPC, "panic" ) && NPCInfo->goalEntity == NULL ) + {//need a weapon, any lying around? + gentity_t *foundWeap = NPC_SearchForWeapons(); + if ( foundWeap ) + {//try to nav to it + /* + if ( !trap_Nav_GetBestPathBetweenEnts( NPC, foundWeap, NF_CLEAR_PATH ) + || trap_Nav_GetBestNodeAltRoute( NPC->waypoint, foundWeap->waypoint ) == WAYPOINT_NONE ) + {//can't possibly have a route to any OR can't possibly have a route to this one OR don't have a route to this one + if ( !NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, foundWeap->r.currentOrigin, NPC->clipmask, ENTITYNUM_NONE ) ) + {//don't even have a clear straight path to this one + } + else + { + NPC_SetPickUpGoal( foundWeap ); + } + } + else + */ + { + NPC_SetPickUpGoal( foundWeap ); + } + } + } + } +} + +void NPC_AimAdjust( int change ) +{ + if ( !TIMER_Exists( NPC, "aimDebounce" ) ) + { + int debounce = 500+(3-g_spskill.integer)*100; + TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) ); + //int debounce = 1000+(3-g_spskill.integer)*500; + //TIMER_Set( NPC, "aimDebounce", Q_irand( debounce, debounce+2000 ) ); + return; + } + if ( TIMER_Done( NPC, "aimDebounce" ) ) + { + int debounce; + + NPCInfo->currentAim += change; + if ( NPCInfo->currentAim > NPCInfo->stats.aim ) + {//can never be better than max aim + NPCInfo->currentAim = NPCInfo->stats.aim; + } + else if ( NPCInfo->currentAim < -30 ) + {//can never be worse than this + NPCInfo->currentAim = -30; + } + + //Com_Printf( "%s new aim = %d\n", NPC->NPC_type, NPCInfo->currentAim ); + + debounce = 500+(3-g_spskill.integer)*100; + TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) ); + //int debounce = 1000+(3-g_spskill.integer)*500; + //TIMER_Set( NPC, "aimDebounce", Q_irand( debounce, debounce+2000 ) ); + } +} + +void G_AimSet( gentity_t *self, int aim ) +{ + if ( self->NPC ) + { + int debounce; + + self->NPC->currentAim = aim; + //Com_Printf( "%s new aim = %d\n", self->NPC_type, self->NPC->currentAim ); + + debounce = 500+(3-g_spskill.integer)*100; + TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+1000 ) ); + // int debounce = 1000+(3-g_spskill.integer)*500; + // TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+2000 ) ); + } +} diff --git a/codemp/game/NPC_goal.c b/codemp/game/NPC_goal.c new file mode 100644 index 0000000..6b2066a --- /dev/null +++ b/codemp/game/NPC_goal.c @@ -0,0 +1,267 @@ +//b_goal.cpp +#include "b_local.h" +#include "../icarus/Q3_Interface.h" + +extern qboolean FlyingCreature( gentity_t *ent ); +/* +SetGoal +*/ + +void SetGoal( gentity_t *goal, float rating ) +{ + NPCInfo->goalEntity = goal; +// NPCInfo->goalEntityNeed = rating; + NPCInfo->goalTime = level.time; +// NAV_ClearLastRoute(NPC); + if ( goal ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_SetGoal: %s @ %s (%f)\n", goal->classname, vtos( goal->currentOrigin), rating ); + } + else + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_SetGoal: NONE\n" ); + } +} + + +/* +NPC_SetGoal +*/ + +void NPC_SetGoal( gentity_t *goal, float rating ) +{ + if ( goal == NPCInfo->goalEntity ) + { + return; + } + + if ( !goal ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_ERROR, "NPC_SetGoal: NULL goal\n" ); + return; + } + + if ( goal->client ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_ERROR, "NPC_SetGoal: goal is a client\n" ); + return; + } + + if ( NPCInfo->goalEntity ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_SetGoal: push %s\n", NPCInfo->goalEntity->classname ); + NPCInfo->lastGoalEntity = NPCInfo->goalEntity; +// NPCInfo->lastGoalEntityNeed = NPCInfo->goalEntityNeed; + } + + SetGoal( goal, rating ); +} + + +/* +NPC_ClearGoal +*/ + +void NPC_ClearGoal( void ) +{ + gentity_t *goal; + + if ( !NPCInfo->lastGoalEntity ) + { + SetGoal( NULL, 0.0 ); + return; + } + + goal = NPCInfo->lastGoalEntity; + NPCInfo->lastGoalEntity = NULL; +// NAV_ClearLastRoute(NPC); + if ( goal->inuse && !(goal->s.eFlags & EF_NODRAW) ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_ClearGoal: pop %s\n", goal->classname ); + SetGoal( goal, 0 );//, NPCInfo->lastGoalEntityNeed + return; + } + + SetGoal( NULL, 0.0 ); +} + +/* +------------------------- +G_BoundsOverlap +------------------------- +*/ + +qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2) +{//NOTE: flush up against counts as overlapping + if(mins1[0]>maxs2[0]) + return qfalse; + + if(mins1[1]>maxs2[1]) + return qfalse; + + if(mins1[2]>maxs2[2]) + return qfalse; + + if(maxs1[0]goalTime = level.time; + +//MCG - Begin + NPCInfo->aiFlags &= ~NPCAI_MOVING; + ucmd.forwardmove = 0; + //Return that the goal was reached + trap_ICARUS_TaskIDComplete( NPC, TID_MOVE_NAV ); +//MCG - End +} +/* +ReachedGoal + +id removed checks against waypoints and is now checking surfaces +*/ +//qboolean NAV_HitNavGoal( vec3_t point, vec3_t mins, vec3_t maxs, gentity_t *goal, qboolean flying ); +qboolean ReachedGoal( gentity_t *goal ) +{ + //FIXME: For script waypoints, need a special check +/* + int goalWpNum; + vec3_t vec; + //vec3_t angles; + float delta; + + if ( goal->flags & FL_NAVGOAL ) + {//waypoint_navgoal + return NAV_HitNavGoal( NPC->currentOrigin, NPC->mins, NPC->maxs, goal, FlyingCreature( NPC ) ); + } + + if ( goal == NPCInfo->tempGoal && !(goal->flags & FL_NAVGOAL)) + {//MUST touch waypoints, even if moving to it + //This is odd, it just checks to see if they are on the same + //surface and the tempGoal in in the FOV - does NOT check distance! + // are we on same surface? + + //FIXME: NPC->waypoint reset every frame, need to find it first + //Should we do that here? (Still will do it only once per frame) + if ( NPC->waypoint >= 0 && NPC->waypoint < num_waypoints ) + { + goalWpNum = NAV_FindWaypointAt ( goal->currentOrigin ); + if ( NPC->waypoint != goalWpNum ) + { + return qfalse; + } + } + + VectorSubtract ( NPCInfo->tempGoal->currentOrigin, NPC->currentOrigin, vec); + //Who cares if it's in our FOV?! + /* + // is it in our FOV + vectoangles ( vec, angles ); + delta = AngleDelta ( NPC->client->ps.viewangles[YAW], angles[YAW] ); + if ( fabs ( delta ) > NPCInfo->stats.hfov ) + { + return qfalse; + } + */ + + /* + //If in the same waypoint as tempGoal, we're there, right? + if ( goal->waypoint >= 0 && goal->waypoint < num_waypoints ) + { + if ( NPC->waypoint == goal->waypoint ) + { + return qtrue; + } + } + */ + +/* + if ( VectorLengthSquared( vec ) < (64*64) ) + {//Close enough + return qtrue; + } + + return qfalse; + } +*/ + if ( NPCInfo->aiFlags & NPCAI_TOUCHED_GOAL ) + { + NPCInfo->aiFlags &= ~NPCAI_TOUCHED_GOAL; + return qtrue; + } +/* + if ( goal->s.eFlags & EF_NODRAW ) + { + goalWpNum = NAV_FindWaypointAt( goal->currentOrigin ); + if ( NPC->waypoint == goalWpNum ) + { + return qtrue; + } + return qfalse; + } + + if(goal->client && goal->health <= 0) + {//trying to get to dead guy + goalWpNum = NAV_FindWaypointAt( goal->currentOrigin ); + if ( NPC->waypoint == goalWpNum ) + { + VectorSubtract(NPC->currentOrigin, goal->currentOrigin, vec); + vec[2] = 0; + delta = VectorLengthSquared(vec); + if(delta <= 800) + {//with 20-30 of other guy's origin + return qtrue; + } + } + } +*/ + return NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, goal->r.currentOrigin, NPCInfo->goalRadius, FlyingCreature( NPC ) ); +} + +/* +static gentity_t *UpdateGoal( void ) + +Id removed a lot of shit here... doesn't seem to handle waypoints independantly of goalentity + +In fact, doesn't seem to be any waypoint info on entities at all any more? + +MCG - Since goal is ALWAYS goalEntity, took out a lot of sending goal entity pointers around for no reason +*/ + +gentity_t *UpdateGoal( void ) +{ + gentity_t *goal; + + if ( !NPCInfo->goalEntity ) + { + return NULL; + } + + if ( !NPCInfo->goalEntity->inuse ) + {//Somehow freed it, but didn't clear it + NPC_ClearGoal(); + return NULL; + } + + goal = NPCInfo->goalEntity; + + if ( ReachedGoal( goal ) ) + { + NPC_ReachedGoal(); + goal = NULL;//so they don't keep trying to move to it + }//else if fail, need to tell script so? + + return goal; +} diff --git a/codemp/game/NPC_misc.c b/codemp/game/NPC_misc.c new file mode 100644 index 0000000..69c3f32 --- /dev/null +++ b/codemp/game/NPC_misc.c @@ -0,0 +1,73 @@ +// +// NPC_misc.cpp +// +#include "b_local.h" +#include "q_shared.h" + +/* +Debug_Printf +*/ +void Debug_Printf (vmCvar_t *cv, int debugLevel, char *fmt, ...) +{ + char *color; + va_list argptr; + char msg[1024]; + + if (cv->value < debugLevel) + return; + + if (debugLevel == DEBUG_LEVEL_DETAIL) + color = S_COLOR_WHITE; + else if (debugLevel == DEBUG_LEVEL_INFO) + color = S_COLOR_GREEN; + else if (debugLevel == DEBUG_LEVEL_WARNING) + color = S_COLOR_YELLOW; + else if (debugLevel == DEBUG_LEVEL_ERROR) + color = S_COLOR_RED; + else + color = S_COLOR_RED; + + va_start (argptr,fmt); + vsprintf (msg, fmt, argptr); + va_end (argptr); + + Com_Printf("%s%5i:%s", color, level.time, msg); +} + + +/* +Debug_NPCPrintf +*/ +void Debug_NPCPrintf (gentity_t *printNPC, vmCvar_t *cv, int debugLevel, char *fmt, ...) +{ + int color; + va_list argptr; + char msg[1024]; + + if (cv->value < debugLevel) + { + return; + } + +// if ( debugNPCName.string[0] && Q_stricmp( debugNPCName.string, printNPC->targetname) != 0 ) +// { +// return; +// } + + if (debugLevel == DEBUG_LEVEL_DETAIL) + color = COLOR_WHITE; + else if (debugLevel == DEBUG_LEVEL_INFO) + color = COLOR_GREEN; + else if (debugLevel == DEBUG_LEVEL_WARNING) + color = COLOR_YELLOW; + else if (debugLevel == DEBUG_LEVEL_ERROR) + color = COLOR_RED; + else + color = COLOR_RED; + + va_start (argptr,fmt); + vsprintf (msg, fmt, argptr); + va_end (argptr); + + Com_Printf ("%c%c%5i (%s) %s", Q_COLOR_ESCAPE, color, level.time, printNPC->targetname, msg); +} diff --git a/codemp/game/NPC_move.c b/codemp/game/NPC_move.c new file mode 100644 index 0000000..3714afa --- /dev/null +++ b/codemp/game/NPC_move.c @@ -0,0 +1,505 @@ +// +// NPC_move.cpp +// +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" + +void G_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color ); + +qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2); +int NAV_Steer( gentity_t *self, vec3_t dir, float distance ); +extern int GetTime ( int lastTime ); + +navInfo_t frameNavInfo; +extern qboolean FlyingCreature( gentity_t *ent ); + +#include "../namespace_begin.h" +extern qboolean PM_InKnockDown( playerState_t *ps ); +#include "../namespace_end.h" + +/* +------------------------- +NPC_ClearPathToGoal +------------------------- +*/ + +qboolean NPC_ClearPathToGoal( vec3_t dir, gentity_t *goal ) +{ + trace_t trace; + float radius, dist, tFrac; + + //FIXME: What does do about area portals? THIS IS BROKEN + //if ( gi.inPVS( NPC->r.currentOrigin, goal->r.currentOrigin ) == qfalse ) + // return qfalse; + + //Look ahead and see if we're clear to move to our goal position + if ( NAV_CheckAhead( NPC, goal->r.currentOrigin, &trace, ( NPC->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) ) + { + //VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, dir ); + return qtrue; + } + + if (!FlyingCreature(NPC)) + { + //See if we're too far above + if ( fabs( NPC->r.currentOrigin[2] - goal->r.currentOrigin[2] ) > 48 ) + return qfalse; + } + + //This is a work around + radius = ( NPC->r.maxs[0] > NPC->r.maxs[1] ) ? NPC->r.maxs[0] : NPC->r.maxs[1]; + dist = Distance( NPC->r.currentOrigin, goal->r.currentOrigin ); + tFrac = 1.0f - ( radius / dist ); + + if ( trace.fraction >= tFrac ) + return qtrue; + + //See if we're looking for a navgoal + if ( goal->flags & FL_NAVGOAL ) + { + //Okay, didn't get all the way there, let's see if we got close enough: + if ( NAV_HitNavGoal( trace.endpos, NPC->r.mins, NPC->r.maxs, goal->r.currentOrigin, NPCInfo->goalRadius, FlyingCreature( NPC ) ) ) + { + //VectorSubtract(goal->r.currentOrigin, NPC->r.currentOrigin, dir); + return qtrue; + } + } + + return qfalse; +} + +/* +------------------------- +NPC_CheckCombatMove +------------------------- +*/ + +ID_INLINE qboolean NPC_CheckCombatMove( void ) +{ + //return NPCInfo->combatMove; + if ( ( NPCInfo->goalEntity && NPC->enemy && NPCInfo->goalEntity == NPC->enemy ) || ( NPCInfo->combatMove ) ) + { + return qtrue; + } + + if ( NPCInfo->goalEntity && NPCInfo->watchTarget ) + { + if ( NPCInfo->goalEntity != NPCInfo->watchTarget ) + { + return qtrue; + } + } + + return qfalse; +} + +/* +------------------------- +NPC_LadderMove +------------------------- +*/ + +static void NPC_LadderMove( vec3_t dir ) +{ + //FIXME: this doesn't guarantee we're facing ladder + //ALSO: Need to be able to get off at top + //ALSO: Need to play an anim + //ALSO: Need transitionary anims? + + if ( ( dir[2] > 0 ) || ( dir[2] < 0 && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) ) + { + //Set our movement direction + ucmd.upmove = (dir[2] > 0) ? 127 : -127; + + //Don't move around on XY + ucmd.forwardmove = ucmd.rightmove = 0; + } +} + +/* +------------------------- +NPC_GetMoveInformation +------------------------- +*/ + +ID_INLINE qboolean NPC_GetMoveInformation( vec3_t dir, float *distance ) +{ + //NOTENOTE: Use path stacks! + + //Make sure we have somewhere to go + if ( NPCInfo->goalEntity == NULL ) + return qfalse; + + //Get our move info + VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir ); + *distance = VectorNormalize( dir ); + + VectorCopy( NPCInfo->goalEntity->r.currentOrigin, NPCInfo->blockedDest ); + + return qtrue; +} + +/* +------------------------- +NAV_GetLastMove +------------------------- +*/ + +void NAV_GetLastMove( navInfo_t *info ) +{ + *info = frameNavInfo; +} + +/* +------------------------- +NPC_GetMoveDirection +------------------------- +*/ + +qboolean NPC_GetMoveDirection( vec3_t out, float *distance ) +{ + vec3_t angles; + + //Clear the struct + memset( &frameNavInfo, 0, sizeof( frameNavInfo ) ); + + //Get our movement, if any + if ( NPC_GetMoveInformation( frameNavInfo.direction, &frameNavInfo.distance ) == qfalse ) + return qfalse; + + //Setup the return value + *distance = frameNavInfo.distance; + + //For starters + VectorCopy( frameNavInfo.direction, frameNavInfo.pathDirection ); + + //If on a ladder, move appropriately + if ( NPC->watertype & CONTENTS_LADDER ) + { + NPC_LadderMove( frameNavInfo.direction ); + return qtrue; + } + + //Attempt a straight move to goal + if ( NPC_ClearPathToGoal( frameNavInfo.direction, NPCInfo->goalEntity ) == qfalse ) + { + //See if we're just stuck + if ( NAV_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) + { + //Can't reach goal, just face + vectoangles( frameNavInfo.direction, angles ); + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + return qfalse; + } + + frameNavInfo.flags |= NIF_MACRO_NAV; + } + + //Avoid any collisions on the way + if ( NAV_AvoidCollision( NPC, NPCInfo->goalEntity, &frameNavInfo ) == qfalse ) + { + //FIXME: Emit a warning, this is a worst case scenario + //FIXME: if we have a clear path to our goal (exluding bodies), but then this + // check (against bodies only) fails, shouldn't we fall back + // to macro navigation? Like so: + if ( !(frameNavInfo.flags&NIF_MACRO_NAV) ) + {//we had a clear path to goal and didn't try macro nav, but can't avoid collision so try macro nav here + //See if we're just stuck + if ( NAV_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) + { + //Can't reach goal, just face + vectoangles( frameNavInfo.direction, angles ); + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + return qfalse; + } + + frameNavInfo.flags |= NIF_MACRO_NAV; + } + } + + //Setup the return values + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + + return qtrue; +} + +/* +------------------------- +NPC_GetMoveDirectionAltRoute +------------------------- +*/ +extern int NAVNEW_MoveToGoal( gentity_t *self, navInfo_t *info ); +extern qboolean NAVNEW_AvoidCollision( gentity_t *self, gentity_t *goal, navInfo_t *info, qboolean setBlockedInfo, int blockedMovesLimit ); +qboolean NPC_GetMoveDirectionAltRoute( vec3_t out, float *distance, qboolean tryStraight ) +{ + vec3_t angles; + + NPCInfo->aiFlags &= ~NPCAI_BLOCKED; + + //Clear the struct + memset( &frameNavInfo, 0, sizeof( frameNavInfo ) ); + + //Get our movement, if any + if ( NPC_GetMoveInformation( frameNavInfo.direction, &frameNavInfo.distance ) == qfalse ) + return qfalse; + + //Setup the return value + *distance = frameNavInfo.distance; + + //For starters + VectorCopy( frameNavInfo.direction, frameNavInfo.pathDirection ); + + //If on a ladder, move appropriately + if ( NPC->watertype & CONTENTS_LADDER ) + { + NPC_LadderMove( frameNavInfo.direction ); + return qtrue; + } + + //Attempt a straight move to goal + if ( !tryStraight || NPC_ClearPathToGoal( frameNavInfo.direction, NPCInfo->goalEntity ) == qfalse ) + {//blocked + //Can't get straight to goal, use macro nav + if ( NAVNEW_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) + { + //Can't reach goal, just face + vectoangles( frameNavInfo.direction, angles ); + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + return qfalse; + } + //else we are on our way + frameNavInfo.flags |= NIF_MACRO_NAV; + } + else + {//we have no architectural problems, see if there are ents inthe way and try to go around them + //not blocked + if ( d_altRoutes.integer ) + {//try macro nav + navInfo_t tempInfo; + memcpy( &tempInfo, &frameNavInfo, sizeof( tempInfo ) ); + if ( NAVNEW_AvoidCollision( NPC, NPCInfo->goalEntity, &tempInfo, qtrue, 5 ) == qfalse ) + {//revert to macro nav + //Can't get straight to goal, dump tempInfo and use macro nav + if ( NAVNEW_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) + { + //Can't reach goal, just face + vectoangles( frameNavInfo.direction, angles ); + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + return qfalse; + } + //else we are on our way + frameNavInfo.flags |= NIF_MACRO_NAV; + } + else + {//otherwise, either clear or can avoid + memcpy( &frameNavInfo, &tempInfo, sizeof( frameNavInfo ) ); + } + } + else + {//OR: just give up + if ( NAVNEW_AvoidCollision( NPC, NPCInfo->goalEntity, &frameNavInfo, qtrue, 30 ) == qfalse ) + {//give up + return qfalse; + } + } + } + + //Setup the return values + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + + return qtrue; +} + +void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir ) +{ + vec3_t forward, right; + float fDot, rDot; + + AngleVectors( self->r.currentAngles, forward, right, NULL ); + + dir[2] = 0; + VectorNormalize( dir ); + //NPCs cheat and store this directly because converting movement into a ucmd loses precision + VectorCopy( dir, self->client->ps.moveDir ); + + fDot = DotProduct( forward, dir ) * 127.0f; + rDot = DotProduct( right, dir ) * 127.0f; + //Must clamp this because DotProduct is not guaranteed to return a number within -1 to 1, and that would be bad when we're shoving this into a signed byte + if ( fDot > 127.0f ) + { + fDot = 127.0f; + } + if ( fDot < -127.0f ) + { + fDot = -127.0f; + } + if ( rDot > 127.0f ) + { + rDot = 127.0f; + } + if ( rDot < -127.0f ) + { + rDot = -127.0f; + } + cmd->forwardmove = floor(fDot); + cmd->rightmove = floor(rDot); + + /* + vec3_t wishvel; + for ( int i = 0 ; i < 3 ; i++ ) + { + wishvel[i] = forward[i]*cmd->forwardmove + right[i]*cmd->rightmove; + } + VectorNormalize( wishvel ); + if ( !VectorCompare( wishvel, dir ) ) + { + Com_Printf( "PRECISION LOSS: %s != %s\n", vtos(wishvel), vtos(dir) ); + } + */ +} + +/* +------------------------- +NPC_MoveToGoal + + Now assumes goal is goalEntity, was no reason for it to be otherwise +------------------------- +*/ +#if AI_TIMERS +extern int navTime; +#endif// AI_TIMERS +qboolean NPC_MoveToGoal( qboolean tryStraight ) +{ + float distance; + vec3_t dir; + +#if AI_TIMERS + int startTime = GetTime(0); +#endif// AI_TIMERS + //If taking full body pain, don't move + if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->s.legsAnim >= BOTH_PAIN1 ) && ( NPC->s.legsAnim <= BOTH_PAIN18 ) ) ) + { + return qtrue; + } + + /* + if( NPC->s.eFlags & EF_LOCKED_TO_WEAPON ) + {//If in an emplaced gun, never try to navigate! + return qtrue; + } + */ + //rwwFIXMEFIXME: emplaced support + + //FIXME: if can't get to goal & goal is a target (enemy), try to find a waypoint that has line of sight to target, at least? + //Get our movement direction +#if 1 + if ( NPC_GetMoveDirectionAltRoute( dir, &distance, tryStraight ) == qfalse ) +#else + if ( NPC_GetMoveDirection( dir, &distance ) == qfalse ) +#endif + return qfalse; + + NPCInfo->distToGoal = distance; + + //Convert the move to angles + vectoangles( dir, NPCInfo->lastPathAngles ); + if ( (ucmd.buttons&BUTTON_WALKING) ) + { + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + } + else + { + NPC->client->ps.speed = NPCInfo->stats.runSpeed; + } + + //FIXME: still getting ping-ponging in certain cases... !!! Nav/avoidance error? WTF???!!! + //If in combat move, then move directly towards our goal + if ( NPC_CheckCombatMove() ) + {//keep current facing + G_UcmdMoveForDir( NPC, &ucmd, dir ); + } + else + {//face our goal + //FIXME: strafe instead of turn if change in dir is small and temporary + NPCInfo->desiredPitch = 0.0f; + NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->lastPathAngles[YAW] ); + + //Pitch towards the goal and also update if flying or swimming + if ( (NPC->client->ps.eFlags2&EF2_FLYING) )//moveType == MT_FLYSWIM ) + { + NPCInfo->desiredPitch = AngleNormalize360( NPCInfo->lastPathAngles[PITCH] ); + + if ( dir[2] ) + { + float scale = (dir[2] * distance); + if ( scale > 64 ) + { + scale = 64; + } + else if ( scale < -64 ) + { + scale = -64; + } + NPC->client->ps.velocity[2] = scale; + //NPC->client->ps.velocity[2] = (dir[2] > 0) ? 64 : -64; + } + } + + //Set any final info + ucmd.forwardmove = 127; + } + +#if AI_TIMERS + navTime += GetTime( startTime ); +#endif// AI_TIMERS + return qtrue; +} + +/* +------------------------- +void NPC_SlideMoveToGoal( void ) + + Now assumes goal is goalEntity, if want to use tempGoal, you set that before calling the func +------------------------- +*/ +qboolean NPC_SlideMoveToGoal( void ) +{ + float saveYaw = NPC->client->ps.viewangles[YAW]; + qboolean ret; + + NPCInfo->combatMove = qtrue; + + ret = NPC_MoveToGoal( qtrue ); + + NPCInfo->desiredYaw = saveYaw; + + return ret; +} + + +/* +------------------------- +NPC_ApplyRoff +------------------------- +*/ + +void NPC_ApplyRoff(void) +{ + BG_PlayerStateToEntityState( &NPC->client->ps, &NPC->s, qfalse ); + //VectorCopy ( NPC->r.currentOrigin, NPC->lastOrigin ); + //rwwFIXMEFIXME: Any significance to this? + + // use the precise origin for linking + trap_LinkEntity(NPC); +} diff --git a/codemp/game/NPC_reactions.c b/codemp/game/NPC_reactions.c new file mode 100644 index 0000000..d107ed0 --- /dev/null +++ b/codemp/game/NPC_reactions.c @@ -0,0 +1,1127 @@ +//NPC_reactions.cpp +#include "b_local.h" +#include "anims.h" +#include "w_saber.h" + +extern qboolean G_CheckForStrongAttackMomentum( gentity_t *self ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +extern void cgi_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); +extern qboolean NPC_CheckLookTarget( gentity_t *self ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +extern qboolean Jedi_WaitingAmbush( gentity_t *self ); +extern void Jedi_Ambush( gentity_t *self ); +extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent); + +#include "../namespace_begin.h" +extern qboolean BG_SaberInSpecialAttack( int anim ); +extern qboolean PM_SpinningSaberAnim( int anim ); +extern qboolean PM_SpinningAnim( int anim ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean BG_FlippingAnim( int anim ); +extern qboolean PM_RollingAnim( int anim ); +extern qboolean PM_InCartwheel( int anim ); +extern qboolean BG_CrouchAnim( int anim ); +#include "../namespace_end.h" + +extern int teamLastEnemyTime[]; +extern int killPlayerTimer; + +//float g_crosshairEntDist = Q3_INFINITE; +//int g_crosshairSameEntTime = 0; +//int g_crosshairEntNum = ENTITYNUM_NONE; +//int g_crosshairEntTime = 0; + +/* +------------------------- +NPC_CheckAttacker +------------------------- +*/ + +static void NPC_CheckAttacker( gentity_t *other, int mod ) +{ + //FIXME: I don't see anything in here that would stop teammates from taking a teammate + // as an enemy. Ideally, there would be code before this to prevent that from + // happening, but that is presumptuous. + + //valid ent - FIXME: a VALIDENT macro would be nice here + if ( !other ) + return; + + if ( other == NPC ) + return; + + if ( !other->inuse ) + return; + + //Don't take a target that doesn't want to be + if ( other->flags & FL_NOTARGET ) + return; + +// if ( NPC->svFlags & SVF_LOCKEDENEMY ) +// {//IF LOCKED, CANNOT CHANGE ENEMY!!!!! +// return; +// } + //rwwFIXMEFIXME: support this + + //If we haven't taken a target, just get mad + if ( NPC->enemy == NULL )//was using "other", fixed to NPC + { + G_SetEnemy( NPC, other ); + return; + } + + //we have an enemy, see if he's dead + if ( NPC->enemy->health <= 0 ) + { + G_ClearEnemy( NPC ); + G_SetEnemy( NPC, other ); + return; + } + + //Don't take the same enemy again + if ( other == NPC->enemy ) + return; + + if ( NPC->client->ps.weapon == WP_SABER ) + {//I'm a jedi + if ( mod == MOD_SABER ) + {//I was hit by a saber FIXME: what if this was a thrown saber? + //always switch to this enemy if I'm a jedi and hit by another saber + G_ClearEnemy( NPC ); + G_SetEnemy( NPC, other ); + return; + } + } + //Special case player interactions + if ( other == &g_entities[0] ) + { + //Account for the skill level to skew the results + float luckThreshold; + + switch ( g_spskill.integer ) + { + //Easiest difficulty, mild chance of picking up the player + case 0: + luckThreshold = 0.9f; + break; + + //Medium difficulty, half-half chance of picking up the player + case 1: + luckThreshold = 0.5f; + break; + + //Hardest difficulty, always turn on attacking player + case 2: + default: + luckThreshold = 0.0f; + break; + } + + //Randomly pick up the target + if ( random() > luckThreshold ) + { + G_ClearEnemy( other ); + other->enemy = NPC; + } + + return; + } +} + +void NPC_SetPainEvent( gentity_t *self ) +{ + if ( !self->NPC || !(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + { + // no more borg + // if( self->client->playerTeam != TEAM_BORG ) + // { + //if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) ) + if (!trap_ICARUS_TaskIDPending(self, TID_CHAN_VOICE) && self->client) + { + //G_AddEvent( self, EV_PAIN, floor((float)self->health/self->max_health*100.0f) ); + G_AddEvent( self, EV_PAIN, floor((float)self->health/self->client->ps.stats[STAT_MAX_HEALTH]*100.0f) ); + //rwwFIXMEFIXME: Do this properly? + } + // } + } +} + +/* +------------------------- +NPC_GetPainChance +------------------------- +*/ + +float NPC_GetPainChance( gentity_t *self, int damage ) +{ + float pain_chance; + if ( !self->enemy ) + {//surprised, always take pain + return 1.0f; + } + + if (!self->client) + { + return 1.0f; + } + + //if ( damage > self->max_health/2.0f ) + if (damage > self->client->ps.stats[STAT_MAX_HEALTH]/2.0f) + { + return 1.0f; + } + + pain_chance = (float)(self->client->ps.stats[STAT_MAX_HEALTH]-self->health)/(self->client->ps.stats[STAT_MAX_HEALTH]*2.0f) + (float)damage/(self->client->ps.stats[STAT_MAX_HEALTH]/2.0f); + switch ( g_spskill.integer ) + { + case 0: //easy + //return 0.75f; + break; + + case 1://med + pain_chance *= 0.5f; + //return 0.35f; + break; + + case 2://hard + default: + pain_chance *= 0.1f; + //return 0.05f; + break; + } + //Com_Printf( "%s: %4.2f\n", self->NPC_type, pain_chance ); + return pain_chance; +} + +/* +------------------------- +NPC_ChoosePainAnimation +------------------------- +*/ + +#define MIN_PAIN_TIME 200 + +extern int G_PickPainAnim( gentity_t *self, vec3_t point, int damage, int hitLoc ); +void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, vec3_t point, int damage, int mod, int hitLoc, int voiceEvent ) +{ + int pain_anim = -1; + float pain_chance; + + //If we've already taken pain, then don't take it again + if ( level.time < self->painDebounceTime && /*mod != MOD_ELECTROCUTE &&*/ mod != MOD_MELEE ) //rwwFIXMEFIXME: MOD_ELECTROCUTE + {//FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim? + return; + } + + if ( self->s.weapon == WP_THERMAL && self->client->ps.weaponTime > 0 ) + {//don't interrupt thermal throwing anim + return; + } +/* + else if ( self->client->NPC_class == CLASS_GALAKMECH ) + { + if ( hitLoc == HL_GENERIC1 ) + {//hit the antenna! + pain_chance = 1.0f; + // self->s.powerups |= ( 1 << PW_SHOCKED ); + // self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); + //rwwFIXMEFIXME: support for this + } + // else if ( self->client->ps.powerups[PW_GALAK_SHIELD] ) + // {//shield up + // return; + // } + //rwwFIXMEFIXME: and this + else if ( self->health > 200 && damage < 100 ) + {//have a *lot* of health + pain_chance = 0.05f; + } + else + {//the lower my health and greater the damage, the more likely I am to play a pain anim + pain_chance = (200.0f-self->health)/100.0f + damage/50.0f; + } + } +*/ + else if ( self->client && self->client->playerTeam == NPCTEAM_PLAYER && other && !other->s.number ) + {//ally shot by player always complains + pain_chance = 1.1f; + } + else + { + if ( other && other->s.weapon == WP_SABER || /*mod == MOD_ELECTROCUTE ||*/ mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/ ) + { + pain_chance = 1.0f;//always take pain from saber + } + else if ( mod == MOD_MELEE ) + {//higher in rank (skill) we are, less likely we are to be fazed by a punch + pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN); + } + else if ( self->client->NPC_class == CLASS_PROTOCOL ) + { + pain_chance = 1.0f; + } + else + { + pain_chance = NPC_GetPainChance( self, damage ); + } + if ( self->client->NPC_class == CLASS_DESANN ) + { + pain_chance *= 0.5f; + } + } + + //See if we're going to flinch + if ( random() < pain_chance ) + { + int animLength; + + //Pick and play our animation + if ( self->client->ps.fd.forceGripBeingGripped < level.time ) + {//not being force-gripped or force-drained + if ( /*G_CheckForStrongAttackMomentum( self ) //rwwFIXMEFIXME: Is this needed? + ||*/ PM_SpinningAnim( self->client->ps.legsAnim ) + || BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) + || PM_InKnockDown( &self->client->ps ) + || PM_RollingAnim( self->client->ps.legsAnim ) + || (BG_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) ) + {//strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain + } + else + {//play an anim + int parts; + + if ( self->client->NPC_class == CLASS_GALAKMECH ) + {//only has 1 for now + //FIXME: never plays this, it seems... + pain_anim = BOTH_PAIN1; + } + else if ( mod == MOD_MELEE ) + { + pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN2, BOTH_PAIN3 ); + } + else if ( self->s.weapon == WP_SABER ) + {//temp HACK: these are the only 2 pain anims that look good when holding a saber + pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN2, BOTH_PAIN3 ); + } + /* + else if ( mod != MOD_ELECTROCUTE ) + { + pain_anim = G_PickPainAnim( self, point, damage, hitLoc ); + } + */ + + if ( pain_anim == -1 ) + { + pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN1, BOTH_PAIN18 ); + } + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;//next attack must be a quick attack + self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in + parts = SETANIM_BOTH; + if ( BG_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) ) + { + parts = SETANIM_LEGS; + } + + if (pain_anim != -1) + { + NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + if ( voiceEvent != -1 ) + { + G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) ); + } + else + { + NPC_SetPainEvent( self ); + } + } + else + { + G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); + } + + //Setup the timing for it + /* + if ( mod == MOD_ELECTROCUTE ) + { + self->painDebounceTime = level.time + 4000; + } + */ + animLength = bgAllAnims[self->localAnimIndex].anims[pain_anim].numFrames * fabs((float)(bgHumanoidAnimations[pain_anim].frameLerp)); + + self->painDebounceTime = level.time + animLength; + self->client->ps.weaponTime = 0; + } +} + +/* +=============== +NPC_Pain +=============== +*/ +void NPC_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + team_t otherTeam = TEAM_FREE; + int voiceEvent = -1; + gentity_t *other = attacker; + int mod = gPainMOD; + int hitLoc = gPainHitLoc; + vec3_t point; + + VectorCopy(gPainPoint, point); + + if ( self->NPC == NULL ) + return; + + if ( other == NULL ) + return; + + //or just remove ->pain in player_die? + if ( self->client->ps.pm_type == PM_DEAD ) + return; + + if ( other == self ) + return; + + //MCG: Ignore damage from your own team for now + if ( other->client ) + { + otherTeam = other->client->playerTeam; + // if ( otherTeam == TEAM_DISGUISE ) + // { + // otherTeam = TEAM_PLAYER; + // } + } + + if ( self->client->playerTeam + && other->client + && otherTeam == self->client->playerTeam + /* && (!player->client->ps.viewEntity || other->s.number != player->client->ps.viewEntity)*/) + //rwwFIXMEFIXME: Will need modification when player controllable npcs are done + {//hit by a teammate + if ( other != self->enemy && self != other->enemy ) + {//we weren't already enemies + if ( self->enemy || other->enemy + + //|| (other->s.number&&other->s.number!=player->client->ps.viewEntity) + //rwwFIXMEFIXME: same + + /*|| (!other->s.number&&Q_irand( 0, 3 ))*/ ) + {//if one of us actually has an enemy already, it's okay, just an accident OR wasn't hit by player or someone controlled by player OR player hit ally and didn't get 25% chance of getting mad (FIXME:accumulate anger+base on diff?) + //FIXME: player should have to do a certain amount of damage to ally or hit them several times to make them mad + //Still run pain and flee scripts + if ( self->client && self->NPC ) + {//Run any pain instructions + if ( self->health <= (self->client->ps.stats[STAT_MAX_HEALTH]/3) && G_ActivateBehavior(self, BSET_FLEE) ) + { + + } + else// if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) ) + { + G_ActivateBehavior(self, BSET_PAIN); + } + } + if ( damage != -1 ) + {//-1 == don't play pain anim + //Set our proper pain animation + if ( Q_irand( 0, 1 ) ) + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN ); + } + else + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, -1 ); + } + } + return; + } + else if ( self->NPC && !other->s.number )//should be assumed, but... + {//dammit, stop that! + if ( self->NPC->charmedTime ) + {//mindtricked + return; + } + else if ( self->NPC->ffireCount < 3+((2-g_spskill.integer)*2) ) + {//not mad enough yet + //Com_Printf( "chck: %d < %d\n", self->NPC->ffireCount, 3+((2-g_spskill.integer)*2) ); + if ( damage != -1 ) + {//-1 == don't play pain anim + //Set our proper pain animation + if ( Q_irand( 0, 1 ) ) + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN ); + } + else + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, -1 ); + } + } + return; + } + else if ( G_ActivateBehavior( self, BSET_FFIRE ) ) + {//we have a specific script to run, so do that instead + return; + } + else + {//okay, we're going to turn on our ally, we need to set and lock our enemy and put ourselves in a bstate that lets us attack him (and clear any flags that would stop us) + self->NPC->blockedSpeechDebounceTime = 0; + voiceEvent = EV_FFTURN; + self->NPC->behaviorState = self->NPC->tempBehavior = self->NPC->defaultBehavior = BS_DEFAULT; + other->flags &= ~FL_NOTARGET; + //self->svFlags &= ~(SVF_IGNORE_ENEMIES|SVF_ICARUS_FREEZE|SVF_NO_COMBAT_SOUNDS); + self->r.svFlags &= ~SVF_ICARUS_FREEZE; + G_SetEnemy( self, other ); + //self->svFlags |= SVF_LOCKEDENEMY; //rwwFIXMEFIXME: proper support for these flags. + self->NPC->scriptFlags &= ~(SCF_DONT_FIRE|SCF_CROUCHED|SCF_WALKING|SCF_NO_COMBAT_TALK|SCF_FORCED_MARCH); + self->NPC->scriptFlags |= (SCF_CHASE_ENEMIES|SCF_NO_MIND_TRICK); + //NOTE: we also stop ICARUS altogether + //stop_icarus = qtrue; + //rwwFIXMEFIXME: stop icarus? + if ( !killPlayerTimer ) + { + killPlayerTimer = level.time + 10000; + } + } + } + } + } + + SaveNPCGlobals(); + SetNPCGlobals( self ); + + //Do extra bits + if ( NPCInfo->ignorePain == qfalse ) + { + NPCInfo->confusionTime = 0;//clear any charm or confusion, regardless + if ( damage != -1 ) + {//-1 == don't play pain anim + //Set our proper pain animation + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, voiceEvent ); + } + //Check to take a new enemy + if ( NPC->enemy != other && NPC != other ) + {//not already mad at them + NPC_CheckAttacker( other, mod ); + } + } + + //Attempt to run any pain instructions + if ( self->client && self->NPC ) + { + //FIXME: This needs better heuristics perhaps + if(self->health <= (self->client->ps.stats[STAT_MAX_HEALTH]/3) && G_ActivateBehavior(self, BSET_FLEE) ) + { + } + else //if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) ) + { + G_ActivateBehavior(self, BSET_PAIN); + } + } + + //Attempt to fire any paintargets we might have + if( self->paintarget && self->paintarget[0] ) + { + G_UseTargets2(self, other, self->paintarget); + } + + RestoreNPCGlobals(); +} + +/* +------------------------- +NPC_Touch +------------------------- +*/ +extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname ); +void NPC_Touch(gentity_t *self, gentity_t *other, trace_t *trace) +{ + if(!self->NPC) + return; + + SaveNPCGlobals(); + SetNPCGlobals( self ); + + if ( self->message && self->health <= 0 ) + {//I am dead and carrying a key + //if ( other && player && player->health > 0 && other == player ) + if (other && other->client && other->s.number < MAX_CLIENTS) + {//player touched me + /* + char *text; + qboolean keyTaken; + //give him my key + if ( Q_stricmp( "goodie", self->message ) == 0 ) + {//a goodie key + if ( (keyTaken = INV_GoodieKeyGive( other )) == qtrue ) + { + text = "cp @SP_INGAME_TOOK_IMPERIAL_GOODIE_KEY"; + G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_GOODIE_KEY )-bg_itemlist) ); + } + else + { + text = "cp @SP_INGAME_CANT_CARRY_GOODIE_KEY"; + } + } + else + {//a named security key + if ( (keyTaken = INV_SecurityKeyGive( player, self->message )) == qtrue ) + { + text = "cp @SP_INGAME_TOOK_IMPERIAL_SECURITY_KEY"; + G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_SECURITY_KEY )-bg_itemlist) ); + } + else + { + text = "cp @SP_INGAME_CANT_CARRY_SECURITY_KEY"; + } + } + */ + //rwwFIXMEFIXME: support for goodie/security keys? + /* + if ( keyTaken ) + {//remove my key + NPC_SetSurfaceOnOff( self, "l_arm_key", 0x00000002 ); + self->message = NULL; + //FIXME: temp pickup sound + G_Sound( player, G_SoundIndex( "sound/weapons/key_pkup.wav" ) ); + //FIXME: need some event to pass to cgame for sound/graphic/message? + } + //FIXME: temp message + gi.SendServerCommand( NULL, text ); + */ + } + } + + if ( other->client ) + {//FIXME: if pushing against another bot, both ucmd.rightmove = 127??? + //Except if not facing one another... + if ( other->health > 0 ) + { + NPCInfo->touchedByPlayer = other; + } + + if ( other == NPCInfo->goalEntity ) + { + NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL; + } + + if( /*!(self->svFlags&SVF_LOCKEDENEMY) && !(self->svFlags&SVF_IGNORE_ENEMIES) &&*/ !(other->flags & FL_NOTARGET) ) + { + if ( self->client->enemyTeam ) + {//See if we bumped into an enemy + if ( other->client->playerTeam == self->client->enemyTeam ) + {//bumped into an enemy + if( NPCInfo->behaviorState != BS_HUNT_AND_KILL && !NPCInfo->tempBehavior ) + {//MCG - Begin: checking specific BS mode here, this is bad, a HACK + //FIXME: not medics? + if ( NPC->enemy != other ) + {//not already mad at them + G_SetEnemy( NPC, other ); + } + // NPCInfo->tempBehavior = BS_HUNT_AND_KILL; + } + } + } + } + + //FIXME: do this if player is moving toward me and with a certain dist? + /* + if ( other->s.number == 0 && self->client->playerTeam == other->client->playerTeam ) + { + VectorAdd( self->client->pushVec, other->client->ps.velocity, self->client->pushVec ); + } + */ + } + else + {//FIXME: check for SVF_NONNPC_ENEMY flag here? + if ( other->health > 0 ) + { + //if ( NPC->enemy == other && (other->svFlags&SVF_NONNPC_ENEMY) ) + if (0) //rwwFIXMEFIXME: Can probably just check if num < MAX_CLIENTS for non-npc enemy stuff + { + NPCInfo->touchedByPlayer = other; + } + } + + if ( other == NPCInfo->goalEntity ) + { + NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL; + } + } + + RestoreNPCGlobals(); +} + +/* +------------------------- +NPC_TempLookTarget +------------------------- +*/ + +void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ) +{ + if ( !self->client ) + { + return; + } + + if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//lookTarget is set by and to the monster that's holding you, no other operations can change that + return; + } + + if ( !minLookTime ) + { + minLookTime = 1000; + } + + if ( !maxLookTime ) + { + maxLookTime = 1000; + } + + if ( !NPC_CheckLookTarget( self ) ) + {//Not already looking at something else + //Look at him for 1 to 3 seconds + NPC_SetLookTarget( self, lookEntNum, level.time + Q_irand( minLookTime, maxLookTime ) ); + } +} + +void NPC_Respond( gentity_t *self, int userNum ) +{ + int event = -1; + /* + + if ( Q_irand( 0, 1 ) ) + { + event = Q_irand(EV_RESPOND1, EV_RESPOND3); + } + else + { + event = Q_irand(EV_BUSY1, EV_BUSY3); + } + */ + + if ( !Q_irand( 0, 1 ) ) + {//set looktarget to them for a second or two + NPC_TempLookTarget( self, userNum, 1000, 3000 ); + } + + //some last-minute hacked in responses + switch ( self->client->NPC_class ) + { + case CLASS_JAN: + if ( self->enemy ) + { + if ( !Q_irand( 0, 2 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 2 ) ) + { + event = EV_SUSPICIOUS4; + } + else if ( !Q_irand( 0, 1 ) ) + { + event = EV_SOUND1; + } + else + { + event = EV_CONFUSE1; + } + break; + case CLASS_LANDO: + if ( self->enemy ) + { + if ( !Q_irand( 0, 2 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 6 ) ) + { + event = EV_SIGHT2; + } + else if ( !Q_irand( 0, 5 ) ) + { + event = EV_GIVEUP4; + } + else if ( Q_irand( 0, 4 ) > 1 ) + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + else + { + event = Q_irand( EV_JDETECTED1, EV_JDETECTED2 ); + } + break; + case CLASS_LUKE: + if ( self->enemy ) + { + event = EV_COVER1; + } + else + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + break; + case CLASS_JEDI: + if ( !self->enemy ) + { + /* + if ( !(self->svFlags&SVF_IGNORE_ENEMIES) + && (self->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES) + && self->client->enemyTeam == TEAM_ENEMY ) + */ + if (0) //rwwFIXMEFIXME: support flags! + { + event = Q_irand( EV_ANGER1, EV_ANGER3 ); + } + else + { + event = Q_irand( EV_TAUNT1, EV_TAUNT2 ); + } + } + break; + case CLASS_PRISONER: + if ( self->enemy ) + { + if ( Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + } + else + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + break; + case CLASS_REBEL: + if ( self->enemy ) + { + if ( !Q_irand( 0, 2 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else + { + event = Q_irand( EV_DETECTED1, EV_DETECTED5 ); + } + } + else + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + break; + case CLASS_BESPIN_COP: + if ( !Q_stricmp( "bespincop", self->NPC_type ) ) + {//variant 1 + if ( self->enemy ) + { + if ( Q_irand( 0, 9 ) > 6 ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 6 ) > 4 ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 3 ) ) + { + event = Q_irand( EV_SIGHT2, EV_SIGHT3 ); + } + else if ( !Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + else if ( !Q_irand( 0, 2 ) ) + { + event = EV_LOST1; + } + else if ( !Q_irand( 0, 1 ) ) + { + event = EV_ESCAPING2; + } + else + { + event = EV_GIVEUP4; + } + } + else + {//variant2 + if ( self->enemy ) + { + if ( Q_irand( 0, 9 ) > 6 ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 6 ) > 4 ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 3 ) ) + { + event = Q_irand( EV_SIGHT1, EV_SIGHT2 ); + } + else if ( !Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + else if ( !Q_irand( 0, 2 ) ) + { + event = EV_LOST1; + } + else if ( !Q_irand( 0, 1 ) ) + { + event = EV_GIVEUP3; + } + else + { + event = EV_CONFUSE1; + } + } + break; + case CLASS_R2D2: // droid + G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3)))); + break; + case CLASS_R5D2: // droid + G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4)))); + break; + case CLASS_MOUSE: // droid + G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3)))); + break; + case CLASS_GONK: // droid + G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2)))); + break; + } + + if ( event != -1 ) + { + //hack here because we reuse some "combat" and "extra" sounds + qboolean addFlag = (self->NPC->scriptFlags&SCF_NO_COMBAT_TALK); + self->NPC->scriptFlags &= ~SCF_NO_COMBAT_TALK; + + G_AddVoiceEvent( self, event, 3000 ); + + if ( addFlag ) + { + self->NPC->scriptFlags |= SCF_NO_COMBAT_TALK; + } + } +} + +/* +------------------------- +NPC_UseResponse +------------------------- +*/ + +void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone ) +{ + if ( !self->NPC || !self->client ) + { + return; + } + + if ( user->s.number != 0 ) + {//not used by the player + if ( useWhenDone ) + { + G_ActivateBehavior( self, BSET_USE ); + } + return; + } + + if ( user->client && self->client->playerTeam != user->client->playerTeam && self->client->playerTeam != NPCTEAM_NEUTRAL ) + {//only those on the same team react + if ( useWhenDone ) + { + G_ActivateBehavior( self, BSET_USE ); + } + return; + } + + if ( self->NPC->blockedSpeechDebounceTime > level.time ) + {//I'm not responding right now + return; + } + + /* + if ( gi.VoiceVolume[self->s.number] ) + {//I'm talking already + if ( !useWhenDone ) + {//you're not trying to use me + return; + } + } + */ + //rwwFIXMEFIXME: Support for this? + + if ( useWhenDone ) + { + G_ActivateBehavior( self, BSET_USE ); + } + else + { + NPC_Respond( self, user->s.number ); + } +} + +/* +------------------------- +NPC_Use +------------------------- +*/ +extern void Add_Batteries( gentity_t *ent, int *count ); + +void NPC_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if (self->client->ps.pm_type == PM_DEAD) + {//or just remove ->pain in player_die? + return; + } + + SaveNPCGlobals(); + SetNPCGlobals( self ); + + if(self->client && self->NPC) + { + // If this is a vehicle, let the other guy board it. Added 12/14/02 by AReis. + if ( self->client->NPC_class == CLASS_VEHICLE ) + { + Vehicle_t *pVeh = self->m_pVehicle; + + if ( pVeh && pVeh->m_pVehicleInfo ) + { + //if I used myself, eject everyone on me + if ( other == self ) + { + pVeh->m_pVehicleInfo->EjectAll( pVeh ); + } + // If other is already riding this vehicle (self), eject him. + else if ( other->s.owner == self->s.number ) + { + pVeh->m_pVehicleInfo->Eject( pVeh, (bgEntity_t *)other, qfalse ); + } + // Otherwise board this vehicle. + else + { + pVeh->m_pVehicleInfo->Board( pVeh, (bgEntity_t *)other ); + } + } + } + else if ( Jedi_WaitingAmbush( NPC ) ) + { + Jedi_Ambush( NPC ); + } + //Run any use instructions + if ( activator && activator->s.number == 0 && self->client->NPC_class == CLASS_GONK ) + { + // must be using the gonk, so attempt to give battery power. + // NOTE: this will steal up to MAX_BATTERIES for the activator, leaving the residual on the gonk for potential later use. +// Add_Batteries( activator, &self->client->ps.batteryCharge ); + //rwwFIXMEFIXME: support for this? + } + // Not using MEDICs anymore +/* + if ( self->NPC->behaviorState == BS_MEDIC_HIDE && activator->client ) + {//Heal me NOW, dammit! + if ( activator->health < activator->client->ps.stats[STAT_MAX_HEALTH] ) + {//person needs help + if ( self->NPC->eventualGoal != activator ) + {//not my current patient already + NPC_TakePatient( activator ); + G_ActivateBehavior( self, BSET_USE ); + } + } + else if ( !self->enemy && activator->s.number == 0 && !gi.VoiceVolume[self->s.number] && !(self->NPC->scriptFlags&SCF_NO_RESPONSE) ) + {//I don't have an enemy and I'm not talking and I was used by the player + NPC_UseResponse( self, other, qfalse ); + } + } +*/ +// else if ( self->behaviorSet[BSET_USE] ) + if ( self->behaviorSet[BSET_USE] ) + { + NPC_UseResponse( self, other, qtrue ); + } +// else if ( isMedic( self ) ) +// {//Heal me NOW, dammit! +// NPC_TakePatient( activator ); +// } + else if ( !self->enemy + && activator->s.number == 0 + && /*!gi.VoiceVolume[self->s.number] &&*/ !(self->NPC->scriptFlags&SCF_NO_RESPONSE) ) + //rwwFIXMEFIXME: voice volume support? + {//I don't have an enemy and I'm not talking and I was used by the player + NPC_UseResponse( self, other, qfalse ); + } + } + + RestoreNPCGlobals(); +} + +void NPC_CheckPlayerAim( void ) +{ + //FIXME: need appropriate dialogue + /* + gentity_t *player = &g_entities[0]; + + if ( player && player->client && player->client->ps.weapon > (int)(WP_NONE) && player->client->ps.weapon < (int)(WP_TRICORDER) ) + {//player has a weapon ready + if ( g_crosshairEntNum == NPC->s.number && level.time - g_crosshairEntTime < 200 + && g_crosshairSameEntTime >= 3000 && g_crosshairEntDist < 256 ) + {//if the player holds the crosshair on you for a few seconds + //ask them what the fuck they're doing + G_AddVoiceEvent( NPC, Q_irand( EV_FF_1A, EV_FF_1C ), 0 ); + } + } + */ +} + +void NPC_CheckAllClear( void ) +{ + //FIXME: need to make this happen only once after losing enemies, not over and over again + /* + if ( NPC->client && !NPC->enemy && level.time - teamLastEnemyTime[NPC->client->playerTeam] > 10000 ) + {//Team hasn't seen an enemy in 10 seconds + if ( !Q_irand( 0, 2 ) ) + { + G_AddVoiceEvent( NPC, Q_irand(EV_SETTLE1, EV_SETTLE3), 3000 ); + } + } + */ +} diff --git a/codemp/game/NPC_senses.c b/codemp/game/NPC_senses.c new file mode 100644 index 0000000..0bb6af1 --- /dev/null +++ b/codemp/game/NPC_senses.c @@ -0,0 +1,934 @@ +//NPC_senses.cpp + +#include "b_local.h" + +extern int eventClearTime; +/* +qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask) + +returns true if can see from point 1 to 2, even through glass (1 pane)- doesn't work with portals +*/ +qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask) +{ + trace_t tr; + gentity_t *hit; + + trap_Trace ( &tr, point1, NULL, NULL, point2, ignore, clipmask ); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + hit = &g_entities[ tr.entityNum ]; + if(EntIsGlass(hit)) + { + vec3_t newpoint1; + VectorCopy(tr.endpos, newpoint1); + trap_Trace (&tr, newpoint1, NULL, NULL, point2, hit->s.number, clipmask ); + + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + } + + return qfalse; +} + +/* +CanSee +determine if NPC can see an entity + +This is a straight line trace check. This function does not look at PVS or FOV, +or take any AI related factors (for example, the NPC's reaction time) into account + +FIXME do we need fat and thin version of this? +*/ +qboolean CanSee ( gentity_t *ent ) +{ + trace_t tr; + vec3_t eyes; + vec3_t spot; + + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); + + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); + trap_Trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE ); + ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_HEAD, spot ); + trap_Trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE ); + ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_LEGS, spot ); + trap_Trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE ); + ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + return qfalse; +} + +qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold ) +{ + vec3_t dir, forward, angles; + float dot; + + VectorSubtract( spot, from, dir ); + dir[2] = 0; + VectorNormalize( dir ); + + VectorCopy( fromAngles, angles ); + angles[0] = 0; + AngleVectors( angles, forward, NULL, NULL ); + + dot = DotProduct( dir, forward ); + + return (dot > threshHold); +} + +/* +InFOV + +IDEA: further off to side of FOV range, higher chance of failing even if technically in FOV, + keep core of 50% to sides as always succeeding +*/ + +//Position compares + +qboolean InFOV3( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV ) +{ + vec3_t deltaVector, angles, deltaAngles; + + VectorSubtract ( spot, from, deltaVector ); + vectoangles ( deltaVector, angles ); + + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + return qfalse; +} + +//NPC to position + +qboolean InFOV2( vec3_t origin, gentity_t *from, int hFOV, int vFOV ) +{ + vec3_t fromAngles, eyes; + + if( from->client ) + { + VectorCopy(from->client->ps.viewangles, fromAngles); + } + else + { + VectorCopy(from->s.angles, fromAngles); + } + + CalcEntitySpot( from, SPOT_HEAD, eyes ); + + return InFOV3( origin, eyes, fromAngles, hFOV, vFOV ); +} + +//Entity to entity + +qboolean InFOV ( gentity_t *ent, gentity_t *from, int hFOV, int vFOV ) +{ + vec3_t eyes; + vec3_t spot; + vec3_t deltaVector; + vec3_t angles, fromAngles; + vec3_t deltaAngles; + + if( from->client ) + { + if( !VectorCompare( from->client->renderInfo.eyeAngles, vec3_origin ) ) + {//Actual facing of tag_head! + //NOTE: Stasis aliens may have a problem with this? + VectorCopy( from->client->renderInfo.eyeAngles, fromAngles ); + } + else + { + VectorCopy( from->client->ps.viewangles, fromAngles ); + } + } + else + { + VectorCopy(from->s.angles, fromAngles); + } + + CalcEntitySpot( from, SPOT_HEAD_LEAN, eyes ); + + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); + VectorSubtract ( spot, eyes, deltaVector); + + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_HEAD, spot ); + VectorSubtract ( spot, eyes, deltaVector); + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_LEGS, spot ); + VectorSubtract ( spot, eyes, deltaVector); + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + return qfalse; +} + +qboolean InVisrange ( gentity_t *ent ) +{//FIXME: make a calculate visibility for ents that takes into account + //lighting, movement, turning, crouch/stand up, other anims, hide brushes, etc. + vec3_t eyes; + vec3_t spot; + vec3_t deltaVector; + float visrange = (NPCInfo->stats.visrange*NPCInfo->stats.visrange); + + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); + + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); + VectorSubtract ( spot, eyes, deltaVector); + + /*if(ent->client) + { + float vel, avel; + if(ent->client->ps.velocity[0] || ent->client->ps.velocity[1] || ent->client->ps.velocity[2]) + { + vel = VectorLength(ent->client->ps.velocity); + if(vel > 128) + { + visrange += visrange * (vel/256); + } + } + + if(ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + {//FIXME: shouldn't they need to have line of sight to you to detect this? + avel = VectorLength(ent->avelocity); + if(avel > 15) + { + visrange += visrange * (avel/60); + } + } + }*/ + + if(VectorLengthSquared(deltaVector) > visrange) + { + return qfalse; + } + + return qtrue; +} + +/* +NPC_CheckVisibility +*/ + +visibility_t NPC_CheckVisibility ( gentity_t *ent, int flags ) +{ + // flags should never be 0 + if ( !flags ) + { + return VIS_NOT; + } + + // check PVS + if ( flags & CHECK_PVS ) + { + if ( !trap_InPVS ( ent->r.currentOrigin, NPC->r.currentOrigin ) ) + { + return VIS_NOT; + } + } + if ( !(flags & (CHECK_360|CHECK_FOV|CHECK_SHOOT)) ) + { + return VIS_PVS; + } + + // check within visrange + if (flags & CHECK_VISRANGE) + { + if( !InVisrange ( ent ) ) + { + return VIS_PVS; + } + } + + // check 360 degree visibility + //Meaning has to be a direct line of site + if ( flags & CHECK_360 ) + { + if ( !CanSee ( ent ) ) + { + return VIS_PVS; + } + } + if ( !(flags & (CHECK_FOV|CHECK_SHOOT)) ) + { + return VIS_360; + } + + // check FOV + if ( flags & CHECK_FOV ) + { + if ( !InFOV ( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov) ) + { + return VIS_360; + } + } + + if ( !(flags & CHECK_SHOOT) ) + { + return VIS_FOV; + } + + // check shootability + if ( flags & CHECK_SHOOT ) + { + if ( !CanShoot ( ent, NPC ) ) + { + return VIS_FOV; + } + } + + return VIS_SHOOT; +} + +/* +------------------------- +NPC_CheckSoundEvents +------------------------- +*/ +static int G_CheckSoundEvents( gentity_t *self, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) +{ + int bestEvent = -1; + int bestAlert = -1; + int bestTime = -1; + int i; + float dist, radius; + + maxHearDist *= maxHearDist; + + for ( i = 0; i < level.numAlertEvents; i++ ) + { + //are we purposely ignoring this alert? + if ( i == ignoreAlert ) + continue; + //We're only concerned about sounds + if ( level.alertEvents[i].type != AET_SOUND ) + continue; + //must be at least this noticable + if ( level.alertEvents[i].level < minAlertLevel ) + continue; + //must have an owner? + if ( mustHaveOwner && !level.alertEvents[i].owner ) + continue; + //Must be within range + dist = DistanceSquared( level.alertEvents[i].position, self->r.currentOrigin ); + + //can't hear it + if ( dist > maxHearDist ) + continue; + + radius = level.alertEvents[i].radius * level.alertEvents[i].radius; + if ( dist > radius ) + continue; + + if ( level.alertEvents[i].addLight ) + {//a quiet sound, must have LOS to hear it + if ( G_ClearLOS5( self, level.alertEvents[i].position ) == qfalse ) + {//no LOS, didn't hear it + continue; + } + } + + //See if this one takes precedence over the previous one + if ( level.alertEvents[i].level >= bestAlert //higher alert level + || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer + {//NOTE: equal is better because it's later in the array + bestEvent = i; + bestAlert = level.alertEvents[i].level; + bestTime = level.alertEvents[i].timestamp; + } + } + + return bestEvent; +} + +float G_GetLightLevel( vec3_t pos, vec3_t fromDir ) +{ + /* + vec3_t ambient={0}, directed, lightDir; + + cgi_R_GetLighting( pos, ambient, directed, lightDir ); + lightLevel = VectorLength( ambient ) + (VectorLength( directed )*DotProduct( lightDir, fromDir )); + */ + float lightLevel; + //rwwFIXMEFIXME: ...this is evil. We can possibly read from the server BSP data, or load the lightmap along + //with collision data and whatnot, but is it worth it? + lightLevel = 255; + + return lightLevel; +} +/* +------------------------- +NPC_CheckSightEvents +------------------------- +*/ +static int G_CheckSightEvents( gentity_t *self, int hFOV, int vFOV, float maxSeeDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) +{ + int bestEvent = -1; + int bestAlert = -1; + int bestTime = -1; + int i; + float dist, radius; + + maxSeeDist *= maxSeeDist; + for ( i = 0; i < level.numAlertEvents; i++ ) + { + //are we purposely ignoring this alert? + if ( i == ignoreAlert ) + continue; + //We're only concerned about sounds + if ( level.alertEvents[i].type != AET_SIGHT ) + continue; + //must be at least this noticable + if ( level.alertEvents[i].level < minAlertLevel ) + continue; + //must have an owner? + if ( mustHaveOwner && !level.alertEvents[i].owner ) + continue; + + //Must be within range + dist = DistanceSquared( level.alertEvents[i].position, self->r.currentOrigin ); + + //can't see it + if ( dist > maxSeeDist ) + continue; + + radius = level.alertEvents[i].radius * level.alertEvents[i].radius; + if ( dist > radius ) + continue; + + //Must be visible + if ( InFOV2( level.alertEvents[i].position, self, hFOV, vFOV ) == qfalse ) + continue; + + if ( G_ClearLOS5( self, level.alertEvents[i].position ) == qfalse ) + continue; + + //FIXME: possibly have the light level at this point affect the + // visibility/alert level of this event? Would also + // need to take into account how bright the event + // itself is. A lightsaber would stand out more + // in the dark... maybe pass in a light level that + // is added to the actual light level at this position? + + //See if this one takes precedence over the previous one + if ( level.alertEvents[i].level >= bestAlert //higher alert level + || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer + {//NOTE: equal is better because it's later in the array + bestEvent = i; + bestAlert = level.alertEvents[i].level; + bestTime = level.alertEvents[i].timestamp; + } + } + + return bestEvent; +} + +/* +------------------------- +NPC_CheckAlertEvents + + NOTE: Should all NPCs create alertEvents too so they can detect each other? +------------------------- +*/ + +int G_CheckAlertEvents( gentity_t *self, qboolean checkSight, qboolean checkSound, float maxSeeDist, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) +{ + int bestSoundEvent = -1; + int bestSightEvent = -1; + int bestSoundAlert = -1; + int bestSightAlert = -1; + + if ( &g_entities[0] == NULL || g_entities[0].health <= 0 ) + { + //player is dead + return -1; + } + + //get sound event + bestSoundEvent = G_CheckSoundEvents( self, maxHearDist, ignoreAlert, mustHaveOwner, minAlertLevel ); + //get sound event alert level + if ( bestSoundEvent >= 0 ) + { + bestSoundAlert = level.alertEvents[bestSoundEvent].level; + } + + //get sight event + if ( self->NPC ) + { + bestSightEvent = G_CheckSightEvents( self, self->NPC->stats.hfov, self->NPC->stats.vfov, maxSeeDist, ignoreAlert, mustHaveOwner, minAlertLevel ); + } + else + { + bestSightEvent = G_CheckSightEvents( self, 80, 80, maxSeeDist, ignoreAlert, mustHaveOwner, minAlertLevel );//FIXME: look at cg_view to get more accurate numbers? + } + //get sight event alert level + if ( bestSightEvent >= 0 ) + { + bestSightAlert = level.alertEvents[bestSightEvent].level; + } + + //return the one that has a higher alert (or sound if equal) + //FIXME: This doesn't take the distance of the event into account + + if ( bestSightEvent >= 0 && bestSightAlert > bestSoundAlert ) + {//valid best sight event, more important than the sound event + //get the light level of the alert event for this checker + vec3_t eyePoint, sightDir; + //get eye point + CalcEntitySpot( self, SPOT_HEAD_LEAN, eyePoint ); + VectorSubtract( level.alertEvents[bestSightEvent].position, eyePoint, sightDir ); + level.alertEvents[bestSightEvent].light = level.alertEvents[bestSightEvent].addLight + G_GetLightLevel( level.alertEvents[bestSightEvent].position, sightDir ); + //return the sight event + return bestSightEvent; + } + //return the sound event + return bestSoundEvent; +} + +int NPC_CheckAlertEvents( qboolean checkSight, qboolean checkSound, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) +{ + return G_CheckAlertEvents( NPC, checkSight, checkSound, NPCInfo->stats.visrange, NPCInfo->stats.earshot, ignoreAlert, mustHaveOwner, minAlertLevel ); +} + +qboolean G_CheckForDanger( gentity_t *self, int alertEvent ) +{//FIXME: more bStates need to call this? + if ( alertEvent == -1 ) + { + return qfalse; + } + + if ( level.alertEvents[alertEvent].level >= AEL_DANGER ) + {//run away! + if ( !level.alertEvents[alertEvent].owner || !level.alertEvents[alertEvent].owner->client || (level.alertEvents[alertEvent].owner!=self&&level.alertEvents[alertEvent].owner->client->playerTeam!=self->client->playerTeam) ) + { + if ( self->NPC ) + { + if ( self->NPC->scriptFlags & SCF_DONT_FLEE ) + {//can't flee + return qfalse; + } + else + { + NPC_StartFlee( level.alertEvents[alertEvent].owner, level.alertEvents[alertEvent].position, level.alertEvents[alertEvent].level, 3000, 6000 ); + return qtrue; + } + } + else + { + return qtrue; + } + } + } + return qfalse; +} +qboolean NPC_CheckForDanger( int alertEvent ) +{//FIXME: more bStates need to call this? + return G_CheckForDanger( NPC, alertEvent ); +} + +/* +------------------------- +AddSoundEvent +------------------------- +*/ +qboolean RemoveOldestAlert( void ); +void AddSoundEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, qboolean needLOS ) +{ + //FIXME: Handle this in another manner? + if ( level.numAlertEvents >= MAX_ALERT_EVENTS ) + { + if ( !RemoveOldestAlert() ) + {//how could that fail? + return; + } + } + + if ( owner == NULL && alertLevel < AEL_DANGER ) //allows un-owned danger alerts + return; + + //FIXME: if owner is not a player or player ally, and there are no player allies present, + // perhaps we don't need to store the alert... unless we want the player to + // react to enemy alert events in some way? + + VectorCopy( position, level.alertEvents[ level.numAlertEvents ].position ); + + level.alertEvents[ level.numAlertEvents ].radius = radius; + level.alertEvents[ level.numAlertEvents ].level = alertLevel; + level.alertEvents[ level.numAlertEvents ].type = AET_SOUND; + level.alertEvents[ level.numAlertEvents ].owner = owner; + if ( needLOS ) + {//a very low-level sound, when check this sound event, check for LOS + level.alertEvents[ level.numAlertEvents ].addLight = 1; //will force an LOS trace on this sound + } + else + { + level.alertEvents[ level.numAlertEvents ].addLight = 0; //will force an LOS trace on this sound + } + level.alertEvents[ level.numAlertEvents ].ID = level.curAlertID++; + level.alertEvents[ level.numAlertEvents ].timestamp = level.time; + + level.numAlertEvents++; +} + +/* +------------------------- +AddSightEvent +------------------------- +*/ + +void AddSightEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, float addLight ) +{ + //FIXME: Handle this in another manner? + if ( level.numAlertEvents >= MAX_ALERT_EVENTS ) + { + if ( !RemoveOldestAlert() ) + {//how could that fail? + return; + } + } + + if ( owner == NULL && alertLevel < AEL_DANGER ) //allows un-owned danger alerts + return; + + //FIXME: if owner is not a player or player ally, and there are no player allies present, + // perhaps we don't need to store the alert... unless we want the player to + // react to enemy alert events in some way? + + VectorCopy( position, level.alertEvents[ level.numAlertEvents ].position ); + + level.alertEvents[ level.numAlertEvents ].radius = radius; + level.alertEvents[ level.numAlertEvents ].level = alertLevel; + level.alertEvents[ level.numAlertEvents ].type = AET_SIGHT; + level.alertEvents[ level.numAlertEvents ].owner = owner; + level.alertEvents[ level.numAlertEvents ].addLight = addLight; //will get added to actual light at that point when it's checked + level.alertEvents[ level.numAlertEvents ].ID = level.curAlertID++; + level.alertEvents[ level.numAlertEvents ].timestamp = level.time; + + level.numAlertEvents++; +} + +/* +------------------------- +ClearPlayerAlertEvents +------------------------- +*/ + +void ClearPlayerAlertEvents( void ) +{ + int curNumAlerts = level.numAlertEvents; + int i; + //loop through them all (max 32) + for ( i = 0; i < curNumAlerts; i++ ) + { + //see if the event is old enough to delete + if ( level.alertEvents[i].timestamp && level.alertEvents[i].timestamp + ALERT_CLEAR_TIME < level.time ) + {//this event has timed out + //drop the count + level.numAlertEvents--; + //shift the rest down + if ( level.numAlertEvents > 0 ) + {//still have more in the array + if ( (i+1) < MAX_ALERT_EVENTS ) + { + memmove( &level.alertEvents[i], &level.alertEvents[i+1], sizeof(alertEvent_t)*(MAX_ALERT_EVENTS-(i+1) ) ); + } + } + else + {//just clear this one... or should we clear the whole array? + memset( &level.alertEvents[i], 0, sizeof( alertEvent_t ) ); + } + } + } + //make sure this never drops below zero... if it does, something very very bad happened + assert( level.numAlertEvents >= 0 ); + + if ( eventClearTime < level.time ) + {//this is just a 200ms debouncer so things that generate constant alerts (like corpses and missiles) add an alert every 200 ms + eventClearTime = level.time + ALERT_CLEAR_TIME; + } +} + +qboolean RemoveOldestAlert( void ) +{ + int oldestEvent = -1, oldestTime = Q3_INFINITE; + int i; + //loop through them all (max 32) + for ( i = 0; i < level.numAlertEvents; i++ ) + { + //see if the event is old enough to delete + if ( level.alertEvents[i].timestamp < oldestTime ) + { + oldestEvent = i; + oldestTime = level.alertEvents[i].timestamp; + } + } + if ( oldestEvent != -1 ) + { + //drop the count + level.numAlertEvents--; + //shift the rest down + if ( level.numAlertEvents > 0 ) + {//still have more in the array + if ( (oldestEvent+1) < MAX_ALERT_EVENTS ) + { + memmove( &level.alertEvents[oldestEvent], &level.alertEvents[oldestEvent+1], sizeof(alertEvent_t)*(MAX_ALERT_EVENTS-(oldestEvent+1) ) ); + } + } + else + {//just clear this one... or should we clear the whole array? + memset( &level.alertEvents[oldestEvent], 0, sizeof( alertEvent_t ) ); + } + } + //make sure this never drops below zero... if it does, something very very bad happened + assert( level.numAlertEvents >= 0 ); + //return true is have room for one now + return (level.numAlertEvents hFOV ) + return 0.0f; + + return ( ( hFOV - delta ) / hFOV ); +} + +/* +------------------------- +NPC_GetVFOVPercentage +------------------------- +*/ + +float NPC_GetVFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float vFOV ) +{ + vec3_t deltaVector, angles; + float delta; + + VectorSubtract ( spot, from, deltaVector ); + + vectoangles ( deltaVector, angles ); + + delta = fabs( AngleDelta ( facing[PITCH], angles[PITCH] ) ); + + if ( delta > vFOV ) + return 0.0f; + + return ( ( vFOV - delta ) / vFOV ); +} + +#define MAX_INTEREST_DIST ( 256 * 256 ) +/* +------------------------- +NPC_FindLocalInterestPoint +------------------------- +*/ + +int G_FindLocalInterestPoint( gentity_t *self ) +{ + int i, bestPoint = ENTITYNUM_NONE; + float dist, bestDist = Q3_INFINITE; + vec3_t diffVec, eyes; + + CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes ); + for ( i = 0; i < level.numInterestPoints; i++ ) + { + //Don't ignore portals? If through a portal, need to look at portal! + if ( trap_InPVS( level.interestPoints[i].origin, eyes ) ) + { + VectorSubtract( level.interestPoints[i].origin, eyes, diffVec ); + if ( (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 < 48 && + fabs(diffVec[2]) > (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 ) + {//Too close to look so far up or down + continue; + } + dist = VectorLengthSquared( diffVec ); + //Some priority to more interesting points + //dist -= ((int)level.interestPoints[i].lookMode * 5) * ((int)level.interestPoints[i].lookMode * 5); + if ( dist < MAX_INTEREST_DIST && dist < bestDist ) + { + if ( G_ClearLineOfSight( eyes, level.interestPoints[i].origin, self->s.number, MASK_OPAQUE ) ) + { + bestDist = dist; + bestPoint = i; + } + } + } + } + if ( bestPoint != ENTITYNUM_NONE && level.interestPoints[bestPoint].target ) + { + G_UseTargets2( self, self, level.interestPoints[bestPoint].target ); + } + return bestPoint; +} + +/*QUAKED target_interest (1 0.8 0.5) (-4 -4 -4) (4 4 4) +A point that a squadmate will look at if standing still + +target - thing to fire when someone looks at this thing +*/ + +void SP_target_interest( gentity_t *self ) +{//FIXME: rename point_interest + if(level.numInterestPoints >= MAX_INTEREST_POINTS) + { + Com_Printf("ERROR: Too many interest points, limit is %d\n", MAX_INTEREST_POINTS); + G_FreeEntity(self); + return; + } + + VectorCopy(self->r.currentOrigin, level.interestPoints[level.numInterestPoints].origin); + + if(self->target && self->target[0]) + { + level.interestPoints[level.numInterestPoints].target = G_NewString( self->target ); + } + + level.numInterestPoints++; + + G_FreeEntity(self); +} diff --git a/codemp/game/NPC_sounds.c b/codemp/game/NPC_sounds.c new file mode 100644 index 0000000..2d7e041 --- /dev/null +++ b/codemp/game/NPC_sounds.c @@ -0,0 +1,93 @@ +//NPC_sounds.cpp +#include "b_local.h" +#include "../icarus/Q3_Interface.h" + +/* +void NPC_AngerSound (void) +{ + if(NPCInfo->investigateSoundDebounceTime) + return; + + NPCInfo->investigateSoundDebounceTime = 1; + +// switch((int)NPC->client->race) +// { +// case RACE_KLINGON: + //G_Sound(NPC, G_SoundIndex(va("sound/mgtest/klingon/talk%d.wav", Q_irand(1, 4)))); +// break; +// } +} +*/ + +extern void G_SpeechEvent( gentity_t *self, int event ); +void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ) +{ + if ( !self->NPC ) + { + return; + } + + if ( !self->client || self->client->ps.pm_type >= PM_DEAD ) + { + return; + } + + if ( self->NPC->blockedSpeechDebounceTime > level.time ) + { + return; + } + + if ( trap_ICARUS_TaskIDPending( self, TID_CHAN_VOICE ) ) + { + return; + } + + + if ( (self->NPC->scriptFlags&SCF_NO_COMBAT_TALK) && ( (event >= EV_ANGER1 && event <= EV_VICTORY3) || (event >= EV_CHASE1 && event <= EV_SUSPICIOUS5) ) )//(event < EV_FF_1A || event > EV_FF_3C) && (event < EV_RESPOND1 || event > EV_MISSION3) ) + { + return; + } + + if ( (self->NPC->scriptFlags&SCF_NO_ALERT_TALK) && (event >= EV_GIVEUP1 && event <= EV_SUSPICIOUS5) ) + { + return; + } + //FIXME: Also needs to check for teammates. Don't want + // everyone babbling at once + + //NOTE: was losing too many speech events, so we do it directly now, screw networking! + //G_AddEvent( self, event, 0 ); + G_SpeechEvent( self, event ); + + //won't speak again for 5 seconds (unless otherwise specified) + self->NPC->blockedSpeechDebounceTime = level.time + ((speakDebounceTime==0) ? 5000 : speakDebounceTime); +} + +void NPC_PlayConfusionSound( gentity_t *self ) +{ + if ( self->health > 0 ) + { + if ( self->enemy ||//was mad + !TIMER_Done( self, "enemyLastVisible" ) ||//saw something suspicious + self->client->renderInfo.lookTarget == 0//was looking at player + ) + { + self->NPC->blockedSpeechDebounceTime = 0;//make sure we say this + G_AddVoiceEvent( self, Q_irand( EV_CONFUSE2, EV_CONFUSE3 ), 2000 ); + } + else if ( self->NPC && self->NPC->investigateDebounceTime+self->NPC->pauseTime > level.time )//was checking something out + { + self->NPC->blockedSpeechDebounceTime = 0;//make sure we say this + G_AddVoiceEvent( self, EV_CONFUSE1, 2000 ); + } + //G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} diff --git a/codemp/game/NPC_spawn.c b/codemp/game/NPC_spawn.c new file mode 100644 index 0000000..50dfc31 --- /dev/null +++ b/codemp/game/NPC_spawn.c @@ -0,0 +1,4272 @@ +//b_spawn.cpp +//added by MCG +#include "b_local.h" +#include "anims.h" +#include "w_saber.h" +#include "bg_saga.h" +#include "bg_vehicles.h" +#include "g_nav.h" +#include "../cgame/cg_local.h" +#include "../renderer/modelmem.h" + +extern void G_DebugPrint( int level, const char *format, ... ); + +extern qboolean G_CheckInSolid (gentity_t *self, qboolean fix); +extern void ClientUserinfoChanged( int clientNum ); +extern qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ); +extern void Jedi_Cloak( gentity_t *self ); + +//extern void FX_BorgTeleport( vec3_t org ); + +extern void Q3_SetParm (int entID, int parmNum, const char *parmValue); +extern team_t TranslateTeamName( const char *name ); +extern char *TeamNames[TEAM_NUM_TEAMS]; + +//extern void CG_ShimmeryThing_Spawner( vec3_t start, vec3_t end, float radius, qboolean taper, int duration ); + +//extern void NPC_StasisSpawnEffect( gentity_t *ent ); + +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); + +extern void ST_ClearTimers( gentity_t *ent ); +extern void Jedi_ClearTimers( gentity_t *ent ); +extern void NPC_ShadowTrooper_Precache( void ); +extern void NPC_Gonk_Precache( void ); +extern void NPC_Mouse_Precache( void ); +extern void NPC_Seeker_Precache( void ); +extern void NPC_Remote_Precache( void ); +extern void NPC_R2D2_Precache(void); +extern void NPC_R5D2_Precache(void); +extern void NPC_Probe_Precache(void); +//extern void NPC_Interrogator_Precache(gentity_t *self); +extern void NPC_MineMonster_Precache( void ); +extern void NPC_Howler_Precache( void ); +extern void NPC_ATST_Precache(void); +extern void NPC_Sentry_Precache(void); +//extern void NPC_Mark1_Precache(void); +//extern void NPC_Mark2_Precache(void); +//extern void NPC_GalakMech_Precache( void ); +//extern void NPC_GalakMech_Init( gentity_t *ent ); +extern void NPC_Protocol_Precache( void ); +extern void Boba_Precache( void ); +extern void NPC_Wampa_Precache( void ); +gentity_t *NPC_SpawnType( gentity_t *ent, char *npc_type, char *targetname, qboolean isVehicle ); + +extern void Rancor_SetBolts( gentity_t *self ); +extern void Wampa_SetBolts( gentity_t *self ); + +#define NSF_DROP_TO_FLOOR 16 + +// PAIN functions... +// +extern void funcBBrushPain (gentity_t *self, gentity_t *attacker, int damage); +extern void misc_model_breakable_pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void station_pain (gentity_t *self, gentity_t *attacker, int damage); +extern void func_usable_pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_ATST_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_ST_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Jedi_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Droid_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Probe_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_MineMonster_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Howler_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Seeker_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Remote_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void emplaced_gun_pain (gentity_t *self, gentity_t *attacker, int damage); +//extern void NPC_Mark1_Pain (gentity_t *self, gentity_t *attacker, int damage); +//extern void NPC_GM_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Sentry_Pain (gentity_t *self, gentity_t *attacker, int damage); +//extern void NPC_Mark2_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void PlayerPain (gentity_t *self, gentity_t *attacker, int damage); +extern void GasBurst (gentity_t *self, gentity_t *attacker, int damage); +extern void CrystalCratePain (gentity_t *self, gentity_t *attacker, int damage); +extern void TurretPain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Wampa_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Rancor_Pain (gentity_t *self, gentity_t *attacker, int damage); + + +//void HirogenAlpha_Precache( void ); + +int WP_SetSaberModel( gclient_t *client, class_t npcClass ) +{ + //rwwFIXMEFIXME: Do something here, need to let the client know. + return 1; +} + +/* +------------------------- +NPC_PainFunc +------------------------- +*/ +typedef void (PAIN_FUNC) (gentity_t *self, gentity_t *attacker, int damage); + +PAIN_FUNC *NPC_PainFunc( gentity_t *ent ) +{ + void (*func)(gentity_t *self, gentity_t *attacker, int damage); + + if ( ent->client->ps.weapon == WP_SABER ) + { + func = NPC_Jedi_Pain; + } + else + { + // team no longer indicates race/species, use NPC_class to determine different npc types + /* + switch ( ent->client->playerTeam ) + { + default: + func = painF_NPC_Pain; + break; + } + */ + switch( ent->client->NPC_class ) + { + // troopers get special pain + case CLASS_STORMTROOPER: + case CLASS_SWAMPTROOPER: + func = NPC_ST_Pain; + break; + + case CLASS_SEEKER: + func = NPC_Seeker_Pain; + break; + + case CLASS_REMOTE: + func = NPC_Remote_Pain; + break; + + case CLASS_MINEMONSTER: + func = NPC_MineMonster_Pain; + break; + + case CLASS_HOWLER: + func = NPC_Howler_Pain; + break; + + // all other droids, did I miss any? + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + case CLASS_MOUSE: + case CLASS_PROTOCOL: + case CLASS_INTERROGATOR: + func = NPC_Droid_Pain; + break; + case CLASS_PROBE: + func = NPC_Probe_Pain; + break; + + case CLASS_SENTRY: + func = NPC_Sentry_Pain; + break; + case CLASS_MARK1: + assert( 0 ); +// func = NPC_Mark1_Pain; + break; + case CLASS_MARK2: + assert( 0 ); +// func = NPC_Mark2_Pain; + break; + case CLASS_ATST: + assert( 0 ); +// func = NPC_ATST_Pain; + break; + case CLASS_GALAKMECH: + assert( 0 ); +// func = NPC_GM_Pain; + break; + case CLASS_RANCOR: + func = NPC_Rancor_Pain; + break; + case CLASS_WAMPA: + func = NPC_Wampa_Pain; + break; + // everyone else gets the normal pain func + default: + func = NPC_Pain; + break; + } + + } + + return func; +} + + +/* +------------------------- +NPC_TouchFunc +------------------------- +*/ +typedef void (TOUCH_FUNC) (gentity_t *self, gentity_t *other, trace_t *trace); + +TOUCH_FUNC *NPC_TouchFunc( gentity_t *ent ) +{ + void (*func)(gentity_t *self, gentity_t *other, trace_t *trace); + + func = NPC_Touch; + + return func; +} + +/* +------------------------- +NPC_SetMiscDefaultData +------------------------- +*/ + +extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum ); +void NPC_SetMiscDefaultData( gentity_t *ent ) +{ + if ( ent->spawnflags & SFB_CINEMATIC ) + {//if a cinematic guy, default us to wait bState + ent->NPC->behaviorState = BS_CINEMATIC; + } + if ( ent->client->NPC_class == CLASS_BOBAFETT ) + {//set some stuff, precache + Boba_Precache(); + ent->client->ps.fd.forcePowersKnown |= ( 1 << FP_LEVITATION ); + ent->client->ps.fd.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3; + ent->client->ps.fd.forcePower = 100; + ent->NPC->scriptFlags |= (SCF_ALT_FIRE|SCF_NO_GROUPS); + } + //if ( !Q_stricmp( "atst_vehicle", ent->NPC_type ) )//FIXME: has to be a better, easier way to tell this, no? + if (ent->s.NPC_class == CLASS_VEHICLE && ent->m_pVehicle) + { + ent->s.g2radius = 255;//MAX for this value, was (ent->r.maxs[2]-ent->r.mins[2]), which is 272 or something + + if (ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER) + { + ent->mass = 2000;//??? + ent->flags |= (FL_SHIELDED|FL_NO_KNOCKBACK); + ent->pain = NPC_ATST_Pain; + } + //turn the damn hatch cover on and LEAVE it on + trap_G2API_SetSurfaceOnOff( ent->ghoul2, "head_hatchcover", 0/*TURN_ON*/ ); + } + if ( !Q_stricmp( "wampa", ent->NPC_type ) ) + {//FIXME: extern this into NPC.cfg? + Wampa_SetBolts( ent ); + ent->s.g2radius = 80;//??? + ent->mass = 300;//??? + ent->flags |= FL_NO_KNOCKBACK; + ent->pain = NPC_Wampa_Pain; + } + if ( ent->client->NPC_class == CLASS_RANCOR ) + { + Rancor_SetBolts( ent ); + ent->s.g2radius = 255;//MAX for this value, was (ent->r.maxs[2]-ent->r.mins[2]), which is 272 or something + ent->mass = 1000;//??? + ent->flags |= FL_NO_KNOCKBACK; + ent->pain = NPC_Rancor_Pain; + ent->health *= 4; + } + if ( !Q_stricmp( "Yoda", ent->NPC_type ) ) + {//FIXME: extern this into NPC.cfg? + ent->NPC->scriptFlags |= SCF_NO_FORCE;//force powers don't work on him + } + if ( !Q_stricmp( "emperor", ent->NPC_type ) ) + {//FIXME: extern this into NPC.cfg? + ent->NPC->scriptFlags |= SCF_DONT_FIRE;//so he uses only force powers + } + //================== +// if ( ent->client->ps.saber[0].type != SABER_NONE ) + if (ent->client->ps.weapon == WP_SABER) //rwwFIXMEFIXME: is this going to work? + {//if I'm equipped with a saber, initialize it (them) + // ent->client->ps.SaberDeactivate(); + // ent->client->ps.SetSaberLength( 0 ); + WP_SaberInitBladeData( ent ); + ent->client->ps.saberHolstered = 2; + // G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saber[0].model, ent->handRBolt, 0 ); + // if ( ent->client->ps.dualSabers ) + // { + // G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saber[1].model, ent->handLBolt, 1 ); + // } + Jedi_ClearTimers( ent ); + } + if ( ent->client->ps.fd.forcePowersKnown != 0 ) + { + WP_InitForcePowers( ent ); + WP_SpawnInitForcePowers(ent); //rww + } + if ( ent->client->NPC_class == CLASS_SEEKER ) + { + ent->NPC->defaultBehavior = BS_DEFAULT; + ent->client->ps.gravity = 0; + ent->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY; + ent->client->ps.eFlags2 |= EF2_FLYING; + ent->count = 30; // SEEKER shot ammo count + } + //***I'm not sure whether I should leave this as a TEAM_ switch, I think NPC_class may be more appropriate - dmv + switch(ent->client->playerTeam) + { + case NPCTEAM_PLAYER: + //ent->flags |= FL_NO_KNOCKBACK; + if ( ent->client->NPC_class == CLASS_JEDI || ent->client->NPC_class == CLASS_LUKE ) + {//good jedi + ent->client->enemyTeam = NPCTEAM_ENEMY; + if ( ent->spawnflags & JSF_AMBUSH ) + {//ambusher + ent->NPC->scriptFlags |= SCF_IGNORE_ALERTS; + ent->client->noclip = qtrue;//hang + } + } + else + { + /* + if (ent->client->ps.weapon != WP_NONE) + { + G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 ); + } + */ + switch ( ent->client->ps.weapon ) + { + case WP_BRYAR_PISTOL://FIXME: new weapon: imp blaster pistol + // case WP_BLASTER_PISTOL: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_REPEATER: + case WP_DEMP2: + case WP_FLECHETTE: + case WP_ROCKET_LAUNCHER: + default: + break; + case WP_THERMAL: + case WP_BLASTER: + //FIXME: health in NPCs.cfg, and not all blaster users are stormtroopers + //ent->health = 25; + //FIXME: not necc. a ST + ST_ClearTimers( ent ); + if ( ent->NPC->rank >= RANK_LT || ent->client->ps.weapon == WP_THERMAL ) + {//officers, grenade-throwers use alt-fire + //ent->health = 50; + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + break; + } + } + if ( ent->client->NPC_class == CLASS_KYLE || ent->client->NPC_class == CLASS_VEHICLE || (ent->spawnflags & SFB_CINEMATIC) ) + { + ent->NPC->defaultBehavior = BS_CINEMATIC; + } + else + { + /* + ent->NPC->defaultBehavior = BS_FOLLOW_LEADER; + ent->client->leader = &g_entities[0]; + */ + } + break; + + case NPCTEAM_NEUTRAL: + + if ( Q_stricmp( ent->NPC_type, "gonk" ) == 0 ) + { + // I guess we generically make them player usable + ent->r.svFlags |= SVF_PLAYER_USABLE; + + // Not even sure if we want to give different levels of batteries? ...Or even that these are the values we'd want to use. + /* + switch ( g_spskill.integer ) + { + case 0: // EASY + ent->client->ps.batteryCharge = MAX_BATTERIES * 0.8f; + break; + case 1: // MEDIUM + ent->client->ps.batteryCharge = MAX_BATTERIES * 0.75f; + break; + default : + case 2: // HARD + ent->client->ps.batteryCharge = MAX_BATTERIES * 0.5f; + break; + }*/ + //rwwFIXMEFIXME: Make use of this. + } + break; + + case NPCTEAM_ENEMY: + { + ent->NPC->defaultBehavior = BS_DEFAULT; + if ( ent->client->NPC_class == CLASS_SHADOWTROOPER ) + {//FIXME: a spawnflag? + Jedi_Cloak( ent ); + } + if( ent->client->NPC_class == CLASS_TAVION || + ent->client->NPC_class == CLASS_REBORN || + ent->client->NPC_class == CLASS_DESANN || + ent->client->NPC_class == CLASS_SHADOWTROOPER ) + { + ent->client->enemyTeam = NPCTEAM_PLAYER; + if ( ent->spawnflags & JSF_AMBUSH ) + {//ambusher + ent->NPC->scriptFlags |= SCF_IGNORE_ALERTS; + ent->client->noclip = qtrue;//hang + } + } + else if( ent->client->NPC_class == CLASS_PROBE || ent->client->NPC_class == CLASS_REMOTE || + ent->client->NPC_class == CLASS_INTERROGATOR || ent->client->NPC_class == CLASS_SENTRY) + { + ent->NPC->defaultBehavior = BS_DEFAULT; + ent->client->ps.gravity = 0; + ent->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY; + ent->client->ps.eFlags2 |= EF2_FLYING; + } + else + { + // G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 ); + switch ( ent->client->ps.weapon ) + { + case WP_BRYAR_PISTOL: + break; + // case WP_BLASTER_PISTOL: + // break; + case WP_DISRUPTOR: + //Sniper + //ent->NPC->scriptFlags |= SCF_ALT_FIRE;//FIXME: use primary fire sometimes? Up close? Different class of NPC? + break; + case WP_BOWCASTER: + break; + case WP_REPEATER: + //machine-gunner + break; + case WP_DEMP2: + break; + case WP_FLECHETTE: + //shotgunner + if ( !Q_stricmp( "stofficeralt", ent->NPC_type ) ) + { + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + break; + case WP_ROCKET_LAUNCHER: + break; + case WP_THERMAL: + //Gran, use main, bouncy fire +// ent->NPC->scriptFlags |= SCF_ALT_FIRE; + break; + case WP_STUN_BATON: + break; + default: + case WP_BLASTER: + //FIXME: health in NPCs.cfg, and not all blaster users are stormtroopers + //FIXME: not necc. a ST + ST_ClearTimers( ent ); + if ( ent->NPC->rank >= RANK_COMMANDER ) + {//commanders use alt-fire + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + if ( !Q_stricmp( "rodian2", ent->NPC_type ) ) + { + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + break; + } + if ( !Q_stricmp( "galak_mech", ent->NPC_type ) ) + {//starts with armor + assert( 0 ); +// NPC_GalakMech_Init( ent ); + } + } + } + break; + + default: + break; + } + + + if ( ent->client->NPC_class == CLASS_SEEKER + && ent->activator ) + {//assume my teams are already set correctly + } + else + { + //for siege, want "bad" npc's allied with the "bad" team + if (g_gametype.integer == GT_SIEGE && ent->s.NPC_class != CLASS_VEHICLE) + { + if (ent->client->enemyTeam == NPCTEAM_PLAYER) + { + ent->client->sess.sessionTeam = SIEGETEAM_TEAM1; + } + else if (ent->client->enemyTeam == NPCTEAM_ENEMY) + { + ent->client->sess.sessionTeam = SIEGETEAM_TEAM2; + } + else + { + ent->client->sess.sessionTeam = TEAM_FREE; + } + } + } + + if ( ent->client->NPC_class == CLASS_ATST || ent->client->NPC_class == CLASS_MARK1 ) // chris/steve/kevin requested that the mark1 be shielded also + { + ent->flags |= (FL_SHIELDED|FL_NO_KNOCKBACK); + } +} + +/* +------------------------- +NPC_WeaponsForTeam +------------------------- +*/ + +int NPC_WeaponsForTeam( team_t team, int spawnflags, const char *NPC_type ) +{ + //*** not sure how to handle this, should I pass in class instead of team and go from there? - dmv + switch(team) + { + // no longer exists +// case TEAM_BORG: +// break; + +// case TEAM_HIROGEN: +// if( Q_stricmp( "hirogenalpha", NPC_type ) == 0 ) +// return ( 1 << WP_BLASTER); + //Falls through + +// case TEAM_KLINGON: + + //NOTENOTE: Falls through + +// case TEAM_IMPERIAL: + case NPCTEAM_ENEMY: + if ( Q_stricmp( "tavion", NPC_type ) == 0 || + Q_strncmp( "reborn", NPC_type, 6 ) == 0 || + Q_stricmp( "desann", NPC_type ) == 0 || + Q_strncmp( "shadowtrooper", NPC_type, 13 ) == 0 ) + return ( 1 << WP_SABER); +// return ( 1 << WP_IMPERIAL_BLADE); + //NOTENOTE: Falls through if not a knife user + +// case TEAM_SCAVENGERS: +// case TEAM_MALON: + //FIXME: default weapon in npc config? + if ( Q_strncmp( "stofficer", NPC_type, 9 ) == 0 ) + { + return ( 1 << WP_FLECHETTE); + } + if ( Q_stricmp( "stcommander", NPC_type ) == 0 ) + { + return ( 1 << WP_REPEATER); + } + if ( Q_stricmp( "swamptrooper", NPC_type ) == 0 ) + { + return ( 1 << WP_FLECHETTE); + } + if ( Q_stricmp( "swamptrooper2", NPC_type ) == 0 ) + { + return ( 1 << WP_REPEATER); + } + if ( Q_stricmp( "rockettrooper", NPC_type ) == 0 ) + { + return ( 1 << WP_ROCKET_LAUNCHER); + } + if ( Q_strncmp( "shadowtrooper", NPC_type, 13 ) == 0 ) + { + return ( 1 << WP_SABER);//|( 1 << WP_RAPID_CONCUSSION)? + } + if ( Q_stricmp( "imperial", NPC_type ) == 0 ) + { + //return ( 1 << WP_BLASTER_PISTOL); + return ( 1 << WP_BLASTER); + } + if ( Q_strncmp( "impworker", NPC_type, 9 ) == 0 ) + { + //return ( 1 << WP_BLASTER_PISTOL); + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "stormpilot", NPC_type ) == 0 ) + { + //return ( 1 << WP_BLASTER_PISTOL); + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "galak", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "galak_mech", NPC_type ) == 0 ) + { + return ( 1 << WP_REPEATER); + } + if ( Q_strncmp( "ugnaught", NPC_type, 8 ) == 0 ) + { + return WP_NONE; + } + if ( Q_stricmp( "granshooter", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "granboxer", NPC_type ) == 0 ) + { + return ( 1 << WP_STUN_BATON); + } + if ( Q_strncmp( "gran", NPC_type, 4 ) == 0 ) + { + return (( 1 << WP_THERMAL)|( 1 << WP_STUN_BATON)); + } + if ( Q_stricmp( "rodian", NPC_type ) == 0 ) + { + return ( 1 << WP_DISRUPTOR); + } + if ( Q_stricmp( "rodian2", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + + if (( Q_stricmp( "interrogator",NPC_type) == 0) || ( Q_stricmp( "sentry",NPC_type) == 0) || (Q_strncmp( "protocol",NPC_type,8) == 0) ) + { + return WP_NONE; + } + + if ( Q_strncmp( "weequay", NPC_type, 7 ) == 0 ) + { + return ( 1 << WP_BOWCASTER);//|( 1 << WP_STAFF )(FIXME: new weap?) + } + if ( Q_stricmp( "impofficer", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "impcommander", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if (( Q_stricmp( "probe", NPC_type ) == 0 ) || ( Q_stricmp( "seeker", NPC_type ) == 0 )) + { + //return ( 1 << WP_BOT_LASER); + return 0; + } + if ( Q_stricmp( "remote", NPC_type ) == 0 ) + { + //return ( 1 << WP_BOT_LASER ); + return 0; + } + if ( Q_stricmp( "trandoshan", NPC_type ) == 0 ) + { + return (1<client->playerTeam, ent->spawnflags, ent->NPC_type ); + + ent->client->ps.stats[STAT_WEAPONS] = 0; + for ( curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ ) + { + if ( (weapons & ( 1 << curWeap )) ) + { + ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << curWeap ); +// RegisterItem( FindItemForWeapon( (weapon_t)(curWeap) ) ); //precache the weapon + //rwwFIXMEFIXME: Precache + ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[curWeap].ammoIndex] = 100;//FIXME: max ammo + + if ( bestWeap == WP_SABER ) + { + // still want to register other weapons -- force saber to be best weap + continue; + } + + if ( curWeap == WP_STUN_BATON ) + { + if ( bestWeap == WP_NONE ) + {// We'll only consider giving Melee since we haven't found anything better yet. + bestWeap = curWeap; + } + } + else if ( curWeap > bestWeap || bestWeap == WP_STUN_BATON ) + { + // This will never override saber as best weap. Also will override WP_STUN_BATON if something better comes later in the list + bestWeap = curWeap; + } + } + } + + ent->client->ps.weapon = bestWeap; +} + +/* +------------------------- +NPC_SpawnEffect + + NOTE: Make sure any effects called here have their models, tga's and sounds precached in + CG_RegisterNPCEffects in cg_player.cpp +------------------------- +*/ + +void NPC_SpawnEffect (gentity_t *ent) +{ +} + +//-------------------------------------------------------------- +// NPC_SetFX_SpawnStates +// +// Set up any special parms for spawn effects +//-------------------------------------------------------------- +void NPC_SetFX_SpawnStates( gentity_t *ent ) +{ + if ( !(ent->NPC->aiFlags&NPCAI_CUSTOM_GRAVITY) ) + { + ent->client->ps.gravity = g_gravity.value; + } +} + +/* +================ +NPC_SpotWouldTelefrag + +================ +*/ +qboolean NPC_SpotWouldTelefrag( gentity_t *npc ) +{ + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( npc->r.currentOrigin, npc->r.mins, mins ); + VectorAdd( npc->r.currentOrigin, npc->r.maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + if (hit->inuse + && hit->client + && hit->s.number != npc->s.number + && (hit->r.contents&MASK_NPCSOLID) + && hit->s.number != npc->r.ownerNum + && hit->r.ownerNum != npc->s.number) + { + return qtrue; + } + + } + + return qfalse; +} + +//-------------------------------------------------------------- +void NPC_Begin (gentity_t *ent) +{ + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + usercmd_t ucmd; + gentity_t *spawnPoint = NULL; + + memset( &ucmd, 0, sizeof( ucmd ) ); + + if ( !(ent->spawnflags & SFB_NOTSOLID) ) + {//No NPCs should telefrag + if (NPC_SpotWouldTelefrag(ent)) + { + if ( ent->wait < 0 ) + {//remove yourself + G_DebugPrint( WL_DEBUG, "NPC %s could not spawn, firing target3 (%s) and removing self\n", ent->targetname, ent->target3 ); + //Fire off our target3 + G_UseTargets2( ent, ent, ent->target3 ); + + //Kill us + ent->think = G_FreeEntity; + ent->nextthink = level.time + 100; + } + else + { + G_DebugPrint( WL_DEBUG, "NPC %s could not spawn, waiting %4.2 secs to try again\n", ent->targetname, ent->wait/1000.0f ); + ent->think = NPC_Begin; + ent->nextthink = level.time + ent->wait;//try again in half a second + } + return; + } + } + //Spawn effect + NPC_SpawnEffect( ent ); + + VectorCopy( ent->client->ps.origin, spawn_origin); + VectorCopy( ent->s.angles, spawn_angles); + spawn_angles[YAW] = ent->NPC->desiredYaw; + + client = ent->client; + + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + + client->airOutTime = level.time + 12000; + + client->ps.clientNum = ent->s.number; + // clear entity values + + if ( ent->health ) // Was health supplied in map + { + client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = ent->health; + } + else if ( ent->NPC->stats.health ) // Was health supplied in NPC.cfg? + { + + if ( ent->client->NPC_class != CLASS_REBORN + && ent->client->NPC_class != CLASS_SHADOWTROOPER + //&& ent->client->NPC_class != CLASS_TAVION + //&& ent->client->NPC_class != CLASS_DESANN + && ent->client->NPC_class != CLASS_JEDI ) + {// up everyone except jedi + ent->NPC->stats.health += ent->NPC->stats.health/4 * g_spskill.integer; // 100% on easy, 125% on medium, 150% on hard + } + + client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = ent->NPC->stats.health; + } + else + { + client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = 100; + } + + if ( !Q_stricmp( "rodian", ent->NPC_type ) ) + {//sniper + //NOTE: this will get overridden by any aim settings in their spawnscripts + switch ( g_spskill.integer ) + { + case 0: + ent->NPC->stats.aim = 1; + break; + case 1: + ent->NPC->stats.aim = Q_irand( 2, 3 ); + break; + case 2: + ent->NPC->stats.aim = Q_irand( 3, 4 ); + break; + } + } + else if ( ent->client->NPC_class == CLASS_STORMTROOPER + || ent->client->NPC_class == CLASS_SWAMPTROOPER + || ent->client->NPC_class == CLASS_IMPWORKER + || !Q_stricmp( "rodian2", ent->NPC_type ) ) + {//tweak yawspeed for these NPCs based on difficulty + switch ( g_spskill.integer ) + { + case 0: + ent->NPC->stats.yawSpeed *= 0.75f; + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + { + ent->NPC->stats.aim -= Q_irand( 3, 6 ); + } + break; + case 1: + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + { + ent->NPC->stats.aim -= Q_irand( 2, 4 ); + } + break; + case 2: + ent->NPC->stats.yawSpeed *= 1.5f; + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + { + ent->NPC->stats.aim -= Q_irand( 0, 2 ); + } + break; + } + } + else if ( ent->client->NPC_class == CLASS_REBORN + || ent->client->NPC_class == CLASS_SHADOWTROOPER ) + { + switch ( g_spskill.integer ) + { + case 1: + ent->NPC->stats.yawSpeed *= 1.25f; + break; + case 2: + ent->NPC->stats.yawSpeed *= 1.5f; + break; + } + } + + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->mass = 10; + ent->takedamage = qtrue; + ent->inuse = qtrue; + ent->classname = "NPC"; +// if ( ent->client->race == RACE_HOLOGRAM ) +// {//can shoot through holograms, but not walk through them +// ent->contents = CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_ITEM;//contents_corspe to make them show up in ID and use traces +// ent->clipmask = MASK_NPCSOLID; +// } else + if(!(ent->spawnflags & SFB_NOTSOLID)) + { + ent->r.contents = CONTENTS_BODY; + ent->clipmask = MASK_NPCSOLID; + } + else + { + ent->r.contents = 0; + ent->clipmask = MASK_NPCSOLID&~CONTENTS_BODY; + } + /* + if(!ent->client->moveType)//Static? + { + ent->client->moveType = MT_RUNJUMP; + } + */ + //rwwFIXMEFIXME: movetype support + + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->client->ps.rocketLockIndex = ENTITYNUM_NONE; + ent->client->ps.rocketLockTime = 0; + + //visible to player and NPCs + if ( ent->client->NPC_class != CLASS_R2D2 && + ent->client->NPC_class != CLASS_R5D2 && + ent->client->NPC_class != CLASS_MOUSE && + ent->client->NPC_class != CLASS_GONK && + ent->client->NPC_class != CLASS_PROTOCOL ) + { + ent->flags &= ~FL_NOTARGET; + } + ent->s.eFlags &= ~EF_NODRAW; + + NPC_SetFX_SpawnStates( ent ); + + //client->ps.friction = 6; + //rwwFIXMEFIXME: per ent friction? + + if ( ent->client->ps.weapon == WP_NONE ) + {//not set by the NPCs.cfg + NPC_SetWeapons(ent); + } + //select the weapon + ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[ent->client->ps.weapon].ammoIndex]; + ent->client->ps.weaponstate = WEAPON_IDLE; + ChangeWeapon( ent, ent->client->ps.weapon ); + + VectorCopy( spawn_origin, client->ps.origin ); + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + // clear entity state values + //ent->s.eType = ET_PLAYER; + ent->s.eType = ET_NPC; +// ent->s.skinNum = ent - g_entities - 1; // used as index to get custom models + + VectorCopy (spawn_origin, ent->s.origin); +// ent->s.origin[2] += 1; // make sure off ground + + SetClientViewAngle( ent, spawn_angles ); + client->renderInfo.lookTarget = ENTITYNUM_NONE; + + if(!(ent->spawnflags & 64)) + { + G_KillBox( ent ); + trap_LinkEntity (ent); + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + client->inactivityTime = level.time + g_inactivity.value * 1000; + client->latched_buttons = 0; + if ( ent->s.m_iVehicleNum ) + {//I'm an NPC in a vehicle (or a vehicle), I already have owner set + } + else if ( client->NPC_class == CLASS_SEEKER && ent->activator != NULL ) + {//somebody else "owns" me + ent->s.owner = ent->r.ownerNum = ent->activator->s.number; + } + else + { + ent->s.owner = ENTITYNUM_NONE; + } + + // set default animations + if ( ent->client->NPC_class != CLASS_VEHICLE ) + { + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_NORMAL ); + } + + if( spawnPoint ) + { + // fire the targets of the spawn point + G_UseTargets( spawnPoint, ent ); + } + + //ICARUS include + trap_ICARUS_InitEnt( ent ); + +//==NPC initialization + SetNPCGlobals( ent ); + + ent->enemy = NULL; + NPCInfo->timeOfDeath = 0; + NPCInfo->shotTime = 0; + NPC_ClearGoal(); + NPC_ChangeWeapon( ent->client->ps.weapon ); + +//==Final NPC initialization + ent->pain = NPC_PainFunc( ent ); //painF_NPC_Pain; + ent->touch = NPC_TouchFunc( ent ); //touchF_NPC_Touch; +// ent->NPC->side = 1; + + ent->client->ps.ping = ent->NPC->stats.reactions * 50; + + //MCG - Begin: NPC hacks + //FIXME: Set the team correctly + if (ent->s.NPC_class != CLASS_VEHICLE || g_gametype.integer != GT_SIEGE) + { + ent->client->ps.persistant[PERS_TEAM] = ent->client->playerTeam; + } + + ent->use = NPC_Use; + ent->think = NPC_Think; + ent->nextthink = level.time + FRAMETIME + Q_irand(0, 100); + + NPC_SetMiscDefaultData( ent ); + if ( ent->health <= 0 ) + { + //ORIGINAL ID: health will count down towards max_health + ent->health = client->ps.stats[STAT_HEALTH] = ent->client->pers.maxHealth; + } + else + { + client->ps.stats[STAT_HEALTH] = ent->health; + } + + if (ent->s.shouldtarget) + { + ent->maxHealth = ent->health; + G_ScaleNetHealth(ent); + } + + ChangeWeapon( ent, ent->client->ps.weapon );//yes, again... sigh + + if ( !(ent->spawnflags & SFB_STARTINSOLID) ) + {//Not okay to start in solid + G_CheckInSolid( ent, qtrue ); + } + VectorClear( ent->NPC->lastClearOrigin ); + + //Run a script if you have one assigned to you + if ( G_ActivateBehavior( ent, BSET_SPAWN ) ) + { + trap_ICARUS_MaintainTaskManager(ent->s.number); + } + + VectorCopy( ent->r.currentOrigin, ent->client->renderInfo.eyePoint ); + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + memset( &ucmd, 0, sizeof( ucmd ) ); + //_VectorCopy( client->pers.cmd_angles, ucmd.angles ); + VectorCopy(client->pers.cmd.angles, ucmd.angles); + + ent->client->ps.groundEntityNum = ENTITYNUM_NONE; + + if ( ent->NPC->aiFlags & NPCAI_MATCHPLAYERWEAPON ) + { + //G_MatchPlayerWeapon( ent ); + //rwwFIXMEFIXME: Use this? Probably doesn't really matter for MP. + } + + ClientThink( ent->s.number, &ucmd ); + + trap_LinkEntity( ent ); + + if ( ent->client->playerTeam == NPCTEAM_ENEMY ) + {//valid enemy spawned + if ( !(ent->spawnflags&SFB_CINEMATIC) && ent->NPC->behaviorState != BS_CINEMATIC ) + {//not a cinematic enemy + if ( g_entities[0].client ) + { + //g_entities[0].client->sess.missionStats.enemiesSpawned++; + //rwwFIXMEFIXME: Do something here? And this is a rather strange place to be storing + //this sort of data. + } + } + } + ent->waypoint = ent->NPC->homeWp = WAYPOINT_NONE; + + if ( ent->m_pVehicle ) + {//a vehicle + //check for droidunit + if ( ent->m_pVehicle->m_iDroidUnitTag != -1 ) + { + char *droidNPCType = NULL; + gentity_t *droidEnt = NULL; + if ( ent->model2 + && ent->model2[0] ) + {//specified on the NPC_Vehicle spawner ent + droidNPCType = ent->model2; + } + else if ( ent->m_pVehicle->m_pVehicleInfo->droidNPC + && ent->m_pVehicle->m_pVehicleInfo->droidNPC[0] ) + {//specified in the vehicle's .veh file + droidNPCType = ent->m_pVehicle->m_pVehicleInfo->droidNPC; + } + + if ( droidNPCType != NULL ) + { + if ( Q_stricmp( "random", droidNPCType ) == 0 + || Q_stricmp( "default", droidNPCType ) == 0 ) + {//use default - R2D2 or R5D2 + if ( Q_irand( 0, 1 ) ) + { + droidNPCType = "r2d2"; + } + else + { + droidNPCType = "r5d2"; + } + } + droidEnt = NPC_SpawnType( ent, droidNPCType, NULL, qfalse ); + if ( droidEnt != NULL ) + { + if ( droidEnt->client ) + { + droidEnt->client->ps.m_iVehicleNum = + droidEnt->s.m_iVehicleNum = + //droidEnt->s.otherEntityNum2 = + droidEnt->s.owner = + droidEnt->r.ownerNum = ent->s.number; + ent->m_pVehicle->m_pDroidUnit = (bgEntity_t *)droidEnt; + //SP way: + //droidEnt->s.m_iVehicleNum = ent->s.number; + //droidEnt->owner = ent; + VectorCopy( ent->r.currentOrigin, droidEnt->s.origin ); + VectorCopy( ent->r.currentOrigin, droidEnt->client->ps.origin ); + G_SetOrigin( droidEnt, droidEnt->s.origin ); + trap_LinkEntity( droidEnt ); + VectorCopy( ent->r.currentAngles, droidEnt->s.angles ); + G_SetAngles( droidEnt, droidEnt->s.angles ); + if ( droidEnt->NPC ) + { + droidEnt->NPC->desiredYaw = droidEnt->s.angles[YAW]; + droidEnt->NPC->desiredPitch = droidEnt->s.angles[PITCH]; + } + droidEnt->flags |= FL_UNDYING; + } + else + {//wtf? + G_FreeEntity( droidEnt ); + } + } + } + } + } +} + +gNPC_t *gNPCPtrs[MAX_GENTITIES]; + +gNPC_t *New_NPC_t(int entNum) +{ + gNPC_t *ptr; + + if (!gNPCPtrs[entNum]) + { + gNPCPtrs[entNum] = (gNPC_t *)BG_Alloc (sizeof(gNPC_t)); + } + + ptr = gNPCPtrs[entNum]; + + if (ptr) + { + // clear it... + // + memset(ptr, 0, sizeof( *ptr ) ); + } + + return ptr; +} + +#ifdef _XBOX +void NPC_NPCPtrsClear(void) +{ + for(int i=0; iownername ) + { + ent->parent = G_Find( NULL, FOFS( targetname ), ent->ownername ); + + if ( ( ent->parent ) && ( ent->parent->health <= 0 ) ) + {//our spawner thing is broken + if ( ent->target2 && ent->target2[0] ) + { + //Fire off our target2 + G_UseTargets2( ent, ent, ent->target2 ); + + //Kill us + ent->e_ThinkFunc = thinkF_G_FreeEntity; + ent->nextthink = level.time + 100; + } + else + { + //Try to spawn again in one second + ent->e_ThinkFunc = thinkF_NPC_Spawn_Go; + ent->nextthink = level.time + 1000; + } + return qfalse; + } + } + + //Test for an entity blocking the spawn + trace_t tr; + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ent->s.number, MASK_NPCSOLID ); + + //Can't have anything in the way + if ( tr.allsolid || tr.startsolid ) + { + ent->nextthink = level.time + 1000; + return qfalse; + } + + return qtrue; +} +*/ +void NPC_DefaultScriptFlags( gentity_t *ent ) +{ + if ( !ent || !ent->NPC ) + { + return; + } + //Set up default script flags + ent->NPC->scriptFlags = (SCF_CHASE_ENEMIES|SCF_LOOK_FOR_ENEMIES); +} +/* +------------------------- +NPC_Spawn_Go +------------------------- +*/ +#include "../namespace_begin.h" +extern void G_CreateAnimalNPC( Vehicle_t **pVeh, const char *strAnimalType ); +extern void G_CreateSpeederNPC( Vehicle_t **pVeh, const char *strType ); +extern void G_CreateWalkerNPC( Vehicle_t **pVeh, const char *strAnimalType ); +extern void G_CreateFighterNPC( Vehicle_t **pVeh, const char *strType ); +#include "../namespace_end.h" + +gentity_t *NPC_Spawn_Do( gentity_t *ent, int vehicle ) +{ + gentity_t *newent = NULL; + int index; + vec3_t saveOrg; + +/* //Do extra code for stasis spawners + if ( Q_stricmp( ent->classname, "NPC_Stasis" ) == 0 ) + { + if ( NPC_StasisSpawn_Go( ent ) == qfalse ) + return; + } +*/ + + //Test for drop to floor + if ( ent->spawnflags & NSF_DROP_TO_FLOOR ) + { + trace_t tr; + vec3_t bottom; + + VectorCopy( ent->r.currentOrigin, saveOrg ); + VectorCopy( ent->r.currentOrigin, bottom ); + bottom[2] = MIN_WORLD_COORD; + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, bottom, ent->s.number, MASK_NPCSOLID ); + if ( !tr.allsolid && !tr.startsolid && tr.fraction < 1.0 ) + { + G_SetOrigin( ent, tr.endpos ); + } + } + + //Check the spawner's count + if( ent->count != -1 ) + { + ent->count--; + + if( ent->count <= 0 ) + { + ent->use = 0;//never again + //FIXME: why not remove me...? Because of all the string pointers? Just do G_NewStrings? + } + } + + if(vehicle) { + newent = G_SpawnVehicle(); + } else { + newent = G_Spawn(); + } + + if ( newent == NULL ) + { + Com_Printf ( S_COLOR_RED"ERROR: NPC G_Spawn failed\n" ); + return NULL; + } + + newent->fullName = ent->fullName; + + newent->NPC = New_NPC_t(newent->s.number); + if ( newent->NPC == NULL ) + { + Com_Printf ( S_COLOR_RED"ERROR: NPC G_Alloc NPC failed\n" ); + goto finish; + return NULL; + } + + //newent->client = (gclient_s *)G_Alloc (sizeof(gclient_s)); + G_CreateFakeClient(newent->s.number, &newent->client); + + newent->NPC->tempGoal = G_Spawn(); + + if ( newent->NPC->tempGoal == NULL ) + { + newent->NPC = NULL; + goto finish; + return NULL; + } + + newent->NPC->tempGoal->classname = "NPC_goal"; + newent->NPC->tempGoal->parent = newent; + newent->NPC->tempGoal->r.svFlags |= SVF_NOCLIENT; + + if ( newent->client == NULL ) + { + Com_Printf ( S_COLOR_RED"ERROR: NPC BG_Alloc client failed\n" ); + goto finish; + return NULL; + } + + memset ( newent->client, 0, sizeof(*newent->client) ); + + //Assign the pointer for bg entity access + newent->playerState = &newent->client->ps; + +//==NPC_Connect( newent, net_name );=================================== + + if ( ent->NPC_type == NULL ) + { + ent->NPC_type = "random"; + } + else + { + ent->NPC_type = Q_strlwr( G_NewString( ent->NPC_type ) ); + } + + /* + if ( ent->svFlags & SVF_NO_BASIC_SOUNDS ) + { + newent->svFlags |= SVF_NO_BASIC_SOUNDS; + } + if ( ent->svFlags & SVF_NO_COMBAT_SOUNDS ) + { + newent->svFlags |= SVF_NO_COMBAT_SOUNDS; + } + if ( ent->svFlags & SVF_NO_EXTRA_SOUNDS ) + { + newent->svFlags |= SVF_NO_EXTRA_SOUNDS; + } + */ + //rwwFIXMEFIXME: Use all these flags? + + if ( ent->message ) + {//has a key + newent->message = ent->message;//transfer the key name + newent->flags |= FL_NO_KNOCKBACK;//don't fall off ledges + } + + // If this is a vehicle we need to see what kind it is so we properlly allocate it. + if ( Q_stricmp( ent->classname, "NPC_Vehicle" ) == 0 ) + { + // Get the vehicle entry index. + int iVehIndex = BG_VehicleGetIndex( ent->NPC_type ); + + if ( iVehIndex == VEHICLE_NONE ) + { + G_FreeEntity( newent ); + //get rid of the spawner, too, I guess + G_FreeEntity( ent ); + return NULL; + } + // NOTE: If you change/add any of these, update NPC_Spawn_f for the new vehicle you + // want to be able to spawn in manually. + + // See what kind of vehicle this is and allocate it properly. + switch( g_vehicleInfo[iVehIndex].type ) + { + case VH_ANIMAL: + // Create the animal (making sure all it's data is initialized). + G_CreateAnimalNPC( &newent->m_pVehicle, ent->NPC_type ); + break; + case VH_SPEEDER: + // Create the speeder (making sure all it's data is initialized). + G_CreateSpeederNPC( &newent->m_pVehicle, ent->NPC_type ); + break; + case VH_FIGHTER: + // Create the fighter (making sure all it's data is initialized). + G_CreateFighterNPC( &newent->m_pVehicle, ent->NPC_type ); + break; + case VH_WALKER: + // Create the walker (making sure all it's data is initialized). + G_CreateWalkerNPC( &newent->m_pVehicle, ent->NPC_type ); + break; + + default: + Com_Printf ( S_COLOR_RED "ERROR: Couldn't spawn NPC %s\n", ent->NPC_type ); + G_FreeEntity( newent ); + //get rid of the spawner, too, I guess + G_FreeEntity( ent ); + return NULL; + } + + assert(newent->m_pVehicle && + newent->m_pVehicle->m_pVehicleInfo && + newent->m_pVehicle->m_pVehicleInfo->Initialize); + + //set up my happy prediction hack + newent->m_pVehicle->m_vOrientation = &newent->client->ps.vehOrientation[0]; + + // Setup the vehicle. + newent->m_pVehicle->m_pParentEntity = (bgEntity_t *)newent; + newent->m_pVehicle->m_pVehicleInfo->Initialize( newent->m_pVehicle ); + + //cache all the assets + newent->m_pVehicle->m_pVehicleInfo->RegisterAssets( newent->m_pVehicle ); + //set the class + newent->client->NPC_class = CLASS_VEHICLE; + if ( g_vehicleInfo[iVehIndex].type == VH_FIGHTER ) + {//FIXME: EXTERN!!! + newent->flags |= (FL_NO_KNOCKBACK|FL_SHIELDED|FL_DMG_BY_HEAVY_WEAP_ONLY);//don't get pushed around, blasters bounce off, only damage from heavy weaps + } + //WTF?!!! Ships spawning in pointing straight down! + //set them up to start landed + newent->m_pVehicle->m_vOrientation[YAW] = ent->s.angles[YAW]; + newent->m_pVehicle->m_vOrientation[PITCH] = newent->m_pVehicle->m_vOrientation[ROLL] = 0.0f; + G_SetAngles( newent, newent->m_pVehicle->m_vOrientation ); + SetClientViewAngle( newent, newent->m_pVehicle->m_vOrientation ); + + //newent->m_pVehicle->m_ulFlags |= VEH_GEARSOPEN; + //why? this would just make it so the initial anim never got played... -rww + //There was no initial anim, it would just open the gear even though it's already on the ground (fixed now, made an initial anim) + + //For SUSPEND spawnflag, the amount of time to drop like a rock after SUSPEND turns off + newent->fly_sound_debounce_time = ent->fly_sound_debounce_time; + + //for no-pilot-death delay + newent->damage = ent->damage; + + //no-pilot-death distance + newent->speed = ent->speed; + + //for veh transfer all healy stuff + newent->healingclass = ent->healingclass; + newent->healingsound = ent->healingsound; + newent->healingrate = ent->healingrate; + newent->model2 = ent->model2;//for droidNPC + } + else + { + newent->client->ps.weapon = WP_NONE;//init for later check in NPC_Begin + } + + VectorCopy(ent->s.origin, newent->s.origin); + VectorCopy(ent->s.origin, newent->client->ps.origin); + VectorCopy(ent->s.origin, newent->r.currentOrigin); + G_SetOrigin(newent, ent->s.origin);//just to be sure! + //NOTE: on vehicles, anything in the .npc file will STOMP data on the NPC that's set by the vehicle + if ( !NPC_ParseParms( ent->NPC_type, newent ) ) + { + Com_Printf ( S_COLOR_RED "ERROR: Couldn't spawn NPC %s\n", ent->NPC_type ); + G_FreeEntity( newent ); + //get rid of the spawner, too, I guess + G_FreeEntity( ent ); + return NULL; + } + + if ( ent->NPC_type ) + { + if ( !Q_stricmp( ent->NPC_type, "kyle" ) ) + {//FIXME: "player", not Kyle? Or check NPC_type against player's NPC_type? + newent->NPC->aiFlags |= NPCAI_MATCHPLAYERWEAPON; + } + else if ( !Q_stricmp( ent->NPC_type, "test" ) ) + { + int n; + for ( n = 0; n < 1 ; n++) + { + if ( g_entities[n].s.eType != ET_NPC && g_entities[n].client) + { + VectorCopy(g_entities[n].s.origin, newent->s.origin); + newent->client->playerTeam = newent->s.teamowner = g_entities[n].client->playerTeam; + break; + } + } + newent->NPC->defaultBehavior = newent->NPC->behaviorState = BS_WAIT; + newent->classname = "NPC"; + // newent->svFlags |= SVF_NOPUSH; + } + } +//===================================================================== + //set the info we want + if ( !newent->health ) + { + newent->health = ent->health; + } + newent->script_targetname = ent->NPC_targetname; + newent->targetname = ent->NPC_targetname; + newent->target = ent->NPC_target;//death + newent->target2 = ent->target2;//knocked out death + newent->target3 = ent->target3;//??? + newent->target4 = ent->target4;//ffire death + newent->wait = ent->wait; + + for( index = BSET_FIRST; index < NUM_BSETS; index++) + { + if ( ent->behaviorSet[index] ) + { + newent->behaviorSet[index] = ent->behaviorSet[index]; + } + } + + newent->classname = "NPC"; + newent->NPC_type = ent->NPC_type; + trap_UnlinkEntity(newent); + + VectorCopy(ent->s.angles, newent->s.angles); + VectorCopy(ent->s.angles, newent->r.currentAngles); + VectorCopy(ent->s.angles, newent->client->ps.viewangles); + newent->NPC->desiredYaw =ent->s.angles[YAW]; + + trap_LinkEntity(newent); + newent->spawnflags = ent->spawnflags; + + if(ent->paintarget) + { //safe to point at owner's string since memory is never freed during game + newent->paintarget = ent->paintarget; + } + if(ent->opentarget) + { + newent->opentarget = ent->opentarget; + } + +//==New stuff===================================================================== + newent->s.eType = ET_NPC;//ET_PLAYER; + + //FIXME: Call CopyParms + if ( ent->parms ) + { + int parmNum; + + for ( parmNum = 0; parmNum < MAX_PARMS; parmNum++ ) + { + if ( ent->parms->parm[parmNum] && ent->parms->parm[parmNum][0] ) + { + Q3_SetParm( newent->s.number, parmNum, ent->parms->parm[parmNum] ); + } + } + } + //FIXME: copy cameraGroup, store mine in message or other string field + + //set origin + newent->s.pos.trType = TR_INTERPOLATE; + newent->s.pos.trTime = level.time; + VectorCopy( newent->r.currentOrigin, newent->s.pos.trBase ); + VectorClear( newent->s.pos.trDelta ); + newent->s.pos.trDuration = 0; + //set angles + newent->s.apos.trType = TR_INTERPOLATE; + newent->s.apos.trTime = level.time; + //VectorCopy( newent->r.currentOrigin, newent->s.apos.trBase ); + //Why was the origin being used as angles? Typo I'm assuming -rww + VectorCopy( newent->s.angles, newent->s.apos.trBase ); + + VectorClear( newent->s.apos.trDelta ); + newent->s.apos.trDuration = 0; + + newent->NPC->combatPoint = -1; + + newent->flags |= FL_NOTARGET;//So he's ignored until he's fully spawned + newent->s.eFlags |= EF_NODRAW;//So he's ignored until he's fully spawned + + newent->think = NPC_Begin; + newent->nextthink = level.time + FRAMETIME; + NPC_DefaultScriptFlags( newent ); + + //copy over team variables, too + newent->s.shouldtarget = ent->s.shouldtarget; + newent->s.teamowner = ent->s.teamowner; + newent->alliedTeam = ent->alliedTeam; + newent->teamnodmg = ent->teamnodmg; + if ( ent->team && ent->team[0] ) + {//specified team directly? + newent->client->sess.sessionTeam = atoi(ent->team); + } + else if ( newent->s.teamowner != TEAM_FREE ) + { + newent->client->sess.sessionTeam = newent->s.teamowner; + } + else if ( newent->alliedTeam != TEAM_FREE ) + { + newent->client->sess.sessionTeam = newent->alliedTeam; + } + else if ( newent->teamnodmg != TEAM_FREE ) + { + newent->client->sess.sessionTeam = newent->teamnodmg; + } + else + { + newent->client->sess.sessionTeam = TEAM_FREE; + } + newent->client->ps.persistant[PERS_TEAM] = newent->client->sess.sessionTeam; + + trap_LinkEntity (newent); + + if(!ent->use) + { + if( ent->target ) + {//use any target we're pointed at + G_UseTargets ( ent, ent ); + } + if(ent->closetarget) + {//last guy should fire this target when he dies + newent->target = ent->closetarget; + } + ent->targetname = NULL; + //why not remove me...? Because of all the string pointers? Just do G_NewStrings? + G_FreeEntity( ent );//bye! + } + +finish: + if ( ent->spawnflags & NSF_DROP_TO_FLOOR ) + { + G_SetOrigin( ent, saveOrg ); + } + + return newent; +} + +void NPC_Spawn_Go(gentity_t *ent) +{ + NPC_Spawn_Do(ent, false); +} + +/* +------------------------- +NPC_StasisSpawnEffect +------------------------- +*/ +/* +void NPC_StasisSpawnEffect( gentity_t *ent ) +{ + vec3_t start, end, forward; + qboolean taper; + + //Floor or wall? + if ( ent->spawnflags & 1 ) + { + AngleVectors( ent->s.angles, forward, NULL, NULL ); + VectorMA( ent->r.currentOrigin, 24, forward, end ); + VectorMA( ent->r.currentOrigin, -20, forward, start ); + + start[2] += 64; + + taper = qtrue; + } + else + { + VectorCopy( ent->r.currentOrigin, start ); + VectorCopy( start, end ); + end[2] += 48; + taper = qfalse; + } + + //Add the effect +// CG_ShimmeryThing_Spawner( start, end, 32, qtrue, 1000 ); +} +*/ +/* +------------------------- +NPC_ShySpawn +------------------------- +*/ + +#define SHY_THINK_TIME 1000 +#define SHY_SPAWN_DISTANCE 128 +#define SHY_SPAWN_DISTANCE_SQR ( SHY_SPAWN_DISTANCE * SHY_SPAWN_DISTANCE ) + +void NPC_ShySpawn( gentity_t *ent ) +{ + ent->nextthink = level.time + SHY_THINK_TIME; + ent->think = NPC_ShySpawn; + + //rwwFIXMEFIXME: Care about other clients not just 0? + if ( DistanceSquared( g_entities[0].r.currentOrigin, ent->r.currentOrigin ) <= SHY_SPAWN_DISTANCE_SQR ) + return; + + if ( (InFOV( ent, &g_entities[0], 80, 64 )) ) // FIXME: hardcoded fov + if ( (NPC_ClearLOS2( &g_entities[0], ent->r.currentOrigin )) ) + return; + + ent->think = 0; + ent->nextthink = 0; + + NPC_Spawn_Go( ent ); +} + +/* +------------------------- +NPC_Spawn +------------------------- +*/ + +void NPC_Spawn ( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + //delay before spawning NPC + if( ent->delay ) + { +/* //Stasis does an extra step + if ( Q_stricmp( ent->classname, "NPC_Stasis" ) == 0 ) + { + if ( NPC_StasisSpawn_Go( ent ) == qfalse ) + return; + } +*/ + if ( ent->spawnflags & 2048 ) // SHY + { + ent->think = NPC_ShySpawn; + } + else + { + ent->think = NPC_Spawn_Go; + } + + ent->nextthink = level.time + ent->delay; + } + else + { + if ( ent->spawnflags & 2048 ) // SHY + { + NPC_ShySpawn( ent ); + } + else + { + NPC_Spawn_Do( ent, false ); + } + } +} + +/*QUAKED NPC_spawner (1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +NPC_type - name of NPC (in npcs.cfg) to spawn in + +targetname - name this NPC goes by for targetting +target - NPC will fire this when it spawns it's last NPC (should this be when the last NPC it spawned dies?) +target2 - Fired by stasis spawners when they try to spawn while their spawner model is broken +target3 - Fired by spawner if they try to spawn and are blocked and have a wait < 0 (removes them) + +If targeted, will only spawn a NPC when triggered +count - how many NPCs to spawn (only if targetted) default = 1 +delay - how long to wait to spawn after used +wait - if trying to spawn and blocked, how many seconds to wait before trying again (default = 0.5, < 0 = never try again and fire target2) + +NPC_targetname - NPC's targetname AND script_targetname +NPC_target - NPC's target to fire when killed +NPC_target2 - NPC's target to fire when knocked out +NPC_target4 - NPC's target to fire when killed by friendly fire +NPC_type - type of NPC ("Borg" (default), "Xian", etc) +health - starting health (default = 100) + +spawnscript - default script to run once spawned (none by default) +usescript - default script to run when used (none by default) +awakescript - default script to run once awoken (none by default) +angerscript - default script to run once angered (none by default) +painscript - default script to run when hit (none by default) +fleescript - default script to run when hit and below 50% health (none by default) +deathscript - default script to run when killed (none by default) +These strings can be used to activate behaviors instead of scripts - these are checked +first and so no scripts should be names with these names: + default - 0: whatever + idle - 1: Stand around, do abolutely nothing + roam - 2: Roam around, collect stuff + walk - 3: Crouch-Walk toward their goals + run - 4: Run toward their goals + standshoot - 5: Stay in one spot and shoot- duck when neccesary + standguard - 6: Wait around for an enemy + patrol - 7: Follow a path, looking for enemies + huntkill - 8: Track down enemies and kill them + evade - 9: Run from enemies + evadeshoot - 10: Run from enemies, shoot them if they hit you + runshoot - 11: Run to your goal and shoot enemy when possible + defend - 12: Defend an entity or spot? + snipe - 13: Stay hidden, shoot enemy only when have perfect shot and back turned + combat - 14: Attack, evade, use cover, move about, etc. Full combat AI - id NPC code + medic - 15: Go for lowest health buddy, hide and heal him. + takecover - 16: Find nearest cover from enemies + getammo - 17: Go get some ammo + advancefight - 18: Go somewhere and fight along the way + face - 19: turn until facing desired angles + wait - 20: do nothing + formation - 21: Maintain a formation + crouch - 22: Crouch-walk toward their goals + +delay - after spawned or triggered, how many seconds to wait to spawn the NPC + +showhealth - set to 1 to show health bar on this entity when crosshair is over it + +teamowner - crosshair shows green for this team, red for opposite team + 0 - none + 1 - red + 2 - blue + +teamuser - only this team can use this NPC + 0 - none + 1 - red + 2 - blue + +teamnodmg - team that NPC does not take damage from (turrets and other auto-defenses that have "alliedTeam" set to this team won't target this NPC) + 0 - none + 1 - red + 2 - blue +*/ +//void NPC_PrecacheModels ( char *NPCName ); +extern void NPC_PrecacheAnimationCFG( const char *NPC_type ); +void NPC_Precache ( gentity_t *spawner ); +void NPC_PrecacheType( char *NPC_type ) +{ + gentity_t *fakespawner = G_Spawn(); + if ( fakespawner ) + { + fakespawner->NPC_type = NPC_type; + NPC_Precache( fakespawner ); + //NOTE: does the spawner have to stay around to send any precached info to the clients...? + G_FreeEntity( fakespawner ); + } +} + +void SP_NPC_spawner( gentity_t *self) +{ + int t; + + if (!g_allowNPC.integer) + { + self->think = G_FreeEntity; + self->nextthink = level.time; + return; + } + if ( !self->fullName || !self->fullName[0] ) + { + //FIXME: make an index into an external string table for localization + self->fullName = "Humanoid Lifeform"; + } + + //register/precache the models needed for this NPC, not anymore + //self->classname = "NPC_spawner"; + + if(!self->count) + { + self->count = 1; + } + + /* + {//Stop loading of certain extra sounds + static int garbage; + + if ( G_SpawnInt( "noBasicSounds", "0", &garbage ) ) + { + self->svFlags |= SVF_NO_BASIC_SOUNDS; + } + if ( G_SpawnInt( "noCombatSounds", "0", &garbage ) ) + { + self->svFlags |= SVF_NO_COMBAT_SOUNDS; + } + if ( G_SpawnInt( "noExtraSounds", "0", &garbage ) ) + { + self->svFlags |= SVF_NO_EXTRA_SOUNDS; + } + } + */ + //rwwFIXMEFIXME: Use these flags? + + if ( !self->wait ) + { + self->wait = 500; + } + else + { + self->wait *= 1000;//1 = 1 msec, 1000 = 1 sec + } + + self->delay *= 1000;//1 = 1 msec, 1000 = 1 sec + + G_SpawnInt( "showhealth", "0", &t ); + if (t) + { + self->s.shouldtarget = qtrue; + } + /* + if ( self->delay > 0 ) + { + self->svFlags |= SVF_NPC_PRECACHE; + } + */ + //rwwFIXMEFIXME: support for this flag? + + //We have to load the animation.cfg now because spawnscripts are going to want to set anims and we need to know their length and if they're valid + NPC_PrecacheAnimationCFG( self->NPC_type ); + + //rww - can't cheat and do this on the client like in SP, so I'm doing this. + NPC_Precache(self); + + if ( self->targetname ) + {//Wait for triggering + self->use = NPC_Spawn; + // self->svFlags |= SVF_NPC_PRECACHE;//FIXME: precache my weapons somehow? + + //NPC_PrecacheModels( self->NPC_type ); + } + else + { + //NOTE: auto-spawners never check for shy spawning + //if ( spawning ) + if (1) //just gonna always do this I suppose. + {//in entity spawn stage - map starting up + self->think = NPC_Spawn_Go; + self->nextthink = level.time + START_TIME_REMOVE_ENTS + 50; + } + else + {//else spawn right now + NPC_Spawn( self, self, self ); + } + } + + //FIXME: store cameraGroup somewhere else and apply to spawned NPCs' cameraGroup + //Or just don't include NPC_spawners in cameraGroupings +} + +extern void G_VehicleSpawn( gentity_t *self ); +/*QUAKED NPC_Vehicle (1 0 0) (-16 -16 -24) (16 16 32) NO_PILOT_DIE SUSPENDED x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +NO_PILOT_DIE - die after certain amount of time of not having a pilot +SUSPENDED - Fighters: Don't drop until someone gets in it (this only works as long as no-nw has *ever* ridden the vehicle, to simulate ships that are suspended-docked) - note: ships inside trigger_spaces do not drop when unoccupied +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +set NPC_type to vehicle name in vehicles.dat + +"dropTime" use with SUSPENDED - if set, the vehicle will drop straight down for this number of seconds before flying forward +"dmg" use with NO_PILOT_DIE - delay in milliseconds for ship to explode if no pilot (default 10000) +"speed" use with NO_PILOT_DIE - distance for pilot to get away from ship after dismounting before it starts counting down the death timer +"model2" - if the vehicle can have a droid (has "*droidunit" tag), this NPC will be spawned and placed there - note: game will automatically use the one specified in the .veh file (if any) or, absent that, it will use an R2D2 or R5D2 NPC) + +showhealth - set to 1 to show health bar on this entity when crosshair is over it + +teamowner - crosshair shows green for this team, red for opposite team + 0 - none + 1 - red + 2 - blue + +teamuser - only this team can use this NPC + 0 - none + 1 - red + 2 - blue + +teamnodmg - team that NPC does not take damage from (turrets and other auto-defenses that have "alliedTeam" set to this team won't target this NPC) + 0 - none + 1 - red + 2 - blue +*/ +qboolean NPC_VehiclePrecache( gentity_t *spawner ) +{ + char *droidNPCType = NULL; + //This will precache the vehicle + vehicleInfo_t *pVehInfo; + int iVehIndex = BG_VehicleGetIndex( spawner->NPC_type ); + if ( iVehIndex == VEHICLE_NONE ) + {//fixme: error msg? + return qfalse; + } + + G_ModelIndex(va("$%s", spawner->NPC_type)); //make sure the thing is frickin precached + //now cache his model/skin/anim config + pVehInfo = &g_vehicleInfo[iVehIndex]; + if (pVehInfo->model && pVehInfo->model[0]) + { + void *tempG2 = NULL; + int skin = 0; + if (pVehInfo->skin && pVehInfo->skin[0]) + { + skin = trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", pVehInfo->model, pVehInfo->skin)); + } + ModelMem.SetNPCMode(true); + trap_G2API_InitGhoul2Model(&tempG2, va("models/players/%s/model.glm", pVehInfo->model), 0, skin, 0, 0, 0); + ModelMem.SetNPCMode(false); + if (tempG2) + { //now, cache the anim config. + char GLAName[1024]; + + GLAName[0] = 0; + trap_G2API_GetGLAName(tempG2, 0, GLAName); + + if (GLAName[0]) + { + char *slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + + BG_ParseAnimationFile(GLAName, NULL, qfalse); + } + } + trap_G2API_CleanGhoul2Models(&tempG2); + } + } + + //also precache the droid NPC if there is one + if ( spawner->model2 + && spawner->model2[0] ) + { + droidNPCType = spawner->model2; + } + else if ( g_vehicleInfo[iVehIndex].droidNPC + && g_vehicleInfo[iVehIndex].droidNPC[0] ) + { + droidNPCType = g_vehicleInfo[iVehIndex].droidNPC; + } + + if ( droidNPCType ) + { + if ( Q_stricmp( "random", droidNPCType ) == 0 + || Q_stricmp( "default", droidNPCType ) == 0 ) + {//precache both r2 and r5, as defaults + NPC_PrecacheType( "r2d2" ); + NPC_PrecacheType( "r5d2" ); + } + else + { + NPC_PrecacheType( droidNPCType ); + } + } + return qtrue; +} + +void NPC_VehicleSpawnUse( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( self->delay ) + { + self->think = G_VehicleSpawn; + self->nextthink = level.time + self->delay; + } + else + { + G_VehicleSpawn( self ); + } +} + +void SP_NPC_Vehicle( gentity_t *self) +{ + float dropTime; + int t; + if ( !self->NPC_type ) + { + self->NPC_type = "swoop"; + } + + if ( !self->classname ) + { + self->classname = "NPC_Vehicle"; + } + + if ( !self->wait ) + { + self->wait = 500; + } + else + { + self->wait *= 1000;//1 = 1 msec, 1000 = 1 sec + } + self->delay *= 1000;//1 = 1 msec, 1000 = 1 sec + + G_SetOrigin( self, self->s.origin ); + G_SetAngles( self, self->s.angles ); + G_SpawnFloat( "dropTime", "0", &dropTime ); + if ( dropTime ) + { + self->fly_sound_debounce_time = ceil(dropTime*1000.0); + } + + G_SpawnInt( "showhealth", "0", &t ); + if (t) + { + self->s.shouldtarget = qtrue; + } + //FIXME: PRECACHE!!! + + if ( self->targetname ) + { + if ( !NPC_VehiclePrecache( self ) ) + {//FIXME: err msg? + G_FreeEntity( self ); + return; + } + self->use = NPC_VehicleSpawnUse; + } + else + { + if ( self->delay ) + { + if ( !NPC_VehiclePrecache( self ) ) + {//FIXME: err msg? + G_FreeEntity( self ); + return; + } + self->think = G_VehicleSpawn; + self->nextthink = level.time + self->delay; + } + else + { + G_VehicleSpawn( self ); + } + } +} + +//Characters + +//STAR WARS NPCs============================================================================ +/*QUAKED NPC_spawner (1 0 0) (-16 -16 -24) (16 16 32) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY + +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +targetname - name this NPC goes by for targetting +target - NPC will fire this when it spawns it's last NPC (should this be when the last NPC it spawned dies?) + +If targeted, will only spawn a NPC when triggered +count - how many NPCs to spawn (only if targetted) default = 1 + +NPC_type - the name of the NPC (from NPCs.cfg or a from .npc file) + +NPC_targetname - NPC's targetname AND script_targetname +NPC_target - NPC's target to fire when killed +health - starting health (default = 100) +playerTeam - Who not to shoot! (default is TEAM_STARFLEET) + TEAM_FREE (none) = 0 + TEAM_RED = 1 + TEAM_BLUE = 2 + TEAM_GOLD = 3 + TEAM_GREEN = 4 + TEAM_STARFLEET = 5 + TEAM_BORG = 6 + TEAM_SCAVENGERS = 7 + TEAM_STASIS = 8 + TEAM_NPCS = 9 + TEAM_HARVESTER, = 10 + TEAM_FORGE = 11 +enemyTeam - Who to shoot (all but own if not set) + +spawnscript - default script to run once spawned (none by default) +usescript - default script to run when used (none by default) +awakescript - default script to run once awoken (none by default) +angerscript - default script to run once angered (none by default) +painscript - default script to run when hit (none by default) +fleescript - default script to run when hit and below 50% health (none by default) +deathscript - default script to run when killed (none by default) + +These strings can be used to activate behaviors instead of scripts - these are checked +first and so no scripts should be names with these names: + default - 0: whatever + idle - 1: Stand around, do abolutely nothing + roam - 2: Roam around, collect stuff + walk - 3: Crouch-Walk toward their goals + run - 4: Run toward their goals + standshoot - 5: Stay in one spot and shoot- duck when neccesary + standguard - 6: Wait around for an enemy + patrol - 7: Follow a path, looking for enemies + huntkill - 8: Track down enemies and kill them + evade - 9: Run from enemies + evadeshoot - 10: Run from enemies, shoot them if they hit you + runshoot - 11: Run to your goal and shoot enemy when possible + defend - 12: Defend an entity or spot? + snipe - 13: Stay hidden, shoot enemy only when have perfect shot and back turned + combat - 14: Attack, evade, use cover, move about, etc. Full combat AI - id NPC code + medic - 15: Go for lowest health buddy, hide and heal him. + takecover - 16: Find nearest cover from enemies + getammo - 17: Go get some ammo + advancefight - 18: Go somewhere and fight along the way + face - 19: turn until facing desired angles + wait - 20: do nothing + formation - 21: Maintain a formation + crouch - 22: Crouch-walk toward their goals + +delay - after spawned or triggered, how many seconds to wait to spawn the NPC +*/ + +//============================================================================================= +//CHARACTERS +//============================================================================================= + +/*QUAKED NPC_Kyle (1 0 0) (-16 -16 -24) (16 16 32) x RIFLEMAN PHASER TRICORDER DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Kyle( gentity_t *self) +{ + self->NPC_type = "Kyle"; + + WP_SetSaberModel( NULL, CLASS_KYLE ); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Lando(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Lando( gentity_t *self) +{ + self->NPC_type = "Lando"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Jan(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Jan( gentity_t *self) +{ + self->NPC_type = "Jan"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Luke(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Luke( gentity_t *self) +{ + self->NPC_type = "Luke"; + + WP_SetSaberModel( NULL, CLASS_LUKE ); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_MonMothma(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_MonMothma( gentity_t *self) +{ + self->NPC_type = "MonMothma"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Tavion (1 0 0) (-16 -16 -24) (16 16 32) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tavion( gentity_t *self) +{ + self->NPC_type = "Tavion"; + + WP_SetSaberModel( NULL, CLASS_TAVION ); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Tavion_New (1 0 0) (-16 -16 -24) (16 16 32) SCEPTER SITH_SWORD x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Has a red lightsaber and force powers, uses her saber style from JK2 + +SCEPTER - Has a red lightsaber and force powers, Ragnos' Scepter in left hand, uses dual saber style and occasionally attacks with Scepter +SITH_SWORD - Has Ragnos' Sith Sword in right hand and force powers, uses strong style +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tavion_New( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "tavion_scepter"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "tavion_sith_sword"; + } + else + { + self->NPC_type = "tavion_new"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Alora (1 0 0) (-16 -16 -24) (16 16 32) DUAL x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Lightsaber and level 2 force powers, 300 health + +DUAL - Dual sabers and level 3 force powers, 500 health +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Alora( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "alora_dual"; + } + else + { + self->NPC_type = "alora"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Reborn_New(1 0 0) (-16 -16 -24) (16 16 40) DUAL STAFF WEAK x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Reborn is an excellent lightsaber fighter, acrobatic and uses force powers. Full-length red saber, 200 health. + +DUAL - Use 2 shorter sabers +STAFF - Uses a saber staff +WEAK - Is a bit less tough +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Reborn_New( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&4) ) + {//weaker guys + if ( (self->spawnflags&1) ) + { + self->NPC_type = "reborn_dual2"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "reborn_staff2"; + } + else + { + self->NPC_type = "reborn_new2"; + } + } + else + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "reborn_dual"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "reborn_staff"; + } + else + { + self->NPC_type = "reborn_new"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist_Saber(1 0 0) (-16 -16 -24) (16 16 40) MED STRONG ALL THROW CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Uses a saber and no force powers. 100 health. + +default fencer uses fast style - weak, but can attack rapidly. Good defense. + +MED - Uses medium style - average speed and attack strength, average defense. +STRONG - Uses strong style, slower than others, but can do a lot of damage with one blow. Weak defense. +ALL - Knows all 3 styles, switches between them, good defense. +THROW - can throw their saber (level 2) - reduces their defense some (can use this spawnflag alone or in combination with any *one* of the previous 3 spawnflags) + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist_Saber( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_med_throw"; + } + else + { + self->NPC_type = "cultist_saber_med"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_strong_throw"; + } + else + { + self->NPC_type = "cultist_saber_strong"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_all_throw"; + } + else + { + self->NPC_type = "cultist_saber_all"; + } + } + else + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_throw"; + } + else + { + self->NPC_type = "cultist_saber"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist_Saber_Powers(1 0 0) (-16 -16 -24) (16 16 40) MED STRONG ALL THROW CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Uses a saber and has a couple low-level powers. 150 health. + +default fencer uses fast style - weak, but can attack rapidly. Good defense. + +MED - Uses medium style - average speed and attack strength, average defense. +STRONG - Uses strong style, slower than others, but can do a lot of damage with one blow. Weak defense. +ALL - Knows all 3 styles, switches between them, good defense. +THROW - can throw their saber (level 2) - reduces their defense some (can use this spawnflag alone or in combination with any *one* of the previous 3 spawnflags) + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist_Saber_Powers( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_med_throw2"; + } + else + { + self->NPC_type = "cultist_saber_med2"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_strong_throw2"; + } + else + { + self->NPC_type = "cultist_saber_strong2"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_all_throw2"; + } + else + { + self->NPC_type = "cultist_saber_all2"; + } + } + else + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_throw"; + } + else + { + self->NPC_type = "cultist_saber2"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist(1 0 0) (-16 -16 -24) (16 16 40) SABER GRIP LIGHTNING DRAIN CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Cultist uses a blaster and force powers. 40 health. + +SABER - Uses a saber and no force powers +GRIP - Uses no weapon and grip, push and pull +LIGHTNING - Uses no weapon and lightning and push +DRAIN - Uses no weapons and drain and push + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = NULL; + self->spawnflags = 0;//fast, no throw + switch ( Q_irand( 0, 2 ) ) + { + case 0://medium + self->spawnflags |= 1; + break; + case 1://strong + self->spawnflags |= 2; + break; + case 2://all + self->spawnflags |= 4; + break; + } + if ( Q_irand( 0, 1 ) ) + {//throw + self->spawnflags |= 8; + } + SP_NPC_Cultist_Saber( self ); + return; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "cultist_grip"; + } + else if ( (self->spawnflags&4) ) + { + self->NPC_type = "cultist_lightning"; + } + else if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_drain"; + } + else + { + self->NPC_type = "cultist"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist_Commando(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Cultist uses dual blaster pistols and force powers. 40 health. + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist_Commando( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "cultistcommando"; + } + SP_NPC_spawner( self ); +} +/*QUAKED NPC_Cultist_Destroyer(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Cultist has no weapons, runs up to you chanting & building up a Force Destruction blast - when gets to you, screams & explodes + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist_Destroyer( gentity_t *self) +{ + self->NPC_type = "cultist";//"cultist_explode"; + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Reelo(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Reelo( gentity_t *self) +{ + self->NPC_type = "Reelo"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Galak(1 0 0) (-16 -16 -24) (16 16 40) MECH x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +MECH - will be the armored Galak + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Galak( gentity_t *self) +{ + assert( 0 ); +/* + if ( self->spawnflags & 1 ) + { + self->NPC_type = "Galak_Mech"; + NPC_GalakMech_Precache(); + } + else + { + self->NPC_type = "Galak"; + } + + SP_NPC_spawner( self ); +*/ +} + +/*QUAKED NPC_Desann(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Desann( gentity_t *self) +{ + self->NPC_type = "Desann"; + + WP_SetSaberModel( NULL, CLASS_DESANN ); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Bartender(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Bartender( gentity_t *self) +{ + self->NPC_type = "Bartender"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_MorganKatarn(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_MorganKatarn( gentity_t *self) +{ + self->NPC_type = "MorganKatarn"; + + SP_NPC_spawner( self ); +} + +//============================================================================================= +//ALLIES +//============================================================================================= + +/*QUAKED NPC_Jedi(1 0 0) (-16 -16 -24) (16 16 40) TRAINER x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +TRAINER - Special Jedi- instructor +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Ally Jedi NPC Buddy - tags along with player +*/ +void SP_NPC_Jedi( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "jeditrainer"; + } + else + { + /* + if ( !Q_irand( 0, 2 ) ) + { + self->NPC_type = "JediF"; + } + else + */if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Jedi"; + } + else + { + self->NPC_type = "Jedi2"; + } + } + } + + WP_SetSaberModel( NULL, CLASS_JEDI ); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Prisoner(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Prisoner( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Prisoner"; + } + else + { + self->NPC_type = "Prisoner2"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Rebel(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Rebel( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Rebel"; + } + else + { + self->NPC_type = "Rebel2"; + } + } + + SP_NPC_spawner( self ); +} + +//============================================================================================= +//ENEMIES +//============================================================================================= + +/*QUAKED NPC_Stormtrooper(1 0 0) (-16 -16 -24) (16 16 40) OFFICER COMMANDER ALTOFFICER ROCKET DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +30 health, blaster + +OFFICER - 60 health, flechette +COMMANDER - 60 health, heavy repeater +ALTOFFICER - 60 health, alt-fire flechette (grenades) +ROCKET - 60 health, rocket launcher + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Stormtrooper( gentity_t *self) +{ + if ( self->spawnflags & 8 ) + {//rocketer + self->NPC_type = "rockettrooper"; + } + else if ( self->spawnflags & 4 ) + {//alt-officer + self->NPC_type = "stofficeralt"; + } + else if ( self->spawnflags & 2 ) + {//commander + self->NPC_type = "stcommander"; + } + else if ( self->spawnflags & 1 ) + {//officer + self->NPC_type = "stofficer"; + } + else + {//regular trooper + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "StormTrooper"; + } + else + { + self->NPC_type = "StormTrooper2"; + } + } + + SP_NPC_spawner( self ); +} +void SP_NPC_StormtrooperOfficer( gentity_t *self) +{ + self->spawnflags |= 1; + SP_NPC_Stormtrooper( self ); +} +/*QUAKED NPC_Snowtrooper(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +30 health, blaster + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Snowtrooper( gentity_t *self) +{ + self->NPC_type = "snowtrooper"; + + SP_NPC_spawner( self ); +} +/*QUAKED NPC_Tie_Pilot(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +30 health, blaster + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tie_Pilot( gentity_t *self) +{ + self->NPC_type = "stormpilot"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Ugnaught(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Ugnaught( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Ugnaught"; + } + else + { + self->NPC_type = "Ugnaught2"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Jawa(1 0 0) (-16 -16 -24) (16 16 40) ARMED x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +ARMED - starts with the Jawa gun in-hand + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Jawa( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "jawa_armed"; + } + else + { + self->NPC_type = "jawa"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Gran(1 0 0) (-16 -16 -24) (16 16 40) SHOOTER BOXER x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +Uses grenade + +SHOOTER - uses blaster instead of +BOXER - uses fists only +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Gran( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "granshooter"; + } + else if ( self->spawnflags & 2 ) + { + self->NPC_type = "granboxer"; + } + else + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "gran"; + } + else + { + self->NPC_type = "gran2"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Rodian(1 0 0) (-16 -16 -24) (16 16 40) BLASTER NO_HIDE x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +BLASTER uses a blaster instead of sniper rifle, different skin +NO_HIDE (only applicable with snipers) does not duck and hide between shots +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Rodian( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags&1 ) + { + self->NPC_type = "rodian2"; + } + else + { + self->NPC_type = "rodian"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Weequay(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Weequay( gentity_t *self) +{ + if ( !self->NPC_type ) + { + switch ( Q_irand( 0, 3 ) ) + { + case 0: + self->NPC_type = "Weequay"; + break; + case 1: + self->NPC_type = "Weequay2"; + break; + case 2: + self->NPC_type = "Weequay3"; + break; + case 3: + self->NPC_type = "Weequay4"; + break; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Trandoshan(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Trandoshan( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "Trandoshan"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Tusken(1 0 0) (-16 -16 -24) (16 16 40) SNIPER x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tusken( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "tuskensniper"; + } + else + { + self->NPC_type = "tusken"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Noghri(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Noghri( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "noghri"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_SwampTrooper(1 0 0) (-16 -16 -24) (16 16 40) REPEATER x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +REPEATER - Swaptrooper who uses the repeater +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_SwampTrooper( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "SwampTrooper2"; + } + else + { + self->NPC_type = "SwampTrooper"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Imperial(1 0 0) (-16 -16 -24) (16 16 40) OFFICER COMMANDER x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY + +Greyshirt grunt, uses blaster pistol, 20 health. + +OFFICER - Brownshirt Officer, uses blaster rifle, 40 health +COMMANDER - Blackshirt Commander, uses rapid-fire blaster rifle, 80 healt + +"message" - if a COMMANDER, turns on his key surface. This is the name of the key you get when you walk over his body. This must match the "message" field of the func_security_panel you want this key to open. Set to "goodie" to have him carrying a goodie key that player can use to operate doors with "GOODIE" spawnflag. + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Imperial( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "ImpOfficer"; + } + else if ( self->spawnflags & 2 ) + { + self->NPC_type = "ImpCommander"; + } + else + { + self->NPC_type = "Imperial"; + } + } + + /* + if ( self->message ) + {//may drop a key, precache the key model and pickup sound + G_SoundIndex( "sound/weapons/key_pkup.wav" ); + if ( !Q_stricmp( "goodie", self->message ) ) + { + RegisterItem( FindItemForInventory( INV_GOODIE_KEY ) ); + } + else + { + RegisterItem( FindItemForInventory( INV_SECURITY_KEY ) ); + } + } + */ + //rwwFIXMEFIXME: Allow goodie keys + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_ImpWorker(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_ImpWorker( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( !Q_irand( 0, 2 ) ) + { + self->NPC_type = "ImpWorker"; + } + else if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "ImpWorker2"; + } + else + { + self->NPC_type = "ImpWorker3"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_BespinCop(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_BespinCop( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( !Q_irand( 0, 1 ) ) + { + self->NPC_type = "BespinCop"; + } + else + { + self->NPC_type = "BespinCop2"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Reborn(1 0 0) (-16 -16 -24) (16 16 40) FORCE FENCER ACROBAT BOSS CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Default Reborn is A poor lightsaber fighter, acrobatic and uses no force powers. Yellow saber, 40 health. + +FORCE - Uses force powers but is not the best lightsaber fighter and not acrobatic. Purple saber, 75 health. +FENCER - A good lightsaber fighter, but not acrobatic and uses no force powers. Yellow saber, 100 health. +ACROBAT - quite acrobatic, but not the best lightsaber fighter and uses no force powers. Red saber, 100 health. +BOSS - quite acrobatic, good lightsaber fighter and uses force powers. Orange saber, 150 health. + +NOTE: Saber colors are temporary until they have different by skins to tell them apart + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Reborn( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "rebornforceuser"; + } + else if ( self->spawnflags & 2 ) + { + self->NPC_type = "rebornfencer"; + } + else if ( self->spawnflags & 4 ) + { + self->NPC_type = "rebornacrobat"; + } + else if ( self->spawnflags & 8 ) + { + self->NPC_type = "rebornboss"; + } + else + { + self->NPC_type = "reborn"; + } + } + + WP_SetSaberModel( NULL, CLASS_REBORN ); + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_ShadowTrooper(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_ShadowTrooper( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( !Q_irand( 0, 1 ) ) + { + self->NPC_type = "ShadowTrooper"; + } + else + { + self->NPC_type = "ShadowTrooper2"; + } + } + + NPC_ShadowTrooper_Precache(); + WP_SetSaberModel( NULL, CLASS_SHADOWTROOPER ); + + SP_NPC_spawner( self ); +} +//============================================================================================= +//MONSTERS +//============================================================================================= + +/*QUAKED NPC_Monster_Murjj (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Murjj( gentity_t *self) +{ + self->NPC_type = "Murjj"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Swamp (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Swamp( gentity_t *self) +{ + self->NPC_type = "Swamp"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Howler (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Howler( gentity_t *self) +{ + self->NPC_type = "howler"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_MineMonster (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_MineMonster( gentity_t *self) +{ + self->NPC_type = "minemonster"; + + SP_NPC_spawner( self ); + NPC_MineMonster_Precache(); +} + +/*QUAKED NPC_Monster_Claw (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Claw( gentity_t *self) +{ + self->NPC_type = "Claw"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Glider (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Glider( gentity_t *self) +{ + self->NPC_type = "Glider"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Flier2 (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Flier2( gentity_t *self) +{ + self->NPC_type = "Flier2"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Lizard (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Lizard( gentity_t *self) +{ + self->NPC_type = "Lizard"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Fish (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Fish( gentity_t *self) +{ + self->NPC_type = "Fish"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Wampa (1 0 0) (-12 -12 -24) (12 12 40) WANDER SEARCH x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +WANDER - When I don't have an enemy, I'll just wander the waypoint network aimlessly +SEARCH - When I don't have an enemy, I'll go back and forth between the nearest waypoints, looking for enemies +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Wampa( gentity_t *self) +{ + self->NPC_type = "wampa"; + + NPC_Wampa_Precache(); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Rancor (1 0 0) (-30 -30 -24) (30 30 104) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Rancor( gentity_t *self) +{ + self->NPC_type = "rancor"; + + SP_NPC_spawner( self ); +} + +//============================================================================================= +//DROIDS +//============================================================================================= + +/*QUAKED NPC_Droid_Interrogator (1 0 0) (-12 -12 -24) (12 12 0) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Droid_Interrogator( gentity_t *self) +{ + assert( 0 ); +/* + self->NPC_type = "interrogator"; + + SP_NPC_spawner( self ); + + NPC_Interrogator_Precache(self); +*/ +} + +/*QUAKED NPC_Droid_Probe (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Imperial Probe Droid - the multilegged floating droid that Han and Chewie shot on the ice planet Hoth +*/ +void SP_NPC_Droid_Probe( gentity_t *self) +{ + self->NPC_type = "probe"; + + SP_NPC_spawner( self ); + + NPC_Probe_Precache(); +} + +/*QUAKED NPC_Droid_Mark1 (1 0 0) (-36 -36 -24) (36 36 80) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Big walking droid + +*/ +void SP_NPC_Droid_Mark1( gentity_t *self) +{ + assert( 0 ); +/* + self->NPC_type = "mark1"; + + SP_NPC_spawner( self ); + + NPC_Mark1_Precache(); +*/ +} + +/*QUAKED NPC_Droid_Mark2 (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Small rolling droid with one gun. + +*/ +void SP_NPC_Droid_Mark2( gentity_t *self) +{ + assert( 0 ); +/* + self->NPC_type = "mark2"; + + SP_NPC_spawner( self ); + + NPC_Mark2_Precache(); +*/ +} + +/*QUAKED NPC_Droid_ATST (1 0 0) (-40 -40 -24) (40 40 248) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Droid_ATST( gentity_t *self) +{ + assert( 0 ); +/* + if ( (self->spawnflags&1) ) + { + self->NPC_type = "atst_vehicle"; + } + else + { + self->NPC_type = "atst"; + } + + SP_NPC_spawner( self ); + + NPC_ATST_Precache(); +*/ +} + +/*QUAKED NPC_Droid_Remote (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Remote Droid - the floating round droid used by Obi Wan to train Luke about the force while on the Millenium Falcon. +*/ +void SP_NPC_Droid_Remote( gentity_t *self) +{ + self->NPC_type = "remote"; + + SP_NPC_spawner( self ); + + NPC_Remote_Precache(); +} + +/*QUAKED NPC_Droid_Seeker (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Seeker Droid - floating round droids that shadow troopers spawn +*/ +void SP_NPC_Droid_Seeker( gentity_t *self) +{ + self->NPC_type = "seeker"; + + SP_NPC_spawner( self ); + + NPC_Seeker_Precache(); +} + +/*QUAKED NPC_Droid_Sentry (1 0 0) (-24 -24 -24) (24 24 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Sentry Droid - Large, armored floating Imperial droids with 3 forward-facing gun turrets +*/ +void SP_NPC_Droid_Sentry( gentity_t *self) +{ + self->NPC_type = "sentry"; + + SP_NPC_spawner( self ); + + NPC_Sentry_Precache(); +} + +/*QUAKED NPC_Droid_Gonk (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Gonk Droid - the droid that looks like a walking ice machine. Was in the Jawa land crawler, walking around talking to itself. + +NOTARGET by default +*/ +void SP_NPC_Droid_Gonk( gentity_t *self) +{ + self->NPC_type = "gonk"; + + SP_NPC_spawner( self ); + + //precache the Gonk sounds + NPC_Gonk_Precache(); +} + +/*QUAKED NPC_Droid_Mouse (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Mouse Droid - small, box shaped droid, first seen on the Death Star. Chewie yelled at it and it backed up and ran away. + +NOTARGET by default +*/ +void SP_NPC_Droid_Mouse( gentity_t *self) +{ + self->NPC_type = "mouse"; + + SP_NPC_spawner( self ); + + //precache the Mouse sounds + NPC_Mouse_Precache(); + +} + +/*QUAKED NPC_Droid_R2D2 (1 0 0) (-12 -12 -24) (12 12 40) IMPERIAL x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +R2D2 Droid - you probably know this one already. + +NOTARGET by default +*/ +void SP_NPC_Droid_R2D2( gentity_t *self) +{ + if ( self->spawnflags&1 ) + {//imperial skin + self->NPC_type = "r2d2_imp"; + } + else + { + self->NPC_type = "r2d2"; + } + + SP_NPC_spawner( self ); + + NPC_R2D2_Precache(); +} + +/*QUAKED NPC_Droid_R5D2 (1 0 0) (-12 -12 -24) (12 12 40) IMPERIAL ALWAYSDIE x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +ALWAYSDIE - won't go into spinning zombie AI when at low health. +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +R5D2 Droid - the droid originally chosen by Uncle Owen until it blew a bad motivator, and they took R2D2 instead. + +NOTARGET by default +*/ +void SP_NPC_Droid_R5D2( gentity_t *self) +{ + if ( self->spawnflags&1 ) + {//imperial skin + self->NPC_type = "r5d2_imp"; + } + else + { + self->NPC_type = "r5d2"; + } + + SP_NPC_spawner( self ); + + NPC_R5D2_Precache(); +} + +/*QUAKED NPC_Droid_Protocol (1 0 0) (-12 -12 -24) (12 12 40) IMPERIAL x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +NOTARGET by default +*/ +void SP_NPC_Droid_Protocol( gentity_t *self) +{ + if ( self->spawnflags&1 ) + {//imperial skin + self->NPC_type = "protocol_imp"; + } + else + { + self->NPC_type = "protocol"; + } + + SP_NPC_spawner( self ); + NPC_Protocol_Precache(); +} + + +//NPC console commands +/* +NPC_Spawn_f +*/ + +gentity_t *NPC_SpawnType( gentity_t *ent, char *npc_type, char *targetname, qboolean isVehicle ) +{ + gentity_t *NPCspawner = G_Spawn(); + vec3_t forward, end; + trace_t trace; + + if(!NPCspawner) + { + Com_Printf( S_COLOR_RED"NPC_Spawn Error: Out of entities!\n" ); + return NULL; + } + + NPCspawner->think = G_FreeEntity; + NPCspawner->nextthink = level.time + FRAMETIME; + + if ( !npc_type ) + { + return NULL; + } + + if (!npc_type[0]) + { + Com_Printf( S_COLOR_RED"Error, expected one of:\n"S_COLOR_WHITE" NPC spawn [NPC type (from ext_data/NPCs)]\n NPC spawn vehicle [VEH type (from ext_data/vehicles)]\n" ); + return NULL; + } + + if ( !ent || !ent->client ) + {//screw you, go away + return NULL; + } + + //rwwFIXMEFIXME: Care about who is issuing this command/other clients besides 0? + //Spawn it at spot of first player + //FIXME: will gib them! + AngleVectors(ent->client->ps.viewangles, forward, NULL, NULL); + VectorNormalize(forward); + VectorMA(ent->r.currentOrigin, 64, forward, end); + trap_Trace(&trace, ent->r.currentOrigin, NULL, NULL, end, 0, MASK_SOLID); + VectorCopy(trace.endpos, end); + end[2] -= 24; + trap_Trace(&trace, trace.endpos, NULL, NULL, end, 0, MASK_SOLID); + VectorCopy(trace.endpos, end); + end[2] += 24; + G_SetOrigin(NPCspawner, end); + VectorCopy(NPCspawner->r.currentOrigin, NPCspawner->s.origin); + //set the yaw so that they face away from player + NPCspawner->s.angles[1] = ent->client->ps.viewangles[1]; + + trap_LinkEntity(NPCspawner); + + NPCspawner->NPC_type = G_NewString( npc_type ); + + if ( targetname ) + { + NPCspawner->NPC_targetname = G_NewString(targetname); + } + + NPCspawner->count = 1; + + NPCspawner->delay = 0; + + //NPCspawner->spawnflags |= SFB_NOTSOLID; + + //NPCspawner->playerTeam = TEAM_FREE; + //NPCspawner->behaviorSet[BSET_SPAWN] = "common/guard"; + + if ( isVehicle ) + { + NPCspawner->classname = "NPC_Vehicle"; + } + + //call precache funcs for James' builds + if ( !Q_stricmp( "gonk", NPCspawner->NPC_type)) + { + NPC_Gonk_Precache(); + } + else if ( !Q_stricmp( "mouse", NPCspawner->NPC_type)) + { + NPC_Mouse_Precache(); + } + else if ( !Q_strncmp( "r2d2", NPCspawner->NPC_type, 4)) + { + NPC_R2D2_Precache(); + } + else if ( !Q_stricmp( "atst", NPCspawner->NPC_type)) + { + assert( 0 ); +// NPC_ATST_Precache(); + } + else if ( !Q_strncmp( "r5d2", NPCspawner->NPC_type, 4)) + { + NPC_R5D2_Precache(); + } + else if ( !Q_stricmp( "mark1", NPCspawner->NPC_type)) + { + assert( 0 ); +// NPC_Mark1_Precache(); + } + else if ( !Q_stricmp( "mark2", NPCspawner->NPC_type)) + { + assert( 0 ); +// NPC_Mark2_Precache(); + } + else if ( !Q_stricmp( "interrogator", NPCspawner->NPC_type)) + { + assert( 0 ); +// NPC_Interrogator_Precache(NULL); + } + else if ( !Q_stricmp( "probe", NPCspawner->NPC_type)) + { + NPC_Probe_Precache(); + } + else if ( !Q_stricmp( "seeker", NPCspawner->NPC_type)) + { + NPC_Seeker_Precache(); + } + else if ( !Q_stricmp( "remote", NPCspawner->NPC_type)) + { + NPC_Remote_Precache(); + } + else if ( !Q_strncmp( "shadowtrooper", NPCspawner->NPC_type, 13 ) ) + { + NPC_ShadowTrooper_Precache(); + } + else if ( !Q_stricmp( "minemonster", NPCspawner->NPC_type )) + { + NPC_MineMonster_Precache(); + } + else if ( !Q_stricmp( "howler", NPCspawner->NPC_type )) + { + NPC_Howler_Precache(); + } + else if ( !Q_stricmp( "sentry", NPCspawner->NPC_type )) + { + NPC_Sentry_Precache(); + } + else if ( !Q_stricmp( "protocol", NPCspawner->NPC_type )) + { + NPC_Protocol_Precache(); + } + else if ( !Q_stricmp( "galak_mech", NPCspawner->NPC_type )) + { + assert( 0 ); +// NPC_GalakMech_Precache(); + } + else if ( !Q_stricmp( "wampa", NPCspawner->NPC_type )) + { + NPC_Wampa_Precache(); + } + + return (NPC_Spawn_Do( NPCspawner, false )); +} + +void NPC_Spawn_f( gentity_t *ent ) +{ + char npc_type[1024]; + char targetname[1024]; + qboolean isVehicle = qfalse; + + trap_Argv(2, npc_type, 1024); + if ( Q_stricmp( "vehicle", npc_type ) == 0 ) + { + isVehicle = qtrue; + trap_Argv(3, npc_type, 1024); + trap_Argv(4, targetname, 1024); + } + else + { + trap_Argv(3, targetname, 1024); + } + + NPC_SpawnType( ent, npc_type, targetname, isVehicle ); +} + +/* +NPC_Kill_f +*/ +extern stringID_table_t TeamTable[]; +void NPC_Kill_f( void ) +{ + int n; + gentity_t *player; + char name[1024]; + team_t killTeam = TEAM_FREE; + qboolean killNonSF = qfalse; + + trap_Argv(2, name, 1024); + + if ( !name[0] ) + { + Com_Printf( S_COLOR_RED"Error, Expected:\n"); + Com_Printf( S_COLOR_RED"NPC kill '[NPC targetname]' - kills NPCs with certain targetname\n" ); + Com_Printf( S_COLOR_RED"or\n" ); + Com_Printf( S_COLOR_RED"NPC kill 'all' - kills all NPCs\n" ); + Com_Printf( S_COLOR_RED"or\n" ); + Com_Printf( S_COLOR_RED"NPC team '[teamname]' - kills all NPCs of a certain team ('nonally' is all but your allies)\n" ); + return; + } + + if ( Q_stricmp( "team", name ) == 0 ) + { + trap_Argv(3, name, 1024); + + if ( !name[0] ) + { + Com_Printf( S_COLOR_RED"NPC_Kill Error: 'npc kill team' requires a team name!\n" ); + Com_Printf( S_COLOR_RED"Valid team names are:\n"); + for ( n = (TEAM_FREE + 1); n < TEAM_NUM_TEAMS; n++ ) + { + Com_Printf( S_COLOR_RED"%s\n", TeamNames[n] ); + } + Com_Printf( S_COLOR_RED"nonally - kills all but your teammates\n" ); + return; + } + + if ( Q_stricmp( "nonally", name ) == 0 ) + { + killNonSF = qtrue; + } + else + { + killTeam = (team_t)GetIDForString( TeamTable, name ); + + if ( killTeam == TEAM_FREE ) + { + Com_Printf( S_COLOR_RED"NPC_Kill Error: team '%s' not recognized\n", name ); + Com_Printf( S_COLOR_RED"Valid team names are:\n"); + for ( n = (TEAM_FREE + 1); n < TEAM_NUM_TEAMS; n++ ) + { + Com_Printf( S_COLOR_RED"%s\n", TeamNames[n] ); + } + Com_Printf( S_COLOR_RED"nonally - kills all but your teammates\n" ); + return; + } + } + } + + for ( n = 1; n < ENTITYNUM_MAX_NORMAL; n++) + { + player = &g_entities[n]; + if (!player->inuse) { + continue; + } + if ( killNonSF ) + { + if ( player ) + { + if ( player->client ) + { + if ( player->client->playerTeam != NPCTEAM_PLAYER ) + { + Com_Printf( S_COLOR_GREEN"Killing NPC %s named %s\n", player->NPC_type, player->targetname ); + player->health = 0; + + if (player->die && player->client) + { + player->die(player, player, player, player->client->pers.maxHealth, MOD_UNKNOWN); + } + } + } + else if ( player->NPC_type && player->classname && player->classname[0] && Q_stricmp( "NPC_starfleet", player->classname ) != 0 ) + {//A spawner, remove it + Com_Printf( S_COLOR_GREEN"Removing NPC spawner %s with NPC named %s\n", player->NPC_type, player->NPC_targetname ); + G_FreeEntity( player ); + //FIXME: G_UseTargets2(player, player, player->NPC_target & player->target);? + } + } + } + else if ( player && player->NPC && player->client ) + { + if ( killTeam != TEAM_FREE ) + { + if ( player->client->playerTeam == killTeam ) + { + Com_Printf( S_COLOR_GREEN"Killing NPC %s named %s\n", player->NPC_type, player->targetname ); + player->health = 0; + if (player->die) + { + player->die(player, player, player, player->client->pers.maxHealth, MOD_UNKNOWN); + } + } + } + else if( (player->targetname && Q_stricmp( name, player->targetname ) == 0) + || Q_stricmp( name, "all" ) == 0 ) + { + Com_Printf( S_COLOR_GREEN"Killing NPC %s named %s\n", player->NPC_type, player->targetname ); + player->health = 0; + player->client->ps.stats[STAT_HEALTH] = 0; + if (player->die) + { + player->die(player, player, player, 100, MOD_UNKNOWN); + } + } + } + /* + else if ( player && (player->svFlags&SVF_NPC_PRECACHE) ) + {//a spawner + Com_Printf( S_COLOR_GREEN"Removing NPC spawner %s named %s\n", player->NPC_type, player->targetname ); + G_FreeEntity( player ); + } + */ + //rwwFIXMEFIXME: should really do something here. + } +} + +void NPC_PrintScore( gentity_t *ent ) +{ + Com_Printf( "%s: %d\n", ent->targetname, ent->client->ps.persistant[PERS_SCORE] ); +} + +/* +Svcmd_NPC_f + +parse and dispatch bot commands +*/ +qboolean showBBoxes = qfalse; +void Cmd_NPC_f( gentity_t *ent ) +{ + char cmd[1024]; + + trap_Argv( 1, cmd, 1024 ); + + if ( !cmd[0] ) + { + Com_Printf( "Valid NPC commands are:\n" ); + Com_Printf( " spawn [NPC type (from NCPCs.cfg)]\n" ); + Com_Printf( " kill [NPC targetname] or [all(kills all NPCs)] or 'team [teamname]'\n" ); + Com_Printf( " showbounds (draws exact bounding boxes of NPCs)\n" ); + Com_Printf( " score [NPC targetname] (prints number of kills per NPC)\n" ); + } + else if ( Q_stricmp( cmd, "spawn" ) == 0 ) + { + NPC_Spawn_f( ent ); + } + else if ( Q_stricmp( cmd, "kill" ) == 0 ) + { + NPC_Kill_f(); + } + else if ( Q_stricmp( cmd, "showbounds" ) == 0 ) + {//Toggle on and off + showBBoxes = showBBoxes ? qfalse : qtrue; + } + else if ( Q_stricmp ( cmd, "score" ) == 0 ) + { + char cmd2[1024]; + gentity_t *ent = NULL; + + trap_Argv(2, cmd2, 1024); + + if ( !cmd2[0] ) + {//Show the score for all NPCs + int i; + + Com_Printf( "SCORE LIST:\n" ); + for ( i = 0; i < ENTITYNUM_WORLD; i++ ) + { + ent = &g_entities[i]; + if ( !ent || !ent->client ) + { + continue; + } + NPC_PrintScore( ent ); + } + } + else + { + if ( (ent = G_Find( NULL, FOFS(targetname), cmd2 )) != NULL && ent->client ) + { + NPC_PrintScore( ent ); + } + else + { + Com_Printf( "ERROR: NPC score - no such NPC %s\n", cmd2 ); + } + } + } +} diff --git a/codemp/game/NPC_stats.c b/codemp/game/NPC_stats.c new file mode 100644 index 0000000..8ec4945 --- /dev/null +++ b/codemp/game/NPC_stats.c @@ -0,0 +1,3352 @@ +//NPC_stats.cpp +#include "b_local.h" +#include "b_public.h" +#include "anims.h" +#include "../ghoul2/G2.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../renderer/modelmem.h" +#endif + +extern qboolean NPCsPrecached; + +#include "../namespace_begin.h" +extern qboolean WP_SaberParseParms( const char *SaberName, saberInfo_t *saber ); +extern void WP_RemoveSaber( saberInfo_t *sabers, int saberNum ); +#include "../namespace_end.h" + +stringID_table_t TeamTable[] = +{ + ENUM2STRING(NPCTEAM_FREE), // caution, some code checks a team_t via "if (!team_t_varname)" so I guess this should stay as entry 0, great or what? -slc + ENUM2STRING(NPCTEAM_PLAYER), + ENUM2STRING(NPCTEAM_ENEMY), + ENUM2STRING(NPCTEAM_NEUTRAL), // most droids are team_neutral, there are some exceptions like Probe,Seeker,Interrogator + "", -1 +}; + +// this list was made using the model directories, this MUST be in the same order as the CLASS_ enum in teams.h +stringID_table_t ClassTable[] = +{ + ENUM2STRING(CLASS_NONE), // hopefully this will never be used by an npc), just covering all bases + ENUM2STRING(CLASS_ATST), // technically droid... + ENUM2STRING(CLASS_BARTENDER), + ENUM2STRING(CLASS_BESPIN_COP), + ENUM2STRING(CLASS_CLAW), + ENUM2STRING(CLASS_COMMANDO), + ENUM2STRING(CLASS_DESANN), + ENUM2STRING(CLASS_FISH), + ENUM2STRING(CLASS_FLIER2), + ENUM2STRING(CLASS_GALAK), + ENUM2STRING(CLASS_GLIDER), + ENUM2STRING(CLASS_GONK), // droid + ENUM2STRING(CLASS_GRAN), + ENUM2STRING(CLASS_HOWLER), +// ENUM2STRING(CLASS_RANCOR), + ENUM2STRING(CLASS_IMPERIAL), + ENUM2STRING(CLASS_IMPWORKER), + ENUM2STRING(CLASS_INTERROGATOR), // droid + ENUM2STRING(CLASS_JAN), + ENUM2STRING(CLASS_JEDI), + ENUM2STRING(CLASS_KYLE), + ENUM2STRING(CLASS_LANDO), + ENUM2STRING(CLASS_LIZARD), + ENUM2STRING(CLASS_LUKE), + ENUM2STRING(CLASS_MARK1), // droid + ENUM2STRING(CLASS_MARK2), // droid + ENUM2STRING(CLASS_GALAKMECH), // droid + ENUM2STRING(CLASS_MINEMONSTER), + ENUM2STRING(CLASS_MONMOTHA), + ENUM2STRING(CLASS_MORGANKATARN), + ENUM2STRING(CLASS_MOUSE), // droid + ENUM2STRING(CLASS_MURJJ), + ENUM2STRING(CLASS_PRISONER), + ENUM2STRING(CLASS_PROBE), // droid + ENUM2STRING(CLASS_PROTOCOL), // droid + ENUM2STRING(CLASS_R2D2), // droid + ENUM2STRING(CLASS_R5D2), // droid + ENUM2STRING(CLASS_REBEL), + ENUM2STRING(CLASS_REBORN), + ENUM2STRING(CLASS_REELO), + ENUM2STRING(CLASS_REMOTE), + ENUM2STRING(CLASS_RODIAN), + ENUM2STRING(CLASS_SEEKER), // droid + ENUM2STRING(CLASS_SENTRY), + ENUM2STRING(CLASS_SHADOWTROOPER), + ENUM2STRING(CLASS_STORMTROOPER), + ENUM2STRING(CLASS_SWAMP), + ENUM2STRING(CLASS_SWAMPTROOPER), + ENUM2STRING(CLASS_TAVION), + ENUM2STRING(CLASS_TRANDOSHAN), + ENUM2STRING(CLASS_UGNAUGHT), + ENUM2STRING(CLASS_JAWA), + ENUM2STRING(CLASS_WEEQUAY), + ENUM2STRING(CLASS_BOBAFETT), + //ENUM2STRING(CLASS_ROCKETTROOPER), + //ENUM2STRING(CLASS_PLAYER), + ENUM2STRING(CLASS_VEHICLE), + ENUM2STRING(CLASS_RANCOR), + ENUM2STRING(CLASS_WAMPA), + "", -1 +}; + +stringID_table_t BSTable[] = +{ + ENUM2STRING(BS_DEFAULT),//# default behavior for that NPC + ENUM2STRING(BS_ADVANCE_FIGHT),//# Advance to captureGoal and shoot enemies if you can + ENUM2STRING(BS_SLEEP),//# Play awake script when startled by sound + ENUM2STRING(BS_FOLLOW_LEADER),//# Follow your leader and shoot any enemies you come across + ENUM2STRING(BS_JUMP),//# Face navgoal and jump to it. + ENUM2STRING(BS_SEARCH),//# Using current waypoint as a base), search the immediate branches of waypoints for enemies + ENUM2STRING(BS_WANDER),//# Wander down random waypoint paths + ENUM2STRING(BS_NOCLIP),//# Moves through walls), etc. + ENUM2STRING(BS_REMOVE),//# Waits for player to leave PVS then removes itself + ENUM2STRING(BS_CINEMATIC),//# Does nothing but face it's angles and move to a goal if it has one + //the rest are internal only + "", -1, +}; + +#define stringIDExpand(str, strEnum) str, strEnum, ENUM2STRING(strEnum) + +stringID_table_t BSETTable[] = +{ + ENUM2STRING(BSET_SPAWN),//# script to use when first spawned + ENUM2STRING(BSET_USE),//# script to use when used + ENUM2STRING(BSET_AWAKE),//# script to use when awoken/startled + ENUM2STRING(BSET_ANGER),//# script to use when aquire an enemy + ENUM2STRING(BSET_ATTACK),//# script to run when you attack + ENUM2STRING(BSET_VICTORY),//# script to run when you kill someone + ENUM2STRING(BSET_LOSTENEMY),//# script to run when you can't find your enemy + ENUM2STRING(BSET_PAIN),//# script to use when take pain + ENUM2STRING(BSET_FLEE),//# script to use when take pain below 50% of health + ENUM2STRING(BSET_DEATH),//# script to use when killed + ENUM2STRING(BSET_DELAYED),//# script to run when self->delayScriptTime is reached + ENUM2STRING(BSET_BLOCKED),//# script to run when blocked by a friendly NPC or player + ENUM2STRING(BSET_BUMPED),//# script to run when bumped into a friendly NPC or player (can set bumpRadius) + ENUM2STRING(BSET_STUCK),//# script to run when blocked by a wall + ENUM2STRING(BSET_FFIRE),//# script to run when player shoots their own teammates + ENUM2STRING(BSET_FFDEATH),//# script to run when player kills a teammate + stringIDExpand("", BSET_INVALID), + "", -1, +}; + +#include "../namespace_begin.h" +extern stringID_table_t WPTable[]; +extern stringID_table_t FPTable[]; +#include "../namespace_end.h" + +char *TeamNames[TEAM_NUM_TEAMS] = +{ + "", +// "starfleet", +// "borg", +// "parasite", +// "scavengers", +// "klingon", +// "malon", +// "hirogen", +// "imperial", +// "stasis", +// "species8472", +// "dreadnought", +// "forge", +// "disguise", +// "player (not valid)" + "player", + "enemy", + "neutral" +}; + +// this list was made using the model directories, this MUST be in the same order as the CLASS_ enum in teams.h +char *ClassNames[CLASS_NUM_CLASSES] = +{ + "", // class none + "atst", + "bartender", + "bespin_cop", + "claw", + "commando", + "desann", + "fish", + "flier2", + "galak", + "glider", + "gonk", + "gran", + "howler", + "imperial", + "impworker", + "interrogator", + "jan", + "jedi", + "kyle", + "lando", + "lizard", + "luke", + "mark1", + "mark2", + "galak_mech", + "minemonster", + "monmotha", + "morgankatarn", + "mouse", + "murjj", + "prisoner", + "probe", + "protocol", + "r2d2", + "r5d2", + "rebel", + "reborn", + "reelo", + "remote", + "rodian", + "seeker", + "sentry", + "shadowtrooper", + "stormtrooper", + "swamp", + "swamptrooper", + "tavion", + "trandoshan", + "ugnaught", + "weequay", + "bobafett", + "vehicle", + "rancor", + "wampa", +}; + + +/* +NPC_ReactionTime +*/ +//FIXME use grandom in here +int NPC_ReactionTime ( void ) +{ + return 200 * ( 6 - NPCInfo->stats.reactions ); +} + +// +// parse support routines +// + +#include "../namespace_begin.h" +extern qboolean BG_ParseLiteral( const char **data, const char *string ); +#include "../namespace_end.h" + +// +// NPC parameters file : scripts/NPCs.cfg +// +#define MAX_NPC_DATA_SIZE 0x20000 +char NPCParms[MAX_NPC_DATA_SIZE]; +char NPCFile[MAX_QPATH]; + +/* +team_t TranslateTeamName( const char *name ) +{ + int n; + + for ( n = (NPCTEAM_FREE + 1); n < NPCTEAM_NUM_TEAMS; n++ ) + { + if ( Q_stricmp( TeamNames[n], name ) == 0 ) + { + return ((team_t) n); + } + } + + return NPCTEAM_FREE; +} + +class_t TranslateClassName( const char *name ) +{ + int n; + + for ( n = (CLASS_NONE + 1); n < CLASS_NUM_CLASSES; n++ ) + { + if ( Q_stricmp( ClassNames[n], name ) == 0 ) + { + return ((class_t) n); + } + } + + return CLASS_NONE; // I hope this never happens, maybe print a warning +} +*/ + +/* +static race_t TranslateRaceName( const char *name ) +{ + if ( !Q_stricmp( name, "human" ) ) + { + return RACE_HUMAN; + } + return RACE_NONE; +} +*/ +/* +static rank_t TranslateRankName( const char *name ) + + Should be used to determine pip bolt-ons +*/ +static rank_t TranslateRankName( const char *name ) +{ + if ( !Q_stricmp( name, "civilian" ) ) + { + return RANK_CIVILIAN; + } + + if ( !Q_stricmp( name, "crewman" ) ) + { + return RANK_CREWMAN; + } + + if ( !Q_stricmp( name, "ensign" ) ) + { + return RANK_ENSIGN; + } + + if ( !Q_stricmp( name, "ltjg" ) ) + { + return RANK_LT_JG; + } + + if ( !Q_stricmp( name, "lt" ) ) + { + return RANK_LT; + } + + if ( !Q_stricmp( name, "ltcomm" ) ) + { + return RANK_LT_COMM; + } + + if ( !Q_stricmp( name, "commander" ) ) + { + return RANK_COMMANDER; + } + + if ( !Q_stricmp( name, "captain" ) ) + { + return RANK_CAPTAIN; + } + + return RANK_CIVILIAN; +} + +#include "../namespace_begin.h" +extern saber_colors_t TranslateSaberColor( const char *name ); +#include "../namespace_end.h" + +/* static int MethodNameToNumber( const char *name ) { + if ( !Q_stricmp( name, "EXPONENTIAL" ) ) { + return METHOD_EXPONENTIAL; + } + if ( !Q_stricmp( name, "LINEAR" ) ) { + return METHOD_LINEAR; + } + if ( !Q_stricmp( name, "LOGRITHMIC" ) ) { + return METHOD_LOGRITHMIC; + } + if ( !Q_stricmp( name, "ALWAYS" ) ) { + return METHOD_ALWAYS; + } + if ( !Q_stricmp( name, "NEVER" ) ) { + return METHOD_NEVER; + } + return -1; +} + +static int ItemNameToNumber( const char *name, int itemType ) { +// int n; + + for ( n = 0; n < bg_numItems; n++ ) { + if ( bg_itemlist[n].type != itemType ) { + continue; + } + if ( Q_stricmp( bg_itemlist[n].classname, name ) == 0 ) { + return bg_itemlist[n].tag; + } + } + return -1; +} +*/ + +//rwwFIXMEFIXME: movetypes +/* +static int MoveTypeNameToEnum( const char *name ) +{ + if(!Q_stricmp("runjump", name)) + { + return MT_RUNJUMP; + } + else if(!Q_stricmp("walk", name)) + { + return MT_WALK; + } + else if(!Q_stricmp("flyswim", name)) + { + return MT_FLYSWIM; + } + else if(!Q_stricmp("static", name)) + { + return MT_STATIC; + } + + return MT_STATIC; +} +*/ + +//#define CONVENIENT_ANIMATION_FILE_DEBUG_THING + +#ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING +void SpewDebugStuffToFile(animation_t *anims) +{ + char BGPAFtext[40000]; + fileHandle_t f; + int i = 0; + + trap_FS_FOpenFile("file_of_debug_stuff_SP.txt", &f, FS_WRITE); + + if (!f) + { + return; + } + + BGPAFtext[0] = 0; + + while (i < MAX_ANIMATIONS) + { + strcat(BGPAFtext, va("%i %i\n", i, anims[i].frameLerp)); + i++; + } + + trap_FS_Write(BGPAFtext, strlen(BGPAFtext), f); + trap_FS_FCloseFile(f); +} +#endif + +qboolean G_ParseAnimFileSet( const char *filename, const char *animCFG, int *animFileIndex ) +{ + *animFileIndex = BG_ParseAnimationFile(filename, NULL, qfalse); + //if it's humanoid we should have it cached and return it, if it is not it will be loaded (unless it's also cached already) + + if (*animFileIndex == -1) + { + return qfalse; + } + + //I guess this isn't really even needed game-side. + //BG_ParseAnimationSndFile(filename, *animFileIndex); + return qtrue; +} + +void NPC_PrecacheAnimationCFG( const char *NPC_type ) +{ +#if 0 //rwwFIXMEFIXME: Actually precache stuff here. + char filename[MAX_QPATH]; + const char *token; + const char *value; + const char *p; + int junk; + + if ( !Q_stricmp( "random", NPC_type ) ) + {//sorry, can't precache a random just yet + return; + } + + p = NPCParms; + COM_BeginParseSession(NPCFile); + + // look for the right NPC + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + return; + + if ( !Q_stricmp( token, NPC_type ) ) + { + break; + } + + SkipBracedSection( &p ); + } + + if ( !p ) + { + return; + } + + if ( BG_ParseLiteral( &p, "{" ) ) + { + return; + } + + // parse the NPC info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPC_type ); + return; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + // legsmodel + if ( !Q_stricmp( token, "legsmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //must copy data out of this pointer into a different part of memory because the funcs we're about to call will call COM_ParseExt + Q_strncpyz( filename, value, sizeof( filename ) ); + G_ParseAnimFileSet( filename, filename, &junk ); + return; + } + + // playerModel + if ( !Q_stricmp( token, "playerModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + /* + char animName[MAX_QPATH]; + char *GLAName; + char *slash = NULL; + char *strippedName; + + int handle = gi.G2API_PrecacheGhoul2Model( va( "models/players/%s/model.glm", value ) ); + if ( handle > 0 )//FIXME: isn't 0 a valid handle? + { + GLAName = gi.G2API_GetAnimFileNameIndex( handle ); + if ( GLAName ) + { + Q_strncpyz( animName, GLAName, sizeof( animName ), qtrue ); + slash = strrchr( animName, '/' ); + if ( slash ) + { + *slash = 0; + } + strippedName = COM_SkipPath( animName ); + + //must copy data out of this pointer into a different part of memory because the funcs we're about to call will call COM_ParseExt + Q_strncpyz( filename, value, sizeof( filename ), qtrue ); + G_ParseAnimFileSet( value, strippedName, &junk );//qfalse ); + //FIXME: still not precaching the animsounds.cfg? + return; + } + } + */ + //rwwFIXMEFIXME: Do this properly. + } + } +#endif +} + +extern int NPC_WeaponsForTeam( team_t team, int spawnflags, const char *NPC_type ); +void NPC_PrecacheWeapons( team_t playerTeam, int spawnflags, char *NPCtype ) +{ + int weapons = NPC_WeaponsForTeam( playerTeam, spawnflags, NPCtype ); + int curWeap; + + for ( curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ ) + { + if (weapons & (1 << curWeap)) + { + RegisterItem(BG_FindItemForWeapon((weapon_t)curWeap)); + } + } + +#if 0 //rwwFIXMEFIXME: actually precache weapons here + int weapons = NPC_WeaponsForTeam( playerTeam, spawnflags, NPCtype ); + gitem_t *item; + for ( int curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ ) + { + if ( (weapons & ( 1 << curWeap )) ) + { + item = FindItemForWeapon( ((weapon_t)(curWeap)) ); //precache the weapon + CG_RegisterItemSounds( (item-bg_itemlist) ); + CG_RegisterItemVisuals( (item-bg_itemlist) ); + //precache the in-hand/in-world ghoul2 weapon model + + char weaponModel[64]; + + strcpy (weaponModel, weaponData[curWeap].weaponMdl); + if (char *spot = strstr(weaponModel, ".md3") ) { + *spot = 0; + spot = strstr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on + if (!spot) { + strcat (weaponModel, "_w"); + } + strcat (weaponModel, ".glm"); //and change to ghoul2 + } + gi.G2API_PrecacheGhoul2Model( weaponModel ); // correct way is item->world_model + } + } +#endif +} + +/* +void NPC_Precache ( char *NPCName ) + +Precaches NPC skins, tgas and md3s. + +*/ +void NPC_Precache ( gentity_t *spawner ) +{ + npcteam_t playerTeam = NPCTEAM_FREE; + const char *token; + const char *value; + const char *p; + char *patch; + char sound[MAX_QPATH]; + qboolean md3Model = qfalse; + char playerModel[MAX_QPATH]; + char customSkin[MAX_QPATH]; + + if ( !Q_stricmp( "random", spawner->NPC_type ) ) + {//sorry, can't precache a random just yet + return; + } + strcpy(customSkin,"default"); + + p = NPCParms; + COM_BeginParseSession(NPCFile); + + // look for the right NPC + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + return; + + if ( !Q_stricmp( token, spawner->NPC_type ) ) + { + break; + } + + SkipBracedSection( &p ); + } + + if ( !p ) + { + return; + } + + if ( BG_ParseLiteral( &p, "{" ) ) + { + return; + } + + // parse the NPC info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", spawner->NPC_type ); + return; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + // headmodel + if ( !Q_stricmp( token, "headmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + } + else + { + //Q_strncpyz( ri.headModelName, value, sizeof(ri.headModelName), qtrue); + } + md3Model = qtrue; + continue; + } + + // torsomodel + if ( !Q_stricmp( token, "torsomodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + } + else + { + //Q_strncpyz( ri.torsoModelName, value, sizeof(ri.torsoModelName), qtrue); + } + md3Model = qtrue; + continue; + } + + // legsmodel + if ( !Q_stricmp( token, "legsmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //Q_strncpyz( ri.legsModelName, value, sizeof(ri.legsModelName), qtrue); + md3Model = qtrue; + continue; + } + + // playerModel + if ( !Q_stricmp( token, "playerModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( playerModel, value, sizeof(playerModel)); + md3Model = qfalse; + continue; + } + + // customSkin + if ( !Q_stricmp( token, "customSkin" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( customSkin, value, sizeof(customSkin)); + continue; + } + + // playerTeam + if ( !Q_stricmp( token, "playerTeam" ) ) + { + char tk[4096]; //rww - hackilicious! + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //playerTeam = TranslateTeamName(value); + Com_sprintf(tk, sizeof(tk), "NPC%s", token); + playerTeam = (team_t)GetIDForString( TeamTable, tk ); + continue; + } + + + // snd + if ( !Q_stricmp( token, "snd" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + //if ( !(spawner->svFlags&SVF_NO_BASIC_SOUNDS) ) + if (1) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + spawner->s.csSounds_Std = G_SoundIndex( va("*$%s", sound) ); + } + continue; + } + + // sndcombat + if ( !Q_stricmp( token, "sndcombat" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + //if ( !(spawner->svFlags&SVF_NO_COMBAT_SOUNDS) ) + if (1) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + spawner->s.csSounds_Combat = G_SoundIndex( va("*$%s", sound) ); + } + continue; + } + + // sndextra + if ( !Q_stricmp( token, "sndextra" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + //if ( !(spawner->svFlags&SVF_NO_EXTRA_SOUNDS) ) + if (1) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + spawner->s.csSounds_Extra = G_SoundIndex( va("*$%s", sound) ); + } + continue; + } + + // sndjedi + if ( !Q_stricmp( token, "sndjedi" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + //if ( !(spawner->svFlags&SVF_NO_EXTRA_SOUNDS) ) + if (1) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + spawner->s.csSounds_Jedi = G_SoundIndex( va("*$%s", sound) ); + } + continue; + } + + if (!Q_stricmp(token, "weapon")) + { + int curWeap; + + if (COM_ParseString(&p, &value)) + { + continue; + } + + curWeap = GetIDForString( WPTable, value ); + + if (curWeap >= 0 && curWeap < WP_NUM_WEAPONS) + { + RegisterItem(BG_FindItemForWeapon((weapon_t)curWeap)); + } + continue; + } + } + + // If we're not a vehicle, then an error here would be valid... + if ( !spawner->client || spawner->client->NPC_class != CLASS_VEHICLE ) + { + if ( md3Model ) + { + Com_Printf("MD3 model using NPCs are not supported in MP\n"); + } + else + { //if we have a model/skin then index them so they'll be registered immediately + //when the client gets a configstring update. + char modelName[MAX_QPATH]; + + Com_sprintf(modelName, sizeof(modelName), "models/players/%s/model.glm", playerModel); + if (customSkin[0]) + { //append it after a * + strcat( modelName, va("*%s", customSkin) ); + } + + G_ModelIndex(modelName); + } + } + + //precache this NPC's possible weapons + NPC_PrecacheWeapons( playerTeam, spawner->spawnflags, spawner->NPC_type ); + +// CG_RegisterNPCCustomSounds( &ci ); +// CG_RegisterNPCEffects( playerTeam ); + //rwwFIXMEFIXME: same + //FIXME: Look for a "sounds" directory and precache death, pain, alert sounds +} + +#if 0 +void NPC_BuildRandom( gentity_t *NPC ) +{ + int sex, color, head; + + sex = Q_irand(0, 2); + color = Q_irand(0, 2); + switch( sex ) + { + case 0://female + head = Q_irand(0, 2); + switch( head ) + { + default: + case 0: + Q_strncpyz( NPC->client->renderInfo.headModelName, "garren", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 1: + Q_strncpyz( NPC->client->renderInfo.headModelName, "garren/salma", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 2: + Q_strncpyz( NPC->client->renderInfo.headModelName, "garren/mackey", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + color = Q_irand(3, 5);//torso needs to be afam + break; + } + switch( color ) + { + default: + case 0: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/gold", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 1: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 2: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/blue", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 3: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframG", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 4: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframR", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 5: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframB", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + } + Q_strncpyz( NPC->client->renderInfo.legsModelName, "crewfemale", sizeof(NPC->client->renderInfo.legsModelName), qtrue ); + break; + default: + case 1://male + case 2://male + head = Q_irand(0, 4); + switch( head ) + { + default: + case 0: + Q_strncpyz( NPC->client->renderInfo.headModelName, "chakotay/nelson", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 1: + Q_strncpyz( NPC->client->renderInfo.headModelName, "paris/chase", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 2: + Q_strncpyz( NPC->client->renderInfo.headModelName, "doctor/pasty", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 3: + Q_strncpyz( NPC->client->renderInfo.headModelName, "kim/durk", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 4: + Q_strncpyz( NPC->client->renderInfo.headModelName, "paris/kray", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + } + switch( color ) + { + default: + case 0: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin/red", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 1: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 2: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin/blue", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + //NOTE: 3 - 5 should be red, gold & blue, afram hands + } + Q_strncpyz( NPC->client->renderInfo.legsModelName, "crewthin", sizeof(NPC->client->renderInfo.legsModelName), qtrue ); + break; + } + + NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = Q_irand(87, 102)/100.0f; +// NPC->client->race = RACE_HUMAN; + NPC->NPC->rank = RANK_CREWMAN; + NPC->client->playerTeam = NPC->s.teamowner = TEAM_PLAYER; + NPC->client->clientInfo.customBasicSoundDir = "kyle";//FIXME: generic default? +} +#endif + +extern void SetupGameGhoul2Model(gentity_t *ent, char *modelname, char *skinName); +qboolean NPC_ParseParms( const char *NPCName, gentity_t *NPC ) +{ + const char *token; + const char *value; + const char *p; + int n; + float f; + char *patch; + char sound[MAX_QPATH]; + char playerModel[MAX_QPATH]; + char customSkin[MAX_QPATH]; + renderInfo_t *ri = &NPC->client->renderInfo; + gNPCstats_t *stats = NULL; + qboolean md3Model = qtrue; + char surfOff[1024]; + char surfOn[1024]; + qboolean parsingPlayer = qfalse; + vec3_t playerMins; + vec3_t playerMaxs; + int npcSaber1 = 0; + int npcSaber2 = 0; + + VectorSet(playerMins, -15, -15, DEFAULT_MINS_2); + VectorSet(playerMaxs, 15, 15, DEFAULT_MAXS_2); + + strcpy(customSkin,"default"); + if ( !NPCName || !NPCName[0]) + { + NPCName = "Player"; + } + + if ( !NPC->s.number && NPC->client != NULL ) + {//player, only want certain data + parsingPlayer = qtrue; + } + + if ( NPC->NPC ) + { + stats = &NPC->NPC->stats; +/* + NPC->NPC->allWeaponOrder[0] = WP_BRYAR_PISTOL; + NPC->NPC->allWeaponOrder[1] = WP_SABER; + NPC->NPC->allWeaponOrder[2] = WP_IMOD; + NPC->NPC->allWeaponOrder[3] = WP_SCAVENGER_RIFLE; + NPC->NPC->allWeaponOrder[4] = WP_TRICORDER; + NPC->NPC->allWeaponOrder[6] = WP_NONE; + NPC->NPC->allWeaponOrder[6] = WP_NONE; + NPC->NPC->allWeaponOrder[7] = WP_NONE; +*/ + // fill in defaults + stats->aggression = 3; + stats->aim = 3; + stats->earshot = 1024; + stats->evasion = 3; + stats->hfov = 90; + stats->intelligence = 3; + stats->move = 3; + stats->reactions = 3; + stats->vfov = 60; + stats->vigilance = 0.1f; + stats->visrange = 1024; + + stats->health = 0; + + stats->yawSpeed = 90; + stats->walkSpeed = 90; + stats->runSpeed = 300; + stats->acceleration = 15;//Increase/descrease speed this much per frame (20fps) + } + else + { + stats = NULL; + } + + //Set defaults + //FIXME: should probably put default torso and head models, but what about enemies + //that don't have any- like Stasis? + //Q_strncpyz( ri->headModelName, DEFAULT_HEADMODEL, sizeof(ri->headModelName), qtrue); + //Q_strncpyz( ri->torsoModelName, DEFAULT_TORSOMODEL, sizeof(ri->torsoModelName), qtrue); + //Q_strncpyz( ri->legsModelName, DEFAULT_LEGSMODEL, sizeof(ri->legsModelName), qtrue); + //FIXME: should we have one for weapon too? + memset( (char *)surfOff, 0, sizeof(surfOff) ); + memset( (char *)surfOn, 0, sizeof(surfOn) ); + + /* + ri->headYawRangeLeft = 50; + ri->headYawRangeRight = 50; + ri->headPitchRangeUp = 40; + ri->headPitchRangeDown = 50; + ri->torsoYawRangeLeft = 60; + ri->torsoYawRangeRight = 60; + ri->torsoPitchRangeUp = 30; + ri->torsoPitchRangeDown = 70; + */ + + ri->headYawRangeLeft = 80; + ri->headYawRangeRight = 80; + ri->headPitchRangeUp = 45; + ri->headPitchRangeDown = 45; + ri->torsoYawRangeLeft = 60; + ri->torsoYawRangeRight = 60; + ri->torsoPitchRangeUp = 30; + ri->torsoPitchRangeDown = 50; + + VectorCopy(playerMins, NPC->r.mins); + VectorCopy(playerMaxs, NPC->r.maxs); + NPC->client->ps.crouchheight = CROUCH_MAXS_2; + NPC->client->ps.standheight = DEFAULT_MAXS_2; + + //rwwFIXMEFIXME: ... + /* + NPC->client->moveType = MT_RUNJUMP; + + NPC->client->dismemberProbHead = 100; + NPC->client->dismemberProbArms = 100; + NPC->client->dismemberProbHands = 100; + NPC->client->dismemberProbWaist = 100; + NPC->client->dismemberProbLegs = 100; + + NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = 1.0f; + */ + + NPC->client->ps.customRGBA[0]=255; + NPC->client->ps.customRGBA[1]=255; + NPC->client->ps.customRGBA[2]=255; + NPC->client->ps.customRGBA[3]=255; + + if ( !Q_stricmp( "random", NPCName ) ) + {//Randomly assemble a starfleet guy + //NPC_BuildRandom( NPC ); + Com_Printf("RANDOM NPC NOT SUPPORTED IN MP\n"); + return qfalse; + } + else + { + int fp; + + p = NPCParms; + COM_BeginParseSession(NPCFile); + + // look for the right NPC + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return qfalse; + } + + if ( !Q_stricmp( token, NPCName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { + return qfalse; + } + + if ( BG_ParseLiteral( &p, "{" ) ) + { + return qfalse; + } + + // parse the NPC info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPCName ); + return qfalse; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + //===MODEL PROPERTIES=========================================================== + // custom color + if ( !Q_stricmp( token, "customRGBA" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( !Q_stricmp( value, "random") ) + { + NPC->client->ps.customRGBA[0]=Q_irand(0,255); + NPC->client->ps.customRGBA[1]=Q_irand(0,255); + NPC->client->ps.customRGBA[2]=Q_irand(0,255); + NPC->client->ps.customRGBA[3]=255; + } + else + { + NPC->client->ps.customRGBA[0]=atoi(value); + + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + NPC->client->ps.customRGBA[1]=n; + + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + NPC->client->ps.customRGBA[2]=n; + + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + NPC->client->ps.customRGBA[3]=n; + } + continue; + } + + // headmodel + if ( !Q_stricmp( token, "headmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + //Zero the head clamp range so the torso & legs don't lag behind + ri->headYawRangeLeft = + ri->headYawRangeRight = + ri->headPitchRangeUp = + ri->headPitchRangeDown = 0; + } + continue; + } + + // torsomodel + if ( !Q_stricmp( token, "torsomodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + //Zero the torso clamp range so the legs don't lag behind + ri->torsoYawRangeLeft = + ri->torsoYawRangeRight = + ri->torsoPitchRangeUp = + ri->torsoPitchRangeDown = 0; + } + continue; + } + + // legsmodel + if ( !Q_stricmp( token, "legsmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + /* + Q_strncpyz( ri->legsModelName, value, sizeof(ri->legsModelName), qtrue); + //Need to do this here to get the right index + G_ParseAnimFileSet( ri->legsModelName, ri->legsModelName, &ci->animFileIndex ); + */ + continue; + } + + // playerModel + if ( !Q_stricmp( token, "playerModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( playerModel, value, sizeof(playerModel)); + md3Model = qfalse; + continue; + } + + // customSkin + if ( !Q_stricmp( token, "customSkin" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( customSkin, value, sizeof(customSkin)); + continue; + } + + // surfOff + if ( !Q_stricmp( token, "surfOff" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( surfOff[0] ) + { + Q_strcat( (char *)surfOff, sizeof(surfOff), "," ); + Q_strcat( (char *)surfOff, sizeof(surfOff), value ); + } + else + { + Q_strncpyz( surfOff, value, sizeof(surfOff)); + } + continue; + } + + // surfOn + if ( !Q_stricmp( token, "surfOn" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( surfOn[0] ) + { + Q_strcat( (char *)surfOn, sizeof(surfOn), "," ); + Q_strcat( (char *)surfOn, sizeof(surfOn), value ); + } + else + { + Q_strncpyz( surfOn, value, sizeof(surfOn)); + } + continue; + } + + //headYawRangeLeft + if ( !Q_stricmp( token, "headYawRangeLeft" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + ri->headYawRangeLeft = n; + continue; + } + + //headYawRangeRight + if ( !Q_stricmp( token, "headYawRangeRight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + ri->headYawRangeRight = n; + continue; + } + + //headPitchRangeUp + if ( !Q_stricmp( token, "headPitchRangeUp" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + ri->headPitchRangeUp = n; + continue; + } + + //headPitchRangeDown + if ( !Q_stricmp( token, "headPitchRangeDown" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + ri->headPitchRangeDown = n; + continue; + } + + //torsoYawRangeLeft + if ( !Q_stricmp( token, "torsoYawRangeLeft" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + ri->torsoYawRangeLeft = n; + continue; + } + + //torsoYawRangeRight + if ( !Q_stricmp( token, "torsoYawRangeRight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + ri->torsoYawRangeRight = n; + continue; + } + + //torsoPitchRangeUp + if ( !Q_stricmp( token, "torsoPitchRangeUp" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + ri->torsoPitchRangeUp = n; + continue; + } + + //torsoPitchRangeDown + if ( !Q_stricmp( token, "torsoPitchRangeDown" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + ri->torsoPitchRangeDown = n; + continue; + } + + // Uniform XYZ scale + if ( !Q_stricmp( token, "scale" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + NPC->client->ps.iModelScale = n; //so the client knows + if (n >= 1024) + { +#ifndef FINAL_BUILD + Com_Printf("WARNING: MP does not support scaling up to or over 1024%\n"); +#endif + n = 1023; + } + + NPC->modelScale[0] = NPC->modelScale[1] = NPC->modelScale[2] = n/100.0f; + } + continue; + } + + //X scale + if ( !Q_stricmp( token, "scaleX" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + Com_Printf("MP doesn't support xyz scaling, use 'scale'.\n"); + //NPC->s.modelScale[0] = n/100.0f; + } + continue; + } + + //Y scale + if ( !Q_stricmp( token, "scaleY" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + Com_Printf("MP doesn't support xyz scaling, use 'scale'.\n"); + //NPC->s.modelScale[1] = n/100.0f; + } + continue; + } + + //Z scale + if ( !Q_stricmp( token, "scaleZ" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + Com_Printf("MP doesn't support xyz scaling, use 'scale'.\n"); + // NPC->s.modelScale[2] = n/100.0f; + } + continue; + } + + //===AI STATS===================================================================== + if ( !parsingPlayer ) + { + // aggression + if ( !Q_stricmp( token, "aggression" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->aggression = n; + } + continue; + } + + // aim + if ( !Q_stricmp( token, "aim" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->aim = n; + } + continue; + } + + // earshot + if ( !Q_stricmp( token, "earshot" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->earshot = f; + } + continue; + } + + // evasion + if ( !Q_stricmp( token, "evasion" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + if ( NPC->NPC ) + { + stats->evasion = n; + } + continue; + } + + // hfov + if ( !Q_stricmp( token, "hfov" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 30 || n > 180 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->hfov = n;// / 2; //FIXME: Why was this being done?! + } + continue; + } + + // intelligence + if ( !Q_stricmp( token, "intelligence" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->intelligence = n; + } + continue; + } + + // move + if ( !Q_stricmp( token, "move" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->move = n; + } + continue; + } + + // reactions + if ( !Q_stricmp( token, "reactions" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->reactions = n; + } + continue; + } + + // shootDistance + if ( !Q_stricmp( token, "shootDistance" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->shootDistance = f; + } + continue; + } + + // vfov + if ( !Q_stricmp( token, "vfov" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 30 || n > 180 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->vfov = n / 2; + } + continue; + } + + // vigilance + if ( !Q_stricmp( token, "vigilance" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->vigilance = f; + } + continue; + } + + // visrange + if ( !Q_stricmp( token, "visrange" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->visrange = f; + } + continue; + } + + // race + // if ( !Q_stricmp( token, "race" ) ) + // { + // if ( COM_ParseString( &p, &value ) ) + // { + // continue; + // } + // NPC->client->race = TranslateRaceName(value); + // continue; + // } + + // rank + if ( !Q_stricmp( token, "rank" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->NPC ) + { + NPC->NPC->rank = TranslateRankName(value); + } + continue; + } + } + + // health + if ( !Q_stricmp( token, "health" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + if ( NPC->NPC ) + { + stats->health = n; + } + else if ( parsingPlayer ) + { + NPC->client->ps.stats[STAT_MAX_HEALTH] = NPC->client->pers.maxHealth = n; + } + continue; + } + + // fullName + if ( !Q_stricmp( token, "fullName" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + NPC->fullName = G_NewString(value); + continue; + } + + // playerTeam + if ( !Q_stricmp( token, "playerTeam" ) ) + { + char tk[4096]; //rww - hackilicious! + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Com_sprintf(tk, sizeof(tk), "NPC%s", token); + NPC->client->playerTeam = NPC->s.teamowner = (team_t)GetIDForString( TeamTable, tk );//TranslateTeamName(value); + continue; + } + + // enemyTeam + if ( !Q_stricmp( token, "enemyTeam" ) ) + { + char tk[4096]; //rww - hackilicious! + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Com_sprintf(tk, sizeof(tk), "NPC%s", token); + NPC->client->enemyTeam = (team_t)GetIDForString( TeamTable, tk );//TranslateTeamName(value); + continue; + } + + // class + if ( !Q_stricmp( token, "class" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + NPC->client->NPC_class = (class_t)GetIDForString( ClassTable, value ); + NPC->s.NPC_class = NPC->client->NPC_class; //we actually only need this value now, but at the moment I don't feel like changing the 200+ references to client->NPC_class. + + // No md3's for vehicles. + if ( NPC->client->NPC_class == CLASS_VEHICLE ) + { + if ( !NPC->m_pVehicle ) + {//you didn't spawn this guy right! + Com_Printf ( S_COLOR_RED "ERROR: Tried to spawn a vehicle NPC (%s) without using NPC_Vehicle or 'NPC spawn vehicle '!!! Bad, bad, bad! Shame on you!\n", NPCName ); + return qfalse; + } + md3Model = qfalse; + } + + continue; + } + + // dismemberment probability for head + if ( !Q_stricmp( token, "dismemberProbHead" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + // NPC->client->dismemberProbHead = n; + //rwwFIXMEFIXME: support for this? + } + continue; + } + + // dismemberment probability for arms + if ( !Q_stricmp( token, "dismemberProbArms" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + // NPC->client->dismemberProbArms = n; + } + continue; + } + + // dismemberment probability for hands + if ( !Q_stricmp( token, "dismemberProbHands" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + // NPC->client->dismemberProbHands = n; + } + continue; + } + + // dismemberment probability for waist + if ( !Q_stricmp( token, "dismemberProbWaist" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + // NPC->client->dismemberProbWaist = n; + } + continue; + } + + // dismemberment probability for legs + if ( !Q_stricmp( token, "dismemberProbLegs" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + // NPC->client->dismemberProbLegs = n; + } + continue; + } + + //===MOVEMENT STATS============================================================ + + if ( !Q_stricmp( token, "width" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + + NPC->r.mins[0] = NPC->r.mins[1] = -n; + NPC->r.maxs[0] = NPC->r.maxs[1] = n; + continue; + } + + if ( !Q_stricmp( token, "height" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + if ( NPC->client->NPC_class == CLASS_VEHICLE + && NPC->m_pVehicle + && NPC->m_pVehicle->m_pVehicleInfo + && NPC->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + {//a flying vehicle's origin must be centered in bbox and it should spawn on the ground + //trace_t tr; + //vec3_t bottom; + //float adjust = 32.0f; + NPC->r.maxs[2] = NPC->client->ps.standheight = (n/2.0f); + NPC->r.mins[2] = -NPC->r.maxs[2]; + NPC->s.origin[2] += (DEFAULT_MINS_2-NPC->r.mins[2])+0.125f; + VectorCopy(NPC->s.origin, NPC->client->ps.origin); + VectorCopy(NPC->s.origin, NPC->r.currentOrigin); + G_SetOrigin( NPC, NPC->s.origin ); + trap_LinkEntity(NPC); + //now trace down + /* + VectorCopy( NPC->s.origin, bottom ); + bottom[2] -= adjust; + trap_Trace( &tr, NPC->s.origin, NPC->r.mins, NPC->r.maxs, bottom, NPC->s.number, MASK_NPCSOLID ); + if ( !tr.allsolid && !tr.startsolid ) + { + G_SetOrigin( NPC, tr.endpos ); + trap_LinkEntity(NPC); + } + */ + } + else + { + NPC->r.mins[2] = DEFAULT_MINS_2;//Cannot change + NPC->r.maxs[2] = NPC->client->ps.standheight = n + DEFAULT_MINS_2; + } + NPC->radius = n; + continue; + } + + if ( !Q_stricmp( token, "crouchheight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + + NPC->client->ps.crouchheight = n + DEFAULT_MINS_2; + continue; + } + + if ( !parsingPlayer ) + { + if ( !Q_stricmp( token, "movetype" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( Q_stricmp( "flyswim", value ) == 0 ) + { + NPC->client->ps.eFlags2 |= EF2_FLYING; + } + //NPC->client->moveType = (movetype_t)MoveTypeNameToEnum(value); + //rwwFIXMEFIXME: support for movetypes + continue; + } + + // yawSpeed + if ( !Q_stricmp( token, "yawSpeed" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n <= 0) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->yawSpeed = ((float)(n)); + } + continue; + } + + // walkSpeed + if ( !Q_stricmp( token, "walkSpeed" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + if ( NPC->NPC ) + { + stats->walkSpeed = n; + } + continue; + } + + //runSpeed + if ( !Q_stricmp( token, "runSpeed" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + if ( NPC->NPC ) + { + stats->runSpeed = n; + } + continue; + } + + //acceleration + if ( !Q_stricmp( token, "acceleration" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + if ( NPC->NPC ) + { + stats->acceleration = n; + } + continue; + } + //sex - skip in MP + if ( !Q_stricmp( token, "sex" ) ) + { + SkipRestOfLine( &p ); + continue; + } +//===MISC=============================================================================== + // default behavior + if ( !Q_stricmp( token, "behavior" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < BS_DEFAULT || n >= NUM_BSTATES ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); +#endif + continue; + } + if ( NPC->NPC ) + { + NPC->NPC->defaultBehavior = (bState_t)(n); + } + continue; + } + } + + // snd + if ( !Q_stricmp( token, "snd" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //if ( !(NPC->svFlags&SVF_NO_BASIC_SOUNDS) ) + if (1) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + // ci->customBasicSoundDir = G_NewString( sound ); + //rwwFIXMEFIXME: Hooray for violating client server rules + } + continue; + } + + // sndcombat + if ( !Q_stricmp( token, "sndcombat" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //if ( !(NPC->svFlags&SVF_NO_COMBAT_SOUNDS) ) + if (1) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + // ci->customCombatSoundDir = G_NewString( sound ); + } + continue; + } + + // sndextra + if ( !Q_stricmp( token, "sndextra" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //if ( !(NPC->svFlags&SVF_NO_EXTRA_SOUNDS) ) + if (1) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + // ci->customExtraSoundDir = G_NewString( sound ); + } + continue; + } + + // sndjedi + if ( !Q_stricmp( token, "sndjedi" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //if ( !(NPC->svFlags&SVF_NO_EXTRA_SOUNDS) ) + if (1) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + //ci->customJediSoundDir = G_NewString( sound ); + } + continue; + } + + //New NPC/jedi stats: + //starting weapon + if ( !Q_stricmp( token, "weapon" ) ) + { + int weap; + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //FIXME: need to precache the weapon, too? (in above func) + weap = GetIDForString( WPTable, value ); + if ( weap >= WP_NONE && weap <= WP_NUM_WEAPONS )///*WP_BLASTER_PISTOL*/WP_SABER ) //?! + { + NPC->client->ps.weapon = weap; + NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << NPC->client->ps.weapon ); + if ( weap > WP_NONE ) + { + // RegisterItem( FindItemForWeapon( (weapon_t)(NPC->client->ps.weapon) ) ); //precache the weapon + NPC->client->ps.ammo[weaponData[NPC->client->ps.weapon].ammoIndex] = 100;//FIXME: max ammo! + } + } + continue; + } + + if ( !parsingPlayer ) + { + //altFire + if ( !Q_stricmp( token, "altFire" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( NPC->NPC ) + { + if ( n != 0 ) + { + NPC->NPC->scriptFlags |= SCF_ALT_FIRE; + } + } + continue; + } + //Other unique behaviors/numbers that are currently hardcoded? + } + + //force powers + fp = GetIDForString( FPTable, token ); + if ( fp >= FP_FIRST && fp < NUM_FORCE_POWERS ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + //FIXME: need to precache the fx, too? (in above func) + //cap + if ( n > 5 ) + { + n = 5; + } + else if ( n < 0 ) + { + n = 0; + } + if ( n ) + {//set + NPC->client->ps.fd.forcePowersKnown |= ( 1 << fp ); + } + else + {//clear + NPC->client->ps.fd.forcePowersKnown &= ~( 1 << fp ); + } + NPC->client->ps.fd.forcePowerLevel[fp] = n; + continue; + } + + //max force power + if ( !Q_stricmp( token, "forcePowerMax" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + NPC->client->ps.fd.forcePowerMax = n; + continue; + } + + //force regen rate - default is 100ms + if ( !Q_stricmp( token, "forceRegenRate" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + //NPC->client->ps.forcePowerRegenRate = n; + //rwwFIXMEFIXME: support this? + continue; + } + + //force regen amount - default is 1 (points per second) + if ( !Q_stricmp( token, "forceRegenAmount" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + //NPC->client->ps.forcePowerRegenAmount = n; + //rwwFIXMEFIXME: support this? + continue; + } + + //have a sabers.cfg and just name your saber in your NPCs.cfg/ICARUS script + //saber name + if ( !Q_stricmp( token, "saber" ) ) + { + char *saberName; + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + saberName = (char *)BG_TempAlloc(4096);//G_NewString( value ); + strcpy(saberName, value); + + WP_SaberParseParms( saberName, &NPC->client->saber[0] ); + npcSaber1 = G_ModelIndex(va("@%s", saberName)); + + BG_TempFree(4096); + continue; + } + + //second saber name + if ( !Q_stricmp( token, "saber2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if ( !NPC->client->saber[0].twoHanded ) + {//can't use a second saber if first one is a two-handed saber...? + char *saberName = (char *)BG_TempAlloc(4096);//G_NewString( value ); + strcpy(saberName, value); + + WP_SaberParseParms( saberName, &NPC->client->saber[1] ); + if ( NPC->client->saber[1].twoHanded ) + {//tsk tsk, can't use a twoHanded saber as second saber + WP_RemoveSaber( NPC->client->saber, 1 ); + } + else + { + //NPC->client->ps.dualSabers = qtrue; + npcSaber2 = G_ModelIndex(va("@%s", saberName)); + } + BG_TempFree(4096); + } + continue; + } + + // saberColor + if ( !Q_stricmp( token, "saberColor" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + saber_colors_t color = TranslateSaberColor( value ); + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[0].blade[n].color = color; + } + } + continue; + } + + if ( !Q_stricmp( token, "saberColor2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[1].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[2].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor4" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[3].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor5" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[4].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor6" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[5].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor7" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[6].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor8" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[7].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + saber_colors_t color = TranslateSaberColor( value ); + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[1].blade[n].color = color; + } + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[1].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[2].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color4" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[3].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color5" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[4].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color6" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[5].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color7" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[6].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color8" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[7].color = TranslateSaberColor( value ); + } + continue; + } + + //saber length + if ( !Q_stricmp( token, "saberLength" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[0].blade[n].lengthMax = f; + } + continue; + } + + if ( !Q_stricmp( token, "saberLength2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[1].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength3" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[2].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength4" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[3].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength5" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[4].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength6" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[5].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength7" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[6].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength8" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[7].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[1].blade[n].lengthMax = f; + } + continue; + } + + if ( !Q_stricmp( token, "saber2Length2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[1].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length3" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[2].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length4" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[3].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length5" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[4].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length6" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[5].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length7" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[6].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length8" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[7].lengthMax = f; + continue; + } + + //saber radius + if ( !Q_stricmp( token, "saberRadius" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[0].blade[n].radius = f; + } + continue; + } + + if ( !Q_stricmp( token, "saberRadius2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[1].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius3" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[2].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius4" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[3].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius5" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[4].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius6" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[5].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius7" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[6].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius8" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[7].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[1].blade[n].radius = f; + } + continue; + } + + if ( !Q_stricmp( token, "saber2Radius2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[1].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius3" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[2].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius4" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[3].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius5" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[4].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius6" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[5].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius7" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[6].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius8" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[7].radius = f; + continue; + } + + //ADD: + //saber sounds (on, off, loop) + //loop sound (like Vader's breathing or droid bleeps, etc.) + + //starting saber style + if ( !Q_stricmp( token, "saberStyle" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( n < 0 ) + { + n = 0; + } + else if ( n > 5 ) + { + n = 5; + } + NPC->client->ps.fd.saberAnimLevel = n; + /* + if ( parsingPlayer ) + { + cg.saberAnimLevelPending = n; + } + */ + continue; + } + + if ( !parsingPlayer ) + { +#ifndef FINAL_BUILD + Com_Printf( "WARNING: unknown keyword '%s' while parsing '%s'\n", token, NPCName ); +#endif + } + SkipRestOfLine( &p ); + } + } + +/* +Ghoul2 Insert Start +*/ + if ( !md3Model ) + { + qboolean setTypeBack = qfalse; + + if (npcSaber1 == 0) + { //use "kyle" for a default then + npcSaber1 = G_ModelIndex("@Kyle"); + WP_SaberParseParms( "Kyle", &NPC->client->saber[0] ); + } + + NPC->s.npcSaber1 = npcSaber1; + NPC->s.npcSaber2 = npcSaber2; + + if (!customSkin[0]) + { + strcpy(customSkin, "default"); + } + + if ( NPC->client && NPC->client->NPC_class == CLASS_VEHICLE ) + { //vehicles want their names fed in as models + //we put the $ in front to indicate a name and not a model + strcpy(playerModel, va("$%s", NPCName)); + } +#ifdef _XBOX + // Gotta do this so NPC models don't take up model memory space + // reserved for players + ModelMem.SetNPCMode(true); +#endif + SetupGameGhoul2Model(NPC, playerModel, customSkin); +#ifdef _XBOX + ModelMem.SetNPCMode(false); +#endif + + if (!NPC->NPC_type) + { //just do this for now so NPC_Precache can see the name. + NPC->NPC_type = (char *)NPCName; + setTypeBack = qtrue; + } + + NPC_Precache(NPC); //this will just soundindex some values for sounds on the client, + + if (setTypeBack) + { //don't want this being set if we aren't ready yet. + NPC->NPC_type = NULL; + } + } + else + { + Com_Printf("MD3 MODEL NPC'S ARE NOT SUPPORTED IN MP!\n"); + return qfalse; + } +/* +Ghoul2 Insert End +*/ + /* + if( NPCsPrecached ) + {//Spawning in after initial precache, our models are precached, we just need to set our clientInfo + CG_RegisterClientModels( NPC->s.number ); + CG_RegisterNPCCustomSounds( ci ); + CG_RegisterNPCEffects( NPC->client->playerTeam ); + } + */ + //rwwFIXMEFIXME: Do something here I guess to properly precache stuff. + + return qtrue; +} + +#ifdef _XBOX +char *npcParseBuffer = NULL; +#else +char npcParseBuffer[MAX_NPC_DATA_SIZE]; +#endif + +void NPC_LoadParms( void ) +{ + int len, totallen, npcExtFNLen, mainBlockLen, fileCnt, i; +// const char *filename = "ext_data/NPC2.cfg"; + char /**buffer,*/ *holdChar, *marker; + char npcExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + len = 0; + + //remember where to store the next one + totallen = mainBlockLen = len; + marker = NPCParms+totallen; + *marker = 0; + + //now load in the extra .npc extensions + fileCnt = trap_FS_GetFileList("ext_data/NPCs", ".npc", npcExtensionListBuf, sizeof(npcExtensionListBuf) ); + +#ifdef _XBOX + npcParseBuffer = (char *) Z_Malloc(MAX_NPC_DATA_SIZE, TAG_TEMP_WORKSPACE, qfalse, 4); +#endif + + holdChar = npcExtensionListBuf; + for ( i = 0; i < fileCnt; i++, holdChar += npcExtFNLen + 1 ) + { + npcExtFNLen = strlen( holdChar ); + +// Com_Printf( "Parsing %s\n", holdChar ); + + len = trap_FS_FOpenFile(va( "ext_data/NPCs/%s", holdChar), &f, FS_READ); + + if ( len == -1 ) + { + Com_Printf( "error reading file\n" ); + } + else + { + if ( totallen + len >= MAX_NPC_DATA_SIZE ) { + G_Error( "NPC extensions (*.npc) are too large" ); + } + trap_FS_Read(npcParseBuffer, len, f); + npcParseBuffer[len] = 0; + + strcat( marker, npcParseBuffer ); + strcat(marker, "\n"); + trap_FS_FCloseFile(f); + + totallen += len; + marker = NPCParms+totallen; + //*marker = 0; //rww - make sure this is null or strcat will not append to the correct place + //rww 12/19/02-actually the probelm was npcParseBuffer not being nul-term'd, which could cause issues in the strcat too + } + } + +#ifdef _XBOX + Z_Free(npcParseBuffer); + npcParseBuffer = NULL; +#endif + +} diff --git a/codemp/game/NPC_utils.c b/codemp/game/NPC_utils.c new file mode 100644 index 0000000..5385fed --- /dev/null +++ b/codemp/game/NPC_utils.c @@ -0,0 +1,1788 @@ +//NPC_utils.cpp + +#include "b_local.h" +#include "../icarus/Q3_Interface.h" +#include "../ghoul2/G2.h" + +int teamNumbers[TEAM_NUM_TEAMS]; +int teamStrength[TEAM_NUM_TEAMS]; +int teamCounter[TEAM_NUM_TEAMS]; + +#define VALID_ATTACK_CONE 2.0f //Degrees +extern void G_DebugPrint( int level, const char *format, ... ); + +/* +void CalcEntitySpot ( gentity_t *ent, spot_t spot, vec3_t point ) + +Added: Uses shootAngles if a NPC has them + +*/ +void CalcEntitySpot ( const gentity_t *ent, const spot_t spot, vec3_t point ) +{ + vec3_t forward, up, right; + vec3_t start, end; + trace_t tr; + + if ( !ent ) + { + return; + } + switch ( spot ) + { + case SPOT_ORIGIN: + if(VectorCompare(ent->r.currentOrigin, vec3_origin)) + {//brush + VectorSubtract(ent->r.absmax, ent->r.absmin, point);//size + VectorMA(ent->r.absmin, 0.5, point, point); + } + else + { + VectorCopy ( ent->r.currentOrigin, point ); + } + break; + + case SPOT_CHEST: + case SPOT_HEAD: + if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) /*&& (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD)*/ ) + {//Actual tag_head eyespot! + //FIXME: Stasis aliens may have a problem here... + VectorCopy( ent->client->renderInfo.eyePoint, point ); + if ( ent->client->NPC_class == CLASS_ATST ) + {//adjust up some + point[2] += 28;//magic number :) + } + if ( ent->NPC ) + {//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards + point[0] = ent->r.currentOrigin[0]; + point[1] = ent->r.currentOrigin[1]; + } + /* + else if (ent->s.eType == ET_PLAYER ) + { + SubtractLeanOfs( ent, point ); + } + */ + } + else + { + VectorCopy ( ent->r.currentOrigin, point ); + if ( ent->client ) + { + point[2] += ent->client->ps.viewheight; + } + } + if ( spot == SPOT_CHEST && ent->client ) + { + if ( ent->client->NPC_class != CLASS_ATST ) + {//adjust up some + point[2] -= ent->r.maxs[2]*0.2f; + } + } + break; + + case SPOT_HEAD_LEAN: + if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) /*&& (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD*/ ) + {//Actual tag_head eyespot! + //FIXME: Stasis aliens may have a problem here... + VectorCopy( ent->client->renderInfo.eyePoint, point ); + if ( ent->client->NPC_class == CLASS_ATST ) + {//adjust up some + point[2] += 28;//magic number :) + } + if ( ent->NPC ) + {//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards + point[0] = ent->r.currentOrigin[0]; + point[1] = ent->r.currentOrigin[1]; + } + /* + else if ( ent->s.eType == ET_PLAYER ) + { + SubtractLeanOfs( ent, point ); + } + */ + //NOTE: automatically takes leaning into account! + } + else + { + VectorCopy ( ent->r.currentOrigin, point ); + if ( ent->client ) + { + point[2] += ent->client->ps.viewheight; + } + //AddLeanOfs ( ent, point ); + } + break; + + //FIXME: implement... + //case SPOT_CHEST: + //Returns point 3/4 from tag_torso to tag_head? + //break; + + case SPOT_LEGS: + VectorCopy ( ent->r.currentOrigin, point ); + point[2] += (ent->r.mins[2] * 0.5); + break; + + case SPOT_WEAPON: + if( ent->NPC && !VectorCompare( ent->NPC->shootAngles, vec3_origin ) && !VectorCompare( ent->NPC->shootAngles, ent->client->ps.viewangles )) + { + AngleVectors( ent->NPC->shootAngles, forward, right, up ); + } + else + { + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + } + CalcMuzzlePoint( (gentity_t*)ent, forward, right, up, point ); + //NOTE: automatically takes leaning into account! + break; + + case SPOT_GROUND: + // if entity is on the ground, just use it's absmin + if ( ent->s.groundEntityNum != -1 ) + { + VectorCopy( ent->r.currentOrigin, point ); + point[2] = ent->r.absmin[2]; + break; + } + + // if it is reasonably close to the ground, give the point underneath of it + VectorCopy( ent->r.currentOrigin, start ); + start[2] = ent->r.absmin[2]; + VectorCopy( start, end ); + end[2] -= 64; + trap_Trace( &tr, start, ent->r.mins, ent->r.maxs, end, ent->s.number, MASK_PLAYERSOLID ); + if ( tr.fraction < 1.0 ) + { + VectorCopy( tr.endpos, point); + break; + } + + // otherwise just use the origin + VectorCopy( ent->r.currentOrigin, point ); + break; + + default: + VectorCopy ( ent->r.currentOrigin, point ); + break; + } +} + + +//=================================================================================== + +/* +qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) + +Added: option to do just pitch or just yaw + +Does not include "aim" in it's calculations + +FIXME: stop compressing angles into shorts!!!! +*/ +qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) +{ +#if 1 + + float error; + float decay; + float targetPitch = 0; + float targetYaw = 0; + float yawSpeed; + qboolean exact = qtrue; + + // if angle changes are locked; just keep the current angles + // aimTime isn't even set anymore... so this code was never reached, but I need a way to lock NPC's yaw, so instead of making a new SCF_ flag, just use the existing render flag... - dmv + if ( !NPC->enemy && ( (level.time < NPCInfo->aimTime) /*|| NPC->client->renderInfo.renderFlags & RF_LOCKEDANGLE*/) ) + { + if(doPitch) + targetPitch = NPCInfo->lockedDesiredPitch; + + if(doYaw) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + // we're changing the lockedDesired Pitch/Yaw below so it's lost it's original meaning, get rid of the lock flag + // NPC->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE; + + if(doPitch) + { + targetPitch = NPCInfo->desiredPitch; + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + } + + if(doYaw) + { + targetYaw = NPCInfo->desiredYaw; + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + } + + if ( NPC->s.weapon == WP_EMPLACED_GUN ) + { + // FIXME: this seems to do nothing, actually... + yawSpeed = 20; + } + else + { + yawSpeed = NPCInfo->stats.yawSpeed; + } + + if ( NPC->s.weapon == WP_SABER && NPC->client->ps.fd.forcePowersActive&(1<client->ps.viewangles[YAW], targetYaw ); + if( fabs(error) > MIN_ANGLE_ERROR ) + { + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0f / 1000.0f;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; + } + + //FIXME: have a pitchSpeed? + if( doPitch ) + { + // decay pitch error + error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + if ( fabs(error) > MIN_ANGLE_ERROR ) + { + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0f / 1000.0f;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + if ( exact && trap_ICARUS_TaskIDPending( NPC, TID_ANGLE_FACE ) ) + { + trap_ICARUS_TaskIDComplete( NPC, TID_ANGLE_FACE ); + } + return exact; + +#else + + float error; + float decay; + float targetPitch = 0; + float targetYaw = 0; + float yawSpeed; + //float runningMod = NPCInfo->currentSpeed/100.0f; + qboolean exact = qtrue; + qboolean doSound = qfalse; + + // if angle changes are locked; just keep the current angles + if ( level.time < NPCInfo->aimTime ) + { + if(doPitch) + targetPitch = NPCInfo->lockedDesiredPitch; + if(doYaw) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + if(doPitch) + targetPitch = NPCInfo->desiredPitch; + if(doYaw) + targetYaw = NPCInfo->desiredYaw; + +// NPCInfo->aimTime = level.time + 250; + if(doPitch) + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + if(doYaw) + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + + yawSpeed = NPCInfo->stats.yawSpeed; + + if(doYaw) + { + // decay yaw error + error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); + if( fabs(error) > MIN_ANGLE_ERROR ) + { + /* + if(NPC->client->playerTeam == TEAM_BORG&& + NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) + {//HACK - borg turn more jittery + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + //Snap to + if(fabs(error) > 10) + { + if(random() > 0.6) + { + doSound = qtrue; + } + } + + if ( error < 0.0)//-10.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else if ( error > 0.0)//10.0 ) + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + else*/ + + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; + } + + //FIXME: have a pitchSpeed? + if(doPitch) + { + // decay pitch error + error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + if ( fabs(error) > MIN_ANGLE_ERROR ) + { + /* + if(NPC->client->playerTeam == TEAM_BORG&& + NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) + {//HACK - borg turn more jittery + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + //Snap to + if(fabs(error) > 10) + { + if(random() > 0.6) + { + doSound = qtrue; + } + } + + if ( error < 0.0)//-10.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else if ( error > 0.0)//10.0 ) + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + else*/ + + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + /* + if(doSound) + { + G_Sound(NPC, G_SoundIndex(va("sound/enemies/borg/borgservo%d.wav", Q_irand(1, 8)))); + } + */ + + return exact; + +#endif + +} + +void NPC_AimWiggle( vec3_t enemy_org ) +{ + //shoot for somewhere between the head and torso + //NOTE: yes, I know this looks weird, but it works + if ( NPCInfo->aimErrorDebounceTime < level.time ) + { + NPCInfo->aimOfs[0] = 0.3*flrand(NPC->enemy->r.mins[0], NPC->enemy->r.maxs[0]); + NPCInfo->aimOfs[1] = 0.3*flrand(NPC->enemy->r.mins[1], NPC->enemy->r.maxs[1]); + if ( NPC->enemy->r.maxs[2] > 0 ) + { + NPCInfo->aimOfs[2] = NPC->enemy->r.maxs[2]*flrand(0.0f, -1.0f); + } + } + VectorAdd( enemy_org, NPCInfo->aimOfs, enemy_org ); +} + +/* +qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) + + Includes aim when determining angles - so they don't always hit... + */ +qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) +{ + +#if 0 + + float diff; + float error; + float targetPitch = 0; + float targetYaw = 0; + qboolean exact = qtrue; + + if ( level.time < NPCInfo->aimTime ) + { + if( doPitch ) + targetPitch = NPCInfo->lockedDesiredPitch; + + if( doYaw ) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + if( doPitch ) + { + targetPitch = NPCInfo->desiredPitch; + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + } + + if( doYaw ) + { + targetYaw = NPCInfo->desiredYaw; + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + } + + if( doYaw ) + { + // add yaw error based on NPCInfo->aim value + error = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); + + if(Q_irand(0, 1)) + error *= -1; + + diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); + + if ( diff ) + exact = qfalse; + + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; + } + + if( doPitch ) + { + // add pitch error based on NPCInfo->aim value + error = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); + + diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + + if ( diff ) + exact = qfalse; + + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + return exact; + +#else + + float error, diff; + float decay; + float targetPitch = 0; + float targetYaw = 0; + qboolean exact = qtrue; + + // if angle changes are locked; just keep the current angles + if ( level.time < NPCInfo->aimTime ) + { + if(doPitch) + targetPitch = NPCInfo->lockedDesiredPitch; + if(doYaw) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + if(doPitch) + targetPitch = NPCInfo->desiredPitch; + if(doYaw) + targetYaw = NPCInfo->desiredYaw; + +// NPCInfo->aimTime = level.time + 250; + if(doPitch) + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + if(doYaw) + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + + if ( NPCInfo->aimErrorDebounceTime < level.time ) + { + if ( Q_irand(0, 1 ) ) + { + NPCInfo->lastAimErrorYaw = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); + } + if ( Q_irand(0, 1 ) ) + { + NPCInfo->lastAimErrorPitch = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); + } + NPCInfo->aimErrorDebounceTime = level.time + Q_irand(250, 2000); + } + + if(doYaw) + { + // decay yaw diff + diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); + + if ( diff) + { + exact = qfalse; + + decay = 60.0 + 80.0; + decay *= 50.0f / 1000.0f;//msec + if ( diff < 0.0 ) + { + diff += decay; + if ( diff > 0.0 ) + { + diff = 0.0; + } + } + else + { + diff -= decay; + if ( diff < 0.0 ) + { + diff = 0.0; + } + } + } + + // add yaw error based on NPCInfo->aim value + error = NPCInfo->lastAimErrorYaw; + + /* + if(Q_irand(0, 1)) + { + error *= -1; + } + */ + + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; + } + + if(doPitch) + { + // decay pitch diff + diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + if ( diff) + { + exact = qfalse; + + decay = 60.0 + 80.0; + decay *= 50.0f / 1000.0f;//msec + if ( diff < 0.0 ) + { + diff += decay; + if ( diff > 0.0 ) + { + diff = 0.0; + } + } + else + { + diff -= decay; + if ( diff < 0.0 ) + { + diff = 0.0; + } + } + } + + error = NPCInfo->lastAimErrorPitch; + + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + return exact; + +#endif + +} +//=================================================================================== + +/* +static void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) + +Does update angles on shootAngles +*/ + +void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) +{//FIXME: shoot angles either not set right or not used! + float error; + float decay; + float targetPitch = 0; + float targetYaw = 0; + + if(doPitch) + targetPitch = angles[PITCH]; + if(doYaw) + targetYaw = angles[YAW]; + + + if(doYaw) + { + // decay yaw error + error = AngleDelta ( NPCInfo->shootAngles[YAW], targetYaw ); + if ( error ) + { + decay = 60.0 + 80.0 * NPCInfo->stats.aim; + decay *= 100.0f / 1000.0f;//msec + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + NPCInfo->shootAngles[YAW] = targetYaw + error; + } + + if(doPitch) + { + // decay pitch error + error = AngleDelta ( NPCInfo->shootAngles[PITCH], targetPitch ); + if ( error ) + { + decay = 60.0 + 80.0 * NPCInfo->stats.aim; + decay *= 100.0f / 1000.0f;//msec + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + NPCInfo->shootAngles[PITCH] = targetPitch + error; + } +} + +/* +void SetTeamNumbers (void) + +Sets the number of living clients on each team + +FIXME: Does not account for non-respawned players! +FIXME: Don't include medics? +*/ +void SetTeamNumbers (void) +{ + gentity_t *found; + int i; + + for( i = 0; i < TEAM_NUM_TEAMS; i++ ) + { + teamNumbers[i] = 0; + teamStrength[i] = 0; + } + + for( i = 0; i < 1 ; i++ ) + { + found = &g_entities[i]; + + if( found->client ) + { + if( found->health > 0 )//FIXME: or if a player! + { + teamNumbers[found->client->playerTeam]++; + teamStrength[found->client->playerTeam] += found->health; + } + } + } + + for( i = 0; i < TEAM_NUM_TEAMS; i++ ) + {//Get the average health + teamStrength[i] = floor( ((float)(teamStrength[i])) / ((float)(teamNumbers[i])) ); + } +} + +extern stringID_table_t BSTable[]; +extern stringID_table_t BSETTable[]; +qboolean G_ActivateBehavior (gentity_t *self, int bset ) +{ + bState_t bSID = (bState_t)-1; + char *bs_name = NULL; + + if ( !self ) + { + return qfalse; + } + + bs_name = self->behaviorSet[bset]; + + if( !(VALIDSTRING( bs_name )) ) + { + return qfalse; + } + + if ( self->NPC ) + { + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + } + + if(bSID > -1) + { + self->NPC->tempBehavior = BS_DEFAULT; + self->NPC->behaviorState = bSID; + } + else + { + /* + char newname[MAX_FILENAME_LENGTH]; + sprintf((char *) &newname, "%s/%s", Q3_SCRIPT_DIR, bs_name ); + */ + + //FIXME: between here and actually getting into the ICARUS_RunScript function, the stack gets blown! + //if ( ( ICARUS_entFilter == -1 ) || ( ICARUS_entFilter == self->s.number ) ) + if (0) + { + G_DebugPrint( WL_VERBOSE, "%s attempting to run bSet %s (%s)\n", self->targetname, GetStringForID( BSETTable, bset ), bs_name ); + } + trap_ICARUS_RunScript( self, va( "%s/%s", Q3_SCRIPT_DIR, bs_name ) ); + } + return qtrue; +} + + +/* +============================================================================= + + Extended Functions + +============================================================================= +*/ + +//rww - special system for sync'ing bone angles between client and server. +void NPC_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles) +{ +#ifdef _XBOX + byte *thebone = &ent->s.boneIndex1; + byte *firstFree = NULL; +#else + int *thebone = &ent->s.boneIndex1; + int *firstFree = NULL; +#endif + int i = 0; + int boneIndex = G_BoneIndex(bone); + int flags, up, right, forward; + vec3_t *boneVector = &ent->s.boneAngles1; + vec3_t *freeBoneVec = NULL; + + while (thebone) + { + if (!*thebone && !firstFree) + { //if the value is 0 then this index is clear, we can use it if we don't find the bone we want already existing. + firstFree = thebone; + freeBoneVec = boneVector; + } + else if (*thebone) + { + if (*thebone == boneIndex) + { //this is it + break; + } + } + + switch (i) + { + case 0: + thebone = &ent->s.boneIndex2; + boneVector = &ent->s.boneAngles2; + break; +/* + case 1: + thebone = &ent->s.boneIndex3; + boneVector = &ent->s.boneAngles3; + break; + case 2: + thebone = &ent->s.boneIndex4; + boneVector = &ent->s.boneAngles4; + break; +*/ + default: + thebone = NULL; + boneVector = NULL; + break; + } + + i++; + } + + if (!thebone) + { //didn't find it, create it + if (!firstFree) + { //no free bones.. can't do a thing then. +#ifndef FINAL_BUILD + Com_Printf("WARNING: NPC has no free bone indexes\n"); +#endif + return; + } + + thebone = firstFree; + + *thebone = boneIndex; + boneVector = freeBoneVec; + } + + //If we got here then we have a vector and an index. + + //Copy the angles over the vector in the entitystate, so we can use the corresponding index + //to set the bone angles on the client. + VectorCopy(angles, *boneVector); + + //Now set the angles on our server instance if we have one. + + if (!ent->ghoul2) + { + return; + } + + flags = BONE_ANGLES_POSTMULT; + up = POSITIVE_X; + right = NEGATIVE_Y; + forward = NEGATIVE_Z; + + //first 3 bits is forward, second 3 bits is right, third 3 bits is up + ent->s.boneOrient = ((forward)|(right<<3)|(up<<6)); + + trap_G2API_SetBoneAngles(ent->ghoul2, 0, bone, angles, flags, up, right, forward, NULL, 100, level.time); +} + +//rww - and another method of automatically managing surface status for the client and server at once +#define TURN_ON 0x00000000 +#define TURN_OFF 0x00000100 + +void NPC_SetSurfaceOnOff(gentity_t *ent, const char *surfaceName, int surfaceFlags) +{ + int i = 0; + qboolean foundIt = qfalse; + + while (i < BG_NUM_TOGGLEABLE_SURFACES && bgToggleableSurfaces[i]) + { + if (!Q_stricmp(surfaceName, bgToggleableSurfaces[i])) + { //got it + foundIt = qtrue; + break; + } + i++; + } + + if (!foundIt) + { +#ifndef FINAL_BUILD + Com_Printf("WARNING: Tried to toggle NPC surface that isn't in toggleable surface list (%s)\n", surfaceName); +#endif + return; + } + + if (surfaceFlags == TURN_ON) + { //Make sure the entitystate values reflect this surface as on now. + ent->s.surfacesOn |= (1 << i); + ent->s.surfacesOff &= ~(1 << i); + } + else + { //Otherwise make sure they're off. + ent->s.surfacesOn &= ~(1 << i); + ent->s.surfacesOff |= (1 << i); + } + + if (!ent->ghoul2) + { + return; + } + + trap_G2API_SetSurfaceOnOff(ent->ghoul2, surfaceName, surfaceFlags); +} + +//rww - cheap check to see if an armed client is looking in our general direction +qboolean NPC_SomeoneLookingAtMe(gentity_t *ent) +{ + int i = 0; + gentity_t *pEnt; + + while (i < MAX_CLIENTS) + { + pEnt = &g_entities[i]; + + if (pEnt && pEnt->inuse && pEnt->client && pEnt->client->sess.sessionTeam != TEAM_SPECTATOR && + !(pEnt->client->ps.pm_flags & PMF_FOLLOW) && pEnt->s.weapon != WP_NONE) + { + if (trap_InPVS(ent->r.currentOrigin, pEnt->r.currentOrigin)) + { + if (InFOV( ent, pEnt, 30, 30 )) + { //I'm in a 30 fov or so cone from this player.. that's enough I guess. + return qtrue; + } + } + } + + i++; + } + + return qfalse; +} + +qboolean NPC_ClearLOS( const vec3_t start, const vec3_t end ) +{ + return G_ClearLOS( NPC, start, end ); +} +qboolean NPC_ClearLOS5( const vec3_t end ) +{ + return G_ClearLOS5( NPC, end ); +} +qboolean NPC_ClearLOS4( gentity_t *ent ) +{ + return G_ClearLOS4( NPC, ent ); +} +qboolean NPC_ClearLOS3( const vec3_t start, gentity_t *ent ) +{ + return G_ClearLOS3( NPC, start, ent ); +} +qboolean NPC_ClearLOS2( gentity_t *ent, const vec3_t end ) +{ + return G_ClearLOS2( NPC, ent, end ); +} + +/* +------------------------- +NPC_ValidEnemy +------------------------- +*/ + +qboolean NPC_ValidEnemy( gentity_t *ent ) +{ + int entTeam = TEAM_FREE; + //Must be a valid pointer + if ( ent == NULL ) + return qfalse; + + //Must not be me + if ( ent == NPC ) + return qfalse; + + //Must not be deleted + if ( ent->inuse == qfalse ) + return qfalse; + + //Must be alive + if ( ent->health <= 0 ) + return qfalse; + + //In case they're in notarget mode + if ( ent->flags & FL_NOTARGET ) + return qfalse; + + //Must be an NPC + if ( ent->client == NULL ) + { + // if ( ent->svFlags&SVF_NONNPC_ENEMY ) + if (ent->s.eType != ET_NPC) + {//still potentially valid + if ( ent->alliedTeam == NPC->client->playerTeam ) + { + return qfalse; + } + else + { + return qtrue; + } + } + else + { + return qfalse; + } + } + else if ( ent->client && ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + {//don't go after spectators + return qfalse; + } + if ( ent->NPC && ent->client ) + { + entTeam = ent->client->playerTeam; + } + else if ( ent->client ) + { + if (g_gametype.integer < GT_TEAM) + { + entTeam = NPCTEAM_PLAYER; + } + else + { + if ( ent->client->sess.sessionTeam == TEAM_BLUE ) + { + entTeam = NPCTEAM_PLAYER; + } + else if ( ent->client->sess.sessionTeam == TEAM_RED ) + { + entTeam = NPCTEAM_ENEMY; + } + else + { + entTeam = NPCTEAM_NEUTRAL; + } + } + } + //Can't be on the same team + if ( ent->client->playerTeam == NPC->client->playerTeam ) + return qfalse; + + //if haven't seen him in a while, give up + //if ( NPCInfo->enemyLastSeenTime != 0 && level.time - NPCInfo->enemyLastSeenTime > 7000 )//FIXME: make a stat? + // return qfalse; + if ( entTeam == NPC->client->enemyTeam //simplest case: they're on my enemy team + || (NPC->client->enemyTeam == NPCTEAM_FREE && ent->client->NPC_class != NPC->client->NPC_class )//I get mad at anyone and this guy isn't the same class as me + || (ent->client->NPC_class == CLASS_WAMPA && ent->enemy )//a rampaging wampa + || (ent->client->NPC_class == CLASS_RANCOR && ent->enemy )//a rampaging rancor + || (entTeam == NPCTEAM_FREE && ent->client->enemyTeam == NPCTEAM_FREE && ent->enemy && ent->enemy->client && (ent->enemy->client->playerTeam == NPC->client->playerTeam||(ent->enemy->client->playerTeam != NPCTEAM_ENEMY&&NPC->client->playerTeam==NPCTEAM_PLAYER))) //enemy is a rampaging non-aligned creature who is attacking someone on our team or a non-enemy (this last condition is used only if we're a good guy - in effect, we protect the innocent) + ) + { + return qtrue; + } + + return qfalse; +} + +/* +------------------------- +NPC_TargetVisible +------------------------- +*/ + +qboolean NPC_TargetVisible( gentity_t *ent ) +{ + //Make sure we're in a valid range + if ( DistanceSquared( ent->r.currentOrigin, NPC->r.currentOrigin ) > ( NPCInfo->stats.visrange * NPCInfo->stats.visrange ) ) + return qfalse; + + //Check our FOV + if ( InFOV( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) + return qfalse; + + //Check for sight + if ( NPC_ClearLOS4( ent ) == qfalse ) + return qfalse; + + return qtrue; +} + +/* +------------------------- +NPC_GetCheckDelta +------------------------- +*/ + +/* +#define CHECK_TIME_BASE 250 +#define CHECK_TIME_BASE_SQUARED ( CHECK_TIME_BASE * CHECK_TIME_BASE ) + +static int NPC_GetCheckDelta( void ) +{ + if ( NPC_ValidEnemy( NPC->enemy ) == qfalse ) + { + int distance = DistanceSquared( NPC->r.currentOrigin, g_entities[0].currentOrigin ); + + distance /= CHECK_TIME_BASE_SQUARED; + + return ( CHECK_TIME_BASE * distance ); + } + + return 0; +} +*/ + +/* +------------------------- +NPC_FindNearestEnemy +------------------------- +*/ + +#define MAX_RADIUS_ENTS 256 //NOTE: This can cause entities to be lost +#define NEAR_DEFAULT_RADIUS 256 + +int NPC_FindNearestEnemy( gentity_t *ent ) +{ + int iradiusEnts[ MAX_RADIUS_ENTS ]; + gentity_t *radEnt; + vec3_t mins, maxs; + int nearestEntID = -1; + float nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE; + float distance; + int numEnts, numChecks = 0; + int i; + + //Setup the bbox to search in + for ( i = 0; i < 3; i++ ) + { + mins[i] = ent->r.currentOrigin[i] - NPCInfo->stats.visrange; + maxs[i] = ent->r.currentOrigin[i] + NPCInfo->stats.visrange; + } + + //Get a number of entities in a given space + numEnts = trap_EntitiesInBox( mins, maxs, iradiusEnts, MAX_RADIUS_ENTS ); + + for ( i = 0; i < numEnts; i++ ) + { + radEnt = &g_entities[iradiusEnts[i]]; + //Don't consider self + if ( radEnt == ent ) + continue; + + //Must be valid + if ( NPC_ValidEnemy( radEnt ) == qfalse ) + continue; + + numChecks++; + //Must be visible + if ( NPC_TargetVisible( radEnt ) == qfalse ) + continue; + + distance = DistanceSquared( ent->r.currentOrigin, radEnt->r.currentOrigin ); + + //Found one closer to us + if ( distance < nearestDist ) + { + nearestEntID = radEnt->s.number; + nearestDist = distance; + } + } + + return nearestEntID; +} + +/* +------------------------- +NPC_PickEnemyExt +------------------------- +*/ + +gentity_t *NPC_PickEnemyExt( qboolean checkAlerts ) +{ + //Check for Hazard Team status and remove this check + /* + if ( NPC->client->playerTeam != TEAM_STARFLEET ) + { + //If we've found the player, return it + if ( NPC_FindPlayer() ) + return &g_entities[0]; + } + */ + + //If we've asked for the closest enemy + int entID = NPC_FindNearestEnemy( NPC ); + + //If we have a valid enemy, use it + if ( entID >= 0 ) + return &g_entities[entID]; + + if ( checkAlerts ) + { + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + alertEvent_t *event = &level.alertEvents[alertEvent]; + + //Don't pay attention to our own alerts + if ( event->owner == NPC ) + return NULL; + + if ( event->level >= AEL_DISCOVERED ) + { + //If it's the player, attack him + if ( event->owner == &g_entities[0] ) + return event->owner; + + //If it's on our team, then take its enemy as well + if ( ( event->owner->client ) && ( event->owner->client->playerTeam == NPC->client->playerTeam ) ) + return event->owner->enemy; + } + } + } + + return NULL; +} + +/* +------------------------- +NPC_FindPlayer +------------------------- +*/ + +qboolean NPC_FindPlayer( void ) +{ + return NPC_TargetVisible( &g_entities[0] ); +} + +/* +------------------------- +NPC_CheckPlayerDistance +------------------------- +*/ + +static qboolean NPC_CheckPlayerDistance( void ) +{ + return qfalse;//MOOT in MP + /* + float distance; + + //Make sure we have an enemy + if ( NPC->enemy == NULL ) + return qfalse; + + //Only do this for non-players + if ( NPC->enemy->s.number == 0 ) + return qfalse; + + //must be set up to get mad at player + if ( !NPC->client || NPC->client->enemyTeam != NPCTEAM_PLAYER ) + return qfalse; + + //Must be within our FOV + if ( InFOV( &g_entities[0], NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) + return qfalse; + + distance = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + + if ( distance > DistanceSquared( NPC->r.currentOrigin, g_entities[0].r.currentOrigin ) ) + { //rwwFIXMEFIXME: care about all clients not just client 0 + G_SetEnemy( NPC, &g_entities[0] ); + return qtrue; + } + + return qfalse; + */ +} + +/* +------------------------- +NPC_FindEnemy +------------------------- +*/ + +qboolean NPC_FindEnemy( qboolean checkAlerts ) +{ + gentity_t *newenemy; + + //We're ignoring all enemies for now + //if( NPC->svFlags & SVF_IGNORE_ENEMIES ) + if (0) //rwwFIXMEFIXME: support for flag + { + G_ClearEnemy( NPC ); + return qfalse; + } + + //we can't pick up any enemies for now + if( NPCInfo->confusionTime > level.time ) + { + return qfalse; + } + + //Don't want a new enemy + //rwwFIXMEFIXME: support for locked enemy + //if ( ( ValidEnemy( NPC->enemy ) ) && ( NPC->svFlags & SVF_LOCKEDENEMY ) ) + // return qtrue; + + //See if the player is closer than our current enemy + if ( NPC_CheckPlayerDistance() ) + { + return qtrue; + } + + //Otherwise, turn off the flag +// NPC->svFlags &= ~SVF_LOCKEDENEMY; + //See if the player is closer than our current enemy + if ( NPC->client->NPC_class != CLASS_RANCOR + && NPC->client->NPC_class != CLASS_WAMPA + //&& NPC->client->NPC_class != CLASS_SAND_CREATURE + && NPC_CheckPlayerDistance() ) + {//rancors, wampas & sand creatures don't care if player is closer, they always go with closest + return qtrue; + } + + //If we've gotten here alright, then our target it still valid + if ( NPC_ValidEnemy( NPC->enemy ) ) + return qtrue; + + newenemy = NPC_PickEnemyExt( checkAlerts ); + + //if we found one, take it as the enemy + if( NPC_ValidEnemy( newenemy ) ) + { + G_SetEnemy( NPC, newenemy ); + return qtrue; + } + + return qfalse; +} + +/* +------------------------- +NPC_CheckEnemyExt +------------------------- +*/ + +qboolean NPC_CheckEnemyExt( qboolean checkAlerts ) +{ + //Make sure we're ready to think again +/* + if ( NPCInfo->enemyCheckDebounceTime > level.time ) + return qfalse; + + //Get our next think time + NPCInfo->enemyCheckDebounceTime = level.time + NPC_GetCheckDelta(); + + //Attempt to find an enemy + return NPC_FindEnemy(); +*/ + return NPC_FindEnemy( checkAlerts ); +} + +/* +------------------------- +NPC_FacePosition +------------------------- +*/ + +qboolean NPC_FacePosition( vec3_t position, qboolean doPitch ) +{ + vec3_t muzzle; + vec3_t angles; + float yawDelta; + qboolean facing = qtrue; + + //Get the positions + if ( NPC->client && (NPC->client->NPC_class == CLASS_RANCOR || NPC->client->NPC_class == CLASS_WAMPA) )// || NPC->client->NPC_class == CLASS_SAND_CREATURE) ) + { + CalcEntitySpot( NPC, SPOT_ORIGIN, muzzle ); + muzzle[2] += NPC->r.maxs[2] * 0.75f; + } + else if ( NPC->client && NPC->client->NPC_class == CLASS_GALAKMECH ) + { + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + } + else + { + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD + } + + //Find the desired angles + GetAnglesForDirection( muzzle, position, angles ); + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + + if ( NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_ATST ) + { + // FIXME: this is kind of dumb, but it was the easiest way to get it to look sort of ok + NPCInfo->desiredYaw += flrand( -5, 5 ) + sin( level.time * 0.004f ) * 7; + NPCInfo->desiredPitch += flrand( -2, 2 ); + } + //Face that yaw + NPC_UpdateAngles( qtrue, qtrue ); + + //Find the delta between our goal and our current facing + yawDelta = AngleNormalize360( NPCInfo->desiredYaw - ( SHORT2ANGLE( ucmd.angles[YAW] + client->ps.delta_angles[YAW] ) ) ); + + //See if we are facing properly + if ( fabs( yawDelta ) > VALID_ATTACK_CONE ) + facing = qfalse; + + if ( doPitch ) + { + //Find the delta between our goal and our current facing + float currentAngles = ( SHORT2ANGLE( ucmd.angles[PITCH] + client->ps.delta_angles[PITCH] ) ); + float pitchDelta = NPCInfo->desiredPitch - currentAngles; + + //See if we are facing properly + if ( fabs( pitchDelta ) > VALID_ATTACK_CONE ) + facing = qfalse; + } + + return facing; +} + +/* +------------------------- +NPC_FaceEntity +------------------------- +*/ + +qboolean NPC_FaceEntity( gentity_t *ent, qboolean doPitch ) +{ + vec3_t entPos; + + //Get the positions + CalcEntitySpot( ent, SPOT_HEAD_LEAN, entPos ); + + return NPC_FacePosition( entPos, doPitch ); +} + +/* +------------------------- +NPC_FaceEnemy +------------------------- +*/ + +qboolean NPC_FaceEnemy( qboolean doPitch ) +{ + if ( NPC == NULL ) + return qfalse; + + if ( NPC->enemy == NULL ) + return qfalse; + + return NPC_FaceEntity( NPC->enemy, doPitch ); +} + +/* +------------------------- +NPC_CheckCanAttackExt +------------------------- +*/ + +qboolean NPC_CheckCanAttackExt( void ) +{ + //We don't want them to shoot + if( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + return qfalse; + + //Turn to face + if ( NPC_FaceEnemy( qtrue ) == qfalse ) + return qfalse; + + //Must have a clear line of sight to the target + if ( NPC_ClearShot( NPC->enemy ) == qfalse ) + return qfalse; + + return qtrue; +} + +/* +------------------------- +NPC_ClearLookTarget +------------------------- +*/ + +void NPC_ClearLookTarget( gentity_t *self ) +{ + if ( !self->client ) + { + return; + } + + if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//lookTarget is set by and to the monster that's holding you, no other operations can change that + return; + } + + self->client->renderInfo.lookTarget = ENTITYNUM_NONE;//ENTITYNUM_WORLD; + self->client->renderInfo.lookTargetClearTime = 0; +} + +/* +------------------------- +NPC_SetLookTarget +------------------------- +*/ +void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ) +{ + if ( !self->client ) + { + return; + } + + if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//lookTarget is set by and to the monster that's holding you, no other operations can change that + return; + } + + self->client->renderInfo.lookTarget = entNum; + self->client->renderInfo.lookTargetClearTime = clearTime; +} + +/* +------------------------- +NPC_CheckLookTarget +------------------------- +*/ +qboolean NPC_CheckLookTarget( gentity_t *self ) +{ + if ( self->client ) + { + if ( self->client->renderInfo.lookTarget >= 0 && self->client->renderInfo.lookTarget < ENTITYNUM_WORLD ) + {//within valid range + if ( (&g_entities[self->client->renderInfo.lookTarget] == NULL) || !g_entities[self->client->renderInfo.lookTarget].inuse ) + {//lookTarget not inuse or not valid anymore + NPC_ClearLookTarget( self ); + } + else if ( self->client->renderInfo.lookTargetClearTime && self->client->renderInfo.lookTargetClearTime < level.time ) + {//Time to clear lookTarget + NPC_ClearLookTarget( self ); + } + else if ( g_entities[self->client->renderInfo.lookTarget].client && self->enemy && (&g_entities[self->client->renderInfo.lookTarget] != self->enemy) ) + {//should always look at current enemy if engaged in battle... FIXME: this could override certain scripted lookTargets...??? + NPC_ClearLookTarget( self ); + } + else + { + return qtrue; + } + } + } + + return qfalse; +} + +/* +------------------------- +NPC_CheckCharmed +------------------------- +*/ +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +void NPC_CheckCharmed( void ) +{ + if ( NPCInfo->charmedTime && NPCInfo->charmedTime < level.time && NPC->client ) + {//we were charmed, set us back! + NPC->client->playerTeam = NPC->genericValue1; + NPC->client->enemyTeam = NPC->genericValue2; + NPC->s.teamowner = NPC->genericValue3; + + NPC->client->leader = NULL; + if ( NPCInfo->tempBehavior == BS_FOLLOW_LEADER ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + G_ClearEnemy( NPC ); + NPCInfo->charmedTime = 0; + //say something to let player know you've snapped out of it + G_AddVoiceEvent( NPC, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } +} + +void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex ) +{ + mdxaBone_t boltMatrix; + vec3_t result, angles; + + if (!self || !self->inuse) + { + return; + } + + if (self->client) + { //clients don't actually even keep r.currentAngles maintained + VectorSet(angles, 0, self->client->ps.viewangles[YAW], 0); + } + else + { + VectorSet(angles, 0, self->r.currentAngles[YAW], 0); + } + + if ( /*!self || ...haha (sorry, i'm tired)*/ !self->ghoul2 ) + { + return; + } + + trap_G2API_GetBoltMatrix( self->ghoul2, modelIndex, + boltIndex, + &boltMatrix, angles, self->r.currentOrigin, level.time, + NULL, self->modelScale ); + if ( pos ) + { + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, result ); + VectorCopy( result, pos ); + } +} + +float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex ) +{ + vec3_t org; + + if ( !targEnt ) + { + return Q3_INFINITE; + } + + G_GetBoltPosition( NPC, boltIndex, org, 0 ); + + return (Distance( targEnt->r.currentOrigin, org )); +} + +float NPC_EnemyRangeFromBolt( int boltIndex ) +{ + return (NPC_EntRangeFromBolt( NPC->enemy, boltIndex )); +} + +int NPC_GetEntsNearBolt( int *radiusEnts, float radius, int boltIndex, vec3_t boltOrg ) +{ + vec3_t mins, maxs; + int i; + + //get my handRBolt's position + vec3_t org; + + G_GetBoltPosition( NPC, boltIndex, org, 0 ); + + VectorCopy( org, boltOrg ); + + //Setup the bbox to search in + for ( i = 0; i < 3; i++ ) + { + mins[i] = boltOrg[i] - radius; + maxs[i] = boltOrg[i] + radius; + } + + //Get the number of entities in a given space + return (trap_EntitiesInBox( mins, maxs, radiusEnts, 128 )); +} diff --git a/codemp/game/SpeederNPC.c b/codemp/game/SpeederNPC.c new file mode 100644 index 0000000..ae15c59 --- /dev/null +++ b/codemp/game/SpeederNPC.c @@ -0,0 +1,1134 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#include "..\game\wp_saber.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +extern void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +#endif + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +#ifdef QAGAME //SP or gameside MP +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +#endif + +#ifdef _JK2MP + +#include "../namespace_begin.h" + +extern void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int BG_GetTime(void); +extern qboolean BG_SabersOff( playerState_t *ps ); +#endif + +//Alright, actually, most of this file is shared between game and cgame for MP. +//I would like to keep it this way, so when modifying for SP please keep in +//mind the bgEntity restrictions imposed. -rww + +#define STRAFERAM_DURATION 8 +#define STRAFERAM_ANGLE 8 + + +#ifndef _JK2MP +bool VEH_StartStrafeRam(Vehicle_t *pVeh, bool Right) +{ + if (!(pVeh->m_ulFlags&VEH_STRAFERAM)) + { + float speed = VectorLength(pVeh->m_pParentEntity->client->ps.velocity); + if (speed>400.0f) + { + // Compute Pos3 + //-------------- + vec3_t right; + AngleVectors(pVeh->m_vOrientation, 0, right, 0); + VectorMA(pVeh->m_pParentEntity->client->ps.velocity, (Right)?( speed):(-speed), right, pVeh->m_pParentEntity->pos3); + + pVeh->m_ulFlags |= VEH_STRAFERAM; + parentPS->hackingTime = (Right)?(STRAFERAM_DURATION):(-STRAFERAM_DURATION); + + if (pVeh->m_iSoundDebounceTimerm_pVehicleInfo->soundShift1; break; + case 2: shiftSound=pVeh->m_pVehicleInfo->soundShift2; break; + case 3: shiftSound=pVeh->m_pVehicleInfo->soundShift3; break; + case 4: shiftSound=pVeh->m_pVehicleInfo->soundShift4; break; + } + if (shiftSound) + { + pVeh->m_iSoundDebounceTimer = level.time + Q_irand(1000, 4000); + G_SoundIndexOnEnt( pVeh->m_pParentEntity, CHAN_AUTO, shiftSound); + } + } + return true; + } + } + return false; +} +#else +bool VEH_StartStrafeRam(Vehicle_t *pVeh, bool Right, int Duration) +{ + return false; +} +#endif + + +#ifdef QAGAME //game-only.. for now +// Like a think or move command, this updates various vehicle properties. +bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + if ( !g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ) ) + { + return false; + } + + // See whether this vehicle should be exploding. + if ( pVeh->m_iDieTime != 0 ) + { + pVeh->m_pVehicleInfo->DeathUpdate( pVeh ); + } + + // Update move direction. +#ifndef _JK2MP //this makes prediction unhappy, and rightfully so. + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + + if ( pVeh->m_ulFlags & VEH_FLYING ) + { + vec3_t vVehAngles; + VectorSet(vVehAngles, 0, pVeh->m_vOrientation[YAW], 0 ); + AngleVectors( vVehAngles, parent->client->ps.moveDir, NULL, NULL ); + } + else + { + vec3_t vVehAngles; + VectorSet(vVehAngles, pVeh->m_vOrientation[PITCH], pVeh->m_vOrientation[YAW], 0 ); + AngleVectors( vVehAngles, parent->client->ps.moveDir, NULL, NULL ); + } + + // Check For A Strafe Ram + //------------------------ + if (!(pVeh->m_ulFlags&VEH_STRAFERAM) && !(pVeh->m_ulFlags&VEH_FLYING)) + { + // Started A Strafe + //------------------ + if (pVeh->m_ucmd.rightmove && !parentPS->hackingTime) + { + parentPS->hackingTime = (pVeh->m_ucmd.rightmove>0)?(level.time):(-1*level.time); + } + + // Ended A Strafe + //---------------- + else if (!pVeh->m_ucmd.rightmove && parentPS->hackingTime) + { + // If It Was A Short Burst, Start The Strafe Ram + //----------------------------------------------- + if ((level.time - abs(parentPS->hackingTime))<300) + { + if (!VEH_StartStrafeRam(pVeh, (parentPS->hackingTime>0))) + { + parentPS->hackingTime = 0; + } + } + + // Otherwise, Clear The Timer + //---------------------------- + else + { + parentPS->hackingTime = 0; + } + } + } + + // If Currently In A StrafeRam, Check To See If It Is Done (Timed Out) + //--------------------------------------------------------------------- + else if (!parentPS->hackingTime) + { + pVeh->m_ulFlags &=~VEH_STRAFERAM; + } + + + // Exhaust Effects Start And Stop When The Accelerator Is Pressed + //---------------------------------------------------------------- + if (pVeh->m_pVehicleInfo->iExhaustFX) + { + // Start It On Each Exhaust Bolt + //------------------------------- + if (pVeh->m_ucmd.forwardmove && !(pVeh->m_ulFlags&VEH_ACCELERATORON)) + { + pVeh->m_ulFlags |= VEH_ACCELERATORON; + for (int i=0; (im_iExhaustTag[i]!=-1); i++) + { + G_PlayEffect(pVeh->m_pVehicleInfo->iExhaustFX, parent->playerModel, pVeh->m_iExhaustTag[i], parent->s.number, parent->currentOrigin, 1, qtrue); + } + } + + // Stop It On Each Exhaust Bolt + //------------------------------ + else if (!pVeh->m_ucmd.forwardmove && (pVeh->m_ulFlags&VEH_ACCELERATORON)) + { + pVeh->m_ulFlags &=~VEH_ACCELERATORON; + for (int i=0; (im_iExhaustTag[i]!=-1); i++) + { + G_StopEffect(pVeh->m_pVehicleInfo->iExhaustFX, parent->playerModel, pVeh->m_iExhaustTag[i], parent->s.number); + } + } + } + + if (!(pVeh->m_ulFlags&VEH_ARMORLOW) && (pVeh->m_iArmor <= pVeh->m_pVehicleInfo->armor/3)) + { + pVeh->m_ulFlags |= VEH_ARMORLOW; + + } + + // Armor Gone Effects (Fire) + //--------------------------- + if (pVeh->m_pVehicleInfo->iArmorGoneFX) + { + if (!(pVeh->m_ulFlags&VEH_ARMORGONE) && (pVeh->m_iArmor <= 0)) + { + pVeh->m_ulFlags |= VEH_ARMORGONE; + G_PlayEffect(pVeh->m_pVehicleInfo->iArmorGoneFX, parent->playerModel, parent->crotchBolt, parent->s.number, parent->currentOrigin, 1, qtrue); + parent->s.loopSound = G_SoundIndex( "sound/vehicles/common/fire_lp.wav" ); + } + } +#endif + + return true; +} +#endif //QAGAME + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + playerState_t *parentPS; + playerState_t *pilotPS = NULL; + int curTime; + +#ifdef _JK2MP + parentPS = pVeh->m_pParentEntity->playerState; + if (pVeh->m_pPilot) + { + pilotPS = pVeh->m_pPilot->playerState; + } +#else + parentPS = &pVeh->m_pParentEntity->client->ps; + if (pVeh->m_pPilot) + { + pilotPS = &pVeh->m_pPilot->client->ps; + } +#endif + + + // If we're flying, make us accelerate at 40% (about half) acceleration rate, and restore the pitch + // to origin (straight) position (at 5% increments). + if ( pVeh->m_ulFlags & VEH_FLYING ) + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier * 0.4f; + } +#ifdef _JK2MP + else if ( !parentPS->m_iVehicleNum ) +#else + else if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//drifts to a stop + speedInc = 0; + //pVeh->m_ucmd.forwardmove = 127; + } + else + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + +#ifndef _JK2MP//SP + curTime = level.time; +#elif QAGAME//MP GAME + curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + curTime = pm->cmd.serverTime; +#endif + + + + if ( (pVeh->m_pPilot /*&& (pilotPS->weapon == WP_NONE || pilotPS->weapon == WP_MELEE )*/ && + (pVeh->m_ucmd.buttons & BUTTON_ALT_ATTACK) && pVeh->m_pVehicleInfo->turboSpeed) + /*|| + (parentPS && parentPS->electrifyTime > curTime && pVeh->m_pVehicleInfo->turboSpeed)*/ //make them go! + ) + { + if ( (parentPS && parentPS->electrifyTime > curTime) || + (pVeh->m_pPilot->playerState && + (pVeh->m_pPilot->playerState->weapon == WP_MELEE || + (pVeh->m_pPilot->playerState->weapon == WP_SABER && BG_SabersOff( pVeh->m_pPilot->playerState ) ))) ) + { + if ((curTime - pVeh->m_iTurboTime)>pVeh->m_pVehicleInfo->turboRecharge) + { + pVeh->m_iTurboTime = (curTime + pVeh->m_pVehicleInfo->turboDuration); + if (pVeh->m_pVehicleInfo->iTurboStartFX) + { + int i; + for (i=0; (im_iExhaustTag[i]!=-1); i++) + { +#ifdef QAGAME + if (pVeh->m_pParentEntity && + pVeh->m_pParentEntity->ghoul2 && + pVeh->m_pParentEntity->playerState) + { //fine, I'll use a tempent for this, but only because it's played only once at the start of a turbo. + vec3_t boltOrg, boltDir; + mdxaBone_t boltMatrix; + + VectorSet(boltDir, 0.0f, pVeh->m_pParentEntity->playerState->viewangles[YAW], 0.0f); + + trap_G2API_GetBoltMatrix(pVeh->m_pParentEntity->ghoul2, 0, pVeh->m_iExhaustTag[i], &boltMatrix, boltDir, pVeh->m_pParentEntity->playerState->origin, level.time, NULL, pVeh->m_pParentEntity->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltDir); + G_PlayEffectID(pVeh->m_pVehicleInfo->iTurboStartFX, boltOrg, boltDir); + } +#endif + } + } + parentPS->speed = pVeh->m_pVehicleInfo->turboSpeed; // Instantly Jump To Turbo Speed + } + } + } + + // Slide Breaking + if (pVeh->m_ulFlags&VEH_SLIDEBREAKING) + { + if (pVeh->m_ucmd.forwardmove>=0 +#ifndef _JK2MP + || ((level.time - pVeh->m_pParentEntity->lastMoveTime)>500) +#endif + ) + { + pVeh->m_ulFlags &= ~VEH_SLIDEBREAKING; + } + parentPS->speed = 0; + } + else if ( + (curTime > pVeh->m_iTurboTime) && + !(pVeh->m_ulFlags&VEH_FLYING) && + pVeh->m_ucmd.forwardmove<0 && + fabs(pVeh->m_vOrientation[ROLL])>25.0f) + { + pVeh->m_ulFlags |= VEH_SLIDEBREAKING; + } + + + if ( curTime < pVeh->m_iTurboTime ) + { + speedMax = pVeh->m_pVehicleInfo->turboSpeed; + if (parentPS) + { + parentPS->eFlags |= EF_JETPACK_ACTIVE; + } + } + else + { + speedMax = pVeh->m_pVehicleInfo->speedMax; + if (parentPS) + { + parentPS->eFlags &= ~EF_JETPACK_ACTIVE; + } + } + + + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + + if ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + parentPS->speed -= speedIdleDec; + } + } + // No input, so coast to stop. + else if ( parentPS->speed > 0.0f ) + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < 0.0f ) + { + parentPS->speed = 0.0f; + } + } + else if ( parentPS->speed < 0.0f ) + { + parentPS->speed += speedIdleDec; + if ( parentPS->speed > 0.0f ) + { + parentPS->speed = 0.0f; + } + } + } + else + { + if ( !pVeh->m_pVehicleInfo->strafePerc +#ifdef _JK2MP + || (0 && pVeh->m_pParentEntity->s.number < MAX_CLIENTS) ) +#else + || (!g_speederControlScheme->value && !pVeh->m_pParentEntity->s.number) ) +#endif + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + //pVeh->m_ucmd.rightmove = 0; + } + } + + if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + + if (parentPS && parentPS->electrifyTime > curTime) + { + parentPS->speed *= (pVeh->m_fTimeModifier/60.0f); + } + + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +//Oh, and please, use "< MAX_CLIENTS" to check for "player" and not +//"!s.number", this is a universal check that will work for both SP +//and MP. -rww +// ProcessOrientCommands the Vehicle. +#ifdef _JK2MP //temp hack til mp speeder controls are sorted -rww +extern void AnimalProcessOri(Vehicle_t *pVeh); +#endif +void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + playerState_t *riderPS; + playerState_t *parentPS; + +#ifdef _JK2MP + float angDif; + + if (pVeh->m_pPilot) + { + riderPS = pVeh->m_pPilot->playerState; + } + else + { + riderPS = pVeh->m_pParentEntity->playerState; + } + parentPS = pVeh->m_pParentEntity->playerState; + + //pVeh->m_vOrientation[YAW] = 0.0f;//riderPS->viewangles[YAW]; + angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*4.0f; //magic number hackery + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - angDif*(pVeh->m_fTimeModifier*0.2f)); + + if (parentPS->electrifyTime > pm->cmd.serverTime) + { //do some crazy stuff + pVeh->m_vOrientation[YAW] += (sin(pm->cmd.serverTime/1000.0f)*3.0f)*pVeh->m_fTimeModifier; + } + } + +#else + gentity_t *rider = pVeh->m_pParentEntity->owner; + if ( !rider || !rider->client ) + { + riderPS = &pVeh->m_pParentEntity->client->ps; + } + else + { + riderPS = &rider->client->ps; + } + parentPS = &pVeh->m_pParentEntity->client->ps; + + if (pVeh->m_ulFlags & VEH_FLYING) + { + pVeh->m_vOrientation[YAW] += pVeh->m_vAngularVelocity; + } + else if ( + (pVeh->m_ulFlags & VEH_SLIDEBREAKING) || // No Angles Control While Out Of Control + (pVeh->m_ulFlags & VEH_OUTOFCONTROL) // No Angles Control While Out Of Control + ) + { + // Any ability to change orientation? + } + else if ( + (pVeh->m_ulFlags & VEH_STRAFERAM) // No Angles Control While Strafe Ramming + ) + { + if (parentPS->hackingTime>0) + { + parentPS->hackingTime--; + pVeh->m_vOrientation[ROLL] += (parentPS->hackingTime<( STRAFERAM_DURATION/2))?(-STRAFERAM_ANGLE):( STRAFERAM_ANGLE); + } + else if (pVeh->hackingTime<0) + { + parentPS->hackingTime++; + pVeh->m_vOrientation[ROLL] += (parentPS->hackingTime>(-STRAFERAM_DURATION/2))?( STRAFERAM_ANGLE):(-STRAFERAM_ANGLE); + } + } + else + { + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + } +#endif + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef QAGAME + +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); + +// This function makes sure that the vehicle is properly animated. +void AnimateVehicle( Vehicle_t *pVeh ) +{ +} + +#endif //QAGAME + +//rest of file is shared + +#ifndef _JK2MP +extern void CG_ChangeWeapon( int num ); +#endif + + +#ifndef _JK2MP +extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 ); +#endif + + +//NOTE NOTE NOTE NOTE NOTE NOTE +//I want to keep this function BG too, because it's fairly generic already, and it +//would be nice to have proper prediction of animations. -rww +// This function makes sure that the rider's in this vehicle are properly animated. +void AnimateRiders( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_VS_IDLE; + float fSpeedPercToMax; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + playerState_t *pilotPS; + playerState_t *parentPS; + int curTime; + + + // Boarding animation. + if ( pVeh->m_iBoarding != 0 ) + { + // We've just started moarding, set the amount of time it will take to finish moarding. + if ( pVeh->m_iBoarding < 0 ) + { + int iAnimLen; + + // Boarding from left... + if ( pVeh->m_iBoarding == -1 ) + { + Anim = BOTH_VS_MOUNT_L; + } + else if ( pVeh->m_iBoarding == -2 ) + { + Anim = BOTH_VS_MOUNT_R; + } + else if ( pVeh->m_iBoarding == -3 ) + { + Anim = BOTH_VS_MOUNTJUMP_L; + } + else if ( pVeh->m_iBoarding == VEH_MOUNT_THROW_LEFT) + { + iBlend = 0; + Anim = BOTH_VS_MOUNTTHROW_R; + } + else if ( pVeh->m_iBoarding == VEH_MOUNT_THROW_RIGHT) + { + iBlend = 0; + Anim = BOTH_VS_MOUNTTHROW_L; + } + + // Set the delay time (which happens to be the time it takes for the animation to complete). + // NOTE: Here I made it so the delay is actually 40% (0.4f) of the animation time. +#ifdef _JK2MP + iAnimLen = BG_AnimLength( pVeh->m_pPilot->localAnimIndex, Anim ) * 0.4f; + pVeh->m_iBoarding = BG_GetTime() + iAnimLen; +#else + iAnimLen = PM_AnimLength( pVeh->m_pPilot->client->clientInfo.animFileIndex, Anim );// * 0.4f; + if (pVeh->m_iBoarding!=VEH_MOUNT_THROW_LEFT && pVeh->m_iBoarding!=VEH_MOUNT_THROW_RIGHT) + { + pVeh->m_iBoarding = level.time + (iAnimLen*0.4f); + } + else + { + pVeh->m_iBoarding = level.time + iAnimLen; + } +#endif + // Set the animation, which won't be interrupted until it's completed. + // TODO: But what if he's killed? Should the animation remain persistant??? + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + +#ifdef _JK2MP + BG_SetAnim(pVeh->m_pPilot->playerState, bgAllAnims[pVeh->m_pPilot->localAnimIndex].anims, + SETANIM_BOTH, Anim, iFlags, iBlend); +#else + NPC_SetAnim( pVeh->m_pPilot, SETANIM_BOTH, Anim, iFlags, iBlend ); + if (pVeh->m_pOldPilot) + { + iAnimLen = PM_AnimLength( pVeh->m_pPilot->client->clientInfo.animFileIndex, BOTH_VS_MOUNTTHROWEE); + NPC_SetAnim( pVeh->m_pOldPilot, SETANIM_BOTH, BOTH_VS_MOUNTTHROWEE, iFlags, iBlend ); + } +#endif + } + +#ifndef _JK2MP + if (pVeh->m_pOldPilot && pVeh->m_pOldPilot->client->ps.torsoAnimTimer<=0) + { + if (Q_irand(0, player->count)==0) + { + player->count++; + player->lastEnemy = pVeh->m_pOldPilot; + G_StartMatrixEffect(player, MEF_LOOK_AT_ENEMY|MEF_NO_RANGEVAR|MEF_NO_VERTBOB|MEF_NO_SPIN, 1000); + } + + gentity_t* oldPilot = pVeh->m_pOldPilot; + pVeh->m_pVehicleInfo->Eject(pVeh, pVeh->m_pOldPilot, qtrue); // will set pointer to zero + + // Kill Him + //---------- + oldPilot->client->noRagTime = -1; // no ragdoll for you + G_Damage(oldPilot, pVeh->m_pPilot, pVeh->m_pPilot, pVeh->m_pPilot->currentAngles, pVeh->m_pPilot->currentOrigin, 1000, 0, MOD_CRUSH); + + // Compute THe Throw Direction As Backwards From The Vehicle's Velocity + //---------------------------------------------------------------------- + vec3_t throwDir; + VectorScale(pVeh->m_pParentEntity->client->ps.velocity, -1.0f, throwDir); + VectorNormalize(throwDir); + throwDir[2] += 0.3f; // up a little + + // Now Throw Him Out + //------------------- + G_Throw(oldPilot, throwDir, VectorLength(pVeh->m_pParentEntity->client->ps.velocity)/10.0f); + NPC_SetAnim(oldPilot, SETANIM_BOTH, BOTH_DEATHBACKWARD1, SETANIM_FLAG_OVERRIDE, iBlend ); + } +#endif + + return; + } + +#ifdef _JK2MP //fixme + if (1) return; +#endif + +#ifdef _JK2MP + pilotPS = pVeh->m_pPilot->playerState; + parentPS = pVeh->m_pPilot->playerState; +#else + pilotPS = &pVeh->m_pPilot->client->ps; + parentPS = &pVeh->m_pParentEntity->client->ps; +#endif + +#ifndef _JK2MP//SP + curTime = level.time; +#elif QAGAME//MP GAME + curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + curTime = pm->cmd.serverTime; +#endif + + // Percentage of maximum speed relative to current speed. + fSpeedPercToMax = parentPS->speed / pVeh->m_pVehicleInfo->speedMax; + + // Going in reverse... +#ifdef _JK2MP + if ( pVeh->m_ucmd.forwardmove < 0 && !(pVeh->m_ulFlags & VEH_SLIDEBREAKING)) +#else + if ( fSpeedPercToMax < -0.018f && !(pVeh->m_ulFlags & VEH_SLIDEBREAKING)) +#endif + { + Anim = BOTH_VS_REV; + iBlend = 500; + } + else + { + bool HasWeapon = ((pilotPS->weapon != WP_NONE) && (pilotPS->weapon != WP_MELEE)); + bool Attacking = (HasWeapon && !!(pVeh->m_ucmd.buttons&BUTTON_ATTACK)); +#ifdef _JK2MP //fixme: flying tends to spaz out a lot + bool Flying = false; + bool Crashing = false; +#else + bool Flying = !!(pVeh->m_ulFlags & VEH_FLYING); + bool Crashing = !!(pVeh->m_ulFlags & VEH_CRASHING); +#endif + bool Right = (pVeh->m_ucmd.rightmove>0); + bool Left = (pVeh->m_ucmd.rightmove<0); + bool Turbo = (curTimem_iTurboTime); + EWeaponPose WeaponPose = WPOSE_NONE; + + + // Remove Crashing Flag + //---------------------- + pVeh->m_ulFlags &= ~VEH_CRASHING; + + + // Put Away Saber When It Is Not Active + //-------------------------------------- +#ifndef _JK2MP + if (HasWeapon && (Turbo || (pilotPS->weapon==WP_SABER && !pilotPS->SaberActive()))) + { + if (pVeh->m_pPilot->s.numberm_pPilot->client->ps.weapon = WP_NONE; + G_RemoveWeaponModels(pVeh->m_pPilot); + } +#endif + + // Don't Interrupt Attack Anims + //------------------------------ +#ifdef _JK2MP + if (pilotPS->weaponTime>0) + { + return; + } +#else + if (pilotPS->torsoAnim>=BOTH_VS_ATL_S && pilotPS->torsoAnim<=BOTH_VS_ATF_G) + { + float bodyCurrent = 0.0f; + int bodyEnd = 0; + if (!!gi.G2API_GetBoneAnimIndex(&pVeh->m_pPilot->ghoul2[pVeh->m_pPilot->playerModel], pVeh->m_pPilot->rootBone, level.time, &bodyCurrent, NULL, &bodyEnd, NULL, NULL, NULL)) + { + if (bodyCurrent<=((float)(bodyEnd)-1.5f)) + { + return; + } + } + } +#endif + + // Compute The Weapon Pose + //-------------------------- + if (pilotPS->weapon==WP_BLASTER) + { + WeaponPose = WPOSE_BLASTER; + } + else if (pilotPS->weapon==WP_SABER) + { + if ( (pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VS_ATL_TO_R_S) + { + pVeh->m_ulFlags &= ~VEH_SABERINLEFTHAND; + } + if (!(pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VS_ATR_TO_L_S) + { + pVeh->m_ulFlags |= VEH_SABERINLEFTHAND; + } + WeaponPose = (pVeh->m_ulFlags&VEH_SABERINLEFTHAND)?(WPOSE_SABERLEFT):(WPOSE_SABERRIGHT); + } + + + if (Attacking && WeaponPose) + {// Attack! + iBlend = 100; + iFlags = SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART; + + // Auto Aiming + //=============================================== + if (!Left && !Right) // Allow player strafe keys to override + { +#ifndef _JK2MP + if (pVeh->m_pPilot->enemy) + { + vec3_t toEnemy; + float toEnemyDistance; + vec3_t actorRight; + float actorRightDot; + + VectorSubtract(pVeh->m_pPilot->currentOrigin, pVeh->m_pPilot->enemy->currentOrigin, toEnemy); + toEnemyDistance = VectorNormalize(toEnemy); + + AngleVectors(pVeh->m_pParentEntity->currentAngles, 0, actorRight, 0); + actorRightDot = DotProduct(toEnemy, actorRight); + + if (fabsf(actorRightDot)>0.5f || pilotPS->weapon==WP_SABER) + { + Left = (actorRightDot>0.0f); + Right = !Left; + } + else + { + Right = Left = false; + } + } + else +#endif + if (pilotPS->weapon==WP_SABER && !Left && !Right) + { + Left = (WeaponPose==WPOSE_SABERLEFT); + Right = !Left; + } + } + + + if (Left) + {// Attack Left + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VS_ATL_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_ATL_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_ATR_TO_L_S; break; + default: assert(0); + } + } + else if (Right) + {// Attack Right + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VS_ATR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_ATL_TO_R_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_ATR_S; break; + default: assert(0); + } + } + else + {// Attack Ahead + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VS_ATF_G; break; + default: assert(0); + } + } + + } + else if (Left && pVeh->m_ucmd.buttons&BUTTON_USE) + {// Look To The Left Behind + iBlend = 400; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + switch(WeaponPose) + { + case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; + default: Anim = BOTH_VS_LOOKLEFT; + } + } + else if (Right && pVeh->m_ucmd.buttons&BUTTON_USE) + {// Look To The Right Behind + iBlend = 400; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + switch(WeaponPose) + { + case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; + default: Anim = BOTH_VS_LOOKRIGHT; + } + } + else if (Turbo) + {// Kicked In Turbo + iBlend = 50; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + Anim = BOTH_VS_TURBO; + } + else if (Flying) + {// Off the ground in a jump + iBlend = 800; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_AIR; break; + case WPOSE_BLASTER: Anim = BOTH_VS_AIR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_AIR_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_AIR_SR; break; + default: assert(0); + } + } + else if (Crashing) + {// Hit the ground! + iBlend = 100; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_LAND; break; + case WPOSE_BLASTER: Anim = BOTH_VS_LAND_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_LAND_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_LAND_SR; break; + default: assert(0); + } + } + else + {// No Special Moves + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + + if (pVeh->m_vOrientation[ROLL] <= -20) + {// Lean Left + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_LEANL; break; + case WPOSE_BLASTER: Anim = BOTH_VS_LEANL_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_LEANL_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_LEANL_SR; break; + default: assert(0); + } + } + else if (pVeh->m_vOrientation[ROLL] >= 20) + {// Lean Right + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_LEANR; break; + case WPOSE_BLASTER: Anim = BOTH_VS_LEANR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_LEANR_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_LEANR_SR; break; + default: assert(0); + } + } + else + {// No Lean + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_IDLE; break; + case WPOSE_BLASTER: Anim = BOTH_VS_IDLE_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; + default: assert(0); + } + } + }// No Special Moves + }// Going backwards? + +#ifdef _JK2MP + iFlags &= ~SETANIM_FLAG_OVERRIDE; + if (pVeh->m_pPilot->playerState->torsoAnim == Anim) + { + pVeh->m_pPilot->playerState->torsoTimer = BG_AnimLength(pVeh->m_pPilot->localAnimIndex, Anim); + } + if (pVeh->m_pPilot->playerState->legsAnim == Anim) + { + pVeh->m_pPilot->playerState->legsTimer = BG_AnimLength(pVeh->m_pPilot->localAnimIndex, Anim); + } + BG_SetAnim(pVeh->m_pPilot->playerState, bgAllAnims[pVeh->m_pPilot->localAnimIndex].anims, + SETANIM_BOTH, Anim, iFlags|SETANIM_FLAG_HOLD, iBlend); +#else + NPC_SetAnim( pVeh->m_pPilot, SETANIM_BOTH, Anim, iFlags, iBlend ); +#endif +} + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +void G_SetSpeederVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + pVehInfo->AnimateVehicle = AnimateVehicle; + pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; +// pVehInfo->Board = Board; +// pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; +// pVehInfo->DeathUpdate = DeathUpdate; +// pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; + pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif + + //shared + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +void G_CreateSpeederNPC( Vehicle_t **pVeh, const char *strType ) +{ +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strType )]; +#else + // Allocate the Vehicle. + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strType )]; +#endif +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/codemp/game/WalkerNPC.c b/codemp/game/WalkerNPC.c new file mode 100644 index 0000000..394d140 --- /dev/null +++ b/codemp/game/WalkerNPC.c @@ -0,0 +1,636 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +#endif + +#ifdef QAGAME //we only want a few of these functions for BG + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern void Vehicle_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_VehicleTrace( trace_t *results, const vec3_t start, const vec3_t tMins, const vec3_t tMaxs, const vec3_t end, int passEntityNum, int contentmask ); + +static void RegisterAssets( Vehicle_t *pVeh ) +{ + //atst uses turret weapon +#ifdef _JK2MP + RegisterItem(BG_FindItemForWeapon(WP_TURRET)); +#else + // PUT SOMETHING HERE... +#endif + + //call the standard RegisterAssets now + g_vehicleInfo[VEHICLE_BASE].RegisterAssets( pVeh ); +} + +// Like a think or move command, this updates various vehicle properties. +/* +static bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + return g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ); +} +*/ + +// Board this Vehicle (get on). The first entity to board an empty vehicle becomes the Pilot. +static bool Board( Vehicle_t *pVeh, bgEntity_t *pEnt ) +{ + if ( !g_vehicleInfo[VEHICLE_BASE].Board( pVeh, pEnt ) ) + return false; + + // Set the board wait time (they won't be able to do anything, including getting off, for this amount of time). + pVeh->m_iBoarding = level.time + 1500; + + return true; +} +#endif //QAGAME + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + float fWalkSpeedMax; + bgEntity_t *parent = pVeh->m_pParentEntity; +#ifdef _JK2MP + playerState_t *parentPS = parent->playerState; +#else + playerState_t *parentPS = &parent->client->ps; +#endif + + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + speedMax = pVeh->m_pVehicleInfo->speedMax; + + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + +#ifdef _JK2MP + if ( !parentPS->m_iVehicleNum ) +#else + if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//drifts to a stop + speedInc = speedIdle * pVeh->m_fTimeModifier; + VectorClear( parentPS->moveDir ); + //m_ucmd.forwardmove = 127; + parentPS->speed = 0; + } + else + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + + if ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + parentPS->speed -= speedIdleDec; + } + } + // No input, so coast to stop. + else if ( parentPS->speed > 0.0f ) + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < 0.0f ) + { + parentPS->speed = 0.0f; + } + } + else if ( parentPS->speed < 0.0f ) + { + parentPS->speed += speedIdleDec; + if ( parentPS->speed > 0.0f ) + { + parentPS->speed = 0.0f; + } + } + } + else + { + if ( pVeh->m_ucmd.forwardmove < 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + if ( pVeh->m_ucmd.upmove < 0 ) + { + pVeh->m_ucmd.upmove = 0; + } + + pVeh->m_ucmd.rightmove = 0; + + /*if ( !pVeh->m_pVehicleInfo->strafePerc + || (!g_speederControlScheme->value && !parent->s.number) ) + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + pVeh->m_ucmd.rightmove = 0; + }*/ + } + + if (parentPS && parentPS->electrifyTime > pm->cmd.serverTime) + { + speedMax *= 0.5f; + } + + fWalkSpeedMax = speedMax * 0.275f; + if ( pVeh->m_ucmd.buttons & BUTTON_WALKING && parentPS->speed > fWalkSpeedMax ) + { + parentPS->speed = fWalkSpeedMax; + } + else if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + + if (parentPS->stats[STAT_HEALTH] <= 0) + { //don't keep moving while you're dying! + parentPS->speed = 0; + } + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +#ifdef _JK2MP +void WalkerYawAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ + float angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); + + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*1.5f; //magic number hackery + + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +} + +/* +void WalkerPitchAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ + float angDif = AngleSubtract(pVeh->m_vOrientation[PITCH], riderPS->viewangles[PITCH]); + + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*0.8f; //magic number hackery + + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[PITCH] = AngleNormalize360(pVeh->m_vOrientation[PITCH] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +} +*/ +#endif + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessOrientCommands the Vehicle. +static void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + float speed; + bgEntity_t *parent = pVeh->m_pParentEntity; + playerState_t *parentPS, *riderPS; + +#ifdef _JK2MP + bgEntity_t *rider = NULL; + if (parent->s.owner != ENTITYNUM_NONE) + { + rider = PM_BGEntForNum(parent->s.owner); //&g_entities[parent->r.ownerNum]; + } +#else + gentity_t *rider = parent->owner; +#endif + +#ifdef _JK2MP + if ( !rider ) +#else + if ( !rider || !rider->client ) +#endif + { + rider = parent; + } + +#ifdef _JK2MP + parentPS = parent->playerState; + riderPS = rider->playerState; +#else + parentPS = &parent->client->ps; + riderPS = &rider->client->ps; +#endif + + speed = VectorLength( parentPS->velocity ); + + // If the player is the rider... + if ( rider->s.number < MAX_CLIENTS ) + {//FIXME: use the vehicle's turning stat in this calc +#ifdef _JK2MP + WalkerYawAdjust(pVeh, riderPS, parentPS); + //FighterPitchAdjust(pVeh, riderPS, parentPS); + pVeh->m_vOrientation[PITCH] = riderPS->viewangles[PITCH]; +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + pVeh->m_vOrientation[PITCH] = riderPS->viewangles[PITCH]; +#endif + } + else + { + float turnSpeed = pVeh->m_pVehicleInfo->turningSpeed; + if ( !pVeh->m_pVehicleInfo->turnWhenStopped + && !parentPS->speed )//FIXME: or !pVeh->m_ucmd.forwardmove? + {//can't turn when not moving + //FIXME: or ramp up to max turnSpeed? + turnSpeed = 0.0f; + } +#ifdef _JK2MP + if (rider->s.eType == ET_NPC) +#else + if ( !rider || rider->NPC ) +#endif + {//help NPCs out some + turnSpeed *= 2.0f; +#ifdef _JK2MP + if (parentPS->speed > 200.0f) +#else + if ( parent->client->ps.speed > 200.0f ) +#endif + { + turnSpeed += turnSpeed * parentPS->speed/200.0f*0.05f; + } + } + turnSpeed *= pVeh->m_fTimeModifier; + + //default control scheme: strafing turns, mouselook aims + if ( pVeh->m_ucmd.rightmove < 0 ) + { + pVeh->m_vOrientation[YAW] += turnSpeed; + } + else if ( pVeh->m_ucmd.rightmove > 0 ) + { + pVeh->m_vOrientation[YAW] -= turnSpeed; + } + + if ( pVeh->m_pVehicleInfo->malfunctionArmorLevel && pVeh->m_iArmor <= pVeh->m_pVehicleInfo->malfunctionArmorLevel ) + {//damaged badly + } + } + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef QAGAME //back to our game-only functions +// This function makes sure that the vehicle is properly animated. +static void AnimateVehicle( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_STAND1; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + float fSpeedPercToMax; + + // We're dead (boarding is reused here so I don't have to make another variable :-). + if ( parent->health <= 0 ) + { + /* + if ( pVeh->m_iBoarding != -999 ) // Animate the death just once! + { + pVeh->m_iBoarding = -999; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + // FIXME! Why do you keep repeating over and over!!?!?!? Bastard! + //Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_DEATH1, iFlags, iBlend ); + } + */ + return; + } + +// Following is redundant to g_vehicles.c +// if ( pVeh->m_iBoarding ) +// { +// //we have no boarding anim +// if (pVeh->m_iBoarding < level.time) +// { //we are on now +// pVeh->m_iBoarding = 0; +// } +// else +// { +// return; +// } +// } + + // Percentage of maximum speed relative to current speed. + //float fSpeed = VectorLength( client->ps.velocity ); + fSpeedPercToMax = parent->client->ps.speed / pVeh->m_pVehicleInfo->speedMax; + + // If we're moving... + if ( fSpeedPercToMax > 0.0f ) //fSpeedPercToMax >= 0.85f ) + { + float fYawDelta; + + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE; + fYawDelta = pVeh->m_vPrevOrientation[YAW] - pVeh->m_vOrientation[YAW]; + + // NOTE: Mikes suggestion for fixing the stuttering walk (left/right) is to maintain the + // current frame between animations. I have no clue how to do this and have to work on other + // stuff so good luck to him :-p AReis + + // If we're walking (or our speed is less than .275%)... + if ( ( pVeh->m_ucmd.buttons & BUTTON_WALKING ) || fSpeedPercToMax < 0.275f ) + { + // Make them lean if we're turning. + /*if ( fYawDelta < -0.0001f ) + { + Anim = BOTH_VT_WALK_FWD_L; + } + else if ( fYawDelta > 0.0001 ) + { + Anim = BOTH_VT_WALK_FWD_R; + } + else*/ + { + Anim = BOTH_WALK1; + } + } + // otherwise we're running. + else + { + // Make them lean if we're turning. + /*if ( fYawDelta < -0.0001f ) + { + Anim = BOTH_VT_RUN_FWD_L; + } + else if ( fYawDelta > 0.0001 ) + { + Anim = BOTH_VT_RUN_FWD_R; + } + else*/ + { + Anim = BOTH_RUN1; + } + } + } + else + { + // Going in reverse... + if ( fSpeedPercToMax < -0.018f ) + { + iFlags = SETANIM_FLAG_NORMAL; + Anim = BOTH_WALKBACK1; + iBlend = 500; + } + else + { + //int iChance = Q_irand( 0, 20000 ); + + // Every once in a while buck or do a different idle... + iFlags = SETANIM_FLAG_NORMAL | SETANIM_FLAG_RESTART | SETANIM_FLAG_HOLD; + iBlend = 600; +#ifdef _JK2MP + if (parent->client->ps.m_iVehicleNum) +#else + if ( pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//occupado + Anim = BOTH_STAND1; + } + else + {//wide open for you, baby + Anim = BOTH_STAND2; + } + } + } + + Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); +} + +//rwwFIXMEFIXME: This is all going to have to be predicted I think, or it will feel awful +//and lagged +#endif //QAGAME + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +//on the client this function will only set up the process command funcs +void G_SetWalkerVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + pVehInfo->AnimateVehicle = AnimateVehicle; +// pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; + pVehInfo->Board = Board; +// pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; +// pVehInfo->DeathUpdate = DeathUpdate; + pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; +// pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif //QAGAME + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +//this is a BG function too in MP so don't un-bg-compatibilify it -rww +void G_CreateWalkerNPC( Vehicle_t **pVeh, const char *strAnimalType ) +{ + // Allocate the Vehicle. +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#else + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#endif +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/codemp/game/ai.h b/codemp/game/ai.h new file mode 100644 index 0000000..7c7df68 --- /dev/null +++ b/codemp/game/ai.h @@ -0,0 +1,126 @@ +#ifndef __AI__ +#define __AI__ + +//Distance ratings +typedef enum +{ + DIST_MELEE, + DIST_LONG, +} distance_e; + +//Attack types +typedef enum +{ + ATTACK_MELEE, + ATTACK_RANGE, +} attack_e; + +enum +{ + SQUAD_IDLE, //No target found, waiting + SQUAD_STAND_AND_SHOOT, //Standing in position and shoot (no cover) + SQUAD_RETREAT, //Running away from combat + SQUAD_COVER, //Under protective cover + SQUAD_TRANSITION, //Moving between points, not firing + SQUAD_POINT, //On point, laying down suppressive fire + SQUAD_SCOUT, //Poking out to draw enemy + NUM_SQUAD_STATES, +}; + +//sigh... had to move in here for groupInfo +typedef enum //# rank_e +{ + RANK_CIVILIAN, + RANK_CREWMAN, + RANK_ENSIGN, + RANK_LT_JG, + RANK_LT, + RANK_LT_COMM, + RANK_COMMANDER, + RANK_CAPTAIN +} rank_t; + +qboolean NPC_CheckPlayerTeamStealth( void ); + +//AI_GRENADIER +void NPC_BSGrenadier_Default( void ); + +//AI_SNIPER +void NPC_BSSniper_Default( void ); + +//AI_STORMTROOPER +void NPC_BSST_Investigate( void ); +void NPC_BSST_Default( void ); +void NPC_BSST_Sleep( void ); + +//AI_JEDI +void NPC_BSJedi_Investigate( void ); +void NPC_BSJedi_Default( void ); +void NPC_BSJedi_FollowLeader( void ); + +// AI_DROID +void NPC_BSDroid_Default( void ); + +// AI_ImperialProbe +void NPC_BSImperialProbe_Default( void ); + +// AI_atst +//void NPC_BSATST_Default( void ); + +//void NPC_BSInterrogator_Default( void ); + +// AI Mark 1 +//void NPC_BSMark1_Default( void ); + +// AI Mark 2 +//void NPC_BSMark2_Default( void ); + + +void NPC_BSMineMonster_Default( void ); +void NPC_BSHowler_Default( void ); +void NPC_BSRancor_Default( void ); + +//Utilities +//Group AI +#define MAX_FRAME_GROUPS 32 +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct AIGroupMember_s +{ + int number; + int waypoint; + int pathCostToEnemy; + int closestBuddy; +} AIGroupMember_t; + +#define MAX_GROUP_MEMBERS 32 +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct AIGroupInfo_s +{ + int numGroup; + qboolean processed; + team_t team; + gentity_t *enemy; + int enemyWP; + int speechDebounceTime; + int lastClearShotTime; + int lastSeenEnemyTime; + int morale; + int moraleAdjust; + int moraleDebounce; + int memberValidateTime; + int activeMemberNum; + gentity_t *commander; + vec3_t enemyLastSeenPos; + int numState[ NUM_SQUAD_STATES ]; + AIGroupMember_t member[ MAX_GROUP_MEMBERS ]; +} AIGroupInfo_t; + +int AI_GetGroupSize( vec3_t origin, int radius, team_t playerTeam, gentity_t *avoid ); +int AI_GetGroupSize2( gentity_t *ent, int radius ); + +void AI_GetGroup( gentity_t *self ); + +qboolean AI_CheckEnemyCollision( gentity_t *ent, qboolean takeEnemy ); +gentity_t *AI_DistributeAttack( gentity_t *attacker, gentity_t *enemy, team_t team, int threshold ); + +#endif //__AI__ diff --git a/codemp/game/ai_main.c b/codemp/game/ai_main.c new file mode 100644 index 0000000..c367796 --- /dev/null +++ b/codemp/game/ai_main.c @@ -0,0 +1,7649 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_main.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_main.c $ + * $Author: osman $ + * $Revision: 1.27 $ + * $Modtime: 6/06/01 1:11p $ + * $Date: 2003/10/07 13:36:41 $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" //bot lib interface +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "w_saber.h" +// +#include "chars.h" +#include "inv.h" +#include "syn.h" + +/* +#define BOT_CTF_DEBUG 1 +*/ + +#define MAX_PATH 144 + +#define BOT_THINK_TIME 0 + +//bot states +bot_state_t *botstates[MAX_CLIENTS]; +//number of bots +int numbots; +//floating point time +float floattime; +//time to do a regular update +float regularupdate_time; +// + +//for siege: +extern int rebel_attackers; +extern int imperial_attackers; + +boteventtracker_t gBotEventTracker[MAX_CLIENTS]; + +//rww - new bot cvars.. +vmCvar_t bot_forcepowers; +vmCvar_t bot_forgimmick; +vmCvar_t bot_honorableduelacceptance; +vmCvar_t bot_pvstype; +vmCvar_t bot_normgpath; +#ifndef FINAL_BUILD +vmCvar_t bot_getinthecarrr; +#endif + +#ifdef _DEBUG +vmCvar_t bot_nogoals; +vmCvar_t bot_debugmessages; +#endif + +vmCvar_t bot_attachments; +vmCvar_t bot_camp; + +vmCvar_t bot_wp_info; +vmCvar_t bot_wp_edit; +vmCvar_t bot_wp_clearweight; +vmCvar_t bot_wp_distconnect; +vmCvar_t bot_wp_visconnect; +//end rww + +wpobject_t *flagRed; +wpobject_t *oFlagRed; +wpobject_t *flagBlue; +wpobject_t *oFlagBlue; + +gentity_t *eFlagRed; +gentity_t *droppedRedFlag; +gentity_t *eFlagBlue; +gentity_t *droppedBlueFlag; + +char *ctfStateNames[] = { + "CTFSTATE_NONE", + "CTFSTATE_ATTACKER", + "CTFSTATE_DEFENDER", + "CTFSTATE_RETRIEVAL", + "CTFSTATE_GUARDCARRIER", + "CTFSTATE_GETFLAGHOME", + "CTFSTATE_MAXCTFSTATES" +}; + +char *ctfStateDescriptions[] = { + "I'm not occupied", + "I'm attacking the enemy's base", + "I'm defending our base", + "I'm getting our flag back", + "I'm escorting our flag carrier", + "I've got the enemy's flag" +}; + +char *siegeStateDescriptions[] = { + "I'm not occupied", + "I'm attemtping to complete the current objective", + "I'm preventing the enemy from completing their objective" +}; + +char *teamplayStateDescriptions[] = { + "I'm not occupied", + "I'm following my squad commander", + "I'm assisting my commanding", + "I'm attempting to regroup and form a new squad" +}; + +void BotStraightTPOrderCheck(gentity_t *ent, int ordernum, bot_state_t *bs) +{ + switch (ordernum) + { + case 0: + if (bs->squadLeader == ent) + { + bs->teamplayState = 0; + bs->squadLeader = NULL; + } + break; + case TEAMPLAYSTATE_FOLLOWING: + bs->teamplayState = ordernum; + bs->isSquadLeader = 0; + bs->squadLeader = ent; + bs->wpDestSwitchTime = 0; + break; + case TEAMPLAYSTATE_ASSISTING: + bs->teamplayState = ordernum; + bs->isSquadLeader = 0; + bs->squadLeader = ent; + bs->wpDestSwitchTime = 0; + break; + default: + bs->teamplayState = ordernum; + break; + } +} + +void BotSelectWeapon(int client, int weapon) +{ + if (weapon <= WP_NONE) + { +// assert(0); + return; + } + trap_EA_SelectWeapon(client, weapon); +} + +void BotReportStatus(bot_state_t *bs) +{ + if (g_gametype.integer == GT_TEAM) + { + trap_EA_SayTeam(bs->client, teamplayStateDescriptions[bs->teamplayState]); + } + else if (g_gametype.integer == GT_SIEGE) + { + trap_EA_SayTeam(bs->client, siegeStateDescriptions[bs->siegeState]); + } + else if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) + { + trap_EA_SayTeam(bs->client, ctfStateDescriptions[bs->ctfState]); + } +} + +//accept a team order from a player +void BotOrder(gentity_t *ent, int clientnum, int ordernum) +{ + int stateMin = 0; + int stateMax = 0; + int i = 0; + + if (!ent || !ent->client || !ent->client->sess.teamLeader) + { + return; + } + + if (clientnum != -1 && !botstates[clientnum]) + { + return; + } + + if (clientnum != -1 && !OnSameTeam(ent, &g_entities[clientnum])) + { + return; + } + + if (g_gametype.integer != GT_CTF && g_gametype.integer != GT_CTY && g_gametype.integer != GT_SIEGE && + g_gametype.integer != GT_TEAM) + { + return; + } + + if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) + { + stateMin = CTFSTATE_NONE; + stateMax = CTFSTATE_MAXCTFSTATES; + } + else if (g_gametype.integer == GT_SIEGE) + { + stateMin = SIEGESTATE_NONE; + stateMax = SIEGESTATE_MAXSIEGESTATES; + } + else if (g_gametype.integer == GT_TEAM) + { + stateMin = TEAMPLAYSTATE_NONE; + stateMax = TEAMPLAYSTATE_MAXTPSTATES; + } + + if ((ordernum < stateMin && ordernum != -1) || ordernum >= stateMax) + { + return; + } + + if (clientnum != -1) + { + if (ordernum == -1) + { + BotReportStatus(botstates[clientnum]); + } + else + { + BotStraightTPOrderCheck(ent, ordernum, botstates[clientnum]); + botstates[clientnum]->state_Forced = ordernum; +/* + botstates[clientnum]->chatObject = ent; + botstates[clientnum]->chatAltObject = NULL; + if (BotDoChat(botstates[clientnum], "OrderAccepted", 1)) + { + botstates[clientnum]->chatTeam = 1; + } +*/ + } + } + else + { + while (i < MAX_CLIENTS) + { + if (botstates[i] && OnSameTeam(ent, &g_entities[i])) + { + if (ordernum == -1) + { + BotReportStatus(botstates[i]); + } + else + { + BotStraightTPOrderCheck(ent, ordernum, botstates[i]); + botstates[i]->state_Forced = ordernum; +/* + botstates[i]->chatObject = ent; + botstates[i]->chatAltObject = NULL; + if (BotDoChat(botstates[i], "OrderAccepted", 0)) + { + botstates[i]->chatTeam = 1; + } +*/ + } + } + + i++; + } + } +} + +//See if bot is mindtricked by the client in question +int BotMindTricked(int botClient, int enemyClient) +{ + forcedata_t *fd; + + if (!g_entities[enemyClient].client) + { + return 0; + } + + fd = &g_entities[enemyClient].client->ps.fd; + + if (!fd) + { + return 0; + } + + if (botClient > 47) + { + if (fd->forceMindtrickTargetIndex4 & (1 << (botClient-48))) + { + return 1; + } + } + else if (botClient > 31) + { + if (fd->forceMindtrickTargetIndex3 & (1 << (botClient-32))) + { + return 1; + } + } + else if (botClient > 15) + { + if (fd->forceMindtrickTargetIndex2 & (1 << (botClient-16))) + { + return 1; + } + } + else + { + if (fd->forceMindtrickTargetIndex & (1 << botClient)) + { + return 1; + } + } + + return 0; +} + +int BotGetWeaponRange(bot_state_t *bs); +int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent); + +void ExitLevel( void ); + +void QDECL BotAI_Print(int type, char *fmt, ...) { return; } + +qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower ); + +int IsTeamplay(void) +{ + if ( g_gametype.integer < GT_TEAM ) + { + return 0; + } + + return 1; +} + +/* +================== +BotAI_GetClientState +================== +*/ +int BotAI_GetClientState( int clientNum, playerState_t *state ) { + gentity_t *ent; + + ent = &g_entities[clientNum]; + if ( !ent->inuse ) { + return qfalse; + } + if ( !ent->client ) { + return qfalse; + } + + memcpy( state, &ent->client->ps, sizeof(playerState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetEntityState +================== +*/ +int BotAI_GetEntityState( int entityNum, entityState_t *state ) { + gentity_t *ent; + + ent = &g_entities[entityNum]; + memset( state, 0, sizeof(entityState_t) ); + if (!ent->inuse) return qfalse; + if (!ent->r.linked) return qfalse; + if (ent->r.svFlags & SVF_NOCLIENT) return qfalse; + memcpy( state, &ent->s, sizeof(entityState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetSnapshotEntity +================== +*/ +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { + int entNum; + + entNum = trap_BotGetSnapshotEntity( clientNum, sequence ); + if ( entNum == -1 ) { + memset(state, 0, sizeof(entityState_t)); + return -1; + } + + BotAI_GetEntityState( entNum, state ); + + return sequence + 1; +} + +/* +============== +BotEntityInfo +============== +*/ +void BotEntityInfo(int entnum, aas_entityinfo_t *info) { + trap_AAS_EntityInfo(entnum, info); +} + +/* +============== +NumBots +============== +*/ +int NumBots(void) { + return numbots; +} + +/* +============== +AngleDifference +============== +*/ +float AngleDifference(float ang1, float ang2) { + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + return diff; +} + +/* +============== +BotChangeViewAngle +============== +*/ +float BotChangeViewAngle(float angle, float ideal_angle, float speed) { + float move; + + angle = AngleMod(angle); + ideal_angle = AngleMod(ideal_angle); + if (angle == ideal_angle) return angle; + move = ideal_angle - angle; + if (ideal_angle > angle) { + if (move > 180.0) move -= 360.0; + } + else { + if (move < -180.0) move += 360.0; + } + if (move > 0) { + if (move > speed) move = speed; + } + else { + if (move < -speed) move = -speed; + } + return AngleMod(angle + move); +} + +/* +============== +BotChangeViewAngles +============== +*/ +void BotChangeViewAngles(bot_state_t *bs, float thinktime) { + float diff, factor, maxchange, anglespeed, disired_speed; + int i; + + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + if (bs->settings.skill <= 1) + { + factor = (bs->skills.turnspeed_combat*0.4f)*bs->settings.skill; + } + else if (bs->settings.skill <= 2) + { + factor = (bs->skills.turnspeed_combat*0.6f)*bs->settings.skill; + } + else if (bs->settings.skill <= 3) + { + factor = (bs->skills.turnspeed_combat*0.8f)*bs->settings.skill; + } + else + { + factor = bs->skills.turnspeed_combat*bs->settings.skill; + } + } + else + { + factor = bs->skills.turnspeed; + } + + if (factor > 1) + { + factor = 1; + } + if (factor < 0.001) + { + factor = 0.001f; + } + + maxchange = bs->skills.maxturn; + + //if (maxchange < 240) maxchange = 240; + maxchange *= thinktime; + for (i = 0; i < 2; i++) { + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]); + diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]); + disired_speed = diff * factor; + bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed); + if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange; + if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange; + anglespeed = bs->viewanglespeed[i]; + if (anglespeed > maxchange) anglespeed = maxchange; + if (anglespeed < -maxchange) anglespeed = -maxchange; + bs->viewangles[i] += anglespeed; + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + bs->viewanglespeed[i] *= 0.45 * (1 - factor); + } + if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; + trap_EA_View(bs->client, bs->viewangles); +} + +/* +============== +BotInputToUserCommand +============== +*/ +void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time, int useTime) { + vec3_t angles, forward, right; + short temp; + int j; + + //clear the whole structure + memset(ucmd, 0, sizeof(usercmd_t)); + // + //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); + //the duration for the user command in milli seconds + ucmd->serverTime = time; + // + if (bi->actionflags & ACTION_DELAYEDJUMP) { + bi->actionflags |= ACTION_JUMP; + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } + //set the buttons + if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK; + if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK; + if (bi->actionflags & ACTION_ALT_ATTACK) ucmd->buttons |= BUTTON_ALT_ATTACK; +// if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK; + if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE; + if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE; + if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING; + + if (bi->actionflags & ACTION_FORCEPOWER) ucmd->buttons |= BUTTON_FORCEPOWER; + + if (useTime < level.time && Q_irand(1, 10) < 5) + { //for now just hit use randomly in case there's something useable around + ucmd->buttons |= BUTTON_USE; + } +#if 0 +// Here's an interesting bit. The bots in TA used buttons to do additional gestures. +// I ripped them out because I didn't want too many buttons given the fact that I was already adding some for JK2. +// We can always add some back in if we want though. + if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE; + if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE; + if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG; + if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE; + if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL; + if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME; +#endif //0 + + if (bi->weapon == WP_NONE) + { +#ifdef _DEBUG +// Com_Printf("WARNING: Bot tried to use WP_NONE!\n"); +#endif + bi->weapon = WP_BRYAR_PISTOL; + } + + // + ucmd->weapon = bi->weapon; + //set the view angles + //NOTE: the ucmd->angles are the angles WITHOUT the delta angles + ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]); + ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]); + ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]); + //subtract the delta angles + for (j = 0; j < 3; j++) { + temp = ucmd->angles[j] - delta_angles[j]; + ucmd->angles[j] = temp; + } + //NOTE: movement is relative to the REAL view angles + //get the horizontal forward and right vector + //get the pitch in the range [-180, 180] + if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH]; + else angles[PITCH] = 0; + angles[YAW] = bi->viewangles[YAW]; + angles[ROLL] = 0; + AngleVectors(angles, forward, right, NULL); + //bot input speed is in the range [0, 400] + bi->speed = bi->speed * 127 / 400; + //set the view independent movement + ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed; + ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed; + ucmd->upmove = abs((int)(forward[2])) * bi->dir[2] * bi->speed; + //normal keyboard movement + if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127; + if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127; + if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127; + if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127; + //jump/moveup + if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127; + //crouch/movedown + if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127; + // + //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); + //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); +} + +/* +============== +BotUpdateInput +============== +*/ +void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) { + bot_input_t bi; + int j; + + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //change the bot view angles + BotChangeViewAngles(bs, (float) elapsed_time / 1000); + //retrieve the bot input + trap_EA_GetInput(bs->client, (float) time / 1000, &bi); + //respawn hack + if (bi.actionflags & ACTION_RESPAWN) { + if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK); + } + //convert the bot input to a usercmd + BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time, bs->noUseTime); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } +} + +/* +============== +RemoveColorEscapeSequences +============== +*/ +void RemoveColorEscapeSequences( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (Q_IsColorString(&text[i])) { + i++; + continue; + } + if (text[i] > 0x7E) + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + + +/* +============== +BotAI +============== +*/ +int BotAI(int client, float thinktime) { + bot_state_t *bs; + char buf[1024], *args; + int j; +#ifdef _DEBUG + int start = 0; + int end = 0; +#endif + + trap_EA_ResetInput(client); + // + bs = botstates[client]; + if (!bs || !bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client); + return qfalse; + } + + //retrieve the current client state + BotAI_GetClientState( client, &bs->cur_ps ); + + //retrieve any waiting server commands + while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) { + //have buf point to the command and args to the command arguments + args = strchr( buf, ' '); + if (!args) continue; + *args++ = '\0'; + + //remove color espace sequences from the arguments + RemoveColorEscapeSequences( args ); + + if (!Q_stricmp(buf, "cp ")) + { /*CenterPrintf*/ } + else if (!Q_stricmp(buf, "cs")) + { /*ConfigStringModified*/ } + else if (!Q_stricmp(buf, "scores")) + { /*FIXME: parse scores?*/ } + else if (!Q_stricmp(buf, "clientLevelShot")) + { /*ignore*/ } + } + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //increase the local time of the bot + bs->ltime += thinktime; + // + bs->thinktime = thinktime; + //origin of the bot + VectorCopy(bs->cur_ps.origin, bs->origin); + //eye coordinates of the bot + VectorCopy(bs->cur_ps.origin, bs->eye); + bs->eye[2] += bs->cur_ps.viewheight; + //get the area the bot is in + +#ifdef _DEBUG + start = trap_Milliseconds(); +#endif + StandardBotAI(bs, thinktime); +#ifdef _DEBUG + end = trap_Milliseconds(); + + trap_Cvar_Update(&bot_debugmessages); + + if (bot_debugmessages.integer) + { + Com_Printf("Single AI frametime: %i\n", (end - start)); + } +#endif + + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //everything was ok + return qtrue; +} + +/* +================== +BotScheduleBotThink +================== +*/ +void BotScheduleBotThink(void) { + int i, botnum; + + botnum = 0; + + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + //initialize the bot think residual time + botstates[i]->botthink_residual = BOT_THINK_TIME * botnum / numbots; + botnum++; + } +} + +int PlayersInGame(void) +{ + int i = 0; + gentity_t *ent; + int pl = 0; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->pers.connected == CON_CONNECTED) + { + pl++; + } + + i++; + } + + return pl; +} + +/* +============== +BotAISetupClient +============== +*/ +int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { + bot_state_t *bs; + + if (!botstates[client]) botstates[client] = (bot_state_t *) B_Alloc(sizeof(bot_state_t)); //G_Alloc(sizeof(bot_state_t)); + //rww - G_Alloc bad! B_Alloc good. + + memset(botstates[client], 0, sizeof(bot_state_t)); + + bs = botstates[client]; + + if (bs && bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); + return qfalse; + } + + memcpy(&bs->settings, settings, sizeof(bot_settings_t)); + + bs->client = client; //need to know the client number before doing personality stuff + + //initialize weapon weight defaults.. + bs->botWeaponWeights[WP_NONE] = 0; + bs->botWeaponWeights[WP_STUN_BATON] = 1; + bs->botWeaponWeights[WP_SABER] = 10; + bs->botWeaponWeights[WP_BRYAR_PISTOL] = 11; + bs->botWeaponWeights[WP_BLASTER] = 12; + bs->botWeaponWeights[WP_DISRUPTOR] = 13; + bs->botWeaponWeights[WP_BOWCASTER] = 14; + bs->botWeaponWeights[WP_REPEATER] = 15; + bs->botWeaponWeights[WP_DEMP2] = 16; + bs->botWeaponWeights[WP_FLECHETTE] = 17; + bs->botWeaponWeights[WP_ROCKET_LAUNCHER] = 18; + bs->botWeaponWeights[WP_THERMAL] = 14; + bs->botWeaponWeights[WP_TRIP_MINE] = 0; + bs->botWeaponWeights[WP_DET_PACK] = 0; + bs->botWeaponWeights[WP_MELEE] = 1; + + BotUtilizePersonality(bs); + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { + bs->botWeaponWeights[WP_SABER] = 13; + } + + bs->inuse = qtrue; + bs->entitynum = client; + bs->setupcount = 4; + bs->entergame_time = FloatTime(); + numbots++; + + //NOTE: reschedule the bot thinking + BotScheduleBotThink(); + +/* + if (PlayersInGame()) + { //don't talk to yourself + BotDoChat(bs, "GeneralGreetings", 0); + } +*/ + + return qtrue; +} + +/* +============== +BotAIShutdownClient +============== +*/ +int BotAIShutdownClient(int client, qboolean restart) { + bot_state_t *bs; + + bs = botstates[client]; + if (!bs || !bs->inuse) { + //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); + return qfalse; + } + + //clear the bot state + memset(bs, 0, sizeof(bot_state_t)); + //set the inuse flag to qfalse + bs->inuse = qfalse; + //there's one bot less + numbots--; + //everything went ok + return qtrue; +} + +/* +============== +BotResetState + +called when a bot enters the intermission or observer mode and +when the level is changed +============== +*/ +void BotResetState(bot_state_t *bs) { + int client, entitynum, inuse; + int movestate, goalstate, weaponstate; + bot_settings_t settings; + playerState_t ps; //current player state + float entergame_time; + + //save some things that should not be reset here + memcpy(&settings, &bs->settings, sizeof(bot_settings_t)); + memcpy(&ps, &bs->cur_ps, sizeof(playerState_t)); + inuse = bs->inuse; + client = bs->client; + entitynum = bs->entitynum; + movestate = bs->ms; + goalstate = bs->gs; + weaponstate = bs->ws; + entergame_time = bs->entergame_time; + //reset the whole state + memset(bs, 0, sizeof(bot_state_t)); + //copy back some state stuff that should not be reset + bs->ms = movestate; + bs->gs = goalstate; + bs->ws = weaponstate; + memcpy(&bs->cur_ps, &ps, sizeof(playerState_t)); + memcpy(&bs->settings, &settings, sizeof(bot_settings_t)); + bs->inuse = inuse; + bs->client = client; + bs->entitynum = entitynum; + bs->entergame_time = entergame_time; +} + +/* +============== +BotAILoadMap +============== +*/ +int BotAILoadMap( int restart ) { + int i; + + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotResetState( botstates[i] ); + botstates[i]->setupcount = 4; + } + } + + return qtrue; +} + +//rww - bot ai + +//standard visibility check +int OrgVisible(vec3_t org1, vec3_t org2, int ignore) +{ + trace_t tr; + + trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1) + { + return 1; + } + + return 0; +} + +//special waypoint visibility check +int WPOrgVisible(gentity_t *bot, vec3_t org1, vec3_t org2, int ignore) +{ + trace_t tr; + gentity_t *ownent; + + trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1) + { + trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_PLAYERSOLID); + + if (tr.fraction != 1 && tr.entityNum != ENTITYNUM_NONE && g_entities[tr.entityNum].s.eType == ET_SPECIAL) + { + if (g_entities[tr.entityNum].parent && g_entities[tr.entityNum].parent->client) + { + ownent = g_entities[tr.entityNum].parent; + + if (OnSameTeam(bot, ownent) || bot->s.number == ownent->s.number) + { + return 1; + } + } + return 2; + } + + return 1; + } + + return 0; +} + +//visibility check with hull trace +int OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore) +{ + trace_t tr; + + if (g_RMG.integer) + { + trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID); + } + else + { + trap_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID); + } + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + + return 0; +} + +//see if there's a func_* ent under the given pos. +//kind of badly done, but this shouldn't happen +//often. +int CheckForFunc(vec3_t org, int ignore) +{ + gentity_t *fent; + vec3_t under; + trace_t tr; + + VectorCopy(org, under); + + under[2] -= 64; + + trap_Trace(&tr, org, NULL, NULL, under, ignore, MASK_SOLID); + + if (tr.fraction == 1) + { + return 0; + } + + fent = &g_entities[tr.entityNum]; + + if (!fent) + { + return 0; + } + + if (strstr(fent->classname, "func_")) + { + return 1; //there's a func brush here + } + + return 0; +} + +//perform pvs check based on rmg or not +qboolean BotPVSCheck( const vec3_t p1, const vec3_t p2 ) +{ + if (g_RMG.integer && bot_pvstype.integer) + { + vec3_t subPoint; + VectorSubtract(p1, p2, subPoint); + + if (VectorLength(subPoint) > 5000) + { + return qfalse; + } + return qtrue; + } + + return trap_InPVS(p1, p2); +} + +//get the index to the nearest visible waypoint in the global trail +int GetNearestVisibleWP(vec3_t org, int ignore) +{ + int i; + float bestdist; + float flLen; + int bestindex; + vec3_t a, mins, maxs; + + i = 0; + if (g_RMG.integer) + { + bestdist = 300; + } + else + { + bestdist = 800;//99999; + //don't trace over 800 units away to avoid GIANT HORRIBLE SPEED HITS ^_^ + } + bestindex = -1; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -1; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 1; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(org, gWPArray[i]->origin, a); + flLen = VectorLength(a); + + if (flLen < bestdist && (g_RMG.integer || BotPVSCheck(org, gWPArray[i]->origin)) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore)) + { + bestdist = flLen; + bestindex = i; + } + } + + i++; + } + + return bestindex; +} + +//wpDirection +//0 == FORWARD +//1 == BACKWARD + +//see if this is a valid waypoint to pick up in our +//current state (whatever that may be) +int PassWayCheck(bot_state_t *bs, int windex) +{ + if (!gWPArray[windex] || !gWPArray[windex]->inuse) + { //bad point index + return 0; + } + + if (g_RMG.integer) + { + if ((gWPArray[windex]->flags & WPFLAG_RED_FLAG) || + (gWPArray[windex]->flags & WPFLAG_BLUE_FLAG)) + { //red or blue flag, we'd like to get here + return 1; + } + } + + if (bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_FWD)) + { //we're not travelling in a direction on the trail that will allow us to pass this point + return 0; + } + else if (!bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_BACK)) + { //we're not travelling in a direction on the trail that will allow us to pass this point + return 0; + } + + if (bs->wpCurrent && gWPArray[windex]->forceJumpTo && + gWPArray[windex]->origin[2] > (bs->wpCurrent->origin[2]+64) && + bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[windex]->forceJumpTo) + { //waypoint requires force jump level greater than our current one to pass + return 0; + } + + return 1; +} + +//tally up the distance between two waypoints +float TotalTrailDistance(int start, int end, bot_state_t *bs) +{ + int beginat; + int endat; + float distancetotal; + + distancetotal = 0; + + if (start > end) + { + beginat = end; + endat = start; + } + else + { + beginat = start; + endat = end; + } + + while (beginat < endat) + { + if (beginat >= gWPNum || !gWPArray[beginat] || !gWPArray[beginat]->inuse) + { //invalid waypoint index + return -1; + } + + if (!g_RMG.integer) + { + if ((end > start && gWPArray[beginat]->flags & WPFLAG_ONEWAY_BACK) || + (start > end && gWPArray[beginat]->flags & WPFLAG_ONEWAY_FWD)) + { //a one-way point, this means this path cannot be travelled to the final point + return -1; + } + } + +#if 0 //disabled force jump checks for now + if (gWPArray[beginat]->forceJumpTo) + { + if (gWPArray[beginat-1] && gWPArray[beginat-1]->origin[2]+64 < gWPArray[beginat]->origin[2]) + { + gdif = gWPArray[beginat]->origin[2] - gWPArray[beginat-1]->origin[2]; + } + + if (gdif) + { + if (bs && bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[beginat]->forceJumpTo) + { + return -1; + } + } + } + + if (bs->wpCurrent && gWPArray[windex]->forceJumpTo && + gWPArray[windex]->origin[2] > (bs->wpCurrent->origin[2]+64) && + bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[windex]->forceJumpTo) + { + return -1; + } +#endif + + distancetotal += gWPArray[beginat]->disttonext; + + beginat++; + } + + return distancetotal; +} + +//see if there's a route shorter than our current one to get +//to the final destination we currently desire +void CheckForShorterRoutes(bot_state_t *bs, int newwpindex) +{ + float bestlen; + float checklen; + int bestindex; + int i; + int fj; + + i = 0; + fj = 0; + + if (!bs->wpDestination) + { + return; + } + + //set our traversal direction based on the index of the point + if (newwpindex < bs->wpDestination->index) + { + bs->wpDirection = 0; + } + else if (newwpindex > bs->wpDestination->index) + { + bs->wpDirection = 1; + } + + //can't switch again yet + if (bs->wpSwitchTime > level.time) + { + return; + } + + //no neighboring points to check off of + if (!gWPArray[newwpindex]->neighbornum) + { + return; + } + + //get the trail distance for our wp + bestindex = newwpindex; + bestlen = TotalTrailDistance(newwpindex, bs->wpDestination->index, bs); + + while (i < gWPArray[newwpindex]->neighbornum) + { //now go through the neighbors and check the distance to the desired point from each neighbor + checklen = TotalTrailDistance(gWPArray[newwpindex]->neighbors[i].num, bs->wpDestination->index, bs); + + if (checklen < bestlen-64 || bestlen == -1) + { //this path covers less distance, let's take it instead + if (bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] >= gWPArray[newwpindex]->neighbors[i].forceJumpTo) + { + bestlen = checklen; + bestindex = gWPArray[newwpindex]->neighbors[i].num; + + if (gWPArray[newwpindex]->neighbors[i].forceJumpTo) + { + fj = gWPArray[newwpindex]->neighbors[i].forceJumpTo; + } + else + { + fj = 0; + } + } + } + + i++; + } + + if (bestindex != newwpindex && bestindex != -1) + { //we found a path we want to switch to, let's do it + bs->wpCurrent = gWPArray[bestindex]; + bs->wpSwitchTime = level.time + 3000; + + if (fj) + { //do we have to force jump to get to this neighbor? +#ifndef FORCEJUMP_INSTANTMETHOD + bs->forceJumpChargeTime = level.time + 1000; + bs->beStill = level.time + 1000; + bs->forceJumping = bs->forceJumpChargeTime; +#else + bs->beStill = level.time + 500; + bs->jumpTime = level.time + fj*1200; + bs->jDelay = level.time + 200; + bs->forceJumping = bs->jumpTime; +#endif + } + } +} + +//check for flags on the waypoint we're currently travelling to +//and perform the desired behavior based on the flag +void WPConstantRoutine(bot_state_t *bs) +{ + if (!bs->wpCurrent) + { + return; + } + + if (bs->wpCurrent->flags & WPFLAG_DUCK) + { //duck while travelling to this point + bs->duckTime = level.time + 100; + } + +#ifndef FORCEJUMP_INSTANTMETHOD + if (bs->wpCurrent->flags & WPFLAG_JUMP) + { //jump while travelling to this point + float heightDif = (bs->wpCurrent->origin[2] - bs->origin[2]+16); + + if (bs->origin[2]+16 >= bs->wpCurrent->origin[2]) + { //don't need to jump, we're already higher than this point + heightDif = 0; + } + + if (heightDif > 40 && (bs->cur_ps.fd.forcePowersKnown & (1 << FP_LEVITATION)) && (bs->cur_ps.fd.forceJumpCharge < (forceJumpStrength[bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION]]-100) || bs->cur_ps.groundEntityNum == ENTITYNUM_NONE)) + { //alright, let's jump + bs->forceJumpChargeTime = level.time + 1000; + if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE && bs->jumpPrep < (level.time-300)) + { + bs->jumpPrep = level.time + 700; + } + bs->beStill = level.time + 300; + bs->jumpTime = 0; + + if (bs->wpSeenTime < (level.time + 600)) + { + bs->wpSeenTime = level.time + 600; + } + } + else if (heightDif > 64 && !(bs->cur_ps.fd.forcePowersKnown & (1 << FP_LEVITATION))) + { //this point needs force jump to reach and we don't have it + //Kill the current point and turn around + bs->wpCurrent = NULL; + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + + return; + } + } +#endif + + if (bs->wpCurrent->forceJumpTo) + { +#ifdef FORCEJUMP_INSTANTMETHOD + if (bs->origin[2]+16 < bs->wpCurrent->origin[2]) + { + bs->jumpTime = level.time + 100; + } +#else + float heightDif = (bs->wpCurrent->origin[2] - bs->origin[2]+16); + + if (bs->origin[2]+16 >= bs->wpCurrent->origin[2]) + { //then why exactly would we be force jumping? + heightDif = 0; + } + + if (bs->cur_ps.fd.forceJumpCharge < (forceJumpStrength[bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION]]-100)) + { + bs->forceJumpChargeTime = level.time + 200; + } +#endif + } +} + +//check if our ctf state is to guard the base +qboolean BotCTFGuardDuty(bot_state_t *bs) +{ + if (g_gametype.integer != GT_CTF && + g_gametype.integer != GT_CTY) + { + return qfalse; + } + + if (bs->ctfState == CTFSTATE_DEFENDER) + { + return qtrue; + } + + return qfalse; +} + +//when we reach the waypoint we are travelling to, +//this function will be called. We will perform any +//checks for flags on the current wp and activate +//any "touch" events based on that. +void WPTouchRoutine(bot_state_t *bs) +{ + int lastNum; + + if (!bs->wpCurrent) + { + return; + } + + bs->wpTravelTime = level.time + 10000; + + if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC) + { //don't try to use any nearby map objects for a little while + bs->noUseTime = level.time + 4000; + } + +#ifdef FORCEJUMP_INSTANTMETHOD + if ((bs->wpCurrent->flags & WPFLAG_JUMP) && bs->wpCurrent->forceJumpTo) + { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is + //handled elsewhere. + bs->jumpTime = level.time + 100; + } +#else + if ((bs->wpCurrent->flags & WPFLAG_JUMP) && !bs->wpCurrent->forceJumpTo) + { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is + //handled elsewhere. + bs->jumpTime = level.time + 100; + } +#endif + + if (bs->isCamper && bot_camp.integer && (BotIsAChickenWuss(bs) || BotCTFGuardDuty(bs) || bs->isCamper == 2) && ((bs->wpCurrent->flags & WPFLAG_SNIPEORCAMP) || (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND)) && + bs->cur_ps.weapon != WP_SABER && bs->cur_ps.weapon != WP_MELEE && bs->cur_ps.weapon != WP_STUN_BATON) + { //if we're a camper and a chicken then camp + if (bs->wpDirection) + { + lastNum = bs->wpCurrent->index+1; + } + else + { + lastNum = bs->wpCurrent->index-1; + } + + if (gWPArray[lastNum] && gWPArray[lastNum]->inuse && gWPArray[lastNum]->index && bs->isCamping < level.time) + { + bs->isCamping = level.time + rand()%15000 + 30000; + bs->wpCamping = bs->wpCurrent; + bs->wpCampingTo = gWPArray[lastNum]; + + if (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND) + { + bs->campStanding = qtrue; + } + else + { + bs->campStanding = qfalse; + } + } + + } + else if ((bs->cur_ps.weapon == WP_SABER || bs->cur_ps.weapon == WP_STUN_BATON || bs->cur_ps.weapon == WP_MELEE) && + bs->isCamping > level.time) + { //don't snipe/camp with a melee weapon, that would be silly + bs->isCamping = 0; + bs->wpCampingTo = NULL; + bs->wpCamping = NULL; + } + + if (bs->wpDestination) + { + if (bs->wpCurrent->index == bs->wpDestination->index) + { + bs->wpDestination = NULL; + + if (bs->runningLikeASissy) + { //this obviously means we're scared and running, so we'll want to keep our navigational priorities less delayed + bs->destinationGrabTime = level.time + 500; + } + else + { + bs->destinationGrabTime = level.time + 3500; + } + } + else + { + CheckForShorterRoutes(bs, bs->wpCurrent->index); + } + } +} + +//could also slowly lerp toward, but for now +//just copying straight over. +void MoveTowardIdealAngles(bot_state_t *bs) +{ + VectorCopy(bs->goalAngles, bs->ideal_viewangles); +} + +#define BOT_STRAFE_AVOIDANCE + +#ifdef BOT_STRAFE_AVOIDANCE +#define STRAFEAROUND_RIGHT 1 +#define STRAFEAROUND_LEFT 2 + +//do some trace checks for strafing to get an idea of where we +//are and if we should move to avoid obstacles. +int BotTrace_Strafe(bot_state_t *bs, vec3_t traceto) +{ + vec3_t playerMins = {-15, -15, /*DEFAULT_MINS_2*/-8}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + vec3_t from, to; + vec3_t dirAng, dirDif; + vec3_t forward, right; + trace_t tr; + + if (bs->cur_ps.groundEntityNum == ENTITYNUM_NONE) + { //don't do this in the air, it can be.. dangerous. + return 0; + } + + VectorSubtract(traceto, bs->origin, dirAng); + VectorNormalize(dirAng); + vectoangles(dirAng, dirAng); + + if (AngleDifference(bs->viewangles[YAW], dirAng[YAW]) > 60 || + AngleDifference(bs->viewangles[YAW], dirAng[YAW]) < -60) + { //If we aren't facing the direction we're going here, then we've got enough excuse to be too stupid to strafe around anyway + return 0; + } + + VectorCopy(bs->origin, from); + VectorCopy(traceto, to); + + VectorSubtract(to, from, dirDif); + VectorNormalize(dirDif); + vectoangles(dirDif, dirDif); + + AngleVectors(dirDif, forward, 0, 0); + + to[0] = from[0] + forward[0]*32; + to[1] = from[1] + forward[1]*32; + to[2] = from[2] + forward[2]*32; + + trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return 0; + } + + AngleVectors(dirAng, 0, right, 0); + + from[0] += right[0]*32; + from[1] += right[1]*32; + from[2] += right[2]*16; + + to[0] += right[0]*32; + to[1] += right[1]*32; + to[2] += right[2]*32; + + trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return STRAFEAROUND_RIGHT; + } + + from[0] -= right[0]*64; + from[1] -= right[1]*64; + from[2] -= right[2]*64; + + to[0] -= right[0]*64; + to[1] -= right[1]*64; + to[2] -= right[2]*64; + + trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return STRAFEAROUND_LEFT; + } + + return 0; +} +#endif + +//Similar to the trace check, but we want to trace to see +//if there's anything we can jump over. +int BotTrace_Jump(bot_state_t *bs, vec3_t traceto) +{ + vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod; + trace_t tr; + int orTr; + + VectorSubtract(traceto, bs->origin, a); + vectoangles(a, a); + + AngleVectors(a, fwd, NULL, NULL); + + traceto_mod[0] = bs->origin[0] + fwd[0]*4; + traceto_mod[1] = bs->origin[1] + fwd[1]*4; + traceto_mod[2] = bs->origin[2] + fwd[2]*4; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -18; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + trap_Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return 0; + } + + orTr = tr.entityNum; + + VectorCopy(bs->origin, tracefrom_mod); + + tracefrom_mod[2] += 41; + traceto_mod[2] += 41; + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 8; + + trap_Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + if (orTr >= 0 && orTr < MAX_CLIENTS && botstates[orTr] && botstates[orTr]->jumpTime > level.time) + { + return 0; //so bots don't try to jump over each other at the same time + } + + if (bs->currentEnemy && bs->currentEnemy->s.number == orTr && (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER || BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE)) + { + return 0; + } + + return 1; + } + + return 0; +} + +//And yet another check to duck under any obstacles. +int BotTrace_Duck(bot_state_t *bs, vec3_t traceto) +{ + vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod; + trace_t tr; + + VectorSubtract(traceto, bs->origin, a); + vectoangles(a, a); + + AngleVectors(a, fwd, NULL, NULL); + + traceto_mod[0] = bs->origin[0] + fwd[0]*4; + traceto_mod[1] = bs->origin[1] + fwd[1]*4; + traceto_mod[2] = bs->origin[2] + fwd[2]*4; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -23; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 8; + + trap_Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction != 1) + { + return 0; + } + + VectorCopy(bs->origin, tracefrom_mod); + + tracefrom_mod[2] += 31;//33; + traceto_mod[2] += 31;//33; + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + trap_Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction != 1) + { + return 1; + } + + return 0; +} + +//check of the potential enemy is a valid one +int PassStandardEnemyChecks(bot_state_t *bs, gentity_t *en) +{ + if (!bs || !en) + { //shouldn't happen + return 0; + } + + if (!en->client) + { //not a client, don't care about him + return 0; + } + + if (en->health < 1) + { //he's already dead + return 0; + } + + if (!en->takedamage) + { //a client that can't take damage? + return 0; + } + + if (bs->doingFallback && + (gLevelFlags & LEVELFLAG_IGNOREINFALLBACK)) + { //we screwed up in our nav routines somewhere and we've reverted to a fallback state to + //try to get back on the trail. If the level specifies to ignore enemies in this state, + //then ignore them. + return 0; + } + + if (en->client->ps.pm_type == PM_INTERMISSION || + en->client->ps.pm_type == PM_SPECTATOR || + en->client->sess.sessionTeam == TEAM_SPECTATOR) + { //don't attack spectators + return 0; + } + + if (!en->client->pers.connected) + { //a "zombie" client? + return 0; + } + + if (!en->s.solid) + { //shouldn't happen + return 0; + } + + if (bs->client == en->s.number) + { //don't attack yourself + return 0; + } + + if (OnSameTeam(&g_entities[bs->client], en)) + { //don't attack teammates + return 0; + } + + if (BotMindTricked(bs->client, en->s.number)) + { + if (bs->currentEnemy && bs->currentEnemy->s.number == en->s.number) + { //if mindtricked by this enemy, then be less "aware" of them, even though + //we know they're there. + vec3_t vs; + float vLen = 0; + + VectorSubtract(bs->origin, en->client->ps.origin, vs); + vLen = VectorLength(vs); + + if (vLen > 64 /*&& (level.time - en->client->dangerTime) > 150*/) + { + return 0; + } + } + } + + if (en->client->ps.duelInProgress && en->client->ps.duelIndex != bs->client) + { //don't attack duelists unless you're dueling them + return 0; + } + + if (bs->cur_ps.duelInProgress && en->s.number != bs->cur_ps.duelIndex) + { //ditto, the other way around + return 0; + } +/* + if (g_gametype.integer == GT_JEDIMASTER && !en->client->ps.isJediMaster && !bs->cur_ps.isJediMaster) + { //rules for attacking non-JM in JM mode + vec3_t vs; + float vLen = 0; + + if (!g_friendlyFire.integer) + { //can't harm non-JM in JM mode if FF is off + return 0; + } + + VectorSubtract(bs->origin, en->client->ps.origin, vs); + vLen = VectorLength(vs); + + if (vLen > 350) + { + return 0; + } + } +*/ + return 1; +} + +//Notifies the bot that he has taken damage from "attacker". +void BotDamageNotification(gclient_t *bot, gentity_t *attacker) +{ + bot_state_t *bs; + bot_state_t *bs_a; + int i; + + if (!bot || !attacker || !attacker->client) + { + return; + } + + if (bot->ps.clientNum >= MAX_CLIENTS) + { //an NPC.. do nothing for them. + return; + } + + if (attacker->s.number >= MAX_CLIENTS) + { //if attacker is an npc also don't care I suppose. + return; + } + + bs_a = botstates[attacker->s.number]; + + if (bs_a) + { //if the client attacking us is a bot as well + bs_a->lastAttacked = &g_entities[bot->ps.clientNum]; + i = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && + i != bs_a->client && + botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum]) + { + botstates[i]->lastAttacked = NULL; + } + + i++; + } + } + else //got attacked by a real client, so no one gets rights to lastAttacked + { + i = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && + botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum]) + { + botstates[i]->lastAttacked = NULL; + } + + i++; + } + } + + bs = botstates[bot->ps.clientNum]; + + if (!bs) + { + return; + } + + bs->lastHurt = attacker; + + if (bs->currentEnemy) + { //we don't care about the guy attacking us if we have an enemy already + return; + } + + if (!PassStandardEnemyChecks(bs, attacker)) + { //the person that hurt us is not a valid enemy + return; + } + + if (PassLovedOneCheck(bs, attacker)) + { //the person that hurt us is the one we love! + bs->currentEnemy = attacker; + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } +} + +//perform cheap "hearing" checks based on the event catching +//system +int BotCanHear(bot_state_t *bs, gentity_t *en, float endist) +{ + float minlen; + + if (!en || !en->client) + { + return 0; + } + + if (en && en->client && en->client->ps.otherSoundTime > level.time) + { //they made a noise in recent time + minlen = en->client->ps.otherSoundLen; + goto checkStep; + } + + if (en && en->client && en->client->ps.footstepTime > level.time) + { //they made a footstep + minlen = 256; + goto checkStep; + } + + if (gBotEventTracker[en->s.number].eventTime < level.time) + { //no recent events to check + return 0; + } + + switch(gBotEventTracker[en->s.number].events[gBotEventTracker[en->s.number].eventSequence & (MAX_PS_EVENTS-1)]) + { //did the last event contain a sound? + case EV_GLOBAL_SOUND: + minlen = 256; + break; + case EV_FIRE_WEAPON: + case EV_ALT_FIRE: + case EV_SABER_ATTACK: + minlen = 512; + break; + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: + case EV_FOOTSTEP: + case EV_FOOTSTEP_METAL: + case EV_FOOTWADE: + minlen = 256; + break; + case EV_JUMP: + case EV_ROLL: + minlen = 256; + break; + default: + minlen = 999999; + break; + } +checkStep: + if (BotMindTricked(bs->client, en->s.number)) + { //if mindtricked by this person, cut down on the minlen so they can't "hear" as well + minlen /= 4; + } + + if (endist <= minlen) + { //we heard it + return 1; + } + + return 0; +} + +//check for new events +void UpdateEventTracker(void) +{ + int i; + + i = 0; + + while (i < MAX_CLIENTS) + { + if (gBotEventTracker[i].eventSequence != level.clients[i].ps.eventSequence) + { //updated event + gBotEventTracker[i].eventSequence = level.clients[i].ps.eventSequence; + gBotEventTracker[i].events[0] = level.clients[i].ps.events[0]; + gBotEventTracker[i].events[1] = level.clients[i].ps.events[1]; + gBotEventTracker[i].eventTime = level.time + 0.5; + } + + i++; + } +} + +//check if said angles are within our fov +int InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) +{ + int i; + float diff, angle; + + for (i = 0; i < 2; i++) + { + angle = AngleMod(viewangles[i]); + angles[i] = AngleMod(angles[i]); + diff = angles[i] - angle; + if (angles[i] > angle) + { + if (diff > 180.0) + { + diff -= 360.0; + } + } + else + { + if (diff < -180.0) + { + diff += 360.0; + } + } + if (diff > 0) + { + if (diff > fov * 0.5) + { + return 0; + } + } + else + { + if (diff < -fov * 0.5) + { + return 0; + } + } + } + return 1; +} + +//We cannot hurt the ones we love. Unless of course this +//function says we can. +int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent) +{ + int i; + bot_state_t *loved; + + if (!bs->lovednum) + { + return 1; + } + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { //There is no love in 1-on-1 + return 1; + } + + i = 0; + + if (!botstates[ent->s.number]) + { //not a bot + return 1; + } + + if (!bot_attachments.integer) + { + return 1; + } + + loved = botstates[ent->s.number]; + + while (i < bs->lovednum) + { + if (strcmp(level.clients[loved->client].pers.netname, bs->loved[i].name) == 0) + { + if (!IsTeamplay() && bs->loved[i].level < 2) + { //if FFA and level of love is not greater than 1, just don't care + return 1; + } + else if (IsTeamplay() && !OnSameTeam(&g_entities[bs->client], &g_entities[loved->client]) && bs->loved[i].level < 2) + { //is teamplay, but not on same team and level < 2 + return 1; + } + else + { + return 0; + } + } + + i++; + } + + return 1; +} + +qboolean G_ThereIsAMaster(void); + +//standard check to find a new enemy. +int ScanForEnemies(bot_state_t *bs) +{ + vec3_t a; + float distcheck; + float closest; + int bestindex; + int i; + float hasEnemyDist = 0; + qboolean noAttackNonJM = qfalse; + + closest = 999999; + i = 0; + bestindex = -1; + + if (bs->currentEnemy) + { //only switch to a new enemy if he's significantly closer + hasEnemyDist = bs->frame_Enemy_Len; + } +/* + if (bs->currentEnemy && bs->currentEnemy->client && + bs->currentEnemy->client->ps.isJediMaster) + { //The Jedi Master must die. + return -1; + } + + if (g_gametype.integer == GT_JEDIMASTER) + { + if (G_ThereIsAMaster() && !bs->cur_ps.isJediMaster) + { //if friendly fire is on in jedi master we can attack people that bug us + if (!g_friendlyFire.integer) + { + noAttackNonJM = qtrue; + } + else + { + closest = 128; //only get mad at people if they get close enough to you to anger you, or hurt you + } + } + } +*/ + while (i <= MAX_CLIENTS) + { + if (i != bs->client && g_entities[i].client && !OnSameTeam(&g_entities[bs->client], &g_entities[i]) && PassStandardEnemyChecks(bs, &g_entities[i]) && BotPVSCheck(g_entities[i].client->ps.origin, bs->eye) && PassLovedOneCheck(bs, &g_entities[i])) + { + VectorSubtract(g_entities[i].client->ps.origin, bs->eye, a); + distcheck = VectorLength(a); + vectoangles(a, a); +/* + if (g_entities[i].client->ps.isJediMaster) + { //make us think the Jedi Master is close so we'll attack him above all + distcheck = 1; + } +*/ + if (distcheck < closest && ((InFieldOfVision(bs->viewangles, 90, a) && !BotMindTricked(bs->client, i)) || BotCanHear(bs, &g_entities[i], distcheck)) && OrgVisible(bs->eye, g_entities[i].client->ps.origin, -1)) + { + if (BotMindTricked(bs->client, i)) + { + if (distcheck < 256 || (level.time - g_entities[i].client->dangerTime) < 100) + { + if (!hasEnemyDist || distcheck < (hasEnemyDist - 128)) + { //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out + if (!noAttackNonJM ) //|| g_entities[i].client->ps.isJediMaster) + { + closest = distcheck; + bestindex = i; + } + } + } + } + else + { + if (!hasEnemyDist || distcheck < (hasEnemyDist - 128)) + { //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out + if (!noAttackNonJM ) //|| g_entities[i].client->ps.isJediMaster) + { + closest = distcheck; + bestindex = i; + } + } + } + } + } + i++; + } + + return bestindex; +} + +int WaitingForNow(bot_state_t *bs, vec3_t goalpos) +{ //checks if the bot is doing something along the lines of waiting for an elevator to raise up + vec3_t xybot, xywp, a; + + if (!bs->wpCurrent) + { + return 0; + } + + if ((int)goalpos[0] != (int)bs->wpCurrent->origin[0] || + (int)goalpos[1] != (int)bs->wpCurrent->origin[1] || + (int)goalpos[2] != (int)bs->wpCurrent->origin[2]) + { + return 0; + } + + VectorCopy(bs->origin, xybot); + VectorCopy(bs->wpCurrent->origin, xywp); + + xybot[2] = 0; + xywp[2] = 0; + + VectorSubtract(xybot, xywp, a); + + if (VectorLength(a) < 16 && bs->frame_Waypoint_Len > 100) + { + if (CheckForFunc(bs->origin, bs->client)) + { + return 1; //we're probably standing on an elevator and riding up/down. Or at least we hope so. + } + } + else if (VectorLength(a) < 64 && bs->frame_Waypoint_Len > 64 && + CheckForFunc(bs->origin, bs->client)) + { + bs->noUseTime = level.time + 2000; + } + + return 0; +} + +//get an ideal distance for us to be at in relation to our opponent +//based on our weapon. +int BotGetWeaponRange(bot_state_t *bs) +{ + switch (bs->cur_ps.weapon) + { + case WP_STUN_BATON: + case WP_MELEE: + return BWEAPONRANGE_MELEE; + case WP_SABER: + return BWEAPONRANGE_SABER; + case WP_BRYAR_PISTOL: + return BWEAPONRANGE_MID; + case WP_BLASTER: + return BWEAPONRANGE_MID; + case WP_DISRUPTOR: + return BWEAPONRANGE_MID; + case WP_BOWCASTER: + return BWEAPONRANGE_LONG; + case WP_REPEATER: + return BWEAPONRANGE_MID; + case WP_DEMP2: + return BWEAPONRANGE_LONG; + case WP_FLECHETTE: + return BWEAPONRANGE_LONG; + case WP_ROCKET_LAUNCHER: + return BWEAPONRANGE_LONG; + case WP_THERMAL: + return BWEAPONRANGE_LONG; + case WP_TRIP_MINE: + return BWEAPONRANGE_LONG; + case WP_DET_PACK: + return BWEAPONRANGE_LONG; + default: + return BWEAPONRANGE_MID; + } +} + +//see if we want to run away from the opponent for whatever reason +int BotIsAChickenWuss(bot_state_t *bs) +{ + int bWRange; + + if (gLevelFlags & LEVELFLAG_IMUSTNTRUNAWAY) + { //The level says we mustn't run away! + return 0; + } + + if (g_gametype.integer == GT_SINGLE_PLAYER) + { //"coop" (not really) + return 0; + } +/* + if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster) + { //Then you may know no fear. + //Well, unless he's strong. + if (bs->currentEnemy && bs->currentEnemy->client && + bs->currentEnemy->client->ps.isJediMaster && + bs->currentEnemy->health > 40 && + bs->cur_ps.weapon < WP_ROCKET_LAUNCHER) + { //explosive weapons are most effective against the Jedi Master + goto jmPass; + } + return 0; + } +*/ + if (g_gametype.integer == GT_CTF && bs->currentEnemy && bs->currentEnemy->client) + { + if (bs->currentEnemy->client->ps.powerups[PW_REDFLAG] || + bs->currentEnemy->client->ps.powerups[PW_BLUEFLAG]) + { //don't be afraid of flag carriers, they must die! + return 0; + } + } + +jmPass: + if (bs->chickenWussCalculationTime > level.time) + { + return 2; //don't want to keep going between two points... + } + + if (bs->cur_ps.fd.forcePowersActive & (1 << FP_RAGE)) + { //don't run while raging + return 0; + } +/* + if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster) + { //be frightened of the jedi master? I guess in this case. + return 1; + } +*/ + bs->chickenWussCalculationTime = level.time + MAX_CHICKENWUSS_TIME; + + if (g_entities[bs->client].health < BOT_RUN_HEALTH) + { //we're low on health, let's get away + return 1; + } + + bWRange = BotGetWeaponRange(bs); + + if (bWRange == BWEAPONRANGE_MELEE || bWRange == BWEAPONRANGE_SABER) + { + if (bWRange != BWEAPONRANGE_SABER || !bs->saberSpecialist) + { //run away if we're using melee, or if we're using a saber and not a "saber specialist" + return 1; + } + } + + if (bs->cur_ps.weapon == WP_BRYAR_PISTOL) + { //the bryar is a weak weapon, so just try to find a new one if it's what you're having to use + return 1; + } + + if (bs->currentEnemy && bs->currentEnemy->client && + bs->currentEnemy->client->ps.weapon == WP_SABER && + bs->frame_Enemy_Len < 512 && bs->cur_ps.weapon != WP_SABER) + { //if close to an enemy with a saber and not using a saber, then try to back off + return 1; + } + + if ((level.time-bs->cur_ps.electrifyTime) < 16000) + { //lightning is dangerous. + return 1; + } + + //didn't run, reset the timer + bs->chickenWussCalculationTime = 0; + + return 0; +} + +//look for "bad things". bad things include detpacks, thermal detonators, +//and other dangerous explodey items. +gentity_t *GetNearestBadThing(bot_state_t *bs) +{ + int i = 0; + float glen; + vec3_t hold; + int bestindex = 0; + float bestdist = 800; //if not within a radius of 800, it's no threat anyway + int foundindex = 0; + float factor = 0; + gentity_t *ent; + trace_t tr; + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + if ( (ent && + !ent->client && + ent->inuse && + ent->damage && + /*(ent->s.weapon == WP_THERMAL || ent->s.weapon == WP_FLECHETTE)*/ + ent->s.weapon && + ent->splashDamage) || + (ent && + ent->genericValue5 == 1000 && + ent->inuse && + ent->health > 0 && + ent->genericValue3 != bs->client && + g_entities[ent->genericValue3].client && !OnSameTeam(&g_entities[bs->client], &g_entities[ent->genericValue3])) ) + { //try to escape from anything with a non-0 s.weapon and non-0 damage. This hopefully only means dangerous projectiles. + //Or a sentry gun if bolt_Head == 1000. This is a terrible hack, yes. + VectorSubtract(bs->origin, ent->r.currentOrigin, hold); + glen = VectorLength(hold); + + if (ent->s.weapon != WP_THERMAL && ent->s.weapon != WP_FLECHETTE && + ent->s.weapon != WP_DET_PACK && ent->s.weapon != WP_TRIP_MINE) + { + factor = 0.5; + + if (ent->s.weapon && glen <= 256 && bs->settings.skill > 2) + { //it's a projectile so push it away + bs->doForcePush = level.time + 700; + //G_Printf("PUSH PROJECTILE\n"); + } + } + else + { + factor = 1; + } + + if (ent->s.weapon == WP_ROCKET_LAUNCHER && + (ent->r.ownerNum == bs->client || + (ent->r.ownerNum > 0 && ent->r.ownerNum < MAX_CLIENTS && + g_entities[ent->r.ownerNum].client && OnSameTeam(&g_entities[bs->client], &g_entities[ent->r.ownerNum]))) ) + { //don't be afraid of your own rockets or your teammates' rockets + factor = 0; + } + + if (glen < bestdist*factor && BotPVSCheck(bs->origin, ent->s.pos.trBase)) + { + trap_Trace(&tr, bs->origin, NULL, NULL, ent->s.pos.trBase, bs->client, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == ent->s.number) + { + bestindex = i; + bestdist = glen; + foundindex = 1; + } + } + } + + if (ent && !ent->client && ent->inuse && ent->damage && ent->s.weapon && ent->r.ownerNum < MAX_CLIENTS && ent->r.ownerNum >= 0) + { //if we're in danger of a projectile belonging to someone and don't have an enemy, set the enemy to them + gentity_t *projOwner = &g_entities[ent->r.ownerNum]; + + if (projOwner && projOwner->inuse && projOwner->client) + { + if (!bs->currentEnemy) + { + if (PassStandardEnemyChecks(bs, projOwner)) + { + if (PassLovedOneCheck(bs, projOwner)) + { + VectorSubtract(bs->origin, ent->r.currentOrigin, hold); + glen = VectorLength(hold); + + if (glen < 512) + { + bs->currentEnemy = projOwner; + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } + } + } + } + } + } + + i++; + } + + if (foundindex) + { + bs->dontGoBack = level.time + 1500; + return &g_entities[bestindex]; + } + else + { + return NULL; + } +} + +//Keep our CTF priorities on defending our team's flag +int BotDefendFlag(bot_state_t *bs) +{ + wpobject_t *flagPoint; + vec3_t a; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + flagPoint = flagRed; + } + else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE) + { + flagPoint = flagBlue; + } + else + { + return 0; + } + + if (!flagPoint) + { + return 0; + } + + VectorSubtract(bs->origin, flagPoint->origin, a); + + if (VectorLength(a) > BASE_GUARD_DISTANCE) + { + bs->wpDestination = flagPoint; + } + + return 1; +} + +//Keep our CTF priorities on getting the other team's flag +int BotGetEnemyFlag(bot_state_t *bs) +{ + wpobject_t *flagPoint; + vec3_t a; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + flagPoint = flagBlue; + } + else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE) + { + flagPoint = flagRed; + } + else + { + return 0; + } + + if (!flagPoint) + { + return 0; + } + + VectorSubtract(bs->origin, flagPoint->origin, a); + + if (VectorLength(a) > BASE_GETENEMYFLAG_DISTANCE) + { + bs->wpDestination = flagPoint; + } + + return 1; +} + +//Our team's flag is gone, so try to get it back +int BotGetFlagBack(bot_state_t *bs) +{ + int i = 0; + int myFlag = 0; + int foundCarrier = 0; + int tempInt = 0; + gentity_t *ent = NULL; + vec3_t usethisvec; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + myFlag = PW_REDFLAG; + } + else + { + myFlag = PW_BLUEFLAG; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent)) + { + foundCarrier = 1; + break; + } + + i++; + } + + if (!foundCarrier) + { + return 0; + } + + if (!ent) + { + return 0; + } + + if (bs->wpDestSwitchTime < level.time) + { + if (ent->client) + { + VectorCopy(ent->client->ps.origin, usethisvec); + } + else + { + VectorCopy(ent->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000); + } + } + + return 1; +} + +//Someone else on our team has the enemy flag, so try to get +//to their assistance +int BotGuardFlagCarrier(bot_state_t *bs) +{ + int i = 0; + int enemyFlag = 0; + int foundCarrier = 0; + int tempInt = 0; + gentity_t *ent = NULL; + vec3_t usethisvec; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + enemyFlag = PW_BLUEFLAG; + } + else + { + enemyFlag = PW_REDFLAG; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent)) + { + foundCarrier = 1; + break; + } + + i++; + } + + if (!foundCarrier) + { + return 0; + } + + if (!ent) + { + return 0; + } + + if (bs->wpDestSwitchTime < level.time) + { + if (ent->client) + { + VectorCopy(ent->client->ps.origin, usethisvec); + } + else + { + VectorCopy(ent->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000); + } + } + + return 1; +} + +//We have the flag, let's get it home. +int BotGetFlagHome(bot_state_t *bs) +{ + wpobject_t *flagPoint; + vec3_t a; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + flagPoint = flagRed; + } + else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE) + { + flagPoint = flagBlue; + } + else + { + return 0; + } + + if (!flagPoint) + { + return 0; + } + + VectorSubtract(bs->origin, flagPoint->origin, a); + + if (VectorLength(a) > BASE_FLAGWAIT_DISTANCE) + { + bs->wpDestination = flagPoint; + } + + return 1; +} + +void GetNewFlagPoint(wpobject_t *wp, gentity_t *flagEnt, int team) +{ //get the nearest possible waypoint to the flag since it's not in its original position + int i = 0; + vec3_t a, mins, maxs; + float bestdist; + float testdist; + int bestindex = 0; + int foundindex = 0; + trace_t tr; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -5; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 5; + + VectorSubtract(wp->origin, flagEnt->s.pos.trBase, a); + + bestdist = VectorLength(a); + + if (bestdist <= WP_KEEP_FLAG_DIST) + { + trap_Trace(&tr, wp->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID); + + if (tr.fraction == 1) + { //this point is good + return; + } + } + + while (i < gWPNum) + { + VectorSubtract(gWPArray[i]->origin, flagEnt->s.pos.trBase, a); + testdist = VectorLength(a); + + if (testdist < bestdist) + { + trap_Trace(&tr, gWPArray[i]->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID); + + if (tr.fraction == 1) + { + foundindex = 1; + bestindex = i; + bestdist = testdist; + } + } + + i++; + } + + if (foundindex) + { + if (team == TEAM_RED) + { + flagRed = gWPArray[bestindex]; + } + else + { + flagBlue = gWPArray[bestindex]; + } + } +} + +//See if our CTF state should take priority in our nav routines +int CTFTakesPriority(bot_state_t *bs) +{ + gentity_t *ent = NULL; + int enemyFlag = 0; + int myFlag = 0; + int enemyHasOurFlag = 0; + int weHaveEnemyFlag = 0; + int numOnMyTeam = 0; + int numOnEnemyTeam = 0; + int numAttackers = 0; + int numDefenders = 0; + int i = 0; + int idleWP; + int dosw = 0; + wpobject_t *dest_sw = NULL; +#ifdef BOT_CTF_DEBUG + vec3_t t; + + G_Printf("CTFSTATE: %s\n", ctfStateNames[bs->ctfState]); +#endif + + if (g_gametype.integer != GT_CTF && g_gametype.integer != GT_CTY) + { + return 0; + } + + if (bs->cur_ps.weapon == WP_BRYAR_PISTOL && + (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_GATHER_TIME) + { //get the nearest weapon laying around base before heading off for battle + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + if (bs->wpDestSwitchTime < level.time) + { + bs->wpDestination = gWPArray[idleWP]; + } + return 1; + } + } + else if (bs->cur_ps.weapon == WP_BRYAR_PISTOL && + (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_CHASE_CTF && + bs->wpDestination && bs->wpDestination->weight) + { + dest_sw = bs->wpDestination; + dosw = 1; + } + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + myFlag = PW_REDFLAG; + } + else + { + myFlag = PW_BLUEFLAG; + } + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + enemyFlag = PW_BLUEFLAG; + } + else + { + enemyFlag = PW_REDFLAG; + } + + if (!flagRed || !flagBlue || + !flagRed->inuse || !flagBlue->inuse || + !eFlagRed || !eFlagBlue) + { + return 0; + } + +#ifdef BOT_CTF_DEBUG + VectorCopy(flagRed->origin, t); + t[2] += 128; + G_TestLine(flagRed->origin, t, 0x0000ff, 500); + + VectorCopy(flagBlue->origin, t); + t[2] += 128; + G_TestLine(flagBlue->origin, t, 0x0000ff, 500); +#endif + + if (droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM)) + { + GetNewFlagPoint(flagRed, droppedRedFlag, TEAM_RED); + } + else + { + flagRed = oFlagRed; + } + + if (droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM)) + { + GetNewFlagPoint(flagBlue, droppedBlueFlag, TEAM_BLUE); + } + else + { + flagBlue = oFlagBlue; + } + + if (!bs->ctfState) + { + return 0; + } + + i = 0; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client) + { + if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent)) + { + weHaveEnemyFlag = 1; + } + else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent)) + { + enemyHasOurFlag = 1; + } + + if (OnSameTeam(&g_entities[bs->client], ent)) + { + numOnMyTeam++; + } + else + { + numOnEnemyTeam++; + } + + if (botstates[ent->s.number]) + { + if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER || + botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL) + { + numAttackers++; + } + else + { + numDefenders++; + } + } + else + { //assume real players to be attackers in our logic + numAttackers++; + } + } + i++; + } + + if (bs->cur_ps.powerups[enemyFlag]) + { + if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag) + { + bs->ctfState = CTFSTATE_RETRIEVAL; + } + else + { + bs->ctfState = CTFSTATE_GETFLAGHOME; + } + } + else if (bs->ctfState == CTFSTATE_GETFLAGHOME) + { + bs->ctfState = 0; + } + + if (bs->state_Forced) + { + bs->ctfState = bs->state_Forced; + } + + if (bs->ctfState == CTFSTATE_DEFENDER) + { + if (BotDefendFlag(bs)) + { + goto success; + } + } + + if (bs->ctfState == CTFSTATE_ATTACKER) + { + if (BotGetEnemyFlag(bs)) + { + goto success; + } + } + + if (bs->ctfState == CTFSTATE_RETRIEVAL) + { + if (BotGetFlagBack(bs)) + { + goto success; + } + else + { //can't find anyone on another team being a carrier, so ignore this priority + bs->ctfState = 0; + } + } + + if (bs->ctfState == CTFSTATE_GUARDCARRIER) + { + if (BotGuardFlagCarrier(bs)) + { + goto success; + } + else + { //can't find anyone on our team being a carrier, so ignore this priority + bs->ctfState = 0; + } + } + + if (bs->ctfState == CTFSTATE_GETFLAGHOME) + { + if (BotGetFlagHome(bs)) + { + goto success; + } + } + + return 0; + +success: + if (dosw) + { //allow ctf code to run, but if after a particular item then keep going after it + bs->wpDestination = dest_sw; + } + + return 1; +} + +int EntityVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore, int ignore2) +{ + trace_t tr; + + trap_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + else if (tr.entityNum != ENTITYNUM_NONE && tr.entityNum == ignore2) + { + return 1; + } + + return 0; +} + +//Get the closest objective for siege and go after it +int Siege_TargetClosestObjective(bot_state_t *bs, int flag) +{ + int i = 0; + int bestindex = -1; + float testdistance = 0; + float bestdistance = 999999999; + gentity_t *goalent; + vec3_t a, dif; + vec3_t mins, maxs; + + mins[0] = -1; + mins[1] = -1; + mins[2] = -1; + + maxs[0] = 1; + maxs[1] = 1; + maxs[2] = 1; + + if ( bs->wpDestination && (bs->wpDestination->flags & flag) && bs->wpDestination->associated_entity != ENTITYNUM_NONE && + &g_entities[bs->wpDestination->associated_entity] && g_entities[bs->wpDestination->associated_entity].use ) + { + goto hasPoint; + } + + while (i < gWPNum) + { + if ( gWPArray[i] && gWPArray[i]->inuse && (gWPArray[i]->flags & flag) && gWPArray[i]->associated_entity != ENTITYNUM_NONE && + &g_entities[gWPArray[i]->associated_entity] && g_entities[gWPArray[i]->associated_entity].use ) + { + VectorSubtract(gWPArray[i]->origin, bs->origin, a); + testdistance = VectorLength(a); + + if (testdistance < bestdistance) + { + bestdistance = testdistance; + bestindex = i; + } + } + + i++; + } + + if (bestindex != -1) + { + bs->wpDestination = gWPArray[bestindex]; + } + else + { + return 0; + } +hasPoint: + goalent = &g_entities[bs->wpDestination->associated_entity]; + + if (!goalent) + { + return 0; + } + + VectorSubtract(bs->origin, bs->wpDestination->origin, a); + + testdistance = VectorLength(a); + + dif[0] = (goalent->r.absmax[0]+goalent->r.absmin[0])/2; + dif[1] = (goalent->r.absmax[1]+goalent->r.absmin[1])/2; + dif[2] = (goalent->r.absmax[2]+goalent->r.absmin[2])/2; + //brush models can have tricky origins, so this is our hacky method of getting the center point + + if (goalent->takedamage && testdistance < BOT_MIN_SIEGE_GOAL_SHOOT && + EntityVisibleBox(bs->origin, mins, maxs, dif, bs->client, goalent->s.number)) + { + bs->shootGoal = goalent; + bs->touchGoal = NULL; + } + else if (goalent->use && testdistance < BOT_MIN_SIEGE_GOAL_TRAVEL) + { + bs->shootGoal = NULL; + bs->touchGoal = goalent; + } + else + { //don't know how to handle this goal object! + bs->shootGoal = NULL; + bs->touchGoal = NULL; + } + + if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE || + BotGetWeaponRange(bs) == BWEAPONRANGE_SABER) + { + bs->shootGoal = NULL; //too risky + } + + if (bs->touchGoal) + { + //G_Printf("Please, master, let me touch it!\n"); + VectorCopy(dif, bs->goalPosition); + } + + return 1; +} + +void Siege_DefendFromAttackers(bot_state_t *bs) +{ //this may be a little cheap, but the best way to find our defending point is probably + //to just find the nearest person on the opposing team since they'll most likely + //be on offense in this situation + int wpClose = -1; + int i = 0; + float testdist = 999999; + int bestindex = -1; + float bestdist = 999999; + gentity_t *ent; + vec3_t a; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->sess.sessionTeam != g_entities[bs->client].client->sess.sessionTeam && + ent->health > 0 && ent->client->sess.sessionTeam != TEAM_SPECTATOR) + { + VectorSubtract(ent->client->ps.origin, bs->origin, a); + + testdist = VectorLength(a); + + if (testdist < bestdist) + { + bestindex = i; + bestdist = testdist; + } + } + + i++; + } + + if (bestindex == -1) + { + return; + } + + wpClose = GetNearestVisibleWP(g_entities[bestindex].client->ps.origin, -1); + + if (wpClose != -1 && gWPArray[wpClose] && gWPArray[wpClose]->inuse) + { + bs->wpDestination = gWPArray[wpClose]; + bs->destinationGrabTime = level.time + 10000; + } +} + +//how many defenders on our team? +int Siege_CountDefenders(bot_state_t *bs) +{ + int i = 0; + int num = 0; + gentity_t *ent; + bot_state_t *bot; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + bot = botstates[i]; + + if (ent && ent->client && bot) + { + if (bot->siegeState == SIEGESTATE_DEFENDER && + ent->client->sess.sessionTeam == g_entities[bs->client].client->sess.sessionTeam) + { + num++; + } + } + + i++; + } + + return num; +} + +//how many other players on our team? +int Siege_CountTeammates(bot_state_t *bs) +{ + int i = 0; + int num = 0; + gentity_t *ent; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client) + { + if (ent->client->sess.sessionTeam == g_entities[bs->client].client->sess.sessionTeam) + { + num++; + } + } + + i++; + } + + return num; +} + +//see if siege objective completion should take priority in our +//nav routines. +int SiegeTakesPriority(bot_state_t *bs) +{ + int attacker; + int flagForDefendableObjective; + int flagForAttackableObjective; + int defenders, teammates; + int idleWP; + wpobject_t *dest_sw = NULL; + int dosw = 0; + gclient_t *bcl; + vec3_t dif; + trace_t tr; + + if (g_gametype.integer != GT_SIEGE) + { + return 0; + } + + bcl = g_entities[bs->client].client; + + if (!bcl) + { + return 0; + } + + if (bs->cur_ps.weapon == WP_BRYAR_PISTOL && + (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_GATHER_TIME) + { //get the nearest weapon laying around base before heading off for battle + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + if (bs->wpDestSwitchTime < level.time) + { + bs->wpDestination = gWPArray[idleWP]; + } + return 1; + } + } + else if (bs->cur_ps.weapon == WP_BRYAR_PISTOL && + (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_CHASE_TIME && + bs->wpDestination && bs->wpDestination->weight) + { + dest_sw = bs->wpDestination; + dosw = 1; + } + + if (bcl->sess.sessionTeam == SIEGETEAM_TEAM1) + { + attacker = imperial_attackers; + flagForDefendableObjective = WPFLAG_SIEGE_REBELOBJ; + flagForAttackableObjective = WPFLAG_SIEGE_IMPERIALOBJ; + } + else + { + attacker = rebel_attackers; + flagForDefendableObjective = WPFLAG_SIEGE_IMPERIALOBJ; + flagForAttackableObjective = WPFLAG_SIEGE_REBELOBJ; + } + + if (attacker) + { + bs->siegeState = SIEGESTATE_ATTACKER; + } + else + { + bs->siegeState = SIEGESTATE_DEFENDER; + defenders = Siege_CountDefenders(bs); + teammates = Siege_CountTeammates(bs); + + if (defenders > teammates/3 && teammates > 1) + { //devote around 1/4 of our team to completing our own side goals even if we're a defender. + //If we have no side goals we will realize that later on and join the defenders + bs->siegeState = SIEGESTATE_ATTACKER; + } + } + + if (bs->state_Forced) + { + bs->siegeState = bs->state_Forced; + } + + if (bs->siegeState == SIEGESTATE_ATTACKER) + { + if (!Siege_TargetClosestObjective(bs, flagForAttackableObjective)) + { //looks like we have no goals other than to keep the other team from completing objectives + Siege_DefendFromAttackers(bs); + if (bs->shootGoal) + { + dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2; + dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2; + dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2; + + if (!BotPVSCheck(bs->origin, dif)) + { + bs->shootGoal = NULL; + } + else + { + trap_Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID); + + if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number) + { + bs->shootGoal = NULL; + } + } + } + } + } + else if (bs->siegeState == SIEGESTATE_DEFENDER) + { + Siege_DefendFromAttackers(bs); + if (bs->shootGoal) + { + dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2; + dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2; + dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2; + + if (!BotPVSCheck(bs->origin, dif)) + { + bs->shootGoal = NULL; + } + else + { + trap_Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID); + + if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number) + { + bs->shootGoal = NULL; + } + } + } + } + else + { //get busy! + Siege_TargetClosestObjective(bs, flagForAttackableObjective); + if (bs->shootGoal) + { + dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2; + dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2; + dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2; + + if (!BotPVSCheck(bs->origin, dif)) + { + bs->shootGoal = NULL; + } + else + { + trap_Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID); + + if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number) + { + bs->shootGoal = NULL; + } + } + } + } + + if (dosw) + { //allow siege objective code to run, but if after a particular item then keep going after it + bs->wpDestination = dest_sw; + } + + return 1; +} + +//see if jedi master priorities should take priority in our nav +//routines. +int JMTakesPriority(bot_state_t *bs) +{ + int i = 0; + int wpClose = -1; + gentity_t *theImportantEntity = NULL; + + if (g_gametype.integer != GT_JEDIMASTER) + { + return 0; + } +/* + if (bs->cur_ps.isJediMaster) + { + return 0; + } +*/ + //jmState becomes the index for the one who carries the saber. If jmState is -1 then the saber is currently + //without an owner + bs->jmState = -1; + + while (i < MAX_CLIENTS) + { +/* + if (g_entities[i].client && g_entities[i].inuse && + g_entities[i].client->ps.isJediMaster) + { + bs->jmState = i; + break; + } +*/ + i++; + } + + if (bs->jmState != -1) + { + theImportantEntity = &g_entities[bs->jmState]; + } + else + { + theImportantEntity = gJMSaberEnt; + } + + if (theImportantEntity && theImportantEntity->inuse && bs->destinationGrabTime < level.time) + { + if (theImportantEntity->client) + { + wpClose = GetNearestVisibleWP(theImportantEntity->client->ps.origin, theImportantEntity->s.number); + } + else + { + wpClose = GetNearestVisibleWP(theImportantEntity->r.currentOrigin, theImportantEntity->s.number); + } + + if (wpClose != -1 && gWPArray[wpClose] && gWPArray[wpClose]->inuse) + { + /* + Com_Printf("BOT GRABBED IDEAL JM LOCATION\n"); + if (bs->wpDestination != gWPArray[wpClose]) + { + Com_Printf("IDEAL WAS NOT ALREADY IDEAL\n"); + + if (!bs->wpDestination) + { + Com_Printf("IDEAL WAS NULL\n"); + } + } + */ + bs->wpDestination = gWPArray[wpClose]; + bs->destinationGrabTime = level.time + 4000; + } + } + + return 1; +} + +//see if we already have an item/powerup/etc. that is associated +//with this waypoint. +int BotHasAssociated(bot_state_t *bs, wpobject_t *wp) +{ + gentity_t *as; + + if (wp->associated_entity == ENTITYNUM_NONE) + { //make it think this is an item we have so we don't go after nothing + return 1; + } + + as = &g_entities[wp->associated_entity]; + + if (!as || !as->item) + { + return 0; + } + + if (as->item->giType == IT_WEAPON) + { + if (bs->cur_ps.stats[STAT_WEAPONS] & (1 << as->item->giTag)) + { + return 1; + } + + return 0; + } + else if (as->item->giType == IT_HOLDABLE) + { + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << as->item->giTag)) + { + return 1; + } + + return 0; + } + else if (as->item->giType == IT_POWERUP) + { + if (bs->cur_ps.powerups[as->item->giTag]) + { + return 1; + } + + return 0; + } + else if (as->item->giType == IT_AMMO) + { + if (bs->cur_ps.ammo[as->item->giTag] > 10) //hack + { + return 1; + } + + return 0; + } + + return 0; +} + +//we don't really have anything we want to do right now, +//let's just find the best thing to do given the current +//situation. +int GetBestIdleGoal(bot_state_t *bs) +{ + int i = 0; + int highestweight = 0; + int desiredindex = -1; + int dist_to_weight = 0; + int traildist; + + if (!bs->wpCurrent) + { + return -1; + } + + if (bs->isCamper != 2) + { + if (bs->randomNavTime < level.time) + { + if (Q_irand(1, 10) < 5) + { + bs->randomNav = 1; + } + else + { + bs->randomNav = 0; + } + + bs->randomNavTime = level.time + Q_irand(5000, 15000); + } + } + + if (bs->randomNav) + { //stop looking for items and/or camping on them + return -1; + } + + while (i < gWPNum) + { + if (gWPArray[i] && + gWPArray[i]->inuse && + (gWPArray[i]->flags & WPFLAG_GOALPOINT) && + gWPArray[i]->weight > highestweight && + !BotHasAssociated(bs, gWPArray[i])) + { + traildist = TotalTrailDistance(bs->wpCurrent->index, i, bs); + + if (traildist != -1) + { + dist_to_weight = (int)traildist/10000; + dist_to_weight = (gWPArray[i]->weight)-dist_to_weight; + + if (dist_to_weight > highestweight) + { + highestweight = dist_to_weight; + desiredindex = i; + } + } + } + + i++; + } + + return desiredindex; +} + +//go through the list of possible priorities for navigating +//and work out the best destination point. +void GetIdealDestination(bot_state_t *bs) +{ + int tempInt, cWPIndex, bChicken, idleWP; + float distChange, plusLen, minusLen; + vec3_t usethisvec, a; + gentity_t *badthing; + +#ifdef _DEBUG + trap_Cvar_Update(&bot_nogoals); + + if (bot_nogoals.integer) + { + return; + } +#endif + + if (!bs->wpCurrent) + { + return; + } + + if ((level.time - bs->escapeDirTime) > 4000) + { + badthing = GetNearestBadThing(bs); + } + else + { + badthing = NULL; + } + + if (badthing && badthing->inuse && + badthing->health > 0 && badthing->takedamage) + { + bs->dangerousObject = badthing; + } + else + { + bs->dangerousObject = NULL; + } + + if (!badthing && bs->wpDestIgnoreTime > level.time) + { + return; + } + + if (!badthing && bs->dontGoBack > level.time) + { + if (bs->wpDestination) + { + bs->wpStoreDest = bs->wpDestination; + } + bs->wpDestination = NULL; + return; + } + else if (!badthing && bs->wpStoreDest) + { //after we finish running away, switch back to our original destination + bs->wpDestination = bs->wpStoreDest; + bs->wpStoreDest = NULL; + } + + if (badthing && bs->wpCamping) + { + bs->wpCamping = NULL; + } + + if (bs->wpCamping) + { + bs->wpDestination = bs->wpCamping; + return; + } + + if (!badthing && CTFTakesPriority(bs)) + { + if (bs->ctfState) + { + bs->runningToEscapeThreat = 1; + } + return; + } + else if (!badthing && SiegeTakesPriority(bs)) + { + if (bs->siegeState) + { + bs->runningToEscapeThreat = 1; + } + return; + } + else if (!badthing && JMTakesPriority(bs)) + { + bs->runningToEscapeThreat = 1; + } + + if (badthing) + { + bs->runningLikeASissy = level.time + 100; + + if (bs->wpDestination) + { + bs->wpStoreDest = bs->wpDestination; + } + bs->wpDestination = NULL; + + if (bs->wpDirection) + { + tempInt = bs->wpCurrent->index+1; + } + else + { + tempInt = bs->wpCurrent->index-1; + } + + if (gWPArray[tempInt] && gWPArray[tempInt]->inuse && bs->escapeDirTime < level.time) + { + VectorSubtract(badthing->s.pos.trBase, bs->wpCurrent->origin, a); + plusLen = VectorLength(a); + VectorSubtract(badthing->s.pos.trBase, gWPArray[tempInt]->origin, a); + minusLen = VectorLength(a); + + if (plusLen < minusLen) + { + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + + bs->wpCurrent = gWPArray[tempInt]; + + bs->escapeDirTime = level.time + Q_irand(500, 1000);//Q_irand(1000, 1400); + + //G_Printf("Escaping from scary bad thing [%s]\n", badthing->classname); + } + } + //G_Printf("Run away run away run away!\n"); + return; + } + + distChange = 0; //keep the compiler from complaining + + tempInt = BotGetWeaponRange(bs); + + if (tempInt == BWEAPONRANGE_MELEE) + { + distChange = 1; + } + else if (tempInt == BWEAPONRANGE_SABER) + { + distChange = 1; + } + else if (tempInt == BWEAPONRANGE_MID) + { + distChange = 128; + } + else if (tempInt == BWEAPONRANGE_LONG) + { + distChange = 300; + } + + if (bs->revengeEnemy && bs->revengeEnemy->health > 0 && + bs->revengeEnemy->client && (bs->revengeEnemy->client->pers.connected == CA_ACTIVE || bs->revengeEnemy->client->pers.connected == CA_AUTHORIZING)) + { //if we hate someone, always try to get to them + if (bs->wpDestSwitchTime < level.time) + { + if (bs->revengeEnemy->client) + { + VectorCopy(bs->revengeEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->revengeEnemy->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000); + } + } + } + else if (bs->squadLeader && bs->squadLeader->health > 0 && + bs->squadLeader->client && (bs->squadLeader->client->pers.connected == CA_ACTIVE || bs->squadLeader->client->pers.connected == CA_AUTHORIZING)) + { + if (bs->wpDestSwitchTime < level.time) + { + if (bs->squadLeader->client) + { + VectorCopy(bs->squadLeader->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->squadLeader->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000); + } + } + } + else if (bs->currentEnemy) + { + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, usethisvec); + } + + bChicken = BotIsAChickenWuss(bs); + bs->runningToEscapeThreat = bChicken; + + if (bs->frame_Enemy_Len < distChange || (bChicken && bChicken != 2)) + { + cWPIndex = bs->wpCurrent->index; + + if (bs->frame_Enemy_Len > 400) + { //good distance away, start running toward a good place for an item or powerup or whatever + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + bs->wpDestination = gWPArray[idleWP]; + } + } + else if (gWPArray[cWPIndex-1] && gWPArray[cWPIndex-1]->inuse && + gWPArray[cWPIndex+1] && gWPArray[cWPIndex+1]->inuse) + { + VectorSubtract(gWPArray[cWPIndex+1]->origin, usethisvec, a); + plusLen = VectorLength(a); + VectorSubtract(gWPArray[cWPIndex-1]->origin, usethisvec, a); + minusLen = VectorLength(a); + + if (minusLen > plusLen) + { + bs->wpDestination = gWPArray[cWPIndex-1]; + } + else + { + bs->wpDestination = gWPArray[cWPIndex+1]; + } + } + } + else if (bChicken != 2 && bs->wpDestSwitchTime < level.time) + { + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + + if (g_gametype.integer == GT_SINGLE_PLAYER) + { //be more aggressive + bs->wpDestSwitchTime = level.time + Q_irand(300, 1000); + } + else + { + bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000); + } + } + } + } + + if (!bs->wpDestination && bs->wpDestSwitchTime < level.time) + { + //G_Printf("I need something to do\n"); + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + bs->wpDestination = gWPArray[idleWP]; + } + } +} + +//commander CTF AI - tell other bots in the so-called +//"squad" what to do. +void CommanderBotCTFAI(bot_state_t *bs) +{ + int i = 0; + gentity_t *ent; + int squadmates = 0; + gentity_t *squad[MAX_CLIENTS]; + int defendAttackPriority = 0; //0 == attack, 1 == defend + int guardDefendPriority = 0; //0 == defend, 1 == guard + int attackRetrievePriority = 0; //0 == retrieve, 1 == attack + int myFlag = 0; + int enemyFlag = 0; + int enemyHasOurFlag = 0; + int weHaveEnemyFlag = 0; + int numOnMyTeam = 0; + int numOnEnemyTeam = 0; + int numAttackers = 0; + int numDefenders = 0; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + myFlag = PW_REDFLAG; + } + else + { + myFlag = PW_BLUEFLAG; + } + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + enemyFlag = PW_BLUEFLAG; + } + else + { + enemyFlag = PW_REDFLAG; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client) + { + if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent)) + { + weHaveEnemyFlag = 1; + } + else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent)) + { + enemyHasOurFlag = 1; + } + + if (OnSameTeam(&g_entities[bs->client], ent)) + { + numOnMyTeam++; + } + else + { + numOnEnemyTeam++; + } + + if (botstates[ent->s.number]) + { + if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER || + botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL) + { + numAttackers++; + } + else + { + numDefenders++; + } + } + else + { //assume real players to be attackers in our logic + numAttackers++; + } + } + i++; + } + + i = 0; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && botstates[i] && botstates[i]->squadLeader && botstates[i]->squadLeader->s.number == bs->client && i != bs->client) + { + squad[squadmates] = ent; + squadmates++; + } + + i++; + } + + squad[squadmates] = &g_entities[bs->client]; + squadmates++; + + i = 0; + + if (enemyHasOurFlag && !weHaveEnemyFlag) + { //start off with an attacker instead of a retriever if we don't have the enemy flag yet so that they can't capture it first. + //after that we focus on getting our flag back. + attackRetrievePriority = 1; + } + + while (i < squadmates) + { + if (squad[i] && squad[i]->client && botstates[squad[i]->s.number]) + { + if (botstates[squad[i]->s.number]->ctfState != CTFSTATE_GETFLAGHOME) + { //never tell a bot to stop trying to bring the flag to the base + if (defendAttackPriority) + { + if (weHaveEnemyFlag) + { + if (guardDefendPriority) + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_GUARDCARRIER; + guardDefendPriority = 0; + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER; + guardDefendPriority = 1; + } + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER; + } + defendAttackPriority = 0; + } + else + { + if (enemyHasOurFlag) + { + if (attackRetrievePriority) + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER; + attackRetrievePriority = 0; + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL; + attackRetrievePriority = 1; + } + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER; + } + defendAttackPriority = 1; + } + } + else if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag) + { //I'm the only one on my team who will attack and the enemy has my flag, I have to go after him + botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL; + } + } + + i++; + } +} + +//similar to ctf ai, for siege +void CommanderBotSiegeAI(bot_state_t *bs) +{ + int i = 0; + int squadmates = 0; + int commanded = 0; + int teammates = 0; + gentity_t *squad[MAX_CLIENTS]; + gentity_t *ent; + bot_state_t *bst; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent) && botstates[ent->s.number]) + { + bst = botstates[ent->s.number]; + + if (bst && !bst->isSquadLeader && !bst->state_Forced) + { + squad[squadmates] = ent; + squadmates++; + } + else if (bst && !bst->isSquadLeader && bst->state_Forced) + { //count them as commanded + commanded++; + } + } + + if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent)) + { + teammates++; + } + + i++; + } + + if (!squadmates) + { + return; + } + + //tell squad mates to do what I'm doing, up to half of team, let the other half make their own decisions + i = 0; + + while (i < squadmates && squad[i]) + { + bst = botstates[squad[i]->s.number]; + + if (commanded > teammates/2) + { + break; + } + + if (bst) + { + bst->state_Forced = bs->siegeState; + bst->siegeState = bs->siegeState; + commanded++; + } + + i++; + } +} + +//teamplay ffa squad ai +void BotDoTeamplayAI(bot_state_t *bs) +{ + if (bs->state_Forced) + { + bs->teamplayState = bs->state_Forced; + } + + if (bs->teamplayState == TEAMPLAYSTATE_REGROUP) + { //force to find a new leader + bs->squadLeader = NULL; + bs->isSquadLeader = 0; + } +} + +//like ctf and siege commander ai, instruct the squad +void CommanderBotTeamplayAI(bot_state_t *bs) +{ + int i = 0; + int squadmates = 0; + int teammates = 0; + int teammate_indanger = -1; + int teammate_helped = 0; + int foundsquadleader = 0; + int worsthealth = 50; + gentity_t *squad[MAX_CLIENTS]; + gentity_t *ent; + bot_state_t *bst; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent) && botstates[ent->s.number]) + { + bst = botstates[ent->s.number]; + + if (foundsquadleader && bst && bst->isSquadLeader) + { //never more than one squad leader + bst->isSquadLeader = 0; + } + + if (bst && !bst->isSquadLeader) + { + squad[squadmates] = ent; + squadmates++; + } + else if (bst) + { + foundsquadleader = 1; + } + } + + if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent)) + { + teammates++; + + if (ent->health < worsthealth) + { + teammate_indanger = ent->s.number; + worsthealth = ent->health; + } + } + + i++; + } + + if (!squadmates) + { + return; + } + + i = 0; + + while (i < squadmates && squad[i]) + { + bst = botstates[squad[i]->s.number]; + + if (bst && !bst->state_Forced) + { //only order if this guy is not being ordered directly by the real player team leader + if (teammate_indanger >= 0 && !teammate_helped) + { //send someone out to help whoever needs help most at the moment + bst->teamplayState = TEAMPLAYSTATE_ASSISTING; + bst->squadLeader = &g_entities[teammate_indanger]; + teammate_helped = 1; + } + else if ((teammate_indanger == -1 || teammate_helped) && bst->teamplayState == TEAMPLAYSTATE_ASSISTING) + { //no teammates need help badly, but this guy is trying to help them anyway, so stop + bst->teamplayState = TEAMPLAYSTATE_FOLLOWING; + bst->squadLeader = &g_entities[bs->client]; + } + + if (bs->squadRegroupInterval < level.time && Q_irand(1, 10) < 5) + { //every so often tell the squad to regroup for the sake of variation + if (bst->teamplayState == TEAMPLAYSTATE_FOLLOWING) + { + bst->teamplayState = TEAMPLAYSTATE_REGROUP; + } + + bs->isSquadLeader = 0; + bs->squadCannotLead = level.time + 500; + bs->squadRegroupInterval = level.time + Q_irand(45000, 65000); + } + } + + i++; + } +} + +//pick which commander ai to use based on gametype +void CommanderBotAI(bot_state_t *bs) +{ + if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) + { + CommanderBotCTFAI(bs); + } + else if (g_gametype.integer == GT_SIEGE) + { + CommanderBotSiegeAI(bs); + } + else if (g_gametype.integer == GT_TEAM) + { + CommanderBotTeamplayAI(bs); + } +} + +//close range combat routines +void MeleeCombatHandling(bot_state_t *bs) +{ + vec3_t usethisvec; + vec3_t downvec; + vec3_t midorg; + vec3_t a; + vec3_t fwd; + vec3_t mins, maxs; + trace_t tr; + int en_down; + int me_down; + int mid_down; + + if (!bs->currentEnemy) + { + return; + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, usethisvec); + } + + if (bs->meleeStrafeTime < level.time) + { + if (bs->meleeStrafeDir) + { + bs->meleeStrafeDir = 0; + } + else + { + bs->meleeStrafeDir = 1; + } + + bs->meleeStrafeTime = level.time + Q_irand(500, 1800); + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = -24; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + VectorCopy(usethisvec, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID); + + en_down = (int)tr.endpos[2]; + + VectorCopy(bs->origin, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID); + + me_down = (int)tr.endpos[2]; + + VectorSubtract(usethisvec, bs->origin, a); + vectoangles(a, a); + AngleVectors(a, fwd, NULL, NULL); + + midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2; + midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2; + midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2; + + VectorCopy(midorg, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID); + + mid_down = (int)tr.endpos[2]; + + if (me_down == en_down && + en_down == mid_down) + { + VectorCopy(usethisvec, bs->goalPosition); + } +} + +//saber combat routines (it's simple, but it works) +void SaberCombatHandling(bot_state_t *bs) +{ + vec3_t usethisvec; + vec3_t downvec; + vec3_t midorg; + vec3_t a; + vec3_t fwd; + vec3_t mins, maxs; + trace_t tr; + int en_down; + int me_down; + int mid_down; + + if (!bs->currentEnemy) + { + return; + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, usethisvec); + } + + if (bs->meleeStrafeTime < level.time) + { + if (bs->meleeStrafeDir) + { + bs->meleeStrafeDir = 0; + } + else + { + bs->meleeStrafeDir = 1; + } + + bs->meleeStrafeTime = level.time + Q_irand(500, 1800); + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = -24; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + VectorCopy(usethisvec, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID); + + en_down = (int)tr.endpos[2]; + + if (tr.startsolid || tr.allsolid) + { + en_down = 1; + me_down = 2; + } + else + { + VectorCopy(bs->origin, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID); + + me_down = (int)tr.endpos[2]; + + if (tr.startsolid || tr.allsolid) + { + en_down = 1; + me_down = 2; + } + } + + VectorSubtract(usethisvec, bs->origin, a); + vectoangles(a, a); + AngleVectors(a, fwd, NULL, NULL); + + midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2; + midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2; + midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2; + + VectorCopy(midorg, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID); + + mid_down = (int)tr.endpos[2]; + + if (me_down == en_down && + en_down == mid_down) + { + if (usethisvec[2] > (bs->origin[2]+32) && + bs->currentEnemy->client && + bs->currentEnemy->client->ps.groundEntityNum == ENTITYNUM_NONE) + { + bs->jumpTime = level.time + 100; + } + + if (bs->frame_Enemy_Len > 128) + { //be ready to attack + bs->saberDefending = 0; + bs->saberDefendDecideTime = level.time + Q_irand(1000, 2000); + } + else + { + if (bs->saberDefendDecideTime < level.time) + { + if (bs->saberDefending) + { + bs->saberDefending = 0; + } + else + { + bs->saberDefending = 1; + } + + bs->saberDefendDecideTime = level.time + Q_irand(500, 2000); + } + } + + if (bs->frame_Enemy_Len < 54) + { + VectorCopy(bs->origin, bs->goalPosition); + bs->saberBFTime = 0; + } + else + { + VectorCopy(usethisvec, bs->goalPosition); + } + + if (bs->currentEnemy && bs->currentEnemy->client) + { + if (!BG_SaberInSpecial(bs->currentEnemy->client->ps.saberMove) && bs->frame_Enemy_Len > 90 && bs->saberBFTime > level.time && bs->saberBTime > level.time && bs->beStill < level.time && bs->saberSTime < level.time) + { + bs->beStill = level.time + Q_irand(500, 1000); + bs->saberSTime = level.time + Q_irand(1200, 1800); + } + else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len < 80 && (Q_irand(1, 10) < 8 && bs->saberBFTime < level.time) || bs->saberBTime > level.time || BG_SaberInKata(bs->currentEnemy->client->ps.saberMove) || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK_DUAL) + { + vec3_t vs; + vec3_t groundcheck; + int idealDist; + int checkIncr = 0; + + VectorSubtract(bs->origin, usethisvec, vs); + VectorNormalize(vs); + + if (BG_SaberInKata(bs->currentEnemy->client->ps.saberMove) || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK_DUAL) + { + idealDist = 256; + } + else + { + idealDist = 64; + } + + while (checkIncr < idealDist) + { + bs->goalPosition[0] = bs->origin[0] + vs[0]*checkIncr; + bs->goalPosition[1] = bs->origin[1] + vs[1]*checkIncr; + bs->goalPosition[2] = bs->origin[2] + vs[2]*checkIncr; + + if (bs->saberBTime < level.time) + { + bs->saberBFTime = level.time + Q_irand(900, 1300); + bs->saberBTime = level.time + Q_irand(300, 700); + } + + VectorCopy(bs->goalPosition, groundcheck); + + groundcheck[2] -= 64; + + trap_Trace(&tr, bs->goalPosition, NULL, NULL, groundcheck, bs->client, MASK_SOLID); + + if (tr.fraction == 1.0f) + { //don't back off of a ledge + VectorCopy(usethisvec, bs->goalPosition); + break; + } + checkIncr += 64; + } + } + else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len >= 75) + { + bs->saberBFTime = level.time + Q_irand(700, 1300); + bs->saberBTime = 0; + } + } + + /*AngleVectors(bs->viewangles, NULL, fwd, NULL); + + if (bs->meleeStrafeDir) + { + bs->goalPosition[0] += fwd[0]*16; + bs->goalPosition[1] += fwd[1]*16; + bs->goalPosition[2] += fwd[2]*16; + } + else + { + bs->goalPosition[0] -= fwd[0]*16; + bs->goalPosition[1] -= fwd[1]*16; + bs->goalPosition[2] -= fwd[2]*16; + }*/ + } + else if (bs->frame_Enemy_Len <= 56) + { + bs->doAttack = 1; + bs->saberDefending = 0; + } +} + +//should we be "leading" our aim with this weapon? And if +//so, by how much? +float BotWeaponCanLead(bot_state_t *bs) +{ + int weap = bs->cur_ps.weapon; + + if (weap == WP_BRYAR_PISTOL) + { + return 0.5; + } + if (weap == WP_BLASTER) + { + return 0.35; + } + if (weap == WP_BOWCASTER) + { + return 0.5; + } + if (weap == WP_REPEATER) + { + return 0.45; + } + if (weap == WP_THERMAL) + { + return 0.5; + } + if (weap == WP_DEMP2) + { + return 0.35; + } + if (weap == WP_ROCKET_LAUNCHER) + { + return 0.7; + } + + return 0; +} + +//offset the desired view angles with aim leading in mind +void BotAimLeading(bot_state_t *bs, vec3_t headlevel, float leadAmount) +{ + int x; + vec3_t predictedSpot; + vec3_t movementVector; + vec3_t a, ang; + float vtotal; + + if (!bs->currentEnemy || + !bs->currentEnemy->client) + { + return; + } + + if (!bs->frame_Enemy_Len) + { + return; + } + + vtotal = 0; + + if (bs->currentEnemy->client->ps.velocity[0] < 0) + { + vtotal += -bs->currentEnemy->client->ps.velocity[0]; + } + else + { + vtotal += bs->currentEnemy->client->ps.velocity[0]; + } + + if (bs->currentEnemy->client->ps.velocity[1] < 0) + { + vtotal += -bs->currentEnemy->client->ps.velocity[1]; + } + else + { + vtotal += bs->currentEnemy->client->ps.velocity[1]; + } + + if (bs->currentEnemy->client->ps.velocity[2] < 0) + { + vtotal += -bs->currentEnemy->client->ps.velocity[2]; + } + else + { + vtotal += bs->currentEnemy->client->ps.velocity[2]; + } + + //G_Printf("Leadin target with a velocity total of %f\n", vtotal); + + VectorCopy(bs->currentEnemy->client->ps.velocity, movementVector); + + VectorNormalize(movementVector); + + x = bs->frame_Enemy_Len*leadAmount; //hardly calculated with an exact science, but it works + + if (vtotal > 400) + { + vtotal = 400; + } + + if (vtotal) + { + x = (bs->frame_Enemy_Len*0.9)*leadAmount*(vtotal*0.0012); //hardly calculated with an exact science, but it works + } + else + { + x = (bs->frame_Enemy_Len*0.9)*leadAmount; //hardly calculated with an exact science, but it works + } + + predictedSpot[0] = headlevel[0] + (movementVector[0]*x); + predictedSpot[1] = headlevel[1] + (movementVector[1]*x); + predictedSpot[2] = headlevel[2] + (movementVector[2]*x); + + VectorSubtract(predictedSpot, bs->eye, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); +} + +//wobble our aim around based on our sk1llz +void BotAimOffsetGoalAngles(bot_state_t *bs) +{ + int i; + float accVal; + i = 0; + + if (bs->skills.perfectaim) + { + return; + } + + if (bs->aimOffsetTime > level.time) + { + if (bs->aimOffsetAmtYaw) + { + bs->goalAngles[YAW] += bs->aimOffsetAmtYaw; + } + + if (bs->aimOffsetAmtPitch) + { + bs->goalAngles[PITCH] += bs->aimOffsetAmtPitch; + } + + while (i <= 2) + { + if (bs->goalAngles[i] > 360) + { + bs->goalAngles[i] -= 360; + } + + if (bs->goalAngles[i] < 0) + { + bs->goalAngles[i] += 360; + } + + i++; + } + return; + } + + accVal = bs->skills.accuracy/bs->settings.skill; + + if (bs->currentEnemy && BotMindTricked(bs->client, bs->currentEnemy->s.number)) + { //having to judge where they are by hearing them, so we should be quite inaccurate here + accVal *= 7; + + if (accVal < 30) + { + accVal = 30; + } + } + + if (bs->revengeEnemy && bs->revengeHateLevel && + bs->currentEnemy == bs->revengeEnemy) + { //bot becomes more skilled as anger level raises + accVal = accVal/bs->revengeHateLevel; + } + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { //assume our goal is aiming at the enemy, seeing as he's visible and all + if (!bs->currentEnemy->s.pos.trDelta[0] && + !bs->currentEnemy->s.pos.trDelta[1] && + !bs->currentEnemy->s.pos.trDelta[2]) + { + accVal = 0; //he's not even moving, so he shouldn't really be hard to hit. + } + else + { + accVal += accVal*0.25; //if he's moving he's this much harder to hit + } + + if (g_entities[bs->client].s.pos.trDelta[0] || + g_entities[bs->client].s.pos.trDelta[1] || + g_entities[bs->client].s.pos.trDelta[2]) + { + accVal += accVal*0.15; //make it somewhat harder to aim if we're moving also + } + } + + if (accVal > 90) + { + accVal = 90; + } + if (accVal < 1) + { + accVal = 0; + } + + if (!accVal) + { + bs->aimOffsetAmtYaw = 0; + bs->aimOffsetAmtPitch = 0; + return; + } + + if (rand()%10 <= 5) + { + bs->aimOffsetAmtYaw = rand()%(int)accVal; + } + else + { + bs->aimOffsetAmtYaw = -(rand()%(int)accVal); + } + + if (rand()%10 <= 5) + { + bs->aimOffsetAmtPitch = rand()%(int)accVal; + } + else + { + bs->aimOffsetAmtPitch = -(rand()%(int)accVal); + } + + bs->aimOffsetTime = level.time + rand()%500 + 200; +} + +//do we want to alt fire with this weapon? +int ShouldSecondaryFire(bot_state_t *bs) +{ + int weap; + int dif; + float rTime; + + weap = bs->cur_ps.weapon; + + if (bs->cur_ps.ammo[weaponData[weap].ammoIndex] < weaponData[weap].altEnergyPerShot) + { + return 0; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && bs->cur_ps.weapon == WP_ROCKET_LAUNCHER) + { + float heldTime = (level.time - bs->cur_ps.weaponChargeTime); + + rTime = bs->cur_ps.rocketLockTime; + + if (rTime < 1) + { + rTime = bs->cur_ps.rocketLastValidTime; + } + + if (heldTime > 5000) + { //just give up and release it if we can't manage a lock in 5 seconds + return 2; + } + + if (rTime > 0) + { + dif = ( level.time - rTime ) / ( 1200.0f / 16.0f ); + + if (dif >= 10) + { + return 2; + } + else if (bs->frame_Enemy_Len > 250) + { + return 1; + } + } + else if (bs->frame_Enemy_Len > 250) + { + return 1; + } + } + else if ((bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) && (level.time - bs->cur_ps.weaponChargeTime) > bs->altChargeTime) + { + return 2; + } + else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) + { + return 1; + } + + if (weap == WP_BRYAR_PISTOL && bs->frame_Enemy_Len < 300) + { + return 1; + } + else if (weap == WP_BOWCASTER && bs->frame_Enemy_Len > 300) + { + return 1; + } + else if (weap == WP_REPEATER && bs->frame_Enemy_Len < 600 && bs->frame_Enemy_Len > 250) + { + return 1; + } + else if (weap == WP_BLASTER && bs->frame_Enemy_Len < 300) + { + return 1; + } + else if (weap == WP_ROCKET_LAUNCHER && bs->frame_Enemy_Len > 250) + { + return 1; + } + + return 0; +} + +//standard weapon combat routines +int CombatBotAI(bot_state_t *bs, float thinktime) +{ + vec3_t eorg, a; + int secFire; + float fovcheck; + + if (!bs->currentEnemy) + { + return 0; + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, eorg); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, eorg); + } + + VectorSubtract(eorg, bs->eye, a); + vectoangles(a, a); + + if (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER) + { + if (bs->frame_Enemy_Len <= SABER_ATTACK_RANGE) + { + bs->doAttack = 1; + } + } + else if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE) + { + if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE) + { + bs->doAttack = 1; + } + } + else + { + if (bs->cur_ps.weapon == WP_THERMAL || bs->cur_ps.weapon == WP_ROCKET_LAUNCHER) + { //be careful with the hurty weapons + fovcheck = 40; + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && + bs->cur_ps.weapon == WP_ROCKET_LAUNCHER) + { //if we're charging the weapon up then we can hold fire down within a normal fov + fovcheck = 60; + } + } + else + { + fovcheck = 60; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING || + bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) + { + fovcheck = 160; + } + + if (bs->frame_Enemy_Len < 128) + { + fovcheck *= 2; + } + + if (InFieldOfVision(bs->viewangles, fovcheck, a)) + { + if (bs->cur_ps.weapon == WP_THERMAL) + { + if (((level.time - bs->cur_ps.weaponChargeTime) < (bs->frame_Enemy_Len*2) && + (level.time - bs->cur_ps.weaponChargeTime) < 4000 && + bs->frame_Enemy_Len > 64) || + (bs->cur_ps.weaponstate != WEAPON_CHARGING && + bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT)) + { + if (bs->cur_ps.weaponstate != WEAPON_CHARGING && bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT) + { + if (bs->frame_Enemy_Len > 512 && bs->frame_Enemy_Len < 800) + { + bs->doAltAttack = 1; + //bs->doAttack = 1; + } + else + { + bs->doAttack = 1; + //bs->doAltAttack = 1; + } + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING) + { + bs->doAttack = 1; + } + else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) + { + bs->doAltAttack = 1; + } + } + } + else + { + secFire = ShouldSecondaryFire(bs); + + if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT && + bs->cur_ps.weaponstate != WEAPON_CHARGING) + { + bs->altChargeTime = Q_irand(500, 1000); + } + + if (secFire == 1) + { + bs->doAltAttack = 1; + } + else if (!secFire) + { + if (bs->cur_ps.weapon != WP_THERMAL) + { + if (bs->cur_ps.weaponstate != WEAPON_CHARGING || + bs->altChargeTime > (level.time - bs->cur_ps.weaponChargeTime)) + { + bs->doAttack = 1; + } + } + else + { + bs->doAttack = 1; + } + } + + if (secFire == 2) + { //released a charge + return 1; + } + } + } + } + + return 0; +} + +//we messed up and got off the normal path, let's fall +//back to jumping around and turning in random +//directions off walls to see if we can get back to a +//good place. +int BotFallbackNavigation(bot_state_t *bs) +{ + vec3_t b_angle, fwd, trto, mins, maxs; + trace_t tr; + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + return 2; //we're busy + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + bs->goalAngles[PITCH] = 0; + bs->goalAngles[ROLL] = 0; + + VectorCopy(bs->goalAngles, b_angle); + + AngleVectors(b_angle, fwd, NULL, NULL); + + trto[0] = bs->origin[0] + fwd[0]*16; + trto[1] = bs->origin[1] + fwd[1]*16; + trto[2] = bs->origin[2] + fwd[2]*16; + + trap_Trace(&tr, bs->origin, mins, maxs, trto, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.fraction == 1) + { + VectorCopy(trto, bs->goalPosition); + return 1; //success! + } + else + { + bs->goalAngles[YAW] = rand()%360; + } + + return 0; +} + +int BotTryAnotherWeapon(bot_state_t *bs) +{ //out of ammo, resort to the first weapon we come across that has ammo + int i; + + i = 1; + + while (i < WP_NUM_WEAPONS) + { + if (bs->cur_ps.ammo[weaponData[i].ammoIndex] >= weaponData[i].energyPerShot && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i))) + { + bs->virtualWeapon = i; + BotSelectWeapon(bs->client, i); + //bs->cur_ps.weapon = i; + //level.clients[bs->client].ps.weapon = i; + return 1; + } + + i++; + } + + if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1) + { //should always have this.. shouldn't we? + bs->virtualWeapon = 1; + BotSelectWeapon(bs->client, 1); + //bs->cur_ps.weapon = 1; + //level.clients[bs->client].ps.weapon = 1; + return 1; + } + + return 0; +} + +//is this weapon available to us? +qboolean BotWeaponSelectable(bot_state_t *bs, int weapon) +{ + if (weapon == WP_NONE) + { + return qfalse; + } + + if (bs->cur_ps.ammo[weaponData[weapon].ammoIndex] >= weaponData[weapon].energyPerShot && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << weapon))) + { + return qtrue; + } + + return qfalse; +} + +//select the best weapon we can +int BotSelectIdealWeapon(bot_state_t *bs) +{ + int i; + int bestweight = -1; + int bestweapon = 0; + + i = 0; + + while (i < WP_NUM_WEAPONS) + { + if (bs->cur_ps.ammo[weaponData[i].ammoIndex] >= weaponData[i].energyPerShot && + bs->botWeaponWeights[i] > bestweight && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i))) + { + if (i == WP_THERMAL) + { //special case.. + if (bs->currentEnemy && bs->frame_Enemy_Len < 700) + { + bestweight = bs->botWeaponWeights[i]; + bestweapon = i; + } + } + else + { + bestweight = bs->botWeaponWeights[i]; + bestweapon = i; + } + } + + i++; + } + + if ( bs->currentEnemy && bs->frame_Enemy_Len < 300 && + (bestweapon == WP_BRYAR_PISTOL || bestweapon == WP_BLASTER || bestweapon == WP_BOWCASTER) && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) ) + { + bestweapon = WP_SABER; + bestweight = 1; + } + + if ( bs->currentEnemy && bs->frame_Enemy_Len > 300 && + bs->currentEnemy->client && bs->currentEnemy->client->ps.weapon != WP_SABER && + (bestweapon == WP_SABER) ) + { //if the enemy is far away, and we have our saber selected, see if we have any good distance weapons instead + if (BotWeaponSelectable(bs, WP_DISRUPTOR)) + { + bestweapon = WP_DISRUPTOR; + bestweight = 1; + } + else if (BotWeaponSelectable(bs, WP_ROCKET_LAUNCHER)) + { + bestweapon = WP_ROCKET_LAUNCHER; + bestweight = 1; + } + else if (BotWeaponSelectable(bs, WP_BOWCASTER)) + { + bestweapon = WP_BOWCASTER; + bestweight = 1; + } + else if (BotWeaponSelectable(bs, WP_BLASTER)) + { + bestweapon = WP_BLASTER; + bestweight = 1; + } + else if (BotWeaponSelectable(bs, WP_REPEATER)) + { + bestweapon = WP_REPEATER; + bestweight = 1; + } + else if (BotWeaponSelectable(bs, WP_DEMP2)) + { + bestweapon = WP_DEMP2; + bestweight = 1; + } + } + + //assert(bs->cur_ps.weapon > 0 && bestweapon > 0); + + if (bestweight != -1 && bs->cur_ps.weapon != bestweapon && bs->virtualWeapon != bestweapon) + { + bs->virtualWeapon = bestweapon; + BotSelectWeapon(bs->client, bestweapon); + //bs->cur_ps.weapon = bestweapon; + //level.clients[bs->client].ps.weapon = bestweapon; + return 1; + } + + //assert(bs->cur_ps.weapon > 0); + + return 0; +} + +//check/select the chosen weapon +int BotSelectChoiceWeapon(bot_state_t *bs, int weapon, int doselection) +{ //if !doselection then bot will only check if he has the specified weapon and return 1 (yes) or 0 (no) + int i; + int hasit = 0; + + i = 0; + + while (i < WP_NUM_WEAPONS) + { + if (bs->cur_ps.ammo[weaponData[i].ammoIndex] > weaponData[i].energyPerShot && + i == weapon && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i))) + { + hasit = 1; + break; + } + + i++; + } + + if (hasit && bs->cur_ps.weapon != weapon && doselection && bs->virtualWeapon != weapon) + { + bs->virtualWeapon = weapon; + BotSelectWeapon(bs->client, weapon); + //bs->cur_ps.weapon = weapon; + //level.clients[bs->client].ps.weapon = weapon; + return 2; + } + + if (hasit) + { + return 1; + } + + return 0; +} + +//override our standard weapon choice with a melee weapon +int BotSelectMelee(bot_state_t *bs) +{ + if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1) + { + bs->virtualWeapon = 1; + BotSelectWeapon(bs->client, 1); + //bs->cur_ps.weapon = 1; + //level.clients[bs->client].ps.weapon = 1; + return 1; + } + + return 0; +} + +//See if we our in love with the potential bot. +int GetLoveLevel(bot_state_t *bs, bot_state_t *love) +{ + int i = 0; + const char *lname = NULL; + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { //There is no love in 1-on-1 + return 0; + } + + if (!bs || !love || !g_entities[love->client].client) + { + return 0; + } + + if (!bs->lovednum) + { + return 0; + } + + if (!bot_attachments.integer) + { + return 1; + } + + lname = g_entities[love->client].client->pers.netname; + + if (!lname) + { + return 0; + } + + while (i < bs->lovednum) + { + if (strcmp(bs->loved[i].name, lname) == 0) + { + return bs->loved[i].level; + } + + i++; + } + + return 0; +} + +//Our loved one was killed. We must become infuriated! +void BotLovedOneDied(bot_state_t *bs, bot_state_t *loved, int lovelevel) +{ + if (!loved->lastHurt || !loved->lastHurt->client || + loved->lastHurt->s.number == loved->client) + { + return; + } + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { //There is no love in 1-on-1 + return; + } + + if (!IsTeamplay()) + { + if (lovelevel < 2) + { + return; + } + } + else if (OnSameTeam(&g_entities[bs->client], loved->lastHurt)) + { //don't hate teammates no matter what + return; + } + + if (loved->client == loved->lastHurt->s.number) + { + return; + } + + if (bs->client == loved->lastHurt->s.number) + { //oops! + return; + } + + if (!bot_attachments.integer) + { + return; + } + + if (!PassLovedOneCheck(bs, loved->lastHurt)) + { //a loved one killed a loved one.. you cannot hate them +/* + bs->chatObject = loved->lastHurt; + bs->chatAltObject = &g_entities[loved->client]; + BotDoChat(bs, "LovedOneKilledLovedOne", 0); +*/ + return; + } + + if (bs->revengeEnemy == loved->lastHurt) + { + if (bs->revengeHateLevel < bs->loved_death_thresh) + { + bs->revengeHateLevel++; + + if (bs->revengeHateLevel == bs->loved_death_thresh) + { + //broke into the highest anger level + //CHAT: Hatred section +/* + bs->chatObject = loved->lastHurt; + bs->chatAltObject = NULL; + BotDoChat(bs, "Hatred", 1); +*/ + } + } + } + else if (bs->revengeHateLevel < bs->loved_death_thresh-1) + { //only switch hatred if we don't hate the existing revenge-enemy too much + //CHAT: BelovedKilled section +/* + bs->chatObject = &g_entities[loved->client]; + bs->chatAltObject = loved->lastHurt; + BotDoChat(bs, "BelovedKilled", 0); +*/ + bs->revengeHateLevel = 0; + bs->revengeEnemy = loved->lastHurt; + } +} + +void BotDeathNotify(bot_state_t *bs) +{ //in case someone has an emotional attachment to us, we'll notify them + int i = 0; + int ltest = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && botstates[i]->lovednum) + { + ltest = 0; + while (ltest < botstates[i]->lovednum) + { + if (strcmp(level.clients[bs->client].pers.netname, botstates[i]->loved[ltest].name) == 0) + { + BotLovedOneDied(botstates[i], bs, botstates[i]->loved[ltest].level); + break; + } + + ltest++; + } + } + + i++; + } +} + +//perform strafe trace checks +void StrafeTracing(bot_state_t *bs) +{ + vec3_t mins, maxs; + vec3_t right, rorg, drorg; + trace_t tr; + + mins[0] = -15; + mins[1] = -15; + //mins[2] = -24; + mins[2] = -22; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + AngleVectors(bs->viewangles, NULL, right, NULL); + + if (bs->meleeStrafeDir) + { + rorg[0] = bs->origin[0] - right[0]*32; + rorg[1] = bs->origin[1] - right[1]*32; + rorg[2] = bs->origin[2] - right[2]*32; + } + else + { + rorg[0] = bs->origin[0] + right[0]*32; + rorg[1] = bs->origin[1] + right[1]*32; + rorg[2] = bs->origin[2] + right[2]*32; + } + + trap_Trace(&tr, bs->origin, mins, maxs, rorg, bs->client, MASK_SOLID); + + if (tr.fraction != 1) + { + bs->meleeStrafeDisable = level.time + Q_irand(500, 1500); + } + + VectorCopy(rorg, drorg); + + drorg[2] -= 32; + + trap_Trace(&tr, rorg, NULL, NULL, drorg, bs->client, MASK_SOLID); + + if (tr.fraction == 1) + { //this may be a dangerous ledge, so don't strafe over it just in case + bs->meleeStrafeDisable = level.time + Q_irand(500, 1500); + } +} + +//doing primary weapon fire +int PrimFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING && + bs->doAttack) + { + return 1; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING && + !bs->doAttack) + { + return 1; + } + + return 0; +} + +//should we keep our primary weapon from firing? +int KeepPrimFromFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING && + bs->doAttack) + { + bs->doAttack = 0; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING && + !bs->doAttack) + { + bs->doAttack = 1; + } + + return 0; +} + +//doing secondary weapon fire +int AltFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT && + bs->doAltAttack) + { + return 1; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && + !bs->doAltAttack) + { + return 1; + } + + return 0; +} + +//should we keep our alt from firing? +int KeepAltFromFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT && + bs->doAltAttack) + { + bs->doAltAttack = 0; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && + !bs->doAltAttack) + { + bs->doAltAttack = 1; + } + + return 0; +} + +//Try not to shoot our friends in the back. Or in the face. Or anywhere, really. +gentity_t *CheckForFriendInLOF(bot_state_t *bs) +{ + vec3_t fwd; + vec3_t trfrom, trto; + vec3_t mins, maxs; + gentity_t *trent; + trace_t tr; + + mins[0] = -3; + mins[1] = -3; + mins[2] = -3; + + maxs[0] = 3; + maxs[1] = 3; + maxs[2] = 3; + + AngleVectors(bs->viewangles, fwd, NULL, NULL); + + VectorCopy(bs->eye, trfrom); + + trto[0] = trfrom[0] + fwd[0]*2048; + trto[1] = trfrom[1] + fwd[1]*2048; + trto[2] = trfrom[2] + fwd[2]*2048; + + trap_Trace(&tr, trfrom, mins, maxs, trto, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction != 1 && tr.entityNum < MAX_CLIENTS) + { + trent = &g_entities[tr.entityNum]; + + if (trent && trent->client) + { + if (IsTeamplay() && OnSameTeam(&g_entities[bs->client], trent)) + { + return trent; + } + + if (botstates[trent->s.number] && GetLoveLevel(bs, botstates[trent->s.number]) > 1) + { + return trent; + } + } + } + + return NULL; +} + +void BotScanForLeader(bot_state_t *bs) +{ //bots will only automatically obtain a leader if it's another bot using this method. + int i = 0; + gentity_t *ent; + + if (bs->isSquadLeader) + { + return; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && botstates[i] && botstates[i]->isSquadLeader && bs->client != i) + { + if (OnSameTeam(&g_entities[bs->client], ent)) + { + bs->squadLeader = ent; + break; + } + if (GetLoveLevel(bs, botstates[i]) > 1 && !IsTeamplay()) + { //ignore love status regarding squad leaders if we're in teamplay + bs->squadLeader = ent; + break; + } + } + + i++; + } +} + +//w3rd to the p33pz. +/* +void BotReplyGreetings(bot_state_t *bs) +{ + int i = 0; + int numhello = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && + botstates[i]->canChat && + i != bs->client) + { + botstates[i]->chatObject = &g_entities[bs->client]; + botstates[i]->chatAltObject = NULL; + if (BotDoChat(botstates[i], "ResponseGreetings", 0)) + { + numhello++; + } + } + + if (numhello > 3) + { //don't let more than 4 bots say hello at once + return; + } + + i++; + } +} +*/ + +//try to move in to grab a nearby flag +void CTFFlagMovement(bot_state_t *bs) +{ + int diddrop = 0; + gentity_t *desiredDrop = NULL; + vec3_t a, mins, maxs; + trace_t tr; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -7; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 7; + + if (bs->wantFlag && (bs->wantFlag->flags & FL_DROPPED_ITEM)) + { + if (bs->staticFlagSpot[0] == bs->wantFlag->s.pos.trBase[0] && + bs->staticFlagSpot[1] == bs->wantFlag->s.pos.trBase[1] && + bs->staticFlagSpot[2] == bs->wantFlag->s.pos.trBase[2]) + { + VectorSubtract(bs->origin, bs->wantFlag->s.pos.trBase, a); + + if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE) + { + VectorCopy(bs->wantFlag->s.pos.trBase, bs->goalPosition); + return; + } + else + { + bs->wantFlag = NULL; + } + } + else + { + bs->wantFlag = NULL; + } + } + else if (bs->wantFlag) + { + bs->wantFlag = NULL; + } + + if (flagRed && flagBlue) + { + if (bs->wpDestination == flagRed || + bs->wpDestination == flagBlue) + { + if (bs->wpDestination == flagRed && droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM) && droppedRedFlag->classname && strcmp(droppedRedFlag->classname, "freed") != 0) + { + desiredDrop = droppedRedFlag; + diddrop = 1; + } + if (bs->wpDestination == flagBlue && droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM) && droppedBlueFlag->classname && strcmp(droppedBlueFlag->classname, "freed") != 0) + { + desiredDrop = droppedBlueFlag; + diddrop = 1; + } + + if (diddrop && desiredDrop) + { + VectorSubtract(bs->origin, desiredDrop->s.pos.trBase, a); + + if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE) + { + trap_Trace(&tr, bs->origin, mins, maxs, desiredDrop->s.pos.trBase, bs->client, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == desiredDrop->s.number) + { + VectorCopy(desiredDrop->s.pos.trBase, bs->goalPosition); + VectorCopy(desiredDrop->s.pos.trBase, bs->staticFlagSpot); + return; + } + } + } + } + } +} + +//see if we want to make our detpacks blow up +void BotCheckDetPacks(bot_state_t *bs) +{ + gentity_t *dp = NULL; + gentity_t *myDet = NULL; + vec3_t a; + float enLen; + float myLen; + + while ( (dp = G_Find( dp, FOFS(classname), "detpack") ) != NULL ) + { + if (dp && dp->parent && dp->parent->s.number == bs->client) + { + myDet = dp; + break; + } + } + + if (!myDet) + { + return; + } + + if (!bs->currentEnemy || !bs->currentEnemy->client || !bs->frame_Enemy_Vis) + { //require the enemy to be visilbe just to be fair.. + + //unless.. + if (bs->currentEnemy && bs->currentEnemy->client && + (level.time - bs->plantContinue) < 5000) + { //it's a fresh plant (within 5 seconds) so we should be able to guess + goto stillmadeit; + } + return; + } + +stillmadeit: + + VectorSubtract(bs->currentEnemy->client->ps.origin, myDet->s.pos.trBase, a); + enLen = VectorLength(a); + + VectorSubtract(bs->origin, myDet->s.pos.trBase, a); + myLen = VectorLength(a); + + if (enLen > myLen) + { + return; + } + + if (enLen < BOT_PLANT_BLOW_DISTANCE && OrgVisible(bs->currentEnemy->client->ps.origin, myDet->s.pos.trBase, bs->currentEnemy->s.number)) + { //we could just call the "blow all my detpacks" function here, but I guess that's cheating. + bs->plantKillEmAll = level.time + 500; + } +} + +//see if it would be beneficial at this time to use one of our inv items +int BotUseInventoryItem(bot_state_t *bs) +{ + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC)) + { + if (g_entities[bs->client].health <= 75) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_MEDPAC, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC_BIG)) + { + if (g_entities[bs->client].health <= 50) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_MEDPAC_BIG, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SEEKER)) + { + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SEEKER, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SENTRY_GUN)) + { + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SENTRY_GUN, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SHIELD)) + { + if (bs->currentEnemy && bs->frame_Enemy_Vis && bs->runningToEscapeThreat) + { //this will (hopefully) result in the bot placing the shield down while facing + //the enemy and running away + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SHIELD, IT_HOLDABLE); + goto wantuseitem; + } + } + + return 0; + +wantuseitem: + level.clients[bs->client].ps.stats[STAT_HOLDABLE_ITEM] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM]; + + return 1; +} + +//trace forward to see if we can plant a detpack or something +int BotSurfaceNear(bot_state_t *bs) +{ + trace_t tr; + vec3_t fwd; + + AngleVectors(bs->viewangles, fwd, NULL, NULL); + + fwd[0] = bs->origin[0]+(fwd[0]*64); + fwd[1] = bs->origin[1]+(fwd[1]*64); + fwd[2] = bs->origin[2]+(fwd[2]*64); + + trap_Trace(&tr, bs->origin, NULL, NULL, fwd, bs->client, MASK_SOLID); + + if (tr.fraction != 1) + { + return 1; + } + + return 0; +} + +//could we block projectiles from the weapon potentially with a light saber? +int BotWeaponBlockable(int weapon) +{ + switch (weapon) + { + case WP_STUN_BATON: + case WP_MELEE: + return 0; + case WP_DISRUPTOR: + return 0; + case WP_DEMP2: + return 0; + case WP_ROCKET_LAUNCHER: + return 0; + case WP_THERMAL: + return 0; + case WP_TRIP_MINE: + return 0; + case WP_DET_PACK: + return 0; + default: + return 1; + } +} + +void Cmd_EngageDuel_f(gentity_t *ent); +void Cmd_ToggleSaber_f(gentity_t *ent); + +//movement overrides +void Bot_SetForcedMovement(int bot, int forward, int right, int up) +{ + bot_state_t *bs; + + bs = botstates[bot]; + + if (!bs) + { //not a bot + return; + } + + if (forward != -1) + { + if (bs->forceMove_Forward) + { + bs->forceMove_Forward = 0; + } + else + { + bs->forceMove_Forward = forward; + } + } + if (right != -1) + { + if (bs->forceMove_Right) + { + bs->forceMove_Right = 0; + } + else + { + bs->forceMove_Right = right; + } + } + if (up != -1) + { + if (bs->forceMove_Up) + { + bs->forceMove_Up = 0; + } + else + { + bs->forceMove_Up = up; + } + } +} + +//the main AI loop. +//please don't be too frightened. +void StandardBotAI(bot_state_t *bs, float thinktime) +{ + int wp, enemy; + int desiredIndex; + int goalWPIndex; + int doingFallback = 0; + int fjHalt; + vec3_t a, ang, headlevel, eorg, noz_x, noz_y, dif, a_fo; + float reaction; + float bLeadAmount; + int meleestrafe = 0; + int useTheForce = 0; + int forceHostile = 0; + int cBAI = 0; + gentity_t *friendInLOF = 0; + float mLen; + int visResult = 0; + int selResult = 0; + int mineSelect = 0; + int detSelect = 0; + vec3_t preFrameGAngles; + + if (gDeactivated) + { + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpDirection = 0; + return; + } + + if (g_entities[bs->client].inuse && + g_entities[bs->client].client && + g_entities[bs->client].client->sess.sessionTeam == TEAM_SPECTATOR) + { + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpDirection = 0; + return; + } + + +#ifndef FINAL_BUILD + if (bot_getinthecarrr.integer) + { //stupid vehicle debug, I tire of having to connect another client to test passengers. + gentity_t *botEnt = &g_entities[bs->client]; + + if (botEnt->inuse && botEnt->client && botEnt->client->ps.m_iVehicleNum) + { //in a vehicle, so... + bs->noUseTime = level.time + 5000; + + if (bot_getinthecarrr.integer != 2) + { + trap_EA_MoveForward(bs->client); + + if (bot_getinthecarrr.integer == 3) + { //use alt fire + trap_EA_Alt_Attack(bs->client); + } + } + } + else + { //find one, get in + int i = 0; + gentity_t *vehicle = NULL; + //find the nearest, manned vehicle + while (i < MAX_GENTITIES) + { + vehicle = &g_entities[i]; + + if (vehicle->inuse && vehicle->client && vehicle->s.eType == ET_NPC && + vehicle->s.NPC_class == CLASS_VEHICLE && vehicle->m_pVehicle && + (vehicle->client->ps.m_iVehicleNum || bot_getinthecarrr.integer == 2)) + { //ok, this is a vehicle, and it has a pilot/passengers + break; + } + i++; + } + if (i != MAX_GENTITIES && vehicle) + { //broke before end so we must've found something + vec3_t v; + + VectorSubtract(vehicle->client->ps.origin, bs->origin, v); + VectorNormalize(v); + vectoangles(v, bs->goalAngles); + MoveTowardIdealAngles(bs); + trap_EA_Move(bs->client, v, 5000.0f); + + if (bs->noUseTime < (level.time-400)) + { + bs->noUseTime = level.time + 500; + } + } + } + + return; + } +#endif + + if (bot_forgimmick.integer) + { + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpDirection = 0; + + if (bot_forgimmick.integer == 2) + { //for debugging saber stuff, this is handy + trap_EA_Attack(bs->client); + } + + if (bot_forgimmick.integer == 3) + { //for testing cpu usage moving around rmg terrain without AI + vec3_t mdir; + + VectorSubtract(bs->origin, vec3_origin, mdir); + VectorNormalize(mdir); + trap_EA_Attack(bs->client); + trap_EA_Move(bs->client, mdir, 5000); + } + + if (bot_forgimmick.integer == 4) + { //constantly move toward client 0 + if (g_entities[0].client && g_entities[0].inuse) + { + vec3_t mdir; + + VectorSubtract(g_entities[0].client->ps.origin, bs->origin, mdir); + VectorNormalize(mdir); + trap_EA_Move(bs->client, mdir, 5000); + } + } + + if (bs->forceMove_Forward) + { + if (bs->forceMove_Forward > 0) + { + trap_EA_MoveForward(bs->client); + } + else + { + trap_EA_MoveBack(bs->client); + } + } + if (bs->forceMove_Right) + { + if (bs->forceMove_Right > 0) + { + trap_EA_MoveRight(bs->client); + } + else + { + trap_EA_MoveLeft(bs->client); + } + } + if (bs->forceMove_Up) + { + trap_EA_Jump(bs->client); + } + return; + } + + if (!bs->lastDeadTime) + { //just spawned in? + bs->lastDeadTime = level.time; + } + + if (g_entities[bs->client].health < 1) + { + bs->lastDeadTime = level.time; + + if (!bs->deathActivitiesDone && bs->lastHurt && bs->lastHurt->client && bs->lastHurt->s.number != bs->client) + { + BotDeathNotify(bs); + if (PassLovedOneCheck(bs, bs->lastHurt)) + { + //CHAT: Died +/* + bs->chatObject = bs->lastHurt; + bs->chatAltObject = NULL; + BotDoChat(bs, "Died", 0); +*/ + } + else if (!PassLovedOneCheck(bs, bs->lastHurt) && + botstates[bs->lastHurt->s.number] && + PassLovedOneCheck(botstates[bs->lastHurt->s.number], &g_entities[bs->client])) + { //killed by a bot that I love, but that does not love me +/* + bs->chatObject = bs->lastHurt; + bs->chatAltObject = NULL; + BotDoChat(bs, "KilledOnPurposeByLove", 0); +*/ + } + + bs->deathActivitiesDone = 1; + } + + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpCamping = NULL; + bs->wpCampingTo = NULL; + bs->wpStoreDest = NULL; + bs->wpDestIgnoreTime = 0; + bs->wpDestSwitchTime = 0; + bs->wpSeenTime = 0; + bs->wpDirection = 0; + + if (rand()%10 < 5) // && (!bs->doChat || bs->chatTime < level.time)) + { + trap_EA_Attack(bs->client); + } + + return; + } + + VectorCopy(bs->goalAngles, preFrameGAngles); + + bs->doAttack = 0; + bs->doAltAttack = 0; + //reset the attack states + + if (bs->isSquadLeader) + { + CommanderBotAI(bs); + } + else + { + BotDoTeamplayAI(bs); + } + + if (!bs->currentEnemy) + { + bs->frame_Enemy_Vis = 0; + } + + if (bs->revengeEnemy && bs->revengeEnemy->client && + bs->revengeEnemy->client->pers.connected != CA_ACTIVE && bs->revengeEnemy->client->pers.connected != CA_AUTHORIZING) + { + bs->revengeEnemy = NULL; + bs->revengeHateLevel = 0; + } + + if (bs->currentEnemy && bs->currentEnemy->client && + bs->currentEnemy->client->pers.connected != CA_ACTIVE && bs->currentEnemy->client->pers.connected != CA_AUTHORIZING) + { + bs->currentEnemy = NULL; + } + + fjHalt = 0; + +#ifndef FORCEJUMP_INSTANTMETHOD + if (bs->forceJumpChargeTime > level.time) + { + useTheForce = 1; + forceHostile = 0; + } + + if (bs->currentEnemy && bs->currentEnemy->client && bs->frame_Enemy_Vis && bs->forceJumpChargeTime < level.time) +#else + if (bs->currentEnemy && bs->currentEnemy->client && bs->frame_Enemy_Vis) +#endif + { + VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, a_fo); + vectoangles(a_fo, a_fo); + + //do this above all things + if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PUSH)) && (bs->doForcePush > level.time || bs->cur_ps.fd.forceGripBeingGripped > level.time) && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PUSH]][FP_PUSH] /*&& InFieldOfVision(bs->viewangles, 50, a_fo)*/) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_PUSH; + useTheForce = 1; + forceHostile = 1; + } + else if (bs->cur_ps.fd.forceSide == FORCE_DARKSIDE) + { //try dark side powers + //in order of priority top to bottom + if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_GRIP)) && (bs->cur_ps.fd.forcePowersActive & (1 << FP_GRIP)) && InFieldOfVision(bs->viewangles, 50, a_fo)) + { //already gripping someone, so hold it + level.clients[bs->client].ps.fd.forcePowerSelected = FP_GRIP; + useTheForce = 1; + forceHostile = 1; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_LIGHTNING)) && bs->frame_Enemy_Len < FORCE_LIGHTNING_RADIUS && level.clients[bs->client].ps.fd.forcePower > 50 && InFieldOfVision(bs->viewangles, 50, a_fo)) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_LIGHTNING; + useTheForce = 1; + forceHostile = 1; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_GRIP)) && bs->frame_Enemy_Len < MAX_GRIP_DISTANCE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_GRIP]][FP_GRIP] && InFieldOfVision(bs->viewangles, 50, a_fo)) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_GRIP; + useTheForce = 1; + forceHostile = 1; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_RAGE)) && g_entities[bs->client].health < 25 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_RAGE]][FP_RAGE]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_RAGE; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_DRAIN)) && bs->frame_Enemy_Len < MAX_DRAIN_DISTANCE && level.clients[bs->client].ps.fd.forcePower > 50 && InFieldOfVision(bs->viewangles, 50, a_fo) && bs->currentEnemy->client->ps.fd.forcePower > 10 && bs->currentEnemy->client->ps.fd.forceSide == FORCE_LIGHTSIDE) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_DRAIN; + useTheForce = 1; + forceHostile = 1; + } + } + else if (bs->cur_ps.fd.forceSide == FORCE_LIGHTSIDE) + { //try light side powers + if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && bs->cur_ps.fd.forceGripCripple && + level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB]) + { //absorb to get out + level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && bs->cur_ps.electrifyTime >= level.time && + level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB]) + { //absorb lightning + level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_TELEPATHY)) && bs->frame_Enemy_Len < MAX_TRICK_DISTANCE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TELEPATHY]][FP_TELEPATHY] && InFieldOfVision(bs->viewangles, 50, a_fo) && !(bs->currentEnemy->client->ps.fd.forcePowersActive & (1 << FP_SEE))) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_TELEPATHY; + useTheForce = 1; + forceHostile = 1; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && g_entities[bs->client].health < 75 && bs->currentEnemy->client->ps.fd.forceSide == FORCE_DARKSIDE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PROTECT)) && g_entities[bs->client].health < 35 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PROTECT]][FP_PROTECT]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_PROTECT; + useTheForce = 1; + forceHostile = 0; + } + } + + if (!useTheForce) + { //try neutral powers + if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PUSH)) && bs->cur_ps.fd.forceGripBeingGripped > level.time && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PUSH]][FP_PUSH] && InFieldOfVision(bs->viewangles, 50, a_fo)) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_PUSH; + useTheForce = 1; + forceHostile = 1; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_SPEED)) && g_entities[bs->client].health < 25 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_SPEED]][FP_SPEED]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_SPEED; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_SEE)) && BotMindTricked(bs->client, bs->currentEnemy->s.number) && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_SEE]][FP_SEE]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_SEE; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PULL)) && bs->frame_Enemy_Len < 256 && level.clients[bs->client].ps.fd.forcePower > 75 && InFieldOfVision(bs->viewangles, 50, a_fo)) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_PULL; + useTheForce = 1; + forceHostile = 1; + } + } + } + + if (!useTheForce) + { //try powers that we don't care if we have an enemy for + if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_HEAL)) && g_entities[bs->client].health < 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_HEAL]][FP_HEAL] && bs->cur_ps.fd.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_1) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_HEAL; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_HEAL)) && g_entities[bs->client].health < 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_HEAL]][FP_HEAL] && !bs->currentEnemy && bs->isCamping > level.time) + { //only meditate and heal if we're camping + level.clients[bs->client].ps.fd.forcePowerSelected = FP_HEAL; + useTheForce = 1; + forceHostile = 0; + } + } + + if (useTheForce && forceHostile) + { + if (bs->currentEnemy && bs->currentEnemy->client && + !ForcePowerUsableOn(&g_entities[bs->client], bs->currentEnemy, level.clients[bs->client].ps.fd.forcePowerSelected)) + { + useTheForce = 0; + forceHostile = 0; + } + } + + doingFallback = 0; + + bs->deathActivitiesDone = 0; + + if (BotUseInventoryItem(bs)) + { + if (rand()%10 < 5) + { + trap_EA_Use(bs->client); + } + } + + if (bs->cur_ps.ammo[weaponData[bs->cur_ps.weapon].ammoIndex] < weaponData[bs->cur_ps.weapon].energyPerShot) + { + if (BotTryAnotherWeapon(bs)) + { + return; + } + } + else + { + if (bs->currentEnemy && bs->lastVisibleEnemyIndex == bs->currentEnemy->s.number && + bs->frame_Enemy_Vis && bs->forceWeaponSelect /*&& bs->plantContinue < level.time*/) + { + bs->forceWeaponSelect = 0; + } + + if (bs->plantContinue > level.time) + { + bs->doAttack = 1; + bs->destinationGrabTime = 0; + } + + if (!bs->forceWeaponSelect && bs->cur_ps.hasDetPackPlanted && bs->plantKillEmAll > level.time) + { + bs->forceWeaponSelect = WP_DET_PACK; + } + + if (bs->forceWeaponSelect) + { + selResult = BotSelectChoiceWeapon(bs, bs->forceWeaponSelect, 1); + } + + if (selResult) + { + if (selResult == 2) + { //newly selected + return; + } + } + else if (BotSelectIdealWeapon(bs)) + { + return; + } + } + /*if (BotSelectMelee(bs)) + { + return; + }*/ + + reaction = bs->skills.reflex/bs->settings.skill; + + if (reaction < 0) + { + reaction = 0; + } + if (reaction > 2000) + { + reaction = 2000; + } + + if (!bs->currentEnemy) + { + bs->timeToReact = level.time + reaction; + } + + if (bs->cur_ps.weapon == WP_DET_PACK && bs->cur_ps.hasDetPackPlanted && bs->plantKillEmAll > level.time) + { + bs->doAltAttack = 1; + } + + if (bs->wpCamping) + { + if (bs->isCamping < level.time) + { + bs->wpCamping = NULL; + bs->isCamping = 0; + } + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + bs->wpCamping = NULL; + bs->isCamping = 0; + } + } + + if (bs->wpCurrent && + (bs->wpSeenTime < level.time || bs->wpTravelTime < level.time)) + { + bs->wpCurrent = NULL; + } + + if (bs->currentEnemy) + { + if (bs->enemySeenTime < level.time || + !PassStandardEnemyChecks(bs, bs->currentEnemy)) + { + if (bs->revengeEnemy == bs->currentEnemy && + bs->currentEnemy->health < 1 && + bs->lastAttacked && bs->lastAttacked == bs->currentEnemy) + { + //CHAT: Destroyed hated one [KilledHatedOne section] +/* + bs->chatObject = bs->revengeEnemy; + bs->chatAltObject = NULL; + BotDoChat(bs, "KilledHatedOne", 1); +*/ + bs->revengeEnemy = NULL; + bs->revengeHateLevel = 0; + } + else if (bs->currentEnemy->health < 1 && PassLovedOneCheck(bs, bs->currentEnemy) && + bs->lastAttacked && bs->lastAttacked == bs->currentEnemy) + { + //CHAT: Killed +/* + bs->chatObject = bs->currentEnemy; + bs->chatAltObject = NULL; + BotDoChat(bs, "Killed", 0); +*/ + } + + bs->currentEnemy = NULL; + } + } + + if (bot_honorableduelacceptance.integer) + { + if (bs->currentEnemy && bs->currentEnemy->client && + bs->cur_ps.weapon == WP_SABER && + g_privateDuel.integer && + bs->frame_Enemy_Vis && + bs->frame_Enemy_Len < 400 && + bs->currentEnemy->client->ps.weapon == WP_SABER && + bs->currentEnemy->client->ps.saberHolstered) + { + vec3_t e_ang_vec; + + VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, e_ang_vec); + + if (InFieldOfVision(bs->viewangles, 100, e_ang_vec)) + { //Our enemy has his saber holstered and has challenged us to a duel, so challenge him back + if (!bs->cur_ps.saberHolstered) + { + Cmd_ToggleSaber_f(&g_entities[bs->client]); + } + else + { + if (bs->currentEnemy->client->ps.duelIndex == bs->client && + bs->currentEnemy->client->ps.duelTime > level.time && + !bs->cur_ps.duelInProgress) + { + Cmd_EngageDuel_f(&g_entities[bs->client]); + } + } + + bs->doAttack = 0; + bs->doAltAttack = 0; + bs->botChallengingTime = level.time + 100; + bs->beStill = level.time + 100; + } + } + } + //Apparently this "allows you to cheese" when fighting against bots. I'm not sure why you'd want to con bots + //into an easy kill, since they're bots and all. But whatever. + + if (!bs->wpCurrent) + { + wp = GetNearestVisibleWP(bs->origin, bs->client); + + if (wp != -1) + { + bs->wpCurrent = gWPArray[wp]; + bs->wpSeenTime = level.time + 1500; + bs->wpTravelTime = level.time + 10000; //never take more than 10 seconds to travel to a waypoint + } + } + + if (bs->enemySeenTime < level.time || !bs->frame_Enemy_Vis || !bs->currentEnemy || + (bs->currentEnemy /*&& bs->cur_ps.weapon == WP_SABER && bs->frame_Enemy_Len > 300*/)) + { + enemy = ScanForEnemies(bs); + + if (enemy != -1) + { + bs->currentEnemy = &g_entities[enemy]; + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } + } + + if (!bs->squadLeader && !bs->isSquadLeader) + { + BotScanForLeader(bs); + } + + if (!bs->squadLeader && bs->squadCannotLead < level.time) + { //if still no leader after scanning, then become a squad leader + bs->isSquadLeader = 1; + } + + if (bs->isSquadLeader && bs->squadLeader) + { //we don't follow anyone if we are a leader + bs->squadLeader = NULL; + } + + //ESTABLISH VISIBILITIES AND DISTANCES FOR THE WHOLE FRAME HERE + if (bs->wpCurrent) + { + if (g_RMG.integer) + { //this is somewhat hacky, but in RMG we don't really care about vertical placement because points are scattered across only the terrain. + vec3_t vecB, vecC; + + vecB[0] = bs->origin[0]; + vecB[1] = bs->origin[1]; + vecB[2] = bs->origin[2]; + + vecC[0] = bs->wpCurrent->origin[0]; + vecC[1] = bs->wpCurrent->origin[1]; + vecC[2] = vecB[2]; + + + VectorSubtract(vecC, vecB, a); + } + else + { + VectorSubtract(bs->wpCurrent->origin, bs->origin, a); + } + bs->frame_Waypoint_Len = VectorLength(a); + + visResult = WPOrgVisible(&g_entities[bs->client], bs->origin, bs->wpCurrent->origin, bs->client); + + if (visResult == 2) + { + bs->frame_Waypoint_Vis = 0; + bs->wpSeenTime = 0; + bs->wpDestination = NULL; + bs->wpDestIgnoreTime = level.time + 5000; + + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + } + else if (visResult) + { + bs->frame_Waypoint_Vis = 1; + } + else + { + bs->frame_Waypoint_Vis = 0; + } + } + + if (bs->currentEnemy) + { + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, eorg); + eorg[2] += bs->currentEnemy->client->ps.viewheight; + } + else + { + VectorCopy(bs->currentEnemy->s.origin, eorg); + } + + VectorSubtract(eorg, bs->eye, a); + bs->frame_Enemy_Len = VectorLength(a); + + if (OrgVisible(bs->eye, eorg, bs->client)) + { + bs->frame_Enemy_Vis = 1; + VectorCopy(eorg, bs->lastEnemySpotted); + VectorCopy(bs->origin, bs->hereWhenSpotted); + bs->lastVisibleEnemyIndex = bs->currentEnemy->s.number; + //VectorCopy(bs->eye, bs->lastEnemySpotted); + bs->hitSpotted = 0; + } + else + { + bs->frame_Enemy_Vis = 0; + } + } + else + { + bs->lastVisibleEnemyIndex = ENTITYNUM_NONE; + } + //END + + if (bs->frame_Enemy_Vis) + { + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } + + if (bs->wpCurrent) + { + int wpTouchDist = BOT_WPTOUCH_DISTANCE; + WPConstantRoutine(bs); + + if (!bs->wpCurrent) + { //WPConstantRoutine has the ability to nullify the waypoint if it fails certain checks, so.. + return; + } + + if (bs->wpCurrent->flags & WPFLAG_WAITFORFUNC) + { + if (!CheckForFunc(bs->wpCurrent->origin, -1)) + { + bs->beStill = level.time + 500; //no func brush under.. wait + } + } + if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC) + { + if (CheckForFunc(bs->wpCurrent->origin, -1)) + { + bs->beStill = level.time + 500; //func brush under.. wait + } + } + + if (bs->frame_Waypoint_Vis || (bs->wpCurrent->flags & WPFLAG_NOVIS)) + { + if (g_RMG.integer) + { + bs->wpSeenTime = level.time + 5000; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it + } + else + { + bs->wpSeenTime = level.time + 1500; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it + } + } + VectorCopy(bs->wpCurrent->origin, bs->goalPosition); + if (bs->wpDirection) + { + goalWPIndex = bs->wpCurrent->index-1; + } + else + { + goalWPIndex = bs->wpCurrent->index+1; + } + + if (bs->wpCamping) + { + VectorSubtract(bs->wpCampingTo->origin, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + + VectorSubtract(bs->origin, bs->wpCamping->origin, a); + if (VectorLength(a) < 64) + { + VectorCopy(bs->wpCamping->origin, bs->goalPosition); + bs->beStill = level.time + 1000; + + if (!bs->campStanding) + { + bs->duckTime = level.time + 1000; + } + } + } + else if (gWPArray[goalWPIndex] && gWPArray[goalWPIndex]->inuse && + !(gLevelFlags & LEVELFLAG_NOPOINTPREDICTION)) + { + VectorSubtract(gWPArray[goalWPIndex]->origin, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + } + else + { + VectorSubtract(bs->wpCurrent->origin, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + } + + if (bs->destinationGrabTime < level.time /*&& (!bs->wpDestination || (bs->currentEnemy && bs->frame_Enemy_Vis))*/) + { + GetIdealDestination(bs); + } + + if (bs->wpCurrent && bs->wpDestination) + { + if (TotalTrailDistance(bs->wpCurrent->index, bs->wpDestination->index, bs) == -1) + { + bs->wpDestination = NULL; + bs->destinationGrabTime = level.time + 10000; + } + } + + if (g_RMG.integer) + { + if (bs->frame_Waypoint_Vis) + { + if (bs->wpCurrent && !bs->wpCurrent->flags) + { + wpTouchDist *= 3; + } + } + } + + if (bs->frame_Waypoint_Len < wpTouchDist || (g_RMG.integer && bs->frame_Waypoint_Len < wpTouchDist*2)) + { + WPTouchRoutine(bs); + + if (!bs->wpDirection) + { + desiredIndex = bs->wpCurrent->index+1; + } + else + { + desiredIndex = bs->wpCurrent->index-1; + } + + if (gWPArray[desiredIndex] && + gWPArray[desiredIndex]->inuse && + desiredIndex < gWPNum && + desiredIndex >= 0 && + PassWayCheck(bs, desiredIndex)) + { + bs->wpCurrent = gWPArray[desiredIndex]; + } + else + { + if (bs->wpDestination) + { + bs->wpDestination = NULL; + bs->destinationGrabTime = level.time + 10000; + } + + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + } + } + } + else //We can't find a waypoint, going to need a fallback routine. + { + /*if (g_gametype.integer == GT_DUEL)*/ + { //helps them get out of messy situations + /*if ((level.time - bs->forceJumpChargeTime) > 3500) + { + bs->forceJumpChargeTime = level.time + 2000; + trap_EA_MoveForward(bs->client); + } + */ + bs->jumpTime = level.time + 1500; + bs->jumpHoldTime = level.time + 1500; + bs->jDelay = 0; + } + doingFallback = BotFallbackNavigation(bs); + } + + if (g_RMG.integer) + { //for RMG if the bot sticks around an area too long, jump around randomly some to spread to a new area (horrible hacky method) + vec3_t vSubDif; + + VectorSubtract(bs->origin, bs->lastSignificantAreaChange, vSubDif); + if (VectorLength(vSubDif) > 1500) + { + VectorCopy(bs->origin, bs->lastSignificantAreaChange); + bs->lastSignificantChangeTime = level.time + 20000; + } + + if (bs->lastSignificantChangeTime < level.time) + { + bs->iHaveNoIdeaWhereIAmGoing = level.time + 17000; + } + } + + if (bs->iHaveNoIdeaWhereIAmGoing > level.time && !bs->currentEnemy) + { + VectorCopy(preFrameGAngles, bs->goalAngles); + bs->wpCurrent = NULL; + bs->wpSwitchTime = level.time + 150; + doingFallback = BotFallbackNavigation(bs); + bs->jumpTime = level.time + 150; + bs->jumpHoldTime = level.time + 150; + bs->jDelay = 0; + bs->lastSignificantChangeTime = level.time + 25000; + } + + if (bs->wpCurrent && g_RMG.integer) + { + qboolean doJ = qfalse; + + if (bs->wpCurrent->origin[2]-192 > bs->origin[2]) + { + doJ = qtrue; + } + else if ((bs->wpTravelTime - level.time) < 5000 && bs->wpCurrent->origin[2]-64 > bs->origin[2]) + { + doJ = qtrue; + } + else if ((bs->wpTravelTime - level.time) < 7000 && (bs->wpCurrent->flags & WPFLAG_RED_FLAG)) + { + if ((level.time - bs->jumpTime) > 200) + { + bs->jumpTime = level.time + 100; + bs->jumpHoldTime = level.time + 100; + bs->jDelay = 0; + } + } + else if ((bs->wpTravelTime - level.time) < 7000 && (bs->wpCurrent->flags & WPFLAG_BLUE_FLAG)) + { + if ((level.time - bs->jumpTime) > 200) + { + bs->jumpTime = level.time + 100; + bs->jumpHoldTime = level.time + 100; + bs->jDelay = 0; + } + } + else if (bs->wpCurrent->index > 0) + { + if ((bs->wpTravelTime - level.time) < 7000) + { + if ((gWPArray[bs->wpCurrent->index-1]->flags & WPFLAG_RED_FLAG) || + (gWPArray[bs->wpCurrent->index-1]->flags & WPFLAG_BLUE_FLAG)) + { + if ((level.time - bs->jumpTime) > 200) + { + bs->jumpTime = level.time + 100; + bs->jumpHoldTime = level.time + 100; + bs->jDelay = 0; + } + } + } + } + + if (doJ) + { + bs->jumpTime = level.time + 1500; + bs->jumpHoldTime = level.time + 1500; + bs->jDelay = 0; + } + } + + if (doingFallback) + { + bs->doingFallback = qtrue; + } + else + { + bs->doingFallback = qfalse; + } + + if (bs->timeToReact < level.time && bs->currentEnemy && bs->enemySeenTime > level.time + (ENEMY_FORGET_MS - (ENEMY_FORGET_MS*0.2))) + { + if (bs->frame_Enemy_Vis) + { + cBAI = CombatBotAI(bs, thinktime); + } + else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) + { //keep charging in case we see him again before we lose track of him + bs->doAltAttack = 1; + } + else if (bs->cur_ps.weaponstate == WEAPON_CHARGING) + { //keep charging in case we see him again before we lose track of him + bs->doAttack = 1; + } + + if (bs->destinationGrabTime > level.time + 100) + { + bs->destinationGrabTime = level.time + 100; //assures that we will continue staying within a general area of where we want to be in a combat situation + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, headlevel); + headlevel[2] += bs->currentEnemy->client->ps.viewheight; + } + else + { + VectorCopy(bs->currentEnemy->client->ps.origin, headlevel); + } + + if (!bs->frame_Enemy_Vis) + { + //if (!bs->hitSpotted && VectorLength(a) > 256) + if (OrgVisible(bs->eye, bs->lastEnemySpotted, -1)) + { + VectorCopy(bs->lastEnemySpotted, headlevel); + VectorSubtract(headlevel, bs->eye, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + + if (bs->cur_ps.weapon == WP_FLECHETTE && + bs->cur_ps.weaponstate == WEAPON_READY && + bs->currentEnemy && bs->currentEnemy->client) + { + mLen = VectorLength(a) > 128; + if (mLen > 128 && mLen < 1024) + { + VectorSubtract(bs->currentEnemy->client->ps.origin, bs->lastEnemySpotted, a); + + if (VectorLength(a) < 300) + { + bs->doAltAttack = 1; + } + } + } + } + } + else + { + bLeadAmount = BotWeaponCanLead(bs); + if ((bs->skills.accuracy/bs->settings.skill) <= 8 && + bLeadAmount) + { + BotAimLeading(bs, headlevel, bLeadAmount); + } + else + { + VectorSubtract(headlevel, bs->eye, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + } + + BotAimOffsetGoalAngles(bs); + } + } + + if (bs->cur_ps.saberInFlight) + { + bs->saberThrowTime = level.time + Q_irand(4000, 10000); + } + + if (bs->currentEnemy) + { + if (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER) + { + int saberRange = SABER_ATTACK_RANGE; + + VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, a_fo); + vectoangles(a_fo, a_fo); + + if (bs->saberPowerTime < level.time) + { //Don't just use strong attacks constantly, switch around a bit + if (Q_irand(1, 10) <= 5) + { + bs->saberPower = qtrue; + } + else + { + bs->saberPower = qfalse; + } + + bs->saberPowerTime = level.time + Q_irand(3000, 15000); + } + + if ( g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_STAFF + && g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_DUAL ) + { + if (bs->currentEnemy->health > 75 + && g_entities[bs->client].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > 2) + { + if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_STRONG + && bs->saberPower) + { //if we are up against someone with a lot of health and we have a strong attack available, then h4q them + Cmd_SaberAttackCycle_f(&g_entities[bs->client]); + } + } + else if (bs->currentEnemy->health > 40 + && g_entities[bs->client].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > 1) + { + if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_MEDIUM) + { //they're down on health a little, use level 2 if we can + Cmd_SaberAttackCycle_f(&g_entities[bs->client]); + } + } + else + { + if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_FAST) + { //they've gone below 40 health, go at them with quick attacks + Cmd_SaberAttackCycle_f(&g_entities[bs->client]); + } + } + } + + if (g_gametype.integer == GT_SINGLE_PLAYER) + { + saberRange *= 3; + } + + if (bs->frame_Enemy_Len <= saberRange) + { + SaberCombatHandling(bs); + + if (bs->frame_Enemy_Len < 80) + { + meleestrafe = 1; + } + } + else if (bs->saberThrowTime < level.time && !bs->cur_ps.saberInFlight && + (bs->cur_ps.fd.forcePowersKnown & (1 << FP_SABERTHROW)) && + InFieldOfVision(bs->viewangles, 30, a_fo) && + bs->frame_Enemy_Len < BOT_SABER_THROW_RANGE && + bs->cur_ps.fd.saberAnimLevel != SS_STAFF) + { + bs->doAltAttack = 1; + bs->doAttack = 0; + } + else if (bs->cur_ps.saberInFlight && bs->frame_Enemy_Len > 300 && bs->frame_Enemy_Len < BOT_SABER_THROW_RANGE) + { + bs->doAltAttack = 1; + bs->doAttack = 0; + } + } + else if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE) + { + if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE) + { + MeleeCombatHandling(bs); + meleestrafe = 1; + } + } + } + + if (doingFallback && bs->currentEnemy) //just stand and fire if we have no idea where we are + { + VectorCopy(bs->origin, bs->goalPosition); + } + + if (bs->forceJumping > level.time) + { + VectorCopy(bs->origin, noz_x); + VectorCopy(bs->goalPosition, noz_y); + + noz_x[2] = noz_y[2]; + + VectorSubtract(noz_x, noz_y, noz_x); + + if (VectorLength(noz_x) < 32) + { + fjHalt = 1; + } + } + +/* + if (bs->doChat && bs->chatTime > level.time && (!bs->currentEnemy || !bs->frame_Enemy_Vis)) + { + return; + } + else if (bs->doChat && bs->currentEnemy && bs->frame_Enemy_Vis) + { + //bs->chatTime = level.time + bs->chatTime_stored; + bs->doChat = 0; //do we want to keep the bot waiting to chat until after the enemy is gone? + bs->chatTeam = 0; + } + else if (bs->doChat && bs->chatTime <= level.time) + { + if (bs->chatTeam) + { + trap_EA_SayTeam(bs->client, bs->currentChat); + bs->chatTeam = 0; + } + else + { + trap_EA_Say(bs->client, bs->currentChat); + } + if (bs->doChat == 2) + { + BotReplyGreetings(bs); + } + bs->doChat = 0; + } +*/ + + CTFFlagMovement(bs); + + if (/*bs->wpDestination &&*/ bs->shootGoal && + /*bs->wpDestination->associated_entity == bs->shootGoal->s.number &&*/ + bs->shootGoal->health > 0 && bs->shootGoal->takedamage) + { + dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2; + dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2; + dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2; + + if (!bs->currentEnemy || bs->frame_Enemy_Len > 256) + { //if someone is close then don't stop shooting them for this + VectorSubtract(dif, bs->eye, a); + vectoangles(a, a); + VectorCopy(a, bs->goalAngles); + + if (InFieldOfVision(bs->viewangles, 30, a) && + EntityVisibleBox(bs->origin, NULL, NULL, dif, bs->client, bs->shootGoal->s.number)) + { + bs->doAttack = 1; + } + } + } + + if (bs->cur_ps.hasDetPackPlanted) + { //check if our enemy gets near it and detonate if he does + BotCheckDetPacks(bs); + } + else if (bs->currentEnemy && bs->lastVisibleEnemyIndex == bs->currentEnemy->s.number && !bs->frame_Enemy_Vis && bs->plantTime < level.time && + !bs->doAttack && !bs->doAltAttack) + { + VectorSubtract(bs->origin, bs->hereWhenSpotted, a); + + if (bs->plantDecided > level.time || (bs->frame_Enemy_Len < BOT_PLANT_DISTANCE*2 && VectorLength(a) < BOT_PLANT_DISTANCE)) + { + mineSelect = BotSelectChoiceWeapon(bs, WP_TRIP_MINE, 0); + detSelect = BotSelectChoiceWeapon(bs, WP_DET_PACK, 0); + if (bs->cur_ps.hasDetPackPlanted) + { + detSelect = 0; + } + + if (bs->plantDecided > level.time && bs->forceWeaponSelect && + bs->cur_ps.weapon == bs->forceWeaponSelect) + { + bs->doAttack = 1; + bs->plantDecided = 0; + bs->plantTime = level.time + BOT_PLANT_INTERVAL; + bs->plantContinue = level.time + 500; + bs->beStill = level.time + 500; + } + else if (mineSelect || detSelect) + { + if (BotSurfaceNear(bs)) + { + if (!mineSelect) + { //if no mines use detpacks, otherwise use mines + mineSelect = WP_DET_PACK; + } + else + { + mineSelect = WP_TRIP_MINE; + } + + detSelect = BotSelectChoiceWeapon(bs, mineSelect, 1); + + if (detSelect && detSelect != 2) + { //We have it and it is now our weapon + bs->plantDecided = level.time + 1000; + bs->forceWeaponSelect = mineSelect; + return; + } + else if (detSelect == 2) + { + bs->forceWeaponSelect = mineSelect; + return; + } + } + } + } + } + else if (bs->plantContinue < level.time) + { + bs->forceWeaponSelect = 0; + } +/* + if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster && bs->jmState == -1 && gJMSaberEnt && gJMSaberEnt->inuse) + { + vec3_t saberLen; + float fSaberLen = 0; + + VectorSubtract(bs->origin, gJMSaberEnt->r.currentOrigin, saberLen); + fSaberLen = VectorLength(saberLen); + + if (fSaberLen < 256) + { + if (OrgVisible(bs->origin, gJMSaberEnt->r.currentOrigin, bs->client)) + { + VectorCopy(gJMSaberEnt->r.currentOrigin, bs->goalPosition); + } + } + } +*/ + if (bs->beStill < level.time && !WaitingForNow(bs, bs->goalPosition) && !fjHalt) + { + VectorSubtract(bs->goalPosition, bs->origin, bs->goalMovedir); + VectorNormalize(bs->goalMovedir); + + if (bs->jumpTime > level.time && bs->jDelay < level.time && + level.clients[bs->client].pers.cmd.upmove > 0) + { + // trap_EA_Move(bs->client, bs->origin, 5000); + bs->beStill = level.time + 200; + } + else + { + trap_EA_Move(bs->client, bs->goalMovedir, 5000); + } + + if (meleestrafe) + { + StrafeTracing(bs); + } + + if (bs->meleeStrafeDir && meleestrafe && bs->meleeStrafeDisable < level.time) + { + trap_EA_MoveRight(bs->client); + } + else if (meleestrafe && bs->meleeStrafeDisable < level.time) + { + trap_EA_MoveLeft(bs->client); + } + + if (BotTrace_Jump(bs, bs->goalPosition)) + { + bs->jumpTime = level.time + 100; + } + else if (BotTrace_Duck(bs, bs->goalPosition)) + { + bs->duckTime = level.time + 100; + } +#ifdef BOT_STRAFE_AVOIDANCE + else + { + int strafeAround = BotTrace_Strafe(bs, bs->goalPosition); + + if (strafeAround == STRAFEAROUND_RIGHT) + { + trap_EA_MoveRight(bs->client); + } + else if (strafeAround == STRAFEAROUND_LEFT) + { + trap_EA_MoveLeft(bs->client); + } + } +#endif + } + +#ifndef FORCEJUMP_INSTANTMETHOD + if (bs->forceJumpChargeTime > level.time) + { + bs->jumpTime = 0; + } +#endif + + if (bs->jumpPrep > level.time) + { + bs->forceJumpChargeTime = 0; + } + + if (bs->forceJumpChargeTime > level.time) + { + bs->jumpHoldTime = ((bs->forceJumpChargeTime - level.time)/2) + level.time; + bs->forceJumpChargeTime = 0; + } + + if (bs->jumpHoldTime > level.time) + { + bs->jumpTime = bs->jumpHoldTime; + } + + if (bs->jumpTime > level.time && bs->jDelay < level.time) + { + if (bs->jumpHoldTime > level.time) + { + trap_EA_Jump(bs->client); + if (bs->wpCurrent) + { + if ((bs->wpCurrent->origin[2] - bs->origin[2]) < 64) + { + trap_EA_MoveForward(bs->client); + } + } + else + { + trap_EA_MoveForward(bs->client); + } + if (g_entities[bs->client].client->ps.groundEntityNum == ENTITYNUM_NONE) + { + g_entities[bs->client].client->ps.pm_flags |= PMF_JUMP_HELD; + } + } + else if (!(bs->cur_ps.pm_flags & PMF_JUMP_HELD)) + { + trap_EA_Jump(bs->client); + } + } + + if (bs->duckTime > level.time) + { + trap_EA_Crouch(bs->client); + } + + if ( bs->dangerousObject && bs->dangerousObject->inuse && bs->dangerousObject->health > 0 && + bs->dangerousObject->takedamage && (!bs->frame_Enemy_Vis || !bs->currentEnemy) && + (BotGetWeaponRange(bs) == BWEAPONRANGE_MID || BotGetWeaponRange(bs) == BWEAPONRANGE_LONG) && + bs->cur_ps.weapon != WP_DET_PACK && bs->cur_ps.weapon != WP_TRIP_MINE && + !bs->shootGoal ) + { + float danLen; + + VectorSubtract(bs->dangerousObject->r.currentOrigin, bs->eye, a); + + danLen = VectorLength(a); + + if (danLen > 256) + { + vectoangles(a, a); + VectorCopy(a, bs->goalAngles); + + if (Q_irand(1, 10) < 5) + { + bs->goalAngles[YAW] += Q_irand(0, 3); + bs->goalAngles[PITCH] += Q_irand(0, 3); + } + else + { + bs->goalAngles[YAW] -= Q_irand(0, 3); + bs->goalAngles[PITCH] -= Q_irand(0, 3); + } + + if (InFieldOfVision(bs->viewangles, 30, a) && + EntityVisibleBox(bs->origin, NULL, NULL, bs->dangerousObject->r.currentOrigin, bs->client, bs->dangerousObject->s.number)) + { + bs->doAttack = 1; + } + } + } + + if (PrimFiring(bs) || + AltFiring(bs)) + { + friendInLOF = CheckForFriendInLOF(bs); + + if (friendInLOF) + { + if (PrimFiring(bs)) + { + KeepPrimFromFiring(bs); + } + if (AltFiring(bs)) + { + KeepAltFromFiring(bs); + } + if (useTheForce && forceHostile) + { + useTheForce = 0; + } + + if (!useTheForce && friendInLOF->client) + { //we have a friend here and are not currently using force powers, see if we can help them out + if (friendInLOF->health <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_HEAL]][FP_TEAM_HEAL]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_HEAL; + useTheForce = 1; + forceHostile = 0; + } + else if (friendInLOF->client->ps.fd.forcePower <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_FORCE]][FP_TEAM_FORCE]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_FORCE; + useTheForce = 1; + forceHostile = 0; + } + } + } + } + else if (g_gametype.integer >= GT_TEAM) + { //still check for anyone to help.. + friendInLOF = CheckForFriendInLOF(bs); + + if (!useTheForce && friendInLOF) + { + if (friendInLOF->health <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_HEAL]][FP_TEAM_HEAL]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_HEAL; + useTheForce = 1; + forceHostile = 0; + } + else if (friendInLOF->client->ps.fd.forcePower <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_FORCE]][FP_TEAM_FORCE]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_FORCE; + useTheForce = 1; + forceHostile = 0; + } + } + } + + if (bs->doAttack && bs->cur_ps.weapon == WP_DET_PACK && + bs->cur_ps.hasDetPackPlanted) + { //maybe a bit hackish, but bots only want to plant one of these at any given time to avoid complications + bs->doAttack = 0; + } + + if (bs->doAttack && bs->cur_ps.weapon == WP_SABER && + bs->saberDefending && bs->currentEnemy && bs->currentEnemy->client && + BotWeaponBlockable(bs->currentEnemy->client->ps.weapon) ) + { + bs->doAttack = 0; + } + + if (bs->cur_ps.saberLockTime > level.time) + { + if (rand()%10 < 5) + { + bs->doAttack = 1; + } + else + { + bs->doAttack = 0; + } + } + + if (bs->botChallengingTime > level.time) + { + bs->doAttack = 0; + bs->doAltAttack = 0; + } + + if (bs->cur_ps.weapon == WP_SABER && + bs->cur_ps.saberInFlight && + !bs->cur_ps.saberEntityNum) + { //saber knocked away, keep trying to get it back + bs->doAttack = 1; + bs->doAltAttack = 0; + } + + if (bs->doAttack) + { + trap_EA_Attack(bs->client); + } + else if (bs->doAltAttack) + { + trap_EA_Alt_Attack(bs->client); + } + + if (useTheForce && forceHostile && bs->botChallengingTime > level.time) + { + useTheForce = qfalse; + } + + if (useTheForce) + { +#ifndef FORCEJUMP_INSTANTMETHOD + if (bs->forceJumpChargeTime > level.time) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_LEVITATION; + trap_EA_ForcePower(bs->client); + } + else + { +#endif + if (bot_forcepowers.integer && !g_forcePowerDisable.integer) + { + trap_EA_ForcePower(bs->client); + } +#ifndef FORCEJUMP_INSTANTMETHOD + } +#endif + } + + MoveTowardIdealAngles(bs); +} + +int gUpdateVars = 0; + +/* +================== +BotAIStartFrame +================== +*/ +static int local_time; +static int lastbotthink_time; + +int BotAIStartFrame(int time) { + int i; + int elapsed_time, thinktime; + static int botlib_residual; + + if (gUpdateVars < level.time) + { + trap_Cvar_Update(&bot_pvstype); + trap_Cvar_Update(&bot_camp); + trap_Cvar_Update(&bot_attachments); + trap_Cvar_Update(&bot_forgimmick); + trap_Cvar_Update(&bot_honorableduelacceptance); +#ifndef FINAL_BUILD + trap_Cvar_Update(&bot_getinthecarrr); +#endif + gUpdateVars = level.time + 1000; + } + + G_CheckBotSpawn(); + + //rww - addl bot frame functions + if (gBotEdit) + { + trap_Cvar_Update(&bot_wp_info); + BotWaypointRender(); + } + + UpdateEventTracker(); + //end rww + + //cap the bot think time + //if the bot think time changed we should reschedule the bots + if (BOT_THINK_TIME != lastbotthink_time) { + lastbotthink_time = BOT_THINK_TIME; + BotScheduleBotThink(); + } + + elapsed_time = time - local_time; + local_time = time; + + if (elapsed_time > BOT_THINK_TIME) thinktime = elapsed_time; + else thinktime = BOT_THINK_TIME; + + // execute scheduled bot AI + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + // + botstates[i]->botthink_residual += elapsed_time; + // + if ( botstates[i]->botthink_residual >= thinktime ) { + botstates[i]->botthink_residual -= thinktime; + + if (g_entities[i].client->pers.connected == CON_CONNECTED) { + BotAI(i, (float) thinktime / 1000); + } + } + } + + // execute bot user commands every frame + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + if( g_entities[i].client->pers.connected != CON_CONNECTED ) { + continue; + } + + BotUpdateInput(botstates[i], time, elapsed_time); + trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + + return qtrue; +} + +/* +============== +BotAISetup +============== +*/ +int BotAISetup( int restart ) { + //rww - new bot cvars.. + trap_Cvar_Register(&bot_forcepowers, "bot_forcepowers", "1", CVAR_CHEAT); + trap_Cvar_Register(&bot_forgimmick, "bot_forgimmick", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_honorableduelacceptance, "bot_honorableduelacceptance", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_pvstype, "bot_pvstype", "1", CVAR_CHEAT); +#ifndef FINAL_BUILD + trap_Cvar_Register(&bot_getinthecarrr, "bot_getinthecarrr", "0", 0); +#endif + +#ifdef _DEBUG + trap_Cvar_Register(&bot_nogoals, "bot_nogoals", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_debugmessages, "bot_debugmessages", "0", CVAR_CHEAT); +#endif + + trap_Cvar_Register(&bot_attachments, "bot_attachments", "1", 0); + trap_Cvar_Register(&bot_camp, "bot_camp", "1", 0); + + trap_Cvar_Register(&bot_wp_info, "bot_wp_info", "1", 0); + trap_Cvar_Register(&bot_wp_edit, "bot_wp_edit", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_wp_clearweight, "bot_wp_clearweight", "1", 0); + trap_Cvar_Register(&bot_wp_distconnect, "bot_wp_distconnect", "1", 0); + trap_Cvar_Register(&bot_wp_visconnect, "bot_wp_visconnect", "1", 0); + + trap_Cvar_Update(&bot_forcepowers); + //end rww + + eFlagRed = NULL; + eFlagBlue = NULL; + droppedRedFlag = NULL; + droppedBlueFlag = NULL; + + //if the game is restarted for a tournament + if (restart) { + return qtrue; + } + + //initialize the bot states + memset( botstates, 0, sizeof(botstates) ); + + if (!trap_BotLibSetup()) + { + return qfalse; //wts?! + } + + return qtrue; +} + +/* +============== +BotAIShutdown +============== +*/ +int BotAIShutdown( int restart ) { + + int i; + + //if the game is restarted for a tournament + if ( restart ) { + //shutdown all the bots in the botlib + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotAIShutdownClient(botstates[i]->client, restart); + } + } + //don't shutdown the bot library + } + else { + trap_BotLibShutdown(); + } + + eFlagRed = NULL; + eFlagBlue = NULL; + droppedRedFlag = NULL; + droppedBlueFlag = NULL; + local_time = 0; + lastbotthink_time = 0; + + return qtrue; +} + diff --git a/codemp/game/ai_main.h b/codemp/game/ai_main.h new file mode 100644 index 0000000..5cd722e --- /dev/null +++ b/codemp/game/ai_main.h @@ -0,0 +1,411 @@ +#include "bg_saga.h" + +#define DEFAULT_FORCEPOWERS "5-1-000000000000000000" + +//#define FORCEJUMP_INSTANTMETHOD 1 + +#ifdef _XBOX // No bot has more than 150 bytes of chat right now +#define MAX_CHAT_BUFFER_SIZE 256 +#else +#define MAX_CHAT_BUFFER_SIZE 8192 +#endif +#define MAX_CHAT_LINE_SIZE 128 + +#define TABLE_BRANCH_DISTANCE 32 +#define MAX_NODETABLE_SIZE 16384 + +#define MAX_LOVED_ONES 4 +#define MAX_ATTACHMENT_NAME 64 + +#define MAX_FORCE_INFO_SIZE 2048 + +#define WPFLAG_JUMP 0x00000010 //jump when we hit this +#define WPFLAG_DUCK 0x00000020 //duck while moving around here +#define WPFLAG_NOVIS 0x00000400 //go here for a bit even with no visibility +#define WPFLAG_SNIPEORCAMPSTAND 0x00000800 //a good position to snipe or camp - stand +#define WPFLAG_WAITFORFUNC 0x00001000 //wait for a func brushent under this point before moving here +#define WPFLAG_SNIPEORCAMP 0x00002000 //a good position to snipe or camp - crouch +#define WPFLAG_ONEWAY_FWD 0x00004000 //can only go forward on the trial from here (e.g. went over a ledge) +#define WPFLAG_ONEWAY_BACK 0x00008000 //can only go backward on the trail from here +#define WPFLAG_GOALPOINT 0x00010000 //make it a goal to get here.. goal points will be decided by setting "weight" values +#define WPFLAG_RED_FLAG 0x00020000 //red flag +#define WPFLAG_BLUE_FLAG 0x00040000 //blue flag +#define WPFLAG_SIEGE_REBELOBJ 0x00080000 //rebel siege objective +#define WPFLAG_SIEGE_IMPERIALOBJ 0x00100000 //imperial siege objective +#define WPFLAG_NOMOVEFUNC 0x00200000 //don't move over if a func is under + +#define WPFLAG_CALCULATED 0x00400000 //don't calculate it again +#define WPFLAG_NEVERONEWAY 0x00800000 //never flag it as one-way + +#define LEVELFLAG_NOPOINTPREDICTION 1 //don't take waypoint beyond current into account when adjusting path view angles +#define LEVELFLAG_IGNOREINFALLBACK 2 //ignore enemies when in a fallback navigation routine +#define LEVELFLAG_IMUSTNTRUNAWAY 4 //don't be scared + +#define WP_KEEP_FLAG_DIST 128 + +#define BWEAPONRANGE_MELEE 1 +#define BWEAPONRANGE_MID 2 +#define BWEAPONRANGE_LONG 3 +#define BWEAPONRANGE_SABER 4 + +#define MELEE_ATTACK_RANGE 256 +#define SABER_ATTACK_RANGE 128 +#define MAX_CHICKENWUSS_TIME 10000 //wait 10 secs between checking which run-away path to take + +#define BOT_RUN_HEALTH 40 +#define BOT_WPTOUCH_DISTANCE 32 +#define ENEMY_FORGET_MS 10000 +//if our enemy isn't visible within 10000ms (aprx 10sec) then "forget" about him and treat him like every other threat, but still look for +//more immediate threats while main enemy is not visible + +#define BOT_PLANT_DISTANCE 256 //plant if within this radius from the last spotted enemy position +#define BOT_PLANT_INTERVAL 15000 //only plant once per 15 seconds at max +#define BOT_PLANT_BLOW_DISTANCE 256 //blow det packs if enemy is within this radius and I am further away than the enemy + +#define BOT_MAX_WEAPON_GATHER_TIME 1000 //spend a max of 1 second after spawn issuing orders to gather weapons before attacking enemy base +#define BOT_MAX_WEAPON_CHASE_TIME 15000 //time to spend gathering the weapon before persuing the enemy base (in case it takes longer than expected) + +#define BOT_MAX_WEAPON_CHASE_CTF 5000 //time to spend gathering the weapon before persuing the enemy base (in case it takes longer than expected) [ctf-only] + +#define BOT_MIN_SIEGE_GOAL_SHOOT 1024 +#define BOT_MIN_SIEGE_GOAL_TRAVEL 128 + +#define BASE_GUARD_DISTANCE 256 //guarding the flag +#define BASE_FLAGWAIT_DISTANCE 256 //has the enemy flag and is waiting in his own base for his flag to be returned +#define BASE_GETENEMYFLAG_DISTANCE 256 //waiting around to get the enemy's flag + +#define BOT_FLAG_GET_DISTANCE 256 + +#define BOT_SABER_THROW_RANGE 800 + +typedef enum +{ + CTFSTATE_NONE, + CTFSTATE_ATTACKER, + CTFSTATE_DEFENDER, + CTFSTATE_RETRIEVAL, + CTFSTATE_GUARDCARRIER, + CTFSTATE_GETFLAGHOME, + CTFSTATE_MAXCTFSTATES +} bot_ctf_state_t; + +typedef enum +{ + SIEGESTATE_NONE, + SIEGESTATE_ATTACKER, + SIEGESTATE_DEFENDER, + SIEGESTATE_MAXSIEGESTATES +} bot_siege_state_t; + +typedef enum +{ + TEAMPLAYSTATE_NONE, + TEAMPLAYSTATE_FOLLOWING, + TEAMPLAYSTATE_ASSISTING, + TEAMPLAYSTATE_REGROUP, + TEAMPLAYSTATE_MAXTPSTATES +} bot_teamplay_state_t; + +typedef struct botattachment_s +{ + int level; + char name[MAX_ATTACHMENT_NAME]; +} botattachment_t; + +typedef struct nodeobject_s +{ + vec3_t origin; +// int index; + float weight; + int flags; +#ifdef _XBOX + short neighbornum; + short inuse; +#else + int neighbornum; + int inuse; +#endif +} nodeobject_t; + +typedef struct boteventtracker_s +{ + int eventSequence; + int events[MAX_PS_EVENTS]; + float eventTime; +} boteventtracker_t; + +typedef struct botskills_s +{ + int reflex; + float accuracy; + float turnspeed; + float turnspeed_combat; + float maxturn; + int perfectaim; +} botskills_t; + +//bot state +typedef struct bot_state_s +{ + int inuse; //true if this state is used by a bot client + int botthink_residual; //residual for the bot thinks + int client; //client number of the bot + int entitynum; //entity number of the bot + playerState_t cur_ps; //current player state + usercmd_t lastucmd; //usercmd from last frame + bot_settings_t settings; //several bot settings + float thinktime; //time the bot thinks this frame + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t eye; //eye coordinates of the bot + int setupcount; //true when the bot has just been setup + float ltime; //local bot time + float entergame_time; //time the bot entered the game + int ms; //move state of the bot + int gs; //goal state of the bot + int ws; //weapon state of the bot + vec3_t viewangles; //current view angles + vec3_t ideal_viewangles; //ideal view angles + vec3_t viewanglespeed; + + //rww - new AI values + gentity_t *currentEnemy; + gentity_t *revengeEnemy; + + gentity_t *squadLeader; + + gentity_t *lastHurt; + gentity_t *lastAttacked; + + gentity_t *wantFlag; + + gentity_t *touchGoal; + gentity_t *shootGoal; + + gentity_t *dangerousObject; + + vec3_t staticFlagSpot; + + int revengeHateLevel; + int isSquadLeader; + + int squadRegroupInterval; + int squadCannotLead; + + int lastDeadTime; + + wpobject_t *wpCurrent; + wpobject_t *wpDestination; + wpobject_t *wpStoreDest; + vec3_t goalAngles; + vec3_t goalMovedir; + vec3_t goalPosition; + + vec3_t lastEnemySpotted; + vec3_t hereWhenSpotted; + int lastVisibleEnemyIndex; + int hitSpotted; + + int wpDirection; + + float destinationGrabTime; + float wpSeenTime; + float wpTravelTime; + float wpDestSwitchTime; + float wpSwitchTime; + float wpDestIgnoreTime; + + float timeToReact; + + float enemySeenTime; + + float chickenWussCalculationTime; + + float beStill; + float duckTime; + float jumpTime; + float jumpHoldTime; + float jumpPrep; + float forceJumping; + float jDelay; + + float aimOffsetTime; + float aimOffsetAmtYaw; + float aimOffsetAmtPitch; + + float frame_Waypoint_Len; + int frame_Waypoint_Vis; + float frame_Enemy_Len; + int frame_Enemy_Vis; + + int isCamper; + float isCamping; + wpobject_t *wpCamping; + wpobject_t *wpCampingTo; + qboolean campStanding; + + int randomNavTime; + int randomNav; + + int saberSpecialist; +/* + int canChat; + int chatFrequency; + char currentChat[MAX_CHAT_LINE_SIZE]; + float chatTime; + float chatTime_stored; + int doChat; + int chatTeam; + gentity_t *chatObject; + gentity_t *chatAltObject; +*/ + float meleeStrafeTime; + int meleeStrafeDir; + float meleeStrafeDisable; + + int altChargeTime; + + float escapeDirTime; + + float dontGoBack; + + int doAttack; + int doAltAttack; + + int forceWeaponSelect; + int virtualWeapon; + + int plantTime; + int plantDecided; + int plantContinue; + int plantKillEmAll; + + int runningLikeASissy; + int runningToEscapeThreat; + + //char chatBuffer[MAX_CHAT_BUFFER_SIZE]; + //Since we're once again not allocating bot structs dynamically, + //shoving a 64k chat buffer into one is a bad thing. + + botskills_t skills; + + botattachment_t loved[MAX_LOVED_ONES]; + int lovednum; + + int loved_death_thresh; + + int deathActivitiesDone; + + float botWeaponWeights[WP_NUM_WEAPONS]; + + int ctfState; + + int siegeState; + + int teamplayState; + + int jmState; + + int state_Forced; //set by player ordering menu + + int saberDefending; + int saberDefendDecideTime; + int saberBFTime; + int saberBTime; + int saberSTime; + int saberThrowTime; + + qboolean saberPower; + int saberPowerTime; + + int botChallengingTime; + + char forceinfo[MAX_FORCE_INFO_SIZE]; + +#ifndef FORCEJUMP_INSTANTMETHOD + int forceJumpChargeTime; +#endif + + int doForcePush; + + int noUseTime; + qboolean doingFallback; + + int iHaveNoIdeaWhereIAmGoing; + vec3_t lastSignificantAreaChange; + int lastSignificantChangeTime; + + int forceMove_Forward; + int forceMove_Right; + int forceMove_Up; + //end rww +} bot_state_t; + +void *B_TempAlloc(int size); +void B_TempFree(int size); + +void *B_Alloc(int size); +void B_Free(void *ptr); + +//resets the whole bot state +void BotResetState(bot_state_t *bs); +//returns the number of bots in the game +int NumBots(void); + +void BotUtilizePersonality(bot_state_t *bs); +int BotDoChat(bot_state_t *bs, char *section, int always); +void StandardBotAI(bot_state_t *bs, float thinktime); +void BotWaypointRender(void); +int OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore); +int BotIsAChickenWuss(bot_state_t *bs); +int GetNearestVisibleWP(vec3_t org, int ignore); +int GetBestIdleGoal(bot_state_t *bs); + +char *ConcatArgs( int start ); + +extern vmCvar_t bot_forcepowers; +extern vmCvar_t bot_forgimmick; +extern vmCvar_t bot_honorableduelacceptance; +#ifdef _DEBUG +extern vmCvar_t bot_nogoals; +extern vmCvar_t bot_debugmessages; +#endif + +extern vmCvar_t bot_attachments; +extern vmCvar_t bot_camp; + +extern vmCvar_t bot_wp_info; +extern vmCvar_t bot_wp_edit; +extern vmCvar_t bot_wp_clearweight; +extern vmCvar_t bot_wp_distconnect; +extern vmCvar_t bot_wp_visconnect; + +extern wpobject_t *flagRed; +extern wpobject_t *oFlagRed; +extern wpobject_t *flagBlue; +extern wpobject_t *oFlagBlue; + +extern gentity_t *eFlagRed; +extern gentity_t *eFlagBlue; + +//extern char gBotChatBuffer[MAX_CLIENTS][MAX_CHAT_BUFFER_SIZE]; +extern float gWPRenderTime; +extern float gDeactivated; +extern float gBotEdit; +extern int gWPRenderedFrame; + +#include "../namespace_begin.h" +extern wpobject_t *gWPArray[MAX_WPARRAY_SIZE]; +extern int gWPNum; +#include "../namespace_end.h" + +extern int gLastPrintedIndex; +#ifndef _XBOX +extern nodeobject_t nodetable[MAX_NODETABLE_SIZE]; +#endif +extern int nodenum; + +extern int gLevelFlags; + +extern float floattime; +#define FloatTime() floattime diff --git a/codemp/game/ai_util.c b/codemp/game/ai_util.c new file mode 100644 index 0000000..eada1cd --- /dev/null +++ b/codemp/game/ai_util.c @@ -0,0 +1,862 @@ +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" +#include "ai_main.h" + +#ifdef BOT_ZMALLOC +#define MAX_BALLOC 8192 + +void *BAllocList[MAX_BALLOC]; +#endif + +//char gBotChatBuffer[MAX_CLIENTS][MAX_CHAT_BUFFER_SIZE]; + +void *B_TempAlloc(int size) +{ + return BG_TempAlloc(size); +} + +void B_TempFree(int size) +{ + BG_TempFree(size); +} + + +void *B_Alloc(int size) +{ +#ifdef BOT_ZMALLOC + void *ptr = NULL; + int i = 0; + +#ifdef BOTMEMTRACK + int free = 0; + int used = 0; + + while (i < MAX_BALLOC) + { + if (!BAllocList[i]) + { + free++; + } + else + { + used++; + } + + i++; + } + + G_Printf("Allocations used: %i\nFree allocation slots: %i\n", used, free); + + i = 0; +#endif + + ptr = trap_BotGetMemoryGame(size); + + while (i < MAX_BALLOC) + { + if (!BAllocList[i]) + { + BAllocList[i] = ptr; + break; + } + i++; + } + + if (i == MAX_BALLOC) + { + //If this happens we'll have to rely on this chunk being freed manually with B_Free, which it hopefully will be +#ifdef DEBUG + G_Printf("WARNING: MAXIMUM B_ALLOC ALLOCATIONS EXCEEDED\n"); +#endif + } + + return ptr; +#else + + return BG_Alloc(size); + +#endif +} + +void B_Free(void *ptr) +{ +#ifdef BOT_ZMALLOC + int i = 0; + +#ifdef BOTMEMTRACK + int free = 0; + int used = 0; + + while (i < MAX_BALLOC) + { + if (!BAllocList[i]) + { + free++; + } + else + { + used++; + } + + i++; + } + + G_Printf("Allocations used: %i\nFree allocation slots: %i\n", used, free); + + i = 0; +#endif + + while (i < MAX_BALLOC) + { + if (BAllocList[i] == ptr) + { + BAllocList[i] = NULL; + break; + } + + i++; + } + + if (i == MAX_BALLOC) + { + //Likely because the limit was exceeded and we're now freeing the chunk manually as we hoped would happen +#ifdef DEBUG + G_Printf("WARNING: Freeing allocation which is not in the allocation structure\n"); +#endif + } + + trap_BotFreeMemoryGame(ptr); +#endif +} + +void B_InitAlloc(void) +{ +#ifdef BOT_ZMALLOC + memset(BAllocList, 0, sizeof(BAllocList)); +#endif + + memset(gWPArray, 0, sizeof(gWPArray)); +} + +void B_CleanupAlloc(void) +{ + for(int i=0; icanChat) + { + return 0; + } + + if (bs->doChat) + { //already have a chat scheduled + return 0; + } + + if (trap_Cvar_VariableIntegerValue("se_language")) + { //no chatting unless English. + return 0; + } + + if (Q_irand(1, 10) > bs->chatFrequency && !always) + { + return 0; + } + + bs->chatTeam = 0; + + chatgroup = (char *)B_TempAlloc(MAX_CHAT_BUFFER_SIZE); + + rVal = GetValueGroup(gBotChatBuffer[bs->client], section, chatgroup); + + if (!rVal) //the bot has no group defined for the specified chat event + { + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + return 0; + } + + inc_1 = 0; + inc_2 = 2; + + while (chatgroup[inc_2] && chatgroup[inc_2] != '\0') + { + if (chatgroup[inc_2] != 13 && chatgroup[inc_2] != 9) + { + chatgroup[inc_1] = chatgroup[inc_2]; + inc_1++; + } + inc_2++; + } + chatgroup[inc_1] = '\0'; + + inc_1 = 0; + + lines = 0; + + while (chatgroup[inc_1] && chatgroup[inc_1] != '\0') + { + if (chatgroup[inc_1] == '\n') + { + lines++; + } + inc_1++; + } + + if (!lines) + { + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + return 0; + } + + getthisline = Q_irand(0, (lines+1)); + + if (getthisline < 1) + { + getthisline = 1; + } + if (getthisline > lines) + { + getthisline = lines; + } + + checkedline = 1; + + inc_1 = 0; + + while (checkedline != getthisline) + { + if (chatgroup[inc_1] && chatgroup[inc_1] != '\0') + { + if (chatgroup[inc_1] == '\n') + { + inc_1++; + checkedline++; + } + } + + if (checkedline == getthisline) + { + break; + } + + inc_1++; + } + + //we're at the starting position of the desired line here + inc_2 = 0; + + while (chatgroup[inc_1] != '\n') + { + chatgroup[inc_2] = chatgroup[inc_1]; + inc_2++; + inc_1++; + } + chatgroup[inc_2] = '\0'; + + //trap_EA_Say(bs->client, chatgroup); + inc_1 = 0; + inc_2 = 0; + + if (strlen(chatgroup) > MAX_CHAT_LINE_SIZE) + { + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + return 0; + } + + while (chatgroup[inc_1]) + { + if (chatgroup[inc_1] == '%' && chatgroup[inc_1+1] != '%') + { + inc_1++; + + if (chatgroup[inc_1] == 's' && bs->chatObject) + { + cobject = bs->chatObject; + } + else if (chatgroup[inc_1] == 'a' && bs->chatAltObject) + { + cobject = bs->chatAltObject; + } + else + { + cobject = NULL; + } + + if (cobject && cobject->client) + { + inc_n = 0; + + while (cobject->client->pers.netname[inc_n]) + { + bs->currentChat[inc_2] = cobject->client->pers.netname[inc_n]; + inc_2++; + inc_n++; + } + inc_2--; //to make up for the auto-increment below + } + } + else + { + bs->currentChat[inc_2] = chatgroup[inc_1]; + } + inc_2++; + inc_1++; + } + bs->currentChat[inc_2] = '\0'; + + if (strcmp(section, "GeneralGreetings") == 0) + { + bs->doChat = 2; + } + else + { + bs->doChat = 1; + } + bs->chatTime_stored = (strlen(bs->currentChat)*45)+Q_irand(1300, 1500); + bs->chatTime = level.time + bs->chatTime_stored; + + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + + return 1; +} +*/ + +void ParseEmotionalAttachments(bot_state_t *bs, char *buf) +{ + int i = 0; + int i_c = 0; + char tbuf[16]; + + while (buf[i] && buf[i] != '}') + { + while (buf[i] == ' ' || buf[i] == '{' || buf[i] == 9 || buf[i] == 13 || buf[i] == '\n') + { + i++; + } + + if (buf[i] && buf[i] != '}') + { + i_c = 0; + while (buf[i] != '{' && buf[i] != 9 && buf[i] != 13 && buf[i] != '\n') + { + bs->loved[bs->lovednum].name[i_c] = buf[i]; + i_c++; + i++; + } + bs->loved[bs->lovednum].name[i_c] = '\0'; + + while (buf[i] == ' ' || buf[i] == '{' || buf[i] == 9 || buf[i] == 13 || buf[i] == '\n') + { + i++; + } + + i_c = 0; + + while (buf[i] != '{' && buf[i] != 9 && buf[i] != 13 && buf[i] != '\n') + { + tbuf[i_c] = buf[i]; + i_c++; + i++; + } + tbuf[i_c] = '\0'; + + bs->loved[bs->lovednum].level = atoi(tbuf); + + bs->lovednum++; + } + else + { + break; + } + + if (bs->lovednum >= MAX_LOVED_ONES) + { + return; + } + + i++; + } +} + +/* +int ReadChatGroups(bot_state_t *bs, char *buf) +{ + char *cgroupbegin; + int cgbplace; + int i; + + cgroupbegin = strstr(buf, "BEGIN_CHAT_GROUPS"); + + if (!cgroupbegin) + { + return 0; + } + + if (strlen(cgroupbegin) >= MAX_CHAT_BUFFER_SIZE) + { + G_Printf(S_COLOR_RED "Error: Personality chat section exceeds max size\n"); + return 0; + } + + cgbplace = cgroupbegin - buf+1; + + while (buf[cgbplace] != '\n') + { + cgbplace++; + } + + i = 0; + + while (buf[cgbplace] && buf[cgbplace] != '\0') + { + gBotChatBuffer[bs->client][i] = buf[cgbplace]; + i++; + cgbplace++; + } + + gBotChatBuffer[bs->client][i] = '\0'; + + return 1; +} +*/ + +void BotUtilizePersonality(bot_state_t *bs) +{ + fileHandle_t f; + int len, rlen; + int failed; + int i; + //char buf[131072]; + char *readbuf, *group; + + len = trap_FS_FOpenFile(bs->settings.personalityfile, &f, FS_READ); + + failed = 0; + + if (!f) + { + G_Printf(S_COLOR_RED "Error: Specified personality not found\n"); + return; + } + + char *buf = (char *)B_TempAlloc(len + 1); + trap_FS_Read(buf, len, f); + + buf[len] = 0; + + readbuf = (char *)B_TempAlloc(1024); + group = (char *)B_TempAlloc(len); + + if (!GetValueGroup(buf, "GeneralBotInfo", group)) + { + G_Printf(S_COLOR_RED "Personality file contains no GeneralBotInfo group\n"); + failed = 1; //set failed so we know to set everything to default values + } + + if (!failed && GetPairedValue(group, "reflex", readbuf)) + { + bs->skills.reflex = atoi(readbuf); + } + else + { + bs->skills.reflex = 100; //default + } + + if (!failed && GetPairedValue(group, "accuracy", readbuf)) + { + bs->skills.accuracy = atof(readbuf); + } + else + { + bs->skills.accuracy = 10; //default + } + + if (!failed && GetPairedValue(group, "turnspeed", readbuf)) + { + bs->skills.turnspeed = atof(readbuf); + } + else + { + bs->skills.turnspeed = 0.01f; //default + } + + if (!failed && GetPairedValue(group, "turnspeed_combat", readbuf)) + { + bs->skills.turnspeed_combat = atof(readbuf); + } + else + { + bs->skills.turnspeed_combat = 0.05f; //default + } + + if (!failed && GetPairedValue(group, "maxturn", readbuf)) + { + bs->skills.maxturn = atof(readbuf); + } + else + { + bs->skills.maxturn = 360; //default + } + + if (!failed && GetPairedValue(group, "perfectaim", readbuf)) + { + bs->skills.perfectaim = atoi(readbuf); + } + else + { + bs->skills.perfectaim = 0; //default + } +/* + if (!failed && GetPairedValue(group, "chatability", readbuf)) + { + bs->canChat = atoi(readbuf); + } + else + { + bs->canChat = 0; //default + } + + if (!failed && GetPairedValue(group, "chatfrequency", readbuf)) + { + bs->chatFrequency = atoi(readbuf); + } + else + { + bs->chatFrequency = 5; //default + } +*/ + if (!failed && GetPairedValue(group, "hatelevel", readbuf)) + { + bs->loved_death_thresh = atoi(readbuf); + } + else + { + bs->loved_death_thresh = 3; //default + } + + if (!failed && GetPairedValue(group, "camper", readbuf)) + { + bs->isCamper = atoi(readbuf); + } + else + { + bs->isCamper = 0; //default + } + + if (!failed && GetPairedValue(group, "saberspecialist", readbuf)) + { + bs->saberSpecialist = atoi(readbuf); + } + else + { + bs->saberSpecialist = 0; //default + } + + if (!failed && GetPairedValue(group, "forceinfo", readbuf)) + { + Com_sprintf(bs->forceinfo, sizeof(bs->forceinfo), "%s\0", readbuf); + } + else + { + Com_sprintf(bs->forceinfo, sizeof(bs->forceinfo), "%s\0", DEFAULT_FORCEPOWERS); + } + + i = 0; + +/* + while (i < MAX_CHAT_BUFFER_SIZE) + { //clear out the chat buffer for this bot + gBotChatBuffer[bs->client][i] = '\0'; + i++; + } + + if (bs->canChat) + { + if (!ReadChatGroups(bs, buf)) + { + bs->canChat = 0; + } + } +*/ + if (GetValueGroup(buf, "BotWeaponWeights", group)) + { + if (GetPairedValue(group, "WP_STUN_BATON", readbuf)) + { + bs->botWeaponWeights[WP_STUN_BATON] = atoi(readbuf); + bs->botWeaponWeights[WP_MELEE] = bs->botWeaponWeights[WP_STUN_BATON]; + } + + if (GetPairedValue(group, "WP_SABER", readbuf)) + { + bs->botWeaponWeights[WP_SABER] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_BRYAR_PISTOL", readbuf)) + { + bs->botWeaponWeights[WP_BRYAR_PISTOL] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_BLASTER", readbuf)) + { + bs->botWeaponWeights[WP_BLASTER] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_DISRUPTOR", readbuf)) + { + bs->botWeaponWeights[WP_DISRUPTOR] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_BOWCASTER", readbuf)) + { + bs->botWeaponWeights[WP_BOWCASTER] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_REPEATER", readbuf)) + { + bs->botWeaponWeights[WP_REPEATER] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_DEMP2", readbuf)) + { + bs->botWeaponWeights[WP_DEMP2] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_FLECHETTE", readbuf)) + { + bs->botWeaponWeights[WP_FLECHETTE] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_ROCKET_LAUNCHER", readbuf)) + { + bs->botWeaponWeights[WP_ROCKET_LAUNCHER] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_THERMAL", readbuf)) + { + bs->botWeaponWeights[WP_THERMAL] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_TRIP_MINE", readbuf)) + { + bs->botWeaponWeights[WP_TRIP_MINE] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_DET_PACK", readbuf)) + { + bs->botWeaponWeights[WP_DET_PACK] = atoi(readbuf); + } + } + + bs->lovednum = 0; + + if (GetValueGroup(buf, "EmotionalAttachments", group)) + { + ParseEmotionalAttachments(bs, group); + } + + B_TempFree(len + 1); //buf + B_TempFree(1024); //readbuf + B_TempFree(len); //group + trap_FS_FCloseFile(f); +} diff --git a/codemp/game/ai_wpnav.c b/codemp/game/ai_wpnav.c new file mode 100644 index 0000000..29a3f9e --- /dev/null +++ b/codemp/game/ai_wpnav.c @@ -0,0 +1,3813 @@ +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" +#include "ai_main.h" + +float gWPRenderTime = 0; +float gDeactivated = 0; +float gBotEdit = 0; +int gWPRenderedFrame = 0; + +#include "../namespace_begin.h" +wpobject_t *gWPArray[MAX_WPARRAY_SIZE]; +int gWPNum = 0; +#include "../namespace_end.h" + +int gLastPrintedIndex = -1; + +#ifndef _XBOX +nodeobject_t nodetable[MAX_NODETABLE_SIZE]; +int nodenum; //so we can connect broken trails +#endif + +int gLevelFlags = 0; + +char *GetFlagStr( int flags ) +{ + char *flagstr; + int i; + + flagstr = (char *)B_TempAlloc(128); + i = 0; + + if (!flags) + { + strcpy(flagstr, "none\0"); + goto fend; + } + + if (flags & WPFLAG_JUMP) + { + flagstr[i] = 'j'; + i++; + } + + if (flags & WPFLAG_DUCK) + { + flagstr[i] = 'd'; + i++; + } + + if (flags & WPFLAG_SNIPEORCAMPSTAND) + { + flagstr[i] = 'c'; + i++; + } + + if (flags & WPFLAG_WAITFORFUNC) + { + flagstr[i] = 'f'; + i++; + } + + if (flags & WPFLAG_SNIPEORCAMP) + { + flagstr[i] = 's'; + i++; + } + + if (flags & WPFLAG_ONEWAY_FWD) + { + flagstr[i] = 'x'; + i++; + } + + if (flags & WPFLAG_ONEWAY_BACK) + { + flagstr[i] = 'y'; + i++; + } + + if (flags & WPFLAG_GOALPOINT) + { + flagstr[i] = 'g'; + i++; + } + + if (flags & WPFLAG_NOVIS) + { + flagstr[i] = 'n'; + i++; + } + + if (flags & WPFLAG_NOMOVEFUNC) + { + flagstr[i] = 'm'; + i++; + } + + if (flags & WPFLAG_RED_FLAG) + { + if (i) + { + flagstr[i] = ' '; + i++; + } + flagstr[i] = 'r'; + i++; + flagstr[i] = 'e'; + i++; + flagstr[i] = 'd'; + i++; + flagstr[i] = ' '; + i++; + flagstr[i] = 'f'; + i++; + flagstr[i] = 'l'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = 'g'; + i++; + } + + if (flags & WPFLAG_BLUE_FLAG) + { + if (i) + { + flagstr[i] = ' '; + i++; + } + flagstr[i] = 'b'; + i++; + flagstr[i] = 'l'; + i++; + flagstr[i] = 'u'; + i++; + flagstr[i] = 'e'; + i++; + flagstr[i] = ' '; + i++; + flagstr[i] = 'f'; + i++; + flagstr[i] = 'l'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = 'g'; + i++; + } + + if (flags & WPFLAG_SIEGE_IMPERIALOBJ) + { + if (i) + { + flagstr[i] = ' '; + i++; + } + flagstr[i] = 's'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = 'g'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = '_'; + i++; + flagstr[i] = 'i'; + i++; + flagstr[i] = 'm'; + i++; + flagstr[i] = 'p'; + i++; + } + + if (flags & WPFLAG_SIEGE_REBELOBJ) + { + if (i) + { + flagstr[i] = ' '; + i++; + } + flagstr[i] = 's'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = 'g'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = '_'; + i++; + flagstr[i] = 'r'; + i++; + flagstr[i] = 'e'; + i++; + flagstr[i] = 'b'; + i++; + } + + flagstr[i] = '\0'; + + if (i == 0) + { + strcpy(flagstr, "unknown\0"); + } + +fend: + return flagstr; +} + +void G_TestLine(vec3_t start, vec3_t end, int color, int time) +{ + gentity_t *te; + + te = G_TempEntity( start, EV_TESTLINE ); + VectorCopy(start, te->s.origin); + VectorCopy(end, te->s.origin2); + te->s.time2 = time; + te->s.weapon = color; + te->r.svFlags |= SVF_BROADCAST; +} + +void BotWaypointRender(void) +{ + int i, n; + int inc_checker; + int bestindex; + int gotbestindex; + float bestdist; + float checkdist; + gentity_t *plum; + gentity_t *viewent; + char *flagstr; + vec3_t a; + + if (!gBotEdit) + { + return; + } + + bestindex = 0; + + if (gWPRenderTime > level.time) + { + goto checkprint; + } + + gWPRenderTime = level.time + 100; + + i = gWPRenderedFrame; + inc_checker = gWPRenderedFrame; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + plum = G_TempEntity( gWPArray[i]->origin, EV_SCOREPLUM ); + plum->r.svFlags |= SVF_BROADCAST; + plum->s.time = i; + + n = 0; + + while (n < gWPArray[i]->neighbornum) + { + if (gWPArray[i]->neighbors[n].forceJumpTo && gWPArray[gWPArray[i]->neighbors[n].num]) + { + G_TestLine(gWPArray[i]->origin, gWPArray[gWPArray[i]->neighbors[n].num]->origin, 0x0000ff, 5000); + } + n++; + } + + gWPRenderedFrame++; + } + else + { + gWPRenderedFrame = 0; + break; + } + + if ((i - inc_checker) > 4) + { + break; //don't render too many at once + } + i++; + } + + if (i >= gWPNum) + { + gWPRenderTime = level.time + 1500; //wait a bit after we finish doing the whole trail + gWPRenderedFrame = 0; + } + +checkprint: + + if (!bot_wp_info.value) + { + return; + } + + viewent = &g_entities[0]; //only show info to the first client + + if (!viewent || !viewent->client) + { //client isn't in the game yet? + return; + } + + bestdist = 256; //max distance for showing point info + gotbestindex = 0; + + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(viewent->client->ps.origin, gWPArray[i]->origin, a); + + checkdist = VectorLength(a); + + if (checkdist < bestdist) + { + bestdist = checkdist; + bestindex = i; + gotbestindex = 1; + } + } + i++; + } + + if (gotbestindex && bestindex != gLastPrintedIndex) + { + flagstr = GetFlagStr(gWPArray[bestindex]->flags); + gLastPrintedIndex = bestindex; + G_Printf(S_COLOR_YELLOW "Waypoint %i\nFlags - %i (%s) (w%f)\nOrigin - (%i %i %i)\n", (int)(gWPArray[bestindex]->index), (int)(gWPArray[bestindex]->flags), flagstr, gWPArray[bestindex]->weight, (int)(gWPArray[bestindex]->origin[0]), (int)(gWPArray[bestindex]->origin[1]), (int)(gWPArray[bestindex]->origin[2])); + //GetFlagStr allocates 128 bytes for this, if it's changed then obviously this must be as well + B_TempFree(128); //flagstr + + plum = G_TempEntity( gWPArray[bestindex]->origin, EV_SCOREPLUM ); + plum->r.svFlags |= SVF_BROADCAST; + plum->s.time = bestindex; //render it once + } + else if (!gotbestindex) + { + gLastPrintedIndex = -1; + } +} + +void TransferWPData(int from, int to) +{ + if (!gWPArray[to]) + { + gWPArray[to] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + if (!gWPArray[to]) + { + G_Printf(S_COLOR_RED "FATAL ERROR: Could not allocated memory for waypoint\n"); + } + + gWPArray[to]->flags = gWPArray[from]->flags; + gWPArray[to]->weight = gWPArray[from]->weight; + gWPArray[to]->associated_entity = gWPArray[from]->associated_entity; + gWPArray[to]->disttonext = gWPArray[from]->disttonext; + gWPArray[to]->forceJumpTo = gWPArray[from]->forceJumpTo; + gWPArray[to]->index = to; + gWPArray[to]->inuse = gWPArray[from]->inuse; + VectorCopy(gWPArray[from]->origin, gWPArray[to]->origin); +} + +void CreateNewWP(vec3_t origin, int flags) +{ + if (gWPNum >= MAX_WPARRAY_SIZE) + { + if (!g_RMG.integer) + { + G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); + } + return; + } + + if (!gWPArray[gWPNum]) + { + gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + if (!gWPArray[gWPNum]) + { + G_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n"); + } + + gWPArray[gWPNum]->flags = flags; + gWPArray[gWPNum]->weight = 0; //calculated elsewhere + gWPArray[gWPNum]->associated_entity = ENTITYNUM_NONE; //set elsewhere + gWPArray[gWPNum]->forceJumpTo = 0; + gWPArray[gWPNum]->disttonext = 0; //calculated elsewhere + gWPArray[gWPNum]->index = gWPNum; + gWPArray[gWPNum]->inuse = 1; + VectorCopy(origin, gWPArray[gWPNum]->origin); + gWPNum++; +} + +void CreateNewWP_FromObject(wpobject_t *wp) +{ + int i; + + if (gWPNum >= MAX_WPARRAY_SIZE) + { + return; + } + + if (!gWPArray[gWPNum]) + { + gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + if (!gWPArray[gWPNum]) + { + G_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n"); + } + + gWPArray[gWPNum]->flags = wp->flags; + gWPArray[gWPNum]->weight = wp->weight; + gWPArray[gWPNum]->associated_entity = wp->associated_entity; + gWPArray[gWPNum]->disttonext = wp->disttonext; + gWPArray[gWPNum]->forceJumpTo = wp->forceJumpTo; + gWPArray[gWPNum]->index = gWPNum; + gWPArray[gWPNum]->inuse = 1; + VectorCopy(wp->origin, gWPArray[gWPNum]->origin); + gWPArray[gWPNum]->neighbornum = wp->neighbornum; + + i = wp->neighbornum; + + while (i >= 0) + { + gWPArray[gWPNum]->neighbors[i].num = wp->neighbors[i].num; + gWPArray[gWPNum]->neighbors[i].forceJumpTo = wp->neighbors[i].forceJumpTo; + + i--; + } + + if (gWPArray[gWPNum]->flags & WPFLAG_RED_FLAG) + { + flagRed = gWPArray[gWPNum]; + oFlagRed = flagRed; + } + else if (gWPArray[gWPNum]->flags & WPFLAG_BLUE_FLAG) + { + flagBlue = gWPArray[gWPNum]; + oFlagBlue = flagBlue; + } + + gWPNum++; +} + +void RemoveWP(void) +{ + if (gWPNum <= 0) + { + return; + } + + gWPNum--; + + if (!gWPArray[gWPNum] || !gWPArray[gWPNum]->inuse) + { + return; + } + + //B_Free((wpobject_t *)gWPArray[gWPNum]); + if (gWPArray[gWPNum]) + { + memset( gWPArray[gWPNum], 0, sizeof(gWPArray[gWPNum]) ); + } + + //gWPArray[gWPNum] = NULL; + + if (gWPArray[gWPNum]) + { + gWPArray[gWPNum]->inuse = 0; + } +} + +void RemoveAllWP(void) +{ + while(gWPNum) { + RemoveWP(); + } +} + +void RemoveWP_InTrail(int afterindex) +{ + int foundindex; + int foundanindex; + int didchange; + int i; + + foundindex = 0; + foundanindex = 0; + didchange = 0; + i = 0; + + if (afterindex < 0 || afterindex >= gWPNum) + { + G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return; + } + + i = 0; + + while (i <= gWPNum) + { + if (gWPArray[i] && gWPArray[i]->index == foundindex) + { + //B_Free(gWPArray[i]); + + //Keep reusing the memory + memset( gWPArray[i], 0, sizeof(gWPArray[i]) ); + + //gWPArray[i] = NULL; + gWPArray[i]->inuse = 0; + didchange = 1; + } + else if (gWPArray[i] && didchange) + { + TransferWPData(i, i-1); + //B_Free(gWPArray[i]); + + //Keep reusing the memory + memset( gWPArray[i], 0, sizeof(gWPArray[i]) ); + + //gWPArray[i] = NULL; + gWPArray[i]->inuse = 0; + } + + i++; + } + gWPNum--; +} + +int CreateNewWP_InTrail(vec3_t origin, int flags, int afterindex) +{ + int foundindex; + int foundanindex; + int i; + + foundindex = 0; + foundanindex = 0; + i = 0; + + if (gWPNum >= MAX_WPARRAY_SIZE) + { + if (!g_RMG.integer) + { + G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); + } + return 0; + } + + if (afterindex < 0 || afterindex >= gWPNum) + { + G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return 0; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return 0; + } + + i = gWPNum; + + while (i >= 0) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex) + { + TransferWPData(i, i+1); + } + else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex) + { + i++; + + if (!gWPArray[i]) + { + gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + gWPArray[i]->flags = flags; + gWPArray[i]->weight = 0; //calculated elsewhere + gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere + gWPArray[i]->disttonext = 0; //calculated elsewhere + gWPArray[i]->forceJumpTo = 0; + gWPArray[i]->index = i; + gWPArray[i]->inuse = 1; + VectorCopy(origin, gWPArray[i]->origin); + gWPNum++; + break; + } + + i--; + } + + return 1; +} + +int CreateNewWP_InsertUnder(vec3_t origin, int flags, int afterindex) +{ + int foundindex; + int foundanindex; + int i; + + foundindex = 0; + foundanindex = 0; + i = 0; + + if (gWPNum >= MAX_WPARRAY_SIZE) + { + if (!g_RMG.integer) + { + G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); + } + return 0; + } + + if (afterindex < 0 || afterindex >= gWPNum) + { + G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return 0; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return 0; + } + + i = gWPNum; + + while (i >= 0) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex) + { + TransferWPData(i, i+1); + } + else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex) + { + //i++; + TransferWPData(i, i+1); + + if (!gWPArray[i]) + { + gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + gWPArray[i]->flags = flags; + gWPArray[i]->weight = 0; //calculated elsewhere + gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere + gWPArray[i]->disttonext = 0; //calculated elsewhere + gWPArray[i]->forceJumpTo = 0; + gWPArray[i]->index = i; + gWPArray[i]->inuse = 1; + VectorCopy(origin, gWPArray[i]->origin); + gWPNum++; + break; + } + + i--; + } + + return 1; +} + +void TeleportToWP(gentity_t *pl, int afterindex) +{ + int foundindex; + int foundanindex; + int i; + + if (!pl || !pl->client) + { + return; + } + + foundindex = 0; + foundanindex = 0; + i = 0; + + if (afterindex < 0 || afterindex >= gWPNum) + { + G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return; + } + + VectorCopy(gWPArray[foundindex]->origin, pl->client->ps.origin); + + return; +} + +void WPFlagsModify(int wpnum, int flags) +{ + if (wpnum < 0 || wpnum >= gWPNum || !gWPArray[wpnum] || !gWPArray[wpnum]->inuse) + { + G_Printf(S_COLOR_YELLOW "WPFlagsModify: Waypoint %i does not exist\n", wpnum); + return; + } + + gWPArray[wpnum]->flags = flags; +} + +static int NotWithinRange(int base, int extent) +{ + if (extent > base && base+5 >= extent) + { + return 0; + } + + if (extent < base && base-5 <= extent) + { + return 0; + } + + return 1; +} + +#ifndef _XBOX +int NodeHere(vec3_t spot) +{ + int i; + + i = 0; + + while (i < nodenum) + { + if ((int)nodetable[i].origin[0] == (int)spot[0] && + (int)nodetable[i].origin[1] == (int)spot[1]) + { + if ((int)nodetable[i].origin[2] == (int)spot[2] || + ((int)nodetable[i].origin[2] < (int)spot[2] && (int)nodetable[i].origin[2]+5 > (int)spot[2]) || + ((int)nodetable[i].origin[2] > (int)spot[2] && (int)nodetable[i].origin[2]-5 < (int)spot[2])) + { + return 1; + } + } + i++; + } + + return 0; +} +#endif + +int CanGetToVector(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs) +{ + trace_t tr; + + trap_Trace(&tr, org1, mins, maxs, org2, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + + return 0; +} + +#if 0 +int CanGetToVectorTravel(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs) +{ + trace_t tr; + vec3_t a, ang, fwd; + vec3_t midpos, dmid; + float startheight, midheight, fLen; + + mins[2] = -13; + maxs[2] = 13; + + trap_Trace(&tr, org1, mins, maxs, org2, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.fraction != 1 || tr.startsolid || tr.allsolid) + { + return 0; + } + + VectorSubtract(org2, org1, a); + + vectoangles(a, ang); + + AngleVectors(ang, fwd, NULL, NULL); + + fLen = VectorLength(a)/2; + + midpos[0] = org1[0] + fwd[0]*fLen; + midpos[1] = org1[1] + fwd[1]*fLen; + midpos[2] = org1[2] + fwd[2]*fLen; + + VectorCopy(org1, dmid); + dmid[2] -= 1024; + + trap_Trace(&tr, midpos, NULL, NULL, dmid, ENTITYNUM_NONE, MASK_SOLID); + + startheight = org1[2] - tr.endpos[2]; + + VectorCopy(midpos, dmid); + dmid[2] -= 1024; + + trap_Trace(&tr, midpos, NULL, NULL, dmid, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.startsolid || tr.allsolid) + { + return 1; + } + + midheight = midpos[2] - tr.endpos[2]; + + if (midheight > startheight*2) + { + return 0; //too steep of a drop.. can't go on + } + + return 1; +} +#else +int CanGetToVectorTravel(vec3_t org1, vec3_t moveTo, vec3_t mins, vec3_t maxs) +//int ExampleAnimEntMove(gentity_t *self, vec3_t moveTo, float stepSize) +{ + trace_t tr; + vec3_t stepTo; + vec3_t stepSub; + vec3_t stepGoal; + vec3_t workingOrg; + vec3_t lastIncrement; + vec3_t finalMeasure; + float stepSize = 0; + float measureLength = 0; + int didMove = 0; + int traceMask = MASK_PLAYERSOLID; + qboolean initialDone = qfalse; + + VectorCopy(org1, workingOrg); + VectorCopy(org1, lastIncrement); + + VectorCopy(moveTo, stepTo); + stepTo[2] = workingOrg[2]; + + VectorSubtract(stepTo, workingOrg, stepSub); + stepSize = VectorLength(stepSub); //make the step size the length of the original positions without Z + + VectorNormalize(stepSub); + + while (!initialDone || didMove) + { + initialDone = qtrue; + didMove = 0; + + stepGoal[0] = workingOrg[0] + stepSub[0]*stepSize; + stepGoal[1] = workingOrg[1] + stepSub[1]*stepSize; + stepGoal[2] = workingOrg[2] + stepSub[2]*stepSize; + + trap_Trace(&tr, workingOrg, mins, maxs, stepGoal, ENTITYNUM_NONE, traceMask); + + if (!tr.startsolid && !tr.allsolid && tr.fraction) + { + vec3_t vecSub; + VectorSubtract(workingOrg, tr.endpos, vecSub); + + if (VectorLength(vecSub) > (stepSize/2)) + { + workingOrg[0] = tr.endpos[0]; + workingOrg[1] = tr.endpos[1]; + //trap_LinkEntity(self); + didMove = 1; + } + } + + if (didMove != 1) + { //stair check + vec3_t trFrom; + vec3_t trTo; + vec3_t trDir; + vec3_t vecMeasure; + + VectorCopy(tr.endpos, trFrom); + trFrom[2] += 16; + + VectorSubtract(/*tr.endpos*/stepGoal, workingOrg, trDir); + VectorNormalize(trDir); + trTo[0] = tr.endpos[0] + trDir[0]*2; + trTo[1] = tr.endpos[1] + trDir[1]*2; + trTo[2] = tr.endpos[2] + trDir[2]*2; + trTo[2] += 16; + + VectorSubtract(trFrom, trTo, vecMeasure); + + if (VectorLength(vecMeasure) > 1) + { + trap_Trace(&tr, trFrom, mins, maxs, trTo, ENTITYNUM_NONE, traceMask); + + if (!tr.startsolid && !tr.allsolid && tr.fraction == 1) + { //clear trace here, probably up a step + vec3_t trDown; + vec3_t trUp; + VectorCopy(tr.endpos, trUp); + VectorCopy(tr.endpos, trDown); + trDown[2] -= 16; + + trap_Trace(&tr, trFrom, mins, maxs, trTo, ENTITYNUM_NONE, traceMask); + + if (!tr.startsolid && !tr.allsolid) + { //plop us down on the step after moving up + VectorCopy(tr.endpos, workingOrg); + //trap_LinkEntity(self); + didMove = 1; + } + } + } + } + + VectorSubtract(lastIncrement, workingOrg, finalMeasure); + measureLength = VectorLength(finalMeasure); + + if (!measureLength) + { //no progress, break out. If last movement was a sucess didMove will equal 1. + break; + } + + stepSize -= measureLength; //subtract the progress distance from the step size so we don't overshoot the mark. + if (stepSize <= 0) + { + break; + } + + VectorCopy(workingOrg, lastIncrement); + } + + return didMove; +} +#endif + +#ifndef _XBOX +int ConnectTrail(int startindex, int endindex, qboolean behindTheScenes) +{ + int foundit; + int cancontinue; + int i; + int failsafe; + int successnodeindex; + int insertindex; + int prenodestart; + byte extendednodes[MAX_NODETABLE_SIZE]; //for storing checked nodes and not trying to extend them each a bazillion times + float fvecmeas; + float baseheight; + float branchDistance; + float maxDistFactor = 256; + vec3_t a; + vec3_t startplace, starttrace; + vec3_t mins, maxs; + vec3_t testspot; + vec3_t validspotpos; + trace_t tr; + + if (g_RMG.integer) + { //this might be temporary. Or not. + if (!(gWPArray[startindex]->flags & WPFLAG_NEVERONEWAY) && + !(gWPArray[endindex]->flags & WPFLAG_NEVERONEWAY)) + { + gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD; + gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK; + } + return 0; + } + + if (!g_RMG.integer) + { + branchDistance = TABLE_BRANCH_DISTANCE; + } + else + { + branchDistance = 512; //be less precise here, terrain is fairly broad, and we don't want to take an hour precalculating + } + + if (g_RMG.integer) + { + maxDistFactor = 700; + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 0; + + nodenum = 0; + foundit = 0; + + i = 0; + + successnodeindex = 0; + + while (i < MAX_NODETABLE_SIZE) //clear it out before using it + { + nodetable[i].flags = 0; +// nodetable[i].index = 0; + nodetable[i].inuse = 0; + nodetable[i].neighbornum = 0; + nodetable[i].origin[0] = 0; + nodetable[i].origin[1] = 0; + nodetable[i].origin[2] = 0; + nodetable[i].weight = 0; + + extendednodes[i] = 0; + + i++; + } + + i = 0; + + if (!behindTheScenes) + { + G_Printf(S_COLOR_YELLOW "Point %i is not connected to %i - Repairing...\n", startindex, endindex); + } + + VectorCopy(gWPArray[startindex]->origin, startplace); + + VectorCopy(startplace, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, startplace, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); + + baseheight = startplace[2] - tr.endpos[2]; + + cancontinue = 1; + + VectorCopy(startplace, nodetable[nodenum].origin); + nodetable[nodenum].weight = 1; + nodetable[nodenum].inuse = 1; +// nodetable[nodenum].index = nodenum; + nodenum++; + + while (nodenum < MAX_NODETABLE_SIZE && !foundit && cancontinue) + { + if (g_RMG.integer) + { //adjust the branch distance dynamically depending on the distance from the start and end points. + vec3_t startDist; + vec3_t endDist; + float startDistf; + float endDistf; + + VectorSubtract(nodetable[nodenum-1].origin, gWPArray[startindex]->origin, startDist); + VectorSubtract(nodetable[nodenum-1].origin, gWPArray[endindex]->origin, endDist); + + startDistf = VectorLength(startDist); + endDistf = VectorLength(endDist); + + if (startDistf < 64 || endDistf < 64) + { + branchDistance = 64; + } + else if (startDistf < 128 || endDistf < 128) + { + branchDistance = 128; + } + else if (startDistf < 256 || endDistf < 256) + { + branchDistance = 256; + } + else if (startDistf < 512 || endDistf < 512) + { + branchDistance = 512; + } + else + { + branchDistance = 800; + } + } + cancontinue = 0; + i = 0; + prenodestart = nodenum; + + while (i < prenodestart) + { + if (extendednodes[i] != 1) + { + VectorSubtract(gWPArray[endindex]->origin, nodetable[i].origin, a); + fvecmeas = VectorLength(a); + + if (fvecmeas < 128 && CanGetToVector(gWPArray[endindex]->origin, nodetable[i].origin, mins, maxs)) + { + foundit = 1; + successnodeindex = i; + break; + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[0] += branchDistance; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; +// nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[0] -= branchDistance; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; +// nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[1] += branchDistance; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; +// nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[1] -= branchDistance; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; +// nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + extendednodes[i] = 1; + } + + i++; + } + } + + if (!foundit) + { +#ifndef _DEBUG //if debug just always print this. + if (!behindTheScenes) +#endif + { + G_Printf(S_COLOR_RED "Could not link %i to %i, unreachable by node branching.\n", startindex, endindex); + } + gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD; + gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK; + if (!behindTheScenes) + { + G_Printf(S_COLOR_YELLOW "Since points cannot be connected, point %i has been flagged as only-forward and point %i has been flagged as only-backward.\n", startindex, endindex); + } + + /*while (nodenum >= 0) + { + if (nodetable[nodenum].origin[0] || nodetable[nodenum].origin[1] || nodetable[nodenum].origin[2]) + { + CreateNewWP(nodetable[nodenum].origin, nodetable[nodenum].flags); + } + + nodenum--; + }*/ + //The above code transfers nodes into the "rendered" waypoint array. Strictly for debugging. + + if (!behindTheScenes) + { //just use what we have if we're auto-pathing the level + return 0; + } + else + { + vec3_t endDist; + int nCount = 0; + int idealNode = -1; + float bestDist = 0; + float testDist; + + if (nodenum <= 10) + { //not enough to even really bother. + return 0; + } + + //Since it failed, find whichever node is closest to the desired end. + while (nCount < nodenum) + { + VectorSubtract(nodetable[nCount].origin, gWPArray[endindex]->origin, endDist); + testDist = VectorLength(endDist); + if (idealNode == -1) + { + idealNode = nCount; + bestDist = testDist; + nCount++; + continue; + } + + if (testDist < bestDist) + { + idealNode = nCount; + bestDist = testDist; + } + + nCount++; + } + + if (idealNode == -1) + { + return 0; + } + + successnodeindex = idealNode; + } + } + + i = successnodeindex; + insertindex = startindex; + failsafe = 0; + VectorCopy(gWPArray[startindex]->origin, validspotpos); + + while (failsafe < MAX_NODETABLE_SIZE && i < MAX_NODETABLE_SIZE && i >= 0) + { + VectorSubtract(validspotpos, nodetable[i].origin, a); + if (!nodetable[nodetable[i].neighbornum].inuse || !CanGetToVectorTravel(validspotpos, /*nodetable[nodetable[i].neighbornum].origin*/nodetable[i].origin, mins, maxs) || VectorLength(a) > maxDistFactor || (!CanGetToVectorTravel(validspotpos, gWPArray[endindex]->origin, mins, maxs) && CanGetToVectorTravel(nodetable[i].origin, gWPArray[endindex]->origin, mins, maxs)) ) + { + nodetable[i].flags |= WPFLAG_CALCULATED; + if (!CreateNewWP_InTrail(nodetable[i].origin, nodetable[i].flags, insertindex)) + { + if (!behindTheScenes) + { + G_Printf(S_COLOR_RED "Could not link %i to %i, waypoint limit hit.\n", startindex, endindex); + } + return 0; + } + + VectorCopy(nodetable[i].origin, validspotpos); + } + + if (i == 0) + { + break; + } + + i = nodetable[i].neighbornum; + + failsafe++; + } + + if (!behindTheScenes) + { + G_Printf(S_COLOR_YELLOW "Finished connecting %i to %i.\n", startindex, endindex); + } + + return 1; +} +#endif + +int OpposingEnds(int start, int end) +{ + if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse) + { + return 0; + } + + if ((gWPArray[start]->flags & WPFLAG_ONEWAY_FWD) && + (gWPArray[end]->flags & WPFLAG_ONEWAY_BACK)) + { + return 1; + } + + return 0; +} + +int DoorBlockingSection(int start, int end) +{ //if a door blocks the trail, we'll just have to assume the points on each side are in visibility when it's open + trace_t tr; + gentity_t *testdoor; + int start_trace_index; + + if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse) + { + return 0; + } + + trap_Trace(&tr, gWPArray[start]->origin, NULL, NULL, gWPArray[end]->origin, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.fraction == 1) + { + return 0; + } + + testdoor = &g_entities[tr.entityNum]; + + if (!testdoor) + { + return 0; + } + + if (!strstr(testdoor->classname, "func_")) + { + return 0; + } + + start_trace_index = tr.entityNum; + + trap_Trace(&tr, gWPArray[end]->origin, NULL, NULL, gWPArray[start]->origin, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.fraction == 1) + { + return 0; + } + + if (start_trace_index == tr.entityNum) + { + return 1; + } + + return 0; +} + +#ifndef _XBOX +int RepairPaths(qboolean behindTheScenes) +{ + int i; + int preAmount = 0; + int ctRet; + vec3_t a; + float maxDistFactor = 400; + + if (!gWPNum) + { + return 0; + } + + if (g_RMG.integer) + { + maxDistFactor = 800; //higher tolerance here. + } + + i = 0; + + preAmount = gWPNum; + + trap_Cvar_Update(&bot_wp_distconnect); + trap_Cvar_Update(&bot_wp_visconnect); + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i+1] && gWPArray[i+1]->inuse) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); + + if (!(gWPArray[i+1]->flags & WPFLAG_NOVIS) && + !(gWPArray[i+1]->flags & WPFLAG_JUMP) && //don't calculate on jump points because they might not always want to be visible (in cases of force jumping) + !(gWPArray[i]->flags & WPFLAG_CALCULATED) && //don't calculate it again + !OpposingEnds(i, i+1) && + ((bot_wp_distconnect.value && VectorLength(a) > maxDistFactor) || (!OrgVisible(gWPArray[i]->origin, gWPArray[i+1]->origin, ENTITYNUM_NONE) && bot_wp_visconnect.value) ) && + !DoorBlockingSection(i, i+1)) + { + ctRet = ConnectTrail(i, i+1, behindTheScenes); + + if (gWPNum >= MAX_WPARRAY_SIZE) + { //Bad! + gWPNum = MAX_WPARRAY_SIZE; + break; + } + + /*if (!ctRet) + { + return 0; + }*/ //we still want to write it.. + } + } + + i++; + } + + return 1; +} +#endif + +int OrgVisibleCurve(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore) +{ + trace_t tr; + vec3_t evenorg1; + + VectorCopy(org1, evenorg1); + evenorg1[2] = org2[2]; + + trap_Trace(&tr, evenorg1, mins, maxs, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + trap_Trace(&tr, evenorg1, mins, maxs, org1, ignore, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + } + + return 0; +} + +int CanForceJumpTo(int baseindex, int testingindex, float distance) +{ + float heightdif; + vec3_t xy_base, xy_test, v, mins, maxs; + wpobject_t *wpBase = gWPArray[baseindex]; + wpobject_t *wpTest = gWPArray[testingindex]; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -15; //-1 + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 15; //1 + + if (!wpBase || !wpBase->inuse || !wpTest || !wpTest->inuse) + { + return 0; + } + + if (distance > 400) + { + return 0; + } + + VectorCopy(wpBase->origin, xy_base); + VectorCopy(wpTest->origin, xy_test); + + xy_base[2] = xy_test[2]; + + VectorSubtract(xy_base, xy_test, v); + + if (VectorLength(v) > MAX_NEIGHBOR_LINK_DISTANCE) + { + return 0; + } + + if ((int)wpBase->origin[2] < (int)wpTest->origin[2]) + { + heightdif = wpTest->origin[2] - wpBase->origin[2]; + } + else + { + return 0; //err.. + } + + if (heightdif < 128) + { //don't bother.. + return 0; + } + + if (heightdif > 512) + { //too high + return 0; + } + + if (!OrgVisibleCurve(wpBase->origin, mins, maxs, wpTest->origin, ENTITYNUM_NONE)) + { + return 0; + } + + if (heightdif > 400) + { + return 3; + } + else if (heightdif > 256) + { + return 2; + } + else + { + return 1; + } +} + +void CalculatePaths(void) +{ + int i; + int c; + int forceJumpable; + int maxNeighborDist = MAX_NEIGHBOR_LINK_DISTANCE; + float nLDist; + vec3_t a; + vec3_t mins, maxs; + + if (!gWPNum) + { + return; + } + + if (g_RMG.integer) + { + maxNeighborDist = DEFAULT_GRID_SPACING + (DEFAULT_GRID_SPACING*0.5); + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = -15; //-1 + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 15; //1 + + //now clear out all the neighbor data before we recalculate + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->neighbornum) + { + while (gWPArray[i]->neighbornum >= 0) + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = 0; + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0; + gWPArray[i]->neighbornum--; + } + gWPArray[i]->neighbornum = 0; + } + + i++; + } + + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + c = 0; + + while (c < gWPNum) + { + if (gWPArray[c] && gWPArray[c]->inuse && i != c && + NotWithinRange(i, c)) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[c]->origin, a); + + nLDist = VectorLength(a); + forceJumpable = CanForceJumpTo(i, c, nLDist); + + if ((nLDist < maxNeighborDist || forceJumpable) && + ((int)gWPArray[i]->origin[2] == (int)gWPArray[c]->origin[2] || forceJumpable) && + (OrgVisibleBox(gWPArray[i]->origin, mins, maxs, gWPArray[c]->origin, ENTITYNUM_NONE) || forceJumpable)) + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = c; + if (forceJumpable && ((int)gWPArray[i]->origin[2] != (int)gWPArray[c]->origin[2] || nLDist < maxNeighborDist)) + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 999;//forceJumpable; //FJSR + } + else + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0; + } + gWPArray[i]->neighbornum++; + } + + if (gWPArray[i]->neighbornum >= MAX_NEIGHBOR_SIZE) + { + break; + } + } + c++; + } + } + i++; + } +} + +gentity_t *GetObjectThatTargets(gentity_t *ent) +{ + gentity_t *next = NULL; + + if (!ent->targetname) + { + return NULL; + } + + next = G_Find( next, FOFS(target), ent->targetname ); + + if (next) + { + return next; + } + + return NULL; +} + +void CalculateSiegeGoals(void) +{ + int i = 0; + int looptracker = 0; + int wpindex = 0; + vec3_t dif; + gentity_t *ent; + gentity_t *tent = NULL, *t2ent = NULL; + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + tent = NULL; + + if (ent && ent->classname && strcmp(ent->classname, "info_siege_objective") == 0) + { + tent = ent; + t2ent = GetObjectThatTargets(tent); + looptracker = 0; + + while (t2ent && looptracker < 2048) + { //looptracker keeps us from getting stuck in case something is set up weird on this map + tent = t2ent; + t2ent = GetObjectThatTargets(tent); + looptracker++; + } + + if (looptracker >= 2048) + { //something unpleasent has happened + tent = NULL; + break; + } + } + + if (tent && ent && tent != ent) + { //tent should now be the object attached to the mission objective + dif[0] = (tent->r.absmax[0]+tent->r.absmin[0])/2; + dif[1] = (tent->r.absmax[1]+tent->r.absmin[1])/2; + dif[2] = (tent->r.absmax[2]+tent->r.absmin[2])/2; + + wpindex = GetNearestVisibleWP(dif, tent->s.number); + + if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse) + { //found the waypoint nearest the center of this objective-related object + if (ent->side == SIEGETEAM_TEAM1) + { + gWPArray[wpindex]->flags |= WPFLAG_SIEGE_IMPERIALOBJ; + } + else + { + gWPArray[wpindex]->flags |= WPFLAG_SIEGE_REBELOBJ; + } + + gWPArray[wpindex]->associated_entity = tent->s.number; + } + } + + i++; + } +} + +float botGlobalNavWeaponWeights[WP_NUM_WEAPONS] = +{ + 0,//WP_NONE, + + 0,//WP_STUN_BATON, + 0,//WP_MELEE + 0,//WP_SABER, // NOTE: lots of code assumes this is the first weapon (... which is crap) so be careful -Ste. + 0,//WP_BRYAR_PISTOL, + 3,//WP_BLASTER, + 5,//WP_DISRUPTOR, + 4,//WP_BOWCASTER, + 6,//WP_REPEATER, + 7,//WP_DEMP2, + 8,//WP_FLECHETTE, + 9,//WP_ROCKET_LAUNCHER, + 3,//WP_THERMAL, + 3,//WP_TRIP_MINE, + 3,//WP_DET_PACK, + 0//WP_EMPLACED_GUN, +}; + +int GetNearestVisibleWPToItem(vec3_t org, int ignore) +{ + int i; + float bestdist; + float flLen; + int bestindex; + vec3_t a, mins, maxs; + + i = 0; + bestdist = 64; //has to be less than 64 units to the item or it isn't safe enough + bestindex = -1; + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && + gWPArray[i]->origin[2]-15 < org[2] && + gWPArray[i]->origin[2]+15 > org[2]) + { + VectorSubtract(org, gWPArray[i]->origin, a); + flLen = VectorLength(a); + + if (flLen < bestdist && trap_InPVS(org, gWPArray[i]->origin) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore)) + { + bestdist = flLen; + bestindex = i; + } + } + + i++; + } + + return bestindex; +} + +void CalculateWeightGoals(void) +{ //set waypoint weights depending on weapon and item placement + int i = 0; + int wpindex = 0; + gentity_t *ent; + float weight; + + trap_Cvar_Update(&bot_wp_clearweight); + + if (bot_wp_clearweight.integer) + { //if set then flush out all weight/goal values before calculating them again + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + gWPArray[i]->weight = 0; + + if (gWPArray[i]->flags & WPFLAG_GOALPOINT) + { + gWPArray[i]->flags -= WPFLAG_GOALPOINT; + } + } + + i++; + } + } + + i = 0; + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + weight = 0; + + if (ent && ent->classname) + { + if (strcmp(ent->classname, "item_seeker") == 0) + { + weight = 2; + } + else if (strcmp(ent->classname, "item_shield") == 0) + { + weight = 2; + } + else if (strcmp(ent->classname, "item_medpac") == 0) + { + weight = 2; + } + else if (strcmp(ent->classname, "item_sentry_gun") == 0) + { + weight = 2; + } + else if (strcmp(ent->classname, "item_force_enlighten_dark") == 0) + { + weight = 5; + } + else if (strcmp(ent->classname, "item_force_enlighten_light") == 0) + { + weight = 5; + } + else if (strcmp(ent->classname, "item_force_boon") == 0) + { + weight = 5; + } + else if (strcmp(ent->classname, "item_ysalimari") == 0) + { + weight = 2; + } + else if (strstr(ent->classname, "weapon_") && ent->item) + { + weight = botGlobalNavWeaponWeights[ent->item->giTag]; + } + else if (ent->item && ent->item->giType == IT_AMMO) + { + weight = 3; + } + } + + if (ent && weight) + { + wpindex = GetNearestVisibleWPToItem(ent->s.pos.trBase, ent->s.number); + + if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse) + { //found the waypoint nearest the center of this object + gWPArray[wpindex]->weight = weight; + gWPArray[wpindex]->flags |= WPFLAG_GOALPOINT; + gWPArray[wpindex]->associated_entity = ent->s.number; + } + } + + i++; + } +} + +void CalculateJumpRoutes(void) +{ + int i = 0; + float nheightdif = 0; + float pheightdif = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + if (gWPArray[i]->flags & WPFLAG_JUMP) + { + nheightdif = 0; + pheightdif = 0; + + gWPArray[i]->forceJumpTo = 0; + + if (gWPArray[i-1] && gWPArray[i-1]->inuse && (gWPArray[i-1]->origin[2]+16) < gWPArray[i]->origin[2]) + { + nheightdif = (gWPArray[i]->origin[2] - gWPArray[i-1]->origin[2]); + } + + if (gWPArray[i+1] && gWPArray[i+1]->inuse && (gWPArray[i+1]->origin[2]+16) < gWPArray[i]->origin[2]) + { + pheightdif = (gWPArray[i]->origin[2] - gWPArray[i+1]->origin[2]); + } + + if (nheightdif > pheightdif) + { + pheightdif = nheightdif; + } + + if (pheightdif) + { + if (pheightdif > 500) + { + gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_3; //FJSR + } + else if (pheightdif > 256) + { + gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_2; //FJSR + } + else if (pheightdif > 128) + { + gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_1; //FJSR + } + } + } + } + + i++; + } +} + +int LoadPathData(const char *filename) +{ + fileHandle_t f; + char *fileString; + char *currentVar; + char *routePath; + wpobject_t thiswp; + int len; + int i, i_cv; + int nei_num; + + i = 0; + i_cv = 0; + + routePath = (char *)B_TempAlloc(1024); + + Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename); + + len = trap_FS_FOpenFile(routePath, &f, FS_READ); + + B_TempFree(1024); //routePath + + if (!f) + { + G_Printf(S_COLOR_YELLOW "Bot route data not found for %s\n", filename); + return 2; + } + + if (len >= 524288) + { + G_Printf(S_COLOR_RED "Route file exceeds maximum length\n"); + return 0; + } + + fileString = (char *)B_TempAlloc(len + 1); + currentVar = (char *)B_TempAlloc(2048); + + trap_FS_Read(fileString, len, f); + + if (fileString[i] == 'l') + { //contains a "levelflags" entry.. + char readLFlags[64]; + i_cv = 0; + + while (fileString[i] != ' ') + { + i++; + } + i++; + while (fileString[i] != '\n') + { + readLFlags[i_cv] = fileString[i]; + i_cv++; + i++; + } + readLFlags[i_cv] = 0; + i++; + + gLevelFlags = atoi(readLFlags); + } + else + { + gLevelFlags = 0; + } + + while (i < len) + { + i_cv = 0; + + thiswp.index = 0; + thiswp.flags = 0; + thiswp.inuse = 0; + thiswp.neighbornum = 0; + thiswp.origin[0] = 0; + thiswp.origin[1] = 0; + thiswp.origin[2] = 0; + thiswp.weight = 0; + thiswp.associated_entity = ENTITYNUM_NONE; + thiswp.forceJumpTo = 0; + thiswp.disttonext = 0; + nei_num = 0; + + while (nei_num < MAX_NEIGHBOR_SIZE) + { + thiswp.neighbors[nei_num].num = 0; + thiswp.neighbors[nei_num].forceJumpTo = 0; + + nei_num++; + } + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.index = atoi(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.flags = atoi(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.weight = atof(currentVar); + + i_cv = 0; + i++; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.origin[0] = atof(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.origin[1] = atof(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ')') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.origin[2] = atof(currentVar); + + i += 4; + + while (fileString[i] != '}') + { + i_cv = 0; + while (fileString[i] != ' ' && fileString[i] != '-') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.neighbors[thiswp.neighbornum].num = atoi(currentVar); + + if (fileString[i] == '-') + { + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 999; //atoi(currentVar); //FJSR + } + else + { + thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 0; + } + + thiswp.neighbornum++; + + i++; + } + + i_cv = 0; + i++; + i++; + + while (fileString[i] != '\n') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.disttonext = atof(currentVar); + + CreateNewWP_FromObject(&thiswp); + i++; + } + + B_TempFree(len + 1); //fileString + B_TempFree(2048); //currentVar + + trap_FS_FCloseFile(f); + + if (g_gametype.integer == GT_SIEGE) + { + CalculateSiegeGoals(); + } + + CalculateWeightGoals(); + //calculate weights for idle activity goals when + //the bot has absolutely nothing else to do + + CalculateJumpRoutes(); + //Look at jump points and mark them as requiring + //force jumping as needed + + return 1; +} + +void FlagObjects(void) +{ + int i = 0, bestindex = 0, found = 0; + float bestdist = 999999, tlen = 0; + gentity_t *flag_red, *flag_blue, *ent; + vec3_t a, mins, maxs; + trace_t tr; + + flag_red = NULL; + flag_blue = NULL; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -5; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 5; + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + if (ent && ent->inuse && ent->classname) + { + if (!flag_red && strcmp(ent->classname, "team_CTF_redflag") == 0) + { + flag_red = ent; + } + else if (!flag_blue && strcmp(ent->classname, "team_CTF_blueflag") == 0) + { + flag_blue = ent; + } + + if (flag_red && flag_blue) + { + break; + } + } + + i++; + } + + i = 0; + + if (!flag_red || !flag_blue) + { + return; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(flag_red->s.pos.trBase, gWPArray[i]->origin, a); + tlen = VectorLength(a); + + if (tlen < bestdist) + { + trap_Trace(&tr, flag_red->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_red->s.number, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == flag_red->s.number) + { + bestdist = tlen; + bestindex = i; + found = 1; + } + } + + } + + i++; + } + + if (found) + { + gWPArray[bestindex]->flags |= WPFLAG_RED_FLAG; + flagRed = gWPArray[bestindex]; + oFlagRed = flagRed; + eFlagRed = flag_red; + } + + bestdist = 999999; + bestindex = 0; + found = 0; + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(flag_blue->s.pos.trBase, gWPArray[i]->origin, a); + tlen = VectorLength(a); + + if (tlen < bestdist) + { + trap_Trace(&tr, flag_blue->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_blue->s.number, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == flag_blue->s.number) + { + bestdist = tlen; + bestindex = i; + found = 1; + } + } + + } + + i++; + } + + if (found) + { + gWPArray[bestindex]->flags |= WPFLAG_BLUE_FLAG; + flagBlue = gWPArray[bestindex]; + oFlagBlue = flagBlue; + eFlagBlue = flag_blue; + } +} + +#ifndef _XBOX +int SavePathData(const char *filename) +{ + fileHandle_t f; + char *fileString; + char *storeString; + char *routePath; + vec3_t a; + float flLen; + int i, s, n; + + fileString = NULL; + i = 0; + s = 0; + + if (!gWPNum) + { + return 0; + } + + routePath = (char *)B_TempAlloc(1024); + + Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename); + + trap_FS_FOpenFile(routePath, &f, FS_WRITE); + + B_TempFree(1024); //routePath + + if (!f) + { + G_Printf(S_COLOR_RED "ERROR: Could not open file to write path data\n"); + return 0; + } + + if (!RepairPaths(qfalse)) //check if we can see all waypoints from the last. If not, try to branch over. + { + trap_FS_FCloseFile(f); + return 0; + } + + CalculatePaths(); //make everything nice and connected before saving + + FlagObjects(); //currently only used for flagging waypoints nearest CTF flags + + fileString = (char *)B_TempAlloc(524288); + storeString = (char *)B_TempAlloc(4096); + + Com_sprintf(fileString, 524288, "%i %i %f (%f %f %f) { ", gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]); + + n = 0; + + while (n < gWPArray[i]->neighbornum) + { + if (gWPArray[i]->neighbors[n].forceJumpTo) + { + Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo); + } + else + { + Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num); + } + n++; + } + + if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); + flLen = VectorLength(a); + } + else + { + flLen = 0; + } + + gWPArray[i]->disttonext = flLen; + + Com_sprintf(fileString, 524288, "%s} %f\n", fileString, flLen); + + i++; + + while (i < gWPNum) + { + //sprintf(fileString, "%s%i %i %f (%f %f %f) { ", fileString, gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]); + Com_sprintf(storeString, 4096, "%i %i %f (%f %f %f) { ", gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]); + + n = 0; + + while (n < gWPArray[i]->neighbornum) + { + if (gWPArray[i]->neighbors[n].forceJumpTo) + { + Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo); + } + else + { + Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num); + } + n++; + } + + if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); + flLen = VectorLength(a); + } + else + { + flLen = 0; + } + + gWPArray[i]->disttonext = flLen; + + Com_sprintf(storeString, 4096, "%s} %f\n", storeString, flLen); + + strcat(fileString, storeString); + + i++; + } + + trap_FS_Write(fileString, strlen(fileString), f); + + B_TempFree(524288); //fileString + B_TempFree(4096); //storeString + + trap_FS_FCloseFile(f); + + G_Printf("Path data has been saved and updated. You may need to restart the level for some things to be properly calculated.\n"); + + return 1; +} +#endif + +//#define PAINFULLY_DEBUGGING_THROUGH_VM + +#define MAX_SPAWNPOINT_ARRAY 64 +int gSpawnPointNum = 0; +gentity_t *gSpawnPoints[MAX_SPAWNPOINT_ARRAY]; + +#ifndef _XBOX +int G_NearestNodeToPoint(vec3_t point) +{ //gets the node on the entire grid which is nearest to the specified coordinates. + vec3_t vSub; + int bestIndex = -1; + int i = 0; + float bestDist = 0; + float testDist = 0; + + while (i < nodenum) + { + VectorSubtract(nodetable[i].origin, point, vSub); + testDist = VectorLength(vSub); + + if (bestIndex == -1) + { + bestIndex = i; + bestDist = testDist; + + i++; + continue; + } + + if (testDist < bestDist) + { + bestIndex = i; + bestDist = testDist; + } + i++; + } + + return bestIndex; +} +#endif + +#ifndef _XBOX +void G_NodeClearForNext(void) +{ //reset nodes for the next trail connection. + int i = 0; + + while (i < nodenum) + { + nodetable[i].flags = 0; + nodetable[i].weight = 99999; + + i++; + } +} + +void G_NodeClearFlags(void) +{ //only clear out flags so nodes can be reused. + int i = 0; + + while (i < nodenum) + { + nodetable[i].flags = 0; + + i++; + } +} + +int G_NodeMatchingXY(float x, float y) +{ //just get the first unflagged node with the matching x,y coordinates. + int i = 0; + + while (i < nodenum) + { + if (nodetable[i].origin[0] == x && + nodetable[i].origin[1] == y && + !nodetable[i].flags) + { + return i; + } + + i++; + } + + return -1; +} + +int G_NodeMatchingXY_BA(int x, int y, int final) +{ //return the node with the lowest weight that matches the specified x,y coordinates. + int i = 0; + int bestindex = -1; + float bestWeight = 9999; + + while (i < nodenum) + { + if ((int)nodetable[i].origin[0] == x && + (int)nodetable[i].origin[1] == y && + !nodetable[i].flags && + ((nodetable[i].weight < bestWeight) || (i == final))) + { + if (i == final) + { + return i; + } + bestindex = i; + bestWeight = nodetable[i].weight; + } + + i++; + } + + return bestindex; +} + +int G_RecursiveConnection(int start, int end, int weight, qboolean traceCheck, float baseHeight) +{ + int indexDirections[4]; //0 == down, 1 == up, 2 == left, 3 == right + int recursiveIndex = -1; + int i = 0; + int passWeight = weight; + vec2_t givenXY; + trace_t tr; + + passWeight++; + nodetable[start].weight = passWeight; + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[0] -= DEFAULT_GRID_SPACING; + indexDirections[0] = G_NodeMatchingXY(givenXY[0], givenXY[1]); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[0] += DEFAULT_GRID_SPACING; + indexDirections[1] = G_NodeMatchingXY(givenXY[0], givenXY[1]); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[1] -= DEFAULT_GRID_SPACING; + indexDirections[2] = G_NodeMatchingXY(givenXY[0], givenXY[1]); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[1] += DEFAULT_GRID_SPACING; + indexDirections[3] = G_NodeMatchingXY(givenXY[0], givenXY[1]); + + i = 0; + while (i < 4) + { + if (indexDirections[i] == end) + { //we've connected all the way to the destination. + return indexDirections[i]; + } + + if (indexDirections[i] != -1 && nodetable[indexDirections[i]].flags) + { //this point is already used, so it's not valid. + indexDirections[i] = -1; + } + else if (indexDirections[i] != -1) + { //otherwise mark it as used. + nodetable[indexDirections[i]].flags = 1; + } + + if (indexDirections[i] != -1 && traceCheck) + { //if we care about trace visibility between nodes, perform the check and mark as not valid if the trace isn't clear. + trap_Trace(&tr, nodetable[start].origin, NULL, NULL, nodetable[indexDirections[i]].origin, ENTITYNUM_NONE, CONTENTS_SOLID); + + if (tr.fraction != 1) + { + indexDirections[i] = -1; + } + } + + if (indexDirections[i] != -1) + { //it's still valid, so keep connecting via this point. + recursiveIndex = G_RecursiveConnection(indexDirections[i], end, passWeight, traceCheck, baseHeight); + } + + if (recursiveIndex != -1) + { //the result of the recursive check was valid, so return it. + return recursiveIndex; + } + + i++; + } + + return recursiveIndex; +} + +#ifdef DEBUG_NODE_FILE +void G_DebugNodeFile() +{ + fileHandle_t f; + int i = 0; + float placeX; + char fileString[131072]; + gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); + + fileString[0] = 0; + + placeX = terrain->r.absmin[0]; + + while (i < nodenum) + { + strcat(fileString, va("%i-%f ", i, nodetable[i].weight)); + placeX += DEFAULT_GRID_SPACING; + + if (placeX >= terrain->r.absmax[0]) + { + strcat(fileString, "\n"); + placeX = terrain->r.absmin[0]; + } + i++; + } + + trap_FS_FOpenFile("ROUTEDEBUG.txt", &f, FS_WRITE); + trap_FS_Write(fileString, strlen(fileString), f); + trap_FS_FCloseFile(f); +} +#endif + +#endif +//#define ASCII_ART_DEBUG +//#define ASCII_ART_NODE_DEBUG + +#ifdef ASCII_ART_DEBUG + +#define ALLOWABLE_DEBUG_FILE_SIZE 1048576 + +void CreateAsciiTableRepresentation() +{ //Draw a text grid of the entire waypoint array (useful for debugging final waypoint placement) + fileHandle_t f; + int i = 0; + int sP = 0; + int placeX; + int placeY; + int oldX; + int oldY; + char fileString[ALLOWABLE_DEBUG_FILE_SIZE]; + char bChr = '+'; + gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); + + placeX = terrain->r.absmin[0]; + placeY = terrain->r.absmin[1]; + + oldX = placeX-1; + oldY = placeY-1; + + while (placeY < terrain->r.absmax[1]) + { + while (placeX < terrain->r.absmax[0]) + { + qboolean gotit = qfalse; + + i = 0; + while (i < gWPNum) + { + if (((int)gWPArray[i]->origin[0] <= placeX && (int)gWPArray[i]->origin[0] > oldX) && + ((int)gWPArray[i]->origin[1] <= placeY && (int)gWPArray[i]->origin[1] > oldY)) + { + gotit = qtrue; + break; + } + i++; + } + + if (gotit) + { + if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD) + { + bChr = 'F'; + } + else if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK) + { + bChr = 'B'; + } + else + { + bChr = '+'; + } + + if (gWPArray[i]->index < 10) + { + fileString[sP] = bChr; + fileString[sP+1] = '0'; + fileString[sP+2] = '0'; + fileString[sP+3] = va("%i", gWPArray[i]->index)[0]; + } + else if (gWPArray[i]->index < 100) + { + char *vastore = va("%i", gWPArray[i]->index); + + fileString[sP] = bChr; + fileString[sP+1] = '0'; + fileString[sP+2] = vastore[0]; + fileString[sP+3] = vastore[1]; + } + else if (gWPArray[i]->index < 1000) + { + char *vastore = va("%i", gWPArray[i]->index); + + fileString[sP] = bChr; + fileString[sP+1] = vastore[0]; + fileString[sP+2] = vastore[1]; + fileString[sP+3] = vastore[2]; + } + else + { + fileString[sP] = 'X'; + fileString[sP+1] = 'X'; + fileString[sP+2] = 'X'; + fileString[sP+3] = 'X'; + } + } + else + { + fileString[sP] = '-'; + fileString[sP+1] = '-'; + fileString[sP+2] = '-'; + fileString[sP+3] = '-'; + } + + sP += 4; + + if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) + { + break; + } + oldX = placeX; + placeX += DEFAULT_GRID_SPACING; + } + + placeX = terrain->r.absmin[0]; + oldX = placeX-1; + fileString[sP] = '\n'; + sP++; + + if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) + { + break; + } + + oldY = placeY; + placeY += DEFAULT_GRID_SPACING; + } + + fileString[sP] = 0; + + trap_FS_FOpenFile("ROUTEDRAWN.txt", &f, FS_WRITE); + trap_FS_Write(fileString, strlen(fileString), f); + trap_FS_FCloseFile(f); +} + +void CreateAsciiNodeTableRepresentation(int start, int end) +{ //draw a text grid of a single node path, from point A to Z. + fileHandle_t f; + int i = 0; + int sP = 0; + int placeX; + int placeY; + int oldX; + int oldY; + char fileString[ALLOWABLE_DEBUG_FILE_SIZE]; + gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); + + placeX = terrain->r.absmin[0]; + placeY = terrain->r.absmin[1]; + + oldX = placeX-1; + oldY = placeY-1; + + while (placeY < terrain->r.absmax[1]) + { + while (placeX < terrain->r.absmax[0]) + { + qboolean gotit = qfalse; + + i = 0; + while (i < nodenum) + { + if (((int)nodetable[i].origin[0] <= placeX && (int)nodetable[i].origin[0] > oldX) && + ((int)nodetable[i].origin[1] <= placeY && (int)nodetable[i].origin[1] > oldY)) + { + gotit = qtrue; + break; + } + i++; + } + + if (gotit) + { + if (i == start) + { //beginning of the node trail + fileString[sP] = 'A'; + fileString[sP+1] = 'A'; + fileString[sP+2] = 'A'; + fileString[sP+3] = 'A'; + } + else if (i == end) + { //destination of the node trail + fileString[sP] = 'Z'; + fileString[sP+1] = 'Z'; + fileString[sP+2] = 'Z'; + fileString[sP+3] = 'Z'; + } + else if (nodetable[i].weight < 10) + { + fileString[sP] = '+'; + fileString[sP+1] = '0'; + fileString[sP+2] = '0'; + fileString[sP+3] = va("%f", nodetable[i].weight)[0]; + } + else if (nodetable[i].weight < 100) + { + char *vastore = va("%f", nodetable[i].weight); + + fileString[sP] = '+'; + fileString[sP+1] = '0'; + fileString[sP+2] = vastore[0]; + fileString[sP+3] = vastore[1]; + } + else if (nodetable[i].weight < 1000) + { + char *vastore = va("%f", nodetable[i].weight); + + fileString[sP] = '+'; + fileString[sP+1] = vastore[0]; + fileString[sP+2] = vastore[1]; + fileString[sP+3] = vastore[2]; + } + else + { + fileString[sP] = 'X'; + fileString[sP+1] = 'X'; + fileString[sP+2] = 'X'; + fileString[sP+3] = 'X'; + } + } + else + { + fileString[sP] = '-'; + fileString[sP+1] = '-'; + fileString[sP+2] = '-'; + fileString[sP+3] = '-'; + } + + sP += 4; + + if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) + { + break; + } + oldX = placeX; + placeX += DEFAULT_GRID_SPACING; + } + + placeX = terrain->r.absmin[0]; + oldX = placeX-1; + fileString[sP] = '\n'; + sP++; + + if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) + { + break; + } + + oldY = placeY; + placeY += DEFAULT_GRID_SPACING; + } + + fileString[sP] = 0; + + trap_FS_FOpenFile("ROUTEDRAWN.txt", &f, FS_WRITE); + trap_FS_Write(fileString, strlen(fileString), f); + trap_FS_FCloseFile(f); +} +#endif + +#ifndef _XBOX +qboolean G_BackwardAttachment(int start, int finalDestination, int insertAfter) +{ //After creating a node path between 2 points, this function links the 2 points with actual waypoint data. + int indexDirections[4]; //0 == down, 1 == up, 2 == left, 3 == right + int i = 0; + int lowestWeight = 9999; + int desiredIndex = -1; + vec2_t givenXY; + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[0] -= DEFAULT_GRID_SPACING; + indexDirections[0] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[0] += DEFAULT_GRID_SPACING; + indexDirections[1] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[1] -= DEFAULT_GRID_SPACING; + indexDirections[2] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[1] += DEFAULT_GRID_SPACING; + indexDirections[3] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); + + while (i < 4) + { + if (indexDirections[i] != -1) + { + if (indexDirections[i] == finalDestination) + { //hooray, we've found the original point and linked all the way back to it. + CreateNewWP_InsertUnder(nodetable[start].origin, 0, insertAfter); + CreateNewWP_InsertUnder(nodetable[indexDirections[i]].origin, 0, insertAfter); + return qtrue; + } + + if (nodetable[indexDirections[i]].weight < lowestWeight && nodetable[indexDirections[i]].weight && !nodetable[indexDirections[i]].flags /*&& (nodetable[indexDirections[i]].origin[2]-64 < nodetable[start].origin[2])*/) + { + desiredIndex = indexDirections[i]; + lowestWeight = nodetable[indexDirections[i]].weight; + } + } + i++; + } + + if (desiredIndex != -1) + { //Create a waypoint here, and then recursively call this function for the next neighbor with the lowest weight. + if (gWPNum < 3900) + { + CreateNewWP_InsertUnder(nodetable[start].origin, 0, insertAfter); + } + else + { +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("WAYPOINTS FULL\n"); +#endif + return qfalse; + } + + nodetable[start].flags = 1; + return G_BackwardAttachment(desiredIndex, finalDestination, insertAfter); + } + + return qfalse; +} + + +#ifdef _DEBUG +#define PATH_TIME_DEBUG +#endif + +void G_RMGPathing(void) +{ //Generate waypoint information on-the-fly for the random mission. + float placeX, placeY, placeZ; + int i = 0; + int gridSpacing = DEFAULT_GRID_SPACING; + int nearestIndex = 0; + int nearestIndexForNext = 0; +#ifdef PATH_TIME_DEBUG + int startTime = 0; + int endTime = 0; +#endif + vec3_t downVec, trMins, trMaxs; + trace_t tr; + gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); + + if (!terrain || !terrain->inuse || terrain->s.eType != ET_TERRAIN) + { + G_Printf("Error: RMG with no terrain!\n"); + return; + } + +#ifdef PATH_TIME_DEBUG + startTime = trap_Milliseconds(); +#endif + + nodenum = 0; + memset(&nodetable, 0, sizeof(nodetable)); + + VectorSet(trMins, -15, -15, DEFAULT_MINS_2); + VectorSet(trMaxs, 15, 15, DEFAULT_MAXS_2); + + placeX = terrain->r.absmin[0]; + placeY = terrain->r.absmin[1]; + placeZ = terrain->r.absmax[2]-400; + + //skim through the entirety of the terrain limits and drop nodes, removing + //nodes that start in solid or fall too high on the terrain. + while (placeY < terrain->r.absmax[1]) + { + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; + } + + while (placeX < terrain->r.absmax[0]) + { + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; + } + + nodetable[nodenum].origin[0] = placeX; + nodetable[nodenum].origin[1] = placeY; + nodetable[nodenum].origin[2] = placeZ; + + VectorCopy(nodetable[nodenum].origin, downVec); + downVec[2] -= 3000; + trap_Trace(&tr, nodetable[nodenum].origin, trMins, trMaxs, downVec, ENTITYNUM_NONE, MASK_SOLID); + + if ((tr.entityNum >= ENTITYNUM_WORLD || g_entities[tr.entityNum].s.eType == ET_TERRAIN) && tr.endpos[2] < terrain->r.absmin[2]+750) + { //only drop nodes on terrain directly + VectorCopy(tr.endpos, nodetable[nodenum].origin); + nodenum++; + } + else + { + VectorClear(nodetable[nodenum].origin); + } + + placeX += gridSpacing; + } + + placeX = terrain->r.absmin[0]; + placeY += gridSpacing; + } + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("NODE GRID PLACED ON TERRAIN\n"); +#endif + + G_NodeClearForNext(); + + //The grid has been placed down, now use it to connect the points in the level. + while (i < gSpawnPointNum-1) + { + if (!gSpawnPoints[i] || !gSpawnPoints[i]->inuse || !gSpawnPoints[i+1] || !gSpawnPoints[i+1]->inuse) + { + i++; + continue; + } + + nearestIndex = G_NearestNodeToPoint(gSpawnPoints[i]->s.origin); + nearestIndexForNext = G_NearestNodeToPoint(gSpawnPoints[i+1]->s.origin); + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("%i GOT %i INDEX WITH %i INDEX FOR NEXT\n", nearestIndex, nearestIndexForNext); +#endif + + if (nearestIndex == -1 || nearestIndexForNext == -1) + { //Looks like there is no grid data near one of the points. Ideally, this will never happen. + i++; + continue; + } + + if (nearestIndex == nearestIndexForNext) + { //Two spawn points on top of each other? We don't need to do both points, keep going until the next differs. + i++; + continue; + } + + //So, nearestIndex is now the node for the spawn point we're on, and nearestIndexForNext is the + //node we want to get to from here. + + //For now I am going to branch out mindlessly, but I will probably want to use some sort of A* algorithm + //here to lessen the time taken. + if (G_RecursiveConnection(nearestIndex, nearestIndexForNext, 0, qtrue, terrain->r.absmin[2]) != nearestIndexForNext) + { //failed to branch to where we want. Oh well, try it without trace checks. + G_NodeClearForNext(); + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FAILED RECURSIVE WITH TRACES\n"); +#endif + + if (G_RecursiveConnection(nearestIndex, nearestIndexForNext, 0, qfalse, terrain->r.absmin[2]) != nearestIndexForNext) + { //still failed somehow. Just disregard this point. +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FAILED RECURSIVE -WITHOUT- TRACES (?!?!)\n"); +#endif + G_NodeClearForNext(); + i++; + continue; + } + } + + //Now our node array is set up so that highest reasonable weight is the destination node, and 2 is next to the original index, + //so trace back to that point. + G_NodeClearFlags(); + +#ifdef ASCII_ART_DEBUG +#ifdef ASCII_ART_NODE_DEBUG + CreateAsciiNodeTableRepresentation(nearestIndex, nearestIndexForNext); +#endif +#endif + if (G_BackwardAttachment(nearestIndexForNext, nearestIndex, gWPNum-1)) + { //successfully connected the trail from nearestIndex to nearestIndexForNext + if (gSpawnPoints[i+1]->inuse && gSpawnPoints[i+1]->item && + gSpawnPoints[i+1]->item->giType == IT_TEAM) + { //This point is actually a CTF flag. + if (gSpawnPoints[i+1]->item->giTag == PW_REDFLAG || gSpawnPoints[i+1]->item->giTag == PW_BLUEFLAG) + { //Place a waypoint on the flag next in the trail, so the nearest grid point will link to it. + CreateNewWP_InsertUnder(gSpawnPoints[i+1]->s.origin, WPFLAG_NEVERONEWAY, gWPNum-1); + } + } + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("BACKWARD ATTACHMENT %i SUCCESS\n", i); +#endif + } + else + { +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("BACKWARD ATTACHMENT FAILED\n"); +#endif + break; + } + +#ifdef DEBUG_NODE_FILE + G_DebugNodeFile(); +#endif + + G_NodeClearForNext(); + i++; + } + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINISHED RMG AUTOPATH\n"); +#endif + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("BEGINNING PATH REPAIR...\n"); +#endif + RepairPaths(qtrue); //this has different behaviour for RMG and will just flag all points one way that don't trace to each other. +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINISHED PATH REPAIR.\n"); +#endif + +#ifdef PATH_TIME_DEBUG + endTime = trap_Milliseconds(); + + G_Printf("Total routing time taken: %ims\n", (endTime - startTime)); +#endif + +#ifdef ASCII_ART_DEBUG + CreateAsciiTableRepresentation(); +#endif +} +#endif + +#ifndef _XBOX +void BeginAutoPathRoutine(void) +{ //Called for RMG levels. + int i = 0; + gentity_t *ent = NULL; + vec3_t v; + + gSpawnPointNum = 0; + + CreateNewWP(vec3_origin, 0); //create a dummy waypoint to insert under + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + if (ent && ent->inuse && ent->classname && ent->classname[0] && !Q_stricmp(ent->classname, "info_player_deathmatch")) + { + if (ent->s.origin[2] < 1280) + { //h4x + gSpawnPoints[gSpawnPointNum] = ent; + gSpawnPointNum++; + } + } + else if (ent && ent->inuse && ent->item && ent->item->giType == IT_TEAM && + (ent->item->giTag == PW_REDFLAG || ent->item->giTag == PW_BLUEFLAG)) + { //also make it path to flags in CTF. + gSpawnPoints[gSpawnPointNum] = ent; + gSpawnPointNum++; + } + + i++; + } + + if (gSpawnPointNum < 1) + { + return; + } + + G_RMGPathing(); +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("LINKING PATHS...\n"); +#endif + + //rww - Using a faster in-engine version because we're having to wait for this stuff to get done as opposed to just saving it once. + trap_Bot_UpdateWaypoints(gWPNum, gWPArray); + trap_Bot_CalculatePaths(g_RMG.integer); + //CalculatePaths(); //make everything nice and connected + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINISHED LINKING PATHS.\n"); +#endif + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FLAGGING OBJECTS...\n"); +#endif + FlagObjects(); //currently only used for flagging waypoints nearest CTF flags +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINISHED FLAGGING OBJECTS.\n"); +#endif + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("CALCULATING WAYPOINT DISTANCES...\n"); +#endif + i = 0; + + while (i < gWPNum-1) + { //disttonext is normally set on save, and when a file is loaded. For RMG we must do it after calc'ing. + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, v); + gWPArray[i]->disttonext = VectorLength(v); + i++; + } +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINISHED CALCULATING.\n"); +#endif + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINAL STEP...\n"); +#endif + RemoveWP(); //remove the dummy point at the end of the trail +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("COMPLETE.\n"); +#endif + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + if (gWPNum >= 4096-1) + { + Com_Printf("%i waypoints say that YOU ARE A TERRIBLE MAN.\n", gWPNum); + } +#endif +} + +#endif +extern vmCvar_t bot_normgpath; + +void LoadPath_ThisLevel(void) +{ + vmCvar_t mapname; + int i = 0; + gentity_t *ent = NULL; + + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + + if (g_RMG.integer) + { //If RMG, generate the path on-the-fly +#ifdef _XBOX + assert(0); +#else + trap_Cvar_Register(&bot_normgpath, "bot_normgpath", "1", CVAR_CHEAT); + //note: This is disabled for now as I'm using standard bot nav + //on premade terrain levels. + + if (!bot_normgpath.integer) + { //autopath the random map + BeginAutoPathRoutine(); + } + else + { //try loading standard nav data + LoadPathData(mapname.string); + } + + gLevelFlags |= LEVELFLAG_NOPOINTPREDICTION; +#endif + } + else + { + if (LoadPathData(mapname.string) == 2) + { + //enter "edit" mode if cheats enabled? + } + } + + trap_Cvar_Update(&bot_wp_edit); + + if (bot_wp_edit.value) + { + gBotEdit = 1; + } + else + { + gBotEdit = 0; + } + + //set the flag entities + while (i < level.num_entities) + { + ent = &g_entities[i]; + + if (ent && ent->inuse && ent->classname) + { + if (!eFlagRed && strcmp(ent->classname, "team_CTF_redflag") == 0) + { + eFlagRed = ent; + } + else if (!eFlagBlue && strcmp(ent->classname, "team_CTF_blueflag") == 0) + { + eFlagBlue = ent; + } + + if (eFlagRed && eFlagBlue) + { + break; + } + } + + i++; + } + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("BOT PATHING IS COMPLETE.\n"); +#endif +} + +gentity_t *GetClosestSpawn(gentity_t *ent) +{ + gentity_t *spawn; + gentity_t *closestSpawn = NULL; + float closestDist = -1; + int i = MAX_CLIENTS; + + spawn = NULL; + + while (i < level.num_entities) + { + spawn = &g_entities[i]; + + if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) ) + { + float checkDist; + vec3_t vSub; + + VectorSubtract(ent->client->ps.origin, spawn->r.currentOrigin, vSub); + checkDist = VectorLength(vSub); + + if (closestDist == -1 || checkDist < closestDist) + { + closestSpawn = spawn; + closestDist = checkDist; + } + } + + i++; + } + + return closestSpawn; +} + +gentity_t *GetNextSpawnInIndex(gentity_t *currentSpawn) +{ + gentity_t *spawn; + gentity_t *nextSpawn = NULL; + int i = currentSpawn->s.number+1; + + spawn = NULL; + + while (i < level.num_entities) + { + spawn = &g_entities[i]; + + if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) ) + { + nextSpawn = spawn; + break; + } + + i++; + } + + if (!nextSpawn) + { //loop back around to 0 + i = MAX_CLIENTS; + + while (i < level.num_entities) + { + spawn = &g_entities[i]; + + if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) ) + { + nextSpawn = spawn; + break; + } + + i++; + } + } + + return nextSpawn; +} + +int AcceptBotCommand(char *cmd, gentity_t *pl) +{ + int OptionalArgument, i; + int FlagsFromArgument; + char *OptionalSArgument, *RequiredSArgument; +#ifndef _XBOX + vmCvar_t mapname; +#endif + + if (!gBotEdit) + { + return 0; + } + + OptionalArgument = 0; + i = 0; + FlagsFromArgument = 0; + OptionalSArgument = NULL; + RequiredSArgument = NULL; + + //if a waypoint editing related command is issued, bots will deactivate. + //once bot_wp_save is issued and the trail is recalculated, bots will activate again. + + if (!pl || !pl->client) + { + return 0; + } + + if (Q_stricmp (cmd, "bot_wp_cmdlist") == 0) //lists all the bot waypoint commands. + { + G_Printf(S_COLOR_YELLOW "bot_wp_add" S_COLOR_WHITE " - Add a waypoint (optional int parameter will insert the point after the specified waypoint index in a trail)\n\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_rem" S_COLOR_WHITE " - Remove a waypoint (removes last unless waypoint index is specified as a parameter)\n\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_addflagged" S_COLOR_WHITE " - Same as wp_add, but adds a flagged point (type bot_wp_addflagged for help)\n\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_switchflags" S_COLOR_WHITE " - Switches flags on an existing waypoint (type bot_wp_switchflags for help)\n\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_tele" S_COLOR_WHITE " - Teleport yourself to the specified waypoint's location\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_killoneways" S_COLOR_WHITE " - Removes oneway (backward and forward) flags on all waypoints in the level\n\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_save" S_COLOR_WHITE " - Saves all waypoint data into a file for later use\n"); + + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_add") == 0) + { + gDeactivated = 1; + OptionalSArgument = ConcatArgs( 1 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + CreateNewWP_InTrail(pl->client->ps.origin, 0, OptionalArgument); + } + else + { + CreateNewWP(pl->client->ps.origin, 0); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_rem") == 0) + { + gDeactivated = 1; + + OptionalSArgument = ConcatArgs( 1 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + RemoveWP_InTrail(OptionalArgument); + } + else + { + RemoveWP(); + } + + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_tele") == 0) + { + gDeactivated = 1; + OptionalSArgument = ConcatArgs( 1 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + TeleportToWP(pl, OptionalArgument); + } + else + { + G_Printf(S_COLOR_YELLOW "You didn't specify an index. Assuming last.\n"); + TeleportToWP(pl, gWPNum-1); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_spawntele") == 0) + { + gentity_t *closestSpawn = GetClosestSpawn(pl); + + if (!closestSpawn) + { //There should always be a spawn point.. + return 1; + } + + closestSpawn = GetNextSpawnInIndex(closestSpawn); + + if (closestSpawn) + { + VectorCopy(closestSpawn->r.currentOrigin, pl->client->ps.origin); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_addflagged") == 0) + { + gDeactivated = 1; + + RequiredSArgument = ConcatArgs( 1 ); + + if (!RequiredSArgument || !RequiredSArgument[0]) + { + G_Printf(S_COLOR_YELLOW "Flag string needed for bot_wp_addflagged\nj - Jump point\nd - Duck point\nc - Snipe or camp standing\nf - Wait for func\nm - Do not move to when func is under\ns - Snipe or camp\nx - Oneway, forward\ny - Oneway, back\ng - Mission goal\nn - No visibility\nExample (for a point the bot would jump at, and reverse on when traveling a trail backwards):\nbot_wp_addflagged jx\n"); + return 1; + } + + while (RequiredSArgument[i]) + { + if (RequiredSArgument[i] == 'j') + { + FlagsFromArgument |= WPFLAG_JUMP; + } + else if (RequiredSArgument[i] == 'd') + { + FlagsFromArgument |= WPFLAG_DUCK; + } + else if (RequiredSArgument[i] == 'c') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND; + } + else if (RequiredSArgument[i] == 'f') + { + FlagsFromArgument |= WPFLAG_WAITFORFUNC; + } + else if (RequiredSArgument[i] == 's') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMP; + } + else if (RequiredSArgument[i] == 'x') + { + FlagsFromArgument |= WPFLAG_ONEWAY_FWD; + } + else if (RequiredSArgument[i] == 'y') + { + FlagsFromArgument |= WPFLAG_ONEWAY_BACK; + } + else if (RequiredSArgument[i] == 'g') + { + FlagsFromArgument |= WPFLAG_GOALPOINT; + } + else if (RequiredSArgument[i] == 'n') + { + FlagsFromArgument |= WPFLAG_NOVIS; + } + else if (RequiredSArgument[i] == 'm') + { + FlagsFromArgument |= WPFLAG_NOMOVEFUNC; + } + + i++; + } + + OptionalSArgument = ConcatArgs( 2 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + CreateNewWP_InTrail(pl->client->ps.origin, FlagsFromArgument, OptionalArgument); + } + else + { + CreateNewWP(pl->client->ps.origin, FlagsFromArgument); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_switchflags") == 0) + { + gDeactivated = 1; + + RequiredSArgument = ConcatArgs( 1 ); + + if (!RequiredSArgument || !RequiredSArgument[0]) + { + G_Printf(S_COLOR_YELLOW "Flag string needed for bot_wp_switchflags\nType bot_wp_addflagged for a list of flags and their corresponding characters, or use 0 for no flags.\nSyntax: bot_wp_switchflags \n"); + return 1; + } + + while (RequiredSArgument[i]) + { + if (RequiredSArgument[i] == 'j') + { + FlagsFromArgument |= WPFLAG_JUMP; + } + else if (RequiredSArgument[i] == 'd') + { + FlagsFromArgument |= WPFLAG_DUCK; + } + else if (RequiredSArgument[i] == 'c') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND; + } + else if (RequiredSArgument[i] == 'f') + { + FlagsFromArgument |= WPFLAG_WAITFORFUNC; + } + else if (RequiredSArgument[i] == 's') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMP; + } + else if (RequiredSArgument[i] == 'x') + { + FlagsFromArgument |= WPFLAG_ONEWAY_FWD; + } + else if (RequiredSArgument[i] == 'y') + { + FlagsFromArgument |= WPFLAG_ONEWAY_BACK; + } + else if (RequiredSArgument[i] == 'g') + { + FlagsFromArgument |= WPFLAG_GOALPOINT; + } + else if (RequiredSArgument[i] == 'n') + { + FlagsFromArgument |= WPFLAG_NOVIS; + } + else if (RequiredSArgument[i] == 'm') + { + FlagsFromArgument |= WPFLAG_NOMOVEFUNC; + } + + i++; + } + + OptionalSArgument = ConcatArgs( 2 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + WPFlagsModify(OptionalArgument, FlagsFromArgument); + } + else + { + G_Printf(S_COLOR_YELLOW "Waypoint number (to modify) needed for bot_wp_switchflags\nSyntax: bot_wp_switchflags \n"); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_killoneways") == 0) + { + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD) + { + gWPArray[i]->flags -= WPFLAG_ONEWAY_FWD; + } + if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK) + { + gWPArray[i]->flags -= WPFLAG_ONEWAY_BACK; + } + } + + i++; + } + + return 1; + } + +#ifndef _XBOX + if (Q_stricmp (cmd, "bot_wp_save") == 0) + { + gDeactivated = 0; + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + SavePathData(mapname.string); + return 1; + } +#endif + + return 0; +} diff --git a/codemp/game/anims.h b/codemp/game/anims.h new file mode 100644 index 0000000..8ad06f5 --- /dev/null +++ b/codemp/game/anims.h @@ -0,0 +1,1797 @@ +#ifndef __ANIMS_H__ +#define __ANIMS_H__ +// playerAnimations + + +typedef enum //# animNumber_e +{ + //================================================= + //HEAD ANIMS + //================================================= + //# #sep Head-only anims + FACE_TALK0, //# silent + FACE_TALK1, //# quiet + FACE_TALK2, //# semi-quiet + FACE_TALK3, //# semi-loud + FACE_TALK4, //# loud + FACE_ALERT, //# + FACE_SMILE, //# + FACE_FROWN, //# + FACE_DEAD, //# + + //================================================= + //ANIMS IN WHICH UPPER AND LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep BOTH_ DEATHS + BOTH_DEATH1, //# First Death anim + BOTH_DEATH2, //# Second Death anim + BOTH_DEATH3, //# Third Death anim + BOTH_DEATH4, //# Fourth Death anim + BOTH_DEATH5, //# Fifth Death anim + BOTH_DEATH6, //# Sixth Death anim + BOTH_DEATH7, //# Seventh Death anim + BOTH_DEATH8, //# + BOTH_DEATH9, //# + BOTH_DEATH10, //# + BOTH_DEATH11, //# + BOTH_DEATH12, //# + BOTH_DEATH13, //# + BOTH_DEATH14, //# + BOTH_DEATH15, //# + BOTH_DEATH16, //# + BOTH_DEATH17, //# + BOTH_DEATH18, //# + BOTH_DEATH19, //# + BOTH_DEATH20, //# + BOTH_DEATH21, //# + BOTH_DEATH22, //# + BOTH_DEATH23, //# + BOTH_DEATH24, //# + BOTH_DEATH25, //# + + BOTH_DEATHFORWARD1, //# First Death in which they get thrown forward + BOTH_DEATHFORWARD2, //# Second Death in which they get thrown forward + BOTH_DEATHFORWARD3, //# Tavion's falling in cin# 23 + BOTH_DEATHBACKWARD1, //# First Death in which they get thrown backward + BOTH_DEATHBACKWARD2, //# Second Death in which they get thrown backward + + BOTH_DEATH1IDLE, //# Idle while close to death + BOTH_LYINGDEATH1, //# Death to play when killed lying down + BOTH_STUMBLEDEATH1, //# Stumble forward and fall face first death + BOTH_FALLDEATH1, //# Fall forward off a high cliff and splat death - start + BOTH_FALLDEATH1INAIR, //# Fall forward off a high cliff and splat death - loop + BOTH_FALLDEATH1LAND, //# Fall forward off a high cliff and splat death - hit bottom + BOTH_DEATH_ROLL, //# Death anim from a roll + BOTH_DEATH_FLIP, //# Death anim from a flip + BOTH_DEATH_SPIN_90_R, //# Death anim when facing 90 degrees right + BOTH_DEATH_SPIN_90_L, //# Death anim when facing 90 degrees left + BOTH_DEATH_SPIN_180, //# Death anim when facing backwards + BOTH_DEATH_LYING_UP, //# Death anim when lying on back + BOTH_DEATH_LYING_DN, //# Death anim when lying on front + BOTH_DEATH_FALLING_DN, //# Death anim when falling on face + BOTH_DEATH_FALLING_UP, //# Death anim when falling on back + BOTH_DEATH_CROUCHED, //# Death anim when crouched + //# #sep BOTH_ DEAD POSES # Should be last frame of corresponding previous anims + BOTH_DEAD1, //# First Death finished pose + BOTH_DEAD2, //# Second Death finished pose + BOTH_DEAD3, //# Third Death finished pose + BOTH_DEAD4, //# Fourth Death finished pose + BOTH_DEAD5, //# Fifth Death finished pose + BOTH_DEAD6, //# Sixth Death finished pose + BOTH_DEAD7, //# Seventh Death finished pose + BOTH_DEAD8, //# + BOTH_DEAD9, //# + BOTH_DEAD10, //# + BOTH_DEAD11, //# + BOTH_DEAD12, //# + BOTH_DEAD13, //# + BOTH_DEAD14, //# + BOTH_DEAD15, //# + BOTH_DEAD16, //# + BOTH_DEAD17, //# + BOTH_DEAD18, //# + BOTH_DEAD19, //# + BOTH_DEAD20, //# + BOTH_DEAD21, //# + BOTH_DEAD22, //# + BOTH_DEAD23, //# + BOTH_DEAD24, //# + BOTH_DEAD25, //# + BOTH_DEADFORWARD1, //# First thrown forward death finished pose + BOTH_DEADFORWARD2, //# Second thrown forward death finished pose + BOTH_DEADBACKWARD1, //# First thrown backward death finished pose + BOTH_DEADBACKWARD2, //# Second thrown backward death finished pose + BOTH_LYINGDEAD1, //# Killed lying down death finished pose + BOTH_STUMBLEDEAD1, //# Stumble forward death finished pose + BOTH_FALLDEAD1LAND, //# Fall forward and splat death finished pose + //# #sep BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + BOTH_DEADFLOP1, //# React to being shot from First Death finished pose + BOTH_DEADFLOP2, //# React to being shot from Second Death finished pose + BOTH_DISMEMBER_HEAD1, //# + BOTH_DISMEMBER_TORSO1, //# + BOTH_DISMEMBER_LLEG, //# + BOTH_DISMEMBER_RLEG, //# + BOTH_DISMEMBER_RARM, //# + BOTH_DISMEMBER_LARM, //# + //# #sep BOTH_ PAINS + BOTH_PAIN1, //# First take pain anim + BOTH_PAIN2, //# Second take pain anim + BOTH_PAIN3, //# Third take pain anim + BOTH_PAIN4, //# Fourth take pain anim + BOTH_PAIN5, //# Fifth take pain anim - from behind + BOTH_PAIN6, //# Sixth take pain anim - from behind + BOTH_PAIN7, //# Seventh take pain anim - from behind + BOTH_PAIN8, //# Eigth take pain anim - from behind + BOTH_PAIN9, //# + BOTH_PAIN10, //# + BOTH_PAIN11, //# + BOTH_PAIN12, //# + BOTH_PAIN13, //# + BOTH_PAIN14, //# + BOTH_PAIN15, //# + BOTH_PAIN16, //# + BOTH_PAIN17, //# + BOTH_PAIN18, //# + + //# #sep BOTH_ ATTACKS + BOTH_ATTACK1, //# Attack with stun baton + BOTH_ATTACK2, //# Attack with one-handed pistol + BOTH_ATTACK3, //# Attack with blaster rifle + BOTH_ATTACK4, //# Attack with disruptor + BOTH_ATTACK5, //# Another Rancor Attack + BOTH_ATTACK6, //# Yet Another Rancor Attack + BOTH_ATTACK7, //# Yet Another Rancor Attack + BOTH_ATTACK10, //# Attack with thermal det + BOTH_ATTACK11, //# "Attack" with tripmine and detpack + BOTH_MELEE1, //# First melee attack + BOTH_MELEE2, //# Second melee attack + BOTH_THERMAL_READY, //# pull back with thermal + BOTH_THERMAL_THROW, //# throw thermal + //* #sep BOTH_ SABER ANIMS + //Saber attack anims - power level 1 + BOTH_A1_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A1__L__R, //# Fast weak horizontal attack left to right + BOTH_A1__R__L, //# Fast weak horizontal attack right to left + BOTH_A1_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A1_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A1_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A1_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T1_BR__R, //# Fast arc bottom right to right + BOTH_T1_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T1_BR__L, //# Fast weak spin bottom right to left + BOTH_T1_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T1__R_TR, //# Fast arc right to top right + BOTH_T1__R_TL, //# Fast arc right to top left + BOTH_T1__R__L, //# Fast weak spin right to left + BOTH_T1__R_BL, //# Fast weak spin right to bottom left + BOTH_T1_TR_BR, //# Fast arc top right to bottom right + BOTH_T1_TR_TL, //# Fast arc top right to top left + BOTH_T1_TR__L, //# Fast arc top right to left + BOTH_T1_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T1_T__BR, //# Fast arc top to bottom right + BOTH_T1_T___R, //# Fast arc top to right + BOTH_T1_T__TR, //# Fast arc top to top right + BOTH_T1_T__TL, //# Fast arc top to top left + BOTH_T1_T___L, //# Fast arc top to left + BOTH_T1_T__BL, //# Fast arc top to bottom left + BOTH_T1_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T1_TL_BL, //# Fast arc top left to bottom left + BOTH_T1__L_BR, //# Fast weak spin left to bottom right + BOTH_T1__L__R, //# Fast weak spin left to right + BOTH_T1__L_TL, //# Fast arc left to top left + BOTH_T1_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T1_BL__R, //# Fast weak spin bottom left to right + BOTH_T1_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T1_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T1_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T1_TR_BR) + BOTH_T1_BR_T_, //# Fast arc bottom right to top (use: BOTH_T1_T__BR) + BOTH_T1__R_BR, //# Fast arc right to bottom right (use: BOTH_T1_BR__R) + BOTH_T1__R_T_, //# Fast ar right to top (use: BOTH_T1_T___R) + BOTH_T1_TR__R, //# Fast arc top right to right (use: BOTH_T1__R_TR) + BOTH_T1_TR_T_, //# Fast arc top right to top (use: BOTH_T1_T__TR) + BOTH_T1_TL__R, //# Fast arc top left to right (use: BOTH_T1__R_TL) + BOTH_T1_TL_TR, //# Fast arc top left to top right (use: BOTH_T1_TR_TL) + BOTH_T1_TL_T_, //# Fast arc top left to top (use: BOTH_T1_T__TL) + BOTH_T1_TL__L, //# Fast arc top left to left (use: BOTH_T1__L_TL) + BOTH_T1__L_TR, //# Fast arc left to top right (use: BOTH_T1_TR__L) + BOTH_T1__L_T_, //# Fast arc left to top (use: BOTH_T1_T___L) + BOTH_T1__L_BL, //# Fast arc left to bottom left (use: BOTH_T1_BL__L) + BOTH_T1_BL_T_, //# Fast arc bottom left to top (use: BOTH_T1_T__BL) + BOTH_T1_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T1_TL_BL) + //Saber Attack Start Transitions + BOTH_S1_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S1_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S1_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S1_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S1_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S1_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S1_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R1_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R1__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R1__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R1_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R1_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R1_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R1_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B1_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B1__R___, //# Bounce-back if attack from R is blocked + BOTH_B1_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B1_T____, //# Bounce-back if attack from T is blocked + BOTH_B1_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B1__L___, //# Bounce-back if attack from L is blocked + BOTH_B1_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D1_BR___, //# Deflection toward BR + BOTH_D1__R___, //# Deflection toward R + BOTH_D1_TR___, //# Deflection toward TR + BOTH_D1_TL___, //# Deflection toward TL + BOTH_D1__L___, //# Deflection toward L + BOTH_D1_BL___, //# Deflection toward BL + BOTH_D1_B____, //# Deflection toward B + //Saber attack anims - power level 2 + BOTH_A2_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A2__L__R, //# Fast weak horizontal attack left to right + BOTH_A2__R__L, //# Fast weak horizontal attack right to left + BOTH_A2_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A2_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A2_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A2_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T2_BR__R, //# Fast arc bottom right to right + BOTH_T2_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T2_BR__L, //# Fast weak spin bottom right to left + BOTH_T2_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T2__R_TR, //# Fast arc right to top right + BOTH_T2__R_TL, //# Fast arc right to top left + BOTH_T2__R__L, //# Fast weak spin right to left + BOTH_T2__R_BL, //# Fast weak spin right to bottom left + BOTH_T2_TR_BR, //# Fast arc top right to bottom right + BOTH_T2_TR_TL, //# Fast arc top right to top left + BOTH_T2_TR__L, //# Fast arc top right to left + BOTH_T2_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T2_T__BR, //# Fast arc top to bottom right + BOTH_T2_T___R, //# Fast arc top to right + BOTH_T2_T__TR, //# Fast arc top to top right + BOTH_T2_T__TL, //# Fast arc top to top left + BOTH_T2_T___L, //# Fast arc top to left + BOTH_T2_T__BL, //# Fast arc top to bottom left + BOTH_T2_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T2_TL_BL, //# Fast arc top left to bottom left + BOTH_T2__L_BR, //# Fast weak spin left to bottom right + BOTH_T2__L__R, //# Fast weak spin left to right + BOTH_T2__L_TL, //# Fast arc left to top left + BOTH_T2_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T2_BL__R, //# Fast weak spin bottom left to right + BOTH_T2_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T2_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T2_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T2_TR_BR) + BOTH_T2_BR_T_, //# Fast arc bottom right to top (use: BOTH_T2_T__BR) + BOTH_T2__R_BR, //# Fast arc right to bottom right (use: BOTH_T2_BR__R) + BOTH_T2__R_T_, //# Fast ar right to top (use: BOTH_T2_T___R) + BOTH_T2_TR__R, //# Fast arc top right to right (use: BOTH_T2__R_TR) + BOTH_T2_TR_T_, //# Fast arc top right to top (use: BOTH_T2_T__TR) + BOTH_T2_TL__R, //# Fast arc top left to right (use: BOTH_T2__R_TL) + BOTH_T2_TL_TR, //# Fast arc top left to top right (use: BOTH_T2_TR_TL) + BOTH_T2_TL_T_, //# Fast arc top left to top (use: BOTH_T2_T__TL) + BOTH_T2_TL__L, //# Fast arc top left to left (use: BOTH_T2__L_TL) + BOTH_T2__L_TR, //# Fast arc left to top right (use: BOTH_T2_TR__L) + BOTH_T2__L_T_, //# Fast arc left to top (use: BOTH_T2_T___L) + BOTH_T2__L_BL, //# Fast arc left to bottom left (use: BOTH_T2_BL__L) + BOTH_T2_BL_T_, //# Fast arc bottom left to top (use: BOTH_T2_T__BL) + BOTH_T2_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T2_TL_BL) + //Saber Attack Start Transitions + BOTH_S2_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S2_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S2_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S2_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S2_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S2_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S2_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R2_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R2__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R2__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R2_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R2_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R2_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R2_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B2_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B2__R___, //# Bounce-back if attack from R is blocked + BOTH_B2_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B2_T____, //# Bounce-back if attack from T is blocked + BOTH_B2_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B2__L___, //# Bounce-back if attack from L is blocked + BOTH_B2_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D2_BR___, //# Deflection toward BR + BOTH_D2__R___, //# Deflection toward R + BOTH_D2_TR___, //# Deflection toward TR + BOTH_D2_TL___, //# Deflection toward TL + BOTH_D2__L___, //# Deflection toward L + BOTH_D2_BL___, //# Deflection toward BL + BOTH_D2_B____, //# Deflection toward B + //Saber attack anims - power level 3 + BOTH_A3_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A3__L__R, //# Fast weak horizontal attack left to right + BOTH_A3__R__L, //# Fast weak horizontal attack right to left + BOTH_A3_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A3_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A3_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A3_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T3_BR__R, //# Fast arc bottom right to right + BOTH_T3_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T3_BR__L, //# Fast weak spin bottom right to left + BOTH_T3_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T3__R_TR, //# Fast arc right to top right + BOTH_T3__R_TL, //# Fast arc right to top left + BOTH_T3__R__L, //# Fast weak spin right to left + BOTH_T3__R_BL, //# Fast weak spin right to bottom left + BOTH_T3_TR_BR, //# Fast arc top right to bottom right + BOTH_T3_TR_TL, //# Fast arc top right to top left + BOTH_T3_TR__L, //# Fast arc top right to left + BOTH_T3_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T3_T__BR, //# Fast arc top to bottom right + BOTH_T3_T___R, //# Fast arc top to right + BOTH_T3_T__TR, //# Fast arc top to top right + BOTH_T3_T__TL, //# Fast arc top to top left + BOTH_T3_T___L, //# Fast arc top to left + BOTH_T3_T__BL, //# Fast arc top to bottom left + BOTH_T3_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T3_TL_BL, //# Fast arc top left to bottom left + BOTH_T3__L_BR, //# Fast weak spin left to bottom right + BOTH_T3__L__R, //# Fast weak spin left to right + BOTH_T3__L_TL, //# Fast arc left to top left + BOTH_T3_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T3_BL__R, //# Fast weak spin bottom left to right + BOTH_T3_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T3_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T3_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T3_TR_BR) + BOTH_T3_BR_T_, //# Fast arc bottom right to top (use: BOTH_T3_T__BR) + BOTH_T3__R_BR, //# Fast arc right to bottom right (use: BOTH_T3_BR__R) + BOTH_T3__R_T_, //# Fast ar right to top (use: BOTH_T3_T___R) + BOTH_T3_TR__R, //# Fast arc top right to right (use: BOTH_T3__R_TR) + BOTH_T3_TR_T_, //# Fast arc top right to top (use: BOTH_T3_T__TR) + BOTH_T3_TL__R, //# Fast arc top left to right (use: BOTH_T3__R_TL) + BOTH_T3_TL_TR, //# Fast arc top left to top right (use: BOTH_T3_TR_TL) + BOTH_T3_TL_T_, //# Fast arc top left to top (use: BOTH_T3_T__TL) + BOTH_T3_TL__L, //# Fast arc top left to left (use: BOTH_T3__L_TL) + BOTH_T3__L_TR, //# Fast arc left to top right (use: BOTH_T3_TR__L) + BOTH_T3__L_T_, //# Fast arc left to top (use: BOTH_T3_T___L) + BOTH_T3__L_BL, //# Fast arc left to bottom left (use: BOTH_T3_BL__L) + BOTH_T3_BL_T_, //# Fast arc bottom left to top (use: BOTH_T3_T__BL) + BOTH_T3_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T3_TL_BL) + //Saber Attack Start Transitions + BOTH_S3_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S3_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S3_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S3_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S3_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S3_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S3_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R3_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R3__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R3__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R3_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R3_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R3_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R3_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B3_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B3__R___, //# Bounce-back if attack from R is blocked + BOTH_B3_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B3_T____, //# Bounce-back if attack from T is blocked + BOTH_B3_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B3__L___, //# Bounce-back if attack from L is blocked + BOTH_B3_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D3_BR___, //# Deflection toward BR + BOTH_D3__R___, //# Deflection toward R + BOTH_D3_TR___, //# Deflection toward TR + BOTH_D3_TL___, //# Deflection toward TL + BOTH_D3__L___, //# Deflection toward L + BOTH_D3_BL___, //# Deflection toward BL + BOTH_D3_B____, //# Deflection toward B + //Saber attack anims - power level 4 - Desann's + BOTH_A4_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A4__L__R, //# Fast weak horizontal attack left to right + BOTH_A4__R__L, //# Fast weak horizontal attack right to left + BOTH_A4_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A4_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A4_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A4_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T4_BR__R, //# Fast arc bottom right to right + BOTH_T4_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T4_BR__L, //# Fast weak spin bottom right to left + BOTH_T4_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T4__R_TR, //# Fast arc right to top right + BOTH_T4__R_TL, //# Fast arc right to top left + BOTH_T4__R__L, //# Fast weak spin right to left + BOTH_T4__R_BL, //# Fast weak spin right to bottom left + BOTH_T4_TR_BR, //# Fast arc top right to bottom right + BOTH_T4_TR_TL, //# Fast arc top right to top left + BOTH_T4_TR__L, //# Fast arc top right to left + BOTH_T4_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T4_T__BR, //# Fast arc top to bottom right + BOTH_T4_T___R, //# Fast arc top to right + BOTH_T4_T__TR, //# Fast arc top to top right + BOTH_T4_T__TL, //# Fast arc top to top left + BOTH_T4_T___L, //# Fast arc top to left + BOTH_T4_T__BL, //# Fast arc top to bottom left + BOTH_T4_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T4_TL_BL, //# Fast arc top left to bottom left + BOTH_T4__L_BR, //# Fast weak spin left to bottom right + BOTH_T4__L__R, //# Fast weak spin left to right + BOTH_T4__L_TL, //# Fast arc left to top left + BOTH_T4_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T4_BL__R, //# Fast weak spin bottom left to right + BOTH_T4_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T4_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T4_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T4_TR_BR) + BOTH_T4_BR_T_, //# Fast arc bottom right to top (use: BOTH_T4_T__BR) + BOTH_T4__R_BR, //# Fast arc right to bottom right (use: BOTH_T4_BR__R) + BOTH_T4__R_T_, //# Fast ar right to top (use: BOTH_T4_T___R) + BOTH_T4_TR__R, //# Fast arc top right to right (use: BOTH_T4__R_TR) + BOTH_T4_TR_T_, //# Fast arc top right to top (use: BOTH_T4_T__TR) + BOTH_T4_TL__R, //# Fast arc top left to right (use: BOTH_T4__R_TL) + BOTH_T4_TL_TR, //# Fast arc top left to top right (use: BOTH_T4_TR_TL) + BOTH_T4_TL_T_, //# Fast arc top left to top (use: BOTH_T4_T__TL) + BOTH_T4_TL__L, //# Fast arc top left to left (use: BOTH_T4__L_TL) + BOTH_T4__L_TR, //# Fast arc left to top right (use: BOTH_T4_TR__L) + BOTH_T4__L_T_, //# Fast arc left to top (use: BOTH_T4_T___L) + BOTH_T4__L_BL, //# Fast arc left to bottom left (use: BOTH_T4_BL__L) + BOTH_T4_BL_T_, //# Fast arc bottom left to top (use: BOTH_T4_T__BL) + BOTH_T4_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T4_TL_BL) + //Saber Attack Start Transitions + BOTH_S4_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S4_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S4_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S4_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S4_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S4_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S4_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R4_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R4__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R4__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R4_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R4_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R4_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R4_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B4_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B4__R___, //# Bounce-back if attack from R is blocked + BOTH_B4_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B4_T____, //# Bounce-back if attack from T is blocked + BOTH_B4_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B4__L___, //# Bounce-back if attack from L is blocked + BOTH_B4_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D4_BR___, //# Deflection toward BR + BOTH_D4__R___, //# Deflection toward R + BOTH_D4_TR___, //# Deflection toward TR + BOTH_D4_TL___, //# Deflection toward TL + BOTH_D4__L___, //# Deflection toward L + BOTH_D4_BL___, //# Deflection toward BL + BOTH_D4_B____, //# Deflection toward B + //Saber attack anims - power level 5 - Tavion's + BOTH_A5_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A5__L__R, //# Fast weak horizontal attack left to right + BOTH_A5__R__L, //# Fast weak horizontal attack right to left + BOTH_A5_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A5_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A5_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A5_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T5_BR__R, //# Fast arc bottom right to right + BOTH_T5_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T5_BR__L, //# Fast weak spin bottom right to left + BOTH_T5_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T5__R_TR, //# Fast arc right to top right + BOTH_T5__R_TL, //# Fast arc right to top left + BOTH_T5__R__L, //# Fast weak spin right to left + BOTH_T5__R_BL, //# Fast weak spin right to bottom left + BOTH_T5_TR_BR, //# Fast arc top right to bottom right + BOTH_T5_TR_TL, //# Fast arc top right to top left + BOTH_T5_TR__L, //# Fast arc top right to left + BOTH_T5_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T5_T__BR, //# Fast arc top to bottom right + BOTH_T5_T___R, //# Fast arc top to right + BOTH_T5_T__TR, //# Fast arc top to top right + BOTH_T5_T__TL, //# Fast arc top to top left + BOTH_T5_T___L, //# Fast arc top to left + BOTH_T5_T__BL, //# Fast arc top to bottom left + BOTH_T5_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T5_TL_BL, //# Fast arc top left to bottom left + BOTH_T5__L_BR, //# Fast weak spin left to bottom right + BOTH_T5__L__R, //# Fast weak spin left to right + BOTH_T5__L_TL, //# Fast arc left to top left + BOTH_T5_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T5_BL__R, //# Fast weak spin bottom left to right + BOTH_T5_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T5_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T5_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T5_TR_BR) + BOTH_T5_BR_T_, //# Fast arc bottom right to top (use: BOTH_T5_T__BR) + BOTH_T5__R_BR, //# Fast arc right to bottom right (use: BOTH_T5_BR__R) + BOTH_T5__R_T_, //# Fast ar right to top (use: BOTH_T5_T___R) + BOTH_T5_TR__R, //# Fast arc top right to right (use: BOTH_T5__R_TR) + BOTH_T5_TR_T_, //# Fast arc top right to top (use: BOTH_T5_T__TR) + BOTH_T5_TL__R, //# Fast arc top left to right (use: BOTH_T5__R_TL) + BOTH_T5_TL_TR, //# Fast arc top left to top right (use: BOTH_T5_TR_TL) + BOTH_T5_TL_T_, //# Fast arc top left to top (use: BOTH_T5_T__TL) + BOTH_T5_TL__L, //# Fast arc top left to left (use: BOTH_T5__L_TL) + BOTH_T5__L_TR, //# Fast arc left to top right (use: BOTH_T5_TR__L) + BOTH_T5__L_T_, //# Fast arc left to top (use: BOTH_T5_T___L) + BOTH_T5__L_BL, //# Fast arc left to bottom left (use: BOTH_T5_BL__L) + BOTH_T5_BL_T_, //# Fast arc bottom left to top (use: BOTH_T5_T__BL) + BOTH_T5_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T5_TL_BL) + //Saber Attack Start Transitions + BOTH_S5_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S5_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S5_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S5_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S5_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S5_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S5_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R5_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R5__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R5__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R5_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R5_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R5_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R5_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B5_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B5__R___, //# Bounce-back if attack from R is blocked + BOTH_B5_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B5_T____, //# Bounce-back if attack from T is blocked + BOTH_B5_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B5__L___, //# Bounce-back if attack from L is blocked + BOTH_B5_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D5_BR___, //# Deflection toward BR + BOTH_D5__R___, //# Deflection toward R + BOTH_D5_TR___, //# Deflection toward TR + BOTH_D5_TL___, //# Deflection toward TL + BOTH_D5__L___, //# Deflection toward L + BOTH_D5_BL___, //# Deflection toward BL + BOTH_D5_B____, //# Deflection toward B + //Saber attack anims - power level 6 + BOTH_A6_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A6__L__R, //# Fast weak horizontal attack left to right + BOTH_A6__R__L, //# Fast weak horizontal attack right to left + BOTH_A6_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A6_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A6_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A6_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T6_BR__R, //# Fast arc bottom right to right + BOTH_T6_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T6_BR__L, //# Fast weak spin bottom right to left + BOTH_T6_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T6__R_TR, //# Fast arc right to top right + BOTH_T6__R_TL, //# Fast arc right to top left + BOTH_T6__R__L, //# Fast weak spin right to left + BOTH_T6__R_BL, //# Fast weak spin right to bottom left + BOTH_T6_TR_BR, //# Fast arc top right to bottom right + BOTH_T6_TR_TL, //# Fast arc top right to top left + BOTH_T6_TR__L, //# Fast arc top right to left + BOTH_T6_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T6_T__BR, //# Fast arc top to bottom right + BOTH_T6_T___R, //# Fast arc top to right + BOTH_T6_T__TR, //# Fast arc top to top right + BOTH_T6_T__TL, //# Fast arc top to top left + BOTH_T6_T___L, //# Fast arc top to left + BOTH_T6_T__BL, //# Fast arc top to bottom left + BOTH_T6_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T6_TL_BL, //# Fast arc top left to bottom left + BOTH_T6__L_BR, //# Fast weak spin left to bottom right + BOTH_T6__L__R, //# Fast weak spin left to right + BOTH_T6__L_TL, //# Fast arc left to top left + BOTH_T6_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T6_BL__R, //# Fast weak spin bottom left to right + BOTH_T6_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T6_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T6_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T6_TR_BR) + BOTH_T6_BR_T_, //# Fast arc bottom right to top (use: BOTH_T6_T__BR) + BOTH_T6__R_BR, //# Fast arc right to bottom right (use: BOTH_T6_BR__R) + BOTH_T6__R_T_, //# Fast ar right to top (use: BOTH_T6_T___R) + BOTH_T6_TR__R, //# Fast arc top right to right (use: BOTH_T6__R_TR) + BOTH_T6_TR_T_, //# Fast arc top right to top (use: BOTH_T6_T__TR) + BOTH_T6_TL__R, //# Fast arc top left to right (use: BOTH_T6__R_TL) + BOTH_T6_TL_TR, //# Fast arc top left to top right (use: BOTH_T6_TR_TL) + BOTH_T6_TL_T_, //# Fast arc top left to top (use: BOTH_T6_T__TL) + BOTH_T6_TL__L, //# Fast arc top left to left (use: BOTH_T6__L_TL) + BOTH_T6__L_TR, //# Fast arc left to top right (use: BOTH_T6_TR__L) + BOTH_T6__L_T_, //# Fast arc left to top (use: BOTH_T6_T___L) + BOTH_T6__L_BL, //# Fast arc left to bottom left (use: BOTH_T6_BL__L) + BOTH_T6_BL_T_, //# Fast arc bottom left to top (use: BOTH_T6_T__BL) + BOTH_T6_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T6_TL_BL) + //Saber Attack Start Transitions + BOTH_S6_S6_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S6_S6__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S6_S6__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S6_S6_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S6_S6_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S6_S6_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S6_S6_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R6_B__S6, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R6__L_S6, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R6__R_S6, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R6_TL_S6, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R6_BR_S6, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R6_BL_S6, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R6_TR_S6, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B6_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B6__R___, //# Bounce-back if attack from R is blocked + BOTH_B6_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B6_T____, //# Bounce-back if attack from T is blocked + BOTH_B6_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B6__L___, //# Bounce-back if attack from L is blocked + BOTH_B6_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D6_BR___, //# Deflection toward BR + BOTH_D6__R___, //# Deflection toward R + BOTH_D6_TR___, //# Deflection toward TR + BOTH_D6_TL___, //# Deflection toward TL + BOTH_D6__L___, //# Deflection toward L + BOTH_D6_BL___, //# Deflection toward BL + BOTH_D6_B____, //# Deflection toward B + //Saber attack anims - power level 7 + BOTH_A7_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A7__L__R, //# Fast weak horizontal attack left to right + BOTH_A7__R__L, //# Fast weak horizontal attack right to left + BOTH_A7_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A7_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A7_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A7_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T7_BR__R, //# Fast arc bottom right to right + BOTH_T7_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T7_BR__L, //# Fast weak spin bottom right to left + BOTH_T7_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T7__R_TR, //# Fast arc right to top right + BOTH_T7__R_TL, //# Fast arc right to top left + BOTH_T7__R__L, //# Fast weak spin right to left + BOTH_T7__R_BL, //# Fast weak spin right to bottom left + BOTH_T7_TR_BR, //# Fast arc top right to bottom right + BOTH_T7_TR_TL, //# Fast arc top right to top left + BOTH_T7_TR__L, //# Fast arc top right to left + BOTH_T7_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T7_T__BR, //# Fast arc top to bottom right + BOTH_T7_T___R, //# Fast arc top to right + BOTH_T7_T__TR, //# Fast arc top to top right + BOTH_T7_T__TL, //# Fast arc top to top left + BOTH_T7_T___L, //# Fast arc top to left + BOTH_T7_T__BL, //# Fast arc top to bottom left + BOTH_T7_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T7_TL_BL, //# Fast arc top left to bottom left + BOTH_T7__L_BR, //# Fast weak spin left to bottom right + BOTH_T7__L__R, //# Fast weak spin left to right + BOTH_T7__L_TL, //# Fast arc left to top left + BOTH_T7_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T7_BL__R, //# Fast weak spin bottom left to right + BOTH_T7_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T7_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T7_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T7_TR_BR) + BOTH_T7_BR_T_, //# Fast arc bottom right to top (use: BOTH_T7_T__BR) + BOTH_T7__R_BR, //# Fast arc right to bottom right (use: BOTH_T7_BR__R) + BOTH_T7__R_T_, //# Fast ar right to top (use: BOTH_T7_T___R) + BOTH_T7_TR__R, //# Fast arc top right to right (use: BOTH_T7__R_TR) + BOTH_T7_TR_T_, //# Fast arc top right to top (use: BOTH_T7_T__TR) + BOTH_T7_TL__R, //# Fast arc top left to right (use: BOTH_T7__R_TL) + BOTH_T7_TL_TR, //# Fast arc top left to top right (use: BOTH_T7_TR_TL) + BOTH_T7_TL_T_, //# Fast arc top left to top (use: BOTH_T7_T__TL) + BOTH_T7_TL__L, //# Fast arc top left to left (use: BOTH_T7__L_TL) + BOTH_T7__L_TR, //# Fast arc left to top right (use: BOTH_T7_TR__L) + BOTH_T7__L_T_, //# Fast arc left to top (use: BOTH_T7_T___L) + BOTH_T7__L_BL, //# Fast arc left to bottom left (use: BOTH_T7_BL__L) + BOTH_T7_BL_T_, //# Fast arc bottom left to top (use: BOTH_T7_T__BL) + BOTH_T7_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T7_TL_BL) + //Saber Attack Start Transitions + BOTH_S7_S7_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S7_S7__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S7_S7__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S7_S7_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S7_S7_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S7_S7_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S7_S7_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R7_B__S7, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R7__L_S7, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R7__R_S7, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R7_TL_S7, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R7_BR_S7, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R7_BL_S7, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R7_TR_S7, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B7_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B7__R___, //# Bounce-back if attack from R is blocked + BOTH_B7_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B7_T____, //# Bounce-back if attack from T is blocked + BOTH_B7_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B7__L___, //# Bounce-back if attack from L is blocked + BOTH_B7_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D7_BR___, //# Deflection toward BR + BOTH_D7__R___, //# Deflection toward R + BOTH_D7_TR___, //# Deflection toward TR + BOTH_D7_TL___, //# Deflection toward TL + BOTH_D7__L___, //# Deflection toward L + BOTH_D7_BL___, //# Deflection toward BL + BOTH_D7_B____, //# Deflection toward B + //Saber parry anims + BOTH_P1_S1_T_, //# Block shot/saber top + BOTH_P1_S1_TR, //# Block shot/saber top right + BOTH_P1_S1_TL, //# Block shot/saber top left + BOTH_P1_S1_BL, //# Block shot/saber bottom left + BOTH_P1_S1_BR, //# Block shot/saber bottom right + //Saber knockaway + BOTH_K1_S1_T_, //# knockaway saber top + BOTH_K1_S1_TR, //# knockaway saber top right + BOTH_K1_S1_TL, //# knockaway saber top left + BOTH_K1_S1_BL, //# knockaway saber bottom left + BOTH_K1_S1_B_, //# knockaway saber bottom + BOTH_K1_S1_BR, //# knockaway saber bottom right + //Saber attack knocked away + BOTH_V1_BR_S1, //# BR attack knocked away + BOTH_V1__R_S1, //# R attack knocked away + BOTH_V1_TR_S1, //# TR attack knocked away + BOTH_V1_T__S1, //# T attack knocked away + BOTH_V1_TL_S1, //# TL attack knocked away + BOTH_V1__L_S1, //# L attack knocked away + BOTH_V1_BL_S1, //# BL attack knocked away + BOTH_V1_B__S1, //# B attack knocked away + //Saber parry broken + BOTH_H1_S1_T_, //# saber knocked down from top parry + BOTH_H1_S1_TR, //# saber knocked down-left from TR parry + BOTH_H1_S1_TL, //# saber knocked down-right from TL parry + BOTH_H1_S1_BL, //# saber knocked up-right from BL parry + BOTH_H1_S1_B_, //# saber knocked up over head from ready? + BOTH_H1_S1_BR, //# saber knocked up-left from BR parry + //Dual Saber parry anims + BOTH_P6_S6_T_, //# Block shot/saber top + BOTH_P6_S6_TR, //# Block shot/saber top right + BOTH_P6_S6_TL, //# Block shot/saber top left + BOTH_P6_S6_BL, //# Block shot/saber bottom left + BOTH_P6_S6_BR, //# Block shot/saber bottom right + //Dual Saber knockaway + BOTH_K6_S6_T_, //# knockaway saber top + BOTH_K6_S6_TR, //# knockaway saber top right + BOTH_K6_S6_TL, //# knockaway saber top left + BOTH_K6_S6_BL, //# knockaway saber bottom left + BOTH_K6_S6_B_, //# knockaway saber bottom + BOTH_K6_S6_BR, //# knockaway saber bottom right + //Dual Saber attack knocked away + BOTH_V6_BR_S6, //# BR attack knocked away + BOTH_V6__R_S6, //# R attack knocked away + BOTH_V6_TR_S6, //# TR attack knocked away + BOTH_V6_T__S6, //# T attack knocked away + BOTH_V6_TL_S6, //# TL attack knocked away + BOTH_V6__L_S6, //# L attack knocked away + BOTH_V6_BL_S6, //# BL attack knocked away + BOTH_V6_B__S6, //# B attack knocked away + //Dual Saber parry broken + BOTH_H6_S6_T_, //# saber knocked down from top parry + BOTH_H6_S6_TR, //# saber knocked down-left from TR parry + BOTH_H6_S6_TL, //# saber knocked down-right from TL parry + BOTH_H6_S6_BL, //# saber knocked up-right from BL parry + BOTH_H6_S6_B_, //# saber knocked up over head from ready? + BOTH_H6_S6_BR, //# saber knocked up-left from BR parry + //SaberStaff parry anims + BOTH_P7_S7_T_, //# Block shot/saber top + BOTH_P7_S7_TR, //# Block shot/saber top right + BOTH_P7_S7_TL, //# Block shot/saber top left + BOTH_P7_S7_BL, //# Block shot/saber bottom left + BOTH_P7_S7_BR, //# Block shot/saber bottom right + //SaberStaff knockaway + BOTH_K7_S7_T_, //# knockaway saber top + BOTH_K7_S7_TR, //# knockaway saber top right + BOTH_K7_S7_TL, //# knockaway saber top left + BOTH_K7_S7_BL, //# knockaway saber bottom left + BOTH_K7_S7_B_, //# knockaway saber bottom + BOTH_K7_S7_BR, //# knockaway saber bottom right + //SaberStaff attack knocked away + BOTH_V7_BR_S7, //# BR attack knocked away + BOTH_V7__R_S7, //# R attack knocked away + BOTH_V7_TR_S7, //# TR attack knocked away + BOTH_V7_T__S7, //# T attack knocked away + BOTH_V7_TL_S7, //# TL attack knocked away + BOTH_V7__L_S7, //# L attack knocked away + BOTH_V7_BL_S7, //# BL attack knocked away + BOTH_V7_B__S7, //# B attack knocked away + //SaberStaff parry broken + BOTH_H7_S7_T_, //# saber knocked down from top parry + BOTH_H7_S7_TR, //# saber knocked down-left from TR parry + BOTH_H7_S7_TL, //# saber knocked down-right from TL parry + BOTH_H7_S7_BL, //# saber knocked up-right from BL parry + BOTH_H7_S7_B_, //# saber knocked up over head from ready? + BOTH_H7_S7_BR, //# saber knocked up-left from BR parry + //Sabers locked anims + //* #sep BOTH_ SABER LOCKED ANIMS + //BOTH_(DL, S, ST)_(DL, S, ST)_(T, S)_(L, B, SB)_1(_W, _L) +//===Single locks================================================================== +//SINGLE vs. DUAL + //side locks - I'm using a single and they're using dual + BOTH_LK_S_DL_S_B_1_L, //normal break I lost + BOTH_LK_S_DL_S_B_1_W, //normal break I won + BOTH_LK_S_DL_S_L_1, //lock if I'm using single vs. a dual + BOTH_LK_S_DL_S_SB_1_L, //super break I lost + BOTH_LK_S_DL_S_SB_1_W, //super break I won + //top locks + BOTH_LK_S_DL_T_B_1_L, //normal break I lost + BOTH_LK_S_DL_T_B_1_W, //normal break I won + BOTH_LK_S_DL_T_L_1, //lock if I'm using single vs. a dual + BOTH_LK_S_DL_T_SB_1_L, //super break I lost + BOTH_LK_S_DL_T_SB_1_W, //super break I won +//SINGLE vs. STAFF + //side locks + BOTH_LK_S_ST_S_B_1_L, //normal break I lost + BOTH_LK_S_ST_S_B_1_W, //normal break I won + BOTH_LK_S_ST_S_L_1, //lock if I'm using single vs. a staff + BOTH_LK_S_ST_S_SB_1_L, //super break I lost + BOTH_LK_S_ST_S_SB_1_W, //super break I won + //top locks + BOTH_LK_S_ST_T_B_1_L, //normal break I lost + BOTH_LK_S_ST_T_B_1_W, //normal break I won + BOTH_LK_S_ST_T_L_1, //lock if I'm using single vs. a staff + BOTH_LK_S_ST_T_SB_1_L, //super break I lost + BOTH_LK_S_ST_T_SB_1_W, //super break I won +//SINGLE vs. SINGLE + //side locks + BOTH_LK_S_S_S_B_1_L, //normal break I lost + BOTH_LK_S_S_S_B_1_W, //normal break I won + BOTH_LK_S_S_S_L_1, //lock if I'm using single vs. a single and I initiated + BOTH_LK_S_S_S_SB_1_L, //super break I lost + BOTH_LK_S_S_S_SB_1_W, //super break I won + //top locks + BOTH_LK_S_S_T_B_1_L, //normal break I lost + BOTH_LK_S_S_T_B_1_W, //normal break I won + BOTH_LK_S_S_T_L_1, //lock if I'm using single vs. a single and I initiated + BOTH_LK_S_S_T_SB_1_L, //super break I lost + BOTH_LK_S_S_T_SB_1_W, //super break I won +//===Dual Saber locks================================================================== +//DUAL vs. DUAL + //side locks + BOTH_LK_DL_DL_S_B_1_L, //normal break I lost + BOTH_LK_DL_DL_S_B_1_W, //normal break I won + BOTH_LK_DL_DL_S_L_1, //lock if I'm using dual vs. dual and I initiated + BOTH_LK_DL_DL_S_SB_1_L, //super break I lost + BOTH_LK_DL_DL_S_SB_1_W, //super break I won + //top locks + BOTH_LK_DL_DL_T_B_1_L, //normal break I lost + BOTH_LK_DL_DL_T_B_1_W, //normal break I won + BOTH_LK_DL_DL_T_L_1, //lock if I'm using dual vs. dual and I initiated + BOTH_LK_DL_DL_T_SB_1_L, //super break I lost + BOTH_LK_DL_DL_T_SB_1_W, //super break I won +//DUAL vs. STAFF + //side locks + BOTH_LK_DL_ST_S_B_1_L, //normal break I lost + BOTH_LK_DL_ST_S_B_1_W, //normal break I won + BOTH_LK_DL_ST_S_L_1, //lock if I'm using dual vs. a staff + BOTH_LK_DL_ST_S_SB_1_L, //super break I lost + BOTH_LK_DL_ST_S_SB_1_W, //super break I won + //top locks + BOTH_LK_DL_ST_T_B_1_L, //normal break I lost + BOTH_LK_DL_ST_T_B_1_W, //normal break I won + BOTH_LK_DL_ST_T_L_1, //lock if I'm using dual vs. a staff + BOTH_LK_DL_ST_T_SB_1_L, //super break I lost + BOTH_LK_DL_ST_T_SB_1_W, //super break I won +//DUAL vs. SINGLE + //side locks + BOTH_LK_DL_S_S_B_1_L, //normal break I lost + BOTH_LK_DL_S_S_B_1_W, //normal break I won + BOTH_LK_DL_S_S_L_1, //lock if I'm using dual vs. a single + BOTH_LK_DL_S_S_SB_1_L, //super break I lost + BOTH_LK_DL_S_S_SB_1_W, //super break I won + //top locks + BOTH_LK_DL_S_T_B_1_L, //normal break I lost + BOTH_LK_DL_S_T_B_1_W, //normal break I won + BOTH_LK_DL_S_T_L_1, //lock if I'm using dual vs. a single + BOTH_LK_DL_S_T_SB_1_L, //super break I lost + BOTH_LK_DL_S_T_SB_1_W, //super break I won +//===Saber Staff locks================================================================== +//STAFF vs. DUAL + //side locks + BOTH_LK_ST_DL_S_B_1_L, //normal break I lost + BOTH_LK_ST_DL_S_B_1_W, //normal break I won + BOTH_LK_ST_DL_S_L_1, //lock if I'm using staff vs. dual + BOTH_LK_ST_DL_S_SB_1_L, //super break I lost + BOTH_LK_ST_DL_S_SB_1_W, //super break I won + //top locks + BOTH_LK_ST_DL_T_B_1_L, //normal break I lost + BOTH_LK_ST_DL_T_B_1_W, //normal break I won + BOTH_LK_ST_DL_T_L_1, //lock if I'm using staff vs. dual + BOTH_LK_ST_DL_T_SB_1_L, //super break I lost + BOTH_LK_ST_DL_T_SB_1_W, //super break I won +//STAFF vs. STAFF + //side locks + BOTH_LK_ST_ST_S_B_1_L, //normal break I lost + BOTH_LK_ST_ST_S_B_1_W, //normal break I won + BOTH_LK_ST_ST_S_L_1, //lock if I'm using staff vs. a staff and I initiated + BOTH_LK_ST_ST_S_SB_1_L, //super break I lost + BOTH_LK_ST_ST_S_SB_1_W, //super break I won + //top locks + BOTH_LK_ST_ST_T_B_1_L, //normal break I lost + BOTH_LK_ST_ST_T_B_1_W, //normal break I won + BOTH_LK_ST_ST_T_L_1, //lock if I'm using staff vs. a staff and I initiated + BOTH_LK_ST_ST_T_SB_1_L, //super break I lost + BOTH_LK_ST_ST_T_SB_1_W, //super break I won +//STAFF vs. SINGLE + //side locks + BOTH_LK_ST_S_S_B_1_L, //normal break I lost + BOTH_LK_ST_S_S_B_1_W, //normal break I won + BOTH_LK_ST_S_S_L_1, //lock if I'm using staff vs. a single + BOTH_LK_ST_S_S_SB_1_L, //super break I lost + BOTH_LK_ST_S_S_SB_1_W, //super break I won + //top locks + BOTH_LK_ST_S_T_B_1_L, //normal break I lost + BOTH_LK_ST_S_T_B_1_W, //normal break I won + BOTH_LK_ST_S_T_L_1, //lock if I'm using staff vs. a single + BOTH_LK_ST_S_T_SB_1_L, //super break I lost + BOTH_LK_ST_S_T_SB_1_W, //super break I won +//Special cases for same saber style vs. each other (won't fit in nice 5-anim size lists above) + BOTH_LK_S_S_S_L_2, //lock if I'm using single vs. a single and other intitiated + BOTH_LK_S_S_T_L_2, //lock if I'm using single vs. a single and other initiated + BOTH_LK_DL_DL_S_L_2, //lock if I'm using dual vs. dual and other initiated + BOTH_LK_DL_DL_T_L_2, //lock if I'm using dual vs. dual and other initiated + BOTH_LK_ST_ST_S_L_2, //lock if I'm using staff vs. a staff and other initiated + BOTH_LK_ST_ST_T_L_2, //lock if I'm using staff vs. a staff and other initiated +//===End Saber locks================================================================== + //old locks + BOTH_BF2RETURN, //# + BOTH_BF2BREAK, //# + BOTH_BF2LOCK, //# + BOTH_BF1RETURN, //# + BOTH_BF1BREAK, //# + BOTH_BF1LOCK, //# + BOTH_CWCIRCLE_R2__R_S1, //# + BOTH_CCWCIRCLE_R2__L_S1, //# + BOTH_CWCIRCLE_A2__L__R, //# + BOTH_CCWCIRCLE_A2__R__L, //# + BOTH_CWCIRCLEBREAK, //# + BOTH_CCWCIRCLEBREAK, //# + BOTH_CWCIRCLELOCK, //# + BOTH_CCWCIRCLELOCK, //# + //other saber anims + //* #sep BOTH_ SABER MISC ANIMS + BOTH_SABERFAST_STANCE, + BOTH_SABERSLOW_STANCE, + BOTH_SABERDUAL_STANCE, + BOTH_SABERSTAFF_STANCE, + BOTH_A2_STABBACK1, //# Stab saber backward + BOTH_ATTACK_BACK, //# Swing around backwards and attack + BOTH_JUMPFLIPSLASHDOWN1,//# + BOTH_JUMPFLIPSTABDOWN,//# + BOTH_FORCELEAP2_T__B_,//# + BOTH_LUNGE2_B__T_,//# + BOTH_CROUCHATTACKBACK1,//# + //New specials for JKA: + BOTH_JUMPATTACK6,//# + BOTH_JUMPATTACK7,//# + BOTH_SPINATTACK6,//# + BOTH_SPINATTACK7,//# + BOTH_S1_S6,//# From stand1 to saberdual stance - turning on your dual sabers + BOTH_S6_S1,//# From dualstaff stance to stand1 - turning off your dual sabers + BOTH_S1_S7,//# From stand1 to saberstaff stance - turning on your saberstaff + BOTH_S7_S1,//# From saberstaff stance to stand1 - turning off your saberstaff + BOTH_FORCELONGLEAP_START, + BOTH_FORCELONGLEAP_ATTACK, + BOTH_FORCELONGLEAP_LAND, + BOTH_FORCEWALLRUNFLIP_START, + BOTH_FORCEWALLRUNFLIP_END, + BOTH_FORCEWALLRUNFLIP_ALT, + BOTH_FORCEWALLREBOUND_FORWARD, + BOTH_FORCEWALLREBOUND_LEFT, + BOTH_FORCEWALLREBOUND_BACK, + BOTH_FORCEWALLREBOUND_RIGHT, + BOTH_FORCEWALLHOLD_FORWARD, + BOTH_FORCEWALLHOLD_LEFT, + BOTH_FORCEWALLHOLD_BACK, + BOTH_FORCEWALLHOLD_RIGHT, + BOTH_FORCEWALLRELEASE_FORWARD, + BOTH_FORCEWALLRELEASE_LEFT, + BOTH_FORCEWALLRELEASE_BACK, + BOTH_FORCEWALLRELEASE_RIGHT, + BOTH_A7_KICK_F, + BOTH_A7_KICK_B, + BOTH_A7_KICK_R, + BOTH_A7_KICK_L, + BOTH_A7_KICK_S, + BOTH_A7_KICK_BF, + BOTH_A7_KICK_BF_STOP, + BOTH_A7_KICK_RL, + BOTH_A7_KICK_F_AIR, + BOTH_A7_KICK_B_AIR, + BOTH_A7_KICK_R_AIR, + BOTH_A7_KICK_L_AIR, + BOTH_FLIP_ATTACK7, + BOTH_FLIP_HOLD7, + BOTH_FLIP_LAND, + BOTH_PULL_IMPALE_STAB, + BOTH_PULL_IMPALE_SWING, + BOTH_PULLED_INAIR_B, + BOTH_PULLED_INAIR_F, + BOTH_STABDOWN, + BOTH_STABDOWN_STAFF, + BOTH_STABDOWN_DUAL, + BOTH_A6_SABERPROTECT, + BOTH_A7_SOULCAL, + BOTH_A1_SPECIAL, + BOTH_A2_SPECIAL, + BOTH_A3_SPECIAL, + BOTH_ROLL_STAB, + + //# #sep BOTH_ STANDING + BOTH_STAND1, //# Standing idle, no weapon, hands down + BOTH_STAND1IDLE1, //# Random standing idle + BOTH_STAND2, //# Standing idle with a saber + BOTH_STAND2IDLE1, //# Random standing idle + BOTH_STAND2IDLE2, //# Random standing idle + BOTH_STAND3, //# Standing idle with 2-handed weapon + BOTH_STAND3IDLE1, //# Random standing idle + BOTH_STAND4, //# hands clasp behind back + BOTH_STAND5, //# standing idle, no weapon, hand down, back straight + BOTH_STAND5IDLE1, //# Random standing idle + BOTH_STAND6, //# one handed, gun at side, relaxed stand + BOTH_STAND8, //# both hands on hips (male) + BOTH_STAND1TO2, //# Transition from stand1 to stand2 + BOTH_STAND2TO1, //# Transition from stand2 to stand1 + BOTH_STAND2TO4, //# Transition from stand2 to stand4 + BOTH_STAND4TO2, //# Transition from stand4 to stand2 + BOTH_STAND4TOATTACK2, //# relaxed stand to 1-handed pistol ready + BOTH_STANDUP2, //# Luke standing up from his meditation platform (cin # 37) + BOTH_STAND5TOSIT3, //# transition from stand 5 to sit 3 + BOTH_STAND1TOSTAND5, //# Transition from stand1 to stand5 + BOTH_STAND5TOSTAND1, //# Transition from stand5 to stand1 + BOTH_STAND5TOAIM, //# Transition of Kye aiming his gun at Desann (cin #9) + BOTH_STAND5STARTLEDLOOKLEFT, //# Kyle turning to watch the bridge drop (cin #9) + BOTH_STARTLEDLOOKLEFTTOSTAND5, //# Kyle returning to stand 5 from watching the bridge drop (cin #9) + BOTH_STAND5TOSTAND8, //# Transition from stand5 to stand8 + BOTH_STAND7TOSTAND8, //# Tavion putting hands on back of chair (cin #11) + BOTH_STAND8TOSTAND5, //# Transition from stand8 to stand5 + BOTH_STAND9, //# Kyle's standing idle, no weapon, hands down + BOTH_STAND9IDLE1, //# Kyle's random standing idle + BOTH_STAND5SHIFTWEIGHT, //# Weightshift from stand5 to side and back to stand5 + BOTH_STAND5SHIFTWEIGHTSTART, //# From stand5 to side + BOTH_STAND5SHIFTWEIGHTSTOP, //# From side to stand5 + BOTH_STAND5TURNLEFTSTART, //# Start turning left from stand5 + BOTH_STAND5TURNLEFTSTOP, //# Stop turning left from stand5 + BOTH_STAND5TURNRIGHTSTART, //# Start turning right from stand5 + BOTH_STAND5TURNRIGHTSTOP, //# Stop turning right from stand5 + BOTH_STAND5LOOK180LEFTSTART, //# Start looking over left shoulder (cin #17) + BOTH_STAND5LOOK180LEFTSTOP, //# Stop looking over left shoulder (cin #17) + + BOTH_CONSOLE1START, //# typing at a console + BOTH_CONSOLE1, //# typing at a console + BOTH_CONSOLE1STOP, //# typing at a console + BOTH_CONSOLE2START, //# typing at a console with comm link in hand (cin #5) + BOTH_CONSOLE2, //# typing at a console with comm link in hand (cin #5) + BOTH_CONSOLE2STOP, //# typing at a console with comm link in hand (cin #5) + BOTH_CONSOLE2HOLDCOMSTART, //# lean in to type at console while holding comm link in hand (cin #5) + BOTH_CONSOLE2HOLDCOMSTOP, //# lean away after typing at console while holding comm link in hand (cin #5) + + BOTH_GUARD_LOOKAROUND1, //# Cradling weapon and looking around + BOTH_GUARD_IDLE1, //# Cradling weapon and standing + BOTH_GESTURE1, //# Generic gesture, non-specific + BOTH_GESTURE2, //# Generic gesture, non-specific + BOTH_WALK1TALKCOMM1, //# Talking into coom link while walking + BOTH_TALK1, //# Generic talk anim + BOTH_TALK2, //# Generic talk anim + BOTH_TALKCOMM1START, //# Start talking into a comm link + BOTH_TALKCOMM1, //# Talking into a comm link + BOTH_TALKCOMM1STOP, //# Stop talking into a comm link + BOTH_TALKGESTURE1, //# Generic talk anim + + BOTH_HEADTILTLSTART, //# Head tilt to left + BOTH_HEADTILTLSTOP, //# Head tilt to left + BOTH_HEADTILTRSTART, //# Head tilt to right + BOTH_HEADTILTRSTOP, //# Head tilt to right + BOTH_HEADNOD, //# Head shake YES + BOTH_HEADSHAKE, //# Head shake NO + BOTH_SIT2HEADTILTLSTART, //# Head tilt to left from seated position 2 + BOTH_SIT2HEADTILTLSTOP, //# Head tilt to left from seated position 2 + + BOTH_REACH1START, //# Monmothma reaching for crystal + BOTH_REACH1STOP, //# Monmothma reaching for crystal + + BOTH_COME_ON1, //# Jan gesturing to Kyle (cin #32a) + BOTH_STEADYSELF1, //# Jan trying to keep footing (cin #32a) + BOTH_STEADYSELF1END, //# Return hands to side from STEADSELF1 Kyle (cin#5) + BOTH_SILENCEGESTURE1, //# Luke silencing Kyle with a raised hand (cin #37) + BOTH_REACHFORSABER1, //# Luke holding hand out for Kyle's saber (cin #37) + BOTH_SABERKILLER1, //# Tavion about to strike Jan with saber (cin #9) + BOTH_SABERKILLEE1, //# Jan about to be struck by Tavion with saber (cin #9) + BOTH_HUGGER1, //# Kyle hugging Jan (cin #29) + BOTH_HUGGERSTOP1, //# Kyle stop hugging Jan but don't let her go (cin #29) + BOTH_HUGGEE1, //# Jan being hugged (cin #29) + BOTH_HUGGEESTOP1, //# Jan stop being hugged but don't let go (cin #29) + + BOTH_SABERTHROW1START, //# Desann throwing his light saber (cin #26) + BOTH_SABERTHROW1STOP, //# Desann throwing his light saber (cin #26) + BOTH_SABERTHROW2START, //# Kyle throwing his light saber (cin #32) + BOTH_SABERTHROW2STOP, //# Kyle throwing his light saber (cin #32) + + //# #sep BOTH_ SITTING/CROUCHING + BOTH_SIT1, //# Normal chair sit. + BOTH_SIT2, //# Lotus position. + BOTH_SIT3, //# Sitting in tired position, elbows on knees + + BOTH_SIT2TOSTAND5, //# Transition from sit 2 to stand 5 + BOTH_STAND5TOSIT2, //# Transition from stand 5 to sit 2 + BOTH_SIT2TOSIT4, //# Trans from sit2 to sit4 (cin #12) Luke leaning back from lotus position. + BOTH_SIT3TOSTAND5, //# transition from sit 3 to stand 5 + + BOTH_CROUCH1, //# Transition from standing to crouch + BOTH_CROUCH1IDLE, //# Crouching idle + BOTH_CROUCH1WALK, //# Walking while crouched + BOTH_CROUCH1WALKBACK, //# Walking while crouched + BOTH_UNCROUCH1, //# Transition from crouch to standing + BOTH_CROUCH2TOSTAND1, //# going from crouch2 to stand1 + BOTH_CROUCH3, //# Desann crouching down to Kyle (cin 9) + BOTH_UNCROUCH3, //# Desann uncrouching down to Kyle (cin 9) + BOTH_CROUCH4, //# Slower version of crouch1 for cinematics + BOTH_UNCROUCH4, //# Slower version of uncrouch1 for cinematics + + BOTH_GUNSIT1, //# sitting on an emplaced gun. + + // Swoop Vehicle animations. + //* #sep BOTH_ SWOOP ANIMS + BOTH_VS_MOUNT_L, //# Mount from left + BOTH_VS_DISMOUNT_L, //# Dismount to left + BOTH_VS_MOUNT_R, //# Mount from right (symmetry) + BOTH_VS_DISMOUNT_R, //# DISMOUNT TO RIGHT (SYMMETRY) + + BOTH_VS_MOUNTJUMP_L, //# + BOTH_VS_MOUNTTHROW, //# Land on an occupied vehicle & throw off current pilot + BOTH_VS_MOUNTTHROW_L, //# Land on an occupied vehicle & throw off current pilot + BOTH_VS_MOUNTTHROW_R, //# Land on an occupied vehicle & throw off current pilot + BOTH_VS_MOUNTTHROWEE, //# Current pilot getting thrown off by another guy + + BOTH_VS_LOOKLEFT, //# Turn & Look behind and to the left (no weapon) + BOTH_VS_LOOKRIGHT, //# Turn & Look behind and to the right (no weapon) + + BOTH_VS_TURBO, //# Hit The Turbo Button + + BOTH_VS_REV, //# Player looks back as swoop reverses + + BOTH_VS_AIR, //# Player stands up when swoop is airborn + BOTH_VS_AIR_G, //# "" with Gun + BOTH_VS_AIR_SL, //# "" with Saber Left + BOTH_VS_AIR_SR, //# "" with Saber Right + + BOTH_VS_LAND, //# Player bounces down when swoop lands + BOTH_VS_LAND_G, //# "" with Gun + BOTH_VS_LAND_SL, //# "" with Saber Left + BOTH_VS_LAND_SR, //# "" with Saber Right + + BOTH_VS_IDLE, //# Sit + BOTH_VS_IDLE_G, //# Sit (gun) + BOTH_VS_IDLE_SL, //# Sit (saber left) + BOTH_VS_IDLE_SR, //# Sit (saber right) + + BOTH_VS_LEANL, //# Lean left + BOTH_VS_LEANL_G, //# Lean left (gun) + BOTH_VS_LEANL_SL, //# Lean left (saber left) + BOTH_VS_LEANL_SR, //# Lean left (saber right) + + BOTH_VS_LEANR, //# Lean right + BOTH_VS_LEANR_G, //# Lean right (gun) + BOTH_VS_LEANR_SL, //# Lean right (saber left) + BOTH_VS_LEANR_SR, //# Lean right (saber right) + + BOTH_VS_ATL_S, //# Attack left with saber + BOTH_VS_ATR_S, //# Attack right with saber + BOTH_VS_ATR_TO_L_S, //# Attack toss saber from right to left hand + BOTH_VS_ATL_TO_R_S, //# Attack toss saber from left to right hand + BOTH_VS_ATR_G, //# Attack right with gun (90) + BOTH_VS_ATL_G, //# Attack left with gun (90) + BOTH_VS_ATF_G, //# Attack forward with gun + + BOTH_VS_PAIN1, //# Pain + + // Added 12/04/02 by Aurelio. + //* #sep BOTH_ TAUNTAUN ANIMS + BOTH_VT_MOUNT_L, //# Mount from left + BOTH_VT_MOUNT_R, //# Mount from right + BOTH_VT_MOUNT_B, //# Mount from air, behind + BOTH_VT_DISMOUNT, //# Dismount for tauntaun + BOTH_VT_DISMOUNT_L, //# Dismount to tauntauns left + BOTH_VT_DISMOUNT_R, //# Dismount to tauntauns right (symmetry) + + BOTH_VT_WALK_FWD, //# Walk forward + BOTH_VT_WALK_REV, //# Walk backward + BOTH_VT_WALK_FWD_L, //# walk lean left + BOTH_VT_WALK_FWD_R, //# Walk lean right + BOTH_VT_RUN_FWD, //# Run forward + BOTH_VT_RUN_REV, //# Look backwards while running (not weapon specific) + BOTH_VT_RUN_FWD_L, //# Run lean left + BOTH_VT_RUN_FWD_R, //# Run lean right + + BOTH_VT_SLIDEF, //# Tauntaun slides forward with abrupt stop + BOTH_VT_AIR, //# Tauntaun jump + BOTH_VT_ATB, //# Tauntaun tail swipe + BOTH_VT_PAIN1, //# Pain + BOTH_VT_DEATH1, //# Die + BOTH_VT_STAND, //# Stand still and breath + BOTH_VT_BUCK, //# Tauntaun bucking loop animation + + BOTH_VT_LAND, //# Player bounces down when tauntaun lands + BOTH_VT_TURBO, //# Hit The Turbo Button + BOTH_VT_IDLE_SL, //# Sit (saber left) + BOTH_VT_IDLE_SR, //# Sit (saber right) + + BOTH_VT_IDLE, //# Sit with no weapon selected + BOTH_VT_IDLE1, //# Sit with no weapon selected + BOTH_VT_IDLE_S, //# Sit with saber selected + BOTH_VT_IDLE_G, //# Sit with gun selected + BOTH_VT_IDLE_T, //# Sit with thermal grenade selected + + BOTH_VT_ATL_S, //# Attack left with saber + BOTH_VT_ATR_S, //# Attack right with saber + BOTH_VT_ATR_TO_L_S, //# Attack toss saber from right to left hand + BOTH_VT_ATL_TO_R_S, //# Attack toss saber from left to right hand + BOTH_VT_ATR_G, //# Attack right with gun (90) + BOTH_VT_ATL_G, //# Attack left with gun (90) + BOTH_VT_ATF_G, //# Attack forward with gun + + + // Added 2/26/02 by Aurelio. + //* #sep BOTH_ FIGHTER ANIMS + BOTH_GEARS_OPEN, + BOTH_GEARS_CLOSE, + BOTH_WINGS_OPEN, + BOTH_WINGS_CLOSE, + + BOTH_DEATH14_UNGRIP, //# Desann's end death (cin #35) + BOTH_DEATH14_SITUP, //# Tavion sitting up after having been thrown (cin #23) + BOTH_KNEES1, //# Tavion on her knees + BOTH_KNEES2, //# Tavion on her knees looking down + BOTH_KNEES2TO1, //# Transition of KNEES2 to KNEES1 + + //# #sep BOTH_ MOVING + BOTH_WALK1, //# Normal walk + BOTH_WALK2, //# Normal walk + BOTH_WALK_STAFF, //# Walk with saberstaff turned on + BOTH_WALKBACK_STAFF, //# Walk backwards with saberstaff turned on + BOTH_WALK_DUAL, //# Walk with dual turned on + BOTH_WALKBACK_DUAL, //# Walk backwards with dual turned on + BOTH_WALK5, //# Tavion taunting Kyle (cin 22) + BOTH_WALK6, //# Slow walk for Luke (cin 12) + BOTH_WALK7, //# Fast walk + BOTH_RUN1, //# Full run + BOTH_RUN1START, //# Start into full run1 + BOTH_RUN1STOP, //# Stop from full run1 + BOTH_RUN2, //# Full run + BOTH_RUN1TORUN2, //# Wampa run anim transition + BOTH_RUN2TORUN1, //# Wampa run anim transition + BOTH_RUN4, //# Jawa Run + BOTH_RUN_STAFF, //# Run with saberstaff turned on + BOTH_RUNBACK_STAFF, //# Run backwards with saberstaff turned on + BOTH_RUN_DUAL, //# Run with dual turned on + BOTH_RUNBACK_DUAL, //# Run backwards with dual turned on + BOTH_STRAFE_LEFT1, //# Sidestep left, should loop + BOTH_STRAFE_RIGHT1, //# Sidestep right, should loop + BOTH_RUNSTRAFE_LEFT1, //# Sidestep left, should loop + BOTH_RUNSTRAFE_RIGHT1, //# Sidestep right, should loop + BOTH_TURN_LEFT1, //# Turn left, should loop + BOTH_TURN_RIGHT1, //# Turn right, should loop + BOTH_TURNSTAND1, //# Turn from STAND1 position + BOTH_TURNSTAND2, //# Turn from STAND2 position + BOTH_TURNSTAND3, //# Turn from STAND3 position + BOTH_TURNSTAND4, //# Turn from STAND4 position + BOTH_TURNSTAND5, //# Turn from STAND5 position + BOTH_TURNCROUCH1, //# Turn from CROUCH1 position + + BOTH_WALKBACK1, //# Walk1 backwards + BOTH_WALKBACK2, //# Walk2 backwards + BOTH_RUNBACK1, //# Run1 backwards + BOTH_RUNBACK2, //# Run1 backwards + + //# #sep BOTH_ JUMPING + BOTH_JUMP1, //# Jump - wind-up and leave ground + BOTH_INAIR1, //# In air loop (from jump) + BOTH_LAND1, //# Landing (from in air loop) + BOTH_LAND2, //# Landing Hard (from a great height) + + BOTH_JUMPBACK1, //# Jump backwards - wind-up and leave ground + BOTH_INAIRBACK1, //# In air loop (from jump back) + BOTH_LANDBACK1, //# Landing backwards(from in air loop) + + BOTH_JUMPLEFT1, //# Jump left - wind-up and leave ground + BOTH_INAIRLEFT1, //# In air loop (from jump left) + BOTH_LANDLEFT1, //# Landing left(from in air loop) + + BOTH_JUMPRIGHT1, //# Jump right - wind-up and leave ground + BOTH_INAIRRIGHT1, //# In air loop (from jump right) + BOTH_LANDRIGHT1, //# Landing right(from in air loop) + + BOTH_FORCEJUMP1, //# Jump - wind-up and leave ground + BOTH_FORCEINAIR1, //# In air loop (from jump) + BOTH_FORCELAND1, //# Landing (from in air loop) + + BOTH_FORCEJUMPBACK1, //# Jump backwards - wind-up and leave ground + BOTH_FORCEINAIRBACK1, //# In air loop (from jump back) + BOTH_FORCELANDBACK1, //# Landing backwards(from in air loop) + + BOTH_FORCEJUMPLEFT1, //# Jump left - wind-up and leave ground + BOTH_FORCEINAIRLEFT1, //# In air loop (from jump left) + BOTH_FORCELANDLEFT1, //# Landing left(from in air loop) + + BOTH_FORCEJUMPRIGHT1, //# Jump right - wind-up and leave ground + BOTH_FORCEINAIRRIGHT1, //# In air loop (from jump right) + BOTH_FORCELANDRIGHT1, //# Landing right(from in air loop) + //# #sep BOTH_ ACROBATICS + BOTH_FLIP_F, //# Flip forward + BOTH_FLIP_B, //# Flip backwards + BOTH_FLIP_L, //# Flip left + BOTH_FLIP_R, //# Flip right + + BOTH_ROLL_F, //# Roll forward + BOTH_ROLL_B, //# Roll backward + BOTH_ROLL_L, //# Roll left + BOTH_ROLL_R, //# Roll right + + BOTH_HOP_F, //# quickstep forward + BOTH_HOP_B, //# quickstep backwards + BOTH_HOP_L, //# quickstep left + BOTH_HOP_R, //# quickstep right + + BOTH_DODGE_FL, //# lean-dodge forward left + BOTH_DODGE_FR, //# lean-dodge forward right + BOTH_DODGE_BL, //# lean-dodge backwards left + BOTH_DODGE_BR, //# lean-dodge backwards right + BOTH_DODGE_L, //# lean-dodge left + BOTH_DODGE_R, //# lean-dodge right + BOTH_DODGE_HOLD_FL, //# lean-dodge pose forward left + BOTH_DODGE_HOLD_FR, //# lean-dodge pose forward right + BOTH_DODGE_HOLD_BL, //# lean-dodge pose backwards left + BOTH_DODGE_HOLD_BR, //# lean-dodge pose backwards right + BOTH_DODGE_HOLD_L, //# lean-dodge pose left + BOTH_DODGE_HOLD_R, //# lean-dodge pose right + + //MP taunt anims + BOTH_ENGAGETAUNT, + BOTH_BOW, + BOTH_MEDITATE, + BOTH_MEDITATE_END, + BOTH_SHOWOFF_FAST, + BOTH_SHOWOFF_MEDIUM, + BOTH_SHOWOFF_STRONG, + BOTH_SHOWOFF_DUAL, + BOTH_SHOWOFF_STAFF, + BOTH_VICTORY_FAST, + BOTH_VICTORY_MEDIUM, + BOTH_VICTORY_STRONG, + BOTH_VICTORY_DUAL, + BOTH_VICTORY_STAFF, + //other saber/acro anims + BOTH_ARIAL_LEFT, //# + BOTH_ARIAL_RIGHT, //# + BOTH_CARTWHEEL_LEFT, //# + BOTH_CARTWHEEL_RIGHT, //# + BOTH_FLIP_LEFT, //# + BOTH_FLIP_BACK1, //# + BOTH_FLIP_BACK2, //# + BOTH_FLIP_BACK3, //# + BOTH_BUTTERFLY_LEFT, //# + BOTH_BUTTERFLY_RIGHT, //# + BOTH_WALL_RUN_RIGHT, //# + BOTH_WALL_RUN_RIGHT_FLIP,//# + BOTH_WALL_RUN_RIGHT_STOP,//# + BOTH_WALL_RUN_LEFT, //# + BOTH_WALL_RUN_LEFT_FLIP,//# + BOTH_WALL_RUN_LEFT_STOP,//# + BOTH_WALL_FLIP_RIGHT, //# + BOTH_WALL_FLIP_LEFT, //# + BOTH_KNOCKDOWN1, //# knocked backwards + BOTH_KNOCKDOWN2, //# knocked backwards hard + BOTH_KNOCKDOWN3, //# knocked forwards + BOTH_KNOCKDOWN4, //# knocked backwards from crouch + BOTH_KNOCKDOWN5, //# dupe of 3 - will be removed + BOTH_GETUP1, //# + BOTH_GETUP2, //# + BOTH_GETUP3, //# + BOTH_GETUP4, //# + BOTH_GETUP5, //# + BOTH_GETUP_CROUCH_F1, //# + BOTH_GETUP_CROUCH_B1, //# + BOTH_FORCE_GETUP_F1, //# + BOTH_FORCE_GETUP_F2, //# + BOTH_FORCE_GETUP_B1, //# + BOTH_FORCE_GETUP_B2, //# + BOTH_FORCE_GETUP_B3, //# + BOTH_FORCE_GETUP_B4, //# + BOTH_FORCE_GETUP_B5, //# + BOTH_FORCE_GETUP_B6, //# + BOTH_GETUP_BROLL_B, //# + BOTH_GETUP_BROLL_F, //# + BOTH_GETUP_BROLL_L, //# + BOTH_GETUP_BROLL_R, //# + BOTH_GETUP_FROLL_B, //# + BOTH_GETUP_FROLL_F, //# + BOTH_GETUP_FROLL_L, //# + BOTH_GETUP_FROLL_R, //# + BOTH_WALL_FLIP_BACK1, //# + BOTH_WALL_FLIP_BACK2, //# + BOTH_SPIN1, //# + BOTH_CEILING_CLING, //# clinging to ceiling + BOTH_CEILING_DROP, //# dropping from ceiling cling + + //TESTING + BOTH_FJSS_TR_BL, //# jump spin slash tr to bl + BOTH_FJSS_TL_BR, //# jump spin slash bl to tr + BOTH_RIGHTHANDCHOPPEDOFF,//# + BOTH_DEFLECTSLASH__R__L_FIN,//# + BOTH_BASHED1,//# + BOTH_ARIAL_F1,//# + BOTH_BUTTERFLY_FR1,//# + BOTH_BUTTERFLY_FL1,//# + + //NEW SABER/JEDI/FORCE ANIMS + BOTH_BACK_FLIP_UP, //# back flip up Bonus Animation!!!! + BOTH_LOSE_SABER, //# player losing saber (pulled from hand by force pull 4 - Kyle?) + BOTH_STAFF_TAUNT, //# taunt saberstaff + BOTH_DUAL_TAUNT, //# taunt dual + BOTH_A6_FB, //# dual attack front/back + BOTH_A6_LR, //# dual attack left/right + BOTH_A7_HILT, //# saber knock (alt + stand still) + //Alora + BOTH_ALORA_SPIN, //#jump spin attack death ballet + BOTH_ALORA_FLIP_1, //# gymnast move 1 + BOTH_ALORA_FLIP_2, //# gymnast move 2 + BOTH_ALORA_FLIP_3, //# gymnast move3 + BOTH_ALORA_FLIP_B, //# gymnast move back + BOTH_ALORA_SPIN_THROW, //# dual saber throw + BOTH_ALORA_SPIN_SLASH, //# spin slash special bonus animation!! :) + BOTH_ALORA_TAUNT, //# special taunt + //Rosh (Kothos battle) + BOTH_ROSH_PAIN, //# hurt animation (exhausted) + BOTH_ROSH_HEAL, //# healed/rejuvenated + //Tavion + BOTH_TAVION_SCEPTERGROUND, //# stabbing ground with sith sword shoots electricity everywhere + BOTH_TAVION_SWORDPOWER,//# Tavion doing the He-Man(tm) thing + BOTH_SCEPTER_START, //#Point scepter and attack start + BOTH_SCEPTER_HOLD, //#Point scepter and attack hold + BOTH_SCEPTER_STOP, //#Point scepter and attack stop + //Kyle Boss + BOTH_KYLE_GRAB, //# grab + BOTH_KYLE_MISS, //# miss + BOTH_KYLE_PA_1, //# hold 1 + BOTH_PLAYER_PA_1, //# player getting held 1 + BOTH_KYLE_PA_2, //# hold 2 + BOTH_PLAYER_PA_2, //# player getting held 2 + BOTH_PLAYER_PA_FLY, //# player getting knocked back from punch at end of hold 1 + BOTH_KYLE_PA_3, //# hold 3 + BOTH_PLAYER_PA_3, //# player getting held 3 + BOTH_PLAYER_PA_3_FLY,//# player getting thrown at end of hold 3 + //Rancor + BOTH_BUCK_RIDER, //# Rancor bucks when someone is on him + //WAMPA Grabbing enemy + BOTH_HOLD_START, //# + BOTH_HOLD_MISS, //# + BOTH_HOLD_IDLE, //# + BOTH_HOLD_END, //# + BOTH_HOLD_ATTACK, //# + BOTH_HOLD_SNIFF, //# Sniff the guy you're holding + BOTH_HOLD_DROP, //# just drop 'em + //BEING GRABBED BY WAMPA + BOTH_GRABBED, //# + BOTH_RELEASED, //# + BOTH_HANG_IDLE, //# + BOTH_HANG_ATTACK, //# + BOTH_HANG_PAIN, //# + + //# #sep BOTH_ MISC MOVEMENT + BOTH_HIT1, //# Kyle hit by crate in cin #9 + BOTH_LADDER_UP1, //# Climbing up a ladder with rungs at 16 unit intervals + BOTH_LADDER_DWN1, //# Climbing down a ladder with rungs at 16 unit intervals + BOTH_LADDER_IDLE, //# Just sitting on the ladder + + //# #sep BOTH_ FLYING IDLE + BOTH_FLY_SHIELDED, //# For sentry droid, shields in + + //# #sep BOTH_ SWIMMING + BOTH_SWIM_IDLE1, //# Swimming Idle 1 + BOTH_SWIMFORWARD, //# Swim forward loop + BOTH_SWIMBACKWARD, //# Swim backward loop + + //# #sep BOTH_ LYING + BOTH_SLEEP1, //# laying on back-rknee up-rhand on torso + BOTH_SLEEP6START, //# Kyle leaning back to sleep (cin 20) + BOTH_SLEEP6STOP, //# Kyle waking up and shaking his head (cin 21) + BOTH_SLEEP1GETUP, //# alarmed and getting up out of sleep1 pose to stand + BOTH_SLEEP1GETUP2, //# + + BOTH_CHOKE1START, //# tavion in force grip choke + BOTH_CHOKE1STARTHOLD, //# loop of tavion in force grip choke + BOTH_CHOKE1, //# tavion in force grip choke + + BOTH_CHOKE2, //# tavion recovering from force grip choke + BOTH_CHOKE3, //# left-handed choke (for people still holding a weapon) + + //# #sep BOTH_ HUNTER-SEEKER BOT-SPECIFIC + BOTH_POWERUP1, //# Wakes up + + BOTH_TURNON, //# Protocol Droid wakes up + BOTH_TURNOFF, //# Protocol Droid shuts off + + BOTH_BUTTON1, //# Single button push with right hand + BOTH_BUTTON2, //# Single button push with left finger + BOTH_BUTTON_HOLD, //# Single button hold with left hand + BOTH_BUTTON_RELEASE, //# Single button release with left hand + + //# JEDI-SPECIFIC + //# #sep BOTH_ FORCE ANIMS + BOTH_RESISTPUSH, //# plant yourself to resist force push/pulls. + BOTH_FORCEPUSH, //# Use off-hand to do force power. + BOTH_FORCEPULL, //# Use off-hand to do force power. + BOTH_MINDTRICK1, //# Use off-hand to do mind trick + BOTH_MINDTRICK2, //# Use off-hand to do distraction + BOTH_FORCELIGHTNING, //# Use off-hand to do lightning + BOTH_FORCELIGHTNING_START, //# Use off-hand to do lightning - start + BOTH_FORCELIGHTNING_HOLD, //# Use off-hand to do lightning - hold + BOTH_FORCELIGHTNING_RELEASE,//# Use off-hand to do lightning - release + BOTH_FORCEHEAL_START, //# Healing meditation pose start + BOTH_FORCEHEAL_STOP, //# Healing meditation pose end + BOTH_FORCEHEAL_QUICK, //# Healing meditation gesture + BOTH_SABERPULL, //# Use off-hand to do force power. + BOTH_FORCEGRIP1, //# force-gripping (no anim?) + BOTH_FORCEGRIP3, //# force-gripping (right hand) + BOTH_FORCEGRIP3THROW, //# throwing while force-gripping (right hand) + BOTH_FORCEGRIP_HOLD, //# Use off-hand to do grip - hold + BOTH_FORCEGRIP_RELEASE,//# Use off-hand to do grip - release + BOTH_TOSS1, //# throwing to left after force gripping + BOTH_TOSS2, //# throwing to right after force gripping + //NEW force anims for JKA: + BOTH_FORCE_RAGE, + BOTH_FORCE_2HANDEDLIGHTNING, + BOTH_FORCE_2HANDEDLIGHTNING_START, + BOTH_FORCE_2HANDEDLIGHTNING_HOLD, + BOTH_FORCE_2HANDEDLIGHTNING_RELEASE, + BOTH_FORCE_DRAIN, + BOTH_FORCE_DRAIN_START, + BOTH_FORCE_DRAIN_HOLD, + BOTH_FORCE_DRAIN_RELEASE, + BOTH_FORCE_DRAIN_GRAB_START, + BOTH_FORCE_DRAIN_GRAB_HOLD, + BOTH_FORCE_DRAIN_GRAB_END, + BOTH_FORCE_DRAIN_GRABBED, + BOTH_FORCE_ABSORB, + BOTH_FORCE_ABSORB_START, + BOTH_FORCE_ABSORB_END, + BOTH_FORCE_PROTECT, + BOTH_FORCE_PROTECT_FAST, + + BOTH_WIND, + + BOTH_STAND_TO_KNEEL, + BOTH_KNEEL_TO_STAND, + + BOTH_TUSKENATTACK1, + BOTH_TUSKENATTACK2, + BOTH_TUSKENATTACK3, + BOTH_TUSKENLUNGE1, + BOTH_TUSKENTAUNT1, + + BOTH_COWER1_START, //# cower start + BOTH_COWER1, //# cower loop + BOTH_COWER1_STOP, //# cower stop + BOTH_SONICPAIN_START, + BOTH_SONICPAIN_HOLD, + BOTH_SONICPAIN_END, + + //new anim slots per Jarrod's request + BOTH_STAND10, + BOTH_STAND10_TALK1, + BOTH_STAND10_TALK2, + BOTH_STAND10TOSTAND1, + + BOTH_STAND1_TALK1, + BOTH_STAND1_TALK2, + BOTH_STAND1_TALK3, + + BOTH_SIT4, + BOTH_SIT5, + BOTH_SIT5_TALK1, + BOTH_SIT5_TALK2, + BOTH_SIT5_TALK3, + + BOTH_SIT6, + BOTH_SIT7, + + //================================================= + //ANIMS IN WHICH ONLY THE UPPER OBJECTS ARE IN MD3 + //================================================= + //# #sep TORSO_ WEAPON-RELATED + TORSO_DROPWEAP1, //# Put weapon away + TORSO_DROPWEAP4, //# Put weapon away + TORSO_RAISEWEAP1, //# Draw Weapon + TORSO_RAISEWEAP4, //# Draw Weapon + TORSO_WEAPONREADY1, //# Ready to fire stun baton + TORSO_WEAPONREADY2, //# Ready to fire one-handed blaster pistol + TORSO_WEAPONREADY3, //# Ready to fire blaster rifle + TORSO_WEAPONREADY4, //# Ready to fire sniper rifle + TORSO_WEAPONREADY10, //# Ready to fire thermal det + TORSO_WEAPONIDLE2, //# Holding one-handed blaster + TORSO_WEAPONIDLE3, //# Holding blaster rifle + TORSO_WEAPONIDLE4, //# Holding sniper rifle + TORSO_WEAPONIDLE10, //# Holding thermal det + + //# #sep TORSO_ MISC + TORSO_SURRENDER_START, //# arms up + TORSO_SURRENDER_STOP, //# arms back down + + TORSO_CHOKING1, //# TEMP + + TORSO_HANDSIGNAL1, + TORSO_HANDSIGNAL2, + TORSO_HANDSIGNAL3, + TORSO_HANDSIGNAL4, + TORSO_HANDSIGNAL5, + + + //================================================= + //ANIMS IN WHICH ONLY THE LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep Legs-only anims + LEGS_TURN1, //# What legs do when you turn your lower body to match your upper body facing + LEGS_TURN2, //# Leg turning from stand2 + LEGS_LEAN_LEFT1, //# Lean left + LEGS_LEAN_RIGHT1, //# Lean Right + LEGS_CHOKING1, //# TEMP + LEGS_LEFTUP1, //# On a slope with left foot 4 higher than right + LEGS_LEFTUP2, //# On a slope with left foot 8 higher than right + LEGS_LEFTUP3, //# On a slope with left foot 12 higher than right + LEGS_LEFTUP4, //# On a slope with left foot 16 higher than right + LEGS_LEFTUP5, //# On a slope with left foot 20 higher than right + LEGS_RIGHTUP1, //# On a slope with RIGHT foot 4 higher than left + LEGS_RIGHTUP2, //# On a slope with RIGHT foot 8 higher than left + LEGS_RIGHTUP3, //# On a slope with RIGHT foot 12 higher than left + LEGS_RIGHTUP4, //# On a slope with RIGHT foot 16 higher than left + LEGS_RIGHTUP5, //# On a slope with RIGHT foot 20 higher than left + LEGS_S1_LUP1, + LEGS_S1_LUP2, + LEGS_S1_LUP3, + LEGS_S1_LUP4, + LEGS_S1_LUP5, + LEGS_S1_RUP1, + LEGS_S1_RUP2, + LEGS_S1_RUP3, + LEGS_S1_RUP4, + LEGS_S1_RUP5, + LEGS_S3_LUP1, + LEGS_S3_LUP2, + LEGS_S3_LUP3, + LEGS_S3_LUP4, + LEGS_S3_LUP5, + LEGS_S3_RUP1, + LEGS_S3_RUP2, + LEGS_S3_RUP3, + LEGS_S3_RUP4, + LEGS_S3_RUP5, + LEGS_S4_LUP1, + LEGS_S4_LUP2, + LEGS_S4_LUP3, + LEGS_S4_LUP4, + LEGS_S4_LUP5, + LEGS_S4_RUP1, + LEGS_S4_RUP2, + LEGS_S4_RUP3, + LEGS_S4_RUP4, + LEGS_S4_RUP5, + LEGS_S5_LUP1, + LEGS_S5_LUP2, + LEGS_S5_LUP3, + LEGS_S5_LUP4, + LEGS_S5_LUP5, + LEGS_S5_RUP1, + LEGS_S5_RUP2, + LEGS_S5_RUP3, + LEGS_S5_RUP4, + LEGS_S5_RUP5, + LEGS_S6_LUP1, + LEGS_S6_LUP2, + LEGS_S6_LUP3, + LEGS_S6_LUP4, + LEGS_S6_LUP5, + LEGS_S6_RUP1, + LEGS_S6_RUP2, + LEGS_S6_RUP3, + LEGS_S6_RUP4, + LEGS_S6_RUP5, + LEGS_S7_LUP1, + LEGS_S7_LUP2, + LEGS_S7_LUP3, + LEGS_S7_LUP4, + LEGS_S7_LUP5, + LEGS_S7_RUP1, + LEGS_S7_RUP2, + LEGS_S7_RUP3, + LEGS_S7_RUP4, + LEGS_S7_RUP5, + + //New anim as per Jarrod's request + LEGS_TURN180, + + //====================================================== + //cinematic anims + //====================================================== + //# #sep BOTH_ CINEMATIC-ONLY + BOTH_CIN_1, //# Level specific cinematic 1 + BOTH_CIN_2, //# Level specific cinematic 2 + BOTH_CIN_3, //# Level specific cinematic 3 + BOTH_CIN_4, //# Level specific cinematic 4 + BOTH_CIN_5, //# Level specific cinematic 5 + BOTH_CIN_6, //# Level specific cinematic 6 + BOTH_CIN_7, //# Level specific cinematic 7 + BOTH_CIN_8, //# Level specific cinematic 8 + BOTH_CIN_9, //# Level specific cinematic 9 + BOTH_CIN_10, //# Level specific cinematic 10 + BOTH_CIN_11, //# Level specific cinematic 11 + BOTH_CIN_12, //# Level specific cinematic 12 + BOTH_CIN_13, //# Level specific cinematic 13 + BOTH_CIN_14, //# Level specific cinematic 14 + BOTH_CIN_15, //# Level specific cinematic 15 + BOTH_CIN_16, //# Level specific cinematic 16 + BOTH_CIN_17, //# Level specific cinematic 17 + BOTH_CIN_18, //# Level specific cinematic 18 + BOTH_CIN_19, //# Level specific cinematic 19 + BOTH_CIN_20, //# Level specific cinematic 20 + BOTH_CIN_21, //# Level specific cinematic 21 + BOTH_CIN_22, //# Level specific cinematic 22 + BOTH_CIN_23, //# Level specific cinematic 23 + BOTH_CIN_24, //# Level specific cinematic 24 + BOTH_CIN_25, //# Level specific cinematic 25 + BOTH_CIN_26, //# Level specific cinematic + BOTH_CIN_27, //# Level specific cinematic + BOTH_CIN_28, //# Level specific cinematic + BOTH_CIN_29, //# Level specific cinematic + BOTH_CIN_30, //# Level specific cinematic + BOTH_CIN_31, //# Level specific cinematic + BOTH_CIN_32, //# Level specific cinematic + BOTH_CIN_33, //# Level specific cinematic + BOTH_CIN_34, //# Level specific cinematic + BOTH_CIN_35, //# Level specific cinematic + BOTH_CIN_36, //# Level specific cinematic + BOTH_CIN_37, //# Level specific cinematic + BOTH_CIN_38, //# Level specific cinematic + BOTH_CIN_39, //# Level specific cinematic + BOTH_CIN_40, //# Level specific cinematic + BOTH_CIN_41, //# Level specific cinematic + BOTH_CIN_42, //# Level specific cinematic + BOTH_CIN_43, //# Level specific cinematic + BOTH_CIN_44, //# Level specific cinematic + BOTH_CIN_45, //# Level specific cinematic + BOTH_CIN_46, //# Level specific cinematic + BOTH_CIN_47, //# Level specific cinematic + BOTH_CIN_48, //# Level specific cinematic + BOTH_CIN_49, //# Level specific cinematic + BOTH_CIN_50, //# Level specific cinematic + + //# #eol + MAX_ANIMATIONS, + MAX_TOTALANIMATIONS, +} animNumber_t; + +#define SABER_ANIM_GROUP_SIZE (BOTH_A2_T__B_ - BOTH_A1_T__B_) + + +#endif// #ifndef __ANIMS_H__ + diff --git a/codemp/game/b_local.h b/codemp/game/b_local.h new file mode 100644 index 0000000..2009869 --- /dev/null +++ b/codemp/game/b_local.h @@ -0,0 +1,329 @@ +//B_local.h +//re-added by MCG +#ifndef __B_LOCAL_H__ +#define __B_LOCAL_H__ + +#include "g_local.h" +#include "b_public.h" +#include "say.h" + +#include "ai.h" + +#define AI_TIMERS 0//turn on to see print-outs of AI/nav timing +// +// Navigation susbsystem +// + +#define NAVF_DUCK 0x00000001 +#define NAVF_JUMP 0x00000002 +#define NAVF_HOLD 0x00000004 +#define NAVF_SLOW 0x00000008 + +#define DEBUG_LEVEL_DETAIL 4 +#define DEBUG_LEVEL_INFO 3 +#define DEBUG_LEVEL_WARNING 2 +#define DEBUG_LEVEL_ERROR 1 +#define DEBUG_LEVEL_NONE 0 + +#define MAX_GOAL_REACHED_DIST_SQUARED 256//16 squared +#define MIN_ANGLE_ERROR 0.01f + +#define MIN_ROCKET_DIST_SQUARED 16384//128*128 +// +// NPC.cpp +// +// ai debug cvars +void SetNPCGlobals( gentity_t *ent ); +void SaveNPCGlobals(void); +void RestoreNPCGlobals(void); +extern vmCvar_t debugNPCAI; // used to print out debug info about the NPC AI +extern vmCvar_t debugNPCFreeze; // set to disable NPC ai and temporarily freeze them in place +extern vmCvar_t debugNPCAimingBeam; +extern vmCvar_t debugBreak; +extern vmCvar_t debugNoRoam; +extern vmCvar_t d_JediAI; +extern vmCvar_t d_saberCombat; + +extern void NPC_Think ( gentity_t *self); + +//NPC_reactions.cpp +extern void NPC_Pain(gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Touch( gentity_t *self, gentity_t *other, trace_t *trace ); +extern void NPC_Use( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern float NPC_GetPainChance( gentity_t *self, int damage ); + +// +// NPC_misc.cpp +// +extern void Debug_Printf( vmCvar_t *cv, int level, char *fmt, ... ); +extern void Debug_NPCPrintf( gentity_t *printNPC, vmCvar_t *cv, int debugLevel, char *fmt, ... ); + +//MCG - Begin============================================================ +//NPC_ai variables - shared by NPC.cpp andf the following modules +extern gentity_t *NPC; +extern gNPC_t *NPCInfo; +extern gclient_t *client; +extern usercmd_t ucmd; +extern visibility_t enemyVisibility; + +//AI_Default +extern qboolean NPC_CheckInvestigate( int alertEventNum ); +extern qboolean NPC_StandTrackAndShoot (gentity_t *NPC, qboolean canDuck); +extern void NPC_BSIdle( void ); +extern void NPC_BSPointShoot(qboolean shoot); +extern void NPC_BSStandGuard (void); +extern void NPC_BSPatrol (void); +extern void NPC_BSHuntAndKill (void); +extern void NPC_BSStandAndShoot (void); +extern void NPC_BSRunAndShoot (void); +extern void NPC_BSWait( void ); +extern void NPC_BSDefault( void ); + +//NPC_behavior +extern void NPC_BSAdvanceFight (void); +extern void NPC_BSInvestigate (void); +extern void NPC_BSSleep( void ); +extern void NPC_BSFlee (void); +extern void NPC_BSFollowLeader (void); +extern void NPC_BSJump (void); +extern void NPC_BSRemove (void); +extern void NPC_BSSearch (void); +extern void NPC_BSSearchStart (int homeWp, bState_t bState); +extern void NPC_BSWander (void); +extern void NPC_BSFlee( void ); +extern void NPC_StartFlee( gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ); +extern void G_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ); + +//NPC_combat +extern int ChooseBestWeapon( void ); +extern void NPC_ChangeWeapon( int newWeapon ); +extern void ShootThink( void ); +extern void WeaponThink( qboolean inCombat ); +extern qboolean HaveWeapon( int weapon ); +extern qboolean CanShoot ( gentity_t *ent, gentity_t *shooter ); +extern void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ); +extern gentity_t *NPC_PickEnemy (gentity_t *closestTo, int enemyTeam, qboolean checkVis, qboolean findPlayersFirst, qboolean findClosest); +extern gentity_t *NPC_CheckEnemy (qboolean findNew, qboolean tooFarOk, qboolean setEnemy ); //setEnemy = qtrue +extern qboolean NPC_CheckAttack (float scale); +extern qboolean NPC_CheckDefend (float scale); +extern qboolean NPC_CheckCanAttack (float attack_scale, qboolean stationary); +extern int NPC_AttackDebounceForWeapon (void); +extern qboolean EntIsGlass (gentity_t *check); +extern qboolean ShotThroughGlass (trace_t *tr, gentity_t *target, vec3_t spot, int mask); +extern qboolean ValidEnemy (gentity_t *ent); +extern void G_ClearEnemy (gentity_t *self); +extern gentity_t *NPC_PickAlly ( qboolean facingEachOther, float range, qboolean ignoreGroup, qboolean movingOnly ); +extern void NPC_LostEnemyDecideChase(void); +extern float NPC_MaxDistSquaredForWeapon( void ); +extern qboolean NPC_EvaluateShot( int hit, qboolean glassOK ); +extern int NPC_ShotEntity( gentity_t *ent, vec3_t impactPos ); //impactedPos = NULL + +//NPC_formation +extern qboolean NPC_SlideMoveToGoal (void); +extern float NPC_FindClosestTeammate (gentity_t *self); +extern void NPC_CalcClosestFormationSpot(gentity_t *self); +extern void G_MaintainFormations (gentity_t *self); +extern void NPC_BSFormation (void); +extern void NPC_CreateFormation (gentity_t *self); +extern void NPC_DropFormation (gentity_t *self); +extern void NPC_ReorderFormation (gentity_t *self); +extern void NPC_InsertIntoFormation (gentity_t *self); +extern void NPC_DeleteFromFormation (gentity_t *self); + +#define COLLISION_RADIUS 32 +#define NUM_POSITIONS 30 + +//NPC spawnflags +#define SFB_SMALLHULL 1 + +#define SFB_RIFLEMAN 2 +#define SFB_OLDBORG 2//Borg +#define SFB_PHASER 4 +#define SFB_GUN 4//Borg +#define SFB_TRICORDER 8 +#define SFB_TASER 8//Borg +#define SFB_DRILL 16//Borg + +#define SFB_CINEMATIC 32 +#define SFB_NOTSOLID 64 +#define SFB_STARTINSOLID 128 + +//NPC_goal +extern void SetGoal( gentity_t *goal, float rating ); +extern void NPC_SetGoal( gentity_t *goal, float rating ); +extern void NPC_ClearGoal( void ); +extern void NPC_ReachedGoal( void ); +extern qboolean ReachedGoal( gentity_t *goal ); +extern gentity_t *UpdateGoal( void ); +extern qboolean NPC_ClearPathToGoal(vec3_t dir, gentity_t *goal); +extern qboolean NPC_MoveToGoal( qboolean tryStraight ); + +//NPC_reactions + +//NPC_senses +#define ALERT_CLEAR_TIME 200 +#define CHECK_PVS 1 +#define CHECK_360 2 +#define CHECK_FOV 4 +#define CHECK_SHOOT 8 +#define CHECK_VISRANGE 16 +extern qboolean CanSee ( gentity_t *ent ); +extern qboolean InFOV ( gentity_t *ent, gentity_t *from, int hFOV, int vFOV ); +extern qboolean InFOV2( vec3_t origin, gentity_t *from, int hFOV, int vFOV ); +extern qboolean InFOV3( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV ); +extern visibility_t NPC_CheckVisibility ( gentity_t *ent, int flags ); +extern qboolean InVisrange ( gentity_t *ent ); + +//NPC_sounds +//extern void NPC_AngerSound(void); + +//NPC_spawn +extern void NPC_Spawn ( gentity_t *ent, gentity_t *other, gentity_t *activator ); + +//NPC_stats +extern int NPC_ReactionTime ( void ); +extern qboolean NPC_ParseParms( const char *NPCName, gentity_t *NPC ); +extern void NPC_LoadParms( void ); + +//NPC_utils +extern int teamNumbers[TEAM_NUM_TEAMS]; +extern int teamStrength[TEAM_NUM_TEAMS]; +extern int teamCounter[TEAM_NUM_TEAMS]; +extern void CalcEntitySpot ( const gentity_t *ent, const spot_t spot, vec3_t point ); +extern qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ); +extern void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ); +extern qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ); +extern void SetTeamNumbers (void); +extern qboolean G_ActivateBehavior (gentity_t *self, int bset ); +extern void NPC_AimWiggle( vec3_t enemy_org ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); + +//g_nav.cpp +extern int NAV_FindClosestWaypointForEnt (gentity_t *ent, int targWp); +extern qboolean NAV_CheckAhead( gentity_t *self, vec3_t end, trace_t *trace, int clipmask ); + +//NPC_combat +extern float IdealDistance ( gentity_t *self ); + +//g_squad +extern void NPC_SetSayState (gentity_t *self, gentity_t *to, int saying); + +//g_utils +extern qboolean G_CheckInSolid (gentity_t *self, qboolean fix); + +//MCG - End============================================================ + +// NPC.cpp +extern void NPC_SetAnim(gentity_t *ent, int type, int anim, int priority); +extern qboolean NPC_EnemyTooFar(gentity_t *enemy, float dist, qboolean toShoot); + +// ================================================================== + +//rww - special system for sync'ing bone angles between client and server. +void NPC_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles); + +//rww - and another method of automatically managing surface status for the client and server at once +void NPC_SetSurfaceOnOff(gentity_t *ent, const char *surfaceName, int surfaceFlags); + +extern qboolean NPC_ClearLOS( const vec3_t start, const vec3_t end ); +extern qboolean NPC_ClearLOS5( const vec3_t end ); +extern qboolean NPC_ClearLOS4( gentity_t *ent ) ; +extern qboolean NPC_ClearLOS3( const vec3_t start, gentity_t *ent ); +extern qboolean NPC_ClearLOS2( gentity_t *ent, const vec3_t end ); + +extern qboolean NPC_ClearShot( gentity_t *ent ); + +extern int NPC_FindCombatPoint( const vec3_t position, const vec3_t avoidPosition, vec3_t enemyPosition, const int flags, const float avoidDist, const int ignorePoint ); //ignorePoint = -1 + + +extern qboolean NPC_ReserveCombatPoint( int combatPointID ); +extern qboolean NPC_FreeCombatPoint( int combatPointID, qboolean failed ); //failed = qfalse +extern qboolean NPC_SetCombatPoint( int combatPointID ); + +#define CP_ANY 0 //No flags +#define CP_COVER 0x00000001 //The enemy cannot currently shoot this position +#define CP_CLEAR 0x00000002 //This cover point has a clear shot to the enemy +#define CP_FLEE 0x00000004 //This cover point is marked as a flee point +#define CP_DUCK 0x00000008 //This cover point is marked as a duck point +#define CP_NEAREST 0x00000010 //Find the nearest combat point +#define CP_AVOID_ENEMY 0x00000020 //Avoid our enemy +#define CP_INVESTIGATE 0x00000040 //A special point worth enemy investigation if searching +#define CP_SQUAD 0x00000080 //Squad path +#define CP_AVOID 0x00000100 //Avoid supplied position +#define CP_APPROACH_ENEMY 0x00000200 //Try to get closer to enemy +#define CP_CLOSEST 0x00000400 //Take the closest combatPoint to the enemy that's available +#define CP_FLANK 0x00000800 //Pick a combatPoint behind the enemy +#define CP_HAS_ROUTE 0x00001000 //Pick a combatPoint that we have a route to +#define CP_SNIPE 0x00002000 //Pick a combatPoint that is marked as a sniper spot +#define CP_SAFE 0x00004000 //Pick a combatPoint that is not have dangerTime +#define CP_HORZ_DIST_COLL 0x00008000 //Collect combat points within *horizontal* dist +#define CP_NO_PVS 0x00010000 //A combat point out of the PVS of enemy pos +#define CP_RETREAT 0x00020000 //Try to get farther from enemy + +#define CPF_NONE 0 +#define CPF_DUCK 0x00000001 +#define CPF_FLEE 0x00000002 +#define CPF_INVESTIGATE 0x00000004 +#define CPF_SQUAD 0x00000008 +#define CPF_LEAN 0x00000010 +#define CPF_SNIPE 0x00000020 + +#define MAX_COMBAT_POINT_CHECK 32 + +extern qboolean NPC_ValidEnemy( gentity_t *ent ); +extern qboolean NPC_CheckEnemyExt( qboolean checkAlerts ); //checkAlerts = qfalse +extern qboolean NPC_FindPlayer( void ); +extern qboolean NPC_CheckCanAttackExt( void ); + +extern int NPC_CheckAlertEvents( qboolean checkSight, qboolean checkSound, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ); //ignoreAlert = -1, mustHaveOwner = qfalse, minAlertLevel = AEL_MINOR +extern qboolean NPC_CheckForDanger( int alertEvent ); +extern void G_AlertTeam( gentity_t *victim, gentity_t *attacker, float radius, float soundDist ); + +extern int NPC_FindSquadPoint( vec3_t position ); + +extern void ClearPlayerAlertEvents( void ); + +extern qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2); +extern qboolean NAV_HitNavGoal( vec3_t point, vec3_t mins, vec3_t maxs, vec3_t dest, int radius, qboolean flying ); + +extern void NPC_SetMoveGoal( gentity_t *ent, vec3_t point, int radius, qboolean isNavGoal, int combatPoint, gentity_t *targetEnt ); //isNavGoal = qfalse, combatPoint = -1, targetEnt = NULL + +extern qboolean NAV_ClearPathToPoint(gentity_t *self, vec3_t pmins, vec3_t pmaxs, vec3_t point, int clipmask, int okToHitEnt ); +extern void NPC_ApplyWeaponFireDelay(void); + +//NPC_FaceXXX suite +extern qboolean NPC_FacePosition( vec3_t position, qboolean doPitch ); //doPitch = qtrue +extern qboolean NPC_FaceEntity( gentity_t *ent, qboolean doPitch ); //doPitch = qtrue +extern qboolean NPC_FaceEnemy( qboolean doPitch ); //doPitch = qtrue + +//Skill level cvar +extern vmCvar_t g_spskill; + +#define NIF_NONE 0x00000000 +#define NIF_FAILED 0x00000001 //failed to find a way to the goal +#define NIF_MACRO_NAV 0x00000002 //using macro navigation +#define NIF_COLLISION 0x00000004 //resolving collision with an entity +#define NIF_BLOCKED 0x00000008 //blocked from moving + +/* +------------------------- +struct navInfo_s +------------------------- +*/ + +typedef struct navInfo_s +{ + gentity_t *blocker; + vec3_t direction; + vec3_t pathDirection; + float distance; + trace_t trace; + int flags; +} navInfo_t; + +extern int NAV_MoveToGoal( gentity_t *self, navInfo_t *info ); +extern void NAV_GetLastMove( navInfo_t *info ); +extern qboolean NAV_AvoidCollision( gentity_t *self, gentity_t *goal, navInfo_t *info ); + + +#endif diff --git a/codemp/game/b_public.h b/codemp/game/b_public.h new file mode 100644 index 0000000..e49e259 --- /dev/null +++ b/codemp/game/b_public.h @@ -0,0 +1,355 @@ +#ifndef __B_PUBLIC_H__ +#define __B_PUBLIC_H__ + +#include "ai.h" + +#define NPCAI_CHECK_WEAPON 0x00000001 +#define NPCAI_BURST_WEAPON 0x00000002 +#define NPCAI_MOVING 0x00000004 +#define NPCAI_TOUCHED_GOAL 0x00000008 +#define NPCAI_PUSHED 0x00000010 +#define NPCAI_NO_COLL_AVOID 0x00000020 +#define NPCAI_BLOCKED 0x00000040 +#define NPCAI_OFF_PATH 0x00000100 +#define NPCAI_IN_SQUADPOINT 0x00000200 +#define NPCAI_STRAIGHT_TO_DESTPOS 0x00000400 +#define NPCAI_NO_SLOWDOWN 0x00001000 +#define NPCAI_LOST 0x00002000 //Can't nav to his goal +#define NPCAI_SHIELDS 0x00004000 //Has shields, borg can adapt +#define NPCAI_GREET_ALLIES 0x00008000 //Say hi to nearby allies +#define NPCAI_FORM_TELE_NAV 0x00010000 //Tells formation people to use nav info to get to +#define NPCAI_ENROUTE_TO_HOMEWP 0x00020000 //Lets us know to run our lostenemyscript when we get to homeWp +#define NPCAI_MATCHPLAYERWEAPON 0x00040000 //Match the player's weapon except when it changes during cinematics +#define NPCAI_DIE_ON_IMPACT 0x00100000 //Next time you crashland, die! +#define NPCAI_CUSTOM_GRAVITY 0x00200000 //Don't use g_gravity, I fly! + +//Script flags +#define SCF_CROUCHED 0x00000001 //Force ucmd.upmove to be -127 +#define SCF_WALKING 0x00000002 //Force BUTTON_WALKING to be pressed +#define SCF_MORELIGHT 0x00000004 //NPC will have a minlight of 96 +#define SCF_LEAN_RIGHT 0x00000008 //Force rightmove+BUTTON_USE +#define SCF_LEAN_LEFT 0x00000010 //Force leftmove+BUTTON_USE +#define SCF_RUNNING 0x00000020 //Takes off walking button, overrides SCF_WALKING +#define SCF_ALT_FIRE 0x00000040 //Force to use alt-fire when firing +#define SCF_NO_RESPONSE 0x00000080 //NPC will not do generic responses to being used +#define SCF_FFDEATH 0x00000100 //Just tells player_die to run the friendly fire deathscript +#define SCF_NO_COMBAT_TALK 0x00000200 //NPC will not use their generic combat chatter stuff +#define SCF_CHASE_ENEMIES 0x00000400 //NPC chase enemies - FIXME: right now this is synonymous with using combat points... should it be? +#define SCF_LOOK_FOR_ENEMIES 0x00000800 //NPC be on the lookout for enemies +#define SCF_FACE_MOVE_DIR 0x00001000 //NPC face direction it's moving - FIXME: not really implemented right now +#define SCF_IGNORE_ALERTS 0x00002000 //NPC ignore alert events +#define SCF_DONT_FIRE 0x00004000 //NPC won't shoot +#define SCF_DONT_FLEE 0x00008000 //NPC never flees +#define SCF_FORCED_MARCH 0x00010000 //NPC that the player must aim at to make him walk +#define SCF_NO_GROUPS 0x00020000 //NPC cannot alert groups or be part of a group +#define SCF_FIRE_WEAPON 0x00040000 //NPC will fire his (her) weapon +#define SCF_NO_MIND_TRICK 0x00080000 //Not succeptible to mind tricks +#define SCF_USE_CP_NEAREST 0x00100000 //Will use combat point close to it, not next to player or try and flank player +#define SCF_NO_FORCE 0x00200000 //Not succeptible to force powers +#define SCF_NO_FALLTODEATH 0x00400000 //NPC will not scream and tumble and fall to hit death over large drops +#define SCF_NO_ACROBATICS 0x00800000 //Jedi won't jump, roll or cartwheel +#define SCF_USE_SUBTITLES 0x01000000 //Regardless of subtitle setting, this NPC will display subtitles when it speaks lines +#define SCF_NO_ALERT_TALK 0x02000000 //Will not say alert sounds, but still can be woken up by alerts + +//#ifdef __DEBUG + +//Debug flag definitions + +#define AID_IDLE 0x00000000 //Nothing is happening +#define AID_ACQUIRED 0x00000001 //A target has been found +#define AID_LOST 0x00000002 //Alert, but no target is in sight +#define AID_CONFUSED 0x00000004 //Is unable to come up with a course of action +#define AID_LOSTPATH 0x00000008 //Cannot make a valid movement due to lack of connections + +//#endif //__DEBUG + +//extern qboolean showWaypoints; + +typedef enum {VIS_UNKNOWN, VIS_NOT, VIS_PVS, VIS_360, VIS_FOV, VIS_SHOOT} visibility_t; +typedef enum {SPOT_ORIGIN, SPOT_CHEST, SPOT_HEAD, SPOT_HEAD_LEAN, SPOT_WEAPON, SPOT_LEGS, SPOT_GROUND} spot_t; + +typedef enum //# lookMode_e +{ + LM_ENT = 0, + LM_INTEREST +} lookMode_t; + +typedef enum //# jumpState_e +{ + JS_WAITING = 0, + JS_FACING, + JS_CROUCHING, + JS_JUMPING, + JS_LANDING +} jumpState_t; + +typedef struct gNPCstats_e +{//Stats, loaded in, and can be set by scripts + //AI + int aggression; // " + int aim; // " + float earshot; // " + int evasion; // " + int hfov; // horizontal field of view + int intelligence; // " + int move; // " + int reactions; // 1-5, higher is better + float shootDistance; //Maximum range- overrides range set for weapon if nonzero + int vfov; // vertical field of view + float vigilance; // " + float visrange; // " + //Movement + int runSpeed; + int walkSpeed; + float yawSpeed; // 1 - whatever, default is 50 + int health; + int acceleration; +} gNPCstats_t; + +// NOTE!!! If you add any ptr fields into this structure could you please tell me so I can update the load/save code? +// so far the only things I've got to cope with are a bunch of gentity_t*'s, but tell me if any more get added -slc +// + +#define MAX_ENEMY_POS_LAG 2400 +#define ENEMY_POS_LAG_INTERVAL 100 +#define ENEMY_POS_LAG_STEPS (MAX_ENEMY_POS_LAG/ENEMY_POS_LAG_INTERVAL) +typedef struct +{ + //FIXME: Put in playerInfo or something + int timeOfDeath; //FIXME do we really need both of these + gentity_t *touchedByPlayer; + + visibility_t enemyLastVisibility; + + int aimTime; + float desiredYaw; + float desiredPitch; + float lockedDesiredYaw; + float lockedDesiredPitch; + gentity_t *aimingBeam; // debugging aid + + vec3_t enemyLastSeenLocation; + int enemyLastSeenTime; + vec3_t enemyLastHeardLocation; + int enemyLastHeardTime; + int lastAlertID; //unique ID + + int eFlags; + int aiFlags; + + int currentAmmo; // this sucks, need to find a better way + int shotTime; + int burstCount; + int burstMin; + int burstMean; + int burstMax; + int burstSpacing; + int attackHold; + int attackHoldTime; + vec3_t shootAngles; //Angles to where bot is shooting - fixme: make he torso turn to reflect these + + //extra character info + rank_t rank; //for pips + + //Behavior state info + bState_t behaviorState; //determines what actions he should be doing + bState_t defaultBehavior;//State bot will default to if none other set + bState_t tempBehavior;//While valid, overrides other behavior + + qboolean ignorePain; //only play pain scripts when take pain + + int duckDebounceTime;//Keeps them ducked for a certain time + int walkDebounceTime; + int enemyCheckDebounceTime; + int investigateDebounceTime; + int investigateCount; + vec3_t investigateGoal; + int investigateSoundDebounceTime; + int greetingDebounceTime;//when we can greet someone next + gentity_t *eventOwner; + + //bState-specific fields + gentity_t *coverTarg; + jumpState_t jumpState; + float followDist; + + // goal, navigation & pathfinding + gentity_t *tempGoal; // used for locational goals (player's last seen/heard position) + gentity_t *goalEntity; + gentity_t *lastGoalEntity; + gentity_t *eventualGoal; + gentity_t *captureGoal; //Where we should try to capture + gentity_t *defendEnt; //Who we're trying to protect + gentity_t *greetEnt; //Who we're greeting + int goalTime; //FIXME: This is never actually used + qboolean straightToGoal; //move straight at navgoals + float distToGoal; + int navTime; + int blockingEntNum; + int blockedSpeechDebounceTime; + int lastSideStepSide; + int sideStepHoldTime; + int homeWp; + AIGroupInfo_t *group; + + vec3_t lastPathAngles; //So we know which way to face generally when we stop + + //stats + gNPCstats_t stats; + int aimErrorDebounceTime; + float lastAimErrorYaw; + float lastAimErrorPitch; + vec3_t aimOfs; + int currentAim; + int currentAggression; + + //scriptflags + int scriptFlags;//in b_local.h + + //moveInfo + int desiredSpeed; + int currentSpeed; + char last_forwardmove; + char last_rightmove; + vec3_t lastClearOrigin; + int consecutiveBlockedMoves; + int blockedDebounceTime; + int shoveCount; + vec3_t blockedDest; + + // + int combatPoint;//NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point + int lastFailedCombatPoint;//NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point + int movementSpeech; //what to say when you first successfully move + float movementSpeechChance;//how likely you are to say it + + //Testing physics at 20fps + int nextBStateThink; + usercmd_t last_ucmd; + + // + //JWEIER ADDITIONS START + + qboolean combatMove; + int goalRadius; + + //FIXME: These may be redundant + + /* + int weaponTime; //Time until refire is valid + int jumpTime; + */ + int pauseTime; //Time to stand still + int standTime; + + int localState; //Tracking information local to entity + int squadState; //Tracking information for team level interaction + + //JWEIER ADDITIONS END + // + + int confusionTime; //Doesn't respond to alerts or pick up enemies (unless shot) until this time is up + int charmedTime; //charmed to enemy team + int controlledTime; //controlled by player + int surrenderTime; //Hands up + + //Lagging enemy position - FIXME: seems awful wasteful... + vec3_t enemyLaggedPos[ENEMY_POS_LAG_STEPS]; + + gentity_t *watchTarget; //for BS_CINEMATIC, keeps facing this ent + + int ffireCount; //sigh... you'd think I'd be able to find a way to do this without having to use 3 int fields, but... + int ffireDebounce; + int ffireFadeDebounce; +} gNPC_t; + +void G_SquadPathsInit(void); +void NPC_InitGame( void ); +void G_LoadBoltOns( void ); +void Svcmd_NPC_f( void ); +void NAV_DebugShowWaypoints (void); +void NAV_DebugShowBoxes (void); +void NAV_DebugShowSquadPaths (void); +/* +void Bot_InitGame( void ); +void Bot_InitPreSpawn( void ); +void Bot_InitPostSpawn( void ); +void Bot_Shutdown( void ); +void Bot_Think( gentity_t *ent, int msec ); +void Bot_Connect( gentity_t *bot, char *botName ); +void Bot_Begin( gentity_t *bot ); +void Bot_Disconnect( gentity_t *bot ); +void Svcmd_Bot_f( void ); +void Nav_ItemSpawn( gentity_t *ent, int remaining ); +*/ + +// +// This section should be moved to QFILES.H +// +/* +#define NAVFILE_ID (('I')+('N'<<8)+('A'<<16)+('V'<<24)) +#define NAVFILE_VERSION 6 + +typedef struct { + unsigned id; + unsigned version; + unsigned checksum; + unsigned surfaceCount; + unsigned edgeCount; +} navheader_t; + + +#define MAX_SURFACES 4096 + +#define NSF_PUSH 0x00000001 +#define NSF_WATERLEVEL1 0x00000002 +#define NSF_WATERLEVEL2 0x00000004 +#define NSF_WATER_NOAIR 0x00000008 +#define NSF_DUCK 0x00000010 +#define NSF_PAIN 0x00000020 +#define NSF_TELEPORTER 0x00000040 +#define NSF_PLATHIGH 0x00000080 +#define NSF_PLATLOW 0x00000100 +#define NSF_DOOR_FLOOR 0x00000200 +#define NSF_DOOR_SHOOT 0x00000400 +#define NSF_DOOR_BUTTON 0x00000800 +#define NSF_BUTTON 0x00001000 + +typedef struct { + vec3_t origin; + vec2_t absmin; + vec2_t absmax; + int parm; + unsigned flags; + unsigned edgeCount; + unsigned edgeIndex; +} nsurface_t; + + +#define NEF_DUCK 0x00000001 +#define NEF_JUMP 0x00000002 +#define NEF_HOLD 0x00000004 +#define NEF_WALK 0x00000008 +#define NEF_RUN 0x00000010 +#define NEF_NOAIRMOVE 0x00000020 +#define NEF_LEFTGROUND 0x00000040 +#define NEF_PLAT 0x00000080 +#define NEF_FALL1 0x00000100 +#define NEF_FALL2 0x00000200 +#define NEF_DOOR_SHOOT 0x00000400 +#define NEF_DOOR_BUTTON 0x00000800 +#define NEF_BUTTON 0x00001000 + +typedef struct { + vec3_t origin; + vec2_t absmin; // region within this surface that is the portal to the other surface + vec2_t absmax; + int surfaceNum; + unsigned flags; // jump, prerequisite button, will take falling damage, etc... + float cost; + int dirIndex; + vec3_t endSpot; + int parm; +} nedge_t; +*/ +#endif diff --git a/codemp/game/be_aas.h b/codemp/game/be_aas.h new file mode 100644 index 0000000..4132daf --- /dev/null +++ b/codemp/game/be_aas.h @@ -0,0 +1,205 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_aas.h + * + * desc: Area Awareness System, stuff exported to the AI + * + * $Archive: /source/code/botlib/be_aas.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:43:59 $ + * + *****************************************************************************/ + +#ifndef MAX_STRINGFIELD +#define MAX_STRINGFIELD 80 +#endif + +//travel flags +#define TFL_INVALID 0x00000001 //traveling temporary not possible +#define TFL_WALK 0x00000002 //walking +#define TFL_CROUCH 0x00000004 //crouching +#define TFL_BARRIERJUMP 0x00000008 //jumping onto a barrier +#define TFL_JUMP 0x00000010 //jumping +#define TFL_LADDER 0x00000020 //climbing a ladder +#define TFL_WALKOFFLEDGE 0x00000080 //walking of a ledge +#define TFL_SWIM 0x00000100 //swimming +#define TFL_WATERJUMP 0x00000200 //jumping out of the water +#define TFL_TELEPORT 0x00000400 //teleporting +#define TFL_ELEVATOR 0x00000800 //elevator +#define TFL_ROCKETJUMP 0x00001000 //rocket jumping +#define TFL_BFGJUMP 0x00002000 //bfg jumping +#define TFL_GRAPPLEHOOK 0x00004000 //grappling hook +#define TFL_DOUBLEJUMP 0x00008000 //double jump +#define TFL_RAMPJUMP 0x00010000 //ramp jump +#define TFL_STRAFEJUMP 0x00020000 //strafe jump +#define TFL_JUMPPAD 0x00040000 //jump pad +#define TFL_AIR 0x00080000 //travel through air +#define TFL_WATER 0x00100000 //travel through water +#define TFL_SLIME 0x00200000 //travel through slime +#define TFL_LAVA 0x00400000 //travel through lava +#define TFL_DONOTENTER 0x00800000 //travel through donotenter area +#define TFL_FUNCBOB 0x01000000 //func bobbing +#define TFL_FLIGHT 0x02000000 //flight +#define TFL_BRIDGE 0x04000000 //move over a bridge +// +#define TFL_NOTTEAM1 0x08000000 //not team 1 +#define TFL_NOTTEAM2 0x10000000 //not team 2 + +//default travel flags +#define TFL_DEFAULT TFL_WALK|TFL_CROUCH|TFL_BARRIERJUMP|\ + TFL_JUMP|TFL_LADDER|\ + TFL_WALKOFFLEDGE|TFL_SWIM|TFL_WATERJUMP|\ + TFL_TELEPORT|TFL_ELEVATOR|\ + TFL_AIR|TFL_WATER|TFL_JUMPPAD|TFL_FUNCBOB + +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; + +//a trace is returned when a box is swept through the AAS world +typedef struct aas_trace_s +{ + 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 + int ent; // entity blocking the trace + int lastarea; // last area the trace was in (zero if none) + int area; // area blocking the trace (zero if none) + int planenum; // number of the plane that was hit +} aas_trace_t; + +/* Defined in botlib.h + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//a trace is returned when a box is swept through the BSP world +typedef struct bsp_trace_s +{ + 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 + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // hit surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; +// +*/ + +//entity info +typedef struct aas_entityinfo_s +{ + int valid; // true if updated this frame + int type; // entity type + int flags; // entity flags + float ltime; // local time + float update_time; // time between last and current update + int number; // number of the entity + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t lastvisorigin; // last visible origin + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // current legs anim + int torsoAnim; // current torso anim +} aas_entityinfo_t; + +// area info +typedef struct aas_areainfo_s +{ + int contents; + int flags; + int presencetype; + int cluster; + vec3_t mins; + vec3_t maxs; + vec3_t center; +} aas_areainfo_t; + +// client movement prediction stop events, stop as soon as: +#define SE_NONE 0 +#define SE_HITGROUND 1 // the ground is hit +#define SE_LEAVEGROUND 2 // there's no ground +#define SE_ENTERWATER 4 // water is entered +#define SE_ENTERSLIME 8 // slime is entered +#define SE_ENTERLAVA 16 // lava is entered +#define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage +#define SE_GAP 64 // there's a gap +#define SE_TOUCHJUMPPAD 128 // touching a jump pad area +#define SE_TOUCHTELEPORTER 256 // touching teleporter +#define SE_ENTERAREA 512 // the given stoparea is entered +#define SE_HITGROUNDAREA 1024 // a ground face in the area is hit +#define SE_HITBOUNDINGBOX 2048 // hit the specified bounding box +#define SE_TOUCHCLUSTERPORTAL 4096 // touching a cluster portal + +typedef struct aas_clientmove_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + vec3_t velocity; //velocity at the end of movement prediction + aas_trace_t trace; //last trace + int presencetype; //presence type at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + float time; //time predicted ahead + int frames; //number of frames predicted ahead +} aas_clientmove_t; + +// alternate route goals +#define ALTROUTEGOAL_ALL 1 +#define ALTROUTEGOAL_CLUSTERPORTALS 2 +#define ALTROUTEGOAL_VIEWPORTALS 4 + +typedef struct aas_altroutegoal_s +{ + vec3_t origin; + int areanum; + unsigned short starttraveltime; + unsigned short goaltraveltime; + unsigned short extratraveltime; +} aas_altroutegoal_t; + +// route prediction stop events +#define RSE_NONE 0 +#define RSE_NOROUTE 1 //no route to goal +#define RSE_USETRAVELTYPE 2 //stop as soon as on of the given travel types is used +#define RSE_ENTERCONTENTS 4 //stop when entering the given contents +#define RSE_ENTERAREA 8 //stop when entering the given area + +typedef struct aas_predictroute_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + int endtravelflags; //end travel flags + int numareas; //number of areas predicted ahead + int time; //time predicted ahead (in hundreth of a sec) +} aas_predictroute_t; diff --git a/codemp/game/be_ai_char.h b/codemp/game/be_ai_char.h new file mode 100644 index 0000000..b86c67c --- /dev/null +++ b/codemp/game/be_ai_char.h @@ -0,0 +1,32 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_char.h + * + * desc: bot characters + * + * $Archive: /source/code/botlib/be_ai_char.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:43:59 $ + * + *****************************************************************************/ + +//loads a bot character from a file +int BotLoadCharacter(char *charfile, float skill); +//frees a bot character +void BotFreeCharacter(int character); +//returns a float characteristic +float Characteristic_Float(int character, int index); +//returns a bounded float characteristic +float Characteristic_BFloat(int character, int index, float min, float max); +//returns an integer characteristic +int Characteristic_Integer(int character, int index); +//returns a bounded integer characteristic +int Characteristic_BInteger(int character, int index, int min, int max); +//returns a string characteristic +void Characteristic_String(int character, int index, char *buf, int size); +//free cached bot characters +void BotShutdownCharacters(void); diff --git a/codemp/game/be_ai_chat.h b/codemp/game/be_ai_chat.h new file mode 100644 index 0000000..4d16e7b --- /dev/null +++ b/codemp/game/be_ai_chat.h @@ -0,0 +1,97 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: be_ai_chat.h + * + * desc: char AI + * + * $Archive: /source/code/botlib/be_ai_chat.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:43:59 $ + * + *****************************************************************************/ + +#define MAX_MESSAGE_SIZE 256 +#define MAX_CHATTYPE_NAME 32 +#define MAX_MATCHVARIABLES 8 + +#define CHAT_GENDERLESS 0 +#define CHAT_GENDERFEMALE 1 +#define CHAT_GENDERMALE 2 + +#define CHAT_ALL 0 +#define CHAT_TEAM 1 +#define CHAT_TELL 2 + +//a console message +typedef struct bot_consolemessage_s +{ + int handle; + float time; //message time + int type; //message type + char message[MAX_MESSAGE_SIZE]; //message + struct bot_consolemessage_s *prev, *next; //prev and next in list +} bot_consolemessage_t; + +//match variable +typedef struct bot_matchvariable_s +{ + char offset; + int length; +} bot_matchvariable_t; +//returned to AI when a match is found +typedef struct bot_match_s +{ + char string[MAX_MESSAGE_SIZE]; + int type; + int subtype; + bot_matchvariable_t variables[MAX_MATCHVARIABLES]; +} bot_match_t; + +//setup the chat AI +int BotSetupChatAI(void); +//shutdown the chat AI +void BotShutdownChatAI(void); +//returns the handle to a newly allocated chat state +int BotAllocChatState(void); +//frees the chatstate +void BotFreeChatState(int handle); +//adds a console message to the chat state +void BotQueueConsoleMessage(int chatstate, int type, char *message); +//removes the console message from the chat state +void BotRemoveConsoleMessage(int chatstate, int handle); +//returns the next console message from the state +int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm); +//returns the number of console messages currently stored in the state +int BotNumConsoleMessages(int chatstate); +//selects a chat message of the given type +void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the number of initial chat messages of the given type +int BotNumInitialChats(int chatstate, char *type); +//find and select a reply for the given message +int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the length of the currently selected chat message +int BotChatLength(int chatstate); +//enters the selected chat message +void BotEnterChat(int chatstate, int clientto, int sendto); +//get the chat message ready to be output +void BotGetChatMessage(int chatstate, char *buf, int size); +//checks if the first string contains the second one, returns index into first string or -1 if not found +int StringContains(char *str1, char *str2, int casesensitive); +//finds a match for the given string using the match templates +int BotFindMatch(char *str, bot_match_t *match, unsigned long int context); +//returns a variable from a match +void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size); +//unify all the white spaces in the string +void UnifyWhiteSpaces(char *string); +//replace all the context related synonyms in the string +void BotReplaceSynonyms(char *string, unsigned long int context); +//loads a chat file for the chat state +int BotLoadChatFile(int chatstate, char *chatfile, char *chatname); +//store the gender of the bot in the chat state +void BotSetChatGender(int chatstate, int gender); +//store the bot name in the chat state +void BotSetChatName(int chatstate, char *name, int client); + diff --git a/codemp/game/be_ai_gen.h b/codemp/game/be_ai_gen.h new file mode 100644 index 0000000..75d51e2 --- /dev/null +++ b/codemp/game/be_ai_gen.h @@ -0,0 +1,17 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_gen.h + * + * desc: genetic selection + * + * $Archive: /source/code/botlib/be_ai_gen.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child); diff --git a/codemp/game/be_ai_goal.h b/codemp/game/be_ai_goal.h new file mode 100644 index 0000000..000ef91 --- /dev/null +++ b/codemp/game/be_ai_goal.h @@ -0,0 +1,102 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: be_ai_goal.h + * + * desc: goal AI + * + * $Archive: /source/code/botlib/be_ai_goal.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +#define MAX_AVOIDGOALS 256 +#define MAX_GOALSTACK 8 + +#define GFL_NONE 0 +#define GFL_ITEM 1 +#define GFL_ROAM 2 +#define GFL_DROPPED 4 + +//a bot goal +typedef struct bot_goal_s +{ + vec3_t origin; //origin of the goal + int areanum; //area number of the goal + vec3_t mins, maxs; //mins and maxs of the goal + int entitynum; //number of the goal entity + int number; //goal number + int flags; //goal flags + int iteminfo; //item information +} bot_goal_t; + +//reset the whole goal state, but keep the item weights +void BotResetGoalState(int goalstate); +//reset avoid goals +void BotResetAvoidGoals(int goalstate); +//remove the goal with the given number from the avoid goals +void BotRemoveFromAvoidGoals(int goalstate, int number); +//push a goal onto the goal stack +void BotPushGoal(int goalstate, bot_goal_t *goal); +//pop a goal from the goal stack +void BotPopGoal(int goalstate); +//empty the bot's goal stack +void BotEmptyGoalStack(int goalstate); +//dump the avoid goals +void BotDumpAvoidGoals(int goalstate); +//dump the goal stack +void BotDumpGoalStack(int goalstate); +//get the name name of the goal with the given number +void BotGoalName(int number, char *name, int size); +//get the top goal from the stack +int BotGetTopGoal(int goalstate, bot_goal_t *goal); +//get the second goal on the stack +int BotGetSecondGoal(int goalstate, bot_goal_t *goal); +//choose the best long term goal item for the bot +int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags); +//choose the best nearby goal item for the bot +//the item may not be further away from the current bot position than maxtime +//also the travel time from the nearby goal towards the long term goal may not +//be larger than the travel time towards the long term goal from the current bot position +int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime); +//returns true if the bot touches the goal +int BotTouchingGoal(vec3_t origin, bot_goal_t *goal); +//returns true if the goal should be visible but isn't +int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal); +//search for a goal for the given classname, the index can be used +//as a start point for the search when multiple goals are available with that same classname +int BotGetLevelItemGoal(int index, char *classname, bot_goal_t *goal); +//get the next camp spot in the map +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal); +//get the map location with the given name +int BotGetMapLocationGoal(char *name, bot_goal_t *goal); +//returns the avoid goal time +float BotAvoidGoalTime(int goalstate, int number); +//set the avoid goal time +void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime); +//initializes the items in the level +void BotInitLevelItems(void); +//regularly update dynamic entity items (dropped weapons, flags etc.) +void BotUpdateEntityItems(void); +//interbreed the goal fuzzy logic +void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child); +//save the goal fuzzy logic to disk +void BotSaveGoalFuzzyLogic(int goalstate, char *filename); +//mutate the goal fuzzy logic +void BotMutateGoalFuzzyLogic(int goalstate, float range); +//loads item weights for the bot +int BotLoadItemWeights(int goalstate, char *filename); +//frees the item weights of the bot +void BotFreeItemWeights(int goalstate); +//returns the handle of a newly allocated goal state +int BotAllocGoalState(int client); +//free the given goal state +void BotFreeGoalState(int handle); +//setup the goal AI +int BotSetupGoalAI(void); +//shut down the goal AI +void BotShutdownGoalAI(void); diff --git a/codemp/game/be_ai_move.h b/codemp/game/be_ai_move.h new file mode 100644 index 0000000..03ed5fe --- /dev/null +++ b/codemp/game/be_ai_move.h @@ -0,0 +1,126 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_move.h + * + * desc: movement AI + * + * $Archive: /source/code/botlib/be_ai_move.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +//movement types +#define MOVE_WALK 1 +#define MOVE_CROUCH 2 +#define MOVE_JUMP 4 +#define MOVE_GRAPPLE 8 +#define MOVE_ROCKETJUMP 16 +#define MOVE_BFGJUMP 32 +//move flags +#define MFL_BARRIERJUMP 1 //bot is performing a barrier jump +#define MFL_ONGROUND 2 //bot is in the ground +#define MFL_SWIMMING 4 //bot is swimming +#define MFL_AGAINSTLADDER 8 //bot is against a ladder +#define MFL_WATERJUMP 16 //bot is waterjumping +#define MFL_TELEPORTED 32 //bot is being teleported +#define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple +#define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook +#define MFL_GRAPPLERESET 256 //bot has reset the grapple +#define MFL_WALK 512 //bot should walk slowly +// move result flags +#define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement +#define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming +#define MOVERESULT_WAITING 4 //bot is waiting for something +#define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code +#define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement +#define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle +#define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing +#define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat) +#define MOVERESULT_BLOCKEDBYAVOIDSPOT 256 //bot is blocked by an avoid spot +// +#define MAX_AVOIDREACH 1 +#define MAX_AVOIDSPOTS 32 +// avoid spot types +#define AVOID_CLEAR 0 //clear all avoid spots +#define AVOID_ALWAYS 1 //avoid always +#define AVOID_DONTBLOCK 2 //never totally block +// restult types +#define RESULTTYPE_ELEVATORUP 1 //elevator is up +#define RESULTTYPE_WAITFORFUNCBOBBING 2 //waiting for func bobbing to arrive +#define RESULTTYPE_BADGRAPPLEPATH 4 //grapple path is obstructed +#define RESULTTYPE_INSOLIDAREA 8 //stuck in solid area, this is bad + +//structure used to initialize the movement state +//the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate +typedef struct bot_initmove_s +{ + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + int or_moveflags; //values ored to the movement flags +} bot_initmove_t; + +//NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set +typedef struct bot_moveresult_s +{ + int failure; //true if movement failed all together + int type; //failure or blocked type + int blocked; //true if blocked by an entity + int blockentity; //entity blocking the bot + int traveltype; //last executed travel type + int flags; //result flags + int weapon; //weapon used for movement + vec3_t movedir; //movement direction + vec3_t ideal_viewangles; //ideal viewangles for the movement +} bot_moveresult_t; + +// bk001204: from code/botlib/be_ai_move.c +// TTimo 04/12/2001 was moved here to avoid dup defines +typedef struct bot_avoidspot_s +{ + vec3_t origin; + float radius; + int type; +} bot_avoidspot_t; + +//resets the whole move state +void BotResetMoveState(int movestate); +//moves the bot to the given goal +void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags); +//moves the bot in the specified direction using the specified type of movement +int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type); +//reset avoid reachability +void BotResetAvoidReach(int movestate); +//resets the last avoid reachability +void BotResetLastAvoidReach(int movestate); +//returns a reachability area if the origin is in one +int BotReachabilityArea(vec3_t origin, int client); +//view target based on movement +int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target); +//predict the position of a player based on movement towards a goal +int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target); +//returns the handle of a newly allocated movestate +int BotAllocMoveState(void); +//frees the movestate with the given handle +void BotFreeMoveState(int handle); +//initialize movement state before performing any movement +void BotInitMoveState(int handle, bot_initmove_t *initmove); +//add a spot to avoid (if type == AVOID_CLEAR all spots are removed) +void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type); +//must be called every map change +void BotSetBrushModelTypes(void); +//setup movement AI +int BotSetupMoveAI(void); +//shutdown movement AI +void BotShutdownMoveAI(void); + diff --git a/codemp/game/be_ai_weap.h b/codemp/game/be_ai_weap.h new file mode 100644 index 0000000..80bfdf9 --- /dev/null +++ b/codemp/game/be_ai_weap.h @@ -0,0 +1,88 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_weap.h + * + * desc: weapon AI + * + * $Archive: /source/code/botlib/be_ai_weap.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +//projectile flags +#define PFL_WINDOWDAMAGE 1 //projectile damages through window +#define PFL_RETURN 2 //set when projectile returns to owner +//weapon flags +#define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event +//damage types +#define DAMAGETYPE_IMPACT 1 //damage on impact +#define DAMAGETYPE_RADIAL 2 //radial damage +#define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile + +typedef struct projectileinfo_s +{ + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int flags; + float gravity; + int damage; + float radius; + int visdamage; + int damagetype; + int healthinc; + float push; + float detonation; + float bounce; + float bouncefric; + float bouncestop; +} projectileinfo_t; + +typedef struct weaponinfo_s +{ + int valid; //true if the weapon info is valid + int number; //number of the weapon + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int level; + int weaponindex; + int flags; + char projectile[MAX_STRINGFIELD]; + int numprojectiles; + float hspread; + float vspread; + float speed; + float acceleration; + vec3_t recoil; + vec3_t offset; + vec3_t angleoffset; + float extrazvelocity; + int ammoamount; + int ammoindex; + float activate; + float reload; + float spinup; + float spindown; + projectileinfo_t proj; //pointer to the used projectile +} weaponinfo_t; + +//setup the weapon AI +int BotSetupWeaponAI(void); +//shut down the weapon AI +void BotShutdownWeaponAI(void); +//returns the best weapon to fight with +int BotChooseBestFightWeapon(int weaponstate, int *inventory); +//returns the information of the current weapon +void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo); +//loads the weapon weights +int BotLoadWeaponWeights(int weaponstate, char *filename); +//returns a handle to a newly allocated weapon state +int BotAllocWeaponState(void); +//frees the weapon state +void BotFreeWeaponState(int weaponstate); +//resets the whole weapon state +void BotResetWeaponState(int weaponstate); diff --git a/codemp/game/be_ea.h b/codemp/game/be_ea.h new file mode 100644 index 0000000..38180ff --- /dev/null +++ b/codemp/game/be_ea.h @@ -0,0 +1,52 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ea.h + * + * desc: elementary actions + * + * $Archive: /source/code/botlib/be_ea.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +//ClientCommand elementary actions +void EA_Say(int client, char *str); +void EA_SayTeam(int client, char *str); +void EA_Command(int client, char *command ); + +void EA_Action(int client, int action); +void EA_Crouch(int client); +void EA_Walk(int client); +void EA_MoveUp(int client); +void EA_MoveDown(int client); +void EA_MoveForward(int client); +void EA_MoveBack(int client); +void EA_MoveLeft(int client); +void EA_MoveRight(int client); +void EA_Attack(int client); +void EA_Alt_Attack(int client); +void EA_ForcePower(int client); +void EA_Respawn(int client); +void EA_Talk(int client); +void EA_Gesture(int client); +void EA_Use(int client); + +//regular elementary actions +void EA_SelectWeapon(int client, int weapon); +void EA_Jump(int client); +void EA_DelayedJump(int client); +void EA_Move(int client, vec3_t dir, float speed); +void EA_View(int client, vec3_t viewangles); + +//send regular input to the server +void EA_EndRegular(int client, float thinktime); +void EA_GetInput(int client, float thinktime, bot_input_t *input); +void EA_ResetInput(int client); +//setup and shutdown routines +int EA_Setup(void); +void EA_Shutdown(void); diff --git a/codemp/game/bg_g2_utils.c b/codemp/game/bg_g2_utils.c new file mode 100644 index 0000000..d72aa76 --- /dev/null +++ b/codemp/game/bg_g2_utils.c @@ -0,0 +1,124 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_g2_utils.c -- both games misc functions, all completely stateless + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_strap.h" + +#ifdef QAGAME +#include "g_local.h" +#endif + +#ifdef UI_EXPORTS +#include "../ui/ui_local.h" +#endif + +#ifndef UI_EXPORTS +#ifndef QAGAME +#include "../cgame/cg_local.h" +#endif +#endif + +#include "../namespace_begin.h" + +void BG_AttachToRancor( void *ghoul2, + float rancYaw, + vec3_t rancOrigin, + int time, + qhandle_t *modelList, + vec3_t modelScale, + qboolean inMouth, + vec3_t out_origin, + vec3_t out_angles, + vec3_t out_axis[3] ) +{ + mdxaBone_t boltMatrix; + int boltIndex; + vec3_t rancAngles; + vec3_t temp_angles; + // Getting the bolt here + if ( inMouth ) + {//in mouth + boltIndex = trap_G2API_AddBolt(ghoul2, 0, "jaw_bone"); + } + else + {//in right hand + boltIndex = trap_G2API_AddBolt(ghoul2, 0, "*r_hand"); + } + VectorSet( rancAngles, 0, rancYaw, 0 ); + trap_G2API_GetBoltMatrix( ghoul2, 0, boltIndex, + &boltMatrix, rancAngles, rancOrigin, time, + modelList, modelScale ); + // Storing ent position, bolt position, and bolt axis + if ( out_origin ) + { + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, out_origin ); + } + if ( out_axis ) + { + if ( inMouth ) + {//in mouth + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_Z, out_axis[0] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, out_axis[1] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, out_axis[2] ); + } + else + {//in hand + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, out_axis[0] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_X, out_axis[1] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_Z, out_axis[2] ); + } + //FIXME: this is messing up our axis and turning us inside-out? + if ( out_angles ) + { + vectoangles( out_axis[0], out_angles ); + vectoangles( out_axis[2], temp_angles ); + out_angles[ROLL] = -temp_angles[PITCH]; + } + } + else if ( out_angles ) + { + vec3_t temp_axis[3]; + if ( inMouth ) + {//in mouth + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_Z, temp_axis[0] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, temp_axis[2] ); + } + else + {//in hand + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, temp_axis[0] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_Z, temp_axis[2] ); + } + //FIXME: this is messing up our axis and turning us inside-out? + vectoangles( temp_axis[0], out_angles ); + vectoangles( temp_axis[2], temp_angles ); + out_angles[ROLL] = -temp_angles[PITCH]; + } +} + +#define MAX_VARIANTS 8 +qboolean BG_GetRootSurfNameWithVariant( void *ghoul2, const char *rootSurfName, char *returnSurfName, int returnSize ) +{ + if ( !ghoul2 || !trap_G2API_GetSurfaceRenderStatus( ghoul2, 0, rootSurfName ) ) + {//see if the basic name without variants is on + Q_strncpyz( returnSurfName, rootSurfName, returnSize ); + return qtrue; + } + else + {//check variants + int i; + for ( i = 0; i < MAX_VARIANTS; i++ ) + { + Com_sprintf( returnSurfName, returnSize, "%s%c", rootSurfName, 'a'+i ); + if ( !trap_G2API_GetSurfaceRenderStatus( ghoul2, 0, returnSurfName ) ) + { + return qtrue; + } + } + } + Q_strncpyz( returnSurfName, rootSurfName, returnSize ); + return qfalse; +} + +#include "../namespace_end.h" diff --git a/codemp/game/bg_lib.c b/codemp/game/bg_lib.c new file mode 100644 index 0000000..4f805d4 --- /dev/null +++ b/codemp/game/bg_lib.c @@ -0,0 +1,1318 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_lib,c -- standard C library replacement routines used by code +// compiled for the virtual machine + +#include "q_shared.h" + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$Id: bg_lib.c,v 1.4 2003/03/15 23:44:00 osman Exp $"; +#endif /* LIBC_SCCS and not lint */ + +// bk001127 - needed for DLL's +#if !defined( Q3_VM ) +typedef int cmp_t(const void *, const void *); +#endif + +static char* med3(char *, char *, char *, cmp_t *); +static void swapfunc(char *, char *, int, int); + +#ifndef min +#define min(a, b) (a) < (b) ? a : b +#endif + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + register TYPE *pi = (TYPE *) (parmi); \ + register TYPE *pj = (TYPE *) (parmj); \ + do { \ + register TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static void swapfunc( char* a, char* b, int n, int swaptype) +{ + if(swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static char *med3(char* a, char* b, char* c, cmp_t* cmp) +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); +} + +void qsort( void* a, size_t n, size_t es, cmp_t* cmp) +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype, swap_cnt; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = cmp(pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) + qsort(a, r / es, es, cmp); + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp);*/ +} + +//================================================================================== + + +// this file is excluded from release builds because of intrinsics + +// bk001211 - gcc errors on compiling strcpy: parse error before `__extension__' +#if defined ( Q3_VM ) + +size_t strlen( const char *string ) { + const char *s; + + s = string; + while ( *s ) { + s++; + } + return s - string; +} + + +char *strcat( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *s ) { + s++; + } + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + +char *strcpy( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + + +int strcmp( const char *string1, const char *string2 ) { + while ( *string1 == *string2 && *string1 && *string2 ) { + string1++; + string2++; + } + return *string1 - *string2; +} + + +char *strchr( const char *string, int c ) { + while ( *string ) { + if ( *string == c ) { + return ( char * )string; + } + string++; + } + return (char *)0; +} + +char *strstr( const char *string, const char *strCharSet ) { + while ( *string ) { + int i; + + for ( i = 0 ; strCharSet[i] ; i++ ) { + if ( string[i] != strCharSet[i] ) { + break; + } + } + if ( !strCharSet[i] ) { + return (char *)string; + } + string++; + } + return (char *)0; +} +#endif // bk001211 + +// bk001120 - presumably needed for Mac +//#if !defined(_MSC_VER) && !defined(__linux__) +// bk001127 - undid undo +#if defined ( Q3_VM ) +int tolower( int c ) { + if ( c >= 'A' && c <= 'Z' ) { + c += 'a' - 'A'; + } + return c; +} + + +int toupper( int c ) { + if ( c >= 'a' && c <= 'z' ) { + c += 'A' - 'a'; + } + return c; +} + +#endif +//#ifndef _MSC_VER + +void *memmove( void *dest, const void *src, size_t count ) { + int i; + + if ( dest > src ) { + for ( i = count-1 ; i >= 0 ; i-- ) { + ((char *)dest)[i] = ((char *)src)[i]; + } + } else { + for ( i = 0 ; i < count ; i++ ) { + ((char *)dest)[i] = ((char *)src)[i]; + } + } + return dest; +} + + +#if 0 + +double floor( double x ) { + return (int)(x + 0x40000000) - 0x40000000; +} + +void *memset( void *dest, int c, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = c; + } + return dest; +} + +void *memcpy( void *dest, const void *src, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = ((char *)src)[count]; + } + return dest; +} + +char *strncpy( char *strDest, const char *strSource, size_t count ) { + char *s; + + s = strDest; + while ( *strSource && count ) { + *s++ = *strSource++; + count--; + } + while ( count-- ) { + *s++ = 0; + } + return strDest; +} + +double sqrt( double x ) { + float y; + float delta; + float maxError; + + if ( x <= 0 ) { + return 0; + } + + // initial guess + y = x / 2; + + // refine + maxError = x * 0.001; + + do { + delta = ( y * y ) - x; + y -= delta / ( 2 * y ); + } while ( delta > maxError || delta < -maxError ); + + return y; +} + + +float sintable[1024] = { +0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, +0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, +0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, +0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, +0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, +0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, +0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, +0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, +0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, +0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, +0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, +0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, +0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, +0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, +0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, +0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, +0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, +0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, +0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, +0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, +0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, +0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, +0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, +0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, +0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, +0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, +0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, +0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, +0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, +0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, +0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, +0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, +0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, +0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, +0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, +0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, +0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, +0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, +0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, +0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, +0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, +0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, +0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, +0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, +0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, +0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, +0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, +0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, +0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, +0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, +0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, +0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, +0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, +0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, +0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, +0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, +0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, +0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, +0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, +0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, +0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, +0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, +0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, +0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, +0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, +0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, +0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, +0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, +0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, +0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, +0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, +0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, +0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, +0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, +0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, +0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, +0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, +0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, +0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, +0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, +0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, +0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, +0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, +0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, +0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, +0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, +0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, +0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, +0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, +0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, +0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, +0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, +0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, +0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, +0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, +0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, +0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, +0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, +0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, +0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, +0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, +0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, +0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, +0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, +0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, +0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, +0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, +0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, +0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, +0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, +0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, +0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, +0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, +0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, +0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, +0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, +0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, +0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, +0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, +0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, +0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, +0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, +0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, +0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, +0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, +0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, +0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, +0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 +}; + +double sin( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 0: + return sintable[index]; + case 1: + return sintable[1023-index]; + case 2: + return -sintable[index]; + case 3: + return -sintable[1023-index]; + } + return 0; +} + + +double cos( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 3: + return sintable[index]; + case 0: + return sintable[1023-index]; + case 1: + return -sintable[index]; + case 2: + return -sintable[1023-index]; + } + return 0; +} + + +/* +void create_acostable( void ) { + int i; + FILE *fp; + float a; + + fp = fopen("c:\\acostable.txt", "w"); + fprintf(fp, "float acostable[] = {"); + for (i = 0; i < 1024; i++) { + if (!(i & 7)) + fprintf(fp, "\n"); + a = acos( (float) -1 + i / 512 ); + fprintf(fp, "%1.8f,", a); + } + fprintf(fp, "\n}\n"); + fclose(fp); +} +*/ + +float acostable[] = { +3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422, +2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629, +2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962, +2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724, +2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926, +2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688, +2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735, +2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133, +2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440, +2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769, +2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409, +2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238, +2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096, +2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722, +2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034, +2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622, +2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379, +2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237, +2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964, +2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010, +2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388, +2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580, +2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460, +2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238, +2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408, +2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709, +2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092, +2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692, +2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805, +2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865, +2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435, +2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185, +2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884, +2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387, +2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628, +2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610, +2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399, +2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119, +1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942, +1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088, +1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818, +1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433, +1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266, +1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683, +1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076, +1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866, +1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495, +1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429, +1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151, +1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164, +1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985, +1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148, +1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198, +1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692, +1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199, +1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297, +1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571, +1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615, +1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028, +1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416, +1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388, +1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556, +1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536, +1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945, +1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403, +1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526, +1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933, +1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240, +1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060, +1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004, +1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677, +1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682, +1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612, +1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056, +1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594, +1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795, +1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222, +1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422, +1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932, +1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273, +1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951, +1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456, +1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257, +1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803, +1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519, +1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806, +1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037, +1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553, +1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663, +1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638, +1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709, +1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061, +1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831, +1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098, +1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882, +1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136, +1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735, +1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471, +1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039, +0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030, +0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912, +0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017, +0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524, +0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430, +0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531, +0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385, +0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277, +0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172, +0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655, +0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865, +0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402, +0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217, +0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469, +0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332, +0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753, +0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106, +0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735, +0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288, +0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758, +0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028, +0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573, +0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652, +0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513, +0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209, +0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574, +0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250, +0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575, +0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018, +} + +double acos( double x ) { + int index; + + if (x < -1) + x = -1; + if (x > 1) + x = 1; + index = (float) (1.0 + x) * 511.9; + return acostable[index]; +} + +double atan2( double y, double x ) { + float base; + float temp; + float dir; + float test; + int i; + + if ( x < 0 ) { + if ( y >= 0 ) { + // quad 1 + base = M_PI / 2; + temp = x; + x = y; + y = -temp; + } else { + // quad 2 + base = M_PI; + x = -x; + y = -y; + } + } else { + if ( y < 0 ) { + // quad 3 + base = 3 * M_PI / 2; + temp = x; + x = -y; + y = temp; + } + } + + if ( y > x ) { + base += M_PI/2; + temp = x; + x = y; + y = temp; + dir = -1; + } else { + dir = 1; + } + + // calcualte angle in octant 0 + if ( x == 0 ) { + return base; + } + y /= x; + + for ( i = 0 ; i < 512 ; i++ ) { + test = sintable[i] / sintable[1023-i]; + if ( test > y ) { + break; + } + } + + return base + dir * i * ( M_PI/2048); +} + + +#endif + +#ifdef Q3_VM +// bk001127 - guarded this tan replacement +// ld: undefined versioned symbol name tan@@GLIBC_2.0 +double tan( double x ) { + return sin(x) / cos(x); +} +#endif + + +static int randSeed = 0; + +void srand( unsigned seed ) { + randSeed = seed; +} + +int rand( void ) { + randSeed = (69069 * randSeed + 1); + return randSeed & 0x7fff; +} + +double atof( const char *string ) { + float sign; + float value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + c = string[0]; + if ( c != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } else { + string++; + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + + return value * sign; +} + +double _atof( const char **stringPtr ) { + const char *string; + float sign; + float value; + int c = '0'; // bk001211 - uninitialized use possible + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + *stringPtr = string; + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + if ( string[0] != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + *stringPtr = string; + + return value * sign; +} + + +// bk001120 - presumably needed for Mac +//#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) + +// bk001127 - undid undo +#if defined ( Q3_VM ) +int atoi( const char *string ) { + int sign; + int value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + return value * sign; +} + + +int _atoi( const char **stringPtr ) { + int sign; + int value; + int c; + const char *string; + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + *stringPtr = string; + + return value * sign; +} + +int abs( int n ) { + return n < 0 ? -n : n; +} + +double fabs( double x ) { + return x < 0 ? -x : x; +} + + + +//========================================================= + + +#define ALT 0x00000001 /* alternate form */ +#define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */ +#define LADJUST 0x00000004 /* left adjustment */ +#define LONGDBL 0x00000008 /* long double */ +#define LONGINT 0x00000010 /* long integer */ +#define QUADINT 0x00000020 /* quad integer */ +#define SHORTINT 0x00000040 /* short integer */ +#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ +#define FPT 0x00000100 /* floating point number */ + +#define to_digit(c) ((c) - '0') +#define is_digit(c) ((unsigned)to_digit(c) <= 9) +#define to_char(n) ((n) + '0') + +void AddInt( char **buf_p, int val, int width, int flags ) { + char text[32]; + int digits; + int signedVal; + char *buf; + + digits = 0; + signedVal = val; + if ( val < 0 ) { + val = -val; + } + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + if( !( flags & LADJUST ) ) { + while ( digits < width ) { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + width--; + } + } + + while ( digits-- ) { + *buf++ = text[digits]; + width--; + } + + if( flags & LADJUST ) { + while ( width-- ) { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + } + } + + *buf_p = buf; +} + +void AddFloat( char **buf_p, float fval, int width, int prec ) { + char text[32]; + int digits; + float signedVal; + char *buf; + int val; + + // get the sign + signedVal = fval; + if ( fval < 0 ) { + fval = -fval; + } + + // write the float number + digits = 0; + val = (int)fval; + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + while ( digits < width ) { + *buf++ = ' '; + width--; + } + + while ( digits-- ) { + *buf++ = text[digits]; + } + + *buf_p = buf; + + if (prec < 0) + prec = 6; + // write the fraction + digits = 0; + while (digits < prec) { + fval -= (int) fval; + fval *= 10.0; + val = (int) fval; + text[digits++] = '0' + val % 10; + } + + if (digits > 0) { + buf = *buf_p; + *buf++ = '.'; + for (prec = 0; prec < digits; prec++) { + *buf++ = text[prec]; + } + *buf_p = buf; + } +} + + +void AddString( char **buf_p, char *string, int width, int prec ) { + int size; + char *buf; + + buf = *buf_p; + + if ( string == NULL ) { + string = "(null)"; + prec = -1; + } + + if ( prec >= 0 ) { + for( size = 0; size < prec; size++ ) { + if( string[size] == '\0' ) { + break; + } + } + } + else { + size = strlen( string ); + } + + width -= size; + + while( size-- ) { + *buf++ = *string++; + } + + while( width-- > 0 ) { + *buf++ = ' '; + } + + *buf_p = buf; +} + +/* +vsprintf + +I'm not going to support a bunch of the more arcane stuff in here +just to keep it simpler. For example, the '*' and '$' are not +currently supported. I've tried to make it so that it will just +parse and ignore formats we don't support. +*/ +int vsprintf( char *buffer, const char *fmt, va_list argptr ) { + int *arg; + char *buf_p; + char ch; + int flags; + int width; + int prec; + int n; + char sign; + + buf_p = buffer; + arg = (int *)argptr; + + while( qtrue ) { + // run through the format string until we hit a '%' or '\0' + for ( ch = *fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) { + *buf_p++ = ch; + } + if ( ch == '\0' ) { + goto done; + } + + // skip over the '%' + fmt++; + + // reset formatting state + flags = 0; + width = 0; + prec = -1; + sign = '\0'; + +rflag: + ch = *fmt++; +reswitch: + switch( ch ) { + case '-': + flags |= LADJUST; + goto rflag; + case '.': + n = 0; + while( is_digit( ( ch = *fmt++ ) ) ) { + n = 10 * n + ( ch - '0' ); + } + prec = n < 0 ? -1 : n; + goto reswitch; + case '0': + flags |= ZEROPAD; + goto rflag; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + n = 0; + do { + n = 10 * n + ( ch - '0' ); + ch = *fmt++; + } while( is_digit( ch ) ); + width = n; + goto reswitch; + case 'c': + *buf_p++ = (char)*arg; + arg++; + break; + case 'd': + case 'i': + AddInt( &buf_p, *arg, width, flags ); + arg++; + break; + case 'f': + AddFloat( &buf_p, *(double *)arg, width, prec ); +#ifdef __LCC__ + arg += 1; // everything is 32 bit in my compiler +#else + arg += 2; +#endif + break; + case 's': + AddString( &buf_p, (char *)*arg, width, prec ); + arg++; + break; + case '%': + *buf_p++ = ch; + break; + default: + *buf_p++ = (char)*arg; + arg++; + break; + } + } + +done: + *buf_p = 0; + return buf_p - buffer; +} + + +/* this is really crappy */ +int sscanf( const char *buffer, const char *fmt, ... ) { + int cmd; + int **arg; + int count; + + arg = (int **)&fmt + 1; + count = 0; + + while ( *fmt ) { + if ( fmt[0] != '%' ) { + fmt++; + continue; + } + + cmd = fmt[1]; + fmt += 2; + + switch ( cmd ) { + case 'i': + case 'd': + case 'u': + **arg = _atoi( &buffer ); + break; + case 'f': + *(float *)*arg = _atof( &buffer ); + break; + } + arg++; + } + + return count; +} + +#endif diff --git a/codemp/game/bg_lib.h b/codemp/game/bg_lib.h new file mode 100644 index 0000000..7e1d089 --- /dev/null +++ b/codemp/game/bg_lib.h @@ -0,0 +1,70 @@ +// bg_lib.h -- standard C library replacement routines used by code +// compiled for the virtual machine + +// This file is NOT included on native builds + +typedef int size_t; + +typedef char * va_list; +#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) +#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) +#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) +#define va_end(ap) ( ap = (va_list)0 ) + +#define CHAR_BIT 8 /* number of bits in a char */ +#define SCHAR_MIN (-128) /* minimum signed char value */ +#define SCHAR_MAX 127 /* maximum signed char value */ +#define UCHAR_MAX 0xff /* maximum unsigned char value */ + +#define SHRT_MIN (-32768) /* minimum (signed) short value */ +#define SHRT_MAX 32767 /* maximum (signed) short value */ +#define USHRT_MAX 0xffff /* maximum unsigned short value */ +#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ +#define INT_MAX 2147483647 /* maximum (signed) int value */ +#define UINT_MAX 0xffffffff /* maximum unsigned int value */ +#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */ +#define LONG_MAX 2147483647L /* maximum (signed) long value */ +#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ + +// Misc functions +typedef int cmp_t(const void *, const void *); +void qsort(void *a, size_t n, size_t es, cmp_t *cmp); +void srand( unsigned seed ); +int rand( void ); + +// String functions +size_t strlen( const char *string ); +char *strcat( char *strDestination, const char *strSource ); +char *strcpy( char *strDestination, const char *strSource ); +int strcmp( const char *string1, const char *string2 ); +char *strchr( const char *string, int c ); +char *strstr( const char *string, const char *strCharSet ); +char *strncpy( char *strDest, const char *strSource, size_t count ); +int tolower( int c ); +int toupper( int c ); + +double atof( const char *string ); +double _atof( const char **stringPtr ); +int atoi( const char *string ); +int _atoi( const char **stringPtr ); + +int vsprintf( char *buffer, const char *fmt, va_list argptr ); +int sscanf( const char *buffer, const char *fmt, ... ); + +// Memory functions +void *memmove( void *dest, const void *src, size_t count ); +void *memset( void *dest, int c, size_t count ); +void *memcpy( void *dest, const void *src, size_t count ); + +// Math functions +double ceil( double x ); +double floor( double x ); +double sqrt( double x ); +double sin( double x ); +double cos( double x ); +double atan2( double y, double x ); +double tan( double x ); +int abs( int n ); +double fabs( double x ); +double acos( double x ); + diff --git a/codemp/game/bg_local.h b/codemp/game/bg_local.h new file mode 100644 index 0000000..206d125 --- /dev/null +++ b/codemp/game/bg_local.h @@ -0,0 +1,109 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_local.h -- local definitions for the bg (both games) files + +#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes + +#define TIMER_LAND 130 +#define TIMER_GESTURE (34*66+50) + +#define OVERCLIP 1.001f + +// 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 +typedef struct +{ + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +#include "../namespace_begin.h" + +extern pml_t pml; + +// movement parameters +extern float pm_stopspeed; +extern float pm_duckScale; +extern float pm_swimScale; +extern float pm_wadeScale; + +extern float pm_accelerate; +extern float pm_airaccelerate; +extern float pm_wateraccelerate; +extern float pm_flyaccelerate; + +extern float pm_friction; +extern float pm_waterfriction; +extern float pm_flightfriction; + +extern int c_pmove; + +extern int forcePowerNeeded[NUM_FORCE_POWER_LEVELS][NUM_FORCE_POWERS]; + +// Had to add these here because there was no file access within the BG right now. +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +#include "../namespace_end.h" + +//PM anim utility functions: +#include "../namespace_begin.h" +qboolean PM_SaberInParry( int move ); +qboolean PM_SaberInKnockaway( int move ); +qboolean PM_SaberInReflect( int move ); +qboolean PM_SaberInStart( int move ); +qboolean PM_InSaberAnim( int anim ); +qboolean PM_InKnockDown( playerState_t *ps ); +qboolean PM_PainAnim( int anim ); +qboolean PM_JumpingAnim( int anim ); +qboolean PM_LandingAnim( int anim ); +qboolean PM_SpinningAnim( int anim ); +qboolean PM_InOnGroundAnim ( int anim ); +qboolean PM_InRollComplete( playerState_t *ps, int anim ); + +int PM_AnimLength( int index, animNumber_t anim ); + +int PM_GetSaberStance(void); +float PM_GroundDistance(void); +qboolean PM_SomeoneInFront(trace_t *tr); +saberMoveName_t PM_SaberFlipOverAttackMove(void); +saberMoveName_t PM_SaberJumpAttackMove( void ); + +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepSlideMove( qboolean gravity ); + +void PM_StartTorsoAnim( int anim ); +void PM_ContinueLegsAnim( int anim ); +void PM_ForceLegsAnim( int anim ); + +void PM_BeginWeaponChange( int weapon ); +void PM_FinishWeaponChange( void ); + +void PM_SetAnim(int setAnimParts,int anim,int setAnimFlags, int blendTime); + +void PM_WeaponLightsaber(void); +void PM_SetSaberMove(short newMove); + +void PM_SetForceJumpZStart(float value); + +void BG_CycleInven(playerState_t *ps, int direction); + +#include "../namespace_end.h" diff --git a/codemp/game/bg_misc.c b/codemp/game/bg_misc.c new file mode 100644 index 0000000..e0a7e34 --- /dev/null +++ b/codemp/game/bg_misc.c @@ -0,0 +1,3415 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_misc.c -- both games misc functions, all completely stateless + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_strap.h" + +#ifdef QAGAME +#include "g_local.h" +#endif + +#ifdef UI_EXPORTS +#include "../ui/ui_local.h" +#endif + +#ifndef UI_EXPORTS +#ifndef QAGAME +#include "../cgame/cg_local.h" +#endif +#endif + +#ifdef _XBOX +extern void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int iAlign); +extern void Z_Free(void *pvAddress); +#endif + +#ifdef QAGAME +extern void Q3_SetParm (int entID, int parmNum, const char *parmValue); +#endif + +#include "../namespace_begin.h" + +const char *bgToggleableSurfaces[BG_NUM_TOGGLEABLE_SURFACES] = +{ + "l_arm_key", //0 + "torso_canister1", + "torso_canister2", + "torso_canister3", + "torso_tube1", + "torso_tube2", //5 + "torso_tube3", + "torso_tube4", + "torso_tube5", + "torso_tube6", + "r_arm", //10 + "l_arm", + "torso_shield", + "torso_galaktorso", + "torso_collar", +// "torso_eyes_mouth", //15 +// "torso_galakhead", +// "torso_galakface", +// "torso_antenna_base_cap", +// "torso_antenna", +// "l_arm_augment", //20 +// "l_arm_middle", +// "l_arm_wrist", +// "r_arm_middle", //yeah.. galak's surf stuff is no longer auto, sorry! need the space for vehicle surfs. + "r_wing1", //15 + "r_wing2", + "l_wing1", + "l_wing2", + "r_gear", + "l_gear", //20 + "nose", + "blah4", + "blah5", + "l_hand", + "r_hand", //25 + "helmet", + "head", + "head_concussion_charger", + "head_light_blaster_cann", //29 + NULL +}; + +const int bgToggleableSurfaceDebris[BG_NUM_TOGGLEABLE_SURFACES] = +{ + 0, //0 + 0, + 0, + 0, + 0, + 0, //5 + 0, + 0, + 0, + 0, + 0, //10 + 0, + 0, + 0, + 0, //>= 2 means it should create a flame trail when destroyed (for vehicles) + 3, //15 + 5, //rwing2 + 4, + 6, //lwing2 + 0, //rgear + 0, //lgear //20 + 7, //nose + 0, //blah + 0, //blah + 0, + 0, //25 + 0, + 0, + 0, + 0, //29 + -1 +}; + +const char *bg_customSiegeSoundNames[MAX_CUSTOM_SIEGE_SOUNDS] = +{ + "*att_attack", + "*att_primary", + "*att_second", + "*def_guns", + "*def_position", + "*def_primary", + "*def_second", + "*reply_coming", + "*reply_go", + "*reply_no", + "*reply_stay", + "*reply_yes", + "*req_assist", + "*req_demo", + "*req_hvy", + "*req_medic", + "*req_sup", + "*req_tech", + "*spot_air", + "*spot_defenses", + "*spot_emplaced", + "*spot_sniper", + "*spot_troops", + "*tac_cover", + "*tac_fallback", + "*tac_follow", + "*tac_hold", + "*tac_split", + "*tac_together", + NULL +}; + +//rww - not putting @ in front of these because +//we don't need them in a cgame StringEd lookup. +//Let me know if this causes problems, pat. +char *forceMasteryLevels[NUM_FORCE_MASTERY_LEVELS] = +{ + "MASTERY0", //"Uninitiated", // FORCE_MASTERY_UNINITIATED, + "MASTERY1", //"Initiate", // FORCE_MASTERY_INITIATE, + "MASTERY2", //"Padawan", // FORCE_MASTERY_PADAWAN, + "MASTERY3", //"Jedi", // FORCE_MASTERY_JEDI, + "MASTERY4", //"Jedi Adept", // FORCE_MASTERY_JEDI_GUARDIAN, + "MASTERY5", //"Jedi Guardian", // FORCE_MASTERY_JEDI_ADEPT, + "MASTERY6", //"Jedi Knight", // FORCE_MASTERY_JEDI_KNIGHT, + "MASTERY7", //"Jedi Master" // FORCE_MASTERY_JEDI_MASTER, +}; + +int forceMasteryPoints[NUM_FORCE_MASTERY_LEVELS] = +{ + 0, // FORCE_MASTERY_UNINITIATED, + 5, // FORCE_MASTERY_INITIATE, + 10, // FORCE_MASTERY_PADAWAN, + 20, // FORCE_MASTERY_JEDI, + 30, // FORCE_MASTERY_JEDI_GUARDIAN, + 50, // FORCE_MASTERY_JEDI_ADEPT, + 75, // FORCE_MASTERY_JEDI_KNIGHT, + 100 // FORCE_MASTERY_JEDI_MASTER, +}; + +int bgForcePowerCost[NUM_FORCE_POWERS][NUM_FORCE_POWER_LEVELS] = //0 == neutral +{ + { 0, 2, 4, 6 }, // Heal // FP_HEAL + { 0, 0, 2, 6 }, // Jump //FP_LEVITATION,//hold/duration + { 0, 2, 4, 6 }, // Speed //FP_SPEED,//duration + { 0, 1, 3, 6 }, // Push //FP_PUSH,//hold/duration + { 0, 1, 3, 6 }, // Pull //FP_PULL,//hold/duration + { 0, 4, 6, 8 }, // Mind Trick //FP_TELEPATHY,//instant + { 0, 1, 3, 6 }, // Grip //FP_GRIP,//hold/duration + { 0, 2, 5, 8 }, // Lightning //FP_LIGHTNING,//hold/duration + { 0, 4, 6, 8 }, // Dark Rage //FP_RAGE,//duration + { 0, 2, 5, 8 }, // Protection //FP_PROTECT,//duration + { 0, 1, 3, 6 }, // Absorb //FP_ABSORB,//duration + { 0, 1, 3, 6 }, // Team Heal //FP_TEAM_HEAL,//instant + { 0, 1, 3, 6 }, // Team Force //FP_TEAM_FORCE,//instant + { 0, 2, 4, 6 }, // Drain //FP_DRAIN,//hold/duration + { 0, 2, 5, 8 }, // Sight //FP_SEE,//duration + { 0, 1, 5, 8 }, // Saber Attack //FP_SABER_OFFENSE, + { 0, 1, 5, 8 }, // Saber Defend //FP_SABER_DEFENSE, + { 0, 4, 6, 8 } // Saber Throw //FP_SABERTHROW, + //NUM_FORCE_POWERS +}; + +int forcePowerSorted[NUM_FORCE_POWERS] = +{ //rww - always use this order when drawing force powers for any reason + FP_TELEPATHY, + FP_HEAL, + FP_ABSORB, + FP_PROTECT, + FP_TEAM_HEAL, + FP_LEVITATION, + FP_SPEED, + FP_PUSH, + FP_PULL, + FP_SEE, + FP_LIGHTNING, + FP_DRAIN, + FP_RAGE, + FP_GRIP, + FP_TEAM_FORCE, + FP_SABER_OFFENSE, + FP_SABER_DEFENSE, + FP_SABERTHROW +}; + +int forcePowerDarkLight[NUM_FORCE_POWERS] = //0 == neutral +{ //nothing should be usable at rank 0.. + FORCE_LIGHTSIDE,//FP_HEAL,//instant + 0,//FP_LEVITATION,//hold/duration + 0,//FP_SPEED,//duration + 0,//FP_PUSH,//hold/duration + 0,//FP_PULL,//hold/duration + FORCE_LIGHTSIDE,//FP_TELEPATHY,//instant + FORCE_DARKSIDE,//FP_GRIP,//hold/duration + FORCE_DARKSIDE,//FP_LIGHTNING,//hold/duration + FORCE_DARKSIDE,//FP_RAGE,//duration + FORCE_LIGHTSIDE,//FP_PROTECT,//duration + FORCE_LIGHTSIDE,//FP_ABSORB,//duration + FORCE_LIGHTSIDE,//FP_TEAM_HEAL,//instant + FORCE_DARKSIDE,//FP_TEAM_FORCE,//instant + FORCE_DARKSIDE,//FP_DRAIN,//hold/duration + 0,//FP_SEE,//duration + 0,//FP_SABER_OFFENSE, + 0,//FP_SABER_DEFENSE, + 0//FP_SABERTHROW, + //NUM_FORCE_POWERS +}; + +int WeaponReadyAnim[WP_NUM_WEAPONS] = +{ + TORSO_DROPWEAP1,//WP_NONE, + + TORSO_WEAPONREADY3,//WP_STUN_BATON, + TORSO_WEAPONREADY3,//WP_MELEE, + BOTH_STAND2,//WP_SABER, + TORSO_WEAPONREADY2,//WP_BRYAR_PISTOL, + TORSO_WEAPONREADY3,//WP_BLASTER, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY4,//WP_DISRUPTOR, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY5,//WP_BOWCASTER, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY6,//WP_REPEATER, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY7,//WP_DEMP2, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY8,//WP_FLECHETTE, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY9,//WP_ROCKET_LAUNCHER, + TORSO_WEAPONREADY10,//WP_THERMAL, + TORSO_WEAPONREADY10,//TORSO_WEAPONREADY11,//WP_TRIP_MINE, + TORSO_WEAPONREADY10,//TORSO_WEAPONREADY12,//WP_DET_PACK, + TORSO_WEAPONREADY3,//WP_CONCUSSION + TORSO_WEAPONREADY2,//WP_BRYAR_OLD, + + //NOT VALID (e.g. should never really be used): + BOTH_STAND1,//WP_EMPLACED_GUN, + TORSO_WEAPONREADY1//WP_TURRET, +}; + +int WeaponReadyLegsAnim[WP_NUM_WEAPONS] = +{ + BOTH_STAND1,//WP_NONE, + + BOTH_STAND1,//WP_STUN_BATON, + BOTH_STAND1,//WP_MELEE, + BOTH_STAND2,//WP_SABER, + BOTH_STAND1,//WP_BRYAR_PISTOL, + BOTH_STAND1,//WP_BLASTER, + BOTH_STAND1,//TORSO_WEAPONREADY4,//WP_DISRUPTOR, + BOTH_STAND1,//TORSO_WEAPONREADY5,//WP_BOWCASTER, + BOTH_STAND1,//TORSO_WEAPONREADY6,//WP_REPEATER, + BOTH_STAND1,//TORSO_WEAPONREADY7,//WP_DEMP2, + BOTH_STAND1,//TORSO_WEAPONREADY8,//WP_FLECHETTE, + BOTH_STAND1,//TORSO_WEAPONREADY9,//WP_ROCKET_LAUNCHER, + BOTH_STAND1,//WP_THERMAL, + BOTH_STAND1,//TORSO_WEAPONREADY11,//WP_TRIP_MINE, + BOTH_STAND1,//TORSO_WEAPONREADY12,//WP_DET_PACK, + BOTH_STAND1,//WP_CONCUSSION + BOTH_STAND1,//WP_BRYAR_OLD, + + //NOT VALID (e.g. should never really be used): + BOTH_STAND1,//WP_EMPLACED_GUN, + BOTH_STAND1//WP_TURRET, +}; + +int WeaponAttackAnim[WP_NUM_WEAPONS] = +{ + BOTH_ATTACK1,//WP_NONE, //(shouldn't happen) + + BOTH_ATTACK3,//WP_STUN_BATON, + BOTH_ATTACK3,//WP_MELEE, + BOTH_STAND2,//WP_SABER, //(has its own handling) + BOTH_ATTACK2,//WP_BRYAR_PISTOL, + BOTH_ATTACK3,//WP_BLASTER, + BOTH_ATTACK3,//BOTH_ATTACK4,//WP_DISRUPTOR, + BOTH_ATTACK3,//BOTH_ATTACK5,//WP_BOWCASTER, + BOTH_ATTACK3,//BOTH_ATTACK6,//WP_REPEATER, + BOTH_ATTACK3,//BOTH_ATTACK7,//WP_DEMP2, + BOTH_ATTACK3,//BOTH_ATTACK8,//WP_FLECHETTE, + BOTH_ATTACK3,//BOTH_ATTACK9,//WP_ROCKET_LAUNCHER, + BOTH_THERMAL_THROW,//WP_THERMAL, + BOTH_ATTACK3,//BOTH_ATTACK11,//WP_TRIP_MINE, + BOTH_ATTACK3,//BOTH_ATTACK12,//WP_DET_PACK, + BOTH_ATTACK2,//WP_BRYAR_OLD, + + //NOT VALID (e.g. should never really be used): + BOTH_STAND1,//WP_EMPLACED_GUN, + BOTH_ATTACK1//WP_TURRET, +}; + +qboolean BG_FileExists(const char *fileName) +{ + if (fileName && fileName[0]) + { + int fh = 0; + trap_FS_FOpenFile(fileName, &fh, FS_READ); + if (fh > 0) + { + trap_FS_FCloseFile(fh); + return qtrue; + } + } + + return qfalse; +} + +#ifndef UI_EXPORTS //don't need this stuff in the ui + +// Following functions don't need to be in namespace, they're already +// different per-module +#include "../namespace_end.h" + +#ifdef QAGAME +char *G_NewString( const char *string ); +#else +char *CG_NewString( const char *string ); +#endif + +#include "../namespace_begin.h" + +/* +=============== +BG_ParseField + +Takes a key/value pair and sets the binary values +in a gentity/centity/whatever the hell you want +=============== +*/ + +void BG_ParseField( BG_field_t *l_fields, const char *key, const char *value, byte *ent ) +{ + BG_field_t *f; + byte *b; + float v; + vec3_t vec; + + for ( f=l_fields ; f->name ; f++ ) { + if ( !Q_stricmp(f->name, key) ) { + // found it + b = (byte *)ent; + + switch( f->type ) { + case F_LSTRING: +#ifdef QAGAME + *(char **)(b+f->ofs) = G_NewString (value); +#else + *(char **)(b+f->ofs) = CG_NewString (value); +#endif + 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; +#ifdef QAGAME + case F_PARM1: + case F_PARM2: + case F_PARM3: + case F_PARM4: + case F_PARM5: + case F_PARM6: + case F_PARM7: + case F_PARM8: + case F_PARM9: + case F_PARM10: + case F_PARM11: + case F_PARM12: + case F_PARM13: + case F_PARM14: + case F_PARM15: + case F_PARM16: + Q3_SetParm( ((gentity_t *)(ent))->s.number, (f->type - F_PARM1), (char *) value ); + break; +#endif + default: + case F_IGNORE: + break; + } + return; + } + } +} + +#endif + +/* +================ +BG_LegalizedForcePowers + +The magical function to end all functions. +This will take the force power string in powerOut and parse through it, then legalize +it based on the supposed rank and spit it into powerOut, returning true if it was legal +to begin with and false if not. +fpDisabled is actually only expected (needed) from the server, because the ui disables +force power selection anyway when force powers are disabled on the server. +================ +*/ +qboolean BG_LegalizedForcePowers(char *powerOut, int maxRank, qboolean freeSaber, int teamForce, int gametype, int fpDisabled) +{ + char powerBuf[128]; + char readBuf[128]; + qboolean maintainsValidity = qtrue; + int powerLen = strlen(powerOut); + int i = 0; + int c = 0; + int allowedPoints = 0; + int usedPoints = 0; + int countDown = 0; + + int final_Side; + int final_Powers[NUM_FORCE_POWERS]; + + if (powerLen >= 128) + { //This should not happen. If it does, this is obviously a bogus string. + //They can have this string. Because I said so. + strcpy(powerBuf, "7-1-032330000000001333"); + maintainsValidity = qfalse; + } + else + { + strcpy(powerBuf, powerOut); //copy it as the original + } + + //first of all, print the max rank into the string as the rank + strcpy(powerOut, va("%i-", maxRank)); + + while (i < 128 && powerBuf[i] && powerBuf[i] != '-') + { + i++; + } + i++; + while (i < 128 && powerBuf[i] && powerBuf[i] != '-') + { + readBuf[c] = powerBuf[i]; + c++; + i++; + } + readBuf[c] = 0; + i++; + //at this point, readBuf contains the intended side + final_Side = atoi(readBuf); + + if (final_Side != FORCE_LIGHTSIDE && + final_Side != FORCE_DARKSIDE) + { //Not a valid side. You will be dark. Because I said so. (this is something that should never actually happen unless you purposely feed in an invalid config) + final_Side = FORCE_DARKSIDE; + maintainsValidity = qfalse; + } + + if (teamForce) + { //If we are under force-aligned teams, make sure we're on the right side. + if (final_Side != teamForce) + { + final_Side = teamForce; + //maintainsValidity = qfalse; + //Not doing this, for now. Let them join the team with their filtered powers. + } + } + + //Now we have established a valid rank, and a valid side. + //Read the force powers in, and cut them down based on the various rules supplied. + c = 0; + while (i < 128 && powerBuf[i] && powerBuf[i] != '\n' && c < NUM_FORCE_POWERS) + { + readBuf[0] = powerBuf[i]; + readBuf[1] = 0; + final_Powers[c] = atoi(readBuf); + c++; + i++; + } + + //final_Powers now contains all the stuff from the string + //Set the maximum allowed points used based on the max rank level, and count the points actually used. + allowedPoints = forceMasteryPoints[maxRank]; + + i = 0; + while (i < NUM_FORCE_POWERS) + { //if this power doesn't match the side we're on, then 0 it now. + if (final_Powers[i] && + forcePowerDarkLight[i] && + forcePowerDarkLight[i] != final_Side) + { + final_Powers[i] = 0; + //This is only likely to happen with g_forceBasedTeams. Let it slide. + } + + if ( final_Powers[i] && + (fpDisabled & (1 << i)) ) + { //if this power is disabled on the server via said server option, then we don't get it. + final_Powers[i] = 0; + } + + i++; + } + + if (gametype < GT_TEAM) + { //don't bother with team powers then + final_Powers[FP_TEAM_HEAL] = 0; + final_Powers[FP_TEAM_FORCE] = 0; + } + + usedPoints = 0; + i = 0; + while (i < NUM_FORCE_POWERS) + { + countDown = 0; + + countDown = final_Powers[i]; + + while (countDown > 0) + { + usedPoints += bgForcePowerCost[i][countDown]; //[fp index][fp level] + //if this is jump, or we have a free saber and it's offense or defense, take the level back down on level 1 + if ( countDown == 1 && + ((i == FP_LEVITATION) || + (i == FP_SABER_OFFENSE && freeSaber) || + (i == FP_SABER_DEFENSE && freeSaber)) ) + { + usedPoints -= bgForcePowerCost[i][countDown]; + } + countDown--; + } + + i++; + } + + if (usedPoints > allowedPoints) + { //Time to do the fancy stuff. (meaning, slowly cut parts off while taking a guess at what is most or least important in the config) + int attemptedCycles = 0; + int powerCycle = 2; + int minPow = 0; + + if (freeSaber) + { + minPow = 1; + } + + maintainsValidity = qfalse; + + while (usedPoints > allowedPoints) + { + c = 0; + + while (c < NUM_FORCE_POWERS && usedPoints > allowedPoints) + { + if (final_Powers[c] && final_Powers[c] < powerCycle) + { //kill in order of lowest powers, because the higher powers are probably more important + if (c == FP_SABER_OFFENSE && + (final_Powers[FP_SABER_DEFENSE] > minPow || final_Powers[FP_SABERTHROW] > 0)) + { //if we're on saber attack, only suck it down if we have no def or throw either + int whichOne = FP_SABERTHROW; //first try throw + + if (!final_Powers[whichOne]) + { + whichOne = FP_SABER_DEFENSE; //if no throw, drain defense + } + + while (final_Powers[whichOne] > 0 && usedPoints > allowedPoints) + { + if ( final_Powers[whichOne] > 1 || + ( (whichOne != FP_SABER_OFFENSE || !freeSaber) && + (whichOne != FP_SABER_DEFENSE || !freeSaber) ) ) + { //don't take attack or defend down on level 1 still, if it's free + usedPoints -= bgForcePowerCost[whichOne][final_Powers[whichOne]]; + final_Powers[whichOne]--; + } + else + { + break; + } + } + } + else + { + while (final_Powers[c] > 0 && usedPoints > allowedPoints) + { + if ( final_Powers[c] > 1 || + ((c != FP_LEVITATION) && + (c != FP_SABER_OFFENSE || !freeSaber) && + (c != FP_SABER_DEFENSE || !freeSaber)) ) + { + usedPoints -= bgForcePowerCost[c][final_Powers[c]]; + final_Powers[c]--; + } + else + { + break; + } + } + } + } + + c++; + } + + powerCycle++; + attemptedCycles++; + + if (attemptedCycles > NUM_FORCE_POWERS) + { //I think this should be impossible. But just in case. + break; + } + } + + if (usedPoints > allowedPoints) + { //Still? Fine then.. we will kill all of your powers, except the freebies. + i = 0; + + while (i < NUM_FORCE_POWERS) + { + final_Powers[i] = 0; + if (i == FP_LEVITATION || + (i == FP_SABER_OFFENSE && freeSaber) || + (i == FP_SABER_DEFENSE && freeSaber)) + { + final_Powers[i] = 1; + } + i++; + } + usedPoints = 0; + } + } + + if (freeSaber) + { + if (final_Powers[FP_SABER_OFFENSE] < 1) + { + final_Powers[FP_SABER_OFFENSE] = 1; + } + if (final_Powers[FP_SABER_DEFENSE] < 1) + { + final_Powers[FP_SABER_DEFENSE] = 1; + } + } + if (final_Powers[FP_LEVITATION] < 1) + { + final_Powers[FP_LEVITATION] = 1; + } + + i = 0; + while (i < NUM_FORCE_POWERS) + { + if (final_Powers[i] > FORCE_LEVEL_3) + { + final_Powers[i] = FORCE_LEVEL_3; + } + i++; + } + + if (fpDisabled) + { //If we specifically have attack or def disabled, force them up to level 3. It's the way + //things work for the case of all powers disabled. + //If jump is disabled, down-cap it to level 1. Otherwise don't do a thing. + if (fpDisabled & (1 << FP_LEVITATION)) + { + final_Powers[FP_LEVITATION] = 1; + } + if (fpDisabled & (1 << FP_SABER_OFFENSE)) + { + final_Powers[FP_SABER_OFFENSE] = 3; + } + if (fpDisabled & (1 << FP_SABER_DEFENSE)) + { + final_Powers[FP_SABER_DEFENSE] = 3; + } + } + + if (final_Powers[FP_SABER_OFFENSE] < 1) + { + final_Powers[FP_SABER_DEFENSE] = 0; + final_Powers[FP_SABERTHROW] = 0; + } + + //We finally have all the force powers legalized and stored locally. + //Put them all into the string and return the result. We already have + //the rank there, so print the side and the powers now. + Q_strcat(powerOut, 128, va("%i-", final_Side)); + + i = strlen(powerOut); + c = 0; + while (c < NUM_FORCE_POWERS) + { + strcpy(readBuf, va("%i", final_Powers[c])); + powerOut[i] = readBuf[0]; + c++; + i++; + } + powerOut[i] = 0; + + return maintainsValidity; +} + +#ifdef __LCC__ +// given a boltmatrix, return in vec a normalised vector for the axis requested in flags +void BG_GiveMeVectorFromMatrix(mdxaBone_t *boltMatrix, int flags, vec3_t vec) +{ + switch (flags) + { + case ORIGIN: + vec[0] = boltMatrix->matrix[0][3]; + vec[1] = boltMatrix->matrix[1][3]; + vec[2] = boltMatrix->matrix[2][3]; + break; + case POSITIVE_Y: + vec[0] = boltMatrix->matrix[0][1]; + vec[1] = boltMatrix->matrix[1][1]; + vec[2] = boltMatrix->matrix[2][1]; + break; + case POSITIVE_X: + vec[0] = boltMatrix->matrix[0][0]; + vec[1] = boltMatrix->matrix[1][0]; + vec[2] = boltMatrix->matrix[2][0]; + break; + case POSITIVE_Z: + vec[0] = boltMatrix->matrix[0][2]; + vec[1] = boltMatrix->matrix[1][2]; + vec[2] = boltMatrix->matrix[2][2]; + break; + case NEGATIVE_Y: + vec[0] = -boltMatrix->matrix[0][1]; + vec[1] = -boltMatrix->matrix[1][1]; + vec[2] = -boltMatrix->matrix[2][1]; + break; + case NEGATIVE_X: + vec[0] = -boltMatrix->matrix[0][0]; + vec[1] = -boltMatrix->matrix[1][0]; + vec[2] = -boltMatrix->matrix[2][0]; + break; + case NEGATIVE_Z: + vec[0] = -boltMatrix->matrix[0][2]; + vec[1] = -boltMatrix->matrix[1][2]; + vec[2] = -boltMatrix->matrix[2][2]; + break; + } +} +#endif + +/*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended +DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION. +The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface. + +If an item is the target of another entity, it will not spawn in until fired. + +An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. + +"notfree" if set to 1, don't spawn in free for all games +"notteam" if set to 1, don't spawn in team games +"notsingle" if set to 1, don't spawn in single player games +"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. +"random" random number of plus or minus seconds varied from the respawn time +"count" override quantity or duration on most items. +*/ + +gitem_t bg_itemlist[] = +{ + { + NULL, // classname + NULL, // pickup_sound + { NULL, // world_model[0] + NULL, // world_model[1] + 0, 0} , // world_model[2],[3] + NULL, // view_model +/* icon */ NULL, // icon +/* pickup */ //NULL, // pickup_name + 0, // quantity + 0, // giType (IT_*) + 0, // giTag +/* precache */ "", // precaches +/* sounds */ "", // sounds + "" // description + }, // leave index 0 alone + + // + // Pickups + // + +/*QUAKED item_shield_sm_instant (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Instant shield pickup, restores 25 +*/ + { + "item_shield_sm_instant", + "sound/player/pickupshield.wav", + { "models/map_objects/mp/psd_sm.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/small_shield", +/* pickup */// "Shield Small", + 25, + IT_ARMOR, + 1, //special for shield - max on pickup is maxhealth*tag, thus small shield goes up to 100 shield +/* precache */ "", +/* sounds */ "" + "" // description + }, + +/*QUAKED item_shield_lrg_instant (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Instant shield pickup, restores 100 +*/ + { + "item_shield_lrg_instant", + "sound/player/pickupshield.wav", + { "models/map_objects/mp/psd.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/large_shield", +/* pickup */// "Shield Large", + 100, + IT_ARMOR, + 2, //special for shield - max on pickup is maxhealth*tag, thus large shield goes up to 200 shield +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_medpak_instant (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Instant medpack pickup, heals 25 +*/ + { + "item_medpak_instant", + "sound/player/pickuphealth.wav", + { "models/map_objects/mp/medpac.md3", + 0, 0, 0 }, +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_medkit", +/* pickup */// "Medpack", + 25, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + + // + // ITEMS + // + +/*QUAKED item_seeker (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +30 seconds of seeker drone +*/ + { + "item_seeker", + "sound/weapons/w_pkup.wav", + { "models/items/remote.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_seeker", +/* pickup */// "Seeker Drone", + 120, + IT_HOLDABLE, + HI_SEEKER, +/* precache */ "", +/* sounds */ "", + "@MENUS_AN_ATTACK_DRONE_SIMILAR" // description + }, + +/*QUAKED item_shield (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Portable shield +*/ + { + "item_shield", + "sound/weapons/w_pkup.wav", + { "models/map_objects/mp/shield.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_shieldwall", +/* pickup */// "Forcefield", + 120, + IT_HOLDABLE, + HI_SHIELD, +/* precache */ "", +/* sounds */ "sound/weapons/detpack/stick.wav sound/movers/doors/forcefield_on.wav sound/movers/doors/forcefield_off.wav sound/movers/doors/forcefield_lp.wav sound/effects/bumpfield.wav", + "@MENUS_THIS_STATIONARY_ENERGY" // description + }, + +/*QUAKED item_medpac (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Bacta canister pickup, heals 25 on use +*/ + { + "item_medpac", //should be item_bacta + "sound/weapons/w_pkup.wav", + { "models/map_objects/mp/bacta.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_bacta", +/* pickup */// "Bacta Canister", + 25, + IT_HOLDABLE, + HI_MEDPAC, +/* precache */ "", +/* sounds */ "", + "@SP_INGAME_BACTA_DESC" // description + }, + +/*QUAKED item_medpac_big (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Big bacta canister pickup, heals 50 on use +*/ + { + "item_medpac_big", //should be item_bacta + "sound/weapons/w_pkup.wav", + { "models/items/big_bacta.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_big_bacta", +/* pickup */// "Bacta Canister", + 25, + IT_HOLDABLE, + HI_MEDPAC_BIG, +/* precache */ "", +/* sounds */ "", + "@SP_INGAME_BACTA_DESC" // description + }, + +/*QUAKED item_binoculars (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +These will be standard equipment on the player - DO NOT PLACE +*/ + { + "item_binoculars", + "sound/weapons/w_pkup.wav", + { "models/items/binoculars.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_zoom", +/* pickup */// "Binoculars", + 60, + IT_HOLDABLE, + HI_BINOCULARS, +/* precache */ "", +/* sounds */ "", + "@SP_INGAME_LA_GOGGLES_DESC" // description + }, + +/*QUAKED item_sentry_gun (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Sentry gun inventory pickup. +*/ + { + "item_sentry_gun", + "sound/weapons/w_pkup.wav", + { "models/items/psgun.glm", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_sentrygun", +/* pickup */// "Sentry Gun", + 120, + IT_HOLDABLE, + HI_SENTRY_GUN, +/* precache */ "", +/* sounds */ "", + "@MENUS_THIS_DEADLY_WEAPON_IS" // description + }, + +/*QUAKED item_jetpack (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Do not place. +*/ + { + "item_jetpack", + "sound/weapons/w_pkup.wav", + { "models/items/psgun.glm", //FIXME: no model + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_jetpack", +/* pickup */// "Sentry Gun", + 120, + IT_HOLDABLE, + HI_JETPACK, +/* precache */ "effects/boba/jet.efx", +/* sounds */ "sound/chars/boba/JETON.wav sound/chars/boba/JETHOVER.wav sound/effects/fire_lp.wav", + "@MENUS_JETPACK_DESC" // description + }, + +/*QUAKED item_healthdisp (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Do not place. For siege classes ONLY. +*/ + { + "item_healthdisp", + "sound/weapons/w_pkup.wav", + { "models/map_objects/mp/bacta.md3", //replace me + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_healthdisp", +/* pickup */// "Sentry Gun", + 120, + IT_HOLDABLE, + HI_HEALTHDISP, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_ammodisp (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Do not place. For siege classes ONLY. +*/ + { + "item_ammodisp", + "sound/weapons/w_pkup.wav", + { "models/map_objects/mp/bacta.md3", //replace me + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_ammodisp", +/* pickup */// "Sentry Gun", + 120, + IT_HOLDABLE, + HI_AMMODISP, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_eweb_holdable (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Do not place. For siege classes ONLY. +*/ + { + "item_eweb_holdable", + "sound/interface/shieldcon_empty", + { "models/map_objects/hoth/eweb_model.glm", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_eweb", +/* pickup */// "Sentry Gun", + 120, + IT_HOLDABLE, + HI_EWEB, +/* precache */ "", +/* sounds */ "", + "@MENUS_EWEB_DESC" // description + }, + +/*QUAKED item_seeker (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +30 seconds of seeker drone +*/ + { + "item_cloak", + "sound/weapons/w_pkup.wav", + { "models/items/psgun.glm", //FIXME: no model + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_cloak", +/* pickup */// "Seeker Drone", + 120, + IT_HOLDABLE, + HI_CLOAK, +/* precache */ "", +/* sounds */ "", + "@MENUS_CLOAK_DESC" // description + }, + +/*QUAKED item_force_enlighten_light (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Adds one rank to all Force powers temporarily. Only light jedi can use. +*/ + { + "item_force_enlighten_light", + "sound/player/enlightenment.wav", + { "models/map_objects/mp/jedi_enlightenment.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/mpi_jlight", +/* pickup */// "Light Force Enlightenment", + 25, + IT_POWERUP, + PW_FORCE_ENLIGHTENED_LIGHT, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_force_enlighten_dark (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Adds one rank to all Force powers temporarily. Only dark jedi can use. +*/ + { + "item_force_enlighten_dark", + "sound/player/enlightenment.wav", + { "models/map_objects/mp/dk_enlightenment.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/mpi_dklight", +/* pickup */// "Dark Force Enlightenment", + 25, + IT_POWERUP, + PW_FORCE_ENLIGHTENED_DARK, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_force_boon (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Unlimited Force Pool for a short time. +*/ + { + "item_force_boon", + "sound/player/boon.wav", + { "models/map_objects/mp/force_boon.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/mpi_fboon", +/* pickup */// "Force Boon", + 25, + IT_POWERUP, + PW_FORCE_BOON, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_ysalimari (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +A small lizard carried on the player, which prevents the possessor from using any Force power. However, he is unaffected by any Force power. +*/ + { + "item_ysalimari", + "sound/player/ysalimari.wav", + { "models/map_objects/mp/ysalimari.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/mpi_ysamari", +/* pickup */// "Ysalamiri", + 25, + IT_POWERUP, + PW_YSALAMIRI, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + // + // WEAPONS + // + +/*QUAKED weapon_stun_baton (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + "weapon_stun_baton", + "sound/weapons/w_pkup.wav", + { "models/weapons2/stun_baton/baton_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/stun_baton/baton.md3", +/* icon */ "gfx/hud/w_icon_stunbaton", +/* pickup */// "Stun Baton", + 100, + IT_WEAPON, + WP_STUN_BATON, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED weapon_melee (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + "weapon_melee", + "sound/weapons/w_pkup.wav", + { "models/weapons2/stun_baton/baton_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/stun_baton/baton.md3", +/* icon */ "gfx/hud/w_icon_melee", +/* pickup */// "Stun Baton", + 100, + IT_WEAPON, + WP_MELEE, +/* precache */ "", +/* sounds */ "", + "@MENUS_MELEE_DESC" // description + }, + +/*QUAKED weapon_saber (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + "weapon_saber", + "sound/weapons/w_pkup.wav", + { "models/weapons2/saber/saber_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/saber/saber_w.md3", +/* icon */ "gfx/hud/w_icon_lightsaber", +/* pickup */// "Lightsaber", + 100, + IT_WEAPON, + WP_SABER, +/* precache */ "", +/* sounds */ "", + "@MENUS_AN_ELEGANT_WEAPON_FOR" // description + }, + +/*QUAKED weapon_bryar_pistol (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + //"weapon_bryar_pistol", + "weapon_blaster_pistol", + "sound/weapons/w_pkup.wav", + { "models/weapons2/blaster_pistol/blaster_pistol_w.glm",//"models/weapons2/briar_pistol/briar_pistol_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/blaster_pistol/blaster_pistol.md3",//"models/weapons2/briar_pistol/briar_pistol.md3", +/* icon */ "gfx/hud/w_icon_blaster_pistol",//"gfx/hud/w_icon_rifle", +/* pickup */// "Bryar Pistol", + 100, + IT_WEAPON, + WP_BRYAR_PISTOL, +/* precache */ "", +/* sounds */ "", + "@MENUS_BLASTER_PISTOL_DESC" // description + }, + +/*QUAKED weapon_concussion_rifle (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_concussion_rifle", + "sound/weapons/w_pkup.wav", + { "models/weapons2/concussion/c_rifle_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/concussion/c_rifle.md3", +/* icon */ "gfx/hud/w_icon_c_rifle",//"gfx/hud/w_icon_rifle", +/* pickup */// "Concussion Rifle", + 50, + IT_WEAPON, + WP_CONCUSSION, +/* precache */ "", +/* sounds */ "", + "@MENUS_CONC_RIFLE_DESC" // description + }, + +/*QUAKED weapon_bryar_pistol_old (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + "weapon_bryar_pistol", + "sound/weapons/w_pkup.wav", + { "models/weapons2/briar_pistol/briar_pistol_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/briar_pistol/briar_pistol.md3", +/* icon */ "gfx/hud/w_icon_briar",//"gfx/hud/w_icon_rifle", +/* pickup */// "Bryar Pistol", + 100, + IT_WEAPON, + WP_BRYAR_OLD, +/* precache */ "", +/* sounds */ "", + "@SP_INGAME_BLASTER_PISTOL" // description + }, + +/*QUAKED weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_blaster", + "sound/weapons/w_pkup.wav", + { "models/weapons2/blaster_r/blaster_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/blaster_r/blaster.md3", +/* icon */ "gfx/hud/w_icon_blaster", +/* pickup */// "E11 Blaster Rifle", + 100, + IT_WEAPON, + WP_BLASTER, +/* precache */ "", +/* sounds */ "", + "@MENUS_THE_PRIMARY_WEAPON_OF" // description + }, + +/*QUAKED weapon_disruptor (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_disruptor", + "sound/weapons/w_pkup.wav", + { "models/weapons2/disruptor/disruptor_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/disruptor/disruptor.md3", +/* icon */ "gfx/hud/w_icon_disruptor", +/* pickup */// "Tenloss Disruptor Rifle", + 100, + IT_WEAPON, + WP_DISRUPTOR, +/* precache */ "", +/* sounds */ "", + "@MENUS_THIS_NEFARIOUS_WEAPON" // description + }, + +/*QUAKED weapon_bowcaster (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_bowcaster", + "sound/weapons/w_pkup.wav", + { "models/weapons2/bowcaster/bowcaster_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/bowcaster/bowcaster.md3", +/* icon */ "gfx/hud/w_icon_bowcaster", +/* pickup */// "Wookiee Bowcaster", + 100, + IT_WEAPON, + WP_BOWCASTER, +/* precache */ "", +/* sounds */ "", + "@MENUS_THIS_ARCHAIC_LOOKING" // description + }, + +/*QUAKED weapon_repeater (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_repeater", + "sound/weapons/w_pkup.wav", + { "models/weapons2/heavy_repeater/heavy_repeater_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/heavy_repeater/heavy_repeater.md3", +/* icon */ "gfx/hud/w_icon_repeater", +/* pickup */// "Imperial Heavy Repeater", + 100, + IT_WEAPON, + WP_REPEATER, +/* precache */ "", +/* sounds */ "", + "@MENUS_THIS_DESTRUCTIVE_PROJECTILE" // description + }, + +/*QUAKED weapon_demp2 (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +NOTENOTE This weapon is not yet complete. Don't place it. +*/ + { + "weapon_demp2", + "sound/weapons/w_pkup.wav", + { "models/weapons2/demp2/demp2_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/demp2/demp2.md3", +/* icon */ "gfx/hud/w_icon_demp2", +/* pickup */// "DEMP2", + 100, + IT_WEAPON, + WP_DEMP2, +/* precache */ "", +/* sounds */ "", + "@MENUS_COMMONLY_REFERRED_TO" // description + }, + +/*QUAKED weapon_flechette (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_flechette", + "sound/weapons/w_pkup.wav", + { "models/weapons2/golan_arms/golan_arms_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/golan_arms/golan_arms.md3", +/* icon */ "gfx/hud/w_icon_flechette", +/* pickup */// "Golan Arms Flechette", + 100, + IT_WEAPON, + WP_FLECHETTE, +/* precache */ "", +/* sounds */ "", + "@MENUS_WIDELY_USED_BY_THE_CORPORATE" // description + }, + +/*QUAKED weapon_rocket_launcher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_rocket_launcher", + "sound/weapons/w_pkup.wav", + { "models/weapons2/merr_sonn/merr_sonn_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/merr_sonn/merr_sonn.md3", +/* icon */ "gfx/hud/w_icon_merrsonn", +/* pickup */// "Merr-Sonn Missile System", + 3, + IT_WEAPON, + WP_ROCKET_LAUNCHER, +/* precache */ "", +/* sounds */ "", + "@MENUS_THE_PLX_2M_IS_AN_EXTREMELY" // description + }, + +/*QUAKED ammo_thermal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_thermal", + "sound/weapons/w_pkup.wav", + { "models/weapons2/thermal/thermal_pu.md3", + "models/weapons2/thermal/thermal_w.glm", 0, 0}, +/* view */ "models/weapons2/thermal/thermal.md3", +/* icon */ "gfx/hud/w_icon_thermal", +/* pickup */// "Thermal Detonators", + 4, + IT_AMMO, + AMMO_THERMAL, +/* precache */ "", +/* sounds */ "", + "@MENUS_THE_THERMAL_DETONATOR" // description + }, + +/*QUAKED ammo_tripmine (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_tripmine", + "sound/weapons/w_pkup.wav", + { "models/weapons2/laser_trap/laser_trap_pu.md3", + "models/weapons2/laser_trap/laser_trap_w.glm", 0, 0}, +/* view */ "models/weapons2/laser_trap/laser_trap.md3", +/* icon */ "gfx/hud/w_icon_tripmine", +/* pickup */// "Trip Mines", + 3, + IT_AMMO, + AMMO_TRIPMINE, +/* precache */ "", +/* sounds */ "", + "@MENUS_TRIP_MINES_CONSIST_OF" // description + }, + +/*QUAKED ammo_detpack (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_detpack", + "sound/weapons/w_pkup.wav", + { "models/weapons2/detpack/det_pack_pu.md3", "models/weapons2/detpack/det_pack_proj.glm", "models/weapons2/detpack/det_pack_w.glm", 0}, +/* view */ "models/weapons2/detpack/det_pack.md3", +/* icon */ "gfx/hud/w_icon_detpack", +/* pickup */// "Det Packs", + 3, + IT_AMMO, + AMMO_DETPACK, +/* precache */ "", +/* sounds */ "", + "@MENUS_A_DETONATION_PACK_IS" // description + }, + +/*QUAKED weapon_thermal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_thermal", + "sound/weapons/w_pkup.wav", + { "models/weapons2/thermal/thermal_w.glm", "models/weapons2/thermal/thermal_pu.md3", + 0, 0 }, +/* view */ "models/weapons2/thermal/thermal.md3", +/* icon */ "gfx/hud/w_icon_thermal", +/* pickup */// "Thermal Detonator", + 4, + IT_WEAPON, + WP_THERMAL, +/* precache */ "", +/* sounds */ "", + "@MENUS_THE_THERMAL_DETONATOR" // description + }, + +/*QUAKED weapon_trip_mine (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_trip_mine", + "sound/weapons/w_pkup.wav", + { "models/weapons2/laser_trap/laser_trap_w.glm", "models/weapons2/laser_trap/laser_trap_pu.md3", + 0, 0}, +/* view */ "models/weapons2/laser_trap/laser_trap.md3", +/* icon */ "gfx/hud/w_icon_tripmine", +/* pickup */// "Trip Mine", + 3, + IT_WEAPON, + WP_TRIP_MINE, +/* precache */ "", +/* sounds */ "", + "@MENUS_TRIP_MINES_CONSIST_OF" // description + }, + +/*QUAKED weapon_det_pack (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_det_pack", + "sound/weapons/w_pkup.wav", + { "models/weapons2/detpack/det_pack_proj.glm", "models/weapons2/detpack/det_pack_pu.md3", "models/weapons2/detpack/det_pack_w.glm", 0}, +/* view */ "models/weapons2/detpack/det_pack.md3", +/* icon */ "gfx/hud/w_icon_detpack", +/* pickup */// "Det Pack", + 3, + IT_WEAPON, + WP_DET_PACK, +/* precache */ "", +/* sounds */ "", + "@MENUS_A_DETONATION_PACK_IS" // description + }, + +/*QUAKED weapon_emplaced (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_emplaced", + "sound/weapons/w_pkup.wav", + { "models/weapons2/blaster_r/blaster_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/blaster_r/blaster.md3", +/* icon */ "gfx/hud/w_icon_blaster", +/* pickup */// "Emplaced Gun", + 50, + IT_WEAPON, + WP_EMPLACED_GUN, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + +//NOTE: This is to keep things from messing up because the turret weapon type isn't real + { + "weapon_turretwp", + "sound/weapons/w_pkup.wav", + { "models/weapons2/blaster_r/blaster_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/blaster_r/blaster.md3", +/* icon */ "gfx/hud/w_icon_blaster", +/* pickup */// "Turret Gun", + 50, + IT_WEAPON, + WP_TURRET, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + // + // AMMO ITEMS + // + +/*QUAKED ammo_force (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + "ammo_force", + "sound/player/pickupenergy.wav", + { "models/items/energy_cell.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/hud/w_icon_blaster", +/* pickup */// "Force??", + 100, + IT_AMMO, + AMMO_FORCE, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED ammo_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Ammo for the Bryar and Blaster pistols. +*/ + { + "ammo_blaster", + "sound/player/pickupenergy.wav", + { "models/items/energy_cell.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_battery", +/* pickup */// "Blaster Pack", + 100, + IT_AMMO, + AMMO_BLASTER, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED ammo_powercell (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Ammo for Tenloss Disruptor, Wookie Bowcaster, and the Destructive Electro Magnetic Pulse (demp2 ) guns +*/ + { + "ammo_powercell", + "sound/player/pickupenergy.wav", + { "models/items/power_cell.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/ammo_power_cell", +/* pickup */// "Power Cell", + 100, + IT_AMMO, + AMMO_POWERCELL, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED ammo_metallic_bolts (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Ammo for Imperial Heavy Repeater and the Golan Arms Flechette +*/ + { + "ammo_metallic_bolts", + "sound/player/pickupenergy.wav", + { "models/items/metallic_bolts.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/ammo_metallic_bolts", +/* pickup */// "Metallic Bolts", + 100, + IT_AMMO, + AMMO_METAL_BOLTS, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Ammo for Merr-Sonn portable missile launcher +*/ + { + "ammo_rockets", + "sound/player/pickupenergy.wav", + { "models/items/rockets.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/ammo_rockets", +/* pickup */// "Rockets", + 3, + IT_AMMO, + AMMO_ROCKETS, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED ammo_all (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +DO NOT PLACE in a map, this is only for siege classes that have ammo +dispensing ability +*/ + { + "ammo_all", + "sound/player/pickupenergy.wav", + { "models/items/battery.md3", //replace me + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/ammo_rockets", //replace me +/* pickup */// "Rockets", + 0, + IT_AMMO, + -1, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + // + // POWERUP ITEMS + // +/*QUAKED team_CTF_redflag (1 0 0) (-16 -16 -16) (16 16 16) +Only in CTF games +*/ + { + "team_CTF_redflag", + NULL, + { "models/flags/r_flag.md3", + "models/flags/r_flag_ysal.md3", 0, 0 }, +/* view */ NULL, +/* icon */ "gfx/hud/mpi_rflag", +/* pickup */// "Red Flag", + 0, + IT_TEAM, + PW_REDFLAG, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16) +Only in CTF games +*/ + { + "team_CTF_blueflag", + NULL, + { "models/flags/b_flag.md3", + "models/flags/b_flag_ysal.md3", 0, 0 }, +/* view */ NULL, +/* icon */ "gfx/hud/mpi_bflag", +/* pickup */// "Blue Flag", + 0, + IT_TEAM, + PW_BLUEFLAG, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + // + // PERSISTANT POWERUP ITEMS + // + + /*QUAKED team_CTF_neutralflag (0 0 1) (-16 -16 -16) (16 16 16) +Only in One Flag CTF games +*/ + { + "team_CTF_neutralflag", + NULL, + { "models/flags/n_flag.md3", + 0, 0, 0 }, +/* view */ NULL, +/* icon */ "icons/iconf_neutral1", +/* pickup */// "Neutral Flag", + 0, + IT_TEAM, + PW_NEUTRALFLAG, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + { + "item_redcube", + "sound/player/pickupenergy.wav", + { "models/powerups/orb/r_orb.md3", + 0, 0, 0 }, +/* view */ NULL, +/* icon */ "icons/iconh_rorb", +/* pickup */// "Red Cube", + 0, + IT_TEAM, + 0, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + { + "item_bluecube", + "sound/player/pickupenergy.wav", + { "models/powerups/orb/b_orb.md3", + 0, 0, 0 }, +/* view */ NULL, +/* icon */ "icons/iconh_borb", +/* pickup */// "Blue Cube", + 0, + IT_TEAM, + 0, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + // end of list marker + {NULL} +}; + +int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1; + +float vectoyaw( const vec3_t vec ) { + float yaw; + + if (vec[YAW] == 0 && vec[PITCH] == 0) { + yaw = 0; + } else { + if (vec[PITCH]) { + yaw = ( atan2( vec[YAW], vec[PITCH]) * 180 / M_PI ); + } else if (vec[YAW] > 0) { + yaw = 90; + } else { + yaw = 270; + } + if (yaw < 0) { + yaw += 360; + } + } + + return yaw; +} + +qboolean BG_HasYsalamiri(int gametype, playerState_t *ps) +{ + if (gametype == GT_CTY && + (ps->powerups[PW_REDFLAG] || ps->powerups[PW_BLUEFLAG])) + { + return qtrue; + } + + if (ps->powerups[PW_YSALAMIRI]) + { + return qtrue; + } + + return qfalse; +} + +qboolean BG_CanUseFPNow(int gametype, playerState_t *ps, int time, forcePowers_t power) +{ + if (BG_HasYsalamiri(gametype, ps)) + { + return qfalse; + } + + if ( ps->forceRestricted || ps->trueNonJedi ) + { + return qfalse; + } + + if (ps->weapon == WP_EMPLACED_GUN) + { //can't use any of your powers while on an emplaced weapon + return qfalse; + } + + if (ps->m_iVehicleNum) + { //can't use powers while riding a vehicle (this may change, I don't know) + return qfalse; + } + + if (ps->duelInProgress) + { + if (power != FP_SABER_OFFENSE && power != FP_SABER_DEFENSE && /*power != FP_SABERTHROW &&*/ + power != FP_LEVITATION) + { + if (!ps->saberLockFrame || power != FP_PUSH) + { + return qfalse; + } + } + } + + if (ps->saberLockFrame || ps->saberLockTime > time) + { + if (power != FP_PUSH) + { + return qfalse; + } + } + + if (ps->fallingToDeath) + { + return qfalse; + } + + if ((ps->brokenLimbs & (1 << BROKENLIMB_RARM)) || + (ps->brokenLimbs & (1 << BROKENLIMB_LARM))) + { //powers we can't use with a broken arm + switch (power) + { + case FP_PUSH: + case FP_PULL: + case FP_GRIP: + case FP_LIGHTNING: + case FP_DRAIN: + return qfalse; + default: + break; + } + } + + return qtrue; +} + +/* +============== +BG_FindItemForPowerup +============== +*/ +gitem_t *BG_FindItemForPowerup( powerup_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( (bg_itemlist[i].giType == IT_POWERUP || + bg_itemlist[i].giType == IT_TEAM) && + bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + return NULL; +} + + +/* +============== +BG_FindItemForHoldable +============== +*/ +gitem_t *BG_FindItemForHoldable( holdable_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( bg_itemlist[i].giType == IT_HOLDABLE && bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + Com_Error( ERR_DROP, "HoldableItem not found" ); + + return NULL; +} + + +/* +=============== +BG_FindItemForWeapon + +=============== +*/ +gitem_t *BG_FindItemForWeapon( weapon_t weapon ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++) { + if ( it->giType == IT_WEAPON && it->giTag == weapon ) { + return it; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon); + return NULL; +} + +/* +=============== +BG_FindItemForAmmo + +=============== +*/ +gitem_t *BG_FindItemForAmmo( ammo_t ammo ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++) { + if ( it->giType == IT_AMMO && it->giTag == ammo ) { + return it; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for ammo %i", ammo); + return NULL; +} + +/* +=============== +BG_FindItem + +=============== +*/ +gitem_t *BG_FindItem( const char *classname ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { + if ( !Q_stricmp( it->classname, classname) ) + return it; + } + + return NULL; +} + +/* +============ +BG_PlayerTouchesItem + +Items can be picked up without actually touching their physical bounds to make +grabbing them easier +============ +*/ +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) { + vec3_t origin; + + BG_EvaluateTrajectory( &item->pos, atTime, origin ); + + // we are ignoring ducked differences here + if ( ps->origin[0] - origin[0] > 44 + || ps->origin[0] - origin[0] < -50 + || ps->origin[1] - origin[1] > 36 + || ps->origin[1] - origin[1] < -36 + || ps->origin[2] - origin[2] > 36 + || ps->origin[2] - origin[2] < -36 ) { + return qfalse; + } + + return qtrue; +} + +int BG_ProperForceIndex(int power) +{ + int i = 0; + + while (i < NUM_FORCE_POWERS) + { + if (forcePowerSorted[i] == power) + { + return i; + } + + i++; + } + + return -1; +} + +void BG_CycleForce(playerState_t *ps, int direction) +{ + int i = ps->fd.forcePowerSelected; + int x = i; + int presel = i; + int foundnext = -1; + + if (!ps->fd.forcePowersKnown & (1 << x) || + x >= NUM_FORCE_POWERS || + x == -1) + { //apparently we have no valid force powers + return; + } + + x = BG_ProperForceIndex(x); + presel = x; + + if (direction == 1) + { //get the next power + x++; + } + else + { //get the previous power + x--; + } + + if (x >= NUM_FORCE_POWERS) + { //cycled off the end.. cycle around to the first + x = 0; + } + if (x < 0) + { //cycled off the beginning.. cycle around to the last + x = NUM_FORCE_POWERS-1; + } + + i = forcePowerSorted[x]; //the "sorted" value of this power + + while (x != presel) + { //loop around to the current force power + if (ps->fd.forcePowersKnown & (1 << i) && i != ps->fd.forcePowerSelected) + { //we have the force power + if (i != FP_LEVITATION && + i != FP_SABER_OFFENSE && + i != FP_SABER_DEFENSE && + i != FP_SABERTHROW) + { //it's selectable + foundnext = i; + break; + } + } + + if (direction == 1) + { //next + x++; + } + else + { //previous + x--; + } + + if (x >= NUM_FORCE_POWERS) + { //loop around + x = 0; + } + if (x < 0) + { //loop around + x = NUM_FORCE_POWERS-1; + } + + i = forcePowerSorted[x]; //set to the sorted value again + } + + if (foundnext != -1) + { //found one, select it + ps->fd.forcePowerSelected = foundnext; + } +} + +int BG_GetItemIndexByTag(int tag, int type) +{ //Get the itemlist index from the tag and type + int i = 0; + + while (i < bg_numItems) + { + if (bg_itemlist[i].giTag == tag && + bg_itemlist[i].giType == type) + { + return i; + } + + i++; + } + + return 0; +} + +//yeah.. +qboolean BG_IsItemSelectable(playerState_t *ps, int item) +{ + if (item == HI_HEALTHDISP || item == HI_AMMODISP || + item == HI_JETPACK) + { + return qfalse; + } + return qtrue; +} + +void BG_CycleInven(playerState_t *ps, int direction) +{ + int i; + int dontFreeze = 0; + int original; + + i = bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag; + original = i; + + if (direction == 1) + { //next + i++; + if (i == HI_NUM_HOLDABLE) + { + i = 1; + } + } + else + { //previous + i--; + if (i == 0) + { + i = HI_NUM_HOLDABLE-1; + } + } + + while (i != original) + { //go in a full loop until hitting something, if hit nothing then select nothing + if (ps->stats[STAT_HOLDABLE_ITEMS] & (1 << i)) + { //we have it, select it. + if (BG_IsItemSelectable(ps, i)) + { + ps->stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(i, IT_HOLDABLE); + break; + } + } + + if (direction == 1) + { //next + i++; + } + else + { //previous + i--; + } + + if (i <= 0) + { //wrap around to the last + i = HI_NUM_HOLDABLE-1; + } + else if (i >= HI_NUM_HOLDABLE) + { //wrap around to the first + i = 1; + } + + dontFreeze++; + if (dontFreeze >= 32) + { //yeah, sure, whatever (it's 2 am and I'm paranoid and can't frickin think) + break; + } + } +} + +/* +================ +BG_CanItemBeGrabbed + +Returns false if the item should not be picked up. +This needs to be the same for client side prediction and server use. +================ +*/ +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ) { + gitem_t *item; + + if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) { + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" ); + } + + item = &bg_itemlist[ent->modelindex]; + + if ( ps ) + { + if ( ps->trueJedi ) + {//force powers and saber only + if ( item->giType != IT_TEAM //not a flag + && item->giType != IT_ARMOR//not shields + && (item->giType != IT_WEAPON || item->giTag != WP_SABER)//not a saber + && (item->giType != IT_HOLDABLE || item->giTag != HI_SEEKER)//not a seeker + && (item->giType != IT_POWERUP || item->giTag == PW_YSALAMIRI) )//not a force pick-up + { + return qfalse; + } + } + else if ( ps->trueNonJedi ) + {//can't pick up force powerups + if ( (item->giType == IT_POWERUP && item->giTag != PW_YSALAMIRI) //if a powerup, can only can pick up ysalamiri + || (item->giType == IT_HOLDABLE && item->giTag == HI_SEEKER)//if holdable, cannot pick up seeker + || (item->giType == IT_WEAPON && item->giTag == WP_SABER ) )//or if it's a saber + { + return qfalse; + } + } +/* + if ( ps->isJediMaster && item && (item->giType == IT_WEAPON || item->giType == IT_AMMO)) + {//jedi master cannot pick up weapons + return qfalse; + } +*/ + if ( ps->duelInProgress ) + { //no picking stuff up while in a duel, no matter what the type is + return qfalse; + } + } + else + {//safety return since below code assumes a non-null ps + return qfalse; + } + + switch( item->giType ) { + case IT_WEAPON: + if (ent->generic1 == ps->clientNum && ent->powerups) + { + return qfalse; + } + if (!(ent->eFlags & EF_DROPPEDWEAPON) && (ps->stats[STAT_WEAPONS] & (1 << item->giTag)) && + item->giTag != WP_THERMAL && item->giTag != WP_TRIP_MINE && item->giTag != WP_DET_PACK) + { //weaponstay stuff.. if this isn't dropped, and you already have it, you don't get it. + return qfalse; + } + if (item->giTag == WP_THERMAL || item->giTag == WP_TRIP_MINE || item->giTag == WP_DET_PACK) + { //check to see if full on ammo for this, if so, then.. + int ammoIndex = weaponData[item->giTag].ammoIndex; + if (ps->ammo[ammoIndex] >= ammoData[ammoIndex].max) + { //don't need it + return qfalse; + } + } + return qtrue; // weapons are always picked up + + case IT_AMMO: + if (item->giTag == -1) + { //special case for "all ammo" packs + return qtrue; + } + if ( ps->ammo[item->giTag] >= ammoData[item->giTag].max) { + return qfalse; // can't hold any more + } + return qtrue; + + case IT_ARMOR: + if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH]/* * item->giTag*/ ) { + return qfalse; + } + return qtrue; + + case IT_HEALTH: + // small and mega healths will go over the max, otherwise + // don't pick up if already at max + if ((ps->fd.forcePowersActive & (1 << FP_RAGE))) + { + return qfalse; + } + + if ( item->quantity == 5 || item->quantity == 100 ) { + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } + return qtrue; + } + + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { + return qfalse; + } + return qtrue; + + case IT_POWERUP: + if (ps && (ps->powerups[PW_YSALAMIRI])) + { + if (item->giTag != PW_YSALAMIRI) + { + return qfalse; + } + } + return qtrue; // powerups are always picked up + + case IT_TEAM: // team items, such as flags + if( gametype == GT_CTF || gametype == GT_CTY ) { + // ent->modelindex2 is non-zero on items if they are dropped + // we need to know this because we can pick up our dropped flag (and return it) + // but we can't pick up our flag at base + if (ps->persistant[PERS_TEAM] == TEAM_RED) { + if (item->giTag == PW_BLUEFLAG || + (item->giTag == PW_REDFLAG && ent->modelindex2) || + (item->giTag == PW_REDFLAG && ps->powerups[PW_BLUEFLAG]) ) + return qtrue; + } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { + if (item->giTag == PW_REDFLAG || + (item->giTag == PW_BLUEFLAG && ent->modelindex2) || + (item->giTag == PW_BLUEFLAG && ps->powerups[PW_REDFLAG]) ) + return qtrue; + } + } + + return qfalse; + + case IT_HOLDABLE: + if ( ps->stats[STAT_HOLDABLE_ITEMS] & (1 << item->giTag)) + { + return qfalse; + } + return qtrue; + + case IT_BAD: + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" ); + default: +#ifndef Q3_VM +#ifndef NDEBUG // bk0001204 + Com_Printf("BG_CanItemBeGrabbed: unknown enum %d\n", item->giType ); +#endif +#endif + break; + } + + return qfalse; +} + +//====================================================================== + +/* +================ +BG_EvaluateTrajectory + +================ +*/ +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorCopy( tr->trBase, result ); + break; + case TR_LINEAR: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = sin( deltaTime * M_PI * 2 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + atTime = tr->trTime + tr->trDuration; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + if ( deltaTime < 0 ) { + deltaTime = 0; + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_NONLINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) + { + atTime = tr->trTime + tr->trDuration; + } + //new slow-down at end + if ( atTime - tr->trTime > tr->trDuration || atTime - tr->trTime <= 0 ) + { + deltaTime = 0; + } + else + {//FIXME: maybe scale this somehow? So that it starts out faster and stops faster? + deltaTime = tr->trDuration*0.001f*((float)cos( DEG2RAD(90.0f - (90.0f*((float)atTime-tr->trTime)/(float)tr->trDuration)) )); + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + default: +#ifdef QAGAME + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: [GAME SIDE] unknown trType: %i", tr->trType ); +#else + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: [CLIENTGAME SIDE] unknown trType: %i", tr->trType ); +#endif + break; + } +} + +/* +================ +BG_EvaluateTrajectoryDelta + +For determining velocity at a given time +================ +*/ +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos + phase *= 0.5; + VectorScale( tr->trDelta, phase, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + case TR_NONLINEAR_STOP: + if ( atTime - tr->trTime > tr->trDuration || atTime - tr->trTime <= 0 ) + { + VectorClear( result ); + return; + } + deltaTime = tr->trDuration*0.001f*((float)cos( DEG2RAD(90.0f - (90.0f*((float)atTime-tr->trTime)/(float)tr->trDuration)) )); + VectorScale( tr->trDelta, deltaTime, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +char *eventnames[] = { + "EV_NONE", + + "EV_CLIENTJOIN", + + "EV_FOOTSTEP", + "EV_FOOTSTEP_METAL", + "EV_FOOTSPLASH", + "EV_FOOTWADE", + "EV_SWIM", + + "EV_STEP_4", + "EV_STEP_8", + "EV_STEP_12", + "EV_STEP_16", + + "EV_FALL", + + "EV_JUMP_PAD", // boing sound at origin", jump sound on player + + "EV_GHOUL2_MARK", //create a projectile impact mark on something with a client-side g2 instance. + + "EV_GLOBAL_DUEL", + "EV_PRIVATE_DUEL", + + "EV_JUMP", + "EV_ROLL", + "EV_WATER_TOUCH", // foot touches + "EV_WATER_LEAVE", // foot leaves + "EV_WATER_UNDER", // head touches + "EV_WATER_CLEAR", // head leaves + + "EV_ITEM_PICKUP", // normal item pickups are predictable + "EV_GLOBAL_ITEM_PICKUP", // powerup / team sounds are broadcast to everyone + + "EV_VEH_FIRE", + + "EV_NOAMMO", + "EV_CHANGE_WEAPON", + "EV_FIRE_WEAPON", + "EV_ALT_FIRE", + "EV_SABER_ATTACK", + "EV_SABER_HIT", + "EV_SABER_BLOCK", + "EV_SABER_CLASHFLARE", + "EV_SABER_UNHOLSTER", + "EV_BECOME_JEDIMASTER", + "EV_DISRUPTOR_MAIN_SHOT", + "EV_DISRUPTOR_SNIPER_SHOT", + "EV_DISRUPTOR_SNIPER_MISS", + "EV_DISRUPTOR_HIT", + "EV_DISRUPTOR_ZOOMSOUND", + + "EV_PREDEFSOUND", + + "EV_TEAM_POWER", + + "EV_SCREENSHAKE", + + "EV_LOCALTIMER", + + "EV_USE", // +Use key + + "EV_USE_ITEM0", + "EV_USE_ITEM1", + "EV_USE_ITEM2", + "EV_USE_ITEM3", + "EV_USE_ITEM4", + "EV_USE_ITEM5", + "EV_USE_ITEM6", + "EV_USE_ITEM7", + "EV_USE_ITEM8", + "EV_USE_ITEM9", + "EV_USE_ITEM10", + "EV_USE_ITEM11", + "EV_USE_ITEM12", + "EV_USE_ITEM13", + "EV_USE_ITEM14", + "EV_USE_ITEM15", + + "EV_ITEMUSEFAIL", + + "EV_ITEM_RESPAWN", + "EV_ITEM_POP", + "EV_PLAYER_TELEPORT_IN", + "EV_PLAYER_TELEPORT_OUT", + + "EV_GRENADE_BOUNCE", // eventParm will be the soundindex + "EV_MISSILE_STICK", + + "EV_PLAY_EFFECT", + "EV_PLAY_EFFECT_ID", //finally gave in and added it.. + "EV_PLAY_PORTAL_EFFECT_ID", + + "EV_PLAYDOORSOUND", + "EV_PLAYDOORLOOPSOUND", + "EV_BMODEL_SOUND", + + "EV_MUTE_SOUND", + "EV_VOICECMD_SOUND", + "EV_GENERAL_SOUND", + "EV_GLOBAL_SOUND", // no attenuation + "EV_GLOBAL_TEAM_SOUND", + "EV_ENTITY_SOUND", + + "EV_PLAY_ROFF", + + "EV_GLASS_SHATTER", + "EV_DEBRIS", + "EV_MISC_MODEL_EXP", + + "EV_CONC_ALT_IMPACT", + + "EV_MISSILE_HIT", + "EV_MISSILE_MISS", + "EV_MISSILE_MISS_METAL", + "EV_BULLET", // otherEntity is the shooter + + "EV_PAIN", + "EV_DEATH1", + "EV_DEATH2", + "EV_DEATH3", + "EV_OBITUARY", + + "EV_POWERUP_QUAD", + "EV_POWERUP_BATTLESUIT", + //"EV_POWERUP_REGEN", + + "EV_FORCE_DRAINED", + + "EV_GIB_PLAYER", // gib a previously living player + "EV_SCOREPLUM", // score plum + + "EV_CTFMESSAGE", + + "EV_BODYFADE", + + "EV_SIEGE_ROUNDOVER", + "EV_SIEGE_OBJECTIVECOMPLETE", + + "EV_DESTROY_GHOUL2_INSTANCE", + + "EV_DESTROY_WEAPON_MODEL", + + "EV_GIVE_NEW_RANK", + "EV_SET_FREE_SABER", + "EV_SET_FORCE_DISABLE", + + "EV_WEAPON_CHARGE", + "EV_WEAPON_CHARGE_ALT", + + "EV_SHIELD_HIT", + + "EV_DEBUG_LINE", + "EV_TESTLINE", + "EV_STOPLOOPINGSOUND", + "EV_STARTLOOPINGSOUND", + "EV_TAUNT", + + "EV_TAUNT_YES", + "EV_TAUNT_NO", + "EV_TAUNT_FOLLOWME", + "EV_TAUNT_GETFLAG", + "EV_TAUNT_GUARDBASE", + "EV_TAUNT_PATROL", + +}; + +/* +=============== +BG_AddPredictableEventToPlayerstate + +Handles the sequence numbers +=============== +*/ + +//void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { + +#ifdef _DEBUG + { + static vmCvar_t showEvents; + static qboolean isRegistered = qfalse; + + if (!isRegistered) + { + trap_Cvar_Register(&showEvents, "showevents", "0", 0); + isRegistered = qtrue; + } + + if ( showEvents.integer != 0 ) { +#ifdef QAGAME + Com_Printf(" game event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); +#else + Com_Printf("Cgame event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); +#endif + } + } +#endif + ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; + ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; + ps->eventSequence++; +} + +/* +======================== +BG_TouchJumpPad +======================== +*/ +void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ) { + vec3_t angles; + float p; + int effectNum; + + // spectators don't use jump pads + if ( ps->pm_type != PM_NORMAL && ps->pm_type != PM_JETPACK && ps->pm_type != PM_FLOAT ) { + return; + } + + // if we didn't hit this same jumppad the previous frame + // then don't play the event sound again if we are in a fat trigger + if ( ps->jumppad_ent != jumppad->number ) { + + vectoangles( jumppad->origin2, angles); + p = fabs( AngleNormalize180( angles[PITCH] ) ); + if( p < 45 ) { + effectNum = 0; + } else { + effectNum = 1; + } + } + // remember hitting this jumppad this frame + ps->jumppad_ent = jumppad->number; + ps->jumppad_frame = ps->pmove_framecount; + // give the player the velocity from the jumppad + VectorCopy( jumppad->origin2, ps->velocity ); +} + +/* +================= +BG_EmplacedView + +Shared code for emplaced angle gun constriction +================= +*/ +int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint) +{ + float dif = AngleSubtract(baseAngles[YAW], angles[YAW]); + + if (dif > constraint || + dif < -constraint) + { + float amt; + + if (dif > constraint) + { + amt = (dif-constraint); + dif = constraint; + } + else if (dif < -constraint) + { + amt = (dif+constraint); + dif = -constraint; + } + else + { + amt = 0.0f; + } + + *newYaw = AngleSubtract(angles[YAW], -dif); + + if (amt > 1.0f || amt < -1.0f) + { //significant, force the view + return 2; + } + else + { //just a little out of range + return 1; + } + } + + return 0; +} + +//To see if the client is trying to use one of the included skins not meant for MP. +//I don't much care for hardcoded strings, but this seems the best way to go. +qboolean BG_IsValidCharacterModel(const char *modelName, const char *skinName) +{ + if (!Q_stricmp(modelName, "kyle")) + { + if (!Q_stricmp(skinName, "fpls")) + { + return qfalse; + } + else if (!Q_stricmp(skinName, "fpls2")) + { + return qfalse; + } + else if (!Q_stricmp(skinName, "fpls3")) + { + return qfalse; + } + } + return qtrue; +} + +qboolean BG_ValidateSkinForTeam( const char *modelName, char *skinName, int team, float *colors ) +{ + char trunc[6]; + int i = 0; + while (i < 5) + { + trunc[i] = modelName[i]; + i++; + } + trunc[i] = 0; + + if (!Q_stricmp(trunc, "jedi_")) + { //argh, it's a custom player skin! + if (team == TEAM_RED && colors) + { + colors[0] = 1.0f; + colors[1] = 0.0f; + colors[2] = 0.0f; + } + else if (team == TEAM_BLUE && colors) + { + colors[0] = 0.0f; + colors[1] = 0.0f; + colors[2] = 1.0f; + } + return qtrue; + } + + if (team == TEAM_RED) + { + if ( Q_stricmp( "red", skinName ) != 0 ) + {//not "red" + if ( Q_stricmp( "blue", skinName ) == 0 + || Q_stricmp( "default", skinName ) == 0 + || strchr(skinName, '|')//a multi-skin playerModel + || !BG_IsValidCharacterModel(modelName, skinName) ) + { + Q_strncpyz(skinName, "red", MAX_QPATH); + return qfalse; + } + else + {//need to set it to red + int len = strlen( skinName ); + if ( len < 3 ) + {//too short to be "red" + Q_strcat(skinName, MAX_QPATH, "_red"); + } + else + { + char *start = &skinName[len-3]; + if ( Q_strncmp( "red", start, 3 ) != 0 ) + {//doesn't already end in "red" + if ( len+4 >= MAX_QPATH ) + {//too big to append "_red" + Q_strncpyz(skinName, "red", MAX_QPATH); + return qfalse; + } + else + { + Q_strcat(skinName, MAX_QPATH, "_red"); + } + } + } + //if file does not exist, set to "red" + if ( !BG_FileExists( va( "models/players/%s/model_%s.skin", modelName, skinName ) ) ) + { + Q_strncpyz(skinName, "red", MAX_QPATH); + } + return qfalse; + } + } + + } + else if (team == TEAM_BLUE) + { + if ( Q_stricmp( "blue", skinName ) != 0 ) + { + if ( Q_stricmp( "red", skinName ) == 0 + || Q_stricmp( "default", skinName ) == 0 + || strchr(skinName, '|')//a multi-skin playerModel + || !BG_IsValidCharacterModel(modelName, skinName) ) + { + Q_strncpyz(skinName, "blue", MAX_QPATH); + return qfalse; + } + else + {//need to set it to blue + int len = strlen( skinName ); + if ( len < 4 ) + {//too short to be "blue" + Q_strcat(skinName, MAX_QPATH, "_blue"); + } + else + { + char *start = &skinName[len-4]; + if ( Q_strncmp( "blue", start, 4 ) != 0 ) + {//doesn't already end in "blue" + if ( len+5 >= MAX_QPATH ) + {//too big to append "_blue" + Q_strncpyz(skinName, "blue", MAX_QPATH); + return qfalse; + } + else + { + Q_strcat(skinName, MAX_QPATH, "_blue"); + } + } + } + //if file does not exist, set to "blue" + if ( !BG_FileExists( va( "models/players/%s/model_%s.skin", modelName, skinName ) ) ) + { + Q_strncpyz(skinName, "blue", MAX_QPATH); + } + return qfalse; + } + } + } + return qtrue; +} + +/* +======================== +BG_PlayerStateToEntityState + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) { + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + s->eType = ET_INVISIBLE; + } else { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_INTERPOLATE; + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) { + SnapVector( s->pos.trBase ); + } + // set the trDelta for flag direction + VectorCopy( ps->velocity, s->pos.trDelta ); + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) { + SnapVector( s->apos.trBase ); + } + + s->trickedentindex = ps->fd.forceMindtrickTargetIndex; + s->trickedentindex2 = ps->fd.forceMindtrickTargetIndex2; + s->trickedentindex3 = ps->fd.forceMindtrickTargetIndex3; + s->trickedentindex4 = ps->fd.forceMindtrickTargetIndex4; + + s->forceFrame = ps->saberLockFrame; + + s->emplacedOwner = ps->electrifyTime; + + s->speed = ps->speed; + + s->genericenemyindex = ps->genericEnemyIndex; + + s->activeForcePass = ps->activeForcePass; + + s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + + s->legsFlip = ps->legsFlip; + s->torsoFlip = ps->torsoFlip; + + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + s->eFlags2 = ps->eFlags2; + + s->saberInFlight = ps->saberInFlight; + s->saberEntityNum = ps->saberEntityNum; + s->saberMove = ps->saberMove; + s->forcePowersActive = ps->fd.forcePowersActive; + + if (ps->duelInProgress) + { + s->bolt1 = 1; + } + else + { + s->bolt1 = 0; + } + + s->otherEntityNum2 = ps->emplacedIndex; + + s->saberHolstered = ps->saberHolstered; + + if (ps->genericEnemyIndex != -1) + { + s->eFlags |= EF_SEEKERDRONE; + } + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->eFlags |= EF_DEAD; + } else { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; + + //NOT INCLUDED IN ENTITYSTATETOPLAYERSTATE: + s->modelindex2 = ps->weaponstate; + s->constantLight = ps->weaponChargeTime; + + VectorCopy(ps->lastHitLoc, s->origin2); + +// s->isJediMaster = ps->isJediMaster; + +// s->time2 = ps->holocronBits; + s->time2 = 0; // ??? + + s->fireflag = ps->fd.saberAnimLevel; + + s->heldByClient = ps->heldByClient; + s->ragAttach = ps->ragAttach; + + s->iModelScale = ps->iModelScale; + + s->brokenLimbs = ps->brokenLimbs; + + s->hasLookTarget = ps->hasLookTarget; + s->lookTarget = ps->lookTarget; + + s->customRGBA[0] = ps->customRGBA[0]; + s->customRGBA[1] = ps->customRGBA[1]; + s->customRGBA[2] = ps->customRGBA[2]; + s->customRGBA[3] = ps->customRGBA[3]; + + s->m_iVehicleNum = ps->m_iVehicleNum; +} + +/* +======================== +BG_PlayerStateToEntityStateExtraPolate + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) { + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + s->eType = ET_INVISIBLE; + } else { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_LINEAR_STOP; + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) { + SnapVector( s->pos.trBase ); + } + // set the trDelta for flag direction and linear prediction + VectorCopy( ps->velocity, s->pos.trDelta ); + // set the time for linear prediction + s->pos.trTime = time; + // set maximum extra polation time + s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) { + SnapVector( s->apos.trBase ); + } + + s->trickedentindex = ps->fd.forceMindtrickTargetIndex; + s->trickedentindex2 = ps->fd.forceMindtrickTargetIndex2; + s->trickedentindex3 = ps->fd.forceMindtrickTargetIndex3; + s->trickedentindex4 = ps->fd.forceMindtrickTargetIndex4; + + s->forceFrame = ps->saberLockFrame; + + s->emplacedOwner = ps->electrifyTime; + + s->speed = ps->speed; + + s->genericenemyindex = ps->genericEnemyIndex; + + s->activeForcePass = ps->activeForcePass; + + s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + + s->legsFlip = ps->legsFlip; + s->torsoFlip = ps->torsoFlip; + + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + s->eFlags2 = ps->eFlags2; + + s->saberInFlight = ps->saberInFlight; + s->saberEntityNum = ps->saberEntityNum; + s->saberMove = ps->saberMove; + s->forcePowersActive = ps->fd.forcePowersActive; + + if (ps->duelInProgress) + { + s->bolt1 = 1; + } + else + { + s->bolt1 = 0; + } + + s->otherEntityNum2 = ps->emplacedIndex; + + s->saberHolstered = ps->saberHolstered; + + if (ps->genericEnemyIndex != -1) + { + s->eFlags |= EF_SEEKERDRONE; + } + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->eFlags |= EF_DEAD; + } else { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; + + //NOT INCLUDED IN ENTITYSTATETOPLAYERSTATE: + s->modelindex2 = ps->weaponstate; + s->constantLight = ps->weaponChargeTime; + + VectorCopy(ps->lastHitLoc, s->origin2); + +// s->isJediMaster = ps->isJediMaster; + +// s->time2 = ps->holocronBits; + s->time2 = 0; // ??? + + s->fireflag = ps->fd.saberAnimLevel; + + s->heldByClient = ps->heldByClient; + s->ragAttach = ps->ragAttach; + + s->iModelScale = ps->iModelScale; + + s->brokenLimbs = ps->brokenLimbs; + + s->hasLookTarget = ps->hasLookTarget; + s->lookTarget = ps->lookTarget; + + s->customRGBA[0] = ps->customRGBA[0]; + s->customRGBA[1] = ps->customRGBA[1]; + s->customRGBA[2] = ps->customRGBA[2]; + s->customRGBA[3] = ps->customRGBA[3]; + + s->m_iVehicleNum = ps->m_iVehicleNum; +} + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +//perform the appropriate model precache routine +#ifdef QAGAME //game +extern int trap_G2API_InitGhoul2Model(void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, + qhandle_t customShader, int modelFlags, int lodBias); //exists on game/cgame/ui, only used on game +extern void trap_G2API_CleanGhoul2Models(void **ghoul2Ptr); //exists on game/cgame/ui, only used on game +#else //cgame/ui +extern qhandle_t trap_R_RegisterModel( const char *name ); //exists on cgame/ui +#endif +//game/cgame/ui +extern qhandle_t trap_R_RegisterSkin( const char *name ); //exists on game/cgame/ui + +int BG_ModelCache(const char *modelName, const char *skinName) +{ +#ifdef QAGAME + void *g2 = NULL; + + if (skinName && skinName[0]) + { + trap_R_RegisterSkin(skinName); + } + + //I could hook up a precache ghoul2 function, but oh well, this works + trap_G2API_InitGhoul2Model(&g2, modelName, 0, 0, 0, 0, 0); + if (g2) + { //now get rid of it + trap_G2API_CleanGhoul2Models(&g2); + } + return 0; +#else + if (skinName && skinName[0]) + { + trap_R_RegisterSkin(skinName); + } + return trap_R_RegisterModel(modelName); +#endif +} + +#ifdef _XBOX // Hacky BG_Alloc replacement + +// This file claims to be stateless. Yeah, right. Regardless, I'm not setting +// aside 5.5 MB of static buffers for this crap. Let's use Z_Malloc. Of course, +// we still need to deal with the fact that any code using BG_Malloc is almost +// certainly leaking memory like a sieve. + +// Dave addendum - TAG_BG_ALLOC is entirely freed when the level starts. +void *BG_Alloc ( int size ) +{ + return Z_Malloc(size, TAG_BG_ALLOC, qfalse, 4); +} + +void *BG_AllocUnaligned ( int size ) +{ + // Ignore the unaligned hint, this function isn't called anyway + return Z_Malloc(size, TAG_BG_ALLOC, qfalse, 4); +} + +// Because the interface to BG_TempAlloc/BG_TempFree is brain-dead, we need +// to remember our last few temporary allocations performed. +#define MAX_TEMP_ALLOCS 3 +static void *tempAllocPointers[MAX_TEMP_ALLOCS] = { 0 }; +static int tempAllocSizes[MAX_TEMP_ALLOCS] = { 0 }; + +void *BG_TempAlloc( int size ) +{ + int i; + + // Do we have a free spot? + for (i = 0; i < MAX_TEMP_ALLOCS; ++i) + if (!tempAllocPointers[i]) + break; + + if (i == MAX_TEMP_ALLOCS) + { + assert(!"No space for TempAlloc -> Increase MAX_TEMP_ALLOCS"); + return NULL; + } + + tempAllocPointers[i] = Z_Malloc(size, TAG_TEMP_WORKSPACE, qfalse, 4); + tempAllocSizes[i] = size; + + return tempAllocPointers[i]; +} + +void BG_TempFree( int size ) +{ + int i; + + // Find the allocation + for (i = MAX_TEMP_ALLOCS - 1; i >= 0; --i) + if (tempAllocPointers[i] && (tempAllocSizes[i] == size)) + break; + + if (i < 0) + { + assert(!"BG_TempFree doesn't match a call to BG_TempAlloc"); + return; + } + + Z_Free(tempAllocPointers[i]); + tempAllocPointers[i] = 0; + tempAllocSizes[i] = 0; + + return; +} + +char *BG_StringAlloc ( const char *source ) +{ + char *dest; + + dest = (char *) BG_Alloc ( strlen ( source ) + 1 ); + strcpy ( dest, source ); + return dest; +} + +qboolean BG_OutOfMemory ( void ) +{ + // Never called + return qfalse; +} + +#else // _XBOX + +#ifdef QAGAME +#define MAX_POOL_SIZE 3000000 //1024000 +#elif defined CGAME //don't need as much for cgame stuff. 2mb will be fine. +#define MAX_POOL_SIZE 2048000 +#else //And for the ui the only thing we'll be using this for anyway is allocating anim data for g2 menu models +#define MAX_POOL_SIZE 512000 +#endif + +//I am using this for all the stuff like NPC client structures on server/client and +//non-humanoid animations as well until/if I can get dynamic memory working properly +//with casted datatypes, which is why it is so large. + + +static char bg_pool[MAX_POOL_SIZE]; +static int bg_poolSize = 0; +static int bg_poolTail = MAX_POOL_SIZE; + +void *BG_Alloc ( int size ) +{ + bg_poolSize = ((bg_poolSize + 0x00000003) & 0xfffffffc); + + if (bg_poolSize + size > bg_poolTail) + { + Com_Error( ERR_DROP, "BG_Alloc: buffer exceeded tail (%d > %d)", bg_poolSize + size, bg_poolTail); + return 0; + } + + bg_poolSize += size; + + return &bg_pool[bg_poolSize-size]; +} + +void *BG_AllocUnaligned ( int size ) +{ + if (bg_poolSize + size > bg_poolTail) + { + Com_Error( ERR_DROP, "BG_AllocUnaligned: buffer exceeded tail (%d > %d)", bg_poolSize + size, bg_poolTail); + return 0; + } + + bg_poolSize += size; + + return &bg_pool[bg_poolSize-size]; +} + +void *BG_TempAlloc( int size ) +{ + size = ((size + 0x00000003) & 0xfffffffc); + + if (bg_poolTail - size < bg_poolSize) + { + Com_Error( ERR_DROP, "BG_TempAlloc: buffer exceeded head (%d > %d)", bg_poolTail - size, bg_poolSize); + return 0; + } + + bg_poolTail -= size; + + return &bg_pool[bg_poolTail]; +} + +void BG_TempFree( int size ) +{ + size = ((size + 0x00000003) & 0xfffffffc); + + if (bg_poolTail+size > MAX_POOL_SIZE) + { + Com_Error( ERR_DROP, "BG_TempFree: tail greater than size (%d > %d)", bg_poolTail+size, MAX_POOL_SIZE ); + } + + bg_poolTail += size; +} + +char *BG_StringAlloc ( const char *source ) +{ + char *dest; + + dest = BG_Alloc ( strlen ( source ) + 1 ); + strcpy ( dest, source ); + return dest; +} + +qboolean BG_OutOfMemory ( void ) +{ + return bg_poolSize >= MAX_POOL_SIZE; +} + +#endif // _XBOX && QAGAME + +#include "../namespace_end.h" diff --git a/codemp/game/bg_panimate.c b/codemp/game/bg_panimate.c new file mode 100644 index 0000000..4b5c276 --- /dev/null +++ b/codemp/game/bg_panimate.c @@ -0,0 +1,2967 @@ +// BG_PAnimate.c + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_strap.h" +#include "bg_local.h" +#include "anims.h" +#include "../cgame/animtable.h" +#ifdef QAGAME +#include "g_local.h" +#endif + +#ifdef CGAME +#include "../namespace_begin.h" +extern sfxHandle_t trap_S_RegisterSound( const char *sample); +extern int trap_FX_RegisterEffect( const char *file); +#include "../namespace_end.h" +#endif + +/* +============================================================================== +BEGIN: Animation utility functions (sequence checking) +============================================================================== +*/ +//Called regardless of pm validity: + +// VVFIXME - Most of these functions are totally stateless and stupid. Don't +// need multiple copies of this, but it's much easier (and less likely to +// break in the future) if I keep separate namespace versions now. +#include "../namespace_begin.h" + +qboolean BG_SaberStanceAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_STAND1://not really a saberstance anim, actually... "saber off" stance + case BOTH_STAND2://single-saber, medium style + case BOTH_SABERFAST_STANCE://single-saber, fast style + case BOTH_SABERSLOW_STANCE://single-saber, strong style + case BOTH_SABERSTAFF_STANCE://saber staff style + case BOTH_SABERDUAL_STANCE://dual saber style + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_CrouchAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_SIT1: //# Normal chair sit. + case BOTH_SIT2: //# Lotus position. + case BOTH_SIT3: //# Sitting in tired position: elbows on knees + case BOTH_CROUCH1: //# Transition from standing to crouch + case BOTH_CROUCH1IDLE: //# Crouching idle + case BOTH_CROUCH1WALK: //# Walking while crouched + case BOTH_CROUCH1WALKBACK: //# Walking while crouched + case BOTH_CROUCH2TOSTAND1: //# going from crouch2 to stand1 + case BOTH_CROUCH3: //# Desann crouching down to Kyle (cin 9) + case BOTH_KNEES1: //# Tavion on her knees + case BOTH_CROUCHATTACKBACK1://FIXME: not if in middle of anim? + case BOTH_ROLL_STAB: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InSpecialJump( int anim ) +{ + switch ( (anim) ) + { + case BOTH_WALL_RUN_RIGHT: + case BOTH_WALL_RUN_RIGHT_STOP: + case BOTH_WALL_RUN_RIGHT_FLIP: + case BOTH_WALL_RUN_LEFT: + case BOTH_WALL_RUN_LEFT_STOP: + case BOTH_WALL_RUN_LEFT_FLIP: + case BOTH_WALL_FLIP_RIGHT: + case BOTH_WALL_FLIP_LEFT: + case BOTH_FLIP_BACK1: + case BOTH_FLIP_BACK2: + case BOTH_FLIP_BACK3: + case BOTH_WALL_FLIP_BACK1: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_FORCELEAP2_T__B_: + case BOTH_JUMPFLIPSLASHDOWN1://# + case BOTH_JUMPFLIPSTABDOWN://# + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_ARIAL_F1: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + + case BOTH_FORCELONGLEAP_START: + case BOTH_FORCELONGLEAP_ATTACK: + case BOTH_FORCEWALLRUNFLIP_START: + case BOTH_FORCEWALLRUNFLIP_END: + case BOTH_FORCEWALLRUNFLIP_ALT: + case BOTH_FLIP_ATTACK7: + case BOTH_FLIP_HOLD7: + case BOTH_FLIP_LAND: + case BOTH_A7_SOULCAL: + return qtrue; + } + if ( BG_InReboundJump( anim ) ) + { + return qtrue; + } + if ( BG_InReboundHold( anim ) ) + { + return qtrue; + } + if ( BG_InReboundRelease( anim ) ) + { + return qtrue; + } + if ( BG_InBackFlip( anim ) ) + { + return qtrue; + } + return qfalse; +} + +qboolean BG_InSaberStandAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_SABERFAST_STANCE: + case BOTH_STAND2: + case BOTH_SABERSLOW_STANCE: + case BOTH_SABERDUAL_STANCE: + case BOTH_SABERSTAFF_STANCE: + return qtrue; + default: + return qfalse; + } +} + +qboolean BG_InReboundJump( int anim ) +{ + switch ( anim ) + { + case BOTH_FORCEWALLREBOUND_FORWARD: + case BOTH_FORCEWALLREBOUND_LEFT: + case BOTH_FORCEWALLREBOUND_BACK: + case BOTH_FORCEWALLREBOUND_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InReboundHold( int anim ) +{ + switch ( anim ) + { + case BOTH_FORCEWALLHOLD_FORWARD: + case BOTH_FORCEWALLHOLD_LEFT: + case BOTH_FORCEWALLHOLD_BACK: + case BOTH_FORCEWALLHOLD_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InReboundRelease( int anim ) +{ + switch ( anim ) + { + case BOTH_FORCEWALLRELEASE_FORWARD: + case BOTH_FORCEWALLRELEASE_LEFT: + case BOTH_FORCEWALLRELEASE_BACK: + case BOTH_FORCEWALLRELEASE_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InBackFlip( int anim ) +{ + switch ( anim ) + { + case BOTH_FLIP_BACK1: + case BOTH_FLIP_BACK2: + case BOTH_FLIP_BACK3: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_DirectFlippingAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_FLIP_F: //# Flip forward + case BOTH_FLIP_B: //# Flip backwards + case BOTH_FLIP_L: //# Flip left + case BOTH_FLIP_R: //# Flip right + return qtrue; + break; + } + + return qfalse; +} + +qboolean BG_SaberInAttack( int move ) +{ + if ( move >= LS_A_TL2BR && move <= LS_A_T2B ) + { + return qtrue; + } + switch ( move ) + { + case LS_A_BACK: + case LS_A_BACK_CR: + case LS_A_BACKSTAB: + case LS_ROLL_STAB: + case LS_A_LUNGE: + case LS_A_JUMP_T__B_: + case LS_A_FLIP_STAB: + case LS_A_FLIP_SLASH: + case LS_JUMPATTACK_DUAL: + case LS_JUMPATTACK_ARIAL_LEFT: + case LS_JUMPATTACK_ARIAL_RIGHT: + case LS_JUMPATTACK_CART_LEFT: + case LS_JUMPATTACK_CART_RIGHT: + case LS_JUMPATTACK_STAFF_LEFT: + case LS_JUMPATTACK_STAFF_RIGHT: + case LS_BUTTERFLY_LEFT: + case LS_BUTTERFLY_RIGHT: + case LS_A_BACKFLIP_ATK: + case LS_SPINATTACK_DUAL: + case LS_SPINATTACK: + case LS_LEAP_ATTACK: + case LS_SWOOP_ATTACK_RIGHT: + case LS_SWOOP_ATTACK_LEFT: + case LS_TAUNTAUN_ATTACK_RIGHT: + case LS_TAUNTAUN_ATTACK_LEFT: + case LS_KICK_F: + case LS_KICK_B: + case LS_KICK_R: + case LS_KICK_L: + case LS_KICK_S: + case LS_KICK_BF: + case LS_KICK_RL: + case LS_KICK_F_AIR: + case LS_KICK_B_AIR: + case LS_KICK_R_AIR: + case LS_KICK_L_AIR: + case LS_STABDOWN: + case LS_STABDOWN_STAFF: + case LS_STABDOWN_DUAL: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_UPSIDE_DOWN_ATTACK: + case LS_PULL_ATTACK_STAB: + case LS_PULL_ATTACK_SWING: + case LS_SPINATTACK_ALORA: + case LS_DUAL_FB: + case LS_DUAL_LR: + case LS_HILT_BASH: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_SaberInKata( int saberMove ) +{ + switch ( saberMove ) + { + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + return qtrue; + } + return qfalse; +} + +qboolean BG_InKataAnim(int anim) +{ + switch (anim) + { + case BOTH_A6_SABERPROTECT: + case BOTH_A7_SOULCAL: + case BOTH_A1_SPECIAL: + case BOTH_A2_SPECIAL: + case BOTH_A3_SPECIAL: + return qtrue; + } + return qfalse; +} + +qboolean BG_SaberInSpecial( int move ) +{ + switch( move ) + { + case LS_A_BACK: + case LS_A_BACK_CR: + case LS_A_BACKSTAB: + case LS_ROLL_STAB: + case LS_A_LUNGE: + case LS_A_JUMP_T__B_: + case LS_A_FLIP_STAB: + case LS_A_FLIP_SLASH: + case LS_JUMPATTACK_DUAL: + case LS_JUMPATTACK_ARIAL_LEFT: + case LS_JUMPATTACK_ARIAL_RIGHT: + case LS_JUMPATTACK_CART_LEFT: + case LS_JUMPATTACK_CART_RIGHT: + case LS_JUMPATTACK_STAFF_LEFT: + case LS_JUMPATTACK_STAFF_RIGHT: + case LS_BUTTERFLY_LEFT: + case LS_BUTTERFLY_RIGHT: + case LS_A_BACKFLIP_ATK: + case LS_SPINATTACK_DUAL: + case LS_SPINATTACK: + case LS_LEAP_ATTACK: + case LS_SWOOP_ATTACK_RIGHT: + case LS_SWOOP_ATTACK_LEFT: + case LS_TAUNTAUN_ATTACK_RIGHT: + case LS_TAUNTAUN_ATTACK_LEFT: + case LS_KICK_F: + case LS_KICK_B: + case LS_KICK_R: + case LS_KICK_L: + case LS_KICK_S: + case LS_KICK_BF: + case LS_KICK_RL: + case LS_KICK_F_AIR: + case LS_KICK_B_AIR: + case LS_KICK_R_AIR: + case LS_KICK_L_AIR: + case LS_STABDOWN: + case LS_STABDOWN_STAFF: + case LS_STABDOWN_DUAL: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_UPSIDE_DOWN_ATTACK: + case LS_PULL_ATTACK_STAB: + case LS_PULL_ATTACK_SWING: + case LS_SPINATTACK_ALORA: + case LS_DUAL_FB: + case LS_DUAL_LR: + case LS_HILT_BASH: + return qtrue; + } + return qfalse; +} + +qboolean BG_KickMove( int move ) +{ + switch( move ) + { + case LS_KICK_F: + case LS_KICK_B: + case LS_KICK_R: + case LS_KICK_L: + case LS_KICK_S: + case LS_KICK_BF: + case LS_KICK_RL: + case LS_KICK_F_AIR: + case LS_KICK_B_AIR: + case LS_KICK_R_AIR: + case LS_KICK_L_AIR: + case LS_HILT_BASH: + return qtrue; + } + return qfalse; +} + +qboolean BG_SaberInIdle( int move ) +{ + switch ( move ) + { + case LS_NONE: + case LS_READY: + case LS_DRAW: + case LS_PUTAWAY: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InExtraDefenseSaberMove( int move ) +{ + switch ( move ) + { + case LS_SPINATTACK_DUAL: + case LS_SPINATTACK: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_JUMPATTACK_DUAL: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_FlippingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_FLIP_F: //# Flip forward + case BOTH_FLIP_B: //# Flip backwards + case BOTH_FLIP_L: //# Flip left + case BOTH_FLIP_R: //# Flip right + case BOTH_WALL_RUN_RIGHT_FLIP: + case BOTH_WALL_RUN_LEFT_FLIP: + case BOTH_WALL_FLIP_RIGHT: + case BOTH_WALL_FLIP_LEFT: + case BOTH_FLIP_BACK1: + case BOTH_FLIP_BACK2: + case BOTH_FLIP_BACK3: + case BOTH_WALL_FLIP_BACK1: + //Not really flips, but... + case BOTH_WALL_RUN_RIGHT: + case BOTH_WALL_RUN_LEFT: + case BOTH_WALL_RUN_RIGHT_STOP: + case BOTH_WALL_RUN_LEFT_STOP: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + // + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_ARIAL_F1: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + case BOTH_JUMPFLIPSLASHDOWN1: + case BOTH_JUMPFLIPSTABDOWN: + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + //JKA + case BOTH_FORCEWALLRUNFLIP_END: + case BOTH_FORCEWALLRUNFLIP_ALT: + case BOTH_FLIP_ATTACK7: + case BOTH_A7_SOULCAL: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_SpinningSaberAnim( int anim ) +{ + switch ( anim ) + { + //level 1 - FIXME: level 1 will have *no* spins + case BOTH_T1_BR_BL: + case BOTH_T1__R__L: + case BOTH_T1__R_BL: + case BOTH_T1_TR_BL: + case BOTH_T1_BR_TL: + case BOTH_T1_BR__L: + case BOTH_T1_TL_BR: + case BOTH_T1__L_BR: + case BOTH_T1__L__R: + case BOTH_T1_BL_BR: + case BOTH_T1_BL__R: + case BOTH_T1_BL_TR: + //level 2 + case BOTH_T2_BR__L: + case BOTH_T2_BR_BL: + case BOTH_T2__R_BL: + case BOTH_T2__L_BR: + case BOTH_T2_BL_BR: + case BOTH_T2_BL__R: + //level 3 + case BOTH_T3_BR__L: + case BOTH_T3_BR_BL: + case BOTH_T3__R_BL: + case BOTH_T3__L_BR: + case BOTH_T3_BL_BR: + case BOTH_T3_BL__R: + //level 4 + case BOTH_T4_BR__L: + case BOTH_T4_BR_BL: + case BOTH_T4__R_BL: + case BOTH_T4__L_BR: + case BOTH_T4_BL_BR: + case BOTH_T4_BL__R: + //level 5 + case BOTH_T5_BR_BL: + case BOTH_T5__R__L: + case BOTH_T5__R_BL: + case BOTH_T5_TR_BL: + case BOTH_T5_BR_TL: + case BOTH_T5_BR__L: + case BOTH_T5_TL_BR: + case BOTH_T5__L_BR: + case BOTH_T5__L__R: + case BOTH_T5_BL_BR: + case BOTH_T5_BL__R: + case BOTH_T5_BL_TR: + //level 6 + case BOTH_T6_BR_TL: + case BOTH_T6__R_TL: + case BOTH_T6__R__L: + case BOTH_T6__R_BL: + case BOTH_T6_TR_TL: + case BOTH_T6_TR__L: + case BOTH_T6_TR_BL: + case BOTH_T6_T__TL: + case BOTH_T6_T__BL: + case BOTH_T6_TL_BR: + case BOTH_T6__L_BR: + case BOTH_T6__L__R: + case BOTH_T6_TL__R: + case BOTH_T6_TL_TR: + case BOTH_T6__L_TR: + case BOTH_T6__L_T_: + case BOTH_T6_BL_T_: + case BOTH_T6_BR__L: + case BOTH_T6_BR_BL: + case BOTH_T6_BL_BR: + case BOTH_T6_BL__R: + case BOTH_T6_BL_TR: + //level 7 + case BOTH_T7_BR_TL: + case BOTH_T7_BR__L: + case BOTH_T7_BR_BL: + case BOTH_T7__R__L: + case BOTH_T7__R_BL: + case BOTH_T7_TR__L: + case BOTH_T7_T___R: + case BOTH_T7_TL_BR: + case BOTH_T7__L_BR: + case BOTH_T7__L__R: + case BOTH_T7_BL_BR: + case BOTH_T7_BL__R: + case BOTH_T7_BL_TR: + case BOTH_T7_TL_TR: + case BOTH_T7_T__BR: + case BOTH_T7__L_TR: + case BOTH_V7_BL_S7: + //special + //case BOTH_A2_STABBACK1: + case BOTH_ATTACK_BACK: + case BOTH_CROUCHATTACKBACK1: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_JUMPFLIPSLASHDOWN1: + case BOTH_JUMPFLIPSTABDOWN: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_SaberInSpecialAttack( int anim ) +{ + switch ( anim ) + { + case BOTH_A2_STABBACK1: + case BOTH_ATTACK_BACK: + case BOTH_CROUCHATTACKBACK1: + case BOTH_ROLL_STAB: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_LUNGE2_B__T_: + case BOTH_FORCELEAP2_T__B_: + case BOTH_JUMPFLIPSLASHDOWN1://# + case BOTH_JUMPFLIPSTABDOWN://# + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + case BOTH_SPINATTACK6: + case BOTH_SPINATTACK7: + case BOTH_FORCELONGLEAP_ATTACK: + case BOTH_VS_ATR_S: + case BOTH_VS_ATL_S: + case BOTH_VT_ATR_S: + case BOTH_VT_ATL_S: + case BOTH_A7_KICK_F: + case BOTH_A7_KICK_B: + case BOTH_A7_KICK_R: + case BOTH_A7_KICK_L: + case BOTH_A7_KICK_S: + case BOTH_A7_KICK_BF: + case BOTH_A7_KICK_RL: + case BOTH_A7_KICK_F_AIR: + case BOTH_A7_KICK_B_AIR: + case BOTH_A7_KICK_R_AIR: + case BOTH_A7_KICK_L_AIR: + case BOTH_STABDOWN: + case BOTH_STABDOWN_STAFF: + case BOTH_STABDOWN_DUAL: + case BOTH_A6_SABERPROTECT: + case BOTH_A7_SOULCAL: + case BOTH_A1_SPECIAL: + case BOTH_A2_SPECIAL: + case BOTH_A3_SPECIAL: + case BOTH_FLIP_ATTACK7: + case BOTH_PULL_IMPALE_STAB: + case BOTH_PULL_IMPALE_SWING: + case BOTH_ALORA_SPIN_SLASH: + case BOTH_A6_FB: + case BOTH_A6_LR: + case BOTH_A7_HILT: + return qtrue; + } + return qfalse; +} + +qboolean BG_KickingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_A7_KICK_F: + case BOTH_A7_KICK_B: + case BOTH_A7_KICK_R: + case BOTH_A7_KICK_L: + case BOTH_A7_KICK_S: + case BOTH_A7_KICK_BF: + case BOTH_A7_KICK_RL: + case BOTH_A7_KICK_F_AIR: + case BOTH_A7_KICK_B_AIR: + case BOTH_A7_KICK_R_AIR: + case BOTH_A7_KICK_L_AIR: + case BOTH_A7_HILT: + //NOT kicks, but do kick traces anyway + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + return qtrue; + break; + } + return qfalse; +} + +int BG_InGrappleMove(int anim) +{ + switch (anim) + { + case BOTH_KYLE_GRAB: + case BOTH_KYLE_MISS: + return 1; //grabbing at someone + case BOTH_KYLE_PA_1: + case BOTH_KYLE_PA_2: + return 2; //beating the shit out of someone + case BOTH_PLAYER_PA_1: + case BOTH_PLAYER_PA_2: + case BOTH_PLAYER_PA_FLY: + return 3; //getting the shit beaten out of you + break; + } + + return 0; +} + +int BG_BrokenParryForAttack( int move ) +{ + //Our attack was knocked away by a knockaway parry + //FIXME: need actual anims for this + //FIXME: need to know which side of the saber was hit! For now, we presume the saber gets knocked away from the center + switch ( saberMoveData[move].startQuad ) + { + case Q_B: + return LS_V1_B_; + break; + case Q_BR: + return LS_V1_BR; + break; + case Q_R: + return LS_V1__R; + break; + case Q_TR: + return LS_V1_TR; + break; + case Q_T: + return LS_V1_T_; + break; + case Q_TL: + return LS_V1_TL; + break; + case Q_L: + return LS_V1__L; + break; + case Q_BL: + return LS_V1_BL; + break; + } + return LS_NONE; +} + +int BG_BrokenParryForParry( int move ) +{ + //FIXME: need actual anims for this + //FIXME: need to know which side of the saber was hit! For now, we presume the saber gets knocked away from the center + switch ( move ) + { + case LS_PARRY_UP: + //Hmm... since we don't know what dir the hit came from, randomly pick knock down or knock back + if ( Q_irand( 0, 1 ) ) + { + return LS_H1_B_; + } + else + { + return LS_H1_T_; + } + break; + case LS_PARRY_UR: + return LS_H1_TR; + break; + case LS_PARRY_UL: + return LS_H1_TL; + break; + case LS_PARRY_LR: + return LS_H1_BR; + break; + case LS_PARRY_LL: + return LS_H1_BL; + break; + case LS_READY: + return LS_H1_B_;//??? + break; + } + return LS_NONE; +} + +int BG_KnockawayForParry( int move ) +{ + //FIXME: need actual anims for this + //FIXME: need to know which side of the saber was hit! For now, we presume the saber gets knocked away from the center + switch ( move ) + { + case BLOCKED_TOP://LS_PARRY_UP: + return LS_K1_T_;//push up + break; + case BLOCKED_UPPER_RIGHT://LS_PARRY_UR: + default://case LS_READY: + return LS_K1_TR;//push up, slightly to right + break; + case BLOCKED_UPPER_LEFT://LS_PARRY_UL: + return LS_K1_TL;//push up and to left + break; + case BLOCKED_LOWER_RIGHT://LS_PARRY_LR: + return LS_K1_BR;//push down and to left + break; + case BLOCKED_LOWER_LEFT://LS_PARRY_LL: + return LS_K1_BL;//push down and to right + break; + } + //return LS_NONE; +} + +qboolean BG_InRoll( playerState_t *ps, int anim ) +{ + switch ( (anim) ) + { + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + if ( ps->legsTimer > 0 ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean BG_InSpecialDeathAnim( int anim ) +{ + switch( anim ) + { + case BOTH_DEATH_ROLL: //# Death anim from a roll + case BOTH_DEATH_FLIP: //# Death anim from a flip + case BOTH_DEATH_SPIN_90_R: //# Death anim when facing 90 degrees right + case BOTH_DEATH_SPIN_90_L: //# Death anim when facing 90 degrees left + case BOTH_DEATH_SPIN_180: //# Death anim when facing backwards + case BOTH_DEATH_LYING_UP: //# Death anim when lying on back + case BOTH_DEATH_LYING_DN: //# Death anim when lying on front + case BOTH_DEATH_FALLING_DN: //# Death anim when falling on face + case BOTH_DEATH_FALLING_UP: //# Death anim when falling on back + case BOTH_DEATH_CROUCHED: //# Death anim when crouched + return qtrue; + break; + default: + return qfalse; + break; + } +} + +qboolean BG_InDeathAnim ( int anim ) +{//Purposely does not cover stumbledeath and falldeath... + switch( anim ) + { + case BOTH_DEATH1: //# First Death anim + case BOTH_DEATH2: //# Second Death anim + case BOTH_DEATH3: //# Third Death anim + case BOTH_DEATH4: //# Fourth Death anim + case BOTH_DEATH5: //# Fifth Death anim + case BOTH_DEATH6: //# Sixth Death anim + case BOTH_DEATH7: //# Seventh Death anim + case BOTH_DEATH8: //# + case BOTH_DEATH9: //# + case BOTH_DEATH10: //# + case BOTH_DEATH11: //# + case BOTH_DEATH12: //# + case BOTH_DEATH13: //# + case BOTH_DEATH14: //# + case BOTH_DEATH14_UNGRIP: //# Desann's end death (cin #35) + case BOTH_DEATH14_SITUP: //# Tavion sitting up after having been thrown (cin #23) + case BOTH_DEATH15: //# + case BOTH_DEATH16: //# + case BOTH_DEATH17: //# + case BOTH_DEATH18: //# + case BOTH_DEATH19: //# + case BOTH_DEATH20: //# + case BOTH_DEATH21: //# + case BOTH_DEATH22: //# + case BOTH_DEATH23: //# + case BOTH_DEATH24: //# + case BOTH_DEATH25: //# + + case BOTH_DEATHFORWARD1: //# First Death in which they get thrown forward + case BOTH_DEATHFORWARD2: //# Second Death in which they get thrown forward + case BOTH_DEATHFORWARD3: //# Tavion's falling in cin# 23 + case BOTH_DEATHBACKWARD1: //# First Death in which they get thrown backward + case BOTH_DEATHBACKWARD2: //# Second Death in which they get thrown backward + + case BOTH_DEATH1IDLE: //# Idle while close to death + case BOTH_LYINGDEATH1: //# Death to play when killed lying down + case BOTH_STUMBLEDEATH1: //# Stumble forward and fall face first death + case BOTH_FALLDEATH1: //# Fall forward off a high cliff and splat death - start + case BOTH_FALLDEATH1INAIR: //# Fall forward off a high cliff and splat death - loop + case BOTH_FALLDEATH1LAND: //# Fall forward off a high cliff and splat death - hit bottom + //# #sep case BOTH_ DEAD POSES # Should be last frame of corresponding previous anims + case BOTH_DEAD1: //# First Death finished pose + case BOTH_DEAD2: //# Second Death finished pose + case BOTH_DEAD3: //# Third Death finished pose + case BOTH_DEAD4: //# Fourth Death finished pose + case BOTH_DEAD5: //# Fifth Death finished pose + case BOTH_DEAD6: //# Sixth Death finished pose + case BOTH_DEAD7: //# Seventh Death finished pose + case BOTH_DEAD8: //# + case BOTH_DEAD9: //# + case BOTH_DEAD10: //# + case BOTH_DEAD11: //# + case BOTH_DEAD12: //# + case BOTH_DEAD13: //# + case BOTH_DEAD14: //# + case BOTH_DEAD15: //# + case BOTH_DEAD16: //# + case BOTH_DEAD17: //# + case BOTH_DEAD18: //# + case BOTH_DEAD19: //# + case BOTH_DEAD20: //# + case BOTH_DEAD21: //# + case BOTH_DEAD22: //# + case BOTH_DEAD23: //# + case BOTH_DEAD24: //# + case BOTH_DEAD25: //# + case BOTH_DEADFORWARD1: //# First thrown forward death finished pose + case BOTH_DEADFORWARD2: //# Second thrown forward death finished pose + case BOTH_DEADBACKWARD1: //# First thrown backward death finished pose + case BOTH_DEADBACKWARD2: //# Second thrown backward death finished pose + case BOTH_LYINGDEAD1: //# Killed lying down death finished pose + case BOTH_STUMBLEDEAD1: //# Stumble forward death finished pose + case BOTH_FALLDEAD1LAND: //# Fall forward and splat death finished pose + //# #sep case BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + case BOTH_DEADFLOP1: //# React to being shot from First Death finished pose + case BOTH_DEADFLOP2: //# React to being shot from Second Death finished pose + case BOTH_DISMEMBER_HEAD1: //# + case BOTH_DISMEMBER_TORSO1: //# + case BOTH_DISMEMBER_LLEG: //# + case BOTH_DISMEMBER_RLEG: //# + case BOTH_DISMEMBER_RARM: //# + case BOTH_DISMEMBER_LARM: //# + return qtrue; + break; + default: + return BG_InSpecialDeathAnim( anim ); + break; + } +} + +qboolean BG_InKnockDownOnly( int anim ) +{ + switch ( anim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + return qtrue; + } + return qfalse; +} + +qboolean BG_InSaberLockOld( int anim ) +{ + switch ( anim ) + { + case BOTH_BF2LOCK: + case BOTH_BF1LOCK: + case BOTH_CWCIRCLELOCK: + case BOTH_CCWCIRCLELOCK: + return qtrue; + } + return qfalse; +} + +qboolean BG_InSaberLock( int anim ) +{ + switch ( anim ) + { + case BOTH_LK_S_DL_S_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_DL_T_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_ST_S_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_S_ST_T_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_S_S_S_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_S_S_T_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_DL_DL_S_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_DL_T_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_ST_S_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_DL_ST_T_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_DL_S_S_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_DL_S_T_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_ST_DL_S_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_DL_T_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_ST_S_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_ST_T_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_S_S_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_ST_S_T_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_S_S_S_L_2: + case BOTH_LK_S_S_T_L_2: + case BOTH_LK_DL_DL_S_L_2: + case BOTH_LK_DL_DL_T_L_2: + case BOTH_LK_ST_ST_S_L_2: + case BOTH_LK_ST_ST_T_L_2: + return qtrue; + break; + default: + return BG_InSaberLockOld( anim ); + break; + } + //return qfalse; +} + +//Called only where pm is valid (not all require pm, but some do): +qboolean PM_InCartwheel( int anim ) +{ + switch ( anim ) + { + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_ARIAL_F1: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InKnockDownOnGround( playerState_t *ps ) +{ + switch ( ps->legsAnim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + case BOTH_RELEASED: + //if ( PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)ps->legsAnim ) - ps->legsAnimTimer > 300 ) + {//at end of fall down anim + return qtrue; + } + break; + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_GETUP_CROUCH_F1: + case BOTH_GETUP_CROUCH_B1: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_FORCE_GETUP_B6: + if ( BG_AnimLength( 0, (animNumber_t)ps->legsAnim ) - ps->legsTimer < 500 ) + {//at beginning of getup anim + return qtrue; + } + break; + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + if ( BG_AnimLength( 0, (animNumber_t)ps->legsAnim ) - ps->legsTimer < 500 ) + {//at beginning of getup anim + return qtrue; + } + break; + case BOTH_LK_DL_ST_T_SB_1_L: + if ( ps->legsTimer < 1000 ) + { + return qtrue; + } + break; + case BOTH_PLAYER_PA_3_FLY: + if ( ps->legsTimer < 300 ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean BG_StabDownAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_STABDOWN: + case BOTH_STABDOWN_STAFF: + case BOTH_STABDOWN_DUAL: + return qtrue; + } + return qfalse; +} + +int PM_SaberBounceForAttack( int move ) +{ + switch ( saberMoveData[move].startQuad ) + { + case Q_B: + case Q_BR: + return LS_B1_BR; + break; + case Q_R: + return LS_B1__R; + break; + case Q_TR: + return LS_B1_TR; + break; + case Q_T: + return LS_B1_T_; + break; + case Q_TL: + return LS_B1_TL; + break; + case Q_L: + return LS_B1__L; + break; + case Q_BL: + return LS_B1_BL; + break; + } + return LS_NONE; +} + +int PM_SaberDeflectionForQuad( int quad ) +{ + switch ( quad ) + { + case Q_B: + return LS_D1_B_; + break; + case Q_BR: + return LS_D1_BR; + break; + case Q_R: + return LS_D1__R; + break; + case Q_TR: + return LS_D1_TR; + break; + case Q_T: + return LS_D1_T_; + break; + case Q_TL: + return LS_D1_TL; + break; + case Q_L: + return LS_D1__L; + break; + case Q_BL: + return LS_D1_BL; + break; + } + return LS_NONE; +} + +qboolean PM_SaberInDeflect( int move ) +{ + if ( move >= LS_D1_BR && move <= LS_D1_B_ ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInParry( int move ) +{ + if ( move >= LS_PARRY_UP && move <= LS_PARRY_LL ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInKnockaway( int move ) +{ + if ( move >= LS_K1_T_ && move <= LS_K1_BL ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInReflect( int move ) +{ + if ( move >= LS_REFLECT_UP && move <= LS_REFLECT_LL ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInStart( int move ) +{ + if ( move >= LS_S_TL2BR && move <= LS_S_T2B ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInReturn( int move ) +{ + if ( move >= LS_R_TL2BR && move <= LS_R_T2B ) + { + return qtrue; + } + return qfalse; +} + +qboolean BG_SaberInReturn( int move ) +{ + return PM_SaberInReturn( move ); +} + +qboolean PM_InSaberAnim( int anim ) +{ + if ( (anim) >= BOTH_A1_T__B_ && (anim) <= BOTH_H1_S1_BR ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_InKnockDown( playerState_t *ps ) +{ + switch ( (ps->legsAnim) ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + return qtrue; + break; + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + if ( ps->legsTimer ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean PM_PainAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_PAIN1: //# First take pain anim + case BOTH_PAIN2: //# Second take pain anim + case BOTH_PAIN3: //# Third take pain anim + case BOTH_PAIN4: //# Fourth take pain anim + case BOTH_PAIN5: //# Fifth take pain anim - from behind + case BOTH_PAIN6: //# Sixth take pain anim - from behind + case BOTH_PAIN7: //# Seventh take pain anim - from behind + case BOTH_PAIN8: //# Eigth take pain anim - from behind + case BOTH_PAIN9: //# + case BOTH_PAIN10: //# + case BOTH_PAIN11: //# + case BOTH_PAIN12: //# + case BOTH_PAIN13: //# + case BOTH_PAIN14: //# + case BOTH_PAIN15: //# + case BOTH_PAIN16: //# + case BOTH_PAIN17: //# + case BOTH_PAIN18: //# + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_JumpingAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_JUMP1: //# Jump - wind-up and leave ground + case BOTH_INAIR1: //# In air loop (from jump) + case BOTH_LAND1: //# Landing (from in air loop) + case BOTH_LAND2: //# Landing Hard (from a great height) + case BOTH_JUMPBACK1: //# Jump backwards - wind-up and leave ground + case BOTH_INAIRBACK1: //# In air loop (from jump back) + case BOTH_LANDBACK1: //# Landing backwards(from in air loop) + case BOTH_JUMPLEFT1: //# Jump left - wind-up and leave ground + case BOTH_INAIRLEFT1: //# In air loop (from jump left) + case BOTH_LANDLEFT1: //# Landing left(from in air loop) + case BOTH_JUMPRIGHT1: //# Jump right - wind-up and leave ground + case BOTH_INAIRRIGHT1: //# In air loop (from jump right) + case BOTH_LANDRIGHT1: //# Landing right(from in air loop) + case BOTH_FORCEJUMP1: //# Jump - wind-up and leave ground + case BOTH_FORCEINAIR1: //# In air loop (from jump) + case BOTH_FORCELAND1: //# Landing (from in air loop) + case BOTH_FORCEJUMPBACK1: //# Jump backwards - wind-up and leave ground + case BOTH_FORCEINAIRBACK1: //# In air loop (from jump back) + case BOTH_FORCELANDBACK1: //# Landing backwards(from in air loop) + case BOTH_FORCEJUMPLEFT1: //# Jump left - wind-up and leave ground + case BOTH_FORCEINAIRLEFT1: //# In air loop (from jump left) + case BOTH_FORCELANDLEFT1: //# Landing left(from in air loop) + case BOTH_FORCEJUMPRIGHT1: //# Jump right - wind-up and leave ground + case BOTH_FORCEINAIRRIGHT1: //# In air loop (from jump right) + case BOTH_FORCELANDRIGHT1: //# Landing right(from in air loop) + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_LandingAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_LAND1: //# Landing (from in air loop) + case BOTH_LAND2: //# Landing Hard (from a great height) + case BOTH_LANDBACK1: //# Landing backwards(from in air loop) + case BOTH_LANDLEFT1: //# Landing left(from in air loop) + case BOTH_LANDRIGHT1: //# Landing right(from in air loop) + case BOTH_FORCELAND1: //# Landing (from in air loop) + case BOTH_FORCELANDBACK1: //# Landing backwards(from in air loop) + case BOTH_FORCELANDLEFT1: //# Landing left(from in air loop) + case BOTH_FORCELANDRIGHT1: //# Landing right(from in air loop) + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SpinningAnim( int anim ) +{ + /* + switch ( anim ) + { + //FIXME: list any other spinning anims + default: + break; + } + */ + return BG_SpinningSaberAnim( anim ); +} + +qboolean PM_InOnGroundAnim ( int anim ) +{ + switch( anim ) + { + case BOTH_DEAD1: + case BOTH_DEAD2: + case BOTH_DEAD3: + case BOTH_DEAD4: + case BOTH_DEAD5: + case BOTH_DEADFORWARD1: + case BOTH_DEADBACKWARD1: + case BOTH_DEADFORWARD2: + case BOTH_DEADBACKWARD2: + case BOTH_LYINGDEATH1: + case BOTH_LYINGDEAD1: + case BOTH_SLEEP1: //# laying on back-rknee up-rhand on torso + case BOTH_KNOCKDOWN1: //# + case BOTH_KNOCKDOWN2: //# + case BOTH_KNOCKDOWN3: //# + case BOTH_KNOCKDOWN4: //# + case BOTH_KNOCKDOWN5: //# + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_GETUP_CROUCH_F1: + case BOTH_GETUP_CROUCH_B1: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_FORCE_GETUP_B6: + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + return qtrue; + break; + } + + return qfalse; +} + +qboolean PM_InRollComplete( playerState_t *ps, int anim ) +{ + switch ( (anim) ) + { + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + if ( ps->legsTimer < 1 ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean PM_CanRollFromSoulCal( playerState_t *ps ) +{ + if ( ps->legsAnim == BOTH_A7_SOULCAL + && ps->legsTimer < 700 + && ps->legsTimer > 250 ) + { + return qtrue; + } + return qfalse; +} + +qboolean BG_SuperBreakLoseAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_LK_S_DL_S_SB_1_L: //super break I lost + case BOTH_LK_S_DL_T_SB_1_L: //super break I lost + case BOTH_LK_S_ST_S_SB_1_L: //super break I lost + case BOTH_LK_S_ST_T_SB_1_L: //super break I lost + case BOTH_LK_S_S_S_SB_1_L: //super break I lost + case BOTH_LK_S_S_T_SB_1_L: //super break I lost + case BOTH_LK_DL_DL_S_SB_1_L: //super break I lost + case BOTH_LK_DL_DL_T_SB_1_L: //super break I lost + case BOTH_LK_DL_ST_S_SB_1_L: //super break I lost + case BOTH_LK_DL_ST_T_SB_1_L: //super break I lost + case BOTH_LK_DL_S_S_SB_1_L: //super break I lost + case BOTH_LK_DL_S_T_SB_1_L: //super break I lost + case BOTH_LK_ST_DL_S_SB_1_L: //super break I lost + case BOTH_LK_ST_DL_T_SB_1_L: //super break I lost + case BOTH_LK_ST_ST_S_SB_1_L: //super break I lost + case BOTH_LK_ST_ST_T_SB_1_L: //super break I lost + case BOTH_LK_ST_S_S_SB_1_L: //super break I lost + case BOTH_LK_ST_S_T_SB_1_L: //super break I lost + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_SuperBreakWinAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_LK_S_DL_S_SB_1_W: //super break I won + case BOTH_LK_S_DL_T_SB_1_W: //super break I won + case BOTH_LK_S_ST_S_SB_1_W: //super break I won + case BOTH_LK_S_ST_T_SB_1_W: //super break I won + case BOTH_LK_S_S_S_SB_1_W: //super break I won + case BOTH_LK_S_S_T_SB_1_W: //super break I won + case BOTH_LK_DL_DL_S_SB_1_W: //super break I won + case BOTH_LK_DL_DL_T_SB_1_W: //super break I won + case BOTH_LK_DL_ST_S_SB_1_W: //super break I won + case BOTH_LK_DL_ST_T_SB_1_W: //super break I won + case BOTH_LK_DL_S_S_SB_1_W: //super break I won + case BOTH_LK_DL_S_T_SB_1_W: //super break I won + case BOTH_LK_ST_DL_S_SB_1_W: //super break I won + case BOTH_LK_ST_DL_T_SB_1_W: //super break I won + case BOTH_LK_ST_ST_S_SB_1_W: //super break I won + case BOTH_LK_ST_ST_T_SB_1_W: //super break I won + case BOTH_LK_ST_S_S_SB_1_W: //super break I won + case BOTH_LK_ST_S_T_SB_1_W: //super break I won + return qtrue; + break; + } + return qfalse; +} + + +qboolean BG_SaberLockBreakAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_BF1BREAK: + case BOTH_BF2BREAK: + case BOTH_CWCIRCLEBREAK: + case BOTH_CCWCIRCLEBREAK: + case BOTH_LK_S_DL_S_B_1_L: //normal break I lost + case BOTH_LK_S_DL_S_B_1_W: //normal break I won + case BOTH_LK_S_DL_T_B_1_L: //normal break I lost + case BOTH_LK_S_DL_T_B_1_W: //normal break I won + case BOTH_LK_S_ST_S_B_1_L: //normal break I lost + case BOTH_LK_S_ST_S_B_1_W: //normal break I won + case BOTH_LK_S_ST_T_B_1_L: //normal break I lost + case BOTH_LK_S_ST_T_B_1_W: //normal break I won + case BOTH_LK_S_S_S_B_1_L: //normal break I lost + case BOTH_LK_S_S_S_B_1_W: //normal break I won + case BOTH_LK_S_S_T_B_1_L: //normal break I lost + case BOTH_LK_S_S_T_B_1_W: //normal break I won + case BOTH_LK_DL_DL_S_B_1_L: //normal break I lost + case BOTH_LK_DL_DL_S_B_1_W: //normal break I won + case BOTH_LK_DL_DL_T_B_1_L: //normal break I lost + case BOTH_LK_DL_DL_T_B_1_W: //normal break I won + case BOTH_LK_DL_ST_S_B_1_L: //normal break I lost + case BOTH_LK_DL_ST_S_B_1_W: //normal break I won + case BOTH_LK_DL_ST_T_B_1_L: //normal break I lost + case BOTH_LK_DL_ST_T_B_1_W: //normal break I won + case BOTH_LK_DL_S_S_B_1_L: //normal break I lost + case BOTH_LK_DL_S_S_B_1_W: //normal break I won + case BOTH_LK_DL_S_T_B_1_L: //normal break I lost + case BOTH_LK_DL_S_T_B_1_W: //normal break I won + case BOTH_LK_ST_DL_S_B_1_L: //normal break I lost + case BOTH_LK_ST_DL_S_B_1_W: //normal break I won + case BOTH_LK_ST_DL_T_B_1_L: //normal break I lost + case BOTH_LK_ST_DL_T_B_1_W: //normal break I won + case BOTH_LK_ST_ST_S_B_1_L: //normal break I lost + case BOTH_LK_ST_ST_S_B_1_W: //normal break I won + case BOTH_LK_ST_ST_T_B_1_L: //normal break I lost + case BOTH_LK_ST_ST_T_B_1_W: //normal break I won + case BOTH_LK_ST_S_S_B_1_L: //normal break I lost + case BOTH_LK_ST_S_S_B_1_W: //normal break I won + case BOTH_LK_ST_S_T_B_1_L: //normal break I lost + case BOTH_LK_ST_S_T_B_1_W: //normal break I won + return qtrue; + break; + } + return (BG_SuperBreakLoseAnim(anim)||BG_SuperBreakWinAnim(anim)); +} + + +qboolean BG_FullBodyTauntAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_GESTURE1: + case BOTH_DUAL_TAUNT: + case BOTH_STAFF_TAUNT: + case BOTH_BOW: + case BOTH_MEDITATE: + case BOTH_SHOWOFF_FAST: + case BOTH_SHOWOFF_MEDIUM: + case BOTH_SHOWOFF_STRONG: + case BOTH_SHOWOFF_DUAL: + case BOTH_SHOWOFF_STAFF: + case BOTH_VICTORY_FAST: + case BOTH_VICTORY_MEDIUM: + case BOTH_VICTORY_STRONG: + case BOTH_VICTORY_DUAL: + case BOTH_VICTORY_STAFF: + return qtrue; + break; + } + return qfalse; +} + + +/* +============= +BG_AnimLength + +Get the "length" of an anim given the local anim index (which skeleton) +and anim number. Obviously does not take things like the length of the +anim while force speeding (as an example) and whatnot into account. +============= +*/ +int BG_AnimLength( int index, animNumber_t anim ) +{ + if (anim >= MAX_ANIMATIONS) + { + return -1; + } + + return bgAllAnims[index].anims[anim].numFrames * fabs((float)(bgAllAnims[index].anims[anim].frameLerp)); +} + +//just use whatever pm->animations is +int PM_AnimLength( int index, animNumber_t anim ) +{ + if (anim >= MAX_ANIMATIONS || !pm->animations) + { + return -1; + } + if ( anim < 0 ) + { + Com_Error(ERR_DROP,"ERROR: anim %d < 0\n", anim ); + } + return pm->animations[anim].numFrames * fabs((float)(pm->animations[anim].frameLerp)); +} + +void PM_DebugLegsAnim(int anim) +{ + int oldAnim = (pm->ps->legsAnim); + int newAnim = (anim); + + if (oldAnim < MAX_TOTALANIMATIONS && oldAnim >= BOTH_DEATH1 && + newAnim < MAX_TOTALANIMATIONS && newAnim >= BOTH_DEATH1) + { + Com_Printf("OLD: %s\n", animTable[oldAnim]); + Com_Printf("NEW: %s\n", animTable[newAnim]); + } +} + +qboolean PM_SaberInTransition( int move ) +{ + if ( move >= LS_T1_BR__R && move <= LS_T1_BL__L ) + { + return qtrue; + } + return qfalse; +} + +qboolean BG_SaberInTransitionAny( int move ) +{ + if ( PM_SaberInStart( move ) ) + { + return qtrue; + } + else if ( PM_SaberInTransition( move ) ) + { + return qtrue; + } + else if ( PM_SaberInReturn( move ) ) + { + return qtrue; + } + return qfalse; +} + +/* +============================================================================== +END: Animation utility functions (sequence checking) +============================================================================== +*/ + +void BG_FlipPart(playerState_t *ps, int part) +{ + if (part == SETANIM_TORSO) + { + if (ps->torsoFlip) + { + ps->torsoFlip = qfalse; + } + else + { + ps->torsoFlip = qtrue; + } + } + else if (part == SETANIM_LEGS) + { + if (ps->legsFlip) + { + ps->legsFlip = qfalse; + } + else + { + ps->legsFlip = qtrue; + } + } +} + +#ifdef Q3_VM +char BGPAFtext[60000]; +#endif +qboolean BGPAFtextLoaded = qfalse; +animation_t bgHumanoidAnimations[MAX_TOTALANIMATIONS]; //humanoid animations are the only ones that are statically allocated. + +//#define CONVENIENT_ANIMATION_FILE_DEBUG_THING + +#ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING +void SpewDebugStuffToFile() +{ + fileHandle_t f; + int i = 0; + + trap_FS_FOpenFile("file_of_debug_stuff_MP.txt", &f, FS_WRITE); + + if (!f) + { + return; + } + + BGPAFtext[0] = 0; + + while (i < MAX_ANIMATIONS) + { + strcat(BGPAFtext, va("%i %i\n", i, bgHumanoidAnimations[i].frameLerp)); + i++; + } + + trap_FS_Write(BGPAFtext, strlen(BGPAFtext), f); + trap_FS_FCloseFile(f); +} +#endif + +bgLoadedAnim_t bgAllAnims[MAX_ANIM_FILES]; +int bgNumAllAnims = 2; //start off at 2, because 0 will always be assigned to humanoid, and 1 will always be rockettrooper + +bgLoadedEvents_t bgAllEvents[MAX_ANIM_FILES]; +int bgNumAnimEvents = 1; +static int bg_animParseIncluding = 0; + +//ALWAYS call on game/cgame init +void BG_InitAnimsets(void) +{ + memset(&bgAllAnims, 0, sizeof(bgAllAnims)); + BGPAFtextLoaded = qfalse; // VVFIXME - The PC doesn't seem to need this, but why? + bgNumAllAnims = 2; + memset(bgAllEvents, 0, sizeof(bgAllEvents)); + bgNumAnimEvents = 1; + bg_animParseIncluding = 0; +} + +//ALWAYS call on game/cgame shutdown +void BG_ClearAnimsets(void) +{ + /* + int i = 1; + + while (i < bgNumAllAnims) + { + if (bgAllAnims[i].anims) + { + strap_TrueFree((void **)&bgAllAnims[i].anims); + } + i++; + } + */ +} + +animation_t *BG_AnimsetAlloc(void) +{ + assert (bgNumAllAnims < MAX_ANIM_FILES); + bgAllAnims[bgNumAllAnims].anims = (animation_t *) BG_Alloc(sizeof(animation_t)*MAX_TOTALANIMATIONS); + + return bgAllAnims[bgNumAllAnims].anims; +} + +void BG_AnimsetFree(animation_t *animset) +{ + /* + if (!animset) + { + return; + } + + strap_TrueFree((void **)&animset); + +#ifdef _DEBUG + if (animset) + { + assert(!"Failed to free anim set"); + } +#endif + */ +} + +#ifndef QAGAME //none of this is actually needed serverside. Could just be moved to cgame code but it's here since it + //used to tie in a lot with the anim loading stuff. +stringID_table_t animEventTypeTable[AEV_NUM_AEV+1] = +{ + ENUM2STRING(AEV_SOUND), //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay + ENUM2STRING(AEV_FOOTSTEP), //# animID AEV_FOOTSTEP framenum footstepType + ENUM2STRING(AEV_EFFECT), //# animID AEV_EFFECT framenum effectpath boltName + ENUM2STRING(AEV_FIRE), //# animID AEV_FIRE framenum altfire chancetofire + ENUM2STRING(AEV_MOVE), //# animID AEV_MOVE framenum forwardpush rightpush uppush + ENUM2STRING(AEV_SOUNDCHAN), //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay + //must be terminated + NULL,-1 +}; + +stringID_table_t footstepTypeTable[NUM_FOOTSTEP_TYPES+1] = +{ + ENUM2STRING(FOOTSTEP_R), + ENUM2STRING(FOOTSTEP_L), + ENUM2STRING(FOOTSTEP_HEAVY_R), + ENUM2STRING(FOOTSTEP_HEAVY_L), + //must be terminated + NULL,-1 +}; + +int CheckAnimFrameForEventType( animevent_t *animEvents, int keyFrame, animEventType_t eventType ) +{ + int i; + + for ( i = 0; i < MAX_ANIM_EVENTS; i++ ) + { + if ( animEvents[i].keyFrame == keyFrame ) + {//there is an animevent on this frame already + if ( animEvents[i].eventType == eventType ) + {//and it is of the same type + return i; + } + } + } + //nope + return -1; +} + +void ParseAnimationEvtBlock(const char *aeb_filename, animevent_t *animEvents, animation_t *animations, int *i,const char **text_p) +{ + const char *token; + int num, n, animNum, keyFrame, lowestVal, highestVal, curAnimEvent, lastAnimEvent = 0; + animEventType_t eventType; + char stringData[MAX_QPATH]; + + // get past starting bracket + while(1) + { + token = COM_Parse( text_p ); + if ( !Q_stricmp( token, "{" ) ) + { + break; + } + } + + //NOTE: instead of a blind increment, increase the index + // this way if we have an event on an anim that already + // has an event of that type, it stomps it + + // read information for each frame + while ( 1 ) + { + if ( lastAnimEvent >= MAX_ANIM_EVENTS ) + { + Com_Error( ERR_DROP, "ParseAnimationEvtBlock: number events in animEvent file %s > MAX_ANIM_EVENTS(%i)", aeb_filename, MAX_ANIM_EVENTS ); + return; + } + // Get base frame of sequence + token = COM_Parse( text_p ); + if ( !token || !token[0]) + { + break; + } + + if ( !Q_stricmp( token, "}" ) ) // At end of block + { + break; + } + + //Compare to same table as animations used + // so we don't have to use actual numbers for animation first frames, + // just need offsets. + //This way when animation numbers change, this table won't have to be updated, + // at least not much. + animNum = GetIDForString(animTable, token); + if(animNum == -1) + {//Unrecognized ANIM ENUM name, or we're skipping this line, keep going till you get a good one +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW"WARNING: Unknown token %s in animEvent file %s\n", token, aeb_filename ); +#endif + while (token[0]) + { + token = COM_ParseExt( text_p, qfalse ); //returns empty string when next token is EOL + } + continue; + } + + if ( animations[animNum].numFrames == 0 ) + {//we don't use this anim +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW"WARNING: %s animevents.cfg: anim %s not used by this model\n", aeb_filename, token); +#endif + //skip this entry + SkipRestOfLine( text_p ); + continue; + } + + token = COM_Parse( text_p ); + eventType = (animEventType_t)GetIDForString(animEventTypeTable, token); + if ( eventType == AEV_NONE || eventType == -1 ) + {//Unrecognized ANIM EVENT TYOE, or we're skipping this line, keep going till you get a good one + //Com_Printf(S_COLOR_YELLOW"WARNING: Unknown token %s in animEvent file %s\n", token, aeb_filename ); + continue; + } + + //set our start frame + keyFrame = animations[animNum].firstFrame; + // Get offset to frame within sequence + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + keyFrame += atoi( token ); + + //see if this frame already has an event of this type on it, if so, overwrite it + curAnimEvent = CheckAnimFrameForEventType( animEvents, keyFrame, eventType ); + if ( curAnimEvent == -1 ) + {//this anim frame doesn't already have an event of this type on it + curAnimEvent = lastAnimEvent; + } + + //now that we know which event index we're going to plug the data into, start doing it + animEvents[curAnimEvent].eventType = eventType; + animEvents[curAnimEvent].keyFrame = keyFrame; + + //now read out the proper data based on the type + switch ( animEvents[curAnimEvent].eventType ) + { + case AEV_SOUNDCHAN: //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + if ( stricmp( token, "CHAN_VOICE_ATTEN" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE_ATTEN; + } + else if ( stricmp( token, "CHAN_VOICE_GLOBAL" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE_GLOBAL; + } + else if ( stricmp( token, "CHAN_ANNOUNCER" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_ANNOUNCER; + } + else if ( stricmp( token, "CHAN_BODY" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_BODY; + } + else if ( stricmp( token, "CHAN_WEAPON" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_WEAPON; + } + else if ( stricmp( token, "CHAN_VOICE" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE; + } + else + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_AUTO; + } + //fall through to normal sound + case AEV_SOUND: //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay + //get soundstring + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + strcpy(stringData, token); + //get lowest value + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + lowestVal = atoi( token ); + //get highest value + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + highestVal = atoi( token ); + //Now precache all the sounds + //NOTE: If we can be assured sequential handles, we can store sound indices + // instead of strings, unfortunately, if these sounds were previously + // registered, we cannot be guaranteed sequential indices. Thus an array + if(lowestVal && highestVal) + { + //assert(highestVal - lowestVal < MAX_RANDOM_ANIM_SOUNDS); + if ((highestVal-lowestVal) >= MAX_RANDOM_ANIM_SOUNDS) + { + highestVal = lowestVal + (MAX_RANDOM_ANIM_SOUNDS-1); + } + for ( n = lowestVal, num = AED_SOUNDINDEX_START; n <= highestVal && num <= AED_SOUNDINDEX_END; n++, num++ ) + { + if (stringData[0] == '*') + { //FIXME? Would be nice to make custom sounds work with animEvents. + animEvents[curAnimEvent].eventData[num] = 0; + } + else + { + animEvents[curAnimEvent].eventData[num] = trap_S_RegisterSound( va( stringData, n ) ); + } + } + animEvents[curAnimEvent].eventData[AED_SOUND_NUMRANDOMSNDS] = num - 1; + } + else + { + if (stringData[0] == '*') + { //FIXME? Would be nice to make custom sounds work with animEvents. + animEvents[curAnimEvent].eventData[AED_SOUNDINDEX_START] = 0; + } + else + { + animEvents[curAnimEvent].eventData[AED_SOUNDINDEX_START] = trap_S_RegisterSound( stringData ); + } +#ifndef FINAL_BUILD + if ( !animEvents[curAnimEvent].eventData[AED_SOUNDINDEX_START] && + stringData[0] != '*') + {//couldn't register it - file not found + Com_Printf( S_COLOR_RED "ParseAnimationSndBlock: sound %s does not exist (animevents.cfg %s)!\n", stringData, aeb_filename ); + } +#endif + animEvents[curAnimEvent].eventData[AED_SOUND_NUMRANDOMSNDS] = 0; + } + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_SOUND_PROBABILITY] = atoi( token ); + break; + case AEV_FOOTSTEP: //# animID AEV_FOOTSTEP framenum footstepType + //get footstep type + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + animEvents[curAnimEvent].eventData[AED_FOOTSTEP_TYPE] = GetIDForString(footstepTypeTable, token); + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_FOOTSTEP_PROBABILITY] = atoi( token ); + break; + case AEV_EFFECT: //# animID AEV_EFFECT framenum effectpath boltName + //get effect index + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + animEvents[curAnimEvent].eventData[AED_EFFECTINDEX] = trap_FX_RegisterEffect( token ); + //get bolt index + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + if ( Q_stricmp( "none", token ) != 0 && Q_stricmp( "NULL", token ) != 0 ) + {//actually are specifying a bolt to use + if (!animEvents[curAnimEvent].stringData) + { //eh, whatever. no dynamic stuff, so this will do. + animEvents[curAnimEvent].stringData = (char *) BG_Alloc(2048); + } + strcpy(animEvents[curAnimEvent].stringData, token); + } + //NOTE: this string will later be used to add a bolt and store the index, as below: + //animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], animEvent->stringData ); + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_EFFECT_PROBABILITY] = atoi( token ); + break; + case AEV_FIRE: //# animID AEV_FIRE framenum altfire chancetofire + //get altfire + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_FIRE_ALT] = atoi( token ); + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_FIRE_PROBABILITY] = atoi( token ); + break; + case AEV_MOVE: //# animID AEV_MOVE framenum forwardpush rightpush uppush + //get forward push + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_MOVE_FWD] = atoi( token ); + //get right push + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_MOVE_RT] = atoi( token ); + //get upwards push + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_MOVE_UP] = atoi( token ); + break; + default: //unknown? + SkipRestOfLine( text_p ); + continue; + break; + } + + if ( curAnimEvent == lastAnimEvent ) + { + lastAnimEvent++; + } + } +} + +/* +====================== +BG_ParseAnimationEvtFile + +Read a configuration file containing animation events +models/players/kyle/animevents.cfg, etc + +This file's presence is not required + +====================== +*/ +int BG_ParseAnimationEvtFile( const char *as_filename, int animFileIndex, int eventFileIndex ) +{ + const char *text_p; + int len; + const char *token; + char text[80000]; + char sfilename[MAX_QPATH]; + fileHandle_t f; + int i, j, upper_i, lower_i; + int usedIndex = -1; + animevent_t *legsAnimEvents; + animevent_t *torsoAnimEvents; + animation_t *animations; + int forcedIndex; + + assert(animFileIndex < MAX_ANIM_FILES); + assert(eventFileIndex < MAX_ANIM_FILES); + + if (eventFileIndex == -1) + { + forcedIndex = 0; + } + else + { + forcedIndex = eventFileIndex; + } + + if (bg_animParseIncluding <= 0) + { //if we should be parsing an included file, skip this part + if ( bgAllEvents[forcedIndex].eventsParsed ) + {//already cached this one + return forcedIndex; + } + } + + legsAnimEvents = bgAllEvents[forcedIndex].legsAnimEvents; + torsoAnimEvents = bgAllEvents[forcedIndex].torsoAnimEvents; + animations = bgAllAnims[animFileIndex].anims; + + if (bg_animParseIncluding <= 0) + { //if we should be parsing an included file, skip this part + //Go through and see if this filename is already in the table. + i = 0; + while (i < bgNumAnimEvents && forcedIndex != 0) + { + if (!Q_stricmp(as_filename, bgAllEvents[i].filename)) + { //looks like we have it already. + return i; + } + i++; + } + } + + // Load and parse animevents.cfg file + Com_sprintf( sfilename, sizeof( sfilename ), "%sanimevents.cfg", as_filename ); + + if (bg_animParseIncluding <= 0) + { //should already be done if we're including + //initialize anim event array + for( i = 0; i < MAX_ANIM_EVENTS; i++ ) + { + //Type of event + torsoAnimEvents[i].eventType = AEV_NONE; + legsAnimEvents[i].eventType = AEV_NONE; + //Frame to play event on + torsoAnimEvents[i].keyFrame = -1; + legsAnimEvents[i].keyFrame = -1; + //we allow storage of one string, temporarily (in case we have to look up an index later, then make sure to set stringData to NULL so we only do the look-up once) + torsoAnimEvents[i].stringData = NULL; + legsAnimEvents[i].stringData = NULL; + //Unique IDs, can be soundIndex of sound file to play OR effect index or footstep type, etc. + for ( j = 0; j < AED_ARRAY_SIZE; j++ ) + { + torsoAnimEvents[i].eventData[j] = -1; + legsAnimEvents[i].eventData[j] = -1; + } + } + } + + // load the file + len = trap_FS_FOpenFile( sfilename, &f, FS_READ ); + if ( len <= 0 ) + {//no file + goto fin; + } + if ( len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile(f); +#ifndef FINAL_BUILD + Com_Error(ERR_DROP, "File %s too long\n", sfilename ); +#else + Com_Printf( "File %s too long\n", sfilename ); +#endif + goto fin; + } + + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + upper_i =0; + lower_i =0; + + // read information for batches of sounds (UPPER or LOWER) + while ( 1 ) + { + // Get base frame of sequence + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) + { + break; + } + + if ( !Q_stricmp(token,"include") ) // grab from another animevents.cfg + {//NOTE: you REALLY should NOT do this after the main block of UPPERSOUNDS and LOWERSOUNDS + const char *include_filename = COM_Parse( &text_p ); + if ( include_filename != NULL ) + { + char fullIPath[MAX_QPATH]; + strcpy(fullIPath, va("models/players/%s/", include_filename)); + bg_animParseIncluding++; + BG_ParseAnimationEvtFile( fullIPath, animFileIndex, forcedIndex ); + bg_animParseIncluding--; + } + } + + if ( !Q_stricmp(token,"UPPEREVENTS") ) // A batch of upper sounds + { + ParseAnimationEvtBlock( as_filename, torsoAnimEvents, animations, &upper_i, &text_p ); + } + + else if ( !Q_stricmp(token,"LOWEREVENTS") ) // A batch of lower sounds + { + ParseAnimationEvtBlock( as_filename, legsAnimEvents, animations, &lower_i, &text_p ); + } + } + + usedIndex = forcedIndex; +fin: + //Mark this anim set so that we know we tried to load he sounds, don't care if the load failed + if (bg_animParseIncluding <= 0) + { //if we should be parsing an included file, skip this part + bgAllEvents[forcedIndex].eventsParsed = qtrue; + strcpy(bgAllEvents[forcedIndex].filename, as_filename); + if (forcedIndex) + { + bgNumAnimEvents++; + } + } + + return usedIndex; +} +#endif + +/* +====================== +BG_ParseAnimationFile + +Read a configuration file containing animation coutns and rates +models/players/visor/animation.cfg, etc + +====================== +*/ +int BG_ParseAnimationFile(const char *filename, animation_t *animset, qboolean isHumanoid) +{ + char *text_p; + int len; + int i; + char *token; + float fps; + int skip; + int usedIndex = -1; + int nextIndex = bgNumAllAnims; + qboolean dynAlloc = qfalse; + qboolean wasLoaded = qfalse; +#ifndef Q3_VM + char BGPAFtext[60000]; +#endif + + fileHandle_t f; + int animNum; + + if (!isHumanoid) + { + i = 0; + while (i < bgNumAllAnims) + { //see if it's been loaded already + if (!Q_stricmp(bgAllAnims[i].filename, filename)) + { + animset = bgAllAnims[i].anims; + return i; //alright, we already have it. + } + i++; + } + + //Looks like it has not yet been loaded. Allocate space for the anim set if we need to, and continue along. + if (!animset) + { + if (strstr(filename, "players/_humanoid/")) + { //then use the static humanoid set. + animset = bgHumanoidAnimations; + nextIndex = 0; + } + else if (strstr(filename, "players/rockettrooper/")) + { //rockettrooper always index 1 + nextIndex = 1; + animset = BG_AnimsetAlloc(); + dynAlloc = qtrue; //so we know to free this memory in case we have to return early. Don't want any leaks. + + if (!animset) + { + assert(!"Anim set alloc failed!"); + return -1; + } + } + else + { + animset = BG_AnimsetAlloc(); + dynAlloc = qtrue; //so we know to free this memory in case we have to return early. Don't want any leaks. + + if (!animset) + { + assert(!"Anim set alloc failed!"); + return -1; + } + } + } + } +#ifdef _DEBUG + else + { + assert(animset); + } +#endif + + // load the file + if (!BGPAFtextLoaded || !isHumanoid) + { //rww - We are always using the same animation config now. So only load it once. + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( (len <= 0) || (len >= sizeof( BGPAFtext ) - 1) ) + { + if (dynAlloc) + { + BG_AnimsetFree(animset); + } + if (len > 0) + { + Com_Error(ERR_DROP, "%s exceeds the allowed game-side animation buffer!", filename); + } + return -1; + } + + trap_FS_Read( BGPAFtext, len, f ); + BGPAFtext[len] = 0; + trap_FS_FCloseFile( f ); + } + else + { + if (dynAlloc) + { + assert(!"Should not have allocated dynamically for humanoid"); + BG_AnimsetFree(animset); + } + return 0; //humanoid index + } + + // parse the text + text_p = BGPAFtext; + skip = 0; // quiet the compiler warning + + //FIXME: have some way of playing anims backwards... negative numFrames? + + //initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100 + for(i = 0; i < MAX_ANIMATIONS; i++) + { + animset[i].firstFrame = 0; + animset[i].numFrames = 0; + animset[i].loopFrames = -1; + animset[i].frameLerp = 100; + } + + // read information for each frame + while(1) + { + token = COM_Parse( (const char **)(&text_p) ); + + if ( !token || !token[0]) + { + break; + } + + animNum = GetIDForString(animTable, token); + if(animNum == -1) + { +//#ifndef FINAL_BUILD +#ifdef _DEBUG + Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, filename); + while (token[0]) + { + token = COM_ParseExt( (const char **) &text_p, qfalse ); //returns empty string when next token is EOL + } +#endif + continue; + } + + token = COM_Parse( (const char **)(&text_p) ); + if ( !token ) + { + break; + } + animset[animNum].firstFrame = atoi( token ); + + token = COM_Parse( (const char **)(&text_p) ); + if ( !token ) + { + break; + } + animset[animNum].numFrames = atoi( token ); + + token = COM_Parse( (const char **)(&text_p) ); + if ( !token ) + { + break; + } + animset[animNum].loopFrames = atoi( token ); + + token = COM_Parse( (const char **)(&text_p) ); + if ( !token ) + { + break; + } + fps = atof( token ); + if ( fps == 0 ) + { + fps = 1;//Don't allow divide by zero error + } + if ( fps < 0 ) + {//backwards + animset[animNum].frameLerp = floor(1000.0f / fps); + } + else + { + animset[animNum].frameLerp = ceil(1000.0f / fps); + } + } +/* +#ifdef _DEBUG + //Check the array, and print the ones that have nothing in them. + for(i = 0; i < MAX_ANIMATIONS; i++) + { + if (animTable[i].name != NULL) // This animation reference exists. + { + if (animset[i].firstFrame <= 0 && animset[i].numFrames <=0) + { // This is an empty animation reference. + Com_Printf("***ANIMTABLE reference #%d (%s) is empty!\n", i, animTable[i].name); + } + } + } +#endif // _DEBUG +*/ +#ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING + SpewDebugStuffToFile(); +#endif + + wasLoaded = BGPAFtextLoaded; + + if (isHumanoid) + { + bgAllAnims[0].anims = animset; + strcpy(bgAllAnims[0].filename, filename); + BGPAFtextLoaded = qtrue; + + usedIndex = 0; + } + else + { + bgAllAnims[nextIndex].anims = animset; + strcpy(bgAllAnims[nextIndex].filename, filename); + + usedIndex = bgNumAllAnims; + + if (nextIndex > 1) + { //don't bother increasing the number if this ended up as a humanoid/rockettrooper load. + bgNumAllAnims++; + } + else + { + BGPAFtextLoaded = qtrue; + usedIndex = nextIndex; + } + } + + /* + if (!wasLoaded && BGPAFtextLoaded) + { //just loaded humanoid skel - we always want the rockettrooper to be after it, in slot 1 +#ifdef _DEBUG + assert(BG_ParseAnimationFile("models/players/rockettrooper/animation.cfg", NULL, qfalse) == 1); +#else + BG_ParseAnimationFile("models/players/rockettrooper/animation.cfg", NULL, qfalse); +#endif + } + */ + + return usedIndex; +} + +/* +=================== +LEGS Animations +Base animation for overall body +=================== +*/ +static void BG_StartLegsAnim( playerState_t *ps, int anim ) +{ + if ( ps->pm_type >= PM_DEAD ) + { + assert(!BG_InDeathAnim(anim)); + //please let me know if this assert fires on you (ideally before you close/ignore it) -rww + + //vehicles are allowed to do this.. IF it's a vehicle death anim + if (ps->clientNum < MAX_CLIENTS || anim != BOTH_VT_DEATH1) + { + return; + } + } + if ( ps->legsTimer > 0 ) + { + return; // a high priority animation is running + } + + if (ps->legsAnim == anim) + { + BG_FlipPart(ps, SETANIM_LEGS); + } +#ifdef QAGAME + else if (g_entities[ps->clientNum].s.legsAnim == anim) + { //toggled anim to one anim then back to the one we were at previously in + //one frame, indicating that anim should be restarted. + BG_FlipPart(ps, SETANIM_LEGS); + } +#endif + ps->legsAnim = anim; + + /* + if ( pm->debugLevel ) { + Com_Printf("%d: StartLegsAnim %d, on client#%d\n", pm->cmd.serverTime, anim, pm->ps->clientNum); + } + */ +} + +void PM_ContinueLegsAnim( int anim ) { + if ( ( pm->ps->legsAnim ) == anim ) { + return; + } + if ( pm->ps->legsTimer > 0 ) { + return; // a high priority animation is running + } + + BG_StartLegsAnim( pm->ps, anim ); +} + +void PM_ForceLegsAnim( int anim) { + if (BG_InSpecialJump(pm->ps->legsAnim) && + pm->ps->legsTimer > 0 && + !BG_InSpecialJump(anim)) + { + return; + } + + if (BG_InRoll(pm->ps, pm->ps->legsAnim) && + pm->ps->legsTimer > 0 && + !BG_InRoll(pm->ps, anim)) + { + return; + } + + pm->ps->legsTimer = 0; + BG_StartLegsAnim( pm->ps, anim ); +} + + + +/* +=================== +TORSO Animations +Override animations for upper body +=================== +*/ +void BG_StartTorsoAnim( playerState_t *ps, int anim ) +{ + if ( ps->pm_type >= PM_DEAD ) + { + assert(!BG_InDeathAnim(anim)); + //please let me know if this assert fires on you (ideally before you close/ignore it) -rww + return; + } + + if (ps->torsoAnim == anim) + { + BG_FlipPart(ps, SETANIM_TORSO); + } +#ifdef QAGAME + else if (g_entities[ps->clientNum].s.torsoAnim == anim) + { //toggled anim to one anim then back to the one we were at previously in + //one frame, indicating that anim should be restarted. + BG_FlipPart(ps, SETANIM_TORSO); + } +#endif + ps->torsoAnim = anim; +} + +void PM_StartTorsoAnim( int anim ) +{ + BG_StartTorsoAnim(pm->ps, anim); +} + + +/* +------------------------- +PM_SetLegsAnimTimer +------------------------- +*/ +void BG_SetLegsAnimTimer(playerState_t *ps, int time) +{ + ps->legsTimer = time; + + if (ps->legsTimer < 0 && time != -1 ) + {//Cap timer to 0 if was counting down, but let it be -1 if that was intentional. NOTENOTE Yeah this seems dumb, but it mirrors SP. + ps->legsTimer = 0; + } +} + +void PM_SetLegsAnimTimer(int time) +{ + BG_SetLegsAnimTimer(pm->ps, time); +} + +/* +------------------------- +PM_SetTorsoAnimTimer +------------------------- +*/ +void BG_SetTorsoAnimTimer(playerState_t *ps, int time ) +{ + ps->torsoTimer = time; + + if (ps->torsoTimer < 0 && time != -1 ) + {//Cap timer to 0 if was counting down, but let it be -1 if that was intentional. NOTENOTE Yeah this seems dumb, but it mirrors SP. + ps->torsoTimer = 0; + } +} + +void PM_SetTorsoAnimTimer(int time ) +{ + BG_SetTorsoAnimTimer(pm->ps, time); +} + +void BG_SaberStartTransAnim( int saberAnimLevel, int anim, float *animSpeed, int broken ) +{ + if ( ( (anim) >= BOTH_T1_BR__R && + (anim) <= BOTH_T1_BL_TL ) || + ( (anim) >= BOTH_T2_BR__R && + (anim) <= BOTH_T2_BL_TL ) || + ( (anim) >= BOTH_T3_BR__R && + (anim) <= BOTH_T3_BL_TL ) ) + { + if ( saberAnimLevel == FORCE_LEVEL_1 ) + { + *animSpeed *= 1.5f; + } + else if ( saberAnimLevel == FORCE_LEVEL_3 ) + { + *animSpeed *= 0.75f; + } + + if (broken & (1< -1); + assert(animations[anim].firstFrame > 0 || animations[anim].numFrames > 0); + + //NOTE: Setting blendTime here breaks actual blending.. + blendTime = 0; + + BG_SaberStartTransAnim(ps->fd.saberAnimLevel, anim, &editAnimSpeed, ps->brokenLimbs); + + // Set torso anim + if (setAnimParts & SETANIM_TORSO) + { + // Don't reset if it's already running the anim + if( !(setAnimFlags & SETANIM_FLAG_RESTART) && (ps->torsoAnim) == anim ) + { + goto setAnimLegs; + } + // or if a more important anim is running + if( !(setAnimFlags & SETANIM_FLAG_OVERRIDE) && ((ps->torsoTimer > 0)||(ps->torsoTimer == -1)) ) + { + goto setAnimLegs; + } + + BG_StartTorsoAnim(ps, anim); + + if (setAnimFlags & SETANIM_FLAG_HOLD) + { + if (setAnimFlags & SETANIM_FLAG_HOLDLESS) + { // Make sure to only wait in full 1/20 sec server frame intervals. + int dur; + int speedDif; + +// dur = (animations[anim].numFrames-1) * fabs((float)(animations[anim].frameLerp)); + dur = (animations[anim].numFrames-0.5f) * fabs((float)(animations[anim].frameLerp)); + speedDif = dur - (dur * editAnimSpeed); + dur += speedDif; + if (dur > 1) + { + ps->torsoTimer = dur-1; + } + else + { + ps->torsoTimer = fabs((float)(animations[anim].frameLerp)); + } + } + else + { + ps->torsoTimer = ((animations[anim].numFrames ) * fabs((float)(animations[anim].frameLerp))); + } + + if (ps->fd.forcePowersActive & (1 << FP_RAGE)) + { + ps->torsoTimer /= 1.7; + } + } + } + +setAnimLegs: + // Set legs anim + if (setAnimParts & SETANIM_LEGS) + { + // Don't reset if it's already running the anim + if( !(setAnimFlags & SETANIM_FLAG_RESTART) && (ps->legsAnim) == anim ) + { + goto setAnimDone; + } + // or if a more important anim is running + if( !(setAnimFlags & SETANIM_FLAG_OVERRIDE) && ((ps->legsTimer > 0)||(ps->legsTimer == -1)) ) + { + goto setAnimDone; + } + + BG_StartLegsAnim(ps, anim); + + if (setAnimFlags & SETANIM_FLAG_HOLD) + { + if (setAnimFlags & SETANIM_FLAG_HOLDLESS) + { // Make sure to only wait in full 1/20 sec server frame intervals. + int dur; + int speedDif; + + dur = (animations[anim].numFrames-1) * fabs((float)(animations[anim].frameLerp)); + speedDif = dur - (dur * editAnimSpeed); + dur += speedDif; + if (dur > 1) + { + ps->legsTimer = dur-1; + } + else + { + ps->legsTimer = fabs((float)(animations[anim].frameLerp)); + } + } + else + { + ps->legsTimer = ((animations[anim].numFrames ) * fabs((float)(animations[anim].frameLerp))); + } + + if (PM_RunningAnim(anim) || + PM_WalkingAnim(anim)) //these guys are ok, they don't actually reference pm + { + if (ps->fd.forcePowersActive & (1 << FP_RAGE)) + { + ps->legsTimer /= 1.3; + } + else if (ps->fd.forcePowersActive & (1 << FP_SPEED)) + { + ps->legsTimer /= 1.7; + } + } + } + } + +setAnimDone: + return; +} + +void PM_SetAnimFinal(int setAnimParts,int anim,int setAnimFlags, + int blendTime) // default blendTime=350 +{ + BG_SetAnimFinal(pm->ps, pm->animations, setAnimParts, anim, setAnimFlags, blendTime); +} + + +qboolean BG_HasAnimation(int animIndex, int animation) +{ + animation_t *animations; + + //must be a valid anim number + if ( animation < 0 || animation >= MAX_ANIMATIONS ) + { + return qfalse; + } + + //Must have a file index entry + if( animIndex < 0 || animIndex > bgNumAllAnims ) + return qfalse; + + animations = bgAllAnims[animIndex].anims; + + //No frames, no anim + if ( animations[animation].numFrames == 0 ) + return qfalse; + + //Has the sequence + return qtrue; +} + +int BG_PickAnim( int animIndex, int minAnim, int maxAnim ) +{ + int anim; + int count = 0; + + do + { + anim = Q_irand(minAnim, maxAnim); + count++; + } + while ( !BG_HasAnimation( animIndex, anim ) && count < 1000 ); + + if (count == 1000) + { //guess we just don't have a death anim then. + return -1; + } + + return anim; +} + +//I want to be able to use this on a playerstate even when we are not the focus +//of a pmove too so I have ported it to true BGishness. +//Please do not reference pm in this function or any functions that it calls, +//or I will cry. -rww +void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts,int anim,int setAnimFlags, int blendTime) +{ + if (!animations) + { + animations = bgAllAnims[0].anims; + } + + if (animations[anim].firstFrame == 0 && animations[anim].numFrames == 0) + { + if (anim == BOTH_RUNBACK1 || + anim == BOTH_WALKBACK1 || + anim == BOTH_RUN1) + { //hack for droids + anim = BOTH_WALK2; + } + + if (animations[anim].firstFrame == 0 && animations[anim].numFrames == 0) + { //still? Just return then I guess. + return; + } + } + + /* + if (BG_InSpecialJump(anim)) + { + setAnimFlags |= SETANIM_FLAG_RESTART; + } + */ + //Don't know why I put this here originally but it's messing stuff up now and it isn't needed. + +// if (BG_InRoll(ps, ps->legsAnim)) +// { //never interrupt a roll +// return; +// } + + if (setAnimFlags&SETANIM_FLAG_OVERRIDE) + { + if (setAnimParts & SETANIM_TORSO) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || (ps->torsoAnim) != anim ) + { + BG_SetTorsoAnimTimer(ps, 0); + } + } + if (setAnimParts & SETANIM_LEGS) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || (ps->legsAnim) != anim ) + { + BG_SetLegsAnimTimer(ps, 0); + } + } + } + + BG_SetAnimFinal(ps, animations, setAnimParts, anim, setAnimFlags, blendTime); +} + +void PM_SetAnim(int setAnimParts,int anim,int setAnimFlags, int blendTime) +{ + BG_SetAnim(pm->ps, pm->animations, setAnimParts, anim, setAnimFlags, blendTime); +} + +#include "../namespace_end.h" // End of animation utilities diff --git a/codemp/game/bg_pmove.c b/codemp/game/bg_pmove.c new file mode 100644 index 0000000..35e9298 --- /dev/null +++ b/codemp/game/bg_pmove.c @@ -0,0 +1,10880 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_pmove.c -- both games player movement code +// takes a playerstate and a usercmd as input and returns a modifed playerstate + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" +#include "bg_strap.h" +#include "../ghoul2/G2.h" + +#ifdef QAGAME +#include "g_local.h" //ahahahahhahahaha@$!$! +#endif + +#define MAX_WEAPON_CHARGE_TIME 5000 + +#ifdef QAGAME +extern void G_CheapWeaponFire(int entNum, int ev); +extern qboolean TryGrapple(gentity_t *ent); //g_cmds.c +extern void trap_FX_PlayEffect( const char *file, vec3_t org, vec3_t fwd, int vol, int rad ); +#endif +#include "..\client\fffx.h" +#include "../namespace_begin.h" +extern qboolean BG_FullBodyTauntAnim( int anim ); +extern float PM_WalkableGroundDistance(void); +extern qboolean PM_GroundSlideOkay( float zNormal ); + +pmove_t *pm; +pml_t pml; + +bgEntity_t *pm_entSelf = NULL; +bgEntity_t *pm_entVeh = NULL; + +qboolean gPMDoSlowFall = qfalse; + +qboolean pm_cancelOutZoom = qfalse; + +// movement parameters +float pm_stopspeed = 100.0f; +float pm_duckScale = 0.50f; +float pm_swimScale = 0.50f; +float pm_wadeScale = 0.70f; + +float pm_vehicleaccelerate = 36.0f; +float pm_accelerate = 10.0f; +float pm_airaccelerate = 1.0f; +float pm_wateraccelerate = 4.0f; +float pm_flyaccelerate = 8.0f; + +float pm_friction = 6.0f; +float pm_waterfriction = 1.0f; +float pm_flightfriction = 3.0f; +float pm_spectatorfriction = 5.0f; + +int c_pmove = 0; + +float forceSpeedLevels[4] = +{ + 1, //rank 0? + 1.25, + 1.5, + 1.75 +}; + +int forcePowerNeeded[NUM_FORCE_POWER_LEVELS][NUM_FORCE_POWERS] = +{ + { //nothing should be usable at rank 0.. + 999,//FP_HEAL,//instant + 999,//FP_LEVITATION,//hold/duration + 999,//FP_SPEED,//duration + 999,//FP_PUSH,//hold/duration + 999,//FP_PULL,//hold/duration + 999,//FP_TELEPATHY,//instant + 999,//FP_GRIP,//hold/duration + 999,//FP_LIGHTNING,//hold/duration + 999,//FP_RAGE,//duration + 999,//FP_PROTECT,//duration + 999,//FP_ABSORB,//duration + 999,//FP_TEAM_HEAL,//instant + 999,//FP_TEAM_FORCE,//instant + 999,//FP_DRAIN,//hold/duration + 999,//FP_SEE,//duration + 999,//FP_SABER_OFFENSE, + 999,//FP_SABER_DEFENSE, + 999//FP_SABERTHROW, + //NUM_FORCE_POWERS + }, + { + 65,//FP_HEAL,//instant //was 25, but that was way too little + 10,//FP_LEVITATION,//hold/duration + 50,//FP_SPEED,//duration + 20,//FP_PUSH,//hold/duration + 20,//FP_PULL,//hold/duration + 20,//FP_TELEPATHY,//instant + 30,//FP_GRIP,//hold/duration + 1,//FP_LIGHTNING,//hold/duration + 50,//FP_RAGE,//duration + 50,//FP_PROTECT,//duration + 50,//FP_ABSORB,//duration + 50,//FP_TEAM_HEAL,//instant + 50,//FP_TEAM_FORCE,//instant + 20,//FP_DRAIN,//hold/duration + 20,//FP_SEE,//duration + 0,//FP_SABER_OFFENSE, + 2,//FP_SABER_DEFENSE, + 20//FP_SABERTHROW, + //NUM_FORCE_POWERS + }, + { + 60,//FP_HEAL,//instant + 10,//FP_LEVITATION,//hold/duration + 50,//FP_SPEED,//duration + 20,//FP_PUSH,//hold/duration + 20,//FP_PULL,//hold/duration + 20,//FP_TELEPATHY,//instant + 30,//FP_GRIP,//hold/duration + 1,//FP_LIGHTNING,//hold/duration + 50,//FP_RAGE,//duration + 25,//FP_PROTECT,//duration + 25,//FP_ABSORB,//duration + 33,//FP_TEAM_HEAL,//instant + 33,//FP_TEAM_FORCE,//instant + 20,//FP_DRAIN,//hold/duration + 20,//FP_SEE,//duration + 0,//FP_SABER_OFFENSE, + 1,//FP_SABER_DEFENSE, + 20//FP_SABERTHROW, + //NUM_FORCE_POWERS + }, + { + 50,//FP_HEAL,//instant //You get 5 points of health.. for 50 force points! + 10,//FP_LEVITATION,//hold/duration + 50,//FP_SPEED,//duration + 20,//FP_PUSH,//hold/duration + 20,//FP_PULL,//hold/duration + 20,//FP_TELEPATHY,//instant + 60,//FP_GRIP,//hold/duration + 1,//FP_LIGHTNING,//hold/duration + 50,//FP_RAGE,//duration + 10,//FP_PROTECT,//duration + 10,//FP_ABSORB,//duration + 25,//FP_TEAM_HEAL,//instant + 25,//FP_TEAM_FORCE,//instant + 20,//FP_DRAIN,//hold/duration + 20,//FP_SEE,//duration + 0,//FP_SABER_OFFENSE, + 0,//FP_SABER_DEFENSE, + 20//FP_SABERTHROW, + //NUM_FORCE_POWERS + } +}; + +float forceJumpHeight[NUM_FORCE_POWER_LEVELS] = +{ + 32,//normal jump (+stepheight+crouchdiff = 66) + 96,//(+stepheight+crouchdiff = 130) + 192,//(+stepheight+crouchdiff = 226) + 384//(+stepheight+crouchdiff = 418) +}; + +float forceJumpStrength[NUM_FORCE_POWER_LEVELS] = +{ + JUMP_VELOCITY,//normal jump + 420, + 590, + 840 +}; + +//rww - Get a pointer to the bgEntity by the index +bgEntity_t *PM_BGEntForNum( int num ) +{ + bgEntity_t *ent; + + if (!pm) + { + assert(!"You cannot call PM_BGEntForNum outside of pm functions!"); + return NULL; + } + + if (!pm->baseEnt) + { + assert(!"Base entity address not set"); + return NULL; + } + + if (!pm->entSize) + { + assert(!"sizeof(ent) is 0, impossible (not set?)"); + return NULL; + } + + assert(num >= 0 && num < MAX_GENTITIES); + + ent = (bgEntity_t *)((byte *)pm->baseEnt + pm->entSize*(num)); + + return ent; +} + +qboolean BG_SabersOff( playerState_t *ps ) +{ + if ( !ps->saberHolstered ) + { + return qfalse; + } + if ( ps->fd.saberAnimLevelBase == SS_DUAL + || ps->fd.saberAnimLevelBase == SS_STAFF ) + { + if ( ps->saberHolstered < 2 ) + { + return qfalse; + } + } + return qtrue; +} + +qboolean BG_KnockDownable(playerState_t *ps) +{ + if (!ps) + { //just for safety + return qfalse; + } + + if (ps->m_iVehicleNum) + { //riding a vehicle, don't knock me down + return qfalse; + } + + if (ps->emplacedIndex) + { //using emplaced gun or eweb, can't be knocked down + return qfalse; + } + + //ok, I guess? + return qtrue; +} + +//I should probably just do a global inline sometime. +#ifndef __LCC__ +#define PM_INLINE ID_INLINE +#else +#define PM_INLINE //none +#endif + +//hacky assumption check, assume any client non-humanoid is a rocket trooper +qboolean PM_INLINE PM_IsRocketTrooper(void) +{ + /* + if (pm->ps->clientNum < MAX_CLIENTS && + pm->gametype == GT_SIEGE && + pm->nonHumanoid) + { + return qtrue; + } + */ + + return qfalse; +} + +int PM_GetSaberStance(void) +{ + int anim = BOTH_STAND2; + + if (!pm->ps->saberEntityNum) + { //lost it + return BOTH_STAND1; + } + + if ( BG_SabersOff( pm->ps ) ) + { + return BOTH_STAND1; + } + + switch ( pm->ps->fd.saberAnimLevel ) + { + case SS_DUAL: + anim = BOTH_SABERDUAL_STANCE; + break; + case SS_STAFF: + anim = BOTH_SABERSTAFF_STANCE; + break; + case FORCE_LEVEL_1: + case FORCE_LEVEL_5: + anim = BOTH_SABERFAST_STANCE; + break; + case FORCE_LEVEL_3: + anim = BOTH_SABERSLOW_STANCE; + break; + case FORCE_LEVEL_0: + case FORCE_LEVEL_2: + case FORCE_LEVEL_4: + default: + anim = BOTH_STAND2; + break; + } + return anim; +} + +qboolean PM_DoSlowFall(void) +{ + if ( ( (pm->ps->legsAnim) == BOTH_WALL_RUN_RIGHT || (pm->ps->legsAnim) == BOTH_WALL_RUN_LEFT ) && pm->ps->legsTimer > 500 ) + { + return qtrue; + } + + return qfalse; +} + +//begin vehicle functions crudely ported from sp -rww +/* +==================================================================== +void pitch_roll_for_slope (edict_t *forwhom, vec3_t *slope, vec3_t storeAngles ) + +MG + +This will adjust the pitch and roll of a monster to match +a given slope - if a non-'0 0 0' slope is passed, it will +use that value, otherwise it will use the ground underneath +the monster. If it doesn't find a surface, it does nothinh\g +and returns. +==================================================================== +*/ + +void PM_pitch_roll_for_slope( bgEntity_t *forwhom, vec3_t pass_slope, vec3_t storeAngles ) +{ + vec3_t slope; + vec3_t nvf, ovf, ovr, startspot, endspot, new_angles = { 0, 0, 0 }; + float pitch, mod, dot; + + //if we don't have a slope, get one + if( !pass_slope || VectorCompare( vec3_origin, pass_slope ) ) + { + trace_t trace; + + VectorCopy( pm->ps->origin, startspot ); + startspot[2] += pm->mins[2] + 4; + VectorCopy( startspot, endspot ); + endspot[2] -= 300; + pm->trace( &trace, pm->ps->origin, vec3_origin, vec3_origin, endspot, forwhom->s.number, MASK_SOLID ); +// if(trace_fraction>0.05&&forwhom.movetype==MOVETYPE_STEP) +// forwhom.flags(-)FL_ONGROUND; + + if ( trace.fraction >= 1.0 ) + return; + + if( !( &trace.plane ) ) + return; + + if ( VectorCompare( vec3_origin, trace.plane.normal ) ) + return; + + VectorCopy( trace.plane.normal, slope ); + } + else + { + VectorCopy( pass_slope, slope ); + } + + if ( forwhom->s.NPC_class == CLASS_VEHICLE ) + {//special code for vehicles + Vehicle_t *pVeh = forwhom->m_pVehicle; + vec3_t tempAngles; + + tempAngles[PITCH] = tempAngles[ROLL] = 0; + tempAngles[YAW] = pVeh->m_vOrientation[YAW]; + AngleVectors( tempAngles, ovf, ovr, NULL ); + } + else + { + AngleVectors( pm->ps->viewangles, ovf, ovr, NULL ); + } + + vectoangles( slope, new_angles ); + pitch = new_angles[PITCH] + 90; + new_angles[ROLL] = new_angles[PITCH] = 0; + + AngleVectors( new_angles, nvf, NULL, NULL ); + + mod = DotProduct( nvf, ovr ); + + if ( mod<0 ) + mod = -1; + else + mod = 1; + + dot = DotProduct( nvf, ovf ); + + if ( storeAngles ) + { + storeAngles[PITCH] = dot * pitch; + storeAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + } + else //if ( forwhom->client ) + { + float oldmins2; + + pm->ps->viewangles[PITCH] = dot * pitch; + pm->ps->viewangles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + oldmins2 = pm->mins[2]; + pm->mins[2] = -24 + 12 * fabs(pm->ps->viewangles[PITCH])/180.0f; + //FIXME: if it gets bigger, move up + if ( oldmins2 > pm->mins[2] ) + {//our mins is now lower, need to move up + //FIXME: trace? + pm->ps->origin[2] += (oldmins2 - pm->mins[2]); + //forwhom->currentOrigin[2] = forwhom->client->ps.origin[2]; + //gi.linkentity( forwhom ); + } + } + /* + else + { + forwhom->currentAngles[PITCH] = dot * pitch; + forwhom->currentAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + } + */ +} + +#define FLY_NONE 0 +#define FLY_NORMAL 1 +#define FLY_VEHICLE 2 +#define FLY_HOVER 3 +static int pm_flying = FLY_NONE; + +void PM_SetSpecialMoveValues (void) +{ + bgEntity_t *pEnt; + + if (pm->ps->clientNum < MAX_CLIENTS) + { //we know that real players aren't vehs + pm_flying = FLY_NONE; + return; + } + + //default until we decide otherwise + pm_flying = FLY_NONE; + + pEnt = pm_entSelf; + + if ( pEnt ) + { + if ( (pm->ps->eFlags2&EF2_FLYING) )// pm->gent->client->moveType == MT_FLYSWIM ) + { + pm_flying = FLY_NORMAL; + } + else if ( pEnt->s.NPC_class == CLASS_VEHICLE ) + { + if ( pEnt->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + { + pm_flying = FLY_VEHICLE; + } + else if ( pEnt->m_pVehicle->m_pVehicleInfo->hoverHeight > 0 ) + { + pm_flying = FLY_HOVER; + } + } + } +} + +static void PM_SetVehicleAngles( vec3_t normal ) +{ + bgEntity_t *pEnt = pm_entSelf; + Vehicle_t *pVeh; + vec3_t vAngles; + float vehicleBankingSpeed; + float pitchBias; + int i; + + if ( !pEnt || pEnt->s.NPC_class != CLASS_VEHICLE ) + { + return; + } + + pVeh = pEnt->m_pVehicle; + + //float curVehicleBankingSpeed; + vehicleBankingSpeed = (pVeh->m_pVehicleInfo->bankingSpeed*32.0f)*pml.frametime;//0.25f + + if ( vehicleBankingSpeed <= 0 + || ( pVeh->m_pVehicleInfo->pitchLimit == 0 && pVeh->m_pVehicleInfo->rollLimit == 0 ) ) + {//don't bother, this vehicle doesn't bank + return; + } + //FIXME: do 3 traces to define a plane and use that... smoothes it out some, too... + //pitch_roll_for_slope( pm->gent, normal, vAngles ); + //FIXME: maybe have some pitch control in water and/or air? + + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + pitchBias = 0.0f; + } + else + { + //FIXME: gravity does not matter in SPACE!!! + //center of gravity affects pitch in air/water (FIXME: what about roll?) + pitchBias = 90.0f*pVeh->m_pVehicleInfo->centerOfGravity[0];//if centerOfGravity is all the way back (-1.0f), vehicle pitches up 90 degrees when in air + } + + VectorClear( vAngles ); + if ( pm->waterlevel > 0 ) + {//in water + //view pitch has some influence when in water + //FIXME: take center of gravity into account? + vAngles[PITCH] += (pm->ps->viewangles[PITCH]-vAngles[PITCH])*0.75f + (pitchBias*0.5); + } + else if ( normal ) + {//have a valid surface below me + PM_pitch_roll_for_slope( pEnt, normal, vAngles ); + if ( (pml.groundTrace.contents&(CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) ) + {//on water + //view pitch has some influence when on a fluid surface + //FIXME: take center of gravity into account + vAngles[PITCH] += (pm->ps->viewangles[PITCH]-vAngles[PITCH])*0.5f + (pitchBias*0.5f); + } + } + else + {//in air, let pitch match view...? + //FIXME: take center of gravity into account + vAngles[PITCH] = pm->ps->viewangles[PITCH]*0.5f + pitchBias; + //don't bank so fast when in the air + vehicleBankingSpeed *= (0.125f*pml.frametime); + } + //NOTE: if angles are flat and we're moving through air (not on ground), + // then pitch/bank? + if ( pVeh->m_pVehicleInfo->rollLimit > 0 ) + { + //roll when banking + vec3_t velocity; + float speed; + VectorCopy( pm->ps->velocity, velocity ); + velocity[2] = 0.0f; + speed = VectorNormalize( velocity ); + if ( speed > 32.0f || speed < -32.0f ) + { + vec3_t rt, tempVAngles; + float side; + float dp; + + // Magic number fun! Speed is used for banking, so modulate the speed by a sine wave + //FIXME: this banks too early + speed *= sin( (150 + pml.frametime) * 0.003 ); + + // Clamp to prevent harsh rolling + if ( speed > 60 ) + speed = 60; + + VectorCopy( pVeh->m_vOrientation, tempVAngles ); + tempVAngles[ROLL] = 0; + AngleVectors( tempVAngles, NULL, rt, NULL ); + dp = DotProduct( velocity, rt ); + side = speed * dp; + vAngles[ROLL] -= side; + } + } + + //cap + if ( pVeh->m_pVehicleInfo->pitchLimit != -1 ) + { + if ( vAngles[PITCH] > pVeh->m_pVehicleInfo->pitchLimit ) + { + vAngles[PITCH] = pVeh->m_pVehicleInfo->pitchLimit; + } + else if ( vAngles[PITCH] < -pVeh->m_pVehicleInfo->pitchLimit ) + { + vAngles[PITCH] = -pVeh->m_pVehicleInfo->pitchLimit; + } + } + + if ( vAngles[ROLL] > pVeh->m_pVehicleInfo->rollLimit ) + { + vAngles[ROLL] = pVeh->m_pVehicleInfo->rollLimit; + } + else if ( vAngles[ROLL] < -pVeh->m_pVehicleInfo->rollLimit ) + { + vAngles[ROLL] = -pVeh->m_pVehicleInfo->rollLimit; + } + + //do it + for ( i = 0; i < 3; i++ ) + { + if ( i == YAW ) + {//yawing done elsewhere + continue; + } + //bank faster the higher the difference is + /* + else if ( i == PITCH ) + { + curVehicleBankingSpeed = vehicleBankingSpeed*fabs(AngleNormalize180(AngleSubtract( vAngles[PITCH], pVeh->m_vOrientation[PITCH] )))/(g_vehicleInfo[pm->ps->vehicleIndex].pitchLimit/2.0f); + } + else if ( i == ROLL ) + { + curVehicleBankingSpeed = vehicleBankingSpeed*fabs(AngleNormalize180(AngleSubtract( vAngles[ROLL], pVeh->m_vOrientation[ROLL] )))/(g_vehicleInfo[pm->ps->vehicleIndex].rollLimit/2.0f); + } + + if ( curVehicleBankingSpeed ) + */ + { + if ( pVeh->m_vOrientation[i] >= vAngles[i] + vehicleBankingSpeed ) + { + pVeh->m_vOrientation[i] -= vehicleBankingSpeed; + } + else if ( pVeh->m_vOrientation[i] <= vAngles[i] - vehicleBankingSpeed ) + { + pVeh->m_vOrientation[i] += vehicleBankingSpeed; + } + else + { + pVeh->m_vOrientation[i] = vAngles[i]; + } + } + } +} + +#ifndef QAGAME +extern vmCvar_t cg_paused; +#endif + +void BG_ExternThisSoICanRecompileInDebug( Vehicle_t *pVeh, playerState_t *riderPS ) +{ +/* + float pitchSubtract, pitchDelta, yawDelta; + //Com_Printf( S_COLOR_RED"PITCH: %4.2f, YAW: %4.2f, ROLL: %4.2f\n", riderPS->viewangles[0],riderPS->viewangles[1],riderPS->viewangles[2]); + yawDelta = AngleSubtract(riderPS->viewangles[YAW],pVeh->m_vPrevRiderViewAngles[YAW]); +#ifndef QAGAME + if ( !cg_paused.integer ) + { + //Com_Printf( "%d - yawDelta %4.2f\n", pm->cmd.serverTime, yawDelta ); + } +#endif + yawDelta *= (4.0f*pVeh->m_fTimeModifier); + pVeh->m_vOrientation[ROLL] -= yawDelta; + + pitchDelta = AngleSubtract(riderPS->viewangles[PITCH],pVeh->m_vPrevRiderViewAngles[PITCH]); + pitchDelta *= (2.0f*pVeh->m_fTimeModifier); + pitchSubtract = pitchDelta * (fabs(pVeh->m_vOrientation[ROLL])/90.0f); + pVeh->m_vOrientation[PITCH] += pitchDelta-pitchSubtract; + if ( pVeh->m_vOrientation[ROLL] > 0 ) + { + pVeh->m_vOrientation[YAW] += pitchSubtract; + } + else + { + pVeh->m_vOrientation[YAW] -= pitchSubtract; + } + pVeh->m_vOrientation[PITCH] = AngleNormalize180( pVeh->m_vOrientation[PITCH] ); + pVeh->m_vOrientation[YAW] = AngleNormalize360( pVeh->m_vOrientation[YAW] ); + pVeh->m_vOrientation[ROLL] = AngleNormalize180( pVeh->m_vOrientation[ROLL] ); + + VectorCopy( riderPS->viewangles, pVeh->m_vPrevRiderViewAngles ); +*/ +} + +void BG_VehicleTurnRateForSpeed( Vehicle_t *pVeh, float speed, float *mPitchOverride, float *mYawOverride ) +{ + if ( pVeh && pVeh->m_pVehicleInfo ) + { + float speedFrac = 1.0f; + if ( pVeh->m_pVehicleInfo->speedDependantTurning ) + { + if ( pVeh->m_LandTrace.fraction >= 1.0f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + { + speedFrac = (speed/(pVeh->m_pVehicleInfo->speedMax*0.75f)); + if ( speedFrac < 0.25f ) + { + speedFrac = 0.25f; + } + else if ( speedFrac > 1.0f ) + { + speedFrac = 1.0f; + } + } + } + if ( pVeh->m_pVehicleInfo->mousePitch ) + { + *mPitchOverride = pVeh->m_pVehicleInfo->mousePitch*speedFrac; + } + if ( pVeh->m_pVehicleInfo->mouseYaw ) + { + *mYawOverride = pVeh->m_pVehicleInfo->mouseYaw*speedFrac; + } + } +} + +#include "../namespace_end.h" + +// Following couple things don't belong in the DLL namespace! +#ifdef QAGAME +typedef struct gentity_s gentity_t; +gentity_t *G_PlayEffectID(const int fxID, vec3_t org, vec3_t ang); +#endif + +#include "../namespace_begin.h" + +static void PM_GroundTraceMissed( void ); +void PM_HoverTrace( void ) +{ + Vehicle_t *pVeh; + float hoverHeight; + vec3_t point, vAng, fxAxis[3]; + trace_t *trace; + float relativeWaterLevel; + + bgEntity_t *pEnt = pm_entSelf; + if ( !pEnt || pEnt->s.NPC_class != CLASS_VEHICLE ) + { + return; + } + + pVeh = pEnt->m_pVehicle; + hoverHeight = pVeh->m_pVehicleInfo->hoverHeight; + trace = &pml.groundTrace; + + pml.groundPlane = qfalse; + + //relativeWaterLevel = (pm->ps->waterheight - (pm->ps->origin[2]+pm->mins[2])); + relativeWaterLevel = pm->waterlevel; //I.. guess this works + if ( pm->waterlevel && relativeWaterLevel >= 0 ) + {//in water + if ( pVeh->m_pVehicleInfo->bouyancy <= 0.0f ) + {//sink like a rock + } + else + {//rise up + float floatHeight = (pVeh->m_pVehicleInfo->bouyancy * ((pm->maxs[2]-pm->mins[2])*0.5f)) - (hoverHeight*0.5f);//1.0f should make you float half-in, half-out of water + if ( relativeWaterLevel > floatHeight ) + {//too low, should rise up + pm->ps->velocity[2] += (relativeWaterLevel - floatHeight) * pVeh->m_fTimeModifier; + } + } + //if ( pm->ps->waterheight < pm->ps->origin[2]+pm->maxs[2] ) + if (pm->waterlevel <= 1) + {//part of us is sticking out of water + if ( fabs(pm->ps->velocity[0]) + fabs(pm->ps->velocity[1]) > 100 ) + {//moving at a decent speed + if ( Q_irand( pml.frametime, 100 ) >= 50 ) + {//splash + vec3_t wakeOrg; + + vAng[PITCH] = vAng[ROLL] = 0; + vAng[YAW] = pVeh->m_vOrientation[YAW]; + AngleVectors( vAng, fxAxis[2], fxAxis[1], fxAxis[0] ); + VectorCopy( pm->ps->origin, wakeOrg ); + //wakeOrg[2] = pm->ps->waterheight; + if (pm->waterlevel >= 2) + { + wakeOrg[2] = pm->ps->origin[2]+16; + } + else + { + wakeOrg[2] = pm->ps->origin[2]; + } +#ifdef QAGAME //yeah, this is kind of crappy and makes no use of prediction whatsoever + if ( pVeh->m_pVehicleInfo->iWakeFX ) + { + //G_PlayEffectID( pVeh->m_pVehicleInfo->iWakeFX, wakeOrg, fxAxis[0] ); + //tempent use bad! + G_AddEvent((gentity_t *)pEnt, EV_PLAY_EFFECT_ID, pVeh->m_pVehicleInfo->iWakeFX); + } +#endif + } + } + } + } + else + { + int traceContents; + float minNormal = (float)MIN_WALK_NORMAL; + minNormal = pVeh->m_pVehicleInfo->maxSlope; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - hoverHeight; + + //FIXME: check for water, too? If over water, go slower and make wave effect + // If *in* water, go really slow and use bouyancy stat to determine how far below surface to float + + //NOTE: if bouyancy is 2.0f or higher, you float over water like it's solid ground. + // if it's 1.0f, you sink halfway into water. If it's 0, you sink... + traceContents = pm->tracemask; + if ( pVeh->m_pVehicleInfo->bouyancy >= 2.0f ) + {//sit on water + traceContents |= (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); + } + pm->trace( trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, traceContents ); + if (trace->plane.normal[0] > 0.5f || trace->plane.normal[0] < -0.5f || + trace->plane.normal[1] > 0.5f || trace->plane.normal[1] < -0.5f) + { //steep slanted hill, don't go up it. + float d = fabs(trace->plane.normal[0]); + float e = fabs(trace->plane.normal[1]); + if (e > d) + { + d = e; + } + pm->ps->velocity[2] = -300.0f*d; + } + else if ( trace->plane.normal[2] >= minNormal ) + {//not a steep slope, so push us up + if ( trace->fraction < 1.0f ) + {//push up off ground + float hoverForce = pVeh->m_pVehicleInfo->hoverStrength; + if ( trace->fraction > 0.5f ) + { + pm->ps->velocity[2] += (1.0f-trace->fraction)*hoverForce*pVeh->m_fTimeModifier; + } + else + { + pm->ps->velocity[2] += (0.5f-(trace->fraction*trace->fraction))*hoverForce*2.0f*pVeh->m_fTimeModifier; + } + if ( (trace->contents&(CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) ) + {//hovering on water, make a spash if moving + if ( fabs(pm->ps->velocity[0]) + fabs(pm->ps->velocity[1]) > 100 ) + {//moving at a decent speed + if ( Q_irand( pml.frametime, 100 ) >= 50 ) + {//splash + vAng[PITCH] = vAng[ROLL] = 0; + vAng[YAW] = pVeh->m_vOrientation[YAW]; + AngleVectors( vAng, fxAxis[2], fxAxis[1], fxAxis[0] ); +#ifdef QAGAME + if ( pVeh->m_pVehicleInfo->iWakeFX ) + { + G_PlayEffectID( pVeh->m_pVehicleInfo->iWakeFX, trace->endpos, fxAxis[0] ); + } +#endif + } + } + } + pml.groundPlane = qtrue; + } + } + } + if ( pml.groundPlane ) + { + PM_SetVehicleAngles( pml.groundTrace.plane.normal ); + // We're on the ground. + pVeh->m_ulFlags &= ~VEH_FLYING; + + pVeh->m_vAngularVelocity = 0.0f; + } + else + { + PM_SetVehicleAngles( NULL ); + // We're flying in the air. + pVeh->m_ulFlags |= VEH_FLYING; + //groundTrace + + if (pVeh->m_vAngularVelocity==0.0f) + { + pVeh->m_vAngularVelocity = pVeh->m_vOrientation[YAW] - pVeh->m_vPrevOrientation[YAW]; + if (pVeh->m_vAngularVelocity<-15.0f) + { + pVeh->m_vAngularVelocity = -15.0f; + } + if (pVeh->m_vAngularVelocity> 15.0f) + { + pVeh->m_vAngularVelocity = 15.0f; + } + } + //pVeh->m_vAngularVelocity *= 0.95f; // Angular Velocity Decays Over Time + if (pVeh->m_vAngularVelocity > 0.0f) + { + pVeh->m_vAngularVelocity -= pml.frametime; + if (pVeh->m_vAngularVelocity < 0.0f) + { + pVeh->m_vAngularVelocity = 0.0f; + } + } + else if (pVeh->m_vAngularVelocity < 0.0f) + { + pVeh->m_vAngularVelocity += pml.frametime; + if (pVeh->m_vAngularVelocity > 0.0f) + { + pVeh->m_vAngularVelocity = 0.0f; + } + } + } + PM_GroundTraceMissed(); +} +//end vehicle functions crudely ported from sp -rww + +/* +=============== +PM_AddEvent + +=============== +*/ +void PM_AddEvent( int newEvent ) { + BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); +} + +void PM_AddEventWithParm( int newEvent, int parm ) +{ + BG_AddPredictableEventToPlayerstate( newEvent, parm, pm->ps ); +} + +/* +=============== +PM_AddTouchEnt +=============== +*/ +void PM_AddTouchEnt( int entityNum ) { + int i; + + if ( entityNum == ENTITYNUM_WORLD ) { + return; + } + if ( pm->numtouch == MAXTOUCH ) { + return; + } + + // see if it is already added + for ( i = 0 ; i < pm->numtouch ; i++ ) { + if ( pm->touchents[ i ] == entityNum ) { + return; + } + } + + // add it + pm->touchents[pm->numtouch] = entityNum; + pm->numtouch++; +} + + +/* +================== +PM_ClipVelocity + +Slide off of the impacting surface +================== +*/ +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { + float backoff; + float change; + float oldInZ; + int i; + + if ( (pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//no sliding! + VectorCopy( in, out ); + return; + } + oldInZ = in[2]; + + backoff = DotProduct (in, normal); + + if ( backoff < 0 ) { + backoff *= overbounce; + } else { + backoff /= overbounce; + } + + for ( i=0 ; i<3 ; i++ ) { + change = normal[i]*backoff; + out[i] = in[i] - change; + } + if ( pm->stepSlideFix ) + { + if ( pm->ps->clientNum < MAX_CLIENTS//normal player + && pm->ps->groundEntityNum != ENTITYNUM_NONE//on the ground + && normal[2] < MIN_WALK_NORMAL )//sliding against a steep slope + {//if walking on the ground, don't slide up slopes that are too steep to walk on + out[2] = oldInZ; + } + } +} + + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +static void PM_Friction( void ) { + vec3_t vec; + float *vel; + float speed, newspeed, control; + float drop; + bgEntity_t *pEnt = NULL; + + vel = pm->ps->velocity; + + VectorCopy( vel, vec ); + if ( pml.walking ) { + vec[2] = 0; // ignore slope movement + } + + speed = VectorLength(vec); + if (speed < 1) { + vel[0] = 0; + vel[1] = 0; // allow sinking underwater + if (pm->ps->pm_type == PM_SPECTATOR) + { + vel[2] = 0; + } + // FIXME: still have z friction underwater? + return; + } + + drop = 0; + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + pEnt = pm_entSelf; + } + + // apply ground friction, even if on ladder + if (pm_flying != FLY_VEHICLE && + pEnt && + pEnt->s.NPC_class == CLASS_VEHICLE && + pEnt->m_pVehicle && + pEnt->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL && + pEnt->m_pVehicle->m_pVehicleInfo->type != VH_WALKER && + pEnt->m_pVehicle->m_pVehicleInfo->friction ) + { + float friction = pEnt->m_pVehicle->m_pVehicleInfo->friction; + if ( !(pm->ps->pm_flags & PMF_TIME_KNOCKBACK) /*&& !(pm->ps->pm_flags & PMF_TIME_NOFRICTION)*/ ) + { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*friction*pml.frametime; + /* + if ( Flying == FLY_HOVER ) + { + if ( pm->cmd.rightmove ) + {//if turning, increase friction + control *= 2.0f; + } + if ( pm->ps->groundEntityNum < ENTITYNUM_NONE ) + {//on the ground + drop += control*friction*pml.frametime; + } + else if ( pml.groundPlane ) + {//on a slope + drop += control*friction*2.0f*pml.frametime; + } + else + {//in air + drop += control*2.0f*friction*pml.frametime; + } + } + */ + } + } + else if ( pm_flying != FLY_NORMAL && pm_flying != FLY_VEHICLE ) + { + // apply ground friction + if ( pm->waterlevel <= 1 ) { + if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) { + // if getting knocked back, no friction + if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*pm_friction*pml.frametime; + } + } + } + } + + if ( pm_flying == FLY_VEHICLE ) + { + if ( !(pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) + { + control = speed;// < pm_stopspeed ? pm_stopspeed : speed; + drop += control*pm_friction*pml.frametime; + } + } + + // apply water friction even if just wading + if ( pm->waterlevel ) { + drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; + } + // If on a client then there is no friction + else if ( pm->ps->groundEntityNum < MAX_CLIENTS ) + { + drop = 0; + } + + if ( pm->ps->pm_type == PM_SPECTATOR || pm->ps->pm_type == PM_FLOAT ) + { + if (pm->ps->pm_type == PM_FLOAT) + { //almost no friction while floating + drop += speed*0.1*pml.frametime; + } + else + { + drop += speed*pm_spectatorfriction*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 +============== +*/ +static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) +{ + if (pm->gametype != GT_SIEGE + || pm->ps->m_iVehicleNum + || pm->ps->clientNum >= MAX_CLIENTS + || pm->ps->pm_type != PM_NORMAL) + { //standard method, allows "bunnyhopping" and whatnot + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct (pm->ps->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0 && pm->ps->clientNum < MAX_CLIENTS) { + return; + } + + if (addspeed < 0) + { + accelspeed = (-accel)*pml.frametime*wishspeed; + if (accelspeed < addspeed) { + accelspeed = addspeed; + } + } + else + { + accelspeed = accel*pml.frametime*wishspeed; + if (accelspeed > addspeed) { + accelspeed = addspeed; + } + } + + for (i=0 ; i<3 ; i++) { + pm->ps->velocity[i] += accelspeed*wishdir[i]; + } + } + else + { //use the proper way for siege + vec3_t wishVelocity; + vec3_t pushDir; + float pushLen; + float canPush; + + VectorScale( wishdir, wishspeed, wishVelocity ); + VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); + pushLen = VectorNormalize( pushDir ); + + canPush = accel*pml.frametime*wishspeed; + if (canPush > pushLen) { + canPush = pushLen; + } + + VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); + } +} + + + +/* +============ +PM_CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +static float PM_CmdScale( usercmd_t *cmd ) { + int max; + float total; + float scale; + int umove = 0; //cmd->upmove; + //don't factor upmove into scaling speed + + max = abs( cmd->forwardmove ); + if ( abs( cmd->rightmove ) > max ) { + max = abs( cmd->rightmove ); + } + if ( abs( umove ) > max ) { + max = abs( umove ); + } + if ( !max ) { + return 0; + } + + total = sqrt( (float)(cmd->forwardmove * cmd->forwardmove + + cmd->rightmove * cmd->rightmove + umove * umove) ); + scale = (float)pm->ps->speed * max / ( 127.0 * total ); + + return scale; +} + + +/* +================ +PM_SetMovementDir + +Determine the rotation of the legs reletive +to the facing dir +================ +*/ +static void PM_SetMovementDir( void ) { + if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { + if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 0; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 1; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 2; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 3; + } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 4; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 5; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 6; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 7; + } + } else { + // if they aren't actively going directly sideways, + // change the animation to the diagonal so they + // don't stop too crooked + if ( pm->ps->movementDir == 2 ) { + pm->ps->movementDir = 1; + } else if ( pm->ps->movementDir == 6 ) { + pm->ps->movementDir = 7; + } + } +} + +#define METROID_JUMP 1 + +qboolean PM_ForceJumpingUp(void) +{ + if ( !(pm->ps->fd.forcePowersActive&(1<ps->fd.forceJumpCharge ) + {//already jumped and let go + return qfalse; + } + + if ( BG_InSpecialJump( pm->ps->legsAnim ) ) + { + return qfalse; + } + + if (BG_SaberInSpecial(pm->ps->saberMove)) + { + return qfalse; + } + + if (BG_SaberInSpecialAttack(pm->ps->legsAnim)) + { + return qfalse; + } + + if (BG_HasYsalamiri(pm->gametype, pm->ps)) + { + return qfalse; + } + + if (!BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION)) + { + return qfalse; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE && //in air + (pm->ps->pm_flags & PMF_JUMP_HELD) && //jumped + pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 && //force-jump capable + pm->ps->velocity[2] > 0 )//going up + { + return qtrue; + } + return qfalse; +} + +static void PM_JumpForDir( void ) +{ + int anim = BOTH_JUMP1; + if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_JUMP1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_JUMPBACK1; + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_JUMPRIGHT1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_JUMPLEFT1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + anim = BOTH_JUMP1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + if(!BG_InDeathAnim(pm->ps->legsAnim)) + { + PM_SetAnim(SETANIM_LEGS,anim,SETANIM_FLAG_OVERRIDE, 100); + } +} + +void PM_SetPMViewAngle(playerState_t *ps, vec3_t angle, usercmd_t *ucmd) +{ + int i; + + for (i=0 ; i<3 ; i++) + { // set the delta angle + int cmdAngle; + + cmdAngle = ANGLE2SHORT(angle[i]); + ps->delta_angles[i] = cmdAngle - ucmd->angles[i]; + } + VectorCopy (angle, ps->viewangles); +} + +qboolean PM_AdjustAngleForWallRun( playerState_t *ps, usercmd_t *ucmd, qboolean doMove ) +{ + if (( (ps->legsAnim) == BOTH_WALL_RUN_RIGHT || (ps->legsAnim) == BOTH_WALL_RUN_LEFT ) && ps->legsTimer > 500 ) + {//wall-running and not at end of anim + //stick to wall, if there is one + vec3_t fwd, rt, traceTo, mins, maxs, fwdAngles; + trace_t trace; + float dist, yawAdjust; + + VectorSet(mins, -15, -15, 0); + VectorSet(maxs, 15, 15, 24); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( fwdAngles, fwd, rt, NULL ); + if ( (ps->legsAnim) == BOTH_WALL_RUN_RIGHT ) + { + dist = 128; + yawAdjust = -90; + } + else + { + dist = -128; + yawAdjust = 90; + } + VectorMA( ps->origin, dist, rt, traceTo ); + + pm->trace( &trace, ps->origin, mins, maxs, traceTo, ps->clientNum, MASK_PLAYERSOLID ); + + if ( trace.fraction < 1.0f + && (trace.plane.normal[2] >= 0.0f && trace.plane.normal[2] <= 0.4f) )//&& ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) + { + trace_t trace2; + vec3_t traceTo2; + vec3_t wallRunFwd, wallRunAngles; + + VectorClear( wallRunAngles ); + wallRunAngles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust; + AngleVectors( wallRunAngles, wallRunFwd, NULL, NULL ); + + VectorMA( pm->ps->origin, 32, wallRunFwd, traceTo2 ); + pm->trace( &trace2, pm->ps->origin, mins, maxs, traceTo2, pm->ps->clientNum, MASK_PLAYERSOLID ); + if ( trace2.fraction < 1.0f && DotProduct( trace2.plane.normal, wallRunFwd ) <= -0.999f ) + {//wall we can't run on in front of us + trace.fraction = 1.0f;//just a way to get it to kick us off the wall below + } + } + + if ( trace.fraction < 1.0f + && (trace.plane.normal[2] >= 0.0f&&trace.plane.normal[2] <= 0.4f/*MAX_WALL_RUN_Z_NORMAL*/) ) + {//still a wall there + if ( (ps->legsAnim) == BOTH_WALL_RUN_RIGHT ) + { + ucmd->rightmove = 127; + } + else + { + ucmd->rightmove = -127; + } + if ( ucmd->upmove < 0 ) + { + ucmd->upmove = 0; + } + //make me face perpendicular to the wall + ps->viewangles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust; + + PM_SetPMViewAngle(ps, ps->viewangles, ucmd); + + ucmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - ps->delta_angles[YAW]; + if ( doMove ) + { + //push me forward + float zVel = ps->velocity[2]; + if ( ps->legsTimer > 500 ) + {//not at end of anim yet + float speed = 175; + if ( ucmd->forwardmove < 0 ) + {//slower + speed = 100; + } + else if ( ucmd->forwardmove > 0 ) + { + speed = 250;//running speed + } + VectorScale( fwd, speed, ps->velocity ); + } + ps->velocity[2] = zVel;//preserve z velocity + //pull me toward the wall, too + VectorMA( ps->velocity, dist, rt, ps->velocity ); + } + ucmd->forwardmove = 0; + return qtrue; + } + else if ( doMove ) + {//stop it + if ( (ps->legsAnim) == BOTH_WALL_RUN_RIGHT ) + { + PM_SetAnim(SETANIM_BOTH, BOTH_WALL_RUN_RIGHT_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + else if ( (ps->legsAnim) == BOTH_WALL_RUN_LEFT ) + { + PM_SetAnim(SETANIM_BOTH, BOTH_WALL_RUN_LEFT_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + } + } + + return qfalse; +} + +qboolean PM_AdjustAnglesForWallRunUpFlipAlt( usercmd_t *ucmd ) +{ +// ucmd->angles[PITCH] = ANGLE2SHORT( pm->ps->viewangles[PITCH] ) - pm->ps->delta_angles[PITCH]; +// ucmd->angles[YAW] = ANGLE2SHORT( pm->ps->viewangles[YAW] ) - pm->ps->delta_angles[YAW]; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, ucmd); + return qtrue; +} + +qboolean PM_AdjustAngleForWallRunUp( playerState_t *ps, usercmd_t *ucmd, qboolean doMove ) +{ + if ( ps->legsAnim == BOTH_FORCEWALLRUNFLIP_START ) + {//wall-running up + //stick to wall, if there is one + vec3_t fwd, traceTo, mins, maxs, fwdAngles; + trace_t trace; + float dist = 128; + + VectorSet(mins, -15,-15,0); + VectorSet(maxs, 15,15,24); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + VectorMA( ps->origin, dist, fwd, traceTo ); + pm->trace( &trace, ps->origin, mins, maxs, traceTo, ps->clientNum, MASK_PLAYERSOLID ); + if ( trace.fraction > 0.5f ) + {//hmm, some room, see if there's a floor right here + trace_t trace2; + vec3_t top, bottom; + + VectorCopy( trace.endpos, top ); + top[2] += (pm->mins[2]*-1) + 4.0f; + VectorCopy( top, bottom ); + bottom[2] -= 64.0f; + pm->trace( &trace2, top, pm->mins, pm->maxs, bottom, ps->clientNum, MASK_PLAYERSOLID ); + if ( !trace2.allsolid + && !trace2.startsolid + && trace2.fraction < 1.0f + && trace2.plane.normal[2] > 0.7f )//slope we can stand on + {//cool, do the alt-flip and land on whetever it is we just scaled up + VectorScale( fwd, 100, pm->ps->velocity ); + pm->ps->velocity[2] += 400; + PM_SetAnim(SETANIM_BOTH, BOTH_FORCEWALLRUNFLIP_ALT, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + pm->ps->pm_flags |= PMF_JUMP_HELD; + //ent->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + //ent->client->ps.forcePowersActive |= (1<upmove = 0; + return qfalse; + } + } + + if ( //ucmd->upmove <= 0 && + ps->legsTimer > 0 && + ucmd->forwardmove > 0 && + trace.fraction < 1.0f && + (trace.plane.normal[2] >= 0.0f&&trace.plane.normal[2]<=0.4f/*MAX_WALL_RUN_Z_NORMAL*/) ) + {//still a vertical wall there + //make sure there's not a ceiling above us! + trace_t trace2; + VectorCopy( ps->origin, traceTo ); + traceTo[2] += 64; + pm->trace( &trace2, ps->origin, mins, maxs, traceTo, ps->clientNum, MASK_PLAYERSOLID ); + if ( trace2.fraction < 1.0f ) + {//will hit a ceiling, so force jump-off right now + //NOTE: hits any entity or clip brush in the way, too, not just architecture! + } + else + {//all clear, keep going + //FIXME: don't pull around 90 turns + //FIXME: simulate stepping up steps here, somehow? + ucmd->forwardmove = 127; + if ( ucmd->upmove < 0 ) + { + ucmd->upmove = 0; + } + //make me face the wall + ps->viewangles[YAW] = vectoyaw( trace.plane.normal )+180; + PM_SetPMViewAngle(ps, ps->viewangles, ucmd); + /* + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + */ + ucmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - ps->delta_angles[YAW]; + //if ( ent->s.number || !player_locked ) + if (1) //aslkfhsakf + { + if ( doMove ) + { + //pull me toward the wall + VectorScale( trace.plane.normal, -dist*trace.fraction, ps->velocity ); + //push me up + if ( ps->legsTimer > 200 ) + {//not at end of anim yet + float speed = 300; + /* + if ( ucmd->forwardmove < 0 ) + {//slower + speed = 100; + } + else if ( ucmd->forwardmove > 0 ) + { + speed = 250;//running speed + } + */ + ps->velocity[2] = speed;//preserve z velocity + } + } + } + ucmd->forwardmove = 0; + return qtrue; + } + } + //failed! + if ( doMove ) + {//stop it + VectorScale( fwd, -300.0f, ps->velocity ); + ps->velocity[2] += 200; + //NPC_SetAnim( ent, SETANIM_BOTH, BOTH_FORCEWALLRUNFLIP_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + //why?!?#?#@!%$R@$KR#F:Hdl;asfm + PM_SetAnim(SETANIM_BOTH, BOTH_FORCEWALLRUNFLIP_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + ps->pm_flags |= PMF_JUMP_HELD; + //ent->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + + //FIXME do I need this in mp? + //ent->client->ps.forcePowersActive |= (1<upmove = 0; + //return qtrue; + } + } + return qfalse; +} + +#define JUMP_OFF_WALL_SPEED 200.0f +//nice... +static float BG_ForceWallJumpStrength( void ) +{ + return (forceJumpStrength[FORCE_LEVEL_3]/2.5f); +} + +qboolean PM_AdjustAngleForWallJump( playerState_t *ps, usercmd_t *ucmd, qboolean doMove ) +{ + if ( ( ( BG_InReboundJump( ps->legsAnim ) || BG_InReboundHold( ps->legsAnim ) ) + && ( BG_InReboundJump( ps->torsoAnim ) || BG_InReboundHold( ps->torsoAnim ) ) ) + || (pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//hugging wall, getting ready to jump off + //stick to wall, if there is one + vec3_t checkDir, traceTo, mins, maxs, fwdAngles; + trace_t trace; + float dist = 128.0f, yawAdjust; + + VectorSet(mins, pm->mins[0],pm->mins[1],0); + VectorSet(maxs, pm->maxs[0],pm->maxs[1],24); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + switch ( ps->legsAnim ) + { + case BOTH_FORCEWALLREBOUND_RIGHT: + case BOTH_FORCEWALLHOLD_RIGHT: + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + yawAdjust = -90; + break; + case BOTH_FORCEWALLREBOUND_LEFT: + case BOTH_FORCEWALLHOLD_LEFT: + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + VectorScale( checkDir, -1, checkDir ); + yawAdjust = 90; + break; + case BOTH_FORCEWALLREBOUND_FORWARD: + case BOTH_FORCEWALLHOLD_FORWARD: + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + yawAdjust = 180; + break; + case BOTH_FORCEWALLREBOUND_BACK: + case BOTH_FORCEWALLHOLD_BACK: + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + VectorScale( checkDir, -1, checkDir ); + yawAdjust = 0; + break; + default: + //WTF??? + pm->ps->pm_flags &= ~PMF_STUCK_TO_WALL; + return qfalse; + break; + } + if ( pm->debugMelee ) + {//uber-skillz + if ( ucmd->upmove > 0 ) + {//hold on until you let go manually + if ( BG_InReboundHold( ps->legsAnim ) ) + {//keep holding + if ( ps->legsTimer < 150 ) + { + ps->legsTimer = 150; + } + } + else + {//if got to hold part of anim, play hold anim + if ( ps->legsTimer <= 300 ) + { + ps->saberHolstered = 2; + PM_SetAnim( SETANIM_BOTH, BOTH_FORCEWALLRELEASE_FORWARD+(ps->legsAnim-BOTH_FORCEWALLHOLD_FORWARD), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + ps->legsTimer = ps->torsoTimer = 150; + } + } + } + } + VectorMA( ps->origin, dist, checkDir, traceTo ); + pm->trace( &trace, ps->origin, mins, maxs, traceTo, ps->clientNum, MASK_PLAYERSOLID ); + if ( //ucmd->upmove <= 0 && + ps->legsTimer > 100 && + trace.fraction < 1.0f && + fabs(trace.plane.normal[2]) <= 0.2f/*MAX_WALL_GRAB_SLOPE*/ ) + {//still a vertical wall there + //FIXME: don't pull around 90 turns + /* + if ( ent->s.number || !player_locked ) + { + ucmd->forwardmove = 127; + } + */ + if ( ucmd->upmove < 0 ) + { + ucmd->upmove = 0; + } + //align me to the wall + ps->viewangles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust; + PM_SetPMViewAngle(ps, ps->viewangles, ucmd); + /* + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + */ + ucmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - ps->delta_angles[YAW]; + //if ( ent->s.number || !player_locked ) + if (1) + { + if ( doMove ) + { + //pull me toward the wall + VectorScale( trace.plane.normal, -128.0f, ps->velocity ); + } + } + ucmd->upmove = 0; + ps->pm_flags |= PMF_STUCK_TO_WALL; + return qtrue; + } + else if ( doMove + && (ps->pm_flags&PMF_STUCK_TO_WALL)) + {//jump off + //push off of it! + ps->pm_flags &= ~PMF_STUCK_TO_WALL; + ps->velocity[0] = ps->velocity[1] = 0; + VectorScale( checkDir, -JUMP_OFF_WALL_SPEED, ps->velocity ); + ps->velocity[2] = BG_ForceWallJumpStrength(); + ps->pm_flags |= PMF_JUMP_HELD;//PMF_JUMPING|PMF_JUMP_HELD; + //G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + ps->fd.forceJumpSound = 1; //this is a stupid thing, i should fix it. + //ent->client->ps.forcePowersActive |= (1<origin[2] < ps->fd.forceJumpZStart) + { + ps->fd.forceJumpZStart = ps->origin[2]; + } + //FIXME do I need this? + + BG_ForcePowerDrain( ps, FP_LEVITATION, 10 ); + //no control for half a second + ps->pm_flags |= PMF_TIME_KNOCKBACK; + ps->pm_time = 500; + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + ucmd->upmove = 127; + + if ( BG_InReboundHold( ps->legsAnim ) ) + {//if was in hold pose, release now + PM_SetAnim( SETANIM_BOTH, BOTH_FORCEWALLRELEASE_FORWARD+(ps->legsAnim-BOTH_FORCEWALLHOLD_FORWARD), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + } + else + { + //PM_JumpForDir(); + PM_SetAnim(SETANIM_LEGS,BOTH_FORCEJUMP1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART, 0); + } + + //return qtrue; + } + } + ps->pm_flags &= ~PMF_STUCK_TO_WALL; + return qfalse; +} + +//Set the height for when a force jump was started. If it's 0, nuge it up (slight hack to prevent holding jump over slopes) +void PM_SetForceJumpZStart(float value) +{ + pm->ps->fd.forceJumpZStart = value; + if (!pm->ps->fd.forceJumpZStart) + { + pm->ps->fd.forceJumpZStart -= 0.1; + } +} + +float forceJumpHeightMax[NUM_FORCE_POWER_LEVELS] = +{ + 66,//normal jump (32+stepheight(18)+crouchdiff(24) = 74) + 130,//(96+stepheight(18)+crouchdiff(24) = 138) + 226,//(192+stepheight(18)+crouchdiff(24) = 234) + 418//(384+stepheight(18)+crouchdiff(24) = 426) +}; + +void PM_GrabWallForJump( int anim ) +{//NOTE!!! assumes an appropriate anim is being passed in!!! + PM_SetAnim( SETANIM_BOTH, anim, SETANIM_FLAG_RESTART|SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + PM_AddEvent( EV_JUMP );//make sound for grab + pm->ps->pm_flags |= PMF_STUCK_TO_WALL; +} + +/* +============= +PM_CheckJump +============= +*/ +static qboolean PM_CheckJump( void ) +{ + if (pm->ps->clientNum >= MAX_CLIENTS) + { + bgEntity_t *pEnt = pm_entSelf; + + if (pEnt->s.eType == ET_NPC && + pEnt->s.NPC_class == CLASS_VEHICLE) + { //no! + return qfalse; + } + } + + if (pm->ps->forceHandExtend == HANDEXTEND_KNOCKDOWN || + pm->ps->forceHandExtend == HANDEXTEND_PRETHROWN || + pm->ps->forceHandExtend == HANDEXTEND_POSTTHROWN) + { + return qfalse; + } + + if (pm->ps->pm_type == PM_JETPACK) + { //there's no actual jumping while we jetpack + return qfalse; + } + + //Don't allow jump until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return qfalse; + } + + if ( PM_InKnockDown( pm->ps ) || BG_InRoll( pm->ps, pm->ps->legsAnim ) ) + {//in knockdown + return qfalse; + } + + if (pm->ps->groundEntityNum != ENTITYNUM_NONE || pm->ps->origin[2] < pm->ps->fd.forceJumpZStart) + { + pm->ps->fd.forcePowersActive &= ~(1<ps->fd.forcePowersActive & (1 << FP_LEVITATION)) + { //Force jump is already active.. continue draining power appropriately until we land. + if (pm->ps->fd.forcePowerDebounce[FP_LEVITATION] < pm->cmd.serverTime) + { + if ( pm->gametype == GT_DUEL + || pm->gametype == GT_POWERDUEL ) + {//jump takes less power + BG_ForcePowerDrain( pm->ps, FP_LEVITATION, 1 ); + } + else + { + BG_ForcePowerDrain( pm->ps, FP_LEVITATION, 5 ); + } + if (pm->ps->fd.forcePowerLevel[FP_LEVITATION] >= FORCE_LEVEL_2) + { + pm->ps->fd.forcePowerDebounce[FP_LEVITATION] = pm->cmd.serverTime + 300; + } + else + { + pm->ps->fd.forcePowerDebounce[FP_LEVITATION] = pm->cmd.serverTime + 200; + } + } + } + + if (pm->ps->forceJumpFlip) + { //Forced jump anim + int anim = BOTH_FORCEINAIR1; + int parts = SETANIM_BOTH; + + if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_FLIP_F; + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_FLIP_B; + } + else if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_FLIP_R; + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_FLIP_L; + } + if ( pm->ps->weaponTime ) + {//FIXME: really only care if we're in a saber attack anim... + parts = SETANIM_LEGS; + } + + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 ); + pm->ps->forceJumpFlip = qfalse; + return qtrue; + } +#if METROID_JUMP + if ( pm->waterlevel < 2 ) + { + if ( pm->ps->gravity > 0 ) + {//can't do this in zero-G + if ( PM_ForceJumpingUp() ) + {//holding jump in air + float curHeight = pm->ps->origin[2] - pm->ps->fd.forceJumpZStart; + //check for max force jump level and cap off & cut z vel + if ( ( curHeight<=forceJumpHeight[0] ||//still below minimum jump height + (pm->ps->fd.forcePower&&pm->cmd.upmove>=10) ) &&////still have force power available and still trying to jump up + curHeight < forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]] && + pm->ps->fd.forceJumpZStart)//still below maximum jump height + {//can still go up + if ( curHeight > forceJumpHeight[0] ) + {//passed normal jump height *2? + if ( !(pm->ps->fd.forcePowersActive&(1<ps->fd.forcePowersActive |= (1<ps->fd.forceJumpSound = 1; + //play flip + if ((pm->cmd.forwardmove || pm->cmd.rightmove) && //pushing in a dir + (pm->ps->legsAnim) != BOTH_FLIP_F &&//not already flipping + (pm->ps->legsAnim) != BOTH_FLIP_B && + (pm->ps->legsAnim) != BOTH_FLIP_R && + (pm->ps->legsAnim) != BOTH_FLIP_L ) + { + int anim = BOTH_FORCEINAIR1; + int parts = SETANIM_BOTH; + + if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_FLIP_F; + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_FLIP_B; + } + else if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_FLIP_R; + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_FLIP_L; + } + if ( pm->ps->weaponTime ) + { + parts = SETANIM_LEGS; + } + + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 ); + } + else if ( pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + { + vec3_t facingFwd, facingRight, facingAngles; + int anim = -1; + float dotR, dotF; + + VectorSet(facingAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( facingAngles, facingFwd, facingRight, NULL ); + dotR = DotProduct( facingRight, pm->ps->velocity ); + dotF = DotProduct( facingFwd, pm->ps->velocity ); + + if ( fabs(dotR) > fabs(dotF) * 1.5 ) + { + if ( dotR > 150 ) + { + anim = BOTH_FORCEJUMPRIGHT1; + } + else if ( dotR < -150 ) + { + anim = BOTH_FORCEJUMPLEFT1; + } + } + else + { + if ( dotF > 150 ) + { + anim = BOTH_FORCEJUMP1; + } + else if ( dotF < -150 ) + { + anim = BOTH_FORCEJUMPBACK1; + } + } + if ( anim != -1 ) + { + int parts = SETANIM_BOTH; + if ( pm->ps->weaponTime ) + {//FIXME: really only care if we're in a saber attack anim... + parts = SETANIM_LEGS; + } + + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 ); + } + } + } + else + { //jump is already active (the anim has started) + if ( pm->ps->legsTimer < 1 ) + {//not in the middle of a legsAnim + int anim = (pm->ps->legsAnim); + int newAnim = -1; + switch ( anim ) + { + case BOTH_FORCEJUMP1: + newAnim = BOTH_FORCELAND1;//BOTH_FORCEINAIR1; + break; + case BOTH_FORCEJUMPBACK1: + newAnim = BOTH_FORCELANDBACK1;//BOTH_FORCEINAIRBACK1; + break; + case BOTH_FORCEJUMPLEFT1: + newAnim = BOTH_FORCELANDLEFT1;//BOTH_FORCEINAIRLEFT1; + break; + case BOTH_FORCEJUMPRIGHT1: + newAnim = BOTH_FORCELANDRIGHT1;//BOTH_FORCEINAIRRIGHT1; + break; + } + if ( newAnim != -1 ) + { + int parts = SETANIM_BOTH; + if ( pm->ps->weaponTime ) + { + parts = SETANIM_LEGS; + } + + PM_SetAnim( parts, newAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 ); + } + } + } + } + + //need to scale this down, start with height velocity (based on max force jump height) and scale down to regular jump vel + pm->ps->velocity[2] = (forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]]-curHeight)/forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]]*forceJumpStrength[pm->ps->fd.forcePowerLevel[FP_LEVITATION]];//JUMP_VELOCITY; + pm->ps->velocity[2] /= 10; + pm->ps->velocity[2] += JUMP_VELOCITY; + pm->ps->pm_flags |= PMF_JUMP_HELD; + } + else if ( curHeight > forceJumpHeight[0] && curHeight < forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]] - forceJumpHeight[0] ) + {//still have some headroom, don't totally stop it + if ( pm->ps->velocity[2] > JUMP_VELOCITY ) + { + pm->ps->velocity[2] = JUMP_VELOCITY; + } + } + else + { + //pm->ps->velocity[2] = 0; + //rww - changed for the sake of balance in multiplayer + + if ( pm->ps->velocity[2] > JUMP_VELOCITY ) + { + pm->ps->velocity[2] = JUMP_VELOCITY; + } + } + pm->cmd.upmove = 0; + return qfalse; + } + } + } + +#endif + + //Not jumping + if ( pm->cmd.upmove < 10 && pm->ps->groundEntityNum != ENTITYNUM_NONE) { + return qfalse; + } + + // must wait for jump to be released + if ( pm->ps->pm_flags & PMF_JUMP_HELD ) + { + // clear upmove so cmdscale doesn't lower running speed + pm->cmd.upmove = 0; + return qfalse; + } + + if ( pm->ps->gravity <= 0 ) + {//in low grav, you push in the dir you're facing as long as there is something behind you to shove off of + vec3_t forward, back; + trace_t trace; + + AngleVectors( pm->ps->viewangles, forward, NULL, NULL ); + VectorMA( pm->ps->origin, -8, forward, back ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, back, pm->ps->clientNum, pm->tracemask ); + + if ( trace.fraction <= 1.0f ) + { + VectorMA( pm->ps->velocity, JUMP_VELOCITY*2, forward, pm->ps->velocity ); + PM_SetAnim(SETANIM_LEGS,BOTH_FORCEJUMP1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART, 150); + }//else no surf close enough to push off of + pm->cmd.upmove = 0; + } + else if ( pm->cmd.upmove > 0 && pm->waterlevel < 2 && + pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 && + !(pm->ps->pm_flags&PMF_JUMP_HELD) && + (pm->ps->weapon == WP_SABER || pm->ps->weapon == WP_MELEE) && + !PM_IsRocketTrooper() && + !BG_HasYsalamiri(pm->gametype, pm->ps) && + BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION) ) + { + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//on the ground + //check for left-wall and right-wall special jumps + int anim = -1; + float vertPush = 0; + if ( pm->cmd.rightmove > 0 && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + {//strafing right + if ( pm->cmd.forwardmove > 0 ) + {//wall-run + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.0f; + anim = BOTH_WALL_RUN_RIGHT; + } + else if ( pm->cmd.forwardmove == 0 ) + {//wall-flip + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + anim = BOTH_WALL_FLIP_RIGHT; + } + } + else if ( pm->cmd.rightmove < 0 && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + {//strafing left + if ( pm->cmd.forwardmove > 0 ) + {//wall-run + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.0f; + anim = BOTH_WALL_RUN_LEFT; + } + else if ( pm->cmd.forwardmove == 0 ) + {//wall-flip + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + anim = BOTH_WALL_FLIP_LEFT; + } + } + else if ( pm->cmd.forwardmove < 0 && !(pm->cmd.buttons&BUTTON_ATTACK) ) + {//backflip + vertPush = JUMP_VELOCITY; + anim = BOTH_FLIP_BACK1;//BG_PickAnim( BOTH_FLIP_BACK1, BOTH_FLIP_BACK3 ); + } + + vertPush += 128; //give them an extra shove + + if ( anim != -1 ) + { + vec3_t fwd, right, traceto, mins, maxs, fwdAngles; + vec3_t idealNormal={0}, wallNormal={0}; + trace_t trace; + qboolean doTrace = qfalse; + int contents = MASK_SOLID;//MASK_PLAYERSOLID; + + VectorSet(mins, pm->mins[0],pm->mins[1],0); + VectorSet(maxs, pm->maxs[0],pm->maxs[1],24); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + memset(&trace, 0, sizeof(trace)); //to shut the compiler up + + AngleVectors( fwdAngles, fwd, right, NULL ); + + //trace-check for a wall, if necc. + switch ( anim ) + { + case BOTH_WALL_FLIP_LEFT: + //NOTE: purposely falls through to next case! + case BOTH_WALL_RUN_LEFT: + doTrace = qtrue; + VectorMA( pm->ps->origin, -16, right, traceto ); + break; + + case BOTH_WALL_FLIP_RIGHT: + //NOTE: purposely falls through to next case! + case BOTH_WALL_RUN_RIGHT: + doTrace = qtrue; + VectorMA( pm->ps->origin, 16, right, traceto ); + break; + + case BOTH_WALL_FLIP_BACK1: + doTrace = qtrue; + VectorMA( pm->ps->origin, 16, fwd, traceto ); + break; + } + + if ( doTrace ) + { + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, contents ); + VectorCopy( trace.plane.normal, wallNormal ); + VectorNormalize( wallNormal ); + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + } + + if ( !doTrace || (trace.fraction < 1.0f && (trace.entityNum < MAX_CLIENTS || DotProduct(wallNormal,idealNormal) > 0.7)) ) + {//there is a wall there.. or hit a client + if ( (anim != BOTH_WALL_RUN_LEFT + && anim != BOTH_WALL_RUN_RIGHT + && anim != BOTH_FORCEWALLRUNFLIP_START) + || (wallNormal[2] >= 0.0f&&wallNormal[2]<=0.4f/*MAX_WALL_RUN_Z_NORMAL*/) ) + {//wall-runs can only run on perfectly flat walls, sorry. + int parts; + //move me to side + if ( anim == BOTH_WALL_FLIP_LEFT ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, 150, right, pm->ps->velocity ); + } + else if ( anim == BOTH_WALL_FLIP_RIGHT ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -150, right, pm->ps->velocity ); + } + else if ( anim == BOTH_FLIP_BACK1 + || anim == BOTH_FLIP_BACK2 + || anim == BOTH_FLIP_BACK3 + || anim == BOTH_WALL_FLIP_BACK1 ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity ); + } + + /* + if ( doTrace && anim != BOTH_WALL_RUN_LEFT && anim != BOTH_WALL_RUN_RIGHT ) + { + if (trace.entityNum < MAX_CLIENTS) + { + pm->ps->forceKickFlip = trace.entityNum+1; //let the server know that this person gets kicked by this client + } + } + */ + + //up + if ( vertPush ) + { + pm->ps->velocity[2] = vertPush; + pm->ps->fd.forcePowersActive |= (1 << FP_LEVITATION); + } + //animate me + parts = SETANIM_LEGS; + if ( anim == BOTH_BUTTERFLY_LEFT ) + { + parts = SETANIM_BOTH; + pm->cmd.buttons&=~BUTTON_ATTACK; + pm->ps->saberMove = LS_NONE; + } + else if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + if ( anim == BOTH_BUTTERFLY_LEFT ) + { + pm->ps->weaponTime = pm->ps->torsoTimer; + } + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->cmd.upmove = 0; + pm->ps->fd.forceJumpSound = 1; + } + } + } + } + else + {//in the air + int legsAnim = pm->ps->legsAnim; + + if ( legsAnim == BOTH_WALL_RUN_LEFT || legsAnim == BOTH_WALL_RUN_RIGHT ) + {//running on a wall + vec3_t right, traceto, mins, maxs, fwdAngles; + trace_t trace; + int anim = -1; + + VectorSet(mins, pm->mins[0], pm->mins[0], 0); + VectorSet(maxs, pm->maxs[0], pm->maxs[0], 24); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( fwdAngles, NULL, right, NULL ); + + if ( legsAnim == BOTH_WALL_RUN_LEFT ) + { + if ( pm->ps->legsTimer > 400 ) + {//not at the end of the anim + float animLen = PM_AnimLength( 0, (animNumber_t)BOTH_WALL_RUN_LEFT ); + if ( pm->ps->legsTimer < animLen - 400 ) + {//not at start of anim + VectorMA( pm->ps->origin, -16, right, traceto ); + anim = BOTH_WALL_RUN_LEFT_FLIP; + } + } + } + else if ( legsAnim == BOTH_WALL_RUN_RIGHT ) + { + if ( pm->ps->legsTimer > 400 ) + {//not at the end of the anim + float animLen = PM_AnimLength( 0, (animNumber_t)BOTH_WALL_RUN_RIGHT ); + if ( pm->ps->legsTimer < animLen - 400 ) + {//not at start of anim + VectorMA( pm->ps->origin, 16, right, traceto ); + anim = BOTH_WALL_RUN_RIGHT_FLIP; + } + } + } + if ( anim != -1 ) + { + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + if ( trace.fraction < 1.0f ) + {//flip off wall + int parts = 0; + + if ( anim == BOTH_WALL_RUN_LEFT_FLIP ) + { + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + VectorMA( pm->ps->velocity, 150, right, pm->ps->velocity ); + } + else if ( anim == BOTH_WALL_RUN_RIGHT_FLIP ) + { + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + VectorMA( pm->ps->velocity, -150, right, pm->ps->velocity ); + } + parts = SETANIM_LEGS; + if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + pm->cmd.upmove = 0; + } + } + if ( pm->cmd.upmove != 0 ) + {//jump failed, so don't try to do normal jump code, just return + return qfalse; + } + } + //NEW JKA + else if ( pm->ps->legsAnim == BOTH_FORCEWALLRUNFLIP_START ) + { + vec3_t fwd, traceto, mins, maxs, fwdAngles; + trace_t trace; + int anim = -1; + float animLen; + + VectorSet(mins, pm->mins[0], pm->mins[0], 0.0f); + VectorSet(maxs, pm->maxs[0], pm->maxs[0], 24.0f); + //hmm, did you mean [1] and [1]? + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0.0f); + AngleVectors( fwdAngles, fwd, NULL, NULL ); + + assert(pm_entSelf); //null pm_entSelf would be a Bad Thing + animLen = BG_AnimLength( pm_entSelf->localAnimIndex, BOTH_FORCEWALLRUNFLIP_START ); + if ( pm->ps->legsTimer < animLen - 400 ) + {//not at start of anim + VectorMA( pm->ps->origin, 16, fwd, traceto ); + anim = BOTH_FORCEWALLRUNFLIP_END; + } + if ( anim != -1 ) + { + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + if ( trace.fraction < 1.0f ) + {//flip off wall + int parts = SETANIM_LEGS; + + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + VectorMA( pm->ps->velocity, -300, fwd, pm->ps->velocity ); + pm->ps->velocity[2] += 200; + if ( !pm->ps->weaponTime ) + {//not attacking, set anim on both + parts = SETANIM_BOTH; + } + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + //FIXME: do damage to traceEnt, like above? + //pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + //ha ha, so silly with your silly jumpy fally flags. + pm->cmd.upmove = 0; + PM_AddEvent( EV_JUMP ); + } + } + if ( pm->cmd.upmove != 0 ) + {//jump failed, so don't try to do normal jump code, just return + return qfalse; + } + } + /* + else if ( pm->cmd.forwardmove > 0 //pushing forward + && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 + && pm->ps->velocity[2] > 200 + && PM_GroundDistance() <= 80 //unfortunately we do not have a happy ground timer like SP (this would use up more bandwidth if we wanted prediction workign right), so we'll just use the actual ground distance. + && !BG_InSpecialJump(pm->ps->legsAnim)) + {//run up wall, flip backwards + vec3_t fwd, traceto, mins, maxs, fwdAngles; + trace_t trace; + vec3_t idealNormal; + + VectorSet(mins, pm->mins[0],pm->mins[1],pm->mins[2]); + VectorSet(maxs, pm->maxs[0],pm->maxs[1],pm->maxs[2]); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + VectorMA( pm->ps->origin, 32, fwd, traceto ); + + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, MASK_PLAYERSOLID );//FIXME: clip brushes too? + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + + if ( trace.fraction < 1.0f ) + {//there is a wall there + int parts = SETANIM_LEGS; + + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity ); + pm->ps->velocity[2] += 128; + + if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + PM_SetAnim( parts, BOTH_WALL_FLIP_BACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + + pm->ps->legsTimer -= 600; //I force this anim to play to the end to prevent landing on your head and suddenly flipping over. + //It is a bit too long at the end though, so I'll just shorten it. + + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + pm->cmd.upmove = 0; + pm->ps->fd.forceJumpSound = 1; + BG_ForcePowerDrain( pm->ps, FP_LEVITATION, 5 ); + + if (trace.entityNum < MAX_CLIENTS) + { + pm->ps->forceKickFlip = trace.entityNum+1; //let the server know that this person gets kicked by this client + } + } + } + */ + else if ( pm->cmd.forwardmove > 0 //pushing forward + && pm->ps->fd.forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 + && PM_WalkableGroundDistance() <= 80 //unfortunately we do not have a happy ground timer like SP (this would use up more bandwidth if we wanted prediction workign right), so we'll just use the actual ground distance. + && (pm->ps->legsAnim == BOTH_JUMP1 || pm->ps->legsAnim == BOTH_INAIR1 ) )//not in a flip or spin or anything + {//run up wall, flip backwards + //FIXME: have to be moving... make sure it's opposite the wall... or at least forward? + int wallWalkAnim = BOTH_WALL_FLIP_BACK1; + int parts = SETANIM_LEGS; + int contents = MASK_SOLID;//MASK_PLAYERSOLID;//CONTENTS_SOLID; + //qboolean kick = qtrue; + if ( pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_2 ) + { + wallWalkAnim = BOTH_FORCEWALLRUNFLIP_START; + parts = SETANIM_BOTH; + //kick = qfalse; + } + else + { + if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + } + //if ( PM_HasAnimation( pm->gent, wallWalkAnim ) ) + if (1) //sure, we have it! Because I SAID SO. + { + vec3_t fwd, traceto, mins, maxs, fwdAngles; + trace_t trace; + vec3_t idealNormal; + bgEntity_t *traceEnt; + + VectorSet(mins, pm->mins[0], pm->mins[1], 0.0f); + VectorSet(maxs, pm->maxs[0], pm->maxs[1], 24.0f); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0.0f); + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + VectorMA( pm->ps->origin, 32, fwd, traceto ); + + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, contents );//FIXME: clip brushes too? + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + traceEnt = PM_BGEntForNum(trace.entityNum); + + if ( trace.fraction < 1.0f + &&((trace.entityNums.solid!=SOLID_BMODEL)||DotProduct(trace.plane.normal,idealNormal)>0.7) ) + {//there is a wall there + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + if ( wallWalkAnim == BOTH_FORCEWALLRUNFLIP_START ) + { + pm->ps->velocity[2] = forceJumpStrength[FORCE_LEVEL_3]/2.0f; + } + else + { + VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity ); + pm->ps->velocity[2] += 150.0f; + } + //animate me + PM_SetAnim( parts, wallWalkAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); +// pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + //again with the flags! + //G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + //yucky! + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + pm->cmd.upmove = 0; + pm->ps->fd.forceJumpSound = 1; + BG_ForcePowerDrain( pm->ps, FP_LEVITATION, 5 ); + + //kick if jumping off an ent + /* + if ( kick && traceEnt && (traceEnt->s.eType == ET_PLAYER || traceEnt->s.eType == ET_NPC) ) + { //kick that thang! + pm->ps->forceKickFlip = traceEnt->s.number+1; + } + */ + pm->cmd.rightmove = pm->cmd.forwardmove= 0; + } + } + } + else if ( (!BG_InSpecialJump( legsAnim )//not in a special jump anim + ||BG_InReboundJump( legsAnim )//we're already in a rebound + ||BG_InBackFlip( legsAnim ) )//a backflip (needed so you can jump off a wall behind you) + //&& pm->ps->velocity[2] <= 0 + && pm->ps->velocity[2] > -1200 //not falling down very fast + && !(pm->ps->pm_flags&PMF_JUMP_HELD)//have to have released jump since last press + && (pm->cmd.forwardmove||pm->cmd.rightmove)//pushing in a direction + //&& pm->ps->forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_2//level 3 jump or better + //&& WP_ForcePowerAvailable( pm->gent, FP_LEVITATION, 10 )//have enough force power to do another one + && BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION) + && (pm->ps->origin[2]-pm->ps->fd.forceJumpZStart) < (forceJumpHeightMax[FORCE_LEVEL_3]-(BG_ForceWallJumpStrength()/2.0f)) //can fit at least one more wall jump in (yes, using "magic numbers"... for now) + //&& (pm->ps->legsAnim == BOTH_JUMP1 || pm->ps->legsAnim == BOTH_INAIR1 ) )//not in a flip or spin or anything + ) + {//see if we're pushing at a wall and jump off it if so + //FIXME: make sure we have enough force power + //FIXME: check to see if we can go any higher + //FIXME: limit to a certain number of these in a row? + //FIXME: maybe don't require a ucmd direction, just check all 4? + //FIXME: should stick to the wall for a second, then push off... + vec3_t checkDir, traceto, mins, maxs, fwdAngles; + trace_t trace; + vec3_t idealNormal; + int anim = -1; + + VectorSet(mins, pm->mins[0], pm->mins[1], 0.0f); + VectorSet(maxs, pm->maxs[0], pm->maxs[1], 24.0f); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0.0f); + + if ( pm->cmd.rightmove ) + { + if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_FORCEWALLREBOUND_RIGHT; + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_FORCEWALLREBOUND_LEFT; + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + VectorScale( checkDir, -1, checkDir ); + } + } + else if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_FORCEWALLREBOUND_FORWARD; + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_FORCEWALLREBOUND_BACK; + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + VectorScale( checkDir, -1, checkDir ); + } + if ( anim != -1 ) + {//trace in the dir we're pushing in and see if there's a vertical wall there + bgEntity_t *traceEnt; + + VectorMA( pm->ps->origin, 8, checkDir, traceto ); + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID );//FIXME: clip brushes too? + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + traceEnt = PM_BGEntForNum(trace.entityNum); + if ( trace.fraction < 1.0f + &&fabs(trace.plane.normal[2]) <= 0.2f/*MAX_WALL_GRAB_SLOPE*/ + &&((trace.entityNums.solid!=SOLID_BMODEL)||DotProduct(trace.plane.normal,idealNormal)>0.7) ) + {//there is a wall there + float dot = DotProduct( pm->ps->velocity, trace.plane.normal ); + if ( dot < 1.0f ) + {//can't be heading *away* from the wall! + //grab it! + PM_GrabWallForJump( anim ); + } + } + } + } + else + { + //FIXME: if in a butterfly, kick people away? + } + //END NEW JKA + } + } + + /* + if ( pm->cmd.upmove > 0 + && (pm->ps->weapon == WP_SABER || pm->ps->weapon == WP_MELEE) + && !PM_IsRocketTrooper() + && (pm->ps->weaponTime > 0||pm->cmd.buttons&BUTTON_ATTACK) ) + {//okay, we just jumped and we're in an attack + if ( !BG_InRoll( pm->ps, pm->ps->legsAnim ) + && !PM_InKnockDown( pm->ps ) + && !BG_InDeathAnim(pm->ps->legsAnim) + && !BG_FlippingAnim( pm->ps->legsAnim ) + && !PM_SpinningAnim( pm->ps->legsAnim ) + && !BG_SaberInSpecialAttack( pm->ps->torsoAnim ) + && ( BG_SaberInAttack( pm->ps->saberMove ) ) ) + {//not in an anim we shouldn't interrupt + //see if it's not too late to start a special jump-attack + float animLength = PM_AnimLength( 0, (animNumber_t)pm->ps->torsoAnim ); + if ( animLength - pm->ps->torsoTimer < 500 ) + {//just started the saberMove + //check for special-case jump attacks + if ( pm->ps->fd.saberAnimLevel == FORCE_LEVEL_2 ) + {//using medium attacks + if (PM_GroundDistance() < 32 && + !BG_InSpecialJump(pm->ps->legsAnim)) + { //FLIP AND DOWNWARD ATTACK + //trace_t tr; + + //if (PM_SomeoneInFront(&tr)) + { + PM_SetSaberMove(PM_SaberFlipOverAttackMove()); + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + VectorClear(pml.groundTrace.plane.normal); + + pm->ps->weaponTime = pm->ps->torsoTimer; + } + } + } + else if ( pm->ps->fd.saberAnimLevel == FORCE_LEVEL_3 ) + {//using strong attacks + if ( pm->cmd.forwardmove > 0 && //going forward + (pm->cmd.buttons & BUTTON_ATTACK) && //must be holding attack still + PM_GroundDistance() < 32 && + !BG_InSpecialJump(pm->ps->legsAnim)) + {//strong attack: jump-hack + PM_SetSaberMove( PM_SaberJumpAttackMove() ); + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + VectorClear(pml.groundTrace.plane.normal); + + pm->ps->weaponTime = pm->ps->torsoTimer; + } + } + } + } + } + */ + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + return qfalse; + } + if ( pm->cmd.upmove > 0 ) + {//no special jumps + pm->ps->velocity[2] = JUMP_VELOCITY; + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + pm->ps->pm_flags |= PMF_JUMP_HELD; + } + + //Jumping + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + PM_SetForceJumpZStart(pm->ps->origin[2]); + + PM_AddEvent( EV_JUMP ); + + //Set the animations + if ( pm->ps->gravity > 0 && !BG_InSpecialJump( pm->ps->legsAnim ) ) + { + PM_JumpForDir(); + } + + return qtrue; +} +/* +============= +PM_CheckWaterJump +============= +*/ +static qboolean PM_CheckWaterJump( void ) { + vec3_t spot; + int cont; + vec3_t flatforward; + + if (pm->ps->pm_time) { + return qfalse; + } + + // check for water jump + if ( pm->waterlevel != 2 ) { + return qfalse; + } + + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + VectorMA (pm->ps->origin, 30, flatforward, spot); + spot[2] += 4; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( !(cont & CONTENTS_SOLID) ) { + return qfalse; + } + + spot[2] += 16; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( cont ) { + return qfalse; + } + + // jump out of water + VectorScale (pml.forward, 200, pm->ps->velocity); + pm->ps->velocity[2] = 350; + + pm->ps->pm_flags |= PMF_TIME_WATERJUMP; + pm->ps->pm_time = 2000; + + return qtrue; +} + +//============================================================================ + + +/* +=================== +PM_WaterJumpMove + +Flying out of the water +=================== +*/ +static void PM_WaterJumpMove( void ) { + // waterjump has no control, but falls + + PM_StepSlideMove( qtrue ); + + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + if (pm->ps->velocity[2] < 0) { + // cancel as soon as we are falling down again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +static void PM_WaterMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + if ( PM_CheckWaterJump() ) { + PM_WaterJumpMove(); + return; + } +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if (pm->ps->velocity[2] > -300) { + if ( pm->watertype == CONTENTS_WATER ) { + pm->ps->velocity[2] = 100; + } else if (pm->watertype == CONTENTS_SLIME) { + pm->ps->velocity[2] = 80; + } else { + pm->ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = -60; // sink towards bottom + } else { + for (i=0 ; i<3 ; i++) + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + if ( wishspeed > pm->ps->speed * pm_swimScale ) { + wishspeed = pm->ps->speed * pm_swimScale; + } + + PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); + + // make sure we can go up slopes easily under water + if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { + vel = VectorLength(pm->ps->velocity); + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + } + + PM_SlideMove( qfalse ); +} + +/* +=================== +PM_FlyVehicleMove + +=================== +*/ +static void PM_FlyVehicleMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float zVel; + float fmove = 0.0f, smove = 0.0f; + + // We don't use these here because we pre-calculate the movedir in the vehicle update anyways, and if + // you leave this, you get strange motion during boarding (the player can move the vehicle). + //fmove = pm->cmd.forwardmove; + //smove = pm->cmd.rightmove; + + // normal slowdown + if ( pm->ps->gravity && pm->ps->velocity[2] < 0 && pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//falling + zVel = pm->ps->velocity[2]; + PM_Friction (); + pm->ps->velocity[2] = zVel; + } + else + { + PM_Friction (); + if ( pm->ps->velocity[2] < 0 && pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + pm->ps->velocity[2] = 0; // ignore slope movement + } + } + + scale = PM_CmdScale( &pm->cmd ); + + // Get The WishVel And WishSpeed + //------------------------------- + if ( pm->ps->clientNum >= MAX_CLIENTS ) + {//NPC + + // If The UCmds Were Set, But Never Converted Into A MoveDir, Then Make The WishDir From UCmds + //-------------------------------------------------------------------------------------------- + if ((fmove!=0.0f || smove!=0.0f) && VectorCompare(pm->ps->moveDir, vec3_origin)) + { + //gi.Printf("Generating MoveDir\n"); + for ( i = 0 ; i < 3 ; i++ ) + { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + } + // Otherwise, Use The Move Dir + //----------------------------- + else + { + wishspeed = pm->ps->speed; + VectorScale( pm->ps->moveDir, pm->ps->speed, wishvel ); + VectorCopy( pm->ps->moveDir, wishdir ); + } + } + else + { + for ( i = 0 ; i < 3 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + // when going up or down slopes the wish velocity should Not be zero + // wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + } + + // Handle negative speed. + if ( wishspeed < 0 ) + { + wishspeed = wishspeed * -1.0f; + VectorScale( wishvel, -1.0f, wishvel ); + VectorScale( wishdir, -1.0f, wishdir ); + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + PM_Accelerate( wishdir, wishspeed, 100 ); + + PM_StepSlideMove( 1 ); +} + +/* +=================== +PM_FlyMove + +Only with the flight powerup +=================== +*/ +static void PM_FlyMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + // normal slowdown + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + + if ( pm->ps->pm_type == PM_SPECTATOR && pm->cmd.buttons & BUTTON_ALT_ATTACK) { + //turbo boost + scale *= 10; + } + + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = pm->ps->speed * (pm->cmd.upmove/127.0f); + } else { + for (i=0 ; i<3 ; i++) { + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + } + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); + + PM_StepSlideMove( qfalse ); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +static void PM_AirMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + float accelerate; + usercmd_t cmd; + Vehicle_t *pVeh = NULL; + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + bgEntity_t *pEnt = pm_entSelf; + + if ( pEnt && pEnt->s.NPC_class == CLASS_VEHICLE ) + { + pVeh = pEnt->m_pVehicle; + } + } + + if (pm->ps->pm_type != PM_SPECTATOR) + { +#if METROID_JUMP + PM_CheckJump(); +#else + if (pm->ps->fd.forceJumpZStart && + pm->ps->forceJumpFlip) + { + PM_CheckJump(); + } +#endif + } + PM_Friction(); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + if ( pVeh && pVeh->m_pVehicleInfo->hoverHeight > 0 ) + {//in a hovering vehicle, have air control + if ( 1 ) + { + wishspeed = pm->ps->speed; + VectorScale( pm->ps->moveDir, pm->ps->speed, wishvel ); + VectorCopy( pm->ps->moveDir, wishdir ); + scale = 1.0f; + } +#if 0 + else + { + float controlMod = 1.0f; + if ( pml.groundPlane ) + {//on a slope of some kind, shouldn't have much control and should slide a lot + controlMod = pml.groundTrace.plane.normal[2]; + } + + vec3_t vfwd, vrt; + vec3_t vAngles; + + VectorCopy( pVeh->m_vOrientation, vAngles ); + vAngles[ROLL] = 0;//since we're a hovercraft, we really don't want to stafe up into the air if we're banking + AngleVectors( vAngles, vfwd, vrt, NULL ); + + float speed = pm->ps->speed; + float strafeSpeed = 0; + + if ( fmove < 0 ) + {//going backwards + if ( speed < 0 ) + {//speed is negative, but since our command is reverse, make speed positive + speed = fabs( speed ); + /* + if ( pml.groundPlane ) + {//on a slope, still have some control + speed = fabs( speed ); + } + else + {//can't reverse in air + speed = 0; + } + */ + } + else if ( speed > 0 ) + {//trying to move back but speed is still positive, so keep moving forward (we'll slow down eventually) + speed = 0; + } + } + + if ( pm->ps->clientNum < MAX_CLIENTS ) + {//do normal adding to wishvel + VectorScale( vfwd, speed*controlMod*(fmove/127.0f), wishvel ); + //just add strafing + if ( pVeh->m_pVehicleInfo->strafePerc ) + {//we can strafe + if ( smove ) + {//trying to strafe + float minSpeed = pVeh->m_pVehicleInfo->speedMax * 0.5f * pVeh->m_pVehicleInfo->strafePerc; + strafeSpeed = fabs(DotProduct( pm->ps->velocity, vfwd ))*pVeh->m_pVehicleInfo->strafePerc; + if ( strafeSpeed < minSpeed ) + { + strafeSpeed = minSpeed; + } + strafeSpeed *= controlMod*((float)(smove))/127.0f; + if ( strafeSpeed < 0 ) + {//pm_accelerate does not understand negative numbers + strafeSpeed *= -1; + VectorScale( vrt, -1, vrt ); + } + //now just add it to actual velocity + PM_Accelerate( vrt, strafeSpeed, pVeh->m_pVehicleInfo->traction ); + } + } + } + else + { + if ( pVeh->m_pVehicleInfo->strafePerc ) + {//we can strafe + if ( pm->ps->clientNum ) + {//alternate control scheme: can strafe + if ( smove ) + { + /* + if ( fmove > 0 ) + {//actively accelerating + strafeSpeed = pm->ps->speed; + } + else + {//not stepping on accelerator, only strafe based on magnitude of current forward velocity + strafeSpeed = fabs(DotProduct( pm->ps->velocity, vfwd )); + } + */ + strafeSpeed = ((float)(smove))/127.0f; + } + } + } + //strafing takes away from forward speed + VectorScale( vfwd, (fmove/127.0f)*(1.0f-pVeh->m_pVehicleInfo->strafePerc), wishvel ); + if ( strafeSpeed ) + { + VectorMA( wishvel, strafeSpeed*pVeh->m_pVehicleInfo->strafePerc, vrt, wishvel ); + } + VectorNormalize( wishvel ); + VectorScale( wishvel, speed*controlMod, wishvel ); + } + } +#endif + } + else if ( gPMDoSlowFall ) + {//no air-control + VectorClear( wishvel ); + } + else if (pm->ps->pm_type == PM_JETPACK) + { //reduced air control while not jetting + for ( i = 0 ; i < 2 ; i++ ) + { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + wishvel[2] = 0; + + if (pm->cmd.upmove <= 0) + { + VectorScale(wishvel, 0.8f, wishvel); + } + else + { //if we are jetting then we have more control than usual + VectorScale(wishvel, 2.0f, wishvel); + } + } + else + { + for ( i = 0 ; i < 2 ; i++ ) + { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + wishvel[2] = 0; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + accelerate = pm_airaccelerate; + if ( pVeh && pVeh->m_pVehicleInfo->type == VH_SPEEDER ) + {//speeders have more control in air + //in mid-air + accelerate = pVeh->m_pVehicleInfo->traction; + if ( pml.groundPlane ) + {//on a slope of some kind, shouldn't have much control and should slide a lot + accelerate *= 0.5f; + } + } + // not on ground, so little effect on velocity + PM_Accelerate (wishdir, wishspeed, accelerate); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( pml.groundPlane ) + { + if ( !(pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//don't slide when stuck to a wall + if ( PM_GroundSlideOkay( pml.groundTrace.plane.normal[2] ) ) + { + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + } + + if ( (pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//no grav when stuck to wall + PM_StepSlideMove( qfalse ); + } + else + { + PM_StepSlideMove( qtrue ); + } +} + +/* +=================== +PM_WalkMove + +=================== +*/ +static void PM_WalkMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed = 0.0f; + float scale; + usercmd_t cmd; + float accelerate; + float vel; + qboolean npcMovement = qfalse; + + if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { + // begin swimming + PM_WaterMove(); + return; + } + + + if (pm->ps->pm_type != PM_SPECTATOR) + { + if ( PM_CheckJump () ) { + // jumped away + if ( pm->waterlevel > 1 ) { + PM_WaterMove(); + } else { + PM_AirMove(); + } + return; + } + } + + PM_Friction (); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + + // project the forward and right directions onto the ground plane + PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); + PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + // + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + // Get The WishVel And WishSpeed + //------------------------------- + if ( pm->ps->clientNum >= MAX_CLIENTS && !VectorCompare( pm->ps->moveDir, vec3_origin ) ) + {//NPC + bgEntity_t *pEnt = pm_entSelf; + + if (pEnt && pEnt->s.NPC_class == CLASS_VEHICLE) + { + // If The UCmds Were Set, But Never Converted Into A MoveDir, Then Make The WishDir From UCmds + //-------------------------------------------------------------------------------------------- + if ((fmove!=0.0f || smove!=0.0f) && VectorCompare(pm->ps->moveDir, vec3_origin)) + { + //gi.Printf("Generating MoveDir\n"); + for ( i = 0 ; i < 3 ; i++ ) + { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + } + // Otherwise, Use The Move Dir + //----------------------------- + else + { + //wishspeed = pm->ps->speed; + VectorScale( pm->ps->moveDir, pm->ps->speed, wishvel ); + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + } + + npcMovement = qtrue; + } + } + + if (!npcMovement) + { + for ( i = 0 ; i < 3 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + // when going up or down slopes the wish velocity should Not be zero + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + } + + // clamp the speed lower if ducking + if ( pm->ps->pm_flags & PMF_DUCKED ) { + if ( wishspeed > pm->ps->speed * pm_duckScale ) { + wishspeed = pm->ps->speed * pm_duckScale; + } + } + else if ( (pm->ps->pm_flags & PMF_ROLLING) && !BG_InRoll(pm->ps, pm->ps->legsAnim) && + !PM_InRollComplete(pm->ps, pm->ps->legsAnim)) + { + if ( wishspeed > pm->ps->speed * pm_duckScale ) { + wishspeed = pm->ps->speed * pm_duckScale; + } + } + + // clamp the speed lower if wading or walking on the bottom + if ( pm->waterlevel ) { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; + if ( wishspeed > pm->ps->speed * waterScale ) { + wishspeed = pm->ps->speed * waterScale; + } + } + + // when a player gets hit, they temporarily lose + // full control, which allows them to be moved a bit + if ( pm_flying == FLY_HOVER ) + { + accelerate = pm_vehicleaccelerate; + } + else if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + { + accelerate = pm_airaccelerate; + } + else + { + accelerate = pm_accelerate; + } + + PM_Accelerate (wishdir, wishspeed, accelerate); + /* + if (pm->ps->clientNum >= MAX_CLIENTS) + { +#ifdef QAGAME + Com_Printf("^1S: %f, %f\n", wishspeed, pm->ps->speed); +#else + Com_Printf("^2C: %f, %f\n", wishspeed, pm->ps->speed); +#endif + } + */ + + //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); + //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); + + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + { + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + } + + vel = VectorLength(pm->ps->velocity); + + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + // don't decrease velocity when going up or down a slope + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + + // don't do anything if standing still + if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) { + return; + } + + PM_StepSlideMove( qfalse ); + + //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); +} + + +/* +============== +PM_DeadMove +============== +*/ +static void PM_DeadMove( void ) { + float forward; + + if ( !pml.walking ) { + return; + } + + // extra friction + + forward = VectorLength (pm->ps->velocity); + forward -= 20; + if ( forward <= 0 ) { + VectorClear (pm->ps->velocity); + } else { + VectorNormalize (pm->ps->velocity); + VectorScale (pm->ps->velocity, forward, pm->ps->velocity); + } +} + + +/* +=============== +PM_NoclipMove +=============== +*/ +static void PM_NoclipMove( void ) { + float speed, drop, friction, control, newspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + + // friction + + speed = VectorLength (pm->ps->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pm->ps->velocity); + } + else + { + drop = 0; + + friction = pm_friction*1.5; // 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; + + VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + if (pm->cmd.buttons & BUTTON_ATTACK) { //turbo boost + scale *= 10; + } + if (pm->cmd.buttons & BUTTON_ALT_ATTACK) { //turbo boost + scale *= 10; + } + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + for (i=0 ; i<3 ; i++) + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + wishvel[2] += pm->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + // move + VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); +} + +//============================================================================ + +/* +================ +PM_FootstepForSurface + +Returns an event number apropriate for the groundsurface +================ +*/ +static int PM_FootstepForSurface( void ) +{ + if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) + { + return 0; + } + return ( pml.groundTrace.surfaceFlags & MATERIAL_MASK ); +} + +extern qboolean PM_CanRollFromSoulCal( playerState_t *ps ); +static int PM_TryRoll( void ) +{ + trace_t trace; + int anim = -1; + vec3_t fwd, right, traceto, mins, maxs, fwdAngles; + + if ( BG_SaberInAttack( pm->ps->saberMove ) || BG_SaberInSpecialAttack( pm->ps->torsoAnim ) + || BG_SpinningSaberAnim( pm->ps->legsAnim ) + || PM_SaberInStart( pm->ps->saberMove ) ) + {//attacking or spinning (or, if player, starting an attack) + if ( PM_CanRollFromSoulCal( pm->ps ) ) + {//hehe + } + else + { + return 0; + } + } + + if ((pm->ps->weapon != WP_SABER && pm->ps->weapon != WP_MELEE) || + PM_IsRocketTrooper() || + BG_HasYsalamiri(pm->gametype, pm->ps) || + !BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION)) + { //Not using saber, or can't use jump + return 0; + } + + VectorSet(mins, pm->mins[0],pm->mins[1],pm->mins[2]+STEPSIZE); + VectorSet(maxs, pm->maxs[0],pm->maxs[1],pm->ps->crouchheight); + + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( fwdAngles, fwd, right, NULL ); + + if ( pm->cmd.forwardmove ) + { //check forward/backward rolls + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + anim = BOTH_ROLL_B; + VectorMA( pm->ps->origin, -64, fwd, traceto ); + } + else + { + anim = BOTH_ROLL_F; + VectorMA( pm->ps->origin, 64, fwd, traceto ); + } + } + else if ( pm->cmd.rightmove > 0 ) + { //right + anim = BOTH_ROLL_R; + VectorMA( pm->ps->origin, 64, right, traceto ); + } + else if ( pm->cmd.rightmove < 0 ) + { //left + anim = BOTH_ROLL_L; + VectorMA( pm->ps->origin, -64, right, traceto ); + } + + if ( anim != -1 ) + { //We want to roll. Perform a trace to see if we can, and if so, send us into one. + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID ); + if ( trace.fraction >= 1.0f ) + { + pm->ps->saberMove = LS_NONE; + return anim; + } + } + return 0; +} + +#ifdef QAGAME +static void PM_CrashLandEffect( void ) +{ + float delta; + if ( pm->waterlevel ) + { + return; + } + delta = fabs(pml.previous_velocity[2])/10;//VectorLength( pml.previous_velocity );? + if ( delta >= 30 ) + { + vec3_t bottom; + int effectID = -1; + int material = (pml.groundTrace.surfaceFlags&MATERIAL_MASK); + VectorSet( bottom, pm->ps->origin[0],pm->ps->origin[1],pm->ps->origin[2]+pm->mins[2]+1 ); + switch ( material ) + { + case MATERIAL_MUD: + effectID = EFFECT_LANDING_MUD; + break; + case MATERIAL_SAND: + effectID = EFFECT_LANDING_SAND; + break; + case MATERIAL_DIRT: + effectID = EFFECT_LANDING_DIRT; + break; + case MATERIAL_SNOW: + effectID = EFFECT_LANDING_SNOW; + break; + case MATERIAL_GRAVEL: + effectID = EFFECT_LANDING_GRAVEL; + break; + } + + if ( effectID != -1 ) + { + G_PlayEffect( effectID, bottom, pml.groundTrace.plane.normal ); + } + } +} +#endif +/* +================= +PM_CrashLand + +Check for hard landings that generate sound events +================= +*/ +static void PM_CrashLand( void ) { + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + qboolean didRoll = qfalse; + + // calculate the exact velocity on landing + dist = pm->ps->origin[2] - pml.previous_origin[2]; + vel = pml.previous_velocity[2]; + acc = -pm->ps->gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if ( den < 0 ) { + pm->ps->inAirAnim = qfalse; + return; + } + t = (-b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta*delta * 0.0001; + +#ifdef QAGAME + PM_CrashLandEffect(); +#endif + // ducking while falling doubles damage + if ( pm->ps->pm_flags & PMF_DUCKED ) { + delta *= 2; + } + + if (pm->ps->legsAnim == BOTH_A7_KICK_F_AIR || + pm->ps->legsAnim == BOTH_A7_KICK_B_AIR || + pm->ps->legsAnim == BOTH_A7_KICK_R_AIR || + pm->ps->legsAnim == BOTH_A7_KICK_L_AIR) + { + int landAnim = -1; + switch ( pm->ps->legsAnim ) + { + case BOTH_A7_KICK_F_AIR: + landAnim = BOTH_FORCELAND1; + break; + case BOTH_A7_KICK_B_AIR: + landAnim = BOTH_FORCELANDBACK1; + break; + case BOTH_A7_KICK_R_AIR: + landAnim = BOTH_FORCELANDRIGHT1; + break; + case BOTH_A7_KICK_L_AIR: + landAnim = BOTH_FORCELANDLEFT1; + break; + } + if ( landAnim != -1 ) + { + if ( pm->ps->torsoAnim == pm->ps->legsAnim ) + { + PM_SetAnim(SETANIM_BOTH, landAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + else + { + PM_SetAnim(SETANIM_LEGS, landAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + } + } + else if (pm->ps->legsAnim == BOTH_FORCEJUMPLEFT1 || + pm->ps->legsAnim == BOTH_FORCEJUMPRIGHT1 || + pm->ps->legsAnim == BOTH_FORCEJUMPBACK1 || + pm->ps->legsAnim == BOTH_FORCEJUMP1) + { + int fjAnim; + switch (pm->ps->legsAnim) + { + case BOTH_FORCEJUMPLEFT1: + fjAnim = BOTH_LANDLEFT1; + break; + case BOTH_FORCEJUMPRIGHT1: + fjAnim = BOTH_LANDRIGHT1; + break; + case BOTH_FORCEJUMPBACK1: + fjAnim = BOTH_LANDBACK1; + break; + default: + fjAnim = BOTH_LAND1; + break; + } + PM_SetAnim(SETANIM_BOTH, fjAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + // decide which landing animation to use + else if (!BG_InRoll(pm->ps, pm->ps->legsAnim) && pm->ps->inAirAnim && !pm->ps->m_iVehicleNum) + { //only play a land animation if we transitioned into an in-air animation while off the ground + if (!BG_SaberInSpecial(pm->ps->saberMove)) + { + if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { + PM_ForceLegsAnim( BOTH_LANDBACK1 ); + } else { + PM_ForceLegsAnim( BOTH_LAND1 ); + } + } + } + + if (pm->ps->weapon != WP_SABER && pm->ps->weapon != WP_MELEE && !PM_IsRocketTrooper()) + { //saber handles its own anims + //This will push us back into our weaponready stance from the land anim. + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { + PM_StartTorsoAnim( TORSO_WEAPONREADY4 ); + } + else + { + if (pm->ps->weapon == WP_EMPLACED_GUN) + { + PM_StartTorsoAnim( BOTH_GUNSIT1 ); + } + else + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + } + } + + if (!BG_InSpecialJump(pm->ps->legsAnim) || + pm->ps->legsTimer < 1 || + (pm->ps->legsAnim) == BOTH_WALL_RUN_LEFT || + (pm->ps->legsAnim) == BOTH_WALL_RUN_RIGHT) + { //Only set the timer if we're in an anim that can be interrupted (this would not be, say, a flip) + if (!BG_InRoll(pm->ps, pm->ps->legsAnim) && pm->ps->inAirAnim) + { + if (!BG_SaberInSpecial(pm->ps->saberMove) || pm->ps->weapon != WP_SABER) + { + if (pm->ps->legsAnim != BOTH_FORCELAND1 && + pm->ps->legsAnim != BOTH_FORCELANDBACK1 && + pm->ps->legsAnim != BOTH_FORCELANDRIGHT1 && + pm->ps->legsAnim != BOTH_FORCELANDLEFT1) + { //don't override if we have started a force land + pm->ps->legsTimer = TIMER_LAND; + } + } + } + } + + pm->ps->inAirAnim = qfalse; + + if (pm->ps->m_iVehicleNum) + { //don't do fall stuff while on a vehicle + return; + } + + // never take falling damage if completely underwater + if ( pm->waterlevel == 3 ) { + return; + } + + // reduce falling damage if there is standing water + if ( pm->waterlevel == 2 ) { + delta *= 0.25; + } + if ( pm->waterlevel == 1 ) { + delta *= 0.5; + } + + if ( delta < 1 ) { + return; + } + + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + if( delta >= 2 && !PM_InOnGroundAnim( pm->ps->legsAnim ) && !PM_InKnockDown( pm->ps ) && !BG_InRoll(pm->ps, pm->ps->legsAnim) && + pm->ps->forceHandExtend == HANDEXTEND_NONE ) + {//roll! + int anim = PM_TryRoll(); + + if (PM_InRollComplete(pm->ps, pm->ps->legsAnim)) + { + anim = 0; + pm->ps->legsTimer = 0; + pm->ps->legsAnim = 0; + PM_SetAnim(SETANIM_BOTH,BOTH_LAND1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150); + pm->ps->legsTimer = TIMER_LAND; + } + + if ( anim ) + {//absorb some impact + pm->ps->legsTimer = 0; + delta /= 3; // /= 2 just cancels out the above delta *= 2 when landing while crouched, the roll itself should absorb a little damage + pm->ps->legsAnim = 0; + if (pm->ps->torsoAnim == BOTH_A7_SOULCAL) + { //get out of it on torso + pm->ps->torsoTimer = 0; + } + PM_SetAnim(SETANIM_BOTH,anim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150); + didRoll = qtrue; + } + } + } + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) { + if (delta > 7) + { + int delta_send = (int)delta; + + if (delta_send > 600) + { //will never need to know any value above this + delta_send = 600; + } + + if (pm->ps->fd.forceJumpZStart) + { + if ((int)pm->ps->origin[2] >= (int)pm->ps->fd.forceJumpZStart) + { //was force jumping, landed on higher or same level as when force jump was started + if (delta_send > 8) + { + delta_send = 8; + } + } + else + { + if (delta_send > 8) + { + int dif = ((int)pm->ps->fd.forceJumpZStart - (int)pm->ps->origin[2]); + int dmgLess = (forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]] - dif); + + if (dmgLess < 0) + { + dmgLess = 0; + } + + delta_send -= (dmgLess*0.3); + + if (delta_send < 8) + { + delta_send = 8; + } + + //Com_Printf("Damage sub: %i\n", (int)((dmgLess*0.1))); + } + } + } + + if (didRoll) + { //Add the appropriate event.. + PM_AddEventWithParm( EV_ROLL, delta_send ); + } + else + { + PM_AddEventWithParm( EV_FALL, delta_send ); + } + } + else + { + if (didRoll) + { + PM_AddEventWithParm( EV_ROLL, 0 ); + } + else + { + PM_AddEventWithParm( EV_FOOTSTEP, PM_FootstepForSurface() ); + } + } + } + + // make sure velocity resets so we don't bounce back up again in case we miss the clear elsewhere + pm->ps->velocity[2] = 0; + + // start footstep cycle over + pm->ps->bobCycle = 0; +} + +/* +============= +PM_CorrectAllSolid +============= +*/ +static int PM_CorrectAllSolid( trace_t *trace ) { + int i, j, k; + vec3_t point; + + if ( pm->debugLevel ) { + Com_Printf("%i:allsolid\n", c_pmove); + } + + // jitter around + for (i = -1; i <= 1; i++) { + for (j = -1; j <= 1; j++) { + for (k = -1; k <= 1; k++) { + VectorCopy(pm->ps->origin, point); + point[0] += (float) i; + point[1] += (float) j; + point[2] += (float) k; + pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( !trace->allsolid ) { + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = *trace; + return qtrue; + } + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + return qfalse; +} + +/* +============= +PM_GroundTraceMissed + +The ground trace didn't hit a surface, so we are in freefall +============= +*/ +static void PM_GroundTraceMissed( void ) { + trace_t trace; + vec3_t point; + + //rww - don't want to do this when handextend_choke, because you can be standing on the ground + //while still holding your throat. + if ( pm->ps->pm_type == PM_FLOAT ) + { + //we're assuming this is because you're being choked + int parts = SETANIM_LEGS; + + //rww - also don't use SETANIM_FLAG_HOLD, it will cause the legs to float around a bit before going into + //a proper anim even when on the ground. + PM_SetAnim(parts, BOTH_CHOKE3, SETANIM_FLAG_OVERRIDE, 100); + } + else if ( pm->ps->pm_type == PM_JETPACK ) + {//jetpacking + //rww - also don't use SETANIM_FLAG_HOLD, it will cause the legs to float around a bit before going into + //a proper anim even when on the ground. + //PM_SetAnim(SETANIM_LEGS,BOTH_FORCEJUMP1,SETANIM_FLAG_OVERRIDE, 100); + } + //If the anim is choke3, act like we just went into the air because we aren't in a float + else if ( pm->ps->groundEntityNum != ENTITYNUM_NONE || (pm->ps->legsAnim) == BOTH_CHOKE3 ) + { + // we just transitioned into freefall + if ( pm->debugLevel ) { + Com_Printf("%i:lift\n", c_pmove); + } + + // if they aren't in a jumping animation and the ground is a ways away, force into it + // if we didn't do the trace, the player would be backflipping down staircases + VectorCopy( pm->ps->origin, point ); + point[2] -= 64; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 || pm->ps->pm_type == PM_FLOAT ) { + if ( pm->ps->velocity[2] <= 0 && !(pm->ps->pm_flags&PMF_JUMP_HELD)) + { + //PM_SetAnim(SETANIM_LEGS,BOTH_INAIR1,SETANIM_FLAG_OVERRIDE, 100); + PM_SetAnim(SETANIM_LEGS,BOTH_INAIR1,0, 100); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.forwardmove >= 0 ) + { + PM_SetAnim(SETANIM_LEGS,BOTH_JUMP1,SETANIM_FLAG_OVERRIDE, 100); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + PM_SetAnim(SETANIM_LEGS,BOTH_JUMPBACK1,SETANIM_FLAG_OVERRIDE, 100); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->ps->inAirAnim = qtrue; + } + } + else if (!pm->ps->inAirAnim) + { + // if they aren't in a jumping animation and the ground is a ways away, force into it + // if we didn't do the trace, the player would be backflipping down staircases + VectorCopy( pm->ps->origin, point ); + point[2] -= 64; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 || pm->ps->pm_type == PM_FLOAT ) + { + pm->ps->inAirAnim = qtrue; + } + } + + if (PM_InRollComplete(pm->ps, pm->ps->legsAnim)) + { //Client won't catch an animation restart because it only checks frame against incoming frame, so if you roll when you land after rolling + //off of something it won't replay the roll anim unless we switch it off in the air. This fixes that. + PM_SetAnim(SETANIM_BOTH,BOTH_INAIR1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150); + pm->ps->inAirAnim = qtrue; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + + +/* +============= +PM_GroundTrace +============= +*/ +static void PM_GroundTrace( void ) { + vec3_t point; + trace_t trace; + float minNormal = (float)MIN_WALK_NORMAL; + + if ( pm->ps->clientNum >= MAX_CLIENTS) + { + bgEntity_t *pEnt = pm_entSelf; + + if (pEnt && pEnt->s.NPC_class == CLASS_VEHICLE) + { + minNormal = pEnt->m_pVehicle->m_pVehicleInfo->maxSlope; + } + } + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if ( trace.allsolid ) { + if ( !PM_CorrectAllSolid(&trace) ) + return; + } + + if (pm->ps->pm_type == PM_FLOAT || pm->ps->pm_type == PM_JETPACK) + { + PM_GroundTraceMissed(); + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0 ) { + PM_GroundTraceMissed(); + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // check if getting thrown off the ground + if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { + if ( pm->debugLevel ) { + Com_Printf("%i:kickoff\n", c_pmove); + } + // go into jump animation + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( BOTH_JUMP1 ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( BOTH_JUMPBACK1 ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // slopes that are too steep will not be considered onground + if ( trace.plane.normal[2] < minNormal ) { + if ( pm->debugLevel ) { + Com_Printf("%i:steep\n", c_pmove); + } + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qtrue; + pml.walking = qfalse; + return; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // hitting solid ground will end a waterjump + if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) + { + pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); + pm->ps->pm_time = 0; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + // just hit the ground + if ( pm->debugLevel ) { + Com_Printf("%i:Land\n", c_pmove); + } + + PM_CrashLand(); + +#ifdef QAGAME + if (pm->ps->clientNum < MAX_CLIENTS && + !pm->ps->m_iVehicleNum && + trace.entityNum < ENTITYNUM_WORLD && + trace.entityNum >= MAX_CLIENTS && + !pm->ps->zoomMode && + pm_entSelf) + { //check if we landed on a vehicle + gentity_t *trEnt = &g_entities[trace.entityNum]; + if (trEnt->inuse && trEnt->client && trEnt->s.eType == ET_NPC && trEnt->s.NPC_class == CLASS_VEHICLE && + !trEnt->client->ps.m_iVehicleNum && + trEnt->m_pVehicle && + trEnt->m_pVehicle->m_pVehicleInfo->type != VH_WALKER && + trEnt->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) + { //it's a vehicle alright, let's board it.. if it's not an atst or ship + if (!BG_SaberInSpecial(pm->ps->saberMove) && + pm->ps->forceHandExtend == HANDEXTEND_NONE && + pm->ps->weaponTime <= 0) + { + gentity_t *servEnt = (gentity_t *)pm_entSelf; + if (g_gametype.integer < GT_TEAM || + !trEnt->alliedTeam || + (trEnt->alliedTeam == servEnt->client->sess.sessionTeam)) + { //not belonging to a team, or client is on same team + trEnt->m_pVehicle->m_pVehicleInfo->Board(trEnt->m_pVehicle, pm_entSelf); + } + } + } + } +#endif + + // don't do landing time if we were just going down a slope + if ( pml.previous_velocity[2] < -200 ) { + // don't allow another jump for a little while + pm->ps->pm_flags |= PMF_TIME_LAND; + pm->ps->pm_time = 250; + } + } + + pm->ps->groundEntityNum = trace.entityNum; + pm->ps->lastOnGround = pm->cmd.serverTime; + + PM_AddTouchEnt( trace.entityNum ); +} + + +/* +============= +PM_SetWaterLevel +============= +*/ +static void PM_SetWaterLevel( void ) { + vec3_t point; + int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + pm->waterlevel = 0; + pm->watertype = 0; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] + MINS_Z + 1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if ( cont & MASK_WATER ) { + sample2 = pm->ps->viewheight - MINS_Z; + sample1 = sample2 / 2; + + pm->watertype = cont; + pm->waterlevel = 1; + point[2] = pm->ps->origin[2] + MINS_Z + sample1; + cont = pm->pointcontents (point, pm->ps->clientNum ); + if ( cont & MASK_WATER ) { + pm->waterlevel = 2; + point[2] = pm->ps->origin[2] + MINS_Z + sample2; + cont = pm->pointcontents (point, pm->ps->clientNum ); + if ( cont & MASK_WATER ){ + pm->waterlevel = 3; + } + } + } + +} + +qboolean PM_CheckDualForwardJumpDuck( void ) +{ + qboolean resized = qfalse; + if ( pm->ps->legsAnim == BOTH_JUMPATTACK6 ) + { + //dynamically reduce bounding box to let character sail over heads of enemies + if ( ( pm->ps->legsTimer >= 1450 + && PM_AnimLength( 0, BOTH_JUMPATTACK6 ) - pm->ps->legsTimer >= 400 ) + ||(pm->ps->legsTimer >= 400 + && PM_AnimLength( 0, BOTH_JUMPATTACK6 ) - pm->ps->legsTimer >= 1100 ) ) + {//in a part of the anim that we're pretty much sideways in, raise up the mins + pm->mins[2] = 0; + pm->ps->pm_flags |= PMF_FIX_MINS; + resized = qtrue; + } + } + return resized; +} + +void PM_CheckFixMins( void ) +{ + if ( (pm->ps->pm_flags&PMF_FIX_MINS) )// pm->mins[2] > DEFAULT_MINS_2 ) + {//drop the mins back down + //do a trace to make sure it's okay + trace_t trace; + vec3_t end, curMins, curMaxs; + + VectorSet( end, pm->ps->origin[0], pm->ps->origin[1], pm->ps->origin[2]+MINS_Z ); + VectorSet( curMins, pm->mins[0], pm->mins[1], 0 ); + VectorSet( curMaxs, pm->maxs[0], pm->maxs[1], pm->ps->standheight ); + + pm->trace( &trace, pm->ps->origin, curMins, curMaxs, end, pm->ps->clientNum, pm->tracemask ); + if ( !trace.allsolid && !trace.startsolid ) + {//should never start in solid + if ( trace.fraction >= 1.0f ) + {//all clear + //drop the bottom of my bbox back down + pm->mins[2] = MINS_Z; + pm->ps->pm_flags &= ~PMF_FIX_MINS; + } + else + {//move me up so the bottom of my bbox will be where the trace ended, at least + //need to trace up, too + float updist = ((1.0f-trace.fraction) * -MINS_Z); + end[2] = pm->ps->origin[2]+updist; + pm->trace( &trace, pm->ps->origin, curMins, curMaxs, end, pm->ps->clientNum, pm->tracemask ); + if ( !trace.allsolid && !trace.startsolid ) + {//should never start in solid + if ( trace.fraction >= 1.0f ) + {//all clear + //move me up + pm->ps->origin[2] += updist; + //drop the bottom of my bbox back down + pm->mins[2] = MINS_Z; + pm->ps->pm_flags &= ~PMF_FIX_MINS; + } + else + {//crap, no room to expand, so just crouch us + if ( pm->ps->legsAnim != BOTH_JUMPATTACK6 + || pm->ps->legsTimer <= 200 ) + {//at the end of the anim, and we can't leave ourselves like this + //so drop the maxs, put the mins back and move us up + pm->maxs[2] += MINS_Z; + pm->ps->origin[2] -= MINS_Z; + pm->mins[2] = MINS_Z; + //this way we'll be in a crouch when we're done + if ( pm->ps->legsAnim == BOTH_JUMPATTACK6 ) + { + pm->ps->legsTimer = pm->ps->torsoTimer = 0; + } + pm->ps->pm_flags |= PMF_DUCKED; + //FIXME: do we need to set a crouch anim here? + pm->ps->pm_flags &= ~PMF_FIX_MINS; + } + } + }//crap, stuck + } + }//crap, stuck! + } +} + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->ps->viewheight +============== +*/ +static void PM_CheckDuck (void) +{ + trace_t trace; + + if ( pm->ps->m_iVehicleNum > 0 && pm->ps->m_iVehicleNum < ENTITYNUM_NONE ) + {//riding a vehicle or are a vehicle + //no ducking or rolling when on a vehicle + //right? not even on ones that you just ride on top of? + pm->ps->pm_flags &= ~PMF_DUCKED; + pm->ps->pm_flags &= ~PMF_ROLLING; + //NOTE: we don't clear the pm->cmd.upmove here because + //the vehicle code may need it later... but, for riders, + //it should have already been copied over to the vehicle, right? + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + return; + } + if (pm_entVeh && pm_entVeh->m_pVehicle && + (pm_entVeh->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER || + pm_entVeh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL)) + { + trace_t solidTr; + + pm->mins[0] = -16; + pm->mins[1] = -16; + pm->mins[2] = MINS_Z; + + pm->maxs[0] = 16; + pm->maxs[1] = 16; + pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + + pm->trace (&solidTr, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->m_iVehicleNum, pm->tracemask); + if (solidTr.startsolid || solidTr.allsolid || solidTr.fraction != 1.0f) + { //whoops, can't fit here. Down to 0! + VectorClear(pm->mins); + VectorClear(pm->maxs); +#ifdef QAGAME + { + gentity_t *me = &g_entities[pm->ps->clientNum]; + if (me->inuse && me->client) + { //yeah, this is a really terrible hack. + me->client->solidHack = level.time + 200; + } + } +#endif + } + } + } + else + { + if (pm->ps->clientNum < MAX_CLIENTS) + { + pm->mins[0] = -15; + pm->mins[1] = -15; + + pm->maxs[0] = 15; + pm->maxs[1] = 15; + } + + if ( PM_CheckDualForwardJumpDuck() ) + {//special anim resizing us + } + else + { + PM_CheckFixMins(); + + if ( !pm->mins[2] ) + { + pm->mins[2] = MINS_Z; + } + } + + if (pm->ps->pm_type == PM_DEAD && pm->ps->clientNum < MAX_CLIENTS) + { + pm->maxs[2] = -8; + pm->ps->viewheight = DEAD_VIEWHEIGHT; + return; + } + + if (BG_InRoll(pm->ps, pm->ps->legsAnim) && !BG_KickingAnim(pm->ps->legsAnim)) + { + pm->maxs[2] = pm->ps->crouchheight; //CROUCH_MAXS_2; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + pm->ps->pm_flags &= ~PMF_DUCKED; + pm->ps->pm_flags |= PMF_ROLLING; + return; + } + else if (pm->ps->pm_flags & PMF_ROLLING) + { + // try to stand up + pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if (!trace.allsolid) + pm->ps->pm_flags &= ~PMF_ROLLING; + } + else if (pm->cmd.upmove < 0 || + pm->ps->forceHandExtend == HANDEXTEND_KNOCKDOWN || + pm->ps->forceHandExtend == HANDEXTEND_PRETHROWN || + pm->ps->forceHandExtend == HANDEXTEND_POSTTHROWN) + { // duck + pm->ps->pm_flags |= PMF_DUCKED; + } + else + { // stand up if possible + if (pm->ps->pm_flags & PMF_DUCKED) + { + // try to stand up + pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if (!trace.allsolid) + pm->ps->pm_flags &= ~PMF_DUCKED; + } + } + } + + if (pm->ps->pm_flags & PMF_DUCKED) + { + pm->maxs[2] = pm->ps->crouchheight;//CROUCH_MAXS_2; + pm->ps->viewheight = CROUCH_VIEWHEIGHT; + } + else if (pm->ps->pm_flags & PMF_ROLLING) + { + pm->maxs[2] = pm->ps->crouchheight;//CROUCH_MAXS_2; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + } + else + { + pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + } +} + + + +//=================================================================== + + + +/* +============== +PM_Use + +Generates a use event +============== +*/ +#define USE_DELAY 2000 + +void PM_Use( void ) +{ + if ( pm->ps->useTime > 0 ) + pm->ps->useTime -= 100;//pm->cmd.msec; + + if ( pm->ps->useTime > 0 ) { + return; + } + + if ( ! (pm->cmd.buttons & BUTTON_USE ) ) + { + pm->useEvent = 0; + pm->ps->useTime = 0; + return; + } + + pm->useEvent = EV_USE; + pm->ps->useTime = USE_DELAY; +} + +qboolean PM_WalkingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_WALK1: //# Normal walk + case BOTH_WALK2: //# Normal walk with saber + case BOTH_WALK_STAFF: //# Normal walk with staff + case BOTH_WALK_DUAL: //# Normal walk with staff + case BOTH_WALK5: //# Tavion taunting Kyle (cin 22) + case BOTH_WALK6: //# Slow walk for Luke (cin 12) + case BOTH_WALK7: //# Fast walk + case BOTH_WALKBACK1: //# Walk1 backwards + case BOTH_WALKBACK2: //# Walk2 backwards + case BOTH_WALKBACK_STAFF: //# Walk backwards with staff + case BOTH_WALKBACK_DUAL: //# Walk backwards with dual + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_RunningAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_RUN1: + case BOTH_RUN2: + case BOTH_RUN_STAFF: + case BOTH_RUN_DUAL: + case BOTH_RUNBACK1: + case BOTH_RUNBACK2: + case BOTH_RUNBACK_STAFF: + case BOTH_RUNBACK_DUAL: + case BOTH_RUN1START: //# Start into full run1 + case BOTH_RUN1STOP: //# Stop from full run1 + case BOTH_RUNSTRAFE_LEFT1: //# Sidestep left: should loop + case BOTH_RUNSTRAFE_RIGHT1: //# Sidestep right: should loop + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SwimmingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_SWIM_IDLE1: //# Swimming Idle 1 + case BOTH_SWIMFORWARD: //# Swim forward loop + case BOTH_SWIMBACKWARD: //# Swim backward loop + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_RollingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_ROLL_F: //# Roll forward + case BOTH_ROLL_B: //# Roll backward + case BOTH_ROLL_L: //# Roll left + case BOTH_ROLL_R: //# Roll right + return qtrue; + break; + } + return qfalse; +} + +void PM_AnglesForSlope( const float yaw, const vec3_t slope, vec3_t angles ) +{ + vec3_t nvf, ovf, ovr, new_angles; + float pitch, mod, dot; + + VectorSet( angles, 0, yaw, 0 ); + AngleVectors( angles, ovf, ovr, NULL ); + + vectoangles( slope, new_angles ); + pitch = new_angles[PITCH] + 90; + new_angles[ROLL] = new_angles[PITCH] = 0; + + AngleVectors( new_angles, nvf, NULL, NULL ); + + mod = DotProduct( nvf, ovr ); + + if ( mod < 0 ) + mod = -1; + else + mod = 1; + + dot = DotProduct( nvf, ovf ); + + angles[YAW] = 0; + angles[PITCH] = dot * pitch; + angles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); +} + +void PM_FootSlopeTrace( float *pDiff, float *pInterval ) +{ + vec3_t footLOrg, footROrg, footLBot, footRBot; + vec3_t footLPoint, footRPoint; + vec3_t footMins, footMaxs; + vec3_t footLSlope, footRSlope; + + trace_t trace; + float diff, interval; + + mdxaBone_t boltMatrix; + vec3_t G2Angles; + + VectorSet(G2Angles, 0, pm->ps->viewangles[YAW], 0); + + interval = 4;//? + + strap_G2API_GetBoltMatrix( pm->ghoul2, 0, pm->g2Bolts_LFoot, + &boltMatrix, G2Angles, pm->ps->origin, pm->cmd.serverTime, + NULL, pm->modelScale ); + footLPoint[0] = boltMatrix.matrix[0][3]; + footLPoint[1] = boltMatrix.matrix[1][3]; + footLPoint[2] = boltMatrix.matrix[2][3]; + + strap_G2API_GetBoltMatrix( pm->ghoul2, 0, pm->g2Bolts_RFoot, + &boltMatrix, G2Angles, pm->ps->origin, pm->cmd.serverTime, + NULL, pm->modelScale ); + footRPoint[0] = boltMatrix.matrix[0][3]; + footRPoint[1] = boltMatrix.matrix[1][3]; + footRPoint[2] = boltMatrix.matrix[2][3]; + + //get these on the cgame and store it, save ourselves a ghoul2 construct skel call + VectorCopy( footLPoint, footLOrg ); + VectorCopy( footRPoint, footROrg ); + + //step 2: adjust foot tag z height to bottom of bbox+1 + footLOrg[2] = pm->ps->origin[2] + pm->mins[2] + 1; + footROrg[2] = pm->ps->origin[2] + pm->mins[2] + 1; + VectorSet( footLBot, footLOrg[0], footLOrg[1], footLOrg[2] - interval*10 ); + VectorSet( footRBot, footROrg[0], footROrg[1], footROrg[2] - interval*10 ); + + //step 3: trace down from each, find difference + VectorSet( footMins, -3, -3, 0 ); + VectorSet( footMaxs, 3, 3, 1 ); + + pm->trace( &trace, footLOrg, footMins, footMaxs, footLBot, pm->ps->clientNum, pm->tracemask ); + VectorCopy( trace.endpos, footLBot ); + VectorCopy( trace.plane.normal, footLSlope ); + + pm->trace( &trace, footROrg, footMins, footMaxs, footRBot, pm->ps->clientNum, pm->tracemask ); + VectorCopy( trace.endpos, footRBot ); + VectorCopy( trace.plane.normal, footRSlope ); + + diff = footLBot[2] - footRBot[2]; + + if ( pDiff != NULL ) + { + *pDiff = diff; + } + if ( pInterval != NULL ) + { + *pInterval = interval; + } +} + +qboolean BG_InSlopeAnim( int anim ) +{ + switch ( anim ) + { + case LEGS_LEFTUP1: //# On a slope with left foot 4 higher than right + case LEGS_LEFTUP2: //# On a slope with left foot 8 higher than right + case LEGS_LEFTUP3: //# On a slope with left foot 12 higher than right + case LEGS_LEFTUP4: //# On a slope with left foot 16 higher than right + case LEGS_LEFTUP5: //# On a slope with left foot 20 higher than right + case LEGS_RIGHTUP1: //# On a slope with RIGHT foot 4 higher than left + case LEGS_RIGHTUP2: //# On a slope with RIGHT foot 8 higher than left + case LEGS_RIGHTUP3: //# On a slope with RIGHT foot 12 higher than left + case LEGS_RIGHTUP4: //# On a slope with RIGHT foot 16 higher than left + case LEGS_RIGHTUP5: //# On a slope with RIGHT foot 20 higher than left + case LEGS_S1_LUP1: + case LEGS_S1_LUP2: + case LEGS_S1_LUP3: + case LEGS_S1_LUP4: + case LEGS_S1_LUP5: + case LEGS_S1_RUP1: + case LEGS_S1_RUP2: + case LEGS_S1_RUP3: + case LEGS_S1_RUP4: + case LEGS_S1_RUP5: + case LEGS_S3_LUP1: + case LEGS_S3_LUP2: + case LEGS_S3_LUP3: + case LEGS_S3_LUP4: + case LEGS_S3_LUP5: + case LEGS_S3_RUP1: + case LEGS_S3_RUP2: + case LEGS_S3_RUP3: + case LEGS_S3_RUP4: + case LEGS_S3_RUP5: + case LEGS_S4_LUP1: + case LEGS_S4_LUP2: + case LEGS_S4_LUP3: + case LEGS_S4_LUP4: + case LEGS_S4_LUP5: + case LEGS_S4_RUP1: + case LEGS_S4_RUP2: + case LEGS_S4_RUP3: + case LEGS_S4_RUP4: + case LEGS_S4_RUP5: + case LEGS_S5_LUP1: + case LEGS_S5_LUP2: + case LEGS_S5_LUP3: + case LEGS_S5_LUP4: + case LEGS_S5_LUP5: + case LEGS_S5_RUP1: + case LEGS_S5_RUP2: + case LEGS_S5_RUP3: + case LEGS_S5_RUP4: + case LEGS_S5_RUP5: + return qtrue; + break; + } + return qfalse; +} + +#define SLOPE_RECALC_INT 100 + +qboolean PM_AdjustStandAnimForSlope( void ) +{ + float diff; + float interval; + int destAnim; + int legsAnim; + #define SLOPERECALCVAR pm->ps->slopeRecalcTime //this is purely convenience + + if (!pm->ghoul2) + { //probably just changed models and not quite in sync yet + return qfalse; + } + + if ( pm->g2Bolts_LFoot == -1 || pm->g2Bolts_RFoot == -1 ) + {//need these bolts! + return qfalse; + } + + //step 1: find the 2 foot tags + PM_FootSlopeTrace( &diff, &interval ); + + //step 4: based on difference, choose one of the left/right slope-match intervals + if ( diff >= interval*5 ) + { + destAnim = LEGS_LEFTUP5; + } + else if ( diff >= interval*4 ) + { + destAnim = LEGS_LEFTUP4; + } + else if ( diff >= interval*3 ) + { + destAnim = LEGS_LEFTUP3; + } + else if ( diff >= interval*2 ) + { + destAnim = LEGS_LEFTUP2; + } + else if ( diff >= interval ) + { + destAnim = LEGS_LEFTUP1; + } + else if ( diff <= interval*-5 ) + { + destAnim = LEGS_RIGHTUP5; + } + else if ( diff <= interval*-4 ) + { + destAnim = LEGS_RIGHTUP4; + } + else if ( diff <= interval*-3 ) + { + destAnim = LEGS_RIGHTUP3; + } + else if ( diff <= interval*-2 ) + { + destAnim = LEGS_RIGHTUP2; + } + else if ( diff <= interval*-1 ) + { + destAnim = LEGS_RIGHTUP1; + } + else + { + return qfalse; + } + + legsAnim = pm->ps->legsAnim; + //adjust for current legs anim + switch ( legsAnim ) + { + case BOTH_STAND1: + + case LEGS_S1_LUP1: + case LEGS_S1_LUP2: + case LEGS_S1_LUP3: + case LEGS_S1_LUP4: + case LEGS_S1_LUP5: + case LEGS_S1_RUP1: + case LEGS_S1_RUP2: + case LEGS_S1_RUP3: + case LEGS_S1_RUP4: + case LEGS_S1_RUP5: + destAnim = LEGS_S1_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND2: + case BOTH_SABERFAST_STANCE: + case BOTH_SABERSLOW_STANCE: + case BOTH_CROUCH1IDLE: + case BOTH_CROUCH1: + case LEGS_LEFTUP1: //# On a slope with left foot 4 higher than right + case LEGS_LEFTUP2: //# On a slope with left foot 8 higher than right + case LEGS_LEFTUP3: //# On a slope with left foot 12 higher than right + case LEGS_LEFTUP4: //# On a slope with left foot 16 higher than right + case LEGS_LEFTUP5: //# On a slope with left foot 20 higher than right + case LEGS_RIGHTUP1: //# On a slope with RIGHT foot 4 higher than left + case LEGS_RIGHTUP2: //# On a slope with RIGHT foot 8 higher than left + case LEGS_RIGHTUP3: //# On a slope with RIGHT foot 12 higher than left + case LEGS_RIGHTUP4: //# On a slope with RIGHT foot 16 higher than left + case LEGS_RIGHTUP5: //# On a slope with RIGHT foot 20 higher than left + //fine + break; + case BOTH_STAND3: + case LEGS_S3_LUP1: + case LEGS_S3_LUP2: + case LEGS_S3_LUP3: + case LEGS_S3_LUP4: + case LEGS_S3_LUP5: + case LEGS_S3_RUP1: + case LEGS_S3_RUP2: + case LEGS_S3_RUP3: + case LEGS_S3_RUP4: + case LEGS_S3_RUP5: + destAnim = LEGS_S3_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND4: + case LEGS_S4_LUP1: + case LEGS_S4_LUP2: + case LEGS_S4_LUP3: + case LEGS_S4_LUP4: + case LEGS_S4_LUP5: + case LEGS_S4_RUP1: + case LEGS_S4_RUP2: + case LEGS_S4_RUP3: + case LEGS_S4_RUP4: + case LEGS_S4_RUP5: + destAnim = LEGS_S4_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND5: + case LEGS_S5_LUP1: + case LEGS_S5_LUP2: + case LEGS_S5_LUP3: + case LEGS_S5_LUP4: + case LEGS_S5_LUP5: + case LEGS_S5_RUP1: + case LEGS_S5_RUP2: + case LEGS_S5_RUP3: + case LEGS_S5_RUP4: + case LEGS_S5_RUP5: + destAnim = LEGS_S5_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND6: + default: + return qfalse; + break; + } + + //step 5: based on the chosen interval and the current legsAnim, pick the correct anim + //step 6: increment/decrement to the dest anim, not instant + if ( (legsAnim >= LEGS_LEFTUP1 && legsAnim <= LEGS_LEFTUP5) + || (legsAnim >= LEGS_S1_LUP1 && legsAnim <= LEGS_S1_LUP5) + || (legsAnim >= LEGS_S3_LUP1 && legsAnim <= LEGS_S3_LUP5) + || (legsAnim >= LEGS_S4_LUP1 && legsAnim <= LEGS_S4_LUP5) + || (legsAnim >= LEGS_S5_LUP1 && legsAnim <= LEGS_S5_LUP5) ) + {//already in left-side up + if ( destAnim > legsAnim && SLOPERECALCVAR < pm->cmd.serverTime ) + { + legsAnim++; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim < legsAnim && SLOPERECALCVAR < pm->cmd.serverTime ) + { + legsAnim--; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else //if (SLOPERECALCVAR < pm->cmd.serverTime) + { + legsAnim = destAnim; + } + + destAnim = legsAnim; + } + else if ( (legsAnim >= LEGS_RIGHTUP1 && legsAnim <= LEGS_RIGHTUP5) + || (legsAnim >= LEGS_S1_RUP1 && legsAnim <= LEGS_S1_RUP5) + || (legsAnim >= LEGS_S3_RUP1 && legsAnim <= LEGS_S3_RUP5) + || (legsAnim >= LEGS_S4_RUP1 && legsAnim <= LEGS_S4_RUP5) + || (legsAnim >= LEGS_S5_RUP1 && legsAnim <= LEGS_S5_RUP5) ) + {//already in right-side up + if ( destAnim > legsAnim && SLOPERECALCVAR < pm->cmd.serverTime ) + { + legsAnim++; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim < legsAnim && SLOPERECALCVAR < pm->cmd.serverTime ) + { + legsAnim--; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else //if (SLOPERECALCVAR < pm->cmd.serverTime) + { + legsAnim = destAnim; + } + + destAnim = legsAnim; + } + else + {//in a stand of some sort? + switch ( legsAnim ) + { + case BOTH_STAND1: + case TORSO_WEAPONREADY1: + case TORSO_WEAPONREADY2: + case TORSO_WEAPONREADY3: + case TORSO_WEAPONREADY10: + + if ( destAnim >= LEGS_S1_LUP1 && destAnim <= LEGS_S1_LUP5 ) + {//going into left side up + destAnim = LEGS_S1_LUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S1_RUP1 && destAnim <= LEGS_S1_RUP5 ) + {//going into right side up + destAnim = LEGS_S1_RUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND2: + case BOTH_SABERFAST_STANCE: + case BOTH_SABERSLOW_STANCE: + case BOTH_CROUCH1IDLE: + if ( destAnim >= LEGS_LEFTUP1 && destAnim <= LEGS_LEFTUP5 ) + {//going into left side up + destAnim = LEGS_LEFTUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_RIGHTUP1 && destAnim <= LEGS_RIGHTUP5 ) + {//going into right side up + destAnim = LEGS_RIGHTUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND3: + if ( destAnim >= LEGS_S3_LUP1 && destAnim <= LEGS_S3_LUP5 ) + {//going into left side up + destAnim = LEGS_S3_LUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S3_RUP1 && destAnim <= LEGS_S3_RUP5 ) + {//going into right side up + destAnim = LEGS_S3_RUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND4: + if ( destAnim >= LEGS_S4_LUP1 && destAnim <= LEGS_S4_LUP5 ) + {//going into left side up + destAnim = LEGS_S4_LUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S4_RUP1 && destAnim <= LEGS_S4_RUP5 ) + {//going into right side up + destAnim = LEGS_S4_RUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND5: + if ( destAnim >= LEGS_S5_LUP1 && destAnim <= LEGS_S5_LUP5 ) + {//going into left side up + destAnim = LEGS_S5_LUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S5_RUP1 && destAnim <= LEGS_S5_RUP5 ) + {//going into right side up + destAnim = LEGS_S5_RUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND6: + default: + return qfalse; + break; + } + } + //step 7: set the anim + //PM_SetAnim( SETANIM_LEGS, destAnim, SETANIM_FLAG_NORMAL, 100 ); + PM_ContinueLegsAnim(destAnim); + + return qtrue; +} + +extern int WeaponReadyLegsAnim[WP_NUM_WEAPONS]; + +//rww - slowly back out of slope leg anims, to prevent skipping between slope anims and general jittering +int PM_LegsSlopeBackTransition(int desiredAnim) +{ + int anim = pm->ps->legsAnim; + int resultingAnim = desiredAnim; + + switch ( anim ) + { + case LEGS_LEFTUP2: //# On a slope with left foot 8 higher than right + case LEGS_LEFTUP3: //# On a slope with left foot 12 higher than right + case LEGS_LEFTUP4: //# On a slope with left foot 16 higher than right + case LEGS_LEFTUP5: //# On a slope with left foot 20 higher than right + case LEGS_RIGHTUP2: //# On a slope with RIGHT foot 8 higher than left + case LEGS_RIGHTUP3: //# On a slope with RIGHT foot 12 higher than left + case LEGS_RIGHTUP4: //# On a slope with RIGHT foot 16 higher than left + case LEGS_RIGHTUP5: //# On a slope with RIGHT foot 20 higher than left + case LEGS_S1_LUP2: + case LEGS_S1_LUP3: + case LEGS_S1_LUP4: + case LEGS_S1_LUP5: + case LEGS_S1_RUP2: + case LEGS_S1_RUP3: + case LEGS_S1_RUP4: + case LEGS_S1_RUP5: + case LEGS_S3_LUP2: + case LEGS_S3_LUP3: + case LEGS_S3_LUP4: + case LEGS_S3_LUP5: + case LEGS_S3_RUP2: + case LEGS_S3_RUP3: + case LEGS_S3_RUP4: + case LEGS_S3_RUP5: + case LEGS_S4_LUP2: + case LEGS_S4_LUP3: + case LEGS_S4_LUP4: + case LEGS_S4_LUP5: + case LEGS_S4_RUP2: + case LEGS_S4_RUP3: + case LEGS_S4_RUP4: + case LEGS_S4_RUP5: + case LEGS_S5_LUP2: + case LEGS_S5_LUP3: + case LEGS_S5_LUP4: + case LEGS_S5_LUP5: + case LEGS_S5_RUP2: + case LEGS_S5_RUP3: + case LEGS_S5_RUP4: + case LEGS_S5_RUP5: + if (pm->ps->slopeRecalcTime < pm->cmd.serverTime) + { + resultingAnim = anim-1; + pm->ps->slopeRecalcTime = pm->cmd.serverTime + 8;//SLOPE_RECALC_INT; + } + else + { + resultingAnim = anim; + } + VectorClear(pm->ps->velocity); + break; + } + + return resultingAnim; +} + +/* +=============== +PM_Footsteps +=============== +*/ +static void PM_Footsteps( void ) { + float bobmove; + int old; + qboolean footstep; + int setAnimFlags = 0; + + if ( (PM_InSaberAnim( (pm->ps->legsAnim) ) && !BG_SpinningSaberAnim( (pm->ps->legsAnim) )) + || (pm->ps->legsAnim) == BOTH_STAND1 + || (pm->ps->legsAnim) == BOTH_STAND1TO2 + || (pm->ps->legsAnim) == BOTH_STAND2TO1 + || (pm->ps->legsAnim) == BOTH_STAND2 + || (pm->ps->legsAnim) == BOTH_SABERFAST_STANCE + || (pm->ps->legsAnim) == BOTH_SABERSLOW_STANCE + || (pm->ps->legsAnim) == BOTH_BUTTON_HOLD + || (pm->ps->legsAnim) == BOTH_BUTTON_RELEASE + || PM_LandingAnim( (pm->ps->legsAnim) ) + || PM_PainAnim( (pm->ps->legsAnim) )) + {//legs are in a saber anim, and not spinning, be sure to override it + setAnimFlags |= SETANIM_FLAG_OVERRIDE; + } + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0] + + pm->ps->velocity[1] * pm->ps->velocity[1] ); + + if (pm->ps->saberMove == LS_SPINATTACK) + { + PM_ContinueLegsAnim( pm->ps->torsoAnim ); + } + else if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + + // airborne leaves position in cycle intact, but doesn't advance + if ( pm->waterlevel > 1 ) + { + if (pm->xyspeed > 60) + { + PM_ContinueLegsAnim( BOTH_SWIMFORWARD ); + } + else + { + PM_ContinueLegsAnim( BOTH_SWIM_IDLE1 ); + } + } + return; + } + // if not trying to move + else if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { + if ( pm->xyspeed < 5 ) { + pm->ps->bobCycle = 0; // start at beginning of cycle again + if ( pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_RANCOR ) + { + if ( (pm->ps->eFlags2&EF2_USE_ALT_ANIM) ) + {//holding someone + PM_ContinueLegsAnim( BOTH_STAND4 ); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND4,SETANIM_FLAG_NORMAL); + } + else if ( (pm->ps->eFlags2&EF2_ALERTED) ) + {//have an enemy or have had one since we spawned + PM_ContinueLegsAnim( BOTH_STAND2 ); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND2,SETANIM_FLAG_NORMAL); + } + else + {//just stand there + PM_ContinueLegsAnim( BOTH_STAND1 ); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else if ( pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_WAMPA ) + { + if ( (pm->ps->eFlags2&EF2_USE_ALT_ANIM) ) + {//holding a victim + PM_ContinueLegsAnim( BOTH_STAND2 ); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND2,SETANIM_FLAG_NORMAL); + } + else + {//not holding a victim + PM_ContinueLegsAnim( BOTH_STAND1 ); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else if ( (pm->ps->pm_flags & PMF_DUCKED) || (pm->ps->pm_flags & PMF_ROLLING) ) { + if ((pm->ps->legsAnim) != BOTH_CROUCH1IDLE) + { + PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1IDLE, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim( BOTH_CROUCH1IDLE ); + } + } else { + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { + ///???? continue legs anim on a torso anim...??!!! + //yeah.. the anim has a valid pose for the legs, it uses it (you can't move while using disruptor) + PM_ContinueLegsAnim( TORSO_WEAPONREADY4 ); + } + else + { + if (pm->ps->weapon == WP_SABER && BG_SabersOff( pm->ps ) ) + { + if (!PM_AdjustStandAnimForSlope()) + { + //PM_ContinueLegsAnim( BOTH_STAND1 ); + PM_ContinueLegsAnim(PM_LegsSlopeBackTransition(BOTH_STAND1)); + } + } + else + { + if (pm->ps->weapon != WP_SABER || !PM_AdjustStandAnimForSlope()) + { + if (pm->ps->weapon == WP_SABER) + { + PM_ContinueLegsAnim(PM_LegsSlopeBackTransition(PM_GetSaberStance())); + } + else + { + PM_ContinueLegsAnim(PM_LegsSlopeBackTransition(WeaponReadyLegsAnim[pm->ps->weapon])); + } + } + } + } + } + } + return; + } + + + footstep = qfalse; + + if (pm->ps->saberMove == LS_SPINATTACK) + { + bobmove = 0.2f; + PM_ContinueLegsAnim( pm->ps->torsoAnim ); + } + else if ( pm->ps->pm_flags & PMF_DUCKED ) + { + int rolled = 0; + + bobmove = 0.5; // ducked characters bob much faster + + if ( ( PM_RunningAnim( pm->ps->legsAnim ) || PM_CanRollFromSoulCal( pm->ps ) ) && + !BG_InRoll(pm->ps, pm->ps->legsAnim) ) + {//roll! + rolled = PM_TryRoll(); + } + if ( !rolled ) + { //if the roll failed or didn't attempt, do standard crouching anim stuff. + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + if ((pm->ps->legsAnim) != BOTH_CROUCH1WALKBACK) + { + PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALKBACK, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim( BOTH_CROUCH1WALKBACK ); + } + } + else { + if ((pm->ps->legsAnim) != BOTH_CROUCH1WALK) + { + PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALK, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim( BOTH_CROUCH1WALK ); + } + } + } + else + { //otherwise send us into the roll + pm->ps->legsTimer = 0; + pm->ps->legsAnim = 0; + PM_SetAnim(SETANIM_BOTH,rolled,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150); + PM_AddEventWithParm( EV_ROLL, 0 ); + pm->maxs[2] = pm->ps->crouchheight;//CROUCH_MAXS_2; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + pm->ps->pm_flags &= ~PMF_DUCKED; + pm->ps->pm_flags |= PMF_ROLLING; + } + } + else if ((pm->ps->pm_flags & PMF_ROLLING) && !BG_InRoll(pm->ps, pm->ps->legsAnim) && + !PM_InRollComplete(pm->ps, pm->ps->legsAnim)) + { + bobmove = 0.5; // ducked characters bob much faster + + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + if ((pm->ps->legsAnim) != BOTH_CROUCH1WALKBACK) + { + PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALKBACK, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim( BOTH_CROUCH1WALKBACK ); + } + } + else + { + if ((pm->ps->legsAnim) != BOTH_CROUCH1WALK) + { + PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALK, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim( BOTH_CROUCH1WALK ); + } + } + } + else + { + int desiredAnim = -1; + + if ((pm->ps->legsAnim == BOTH_FORCELAND1 || + pm->ps->legsAnim == BOTH_FORCELANDBACK1 || + pm->ps->legsAnim == BOTH_FORCELANDRIGHT1 || + pm->ps->legsAnim == BOTH_FORCELANDLEFT1) && + pm->ps->legsTimer > 0) + { //let it finish first + bobmove = 0.2f; + } + else if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) + {//running + bobmove = 0.4f; // faster speeds bob faster + if ( pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_WAMPA ) + { + if ( (pm->ps->eFlags2&EF2_USE_ALT_ANIM) ) + {//full on run, on all fours + desiredAnim = BOTH_RUN1; + } + else + {//regular, upright run + desiredAnim = BOTH_RUN2; + } + } + else if ( pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_RANCOR ) + {//no run anims + if ( (pm->ps->pm_flags&PMF_BACKWARDS_RUN) ) + { + desiredAnim = BOTH_WALKBACK1; + } + else + { + desiredAnim = BOTH_WALK1; + } + } + else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + switch (pm->ps->fd.saberAnimLevel) + { + case SS_STAFF: + if ( pm->ps->saberHolstered > 1 ) + {//saber off + desiredAnim = BOTH_RUNBACK1; + } + else + { + //desiredAnim = BOTH_RUNBACK_STAFF; + //hmm.. stuff runback anim is pretty messed up for some reason. + desiredAnim = BOTH_RUNBACK2; + } + break; + case SS_DUAL: + if ( pm->ps->saberHolstered > 1 ) + {//sabers off + desiredAnim = BOTH_RUNBACK1; + } + else + { + //desiredAnim = BOTH_RUNBACK_DUAL; + //and so is the dual + desiredAnim = BOTH_RUNBACK2; + } + break; + default: + if ( pm->ps->saberHolstered ) + {//saber off + desiredAnim = BOTH_RUNBACK1; + } + else + { + desiredAnim = BOTH_RUNBACK2; + } + break; + } + } + else + { + switch (pm->ps->fd.saberAnimLevel) + { + case SS_STAFF: + if ( pm->ps->saberHolstered > 1 ) + {//blades off + desiredAnim = BOTH_RUN1; + } + else if ( pm->ps->saberHolstered == 1 ) + {//1 blade on + desiredAnim = BOTH_RUN2; + } + else + { + if (pm->ps->fd.forcePowersActive & (1<ps->saberHolstered > 1 ) + {//blades off + desiredAnim = BOTH_RUN1; + } + else if ( pm->ps->saberHolstered == 1 ) + {//1 saber on + desiredAnim = BOTH_RUN2; + } + else + { + desiredAnim = BOTH_RUN_DUAL; + } + break; + default: + if ( pm->ps->saberHolstered ) + {//saber off + desiredAnim = BOTH_RUN1; + } + else + { + desiredAnim = BOTH_RUN2; + } + break; + } + } + footstep = qtrue; + } + else + { + bobmove = 0.2f; // walking bobs slow + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + switch (pm->ps->fd.saberAnimLevel) + { + case SS_STAFF: + if ( pm->ps->saberHolstered > 1 ) + { + desiredAnim = BOTH_WALKBACK1; + } + else if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALKBACK2; + } + else + { + desiredAnim = BOTH_WALKBACK_STAFF; + } + break; + case SS_DUAL: + if ( pm->ps->saberHolstered > 1 ) + { + desiredAnim = BOTH_WALKBACK1; + } + else if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALKBACK2; + } + else + { + desiredAnim = BOTH_WALKBACK_DUAL; + } + break; + default: + if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALKBACK1; + } + else + { + desiredAnim = BOTH_WALKBACK2; + } + break; + } + } + else + { + if ( BG_SabersOff( pm->ps ) ) + { + desiredAnim = BOTH_WALK1; + } + else + { + switch (pm->ps->fd.saberAnimLevel) + { + case SS_STAFF: + if ( pm->ps->saberHolstered > 1 ) + { + desiredAnim = BOTH_WALK1; + } + else if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALK2; + } + else + { + desiredAnim = BOTH_WALK_STAFF; + } + break; + case SS_DUAL: + if ( pm->ps->saberHolstered > 1 ) + { + desiredAnim = BOTH_WALK1; + } + else if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALK2; + } + else + { + desiredAnim = BOTH_WALK_DUAL; + } + break; + default: + if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALK1; + } + else + { + desiredAnim = BOTH_WALK2; + } + break; + } + } + } + } + + if (desiredAnim != -1) + { + int ires = PM_LegsSlopeBackTransition(desiredAnim); + + if ((pm->ps->legsAnim) != desiredAnim && ires == desiredAnim) + { + PM_SetAnim(SETANIM_LEGS, desiredAnim, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim(ires); + } + } + } + + // check for footstep / splash sounds + old = pm->ps->bobCycle; + pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; + + // if we just crossed a cycle boundary, play an apropriate footstep event + if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) + { + pm->ps->footstepTime = pm->cmd.serverTime + 300; + if ( pm->waterlevel == 1 ) { + // splashing + PM_AddEvent( EV_FOOTSPLASH ); + } else if ( pm->waterlevel == 2 ) { + // wading / swimming at surface + PM_AddEvent( EV_SWIM ); + } else if ( pm->waterlevel == 3 ) { + // no sound when completely underwater + } + } +} + +/* +============== +PM_WaterEvents + +Generate sound events for entering and leaving water +============== +*/ +static void PM_WaterEvents( void ) { // FIXME? +#ifdef QAGAME + qboolean impact_splash = qfalse; +#endif + // + // if just entered a water volume, play a sound + // + if (!pml.previous_waterlevel && pm->waterlevel) { +#ifdef QAGAME + if ( VectorLengthSquared( pm->ps->velocity ) > 40000 ) + { + impact_splash = qtrue; + } +#endif + PM_AddEvent( EV_WATER_TOUCH ); + } + + // + // if just completely exited a water volume, play a sound + // + if (pml.previous_waterlevel && !pm->waterlevel) { +#ifdef QAGAME + if ( VectorLengthSquared( pm->ps->velocity ) > 40000 ) + { + impact_splash = qtrue; + } +#endif + PM_AddEvent( EV_WATER_LEAVE ); + } + +#ifdef QAGAME + if ( impact_splash ) + { + //play the splash effect + trace_t tr; + vec3_t start, end; + + + VectorCopy( pm->ps->origin, start ); + VectorCopy( pm->ps->origin, end ); + + // FIXME: set start and end better + start[2] += 10; + end[2] -= 40; + + pm->trace( &tr, start, vec3_origin, vec3_origin, end, pm->ps->clientNum, MASK_WATER ); + + if ( tr.fraction < 1.0f ) + { + if ( (tr.contents&CONTENTS_LAVA) ) + { + G_PlayEffect( EFFECT_LAVA_SPLASH, tr.endpos, tr.plane.normal ); + } + else if ( (tr.contents&CONTENTS_SLIME) ) + { + G_PlayEffect( EFFECT_ACID_SPLASH, tr.endpos, tr.plane.normal ); + } + else //must be water + { + G_PlayEffect( EFFECT_WATER_SPLASH, tr.endpos, tr.plane.normal ); + } + } + } +#endif + + // + // check for head just going under water + // + if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { + PM_AddEvent( EV_WATER_UNDER ); + } + + // + // check for head just coming out of water + // + if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { + PM_AddEvent( EV_WATER_CLEAR ); + } +} + + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +void PM_BeginWeaponChange( int weapon ) { + if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) { + return; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + return; + } + + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + return; + } + + // turn of any kind of zooming when weapon switching. + if (pm->ps->zoomMode) + { + pm->ps->zoomMode = 0; + pm->ps->zoomTime = pm->ps->commandTime; + } + + PM_AddEventWithParm( EV_CHANGE_WEAPON, weapon ); + pm->ps->weaponstate = WEAPON_DROPPING; + pm->ps->weaponTime += 200; + //PM_StartTorsoAnim( TORSO_DROPWEAP1 ); + PM_SetAnim(SETANIM_TORSO, TORSO_DROPWEAP1, SETANIM_FLAG_OVERRIDE, 0); +} + + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +void PM_FinishWeaponChange( void ) { + int weapon; + + weapon = pm->cmd.weapon; + if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { + weapon = WP_NONE; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + weapon = WP_NONE; + } + + if (weapon == WP_SABER) + { + PM_SetSaberMove(LS_DRAW); + } + else + { + //PM_StartTorsoAnim( TORSO_RAISEWEAP1); + PM_SetAnim(SETANIM_TORSO, TORSO_RAISEWEAP1, SETANIM_FLAG_OVERRIDE, 0); + } + pm->ps->weapon = weapon; + pm->ps->weaponstate = WEAPON_RAISING; + pm->ps->weaponTime += 250; +} + +void PM_RocketLock( float lockDist, qboolean vehicleLock ) +{ + // Not really a charge weapon, but we still want to delay fire until the button comes up so that we can + // implement our alt-fire locking stuff + vec3_t ang; + trace_t tr; + + vec3_t muzzleOffPoint, muzzlePoint, forward, right, up; + + if ( vehicleLock ) + { + AngleVectors( pm->ps->viewangles, forward, right, up ); + VectorCopy( pm->ps->origin, muzzlePoint ); + VectorMA( muzzlePoint, lockDist, forward, ang ); + } + else + { + AngleVectors( pm->ps->viewangles, forward, right, up ); + + AngleVectors(pm->ps->viewangles, ang, NULL, NULL); + + VectorCopy( pm->ps->origin, muzzlePoint ); + VectorCopy(WP_MuzzlePoint[WP_ROCKET_LAUNCHER], muzzleOffPoint); + + VectorMA(muzzlePoint, muzzleOffPoint[0], forward, muzzlePoint); + VectorMA(muzzlePoint, muzzleOffPoint[1], right, muzzlePoint); + muzzlePoint[2] += pm->ps->viewheight + muzzleOffPoint[2]; + ang[0] = muzzlePoint[0] + ang[0]*lockDist; + ang[1] = muzzlePoint[1] + ang[1]*lockDist; + ang[2] = muzzlePoint[2] + ang[2]*lockDist; + } + + + pm->trace(&tr, muzzlePoint, NULL, NULL, ang, pm->ps->clientNum, MASK_PLAYERSOLID); + + if (tr.fraction != 1 && tr.entityNum < ENTITYNUM_NONE && tr.entityNum != pm->ps->clientNum) + { + bgEntity_t *bgEnt = PM_BGEntForNum(tr.entityNum); + if ( bgEnt && (bgEnt->s.powerups&PW_CLOAKED) ) + { + pm->ps->rocketLockIndex = ENTITYNUM_NONE; + pm->ps->rocketLockTime = 0; + } + else if (bgEnt && (bgEnt->s.eType == ET_PLAYER || bgEnt->s.eType == ET_NPC)) + { + if (pm->ps->rocketLockIndex == ENTITYNUM_NONE) + { + pm->ps->rocketLockIndex = tr.entityNum; + pm->ps->rocketLockTime = pm->cmd.serverTime; + } + else if (pm->ps->rocketLockIndex != tr.entityNum && pm->ps->rocketTargetTime < pm->cmd.serverTime) + { + pm->ps->rocketLockIndex = tr.entityNum; + pm->ps->rocketLockTime = pm->cmd.serverTime; + } + else if (pm->ps->rocketLockIndex == tr.entityNum) + { + if (pm->ps->rocketLockTime == -1) + { + pm->ps->rocketLockTime = pm->ps->rocketLastValidTime; + } + } + + if (pm->ps->rocketLockIndex == tr.entityNum) + { + pm->ps->rocketTargetTime = pm->cmd.serverTime + 500; + } + } + else if (!vehicleLock) + { + if (pm->ps->rocketTargetTime < pm->cmd.serverTime) + { + pm->ps->rocketLockIndex = ENTITYNUM_NONE; + pm->ps->rocketLockTime = 0; + } + } + } + else if (pm->ps->rocketTargetTime < pm->cmd.serverTime) + { + pm->ps->rocketLockIndex = ENTITYNUM_NONE; + pm->ps->rocketLockTime = 0; + } + else + { + if (pm->ps->rocketLockTime != -1) + { + pm->ps->rocketLastValidTime = pm->ps->rocketLockTime; + } + pm->ps->rocketLockTime = -1; + } +} + + +//--------------------------------------- +static qboolean PM_DoChargedWeapons( qboolean vehicleRocketLock, bgEntity_t *veh ) +//--------------------------------------- +{ + qboolean charging = qfalse, + altFire = qfalse; + + if ( vehicleRocketLock ) + { + if ( (pm->cmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) + {//actually charging + if ( veh + && veh->m_pVehicle ) + {//just make sure we have this veh info + if ( ( (pm->cmd.buttons&BUTTON_ATTACK) + &&g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID].fHoming + &&pm->ps->ammo[0]>=g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID].iAmmoPerShot ) + || + ( (pm->cmd.buttons&BUTTON_ALT_ATTACK) + &&g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID].fHoming + &&pm->ps->ammo[1]>=g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID].iAmmoPerShot ) ) + {//pressing the appropriate fire button for the lock-on/charging weapon + PM_RocketLock(16384, qtrue); + charging = qtrue; + } + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + altFire = qtrue; + } + } + } + //else, let go and should fire now + } + else + { + // If you want your weapon to be a charging weapon, just set this bit up + switch( pm->ps->weapon ) + { + //------------------ + case WP_BRYAR_PISTOL: + + // alt-fire charges the weapon + //if ( pm->gametype == GT_SIEGE ) + if (1) + { + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + charging = qtrue; + altFire = qtrue; + } + } + break; + + case WP_CONCUSSION: + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + altFire = qtrue; + } + break; + + case WP_BRYAR_OLD: + + // alt-fire charges the weapon + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + charging = qtrue; + altFire = qtrue; + } + break; + + //------------------ + case WP_BOWCASTER: + + // primary fire charges the weapon + if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + charging = qtrue; + } + break; + + //------------------ + case WP_ROCKET_LAUNCHER: + if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) + && pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] >= weaponData[pm->ps->weapon].altEnergyPerShot ) + { + PM_RocketLock(2048,qfalse); + charging = qtrue; + altFire = qtrue; + } + break; + + //------------------ + case WP_THERMAL: + + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + altFire = qtrue; // override default of not being an alt-fire + charging = qtrue; + } + else if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + charging = qtrue; + } + break; + + case WP_DEMP2: + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + altFire = qtrue; // override default of not being an alt-fire + charging = qtrue; + } + break; + + case WP_DISRUPTOR: + if ((pm->cmd.buttons & BUTTON_ATTACK) && + pm->ps->zoomMode == 1 && + pm->ps->zoomLocked) + { + if (!pm->cmd.forwardmove && + !pm->cmd.rightmove && + pm->cmd.upmove <= 0) + { + charging = qtrue; + altFire = qtrue; + + FF_Play( fffx_StartConst ); + } + else + { + charging = qfalse; + altFire = qfalse; + + FF_Play( fffx_StopConst ); + } + } + else + { + FF_Play( fffx_StopConst ); + } + + if (pm->ps->zoomMode != 1 && + pm->ps->weaponstate == WEAPON_CHARGING_ALT) + { + pm->ps->weaponstate = WEAPON_READY; + charging = qfalse; + altFire = qfalse; + FF_Play( fffx_StopConst ); + } + + } // end switch + } + + // set up the appropriate weapon state based on the button that's down. + // Note that we ALWAYS return if charging is set ( meaning the buttons are still down ) + if ( charging ) + { + if ( altFire ) + { + if ( pm->ps->weaponstate != WEAPON_CHARGING_ALT ) + { + // charge isn't started, so do it now + pm->ps->weaponstate = WEAPON_CHARGING_ALT; + pm->ps->weaponChargeTime = pm->cmd.serverTime; + pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].altChargeSubTime; + +#ifdef _DEBUG + Com_Printf("Starting charge\n"); +#endif + assert(pm->ps->weapon > WP_NONE); + BG_AddPredictableEventToPlayerstate(EV_WEAPON_CHARGE_ALT, pm->ps->weapon, pm->ps); + } + + if ( vehicleRocketLock ) + {//check vehicle ammo + if ( veh && pm->ps->ammo[1] < g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID].iAmmoPerShot ) + { + pm->ps->weaponstate = WEAPON_CHARGING_ALT; + goto rest; + } + } + else if (pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < (weaponData[pm->ps->weapon].altChargeSub+weaponData[pm->ps->weapon].altEnergyPerShot)) + { + pm->ps->weaponstate = WEAPON_CHARGING_ALT; + + goto rest; + } + else if ((pm->cmd.serverTime - pm->ps->weaponChargeTime) < weaponData[pm->ps->weapon].altMaxCharge) + { + if (pm->ps->weaponChargeSubtractTime < pm->cmd.serverTime) + { + pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= weaponData[pm->ps->weapon].altChargeSub; + pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].altChargeSubTime; + } + } + } + else + { + if ( pm->ps->weaponstate != WEAPON_CHARGING ) + { + // charge isn't started, so do it now + pm->ps->weaponstate = WEAPON_CHARGING; + pm->ps->weaponChargeTime = pm->cmd.serverTime; + pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].chargeSubTime; + +#ifdef _DEBUG + Com_Printf("Starting charge\n"); +#endif + BG_AddPredictableEventToPlayerstate(EV_WEAPON_CHARGE, pm->ps->weapon, pm->ps); + } + + if ( vehicleRocketLock ) + { + if ( veh && pm->ps->ammo[0] < g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID].iAmmoPerShot ) + {//check vehicle ammo + pm->ps->weaponstate = WEAPON_CHARGING; + goto rest; + } + } + else if (pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < (weaponData[pm->ps->weapon].chargeSub+weaponData[pm->ps->weapon].energyPerShot)) + { + pm->ps->weaponstate = WEAPON_CHARGING; + + goto rest; + } + else if ((pm->cmd.serverTime - pm->ps->weaponChargeTime) < weaponData[pm->ps->weapon].maxCharge) + { + if (pm->ps->weaponChargeSubtractTime < pm->cmd.serverTime) + { + pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= weaponData[pm->ps->weapon].chargeSub; + pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].chargeSubTime; + } + } + } + + return qtrue; // short-circuit rest of weapon code + } +rest: + // Only charging weapons should be able to set these states...so.... + // let's see which fire mode we need to set up now that the buttons are up + if ( pm->ps->weaponstate == WEAPON_CHARGING ) + { + // weapon has a charge, so let us do an attack +#ifdef _DEBUG + Com_Printf("Firing. Charge time=%d\n", pm->cmd.serverTime - pm->ps->weaponChargeTime); +#endif + + // dumb, but since we shoot a charged weapon on button-up, we need to repress this button for now + pm->cmd.buttons |= BUTTON_ATTACK; + pm->ps->eFlags |= EF_FIRING; + } + else if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT ) + { + // weapon has a charge, so let us do an alt-attack +#ifdef _DEBUG + Com_Printf("Firing. Charge time=%d\n", pm->cmd.serverTime - pm->ps->weaponChargeTime); +#endif + + // dumb, but since we shoot a charged weapon on button-up, we need to repress this button for now + pm->cmd.buttons |= BUTTON_ALT_ATTACK; + pm->ps->eFlags |= (EF_FIRING|EF_ALT_FIRING); + } + + return qfalse; // continue with the rest of the weapon code +} + + +#define BOWCASTER_CHARGE_UNIT 200.0f // bowcaster charging gives us one more unit every 200ms--if you change this, you'll have to do the same in g_weapon +#define BRYAR_CHARGE_UNIT 200.0f // bryar charging gives us one more unit every 200ms--if you change this, you'll have to do the same in g_weapon + +int PM_ItemUsable(playerState_t *ps, int forcedUse) +{ + vec3_t fwd, fwdorg, dest, pos; + vec3_t yawonly; + vec3_t mins, maxs; + vec3_t trtest; + trace_t tr; + + if (ps->m_iVehicleNum) + { + return 0; + } + + if (ps->pm_flags & PMF_USE_ITEM_HELD) + { //force to let go first + return 0; + } + + if (ps->duelInProgress) + { //not allowed to use holdables while in a private duel. + return 0; + } + + if (!forcedUse) + { + forcedUse = bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag; + } + + if (!BG_IsItemSelectable(ps, forcedUse)) + { + return 0; + } + + switch (forcedUse) + { + case HI_MEDPAC: + case HI_MEDPAC_BIG: + if (ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH]) + { + return 0; + } + if (ps->stats[STAT_HEALTH] <= 0 || + (ps->eFlags & EF_DEAD)) + { + return 0; + } + + return 1; + case HI_SEEKER: + if (ps->eFlags & EF_SEEKERDRONE) + { + PM_AddEventWithParm(EV_ITEMUSEFAIL, SEEKER_ALREADYDEPLOYED); + return 0; + } + + return 1; + case HI_SENTRY_GUN: + if (ps->fd.sentryDeployed) + { + PM_AddEventWithParm(EV_ITEMUSEFAIL, SENTRY_ALREADYPLACED); + return 0; + } + + yawonly[ROLL] = 0; + yawonly[PITCH] = 0; + yawonly[YAW] = ps->viewangles[YAW]; + + VectorSet( mins, -8, -8, 0 ); + VectorSet( maxs, 8, 8, 24 ); + + AngleVectors(yawonly, fwd, NULL, NULL); + + fwdorg[0] = ps->origin[0] + fwd[0]*64; + fwdorg[1] = ps->origin[1] + fwd[1]*64; + fwdorg[2] = ps->origin[2] + fwd[2]*64; + + trtest[0] = fwdorg[0] + fwd[0]*16; + trtest[1] = fwdorg[1] + fwd[1]*16; + trtest[2] = fwdorg[2] + fwd[2]*16; + + pm->trace(&tr, ps->origin, mins, maxs, trtest, ps->clientNum, MASK_PLAYERSOLID); + + if ((tr.fraction != 1 && tr.entityNum != ps->clientNum) || tr.startsolid || tr.allsolid) + { + PM_AddEventWithParm(EV_ITEMUSEFAIL, SENTRY_NOROOM); + return 0; + } + + return 1; + case HI_SHIELD: + mins[0] = -8; + mins[1] = -8; + mins[2] = 0; + + maxs[0] = 8; + maxs[1] = 8; + maxs[2] = 8; + + AngleVectors (ps->viewangles, fwd, NULL, NULL); + fwd[2] = 0; + VectorMA(ps->origin, 64, fwd, dest); + pm->trace(&tr, ps->origin, mins, maxs, dest, ps->clientNum, MASK_SHOT ); + if (tr.fraction > 0.9 && !tr.startsolid && !tr.allsolid) + { + VectorCopy(tr.endpos, pos); + VectorSet( dest, pos[0], pos[1], pos[2] - 4096 ); + pm->trace( &tr, pos, mins, maxs, dest, ps->clientNum, MASK_SOLID ); + if ( !tr.startsolid && !tr.allsolid ) + { + return 1; + } + } + PM_AddEventWithParm(EV_ITEMUSEFAIL, SHIELD_NOROOM); + return 0; + case HI_JETPACK: //check for stuff here? + return 1; + case HI_HEALTHDISP: + return 1; + case HI_AMMODISP: + return 1; + case HI_EWEB: + return 1; + case HI_CLOAK: //check for stuff here? + return 1; + default: + return 1; + } +} + +//cheesy vehicle weapon hackery +qboolean PM_CanSetWeaponAnims(void) +{ + if (pm->ps->m_iVehicleNum) + { + return qfalse; + } + + return qtrue; +} + +//perform player anim overrides while on vehicle. +extern int PM_irand_timesync(int val1, int val2); +void PM_VehicleWeaponAnimate(void) +{ + bgEntity_t *veh = pm_entVeh; + Vehicle_t *pVeh; + int iFlags = 0, iBlend = 0, Anim = -1; + + if (!veh || + !veh->m_pVehicle || + !veh->m_pVehicle->m_pPilot || + !veh->m_pVehicle->m_pPilot->playerState || + pm->ps->clientNum != veh->m_pVehicle->m_pPilot->playerState->clientNum) + { //make sure the vehicle exists, and its pilot is this player + return; + } + + pVeh = veh->m_pVehicle; + + if (pVeh->m_pVehicleInfo->type == VH_WALKER || + pVeh->m_pVehicleInfo->type == VH_FIGHTER) + { //slightly hacky I guess, but whatever. + return; + } +backAgain: + // If they're firing, play the right fire animation. + if ( pm->cmd.buttons & ( BUTTON_ATTACK | BUTTON_ALT_ATTACK ) ) + { + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + iBlend = 200; + + switch ( pm->ps->weapon ) + { + case WP_SABER: + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { //don't do anything.. I guess. + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + goto backAgain; + } + // If we're already in an attack animation, leave (let it continue). + if (pm->ps->torsoTimer <= 0) + { //we'll be starting a new attack + PM_AddEvent(EV_SABER_ATTACK); + } + + //just set it to something so we have a proper trail. This is a stupid + //hack (much like the rest of this function) + pm->ps->saberMove = LS_R_TL2BR; + + if ( pm->ps->torsoTimer > 0 && (pm->ps->torsoAnim == BOTH_VS_ATR_S || + pm->ps->torsoAnim == BOTH_VS_ATL_S) ) + { + /* + //FIXME: no need to even call the PM_SetAnim at all in this case + Anim = (animNumber_t)pm->ps->torsoAnim; + iFlags = SETANIM_FLAG_NORMAL; + break; + */ + return; + } + + // Start the attack. + if ( pm->cmd.rightmove > 0 ) //right side attack + { + Anim = BOTH_VS_ATR_S; + } + else if ( pm->cmd.rightmove < 0 ) //left-side attack + { + Anim = BOTH_VS_ATL_S; + } + else //random + { + //FIXME: alternate back and forth or auto-aim? + //if ( !Q_irand( 0, 1 ) ) + if (!PM_irand_timesync(0, 1)) + { + Anim = BOTH_VS_ATR_S; + } + else + { + Anim = BOTH_VS_ATL_S; + } + } + + if (pm->ps->torsoTimer <= 0) + { //restart the anim if we are already in it (and finished) + iFlags |= SETANIM_FLAG_RESTART; + } + break; + + case WP_BLASTER: + // Override the shoot anim. + if ( pm->ps->torsoAnim == BOTH_ATTACK3 ) + { + if ( pm->cmd.rightmove > 0 ) //right side attack + { + Anim = BOTH_VS_ATR_G; + } + else if ( pm->cmd.rightmove < 0 ) //left side + { + Anim = BOTH_VS_ATL_G; + } + else //frontal + { + Anim = BOTH_VS_ATF_G; + } + } + break; + + default: + Anim = BOTH_VS_IDLE; + break; + } + } + else if (veh->playerState && veh->playerState->speed < 0 && + pVeh->m_pVehicleInfo->type == VH_ANIMAL) + { //tauntaun is going backwards + Anim = BOTH_VT_WALK_REV; + iBlend = 600; + } + else if (veh->playerState && veh->playerState->speed < 0 && + pVeh->m_pVehicleInfo->type == VH_SPEEDER) + { //speeder is going backwards + Anim = BOTH_VS_REV; + iBlend = 600; + } + // They're not firing so play the Idle for the weapon. + else + { + iFlags = SETANIM_FLAG_NORMAL; + + switch ( pm->ps->weapon ) + { + case WP_SABER: + if ( BG_SabersOff( pm->ps ) ) + { //saber holstered, normal idle + Anim = BOTH_VS_IDLE; + } + // In the Air. + //else if ( pVeh->m_ulFlags & VEH_FLYING ) + else if (0) + { + iBlend = 800; + Anim = BOTH_VS_AIR_G; + iFlags = SETANIM_FLAG_OVERRIDE; + } + // Crashing. + //else if ( pVeh->m_ulFlags & VEH_CRASHING ) + else if (0) + { + pVeh->m_ulFlags &= ~VEH_CRASHING; // Remove the flag, we are doing the animation. + iBlend = 800; + Anim = BOTH_VS_LAND_SR; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + } + else + { + Anim = BOTH_VS_IDLE_SR; + } + break; + + case WP_BLASTER: + // In the Air. + //if ( pVeh->m_ulFlags & VEH_FLYING ) + if (0) + { + iBlend = 800; + Anim = BOTH_VS_AIR_G; + iFlags = SETANIM_FLAG_OVERRIDE; + } + // Crashing. + //else if ( pVeh->m_ulFlags & VEH_CRASHING ) + else if (0) + { + pVeh->m_ulFlags &= ~VEH_CRASHING; // Remove the flag, we are doing the animation. + iBlend = 800; + Anim = BOTH_VS_LAND_G; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + } + else + { + Anim = BOTH_VS_IDLE_G; + } + break; + + default: + Anim = BOTH_VS_IDLE; + break; + } + } + + if (Anim != -1) + { //override it + if (pVeh->m_pVehicleInfo->type == VH_ANIMAL) + { //agh.. remap anims for the tauntaun + switch (Anim) + { + case BOTH_VS_IDLE: + if (veh->playerState && veh->playerState->speed > 0) + { + if (veh->playerState->speed > pVeh->m_pVehicleInfo->speedMax) + { //turbo + Anim = BOTH_VT_TURBO; + } + else + { + Anim = BOTH_VT_RUN_FWD; + } + } + else + { + Anim = BOTH_VT_IDLE; + } + break; + case BOTH_VS_ATR_S: + Anim = BOTH_VT_ATR_S; + break; + case BOTH_VS_ATL_S: + Anim = BOTH_VT_ATL_S; + break; + case BOTH_VS_ATR_G: + Anim = BOTH_VT_ATR_G; + break; + case BOTH_VS_ATL_G: + Anim = BOTH_VT_ATL_G; + break; + case BOTH_VS_ATF_G: + Anim = BOTH_VT_ATF_G; + break; + case BOTH_VS_IDLE_SL: + Anim = BOTH_VT_IDLE_S; + break; + case BOTH_VS_IDLE_SR: + Anim = BOTH_VT_IDLE_S; + break; + case BOTH_VS_IDLE_G: + Anim = BOTH_VT_IDLE_G; + break; + + //should not happen for tauntaun: + case BOTH_VS_AIR_G: + case BOTH_VS_LAND_SL: + case BOTH_VS_LAND_SR: + case BOTH_VS_LAND_G: + return; + default: + break; + } + } + + PM_SetAnim(SETANIM_BOTH, Anim, iFlags, iBlend); + } +} + +/* +============== +PM_Weapon + +Generates weapon events and modifes the weapon counter +============== +*/ +extern int PM_KickMoveForConditions(void); +static void PM_Weapon( void ) +{ + int addTime; + int amount; + int killAfterItem = 0; + bgEntity_t *veh = NULL; + qboolean vehicleRocketLock = qfalse; + +#ifdef QAGAME + if (pm->ps->clientNum >= MAX_CLIENTS && + pm->ps->weapon == WP_NONE && + pm->cmd.weapon == WP_NONE && + pm_entSelf) + { //npc with no weapon + gentity_t *gent = (gentity_t *)pm_entSelf; + if (gent->inuse && gent->client && + !gent->localAnimIndex) + { //humanoid + pm->ps->torsoAnim = pm->ps->legsAnim; + pm->ps->torsoTimer = pm->ps->legsTimer; + return; + } + } +#endif + + if (!pm->ps->emplacedIndex && + pm->ps->weapon == WP_EMPLACED_GUN) + { //oh no! + int i = 0; + int weap = -1; + + while (i < WP_NUM_WEAPONS) + { + if ((pm->ps->stats[STAT_WEAPONS] & (1 << i)) && i != WP_NONE) + { //this one's good + weap = i; + break; + } + i++; + } + + if (weap != -1) + { + pm->cmd.weapon = weap; + pm->ps->weapon = weap; + return; + } + } + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + { //riding a vehicle + veh = pm_entVeh; + if ( veh && + (veh->m_pVehicle && veh->m_pVehicle->m_pVehicleInfo->type == VH_WALKER || veh->m_pVehicle && veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) ) + {//riding a walker/fighter + //keep saber off, do no weapon stuff at all! + pm->ps->saberHolstered = 2; +#ifdef QAGAME + pm->cmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); +#else + if ( g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID].fHoming + || g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID].fHoming ) + {//our vehicle uses a rocket launcher, so do the normal checks + vehicleRocketLock = qtrue; + pm->cmd.buttons &= ~BUTTON_ATTACK; + } + else + { + pm->cmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); + } +#endif + } + } + + if (pm->ps->weapon != WP_DISRUPTOR //not using disruptor + && pm->ps->weapon != WP_ROCKET_LAUNCHER//not using rocket launcher + && pm->ps->weapon != WP_THERMAL//not using thermals + && !pm->ps->m_iVehicleNum )//not a vehicle or in a vehicle + { //check for exceeding max charge time if not using disruptor or rocket launcher or thermals + if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT ) + { + int timeDif = (pm->cmd.serverTime - pm->ps->weaponChargeTime); + + if (timeDif > MAX_WEAPON_CHARGE_TIME) + { + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + } + } + + if ( pm->ps->weaponstate == WEAPON_CHARGING ) + { + int timeDif = (pm->cmd.serverTime - pm->ps->weaponChargeTime); + + if (timeDif > MAX_WEAPON_CHARGE_TIME) + { + pm->cmd.buttons &= ~BUTTON_ATTACK; + } + } + } + + if (pm->ps->forceHandExtend == HANDEXTEND_WEAPONREADY && + PM_CanSetWeaponAnims()) + { //reset into weapon stance + if (pm->ps->weapon != WP_SABER && pm->ps->weapon != WP_MELEE && !PM_IsRocketTrooper()) + { //saber handles its own anims + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { + //PM_StartTorsoAnim( TORSO_WEAPONREADY4 ); + PM_StartTorsoAnim( TORSO_RAISEWEAP1); + } + else + { + if (pm->ps->weapon == WP_EMPLACED_GUN) + { + PM_StartTorsoAnim( BOTH_GUNSIT1 ); + } + else + { + //PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + PM_StartTorsoAnim( TORSO_RAISEWEAP1); + } + } + } + + //we now go into a weapon raise anim after every force hand extend. + //this is so that my holster-view-weapon-when-hand-extend stuff works. + pm->ps->weaponstate = WEAPON_RAISING; + pm->ps->weaponTime += 250; + + pm->ps->forceHandExtend = HANDEXTEND_NONE; + } + else if (pm->ps->forceHandExtend != HANDEXTEND_NONE) + { //nothing else should be allowed to happen during this time, including weapon fire + int desiredAnim = 0; + qboolean seperateOnTorso = qfalse; + qboolean playFullBody = qfalse; + int desiredOnTorso = 0; + + switch(pm->ps->forceHandExtend) + { + case HANDEXTEND_FORCEPUSH: + desiredAnim = BOTH_FORCEPUSH; + break; + case HANDEXTEND_FORCEPULL: + desiredAnim = BOTH_FORCEPULL; + break; + case HANDEXTEND_FORCE_HOLD: + if ( (pm->ps->fd.forcePowersActive&(1<ps->fd.forcePowersActive&(1<ps->weapon == WP_MELEE + && pm->ps->activeForcePass > FORCE_LEVEL_2 ) + {//2-handed lightning + desiredAnim = BOTH_FORCE_2HANDEDLIGHTNING_HOLD; + } + else + { + desiredAnim = BOTH_FORCELIGHTNING_HOLD; + } + } + else if ( (pm->ps->fd.forcePowersActive&(1<ps->forceDodgeAnim; + break; + case HANDEXTEND_KNOCKDOWN: + if (pm->ps->forceDodgeAnim) + { + if (pm->ps->forceDodgeAnim > 4) + { //this means that we want to play a sepereate anim on the torso + int originalDAnim = pm->ps->forceDodgeAnim-8; //-8 is the original legs anim + if (originalDAnim == 2) + { + desiredAnim = BOTH_FORCE_GETUP_B1; + } + else if (originalDAnim == 3) + { + desiredAnim = BOTH_FORCE_GETUP_B3; + } + else + { + desiredAnim = BOTH_GETUP1; + } + + //now specify the torso anim + seperateOnTorso = qtrue; + desiredOnTorso = BOTH_FORCEPUSH; + } + else if (pm->ps->forceDodgeAnim == 2) + { + desiredAnim = BOTH_FORCE_GETUP_B1; + } + else if (pm->ps->forceDodgeAnim == 3) + { + desiredAnim = BOTH_FORCE_GETUP_B3; + } + else + { + desiredAnim = BOTH_GETUP1; + } + } + else + { + desiredAnim = BOTH_KNOCKDOWN1; + } + break; + case HANDEXTEND_DUELCHALLENGE: + desiredAnim = BOTH_ENGAGETAUNT; + break; + case HANDEXTEND_TAUNT: + desiredAnim = pm->ps->forceDodgeAnim; + if ( desiredAnim != BOTH_ENGAGETAUNT + && VectorCompare( pm->ps->velocity, vec3_origin ) + && pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + playFullBody = qtrue; + } + break; + case HANDEXTEND_PRETHROW: + desiredAnim = BOTH_A3_TL_BR; + playFullBody = qtrue; + break; + case HANDEXTEND_POSTTHROW: + desiredAnim = BOTH_D3_TL___; + playFullBody = qtrue; + break; + case HANDEXTEND_PRETHROWN: + desiredAnim = BOTH_KNEES1; + playFullBody = qtrue; + break; + case HANDEXTEND_POSTTHROWN: + if (pm->ps->forceDodgeAnim) + { + desiredAnim = BOTH_FORCE_GETUP_F2; + } + else + { + desiredAnim = BOTH_KNOCKDOWN5; + } + playFullBody = qtrue; + break; + case HANDEXTEND_DRAGGING: + desiredAnim = BOTH_B1_BL___; + break; + case HANDEXTEND_JEDITAUNT: + desiredAnim = BOTH_GESTURE1; + //playFullBody = qtrue; + break; + //Hmm... maybe use these, too? + //BOTH_FORCEHEAL_QUICK //quick heal (SP level 2 & 3) + //BOTH_MINDTRICK1 // wave (maybe for mind trick 2 & 3 - whole area, and for force seeing) + //BOTH_MINDTRICK2 // tap (maybe for mind trick 1 - one person) + //BOTH_FORCEGRIP_START //start grip + //BOTH_FORCEGRIP_HOLD //hold grip + //BOTH_FORCEGRIP_RELEASE //release grip + //BOTH_FORCELIGHTNING //quick lightning burst (level 1) + //BOTH_FORCELIGHTNING_START //start lightning + //BOTH_FORCELIGHTNING_HOLD //hold lightning + //BOTH_FORCELIGHTNING_RELEASE //release lightning + default: + desiredAnim = BOTH_FORCEPUSH; + break; + } + + if (!seperateOnTorso) + { //of seperateOnTorso, handle it after setting the legs + PM_SetAnim(SETANIM_TORSO, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->torsoTimer = 1; + } + + if (playFullBody) + { //sorry if all these exceptions are getting confusing. This one just means play on both legs and torso. + PM_SetAnim(SETANIM_BOTH, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->legsTimer = pm->ps->torsoTimer = 1; + } + else if (pm->ps->forceHandExtend == HANDEXTEND_DODGE || pm->ps->forceHandExtend == HANDEXTEND_KNOCKDOWN || + (pm->ps->forceHandExtend == HANDEXTEND_CHOKE && pm->ps->groundEntityNum == ENTITYNUM_NONE) ) + { //special case, play dodge anim on whole body, choke anim too if off ground + if (seperateOnTorso) + { + PM_SetAnim(SETANIM_LEGS, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->legsTimer = 1; + + PM_SetAnim(SETANIM_TORSO, desiredOnTorso, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->torsoTimer = 1; + } + else + { + PM_SetAnim(SETANIM_LEGS, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->legsTimer = 1; + } + } + + return; + } + + if (BG_InSpecialJump(pm->ps->legsAnim) || + BG_InRoll(pm->ps, pm->ps->legsAnim) || + PM_InRollComplete(pm->ps, pm->ps->legsAnim)) + { + /* + if (pm->cmd.weapon != WP_MELEE && + pm->ps->weapon != WP_MELEE && + (pm->ps->stats[STAT_WEAPONS] & (1<cmd.weapon = WP_SABER; + pm->ps->weapon = WP_SABER; + } + */ + if (pm->ps->weaponTime < pm->ps->legsTimer) + { + pm->ps->weaponTime = pm->ps->legsTimer; + } + } + + if (pm->ps->duelInProgress) + { + pm->cmd.weapon = WP_SABER; + pm->ps->weapon = WP_SABER; + + if (pm->ps->duelTime >= pm->cmd.serverTime) + { + pm->cmd.upmove = 0; + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + } + } + + if (pm->ps->weapon == WP_SABER && pm->ps->saberMove != LS_READY && pm->ps->saberMove != LS_NONE) + { + pm->cmd.weapon = WP_SABER; //don't allow switching out mid-attack + } + + if (pm->ps->weapon == WP_SABER) + { + //rww - we still need the item stuff, so we won't return immediately + PM_WeaponLightsaber(); + killAfterItem = 1; + } + else if (pm->ps->weapon != WP_EMPLACED_GUN) + { + pm->ps->saberHolstered = 0; + } + + if (PM_CanSetWeaponAnims()) + { + if (pm->ps->weapon == WP_THERMAL || + pm->ps->weapon == WP_TRIP_MINE || + pm->ps->weapon == WP_DET_PACK) + { + if (pm->ps->weapon == WP_THERMAL) + { + if ((pm->ps->torsoAnim) == WeaponAttackAnim[pm->ps->weapon] && + (pm->ps->weaponTime-200) <= 0) + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + } + else + { + if ((pm->ps->torsoAnim) == WeaponAttackAnim[pm->ps->weapon] && + (pm->ps->weaponTime-700) <= 0) + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + } + } + } + + // don't allow attack until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + + // ignore if spectator + if ( pm->ps->clientNum < MAX_CLIENTS && pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->ps->weapon = WP_NONE; + return; + } + + // check for item using + if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { + if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + && pm->ps->m_iVehicleNum) + {//riding a vehicle, can't use holdable items, this button operates as the weapon link/unlink toggle + return; + } + + if (!pm->ps->stats[STAT_HOLDABLE_ITEM]) + { + return; + } + + if (!PM_ItemUsable(pm->ps, 0)) + { + pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + return; + } + else + { + if (pm->ps->stats[STAT_HOLDABLE_ITEMS] & (1 << bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag)) + { + if (bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_BINOCULARS && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_JETPACK && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_HEALTHDISP && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_AMMODISP && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_CLOAK && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_EWEB) + { //never use up the binoculars or jetpack or dispensers or cloak or ... + pm->ps->stats[STAT_HOLDABLE_ITEMS] -= (1 << bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag); + } + } + else + { + return; //this should not happen... + } + + pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag ); + + if (bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_BINOCULARS && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_JETPACK && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_HEALTHDISP && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_AMMODISP && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_CLOAK && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_EWEB) + { + pm->ps->stats[STAT_HOLDABLE_ITEM] = 0; + BG_CycleInven(pm->ps, 1); + } + } + return; + } + } else { + pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; + } + + /* + if (pm->ps->weapon == WP_SABER || pm->ps->weapon == WP_MELEE) + { //we can't toggle zoom while using saber (for obvious reasons) so make sure it's always off + pm->ps->zoomMode = 0; + pm->ps->zoomFov = 0; + pm->ps->zoomLocked = qfalse; + pm->ps->zoomLockTime = 0; + } + */ + + if (killAfterItem) + { + return; + } + + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + } +/* + if (pm->ps->isJediMaster && pm->ps->emplacedIndex) + { + pm->ps->emplacedIndex = 0; + pm->ps->saberHolstered = 0; + } +*/ + if (pm->ps->duelInProgress && pm->ps->emplacedIndex) + { + pm->ps->emplacedIndex = 0; + pm->ps->saberHolstered = 0; + } + + if (pm->ps->weapon == WP_EMPLACED_GUN && pm->ps->emplacedIndex) + { + pm->cmd.weapon = WP_EMPLACED_GUN; //No switch for you! + PM_StartTorsoAnim( BOTH_GUNSIT1 ); + } + + if ( //pm->ps->isJediMaster || + pm->ps->duelInProgress || pm->ps->trueJedi) + { + pm->cmd.weapon = WP_SABER; + pm->ps->weapon = WP_SABER; + + if ( //pm->ps->isJediMaster || + pm->ps->trueJedi) + { + pm->ps->stats[STAT_WEAPONS] = (1 << WP_SABER); + } + } + + amount = weaponData[pm->ps->weapon].energyPerShot; + + // take an ammo away if not infinite + if ( pm->ps->weapon != WP_NONE && + pm->ps->weapon == pm->cmd.weapon && + (pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING) ) + { + if ( pm->ps->clientNum < MAX_CLIENTS && pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 ) + { + // enough energy to fire this weapon? + if (pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < weaponData[pm->ps->weapon].energyPerShot && + pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < weaponData[pm->ps->weapon].altEnergyPerShot) + { //the weapon is out of ammo essentially because it cannot fire primary or secondary, so do the switch + //regardless of if the player is attacking or not + PM_AddEventWithParm( EV_NOAMMO, WP_NUM_WEAPONS+pm->ps->weapon ); + + if (pm->ps->weaponTime < 500) + { + pm->ps->weaponTime += 500; + } + return; + } + + if (pm->ps->weapon == WP_DET_PACK && !pm->ps->hasDetPackPlanted && pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < 1) + { + PM_AddEventWithParm( EV_NOAMMO, WP_NUM_WEAPONS+pm->ps->weapon ); + + if (pm->ps->weaponTime < 500) + { + pm->ps->weaponTime += 500; + } + return; + } + } + } + + // check for weapon change + // can't change if weapon is firing, but can change + // again if lowering or raising + if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { + if ( pm->ps->weapon != pm->cmd.weapon ) { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + + if ( pm->ps->weaponTime > 0 ) { + return; + } + + if (pm->ps->weapon == WP_DISRUPTOR && + pm->ps->zoomMode == 1) + { + if (pm_cancelOutZoom) + { + pm->ps->zoomMode = 0; + pm->ps->zoomFov = 0; + pm->ps->zoomLocked = qfalse; + pm->ps->zoomLockTime = 0; + PM_AddEvent( EV_DISRUPTOR_ZOOMSOUND ); + return; + } + + if (pm->cmd.forwardmove || + pm->cmd.rightmove || + pm->cmd.upmove > 0) + { + return; + } + } + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + if ( pm->ps->weaponstate == WEAPON_RAISING ) { + pm->ps->weaponstate = WEAPON_READY; + if (PM_CanSetWeaponAnims()) + { + if ( pm->ps->weapon == WP_SABER ) + { + PM_StartTorsoAnim( PM_GetSaberStance() ); + } + else if (pm->ps->weapon == WP_MELEE || PM_IsRocketTrooper()) + { + PM_StartTorsoAnim( pm->ps->legsAnim ); + } + else + { + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { + PM_StartTorsoAnim( TORSO_WEAPONREADY4 ); + } + else + { + if (pm->ps->weapon == WP_EMPLACED_GUN) + { + PM_StartTorsoAnim( BOTH_GUNSIT1 ); + } + else + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + } + } + } + return; + } + + if (PM_CanSetWeaponAnims() && + !PM_IsRocketTrooper() && + pm->ps->weaponstate == WEAPON_READY && pm->ps->weaponTime <= 0 && + (pm->ps->weapon >= WP_BRYAR_PISTOL || pm->ps->weapon == WP_STUN_BATON) && + pm->ps->torsoTimer <= 0 && + (pm->ps->torsoAnim) != WeaponReadyAnim[pm->ps->weapon] && + pm->ps->torsoAnim != TORSO_WEAPONIDLE3 && + pm->ps->weapon != WP_EMPLACED_GUN) + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + else if (PM_CanSetWeaponAnims() && + pm->ps->weapon == WP_MELEE) + { + if (pm->ps->weaponTime <= 0 && + pm->ps->forceHandExtend == HANDEXTEND_NONE) + { + int desTAnim = pm->ps->legsAnim; + + if (desTAnim == BOTH_STAND1 || + desTAnim == BOTH_STAND2) + { //remap the standard standing anims for melee stance + desTAnim = BOTH_STAND6; + } + + if (!(pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK))) + { //don't do this while holding attack + if (pm->ps->torsoAnim != desTAnim) + { + PM_StartTorsoAnim( desTAnim ); + } + } + } + } + else if (PM_CanSetWeaponAnims() && PM_IsRocketTrooper()) + { + int desTAnim = pm->ps->legsAnim; + + if (!(pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK))) + { //don't do this while holding attack + if (pm->ps->torsoAnim != desTAnim) + { + PM_StartTorsoAnim( desTAnim ); + } + } + } + + if (((pm->ps->torsoAnim) == TORSO_WEAPONREADY4 || + (pm->ps->torsoAnim) == BOTH_ATTACK4) && + (pm->ps->weapon != WP_DISRUPTOR || pm->ps->zoomMode != 1)) + { + if (pm->ps->weapon == WP_EMPLACED_GUN) + { + PM_StartTorsoAnim( BOTH_GUNSIT1 ); + } + else if (PM_CanSetWeaponAnims()) + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + } + else if (((pm->ps->torsoAnim) != TORSO_WEAPONREADY4 && + (pm->ps->torsoAnim) != BOTH_ATTACK4) && + PM_CanSetWeaponAnims() && + (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1)) + { + PM_StartTorsoAnim( TORSO_WEAPONREADY4 ); + } + + if (pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_VEHICLE) + {//we are a vehicle + veh = pm_entSelf; + } + if ( veh + && veh->m_pVehicle ) + { + if ( g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID].fHoming + || g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID].fHoming ) + {//don't clear the rocket locking ever? + vehicleRocketLock = qtrue; + } + } + + if ( !vehicleRocketLock ) + { + if (pm->ps->weapon != WP_ROCKET_LAUNCHER) + { + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + {//riding a vehicle, the vehicle will tell me my rocketlock stuff... + } + else + { + pm->ps->rocketLockIndex = ENTITYNUM_NONE; + pm->ps->rocketLockTime = 0; + pm->ps->rocketTargetTime = 0; + } + } + } + + if ( PM_DoChargedWeapons(vehicleRocketLock, veh)) + { + // In some cases the charged weapon code may want us to short circuit the rest of the firing code + return; + } + + // check for fire + if ( ! (pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK))) + { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + + if (pm->ps->weapon == WP_EMPLACED_GUN) + { + addTime = weaponData[pm->ps->weapon].fireTime; + pm->ps->weaponTime += addTime; + if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) ) + { + PM_AddEvent( EV_ALT_FIRE ); + } + else + { + PM_AddEvent( EV_FIRE_WEAPON ); + } + return; + } + else if (pm->ps->m_iVehicleNum + && pm_entSelf->s.NPC_class==CLASS_VEHICLE) + { //a vehicle NPC that has a pilot + pm->ps->weaponstate = WEAPON_FIRING; + pm->ps->weaponTime += 100; +#ifdef QAGAME //hack, only do it game-side. vehicle weapons don't really need predicting I suppose. + if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) ) + { + G_CheapWeaponFire(pm->ps->clientNum, EV_ALT_FIRE); + } + else + { + G_CheapWeaponFire(pm->ps->clientNum, EV_FIRE_WEAPON); + } +#endif + /* + addTime = weaponData[WP_EMPLACED_GUN].fireTime; + pm->ps->weaponTime += addTime; + if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) ) + { + PM_AddEvent( EV_ALT_FIRE ); + } + else + { + PM_AddEvent( EV_FIRE_WEAPON ); + } + */ + return; + } + + if (pm->ps->weapon == WP_DISRUPTOR && + (pm->cmd.buttons & BUTTON_ALT_ATTACK) && + !pm->ps->zoomLocked) + { + return; + } + + if (pm->ps->weapon == WP_DISRUPTOR && + (pm->cmd.buttons & BUTTON_ALT_ATTACK) && + pm->ps->zoomMode == 2) + { //can't use disruptor secondary while zoomed binoculars + return; + } + + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { + PM_StartTorsoAnim( BOTH_ATTACK4 ); + } + else if (pm->ps->weapon == WP_MELEE) + { //special anims for standard melee attacks + //Alternate between punches and use the anim length as weapon time. + if (!pm->ps->m_iVehicleNum) + { //if riding a vehicle don't do this stuff at all + if (pm->debugMelee && + (pm->cmd.buttons & BUTTON_ATTACK) && + (pm->cmd.buttons & BUTTON_ALT_ATTACK)) + { //ok, grapple time +#if 0 //eh, I want to try turning the saber off, but can't do that reliably for prediction.. + qboolean icandoit = qtrue; + if (pm->ps->weaponTime > 0) + { //weapon busy + icandoit = qfalse; + } + if (pm->ps->forceHandExtend != HANDEXTEND_NONE) + { //force power or knockdown or something + icandoit = qfalse; + } + if (pm->ps->weapon != WP_SABER && pm->ps->weapon != WP_MELEE) + { + icandoit = qfalse; + } + + if (icandoit) + { + //G_SetAnim(ent, &ent->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_GRAB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + PM_SetAnim(SETANIM_BOTH, BOTH_KYLE_GRAB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + if (pm->ps->torsoAnim == BOTH_KYLE_GRAB) + { //providing the anim set succeeded.. + pm->ps->torsoTimer += 500; //make the hand stick out a little longer than it normally would + if (pm->ps->legsAnim == pm->ps->torsoAnim) + { + pm->ps->legsTimer = pm->ps->torsoTimer; + } + pm->ps->weaponTime = pm->ps->torsoTimer; + return; + } + } +#else + #ifdef QAGAME + if (pm_entSelf) + { + if (TryGrapple((gentity_t *)pm_entSelf)) + { + return; + } + } + #else + return; + #endif +#endif + } + else if (pm->debugMelee && + (pm->cmd.buttons & BUTTON_ALT_ATTACK)) + { //kicks + if (!BG_KickingAnim(pm->ps->torsoAnim) && + !BG_KickingAnim(pm->ps->legsAnim)) + { + int kickMove = PM_KickMoveForConditions(); + if (kickMove == LS_HILT_BASH) + { //yeah.. no hilt to bash with! + kickMove = LS_KICK_F; + } + + if (kickMove != -1) + { + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//if in air, convert kick to an in-air kick + float gDist = PM_GroundDistance(); + //let's only allow air kicks if a certain distance from the ground + //it's silly to be able to do them right as you land. + //also looks wrong to transition from a non-complete flip anim... + if ((!BG_FlippingAnim( pm->ps->legsAnim ) || pm->ps->legsTimer <= 0) && + gDist > 64.0f && //strict minimum + gDist > (-pm->ps->velocity[2])-64.0f //make sure we are high to ground relative to downward velocity as well + ) + { + switch ( kickMove ) + { + case LS_KICK_F: + kickMove = LS_KICK_F_AIR; + break; + case LS_KICK_B: + kickMove = LS_KICK_B_AIR; + break; + case LS_KICK_R: + kickMove = LS_KICK_R_AIR; + break; + case LS_KICK_L: + kickMove = LS_KICK_L_AIR; + break; + default: //oh well, can't do any other kick move while in-air + kickMove = -1; + break; + } + } + else + { //off ground, but too close to ground + kickMove = -1; + } + } + } + + if (kickMove != -1) + { + int kickAnim = saberMoveData[kickMove].animToUse; + + if (kickAnim != -1) + { + PM_SetAnim(SETANIM_BOTH, kickAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + if (pm->ps->legsAnim == kickAnim) + { + pm->ps->weaponTime = pm->ps->legsTimer; + return; + } + } + } + } + + //if got here then no move to do so put torso into leg idle or whatever + if (pm->ps->torsoAnim != pm->ps->legsAnim) + { + PM_SetAnim(SETANIM_BOTH, pm->ps->legsAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + pm->ps->weaponTime = 0; + return; + } + else + { //just punch + int desTAnim = BOTH_MELEE1; + if (pm->ps->torsoAnim == BOTH_MELEE1) + { + desTAnim = BOTH_MELEE2; + } + PM_StartTorsoAnim( desTAnim ); + + if (pm->ps->torsoAnim == desTAnim) + { + pm->ps->weaponTime = pm->ps->torsoTimer; + } + } + } + } + else + { + PM_StartTorsoAnim( WeaponAttackAnim[pm->ps->weapon] ); + } + + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + amount = weaponData[pm->ps->weapon].altEnergyPerShot; + } + else + { + amount = weaponData[pm->ps->weapon].energyPerShot; + } + + pm->ps->weaponstate = WEAPON_FIRING; + + // take an ammo away if not infinite + if ( pm->ps->clientNum < MAX_CLIENTS && pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 ) + { + // enough energy to fire this weapon? + if ((pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] - amount) >= 0) + { + pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= amount; + } + else // Not enough energy + { + // Switch weapons + if (pm->ps->weapon != WP_DET_PACK || !pm->ps->hasDetPackPlanted) + { + PM_AddEventWithParm( EV_NOAMMO, WP_NUM_WEAPONS+pm->ps->weapon ); + if (pm->ps->weaponTime < 500) + { + pm->ps->weaponTime += 500; + } + } + return; + } + } + + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) { + //if ( pm->ps->weapon == WP_BRYAR_PISTOL && pm->gametype != GT_SIEGE ) + if (0) + { //kind of a hack for now + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = weaponData[pm->ps->weapon].fireTime; + } + else if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode != 1) + { + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = weaponData[pm->ps->weapon].fireTime; + } + else + { + if (pm->ps->weapon != WP_MELEE || + !pm->ps->m_iVehicleNum) + { //do not fire melee events at all when on vehicle + PM_AddEvent( EV_ALT_FIRE ); + } + addTime = weaponData[pm->ps->weapon].altFireTime; + } + } + else { + if (pm->ps->weapon != WP_MELEE || + !pm->ps->m_iVehicleNum) + { //do not fire melee events at all when on vehicle + PM_AddEvent( EV_FIRE_WEAPON ); + } + addTime = weaponData[pm->ps->weapon].fireTime; + if ( pm->gametype == GT_SIEGE && pm->ps->weapon == WP_DET_PACK ) + { // were far too spammy before? So says Rick. + addTime *= 2; + } + } + + /* + if ( pm->ps->powerups[PW_HASTE] ) { + addTime /= 1.3; + } + */ + + if (pm->ps->fd.forcePowersActive & (1 << FP_RAGE)) + { + addTime *= 0.75; + } + else if (pm->ps->fd.forceRageRecoveryTime > pm->cmd.serverTime) + { + addTime *= 1.5; + } + + pm->ps->weaponTime += addTime; +} + +/* +================ +PM_Animate +================ +*/ + +static void PM_Animate( void ) { + if ( pm->cmd.buttons & BUTTON_GESTURE ) { + if (pm->ps->m_iVehicleNum) + { //eh, fine, clear it + if (pm->ps->forceHandExtendTime < pm->cmd.serverTime) + { + pm->ps->forceHandExtend = HANDEXTEND_NONE; + } + } + + if ( pm->ps->torsoTimer < 1 && pm->ps->forceHandExtend == HANDEXTEND_NONE && + pm->ps->legsTimer < 1 && pm->ps->weaponTime < 1 && pm->ps->saberLockTime < pm->cmd.serverTime) { + + pm->ps->forceHandExtend = HANDEXTEND_TAUNT; + + //FIXME: random taunt anims? + pm->ps->forceDodgeAnim = BOTH_ENGAGETAUNT; + + pm->ps->forceHandExtendTime = pm->cmd.serverTime + 1000; + + //pm->ps->weaponTime = 100; + + PM_AddEvent( EV_TAUNT ); + } +#if 0 +// Here's an interesting bit. The bots in TA used buttons to do additional gestures. +// I ripped them out because I didn't want too many buttons given the fact that I was already adding some for JK2. +// We can always add some back in if we want though. + } else if ( pm->cmd.buttons & BUTTON_GETFLAG ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GETFLAG ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_GUARDBASE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GUARDBASE ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_PATROL ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_PATROL ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_FOLLOWME ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_FOLLOWME ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_AFFIRMATIVE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_AFFIRMATIVE); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_NEGATIVE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_NEGATIVE ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } +#endif // + } +} + + +/* +================ +PM_DropTimers +================ +*/ +static void PM_DropTimers( void ) { + // drop misc timing counter + if ( pm->ps->pm_time ) { + if ( pml.msec >= pm->ps->pm_time ) { + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } else { + pm->ps->pm_time -= pml.msec; + } + } + + // drop animation counter + if ( pm->ps->legsTimer > 0 ) { + pm->ps->legsTimer -= pml.msec; + if ( pm->ps->legsTimer < 0 ) { + pm->ps->legsTimer = 0; + } + } + + if ( pm->ps->torsoTimer > 0 ) { + pm->ps->torsoTimer -= pml.msec; + if ( pm->ps->torsoTimer < 0 ) { + pm->ps->torsoTimer = 0; + } + } +} + +// Following function is stateless (at the moment). And hoisting it out +// of the namespace here is easier than fixing all the places it's used, +// which includes files that are also compiled in SP. We do need to make +// sure we only get one copy in the linker, though. + +#include "../namespace_end.h" + +#if !defined(_XBOX) || defined(QAGAME) +extern vmCvar_t bg_fighterAltControl; +qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ) +{ + if ( bg_fighterAltControl.integer + && ps->clientNum < MAX_CLIENTS //real client + && ps->m_iVehicleNum//in a vehicle + && pVeh //valid vehicle data pointer + && pVeh->m_pVehicleInfo//valid vehicle info + && pVeh->m_pVehicleInfo->type == VH_FIGHTER )//fighter + //FIXME: specify per vehicle instead of assuming true for all fighters + //FIXME: map/server setting? + {//can roll and pitch without limitation! + return qtrue; + } + return qfalse; +} +#else +extern qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ); +#endif + +#include "../namespace_begin.h" + +/* +================ +PM_UpdateViewAngles + +This can be used as another entry point when only the viewangles +are being updated isntead of a full move +================ +*/ +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { + short temp; + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) { + return; // no view changes at all + } + + if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) { + return; // no view changes at all + } + + // circularly clamp the angles with deltas + for (i=0 ; i<3 ; i++) { + temp = cmd->angles[i] + ps->delta_angles[i]; + if ( pm_entVeh + && pm_entVeh->m_pVehicle + && pm_entVeh->m_pVehicle->m_pVehicleInfo + && pm_entVeh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER + && (cmd->serverTime-pm_entVeh->playerState->hyperSpaceTime) >= HYPERSPACE_TIME ) + {//in a vehicle and not hyperspacing + if ( i == PITCH ) + { + int pitchClamp = ANGLE2SHORT(AngleNormalize180(pm_entVeh->m_pVehicle->m_vPrevRiderViewAngles[PITCH]+10.0f)); + // don't let the player look up or down more than 22.5 degrees + if ( temp > pitchClamp ) + { + ps->delta_angles[i] = pitchClamp - cmd->angles[i]; + temp = pitchClamp; + } + else if ( temp < -pitchClamp ) + { + ps->delta_angles[i] = -pitchClamp - cmd->angles[i]; + temp = -pitchClamp; + } + } + if ( i == YAW ) + { + int yawClamp = ANGLE2SHORT(AngleNormalize180(pm_entVeh->m_pVehicle->m_vPrevRiderViewAngles[YAW]+10.0f)); + // don't let the player look left or right more than 22.5 degrees + if ( temp > yawClamp ) + { + ps->delta_angles[i] = yawClamp - cmd->angles[i]; + temp = yawClamp; + } + else if ( temp < -yawClamp ) + { + ps->delta_angles[i] = -yawClamp - cmd->angles[i]; + temp = -yawClamp; + } + } + } + else + { + if ( i == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) { + ps->delta_angles[i] = 16000 - cmd->angles[i]; + temp = 16000; + } else if ( temp < -16000 ) { + ps->delta_angles[i] = -16000 - cmd->angles[i]; + temp = -16000; + } + } + } + ps->viewangles[i] = SHORT2ANGLE(temp); + } +} + +/* +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { + short temp; + int i; + float rootPitch = 0, pitchMin=-90, pitchMax=90, yawMin=0, yawMax=0, lockedYawValue = 0; //just to shut up warnings + qboolean lockedYaw = qfalse, clamped = qfalse; + bgEntity_t *vehEnt = NULL; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) { + return; // no view changes at all + } + + if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) { + return; // no view changes at all + } + + // If we're a vehicle, or we're riding a vehicle...? + if ( ps->m_iVehicleNum ) + { + if ( ps->clientNum < MAX_CLIENTS ) + { //player riding vehicle + vehEnt = PM_BGEntForNum(ps->m_iVehicleNum); + } + else + { //vehicle with player pilot + vehEnt = PM_BGEntForNum(ps->clientNum); + } + if ( vehEnt ) + {//there is a vehicle + Vehicle_t *pVeh = vehEnt->m_pVehicle; + if ( pVeh && pVeh->m_pVehicleInfo ) + { + // There is a vehicle... + if ( pVeh->m_pVehicleInfo->type != VH_ANIMAL ) + {//animals just turn normally, no clamping + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + rootPitch = pVeh->m_vOrientation[PITCH];//gent->owner->client->ps.vehicleAngles[PITCH];//??? what if goes over 90 when add the min/max? + if ( pVeh->m_pVehicleInfo->pitchLimit == -1 ) + { + pitchMax = 180; + } + else + { + pitchMax = pVeh->m_pVehicleInfo->pitchLimit; + } + pitchMin = -pitchMax; + } + else + { + lockedYawValue = 0;//gent->owner->client->ps.vehicleAngles[YAW]; + lockedYaw = qtrue; + yawMax = pVeh->m_pVehicleInfo->lookYaw; + yawMin = -yawMax; + rootPitch = 0;//gent->owner->client->ps.vehicleAngles[PITCH];//??? what if goes over 90 when add the min/max? + pitchMax = pVeh->m_pVehicleInfo->lookPitch; + pitchMin = -pitchMax; + } + } + } + } + } + if ( 1 ) + { + const short pitchClampMin = ANGLE2SHORT(rootPitch+pitchMin); + const short pitchClampMax = ANGLE2SHORT(rootPitch+pitchMax); + const short yawClampMin = ANGLE2SHORT(lockedYawValue+yawMin); + const short yawClampMax = ANGLE2SHORT(lockedYawValue+yawMax); + for (i=0 ; i<3 ; i++) + { + temp = cmd->angles[i] + ps->delta_angles[i]; + if ( i == PITCH ) + { + //FIXME get this limit from the NPCs stats? + // don't let the player look up or down more than 90 degrees + if ( temp > pitchClampMax ) + { + ps->delta_angles[i] = (pitchClampMax - cmd->angles[i]) & 0xffff; //& clamp to short + temp = pitchClampMax; + clamped = qtrue; + } + else if ( temp < pitchClampMin ) + { + ps->delta_angles[i] = (pitchClampMin - cmd->angles[i]) & 0xffff; //& clamp to short + temp = pitchClampMin; + clamped = qtrue; + } + } + if ( i == YAW && lockedYaw ) + { + //FIXME get this limit from the NPCs stats? + // don't let the player look up or down more than 90 degrees + if ( temp > yawClampMax ) + { + ps->delta_angles[i] = (yawClampMax - cmd->angles[i]) & 0xffff; //& clamp to short + temp = yawClampMax; + clamped = qtrue; + } + else if ( temp < yawClampMin ) + { + ps->delta_angles[i] = (yawClampMin - cmd->angles[i]) & 0xffff; //& clamp to short + temp = yawClampMin; + clamped = qtrue; + } + ps->viewangles[i] = SHORT2ANGLE(temp); + } + else + { + ps->viewangles[i] = SHORT2ANGLE(temp); + } + } + } + else + { + // circularly clamp the angles with deltas + for (i=0 ; i<3 ; i++) { + temp = cmd->angles[i] + ps->delta_angles[i]; + if ( i == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) { + ps->delta_angles[i] = 16000 - cmd->angles[i]; + temp = 16000; + } else if ( temp < -16000 ) { + ps->delta_angles[i] = -16000 - cmd->angles[i]; + temp = -16000; + } + } + ps->viewangles[i] = SHORT2ANGLE(temp); + } + } + +} +*/ + +//------------------------------------------- +void PM_AdjustAttackStates( pmove_t *pm ) +//------------------------------------------- +{ + int amount; + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + { //riding a vehicle + bgEntity_t *veh = pm_entVeh; + if ( veh && + (veh->m_pVehicle && (veh->m_pVehicle->m_pVehicleInfo->type == VH_WALKER || veh->m_pVehicle && veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)) ) + {//riding a walker/fighter + //not firing, ever + pm->ps->eFlags &= ~(EF_FIRING|EF_ALT_FIRING); + return; + } + } + // get ammo usage + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - weaponData[pm->ps->weapon].altEnergyPerShot; + } + else + { + amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - weaponData[pm->ps->weapon].energyPerShot; + } + + // disruptor alt-fire should toggle the zoom mode, but only bother doing this for the player? + if ( pm->ps->weapon == WP_DISRUPTOR && pm->ps->weaponstate == WEAPON_READY ) + { + if ( !(pm->ps->eFlags & EF_ALT_FIRING) && (pm->cmd.buttons & BUTTON_ALT_ATTACK) /*&& + pm->cmd.upmove <= 0 && !pm->cmd.forwardmove && !pm->cmd.rightmove*/) + { + // We just pressed the alt-fire key + if ( !pm->ps->zoomMode && pm->ps->pm_type != PM_DEAD ) + { + // not already zooming, so do it now + pm->ps->zoomMode = 1; + pm->ps->zoomLocked = qfalse; + pm->ps->zoomFov = 80.0f;//cg_fov.value; + pm->ps->zoomLockTime = pm->cmd.serverTime + 50; + PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND); + } + else if (pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime) + { //check for == 1 so we can't turn binoculars off with disruptor alt fire + // already zooming, so must be wanting to turn it off + pm->ps->zoomMode = 0; + pm->ps->zoomTime = pm->ps->commandTime; + pm->ps->zoomLocked = qfalse; + PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND); + pm->ps->weaponTime = 1000; + } + } + else if ( !(pm->cmd.buttons & BUTTON_ALT_ATTACK ) && pm->ps->zoomLockTime < pm->cmd.serverTime) + { + // Not pressing zoom any more + if ( pm->ps->zoomMode ) + { + if (pm->ps->zoomMode == 1 && !pm->ps->zoomLocked) + { //approximate what level the client should be zoomed at based on how long zoom was held + pm->ps->zoomFov = ((pm->cmd.serverTime+50) - pm->ps->zoomLockTime) * 0.035f; + if (pm->ps->zoomFov > 50) + { + pm->ps->zoomFov = 50; + } + if (pm->ps->zoomFov < 1) + { + pm->ps->zoomFov = 1; + } + } + // were zooming in, so now lock the zoom + pm->ps->zoomLocked = qtrue; + } + } + //This seemed like a good idea, but apparently it confuses people. So disabled for now. + /* + else if (!(pm->ps->eFlags & EF_ALT_FIRING) && (pm->cmd.buttons & BUTTON_ALT_ATTACK) && + (pm->cmd.upmove > 0 || pm->cmd.forwardmove || pm->cmd.rightmove)) + { //if you try to zoom while moving, just convert it into a primary attack + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + pm->cmd.buttons |= BUTTON_ATTACK; + } + */ + + /* + if (pm->cmd.upmove > 0 || pm->cmd.forwardmove || pm->cmd.rightmove) + { + if (pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime) + { //check for == 1 so we can't turn binoculars off with disruptor alt fire + pm->ps->zoomMode = 0; + pm->ps->zoomTime = pm->ps->commandTime; + pm->ps->zoomLocked = qfalse; + PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND); + } + } + */ + + if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + // If we are zoomed, we should switch the ammo usage to the alt-fire, otherwise, we'll + // just use whatever ammo was selected from above + if ( pm->ps->zoomMode ) + { + amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - + weaponData[pm->ps->weapon].altEnergyPerShot; + } + } + else + { + // alt-fire button pressing doesn't use any ammo + amount = 0; + } + } + /* + else if (pm->ps->weapon == WP_DISRUPTOR) //still perform certain checks, even if the weapon is not ready + { + if (pm->cmd.upmove > 0 || pm->cmd.forwardmove || pm->cmd.rightmove) + { + if (pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime) + { //check for == 1 so we can't turn binoculars off with disruptor alt fire + pm->ps->zoomMode = 0; + pm->ps->zoomTime = pm->ps->commandTime; + pm->ps->zoomLocked = qfalse; + PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND); + } + } + } + */ + + // set the firing flag for continuous beam weapons, saber will fire even if out of ammo + if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && + pm->ps->pm_type != PM_INTERMISSION && + ( pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) && + ( amount >= 0 || pm->ps->weapon == WP_SABER )) + { + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + pm->ps->eFlags |= EF_ALT_FIRING; + } + else + { + pm->ps->eFlags &= ~EF_ALT_FIRING; + } + + // This flag should always get set, even when alt-firing + pm->ps->eFlags |= EF_FIRING; + } + else + { + // Clear 'em out + pm->ps->eFlags &= ~(EF_FIRING|EF_ALT_FIRING); + } + + // disruptor should convert a main fire to an alt-fire if the gun is currently zoomed + if ( pm->ps->weapon == WP_DISRUPTOR) + { + if ( pm->cmd.buttons & BUTTON_ATTACK && pm->ps->zoomMode == 1 && pm->ps->zoomLocked) + { + // converting the main fire to an alt-fire + pm->cmd.buttons |= BUTTON_ALT_ATTACK; + pm->ps->eFlags |= EF_ALT_FIRING; + } + else if ( pm->cmd.buttons & BUTTON_ALT_ATTACK && pm->ps->zoomMode == 1 && pm->ps->zoomLocked) + { + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + pm->ps->eFlags &= ~EF_ALT_FIRING; + } + } +} + +void BG_CmdForRoll( playerState_t *ps, int anim, usercmd_t *pCmd ) +{ + switch ( (anim) ) + { + case BOTH_ROLL_F: + pCmd->forwardmove = 127; + pCmd->rightmove = 0; + break; + case BOTH_ROLL_B: + pCmd->forwardmove = -127; + pCmd->rightmove = 0; + break; + case BOTH_ROLL_R: + pCmd->forwardmove = 0; + pCmd->rightmove = 127; + break; + case BOTH_ROLL_L: + pCmd->forwardmove = 0; + pCmd->rightmove = -127; + break; + case BOTH_GETUP_BROLL_R: + pCmd->forwardmove = 0; + pCmd->rightmove = 48; + //NOTE: speed is 400 + break; + + case BOTH_GETUP_FROLL_R: + if ( ps->legsTimer <= 250 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + pCmd->forwardmove = 0; + pCmd->rightmove = 48; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_BROLL_L: + pCmd->forwardmove = 0; + pCmd->rightmove = -48; + //NOTE: speed is 400 + break; + + case BOTH_GETUP_FROLL_L: + if ( ps->legsTimer <= 250 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + pCmd->forwardmove = 0; + pCmd->rightmove = -48; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_BROLL_B: + if ( ps->torsoTimer <= 250 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else if ( PM_AnimLength( 0, (animNumber_t)ps->legsAnim ) - ps->torsoTimer < 350 ) + {//beginning of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + //FIXME: ramp down over length of anim + pCmd->forwardmove = -64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_FROLL_B: + if ( ps->torsoTimer <= 100 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else if ( PM_AnimLength( 0, (animNumber_t)ps->legsAnim ) - ps->torsoTimer < 200 ) + {//beginning of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + //FIXME: ramp down over length of anim + pCmd->forwardmove = -64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_BROLL_F: + if ( ps->torsoTimer <= 550 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else if ( PM_AnimLength( 0, (animNumber_t)ps->legsAnim ) - ps->torsoTimer < 150 ) + {//beginning of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + pCmd->forwardmove = 64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_FROLL_F: + if ( ps->torsoTimer <= 100 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + //FIXME: ramp down over length of anim + pCmd->forwardmove = 64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + } + pCmd->upmove = 0; +} + +qboolean PM_SaberInTransition( int move ); + +void BG_AdjustClientSpeed(playerState_t *ps, usercmd_t *cmd, int svTime) +{ + if (ps->clientNum >= MAX_CLIENTS) + { + bgEntity_t *bgEnt = pm_entSelf; + + if (bgEnt && bgEnt->s.NPC_class == CLASS_VEHICLE) + { //vehicles manage their own speed + return; + } + } + + //For prediction, always reset speed back to the last known server base speed + //If we didn't do this, under lag we'd eventually dwindle speed down to 0 even though + //that would not be the correct predicted value. + ps->speed = ps->basespeed; + + if (ps->forceHandExtend == HANDEXTEND_DODGE) + { + ps->speed = 0; + } + + if (ps->forceHandExtend == HANDEXTEND_KNOCKDOWN || + ps->forceHandExtend == HANDEXTEND_PRETHROWN || + ps->forceHandExtend == HANDEXTEND_POSTTHROWN) + { + ps->speed = 0; + } + + + if ( cmd->forwardmove < 0 && !(cmd->buttons&BUTTON_WALKING) && pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//running backwards is slower than running forwards (like SP) + ps->speed *= 0.75; + } + + if (ps->fd.forcePowersActive & (1 << FP_GRIP)) + { + ps->speed *= 0.4; + } + + if (ps->fd.forcePowersActive & (1 << FP_SPEED)) + { + ps->speed *= 1.7; + } + else if (ps->fd.forcePowersActive & (1 << FP_RAGE)) + { + ps->speed *= 1.3; + } + else if (ps->fd.forceRageRecoveryTime > svTime) + { + ps->speed *= 0.75; + } + + if (pm->ps->weapon == WP_DISRUPTOR && + pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime) + { + ps->speed *= 0.5f; + } + + + if (ps->fd.forceGripCripple) + { + if (ps->fd.forcePowersActive & (1 << FP_RAGE)) + { + ps->speed *= 0.9; + } + else if (ps->fd.forcePowersActive & (1 << FP_SPEED)) + { //force speed will help us escape + ps->speed *= 0.8; + } + else + { + ps->speed *= 0.2; + } + } + + if ( BG_SaberInAttack( ps->saberMove ) && cmd->forwardmove < 0 ) + {//if running backwards while attacking, don't run as fast. + switch( ps->fd.saberAnimLevel ) + { + case FORCE_LEVEL_1: + ps->speed *= 0.75f; + break; + case FORCE_LEVEL_2: + case SS_DUAL: + case SS_STAFF: + ps->speed *= 0.60f; + break; + case FORCE_LEVEL_3: + ps->speed *= 0.45f; + break; + default: + break; + } + } + else if ( BG_SpinningSaberAnim( ps->legsAnim ) ) + { + if (ps->fd.saberAnimLevel == FORCE_LEVEL_3) + { + ps->speed *= 0.3f; + } + else + { + ps->speed *= 0.5f; + } + } + else if ( ps->weapon == WP_SABER && BG_SaberInAttack( ps->saberMove ) ) + {//if attacking with saber while running, drop your speed + switch( ps->fd.saberAnimLevel ) + { + case FORCE_LEVEL_2: + case SS_DUAL: + case SS_STAFF: + ps->speed *= 0.85f; + break; + case FORCE_LEVEL_3: + ps->speed *= 0.55f; + break; + default: + break; + } + } + else if (ps->weapon == WP_SABER && ps->fd.saberAnimLevel == FORCE_LEVEL_3 && + PM_SaberInTransition(ps->saberMove)) + { //Now, we want to even slow down in transitions for level 3 (since it has chains and stuff now) + if (cmd->forwardmove < 0) + { + ps->speed *= 0.4f; + } + else + { + ps->speed *= 0.6f; + } + } + + if ( BG_InRoll( ps, ps->legsAnim ) && ps->speed > 50 ) + { //can't roll unless you're able to move normally + if ((ps->legsAnim) == BOTH_ROLL_B) + { //backwards roll is pretty fast, should also be slower + if (ps->legsTimer > 800) + { + ps->speed = ps->legsTimer/2.5; + } + else + { + ps->speed = ps->legsTimer/6.0;//450; + } + } + else + { + if (ps->legsTimer > 800) + { + ps->speed = ps->legsTimer/1.5;//450; + } + else + { + ps->speed = ps->legsTimer/5.0;//450; + } + } + if (ps->speed > 600) + { + ps->speed = 600; + } + //Automatically slow down as the roll ends. + } +} + +qboolean BG_InRollAnim( entityState_t *cent ) +{ + switch ( (cent->legsAnim) ) + { + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + return qtrue; + } + return qfalse; +} + +qboolean BG_InKnockDown( int anim ) +{ + switch ( (anim) ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + return qtrue; + break; + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InRollES( entityState_t *ps, int anim ) +{ + switch ( (anim) ) + { + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + return qtrue; + break; + } + return qfalse; +} + +void BG_IK_MoveArm(void *ghoul2, int lHandBolt, int time, entityState_t *ent, int basePose, vec3_t desiredPos, qboolean *ikInProgress, + vec3_t origin, vec3_t angles, vec3_t scale, int blendTime, qboolean forceHalt) +{ + mdxaBone_t lHandMatrix; + vec3_t lHand; + vec3_t torg; + float distToDest; + + if (!ghoul2) + { + return; + } + + assert(bgHumanoidAnimations[basePose].firstFrame > 0); + + if (!*ikInProgress && !forceHalt) + { + int baseposeAnim = basePose; + sharedSetBoneIKStateParams_t ikP; + + //restrict the shoulder joint + //VectorSet(ikP.pcjMins,-50.0f,-80.0f,-15.0f); + //VectorSet(ikP.pcjMaxs,15.0f,40.0f,15.0f); + + //for now, leaving it unrestricted, but restricting elbow joint. + //This lets us break the arm however we want in order to fling people + //in throws, and doesn't look bad. + VectorSet(ikP.pcjMins,0,0,0); + VectorSet(ikP.pcjMaxs,0,0,0); + + //give the info on our entity. + ikP.blendTime = blendTime; + VectorCopy(origin, ikP.origin); + VectorCopy(angles, ikP.angles); + ikP.angles[PITCH] = 0; + ikP.pcjOverrides = 0; + ikP.radius = 10.0f; + VectorCopy(scale, ikP.scale); + + //base pose frames for the limb + ikP.startFrame = bgHumanoidAnimations[baseposeAnim].firstFrame + bgHumanoidAnimations[baseposeAnim].numFrames; + ikP.endFrame = bgHumanoidAnimations[baseposeAnim].firstFrame + bgHumanoidAnimations[baseposeAnim].numFrames; + + ikP.forceAnimOnBone = qfalse; //let it use existing anim if it's the same as this one. + + //we want to call with a null bone name first. This will init all of the + //ik system stuff on the g2 instance, because we need ragdoll effectors + //in order for our pcj's to know how to angle properly. + if (!strap_G2API_SetBoneIKState(ghoul2, time, NULL, IKS_DYNAMIC, &ikP)) + { + assert(!"Failed to init IK system for g2 instance!"); + } + + //Now, create our IK bone state. + if (strap_G2API_SetBoneIKState(ghoul2, time, "lhumerus", IKS_DYNAMIC, &ikP)) + { + //restrict the elbow joint + VectorSet(ikP.pcjMins,-90.0f,-20.0f,-20.0f); + VectorSet(ikP.pcjMaxs,30.0f,20.0f,-20.0f); + + if (strap_G2API_SetBoneIKState(ghoul2, time, "lradius", IKS_DYNAMIC, &ikP)) + { //everything went alright. + *ikInProgress = qtrue; + } + } + } + + if (*ikInProgress && !forceHalt) + { //actively update our ik state. + sharedIKMoveParams_t ikM; + sharedRagDollUpdateParams_t tuParms; + vec3_t tAngles; + + //set the argument struct up + VectorCopy(desiredPos, ikM.desiredOrigin); //we want the bone to move here.. if possible + + VectorCopy(angles, tAngles); + tAngles[PITCH] = tAngles[ROLL] = 0; + + strap_G2API_GetBoltMatrix(ghoul2, 0, lHandBolt, &lHandMatrix, tAngles, origin, time, 0, scale); + //Get the point position from the matrix. + lHand[0] = lHandMatrix.matrix[0][3]; + lHand[1] = lHandMatrix.matrix[1][3]; + lHand[2] = lHandMatrix.matrix[2][3]; + + VectorSubtract(lHand, desiredPos, torg); + distToDest = VectorLength(torg); + + //closer we are, more we want to keep updated. + //if we're far away we don't want to be too fast or we'll start twitching all over. + if (distToDest < 2) + { //however if we're this close we want very precise movement + ikM.movementSpeed = 0.4f; + } + else if (distToDest < 16) + { + ikM.movementSpeed = 0.9f;//8.0f; + } + else if (distToDest < 32) + { + ikM.movementSpeed = 0.8f;//4.0f; + } + else if (distToDest < 64) + { + ikM.movementSpeed = 0.7f;//2.0f; + } + else + { + ikM.movementSpeed = 0.6f; + } + VectorCopy(origin, ikM.origin); //our position in the world. + + ikM.boneName[0] = 0; + if (strap_G2API_IKMove(ghoul2, time, &ikM)) + { + //now do the standard model animate stuff with ragdoll update params. + VectorCopy(angles, tuParms.angles); + tuParms.angles[PITCH] = 0; + + VectorCopy(origin, tuParms.position); + VectorCopy(scale, tuParms.scale); + + tuParms.me = ent->number; + VectorClear(tuParms.velocity); + + strap_G2API_AnimateG2Models(ghoul2, time, &tuParms); + } + else + { + *ikInProgress = qfalse; + } + } + else if (*ikInProgress) + { //kill it + float cFrame, animSpeed; + int sFrame, eFrame, flags; + + strap_G2API_SetBoneIKState(ghoul2, time, "lhumerus", IKS_NONE, NULL); + strap_G2API_SetBoneIKState(ghoul2, time, "lradius", IKS_NONE, NULL); + + //then reset the angles/anims on these PCJs + strap_G2API_SetBoneAngles(ghoul2, 0, "lhumerus", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "lradius", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, time); + + //Get the anim/frames that the pelvis is on exactly, and match the left arm back up with them again. + strap_G2API_GetBoneAnim(ghoul2, "pelvis", (const int)time, &cFrame, &sFrame, &eFrame, &flags, &animSpeed, 0, 0); + strap_G2API_SetBoneAnim(ghoul2, 0, "lhumerus", sFrame, eFrame, flags, animSpeed, time, sFrame, 300); + strap_G2API_SetBoneAnim(ghoul2, 0, "lradius", sFrame, eFrame, flags, animSpeed, time, sFrame, 300); + + //And finally, get rid of all the ik state effector data by calling with null bone name (similar to how we init it). + strap_G2API_SetBoneIKState(ghoul2, time, NULL, IKS_NONE, NULL); + + *ikInProgress = qfalse; + } +} + +//Adjust the head/neck desired angles +void BG_UpdateLookAngles( int lookingDebounceTime, vec3_t lastHeadAngles, int time, vec3_t lookAngles, float lookSpeed, float minPitch, float maxPitch, float minYaw, float maxYaw, float minRoll, float maxRoll ) +{ + static const float fFrameInter = 0.1f; + static vec3_t oldLookAngles; + static vec3_t lookAnglesDiff; + static int ang; + + if ( lookingDebounceTime > time ) + { + //clamp so don't get "Exorcist" effect + if ( lookAngles[PITCH] > maxPitch ) + { + lookAngles[PITCH] = maxPitch; + } + else if ( lookAngles[PITCH] < minPitch ) + { + lookAngles[PITCH] = minPitch; + } + if ( lookAngles[YAW] > maxYaw ) + { + lookAngles[YAW] = maxYaw; + } + else if ( lookAngles[YAW] < minYaw ) + { + lookAngles[YAW] = minYaw; + } + if ( lookAngles[ROLL] > maxRoll ) + { + lookAngles[ROLL] = maxRoll; + } + else if ( lookAngles[ROLL] < minRoll ) + { + lookAngles[ROLL] = minRoll; + } + + //slowly lerp to this new value + //Remember last headAngles + VectorCopy( lastHeadAngles, oldLookAngles ); + VectorSubtract( lookAngles, oldLookAngles, lookAnglesDiff ); + + for ( ang = 0; ang < 3; ang++ ) + { + lookAnglesDiff[ang] = AngleNormalize180( lookAnglesDiff[ang] ); + } + + if( VectorLengthSquared( lookAnglesDiff ) ) + { + lookAngles[PITCH] = AngleNormalize180( oldLookAngles[PITCH]+(lookAnglesDiff[PITCH]*fFrameInter*lookSpeed) ); + lookAngles[YAW] = AngleNormalize180( oldLookAngles[YAW]+(lookAnglesDiff[YAW]*fFrameInter*lookSpeed) ); + lookAngles[ROLL] = AngleNormalize180( oldLookAngles[ROLL]+(lookAnglesDiff[ROLL]*fFrameInter*lookSpeed) ); + } + } + //Remember current lookAngles next time + VectorCopy( lookAngles, lastHeadAngles ); +} + +//for setting visual look (headturn) angles +static void BG_G2ClientNeckAngles( void *ghoul2, int time, const vec3_t lookAngles, vec3_t headAngles, vec3_t neckAngles, vec3_t thoracicAngles, vec3_t headClampMinAngles, vec3_t headClampMaxAngles ) +{ + vec3_t lA; + VectorCopy( lookAngles, lA ); + //clamp the headangles (which should now be relative to the cervical (neck) angles + if ( lA[PITCH] < headClampMinAngles[PITCH] ) + { + lA[PITCH] = headClampMinAngles[PITCH]; + } + else if ( lA[PITCH] > headClampMaxAngles[PITCH] ) + { + lA[PITCH] = headClampMaxAngles[PITCH]; + } + + if ( lA[YAW] < headClampMinAngles[YAW] ) + { + lA[YAW] = headClampMinAngles[YAW]; + } + else if ( lA[YAW] > headClampMaxAngles[YAW] ) + { + lA[YAW] = headClampMaxAngles[YAW]; + } + + if ( lA[ROLL] < headClampMinAngles[ROLL] ) + { + lA[ROLL] = headClampMinAngles[ROLL]; + } + else if ( lA[ROLL] > headClampMaxAngles[ROLL] ) + { + lA[ROLL] = headClampMaxAngles[ROLL]; + } + + //split it up between the neck and cranium + if ( thoracicAngles[PITCH] ) + {//already been set above, blend them + thoracicAngles[PITCH] = (thoracicAngles[PITCH] + (lA[PITCH] * 0.4)) * 0.5f; + } + else + { + thoracicAngles[PITCH] = lA[PITCH] * 0.4; + } + if ( thoracicAngles[YAW] ) + {//already been set above, blend them + thoracicAngles[YAW] = (thoracicAngles[YAW] + (lA[YAW] * 0.1)) * 0.5f; + } + else + { + thoracicAngles[YAW] = lA[YAW] * 0.1; + } + if ( thoracicAngles[ROLL] ) + {//already been set above, blend them + thoracicAngles[ROLL] = (thoracicAngles[ROLL] + (lA[ROLL] * 0.1)) * 0.5f; + } + else + { + thoracicAngles[ROLL] = lA[ROLL] * 0.1; + } + + neckAngles[PITCH] = lA[PITCH] * 0.2f; + neckAngles[YAW] = lA[YAW] * 0.3f; + neckAngles[ROLL] = lA[ROLL] * 0.3f; + + headAngles[PITCH] = lA[PITCH] * 0.4; + headAngles[YAW] = lA[YAW] * 0.6; + headAngles[ROLL] = lA[ROLL] * 0.6; + + /* //non-applicable SP code + if ( G_RidingVehicle( cent->gent ) )// && type == VH_SPEEDER ? + {//aim torso forward too + headAngles[YAW] = neckAngles[YAW] = thoracicAngles[YAW] = 0; + } + */ + + strap_G2API_SetBoneAngles(ghoul2, 0, "cranium", headAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "cervical", neckAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "thoracic", thoracicAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); +} + +//rww - Finally decided to convert all this stuff to BG form. +static void BG_G2ClientSpineAngles( void *ghoul2, int motionBolt, vec3_t cent_lerpOrigin, vec3_t cent_lerpAngles, entityState_t *cent, + int time, vec3_t viewAngles, int ciLegs, int ciTorso, const vec3_t angles, vec3_t thoracicAngles, + vec3_t ulAngles, vec3_t llAngles, vec3_t modelScale, float *tPitchAngle, float *tYawAngle, int *corrTime ) +{ + qboolean doCorr = qfalse; + + //*tPitchAngle = viewAngles[PITCH]; + viewAngles[YAW] = AngleDelta( cent_lerpAngles[YAW], angles[YAW] ); + //*tYawAngle = viewAngles[YAW]; + +#if 1 + if ( !BG_FlippingAnim( cent->legsAnim ) && + !BG_SpinningSaberAnim( cent->legsAnim ) && + !BG_SpinningSaberAnim( cent->torsoAnim ) && + !BG_InSpecialJump( cent->legsAnim ) && + !BG_InSpecialJump( cent->torsoAnim ) && + !BG_InDeathAnim(cent->legsAnim) && + !BG_InDeathAnim(cent->torsoAnim) && + !BG_InRollES(cent, cent->legsAnim) && + !BG_InRollAnim(cent) && + !BG_SaberInSpecial(cent->saberMove) && + !BG_SaberInSpecialAttack(cent->torsoAnim) && + !BG_SaberInSpecialAttack(cent->legsAnim) && + + !BG_InKnockDown(cent->torsoAnim) && + !BG_InKnockDown(cent->legsAnim) && + !BG_InKnockDown(ciTorso) && + !BG_InKnockDown(ciLegs) && + + !BG_FlippingAnim( ciLegs ) && + !BG_SpinningSaberAnim( ciLegs ) && + !BG_SpinningSaberAnim( ciTorso ) && + !BG_InSpecialJump( ciLegs ) && + !BG_InSpecialJump( ciTorso ) && + !BG_InDeathAnim(ciLegs) && + !BG_InDeathAnim(ciTorso) && + !BG_SaberInSpecialAttack(ciTorso) && + !BG_SaberInSpecialAttack(ciLegs) && + + !(cent->eFlags & EF_DEAD) && + (cent->legsAnim) != (cent->torsoAnim) && + (ciLegs) != (ciTorso) && + !cent->m_iVehicleNum) + { + doCorr = qtrue; + } +#else + if ( ((!BG_FlippingAnim( cent->legsAnim ) + && !BG_SpinningSaberAnim( cent->legsAnim ) + && !BG_SpinningSaberAnim( cent->torsoAnim ) + && (cent->legsAnim) != (cent->torsoAnim)) //NOTE: presumes your legs & torso are on the same frame, though they *should* be because PM_SetAnimFinal tries to keep them in synch + || + (!BG_FlippingAnim( ciLegs ) + && !BG_SpinningSaberAnim( ciLegs ) + && !BG_SpinningSaberAnim( ciTorso ) + && (ciLegs) != (ciTorso))) + || + ciLegs != cent->legsAnim + || + ciTorso != cent->torsoAnim) + { + doCorr = qtrue; + *corrTime = time + 1000; //continue correcting for a second after to smooth things out. SP doesn't need this for whatever reason but I can't find a way around it. + } + else if (*corrTime >= time) + { + if (!BG_FlippingAnim( cent->legsAnim ) + && !BG_SpinningSaberAnim( cent->legsAnim ) + && !BG_SpinningSaberAnim( cent->torsoAnim ) + && !BG_FlippingAnim( ciLegs ) + && !BG_SpinningSaberAnim( ciLegs ) + && !BG_SpinningSaberAnim( ciTorso )) + { + doCorr = qtrue; + } + } +#endif + + if (doCorr) + {//FIXME: no need to do this if legs and torso on are same frame + //adjust for motion offset + mdxaBone_t boltMatrix; + vec3_t motionFwd, motionAngles; + vec3_t motionRt, tempAng; + int ang; + + strap_G2API_GetBoltMatrix_NoRecNoRot( ghoul2, 0, motionBolt, &boltMatrix, vec3_origin, cent_lerpOrigin, time, 0, modelScale); + //BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, motionFwd ); + motionFwd[0] = -boltMatrix.matrix[0][1]; + motionFwd[1] = -boltMatrix.matrix[1][1]; + motionFwd[2] = -boltMatrix.matrix[2][1]; + + vectoangles( motionFwd, motionAngles ); + + //BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, motionRt ); + motionRt[0] = -boltMatrix.matrix[0][0]; + motionRt[1] = -boltMatrix.matrix[1][0]; + motionRt[2] = -boltMatrix.matrix[2][0]; + + vectoangles( motionRt, tempAng ); + motionAngles[ROLL] = -tempAng[PITCH]; + + for ( ang = 0; ang < 3; ang++ ) + { + viewAngles[ang] = AngleNormalize180( viewAngles[ang] - AngleNormalize180( motionAngles[ang] ) ); + } + } + + //distribute the angles differently up the spine + //NOTE: each of these distributions must add up to 1.0f + thoracicAngles[PITCH] = viewAngles[PITCH]*0.20f; + llAngles[PITCH] = viewAngles[PITCH]*0.40f; + ulAngles[PITCH] = viewAngles[PITCH]*0.40f; + + thoracicAngles[YAW] = viewAngles[YAW]*0.20f; + ulAngles[YAW] = viewAngles[YAW]*0.35f; + llAngles[YAW] = viewAngles[YAW]*0.45f; + + thoracicAngles[ROLL] = viewAngles[ROLL]*0.20f; + ulAngles[ROLL] = viewAngles[ROLL]*0.35f; + llAngles[ROLL] = viewAngles[ROLL]*0.45f; +} + +/* +================== +CG_SwingAngles +================== +*/ +static float BG_SwingAngles( float destination, float swingTolerance, float clampTolerance, + float speed, float *angle, qboolean *swinging, int frametime ) { + float swing; + float move; + float scale; + + if ( !*swinging ) { + // see if a swing should be started + swing = AngleSubtract( *angle, destination ); + if ( swing > swingTolerance || swing < -swingTolerance ) { + *swinging = qtrue; + } + } + + if ( !*swinging ) { + return 0; + } + + // modify the speed depending on the delta + // so it doesn't seem so linear + swing = AngleSubtract( destination, *angle ); + scale = fabs( swing ); + if ( scale < swingTolerance * 0.5 ) { + scale = 0.5; + } else if ( scale < swingTolerance ) { + scale = 1.0; + } else { + scale = 2.0; + } + + // swing towards the destination angle + if ( swing >= 0 ) { + move = frametime * scale * speed; + if ( move >= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } else if ( swing < 0 ) { + move = frametime * scale * -speed; + if ( move <= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } + + // clamp to no more than tolerance + swing = AngleSubtract( destination, *angle ); + if ( swing > clampTolerance ) { + *angle = AngleMod( destination - (clampTolerance - 1) ); + } else if ( swing < -clampTolerance ) { + *angle = AngleMod( destination + (clampTolerance - 1) ); + } + + return swing; +} + +//#define BONE_BASED_LEG_ANGLES + +//I apologize for this function +qboolean BG_InRoll2( entityState_t *es ) +{ + switch ( (es->legsAnim) ) + { + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + return qtrue; + break; + } + return qfalse; +} + + +extern qboolean BG_SaberLockBreakAnim( int anim ); //bg_panimate.c +void BG_G2PlayerAngles(void *ghoul2, int motionBolt, entityState_t *cent, int time, vec3_t cent_lerpOrigin, + vec3_t cent_lerpAngles, vec3_t legs[3], vec3_t legsAngles, qboolean *tYawing, + qboolean *tPitching, qboolean *lYawing, float *tYawAngle, float *tPitchAngle, + float *lYawAngle, int frametime, vec3_t turAngles, vec3_t modelScale, int ciLegs, + int ciTorso, int *corrTime, vec3_t lookAngles, vec3_t lastHeadAngles, int lookTime, + entityState_t *emplaced, int *crazySmoothFactor) +{ + int adddir = 0; + static int dir; + static int i; + static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; + float degrees_negative = 0; + float degrees_positive = 0; + static float dif; + static float dest; + static float speed; //, speed_dif, speed_desired; + static const float lookSpeed = 1.5f; +#ifdef BONE_BASED_LEG_ANGLES + static float legBoneYaw; +#endif + static vec3_t eyeAngles; + static vec3_t neckAngles; + static vec3_t velocity; + static vec3_t torsoAngles, headAngles; + static vec3_t velPos, velAng; + static vec3_t ulAngles, llAngles, viewAngles, angles, thoracicAngles = {0,0,0}; + static vec3_t headClampMinAngles = {-25,-55,-10}, headClampMaxAngles = {50,50,10}; + + if ( cent->m_iVehicleNum || cent->forceFrame || BG_SaberLockBreakAnim(cent->legsAnim) || BG_SaberLockBreakAnim(cent->torsoAnim) ) + { //a vehicle or riding a vehicle - in either case we don't need to be in here + vec3_t forcedAngles; + + VectorClear(forcedAngles); + forcedAngles[YAW] = cent_lerpAngles[YAW]; + forcedAngles[ROLL] = cent_lerpAngles[ROLL]; + AnglesToAxis( forcedAngles, legs ); + VectorCopy(forcedAngles, legsAngles); + + if (cent->number < MAX_CLIENTS) + { + strap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "cervical", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + } + return; + } + + if ((time+2000) < *corrTime) + { + *corrTime = 0; + } + + VectorCopy( cent_lerpAngles, headAngles ); + headAngles[YAW] = AngleMod( headAngles[YAW] ); + VectorClear( legsAngles ); + VectorClear( torsoAngles ); + // --------- yaw ------------- + + // allow yaw to drift a bit + if ((( cent->legsAnim ) != BOTH_STAND1) || + ( cent->torsoAnim ) != WeaponReadyAnim[cent->weapon] ) + { + // if not standing still, always point all in the same direction + //cent->pe.torso.yawing = qtrue; // always center + *tYawing = qtrue; + //cent->pe.torso.pitching = qtrue; // always center + *tPitching = qtrue; + //cent->pe.legs.yawing = qtrue; // always center + *lYawing = qtrue; + } + + // adjust legs for movement dir + if ( cent->eFlags & EF_DEAD ) { + // don't let dead bodies twitch + dir = 0; + } else { + dir = cent->angles2[YAW]; + if ( dir < 0 || dir > 7 ) { + Com_Error( ERR_DROP, "Bad player movement angle (%i)", dir ); + } + } + + torsoAngles[YAW] = headAngles[YAW]; + + //for now, turn torso instantly and let the legs swing to follow + *tYawAngle = torsoAngles[YAW]; + + // --------- pitch ------------- + + VectorCopy( cent->pos.trDelta, velocity ); + + if (BG_InRoll2(cent)) + { //don't affect angles based on vel then + VectorClear(velocity); + } + else if (cent->weapon == WP_SABER && + BG_SaberInSpecial(cent->saberMove)) + { + VectorClear(velocity); + } + + speed = VectorNormalize( velocity ); + + if (!speed) + { + torsoAngles[YAW] = headAngles[YAW]; + } + + // only show a fraction of the pitch angle in the torso + if ( headAngles[PITCH] > 180 ) { + dest = (-360 + headAngles[PITCH]) * 0.75; + } else { + dest = headAngles[PITCH] * 0.75; + } + + if (cent->m_iVehicleNum) + { //swing instantly on vehicles + *tPitchAngle = dest; + } + else + { + BG_SwingAngles( dest, 15, 30, 0.1, tPitchAngle, tPitching, frametime ); + } + torsoAngles[PITCH] = *tPitchAngle; + + // --------- roll ------------- + + if ( speed ) { + vec3_t axis[3]; + float side; + + speed *= 0.05; + + AnglesToAxis( legsAngles, axis ); + side = speed * DotProduct( velocity, axis[1] ); + legsAngles[ROLL] -= side; + + side = speed * DotProduct( velocity, axis[0] ); + legsAngles[PITCH] += side; + } + + //legsAngles[YAW] = headAngles[YAW] + (movementOffsets[ dir ]*speed_dif); + + //rww - crazy velocity-based leg angle calculation + legsAngles[YAW] = headAngles[YAW]; + velPos[0] = cent_lerpOrigin[0] + velocity[0]; + velPos[1] = cent_lerpOrigin[1] + velocity[1]; + velPos[2] = cent_lerpOrigin[2];// + velocity[2]; + + if ( cent->groundEntityNum == ENTITYNUM_NONE || + cent->forceFrame || + (cent->weapon == WP_EMPLACED_GUN && emplaced) ) + { //off the ground, no direction-based leg angles (same if in saberlock) + VectorCopy(cent_lerpOrigin, velPos); + } + + VectorSubtract(cent_lerpOrigin, velPos, velAng); + + if (!VectorCompare(velAng, vec3_origin)) + { + vectoangles(velAng, velAng); + + if (velAng[YAW] <= legsAngles[YAW]) + { + degrees_negative = (legsAngles[YAW] - velAng[YAW]); + degrees_positive = (360 - legsAngles[YAW]) + velAng[YAW]; + } + else + { + degrees_negative = legsAngles[YAW] + (360 - velAng[YAW]); + degrees_positive = (velAng[YAW] - legsAngles[YAW]); + } + + if ( degrees_negative < degrees_positive ) + { + dif = degrees_negative; + adddir = 0; + } + else + { + dif = degrees_positive; + adddir = 1; + } + + if (dif > 90) + { + dif = (180 - dif); + } + + if (dif > 60) + { + dif = 60; + } + + //Slight hack for when playing is running backward + if (dir == 3 || dir == 5) + { + dif = -dif; + } + + if (adddir) + { + legsAngles[YAW] -= dif; + } + else + { + legsAngles[YAW] += dif; + } + } + + if (cent->m_iVehicleNum) + { //swing instantly on vehicles + *lYawAngle = legsAngles[YAW]; + } + else + { + BG_SwingAngles( legsAngles[YAW], /*40*/0, 90, 0.65, lYawAngle, lYawing, frametime ); + } + legsAngles[YAW] = *lYawAngle; + + /* + // pain twitch + CG_AddPainTwitch( cent, torsoAngles ); + */ + + legsAngles[ROLL] = 0; + torsoAngles[ROLL] = 0; + +// VectorCopy(legsAngles, turAngles); + + // pull the angles back out of the hierarchial chain + AnglesSubtract( headAngles, torsoAngles, headAngles ); + AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); + + legsAngles[PITCH] = 0; + + if (cent->heldByClient) + { //keep the base angles clear when doing the IK stuff, it doesn't compensate for it. + //rwwFIXMEFIXME: Store leg angles off and add them to all the fed in angles for G2 functions? + VectorClear(legsAngles); + legsAngles[YAW] = cent_lerpAngles[YAW]; + } + +#ifdef BONE_BASED_LEG_ANGLES + legBoneYaw = legsAngles[YAW]; + VectorClear(legsAngles); + legsAngles[YAW] = cent_lerpAngles[YAW]; +#endif + + VectorCopy(legsAngles, turAngles); + + AnglesToAxis( legsAngles, legs ); + + VectorCopy( cent_lerpAngles, viewAngles ); + viewAngles[YAW] = viewAngles[ROLL] = 0; + viewAngles[PITCH] *= 0.5; + + VectorSet( angles, 0, legsAngles[1], 0 ); + + angles[0] = legsAngles[0]; + if ( angles[0] > 30 ) + { + angles[0] = 30; + } + else if ( angles[0] < -30 ) + { + angles[0] = -30; + } + + if (cent->weapon == WP_EMPLACED_GUN && + emplaced) + { //if using an emplaced gun, then we want to make sure we're angled to "hold" it right + vec3_t facingAngles; + + VectorSubtract(emplaced->pos.trBase, cent_lerpOrigin, facingAngles); + vectoangles(facingAngles, facingAngles); + + if (emplaced->weapon == WP_NONE) + { //e-web + VectorCopy(facingAngles, legsAngles); + AnglesToAxis( legsAngles, legs ); + } + else + { //misc emplaced + float dif = AngleSubtract(cent_lerpAngles[YAW], facingAngles[YAW]); + + /* + if (emplaced->weapon == WP_NONE) + { //offset is a little bit different for the e-web + dif -= 16.0f; + } + */ + + VectorSet(facingAngles, -16.0f, -dif, 0.0f); + + if (cent->legsAnim == BOTH_STRAFE_LEFT1 || cent->legsAnim == BOTH_STRAFE_RIGHT1) + { //try to adjust so it doesn't look wrong + if (crazySmoothFactor) + { //want to smooth a lot during this because it chops around and looks like ass + *crazySmoothFactor = time + 1000; + } + + BG_G2ClientSpineAngles(ghoul2, motionBolt, cent_lerpOrigin, cent_lerpAngles, cent, time, + viewAngles, ciLegs, ciTorso, angles, thoracicAngles, ulAngles, llAngles, modelScale, + tPitchAngle, tYawAngle, corrTime); + strap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", llAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", ulAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + + VectorAdd(facingAngles, thoracicAngles, facingAngles); + + if (cent->legsAnim == BOTH_STRAFE_LEFT1) + { //this one needs some further correction + facingAngles[YAW] -= 32.0f; + } + } + else + { + //strap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + //strap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + } + + VectorScale(facingAngles, 0.6f, facingAngles); + strap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + VectorScale(facingAngles, 0.8f, facingAngles); + strap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", facingAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + VectorScale(facingAngles, 0.8f, facingAngles); + strap_G2API_SetBoneAngles(ghoul2, 0, "thoracic", facingAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + + //Now we want the head angled toward where we are facing + VectorSet(facingAngles, 0.0f, dif, 0.0f); + VectorScale(facingAngles, 0.6f, facingAngles); + strap_G2API_SetBoneAngles(ghoul2, 0, "cervical", facingAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + + return; //don't have to bother with the rest then + } + } + + BG_G2ClientSpineAngles(ghoul2, motionBolt, cent_lerpOrigin, cent_lerpAngles, cent, time, + viewAngles, ciLegs, ciTorso, angles, thoracicAngles, ulAngles, llAngles, modelScale, + tPitchAngle, tYawAngle, corrTime); + + VectorCopy(cent_lerpAngles, eyeAngles); + + for ( i = 0; i < 3; i++ ) + { + lookAngles[i] = AngleNormalize180( lookAngles[i] ); + eyeAngles[i] = AngleNormalize180( eyeAngles[i] ); + } + AnglesSubtract( lookAngles, eyeAngles, lookAngles ); + + BG_UpdateLookAngles(lookTime, lastHeadAngles, time, lookAngles, lookSpeed, -50.0f, 50.0f, -70.0f, 70.0f, -30.0f, 30.0f); + + BG_G2ClientNeckAngles(ghoul2, time, lookAngles, headAngles, neckAngles, thoracicAngles, headClampMinAngles, headClampMaxAngles); + +#ifdef BONE_BASED_LEG_ANGLES + { + vec3_t bLAngles; + VectorClear(bLAngles); + bLAngles[ROLL] = AngleNormalize180((legBoneYaw - cent_lerpAngles[YAW])); + strap_G2API_SetBoneAngles(ghoul2, 0, "model_root", bLAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + + if (!llAngles[YAW]) + { + llAngles[YAW] -= bLAngles[ROLL]; + } + } +#endif + strap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", llAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", ulAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "thoracic", thoracicAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + //strap_G2API_SetBoneAngles(ghoul2, 0, "cervical", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); +} + +void BG_G2ATSTAngles(void *ghoul2, int time, vec3_t cent_lerpAngles ) +{// up right fwd + strap_G2API_SetBoneAngles(ghoul2, 0, "thoracic", cent_lerpAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); +} + +static qboolean PM_AdjustAnglesForDualJumpAttack( playerState_t *ps, usercmd_t *ucmd ) +{ + //ucmd->angles[PITCH] = ANGLE2SHORT( ps->viewangles[PITCH] ) - ps->delta_angles[PITCH]; + //ucmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - ps->delta_angles[YAW]; + return qtrue; +} + +#ifdef __LCC__ +static void PM_CmdForSaberMoves(usercmd_t *ucmd) +#else +static ID_INLINE void PM_CmdForSaberMoves(usercmd_t *ucmd) +#endif +{ + //DUAL FORWARD+JUMP+ATTACK + if ( ( pm->ps->legsAnim == BOTH_JUMPATTACK6 + && pm->ps->saberMove == LS_JUMPATTACK_DUAL ) + || ( pm->ps->legsAnim == BOTH_BUTTERFLY_FL1 + && pm->ps->saberMove == LS_JUMPATTACK_STAFF_LEFT ) + || ( pm->ps->legsAnim == BOTH_BUTTERFLY_FR1 + && pm->ps->saberMove == LS_JUMPATTACK_STAFF_RIGHT ) + || ( pm->ps->legsAnim == BOTH_BUTTERFLY_RIGHT + && pm->ps->saberMove == LS_BUTTERFLY_RIGHT ) + || ( pm->ps->legsAnim == BOTH_BUTTERFLY_LEFT + && pm->ps->saberMove == LS_BUTTERFLY_LEFT ) ) + { + int aLen = PM_AnimLength(0, BOTH_JUMPATTACK6); + + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + + if ( pm->ps->legsAnim == BOTH_JUMPATTACK6 ) + { //dual stance attack + if ( pm->ps->legsTimer >= 100 //not at end + && (aLen - pm->ps->legsTimer) >= 250 ) //not in beginning + { //middle of anim + //push forward + ucmd->forwardmove = 127; + } + + if ( (pm->ps->legsTimer >= 900 //not at end + && aLen - pm->ps->legsTimer >= 950 ) //not in beginning + || ( pm->ps->legsTimer >= 1600 + && aLen - pm->ps->legsTimer >= 400 ) ) //not in beginning + { //one of the two jumps + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { //still on ground? + if ( pm->ps->groundEntityNum >= MAX_CLIENTS ) + { + //jump! + pm->ps->velocity[2] = 250;//400; + pm->ps->fd.forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + //pm->ps->pm_flags |= PMF_JUMPING; + //FIXME: NPCs yell? + PM_AddEvent(EV_JUMP); + //G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + } + else + { //FIXME: if this is the second jump, maybe we should just stop the anim? + } + } + } + else + { //saberstaff attacks + int aLen = PM_AnimLength(0, (animNumber_t)pm->ps->legsAnim); + float lenMin = 1700.0f; + float lenMax = 1800.0f; + + if (pm->ps->legsAnim == BOTH_BUTTERFLY_LEFT) + { + lenMin = 1200.0f; + lenMax = 1400.0f; + } + + //FIXME: don't slide off people/obstacles? + if ( pm->ps->legsAnim == BOTH_BUTTERFLY_RIGHT + || pm->ps->legsAnim == BOTH_BUTTERFLY_LEFT ) + { + if ( pm->ps->legsTimer > 450 ) + { + switch ( pm->ps->legsAnim ) + { + case BOTH_BUTTERFLY_LEFT: + ucmd->rightmove = -127; + break; + case BOTH_BUTTERFLY_RIGHT: + ucmd->rightmove = 127; + break; + default: + break; + } + } + } + else + { + if ( pm->ps->legsTimer >= 100 //not at end + && aLen - pm->ps->legsTimer >= 250 )//not in beginning + {//middle of anim + //push forward + ucmd->forwardmove = 127; + } + } + + if ( pm->ps->legsTimer >= lenMin && pm->ps->legsTimer < lenMax ) + {//one of the two jumps + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//still on ground? + //jump! + if (pm->ps->legsAnim == BOTH_BUTTERFLY_LEFT) + { + pm->ps->velocity[2] = 350; + } + else + { + pm->ps->velocity[2] = 250; + } + pm->ps->fd.forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + //pm->ps->pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; + //FIXME: NPCs yell? + PM_AddEvent(EV_JUMP); + //G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + else + {//FIXME: if this is the second jump, maybe we should just stop the anim? + } + } + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//can only turn when your feet hit the ground + if (PM_AdjustAnglesForDualJumpAttack(pm->ps, ucmd)) + { + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, ucmd); + } + } + //rwwFIXMEFIXME: Bother with bbox resizing like sp? + } + //STAFF BACK+JUMP+ATTACK + else if (pm->ps->saberMove == LS_A_BACKFLIP_ATK && + pm->ps->legsAnim == BOTH_JUMPATTACK7) + { + int aLen = PM_AnimLength(0, BOTH_JUMPATTACK7); + + if ( pm->ps->legsTimer > 800 //not at end + && aLen - pm->ps->legsTimer >= 400 )//not in beginning + {//middle of anim + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//still on ground? + vec3_t yawAngles, backDir; + + //push backwards some? + VectorSet( yawAngles, 0, pm->ps->viewangles[YAW]+180, 0 ); + AngleVectors( yawAngles, backDir, 0, 0 ); + VectorScale( backDir, 100, pm->ps->velocity ); + + //jump! + pm->ps->velocity[2] = 300; + pm->ps->fd.forceJumpZStart = pm->ps->origin[2]; //so we don't take damage if we land at same height + //pm->ps->pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; + + //FIXME: NPCs yell? + PM_AddEvent(EV_JUMP); + //G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + ucmd->upmove = 0; //clear any actual jump command + } + } + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + } + //STAFF/DUAL SPIN ATTACK + else if (pm->ps->saberMove == LS_SPINATTACK || + pm->ps->saberMove == LS_SPINATTACK_DUAL) + { + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + //lock their viewangles during these attacks. + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, ucmd); + } +} + +//constrain him based on the angles of his vehicle and the caps +void PM_VehicleViewAngles(playerState_t *ps, bgEntity_t *veh, usercmd_t *ucmd) +{ + Vehicle_t *pVeh = veh->m_pVehicle; + qboolean setAngles = qtrue; + vec3_t clampMin; + vec3_t clampMax; + int i; + + if ( veh->m_pVehicle->m_pPilot + && veh->m_pVehicle->m_pPilot->s.number == ps->clientNum ) + {//set the pilot's viewangles to the vehicle's viewangles + if ( 1 )//!BG_UnrestrainedPitchRoll( ps, veh->m_pVehicle ) ) + {//only if not if doing special free-roll/pitch control + setAngles = qtrue; + clampMin[PITCH] = -pVeh->m_pVehicleInfo->lookPitch; + clampMax[PITCH] = pVeh->m_pVehicleInfo->lookPitch; + clampMin[YAW] = clampMax[YAW] = 0; + clampMin[ROLL] = clampMax[ROLL] = -1; + } + } + else + { + //NOTE: passengers can look around freely, UNLESS they're controlling a turret! + for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ ) + { + if ( veh->m_pVehicle->m_pVehicleInfo->turret[i].passengerNum == ps->generic1 ) + {//this turret is my station + setAngles = qtrue; + clampMin[PITCH] = veh->m_pVehicle->m_pVehicleInfo->turret[i].pitchClampUp; + clampMax[PITCH] = veh->m_pVehicle->m_pVehicleInfo->turret[i].pitchClampDown; + clampMin[YAW] = veh->m_pVehicle->m_pVehicleInfo->turret[i].yawClampRight; + clampMax[YAW] = veh->m_pVehicle->m_pVehicleInfo->turret[i].yawClampLeft; + clampMin[ROLL] = clampMax[ROLL] = 0; + break; + } + } + } + if ( setAngles ) + { + for ( i = 0; i < 3; i++ ) + {//clamp viewangles + if ( clampMin[i] == -1 || clampMax[i] == -1 ) + {//no clamp + } + else if ( !clampMin[i] && !clampMax[i] ) + {//no allowance + //ps->viewangles[i] = veh->playerState->viewangles[i]; + } + else + {//allowance + if (ps->viewangles[i] > clampMax[i]) + { + ps->viewangles[i] = clampMax[i]; + } + else if (ps->viewangles[i] < clampMin[i]) + { + ps->viewangles[i] = clampMin[i]; + } + } + } + + PM_SetPMViewAngle(ps, ps->viewangles, ucmd); + } +} + +/* +//constrain him based on the angles of his vehicle and the caps +void PM_VehicleViewAngles(playerState_t *ps, bgEntity_t *veh, usercmd_t *ucmd) +{ + Vehicle_t *pVeh = veh->m_pVehicle; + + //now set the viewangles to the vehicle's directly + ps->viewangles[YAW] = veh->playerState->viewangles[YAW]; + + //constrain the viewangles pitch based on the vehicle properties + if ( !pVeh->m_pVehicleInfo->lookPitch ) + {//not allowed to look up & down! ....??? + ps->viewangles[PITCH] = veh->playerState->viewangles[PITCH]; + } + else + {//clamp + if (ps->viewangles[PITCH] > pVeh->m_pVehicleInfo->lookPitch) + { + ps->viewangles[PITCH] = pVeh->m_pVehicleInfo->lookPitch; + } + else if (ps->viewangles[PITCH] < -pVeh->m_pVehicleInfo->lookPitch) + { + ps->viewangles[PITCH] = -pVeh->m_pVehicleInfo->lookPitch; + } + } + + PM_SetPMViewAngle(ps, ps->viewangles, ucmd); +} +*/ +//see if a weapon is ok to use on a vehicle +qboolean PM_WeaponOkOnVehicle( int weapon ) +{ + //FIXME: check g_vehicleInfo for our vehicle? + switch ( weapon ) + { + //case WP_NONE: + case WP_MELEE: + case WP_SABER: + case WP_BLASTER: + //case WP_THERMAL: + return qtrue; + break; + } + return qfalse; +} + +//do we have a weapon that's ok for using on the vehicle? +int PM_GetOkWeaponForVehicle(void) +{ + int i = 0; + + while (i < WP_NUM_WEAPONS) + { + if ((pm->ps->stats[STAT_WEAPONS] & (1 << i)) && + PM_WeaponOkOnVehicle(i)) + { //this one's good + return i; + } + + i++; + } + + //oh dear! + //assert(!"No valid veh weaps"); + return -1; +} + +//force the vehicle to turn and travel to its forced destination point +void PM_VehForcedTurning(bgEntity_t *veh) +{ + bgEntity_t *dst = PM_BGEntForNum(veh->playerState->vehTurnaroundIndex); + float pitchD, yawD; + vec3_t dir; + + if (!veh || !veh->m_pVehicle) + { + return; + } + + if (!dst) + { //can't find dest ent? + return; + } + + pm->cmd.upmove = veh->m_pVehicle->m_ucmd.upmove = 127; + pm->cmd.forwardmove = veh->m_pVehicle->m_ucmd.forwardmove = 0; + pm->cmd.rightmove = veh->m_pVehicle->m_ucmd.rightmove = 0; + + VectorSubtract(dst->s.origin, veh->playerState->origin, dir); + vectoangles(dir, dir); + + yawD = AngleSubtract(pm->ps->viewangles[YAW], dir[YAW]); + pitchD = AngleSubtract(pm->ps->viewangles[PITCH], dir[PITCH]); + + yawD *= 0.6f*pml.frametime; + pitchD *= 0.6f*pml.frametime; + + veh->playerState->viewangles[YAW] = AngleSubtract(veh->playerState->viewangles[YAW], yawD); + veh->playerState->viewangles[PITCH] = AngleSubtract(veh->playerState->viewangles[PITCH], pitchD); + pm->ps->viewangles[YAW] = veh->playerState->viewangles[YAW]; + pm->ps->viewangles[PITCH] = 0; + + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + PM_SetPMViewAngle(veh->playerState, veh->playerState->viewangles, &pm->cmd); + VectorClear( veh->m_pVehicle->m_vPrevRiderViewAngles ); + veh->m_pVehicle->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(pm->ps->viewangles[YAW]); +} + +void PM_VehFaceHyperspacePoint(bgEntity_t *veh) +{ + + if (!veh || !veh->m_pVehicle) + { + return; + } + else + { + float timeFrac = ((float)(pm->cmd.serverTime-veh->playerState->hyperSpaceTime))/HYPERSPACE_TIME; + float turnRate, aDelta; + int i, matchedAxes = 0; + + pm->cmd.upmove = veh->m_pVehicle->m_ucmd.upmove = 127; + pm->cmd.forwardmove = veh->m_pVehicle->m_ucmd.forwardmove = 0; + pm->cmd.rightmove = veh->m_pVehicle->m_ucmd.rightmove = 0; + + turnRate = (90.0f*pml.frametime); + for ( i = 0; i < 3; i++ ) + { + aDelta = AngleSubtract(veh->playerState->hyperSpaceAngles[i], veh->m_pVehicle->m_vOrientation[i]); + if ( fabs( aDelta ) < turnRate ) + {//all is good + veh->playerState->viewangles[i] = veh->playerState->hyperSpaceAngles[i]; + matchedAxes++; + } + else + { + aDelta = AngleSubtract(veh->playerState->hyperSpaceAngles[i], veh->playerState->viewangles[i]); + if ( fabs( aDelta ) < turnRate ) + { + veh->playerState->viewangles[i] = veh->playerState->hyperSpaceAngles[i]; + } + else if ( aDelta > 0 ) + { + if ( i == YAW ) + { + veh->playerState->viewangles[i] = AngleNormalize360( veh->playerState->viewangles[i]+turnRate ); + } + else + { + veh->playerState->viewangles[i] = AngleNormalize180( veh->playerState->viewangles[i]+turnRate ); + } + } + else + { + if ( i == YAW ) + { + veh->playerState->viewangles[i] = AngleNormalize360( veh->playerState->viewangles[i]-turnRate ); + } + else + { + veh->playerState->viewangles[i] = AngleNormalize180( veh->playerState->viewangles[i]-turnRate ); + } + } + } + } + + pm->ps->viewangles[YAW] = veh->playerState->viewangles[YAW]; + pm->ps->viewangles[PITCH] = 0.0f; + + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + PM_SetPMViewAngle(veh->playerState, veh->playerState->viewangles, &pm->cmd); + VectorClear( veh->m_pVehicle->m_vPrevRiderViewAngles ); + veh->m_pVehicle->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(pm->ps->viewangles[YAW]); + + if ( timeFrac < HYPERSPACE_TELEPORT_FRAC ) + {//haven't gone through yet + if ( matchedAxes < 3 ) + {//not facing the right dir yet + //keep hyperspace time up to date + veh->playerState->hyperSpaceTime += pml.msec; + } + else if ( !(veh->playerState->eFlags2&EF2_HYPERSPACE)) + {//flag us as ready to hyperspace! + veh->playerState->eFlags2 |= EF2_HYPERSPACE; + } + } + } +} + +void BG_VehicleAdjustBBoxForOrientation( Vehicle_t *veh, vec3_t origin, vec3_t mins, vec3_t maxs, + int clientNum, int tracemask, + void (*localTrace)(trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask)) +{ + if ( !veh + || !veh->m_pVehicleInfo->length + || !veh->m_pVehicleInfo->width + || !veh->m_pVehicleInfo->height ) + //|| veh->m_LandTrace.fraction < 1.0f ) + { + return; + } + else if ( veh->m_pVehicleInfo->type != VH_FIGHTER + //&& veh->m_pVehicleInfo->type != VH_SPEEDER + && veh->m_pVehicleInfo->type != VH_FLIER ) + {//only those types of vehicles have dynamic bboxes, the rest just use a static bbox + VectorSet( maxs, veh->m_pVehicleInfo->width/2.0f, veh->m_pVehicleInfo->width/2.0f, veh->m_pVehicleInfo->height+DEFAULT_MINS_2 ); + VectorSet( mins, veh->m_pVehicleInfo->width/-2.0f, veh->m_pVehicleInfo->width/-2.0f, DEFAULT_MINS_2 ); + return; + } + else + { + vec3_t axis[3], point[8]; + vec3_t newMins, newMaxs; + int curAxis = 0, i; + trace_t trace; + + AnglesToAxis( veh->m_vOrientation, axis ); + VectorMA( origin, veh->m_pVehicleInfo->length/2.0f, axis[0], point[0] ); + VectorMA( origin, -veh->m_pVehicleInfo->length/2.0f, axis[0], point[1] ); + //extrapolate each side up and down + VectorMA( point[0], veh->m_pVehicleInfo->height/2.0f, axis[2], point[0] ); + VectorMA( point[0], -veh->m_pVehicleInfo->height, axis[2], point[2] ); + VectorMA( point[1], veh->m_pVehicleInfo->height/2.0f, axis[2], point[1] ); + VectorMA( point[1], -veh->m_pVehicleInfo->height, axis[2], point[3] ); + + VectorMA( origin, veh->m_pVehicleInfo->width/2.0f, axis[1], point[4] ); + VectorMA( origin, -veh->m_pVehicleInfo->width/2.0f, axis[1], point[5] ); + //extrapolate each side up and down + VectorMA( point[4], veh->m_pVehicleInfo->height/2.0f, axis[2], point[4] ); + VectorMA( point[4], -veh->m_pVehicleInfo->height, axis[2], point[6] ); + VectorMA( point[5], veh->m_pVehicleInfo->height/2.0f, axis[2], point[5] ); + VectorMA( point[5], -veh->m_pVehicleInfo->height, axis[2], point[7] ); + /* + VectorMA( origin, veh->m_pVehicleInfo->height/2.0f, axis[2], point[4] ); + VectorMA( origin, -veh->m_pVehicleInfo->height/2.0f, axis[2], point[5] ); + */ + //Now inflate a bbox around these points + VectorCopy( origin, newMins ); + VectorCopy( origin, newMaxs ); + for ( curAxis = 0; curAxis < 3; curAxis++ ) + { + for ( i = 0; i < 8; i++ ) + { + if ( point[i][curAxis] > newMaxs[curAxis] ) + { + newMaxs[curAxis] = point[i][curAxis]; + } + else if ( point[i][curAxis] < newMins[curAxis] ) + { + newMins[curAxis] = point[i][curAxis]; + } + } + } + VectorSubtract( newMins, origin, newMins ); + VectorSubtract( newMaxs, origin, newMaxs ); + //now see if that's a valid way to be + if (localTrace) + { + localTrace( &trace, origin, newMins, newMaxs, origin, clientNum, tracemask ); + } + else + { //don't care about solid stuff then + trace.startsolid = trace.allsolid = 0; + } + if ( !trace.startsolid && !trace.allsolid ) + {//let's use it! + VectorCopy( newMins, mins ); + VectorCopy( newMaxs, maxs ); + } + //else: just use the last one, I guess...? + //FIXME: make it as close as possible? Or actually prevent the change in m_vOrientation? Or push away from anything we hit? + } +} +/* +================ +PmoveSingle + +================ +*/ +extern void trap_SnapVector( float *v ); +extern int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); +extern qboolean BG_FighterUpdate(Vehicle_t *pVeh, const usercmd_t *pUcmd, vec3_t trMins, vec3_t trMaxs, float gravity, + void (*traceFunc)( trace_t *results, const vec3_t start, const vec3_t lmins, const vec3_t lmaxs, const vec3_t end, int passEntityNum, int contentMask )); //FighterNPC.c + +#define JETPACK_HOVER_HEIGHT 64 + +//#define _TESTING_VEH_PREDICTION + +void PM_MoveForKata(usercmd_t *ucmd) +{ + if ( pm->ps->legsAnim == BOTH_A7_SOULCAL + && pm->ps->saberMove == LS_STAFF_SOULCAL ) + {//forward spinning staff attack + ucmd->upmove = 0; + + if ( PM_CanRollFromSoulCal( pm->ps ) ) + { + ucmd->upmove = -127; + ucmd->rightmove = 0; + if (ucmd->forwardmove < 0) + { + ucmd->forwardmove = 0; + } + } + else + { + ucmd->rightmove = 0; + //FIXME: don't slide off people/obstacles? + if ( pm->ps->legsTimer >= 2750 ) + {//not at end + //push forward + ucmd->forwardmove = 64; + } + else + { + ucmd->forwardmove = 0; + } + } + if ( pm->ps->legsTimer >= 2650 + && pm->ps->legsTimer < 2850 ) + {//the jump + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//still on ground? + //jump! + pm->ps->velocity[2] = 250; + pm->ps->fd.forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + // pm->ps->pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; + //FIXME: NPCs yell? + PM_AddEvent(EV_JUMP); + } + } + } + else if (pm->ps->legsAnim == BOTH_A2_SPECIAL) + { //medium kata + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + if (pm->ps->legsTimer < 2700 && pm->ps->legsTimer > 2300) + { + pm->cmd.forwardmove = 127; + } + else if (pm->ps->legsTimer < 900 && pm->ps->legsTimer > 500) + { + pm->cmd.forwardmove = 127; + } + else + { + pm->cmd.forwardmove = 0; + } + } + else if (pm->ps->legsAnim == BOTH_A3_SPECIAL) + { //strong kata + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + if (pm->ps->legsTimer < 1700 && pm->ps->legsTimer > 1000) + { + pm->cmd.forwardmove = 127; + } + else + { + pm->cmd.forwardmove = 0; + } + } + else + { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } +} + +void PmoveSingle (pmove_t *pmove) { + qboolean stiffenedUp = qfalse; + float gDist = 0; + qboolean noAnimate = qfalse; + int savedGravity = 0; + + pm = pmove; + + if (pm->ps->emplacedIndex) + { + if (pm->cmd.buttons & BUTTON_ALT_ATTACK) + { //hackerrific. + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + pm->cmd.buttons |= BUTTON_ATTACK; + } + } + + //set up these "global" bg ents + pm_entSelf = PM_BGEntForNum(pm->ps->clientNum); + if (pm->ps->m_iVehicleNum) + { + if (pm->ps->clientNum < MAX_CLIENTS) + { //player riding vehicle + pm_entVeh = PM_BGEntForNum(pm->ps->m_iVehicleNum); + } + else + { //vehicle with player pilot + pm_entVeh = PM_BGEntForNum(pm->ps->m_iVehicleNum-1); + } + } + else + { //no vehicle ent + pm_entVeh = NULL; + } + + gPMDoSlowFall = PM_DoSlowFall(); + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint fot the previous frame + c_pmove++; + + // clear results + pm->numtouch = 0; + pm->watertype = 0; + pm->waterlevel = 0; + + if (PM_IsRocketTrooper()) + { //kind of nasty, don't let them crouch or anything if nonhumanoid (probably a rockettrooper) + if (pm->cmd.upmove < 0) + { + pm->cmd.upmove = 0; + } + } + + if (pm->ps->pm_type == PM_FLOAT) + { //You get no control over where you go in grip movement + stiffenedUp = qtrue; + } + else if (pm->ps->eFlags & EF_DISINTEGRATION) + { + stiffenedUp = qtrue; + } + else if ( BG_SaberLockBreakAnim( pm->ps->legsAnim ) + || BG_SaberLockBreakAnim( pm->ps->torsoAnim ) + || pm->ps->saberLockTime >= pm->cmd.serverTime ) + {//can't move or turn + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + else if ( pm->ps->saberMove == LS_A_BACK || pm->ps->saberMove == LS_A_BACK_CR || + pm->ps->saberMove == LS_A_BACKSTAB || pm->ps->saberMove == LS_A_FLIP_STAB || + pm->ps->saberMove == LS_A_FLIP_SLASH || pm->ps->saberMove == LS_A_JUMP_T__B_ || + pm->ps->saberMove == LS_DUAL_LR || pm->ps->saberMove == LS_DUAL_FB) + { + if (pm->ps->legsAnim == BOTH_JUMPFLIPSTABDOWN || + pm->ps->legsAnim == BOTH_JUMPFLIPSLASHDOWN1) + { //flipover medium stance attack + if (pm->ps->legsTimer < 1600 && pm->ps->legsTimer > 900) + { + pm->ps->viewangles[YAW] += pml.frametime*240.0f; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + } + stiffenedUp = qtrue; + } + else if ((pm->ps->legsAnim) == (BOTH_A2_STABBACK1) || + (pm->ps->legsAnim) == (BOTH_ATTACK_BACK) || + (pm->ps->legsAnim) == (BOTH_CROUCHATTACKBACK1) || + (pm->ps->legsAnim) == (BOTH_FORCELEAP2_T__B_) || + (pm->ps->legsAnim) == (BOTH_JUMPFLIPSTABDOWN) || + (pm->ps->legsAnim) == (BOTH_JUMPFLIPSLASHDOWN1)) + { + stiffenedUp = qtrue; + } + else if (pm->ps->legsAnim == BOTH_ROLL_STAB) + { + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + else if (pm->ps->heldByClient) + { + stiffenedUp = qtrue; + } + else if (BG_KickMove(pm->ps->saberMove) || BG_KickingAnim(pm->ps->legsAnim)) + { + stiffenedUp = qtrue; + } + else if (BG_InGrappleMove(pm->ps->torsoAnim)) + { + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + else if ( pm->ps->saberMove == LS_STABDOWN_DUAL || + pm->ps->saberMove == LS_STABDOWN_STAFF || + pm->ps->saberMove == LS_STABDOWN) + {//FIXME: need to only move forward until we bump into our target...? + if (pm->ps->legsTimer < 800) + { //freeze movement near end of anim + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + else + { //force forward til then + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + pm->cmd.forwardmove = 64; + } + } + else if (pm->ps->saberMove == LS_PULL_ATTACK_STAB || + pm->ps->saberMove == LS_PULL_ATTACK_SWING) + { + stiffenedUp = qtrue; + } + else if (BG_SaberInKata(pm->ps->saberMove) || + BG_InKataAnim(pm->ps->torsoAnim) || + BG_InKataAnim(pm->ps->legsAnim)) + { + PM_MoveForKata(&pm->cmd); + } + else if ( BG_FullBodyTauntAnim( pm->ps->legsAnim ) + && BG_FullBodyTauntAnim( pm->ps->torsoAnim ) ) + { + if ( (pm->cmd.buttons&BUTTON_ATTACK) + || (pm->cmd.buttons&BUTTON_ALT_ATTACK) + || (pm->cmd.buttons&BUTTON_FORCEPOWER) + || (pm->cmd.buttons&BUTTON_FORCEGRIP) + || (pm->cmd.buttons&BUTTON_FORCE_LIGHTNING) + || (pm->cmd.buttons&BUTTON_FORCE_DRAIN) + || pm->cmd.upmove ) + {//stop the anim + if ( pm->ps->legsAnim == BOTH_MEDITATE + && pm->ps->torsoAnim == BOTH_MEDITATE ) + { + PM_SetAnim( SETANIM_BOTH, BOTH_MEDITATE_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + } + else + { + pm->ps->legsTimer = pm->ps->torsoTimer = 0; + } + if ( pm->ps->forceHandExtend == HANDEXTEND_TAUNT ) + { + pm->ps->forceHandExtend = 0; + } + } + else + { + if ( pm->ps->legsAnim == BOTH_MEDITATE ) + { + if ( pm->ps->legsTimer < 100 ) + { + pm->ps->legsTimer = 100; + } + } + if ( pm->ps->torsoAnim == BOTH_MEDITATE ) + { + if ( pm->ps->torsoTimer < 100 ) + { + pm->ps->legsTimer = 100; + } + pm->ps->forceHandExtend = HANDEXTEND_TAUNT; + pm->ps->forceHandExtendTime = pm->cmd.serverTime + 100; + } + if ( pm->ps->legsTimer > 0 || pm->ps->torsoTimer > 0 ) + { + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + pm->cmd.forwardmove = 0; + pm->cmd.buttons = 0; + } + } + } + else if ( pm->ps->legsAnim == BOTH_MEDITATE_END + && pm->ps->legsTimer > 0 ) + { + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + pm->cmd.forwardmove = 0; + pm->cmd.buttons = 0; + } + else if (pm->ps->legsAnim == BOTH_FORCELAND1 || + pm->ps->legsAnim == BOTH_FORCELANDBACK1 || + pm->ps->legsAnim == BOTH_FORCELANDRIGHT1 || + pm->ps->legsAnim == BOTH_FORCELANDLEFT1) + { //can't move while in a force land + stiffenedUp = qtrue; + } + + if ( pm->ps->saberMove == LS_A_LUNGE ) + {//can't move during lunge + pm->cmd.rightmove = pm->cmd.upmove = 0; + if ( pm->ps->legsTimer > 500 ) + { + pm->cmd.forwardmove = 127; + } + else + { + pm->cmd.forwardmove = 0; + } + } + + if ( pm->ps->saberMove == LS_A_JUMP_T__B_ ) + {//can't move during leap + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//hit the ground + pm->cmd.forwardmove = 0; + } + pm->cmd.rightmove = pm->cmd.upmove = 0; + } + +#if 0 + if ((pm->ps->legsAnim) == BOTH_KISSER1LOOP || + (pm->ps->legsAnim) == BOTH_KISSEE1LOOP) + { + stiffenedUp = qtrue; + } +#endif + + if (pm->ps->emplacedIndex) + { + if (pm->cmd.forwardmove < 0 || PM_GroundDistance() > 32.0f) + { + pm->ps->emplacedIndex = 0; + pm->ps->saberHolstered = 0; + } + else + { + stiffenedUp = qtrue; + } + } + + /* + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->weaponstate == WEAPON_CHARGING_ALT) + { //not allowed to move while charging the disruptor + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + if (pm->cmd.upmove > 0) + { + pm->cmd.upmove = 0; + } + } + */ + + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->weaponstate == WEAPON_CHARGING_ALT) + { //not allowed to move while charging the disruptor + if (pm->cmd.forwardmove || + pm->cmd.rightmove || + pm->cmd.upmove > 0) + { //get out + pm->ps->weaponstate = WEAPON_READY; + pm->ps->weaponTime = 1000; + PM_AddEventWithParm(EV_WEAPON_CHARGE, WP_DISRUPTOR); //cut the weapon charge sound + pm->cmd.upmove = 0; + } + } + else if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { //can't jump + if (pm->cmd.upmove > 0) + { + pm->cmd.upmove = 0; + } + } + + if (stiffenedUp) + { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + + if (pm->ps->fd.forceGripCripple) + { //don't let attack or alt attack if being gripped I guess + pm->cmd.buttons &= ~BUTTON_ATTACK; + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + } + + if ( BG_InRoll( pm->ps, pm->ps->legsAnim ) ) + { //can't roll unless you're able to move normally + BG_CmdForRoll( pm->ps, pm->ps->legsAnim, &pm->cmd ); + } + + PM_CmdForSaberMoves(&pm->cmd); + + BG_AdjustClientSpeed(pm->ps, &pm->cmd, pm->cmd.serverTime); + + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies + } + + // make sure walking button is clear if they are running, to avoid + // proxy no-footsteps cheats + if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { + pm->cmd.buttons &= ~BUTTON_WALKING; + } + + // set the talk balloon flag + if ( pm->cmd.buttons & BUTTON_TALK ) { + pm->ps->eFlags |= EF_TALK; + } else { + pm->ps->eFlags &= ~EF_TALK; + } + + pm_cancelOutZoom = qfalse; + if (pm->ps->weapon == WP_DISRUPTOR && + pm->ps->zoomMode == 1) + { + if ((pm->cmd.buttons & BUTTON_ALT_ATTACK) && + !(pm->cmd.buttons & BUTTON_ATTACK) && + pm->ps->zoomLocked) + { + pm_cancelOutZoom = qtrue; + } + } + // In certain situations, we may want to control which attack buttons are pressed and what kind of functionality + // is attached to them + PM_AdjustAttackStates( pm ); + + // clear the respawned flag if attack and use are cleared + if ( pm->ps->stats[STAT_HEALTH] > 0 && + !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) { + pm->ps->pm_flags &= ~PMF_RESPAWNED; + } + + // if talk button is down, dissallow all other input + // this is to prevent any possible intercept proxy from + // adding fake talk balloons + if ( pmove->cmd.buttons & BUTTON_TALK ) { + // keep the talk button set tho for when the cmd.serverTime > 66 msec + // and the same cmd is used multiple times in Pmove + pmove->cmd.buttons = BUTTON_TALK; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + } + + // clear all pmove local vars + memset (&pml, 0, sizeof(pml)); + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; + if ( pml.msec < 1 ) { + pml.msec = 1; + } else if ( pml.msec > 200 ) { + pml.msec = 200; + } + + /* + if (pm->ps->clientNum >= MAX_CLIENTS) + { +#ifdef QAGAME + Com_Printf( "^1 SERVER N%i msec %d\n", pm->ps->clientNum, pml.msec ); +#else + Com_Printf( "^2 CLIENT N%i msec %d\n", pm->ps->clientNum, pml.msec ); +#endif + } + */ + + pm->ps->commandTime = pmove->cmd.serverTime; + + // save old org in case we get stuck + VectorCopy (pm->ps->origin, pml.previous_origin); + + // save old velocity for crashlanding + VectorCopy (pm->ps->velocity, pml.previous_velocity); + + pml.frametime = pml.msec * 0.001; + + if (pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_VEHICLE) + { //we are a vehicle + bgEntity_t *veh = pm_entSelf; + assert( veh && veh->m_pVehicle); + if ( veh && veh->m_pVehicle ) + { + veh->m_pVehicle->m_fTimeModifier = pml.frametime*60.0f; + } + } + else if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + { + bgEntity_t *veh = pm_entVeh; + + if (veh && veh->playerState && + (pm->cmd.serverTime-veh->playerState->hyperSpaceTime) < HYPERSPACE_TIME) + { //going into hyperspace, turn to face the right angles + PM_VehFaceHyperspacePoint(veh); + } + else if (veh && veh->playerState && + veh->playerState->vehTurnaroundIndex && + veh->playerState->vehTurnaroundTime > pm->cmd.serverTime) + { //riding this vehicle, turn my view too + PM_VehForcedTurning(veh); + } + } + + if ( pm->ps->legsAnim == BOTH_FORCEWALLRUNFLIP_ALT && + pm->ps->legsTimer > 0 ) + { + vec3_t vFwd, fwdAng; + VectorSet(fwdAng, 0.0f, pm->ps->viewangles[YAW], 0.0f); + + AngleVectors( fwdAng, vFwd, NULL, NULL ); + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + float savZ = pm->ps->velocity[2]; + VectorScale( vFwd, 100, pm->ps->velocity ); + pm->ps->velocity[2] = savZ; + } + pm->cmd.forwardmove = pm->cmd.rightmove = pm->cmd.upmove = 0; + PM_AdjustAnglesForWallRunUpFlipAlt( &pm->cmd ); + } + +// PM_AdjustAngleForWallRun(pm->ps, &pm->cmd, qtrue); +// PM_AdjustAnglesForStabDown( pm->ps, &pm->cmd ); + PM_AdjustAngleForWallJump( pm->ps, &pm->cmd, qtrue ); + PM_AdjustAngleForWallRunUp( pm->ps, &pm->cmd, qtrue ); + PM_AdjustAngleForWallRun( pm->ps, &pm->cmd, qtrue ); + + if (pm->ps->saberMove == LS_A_JUMP_T__B_ || pm->ps->saberMove == LS_A_LUNGE || + pm->ps->saberMove == LS_A_BACK_CR || pm->ps->saberMove == LS_A_BACK || + pm->ps->saberMove == LS_A_BACKSTAB) + { + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + +#if 0 + if ((pm->ps->legsAnim) == BOTH_KISSER1LOOP || + (pm->ps->legsAnim) == BOTH_KISSEE1LOOP) + { + pm->ps->viewangles[PITCH] = 0; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } +#endif + + PM_SetSpecialMoveValues(); + + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + AngleVectors (pm->ps->viewangles, pml.forward, pml.right, pml.up); + + if ( pm->cmd.upmove < 10 && !(pm->ps->pm_flags & PMF_STUCK_TO_WALL)) { + // not holding jump + pm->ps->pm_flags &= ~PMF_JUMP_HELD; + } + + // decide if backpedaling animations should be used + if ( pm->cmd.forwardmove < 0 ) { + pm->ps->pm_flags |= PMF_BACKWARDS_RUN; + } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { + pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; + } + + if ( pm->ps->pm_type >= PM_DEAD ) { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + + /* + if (pm->ps->fd.saberAnimLevel == SS_STAFF && + (pm->cmd.buttons & BUTTON_ALT_ATTACK) && + pm->cmd.upmove > 0) + { //this is how you do kick-for-condition + pm->cmd.upmove = 0; + pm->ps->pm_flags |= PMF_JUMP_HELD; + } + */ + + if (pm->ps->saberLockTime >= pm->cmd.serverTime) + { + pm->cmd.upmove = 0; + pm->cmd.forwardmove = 0;//50; + pm->cmd.rightmove = 0;//*= 0.1; + } + + if ( pm->ps->pm_type == PM_SPECTATOR ) { + PM_CheckDuck (); + if (!pm->noSpecMove) + { + PM_FlyMove (); + } + PM_DropTimers (); + return; + } + + if ( pm->ps->pm_type == PM_NOCLIP ) { + if (pm->ps->clientNum < MAX_CLIENTS) + { + PM_NoclipMove (); + PM_DropTimers (); + return; + } + } + + if (pm->ps->pm_type == PM_FREEZE) { + return; // no movement at all + } + + if ( pm->ps->pm_type == PM_INTERMISSION || pm->ps->pm_type == PM_SPINTERMISSION) { + return; // no movement at all + } + + // set watertype, and waterlevel + PM_SetWaterLevel(); + pml.previous_waterlevel = pmove->waterlevel; + + // set mins, maxs, and viewheight + PM_CheckDuck (); + + if (pm->ps->pm_type == PM_JETPACK) + { + gDist = PM_GroundDistance(); + savedGravity = pm->ps->gravity; + + if (gDist < JETPACK_HOVER_HEIGHT+64) + { + pm->ps->gravity *= 0.1f; + } + else + { + pm->ps->gravity *= 0.25f; + } + } + else if (gPMDoSlowFall) + { + savedGravity = pm->ps->gravity; + pm->ps->gravity *= 0.5; + } + + //if we're in jetpack mode then see if we should be jetting around + if (pm->ps->pm_type == PM_JETPACK) + { + if (pm->cmd.rightmove > 0) + { + PM_ContinueLegsAnim(BOTH_INAIRRIGHT1); + } + else if (pm->cmd.rightmove < 0) + { + PM_ContinueLegsAnim(BOTH_INAIRLEFT1); + } + else if (pm->cmd.forwardmove > 0) + { + PM_ContinueLegsAnim(BOTH_INAIR1); + } + else if (pm->cmd.forwardmove < 0) + { + PM_ContinueLegsAnim(BOTH_INAIRBACK1); + } + else + { + PM_ContinueLegsAnim(BOTH_INAIR1); + } + + if (pm->ps->weapon == WP_SABER && + BG_SpinningSaberAnim( pm->ps->legsAnim )) + { //make him stir around since he shouldn't have any real control when spinning + pm->ps->velocity[0] += Q_irand(-100, 100); + pm->ps->velocity[1] += Q_irand(-100, 100); + } + + if (pm->cmd.upmove > 0 && pm->ps->velocity[2] < 256) + { //cap upward velocity off at 256. Seems reasonable. + float addIn = 12.0f; + +/* + //Add based on our distance to the ground if we're already travelling upward + if (pm->ps->velocity[2] > 0) + { + while (gDist > 64) + { //subtract 1 for every 64 units off the ground we get + addIn--; + + gDist -= 64; + + if (addIn <= 0) + { //break out if we're not even going to add anything + break; + } + } + } +*/ + if (pm->ps->velocity[2] > 0) + { + addIn = 12.0f - (gDist / 64.0f); + } + + if (addIn > 0.0f) + { + pm->ps->velocity[2] += addIn; + } + + pm->ps->eFlags |= EF_JETPACK_FLAMING; //going up + } + else + { + pm->ps->eFlags &= ~EF_JETPACK_FLAMING; //idling + + if (pm->ps->velocity[2] < 256) + { + if (pm->ps->velocity[2] < -100) + { + pm->ps->velocity[2] = -100; + } + if (gDist < JETPACK_HOVER_HEIGHT) + { //make sure we're always hovering off the ground somewhat while jetpack is active + pm->ps->velocity[2] += 2; + } + } + } + } + + if (pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && pm_entSelf->m_pVehicle) + { //Now update our mins/maxs to match our m_vOrientation based on our length, width & height + BG_VehicleAdjustBBoxForOrientation( pm_entSelf->m_pVehicle, pm->ps->origin, pm->mins, pm->maxs, pm->ps->clientNum, pm->tracemask, pm->trace ); + } + + // set groundentity + PM_GroundTrace(); + if ( pm_flying == FLY_HOVER ) + {//never stick to the ground + PM_HoverTrace(); + } + + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//on ground + pm->ps->fd.forceJumpZStart = 0; + } + + if ( pm->ps->pm_type == PM_DEAD ) { + if (pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_VEHICLE && + pm_entSelf->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL) + {//vehicles don't use deadmove + } + else + { + PM_DeadMove (); + } + } + + PM_DropTimers(); + +#ifdef _TESTING_VEH_PREDICTION +#ifndef QAGAME + { + vec3_t blah; + VectorMA(pm->ps->origin, 128.0f, pm->ps->moveDir, blah); + CG_TestLine(pm->ps->origin, blah, 1, 0x0000ff, 1); + + VectorMA(pm->ps->origin, 1.0f, pm->ps->velocity, blah); + CG_TestLine(pm->ps->origin, blah, 1, 0xff0000, 1); + } +#endif +#endif + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + { //a player riding a vehicle + bgEntity_t *veh = pm_entVeh; + + if ( veh && veh->m_pVehicle && + (veh->m_pVehicle->m_pVehicleInfo->type == VH_WALKER || veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) ) + {//*sigh*, until we get forced weapon-switching working? + pm->cmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); + pm->ps->eFlags &= ~(EF_FIRING|EF_ALT_FIRING); + //pm->cmd.weapon = pm->ps->weapon; + } + } + + if (!pm->ps->m_iVehicleNum && + pm_entSelf->s.NPC_class!=CLASS_VEHICLE&& + pm_entSelf->s.NPC_class!=CLASS_RANCOR&& + pm->ps->groundEntityNum < ENTITYNUM_WORLD && + pm->ps->groundEntityNum >= MAX_CLIENTS) + { //I am a player client, not riding on a vehicle, and potentially standing on an NPC + bgEntity_t *pEnt = PM_BGEntForNum(pm->ps->groundEntityNum); + + if (pEnt && pEnt->s.eType == ET_NPC && + pEnt->s.NPC_class != CLASS_VEHICLE) //don't bounce on vehicles + { //this is actually an NPC, let's try to bounce of its head to make sure we can't just stand around on top of it. + if (pm->ps->velocity[2] < 270) + { //try forcing velocity up and also force him to jump + pm->ps->velocity[2] = 270; //seems reasonable + pm->cmd.upmove = 127; + } + } +#ifdef QAGAME + else if ( !pm->ps->zoomMode && + pm_entSelf //I exist + && pEnt->m_pVehicle )//ent has a vehicle + { + gentity_t *gEnt = (gentity_t*)pEnt; + if ( gEnt->client + && !gEnt->client->ps.m_iVehicleNum //vehicle is empty + && (gEnt->spawnflags&2) )//SUSPENDED + {//it's a vehicle, see if we should get in it + //if land on an empty, suspended vehicle, get in it + pEnt->m_pVehicle->m_pVehicleInfo->Board( pEnt->m_pVehicle, (bgEntity_t *)pm_entSelf ); + } + } +#endif + } + + if (pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_VEHICLE) + { //we are a vehicle + bgEntity_t *veh = pm_entSelf; + + assert(veh && veh->playerState && veh->m_pVehicle && veh->s.number >= MAX_CLIENTS); + + if (veh->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) + { //kind of hacky, don't want to do this for flying vehicles + veh->m_pVehicle->m_vOrientation[PITCH] = pm->ps->viewangles[PITCH]; + } + + if (!pm->ps->m_iVehicleNum) + { //no one is driving, just update and get out +#ifdef QAGAME + veh->m_pVehicle->m_pVehicleInfo->Update(veh->m_pVehicle, &pm->cmd); + veh->m_pVehicle->m_pVehicleInfo->Animate(veh->m_pVehicle); +#endif + } + else + { + bgEntity_t *self = pm_entVeh; +#ifdef QAGAME + int i = 0; +#endif + + assert(self && self->playerState && self->s.number < MAX_CLIENTS); + + if (pm->ps->pm_type == PM_DEAD && + (veh->m_pVehicle->m_ulFlags & VEH_CRASHING)) + { + veh->m_pVehicle->m_ulFlags &= ~VEH_CRASHING; + } + + if (self->playerState->m_iVehicleNum) + { //only do it if they still have a vehicle (didn't get ejected this update or something) + PM_VehicleViewAngles(self->playerState, veh, &veh->m_pVehicle->m_ucmd); + } + +#ifdef QAGAME + veh->m_pVehicle->m_pVehicleInfo->Update(veh->m_pVehicle, &veh->m_pVehicle->m_ucmd); + veh->m_pVehicle->m_pVehicleInfo->Animate(veh->m_pVehicle); + + veh->m_pVehicle->m_pVehicleInfo->UpdateRider(veh->m_pVehicle, self, &veh->m_pVehicle->m_ucmd); + //update the passengers + while (i < veh->m_pVehicle->m_iNumPassengers) + { + if (veh->m_pVehicle->m_ppPassengers[i]) + { + gentity_t *thePassenger = (gentity_t *)veh->m_pVehicle->m_ppPassengers[i]; //yes, this is, in fact, ass. + if (thePassenger->inuse && thePassenger->client) + { + veh->m_pVehicle->m_pVehicleInfo->UpdateRider(veh->m_pVehicle, veh->m_pVehicle->m_ppPassengers[i], &thePassenger->client->pers.cmd); + } + } + i++; + } +#else + if (!veh->playerState->vehBoarding )//|| veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { + if (veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { //client must explicitly call this for prediction + BG_FighterUpdate(veh->m_pVehicle, &veh->m_pVehicle->m_ucmd, pm->mins, pm->maxs, self->playerState->gravity, pm->trace); + } + + if (veh->m_pVehicle->m_iBoarding == 0) + { + vec3_t vRollAng; + + //make sure we are set as its pilot cgame side + veh->m_pVehicle->m_pPilot = self; + + // Keep track of the old orientation. + VectorCopy( veh->m_pVehicle->m_vOrientation, veh->m_pVehicle->m_vPrevOrientation ); + + veh->m_pVehicle->m_pVehicleInfo->ProcessOrientCommands(veh->m_pVehicle); + PM_SetPMViewAngle(veh->playerState, veh->m_pVehicle->m_vOrientation, &veh->m_pVehicle->m_ucmd); + veh->m_pVehicle->m_pVehicleInfo->ProcessMoveCommands(veh->m_pVehicle); + + vRollAng[YAW] = self->playerState->viewangles[YAW]; + vRollAng[PITCH] = self->playerState->viewangles[PITCH]; + vRollAng[ROLL] = veh->m_pVehicle->m_vOrientation[ROLL]; + PM_SetPMViewAngle(self->playerState, vRollAng, &pm->cmd); + + // Setup the move direction. + if ( veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + { + AngleVectors( veh->m_pVehicle->m_vOrientation, veh->playerState->moveDir, NULL, NULL ); + } + else + { + vec3_t vVehAngles; + + VectorSet(vVehAngles, 0, veh->m_pVehicle->m_vOrientation[YAW], 0); + AngleVectors( vVehAngles, veh->playerState->moveDir, NULL, NULL ); + } + } + } + /* + else + { + veh->playerState->speed = 0.0f; + PM_SetPMViewAngle(self->playerState, veh->playerState->viewangles, &veh->m_pVehicle->m_ucmd); + } + */ + else if (veh->playerState) + { + veh->playerState->speed = 0.0f; + if (veh->m_pVehicle) + { + PM_SetPMViewAngle(self->playerState, veh->m_pVehicle->m_vOrientation, &pm->cmd); + PM_SetPMViewAngle(veh->playerState, veh->m_pVehicle->m_vOrientation, &pm->cmd); + } + } +#endif + } + noAnimate = qtrue; + } + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + {//don't even run physics on a player if he's on a vehicle - he goes where the vehicle goes + } + else + { //don't even run physics on a player if he's on a vehicle - he goes where the vehicle goes + if (pm->ps->pm_type == PM_FLOAT + ||pm_flying == FLY_NORMAL) + { + PM_FlyMove (); + } + else if ( pm_flying == FLY_VEHICLE ) + { + PM_FlyVehicleMove(); + } + else + { + if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { + PM_WaterJumpMove(); + } else if ( pm->waterlevel > 1 ) { + // swimming + PM_WaterMove(); + } else if ( pml.walking ) { + // walking on ground + PM_WalkMove(); + } else { + // airborne + PM_AirMove(); + } + } + } + + if (!noAnimate) + { + PM_Animate(); + } + + // set groundentity, watertype, and waterlevel + PM_GroundTrace(); + if ( pm_flying == FLY_HOVER ) + {//never stick to the ground + PM_HoverTrace(); + } + PM_SetWaterLevel(); + if (pm->cmd.forcesel != -1 && (pm->ps->fd.forcePowersKnown & (1 << pm->cmd.forcesel))) + { + pm->ps->fd.forcePowerSelected = pm->cmd.forcesel; + } + if (pm->cmd.invensel != -1 && (pm->ps->stats[STAT_HOLDABLE_ITEMS] & (1 << pm->cmd.invensel))) + { + pm->ps->stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(pm->cmd.invensel, IT_HOLDABLE); + } + + if (pm->ps->m_iVehicleNum + /*&&pm_entSelf->s.NPC_class!=CLASS_VEHICLE*/ + && pm->ps->clientNum < MAX_CLIENTS) + {//a client riding a vehicle + if ( (pm->ps->eFlags&EF_NODRAW) ) + {//inside the vehicle, do nothing + } + else if (!PM_WeaponOkOnVehicle(pm->cmd.weapon) || !PM_WeaponOkOnVehicle(pm->ps->weapon)) + { //this weapon is not legal for the vehicle, force to our current one + if (!PM_WeaponOkOnVehicle(pm->ps->weapon)) + { //uh-oh! + int weap = PM_GetOkWeaponForVehicle(); + + if (weap != -1) + { + pm->cmd.weapon = weap; + pm->ps->weapon = weap; + } + } + else + { + pm->cmd.weapon = pm->ps->weapon; + } + } + } + + if (!pm->ps->m_iVehicleNum //not a vehicle and not riding one + || pm_entSelf->s.NPC_class==CLASS_VEHICLE //you are a vehicle NPC + || (!(pm->ps->eFlags&EF_NODRAW)&&PM_WeaponOkOnVehicle(pm->cmd.weapon)) )//you're not inside the vehicle and the weapon you're holding can be used when riding this vehicle + { //only run weapons if a valid weapon is selected + // weapons + PM_Weapon(); + } + + PM_Use(); + + if (!pm->ps->m_iVehicleNum && + (pm->ps->clientNum < MAX_CLIENTS || + !pm_entSelf || + pm_entSelf->s.NPC_class != CLASS_VEHICLE)) + { //don't do this if we're on a vehicle, or we are one + // footstep events / legs animations + PM_Footsteps(); + } + + // entering / leaving water splashes + PM_WaterEvents(); + + // snap some parts of playerstate to save network bandwidth + trap_SnapVector( pm->ps->velocity ); + + if (pm->ps->pm_type == PM_JETPACK || gPMDoSlowFall ) + { + pm->ps->gravity = savedGravity; + } + + if (//pm->ps->m_iVehicleNum && + pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_VEHICLE) + { //a vehicle with passengers + bgEntity_t *veh; + + veh = pm_entSelf; + + assert(veh->m_pVehicle); + + //this could be kind of "inefficient" because it's called after every passenger pmove too. + //Maybe instead of AttachRiders we should have each rider call attach for himself? + if (veh->m_pVehicle && veh->ghoul2) + { + veh->m_pVehicle->m_pVehicleInfo->AttachRiders( veh->m_pVehicle ); + } + } + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + && pm->ps->m_iVehicleNum) + { //riding a vehicle, see if we should do some anim overrides + PM_VehicleWeaponAnimate(); + } +} + + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void Pmove (pmove_t *pmove) { + int finalTime; + + finalTime = pmove->cmd.serverTime; + + if ( finalTime < pmove->ps->commandTime ) { + return; // should not happen + } + + if ( finalTime > pmove->ps->commandTime + 1000 ) { + pmove->ps->commandTime = finalTime - 1000; + } + + if (pmove->ps->fallingToDeath) + { + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + pmove->cmd.buttons = 0; + } + + pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<ps->commandTime != finalTime ) { + int msec; + + msec = finalTime - pmove->ps->commandTime; + + if ( pmove->pmove_fixed ) { + if ( msec > pmove->pmove_msec ) { + msec = pmove->pmove_msec; + } + } + else { + if ( msec > 66 ) { + msec = 66; + } + } + pmove->cmd.serverTime = pmove->ps->commandTime + msec; + + PmoveSingle( pmove ); + + if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) { + pmove->cmd.upmove = 20; + } + } +} + +#include "../namespace_end.h" diff --git a/codemp/game/bg_public.h b/codemp/game/bg_public.h new file mode 100644 index 0000000..88d041a --- /dev/null +++ b/codemp/game/bg_public.h @@ -0,0 +1,1673 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_public.h -- definitions shared by both the server game and client game modules + +// because games can change separately from the main system version, we need a +// second version that must match between game and cgame + +#ifndef __BG_PUBLIC_H__ +#define __BG_PUBLIC_H__ + +#include "bg_weapons.h" +#include "anims.h" +#include "bg_vehicles.h" + +//these two defs are shared now because we do clientside ent parsing +#define MAX_SPAWN_VARS 64 +#define MAX_SPAWN_VARS_CHARS 4096 + + +#define GAME_VERSION "basejk-1" + +#define STEPSIZE 18 + +#define DEFAULT_GRAVITY 800 +#define GIB_HEALTH -40 +#define ARMOR_PROTECTION 0.50 // Shields only stop 50% of armor-piercing dmg +#define ARMOR_REDUCTION_FACTOR 0.50 // Certain damage doesn't take off armor as efficiently + +#define JUMP_VELOCITY 225//270 + +#define MAX_ITEMS 256 + +#define RANK_TIED_FLAG 0x4000 + +#define ITEM_RADIUS 15 // item sizes are needed for client side pickup detection + +#define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present + +#define VOTE_TIME 30000 // 30 seconds before vote times out + +#define DEFAULT_MINS_2 -24 +#define DEFAULT_MAXS_2 40 +#define CROUCH_MAXS_2 16 +#define STANDARD_VIEWHEIGHT_OFFSET -4 + +#define MINS_Z -24 +#define DEFAULT_VIEWHEIGHT (DEFAULT_MAXS_2+STANDARD_VIEWHEIGHT_OFFSET)//26 +#define CROUCH_VIEWHEIGHT (CROUCH_MAXS_2+STANDARD_VIEWHEIGHT_OFFSET)//12 +#define DEAD_VIEWHEIGHT -16 + +#define MAX_CLIENT_SCORE_SEND 20 + +// +// config strings are a general means of communicating variable length strings +// from the server to all connected clients. +// + +// CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h +#define CS_MUSIC 2 +#define CS_MESSAGE 3 // from the map worldspawn's message field +#define CS_MOTD 4 // g_motd string for server message of the day +#define CS_WARMUP 5 // server time when the match will be restarted +#define CS_SCORES1 6 +#define CS_SCORES2 7 +#define CS_VOTE_TIME 8 +#define CS_VOTE_STRING 9 +#define CS_VOTE_YES 10 +#define CS_VOTE_NO 11 +#define CS_VOTE_CALLER 12 + +#define CS_TEAMVOTE_TIME 13 +#define CS_TEAMVOTE_STRING 15 +#define CS_TEAMVOTE_YES 17 +#define CS_TEAMVOTE_NO 19 + +#define CS_GAME_VERSION 21 +#define CS_LEVEL_START_TIME 22 // so the timer only shows the current level +#define CS_INTERMISSION 23 // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two +#define CS_FLAGSTATUS 24 // string indicating flag status in CTF +#define CS_SHADERSTATE 25 +#define CS_BOTINFO 26 + +#define CS_ITEMS 28 // string of 0's and 1's that tell which items are present + +#define CS_CLIENT_JEDIMASTER 29 // current jedi master +#define CS_CLIENT_DUELWINNER 30 // current duel round winner - needed for printing at top of scoreboard +#define CS_CLIENT_DUELISTS 31 // client numbers for both current duelists. Needed for a number of client-side things. +#define CS_CLIENT_DUELHEALTHS 32 // nmckenzie: DUEL_HEALTH. Hopefully adding this cs is safe and good? +#define CS_GLOBAL_AMBIENT_SET 33 + +#define CS_AMBIENT_SET 38 + +#define CS_SIEGE_STATE (CS_AMBIENT_SET+MAX_AMBIENT_SETS) +#define CS_SIEGE_OBJECTIVES (CS_SIEGE_STATE+1) +#define CS_SIEGE_TIMEOVERRIDE (CS_SIEGE_OBJECTIVES+1) +#define CS_SIEGE_WINTEAM (CS_SIEGE_TIMEOVERRIDE+1) +#define CS_SIEGE_ICONS (CS_SIEGE_WINTEAM+1) + +#define CS_MODELS (CS_SIEGE_ICONS+1) +#define CS_SKYBOXORG (CS_MODELS+MAX_MODELS) //rww - skybox info +#define CS_SOUNDS (CS_SKYBOXORG+1) +#define CS_ICONS (CS_SOUNDS+MAX_SOUNDS) +#define CS_PLAYERS (CS_ICONS+MAX_ICONS) +/* +Ghoul2 Insert Start +*/ +#define CS_G2BONES (CS_PLAYERS+MAX_CLIENTS) +//rww - used to be CS_CHARSKINS, but I have eliminated the need for that. +/* +Ghoul2 Insert End +*/ +#define CS_LOCATIONS (CS_G2BONES+MAX_G2BONES) +#define CS_PARTICLES (CS_LOCATIONS+MAX_LOCATIONS) +#define CS_EFFECTS (CS_PARTICLES+MAX_LOCATIONS) +#define CS_LIGHT_STYLES (CS_EFFECTS + MAX_FX) + +//rwwRMG - added: +#define CS_TERRAINS (CS_LIGHT_STYLES + (MAX_LIGHT_STYLES*3)) +#define CS_BSP_MODELS (CS_TERRAINS + MAX_TERRAINS) + +//#define CS_MAX (CS_BSP_MODELS + MAX_SUB_BSP)+1 +#define CS_MAX (CS_BSP_MODELS)+1 + +#if (CS_MAX) > MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +typedef enum { + G2_MODELPART_HEAD = 10, + G2_MODELPART_WAIST, + G2_MODELPART_LARM, + G2_MODELPART_RARM, + G2_MODELPART_RHAND, + G2_MODELPART_LLEG, + G2_MODELPART_RLEG +} g2ModelParts_t; + +#define G2_MODEL_PART 50 + +#define BG_NUM_TOGGLEABLE_SURFACES 31 + +#define MAX_CUSTOM_SIEGE_SOUNDS 30 + +#include "../namespace_begin.h" +extern const char *bg_customSiegeSoundNames[MAX_CUSTOM_SIEGE_SOUNDS]; + +extern const char *bgToggleableSurfaces[BG_NUM_TOGGLEABLE_SURFACES]; +extern const int bgToggleableSurfaceDebris[BG_NUM_TOGGLEABLE_SURFACES]; +#include "../namespace_end.h" + +typedef enum { + HANDEXTEND_NONE = 0, + HANDEXTEND_FORCEPUSH, + HANDEXTEND_FORCEPULL, + HANDEXTEND_FORCE_HOLD, + HANDEXTEND_SABERPULL, + HANDEXTEND_CHOKE, //use handextend priorities to choke someone being gripped + HANDEXTEND_WEAPONREADY, + HANDEXTEND_DODGE, + HANDEXTEND_KNOCKDOWN, + HANDEXTEND_DUELCHALLENGE, + HANDEXTEND_TAUNT, + + HANDEXTEND_PRETHROW, + HANDEXTEND_POSTTHROW, + HANDEXTEND_PRETHROWN, + HANDEXTEND_POSTTHROWN, + + HANDEXTEND_DRAGGING, + + HANDEXTEND_JEDITAUNT, +} forceHandAnims_t; + +typedef enum +{ + BROKENLIMB_NONE = 0, + BROKENLIMB_LARM, + BROKENLIMB_RARM, + NUM_BROKENLIMBS +} brokenLimb_t; + +//for supplier class items +#define TOSS_DEBOUNCE_TIME 5000 + +typedef enum { + GT_FFA, // free for all + GT_HOLOCRON, // holocron ffa + GT_JEDIMASTER, // jedi master + GT_DUEL, // one on one tournament + GT_POWERDUEL, + GT_SINGLE_PLAYER, // single player ffa + + //-- team games go after this -- + + GT_TEAM, // team deathmatch + GT_SIEGE, // siege + GT_CTF, // capture the flag + GT_CTY, + GT_MAX_GAME_TYPE +}; +typedef int gametype_t; + +typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; + +extern vec3_t WP_MuzzlePoint[WP_NUM_WEAPONS]; + +#include "../namespace_begin.h" +extern int forcePowerSorted[NUM_FORCE_POWERS]; +#include "../namespace_end.h" + +typedef enum +{ + SABERLOCK_TOP, + SABERLOCK_SIDE, + SABERLOCK_LOCK, + SABERLOCK_BREAK, + SABERLOCK_SUPERBREAK, + SABERLOCK_WIN, + SABERLOCK_LOSE +}; + +typedef enum +{ + DIR_RIGHT, + DIR_LEFT, + DIR_FRONT, + DIR_BACK +}; + +/* +=================================================================================== + +PMOVE MODULE + +The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t +and some other output data. Used for local prediction on the client game and true +movement on the server game. +=================================================================================== +*/ + + +#pragma pack(push, 1) +typedef struct animation_s { + unsigned short firstFrame; + unsigned short numFrames; + short frameLerp; // msec between frames + //initialLerp is abs(frameLerp) + signed char loopFrames; // 0 to numFrames +} animation_t; +#pragma pack(pop) + +#include "../namespace_begin.h" +extern qboolean BGPAFtextLoaded; +extern animation_t bgHumanoidAnimations[MAX_TOTALANIMATIONS]; +#include "../namespace_end.h" + +//#define MAX_ANIM_FILES 16 +#define MAX_ANIM_FILES 6 // I know that I had this number smaller once! +#define MAX_ANIM_EVENTS 300 + +typedef enum +{ + FOOTSTEP_R, + FOOTSTEP_L, + FOOTSTEP_HEAVY_R, + FOOTSTEP_HEAVY_L, + NUM_FOOTSTEP_TYPES +} footstepType_t; + +extern stringID_table_t animEventTypeTable[]; +extern stringID_table_t footstepTypeTable[NUM_FOOTSTEP_TYPES+1]; + +//size of Anim eventData array... +#define MAX_RANDOM_ANIM_SOUNDS 4 +#define AED_ARRAY_SIZE (MAX_RANDOM_ANIM_SOUNDS+3) +//indices for AEV_SOUND data +#define AED_SOUNDINDEX_START 0 +#define AED_SOUNDINDEX_END (MAX_RANDOM_ANIM_SOUNDS-1) +#define AED_SOUND_NUMRANDOMSNDS (MAX_RANDOM_ANIM_SOUNDS) +#define AED_SOUND_PROBABILITY (MAX_RANDOM_ANIM_SOUNDS+1) +//indices for AEV_SOUNDCHAN data +#define AED_SOUNDCHANNEL (MAX_RANDOM_ANIM_SOUNDS+2) +//indices for AEV_FOOTSTEP data +#define AED_FOOTSTEP_TYPE 0 +#define AED_FOOTSTEP_PROBABILITY 1 +//indices for AEV_EFFECT data +#define AED_EFFECTINDEX 0 +#define AED_BOLTINDEX 1 +#define AED_EFFECT_PROBABILITY 2 +#define AED_MODELINDEX 3 +//indices for AEV_FIRE data +#define AED_FIRE_ALT 0 +#define AED_FIRE_PROBABILITY 1 +//indices for AEV_MOVE data +#define AED_MOVE_FWD 0 +#define AED_MOVE_RT 1 +#define AED_MOVE_UP 2 + +typedef enum +{//NOTENOTE: Be sure to update animEventTypeTable and ParseAnimationEvtBlock(...) if you change this enum list! + AEV_NONE, + AEV_SOUND, //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay + AEV_FOOTSTEP, //# animID AEV_FOOTSTEP framenum footstepType chancetoplay + AEV_EFFECT, //# animID AEV_EFFECT framenum effectpath boltName chancetoplay + AEV_FIRE, //# animID AEV_FIRE framenum altfire chancetofire + AEV_MOVE, //# animID AEV_MOVE framenum forwardpush rightpush uppush + AEV_SOUNDCHAN, //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay + AEV_NUM_AEV +} animEventType_t; + +typedef struct animevent_s +{ + animEventType_t eventType; + unsigned short keyFrame; //Frame to play event on + signed short eventData[AED_ARRAY_SIZE]; //Unique IDs, can be soundIndex of sound file to play OR effect index or footstep type, etc. + char *stringData; //we allow storage of one string, temporarily (in case we have to look up an index later, then make sure to set stringData to NULL so we only do the look-up once) +} animevent_t; + +typedef struct +{ + char filename[MAX_QPATH]; + animation_t *anims; +// animsounds_t torsoAnimSnds[MAX_ANIM_SOUNDS]; +// animsounds_t legsAnimSnds[MAX_ANIM_SOUNDS]; +// qboolean soundsCached; +} bgLoadedAnim_t; + +typedef struct +{ + char filename[MAX_QPATH]; + animevent_t torsoAnimEvents[MAX_ANIM_EVENTS]; + animevent_t legsAnimEvents[MAX_ANIM_EVENTS]; + qboolean eventsParsed; +} bgLoadedEvents_t; + +#include "../namespace_begin.h" + +extern bgLoadedAnim_t bgAllAnims[MAX_ANIM_FILES]; + +//In SP this is shared in with the anim stuff, and humanoid anim sets can be loaded +//multiple times just for the sake of sounds being different. We probably wouldn't +//care normally but since we're working in VMs we have to do everything possible to +//cut memory cost. +//On the bright side this also means we're cutting a rather large size out of +//required game-side memory. +#ifndef QAGAME +extern bgLoadedEvents_t bgAllEvents[MAX_ANIM_FILES]; +extern int bgNumAnimEvents; +#endif + +#include "../namespace_end.h" + +typedef enum { + PM_NORMAL, // can accelerate and turn + PM_JETPACK, // special jetpack movement + PM_FLOAT, // float with no gravity in general direction of velocity (intended for gripping) + PM_NOCLIP, // noclip movement + PM_SPECTATOR, // still run into walls + PM_DEAD, // no acceleration or turning, but free falling + PM_FREEZE, // stuck in place with no control + PM_INTERMISSION, // no movement or status bar + PM_SPINTERMISSION // no movement or status bar +} pmtype_t; + +typedef enum { + WEAPON_READY, + WEAPON_RAISING, + WEAPON_DROPPING, + WEAPON_FIRING, + WEAPON_CHARGING, + WEAPON_CHARGING_ALT, + WEAPON_IDLE, //lowered // NOTENOTE Added with saber +} weaponstate_t; + + +typedef enum { + FORCE_MASTERY_UNINITIATED, + FORCE_MASTERY_INITIATE, + FORCE_MASTERY_PADAWAN, + FORCE_MASTERY_JEDI, + FORCE_MASTERY_JEDI_GUARDIAN, + FORCE_MASTERY_JEDI_ADEPT, + FORCE_MASTERY_JEDI_KNIGHT, + FORCE_MASTERY_JEDI_MASTER, + NUM_FORCE_MASTERY_LEVELS +}; + +#include "../namespace_begin.h" +extern char *forceMasteryLevels[NUM_FORCE_MASTERY_LEVELS]; +extern int forceMasteryPoints[NUM_FORCE_MASTERY_LEVELS]; + +extern int bgForcePowerCost[NUM_FORCE_POWERS][NUM_FORCE_POWER_LEVELS]; +#include "../namespace_end.h" + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_ROLLING 4 +#define PMF_BACKWARDS_JUMP 8 // go into backwards land +#define PMF_BACKWARDS_RUN 16 // coast down to backwards run +#define PMF_TIME_LAND 32 // pm_time is time before rejump +#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time +#define PMF_FIX_MINS 128 // mins have been brought up, keep tracing down to fix them +#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump +#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up +#define PMF_USE_ITEM_HELD 1024 +#define PMF_UPDATE_ANIM 2048 // The server updated the animation, the pmove should set the ghoul2 anim to match. +#define PMF_FOLLOW 4096 // spectate following another player +#define PMF_SCOREBOARD 8192 // spectate as a scoreboard +#define PMF_STUCK_TO_WALL 16384 // grabbing a wall + +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) + +#define MAXTOUCH 32 + +typedef struct bgEntity_s +{ + entityState_t s; + playerState_t *playerState; + Vehicle_t *m_pVehicle; //vehicle data + void *ghoul2; //g2 instance + int localAnimIndex; //index locally (game/cgame) to anim data for this skel + vec3_t modelScale; //needed for g2 collision + + //Data type(s) must directly correspond to the head of the gentity and centity structures +} bgEntity_t; + +typedef struct { + // state (in / out) + playerState_t *ps; + + //rww - shared ghoul2 stuff (not actually the same data, but hey) + void *ghoul2; + int g2Bolts_LFoot; + int g2Bolts_RFoot; + vec3_t modelScale; + + //hacky bool so we know if we're dealing with a nonhumanoid (which is probably a rockettrooper) + qboolean nonHumanoid; + + // command (in) + usercmd_t cmd; + int tracemask; // collide against these types of surfaces + int debugLevel; // if set, diagnostic output will be printed + qboolean noFootsteps; // if the game is setup for no footsteps by the server + qboolean gauntletHit; // true if a gauntlet attack would actually hit something + + int framecount; + + // results (out) + int numtouch; + int touchents[MAXTOUCH]; + + int useEvent; + + vec3_t mins, maxs; // bounding box size + + int watertype; + int waterlevel; + + int gametype; + + int debugMelee; + int stepSlideFix; + int noSpecMove; + + animation_t *animations; + + float xyspeed; + + // for fixed msec Pmove + int pmove_fixed; + int pmove_msec; + + // callbacks to test the world + // these will be different functions during game and cgame + void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); + int (*pointcontents)( const vec3_t point, int passEntityNum ); + + int checkDuelLoss; + + //rww - bg entitystate access method + bgEntity_t *baseEnt; //base address of the entity array (g_entities or cg_entities) + int entSize; //size of the struct (gentity_t or centity_t) so things can be dynamic +} pmove_t; + +#include "../namespace_begin.h" + +extern pmove_t *pm; + +#define SETANIM_TORSO 1 +#define SETANIM_LEGS 2 +#define SETANIM_BOTH SETANIM_TORSO|SETANIM_LEGS//3 + +#define SETANIM_FLAG_NORMAL 0//Only set if timer is 0 +#define SETANIM_FLAG_OVERRIDE 1//Override previous +#define SETANIM_FLAG_HOLD 2//Set the new timer +#define SETANIM_FLAG_RESTART 4//Allow restarting the anim if playing the same one (weapon fires) +#define SETANIM_FLAG_HOLDLESS 8//Set the new timer + + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); +void Pmove (pmove_t *pmove); + +#include "../namespace_end.h" + +//=================================================================================== + + +// player_state->stats[] indexes +// NOTE: may not have more than 16 +typedef enum { + STAT_HEALTH, + STAT_HOLDABLE_ITEM, + STAT_HOLDABLE_ITEMS, + STAT_PERSISTANT_POWERUP, + //MAKE SURE STAT_WEAPONS REMAINS 4!!!! + //There is a hardcoded reference in msg.cpp to send it in 32 bits -rww + STAT_WEAPONS = 4, // 16 bit fields + STAT_ARMOR, + STAT_DEAD_YAW, // look this direction when dead (FIXME: get rid of?) + STAT_CLIENTS_READY, // bit mask of clients wishing to exit the intermission (FIXME: configstring?) + STAT_MAX_HEALTH // health / armor limit, changable by handicap +} statIndex_t; + + +// player_state->persistant[] indexes +// these fields are the only part of player_state that isn't +// cleared on respawn +// NOTE: may not have more than 16 +typedef enum { + PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + PERS_HITS, // total points damage inflicted so damage beeps can sound on change + PERS_RANK, // player rank or team rank + PERS_TEAM, // player team + PERS_SPAWN_COUNT, // incremented every respawn + PERS_PLAYEREVENTS, // 16 bits that can be flipped for events + PERS_ATTACKER, // clientnum of last damage inflicter + PERS_ATTACKEE_ARMOR, // health/armor of last person we attacked + PERS_KILLED, // count of the number of times you died + // player awards tracking + PERS_IMPRESSIVE_COUNT, // two railgun hits in a row + PERS_EXCELLENT_COUNT, // two successive kills in a short amount of time + PERS_DEFEND_COUNT, // defend awards + PERS_ASSIST_COUNT, // assist awards + PERS_GAUNTLET_FRAG_COUNT, // kills with the guantlet + PERS_CAPTURES // captures +} persEnum_t; + + +// entityState_t->eFlags +#define EF_G2ANIMATING (1<<0) //perform g2 bone anims based on torsoAnim and legsAnim, works for ET_GENERAL -rww +#define EF_DEAD (1<<1) // don't draw a foe marker over players with EF_DEAD +//#define EF_BOUNCE_SHRAPNEL (1<<2) // special shrapnel flag +//do not use eflags for server-only things, it wastes bandwidth -rww +#define EF_RADAROBJECT (1<<2) // display on team radar + +#define EF_TELEPORT_BIT (1<<3) // toggled every time the origin abruptly changes + +#define EF_SHADER_ANIM (1<<4) // Animating shader (by s.frame) + +#define EF_PLAYER_EVENT (1<<5) +//#define EF_BOUNCE (1<<5) // for missiles +//#define EF_BOUNCE_HALF (1<<6) // for missiles +//these aren't even referenced in bg or client code and do not need to be eFlags, so I +//am using these flags for rag stuff -rww + +#define EF_RAG (1<<6) //ragdoll him even if he's alive + + +#define EF_PERMANENT (1<<7) // rww - I am claiming this. (for permanent entities) + +#define EF_NODRAW (1<<8) // may have an event, but no model (unspawned items) +#define EF_FIRING (1<<9) // for lightning gun +#define EF_ALT_FIRING (1<<10) // for alt-fires, mostly for lightning guns though +#define EF_JETPACK_ACTIVE (1<<11) //jetpack is activated + +#define EF_NOT_USED_1 (1<<12) // not used + +#define EF_TALK (1<<13) // draw a talk balloon +#define EF_CONNECTION (1<<14) // draw a connection trouble sprite +#define EF_NOT_USED_6 (1<<15) // not used + +#define EF_NOT_USED_2 (1<<16) // not used +#define EF_NOT_USED_3 (1<<17) // not used +#define EF_NOT_USED_4 (1<<18) // not used + +#define EF_BODYPUSH (1<<19) //rww - claiming this for fullbody push effect + +#define EF_DOUBLE_AMMO (1<<20) // Hacky way to get around ammo max +#define EF_SEEKERDRONE (1<<21) // show seeker drone floating around head +#define EF_MISSILE_STICK (1<<22) // missiles that stick to the wall. +#define EF_ITEMPLACEHOLDER (1<<23) // item effect +#define EF_SOUNDTRACKER (1<<24) // sound position needs to be updated in relation to another entity +#define EF_DROPPEDWEAPON (1<<25) // it's a dropped weapon +#define EF_DISINTEGRATION (1<<26) // being disintegrated by the disruptor +#define EF_INVULNERABLE (1<<27) // just spawned in or whatever, so is protected + +#define EF_CLIENTSMOOTH (1<<28) // standard lerporigin smooth override on client + +#define EF_JETPACK (1<<29) //rww - wearing a jetpack +#define EF_JETPACK_FLAMING (1<<30) //rww - jetpack fire effect + +#define EF_NOT_USED_5 (1<<31) // not used + +//These new EF2_??? flags were added for NPCs, they really should not be used often. +//NOTE: we only allow 10 of these! +#define EF2_HELD_BY_MONSTER (1<<0) // Being held by something, like a Rancor or a Wampa +#define EF2_USE_ALT_ANIM (1<<1) // For certain special runs/stands for creatures like the Rancor and Wampa whose runs/stands are conditional +#define EF2_ALERTED (1<<2) // For certain special anims, for Rancor: means you've had an enemy, so use the more alert stand +#define EF2_GENERIC_NPC_FLAG (1<<3) // So far, used for Rancor... +#define EF2_FLYING (1<<4) // Flying FIXME: only used on NPCs doesn't *really* have to be passed over, does it? +#define EF2_HYPERSPACE (1<<5) // Used to both start the hyperspace effect on the predicted client and to let the vehicle know it can now jump into hyperspace (after turning to face the proper angle) +#define EF2_BRACKET_ENTITY (1<<6) // Draw as bracketed +#define EF2_SHIP_DEATH (1<<7) // "died in ship" mode +#define EF2_NOT_USED_1 (1<<8) // not used + + +typedef enum { + EFFECT_NONE = 0, + EFFECT_SMOKE, + EFFECT_EXPLOSION, + EFFECT_EXPLOSION_PAS, + EFFECT_SPARK_EXPLOSION, + EFFECT_EXPLOSION_TRIPMINE, + EFFECT_EXPLOSION_DETPACK, + EFFECT_EXPLOSION_FLECHETTE, + EFFECT_STUNHIT, + EFFECT_EXPLOSION_DEMP2ALT, + EFFECT_EXPLOSION_TURRET, + EFFECT_SPARKS, + EFFECT_WATER_SPLASH, + EFFECT_ACID_SPLASH, + EFFECT_LAVA_SPLASH, + EFFECT_LANDING_MUD, + EFFECT_LANDING_SAND, + EFFECT_LANDING_DIRT, + EFFECT_LANDING_SNOW, + EFFECT_LANDING_GRAVEL, + EFFECT_MAX +} effectTypes_t; + +// NOTE: may not have more than 16 +typedef enum { + PW_NONE, + + PW_QUAD, + PW_BATTLESUIT, + PW_PULL, + //PW_INVIS, //rww - removed + //PW_REGEN, //rww - removed + //PW_FLIGHT, //rww - removed + + PW_REDFLAG, + PW_BLUEFLAG, + PW_NEUTRALFLAG, + + PW_SHIELDHIT, + + //PW_SCOUT, //rww - removed + //PW_GUARD, //rww - removed + //PW_DOUBLER, //rww - removed + //PW_AMMOREGEN, //rww - removed + PW_SPEEDBURST, + PW_DISINT_4, + PW_SPEED, + PW_CLOAKED, + PW_FORCE_ENLIGHTENED_LIGHT, + PW_FORCE_ENLIGHTENED_DARK, + PW_FORCE_BOON, + PW_YSALAMIRI, + + PW_NUM_POWERUPS + +}; +typedef int powerup_t; + +typedef enum { + HI_NONE, + + HI_SEEKER, + HI_SHIELD, + HI_MEDPAC, + HI_MEDPAC_BIG, + HI_BINOCULARS, + HI_SENTRY_GUN, + HI_JETPACK, + + HI_HEALTHDISP, + HI_AMMODISP, + HI_EWEB, + HI_CLOAK, + + HI_NUM_HOLDABLE +}; +typedef int holdable_t; + + +typedef enum { + CTFMESSAGE_FRAGGED_FLAG_CARRIER, + CTFMESSAGE_FLAG_RETURNED, + CTFMESSAGE_PLAYER_RETURNED_FLAG, + CTFMESSAGE_PLAYER_CAPTURED_FLAG, + CTFMESSAGE_PLAYER_GOT_FLAG +} ctfMsg_t; + +// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) +#define PLAYEREVENT_DENIEDREWARD 0x0001 +#define PLAYEREVENT_GAUNTLETREWARD 0x0002 + +// entityState_t->event values +// entity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. + +// two bits at the top of the entityState->event field +// will be incremented with each change in the event so +// that an identical event started twice in a row can +// be distinguished. And off the value with ~EV_EVENT_BITS +// to retrieve the actual event number +#define EV_EVENT_BIT1 0x00000100 +#define EV_EVENT_BIT2 0x00000200 +#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) + +#define EVENT_VALID_MSEC 300 + +typedef enum +{ + PDSOUND_NONE, + PDSOUND_PROTECTHIT, + PDSOUND_PROTECT, + PDSOUND_ABSORBHIT, + PDSOUND_ABSORB, + PDSOUND_FORCEJUMP, + PDSOUND_FORCEGRIP +} pdSounds_t; + +typedef enum { + EV_NONE, + + EV_CLIENTJOIN, + + EV_FOOTSTEP, + EV_FOOTSTEP_METAL, + EV_FOOTSPLASH, + EV_FOOTWADE, + EV_SWIM, + + EV_STEP_4, + EV_STEP_8, + EV_STEP_12, + EV_STEP_16, + + EV_FALL, + + EV_JUMP_PAD, // boing sound at origin, jump sound on player + + EV_GHOUL2_MARK, //create a projectile impact mark on something with a client-side g2 instance. + + EV_GLOBAL_DUEL, + EV_PRIVATE_DUEL, + + EV_JUMP, + EV_ROLL, + EV_WATER_TOUCH, // foot touches + EV_WATER_LEAVE, // foot leaves + EV_WATER_UNDER, // head touches + EV_WATER_CLEAR, // head leaves + + EV_ITEM_PICKUP, // normal item pickups are predictable + EV_GLOBAL_ITEM_PICKUP, // powerup / team sounds are broadcast to everyone + + EV_VEH_FIRE, + + EV_NOAMMO, + EV_CHANGE_WEAPON, + EV_FIRE_WEAPON, + EV_ALT_FIRE, + EV_SABER_ATTACK, + EV_SABER_HIT, + EV_SABER_BLOCK, + EV_SABER_CLASHFLARE, + EV_SABER_UNHOLSTER, + EV_BECOME_JEDIMASTER, + EV_DISRUPTOR_MAIN_SHOT, + EV_DISRUPTOR_SNIPER_SHOT, + EV_DISRUPTOR_SNIPER_MISS, + EV_DISRUPTOR_HIT, + EV_DISRUPTOR_ZOOMSOUND, + + EV_PREDEFSOUND, + + EV_TEAM_POWER, + + EV_SCREENSHAKE, + + EV_LOCALTIMER, + + EV_USE, // +Use key + + EV_USE_ITEM0, + EV_USE_ITEM1, + EV_USE_ITEM2, + EV_USE_ITEM3, + EV_USE_ITEM4, + EV_USE_ITEM5, + EV_USE_ITEM6, + EV_USE_ITEM7, + EV_USE_ITEM8, + EV_USE_ITEM9, + EV_USE_ITEM10, + EV_USE_ITEM11, + EV_USE_ITEM12, + EV_USE_ITEM13, + EV_USE_ITEM14, + EV_USE_ITEM15, + + EV_ITEMUSEFAIL, + + EV_ITEM_RESPAWN, + EV_ITEM_POP, + EV_PLAYER_TELEPORT_IN, + EV_PLAYER_TELEPORT_OUT, + + EV_GRENADE_BOUNCE, // eventParm will be the soundindex + EV_MISSILE_STICK, // eventParm will be the soundindex + + EV_PLAY_EFFECT, + EV_PLAY_EFFECT_ID, + EV_PLAY_PORTAL_EFFECT_ID, + + EV_PLAYDOORSOUND, + EV_PLAYDOORLOOPSOUND, + EV_BMODEL_SOUND, + + EV_MUTE_SOUND, + EV_VOICECMD_SOUND, + EV_GENERAL_SOUND, + EV_GLOBAL_SOUND, // no attenuation + EV_GLOBAL_TEAM_SOUND, + EV_ENTITY_SOUND, + + EV_PLAY_ROFF, + + EV_GLASS_SHATTER, + EV_DEBRIS, + EV_MISC_MODEL_EXP, + + EV_CONC_ALT_IMPACT, + + EV_MISSILE_HIT, + EV_MISSILE_MISS, + EV_MISSILE_MISS_METAL, + EV_BULLET, // otherEntity is the shooter + + EV_PAIN, + EV_DEATH1, + EV_DEATH2, + EV_DEATH3, + EV_OBITUARY, + + EV_POWERUP_QUAD, + EV_POWERUP_BATTLESUIT, + //EV_POWERUP_REGEN, + + EV_FORCE_DRAINED, + + EV_GIB_PLAYER, // gib a previously living player + EV_SCOREPLUM, // score plum + + EV_CTFMESSAGE, + + EV_BODYFADE, + + EV_SIEGE_ROUNDOVER, + EV_SIEGE_OBJECTIVECOMPLETE, + + EV_DESTROY_GHOUL2_INSTANCE, + + EV_DESTROY_WEAPON_MODEL, + + EV_GIVE_NEW_RANK, + EV_SET_FREE_SABER, + EV_SET_FORCE_DISABLE, + + EV_WEAPON_CHARGE, + EV_WEAPON_CHARGE_ALT, + + EV_SHIELD_HIT, + + EV_DEBUG_LINE, + EV_TESTLINE, + EV_STOPLOOPINGSOUND, + EV_STARTLOOPINGSOUND, + EV_TAUNT, + + //rww - Begin NPC sound events + EV_ANGER1, //Say when acquire an enemy when didn't have one before + EV_ANGER2, + EV_ANGER3, + + EV_VICTORY1, //Say when killed an enemy + EV_VICTORY2, + EV_VICTORY3, + + EV_CONFUSE1, //Say when confused + EV_CONFUSE2, + EV_CONFUSE3, + + EV_PUSHED1, //Say when pushed + EV_PUSHED2, + EV_PUSHED3, + + EV_CHOKE1, //Say when choking + EV_CHOKE2, + EV_CHOKE3, + + EV_FFWARN, //ffire founds + EV_FFTURN, + //extra sounds for ST + EV_CHASE1, + EV_CHASE2, + EV_CHASE3, + EV_COVER1, + EV_COVER2, + EV_COVER3, + EV_COVER4, + EV_COVER5, + EV_DETECTED1, + EV_DETECTED2, + EV_DETECTED3, + EV_DETECTED4, + EV_DETECTED5, + EV_LOST1, + EV_OUTFLANK1, + EV_OUTFLANK2, + EV_ESCAPING1, + EV_ESCAPING2, + EV_ESCAPING3, + EV_GIVEUP1, + EV_GIVEUP2, + EV_GIVEUP3, + EV_GIVEUP4, + EV_LOOK1, + EV_LOOK2, + EV_SIGHT1, + EV_SIGHT2, + EV_SIGHT3, + EV_SOUND1, + EV_SOUND2, + EV_SOUND3, + EV_SUSPICIOUS1, + EV_SUSPICIOUS2, + EV_SUSPICIOUS3, + EV_SUSPICIOUS4, + EV_SUSPICIOUS5, + //extra sounds for Jedi + EV_COMBAT1, + EV_COMBAT2, + EV_COMBAT3, + EV_JDETECTED1, + EV_JDETECTED2, + EV_JDETECTED3, + EV_TAUNT1, + EV_TAUNT2, + EV_TAUNT3, + EV_JCHASE1, + EV_JCHASE2, + EV_JCHASE3, + EV_JLOST1, + EV_JLOST2, + EV_JLOST3, + EV_DEFLECT1, + EV_DEFLECT2, + EV_DEFLECT3, + EV_GLOAT1, + EV_GLOAT2, + EV_GLOAT3, + EV_PUSHFAIL, + + EV_SIEGESPEC, + +} entity_event_t; // There is a maximum of 256 events (8 bits transmission, 2 high bits for uniqueness) + + +typedef enum { + GTS_RED_CAPTURE, + GTS_BLUE_CAPTURE, + GTS_RED_RETURN, + GTS_BLUE_RETURN, + GTS_RED_TAKEN, + GTS_BLUE_TAKEN, + GTS_REDTEAM_SCORED, + GTS_BLUETEAM_SCORED, + GTS_REDTEAM_TOOK_LEAD, + GTS_BLUETEAM_TOOK_LEAD, + GTS_TEAMS_ARE_TIED +} global_team_sound_t; + + + +typedef enum { + TEAM_FREE, + TEAM_RED, + TEAM_BLUE, + TEAM_SPECTATOR, + + TEAM_NUM_TEAMS +}; +typedef int team_t; + +typedef enum { + DUELTEAM_FREE, + DUELTEAM_LONE, + DUELTEAM_DOUBLE, + + DUELTEAM_SINGLE, // for regular duel matches (not power duel) +} duelTeam_t; + +// Time between location updates +#define TEAM_LOCATION_UPDATE_TIME 1000 + +// How many players on the overlay +#define TEAM_MAXOVERLAY 32 + +//team task +typedef enum { + TEAMTASK_NONE, + TEAMTASK_OFFENSE, + TEAMTASK_DEFENSE, + TEAMTASK_PATROL, + TEAMTASK_FOLLOW, + TEAMTASK_RETRIEVE, + TEAMTASK_ESCORT, + TEAMTASK_CAMP +} teamtask_t; + +// means of death +typedef enum { + MOD_UNKNOWN, + MOD_STUN_BATON, + MOD_MELEE, + MOD_SABER, + MOD_BRYAR_PISTOL, + MOD_BRYAR_PISTOL_ALT, + MOD_BLASTER, + MOD_TURBLAST, + MOD_DISRUPTOR, + MOD_DISRUPTOR_SPLASH, + MOD_DISRUPTOR_SNIPER, + MOD_BOWCASTER, + MOD_REPEATER, + MOD_REPEATER_ALT, + MOD_REPEATER_ALT_SPLASH, + MOD_DEMP2, + MOD_DEMP2_ALT, + MOD_FLECHETTE, + MOD_FLECHETTE_ALT_SPLASH, + MOD_ROCKET, + MOD_ROCKET_SPLASH, + MOD_ROCKET_HOMING, + MOD_ROCKET_HOMING_SPLASH, + MOD_THERMAL, + MOD_THERMAL_SPLASH, + MOD_TRIP_MINE_SPLASH, + MOD_TIMED_MINE_SPLASH, + MOD_DET_PACK_SPLASH, + MOD_VEHICLE, + MOD_CONC, + MOD_CONC_ALT, + MOD_FORCE_DARK, + MOD_SENTRY, + MOD_WATER, + MOD_SLIME, + MOD_LAVA, + MOD_CRUSH, + MOD_TELEFRAG, + MOD_FALLING, + MOD_SUICIDE, + MOD_TARGET_LASER, + MOD_TRIGGER_HURT, + MOD_TEAM_CHANGE, + //AURELIO: when/if you put this back in, remember to make a case for it in all the other places where + //mod's are checked. Also, it probably isn't the most elegant solution for what you want - just add + //a frag back to the player after you call the player_die (and keep a local of his pre-death score to + //make sure he actually lost points, there may be cases where you don't lose points on changing teams + //or suiciding, and so you would actually be giving him a point) -Rich + // I put it back in for now, if it becomes a problem we'll work around it later (it shouldn't though)... + MOD_MAX +} meansOfDeath_t; + + +//--------------------------------------------------------- + +// gitem_t->type +typedef enum { + IT_BAD, + IT_WEAPON, // EFX: rotate + upscale + minlight + IT_AMMO, // EFX: rotate + IT_ARMOR, // EFX: rotate + minlight + IT_HEALTH, // EFX: static external sphere + rotating internal + IT_POWERUP, // instant on, timer based + // EFX: rotate + external ring that rotates + IT_HOLDABLE, // single use, holdable item + // EFX: rotate + bob + IT_PERSISTANT_POWERUP, + IT_TEAM +}; +typedef int itemType_t; + +#define MAX_ITEM_MODELS 4 + +typedef struct gitem_s { + char *classname; // spawning name + char *pickup_sound; + char *world_model[MAX_ITEM_MODELS]; + char *view_model; + char *icon; +// char *pickup_name; // for printing on pickup + + int quantity; // for ammo how much, or duration of powerup + itemType_t giType; // IT_* flags + + int giTag; + + char *precaches; // string of all models and images this item will use + char *sounds; // string of all sounds this item will use + char *description; +} gitem_t; + +// included in both the game dll and the client +#include "../namespace_begin.h" + +extern gitem_t bg_itemlist[]; +extern int bg_numItems; + +float vectoyaw( const vec3_t vec ); + +gitem_t *BG_FindItem( const char *classname ); +gitem_t *BG_FindItemForWeapon( weapon_t weapon ); +gitem_t *BG_FindItemForPowerup( powerup_t pw ); +gitem_t *BG_FindItemForHoldable( holdable_t pw ); +#define ITEM_INDEX(x) ((x)-bg_itemlist) + +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ); + +#include "../namespace_end.h" + + +#define SABER_BLOCK_DUR 150 // number of milliseconds a block animation should take. + + + +// g_dmflags->integer flags +#define DF_NO_FALLING 8 +#define DF_FIXED_FOV 16 +#define DF_NO_FOOTSTEPS 32 + +//rwwRMG - added in CONTENTS_TERRAIN +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_TERRAIN) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY|CONTENTS_TERRAIN) +#define MASK_NPCSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BODY|CONTENTS_TERRAIN) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_TERRAIN) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_TERRAIN) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE|CONTENTS_TERRAIN) + + +// ET_FX States (stored in modelindex2) + +#define FX_STATE_OFF 0 +#define FX_STATE_ONE_SHOT 1 +#define FX_STATE_ONE_SHOT_LIMIT 10 +#define FX_STATE_CONTINUOUS 20 + +// +// entityState_t->eType +// +typedef enum { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_SPECIAL, // rww - force fields + ET_HOLOCRON, // rww - holocron icon displays + ET_MOVER, + ET_BEAM, + ET_PORTAL, + ET_SPEAKER, + ET_PUSH_TRIGGER, + ET_TELEPORT_TRIGGER, + ET_INVISIBLE, + ET_NPC, // ghoul2 player-like entity + ET_TEAM, + ET_BODY, + ET_TERRAIN, + ET_FX, + + ET_EVENTS // any of the EV_* events can be added freestanding + // by setting eType to ET_EVENTS + eventNum + // this avoids having to set eFlags and eventNum +} entityType_t; + + + + +// +// fields are needed for spawning from the entity string +// +//I moved these from g_spawn.c because the entity parsing stuff is semi-shared now -rww +#undef _GAME_SIDE + +#ifdef QAGAME +#define _GAME_SIDE +#elif defined CGAME +#define _GAME_SIDE +#endif + +#ifdef _GAME_SIDE +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_ENTITY, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_PARM1, // Special case for parms + F_PARM2, // Special case for parms + F_PARM3, // Special case for parms + F_PARM4, // Special case for parms + F_PARM5, // Special case for parms + F_PARM6, // Special case for parms + F_PARM7, // Special case for parms + F_PARM8, // Special case for parms + F_PARM9, // Special case for parms + F_PARM10, // Special case for parms + F_PARM11, // Special case for parms + F_PARM12, // Special case for parms + F_PARM13, // Special case for parms + F_PARM14, // Special case for parms + F_PARM15, // Special case for parms + F_PARM16, // Special case for parms + F_IGNORE +} fieldtype_t; + + + + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} BG_field_t; + + + +#endif + + + + +// Okay, here lies the much-dreaded Pat-created FSM movement chart... Heretic II strikes again! +// Why am I inflicting this on you? Well, it's better than hardcoded states. +// Ideally this will be replaced with an external file or more sophisticated move-picker +// once the game gets out of prototype stage. + +// rww - Moved all this to bg_public so that we can access the saberMoveData stuff on the cgame +// which is currently used for determining if a saber trail should be rendered in a given frame +#ifdef LS_NONE +#undef LS_NONE +#endif + +typedef enum { + // Invalid, or saber not armed + LS_NONE = 0, + + // General movements with saber + LS_READY, + LS_DRAW, + LS_PUTAWAY, + + // Attacks + LS_A_TL2BR,//4 + LS_A_L2R, + LS_A_BL2TR, + LS_A_BR2TL, + LS_A_R2L, + LS_A_TR2BL, + LS_A_T2B, + LS_A_BACKSTAB, + LS_A_BACK, + LS_A_BACK_CR, + LS_ROLL_STAB, + LS_A_LUNGE, + LS_A_JUMP_T__B_, + LS_A_FLIP_STAB, + LS_A_FLIP_SLASH, + LS_JUMPATTACK_DUAL, + LS_JUMPATTACK_ARIAL_LEFT, + LS_JUMPATTACK_ARIAL_RIGHT, + LS_JUMPATTACK_CART_LEFT, + LS_JUMPATTACK_CART_RIGHT, + LS_JUMPATTACK_STAFF_LEFT, + LS_JUMPATTACK_STAFF_RIGHT, + LS_BUTTERFLY_LEFT, + LS_BUTTERFLY_RIGHT, + LS_A_BACKFLIP_ATK, + LS_SPINATTACK_DUAL, + LS_SPINATTACK, + LS_LEAP_ATTACK, + LS_SWOOP_ATTACK_RIGHT, + LS_SWOOP_ATTACK_LEFT, + LS_TAUNTAUN_ATTACK_RIGHT, + LS_TAUNTAUN_ATTACK_LEFT, + LS_KICK_F, + LS_KICK_B, + LS_KICK_R, + LS_KICK_L, + LS_KICK_S, + LS_KICK_BF, + LS_KICK_RL, + LS_KICK_F_AIR, + LS_KICK_B_AIR, + LS_KICK_R_AIR, + LS_KICK_L_AIR, + LS_STABDOWN, + LS_STABDOWN_STAFF, + LS_STABDOWN_DUAL, + LS_DUAL_SPIN_PROTECT, + LS_STAFF_SOULCAL, + LS_A1_SPECIAL, + LS_A2_SPECIAL, + LS_A3_SPECIAL, + LS_UPSIDE_DOWN_ATTACK, + LS_PULL_ATTACK_STAB, + LS_PULL_ATTACK_SWING, + LS_SPINATTACK_ALORA, + LS_DUAL_FB, + LS_DUAL_LR, + LS_HILT_BASH, + + //starts + LS_S_TL2BR,//26 + LS_S_L2R, + LS_S_BL2TR,//# Start of attack chaining to SLASH LR2UL + LS_S_BR2TL,//# Start of attack chaining to SLASH LR2UL + LS_S_R2L, + LS_S_TR2BL, + LS_S_T2B, + + //returns + LS_R_TL2BR,//33 + LS_R_L2R, + LS_R_BL2TR, + LS_R_BR2TL, + LS_R_R2L, + LS_R_TR2BL, + LS_R_T2B, + + //transitions + LS_T1_BR__R,//40 + LS_T1_BR_TR, + LS_T1_BR_T_, + LS_T1_BR_TL, + LS_T1_BR__L, + LS_T1_BR_BL, + LS_T1__R_BR,//46 + LS_T1__R_TR, + LS_T1__R_T_, + LS_T1__R_TL, + LS_T1__R__L, + LS_T1__R_BL, + LS_T1_TR_BR,//52 + LS_T1_TR__R, + LS_T1_TR_T_, + LS_T1_TR_TL, + LS_T1_TR__L, + LS_T1_TR_BL, + LS_T1_T__BR,//58 + LS_T1_T___R, + LS_T1_T__TR, + LS_T1_T__TL, + LS_T1_T___L, + LS_T1_T__BL, + LS_T1_TL_BR,//64 + LS_T1_TL__R, + LS_T1_TL_TR, + LS_T1_TL_T_, + LS_T1_TL__L, + LS_T1_TL_BL, + LS_T1__L_BR,//70 + LS_T1__L__R, + LS_T1__L_TR, + LS_T1__L_T_, + LS_T1__L_TL, + LS_T1__L_BL, + LS_T1_BL_BR,//76 + LS_T1_BL__R, + LS_T1_BL_TR, + LS_T1_BL_T_, + LS_T1_BL_TL, + LS_T1_BL__L, + + //Bounces + LS_B1_BR, + LS_B1__R, + LS_B1_TR, + LS_B1_T_, + LS_B1_TL, + LS_B1__L, + LS_B1_BL, + + //Deflected attacks + LS_D1_BR, + LS_D1__R, + LS_D1_TR, + LS_D1_T_, + LS_D1_TL, + LS_D1__L, + LS_D1_BL, + LS_D1_B_, + + //Reflected attacks + LS_V1_BR, + LS_V1__R, + LS_V1_TR, + LS_V1_T_, + LS_V1_TL, + LS_V1__L, + LS_V1_BL, + LS_V1_B_, + + // Broken parries + LS_H1_T_,// + LS_H1_TR, + LS_H1_TL, + LS_H1_BR, + LS_H1_B_, + LS_H1_BL, + + // Knockaways + LS_K1_T_,// + LS_K1_TR, + LS_K1_TL, + LS_K1_BR, + LS_K1_BL, + + // Parries + LS_PARRY_UP,// + LS_PARRY_UR, + LS_PARRY_UL, + LS_PARRY_LR, + LS_PARRY_LL, + + // Projectile Reflections + LS_REFLECT_UP,// + LS_REFLECT_UR, + LS_REFLECT_UL, + LS_REFLECT_LR, + LS_REFLECT_LL, + + LS_MOVE_MAX// +}; +typedef int saberMoveName_t; + +typedef enum { + Q_BR, + Q_R, + Q_TR, + Q_T, + Q_TL, + Q_L, + Q_BL, + Q_B, + Q_NUM_QUADS +} saberQuadrant_t; + +typedef struct +{ + char *name; + int animToUse; + int startQuad; + int endQuad; + unsigned animSetFlags; + int blendTime; + int blocking; + saberMoveName_t chain_idle; // What move to call if the attack button is not pressed at the end of this anim + saberMoveName_t chain_attack; // What move to call if the attack button (and nothing else) is pressed + qboolean trailLength; +} saberMoveData_t; + +#include "../namespace_begin.h" + +extern saberMoveData_t saberMoveData[LS_MOVE_MAX]; + +bgEntity_t *PM_BGEntForNum( int num ); +qboolean BG_KnockDownable(playerState_t *ps); +qboolean BG_LegalizedForcePowers(char *powerOut, int maxRank, qboolean freeSaber, int teamForce, int gametype, int fpDisabled); + +#include "../namespace_end.h" + +#ifdef __LCC__ //can't inline it then, it is declared over in bg_misc in this case +void BG_GiveMeVectorFromMatrix(mdxaBone_t *boltMatrix, int flags, vec3_t vec); +#else +// given a boltmatrix, return in vec a normalised vector for the axis requested in flags +static ID_INLINE void BG_GiveMeVectorFromMatrix(mdxaBone_t *boltMatrix, int flags, vec3_t vec) +{ + switch (flags) + { + case ORIGIN: + vec[0] = boltMatrix->matrix[0][3]; + vec[1] = boltMatrix->matrix[1][3]; + vec[2] = boltMatrix->matrix[2][3]; + break; + case POSITIVE_Y: + vec[0] = boltMatrix->matrix[0][1]; + vec[1] = boltMatrix->matrix[1][1]; + vec[2] = boltMatrix->matrix[2][1]; + break; + case POSITIVE_X: + vec[0] = boltMatrix->matrix[0][0]; + vec[1] = boltMatrix->matrix[1][0]; + vec[2] = boltMatrix->matrix[2][0]; + break; + case POSITIVE_Z: + vec[0] = boltMatrix->matrix[0][2]; + vec[1] = boltMatrix->matrix[1][2]; + vec[2] = boltMatrix->matrix[2][2]; + break; + case NEGATIVE_Y: + vec[0] = -boltMatrix->matrix[0][1]; + vec[1] = -boltMatrix->matrix[1][1]; + vec[2] = -boltMatrix->matrix[2][1]; + break; + case NEGATIVE_X: + vec[0] = -boltMatrix->matrix[0][0]; + vec[1] = -boltMatrix->matrix[1][0]; + vec[2] = -boltMatrix->matrix[2][0]; + break; + case NEGATIVE_Z: + vec[0] = -boltMatrix->matrix[0][2]; + vec[1] = -boltMatrix->matrix[1][2]; + vec[2] = -boltMatrix->matrix[2][2]; + break; + } +} +#endif + +#include "../namespace_begin.h" + +void BG_IK_MoveArm(void *ghoul2, int lHandBolt, int time, entityState_t *ent, int basePose, vec3_t desiredPos, qboolean *ikInProgress, + vec3_t origin, vec3_t angles, vec3_t scale, int blendTime, qboolean forceHalt); + +void BG_G2PlayerAngles(void *ghoul2, int motionBolt, entityState_t *cent, int time, vec3_t cent_lerpOrigin, + vec3_t cent_lerpAngles, vec3_t legs[3], vec3_t legsAngles, qboolean *tYawing, + qboolean *tPitching, qboolean *lYawing, float *tYawAngle, float *tPitchAngle, + float *lYawAngle, int frametime, vec3_t turAngles, vec3_t modelScale, int ciLegs, + int ciTorso, int *corrTime, vec3_t lookAngles, vec3_t lastHeadAngles, int lookTime, + entityState_t *emplaced, int *crazySmoothFactor); +void BG_G2ATSTAngles(void *ghoul2, int time, vec3_t cent_lerpAngles ); + +//BG anim utility functions: + +int BG_AnimLength( int index, animNumber_t anim ); + +qboolean BG_InSpecialJump( int anim ); +qboolean BG_InSaberStandAnim( int anim ); +qboolean BG_InReboundJump( int anim ); +qboolean BG_InReboundHold( int anim ); +qboolean BG_InReboundRelease( int anim ); +qboolean BG_InBackFlip( int anim ); +qboolean BG_DirectFlippingAnim( int anim ); +qboolean BG_SaberInAttack( int move ); +qboolean BG_SaberInSpecial( int move ); +qboolean BG_KickMove( int move ); +qboolean BG_SaberInIdle( int move ); +qboolean BG_FlippingAnim( int anim ); +qboolean BG_SpinningSaberAnim( int anim ); +qboolean BG_SaberInSpecialAttack( int anim ); +qboolean BG_SaberInKata( int saberMove ); +qboolean BG_InKataAnim(int anim); +qboolean BG_KickingAnim( int anim ); +int BG_InGrappleMove(int anim); +int BG_BrokenParryForAttack( int move ); +int BG_BrokenParryForParry( int move ); +int BG_KnockawayForParry( int move ); +qboolean BG_InRoll( playerState_t *ps, int anim ); +qboolean BG_InDeathAnim( int anim ); +qboolean BG_InSaberLockOld( int anim ); +qboolean BG_InSaberLock( int anim ); + +void BG_SaberStartTransAnim( int saberAnimLevel, int anim, float *animSpeed, int broken ); + +void BG_ForcePowerDrain( playerState_t *ps, forcePowers_t forcePower, int overrideAmt ); + +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); + +void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ); + +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); + +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); + +void BG_InitAnimsets(void); +void BG_ClearAnimsets(void); +int BG_ParseAnimationFile(const char *filename, animation_t *animSet, qboolean isHumanoid); +#ifndef QAGAME +int BG_ParseAnimationEvtFile( const char *as_filename, int animFileIndex, int eventFileIndex ); +#endif + +qboolean BG_HasAnimation(int animIndex, int animation); +int BG_PickAnim( int animIndex, int minAnim, int maxAnim ); + +int BG_GetItemIndexByTag(int tag, int type); + +qboolean BG_IsItemSelectable(playerState_t *ps, int item); + +qboolean BG_HasYsalamiri(int gametype, playerState_t *ps); +qboolean BG_CanUseFPNow(int gametype, playerState_t *ps, int time, forcePowers_t power); + +void *BG_Alloc ( int size ); +void *BG_AllocUnaligned ( int size ); +void *BG_TempAlloc( int size ); +void BG_TempFree( int size ); +char *BG_StringAlloc ( const char *source ); +qboolean BG_OutOfMemory ( void ); + +void BG_BLADE_ActivateTrail ( bladeInfo_t *blade, float duration ); +void BG_BLADE_DeactivateTrail ( bladeInfo_t *blade, float duration ); +void BG_SI_Activate( saberInfo_t *saber ); +void BG_SI_Deactivate( saberInfo_t *saber ); +void BG_SI_BladeActivate( saberInfo_t *saber, int iBlade, qboolean bActive ); +qboolean BG_SI_Active(saberInfo_t *saber); +void BG_SI_SetLength( saberInfo_t *saber, float length ); +void BG_SI_SetDesiredLength(saberInfo_t *saber, float len, int bladeNum); +void BG_SI_SetLengthGradual( saberInfo_t *saber, int time ); +float BG_SI_Length(saberInfo_t *saber); +float BG_SI_LengthMax(saberInfo_t *saber); +void BG_SI_ActivateTrail ( saberInfo_t *saber, float duration ); +void BG_SI_DeactivateTrail ( saberInfo_t *saber, float duration ); +extern void BG_AttachToRancor( void *ghoul2,float rancYaw,vec3_t rancOrigin,int time,qhandle_t *modelList,vec3_t modelScale,qboolean inMouth,vec3_t out_origin,vec3_t out_angles,vec3_t out_axis[3] ); + +extern int WeaponReadyAnim[WP_NUM_WEAPONS]; +extern int WeaponAttackAnim[WP_NUM_WEAPONS]; + +extern int forcePowerDarkLight[NUM_FORCE_POWERS]; + +#include "../namespace_end.h" + +#define ARENAS_PER_TIER 4 +#define MAX_ARENAS 1024 +#define MAX_ARENAS_TEXT 8192 + +#define MAX_BOTS 1024 +#define MAX_BOTS_TEXT 8192 + +#define HYPERSPACE_TIME 4000 //For hyperspace triggers +#define HYPERSPACE_TELEPORT_FRAC 0.75f +#define HYPERSPACE_SPEED 10000.0f//was 30000 +#define HYPERSPACE_TURN_RATE 45.0f + +#endif //__BG_PUBLIC_H__ diff --git a/codemp/game/bg_saber.c b/codemp/game/bg_saber.c new file mode 100644 index 0000000..914fdf1 --- /dev/null +++ b/codemp/game/bg_saber.c @@ -0,0 +1,3690 @@ +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" +#include "w_saber.h" + +extern int fooTime( void ); + +#include "../namespace_begin.h" +extern qboolean BG_SabersOff( playerState_t *ps ); + +int PM_irand_timesync(int val1, int val2) +{ + int i; + + i = (val1-1) + (Q_random( &pm->cmd.serverTime )*(val2 - val1)) + 1; + if (i < val1) + { + i = val1; + } + if (i > val2) + { + i = val2; + } + + return i; +} + +void BG_ForcePowerDrain( playerState_t *ps, forcePowers_t forcePower, int overrideAmt ) +{ + //take away the power + int drain = overrideAmt; + + /* + if (ps->powerups[PW_FORCE_BOON]) + { + return; + } + */ + //No longer grant infinite force with boon. + + if ( !drain ) + { + drain = forcePowerNeeded[ps->fd.forcePowerLevel[forcePower]][forcePower]; + } + if ( !drain ) + { + return; + } + + if (forcePower == FP_LEVITATION) + { //special case + int jumpDrain = 0; + + if (ps->velocity[2] > 250) + { + jumpDrain = 20; + } + else if (ps->velocity[2] > 200) + { + jumpDrain = 16; + } + else if (ps->velocity[2] > 150) + { + jumpDrain = 12; + } + else if (ps->velocity[2] > 100) + { + jumpDrain = 8; + } + else if (ps->velocity[2] > 50) + { + jumpDrain = 6; + } + else if (ps->velocity[2] > 0) + { + jumpDrain = 4; + } + + if (jumpDrain) + { + if (ps->fd.forcePowerLevel[FP_LEVITATION]) + { //don't divide by 0! + jumpDrain /= ps->fd.forcePowerLevel[FP_LEVITATION]; + } + } + + ps->fd.forcePower -= jumpDrain; + if ( ps->fd.forcePower < 0 ) + { + ps->fd.forcePower = 0; + } + + return; + } + + ps->fd.forcePower -= drain; + if ( ps->fd.forcePower < 0 ) + { + ps->fd.forcePower = 0; + } +} + +qboolean BG_EnoughForcePowerForMove( int cost ) +{ + if ( pm->ps->fd.forcePower < cost ) + { + PM_AddEvent( EV_NOAMMO ); + return qfalse; + } + + return qtrue; +} + +// Silly, but I'm replacing these macros so they are shorter! +#define AFLAG_IDLE (SETANIM_FLAG_NORMAL) +#define AFLAG_ACTIVE (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD | SETANIM_FLAG_HOLDLESS) +#define AFLAG_WAIT (SETANIM_FLAG_HOLD | SETANIM_FLAG_HOLDLESS) +#define AFLAG_FINISH (SETANIM_FLAG_HOLD) + +//FIXME: add the alternate anims for each style? +saberMoveData_t saberMoveData[LS_MOVE_MAX] = {// NB:randomized + // name anim(do all styles?)startQ endQ setanimflag blend, blocking chain_idle chain_attack trailLen + {"None", BOTH_STAND1, Q_R, Q_R, AFLAG_IDLE, 350, BLK_NO, LS_NONE, LS_NONE, 0 }, // LS_NONE = 0, + + // General movements with saber + {"Ready", BOTH_STAND2, Q_R, Q_R, AFLAG_IDLE, 350, BLK_WIDE, LS_READY, LS_S_R2L, 0 }, // LS_READY, + {"Draw", BOTH_STAND1TO2, Q_R, Q_R, AFLAG_FINISH, 350, BLK_NO, LS_READY, LS_S_R2L, 0 }, // LS_DRAW, + {"Putaway", BOTH_STAND2TO1, Q_R, Q_R, AFLAG_FINISH, 350, BLK_NO, LS_READY, LS_S_R2L, 0 }, // LS_PUTAWAY, + + // Attacks + //UL2LR + {"TL2BR Att", BOTH_A1_TL_BR, Q_TL, Q_BR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_TL2BR, LS_R_TL2BR, 200 }, // LS_A_TL2BR + //SLASH LEFT + {"L2R Att", BOTH_A1__L__R, Q_L, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_L2R, LS_R_L2R, 200 }, // LS_A_L2R + //LL2UR + {"BL2TR Att", BOTH_A1_BL_TR, Q_BL, Q_TR, AFLAG_ACTIVE, 50, BLK_TIGHT, LS_R_BL2TR, LS_R_BL2TR, 200 }, // LS_A_BL2TR + //LR2UL + {"BR2TL Att", BOTH_A1_BR_TL, Q_BR, Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_BR2TL, LS_R_BR2TL, 200 }, // LS_A_BR2TL + //SLASH RIGHT + {"R2L Att", BOTH_A1__R__L, Q_R, Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_R2L, LS_R_R2L, 200 },// LS_A_R2L + //UR2LL + {"TR2BL Att", BOTH_A1_TR_BL, Q_TR, Q_BL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_TR2BL, LS_R_TR2BL, 200 }, // LS_A_TR2BL + //SLASH DOWN + {"T2B Att", BOTH_A1_T__B_, Q_T, Q_B, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_T2B, LS_R_T2B, 200 }, // LS_A_T2B + //special attacks + {"Back Stab", BOTH_A2_STABBACK1, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_BACKSTAB + {"Back Att", BOTH_ATTACK_BACK, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_BACK + {"CR Back Att", BOTH_CROUCHATTACKBACK1,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_BACK_CR + {"RollStab", BOTH_ROLL_STAB, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_ROLL_STAB + {"Lunge Att", BOTH_LUNGE2_B__T_, Q_B, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_LUNGE + {"Jump Att", BOTH_FORCELEAP2_T__B_,Q_T, Q_B, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_JUMP_T__B_ + {"Flip Stab", BOTH_JUMPFLIPSTABDOWN,Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_T___R, 200 }, // LS_A_FLIP_STAB + {"Flip Slash", BOTH_JUMPFLIPSLASHDOWN1,Q_L,Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__R_T_, 200 }, // LS_A_FLIP_SLASH + {"DualJump Atk",BOTH_JUMPATTACK6, Q_R, Q_BL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_BL_TR, 200 }, // LS_JUMPATTACK_DUAL + + {"DualJumpAtkL_A",BOTH_ARIAL_LEFT, Q_R, Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_A_TL2BR, 200 }, // LS_JUMPATTACK_ARIAL_LEFT + {"DualJumpAtkR_A",BOTH_ARIAL_RIGHT, Q_R, Q_TR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_A_TR2BL, 200 }, // LS_JUMPATTACK_ARIAL_RIGHT + + {"DualJumpAtkL_A",BOTH_CARTWHEEL_LEFT, Q_R,Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_TL_BR, 200 }, // LS_JUMPATTACK_CART_LEFT + {"DualJumpAtkR_A",BOTH_CARTWHEEL_RIGHT, Q_R,Q_TR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_TR_BL, 200 }, // LS_JUMPATTACK_CART_RIGHT + + {"DualJumpAtkLStaff", BOTH_BUTTERFLY_FL1,Q_R,Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__L__R, 200 }, // LS_JUMPATTACK_STAFF_LEFT + {"DualJumpAtkRStaff", BOTH_BUTTERFLY_FR1,Q_R,Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__R__L, 200 }, // LS_JUMPATTACK_STAFF_RIGHT + + {"ButterflyLeft", BOTH_BUTTERFLY_LEFT,Q_R,Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__L__R, 200 }, // LS_BUTTERFLY_LEFT + {"ButterflyRight", BOTH_BUTTERFLY_RIGHT,Q_R,Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__R__L, 200 }, // LS_BUTTERFLY_RIGHT + + {"BkFlip Atk", BOTH_JUMPATTACK7, Q_B, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_T___R, 200 }, // LS_A_BACKFLIP_ATK + {"DualSpinAtk", BOTH_SPINATTACK6, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SPINATTACK_DUAL + {"StfSpinAtk", BOTH_SPINATTACK7, Q_L, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SPINATTACK + {"LngLeapAtk", BOTH_FORCELONGLEAP_ATTACK,Q_R,Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_LEAP_ATTACK + {"SwoopAtkR", BOTH_VS_ATR_S, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SWOOP_ATTACK_RIGHT + {"SwoopAtkL", BOTH_VS_ATL_S, Q_L, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SWOOP_ATTACK_LEFT + {"TauntaunAtkR",BOTH_VT_ATR_S, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_TAUNTAUN_ATTACK_RIGHT + {"TauntaunAtkL",BOTH_VT_ATL_S, Q_L, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_TAUNTAUN_ATTACK_LEFT + {"StfKickFwd", BOTH_A7_KICK_F, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_F + {"StfKickBack", BOTH_A7_KICK_B, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_B + {"StfKickRight",BOTH_A7_KICK_R, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_R + {"StfKickLeft", BOTH_A7_KICK_L, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_L + {"StfKickSpin", BOTH_A7_KICK_S, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_S_R2L, 200 }, // LS_KICK_S + {"StfKickBkFwd",BOTH_A7_KICK_BF, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_S_R2L, 200 }, // LS_KICK_BF + {"StfKickSplit",BOTH_A7_KICK_RL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_S_R2L, 200 }, // LS_KICK_RL + {"StfKickFwdAir",BOTH_A7_KICK_F_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_F_AIR + {"StfKickBackAir",BOTH_A7_KICK_B_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_B_AIR + {"StfKickRightAir",BOTH_A7_KICK_R_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_R_AIR + {"StfKickLeftAir",BOTH_A7_KICK_L_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_L_AIR + {"StabDown", BOTH_STABDOWN, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_STABDOWN + {"StabDownStf", BOTH_STABDOWN_STAFF,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_STABDOWN_STAFF + {"StabDownDual",BOTH_STABDOWN_DUAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_STABDOWN_DUAL + {"dualspinprot",BOTH_A6_SABERPROTECT,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 500 }, // LS_DUAL_SPIN_PROTECT + {"StfSoulCal", BOTH_A7_SOULCAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 500 }, // LS_STAFF_SOULCAL + {"specialfast", BOTH_A1_SPECIAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 2000}, // LS_A1_SPECIAL + {"specialmed", BOTH_A2_SPECIAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 2000}, // LS_A2_SPECIAL + {"specialstr", BOTH_A3_SPECIAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 2000}, // LS_A3_SPECIAL + {"upsidedwnatk",BOTH_FLIP_ATTACK7, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200}, // LS_UPSIDE_DOWN_ATTACK + {"pullatkstab", BOTH_PULL_IMPALE_STAB,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200}, // LS_PULL_ATTACK_STAB + {"pullatkswing",BOTH_PULL_IMPALE_SWING,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200}, // LS_PULL_ATTACK_SWING + {"AloraSpinAtk",BOTH_ALORA_SPIN_SLASH,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SPINATTACK_ALORA + {"Dual FB Atk", BOTH_A6_FB, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_DUAL_FB + {"Dual LR Atk", BOTH_A6_LR, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_DUAL_LR + {"StfHiltBash", BOTH_A7_HILT, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_HILT_BASH + + //starts + {"TL2BR St", BOTH_S1_S1_TL, Q_R, Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_TL2BR, LS_A_TL2BR, 200 }, // LS_S_TL2BR + {"L2R St", BOTH_S1_S1__L, Q_R, Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_L2R, LS_A_L2R, 200 }, // LS_S_L2R + {"BL2TR St", BOTH_S1_S1_BL, Q_R, Q_BL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_BL2TR, LS_A_BL2TR, 200 }, // LS_S_BL2TR + {"BR2TL St", BOTH_S1_S1_BR, Q_R, Q_BR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_BR2TL, LS_A_BR2TL, 200 }, // LS_S_BR2TL + {"R2L St", BOTH_S1_S1__R, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_R2L, LS_A_R2L, 200 }, // LS_S_R2L + {"TR2BL St", BOTH_S1_S1_TR, Q_R, Q_TR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_TR2BL, LS_A_TR2BL, 200 }, // LS_S_TR2BL + {"T2B St", BOTH_S1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_T2B, LS_A_T2B, 200 }, // LS_S_T2B + + //returns + {"TL2BR Ret", BOTH_R1_BR_S1, Q_BR, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_TL2BR + {"L2R Ret", BOTH_R1__R_S1, Q_R, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_L2R + {"BL2TR Ret", BOTH_R1_TR_S1, Q_TR, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_BL2TR + {"BR2TL Ret", BOTH_R1_TL_S1, Q_TL, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_BR2TL + {"R2L Ret", BOTH_R1__L_S1, Q_L, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_R2L + {"TR2BL Ret", BOTH_R1_BL_S1, Q_BL, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_TR2BL + {"T2B Ret", BOTH_R1_B__S1, Q_B, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_T2B + + //Transitions + {"BR2R Trans", BOTH_T1_BR__R, Q_BR, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc bottom right to right + {"BR2TR Trans", BOTH_T1_BR_TR, Q_BR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc bottom right to top right (use: BOTH_T1_TR_BR) + {"BR2T Trans", BOTH_T1_BR_T_, Q_BR, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc bottom right to top (use: BOTH_T1_T__BR) + {"BR2TL Trans", BOTH_T1_BR_TL, Q_BR, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast weak spin bottom right to top left + {"BR2L Trans", BOTH_T1_BR__L, Q_BR, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast weak spin bottom right to left + {"BR2BL Trans", BOTH_T1_BR_BL, Q_BR, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast weak spin bottom right to bottom left + {"R2BR Trans", BOTH_T1__R_BR, Q_R, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast arc right to bottom right (use: BOTH_T1_BR__R) + {"R2TR Trans", BOTH_T1__R_TR, Q_R, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc right to top right + {"R2T Trans", BOTH_T1__R_T_, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast ar right to top (use: BOTH_T1_T___R) + {"R2TL Trans", BOTH_T1__R_TL, Q_R, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc right to top left + {"R2L Trans", BOTH_T1__R__L, Q_R, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast weak spin right to left + {"R2BL Trans", BOTH_T1__R_BL, Q_R, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast weak spin right to bottom left + {"TR2BR Trans", BOTH_T1_TR_BR, Q_TR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast arc top right to bottom right + {"TR2R Trans", BOTH_T1_TR__R, Q_TR, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc top right to right (use: BOTH_T1__R_TR) + {"TR2T Trans", BOTH_T1_TR_T_, Q_TR, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc top right to top (use: BOTH_T1_T__TR) + {"TR2TL Trans", BOTH_T1_TR_TL, Q_TR, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc top right to top left + {"TR2L Trans", BOTH_T1_TR__L, Q_TR, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc top right to left + {"TR2BL Trans", BOTH_T1_TR_BL, Q_TR, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast weak spin top right to bottom left + {"T2BR Trans", BOTH_T1_T__BR, Q_T, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast arc top to bottom right + {"T2R Trans", BOTH_T1_T___R, Q_T, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc top to right + {"T2TR Trans", BOTH_T1_T__TR, Q_T, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc top to top right + {"T2TL Trans", BOTH_T1_T__TL, Q_T, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc top to top left + {"T2L Trans", BOTH_T1_T___L, Q_T, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc top to left + {"T2BL Trans", BOTH_T1_T__BL, Q_T, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast arc top to bottom left + {"TL2BR Trans", BOTH_T1_TL_BR, Q_TL, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast weak spin top left to bottom right + {"TL2R Trans", BOTH_T1_TL__R, Q_TL, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc top left to right (use: BOTH_T1__R_TL) + {"TL2TR Trans", BOTH_T1_TL_TR, Q_TL, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc top left to top right (use: BOTH_T1_TR_TL) + {"TL2T Trans", BOTH_T1_TL_T_, Q_TL, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc top left to top (use: BOTH_T1_T__TL) + {"TL2L Trans", BOTH_T1_TL__L, Q_TL, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc top left to left (use: BOTH_T1__L_TL) + {"TL2BL Trans", BOTH_T1_TL_BL, Q_TL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast arc top left to bottom left + {"L2BR Trans", BOTH_T1__L_BR, Q_L, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast weak spin left to bottom right + {"L2R Trans", BOTH_T1__L__R, Q_L, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast weak spin left to right + {"L2TR Trans", BOTH_T1__L_TR, Q_L, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc left to top right (use: BOTH_T1_TR__L) + {"L2T Trans", BOTH_T1__L_T_, Q_L, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc left to top (use: BOTH_T1_T___L) + {"L2TL Trans", BOTH_T1__L_TL, Q_L, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc left to top left + {"L2BL Trans", BOTH_T1__L_BL, Q_L, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast arc left to bottom left (use: BOTH_T1_BL__L) + {"BL2BR Trans", BOTH_T1_BL_BR, Q_BL, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast weak spin bottom left to bottom right + {"BL2R Trans", BOTH_T1_BL__R, Q_BL, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast weak spin bottom left to right + {"BL2TR Trans", BOTH_T1_BL_TR, Q_BL, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast weak spin bottom left to top right + {"BL2T Trans", BOTH_T1_BL_T_, Q_BL, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc bottom left to top (use: BOTH_T1_T__BL) + {"BL2TL Trans", BOTH_T1_BL_TL, Q_BL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc bottom left to top left (use: BOTH_T1_TL_BL) + {"BL2L Trans", BOTH_T1_BL__L, Q_BL, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc bottom left to left + + //Bounces + {"Bounce BR", BOTH_B1_BR___, Q_BR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_T1_BR_TR, 150 }, + {"Bounce R", BOTH_B1__R___, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_T1__R__L, 150 }, + {"Bounce TR", BOTH_B1_TR___, Q_TR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_TR_TL, 150 }, + {"Bounce T", BOTH_B1_T____, Q_T, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_T__BL, 150 }, + {"Bounce TL", BOTH_B1_TL___, Q_TL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_T1_TL_TR, 150 }, + {"Bounce L", BOTH_B1__L___, Q_L, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_T1__L__R, 150 }, + {"Bounce BL", BOTH_B1_BL___, Q_BL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_T1_BL_TR, 150 }, + + //Deflected attacks (like bounces, but slide off enemy saber, not straight back) + {"Deflect BR", BOTH_D1_BR___, Q_BR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_T1_BR_TR, 150 }, + {"Deflect R", BOTH_D1__R___, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_T1__R__L, 150 }, + {"Deflect TR", BOTH_D1_TR___, Q_TR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_TR_TL, 150 }, + {"Deflect T", BOTH_B1_T____, Q_T, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_T__BL, 150 }, + {"Deflect TL", BOTH_D1_TL___, Q_TL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_T1_TL_TR, 150 }, + {"Deflect L", BOTH_D1__L___, Q_L, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_T1__L__R, 150 }, + {"Deflect BL", BOTH_D1_BL___, Q_BL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_T1_BL_TR, 150 }, + {"Deflect B", BOTH_D1_B____, Q_B, Q_B, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_T__BL, 150 }, + + //Reflected attacks + {"Reflected BR",BOTH_V1_BR_S1, Q_BR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_BR + {"Reflected R", BOTH_V1__R_S1, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1__R + {"Reflected TR",BOTH_V1_TR_S1, Q_TR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_TR + {"Reflected T", BOTH_V1_T__S1, Q_T, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_T_ + {"Reflected TL",BOTH_V1_TL_S1, Q_TL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_TL + {"Reflected L", BOTH_V1__L_S1, Q_L, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1__L + {"Reflected BL",BOTH_V1_BL_S1, Q_BL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_BL + {"Reflected B", BOTH_V1_B__S1, Q_B, Q_B, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_B_ + + // Broken parries + {"BParry Top", BOTH_H1_S1_T_, Q_T, Q_B, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_UP, + {"BParry UR", BOTH_H1_S1_TR, Q_TR, Q_BL, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_UR, + {"BParry UL", BOTH_H1_S1_TL, Q_TL, Q_BR, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_UL, + {"BParry LR", BOTH_H1_S1_BL, Q_BL, Q_TR, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_LR, + {"BParry Bot", BOTH_H1_S1_B_, Q_B, Q_T, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_LL + {"BParry LL", BOTH_H1_S1_BR, Q_BR, Q_TL, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_LL + + // Knockaways + {"Knock Top", BOTH_K1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_T1_T__BR, 150 }, // LS_PARRY_UP, + {"Knock UR", BOTH_K1_S1_TR, Q_R, Q_TR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_T1_TR__R, 150 }, // LS_PARRY_UR, + {"Knock UL", BOTH_K1_S1_TL, Q_R, Q_TL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BR2TL, LS_T1_TL__L, 150 }, // LS_PARRY_UL, + {"Knock LR", BOTH_K1_S1_BL, Q_R, Q_BL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TL2BR, LS_T1_BL_TL, 150 }, // LS_PARRY_LR, + {"Knock LL", BOTH_K1_S1_BR, Q_R, Q_BR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TR2BL, LS_T1_BR_TR, 150 }, // LS_PARRY_LL + + // Parry + {"Parry Top", BOTH_P1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_T2B, 150 }, // LS_PARRY_UP, + {"Parry UR", BOTH_P1_S1_TR, Q_R, Q_TL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_TR2BL, 150 }, // LS_PARRY_UR, + {"Parry UL", BOTH_P1_S1_TL, Q_R, Q_TR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BR2TL, LS_A_TL2BR, 150 }, // LS_PARRY_UL, + {"Parry LR", BOTH_P1_S1_BL, Q_R, Q_BR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TL2BR, LS_A_BR2TL, 150 }, // LS_PARRY_LR, + {"Parry LL", BOTH_P1_S1_BR, Q_R, Q_BL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TR2BL, LS_A_BL2TR, 150 }, // LS_PARRY_LL + + // Reflecting a missile + {"Reflect Top", BOTH_P1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_T2B, 300 }, // LS_PARRY_UP, + {"Reflect UR", BOTH_P1_S1_TL, Q_R, Q_TR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BR2TL, LS_A_TL2BR, 300 }, // LS_PARRY_UR, + {"Reflect UL", BOTH_P1_S1_TR, Q_R, Q_TL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_TR2BL, 300 }, // LS_PARRY_UL, + {"Reflect LR", BOTH_P1_S1_BR, Q_R, Q_BL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TR2BL, LS_A_BL2TR, 300 }, // LS_PARRY_LR + {"Reflect LL", BOTH_P1_S1_BL, Q_R, Q_BR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TL2BR, LS_A_BR2TL, 300 }, // LS_PARRY_LL, +}; + + +int transitionMove[Q_NUM_QUADS][Q_NUM_QUADS] = +{ + LS_NONE, //Can't transition to same pos! + LS_T1_BR__R,//40 + LS_T1_BR_TR, + LS_T1_BR_T_, + LS_T1_BR_TL, + LS_T1_BR__L, + LS_T1_BR_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1__R_BR,//46 + LS_NONE, //Can't transition to same pos! + LS_T1__R_TR, + LS_T1__R_T_, + LS_T1__R_TL, + LS_T1__R__L, + LS_T1__R_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_TR_BR,//52 + LS_T1_TR__R, + LS_NONE, //Can't transition to same pos! + LS_T1_TR_T_, + LS_T1_TR_TL, + LS_T1_TR__L, + LS_T1_TR_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_T__BR,//58 + LS_T1_T___R, + LS_T1_T__TR, + LS_NONE, //Can't transition to same pos! + LS_T1_T__TL, + LS_T1_T___L, + LS_T1_T__BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_TL_BR,//64 + LS_T1_TL__R, + LS_T1_TL_TR, + LS_T1_TL_T_, + LS_NONE, //Can't transition to same pos! + LS_T1_TL__L, + LS_T1_TL_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1__L_BR,//70 + LS_T1__L__R, + LS_T1__L_TR, + LS_T1__L_T_, + LS_T1__L_TL, + LS_NONE, //Can't transition to same pos! + LS_T1__L_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_BL_BR,//76 + LS_T1_BL__R, + LS_T1_BL_TR, + LS_T1_BL_T_, + LS_T1_BL_TL, + LS_T1_BL__L, + LS_NONE, //Can't transition to same pos! + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_BL_BR,//NOTE: there are no transitions from bottom, so re-use the bottom right transitions + LS_T1_BR__R, + LS_T1_BR_TR, + LS_T1_BR_T_, + LS_T1_BR_TL, + LS_T1_BR__L, + LS_T1_BR_BL, + LS_NONE //No transitions to bottom, and no anims start there, so shouldn't need any +}; + +saberMoveName_t PM_AttackMoveForQuad( int quad ) +{ + switch ( quad ) + { + case Q_B: + case Q_BR: + return LS_A_BR2TL; + break; + case Q_R: + return LS_A_R2L; + break; + case Q_TR: + return LS_A_TR2BL; + break; + case Q_T: + return LS_A_T2B; + break; + case Q_TL: + return LS_A_TL2BR; + break; + case Q_L: + return LS_A_L2R; + break; + case Q_BL: + return LS_A_BL2TR; + break; + } + return LS_NONE; +} + +qboolean PM_SaberKataDone(int curmove, int newmove); + +int PM_SaberAnimTransitionAnim( int curmove, int newmove ) +{ + int retmove = newmove; + if ( curmove == LS_READY ) + {//just standing there + switch ( newmove ) + { + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + //transition is the start + retmove = LS_S_TL2BR + (newmove-LS_A_TL2BR); + break; + } + } + else + { + switch ( newmove ) + { + //transitioning to ready pose + case LS_READY: + switch ( curmove ) + { + //transitioning from an attack + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + //transition is the return + retmove = LS_R_TL2BR + (newmove-LS_A_TL2BR); + break; + } + break; + //transitioning to an attack + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + if ( newmove == curmove ) + { + //going into an attack + if ( PM_SaberKataDone( curmove, newmove ) ) + {//done with this kata, must return to ready before attack again + retmove = LS_R_TL2BR + (newmove-LS_A_TL2BR); + } + else + {//okay to chain to another attack + retmove = transitionMove[saberMoveData[curmove].endQuad][saberMoveData[newmove].startQuad]; + } + } + else if ( saberMoveData[curmove].endQuad == saberMoveData[newmove].startQuad ) + {//new move starts from same quadrant + retmove = newmove; + } + else + { + switch ( curmove ) + { + //transitioning from an attack + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + case LS_D1_BR: + case LS_D1__R: + case LS_D1_TR: + case LS_D1_T_: + case LS_D1_TL: + case LS_D1__L: + case LS_D1_BL: + case LS_D1_B_: + retmove = transitionMove[saberMoveData[curmove].endQuad][saberMoveData[newmove].startQuad]; + break; + //transitioning from a return + case LS_R_TL2BR: + case LS_R_L2R: + case LS_R_BL2TR: + case LS_R_BR2TL: + case LS_R_R2L: + case LS_R_TR2BL: + case LS_R_T2B: + //transitioning from a bounce + /* + case LS_BOUNCE_UL2LL: + case LS_BOUNCE_LL2UL: + case LS_BOUNCE_L2LL: + case LS_BOUNCE_L2UL: + case LS_BOUNCE_UR2LR: + case LS_BOUNCE_LR2UR: + case LS_BOUNCE_R2LR: + case LS_BOUNCE_R2UR: + case LS_BOUNCE_TOP: + case LS_OVER_UR2UL: + case LS_OVER_UL2UR: + case LS_BOUNCE_UR: + case LS_BOUNCE_UL: + case LS_BOUNCE_LR: + case LS_BOUNCE_LL: + */ + //transitioning from a parry/reflection/knockaway/broken parry + case LS_PARRY_UP: + case LS_PARRY_UR: + case LS_PARRY_UL: + case LS_PARRY_LR: + case LS_PARRY_LL: + case LS_REFLECT_UP: + case LS_REFLECT_UR: + case LS_REFLECT_UL: + case LS_REFLECT_LR: + case LS_REFLECT_LL: + case LS_K1_T_: + case LS_K1_TR: + case LS_K1_TL: + case LS_K1_BR: + case LS_K1_BL: + case LS_V1_BR: + case LS_V1__R: + case LS_V1_TR: + case LS_V1_T_: + case LS_V1_TL: + case LS_V1__L: + case LS_V1_BL: + case LS_V1_B_: + case LS_H1_T_: + case LS_H1_TR: + case LS_H1_TL: + case LS_H1_BR: + case LS_H1_BL: + retmove = transitionMove[saberMoveData[curmove].endQuad][saberMoveData[newmove].startQuad]; + break; + //NB: transitioning from transitions is fine + } + } + break; + //transitioning to any other anim is not supported + } + } + + if ( retmove == LS_NONE ) + { + return newmove; + } + + return retmove; +} + +extern qboolean BG_InKnockDown( int anim ); +saberMoveName_t PM_CheckStabDown( void ) +{ + vec3_t faceFwd, facingAngles; + vec3_t fwd; + bgEntity_t *ent = NULL; + trace_t tr; + //yeah, vm's may complain, but.. who cares! + vec3_t trmins = {-15, -15, -15}; + vec3_t trmaxs = {15, 15, 15}; + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//sorry must be on ground! + return LS_NONE; + } + if ( pm->ps->clientNum < MAX_CLIENTS ) + {//player + pm->ps->velocity[2] = 0; + pm->cmd.upmove = 0; + } + + VectorSet(facingAngles, 0, pm->ps->viewangles[YAW], 0); + AngleVectors( facingAngles, faceFwd, NULL, NULL ); + + //FIXME: need to only move forward until we bump into our target...? + VectorMA(pm->ps->origin, 164.0f, faceFwd, fwd); + + pm->trace(&tr, pm->ps->origin, trmins, trmaxs, fwd, pm->ps->clientNum, MASK_PLAYERSOLID); + + if (tr.entityNum < ENTITYNUM_WORLD) + { + ent = PM_BGEntForNum(tr.entityNum); + } + + if ( ent && + (ent->s.eType == ET_PLAYER || ent->s.eType == ET_NPC) && + BG_InKnockDown( ent->s.legsAnim ) ) + {//guy is on the ground below me, do a top-down attack + if ( pm->ps->fd.saberAnimLevel == SS_DUAL ) + { + return LS_STABDOWN_DUAL; + } + else if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + return LS_STABDOWN_STAFF; + } + else + { + return LS_STABDOWN; + } + } + return LS_NONE; +} + +int PM_SaberMoveQuadrantForMovement( usercmd_t *ucmd ) +{ + if ( ucmd->rightmove > 0 ) + {//moving right + if ( ucmd->forwardmove > 0 ) + {//forward right = TL2BR slash + return Q_TL; + } + else if ( ucmd->forwardmove < 0 ) + {//backward right = BL2TR uppercut + return Q_BL; + } + else + {//just right is a left slice + return Q_L; + } + } + else if ( ucmd->rightmove < 0 ) + {//moving left + if ( ucmd->forwardmove > 0 ) + {//forward left = TR2BL slash + return Q_TR; + } + else if ( ucmd->forwardmove < 0 ) + {//backward left = BR2TL uppercut + return Q_BR; + } + else + {//just left is a right slice + return Q_R; + } + } + else + {//not moving left or right + if ( ucmd->forwardmove > 0 ) + {//forward= T2B slash + return Q_T; + } + else if ( ucmd->forwardmove < 0 ) + {//backward= T2B slash //or B2T uppercut? + return Q_T; + } + else + {//Not moving at all + return Q_R; + } + } +} + +//=================================================================== +qboolean PM_SaberInBounce( int move ) +{ + if ( move >= LS_B1_BR && move <= LS_B1_BL ) + { + return qtrue; + } + if ( move >= LS_D1_BR && move <= LS_D1_BL ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInTransition( int move ); + +int saberMoveTransitionAngle[Q_NUM_QUADS][Q_NUM_QUADS] = +{ + 0,//Q_BR,Q_BR, + 45,//Q_BR,Q_R, + 90,//Q_BR,Q_TR, + 135,//Q_BR,Q_T, + 180,//Q_BR,Q_TL, + 215,//Q_BR,Q_L, + 270,//Q_BR,Q_BL, + 45,//Q_BR,Q_B, + 45,//Q_R,Q_BR, + 0,//Q_R,Q_R, + 45,//Q_R,Q_TR, + 90,//Q_R,Q_T, + 135,//Q_R,Q_TL, + 180,//Q_R,Q_L, + 215,//Q_R,Q_BL, + 90,//Q_R,Q_B, + 90,//Q_TR,Q_BR, + 45,//Q_TR,Q_R, + 0,//Q_TR,Q_TR, + 45,//Q_TR,Q_T, + 90,//Q_TR,Q_TL, + 135,//Q_TR,Q_L, + 180,//Q_TR,Q_BL, + 135,//Q_TR,Q_B, + 135,//Q_T,Q_BR, + 90,//Q_T,Q_R, + 45,//Q_T,Q_TR, + 0,//Q_T,Q_T, + 45,//Q_T,Q_TL, + 90,//Q_T,Q_L, + 135,//Q_T,Q_BL, + 180,//Q_T,Q_B, + 180,//Q_TL,Q_BR, + 135,//Q_TL,Q_R, + 90,//Q_TL,Q_TR, + 45,//Q_TL,Q_T, + 0,//Q_TL,Q_TL, + 45,//Q_TL,Q_L, + 90,//Q_TL,Q_BL, + 135,//Q_TL,Q_B, + 215,//Q_L,Q_BR, + 180,//Q_L,Q_R, + 135,//Q_L,Q_TR, + 90,//Q_L,Q_T, + 45,//Q_L,Q_TL, + 0,//Q_L,Q_L, + 45,//Q_L,Q_BL, + 90,//Q_L,Q_B, + 270,//Q_BL,Q_BR, + 215,//Q_BL,Q_R, + 180,//Q_BL,Q_TR, + 135,//Q_BL,Q_T, + 90,//Q_BL,Q_TL, + 45,//Q_BL,Q_L, + 0,//Q_BL,Q_BL, + 45,//Q_BL,Q_B, + 45,//Q_B,Q_BR, + 90,//Q_B,Q_R, + 135,//Q_B,Q_TR, + 180,//Q_B,Q_T, + 135,//Q_B,Q_TL, + 90,//Q_B,Q_L, + 45,//Q_B,Q_BL, + 0//Q_B,Q_B, +}; + +int PM_SaberAttackChainAngle( int move1, int move2 ) +{ + if ( move1 == -1 || move2 == -1 ) + { + return -1; + } + return saberMoveTransitionAngle[saberMoveData[move1].endQuad][saberMoveData[move2].startQuad]; +} + +qboolean PM_SaberKataDone(int curmove, int newmove) +{ + if (pm->ps->m_iVehicleNum) + { //never continue kata on vehicle + if (pm->ps->saberAttackChainCount > 0) + { + return qtrue; + } + } + + if ( pm->ps->fd.saberAnimLevel == SS_DESANN || pm->ps->fd.saberAnimLevel == SS_TAVION ) + {//desann and tavion can link up as many attacks as they want + return qfalse; + } + + if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + //TEMP: for now, let staff attacks infinitely chain + return qfalse; + } + else if ( pm->ps->fd.saberAnimLevel == SS_DUAL ) + { + //TEMP: for now, let staff attacks infinitely chain + return qfalse; + } + else if ( pm->ps->fd.saberAnimLevel == FORCE_LEVEL_3 ) + { + if ( curmove == LS_NONE || newmove == LS_NONE ) + { + if ( pm->ps->fd.saberAnimLevel >= FORCE_LEVEL_3 && pm->ps->saberAttackChainCount > PM_irand_timesync( 0, 1 ) ) + { + return qtrue; + } + } + else if ( pm->ps->saberAttackChainCount > PM_irand_timesync( 2, 3 ) ) + { + return qtrue; + } + else if ( pm->ps->saberAttackChainCount > 0 ) + { + int chainAngle = PM_SaberAttackChainAngle( curmove, newmove ); + if ( chainAngle < 135 || chainAngle > 215 ) + {//if trying to chain to a move that doesn't continue the momentum + return qtrue; + } + else if ( chainAngle == 180 ) + {//continues the momentum perfectly, allow it to chain 66% of the time + if ( pm->ps->saberAttackChainCount > 1 ) + { + return qtrue; + } + } + else + {//would continue the movement somewhat, 50% chance of continuing + if ( pm->ps->saberAttackChainCount > 2 ) + { + return qtrue; + } + } + } + } + else + {//Perhaps have chainAngle influence fast and medium chains as well? For now, just do level 3. + if (newmove == LS_A_TL2BR || + newmove == LS_A_L2R || + newmove == LS_A_BL2TR || + newmove == LS_A_BR2TL || + newmove == LS_A_R2L || + newmove == LS_A_TR2BL ) + { //lower chaining tolerance for spinning saber anims + int chainTolerance; + + if (pm->ps->fd.saberAnimLevel == FORCE_LEVEL_1) + { + chainTolerance = 5; + } + else + { + chainTolerance = 3; + } + + if (pm->ps->saberAttackChainCount >= chainTolerance && PM_irand_timesync(1, pm->ps->saberAttackChainCount) > chainTolerance) + { + return qtrue; + } + } + if ( pm->ps->fd.saberAnimLevel == FORCE_LEVEL_2 && pm->ps->saberAttackChainCount > PM_irand_timesync( 2, 5 ) ) + { + return qtrue; + } + } + return qfalse; +} + +void PM_SetAnimFrame( playerState_t *gent, int frame, qboolean torso, qboolean legs ) +{ + gent->saberLockFrame = frame; +} + +int PM_SaberLockWinAnim( qboolean victory, qboolean superBreak ) +{ + int winAnim = -1; + switch ( pm->ps->torsoAnim ) + { +/* + default: +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR-PM_SaberLockBreak: %s not in saberlock anim, anim = (%d)%s\n", pm->gent->NPC_type, pm->ps->torsoAnim, animTable[pm->ps->torsoAnim].name ); +#endif +*/ + case BOTH_BF2LOCK: + if ( superBreak ) + { + winAnim = BOTH_LK_S_S_T_SB_1_W; + } + else if ( !victory ) + { + winAnim = BOTH_BF1BREAK; + } + else + { + pm->ps->saberMove = LS_A_T2B; + winAnim = BOTH_A3_T__B_; + } + break; + case BOTH_BF1LOCK: + if ( superBreak ) + { + winAnim = BOTH_LK_S_S_T_SB_1_W; + } + else if ( !victory ) + { + winAnim = BOTH_KNOCKDOWN4; + } + else + { + pm->ps->saberMove = LS_K1_T_; + winAnim = BOTH_K1_S1_T_; + } + break; + case BOTH_CWCIRCLELOCK: + if ( superBreak ) + { + winAnim = BOTH_LK_S_S_S_SB_1_W; + } + else if ( !victory ) + { + pm->ps->saberMove = LS_V1_BL;//pm->ps->saberBounceMove = + pm->ps->saberBlocked = BLOCKED_PARRY_BROKEN; + winAnim = BOTH_V1_BL_S1; + } + else + { + winAnim = BOTH_CWCIRCLEBREAK; + } + break; + case BOTH_CCWCIRCLELOCK: + if ( superBreak ) + { + winAnim = BOTH_LK_S_S_S_SB_1_W; + } + else if ( !victory ) + { + pm->ps->saberMove = LS_V1_BR;//pm->ps->saberBounceMove = + pm->ps->saberBlocked = BLOCKED_PARRY_BROKEN; + winAnim = BOTH_V1_BR_S1; + } + else + { + winAnim = BOTH_CCWCIRCLEBREAK; + } + break; + default: + //must be using new system: + break; + } + if ( winAnim != -1 ) + { + PM_SetAnim( SETANIM_BOTH, winAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + pm->ps->weaponTime = pm->ps->torsoTimer; + pm->ps->saberBlocked = BLOCKED_NONE; + pm->ps->weaponstate = WEAPON_FIRING; + /* + if ( superBreak + && winAnim != BOTH_LK_ST_DL_T_SB_1_W ) + {//going to attack with saber, do a saber trail + pm->ps->SaberActivateTrail( 200 ); + } + */ + } + return winAnim; +} + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ + +// Need to avoid nesting namespaces! +#include "../namespace_end.h" + +#include "g_local.h" +extern void NPC_SetAnim(gentity_t *ent, int setAnimParts, int anim, int setAnimFlags); +//extern gentity_t g_entities[]; +extern gentity_t *g_entities; + +#include "../namespace_begin.h" + +#endif +int PM_SaberLockLoseAnim( playerState_t *genemy, qboolean victory, qboolean superBreak ) +{ + int loseAnim = -1; + switch ( genemy->torsoAnim ) + { +/* + default: +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR-PM_SaberLockBreak: %s not in saberlock anim, anim = (%d)%s\n", genemy->NPC_type, genemy->client->ps.torsoAnim, animTable[genemy->client->ps.torsoAnim].name ); +#endif +*/ + case BOTH_BF2LOCK: + if ( superBreak ) + { + loseAnim = BOTH_LK_S_S_T_SB_1_L; + } + else if ( !victory ) + { + loseAnim = BOTH_BF1BREAK; + } + else + { + if ( !victory ) + {//no-one won + genemy->saberMove = LS_K1_T_; + loseAnim = BOTH_K1_S1_T_; + } + else + {//FIXME: this anim needs to transition back to ready when done + loseAnim = BOTH_BF1BREAK; + } + } + break; + case BOTH_BF1LOCK: + if ( superBreak ) + { + loseAnim = BOTH_LK_S_S_T_SB_1_L; + } + else if ( !victory ) + { + loseAnim = BOTH_KNOCKDOWN4; + } + else + { + if ( !victory ) + {//no-one won + genemy->saberMove = LS_A_T2B; + loseAnim = BOTH_A3_T__B_; + } + else + { + loseAnim = BOTH_KNOCKDOWN4; + } + } + break; + case BOTH_CWCIRCLELOCK: + if ( superBreak ) + { + loseAnim = BOTH_LK_S_S_S_SB_1_L; + } + else if ( !victory ) + { + genemy->saberMove = LS_V1_BL;//genemy->saberBounceMove = + genemy->saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BL_S1; + } + else + { + if ( !victory ) + {//no-one won + loseAnim = BOTH_CCWCIRCLEBREAK; + } + else + { + genemy->saberMove = LS_V1_BL;//genemy->saberBounceMove = + genemy->saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BL_S1; + /* + genemy->client->ps.saberMove = genemy->client->ps.saberBounceMove = LS_H1_BR; + genemy->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_H1_S1_BL; + */ + } + } + break; + case BOTH_CCWCIRCLELOCK: + if ( superBreak ) + { + loseAnim = BOTH_LK_S_S_S_SB_1_L; + } + else if ( !victory ) + { + genemy->saberMove = LS_V1_BR;//genemy->saberBounceMove = + genemy->saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BR_S1; + } + else + { + if ( !victory ) + {//no-one won + loseAnim = BOTH_CWCIRCLEBREAK; + } + else + { + genemy->saberMove = LS_V1_BR;//genemy->saberBounceMove = + genemy->saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BR_S1; + /* + genemy->client->ps.saberMove = genemy->client->ps.saberBounceMove = LS_H1_BL; + genemy->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_H1_S1_BR; + */ + } + } + break; + } + if ( loseAnim != -1 ) + { +#ifdef QAGAME + NPC_SetAnim( &g_entities[genemy->clientNum], SETANIM_BOTH, loseAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + genemy->weaponTime = genemy->torsoTimer;// + 250; +#endif + genemy->saberBlocked = BLOCKED_NONE; + genemy->weaponstate = WEAPON_READY; + } + return loseAnim; +} + +int PM_SaberLockResultAnim( playerState_t *duelist, qboolean superBreak, qboolean won ) +{ + int baseAnim = duelist->torsoAnim; + switch ( baseAnim ) + { + case BOTH_LK_S_S_S_L_2: //lock if I'm using single vs. a single and other intitiated + baseAnim = BOTH_LK_S_S_S_L_1; + break; + case BOTH_LK_S_S_T_L_2: //lock if I'm using single vs. a single and other initiated + baseAnim = BOTH_LK_S_S_T_L_1; + break; + case BOTH_LK_DL_DL_S_L_2: //lock if I'm using dual vs. dual and other initiated + baseAnim = BOTH_LK_DL_DL_S_L_1; + break; + case BOTH_LK_DL_DL_T_L_2: //lock if I'm using dual vs. dual and other initiated + baseAnim = BOTH_LK_DL_DL_T_L_1; + break; + case BOTH_LK_ST_ST_S_L_2: //lock if I'm using staff vs. a staff and other initiated + baseAnim = BOTH_LK_ST_ST_S_L_1; + break; + case BOTH_LK_ST_ST_T_L_2: //lock if I'm using staff vs. a staff and other initiated + baseAnim = BOTH_LK_ST_ST_T_L_1; + break; + } + //what kind of break? + if ( !superBreak ) + { + baseAnim -= 2; + } + else if ( superBreak ) + { + baseAnim += 1; + } + else + {//WTF? Not a valid result + return -1; + } + //win or lose? + if ( won ) + { + baseAnim += 1; + } + + //play the anim and hold it +#ifdef QAGAME + //server-side: set it on the other guy, too + if ( duelist->clientNum == pm->ps->clientNum ) + {//me + PM_SetAnim( SETANIM_BOTH, baseAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + } + else + {//other guy + NPC_SetAnim( &g_entities[duelist->clientNum], SETANIM_BOTH, baseAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } +#else + PM_SetAnim( SETANIM_BOTH, baseAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); +#endif + + if ( superBreak + && !won ) + {//if you lose a superbreak, you're defenseless + /* + //Taken care of in SetSaberBoxSize() + //make saberent not block + gentity_t *saberent = &g_entities[duelist->client->ps.saberEntityNum]; + if ( saberent ) + { + VectorClear(saberent->mins); + VectorClear(saberent->maxs); + G_SetOrigin(saberent, duelist->currentOrigin); + } + */ +#ifdef QAGAME + if ( 1 ) +#else + if ( duelist->clientNum == pm->ps->clientNum ) +#endif + { + //set sabermove to none + duelist->saberMove = LS_NONE; + //Hold the anim a little longer than it is + duelist->torsoTimer += 250; + } + } + +#ifdef QAGAME + if ( 1 ) +#else + if ( duelist->clientNum == pm->ps->clientNum ) +#endif + { + //no attacking during this anim + duelist->weaponTime = duelist->torsoTimer; + duelist->saberBlocked = BLOCKED_NONE; + /* + if ( superBreak + && won + && baseAnim != BOTH_LK_ST_DL_T_SB_1_W ) + {//going to attack with saber, do a saber trail + duelist->client->ps.SaberActivateTrail( 200 ); + } + */ + } + return baseAnim; +} + +void PM_SaberLockBreak( playerState_t *genemy, qboolean victory, int strength ) +{ + int winAnim = BOTH_STAND1, loseAnim = BOTH_STAND1; + //qboolean punishLoser = qfalse; + qboolean noKnockdown = qfalse; + qboolean singleVsSingle = qtrue; + qboolean superBreak = (strength+pm->ps->saberLockHits > 0);//Q_irand(2,4)); + + winAnim = PM_SaberLockWinAnim( victory, superBreak ); + if ( winAnim != -1 ) + {//a single vs. single break + loseAnim = PM_SaberLockLoseAnim( genemy, victory, superBreak ); + } + else + {//must be a saberlock that's not between single and single... + singleVsSingle = qfalse; + winAnim = PM_SaberLockResultAnim( pm->ps, superBreak, qtrue ); + pm->ps->weaponstate = WEAPON_FIRING; + loseAnim = PM_SaberLockResultAnim( genemy, superBreak, qfalse ); + genemy->weaponstate = WEAPON_READY; + } + + if ( victory ) + { //someone lost the lock, so punish them by knocking them down + if ( pm->ps->saberLockHits && !superBreak ) + {//there was some over-power in the win, but not enough to superbreak + vec3_t oppDir; + + int strength = 8; + + VectorSubtract(genemy->origin, pm->ps->origin, oppDir); + VectorNormalize(oppDir); + + if (noKnockdown) + { + if (!genemy->saberEntityNum) + { //if he has already lost his saber then just knock him down + noKnockdown = qfalse; + } + } + + if (!noKnockdown && BG_KnockDownable(genemy)) + { + genemy->forceHandExtend = HANDEXTEND_KNOCKDOWN; + genemy->forceHandExtendTime = pm->cmd.serverTime + 1100; + genemy->forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim + + genemy->otherKiller = pm->ps->clientNum; + genemy->otherKillerTime = pm->cmd.serverTime + 5000; + genemy->otherKillerDebounceTime = pm->cmd.serverTime + 100; + + genemy->velocity[0] = oppDir[0]*(strength*40); + genemy->velocity[1] = oppDir[1]*(strength*40); + genemy->velocity[2] = 100; + } + + pm->checkDuelLoss = genemy->clientNum+1; + + pm->ps->saberEventFlags |= SEF_LOCK_WON; + } + } + else + { //If no one lost, then shove each player away from the other + vec3_t oppDir; + + int strength = 4; + + VectorSubtract(genemy->origin, pm->ps->origin, oppDir); + VectorNormalize(oppDir); + genemy->velocity[0] = oppDir[0]*(strength*40); + genemy->velocity[1] = oppDir[1]*(strength*40); + genemy->velocity[2] = 150; + + VectorSubtract(pm->ps->origin, genemy->origin, oppDir); + VectorNormalize(oppDir); + pm->ps->velocity[0] = oppDir[0]*(strength*40); + pm->ps->velocity[1] = oppDir[1]*(strength*40); + pm->ps->velocity[2] = 150; + + genemy->forceHandExtend = HANDEXTEND_WEAPONREADY; + } + + pm->ps->weaponTime = 0; + genemy->weaponTime = 0; + + pm->ps->saberLockTime = genemy->saberLockTime = 0; + pm->ps->saberLockFrame = genemy->saberLockFrame = 0; + pm->ps->saberLockEnemy = genemy->saberLockEnemy = 0; + + pm->ps->forceHandExtend = HANDEXTEND_WEAPONREADY; + + PM_AddEvent( EV_JUMP ); + if ( !victory ) + {//no-one won + BG_AddPredictableEventToPlayerstate(EV_JUMP, 0, genemy); + } + else + { + if ( PM_irand_timesync( 0, 1 ) ) + { + BG_AddPredictableEventToPlayerstate(EV_JUMP, PM_irand_timesync( 0, 75 ), genemy); + } + } +} + +qboolean BG_CheckIncrementLockAnim( int anim, int winOrLose ) +{ + qboolean increment = qfalse;//??? + //RULE: if you are the first style in the lock anim, you advance from LOSING position to WINNING position + // if you are the second style in the lock anim, you advance from WINNING position to LOSING position + switch ( anim ) + { + //increment to win: + case BOTH_LK_DL_DL_S_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_DL_S_L_2: //lock if I'm using dual vs. dual and other initiated + case BOTH_LK_DL_DL_T_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_DL_T_L_2: //lock if I'm using dual vs. dual and other initiated + case BOTH_LK_DL_S_S_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_DL_S_T_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_DL_ST_S_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_DL_ST_T_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_S_S_S_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_S_S_T_L_2: //lock if I'm using single vs. a single and other initiated + case BOTH_LK_ST_S_S_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_ST_S_T_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_ST_ST_T_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_ST_T_L_2: //lock if I'm using staff vs. a staff and other initiated + if ( winOrLose == SABERLOCK_WIN ) + { + increment = qtrue; + } + else + { + increment = qfalse; + } + break; + + //decrement to win: + case BOTH_LK_S_DL_S_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_DL_T_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_S_S_L_2: //lock if I'm using single vs. a single and other intitiated + case BOTH_LK_S_S_T_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_S_ST_S_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_S_ST_T_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_ST_DL_S_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_DL_T_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_ST_S_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_ST_S_L_2: //lock if I'm using staff vs. a staff and other initiated + if ( winOrLose == SABERLOCK_WIN ) + { + increment = qfalse; + } + else + { + increment = qtrue; + } + break; + default: + break; + } + return increment; +} + +extern qboolean ValidAnimFileIndex ( int index ); +void PM_SaberLocked( void ) +{ + int remaining = 0; + playerState_t *genemy; + bgEntity_t *eGenemy = PM_BGEntForNum(pm->ps->saberLockEnemy); + + if (!eGenemy) + { + return; + } + + genemy = eGenemy->playerState; + + if ( !genemy ) + { + return; + } + /*if ( ( (pm->ps->torsoAnim) == BOTH_BF2LOCK || + (pm->ps->torsoAnim) == BOTH_BF1LOCK || + (pm->ps->torsoAnim) == BOTH_CWCIRCLELOCK || + (pm->ps->torsoAnim) == BOTH_CCWCIRCLELOCK ) + && ( (genemy->torsoAnim) == BOTH_BF2LOCK || + (genemy->torsoAnim) == BOTH_BF1LOCK || + (genemy->torsoAnim) == BOTH_CWCIRCLELOCK || + (genemy->torsoAnim) == BOTH_CCWCIRCLELOCK ) + ) + */ //yeah.. + if (pm->ps->saberLockFrame && + genemy->saberLockFrame && + BG_InSaberLock(pm->ps->torsoAnim) && + BG_InSaberLock(genemy->torsoAnim)) + { + float dist = 0; + + pm->ps->torsoTimer = 0; + pm->ps->weaponTime = 0; + genemy->torsoTimer = 0; + genemy->weaponTime = 0; + + dist = DistanceSquared(pm->ps->origin,genemy->origin); + if ( dist < 64 || dist > 6400 ) + {//between 8 and 80 from each other + PM_SaberLockBreak( genemy, qfalse, 0 ); + return; + } + /* + //NOTE: time-out is handled around where PM_SaberLocked is called + if ( pm->ps->saberLockTime <= pm->cmd.serverTime + 500 ) + {//lock just ended + PM_SaberLockBreak( genemy, qfalse, 0 ); + return; + } + */ + if ( pm->ps->saberLockAdvance ) + {//holding attack + animation_t *anim; + float currentFrame; + int curFrame; + int strength = 1; + + pm->ps->saberLockAdvance = qfalse; + + anim = &pm->animations[pm->ps->torsoAnim]; + + currentFrame = pm->ps->saberLockFrame; + + strength = pm->ps->fd.forcePowerLevel[FP_SABER_OFFENSE]+1; + + //advance/decrement my frame number + if ( BG_InSaberLockOld( pm->ps->torsoAnim ) ) + { //old locks + if ( (pm->ps->torsoAnim) == BOTH_CCWCIRCLELOCK || + (pm->ps->torsoAnim) == BOTH_BF2LOCK ) + { + curFrame = floor( currentFrame )-strength; + //drop my frame one + if ( curFrame <= anim->firstFrame ) + {//I won! Break out + PM_SaberLockBreak( genemy, qtrue, strength ); + return; + } + else + { + PM_SetAnimFrame( pm->ps, curFrame, qtrue, qtrue ); + remaining = curFrame-anim->firstFrame; + } + } + else + { + curFrame = ceil( currentFrame )+strength; + //advance my frame one + if ( curFrame >= anim->firstFrame+anim->numFrames ) + {//I won! Break out + PM_SaberLockBreak( genemy, qtrue, strength ); + return; + } + else + { + PM_SetAnimFrame( pm->ps, curFrame, qtrue, qtrue ); + remaining = anim->firstFrame+anim->numFrames-curFrame; + } + } + } + else + { //new locks + if ( BG_CheckIncrementLockAnim( pm->ps->torsoAnim, SABERLOCK_WIN ) ) + { + curFrame = ceil( currentFrame )+strength; + //advance my frame one + if ( curFrame >= anim->firstFrame+anim->numFrames ) + {//I won! Break out + PM_SaberLockBreak( genemy, qtrue, strength ); + return; + } + else + { + PM_SetAnimFrame( pm->ps, curFrame, qtrue, qtrue ); + remaining = anim->firstFrame+anim->numFrames-curFrame; + } + } + else + { + curFrame = floor( currentFrame )-strength; + //drop my frame one + if ( curFrame <= anim->firstFrame ) + {//I won! Break out + PM_SaberLockBreak( genemy, qtrue, strength ); + return; + } + else + { + PM_SetAnimFrame( pm->ps, curFrame, qtrue, qtrue ); + remaining = curFrame-anim->firstFrame; + } + } + } + if ( !PM_irand_timesync( 0, 2 ) ) + { + PM_AddEvent( EV_JUMP ); + } + //advance/decrement enemy frame number + anim = &pm->animations[(genemy->torsoAnim)]; + + if ( BG_InSaberLockOld( genemy->torsoAnim ) ) + { + if ( (genemy->torsoAnim) == BOTH_CWCIRCLELOCK || + (genemy->torsoAnim) == BOTH_BF1LOCK ) + { + if ( !PM_irand_timesync( 0, 2 ) ) + { + BG_AddPredictableEventToPlayerstate(EV_PAIN, floor((float)80/100*100.0f), genemy); + } + PM_SetAnimFrame( genemy, anim->firstFrame+remaining, qtrue, qtrue ); + } + else + { + PM_SetAnimFrame( genemy, anim->firstFrame+anim->numFrames-remaining, qtrue, qtrue ); + } + } + else + {//new locks + if ( BG_CheckIncrementLockAnim( genemy->torsoAnim, SABERLOCK_LOSE ) ) + { + if ( !PM_irand_timesync( 0, 2 ) ) + { + BG_AddPredictableEventToPlayerstate(EV_PAIN, floor((float)80/100*100.0f), genemy); + } + PM_SetAnimFrame( genemy, anim->firstFrame+anim->numFrames-remaining, qtrue, qtrue ); + } + else + { + PM_SetAnimFrame( genemy, anim->firstFrame+remaining, qtrue, qtrue ); + } + } + } + } + else + {//something broke us out of it + PM_SaberLockBreak( genemy, qfalse, 0 ); + } +} + +qboolean PM_SaberInBrokenParry( int move ) +{ + if ( move >= LS_V1_BR && move <= LS_V1_B_ ) + { + return qtrue; + } + if ( move >= LS_H1_T_ && move <= LS_H1_BL ) + { + return qtrue; + } + return qfalse; +} + + +int PM_BrokenParryForParry( int move ) +{ + switch ( move ) + { + case LS_PARRY_UP: + return LS_H1_T_; + break; + case LS_PARRY_UR: + return LS_H1_TR; + break; + case LS_PARRY_UL: + return LS_H1_TL; + break; + case LS_PARRY_LR: + return LS_H1_BL; + break; + case LS_PARRY_LL: + return LS_H1_BR; + break; + case LS_READY: + return LS_H1_B_; + break; + } + return LS_NONE; +} + +#define BACK_STAB_DISTANCE 128 + +qboolean PM_CanBackstab(void) +{ + trace_t tr; + vec3_t flatAng; + vec3_t fwd, back; + vec3_t trmins = {-15, -15, -8}; + vec3_t trmaxs = {15, 15, 8}; + + VectorCopy(pm->ps->viewangles, flatAng); + flatAng[PITCH] = 0; + + AngleVectors(flatAng, fwd, 0, 0); + + back[0] = pm->ps->origin[0] - fwd[0]*BACK_STAB_DISTANCE; + back[1] = pm->ps->origin[1] - fwd[1]*BACK_STAB_DISTANCE; + back[2] = pm->ps->origin[2] - fwd[2]*BACK_STAB_DISTANCE; + + pm->trace(&tr, pm->ps->origin, trmins, trmaxs, back, pm->ps->clientNum, MASK_PLAYERSOLID); + + if (tr.fraction != 1.0 && tr.entityNum >= 0 && tr.entityNum < ENTITYNUM_NONE) + { + bgEntity_t *bgEnt = PM_BGEntForNum(tr.entityNum); + + if (bgEnt && (bgEnt->s.eType == ET_PLAYER || bgEnt->s.eType == ET_NPC)) + { + return qtrue; + } + } + + return qfalse; +} + +saberMoveName_t PM_SaberFlipOverAttackMove(void) +{ + vec3_t fwdAngles, jumpFwd; +// float zDiff = 0; +// playerState_t *psData; +// bgEntity_t *bgEnt; + + VectorCopy( pm->ps->viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + AngleVectors( fwdAngles, jumpFwd, NULL, NULL ); + VectorScale( jumpFwd, 150, pm->ps->velocity );//was 50 + pm->ps->velocity[2] = 400; + + /* + bgEnt = PM_BGEntForNum(tr->entityNum); + + if (!bgEnt) + { + return LS_A_FLIP_STAB; + } + + psData = bgEnt->playerState; + + //go higher for enemies higher than you, lower for those lower than you + if (psData) + { + zDiff = psData->origin[2] - pm->ps->origin[2]; + } + else + { + zDiff = 0; + } + pm->ps->velocity[2] += (zDiff)*1.5f; + + //clamp to decent-looking values + if ( zDiff <= 0 && pm->ps->velocity[2] < 200 ) + {//if we're on same level, don't let me jump so low, I clip into the ground + pm->ps->velocity[2] = 200; + } + else if ( pm->ps->velocity[2] < 100 ) + { + pm->ps->velocity[2] = 100; + } + else if ( pm->ps->velocity[2] > 400 ) + { + pm->ps->velocity[2] = 400; + } + */ + + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + + PM_AddEvent( EV_JUMP ); + pm->ps->fd.forceJumpSound = 1; + pm->cmd.upmove = 0; + + /* + if ( PM_irand_timesync( 0, 1 ) ) + { + return LS_A_FLIP_STAB; + } + else + */ + { + return LS_A_FLIP_SLASH; + } +} + +int PM_SaberBackflipAttackMove( void ) +{ + pm->cmd.upmove = 127; + pm->ps->velocity[2] = 500; + return LS_A_BACKFLIP_ATK; +} + +int PM_SaberDualJumpAttackMove( void ) +{ + //FIXME: to make this move easier to execute, should be allowed to do it + // after you've already started your jump... but jump is delayed in + // this anim, so how do we undo the jump? + pm->cmd.upmove = 0;//no jump just yet + return LS_JUMPATTACK_DUAL; +} + +#define FLIPHACK_DISTANCE 200 + +qboolean PM_SomeoneInFront(trace_t *tr) +{ //Also a very simplified version of the sp counterpart + vec3_t flatAng; + vec3_t fwd, back; + vec3_t trmins = {-15, -15, -8}; + vec3_t trmaxs = {15, 15, 8}; + + VectorCopy(pm->ps->viewangles, flatAng); + flatAng[PITCH] = 0; + + AngleVectors(flatAng, fwd, 0, 0); + + back[0] = pm->ps->origin[0] + fwd[0]*FLIPHACK_DISTANCE; + back[1] = pm->ps->origin[1] + fwd[1]*FLIPHACK_DISTANCE; + back[2] = pm->ps->origin[2] + fwd[2]*FLIPHACK_DISTANCE; + + pm->trace(tr, pm->ps->origin, trmins, trmaxs, back, pm->ps->clientNum, MASK_PLAYERSOLID); + + if (tr->fraction != 1.0 && tr->entityNum >= 0 && tr->entityNum < ENTITYNUM_NONE) + { + bgEntity_t *bgEnt = PM_BGEntForNum(tr->entityNum); + + if (bgEnt && (bgEnt->s.eType == ET_PLAYER || bgEnt->s.eType == ET_NPC)) + { + return qtrue; + } + } + + return qfalse; +} + +saberMoveName_t PM_SaberLungeAttackMove( void ) +{ + vec3_t fwdAngles, jumpFwd; + + VectorCopy( pm->ps->viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + //do the lunge + AngleVectors( fwdAngles, jumpFwd, NULL, NULL ); + VectorScale( jumpFwd, 150, pm->ps->velocity ); + PM_AddEvent( EV_JUMP ); + + return LS_A_LUNGE; +} + +saberMoveName_t PM_SaberJumpAttackMove( void ) +{ + vec3_t fwdAngles, jumpFwd; + + VectorCopy( pm->ps->viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + AngleVectors( fwdAngles, jumpFwd, NULL, NULL ); + VectorScale( jumpFwd, 300, pm->ps->velocity ); + pm->ps->velocity[2] = 280; + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + + PM_AddEvent( EV_JUMP ); + pm->ps->fd.forceJumpSound = 1; + pm->cmd.upmove = 0; + + return LS_A_JUMP_T__B_; +} + +float PM_GroundDistance(void) +{ + trace_t tr; + vec3_t down; + + VectorCopy(pm->ps->origin, down); + + down[2] -= 4096; + + pm->trace(&tr, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, MASK_SOLID); + + VectorSubtract(pm->ps->origin, tr.endpos, down); + + return VectorLength(down); +} + +float PM_WalkableGroundDistance(void) +{ + trace_t tr; + vec3_t down; + + VectorCopy(pm->ps->origin, down); + + down[2] -= 4096; + + pm->trace(&tr, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, MASK_SOLID); + + if ( tr.plane.normal[2] < MIN_WALK_NORMAL ) + {//can't stand on this plane + return 4096; + } + + VectorSubtract(pm->ps->origin, tr.endpos, down); + + return VectorLength(down); +} + +qboolean BG_SaberInTransitionAny( int move ); +static qboolean PM_CanDoDualDoubleAttacks(void) +{ + if (BG_SaberInSpecialAttack(pm->ps->torsoAnim) || + BG_SaberInSpecialAttack(pm->ps->legsAnim)) + { + return qfalse; + } + return qtrue; +} + +static qboolean PM_CheckEnemyPresence( int dir, float radius ) +{ //anyone in this dir? + vec3_t angles; + vec3_t checkDir; + vec3_t tTo; + vec3_t tMins, tMaxs; + trace_t tr; + const float tSize = 12.0f; + //sp uses a bbox ent list check, but.. that's not so easy/fast to + //do in predicted code. So I'll just do a single box trace in the proper direction, + //and take whatever is first hit. + + VectorSet(tMins, -tSize, -tSize, -tSize); + VectorSet(tMaxs, tSize, tSize, tSize); + + VectorCopy(pm->ps->viewangles, angles); + angles[PITCH] = 0.0f; + + switch( dir ) + { + case DIR_RIGHT: + AngleVectors( angles, NULL, checkDir, NULL ); + break; + case DIR_LEFT: + AngleVectors( angles, NULL, checkDir, NULL ); + VectorScale( checkDir, -1, checkDir ); + break; + case DIR_FRONT: + AngleVectors( angles, checkDir, NULL, NULL ); + break; + case DIR_BACK: + AngleVectors( angles, checkDir, NULL, NULL ); + VectorScale( checkDir, -1, checkDir ); + break; + } + + VectorMA(pm->ps->origin, radius, checkDir, tTo); + pm->trace(&tr, pm->ps->origin, tMins, tMaxs, tTo, pm->ps->clientNum, MASK_PLAYERSOLID); + + if (tr.fraction != 1.0f && tr.entityNum < ENTITYNUM_WORLD) + { //let's see who we hit + bgEntity_t *bgEnt = PM_BGEntForNum(tr.entityNum); + + if (bgEnt && + (bgEnt->s.eType == ET_PLAYER || bgEnt->s.eType == ET_NPC)) + { //this guy can be considered an "enemy"... if he is on the same team, oh well. can't bg-check that (without a whole lot of hassle). + return qtrue; + } + } + + //no one in the trace + return qfalse; +} + +#define SABER_ALT_ATTACK_POWER 50//75? +#define SABER_ALT_ATTACK_POWER_LR 10//30? +#define SABER_ALT_ATTACK_POWER_FB 25//30/50? + +extern qboolean PM_SaberInReturn( int move ); //bg_panimate.c +saberMoveName_t PM_CheckPullAttack( void ) +{ +#if 0 //disabling these for MP, they aren't useful + if (!(pm->cmd.buttons & BUTTON_ATTACK)) + { + return LS_NONE; + } + + if ( (pm->ps->saberMove == LS_READY||PM_SaberInReturn(pm->ps->saberMove)||PM_SaberInReflect(pm->ps->saberMove))//ready + //&& (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())//PLAYER ONLY + && pm->ps->fd.saberAnimLevel >= SS_FAST//single saber styles - FIXME: Tavion? + && pm->ps->fd.saberAnimLevel <= SS_STRONG//single saber styles - FIXME: Tavion? + //&& G_TryingPullAttack( pm->gent, &pm->cmd, qfalse ) + //&& pm->ps->fd.forcePowerLevel[FP_PULL] + //rwwFIXMEFIXME: rick has the damn msg.cpp file checked out exclusively so I can't update the bloody psf to send this for prediction + && pm->ps->powerups[PW_DISINT_4] > pm->cmd.serverTime + && !(pm->ps->fd.forcePowersActive & (1<ps->powerups[PW_PULL] > pm->cmd.serverTime + //&& pm->cmd.forwardmove<0//pulling back + && (pm->cmd.buttons&BUTTON_ATTACK)//attacking + && BG_EnoughForcePowerForMove( SABER_ALT_ATTACK_POWER_FB ) )//pm->ps->forcePower >= SABER_ALT_ATTACK_POWER_FB//have enough power + {//FIXME: some NPC logic to do this? + qboolean doMove = qtrue; +// if ( g_saberNewControlScheme->integer +// || g_crosshairEntNum < ENTITYNUM_WORLD )//in old control scheme, there has to be someone there + { + saberMoveName_t pullAttackMove = LS_NONE; + if ( pm->ps->fd.saberAnimLevel == SS_FAST ) + { + pullAttackMove = LS_PULL_ATTACK_STAB; + } + else + { + pullAttackMove = LS_PULL_ATTACK_SWING; + } + + /* + if ( g_crosshairEntNum < ENTITYNUM_WORLD + && pm->gent && pm->gent->client ) + { + gentity_t *targEnt = &g_entities[g_crosshairEntNum]; + if ( targEnt->client + && targEnt->health > 0 + //FIXME: check other things like in knockdown, saberlock, uninterruptable anims, etc. + && !PM_InOnGroundAnim( &targEnt->client->ps ) + && !PM_LockedAnim( targEnt->client->ps.legsAnim ) + && !PM_SuperBreakLoseAnim( targEnt->client->ps.legsAnim ) + && !PM_SuperBreakWinAnim( targEnt->client->ps.legsAnim ) + && targEnt->client->ps.saberLockTime <= 0 + && WP_ForceThrowable( targEnt, targEnt, pm->gent, qtrue, 1.0f, 0.0f, NULL ) ) + { + if ( !g_saberNewControlScheme->integer ) + {//in old control scheme, make sure they're close or far enough away for the move we'll be doing + float targDist = Distance( targEnt->currentOrigin, pm->ps->origin ); + if ( pullAttackMove == LS_PULL_ATTACK_STAB ) + {//must be closer than 512 + if ( targDist > 384.0f ) + { + return LS_NONE; + } + } + else//if ( pullAttackMove == LS_PULL_ATTACK_SWING ) + {//must be farther than 256 + if ( targDist > 512.0f ) + { + return LS_NONE; + } + if ( targDist < 192.0f ) + { + return LS_NONE; + } + } + } + + vec3_t targAngles = {0,targEnt->client->ps.viewangles[YAW],0}; + if ( InFront( pm->ps->origin, targEnt->currentOrigin, targAngles ) ) + { + NPC_SetAnim( targEnt, SETANIM_BOTH, BOTH_PULLED_INAIR_F, SETANIM_FLAG_OVERRIDE, SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( targEnt, SETANIM_BOTH, BOTH_PULLED_INAIR_B, SETANIM_FLAG_OVERRIDE, SETANIM_FLAG_HOLD ); + } + //hold the anim until I'm with done pull anim + targEnt->client->ps.legsAnimTimer = targEnt->client->ps.torsoAnimTimer = PM_AnimLength( pm->gent->client->clientInfo.animFileIndex, (animNumber_t)saberMoveData[pullAttackMove].animToUse ); + //set pullAttackTime + pm->gent->client->ps.pullAttackTime = targEnt->client->ps.pullAttackTime = level.time+targEnt->client->ps.legsAnimTimer; + //make us know about each other + pm->gent->client->ps.pullAttackEntNum = g_crosshairEntNum; + targEnt->client->ps.pullAttackEntNum = pm->ps->clientNum; + //do effect and sound on me + pm->ps->powerups[PW_FORCE_PUSH] = level.time + 1000; + if ( pm->gent ) + { + G_Sound( pm->gent, G_SoundIndex( "sound/weapons/force/pull.wav" ) ); + } + doMove = qtrue; + } + } + */ + if ( doMove ) + { + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB ); + return pullAttackMove; + } + } + } +#endif + return LS_NONE; +} + +qboolean PM_InSecondaryStyle( void ) +{ + if ( pm->ps->fd.saberAnimLevelBase == SS_STAFF + || pm->ps->fd.saberAnimLevelBase == SS_DUAL ) + { + if ( pm->ps->fd.saberAnimLevel != pm->ps->fd.saberAnimLevelBase ) + { + return qtrue; + } + } + return qfalse; +} + +saberMoveName_t PM_SaberAttackForMovement(saberMoveName_t curmove) +{ + saberMoveName_t newmove = LS_NONE; + qboolean noSpecials = PM_InSecondaryStyle(); + + if ( pm->cmd.rightmove > 0 ) + {//moving right + if ( !noSpecials + && pm->ps->velocity[2] > 20.0f //pm->ps->groundEntityNum != ENTITYNUM_NONE//on ground + && (pm->cmd.buttons&BUTTON_ATTACK)//hitting attack + && PM_GroundDistance() < 70.0f //not too high above ground + && ( pm->cmd.upmove > 0 || (pm->ps->pm_flags & PMF_JUMP_HELD) )//focus-holding player + && BG_EnoughForcePowerForMove( SABER_ALT_ATTACK_POWER_LR ) )//have enough power + {//cartwheel right + vec3_t right, fwdAngles; + + VectorSet(fwdAngles, 0.0f, pm->ps->viewangles[YAW], 0.0f); + + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_LR); + + AngleVectors( fwdAngles, NULL, right, NULL ); + pm->ps->velocity[0] = pm->ps->velocity[1] = 0.0f; + VectorMA( pm->ps->velocity, 190.0f, right, pm->ps->velocity ); + if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + newmove = LS_BUTTERFLY_RIGHT; + pm->ps->velocity[2] = 350.0f; + } + else + { + //PM_SetJumped( JUMP_VELOCITY, qtrue ); + PM_AddEvent( EV_JUMP ); + pm->ps->velocity[2] = 300.0f; + + //if ( !Q_irand( 0, 1 ) ) + //if (PM_GroundDistance() >= 25.0f) + if (1) + { + newmove = LS_JUMPATTACK_ARIAL_RIGHT; + } + else + { + newmove = LS_JUMPATTACK_CART_RIGHT; + } + } + } + else if ( pm->cmd.forwardmove > 0 ) + {//forward right = TL2BR slash + newmove = LS_A_TL2BR; + } + else if ( pm->cmd.forwardmove < 0 ) + {//backward right = BL2TR uppercut + newmove = LS_A_BL2TR; + } + else + {//just right is a left slice + newmove = LS_A_L2R; + } + } + else if ( pm->cmd.rightmove < 0 ) + {//moving left + if ( !noSpecials + && pm->ps->velocity[2] > 20.0f //pm->ps->groundEntityNum != ENTITYNUM_NONE//on ground + && (pm->cmd.buttons&BUTTON_ATTACK)//hitting attack + && PM_GroundDistance() < 70.0f //not too high above ground + && ( pm->cmd.upmove > 0 || (pm->ps->pm_flags & PMF_JUMP_HELD) )//focus-holding player + && BG_EnoughForcePowerForMove( SABER_ALT_ATTACK_POWER_LR ) )//have enough power + {//cartwheel left + vec3_t right, fwdAngles; + + VectorSet(fwdAngles, 0.0f, pm->ps->viewangles[YAW], 0.0f); + + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_LR); + + AngleVectors( fwdAngles, NULL, right, NULL ); + pm->ps->velocity[0] = pm->ps->velocity[1] = 0.0f; + VectorMA( pm->ps->velocity, -190.0f, right, pm->ps->velocity ); + if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + newmove = LS_BUTTERFLY_LEFT; + pm->ps->velocity[2] = 250.0f; + } + else + { + //PM_SetJumped( JUMP_VELOCITY, qtrue ); + PM_AddEvent( EV_JUMP ); + pm->ps->velocity[2] = 350.0f; + + //if ( !Q_irand( 0, 1 ) ) + //if (PM_GroundDistance() >= 25.0f) + if (1) + { + newmove = LS_JUMPATTACK_ARIAL_LEFT; + } + else + { + newmove = LS_JUMPATTACK_CART_LEFT; + } + } + } + else if ( pm->cmd.forwardmove > 0 ) + {//forward left = TR2BL slash + newmove = LS_A_TR2BL; + } + else if ( pm->cmd.forwardmove < 0 ) + {//backward left = BR2TL uppercut + newmove = LS_A_BR2TL; + } + else + {//just left is a right slice + newmove = LS_A_R2L; + } + } + else + {//not moving left or right + if ( pm->cmd.forwardmove > 0 ) + {//forward= T2B slash + if (!noSpecials&& + (pm->ps->fd.saberAnimLevel == SS_DUAL || pm->ps->fd.saberAnimLevel == SS_STAFF) && + pm->ps->fd.forceRageRecoveryTime < pm->cmd.serverTime && + //pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 && + (pm->ps->groundEntityNum != ENTITYNUM_NONE || PM_GroundDistance() <= 40) && + pm->ps->velocity[2] >= 0 && + (pm->cmd.upmove > 0 || pm->ps->pm_flags & PMF_JUMP_HELD) && + !BG_SaberInTransitionAny(pm->ps->saberMove) && + !BG_SaberInAttack(pm->ps->saberMove) && + pm->ps->weaponTime <= 0 && + pm->ps->forceHandExtend == HANDEXTEND_NONE && + (pm->cmd.buttons & BUTTON_ATTACK)&& + BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER_FB) ) + { //DUAL/STAFF JUMP ATTACK + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + if (pm->ps->fd.saberAnimLevel == SS_DUAL) + { + newmove = PM_SaberDualJumpAttackMove(); + } + else + { + //rwwFIXMEFIXME I don't like randomness for this sort of thing, gives people reason to + //complain combat is unpredictable. Maybe do something more clever to determine + //if we should do a left or right? + /* + if (PM_irand_timesync(0, 1)) + { + newmove = LS_JUMPATTACK_STAFF_LEFT; + } + else + */ + { + newmove = LS_JUMPATTACK_STAFF_RIGHT; + } + } + } + else if (!noSpecials&& + pm->ps->fd.saberAnimLevel == SS_MEDIUM && + pm->ps->velocity[2] > 100 && + PM_GroundDistance() < 32 && + !BG_InSpecialJump(pm->ps->legsAnim) && + !BG_SaberInSpecialAttack(pm->ps->torsoAnim)&& + BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER_FB)) + { //FLIP AND DOWNWARD ATTACK + //trace_t tr; + + //if (PM_SomeoneInFront(&tr)) + { + newmove = PM_SaberFlipOverAttackMove(); + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + } + } + else if (!noSpecials&& + pm->ps->fd.saberAnimLevel == SS_STRONG && + pm->ps->velocity[2] > 100 && + PM_GroundDistance() < 32 && + !BG_InSpecialJump(pm->ps->legsAnim) && + !BG_SaberInSpecialAttack(pm->ps->torsoAnim)&& + BG_EnoughForcePowerForMove( SABER_ALT_ATTACK_POWER_FB )) + { //DFA + //trace_t tr; + + //if (PM_SomeoneInFront(&tr)) + { + newmove = PM_SaberJumpAttackMove(); + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + } + } + else if ((pm->ps->fd.saberAnimLevel == SS_FAST || pm->ps->fd.saberAnimLevel == SS_DUAL || pm->ps->fd.saberAnimLevel == SS_STAFF) && + pm->ps->groundEntityNum != ENTITYNUM_NONE && + (pm->ps->pm_flags & PMF_DUCKED) && + pm->ps->weaponTime <= 0 && + !BG_SaberInSpecialAttack(pm->ps->torsoAnim)&& + BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER_FB)) + { //LUNGE (weak) + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + if (pm->ps->fd.saberAnimLevel == FORCE_LEVEL_1) + { + newmove = PM_SaberLungeAttackMove(); + } + else if ( !noSpecials && pm->ps->fd.saberAnimLevel == SS_STAFF) + { + newmove = LS_SPINATTACK; + } + else if ( !noSpecials ) + { + newmove = LS_SPINATTACK_DUAL; + } + } + else if ( !noSpecials ) + { + saberMoveName_t stabDownMove = PM_CheckStabDown(); + if (stabDownMove != LS_NONE + && BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER_FB)) + { + newmove = stabDownMove; + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + } + else + { + newmove = LS_A_T2B; + } + } + } + else if ( pm->cmd.forwardmove < 0 ) + {//backward= T2B slash//B2T uppercut? + if (!noSpecials&& + pm->ps->fd.saberAnimLevel == SS_STAFF && + pm->ps->fd.forceRageRecoveryTime < pm->cmd.serverTime && + pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 && + (pm->ps->groundEntityNum != ENTITYNUM_NONE || PM_GroundDistance() <= 40) && + pm->ps->velocity[2] >= 0 && + (pm->cmd.upmove > 0 || pm->ps->pm_flags & PMF_JUMP_HELD) && + !BG_SaberInTransitionAny(pm->ps->saberMove) && + !BG_SaberInAttack(pm->ps->saberMove) && + pm->ps->weaponTime <= 0 && + pm->ps->forceHandExtend == HANDEXTEND_NONE && + (pm->cmd.buttons & BUTTON_ATTACK)) + { //BACKFLIP ATTACK + newmove = PM_SaberBackflipAttackMove(); + } + else if (PM_CanBackstab() && !BG_SaberInSpecialAttack(pm->ps->torsoAnim)) + { //BACKSTAB (attack varies by level) + if (pm->ps->fd.saberAnimLevel >= FORCE_LEVEL_2 && pm->ps->fd.saberAnimLevel != SS_STAFF) + {//medium and higher attacks + if ( (pm->ps->pm_flags&PMF_DUCKED) || pm->cmd.upmove < 0 ) + { + newmove = LS_A_BACK_CR; + } + else + { + newmove = LS_A_BACK; + } + } + else + { //weak attack + newmove = LS_A_BACKSTAB; + } + } + else + { + newmove = LS_A_T2B; + } + } + else if ( PM_SaberInBounce( curmove ) ) + {//bounces should go to their default attack if you don't specify a direction but are attacking + newmove = saberMoveData[curmove].chain_attack; + + if ( PM_SaberKataDone(curmove, newmove) ) + { + newmove = saberMoveData[curmove].chain_idle; + } + else + { + newmove = saberMoveData[curmove].chain_attack; + } + } + else if ( curmove == LS_READY ) + {//Not moving at all, shouldn't have gotten here...? + //for now, just pick a random attack + //newmove = Q_irand( LS_A_TL2BR, LS_A_T2B ); + //rww - If we don't seed with a "common" value, the client and server will get mismatched + //prediction values. Under laggy conditions this will cause the appearance of rapid swing + //sequence changes. + + newmove = LS_A_T2B; //decided we don't like random attacks when idle, use an overhead instead. + } + } + + if (pm->ps->fd.saberAnimLevel == SS_DUAL) + { + if ( ( newmove == LS_A_R2L || newmove == LS_S_R2L + || newmove == LS_A_L2R || newmove == LS_S_L2R ) + && PM_CanDoDualDoubleAttacks() + && PM_CheckEnemyPresence( DIR_RIGHT, 100.0f ) + && PM_CheckEnemyPresence( DIR_LEFT, 100.0f ) ) + {//enemy both on left and right + newmove = LS_DUAL_LR; + //probably already moved, but... + pm->cmd.rightmove = 0; + } + else if ( (newmove == LS_A_T2B || newmove == LS_S_T2B + || newmove == LS_A_BACK || newmove == LS_A_BACK_CR ) + && PM_CanDoDualDoubleAttacks() + && PM_CheckEnemyPresence( DIR_FRONT, 100.0f ) + && PM_CheckEnemyPresence( DIR_BACK, 100.0f ) ) + {//enemy both in front and back + newmove = LS_DUAL_FB; + //probably already moved, but... + pm->cmd.forwardmove = 0; + } + } + + return newmove; +} + +int PM_KickMoveForConditions(void) +{ + int kickMove = -1; + + //FIXME: only if FP_SABER_OFFENSE >= 3 + if ( pm->cmd.rightmove ) + {//kick to side + if ( pm->cmd.rightmove > 0 ) + {//kick right + kickMove = LS_KICK_R; + } + else + {//kick left + kickMove = LS_KICK_L; + } + pm->cmd.rightmove = 0; + } + else if ( pm->cmd.forwardmove ) + {//kick front/back + if ( pm->cmd.forwardmove > 0 ) + {//kick fwd + /* + if (pm->ps->groundEntityNum != ENTITYNUM_NONE && + PM_CheckEnemyPresence( DIR_FRONT, 64.0f )) + { + kickMove = LS_HILT_BASH; + } + else + */ + { + kickMove = LS_KICK_F; + } + } + else + {//kick back + kickMove = LS_KICK_B; + } + pm->cmd.forwardmove = 0; + } + else + { + //if (pm->cmd.buttons & BUTTON_ATTACK) + //if (pm->ps->pm_flags & PMF_JUMP_HELD) + if (0) + { //ok, let's try some fancy kicks + //qboolean is actually of type int anyway, but just for safeness. + int front = (int)PM_CheckEnemyPresence( DIR_FRONT, 100.0f ); + int back = (int)PM_CheckEnemyPresence( DIR_BACK, 100.0f ); + int right = (int)PM_CheckEnemyPresence( DIR_RIGHT, 100.0f ); + int left = (int)PM_CheckEnemyPresence( DIR_LEFT, 100.0f ); + int numEnemy = front+back+right+left; + + if (numEnemy >= 3 || + ((!right || !left) && numEnemy >= 2)) + { //> 2 enemies near, or, >= 2 enemies near and they are not to the right and left. + kickMove = LS_KICK_S; + } + else if (right && left) + { //enemies on both sides + kickMove = LS_KICK_RL; + } + else + { //oh well, just do a forward kick + kickMove = LS_KICK_F; + } + + pm->cmd.upmove = 0; + } + } + + return kickMove; +} + +qboolean BG_InSlopeAnim( int anim ); +qboolean PM_RunningAnim( int anim ); + +qboolean PM_SaberMoveOkayForKata( void ) +{ + if ( pm->ps->saberMove == LS_READY + || PM_SaberInStart( pm->ps->saberMove ) ) + { + return qtrue; + } + else + { + return qfalse; + } +} + +qboolean PM_CanDoKata( void ) +{ + if ( PM_InSecondaryStyle() ) + { + return qfalse; + } + if ( !pm->ps->saberInFlight//not throwing saber + && PM_SaberMoveOkayForKata() + && !BG_SaberInKata(pm->ps->saberMove) + && !BG_InKataAnim(pm->ps->legsAnim) + && !BG_InKataAnim(pm->ps->torsoAnim) + /* + && pm->ps->saberAnimLevel >= SS_FAST//fast, med or strong style + && pm->ps->saberAnimLevel <= SS_STRONG//FIXME: Tavion, too? + */ + && pm->ps->groundEntityNum != ENTITYNUM_NONE//not in the air + && (pm->cmd.buttons&BUTTON_ATTACK)//pressing attack + && (pm->cmd.buttons&BUTTON_ALT_ATTACK)//pressing alt attack + && !pm->cmd.forwardmove//not moving f/b + && !pm->cmd.rightmove//not moving r/l + && pm->cmd.upmove <= 0//not jumping...? + && BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER) )// have enough power + {//FIXME: check rage, etc... + return qtrue; + } + return qfalse; +} + +qboolean PM_CheckAltKickAttack( void ) +{ + if ( (pm->cmd.buttons&BUTTON_ALT_ATTACK) + //&& (!(pm->ps->pm_flags&PMF_ALT_ATTACK_HELD)||PM_SaberInReturn(pm->ps->saberMove)) + && (!BG_FlippingAnim(pm->ps->legsAnim)||pm->ps->legsTimer<=250) + && (pm->ps->fd.saberAnimLevel == SS_STAFF/*||!pm->ps->saber[0].throwable*/) && !pm->ps->saberHolstered ) + { + return qtrue; + } + return qfalse; +} + +int bg_parryDebounce[NUM_FORCE_POWER_LEVELS] = +{ + 500,//if don't even have defense, can't use defense! + 300, + 150, + 50 +}; + +qboolean PM_SaberPowerCheck(void) +{ + if (pm->ps->saberInFlight) + { //so we don't keep doing stupid force out thing while guiding saber. + if (pm->ps->fd.forcePower > forcePowerNeeded[pm->ps->fd.forcePowerLevel[FP_SABERTHROW]][FP_SABERTHROW]) + { + return qtrue; + } + } + else + { + return BG_EnoughForcePowerForMove(forcePowerNeeded[pm->ps->fd.forcePowerLevel[FP_SABERTHROW]][FP_SABERTHROW]); + } + + return qfalse; +} + +/* +================= +PM_WeaponLightsaber + +Consults a chart to choose what to do with the lightsaber. +While this is a little different than the Quake 3 code, there is no clean way of using the Q3 code for this kind of thing. +================= +*/ +// Ultimate goal is to set the sabermove to the proper next location +// Note that if the resultant animation is NONE, then the animation is essentially "idle", and is set in WP_TorsoAnim +qboolean PM_WalkingAnim( int anim ); +qboolean PM_SwimmingAnim( int anim ); +int PM_SaberBounceForAttack( int move ); +qboolean BG_SuperBreakLoseAnim( int anim ); +qboolean BG_SuperBreakWinAnim( int anim ); +void PM_WeaponLightsaber(void) +{ + int addTime,amount; + qboolean delayed_fire = qfalse; + int anim=-1, curmove, newmove=LS_NONE; + + qboolean checkOnlyWeap = qfalse; + + if ( PM_InKnockDown( pm->ps ) || BG_InRoll( pm->ps, pm->ps->legsAnim )) + {//in knockdown + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime <= 0 ) + { + pm->ps->weaponTime = 0; + } + } + if ( pm->ps->legsAnim == BOTH_ROLL_F + && pm->ps->legsTimer <= 250 ) + { + if ( (pm->cmd.buttons&BUTTON_ATTACK) ) + { + if ( BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER_FB) && !pm->ps->saberInFlight ) + { + //make sure the saber is on for this move! + if ( pm->ps->saberHolstered == 2 ) + {//all the way off + pm->ps->saberHolstered = 0; + PM_AddEvent(EV_SABER_UNHOLSTER); + } + PM_SetSaberMove( LS_ROLL_STAB ); + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + } + } + } + return; + } + + if ( pm->ps->saberLockTime > pm->cmd.serverTime ) + { + pm->ps->saberMove = LS_NONE; + PM_SaberLocked(); + return; + } + else + { + if ( /*( (pm->ps->torsoAnim) == BOTH_BF2LOCK || + (pm->ps->torsoAnim) == BOTH_BF1LOCK || + (pm->ps->torsoAnim) == BOTH_CWCIRCLELOCK || + (pm->ps->torsoAnim) == BOTH_CCWCIRCLELOCK ||*/ + pm->ps->saberLockFrame + ) + { + if (pm->ps->saberLockEnemy < ENTITYNUM_NONE && + pm->ps->saberLockEnemy >= 0) + { + bgEntity_t *bgEnt; + playerState_t *en; + + bgEnt = PM_BGEntForNum(pm->ps->saberLockEnemy); + + if (bgEnt) + { + en = bgEnt->playerState; + + if (en) + { + PM_SaberLockBreak(en, qfalse, 0); + return; + } + } + } + + if (/* ( (pm->ps->torsoAnim) == BOTH_BF2LOCK || + (pm->ps->torsoAnim) == BOTH_BF1LOCK || + (pm->ps->torsoAnim) == BOTH_CWCIRCLELOCK || + (pm->ps->torsoAnim) == BOTH_CCWCIRCLELOCK ||*/ + pm->ps->saberLockFrame + ) + { + pm->ps->torsoTimer = 0; + PM_SetAnim(SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_OVERRIDE, 100); + pm->ps->saberLockFrame = 0; + } + } + } + + if ( BG_KickingAnim( pm->ps->legsAnim ) || + BG_KickingAnim( pm->ps->torsoAnim )) + { + if ( pm->ps->legsTimer > 0 ) + {//you're kicking, no interruptions + return; + } + //done? be immeditately ready to do an attack + pm->ps->saberMove = LS_READY; + pm->ps->weaponTime = 0; + } + + if ( BG_SuperBreakLoseAnim( pm->ps->torsoAnim ) + || BG_SuperBreakWinAnim( pm->ps->torsoAnim ) ) + { + if ( pm->ps->torsoTimer > 0 ) + {//never interrupt these + return; + } + } + + if (BG_SabersOff( pm->ps )) + { + if (pm->ps->saberMove != LS_READY) + { + PM_SetSaberMove( LS_READY ); + } + + if ((pm->ps->legsAnim) != (pm->ps->torsoAnim) && !BG_InSlopeAnim(pm->ps->legsAnim) && + pm->ps->torsoTimer <= 0) + { + PM_SetAnim(SETANIM_TORSO,(pm->ps->legsAnim),SETANIM_FLAG_OVERRIDE, 100); + } + else if (BG_InSlopeAnim(pm->ps->legsAnim) && pm->ps->torsoTimer <= 0) + { + PM_SetAnim(SETANIM_TORSO,PM_GetSaberStance(),SETANIM_FLAG_OVERRIDE, 100); + } + + if (pm->ps->weaponTime < 1 && ((pm->cmd.buttons & BUTTON_ALT_ATTACK) || (pm->cmd.buttons & BUTTON_ATTACK))) + { + if (pm->ps->duelTime < pm->cmd.serverTime) + { + if (!pm->ps->m_iVehicleNum) + { //don't let em unholster the saber by attacking while on vehicle + pm->ps->saberHolstered = 0; + PM_AddEvent(EV_SABER_UNHOLSTER); + } + else + { + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + pm->cmd.buttons &= ~BUTTON_ATTACK; + } + } + } + + if ( pm->ps->weaponTime > 0 ) + { + pm->ps->weaponTime -= pml.msec; + } + + checkOnlyWeap = qtrue; + goto weapChecks; + } + + if (!pm->ps->saberEntityNum && pm->ps->saberInFlight) + { //this means our saber has been knocked away + /* + if (pm->ps->saberMove != LS_READY) + { + PM_SetSaberMove( LS_READY ); + } + + if ((pm->ps->legsAnim) != (pm->ps->torsoAnim) && !BG_InSlopeAnim(pm->ps->legsAnim)) + { + PM_SetAnim(SETANIM_TORSO,(pm->ps->legsAnim),SETANIM_FLAG_OVERRIDE, 100); + } + + if (BG_InSaberStandAnim(pm->ps->torsoAnim) || pm->ps->torsoAnim == BOTH_SABERPULL) + { + PM_SetAnim(SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_OVERRIDE, 100); + } + + return; + */ + //Old method, don't want to do this now because we want to finish up reflected attacks and things + //if our saber is pried out of our hands from one. + if ( pm->ps->fd.saberAnimLevel == SS_DUAL ) + { + if ( pm->ps->saberHolstered > 1 ) + { + pm->ps->saberHolstered = 1; + } + } + else + { + pm->cmd.buttons &= ~BUTTON_ATTACK; + } + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + } + + if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) ) + { //might as well just check for a saber throw right here + if (pm->ps->fd.saberAnimLevel == SS_STAFF) + { //kick instead of doing a throw + //if in a saber attack return anim, can interrupt it with a kick + if ( pm->ps->weaponTime > 0//can't fire yet + && PM_SaberInReturn( pm->ps->saberMove )//in a saber return move - FIXME: what about transitions? + //&& pm->ps->weaponTime <= 250//should be able to fire soon + //&& pm->ps->torsoTimer <= 250//torso almost done + && pm->ps->saberBlocked == BLOCKED_NONE//not interacting with any other saber + && !(pm->cmd.buttons&BUTTON_ATTACK) )//not trying to swing the saber + { + if ( (pm->cmd.forwardmove||pm->cmd.rightmove)//trying to kick in a specific direction + && PM_CheckAltKickAttack() )//trying to do a kick + {//allow them to do the kick now! + int kickMove = PM_KickMoveForConditions(); + if (kickMove != -1) + { + pm->ps->weaponTime = 0; + PM_SetSaberMove( kickMove ); + return; + } + } + } + } + else if ( pm->ps->weaponTime < 1&& + pm->ps->saberCanThrow && + //pm->ps->fd.forcePower >= forcePowerNeeded[pm->ps->fd.forcePowerLevel[FP_SABERTHROW]][FP_SABERTHROW] && + !BG_HasYsalamiri(pm->gametype, pm->ps) && + BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_SABERTHROW) && + pm->ps->fd.forcePowerLevel[FP_SABERTHROW] > 0 && + PM_SaberPowerCheck() ) + { + trace_t sabTr; + vec3_t fwd, minFwd, sabMins, sabMaxs; + + VectorSet( sabMins, SABERMINS_X, SABERMINS_Y, SABERMINS_Z ); + VectorSet( sabMaxs, SABERMAXS_X, SABERMAXS_Y, SABERMAXS_Z ); + + AngleVectors( pm->ps->viewangles, fwd, NULL, NULL ); + VectorMA( pm->ps->origin, SABER_MIN_THROW_DIST, fwd, minFwd ); + + pm->trace(&sabTr, pm->ps->origin, sabMins, sabMaxs, minFwd, pm->ps->clientNum, MASK_PLAYERSOLID); + + if ( sabTr.allsolid || sabTr.startsolid || sabTr.fraction < 1.0f ) + {//not enough room to throw + } + else + {//throw it + //This will get set to false again once the saber makes it back to its owner game-side + if (!pm->ps->saberInFlight) + { + pm->ps->fd.forcePower -= forcePowerNeeded[pm->ps->fd.forcePowerLevel[FP_SABERTHROW]][FP_SABERTHROW]; + } + + pm->ps->saberInFlight = qtrue; + } + } + } + + if ( pm->ps->saberInFlight && pm->ps->saberEntityNum ) + {//guiding saber + if ( (pm->ps->fd.saberAnimLevel != SS_DUAL //not using 2 sabers + || pm->ps->saberHolstered //left one off - FIXME: saberHolstered 1 should be left one off, 0 should be both on, 2 should be both off + || (!(pm->cmd.buttons&BUTTON_ATTACK)//not trying to start an attack AND... + && (pm->ps->torsoAnim == BOTH_SABERDUAL_STANCE//not already attacking + || pm->ps->torsoAnim == BOTH_SABERPULL//not already attacking + || pm->ps->torsoAnim == BOTH_STAND1//not already attacking + || PM_RunningAnim( pm->ps->torsoAnim ) //not already attacking + || PM_WalkingAnim( pm->ps->torsoAnim ) //not already attacking + || PM_JumpingAnim( pm->ps->torsoAnim )//not already attacking + || PM_SwimmingAnim( pm->ps->torsoAnim ))//not already attacking + ) + ) + ) + { + PM_SetAnim(SETANIM_TORSO, BOTH_SABERPULL, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->torsoTimer = 1; + return; + } + } + + // don't allow attack until all buttons are up + //This is bad. It freezes the attack state and the animations if you hold the button after respawning, and it looks strange. + /* + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + */ + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + return; + } + + /* + + if (pm->ps->weaponstate == WEAPON_READY || + pm->ps->weaponstate == WEAPON_IDLE) + { + if (pm->ps->saberMove != LS_READY && pm->ps->weaponTime <= 0 && !pm->ps->saberBlocked) + { + PM_SetSaberMove( LS_READY ); + } + } + + if(PM_RunningAnim(pm->ps->torsoAnim)) + { + if ((pm->ps->torsoAnim) != (pm->ps->legsAnim)) + { + PM_SetAnim(SETANIM_TORSO,(pm->ps->legsAnim),SETANIM_FLAG_OVERRIDE, 100); + } + } + */ + + // make weapon function + if ( pm->ps->weaponTime > 0 ) + { + //check for special pull move while busy + saberMoveName_t pullmove = PM_CheckPullAttack(); + if (pullmove != LS_NONE) + { + pm->ps->weaponTime = 0; + pm->ps->torsoTimer = 0; + pm->ps->legsTimer = 0; + pm->ps->forceHandExtend = HANDEXTEND_NONE; + pm->ps->weaponstate = WEAPON_READY; + PM_SetSaberMove(pullmove); + return; + } + + pm->ps->weaponTime -= pml.msec; + + //This was stupid and didn't work right. Looks like things are fine without it. + // if (pm->ps->saberBlocked && pm->ps->torsoAnim != saberMoveData[pm->ps->saberMove].animToUse) + // { //rww - keep him in the blocking pose until he can attack again + // PM_SetAnim(SETANIM_TORSO,saberMoveData[pm->ps->saberMove].animToUse,saberMoveData[pm->ps->saberMove].animSetFlags|SETANIM_FLAG_HOLD, saberMoveData[pm->ps->saberMove].blendTime); + // return; + // } + } + else + { + pm->ps->weaponstate = WEAPON_READY; + } + + // Now we react to a block action by the player's lightsaber. + if ( pm->ps->saberBlocked ) + { + if ( pm->ps->saberBlocked >= BLOCKED_UPPER_RIGHT + && pm->ps->saberBlocked < BLOCKED_UPPER_RIGHT_PROJ) + {//hold the parry for a bit + pm->ps->weaponTime = bg_parryDebounce[pm->ps->fd.forcePowerLevel[FP_SABER_DEFENSE]]+200; + } + switch ( pm->ps->saberBlocked ) + { + case BLOCKED_BOUNCE_MOVE: + { //act as a bounceMove and reset the saberMove instead of using a seperate value for it + pm->ps->torsoTimer = 0; + PM_SetSaberMove( pm->ps->saberMove ); + pm->ps->weaponTime = pm->ps->torsoTimer; + pm->ps->saberBlocked = 0; + } + break; + case BLOCKED_PARRY_BROKEN: + //whatever parry we were is in now broken, play the appropriate knocked-away anim + { + int nextMove; + + if ( PM_SaberInBrokenParry( pm->ps->saberMove ) ) + {//already have one...? + nextMove = pm->ps->saberMove; + } + else + { + nextMove = PM_BrokenParryForParry( pm->ps->saberMove ); + } + if ( nextMove != LS_NONE ) + { + PM_SetSaberMove( nextMove ); + pm->ps->weaponTime = pm->ps->torsoTimer; + } + else + {//Maybe in a knockaway? + } + } + break; + case BLOCKED_ATK_BOUNCE: + // If there is absolutely no blocked move in the chart, don't even mess with the animation. + // OR if we are already in a block or parry. + if (pm->ps->saberMove >= LS_T1_BR__R) + {//an actual bounce? Other bounces before this are actually transitions? + pm->ps->saberBlocked = BLOCKED_NONE; + } + else + { + int bounceMove; + + if ( PM_SaberInBounce( pm->ps->saberMove ) || !BG_SaberInAttack( pm->ps->saberMove ) ) + { + if ( pm->cmd.buttons & BUTTON_ATTACK ) + {//transition to a new attack + int newQuad = PM_SaberMoveQuadrantForMovement( &pm->cmd ); + while ( newQuad == saberMoveData[pm->ps->saberMove].startQuad ) + {//player is still in same attack quad, don't repeat that attack because it looks bad, + //FIXME: try to pick one that might look cool? + //newQuad = Q_irand( Q_BR, Q_BL ); + newQuad = PM_irand_timesync( Q_BR, Q_BL ); + //FIXME: sanity check, just in case? + }//else player is switching up anyway, take the new attack dir + bounceMove = transitionMove[saberMoveData[pm->ps->saberMove].startQuad][newQuad]; + } + else + {//return to ready + if ( saberMoveData[pm->ps->saberMove].startQuad == Q_T ) + { + bounceMove = LS_R_BL2TR; + } + else if ( saberMoveData[pm->ps->saberMove].startQuad < Q_T ) + { + bounceMove = LS_R_TL2BR+saberMoveData[pm->ps->saberMove].startQuad-Q_BR; + } + else// if ( saberMoveData[pm->ps->saberMove].startQuad > Q_T ) + { + bounceMove = LS_R_BR2TL+saberMoveData[pm->ps->saberMove].startQuad-Q_TL; + } + } + } + else + {//start the bounce + bounceMove = PM_SaberBounceForAttack( (saberMoveName_t)pm->ps->saberMove ); + } + + PM_SetSaberMove( bounceMove ); + + pm->ps->weaponTime = pm->ps->torsoTimer;//+saberMoveData[bounceMove].blendTime+SABER_BLOCK_DUR; + + } + break; + case BLOCKED_UPPER_RIGHT: + PM_SetSaberMove( LS_PARRY_UR ); + break; + case BLOCKED_UPPER_RIGHT_PROJ: + PM_SetSaberMove( LS_REFLECT_UR ); + break; + case BLOCKED_UPPER_LEFT: + PM_SetSaberMove( LS_PARRY_UL ); + break; + case BLOCKED_UPPER_LEFT_PROJ: + PM_SetSaberMove( LS_REFLECT_UL ); + break; + case BLOCKED_LOWER_RIGHT: + PM_SetSaberMove( LS_PARRY_LR ); + break; + case BLOCKED_LOWER_RIGHT_PROJ: + PM_SetSaberMove( LS_REFLECT_LR ); + break; + case BLOCKED_LOWER_LEFT: + PM_SetSaberMove( LS_PARRY_LL ); + break; + case BLOCKED_LOWER_LEFT_PROJ: + PM_SetSaberMove( LS_REFLECT_LL); + break; + case BLOCKED_TOP: + PM_SetSaberMove( LS_PARRY_UP ); + break; + case BLOCKED_TOP_PROJ: + PM_SetSaberMove( LS_REFLECT_UP ); + break; + default: + pm->ps->saberBlocked = BLOCKED_NONE; + break; + } + if ( pm->ps->saberBlocked >= BLOCKED_UPPER_RIGHT + && pm->ps->saberBlocked < BLOCKED_UPPER_RIGHT_PROJ) + {//hold the parry for a bit + if ( pm->ps->torsoTimer < pm->ps->weaponTime ) + { + pm->ps->torsoTimer = pm->ps->weaponTime; + } + } + + //what the? I don't know why I was doing this. + /* + if (pm->ps->saberBlocked != BLOCKED_ATK_BOUNCE && pm->ps->saberBlocked != BLOCKED_PARRY_BROKEN && pm->ps->weaponTime < 1) + { + pm->ps->torsoTimer = SABER_BLOCK_DUR; + pm->ps->weaponTime = pm->ps->torsoTimer; + } + */ + + //clear block + pm->ps->saberBlocked = 0; + + // Charging is like a lead-up before attacking again. This is an appropriate use, or we can create a new weaponstate for blocking + pm->ps->weaponstate = WEAPON_READY; + + // Done with block, so stop these active weapon branches. + return; + } + +weapChecks: + if (pm->ps->saberEntityNum) + { //only check if we have our saber with us + // check for weapon change + // can't change if weapon is firing, but can change again if lowering or raising + //if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { + if (pm->ps->weaponTime <= 0 && pm->ps->torsoTimer <= 0) + { + if ( pm->ps->weapon != pm->cmd.weapon ) { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + } + + if ( PM_CanDoKata() ) + { + //FIXME: make sure to turn on saber(s)! + switch ( pm->ps->fd.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + PM_SetSaberMove( LS_A1_SPECIAL ); + break; + case SS_MEDIUM: + PM_SetSaberMove( LS_A2_SPECIAL ); + break; + case SS_STRONG: + case SS_DESANN: + PM_SetSaberMove( LS_A3_SPECIAL ); + break; + case SS_DUAL: + PM_SetSaberMove( LS_DUAL_SPIN_PROTECT );//PM_CheckDualSpinProtect(); + break; + case SS_STAFF: + PM_SetSaberMove( LS_STAFF_SOULCAL ); + break; + } + pm->ps->weaponstate = WEAPON_FIRING; + //G_DrainPowerForSpecialMove( pm->gent, FP_SABER_OFFENSE, SABER_ALT_ATTACK_POWER );//FP_SPEED, SINGLE_SPECIAL_POWER ); + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER); + return; + } + + if ( pm->ps->weaponTime > 0 ) + { + return; + } + + // ********************************************************* + // WEAPON_DROPPING + // ********************************************************* + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + // ********************************************************* + // WEAPON_RAISING + // ********************************************************* + + if ( pm->ps->weaponstate == WEAPON_RAISING ) + {//Just selected the weapon + pm->ps->weaponstate = WEAPON_IDLE; + if((pm->ps->legsAnim) == BOTH_WALK1 ) + { + PM_SetAnim(SETANIM_TORSO,BOTH_WALK1,SETANIM_FLAG_NORMAL, 100); + } + else if((pm->ps->legsAnim) == BOTH_RUN1 ) + { + PM_SetAnim(SETANIM_TORSO,BOTH_RUN1,SETANIM_FLAG_NORMAL, 100); + } + else if((pm->ps->legsAnim) == BOTH_RUN2 ) + { + PM_SetAnim(SETANIM_TORSO,BOTH_RUN2,SETANIM_FLAG_NORMAL, 100); + } + else if( pm->ps->legsAnim == BOTH_RUN_STAFF) + { + PM_SetAnim(SETANIM_TORSO,BOTH_RUN_STAFF,SETANIM_FLAG_NORMAL, 100); + } + else if( pm->ps->legsAnim == BOTH_RUN_DUAL) + { + PM_SetAnim(SETANIM_TORSO,BOTH_RUN_DUAL,SETANIM_FLAG_NORMAL, 100); + } + else if((pm->ps->legsAnim) == BOTH_WALK1 ) + { + PM_SetAnim(SETANIM_TORSO,BOTH_WALK1,SETANIM_FLAG_NORMAL, 100); + } + else if( pm->ps->legsAnim == BOTH_WALK2) + { + PM_SetAnim(SETANIM_TORSO,BOTH_WALK2,SETANIM_FLAG_NORMAL, 100); + } + else if( pm->ps->legsAnim == BOTH_WALK_STAFF) + { + PM_SetAnim(SETANIM_TORSO,BOTH_WALK_STAFF,SETANIM_FLAG_NORMAL, 100); + } + else if( pm->ps->legsAnim == BOTH_WALK_DUAL) + { + PM_SetAnim(SETANIM_TORSO,BOTH_WALK_DUAL,SETANIM_FLAG_NORMAL, 100); + } + else + { + PM_SetAnim(SETANIM_TORSO,PM_GetSaberStance(),SETANIM_FLAG_NORMAL, 100); + } + + if (pm->ps->weaponstate == WEAPON_RAISING) + { + return; + } + + } + + if (checkOnlyWeap) + { + return; + } + + // ********************************************************* + // Check for WEAPON ATTACK + // ********************************************************* + if (pm->ps->fd.saberAnimLevel == SS_STAFF && + (pm->cmd.buttons & BUTTON_ALT_ATTACK)) + { //ok, try a kick I guess. + int kickMove = -1; + + if ( !BG_KickingAnim(pm->ps->torsoAnim) && + !BG_KickingAnim(pm->ps->legsAnim) && + !BG_InRoll(pm->ps, pm->ps->legsAnim) && +// !BG_KickMove( pm->ps->saberMove )//not already in a kick + pm->ps->saberMove == LS_READY + && !(pm->ps->pm_flags&PMF_DUCKED)//not ducked + && (pm->cmd.upmove >= 0 ) //not trying to duck + )//&& pm->ps->groundEntityNum != ENTITYNUM_NONE) + {//player kicks + kickMove = PM_KickMoveForConditions(); + } + + if (kickMove != -1) + { + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//if in air, convert kick to an in-air kick + float gDist = PM_GroundDistance(); + //let's only allow air kicks if a certain distance from the ground + //it's silly to be able to do them right as you land. + //also looks wrong to transition from a non-complete flip anim... + if ((!BG_FlippingAnim( pm->ps->legsAnim ) || pm->ps->legsTimer <= 0) && + gDist > 64.0f && //strict minimum + gDist > (-pm->ps->velocity[2])-64.0f //make sure we are high to ground relative to downward velocity as well + ) + { + switch ( kickMove ) + { + case LS_KICK_F: + kickMove = LS_KICK_F_AIR; + break; + case LS_KICK_B: + kickMove = LS_KICK_B_AIR; + break; + case LS_KICK_R: + kickMove = LS_KICK_R_AIR; + break; + case LS_KICK_L: + kickMove = LS_KICK_L_AIR; + break; + default: //oh well, can't do any other kick move while in-air + kickMove = -1; + break; + } + } + else + {//leave it as a normal kick unless we're too high up + if ( gDist > 128.0f || pm->ps->velocity[2] >= 0 ) + { //off ground, but too close to ground + kickMove = -1; + } + } + } + + if (kickMove != -1) + { + PM_SetSaberMove( kickMove ); + return; + } + } + } + + //this is never a valid regular saber attack button + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + + if(!delayed_fire) + { + // Start with the current move, and cross index it with the current control states. + if ( pm->ps->saberMove > LS_NONE && pm->ps->saberMove < LS_MOVE_MAX ) + { + curmove = pm->ps->saberMove; + } + else + { + curmove = LS_READY; + } + + if ( curmove == LS_A_JUMP_T__B_ || pm->ps->torsoAnim == BOTH_FORCELEAP2_T__B_ ) + {//must transition back to ready from this anim + newmove = LS_R_T2B; + } + // check for fire + else if ( !(pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) + {//not attacking + pm->ps->weaponTime = 0; + + if ( pm->ps->weaponTime > 0 ) + {//Still firing + pm->ps->weaponstate = WEAPON_FIRING; + } + else if ( pm->ps->weaponstate != WEAPON_READY ) + { + pm->ps->weaponstate = WEAPON_IDLE; + } + //Check for finishing an anim if necc. + if ( curmove >= LS_S_TL2BR && curmove <= LS_S_T2B ) + {//started a swing, must continue from here + newmove = LS_A_TL2BR + (curmove-LS_S_TL2BR); + } + else if ( curmove >= LS_A_TL2BR && curmove <= LS_A_T2B ) + {//finished an attack, must continue from here + newmove = LS_R_TL2BR + (curmove-LS_A_TL2BR); + } + else if ( PM_SaberInTransition( curmove ) ) + {//in a transition, must play sequential attack + newmove = saberMoveData[curmove].chain_attack; + } + else if ( PM_SaberInBounce( curmove ) ) + {//in a bounce + newmove = saberMoveData[curmove].chain_idle;//oops, not attacking, so don't chain + } + else + {//FIXME: what about returning from a parry? + //PM_SetSaberMove( LS_READY ); + //if ( pm->ps->saberBlockingTime > pm->cmd.serverTime ) + { + PM_SetSaberMove( LS_READY ); + } + return; + } + } + + // *************************************************** + // Pressing attack, so we must look up the proper attack move. + + if ( pm->ps->weaponTime > 0 ) + { // Last attack is not yet complete. + pm->ps->weaponstate = WEAPON_FIRING; + return; + } + else + { + int both = qfalse; + if ( pm->ps->torsoAnim == BOTH_FORCELONGLEAP_ATTACK + || pm->ps->torsoAnim == BOTH_FORCELONGLEAP_LAND ) + {//can't attack in these anims + return; + } + else if ( pm->ps->torsoAnim == BOTH_FORCELONGLEAP_START ) + {//only 1 attack you can do from this anim + if ( pm->ps->torsoTimer >= 200 ) + {//hit it early enough to do the attack + PM_SetSaberMove( LS_LEAP_ATTACK ); + } + return; + } + if ( curmove >= LS_PARRY_UP && curmove <= LS_REFLECT_LL ) + {//from a parry or reflection, can go directly into an attack + switch ( saberMoveData[curmove].endQuad ) + { + case Q_T: + newmove = LS_A_T2B; + break; + case Q_TR: + newmove = LS_A_TR2BL; + break; + case Q_TL: + newmove = LS_A_TL2BR; + break; + case Q_BR: + newmove = LS_A_BR2TL; + break; + case Q_BL: + newmove = LS_A_BL2TR; + break; + //shouldn't be a parry that ends at L, R or B + } + } + + if ( newmove != LS_NONE ) + {//have a valid, final LS_ move picked, so skip findingt he transition move and just get the anim + anim = saberMoveData[newmove].animToUse; + } + + //FIXME: diagonal dirs use the figure-eight attacks from ready pose? + if ( anim == -1 ) + { + //FIXME: take FP_SABER_OFFENSE into account here somehow? + if ( PM_SaberInTransition( curmove ) ) + {//in a transition, must play sequential attack + newmove = saberMoveData[curmove].chain_attack; + } + else if ( curmove >= LS_S_TL2BR && curmove <= LS_S_T2B ) + {//started a swing, must continue from here + newmove = LS_A_TL2BR + (curmove-LS_S_TL2BR); + } + else if ( PM_SaberInBrokenParry( curmove ) ) + {//broken parries must always return to ready + newmove = LS_READY; + } + else//if ( pm->cmd.buttons&BUTTON_ATTACK && !(pm->ps->pm_flags&PMF_ATTACK_HELD) )//only do this if just pressed attack button? + {//get attack move from movement command + /* + if ( PM_SaberKataDone() ) + {//we came from a bounce and cannot chain to another attack because our kata is done + newmove = saberMoveData[curmove].chain_idle; + } + else */ + newmove = PM_SaberAttackForMovement( curmove ); + if ( (PM_SaberInBounce( curmove )||PM_SaberInBrokenParry( curmove )) + && saberMoveData[newmove].startQuad == saberMoveData[curmove].endQuad ) + {//this attack would be a repeat of the last (which was blocked), so don't actually use it, use the default chain attack for this bounce + newmove = saberMoveData[curmove].chain_attack; + } + + if ( PM_SaberKataDone( curmove, newmove ) ) + {//cannot chain this time + newmove = saberMoveData[curmove].chain_idle; + } + } + /* + if ( newmove == LS_NONE ) + {//FIXME: should we allow this? Are there some anims that you should never be able to chain into an attack? + //only curmove that might get in here is LS_NONE, LS_DRAW, LS_PUTAWAY and the LS_R_ returns... all of which are in Q_R + newmove = PM_AttackMoveForQuad( saberMoveData[curmove].endQuad ); + } + */ + if ( newmove != LS_NONE ) + { + //Now get the proper transition move + newmove = PM_SaberAnimTransitionAnim( curmove, newmove ); + anim = saberMoveData[newmove].animToUse; + } + } + + if (anim == -1) + {//not side-stepping, pick neutral anim + // Add randomness for prototype? + newmove = saberMoveData[curmove].chain_attack; + + anim= saberMoveData[newmove].animToUse; + + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove && pm->cmd.upmove >= 0 && pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//not moving at all, so set the anim on entire body + both = qtrue; + } + + } + + if ( anim == -1) + { + switch ( pm->ps->legsAnim ) + { + case BOTH_WALK1: + case BOTH_WALK2: + case BOTH_WALK_STAFF: + case BOTH_WALK_DUAL: + case BOTH_WALKBACK1: + case BOTH_WALKBACK2: + case BOTH_WALKBACK_STAFF: + case BOTH_WALKBACK_DUAL: + case BOTH_RUN1: + case BOTH_RUN2: + case BOTH_RUN_STAFF: + case BOTH_RUN_DUAL: + case BOTH_RUNBACK1: + case BOTH_RUNBACK2: + case BOTH_RUNBACK_STAFF: + anim = pm->ps->legsAnim; + break; + default: + anim = PM_GetSaberStance(); + break; + } + +// if (PM_RunningAnim(anim) && !pm->cmd.forwardmove && !pm->cmd.rightmove) +// { //semi-hacky (if not moving on x-y and still playing the running anim, force the player out of it) +// anim = PM_GetSaberStance(); +// } + newmove = LS_READY; + } + + PM_SetSaberMove( newmove ); + + if ( both && pm->ps->torsoAnim == anim ) + { + PM_SetAnim(SETANIM_LEGS,anim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + } + + //don't fire again until anim is done + pm->ps->weaponTime = pm->ps->torsoTimer; + } + } + + // ********************************************************* + // WEAPON_FIRING + // ********************************************************* + + pm->ps->weaponstate = WEAPON_FIRING; + + amount = weaponData[pm->ps->weapon].energyPerShot; + + addTime = pm->ps->weaponTime; + + pm->ps->saberAttackSequence = pm->ps->torsoAnim; + if ( !addTime ) + { + addTime = weaponData[pm->ps->weapon].fireTime; + } + pm->ps->weaponTime = addTime; +} + +void PM_SetSaberMove(short newMove) +{ + unsigned int setflags = saberMoveData[newMove].animSetFlags; + int anim = saberMoveData[newMove].animToUse; + int parts = SETANIM_TORSO; + + if ( newMove == LS_READY || newMove == LS_A_FLIP_STAB || newMove == LS_A_FLIP_SLASH ) + {//finished with a kata (or in a special move) reset attack counter + pm->ps->saberAttackChainCount = 0; + } + else if ( BG_SaberInAttack( newMove ) ) + {//continuing with a kata, increment attack counter + pm->ps->saberAttackChainCount++; + } + + if (pm->ps->saberAttackChainCount > 16) + { //for the sake of being able to send the value over the net within a reasonable bit count + pm->ps->saberAttackChainCount = 16; + } + + if ( newMove == LS_DRAW ) + { + if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + anim = BOTH_S1_S7; + } + else if ( pm->ps->fd.saberAnimLevel == SS_DUAL ) + { + anim = BOTH_S1_S6; + } + } + else if ( newMove == LS_PUTAWAY ) + { + if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + anim = BOTH_S7_S1; + } + else if ( pm->ps->fd.saberAnimLevel == SS_DUAL ) + { + anim = BOTH_S6_S1; + } + } + else if ( pm->ps->fd.saberAnimLevel == SS_STAFF && newMove >= LS_S_TL2BR && newMove < LS_REFLECT_LL ) + {//staff has an entirely new set of anims, besides special attacks + //FIXME: include ready and draw/putaway? + //FIXME: get hand-made bounces and deflections? + if ( newMove >= LS_V1_BR && newMove <= LS_REFLECT_LL ) + {//there aren't 1-7, just 1, 6 and 7, so just set it + anim = BOTH_P7_S7_T_ + (anim-BOTH_P1_S1_T_);//shift it up to the proper set + } + else + {//add the appropriate animLevel + anim += (pm->ps->fd.saberAnimLevel-FORCE_LEVEL_1) * SABER_ANIM_GROUP_SIZE; + } + } + else if ( pm->ps->fd.saberAnimLevel == SS_DUAL && newMove >= LS_S_TL2BR && newMove < LS_REFLECT_LL ) + { //akimbo has an entirely new set of anims, besides special attacks + //FIXME: include ready and draw/putaway? + //FIXME: get hand-made bounces and deflections? + if ( newMove >= LS_V1_BR && newMove <= LS_REFLECT_LL ) + {//there aren't 1-7, just 1, 6 and 7, so just set it + anim = BOTH_P6_S6_T_ + (anim-BOTH_P1_S1_T_);//shift it up to the proper set + } + else + {//add the appropriate animLevel + anim += (pm->ps->fd.saberAnimLevel-FORCE_LEVEL_1) * SABER_ANIM_GROUP_SIZE; + } + } + /* + else if ( newMove == LS_DRAW && pm->ps->SaberStaff() ) + {//hold saber out front as we turn it on + //FIXME: need a real "draw" anim for this (and put-away) + anim = BOTH_SABERSTAFF_STANCE; + } + */ + else if ( pm->ps->fd.saberAnimLevel > FORCE_LEVEL_1 && + !BG_SaberInIdle( newMove ) && !PM_SaberInParry( newMove ) && !PM_SaberInKnockaway( newMove ) && !PM_SaberInBrokenParry( newMove ) && !PM_SaberInReflect( newMove ) && !BG_SaberInSpecial(newMove)) + {//readies, parries and reflections have only 1 level + anim += (pm->ps->fd.saberAnimLevel-FORCE_LEVEL_1) * SABER_ANIM_GROUP_SIZE; + } + + // If the move does the same animation as the last one, we need to force a restart... + if ( saberMoveData[pm->ps->saberMove].animToUse == anim && newMove > LS_PUTAWAY) + { + setflags |= SETANIM_FLAG_RESTART; + } + + //saber torso anims should always be highest priority (4/12/02 - for special anims only) + if (!pm->ps->m_iVehicleNum) + { //if not riding a vehicle + if (BG_SaberInSpecial(newMove)) + { + setflags |= SETANIM_FLAG_OVERRIDE; + } + /* + if ( newMove == LS_A_LUNGE + || newMove == LS_A_JUMP_T__B_ + || newMove == LS_A_BACKSTAB + || newMove == LS_A_BACK + || newMove == LS_A_BACK_CR + || newMove == LS_A_FLIP_STAB + || newMove == LS_A_FLIP_SLASH + || newMove == LS_JUMPATTACK_DUAL + || newMove == LS_A_BACKFLIP_ATK) + { + setflags |= SETANIM_FLAG_OVERRIDE; + } + */ + } + if ( BG_InSaberStandAnim(anim) || anim == BOTH_STAND1 ) + { + anim = (pm->ps->legsAnim); + + if ((anim >= BOTH_STAND1 && anim <= BOTH_STAND4TOATTACK2) || + (anim >= TORSO_DROPWEAP1 && anim <= TORSO_WEAPONIDLE10)) + { //If standing then use the special saber stand anim + anim = PM_GetSaberStance(); + } + + if (pm->ps->pm_flags & PMF_DUCKED) + { //Playing torso walk anims while crouched makes you look like a monkey + anim = PM_GetSaberStance(); + } + + if (anim == BOTH_WALKBACK1 || anim == BOTH_WALKBACK2 || anim == BOTH_WALK1) + { //normal stance when walking backward so saber doesn't look like it's cutting through leg + anim = PM_GetSaberStance(); + } + + if (BG_InSlopeAnim( anim )) + { + anim = PM_GetSaberStance(); + } + + parts = SETANIM_TORSO; + } + + if (!pm->ps->m_iVehicleNum) + { //if not riding a vehicle + if (newMove == LS_JUMPATTACK_ARIAL_RIGHT || + newMove == LS_JUMPATTACK_ARIAL_LEFT) + { //force only on legs + parts = SETANIM_LEGS; + } + else if ( newMove == LS_A_LUNGE + || newMove == LS_A_JUMP_T__B_ + || newMove == LS_A_BACKSTAB + || newMove == LS_A_BACK + || newMove == LS_A_BACK_CR + || newMove == LS_ROLL_STAB + || newMove == LS_A_FLIP_STAB + || newMove == LS_A_FLIP_SLASH + || newMove == LS_JUMPATTACK_DUAL + || newMove == LS_JUMPATTACK_ARIAL_LEFT + || newMove == LS_JUMPATTACK_ARIAL_RIGHT + || newMove == LS_JUMPATTACK_CART_LEFT + || newMove == LS_JUMPATTACK_CART_RIGHT + || newMove == LS_JUMPATTACK_STAFF_LEFT + || newMove == LS_JUMPATTACK_STAFF_RIGHT + || newMove == LS_A_BACKFLIP_ATK + || newMove == LS_STABDOWN + || newMove == LS_STABDOWN_STAFF + || newMove == LS_STABDOWN_DUAL + || newMove == LS_DUAL_SPIN_PROTECT + || newMove == LS_STAFF_SOULCAL + || newMove == LS_A1_SPECIAL + || newMove == LS_A2_SPECIAL + || newMove == LS_A3_SPECIAL + || newMove == LS_UPSIDE_DOWN_ATTACK + || newMove == LS_PULL_ATTACK_STAB + || newMove == LS_PULL_ATTACK_SWING + || BG_KickMove( newMove ) ) + { + parts = SETANIM_BOTH; + } + else if ( BG_SpinningSaberAnim( anim ) ) + {//spins must be played on entire body + parts = SETANIM_BOTH; + } + else if ( (!pm->cmd.forwardmove&&!pm->cmd.rightmove&&!pm->cmd.upmove)) + {//not trying to run, duck or jump + if ( !BG_FlippingAnim( pm->ps->legsAnim ) && + !BG_InRoll( pm->ps, pm->ps->legsAnim ) && + !PM_InKnockDown( pm->ps ) && + !PM_JumpingAnim( pm->ps->legsAnim ) && + !BG_InSpecialJump( pm->ps->legsAnim ) && + anim != PM_GetSaberStance() && + pm->ps->groundEntityNum != ENTITYNUM_NONE && + !(pm->ps->pm_flags & PMF_DUCKED)) + { + parts = SETANIM_BOTH; + } + else if ( !(pm->ps->pm_flags & PMF_DUCKED) + && ( newMove == LS_SPINATTACK_DUAL || newMove == LS_SPINATTACK ) ) + { + parts = SETANIM_BOTH; + } + } + + PM_SetAnim(parts, anim, setflags, saberMoveData[newMove].blendTime); + if (parts != SETANIM_LEGS && + (pm->ps->legsAnim == BOTH_ARIAL_LEFT || + pm->ps->legsAnim == BOTH_ARIAL_RIGHT)) + { + if (pm->ps->legsTimer > pm->ps->torsoTimer) + { + pm->ps->legsTimer = pm->ps->torsoTimer; + } + } + + } + + if ( (pm->ps->torsoAnim) == anim ) + {//successfully changed anims + //special check for *starting* a saber swing + //playing at attack + if ( BG_SaberInAttack( newMove ) || BG_SaberInSpecialAttack( anim ) ) + { + if ( pm->ps->saberMove != newMove ) + {//wasn't playing that attack before + if ( newMove != LS_KICK_F + && newMove != LS_KICK_B + && newMove != LS_KICK_R + && newMove != LS_KICK_L + && newMove != LS_KICK_F_AIR + && newMove != LS_KICK_B_AIR + && newMove != LS_KICK_R_AIR + && newMove != LS_KICK_L_AIR ) + { + PM_AddEvent(EV_SABER_ATTACK); + } + + if (pm->ps->brokenLimbs) + { //randomly make pain sounds with a broken arm because we are suffering. + int iFactor = -1; + + if (pm->ps->brokenLimbs & (1<ps->brokenLimbs & (1<ps); + } + } + } + } + } + + if (BG_SaberInSpecial(newMove) && + pm->ps->weaponTime < pm->ps->torsoTimer) + { //rww 01-02-03 - I think this will solve the issue of special attacks being interruptable, hopefully without side effects + pm->ps->weaponTime = pm->ps->torsoTimer; + } + + pm->ps->saberMove = newMove; + pm->ps->saberBlocking = saberMoveData[newMove].blocking; + + pm->ps->torsoAnim = anim; + + if (pm->ps->weaponTime <= 0) + { + pm->ps->saberBlocked = BLOCKED_NONE; + } + } +} + +#include "../namespace_end.h" diff --git a/codemp/game/bg_saberLoad.c b/codemp/game/bg_saberLoad.c new file mode 100644 index 0000000..284fc05 --- /dev/null +++ b/codemp/game/bg_saberLoad.c @@ -0,0 +1,1501 @@ +//bg_saberLoad.c +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" +#include "w_saber.h" + +// Borrow the one in UI: +#define MAX_SABER_DATA_SIZE 0x4000 +extern char SaberParms[MAX_SABER_DATA_SIZE]; + +//Could use strap stuff but I don't particularly care at the moment anyway. +#include "../namespace_begin.h" +extern int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +extern void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +extern void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +extern void trap_FS_FCloseFile( fileHandle_t f ); +extern int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +extern qhandle_t trap_R_RegisterSkin( const char *name ); +#include "../namespace_end.h" + + +#ifdef QAGAME +extern int G_SoundIndex( const char *name ); +#elif defined CGAME +#include "../namespace_begin.h" +sfxHandle_t trap_S_RegisterSound( const char *sample); +qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +int trap_FX_RegisterEffect(const char *file); +#include "../namespace_end.h" +#endif + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +#include "../namespace_begin.h" + +int BG_SoundIndex(char *sound) +{ +#ifdef QAGAME + return G_SoundIndex(sound); +#elif defined CGAME + return trap_S_RegisterSound(sound); +#endif +} + +extern stringID_table_t FPTable[]; + +//#define MAX_SABER_DATA_SIZE 0x10000 +//static char SaberParms[MAX_SABER_DATA_SIZE]; + +stringID_table_t SaberTable[] = +{ + ENUM2STRING(SABER_NONE), + ENUM2STRING(SABER_SINGLE), + ENUM2STRING(SABER_STAFF), + ENUM2STRING(SABER_BROAD), + ENUM2STRING(SABER_PRONG), + ENUM2STRING(SABER_DAGGER), + ENUM2STRING(SABER_ARC), + ENUM2STRING(SABER_SAI), + ENUM2STRING(SABER_CLAW), + ENUM2STRING(SABER_LANCE), + ENUM2STRING(SABER_STAR), + ENUM2STRING(SABER_TRIDENT), + "", -1 +}; + +//Also used in npc code +qboolean BG_ParseLiteral( const char **data, const char *string ) +{ + const char *token; + + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) + { + Com_Printf( "unexpected EOF\n" ); + return qtrue; + } + + if ( Q_stricmp( token, string ) ) + { + Com_Printf( "required string '%s' missing\n", string ); + return qtrue; + } + + return qfalse; +} + +saber_colors_t TranslateSaberColor( const char *name ) +{ + if ( !Q_stricmp( name, "red" ) ) + { + return SABER_RED; + } + if ( !Q_stricmp( name, "orange" ) ) + { + return SABER_ORANGE; + } + if ( !Q_stricmp( name, "yellow" ) ) + { + return SABER_YELLOW; + } + if ( !Q_stricmp( name, "green" ) ) + { + return SABER_GREEN; + } + if ( !Q_stricmp( name, "blue" ) ) + { + return SABER_BLUE; + } + if ( !Q_stricmp( name, "purple" ) ) + { + return SABER_PURPLE; + } + if ( !Q_stricmp( name, "random" ) ) + { + return ((saber_colors_t)(Q_irand( SABER_ORANGE, SABER_PURPLE ))); + } + return SABER_BLUE; +} + +saber_styles_t TranslateSaberStyle( const char *name ) +{ + if ( !Q_stricmp( name, "fast" ) ) + { + return SS_FAST; + } + if ( !Q_stricmp( name, "medium" ) ) + { + return SS_MEDIUM; + } + if ( !Q_stricmp( name, "strong" ) ) + { + return SS_STRONG; + } + if ( !Q_stricmp( name, "desann" ) ) + { + return SS_DESANN; + } + if ( !Q_stricmp( name, "tavion" ) ) + { + return SS_TAVION; + } + if ( !Q_stricmp( name, "dual" ) ) + { + return SS_DUAL; + } + if ( !Q_stricmp( name, "staff" ) ) + { + return SS_STAFF; + } + return SS_NONE; +} + +void WP_SaberSetDefaults( saberInfo_t *saber ) +{ + int i; + + //Set defaults so that, if it fails, there's at least something there + for ( i = 0; i < MAX_BLADES; i++ ) + { + saber->blade[i].color = SABER_RED; + saber->blade[i].radius = SABER_RADIUS_STANDARD; + saber->blade[i].lengthMax = 32; + } + + strcpy(saber->name, "default"); + strcpy(saber->fullName, "lightsaber"); + strcpy(saber->model, "models/weapons2/saber_reborn/saber_w.glm"); + saber->skin = 0; + saber->soundOn = BG_SoundIndex( "sound/weapons/saber/enemy_saber_on.wav" ); + saber->soundLoop = BG_SoundIndex( "sound/weapons/saber/saberhum3.wav" ); + saber->soundOff = BG_SoundIndex( "sound/weapons/saber/enemy_saber_off.wav" ); + saber->numBlades = 1; + saber->type = SABER_SINGLE; + saber->style = SS_NONE; + saber->maxChain = 0;//0 = use default behavior + saber->lockable = qtrue; + saber->throwable = qtrue; + saber->disarmable = qtrue; + saber->activeBlocking = qtrue; + saber->twoHanded = qfalse; + saber->forceRestrictions = 0; + saber->lockBonus = 0; + saber->parryBonus = 0; + saber->breakParryBonus = 0; + saber->disarmBonus = 0; + saber->singleBladeStyle = SS_NONE;//makes it so that you use a different style if you only have the first blade active + saber->singleBladeThrowable = qfalse;//makes it so that you can throw this saber if only the first blade is on +// saber->brokenSaber1 = NULL;//if saber is actually hit by another saber, it can be cut in half/broken and will be replaced with this saber in your right hand +// saber->brokenSaber2 = NULL;//if saber is actually hit by another saber, it can be cut in half/broken and will be replaced with this saber in your left hand + saber->returnDamage = qfalse;//when returning from a saber throw, it keeps spinning and doing damage +//===NEW FOR MP ONLY======================================================================================== + //done in cgame (client-side code) + saber->noWallMarks = qfalse; //0 - if 1, stops the saber from drawing marks on the world (good for real-sword type mods) + saber->noDlight = qfalse; //0 - if 1, stops the saber from drawing a dynamic light (good for real-sword type mods) + saber->noBlade = qfalse; //0 - if 1, stops the saber from drawing a blade (good for real-sword type mods) + saber->noClashFlare = qfalse; //0 - if non-zero, the saber will not do the big, white clash flare with other sabers + saber->trailStyle = 0; //0 - default (0) is normal, 1 is a motion blur and 2 is no trail at all (good for real-sword type mods) + saber->g2MarksShader = 0; //none - if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + //saber->bladeShader = 0; //none - if set, overrides the shader used for the saber blade? + //saber->trailShader = 0; //none - if set, overrides the shader used for the saber trail? + saber->spinSound = 0; //none - if set, plays this sound as it spins when thrown + saber->swingSound[0] = 0; //none - if set, plays one of these 3 sounds when swung during an attack - NOTE: must provide all 3!!! + saber->swingSound[1] = 0; //none - if set, plays one of these 3 sounds when swung during an attack - NOTE: must provide all 3!!! + saber->swingSound[2] = 0; //none - if set, plays one of these 3 sounds when swung during an attack - NOTE: must provide all 3!!! + saber->hitSound[0] = 0; //none - if set, plays one of these 3 sounds when saber hits a person - NOTE: must provide all 3!!! + saber->hitSound[1] = 0; //none - if set, plays one of these 3 sounds when saber hits a person - NOTE: must provide all 3!!! + saber->hitSound[2] = 0; //none - if set, plays one of these 3 sounds when saber hits a person - NOTE: must provide all 3!!! + saber->blockSound[0] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits another saber/sword - NOTE: must provide all 3!!! + saber->blockSound[1] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits another saber/sword - NOTE: must provide all 3!!! + saber->blockSound[2] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits another saber/sword - NOTE: must provide all 3!!! + saber->blockEffect = 0; //none - if set, plays this effect when the saber/sword hits another saber/sword (instead of "saber/saber_block.efx") + saber->hitPersonEffect = 0; //none - if set, plays this effect when the saber/sword hits a person (instead of "saber/blood_sparks_mp.efx") + saber->hitOtherEffect = 0; //none - if set, plays this effect when the saber/sword hits something else damagable (instead of "saber/saber_cut.efx") + + //done in game (server-side code) + saber->knockbackScale = 0; //0 - if non-zero, uses damage done to calculate an appropriate amount of knockback + saber->damageScale = 1.0f; //1 - scale up or down the damage done by the saber + saber->noDismemberment = qfalse; //0 - if non-zero, the saber never does dismemberment (good for pointed/blunt melee weapons) + saber->noIdleEffect = qfalse; //0 - if non-zero, the saber will not do damage or any effects when it is idle (not in an attack anim). (good for real-sword type mods) + //saber->bounceOnWalls = qfalse; //0 - if non-zero, the saber will bounce back when it hits solid architecture (good for real-sword type mods) + //saber->stickOnImpact = qfalse; //0 - if non-zero, the saber will stick in the wall when thrown and hits solid architecture (good for sabers that are meant to be thrown). + //saber->noAttack = qfalse; //0 - if non-zero, you cannot attack with the saber (for sabers/weapons that are meant to be thrown only, not used as melee weapons). +//========================================================================================================================================= +} + +#define DEFAULT_SABER "Kyle" + +qboolean WP_SaberParseParms( const char *SaberName, saberInfo_t *saber ) +{ + const char *token; + const char *value; + const char *p; + char useSaber[1024]; + float f; + int n; + qboolean triedDefault = qfalse; + + if ( !saber ) + { + return qfalse; + } + + //Set defaults so that, if it fails, there's at least something there + WP_SaberSetDefaults( saber ); + + if ( !SaberName || !SaberName[0] ) + { + strcpy(useSaber, DEFAULT_SABER); //default + triedDefault = qtrue; + } + else + { + strcpy(useSaber, SaberName); + } + + //try to parse it out + p = SaberParms; + COM_BeginParseSession("saberinfo"); + + // look for the right saber + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + if (!triedDefault) + { //fall back to default and restart, should always be there + p = SaberParms; + COM_BeginParseSession("saberinfo"); + strcpy(useSaber, DEFAULT_SABER); + triedDefault = qtrue; + } + else + { + return qfalse; + } + } + + if ( !Q_stricmp( token, useSaber ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { //even the default saber isn't found? + return qfalse; + } + + //got the name we're using for sure + strcpy(saber->name, useSaber); + + if ( BG_ParseLiteral( &p, "{" ) ) + { + return qfalse; + } + + // parse the saber info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", useSaber ); + return qfalse; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + //saber fullName + if ( !Q_stricmp( token, "name" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + strcpy(saber->fullName, value); + continue; + } + + //saber type + if ( !Q_stricmp( token, "saberType" ) ) + { + int saberType; + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saberType = GetIDForString( SaberTable, value ); + if ( saberType >= SABER_SINGLE && saberType <= NUM_SABERS ) + { + saber->type = (saberType_t)saberType; + } + continue; + } + + //saber hilt + if ( !Q_stricmp( token, "saberModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + strcpy(saber->model, value); + continue; + } + + if ( !Q_stricmp( token, "customSkin" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->skin = trap_R_RegisterSkin(value); + continue; + } + + //on sound + if ( !Q_stricmp( token, "soundOn" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->soundOn = BG_SoundIndex( (char *)value ); + continue; + } + + //loop sound + if ( !Q_stricmp( token, "soundLoop" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->soundLoop = BG_SoundIndex( (char *)value ); + continue; + } + + //off sound + if ( !Q_stricmp( token, "soundOff" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->soundOff = BG_SoundIndex( (char *)value ); + continue; + } + + if ( !Q_stricmp( token, "numBlades" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n >= MAX_BLADES ) + { + Com_Error(ERR_DROP, "WP_SaberParseParms: saber %s has illegal number of blades (%d) max: %d", useSaber, n, MAX_BLADES ); + continue; + } + saber->numBlades = n; + continue; + } + + // saberColor + if ( !Q_stricmpn( token, "saberColor", 10 ) ) + { + if (strlen(token)==10) + { + n = -1; + } + else if (strlen(token)==11) + { + n = atoi(&token[10])-1; + if (n > 7 || n < 1 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberColor '%s' in %s\n", token, useSaber ); +#endif + continue; + } + } + else + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberColor '%s' in %s\n", token, useSaber ); +#endif + continue; + } + + if ( COM_ParseString( &p, &value ) ) //read the color + { + continue; + } + + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same color by default + saber_colors_t color = TranslateSaberColor( value ); + for ( n = 0; n < MAX_BLADES; n++ ) + { + saber->blade[n].color = color; + } + } else + { + saber->blade[n].color = TranslateSaberColor( value ); + } + continue; + } + + //saber length + if ( !Q_stricmpn( token, "saberLength", 11 ) ) + { + if (strlen(token)==11) + { + n = -1; + } + else if (strlen(token)==12) + { + n = atoi(&token[11])-1; + if (n > 7 || n < 1 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberLength '%s' in %s\n", token, useSaber ); +#endif + continue; + } + } + else + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberLength '%s' in %s\n", token, useSaber ); +#endif + continue; + } + + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same length by default + for ( n = 0; n < MAX_BLADES; n++ ) + { + saber->blade[n].lengthMax = f; + } + } + else + { + saber->blade[n].lengthMax = f; + } + continue; + } + + //blade radius + if ( !Q_stricmpn( token, "saberRadius", 11 ) ) + { + if (strlen(token)==11) + { + n = -1; + } + else if (strlen(token)==12) + { + n = atoi(&token[11])-1; + if (n > 7 || n < 1 ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberRadius '%s' in %s\n", token, useSaber ); +#endif + continue; + } + } + else + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberRadius '%s' in %s\n", token, useSaber ); +#endif + continue; + } + + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same length by default + for ( n = 0; n < MAX_BLADES; n++ ) + { + saber->blade[n].radius = f; + } + } + else + { + saber->blade[n].radius = f; + } + continue; + } + + //locked saber style + if ( !Q_stricmp( token, "saberStyle" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->style = TranslateSaberStyle( value ); + continue; + } + + //maxChain + if ( !Q_stricmp( token, "maxChain" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->maxChain = n; + continue; + } + + //lockable + if ( !Q_stricmp( token, "lockable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->lockable = ((qboolean)(n!=0)); + continue; + } + + //throwable + if ( !Q_stricmp( token, "throwable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->throwable = ((qboolean)(n!=0)); + continue; + } + + //disarmable + if ( !Q_stricmp( token, "disarmable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->disarmable = ((qboolean)(n!=0)); + continue; + } + + //active blocking + if ( !Q_stricmp( token, "blocking" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->activeBlocking = ((qboolean)(n!=0)); + continue; + } + + //twoHanded + if ( !Q_stricmp( token, "twoHanded" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->twoHanded = ((qboolean)(n!=0)); + continue; + } + + //force power restrictions + if ( !Q_stricmp( token, "forceRestrict" ) ) + { + int fp; + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + fp = GetIDForString( FPTable, value ); + if ( fp >= FP_FIRST && fp < NUM_FORCE_POWERS ) + { + saber->forceRestrictions |= (1<lockBonus = n; + continue; + } + + //parryBonus + if ( !Q_stricmp( token, "parryBonus" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->parryBonus = n; + continue; + } + + //breakParryBonus + if ( !Q_stricmp( token, "breakParryBonus" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->breakParryBonus = n; + continue; + } + + //disarmBonus + if ( !Q_stricmp( token, "disarmBonus" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->disarmBonus = n; + continue; + } + + //single blade saber style + if ( !Q_stricmp( token, "singleBladeStyle" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->singleBladeStyle = TranslateSaberStyle( value ); + continue; + } + + //single blade throwable + if ( !Q_stricmp( token, "singleBladeThrowable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->singleBladeThrowable = ((qboolean)(n!=0)); + continue; + } + + //broken replacement saber1 (right hand) + if ( !Q_stricmp( token, "brokenSaber1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //saber->brokenSaber1 = G_NewString( value ); + continue; + } + + //broken replacement saber2 (left hand) + if ( !Q_stricmp( token, "brokenSaber2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //saber->brokenSaber2 = G_NewString( value ); + continue; + } + + //spins and does damage on return from saberthrow + if ( !Q_stricmp( token, "returnDamage" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->returnDamage = ((qboolean)(n!=0)); + continue; + } + + //stops the saber from drawing marks on the world (good for real-sword type mods) + if ( !Q_stricmp( token, "noWallMarks" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->noWallMarks = ((qboolean)(n!=0)); + continue; + } + + //stops the saber from drawing a dynamic light (good for real-sword type mods) + if ( !Q_stricmp( token, "noDlight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->noDlight = ((qboolean)(n!=0)); + continue; + } + + //stops the saber from drawing a blade (good for real-sword type mods) + if ( !Q_stricmp( token, "noBlade" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->noBlade = ((qboolean)(n!=0)); + continue; + } + + //default (0) is normal, 1 is a motion blur and 2 is no trail at all (good for real-sword type mods) + if ( !Q_stricmp( token, "trailStyle" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->trailStyle = n; + continue; + } + + //if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + if ( !Q_stricmp( token, "g2MarksShader" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + SkipRestOfLine( &p ); + continue; + } +#ifdef QAGAME//cgame-only cares about this +#elif defined CGAME + saber->g2MarksShader = trap_R_RegisterShader( value ); +#endif + continue; + } + + //if non-zero, uses damage done to calculate an appropriate amount of knockback + if ( !Q_stricmp( token, "knockbackScale" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->knockbackScale = f; + continue; + } + + //scale up or down the damage done by the saber + if ( !Q_stricmp( token, "damageScale" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->damageScale = f; + continue; + } + + //if non-zero, the saber never does dismemberment (good for pointed/blunt melee weapons) + if ( !Q_stricmp( token, "noDismemberment" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->noDismemberment = ((qboolean)(n!=0)); + continue; + } + + //if non-zero, the saber will not do damage or any effects when it is idle (not in an attack anim). (good for real-sword type mods) + if ( !Q_stricmp( token, "noIdleEffect" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->noIdleEffect = ((qboolean)(n!=0)); + continue; + } + + //spin sound (when thrown) + if ( !Q_stricmp( token, "spinSound" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->spinSound = BG_SoundIndex( (char *)value ); + continue; + } + + //swing sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "swingSound1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->swingSound[0] = BG_SoundIndex( (char *)value ); + continue; + } + + //swing sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "swingSound2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->swingSound[1] = BG_SoundIndex( (char *)value ); + continue; + } + + //swing sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "swingSound3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->swingSound[2] = BG_SoundIndex( (char *)value ); + continue; + } + + //hit sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "hitSound1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->hitSound[0] = BG_SoundIndex( (char *)value ); + continue; + } + + //hit sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "hitSound2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->hitSound[1] = BG_SoundIndex( (char *)value ); + continue; + } + + //hit sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "hitSound3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->hitSound[2] = BG_SoundIndex( (char *)value ); + continue; + } + + //block sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "blockSound1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->blockSound[0] = BG_SoundIndex( (char *)value ); + continue; + } + + //block sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "blockSound2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->blockSound[1] = BG_SoundIndex( (char *)value ); + continue; + } + + //block sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "blockSound3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->blockSound[2] = BG_SoundIndex( (char *)value ); + continue; + } + + //block effect - when saber/sword hits another saber/sword + if ( !Q_stricmp( token, "blockEffect" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this +#elif defined CGAME + saber->blockEffect = trap_FX_RegisterEffect( (char *)value ); +#endif + continue; + } + + //hit person effect - when saber/sword hits a person + if ( !Q_stricmp( token, "hitPersonEffect" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this +#elif defined CGAME + saber->hitPersonEffect = trap_FX_RegisterEffect( (char *)value ); +#endif + continue; + } + + //hit other effect - when saber/sword hits sopmething else damagable + if ( !Q_stricmp( token, "hitOtherEffect" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this +#elif defined CGAME + saber->hitOtherEffect = trap_FX_RegisterEffect( (char *)value ); +#endif + continue; + } + + //if non-zero, the saber will not do the big, white clash flare with other sabers + if ( !Q_stricmp( token, "noClashFlare" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->noClashFlare = ((qboolean)(n!=0)); + continue; + } + + if ( !Q_stricmp( token, "notInMP" ) ) + {//ignore this + SkipRestOfLine( &p ); + continue; + } + //FIXME: saber sounds (on, off, loop) + +#ifdef _DEBUG + Com_Printf( "WARNING: unknown keyword '%s' while parsing '%s'\n", token, useSaber ); +#endif + SkipRestOfLine( &p ); + } + + //FIXME: precache the saberModel(s)? + + return qtrue; +} + +qboolean WP_SaberParseParm( const char *saberName, const char *parmname, char *saberData ) +{ + const char *token; + const char *value; + const char *p; + + if ( !saberName || !saberName[0] ) + { + return qfalse; + } + + //try to parse it out + p = SaberParms; + COM_BeginParseSession("saberinfo"); + + // look for the right saber + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return qfalse; + } + + if ( !Q_stricmp( token, saberName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { + return qfalse; + } + + if ( BG_ParseLiteral( &p, "{" ) ) + { + return qfalse; + } + + // parse the saber info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", saberName ); + return qfalse; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + if ( !Q_stricmp( token, parmname ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + strcpy( saberData, value ); + return qtrue; + } + + SkipRestOfLine( &p ); + continue; + } + + return qfalse; +} + +qboolean WP_SaberValidForPlayerInMP( const char *saberName ) +{ + char allowed [8]={0}; + if ( !WP_SaberParseParm( saberName, "notInMP", allowed ) ) + {//not defined, default is yes + return qtrue; + } + if ( !allowed[0] ) + {//not defined, default is yes + return qtrue; + } + else + {//return value + return ((qboolean)(atoi(allowed)==0)); + } +} + +void WP_RemoveSaber( saberInfo_t *sabers, int saberNum ) +{ + if ( !sabers ) + { + return; + } + //reset everything for this saber just in case + WP_SaberSetDefaults( &sabers[saberNum] ); + + strcpy(sabers[saberNum].name, "none"); + sabers[saberNum].model[0] = 0; + + //ent->client->ps.dualSabers = qfalse; + BG_SI_Deactivate(&sabers[saberNum]); + BG_SI_SetLength(&sabers[saberNum], 0.0f); +// if ( ent->weaponModel[saberNum] > 0 ) +// { +// gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[saberNum] ); +// ent->weaponModel[saberNum] = -1; +// } +// if ( saberNum == 1 ) +// { +// ent->client->ps.dualSabers = qfalse; +// } +} + +void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName ) +{ + if ( !sabers ) + { + return; + } + if ( Q_stricmp( "none", saberName ) == 0 || Q_stricmp( "remove", saberName ) == 0 ) + { + if (saberNum != 0) + { //can't remove saber 0 ever + WP_RemoveSaber( sabers, saberNum ); + } + return; + } + + if ( entNum < MAX_CLIENTS && + !WP_SaberValidForPlayerInMP( saberName ) ) + { + WP_SaberParseParms( "Kyle", &sabers[saberNum] );//get saber info + } + else + { + WP_SaberParseParms( saberName, &sabers[saberNum] );//get saber info + } + if (sabers[1].twoHanded) + {//not allowed to use a 2-handed saber as second saber + WP_RemoveSaber( sabers, 1 ); + return; + } + else if (sabers[0].twoHanded && + sabers[1].model[0]) + { //you can't use a two-handed saber with a second saber, so remove saber 2 + WP_RemoveSaber( sabers, 1 ); + return; + } +} + +void WP_SaberSetColor( saberInfo_t *sabers, int saberNum, int bladeNum, char *colorName ) +{ + if ( !sabers ) + { + return; + } + sabers[saberNum].blade[bladeNum].color = TranslateSaberColor( colorName ); +} + +//static char bgSaberParseTBuffer[MAX_SABER_DATA_SIZE]; +#include "../namespace_end.h" +extern void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int iAlign); +extern void Z_Free( void *pvAddress ); +#include "../namespace_begin.h" + +void WP_SaberLoadParms( void ) +{ + int len, totallen, saberExtFNLen, mainBlockLen, fileCnt, i; + //const char *filename = "ext_data/sabers.cfg"; + char *holdChar, *marker; + char saberExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + char *bgSaberParseTBuffer = (char *) Z_Malloc( MAX_SABER_DATA_SIZE, TAG_TEMP_WORKSPACE, qfalse, 4 ); + + len = 0; + + //remember where to store the next one + totallen = mainBlockLen = len; + marker = SaberParms+totallen; + *marker = 0; + + //now load in the extra .sab extensions + fileCnt = trap_FS_GetFileList("ext_data/sabers", ".sab", saberExtensionListBuf, sizeof(saberExtensionListBuf) ); + + holdChar = saberExtensionListBuf; + for ( i = 0; i < fileCnt; i++, holdChar += saberExtFNLen + 1 ) + { + saberExtFNLen = strlen( holdChar ); + + len = trap_FS_FOpenFile(va( "ext_data/sabers/%s", holdChar), &f, FS_READ); + + if ( len == -1 ) + { + Com_Printf( "error reading file\n" ); + } + else + { + if ( (totallen + len + 1/*for the endline*/) >= MAX_SABER_DATA_SIZE ) { + Com_Error(ERR_DROP, "Saber extensions (*.sab) are too large" ); + } + + trap_FS_Read(bgSaberParseTBuffer, len, f); + bgSaberParseTBuffer[len] = 0; + + Q_strcat( marker, MAX_SABER_DATA_SIZE-totallen, bgSaberParseTBuffer ); + trap_FS_FCloseFile(f); + + //get around the stupid problem of not having an endline at the bottom + //of a sab file -rww + Q_strcat(marker, MAX_SABER_DATA_SIZE-totallen, "\n"); + len++; + + totallen += len; + marker = SaberParms+totallen; + } + } + Z_Free( bgSaberParseTBuffer ); +} + +/* +rww - +The following were struct functions in SP. Of course +we can't have that in this codebase so I'm having to +externalize them. Which is why this probably seems +structured a bit oddly. But it's to make porting stuff +easier on myself. SI indicates it was under saberinfo, +and BLADE indicates it was under bladeinfo. +*/ + +//--------------------------------------- +void BG_BLADE_ActivateTrail ( bladeInfo_t *blade, float duration ) +{ + blade->trail[ClientManager::ActiveClientNum()].inAction = qtrue; + blade->trail[ClientManager::ActiveClientNum()].duration = duration; +} + +void BG_BLADE_DeactivateTrail ( bladeInfo_t *blade, float duration ) +{ + blade->trail[ClientManager::ActiveClientNum()].inAction = qfalse; + blade->trail[ClientManager::ActiveClientNum()].duration = duration; +} +//--------------------------------------- +void BG_SI_Activate( saberInfo_t *saber ) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + saber->blade[i].active = qtrue; + } +} + +void BG_SI_Deactivate( saberInfo_t *saber ) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + saber->blade[i].active = qfalse; + } +} + +// Description: Activate a specific Blade of this Saber. +// Created: 10/03/02 by Aurelio Reis, Modified: 10/03/02 by Aurelio Reis. +// [in] int iBlade Which Blade to activate. +// [in] bool bActive Whether to activate it (default true), or deactivate it (false). +// [return] void +void BG_SI_BladeActivate( saberInfo_t *saber, int iBlade, qboolean bActive ) +{ + // Validate blade ID/Index. + if ( iBlade < 0 || iBlade >= saber->numBlades ) + return; + + saber->blade[iBlade].active = bActive; +} + +qboolean BG_SI_Active(saberInfo_t *saber) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + if ( saber->blade[i].active ) + { + return qtrue; + } + } + return qfalse; +} + +void BG_SI_SetLength( saberInfo_t *saber, float length ) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + saber->blade[i].length = length; + } +} + +//not in sp, added it for my own convenience +void BG_SI_SetDesiredLength(saberInfo_t *saber, float len, int bladeNum ) +{ + int i, startBlade = 0, maxBlades = saber->numBlades; + + if ( bladeNum >= 0 && bladeNum < saber->numBlades) + {//doing this on a specific blade + startBlade = bladeNum; + maxBlades = bladeNum+1; + } + for (i = startBlade; i < maxBlades; i++) + { + saber->blade[i].desiredLength = len; + } +} + +//also not in sp, added it for my own convenience +void BG_SI_SetLengthGradual(saberInfo_t *saber, int time) +{ + int i; + float amt, dLen; + + for (i = 0; i < saber->numBlades; i++) + { + dLen = saber->blade[i].desiredLength; + + if (dLen == -1) + { //assume we want max blade len + dLen = saber->blade[i].lengthMax; + } + + if (saber->blade[i].length == dLen) + { + continue; + } + + if (saber->blade[i].length == saber->blade[i].lengthMax || + saber->blade[i].length == 0) + { + saber->blade[i].extendDebounce = time; + if (saber->blade[i].length == 0) + { + saber->blade[i].length++; + } + else + { + saber->blade[i].length--; + } + } + + amt = (time - saber->blade[i].extendDebounce)*0.01; + + if (amt < 0.2f) + { + amt = 0.2f; + } + + if (saber->blade[i].length < dLen) + { + saber->blade[i].length += amt; + + if (saber->blade[i].length > dLen) + { + saber->blade[i].length = dLen; + } + if (saber->blade[i].length > saber->blade[i].lengthMax) + { + saber->blade[i].length = saber->blade[i].lengthMax; + } + } + else if (saber->blade[i].length > dLen) + { + saber->blade[i].length -= amt; + + if (saber->blade[i].length < dLen) + { + saber->blade[i].length = dLen; + } + if (saber->blade[i].length < 0) + { + saber->blade[i].length = 0; + } + } + } +} + +float BG_SI_Length(saberInfo_t *saber) +{//return largest length + int len1 = 0; + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + if ( saber->blade[i].length > len1 ) + { + len1 = saber->blade[i].length; + } + } + return len1; +} + +float BG_SI_LengthMax(saberInfo_t *saber) +{ + int len1 = 0; + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + if ( saber->blade[i].lengthMax > len1 ) + { + len1 = saber->blade[i].lengthMax; + } + } + return len1; +} + +void BG_SI_ActivateTrail ( saberInfo_t *saber, float duration ) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + //saber->blade[i].ActivateTrail( duration ); + BG_BLADE_ActivateTrail(&saber->blade[i], duration); + } +} + +void BG_SI_DeactivateTrail ( saberInfo_t *saber, float duration ) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + //saber->blade[i].DeactivateTrail( duration ); + BG_BLADE_DeactivateTrail(&saber->blade[i], duration); + } +} + +#include "../namespace_end.h" diff --git a/codemp/game/bg_saga.c b/codemp/game/bg_saga.c new file mode 100644 index 0000000..02d5a65 --- /dev/null +++ b/codemp/game/bg_saga.c @@ -0,0 +1,1510 @@ +// Copyright (C) 2000-2002 Raven Software, Inc. +// +/***************************************************************************** + * name: bg_saga.c + * + * desc: Siege module, shared for game, cgame, and ui. + * + * $Author: osman $ + * $Revision: 1.9 $ + * + *****************************************************************************/ +#include "q_shared.h" +#include "bg_saga.h" +#include "bg_weapons.h" +#include "bg_public.h" + +#define SIEGECHAR_TAB 9 //perhaps a bit hacky, but I don't think there's any define existing for "tab" + +// New - only make one copy of this shit. +#ifdef QAGAME +siegeClass_t bgSiegeClasses[MAX_SIEGE_CLASSES]; +int bgNumSiegeClasses = 0; + +siegeTeam_t bgSiegeTeams[MAX_SIEGE_TEAMS]; +int bgNumSiegeTeams = 0; +#endif + +//Could use strap stuff but I don't particularly care at the moment anyway. +#include "../namespace_begin.h" + +extern int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +extern void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +extern void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +extern void trap_FS_FCloseFile( fileHandle_t f ); +extern int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); + +#ifndef QAGAME //cgame, ui +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); +#endif + +char siege_info[MAX_SIEGE_INFO_SIZE]; +int siege_valid = 0; + +siegeTeam_t *team1Theme = NULL; +siegeTeam_t *team2Theme = NULL; + +//class flags +stringID_table_t bgSiegeClassFlagNames[] = +{ + ENUM2STRING(CFL_MORESABERDMG), + ENUM2STRING(CFL_STRONGAGAINSTPHYSICAL), + ENUM2STRING(CFL_FASTFORCEREGEN), + ENUM2STRING(CFL_STATVIEWER), + ENUM2STRING(CFL_HEAVYMELEE), + ENUM2STRING(CFL_SINGLE_ROCKET), + ENUM2STRING(CFL_CUSTOMSKEL), + ENUM2STRING(CFL_EXTRA_AMMO), + "", -1 +}; + +//saber stances +stringID_table_t StanceTable[] = +{ + ENUM2STRING(SS_NONE), + ENUM2STRING(SS_FAST), + ENUM2STRING(SS_MEDIUM), + ENUM2STRING(SS_STRONG), + ENUM2STRING(SS_DESANN), + ENUM2STRING(SS_TAVION), + ENUM2STRING(SS_DUAL), + ENUM2STRING(SS_STAFF), + "", 0 +}; + +//Weapon and force power tables are also used in NPC parsing code and some other places. +stringID_table_t WPTable[] = +{ + "NULL",WP_NONE, + // Player weapons + ENUM2STRING(WP_STUN_BATON), + ENUM2STRING(WP_MELEE), + ENUM2STRING(WP_SABER), + ENUM2STRING(WP_BRYAR_PISTOL), + "WP_BLASTER_PISTOL", WP_BRYAR_PISTOL, + ENUM2STRING(WP_BLASTER), + ENUM2STRING(WP_DISRUPTOR), + ENUM2STRING(WP_BOWCASTER), + ENUM2STRING(WP_REPEATER), + ENUM2STRING(WP_DEMP2), + ENUM2STRING(WP_FLECHETTE), + ENUM2STRING(WP_ROCKET_LAUNCHER), + ENUM2STRING(WP_THERMAL), + ENUM2STRING(WP_TRIP_MINE), + ENUM2STRING(WP_DET_PACK), + ENUM2STRING(WP_CONCUSSION), + ENUM2STRING(WP_BRYAR_OLD), + ENUM2STRING(WP_EMPLACED_GUN), + ENUM2STRING(WP_TURRET), + "", 0 +}; + +stringID_table_t FPTable[] = +{ + ENUM2STRING(FP_HEAL), + ENUM2STRING(FP_LEVITATION), + ENUM2STRING(FP_SPEED), + ENUM2STRING(FP_PUSH), + ENUM2STRING(FP_PULL), + ENUM2STRING(FP_TELEPATHY), + ENUM2STRING(FP_GRIP), + ENUM2STRING(FP_LIGHTNING), + ENUM2STRING(FP_RAGE), + ENUM2STRING(FP_PROTECT), + ENUM2STRING(FP_ABSORB), + ENUM2STRING(FP_TEAM_HEAL), + ENUM2STRING(FP_TEAM_FORCE), + ENUM2STRING(FP_DRAIN), + ENUM2STRING(FP_SEE), + ENUM2STRING(FP_SABER_OFFENSE), + ENUM2STRING(FP_SABER_DEFENSE), + ENUM2STRING(FP_SABERTHROW), + "", -1 +}; + +stringID_table_t HoldableTable[] = +{ + ENUM2STRING(HI_NONE), + + ENUM2STRING(HI_SEEKER), + ENUM2STRING(HI_SHIELD), + ENUM2STRING(HI_MEDPAC), + ENUM2STRING(HI_MEDPAC_BIG), + ENUM2STRING(HI_BINOCULARS), + ENUM2STRING(HI_SENTRY_GUN), + ENUM2STRING(HI_JETPACK), + ENUM2STRING(HI_HEALTHDISP), + ENUM2STRING(HI_AMMODISP), + ENUM2STRING(HI_EWEB), + ENUM2STRING(HI_CLOAK), + + "", -1 +}; + +stringID_table_t PowerupTable[] = +{ + ENUM2STRING(PW_NONE), + ENUM2STRING(PW_QUAD), + ENUM2STRING(PW_BATTLESUIT), + ENUM2STRING(PW_PULL), + ENUM2STRING(PW_REDFLAG), + ENUM2STRING(PW_BLUEFLAG), + ENUM2STRING(PW_NEUTRALFLAG), + ENUM2STRING(PW_SHIELDHIT), + ENUM2STRING(PW_SPEEDBURST), + ENUM2STRING(PW_DISINT_4), + ENUM2STRING(PW_SPEED), + ENUM2STRING(PW_CLOAKED), + ENUM2STRING(PW_FORCE_ENLIGHTENED_LIGHT), + ENUM2STRING(PW_FORCE_ENLIGHTENED_DARK), + ENUM2STRING(PW_FORCE_BOON), + ENUM2STRING(PW_YSALAMIRI), + + "", -1 +}; + + +//====================================== +//Parsing functions +//====================================== +void BG_SiegeStripTabs(char *buf) +{ + int i = 0; + int i_r = 0; + + while (buf[i]) + { + if (buf[i] != SIEGECHAR_TAB) + { //not a tab, just stick it in + buf[i_r] = buf[i]; + } + else + { //If it's a tab, convert it to a space. + buf[i_r] = ' '; + } + + i_r++; + i++; + } + + buf[i_r] = '\0'; +} + +int BG_SiegeGetValueGroup(char *buf, char *group, char *outbuf) +{ + int i = 0; + int j; + char checkGroup[4096]; + qboolean isGroup; + int parseGroups = 0; + + while (buf[i]) + { + if (buf[i] != ' ' && buf[i] != '{' && buf[i] != '}' && buf[i] != '\n' && buf[i] != '\r' && buf[i] != SIEGECHAR_TAB) + { //we're on a valid character + if (buf[i] == '/' && + buf[i+1] == '/') + { //this is a comment, so skip over it + while (buf[i] && buf[i] != '\n' && buf[i] != '\r' && buf[i] != SIEGECHAR_TAB) + { + i++; + } + } + else + { //parse to the next space/endline/eos and check this value against our group value. + j = 0; + + while (buf[i] != ' ' && buf[i] != '\n' && buf[i] != '\r' && buf[i] != SIEGECHAR_TAB && buf[i] != '{' && buf[i]) + { + if (buf[i] == '/' && buf[i+1] == '/') + { //hit a comment, break out. + break; + } + + checkGroup[j] = buf[i]; + j++; + i++; + } + checkGroup[j] = 0; + + //Make sure this is a group as opposed to a globally defined value. + if (buf[i] == '/' && buf[i+1] == '/') + { //stopped on a comment, so first parse to the end of it. + while (buf[i] && buf[i] != '\n' && buf[i] != '\r') + { + i++; + } + while (buf[i] == '\n' || buf[i] == '\r') + { + i++; + } + } + + if (!buf[i]) + { + Com_Error(ERR_DROP, "Unexpected EOF while looking for group '%s'", group); + } + + isGroup = qfalse; + + while (buf[i] && buf[i] == ' ' || buf[i] == SIEGECHAR_TAB || buf[i] == '\n' || buf[i] == '\r') + { //parse to the next valid character + i++; + } + + if (buf[i] == '{') + { //if the next valid character is an opening bracket, then this is indeed a group + isGroup = qtrue; + } + + //Is this the one we want? + if (isGroup && !Q_stricmp(checkGroup, group)) + { //guess so. Parse until we hit the { indicating the beginning of the group. + while (buf[i] != '{' && buf[i]) + { + i++; + } + + if (buf[i]) + { //We're at the start of the group now, so parse to the closing bracket. + j = 0; + + parseGroups = 0; + + while ((buf[i] != '}' || parseGroups) && buf[i]) + { + if (buf[i] == '{') + { //increment for the opening bracket. + parseGroups++; + } + else if (buf[i] == '}') + { //decrement for the closing bracket + parseGroups--; + } + + if (parseGroups < 0) + { //Syntax error, I guess. + Com_Error(ERR_DROP, "Found a closing bracket without an opening bracket while looking for group '%s'", group); + } + + if ((buf[i] != '{' || parseGroups > 1) && + (buf[i] != '}' || parseGroups > 0)) + { //don't put the start and end brackets for this group into the output buffer + outbuf[j] = buf[i]; + j++; + } + + if (buf[i] == '}' && !parseGroups) + { //Alright, we can break out now. + break; + } + + i++; + } + outbuf[j] = 0; + + //Verify that we ended up on the closing bracket. + if (buf[i] != '}') + { + Com_Error(ERR_DROP, "Group '%s' is missing a closing bracket", group); + } + + //Strip the tabs so we're friendly for value parsing. + BG_SiegeStripTabs(outbuf); + + return 1; //we got it, so return 1. + } + else + { + Com_Error(ERR_DROP, "Error parsing group in file, unexpected EOF before opening bracket while looking for group '%s'", group); + } + } + else if (!isGroup) + { //if it wasn't a group, parse to the end of the line + while (buf[i] && buf[i] != '\n' && buf[i] != '\r') + { + i++; + } + } + else + { //this was a group but we not the one we wanted to find, so parse by it. + parseGroups = 0; + + while (buf[i] && (buf[i] != '}' || parseGroups)) + { + if (buf[i] == '{') + { + parseGroups++; + } + else if (buf[i] == '}') + { + parseGroups--; + } + + if (parseGroups < 0) + { //Syntax error, I guess. + Com_Error(ERR_DROP, "Found a closing bracket without an opening bracket while looking for group '%s'", group); + } + + if (buf[i] == '}' && !parseGroups) + { //Alright, we can break out now. + break; + } + + i++; + } + + if (buf[i] != '}') + { + Com_Error(ERR_DROP, "Found an opening bracket without a matching closing bracket while looking for group '%s'", group); + } + + i++; + } + } + } + else if (buf[i] == '{') + { //we're in a group that isn't the one we want, so parse to the end. + parseGroups = 0; + + while (buf[i] && (buf[i] != '}' || parseGroups)) + { + if (buf[i] == '{') + { + parseGroups++; + } + else if (buf[i] == '}') + { + parseGroups--; + } + + if (parseGroups < 0) + { //Syntax error, I guess. + Com_Error(ERR_DROP, "Found a closing bracket without an opening bracket while looking for group '%s'", group); + } + + if (buf[i] == '}' && !parseGroups) + { //Alright, we can break out now. + break; + } + + i++; + } + + if (buf[i] != '}') + { + Com_Error(ERR_DROP, "Found an opening bracket without a matching closing bracket while looking for group '%s'", group); + } + } + + if (!buf[i]) + { + break; + } + i++; + } + + return 0; //guess we never found it. +} + +int BG_SiegeGetPairedValue(char *buf, char *key, char *outbuf) +{ + int i = 0; + int j; + int k; + char checkKey[4096]; + + while (buf[i]) + { + if (buf[i] != ' ' && buf[i] != '{' && buf[i] != '}' && buf[i] != '\n' && buf[i] != '\r') + { //we're on a valid character + if (buf[i] == '/' && + buf[i+1] == '/') + { //this is a comment, so skip over it + while (buf[i] && buf[i] != '\n' && buf[i] != '\r') + { + i++; + } + } + else + { //parse to the next space/endline/eos and check this value against our key value. + j = 0; + + while (buf[i] != ' ' && buf[i] != '\n' && buf[i] != '\r' && buf[i] != SIEGECHAR_TAB && buf[i]) + { + if (buf[i] == '/' && buf[i+1] == '/') + { //hit a comment, break out. + break; + } + + checkKey[j] = buf[i]; + j++; + i++; + } + checkKey[j] = 0; + + k = i; + + while (buf[k] && (buf[k] == ' ' || buf[k] == '\n' || buf[k] == '\r')) + { + k++; + } + + if (buf[k] == '{') + { //this is not the start of a value but rather of a group. We don't want to look in subgroups so skip over the whole thing. + int openB = 0; + + while (buf[i] && (buf[i] != '}' || openB)) + { + if (buf[i] == '{') + { + openB++; + } + else if (buf[i] == '}') + { + openB--; + } + + if (openB < 0) + { + Com_Error(ERR_DROP, "Unexpected closing bracket (too many) while parsing to end of group '%s'", checkKey); + } + + if (buf[i] == '}' && !openB) + { //this is the end of the group + break; + } + i++; + } + + if (buf[i] == '}') + { + i++; + } + } + else + { + //Is this the one we want? + if (buf[i] != '/' || buf[i+1] != '/') + { //make sure we didn't stop on a comment, if we did then this is considered an error in the file. + if (!Q_stricmp(checkKey, key)) + { //guess so. Parse along to the next valid character, then put that into the output buffer and return 1. + while ((buf[i] == ' ' || buf[i] == '\n' || buf[i] == '\r' || buf[i] == SIEGECHAR_TAB) && buf[i]) + { + i++; + } + + if (buf[i]) + { //We're at the start of the value now. + qboolean parseToQuote = qfalse; + + if (buf[i] == '\"') + { //if the value is in quotes, then stop at the next quote instead of ' ' + i++; + parseToQuote = qtrue; + } + + j = 0; + while ( ((!parseToQuote && buf[i] != ' ' && buf[i] != '\n' && buf[i] != '\r') || (parseToQuote && buf[i] != '\"')) ) + { + if (buf[i] == '/' && + buf[i+1] == '/') + { //hit a comment after the value? This isn't an ideal way to be writing things, but we'll support it anyway. + break; + } + outbuf[j] = buf[i]; + j++; + i++; + + if (!buf[i]) + { + if (parseToQuote) + { + Com_Error(ERR_DROP, "Unexpected EOF while looking for endquote, error finding paired value for '%s'", key); + } + else + { + Com_Error(ERR_DROP, "Unexpected EOF while looking for space or endline, error finding paired value for '%s'", key); + } + } + } + outbuf[j] = 0; + + return 1; //we got it, so return 1. + } + else + { + Com_Error(ERR_DROP, "Error parsing file, unexpected EOF while looking for valud '%s'", key); + } + } + else + { //if that wasn't the desired key, then make sure we parse to the end of the line, so we don't mistake a value for a key + while (buf[i] && buf[i] != '\n') + { + i++; + } + } + } + else + { + Com_Error(ERR_DROP, "Error parsing file, found comment, expected value for '%s'", key); + } + } + } + } + + if (!buf[i]) + { + break; + } + i++; + } + + return 0; //guess we never found it. +} +//====================================== +//End parsing functions +//====================================== + + +//====================================== +//Class loading functions +//====================================== +void BG_SiegeTranslateForcePowers(char *buf, siegeClass_t *siegeClass) +{ + char checkPower[1024]; + char checkLevel[256]; + int l = 0; + int k = 0; + int j = 0; + int i = 0; + int parsedLevel = 0; + qboolean allPowers = qfalse; + qboolean noPowers = qfalse; + + if (!Q_stricmp(buf, "FP_ALL")) + { //this is a special case, just give us all the powers on level 3 + allPowers = qtrue; + } + + if (buf[0] == '0' && !buf[1]) + { //no powers then + noPowers = qtrue; + } + + //First clear out the powers, or in the allPowers case, give us all level 3. + while (i < NUM_FORCE_POWERS) + { + if (allPowers) + { + siegeClass->forcePowerLevels[i] = FORCE_LEVEL_3; + } + else + { + siegeClass->forcePowerLevels[i] = 0; + } + i++; + } + + if (allPowers || noPowers) + { //we're done now then. + return; + } + + i = 0; + while (buf[i]) + { //parse through the list which is seperated by |, and add all the weapons into a bitflag + if (buf[i] != ' ' && buf[i] != '|') + { + j = 0; + + while (buf[i] && buf[i] != ' ' && buf[i] != '|' && buf[i] != ',') + { + checkPower[j] = buf[i]; + j++; + i++; + } + checkPower[j] = 0; + + if (buf[i] == ',') + { //parse the power level + i++; + l = 0; + while (buf[i] && buf[i] != ' ' && buf[i] != '|') + { + checkLevel[l] = buf[i]; + l++; + i++; + } + checkLevel[l] = 0; + parsedLevel = atoi(checkLevel); + + //keep sane limits on the powers + if (parsedLevel < 0) + { + parsedLevel = 0; + } + if (parsedLevel > FORCE_LEVEL_5) + { + parsedLevel = FORCE_LEVEL_5; + } + } + else + { //if it's not there, assume level 3 I guess. + parsedLevel = 3; + } + + if (checkPower[0]) + { //Got the name, compare it against the weapon table strings. + k = 0; + + if (!Q_stricmp(checkPower, "FP_JUMP")) + { //haqery + strcpy(checkPower, "FP_LEVITATION"); + } + + while (FPTable[k].id != -1 && FPTable[k].name[0]) + { + if (!Q_stricmp(checkPower, FPTable[k].name)) + { //found it, add the weapon into the weapons value + siegeClass->forcePowerLevels[k] = parsedLevel; + break; + } + k++; + } + } + } + + if (!buf[i]) + { + break; + } + i++; + } +} + +//Used for the majority of generic val parsing stuff. buf should be the value string, +//table should be the appropriate string/id table. If bitflag is qtrue then the +//values are accumulated into a bitflag. If bitflag is qfalse then the first value +//is returned as a directly corresponding id and no further parsing is done. +int BG_SiegeTranslateGenericTable(char *buf, stringID_table_t *table, qboolean bitflag) +{ + int items = 0; + char checkItem[1024]; + int i = 0; + int j = 0; + int k = 0; + + if (buf[0] == '0' && !buf[1]) + { //special case, no items. + return 0; + } + + while (buf[i]) + { //Using basically the same parsing method as we do for weapons and forcepowers. + if (buf[i] != ' ' && buf[i] != '|') + { + j = 0; + + while (buf[i] && buf[i] != ' ' && buf[i] != '|') + { + checkItem[j] = buf[i]; + j++; + i++; + } + checkItem[j] = 0; + + if (checkItem[0]) + { + k = 0; + + while (table[k].name && table[k].name[0]) + { //go through the list and check the parsed flag name against the hardcoded names + if (!Q_stricmp(checkItem, table[k].name)) + { //Got it, so add the value into our items value. + if (bitflag) + { + items |= (1 << table[k].id); + } + else + { //return the value directly then. + return table[k].id; + } + break; + } + k++; + } + } + } + + if (!buf[i]) + { + break; + } + + i++; + } + return items; +} + +char *classTitles[SPC_MAX] = +{ +"infantry", // SPC_INFANTRY +"vanguard", // SPC_VANGUARD +"support", // SPC_SUPPORT +"jedi_general", // SPC_JEDI +"demolitionist", // SPC_DEMOLITIONIST +"heavy_weapons", // SPC_HEAVY_WEAPONS +}; + + +void BG_SiegeParseClassFile(const char *filename, siegeClassDesc_t *descBuffer) +{ + fileHandle_t f; + int len; + int i; + char classInfo[4096]; + char parseBuf[4096]; + + len = trap_FS_FOpenFile(filename, &f, FS_READ); + + if (!f || len >= 4096) + { + return; + } + + trap_FS_Read(classInfo, len, f); + + trap_FS_FCloseFile(f); + + classInfo[len] = 0; + + //first get the description if we have a buffer for it + if (descBuffer) + { + if (!BG_SiegeGetPairedValue(classInfo, "description", descBuffer->desc)) + { + strcpy(descBuffer->desc, "DESCRIPTION UNAVAILABLE"); + } + + //Hit this assert? Memory has already been trashed. Increase + //SIEGE_CLASS_DESC_LEN. + assert(strlen(descBuffer->desc) < SIEGE_CLASS_DESC_LEN); + } + + BG_SiegeGetValueGroup(classInfo, "ClassInfo", classInfo); + + //Parse name + if (BG_SiegeGetPairedValue(classInfo, "name", parseBuf)) + { + strcpy(bgSiegeClasses[bgNumSiegeClasses].name, parseBuf); + } + else + { + Com_Error(ERR_DROP, "Siege class without name entry"); + } + + //Parse forced model + if (BG_SiegeGetPairedValue(classInfo, "model", parseBuf)) + { + strcpy(bgSiegeClasses[bgNumSiegeClasses].forcedModel, parseBuf); + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].forcedModel[0] = 0; + } + + //Parse forced skin + if (BG_SiegeGetPairedValue(classInfo, "skin", parseBuf)) + { + strcpy(bgSiegeClasses[bgNumSiegeClasses].forcedSkin, parseBuf); + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].forcedSkin[0] = 0; + } + + //Parse first saber + if (BG_SiegeGetPairedValue(classInfo, "saber1", parseBuf)) + { + strcpy(bgSiegeClasses[bgNumSiegeClasses].saber1, parseBuf); + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].saber1[0] = 0; + } + + //Parse second saber + if (BG_SiegeGetPairedValue(classInfo, "saber2", parseBuf)) + { + strcpy(bgSiegeClasses[bgNumSiegeClasses].saber2, parseBuf); + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].saber2[0] = 0; + } + + //Parse forced saber stance + if (BG_SiegeGetPairedValue(classInfo, "saberstyle", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].saberStance = BG_SiegeTranslateGenericTable(parseBuf, StanceTable, qtrue); + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].saberStance = 0; + } + + //Parse forced saber color + if (BG_SiegeGetPairedValue(classInfo, "sabercolor", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].forcedSaberColor = atoi(parseBuf); + bgSiegeClasses[bgNumSiegeClasses].hasForcedSaberColor = qtrue; + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].hasForcedSaberColor = qfalse; + } + + //Parse forced saber2 color + if (BG_SiegeGetPairedValue(classInfo, "saber2color", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].forcedSaber2Color = atoi(parseBuf); + bgSiegeClasses[bgNumSiegeClasses].hasForcedSaber2Color = qtrue; + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].hasForcedSaber2Color = qfalse; + } + + //Parse weapons + if (BG_SiegeGetPairedValue(classInfo, "weapons", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].weapons = BG_SiegeTranslateGenericTable(parseBuf, WPTable, qtrue); + } + else + { + Com_Error(ERR_DROP, "Siege class without weapons entry"); + } + + if (!(bgSiegeClasses[bgNumSiegeClasses].weapons & (1 << WP_SABER))) + { //make sure it has melee if there's no saber + bgSiegeClasses[bgNumSiegeClasses].weapons |= (1 << WP_MELEE); + + //always give them this too if they are not a saber user + //bgSiegeClasses[bgNumSiegeClasses].weapons |= (1 << WP_BRYAR_PISTOL); + } + + //Parse forcepowers + if (BG_SiegeGetPairedValue(classInfo, "forcepowers", parseBuf)) + { + BG_SiegeTranslateForcePowers(parseBuf, &bgSiegeClasses[bgNumSiegeClasses]); + } + else + { //fine, clear out the powers. + i = 0; + while (i < NUM_FORCE_POWERS) + { + bgSiegeClasses[bgNumSiegeClasses].forcePowerLevels[i] = 0; + i++; + } + } + + //Parse classflags + if (BG_SiegeGetPairedValue(classInfo, "classflags", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].classflags = BG_SiegeTranslateGenericTable(parseBuf, bgSiegeClassFlagNames, qtrue); + } + else + { //fine, we'll 0 it. + bgSiegeClasses[bgNumSiegeClasses].classflags = 0; + } + + //Parse maxhealth + if (BG_SiegeGetPairedValue(classInfo, "maxhealth", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].maxhealth = atoi(parseBuf); + } + else + { //It's alright, just default to 100 then. + bgSiegeClasses[bgNumSiegeClasses].maxhealth = 100; + } + + //Parse starthealth + if (BG_SiegeGetPairedValue(classInfo, "starthealth", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].starthealth = atoi(parseBuf); + } + else + { //It's alright, just default to 100 then. + bgSiegeClasses[bgNumSiegeClasses].starthealth = 100; + } + + + //Parse startarmor + if (BG_SiegeGetPairedValue(classInfo, "maxarmor", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].maxarmor = atoi(parseBuf); + } + else + { //It's alright, just default to 0 then. + bgSiegeClasses[bgNumSiegeClasses].maxarmor = 0; + } + + //Parse startarmor + if (BG_SiegeGetPairedValue(classInfo, "startarmor", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].startarmor = atoi(parseBuf); + if (!bgSiegeClasses[bgNumSiegeClasses].maxarmor) + { //if they didn't specify a damn max armor then use this. + bgSiegeClasses[bgNumSiegeClasses].maxarmor = bgSiegeClasses[bgNumSiegeClasses].startarmor; + } + } + else + { //default to maxarmor. + bgSiegeClasses[bgNumSiegeClasses].startarmor = bgSiegeClasses[bgNumSiegeClasses].maxarmor; + } + + //Parse speed (this is a multiplier value) + if (BG_SiegeGetPairedValue(classInfo, "speed", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].speed = atof(parseBuf); + } + else + { //It's alright, just default to 1 then. + bgSiegeClasses[bgNumSiegeClasses].speed = 1.0f; + } + + //Parse shader for ui to use + if (BG_SiegeGetPairedValue(classInfo, "uishader", parseBuf)) + { +#ifdef QAGAME +// bgSiegeClasses[bgNumSiegeClasses].uiPortraitShader = 0; +// memset(bgSiegeClasses[bgNumSiegeClasses].uiPortrait,0,sizeof(bgSiegeClasses[bgNumSiegeClasses].uiPortrait)); +#elif defined CGAME +// bgSiegeClasses[bgNumSiegeClasses].uiPortraitShader = 0; +// memset(bgSiegeClasses[bgNumSiegeClasses].uiPortrait,0,sizeof(bgSiegeClasses[bgNumSiegeClasses].uiPortrait)); +#else //ui + bgSiegeClasses[bgNumSiegeClasses].uiPortraitShader = trap_R_RegisterShaderNoMip(parseBuf); + memcpy(bgSiegeClasses[bgNumSiegeClasses].uiPortrait,parseBuf,sizeof(bgSiegeClasses[bgNumSiegeClasses].uiPortrait)); +#endif + } + else + { //I guess this is an essential.. we don't want to render bad shaders or anything. + Com_Error(ERR_DROP, "Siege class without uishader entry"); + } + + //Parse shader for ui to use + if (BG_SiegeGetPairedValue(classInfo, "class_shader", parseBuf)) + { +#ifdef QAGAME +// bgSiegeClasses[bgNumSiegeClasses].classShader = 0; +#else //cgame, ui + bgSiegeClasses[bgNumSiegeClasses].classShader = trap_R_RegisterShaderNoMip(parseBuf); + assert( bgSiegeClasses[bgNumSiegeClasses].classShader ); + if ( !bgSiegeClasses[bgNumSiegeClasses].classShader ) + { + //Com_Error( ERR_DROP, "ERROR: could not find class_shader %s for class %s\n", parseBuf, bgSiegeClasses[bgNumSiegeClasses].name ); + Com_Printf( "ERROR: could not find class_shader %s for class %s\n", parseBuf, bgSiegeClasses[bgNumSiegeClasses].name ); + } + // A very hacky way to determine class . . . + else +#endif + { + // Find the base player class based on the icon name - very bad, I know. + int titleLength,i,arrayTitleLength; + char *holdBuf; + + titleLength = strlen(parseBuf); + for (i=0;ititleLength) // Too long + { + break; + } + + holdBuf = parseBuf + ( titleLength - arrayTitleLength); + if (!strcmp(holdBuf,classTitles[i])) + { + bgSiegeClasses[bgNumSiegeClasses].playerClass = i; + break; + } + } + + // In case the icon name doesn't match up + if (i>=SPC_MAX) + { + bgSiegeClasses[bgNumSiegeClasses].playerClass = SPC_INFANTRY; + } + } + } + else + { //No entry! Bad bad bad + //Com_Error( ERR_DROP, "ERROR: no class_shader defined for class %s\n", bgSiegeClasses[bgNumSiegeClasses].name ); + Com_Printf( "ERROR: no class_shader defined for class %s\n", bgSiegeClasses[bgNumSiegeClasses].name ); + } + + //Parse holdable items to use + if (BG_SiegeGetPairedValue(classInfo, "holdables", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].invenItems = BG_SiegeTranslateGenericTable(parseBuf, HoldableTable, qtrue); + } + else + { //Just don't start out with any then. + bgSiegeClasses[bgNumSiegeClasses].invenItems = 0; + } + + //Parse powerups to use + if (BG_SiegeGetPairedValue(classInfo, "powerups", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].powerups = BG_SiegeTranslateGenericTable(parseBuf, PowerupTable, qtrue); + } + else + { //Just don't start out with any then. + bgSiegeClasses[bgNumSiegeClasses].powerups = 0; + } + + //A successful read. + bgNumSiegeClasses++; +} + +// Count the number of like base classes +int BG_SiegeCountBaseClass(const int team, const short classIndex) +{ + int count = 0,i; + siegeTeam_t *stm; + + stm = BG_SiegeFindThemeForTeam(team); + if (!stm) + { + return(0); + + } + + for (i=0;inumClasses;i++) + { + + if (stm->classes[i]->playerClass == classIndex) + { + count++; + } + } + return(count); +} + +char *BG_GetUIPortraitFile(const int team, const short classIndex, const short cntIndex) +{ + int count = 0,i; + siegeTeam_t *stm; + + stm = BG_SiegeFindThemeForTeam(team); + if (!stm) + { + return(0); + + } + + // Loop through all the classes for this team + for (i=0;inumClasses;i++) + { + // does it match the base class? + if (stm->classes[i]->playerClass == classIndex) + { + if (count==cntIndex) + { + return(stm->classes[i]->uiPortrait); + } + ++count; + } + } + + return(0); +} + +int BG_GetUIPortrait(const int team, const short classIndex, const short cntIndex) +{ + int count = 0,i; + siegeTeam_t *stm; + + stm = BG_SiegeFindThemeForTeam(team); + if (!stm) + { + return(0); + + } + + // Loop through all the classes for this team + for (i=0;inumClasses;i++) + { + // does it match the base class? + if (stm->classes[i]->playerClass == classIndex) + { + if (count==cntIndex) + { + return(stm->classes[i]->uiPortraitShader); + } + ++count; + } + } + + return(0); +} + +// This is really getting ugly - looking to get the base class (within a class) based on the index passed in +siegeClass_t *BG_GetClassOnBaseClass(const int team, const short classIndex, const short cntIndex) +{ + int count = 0,i; + siegeTeam_t *stm; + + stm = BG_SiegeFindThemeForTeam(team); + if (!stm) + { + return(0); + } + + // Loop through all the classes for this team + for (i=0;inumClasses;i++) + { + // does it match the base class? + if (stm->classes[i]->playerClass == classIndex) + { + if (count==cntIndex) + { + return(stm->classes[i]); + } + ++count; + } + } + + return(0); +} + +void BG_SiegeLoadClasses(siegeClassDesc_t *descBuffer) +{ + int numFiles; + int filelen; + char filelist[4096]; + char filename[MAX_QPATH]; + char* fileptr; + int i; + + bgNumSiegeClasses = 0; + + numFiles = trap_FS_GetFileList("ext_data/Siege/Classes", ".scl", filelist, 4096 ); + fileptr = filelist; + + for (i = 0; i < numFiles; i++, fileptr += filelen+1) + { + filelen = strlen(fileptr); + strcpy(filename, "ext_data/Siege/Classes/"); + strcat(filename, fileptr); + + if (descBuffer) + { + BG_SiegeParseClassFile(filename, &descBuffer[i]); + } + else + { + BG_SiegeParseClassFile(filename, NULL); + } + } +} +//====================================== +//End class loading functions +//====================================== + + +//====================================== +//Team loading functions +//====================================== +siegeClass_t *BG_SiegeFindClassByName(const char *classname) +{ + int i = 0; + + while (i < bgNumSiegeClasses) + { + if (!Q_stricmp(bgSiegeClasses[i].name, classname)) + { //found it + return &bgSiegeClasses[i]; + } + i++; + } + + return NULL; +} + +void BG_SiegeParseTeamFile(const char *filename) +{ + fileHandle_t f; + int len; + char teamInfo[2048]; + char parseBuf[1024]; + char lookString[256]; + int i = 1; + qboolean success = qtrue; + + len = trap_FS_FOpenFile(filename, &f, FS_READ); + + if (!f || len >= 2048) + { + return; + } + + trap_FS_Read(teamInfo, len, f); + + trap_FS_FCloseFile(f); + + teamInfo[len] = 0; + + if (BG_SiegeGetPairedValue(teamInfo, "name", parseBuf)) + { + strcpy(bgSiegeTeams[bgNumSiegeTeams].name, parseBuf); + } + else + { + Com_Error(ERR_DROP, "Siege team with no name definition"); + } + +//I don't entirely like doing things this way but it's the easiest way. +#ifdef CGAME + if (BG_SiegeGetPairedValue(teamInfo, "FriendlyShader", parseBuf)) + { + bgSiegeTeams[bgNumSiegeTeams].friendlyShader = trap_R_RegisterShaderNoMip(parseBuf); + } +#else + bgSiegeTeams[bgNumSiegeTeams].friendlyShader = 0; +#endif + + bgSiegeTeams[bgNumSiegeTeams].numClasses = 0; + + if (BG_SiegeGetValueGroup(teamInfo, "Classes", teamInfo)) + { + while (success && i < MAX_SIEGE_CLASSES) + { //keep checking for group values named class# up to MAX_SIEGE_CLASSES until we can't find one. + strcpy(lookString, va("class%i", i)); + + success = BG_SiegeGetPairedValue(teamInfo, lookString, parseBuf); + + if (!success) + { + break; + } + + bgSiegeTeams[bgNumSiegeTeams].classes[bgSiegeTeams[bgNumSiegeTeams].numClasses] = BG_SiegeFindClassByName(parseBuf); + + if (!bgSiegeTeams[bgNumSiegeTeams].classes[bgSiegeTeams[bgNumSiegeTeams].numClasses]) + { + Com_Error(ERR_DROP, "Invalid class specified: '%s'", parseBuf); + } + + bgSiegeTeams[bgNumSiegeTeams].numClasses++; + + i++; + } + } + + if (!bgSiegeTeams[bgNumSiegeTeams].numClasses) + { + Com_Error(ERR_DROP, "Team defined with no allowable classes\n"); + } + + //If we get here then it was a success, so increment the team number + bgNumSiegeTeams++; +} + +void BG_SiegeLoadTeams(void) +{ + int numFiles; + int filelen; + char filelist[4096]; + char filename[MAX_QPATH]; + char* fileptr; + int i; + + bgNumSiegeTeams = 0; + + numFiles = trap_FS_GetFileList("ext_data/Siege/Teams", ".team", filelist, 4096 ); + fileptr = filelist; + + for (i = 0; i < numFiles; i++, fileptr += filelen+1) + { + filelen = strlen(fileptr); + strcpy(filename, "ext_data/Siege/Teams/"); + strcat(filename, fileptr); + BG_SiegeParseTeamFile(filename); + } +} +//====================================== +//End team loading functions +//====================================== + + +//====================================== +//Misc/utility functions +//====================================== +siegeTeam_t *BG_SiegeFindThemeForTeam(int team) +{ + if (team == SIEGETEAM_TEAM1) + { + return team1Theme; + } + else if (team == SIEGETEAM_TEAM2) + { + return team2Theme; + } + + return NULL; +} + +#ifndef UI_EXPORTS //only for game/cgame +//precache all the sabers for the active classes for the team +extern qboolean WP_SaberParseParms( const char *SaberName, saberInfo_t *saber ); //bg_saberLoad.cpp +extern int BG_ModelCache(const char *modelName, const char *skinName); //bg_misc.c + +void BG_PrecacheSabersForSiegeTeam(int team) +{ + siegeTeam_t *t; + saberInfo_t saber; + char *saberName; + int sNum; + + t = BG_SiegeFindThemeForTeam(team); + + if (t) + { + int i = 0; + + while (i < t->numClasses) + { + sNum = 0; + + while (sNum < MAX_SABERS) + { + switch (sNum) + { + case 0: + saberName = &t->classes[i]->saber1[0]; + break; + case 1: + saberName = &t->classes[i]->saber2[0]; + break; + default: + saberName = NULL; + break; + } + + if (saberName && saberName[0]) + { + WP_SaberParseParms(saberName, &saber); + if (!Q_stricmp(saberName, saber.name)) + { //found the matching saber + if (saber.model[0]) + { + BG_ModelCache(saber.model, NULL); + } + } + } + + sNum++; + } + + i++; + } + } +} +#endif + +qboolean BG_SiegeCheckClassLegality(int team, char *classname) +{ + siegeTeam_t **teamPtr = NULL; + int i = 0; + + if (team == SIEGETEAM_TEAM1) + { + teamPtr = &team1Theme; + } + else if (team == SIEGETEAM_TEAM2) + { + teamPtr = &team2Theme; + } + else + { //spectator? Whatever, you're legal then. + return qtrue; + } + + if (!teamPtr || !(*teamPtr)) + { //Well, guess the class is ok, seeing as there is no team theme to begin with. + return qtrue; + } + + //See if the class is listed on the team + while (i < (*teamPtr)->numClasses) + { + if (!Q_stricmp(classname, (*teamPtr)->classes[i]->name)) + { //found it, so it's alright + return qtrue; + } + i++; + } + + //Didn't find it, so copy the name of the first valid class over it. + strcpy(classname, (*teamPtr)->classes[0]->name); + + return qfalse; +} + +siegeTeam_t *BG_SiegeFindTeamForTheme(char *themeName) +{ + int i = 0; + + while (i < bgNumSiegeTeams) + { + if (bgSiegeTeams[i].name && + !Q_stricmp(bgSiegeTeams[i].name, themeName)) + { //this is what we're looking for + return &bgSiegeTeams[i]; + } + + i++; + } + + return NULL; +} + +void BG_SiegeSetTeamTheme(int team, char *themeName) +{ + siegeTeam_t **teamPtr = NULL; + + if (team == SIEGETEAM_TEAM1) + { + teamPtr = &team1Theme; + } + else + { + teamPtr = &team2Theme; + } + + (*teamPtr) = BG_SiegeFindTeamForTheme(themeName); +} + +int BG_SiegeFindClassIndexByName(const char *classname) +{ + int i = 0; + + while (i < bgNumSiegeClasses) + { + if (!Q_stricmp(bgSiegeClasses[i].name, classname)) + { //found it + return i; + } + i++; + } + + return -1; +} +//====================================== +//End misc/utility functions +//====================================== + +#include "../namespace_end.h" diff --git a/codemp/game/bg_saga.h b/codemp/game/bg_saga.h new file mode 100644 index 0000000..220844f --- /dev/null +++ b/codemp/game/bg_saga.h @@ -0,0 +1,116 @@ +#define MAX_SIEGE_INFO_SIZE 16384 + +#define SIEGETEAM_TEAM1 1 //e.g. TEAM_RED +#define SIEGETEAM_TEAM2 2 //e.g. TEAM_BLUE + +#define SIEGE_POINTS_OBJECTIVECOMPLETED 20 +#define SIEGE_POINTS_FINALOBJECTIVECOMPLETED 30 +#define SIEGE_POINTS_TEAMWONROUND 10 + +#define SIEGE_ROUND_BEGIN_TIME 5000 //delay 5 secs after players are in game. + +//#define MAX_SIEGE_CLASSES 128 //up to 128 classes +#define MAX_SIEGE_CLASSES 64 //up to 128 classes +#define MAX_SIEGE_CLASSES_PER_TEAM 16 + +#define MAX_SIEGE_TEAMS 16 //up to 16 diffent teams + +#define MAX_EXDATA_ENTS_TO_SEND MAX_CLIENTS //max number of extended data for ents to send + +// The basic siege player classes +typedef enum +{ + SPC_INFANTRY = 0, + SPC_VANGUARD, + SPC_SUPPORT, + SPC_JEDI, + SPC_DEMOLITIONIST, + SPC_HEAVY_WEAPONS, + SPC_MAX +} siegePlayerClassFlags_t; + +typedef enum +{ + CFL_MORESABERDMG = 0, + CFL_STRONGAGAINSTPHYSICAL, + CFL_FASTFORCEREGEN, + CFL_STATVIEWER, + CFL_HEAVYMELEE, + CFL_SINGLE_ROCKET,//has only 1 rocket to use with rocketlauncher + CFL_CUSTOMSKEL, //class uses a custom skeleton, be sure to load on server etc + CFL_EXTRA_AMMO +} siegeClassFlags_t; + + +#ifdef _XBOX +#define SIEGE_CLASS_DESC_LEN 512 +#else +#define SIEGE_CLASS_DESC_LEN 4096 +#endif +typedef struct +{ + char desc[SIEGE_CLASS_DESC_LEN]; +} siegeClassDesc_t; + +typedef struct +{ + char name[512]; + char forcedModel[256]; + char forcedSkin[256]; + char saber1[64]; + char saber2[64]; + int saberStance; + int weapons; + int forcePowerLevels[NUM_FORCE_POWERS]; + int classflags; + int maxhealth; + int starthealth; + int maxarmor; + int startarmor; + float speed; + qboolean hasForcedSaberColor; + int forcedSaberColor; + qboolean hasForcedSaber2Color; + int forcedSaber2Color; + int invenItems; + int powerups; + int uiPortraitShader; + char uiPortrait[256]; + int classShader; + short playerClass; // SPC_INFANTRY . .. +} siegeClass_t; + +typedef struct +{ + char name[512]; + siegeClass_t *classes[MAX_SIEGE_CLASSES_PER_TEAM]; + int numClasses; + int friendlyShader; +} siegeTeam_t; + +extern siegeClass_t bgSiegeClasses[MAX_SIEGE_CLASSES]; +extern int bgNumSiegeClasses; + +extern siegeTeam_t bgSiegeTeams[MAX_SIEGE_TEAMS]; +extern int bgNumSiegeTeams; + +#include "../namespace_begin.h" + +int BG_SiegeGetValueGroup(char *buf, char *group, char *outbuf); +int BG_SiegeGetPairedValue(char *buf, char *key, char *outbuf); +void BG_SiegeStripTabs(char *buf); + +void BG_SiegeLoadClasses(siegeClassDesc_t *descBuffer); +void BG_SiegeLoadTeams(void); + +siegeTeam_t *BG_SiegeFindThemeForTeam(int team); +void BG_PrecacheSabersForSiegeTeam(int team); +siegeClass_t *BG_SiegeFindClassByName(const char *classname); +qboolean BG_SiegeCheckClassLegality(int team, char *classname); +void BG_SiegeSetTeamTheme(int team, char *themeName); +int BG_SiegeFindClassIndexByName(const char *classname); + +extern char siege_info[MAX_SIEGE_INFO_SIZE]; +extern int siege_valid; + +#include "../namespace_end.h" diff --git a/codemp/game/bg_slidemove.c b/codemp/game/bg_slidemove.c new file mode 100644 index 0000000..a93c91e --- /dev/null +++ b/codemp/game/bg_slidemove.c @@ -0,0 +1,1059 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_slidemove.c -- part of bg_pmove functionality + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +#ifdef QAGAME //yeah, this is kind of bad +#include "g_local.h" +#endif + +/* + +input: origin, velocity, bounds, groundPlane, trace function + +output: origin, velocity, impacts, stairup boolean + +*/ + + +//do vehicle impact stuff +// slight rearrangement by BTO (VV) so that we only have one namespace include +#ifdef QAGAME +extern void G_FlyVehicleSurfaceDestruction(gentity_t *veh, trace_t *trace, int magnitude, qboolean force ); //g_vehicle.c +extern qboolean G_CanBeEnemy(gentity_t *self, gentity_t *enemy); //w_saber.c +extern void Client_CheckImpactBBrush( gentity_t *self, gentity_t *other ); +#endif + +extern qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ); + +#include "../namespace_begin.h" + +extern bgEntity_t *pm_entSelf; +extern bgEntity_t *pm_entVeh; + +//vehicle impact stuff continued... +#ifndef QAGAME //kind of hacky +extern void trap_FX_PlayEffectID( int id, vec3_t org, vec3_t fwd, int vol, int rad ); +#endif + +#ifdef QAGAME +extern qboolean FighterIsLanded( Vehicle_t *pVeh, playerState_t *parentPS ); +#endif + +extern void PM_SetPMViewAngle(playerState_t *ps, vec3_t angle, usercmd_t *ucmd); + +#define MAX_IMPACT_TURN_ANGLE 45.0f +void PM_VehicleImpact(bgEntity_t *pEnt, trace_t *trace) +{ + // See if the vehicle has crashed into the ground. + Vehicle_t *pSelfVeh = pEnt->m_pVehicle; + float magnitude = VectorLength( pm->ps->velocity ) * pSelfVeh->m_pVehicleInfo->mass / 50.0f; + qboolean forceSurfDestruction = qfalse; +#ifdef QAGAME + gentity_t *hitEnt = trace!=NULL?&g_entities[trace->entityNum]:NULL; + + if (!hitEnt || + (pSelfVeh && pSelfVeh->m_pPilot && + hitEnt && hitEnt->s.eType == ET_MISSILE && hitEnt->inuse && + hitEnt->r.ownerNum == pSelfVeh->m_pPilot->s.number) + ) + { + return; + } + + if ( pSelfVeh//I have a vehicle struct + && pSelfVeh->m_iRemovedSurfaces )//vehicle has bits removed + {//spiralling to our deaths, explode on any solid impact + if ( hitEnt->s.NPC_class == CLASS_VEHICLE ) + {//hit another vehicle, explode! + //Give credit to whoever got me into this death spiral state + gentity_t *parent = (gentity_t *)pSelfVeh->m_pParentEntity; + gentity_t *killer = NULL; + if (parent->client->ps.otherKiller < ENTITYNUM_WORLD && + parent->client->ps.otherKillerTime > level.time) + { + gentity_t *potentialKiller = &g_entities[parent->client->ps.otherKiller]; + + if (potentialKiller->inuse && potentialKiller->client) + { //he's valid I guess + killer = potentialKiller; + } + } + //FIXME: damage hitEnt, some, too? Our explosion should hurt them some, but... + G_Damage( (gentity_t *)pEnt, killer, killer, NULL, pm->ps->origin, 999999, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT + return; + } + else if ( !VectorCompare( trace->plane.normal, vec3_origin ) + && (trace->entityNum == ENTITYNUM_WORLD || hitEnt->r.bmodel ) ) + {//have a valid hit plane and we hit a solid brush + vec3_t moveDir; + float impactDot; + VectorCopy( pm->ps->velocity, moveDir ); + VectorNormalize( moveDir ); + impactDot = DotProduct( moveDir, trace->plane.normal ); + if ( impactDot <= -0.7f )//hit rather head-on and hard + {// Just DIE now + //Give credit to whoever got me into this death spiral state + gentity_t *parent = (gentity_t *)pSelfVeh->m_pParentEntity; + gentity_t *killer = NULL; + if (parent->client->ps.otherKiller < ENTITYNUM_WORLD && + parent->client->ps.otherKillerTime > level.time) + { + gentity_t *potentialKiller = &g_entities[parent->client->ps.otherKiller]; + + if (potentialKiller->inuse && potentialKiller->client) + { //he's valid I guess + killer = potentialKiller; + } + } + G_Damage( (gentity_t *)pEnt, killer, killer, NULL, pm->ps->origin, 999999, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT + return; + } + } + } + + if ( trace->entityNum < ENTITYNUM_WORLD + && hitEnt->s.eType == ET_MOVER + && hitEnt->s.apos.trType != TR_STATIONARY//rotating + && (hitEnt->spawnflags&16) //IMPACT + && Q_stricmp( "func_rotating", hitEnt->classname ) == 0 ) + {//hit a func_rotating that is supposed to destroy anything it touches! + //guarantee the hit will happen, thereby taking off a piece of the ship + forceSurfDestruction = qtrue; + } + else if ( (fabs(pm->ps->velocity[0])+fabs(pm->ps->velocity[1])) < 100.0f + && pm->ps->velocity[2] > -100.0f ) +#else + if ( (fabs(pm->ps->velocity[0])+fabs(pm->ps->velocity[1])) < 100.0f + && pm->ps->velocity[2] > -100.0f ) +#endif + /* + if ( (pSelfVeh->m_ulFlags&VEH_GEARSOPEN) + && trace->plane.normal[2] > 0.7f + && fabs(pSelfVeh->m_vOrientation[PITCH]) < 0.2f + && fabs(pSelfVeh->m_vOrientation[ROLL]) < 0.2f )*/ + {//we're landing, we're cool + //FIXME: some sort of landing "thump", not the impactFX + /* + if ( pSelfVeh->m_pVehicleInfo->iImpactFX ) + { + vec3_t up = {0,0,1}; +#ifdef QAGAME + G_PlayEffectID( pSelfVeh->m_pVehicleInfo->iImpactFX, pm->ps->origin, up ); +#else + trap_FX_PlayEffectID( pSelfVeh->m_pVehicleInfo->iImpactFX, pm->ps->origin, up, -1, -1 ); +#endif + } + */ + //this was annoying me -rww + //FIXME: this shouldn't even be getting called when the vehicle is at rest! +#ifdef QAGAME + if (hitEnt && (hitEnt->s.eType == ET_PLAYER || hitEnt->s.eType == ET_NPC) && pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER) + { //always smack players + } + else +#endif + { + return; + } + } + if ( pSelfVeh && + (pSelfVeh->m_pVehicleInfo->type == VH_SPEEDER || pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER) && //this is kind of weird on tauntauns and atst's.. + (magnitude >= 100||forceSurfDestruction) ) + { + if ( pEnt->m_pVehicle->m_iHitDebounce < pm->cmd.serverTime + || forceSurfDestruction ) + {//a bit of a hack, may conflict with getting shot, but... + //FIXME: impact sound and effect should be gotten from g_vehicleInfo...? + //FIXME: should pass in trace.endpos and trace.plane.normal + vec3_t vehUp; +#ifndef QAGAME + bgEntity_t *hitEnt; +#endif + + if ( trace && !pSelfVeh->m_iRemovedSurfaces && !forceSurfDestruction ) + { + qboolean turnFromImpact = qfalse, turnHitEnt = qfalse; + float l = pm->ps->speed*0.5f; + vec3_t bounceDir; +#ifndef QAGAME + bgEntity_t *hitEnt = PM_BGEntForNum(trace->entityNum); +#endif + if ( (trace->entityNum == ENTITYNUM_WORLD || hitEnt->s.solid == SOLID_BMODEL)//bounce off any brush + && !VectorCompare(trace->plane.normal, vec3_origin) )//have a valid plane to bounce off of + { //bounce off in the opposite direction of the impact + if (pSelfVeh->m_pVehicleInfo->type == VH_SPEEDER) + { + pm->ps->speed *= pml.frametime; + VectorCopy(trace->plane.normal, bounceDir); + } + else if ( trace->plane.normal[2] >= MIN_LANDING_SLOPE//flat enough to land on + && pSelfVeh->m_LandTrace.fraction < 1.0f //ground present + && pm->ps->speed <= MIN_LANDING_SPEED ) + {//could land here, don't bounce off, in fact, return altogether! + return; + } + else + { + if (pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER) + { + turnFromImpact = qtrue; + } + VectorCopy(trace->plane.normal, bounceDir); + } + } + else if ( pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER ) + {//check for impact with another fighter +#ifndef QAGAME + bgEntity_t *hitEnt = PM_BGEntForNum(trace->entityNum); +#endif + if ( hitEnt->s.NPC_class == CLASS_VEHICLE + && hitEnt->m_pVehicle + && hitEnt->m_pVehicle->m_pVehicleInfo + && hitEnt->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + {//two vehicles hit each other, turn away from the impact + turnFromImpact = qtrue; + turnHitEnt = qtrue; +#ifndef QAGAME + VectorSubtract( pm->ps->origin, hitEnt->s.origin, bounceDir ); +#else + VectorSubtract( pm->ps->origin, hitEnt->r.currentOrigin, bounceDir ); +#endif + VectorNormalize( bounceDir ); + } + } + if ( turnFromImpact ) + {//bounce off impact surf and turn away + vec3_t pushDir={0}, turnAwayAngles, turnDelta; + float turnStrength, pitchTurnStrength, yawTurnStrength; + vec3_t moveDir; + float bounceDot, turnDivider; + //bounce + if ( !turnHitEnt ) + {//hit wall + VectorScale(bounceDir, (pm->ps->speed*0.25f/pSelfVeh->m_pVehicleInfo->mass), pushDir); + } + else + {//hit another fighter +#ifndef QAGAME + VectorScale( bounceDir, (pm->ps->speed+hitEnt->s.speed)*0.5f, bounceDir ); +#else + if ( hitEnt->client ) + { + VectorScale( bounceDir, (pm->ps->speed+hitEnt->client->ps.speed)*0.5f, pushDir ); + } + else + { + VectorScale( bounceDir, (pm->ps->speed+hitEnt->s.speed)*0.5f, pushDir ); + } +#endif + VectorScale(pushDir, (l/pSelfVeh->m_pVehicleInfo->mass), pushDir); + VectorScale(pushDir, 0.1f, pushDir); + } + VectorNormalize2( pm->ps->velocity, moveDir ); + bounceDot = DotProduct( moveDir, bounceDir )*-1; + if ( bounceDot < 0.1f ) + { + bounceDot = 0.1f; + } + VectorScale( pushDir, bounceDot, pushDir ); + VectorAdd(pm->ps->velocity, pushDir, pm->ps->velocity); + //turn + turnDivider = (pSelfVeh->m_pVehicleInfo->mass/400.0f); + if ( turnHitEnt ) + {//don't turn as much when hit another ship + turnDivider *= 4.0f; + } + if ( turnDivider < 0.5f ) + { + turnDivider = 0.5f; + } + turnStrength = (magnitude/2000.0f); + if ( turnStrength < 0.1f ) + { + turnStrength = 0.1f; + } + else if ( turnStrength > 2.0f ) + { + turnStrength = 2.0f; + } + //get the angles we are going to turn towards + vectoangles( bounceDir, turnAwayAngles ); + //get the delta from our current angles to those new angles + AnglesSubtract( turnAwayAngles, pSelfVeh->m_vOrientation, turnDelta ); + //now do pitch + if ( !bounceDir[2] ) + {//shouldn't be any pitch + } + else + { + pitchTurnStrength = turnStrength*turnDelta[PITCH]; + if ( pitchTurnStrength > MAX_IMPACT_TURN_ANGLE ) + { + pitchTurnStrength = MAX_IMPACT_TURN_ANGLE; + } + else if ( pitchTurnStrength < -MAX_IMPACT_TURN_ANGLE ) + { + pitchTurnStrength = -MAX_IMPACT_TURN_ANGLE; + } + //pSelfVeh->m_vOrientation[PITCH] = AngleNormalize180(pSelfVeh->m_vOrientation[PITCH]+pitchTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + pSelfVeh->m_vFullAngleVelocity[PITCH] = AngleNormalize180(pSelfVeh->m_vOrientation[PITCH]+pitchTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + } + //now do yaw + if ( !bounceDir[0] + && !bounceDir[1] ) + {//shouldn't be any yaw + } + else + { + yawTurnStrength = turnStrength*turnDelta[YAW]; + if ( yawTurnStrength > MAX_IMPACT_TURN_ANGLE ) + { + yawTurnStrength = MAX_IMPACT_TURN_ANGLE; + } + else if ( yawTurnStrength < -MAX_IMPACT_TURN_ANGLE ) + { + yawTurnStrength = -MAX_IMPACT_TURN_ANGLE; + } + //pSelfVeh->m_vOrientation[ROLL] = AngleNormalize180(pSelfVeh->m_vOrientation[ROLL]-yawTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + pSelfVeh->m_vFullAngleVelocity[ROLL] = AngleNormalize180(pSelfVeh->m_vOrientation[ROLL]-yawTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + } + /* + PM_SetPMViewAngle(pm->ps, pSelfVeh->m_vOrientation, &pSelfVeh->m_ucmd); + if ( pm_entVeh ) + {//I'm a vehicle, so pm_entVeh is actually my pilot + bgEntity_t *pilot = pm_entVeh; + if ( !BG_UnrestrainedPitchRoll( pilot->playerState, pSelfVeh ) ) + { + //set the rider's viewangles to the vehicle's viewangles + PM_SetPMViewAngle(pilot->playerState, pSelfVeh->m_vOrientation, &pSelfVeh->m_ucmd); + } + } + */ +#ifdef QAGAME//server-side, turn the guy we hit away from us, too + if ( turnHitEnt//make the other guy turn and get pushed + && hitEnt->client //must be a valid client + && !FighterIsLanded( hitEnt->m_pVehicle, &hitEnt->client->ps )//but not if landed + && !(hitEnt->spawnflags&2) )//and not if suspended + { + l = hitEnt->client->ps.speed; + //now bounce *them* away and turn them + //flip the bounceDir + VectorScale( bounceDir, -1, bounceDir ); + //do bounce + VectorScale( bounceDir, (pm->ps->speed+l)*0.5f, pushDir ); + VectorScale(pushDir, (l*0.5f/hitEnt->m_pVehicle->m_pVehicleInfo->mass), pushDir); + VectorNormalize2( hitEnt->client->ps.velocity, moveDir ); + bounceDot = DotProduct( moveDir, bounceDir )*-1; + if ( bounceDot < 0.1f ) + { + bounceDot = 0.1f; + } + VectorScale( pushDir, bounceDot, pushDir ); + VectorAdd(hitEnt->client->ps.velocity, pushDir, hitEnt->client->ps.velocity); + //turn + turnDivider = (hitEnt->m_pVehicle->m_pVehicleInfo->mass/400.0f); + if ( turnHitEnt ) + {//don't turn as much when hit another ship + turnDivider *= 4.0f; + } + if ( turnDivider < 0.5f ) + { + turnDivider = 0.5f; + } + //get the angles we are going to turn towards + vectoangles( bounceDir, turnAwayAngles ); + //get the delta from our current angles to those new angles + AnglesSubtract( turnAwayAngles, hitEnt->m_pVehicle->m_vOrientation, turnDelta ); + //now do pitch + if ( !bounceDir[2] ) + {//shouldn't be any pitch + } + else + { + pitchTurnStrength = turnStrength*turnDelta[PITCH]; + if ( pitchTurnStrength > MAX_IMPACT_TURN_ANGLE ) + { + pitchTurnStrength = MAX_IMPACT_TURN_ANGLE; + } + else if ( pitchTurnStrength < -MAX_IMPACT_TURN_ANGLE ) + { + pitchTurnStrength = -MAX_IMPACT_TURN_ANGLE; + } + //hitEnt->m_pVehicle->m_vOrientation[PITCH] = AngleNormalize180(hitEnt->m_pVehicle->m_vOrientation[PITCH]+pitchTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + hitEnt->m_pVehicle->m_vFullAngleVelocity[PITCH] = AngleNormalize180(hitEnt->m_pVehicle->m_vOrientation[PITCH]+pitchTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + } + //now do yaw + if ( !bounceDir[0] + && !bounceDir[1] ) + {//shouldn't be any yaw + } + else + { + yawTurnStrength = turnStrength*turnDelta[YAW]; + if ( yawTurnStrength > MAX_IMPACT_TURN_ANGLE ) + { + yawTurnStrength = MAX_IMPACT_TURN_ANGLE; + } + else if ( yawTurnStrength < -MAX_IMPACT_TURN_ANGLE ) + { + yawTurnStrength = -MAX_IMPACT_TURN_ANGLE; + } + //hitEnt->m_pVehicle->m_vOrientation[ROLL] = AngleNormalize180(hitEnt->m_pVehicle->m_vOrientation[ROLL]-yawTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + hitEnt->m_pVehicle->m_vFullAngleVelocity[ROLL] = AngleNormalize180(hitEnt->m_pVehicle->m_vOrientation[ROLL]-yawTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + } + //NOTE: will these angle changes stick or will they be stomped + // when the vehicle goes through its own update and re-grabs + // its angles from its pilot...? Should we do a + // SetClientViewAngles on the pilot? + /* + SetClientViewAngle( hitEnt, hitEnt->m_pVehicle->m_vOrientation ); + if ( hitEnt->m_pVehicle->m_pPilot + && ((gentity_t *)hitEnt->m_pVehicle->m_pPilot)->client ) + { + SetClientViewAngle( (gentity_t *)hitEnt->m_pVehicle->m_pPilot, hitEnt->m_pVehicle->m_vOrientation ); + } + */ + } +#endif + } + } + +#ifdef QAGAME + if (!hitEnt) + { + return; + } + + AngleVectors( pSelfVeh->m_vOrientation, NULL, NULL, vehUp ); + if ( pSelfVeh->m_pVehicleInfo->iImpactFX ) + { + //G_PlayEffectID( pSelfVeh->m_pVehicleInfo->iImpactFX, pm->ps->origin, vehUp ); + //tempent use bad! + G_AddEvent((gentity_t *)pEnt, EV_PLAY_EFFECT_ID, pSelfVeh->m_pVehicleInfo->iImpactFX); + } + pEnt->m_pVehicle->m_iHitDebounce = pm->cmd.serverTime + 200; + magnitude /= pSelfVeh->m_pVehicleInfo->toughness * 50.0f; + + if (hitEnt && (hitEnt->s.eType != ET_TERRAIN || !(hitEnt->spawnflags & 1) || pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER)) + { //don't damage the vehicle from terrain that doesn't want to damage vehicles + if (pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER) + { //increase the damage... + float mult = (pSelfVeh->m_vOrientation[PITCH]*0.1f); + if (mult < 1.0f) + { + mult = 1.0f; + } + if (hitEnt->inuse && hitEnt->takedamage) + { //if the other guy takes damage, don't hurt us a lot for ramming him + //unless it's a vehicle, then we get 1.5 times damage + if (hitEnt->s.eType == ET_NPC && + hitEnt->s.NPC_class == CLASS_VEHICLE && + hitEnt->m_pVehicle) + { + mult = 1.5f; + } + else + { + mult = 0.5f; + } + } + + magnitude *= mult; + } + pSelfVeh->m_iLastImpactDmg = magnitude; + //FIXME: what about proper death credit to the guy who shot you down? + //FIXME: actually damage part of the ship that impacted? + G_Damage( (gentity_t *)pEnt, NULL, NULL, NULL, pm->ps->origin, magnitude*5, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT + + if (pSelfVeh->m_pVehicleInfo->surfDestruction) + { + G_FlyVehicleSurfaceDestruction((gentity_t *)pEnt, trace, magnitude, forceSurfDestruction ); + } + + pSelfVeh->m_ulFlags |= VEH_CRASHING; + } + + if (hitEnt && + hitEnt->inuse && + hitEnt->takedamage) + { //damage this guy because we hit him + float pmult = 1.0f; + int finalD; + gentity_t *attackEnt; + + if ( (hitEnt->s.eType == ET_PLAYER && hitEnt->s.number < MAX_CLIENTS) || + (hitEnt->s.eType == ET_NPC && hitEnt->s.NPC_class != CLASS_VEHICLE) ) + { //probably a humanoid, or something + if (pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER) + { //player die good.. if me fighter + pmult = 2000.0f; + } + else + { + pmult = 40.0f; + } + + if (hitEnt->client && + BG_KnockDownable(&hitEnt->client->ps) && + G_CanBeEnemy((gentity_t *)pEnt, hitEnt)) + { //smash! + if (hitEnt->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN) + { + hitEnt->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + hitEnt->client->ps.forceHandExtendTime = pm->cmd.serverTime + 1100; + hitEnt->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim + } + + hitEnt->client->ps.otherKiller = pEnt->s.number; + hitEnt->client->ps.otherKillerTime = pm->cmd.serverTime + 5000; + hitEnt->client->ps.otherKillerDebounceTime = pm->cmd.serverTime + 100; + + //add my velocity into his to force him along in the correct direction from impact + VectorAdd(hitEnt->client->ps.velocity, pm->ps->velocity, hitEnt->client->ps.velocity); + //upward thrust + hitEnt->client->ps.velocity[2] += 200.0f; + } + } + + if (pSelfVeh->m_pPilot) + { + attackEnt = (gentity_t *)pSelfVeh->m_pPilot; + } + else + { + attackEnt = (gentity_t *)pEnt; + } + + finalD = magnitude*pmult; + if (finalD < 1) + { + finalD = 1; + } + G_Damage( hitEnt, attackEnt, attackEnt, NULL, pm->ps->origin, finalD, 0, MOD_MELEE );//FIXME: MOD_IMPACT + } +#else //this is gonna result in "double effects" for the client doing the prediction. + //it doesn't look bad though. could just use predicted events, but I'm too lazy. + hitEnt = PM_BGEntForNum(trace->entityNum); + + if (!hitEnt || hitEnt->s.owner != pEnt->s.number) + { //don't hit your own missiles! + AngleVectors( pSelfVeh->m_vOrientation, NULL, NULL, vehUp ); + pEnt->m_pVehicle->m_iHitDebounce = pm->cmd.serverTime + 200; + trap_FX_PlayEffectID( pSelfVeh->m_pVehicleInfo->iImpactFX, pm->ps->origin, vehUp, -1, -1 ); + + pSelfVeh->m_ulFlags |= VEH_CRASHING; + } +#endif + } + } +} + +qboolean PM_GroundSlideOkay( float zNormal ) +{ + if ( zNormal > 0 ) + { + if ( pm->ps->velocity[2] > 0 ) + { + if ( pm->ps->legsAnim == BOTH_WALL_RUN_RIGHT + || pm->ps->legsAnim == BOTH_WALL_RUN_LEFT + || pm->ps->legsAnim == BOTH_WALL_RUN_RIGHT_STOP + || pm->ps->legsAnim == BOTH_WALL_RUN_LEFT_STOP + || pm->ps->legsAnim == BOTH_FORCEWALLRUNFLIP_START + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_START + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_ATTACK + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_LAND + || BG_InReboundJump( pm->ps->legsAnim )) + { + return qfalse; + } + } + } + return qtrue; +} + +/* +=============== +qboolean PM_ClientImpact( trace_t *trace, qboolean damageSelf ) + +=============== +*/ +#ifdef QAGAME +qboolean PM_ClientImpact( trace_t *trace ) +{ + //don't try to predict this + gentity_t *traceEnt; + int otherEntityNum = trace->entityNum; + + if ( !pm_entSelf ) + { + return qfalse; + } + + if ( otherEntityNum >= ENTITYNUM_WORLD ) + { + return qfalse; + } + + traceEnt = &g_entities[otherEntityNum]; + + if( VectorLength( pm->ps->velocity ) >= 100 + && pm_entSelf->s.NPC_class != CLASS_VEHICLE + && pm->ps->lastOnGround+100 < level.time ) + //&& pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + Client_CheckImpactBBrush( (gentity_t *)(pm_entSelf), &g_entities[otherEntityNum] ); + } + + if ( !traceEnt + || !(traceEnt->r.contents&pm->tracemask) ) + {//it's dead or not in my way anymore, don't clip against it + return qtrue; + } + + return qfalse; +} +#endif + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 +qboolean PM_SlideMove( qboolean gravity ) { + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t normal, planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + vec3_t clipVelocity; + int i, j, k; + trace_t trace; + vec3_t end; + float time_left; + float into; + vec3_t endVelocity; + vec3_t endClipVelocity; + //qboolean damageSelf = qtrue; + + numbumps = 4; + + VectorCopy (pm->ps->velocity, primal_velocity); + + if ( gravity ) { + VectorCopy( pm->ps->velocity, endVelocity ); + endVelocity[2] -= pm->ps->gravity * pml.frametime; + pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; + primal_velocity[2] = endVelocity[2]; + if ( pml.groundPlane ) { + if ( PM_GroundSlideOkay( pml.groundTrace.plane.normal[2] ) ) + {// slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + } + + time_left = pml.frametime; + + // never turn against the ground plane + if ( pml.groundPlane ) { + numplanes = 1; + VectorCopy( pml.groundTrace.plane.normal, planes[0] ); + if ( !PM_GroundSlideOkay( planes[0][2] ) ) + { + planes[0][2] = 0; + VectorNormalize( planes[0] ); + } + } else { + numplanes = 0; + } + + // never turn against original velocity + VectorNormalize2( pm->ps->velocity, planes[numplanes] ); + numplanes++; + + for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) { + + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // see if we can make it there + pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask); + + if (trace.allsolid) { + // entity is completely trapped in another solid + pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration + return qtrue; + } + + if (trace.fraction > 0) { + // actually covered some distance + VectorCopy (trace.endpos, pm->ps->origin); + } + + if (trace.fraction == 1) { + break; // moved the entire distance + } + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + bgEntity_t *pEnt = pm_entSelf; + + if (pEnt && pEnt->s.eType == ET_NPC && pEnt->s.NPC_class == CLASS_VEHICLE && + pEnt->m_pVehicle) + { //do vehicle impact stuff then + PM_VehicleImpact(pEnt, &trace); + } + } +#ifdef QAGAME + else + { + if ( PM_ClientImpact( &trace ) ) + { + continue; + } + } +#endif + + time_left -= time_left * trace.fraction; + + if (numplanes >= MAX_CLIP_PLANES) { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + VectorCopy( trace.plane.normal, normal ); + + if ( !PM_GroundSlideOkay( normal[2] ) ) + {//wall-running + //never push up off a sloped wall + normal[2] = 0; + VectorNormalize( normal ); + } + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + if ( !(pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//no sliding if stuck to wall! + for ( i = 0 ; i < numplanes ; i++ ) { + if ( VectorCompare( normal, planes[i] ) ) {//DotProduct( normal, planes[i] ) > 0.99 ) { + VectorAdd( normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + if ( i < numplanes ) { + continue; + } + } + VectorCopy (normal, planes[numplanes]); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0 ; i < numplanes ; i++ ) { + into = DotProduct( pm->ps->velocity, planes[i] ); + if ( into >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // see how hard we are hitting things + if ( -into > pml.impactSpeed ) { + pml.impactSpeed = -into; + } + + // slide along the plane + PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); + + // slide along the plane + PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0 ; j < numplanes ; j++ ) { + if ( j == i ) { + continue; + } + if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); + PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + + // see if it goes back into the first clip plane + if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { + continue; + } + + // slide the original velocity along the crease + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, pm->ps->velocity ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the the new move enters + for ( k = 0 ; k < numplanes ; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a triple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if ( gravity ) { + VectorCopy( endVelocity, pm->ps->velocity ); + } + + // don't change velocity if in a timer (FIXME: is this correct?) + if ( pm->ps->pm_time ) { + VectorCopy( primal_velocity, pm->ps->velocity ); + } + + return ( bumpcount != 0 ); +} + +/* +================== +PM_StepSlideMove + +================== +*/ +void PM_StepSlideMove( qboolean gravity ) { + vec3_t start_o, start_v; + vec3_t down_o, down_v; + trace_t trace; +// float down_dist, up_dist; +// vec3_t delta, delta2; + vec3_t up, down; + float stepSize; + qboolean isGiant = qfalse; + bgEntity_t *pEnt; + qboolean skipStep = qfalse; + + VectorCopy (pm->ps->origin, start_o); + VectorCopy (pm->ps->velocity, start_v); + + if ( BG_InReboundHold( pm->ps->legsAnim ) ) + { + gravity = qfalse; + } + + if ( PM_SlideMove( gravity ) == 0 ) { + return; // we got exactly where we wanted to go first try + } + + pEnt = pm_entSelf; + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + if (pEnt && pEnt->s.NPC_class == CLASS_VEHICLE && + pEnt->m_pVehicle && pEnt->m_pVehicle->m_pVehicleInfo->hoverHeight > 0) + { + return; + } + } + + VectorCopy(start_o, down); + down[2] -= STEPSIZE; + pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + VectorSet(up, 0, 0, 1); + // never step up when you still have up velocity + if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || + DotProduct(trace.plane.normal, up) < 0.7)) + { + return; + } + + VectorCopy (pm->ps->origin, down_o); + VectorCopy (pm->ps->velocity, down_v); + + VectorCopy (start_o, up); + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + // apply ground friction, even if on ladder + if (pEnt && + pEnt->s.NPC_class == CLASS_ATST || + (pEnt->s.NPC_class == CLASS_VEHICLE && + pEnt->m_pVehicle && + pEnt->m_pVehicle->m_pVehicleInfo->type == VH_WALKER) + ) + {//AT-STs can step high + up[2] += 66.0f; + isGiant = qtrue; + } + else if ( pEnt && pEnt->s.NPC_class == CLASS_RANCOR ) + {//also can step up high + up[2] += 64.0f; + isGiant = qtrue; + } + else + { + up[2] += STEPSIZE; + } + } + else + { + up[2] += STEPSIZE; + } + + // test the player position if they were a stepheight higher + pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); + if ( trace.allsolid ) { + if ( pm->debugLevel ) { + Com_Printf("%i:bend can't step\n", c_pmove); + } + return; // can't step up + } + + stepSize = trace.endpos[2] - start_o[2]; + // try slidemove from this position + VectorCopy (trace.endpos, pm->ps->origin); + VectorCopy (start_v, pm->ps->velocity); + + PM_SlideMove( gravity ); + + // push down the final amount + VectorCopy (pm->ps->origin, down); + down[2] -= stepSize; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + + if ( pm->stepSlideFix ) + { + if ( pm->ps->clientNum < MAX_CLIENTS + && trace.plane.normal[2] < MIN_WALK_NORMAL ) + {//normal players cannot step up slopes that are too steep to walk on! + vec3_t stepVec; + //okay, the step up ends on a slope that it too steep to step up onto, + //BUT: + //If the step looks like this: + // (B)\__ + // \_____(A) + //Then it might still be okay, so we figure out the slope of the entire move + //from (A) to (B) and if that slope is walk-upabble, then it's okay + VectorSubtract( trace.endpos, down_o, stepVec ); + VectorNormalize( stepVec ); + if ( stepVec[2] > (1.0f-MIN_WALK_NORMAL) ) + { + skipStep = qtrue; + } + } + } + + if ( !trace.allsolid + && !skipStep ) //normal players cannot step up slopes that are too steep to walk on! + { + if ( pm->ps->clientNum >= MAX_CLIENTS//NPC + && isGiant + && trace.entityNum < MAX_CLIENTS + && pEnt + && pEnt->s.NPC_class == CLASS_RANCOR ) + {//Rancor don't step on clients + if ( pm->stepSlideFix ) + { + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + } + else + { + VectorCopy (start_o, pm->ps->origin); + VectorCopy (start_v, pm->ps->velocity); + } + } + else + { + VectorCopy (trace.endpos, pm->ps->origin); + if ( pm->stepSlideFix ) + { + if ( trace.fraction < 1.0 ) { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + } + } + } + else + { + if ( pm->stepSlideFix ) + { + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + } + } + if ( !pm->stepSlideFix ) + { + if ( trace.fraction < 1.0 ) { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + } + +#if 0 + // if the down trace can trace back to the original position directly, don't step + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, start_o, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 ) { + // use the original move + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + if ( pm->debugLevel ) { + Com_Printf("%i:bend\n", c_pmove); + } + } else +#endif + { + // use the step move + float delta; + + delta = pm->ps->origin[2] - start_o[2]; + if ( delta > 2 ) { + if ( delta < 7 ) { + PM_AddEvent( EV_STEP_4 ); + } else if ( delta < 11 ) { + PM_AddEvent( EV_STEP_8 ); + } else if ( delta < 15 ) { + PM_AddEvent( EV_STEP_12 ); + } else { + PM_AddEvent( EV_STEP_16 ); + } + } + if ( pm->debugLevel ) { + Com_Printf("%i:stepped\n", c_pmove); + } + } +} + +#include "../namespace_end.h" + diff --git a/codemp/game/bg_strap.h b/codemp/game/bg_strap.h new file mode 100644 index 0000000..932ee2a --- /dev/null +++ b/codemp/game/bg_strap.h @@ -0,0 +1,38 @@ +//rww - shared trap call system +#include "q_shared.h" +#include "bg_public.h" + +#include "../namespace_begin.h" + +qboolean strap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); + +qboolean strap_G2API_GetBoltMatrix_NoReconstruct(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); + +qboolean strap_G2API_GetBoltMatrix_NoRecNoRot(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); + +qboolean strap_G2API_SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ); + +qboolean strap_G2API_SetBoneAnim(void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame , const int blendTime ); + +qboolean strap_G2API_GetBoneAnim(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList, const int modelIndex); + +void strap_G2API_SetRagDoll(void *ghoul2, sharedRagDollParams_t *params); + +void strap_G2API_AnimateG2Models(void *ghoul2, int time, sharedRagDollUpdateParams_t *params); + +qboolean strap_G2API_SetBoneIKState(void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params); + +qboolean strap_G2API_IKMove(void *ghoul2, int time, sharedIKMoveParams_t *params); + +void strap_TrueMalloc(void **ptr, int size); + +void strap_TrueFree(void **ptr); + +#include "../namespace_end.h" diff --git a/codemp/game/bg_vehicleLoad.c b/codemp/game/bg_vehicleLoad.c new file mode 100644 index 0000000..b045864 --- /dev/null +++ b/codemp/game/bg_vehicleLoad.c @@ -0,0 +1,1678 @@ +//bg_vehicleLoad.c + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifdef _JK2MP + #include "q_shared.h" + #include "bg_public.h" + #include "bg_vehicles.h" + #include "bg_weapons.h" + + //Could use strap stuff but I don't particularly care at the moment anyway. +#include "../namespace_begin.h" + extern int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); + extern void trap_FS_Read( void *buffer, int len, fileHandle_t f ); + extern void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); + extern void trap_FS_FCloseFile( fileHandle_t f ); + extern int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +#include "../namespace_end.h" +#else + #include "g_local.h" + #define QAGAME +#endif + + +#ifdef _JK2MP +#ifndef QAGAME +#ifndef CGAME +#define WE_ARE_IN_THE_UI +#include "../ui/ui_local.h" +#endif +#endif +#endif + +#ifndef _JK2MP +#include "..\Ratl\string_vs.h" +#endif + +#ifdef QAGAME +extern void G_SetSharedVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern int G_ModelIndex( const char *name ); +extern int G_SoundIndex( const char *name ); + #ifdef _JK2MP + extern int G_EffectIndex( const char *name ); + #endif +#elif CGAME +#include "../namespace_begin.h" +extern qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +extern qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +extern qhandle_t trap_R_RegisterShader( const char *name ); +extern qhandle_t trap_R_RegisterShaderNoMip( const char *name ); +extern int trap_FX_RegisterEffect(const char *file); +extern sfxHandle_t trap_S_RegisterSound( const char *sample); // returns buzz if not found +#include "../namespace_end.h" +#else//UI +#include "../namespace_begin.h" +extern qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +extern qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +extern qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +extern qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found +extern sfxHandle_t trap_S_RegisterSound( const char *sample); // returns buzz if not found +#include "../namespace_end.h" +#endif + +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +// These buffers are filled in with the same contents and then just read from in +// a few places. We only need one copy on Xbox. +#define MAX_VEH_WEAPON_DATA_SIZE 0x2000 +#define MAX_VEHICLE_DATA_SIZE 0xA000 + +#if !defined(_XBOX) || defined(QAGAME) + char VehWeaponParms[MAX_VEH_WEAPON_DATA_SIZE]; + char VehicleParms[MAX_VEHICLE_DATA_SIZE]; + +void BG_ClearVehicleParseParms(void) +{ + //You can't strcat to these forever without clearing them! + VehWeaponParms[0] = 0; + VehicleParms[0] = 0; +} + +#else + extern char VehWeaponParms[MAX_VEH_WEAPON_DATA_SIZE]; + extern char VehicleParms[MAX_VEHICLE_DATA_SIZE]; +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +#ifndef WE_ARE_IN_THE_UI +//These funcs are actually shared in both projects +extern void G_SetAnimalVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetSpeederVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetWalkerVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetFighterVehicleFunctions( vehicleInfo_t *pVehInfo ); +#endif + +vehWeaponInfo_t g_vehWeaponInfo[MAX_VEH_WEAPONS]; +int numVehicleWeapons = 1;//first one is null/default + +vehicleInfo_t g_vehicleInfo[MAX_VEHICLES]; +int numVehicles = 0;//first one is null/default + +void BG_VehicleLoadParms( void ); + + +void BG_ClearVehicleLoadInfo(void) +{ + numVehicleWeapons = 1; + numVehicles = 0; + memset(g_vehWeaponInfo, 0, sizeof(g_vehWeaponInfo)); + memset(g_vehicleInfo, 0, sizeof(g_vehicleInfo)); +} + + +typedef enum { + VF_IGNORE, + VF_INT, + VF_FLOAT, + VF_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + VF_VECTOR, + VF_BOOL, + VF_VEHTYPE, + VF_ANIM, + VF_WEAPON, // take string, resolve into index into VehWeaponParms + VF_MODEL, // take the string, get the G_ModelIndex + VF_MODEL_CLIENT, // (cgame only) take the string, get the G_ModelIndex + VF_EFFECT, // take the string, get the G_EffectIndex + VF_EFFECT_CLIENT, // (cgame only) take the string, get the index + VF_SHADER, // (cgame only) take the string, call trap_R_RegisterShader + VF_SHADER_NOMIP,// (cgame only) take the string, call trap_R_RegisterShaderNoMip + VF_SOUND, // take the string, get the G_SoundIndex + VF_SOUND_CLIENT // (cgame only) take the string, get the index +} vehFieldType_t; + +typedef struct +{ + char *name; + int ofs; + vehFieldType_t type; +} vehField_t; + +vehField_t vehWeaponFields[NUM_VWEAP_PARMS] = +{ + {"name", VWFOFS(name), VF_LSTRING}, //unique name of the vehicle + {"projectile", VWFOFS(bIsProjectile), VF_BOOL}, //traceline or entity? + {"hasGravity", VWFOFS(bHasGravity), VF_BOOL}, //if a projectile, drops + {"ionWeapon", VWFOFS(bIonWeapon), VF_BOOL}, //disables ship shields and sends them out of control + {"saberBlockable", VWFOFS(bSaberBlockable), VF_BOOL}, //lightsabers can deflect this projectile + {"muzzleFX", VWFOFS(iMuzzleFX), VF_EFFECT_CLIENT}, //index of Muzzle Effect + {"model", VWFOFS(iModel), VF_MODEL_CLIENT}, //handle to the model used by this projectile + {"shotFX", VWFOFS(iShotFX), VF_EFFECT_CLIENT}, //index of Shot Effect + {"impactFX", VWFOFS(iImpactFX), VF_EFFECT_CLIENT}, //index of Impact Effect + {"g2MarkShader", VWFOFS(iG2MarkShaderHandle), VF_SHADER}, //index of shader to use for G2 marks made on other models when hit by this projectile + {"g2MarkSize", VWFOFS(fG2MarkSize), VF_FLOAT}, //size (diameter) of the ghoul2 mark + {"loopSound", VWFOFS(iLoopSound), VF_SOUND_CLIENT}, //index of loopSound + {"speed", VWFOFS(fSpeed), VF_FLOAT}, //speed of projectile/range of traceline + {"homing", VWFOFS(fHoming), VF_FLOAT}, //0.0 = not homing, 0.5 = half vel to targ, half cur vel, 1.0 = all vel to targ + {"lockOnTime", VWFOFS(iLockOnTime), VF_INT}, //0 = no lock time needed, else # of ms needed to lock on + {"damage", VWFOFS(iDamage), VF_INT}, //damage done when traceline or projectile directly hits target + {"splashDamage", VWFOFS(iSplashDamage), VF_INT},//damage done to ents in splashRadius of end of traceline or projectile origin on impact + {"splashRadius", VWFOFS(fSplashRadius), VF_FLOAT},//radius that ent must be in to take splashDamage (linear fall-off) + {"ammoPerShot", VWFOFS(iAmmoPerShot), VF_INT},//how much "ammo" each shot takes + {"health", VWFOFS(iHealth), VF_INT}, //if non-zero, projectile can be shot, takes this much damage before being destroyed + {"width", VWFOFS(fWidth), VF_FLOAT}, //width of traceline or bounding box of projecile (non-rotating!) + {"height", VWFOFS(fHeight), VF_FLOAT}, //height of traceline or bounding box of projecile (non-rotating!) + {"lifetime", VWFOFS(iLifeTime), VF_INT}, //removes itself after this amount of time + {"explodeOnExpire", VWFOFS(bExplodeOnExpire), VF_BOOL}, //when iLifeTime is up, explodes rather than simply removing itself +}; + +static qboolean BG_ParseVehWeaponParm( vehWeaponInfo_t *vehWeapon, char *parmName, char *pValue ) +{ + int i; + vec3_t vec; + byte *b = (byte *)vehWeapon; + int _iFieldsRead = 0; + vehicleType_t vehType; + char value[1024]; + + Q_strncpyz( value, pValue, sizeof(value) ); + + // Loop through possible parameters + for ( i = 0; i < NUM_VWEAP_PARMS; i++ ) + { + if ( vehWeaponFields[i].name && !Q_stricmp( vehWeaponFields[i].name, parmName ) ) + { + // found it + switch( vehWeaponFields[i].type ) + { + case VF_INT: + *(int *)(b+vehWeaponFields[i].ofs) = atoi(value); + break; + case VF_FLOAT: + *(float *)(b+vehWeaponFields[i].ofs) = atof(value); + break; + case VF_LSTRING: // string on disk, pointer in memory, TAG_LEVEL + if (!*(char **)(b+vehWeaponFields[i].ofs)) + { //just use 1024 bytes in case we want to write over the string +#ifdef _JK2MP + *(char **)(b+vehWeaponFields[i].ofs) = (char *)BG_Alloc(1024);//(char *)BG_Alloc(strlen(value)); + strcpy(*(char **)(b+vehWeaponFields[i].ofs), value); +#else + (*(char **)(b+vehWeaponFields[i].ofs)) = G_NewString( value ); +#endif + } + + break; + case VF_VECTOR: + _iFieldsRead = sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + assert(_iFieldsRead==3 ); + if (_iFieldsRead!=3) + { + Com_Printf (S_COLOR_YELLOW"BG_ParseVehWeaponParm: VEC3 sscanf() failed to read 3 floats ('angle' key bug?)\n"); + } + ((float *)(b+vehWeaponFields[i].ofs))[0] = vec[0]; + ((float *)(b+vehWeaponFields[i].ofs))[1] = vec[1]; + ((float *)(b+vehWeaponFields[i].ofs))[2] = vec[2]; + break; + case VF_BOOL: + *(qboolean *)(b+vehWeaponFields[i].ofs) = (qboolean)(atof(value)!=0); + break; + case VF_VEHTYPE: + vehType = (vehicleType_t)GetIDForString( VehicleTable, value ); + *(vehicleType_t *)(b+vehWeaponFields[i].ofs) = vehType; + break; + case VF_ANIM: + { + int anim = GetIDForString( animTable, value ); + *(int *)(b+vehWeaponFields[i].ofs) = anim; + } + break; + case VF_WEAPON: // take string, resolve into index into VehWeaponParms + //*(int *)(b+vehWeaponFields[i].ofs) = VEH_VehWeaponIndexForName( value ); + break; + case VF_MODEL:// take the string, get the G_ModelIndex +#ifdef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_MODEL_CLIENT: // (MP cgame only) take the string, get the G_ModelIndex +#ifndef _JK2MP + *(int *)(b+vehWeaponFields[i].ofs) = G_ModelIndex( value ); +#elif QAGAME + //*(int *)(b+vehWeaponFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_EFFECT: // take the string, get the G_EffectIndex +#ifdef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_EFFECT_CLIENT: // (MP cgame only) take the string, get the index +#ifndef _JK2MP + *(int *)(b+vehWeaponFields[i].ofs) = G_EffectIndex( value ); +#elif QAGAME + //*(int *)(b+vehWeaponFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_SHADER: // (cgame only) take the string, call trap_R_RegisterShader +#ifdef WE_ARE_IN_THE_UI + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#elif CGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterShader( value ); +#endif + break; + case VF_SHADER_NOMIP:// (cgame only) take the string, call trap_R_RegisterShaderNoMip +#ifndef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#endif + break; + case VF_SOUND: // take the string, get the G_SoundIndex +#ifdef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + case VF_SOUND_CLIENT: // (MP cgame only) take the string, get the index +#ifndef _JK2MP + *(int *)(b+vehWeaponFields[i].ofs) = G_SoundIndex( value ); +#elif QAGAME + //*(int *)(b+vehWeaponFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + default: + //Unknown type? + return qfalse; + break; + } + break; + } + } + if ( i == NUM_VWEAP_PARMS ) + { + return qfalse; + } + else + { + return qtrue; + } +} + +int VEH_LoadVehWeapon( const char *vehWeaponName ) +{//load up specified vehWeapon and save in array: g_vehWeaponInfo + const char *token; + char parmName[128];//we'll assume that no parm name is longer than 128 + char *value; + const char *p; + vehWeaponInfo_t *vehWeapon = NULL; + + //BG_VehWeaponSetDefaults( &g_vehWeaponInfo[0] );//set the first vehicle to default data + + //try to parse data out + p = VehWeaponParms; + +#ifdef _JK2MP + COM_BeginParseSession("vehWeapons"); +#else + COM_BeginParseSession(); +#endif + + vehWeapon = &g_vehWeaponInfo[numVehicleWeapons]; + // look for the right vehicle weapon + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return qfalse; + } + + if ( !Q_stricmp( token, vehWeaponName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { + return qfalse; + } + + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + {//barf + return VEH_WEAPON_NONE; + } + + if ( Q_stricmp( token, "{" ) != 0 ) + { + return VEH_WEAPON_NONE; + } + + // parse the vehWeapon info block + while ( 1 ) + { + SkipRestOfLine( &p ); + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing Vehicle Weapon '%s'\n", vehWeaponName ); + return VEH_WEAPON_NONE; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + Q_strncpyz( parmName, token, sizeof(parmName) ); + value = COM_ParseExt( &p, qtrue ); + if ( !value || !value[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Vehicle Weapon token '%s' has no value!\n", parmName ); + } + else + { + if ( !BG_ParseVehWeaponParm( vehWeapon, parmName, value ) ) + { + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle Weapon key/value pair '%s','%s'!\n", parmName, value ); + } + } + } + if ( vehWeapon->fHoming ) + {//all lock-on weapons use these 2 sounds +#ifdef QAGAME + //Hmm, no need fo have server register this, is there? + //G_SoundIndex( "sound/weapons/torpedo/tick.wav" ); + //G_SoundIndex( "sound/weapons/torpedo/lock.wav" ); +#elif CGAME + trap_S_RegisterSound( "sound/vehicles/weapons/common/tick.wav" ); + trap_S_RegisterSound( "sound/vehicles/weapons/common/lock.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm1.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm2.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm3.wav" ); +#else + trap_S_RegisterSound( "sound/vehicles/weapons/common/tick.wav" ); + trap_S_RegisterSound( "sound/vehicles/weapons/common/lock.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm1.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm2.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm3.wav" ); +#endif + } + return (numVehicleWeapons++); +} + +int VEH_VehWeaponIndexForName( const char *vehWeaponName ) +{ + int vw; + if ( !vehWeaponName || !vehWeaponName[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Trying to read Vehicle Weapon with no name!\n" ); + return VEH_WEAPON_NONE; + } + for ( vw = VEH_WEAPON_BASE; vw < numVehicleWeapons; vw++ ) + { + if ( g_vehWeaponInfo[vw].name + && Q_stricmp( g_vehWeaponInfo[vw].name, vehWeaponName ) == 0 ) + {//already loaded this one + return vw; + } + } + //haven't loaded it yet + if ( vw >= MAX_VEH_WEAPONS ) + {//no more room! + Com_Printf( S_COLOR_RED"ERROR: Too many Vehicle Weapons (max 16), aborting load on %s!\n", vehWeaponName ); + return VEH_WEAPON_NONE; + } + //we have room for another one, load it up and return the index + //HMM... should we not even load the .vwp file until we want to? + vw = VEH_LoadVehWeapon( vehWeaponName ); + if ( vw == VEH_WEAPON_NONE ) + { + Com_Printf( S_COLOR_RED"ERROR: Could not find Vehicle Weapon %s!\n", vehWeaponName ); + } + return vw; +} + +vehField_t vehicleFields[] = +{ + {"name", VFOFS(name), VF_LSTRING}, //unique name of the vehicle + + //general data + {"type", VFOFS(type), VF_VEHTYPE}, //what kind of vehicle + {"numHands", VFOFS(numHands), VF_INT}, //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + {"lookPitch", VFOFS(lookPitch), VF_FLOAT}, //How far you can look up and down off the forward of the vehicle + {"lookYaw", VFOFS(lookYaw), VF_FLOAT}, //How far you can look left and right off the forward of the vehicle + {"length", VFOFS(length), VF_FLOAT}, //how long it is - used for body length traces when turning/moving? + {"width", VFOFS(width), VF_FLOAT}, //how wide it is - used for body length traces when turning/moving? + {"height", VFOFS(height), VF_FLOAT}, //how tall it is - used for body length traces when turning/moving? + {"centerOfGravity", VFOFS(centerOfGravity), VF_VECTOR},//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats + {"speedMax", VFOFS(speedMax), VF_FLOAT}, //top speed + {"turboSpeed", VFOFS(turboSpeed), VF_FLOAT}, //turbo speed + {"speedMin", VFOFS(speedMin), VF_FLOAT}, //if < 0, can go in reverse + {"speedIdle", VFOFS(speedIdle), VF_FLOAT}, //what speed it drifts to when no accel/decel input is given + {"accelIdle", VFOFS(accelIdle), VF_FLOAT}, //if speedIdle > 0, how quickly it goes up to that speed + {"acceleration", VFOFS(acceleration), VF_FLOAT}, //when pressing on accelerator + {"decelIdle", VFOFS(decelIdle), VF_FLOAT}, //when giving no input, how quickly it drops to speedIdle + {"throttleSticks", VFOFS(throttleSticks), VF_BOOL},//if true, speed stays at whatever you accel/decel to, unless you turbo or brake + {"strafePerc", VFOFS(strafePerc), VF_FLOAT}, //multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + {"bankingSpeed", VFOFS(bankingSpeed), VF_FLOAT}, //how quickly it pitches and rolls (not under player control) + {"pitchLimit", VFOFS(pitchLimit), VF_FLOAT}, //how far it can roll forward or backward + {"rollLimit", VFOFS(rollLimit), VF_FLOAT}, //how far it can roll to either side + {"braking", VFOFS(braking), VF_FLOAT}, //when pressing on decelerator + {"mouseYaw", VFOFS(mouseYaw), VF_FLOAT}, // The mouse yaw override. + {"mousePitch", VFOFS(mousePitch), VF_FLOAT}, // The mouse yaw override. + {"turningSpeed", VFOFS(turningSpeed), VF_FLOAT}, //how quickly you can turn + {"turnWhenStopped", VFOFS(turnWhenStopped), VF_BOOL},//whether or not you can turn when not moving + {"traction", VFOFS(traction), VF_FLOAT}, //how much your command input affects velocity + {"friction", VFOFS(friction), VF_FLOAT}, //how much velocity is cut on its own + {"maxSlope", VFOFS(maxSlope), VF_FLOAT}, //the max slope that it can go up with control + {"speedDependantTurning", VFOFS(speedDependantTurning), VF_BOOL},//vehicle turns faster the faster it's going + + //durability stats + {"mass", VFOFS(mass), VF_INT}, //for momentum and impact force (player mass is 10) + {"armor", VFOFS(armor), VF_INT}, //total points of damage it can take + {"shields", VFOFS(shields), VF_INT}, //energy shield damage points + {"shieldRechargeMS", VFOFS(shieldRechargeMS), VF_INT},//energy shield milliseconds per point recharged + {"toughness", VFOFS(toughness), VF_FLOAT}, //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + {"malfunctionArmorLevel", VFOFS(malfunctionArmorLevel), VF_INT},//when armor drops to or below this point, start malfunctioning + {"surfDestruction", VFOFS(surfDestruction), VF_INT}, + + //visuals & sounds + {"model", VFOFS(model), VF_LSTRING}, //what model to use - if make it an NPC's primary model, don't need this? + {"skin", VFOFS(skin), VF_LSTRING}, //what skin to use - if make it an NPC's primary model, don't need this? + {"g2radius", VFOFS(g2radius), VF_INT}, //render radius (really diameter, but...) for the ghoul2 model + {"riderAnim", VFOFS(riderAnim), VF_ANIM}, //what animation the rider uses + {"droidNPC", VFOFS(droidNPC), VF_LSTRING}, //NPC to attach to *droidunit tag (if it exists in the model) + +#ifdef _JK2MP + {"radarIcon", VFOFS(radarIconHandle), VF_SHADER_NOMIP}, //what icon to show on radar in MP + {"dmgIndicFrame", VFOFS(dmgIndicFrameHandle), VF_SHADER_NOMIP}, //what image to use for the frame of the damage indicator + {"dmgIndicShield", VFOFS(dmgIndicShieldHandle), VF_SHADER_NOMIP},//what image to use for the shield of the damage indicator + {"dmgIndicBackground", VFOFS(dmgIndicBackgroundHandle), VF_SHADER_NOMIP},//what image to use for the background of the damage indicator + {"icon_front", VFOFS(iconFrontHandle), VF_SHADER_NOMIP}, //what image to use for the front of the ship on the damage indicator + {"icon_back", VFOFS(iconBackHandle), VF_SHADER_NOMIP}, //what image to use for the back of the ship on the damage indicator + {"icon_right", VFOFS(iconRightHandle), VF_SHADER_NOMIP}, //what image to use for the right of the ship on the damage indicator + {"icon_left", VFOFS(iconLeftHandle), VF_SHADER_NOMIP}, //what image to use for the left of the ship on the damage indicator + {"crosshairShader", VFOFS(crosshairShaderHandle), VF_SHADER_NOMIP}, //what image to use as the crosshair + {"shieldShader", VFOFS(shieldShaderHandle), VF_SHADER}, //What shader to use when drawing the shield shell + + //individual "area" health -rww + {"health_front", VFOFS(health_front), VF_INT}, + {"health_back", VFOFS(health_back), VF_INT}, + {"health_right", VFOFS(health_right), VF_INT}, + {"health_left", VFOFS(health_left), VF_INT}, +#else + {"radarIcon", 0, VF_IGNORE}, //what icon to show on radar in MP +#endif + + {"soundOn", VFOFS(soundOn), VF_SOUND},//sound to play when get on it + {"soundOff", VFOFS(soundOff), VF_SOUND},//sound to play when get off + {"soundLoop", VFOFS(soundLoop), VF_SOUND},//sound to loop while riding it + {"soundTakeOff", VFOFS(soundTakeOff), VF_SOUND},//sound to play when ship takes off + {"soundEngineStart",VFOFS(soundEngineStart),VF_SOUND_CLIENT},//sound to play when ship's thrusters first activate + {"soundSpin", VFOFS(soundSpin), VF_SOUND},//sound to loop while spiraling out of control + {"soundTurbo", VFOFS(soundTurbo), VF_SOUND},//sound to play when turbo/afterburner kicks in + {"soundHyper", VFOFS(soundHyper), VF_SOUND_CLIENT},//sound to play when hits hyperspace + {"soundLand", VFOFS(soundLand), VF_SOUND},//sound to play when ship lands + {"soundFlyBy", VFOFS(soundFlyBy), VF_SOUND_CLIENT},//sound to play when they buzz you + {"soundFlyBy2", VFOFS(soundFlyBy2), VF_SOUND_CLIENT},//alternate sound to play when they buzz you + {"soundShift1", VFOFS(soundShift1), VF_SOUND},//sound to play when changing speeds + {"soundShift2", VFOFS(soundShift2), VF_SOUND},//sound to play when changing speeds + {"soundShift3", VFOFS(soundShift3), VF_SOUND},//sound to play when changing speeds + {"soundShift4", VFOFS(soundShift4), VF_SOUND},//sound to play when changing speeds + + {"exhaustFX", VFOFS(iExhaustFX), VF_EFFECT_CLIENT}, //exhaust effect, played from "*exhaust" bolt(s) + {"turboFX", VFOFS(iTurboFX), VF_EFFECT_CLIENT}, //turbo exhaust effect, played from "*exhaust" bolt(s) when ship is in "turbo" mode + {"turboStartFX", VFOFS(iTurboStartFX), VF_EFFECT}, //turbo start effect, played from "*exhaust" bolt(s) when ship is in "turbo" mode + {"trailFX", VFOFS(iTrailFX), VF_EFFECT_CLIENT}, //trail effect, played from "*trail" bolt(s) + {"impactFX", VFOFS(iImpactFX), VF_EFFECT_CLIENT}, //impact effect, for when it bumps into something + {"explodeFX", VFOFS(iExplodeFX), VF_EFFECT}, //explosion effect, for when it blows up (should have the sound built into explosion effect) + {"wakeFX", VFOFS(iWakeFX), VF_EFFECT_CLIENT}, //effect it makes when going across water + {"dmgFX", VFOFS(iDmgFX), VF_EFFECT_CLIENT}, //effect to play on damage from a weapon or something +#ifdef _JK2MP + {"injureFX", VFOFS(iInjureFX), VF_EFFECT_CLIENT}, //effect to play on partially damaged ship surface + {"noseFX", VFOFS(iNoseFX), VF_EFFECT_CLIENT}, //effect for nose piece flying away when blown off + {"lwingFX", VFOFS(iLWingFX), VF_EFFECT_CLIENT}, //effect for left wing piece flying away when blown off + {"rwingFX", VFOFS(iRWingFX), VF_EFFECT_CLIENT}, //effect for right wing piece flying away when blown off +#else + {"armorLowFX", VFOFS(iArmorLowFX), VF_EFFECT_CLIENT}, //effect to play on damage from a weapon or something + {"armorGoneFX", VFOFS(iArmorGoneFX), VF_EFFECT_CLIENT}, //effect to play on damage from a weapon or something +#endif + + // Weapon stuff: + {"weap1", VFOFS(weapon[0].ID), VF_WEAPON}, //weapon used when press fire + {"weap2", VFOFS(weapon[1].ID), VF_WEAPON},//weapon used when press alt-fire + // The delay between shots for this weapon. + {"weap1Delay", VFOFS(weapon[0].delay), VF_INT}, + {"weap2Delay", VFOFS(weapon[1].delay), VF_INT}, + // Whether or not all the muzzles for each weapon can be linked together (linked delay = weapon delay * number of muzzles linked!) + {"weap1Link", VFOFS(weapon[0].linkable), VF_INT}, + {"weap2Link", VFOFS(weapon[1].linkable), VF_INT}, + // Whether or not to auto-aim the projectiles at the thing under the crosshair when we fire + {"weap1Aim", VFOFS(weapon[0].aimCorrect), VF_BOOL}, + {"weap2Aim", VFOFS(weapon[1].aimCorrect), VF_BOOL}, + //maximum ammo + {"weap1AmmoMax", VFOFS(weapon[0].ammoMax), VF_INT}, + {"weap2AmmoMax", VFOFS(weapon[1].ammoMax), VF_INT}, + //ammo recharge rate - milliseconds per unit (minimum of 100, which is 10 ammo per second) + {"weap1AmmoRechargeMS", VFOFS(weapon[0].ammoRechargeMS), VF_INT}, + {"weap2AmmoRechargeMS", VFOFS(weapon[1].ammoRechargeMS), VF_INT}, + //sound to play when out of ammo (plays default "no ammo" sound if none specified) + {"weap1SoundNoAmmo", VFOFS(weapon[0].soundNoAmmo), VF_SOUND_CLIENT},//sound to play when try to fire weapon 1 with no ammo + {"weap2SoundNoAmmo", VFOFS(weapon[1].soundNoAmmo), VF_SOUND_CLIENT},//sound to play when try to fire weapon 2 with no ammo + + // Which weapon a muzzle fires (has to match one of the weapons this vehicle has). + {"weapMuzzle1", VFOFS(weapMuzzle[0]), VF_WEAPON}, + {"weapMuzzle2", VFOFS(weapMuzzle[1]), VF_WEAPON}, + {"weapMuzzle3", VFOFS(weapMuzzle[2]), VF_WEAPON}, + {"weapMuzzle4", VFOFS(weapMuzzle[3]), VF_WEAPON}, + {"weapMuzzle5", VFOFS(weapMuzzle[4]), VF_WEAPON}, + {"weapMuzzle6", VFOFS(weapMuzzle[5]), VF_WEAPON}, + {"weapMuzzle7", VFOFS(weapMuzzle[6]), VF_WEAPON}, + {"weapMuzzle8", VFOFS(weapMuzzle[7]), VF_WEAPON}, + {"weapMuzzle9", VFOFS(weapMuzzle[8]), VF_WEAPON}, + {"weapMuzzle10", VFOFS(weapMuzzle[9]), VF_WEAPON}, + + // The max height before this ship (?) starts (auto)landing. + {"landingHeight", VFOFS(landingHeight), VF_FLOAT}, + + //other misc stats + {"gravity", VFOFS(gravity), VF_INT}, //normal is 800 + {"hoverHeight", VFOFS(hoverHeight), VF_FLOAT}, //if 0, it's a ground vehicle + {"hoverStrength", VFOFS(hoverStrength), VF_FLOAT}, //how hard it pushes off ground when less than hover height... causes "bounce", like shocks + {"waterProof", VFOFS(waterProof), VF_BOOL}, //can drive underwater if it has to + {"bouyancy", VFOFS(bouyancy), VF_FLOAT}, //when in water, how high it floats (1 is neutral bouyancy) + {"fuelMax", VFOFS(fuelMax), VF_INT}, //how much fuel it can hold (capacity) + {"fuelRate", VFOFS(fuelRate), VF_INT}, //how quickly is uses up fuel + {"turboDuration", VFOFS(turboDuration), VF_INT}, //how long turbo lasts + {"turboRecharge", VFOFS(turboRecharge), VF_INT}, //how long turbo takes to recharge + {"visibility", VFOFS(visibility), VF_INT}, //for sight alerts + {"loudness", VFOFS(loudness), VF_INT}, //for sound alerts + {"explosionRadius", VFOFS(explosionRadius), VF_FLOAT},//range of explosion + {"explosionDamage", VFOFS(explosionDamage), VF_INT},//damage of explosion + + //new stuff + {"maxPassengers", VFOFS(maxPassengers), VF_INT}, // The max number of passengers this vehicle may have (Default = 0). + {"hideRider", VFOFS(hideRider), VF_BOOL}, // rider (and passengers?) should not be drawn + {"killRiderOnDeath", VFOFS(killRiderOnDeath), VF_BOOL},//if rider is on vehicle when it dies, they should die + {"flammable", VFOFS(flammable), VF_BOOL}, //whether or not the vehicle should catch on fire before it explodes + {"explosionDelay", VFOFS(explosionDelay), VF_INT}, //how long the vehicle should be on fire/dying before it explodes + //camera stuff + {"cameraOverride", VFOFS(cameraOverride), VF_BOOL},//override the third person camera with the below values - normal is 0 (off) + {"cameraRange", VFOFS(cameraRange), VF_FLOAT}, //how far back the camera should be - normal is 80 + {"cameraVertOffset", VFOFS(cameraVertOffset), VF_FLOAT},//how high over the vehicle origin the camera should be - normal is 16 + {"cameraHorzOffset", VFOFS(cameraHorzOffset), VF_FLOAT},//how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + {"cameraPitchOffset", VFOFS(cameraPitchOffset), VF_FLOAT},//a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + {"cameraFOV", VFOFS(cameraFOV), VF_FLOAT}, //third person camera FOV, default is 80 + {"cameraAlpha", VFOFS(cameraAlpha), VF_FLOAT}, //fade out the vehicle to this alpha (0.1-1.0f) if it's in the way of the crosshair + {"cameraPitchDependantVertOffset", VFOFS(cameraPitchDependantVertOffset), VF_BOOL}, //use the hacky AT-ST pitch dependant vertical offset +//===TURRETS=========================================================================== + //Turret 1 + {"turret1Weap", VFOFS(turret[0].iWeapon), VF_WEAPON}, + {"turret1Delay", VFOFS(turret[0].iDelay), VF_INT}, + {"turret1AmmoMax", VFOFS(turret[0].iAmmoMax), VF_INT}, + {"turret1AmmoRechargeMS", VFOFS(turret[0].iAmmoRechargeMS), VF_INT}, + {"turret1YawBone", VFOFS(turret[0].yawBone), VF_LSTRING}, + {"turret1PitchBone", VFOFS(turret[0].pitchBone), VF_LSTRING}, + {"turret1YawAxis", VFOFS(turret[0].yawAxis), VF_INT}, + {"turret1PitchAxis", VFOFS(turret[0].pitchAxis), VF_INT}, + {"turret1ClampYawL", VFOFS(turret[0].yawClampLeft), VF_FLOAT}, //how far the turret is allowed to turn left + {"turret1ClampYawR", VFOFS(turret[0].yawClampRight), VF_FLOAT}, //how far the turret is allowed to turn right + {"turret1ClampPitchU", VFOFS(turret[0].pitchClampUp), VF_FLOAT}, //how far the turret is allowed to title up + {"turret1ClampPitchD", VFOFS(turret[0].pitchClampDown), VF_FLOAT}, //how far the turret is allowed to tilt down + {"turret1Muzzle1", VFOFS(turret[0].iMuzzle[0]), VF_INT}, + {"turret1Muzzle2", VFOFS(turret[0].iMuzzle[1]), VF_INT}, + {"turret1TurnSpeed", VFOFS(turret[0].fTurnSpeed), VF_FLOAT}, + {"turret1AI", VFOFS(turret[0].bAI), VF_BOOL}, + {"turret1AILead", VFOFS(turret[0].bAILead), VF_BOOL}, + {"turret1AIRange", VFOFS(turret[0].fAIRange), VF_FLOAT}, + {"turret1PassengerNum", VFOFS(turret[0].passengerNum), VF_INT},//which number passenger can control this turret + {"turret1GunnerViewTag", VFOFS(turret[0].gunnerViewTag), VF_LSTRING}, + + //Turret 2 + {"turret2Weap", VFOFS(turret[1].iWeapon), VF_WEAPON}, + {"turret2Delay", VFOFS(turret[1].iDelay), VF_INT}, + {"turret2AmmoMax", VFOFS(turret[1].iAmmoMax), VF_INT}, + {"turret2AmmoRechargeMS", VFOFS(turret[1].iAmmoRechargeMS), VF_INT}, + {"turret2YawBone", VFOFS(turret[1].yawBone), VF_LSTRING}, + {"turret2PitchBone", VFOFS(turret[1].pitchBone), VF_LSTRING}, + {"turret2YawAxis", VFOFS(turret[1].yawAxis), VF_INT}, + {"turret2PitchAxis", VFOFS(turret[1].pitchAxis), VF_INT}, + {"turret2ClampYawL", VFOFS(turret[1].yawClampLeft), VF_FLOAT}, //how far the turret is allowed to turn left + {"turret2ClampYawR", VFOFS(turret[1].yawClampRight), VF_FLOAT}, //how far the turret is allowed to turn right + {"turret2ClampPitchU", VFOFS(turret[1].pitchClampUp), VF_FLOAT}, //how far the turret is allowed to title up + {"turret2ClampPitchD", VFOFS(turret[1].pitchClampDown), VF_FLOAT}, //how far the turret is allowed to tilt down + {"turret2Muzzle1", VFOFS(turret[1].iMuzzle[0]), VF_INT}, + {"turret2Muzzle2", VFOFS(turret[1].iMuzzle[1]), VF_INT}, + {"turret2TurnSpeed", VFOFS(turret[1].fTurnSpeed), VF_FLOAT}, + {"turret2AI", VFOFS(turret[1].bAI), VF_BOOL}, + {"turret2AILead", VFOFS(turret[1].bAILead), VF_BOOL}, + {"turret2AIRange", VFOFS(turret[1].fAIRange), VF_FLOAT}, + {"turret2PassengerNum", VFOFS(turret[1].passengerNum), VF_INT},//which number passenger can control this turret + {"turret2GunnerViewTag", VFOFS(turret[1].gunnerViewTag), VF_LSTRING}, +//===END TURRETS=========================================================================== + //terminating entry + {0, -1, VF_INT} +}; + +stringID_table_t VehicleTable[VH_NUM_VEHICLES+1] = +{ + ENUM2STRING(VH_NONE), + ENUM2STRING(VH_WALKER), //something you ride inside of, it walks like you, like an AT-ST + ENUM2STRING(VH_FIGHTER), //something you fly inside of, like an X-Wing or TIE fighter + ENUM2STRING(VH_SPEEDER), //something you ride on that hovers, like a speeder or swoop + ENUM2STRING(VH_ANIMAL), //animal you ride on top of that walks, like a tauntaun + ENUM2STRING(VH_FLIER), //animal you ride on top of that flies, like a giant mynoc? + 0, -1 +}; + +// Setup the shared functions (one's that all vehicles would generally use). +void BG_SetSharedVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + //only do the whole thing if we're on game + G_SetSharedVehicleFunctions(pVehInfo); +#endif + +#ifndef WE_ARE_IN_THE_UI + switch( pVehInfo->type ) + { + case VH_SPEEDER: + G_SetSpeederVehicleFunctions( pVehInfo ); + break; + case VH_ANIMAL: + G_SetAnimalVehicleFunctions( pVehInfo ); + break; + case VH_FIGHTER: + G_SetFighterVehicleFunctions( pVehInfo ); + break; + case VH_WALKER: + G_SetWalkerVehicleFunctions( pVehInfo ); + break; + } +#endif +} + +void BG_VehicleSetDefaults( vehicleInfo_t *vehicle ) +{ + memset(vehicle, 0, sizeof(vehicleInfo_t)); +/* +#if _JK2MP + if (!vehicle->name) + { + vehicle->name = (char *)BG_Alloc(1024); + } + strcpy(vehicle->name, "default"); +#else + vehicle->name = G_NewString( "default" ); +#endif + + //general data + vehicle->type = VH_SPEEDER; //what kind of vehicle + //FIXME: no saber or weapons if numHands = 2, should switch to speeder weapon, no attack anim on player + vehicle->numHands = 0; //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + vehicle->lookPitch = 0; //How far you can look up and down off the forward of the vehicle + vehicle->lookYaw = 5; //How far you can look left and right off the forward of the vehicle + vehicle->length = 0; //how long it is - used for body length traces when turning/moving? + vehicle->width = 0; //how wide it is - used for body length traces when turning/moving? + vehicle->height = 0; //how tall it is - used for body length traces when turning/moving? + VectorClear( vehicle->centerOfGravity );//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats - note: these are DESIRED speed, not actual current speed/velocity + vehicle->speedMax = VEH_DEFAULT_SPEED_MAX; //top speed + vehicle->turboSpeed = 0; //turboBoost + vehicle->speedMin = 0; //if < 0, can go in reverse + vehicle->speedIdle = 0; //what speed it drifts to when no accel/decel input is given + vehicle->accelIdle = 0; //if speedIdle > 0, how quickly it goes up to that speed + vehicle->acceleration = VEH_DEFAULT_ACCEL; //when pressing on accelerator (1/2 this when going in reverse) + vehicle->decelIdle = VEH_DEFAULT_DECEL; //when giving no input, how quickly it desired speed drops to speedIdle + vehicle->strafePerc = VEH_DEFAULT_STRAFE_PERC;//multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + vehicle->bankingSpeed = VEH_DEFAULT_BANKING_SPEED; //how quickly it pitches and rolls (not under player control) + vehicle->rollLimit = VEH_DEFAULT_ROLL_LIMIT; //how far it can roll to either side + vehicle->pitchLimit = VEH_DEFAULT_PITCH_LIMIT; //how far it can pitch forward or backward + vehicle->braking = VEH_DEFAULT_BRAKING; //when pressing on decelerator (backwards) + vehicle->turningSpeed = VEH_DEFAULT_TURNING_SPEED; //how quickly you can turn + vehicle->turnWhenStopped = qfalse; //whether or not you can turn when not moving + vehicle->traction = VEH_DEFAULT_TRACTION; //how much your command input affects velocity + vehicle->friction = VEH_DEFAULT_FRICTION; //how much velocity is cut on its own + vehicle->maxSlope = VEH_DEFAULT_MAX_SLOPE; //the max slope that it can go up with control + + //durability stats + vehicle->mass = VEH_DEFAULT_MASS; //for momentum and impact force (player mass is 10) + vehicle->armor = VEH_DEFAULT_MAX_ARMOR; //total points of damage it can take + vehicle->toughness = VEH_DEFAULT_TOUGHNESS; //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + vehicle->malfunctionArmorLevel = 0; //when armor drops to or below this point, start malfunctioning + + //visuals & sounds + //vehicle->model = "models/map_objects/ships/swoop.md3"; //what model to use - if make it an NPC's primary model, don't need this? + if (!vehicle->model) + { + vehicle->model = (char *)BG_Alloc(1024); + } + strcpy(vehicle->model, "models/map_objects/ships/swoop.md3"); + + vehicle->modelIndex = 0; //set internally, not until this vehicle is spawned into the level + vehicle->skin = NULL; //what skin to use - if make it an NPC's primary model, don't need this? + vehicle->riderAnim = BOTH_GUNSIT1; //what animation the rider uses + + vehicle->soundOn = NULL; //sound to play when get on it + vehicle->soundLoop = NULL; //sound to loop while riding it + vehicle->soundOff = NULL; //sound to play when get off + vehicle->exhaustFX = NULL; //exhaust effect, played from "*exhaust" bolt(s) + vehicle->trailFX = NULL; //trail effect, played from "*trail" bolt(s) + vehicle->impactFX = NULL; //explosion effect, for when it blows up (should have the sound built into explosion effect) + vehicle->explodeFX = NULL; //explosion effect, for when it blows up (should have the sound built into explosion effect) + vehicle->wakeFX = NULL; //effect itmakes when going across water + + //other misc stats + vehicle->gravity = VEH_DEFAULT_GRAVITY; //normal is 800 + vehicle->hoverHeight = 0;//VEH_DEFAULT_HOVER_HEIGHT; //if 0, it's a ground vehicle + vehicle->hoverStrength = 0;//VEH_DEFAULT_HOVER_STRENGTH;//how hard it pushes off ground when less than hover height... causes "bounce", like shocks + vehicle->waterProof = qtrue; //can drive underwater if it has to + vehicle->bouyancy = 1.0f; //when in water, how high it floats (1 is neutral bouyancy) + vehicle->fuelMax = 1000; //how much fuel it can hold (capacity) + vehicle->fuelRate = 1; //how quickly is uses up fuel + vehicle->visibility = VEH_DEFAULT_VISIBILITY; //radius for sight alerts + vehicle->loudness = VEH_DEFAULT_LOUDNESS; //radius for sound alerts + vehicle->explosionRadius = VEH_DEFAULT_EXP_RAD; + vehicle->explosionDamage = VEH_DEFAULT_EXP_DMG; + vehicle->maxPassengers = 0; + + //new stuff + vehicle->hideRider = qfalse; // rider (and passengers?) should not be drawn + vehicle->killRiderOnDeath = qfalse; //if rider is on vehicle when it dies, they should die + vehicle->flammable = qfalse; //whether or not the vehicle should catch on fire before it explodes + vehicle->explosionDelay = 0; //how long the vehicle should be on fire/dying before it explodes + //camera stuff + vehicle->cameraOverride = qfalse; //whether or not to use all of the following 3rd person camera override values + vehicle->cameraRange = 0.0f; //how far back the camera should be - normal is 80 + vehicle->cameraVertOffset = 0.0f; //how high over the vehicle origin the camera should be - normal is 16 + vehicle->cameraHorzOffset = 0.0f; //how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + vehicle->cameraPitchOffset = 0.0f; //a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + vehicle->cameraFOV = 0.0f; //third person camera FOV, default is 80 + vehicle->cameraAlpha = qfalse; //fade out the vehicle if it's in the way of the crosshair +*/ +} + +void BG_VehicleClampData( vehicleInfo_t *vehicle ) +{//sanity check and clamp the vehicle's data + int i; + + for ( i = 0; i < 3; i++ ) + { + if ( vehicle->centerOfGravity[i] > 1.0f ) + { + vehicle->centerOfGravity[i] = 1.0f; + } + else if ( vehicle->centerOfGravity[i] < -1.0f ) + { + vehicle->centerOfGravity[i] = -1.0f; + } + } + + // Validate passenger max. + if ( vehicle->maxPassengers > VEH_MAX_PASSENGERS ) + { + vehicle->maxPassengers = VEH_MAX_PASSENGERS; + } + else if ( vehicle->maxPassengers < 0 ) + { + vehicle->maxPassengers = 0; + } +} + +static qboolean BG_ParseVehicleParm( vehicleInfo_t *vehicle, char *parmName, char *pValue ) +{ + int i; + vec3_t vec; + byte *b = (byte *)vehicle; + int _iFieldsRead = 0; + vehicleType_t vehType; + char value[1024]; + + Q_strncpyz( value, pValue, sizeof(value) ); + + // Loop through possible parameters + for ( i = 0; vehicleFields[i].ofs != -1; i++ ) + { + if ( !Q_stricmp( vehicleFields[i].name, parmName ) ) + { + // found it + switch( vehicleFields[i].type ) + { + case VF_IGNORE: + break; + case VF_INT: + *(int *)(b+vehicleFields[i].ofs) = atoi(value); + break; + case VF_FLOAT: + *(float *)(b+vehicleFields[i].ofs) = atof(value); + break; + case VF_LSTRING: // string on disk, pointer in memory, TAG_LEVEL + if (!*(char **)(b+vehicleFields[i].ofs)) + { //just use 128 bytes in case we want to write over the string +#ifdef _JK2MP + *(char **)(b+vehicleFields[i].ofs) = (char *)BG_Alloc(128);//(char *)BG_Alloc(strlen(value)); + strcpy(*(char **)(b+vehicleFields[i].ofs), value); +#else + (*(char **)(b+vehicleFields[i].ofs)) = G_NewString( value ); +#endif + } + + break; + case VF_VECTOR: + _iFieldsRead = sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + assert(_iFieldsRead==3 ); + if (_iFieldsRead!=3) + { + Com_Printf (S_COLOR_YELLOW"BG_ParseVehicleParm: VEC3 sscanf() failed to read 3 floats ('angle' key bug?)\n"); + } + ((float *)(b+vehWeaponFields[i].ofs))[0] = vec[0]; + ((float *)(b+vehWeaponFields[i].ofs))[1] = vec[1]; + ((float *)(b+vehWeaponFields[i].ofs))[2] = vec[2]; + break; + case VF_BOOL: + *(qboolean *)(b+vehicleFields[i].ofs) = (qboolean)(atof(value)!=0); + break; + case VF_VEHTYPE: + vehType = (vehicleType_t)GetIDForString( VehicleTable, value ); + *(vehicleType_t *)(b+vehicleFields[i].ofs) = vehType; + break; + case VF_ANIM: + { + int anim = GetIDForString( animTable, value ); + *(int *)(b+vehicleFields[i].ofs) = anim; + } + break; + case VF_WEAPON: // take string, resolve into index into VehWeaponParms + *(int *)(b+vehicleFields[i].ofs) = VEH_VehWeaponIndexForName( value ); + break; + case VF_MODEL: // take the string, get the G_ModelIndex +#ifdef QAGAME + *(int *)(b+vehicleFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_MODEL_CLIENT: // (MP cgame only) take the string, get the G_ModelIndex +#ifndef _JK2MP + *(int *)(b+vehicleFields[i].ofs) = G_ModelIndex( value ); +#elif QAGAME + //*(int *)(b+vehicleFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_EFFECT: // take the string, get the G_EffectIndex +#ifdef QAGAME + *(int *)(b+vehicleFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehicleFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_EFFECT_CLIENT: // (MP cgame only) take the string, get the G_EffectIndex +#ifndef _JK2MP + *(int *)(b+vehicleFields[i].ofs) = G_EffectIndex( value ); +#elif QAGAME + //*(int *)(b+vehicleFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehicleFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_SHADER: // (cgame only) take the string, call trap_R_RegisterShader +#ifdef WE_ARE_IN_THE_UI + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#elif CGAME + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterShader( value ); +#endif + break; + case VF_SHADER_NOMIP:// (cgame only) take the string, call trap_R_RegisterShaderNoMip +#ifndef QAGAME + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#endif + break; + case VF_SOUND: // take the string, get the G_SoundIndex +#ifdef QAGAME + *(int *)(b+vehicleFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + case VF_SOUND_CLIENT: // (MP cgame only) take the string, get the G_SoundIndex +#ifndef _JK2MP + *(int *)(b+vehicleFields[i].ofs) = G_SoundIndex( value ); +#elif QAGAME + //*(int *)(b+vehicleFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + default: + //Unknown type? + return qfalse; + break; + } + break; + } + } + if ( vehicleFields[i].ofs == -1 ) + { + return qfalse; + } + else + { + return qtrue; + } +} + +int VEH_LoadVehicle( const char *vehicleName ) +{//load up specified vehicle and save in array: g_vehicleInfo + const char *token; + //we'll assume that no parm name is longer than 128 + char parmName[128] = { 0 }; + char weap1[128] = { 0 }, weap2[128] = { 0 }; + char weapMuzzle1[128] = { 0 }; + char weapMuzzle2[128] = { 0 }; + char weapMuzzle3[128] = { 0 }; + char weapMuzzle4[128] = { 0 }; + char weapMuzzle5[128] = { 0 }; + char weapMuzzle6[128] = { 0 }; + char weapMuzzle7[128] = { 0 }; + char weapMuzzle8[128] = { 0 }; + char weapMuzzle9[128] = { 0 }; + char weapMuzzle10[128] = { 0 }; + char *value = NULL; + const char *p = NULL; + vehicleInfo_t *vehicle = NULL; + + // Load the vehicle parms if no vehicles have been loaded yet. + if ( numVehicles == 0 ) + { + BG_VehicleLoadParms(); + } + + //try to parse data out + p = VehicleParms; + +#ifdef _JK2MP + COM_BeginParseSession("vehicles"); +#else + COM_BeginParseSession(); +#endif + + vehicle = &g_vehicleInfo[numVehicles]; + // look for the right vehicle + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return VEHICLE_NONE; + } + + if ( !Q_stricmp( token, vehicleName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + + if ( !p ) + { + return VEHICLE_NONE; + } + + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + {//barf + return VEHICLE_NONE; + } + + if ( Q_stricmp( token, "{" ) != 0 ) + { + return VEHICLE_NONE; + } + + BG_VehicleSetDefaults( vehicle ); + // parse the vehicle info block + while ( 1 ) + { + SkipRestOfLine( &p ); + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing Vehicle '%s'\n", vehicleName ); + return VEHICLE_NONE; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + Q_strncpyz( parmName, token, sizeof(parmName) ); + value = COM_ParseExt( &p, qtrue ); + if ( !value || !value[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Vehicle token '%s' has no value!\n", parmName ); + } + else if ( Q_stricmp( "weap1", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weap1, value, sizeof(weap1) ); + } + else if ( Q_stricmp( "weap2", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weap2, value, sizeof(weap2) ); + } + else if ( Q_stricmp( "weapMuzzle1", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle1, value, sizeof(weapMuzzle1) ); + } + else if ( Q_stricmp( "weapMuzzle2", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle2, value, sizeof(weapMuzzle2) ); + } + else if ( Q_stricmp( "weapMuzzle3", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle3, value, sizeof(weapMuzzle3) ); + } + else if ( Q_stricmp( "weapMuzzle4", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle4, value, sizeof(weapMuzzle4) ); + } + else if ( Q_stricmp( "weapMuzzle5", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle5, value, sizeof(weapMuzzle5) ); + } + else if ( Q_stricmp( "weapMuzzle6", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle6, value, sizeof(weapMuzzle6) ); + } + else if ( Q_stricmp( "weapMuzzle7", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle7, value, sizeof(weapMuzzle7) ); + } + else if ( Q_stricmp( "weapMuzzle8", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle8, value, sizeof(weapMuzzle8) ); + } + else if ( Q_stricmp( "weapMuzzle9", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle9, value, sizeof(weapMuzzle9) ); + } + else if ( Q_stricmp( "weapMuzzle10", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle10, value, sizeof(weapMuzzle10) ); + } + else + { + if ( !BG_ParseVehicleParm( vehicle, parmName, value ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair '%s', '%s'!\n", parmName, value ); +#endif + } + } + } + //NOW: if we have any weapons, go ahead and load them + if ( weap1[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weap1", weap1 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weap1', '%s'!\n", weap1 ); +#endif + } + } + if ( weap2[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weap2", weap2 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weap2', '%s'!\n", weap2 ); +#endif + } + } + if ( weapMuzzle1[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle1", weapMuzzle1 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle1', '%s'!\n", weapMuzzle1 ); +#endif + } + } + if ( weapMuzzle2[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle2", weapMuzzle2 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle2', '%s'!\n", weapMuzzle2 ); +#endif + } + } + if ( weapMuzzle3[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle3", weapMuzzle3 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle3', '%s'!\n", weapMuzzle3 ); +#endif + } + } + if ( weapMuzzle4[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle4", weapMuzzle4 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle4', '%s'!\n", weapMuzzle4 ); +#endif + } + } + if ( weapMuzzle5[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle5", weapMuzzle5 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle5', '%s'!\n", weapMuzzle5 ); +#endif + } + } + if ( weapMuzzle6[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle6", weapMuzzle6 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle6', '%s'!\n", weapMuzzle6 ); +#endif + } + } + if ( weapMuzzle7[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle7", weapMuzzle7 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle7', '%s'!\n", weapMuzzle7 ); +#endif + } + } + if ( weapMuzzle8[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle8", weapMuzzle8 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle8', '%s'!\n", weapMuzzle8 ); +#endif + } + } + if ( weapMuzzle9[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle9", weapMuzzle9 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle9', '%s'!\n", weapMuzzle9 ); +#endif + } + } + if ( weapMuzzle10[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle10", weapMuzzle10 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle10', '%s'!\n", weapMuzzle10 ); +#endif + } + } + +#ifdef _JK2MP + //let's give these guys some defaults + if (!vehicle->health_front) + { + vehicle->health_front = vehicle->armor/4; + } + if (!vehicle->health_back) + { + vehicle->health_back = vehicle->armor/4; + } + if (!vehicle->health_right) + { + vehicle->health_right = vehicle->armor/4; + } + if (!vehicle->health_left) + { + vehicle->health_left = vehicle->armor/4; + } +#endif + + if ( vehicle->model ) + { +#ifdef QAGAME + vehicle->modelIndex = G_ModelIndex( va( "models/players/%s/model.glm", vehicle->model ) ); +#else + vehicle->modelIndex = trap_R_RegisterModel( va( "models/players/%s/model.glm", vehicle->model ) ); +#endif + } + +#ifndef _JK2MP + //SP + if ( vehicle->skin + && vehicle->skin[0] ) + { + ratl::string_vs<256> skins(vehicle->skin); + for (ratl::string_vs<256>::tokenizer i = skins.begin("|"); i!=skins.end(); i++) + { + //this will just turn off surfs if there is a *off shader on a surf, the skin will actually get thrown away when cgame starts + gi.RE_RegisterSkin( va( "models/players/%s/model_%s.skin", vehicle->model, *i) ); + //this is for the server-side call, it will propgate down to cgame with configstrings and register it at the same time as all the other skins for ghoul2 models + G_SkinIndex( va( "models/players/%s/model_%s.skin", vehicle->model, *i) ); + } + } + else + { + //this will just turn off surfs if there is a *off shader on a surf, the skin will actually get thrown away when cgame starts + gi.RE_RegisterSkin( va( "models/players/%s/model_default.skin", vehicle->model) ); + //this is for the server-side call, it will propgate down to cgame with configstrings and register it at the same time as all the other skins for ghoul2 models + G_SkinIndex( va( "models/players/%s/model_default.skin", vehicle->model) ); + } +#else +#ifndef QAGAME + if ( vehicle->skin + && vehicle->skin[0] ) + { + trap_R_RegisterSkin( va( "models/players/%s/model_%s.skin", vehicle->model, vehicle->skin) ); + } +#endif +#endif + //sanity check and clamp the vehicle's data + BG_VehicleClampData( vehicle ); + // Setup the shared function pointers. + BG_SetSharedVehicleFunctions( vehicle ); + //misc effects... FIXME: not even used in MP, are they? + if ( vehicle->explosionDamage ) + { +#ifdef QAGAME + G_EffectIndex( "ships/ship_explosion_mark" ); +#elif CGAME + trap_FX_RegisterEffect( "ships/ship_explosion_mark" ); +#endif + } + if ( vehicle->flammable ) + { +#ifdef QAGAME + G_SoundIndex( "sound/vehicles/common/fire_lp.wav" ); +#elif CGAME + trap_S_RegisterSound( "sound/vehicles/common/fire_lp.wav" ); +#else + trap_S_RegisterSound( "sound/vehicles/common/fire_lp.wav" ); +#endif + } + + if ( vehicle->hoverHeight > 0 ) + { +#ifndef _JK2MP + G_EffectIndex( "ships/swoop_dust" ); +#elif QAGAME + G_EffectIndex( "ships/swoop_dust" ); +#elif CGAME + trap_FX_RegisterEffect( "ships/swoop_dust" ); +#endif + } + +#ifdef QAGAME + G_EffectIndex( "volumetric/black_smoke" ); + G_EffectIndex( "ships/fire" ); + G_SoundIndex( "sound/vehicles/common/release.wav" ); +#elif CGAME + trap_R_RegisterShader( "gfx/menus/radar/bracket" ); + trap_R_RegisterShaderNoMip( "gfx/menus/radar/asteroid" ); + trap_S_RegisterSound( "sound/vehicles/common/impactalarm.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/linkweaps.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/release.wav" ); + trap_FX_RegisterEffect("effects/ships/dest_burning.efx"); + trap_FX_RegisterEffect("effects/ships/dest_destroyed.efx"); + trap_FX_RegisterEffect( "volumetric/black_smoke" ); + trap_FX_RegisterEffect( "ships/fire" ); + trap_FX_RegisterEffect("ships/hyperspace_stars"); + + if ( vehicle->hideRider ) + { + trap_R_RegisterShaderNoMip( "gfx/menus/radar/circle_base" ); + trap_R_RegisterShaderNoMip( "gfx/menus/radar/circle_base_frame" ); + trap_R_RegisterShaderNoMip( "gfx/menus/radar/circle_base_shield" ); + } +#endif + + return (numVehicles++); +} + +int VEH_VehicleIndexForName( const char *vehicleName ) +{ + int v; + if ( !vehicleName || !vehicleName[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Trying to read Vehicle with no name!\n" ); + return VEHICLE_NONE; + } + for ( v = VEHICLE_BASE; v < numVehicles; v++ ) + { + if ( g_vehicleInfo[v].name + && Q_stricmp( g_vehicleInfo[v].name, vehicleName ) == 0 ) + {//already loaded this one + return v; + } + } + //haven't loaded it yet + if ( v >= MAX_VEHICLES ) + {//no more room! + Com_Printf( S_COLOR_RED"ERROR: Too many Vehicles (max 64), aborting load on %s!\n", vehicleName ); + return VEHICLE_NONE; + } + //we have room for another one, load it up and return the index + //HMM... should we not even load the .veh file until we want to? + v = VEH_LoadVehicle( vehicleName ); + if ( v == VEHICLE_NONE ) + { + Com_Printf( S_COLOR_RED"ERROR: Could not find Vehicle %s!\n", vehicleName ); + } + return v; +} + +void BG_VehWeaponLoadParms( void ) +{ + int len, totallen, vehExtFNLen, mainBlockLen, fileCnt, i; + char *holdChar, *marker; + char vehWeaponExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + char *tempReadBuffer; + + len = 0; + + //remember where to store the next one + totallen = mainBlockLen = len; + marker = VehWeaponParms+totallen; + *marker = 0; + + //now load in the extra .veh extensions +#ifdef _JK2MP + fileCnt = trap_FS_GetFileList("ext_data/vehicles/weapons", ".vwp", vehWeaponExtensionListBuf, sizeof(vehWeaponExtensionListBuf) ); +#else + fileCnt = gi.FS_GetFileList("ext_data/vehicles/weapons", ".vwp", vehWeaponExtensionListBuf, sizeof(vehWeaponExtensionListBuf) ); +#endif + + holdChar = vehWeaponExtensionListBuf; + +#ifdef _JK2MP + tempReadBuffer = (char *)BG_TempAlloc(MAX_VEH_WEAPON_DATA_SIZE); +#else + tempReadBuffer = (char *)gi.Malloc( MAX_VEH_WEAPON_DATA_SIZE, TAG_G_ALLOC, qtrue ); +#endif + + // NOTE: Not use TempAlloc anymore... + //Make ABSOLUTELY CERTAIN that BG_Alloc/etc. is not used before + //the subsequent BG_TempFree or the pool will be screwed. + + for ( i = 0; i < fileCnt; i++, holdChar += vehExtFNLen + 1 ) + { + vehExtFNLen = strlen( holdChar ); + +// Com_Printf( "Parsing %s\n", holdChar ); + +#ifdef _JK2MP + len = trap_FS_FOpenFile(va( "ext_data/vehicles/weapons/%s", holdChar), &f, FS_READ); +#else +// len = gi.FS_ReadFile( va( "ext_data/vehicles/weapons/%s", holdChar), (void **) &buffer ); + len = gi.FS_FOpenFile(va( "ext_data/vehicles/weapons/%s", holdChar), &f, FS_READ); +#endif + + if ( len == -1 ) + { + Com_Printf( "error reading file\n" ); + } + else + { +#ifdef _JK2MP + trap_FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#else + gi.FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#endif + + // Don't let it end on a } because that should be a stand-alone token. + if ( totallen && *(marker-1) == '}' ) + { + strcat( marker, " " ); + totallen++; + marker++; + } + + if ( totallen + len >= MAX_VEH_WEAPON_DATA_SIZE ) { + Com_Error(ERR_DROP, "Vehicle Weapon extensions (*.vwp) are too large" ); + } + strcat( marker, tempReadBuffer ); +#ifdef _JK2MP + trap_FS_FCloseFile( f ); +#else + gi.FS_FCloseFile( f ); +#endif + + totallen += len; + marker = VehWeaponParms+totallen; + } + } + +#ifdef _JK2MP + BG_TempFree(MAX_VEH_WEAPON_DATA_SIZE); +#else + gi.Free(tempReadBuffer); tempReadBuffer = NULL; +#endif +} + +void BG_VehicleLoadParms( void ) +{//HMM... only do this if there's a vehicle on the level? + int len, totallen, vehExtFNLen, mainBlockLen, fileCnt, i; +// const char *filename = "ext_data/vehicles.dat"; + char *holdChar, *marker; + char vehExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + char *tempReadBuffer; + + len = 0; + + //remember where to store the next one + totallen = mainBlockLen = len; + marker = VehicleParms+totallen; + *marker = 0; + + //now load in the extra .veh extensions +#ifdef _JK2MP + fileCnt = trap_FS_GetFileList("ext_data/vehicles", ".veh", vehExtensionListBuf, sizeof(vehExtensionListBuf) ); +#else + fileCnt = gi.FS_GetFileList("ext_data/vehicles", ".veh", vehExtensionListBuf, sizeof(vehExtensionListBuf) ); +#endif + + holdChar = vehExtensionListBuf; + +#ifdef _JK2MP + tempReadBuffer = (char *)BG_TempAlloc(MAX_VEHICLE_DATA_SIZE); +#else + tempReadBuffer = (char *)gi.Malloc( MAX_VEHICLE_DATA_SIZE, TAG_G_ALLOC, qtrue ); +#endif + + // NOTE: Not use TempAlloc anymore... + //Make ABSOLUTELY CERTAIN that BG_Alloc/etc. is not used before + //the subsequent BG_TempFree or the pool will be screwed. + + for ( i = 0; i < fileCnt; i++, holdChar += vehExtFNLen + 1 ) + { + vehExtFNLen = strlen( holdChar ); + +// Com_Printf( "Parsing %s\n", holdChar ); + +#ifdef _JK2MP + len = trap_FS_FOpenFile(va( "ext_data/vehicles/%s", holdChar), &f, FS_READ); +#else +// len = gi.FS_ReadFile( va( "ext_data/vehicles/%s", holdChar), (void **) &buffer ); + len = gi.FS_FOpenFile(va( "ext_data/vehicles/%s", holdChar), &f, FS_READ); +#endif + + if ( len == -1 ) + { + Com_Printf( "error reading file\n" ); + } + else + { +#ifdef _JK2MP + trap_FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#else + gi.FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#endif + + // Don't let it end on a } because that should be a stand-alone token. + if ( totallen && *(marker-1) == '}' ) + { + strcat( marker, " " ); + totallen++; + marker++; + } + + if ( totallen + len >= MAX_VEHICLE_DATA_SIZE ) { + Com_Error(ERR_DROP, "Vehicle extensions (*.veh) are too large" ); + } + strcat( marker, tempReadBuffer ); +#ifdef _JK2MP + trap_FS_FCloseFile( f ); +#else + gi.FS_FCloseFile( f ); +#endif + + totallen += len; + marker = VehicleParms+totallen; + } + } + +#ifdef _JK2MP + BG_TempFree(MAX_VEHICLE_DATA_SIZE); +#else + gi.Free(tempReadBuffer); tempReadBuffer = NULL; +#endif + + numVehicles = 1;//first one is null/default + //set the first vehicle to default data + BG_VehicleSetDefaults( &g_vehicleInfo[VEHICLE_BASE] ); + //sanity check and clamp the vehicle's data + BG_VehicleClampData( &g_vehicleInfo[VEHICLE_BASE] ); + // Setup the shared function pointers. + BG_SetSharedVehicleFunctions( &g_vehicleInfo[VEHICLE_BASE] ); + + //Load the Vehicle Weapons data, too + BG_VehWeaponLoadParms(); +} + +int BG_VehicleGetIndex( const char *vehicleName ) +{ + return (VEH_VehicleIndexForName( vehicleName )); +} + +//We get the vehicle name passed in as modelname +//with a $ in front of it. +//we are expected to then get the model for the +//vehicle and stomp over modelname with it. +void BG_GetVehicleModelName(char *modelname) +{ + char *vehName = &modelname[1]; + int vIndex = BG_VehicleGetIndex(vehName); + assert(modelname[0] == '$'); + + if (vIndex == VEHICLE_NONE) + { + Com_Error(ERR_DROP, "BG_GetVehicleModelName: couldn't find vehicle %s", vehName); + } + + strcpy(modelname, g_vehicleInfo[vIndex].model); +} + +void BG_GetVehicleSkinName(char *skinname) +{ + char *vehName = &skinname[1]; + int vIndex = BG_VehicleGetIndex(vehName); + assert(skinname[0] == '$'); + + if (vIndex == VEHICLE_NONE) + { + Com_Error(ERR_DROP, "BG_GetVehicleSkinName: couldn't find vehicle %s", vehName); + } + + if ( !g_vehicleInfo[vIndex].skin + || !g_vehicleInfo[vIndex].skin[0] ) + { + skinname[0] = 0; + } + else + { + strcpy(skinname, g_vehicleInfo[vIndex].skin); + } +} + +#ifdef _JK2MP +#ifndef WE_ARE_IN_THE_UI +//so cgame can assign the function pointer for the vehicle attachment without having to +//bother with all the other funcs that don't really exist cgame-side. +extern int BG_GetTime(void); +extern int trap_G2API_AddBolt(void *ghoul2, int modelIndex, const char *boneName); +extern qboolean trap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); +void AttachRidersGeneric( Vehicle_t *pVeh ) +{ + // If we have a pilot, attach him to the driver tag. + if ( pVeh->m_pPilot ) + { + mdxaBone_t boltMatrix; + vec3_t yawOnlyAngles; + bgEntity_t *parent = pVeh->m_pParentEntity; + bgEntity_t *pilot = pVeh->m_pPilot; + int crotchBolt = trap_G2API_AddBolt(parent->ghoul2, 0, "*driver"); + + assert(parent->playerState); + + VectorSet(yawOnlyAngles, 0, parent->playerState->viewangles[YAW], 0); + + // Get the driver tag. + trap_G2API_GetBoltMatrix( parent->ghoul2, 0, crotchBolt, &boltMatrix, + yawOnlyAngles, parent->playerState->origin, + BG_GetTime(), NULL, parent->modelScale ); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, pilot->playerState->origin ); + } +} +#endif + +#include "../namespace_end.h" + +#endif // _JK2MP + diff --git a/codemp/game/bg_vehicles.h b/codemp/game/bg_vehicles.h new file mode 100644 index 0000000..373d950 --- /dev/null +++ b/codemp/game/bg_vehicles.h @@ -0,0 +1,628 @@ +#ifndef __BG_VEHICLES_H +#define __BG_VEHICLES_H + +#include "q_shared.h" + +typedef struct Vehicle_s Vehicle_t; +typedef struct bgEntity_s bgEntity_t; + +typedef enum +{ + VH_NONE = 0, //0 just in case anyone confuses VH_NONE and VEHICLE_NONE below + VH_WALKER, //something you ride inside of, it walks like you, like an AT-ST + VH_FIGHTER, //something you fly inside of, like an X-Wing or TIE fighter + VH_SPEEDER, //something you ride on that hovers, like a speeder or swoop + VH_ANIMAL, //animal you ride on top of that walks, like a tauntaun + VH_FLIER, //animal you ride on top of that flies, like a giant mynoc? + VH_NUM_VEHICLES +} vehicleType_t; + +typedef enum +{ + WPOSE_NONE = 0, + WPOSE_BLASTER, + WPOSE_SABERLEFT, + WPOSE_SABERRIGHT, +} EWeaponPose; + +#include "../namespace_begin.h" +extern stringID_table_t VehicleTable[VH_NUM_VEHICLES+1]; +#include "../namespace_end.h" + +//=========================================================================================================== +//START VEHICLE WEAPONS +//=========================================================================================================== +typedef struct +{ +//*** IMPORTANT!!! *** the number of variables in the vehWeaponStats_t struct (including all elements of arrays) must be reflected by NUM_VWEAP_PARMS!!! +//*** IMPORTANT!!! *** vWeapFields table correponds to this structure! + char *name; + qboolean bIsProjectile; //traceline or entity? + qboolean bHasGravity; //if a projectile, drops + qboolean bIonWeapon;//disables ship shields and sends them out of control + qboolean bSaberBlockable;//lightsabers can deflect this projectile + int iMuzzleFX; //index of Muzzle Effect + int iModel; //handle to the model used by this projectile + int iShotFX; //index of Shot Effect + int iImpactFX; //index of Impact Effect + int iG2MarkShaderHandle; //index of shader to use for G2 marks made on other models when hit by this projectile + float fG2MarkSize;//size (diameter) of the ghoul2 mark + int iLoopSound; //index of loopSound + float fSpeed; //speed of projectile/range of traceline + float fHoming; //0.0 = not homing, 0.5 = half vel to targ, half cur vel, 1.0 = all vel to targ + int iLockOnTime; //0 = no lock time needed, else # of ms needed to lock on + int iDamage; //damage done when traceline or projectile directly hits target + int iSplashDamage;//damage done to ents in splashRadius of end of traceline or projectile origin on impact + float fSplashRadius;//radius that ent must be in to take splashDamage (linear fall-off) + int iAmmoPerShot; //how much "ammo" each shot takes + int iHealth; //if non-zero, projectile can be shot, takes this much damage before being destroyed + float fWidth; //width of traceline or bounding box of projecile (non-rotating!) + float fHeight; //height of traceline or bounding box of projecile (non-rotating!) + int iLifeTime; //removes itself after this amount of time + qboolean bExplodeOnExpire; //when iLifeTime is up, explodes rather than simply removing itself +} vehWeaponInfo_t; +//NOTE: this MUST stay up to date with the number of variables in the vehFields table!!! +#define NUM_VWEAP_PARMS 24 + +#define VWFOFS(x) ((int)&(((vehWeaponInfo_t *)0)->x)) + +#define MAX_VEH_WEAPONS 16 //sigh... no more than 16 different vehicle weapons +#define VEH_WEAPON_BASE 0 +#define VEH_WEAPON_NONE -1 + +#include "../namespace_begin.h" +extern vehWeaponInfo_t g_vehWeaponInfo[MAX_VEH_WEAPONS]; +extern int numVehicleWeapons; +#include "../namespace_end.h" + +//=========================================================================================================== +//END VEHICLE WEAPONS +//=========================================================================================================== + +#define MAX_VEHICLE_MUZZLES 12 +#define MAX_VEHICLE_EXHAUSTS 12 +#define MAX_VEHICLE_WEAPONS 2 +#define MAX_VEHICLE_TURRETS 2 +#define MAX_VEHICLE_TURRET_MUZZLES 2 + +typedef struct +{ + int iWeapon; //what vehWeaponInfo index to use + int iDelay; //delay between turret muzzle shots + int iAmmoMax; //how much ammo it has + int iAmmoRechargeMS; //how many MS between every point of recharged ammo + char *yawBone; //bone on ship that this turret uses to yaw + char *pitchBone; //bone on ship that this turret uses to pitch + int yawAxis; //axis on yawBone to which we should to apply the yaw angles + int pitchAxis; //axis on pitchBone to which we should to apply the pitch angles + float yawClampLeft; //how far the turret is allowed to turn left + float yawClampRight; //how far the turret is allowed to turn right + float pitchClampUp; //how far the turret is allowed to title up + float pitchClampDown; //how far the turret is allowed to tilt down + int iMuzzle[MAX_VEHICLE_TURRET_MUZZLES];//iMuzzle-1 = index of ship's muzzle to fire this turret's 1st and 2nd shots from + char *gunnerViewTag;//Where to put the view origin of the gunner (name) + float fTurnSpeed; //how quickly the turret can turn + qboolean bAI; //whether or not the turret auto-targets enemies when it's not manned + qboolean bAILead;//whether + float fAIRange; //how far away the AI will look for enemies + int passengerNum;//which passenger, if any, has control of this turret (overrides AI) +} turretStats_t; + +typedef struct +{ +//*** IMPORTANT!!! *** See note at top of next structure!!! *** + // Weapon stuff. + int ID;//index into the weapon data + // The delay between shots for each weapon. + int delay; + // Whether or not all the muzzles for each weapon can be linked together (linked delay = weapon delay * number of muzzles linked!) + int linkable; + // Whether or not to auto-aim the projectiles/tracelines at the thing under the crosshair when we fire + qboolean aimCorrect; + //maximum ammo + int ammoMax; + //ammo recharge rate - milliseconds per unit (minimum of 100, which is 10 ammo per second) + int ammoRechargeMS; + //sound to play when out of ammo (plays default "no ammo" sound if none specified) + int soundNoAmmo; +} vehWeaponStats_t; + +typedef struct +{ +//*** IMPORTANT!!! *** vehFields table correponds to this structure! + char *name; //unique name of the vehicle + + //general data + vehicleType_t type; //what kind of vehicle + int numHands; //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + float lookPitch; //How far you can look up and down off the forward of the vehicle + float lookYaw; //How far you can look left and right off the forward of the vehicle + float length; //how long it is - used for body length traces when turning/moving? + float width; //how wide it is - used for body length traces when turning/moving? + float height; //how tall it is - used for body length traces when turning/moving? + vec3_t centerOfGravity;//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats + float speedMax; //top speed + float turboSpeed; //turbo speed + float speedMin; //if < 0, can go in reverse + float speedIdle; //what speed it drifts to when no accel/decel input is given + float accelIdle; //if speedIdle > 0, how quickly it goes up to that speed + float acceleration; //when pressing on accelerator + float decelIdle; //when giving no input, how quickly it drops to speedIdle + float throttleSticks; //if true, speed stays at whatever you accel/decel to, unless you turbo or brake + float strafePerc; //multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + float bankingSpeed; //how quickly it pitches and rolls (not under player control) + float rollLimit; //how far it can roll to either side + float pitchLimit; //how far it can roll forward or backward + float braking; //when pressing on decelerator + float mouseYaw; // The mouse yaw override. + float mousePitch; // The mouse pitch override. + float turningSpeed; //how quickly you can turn + qboolean turnWhenStopped;//whether or not you can turn when not moving + float traction; //how much your command input affects velocity + float friction; //how much velocity is cut on its own + float maxSlope; //the max slope that it can go up with control + qboolean speedDependantTurning;//vehicle turns faster the faster it's going + + //durability stats + int mass; //for momentum and impact force (player mass is 10) + int armor; //total points of damage it can take + int shields; //energy shield damage points + int shieldRechargeMS;//energy shield milliseconds per point recharged + float toughness; //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + int malfunctionArmorLevel;//when armor drops to or below this point, start malfunctioning + int surfDestruction; //can parts of this thing be torn off on impact? -rww + + //individual "area" health -rww + int health_front; + int health_back; + int health_right; + int health_left; + + //visuals & sounds + char *model; //what model to use - if make it an NPC's primary model, don't need this? + char *skin; //what skin to use - if make it an NPC's primary model, don't need this? + int g2radius; //render radius for the ghoul2 model + int riderAnim; //what animation the rider uses + int radarIconHandle;//what icon to show on radar in MP + int dmgIndicFrameHandle;//what image to use for the frame of the damage indicator + int dmgIndicShieldHandle;//what image to use for the shield of the damage indicator + int dmgIndicBackgroundHandle;//what image to use for the background of the damage indicator + int iconFrontHandle;//what image to use for the front of the ship on the damage indicator + int iconBackHandle; //what image to use for the back of the ship on the damage indicator + int iconRightHandle;//what image to use for the right of the ship on the damage indicator + int iconLeftHandle; //what image to use for the left of the ship on the damage indicator + int crosshairShaderHandle;//what image to use for the left of the ship on the damage indicator + int shieldShaderHandle;//What shader to use when drawing the shield shell + char *droidNPC; //NPC to attach to *droidunit tag (if it exists in the model) + + int soundOn; //sound to play when get on it + int soundTakeOff; //sound to play when ship takes off + int soundEngineStart;//sound to play when ship's thrusters first activate + int soundLoop; //sound to loop while riding it + int soundSpin; //sound to loop while spiraling out of control + int soundTurbo; //sound to play when turbo/afterburner kicks in + int soundHyper; //sound to play when ship lands + int soundLand; //sound to play when ship lands + int soundOff; //sound to play when get off + int soundFlyBy; //sound to play when they buzz you + int soundFlyBy2; //alternate sound to play when they buzz you + int soundShift1; //sound to play when accelerating + int soundShift2; //sound to play when accelerating + int soundShift3; //sound to play when decelerating + int soundShift4; //sound to play when decelerating + + int iExhaustFX; //exhaust effect, played from "*exhaust" bolt(s) + int iTurboFX; //turbo exhaust effect, played from "*exhaust" bolt(s) when ship is in "turbo" mode + int iTurboStartFX; //turbo begin effect, played from "*exhaust" bolts when "turbo" mode begins + int iTrailFX; //trail effect, played from "*trail" bolt(s) + int iImpactFX; //impact effect, for when it bumps into something + int iExplodeFX; //explosion effect, for when it blows up (should have the sound built into explosion effect) + int iWakeFX; //effect it makes when going across water + int iDmgFX; //effect to play on damage from a weapon or something + int iInjureFX; + int iNoseFX; //effect for nose piece flying away when blown off + int iLWingFX; //effect for left wing piece flying away when blown off + int iRWingFX; //effect for right wing piece flying away when blown off + + //Weapon stats + vehWeaponStats_t weapon[MAX_VEHICLE_WEAPONS]; + + // Which weapon a muzzle fires (has to match one of the weapons this vehicle has). So 1 would be weapon 1, + // 2 would be weapon 2 and so on. + int weapMuzzle[MAX_VEHICLE_MUZZLES]; + + //turrets (if any) on the vehicle + turretStats_t turret[MAX_VEHICLE_TURRETS]; + + // The max height before this ship (?) starts (auto)landing. + float landingHeight; + + //other misc stats + int gravity; //normal is 800 + float hoverHeight; //if 0, it's a ground vehicle + float hoverStrength; //how hard it pushes off ground when less than hover height... causes "bounce", like shocks + qboolean waterProof; //can drive underwater if it has to + float bouyancy; //when in water, how high it floats (1 is neutral bouyancy) + int fuelMax; //how much fuel it can hold (capacity) + int fuelRate; //how quickly is uses up fuel + int turboDuration; //how long turbo lasts + int turboRecharge; //how long turbo takes to recharge + int visibility; //for sight alerts + int loudness; //for sound alerts + float explosionRadius;//range of explosion + int explosionDamage;//damage of explosion + + int maxPassengers; // The max number of passengers this vehicle may have (Default = 0). + qboolean hideRider; // rider (and passengers?) should not be drawn + qboolean killRiderOnDeath;//if rider is on vehicle when it dies, they should die + qboolean flammable; //whether or not the vehicle should catch on fire before it explodes + int explosionDelay; //how long the vehicle should be on fire/dying before it explodes + //camera stuff + qboolean cameraOverride; //whether or not to use all of the following 3rd person camera override values + float cameraRange; //how far back the camera should be - normal is 80 + float cameraVertOffset;//how high over the vehicle origin the camera should be - normal is 16 + float cameraHorzOffset;//how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + float cameraPitchOffset;//a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + float cameraFOV; //third person camera FOV, default is 80 + float cameraAlpha; //fade out the vehicle to this alpha (0.1-1.0f) if it's in the way of the crosshair + qboolean cameraPitchDependantVertOffset;//use the hacky AT-ST pitch dependant vertical offset + + //NOTE: some info on what vehicle weapon to use? Like ATST or TIE bomber or TIE fighter or X-Wing...? + +//===VEH_PARM_MAX======================================================================== +//*** IMPORTANT!!! *** vehFields table correponds to this structure! + +//THE FOLLOWING FIELDS are not in the vehFields table because they are internal variables, not read in from the .veh file + int modelIndex; //set internally, not until this vehicle is spawned into the level + + // NOTE: Please note that most of this stuff has been converted from C++ classes to generic C. + // This part of the structure is used to simulate inheritance for vehicles. The basic idea is that all vehicle use + // this vehicle interface since they declare their own functions and assign the function pointer to the + // corresponding function. Meanwhile, the base logic can still call the appropriate functions. In C++ talk all + // of these functions (pointers) are pure virtuals and this is an abstract base class (although it cannot be + // inherited from, only contained and reimplemented (through an object and a setup function respectively)). -AReis + + // Makes sure that the vehicle is properly animated. + void (*AnimateVehicle)( Vehicle_t *pVeh ); + + // Makes sure that the rider's in this vehicle are properly animated. + void (*AnimateRiders)( Vehicle_t *pVeh ); + + // Determine whether this entity is able to board this vehicle or not. + qboolean (*ValidateBoard)( Vehicle_t *pVeh, bgEntity_t *pEnt ); + + // Set the parent entity of this Vehicle NPC. + void (*SetParent)( Vehicle_t *pVeh, bgEntity_t *pParentEntity ); + + // Add a pilot to the vehicle. + void (*SetPilot)( Vehicle_t *pVeh, bgEntity_t *pPilot ); + + // Add a passenger to the vehicle (false if we're full). + qboolean (*AddPassenger)( Vehicle_t *pVeh ); + + // Animate the vehicle and it's riders. + void (*Animate)( Vehicle_t *pVeh ); + + // Board this Vehicle (get on). The first entity to board an empty vehicle becomes the Pilot. + qboolean (*Board)( Vehicle_t *pVeh, bgEntity_t *pEnt ); + + // Eject an entity from the vehicle. + qboolean (*Eject)( Vehicle_t *pVeh, bgEntity_t *pEnt, qboolean forceEject ); + + // Eject all the inhabitants of this vehicle. + qboolean (*EjectAll)( Vehicle_t *pVeh ); + + // Start a delay until the vehicle dies. + void (*StartDeathDelay)( Vehicle_t *pVeh, int iDelayTime ); + + // Update death sequence. + void (*DeathUpdate)( Vehicle_t *pVeh ); + + // Register all the assets used by this vehicle. + void (*RegisterAssets)( Vehicle_t *pVeh ); + + // Initialize the vehicle (should be called by Spawn?). + qboolean (*Initialize)( Vehicle_t *pVeh ); + + // Like a think or move command, this updates various vehicle properties. + qboolean (*Update)( Vehicle_t *pVeh, const usercmd_t *pUcmd ); + + // Update the properties of a Rider (that may reflect what happens to the vehicle). + // + // [return] bool True if still in vehicle, false if otherwise. + qboolean (*UpdateRider)( Vehicle_t *pVeh, bgEntity_t *pRider, usercmd_t *pUcmd ); + + // ProcessMoveCommands the Vehicle. + void (*ProcessMoveCommands)( Vehicle_t *pVeh ); + + // ProcessOrientCommands the Vehicle. + void (*ProcessOrientCommands)( Vehicle_t *pVeh ); + + // Attachs all the riders of this vehicle to their appropriate position/tag (*driver, *pass1, *pass2, whatever...). + void (*AttachRiders)( Vehicle_t *pVeh ); + + // Make someone invisible and un-collidable. + void (*Ghost)( Vehicle_t *pVeh, bgEntity_t *pEnt ); + + // Make someone visible and collidable. + void (*UnGhost)( Vehicle_t *pVeh, bgEntity_t *pEnt ); + + // Get the pilot of this vehicle. + const bgEntity_t *(*GetPilot)( Vehicle_t *pVeh ); + + // Whether this vehicle is currently inhabited (by anyone) or not. + qboolean (*Inhabited)( Vehicle_t *pVeh ); +} vehicleInfo_t; + + +#define VFOFS(x) ((int)&(((vehicleInfo_t *)0)->x)) + +#define MAX_VEHICLES 16 //sigh... no more than 64 individual vehicles +#define VEHICLE_BASE 0 +#define VEHICLE_NONE -1 + +#include "../namespace_begin.h" +extern vehicleInfo_t g_vehicleInfo[MAX_VEHICLES]; +extern int numVehicles; +#include "../namespace_end.h" + +#define VEH_DEFAULT_SPEED_MAX 800.0f +#define VEH_DEFAULT_ACCEL 10.0f +#define VEH_DEFAULT_DECEL 10.0f +#define VEH_DEFAULT_STRAFE_PERC 0.5f +#define VEH_DEFAULT_BANKING_SPEED 0.5f +#define VEH_DEFAULT_ROLL_LIMIT 60.0f +#define VEH_DEFAULT_PITCH_LIMIT 90.0f +#define VEH_DEFAULT_BRAKING 10.0f +#define VEH_DEFAULT_TURNING_SPEED 1.0f +#define VEH_DEFAULT_TRACTION 8.0f +#define VEH_DEFAULT_FRICTION 1.0f +#define VEH_DEFAULT_MAX_SLOPE 0.85f +#define VEH_DEFAULT_MASS 200 +#define VEH_DEFAULT_MAX_ARMOR 200 +#define VEH_DEFAULT_TOUGHNESS 2.5f +#define VEH_DEFAULT_GRAVITY 800 +#define VEH_DEFAULT_HOVER_HEIGHT 64.0f +#define VEH_DEFAULT_HOVER_STRENGTH 10.0f +#define VEH_DEFAULT_VISIBILITY 0 +#define VEH_DEFAULT_LOUDNESS 0 +#define VEH_DEFAULT_EXP_RAD 400.0f +#define VEH_DEFAULT_EXP_DMG 1000 +#define VEH_MAX_PASSENGERS 10 + +#define MAX_STRAFE_TIME 2000.0f//FIXME: extern? +#define MIN_LANDING_SPEED 200//equal to or less than this and close to ground = auto-slow-down to land +#define MIN_LANDING_SLOPE 0.8f//must be pretty flat to land on the surf + +#define VEH_MOUNT_THROW_LEFT -5 +#define VEH_MOUNT_THROW_RIGHT -6 + + +typedef enum +{ + VEH_EJECT_LEFT, + VEH_EJECT_RIGHT, + VEH_EJECT_FRONT, + VEH_EJECT_REAR, + VEH_EJECT_TOP, + VEH_EJECT_BOTTOM +}; + +// Vehicle flags. +typedef enum +{ + VEH_NONE = 0, VEH_FLYING = 0x00000001, VEH_CRASHING = 0x00000002, + VEH_LANDING = 0x00000004, VEH_BUCKING = 0x00000010, VEH_WINGSOPEN = 0x00000020, + VEH_GEARSOPEN = 0x00000040, VEH_SLIDEBREAKING = 0x00000080, VEH_SPINNING = 0x00000100, + VEH_OUTOFCONTROL = 0x00000200, + VEH_SABERINLEFTHAND = 0x00000400 +} vehFlags_t; + +//defines for impact damage surface stuff +#define SHIPSURF_FRONT 0 +#define SHIPSURF_BACK 1 +#define SHIPSURF_RIGHT 2 +#define SHIPSURF_LEFT 3 + +#define SHIPSURF_DAMAGE_FRONT_LIGHT 0 +#define SHIPSURF_DAMAGE_BACK_LIGHT 1 +#define SHIPSURF_DAMAGE_RIGHT_LIGHT 2 +#define SHIPSURF_DAMAGE_LEFT_LIGHT 3 +#define SHIPSURF_DAMAGE_FRONT_HEAVY 4 +#define SHIPSURF_DAMAGE_BACK_HEAVY 5 +#define SHIPSURF_DAMAGE_RIGHT_HEAVY 6 +#define SHIPSURF_DAMAGE_LEFT_HEAVY 7 + +//generic part bits +#define SHIPSURF_BROKEN_A (1<<0) //gear 1 +#define SHIPSURF_BROKEN_B (1<<1) //gear 1 +#define SHIPSURF_BROKEN_C (1<<2) //wing 1 +#define SHIPSURF_BROKEN_D (1<<3) //wing 2 +#define SHIPSURF_BROKEN_E (1<<4) //wing 3 +#define SHIPSURF_BROKEN_F (1<<5) //wing 4 +#define SHIPSURF_BROKEN_G (1<<6) //front + +typedef struct +{ + //linked firing mode + qboolean linked;//weapon 1's muzzles are in linked firing mode + //current weapon ammo + int ammo; + //debouncer for ammo recharge + int lastAmmoInc; + //which muzzle will fire next + int nextMuzzle; +} vehWeaponStatus_t; + +typedef struct +{ + //current weapon ammo + int ammo; + //debouncer for ammo recharge + int lastAmmoInc; + //which muzzle will fire next + int nextMuzzle; + //which entity they're after + int enemyEntNum; + //how long to hold on to our current enemy + int enemyHoldTime; +} vehTurretStatus_t; +// This is the implementation of the vehicle interface and any of the other variables needed. This +// is what actually represents a vehicle. -AReis. +typedef struct Vehicle_s +{ + // The entity who pilots/drives this vehicle. + // NOTE: This is redundant (since m_pParentEntity->owner _should_ be the pilot). This makes things clearer though. + bgEntity_t *m_pPilot; + + int m_iPilotTime; //if spawnflag to die without pilot and this < level.time then die. + int m_iPilotLastIndex; //index to last pilot + qboolean m_bHasHadPilot; //qtrue once the vehicle gets its first pilot + + // The passengers of this vehicle. + //bgEntity_t **m_ppPassengers; + bgEntity_t *m_ppPassengers[VEH_MAX_PASSENGERS]; + + //the droid unit NPC for this vehicle, if any + bgEntity_t *m_pDroidUnit; + + // The number of passengers currently in this vehicle. + int m_iNumPassengers; + + // The entity from which this NPC comes from. + bgEntity_t *m_pParentEntity; + + // If not zero, how long to wait before we can do anything with the vehicle (we're getting on still). + // -1 = board from left, -2 = board from right, -3 = jump/quick board. -4 & -5 = throw off existing pilot + int m_iBoarding; + + // Used to check if we've just started the boarding process + qboolean m_bWasBoarding; + + // The speed the vehicle maintains while boarding occurs (often zero) + vec3_t m_vBoardingVelocity; + + // Time modifier (must only be used in ProcessMoveCommands() and ProcessOrientCommands() and is updated in Update()). + float m_fTimeModifier; + + // Ghoul2 Animation info. + //int m_iDriverTag; + int m_iLeftExhaustTag; + int m_iRightExhaustTag; + int m_iGun1Tag; + int m_iGun1Bone; + int m_iLeftWingBone; + int m_iRightWingBone; + + int m_iExhaustTag[MAX_VEHICLE_EXHAUSTS]; + int m_iMuzzleTag[MAX_VEHICLE_MUZZLES]; + int m_iDroidUnitTag; + int m_iGunnerViewTag[MAX_VEHICLE_TURRETS];//Where to put the view origin of the gunner (index) + + //this stuff is a little bit different from SP, because I am lazy -rww + int m_iMuzzleTime[MAX_VEHICLE_MUZZLES]; + // These are updated every frame and represent the current position and direction for the specific muzzle. + vec3_t m_vMuzzlePos[MAX_VEHICLE_MUZZLES], m_vMuzzleDir[MAX_VEHICLE_MUZZLES]; + + // This is how long to wait before being able to fire a specific muzzle again. This is based on the firing rate + // so that a firing rate of 10 rounds/sec would make this value initially 100 miliseconds. + int m_iMuzzleWait[MAX_VEHICLE_MUZZLES]; + + // The user commands structure. + usercmd_t m_ucmd; + + // The direction an entity will eject from the vehicle towards. + int m_EjectDir; + + // Flags that describe the vehicles behavior. + unsigned long m_ulFlags; + + // NOTE: Vehicle Type ID, Orientation, and Armor MUST be transmitted over the net. + + // The ID of the type of vehicle this is. + int m_iVehicleTypeID; + + // Current angles of this vehicle. + //vec3_t m_vOrientation; + float *m_vOrientation; + //Yeah, since we use the SP code for vehicles, I want to use this value, but I'm going + //to make it a pointer to a vec3_t in the playerstate for prediction's sake. -rww + + // How long you have strafed left or right (increments every frame that you strafe to right, decrements every frame you strafe left) + int m_fStrafeTime; + + // Previous angles of this vehicle. + vec3_t m_vPrevOrientation; + + // Previous viewangles of the rider + vec3_t m_vPrevRiderViewAngles; + + // When control is lost on a speeder, current angular velocity is stored here and applied until landing + float m_vAngularVelocity; + + vec3_t m_vFullAngleVelocity; + + // Current armor and shields of your vehicle (explodes if armor to 0). + int m_iArmor; //hull strength - STAT_HEALTH on NPC + int m_iShields; //energy shielding - STAT_ARMOR on NPC + + //mp-specific + int m_iHitDebounce; + + // Timer for all cgame-FX...? ex: exhaust? + int m_iLastFXTime; + + // When to die. + int m_iDieTime; + + // This pointer is to a valid VehicleInfo (which could be an animal, speeder, fighter, whatever). This + // contains the functions actually used to do things to this specific kind of vehicle as well as shared + // information (max speed, type, etc...). + vehicleInfo_t *m_pVehicleInfo; + + // This trace tells us if we're within landing height. + trace_t m_LandTrace; + + // TEMP: The wing angles (used to animate it). + vec3_t m_vWingAngles; + + //amount of damage done last impact + int m_iLastImpactDmg; + + //bitflag of surfaces that have broken off + int m_iRemovedSurfaces; + + int m_iDmgEffectTime; + + // the last time this vehicle fired a turbo burst + int m_iTurboTime; + + //how long it should drop like a rock for after freed from SUSPEND + int m_iDropTime; + + int m_iSoundDebounceTimer; + + //last time we incremented the shields + int lastShieldInc; + + //so we don't hold it down and toggle it back and forth + qboolean linkWeaponToggleHeld; + + //info about our weapons (linked, ammo, etc.) + vehWeaponStatus_t weaponStatus[MAX_VEHICLE_WEAPONS]; + vehTurretStatus_t turretStatus[MAX_VEHICLE_TURRETS]; + + //the guy who was previously the pilot + bgEntity_t * m_pOldPilot; + +} Vehicle_t; + +#include "../namespace_begin.h" +extern int BG_VehicleGetIndex( const char *vehicleName ); +#include "../namespace_end.h" + +#endif // __BG_VEHICLES_H diff --git a/codemp/game/bg_weapons.c b/codemp/game/bg_weapons.c new file mode 100644 index 0000000..ce1452e --- /dev/null +++ b/codemp/game/bg_weapons.c @@ -0,0 +1,402 @@ +// Copyright (C) 2001-2002 Raven Software +// +// bg_weapons.c -- part of bg_pmove functionality + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +// Muzzle point table... +vec3_t WP_MuzzlePoint[WP_NUM_WEAPONS] = +{// Fwd, right, up. + {0, 0, 0 }, // WP_NONE, + {0 , 8, 0 }, // WP_STUN_BATON, + {0 , 8, 0 }, // WP_MELEE, + {8 , 16, 0 }, // WP_SABER, + {12, 6, -6 }, // WP_BRYAR_PISTOL, + {12, 6, -6 }, // WP_BLASTER, + {12, 6, -6 }, // WP_DISRUPTOR, + {12, 2, -6 }, // WP_BOWCASTER, + {12, 4.5, -6 }, // WP_REPEATER, + {12, 6, -6 }, // WP_DEMP2, + {12, 6, -6 }, // WP_FLECHETTE, + {12, 8, -4 }, // WP_ROCKET_LAUNCHER, + {12, 0, -4 }, // WP_THERMAL, + {12, 0, -10 }, // WP_TRIP_MINE, + {12, 0, -4 }, // WP_DET_PACK, + {12, 6, -6 }, // WP_CONCUSSION + {12, 6, -6 }, // WP_BRYAR_OLD, +}; + +weaponData_t weaponData[WP_NUM_WEAPONS] = +{ + { // WP_NONE +// "No Weapon", // char classname[32]; // Spawning name + AMMO_NONE, // int ammoIndex; // Index to proper ammo slot + 0, // int ammoLow; // Count when ammo is low + 0, // int energyPerShot; // Amount of energy used per shot + 0, // int fireTime; // Amount of time between firings + 0, // int range; // Range of weapon + 0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 0, // int altFireTime; // Amount of time between alt-firings + 0, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_STUN_BATON +// "Stun Baton", // char classname[32]; // Spawning name + AMMO_NONE, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 0, // int energyPerShot; // Amount of energy used per shot + 400, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_MELEE +// "Melee", // char classname[32]; // Spawning name + AMMO_NONE, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 0, // int energyPerShot; // Amount of energy used per shot + 400, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_SABER, +// "Lightsaber", // char classname[32]; // Spawning name + AMMO_NONE, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 0, // int energyPerShot; // Amount of energy used per shot + 100, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 100, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_BRYAR_PISTOL, +// "Bryar Pistol", // char classname[32]; // Spawning name + AMMO_BLASTER, // int ammoIndex; // Index to proper ammo slot + 0,//15, // int ammoLow; // Count when ammo is low + 0,//2, // int energyPerShot; // Amount of energy used per shot + 800,//400, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 0,//2, // int altEnergyPerShot; // Amount of energy used for alt-fire + 800,//400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0,//200, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0,//1, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0,//1500 // int altMaxCharge; // above for secondary + }, + { // WP_BLASTER +// "E11 Blaster Rifle", // char classname[32]; // Spawning name + AMMO_BLASTER, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 2, // int energyPerShot; // Amount of energy used per shot + 350, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 3, // int altEnergyPerShot; // Amount of energy used for alt-fire + 150, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_DISRUPTOR +// "Tenloss Disruptor Rifle",// char classname[32]; // Spawning name + AMMO_POWERCELL, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 5, // int energyPerShot; // Amount of energy used per shot + 600, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 6, // int altEnergyPerShot; // Amount of energy used for alt-fire + 1300, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 200, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 3, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 1700 // int altMaxCharge; // above for secondary + }, + { // WP_BOWCASTER +// "Wookiee Bowcaster", // char classname[32]; // Spawning name + AMMO_POWERCELL, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 5, // int energyPerShot; // Amount of energy used per shot + 1000, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 5, // int altEnergyPerShot; // Amount of energy used for alt-fire + 750, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 400, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 5, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 1700, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_REPEATER +// "Imperial Heavy Repeater",// char classname[32]; // Spawning name + AMMO_METAL_BOLTS, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 1, // int energyPerShot; // Amount of energy used per shot + 100, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 15, // int altEnergyPerShot; // Amount of energy used for alt-fire + 800, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_DEMP2 +// "DEMP2", // char classname[32]; // Spawning name + AMMO_POWERCELL, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 8, // int energyPerShot; // Amount of energy used per shot + 500, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 6, // int altEnergyPerShot; // Amount of energy used for alt-fire + 900, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 250, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 3, // int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 2100 // int altMaxCharge; // above for secondary + }, + { // WP_FLECHETTE +// "Golan Arms Flechette", // char classname[32]; // Spawning name + AMMO_METAL_BOLTS, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 10, // int energyPerShot; // Amount of energy used per shot + 700, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 15, // int altEnergyPerShot; // Amount of energy used for alt-fire + 800, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_ROCKET_LAUNCHER +// "Merr-Sonn Missile System", // char classname[32]; // Spawning name + AMMO_ROCKETS, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 1, // int energyPerShot; // Amount of energy used per shot + 900, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 2, // int altEnergyPerShot; // Amount of energy used for alt-fire + 1200, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_THERMAL +// "Thermal Detonator", // char classname[32]; // Spawning name + AMMO_THERMAL, // int ammoIndex; // Index to proper ammo slot + 0, // int ammoLow; // Count when ammo is low + 1, // int energyPerShot; // Amount of energy used per shot + 800, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 1, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_TRIP_MINE +// "Trip Mine", // char classname[32]; // Spawning name + AMMO_TRIPMINE, // int ammoIndex; // Index to proper ammo slot + 0, // int ammoLow; // Count when ammo is low + 1, // int energyPerShot; // Amount of energy used per shot + 800, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 1, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_DET_PACK +// "Det Pack", // char classname[32]; // Spawning name + AMMO_DETPACK, // int ammoIndex; // Index to proper ammo slot + 0, // int ammoLow; // Count when ammo is low + 1, // int energyPerShot; // Amount of energy used per shot + 800, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_CONCUSSION +// "Concussion Rifle", // char classname[32]; // Spawning name + AMMO_METAL_BOLTS, // int ammoIndex; // Index to proper ammo slot + 40, // int ammoLow; // Count when ammo is low + 40, // int energyPerShot; // Amount of energy used per shot + 800, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 50, // int altEnergyPerShot; // Amount of energy used for alt-fire + 1200, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, // int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_BRYAR_OLD, +// "Bryar Pistol", // char classname[32]; // Spawning name + AMMO_BLASTER, // int ammoIndex; // Index to proper ammo slot + 15, // int ammoLow; // Count when ammo is low + 2, // int energyPerShot; // Amount of energy used per shot + 400, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 2, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 200, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 1, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 1500 // int altMaxCharge; // above for secondary + }, + { // WP_EMPLCACED_GUN +// "Emplaced Gun", // char classname[32]; // Spawning name + /*AMMO_BLASTER*/0, // int ammoIndex; // Index to proper ammo slot + /*5*/0, // int ammoLow; // Count when ammo is low + /*2*/0, // int energyPerShot; // Amount of energy used per shot + 100, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + /*3*/0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 100, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_TURRET - NOTE NOT ACTUALLY USEABLE BY PLAYER! +// "Emplaced Gun", // char classname[32]; // Spawning name + /*AMMO_BLASTER*/0, // int ammoIndex; // Index to proper ammo slot + /*5*/0, // int ammoLow; // Count when ammo is low + /*2*/0, // int energyPerShot; // Amount of energy used per shot + 0, // int fireTime; // Amount of time between firings + 0, // int range; // Range of weapon + /*3*/0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 0, // int altFireTime; // Amount of time between alt-firings + 0, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + } +}; + +ammoData_t ammoData[AMMO_MAX] = +{ + { // AMMO_NONE +// "", // char icon[32]; // Name of ammo icon file + 0 // int max; // Max amount player can hold of ammo + }, + { // AMMO_FORCE +// "", // char icon[32]; // Name of ammo icon file + 100 // int max; // Max amount player can hold of ammo + }, + { // AMMO_BLASTER +// "", // char icon[32]; // Name of ammo icon file + 300 // int max; // Max amount player can hold of ammo + }, + { // AMMO_POWERCELL +// "", // char icon[32]; // Name of ammo icon file + 300 // int max; // Max amount player can hold of ammo + }, + { // AMMO_METAL_BOLTS +// "", // char icon[32]; // Name of ammo icon file + 300 // int max; // Max amount player can hold of ammo + }, + { // AMMO_ROCKETS +// "", // char icon[32]; // Name of ammo icon file + 25 // int max; // Max amount player can hold of ammo + }, + { // AMMO_EMPLACED +// "", // char icon[32]; // Name of ammo icon file + 800 // int max; // Max amount player can hold of ammo + }, + { // AMMO_THERMAL +// "", // char icon[32]; // Name of ammo icon file + 10 // int max; // Max amount player can hold of ammo + }, + { // AMMO_TRIPMINE +// "", // char icon[32]; // Name of ammo icon file + 10 // int max; // Max amount player can hold of ammo + }, + { // AMMO_DETPACK +// "", // char icon[32]; // Name of ammo icon file + 10 // int max; // Max amount player can hold of ammo + } +}; + + diff --git a/codemp/game/bg_weapons.h b/codemp/game/bg_weapons.h new file mode 100644 index 0000000..1e47571 --- /dev/null +++ b/codemp/game/bg_weapons.h @@ -0,0 +1,113 @@ +// Filename:- bg_weapons.h +// +// This crosses both client and server. It could all be crammed into bg_public, but isolation of this type of data is best. + +#ifndef __WEAPONS_H__ +#define __WEAPONS_H__ + +typedef enum { + WP_NONE, + + WP_STUN_BATON, + WP_MELEE, + WP_SABER, + WP_BRYAR_PISTOL, + WP_BLASTER, + WP_DISRUPTOR, + WP_BOWCASTER, + WP_REPEATER, + WP_DEMP2, + WP_FLECHETTE, + WP_ROCKET_LAUNCHER, + WP_THERMAL, + WP_TRIP_MINE, + WP_DET_PACK, + WP_CONCUSSION, + WP_BRYAR_OLD, + WP_EMPLACED_GUN, + WP_TURRET, + +// WP_GAUNTLET, +// WP_MACHINEGUN, // Bryar +// WP_SHOTGUN, // Blaster +// WP_GRENADE_LAUNCHER, // Thermal +// WP_LIGHTNING, // +// WP_RAILGUN, // +// WP_GRAPPLING_HOOK, + + WP_NUM_WEAPONS +}; +typedef int weapon_t; + +//anything > this will be considered not player useable +#define LAST_USEABLE_WEAPON WP_BRYAR_OLD + +typedef enum //# ammo_e +{ + AMMO_NONE, + AMMO_FORCE, // AMMO_PHASER + AMMO_BLASTER, // AMMO_STARFLEET, + AMMO_POWERCELL, // AMMO_ALIEN, + AMMO_METAL_BOLTS, + AMMO_ROCKETS, + AMMO_EMPLACED, + AMMO_THERMAL, + AMMO_TRIPMINE, + AMMO_DETPACK, + AMMO_MAX +} ammo_t; + + +typedef struct weaponData_s +{ +// char classname[32]; // Spawning name + + int ammoIndex; // Index to proper ammo slot + int ammoLow; // Count when ammo is low + + int energyPerShot; // Amount of energy used per shot + int fireTime; // Amount of time between firings + int range; // Range of weapon + + int altEnergyPerShot; // Amount of energy used for alt-fire + int altFireTime; // Amount of time between alt-firings + int altRange; // Range of alt-fire + + int chargeSubTime; // ms interval for subtracting ammo during charge + int altChargeSubTime; // above for secondary + + int chargeSub; // amount to subtract during charge on each interval + int altChargeSub; // above for secondary + + int maxCharge; // stop subtracting once charged for this many ms + int altMaxCharge; // above for secondary +} weaponData_t; + + +typedef struct ammoData_s +{ +// char icon[32]; // Name of ammo icon file + int max; // Max amount player can hold of ammo +} ammoData_t; + + +extern weaponData_t weaponData[WP_NUM_WEAPONS]; +extern ammoData_t ammoData[AMMO_MAX]; + + +// Specific weapon information + +#define FIRST_WEAPON WP_BRYAR_PISTOL // this is the first weapon for next and prev weapon switching +#define MAX_PLAYER_WEAPONS WP_NUM_WEAPONS-1 // this is the max you can switch to and get with the give all. + + +#define DEFAULT_SHOTGUN_SPREAD 700 +#define DEFAULT_SHOTGUN_COUNT 11 + +#define LIGHTNING_RANGE 768 + + + + + +#endif//#ifndef __WEAPONS_H__ diff --git a/codemp/game/botlib.h b/codemp/game/botlib.h new file mode 100644 index 0000000..98b9b7f --- /dev/null +++ b/codemp/game/botlib.h @@ -0,0 +1,508 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: botlib.h + * + * desc: bot AI library + * + * $Archive: /source/code/game/botai.h $ + * $Author: osman $ + * $Revision: 1.5 $ + * $Modtime: 03/01/00 3:32p $ + * $Date: 2003/09/26 02:43:45 $ + * + *****************************************************************************/ + +#define BOTLIB_API_VERSION 2 + +struct aas_clientmove_s; +struct aas_entityinfo_s; +struct aas_areainfo_s; +struct aas_altroutegoal_s; +struct aas_predictroute_s; +struct bot_consolemessage_s; +struct bot_match_s; +struct bot_goal_s; +struct bot_moveresult_s; +struct bot_initmove_s; +struct weaponinfo_s; + +#define BOTFILESBASEFOLDER "botfiles" +//debug line colors +#define LINECOLOR_NONE -1 +#define LINECOLOR_RED 1//0xf2f2f0f0L +#define LINECOLOR_GREEN 2//0xd0d1d2d3L +#define LINECOLOR_BLUE 3//0xf3f3f1f1L +#define LINECOLOR_YELLOW 4//0xdcdddedfL +#define LINECOLOR_ORANGE 5//0xe0e1e2e3L + +//Print types +#define PRT_MESSAGE 1 +#define PRT_WARNING 2 +#define PRT_ERROR 3 +#define PRT_FATAL 4 +#define PRT_EXIT 5 + +//console message types +#define CMS_NORMAL 0 +#define CMS_CHAT 1 + +//botlib error codes +#define BLERR_NOERROR 0 //no error +#define BLERR_LIBRARYNOTSETUP 1 //library not setup +#define BLERR_INVALIDENTITYNUMBER 2 //invalid entity number +#define BLERR_NOAASFILE 3 //no AAS file available +#define BLERR_CANNOTOPENAASFILE 4 //cannot open AAS file +#define BLERR_WRONGAASFILEID 5 //incorrect AAS file id +#define BLERR_WRONGAASFILEVERSION 6 //incorrect AAS file version +#define BLERR_CANNOTREADAASLUMP 7 //cannot read AAS file lump +#define BLERR_CANNOTLOADICHAT 8 //cannot load initial chats +#define BLERR_CANNOTLOADITEMWEIGHTS 9 //cannot load item weights +#define BLERR_CANNOTLOADITEMCONFIG 10 //cannot load item config +#define BLERR_CANNOTLOADWEAPONWEIGHTS 11 //cannot load weapon weights +#define BLERR_CANNOTLOADWEAPONCONFIG 12 //cannot load weapon config + +//action flags +#define ACTION_ATTACK 0x0000001 +#define ACTION_USE 0x0000002 +#define ACTION_RESPAWN 0x0000008 +#define ACTION_JUMP 0x0000010 +#define ACTION_MOVEUP 0x0000020 +#define ACTION_CROUCH 0x0000080 +#define ACTION_MOVEDOWN 0x0000100 +#define ACTION_MOVEFORWARD 0x0000200 +#define ACTION_MOVEBACK 0x0000800 +#define ACTION_MOVELEFT 0x0001000 +#define ACTION_MOVERIGHT 0x0002000 +#define ACTION_DELAYEDJUMP 0x0008000 +#define ACTION_TALK 0x0010000 +#define ACTION_GESTURE 0x0020000 +#define ACTION_WALK 0x0080000 +#define ACTION_FORCEPOWER 0x0100000 +#define ACTION_ALT_ATTACK 0x0200000 +/* +#define ACTION_AFFIRMATIVE 0x0100000 +#define ACTION_NEGATIVE 0x0200000 +#define ACTION_GETFLAG 0x0800000 +#define ACTION_GUARDBASE 0x1000000 +#define ACTION_PATROL 0x2000000 +#define ACTION_FOLLOWME 0x8000000 +*/ + +//the bot input, will be converted to an usercmd_t +typedef struct bot_input_s +{ + float thinktime; //time since last output (in seconds) + vec3_t dir; //movement direction + float speed; //speed in the range [0, 400] + vec3_t viewangles; //the view angles + int actionflags; //one of the ACTION_? flags + int weapon; //weapon to use +} bot_input_t; + +#ifndef BSPTRACE + +#define BSPTRACE + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//remove the bsp_trace_s structure definition l8r on +//a trace is returned when a box is swept through the world +typedef struct bsp_trace_s +{ + 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 + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // the hit point surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; + +#endif // BSPTRACE + +//entity state +typedef struct bot_entitystate_s +{ + int type; // entity type + int flags; // entity flags + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; + int torsoAnim; +} bot_entitystate_t; + +//bot AI library exported functions +typedef struct botlib_import_s +{ + //print messages from the bot library + void (QDECL *Print)(int type, char *fmt, ...); + //trace a bbox through the world + void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); + //trace a bbox against a specific entity + void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask); + //retrieve the contents at the given point + int (*PointContents)(vec3_t point); + //check if the point is in potential visible sight + int (*inPVS)(vec3_t p1, vec3_t p2); + //retrieve the BSP entity data lump + char *(*BSPEntityData)(void); + // + void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); + //send a bot client command + void (*BotClientCommand)(int client, char *command); + //memory allocation + void *(*GetMemory)(int size); // allocate from Zone + void (*FreeMemory)(void *ptr); // free memory from Zone + int (*AvailableMemory)(void); // available Zone memory + void *(*HunkAlloc)(int size); // allocate from hunk + //file system access + int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); + int (*FS_Read)( void *buffer, int len, fileHandle_t f ); + int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); + void (*FS_FCloseFile)( fileHandle_t f ); + int (*FS_Seek)( fileHandle_t f, long offset, int origin ); + //debug visualisation stuff + int (*DebugLineCreate)(void); + void (*DebugLineDelete)(int line); + void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color); + // + int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points); + void (*DebugPolygonDelete)(int id); +} botlib_import_t; + +typedef struct aas_export_s +{ + //----------------------------------- + // be_aas_entity.h + //----------------------------------- + void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info); + //----------------------------------- + // be_aas_main.h + //----------------------------------- + int (*AAS_Initialized)(void); + void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs); + float (*AAS_Time)(void); + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + int (*AAS_PointAreaNum)(vec3_t point); + int (*AAS_PointReachabilityAreaIndex)( vec3_t point ); + int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); + int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info ); + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + int (*AAS_PointContents)(vec3_t point); + int (*AAS_NextBSPEntity)(int ent); + int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size); + int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v); + int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value); + int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value); + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + int (*AAS_AreaReachability)(int areanum); + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags); + int (*AAS_EnableRoutingArea)(int areanum, int enable); + int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum); + //-------------------------------------------- + // be_aas_altroute.c + //-------------------------------------------- + int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals, + int type); + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + int (*AAS_Swimming)(vec3_t origin); + int (*AAS_PredictClientMovement)(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize); +} aas_export_t; + +typedef struct ea_export_s +{ + //ClientCommand elementary actions + void (*EA_Command)(int client, char *command ); + void (*EA_Say)(int client, char *str); + void (*EA_SayTeam)(int client, char *str); + // + void (*EA_Action)(int client, int action); + void (*EA_Gesture)(int client); + void (*EA_Talk)(int client); + void (*EA_Attack)(int client); + void (*EA_Use)(int client); + void (*EA_Respawn)(int client); + void (*EA_MoveUp)(int client); + void (*EA_MoveDown)(int client); + void (*EA_MoveForward)(int client); + void (*EA_MoveBack)(int client); + void (*EA_MoveLeft)(int client); + void (*EA_MoveRight)(int client); + void (*EA_Crouch)(int client); + void (*EA_Alt_Attack)(int client); + void (*EA_ForcePower)(int client); + + void (*EA_SelectWeapon)(int client, int weapon); + void (*EA_Jump)(int client); + void (*EA_DelayedJump)(int client); + void (*EA_Move)(int client, vec3_t dir, float speed); + void (*EA_View)(int client, vec3_t viewangles); + //send regular input to the server + void (*EA_EndRegular)(int client, float thinktime); + void (*EA_GetInput)(int client, float thinktime, bot_input_t *input); + void (*EA_ResetInput)(int client); +} ea_export_t; + +typedef struct ai_export_s +{ + //----------------------------------- + // be_ai_char.h + //----------------------------------- + int (*BotLoadCharacter)(char *charfile, float skill); + void (*BotFreeCharacter)(int character); + float (*Characteristic_Float)(int character, int index); + float (*Characteristic_BFloat)(int character, int index, float min, float max); + int (*Characteristic_Integer)(int character, int index); + int (*Characteristic_BInteger)(int character, int index, int min, int max); + void (*Characteristic_String)(int character, int index, char *buf, int size); + //----------------------------------- + // be_ai_chat.h + //----------------------------------- +// int (*BotAllocChatState)(void); +// void (*BotFreeChatState)(int handle); +// void (*BotQueueConsoleMessage)(int chatstate, int type, char *message); +// void (*BotRemoveConsoleMessage)(int chatstate, int handle); +// int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm); +// int (*BotNumConsoleMessages)(int chatstate); +// void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +// int (*BotNumInitialChats)(int chatstate, char *type); +// int (*BotReplyChat)(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +// int (*BotChatLength)(int chatstate); +// void (*BotEnterChat)(int chatstate, int client, int sendto); +// void (*BotGetChatMessage)(int chatstate, char *buf, int size); +// int (*StringContains)(char *str1, char *str2, int casesensitive); +// int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context); +// void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size); +// void (*UnifyWhiteSpaces)(char *string); +// void (*BotReplaceSynonyms)(char *string, unsigned long int context); +// int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname); +// void (*BotSetChatGender)(int chatstate, int gender); +// void (*BotSetChatName)(int chatstate, char *name, int client); + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + void (*BotResetGoalState)(int goalstate); + void (*BotResetAvoidGoals)(int goalstate); + void (*BotRemoveFromAvoidGoals)(int goalstate, int number); + void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal); + void (*BotPopGoal)(int goalstate); + void (*BotEmptyGoalStack)(int goalstate); + void (*BotDumpAvoidGoals)(int goalstate); + void (*BotDumpGoalStack)(int goalstate); + void (*BotGoalName)(int number, char *name, int size); + int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags); + int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags, + struct bot_goal_s *ltg, float maxtime); + int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal); + int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal); + int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal); + int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal); + int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal); + float (*BotAvoidGoalTime)(int goalstate, int number); + void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime); + void (*BotInitLevelItems)(void); + void (*BotUpdateEntityItems)(void); + int (*BotLoadItemWeights)(int goalstate, char *filename); + void (*BotFreeItemWeights)(int goalstate); + void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child); + void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename); + void (*BotMutateGoalFuzzyLogic)(int goalstate, float range); + int (*BotAllocGoalState)(int client); + void (*BotFreeGoalState)(int handle); + //----------------------------------- + // be_ai_move.h + //----------------------------------- + void (*BotResetMoveState)(int movestate); + void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags); + int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type); + void (*BotResetAvoidReach)(int movestate); + void (*BotResetLastAvoidReach)(int movestate); + int (*BotReachabilityArea)(vec3_t origin, int testground); + int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target); + int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target); + int (*BotAllocMoveState)(void); + void (*BotFreeMoveState)(int handle); + void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove); + void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type); + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory); + void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo); + int (*BotLoadWeaponWeights)(int weaponstate, char *filename); + int (*BotAllocWeaponState)(void); + void (*BotFreeWeaponState)(int weaponstate); + void (*BotResetWeaponState)(int weaponstate); + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child); +} ai_export_t; + +//bot AI library imported functions +typedef struct botlib_export_s +{ + //Area Awareness System functions + aas_export_t aas; + //Elementary Action functions + ea_export_t ea; + //AI functions + ai_export_t ai; + //setup the bot library, returns BLERR_ + int (*BotLibSetup)(void); + //shutdown the bot library, returns BLERR_ + int (*BotLibShutdown)(void); + //sets a library variable returns BLERR_ + int (*BotLibVarSet)(char *var_name, char *value); + //gets a library variable returns BLERR_ + int (*BotLibVarGet)(char *var_name, char *value, int size); + + //sets a C-like define returns BLERR_ + int (*PC_AddGlobalDefine)(char *string); + int (*PC_LoadSourceHandle)(const char *filename); + int (*PC_FreeSourceHandle)(int handle); + int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token); + int (*PC_SourceFileAndLine)(int handle, char *filename, int *line); + int (*PC_LoadGlobalDefines)(const char* filename ); + void (*PC_RemoveAllGlobalDefines) ( void ); + + //start a frame in the bot library + int (*BotLibStartFrame)(float time); + //load a new map in the bot library + int (*BotLibLoadMap)(const char *mapname); + //entity updates + int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state); + //just for testing + int (*Test)(int parm0, char *parm1, vec3_t parm2, vec3_t parm3); +} botlib_export_t; + +//linking of bot library +botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); + +/* Library variables: + +name: default: module(s): description: + +"basedir" "" l_utils.c base directory +"gamedir" "" l_utils.c game directory +"cddir" "" l_utils.c CD directory + +"log" "0" l_log.c enable/disable creating a log file +"maxclients" "4" be_interface.c maximum number of clients +"maxentities" "1024" be_interface.c maximum number of entities +"bot_developer" "0" be_interface.c bot developer mode + +"phys_friction" "6" be_aas_move.c ground friction +"phys_stopspeed" "100" be_aas_move.c stop speed +"phys_gravity" "800" be_aas_move.c gravity value +"phys_waterfriction" "1" be_aas_move.c water friction +"phys_watergravity" "400" be_aas_move.c gravity in water +"phys_maxvelocity" "320" be_aas_move.c maximum velocity +"phys_maxwalkvelocity" "320" be_aas_move.c maximum walk velocity +"phys_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity +"phys_maxswimvelocity" "150" be_aas_move.c maximum swim velocity +"phys_walkaccelerate" "10" be_aas_move.c walk acceleration +"phys_airaccelerate" "1" be_aas_move.c air acceleration +"phys_swimaccelerate" "4" be_aas_move.c swim acceleration +"phys_maxstep" "18" be_aas_move.c maximum step height +"phys_maxsteepness" "0.7" be_aas_move.c maximum floor steepness +"phys_maxbarrier" "32" be_aas_move.c maximum barrier height +"phys_maxwaterjump" "19" be_aas_move.c maximum waterjump height +"phys_jumpvel" "270" be_aas_move.c jump z velocity +"phys_falldelta5" "40" be_aas_move.c +"phys_falldelta10" "60" be_aas_move.c +"rs_waterjump" "400" be_aas_move.c +"rs_teleport" "50" be_aas_move.c +"rs_barrierjump" "100" be_aas_move.c +"rs_startcrouch" "300" be_aas_move.c +"rs_startgrapple" "500" be_aas_move.c +"rs_startwalkoffledge" "70" be_aas_move.c +"rs_startjump" "300" be_aas_move.c +"rs_rocketjump" "500" be_aas_move.c +"rs_bfgjump" "500" be_aas_move.c +"rs_jumppad" "250" be_aas_move.c +"rs_aircontrolledjumppad" "300" be_aas_move.c +"rs_funcbob" "300" be_aas_move.c +"rs_startelevator" "50" be_aas_move.c +"rs_falldamage5" "300" be_aas_move.c +"rs_falldamage10" "500" be_aas_move.c +"rs_maxjumpfallheight" "450" be_aas_move.c + +"max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS +"max_routingcache" "4096" be_aas_route.c maximum routing cache size in KB +"forceclustering" "0" be_aas_main.c force recalculation of clusters +"forcereachability" "0" be_aas_main.c force recalculation of reachabilities +"forcewrite" "0" be_aas_main.c force writing of aas file +"aasoptimize" "0" be_aas_main.c enable aas optimization +"sv_mapChecksum" "0" be_aas_main.c BSP file checksum +"bot_visualizejumppads" "0" be_aas_reach.c visualize jump pads + +"bot_reloadcharacters" "0" - reload bot character files +"ai_gametype" "0" be_ai_goal.c game type +"droppedweight" "1000" be_ai_goal.c additional dropped item weight +"weapindex_rocketlauncher" "5" be_ai_move.c rl weapon index for rocket jumping +"weapindex_bfg10k" "9" be_ai_move.c bfg weapon index for bfg jumping +"weapindex_grapple" "10" be_ai_move.c grapple weapon index for grappling +"entitytypemissile" "3" be_ai_move.c ET_MISSILE +"offhandgrapple" "0" be_ai_move.c enable off hand grapple hook +"cmd_grappleon" "grappleon" be_ai_move.c command to activate off hand grapple +"cmd_grappleoff" "grappleoff" be_ai_move.c command to deactivate off hand grapple +"itemconfig" "items.c" be_ai_goal.c item configuration file +"weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file +"synfile" "syn.c" be_ai_chat.c file with synonyms +"rndfile" "rnd.c" be_ai_chat.c file with random strings +"matchfile" "match.c" be_ai_chat.c file with match strings +"nochat" "0" be_ai_chat.c disable chats +"max_messages" "1024" be_ai_chat.c console message heap size +"max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info +"max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info +"max_iteminfo" "256" be_ai_goal.c maximum number of item info +"max_levelitems" "256" be_ai_goal.c maximum number of level items + +*/ + diff --git a/codemp/game/chars.h b/codemp/game/chars.h new file mode 100644 index 0000000..98d046e --- /dev/null +++ b/codemp/game/chars.h @@ -0,0 +1,124 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +//=========================================================================== +// +// Name: chars.h +// Function: bot characteristics +// Programmer: Mr Elusive (MrElusive@idsoftware.com) +// Last update: 1999-09-08 +// Tab Size: 4 (real tabs) +//=========================================================================== + + +//======================================================== +//======================================================== +//name +#define CHARACTERISTIC_NAME 0 //string +//gender of the bot +#define CHARACTERISTIC_GENDER 1 //string ("male", "female", "it") +//attack skill +// > 0.0 && < 0.2 = don't move +// > 0.3 && < 1.0 = aim at enemy during retreat +// > 0.0 && < 0.4 = only move forward/backward +// >= 0.4 && < 1.0 = circle strafing +// > 0.7 && < 1.0 = random strafe direction change +#define CHARACTERISTIC_ATTACK_SKILL 2 //float [0, 1] +//weapon weight file +#define CHARACTERISTIC_WEAPONWEIGHTS 3 //string +//view angle difference to angle change factor +#define CHARACTERISTIC_VIEW_FACTOR 4 //float <0, 1] +//maximum view angle change +#define CHARACTERISTIC_VIEW_MAXCHANGE 5 //float [1, 360] +//reaction time in seconds +#define CHARACTERISTIC_REACTIONTIME 6 //float [0, 5] +//accuracy when aiming +#define CHARACTERISTIC_AIM_ACCURACY 7 //float [0, 1] +//weapon specific aim accuracy +#define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN 8 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN 9 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER 10 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER 11 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING 12 +#define CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN 13 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_RAILGUN 14 +#define CHARACTERISTIC_AIM_ACCURACY_BFG10K 15 //float [0, 1] +//skill when aiming +// > 0.0 && < 0.9 = aim is affected by enemy movement +// > 0.4 && <= 0.8 = enemy linear leading +// > 0.8 && <= 1.0 = enemy exact movement leading +// > 0.5 && <= 1.0 = prediction shots when enemy is not visible +// > 0.6 && <= 1.0 = splash damage by shooting nearby geometry +#define CHARACTERISTIC_AIM_SKILL 16 //float [0, 1] +//weapon specific aim skill +#define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER 17 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER 18 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_PLASMAGUN 19 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_BFG10K 20 //float [0, 1] +//======================================================== +//chat +//======================================================== +//file with chats +#define CHARACTERISTIC_CHAT_FILE 21 //string +//name of the chat character +#define CHARACTERISTIC_CHAT_NAME 22 //string +//characters per minute type speed +#define CHARACTERISTIC_CHAT_CPM 23 //integer [1, 4000] +//tendency to insult/praise +#define CHARACTERISTIC_CHAT_INSULT 24 //float [0, 1] +//tendency to chat misc +#define CHARACTERISTIC_CHAT_MISC 25 //float [0, 1] +//tendency to chat at start or end of level +#define CHARACTERISTIC_CHAT_STARTENDLEVEL 26 //float [0, 1] +//tendency to chat entering or exiting the game +#define CHARACTERISTIC_CHAT_ENTEREXITGAME 27 //float [0, 1] +//tendency to chat when killed someone +#define CHARACTERISTIC_CHAT_KILL 28 //float [0, 1] +//tendency to chat when died +#define CHARACTERISTIC_CHAT_DEATH 29 //float [0, 1] +//tendency to chat when enemy suicides +#define CHARACTERISTIC_CHAT_ENEMYSUICIDE 30 //float [0, 1] +//tendency to chat when hit while talking +#define CHARACTERISTIC_CHAT_HITTALKING 31 //float [0, 1] +//tendency to chat when bot was hit but didn't dye +#define CHARACTERISTIC_CHAT_HITNODEATH 32 //float [0, 1] +//tendency to chat when bot hit the enemy but enemy didn't dye +#define CHARACTERISTIC_CHAT_HITNOKILL 33 //float [0, 1] +//tendency to randomly chat +#define CHARACTERISTIC_CHAT_RANDOM 34 //float [0, 1] +//tendency to reply +#define CHARACTERISTIC_CHAT_REPLY 35 //float [0, 1] +//======================================================== +//movement +//======================================================== +//tendency to crouch +#define CHARACTERISTIC_CROUCHER 36 //float [0, 1] +//tendency to jump +#define CHARACTERISTIC_JUMPER 37 //float [0, 1] +//tendency to walk +#define CHARACTERISTIC_WALKER 48 //float [0, 1] +//tendency to jump using a weapon +#define CHARACTERISTIC_WEAPONJUMPING 38 //float [0, 1] +//tendency to use the grapple hook when available +#define CHARACTERISTIC_GRAPPLE_USER 39 //float [0, 1] //use this!! +//======================================================== +//goal +//======================================================== +//item weight file +#define CHARACTERISTIC_ITEMWEIGHTS 40 //string +//the aggression of the bot +#define CHARACTERISTIC_AGGRESSION 41 //float [0, 1] +//the self preservation of the bot (rockets near walls etc.) +#define CHARACTERISTIC_SELFPRESERVATION 42 //float [0, 1] +//how likely the bot is to take revenge +#define CHARACTERISTIC_VENGEFULNESS 43 //float [0, 1] //use this!! +//tendency to camp +#define CHARACTERISTIC_CAMPER 44 //float [0, 1] +//======================================================== +//======================================================== +//tendency to get easy frags +#define CHARACTERISTIC_EASY_FRAGGER 45 //float [0, 1] +//how alert the bot is (view distance) +#define CHARACTERISTIC_ALERTNESS 46 //float [0, 1] +//how much the bot fires it's weapon +#define CHARACTERISTIC_FIRETHROTTLE 47 //float [0, 1] + diff --git a/codemp/game/g_ICARUScb.c b/codemp/game/g_ICARUScb.c new file mode 100644 index 0000000..70ca9fe --- /dev/null +++ b/codemp/game/g_ICARUScb.c @@ -0,0 +1,5798 @@ +//==================================================================================== +// +//rww - ICARUS callback file, all that can be handled within vm's is handled in here. +// +//==================================================================================== + +#include "q_shared.h" +#include "bg_public.h" +#include "b_local.h" +#include "../icarus/Q3_Interface.h" +#include "../icarus/Q3_Registers.h" +#include "g_nav.h" + +#include "../namespace_begin.h" +qboolean BG_SabersOff( playerState_t *ps ); +#include "../namespace_end.h" + +extern stringID_table_t BSTable[]; + +//This is a hack I guess. It's because we can't include the file this enum is in +//unless we're using cpp. But we need it for the interpreter stuff. +//In any case, DO NOT modify this enum. + +// Hack++ +// This code is compiled as C++ on Xbox. We could try and rig something above +// so that we only get the C version of the includes (no full Icarus) in that +// scenario, but I think we'll just try to leave this out instead. +#ifndef _XBOX +#ifndef __linux__ +enum +{ + TK_EOF = -1, + TK_UNDEFINED, + TK_COMMENT, + TK_EOL, + TK_CHAR, + TK_STRING, + TK_INT, + TK_INTEGER = TK_INT, + TK_FLOAT, + TK_IDENTIFIER, + TK_USERDEF, +}; +#endif +#endif + +#include "../icarus/interpreter.h" + +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +stringID_table_t setTable[] = +{ + ENUM2STRING(SET_SPAWNSCRIPT),//0 + ENUM2STRING(SET_USESCRIPT), + ENUM2STRING(SET_AWAKESCRIPT), + ENUM2STRING(SET_ANGERSCRIPT), + ENUM2STRING(SET_ATTACKSCRIPT), + ENUM2STRING(SET_VICTORYSCRIPT), + ENUM2STRING(SET_PAINSCRIPT), + ENUM2STRING(SET_FLEESCRIPT), + ENUM2STRING(SET_DEATHSCRIPT), + ENUM2STRING(SET_DELAYEDSCRIPT), + ENUM2STRING(SET_BLOCKEDSCRIPT), + ENUM2STRING(SET_FFIRESCRIPT), + ENUM2STRING(SET_FFDEATHSCRIPT), + ENUM2STRING(SET_MINDTRICKSCRIPT), + ENUM2STRING(SET_NO_MINDTRICK), + ENUM2STRING(SET_ORIGIN), + ENUM2STRING(SET_TELEPORT_DEST), + ENUM2STRING(SET_ANGLES), + ENUM2STRING(SET_XVELOCITY), + ENUM2STRING(SET_YVELOCITY), + ENUM2STRING(SET_ZVELOCITY), + ENUM2STRING(SET_Z_OFFSET), + ENUM2STRING(SET_ENEMY), + ENUM2STRING(SET_LEADER), + ENUM2STRING(SET_NAVGOAL), + ENUM2STRING(SET_ANIM_UPPER), + ENUM2STRING(SET_ANIM_LOWER), + ENUM2STRING(SET_ANIM_BOTH), + ENUM2STRING(SET_ANIM_HOLDTIME_LOWER), + ENUM2STRING(SET_ANIM_HOLDTIME_UPPER), + ENUM2STRING(SET_ANIM_HOLDTIME_BOTH), + ENUM2STRING(SET_PLAYER_TEAM), + ENUM2STRING(SET_ENEMY_TEAM), + ENUM2STRING(SET_BEHAVIOR_STATE), + ENUM2STRING(SET_BEHAVIOR_STATE), + ENUM2STRING(SET_HEALTH), + ENUM2STRING(SET_ARMOR), + ENUM2STRING(SET_DEFAULT_BSTATE), + ENUM2STRING(SET_CAPTURE), + ENUM2STRING(SET_DPITCH), + ENUM2STRING(SET_DYAW), + ENUM2STRING(SET_EVENT), + ENUM2STRING(SET_TEMP_BSTATE), + ENUM2STRING(SET_COPY_ORIGIN), + ENUM2STRING(SET_VIEWTARGET), + ENUM2STRING(SET_WEAPON), + ENUM2STRING(SET_ITEM), + ENUM2STRING(SET_WALKSPEED), + ENUM2STRING(SET_RUNSPEED), + ENUM2STRING(SET_YAWSPEED), + ENUM2STRING(SET_AGGRESSION), + ENUM2STRING(SET_AIM), + ENUM2STRING(SET_FRICTION), + ENUM2STRING(SET_GRAVITY), + ENUM2STRING(SET_IGNOREPAIN), + ENUM2STRING(SET_IGNOREENEMIES), + ENUM2STRING(SET_IGNOREALERTS), + ENUM2STRING(SET_DONTSHOOT), + ENUM2STRING(SET_DONTFIRE), + ENUM2STRING(SET_LOCKED_ENEMY), + ENUM2STRING(SET_NOTARGET), + ENUM2STRING(SET_LEAN), + ENUM2STRING(SET_CROUCHED), + ENUM2STRING(SET_WALKING), + ENUM2STRING(SET_RUNNING), + ENUM2STRING(SET_CHASE_ENEMIES), + ENUM2STRING(SET_LOOK_FOR_ENEMIES), + ENUM2STRING(SET_FACE_MOVE_DIR), + ENUM2STRING(SET_ALT_FIRE), + ENUM2STRING(SET_DONT_FLEE), + ENUM2STRING(SET_FORCED_MARCH), + ENUM2STRING(SET_NO_RESPONSE), + ENUM2STRING(SET_NO_COMBAT_TALK), + ENUM2STRING(SET_NO_ALERT_TALK), + ENUM2STRING(SET_UNDYING), + ENUM2STRING(SET_TREASONED), + ENUM2STRING(SET_DISABLE_SHADER_ANIM), + ENUM2STRING(SET_SHADER_ANIM), + ENUM2STRING(SET_INVINCIBLE), + ENUM2STRING(SET_NOAVOID), + ENUM2STRING(SET_SHOOTDIST), + ENUM2STRING(SET_TARGETNAME), + ENUM2STRING(SET_TARGET), + ENUM2STRING(SET_TARGET2), + ENUM2STRING(SET_LOCATION), + ENUM2STRING(SET_PAINTARGET), + ENUM2STRING(SET_TIMESCALE), + ENUM2STRING(SET_VISRANGE), + ENUM2STRING(SET_EARSHOT), + ENUM2STRING(SET_VIGILANCE), + ENUM2STRING(SET_HFOV), + ENUM2STRING(SET_VFOV), + ENUM2STRING(SET_DELAYSCRIPTTIME), + ENUM2STRING(SET_FORWARDMOVE), + ENUM2STRING(SET_RIGHTMOVE), + ENUM2STRING(SET_LOCKYAW), + ENUM2STRING(SET_SOLID), + ENUM2STRING(SET_CAMERA_GROUP), + ENUM2STRING(SET_CAMERA_GROUP_Z_OFS), + ENUM2STRING(SET_CAMERA_GROUP_TAG), + ENUM2STRING(SET_LOOK_TARGET), + ENUM2STRING(SET_ADDRHANDBOLT_MODEL), + ENUM2STRING(SET_REMOVERHANDBOLT_MODEL), + ENUM2STRING(SET_ADDLHANDBOLT_MODEL), + ENUM2STRING(SET_REMOVELHANDBOLT_MODEL), + ENUM2STRING(SET_FACEAUX), + ENUM2STRING(SET_FACEBLINK), + ENUM2STRING(SET_FACEBLINKFROWN), + ENUM2STRING(SET_FACEFROWN), + ENUM2STRING(SET_FACENORMAL), + ENUM2STRING(SET_FACEEYESCLOSED), + ENUM2STRING(SET_FACEEYESOPENED), + ENUM2STRING(SET_SCROLLTEXT), + ENUM2STRING(SET_LCARSTEXT), + ENUM2STRING(SET_SCROLLTEXTCOLOR), + ENUM2STRING(SET_CAPTIONTEXTCOLOR), + ENUM2STRING(SET_CENTERTEXTCOLOR), + ENUM2STRING(SET_PLAYER_USABLE), + ENUM2STRING(SET_STARTFRAME), + ENUM2STRING(SET_ENDFRAME), + ENUM2STRING(SET_ANIMFRAME), + ENUM2STRING(SET_LOOP_ANIM), + ENUM2STRING(SET_INTERFACE), + ENUM2STRING(SET_SHIELDS), + ENUM2STRING(SET_NO_KNOCKBACK), + ENUM2STRING(SET_INVISIBLE), + ENUM2STRING(SET_VAMPIRE), + ENUM2STRING(SET_FORCE_INVINCIBLE), + ENUM2STRING(SET_GREET_ALLIES), + ENUM2STRING(SET_PLAYER_LOCKED), + ENUM2STRING(SET_LOCK_PLAYER_WEAPONS), + ENUM2STRING(SET_NO_IMPACT_DAMAGE), + ENUM2STRING(SET_PARM1), + ENUM2STRING(SET_PARM2), + ENUM2STRING(SET_PARM3), + ENUM2STRING(SET_PARM4), + ENUM2STRING(SET_PARM5), + ENUM2STRING(SET_PARM6), + ENUM2STRING(SET_PARM7), + ENUM2STRING(SET_PARM8), + ENUM2STRING(SET_PARM9), + ENUM2STRING(SET_PARM10), + ENUM2STRING(SET_PARM11), + ENUM2STRING(SET_PARM12), + ENUM2STRING(SET_PARM13), + ENUM2STRING(SET_PARM14), + ENUM2STRING(SET_PARM15), + ENUM2STRING(SET_PARM16), + ENUM2STRING(SET_DEFEND_TARGET), + ENUM2STRING(SET_WAIT), + ENUM2STRING(SET_COUNT), + ENUM2STRING(SET_SHOT_SPACING), + ENUM2STRING(SET_VIDEO_PLAY), + ENUM2STRING(SET_VIDEO_FADE_IN), + ENUM2STRING(SET_VIDEO_FADE_OUT), + ENUM2STRING(SET_REMOVE_TARGET), + ENUM2STRING(SET_LOADGAME), + ENUM2STRING(SET_MENU_SCREEN), + ENUM2STRING(SET_OBJECTIVE_SHOW), + ENUM2STRING(SET_OBJECTIVE_HIDE), + ENUM2STRING(SET_OBJECTIVE_SUCCEEDED), + ENUM2STRING(SET_OBJECTIVE_FAILED), + ENUM2STRING(SET_MISSIONFAILED), + ENUM2STRING(SET_TACTICAL_SHOW), + ENUM2STRING(SET_TACTICAL_HIDE), + ENUM2STRING(SET_FOLLOWDIST), + ENUM2STRING(SET_SCALE), + ENUM2STRING(SET_OBJECTIVE_CLEARALL), + ENUM2STRING(SET_MISSIONSTATUSTEXT), + ENUM2STRING(SET_WIDTH), + ENUM2STRING(SET_CLOSINGCREDITS), + ENUM2STRING(SET_SKILL), + ENUM2STRING(SET_MISSIONSTATUSTIME), + ENUM2STRING(SET_FULLNAME), + ENUM2STRING(SET_FORCE_HEAL_LEVEL), + ENUM2STRING(SET_FORCE_JUMP_LEVEL), + ENUM2STRING(SET_FORCE_SPEED_LEVEL), + ENUM2STRING(SET_FORCE_PUSH_LEVEL), + ENUM2STRING(SET_FORCE_PULL_LEVEL), + ENUM2STRING(SET_FORCE_MINDTRICK_LEVEL), + ENUM2STRING(SET_FORCE_GRIP_LEVEL), + ENUM2STRING(SET_FORCE_LIGHTNING_LEVEL), + ENUM2STRING(SET_SABER_THROW), + ENUM2STRING(SET_SABER_DEFENSE), + ENUM2STRING(SET_SABER_OFFENSE), + ENUM2STRING(SET_VIEWENTITY), + ENUM2STRING(SET_WATCHTARGET), + ENUM2STRING(SET_SABERACTIVE), + ENUM2STRING(SET_ADJUST_AREA_PORTALS), + ENUM2STRING(SET_DMG_BY_HEAVY_WEAP_ONLY), + ENUM2STRING(SET_SHIELDED), + ENUM2STRING(SET_NO_GROUPS), + ENUM2STRING(SET_FIRE_WEAPON), + ENUM2STRING(SET_INACTIVE), + ENUM2STRING(SET_FUNC_USABLE_VISIBLE), + ENUM2STRING(SET_MISSION_STATUS_SCREEN), + ENUM2STRING(SET_END_SCREENDISSOLVE), + ENUM2STRING(SET_LOOPSOUND), + ENUM2STRING(SET_ICARUS_FREEZE), + ENUM2STRING(SET_ICARUS_UNFREEZE), + ENUM2STRING(SET_USE_CP_NEAREST), + ENUM2STRING(SET_MORELIGHT), + ENUM2STRING(SET_CINEMATIC_SKIPSCRIPT), + ENUM2STRING(SET_NO_FORCE), + ENUM2STRING(SET_NO_FALLTODEATH), + ENUM2STRING(SET_DISMEMBERABLE), + ENUM2STRING(SET_NO_ACROBATICS), + ENUM2STRING(SET_MUSIC_STATE), + ENUM2STRING(SET_USE_SUBTITLES), + ENUM2STRING(SET_CLEAN_DAMAGING_ENTS), + ENUM2STRING(SET_HUD), + +//FIXME: add BOTH_ attributes here too + "", SET_, +}; + +void Q3_TaskIDClear( int *taskID ) +{ + *taskID = -1; +} + +void G_DebugPrint( int level, const char *format, ... ) +{ + va_list argptr; + char text[1024]; + + //Don't print messages they don't want to see + //if ( g_ICARUSDebug->integer < level ) + if (g_developer.integer != 2) + return; + + va_start (argptr, format); + vsprintf (text, format, argptr); + va_end (argptr); + + //Add the color formatting + switch ( level ) + { + case WL_ERROR: + Com_Printf ( S_COLOR_RED"ERROR: %s", text ); + break; + + case WL_WARNING: + Com_Printf ( S_COLOR_YELLOW"WARNING: %s", text ); + break; + + case WL_DEBUG: + { + int entNum; + char *buffer; + + sscanf( text, "%d", &entNum ); + + //if ( ( ICARUS_entFilter >= 0 ) && ( ICARUS_entFilter != entNum ) ) + // return; + + buffer = (char *) text; + buffer += 5; + + if ( ( entNum < 0 ) || ( entNum > MAX_GENTITIES ) ) + entNum = 0; + + Com_Printf ( S_COLOR_BLUE"DEBUG: %s(%d): %s\n", g_entities[entNum].script_targetname, entNum, buffer ); + break; + } + default: + case WL_VERBOSE: + Com_Printf ( S_COLOR_GREEN"INFO: %s", text ); + break; + } +} + +/* +------------------------- +Q3_GetAnimLower +------------------------- +*/ +static char *Q3_GetAnimLower( gentity_t *ent ) +{ + int anim = 0; + + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetAnimLower: attempted to read animation state off non-client!\n" ); + return NULL; + } + + anim = ent->client->ps.legsAnim; + + return (char *)animTable[anim].name; +} + +/* +------------------------- +Q3_GetAnimUpper +------------------------- +*/ +static char *Q3_GetAnimUpper( gentity_t *ent ) +{ + int anim = 0; + + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetAnimUpper: attempted to read animation state off non-client!\n" ); + return NULL; + } + + anim = ent->client->ps.torsoAnim; + + return (char *)animTable[anim].name; +} + +/* +------------------------- +Q3_GetAnimBoth +------------------------- +*/ +static char *Q3_GetAnimBoth( gentity_t *ent ) +{ + char *lowerName, *upperName; + + lowerName = Q3_GetAnimLower( ent ); + upperName = Q3_GetAnimUpper( ent ); + + if ( !lowerName || !lowerName[0] ) + { + G_DebugPrint( WL_WARNING, "Q3_GetAnimBoth: NULL legs animation string found!\n" ); + return NULL; + } + + if ( !upperName || !upperName[0] ) + { + G_DebugPrint( WL_WARNING, "Q3_GetAnimBoth: NULL torso animation string found!\n" ); + return NULL; + } + + if ( Q_stricmp( lowerName, upperName ) ) + { +#ifdef _DEBUG // sigh, cut down on tester reports that aren't important + G_DebugPrint( WL_WARNING, "Q3_GetAnimBoth: legs and torso animations did not match : returning legs\n" ); +#endif + } + + return lowerName; +} + +int Q3_PlaySound( int taskID, int entID, const char *name, const char *channel ) +{ + gentity_t *ent = &g_entities[entID]; + char finalName[MAX_QPATH]; + soundChannel_t voice_chan = CHAN_VOICE; // set a default so the compiler doesn't bitch + qboolean type_voice = qfalse; + int soundHandle; + qboolean bBroadcast; + + Q_strncpyz( finalName, name, MAX_QPATH ); + Q_strupr(finalName); + //G_AddSexToMunroString( finalName, qtrue ); + + COM_StripExtension( (const char *)finalName, finalName ); + + soundHandle = G_SoundIndex( (char *) finalName ); + bBroadcast = qfalse; + + if ( ( Q_stricmp( channel, "CHAN_ANNOUNCER" ) == 0 ) || (ent->classname && Q_stricmp("target_scriptrunner", ent->classname ) == 0) ) { + bBroadcast = qtrue; + } + + + // moved here from further down so I can easily check channel-type without code dup... + // + if ( Q_stricmp( channel, "CHAN_VOICE" ) == 0 ) + { + voice_chan = CHAN_VOICE; + type_voice = qtrue; + } + else if ( Q_stricmp( channel, "CHAN_VOICE_ATTEN" ) == 0 ) + { + voice_chan = CHAN_AUTO;//CHAN_VOICE_ATTEN; + type_voice = qtrue; + } + else if ( Q_stricmp( channel, "CHAN_VOICE_GLOBAL" ) == 0 ) // this should broadcast to everyone, put only casue animation on G_SoundOnEnt... + { + voice_chan = CHAN_AUTO;//CHAN_VOICE_GLOBAL; + type_voice = qtrue; + bBroadcast = qtrue; + } + + // if we're in-camera, check for skipping cinematic and ifso, no subtitle print (since screen is not being + // updated anyway during skipping). This stops leftover subtitles being left onscreen after unskipping. + // + /* + if (!in_camera || + (!g_skippingcin || !g_skippingcin->integer) + ) // paranoia towards project end + { + // Text on + // certain NPC's we always want to use subtitles regardless of subtitle setting + if (g_subtitles->integer == 1 || (ent->NPC && (ent->NPC->scriptFlags & SCF_USE_SUBTITLES) ) ) // Show all text + { + if ( in_camera) // Cinematic + { + trap_SendServerCommand( -1, va("ct \"%s\" %i", finalName, soundHandle) ); + } + else //if (precacheWav[i].speaker==SP_NONE) // lower screen text + { + sharedEntity_t *ent2 = SV_GentityNum(0); + // the numbers in here were either the original ones Bob entered (350), or one arrived at from checking the distance Chell stands at in stasis2 by the computer core that was submitted as a bug report... + // + if (bBroadcast || (DistanceSquared(ent->currentOrigin, ent2->currentOrigin) < ((voice_chan == CHAN_VOICE_ATTEN)?(350 * 350):(1200 * 1200)) ) ) + { + trap_SendServerCommand( -1, va("ct \"%s\" %i", finalName, soundHandle) ); + } + } + } + // Cinematic only + else if (g_subtitles->integer == 2) // Show only talking head text and CINEMATIC + { + if ( in_camera) // Cinematic text + { + trap_SendServerCommand( -1, va("ct \"%s\" %i", finalName, soundHandle)); + } + } + + } + */ + + if ( type_voice ) + { + char buf[128]; + float tFVal = 0; + + trap_Cvar_VariableStringBuffer("timescale", buf, sizeof(buf)); + + tFVal = atof(buf); + + + if ( tFVal > 1.0f ) + {//Skip the damn sound! + return qtrue; + } + else + { + //This the voice channel + G_Sound( ent, voice_chan, G_SoundIndex((char *) finalName) ); + } + //Remember we're waiting for this + trap_ICARUS_TaskIDSet( ent, TID_CHAN_VOICE, taskID ); + + return qfalse; + } + + if ( bBroadcast ) + {//Broadcast the sound + gentity_t *te; + + te = G_TempEntity( ent->r.currentOrigin, EV_GLOBAL_SOUND ); + te->s.eventParm = soundHandle; + te->r.svFlags |= SVF_BROADCAST; + } + else + { + G_Sound( ent, CHAN_AUTO, soundHandle ); + } + + return qtrue; +} + +/* +------------------------- +Q3_Play +------------------------- +*/ +void Q3_Play( int taskID, int entID, const char *type, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !Q_stricmp( type, "PLAY_ROFF" ) ) + { + // Try to load the requested ROFF + ent->roffid = trap_ROFF_Cache((char*)name); + if ( ent->roffid ) + { + ent->roffname = G_NewString( name ); + + // Start the roff from the beginning + //ent->roff_ctr = 0; + + //Save this off for later + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + + // Let the ROFF playing start. + //ent->next_roff_time = level.time; + + //rww - Maybe use pos1 and pos2? I don't think we need to care if these values are sent across the net. + // These need to be initialised up front... + //VectorCopy( ent->r.currentOrigin, ent->pos1 ); + //VectorCopy( ent->r.currentAngles, ent->pos2 ); + VectorCopy( ent->r.currentOrigin, ent->s.origin2 ); + VectorCopy( ent->r.currentAngles, ent->s.angles2 ); + + trap_LinkEntity( ent ); + + trap_ROFF_Play(ent->s.number, ent->roffid, qtrue); + } + } +} + +/* +============= +anglerCallback + +Utility function +============= +*/ +void anglerCallback( gentity_t *ent ) +{ + //Complete the task + trap_ICARUS_TaskIDComplete( ent, TID_ANGLE_FACE ); + + //Set the currentAngles, clear all movement + VectorMA( ent->s.apos.trBase, (ent->s.apos.trDuration*0.001f), ent->s.apos.trDelta, ent->r.currentAngles ); + VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); + VectorClear( ent->s.apos.trDelta ); + ent->s.apos.trDuration = 1; + ent->s.apos.trType = TR_STATIONARY; + ent->s.apos.trTime = level.time; + + //Stop thinking + ent->reached = 0; + if ( ent->think == anglerCallback ) + { + ent->think = 0; + } + + //link + trap_LinkEntity( ent ); +} + +void MatchTeam( gentity_t *teamLeader, int moverState, int time ); +void Blocked_Mover( gentity_t *ent, gentity_t *other ); + +/* +============= +moverCallback + +Utility function +============= +*/ +void moverCallback( gentity_t *ent ) +{ //complete the task + trap_ICARUS_TaskIDComplete( ent, TID_MOVE_NAV ); + + // play sound + ent->s.loopSound = 0;//stop looping sound + ent->s.loopIsSoundset = qfalse; + G_PlayDoorSound( ent, BMS_END );//play end sound + + if ( ent->moverState == MOVER_1TO2 ) + {//reached open + // reached pos2 + MatchTeam( ent, MOVER_POS2, level.time ); + //SetMoverState( ent, MOVER_POS2, level.time ); + } + else if ( ent->moverState == MOVER_2TO1 ) + {//reached closed + MatchTeam( ent, MOVER_POS1, level.time ); + //SetMoverState( ent, MOVER_POS1, level.time ); + } + + if ( ent->blocked == Blocked_Mover ) + { + ent->blocked = 0; + } + +// if ( !Q_stricmp( "misc_model_breakable", ent->classname ) && ent->physicsBounce ) +// {//a gravity-affected model +// misc_model_breakable_gravity_init( ent, qfalse ); +// } +} + +void Blocked_Mover( gentity_t *ent, gentity_t *other ) +{ + // remove anything other than a client -- no longer the case + + // don't remove security keys or goodie keys + if ( (other->s.eType == ET_ITEM) ) + { + // should we be doing anything special if a key blocks it... move it somehow..? + } + // if your not a client, or your a dead client remove yourself... + else if ( other->s.number && (!other->client || (other->client && other->health <= 0 && other->r.contents == CONTENTS_CORPSE && !other->message)) ) + { + //if ( !other->taskManager || !other->taskManager->IsRunning() ) + { + // if an item or weapon can we do a little explosion..? + G_FreeEntity( other ); + return; + } + } + + if ( ent->damage ) { + G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH ); + } +} + +/* +============= +moveAndRotateCallback + +Utility function +============= +*/ +void moveAndRotateCallback( gentity_t *ent ) +{ + //stop turning + anglerCallback( ent ); + //stop moving + moverCallback( ent ); +} + +/* +============= +Q3_Lerp2Start + +Lerps the origin of an entity to its starting position +============= +*/ +void Q3_Lerp2Start( int entID, int taskID, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_Lerp2Start: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_Lerp2Start: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + //FIXME: set up correctly!!! + ent->moverState = MOVER_2TO1; + ent->s.eType = ET_MOVER; + ent->reached = moverCallback; //Callsback the the completion of the move + if ( ent->damage ) + { + ent->blocked = Blocked_Mover; + } + + ent->s.pos.trDuration = duration * 10; //In seconds + ent->s.pos.trTime = level.time; + + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + trap_LinkEntity( ent ); +} + +/* +============= +Q3_Lerp2End + +Lerps the origin of an entity to its ending position +============= +*/ +void Q3_Lerp2End( int entID, int taskID, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_Lerp2End: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_Lerp2End: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + //FIXME: set up correctly!!! + ent->moverState = MOVER_1TO2; + ent->s.eType = ET_MOVER; + ent->reached = moverCallback; //Callsback the the completion of the move + if ( ent->damage ) + { + ent->blocked = Blocked_Mover; + } + + ent->s.pos.trDuration = duration * 10; //In seconds + ent->s.time = level.time; + + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + trap_LinkEntity( ent ); +} + +void InitMoverTrData( gentity_t *ent ); + +/* +============= +Q3_Lerp2Pos + +Lerps the origin and angles of an entity to the destination values + +============= +*/ +void Q3_Lerp2Pos( int taskID, int entID, vec3_t origin, vec3_t angles, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + vec3_t ang; + int i; + moverState_t moverState; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_Lerp2Pos: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_Lerp2Pos: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + //Don't allow a zero duration + if ( duration == 0 ) + duration = 1; + + // + // Movement + + moverState = ent->moverState; + + if ( moverState == MOVER_POS1 || moverState == MOVER_2TO1 ) + { + VectorCopy( ent->r.currentOrigin, ent->pos1 ); + VectorCopy( origin, ent->pos2 ); + + moverState = MOVER_1TO2; + } + else + { + VectorCopy( ent->r.currentOrigin, ent->pos2 ); + VectorCopy( origin, ent->pos1 ); + + moverState = MOVER_2TO1; + } + + InitMoverTrData( ent ); + + ent->s.pos.trDuration = duration; + + // start it going + MatchTeam( ent, moverState, level.time ); + //SetMoverState( ent, moverState, level.time ); + + //Only do the angles if specified + if ( angles != NULL ) + { + // + // Rotation + + for ( i = 0; i < 3; i++ ) + { + ang[i] = AngleDelta( angles[i], ent->r.currentAngles[i] ); + ent->s.apos.trDelta[i] = ( ang[i] / ( duration * 0.001f ) ); + } + + VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); + + if ( ent->alt_fire ) + { + ent->s.apos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.apos.trType = TR_NONLINEAR_STOP; + } + ent->s.apos.trDuration = duration; + + ent->s.apos.trTime = level.time; + + ent->reached = moveAndRotateCallback; + trap_ICARUS_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + } + else + { + //Setup the last bits of information + ent->reached = moverCallback; + } + + if ( ent->damage ) + { + ent->blocked = Blocked_Mover; + } + + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + trap_LinkEntity( ent ); +} + +/* +============= +Q3_LerpAngles + +Lerps the angles to the destination value +============= +*/ +void Q3_Lerp2Angles( int taskID, int entID, vec3_t angles, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + vec3_t ang; + int i; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_Lerp2Angles: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_Lerp2Angles: ent %d is NOT a mover!\n", entID); + return; + } + + //If we want an instant move, don't send 0... + ent->s.apos.trDuration = (duration>0) ? duration : 1; + + for ( i = 0; i < 3; i++ ) + { + ang [i] = AngleSubtract( angles[i], ent->r.currentAngles[i]); + ent->s.apos.trDelta[i] = ( ang[i] / ( ent->s.apos.trDuration * 0.001f ) ); + } + + VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); + + if ( ent->alt_fire ) + { + ent->s.apos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.apos.trType = TR_NONLINEAR_STOP; + } + + ent->s.apos.trTime = level.time; + + trap_ICARUS_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + + //ent->e_ReachedFunc = reachedF_NULL; + ent->think = anglerCallback; + ent->nextthink = level.time + duration; + + trap_LinkEntity( ent ); +} + +/* +============= +Q3_GetTag + +Gets the value of a tag by the give name +============= +*/ +int Q3_GetTag( int entID, const char *name, int lookup, vec3_t info ) +{ + assert( 0 ); + return 0; +/* + gentity_t *ent = &g_entities[entID]; + + if (!ent->inuse) + { + assert(0); + return 0; + } + + switch ( lookup ) + { + case TYPE_ORIGIN: + return TAG_GetOrigin( ent->ownername, name, info ); + break; + + case TYPE_ANGLES: + return TAG_GetAngles( ent->ownername, name, info ); + break; + } + + return 0; +*/ +} + +//----------------------------------------------- + +/* +============ +Q3_Use + +Uses an entity +============ +*/ +void Q3_Use( int entID, const char *target ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_Use: invalid entID %d\n", entID); + return; + } + + if( !target || !target[0] ) + { + G_DebugPrint( WL_WARNING, "Q3_Use: string is NULL!\n" ); + return; + } + + G_UseTargets2(ent, ent, target); +} + +/* +============ +Q3_Kill + Description : + Return type : void + Argument : int entID + Argument : const char *name +============ +*/ +void Q3_Kill( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + gentity_t *victim = NULL; + int o_health; + + if( !Q_stricmp( name, "self") ) + { + victim = ent; + } + else if( !Q_stricmp( name, "enemy" ) ) + { + victim = ent->enemy; + } + else + { + victim = G_Find (NULL, FOFS(targetname), (char *) name ); + } + + if ( !victim ) + { + G_DebugPrint( WL_WARNING, "Q3_Kill: can't find %s\n", name); + return; + } + + //rww - I guess this would only apply to NPCs anyway. I'm not going to bother. + //if ( victim == ent ) + //{//don't ICARUS_FreeEnt me, I'm in the middle of a script! (FIXME: shouldn't ICARUS handle this internally?) + // victim->svFlags |= SVF_KILLED_SELF; + //} + + o_health = victim->health; + victim->health = 0; + if ( victim->client ) + { + victim->flags |= FL_NO_KNOCKBACK; + } + //G_SetEnemy(victim, ent); + if( victim->die != NULL ) // check can be omitted + { + //GEntity_DieFunc( victim, NULL, NULL, o_health, MOD_UNKNOWN ); + victim->die(victim, victim, victim, o_health, MOD_UNKNOWN); + } +} + +/* +============ +Q3_RemoveEnt + Description : + Return type : void + Argument : sharedEntity_t *victim +============ +*/ +void Q3_RemoveEnt( gentity_t *victim ) +{ + if( victim->client ) + { + if ( victim->s.eType != ET_NPC ) + { + G_DebugPrint( WL_WARNING, "Q3_RemoveEnt: You can't remove clients in MP!\n" ); + assert(0); //can't remove clients in MP + } + else + {//remove the NPC + if ( victim->client->NPC_class == CLASS_VEHICLE ) + {//eject everyone out of a vehicle that's about to remove itself + Vehicle_t *pVeh = victim->m_pVehicle; + if ( pVeh && pVeh->m_pVehicleInfo ) + { + pVeh->m_pVehicleInfo->EjectAll( pVeh ); + } + } + victim->think = G_FreeEntity; + victim->nextthink = level.time + 100; + } + /* + //ClientDisconnect(ent); + victim->s.eFlags |= EF_NODRAW; + victim->s.eType = ET_INVISIBLE; + victim->contents = 0; + victim->health = 0; + victim->targetname = NULL; + + if ( victim->NPC && victim->NPC->tempGoal != NULL ) + { + G_FreeEntity( victim->NPC->tempGoal ); + victim->NPC->tempGoal = NULL; + } + if ( victim->client->ps.saberEntityNum != ENTITYNUM_NONE && victim->client->ps.saberEntityNum > 0 ) + { + if ( g_entities[victim->client->ps.saberEntityNum].inuse ) + { + G_FreeEntity( &g_entities[victim->client->ps.saberEntityNum] ); + } + victim->client->ps.saberEntityNum = ENTITYNUM_NONE; + } + //Disappear in half a second + victim->e_ThinkFunc = thinkF_G_FreeEntity; + victim->nextthink = level.time + 500; + return; + */ + } + else + { + victim->think = G_FreeEntity; + victim->nextthink = level.time + 100; + } +} + + +/* +============ +Q3_Remove + Description : + Return type : void + Argument : int entID + Argument : const char *name +============ +*/ +void Q3_Remove( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + gentity_t *victim = NULL; + + if( !Q_stricmp( "self", name ) ) + { + victim = ent; + if ( !victim ) + { + G_DebugPrint( WL_WARNING, "Q3_Remove: can't find %s\n", name ); + return; + } + Q3_RemoveEnt( victim ); + } + else if( !Q_stricmp( "enemy", name ) ) + { + victim = ent->enemy; + if ( !victim ) + { + G_DebugPrint( WL_WARNING, "Q3_Remove: can't find %s\n", name ); + return; + } + Q3_RemoveEnt( victim ); + } + else + { + victim = G_Find( NULL, FOFS(targetname), (char *) name ); + if ( !victim ) + { + G_DebugPrint( WL_WARNING, "Q3_Remove: can't find %s\n", name ); + return; + } + + while ( victim ) + { + Q3_RemoveEnt( victim ); + victim = G_Find( victim, FOFS(targetname), (char *) name ); + } + } +} + +/* +================================================= + + Get / Set Functions + +================================================= +*/ + +/* +============ +Q3_GetFloat + Description : + Return type : int + Argument : int entID + Argument : int type + Argument : const char *name + Argument : float *value +============ +*/ +int Q3_GetFloat( int entID, int type, const char *name, float *value ) +{ + gentity_t *ent = &g_entities[entID]; + int toGet = 0; + + if ( !ent ) + { + return 0; + } + + toGet = GetIDForString( setTable, name ); //FIXME: May want to make a "getTable" as well + //FIXME: I'm getting really sick of these huge switch statements! + + //NOTENOTE: return true if the value was correctly obtained + switch ( toGet ) + { + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + if (ent->parms == NULL) + { + G_DebugPrint( WL_ERROR, "GET_PARM: %s %s did not have any parms set!\n", ent->classname, ent->targetname ); + return 0; // would prefer qfalse, but I'm fitting in with what's here + } + *value = atof( ent->parms->parm[toGet - SET_PARM1] ); + break; + + case SET_COUNT: + *value = ent->count; + break; + + case SET_HEALTH: + *value = ent->health; + break; + + case SET_SKILL: + return 0; + break; + + case SET_XVELOCITY://## %f="0.0" # Velocity along X axis + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_XVELOCITY, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.velocity[0]; + break; + + case SET_YVELOCITY://## %f="0.0" # Velocity along Y axis + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_YVELOCITY, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.velocity[1]; + break; + + case SET_ZVELOCITY://## %f="0.0" # Velocity along Z axis + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_ZVELOCITY, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.velocity[2]; + break; + + case SET_Z_OFFSET: + *value = ent->r.currentOrigin[2] - ent->s.origin[2]; + break; + + case SET_DPITCH://## %f="0.0" # Pitch for NPC to turn to + return 0; + break; + + case SET_DYAW://## %f="0.0" # Yaw for NPC to turn to + return 0; + break; + + case SET_WIDTH://## %f="0.0" # Width of NPC bounding box + *value = ent->r.mins[0]; + break; + case SET_TIMESCALE://## %f="0.0" # Speed-up slow down game (0 - 1.0) + return 0; + break; + case SET_CAMERA_GROUP_Z_OFS://## %s="NULL" # all ents with this cameraGroup will be focused on + return 0; + break; + + case SET_VISRANGE://## %f="0.0" # How far away NPC can see + return 0; + break; + + case SET_EARSHOT://## %f="0.0" # How far an NPC can hear + return 0; + break; + + case SET_VIGILANCE://## %f="0.0" # How often to look for enemies (0 - 1.0) + return 0; + break; + + case SET_GRAVITY://## %f="0.0" # Change this ent's gravity - 800 default + *value = g_gravity.value; + break; + + case SET_FACEEYESCLOSED: + case SET_FACEEYESOPENED: + case SET_FACEAUX: //## %f="0.0" # Set face to Aux expression for number of seconds + case SET_FACEBLINK: //## %f="0.0" # Set face to Blink expression for number of seconds + case SET_FACEBLINKFROWN: //## %f="0.0" # Set face to Blinkfrown expression for number of seconds + case SET_FACEFROWN: //## %f="0.0" # Set face to Frown expression for number of seconds + case SET_FACENORMAL: //## %f="0.0" # Set face to Normal expression for number of seconds + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_FACE___ not implemented\n" ); + return 0; + break; + case SET_WAIT: //## %f="0.0" # Change an entity's wait field + *value = ent->wait; + break; + case SET_FOLLOWDIST: //## %f="0.0" # How far away to stay from leader in BS_FOLLOW_LEADER + return 0; + break; + //# #sep ints + case SET_ANIM_HOLDTIME_LOWER://## %d="0" # Hold lower anim for number of milliseconds + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_ANIM_HOLDTIME_LOWER, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.legsTimer; + break; + case SET_ANIM_HOLDTIME_UPPER://## %d="0" # Hold upper anim for number of milliseconds + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_ANIM_HOLDTIME_UPPER, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.torsoTimer; + break; + case SET_ANIM_HOLDTIME_BOTH://## %d="0" # Hold lower and upper anims for number of milliseconds + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_ANIM_HOLDTIME_BOTH not implemented\n" ); + return 0; + break; + case SET_ARMOR://## %d="0" # Change armor + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_ARMOR, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.stats[STAT_ARMOR]; + break; + case SET_WALKSPEED://## %d="0" # Change walkSpeed + return 0; + break; + case SET_RUNSPEED://## %d="0" # Change runSpeed + return 0; + break; + case SET_YAWSPEED://## %d="0" # Change yawSpeed + return 0; + break; + case SET_AGGRESSION://## %d="0" # Change aggression 1-5 + return 0; + break; + case SET_AIM://## %d="0" # Change aim 1-5 + return 0; + break; + case SET_FRICTION://## %d="0" # Change ent's friction - 6 default + return 0; + break; + case SET_SHOOTDIST://## %d="0" # How far the ent can shoot - 0 uses weapon + return 0; + break; + case SET_HFOV://## %d="0" # Horizontal field of view + return 0; + break; + case SET_VFOV://## %d="0" # Vertical field of view + return 0; + break; + case SET_DELAYSCRIPTTIME://## %d="0" # How many seconds to wait before running delayscript + return 0; + break; + case SET_FORWARDMOVE://## %d="0" # NPC move forward -127(back) to 127 + return 0; + break; + case SET_RIGHTMOVE://## %d="0" # NPC move right -127(left) to 127 + return 0; + break; + case SET_STARTFRAME: //## %d="0" # frame to start animation sequence on + return 0; + break; + case SET_ENDFRAME: //## %d="0" # frame to end animation sequence on + return 0; + break; + case SET_ANIMFRAME: //## %d="0" # of current frame + return 0; + break; + + case SET_SHOT_SPACING://## %d="1000" # Time between shots for an NPC - reset to defaults when changes weapon + return 0; + break; + case SET_MISSIONSTATUSTIME://## %d="0" # Amount of time until Mission Status should be shown after death + return 0; + break; + //# #sep booleans + case SET_IGNOREPAIN://## %t="BOOL_TYPES" # Do not react to pain + return 0; + break; + case SET_IGNOREENEMIES://## %t="BOOL_TYPES" # Do not acquire enemies + return 0; + break; + case SET_IGNOREALERTS://## Do not get enemy set by allies in area(ambush) + return 0; + break; + case SET_DONTSHOOT://## %t="BOOL_TYPES" # Others won't shoot you + return 0; + break; + case SET_NOTARGET://## %t="BOOL_TYPES" # Others won't pick you as enemy + *value = (ent->flags&FL_NOTARGET); + break; + case SET_DONTFIRE://## %t="BOOL_TYPES" # Don't fire your weapon + return 0; + break; + + case SET_LOCKED_ENEMY://## %t="BOOL_TYPES" # Keep current enemy until dead + return 0; + break; + case SET_CROUCHED://## %t="BOOL_TYPES" # Force NPC to crouch + return 0; + break; + case SET_WALKING://## %t="BOOL_TYPES" # Force NPC to move at walkSpeed + return 0; + break; + case SET_RUNNING://## %t="BOOL_TYPES" # Force NPC to move at runSpeed + return 0; + break; + case SET_CHASE_ENEMIES://## %t="BOOL_TYPES" # NPC will chase after enemies + return 0; + break; + case SET_LOOK_FOR_ENEMIES://## %t="BOOL_TYPES" # NPC will be on the lookout for enemies + return 0; + break; + case SET_FACE_MOVE_DIR://## %t="BOOL_TYPES" # NPC will face in the direction it's moving + return 0; + break; + case SET_FORCED_MARCH://## %t="BOOL_TYPES" # Force NPC to move at runSpeed + return 0; + break; + case SET_UNDYING://## %t="BOOL_TYPES" # Can take damage down to 1 but not die + return 0; + break; + case SET_NOAVOID://## %t="BOOL_TYPES" # Will not avoid other NPCs or architecture + return 0; + break; + + case SET_SOLID://## %t="BOOL_TYPES" # Make yourself notsolid or solid + *value = ent->r.contents; + break; + case SET_PLAYER_USABLE://## %t="BOOL_TYPES" # Can be activateby the player's "use" button + *value = (ent->r.svFlags&SVF_PLAYER_USABLE); + break; + case SET_LOOP_ANIM://## %t="BOOL_TYPES" # For non-NPCs: loop your animation sequence + return 0; + break; + case SET_INTERFACE://## %t="BOOL_TYPES" # Player interface on/off + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_INTERFACE not implemented\n" ); + return 0; + break; + case SET_SHIELDS://## %t="BOOL_TYPES" # NPC has no shields (Borg do not adapt) + return 0; + break; + case SET_INVISIBLE://## %t="BOOL_TYPES" # Makes an NPC not solid and not visible + *value = (ent->s.eFlags&EF_NODRAW); + break; + case SET_VAMPIRE://## %t="BOOL_TYPES" # Makes an NPC not solid and not visible + return 0; + break; + case SET_FORCE_INVINCIBLE://## %t="BOOL_TYPES" # Makes an NPC not solid and not visible + return 0; + break; + case SET_GREET_ALLIES://## %t="BOOL_TYPES" # Makes an NPC greet teammates + return 0; + break; + case SET_VIDEO_FADE_IN://## %t="BOOL_TYPES" # Makes video playback fade in + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_VIDEO_FADE_IN not implemented\n" ); + return 0; + break; + case SET_VIDEO_FADE_OUT://## %t="BOOL_TYPES" # Makes video playback fade out + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_VIDEO_FADE_OUT not implemented\n" ); + return 0; + break; + case SET_PLAYER_LOCKED://## %t="BOOL_TYPES" # Makes it so player cannot move + return 0; + break; + case SET_LOCK_PLAYER_WEAPONS://## %t="BOOL_TYPES" # Makes it so player cannot switch weapons + return 0; + break; + case SET_NO_IMPACT_DAMAGE://## %t="BOOL_TYPES" # Makes it so player cannot switch weapons + return 0; + break; + case SET_NO_KNOCKBACK://## %t="BOOL_TYPES" # Stops this ent from taking knockback from weapons + *value = (ent->flags&FL_NO_KNOCKBACK); + break; + case SET_ALT_FIRE://## %t="BOOL_TYPES" # Force NPC to use altfire when shooting + return 0; + break; + case SET_NO_RESPONSE://## %t="BOOL_TYPES" # NPCs will do generic responses when this is on (usescripts override generic responses as well) + return 0; + break; + case SET_INVINCIBLE://## %t="BOOL_TYPES" # Completely unkillable + *value = (ent->flags&FL_GODMODE); + break; + case SET_MISSIONSTATUSACTIVE: //# Turns on Mission Status Screen + return 0; + break; + case SET_NO_COMBAT_TALK://## %t="BOOL_TYPES" # NPCs will not do their combat talking noises when this is on + return 0; + break; + case SET_NO_ALERT_TALK://## %t="BOOL_TYPES" # NPCs will not do their combat talking noises when this is on + return 0; + break; + case SET_USE_CP_NEAREST://## %t="BOOL_TYPES" # NPCs will use their closest combat points, not try and find ones next to the player, or flank player + return 0; + break; + case SET_DISMEMBERABLE://## %t="BOOL_TYPES" # NPC will not be affected by force powers + return 0; + break; + case SET_NO_FORCE: + return 0; + break; + case SET_NO_ACROBATICS: + return 0; + break; + case SET_USE_SUBTITLES: + return 0; + break; + case SET_NO_FALLTODEATH://## %t="BOOL_TYPES" # NPC will not be affected by force powers + return 0; + break; + case SET_MORELIGHT://## %t="BOOL_TYPES" # NPCs will use their closest combat points, not try and find ones next to the player, or flank player + return 0; + break; + case SET_TREASONED://## %t="BOOL_TYPES" # Player has turned on his own- scripts will stop: NPCs will turn on him and level changes load the brig + return 0; + break; + case SET_DISABLE_SHADER_ANIM: //## %t="BOOL_TYPES" # Shaders won't animate + return 0; + break; + case SET_SHADER_ANIM: //## %t="BOOL_TYPES" # Shader will be under frame control + return 0; + break; + + default: + if ( trap_ICARUS_VariableDeclared( name ) != VTYPE_FLOAT ) + return 0; + + return trap_ICARUS_GetFloatVariable( name, value ); + } + + return 1; +} + + +/* +============ +Q3_GetVector + Description : + Return type : int + Argument : int entID + Argument : int type + Argument : const char *name + Argument : vec3_t value +============ +*/ +int Q3_GetVector( int entID, int type, const char *name, vec3_t value ) +{ + gentity_t *ent = &g_entities[entID]; + int toGet = 0; + if ( !ent ) + { + return 0; + } + + toGet = GetIDForString( setTable, name ); //FIXME: May want to make a "getTable" as well + //FIXME: I'm getting really sick of these huge switch statements! + + //NOTENOTE: return true if the value was correctly obtained + switch ( toGet ) + { + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + sscanf( ent->parms->parm[toGet - SET_PARM1], "%f %f %f", &value[0], &value[1], &value[2] ); + break; + + case SET_ORIGIN: + VectorCopy(ent->r.currentOrigin, value); + break; + + case SET_ANGLES: + VectorCopy(ent->r.currentAngles, value); + break; + + case SET_TELEPORT_DEST://## %v="0.0 0.0 0.0" # Set origin here as soon as the area is clear + G_DebugPrint( WL_WARNING, "Q3_GetVector: SET_TELEPORT_DEST not implemented\n" ); + return 0; + break; + + default: + + if ( trap_ICARUS_VariableDeclared( name ) != VTYPE_VECTOR ) + return 0; + + return trap_ICARUS_GetVectorVariable( name, value ); + } + + return 1; +} + +/* +============ +Q3_GetString + Description : + Return type : int + Argument : int entID + Argument : int type + Argument : const char *name + Argument : char **value +============ +*/ +int Q3_GetString( int entID, int type, const char *name, char **value ) +{ + gentity_t *ent = &g_entities[entID]; + int toGet = 0; + if ( !ent ) + { + return 0; + } + + toGet = GetIDForString( setTable, name ); //FIXME: May want to make a "getTable" as well + + switch ( toGet ) + { + case SET_ANIM_BOTH: + *value = (char *) Q3_GetAnimBoth( ent ); + + if ( !value || !value[0] ) + return 0; + + break; + + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + if ( ent->parms ) + { + *value = (char *) ent->parms->parm[toGet - SET_PARM1]; + } + else + { + G_DebugPrint( WL_WARNING, "Q3_GetString: invalid ent %s has no parms!\n", ent->targetname ); + return 0; + } + break; + + case SET_TARGET: + *value = (char *) ent->target; + break; + + case SET_LOCATION: + return 0; + break; + + //# #sep Scripts and other file paths + case SET_SPAWNSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when spawned //0 - do not change these, these are equal to BSET_SPAWN, etc + *value = ent->behaviorSet[BSET_SPAWN]; + break; + case SET_USESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when used + *value = ent->behaviorSet[BSET_USE]; + break; + case SET_AWAKESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when startled + *value = ent->behaviorSet[BSET_AWAKE]; + break; + case SET_ANGERSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script run when find an enemy for the first time + *value = ent->behaviorSet[BSET_ANGER]; + break; + case SET_ATTACKSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when you shoot + *value = ent->behaviorSet[BSET_ATTACK]; + break; + case SET_VICTORYSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when killed someone + *value = ent->behaviorSet[BSET_VICTORY]; + break; + case SET_LOSTENEMYSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when you can't find your enemy + *value = ent->behaviorSet[BSET_LOSTENEMY]; + break; + case SET_PAINSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when hit + *value = ent->behaviorSet[BSET_PAIN]; + break; + case SET_FLEESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when hit and low health + *value = ent->behaviorSet[BSET_FLEE]; + break; + case SET_DEATHSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when killed + *value = ent->behaviorSet[BSET_DEATH]; + break; + case SET_DELAYEDSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run after a delay + *value = ent->behaviorSet[BSET_DELAYED]; + break; + case SET_BLOCKEDSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when blocked by teammate + *value = ent->behaviorSet[BSET_BLOCKED]; + break; + case SET_FFIRESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when player has shot own team repeatedly + *value = ent->behaviorSet[BSET_FFIRE]; + break; + case SET_FFDEATHSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when player kills a teammate + *value = ent->behaviorSet[BSET_FFDEATH]; + break; + + //# #sep Standard strings + case SET_ENEMY://## %s="NULL" # Set enemy by targetname + return 0; + break; + case SET_LEADER://## %s="NULL" # Set for BS_FOLLOW_LEADER + return 0; + break; + case SET_CAPTURE://## %s="NULL" # Set captureGoal by targetname + return 0; + break; + + case SET_TARGETNAME://## %s="NULL" # Set/change your targetname + *value = ent->targetname; + break; + case SET_PAINTARGET://## %s="NULL" # Set/change what to use when hit + return 0; + break; + case SET_CAMERA_GROUP://## %s="NULL" # all ents with this cameraGroup will be focused on + return 0; + break; + case SET_CAMERA_GROUP_TAG://## %s="NULL" # all ents with this cameraGroup will be focused on + return 0; + break; + case SET_LOOK_TARGET://## %s="NULL" # object for NPC to look at + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_LOOK_TARGET, NOT SUPPORTED IN MULTIPLAYER\n" ); + break; + case SET_TARGET2://## %s="NULL" # Set/change your target2: on NPC's: this fires when they're knocked out by the red hypo + return 0; + break; + + case SET_REMOVE_TARGET://## %s="NULL" # Target that is fired when someone completes the BS_REMOVE behaviorState + return 0; + break; + case SET_WEAPON: + return 0; + break; + + case SET_ITEM: + return 0; + break; + case SET_MUSIC_STATE: + return 0; + break; + //The below cannot be gotten + case SET_NAVGOAL://## %s="NULL" # *Move to this navgoal then continue script + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_NAVGOAL not implemented\n" ); + return 0; + break; + case SET_VIEWTARGET://## %s="NULL" # Set angles toward ent by targetname + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_VIEWTARGET not implemented\n" ); + return 0; + break; + case SET_WATCHTARGET://## %s="NULL" # Set angles toward ent by targetname + return 0; + break; + case SET_VIEWENTITY: + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_VIEWENTITY not implemented\n" ); + return 0; + break; + case SET_CAPTIONTEXTCOLOR: //## %s="" # Color of text RED:WHITE:BLUE: YELLOW + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_CAPTIONTEXTCOLOR not implemented\n" ); + return 0; + break; + case SET_CENTERTEXTCOLOR: //## %s="" # Color of text RED:WHITE:BLUE: YELLOW + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_CENTERTEXTCOLOR not implemented\n" ); + return 0; + break; + case SET_SCROLLTEXTCOLOR: //## %s="" # Color of text RED:WHITE:BLUE: YELLOW + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_SCROLLTEXTCOLOR not implemented\n" ); + return 0; + break; + case SET_COPY_ORIGIN://## %s="targetname" # Copy the origin of the ent with targetname to your origin + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_COPY_ORIGIN not implemented\n" ); + return 0; + break; + case SET_DEFEND_TARGET://## %s="targetname" # This NPC will attack the target NPC's enemies + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_COPY_ORIGIN not implemented\n" ); + return 0; + break; + case SET_VIDEO_PLAY://## %s="filename" !!"W:\game\base\video\!!#*.roq" # Play a Video (inGame) + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_VIDEO_PLAY not implemented\n" ); + return 0; + break; + case SET_LOADGAME://## %s="exitholodeck" # Load the savegame that was auto-saved when you started the holodeck + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_LOADGAME not implemented\n" ); + return 0; + break; + case SET_LOCKYAW://## %s="off" # Lock legs to a certain yaw angle (or "off" or "auto" uses current) + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_LOCKYAW not implemented\n" ); + return 0; + break; + case SET_SCROLLTEXT: //## %s="" # key of text string to print + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_SCROLLTEXT not implemented\n" ); + return 0; + break; + case SET_LCARSTEXT: //## %s="" # key of text string to print in LCARS frame + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_LCARSTEXT not implemented\n" ); + return 0; + break; + + case SET_FULLNAME://## %s="NULL" # Set/change your targetname + *value = ent->fullName; + break; + default: + + if ( trap_ICARUS_VariableDeclared( name ) != VTYPE_STRING ) + return 0; + + return trap_ICARUS_GetStringVariable( name, (const char *) *value ); + } + + return 1; +} + +/* +============ +MoveOwner + Description : + Return type : void + Argument : sharedEntity_t *self +============ +*/ +qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ); +void MoveOwner( gentity_t *self ) +{ + gentity_t *owner = &g_entities[self->r.ownerNum]; + + self->nextthink = level.time + FRAMETIME; + self->think = G_FreeEntity; + + if ( !owner || !owner->inuse ) + { + return; + } + + if ( SpotWouldTelefrag2( owner, self->r.currentOrigin ) ) + { + self->think = MoveOwner; + } + else + { + G_SetOrigin( owner, self->r.currentOrigin ); + trap_ICARUS_TaskIDComplete( owner, TID_MOVE_NAV ); + } +} + +/* +============= +Q3_SetTeleportDest + +Copies passed origin to ent running script once there is nothing there blocking the spot +============= +*/ +static qboolean Q3_SetTeleportDest( int entID, vec3_t org ) +{ + gentity_t *teleEnt = &g_entities[entID]; + + if ( teleEnt ) + { + if ( SpotWouldTelefrag2( teleEnt, org ) ) + { + gentity_t *teleporter = G_Spawn(); + + G_SetOrigin( teleporter, org ); + teleporter->r.ownerNum = teleEnt->s.number; + + teleporter->think = MoveOwner; + teleporter->nextthink = level.time + FRAMETIME; + + return qfalse; + } + else + { + G_SetOrigin( teleEnt, org ); + } + } + + return qtrue; +} + +/* +============= +Q3_SetOrigin + +Sets the origin of an entity directly +============= +*/ +static void Q3_SetOrigin( int entID, vec3_t origin ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetOrigin: bad ent %d\n", entID); + return; + } + + trap_UnlinkEntity (ent); + + if(ent->client) + { + VectorCopy(origin, ent->client->ps.origin); + VectorCopy(origin, ent->r.currentOrigin); + ent->client->ps.origin[2] += 1; + + VectorClear (ent->client->ps.velocity); + ent->client->ps.pm_time = 160; // hold time + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + +// G_KillBox (ent); + } + else + { + G_SetOrigin( ent, origin ); + } + + trap_LinkEntity( ent ); +} + +/* +============= +Q3_SetCopyOrigin + +Copies origin of found ent into ent running script +=============` +*/ +static void Q3_SetCopyOrigin( int entID, const char *name ) +{ + gentity_t *found = G_Find( NULL, FOFS(targetname), (char *) name); + + if(found) + { + Q3_SetOrigin( entID, found->r.currentOrigin ); + SetClientViewAngle( &g_entities[entID], found->s.angles ); + } + else + { + G_DebugPrint( WL_WARNING, "Q3_SetCopyOrigin: ent %s not found!\n", name); + } +} + +/* +============= +Q3_SetVelocity + +Set the velocity of an entity directly +============= +*/ +static void Q3_SetVelocity( int entID, int axis, float speed ) +{ + gentity_t *found = &g_entities[entID]; + //FIXME: Not supported + if(!found) + { + G_DebugPrint( WL_WARNING, "Q3_SetVelocity invalid entID %d\n", entID); + return; + } + + if(!found->client) + { + G_DebugPrint( WL_WARNING, "Q3_SetVelocity: not a client %d\n", entID); + return; + } + + //FIXME: add or set? + found->client->ps.velocity[axis] += speed; + + found->client->ps.pm_time = 500; + found->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; +} + +/* +============= +Q3_SetAngles + +Sets the angles of an entity directly +============= +*/ +static void Q3_SetAngles( int entID, vec3_t angles ) +{ + gentity_t *ent = &g_entities[entID]; + + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetAngles: bad ent %d\n", entID); + return; + } + + if (ent->client) + { + SetClientViewAngle( ent, angles ); + } + else + { + VectorCopy( angles, ent->s.angles ); + } + trap_LinkEntity( ent ); +} + +/* +============= +Q3_Lerp2Origin + +Lerps the origin to the destination value +============= +*/ +void Q3_Lerp2Origin( int taskID, int entID, vec3_t origin, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + moverState_t moverState; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_Lerp2Origin: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_Lerp2Origin: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + moverState = ent->moverState; + + if ( moverState == MOVER_POS1 || moverState == MOVER_2TO1 ) + { + VectorCopy( ent->r.currentOrigin, ent->pos1 ); + VectorCopy( origin, ent->pos2 ); + + moverState = MOVER_1TO2; + } + else if ( moverState == MOVER_POS2 || moverState == MOVER_1TO2 ) + { + VectorCopy( ent->r.currentOrigin, ent->pos2 ); + VectorCopy( origin, ent->pos1 ); + + moverState = MOVER_2TO1; + } + + InitMoverTrData( ent ); //FIXME: This will probably break normal things that are being moved... + + ent->s.pos.trDuration = duration; + + // start it going + MatchTeam( ent, moverState, level.time ); + //SetMoverState( ent, moverState, level.time ); + + ent->reached = moverCallback; + if ( ent->damage ) + { + ent->blocked = Blocked_Mover; + } + if ( taskID != -1 ) + { + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + } + // starting sound + G_PlayDoorLoopSound( ent );//start looping sound + G_PlayDoorSound( ent, BMS_START ); //play start sound + + trap_LinkEntity( ent ); +} + +static void Q3_SetOriginOffset( int entID, int axis, float offset ) +{ + gentity_t *ent = &g_entities[entID]; + vec3_t origin; + float duration; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_SetOriginOffset: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_SetOriginOffset: ent %d is NOT a mover!\n", entID); + return; + } + + VectorCopy( ent->s.origin, origin ); + origin[axis] += offset; + duration = 0; + if ( ent->speed ) + { + duration = fabs(offset)/fabs(ent->speed)*1000.0f; + } + Q3_Lerp2Origin( -1, entID, origin, duration ); +} + +/* +============ +SetLowerAnim + Description : + Return type : static void + Argument : int entID + Argument : int animID +============ +*/ +static void SetLowerAnim( int entID, int animID) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "SetLowerAnim: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "SetLowerAnim: ent %d is NOT a player or NPC!\n", entID); + return; + } + + G_SetAnim(ent,NULL,SETANIM_LEGS,animID,SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE,0); +} + +/* +============ +SetUpperAnim + Description : + Return type : static void + Argument : int entID + Argument : int animID +============ +*/ +static void SetUpperAnim ( int entID, int animID) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "SetUpperAnim: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "SetLowerAnim: ent %d is NOT a player or NPC!\n", entID); + return; + } + + G_SetAnim(ent,NULL,SETANIM_TORSO,animID,SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE,0); +} + +/* +============= +Q3_SetAnimUpper + +Sets the upper animation of an entity +============= +*/ +static qboolean Q3_SetAnimUpper( int entID, const char *anim_name ) +{ + int animID = 0; + + animID = GetIDForString( animTable, anim_name ); + + if( animID == -1 ) + { + G_DebugPrint( WL_WARNING, "Q3_SetAnimUpper: unknown animation sequence '%s'\n", anim_name ); + return qfalse; + } + + /* + if ( !PM_HasAnimation( SV_GentityNum(entID), animID ) ) + { + return qfalse; + } + */ + + SetUpperAnim( entID, animID ); + return qtrue; +} + +/* +============= +Q3_SetAnimLower + +Sets the lower animation of an entity +============= +*/ +static qboolean Q3_SetAnimLower( int entID, const char *anim_name ) +{ + int animID = 0; + + //FIXME: Setting duck anim does not actually duck! + + animID = GetIDForString( animTable, anim_name ); + + if( animID == -1 ) + { + G_DebugPrint( WL_WARNING, "Q3_SetAnimLower: unknown animation sequence '%s'\n", anim_name ); + return qfalse; + } + + /* + if ( !PM_HasAnimation( SV_GentityNum(entID), animID ) ) + { + return qfalse; + } + */ + + SetLowerAnim( entID, animID ); + return qtrue; +} + +/* +============ +Q3_SetAnimHoldTime + Description : + Return type : static void + Argument : int entID + Argument : int int_data + Argument : qboolean lower +============ +*/ +static void Q3_SetAnimHoldTime( int entID, int int_data, qboolean lower ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAnimHoldTime is not currently supported in MP\n"); + /* + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetAnimHoldTime: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetAnimHoldTime: ent %d is NOT a player or NPC!\n", entID); + return; + } + + if(lower) + { + PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, int_data ); + } + else + { + PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoAnimTimer, int_data ); + } + */ +} + +/* +============ +Q3_SetHealth + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetHealth( int entID, int data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetHealth: invalid entID %d\n", entID); + return; + } + + if ( data < 0 ) + { + data = 0; + } + + ent->health = data; + + if(!ent->client) + { + return; + } + + ent->client->ps.stats[STAT_HEALTH] = data; + + if ( ent->client->ps.stats[STAT_HEALTH] > ent->client->ps.stats[STAT_MAX_HEALTH] ) + { + ent->health = ent->client->ps.stats[STAT_HEALTH] = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + if ( data == 0 ) + { + ent->health = 1; + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + { //this would be silly + return; + } + + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; + player_die (ent, ent, ent, 100000, MOD_FALLING); + } +} + + +/* +============ +Q3_SetArmor + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetArmor( int entID, int data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetArmor: invalid entID %d\n", entID); + return; + } + + if(!ent->client) + { + return; + } + + ent->client->ps.stats[STAT_ARMOR] = data; + if ( ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_HEALTH] ) + { + ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH]; + } +} + + +/* +============ +Q3_SetBState + Description : + Return type : static qboolean + Argument : int entID + Argument : const char *bs_name +FIXME: this should be a general NPC wrapper function + that is called ANY time a bState is changed... +============ +*/ +static qboolean Q3_SetBState( int entID, const char *bs_name ) +{ + gentity_t *ent = &g_entities[entID]; + bState_t bSID; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetBState: invalid entID %d\n", entID); + return qtrue; + } + + if ( !ent->NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetBState: '%s' is not an NPC\n", ent->targetname ); + return qtrue;//ok to complete + } + + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + if ( bSID > -1 ) + { + if ( bSID == BS_SEARCH || bSID == BS_WANDER ) + { + //FIXME: Reimplement + + if( ent->waypoint != WAYPOINT_NONE ) + { + NPC_BSSearchStart( ent->waypoint, bSID ); + } + else + { + ent->waypoint = NAV_FindClosestWaypointForEnt( ent, WAYPOINT_NONE ); + + if( ent->waypoint != WAYPOINT_NONE ) + { + NPC_BSSearchStart( ent->waypoint, bSID ); + } + /*else if( ent->lastWaypoint >=0 && ent->lastWaypoint < num_waypoints ) + { + NPC_BSSearchStart( ent->lastWaypoint, bSID ); + } + else if( ent->lastValidWaypoint >=0 && ent->lastValidWaypoint < num_waypoints ) + { + NPC_BSSearchStart( ent->lastValidWaypoint, bSID ); + }*/ + else + { + G_DebugPrint( WL_ERROR, "Q3_SetBState: '%s' is not in a valid waypoint to search from!\n", ent->targetname ); + return qtrue; + } + } + } + + + ent->NPC->tempBehavior = BS_DEFAULT;//need to clear any temp behaviour + if ( ent->NPC->behaviorState == BS_NOCLIP && bSID != BS_NOCLIP ) + {//need to rise up out of the floor after noclipping + ent->r.currentOrigin[2] += 0.125; + G_SetOrigin( ent, ent->r.currentOrigin ); + } + ent->NPC->behaviorState = bSID; + if ( bSID == BS_DEFAULT ) + { + ent->NPC->defaultBehavior = bSID; + } + } + + ent->NPC->aiFlags &= ~NPCAI_TOUCHED_GOAL; + +// if ( bSID == BS_FLY ) +// {//FIXME: need a set bState wrapper +// ent->client->moveType = MT_FLYSWIM; +// } +// else + { + //FIXME: these are presumptions! + //Q3_SetGravity( entID, g_gravity->value ); + //ent->client->moveType = MT_RUNJUMP; + } + + if ( bSID == BS_NOCLIP ) + { + ent->client->noclip = qtrue; + } + else + { + ent->client->noclip = qfalse; + } + +/* + if ( bSID == BS_FACE || bSID == BS_POINT_AND_SHOOT || bSID == BS_FACE_ENEMY ) + { + ent->NPC->aimTime = level.time + 5 * 1000;//try for 5 seconds + return qfalse;//need to wait for task complete message + } +*/ + +// if ( bSID == BS_SNIPER || bSID == BS_ADVANCE_FIGHT ) + if ( bSID == BS_ADVANCE_FIGHT ) + { + return qfalse;//need to wait for task complete message + } + +/* + if ( bSID == BS_SHOOT || bSID == BS_POINT_AND_SHOOT ) + {//Let them shoot right NOW + ent->NPC->shotTime = ent->attackDebounceTime = level.time; + } +*/ + if ( bSID == BS_JUMP ) + { + ent->NPC->jumpState = JS_FACING; + } + + return qtrue;//ok to complete +} + + +/* +============ +Q3_SetTempBState + Description : + Return type : static qboolean + Argument : int entID + Argument : const char *bs_name +============ +*/ +static qboolean Q3_SetTempBState( int entID, const char *bs_name ) +{ + gentity_t *ent = &g_entities[entID]; + bState_t bSID; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetTempBState: invalid entID %d\n", entID); + return qtrue; + } + + if ( !ent->NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetTempBState: '%s' is not an NPC\n", ent->targetname ); + return qtrue;//ok to complete + } + + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + if ( bSID > -1 ) + { + ent->NPC->tempBehavior = bSID; + } + +/* + if ( bSID == BS_FACE || bSID == BS_POINT_AND_SHOOT || bSID == BS_FACE_ENEMY ) + { + ent->NPC->aimTime = level.time + 5 * 1000;//try for 5 seconds + return qfalse;//need to wait for task complete message + } +*/ + +/* + if ( bSID == BS_SHOOT || bSID == BS_POINT_AND_SHOOT ) + {//Let them shoot right NOW + ent->NPC->shotTime = ent->attackDebounceTime = level.time; + } +*/ + return qtrue;//ok to complete +} + + +/* +============ +Q3_SetDefaultBState + Description : + Return type : static void + Argument : int entID + Argument : const char *bs_name +============ +*/ +static void Q3_SetDefaultBState( int entID, const char *bs_name ) +{ + gentity_t *ent = &g_entities[entID]; + bState_t bSID; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetDefaultBState: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetDefaultBState: '%s' is not an NPC\n", ent->targetname ); + return; + } + + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + if ( bSID > -1 ) + { + ent->NPC->defaultBehavior = bSID; + } +} + + +/* +============ +Q3_SetDPitch + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetDPitch( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDPitch: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetDYaw + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetDYaw( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDYaw: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetShootDist + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetShootDist( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetShootDist: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetVisrange + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetVisrange( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetVisrange: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetEarshot + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetEarshot( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetEarshot: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetVigilance + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetVigilance( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetVigilance: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetVFOV + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetVFOV( int entID, int data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetVFOV: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetHFOV + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetHFOV( int entID, int data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetHFOV: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetWidth + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetWidth( int entID, int data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetWidth: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetTimeScale + Description : + Return type : static void + Argument : int entID + Argument : const char *data +============ +*/ +static void Q3_SetTimeScale( int entID, const char *data ) +{ + trap_Cvar_Set("timescale", data); +} + + +/* +============ +Q3_SetInvisible + Description : + Return type : static void + Argument : int entID + Argument : qboolean invisible +============ +*/ +static void Q3_SetInvisible( int entID, qboolean invisible ) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetInvisible: invalid entID %d\n", entID); + return; + } + + if ( invisible ) + { + self->s.eFlags |= EF_NODRAW; + if ( self->client ) + { + self->client->ps.eFlags |= EF_NODRAW; + } + self->r.contents = 0; + } + else + { + self->s.eFlags &= ~EF_NODRAW; + if ( self->client ) + { + self->client->ps.eFlags &= ~EF_NODRAW; + } + } +} + +/* +============ +Q3_SetVampire + Description : + Return type : static void + Argument : int entID + Argument : qboolean vampire +============ +*/ +static void Q3_SetVampire( int entID, qboolean vampire ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetVampire: NOT SUPPORTED IN MP\n"); + return; +} +/* +============ +Q3_SetGreetAllies + Description : + Return type : static void + Argument : int entID + Argument : qboolean greet +============ +*/ +static void Q3_SetGreetAllies( int entID, qboolean greet ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetGreetAllies: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetViewTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *name +============ +*/ +static void Q3_SetViewTarget (int entID, const char *name) +{ + G_DebugPrint( WL_WARNING, "Q3_SetViewTarget: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetWatchTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *name +============ +*/ +static void Q3_SetWatchTarget (int entID, const char *name) +{ + G_DebugPrint( WL_WARNING, "Q3_SetWatchTarget: NOT SUPPORTED IN MP\n"); + return; +} + +void Q3_SetLoopSound(int entID, const char *name) +{ + sfxHandle_t index; + gentity_t *self = &g_entities[entID]; + + if ( Q_stricmp( "NULL", name ) == 0 || Q_stricmp( "NONE", name )==0) + { + self->s.loopSound = 0; + self->s.loopIsSoundset = qfalse; + return; + } + + index = G_SoundIndex( (char*)name ); + + if (index) + { + self->s.loopSound = index; + self->s.loopIsSoundset = qfalse; + } + else + { + G_DebugPrint( WL_WARNING, "Q3_SetLoopSound: can't find sound file: '%s'\n", name ); + } +} + +void Q3_SetICARUSFreeze( int entID, const char *name, qboolean freeze ) +{ + gentity_t *self = G_Find( NULL, FOFS(targetname), name ); + if ( !self ) + {//hmm, targetname failed, try script_targetname? + self = G_Find( NULL, FOFS(script_targetname), name ); + } + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetICARUSFreeze: invalid ent %s\n", name); + return; + } + + if ( freeze ) + { + self->r.svFlags |= SVF_ICARUS_FREEZE; + } + else + { + self->r.svFlags &= ~SVF_ICARUS_FREEZE; + } +} + +/* +============ +Q3_SetViewEntity + Description : + Return type : static void + Argument : int entID + Argument : const char *name +============ +*/ +void Q3_SetViewEntity(int entID, const char *name) +{ + G_DebugPrint( WL_WARNING, "Q3_SetViewEntity currently unsupported in MP, ask if you need it.\n"); +} + +/* +============ +Q3_SetWeapon + Description : + Return type : static void + Argument : int entID + Argument : const char *wp_name +============ +*/ +static void Q3_SetWeapon (int entID, const char *wp_name) +{ + G_DebugPrint( WL_WARNING, "Q3_SetWeapon currently unsupported in MP, ask if you need it.\n"); +} + +/* +============ +Q3_SetItem + Description : + Return type : static void + Argument : int entID + Argument : const char *wp_name +============ +*/ +static void Q3_SetItem (int entID, const char *item_name) +{ //rww - unused in mp + G_DebugPrint( WL_WARNING, "Q3_SetItem: NOT SUPPORTED IN MP\n"); + return; +} + + + +/* +============ +Q3_SetWalkSpeed + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetWalkSpeed (int entID, int int_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetWalkSpeed: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetRunSpeed + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetRunSpeed (int entID, int int_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetRunSpeed: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetYawSpeed + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetYawSpeed (int entID, float float_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetYawSpeed: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetAggression + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetAggression(int entID, int int_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAggression: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetAim + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetAim(int entID, int int_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAim: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetFriction + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetFriction(int entID, int int_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetFriction: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetFriction: '%s' is not an NPC/player!\n", self->targetname ); + return; + } + + G_DebugPrint( WL_WARNING, "Q3_SetFriction currently unsupported in MP\n"); +// self->client->ps.friction = int_data; +} + + +/* +============ +Q3_SetGravity + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetGravity(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetGravity: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetGravity: '%s' is not an NPC/player!\n", self->targetname ); + return; + } + + //FIXME: what if we want to return them to normal global gravity? + if ( self->NPC ) + { + self->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY; + } + self->client->ps.gravity = float_data; +} + + +/* +============ +Q3_SetWait + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetWait(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetWait: invalid entID %d\n", entID); + return; + } + + self->wait = float_data; +} + + +static void Q3_SetShotSpacing(int entID, int int_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetShotSpacing: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetFollowDist + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetFollowDist(int entID, float float_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetFollowDist: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetScale + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetScale(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetScale: invalid entID %d\n", entID); + return; + } + + if (self->client) + { + self->client->ps.iModelScale = float_data*100.0f; + } + else + { + self->s.iModelScale = float_data*100.0f; + } +} + +/* +============ +Q3_GameSideCheckStringCounterIncrement + Description : + Return type : static float + Argument : const char *string +============ +*/ +static float Q3_GameSideCheckStringCounterIncrement( const char *string ) +{ + char *numString; + float val = 0.0f; + + if ( string[0] == '+' ) + {//We want to increment whatever the value is by whatever follows the + + if ( string[1] ) + { + numString = (char *)&string[1]; + val = atof( numString ); + } + } + else if ( string[0] == '-' ) + {//we want to decrement + if ( string[1] ) + { + numString = (char *)&string[1]; + val = atof( numString ) * -1; + } + } + + return val; +} + +/* +============ +Q3_SetCount + Description : + Return type : static void + Argument : int entID + Argument : const char *data +============ +*/ +static void Q3_SetCount(int entID, const char *data) +{ + gentity_t *self = &g_entities[entID]; + float val = 0.0f; + + //FIXME: use FOFS() stuff here to make a generic entity field setting? + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetCount: invalid entID %d\n", entID); + return; + } + + if ( (val = Q3_GameSideCheckStringCounterIncrement( data )) ) + { + self->count += (int)(val); + } + else + { + self->count = atoi((char *) data); + } +} + + +/* +============ +Q3_SetTargetName + Description : + Return type : static void + Argument : int entID + Argument : const char *targetname +============ +*/ +static void Q3_SetTargetName (int entID, const char *targetname) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetTargetName: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)targetname))) + { + self->targetname = NULL; + } + else + { + self->targetname = G_NewString( targetname ); + } +} + + +/* +============ +Q3_SetTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *target +============ +*/ +static void Q3_SetTarget (int entID, const char *target) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetTarget: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)target))) + { + self->target = NULL; + } + else + { + self->target = G_NewString( target ); + } +} + +/* +============ +Q3_SetTarget2 + Description : + Return type : static void + Argument : int entID + Argument : const char *target +============ +*/ +static void Q3_SetTarget2 (int entID, const char *target2) +{ + G_DebugPrint( WL_WARNING, "Q3_SetTarget2 does not exist in MP\n"); + /* + sharedEntity_t *self = SV_GentityNum(entID); + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetTarget2: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)target2))) + { + self->target2 = NULL; + } + else + { + self->target2 = G_NewString( target2 ); + } + */ +} +/* +============ +Q3_SetRemoveTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *target +============ +*/ +static void Q3_SetRemoveTarget (int entID, const char *target) +{ + G_DebugPrint( WL_WARNING, "Q3_SetRemoveTarget: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetPainTarget + Description : + Return type : void + Argument : int entID + Argument : const char *targetname +============ +*/ +static void Q3_SetPainTarget (int entID, const char *targetname) +{ + G_DebugPrint( WL_WARNING, "Q3_SetPainTarget: NOT SUPPORTED IN MP\n"); + /* + sharedEntity_t *self = SV_GentityNum(entID); + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetPainTarget: invalid entID %d\n", entID); + return; + } + + if(Q_stricmp("NULL", ((char *)targetname)) == 0) + { + self->paintarget = NULL; + } + else + { + self->paintarget = G_NewString((char *)targetname); + } + */ +} + +/* +============ +Q3_SetFullName + Description : + Return type : static void + Argument : int entID + Argument : const char *fullName +============ +*/ +static void Q3_SetFullName (int entID, const char *fullName) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetFullName: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)fullName))) + { + self->fullName = NULL; + } + else + { + self->fullName = G_NewString( fullName ); + } +} + +static void Q3_SetMusicState( const char *dms ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetMusicState: NOT SUPPORTED IN MP\n"); + return; +} + +static void Q3_SetForcePowerLevel ( int entID, int forcePower, int forceLevel ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetForcePowerLevel: NOT SUPPORTED IN MP\n"); + return; +} +/* +============ +Q3_SetParm + Description : + Return type : void + Argument : int entID + Argument : int parmNum + Argument : const char *parmValue +============ +*/ +void Q3_SetParm (int entID, int parmNum, const char *parmValue) +{ + gentity_t *ent = &g_entities[entID]; + float val; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetParm: invalid entID %d\n", entID); + return; + } + + if ( parmNum < 0 || parmNum >= MAX_PARMS ) + { + G_DebugPrint( WL_WARNING, "SET_PARM: parmNum %d out of range!\n", parmNum ); + return; + } + + if( !ent->parms ) + { + ent->parms = (parms_t *)G_Alloc( sizeof(parms_t) ); + memset( ent->parms, 0, sizeof(parms_t) ); + } + + if ( (val = Q3_GameSideCheckStringCounterIncrement( parmValue )) ) + { + val += atof( ent->parms->parm[parmNum] ); + Com_sprintf( ent->parms->parm[parmNum], sizeof(ent->parms->parm), "%f", val ); + } + else + {//Just copy the string + //copy only 16 characters + strncpy( ent->parms->parm[parmNum], parmValue, sizeof(ent->parms->parm[0]) ); + //set the last charcter to null in case we had to truncate their passed string + if ( ent->parms->parm[parmNum][sizeof(ent->parms->parm[0]) - 1] != 0 ) + {//Tried to set a string that is too long + ent->parms->parm[parmNum][sizeof(ent->parms->parm[0]) - 1] = 0; + G_DebugPrint( WL_WARNING, "SET_PARM: parm%d string too long, truncated to '%s'!\n", parmNum, ent->parms->parm[parmNum] ); + } + } +} + + + +/* +============= +Q3_SetCaptureGoal + +Sets the capture spot goal of an entity +============= +*/ +static void Q3_SetCaptureGoal( int entID, const char *name ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetCaptureGoal: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============= +Q3_SetEvent + +? +============= +*/ +static void Q3_SetEvent( int entID, const char *event_name ) +{ //rwwFIXMEFIXME: Use in MP? + G_DebugPrint( WL_WARNING, "Q3_SetEvent: NOT SUPPORTED IN MP (may be in future, ask if needed)\n"); + return; +} + +/* +============ +Q3_SetIgnorePain + +? +============ +*/ +static void Q3_SetIgnorePain( int entID, qboolean data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetIgnorePain: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetIgnoreEnemies + +? +============ +*/ +static void Q3_SetIgnoreEnemies( int entID, qboolean data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetIgnoreEnemies: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetIgnoreAlerts + +? +============ +*/ +static void Q3_SetIgnoreAlerts( int entID, qboolean data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetIgnoreAlerts: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetNoTarget + +? +============ +*/ +static void Q3_SetNoTarget( int entID, qboolean data) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetNoTarget: invalid entID %d\n", entID); + return; + } + + if(data) + ent->flags |= FL_NOTARGET; + else + ent->flags &= ~FL_NOTARGET; +} + +/* +============ +Q3_SetDontShoot + +? +============ +*/ +static void Q3_SetDontShoot( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDontShoot: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetDontFire + +? +============ +*/ +static void Q3_SetDontFire( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDontFire: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetFireWeapon + +? +============ +*/ +static void Q3_SetFireWeapon(int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetFireWeapon: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetInactive + +? +============ +*/ +static void Q3_SetInactive(int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetInactive: invalid entID %d\n", entID); + return; + } + + if(add) + { + ent->flags |= FL_INACTIVE; + } + else + { + ent->flags &= ~FL_INACTIVE; + } +} + +/* +============ +Q3_SetFuncUsableVisible + +? +============ +*/ +static void Q3_SetFuncUsableVisible(int entID, qboolean visible ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetFuncUsableVisible: invalid entID %d\n", entID); + return; + } + + // Yeah, I know that this doesn't even do half of what the func_usable use code does, but if I've got two things on top of each other...and only + // one is visible at a time....and neither can ever be used......and finally, the shader on it has the shader_anim stuff going on....It doesn't seem + // like I can easily use the other version without nasty side effects. + if( visible ) + { + ent->r.svFlags &= ~SVF_NOCLIENT; + ent->s.eFlags &= ~EF_NODRAW; + } + else + { + ent->r.svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + } +} + +/* +============ +Q3_SetLockedEnemy + +? +============ +*/ +static void Q3_SetLockedEnemy ( int entID, qboolean locked) +{ + G_DebugPrint( WL_WARNING, "Q3_SetLockedEnemy: NOT SUPPORTED IN MP\n"); + return; +} + +char cinematicSkipScript[1024]; + +/* +============ +Q3_SetCinematicSkipScript + +============ +*/ +static void Q3_SetCinematicSkipScript( char *scriptname ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetCinematicSkipScript: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoMindTrick + +? +============ +*/ +static void Q3_SetNoMindTrick( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoMindTrick: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetCrouched + +? +============ +*/ +static void Q3_SetCrouched( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetCrouched: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetWalking + +? +============ +*/ +static void Q3_SetWalking( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetWalking: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetRunning + +? +============ +*/ +static void Q3_SetRunning( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetRunning: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetForcedMarch + +? +============ +*/ +static void Q3_SetForcedMarch( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetForcedMarch: NOT SUPPORTED IN MP\n"); + return; +} +/* +============ +Q3_SetChaseEnemies + +indicates whether the npc should chase after an enemy +============ +*/ +static void Q3_SetChaseEnemies( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetChaseEnemies: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetLookForEnemies + +if set npc will be on the look out for potential enemies +if not set, npc will ignore enemies +============ +*/ +static void Q3_SetLookForEnemies( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetLookForEnemies: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetFaceMoveDir + +============ +*/ +static void Q3_SetFaceMoveDir( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetFaceMoveDir: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetAltFire + +? +============ +*/ +static void Q3_SetAltFire( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAltFire: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetDontFlee + +? +============ +*/ +static void Q3_SetDontFlee( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDontFlee: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoResponse + +? +============ +*/ +static void Q3_SetNoResponse( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoResponse: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetCombatTalk + +? +============ +*/ +static void Q3_SetCombatTalk( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetCombatTalk: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetAlertTalk + +? +============ +*/ +static void Q3_SetAlertTalk( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAlertTalk: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetUseCpNearest + +? +============ +*/ +static void Q3_SetUseCpNearest( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetUseCpNearest: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoForce + +? +============ +*/ +static void Q3_SetNoForce( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoForce: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoAcrobatics + +? +============ +*/ +static void Q3_SetNoAcrobatics( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoAcrobatics: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetUseSubtitles + +? +============ +*/ +static void Q3_SetUseSubtitles( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetUseSubtitles: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoFallToDeath + +? +============ +*/ +static void Q3_SetNoFallToDeath( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoFallToDeath: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetDismemberable + +? +============ +*/ +static void Q3_SetDismemberable( int entID, qboolean dismemberable) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDismemberable: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetMoreLight + +? +============ +*/ +static void Q3_SetMoreLight( int entID, qboolean add ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetMoreLight: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetUndying + +? +============ +*/ +static void Q3_SetUndying( int entID, qboolean undying) +{ + G_DebugPrint( WL_WARNING, "Q3_SetUndying: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetInvincible + +? +============ +*/ +static void Q3_SetInvincible( int entID, qboolean invincible) +{ + G_DebugPrint( WL_WARNING, "Q3_SetInvicible: NOT SUPPORTED IN MP\n"); + return; +} +/* +============ +Q3_SetForceInvincible + Description : + Return type : static void + Argument : int entID + Argument : qboolean forceInv +============ +*/ +static void Q3_SetForceInvincible( int entID, qboolean forceInv ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetForceInvicible: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoAvoid + +? +============ +*/ +static void Q3_SetNoAvoid( int entID, qboolean noAvoid) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoAvoid: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +SolidifyOwner + Description : + Return type : void + Argument : sharedEntity_t *self +============ +*/ +void SolidifyOwner( gentity_t *self ) +{ + int oldContents; + gentity_t *owner = &g_entities[self->r.ownerNum]; + + self->nextthink = level.time + FRAMETIME; + self->think = G_FreeEntity; + + if ( !owner || !owner->inuse ) + { + return; + } + + oldContents = owner->r.contents; + owner->r.contents = CONTENTS_BODY; + if ( SpotWouldTelefrag2( owner, owner->r.currentOrigin ) ) + { + owner->r.contents = oldContents; + self->think = SolidifyOwner; + } + else + { + trap_ICARUS_TaskIDComplete( owner, TID_RESIZE ); + } +} + + +/* +============ +Q3_SetSolid + +? +============ +*/ +static qboolean Q3_SetSolid( int entID, qboolean solid) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent || !ent->inuse ) + { + G_DebugPrint( WL_WARNING, "Q3_SetSolid: invalid entID %d\n", entID); + return qtrue; + } + + if ( solid ) + {//FIXME: Presumption + int oldContents = ent->r.contents; + ent->r.contents = CONTENTS_BODY; + if ( SpotWouldTelefrag2( ent, ent->r.currentOrigin ) ) + { + gentity_t *solidifier = G_Spawn(); + + solidifier->r.ownerNum = ent->s.number; + + solidifier->think = SolidifyOwner; + solidifier->nextthink = level.time + FRAMETIME; + + ent->r.contents = oldContents; + return qfalse; + } + ent->clipmask |= CONTENTS_BODY; + } + else + {//FIXME: Presumption + if ( ent->s.eFlags & EF_NODRAW ) + {//We're invisible too, so set contents to none + ent->r.contents = 0; + } + else + { + ent->r.contents = CONTENTS_CORPSE; + } + } + return qtrue; +} + +/* +============ +Q3_SetForwardMove + +? +============ +*/ +static void Q3_SetForwardMove( int entID, int fmoveVal) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetForwardMove: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetForwardMove: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + G_DebugPrint( WL_WARNING, "Q3_SetForwardMove: NOT SUPPORTED IN MP\n"); + //ent->client->forced_forwardmove = fmoveVal; +} + +/* +============ +Q3_SetRightMove + +? +============ +*/ +static void Q3_SetRightMove( int entID, int rmoveVal) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetRightMove: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetRightMove: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + G_DebugPrint( WL_WARNING, "Q3_SetRightMove: NOT SUPPORTED IN MP\n"); + //ent->client->forced_rightmove = rmoveVal; +} + +/* +============ +Q3_SetLockAngle + +? +============ +*/ +static void Q3_SetLockAngle( int entID, const char *lockAngle) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetLockAngle: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetLockAngle: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + G_DebugPrint( WL_WARNING, "Q3_SetLockAngle is not currently available. Ask if you really need it.\n"); + /* + if(Q_stricmp("off", lockAngle) == 0) + {//free it + ent->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE; + } + else + { + ent->client->renderInfo.renderFlags |= RF_LOCKEDANGLE; + + + if(Q_stricmp("auto", lockAngle) == 0) + {//use current yaw + ent->client->renderInfo.lockYaw = ent->client->ps.viewangles[YAW]; + } + else + {//specified yaw + ent->client->renderInfo.lockYaw = atof((char *)lockAngle); + } + } + */ +} + + +/* +============ +Q3_CameraGroup + +? +============ +*/ +static void Q3_CameraGroup( int entID, char *camG) +{ + G_DebugPrint( WL_WARNING, "Q3_CameraGroup: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_CameraGroupZOfs + +? +============ +*/ +static void Q3_CameraGroupZOfs( float camGZOfs ) +{ + G_DebugPrint( WL_WARNING, "Q3_CameraGroupZOfs: NOT SUPPORTED IN MP\n"); + return; +} +/* +============ +Q3_CameraGroup + +? +============ +*/ +static void Q3_CameraGroupTag( char *camGTag ) +{ + G_DebugPrint( WL_WARNING, "Q3_CameraGroupTag: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_RemoveRHandModel +============ +*/ +static void Q3_RemoveRHandModel( int entID, char *addModel) +{ + G_DebugPrint( WL_WARNING, "Q3_RemoveRHandModel: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_AddRHandModel +============ +*/ +static void Q3_AddRHandModel( int entID, char *addModel) +{ + G_DebugPrint( WL_WARNING, "Q3_AddRHandModel: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_AddLHandModel +============ +*/ +static void Q3_AddLHandModel( int entID, char *addModel) +{ + G_DebugPrint( WL_WARNING, "Q3_AddLHandModel: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_RemoveLHandModel +============ +*/ +static void Q3_RemoveLHandModel( int entID, char *addModel) +{ + G_DebugPrint( WL_WARNING, "Q3_RemoveLHandModel: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_LookTarget + +? +============ +*/ +static void Q3_LookTarget( int entID, char *targetName) +{ + G_DebugPrint( WL_WARNING, "Q3_LookTarget: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_Face + +? +============ +*/ +static void Q3_Face( int entID,int expression, float holdtime) +{ + G_DebugPrint( WL_WARNING, "Q3_Face: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_SetLocation + Description : + Return type : qboolean + Argument : int entID + Argument : const char *location +============ +*/ +static qboolean Q3_SetLocation( int entID, const char *location ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetLocation: NOT SUPPORTED IN MP\n"); + return qtrue; +} + +/* +============ +Q3_SetPlayerLocked + Description : + Return type : void + Argument : int entID + Argument : qboolean locked +============ +*/ +qboolean player_locked = qfalse; +static void Q3_SetPlayerLocked( int entID, qboolean locked ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetPlayerLocked: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_SetLockPlayerWeapons + Description : + Return type : void + Argument : int entID + Argument : qboolean locked +============ +*/ +static void Q3_SetLockPlayerWeapons( int entID, qboolean locked ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetLockPlayerWeapons: NOT SUPPORTED IN MP\n"); +} + + +/* +============ +Q3_SetNoImpactDamage + Description : + Return type : void + Argument : int entID + Argument : qboolean locked +============ +*/ +static void Q3_SetNoImpactDamage( int entID, qboolean noImp ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoImpactDamage: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_SetBehaviorSet + +? +============ +*/ +static qboolean Q3_SetBehaviorSet( int entID, int toSet, const char *scriptname) +{ + gentity_t *ent = &g_entities[entID]; + bSet_t bSet = BSET_INVALID; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetBehaviorSet: invalid entID %d\n", entID); + return qfalse; + } + + switch(toSet) + { + case SET_SPAWNSCRIPT: + bSet = BSET_SPAWN; + break; + case SET_USESCRIPT: + bSet = BSET_USE; + break; + case SET_AWAKESCRIPT: + bSet = BSET_AWAKE; + break; + case SET_ANGERSCRIPT: + bSet = BSET_ANGER; + break; + case SET_ATTACKSCRIPT: + bSet = BSET_ATTACK; + break; + case SET_VICTORYSCRIPT: + bSet = BSET_VICTORY; + break; + case SET_LOSTENEMYSCRIPT: + bSet = BSET_LOSTENEMY; + break; + case SET_PAINSCRIPT: + bSet = BSET_PAIN; + break; + case SET_FLEESCRIPT: + bSet = BSET_FLEE; + break; + case SET_DEATHSCRIPT: + bSet = BSET_DEATH; + break; + case SET_DELAYEDSCRIPT: + bSet = BSET_DELAYED; + break; + case SET_BLOCKEDSCRIPT: + bSet = BSET_BLOCKED; + break; + case SET_FFIRESCRIPT: + bSet = BSET_FFIRE; + break; + case SET_FFDEATHSCRIPT: + bSet = BSET_FFDEATH; + break; + case SET_MINDTRICKSCRIPT: + bSet = BSET_MINDTRICK; + break; + } + + if(bSet < BSET_SPAWN || bSet >= NUM_BSETS) + { + return qfalse; + } + + if(!Q_stricmp("NULL", scriptname)) + { + if ( ent->behaviorSet[bSet] != NULL ) + { +// gi.TagFree( ent->behaviorSet[bSet] ); + } + + ent->behaviorSet[bSet] = NULL; + //memset( &ent->behaviorSet[bSet], 0, sizeof(ent->behaviorSet[bSet]) ); + } + else + { + if ( scriptname ) + { + if ( ent->behaviorSet[bSet] != NULL ) + { +// gi.TagFree( ent->behaviorSet[bSet] ); + } + + ent->behaviorSet[bSet] = G_NewString( (char *) scriptname ); //FIXME: This really isn't good... + } + + //ent->behaviorSet[bSet] = scriptname; + //strncpy( (char *) &ent->behaviorSet[bSet], scriptname, MAX_BSET_LENGTH ); + } + return qtrue; +} + +/* +============ +Q3_SetDelayScriptTime + +? +============ +*/ +static void Q3_SetDelayScriptTime(int entID, int delayTime) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDelayScriptTime: NOT SUPPORTED IN MP\n"); +} + + + + +/* +============ +Q3_SetPlayerUsable + Description : + Return type : void + Argument : int entID + Argument : qboolean usable +============ +*/ +static void Q3_SetPlayerUsable( int entID, qboolean usable ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetPlayerUsable: invalid entID %d\n", entID); + return; + } + + if(usable) + { + ent->r.svFlags |= SVF_PLAYER_USABLE; + } + else + { + ent->r.svFlags &= ~SVF_PLAYER_USABLE; + } +} + +/* +============ +Q3_SetDisableShaderAnims + Description : + Return type : static void + Argument : int entID + Argument : int disabled +============ +*/ +static void Q3_SetDisableShaderAnims( int entID, int disabled ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDisableShaderAnims: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetShaderAnim + Description : + Return type : static void + Argument : int entID + Argument : int disabled +============ +*/ +static void Q3_SetShaderAnim( int entID, int disabled ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetShaderAnim: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetStartFrame + Description : + Return type : static void + Argument : int entID + Argument : int startFrame +============ +*/ +static void Q3_SetStartFrame( int entID, int startFrame ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetStartFrame: NOT SUPPORTED IN MP\n"); +} + + +/* +============ +Q3_SetEndFrame + Description : + Return type : static void + Argument : int entID + Argument : int endFrame +============ +*/ +static void Q3_SetEndFrame( int entID, int endFrame ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetEndFrame: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_SetAnimFrame + Description : + Return type : static void + Argument : int entID + Argument : int startFrame +============ +*/ +static void Q3_SetAnimFrame( int entID, int animFrame ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAnimFrame: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_SetLoopAnim + Description : + Return type : void + Argument : int entID + Argument : qboolean loopAnim +============ +*/ +static void Q3_SetLoopAnim( int entID, qboolean loopAnim ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetLoopAnim: NOT SUPPORTED IN MP\n"); +} + + +/* +============ +Q3_SetShields + Description : + Return type : void + Argument : int entID + Argument : qboolean shields +============ +*/ +static void Q3_SetShields( int entID, qboolean shields ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetShields: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetSaberActive + Description : + Return type : void + Argument : int entID + Argument : qboolean shields +============ +*/ +static void Q3_SetSaberActive( int entID, qboolean active ) +{ + gentity_t *ent = &g_entities[entID]; + + if (!ent || !ent->inuse) + { + return; + } + + if (!ent->client) + { + G_DebugPrint( WL_WARNING, "Q3_SetSaberActive: %d is not a client\n", entID); + } + + //fixme: Take into account player being in state where saber won't toggle? For now we simply won't care. + if (!ent->client->ps.saberHolstered && active) + { + Cmd_ToggleSaber_f(ent); + } + else if (BG_SabersOff( &ent->client->ps ) && !active) + { + Cmd_ToggleSaber_f(ent); + } +} + +/* +============ +Q3_SetNoKnockback + Description : + Return type : void + Argument : int entID + Argument : qboolean noKnockback +============ +*/ +static void Q3_SetNoKnockback( int entID, qboolean noKnockback ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetNoKnockback: invalid entID %d\n", entID); + return; + } + + if ( noKnockback ) + { + ent->flags |= FL_NO_KNOCKBACK; + } + else + { + ent->flags &= ~FL_NO_KNOCKBACK; + } +} + +/* +============ +Q3_SetCleanDamagingEnts + Description : + Return type : void +============ +*/ +static void Q3_SetCleanDamagingEnts( void ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetCleanDamagingEnts: NOT SUPPORTED IN MP\n"); + return; +} + +vec4_t textcolor_caption; +vec4_t textcolor_center; +vec4_t textcolor_scroll; + +/* +------------------------- +SetTextColor +------------------------- +*/ +static void SetTextColor ( vec4_t textcolor,const char *color) +{ + G_DebugPrint( WL_WARNING, "SetTextColor: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============= +Q3_SetCaptionTextColor + +Change color text prints in +============= +*/ +static void Q3_SetCaptionTextColor ( const char *color) +{ + SetTextColor(textcolor_caption,color); +} + +/* +============= +Q3_SetCenterTextColor + +Change color text prints in +============= +*/ +static void Q3_SetCenterTextColor ( const char *color) +{ + SetTextColor(textcolor_center,color); +} + +/* +============= +Q3_SetScrollTextColor + +Change color text prints in +============= +*/ +static void Q3_SetScrollTextColor ( const char *color) +{ + SetTextColor(textcolor_scroll,color); +} + +/* +============= +Q3_ScrollText + +Prints a message in the center of the screen +============= +*/ +static void Q3_ScrollText ( const char *id) +{ + G_DebugPrint( WL_WARNING, "Q3_ScrollText: NOT SUPPORTED IN MP\n"); + //trap_SendServerCommand( -1, va("st \"%s\"", id)); + + return; +} + +/* +============= +Q3_LCARSText + +Prints a message in the center of the screen giving it an LCARS frame around it +============= +*/ +static void Q3_LCARSText ( const char *id) +{ + G_DebugPrint( WL_WARNING, "Q3_ScrollText: NOT SUPPORTED IN MP\n"); + //trap_SendServerCommand( -1, va("lt \"%s\"", id)); + + return; +} + +void UnLockDoors(gentity_t *const ent); +void LockDoors(gentity_t *const ent); + +//returns qtrue if it got to the end, otherwise qfalse. +qboolean Q3_Set( int taskID, int entID, const char *type_name, const char *data ) +{ + gentity_t *ent = &g_entities[entID]; + float float_data; + int int_data, toSet; + vec3_t vector_data; + + //Set this for callbacks + toSet = GetIDForString( setTable, type_name ); + + //TODO: Throw in a showscript command that will list each command and what they're doing... + // maybe as simple as printing that line of the script to the console preceeded by the person's name? + // showscript can take any number of targetnames or "all"? Groupname? + switch ( toSet ) + { + case SET_ORIGIN: + sscanf( data, "%f %f %f", &vector_data[0], &vector_data[1], &vector_data[2] ); + G_SetOrigin( ent, vector_data ); + if ( Q_strncmp( "NPC_", ent->classname, 4 ) == 0 ) + {//hack for moving spawners + VectorCopy( vector_data, ent->s.origin); + } + break; + + case SET_TELEPORT_DEST: + sscanf( data, "%f %f %f", &vector_data[0], &vector_data[1], &vector_data[2] ); + if ( !Q3_SetTeleportDest( entID, vector_data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + return qfalse; + } + break; + + case SET_COPY_ORIGIN: + Q3_SetCopyOrigin( entID, (char *) data ); + break; + + case SET_ANGLES: + //Q3_SetAngles( entID, *(vec3_t *) data); + sscanf( data, "%f %f %f", &vector_data[0], &vector_data[1], &vector_data[2] ); + Q3_SetAngles( entID, vector_data); + break; + + case SET_XVELOCITY: + float_data = atof((char *) data); + Q3_SetVelocity( entID, 0, float_data); + break; + + case SET_YVELOCITY: + float_data = atof((char *) data); + Q3_SetVelocity( entID, 1, float_data); + break; + + case SET_ZVELOCITY: + float_data = atof((char *) data); + Q3_SetVelocity( entID, 2, float_data); + break; + + case SET_Z_OFFSET: + float_data = atof((char *) data); + Q3_SetOriginOffset( entID, 2, float_data); + break; + + case SET_ENEMY: + G_DebugPrint( WL_WARNING, "Q3_SetEnemy: NOT SUPPORTED IN MP\n"); + break; + + case SET_LEADER: + G_DebugPrint( WL_WARNING, "Q3_SetLeader: NOT SUPPORTED IN MP\n"); + break; + + case SET_NAVGOAL: + G_DebugPrint( WL_WARNING, "Q3_SetNavGoal: NOT SUPPORTED IN MP\n"); + break; + + case SET_ANIM_UPPER: + if ( Q3_SetAnimUpper( entID, (char *) data ) ) + { + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the top + trap_ICARUS_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + return qfalse; //Don't call it back + } + break; + + case SET_ANIM_LOWER: + if ( Q3_SetAnimLower( entID, (char *) data ) ) + { + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the bottom + trap_ICARUS_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + return qfalse; //Don't call it back + } + break; + + case SET_ANIM_BOTH: + { + int both = 0; + if ( Q3_SetAnimUpper( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + both++; + } + else + { + G_DebugPrint( WL_ERROR, "Q3_SetAnimUpper: %s does not have anim %s!\n", ent->targetname, (char *)data ); + } + if ( Q3_SetAnimLower( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + both++; + } + else + { + G_DebugPrint( WL_ERROR, "Q3_SetAnimLower: %s does not have anim %s!\n", ent->targetname, (char *)data ); + } + if ( both >= 2 ) + { + trap_ICARUS_TaskIDSet( ent, TID_ANIM_BOTH, taskID ); + } + if ( both ) + { + return qfalse; //Don't call it back + } + } + break; + + case SET_ANIM_HOLDTIME_LOWER: + int_data = atoi((char *) data); + Q3_SetAnimHoldTime( entID, int_data, qtrue ); + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the bottom + trap_ICARUS_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + return qfalse; //Don't call it back + break; + + case SET_ANIM_HOLDTIME_UPPER: + int_data = atoi((char *) data); + Q3_SetAnimHoldTime( entID, int_data, qfalse ); + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the top + trap_ICARUS_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + return qfalse; //Don't call it back + break; + + case SET_ANIM_HOLDTIME_BOTH: + int_data = atoi((char *) data); + Q3_SetAnimHoldTime( entID, int_data, qfalse ); + Q3_SetAnimHoldTime( entID, int_data, qtrue ); + trap_ICARUS_TaskIDSet( ent, TID_ANIM_BOTH, taskID ); + trap_ICARUS_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + trap_ICARUS_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + return qfalse; //Don't call it back + break; + + case SET_PLAYER_TEAM: + G_DebugPrint( WL_WARNING, "Q3_SetPlayerTeam: Not in MP ATM, let a programmer (ideally Rich) know if you need it\n"); + break; + + case SET_ENEMY_TEAM: + G_DebugPrint( WL_WARNING, "Q3_SetEnemyTeam: NOT SUPPORTED IN MP\n"); + break; + + case SET_HEALTH: + int_data = atoi((char *) data); + Q3_SetHealth( entID, int_data ); + break; + + case SET_ARMOR: + int_data = atoi((char *) data); + Q3_SetArmor( entID, int_data ); + break; + + case SET_BEHAVIOR_STATE: + if( !Q3_SetBState( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_BSTATE, taskID ); + return qfalse;//don't complete + } + break; + + case SET_DEFAULT_BSTATE: + Q3_SetDefaultBState( entID, (char *) data ); + break; + + case SET_TEMP_BSTATE: + if( !Q3_SetTempBState( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_BSTATE, taskID ); + return qfalse;//don't complete + } + break; + + case SET_CAPTURE: + Q3_SetCaptureGoal( entID, (char *) data ); + break; + + case SET_DPITCH://FIXME: make these set tempBehavior to BS_FACE and await completion? Or set lockedDesiredPitch/Yaw and aimTime? + float_data = atof((char *) data); + Q3_SetDPitch( entID, float_data ); + trap_ICARUS_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + return qfalse; + break; + + case SET_DYAW: + float_data = atof((char *) data); + Q3_SetDYaw( entID, float_data ); + trap_ICARUS_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + return qfalse; + break; + + case SET_EVENT: + Q3_SetEvent( entID, (char *) data ); + break; + + case SET_VIEWTARGET: + Q3_SetViewTarget( entID, (char *) data ); + trap_ICARUS_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + return qfalse; + break; + + case SET_WATCHTARGET: + Q3_SetWatchTarget( entID, (char *) data ); + break; + + case SET_VIEWENTITY: + Q3_SetViewEntity( entID, (char *) data ); + break; + + case SET_LOOPSOUND: + Q3_SetLoopSound( entID, (char *) data ); + break; + + case SET_ICARUS_FREEZE: + case SET_ICARUS_UNFREEZE: + Q3_SetICARUSFreeze( entID, (char *) data, (qboolean)(toSet==SET_ICARUS_FREEZE) ); + break; + + case SET_WEAPON: + Q3_SetWeapon ( entID, (char *) data); + break; + + case SET_ITEM: + Q3_SetItem ( entID, (char *) data); + break; + + case SET_WALKSPEED: + int_data = atoi((char *) data); + Q3_SetWalkSpeed ( entID, int_data); + break; + + case SET_RUNSPEED: + int_data = atoi((char *) data); + Q3_SetRunSpeed ( entID, int_data); + break; + + case SET_WIDTH: + int_data = atoi((char *) data); + Q3_SetWidth( entID, int_data ); + return qfalse; + break; + + case SET_YAWSPEED: + float_data = atof((char *) data); + Q3_SetYawSpeed ( entID, float_data); + break; + + case SET_AGGRESSION: + int_data = atoi((char *) data); + Q3_SetAggression ( entID, int_data); + break; + + case SET_AIM: + int_data = atoi((char *) data); + Q3_SetAim ( entID, int_data); + break; + + case SET_FRICTION: + int_data = atoi((char *) data); + Q3_SetFriction ( entID, int_data); + break; + + case SET_GRAVITY: + float_data = atof((char *) data); + Q3_SetGravity ( entID, float_data); + break; + + case SET_WAIT: + float_data = atof((char *) data); + Q3_SetWait( entID, float_data); + break; + + case SET_FOLLOWDIST: + float_data = atof((char *) data); + Q3_SetFollowDist( entID, float_data); + break; + + case SET_SCALE: + float_data = atof((char *) data); + Q3_SetScale( entID, float_data); + break; + + case SET_COUNT: + Q3_SetCount( entID, (char *) data); + break; + + case SET_SHOT_SPACING: + int_data = atoi((char *) data); + Q3_SetShotSpacing( entID, int_data ); + break; + + case SET_IGNOREPAIN: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetIgnorePain( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetIgnorePain( entID, qfalse); + break; + + case SET_IGNOREENEMIES: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetIgnoreEnemies( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetIgnoreEnemies( entID, qfalse); + break; + + case SET_IGNOREALERTS: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetIgnoreAlerts( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetIgnoreAlerts( entID, qfalse); + break; + + case SET_DONTSHOOT: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetDontShoot( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetDontShoot( entID, qfalse); + break; + + case SET_DONTFIRE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetDontFire( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetDontFire( entID, qfalse); + break; + + case SET_LOCKED_ENEMY: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetLockedEnemy( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetLockedEnemy( entID, qfalse); + break; + + case SET_NOTARGET: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoTarget( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetNoTarget( entID, qfalse); + break; + + case SET_LEAN: + G_DebugPrint( WL_WARNING, "SET_LEAN NOT SUPPORTED IN MP\n" ); + break; + + case SET_SHOOTDIST: + float_data = atof((char *) data); + Q3_SetShootDist( entID, float_data ); + break; + + case SET_TIMESCALE: + Q3_SetTimeScale( entID, (char *) data ); + break; + + case SET_VISRANGE: + float_data = atof((char *) data); + Q3_SetVisrange( entID, float_data ); + break; + + case SET_EARSHOT: + float_data = atof((char *) data); + Q3_SetEarshot( entID, float_data ); + break; + + case SET_VIGILANCE: + float_data = atof((char *) data); + Q3_SetVigilance( entID, float_data ); + break; + + case SET_VFOV: + int_data = atoi((char *) data); + Q3_SetVFOV( entID, int_data ); + break; + + case SET_HFOV: + int_data = atoi((char *) data); + Q3_SetHFOV( entID, int_data ); + break; + + case SET_TARGETNAME: + Q3_SetTargetName( entID, (char *) data ); + break; + + case SET_TARGET: + Q3_SetTarget( entID, (char *) data ); + break; + + case SET_TARGET2: + Q3_SetTarget2( entID, (char *) data ); + break; + + case SET_LOCATION: + if ( !Q3_SetLocation( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_LOCATION, taskID ); + return qfalse; + } + break; + + case SET_PAINTARGET: + Q3_SetPainTarget( entID, (char *) data ); + break; + + case SET_DEFEND_TARGET: + G_DebugPrint( WL_WARNING, "Q3_SetDefendTarget unimplemented\n", entID ); + //Q3_SetEnemy( entID, (char *) data); + break; + + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + Q3_SetParm( entID, (toSet-SET_PARM1), (char *) data ); + break; + + case SET_SPAWNSCRIPT: + case SET_USESCRIPT: + case SET_AWAKESCRIPT: + case SET_ANGERSCRIPT: + case SET_ATTACKSCRIPT: + case SET_VICTORYSCRIPT: + case SET_PAINSCRIPT: + case SET_FLEESCRIPT: + case SET_DEATHSCRIPT: + case SET_DELAYEDSCRIPT: + case SET_BLOCKEDSCRIPT: + case SET_FFIRESCRIPT: + case SET_FFDEATHSCRIPT: + case SET_MINDTRICKSCRIPT: + if( !Q3_SetBehaviorSet(entID, toSet, (char *) data) ) + G_DebugPrint( WL_ERROR, "Q3_SetBehaviorSet: Invalid bSet %s\n", type_name ); + break; + + case SET_NO_MINDTRICK: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoMindTrick( entID, qtrue); + else + Q3_SetNoMindTrick( entID, qfalse); + break; + + case SET_CINEMATIC_SKIPSCRIPT : + Q3_SetCinematicSkipScript((char *) data); + break; + + + case SET_DELAYSCRIPTTIME: + int_data = atoi((char *) data); + Q3_SetDelayScriptTime( entID, int_data ); + break; + + case SET_CROUCHED: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetCrouched( entID, qtrue); + else + Q3_SetCrouched( entID, qfalse); + break; + + case SET_WALKING: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetWalking( entID, qtrue); + else + Q3_SetWalking( entID, qfalse); + break; + + case SET_RUNNING: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetRunning( entID, qtrue); + else + Q3_SetRunning( entID, qfalse); + break; + + case SET_CHASE_ENEMIES: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetChaseEnemies( entID, qtrue); + else + Q3_SetChaseEnemies( entID, qfalse); + break; + + case SET_LOOK_FOR_ENEMIES: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetLookForEnemies( entID, qtrue); + else + Q3_SetLookForEnemies( entID, qfalse); + break; + + case SET_FACE_MOVE_DIR: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetFaceMoveDir( entID, qtrue); + else + Q3_SetFaceMoveDir( entID, qfalse); + break; + + case SET_ALT_FIRE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetAltFire( entID, qtrue); + else + Q3_SetAltFire( entID, qfalse); + break; + + case SET_DONT_FLEE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetDontFlee( entID, qtrue); + else + Q3_SetDontFlee( entID, qfalse); + break; + + case SET_FORCED_MARCH: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetForcedMarch( entID, qtrue); + else + Q3_SetForcedMarch( entID, qfalse); + break; + + case SET_NO_RESPONSE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoResponse( entID, qtrue); + else + Q3_SetNoResponse( entID, qfalse); + break; + + case SET_NO_COMBAT_TALK: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetCombatTalk( entID, qtrue); + else + Q3_SetCombatTalk( entID, qfalse); + break; + + case SET_NO_ALERT_TALK: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetAlertTalk( entID, qtrue); + else + Q3_SetAlertTalk( entID, qfalse); + break; + + case SET_USE_CP_NEAREST: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetUseCpNearest( entID, qtrue); + else + Q3_SetUseCpNearest( entID, qfalse); + break; + + case SET_NO_FORCE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoForce( entID, qtrue); + else + Q3_SetNoForce( entID, qfalse); + break; + + case SET_NO_ACROBATICS: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoAcrobatics( entID, qtrue); + else + Q3_SetNoAcrobatics( entID, qfalse); + break; + + case SET_USE_SUBTITLES: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetUseSubtitles( entID, qtrue); + else + Q3_SetUseSubtitles( entID, qfalse); + break; + + case SET_NO_FALLTODEATH: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoFallToDeath( entID, qtrue); + else + Q3_SetNoFallToDeath( entID, qfalse); + break; + + case SET_DISMEMBERABLE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetDismemberable( entID, qtrue); + else + Q3_SetDismemberable( entID, qfalse); + break; + + case SET_MORELIGHT: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetMoreLight( entID, qtrue); + else + Q3_SetMoreLight( entID, qfalse); + break; + + + case SET_TREASONED: + G_DebugPrint( WL_VERBOSE, "SET_TREASONED is disabled, do not use\n" ); + /* + G_TeamRetaliation( NULL, SV_GentityNum(0), qfalse ); + ffireLevel = FFIRE_LEVEL_RETALIATION; + */ + break; + + case SET_UNDYING: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetUndying( entID, qtrue); + else + Q3_SetUndying( entID, qfalse); + break; + + case SET_INVINCIBLE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetInvincible( entID, qtrue); + else + Q3_SetInvincible( entID, qfalse); + break; + + case SET_NOAVOID: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoAvoid( entID, qtrue); + else + Q3_SetNoAvoid( entID, qfalse); + break; + + case SET_SOLID: + if(!Q_stricmp("true", ((char *)data))) + { + if ( !Q3_SetSolid( entID, qtrue) ) + { + trap_ICARUS_TaskIDSet( ent, TID_RESIZE, taskID ); + return qfalse; + } + } + else + { + Q3_SetSolid( entID, qfalse); + } + break; + + case SET_INVISIBLE: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetInvisible( entID, qtrue ); + else + Q3_SetInvisible( entID, qfalse ); + break; + + case SET_VAMPIRE: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetVampire( entID, qtrue ); + else + Q3_SetVampire( entID, qfalse ); + break; + + case SET_FORCE_INVINCIBLE: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetForceInvincible( entID, qtrue ); + else + Q3_SetForceInvincible( entID, qfalse ); + break; + + case SET_GREET_ALLIES: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetGreetAllies( entID, qtrue ); + else + Q3_SetGreetAllies( entID, qfalse ); + break; + + case SET_PLAYER_LOCKED: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetPlayerLocked( entID, qtrue ); + else + Q3_SetPlayerLocked( entID, qfalse ); + break; + + case SET_LOCK_PLAYER_WEAPONS: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetLockPlayerWeapons( entID, qtrue ); + else + Q3_SetLockPlayerWeapons( entID, qfalse ); + break; + + case SET_NO_IMPACT_DAMAGE: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetNoImpactDamage( entID, qtrue ); + else + Q3_SetNoImpactDamage( entID, qfalse ); + break; + + case SET_FORWARDMOVE: + int_data = atoi((char *) data); + Q3_SetForwardMove( entID, int_data); + break; + + case SET_RIGHTMOVE: + int_data = atoi((char *) data); + Q3_SetRightMove( entID, int_data); + break; + + case SET_LOCKYAW: + Q3_SetLockAngle( entID, data); + break; + + case SET_CAMERA_GROUP: + Q3_CameraGroup(entID, (char *)data); + break; + case SET_CAMERA_GROUP_Z_OFS: + float_data = atof((char *) data); + Q3_CameraGroupZOfs( float_data ); + break; + case SET_CAMERA_GROUP_TAG: + Q3_CameraGroupTag( (char *)data ); + break; + + //FIXME: put these into camera commands + case SET_LOOK_TARGET: + Q3_LookTarget(entID, (char *)data); + break; + + case SET_ADDRHANDBOLT_MODEL: + Q3_AddRHandModel(entID, (char *)data); + break; + + case SET_REMOVERHANDBOLT_MODEL: + Q3_RemoveRHandModel(entID, (char *)data); + break; + + case SET_ADDLHANDBOLT_MODEL: + Q3_AddLHandModel(entID, (char *)data); + break; + + case SET_REMOVELHANDBOLT_MODEL: + Q3_RemoveLHandModel(entID, (char *)data); + break; + + case SET_FACEEYESCLOSED: + case SET_FACEEYESOPENED: + case SET_FACEAUX: + case SET_FACEBLINK: + case SET_FACEBLINKFROWN: + case SET_FACEFROWN: + case SET_FACENORMAL: + float_data = atof((char *) data); + Q3_Face(entID, toSet, float_data); + break; + + case SET_SCROLLTEXT: + Q3_ScrollText( (char *)data ); + break; + + case SET_LCARSTEXT: + Q3_LCARSText( (char *)data ); + break; + + case SET_CAPTIONTEXTCOLOR: + Q3_SetCaptionTextColor ( (char *)data ); + break; + case SET_CENTERTEXTCOLOR: + Q3_SetCenterTextColor ( (char *)data ); + break; + case SET_SCROLLTEXTCOLOR: + Q3_SetScrollTextColor ( (char *)data ); + break; + + case SET_PLAYER_USABLE: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetPlayerUsable(entID, qtrue); + } + else + { + Q3_SetPlayerUsable(entID, qfalse); + } + break; + + case SET_STARTFRAME: + int_data = atoi((char *) data); + Q3_SetStartFrame(entID, int_data); + break; + + case SET_ENDFRAME: + int_data = atoi((char *) data); + Q3_SetEndFrame(entID, int_data); + + trap_ICARUS_TaskIDSet( ent, TID_ANIM_BOTH, taskID ); + return qfalse; + break; + + case SET_ANIMFRAME: + int_data = atoi((char *) data); + Q3_SetAnimFrame(entID, int_data); + return qfalse; + break; + + case SET_LOOP_ANIM: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetLoopAnim(entID, qtrue); + } + else + { + Q3_SetLoopAnim(entID, qfalse); + } + break; + + case SET_INTERFACE: + G_DebugPrint( WL_WARNING, "Q3_SetInterface: NOT SUPPORTED IN MP\n"); + + break; + + case SET_SHIELDS: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetShields(entID, qtrue); + } + else + { + Q3_SetShields(entID, qfalse); + } + break; + + case SET_SABERACTIVE: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetSaberActive( entID, qtrue ); + } + else + { + Q3_SetSaberActive( entID, qfalse ); + } + break; + + case SET_ADJUST_AREA_PORTALS: + G_DebugPrint( WL_WARNING, "Q3_SetAdjustAreaPortals: NOT SUPPORTED IN MP\n"); + break; + + case SET_DMG_BY_HEAVY_WEAP_ONLY: + G_DebugPrint( WL_WARNING, "Q3_SetDmgByHeavyWeapOnly: NOT SUPPORTED IN MP\n"); + break; + + case SET_SHIELDED: + G_DebugPrint( WL_WARNING, "Q3_SetShielded: NOT SUPPORTED IN MP\n"); + break; + + case SET_NO_GROUPS: + G_DebugPrint( WL_WARNING, "Q3_SetNoGroups: NOT SUPPORTED IN MP\n"); + break; + + case SET_FIRE_WEAPON: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetFireWeapon( entID, qtrue); + } + else if(!Q_stricmp("false", ((char *)data))) + { + Q3_SetFireWeapon( entID, qfalse); + } + break; + + case SET_INACTIVE: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetInactive( entID, qtrue); + } + else if(!Q_stricmp("false", ((char *)data))) + { + Q3_SetInactive( entID, qfalse); + } + else if(!Q_stricmp("unlocked", ((char *)data))) + { + UnLockDoors(&g_entities[entID]); + } + else if(!Q_stricmp("locked", ((char *)data))) + { + LockDoors(&g_entities[entID]); + } + break; + case SET_END_SCREENDISSOLVE: + G_DebugPrint( WL_WARNING, "SET_END_SCREENDISSOLVE: NOT SUPPORTED IN MP\n"); + break; + + case SET_MISSION_STATUS_SCREEN: + //Cvar_Set("cg_missionstatusscreen", "1"); + G_DebugPrint( WL_WARNING, "SET_MISSION_STATUS_SCREEN: NOT SUPPORTED IN MP\n"); + break; + + case SET_FUNC_USABLE_VISIBLE: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetFuncUsableVisible( entID, qtrue); + } + else if(!Q_stricmp("false", ((char *)data))) + { + Q3_SetFuncUsableVisible( entID, qfalse); + } + break; + + case SET_NO_KNOCKBACK: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetNoKnockback(entID, qtrue); + } + else + { + Q3_SetNoKnockback(entID, qfalse); + } + break; + + case SET_VIDEO_PLAY: + // don't do this check now, James doesn't want a scripted cinematic to also skip any Video cinematics as well, + // the "timescale" and "skippingCinematic" cvars will be set back to normal in the Video code, so doing a + // skip will now only skip one section of a multiple-part story (eg VOY1 bridge sequence) + // +// if ( g_timescale->value <= 1.0f ) + { + G_DebugPrint( WL_WARNING, "SET_VIDEO_PLAY: NOT SUPPORTED IN MP\n"); + //SV_SendConsoleCommand( va("inGameCinematic %s\n", (char *)data) ); + } + break; + + case SET_VIDEO_FADE_IN: + G_DebugPrint( WL_WARNING, "SET_VIDEO_FADE_IN: NOT SUPPORTED IN MP\n"); + break; + + case SET_VIDEO_FADE_OUT: + G_DebugPrint( WL_WARNING, "SET_VIDEO_FADE_OUT: NOT SUPPORTED IN MP\n"); + break; + case SET_REMOVE_TARGET: + Q3_SetRemoveTarget( entID, (const char *) data ); + break; + + case SET_LOADGAME: + //gi.SendConsoleCommand( va("load %s\n", (const char *) data ) ); + G_DebugPrint( WL_WARNING, "SET_LOADGAME: NOT SUPPORTED IN MP\n"); + break; + + case SET_MENU_SCREEN: + //UI_SetActiveMenu( (const char *) data ); + break; + + case SET_OBJECTIVE_SHOW: + G_DebugPrint( WL_WARNING, "SET_OBJECTIVE_SHOW: NOT SUPPORTED IN MP\n"); + break; + case SET_OBJECTIVE_HIDE: + G_DebugPrint( WL_WARNING, "SET_OBJECTIVE_HIDE: NOT SUPPORTED IN MP\n"); + break; + case SET_OBJECTIVE_SUCCEEDED: + G_DebugPrint( WL_WARNING, "SET_OBJECTIVE_SUCCEEDED: NOT SUPPORTED IN MP\n"); + break; + case SET_OBJECTIVE_FAILED: + G_DebugPrint( WL_WARNING, "SET_OBJECTIVE_FAILED: NOT SUPPORTED IN MP\n"); + break; + + case SET_OBJECTIVE_CLEARALL: + G_DebugPrint( WL_WARNING, "SET_OBJECTIVE_CLEARALL: NOT SUPPORTED IN MP\n"); + break; + + case SET_MISSIONFAILED: + G_DebugPrint( WL_WARNING, "SET_MISSIONFAILED: NOT SUPPORTED IN MP\n"); + break; + + case SET_MISSIONSTATUSTEXT: + G_DebugPrint( WL_WARNING, "SET_MISSIONSTATUSTEXT: NOT SUPPORTED IN MP\n"); + break; + + case SET_MISSIONSTATUSTIME: + G_DebugPrint( WL_WARNING, "SET_MISSIONSTATUSTIME: NOT SUPPORTED IN MP\n"); + break; + + case SET_CLOSINGCREDITS: + G_DebugPrint( WL_WARNING, "SET_CLOSINGCREDITS: NOT SUPPORTED IN MP\n"); + break; + + case SET_SKILL: +// //can never be set + break; + + case SET_FULLNAME: + Q3_SetFullName( entID, (char *) data ); + break; + + case SET_DISABLE_SHADER_ANIM: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetDisableShaderAnims( entID, qtrue); + } + else + { + Q3_SetDisableShaderAnims( entID, qfalse); + } + break; + + case SET_SHADER_ANIM: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetShaderAnim( entID, qtrue); + } + else + { + Q3_SetShaderAnim( entID, qfalse); + } + break; + + case SET_MUSIC_STATE: + Q3_SetMusicState( (char *) data ); + break; + + case SET_CLEAN_DAMAGING_ENTS: + Q3_SetCleanDamagingEnts(); + break; + + case SET_HUD: + G_DebugPrint( WL_WARNING, "SET_HUD: NOT SUPPORTED IN MP\n"); + break; + + case SET_FORCE_HEAL_LEVEL: + case SET_FORCE_JUMP_LEVEL: + case SET_FORCE_SPEED_LEVEL: + case SET_FORCE_PUSH_LEVEL: + case SET_FORCE_PULL_LEVEL: + case SET_FORCE_MINDTRICK_LEVEL: + case SET_FORCE_GRIP_LEVEL: + case SET_FORCE_LIGHTNING_LEVEL: + case SET_SABER_THROW: + case SET_SABER_DEFENSE: + case SET_SABER_OFFENSE: + int_data = atoi((char *) data); + Q3_SetForcePowerLevel( entID, (toSet-SET_FORCE_HEAL_LEVEL), int_data ); + break; + + default: + //G_DebugPrint( WL_ERROR, "Q3_Set: '%s' is not a valid set field\n", type_name ); + trap_ICARUS_SetVar( taskID, entID, type_name, data ); + break; + } + + return qtrue; +} diff --git a/codemp/game/g_ICARUScb.h b/codemp/game/g_ICARUScb.h new file mode 100644 index 0000000..f80b3e2 --- /dev/null +++ b/codemp/game/g_ICARUScb.h @@ -0,0 +1,15 @@ +int Q3_PlaySound( int taskID, int entID, const char *name, const char *channel ); +qboolean Q3_Set( int taskID, int entID, const char *type_name, const char *data ); +void Q3_Lerp2Pos( int taskID, int entID, vec3_t origin, vec3_t angles, float duration ); +void Q3_Lerp2Origin( int taskID, int entID, vec3_t origin, float duration ); +void Q3_Lerp2Angles( int taskID, int entID, vec3_t angles, float duration ); +int Q3_GetTag( int entID, const char *name, int lookup, vec3_t info ); +void Q3_Lerp2Start( int entID, int taskID, float duration ); +void Q3_Lerp2End( int entID, int taskID, float duration ); +void Q3_Use( int entID, const char *target ); +void Q3_Kill( int entID, const char *name ); +void Q3_Remove( int entID, const char *name ); +void Q3_Play( int taskID, int entID, const char *type, const char *name ); +int Q3_GetFloat( int entID, int type, const char *name, float *value ); +int Q3_GetVector( int entID, int type, const char *name, vec3_t value ); +int Q3_GetString( int entID, int type, const char *name, char **value ); diff --git a/codemp/game/g_active.c b/codemp/game/g_active.c new file mode 100644 index 0000000..7e62cdb --- /dev/null +++ b/codemp/game/g_active.c @@ -0,0 +1,3781 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +#include "g_local.h" +#include "bg_saga.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +extern void Jedi_Cloak( gentity_t *self ); +extern void Jedi_Decloak( gentity_t *self ); + +#include "../namespace_begin.h" +qboolean PM_SaberInTransition( int move ); +qboolean PM_SaberInStart( int move ); +qboolean PM_SaberInReturn( int move ); +#include "../namespace_end.h" +qboolean saberCheckKnockdown_DuelLoss(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other); + +extern vmCvar_t g_saberLockRandomNess; + +void P_SetTwitchInfo(gclient_t *client) +{ + client->ps.painTime = level.time; + client->ps.painDirection ^= 1; +} + +/* +=============== +G_DamageFeedback + +Called just before a snapshot is sent to the given player. +Totals up all damage and generates both the player_state_t +damage values to that client for pain blends and kicks, and +global pain sound events for all clients. +=============== +*/ +void P_DamageFeedback( gentity_t *player ) { + gclient_t *client; + float count; + vec3_t angles; + + client = player->client; + if ( client->ps.pm_type == PM_DEAD ) { + return; + } + + // total points of damage shot at the player this frame + count = client->damage_blood + client->damage_armor; + if ( count == 0 ) { + return; // didn't take any damage + } + + if ( count > 255 ) { + count = 255; + } + + // send the information to the client + + // world damage (falling, slime, etc) uses a special code + // to make the blend blob centered instead of positional + if ( client->damage_fromWorld ) { + client->ps.damagePitch = 255; + client->ps.damageYaw = 255; + + client->damage_fromWorld = qfalse; + } else { + vectoangles( client->damage_from, angles ); + client->ps.damagePitch = angles[PITCH]/360.0 * 256; + client->ps.damageYaw = angles[YAW]/360.0 * 256; + + //cap them since we can't send negative values in here across the net + if (client->ps.damagePitch < 0) + { + client->ps.damagePitch = 0; + } + if (client->ps.damageYaw < 0) + { + client->ps.damageYaw = 0; + } + } + + // play an apropriate pain sound + if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && !(player->s.eFlags & EF_DEAD) ) { + + // don't do more than two pain sounds a second + // nmckenzie: also don't make him loud and whiny if he's only getting nicked. + if ( level.time - client->ps.painTime < 500 || count < 10) { + return; + } + P_SetTwitchInfo(client); + player->pain_debounce_time = level.time + 700; + + G_AddEvent( player, EV_PAIN, player->health ); + client->ps.damageEvent++; + + if (client->damage_armor && !client->damage_blood) + { + client->ps.damageType = 1; //pure shields + } + else if (client->damage_armor) + { + client->ps.damageType = 2; //shields and health + } + else + { + client->ps.damageType = 0; //pure health + } + } + + + client->ps.damageCount = count; + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_knockback = 0; +} + + + +/* +============= +P_WorldEffects + +Check for lava / slime contents and drowning +============= +*/ +void P_WorldEffects( gentity_t *ent ) { + qboolean envirosuit; + int waterlevel; + + if ( ent->client->noclip ) { + ent->client->airOutTime = level.time + 12000; // don't need air + return; + } + + waterlevel = ent->waterlevel; + + envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; + + // + // check for drowning + // + if ( waterlevel == 3 ) { + // envirosuit give air + if ( envirosuit ) { + ent->client->airOutTime = level.time + 10000; + } + + // if out of air, start drowning + if ( ent->client->airOutTime < level.time) { + // drown! + ent->client->airOutTime += 1000; + if ( ent->health > 0 ) { + // take more damage the longer underwater + ent->damage += 2; + if (ent->damage > 15) + ent->damage = 15; + + // play a gurp sound instead of a normal pain sound + if (ent->health <= ent->damage) { + G_Sound(ent, CHAN_VOICE, G_SoundIndex(/*"*drown.wav"*/"sound/player/gurp1.wav")); + } else if (rand()&1) { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav")); + } else { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav")); + } + + // don't play a normal pain sound + ent->pain_debounce_time = level.time + 200; + + G_Damage (ent, NULL, NULL, NULL, NULL, + ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } else { + ent->client->airOutTime = level.time + 12000; + ent->damage = 2; + } + + // + // check for sizzle damage (move to pmove?) + // + if (waterlevel && + (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { + if (ent->health > 0 + && ent->pain_debounce_time <= level.time ) { + + if ( envirosuit ) { + G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); + } else { + if (ent->watertype & CONTENTS_LAVA) { + G_Damage (ent, NULL, NULL, NULL, NULL, + 30*waterlevel, 0, MOD_LAVA); + } + + if (ent->watertype & CONTENTS_SLIME) { + G_Damage (ent, NULL, NULL, NULL, NULL, + 10*waterlevel, 0, MOD_SLIME); + } + } + } + } +} + + + + + +//============================================================== +extern void G_ApplyKnockback( gentity_t *targ, vec3_t newDir, float knockback ); +void DoImpact( gentity_t *self, gentity_t *other, qboolean damageSelf ) +{ + float magnitude, my_mass; + vec3_t velocity; + int cont; + qboolean easyBreakBrush = qtrue; + + if( self->client ) + { + VectorCopy( self->client->ps.velocity, velocity ); + if( !self->mass ) + { + my_mass = 10; + } + else + { + my_mass = self->mass; + } + } + else + { + VectorCopy( self->s.pos.trDelta, velocity ); + if ( self->s.pos.trType == TR_GRAVITY ) + { + velocity[2] -= 0.25f * g_gravity.value; + } + if( !self->mass ) + { + my_mass = 1; + } + else if ( self->mass <= 10 ) + { + my_mass = 10; + } + else + { + my_mass = self->mass;///10; + } + } + + magnitude = VectorLength( velocity ) * my_mass / 10; + + /* + if(pointcontents(self.absmax)==CONTENT_WATER)//FIXME: or other watertypes + magnitude/=3; //water absorbs 2/3 velocity + + if(self.classname=="barrel"&&self.aflag)//rolling barrels are made for impacts! + magnitude*=3; + + if(self.frozen>0&&magnitude<300&&self.flags&FL_ONGROUND&&loser==world&&self.velocity_z<-20&&self.last_onground+0.3material == MAT_GLASS + || other->material == MAT_GLASS_METAL + || other->material == MAT_GRATE1 + || ((other->flags&FL_BBRUSH)&&(other->spawnflags&8/*THIN*/)) + || (other->r.svFlags&SVF_GLASS_BRUSH) ) + { + easyBreakBrush = qtrue; + } + + if ( !self->client || self->client->ps.lastOnGround+300client->ps.lastOnGround+100 < level.time && easyBreakBrush ) ) + { + vec3_t dir1, dir2; + float force = 0, dot; + + if ( easyBreakBrush ) + magnitude *= 2; + + //damage them + if ( magnitude >= 100 && other->s.number < ENTITYNUM_WORLD ) + { + VectorCopy( velocity, dir1 ); + VectorNormalize( dir1 ); + if( VectorCompare( other->r.currentOrigin, vec3_origin ) ) + {//a brush with no origin + VectorCopy ( dir1, dir2 ); + } + else + { + VectorSubtract( other->r.currentOrigin, self->r.currentOrigin, dir2 ); + VectorNormalize( dir2 ); + } + + dot = DotProduct( dir1, dir2 ); + + if ( dot >= 0.2 ) + { + force = dot; + } + else + { + force = 0; + } + + force *= (magnitude/50); + + cont = trap_PointContents( other->r.absmax, other->s.number ); + if( (cont&CONTENTS_WATER) )//|| (self.classname=="barrel"&&self.aflag))//FIXME: or other watertypes + { + force /= 3; //water absorbs 2/3 velocity + } + + /* + if(self.frozen>0&&force>10) + force=10; + */ + + if( ( force >= 1 && other->s.number != 0 ) || force >= 10) + { + /* + dprint("Damage other ("); + dprint(loser.classname); + dprint("): "); + dprint(ftos(force)); + dprint("\n"); + */ + if ( other->r.svFlags & SVF_GLASS_BRUSH ) + { + other->splashRadius = (float)(self->r.maxs[0] - self->r.mins[0])/4.0f; + } + if ( other->takedamage ) + { + G_Damage( other, self, self, velocity, self->r.currentOrigin, force, DAMAGE_NO_ARMOR, MOD_CRUSH);//FIXME: MOD_IMPACT + } + else + { + G_ApplyKnockback( other, dir2, force ); + } + } + } + + if ( damageSelf && self->takedamage ) + { + //Now damage me + //FIXME: more lenient falling damage, especially for when driving a vehicle + if ( self->client && self->client->ps.fd.forceJumpZStart ) + {//we were force-jumping + if ( self->r.currentOrigin[2] >= self->client->ps.fd.forceJumpZStart ) + {//we landed at same height or higher than we landed + magnitude = 0; + } + else + {//FIXME: take off some of it, at least? + magnitude = (self->client->ps.fd.forceJumpZStart-self->r.currentOrigin[2])/3; + } + } + //if(self.classname!="monster_mezzoman"&&self.netname!="spider")//Cats always land on their feet + if( ( magnitude >= 100 + self->health && self->s.number != 0 && self->s.weapon != WP_SABER ) || ( magnitude >= 700 ) )//&& self.safe_time < level.time ))//health here is used to simulate structural integrity + { + if ( (self->s.weapon == WP_SABER || self->s.number == 0) && self->client && self->client->ps.groundEntityNum < ENTITYNUM_NONE && magnitude < 1000 ) + {//players and jedi take less impact damage + //allow for some lenience on high falls + magnitude /= 2; + /* + if ( self.absorb_time >= time )//crouching on impact absorbs 1/2 the damage + { + magnitude/=2; + } + */ + } + magnitude /= 40; + magnitude = magnitude - force/2;//If damage other, subtract half of that damage off of own injury + if ( magnitude >= 1 ) + { + //FIXME: Put in a thingtype impact sound function + /* + dprint("Damage self ("); + dprint(self.classname); + dprint("): "); + dprint(ftos(magnitude)); + dprint("\n"); + */ + /* + if ( self.classname=="player_sheep "&& self.flags&FL_ONGROUND && self.velocity_z > -50 ) + return; + */ + G_Damage( self, NULL, NULL, NULL, self->r.currentOrigin, magnitude/2, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT + } + } + } + + //FIXME: slow my velocity some? + + // NOTENOTE We don't use lastimpact as of yet +// self->lastImpact = level.time; + + /* + if(self.flags&FL_ONGROUND) + self.last_onground=time; + */ + } +} + +void Client_CheckImpactBBrush( gentity_t *self, gentity_t *other ) +{ + if ( !other || !other->inuse ) + { + return; + } + if (!self || !self->inuse || !self->client || + self->client->tempSpectate >= level.time || + self->client->sess.sessionTeam == TEAM_SPECTATOR) + { //hmm.. let's not let spectators ram into breakables. + return; + } + + /* + if (BG_InSpecialJump(self->client->ps.legsAnim)) + { //don't do this either, qa says it creates "balance issues" + return; + } + */ + + if ( other->material == MAT_GLASS + || other->material == MAT_GLASS_METAL + || other->material == MAT_GRATE1 + || ((other->flags&FL_BBRUSH)&&(other->spawnflags&8/*THIN*/)) + || ((other->flags&FL_BBRUSH)&&(other->health<=10)) + || (other->r.svFlags&SVF_GLASS_BRUSH) ) + {//clients only do impact damage against easy-break breakables + DoImpact( self, other, qfalse ); + } +} + + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) { + if (ent->client && ent->client->isHacking) + { //loop hacking sound + ent->client->ps.loopSound = level.snd_hack; + ent->s.loopIsSoundset = qfalse; + } + else if (ent->client && ent->client->isMedHealed > level.time) + { //loop healing sound + ent->client->ps.loopSound = level.snd_medHealed; + ent->s.loopIsSoundset = qfalse; + } + else if (ent->client && ent->client->isMedSupplied > level.time) + { //loop supplying sound + ent->client->ps.loopSound = level.snd_medSupplied; + ent->s.loopIsSoundset = qfalse; + } + else if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { + ent->client->ps.loopSound = level.snd_fry; + ent->s.loopIsSoundset = qfalse; + } else { + ent->client->ps.loopSound = 0; + ent->s.loopIsSoundset = qfalse; + } +} + + + +//============================================================== + +/* +============== +ClientImpacts +============== +*/ +void ClientImpacts( gentity_t *ent, pmove_t *pm ) { + int i, j; + trace_t trace; + gentity_t *other; + + memset( &trace, 0, sizeof( trace ) ); + for (i=0 ; inumtouch ; i++) { + for (j=0 ; jtouchents[j] == pm->touchents[i] ) { + break; + } + } + if (j != i) { + continue; // duplicated + } + other = &g_entities[ pm->touchents[i] ]; + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, other, &trace ); + } + + if ( !other->touch ) { + continue; + } + + other->touch( other, ent, &trace ); + } + +} + +/* +============ +G_TouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_TouchTriggers( gentity_t *ent ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs; + static vec3_t range = { 40, 40, 52 }; + + if ( !ent->client ) { + return; + } + + // dead clients don't activate triggers! + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { + return; + } + + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->r.absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + for ( i=0 ; itouch && !ent->touch ) { + continue; + } + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { + continue; + } + + // ignore most entities if a spectator + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( hit->s.eType != ET_TELEPORT_TRIGGER && + // this is ugly but adding a new ET_? type will + // most likely cause network incompatibilities + hit->touch != Touch_DoorTrigger) { + continue; + } + } + + // use seperate code for determining if an item is picked up + // so you don't have to actually contact its bounding box + if ( hit->s.eType == ET_ITEM ) { + if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { + continue; + } + } else { + if ( !trap_EntityContact( mins, maxs, hit ) ) { + continue; + } + } + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->touch ) { + hit->touch (hit, ent, &trace); + } + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, hit, &trace ); + } + } + + // if we didn't touch a jump pad this pmove frame + if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) { + ent->client->ps.jumppad_frame = 0; + ent->client->ps.jumppad_ent = 0; + } +} + + +/* +============ +G_MoverTouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg ) +{ + int i, num; + float step, stepSize, dist; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs, dir, size, checkSpot; + const vec3_t range = { 40, 40, 52 }; + + // non-moving movers don't hit triggers! + if ( !VectorLengthSquared( ent->s.pos.trDelta ) ) + { + return; + } + + VectorSubtract( ent->r.mins, ent->r.maxs, size ); + stepSize = VectorLength( size ); + if ( stepSize < 1 ) + { + stepSize = 1; + } + + VectorSubtract( ent->r.currentOrigin, oldOrg, dir ); + dist = VectorNormalize( dir ); + for ( step = 0; step <= dist; step += stepSize ) + { + VectorMA( ent->r.currentOrigin, step, dir, checkSpot ); + VectorSubtract( checkSpot, range, mins ); + VectorAdd( checkSpot, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->r.absmin, because that has a one unit pad + VectorAdd( checkSpot, ent->r.mins, mins ); + VectorAdd( checkSpot, ent->r.maxs, maxs ); + + for ( i=0 ; is.eType != ET_PUSH_TRIGGER ) + { + continue; + } + + if ( hit->touch == NULL ) + { + continue; + } + + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) + { + continue; + } + + + if ( !trap_EntityContact( mins, maxs, hit ) ) + { + continue; + } + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->touch != NULL ) + { + hit->touch(hit, ent, &trace); + } + } + } +} + +/* +================= +SpectatorThink +================= +*/ +void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { + pmove_t pm; + gclient_t *client; + + client = ent->client; + + if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { + client->ps.pm_type = PM_SPECTATOR; + client->ps.speed = 400; // faster than normal + client->ps.basespeed = 400; + + //hmm, shouldn't have an anim if you're a spectator, make sure + //it gets cleared. + client->ps.legsAnim = 0; + client->ps.legsTimer = 0; + client->ps.torsoAnim = 0; + client->ps.torsoTimer = 0; + + // set up for pmove + memset (&pm, 0, sizeof(pm)); + pm.ps = &client->ps; + pm.cmd = *ucmd; + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + + pm.noSpecMove = g_noSpecMove.integer; + + pm.animations = NULL; + pm.nonHumanoid = qfalse; + + //Set up bg entity data + pm.baseEnt = (bgEntity_t *)g_entities; + pm.entSize = sizeof(gentity_t); + + // perform a pmove + Pmove (&pm); + // save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + if (ent->client->tempSpectate < level.time) + { + G_TouchTriggers( ent ); + } + trap_UnlinkEntity( ent ); + } + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + + if (client->tempSpectate < level.time) + { + // attack button cycles through spectators + if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { + Cmd_FollowCycle_f( ent, 1 ); + } + + if (client->sess.spectatorState == SPECTATOR_FOLLOW && (ucmd->upmove > 0)) + { //jump now removes you from follow mode + StopFollowing(ent); + } + } +} + + + +/* +================= +ClientInactivityTimer + +Returns qfalse if the client is dropped +================= +*/ +qboolean ClientInactivityTimer( gclient_t *client ) { + if ( ! g_inactivity.integer ) { + // give everyone some time, so if the operator sets g_inactivity during + // gameplay, everyone isn't kicked + client->inactivityTime = level.time + 60 * 1000; + client->inactivityWarning = qfalse; + } else if ( client->pers.cmd.forwardmove || + client->pers.cmd.rightmove || + client->pers.cmd.upmove || + (client->pers.cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) { + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->inactivityWarning = qfalse; + } else if ( !client->pers.localClient ) { + if ( level.time > client->inactivityTime ) { + trap_DropClient( client - level.clients, "Dropped due to inactivity" ); + return qfalse; + } + if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { + client->inactivityWarning = qtrue; + trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); + } + } + return qtrue; +} + +/* +================== +ClientTimerActions + +Actions that happen once a second +================== +*/ +void ClientTimerActions( gentity_t *ent, int msec ) { + gclient_t *client; + + client = ent->client; + client->timeResidual += msec; + + while ( client->timeResidual >= 1000 ) + { + client->timeResidual -= 1000; + + // count down health when over max + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { + ent->health--; + } + + // count down armor when over max + if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { + client->ps.stats[STAT_ARMOR]--; + } + } +} + +/* +==================== +ClientIntermissionThink +==================== +*/ +void ClientIntermissionThink( gclient_t *client ) { + client->ps.eFlags &= ~EF_TALK; + client->ps.eFlags &= ~EF_FIRING; + + // the level will exit when everyone wants to or after timeouts + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = client->pers.cmd.buttons; + if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) { + // this used to be an ^1 but once a player says ready, it should stick + client->readyToExit = 1; + } +} + +extern void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags); +void G_VehicleAttachDroidUnit( gentity_t *vehEnt ) +{ + if ( vehEnt && vehEnt->m_pVehicle && vehEnt->m_pVehicle->m_pDroidUnit != NULL ) + { + gentity_t *droidEnt = (gentity_t *)vehEnt->m_pVehicle->m_pDroidUnit; + mdxaBone_t boltMatrix; + vec3_t fwd; + + trap_G2API_GetBoltMatrix(vehEnt->ghoul2, 0, vehEnt->m_pVehicle->m_iDroidUnitTag, &boltMatrix, vehEnt->r.currentAngles, vehEnt->r.currentOrigin, level.time, + NULL, vehEnt->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, droidEnt->r.currentOrigin); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, fwd); + vectoangles( fwd, droidEnt->r.currentAngles ); + + if ( droidEnt->client ) + { + VectorCopy( droidEnt->r.currentAngles, droidEnt->client->ps.viewangles ); + VectorCopy( droidEnt->r.currentOrigin, droidEnt->client->ps.origin ); + } + + G_SetOrigin( droidEnt, droidEnt->r.currentOrigin ); + trap_LinkEntity( droidEnt ); + + if ( droidEnt->NPC ) + { + NPC_SetAnim( droidEnt, SETANIM_BOTH, BOTH_STAND2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } +} + +//called gameside only from pmove code (convenience) +void G_CheapWeaponFire(int entNum, int ev) +{ + gentity_t *ent = &g_entities[entNum]; + + if (!ent->inuse || !ent->client) + { + return; + } + + switch (ev) + { + case EV_FIRE_WEAPON: + if (ent->m_pVehicle && ent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER && + ent->client && ent->client->ps.m_iVehicleNum) + { //a speeder with a pilot + gentity_t *rider = &g_entities[ent->client->ps.m_iVehicleNum-1]; + if (rider->inuse && rider->client) + { //pilot is valid... + if (rider->client->ps.weapon != WP_MELEE && + (rider->client->ps.weapon != WP_SABER || !rider->client->ps.saberHolstered)) + { //can only attack on speeder when using melee or when saber is holstered + break; + } + } + } + + FireWeapon( ent, qfalse ); + ent->client->dangerTime = level.time; + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + ent->client->invulnerableTimer = 0; + break; + case EV_ALT_FIRE: + FireWeapon( ent, qtrue ); + ent->client->dangerTime = level.time; + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + ent->client->invulnerableTimer = 0; + break; + } +} + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +#include "../namespace_begin.h" +qboolean BG_InKnockDownOnly( int anim ); +#include "../namespace_end.h" + +void ClientEvents( gentity_t *ent, int oldEventSequence ) { + int i;//, j; + int event; + gclient_t *client; + int damage; + vec3_t dir; +// vec3_t origin, angles; +// qboolean fired; +// gitem_t *item; +// gentity_t *drop; + + client = ent->client; + + if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) { + oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; + } + for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { + event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; + + switch ( event ) { + case EV_FALL: + case EV_ROLL: + { + int delta = client->ps.eventParms[ i & (MAX_PS_EVENTS-1) ]; + qboolean knockDownage = qfalse; + + if (ent->client && ent->client->ps.fallingToDeath) + { + break; + } + + if ( ent->s.eType != ET_PLAYER ) + { + break; // not in the player model + } + + if ( g_dmflags.integer & DF_NO_FALLING ) + { + break; + } + + if (BG_InKnockDownOnly(ent->client->ps.legsAnim)) + { + if (delta <= 14) + { + break; + } + knockDownage = qtrue; + } + else + { + if (delta <= 44) + { + break; + } + } + + if (knockDownage) + { + damage = delta*1; //you suffer for falling unprepared. A lot. Makes throws and things useful, and more realistic I suppose. + } + else + { + if (g_gametype.integer == GT_SIEGE && + delta > 60) + { //longer falls hurt more + damage = delta*1; //good enough for now, I guess + } + else + { + damage = delta*0.16; //good enough for now, I guess + } + } + + VectorSet (dir, 0, 0, 1); + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage (ent, NULL, NULL, NULL, NULL, damage, DAMAGE_NO_ARMOR, MOD_FALLING); + + if (ent->health < 1) + { + G_Sound(ent, CHAN_AUTO, G_SoundIndex( "sound/player/fallsplat.wav" )); + } + } + break; + case EV_FIRE_WEAPON: + FireWeapon( ent, qfalse ); + ent->client->dangerTime = level.time; + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + ent->client->invulnerableTimer = 0; + break; + + case EV_ALT_FIRE: + FireWeapon( ent, qtrue ); + ent->client->dangerTime = level.time; + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + ent->client->invulnerableTimer = 0; + break; + + case EV_SABER_ATTACK: + ent->client->dangerTime = level.time; + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + ent->client->invulnerableTimer = 0; + break; + + //rww - Note that these must be in the same order (ITEM#-wise) as they are in holdable_t + case EV_USE_ITEM1: //seeker droid + ItemUse_Seeker(ent); + break; + case EV_USE_ITEM2: //shield + ItemUse_Shield(ent); + break; + case EV_USE_ITEM3: //medpack + ItemUse_MedPack(ent); + break; + case EV_USE_ITEM4: //big medpack + ItemUse_MedPack_Big(ent); + break; + case EV_USE_ITEM5: //binoculars + ItemUse_Binoculars(ent); + break; + case EV_USE_ITEM6: //sentry gun + ItemUse_Sentry(ent); + break; + case EV_USE_ITEM7: //jetpack + ItemUse_Jetpack(ent); + break; + case EV_USE_ITEM8: //health disp + //ItemUse_UseDisp(ent, HI_HEALTHDISP); + break; + case EV_USE_ITEM9: //ammo disp + //ItemUse_UseDisp(ent, HI_AMMODISP); + break; + case EV_USE_ITEM10: //eweb + ItemUse_UseEWeb(ent); + break; + case EV_USE_ITEM11: //cloak + ItemUse_UseCloak(ent); + break; + default: + break; + } + } + +} + +/* +============== +SendPendingPredictableEvents +============== +*/ +void SendPendingPredictableEvents( playerState_t *ps ) { + gentity_t *t; + int event, seq; + int extEvent, number; + + // if there are still events pending + if ( ps->entityEventSequence < ps->eventSequence ) { + // create a temporary entity for this event which is sent to everyone + // except the client who generated the event + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + // set external event to zero before calling BG_PlayerStateToEntityState + extEvent = ps->externalEvent; + ps->externalEvent = 0; + // create temporary entity for event + t = G_TempEntity( ps->origin, event ); + number = t->s.number; + BG_PlayerStateToEntityState( ps, &t->s, qtrue ); + t->s.number = number; + t->s.eType = ET_EVENTS + event; + t->s.eFlags |= EF_PLAYER_EVENT; + t->s.otherEntityNum = ps->clientNum; + // send to everyone except the client who generated the event + t->r.svFlags |= SVF_NOTSINGLECLIENT; + t->r.singleClient = ps->clientNum; + // set back external event + ps->externalEvent = extEvent; + } +} + +/* +================== +G_UpdateClientBroadcasts + +Determines whether this client should be broadcast to any other clients. +A client is broadcast when another client is using force sight or is +================== +*/ +#define MAX_JEDIMASTER_DISTANCE 2500 +#define MAX_JEDIMASTER_FOV 100 + +#define MAX_SIGHT_DISTANCE 1500 +#define MAX_SIGHT_FOV 100 + +static void G_UpdateForceSightBroadcasts ( gentity_t *self ) +{ + int i; + + // Any clients with force sight on should see this client + for ( i = 0; i < level.numConnectedClients; i ++ ) + { + gentity_t *ent = &g_entities[level.sortedClients[i]]; + float dist; + vec3_t angles; + + if ( ent == self ) + { + continue; + } + + // Not using force sight so we shouldnt broadcast to this one + if ( !(ent->client->ps.fd.forcePowersActive & (1<client->ps.origin, ent->client->ps.origin, angles ); + dist = VectorLengthSquared ( angles ); + vectoangles ( angles, angles ); + + // Too far away then just forget it + if ( dist > MAX_SIGHT_DISTANCE * MAX_SIGHT_DISTANCE ) + { + continue; + } + + // If not within the field of view then forget it + if ( !InFieldOfVision ( ent->client->ps.viewangles, MAX_SIGHT_FOV, angles ) ) + { + break; + } + + // Turn on the broadcast bit for the master and since there is only one + // master we are done + self->r.broadcastClients[ent->s.clientNum/32] |= (1 << (ent->s.clientNum%32)); + + break; + } +} + +static void G_UpdateJediMasterBroadcasts ( gentity_t *self ) +{ + int i; + + // Not jedi master mode then nothing to do + if ( g_gametype.integer != GT_JEDIMASTER ) + { + return; + } +/* + // This client isnt the jedi master so it shouldnt broadcast + if ( !self->client->ps.isJediMaster ) + { + return; + } + + // Broadcast ourself to all clients within range + for ( i = 0; i < level.numConnectedClients; i ++ ) + { + gentity_t *ent = &g_entities[level.sortedClients[i]]; + float dist; + vec3_t angles; + + if ( ent == self ) + { + continue; + } + + VectorSubtract( self->client->ps.origin, ent->client->ps.origin, angles ); + dist = VectorLengthSquared ( angles ); + vectoangles ( angles, angles ); + + // Too far away then just forget it + if ( dist > MAX_JEDIMASTER_DISTANCE * MAX_JEDIMASTER_DISTANCE ) + { + continue; + } + + // If not within the field of view then forget it + if ( !InFieldOfVision ( ent->client->ps.viewangles, MAX_JEDIMASTER_FOV, angles ) ) + { + continue; + } + + // Turn on the broadcast bit for the master and since there is only one + // master we are done + self->r.broadcastClients[ent->s.clientNum/32] |= (1 << (ent->s.clientNum%32)); + } +*/ +} + +void G_UpdateClientBroadcasts ( gentity_t *self ) +{ + // Clear all the broadcast bits for this client + memset ( self->r.broadcastClients, 0, sizeof ( self->r.broadcastClients ) ); + + // The jedi master is broadcast to everyone in range + G_UpdateJediMasterBroadcasts ( self ); + + // Anyone with force sight on should see this client + G_UpdateForceSightBroadcasts ( self ); +} + +void G_AddPushVecToUcmd( gentity_t *self, usercmd_t *ucmd ) +{ + vec3_t forward, right, moveDir; + float pushSpeed, fMove, rMove; + + if ( !self->client ) + { + return; + } + pushSpeed = VectorLengthSquared(self->client->pushVec); + if(!pushSpeed) + {//not being pushed + return; + } + + AngleVectors(self->client->ps.viewangles, forward, right, NULL); + VectorScale(forward, ucmd->forwardmove/127.0f * self->client->ps.speed, moveDir); + VectorMA(moveDir, ucmd->rightmove/127.0f * self->client->ps.speed, right, moveDir); + //moveDir is now our intended move velocity + + VectorAdd(moveDir, self->client->pushVec, moveDir); + self->client->ps.speed = VectorNormalize(moveDir); + //moveDir is now our intended move velocity plus our push Vector + + fMove = 127.0 * DotProduct(forward, moveDir); + rMove = 127.0 * DotProduct(right, moveDir); + ucmd->forwardmove = floor(fMove);//If in the same dir , will be positive + ucmd->rightmove = floor(rMove);//If in the same dir , will be positive + + if ( self->client->pushVecTime < level.time ) + { + VectorClear( self->client->pushVec ); + } +} + +qboolean G_StandingAnim( int anim ) +{//NOTE: does not check idles or special (cinematic) stands + switch ( anim ) + { + case BOTH_STAND1: + case BOTH_STAND2: + case BOTH_STAND3: + case BOTH_STAND4: + return qtrue; + break; + } + return qfalse; +} + +qboolean G_ActionButtonPressed(int buttons) +{ + if (buttons & BUTTON_ATTACK) + { + return qtrue; + } + else if (buttons & BUTTON_USE_HOLDABLE) + { + return qtrue; + } + else if (buttons & BUTTON_GESTURE) + { + return qtrue; + } + else if (buttons & BUTTON_USE) + { + return qtrue; + } + else if (buttons & BUTTON_FORCEGRIP) + { + return qtrue; + } + else if (buttons & BUTTON_ALT_ATTACK) + { + return qtrue; + } + else if (buttons & BUTTON_FORCEPOWER) + { + return qtrue; + } + else if (buttons & BUTTON_FORCE_LIGHTNING) + { + return qtrue; + } + else if (buttons & BUTTON_FORCE_DRAIN) + { + return qtrue; + } + + return qfalse; +} + +void G_CheckClientIdle( gentity_t *ent, usercmd_t *ucmd ) +{ + vec3_t viewChange; + qboolean actionPressed; + int buttons; + + if ( !ent || !ent->client || ent->health <= 0 || ent->client->ps.stats[STAT_HEALTH] <= 0 || + ent->client->sess.sessionTeam == TEAM_SPECTATOR || (ent->client->ps.pm_flags & PMF_FOLLOW)) + { + return; + } + + buttons = ucmd->buttons; + + if (ent->r.svFlags & SVF_BOT) + { //they press use all the time.. + buttons &= ~BUTTON_USE; + } + actionPressed = G_ActionButtonPressed(buttons); + + VectorSubtract(ent->client->ps.viewangles, ent->client->idleViewAngles, viewChange); + if ( !VectorCompare( vec3_origin, ent->client->ps.velocity ) + || actionPressed || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove + || !G_StandingAnim( ent->client->ps.legsAnim ) + || (ent->health+ent->client->ps.stats[STAT_ARMOR]) != ent->client->idleHealth + || VectorLength(viewChange) > 10 + || ent->client->ps.legsTimer > 0 + || ent->client->ps.torsoTimer > 0 + || ent->client->ps.weaponTime > 0 + || ent->client->ps.weaponstate == WEAPON_CHARGING + || ent->client->ps.weaponstate == WEAPON_CHARGING_ALT + || ent->client->ps.zoomMode + || (ent->client->ps.weaponstate != WEAPON_READY && ent->client->ps.weapon != WP_SABER) + || ent->client->ps.forceHandExtend != HANDEXTEND_NONE + || ent->client->ps.saberBlocked != BLOCKED_NONE + || ent->client->ps.saberBlocking >= level.time + || ent->client->ps.weapon == WP_MELEE + || (ent->client->ps.weapon != ent->client->pers.cmd.weapon && ent->s.eType != ET_NPC)) + {//FIXME: also check for turning? + qboolean brokeOut = qfalse; + + if ( !VectorCompare( vec3_origin, ent->client->ps.velocity ) + || actionPressed || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove + || (ent->health+ent->client->ps.stats[STAT_ARMOR]) != ent->client->idleHealth + || ent->client->ps.zoomMode + || (ent->client->ps.weaponstate != WEAPON_READY && ent->client->ps.weapon != WP_SABER) + || (ent->client->ps.weaponTime > 0 && ent->client->ps.weapon == WP_SABER) + || ent->client->ps.weaponstate == WEAPON_CHARGING + || ent->client->ps.weaponstate == WEAPON_CHARGING_ALT + || ent->client->ps.forceHandExtend != HANDEXTEND_NONE + || ent->client->ps.saberBlocked != BLOCKED_NONE + || ent->client->ps.saberBlocking >= level.time + || ent->client->ps.weapon == WP_MELEE + || (ent->client->ps.weapon != ent->client->pers.cmd.weapon && ent->s.eType != ET_NPC)) + { + //if in an idle, break out + switch ( ent->client->ps.legsAnim ) + { + case BOTH_STAND1IDLE1: + case BOTH_STAND2IDLE1: + case BOTH_STAND2IDLE2: + case BOTH_STAND3IDLE1: + case BOTH_STAND5IDLE1: + ent->client->ps.legsTimer = 0; + brokeOut = qtrue; + break; + } + switch ( ent->client->ps.torsoAnim ) + { + case BOTH_STAND1IDLE1: + case BOTH_STAND2IDLE1: + case BOTH_STAND2IDLE2: + case BOTH_STAND3IDLE1: + case BOTH_STAND5IDLE1: + ent->client->ps.torsoTimer = 0; + ent->client->ps.weaponTime = 0; + ent->client->ps.saberMove = LS_READY; + brokeOut = qtrue; + break; + } + } + // + ent->client->idleHealth = (ent->health+ent->client->ps.stats[STAT_ARMOR]); + VectorCopy(ent->client->ps.viewangles, ent->client->idleViewAngles); + if ( ent->client->idleTime < level.time ) + { + ent->client->idleTime = level.time; + } + + if (brokeOut && + (ent->client->ps.weaponstate == WEAPON_CHARGING || ent->client->ps.weaponstate == WEAPON_CHARGING_ALT)) + { + ent->client->ps.torsoAnim = TORSO_RAISEWEAP1; + } + } + else if ( level.time - ent->client->idleTime > 5000 ) + {//been idle for 5 seconds + int idleAnim = -1; + switch ( ent->client->ps.legsAnim ) + { + case BOTH_STAND1: + idleAnim = BOTH_STAND1IDLE1; + break; + case BOTH_STAND2: + idleAnim = BOTH_STAND2IDLE1;//Q_irand(BOTH_STAND2IDLE1,BOTH_STAND2IDLE2); + break; + case BOTH_STAND3: + idleAnim = BOTH_STAND3IDLE1; + break; + case BOTH_STAND5: + idleAnim = BOTH_STAND5IDLE1; + break; + } + + if (idleAnim == BOTH_STAND2IDLE1 && Q_irand(1, 10) <= 5) + { + idleAnim = BOTH_STAND2IDLE2; + } + + if ( idleAnim != -1 && /*PM_HasAnimation( ent, idleAnim )*/idleAnim > 0 && idleAnim < MAX_ANIMATIONS ) + { + G_SetAnim(ent, ucmd, SETANIM_BOTH, idleAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + + //don't idle again after this anim for a while + //ent->client->idleTime = level.time + PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)idleAnim ) + Q_irand( 0, 2000 ); + ent->client->idleTime = level.time + ent->client->ps.legsTimer + Q_irand( 0, 2000 ); + } + } +} + +void NPC_Accelerate( gentity_t *ent, qboolean fullWalkAcc, qboolean fullRunAcc ) +{ + if ( !ent->client || !ent->NPC ) + { + return; + } + + if ( !ent->NPC->stats.acceleration ) + {//No acceleration means just start and stop + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + //FIXME: in cinematics always accel/decel? + else if ( ent->NPC->desiredSpeed <= ent->NPC->stats.walkSpeed ) + {//Only accelerate if at walkSpeeds + if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration ) + { + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed += ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed ) + { + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + else if ( fullWalkAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration ) + {//decelerate even when walking + ent->NPC->currentSpeed -= ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) + {//stop on a dime + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + } + else// if ( ent->NPC->desiredSpeed > ent->NPC->stats.walkSpeed ) + {//Only decelerate if at runSpeeds + if ( fullRunAcc && ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration ) + {//Accelerate to runspeed + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed += ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed ) + {//accelerate instantly + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + else if ( fullRunAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration ) + { + ent->NPC->currentSpeed -= ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) + { + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + } +} + +/* +------------------------- +NPC_GetWalkSpeed +------------------------- +*/ + +static int NPC_GetWalkSpeed( gentity_t *ent ) +{ + int walkSpeed = 0; + + if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) ) + return 0; + + switch ( ent->client->playerTeam ) + { + case NPCTEAM_PLAYER: //To shutup compiler, will add entries later (this is stub code) + default: + walkSpeed = ent->NPC->stats.walkSpeed; + break; + } + + return walkSpeed; +} + +/* +------------------------- +NPC_GetRunSpeed +------------------------- +*/ +static int NPC_GetRunSpeed( gentity_t *ent ) +{ + int runSpeed = 0; + + if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) ) + return 0; +/* + switch ( ent->client->playerTeam ) + { + case TEAM_BORG: + runSpeed = ent->NPC->stats.runSpeed; + runSpeed += BORG_RUN_INCR * (g_spskill->integer%3); + break; + + case TEAM_8472: + runSpeed = ent->NPC->stats.runSpeed; + runSpeed += SPECIES_RUN_INCR * (g_spskill->integer%3); + break; + + case TEAM_STASIS: + runSpeed = ent->NPC->stats.runSpeed; + runSpeed += STASIS_RUN_INCR * (g_spskill->integer%3); + break; + + case TEAM_BOTS: + runSpeed = ent->NPC->stats.runSpeed; + break; + + default: + runSpeed = ent->NPC->stats.runSpeed; + break; + } +*/ + // team no longer indicates species/race. Use NPC_class to adjust speed for specific npc types + switch( ent->client->NPC_class) + { + case CLASS_PROBE: // droid cases here to shut-up compiler + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_PROTOCOL: + case CLASS_ATST: // hmm, not really your average droid + case CLASS_MOUSE: + case CLASS_SEEKER: + case CLASS_REMOTE: + runSpeed = ent->NPC->stats.runSpeed; + break; + + default: + runSpeed = ent->NPC->stats.runSpeed*1.3f; //rww - seems to slow in MP for some reason. + break; + } + + return runSpeed; +} + +//Seems like a slightly less than ideal method for this, could it be done on the client? +extern qboolean FlyingCreature( gentity_t *ent ); +void G_CheckMovingLoopingSounds( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( ent->client ) + { + if ( (ent->NPC&&!VectorCompare( vec3_origin, ent->client->ps.moveDir ))//moving using moveDir + || ucmd->forwardmove || ucmd->rightmove//moving using ucmds + || (ucmd->upmove&&FlyingCreature( ent ))//flier using ucmds to move + || (FlyingCreature( ent )&&!VectorCompare( vec3_origin, ent->client->ps.velocity )&&ent->health>0))//flier using velocity to move + { + switch( ent->client->NPC_class ) + { + case CLASS_R2D2: + ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" ); + break; + case CLASS_R5D2: + ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" ); + break; + case CLASS_MARK2: + ent->s.loopSound = G_SoundIndex( "sound/chars/mark2/misc/mark2_move_lp" ); + break; + case CLASS_MOUSE: + ent->s.loopSound = G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" ); + break; + case CLASS_PROBE: + ent->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); + } + } + else + {//not moving under your own control, stop loopSound + if ( ent->client->NPC_class == CLASS_R2D2 || ent->client->NPC_class == CLASS_R5D2 + || ent->client->NPC_class == CLASS_MARK2 || ent->client->NPC_class == CLASS_MOUSE + || ent->client->NPC_class == CLASS_PROBE ) + { + ent->s.loopSound = 0; + } + } + } +} + +void G_HeldByMonster( gentity_t *ent, usercmd_t **ucmd ) +{ + if ( ent + && ent->client + && ent->client->ps.hasLookTarget )//NOTE: lookTarget is an entity number, so this presumes that client 0 is NOT a Rancor... + { + gentity_t *monster = &g_entities[ent->client->ps.lookTarget]; + if ( monster && monster->client ) + { + //take the monster's waypoint as your own + ent->waypoint = monster->waypoint; + if ( monster->s.NPC_class == CLASS_RANCOR ) + {//only possibility right now, may add Wampa and Sand Creature later + BG_AttachToRancor( monster->ghoul2, //ghoul2 info + monster->r.currentAngles[YAW], + monster->r.currentOrigin, + level.time, + NULL, + monster->modelScale, + (monster->client->ps.eFlags2&EF2_GENERIC_NPC_FLAG), + ent->client->ps.origin, + ent->client->ps.viewangles, + NULL ); + } + VectorClear( ent->client->ps.velocity ); + G_SetOrigin( ent, ent->client->ps.origin ); + SetClientViewAngle( ent, ent->client->ps.viewangles ); + G_SetAngles( ent, ent->client->ps.viewangles ); + trap_LinkEntity( ent );//redundant? + } + } + // don't allow movement, weapon switching, and most kinds of button presses + (*ucmd)->forwardmove = 0; + (*ucmd)->rightmove = 0; + (*ucmd)->upmove = 0; +} + +typedef enum +{ + TAUNT_TAUNT = 0, + TAUNT_BOW, + TAUNT_MEDITATE, + TAUNT_FLOURISH, + TAUNT_GLOAT +}; + +void G_SetTauntAnim( gentity_t *ent, int taunt ) +{ + if (ent->client->pers.cmd.upmove || + ent->client->pers.cmd.forwardmove || + ent->client->pers.cmd.rightmove) + { //hack, don't do while moving + return; + } + if ( taunt != TAUNT_TAUNT ) + {//normal taunt always allowed + if ( g_gametype.integer != GT_DUEL + && g_gametype.integer != GT_POWERDUEL ) + {//no taunts unless in Duel + return; + } + } + if ( ent->client->ps.torsoTimer < 1 + && ent->client->ps.forceHandExtend == HANDEXTEND_NONE + && ent->client->ps.legsTimer < 1 + && ent->client->ps.weaponTime < 1 + && ent->client->ps.saberLockTime < level.time ) + { + int anim = -1; + switch ( taunt ) + { + case TAUNT_TAUNT: + if ( ent->client->ps.weapon != WP_SABER ) + { + anim = BOTH_ENGAGETAUNT; + } + else + { + switch ( ent->client->ps.fd.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn off second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOff ); + } + else if ( ent->client->ps.saberHolstered == 0 ) + {//turn off first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOff ); + } + ent->client->ps.saberHolstered = 2; + anim = BOTH_GESTURE1; + break; + case SS_MEDIUM: + case SS_STRONG: + case SS_DESANN: + anim = BOTH_ENGAGETAUNT; + break; + case SS_DUAL: + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn on second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOn ); + } + else if ( ent->client->ps.saberHolstered == 2 ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_DUAL_TAUNT; + break; + case SS_STAFF: + if ( ent->client->ps.saberHolstered > 0 ) + {//turn on all blades + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_STAFF_TAUNT; + break; + } + } + break; + case TAUNT_BOW: + anim = BOTH_BOW; + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn off second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOff ); + } + else if ( ent->client->ps.saberHolstered == 0 ) + {//turn off first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOff ); + } + ent->client->ps.saberHolstered = 2; + break; + case TAUNT_MEDITATE: + anim = BOTH_MEDITATE; + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn off second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOff ); + } + else if ( ent->client->ps.saberHolstered == 0 ) + {//turn off first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOff ); + } + ent->client->ps.saberHolstered = 2; + break; + case TAUNT_FLOURISH: + if ( ent->client->ps.weapon == WP_SABER ) + { + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn on second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOn ); + } + else if ( ent->client->ps.saberHolstered == 2 ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + switch ( ent->client->ps.fd.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + anim = BOTH_SHOWOFF_FAST; + break; + case SS_MEDIUM: + anim = BOTH_SHOWOFF_MEDIUM; + break; + case SS_STRONG: + case SS_DESANN: + anim = BOTH_SHOWOFF_STRONG; + break; + case SS_DUAL: + anim = BOTH_SHOWOFF_DUAL; + break; + case SS_STAFF: + anim = BOTH_SHOWOFF_STAFF; + break; + } + } + break; + case TAUNT_GLOAT: + switch ( ent->client->ps.fd.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + anim = BOTH_VICTORY_FAST; + break; + case SS_MEDIUM: + anim = BOTH_VICTORY_MEDIUM; + break; + case SS_STRONG: + case SS_DESANN: + if ( ent->client->ps.saberHolstered ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_VICTORY_STRONG; + break; + case SS_DUAL: + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn on second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOn ); + } + else if ( ent->client->ps.saberHolstered == 2 ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_VICTORY_DUAL; + break; + case SS_STAFF: + if ( ent->client->ps.saberHolstered ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_VICTORY_STAFF; + break; + } + break; + } + if ( anim != -1 ) + { + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + ent->client->ps.forceHandExtend = HANDEXTEND_TAUNT; + ent->client->ps.forceDodgeAnim = anim; + ent->client->ps.forceHandExtendTime = level.time + BG_AnimLength(ent->localAnimIndex, (animNumber_t)anim); + } + if ( taunt != TAUNT_MEDITATE + && taunt != TAUNT_BOW ) + {//no sound for meditate or bow + G_AddEvent( ent, EV_TAUNT, taunt ); + } + } + } +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame on fast clients. + +If "g_synchronousClients 1" is set, this will be called exactly +once for each server frame, which makes for smooth demo recording. +============== +*/ +void ClientThink_real( gentity_t *ent ) { + gclient_t *client; + pmove_t pm; + int oldEventSequence; + int msec; + usercmd_t *ucmd; + qboolean isNPC = qfalse; + qboolean controlledByPlayer = qfalse; + qboolean killJetFlags = qtrue; + + client = ent->client; + + if (ent->s.eType == ET_NPC) + { + isNPC = qtrue; + } + + // don't think if the client is not yet connected (and thus not yet spawned in) + if (client->pers.connected != CON_CONNECTED && !isNPC) { + return; + } + + // This code was moved here from clientThink to fix a problem with g_synchronousClients + // being set to 1 when in vehicles. + if ( ent->s.number < MAX_CLIENTS && ent->client->ps.m_iVehicleNum ) + {//driving a vehicle + if (g_entities[ent->client->ps.m_iVehicleNum].client) + { + gentity_t *veh = &g_entities[ent->client->ps.m_iVehicleNum]; + + if (veh->m_pVehicle && + veh->m_pVehicle->m_pPilot == (bgEntity_t *)ent) + { //only take input from the pilot... + veh->client->ps.commandTime = ent->client->ps.commandTime; + memcpy(&veh->m_pVehicle->m_ucmd, &ent->client->pers.cmd, sizeof(usercmd_t)); + if ( veh->m_pVehicle->m_ucmd.buttons & BUTTON_TALK ) + { //forced input if "chat bubble" is up + veh->m_pVehicle->m_ucmd.buttons = BUTTON_TALK; + veh->m_pVehicle->m_ucmd.forwardmove = 0; + veh->m_pVehicle->m_ucmd.rightmove = 0; + veh->m_pVehicle->m_ucmd.upmove = 0; + } + } + } + } + + if (!(client->ps.pm_flags & PMF_FOLLOW)) + { + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1 && + bgSiegeClasses[client->siegeClass].saberStance) + { //the class says we have to use this stance set. + if (!(bgSiegeClasses[client->siegeClass].saberStance & (1 << client->ps.fd.saberAnimLevel))) + { //the current stance is not in the bitmask, so find the first one that is. + int i = SS_FAST; + + while (i < SS_NUM_SABER_STYLES) + { + if (bgSiegeClasses[client->siegeClass].saberStance & (1 << i)) + { + if (i == SS_DUAL + && client->ps.saberHolstered == 1 ) + {//one saber should be off, adjust saberAnimLevel accordinly + client->ps.fd.saberAnimLevelBase = i; + client->ps.fd.saberAnimLevel = SS_FAST; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + else if ( i == SS_STAFF + && client->ps.saberHolstered == 1 + && client->saber[0].singleBladeStyle != SS_NONE) + {//one saber or blade should be off, adjust saberAnimLevel accordinly + client->ps.fd.saberAnimLevelBase = i; + client->ps.fd.saberAnimLevel = client->saber[0].singleBladeStyle; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + else + { + client->ps.fd.saberAnimLevelBase = client->ps.fd.saberAnimLevel = i; + client->ps.fd.saberDrawAnimLevel = i; + } + break; + } + + i++; + } + } + } + else if (client->saber[0].model[0] && client->saber[1].model[0]) + { //with two sabs always use akimbo style + client->ps.fd.saberAnimLevelBase = SS_DUAL; + if ( client->ps.saberHolstered == 1 ) + {//one saber should be off, adjust saberAnimLevel accordinly + client->ps.fd.saberAnimLevel = SS_FAST; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + else + { + client->ps.fd.saberAnimLevelBase = client->ps.fd.saberAnimLevel = SS_DUAL; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + } + else if (client->saber[0].style == SS_STAFF) + { //then always use the staff style + client->ps.fd.saberAnimLevelBase = SS_STAFF; + if ( client->ps.saberHolstered == 1 + && client->saber[0].singleBladeStyle != SS_NONE) + {//one blade should be off, adjust saberAnimLevel accordinly + client->ps.fd.saberAnimLevel = client->saber[0].singleBladeStyle; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + else + { + client->ps.fd.saberAnimLevel = SS_STAFF; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + } + } + + // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; + + if ( client && (client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + { + G_HeldByMonster( ent, &ucmd ); + } + + // sanity check the command time to prevent speedup cheating + if ( ucmd->serverTime > level.time + 200 ) { + ucmd->serverTime = level.time + 200; +// G_Printf("serverTime <<<<<\n" ); + } + if ( ucmd->serverTime < level.time - 1000 ) { + ucmd->serverTime = level.time - 1000; +// G_Printf("serverTime >>>>>\n" ); + } + + if (isNPC && (ucmd->serverTime - client->ps.commandTime) < 1) + { + ucmd->serverTime = client->ps.commandTime + 100; + } + + msec = ucmd->serverTime - client->ps.commandTime; + // following others may result in bad times, but we still want + // to check for follow toggles + if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { + return; + } + + if ( msec > 200 ) { + msec = 200; + } + + if ( pmove_msec.integer < 8 ) { + trap_Cvar_Set("pmove_msec", "8"); + } + else if (pmove_msec.integer > 33) { + trap_Cvar_Set("pmove_msec", "33"); + } + + if ( pmove_fixed.integer || client->pers.pmoveFixed ) { + ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + //if (ucmd->serverTime - client->ps.commandTime <= 0) + // return; + } + + // + // check for exiting intermission + // + if ( level.intermissiontime ) { + ClientIntermissionThink( client ); + return; + } + + // spectators don't do much + if ( client->sess.sessionTeam == TEAM_SPECTATOR || client->tempSpectate > level.time ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + return; + } + SpectatorThink( ent, ucmd ); + return; + } + + if (ent && ent->client && (ent->client->ps.eFlags & EF_INVULNERABLE)) + { + if (ent->client->invulnerableTimer <= level.time) + { + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + } + } + + if (ent->s.eType != ET_NPC) + { + // check for inactivity timer, but never drop the local client of a non-dedicated server + if ( !ClientInactivityTimer( client ) ) { + return; + } + } + + //Check if we should have a fullbody push effect around the player + if (client->pushEffectTime > level.time) + { + client->ps.eFlags |= EF_BODYPUSH; + } + else if (client->pushEffectTime) + { + client->pushEffectTime = 0; + client->ps.eFlags &= ~EF_BODYPUSH; + } + + if (client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_JETPACK)) + { + client->ps.eFlags |= EF_JETPACK; + } + else + { + client->ps.eFlags &= ~EF_JETPACK; + } + + if ( client->noclip ) { + client->ps.pm_type = PM_NOCLIP; + } else if ( client->ps.eFlags & EF_DISINTEGRATION ) { + client->ps.pm_type = PM_NOCLIP; + } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + client->ps.pm_type = PM_DEAD; + } else { + if (client->ps.forceGripChangeMovetype) + { + client->ps.pm_type = client->ps.forceGripChangeMovetype; + } + else + { + if (client->jetPackOn) + { + client->ps.pm_type = PM_JETPACK; + client->ps.eFlags |= EF_JETPACK_ACTIVE; + killJetFlags = qfalse; + } + else + { + client->ps.pm_type = PM_NORMAL; + } + } + } + + if (killJetFlags) + { + client->ps.eFlags &= ~EF_JETPACK_ACTIVE; + client->ps.eFlags &= ~EF_JETPACK_FLAMING; + } + +#define SLOWDOWN_DIST 128.0f +#define MIN_NPC_SPEED 16.0f + + if (client->bodyGrabIndex != ENTITYNUM_NONE) + { + gentity_t *grabbed = &g_entities[client->bodyGrabIndex]; + + if (!grabbed->inuse || grabbed->s.eType != ET_BODY || + (grabbed->s.eFlags & EF_DISINTEGRATION) || + (grabbed->s.eFlags & EF_NODRAW)) + { + if (grabbed->inuse && grabbed->s.eType == ET_BODY) + { + grabbed->s.ragAttach = 0; + } + client->bodyGrabIndex = ENTITYNUM_NONE; + } + else + { + mdxaBone_t rhMat; + vec3_t rhOrg, tAng; + vec3_t bodyDir; + float bodyDist; + + ent->client->ps.forceHandExtend = HANDEXTEND_DRAGGING; + + if (ent->client->ps.forceHandExtendTime < level.time + 500) + { + ent->client->ps.forceHandExtendTime = level.time + 1000; + } + + VectorSet(tAng, 0, ent->client->ps.viewangles[YAW], 0); + trap_G2API_GetBoltMatrix(ent->ghoul2, 0, 0, &rhMat, tAng, ent->client->ps.origin, level.time, + NULL, ent->modelScale); //0 is always going to be right hand bolt + BG_GiveMeVectorFromMatrix(&rhMat, ORIGIN, rhOrg); + + VectorSubtract(rhOrg, grabbed->r.currentOrigin, bodyDir); + bodyDist = VectorLength(bodyDir); + + if (bodyDist > 40.0f) + { //can no longer reach + grabbed->s.ragAttach = 0; + client->bodyGrabIndex = ENTITYNUM_NONE; + } + else if (bodyDist > 24.0f) + { + bodyDir[2] = 0; //don't want it floating + //VectorScale(bodyDir, 0.1f, bodyDir); + VectorAdd(grabbed->epVelocity, bodyDir, grabbed->epVelocity); + G_Sound(grabbed, CHAN_AUTO, G_SoundIndex("sound/player/roll1.wav")); + } + } + } + else if (ent->client->ps.forceHandExtend == HANDEXTEND_DRAGGING) + { + ent->client->ps.forceHandExtend = HANDEXTEND_WEAPONREADY; + } + + if (ent->NPC && ent->s.NPC_class != CLASS_VEHICLE) //vehicles manage their own speed + { + //FIXME: swoop should keep turning (and moving forward?) for a little bit? + if ( ent->NPC->combatMove == qfalse ) + { + //if ( !(ucmd->buttons & BUTTON_USE) ) + if (1) + {//Not leaning + qboolean Flying = (ucmd->upmove && (ent->client->ps.eFlags2&EF2_FLYING));//ent->client->moveType == MT_FLYSWIM); + qboolean Climbing = (ucmd->upmove && ent->watertype&CONTENTS_LADDER ); + + //client->ps.friction = 6; + + if ( ucmd->forwardmove || ucmd->rightmove || Flying ) + { + //if ( ent->NPC->behaviorState != BS_FORMATION ) + {//In - Formation NPCs set thier desiredSpeed themselves + if ( ucmd->buttons & BUTTON_WALKING ) + { + ent->NPC->desiredSpeed = NPC_GetWalkSpeed( ent );//ent->NPC->stats.walkSpeed; + } + else//running + { + ent->NPC->desiredSpeed = NPC_GetRunSpeed( ent );//ent->NPC->stats.runSpeed; + } + + if ( ent->NPC->currentSpeed >= 80 && !controlledByPlayer ) + {//At higher speeds, need to slow down close to stuff + //Slow down as you approach your goal + // if ( ent->NPC->distToGoal < SLOWDOWN_DIST && client->race != RACE_BORG && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128 + if ( ent->NPC->distToGoal < SLOWDOWN_DIST && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128 + { + if ( ent->NPC->desiredSpeed > MIN_NPC_SPEED ) + { + float slowdownSpeed = ((float)ent->NPC->desiredSpeed) * ent->NPC->distToGoal / SLOWDOWN_DIST; + + ent->NPC->desiredSpeed = ceil(slowdownSpeed); + if ( ent->NPC->desiredSpeed < MIN_NPC_SPEED ) + {//don't slow down too much + ent->NPC->desiredSpeed = MIN_NPC_SPEED; + } + } + } + } + } + } + else if ( Climbing ) + { + ent->NPC->desiredSpeed = ent->NPC->stats.walkSpeed; + } + else + {//We want to stop + ent->NPC->desiredSpeed = 0; + } + + NPC_Accelerate( ent, qfalse, qfalse ); + + if ( ent->NPC->currentSpeed <= 24 && ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) + {//No-one walks this slow + client->ps.speed = ent->NPC->currentSpeed = 0;//Full stop + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + } + else + { + if ( ent->NPC->currentSpeed <= ent->NPC->stats.walkSpeed ) + {//Play the walkanim + ucmd->buttons |= BUTTON_WALKING; + } + else + { + ucmd->buttons &= ~BUTTON_WALKING; + } + + if ( ent->NPC->currentSpeed > 0 ) + {//We should be moving + if ( Climbing || Flying ) + { + if ( !ucmd->upmove ) + {//We need to force them to take a couple more steps until stopped + ucmd->upmove = ent->NPC->last_ucmd.upmove;//was last_upmove; + } + } + else if ( !ucmd->forwardmove && !ucmd->rightmove ) + {//We need to force them to take a couple more steps until stopped + ucmd->forwardmove = ent->NPC->last_ucmd.forwardmove;//was last_forwardmove; + ucmd->rightmove = ent->NPC->last_ucmd.rightmove;//was last_rightmove; + } + } + + client->ps.speed = ent->NPC->currentSpeed; + // if ( player && player->client && player->client->ps.viewEntity == ent->s.number ) + // { + // } + // else + //rwwFIXMEFIXME: do this and also check for all real client + if (1) + { + //Slow down on turns - don't orbit!!! + float turndelta = 0; + // if the NPC is locked into a Yaw, we want to check the lockedDesiredYaw...otherwise the NPC can't walk backwards, because it always thinks it trying to turn according to desiredYaw + //if( client->renderInfo.renderFlags & RF_LOCKEDANGLE ) // yeah I know the RF_ flag is a pretty ugly hack... + if (0) //rwwFIXMEFIXME: ... + { + turndelta = (180 - fabs( AngleDelta( ent->r.currentAngles[YAW], ent->NPC->lockedDesiredYaw ) ))/180; + } + else + { + turndelta = (180 - fabs( AngleDelta( ent->r.currentAngles[YAW], ent->NPC->desiredYaw ) ))/180; + } + + if ( turndelta < 0.75f ) + { + client->ps.speed = 0; + } + else if ( ent->NPC->distToGoal < 100 && turndelta < 1.0 ) + {//Turn is greater than 45 degrees or closer than 100 to goal + client->ps.speed = floor(((float)(client->ps.speed))*turndelta); + } + } + } + } + } + else + { + ent->NPC->desiredSpeed = ( ucmd->buttons & BUTTON_WALKING ) ? NPC_GetWalkSpeed( ent ) : NPC_GetRunSpeed( ent ); + + client->ps.speed = ent->NPC->desiredSpeed; + } + + if (ucmd->buttons & BUTTON_WALKING) + { //sort of a hack I guess since MP handles walking differently from SP (has some proxy cheat prevention methods) + if (ent->client->ps.speed > 64) + { + ent->client->ps.speed = 64; + } + + if (ucmd->forwardmove > 64) + { + ucmd->forwardmove = ent->client->ps.speed; + } + else if (ucmd->forwardmove < -64) + { + ucmd->forwardmove = -ent->client->ps.speed; + } + + if (ucmd->rightmove > 64) + { + ucmd->rightmove = ent->client->ps.speed; + } + else if ( ucmd->rightmove < -64) + { + ucmd->rightmove = -ent->client->ps.speed; + } + + ent->client->ps.speed = ent->client->ps.basespeed = NPC_GetRunSpeed( ent ); + } + client->ps.basespeed = client->ps.speed; + } + else if (!client->ps.m_iVehicleNum && + (!ent->NPC || ent->s.NPC_class != CLASS_VEHICLE)) //if riding a vehicle it will manage our speed and such + { + // set speed + client->ps.speed = g_speed.value; + + //Check for a siege class speed multiplier + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1) + { + client->ps.speed *= bgSiegeClasses[client->siegeClass].speed; + } + + if (client->bodyGrabIndex != ENTITYNUM_NONE) + { //can't go nearly as fast when dragging a body around + client->ps.speed *= 0.2f; + } + + client->ps.basespeed = client->ps.speed; + } + + if ( !ent->NPC || !(ent->NPC->aiFlags&NPCAI_CUSTOM_GRAVITY) ) + {//use global gravity + if (ent->NPC && ent->s.NPC_class == CLASS_VEHICLE && + ent->m_pVehicle && ent->m_pVehicle->m_pVehicleInfo->gravity) + { //use custom veh gravity + client->ps.gravity = ent->m_pVehicle->m_pVehicleInfo->gravity; + } + else + { + if (ent->client->inSpaceIndex && ent->client->inSpaceIndex != ENTITYNUM_NONE) + { //in space, so no gravity... + client->ps.gravity = 1.0f; + if (ent->s.number < MAX_CLIENTS) + { + VectorScale(client->ps.velocity, 0.8f, client->ps.velocity); + } + } + else + { + if (client->ps.eFlags2 & EF2_SHIP_DEATH) + { //float there + VectorClear(client->ps.velocity); + client->ps.gravity = 1.0f; + } + else + { + client->ps.gravity = g_gravity.value; + } + } + } + } + + if (ent->client->ps.duelInProgress) + { + gentity_t *duelAgainst = &g_entities[ent->client->ps.duelIndex]; + + //Keep the time updated, so once this duel ends this player can't engage in a duel for another + //10 seconds. This will give other people a chance to engage in duels in case this player wants + //to engage again right after he's done fighting and someone else is waiting. + ent->client->ps.fd.privateDuelTime = level.time + 10000; + + if (ent->client->ps.duelTime < level.time) + { + //Bring out the sabers + if (ent->client->ps.weapon == WP_SABER + && ent->client->ps.saberHolstered + && ent->client->ps.duelTime ) + { + ent->client->ps.saberHolstered = 0; + + if (ent->client->saber[0].soundOn) + { + G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOn); + } + if (ent->client->saber[1].soundOn) + { + G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOn); + } + + G_AddEvent(ent, EV_PRIVATE_DUEL, 2); + + ent->client->ps.duelTime = 0; + } + + if (duelAgainst + && duelAgainst->client + && duelAgainst->inuse + && duelAgainst->client->ps.weapon == WP_SABER + && duelAgainst->client->ps.saberHolstered + && duelAgainst->client->ps.duelTime) + { + duelAgainst->client->ps.saberHolstered = 0; + + if (duelAgainst->client->saber[0].soundOn) + { + G_Sound(duelAgainst, CHAN_AUTO, duelAgainst->client->saber[0].soundOn); + } + if (duelAgainst->client->saber[1].soundOn) + { + G_Sound(duelAgainst, CHAN_AUTO, duelAgainst->client->saber[1].soundOn); + } + + G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 2); + + duelAgainst->client->ps.duelTime = 0; + } + } + else + { + client->ps.speed = 0; + client->ps.basespeed = 0; + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + ucmd->upmove = 0; + } + + if (!duelAgainst || !duelAgainst->client || !duelAgainst->inuse || + duelAgainst->client->ps.duelIndex != ent->s.number) + { + ent->client->ps.duelInProgress = 0; + G_AddEvent(ent, EV_PRIVATE_DUEL, 0); + } + else if (duelAgainst->health < 1 || duelAgainst->client->ps.stats[STAT_HEALTH] < 1) + { + ent->client->ps.duelInProgress = 0; + duelAgainst->client->ps.duelInProgress = 0; + + G_AddEvent(ent, EV_PRIVATE_DUEL, 0); + G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 0); + + //Winner gets full health.. providing he's still alive + if (ent->health > 0 && ent->client->ps.stats[STAT_HEALTH] > 0) + { + if (ent->health < ent->client->ps.stats[STAT_MAX_HEALTH]) + { + ent->client->ps.stats[STAT_HEALTH] = ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + + if (g_spawnInvulnerability.integer) + { + ent->client->ps.eFlags |= EF_INVULNERABLE; + ent->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer; + } + } + + /* + trap_SendServerCommand( ent-g_entities, va("print \"%s %s\n\"", ent->client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLDUELWINNER")) ); + trap_SendServerCommand( duelAgainst-g_entities, va("print \"%s %s\n\"", ent->client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLDUELWINNER")) ); + */ + //Private duel announcements are now made globally because we only want one duel at a time. + if (ent->health > 0 && ent->client->ps.stats[STAT_HEALTH] > 0) + { + trap_SendServerCommand( -1, va("cp \"%s %s %s!\n\"", ent->client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLDUELWINNER"), duelAgainst->client->pers.netname) ); + } + else + { //it was a draw, because we both managed to die in the same frame + trap_SendServerCommand( -1, va("cp \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PLDUELTIE")) ); + } + } + else + { + vec3_t vSub; + float subLen = 0; + + VectorSubtract(ent->client->ps.origin, duelAgainst->client->ps.origin, vSub); + subLen = VectorLength(vSub); + + if (subLen >= 1024) + { + ent->client->ps.duelInProgress = 0; + duelAgainst->client->ps.duelInProgress = 0; + + G_AddEvent(ent, EV_PRIVATE_DUEL, 0); + G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 0); + + trap_SendServerCommand( -1, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PLDUELSTOP")) ); + } + } + } + + if (ent->client->doingThrow > level.time) + { + gentity_t *throwee = &g_entities[ent->client->throwingIndex]; + + if (!throwee->inuse || !throwee->client || throwee->health < 1 || + throwee->client->sess.sessionTeam == TEAM_SPECTATOR || + (throwee->client->ps.pm_flags & PMF_FOLLOW) || + throwee->client->throwingIndex != ent->s.number) + { + ent->client->doingThrow = 0; + ent->client->ps.forceHandExtend = HANDEXTEND_NONE; + + if (throwee->inuse && throwee->client) + { + throwee->client->ps.heldByClient = 0; + throwee->client->beingThrown = 0; + + if (throwee->client->ps.forceHandExtend != HANDEXTEND_POSTTHROWN) + { + throwee->client->ps.forceHandExtend = HANDEXTEND_NONE; + } + } + } + } + + if (ent->client->beingThrown > level.time) + { + gentity_t *thrower = &g_entities[ent->client->throwingIndex]; + + if (!thrower->inuse || !thrower->client || thrower->health < 1 || + thrower->client->sess.sessionTeam == TEAM_SPECTATOR || + (thrower->client->ps.pm_flags & PMF_FOLLOW) || + thrower->client->throwingIndex != ent->s.number) + { + ent->client->ps.heldByClient = 0; + ent->client->beingThrown = 0; + + if (ent->client->ps.forceHandExtend != HANDEXTEND_POSTTHROWN) + { + ent->client->ps.forceHandExtend = HANDEXTEND_NONE; + } + + if (thrower->inuse && thrower->client) + { + thrower->client->doingThrow = 0; + thrower->client->ps.forceHandExtend = HANDEXTEND_NONE; + } + } + else if (thrower->inuse && thrower->client && thrower->ghoul2 && + trap_G2_HaveWeGhoul2Models(thrower->ghoul2)) + { +#if 0 + int lHandBolt = trap_G2API_AddBolt(thrower->ghoul2, 0, "*l_hand"); + int pelBolt = trap_G2API_AddBolt(thrower->ghoul2, 0, "pelvis"); + + + if (lHandBolt != -1 && pelBolt != -1) +#endif + { + float pDif = 40.0f; + vec3_t boltOrg, pBoltOrg; + vec3_t tAngles; + vec3_t vDif; + vec3_t entDir, otherAngles; + vec3_t fwd, right; + + //Always look at the thrower. + VectorSubtract( thrower->client->ps.origin, ent->client->ps.origin, entDir ); + VectorCopy( ent->client->ps.viewangles, otherAngles ); + otherAngles[YAW] = vectoyaw( entDir ); + SetClientViewAngle( ent, otherAngles ); + + VectorCopy(thrower->client->ps.viewangles, tAngles); + tAngles[PITCH] = tAngles[ROLL] = 0; + + //Get the direction between the pelvis and position of the hand +#if 0 + mdxaBone_t boltMatrix, pBoltMatrix; + + trap_G2API_GetBoltMatrix(thrower->ghoul2, 0, lHandBolt, &boltMatrix, tAngles, thrower->client->ps.origin, level.time, 0, thrower->modelScale); + boltOrg[0] = boltMatrix.matrix[0][3]; + boltOrg[1] = boltMatrix.matrix[1][3]; + boltOrg[2] = boltMatrix.matrix[2][3]; + + trap_G2API_GetBoltMatrix(thrower->ghoul2, 0, pelBolt, &pBoltMatrix, tAngles, thrower->client->ps.origin, level.time, 0, thrower->modelScale); + pBoltOrg[0] = pBoltMatrix.matrix[0][3]; + pBoltOrg[1] = pBoltMatrix.matrix[1][3]; + pBoltOrg[2] = pBoltMatrix.matrix[2][3]; +#else //above tends to not work once in a while, for various reasons I suppose. + VectorCopy(thrower->client->ps.origin, pBoltOrg); + AngleVectors(tAngles, fwd, right, 0); + boltOrg[0] = pBoltOrg[0] + fwd[0]*8 + right[0]*pDif; + boltOrg[1] = pBoltOrg[1] + fwd[1]*8 + right[1]*pDif; + boltOrg[2] = pBoltOrg[2]; +#endif + //G_TestLine(boltOrg, pBoltOrg, 0x0000ff, 50); + + VectorSubtract(ent->client->ps.origin, boltOrg, vDif); + if (VectorLength(vDif) > 32.0f && (thrower->client->doingThrow - level.time) < 4500) + { //the hand is too far away, and can no longer hold onto us, so escape. + ent->client->ps.heldByClient = 0; + ent->client->beingThrown = 0; + thrower->client->doingThrow = 0; + + thrower->client->ps.forceHandExtend = HANDEXTEND_NONE; + G_EntitySound( thrower, CHAN_VOICE, G_SoundIndex("*pain25.wav") ); + + ent->client->ps.forceDodgeAnim = 2; + ent->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + ent->client->ps.forceHandExtendTime = level.time + 500; + ent->client->ps.velocity[2] = 400; + G_PreDefSound(ent->client->ps.origin, PDSOUND_FORCEJUMP); + } + else if ((client->beingThrown - level.time) < 4000) + { //step into the next part of the throw, and go flying back + float vScale = 400.0f; + ent->client->ps.forceHandExtend = HANDEXTEND_POSTTHROWN; + ent->client->ps.forceHandExtendTime = level.time + 1200; + ent->client->ps.forceDodgeAnim = 0; + + thrower->client->ps.forceHandExtend = HANDEXTEND_POSTTHROW; + thrower->client->ps.forceHandExtendTime = level.time + 200; + + ent->client->ps.heldByClient = 0; + + ent->client->ps.heldByClient = 0; + ent->client->beingThrown = 0; + thrower->client->doingThrow = 0; + + AngleVectors(thrower->client->ps.viewangles, vDif, 0, 0); + ent->client->ps.velocity[0] = vDif[0]*vScale; + ent->client->ps.velocity[1] = vDif[1]*vScale; + ent->client->ps.velocity[2] = 400; + + G_EntitySound( ent, CHAN_VOICE, G_SoundIndex("*pain100.wav") ); + G_EntitySound( thrower, CHAN_VOICE, G_SoundIndex("*jump1.wav") ); + + //Set the thrower as the "other killer", so if we die from fall/impact damage he is credited. + ent->client->ps.otherKiller = thrower->s.number; + ent->client->ps.otherKillerTime = level.time + 8000; + ent->client->ps.otherKillerDebounceTime = level.time + 100; + } + else + { //see if we can move to be next to the hand.. if it's not clear, break the throw. + vec3_t intendedOrigin; + trace_t tr; + trace_t tr2; + + VectorSubtract(boltOrg, pBoltOrg, vDif); + VectorNormalize(vDif); + + VectorClear(ent->client->ps.velocity); + intendedOrigin[0] = pBoltOrg[0] + vDif[0]*pDif; + intendedOrigin[1] = pBoltOrg[1] + vDif[1]*pDif; + intendedOrigin[2] = thrower->client->ps.origin[2]; + + trap_Trace(&tr, intendedOrigin, ent->r.mins, ent->r.maxs, intendedOrigin, ent->s.number, ent->clipmask); + trap_Trace(&tr2, ent->client->ps.origin, ent->r.mins, ent->r.maxs, intendedOrigin, ent->s.number, CONTENTS_SOLID); + + if (tr.fraction == 1.0 && !tr.startsolid && tr2.fraction == 1.0 && !tr2.startsolid) + { + VectorCopy(intendedOrigin, ent->client->ps.origin); + + if ((client->beingThrown - level.time) < 4800) + { + ent->client->ps.heldByClient = thrower->s.number+1; + } + } + else + { //if the guy can't be put here then it's time to break the throw off. + ent->client->ps.heldByClient = 0; + ent->client->beingThrown = 0; + thrower->client->doingThrow = 0; + + thrower->client->ps.forceHandExtend = HANDEXTEND_NONE; + G_EntitySound( thrower, CHAN_VOICE, G_SoundIndex("*pain25.wav") ); + + ent->client->ps.forceDodgeAnim = 2; + ent->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + ent->client->ps.forceHandExtendTime = level.time + 500; + ent->client->ps.velocity[2] = 400; + G_PreDefSound(ent->client->ps.origin, PDSOUND_FORCEJUMP); + } + } + } + } + } + else if (ent->client->ps.heldByClient) + { + ent->client->ps.heldByClient = 0; + } + + /* + if ( client->ps.powerups[PW_HASTE] ) { + client->ps.speed *= 1.3; + } + */ + + //Will probably never need this again, since we have g2 properly serverside now. + //But just in case. + /* + if (client->ps.usingATST && ent->health > 0) + { //we have special shot clip boxes as an ATST + ent->r.contents |= CONTENTS_NOSHOT; + ATST_ManageDamageBoxes(ent); + } + else + { + ent->r.contents &= ~CONTENTS_NOSHOT; + client->damageBoxHandle_Head = 0; + client->damageBoxHandle_RLeg = 0; + client->damageBoxHandle_LLeg = 0; + } + */ + + //rww - moved this stuff into the pmove code so that it's predicted properly + //BG_AdjustClientSpeed(&client->ps, &client->pers.cmd, level.time); + + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + memset (&pm, 0, sizeof(pm)); + + if ( ent->flags & FL_FORCE_GESTURE ) { + ent->flags &= ~FL_FORCE_GESTURE; + ent->client->pers.cmd.buttons |= BUTTON_GESTURE; + } + + if (ent->client && ent->client->ps.fallingToDeath && + (level.time - FALL_FADE_TIME) > ent->client->ps.fallingToDeath) + { //die! + if (ent->health > 0) + { + gentity_t *otherKiller = ent; + if (ent->client->ps.otherKillerTime > level.time && + ent->client->ps.otherKiller != ENTITYNUM_NONE) + { + otherKiller = &g_entities[ent->client->ps.otherKiller]; + + if (!otherKiller->inuse) + { + otherKiller = ent; + } + } + G_Damage(ent, otherKiller, otherKiller, NULL, ent->client->ps.origin, 9999, DAMAGE_NO_PROTECTION, MOD_FALLING); + //player_die(ent, ent, ent, 100000, MOD_FALLING); + // if (!ent->NPC) + // { + // respawn(ent); + // } + // ent->client->ps.fallingToDeath = 0; + + G_MuteSound(ent->s.number, CHAN_VOICE); //stop screaming, because you are dead! + } + } + + if (ent->client->ps.otherKillerTime > level.time && + ent->client->ps.groundEntityNum != ENTITYNUM_NONE && + ent->client->ps.otherKillerDebounceTime < level.time) + { + ent->client->ps.otherKillerTime = 0; + ent->client->ps.otherKiller = ENTITYNUM_NONE; + } + else if (ent->client->ps.otherKillerTime > level.time && + ent->client->ps.groundEntityNum == ENTITYNUM_NONE) + { + if (ent->client->ps.otherKillerDebounceTime < (level.time + 100)) + { + ent->client->ps.otherKillerDebounceTime = level.time + 100; + } + } + +// WP_ForcePowersUpdate( ent, ucmd); //update any active force powers +// WP_SaberPositionUpdate(ent, ucmd); //check the server-side saber point, do apprioriate server-side actions (effects are cs-only) +// WP_SaberStartMissileBlockCheck(ent, ucmd); + + //NOTE: can't put USE here *before* PMove!! + if ( ent->client->ps.useDelay > level.time + && ent->client->ps.m_iVehicleNum ) + {//when in a vehicle, debounce the use... + ucmd->buttons &= ~BUTTON_USE; + } + + //FIXME: need to do this before check to avoid walls and cliffs (or just cliffs?) + G_AddPushVecToUcmd( ent, ucmd ); + + //play/stop any looping sounds tied to controlled movement + G_CheckMovingLoopingSounds( ent, ucmd ); + + pm.ps = &client->ps; + pm.cmd = *ucmd; + if ( pm.ps->pm_type == PM_DEAD ) { + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + else if ( ent->r.svFlags & SVF_BOT ) { + pm.tracemask = MASK_PLAYERSOLID | CONTENTS_MONSTERCLIP; + } + else { + pm.tracemask = MASK_PLAYERSOLID; + } + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + pm.debugLevel = g_debugMove.integer; + pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; + + pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_msec = pmove_msec.integer; + + pm.animations = bgAllAnims[ent->localAnimIndex].anims;//NULL; + + //rww - bgghoul2 + pm.ghoul2 = NULL; + +#ifdef _DEBUG + if (g_disableServerG2.integer) + { + + } + else +#endif + if (ent->ghoul2) + { + if (ent->localAnimIndex > 1) + { //if it isn't humanoid then we will be having none of this. + pm.ghoul2 = NULL; + } + else + { + pm.ghoul2 = ent->ghoul2; + pm.g2Bolts_LFoot = trap_G2API_AddBolt(ent->ghoul2, 0, "*l_leg_foot"); + pm.g2Bolts_RFoot = trap_G2API_AddBolt(ent->ghoul2, 0, "*r_leg_foot"); + } + } + + //point the saber data to the right place +#if 0 + k = 0; + while (k < MAX_SABERS) + { + if (ent->client->saber[k].model[0]) + { + pm.saber[k] = &ent->client->saber[k]; + } + else + { + pm.saber[k] = NULL; + } + k++; + } +#endif + + //I'll just do this every frame in case the scale changes in realtime (don't need to update the g2 inst for that) + VectorCopy(ent->modelScale, pm.modelScale); + //rww end bgghoul2 + + pm.gametype = g_gametype.integer; + pm.debugMelee = g_debugMelee.integer; + pm.stepSlideFix = g_stepSlideFix.integer; + + pm.noSpecMove = g_noSpecMove.integer; + + pm.nonHumanoid = (ent->localAnimIndex > 0); + + VectorCopy( client->ps.origin, client->oldOrigin ); + + /* + if (level.intermissionQueued != 0 && g_singlePlayer.integer) { + if ( level.time - level.intermissionQueued >= 1000 ) { + pm.cmd.buttons = 0; + pm.cmd.forwardmove = 0; + pm.cmd.rightmove = 0; + pm.cmd.upmove = 0; + if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) { + trap_SendConsoleCommand( EXEC_APPEND, "centerview\n"); + } + ent->client->ps.pm_type = PM_SPINTERMISSION; + } + } + */ + + //Set up bg entity data + pm.baseEnt = (bgEntity_t *)g_entities; + pm.entSize = sizeof(gentity_t); + + if (ent->client->ps.saberLockTime > level.time) + { + gentity_t *blockOpp = &g_entities[ent->client->ps.saberLockEnemy]; + + if (blockOpp && blockOpp->inuse && blockOpp->client) + { + vec3_t lockDir, lockAng; + + //VectorClear( ent->client->ps.velocity ); + VectorSubtract( blockOpp->r.currentOrigin, ent->r.currentOrigin, lockDir ); + //lockAng[YAW] = vectoyaw( defDir ); + vectoangles(lockDir, lockAng); + SetClientViewAngle( ent, lockAng ); + } + + if ( ent->client->ps.saberLockHitCheckTime < level.time ) + {//have moved to next frame since last lock push + ent->client->ps.saberLockHitCheckTime = level.time;//so we don't push more than once per server frame + if ( ( ent->client->buttons & BUTTON_ATTACK ) && ! ( ent->client->oldbuttons & BUTTON_ATTACK ) ) + { + if ( ent->client->ps.saberLockHitIncrementTime < level.time ) + {//have moved to next frame since last saberlock attack button press + int lockHits = 0; + ent->client->ps.saberLockHitIncrementTime = level.time;//so we don't register an attack key press more than once per server frame + //NOTE: FP_SABER_OFFENSE level already taken into account in PM_SaberLocked + if ( (ent->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_RAGE]; + } + else + {//normal attack + switch ( ent->client->ps.fd.saberAnimLevel ) + { + case SS_FAST: + lockHits = 1; + break; + case SS_MEDIUM: + case SS_TAVION: + case SS_DUAL: + case SS_STAFF: + lockHits = 2; + break; + case SS_STRONG: + case SS_DESANN: + lockHits = 3; + break; + } + } + if ( ent->client->ps.fd.forceRageRecoveryTime > level.time + && Q_irand( 0, 1 ) ) + {//finished raging: weak + lockHits -= 1; + } + lockHits += ent->client->saber[0].lockBonus; + if ( ent->client->saber[1].model + && ent->client->saber[1].model[0] + && !ent->client->ps.saberHolstered ) + { + lockHits += ent->client->saber[1].lockBonus; + } + ent->client->ps.saberLockHits += lockHits; + if ( g_saberLockRandomNess.integer ) + { + ent->client->ps.saberLockHits += Q_irand( 0, g_saberLockRandomNess.integer ); + if ( ent->client->ps.saberLockHits < 0 ) + { + ent->client->ps.saberLockHits = 0; + } + } + } + } + if ( ent->client->ps.saberLockHits > 0 ) + { + if ( !ent->client->ps.saberLockAdvance ) + { + ent->client->ps.saberLockHits--; + } + ent->client->ps.saberLockAdvance = qtrue; + } + } + } + else + { + ent->client->ps.saberLockFrame = 0; + //check for taunt + if ( (pm.cmd.generic_cmd == GENCMD_ENGAGE_DUEL) && (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) ) + {//already in a duel, make it a taunt command + pm.cmd.buttons |= BUTTON_GESTURE; + } + } + + if (ent->s.number >= MAX_CLIENTS) + { + VectorCopy(ent->r.mins, pm.mins); + VectorCopy(ent->r.maxs, pm.maxs); +#if 1 + if (ent->s.NPC_class == CLASS_VEHICLE && + ent->m_pVehicle ) + { + if ( ent->m_pVehicle->m_pPilot) + { //vehicles want to use their last pilot ucmd I guess + if ((level.time - ent->m_pVehicle->m_ucmd.serverTime) > 2000) + { //Previous owner disconnected, maybe + ent->m_pVehicle->m_ucmd.serverTime = level.time; + ent->client->ps.commandTime = level.time-100; + msec = 100; + } + + memcpy(&pm.cmd, &ent->m_pVehicle->m_ucmd, sizeof(usercmd_t)); + + //no veh can strafe + pm.cmd.rightmove = 0; + //no crouching or jumping! + pm.cmd.upmove = 0; + + //NOTE: button presses were getting lost! + assert(g_entities[ent->m_pVehicle->m_pPilot->s.number].client); + pm.cmd.buttons = (g_entities[ent->m_pVehicle->m_pPilot->s.number].client->pers.cmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)); + } + if ( ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) + { + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//ATST crushes anything underneath it + gentity_t *under = &g_entities[ent->client->ps.groundEntityNum]; + if ( under && under->health && under->takedamage ) + { + vec3_t down = {0,0,-1}; + //FIXME: we'll be doing traces down from each foot, so we'll have a real impact origin + G_Damage( under, ent, ent, down, under->r.currentOrigin, 100, 0, MOD_CRUSH ); + } + } + } + } +#endif + } + + Pmove (&pm); + + if (ent->client->solidHack) + { + if (ent->client->solidHack > level.time) + { //whee! + ent->r.contents = 0; + } + else + { + ent->r.contents = CONTENTS_BODY; + ent->client->solidHack = 0; + } + } + + if ( ent->NPC ) + { + VectorCopy( ent->client->ps.viewangles, ent->r.currentAngles ); + } + + if (pm.checkDuelLoss) + { + if (pm.checkDuelLoss > 0 && (pm.checkDuelLoss <= MAX_CLIENTS || (pm.checkDuelLoss < (MAX_GENTITIES-1) && g_entities[pm.checkDuelLoss-1].s.eType == ET_NPC) ) ) + { + gentity_t *clientLost = &g_entities[pm.checkDuelLoss-1]; + + if (clientLost && clientLost->inuse && clientLost->client && Q_irand(0, 40) > clientLost->health) + { + vec3_t attDir; + VectorSubtract(ent->client->ps.origin, clientLost->client->ps.origin, attDir); + VectorNormalize(attDir); + + VectorClear(clientLost->client->ps.velocity); + clientLost->client->ps.forceHandExtend = HANDEXTEND_NONE; + clientLost->client->ps.forceHandExtendTime = 0; + + gGAvoidDismember = 1; + G_Damage(clientLost, ent, ent, attDir, clientLost->client->ps.origin, 9999, DAMAGE_NO_PROTECTION, MOD_SABER); + + if (clientLost->health < 1) + { + gGAvoidDismember = 2; + G_CheckForDismemberment(clientLost, ent, clientLost->client->ps.origin, 999, (clientLost->client->ps.legsAnim), qfalse); + } + + gGAvoidDismember = 0; + } + else if (clientLost && clientLost->inuse && clientLost->client && + clientLost->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN && clientLost->client->ps.saberEntityNum) + { //if we didn't knock down it was a circle lock. So as punishment, make them lose their saber and go into a proper anim + saberCheckKnockdown_DuelLoss(&g_entities[clientLost->client->ps.saberEntityNum], clientLost, ent); + } + } + + pm.checkDuelLoss = 0; + } + + if (pm.cmd.generic_cmd && + (pm.cmd.generic_cmd != ent->client->lastGenCmd || ent->client->lastGenCmdTime < level.time)) + { + ent->client->lastGenCmd = pm.cmd.generic_cmd; + if (pm.cmd.generic_cmd != GENCMD_FORCE_THROW && + pm.cmd.generic_cmd != GENCMD_FORCE_PULL) + { //these are the only two where you wouldn't care about a delay between + ent->client->lastGenCmdTime = level.time + 300; //default 100ms debounce between issuing the same command. + } + + switch(pm.cmd.generic_cmd) + { + case 0: + break; + case GENCMD_SABERSWITCH: + Cmd_ToggleSaber_f(ent); + break; + case GENCMD_ENGAGE_DUEL: + if ( g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL ) + {//already in a duel, made it a taunt command + } + else + { + Cmd_EngageDuel_f(ent); + } + break; + case GENCMD_FORCE_HEAL: + ForceHeal(ent); + break; + case GENCMD_FORCE_SPEED: + ForceSpeed(ent, 0); + break; + case GENCMD_FORCE_THROW: + ForceThrow(ent, qfalse); + break; + case GENCMD_FORCE_PULL: + ForceThrow(ent, qtrue); + break; + case GENCMD_FORCE_DISTRACT: + ForceTelepathy(ent); + break; + case GENCMD_FORCE_RAGE: + ForceRage(ent); + break; + case GENCMD_FORCE_PROTECT: + ForceProtect(ent); + break; + case GENCMD_FORCE_ABSORB: + ForceAbsorb(ent); + break; + case GENCMD_FORCE_HEALOTHER: + ForceTeamHeal(ent); + break; + case GENCMD_FORCE_FORCEPOWEROTHER: + ForceTeamForceReplenish(ent); + break; + case GENCMD_FORCE_SEEING: + ForceSeeing(ent); + break; + case GENCMD_USE_SEEKER: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SEEKER)) && + G_ItemUsable(&ent->client->ps, HI_SEEKER) ) + { + ItemUse_Seeker(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_SEEKER, 0); + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SEEKER); + } + break; + case GENCMD_USE_FIELD: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SHIELD)) && + G_ItemUsable(&ent->client->ps, HI_SHIELD) ) + { + ItemUse_Shield(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_SHIELD, 0); + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SHIELD); + } + break; + case GENCMD_USE_BACTA: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC)) && + G_ItemUsable(&ent->client->ps, HI_MEDPAC) ) + { + ItemUse_MedPack(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_MEDPAC, 0); + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_MEDPAC); + } + break; + case GENCMD_USE_BACTABIG: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC_BIG)) && + G_ItemUsable(&ent->client->ps, HI_MEDPAC_BIG) ) + { + ItemUse_MedPack_Big(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_MEDPAC_BIG, 0); + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_MEDPAC_BIG); + } + break; + case GENCMD_USE_ELECTROBINOCULARS: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_BINOCULARS)) && + G_ItemUsable(&ent->client->ps, HI_BINOCULARS) ) + { + ItemUse_Binoculars(ent); + if (ent->client->ps.zoomMode == 0) + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 1); + } + else + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 2); + } + } + break; + case GENCMD_ZOOM: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_BINOCULARS)) && + G_ItemUsable(&ent->client->ps, HI_BINOCULARS) ) + { + ItemUse_Binoculars(ent); + if (ent->client->ps.zoomMode == 0) + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 1); + } + else + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 2); + } + } + break; + case GENCMD_USE_SENTRY: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SENTRY_GUN)) && + G_ItemUsable(&ent->client->ps, HI_SENTRY_GUN) ) + { + ItemUse_Sentry(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_SENTRY_GUN, 0); + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SENTRY_GUN); + } + break; + case GENCMD_USE_JETPACK: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_JETPACK)) && + G_ItemUsable(&ent->client->ps, HI_JETPACK) ) + { + ItemUse_Jetpack(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_JETPACK, 0); + /* + if (ent->client->ps.zoomMode == 0) + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 1); + } + else + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 2); + } + */ + } + break; + case GENCMD_USE_HEALTHDISP: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_HEALTHDISP)) && + G_ItemUsable(&ent->client->ps, HI_HEALTHDISP) ) + { + //ItemUse_UseDisp(ent, HI_HEALTHDISP); + G_AddEvent(ent, EV_USE_ITEM0+HI_HEALTHDISP, 0); + } + break; + case GENCMD_USE_AMMODISP: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_AMMODISP)) && + G_ItemUsable(&ent->client->ps, HI_AMMODISP) ) + { + //ItemUse_UseDisp(ent, HI_AMMODISP); + G_AddEvent(ent, EV_USE_ITEM0+HI_AMMODISP, 0); + } + break; + case GENCMD_USE_EWEB: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_EWEB)) && + G_ItemUsable(&ent->client->ps, HI_EWEB) ) + { + ItemUse_UseEWeb(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_EWEB, 0); + } + break; + case GENCMD_USE_CLOAK: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_CLOAK)) && + G_ItemUsable(&ent->client->ps, HI_CLOAK) ) + { + if ( ent->client->ps.powerups[PW_CLOAKED] ) + {//decloak + Jedi_Decloak( ent ); + } + else + {//cloak + Jedi_Cloak( ent ); + } + } + break; + case GENCMD_SABERATTACKCYCLE: + Cmd_SaberAttackCycle_f(ent); + break; + case GENCMD_TAUNT: + G_SetTauntAnim( ent, TAUNT_TAUNT ); + break; + case GENCMD_BOW: + G_SetTauntAnim( ent, TAUNT_BOW ); + break; + case GENCMD_MEDITATE: + G_SetTauntAnim( ent, TAUNT_MEDITATE ); + break; + case GENCMD_FLOURISH: + G_SetTauntAnim( ent, TAUNT_FLOURISH ); + break; + case GENCMD_GLOAT: + G_SetTauntAnim( ent, TAUNT_GLOAT ); + break; + default: + break; + } + } + + // save results of pmove + if ( ent->client->ps.eventSequence != oldEventSequence ) { + ent->eventTime = level.time; + } + if (g_smoothClients.integer) { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qfalse ); + //rww - 12-03-02 - Don't snap the origin of players! It screws prediction all up. + } + else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qfalse ); + } + + if (isNPC) + { + ent->s.eType = ET_NPC; + } + + SendPendingPredictableEvents( &ent->client->ps ); + + if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { + client->fireHeld = qfalse; // for grapple + } + + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + if (ent->s.eType != ET_NPC || + ent->s.NPC_class != CLASS_VEHICLE || + !ent->m_pVehicle || + !ent->m_pVehicle->m_iRemovedSurfaces) + { //let vehicles that are getting broken apart do their own crazy sizing stuff + VectorCopy (pm.mins, ent->r.mins); + VectorCopy (pm.maxs, ent->r.maxs); + } + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + + // execute client events + ClientEvents( ent, oldEventSequence ); + + if ( pm.useEvent ) + { + //TODO: Use +// TryUse( ent ); + } + if ((ent->client->pers.cmd.buttons & BUTTON_USE) && ent->client->ps.useDelay < level.time) + { + TryUse(ent); + ent->client->ps.useDelay = level.time + 100; + } + + // link entity now, after any personal teleporters have been used + trap_LinkEntity (ent); + if ( !ent->client->noclip ) { + G_TouchTriggers( ent ); + } + + // NOTE: now copy the exact origin over otherwise clients can be snapped into solid + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + + //test for solid areas in the AAS file +// BotTestAAS(ent->r.currentOrigin); + + // touch other objects + ClientImpacts( ent, &pm ); + + // save results of triggers and client events + if (ent->client->ps.eventSequence != oldEventSequence) { + ent->eventTime = level.time; + } + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + +// G_VehicleAttachDroidUnit( ent ); + + // Did we kick someone in our pmove sequence? + if (client->ps.forceKickFlip) + { + gentity_t *faceKicked = &g_entities[client->ps.forceKickFlip-1]; + + if (faceKicked && faceKicked->client && (!OnSameTeam(ent, faceKicked) || g_friendlyFire.integer) && + (!faceKicked->client->ps.duelInProgress || faceKicked->client->ps.duelIndex == ent->s.number) && + (!ent->client->ps.duelInProgress || ent->client->ps.duelIndex == faceKicked->s.number)) + { + if ( faceKicked && faceKicked->client && faceKicked->health && faceKicked->takedamage ) + {//push them away and do pain + vec3_t oppDir; + int strength = (int)VectorNormalize2( client->ps.velocity, oppDir ); + + strength *= 0.05; + + VectorScale( oppDir, -1, oppDir ); + + G_Damage( faceKicked, ent, ent, oppDir, client->ps.origin, strength, DAMAGE_NO_ARMOR, MOD_MELEE ); + + if ( faceKicked->client->ps.weapon != WP_SABER || + faceKicked->client->ps.fd.saberAnimLevel != FORCE_LEVEL_3 || + (!BG_SaberInAttack(faceKicked->client->ps.saberMove) && !PM_SaberInStart(faceKicked->client->ps.saberMove) && !PM_SaberInReturn(faceKicked->client->ps.saberMove) && !PM_SaberInTransition(faceKicked->client->ps.saberMove)) ) + { + if (faceKicked->health > 0 && + faceKicked->client->ps.stats[STAT_HEALTH] > 0 && + faceKicked->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN) + { + if (BG_KnockDownable(&faceKicked->client->ps) && Q_irand(1, 10) <= 3) + { //only actually knock over sometimes, but always do velocity hit + faceKicked->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + faceKicked->client->ps.forceHandExtendTime = level.time + 1100; + faceKicked->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim + } + + faceKicked->client->ps.otherKiller = ent->s.number; + faceKicked->client->ps.otherKillerTime = level.time + 5000; + faceKicked->client->ps.otherKillerDebounceTime = level.time + 100; + + faceKicked->client->ps.velocity[0] = oppDir[0]*(strength*40); + faceKicked->client->ps.velocity[1] = oppDir[1]*(strength*40); + faceKicked->client->ps.velocity[2] = 200; + } + } + + G_Sound( faceKicked, CHAN_AUTO, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) ); + } + } + + client->ps.forceKickFlip = 0; + } + + // check for respawning + if ( client->ps.stats[STAT_HEALTH] <= 0 + && !(client->ps.eFlags2&EF2_HELD_BY_MONSTER)//can't respawn while being eaten + && ent->s.eType != ET_NPC ) { + // wait for the attack button to be pressed + if ( level.time > client->respawnTime && !gDoSlowMoDuel ) { + // forcerespawn is to prevent users from waiting out powerups + int forceRes = g_forcerespawn.integer; + + if (g_gametype.integer == GT_POWERDUEL) + { + forceRes = 1; + } + else if (g_gametype.integer == GT_SIEGE && + g_siegeRespawn.integer) + { //wave respawning on + forceRes = 1; + } + + if ( forceRes > 0 && + ( level.time - client->respawnTime ) > forceRes * 1000 ) { + respawn( ent ); + return; + } + + // pressing attack or use is the normal respawn method + if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { + respawn( ent ); + } + } + else if (gDoSlowMoDuel) + { + client->respawnTime = level.time + 1000; + } + return; + } + + // perform once-a-second actions + ClientTimerActions( ent, msec ); + + G_UpdateClientBroadcasts ( ent ); + + //try some idle anims on ent if getting no input and not moving for some time + G_CheckClientIdle( ent, ucmd ); + + // This code was moved here from clientThink to fix a problem with g_synchronousClients + // being set to 1 when in vehicles. + if ( ent->s.number < MAX_CLIENTS && ent->client->ps.m_iVehicleNum ) + {//driving a vehicle + //run it + if (g_entities[ent->client->ps.m_iVehicleNum].inuse && g_entities[ent->client->ps.m_iVehicleNum].client) + { + ClientThink(ent->client->ps.m_iVehicleNum, &g_entities[ent->client->ps.m_iVehicleNum].m_pVehicle->m_ucmd); + } + else + { //vehicle no longer valid? + ent->client->ps.m_iVehicleNum = 0; + } + } + + extern void FF_XboxSaberRumble( void ); + if(ent->s.number == ClientManager::ActiveClientNum()) // player + { + if(ent->client->ps.saberLockTime > level.time) + { + FF_XboxSaberRumble(); + } + } + +} + +/* +================== +G_CheckClientTimeouts + +Checks whether a client has exceded any timeouts and act accordingly +================== +*/ +void G_CheckClientTimeouts ( gentity_t *ent ) +{ + // Only timeout supported right now is the timeout to spectator mode + if ( !g_timeouttospec.integer ) + { + return; + } + + // Already a spectator, no need to boot them to spectator + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + { + return; + } + + // See how long its been since a command was received by the client and if its + // longer than the timeout to spectator then force this client into spectator mode + if ( level.time - ent->client->pers.cmd.serverTime > g_timeouttospec.integer * 1000 ) + { + SetTeam ( ent, "spectator" ); + } +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +void ClientThink( int clientNum,usercmd_t *ucmd ) { + gentity_t *ent; + + ent = g_entities + clientNum; + if (clientNum < MAX_CLIENTS) + { + trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); + } + + // mark the time we got info, so we can display the + // phone jack if they don't get any for a while + ent->client->lastCmdTime = level.time; + + if (ucmd) + { + ent->client->pers.cmd = *ucmd; + } + +/* This was moved to clientthink_real, but since its sort of a risky change i left it here for + now as a more concrete reference - BSD + + if ( clientNum < MAX_CLIENTS + && ent->client->ps.m_iVehicleNum ) + {//driving a vehicle + if (g_entities[ent->client->ps.m_iVehicleNum].client) + { + gentity_t *veh = &g_entities[ent->client->ps.m_iVehicleNum]; + + if (veh->m_pVehicle && + veh->m_pVehicle->m_pPilot == (bgEntity_t *)ent) + { //only take input from the pilot... + veh->client->ps.commandTime = ent->client->ps.commandTime; + memcpy(&veh->m_pVehicle->m_ucmd, &ent->client->pers.cmd, sizeof(usercmd_t)); + if ( veh->m_pVehicle->m_ucmd.buttons & BUTTON_TALK ) + { //forced input if "chat bubble" is up + veh->m_pVehicle->m_ucmd.buttons = BUTTON_TALK; + veh->m_pVehicle->m_ucmd.forwardmove = 0; + veh->m_pVehicle->m_ucmd.rightmove = 0; + veh->m_pVehicle->m_ucmd.upmove = 0; + } + } + } + } +*/ + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { + ClientThink_real( ent ); + } + // vehicles are clients and when running synchronous they still need to think here + // so special case them. + else if ( clientNum >= MAX_CLIENTS ) { + ClientThink_real( ent ); + } + +/* This was moved to clientthink_real, but since its sort of a risky change i left it here for + now as a more concrete reference - BSD + + if ( clientNum < MAX_CLIENTS + && ent->client->ps.m_iVehicleNum ) + {//driving a vehicle + //run it + if (g_entities[ent->client->ps.m_iVehicleNum].inuse && + g_entities[ent->client->ps.m_iVehicleNum].client) + { + ClientThink(ent->client->ps.m_iVehicleNum, &g_entities[ent->client->ps.m_iVehicleNum].m_pVehicle->m_ucmd); + } + else + { //vehicle no longer valid? + ent->client->ps.m_iVehicleNum = 0; + } + } +*/ +} + + +void G_RunClient( gentity_t *ent ) { + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { + return; + } + ent->client->pers.cmd.serverTime = level.time; + ClientThink_real( ent ); +} + + +/* +================== +SpectatorClientEndFrame + +================== +*/ +void SpectatorClientEndFrame( gentity_t *ent ) { + gclient_t *cl; + + if (ent->s.eType == ET_NPC) + { + assert(0); + return; + } + + // if we are doing a chase cam or a remote view, grab the latest info + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { + int clientNum;//, flags; + + clientNum = ent->client->sess.spectatorClient; + + // team follow1 and team follow2 go to whatever clients are playing + if ( clientNum == -1 ) { + clientNum = level.follow1; + } else if ( clientNum == -2 ) { + clientNum = level.follow2; + } + if ( clientNum >= 0 ) { + cl = &level.clients[ clientNum ]; + if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { + //flags = (cl->mGameFlags & ~(PSG_VOTED | PSG_TEAMVOTED)) | (ent->client->mGameFlags & (PSG_VOTED | PSG_TEAMVOTED)); + //ent->client->mGameFlags = flags; + ent->client->ps.eFlags = cl->ps.eFlags; + ent->client->ps = cl->ps; + ent->client->ps.pm_flags |= PMF_FOLLOW; + return; + } else { + // drop them to free spectators unless they are dedicated camera followers + if ( ent->client->sess.spectatorClient >= 0 ) { + ent->client->sess.spectatorState = SPECTATOR_FREE; + ClientBegin( ent->client - level.clients, qtrue ); + } + } + } + } + + if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + ent->client->ps.pm_flags |= PMF_SCOREBOARD; + } else { + ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; + } +} + +/* +============== +ClientEndFrame + +Called at the end of each server frame for each connected client +A fast client will have multiple ClientThink for each ClientEdFrame, +while a slow client may have multiple ClientEndFrame between ClientThink. +============== +*/ +void ClientEndFrame( gentity_t *ent ) { + int i; + clientPersistant_t *pers; + qboolean isNPC = qfalse; + + if (ent->s.eType == ET_NPC) + { + isNPC = qtrue; + } + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + SpectatorClientEndFrame( ent ); + return; + } + + pers = &ent->client->pers; + + // turn off any expired powerups + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ent->client->ps.powerups[ i ] < level.time ) { + ent->client->ps.powerups[ i ] = 0; + } + } + + // save network bandwidth +#if 0 + if ( !g_synchronousClients->integer && (ent->client->ps.pm_type == PM_NORMAL || ent->client->ps.pm_type == PM_JETPACK || ent->client->ps.pm_type == PM_FLOAT) ) { + // FIXME: this must change eventually for non-sync demo recording + VectorClear( ent->client->ps.viewangles ); + } +#endif + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if ( level.intermissiontime ) { + return; + } + + // burn from lava, etc + P_WorldEffects (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // add the EF_CONNECTION flag if we haven't gotten commands recently + if ( level.time - ent->client->lastCmdTime > 1000 ) { + ent->s.eFlags |= EF_CONNECTION; + } else { + ent->s.eFlags &= ~EF_CONNECTION; + } + + ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... + + G_SetClientSound (ent); + + // set the latest infor + if (g_smoothClients.integer) { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qfalse ); + //rww - 12-03-02 - Don't snap the origin of players! It screws prediction all up. + } + else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qfalse ); + } + + if (isNPC) + { + ent->s.eType = ET_NPC; + } + + SendPendingPredictableEvents( &ent->client->ps ); + + // set the bit for the reachability area the client is currently in +// i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin ); +// ent->client->areabits[i >> 3] |= 1 << (i & 7); +} + + diff --git a/codemp/game/g_arenas.c b/codemp/game/g_arenas.c new file mode 100644 index 0000000..8d9d187 --- /dev/null +++ b/codemp/game/g_arenas.c @@ -0,0 +1,343 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// +// g_arenas.c +// + +#include "g_local.h" + + +gentity_t *podium1; +gentity_t *podium2; +gentity_t *podium3; + + +/* +================== +UpdateTournamentInfo +================== +*/ +void UpdateTournamentInfo( void ) { + int i; + gentity_t *player; + int playerClientNum; + int n, accuracy, perfect, msglen; + int buflen; + int score1, score2; + qboolean won; + char buf[32]; + char msg[MAX_STRING_CHARS]; + + // find the real player + player = NULL; + for (i = 0; i < level.maxclients; i++ ) { + player = &g_entities[i]; + if ( !player->inuse ) { + continue; + } + if ( !( player->r.svFlags & SVF_BOT ) ) { + break; + } + } + // this should never happen! + if ( !player || i == level.maxclients ) { + return; + } + playerClientNum = i; + + CalculateRanks(); + + if ( level.clients[playerClientNum].sess.sessionTeam == TEAM_SPECTATOR ) { + Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); + } + else { + if( player->client->accuracy_shots ) { + accuracy = player->client->accuracy_hits * 100 / player->client->accuracy_shots; + } + else { + accuracy = 0; + } + won = qfalse; + if (g_gametype.integer >= GT_CTF) { + score1 = level.teamScores[TEAM_RED]; + score2 = level.teamScores[TEAM_BLUE]; + if (level.clients[playerClientNum].sess.sessionTeam == TEAM_RED) { + won = (level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE]); + } else { + won = (level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED]); + } + } else { + if (&level.clients[playerClientNum] == &level.clients[ level.sortedClients[0] ]) { + won = qtrue; + score1 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; + score2 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; + } else { + score2 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; + score1 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; + } + } + if (won && player->client->ps.persistant[PERS_KILLED] == 0) { + perfect = 1; + } else { + perfect = 0; + } + Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, + player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT],player->client->ps.persistant[PERS_DEFEND_COUNT], + player->client->ps.persistant[PERS_ASSIST_COUNT], player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], + perfect, score1, score2, level.time, player->client->ps.persistant[PERS_CAPTURES] ); + } + + msglen = strlen( msg ); + for( i = 0; i < level.numNonSpectatorClients; i++ ) { + n = level.sortedClients[i]; + Com_sprintf( buf, sizeof(buf), " %i %i %i", n, level.clients[n].ps.persistant[PERS_RANK], level.clients[n].ps.persistant[PERS_SCORE] ); + buflen = strlen( buf ); + if( msglen + buflen + 1 >= sizeof(msg) ) { + break; + } + strcat( msg, buf ); + } + trap_SendConsoleCommand( EXEC_APPEND, msg ); +} + + +/* +static gentity_t *SpawnModelOnVictoryPad( gentity_t *pad, vec3_t offset, gentity_t *ent, int place ) { + gentity_t *body; + vec3_t vec; + vec3_t f, r, u; + + body = G_Spawn(); + if ( !body ) { + G_Printf( S_COLOR_RED "ERROR: out of gentities\n" ); + return NULL; + } + + body->classname = ent->client->pers.netname; + body->client = ent->client; + body->s = ent->s; + body->s.eType = ET_PLAYER; // could be ET_INVISIBLE + body->s.eFlags = 0; // clear EF_TALK, etc + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + body->s.event = 0; + body->s.pos.trType = TR_STATIONARY; + body->s.groundEntityNum = ENTITYNUM_WORLD; + body->s.legsAnim = WeaponReadyAnim[ent->s.weapon]; + body->s.torsoAnim = WeaponReadyAnim[ent->s.weapon]; + if( body->s.weapon == WP_NONE ) { + body->s.weapon = WP_BRYAR_PISTOL; + } + if( body->s.weapon == WP_SABER) { + body->s.torsoAnim = BOTH_STAND2; + } + body->s.event = 0; + body->r.svFlags = ent->r.svFlags; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = CONTENTS_BODY; + body->r.ownerNum = ent->r.ownerNum; + body->takedamage = qfalse; + + VectorSubtract( level.intermission_origin, pad->r.currentOrigin, vec ); + vectoangles( vec, body->s.apos.trBase ); + body->s.apos.trBase[PITCH] = 0; + body->s.apos.trBase[ROLL] = 0; + + AngleVectors( body->s.apos.trBase, f, r, u ); + VectorMA( pad->r.currentOrigin, offset[0], f, vec ); + VectorMA( vec, offset[1], r, vec ); + VectorMA( vec, offset[2], u, vec ); + + G_SetOrigin( body, vec ); + + trap_LinkEntity (body); + + body->count = place; + + return body; +} + + +static void CelebrateStop( gentity_t *player ) { + int anim; + + if( player->s.weapon == WP_SABER) { + anim = BOTH_STAND2; + } + else { + anim = WeaponReadyAnim[player->s.weapon]; + } + player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; +} + + +#define TIMER_GESTURE (34*66+50) +static void CelebrateStart( gentity_t *player ) { + player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | BOTH_TALKGESTURE1; + player->nextthink = level.time + TIMER_GESTURE; + player->think = CelebrateStop; + + +// player->client->ps.events[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = EV_TAUNT; +// player->client->ps.eventParms[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = 0; +// player->client->ps.eventSequence++; + + G_AddEvent(player, EV_TAUNT, 0); +} + + +static vec3_t offsetFirst = {0, 0, 74}; +static vec3_t offsetSecond = {-10, 60, 54}; +static vec3_t offsetThird = {-19, -60, 45}; + +static void PodiumPlacementThink( gentity_t *podium ) { + vec3_t vec; + vec3_t origin; + vec3_t f, r, u; + + podium->nextthink = level.time + 100; + + AngleVectors( level.intermission_angle, vec, NULL, NULL ); + VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); + origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); + G_SetOrigin( podium, origin ); + + if( podium1 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium1->s.apos.trBase ); + podium1->s.apos.trBase[PITCH] = 0; + podium1->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium1->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetFirst[0], f, vec ); + VectorMA( vec, offsetFirst[1], r, vec ); + VectorMA( vec, offsetFirst[2], u, vec ); + + G_SetOrigin( podium1, vec ); + } + + if( podium2 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium2->s.apos.trBase ); + podium2->s.apos.trBase[PITCH] = 0; + podium2->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium2->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetSecond[0], f, vec ); + VectorMA( vec, offsetSecond[1], r, vec ); + VectorMA( vec, offsetSecond[2], u, vec ); + + G_SetOrigin( podium2, vec ); + } + + if( podium3 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium3->s.apos.trBase ); + podium3->s.apos.trBase[PITCH] = 0; + podium3->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium3->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetThird[0], f, vec ); + VectorMA( vec, offsetThird[1], r, vec ); + VectorMA( vec, offsetThird[2], u, vec ); + + G_SetOrigin( podium3, vec ); + } +} + + +static gentity_t *SpawnPodium( void ) { + gentity_t *podium; + vec3_t vec; + vec3_t origin; + + podium = G_Spawn(); + if ( !podium ) { + return NULL; + } + + podium->classname = "podium"; + podium->s.eType = ET_GENERAL; + podium->s.number = podium - g_entities; + podium->clipmask = CONTENTS_SOLID; + podium->r.contents = CONTENTS_SOLID; + podium->s.modelindex = G_ModelIndex( SP_PODIUM_MODEL ); + + AngleVectors( level.intermission_angle, vec, NULL, NULL ); + VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); + origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); + G_SetOrigin( podium, origin ); + + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + podium->s.apos.trBase[YAW] = vectoyaw( vec ); + trap_LinkEntity (podium); + + podium->think = PodiumPlacementThink; + podium->nextthink = level.time + 100; + return podium; +} + + + +//================== +//SpawnModelsOnVictoryPads +//================== + +void SpawnModelsOnVictoryPads( void ) { + gentity_t *player; + gentity_t *podium; + + podium1 = NULL; + podium2 = NULL; + podium3 = NULL; + + podium = SpawnPodium(); + + player = SpawnModelOnVictoryPad( podium, offsetFirst, &g_entities[level.sortedClients[0]], + level.clients[ level.sortedClients[0] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + player->nextthink = level.time + 2000; + player->think = CelebrateStart; + podium1 = player; + } + + player = SpawnModelOnVictoryPad( podium, offsetSecond, &g_entities[level.sortedClients[1]], + level.clients[ level.sortedClients[1] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + podium2 = player; + } + + if ( level.numNonSpectatorClients > 2 ) { + player = SpawnModelOnVictoryPad( podium, offsetThird, &g_entities[level.sortedClients[2]], + level.clients[ level.sortedClients[2] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + podium3 = player; + } + } +} + + + +//=============== +//Svcmd_AbortPodium_f +//=============== + +void Svcmd_AbortPodium_f( void ) { + if( g_gametype.integer != GT_SINGLE_PLAYER ) { + return; + } + + if( podium1 ) { + podium1->nextthink = level.time; + podium1->think = CelebrateStop; + } +} +*/ \ No newline at end of file diff --git a/codemp/game/g_bot.c b/codemp/game/g_bot.c new file mode 100644 index 0000000..8a431fa --- /dev/null +++ b/codemp/game/g_bot.c @@ -0,0 +1,1316 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// g_bot.c + +#include "g_local.h" + + +static int g_numBots; +static char *g_botInfos[MAX_BOTS]; + + +int g_numArenas; +static char *g_arenaInfos[MAX_ARENAS]; + + +#define BOT_BEGIN_DELAY_BASE 2000 +#define BOT_BEGIN_DELAY_INCREMENT 1500 + +#define BOT_SPAWN_QUEUE_DEPTH 16 + +typedef struct { + int clientNum; + int spawnTime; +} botSpawnQueue_t; + +//static int botBeginDelay = 0; // bk001206 - unused, init +static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; + +vmCvar_t bot_minplayers; + +extern gentity_t *podium1; +extern gentity_t *podium2; +extern gentity_t *podium3; + +#include "../namespace_begin.h" +float trap_Cvar_VariableValue( const char *var_name ) { + char buf[128]; + + trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); + return atof(buf); +} +#include "../namespace_end.h" + + +/* +=============== +G_ParseInfos +=============== +*/ +int G_ParseInfos( char *buf, int max, char *infos[] ) { + char *token; + int count; + char key[MAX_TOKEN_CHARS]; + char info[MAX_INFO_STRING]; + + count = 0; + + while ( 1 ) { + token = COM_Parse( (const char **)(&buf) ); + if ( !token[0] ) { + break; + } + if ( strcmp( token, "{" ) ) { + Com_Printf( "Missing { in info file\n" ); + break; + } + + if ( count == max ) { + Com_Printf( "Max infos exceeded\n" ); + break; + } + + info[0] = '\0'; + while ( 1 ) { + token = COM_ParseExt( (const char **)(&buf), qtrue ); + if ( !token[0] ) { + Com_Printf( "Unexpected end of info file\n" ); + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + Q_strncpyz( key, token, sizeof( key ) ); + + token = COM_ParseExt( (const char **)(&buf), qfalse ); + if ( !token[0] ) { + strcpy( token, "" ); + } + Info_SetValueForKey( info, key, token ); + } + //NOTE: extra space for arena number + infos[count] = (char *) G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); + if (infos[count]) { + strcpy(infos[count], info); + count++; + } + } + return count; +} + +/* +=============== +G_LoadArenasFromFile +=============== +*/ +static void G_LoadArenasFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_ARENAS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_ARENAS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] ); +} + +int G_GetMapTypeBits(char *type) +{ + int typeBits = 0; + + if( *type ) { + if( strstr( type, "ffa" ) ) { + typeBits |= (1 << GT_FFA); + typeBits |= (1 << GT_TEAM); + } + if( strstr( type, "holocron" ) ) { + typeBits |= (1 << GT_HOLOCRON); + } + if( strstr( type, "jedimaster" ) ) { + typeBits |= (1 << GT_JEDIMASTER); + } + if( strstr( type, "duel" ) ) { + typeBits |= (1 << GT_DUEL); + typeBits |= (1 << GT_POWERDUEL); + } + if( strstr( type, "powerduel" ) ) { + typeBits |= (1 << GT_DUEL); + typeBits |= (1 << GT_POWERDUEL); + } + if( strstr( type, "siege" ) ) { + typeBits |= (1 << GT_SIEGE); + } + if( strstr( type, "ctf" ) ) { + typeBits |= (1 << GT_CTF); + } + if( strstr( type, "cty" ) ) { + typeBits |= (1 << GT_CTY); + } + } else { + typeBits |= (1 << GT_FFA); + } + + return typeBits; +} + +qboolean G_DoesMapSupportGametype(const char *mapname, int gametype) +{ + int typeBits = 0; + int thisLevel = -1; + int n = 0; + char *type = NULL; + + if (!g_arenaInfos[0]) + { + return qfalse; + } + + if (!mapname || !mapname[0]) + { + return qfalse; + } + + for( n = 0; n < g_numArenas; n++ ) + { + type = Info_ValueForKey( g_arenaInfos[n], "map" ); + + if (Q_stricmp(mapname, type) == 0) + { + thisLevel = n; + break; + } + } + + if (thisLevel == -1) + { + return qfalse; + } + + type = Info_ValueForKey(g_arenaInfos[thisLevel], "type"); + + typeBits = G_GetMapTypeBits(type); + if (typeBits & (1 << gametype)) + { //the map in question supports the gametype in question, so.. + return qtrue; + } + + return qfalse; +} + +//rww - auto-obtain nextmap. I could've sworn Q3 had something like this, but I guess not. +const char *G_RefreshNextMap(int gametype, qboolean forced) +{ + int typeBits = 0; + int thisLevel = 0; + int desiredMap = 0; + int n = 0; + char *type = NULL; + qboolean loopingUp = qfalse; + vmCvar_t mapname; + + if (!g_autoMapCycle.integer && !forced) + { + return NULL; + } + + if (!g_arenaInfos[0]) + { + return NULL; + } + + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + for( n = 0; n < g_numArenas; n++ ) + { + type = Info_ValueForKey( g_arenaInfos[n], "map" ); + + if (Q_stricmp(mapname.string, type) == 0) + { + thisLevel = n; + break; + } + } + + desiredMap = thisLevel; + + n = thisLevel+1; + while (n != thisLevel) + { //now cycle through the arena list and find the next map that matches the gametype we're in + if (!g_arenaInfos[n] || n >= g_numArenas) + { + if (loopingUp) + { //this shouldn't happen, but if it does we have a null entry break in the arena file + //if this is the case just break out of the loop instead of sticking in an infinite loop + break; + } + n = 0; + loopingUp = qtrue; + } + + type = Info_ValueForKey(g_arenaInfos[n], "type"); + + typeBits = G_GetMapTypeBits(type); + if (typeBits & (1 << gametype)) + { + desiredMap = n; + break; + } + + n++; + } + + if (desiredMap == thisLevel) + { //If this is the only level for this game mode or we just can't find a map for this game mode, then nextmap + //will always restart. + trap_Cvar_Set( "nextmap", "map_restart 0"); + } + else + { //otherwise we have a valid nextmap to cycle to, so use it. + type = Info_ValueForKey( g_arenaInfos[desiredMap], "map" ); + trap_Cvar_Set( "nextmap", va("map %s", type)); + } + + return Info_ValueForKey( g_arenaInfos[desiredMap], "map" ); +} + +/* +=============== +G_LoadArenas +=============== +*/ +static void G_LoadArenas( void ) { + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i, n; + int dirlen; + + g_numArenas = 0; + + // get all arenas from .arena files + numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadArenasFromFile(filename); + } +// trap_Printf( va( "%i arenas parsed\n", g_numArenas ) ); + + for( n = 0; n < g_numArenas; n++ ) { + Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) ); + } + + G_RefreshNextMap(g_gametype.integer, qfalse); +} + + +/* +=============== +G_GetArenaInfoByNumber +=============== +*/ +const char *G_GetArenaInfoByMap( const char *map ) { + int n; + + for( n = 0; n < g_numArenas; n++ ) { + if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) { + return g_arenaInfos[n]; + } + } + + return NULL; +} + +#if 0 +/* +================= +PlayerIntroSound +================= +*/ +static void PlayerIntroSound( const char *modelAndSkin ) { + char model[MAX_QPATH]; + char *skin; + + Q_strncpyz( model, modelAndSkin, sizeof(model) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } + else { + skin = model; + } + + if( Q_stricmp( skin, "default" ) == 0 ) { + skin = model; + } + + trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); +} +#endif + +/* +=============== +G_AddRandomBot +=============== +*/ +void G_AddRandomBot( int team ) { + int i, n, num; + float skill; + char *value, netname[36], *teamstr; + gclient_t *cl; + + num = 0; + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if (g_gametype.integer == GT_SIEGE) + { + if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) { + continue; + } + } + else + { + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if (i >= g_maxclients.integer) { + num++; + } + } + num = random() * num; + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if (g_gametype.integer == GT_SIEGE) + { + if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) { + continue; + } + } + else + { + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if (i >= g_maxclients.integer) { + num--; + if (num <= 0) { + skill = trap_Cvar_VariableValue( "g_spSkill" ); + if (team == TEAM_RED) teamstr = "red"; + else if (team == TEAM_BLUE) teamstr = "blue"; + else teamstr = ""; + strncpy(netname, value, sizeof(netname)-1); + netname[sizeof(netname)-1] = '\0'; + Q_CleanStr(netname); + trap_SendConsoleCommand( EXEC_INSERT, va("addbot \"%s\" %f %s %i\n", netname, skill, teamstr, 0) ); + return; + } + } + } +} + +/* +=============== +G_RemoveRandomBot +=============== +*/ +int G_RemoveRandomBot( int team ) { + int i; + char netname[36]; + gclient_t *cl; + + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[i /*cl->ps.clientNum*/].r.svFlags & SVF_BOT) ) { + continue; + } + if (g_gametype.integer == GT_SIEGE) + { + if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) { + continue; + } + } + else + { + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + } + strcpy(netname, cl->pers.netname); + Q_CleanStr(netname); + trap_SendConsoleCommand( EXEC_INSERT, va("kick \"%s\"\n", netname) ); + return qtrue; + } + return qfalse; +} + +/* +=============== +G_CountHumanPlayers +=============== +*/ +int G_CountHumanPlayers( int team ) { + int i, num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + num++; + } + return num; +} + +/* +=============== +G_CountBotPlayers +=============== +*/ +int G_CountBotPlayers( int team ) { + int i, n, num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if (g_gametype.integer == GT_SIEGE) + { + if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) { + continue; + } + } + else + { + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + } + num++; + } + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + num++; + } + return num; +} + +/* +=============== +G_CheckMinimumPlayers +=============== +*/ +int checkminimumplayers_time = 0; + +void G_CheckMinimumPlayers( void ) { + int minplayers; + int humanplayers, botplayers; + + if (g_gametype.integer == GT_SIEGE) + { + return; + } + + if (level.intermissiontime) return; + //only check once each 10 seconds + if (checkminimumplayers_time > level.time - 10000) { + return; + } + checkminimumplayers_time = level.time; + trap_Cvar_Update(&bot_minplayers); + minplayers = bot_minplayers.integer; + if (minplayers <= 0) return; + + if (minplayers > g_maxclients.integer) + { + minplayers = g_maxclients.integer; + } + + humanplayers = G_CountHumanPlayers( -1 ); + botplayers = G_CountBotPlayers( -1 ); + + //humanplayers will be zero at the very beginning of the map. Pretend + //its always at least one so we don't get a bot who disappears as soon + //as the player spawns. + if(humanplayers < 1) { + humanplayers = 1; + } + + if ((humanplayers+botplayers) < minplayers) + { + G_AddRandomBot(-1); + } + else if ((humanplayers+botplayers) > minplayers && botplayers) + { + // try to remove spectators first + if (!G_RemoveRandomBot(TEAM_SPECTATOR)) + { + // just remove the bot that is playing + G_RemoveRandomBot(-1); + } + } + + /* + if (g_gametype.integer >= GT_TEAM) { + int humanplayers2, botplayers2; + if (minplayers >= g_maxclients.integer / 2) { + minplayers = (g_maxclients.integer / 2) -1; + } + + humanplayers = G_CountHumanPlayers( TEAM_RED ); + botplayers = G_CountBotPlayers( TEAM_RED ); + humanplayers2 = G_CountHumanPlayers( TEAM_BLUE ); + botplayers2 = G_CountBotPlayers( TEAM_BLUE ); + // + if ((humanplayers+botplayers+humanplayers2+botplayers) < minplayers) + { + if ((humanplayers+botplayers) < (humanplayers2+botplayers2)) + { + G_AddRandomBot( TEAM_RED ); + } + else + { + G_AddRandomBot( TEAM_BLUE ); + } + } + else if ((humanplayers+botplayers+humanplayers2+botplayers) > minplayers && botplayers) + { + if ((humanplayers+botplayers) < (humanplayers2+botplayers2)) + { + G_RemoveRandomBot( TEAM_BLUE ); + } + else + { + G_RemoveRandomBot( TEAM_RED ); + } + } + } + else if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) { + if (minplayers >= g_maxclients.integer) { + minplayers = g_maxclients.integer-1; + } + humanplayers = G_CountHumanPlayers( -1 ); + botplayers = G_CountBotPlayers( -1 ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + // try to remove spectators first + if (!G_RemoveRandomBot( TEAM_SPECTATOR )) { + // just remove the bot that is playing + G_RemoveRandomBot( -1 ); + } + } + } + else if (g_gametype.integer == GT_FFA) { + if (minplayers >= g_maxclients.integer) { + minplayers = g_maxclients.integer-1; + } + humanplayers = G_CountHumanPlayers( TEAM_FREE ); + botplayers = G_CountBotPlayers( TEAM_FREE ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_FREE ); + } + } + else if (g_gametype.integer == GT_HOLOCRON || g_gametype.integer == GT_JEDIMASTER) { + if (minplayers >= g_maxclients.integer) { + minplayers = g_maxclients.integer-1; + } + humanplayers = G_CountHumanPlayers( TEAM_FREE ); + botplayers = G_CountBotPlayers( TEAM_FREE ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_FREE ); + } + } + */ +} + +/* +=============== +G_CheckBotSpawn +=============== +*/ +void G_CheckBotSpawn( void ) { + int n; + + G_CheckMinimumPlayers(); + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + ClientBegin( botSpawnQueue[n].clientNum, qfalse ); + botSpawnQueue[n].spawnTime = 0; + + /* + if( g_gametype.integer == GT_SINGLE_PLAYER ) { + trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) ); + PlayerIntroSound( Info_ValueForKey (userinfo, "model") ); + } + */ + } +} + + +/* +=============== +AddBotToSpawnQueue +=============== +*/ +static void AddBotToSpawnQueue( int clientNum, int delay ) { + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + botSpawnQueue[n].spawnTime = level.time + delay; + botSpawnQueue[n].clientNum = clientNum; + return; + } + } + + G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" ); + ClientBegin( clientNum, qfalse ); +} + + +/* +=============== +G_RemoveQueuedBotBegin + +Called on client disconnect to make sure the delayed spawn +doesn't happen on a freed index +=============== +*/ +void G_RemoveQueuedBotBegin( int clientNum ) { + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( botSpawnQueue[n].clientNum == clientNum ) { + botSpawnQueue[n].spawnTime = 0; + return; + } + } +} + + +/* +=============== +G_BotConnect +=============== +*/ +qboolean G_BotConnect( int clientNum, qboolean restart ) { + bot_settings_t settings; + char userinfo[MAX_INFO_STRING]; + + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + + Q_strncpyz( settings.personalityfile, Info_ValueForKey( userinfo, "personality" ), sizeof(settings.personalityfile) ); + settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); + Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); + + if (!BotAISetupClient( clientNum, &settings, restart )) { + trap_DropClient( clientNum, "BotAISetupClient failed" ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +G_AddBot +=============== +*/ +static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) { + int clientNum; + char *botinfo; + gentity_t *bot; + char *key; + char *s; + char *botname; + char *model; +// char *headmodel; + char userinfo[MAX_INFO_STRING]; + int preTeam = 0; + + // get the botinfo from bots.txt + botinfo = G_GetBotInfoByName( name ); + if ( !botinfo ) { + G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); + return; + } + + // create the bot's userinfo + userinfo[0] = '\0'; + + botname = Info_ValueForKey( botinfo, "funname" ); + if( !botname[0] ) { + botname = Info_ValueForKey( botinfo, "name" ); + } + // check for an alternative name + if (altname && altname[0]) { + botname = altname; + } + Info_SetValueForKey( userinfo, "name", botname ); + Info_SetValueForKey( userinfo, "rate", "25000" ); + Info_SetValueForKey( userinfo, "snaps", "20" ); + Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); + + if ( skill >= 1 && skill < 2 ) { + Info_SetValueForKey( userinfo, "handicap", "50" ); + } + else if ( skill >= 2 && skill < 3 ) { + Info_SetValueForKey( userinfo, "handicap", "70" ); + } + else if ( skill >= 3 && skill < 4 ) { + Info_SetValueForKey( userinfo, "handicap", "90" ); + } + + key = "model"; + model = Info_ValueForKey( botinfo, key ); + if ( !*model ) { + model = "kyle/default"; + } + Info_SetValueForKey( userinfo, key, model ); + +/* key = "headmodel"; + headmodel = Info_ValueForKey( botinfo, key ); + if ( !*headmodel ) { + headmodel = model; + } + Info_SetValueForKey( userinfo, key, headmodel ); + key = "team_headmodel"; + Info_SetValueForKey( userinfo, key, headmodel ); +*/ + key = "gender"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "male"; + } + Info_SetValueForKey( userinfo, "sex", s ); + + key = "color1"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "4"; + } + Info_SetValueForKey( userinfo, key, s ); + + key = "color2"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "4"; + } + Info_SetValueForKey( userinfo, key, s ); + + key = "saber1"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "single_1"; + } + Info_SetValueForKey( userinfo, key, s ); + + key = "saber2"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "none"; + } + Info_SetValueForKey( userinfo, key, s ); + + s = Info_ValueForKey(botinfo, "personality"); + if (!*s ) + { + Info_SetValueForKey( userinfo, "personality", "botfiles/default.jkb" ); + } + else + { + Info_SetValueForKey( userinfo, "personality", s ); + } + + // have the server allocate a client slot + clientNum = trap_BotAllocateClient(); + if ( clientNum == -1 ) { +// G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); +// G_Printf( S_COLOR_RED "Start server with more 'open' slots.\n" ); + trap_SendServerCommand( -1, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "UNABLE_TO_ADD_BOT"))); + return; + } + + // initialize the bot settings + if( !team || !*team ) { + if( g_gametype.integer >= GT_TEAM ) { + if( PickTeam(clientNum) == TEAM_RED) { + team = "red"; + } + else { + team = "blue"; + } + } + else { + team = "red"; + } + } +// Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) ); + Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) ); + Info_SetValueForKey( userinfo, "team", team ); + + bot = &g_entities[ clientNum ]; + bot->r.svFlags |= SVF_BOT; + bot->inuse = qtrue; + + // register the userinfo + trap_SetUserinfo( clientNum, userinfo ); + + if (g_gametype.integer >= GT_TEAM) + { + if (team && Q_stricmp(team, "red") == 0) + { + bot->client->sess.sessionTeam = TEAM_RED; + } + else if (team && Q_stricmp(team, "blue") == 0) + { + bot->client->sess.sessionTeam = TEAM_BLUE; + } + else + { + bot->client->sess.sessionTeam = PickTeam( -1 ); + } + } + + if (g_gametype.integer == GT_SIEGE) + { + bot->client->sess.siegeDesiredTeam = bot->client->sess.sessionTeam; + bot->client->sess.sessionTeam = TEAM_SPECTATOR; + } + + preTeam = bot->client->sess.sessionTeam; + + // have it connect to the game as a normal client + if ( ClientConnect( clientNum, qtrue, qtrue ) ) { + return; + } + + if (bot->client->sess.sessionTeam != preTeam) + { + trap_GetUserinfo(clientNum, userinfo, MAX_INFO_STRING); + + if (bot->client->sess.sessionTeam == TEAM_SPECTATOR) + { + bot->client->sess.sessionTeam = preTeam; + } + + if (bot->client->sess.sessionTeam == TEAM_RED) + { + team = "Red"; + } + else + { + if (g_gametype.integer == GT_SIEGE) + { + if (bot->client->sess.sessionTeam == TEAM_BLUE) + { + team = "Blue"; + } + else + { + team = "s"; + } + } + else + { + team = "Blue"; + } + } + + Info_SetValueForKey( userinfo, "team", team ); + + trap_SetUserinfo( clientNum, userinfo ); + + bot->client->ps.persistant[ PERS_TEAM ] = bot->client->sess.sessionTeam; + + G_ReadSessionData( bot->client ); + ClientUserinfoChanged( clientNum ); + } + + if (g_gametype.integer == GT_DUEL || + g_gametype.integer == GT_POWERDUEL) + { + int loners = 0; + int doubles = 0; + + bot->client->sess.duelTeam = 0; + G_PowerDuelCount(&loners, &doubles, qtrue); + + if (!doubles || loners > (doubles/2)) + { + bot->client->sess.duelTeam = DUELTEAM_DOUBLE; + } + else + { + bot->client->sess.duelTeam = DUELTEAM_LONE; + } + + bot->client->sess.sessionTeam = TEAM_SPECTATOR; + SetTeam(bot, "s"); + } + else + { + if( delay == 0 ) { + ClientBegin( clientNum, qfalse ); + return; + } + + AddBotToSpawnQueue( clientNum, delay ); + } +} + + +/* +=============== +Svcmd_AddBot_f +=============== +*/ +void Svcmd_AddBot_f( void ) { + float skill; + int delay; + char name[MAX_TOKEN_CHARS]; + char altname[MAX_TOKEN_CHARS]; + char string[MAX_TOKEN_CHARS]; + char team[MAX_TOKEN_CHARS]; + + // are bots enabled? + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + // name + trap_Argv( 1, name, sizeof( name ) ); + if ( !name[0] ) { + trap_Printf( "Usage: Addbot [skill 1-5] [team] [msec delay] [altname]\n" ); + return; + } + + // skill + trap_Argv( 2, string, sizeof( string ) ); + if ( !string[0] ) { + skill = 4; + } + else { + skill = atof( string ); + } + + // team + trap_Argv( 3, team, sizeof( team ) ); + + // delay + trap_Argv( 4, string, sizeof( string ) ); + if ( !string[0] ) { + delay = 0; + } + else { + delay = atoi( string ); + } + + // alternative name + trap_Argv( 5, altname, sizeof( altname ) ); + + G_AddBot( name, skill, team, delay, altname ); + + // if this was issued during gameplay and we are playing locally, + // go ahead and load the bot's media immediately + if ( level.time - level.startTime > 1000 && + trap_Cvar_VariableIntegerValue( "cl_running" ) ) { + trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo + } +} + +/* +=============== +Svcmd_BotList_f +=============== +*/ +void Svcmd_BotList_f( void ) { + int i; + char name[MAX_TOKEN_CHARS]; + char funname[MAX_TOKEN_CHARS]; + char model[MAX_TOKEN_CHARS]; + char personality[MAX_TOKEN_CHARS]; + + trap_Printf("^1name model personality funname\n"); + for (i = 0; i < g_numBots; i++) { + strcpy(name, Info_ValueForKey( g_botInfos[i], "name" )); + if ( !*name ) { + strcpy(name, "Padawan"); + } + strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" )); + if ( !*funname ) { + strcpy(funname, ""); + } + strcpy(model, Info_ValueForKey( g_botInfos[i], "model" )); + if ( !*model ) { + strcpy(model, "kyle/default"); + } + strcpy(personality, Info_ValueForKey( g_botInfos[i], "personality")); + if (!*personality ) { + strcpy(personality, "botfiles/kyle.jkb"); + } + trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, personality, funname)); + } +} + +#if 0 +/* +=============== +G_SpawnBots +=============== +*/ +static void G_SpawnBots( char *botList, int baseDelay ) { + char *bot; + char *p; + float skill; + int delay; + char bots[MAX_INFO_VALUE]; + + podium1 = NULL; + podium2 = NULL; + podium3 = NULL; + + skill = trap_Cvar_VariableValue( "g_spSkill" ); + if( skill < 1 ) { + trap_Cvar_Set( "g_spSkill", "1" ); + skill = 1; + } + else if ( skill > 5 ) { + trap_Cvar_Set( "g_spSkill", "5" ); + skill = 5; + } + + Q_strncpyz( bots, botList, sizeof(bots) ); + p = &bots[0]; + delay = baseDelay; + while( *p ) { + //skip spaces + while( *p && *p == ' ' ) { + p++; + } + if( !p ) { + break; + } + + // mark start of bot name + bot = p; + + // skip until space of null + while( *p && *p != ' ' ) { + p++; + } + if( *p ) { + *p++ = 0; + } + + // we must add the bot this way, calling G_AddBot directly at this stage + // does "Bad Things" + trap_SendConsoleCommand( EXEC_INSERT, va("addbot \"%s\" %f free %i\n", bot, skill, delay) ); + + delay += BOT_BEGIN_DELAY_INCREMENT; + } +} +#endif + + +/* +=============== +G_LoadBotsFromFile +=============== +*/ +static void G_LoadBotsFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_BOTS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] ); +} + +/* +=============== +G_LoadBots +=============== +*/ +static void G_LoadBots( void ) { + vmCvar_t botsFile; + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i; + int dirlen; + + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + g_numBots = 0; + + trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); + if( *botsFile.string ) { + G_LoadBotsFromFile(botsFile.string); + } + else { + //G_LoadBotsFromFile("scripts/bots.txt"); + G_LoadBotsFromFile("botfiles/bots.txt"); + } + + // get all bots from .bot files + numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadBotsFromFile(filename); + } +// trap_Printf( va( "%i bots parsed\n", g_numBots ) ); +} + + + +/* +=============== +G_GetBotInfoByNumber +=============== +*/ +char *G_GetBotInfoByNumber( int num ) { + if( num < 0 || num >= g_numBots ) { + trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); + return NULL; + } + return g_botInfos[num]; +} + + +/* +=============== +G_GetBotInfoByName +=============== +*/ +char *G_GetBotInfoByName( const char *name ) { + int n; + char *value; + + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + if ( !Q_stricmp( value, name ) ) { + return g_botInfos[n]; + } + } + + return NULL; +} + +//rww - pd +void LoadPath_ThisLevel(void); +//end rww + +/* +=============== +G_InitBots +=============== +*/ +void G_InitBots( qboolean restart ) { + G_LoadBots(); + G_LoadArenas(); + + trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO ); + + //rww - new bot route stuff + LoadPath_ThisLevel(); + //end rww +} diff --git a/codemp/game/g_client.c b/codemp/game/g_client.c new file mode 100644 index 0000000..1a678b1 --- /dev/null +++ b/codemp/game/g_client.c @@ -0,0 +1,3935 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#include "g_local.h" +#include "../ghoul2/G2.h" +#include "bg_saga.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#include "../ghoul2/ghoul2_shared.h" +#include "../renderer/modelmem.h" +#endif + +// g_client.c -- client functions that don't happen every frame + +static vec3_t playerMins = {-15, -15, DEFAULT_MINS_2}; +static vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + +extern int g_siegeRespawnCheck; + +void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin ); +void WP_SaberRemoveG2Model( gentity_t *saberent ); + +forcedata_t Client_Force[MAX_CLIENTS]; + +/*QUAKED info_player_duel (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for duelists in duel. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_duel( gentity_t *ent ) +{ + int i; + + G_SpawnInt( "nobots", "0", &i); + if ( i ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_HUMANS; + } +} + +/*QUAKED info_player_duel1 (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for lone duelists in powerduel. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_duel1( gentity_t *ent ) +{ + int i; + + G_SpawnInt( "nobots", "0", &i); + if ( i ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_HUMANS; + } +} + +/*QUAKED info_player_duel2 (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for paired duelists in powerduel. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_duel2( gentity_t *ent ) +{ + int i; + + G_SpawnInt( "nobots", "0", &i); + if ( i ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_HUMANS; + } +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_deathmatch( gentity_t *ent ) { + int i; + + G_SpawnInt( "nobots", "0", &i); + if ( i ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_HUMANS; + } +} + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +Targets will be fired when someone spawns in on them. +equivelant to info_player_deathmatch +*/ +void SP_info_player_start(gentity_t *ent) { + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); +} + +void SiegePointUse( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + //Toggle the point on/off + if (self->genericValue1) + { + self->genericValue1 = 0; + } + else + { + self->genericValue1 = 1; + } +} + +/*QUAKED info_player_siegeteam1 (1 0 0) (-16 -16 -24) (16 16 32) +siege start point - team1 +name and behavior of team1 depends on what is defined in the +.siege file for this level + +startoff - if non-0 spawn point will be disabled until used +idealclass - if specified, this spawn point will be considered +"ideal" for players of this class name. Corresponds to the name +entry in the .scl (siege class) file. +Targets will be fired when someone spawns in on them. +*/ +void SP_info_player_siegeteam1(gentity_t *ent) { + int soff = 0; + + if (g_gametype.integer != GT_SIEGE) + { //turn into a DM spawn if not in siege game mode + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); + + return; + } + + G_SpawnInt("startoff", "0", &soff); + + if (soff) + { //start disabled + ent->genericValue1 = 0; + } + else + { + ent->genericValue1 = 1; + } + + ent->use = SiegePointUse; +} + +/*QUAKED info_player_siegeteam2 (0 0 1) (-16 -16 -24) (16 16 32) +siege start point - team2 +name and behavior of team2 depends on what is defined in the +.siege file for this level + +startoff - if non-0 spawn point will be disabled until used +idealclass - if specified, this spawn point will be considered +"ideal" for players of this class name. Corresponds to the name +entry in the .scl (siege class) file. +Targets will be fired when someone spawns in on them. +*/ +void SP_info_player_siegeteam2(gentity_t *ent) { + int soff = 0; + + if (g_gametype.integer != GT_SIEGE) + { //turn into a DM spawn if not in siege game mode + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); + + return; + } + + G_SpawnInt("startoff", "0", &soff); + + if (soff) + { //start disabled + ent->genericValue1 = 0; + } + else + { + ent->genericValue1 = 1; + } + + ent->use = SiegePointUse; +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_player_intermission( gentity_t *ent ) { + +} + +#define JMSABER_RESPAWN_TIME 20000 //in case it gets stuck somewhere no one can reach + +void ThrowSaberToAttacker(gentity_t *self, gentity_t *attacker) +{ + gentity_t *ent = &g_entities[self->client->ps.saberIndex]; + vec3_t a; + int altVelocity = 0; + + if (!ent || ent->enemy != self) + { //something has gone very wrong (this should never happen) + //but in case it does.. find the saber manually +#ifdef _DEBUG + Com_Printf("Lost the saber! Attempting to use global pointer..\n"); +#endif + ent = gJMSaberEnt; + + if (!ent) + { +#ifdef _DEBUG + Com_Printf("The global pointer was NULL. This is a bad thing.\n"); +#endif + return; + } + +#ifdef _DEBUG + Com_Printf("Got it (%i). Setting enemy to client %i.\n", ent->s.number, self->s.number); +#endif + + ent->enemy = self; + self->client->ps.saberIndex = ent->s.number; + } + + trap_SetConfigstring ( CS_CLIENT_JEDIMASTER, "-1" ); + + if (attacker && attacker->client && self->client->ps.saberInFlight) + { //someone killed us and we had the saber thrown, so actually move this saber to the saber location + //if we killed ourselves with saber thrown, however, same suicide rules of respawning at spawn spot still + //apply. + gentity_t *flyingsaber = &g_entities[self->client->ps.saberEntityNum]; + + if (flyingsaber && flyingsaber->inuse) + { + VectorCopy(flyingsaber->s.pos.trBase, ent->s.pos.trBase); + VectorCopy(flyingsaber->s.pos.trDelta, ent->s.pos.trDelta); + VectorCopy(flyingsaber->s.apos.trBase, ent->s.apos.trBase); + VectorCopy(flyingsaber->s.apos.trDelta, ent->s.apos.trDelta); + + VectorCopy(flyingsaber->r.currentOrigin, ent->r.currentOrigin); + VectorCopy(flyingsaber->r.currentAngles, ent->r.currentAngles); + altVelocity = 1; + } + } + + self->client->ps.saberInFlight = qtrue; //say he threw it anyway in order to properly remove from dead body + + WP_SaberAddG2Model( ent, self->client->saber[0].model, self->client->saber[0].skin ); + + ent->s.eFlags &= ~(EF_NODRAW); + ent->s.modelGhoul2 = 1; + ent->s.eType = ET_MISSILE; + ent->enemy = NULL; + + if (!attacker || !attacker->client) + { + VectorCopy(ent->s.origin2, ent->s.pos.trBase); + VectorCopy(ent->s.origin2, ent->s.origin); + VectorCopy(ent->s.origin2, ent->r.currentOrigin); + ent->pos2[0] = 0; + trap_LinkEntity(ent); + return; + } + + if (!altVelocity) + { + VectorCopy(self->s.pos.trBase, ent->s.pos.trBase); + VectorCopy(self->s.pos.trBase, ent->s.origin); + VectorCopy(self->s.pos.trBase, ent->r.currentOrigin); + + VectorSubtract(attacker->client->ps.origin, ent->s.pos.trBase, a); + + VectorNormalize(a); + + ent->s.pos.trDelta[0] = a[0]*256; + ent->s.pos.trDelta[1] = a[1]*256; + ent->s.pos.trDelta[2] = 256; + } + + trap_LinkEntity(ent); +} + +void JMSaberThink(gentity_t *ent) +{ + gJMSaberEnt = ent; + + if (ent->enemy) + { + if (!ent->enemy->client || !ent->enemy->inuse) + { //disconnected? + VectorCopy(ent->enemy->s.pos.trBase, ent->s.pos.trBase); + VectorCopy(ent->enemy->s.pos.trBase, ent->s.origin); + VectorCopy(ent->enemy->s.pos.trBase, ent->r.currentOrigin); + ent->s.modelindex = G_ModelIndex("models/weapons2/saber/saber_w.glm"); + ent->s.eFlags &= ~(EF_NODRAW); + ent->s.modelGhoul2 = 1; + ent->s.eType = ET_MISSILE; + ent->enemy = NULL; + + ent->pos2[0] = 1; + ent->pos2[1] = 0; //respawn next think + trap_LinkEntity(ent); + } + else + { + ent->pos2[1] = level.time + JMSABER_RESPAWN_TIME; + } + } + else if (ent->pos2[0] && ent->pos2[1] < level.time) + { + VectorCopy(ent->s.origin2, ent->s.pos.trBase); + VectorCopy(ent->s.origin2, ent->s.origin); + VectorCopy(ent->s.origin2, ent->r.currentOrigin); + ent->pos2[0] = 0; + trap_LinkEntity(ent); + } + + ent->nextthink = level.time + 50; + G_RunObject(ent); +} + +void JMSaberTouch(gentity_t *self, gentity_t *other, trace_t *trace) +{ + int i = 0; +// gentity_t *te; + + if (!other || !other->client || other->health < 1) + { + return; + } + + if (self->enemy) + { + return; + } + + if (!self->s.modelindex) + { + return; + } + + if (other->client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) + { + return; + } +/* + if (other->client->ps.isJediMaster) + { + return; + } +*/ + self->enemy = other; + other->client->ps.stats[STAT_WEAPONS] = (1 << WP_SABER); + other->client->ps.weapon = WP_SABER; + other->s.weapon = WP_SABER; + G_AddEvent(other, EV_BECOME_JEDIMASTER, 0); + + // Track the jedi master + trap_SetConfigstring ( CS_CLIENT_JEDIMASTER, va("%i", other->s.number ) ); + + if (g_spawnInvulnerability.integer) + { + other->client->ps.eFlags |= EF_INVULNERABLE; + other->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer; + } + + trap_SendServerCommand( -1, va("cp \"%s %s\n\"", other->client->pers.netname, G_GetStringEdString("MP_SVGAME", "BECOMEJM")) ); + +// other->client->ps.isJediMaster = qtrue; + other->client->ps.saberIndex = self->s.number; + + if (other->health < 200 && other->health > 0) + { //full health when you become the Jedi Master + other->client->ps.stats[STAT_HEALTH] = other->health = 200; + } + + if (other->client->ps.fd.forcePower < 100) + { + other->client->ps.fd.forcePower = 100; + } + + while (i < NUM_FORCE_POWERS) + { + other->client->ps.fd.forcePowersKnown |= (1 << i); + other->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_3; + + i++; + } + + self->pos2[0] = 1; + self->pos2[1] = level.time + JMSABER_RESPAWN_TIME; + + self->s.modelindex = 0; + self->s.eFlags |= EF_NODRAW; + self->s.modelGhoul2 = 0; + self->s.eType = ET_GENERAL; + + /* + te = G_TempEntity( vec3_origin, EV_DESTROY_GHOUL2_INSTANCE ); + te->r.svFlags |= SVF_BROADCAST; + te->s.eventParm = self->s.number; + */ + G_KillG2Queue(self->s.number); + + return; +} + +gentity_t *gJMSaberEnt = NULL; + +/*QUAKED info_jedimaster_start (1 0 0) (-16 -16 -24) (16 16 32) +"jedi master" saber spawn point +*/ +void SP_info_jedimaster_start(gentity_t *ent) +{ + if (g_gametype.integer != GT_JEDIMASTER) + { + gJMSaberEnt = NULL; + G_FreeEntity(ent); + return; + } + + ent->enemy = NULL; + + ent->flags = FL_BOUNCE_HALF; + + ent->s.modelindex = G_ModelIndex("models/weapons2/saber/saber_w.glm"); + ent->s.modelGhoul2 = 1; + ent->s.g2radius = 20; + //ent->s.eType = ET_GENERAL; + ent->s.eType = ET_MISSILE; + ent->s.weapon = WP_SABER; + ent->s.pos.trType = TR_GRAVITY; + ent->s.pos.trTime = level.time; + VectorSet( ent->r.maxs, 3, 3, 3 ); + VectorSet( ent->r.mins, -3, -3, -3 ); + ent->r.contents = CONTENTS_TRIGGER; + ent->clipmask = MASK_SOLID; + + ent->isSaberEntity = qtrue; + + ent->bounceCount = -5; + + ent->physicsObject = qtrue; + + VectorCopy(ent->s.pos.trBase, ent->s.origin2); //remember the spawn spot + + ent->touch = JMSaberTouch; + + trap_LinkEntity(ent); + + ent->think = JMSaberThink; + ent->nextthink = level.time + 50; +} + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +SpotWouldTelefrag + +================ +*/ +qboolean SpotWouldTelefrag( gentity_t *spot ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( spot->s.origin, playerMins, mins ); + VectorAdd( spot->s.origin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + if ( hit->client) { + return qtrue; + } + + } + + return qfalse; +} + +qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ) +{ + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( dest, mover->r.mins, mins ); + VectorAdd( dest, mover->r.maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; ir.contents & mover->r.contents ) + { + return qtrue; + } + } + + return qfalse; +} + +/* +================ +SelectNearestDeathmatchSpawnPoint + +Find the spot that we DON'T want to use +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) { + gentity_t *spot; + vec3_t delta; + float dist, nearestDist; + gentity_t *nearestSpot; + + nearestDist = 999999; + nearestSpot = NULL; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + + VectorSubtract( spot->s.origin, from, delta ); + dist = VectorLength( delta ); + if ( dist < nearestDist ) { + nearestDist = dist; + nearestSpot = spot; + } + } + + return nearestSpot; +} + + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectRandomDeathmatchSpawnPoint( void ) { + gentity_t *spot; + int count; + int selection; + gentity_t *spots[MAX_SPAWN_POINTS]; + + count = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + spots[ count ] = spot; + count++; + } + + if ( !count ) { // no spots that won't telefrag + return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + } + + selection = rand() % count; + return spots[ selection ]; +} + +/* +=========== +SelectRandomFurthestSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { + gentity_t *spot; + vec3_t delta; + float dist; + float list_dist[64]; + gentity_t *list_spot[64]; + int numSpots, rnd, i, j; + + numSpots = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + VectorSubtract( spot->s.origin, avoidPoint, delta ); + dist = VectorLength( delta ); + for (i = 0; i < numSpots; i++) { + if ( dist > list_dist[i] ) { + if ( numSpots >= 64 ) + numSpots = 64-1; + for (j = numSpots; j > i; j--) { + list_dist[j] = list_dist[j-1]; + list_spot[j] = list_spot[j-1]; + } + list_dist[i] = dist; + list_spot[i] = spot; + numSpots++; + if (numSpots > 64) + numSpots = 64; + break; + } + } + if (i >= numSpots && numSpots < 64) { + list_dist[numSpots] = dist; + list_spot[numSpots] = spot; + numSpots++; + } + } + if (!numSpots) { + spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + if (!spot) + G_Error( "Couldn't find a spawn point" ); + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + return spot; + } + + // select a random spot from the spawn points furthest away + rnd = random() * (numSpots / 2); + + VectorCopy (list_spot[rnd]->s.origin, origin); + origin[2] += 9; + VectorCopy (list_spot[rnd]->s.angles, angles); + + return list_spot[rnd]; +} + +gentity_t *SelectDuelSpawnPoint( int team, vec3_t avoidPoint, vec3_t origin, vec3_t angles ) +{ + gentity_t *spot; + vec3_t delta; + float dist; + float list_dist[64]; + gentity_t *list_spot[64]; + int numSpots, rnd, i, j; + char *spotName; + + if (team == DUELTEAM_LONE) + { + spotName = "info_player_duel1"; + } + else if (team == DUELTEAM_DOUBLE) + { + spotName = "info_player_duel2"; + } + else if (team == DUELTEAM_SINGLE) + { + spotName = "info_player_duel"; + } + else + { + spotName = "info_player_deathmatch"; + } +tryAgain: + + numSpots = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), spotName)) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + VectorSubtract( spot->s.origin, avoidPoint, delta ); + dist = VectorLength( delta ); + for (i = 0; i < numSpots; i++) { + if ( dist > list_dist[i] ) { + if ( numSpots >= 64 ) + numSpots = 64-1; + for (j = numSpots; j > i; j--) { + list_dist[j] = list_dist[j-1]; + list_spot[j] = list_spot[j-1]; + } + list_dist[i] = dist; + list_spot[i] = spot; + numSpots++; + if (numSpots > 64) + numSpots = 64; + break; + } + } + if (i >= numSpots && numSpots < 64) { + list_dist[numSpots] = dist; + list_spot[numSpots] = spot; + numSpots++; + } + } + if (!numSpots) + { + if (Q_stricmp(spotName, "info_player_deathmatch")) + { //try the loop again with info_player_deathmatch as the target if we couldn't find a duel spot + spotName = "info_player_deathmatch"; + goto tryAgain; + } + + //If we got here we found no free duel or DM spots, just try the first DM spot + spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + if (!spot) + G_Error( "Couldn't find a spawn point" ); + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + return spot; + } + + // select a random spot from the spawn points furthest away + rnd = random() * (numSpots / 2); + + VectorCopy (list_spot[rnd]->s.origin, origin); + origin[2] += 9; + VectorCopy (list_spot[rnd]->s.angles, angles); + + return list_spot[rnd]; +} + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { + return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); + + /* + gentity_t *spot; + gentity_t *nearestSpot; + + nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); + + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // roll again if it would be real close to point of death + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // last try + spot = SelectRandomDeathmatchSpawnPoint ( ); + } + } + + // find a single player start spot + if (!spot) { + G_Error( "Couldn't find a spawn point" ); + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; + */ +} + +/* +=========== +SelectInitialSpawnPoint + +Try to find a spawn point marked 'initial', otherwise +use normal spawn selection. +============ +*/ +gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { + gentity_t *spot; + + spot = NULL; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( spot->spawnflags & 1 ) { + break; + } + } + + if ( !spot || SpotWouldTelefrag( spot ) ) { + return SelectSpawnPoint( vec3_origin, origin, angles ); + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; +} + +/* +=========== +SelectSpectatorSpawnPoint + +============ +*/ +gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { + FindIntermissionPoint(); + + VectorCopy( level.intermission_origin, origin ); + VectorCopy( level.intermission_angle, angles ); + + return NULL; +} + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + +#define BODY_SINK_TIME 30000//45000 + +/* +=============== +InitBodyQue +=============== +*/ +void InitBodyQue (void) { + int i; + gentity_t *ent; + + level.bodyQueIndex = 0; + for (i=0; iclassname = "bodyque"; + ent->neverFree = qtrue; + level.bodyQue[i] = ent; + } +} + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +void BodySink( gentity_t *ent ) { + if ( level.time - ent->timestamp > BODY_SINK_TIME + 2500 ) { + // the body ques are never actually freed, they are just unlinked + trap_UnlinkEntity( ent ); + ent->physicsObject = qfalse; + return; + } +// ent->nextthink = level.time + 100; +// ent->s.pos.trBase[2] -= 1; + + G_AddEvent(ent, EV_BODYFADE, 0); + ent->nextthink = level.time + 18000; + ent->takedamage = qfalse; +} + +/* +============= +CopyToBodyQue + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +static qboolean CopyToBodyQue( gentity_t *ent ) { + gentity_t *body; + int contents; + int islight = 0; + + if (level.intermissiontime) + { + return qfalse; + } + + trap_UnlinkEntity (ent); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( ent->s.origin, -1 ); + if ( contents & CONTENTS_NODROP ) { + return qfalse; + } + + if (ent->client && (ent->client->ps.eFlags & EF_DISINTEGRATION)) + { //for now, just don't spawn a body if you got disint'd + return qfalse; + } + + // grab a body que and cycle to the next one + body = level.bodyQue[ level.bodyQueIndex ]; + level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; + + trap_UnlinkEntity (body); + body->s = ent->s; + + //avoid oddly angled corpses floating around + body->s.angles[PITCH] = body->s.angles[ROLL] = body->s.apos.trBase[PITCH] = body->s.apos.trBase[ROLL] = 0; + + body->s.g2radius = 100; + + body->s.eType = ET_BODY; + body->s.eFlags = EF_DEAD; // clear EF_TALK, etc + + if (ent->client && (ent->client->ps.eFlags & EF_DISINTEGRATION)) + { + body->s.eFlags |= EF_DISINTEGRATION; + } + + VectorCopy(ent->client->ps.lastHitLoc, body->s.origin2); + + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.loopIsSoundset = qfalse; + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { + body->s.pos.trType = TR_GRAVITY; + body->s.pos.trTime = level.time; + VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); + } else { + body->s.pos.trType = TR_STATIONARY; + } + body->s.event = 0; + + body->s.weapon = ent->s.bolt2; + + if (body->s.weapon == WP_SABER && ent->client->ps.saberInFlight) + { + body->s.weapon = WP_BLASTER; //lie to keep from putting a saber on the corpse, because it was thrown at death + } + + //G_AddEvent(body, EV_BODY_QUEUE_COPY, ent->s.clientNum); + //Now doing this through a modified version of the rcg reliable command. + if (ent->client && ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE) + { + islight = 1; + } +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + trap_SendServerCommand(0, va("ircg %i %i %i %i", ent->s.number, body->s.number, body->s.weapon, islight)); + else +#endif + trap_SendServerCommand(-1, va("ircg %i %i %i %i", ent->s.number, body->s.number, body->s.weapon, islight)); + + body->r.svFlags = ent->r.svFlags | SVF_BROADCAST; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + + body->s.torsoAnim = body->s.legsAnim = ent->client->ps.legsAnim; + + body->s.customRGBA[0] = ent->client->ps.customRGBA[0]; + body->s.customRGBA[1] = ent->client->ps.customRGBA[1]; + body->s.customRGBA[2] = ent->client->ps.customRGBA[2]; + body->s.customRGBA[3] = ent->client->ps.customRGBA[3]; + + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = CONTENTS_CORPSE; + body->r.ownerNum = ent->s.number; + + body->nextthink = level.time + BODY_SINK_TIME; + body->think = BodySink; + + body->die = body_die; + + // don't take more damage if already gibbed + if ( ent->health <= GIB_HEALTH ) { + body->takedamage = qfalse; + } else { + body->takedamage = qtrue; + } + + VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); + trap_LinkEntity (body); + + return qtrue; +} + +//====================================================================== + + +/* +================== +SetClientViewAngle + +================== +*/ +void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { + int i; + + // set the delta angle + for (i=0 ; i<3 ; i++) { + int cmdAngle; + + cmdAngle = ANGLE2SHORT(angle[i]); + ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; + } + VectorCopy( angle, ent->s.angles ); + VectorCopy (ent->s.angles, ent->client->ps.viewangles); +} + +void MaintainBodyQueue(gentity_t *ent) +{ //do whatever should be done taking ragdoll and dismemberment states into account. + qboolean doRCG = qfalse; + + assert(ent && ent->client); + if (ent->client->tempSpectate > level.time || + (ent->client->ps.eFlags2 & EF2_SHIP_DEATH)) + { + ent->client->noCorpse = qtrue; + } + + if (!ent->client->noCorpse && !ent->client->ps.fallingToDeath) + { + if (!CopyToBodyQue (ent)) + { + doRCG = qtrue; + } + } + else + { + ent->client->noCorpse = qfalse; //clear it for next time + ent->client->ps.fallingToDeath = qfalse; + doRCG = qtrue; + } + + if (doRCG) + { //bodyque func didn't manage to call ircg so call this to assure our limbs and ragdoll states are proper on the client. + trap_SendServerCommand(-1, va("rcg %i", ent->s.clientNum)); + } +} + +/* +================ +respawn +================ +*/ +void SiegeRespawn(gentity_t *ent); +void respawn( gentity_t *ent ) { + MaintainBodyQueue(ent); + + if (gEscaping || g_gametype.integer == GT_POWERDUEL) + { + ent->client->sess.sessionTeam = TEAM_SPECTATOR; + ent->client->sess.spectatorState = SPECTATOR_FREE; + ent->client->sess.spectatorClient = 0; + + ent->client->pers.teamState.state = TEAM_BEGIN; + ent->client->sess.spectatorTime = level.time; + ClientSpawn(ent); + ent->client->iAmALoser = qtrue; + return; + } + + trap_UnlinkEntity (ent); + + if (g_gametype.integer == GT_SIEGE) + { + if (g_siegeRespawn.integer) + { + if (ent->client->tempSpectate <= level.time) + { + int minDel = g_siegeRespawn.integer* 2000; + if (minDel < 20000) + { + minDel = 20000; + } + ent->client->tempSpectate = level.time + minDel; + ent->health = ent->client->ps.stats[STAT_HEALTH] = 1; + ent->client->ps.weapon = WP_NONE; + ent->client->ps.stats[STAT_WEAPONS] = 0; + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] = 0; + ent->client->ps.stats[STAT_HOLDABLE_ITEM] = 0; + ent->takedamage = qfalse; + trap_LinkEntity(ent); + + // Respawn time. + if ( ent->s.number < MAX_CLIENTS ) + { + gentity_t *te = G_TempEntity( ent->client->ps.origin, EV_SIEGESPEC ); + te->s.time = g_siegeRespawnCheck; + te->s.owner = ent->s.number; + } + + return; + } + } + SiegeRespawn(ent); + } + else + { + gentity_t *tent; + + ClientSpawn(ent); + + // add a teleportation effect + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = ent->s.clientNum; + } +} + +/* +================ +TeamCount + +Returns number of players on a team +================ +*/ +team_t TeamCount( int ignoreClientNum, int team ) { + int i; + int count = 0; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( i == ignoreClientNum ) { + continue; + } + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == team ) { + count++; + } + else if (g_gametype.integer == GT_SIEGE && + level.clients[i].sess.siegeDesiredTeam == team) + { + count++; + } + } + + return count; +} + +/* +================ +TeamLeader + +Returns the client number of the team leader +================ +*/ +int TeamLeader( int team ) { + int i; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == team ) { + if ( level.clients[i].sess.teamLeader ) + return i; + } + } + + return -1; +} + + +/* +================ +PickTeam + +================ +*/ +team_t PickTeam( int ignoreClientNum ) { + int counts[TEAM_NUM_TEAMS]; + + counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE ); + counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED ); + + if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { + return TEAM_RED; + } + if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { + return TEAM_BLUE; + } + // equal team count, so join the team with the lowest score + if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { + return TEAM_RED; + } + return TEAM_BLUE; +} + +/* +=========== +ForceClientSkin + +Forces a client's skin (for teamplay) +=========== +*/ +/* +static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) { + char *p; + + if ((p = Q_strrchr(model, '/')) != 0) { + *p = 0; + } + + Q_strcat(model, MAX_QPATH, "/"); + Q_strcat(model, MAX_QPATH, skin); +} +*/ + +/* +=========== +ClientCheckName +============ +*/ +static void ClientCleanName( const char *in, char *out, int outSize ) { + int len, colorlessLen; + char ch; + char *p; + int spaces; + + //save room for trailing null byte + outSize--; + + len = 0; + colorlessLen = 0; + p = out; + *p = 0; + spaces = 0; + + while( 1 ) { + ch = *in++; + if( !ch ) { + break; + } + + // don't allow leading spaces + if( !*p && ch == ' ' ) { + continue; + } + + // check colors + if( ch == Q_COLOR_ESCAPE ) { + // solo trailing carat is not a color prefix + if( !*in ) { + break; + } + + // don't allow black in a name, period + if( ColorIndex(*in) == 0 ) { + in++; + continue; + } + + // make sure room in dest for both chars + if( len > outSize - 2 ) { + break; + } + + *out++ = ch; + *out++ = *in++; + len += 2; + continue; + } + + // don't allow too many consecutive spaces + if( ch == ' ' ) { + spaces++; + if( spaces > 3 ) { + continue; + } + } + else { + spaces = 0; + } + + if( len > outSize - 1 ) { + break; + } + + *out++ = ch; + colorlessLen++; + len++; + } + *out = 0; + + // don't allow empty names + if( *p == 0 || colorlessLen == 0 ) { + Q_strncpyz( p, "Padawan", outSize ); + } +} + +#ifdef _DEBUG +void G_DebugWrite(const char *path, const char *text) +{ + fileHandle_t f; + + trap_FS_FOpenFile( path, &f, FS_APPEND ); + trap_FS_Write(text, strlen(text), f); + trap_FS_FCloseFile(f); +} +#endif + +qboolean G_SaberModelSetup(gentity_t *ent) +{ + int i = 0; + qboolean fallbackForSaber = qtrue; + + while (i < MAX_SABERS) + { + if (ent->client->saber[i].model[0]) + { + //first kill it off if we've already got it + if (ent->client->weaponGhoul2[i]) + { + trap_G2API_CleanGhoul2Models(&(ent->client->weaponGhoul2[i])); + } + trap_G2API_InitGhoul2Model(&ent->client->weaponGhoul2[i], ent->client->saber[i].model, 0, 0, -20, 0, 0); + + if (ent->client->weaponGhoul2[i]) + { + int j = 0; + char *tagName; + int tagBolt; + + if (ent->client->saber[i].skin) + { + trap_G2API_SetSkin(ent->client->weaponGhoul2[i], 0, ent->client->saber[i].skin, ent->client->saber[i].skin); + } + + // bolt to right hand for 0, or left hand for 1 + trap_G2API_SetBoltInfo(ent->client->weaponGhoul2[i], 0, i); + + //Add all the bolt points + while (j < ent->client->saber[i].numBlades) + { + tagName = va("*blade%i", j+1); + tagBolt = trap_G2API_AddBolt(ent->client->weaponGhoul2[i], 0, tagName); + + if (tagBolt == -1) + { + if (j == 0) + { //guess this is an 0ldsk3wl saber + tagBolt = trap_G2API_AddBolt(ent->client->weaponGhoul2[i], 0, "*flash"); + fallbackForSaber = qfalse; + break; + } + + if (tagBolt == -1) + { + assert(0); + break; + + } + } + j++; + + fallbackForSaber = qfalse; //got at least one custom saber so don't need default + } + + //Copy it into the main instance + trap_G2API_CopySpecificGhoul2Model(ent->client->weaponGhoul2[i], 0, ent->ghoul2, i+1); + } + } + else + { + break; + } + + i++; + } + + return fallbackForSaber; +} + +/* +=========== +SetupGameGhoul2Model + +There are two ghoul2 model instances per player (actually three). One is on the clientinfo (the base for the client side +player, and copied for player spawns and for corpses). One is attached to the centity itself, which is the model acutally +animated and rendered by the system. The final is the game ghoul2 model. This is animated by pmove on the server, and +is used for determining where the lightsaber should be, and for per-poly collision tests. +=========== +*/ +void *g2SaberInstance = NULL; + +#include "../namespace_begin.h" +qboolean BG_IsValidCharacterModel(const char *modelName, const char *skinName); +qboolean BG_ValidateSkinForTeam( const char *modelName, char *skinName, int team, float *colors ); +void BG_GetVehicleModelName(char *modelname); +#include "../namespace_end.h" + +void SetupGameGhoul2Model(gentity_t *ent, char *modelname, char *skinName) +{ + int handle; + char afilename[MAX_QPATH]; +#if 0 + char /**GLAName,*/ *slash; +#endif + char GLAName[MAX_QPATH]; + vec3_t tempVec = {0,0,0}; + + // First things first. If this is a ghoul2 model, then let's make sure we demolish this first. + if (ent->ghoul2 && trap_G2_HaveWeGhoul2Models(ent->ghoul2)) + { + trap_G2API_CleanGhoul2Models(&(ent->ghoul2)); + } + + //rww - just load the "standard" model for the server" + if (!precachedKyle) + { + int defSkin; + + Com_sprintf( afilename, sizeof( afilename ), "models/players/kyle/model.glm" ); + handle = trap_G2API_InitGhoul2Model(&precachedKyle, afilename, 0, 0, -20, 0, 0); + + if (handle<0) + { + return; + } + + defSkin = trap_R_RegisterSkin("models/players/kyle/model_default.skin"); + trap_G2API_SetSkin(precachedKyle, 0, defSkin, defSkin); + } + + if (precachedKyle && trap_G2_HaveWeGhoul2Models(precachedKyle)) + { + if (d_perPlayerGhoul2.integer || ent->s.number >= MAX_CLIENTS || + G_PlayerHasCustomSkeleton(ent)) + { //rww - allow option for perplayer models on server for collision and bolt stuff. + char modelFullPath[MAX_QPATH]; + char truncModelName[MAX_QPATH]; + char skin[MAX_QPATH]; + char vehicleName[MAX_QPATH]; + int skinHandle = 0; + int i = 0; + char *p; + + // If this is a vehicle, get it's model name. + if ( ent->client->NPC_class == CLASS_VEHICLE ) + { + strcpy(vehicleName, modelname); + BG_GetVehicleModelName(modelname); + strcpy(truncModelName, modelname); + skin[0] = 0; + if ( ent->m_pVehicle + && ent->m_pVehicle->m_pVehicleInfo + && ent->m_pVehicle->m_pVehicleInfo->skin + && ent->m_pVehicle->m_pVehicleInfo->skin[0] ) + { + skinHandle = trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", modelname, ent->m_pVehicle->m_pVehicleInfo->skin)); + } + else + { + skinHandle = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelname)); + } + } + else + { + if (skinName && skinName[0]) + { + strcpy(skin, skinName); + strcpy(truncModelName, modelname); + } + else + { + strcpy(skin, "default"); + + strcpy(truncModelName, modelname); + p = Q_strrchr(truncModelName, '/'); + + if (p) + { + *p = 0; + p++; + + while (p && *p) + { + skin[i] = *p; + i++; + p++; + } + skin[i] = 0; + i = 0; + } + + if (!BG_IsValidCharacterModel(truncModelName, skin)) + { + strcpy(truncModelName, "kyle"); + strcpy(skin, "default"); + } + + if ( g_gametype.integer >= GT_TEAM && g_gametype.integer != GT_SIEGE && !g_trueJedi.integer ) + { + BG_ValidateSkinForTeam( truncModelName, skin, ent->client->sess.sessionTeam, NULL ); + } + else if (g_gametype.integer == GT_SIEGE) + { //force skin for class if appropriate + if (ent->client->siegeClass != -1) + { + siegeClass_t *scl = &bgSiegeClasses[ent->client->siegeClass]; + if (scl->forcedSkin[0]) + { + strcpy(skin, scl->forcedSkin); + } + } + } + } + } + + if (skin[0]) + { + char *useSkinName; + + if (strchr(skin, '|')) + {//three part skin + useSkinName = va("models/players/%s/|%s", truncModelName, skin); + } + else + { + useSkinName = va("models/players/%s/model_%s.skin", truncModelName, skin); + } + + skinHandle = trap_R_RegisterSkin(useSkinName); + } + + strcpy(modelFullPath, va("models/players/%s/model.glm", truncModelName)); + handle = trap_G2API_InitGhoul2Model(&ent->ghoul2, modelFullPath, 0, skinHandle, -20, 0, 0); + + if (handle<0) + { //Huh. Guess we don't have this model. Use the default. + + if (ent->ghoul2 && trap_G2_HaveWeGhoul2Models(ent->ghoul2)) + { + trap_G2API_CleanGhoul2Models(&(ent->ghoul2)); + } + ent->ghoul2 = NULL; + trap_G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2); + } + else + { + trap_G2API_SetSkin(ent->ghoul2, 0, skinHandle, skinHandle); + + GLAName[0] = 0; + trap_G2API_GetGLAName( ent->ghoul2, 0, GLAName); + + if (!GLAName[0] || (!strstr(GLAName, "players/_humanoid/") && ent->s.number < MAX_CLIENTS && !G_PlayerHasCustomSkeleton(ent))) + { //a bad model + trap_G2API_CleanGhoul2Models(&(ent->ghoul2)); + ent->ghoul2 = NULL; + trap_G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2); + } + + if (ent->s.number >= MAX_CLIENTS) + { + ent->s.modelGhoul2 = 1; //so we know to free it on the client when we're removed. + + if (skin[0]) + { //append it after a * + strcat( modelFullPath, va("*%s", skin) ); + } + + if ( ent->client->NPC_class == CLASS_VEHICLE ) + { //vehicles are tricky and send over their vehicle names as the model (the model is then retrieved based on the vehicle name) + ent->s.modelindex = G_ModelIndex(vehicleName); + } + else + { + ent->s.modelindex = G_ModelIndex(modelFullPath); + } + } + } + } + else + { + trap_G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2); + } + } + else + { + return; + } + + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(ent->ghoul2, ent->s.number, qtrue); + + // The model is now loaded. + + GLAName[0] = 0; + + if (!BGPAFtextLoaded) + { + if (BG_ParseAnimationFile("models/players/_humanoid/animation.cfg", bgHumanoidAnimations, qtrue) == -1) + { + Com_Printf( "Failed to load humanoid animation file\n"); + return; + } + } + + if (ent->s.number >= MAX_CLIENTS || G_PlayerHasCustomSkeleton(ent)) + { + ent->localAnimIndex = -1; + + GLAName[0] = 0; + trap_G2API_GetGLAName(ent->ghoul2, 0, GLAName); + + if (GLAName[0] && + !strstr(GLAName, "players/_humanoid/") /*&& + !strstr(GLAName, "players/rockettrooper/")*/) + { //it doesn't use humanoid anims. + char *slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + + ent->localAnimIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); + } + } + else + { //humanoid index. + if (strstr(GLAName, "players/rockettrooper/")) + { + ent->localAnimIndex = 1; + } + else + { + ent->localAnimIndex = 0; + } + } + + if (ent->localAnimIndex == -1) + { + Com_Error(ERR_DROP, "NPC had an invalid GLA\n"); + } + } + else + { + GLAName[0] = 0; + trap_G2API_GetGLAName(ent->ghoul2, 0, GLAName); + + if (strstr(GLAName, "players/rockettrooper/")) + { + //assert(!"Should not have gotten in here with rockettrooper skel"); + ent->localAnimIndex = 1; + } + else + { + ent->localAnimIndex = 0; + } + } + + if (ent->s.NPC_class == CLASS_VEHICLE && + ent->m_pVehicle) + { //do special vehicle stuff + char strTemp[128]; + int i; + + // Setup the default first bolt + i = trap_G2API_AddBolt( ent->ghoul2, 0, "model_root" ); + + // Setup the droid unit. + ent->m_pVehicle->m_iDroidUnitTag = trap_G2API_AddBolt( ent->ghoul2, 0, "*droidunit" ); + + // Setup the Exhausts. + for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ ) + { + Com_sprintf( strTemp, 128, "*exhaust%i", i + 1 ); + ent->m_pVehicle->m_iExhaustTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, strTemp ); + } + + // Setup the Muzzles. + for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) + { + Com_sprintf( strTemp, 128, "*muzzle%i", i + 1 ); + ent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, strTemp ); + if ( ent->m_pVehicle->m_iMuzzleTag[i] == -1 ) + {//ergh, try *flash? + Com_sprintf( strTemp, 128, "*flash%i", i + 1 ); + ent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, strTemp ); + } + } + + // Setup the Turrets. + for ( i = 0; i < MAX_VEHICLE_TURRET_MUZZLES; i++ ) + { + if ( ent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag ) + { + ent->m_pVehicle->m_iGunnerViewTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, ent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag ); + } + else + { + ent->m_pVehicle->m_iGunnerViewTag[i] = -1; + } + } + } + + if (ent->client->ps.weapon == WP_SABER || ent->s.number < MAX_CLIENTS) + { //a player or NPC saber user + trap_G2API_AddBolt(ent->ghoul2, 0, "*r_hand"); + trap_G2API_AddBolt(ent->ghoul2, 0, "*l_hand"); + + //rhand must always be first bolt. lhand always second. Whichever you want the + //jetpack bolted to must always be third. + trap_G2API_AddBolt(ent->ghoul2, 0, "*chestg"); + + trap_G2API_SetBoneAnim(ent->ghoul2, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, level.time, -1, -1); + trap_G2API_SetBoneAngles(ent->ghoul2, 0, "upper_lumbar", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, level.time); + trap_G2API_SetBoneAngles(ent->ghoul2, 0, "cranium", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, level.time); + + if (!g2SaberInstance) + { + trap_G2API_InitGhoul2Model(&g2SaberInstance, "models/weapons2/saber/saber_w.glm", 0, 0, -20, 0, 0); + + if (g2SaberInstance) + { + // indicate we will be bolted to model 0 (ie the player) on bolt 0 (always the right hand) when we get copied + trap_G2API_SetBoltInfo(g2SaberInstance, 0, 0); + // now set up the gun bolt on it + trap_G2API_AddBolt(g2SaberInstance, 0, "*blade1"); + } + } + + if (G_SaberModelSetup(ent)) + { + if (g2SaberInstance) + { + trap_G2API_CopySpecificGhoul2Model(g2SaberInstance, 0, ent->ghoul2, 1); + } + } + } + + if (ent->s.number >= MAX_CLIENTS) + { //some extra NPC stuff + if (trap_G2API_AddBolt(ent->ghoul2, 0, "lower_lumbar") == -1) + { //check now to see if we have this bone for setting anims and such + ent->noLumbar = qtrue; + } + } +} + + + + +/* +=========== +ClientUserInfoChanged + +Called from ClientConnect when the player first connects and +directly by the server system when the player updates a userinfo variable. + +The game can override any of the settings and call trap_SetUserinfo +if desired. +============ +*/ +qboolean G_SetSaber(gentity_t *ent, int saberNum, char *saberName, qboolean siegeOverride); +void G_ValidateSiegeClassForTeam(gentity_t *ent, int team); +void ClientUserinfoChanged( int clientNum ) { + gentity_t *ent; + int teamTask, teamLeader, team, health; + char *s; + char model[MAX_QPATH]; + //char headModel[MAX_QPATH]; + char forcePowers[MAX_QPATH]; + char oldname[MAX_STRING_CHARS]; + gclient_t *client; + char c1[MAX_INFO_STRING]; + char c2[MAX_INFO_STRING]; + char redTeam[MAX_INFO_STRING]; + char blueTeam[MAX_INFO_STRING]; + char userinfo[MAX_INFO_STRING]; + char className[MAX_QPATH]; //name of class type to use in siege + char saberName[MAX_QPATH]; + char saber2Name[MAX_QPATH]; + char *value; + int maxHealth; + qboolean modelChanged = qfalse; + + ent = g_entities + clientNum; + client = ent->client; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if ( !Info_Validate(userinfo) ) { + strcpy (userinfo, "\\name\\badinfo"); + } + + // check for local client + s = Info_ValueForKey( userinfo, "ip" ); + if ( !strcmp( s, "localhost" ) ) { + client->pers.localClient = qtrue; + } + + // check the item prediction + s = Info_ValueForKey( userinfo, "cg_predictItems" ); + if ( !atoi( s ) ) { + client->pers.predictItemPickup = qfalse; + } else { + client->pers.predictItemPickup = qtrue; + } + + // set name + Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey (userinfo, "name"); + ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); + + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) ); + } + } +/* + if ( client->pers.connected == CON_CONNECTED ) { + if ( strcmp( oldname, client->pers.netname ) ) + { + if ( client->pers.netnameTime > level.time ) + { + trap_SendServerCommand( clientNum, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NONAMECHANGE")) ); + + Info_SetValueForKey( userinfo, "name", oldname ); + trap_SetUserinfo( clientNum, userinfo ); + strcpy ( client->pers.netname, oldname ); + } + else + { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " %s %s\n\"", oldname, G_GetStringEdString("MP_SVGAME", "PLRENAME"), client->pers.netname) ); + client->pers.netnameTime = level.time + 5000; + } + } + } +*/ + // set model + Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); + + if (d_perPlayerGhoul2.integer) + { + if (Q_stricmp(model, client->modelname)) + { + strcpy(client->modelname, model); + modelChanged = qtrue; + } + } + + //Get the skin RGB based on his userinfo + value = Info_ValueForKey (userinfo, "char_color_red"); + if (value) + { + client->ps.customRGBA[0] = atoi(value); + } + else + { + client->ps.customRGBA[0] = 255; + } + + value = Info_ValueForKey (userinfo, "char_color_green"); + if (value) + { + client->ps.customRGBA[1] = atoi(value); + } + else + { + client->ps.customRGBA[1] = 255; + } + + value = Info_ValueForKey (userinfo, "char_color_blue"); + if (value) + { + client->ps.customRGBA[2] = atoi(value); + } + else + { + client->ps.customRGBA[2] = 255; + } + + if ((client->ps.customRGBA[0]+client->ps.customRGBA[1]+client->ps.customRGBA[2]) < 100) + { //hmm, too dark! + client->ps.customRGBA[0] = client->ps.customRGBA[1] = client->ps.customRGBA[2] = 255; + } + + client->ps.customRGBA[3]=255; + + Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) ); + + // bots set their team a few frames later + if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) { + s = Info_ValueForKey( userinfo, "team" ); + if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { + team = TEAM_RED; + } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { + team = TEAM_BLUE; + } else { + // pick the team with the least number of players + team = PickTeam( clientNum ); + } + } + else { + team = client->sess.sessionTeam; + } + + //Set the siege class + if (g_gametype.integer == GT_SIEGE) + { + strcpy(className, client->sess.siegeClass); + + //This function will see if the given class is legal for the given team. + //If not className will be filled in with the first legal class for this team. +/* if (!BG_SiegeCheckClassLegality(team, className) && + Q_stricmp(client->sess.siegeClass, "none")) + { //if it isn't legal pop up the class menu + trap_SendServerCommand(ent-g_entities, "scl"); + } +*/ + //Now that the team is legal for sure, we'll go ahead and get an index for it. + client->siegeClass = BG_SiegeFindClassIndexByName(className); + if (client->siegeClass == -1) + { //ok, get the first valid class for the team you're on then, I guess. + BG_SiegeCheckClassLegality(team, className); + strcpy(client->sess.siegeClass, className); + client->siegeClass = BG_SiegeFindClassIndexByName(className); + } + else + { //otherwise, make sure the class we are using is legal. + G_ValidateSiegeClassForTeam(ent, team); + strcpy(className, client->sess.siegeClass); + } + + //Set the sabers if the class dictates + if (client->siegeClass != -1) + { + siegeClass_t *scl = &bgSiegeClasses[client->siegeClass]; + + if (scl->saber1[0]) + { + G_SetSaber(ent, 0, scl->saber1, qtrue); + } + else + { //default I guess + G_SetSaber(ent, 0, "Kyle", qtrue); + } + if (scl->saber2[0]) + { + G_SetSaber(ent, 1, scl->saber2, qtrue); + } + else + { //no second saber then + G_SetSaber(ent, 1, "none", qtrue); + } + + //make sure the saber models are updated + G_SaberModelSetup(ent); + + if (scl->forcedModel[0]) + { //be sure to override the model we actually use + strcpy(model, scl->forcedModel); + if (d_perPlayerGhoul2.integer) + { + if (Q_stricmp(model, client->modelname)) + { + strcpy(client->modelname, model); + modelChanged = qtrue; + } + } + } + + //force them to use their class model on the server, if the class dictates + if (G_PlayerHasCustomSkeleton(ent)) + { + if (Q_stricmp(model, client->modelname) || ent->localAnimIndex == 0) + { + strcpy(client->modelname, model); + modelChanged = qtrue; + } + } + } + } + else + { + strcpy(className, "none"); + } + + // Xbox experiment, let's allow people to change sabers immediately: + qboolean changedSaber = qfalse; + char *saber; + if( g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL ) + { + for( int l = 0; l < MAX_SABERS; ++l ) + { + switch (l) + { + case 0: + saber = &client->sess.saberType[0]; + break; + case 1: + saber = &client->sess.saber2Type[0]; + break; + default: + saber = NULL; + break; + } + + value = Info_ValueForKey (userinfo, va("saber%i", l+1)); + if (saber && + value && + (Q_stricmp(value, saber) || !saber[0] || !client->saber[0].model[0])) + { //doesn't match up (or our session saber is BS), we want to try setting it + if (G_SetSaber(ent, l, value, qfalse)) + { + changedSaber = qtrue; + } + else if (!saber[0] || !client->saber[0].model[0]) + { //Well, we still want to say they changed then (it means this is siege and we have some overrides) + changedSaber = qtrue; + } + } + } + } + + if (changedSaber) + { //make sure our new info is sent out to all the other clients, and give us a valid stance +// ClientUserinfoChanged( ent->s.number ); + + //make sure the saber models are updated + G_SaberModelSetup(ent); + + if (ent->client->saber[0].model[0] && + ent->client->saber[1].model[0]) + { //dual + ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = SS_DUAL; + } + else if (ent->client->saber[0].twoHanded) + { //staff + ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = SS_STAFF; + } + else + { + if (ent->client->sess.saberLevel < SS_FAST) + { + ent->client->sess.saberLevel = SS_FAST; + } + else if (ent->client->sess.saberLevel > SS_STRONG) + { + ent->client->sess.saberLevel = SS_STRONG; + } + ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel; + + if (g_gametype.integer != GT_SIEGE && + ent->client->ps.fd.saberAnimLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { + ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel = ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]; + } + } + } + + //Set the saber name + strcpy(saberName, client->sess.saberType); + strcpy(saber2Name, client->sess.saber2Type); + + // set max health + if (g_gametype.integer == GT_SIEGE && client->siegeClass != -1) + { + siegeClass_t *scl = &bgSiegeClasses[client->siegeClass]; + maxHealth = 100; + + if (scl->maxhealth) + { + maxHealth = scl->maxhealth; + } + + health = maxHealth; + } + else + { + maxHealth = 100; + health = 100; //atoi( Info_ValueForKey( userinfo, "handicap" ) ); + } + client->pers.maxHealth = health; + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > maxHealth ) { + client->pers.maxHealth = 100; + } + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + +/* NOTE: all client side now + + // team + switch( team ) { + case TEAM_RED: + ForceClientSkin(client, model, "red"); +// ForceClientSkin(client, headModel, "red"); + break; + case TEAM_BLUE: + ForceClientSkin(client, model, "blue"); +// ForceClientSkin(client, headModel, "blue"); + break; + } + // don't ever use a default skin in teamplay, it would just waste memory + // however bots will always join a team but they spawn in as spectator + if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) { + ForceClientSkin(client, model, "red"); +// ForceClientSkin(client, headModel, "red"); + } +*/ + + if (g_gametype.integer >= GT_TEAM) { + client->pers.teamInfo = qtrue; + } else { + s = Info_ValueForKey( userinfo, "teamoverlay" ); + if ( ! *s || atoi( s ) != 0 ) { + client->pers.teamInfo = qtrue; + } else { + client->pers.teamInfo = qfalse; + } + } + /* + s = Info_ValueForKey( userinfo, "cg_pmove_fixed" ); + if ( !*s || atoi( s ) == 0 ) { + client->pers.pmoveFixed = qfalse; + } + else { + client->pers.pmoveFixed = qtrue; + } + */ + + // team task (0 = none, 1 = offence, 2 = defence) + teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); + // team Leader (1 = leader, 0 is normal player) + teamLeader = client->sess.teamLeader; + + // colors + strcpy(c1, Info_ValueForKey( userinfo, "color1" )); + strcpy(c2, Info_ValueForKey( userinfo, "color2" )); + + strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" )); + strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" )); + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + if ( ent->r.svFlags & SVF_BOT ) { + s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d\\siegeclass\\%s\\st\\%s\\st2\\%s\\dt\\%i\\sdt\\%i", + client->pers.netname, team, model, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, + Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader, className, saberName, saber2Name, client->sess.duelTeam, client->sess.siegeDesiredTeam ); + } else { + if (g_gametype.integer == GT_SIEGE) + { //more crap to send + s = va("n\\%s\\t\\%i\\model\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d\\siegeclass\\%s\\st\\%s\\st2\\%s\\dt\\%i\\sdt\\%i", + client->pers.netname, client->sess.sessionTeam, model, redTeam, blueTeam, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader, className, saberName, saber2Name, client->sess.duelTeam, client->sess.siegeDesiredTeam); + } + else + { + s = va("n\\%s\\t\\%i\\model\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d\\st\\%s\\st2\\%s\\dt\\%i", + client->pers.netname, client->sess.sessionTeam, model, redTeam, blueTeam, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader, saberName, saber2Name, client->sess.duelTeam); + } + } + + trap_SetConfigstring( CS_PLAYERS+clientNum, s ); + + if(com_dedicated && com_dedicated->integer) { + extern void XBL_PL_UpdatePlayerName(int index, const char *newName); + XBL_PL_UpdatePlayerName(clientNum, client->pers.netname); + } + + if (modelChanged) //only going to be true for allowable server-side custom skeleton cases + { //update the server g2 instance if appropriate + char *modelname = Info_ValueForKey (userinfo, "model"); + SetupGameGhoul2Model(ent, modelname, NULL); + + if (ent->ghoul2 && ent->client) + { + ent->client->renderInfo.lastG2 = NULL; //update the renderinfo bolts next update. + } + + client->torsoAnimExecute = client->legsAnimExecute = -1; + client->torsoLastFlip = client->legsLastFlip = qfalse; + } + + if (g_logClientInfo.integer) + { + G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); + } +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and tournement +restarts. +============ +*/ +char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { + char *value; +// char *areabits; + gclient_t *client; + char userinfo[MAX_INFO_STRING]; + gentity_t *ent; + gentity_t *te; + +#ifdef _XBOX + // If this is a bot or a non-local client, force it into the regular + // memory slots so that the UI slot doesn't eat it +// if(isBot == qtrue || clientNum != ClientManager::ActiveClientNum()) +// ModelMem.forceSlot = true; +#endif + + ent = &g_entities[ clientNum ]; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if ( G_FilterPacket( value ) ) { +// ModelMem.forceSlot = false; + return "Banned."; + } + +/* + if ( !( ent->r.svFlags & SVF_BOT ) && !isBot && g_needpass.integer ) { + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) && + strcmp( g_password.string, value) != 0) { + static char sTemp[1024]; + Q_strncpyz(sTemp, G_GetStringEdString("MP_SVGAME","INVALID_ESCAPE_TO_MAIN"), sizeof (sTemp) ); + return sTemp;// return "Invalid password"; + } + } +*/ + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + + //assign the pointer for bg entity access + ent->playerState = &ent->client->ps; + +// areabits = client->areabits; + + memset( client, 0, sizeof(*client) ); + + client->pers.connected = CON_CONNECTING; + + // read or initialize the session data + if ( firstTime || level.newSession ) { + G_InitSessionData( client, userinfo, isBot ); + } + G_ReadSessionData( client ); + + if (g_gametype.integer == GT_SIEGE && + (firstTime || level.newSession)) + { //if this is the first time then auto-assign a desired siege team and show briefing for that team + client->sess.siegeDesiredTeam = 0;//PickTeam(ent->s.number); + /* + trap_SendServerCommand(ent->s.number, va("sb %i", client->sess.siegeDesiredTeam)); + */ + //don't just show it - they'll see it if they switch to a team on purpose. + } + + + if (g_gametype.integer == GT_SIEGE && client->sess.sessionTeam != TEAM_SPECTATOR) + { + if (firstTime || level.newSession) + { //start as spec + client->sess.siegeDesiredTeam = client->sess.sessionTeam; + client->sess.sessionTeam = TEAM_SPECTATOR; + } + } + else if (g_gametype.integer == GT_POWERDUEL && client->sess.sessionTeam != TEAM_SPECTATOR) + { + client->sess.sessionTeam = TEAM_SPECTATOR; + } + + if( isBot ) { + ent->r.svFlags |= SVF_BOT; + ent->inuse = qtrue; + if( !G_BotConnect( clientNum, !firstTime ) ) { +// ModelMem.forceSlot = false; + return "BotConnectfailed"; + } + } + + // get and distribute relevent paramters + G_LogPrintf( "ClientConnect: %i\n", clientNum ); + ClientUserinfoChanged( clientNum ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if ( firstTime ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLCONNECT")) ); + } + + if ( g_gametype.integer >= GT_TEAM && + client->sess.sessionTeam != TEAM_SPECTATOR ) { + BroadcastTeamChange( client, -1 ); + } + + // count current clients and rank for scoreboard + CalculateRanks(); + + te = G_TempEntity( vec3_origin, EV_CLIENTJOIN ); + te->r.svFlags |= SVF_BROADCAST; + te->s.eventParm = clientNum; + + // for statistics +// client->areabits = areabits; +// if ( !client->areabits ) +// client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 ); + +// ModelMem.forceSlot = false; + return NULL; +} + +void G_WriteClientSessionData( gclient_t *client ); + +#include "../namespace_begin.h" +void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName ); +#include "../namespace_end.h" + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the level. This will happen every level load, +and on transition between teams, but doesn't happen on respawns +============ +*/ +extern qboolean gSiegeRoundBegun; +extern qboolean gSiegeRoundEnded; +void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin); +void ClientBegin( int clientNum, qboolean allowTeamReset ) { + gentity_t *ent; + gclient_t *client; + gentity_t *tent; + int flags, i; + char userinfo[MAX_INFO_VALUE], *modelname; + + ent = g_entities + clientNum; + + if ((ent->r.svFlags & SVF_BOT) && g_gametype.integer >= GT_TEAM) + { + if (allowTeamReset) + { + const char *team = "Red"; + int preSess; + + //SetTeam(ent, ""); + ent->client->sess.sessionTeam = PickTeam(-1); + trap_GetUserinfo(clientNum, userinfo, MAX_INFO_STRING); + + if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) + { + ent->client->sess.sessionTeam = TEAM_RED; + } + + if (ent->client->sess.sessionTeam == TEAM_RED) + { + team = "Red"; + } + else + { + team = "Blue"; + } + + Info_SetValueForKey( userinfo, "team", team ); + + trap_SetUserinfo( clientNum, userinfo ); + + ent->client->ps.persistant[ PERS_TEAM ] = ent->client->sess.sessionTeam; + + preSess = ent->client->sess.sessionTeam; + G_ReadSessionData( ent->client ); + ent->client->sess.sessionTeam = preSess; + G_WriteClientSessionData(ent->client); + ClientUserinfoChanged( clientNum ); + ClientBegin(clientNum, qfalse); + return; + } + } + + client = level.clients + clientNum; + + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + //assign the pointer for bg entity access + ent->playerState = &ent->client->ps; + + client->pers.connected = CON_CONNECTED; + client->pers.enterTime = level.time; + client->pers.teamState.state = TEAM_BEGIN; + + // save eflags around this, because changing teams will + // cause this to happen with a valid entity, and we + // want to make sure the teleport bit is set right + // so the viewpoint doesn't interpolate through the + // world to the new position + flags = client->ps.eFlags; + + i = 0; + + while (i < NUM_FORCE_POWERS) + { + if (ent->client->ps.fd.forcePowersActive & (1 << i)) + { + WP_ForcePowerStop(ent, i); + } + i++; + } + + i = TRACK_CHANNEL_1; + + while (i < NUM_TRACK_CHANNELS) + { + if (ent->client->ps.fd.killSoundEntIndex[i-50] && ent->client->ps.fd.killSoundEntIndex[i-50] < MAX_GENTITIES && ent->client->ps.fd.killSoundEntIndex[i-50] > 0) + { + G_MuteSound(ent->client->ps.fd.killSoundEntIndex[i-50], CHAN_VOICE); + } + i++; + } + i = 0; + + memset( &client->ps, 0, sizeof( client->ps ) ); + client->ps.eFlags = flags; + + client->ps.hasDetPackPlanted = qfalse; + + //first-time force power initialization + WP_InitForcePowers( ent ); + + //init saber ent + WP_SaberInitBladeData( ent ); + + // First time model setup for that player. + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + modelname = Info_ValueForKey (userinfo, "model"); + SetupGameGhoul2Model(ent, modelname, NULL); + + if (ent->ghoul2 && ent->client) + { + ent->client->renderInfo.lastG2 = NULL; //update the renderinfo bolts next update. + } + + if (g_gametype.integer == GT_POWERDUEL && client->sess.sessionTeam != TEAM_SPECTATOR && + client->sess.duelTeam == DUELTEAM_FREE) + { + SetTeam(ent, "s"); + } + else + { + if (g_gametype.integer == GT_SIEGE && (!gSiegeRoundBegun || gSiegeRoundEnded)) + { + SetTeamQuick(ent, TEAM_SPECTATOR, qfalse); + } + + if ((ent->r.svFlags & SVF_BOT) && + g_gametype.integer != GT_SIEGE) + { + char *saberVal = Info_ValueForKey(userinfo, "saber1"); + char *saber2Val = Info_ValueForKey(userinfo, "saber2"); + + if (!saberVal || !saberVal[0]) + { //blah, set em up with a random saber + int r = rand()%50; + char sab1[1024]; + char sab2[1024]; + + if (r <= 17) + { + strcpy(sab1, "Katarn"); + strcpy(sab2, "none"); + } + else if (r <= 34) + { + strcpy(sab1, "Katarn"); + strcpy(sab2, "Katarn"); + } + else + { + strcpy(sab1, "dual_1"); + strcpy(sab2, "none"); + } + G_SetSaber(ent, 0, sab1, qfalse); + G_SetSaber(ent, 0, sab2, qfalse); + Info_SetValueForKey( userinfo, "saber1", sab1 ); + Info_SetValueForKey( userinfo, "saber2", sab2 ); + trap_SetUserinfo( clientNum, userinfo ); + } + else + { + G_SetSaber(ent, 0, saberVal, qfalse); + } + + if (saberVal && saberVal[0] && + (!saber2Val || !saber2Val[0])) + { + G_SetSaber(ent, 0, "none", qfalse); + Info_SetValueForKey( userinfo, "saber2", "none" ); + trap_SetUserinfo( clientNum, userinfo ); + } + else + { + G_SetSaber(ent, 0, saber2Val, qfalse); + } + } + + // locate ent at a spawn point + ClientSpawn( ent ); + } + + + if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { + // send event + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = ent->s.clientNum; + + if ( g_gametype.integer != GT_DUEL || g_gametype.integer == GT_POWERDUEL ) { +// trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLENTER")) ); + } + } + G_LogPrintf( "ClientBegin: %i\n", clientNum ); + + // count current clients and rank for scoreboard + CalculateRanks(); + +// G_ClearClientLog(clientNum); +} + +static qboolean AllForceDisabled(int force) +{ + int i; + + if (force) + { + for (i=0;iclient); + + if (ent->s.NPC_class == CLASS_VEHICLE || ent->localAnimIndex > 1) + { //no broken limbs for vehicles and non-humanoids + return; + } + + if (!arm) + { //repair him + ent->client->ps.brokenLimbs = 0; + return; + } + + if (ent->client->ps.fd.saberAnimLevel == SS_STAFF) + { //I'm too lazy to deal with this as well for now. + return; + } + + if (arm == BROKENLIMB_LARM) + { + if (ent->client->saber[1].model[0] && + ent->client->ps.weapon == WP_SABER && + !ent->client->ps.saberHolstered && + ent->client->saber[1].soundOff) + { //the left arm shuts off its saber upon being broken + G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOff); + } + } + + ent->client->ps.brokenLimbs = 0; //make sure it's cleared out + ent->client->ps.brokenLimbs |= (1 << arm); //this arm is now marked as broken + + //Do a pain anim based on the side. Since getting your arm broken does tend to hurt. + if (arm == BROKENLIMB_LARM) + { + anim = BOTH_PAIN2; + } + else if (arm == BROKENLIMB_RARM) + { + anim = BOTH_PAIN3; + } + + if (anim == -1) + { + return; + } + + G_SetAnim(ent, &ent->client->pers.cmd, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + + //This could be combined into a single event. But I guess limbs don't break often enough to + //worry about it. + G_EntitySound( ent, CHAN_VOICE, G_SoundIndex("*pain25.wav") ); + //FIXME: A nice bone snapping sound instead if possible + G_Sound(ent, CHAN_AUTO, G_SoundIndex( va("sound/player/bodyfall_human%i.wav", Q_irand(1, 3)) )); +} + +//Update the ghoul2 instance anims based on the playerstate values +#include "../namespace_begin.h" +qboolean BG_SaberStanceAnim( int anim ); +qboolean PM_RunningAnim( int anim ); +#include "../namespace_end.h" +void G_UpdateClientAnims(gentity_t *self, float animSpeedScale) +{ + static int f; + static int torsoAnim; + static int legsAnim; + static int firstFrame, lastFrame; + static int aFlags; + static float animSpeed, lAnimSpeedScale; + qboolean setTorso = qfalse; + + torsoAnim = (self->client->ps.torsoAnim); + legsAnim = (self->client->ps.legsAnim); + + if (self->client->ps.saberLockFrame) + { + trap_G2API_SetBoneAnim(self->ghoul2, 0, "model_root", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150); + trap_G2API_SetBoneAnim(self->ghoul2, 0, "lower_lumbar", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150); + trap_G2API_SetBoneAnim(self->ghoul2, 0, "Motion", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150); + return; + } + + if (self->localAnimIndex > 1 && + bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame == 0 && + bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames == 0) + { //We'll allow this for non-humanoids. + goto tryTorso; + } + + if (self->client->legsAnimExecute != legsAnim || self->client->legsLastFlip != self->client->ps.legsFlip) + { + animSpeed = 50.0f / bgAllAnims[self->localAnimIndex].anims[legsAnim].frameLerp; + lAnimSpeedScale = (animSpeed *= animSpeedScale); + + if (bgAllAnims[self->localAnimIndex].anims[legsAnim].loopFrames != -1) + { + aFlags = BONE_ANIM_OVERRIDE_LOOP; + } + else + { + aFlags = BONE_ANIM_OVERRIDE_FREEZE; + } + + if (animSpeed < 0) + { + lastFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame; + firstFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame + bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames; + } + else + { + firstFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame; + lastFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame + bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames; + } + + aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on server position, but it's here just for the sake of matching them. + + trap_G2API_SetBoneAnim(self->ghoul2, 0, "model_root", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + self->client->legsAnimExecute = legsAnim; + self->client->legsLastFlip = self->client->ps.legsFlip; + } + +tryTorso: + if (self->localAnimIndex > 1 && + bgAllAnims[self->localAnimIndex].anims[torsoAnim].firstFrame == 0 && + bgAllAnims[self->localAnimIndex].anims[torsoAnim].numFrames == 0) + + { //If this fails as well just return. + return; + } + else if (self->s.number >= MAX_CLIENTS && + self->s.NPC_class == CLASS_VEHICLE) + { //we only want to set the root bone for vehicles + return; + } + + if ((self->client->torsoAnimExecute != torsoAnim || self->client->torsoLastFlip != self->client->ps.torsoFlip) && + !self->noLumbar) + { + aFlags = 0; + animSpeed = 0; + + f = torsoAnim; + + BG_SaberStartTransAnim(self->client->ps.fd.saberAnimLevel, f, &animSpeedScale, self->client->ps.brokenLimbs); + + animSpeed = 50.0f / bgAllAnims[self->localAnimIndex].anims[f].frameLerp; + lAnimSpeedScale = (animSpeed *= animSpeedScale); + + if (bgAllAnims[self->localAnimIndex].anims[f].loopFrames != -1) + { + aFlags = BONE_ANIM_OVERRIDE_LOOP; + } + else + { + aFlags = BONE_ANIM_OVERRIDE_FREEZE; + } + + aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on client position, but it's here just for the sake of matching them. + + if (animSpeed < 0) + { + lastFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame; + firstFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame + bgAllAnims[self->localAnimIndex].anims[f].numFrames; + } + else + { + firstFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame; + lastFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame + bgAllAnims[self->localAnimIndex].anims[f].numFrames; + } + + trap_G2API_SetBoneAnim(self->ghoul2, 0, "lower_lumbar", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, /*firstFrame why was it this before?*/-1, 150); + + self->client->torsoAnimExecute = torsoAnim; + self->client->torsoLastFlip = self->client->ps.torsoFlip; + + setTorso = qtrue; + } + + if (setTorso && + self->localAnimIndex <= 1) + { //only set the motion bone for humanoids. + trap_G2API_SetBoneAnim(self->ghoul2, 0, "Motion", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + } + +#if 0 //disabled for now + if (self->client->ps.brokenLimbs != self->client->brokenLimbs || + setTorso) + { + if (self->localAnimIndex <= 1 && self->client->ps.brokenLimbs && + (self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM))) + { //broken left arm + char *brokenBone = "lhumerus"; + animation_t *armAnim; + int armFirstFrame; + int armLastFrame; + int armFlags = 0; + float armAnimSpeed; + + armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_DEAD21 ]; + self->client->brokenLimbs = self->client->ps.brokenLimbs; + + armFirstFrame = armAnim->firstFrame; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND); + + trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150); + } + else if (self->localAnimIndex <= 1 && self->client->ps.brokenLimbs && + (self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM))) + { //broken right arm + char *brokenBone = "rhumerus"; + char *supportBone = "lhumerus"; + + self->client->brokenLimbs = self->client->ps.brokenLimbs; + + //Only put the arm in a broken pose if the anim is such that we + //want to allow it. + if ((//self->client->ps.weapon == WP_MELEE || + self->client->ps.weapon != WP_SABER || + BG_SaberStanceAnim(self->client->ps.torsoAnim) || + PM_RunningAnim(self->client->ps.torsoAnim)) && + (!self->client->saber[1].model[0] || self->client->ps.weapon != WP_SABER)) + { + int armFirstFrame; + int armLastFrame; + int armFlags = 0; + float armAnimSpeed; + animation_t *armAnim; + + if (self->client->ps.weapon == WP_MELEE || + self->client->ps.weapon == WP_SABER || + self->client->ps.weapon == WP_BRYAR_PISTOL) + { //don't affect this arm if holding a gun, just make the other arm support it + armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_ATTACK2 ]; + + //armFirstFrame = armAnim->firstFrame; + armFirstFrame = armAnim->firstFrame+armAnim->numFrames; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND); + + trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150); + } + else + { //we want to keep the broken bone updated for some cases + trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + } + + if (self->client->ps.torsoAnim != BOTH_MELEE1 && + self->client->ps.torsoAnim != BOTH_MELEE2 && + (self->client->ps.torsoAnim == TORSO_WEAPONREADY2 || self->client->ps.torsoAnim == BOTH_ATTACK2 || self->client->ps.weapon < WP_BRYAR_PISTOL)) + { + //Now set the left arm to "support" the right one + armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_STAND2 ]; + armFirstFrame = armAnim->firstFrame; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND); + + trap_G2API_SetBoneAnim(self->ghoul2, 0, supportBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150); + } + else + { //we want to keep the support bone updated for some cases + trap_G2API_SetBoneAnim(self->ghoul2, 0, supportBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + } + } + else + { //otherwise, keep it set to the same as the torso + trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + trap_G2API_SetBoneAnim(self->ghoul2, 0, supportBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + } + } + else if (self->client->brokenLimbs) + { //remove the bone now so it can be set again + char *brokenBone = NULL; + int broken = 0; + + //Warning: Don't remove bones that you've added as bolts unless you want to invalidate your bolt index + //(well, in theory, I haven't actually run into the problem) + if (self->client->brokenLimbs & (1<client->brokenLimbs & (1<ghoul2, 0, "lhumerus", 0, 1, 0, 0, level.time, -1, 0); + trap_G2API_RemoveBone(self->ghoul2, "lhumerus", 0); + } + + assert(brokenBone); + + //Set the flags and stuff to 0, so that the remove will succeed + trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, 0, 1, 0, 0, level.time, -1, 0); + + //Now remove it + trap_G2API_RemoveBone(self->ghoul2, brokenBone, 0); + self->client->brokenLimbs &= ~broken; + } + } +#endif +} + +/* +=========== +ClientSpawn + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState +============ +*/ +extern qboolean WP_HasForcePowers( const playerState_t *ps ); +void ClientSpawn(gentity_t *ent) { + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + clientPersistant_t saved; + clientSession_t savedSess; + int persistant[MAX_PERSISTANT]; + gentity_t *spawnPoint; + int flags, gameFlags; + int savedPing; + int accuracy_hits, accuracy_shots; + int eventSequence; + char userinfo[MAX_INFO_STRING]; + forcedata_t savedForce; + int saveSaberNum = ENTITYNUM_NONE; + int wDisable = 0; + int savedSiegeIndex = 0; + int maxHealth; + saberInfo_t saberSaved[MAX_SABERS]; + int l = 0; + void *g2WeaponPtrs[MAX_SABERS]; + char *value; + char *saber; + qboolean changedSaber = qfalse; + qboolean inSiegeWithClass = qfalse; + + index = ent - g_entities; + client = ent->client; + + //first we want the userinfo so we can see if we should update this client's saber -rww + trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); + +#ifdef _XBOX + if (!(ent->r.svFlags & SVF_BOT) && ClientManager::splitScreenMode == qtrue) + { + if (ClientManager::ActiveClientNum() == 0) + { + Info_SetValueForKey(userinfo, "name", "P1"); + } + else + { + Info_SetValueForKey(userinfo, "name", "P2"); + } + } +#endif + + while (l < MAX_SABERS) + { + switch (l) + { + case 0: + saber = &ent->client->sess.saberType[0]; + break; + case 1: + saber = &ent->client->sess.saber2Type[0]; + break; + default: + saber = NULL; + break; + } + + value = Info_ValueForKey (userinfo, va("saber%i", l+1)); + if (saber && + value && + (Q_stricmp(value, saber) || !saber[0] || !ent->client->saber[0].model[0])) + { //doesn't match up (or our session saber is BS), we want to try setting it + if (G_SetSaber(ent, l, value, qfalse)) + { + changedSaber = qtrue; + } + else if (!saber[0] || !ent->client->saber[0].model[0]) + { //Well, we still want to say they changed then (it means this is siege and we have some overrides) + changedSaber = qtrue; + } + } + l++; + } + + if (changedSaber) + { //make sure our new info is sent out to all the other clients, and give us a valid stance + ClientUserinfoChanged( ent->s.number ); + + //make sure the saber models are updated + G_SaberModelSetup(ent); + + l = 0; + while (l < MAX_SABERS) + { //go through and make sure both sabers match the userinfo + switch (l) + { + case 0: + saber = &ent->client->sess.saberType[0]; + break; + case 1: + saber = &ent->client->sess.saber2Type[0]; + break; + default: + saber = NULL; + break; + } + + value = Info_ValueForKey (userinfo, va("saber%i", l+1)); + + if (Q_stricmp(value, saber)) + { //they don't match up, force the user info + Info_SetValueForKey(userinfo, va("saber%i", l+1), saber); + trap_SetUserinfo( ent->s.number, userinfo ); + } + l++; + } + + if (ent->client->saber[0].model[0] && + ent->client->saber[1].model[0]) + { //dual + ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = SS_DUAL; + } + else if (ent->client->saber[0].twoHanded) + { //staff + ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = SS_STAFF; + } + else + { + if (ent->client->sess.saberLevel < SS_FAST) + { + ent->client->sess.saberLevel = SS_FAST; + } + else if (ent->client->sess.saberLevel > SS_STRONG) + { + ent->client->sess.saberLevel = SS_STRONG; + } + ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel; + + if (g_gametype.integer != GT_SIEGE && + ent->client->ps.fd.saberAnimLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { + ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel = ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]; + } + } + } + l = 0; + + if (client->ps.fd.forceDoInit) + { //force a reread of force powers + WP_InitForcePowers( ent ); + client->ps.fd.forceDoInit = 0; + } + + if (ent->client->ps.fd.saberAnimLevel != SS_STAFF && + ent->client->ps.fd.saberAnimLevel != SS_DUAL && + ent->client->ps.fd.saberAnimLevel == ent->client->ps.fd.saberDrawAnimLevel && + ent->client->ps.fd.saberAnimLevel == ent->client->sess.saberLevel) + { + if (ent->client->sess.saberLevel < SS_FAST) + { + ent->client->sess.saberLevel = SS_FAST; + } + else if (ent->client->sess.saberLevel > SS_STRONG) + { + ent->client->sess.saberLevel = SS_STRONG; + } + ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel; + + if (g_gametype.integer != GT_SIEGE && + ent->client->ps.fd.saberAnimLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { + ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel = ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]; + } + } + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + spawnPoint = SelectSpectatorSpawnPoint ( + spawn_origin, spawn_angles); + } else if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) { + // all base oriented team games use the CTF spawn points + spawnPoint = SelectCTFSpawnPoint ( + client->sess.sessionTeam, + client->pers.teamState.state, + spawn_origin, spawn_angles); + } + else if (g_gametype.integer == GT_SIEGE) + { + spawnPoint = SelectSiegeSpawnPoint ( + client->siegeClass, + client->sess.sessionTeam, + client->pers.teamState.state, + spawn_origin, spawn_angles); + } + else { + do { + if (g_gametype.integer == GT_POWERDUEL) + { + spawnPoint = SelectDuelSpawnPoint(client->sess.duelTeam, client->ps.origin, spawn_origin, spawn_angles); + } + else if (g_gametype.integer == GT_DUEL) + { // duel + spawnPoint = SelectDuelSpawnPoint(DUELTEAM_SINGLE, client->ps.origin, spawn_origin, spawn_angles); + } + else + { + // the first spawn should be at a good looking spot + if ( !client->pers.initialSpawn && client->pers.localClient ) { + client->pers.initialSpawn = qtrue; + spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); + } else { + // don't spawn near existing origin if possible + spawnPoint = SelectSpawnPoint ( + client->ps.origin, + spawn_origin, spawn_angles); + } + } + + // Tim needs to prevent bots from spawning at the initial point + // on q3dm0... + if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { + continue; // try again + } + // just to be symetric, we have a nohumans option... + if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { + continue; // try again + } + + break; + + } while ( 1 ); + } + client->pers.teamState.state = TEAM_ACTIVE; + + // toggle the teleport bit so the client knows to not lerp + // and never clear the voted flag + flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT ); + flags ^= EF_TELEPORT_BIT; + gameFlags = ent->client->mGameFlags & ( PSG_VOTED | PSG_TEAMVOTED); + + // clear everything but the persistant data + + saved = client->pers; + savedSess = client->sess; + savedPing = client->ps.ping; +// savedAreaBits = client->areabits; + accuracy_hits = client->accuracy_hits; + accuracy_shots = client->accuracy_shots; + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + persistant[i] = client->ps.persistant[i]; + } + eventSequence = client->ps.eventSequence; + + savedForce = client->ps.fd; + + saveSaberNum = client->ps.saberEntityNum; + + savedSiegeIndex = client->siegeClass; + + l = 0; + while (l < MAX_SABERS) + { + saberSaved[l] = client->saber[l]; + g2WeaponPtrs[l] = client->weaponGhoul2[l]; + l++; + } + + i = 0; + while (i < HL_MAX) + { + ent->locationDamage[i] = 0; + i++; + } + + memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? + client->bodyGrabIndex = ENTITYNUM_NONE; + + //Get the skin RGB based on his userinfo + value = Info_ValueForKey (userinfo, "char_color_red"); + if (value) + { + client->ps.customRGBA[0] = atoi(value); + } + else + { + client->ps.customRGBA[0] = 255; + } + + value = Info_ValueForKey (userinfo, "char_color_green"); + if (value) + { + client->ps.customRGBA[1] = atoi(value); + } + else + { + client->ps.customRGBA[1] = 255; + } + + value = Info_ValueForKey (userinfo, "char_color_blue"); + if (value) + { + client->ps.customRGBA[2] = atoi(value); + } + else + { + client->ps.customRGBA[2] = 255; + } + + if ((client->ps.customRGBA[0]+client->ps.customRGBA[1]+client->ps.customRGBA[2]) < 100) + { //hmm, too dark! + client->ps.customRGBA[0] = client->ps.customRGBA[1] = client->ps.customRGBA[2] = 255; + } + + client->ps.customRGBA[3]=255; + + client->siegeClass = savedSiegeIndex; + + l = 0; + while (l < MAX_SABERS) + { + client->saber[l] = saberSaved[l]; + client->weaponGhoul2[l] = g2WeaponPtrs[l]; + l++; + } + + //or the saber ent num + client->ps.saberEntityNum = saveSaberNum; + client->saberStoredIndex = saveSaberNum; + + client->ps.fd = savedForce; + + client->ps.duelIndex = ENTITYNUM_NONE; + + //spawn with 100 + client->ps.jetpackFuel = 100; + client->ps.cloakFuel = 100; + + client->pers = saved; + client->sess = savedSess; + client->ps.ping = savedPing; +// client->areabits = savedAreaBits; + client->accuracy_hits = accuracy_hits; + client->accuracy_shots = accuracy_shots; + client->lastkilled_client = -1; + + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + client->ps.persistant[i] = persistant[i]; + } + client->ps.eventSequence = eventSequence; + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; + + client->airOutTime = level.time + 12000; + + // set max health + if (g_gametype.integer == GT_SIEGE && client->siegeClass != -1) + { + siegeClass_t *scl = &bgSiegeClasses[client->siegeClass]; + maxHealth = 100; + + if (scl->maxhealth) + { + maxHealth = scl->maxhealth; + } + } + else + { + maxHealth = 100; + } + client->pers.maxHealth = maxHealth;//atoi( Info_ValueForKey( userinfo, "handicap" ) ); + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > maxHealth ) { + client->pers.maxHealth = 100; + } + // clear entity values + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + client->ps.eFlags = flags; + client->mGameFlags = gameFlags; + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[index]; + ent->playerState = &ent->client->ps; + ent->takedamage = qtrue; + ent->inuse = qtrue; + ent->classname = "player"; + ent->r.contents = CONTENTS_BODY; + ent->clipmask = MASK_PLAYERSOLID; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + + VectorCopy (playerMins, ent->r.mins); + VectorCopy (playerMaxs, ent->r.maxs); + client->ps.crouchheight = CROUCH_MAXS_2; + client->ps.standheight = DEFAULT_MAXS_2; + + client->ps.clientNum = index; + //give default weapons + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE ); + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { + wDisable = g_duelWeaponDisable.integer; + } + else + { + wDisable = g_weaponDisable.integer; + } + + + + if ( g_gametype.integer != GT_HOLOCRON + && g_gametype.integer != GT_JEDIMASTER + && !HasSetSaberOnly() + && !AllForceDisabled( g_forcePowerDisable.integer ) + && g_trueJedi.integer ) + { + if ( g_gametype.integer >= GT_TEAM && (client->sess.sessionTeam == TEAM_BLUE || client->sess.sessionTeam == TEAM_RED) ) + {//In Team games, force one side to be merc and other to be jedi + if ( level.numPlayingClients > 0 ) + {//already someone in the game + int i, forceTeam = TEAM_SPECTATOR; + for ( i = 0 ; i < level.maxclients ; i++ ) + { + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == TEAM_BLUE || level.clients[i].sess.sessionTeam == TEAM_RED ) + {//in-game + if ( WP_HasForcePowers( &level.clients[i].ps ) ) + {//this side is using force + forceTeam = level.clients[i].sess.sessionTeam; + } + else + {//other team is using force + if ( level.clients[i].sess.sessionTeam == TEAM_BLUE ) + { + forceTeam = TEAM_RED; + } + else + { + forceTeam = TEAM_BLUE; + } + } + break; + } + } + if ( WP_HasForcePowers( &client->ps ) && client->sess.sessionTeam != forceTeam ) + {//using force but not on right team, switch him over + const char *teamName = TeamName( forceTeam ); + //client->sess.sessionTeam = forceTeam; + SetTeam( ent, (char *)teamName ); + return; + } + } + } + + if ( WP_HasForcePowers( &client->ps ) ) + { + client->ps.trueNonJedi = qfalse; + client->ps.trueJedi = qtrue; + //make sure they only use the saber + client->ps.weapon = WP_SABER; + client->ps.stats[STAT_WEAPONS] = (1 << WP_SABER); + } + else + {//no force powers set + client->ps.trueNonJedi = qtrue; + client->ps.trueJedi = qfalse; + if (!wDisable || !(wDisable & (1 << WP_BRYAR_PISTOL))) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL ); + } + if (!wDisable || !(wDisable & (1 << WP_BLASTER))) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BLASTER ); + } + if (!wDisable || !(wDisable & (1 << WP_BOWCASTER))) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BOWCASTER ); + } + client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER); + client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE); + client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max; + client->ps.weapon = WP_BRYAR_PISTOL; + } + } + else + {//jediVmerc is incompatible with this gametype, turn it off! + trap_Cvar_Set( "g_jediVmerc", "0" ); + if (g_gametype.integer == GT_HOLOCRON) + { + //always get free saber level 1 in holocron + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER ); //these are precached in g_items, ClearRegisteredItems() + } + else + { + if (client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER ); //these are precached in g_items, ClearRegisteredItems() + } + else + { //if you don't have saber attack rank then you don't get a saber + client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE); + } + } + + if (g_gametype.integer != GT_SIEGE) + { + if (!wDisable || !(wDisable & (1 << WP_BRYAR_PISTOL))) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL ); + } + else if (g_gametype.integer == GT_JEDIMASTER) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL ); + } + } + + if (g_gametype.integer == GT_JEDIMASTER) + { + client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER); + client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE); + } + + if (client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) + { + client->ps.weapon = WP_SABER; + } + else if (client->ps.stats[STAT_WEAPONS] & (1 << WP_BRYAR_PISTOL)) + { + client->ps.weapon = WP_BRYAR_PISTOL; + } + else + { + client->ps.weapon = WP_MELEE; + } + } + + /* + client->ps.stats[STAT_HOLDABLE_ITEMS] |= ( 1 << HI_BINOCULARS ); + client->ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_BINOCULARS, IT_HOLDABLE); + */ + + if (g_gametype.integer == GT_SIEGE && client->siegeClass != -1 && + client->sess.sessionTeam != TEAM_SPECTATOR) + { //well then, we will use a custom weaponset for our class + int m = 0; + + client->ps.stats[STAT_WEAPONS] = bgSiegeClasses[client->siegeClass].weapons; + + if (client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) + { + client->ps.weapon = WP_SABER; + } + else if (client->ps.stats[STAT_WEAPONS] & (1 << WP_BRYAR_PISTOL)) + { + client->ps.weapon = WP_BRYAR_PISTOL; + } + else + { + client->ps.weapon = WP_MELEE; + } + inSiegeWithClass = qtrue; + + while (m < WP_NUM_WEAPONS) + { + if (client->ps.stats[STAT_WEAPONS] & (1 << m)) + { + if (client->ps.weapon != WP_SABER) + { //try to find the highest ranking weapon we have + if (m > client->ps.weapon) + { + client->ps.weapon = m; + } + } + + if (m >= WP_BRYAR_PISTOL) + { //Max his ammo out for all the weapons he has. + if ( g_gametype.integer == GT_SIEGE + && m == WP_ROCKET_LAUNCHER ) + {//don't give full ammo! + //FIXME: extern this and check it when getting ammo from supplier, pickups or ammo stations! + if ( client->siegeClass != -1 && + (bgSiegeClasses[client->siegeClass].classflags & (1<ps.ammo[weaponData[m].ammoIndex] = 1; + } + else + { + client->ps.ammo[weaponData[m].ammoIndex] = 10; + } + } + else + { + if ( g_gametype.integer == GT_SIEGE + && client->siegeClass != -1 + && (bgSiegeClasses[client->siegeClass].classflags & (1<ps.ammo[weaponData[m].ammoIndex] = ammoData[weaponData[m].ammoIndex].max*2; + client->ps.eFlags |= EF_DOUBLE_AMMO; + } + else + { + client->ps.ammo[weaponData[m].ammoIndex] = ammoData[weaponData[m].ammoIndex].max; + } + } + } + } + m++; + } + } + + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1 && + client->sess.sessionTeam != TEAM_SPECTATOR) + { //use class-specified inventory + client->ps.stats[STAT_HOLDABLE_ITEMS] = bgSiegeClasses[client->siegeClass].invenItems; + client->ps.stats[STAT_HOLDABLE_ITEM] = 0; + } + else + { + client->ps.stats[STAT_HOLDABLE_ITEMS] = 0; + client->ps.stats[STAT_HOLDABLE_ITEM] = 0; + } + + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1 && + bgSiegeClasses[client->siegeClass].powerups && + client->sess.sessionTeam != TEAM_SPECTATOR) + { //this class has some start powerups + i = 0; + while (i < PW_NUM_POWERUPS) + { + if (bgSiegeClasses[client->siegeClass].powerups & (1 << i)) + { + client->ps.powerups[i] = Q3_INFINITE; + } + i++; + } + } + + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) + { + client->ps.stats[STAT_WEAPONS] = 0; + client->ps.stats[STAT_HOLDABLE_ITEMS] = 0; + client->ps.stats[STAT_HOLDABLE_ITEM] = 0; + } + +// nmckenzie: DESERT_SIEGE... or well, siege generally. This was over-writing the max value, which was NOT good for siege. + if ( inSiegeWithClass == qfalse ) + { + client->ps.ammo[AMMO_BLASTER] = 100; //ammoData[AMMO_BLASTER].max; //100 seems fair. + } +// client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max; +// client->ps.ammo[AMMO_FORCE] = ammoData[AMMO_FORCE].max; +// client->ps.ammo[AMMO_METAL_BOLTS] = ammoData[AMMO_METAL_BOLTS].max; +// client->ps.ammo[AMMO_ROCKETS] = ammoData[AMMO_ROCKETS].max; +/* + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_BRYAR_PISTOL); + if ( g_gametype.integer == GT_TEAM ) { + client->ps.ammo[WP_BRYAR_PISTOL] = 50; + } else { + client->ps.ammo[WP_BRYAR_PISTOL] = 100; + } +*/ + client->ps.rocketLockIndex = ENTITYNUM_NONE; + client->ps.rocketLockTime = 0; + + //rww - Set here to initialize the circling seeker drone to off. + //A quick note about this so I don't forget how it works again: + //ps.genericEnemyIndex is kept in sync between the server and client. + //When it gets set then an entitystate value of the same name gets + //set along with an entitystate flag in the shared bg code. Which + //is why a value needs to be both on the player state and entity state. + //(it doesn't seem to just carry over the entitystate value automatically + //because entity state value is derived from player state data or some + //such) + client->ps.genericEnemyIndex = -1; + +// client->ps.isJediMaster = qfalse; + + if (client->ps.fallingToDeath) + { + client->ps.fallingToDeath = 0; + client->noCorpse = qtrue; + } + + //Do per-spawn force power initialization + WP_SpawnInitForcePowers( ent ); + + // health will count down towards max_health + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1 && + bgSiegeClasses[client->siegeClass].starthealth) + { //class specifies a start health, so use it + ent->health = client->ps.stats[STAT_HEALTH] = bgSiegeClasses[client->siegeClass].starthealth; + } + else if ( g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL ) + {//only start with 100 health in Duel + if ( g_gametype.integer == GT_POWERDUEL && client->sess.duelTeam == DUELTEAM_LONE ) + { + if ( g_duel_fraglimit.integer ) + { + + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = + g_powerDuelStartHealth.integer - ((g_powerDuelStartHealth.integer - g_powerDuelEndHealth.integer) * (float)client->sess.wins / (float)g_duel_fraglimit.integer); + } + else + { + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = 150; + } + } + else + { + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = 100; + } + } + else if (client->ps.stats[STAT_MAX_HEALTH] <= 100) + { + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] * 1.25; + } + else if (client->ps.stats[STAT_MAX_HEALTH] < 125) + { + ent->health = client->ps.stats[STAT_HEALTH] = 125; + } + else + { + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; + } + + // Start with a small amount of armor as well. + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1 /*&& + bgSiegeClasses[client->siegeClass].startarmor*/) + { //class specifies a start armor amount, so use it + client->ps.stats[STAT_ARMOR] = bgSiegeClasses[client->siegeClass].startarmor; + } + else if ( g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL ) + {//no armor in duel + client->ps.stats[STAT_ARMOR] = 0; + } + else + { + client->ps.stats[STAT_ARMOR] = client->ps.stats[STAT_MAX_HEALTH] * 0.25; + } + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, client->ps.origin ); + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + SetClientViewAngle( ent, spawn_angles ); + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + + } else { + G_KillBox( ent ); + trap_LinkEntity (ent); + + // force the base weapon up + //client->ps.weapon = WP_BRYAR_PISTOL; + //client->ps.weaponstate = FIRST_WEAPON; + if (client->ps.weapon <= WP_NONE) + { + client->ps.weapon = WP_BRYAR_PISTOL; + } + + client->ps.torsoTimer = client->ps.legsTimer = 0; + + if (client->ps.weapon == WP_SABER) + { + G_SetAnim(ent, NULL, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS, 0); + } + else + { + G_SetAnim(ent, NULL, SETANIM_TORSO, TORSO_RAISEWEAP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS, 0); + client->ps.legsAnim = WeaponReadyAnim[client->ps.weapon]; + } + client->ps.weaponstate = WEAPON_RAISING; + client->ps.weaponTime = client->ps.torsoTimer; + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->latched_buttons = 0; + + if ( level.intermissiontime ) { + MoveClientToIntermission( ent ); + } else { + // fire the targets of the spawn point + G_UseTargets( spawnPoint, ent ); + + // select the highest weapon number available, after any + // spawn given items have fired + /* + client->ps.weapon = 1; + for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { + if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { + client->ps.weapon = i; + break; + } + } + */ + } + + //set teams for NPCs to recognize + if (g_gametype.integer == GT_SIEGE) + { //Imperial (team1) team is allied with "enemy" NPCs in this mode + if (client->sess.sessionTeam == SIEGETEAM_TEAM1) + { + client->playerTeam = ent->s.teamowner = NPCTEAM_ENEMY; + client->enemyTeam = NPCTEAM_PLAYER; + } + else + { + client->playerTeam = ent->s.teamowner = NPCTEAM_PLAYER; + client->enemyTeam = NPCTEAM_ENEMY; + } + } + else + { + client->playerTeam = ent->s.teamowner = NPCTEAM_PLAYER; + client->enemyTeam = NPCTEAM_ENEMY; + } + + /* + //scaling for the power duel opponent + if (g_gametype.integer == GT_POWERDUEL && + client->sess.duelTeam == DUELTEAM_LONE) + { + client->ps.iModelScale = 125; + VectorSet(ent->modelScale, 1.25f, 1.25f, 1.25f); + } + */ + //Disabled. At least for now. Not sure if I'll want to do it or not eventually. + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ent->client->pers.cmd.serverTime = level.time; + ClientThink( ent-g_entities, NULL ); + + // positively link the client, even if the command times are weird + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } + + if (g_spawnInvulnerability.integer) + { + ent->client->ps.eFlags |= EF_INVULNERABLE; + ent->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer; + } + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + + //rww - make sure client has a valid icarus instance + trap_ICARUS_FreeEnt( ent ); + trap_ICARUS_InitEnt( ent ); +} + + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. + +This should NOT be called directly by any game logic, +call trap_DropClient(), which will call this and do +server system housekeeping. +============ +*/ +void ClientDisconnect( int clientNum ) { + gentity_t *ent; + gentity_t *tent; + int i; + + // cleanup if we are kicking a bot that + // hasn't spawned yet + G_RemoveQueuedBotBegin( clientNum ); + + ent = g_entities + clientNum; + if ( !ent->client ) { + return; + } + + i = 0; + + while (i < NUM_FORCE_POWERS) + { + if (ent->client->ps.fd.forcePowersActive & (1 << i)) + { + WP_ForcePowerStop(ent, i); + } + i++; + } + + i = TRACK_CHANNEL_1; + + while (i < NUM_TRACK_CHANNELS) + { + if (ent->client->ps.fd.killSoundEntIndex[i-50] && ent->client->ps.fd.killSoundEntIndex[i-50] < MAX_GENTITIES && ent->client->ps.fd.killSoundEntIndex[i-50] > 0) + { + G_MuteSound(ent->client->ps.fd.killSoundEntIndex[i-50], CHAN_VOICE); + } + i++; + } + i = 0; + + if (ent->client->ps.m_iVehicleNum) + { //tell it I'm getting off + gentity_t *veh = &g_entities[ent->client->ps.m_iVehicleNum]; + + if (veh->inuse && veh->client && veh->m_pVehicle) + { + int pCon = ent->client->pers.connected; + + ent->client->pers.connected = 0; + veh->m_pVehicle->m_pVehicleInfo->Eject(veh->m_pVehicle, (bgEntity_t *)ent, qtrue); + ent->client->pers.connected = pCon; + } + } + + // stop any following clients + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR + && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW + && level.clients[i].sess.spectatorClient == clientNum ) { + StopFollowing( &g_entities[i] ); + } + } + + // send effect if they were completely connected + if ( ent->client->pers.connected == CON_CONNECTED + && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = ent->s.clientNum; + + // They don't get to take powerups with them! + // Especially important for stuff like CTF flags + TossClientItems( ent ); + } + + G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); + + // if we are playing in tourney mode, give a win to the other player and clear his frags for this round + if ( (g_gametype.integer == GT_DUEL ) + && !level.intermissiontime + && !level.warmupTime ) { + if ( level.sortedClients[1] == clientNum ) { + level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] = 0; + level.clients[ level.sortedClients[0] ].sess.wins++; + ClientUserinfoChanged( level.sortedClients[0] ); + } + else if ( level.sortedClients[0] == clientNum ) { + level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] = 0; + level.clients[ level.sortedClients[1] ].sess.wins++; + ClientUserinfoChanged( level.sortedClients[1] ); + } + } + + if (ent->ghoul2 && trap_G2_HaveWeGhoul2Models(ent->ghoul2)) + { + trap_G2API_CleanGhoul2Models(&ent->ghoul2); + } + i = 0; + while (i < MAX_SABERS) + { + if (ent->client->weaponGhoul2[i] && trap_G2_HaveWeGhoul2Models(ent->client->weaponGhoul2[i])) + { + trap_G2API_CleanGhoul2Models(&ent->client->weaponGhoul2[i]); + } + i++; + } + + trap_UnlinkEntity (ent); + ent->s.modelindex = 0; + ent->inuse = qfalse; + ent->classname = "disconnected"; + ent->client->pers.connected = CON_DISCONNECTED; + ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; + ent->client->sess.sessionTeam = TEAM_FREE; + ent->r.contents = 0; + +//#ifdef _XBOX +// char afilename[MAX_QPATH]; +// +// clientInfo_t *ci = &cgs.clientinfo[clientNum]; +// Com_sprintf( afilename, sizeof( afilename ), "models/players/%s/model.glm", ci->modelName ); +// +// ModelMem.FreeModelMemory(afilename); +// //ModelMem.ModelDecRef(afilename); +//#endif + + trap_SetConfigstring( CS_PLAYERS + clientNum, ""); + + CalculateRanks(); + + if ( ent->r.svFlags & SVF_BOT ) { + BotAIShutdownClient( clientNum, qfalse ); + } + +// G_ClearClientLog(clientNum); +} diff --git a/codemp/game/g_cmds.c b/codemp/game/g_cmds.c new file mode 100644 index 0000000..6ef7035 --- /dev/null +++ b/codemp/game/g_cmds.c @@ -0,0 +1,3958 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#include "g_local.h" +#include "bg_saga.h" + +#include "../../ui/menudef.h" // for the voice chats + +//rww - for getting bot commands... +int AcceptBotCommand(char *cmd, gentity_t *pl); +//end rww + +#include "../namespace_begin.h" +void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName ); +#include "../namespace_end.h" + +void Cmd_NPC_f( gentity_t *ent ); +void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin); + +/* +================== +DeathmatchScoreboardMessage + +================== +*/ +void DeathmatchScoreboardMessage( gentity_t *ent ) { + char entry[1024]; + char string[1400]; + int stringlength; + int i, j; + gclient_t *cl; + int numSorted, scoreFlags, accuracy, perfect; + + // send the latest information on all clients + string[0] = 0; + stringlength = 0; + scoreFlags = 0; + + numSorted = level.numConnectedClients; + + if (numSorted > MAX_CLIENT_SCORE_SEND) + { + numSorted = MAX_CLIENT_SCORE_SEND; + } + + for (i=0 ; i < numSorted ; i++) { + int ping; + + cl = &level.clients[level.sortedClients[i]]; + + if ( cl->pers.connected == CON_CONNECTING ) { + ping = -1; + } else { + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + } + + if( cl->accuracy_shots ) { + accuracy = cl->accuracy_hits * 100 / cl->accuracy_shots; + } + else { + accuracy = 0; + } + perfect = ( cl->ps.persistant[PERS_RANK] == 0 && cl->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; + + Com_sprintf (entry, sizeof(entry), + " %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i], + cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime)/60000, + scoreFlags, g_entities[level.sortedClients[i]].s.powerups, accuracy, + cl->ps.persistant[PERS_IMPRESSIVE_COUNT], + cl->ps.persistant[PERS_EXCELLENT_COUNT], + cl->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], + cl->ps.persistant[PERS_DEFEND_COUNT], + cl->ps.persistant[PERS_ASSIST_COUNT], + perfect, + cl->ps.persistant[PERS_CAPTURES]); + j = strlen(entry); + if (stringlength + j > 1022) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + //still want to know the total # of clients + i = level.numConnectedClients; + + trap_SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i, + level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE], + string ) ); +} + + +/* +================== +Cmd_Score_f + +Request current scoreboard information +================== +*/ +void Cmd_Score_f( gentity_t *ent ) { + DeathmatchScoreboardMessage( ent ); +} + + + +/* +================== +CheatsOk +================== +*/ +qboolean CheatsOk( gentity_t *ent ) { + if ( !g_cheats.integer ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOCHEATS"))); + return qfalse; + } + if ( ent->health <= 0 ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MUSTBEALIVE"))); + return qfalse; + } + return qtrue; +} + + +/* +================== +ConcatArgs +================== +*/ +char *ConcatArgs( int start ) { + int i, c, tlen; + static char line[MAX_STRING_CHARS]; + int len; + char arg[MAX_STRING_CHARS]; + + len = 0; + c = trap_Argc(); + for ( i = start ; i < c ; i++ ) { + trap_Argv( i, arg, sizeof( arg ) ); + tlen = strlen( arg ); + if ( len + tlen >= MAX_STRING_CHARS - 1 ) { + break; + } + memcpy( line + len, arg, tlen ); + len += tlen; + if ( i != c - 1 ) { + line[len] = ' '; + len++; + } + } + + line[len] = 0; + + return line; +} + +/* +================== +SanitizeString + +Remove case and control characters +================== +*/ +void SanitizeString( char *in, char *out ) { + while ( *in ) { + if ( *in == 27 ) { + in += 2; // skip color code + continue; + } + if ( *in < 32 ) { + in++; + continue; + } + *out++ = tolower( (unsigned char) *in++ ); + } + + *out = 0; +} + +/* +================== +ClientNumberFromString + +Returns a player number for either a number or name string +Returns -1 if invalid +================== +*/ +int ClientNumberFromString( gentity_t *to, char *s ) { + gclient_t *cl; + int idnum; + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + + // numeric values are just slot numbers + if (s[0] >= '0' && s[0] <= '9') { + idnum = atoi( s ); + if ( idnum < 0 || idnum >= level.maxclients ) { + trap_SendServerCommand( to-g_entities, va("print \"Bad client slot: %i\n\"", idnum)); + return -1; + } + + cl = &level.clients[idnum]; + if ( cl->pers.connected != CON_CONNECTED ) { + trap_SendServerCommand( to-g_entities, va("print \"Client %i is not active\n\"", idnum)); + return -1; + } + return idnum; + } + + // check for a name match + SanitizeString( s, s2 ); + for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + SanitizeString( cl->pers.netname, n2 ); + if ( !strcmp( n2, s2 ) ) { + return idnum; + } + } + + trap_SendServerCommand( to-g_entities, va("print \"User %s is not on the server\n\"", s)); + return -1; +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (gentity_t *cmdent, int baseArg) +{ + char name[MAX_TOKEN_CHARS]; + gentity_t *ent; + gitem_t *it; + int i; + qboolean give_all; + gentity_t *it_ent; + trace_t trace; + char arg[MAX_TOKEN_CHARS]; + + if ( !CheatsOk( cmdent ) ) { + return; + } + + if (baseArg) + { + char otherindex[MAX_TOKEN_CHARS]; + + trap_Argv( 1, otherindex, sizeof( otherindex ) ); + + if (!otherindex[0]) + { + Com_Printf("giveother requires that the second argument be a client index number.\n"); + return; + } + + i = atoi(otherindex); + + if (i < 0 || i >= MAX_CLIENTS) + { + Com_Printf("%i is not a client index\n", i); + return; + } + + ent = &g_entities[i]; + + if (!ent->inuse || !ent->client) + { + Com_Printf("%i is not an active client\n", i); + return; + } + } + else + { + ent = cmdent; + } + + trap_Argv( 1+baseArg, name, sizeof( name ) ); + + if (Q_stricmp(name, "all") == 0) + give_all = qtrue; + else + give_all = qfalse; + + if (give_all) + { + i = 0; + while (i < HI_NUM_HOLDABLE) + { + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] |= (1 << i); + i++; + } + i = 0; + } + + if (give_all || Q_stricmp( name, "health") == 0) + { + if (trap_Argc() == 3+baseArg) { + trap_Argv( 2+baseArg, arg, sizeof( arg ) ); + ent->health = atoi(arg); + if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]) { + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + } + else { + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + ent->client->ps.stats[STAT_WEAPONS] = (1 << (LAST_USEABLE_WEAPON+1)) - ( 1 << WP_NONE ); + if (!give_all) + return; + } + + if ( !give_all && Q_stricmp(name, "weaponnum") == 0 ) + { + trap_Argv( 2+baseArg, arg, sizeof( arg ) ); + ent->client->ps.stats[STAT_WEAPONS] |= (1 << atoi(arg)); + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + int num = 999; + if (trap_Argc() == 3+baseArg) { + trap_Argv( 2+baseArg, arg, sizeof( arg ) ); + num = atoi(arg); + } + for ( i = 0 ; i < MAX_WEAPONS ; i++ ) { + ent->client->ps.ammo[i] = num; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + if (trap_Argc() == 3+baseArg) { + trap_Argv( 2+baseArg, arg, sizeof( arg ) ); + ent->client->ps.stats[STAT_ARMOR] = atoi(arg); + } else { + ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + + if (!give_all) + return; + } + + if (Q_stricmp(name, "excellent") == 0) { + ent->client->ps.persistant[PERS_EXCELLENT_COUNT]++; + return; + } + if (Q_stricmp(name, "impressive") == 0) { + ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; + return; + } + if (Q_stricmp(name, "gauntletaward") == 0) { + ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++; + return; + } + if (Q_stricmp(name, "defend") == 0) { + ent->client->ps.persistant[PERS_DEFEND_COUNT]++; + return; + } + if (Q_stricmp(name, "assist") == 0) { + ent->client->ps.persistant[PERS_ASSIST_COUNT]++; + return; + } + + // spawn a specific item right on the player + if ( !give_all ) { + it = BG_FindItem (name); + if (!it) { + return; + } + + it_ent = G_Spawn(); + VectorCopy( ent->r.currentOrigin, it_ent->s.origin ); + it_ent->classname = it->classname; + G_SpawnItem (it_ent, it); + FinishSpawningItem(it_ent ); + memset( &trace, 0, sizeof( trace ) ); + Touch_Item (it_ent, ent, &trace); + if (it_ent->inuse) { + G_FreeEntity( it_ent ); + } + } +} + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (gentity_t *ent) +{ + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + if ( ent->client->noclip ) { + msg = "noclip OFF\n"; + } else { + msg = "noclip ON\n"; + } + ent->client->noclip = !ent->client->noclip; + + trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); +} + + +/* +================== +Cmd_LevelShot_f + +This is just to help generate the level pictures +for the menus. It goes to the intermission immediately +and sends over a command to the client to resize the view, +hide the scoreboard, and take a special screenshot +================== +*/ +void Cmd_LevelShot_f( gentity_t *ent ) { + if ( !CheatsOk( ent ) ) { + return; + } + + // doesn't work in single player + if ( g_gametype.integer != 0 ) { + trap_SendServerCommand( ent-g_entities, + "print \"Must be in g_gametype 0 for levelshot\n\"" ); + return; + } + + BeginIntermission(); + trap_SendServerCommand( ent-g_entities, "clientLevelShot" ); +} + + +/* +================== +Cmd_TeamTask_f + +From TA. +================== +*/ +void Cmd_TeamTask_f( gentity_t *ent ) { + char userinfo[MAX_INFO_STRING]; + char arg[MAX_TOKEN_CHARS]; + int task; + int client = ent->client - level.clients; + + if ( trap_Argc() != 2 ) { + return; + } + trap_Argv( 1, arg, sizeof( arg ) ); + task = atoi( arg ); + + trap_GetUserinfo(client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, "teamtask", va("%d", task)); + trap_SetUserinfo(client, userinfo); + ClientUserinfoChanged(client); +} + + + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( gentity_t *ent ) { + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + return; + } + if (ent->health <= 0) { + return; + } + + if ((g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) && + level.numPlayingClients > 1 && !level.warmupTime) + { + if (!g_allowDuelSuicide.integer) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "ATTEMPTDUELKILL")) ); + return; + } + } + + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; + player_die (ent, ent, ent, 100000, MOD_SUICIDE); +} + +gentity_t *G_GetDuelWinner(gclient_t *client) +{ + gclient_t *wCl; + int i; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + wCl = &level.clients[i]; + + if (wCl && wCl != client && /*wCl->ps.clientNum != client->ps.clientNum &&*/ + wCl->pers.connected == CON_CONNECTED && wCl->sess.sessionTeam != TEAM_SPECTATOR) + { + return &g_entities[wCl->ps.clientNum]; + } + } + + return NULL; +} + +/* +================= +BroadCastTeamChange + +Let everyone know about a team change +================= +*/ +void BroadcastTeamChange( gclient_t *client, int oldTeam ) +{ + client->ps.fd.forceDoInit = 1; //every time we change teams make sure our force powers are set right + + if (g_gametype.integer == GT_SIEGE) + { //don't announce these things in siege + return; + } + + // Don't announce these outside of Xbox Live either - where names change oddly: + extern cvar_t *xb_gameType; + if( xb_gameType->integer != 3 ) + return; + + if ( client->sess.sessionTeam == TEAM_RED ) { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", + client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEREDTEAM")) ); + } else if ( client->sess.sessionTeam == TEAM_BLUE ) { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", + client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEBLUETEAM"))); + } else if ( client->sess.sessionTeam == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR ) { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", + client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHESPECTATORS"))); + } else if ( client->sess.sessionTeam == TEAM_FREE ) { + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { + /* + gentity_t *currentWinner = G_GetDuelWinner(client); + + if (currentWinner && currentWinner->client) + { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s %s\n\"", + currentWinner->client->pers.netname, G_GetStringEdString("MP_SVGAME", "VERSUS"), client->pers.netname)); + } + else + { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", + client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEBATTLE"))); + } + */ + //NOTE: Just doing a vs. once it counts two players up + } + else + { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", + client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEBATTLE"))); + } + } + + G_LogPrintf ( "setteam: %i %s %s\n", + client - &level.clients[0], + TeamName ( oldTeam ), + TeamName ( client->sess.sessionTeam ) ); +} + +qboolean G_PowerDuelCheckFail(gentity_t *ent) +{ + int loners = 0; + int doubles = 0; + + if (!ent->client || ent->client->sess.duelTeam == DUELTEAM_FREE) + { + return qtrue; + } + + G_PowerDuelCount(&loners, &doubles, qfalse); + + if (ent->client->sess.duelTeam == DUELTEAM_LONE && loners >= 1) + { + return qtrue; + } + + if (ent->client->sess.duelTeam == DUELTEAM_DOUBLE && doubles >= 2) + { + return qtrue; + } + + return qfalse; +} + +/* +================= +SetTeam +================= +*/ +qboolean g_dontPenalizeTeam = qfalse; +qboolean g_preventTeamBegin = qfalse; +void SetTeam( gentity_t *ent, char *s ) { + int team, oldTeam; + gclient_t *client; + int clientNum; + spectatorState_t specState; + int specClient; + int teamLeader; + + qboolean forcedTeamChange; + + + // + // see what change is requested + // + client = ent->client; + + forcedTeamChange = qfalse; + if (!Q_strncmp(s,"force", 5)) + forcedTeamChange = qtrue; + + clientNum = client - level.clients; + specClient = 0; + specState = SPECTATOR_NOT; + if ( !Q_stricmp( s, "scoreboard" ) || !Q_stricmp( s, "score" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_SCOREBOARD; + } else if ( !Q_stricmp( s, "follow1" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FOLLOW; + specClient = -1; + } else if ( !Q_stricmp( s, "follow2" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FOLLOW; + specClient = -2; + } else if ( !Q_stricmp( s, "spectator" ) || !Q_stricmp( s, "s" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FREE; + } else if ( g_gametype.integer >= GT_TEAM ) { + // if running a team game, assign player to one of the teams + specState = SPECTATOR_NOT; + if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) || !Q_stricmp( s, "forcered" )) { + team = TEAM_RED; + } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) || !Q_stricmp( s, "forceblue" )) { + team = TEAM_BLUE; + } else { + // pick the team with the least number of players + //For now, don't do this. The legalize function will set powers properly now. + /* + if (g_forceBasedTeams.integer) + { + if (ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE) + { + team = TEAM_BLUE; + } + else + { + team = TEAM_RED; + } + } + else + { + */ + team = PickTeam( clientNum ); + //} + } + + if ( g_teamForceBalance.integer && !g_trueJedi.integer ) { + int counts[TEAM_NUM_TEAMS]; + + counts[TEAM_BLUE] = TeamCount( ent->client->ps.clientNum, TEAM_BLUE ); + counts[TEAM_RED] = TeamCount( ent->client->ps.clientNum, TEAM_RED ); + + // We allow a spread of two + if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) { + //For now, don't do this. The legalize function will set powers properly now. + /* + if (g_forceBasedTeams.integer && ent->client->ps.fd.forceSide == FORCE_DARKSIDE) + { + trap_SendServerCommand( ent->client->ps.clientNum, + va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYRED_SWITCH")) ); + } + else + */ + { + trap_SendServerCommand( ent->client->ps.clientNum, + va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYRED")) ); + } + return; // ignore the request + } + if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) { + //For now, don't do this. The legalize function will set powers properly now. + /* + if (g_forceBasedTeams.integer && ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE) + { + trap_SendServerCommand( ent->client->ps.clientNum, + va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYBLUE_SWITCH")) ); + } + else + */ + { + trap_SendServerCommand( ent->client->ps.clientNum, + va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYBLUE")) ); + } + return; // ignore the request + } + + // It's ok, the team we are switching to has less or same number of players + } + + //For now, don't do this. The legalize function will set powers properly now. + /* + if (g_forceBasedTeams.integer) + { + if (team == TEAM_BLUE && ent->client->ps.fd.forceSide != FORCE_LIGHTSIDE) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MUSTBELIGHT")) ); + return; + } + if (team == TEAM_RED && ent->client->ps.fd.forceSide != FORCE_DARKSIDE) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MUSTBEDARK")) ); + return; + } + } + */ + + } else { + // force them to spectators if there aren't any spots free + team = TEAM_FREE; + } + + if (g_gametype.integer == GT_SIEGE) + { + if (client->tempSpectate >= level.time && + team == TEAM_SPECTATOR) + { //sorry, can't do that. + return; + } + + client->sess.siegeDesiredTeam = team; + //oh well, just let them go. + /* + if (team != TEAM_SPECTATOR) + { //can't switch to anything in siege unless you want to switch to being a fulltime spectator + //fill them in on their objectives for this team now + trap_SendServerCommand(ent-g_entities, va("sb %i", client->sess.siegeDesiredTeam)); + + trap_SendServerCommand( ent-g_entities, va("print \"You will be on the selected team the next time the round begins.\n\"") ); + return; + } + */ + if (client->sess.sessionTeam != TEAM_SPECTATOR && + team != TEAM_SPECTATOR) + { //not a spectator now, and not switching to spec, so you have to wait til you die. + //trap_SendServerCommand( ent-g_entities, va("print \"You will be on the selected team the next time you respawn.\n\"") ); + qboolean doBegin; + if (ent->client->tempSpectate >= level.time) + { + doBegin = qfalse; + } + else + { + doBegin = qtrue; + } + + if (doBegin) + { + // Kill them so they automatically respawn in the team they wanted. + if (ent->health > 0) + { + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + player_die( ent, ent, ent, 100000, MOD_TEAM_CHANGE ); + } + } + + if (ent->client->sess.sessionTeam != ent->client->sess.siegeDesiredTeam) + { + SetTeamQuick(ent, ent->client->sess.siegeDesiredTeam, qfalse); + } + + return; + } + } + + // override decision if limiting the players + if ( (g_gametype.integer == GT_DUEL) + && level.numNonSpectatorClients >= 2 ) + { + team = TEAM_SPECTATOR; + } + else if ( (g_gametype.integer == GT_POWERDUEL) + && (level.numPlayingClients >= 3 || G_PowerDuelCheckFail(ent)) ) + { + team = TEAM_SPECTATOR; + } + else if ( g_maxGameClients.integer > 0 && + level.numNonSpectatorClients >= g_maxGameClients.integer ) + { + team = TEAM_SPECTATOR; + } + + // + // decide if we will allow the change + // + oldTeam = client->sess.sessionTeam; + if ( team == oldTeam && team != TEAM_SPECTATOR && !forcedTeamChange) { + return; + } + + // + // execute the team change + // + + //If it's siege then show the mission briefing for the team you just joined. +// if (g_gametype.integer == GT_SIEGE && team != TEAM_SPECTATOR) +// { +// trap_SendServerCommand(clientNum, va("sb %i", team)); +// } + + // if the player was dead leave the body + if ( client->ps.stats[STAT_HEALTH] <= 0 && client->sess.sessionTeam != TEAM_SPECTATOR ) { + MaintainBodyQueue(ent); + } + + // he starts at 'base' + client->pers.teamState.state = TEAM_BEGIN; + if ( oldTeam != TEAM_SPECTATOR ) { + // Kill him (makes sure he loses flags, etc) + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + g_dontPenalizeTeam = qtrue; + player_die (ent, ent, ent, 100000, MOD_SUICIDE); + g_dontPenalizeTeam = qfalse; + + } + // they go to the end of the line for tournements + if ( team == TEAM_SPECTATOR ) { + if ( (g_gametype.integer != GT_DUEL) || (oldTeam != TEAM_SPECTATOR) ) {//so you don't get dropped to the bottom of the queue for changing skins, etc. + client->sess.spectatorTime = level.time; + } + } + + client->sess.sessionTeam = team; + client->sess.spectatorState = specState; + client->sess.spectatorClient = specClient; + + client->sess.teamLeader = qfalse; + if ( team == TEAM_RED || team == TEAM_BLUE ) { + teamLeader = TeamLeader( team ); + // if there is no team leader or the team leader is a bot and this client is not a bot + if ( teamLeader == -1 || ( !(g_entities[clientNum].r.svFlags & SVF_BOT) && (g_entities[teamLeader].r.svFlags & SVF_BOT) ) ) { + //SetLeader( team, clientNum ); + } + } + // make sure there is a team leader on the team the player came from + if ( oldTeam == TEAM_RED || oldTeam == TEAM_BLUE ) { + CheckTeamLeader( oldTeam ); + } + + BroadcastTeamChange( client, oldTeam ); + + //make a disappearing effect where they were before teleporting them to the appropriate spawn point, + //if we were not on the spec team + if (oldTeam != TEAM_SPECTATOR) + { + gentity_t *tent = G_TempEntity( client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = clientNum; + } + + // get and distribute relevent paramters + ClientUserinfoChanged( clientNum ); + + if (!g_preventTeamBegin) + { + ClientBegin( clientNum, qfalse ); + } +} + +/* +================= +StopFollowing + +If the client being followed leaves the game, or you just want to drop +to free floating spectator mode +================= +*/ +void StopFollowing( gentity_t *ent ) { + ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; + ent->client->sess.sessionTeam = TEAM_SPECTATOR; + ent->client->sess.spectatorState = SPECTATOR_FREE; + ent->client->ps.pm_flags &= ~PMF_FOLLOW; + ent->r.svFlags &= ~SVF_BOT; + ent->client->ps.clientNum = ent - g_entities; + ent->client->ps.weapon = WP_NONE; + ent->client->ps.m_iVehicleNum = 0; + ent->client->ps.viewangles[ROLL] = 0.0f; + ent->client->ps.forceHandExtend = HANDEXTEND_NONE; + ent->client->ps.forceHandExtendTime = 0; + ent->client->ps.zoomMode = 0; + ent->client->ps.zoomLocked = 0; + ent->client->ps.zoomLockTime = 0; + ent->client->ps.legsAnim = 0; + ent->client->ps.legsTimer = 0; + ent->client->ps.torsoAnim = 0; + ent->client->ps.torsoTimer = 0; +} + +/* +================= +Cmd_Team_f +================= +*/ +void Cmd_Team_f( gentity_t *ent ) { + int oldTeam; + char s[MAX_TOKEN_CHARS]; + + if ( trap_Argc() != 2 ) { + oldTeam = ent->client->sess.sessionTeam; + switch ( oldTeam ) { + case TEAM_BLUE: + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTBLUETEAM")) ); + break; + case TEAM_RED: + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTREDTEAM")) ); + break; + case TEAM_FREE: + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTFREETEAM")) ); + break; + case TEAM_SPECTATOR: + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTSPECTEAM")) ); + break; + } + return; + } + +// if ( ent->client->switchTeamTime > level.time ) { +// trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) ); +// return; +// } + + + if (gEscaping) + { + return; + } + + // if they are playing a tournement game, count as a loss + if ( g_gametype.integer == GT_DUEL + && ent->client->sess.sessionTeam == TEAM_FREE ) {//in a tournament game + //disallow changing teams + trap_SendServerCommand( ent-g_entities, "print \"Cannot switch teams in Duel\n\"" ); + return; + //FIXME: why should this be a loss??? + //ent->client->sess.losses++; + } + + if (g_gametype.integer == GT_POWERDUEL) + { //don't let clients change teams manually at all in powerduel, it will be taken care of through automated stuff + trap_SendServerCommand( ent-g_entities, "print \"Cannot switch teams in Power Duel\n\"" ); + return; + } + + trap_Argv( 1, s, sizeof( s ) ); + + SetTeam( ent, s ); + + +// ent->client->switchTeamTime = level.time + 5000; +} + +/* +================= +Cmd_DuelTeam_f +================= +*/ +void Cmd_DuelTeam_f(gentity_t *ent) +{ + int oldTeam; + char s[MAX_TOKEN_CHARS]; + + if (g_gametype.integer != GT_POWERDUEL) + { //don't bother doing anything if this is not power duel + return; + } + + /* + if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) + { + trap_SendServerCommand( ent-g_entities, va("print \"You cannot change your duel team unless you are a spectator.\n\"")); + return; + } + */ + + if ( trap_Argc() != 2 ) + { //No arg so tell what team we're currently on. + oldTeam = ent->client->sess.duelTeam; + switch ( oldTeam ) + { + case DUELTEAM_FREE: + trap_SendServerCommand( ent-g_entities, va("print \"None\n\"") ); + break; + case DUELTEAM_LONE: + trap_SendServerCommand( ent-g_entities, va("print \"Single\n\"") ); + break; + case DUELTEAM_DOUBLE: + trap_SendServerCommand( ent-g_entities, va("print \"Double\n\"") ); + break; + default: + break; + } + return; + } + +// if ( ent->client->switchDuelTeamTime > level.time ) +// { //debounce for changing +// trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) ); +// return; +// } + + trap_Argv( 1, s, sizeof( s ) ); + + oldTeam = ent->client->sess.duelTeam; + + if (!Q_stricmp(s, "free")) + { + ent->client->sess.duelTeam = DUELTEAM_FREE; + } + else if (!Q_stricmp(s, "single")) + { + ent->client->sess.duelTeam = DUELTEAM_LONE; + } + else if (!Q_stricmp(s, "double")) + { + ent->client->sess.duelTeam = DUELTEAM_DOUBLE; + } + else + { + trap_SendServerCommand( ent-g_entities, va("print \"'%s' not a valid duel team.\n\"", s) ); + } + + if (oldTeam == ent->client->sess.duelTeam) + { //didn't actually change, so don't care. + return; + } + + if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) + { //ok..die + int curTeam = ent->client->sess.duelTeam; + ent->client->sess.duelTeam = oldTeam; + G_Damage(ent, ent, ent, NULL, ent->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE); + ent->client->sess.duelTeam = curTeam; + } + //reset wins and losses + ent->client->sess.wins = 0; + ent->client->sess.losses = 0; + + //get and distribute relevent paramters + ClientUserinfoChanged( ent->s.number ); + +// ent->client->switchDuelTeamTime = level.time + 5000; +} + +int G_TeamForSiegeClass(const char *clName) +{ + int i = 0; + int team = SIEGETEAM_TEAM1; + siegeTeam_t *stm = BG_SiegeFindThemeForTeam(team); + siegeClass_t *scl; + + if (!stm) + { + return 0; + } + + while (team <= SIEGETEAM_TEAM2) + { + scl = stm->classes[i]; + + if (scl && scl->name[0]) + { + if (!Q_stricmp(clName, scl->name)) + { + return team; + } + } + + i++; + if (i >= MAX_SIEGE_CLASSES || i >= stm->numClasses) + { + if (team == SIEGETEAM_TEAM2) + { + break; + } + team = SIEGETEAM_TEAM2; + stm = BG_SiegeFindThemeForTeam(team); + i = 0; + } + } + + return 0; +} + +/* +================= +Cmd_SiegeClass_f +================= +*/ +void Cmd_SiegeClass_f( gentity_t *ent ) +{ + char className[64]; + int team = 0; + int preScore; + qboolean startedAsSpec = qfalse; + + if (g_gametype.integer != GT_SIEGE) + { //classes are only valid for this gametype + return; + } + + if (!ent->client) + { + return; + } + + if (trap_Argc() < 1) + { + return; + } + +// if ( ent->client->switchClassTime > level.time ) +// { +// trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOCLASSSWITCH")) ); +// return; +// } + + if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) + { + startedAsSpec = qtrue; + } + + trap_Argv( 1, className, sizeof( className ) ); + + team = G_TeamForSiegeClass(className); + + if (!team) + { //not a valid class name + return; + } + + if (ent->client->sess.sessionTeam != team) + { //try changing it then + g_preventTeamBegin = qtrue; + if (team == TEAM_RED) + { + SetTeam(ent, "red"); + } + else if (team == TEAM_BLUE) + { + SetTeam(ent, "blue"); + } + g_preventTeamBegin = qfalse; + + if (ent->client->sess.sessionTeam != team) + { //failed, oh well + if (ent->client->sess.sessionTeam != TEAM_SPECTATOR || + ent->client->sess.siegeDesiredTeam != team) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOCLASSTEAM")) ); + return; + } + } + } + + //preserve 'is score + preScore = ent->client->ps.persistant[PERS_SCORE]; + + //Make sure the class is valid for the team + BG_SiegeCheckClassLegality(team, className); + + //Set the session data + strcpy(ent->client->sess.siegeClass, className); + + // get and distribute relevent paramters + ClientUserinfoChanged( ent->s.number ); + + if (ent->client->tempSpectate < level.time) + { + // Kill him (makes sure he loses flags, etc) + if (ent->health > 0 && !startedAsSpec) + { + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + player_die (ent, ent, ent, 100000, MOD_SUICIDE); + } + + if (ent->client->sess.sessionTeam == TEAM_SPECTATOR || startedAsSpec) + { //respawn them instantly. + ClientBegin( ent->s.number, qfalse ); + } + } + //set it back after we do all the stuff + ent->client->ps.persistant[PERS_SCORE] = preScore; + +// ent->client->switchClassTime = level.time + 5000; +} + +/* +================= +Cmd_ForceChanged_f +================= +*/ +void Cmd_ForceChanged_f( gentity_t *ent ) +{ + char fpChStr[1024]; + const char *buf; +// Cmd_Kill_f(ent); + + // Xbox experiment. Always allow instant changes to force powers. + if( g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL ) + { + WP_InitForcePowers( ent ); + // Also need to trade saber/melee if they changed that: + if( ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] ) + { + ent->client->ps.stats[STAT_WEAPONS] |= (1 << WP_SABER ); + ent->client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_MELEE ); + if( ent->client->ps.weapon == WP_MELEE ) + ent->client->ps.weapon = WP_SABER; + } + else + { + ent->client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER); + ent->client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE); + if( ent->client->ps.weapon == WP_SABER ) + ent->client->ps.weapon = WP_MELEE; + + for( int i = 0; i < MAX_SABERS; ++i ) + { + if(ent->client->weaponGhoul2[1]) + { + trap_G2API_CleanGhoul2Models(&(ent->client->weaponGhoul2[i])); + } + } + } + + goto argCheck; + } + + if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) + { //if it's a spec, just make the changes now + //trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "FORCEAPPLIED")) ); + //No longer print it, as the UI calls this a lot. + WP_InitForcePowers( ent ); + goto argCheck; + } + + buf = G_GetStringEdString("MP_SVGAME", "FORCEPOWERCHANGED"); + + strcpy(fpChStr, buf); + + trap_SendServerCommand( ent-g_entities, va("print \"%s%s\n\n\"", S_COLOR_GREEN, fpChStr) ); + + ent->client->ps.fd.forceDoInit = 1; +argCheck: + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { //If this is duel, don't even bother changing team in relation to this. + return; + } + + if (trap_Argc() > 1) + { + char arg[MAX_TOKEN_CHARS]; + + trap_Argv( 1, arg, sizeof( arg ) ); + + if (arg && arg[0]) + { //if there's an arg, assume it's a combo team command from the UI. + Cmd_Team_f(ent); + } + } +} + +qboolean G_SetSaber(gentity_t *ent, int saberNum, char *saberName, qboolean siegeOverride) +{ + char truncSaberName[64]; + int i = 0; + + if (!siegeOverride && + g_gametype.integer == GT_SIEGE && + ent->client->siegeClass != -1 && + ( + bgSiegeClasses[ent->client->siegeClass].saberStance || + bgSiegeClasses[ent->client->siegeClass].saber1[0] || + bgSiegeClasses[ent->client->siegeClass].saber2[0] + )) + { //don't let it be changed if the siege class has forced any saber-related things + return qfalse; + } + + while (saberName[i] && i < 64-1) + { + truncSaberName[i] = saberName[i]; + i++; + } + truncSaberName[i] = 0; + + if ( saberNum == 0 && (Q_stricmp( "none", truncSaberName ) == 0 || Q_stricmp( "remove", truncSaberName ) == 0) ) + { //can't remove saber 0 like this + strcpy(truncSaberName, "Kyle"); + } + + //Set the saber with the arg given. If the arg is + //not a valid sabername defaults will be used. + WP_SetSaber(ent->s.number, ent->client->saber, saberNum, truncSaberName); + + if (!ent->client->saber[0].model[0]) + { + assert(0); //should never happen! + strcpy(ent->client->sess.saberType, "none"); + } + else + { + strcpy(ent->client->sess.saberType, ent->client->saber[0].name); + } + + if (!ent->client->saber[1].model[0]) + { + strcpy(ent->client->sess.saber2Type, "none"); + } + else + { + strcpy(ent->client->sess.saber2Type, ent->client->saber[1].name); + } + + return qtrue; +} + +/* +================= +Cmd_Follow_f +================= +*/ +void Cmd_Follow_f( gentity_t *ent ) { + int i; + char arg[MAX_TOKEN_CHARS]; + + if ( trap_Argc() != 2 ) { + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { + StopFollowing( ent ); + } + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + i = ClientNumberFromString( ent, arg ); + if ( i == -1 ) { + return; + } + + // can't follow self + if ( &level.clients[ i ] == ent->client ) { + return; + } + + // can't follow another spectator + if ( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR ) { + return; + } + + // if they are playing a tournement game, count as a loss + if ( (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + && ent->client->sess.sessionTeam == TEAM_FREE ) { + //WTF??? + ent->client->sess.losses++; + } + + // first set them to spectator + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + SetTeam( ent, "spectator" ); + } + + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + ent->client->sess.spectatorClient = i; +} + +/* +================= +Cmd_FollowCycle_f +================= +*/ +void Cmd_FollowCycle_f( gentity_t *ent, int dir ) { + int clientnum; + int original; + + // if they are playing a tournement game, count as a loss + if ( (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + && ent->client->sess.sessionTeam == TEAM_FREE ) {\ + //WTF??? + ent->client->sess.losses++; + } + // first set them to spectator + if ( ent->client->sess.spectatorState == SPECTATOR_NOT ) { + SetTeam( ent, "spectator" ); + } + + if ( dir != 1 && dir != -1 ) { + G_Error( "Cmd_FollowCycle_f: bad dir %i", dir ); + } + + clientnum = ent->client->sess.spectatorClient; + original = clientnum; + do { + clientnum += dir; + if ( clientnum >= level.maxclients ) { + clientnum = 0; + } + if ( clientnum < 0 ) { + clientnum = level.maxclients - 1; + } + + // can only follow connected clients + if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) { + continue; + } + + // can't follow another spectator + if ( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR ) { + continue; + } + + // this is good, we can use it + ent->client->sess.spectatorClient = clientnum; + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + return; + } while ( clientnum != original ); + + // leave it where it was +} + + +/* +================== +G_Say +================== +*/ + +static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, char *locMsg ) +{ + if (!other) { + return; + } + if (!other->inuse) { + return; + } + if (!other->client) { + return; + } + if ( other->client->pers.connected != CON_CONNECTED ) { + return; + } + if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { + return; + } + /* + // no chatting to players in tournements + if ( (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + && other->client->sess.sessionTeam == TEAM_FREE + && ent->client->sess.sessionTeam != TEAM_FREE ) { + //Hmm, maybe some option to do so if allowed? Or at least in developer mode... + return; + } + */ + //They've requested I take this out. + + if (g_gametype.integer == GT_SIEGE && + ent->client && (ent->client->tempSpectate >= level.time || ent->client->sess.sessionTeam == TEAM_SPECTATOR) && + other->client->sess.sessionTeam != TEAM_SPECTATOR && + other->client->tempSpectate < level.time) + { //siege temp spectators should not communicate to ingame players + return; + } + + if (locMsg) + { + trap_SendServerCommand( other-g_entities, va("%s \"%s\" \"%s\" \"%c\" \"%s\"", + mode == SAY_TEAM ? "ltchat" : "lchat", + name, locMsg, color, message)); + } + else + { + trap_SendServerCommand( other-g_entities, va("%s \"%s%c%c%s\"", + mode == SAY_TEAM ? "tchat" : "chat", + name, Q_COLOR_ESCAPE, color, message)); + } +} + +#define EC "\x19" + +void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) { + int j; + gentity_t *other; + int color; + char name[64]; + // don't let text be too long for malicious reasons + char text[MAX_SAY_TEXT]; + char location[64]; + char *locMsg = NULL; + + if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { + mode = SAY_ALL; + } + + switch ( mode ) { + default: + case SAY_ALL: + G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); + Com_sprintf (name, sizeof(name), "%s%c%c"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + color = COLOR_GREEN; + break; + case SAY_TEAM: + G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText ); + if (Team_GetLocationMsg(ent, location, sizeof(location))) + { + Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ", + ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + locMsg = location; + } + else + { + Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ", + ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + } + color = COLOR_CYAN; + break; + case SAY_TELL: + if (target && g_gametype.integer >= GT_TEAM && + target->client->sess.sessionTeam == ent->client->sess.sessionTeam && + Team_GetLocationMsg(ent, location, sizeof(location))) + { + Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + locMsg = location; + } + else + { + Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + } + color = COLOR_MAGENTA; + break; + } + + Q_strncpyz( text, chatText, sizeof(text) ); + + if ( target ) { + G_SayTo( ent, target, mode, color, name, text, locMsg ); + return; + } + + // echo the text to the console + if ( g_dedicated.integer ) { + G_Printf( "%s%s\n", name, text); + } + + // send it to all the apropriate clients + for (j = 0; j < level.maxclients; j++) { + other = &g_entities[j]; + G_SayTo( ent, other, mode, color, name, text, locMsg ); + } +} + + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { + char *p; + + if ( trap_Argc () < 2 && !arg0 ) { + return; + } + + if (arg0) + { + p = ConcatArgs( 0 ); + } + else + { + p = ConcatArgs( 1 ); + } + + G_Say( ent, NULL, mode, p ); +} + +/* +================== +Cmd_Tell_f +================== +*/ +static void Cmd_Tell_f( gentity_t *ent ) { + int targetNum; + gentity_t *target; + char *p; + char arg[MAX_TOKEN_CHARS]; + + if ( trap_Argc () < 2 ) { + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + targetNum = atoi( arg ); + if ( targetNum < 0 || targetNum >= level.maxclients ) { + return; + } + + target = &g_entities[targetNum]; + if ( !target || !target->inuse || !target->client ) { + return; + } + + p = ConcatArgs( 2 ); + + G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); + G_Say( ent, target, SAY_TELL, p ); + // don't tell to the player self if it was already directed to this player + // also don't send the chat back to a bot + if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { + G_Say( ent, ent, SAY_TELL, p ); + } +} + +//siege voice command +static void Cmd_VoiceCommand_f(gentity_t *ent) +{ + gentity_t *te; + char arg[MAX_TOKEN_CHARS]; + char *s; + int i = 0; + + if (g_gametype.integer < GT_TEAM) + { + return; + } + + if (trap_Argc() < 2) + { + return; + } + + if (ent->client->sess.sessionTeam == TEAM_SPECTATOR || + ent->client->tempSpectate >= level.time) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOICECHATASSPEC")) ); + return; + } + + trap_Argv(1, arg, sizeof(arg)); + + if (arg[0] == '*') + { //hmm.. don't expect a * to be prepended already. maybe someone is trying to be sneaky. + return; + } + + s = va("*%s", arg); + + //now, make sure it's a valid sound to be playing like this.. so people can't go around + //screaming out death sounds or whatever. + while (i < MAX_CUSTOM_SIEGE_SOUNDS) + { + if (!bg_customSiegeSoundNames[i]) + { + break; + } + if (!Q_stricmp(bg_customSiegeSoundNames[i], s)) + { //it matches this one, so it's ok + break; + } + i++; + } + + if (i == MAX_CUSTOM_SIEGE_SOUNDS || !bg_customSiegeSoundNames[i]) + { //didn't find it in the list + return; + } + + te = G_TempEntity(vec3_origin, EV_VOICECMD_SOUND); + te->s.groundEntityNum = ent->s.number; + te->s.eventParm = G_SoundIndex((char *)bg_customSiegeSoundNames[i]); + te->r.svFlags |= SVF_BROADCAST; +} + + +static char *gc_orders[] = { + "hold your position", + "hold this position", + "come here", + "cover me", + "guard location", + "search and destroy", + "report" +}; + +void Cmd_GameCommand_f( gentity_t *ent ) { + int player; + int order; + char str[MAX_TOKEN_CHARS]; + + trap_Argv( 1, str, sizeof( str ) ); + player = atoi( str ); + trap_Argv( 2, str, sizeof( str ) ); + order = atoi( str ); + + if ( player < 0 || player >= MAX_CLIENTS ) { + return; + } + if ( order < 0 || order > sizeof(gc_orders)/sizeof(char *) ) { + return; + } + G_Say( ent, &g_entities[player], SAY_TELL, gc_orders[order] ); + G_Say( ent, ent, SAY_TELL, gc_orders[order] ); +} + +/* +================== +Cmd_Where_f +================== +*/ +void Cmd_Where_f( gentity_t *ent ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) ); +} + +static const char *gameNames[] = { + "Free For All", + "Holocron FFA", + "Jedi Master", + "Duel", + "Power Duel", + "Single Player", + "Team FFA", + "Siege", + "Capture the Flag", + "Capture the Ysalamiri" +}; + +/* +================== +G_ClientNumberFromName + +Finds the client number of the client with the given name +================== +*/ +int G_ClientNumberFromName ( const char* name ) +{ + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + int i; + gclient_t* cl; + + // check for a name match + SanitizeString( (char*)name, s2 ); + for ( i=0, cl=level.clients ; i < level.numConnectedClients ; i++, cl++ ) + { + SanitizeString( cl->pers.netname, n2 ); + if ( !strcmp( n2, s2 ) ) + { + return i; + } + } + + return -1; +} + +/* +================== +SanitizeString2 + +Rich's revised version of SanitizeString +================== +*/ +void SanitizeString2( char *in, char *out ) +{ + int i = 0; + int r = 0; + + while (in[i]) + { + if (i >= MAX_NAME_LENGTH-1) + { //the ui truncates the name here.. + break; + } + + if (in[i] == '^') + { + if (in[i+1] >= 48 && //'0' + in[i+1] <= 57) //'9' + { //only skip it if there's a number after it for the color + i += 2; + continue; + } + else + { //just skip the ^ + i++; + continue; + } + } + + if (in[i] < 32) + { + i++; + continue; + } + + out[r] = in[i]; + r++; + i++; + } + out[r] = 0; +} + +/* +================== +G_ClientNumberFromStrippedName + +Same as above, but strips special characters out of the names before comparing. +================== +*/ +int G_ClientNumberFromStrippedName ( const char* name ) +{ + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + int i; + gclient_t* cl; + + // check for a name match + SanitizeString2( (char*)name, s2 ); + for ( i=0, cl=level.clients ; i < level.numConnectedClients ; i++, cl++ ) + { + SanitizeString2( cl->pers.netname, n2 ); + if ( !strcmp( n2, s2 ) ) + { + return i; + } + } + + return -1; +} + +/* +================== +Cmd_CallVote_f +================== +*/ +extern void SiegeClearSwitchData(void); //g_saga.c +const char *G_GetArenaInfoByMap( const char *map ); +void Cmd_CallVote_f( gentity_t *ent ) { + int i; + char arg1[MAX_STRING_TOKENS]; + char arg2[MAX_STRING_TOKENS]; +// int n = 0; +// char* type = NULL; + char* mapName = 0; + const char* arenaInfo; + + if ( !g_allowVote.integer ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOTE")) ); + return; + } + + if ( level.voteTime || level.voteExecuteTime >= level.time ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "VOTEINPROGRESS")) ); + return; + } + if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MAXVOTES")) ); + return; + } + + if (g_gametype.integer != GT_DUEL && + g_gametype.integer != GT_POWERDUEL) + { + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSPECVOTE")) ); + return; + } + } + + // make sure it is a valid command to vote on + trap_Argv( 1, arg1, sizeof( arg1 ) ); + trap_Argv( 2, arg2, sizeof( arg2 ) ); + + if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) { + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); + return; + } + + if ( !Q_stricmp( arg1, "map_restart" ) ) { + } else if ( !Q_stricmp( arg1, "nextmap" ) ) { + } else if ( !Q_stricmp( arg1, "map" ) ) { + } else if ( !Q_stricmp( arg1, "g_gametype" ) ) { + } else if ( !Q_stricmp( arg1, "kick" ) ) { + } else if ( !Q_stricmp( arg1, "clientkick" ) ) { + } else if ( !Q_stricmp( arg1, "g_doWarmup" ) ) { + } else if ( !Q_stricmp( arg1, "timelimit" ) ) { + } else if ( !Q_stricmp( arg1, "fraglimit" ) ) { + } else { + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map , g_gametype , kick , clientkick , g_doWarmup, timelimit

KGI4%KQ|CKc5WAxU-$12yh4 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 T(F#N>5UoJ80?`Wmhbr(t)^$2H literal 0 HcmV?d00001 diff --git a/codemp/openal32.lib b/codemp/openal32.lib new file mode 100644 index 0000000000000000000000000000000000000000..86de42002459c4b3e4f37d9bfd092afa9f491dbb GIT binary patch literal 16866 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 literal 0 HcmV?d00001 diff --git a/codemp/png/png.cpp b/codemp/png/png.cpp new file mode 100644 index 0000000..11b489d --- /dev/null +++ b/codemp/png/png.cpp @@ -0,0 +1,783 @@ +// Generic PNG file loading code + +// leave this as first line for PCH reasons... +// +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" + +#include "../zlib32/zip.h" +#include "png.h" +//#include "../qcommon/memory.h" + +// Error returns + +#define PNG_ERROR_OK 0 +#define PNG_ERROR_DECOMP 1 +#define PNG_ERROR_COMP 2 +#define PNG_ERROR_MEMORY 3 +#define PNG_ERROR_NOSIG 4 +#define PNG_ERROR_TOO_SMALL 5 +#define PNG_ERROR_WNP2 6 +#define PNG_ERROR_HNP2 7 +#define PNG_ERROR_NOT_TC 8 +#define PNG_ERROR_INV_FIL 9 +#define PNG_ERROR_FAILED_CRC 10 +#define PNG_ERROR_CREATE_FAIL 11 +#define PNG_ERROR_WRITE 12 +#define PNG_ERROR_NOT_PALETTE 13 +#define PNG_ERROR_NOT8BIT 14 +#define PNG_ERROR_TOO_LARGE 15 + +static int png_error = PNG_ERROR_OK; + +static const byte png_signature[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; +static const char png_copyright[] = "Copyright\0Raven Software Inc. 2001"; +static const char *png_errors[] = +{ + "OK.", + "Error decompressing image data.", + "Error compressing image data.", + "Error allocating memory.", + "PNG signature not found.", + "Image is too small to load.", + "Width is not a power of two.", + "Height is not a power of two.", + "Image is not 24 or 32 bit.", + "Invalid filter or compression type.", + "Failed CRC check.", + "Could not create file.", + "Error writing to file.", + "Image is not indexed colour.", + "Image does not have 8 bits per sample.", + "Image is too large", +}; + +// Gets the error string for a failed PNG operation + +const char *PNG_GetError(void) +{ + return(png_errors[png_error]); +} + +// Create a header chunk + +void PNG_CreateHeader(png_ihdr_t *header, int width, int height, int bytedepth) +{ + header->width = BigLong(width); + header->height = BigLong(height); + header->bitdepth = 8; + + if(bytedepth == 3) + { + header->colortype = 2; + } + if(bytedepth == 4) + { + header->colortype = 6; + } + header->compression = 0; + header->filter = 0; + header->interlace = 0; +} + +// Processes the header chunk and checks to see if all the data is valid + +bool PNG_HandleIHDR(const byte *data, png_image_t *image) +{ + png_ihdr_t *ihdr = (png_ihdr_t *)data; + + image->width = BigLong(ihdr->width); + image->height = BigLong(ihdr->height); + + // Make sure image is a reasonable size + if((image->width < 2) || (image->height < 2)) + { + png_error = PNG_ERROR_TOO_SMALL; + return(false); + } + if(image->width > MAX_PNG_WIDTH) + { + png_error = PNG_ERROR_TOO_LARGE; + return(false); + } + if(ihdr->bitdepth != 8) + { + png_error = PNG_ERROR_NOT8BIT; + return(false); + } + // Check for non power of two size (but not for data files) + if(image->isimage) + { + if(image->width & (image->width - 1)) + { + png_error = PNG_ERROR_WNP2; + return(false); + } + if(image->height & (image->height - 1)) + { + png_error = PNG_ERROR_HNP2; + return(false); + } + } + // Make sure we have a 24 or 32 bit image (for images) + if(image->isimage) + { + if((ihdr->colortype != 2) && (ihdr->colortype != 6)) + { + png_error = PNG_ERROR_NOT_TC; + return(false); + } + } + // Make sure we have an 8 bit grayscale image for data files + if(!image->isimage) + { + if(ihdr->colortype && (ihdr->colortype != 3)) + { + png_error = PNG_ERROR_NOT_PALETTE; + return(false); + } + } + // Make sure we aren't using any wacky compression or filter algos + if(ihdr->compression || ihdr->filter) + { + png_error = PNG_ERROR_INV_FIL; + return(false); + } + // Extract the data we need + if(!ihdr->colortype || (ihdr->colortype == 3)) + { + image->bytedepth = 1; + } + if(ihdr->colortype == 2) + { + image->bytedepth = 3; + } + if(ihdr->colortype == 6) + { + image->bytedepth = 4; + } + return(true); +} + +// Filter a row of data + +void PNG_Filter(byte *out, byte filter, const byte *in, const byte *lastline, ulong rowbytes, ulong bpp) +{ + ulong i; + + switch(filter) + { + case PNG_FILTER_VALUE_NONE: + memcpy(out, in, rowbytes); + break; + case PNG_FILTER_VALUE_SUB: + for(i = 0; i < bpp; i++) + { + *out++ = *in++; + } + for(i = bpp; i < rowbytes; i++) + { + *out++ = *in - *(in - bpp); + in++; + } + break; + case PNG_FILTER_VALUE_UP: + for(i = 0; i < rowbytes; i++) + { + if(lastline) + { + *out++ = *in++ - *lastline++; + } + else + { + *out++ = *in++; + } + } + break; + case PNG_FILTER_VALUE_AVG: + for(i = 0; i < bpp; i++) + { + if(lastline) + { + *out++ = *in++ - (*lastline++ >> 1); + } + else + { + *out++ = *in++; + } + } + for(i = bpp; i < rowbytes; i++) + { + if(lastline) + { + *out++ = *in - ((*lastline++ + *(in - bpp)) >> 1); + } + else + { + *out++ = *in - (*(in - bpp) >> 1); + } + in++; + } + break; + case PNG_FILTER_VALUE_PAETH: + int a, b, c; + int pa, pb, pc, p; + + for(i = 0; i < bpp; i++) + { + if(lastline) + { + *out++ = *in++ - *lastline++; + } + else + { + *out++ = *in++; + } + } + for(i = bpp; i < rowbytes; i++) + { + a = *(in - bpp); + c = 0; + b = 0; + if(lastline) + { + c = *(lastline - bpp); + b = *lastline++; + } + + p = b - c; + pc = a - c; + + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; + + p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; + + *out++ = *in++ - p; + } + break; + } +} + +// Unfilters a row of data + +void PNG_Unfilter(byte *out, byte filter, const byte *lastline, ulong rowbytes, ulong bpp) +{ + ulong i; + + switch(filter) + { + case PNG_FILTER_VALUE_NONE: + break; + case PNG_FILTER_VALUE_SUB: + out += bpp; + for(i = bpp; i < rowbytes; i++) + { + *out += *(out - bpp); + out++; + } + break; + case PNG_FILTER_VALUE_UP: + for(i = 0; i < rowbytes; i++) + { + if(lastline) + { + *out += *lastline++; + } + out++; + } + break; + case PNG_FILTER_VALUE_AVG: + for(i = 0; i < bpp; i++) + { + if(lastline) + { + *out += *lastline++ >> 1; + } + out++; + } + for(i = bpp; i < rowbytes; i++) + { + if(lastline) + { + *out += (*lastline++ + *(out - bpp)) >> 1; + } + else + { + *out += *(out - bpp) >> 1; + } + out++; + } + break; + case PNG_FILTER_VALUE_PAETH: + int a, b, c; + int pa, pb, pc, p; + + for(i = 0; i < bpp; i++) + { + if(lastline) + { + *out += *lastline++; + } + out++; + } + for(i = bpp; i < rowbytes; i++) + { + a = *(out - bpp); + c = 0; + b = 0; + if(lastline) + { + c = *(lastline - bpp); + b = *lastline++; + } + p = b - c; + pc = a - c; + + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; + + p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; + + *out++ += p; + } + break; + default: + break; + } +} + +// Pack up the image data line by line + +bool PNG_Pack(byte *out, ulong *size, ulong maxsize, byte *data, int width, int height, int bytedepth) +{ + z_stream zdata; + ulong rowbytes; + ulong y; + const byte *lastline, *source; + // Storage for filter type and filtered row + byte workline[(MAX_PNG_WIDTH * MAX_PNG_DEPTH) + 1]; + + // Number of bytes per row + rowbytes = width * bytedepth; + + memset(&zdata, 0, sizeof(z_stream)); + if(deflateInit(&zdata, Z_FAST_COMPRESSION_HIGH) != Z_OK) + { + png_error = PNG_ERROR_COMP; + return(false); + } + + zdata.next_out = out; + zdata.avail_out = maxsize; + + lastline = NULL; + source = data + ((height - 1) * rowbytes); + for(y = 0; y < height; y++) + { + // Refilter using the most compressable filter algo + // Assume paeth to speed things up + workline[0] = (byte)PNG_FILTER_VALUE_PAETH; + PNG_Filter(workline + 1, (byte)PNG_FILTER_VALUE_PAETH, source, lastline, rowbytes, bytedepth); + + zdata.next_in = workline; + zdata.avail_in = rowbytes + 1; + if(deflate(&zdata, Z_SYNC_FLUSH) != Z_OK) + { + deflateEnd(&zdata); + png_error = PNG_ERROR_COMP; + return(false); + } + lastline = source; + source -= rowbytes; + } + if(deflate(&zdata, Z_FINISH) != Z_STREAM_END) + { + png_error = PNG_ERROR_COMP; + return(false); + } + *size = zdata.total_out; + deflateEnd(&zdata); + return(true); +} + +// Unpack the image data, line by line + +bool PNG_Unpack(const byte *data, const ulong datasize, png_image_t *image) +{ + ulong rowbytes, zerror, y; + byte filter; + z_stream zdata; + byte *lastline, *out; + +// MD_PushTag(TAG_ZIP_TEMP); + + memset(&zdata, 0, sizeof(z_stream)); + if(inflateInit(&zdata, Z_SYNC_FLUSH) != Z_OK) + { + png_error = PNG_ERROR_DECOMP; +// MD_PopTag(); + return(false); + } + zdata.next_in = (byte *)data; + zdata.avail_in = datasize; + + rowbytes = image->width * image->bytedepth; + + lastline = NULL; + out = image->data; + for(y = 0; y < image->height; y++) + { + // Inflate a row of data + zdata.next_out = &filter; + zdata.avail_out = 1; + if(inflate(&zdata) != Z_OK) + { + inflateEnd(&zdata); + png_error = PNG_ERROR_DECOMP; +// MD_PopTag(); + return(false); + } + zdata.next_out = out; + zdata.avail_out = rowbytes; + zerror = inflate(&zdata); + if((zerror != Z_OK) && (zerror != Z_STREAM_END)) + { + inflateEnd(&zdata); + png_error = PNG_ERROR_DECOMP; +// MD_PopTag(); + return(false); + } + + // Unfilter a row of data + PNG_Unfilter(out, filter, lastline, rowbytes, image->bytedepth); + + lastline = out; + out += rowbytes; + } + inflateEnd(&zdata); +// MD_PopTag(); + return(true); +} + +// Scan through all chunks and process each one + +bool PNG_Load(const byte *data, ulong datasize, png_image_t *image) +{ + bool moredata; + const byte *next; + byte *workspace, *work; + ulong length, type, crc, totallength; + + png_error = PNG_ERROR_OK; + + if(memcmp(data, png_signature, sizeof(png_signature))) + { + png_error = PNG_ERROR_NOSIG; + return(false); + } + data += sizeof(png_signature); + + workspace = (byte *)Z_Malloc(datasize, TAG_TEMP_PNG, qfalse); + work = workspace; + totallength = 0; + + moredata = true; + while(moredata) + { + length = BigLong(*(ulong *)data); + data += sizeof(ulong); + + type = BigLong(*(ulong *)data); + const byte *crcbase = data; + data += sizeof(ulong); + + // CRC checksum location + next = data + length + sizeof(ulong); + + // CRC checksum includes header field + crc = crc32(0, crcbase, length + sizeof(ulong)); + if(crc != (ulong)BigLong(*(ulong *)(next - 4))) + { + if(image->data) + { + Z_Free(image->data); + image->data = NULL; + } + Z_Free(workspace); + png_error = PNG_ERROR_FAILED_CRC; + return(false); + } + switch(type) + { + case PNG_IHDR: + if(!PNG_HandleIHDR(data, image)) + { + Z_Free(workspace); + return(false); + } + image->data = (byte *)Z_Malloc(image->width * image->height * image->bytedepth, TAG_TEMP_PNG, qfalse); + break; + case PNG_IDAT: + // Need to copy all the various IDAT chunks into one big one + // Everything but 3dsmax has one IDAT chunk + memcpy(work, data, length); + work += length; + totallength += length; + break; + case PNG_IEND: + if(!PNG_Unpack(workspace, totallength, image)) + { + Z_Free(workspace); + Z_Free(image->data); + image->data = NULL; + return(false); + } + moredata = false; + break; + default: + break; + } + data = next; + } + Z_Free(workspace); + return(true); +} + +// Outputs a crc'd chunk of PNG data + +bool PNG_OutputChunk(fileHandle_t fp, ulong type, byte *data, ulong size) +{ + ulong crc, little, outcount; + + // Output a standard PNG chunk - length, type, data, crc + little = BigLong(size); + outcount = FS_Write(&little, sizeof(little), fp); + + little = BigLong(type); + crc = crc32(0, (byte *)&little, sizeof(little)); + outcount += FS_Write(&little, sizeof(little), fp); + + if(size) + { + crc = crc32(crc, data, size); + outcount += FS_Write(data, size, fp); + } + + little = BigLong(crc); + outcount += FS_Write(&little, sizeof(little), fp); + + if(outcount != (size + 12)) + { + png_error = PNG_ERROR_WRITE; + return(false); + } + return(true); +} + +// Saves a PNG format compressed image + +bool PNG_Save(const char *name, byte *data, int width, int height, int bytedepth) +{ + byte *work; + fileHandle_t fp; + int maxsize; + ulong size, outcount; + png_ihdr_t png_header; + + png_error = PNG_ERROR_OK; + + // Create the file + fp = FS_FOpenFileWrite(name); + if(!fp) + { + png_error = PNG_ERROR_CREATE_FAIL; + return(false); + } + // Write out the PNG signature + outcount = FS_Write(png_signature, sizeof(png_signature), fp); + if(outcount != sizeof(png_signature)) + { + FS_FCloseFile(fp); + png_error = PNG_ERROR_WRITE; + return(false); + } + // Create and output a valid header + PNG_CreateHeader(&png_header, width, height, bytedepth); + if(!PNG_OutputChunk(fp, PNG_IHDR, (byte *)&png_header, sizeof(png_header))) + { + FS_FCloseFile(fp); + return(false); + } + // Create and output the copyright info + if(!PNG_OutputChunk(fp, PNG_tEXt, (byte *)png_copyright, sizeof(png_copyright))) + { + FS_FCloseFile(fp); + return(false); + } + // Max size of compressed image (source size + 0.1% + 12) + maxsize = (width * height * bytedepth) + 4096; + work = (byte *)Z_Malloc(maxsize, TAG_TEMP_PNG, qtrue); // fixme: optimise to qfalse sometime - ok? + + // Pack up the image data + if(!PNG_Pack(work, &size, maxsize, data, width, height, bytedepth)) + { + Z_Free(work); + FS_FCloseFile(fp); + return(false); + } + // Write out the compressed image data + if(!PNG_OutputChunk(fp, PNG_IDAT, (byte *)work, size)) + { + Z_Free(work); + FS_FCloseFile(fp); + return(false); + } + Z_Free(work); + // Output terminating chunk + if(!PNG_OutputChunk(fp, PNG_IEND, NULL, 0)) + { + FS_FCloseFile(fp); + return(false); + } + FS_FCloseFile(fp); + return(true); +} + +/* +============= +PNG_ConvertTo32 +============= +*/ + +void PNG_ConvertTo32(png_image_t *image) +{ + byte *temp; + byte *old, *old2; + ulong i; + + temp = (byte *)Z_Malloc(image->width * image->height * 4, TAG_TEMP_PNG, qtrue); + old = image->data; + old2 = old; + image->data = temp; + image->bytedepth = 4; + + for(i = 0; i < image->width * image->height; i++) + { + *temp++ = *old++; + *temp++ = *old++; + *temp++ = *old++; + *temp++ = 0xff; + } + Z_Free(old2); +} + +/* +============= +LoadPNG32 +============= +*/ +bool LoadPNG32 (char *name, byte **pixels, int *width, int *height, int *bytedepth) +{ + byte *buffer; + byte **bufferptr = &buffer; + int nLen; + png_image_t png_image; + + if(!pixels) + { + bufferptr = NULL; + } + nLen = FS_ReadFile ( ( char * ) name, (void **)bufferptr); + if (nLen == -1) + { + if(pixels) + { + *pixels = NULL; + } + return(false); + } + if(!pixels) + { + return(true); + } + *pixels = NULL; + png_image.isimage = true; + if(!PNG_Load(buffer, nLen, &png_image)) + { + Com_Printf ("Error parsing %s: %s\n", name, PNG_GetError()); + return(false); + } + if(png_image.bytedepth != 4) + { + PNG_ConvertTo32(&png_image); + } + *pixels = png_image.data; + if(width) + { + *width = png_image.width; + } + if(height) + { + *height = png_image.height; + } + if(bytedepth) + { + *bytedepth = png_image.bytedepth; + } + FS_FreeFile(buffer); + return(true); +} + +/* +============= +LoadPNG8 +============= +*/ +bool LoadPNG8 (char *name, byte **pixels, int *width, int *height) +{ + byte *buffer; + byte **bufferptr = &buffer; + int nLen; + png_image_t png_image; + + if(!pixels) + { + bufferptr = NULL; + } + nLen = FS_ReadFile ( ( char * ) name, (void **)bufferptr); + if (nLen == -1) + { + if(pixels) + { + *pixels = NULL; + } + return(false); + } + if(!pixels) + { + return(true); + } + *pixels = NULL; + png_image.isimage = false; + if(!PNG_Load(buffer, nLen, &png_image)) + { + Com_Printf ("Error parsing %s: %s\n", name, PNG_GetError()); + return(false); + } + *pixels = png_image.data; + if(width) + { + *width = png_image.width; + } + if(height) + { + *height = png_image.height; + } + FS_FreeFile(buffer); + return(true); +} + +// end \ No newline at end of file diff --git a/codemp/png/png.h b/codemp/png/png.h new file mode 100644 index 0000000..3ab0ec2 --- /dev/null +++ b/codemp/png/png.h @@ -0,0 +1,73 @@ +// Known chunk types + +#define PNG_IHDR 'IHDR' +#define PNG_IDAT 'IDAT' +#define PNG_IEND 'IEND' +#define PNG_tEXt 'tEXt' + +#define PNG_PLTE 'PLTE' +#define PNG_bKGD 'bKGD' +#define PNG_cHRM 'cHRM' +#define PNG_gAMA 'gAMA' +#define PNG_hIST 'hIST' +#define PNG_iCCP 'iCCP' +#define PNG_iTXt 'iTXt' +#define PNG_oFFs 'oFFs' +#define PNG_pCAL 'pCAL' +#define PNG_sCAL 'sCAL' +#define PNG_pHYs 'pHYs' +#define PNG_sBIT 'sBIT' +#define PNG_sPLT 'sPLT' +#define PNG_sRGB 'sRGB' +#define PNG_tIME 'tIME' +#define PNG_tRNS 'tRNS' +#define PNG_zTXt 'zTXt' + +// Filter values + +#define PNG_FILTER_VALUE_NONE 0 +#define PNG_FILTER_VALUE_SUB 1 +#define PNG_FILTER_VALUE_UP 2 +#define PNG_FILTER_VALUE_AVG 3 +#define PNG_FILTER_VALUE_PAETH 4 +#define PNG_FILTER_NUM 5 + +// Common defines and typedefs + +#define MAX_PNG_WIDTH 4096 +#define MAX_PNG_DEPTH 4 + +typedef unsigned char byte; +typedef unsigned short word; +typedef unsigned long ulong; + +#pragma pack(push) +#pragma pack(1) + +typedef struct png_ihdr_s +{ + ulong width; + ulong height; + byte bitdepth; // Bits per sample (not per pixel) + byte colortype; // bit 0 - palette; bit 1 - RGB; bit 2 - alpha channel + byte compression; // 0 for zip - error otherwise + byte filter; // 0 for adaptive with the 5 basic types - error otherwise + byte interlace; // 0 for no interlace - 1 for Adam7 interlace +} png_ihdr_t; + +#pragma pack(pop) + +typedef struct png_image_s +{ + byte *data; + ulong width; + ulong height; + ulong bytedepth; + bool isimage; +} png_image_t; + +bool LoadPNG32 (char *name, byte **pixels, int *width, int *height, int *bytedepth); +bool LoadPNG8 (char *name, byte **pixels, int *width, int *height); +bool PNG_Save(const char *name, byte *data, int width, int height, int bytedepth); + +// end \ No newline at end of file diff --git a/codemp/qcommon/chash.h b/codemp/qcommon/chash.h new file mode 100644 index 0000000..d690250 --- /dev/null +++ b/codemp/qcommon/chash.h @@ -0,0 +1,162 @@ +// Notes +// Make sure extension is stripped if it needs to be + +// Template class must have +// 1. A GetName() accessor - a null terminated string case insensitive +// 2. A Destroy() function - normally "delete this" +// 3. SetNext(T *) and T *GetNext() functions + +#define HASH_SIZE 1024 + +template + +class CHash +{ +private: + T *mHashTable[TSize]; + T *mNext; + int mCount; + T *mPrevious; // Internal work variable + long mHash; // Internal work variable + + // Creates the hash value and sets the mHash member + void CreateHash(const char *key) + { + int i = 0; + char letter; + + mHash = 0; + letter = *key++; + while (letter) + { + mHash += (long)(letter) * (i + 119); + + i++; + letter = *key++; + } + mHash &= TSize - 1; + } +public: + // Constructor + CHash(void) + { + memset(mHashTable, NULL, sizeof(mHashTable)); + mNext = NULL; + mCount = 0; + mPrevious = NULL; + mHash = 0; + } + // Destructor + ~CHash(void) + { +#ifdef _DEBUG +// Com_OPrintf("Shutting down %s hash table .....", typeid(T).name()); +#endif + clear(); +#ifdef _DEBUG + Com_OPrintf(" done\n"); +#endif + } + // Returns the total number of entries in the hash table + int count(void) const { return(mCount); } + + // Inserts an item into the hash table + void insert(T *item) + { + CreateHash(item->GetName()); + item->SetNext(mHashTable[mHash]); + mHashTable[mHash] = item; + mCount++; + } + // Finds an item in the hash table (sets the mPrevious member) + T *find(const char *key) + { + CreateHash(key); + T *item = mHashTable[mHash]; + mPrevious = NULL; + while(item) + { + mNext = item->GetNext(); + if(!TCompare(item->GetName(), key)) + { + return(item); + } + mPrevious = item; + item = mNext; + } + return(NULL); + } + // Remove item from the hash table referenced by key + bool remove(const char *key) + { + T *item = find(key); + if(item) + { + T *next = item->GetNext(); + if(mPrevious) + { + mPrevious->SetNext(next); + } + else + { + mHashTable[mHash] = next; + } + item->Destroy(); + mCount--; + return(true); + } + return(false); + } + // Remove item from hash referenced by item + bool remove(T *item) + { + return(remove(item->GetName())); + } + // Returns the first valid entry + T *head(void) + { + mHash = -1; + mNext = NULL; + return(next()); + } + // Returns the next entry in the hash table + T *next(void) + { + T *item; + + assert(mHash < TSize); + + if(mNext) + { + item = mNext; + mNext = item->GetNext(); + return(item); + } + mHash++; + + for( ; mHash < TSize; mHash++) + { + item = mHashTable[mHash]; + if(item) + { + mNext = item->GetNext(); + return(item); + } + } + return(NULL); + } + // Destroy all entries in the hash table + void clear(void) + { + T *item = head(); + while(item) + { + remove(item); + item = next(); + } + } + // Override the [] operator + T *operator[](const char *key) { return(find(key)); } +}; + +// end \ No newline at end of file diff --git a/codemp/qcommon/cm_draw.cpp b/codemp/qcommon/cm_draw.cpp new file mode 100644 index 0000000..cd18238 --- /dev/null +++ b/codemp/qcommon/cm_draw.cpp @@ -0,0 +1,1490 @@ +/////////////////////////////////////////////////////////////////////////////// +// CDraw32 Class Implementation +// +// Basic drawing routines for 32-bit buffer +/////////////////////////////////////////////////////////////////////////////// +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + + +#include "cm_local.h" +#include "cm_draw.h" + + +///////////// statics for CDraw32 ////////////////////////////////// +// Used by all drawing routines as the "current" drawing context +CPixel32* CDraw32::buffer = NULL; // pointer to 32-bit deep pixel buffer +long CDraw32::buf_width=0; // width of buffer in pixels +long CDraw32::buf_height=0; // height of buffer in pixels +long CDraw32::stride = 0; // stride in pixels +long CDraw32::clip_min_x=0; // clip bounds +long CDraw32::clip_min_y=0; // clip bounds +long CDraw32::clip_max_x=0; // clip bounds +long CDraw32::clip_max_y=0; // clip bounds +long* CDraw32::row_off = NULL; // Table for quick Y calculations + +CDraw32::CDraw32() +//USE: constructor +{ +} + +CDraw32::~CDraw32() +//USE: Destructor +{ +} + +int imgKernel[5][5] = +{ + {-1,-1,-1,-1, 0}, + {-1,-1,-1, 0, 1}, + {-1,-1, 0, 1, 1}, + {-1, 0, 1, 1, 1}, + { 0, 1, 1, 1, 1} +}; + +const int KWIDTH = 2; + +void CDraw32::Emboss(long dstX, long dstY, long width, long height, + CPixel32* clrImage, long clrX, long clrY, long clrStride) +{ + CPixel32 *dst; + CPixel32 *clr; + int x,y,i,j; + int dstNextLine; + int clrNextLine; + + assert(buffer != NULL); + + BlitClip(dstX, dstY, width, height, clrX, clrY); + + if (width < 1 || height < 1) + return; + + dst = &buffer[PIXPOS(dstX,dstY,stride)]; + clr = &clrImage[PIXPOS(clrX,clrY,clrStride)]; + + dstNextLine = (stride - width); + clrNextLine = (clrStride - width); + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + int accum = 0; + for (j = -KWIDTH; j<=KWIDTH; j++) + for (i = -KWIDTH; i<=KWIDTH; i++) + { + int xk = CLAMP(x + i, clrX, clrX+width-1); + int yk = CLAMP(y + j, clrY, clrY+height-1); + accum += clrImage[PIXPOS(xk,yk,clrStride)].a * imgKernel[j+KWIDTH][i+KWIDTH]; + } + *dst = LIGHT_PIX(*clr, accum); + dst->a = 255; + ++dst; + ++clr; + } + dst += dstNextLine; + clr += clrNextLine; + } + + +} + +bool CDraw32::SetBufferSize(long width,long height,long stride_len) +//USE: setup for a particular size drawing buffer +// (do not re-setup if buffer size has not changed) +//IN: width,height - size of buffer +// stride_len - distance to next line +//OUT: true if everything goes OK, otherwise false +{ + long i; + + assert(width!=0); + assert(height!=0); + assert(stride_len!=0); + + if (buf_width != width || buf_height != height || + stride_len != stride) + { // need to re-create row_off table + buf_width = width; + buf_height = height; + stride = stride_len; + + if (row_off) + delete [] row_off; + + // row offsets used for quick pixel address calcs + row_off = new long[height]; + + assert(row_off != NULL); + if (row_off == NULL) + return false; + + // table for quick pixel lookups + for (i=0; i=0); + assert(end=0); + assert(enda = alpha; + ++dest; + } + dest += next_line; + } +} + +#define LEFT 1 // code bits +#define RIGHT 2 +#define TOP 4 +#define BOTTOM 8 + +static long code(long x,long y) +//USE: determines where a point is in relation to a bounding box +//IN: x,y - coordinate pair +//OUT: clipping code compaired to global clip context +{ + long c; + + c = 0; + if (x < CDraw32::clip_min_x) c |= LEFT; + if (x > CDraw32::clip_max_x) c |= RIGHT; + if (y < CDraw32::clip_min_y) c |= BOTTOM; + if (y > CDraw32::clip_max_y) c |= TOP; + + return c; +} + +bool CDraw32::ClipLine(long& x1, long& y1, long& x2, long& y2) +//USE: clip a line from (x1,y1) to (x2,y2) to clip bounds +//IN: (x1,y1)-(x2,y2) line +//OUT: return true if something left to draw, otherwise false +{ + long c1,c2,c,x,y,f; + x = x1; + y = y1; + + c1 = code(x1,y1); // find where first pt. is + c2 = code(x2,y2); // find where second pt. is + + if ((c1 & c2) == 0) + { // the line may be visible + while (c1 | c2) + { // where there is 2D clipping to be done + if (c1 & c2) + { + return false; // if both on same side, quit + } + + c = c1; + if (c==0) + { + c = c2; // pick a point + } + + if (c & TOP) + { + f = ((clip_max_y-y1) << 15)/(y2-y1); + x = x1 + (((x2-x1)*f + 16384) >> 15); + y = clip_max_y; + } + else if (c & BOTTOM) + { + f = ((clip_min_y-y1) << 15)/(y2-y1); + x = x1 + (((x2-x1)*f + 16384) >> 15); + y = clip_min_y; + } + else if (c & LEFT) + { + f = ((clip_min_x-x1) << 15)/(x2-x1); + y = y1 + (((y2-y1)*f + 16384) >> 15); + x = clip_min_x; + } + else if (c & RIGHT) + { + f = ((clip_max_x-x1) << 15)/(x2-x1); + y = y1 + (((y2-y1)*f + 16384) >> 15); + x = clip_max_x; + } + if (c==c1) + { + x1=x; y1=y; c1=code(x1,y1); + } + else + { + x2=x; y2=y; c2=code(x2,y2); + } + } // while still needs clipping + } + else + { // line not visible + return false; + } + return true; +} + +void CDraw32::DrawLineNC(long x1, long y1, long x2, long y2, CPixel32 color) +//USE: draw a line from (x1,y1) to (x2,y2) in color (no clip) +//IN: (x1,y1) - starting coordinate +// (x2,y2) - ending coordinate +// color - 32-bit color value +//OUT: none +{ + long d, ax, ay, sx, sy, dx, dy; + CPixel32* dest; + + assert(buffer != NULL); + + dx = x2-x1; + ax = ABS(dx) << 1; + sx = SIGN(dx); + dy = y2-y1; + ay = ABS(dy) << 1; + sy = SIGN(dy); + + if (255 == color.a) + { + if (dy == 0) + { // horz line + if (dx >= 0) + { + dest = &buffer[row_off[y1] + x1]; + int i = dx+1; + while (i--) + *dest++ = color; + } + else + { + dest = &buffer[row_off[y1] + x1 + dx]; + int i = -dx+1; + while (i--) + *dest++ = color; + } + return; + } + + if (dx == 0) + { // vert line + if (dy >= 0) + { + dest = &buffer[row_off[y1] + x1]; + dy++; + } + else + { + dest = &buffer[row_off[y2] + x1]; + dy = -dy + 1; + } + + while (dy-- != 0) + { + *dest = color; + dest += stride; + } + return; + } + } + + // bressenham's algorithm + if (ax > ay) + { + d = ay - (ax >> 1); + while(x1 != x2) + { + PutPixAlphaNC(x1,y1,color); + + if (d >= 0) + { + y1 += sy; + d -= ax; + } + x1 += sx; + d += ay; + } + } + else + { + d = ax - (ay >> 1); + while(y1 != y2) + { + PutPixAlphaNC(x1,y1,color); + if (d >= 0) + { + x1 += sx; + d -= ay; + } + y1 += sy; + d += ax; + } + } + PutPixAlphaNC(x1,y1,color); +} + +void CDraw32::DrawLineAveNC(long x1, long y1, long x2, long y2, CPixel32 color) +//USE: draw a translucent line from (x1,y1) to (x2,y2) in color (no clip) +//IN: (x1,y1) - starting coordinate +// (x2,y2) - ending coordinate +// color - 32-bit color value +//OUT: none +{ + long d, ax, ay, sx, sy, dx, dy; + CPixel32* dest; + + assert(buffer != NULL); + + dx = x2-x1; + ax = ABS(dx) << 1; + sx = SIGN(dx); + dy = y2-y1; + ay = ABS(dy) << 1; + sy = SIGN(dy); + + if (dy == 0) + { // horz line + if (dx >= 0) + { + dest = &buffer[row_off[y1] + x1]; + int i = dx+1; + while (i--) + *dest++ = AVE_PIX(*dest, color); + } + else + { + dest = &buffer[row_off[y1] + x1 + dx]; + int i = -dx+1; + while (i--) + *dest++ = AVE_PIX(*dest, color); + } + return; + } + + if (dx == 0) + { // vert line + if (dy >= 0) + { + dest = &buffer[row_off[y1] + x1]; + dy++; + } + else + { + dest = &buffer[row_off[y2] + x1]; + dy = -dy + 1; + } + + while (dy-- != 0) + { + *dest = AVE_PIX(*dest, color); + dest += stride; + } + return; + } + + // bressenham's algorithm + if (ax > ay) + { + d = ay - (ax >> 1); + while(x1 != x2) + { + PutPixAveNC(x1,y1,color); + + if (d >= 0) + { + y1 += sy; + d -= ax; + } + x1 += sx; + d += ay; + } + } + else + { + d = ax - (ay >> 1); + while(y1 != y2) + { + PutPixAveNC(x1,y1,color); + if (d >= 0) + { + x1 += sx; + d -= ay; + } + y1 += sy; + d += ax; + } + } + PutPixAveNC(x1,y1,color); +} + +void CDraw32::DrawLineAANC(long x0, long y0, long x1, long y1, CPixel32 color) +// Wu antialiased line drawer. +//USE: Function to draw an antialiased line from (x0,y0) to (x1,y1), using an +// antialiasing approach published by Xiaolin Wu in the July 1991 issue of +// Computer Graphics (SIGGRAPH proceedings). +// +//IN: (x0,y0),(x1,y1) = line to draw +// color = 32-bit color +//OUT: none +{ + assert(buffer != NULL); + + // Make sure the line runs top to bottom + if (y0 > y1) + { + SWAP(y0,y1); + SWAP(x0,x1); + } + + long DeltaX = x1 - x0; + long DeltaY = y1 - y0; + long XDir; + + // Draw the initial pixel, which is always exactly intersected by + // the line and so needs no Alpha + PutPixAlphaNC(x0, y0, color); + + if (DeltaX >= 0) + { + XDir = 1; + } + else + { + XDir = -1; + DeltaX = -DeltaX; // make DeltaX positive + } + + // Special-case horizontal, vertical, and diagonal lines, which + // require no Alpha because they go right through the center of + // every pixel + if (DeltaY == 0) + { // Horizontal line + while (DeltaX-- != 0) + { + x0 += XDir; + PutPixAlphaNC(x0, y0, color); + } + return; + } + if (DeltaX == 0) + { // Vertical line + do + { + y0++; + PutPixAlphaNC(x0, y0, color); + } + while (--DeltaY != 0); + return; + } + if (DeltaX == DeltaY) + { // Diagonal line + do + { + x0 += XDir; + y0++; + PutPixAlphaNC(x0, y0, color); + } + while (--DeltaY != 0); + return; + } + + // Line is not horizontal, diagonal, or vertical + unsigned short ErrorAcc = 0; // initialize the line error accumulator to 0 + + // # of bits by which to shift ErrorAcc to get intensity level + const unsigned long IntensityShift = 16 - 8; + + // Is this an X-major or Y-major line? + if (DeltaY > DeltaX) + { + // Y-major line; calculate 16-bit fixed-point fractional part of a + // pixel that X advances each time Y advances 1 pixel, truncating the + // result so that we won't overrun the endpoint along the X axis + unsigned short ErrorAdj = unsigned short + (((unsigned long) DeltaX << 16) / (unsigned long) DeltaY); + + // Draw all pixels other than the first and last + while (--DeltaY) + { + unsigned short ErrorAccTemp = ErrorAcc; // remember currrent accumulated error + ErrorAcc += ErrorAdj; // calculate error for next pixel + if (ErrorAcc <= ErrorAccTemp) + { // The error accumulator turned over, so advance the X coord + x0 += XDir; + } + y0++; // Y-major, so always advance Y + // The IntensityBits most significant bits of ErrorAcc give us the + // intensity Alpha for this pixel, and the complement of the + // Alpha for the paired pixel + unsigned long Alpha = ErrorAcc >> IntensityShift; + unsigned long InvAlpha = 256-Alpha; + + PutPixAlphaNC(x0, y0, + ALPHA_PIX(GetPix(x0, y0), color, Alpha, InvAlpha)); + PutPixAlphaNC(x0+XDir, y0, + ALPHA_PIX(GetPix(x0+XDir, y0), color, InvAlpha, Alpha)); + } + // Draw the final pixel, which is always exactly intersected by the line + // and so needs no Alpha + PutPixAlphaNC(x1, y1, color); + return; + } + // It's an X-major line; calculate 16-bit fixed-point fractional part of a + // pixel that Y advances each time X advances 1 pixel, truncating the + // result to avoid overrunning the endpoint along the X axis + unsigned short ErrorAdj = unsigned short + (((unsigned long) DeltaY << 16) / (unsigned long) DeltaX); + // Draw all pixels other than the first and last + while (--DeltaX) + { + unsigned short ErrorAccTemp = ErrorAcc; // remember currrent accumulated error + ErrorAcc += ErrorAdj; // calculate error for next pixel + if (ErrorAcc <= ErrorAccTemp) + { // The error accumulator turned over, so advance the Y coord + y0++; + } + x0 += XDir; // X-major, so always advance X + // The IntensityBits most significant bits of ErrorAcc give us the + // intensity Alpha for this pixel, and the complement of the + // Alpha for the paired pixel + unsigned long Alpha = ErrorAcc >> IntensityShift; + unsigned long InvAlpha = 256-Alpha; + PutPixAlphaNC(x0, y0, + ALPHA_PIX(GetPix(x0, y0), color, Alpha, InvAlpha)); + PutPixAlphaNC(x0, y0+1, + ALPHA_PIX(GetPix(x0, y0+1), color, InvAlpha, Alpha)); + } + // Draw the final pixel, which is always exactly intersected by the line + // and so needs no Alpha + PutPixAlphaNC(x1, y1, color); +} + +void CDraw32::DrawRectNC(long ulx, long uly, long width, long height, CPixel32 color) +//USE: draw rectangle in solid color, no clipping +//IN: (ulx,uly) - coordinates of upper-left corner of rect +// width, height - dimensions of rectangle +// color - color value +//OUT: none +{ + + assert(buffer != NULL); + assert(ulx>=0); + assert(uly>=0); + assert(ulx+width= limit) + { + if (di < 0) + { + delta = 2*di + 2*y - 1; + if (delta <= 0) + { // move horizontal + last_x = x; + x++; + di += (2*x + 1); + } + else + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + } + else + { + if (di > 0) + { + delta = 2*di - 2*x -1; + if (delta <= 0) + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + else + { // move vertical + y--; + di += (1 - 2*y); + } + } + else /* di = 0 */ + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + } + } + + if (y != last_y) + { // circle fill + DrawLine(xc-last_x,yc+last_y,xc+last_x,yc+last_y,fill); + if (last_y > limit) + { + DrawLine(xc-last_x,yc-last_y,xc+last_x,yc-last_y,fill); + } + last_y = y; + } + } while (y >= limit); + } + + // draw edge + if (edge.a != 0) + { + x = 0; + y = r; + limit = 0; + di = 2*(1-r); + + do + { + // circle edge + PutPix(xc+x, yc+y, edge); + PutPix(xc-x, yc+y, edge); + if (y > limit) + { + PutPix(xc+x, yc-y, edge); + PutPix(xc-x, yc-y, edge); + } + + if (y >= limit) + { + if (di < 0) + { + delta = 2*di + 2*y - 1; + if (delta <= 0) + { // move horizontal + x++; + di += (2*x + 1); + } + else + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + } + else + { + if (di > 0) + { + delta = 2*di - 2*x -1; + if (delta <= 0) + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + else + { // move vertical + y--; + di += (1 - 2*y); + } + } + else /* di = 0 */ + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + } + } + } while (y >= limit); + } +} // DrawCircle + +void CDraw32::DrawCircleAve(long xc, long yc, long r, CPixel32 edge, CPixel32 fill) +//USE: Draw a simple circle in current color with Bresenham's +// circle algorithm. +// a circle with fill and edge colors averaged with dest (-1 = no color) +// See PROCEDURAL ELEMENTS FOR COMPUTER GRAPHICS +// David F. Rogers Pg. 48. +// +//IN: xc,yc - center +// r - radius +// edge - edge color +// fill - fill color +// +//OUT: none (a circle on in the off-screen buffer) +{ + long x,y; + long limit,di,delta; + long last_x,last_y; + long f; + + assert(buffer != NULL); + + if (r < 1) + return; + + // draw fill + if (fill.a != 0) + { + x = 0; last_x = x; + y = r; last_y = y; + di = 2*(1-r); + limit = 0; + + do + { + if (y >= limit) + { + if (di < 0) + { + delta = 2*di + 2*y - 1; + if (delta <= 0) + { // move horizontal + last_x = x; + x++; + di += (2*x + 1); + } + else + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + } + else + { + if (di > 0) + { + delta = 2*di - 2*x -1; + if (delta <= 0) + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + else + { // move vertical + y--; + di += (1 - 2*y); + } + } + else /* di = 0 */ + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + } + } + + if (y != last_y) + { // circle fill + for (f=xc-last_x; f<=xc+last_x; f++) + PutPixAve(f, yc+last_y, fill); + if (last_y > limit) + { + for (f=xc-last_x; f<=xc+last_x; f++) + PutPixAve(f, yc-last_y, fill); + } + last_y = y; + } + } while (y >= limit); + } + + // draw edge + if (edge.a != 0) + { + x = 0; + y = r; + limit = 0; + di = 2*(1-r); + + do + { + // circle edge + PutPixAve(xc+x, yc+y, edge); + PutPixAve(xc-x, yc+y, edge); + if (y > limit) + { + PutPixAve(xc+x, yc-y, edge); + PutPixAve(xc-x, yc-y, edge); + } + if (y >= limit) + { + if (di < 0) + { + delta = 2*di + 2*y - 1; + if (delta <= 0) + { // move horizontal + x++; + di += (2*x + 1); + } + else + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + } + else + { + if (di > 0) + { + delta = 2*di - 2*x -1; + if (delta <= 0) + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + else + { // move vertical + y--; + di += (1 - 2*y); + } + } + else /* di = 0 */ + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + } + } + } while (y >= limit); + } +} // DrawCircleAve + +//////////////////////////////////////////////////////////////////////////// +//Concave Polygon Scan Conversion +//////////////////////////////////////////////////////////////////////////// + +// concave: scan convert nvert-sided concave non-simple polygon +// with vertices at (point[i].x, point[i].y) for i in +// [0..nvert-1] within the window win by +// calling spanproc for each visible span of pixels. +// +// Polygon can be clockwise or counterclockwise. +// +// Algorithm does uniform point sampling at pixel centers. +// Inside-outside test done by even-odd rule: a point is +// considered inside if an emanating ray intersects the polygon +// an odd number of times. +// +// spanproc should fill in pixels from xl to xr inclusive on scanline y, +// +// e.g: +// spanproc(short y, short xl, short xr) +// { +// short x; +// for (x=xl; x<=xr; x++) +// pixel_write(x, y, pixelvalue); +// } + +typedef struct +{ // a polygon edge + // these are fixed point long ints for some accuracy & speed + long x; // x coordinate of edge's intersection with current scanline + long dx; // change in x with respect to y + long i; // edge number: edge i goes from pt[i] to + // pt[i+1] +} POLYEDGE; + +#define INT_SHIFT 13 + +// global for speed +static long n; // number of vertices +static POINT *pt; // vertices + +static long nact; // number of active edges +static POLYEDGE active[256]; // active edge list:edges crossing scanline y +static long ind[256]; // list of vertex indices, sorted by + // pt[ind[j]].y + +static void del_edge(long i) +// remove edge i from active list +{ + int j; + + for (j = 0; j < nact && active[j].i != i; j++) + ; + + // edge not in active list; happens at cliprect->top + if (j >= nact) + { + return; + } + + nact--; + memcpy(&active[j], &active[j + 1], (nact - j) * sizeof(active[0])); +} + +static void ins_edge(long i, long y) +// append edge i to end of active list +{ + int j; + long dx; + POINT *p; + POINT *q; + + j = i < n - 1 ? i + 1 : 0; + if (pt[i].y < pt[j].y) + { + p = &pt[i]; + q = &pt[j]; + } + else + { + p = &pt[j]; + q = &pt[i]; + } + + // initialize x position at intersection of edge with scanline y + if ((q->y - p->y) != 0) + { + dx = (((long)q->x - (long)p->x) * (long)(1<y - (long)p->y)); + } + else + { + // horizontal line + dx = 0; + } + + active[nact].dx = dx; + active[nact].x = (dx * (long)(y - p->y)) + ((long)p->x << INT_SHIFT); + active[nact].i = i; + nact++; +} + +// comparison routines for shellsort +int compare_ind(long *u, long *v) +{ + return (pt[*u].y <= pt[*v].y ? -1 : 1); +} + +int compare_active(POLYEDGE *u, POLYEDGE *v) +{ + return (u->x <= v->x ? -1 : 1); +} + +void shell_sort(void *vec, long n, long siz, + int (*compare)(void*,void*)) +// USE: shell sort aka heap sort. Best sort algorithm for almost sorted list. +{ + byte *a; + byte v[128]; // temp object + long i,j,h; + + a = (byte *)vec; + + // choose size of "heap" + for (h = 1; h <= n/9; h = 3*h+1) + ; + + // divide and conq. + for ( ; h > 0; h /= 3) + { + for (i = h; i < n; i++) + { + // v = a[i]; + memcpy(v,(a+i*siz),siz); + j = i; + // j >= h && a[j-h] > v + while ((j >= h) && compare((void*)(a+(j-h)*siz),(void*)v) > 0) + { + // a[j] = a[j-h] + memcpy((a+j*siz),(a+(j-h)*siz),siz); + j -= h; + } + // a[j] = v; + memcpy((a+j*siz),v,siz); + } + } +} + +void CDraw32::DrawPolygon(long nvert, POINT *point, CPixel32 edge, CPixel32 fill) +//USE: Scan convert a polygon +//IN: nvert: Number of vertices +// point: Vertices of polygon +// edge: edge color +// fill: fill color +//OUT: none +{ + long k, + y0, + y1, + y, + i, + j, + xl, + xr; + + assert(buffer != NULL); + + n = nvert; + + if (n <= 0) + { // nothing to do + return; + } + + pt = point; + + if (fill.a != 0) + { // draw fill + + // create y-sorted array of indices ind[k] into vertex list + for (k = 0; k < n; k++) + { + ind[k] = k; + } + + // sort ind by pt[ind[k]].y + shell_sort(ind, n, sizeof(long), (int (*)(void*,void*)) compare_ind); + + nact = 0; // start with empty active list + k = 0; // ind[k] is next vertex to process + + // ymin of polygon + y0 = MAX(clip_min_y-1, pt[ind[0]].y); + + // ymax of polygon + y1 = MIN(clip_max_y+1, pt[ind[n-1]].y); + + // step through scanlines + for (y = y0; y < y1; y++) + { + // Check vertices between previous scanline + // and current one, if any + for (; (k < n) && (pt[ind[k]].y <= y); k++) + { + i = ind[k]; + // insert or delete edges before and after vertex i + // (i-1 to i, and i to i+1) + // from active list if they cross scanline y + j = i > 0 ? i - 1 : n - 1; // vertex previous to i + if (pt[j].y < y) + { + // old edge, remove from active list + del_edge(j); + } + else + { + if (pt[j].y > y) + { + // new edge, add to active list + ins_edge(j, y); + } + } + j = i < n - 1 ? i + 1 : 0; // vertex next after i + if (pt[j].y < y) + { + // old edge, remove from active list + del_edge(i); + } + else + { + if (pt[j].y > y) + { + // new edge, add to active list + ins_edge(i, y); + } + } + } + + // sort active edge list by active[j].x + shell_sort(active, nact, sizeof(POLYEDGE), (int (*)(void*,void*))compare_active); + + // draw horizontal segments for scanline y + for (j = 0; j < nact; j += 2) + { // draw horizontal segments + // span 'tween j & j+1 is inside, span tween + // j+1 & j+2 is outside + + // left end of span + // convert back from fixed point - round down + xl = (long) (active[j].x >> INT_SHIFT); + if (xl < clip_min_x-1) + { + xl = clip_min_x-1; + } + + // right end of span + // convert back from fixed point - round down + xr = (long) (active[j + 1].x >> INT_SHIFT); + if (xr > clip_max_x) + { + xr = clip_max_x; + } + + if (xl < xr) + { + // draw pixels in span + DrawLine(xl+1,y,xr,y,fill); + } + + // increment edge coords + active[j].x += active[j].dx; + active[j + 1].x += active[j + 1].dx; + } + } + + // sort active edge list by active[j].x + shell_sort(active, nact, sizeof(POLYEDGE), (int (*)(void*,void*))compare_active); + + // draw horizontal segments for scanline y + for (j = 0; j < nact; j += 2) + { // draw horizontal segments + // span 'tween j & j+1 is inside, span tween + // j+1 & j+2 is outside + + // left end of span + // convert back from fixed point - round down + xl = (long) (active[j].x >> INT_SHIFT); + if (xl < clip_min_x-1) + { + xl = clip_min_x-1; + } + + // right end of span + // convert back from fixed point - round up + xr = (long) (active[j + 1].x >> INT_SHIFT); + if (xr > clip_max_x) + { + xr = clip_max_x; + } + + if (xl < xr) + { + // draw pixels in span + DrawLine(xl+1,y,xr,y,fill); + } + + // increment edge coords + active[j].x += active[j].dx; + active[j + 1].x += active[j + 1].dx; + } + } + + if (edge.a != 0) + { // draw edges + for (k = 0; k < n-1; k++) + { + DrawLineAA(pt[k].x,pt[k].y,pt[k+1].x,pt[k+1].y,edge); + } + + DrawLineAA(pt[n-1].x,pt[n-1].y,pt[0].x,pt[0].y,edge); + } + + return; +} + +void CDraw32::Blit(long dstX, long dstY, long width, long height, + CPixel32* srcImage, long srcX, long srcY, long srcStride) +//USE: simple blit +//IN: dstX, dstY - upper left corner of where image will land in buffer +// width, height - width and height of image +// srcImage - src image buffer +// srcX, srcY - upper left corner in src image +// srcStride - number of pixels per line in src image +//OUT: none +{ + CPixel32 *dst; + CPixel32 *src; + int x,y; + + assert(buffer != NULL); + + BlitClip(dstX, dstY, width, height, srcX, srcY); + + if (width < 1 || height < 1) + return; + + dst = &buffer[PIXPOS(dstX,dstY,stride)]; + src = &srcImage[PIXPOS(srcX,srcY,srcStride)]; + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { +// *dst++ = *src++; + byte alpha = src->a; + byte dst_alpha = dst->a; + *dst = ALPHA_PIX(*src, *dst, alpha, 256-alpha); + dst->a = dst_alpha; + ++dst; + ++src; + } + dst += (stride - width); + src += (srcStride - width); + } +} + +void CDraw32::BlitClip(long& dstX, long& dstY, + long& width, long& height, + long& srcX, long& srcY) +//USE: simple blit clip +//IN: dstX, dstY - upper left corner of where image will land in buffer +// width, height - width and height of image +// srcX, srcY - upper left corner in src image +//OUT: none +{ + + // clip to our buffer size + if (dstX < clip_min_x) + { + int dif = (clip_min_x - dstX); + dstX += dif; + srcX += dif; + width -= dif; + } + + if (dstY < clip_min_y) + { + int dif = (clip_min_y - dstY); + dstY += dif; + srcY += dif; + height -= dif; + } + + if (dstX+width-1 > clip_max_x) + { + width -= (dstX+width-1 - clip_max_x); + } + + if (dstY+height-1 > clip_max_y) + { + height -= (dstY+height-1 - clip_max_y); + } +} + +void CDraw32::BlitColor(long dstX, long dstY, long width, long height, + CPixel32* srcImage, long srcX, long srcY, long srcStride, CPixel32 color) +//USE: blit using image alpha as mask +//IN: dstX, dstY - upper left corner of where image will land in buffer +// width, height - width and height of image +// srcImage - src image buffer +// srcX, srcY - upper left corner in src image +// srcStride - number of pixels per line in src image +// color - color to apply to srcImage +//OUT: none +{ + CPixel32 *dst; + CPixel32 *src; + int x,y; + int dstNextLine; + int srcNextLine; + + assert(buffer != NULL); + + BlitClip(dstX, dstY, width, height, srcX, srcY); + + if (width < 1 || height < 1) + return; + + dst = &buffer[PIXPOS(dstX,dstY,stride)]; + src = &srcImage[PIXPOS(srcX,srcY,srcStride)]; + + dstNextLine = (stride - width); + srcNextLine = (srcStride - width); + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + byte alpha = src->a; + *dst = ALPHA_PIX(color, *dst, alpha, 256-alpha); + ++dst; + ++src; + } + dst += dstNextLine; + src += srcNextLine; + } +} + diff --git a/codemp/qcommon/cm_draw.h b/codemp/qcommon/cm_draw.h new file mode 100644 index 0000000..647bc5b --- /dev/null +++ b/codemp/qcommon/cm_draw.h @@ -0,0 +1,250 @@ +/////////////////////////////////////////////////////////////////////////////// +// CDraw32 Class Interface +// +// Basic drawing routines for 32-bit per pixel buffer +/////////////////////////////////////////////////////////////////////////////// + +#if !defined(CM_DRAW_H_INC) +#define CM_DRAW_H_INC + +#ifndef __linux__ +//#include +#include "../qcommon/platform.h" +#endif + +// calc offset into image array for a pixel at (x,y) +#define PIXPOS(x,y,stride) (((y)*(stride))+(x)) + +#ifndef MIN +// handy macros +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define ABS(x) ((x)<0 ? -(x):(x)) +#define SIGN(x) (((x) < 0) ? -1 : (((x) > 0) ? 1 : 0)) +#endif + +#ifndef CLAMP +#define SWAP(a,b) { a^=b; b^=a; a^=b; } +#define SQR(a) ((a)*(a)) +#define CLAMP(v,l,h) ((v)<(l) ? (l) : (v) > (h) ? (h) : (v)) +#define LERP(t, a, b) (((b)-(a))*(t) + (a)) + +// round a to nearest integer towards 0 +#define FLOOR(a) ((a)>0 ? (int)(a) : -(int)(-a)) + +// round a to nearest integer away from 0 +#define CEILING(a) \ +((a)==(int)(a) ? (a) : (a)>0 ? 1+(int)(a) : -(1+(int)(-a))) + +#include +#endif + +class CPixel32 +{ +public: + byte r; + byte g; + byte b; + byte a; + + CPixel32(byte R = 0, byte G = 0, byte B = 0, byte A = 255) : r(R), g(G), b(B), a(A) {} + CPixel32(long l) {r = (l >> 24) & 0xff; g = (l >> 16) & 0xff; b = (l >> 8) & 0xff; a = l & 0xff;}; + + ~CPixel32() + {} +}; + +#define PIX32_SIZE sizeof(CPixel32) + +// standard image operator macros +#define IMAGE_SIZE(width,height) ((width)*(height)*(PIX32_SIZE)) + + +inline CPixel32 AVE_PIX (CPixel32 x, CPixel32 y) + { CPixel32 t; t.r = (byte)(((int)x.r + (int)y.r)>>1); + t.g = (byte)(((int)x.g + (int)y.g)>>1); + t.b = (byte)(((int)x.b + (int)y.b)>>1); + t.a = (byte)(((int)x.a + (int)y.a)>>1); return t;} + +inline CPixel32 ALPHA_PIX (CPixel32 x, CPixel32 y, long alpha, long inv_alpha) + { CPixel32 t; t.r = (byte)((x.r*alpha + y.r*inv_alpha)>>8); + t.g = (byte)((x.g*alpha + y.g*inv_alpha)>>8); + t.b = (byte)((x.b*alpha + y.b*inv_alpha)>>8); +// t.a = (byte)((x.a*alpha + y.a*inv_alpha)>>8); return t;} + t.a = y.a; return t;} + +inline CPixel32 LIGHT_PIX (CPixel32 p, long light) +{ CPixel32 t; + t.r = (byte)CLAMP(((p.r * light)>>10) + p.r, 0, 255); + t.g = (byte)CLAMP(((p.g * light)>>10) + p.g, 0, 255); + t.b = (byte)CLAMP(((p.b * light)>>10) + p.b, 0, 255); + t.a = p.a; return t;} + +// Colors are 32-bit RGBA + +// draw class +class CDraw32 +{ +public: // static drawing context - static so we set only ONCE for many draw calls + static CPixel32* buffer; // pointer to pixel buffer (one active) + static long buf_width; // size of buffer + static long buf_height; // size of buffer + static long stride; // stride of buffer in pixels + static long clip_min_x; // clip bounds + static long clip_min_y; // clip bounds + static long clip_max_x; // clip bounds + static long clip_max_y; // clip bounds + static long* row_off; // Table for quick Y calculations + +private: + void BlitClip(long& dstX, long& dstY, + long& width, long& height, + long& srcX, long& srcY); + +protected: +public: + CDraw32(); // constructor + ~CDraw32(); // destructor + + // set the rect to clip drawing functions to + static void SetClip(long min_x, long min_y,long max_x, long max_y) + {clip_min_x = MAX(min_x,0); clip_max_x = MIN(max_x,buf_width-1); + clip_min_y = MAX(min_y,0); clip_max_y = MIN(max_y,buf_height-1);} + + static void GetClip(long& min_x, long& min_y,long& max_x, long& max_y) + {min_x = clip_min_x; min_y = clip_min_y; + max_x = clip_max_x; max_y = clip_max_y; } + + // set the buffer to use for drawing off-screen + static void SetBuffer(CPixel32* buf) {buffer = buf;}; + + // set the dimensions of the off-screen buffer + static bool SetBufferSize(long width,long height,long stride_len); + + // call this to free the table for quick y calcs before the program ends + static void CleanUp(void) + {if (row_off) delete [] row_off; row_off=NULL; buf_width=0; buf_height=0;} + + // set a pixel at (x,y) to color (no clipping) + void PutPixNC(long x, long y, CPixel32 color) + {buffer[row_off[y] + x] = color;} + + // set a pixel at (x,y) to color + void PutPix(long x, long y, CPixel32 color) + { // clipping check + if (x < clip_min_x || x > clip_max_x || + y < clip_min_y || y > clip_max_y) + return; + PutPixNC(x,y,color); + } + + // get the color of a pixel at (x,y) + CPixel32 GetPix(long x, long y) + {return buffer[row_off[y] + x];} + + // set a pixel at (x,y) with 50% translucency (no clip) + void PutPixAveNC(long x, long y, CPixel32 color) + { PutPixNC(x,y,AVE_PIX(GetPix(x, y), color)); } + + // set a pixel at (x,y) with 50% translucency + void PutPixAve(long x, long y, CPixel32 color) + { // clipping check + if (x < clip_min_x || x > clip_max_x || + y < clip_min_y || y > clip_max_y) + return; + PutPixNC(x,y,AVE_PIX(GetPix(x, y), color)); + } + + // set a pixel at (x,y) with translucency level (no clip) + void PutPixAlphaNC(long x, long y, CPixel32 color) + { PutPixNC(x,y,ALPHA_PIX(color, GetPix(x, y), color.a, 256-color.a));} + + // set a pixel at (x,y) with translucency level + void PutPixAlpha(long x, long y, CPixel32 color) + { // clipping check + if (x < clip_min_x || x > clip_max_x || + y < clip_min_y || y > clip_max_y) + return; + PutPixNC(x,y,ALPHA_PIX(color, GetPix(x, y), color.a, 256-color.a));} + + // clear screen buffer to color from start to end line + void ClearLines(CPixel32 color,long start,long end); + + // clear screen buffer to color provided + void ClearBuffer(CPixel32 color) + {ClearLines(color,0,buf_height-1);}; + + // fill buffer alpha from start to end line + void SetAlphaLines(byte alpha,long start,long end); + + // clear screen buffer to color provided + void SetAlphaBuffer(byte alpha) + {SetAlphaLines(alpha,0,buf_height-1);}; + + // clip a line segment to the clip rect + bool ClipLine(long& x1, long& y1, long& x2, long& y2); + + // draw a solid colored line, no clipping + void DrawLineNC(long x1, long y1, long x2, long y2, CPixel32 color); + + // draw a solid color line + void DrawLine(long x1, long y1, long x2, long y2, CPixel32 color) + { if (ClipLine(x1,y1,x2,y2)) DrawLineNC(x1,y1,x2,y2,color);} + + void DrawLineAveNC(long x1, long y1, long x2, long y2, CPixel32 color); + + // draw a translucent solid color line + void DrawLineAve(long x1, long y1, long x2, long y2, CPixel32 color) + { if (ClipLine(x1,y1,x2,y2)) DrawLineAveNC(x1,y1,x2,y2,color);} + + // draw an anti-aliased line, no clipping + void DrawLineAANC(long x0, long y0, long x1, long y1, CPixel32 color); + + // draw an anti-aliased line + void DrawLineAA(long x1, long y1, long x2, long y2, CPixel32 color) + { if (ClipLine(x1,y1,x2,y2)) DrawLineAANC(x1,y1,x2,y2,color);} + + // draw a filled rectangle, no clipping + void DrawRectNC(long ulx, long uly, long width, long height,CPixel32 color); + + // draw a filled rectangle + void DrawRect(long ulx, long uly, long width, long height, CPixel32 color); + + // draw a filled rectangle + void DrawRectAve(long ulx, long uly, long width, long height,CPixel32 color); + + // draw a box (unfilled rectangle) no clip + void DrawBoxNC(long ulx, long uly, long width, long height, CPixel32 color); + + // draw a box (unfilled rectangle) + void DrawBox(long ulx, long uly, long width, long height, CPixel32 color); + + // draw a box (unfilled rectangle) + void DrawBoxAve(long ulx, long uly, long width, long height, CPixel32 color); + + // draw a circle with fill and edge colors + void DrawCircle(long xc, long yc, long r, CPixel32 edge, CPixel32 fill); + + // draw a circle with fill and edge colors averaged with dest + void DrawCircleAve(long xc, long yc, long r, CPixel32 edge, CPixel32 fill); + + // draw a polygon (complex) with fill and edge colors + void DrawPolygon(long nvert, POINT *point, CPixel32 edge, CPixel32 fill); + + // simple blit function + void BlitNC(long dstX, long dstY, long dstWidth, long dstHeight, + CPixel32* srcImage, long srcX, long srcY, long srcStride); + + void Blit(long dstX, long dstY, long dstWidth, long dstHeight, + CPixel32* srcImage, long srcX, long srcY, long srcStride); + + // blit image times color + void BlitColor(long dstX, long dstY, long dstWidth, long dstHeight, + CPixel32* srcImage, long srcX, long srcY, long srcStride, CPixel32 color); + + void Emboss(long dstX, long dstY, long width, long height, + CPixel32* clrImage, long clrX, long clrY, long clrStride); +}; + +/////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/codemp/qcommon/cm_landscape.h b/codemp/qcommon/cm_landscape.h new file mode 100644 index 0000000..15f40fb --- /dev/null +++ b/codemp/qcommon/cm_landscape.h @@ -0,0 +1,271 @@ +#if !defined(CM_LANDSCAPE_H_INC) +#define CM_LANDSCAPE_H_INC + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include +#pragma warning (pop) + +using namespace std; + +// These are the root classes using data shared in both the server and the renderer. +// This common data is also available to physics + +#define HEIGHT_RESOLUTION 256 + +// Trying to make a guess at the optimal step through the patches +// This is the average of 1 side and the diagonal presuming a square patch +#define TERRAIN_STEP_MAGIC (1.0f / 1.2071f) + +#define MIN_TERXELS 2 +#define MAX_TERXELS 8 +// Defined as 1 << (sqrt(MAX_TERXELS) + 1) +#define MAX_VARIANCE_SIZE 16 + +// Maximum number of instances to pick from an instance file +#define MAX_INSTANCE_TYPES 16 + +// Types of areas + +typedef enum +{ + AT_NONE, + AT_FLAT, + AT_BSP, + AT_NPC, + AT_GROUP, + AT_RIVER, + AT_OBJECTIVE, + AT_PLAYER, + +} areaType_t; + +class CArea +{ +private: + vec3_t mPosition; + float mRadius; + float mAngle; + float mAngleDiff; + int mType; + int mVillageID; +public: + CArea(void) {} + ~CArea(void) {} + + void Init(vec3_t pos, float radius, float angle = 0.0f, int type = AT_NONE, float angleDiff = 0.0f, int villageID = 0) + { + VectorCopy(pos, mPosition); + mRadius = radius; + mAngle = angle; + mAngleDiff = angleDiff; + mType = type; + mVillageID = villageID; + } + float GetRadius(void) const { return(mRadius); } + float GetAngle(void) const { return(mAngle); } + float GetAngleDiff(void) const { return(mAngleDiff); } + vec3_t &GetPosition(void) { return(mPosition); } + int GetType(void) const { return(mType); } + int GetVillageID(void) const { return(mVillageID); } +}; + +typedef list areaList_t; +typedef list::iterator areaIter_t; + +class CCMHeightDetails +{ +private: + int mContents; + int mSurfaceFlags; +public: + CCMHeightDetails(void) {} + ~CCMHeightDetails(void) {} + + // Accessors + const int GetSurfaceFlags(void) const { return(mSurfaceFlags); } + const int GetContents(void) const { return(mContents); } + void SetFlags(const int con, const int sf) { mContents = con; mSurfaceFlags = sf; } +}; + +class CCMPatch +{ +protected: + class CCMLandScape *owner; // Owning landscape + int mHx, mHy; // Terxel coords of patch + byte *mHeightMap; // Pointer to height map to use + byte mCornerHeights[4]; // Heights at the corners of the patch + vec3_t mWorldCoords; // World coordinate offset of this patch. + vec3pair_t mBounds; // mins and maxs of the patch for culling + int mNumBrushes; // number of brushes to collide with in the patch + struct cbrush_s *mPatchBrushData; // List of brushes that make up the patch + int mSurfaceFlags; // surfaceflag of the heightshader + int mContentFlags; // contents of the heightshader +public: + // Constructors + CCMPatch(void) {} + ~CCMPatch(void); + + // Accessors + const vec3_t &GetWorld(void) const { return(mWorldCoords); } + const vec3_t &GetMins(void) const { return(mBounds[0]); } + const vec3_t &GetMaxs(void) const { return(mBounds[1]); } + const vec3pair_t &GetBounds(void) const { return(mBounds); } + const int GetHeightMapX(void) const { return(mHx); } + const int GetHeightMapY(void) const { return(mHy); } + const int GetHeight(int corner) const { return(mCornerHeights[corner]); } + const int GetNumBrushes(void) const { return(mNumBrushes); } + struct cbrush_s *GetCollisionData(void) const { return(mPatchBrushData); } + + void SetSurfaceFlags(const int in) { mSurfaceFlags = in; } + const int GetSurfaceFlags(void) const { return(mSurfaceFlags); } + void SetContents(const int in) { mContentFlags = in; } + const int GetContents(void) const { return(mContentFlags); } + + // Prototypes + void Init(CCMLandScape *ls, int heightX, int heightY, vec3_t world, byte *hMap, byte *patchBrushData); + void InitPlane(struct cbrushside_s *side, cplane_t *plane, vec3_t p0, vec3_t p1, vec3_t p2); + void CreatePatchPlaneData(void); + + void* GetAdjacentBrushX ( int x, int y ); + void* GetAdjacentBrushY ( int x, int y ); +}; + +class CRandomTerrain; + +class CCMLandScape +{ +private: + int mRefCount; // Number of times this class is referenced + thandle_t mTerrainHandle; + byte *mHeightMap; // Pointer to byte array of height samples + byte *mFlattenMap; // Pointer to byte array of flatten samples + int mWidth, mHeight; // Width and height of heightMap excluding the 1 pixel edge + int mTerxels; // Number of terxels per patch side + vec3_t mTerxelSize; // Vector to scale heightMap samples to real world coords + vec3pair_t mBounds; // Real world bounds of terrain brush + vec3_t mSize; // Size of terrain brush in real world coords excluding 1 patch edge + vec3_t mPatchSize; // Size of each patch in the x and y directions only + float mPatchScalarSize; // Horizontal size of the patch + int mBlockWidth, mBlockHeight; // Width and height of heightfield on blocks + CCMPatch *mPatches; + byte *mPatchBrushData; // Base memory from which the patch brush data is taken + bool mHasPhysics; // Set to true unless disabled + CRandomTerrain *mRandomTerrain; + + int mBaseWaterHeight; // Base water height in terxels + float mWaterHeight; // Real world height of the water + int mWaterContents; // Contents of the water shader + int mWaterSurfaceFlags; // Surface flags of the water shader + + unsigned long holdrand; + + list mAreas; // List of flattened areas on this landscape + list::iterator mAreasIt; + + CCMHeightDetails mHeightDetails[HEIGHT_RESOLUTION]; // Surfaceflags per height + vec3_t *mCoords; // Temp storage for real world coords + +public: + CCMLandScape(const char *configstring, bool server); + ~CCMLandScape(void); + + CCMPatch *GetPatch(int x, int y); + + // Prototypes + void PatchCollide(struct traceWork_s *tw, trace_t &trace, const vec3_t start, const vec3_t end, int checkcount); + void TerrainPatchIterate(void (*IterateFunc)( CCMPatch *, void * ), void *userdata) const; + float GetWorldHeight(vec3_t origin, const vec3pair_t bounds, bool aboveGround) const; + float WaterCollide(const vec3_t begin, const vec3_t end, float fraction) const; + void UpdatePatches(void); + void GetTerxelLocalCoords ( int x, int y, vec3_t coords[8] ); + void LoadTerrainDef(const char *td); + void SetShaders(int height, class CCMShader *shader); + void FlattenArea(CArea *area, int height, bool save, bool forceHeight, bool smooth); + void CarveLine ( vec3_t start, vec3_t end, int depth, int width ); + void CarveBezierCurve ( int numCtlPoints, vec3_t* ctlPoints, int steps, int depth, int size ); + void SaveArea(CArea *area); + float FractionBelowLevel(CArea *area, int height); + bool AreaCollision(CArea *area, int *areaTypes, int areaTypeCount); + CArea *GetFirstArea(void); + CArea *GetFirstObjectiveArea(void); + CArea *GetPlayerArea(void); + CArea *GetNextArea(void); + CArea *GetNextObjectiveArea(void); + + // Accessors + const int GetRefCount(void) const { return(mRefCount); } + void IncreaseRefCount(void) { mRefCount++; } + void DecreaseRefCount(void) { mRefCount--; } + const vec3pair_t &GetBounds(void) const { return(mBounds); } + const vec3_t &GetMins(void) const { return(mBounds[0]); } + const vec3_t &GetMaxs(void) const { return(mBounds[1]); } + const vec3_t &GetSize(void) const { return(mSize); } + const vec3_t &GetTerxelSize(void) const { return(mTerxelSize); } + const vec3_t &GetPatchSize(void) const { return(mPatchSize); } + const float GetPatchWidth(void) const { return(mPatchSize[0]); } + const float GetPatchHeight(void) const { return(mPatchSize[1]); } + const float GetPatchScalarSize(void) const { return(mPatchScalarSize); } + const int GetTerxels(void) const { return(mTerxels); } + const int GetRealWidth(void) const { return(mWidth + 1); } + const int GetRealHeight(void) const { return(mHeight + 1); } + const int GetRealArea(void) const { return((mWidth + 1) * (mHeight + 1)); } + const int GetWidth(void) const { return(mWidth); } + const int GetHeight(void) const { return(mHeight); } + const int GetArea(void) const { return(mWidth * mHeight); } + const int GetBlockWidth(void) const { return(mBlockWidth); } + const int GetBlockHeight(void) const { return(mBlockHeight); } + const int GetBlockCount(void) const { return(mBlockWidth * mBlockHeight); } + byte *GetHeightMap(void) const { return(mHeightMap); } + byte *GetFlattenMap(void) const { return(mFlattenMap); } + const thandle_t GetTerrainId(void) const { return(mTerrainHandle); } + void SetTerrainId(const thandle_t terrainId) { mTerrainHandle = terrainId; } + const float CalcWorldHeight(int height) const { return((height * mTerxelSize[2]) + mBounds[0][2]); } + const bool GetHasPhysics(void) const { return(mHasPhysics); } + const bool GetIsRandom(void) const { return(mRandomTerrain != 0); } + const int GetSurfaceFlags(int height) const { return(mHeightDetails[height].GetSurfaceFlags()); } + const int GetContentFlags(int height) const { return(mHeightDetails[height].GetContents()); } + void CalcRealCoords(void); + vec3_t *GetCoords(void) const { return(mCoords); } + + int GetBaseWaterHeight(void) const { return(mBaseWaterHeight); } + void SetRealWaterHeight(int height) { mWaterHeight = height * mTerxelSize[2]; } + float GetWaterHeight(void) const { return(mWaterHeight); } + int GetWaterContents(void) const { return(mWaterContents); } + int GetWaterSurfaceFlags(void) const { return(mWaterSurfaceFlags); } + + CRandomTerrain *GetRandomTerrain(void) { return mRandomTerrain; } + + void rand_seed(int seed); + unsigned long get_rand_seed(void) { return holdrand; } + + float flrand(float min, float max); + int irand(int min, int max); +}; + +void CM_TerrainPatchIterate(const class CCMLandScape *ls, void (*IterateFunc)( CCMPatch *, void * ), void *userdata); +class CCMLandScape *CM_InitTerrain(const char *configstring, thandle_t terrainId, bool server); +float CM_GetWorldHeight(const CCMLandScape *landscape, vec3_t origin, const vec3pair_t bounds, bool aboveGround); +void CM_FlattenArea(CCMLandScape *landscape, CArea *area, int height, bool save, bool forceHeight, bool smooth); +void CM_CarveBezierCurve (CCMLandScape *landscape, int numCtls, vec3_t* ctls, int steps, int depth, int size ); +void CM_SaveArea(CCMLandScape *landscape, CArea *area); +float CM_FractionBelowLevel(CCMLandScape *landscape, CArea *area, int height); +bool CM_AreaCollision(class CCMLandScape *landscape, class CArea *area, int *areaTypes, int areaTypeCount); +CArea *CM_GetFirstArea(CCMLandScape *landscape); +CArea *CM_GetFirstObjectiveArea(CCMLandScape *landscape); +CArea *CM_GetPlayerArea(class CCMLandScape *common); +CArea *CM_GetNextArea(CCMLandScape *landscape); +CArea *CM_GetNextObjectiveArea(CCMLandScape *landscape); +void CM_CircularIterate(byte *data, int width, int height, int xo, int yo, int insideRadius, int outsideRadius, int *user, void (*callback)(byte *, float, int *)); + +CRandomTerrain *CreateRandomTerrain(const char *config, CCMLandScape *landscape, byte *heightmap, int width, int height); + +void SV_LoadMissionDef(const char *configstring, class CCMLandScape *landscape); +void CL_CreateRandomTerrain(const char *config, class CCMLandScape *landscape, byte *image, int width, int height); +void CL_LoadInstanceDef(const char *configstring, class CCMLandScape *landscape); +void CL_LoadMissionDef(const char *configstring, class CCMLandScape *landscape); + +extern cvar_t *com_terrainPhysics; + +#endif + +// end diff --git a/codemp/qcommon/cm_load.cpp b/codemp/qcommon/cm_load.cpp new file mode 100644 index 0000000..f1326da --- /dev/null +++ b/codemp/qcommon/cm_load.cpp @@ -0,0 +1,1184 @@ +// cmodel.c -- model loading +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "cm_local.h" +#include "cm_landscape.h" //rwwRMG - include +//#include "../RMG/RM_Headers.h" //rwwRMG - include + +#ifdef BSPC + +#include "../bspc/l_qfiles.h" + +void SetPlaneSignbits (cplane_t *out) { + int bits, j; + + // for fast box on planeside test + bits = 0; + for (j=0 ; j<3 ; j++) { + if (out->normal[j] < 0) { + bits |= 1<signbits = bits; +} +#endif //BSPC + +// to allow boxes to be treated as brush models, we allocate +// some extra indexes along with those needed by the map +#define BOX_BRUSHES 1 +#define BOX_SIDES 6 +#define BOX_LEAFS 2 +#define BOX_PLANES 12 + +#define LL(x) x=LittleLong(x) + + +clipMap_t cmg; //rwwRMG - changed from cm +int c_pointcontents; +int c_traces, c_brush_traces, c_patch_traces; + + +byte *cmod_base; + +#ifndef BSPC +cvar_t *cm_noAreas; +cvar_t *cm_noCurves; +cvar_t *cm_playerCurveClip; +#endif + +cmodel_t box_model; +cplane_t *box_planes; +cbrush_t *box_brush; + + + +void CM_InitBoxHull (void); +void CM_FloodAreaConnections (clipMap_t &cm); + +//rwwRMG - added: +clipMap_t SubBSP[MAX_SUB_BSP]; +int NumSubBSP, TotalSubModels; + +/* +=============================================================================== + + MAP LOADING + +=============================================================================== +*/ + +/* +================= +CMod_LoadShaders +================= +*/ +void CMod_LoadShaders( lump_t *l, clipMap_t &cm ) +{ + dshader_t *in; + int i, count; + CCMShader *out; + + in = (dshader_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) { + Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size"); + } + count = l->filelen / sizeof(*in); + + if (count < 1) { + Com_Error (ERR_DROP, "Map with no shaders"); + } + cm.shaders = (CCMShader *)Hunk_Alloc( (1+count) * sizeof( *cm.shaders ), h_high ); + cm.numShaders = count; + + out = cm.shaders; + for ( i = 0; i < count; i++, in++, out++ ) + { + Q_strncpyz(out->shader, in->shader, MAX_QPATH); + out->contentFlags = LittleLong( in->contentFlags ); + out->surfaceFlags = LittleLong( in->surfaceFlags ); + } +} + + +/* +================= +CMod_LoadSubmodels +================= +*/ +void CMod_LoadSubmodels( lump_t *l, clipMap_t &cm ) { + dmodel_t *in; + cmodel_t *out; + int i, j, count; + int *indexes; + + in = (dmodel_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "CMod_LoadSubmodels: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no models"); + cm.cmodels = (struct cmodel_s *)Hunk_Alloc( count * sizeof( *cm.cmodels ), h_high ); + cm.numSubModels = count; + + if ( count > MAX_SUBMODELS ) { + Com_Error( ERR_DROP, "MAX_SUBMODELS exceeded" ); + } + + for ( i=0 ; imins[j] = LittleFloat (in->mins[j]) - 1; + out->maxs[j] = LittleFloat (in->maxs[j]) + 1; + } + + //rwwRMG - sof2 doesn't have to add this &cm == &cmg check. + //Are they getting leaf data elsewhere? (the reason this needs to be done is + //in sub bsp instances the first brush model isn't necessary a world model and might be + //real architecture) + if ( i == 0 && &cm == &cmg ) { + out->firstNode = 0; + continue; // world model doesn't need other info + } + + // make a "leaf" just to hold the model's brushes and surfaces + out->firstNode = -1; + + // make a "leaf" just to hold the model's brushes and surfaces + out->leaf.numLeafBrushes = LittleLong( in->numBrushes ); + indexes = (int *)Hunk_Alloc( out->leaf.numLeafBrushes * 4, h_high ); + out->leaf.firstLeafBrush = indexes - cm.leafbrushes; + for ( j = 0 ; j < out->leaf.numLeafBrushes ; j++ ) { + indexes[j] = LittleLong( in->firstBrush ) + j; + } + + out->leaf.numLeafSurfaces = LittleLong( in->numSurfaces ); + indexes = (int *)Hunk_Alloc( out->leaf.numLeafSurfaces * 4, h_high ); + out->leaf.firstLeafSurface = indexes - cm.leafsurfaces; + for ( j = 0 ; j < out->leaf.numLeafSurfaces ; j++ ) { + indexes[j] = LittleLong( in->firstSurface ) + j; + } + } +} + + +/* +================= +CMod_LoadNodes + +================= +*/ +void CMod_LoadNodes( lump_t *l, clipMap_t &cm ) { + dnode_t *in; + int child; + cNode_t *out; + int i, j, count; + + in = (dnode_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map has no nodes"); + cm.nodes = (cNode_t *)Hunk_Alloc( count * sizeof( *cm.nodes ), h_high ); + cm.numNodes = count; + + out = cm.nodes; + + for (i=0 ; iplane = cm.planes + LittleLong( in->planeNum ); + for (j=0 ; j<2 ; j++) + { + child = LittleLong (in->children[j]); + out->children[j] = child; + } + } + +} + +/* +================= +CM_BoundBrush + +================= +*/ +void CM_BoundBrush( cbrush_t *b ) { + b->bounds[0][0] = -b->sides[0].plane->dist; + b->bounds[1][0] = b->sides[1].plane->dist; + + b->bounds[0][1] = -b->sides[2].plane->dist; + b->bounds[1][1] = b->sides[3].plane->dist; + + b->bounds[0][2] = -b->sides[4].plane->dist; + b->bounds[1][2] = b->sides[5].plane->dist; +} + + +/* +================= +CMod_LoadBrushes + +================= +*/ +void CMod_LoadBrushes( lump_t *l, clipMap_t &cm ) { + dbrush_t *in; + cbrush_t *out; + int i, count; + + in = (dbrush_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = l->filelen / sizeof(*in); + + cm.brushes = (cbrush_t *)Hunk_Alloc( ( BOX_BRUSHES + count ) * sizeof( *cm.brushes ), h_high ); + cm.numBrushes = count; + + out = cm.brushes; + + for ( i=0 ; isides = cm.brushsides + LittleLong(in->firstSide); + out->numsides = LittleLong(in->numSides); + + out->shaderNum = LittleLong( in->shaderNum ); + if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum ); + } + out->contents = cm.shaders[out->shaderNum].contentFlags; + + // Landscapes are set up afterwards in the entity spawning + //out->landscape = NULL; //the memory was cleared already by hunk_alloc + //out->checkcount=0; + + CM_BoundBrush( out ); + } + +} + +/* +================= +CMod_LoadLeafs +================= +*/ +void CMod_LoadLeafs (lump_t *l, clipMap_t &cm) +{ + int i; + cLeaf_t *out; + dleaf_t *in; + int count; + + in = (dleaf_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no leafs"); + + cm.leafs = (cLeaf_t *)Hunk_Alloc( ( BOX_LEAFS + count ) * sizeof( *cm.leafs ), h_high ); + cm.numLeafs = count; + + out = cm.leafs; + for ( i=0 ; icluster = LittleLong (in->cluster); + out->area = LittleLong (in->area); + out->firstLeafBrush = LittleLong (in->firstLeafBrush); + out->numLeafBrushes = LittleLong (in->numLeafBrushes); + out->firstLeafSurface = LittleLong (in->firstLeafSurface); + out->numLeafSurfaces = LittleLong (in->numLeafSurfaces); + + if (out->cluster >= cm.numClusters) + cm.numClusters = out->cluster + 1; + if (out->area >= cm.numAreas) + cm.numAreas = out->area + 1; + } + + cm.areas = (cArea_t *)Hunk_Alloc( cm.numAreas * sizeof( *cm.areas ), h_high ); + cm.areaPortals = (int *)Hunk_Alloc( cm.numAreas * cm.numAreas * sizeof( *cm.areaPortals ), h_high ); +} + +/* +================= +CMod_LoadPlanes +================= +*/ +void CMod_LoadPlanes (lump_t *l, clipMap_t &cm) +{ + int i, j; + cplane_t *out; + dplane_t *in; + int count; + int bits; + + in = (dplane_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no planes"); + cm.planes = (struct cplane_s *)Hunk_Alloc( ( BOX_PLANES + count ) * sizeof( *cm.planes ), h_high ); + cm.numPlanes = count; + + out = cm.planes; + + for ( i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) + bits |= 1<dist = LittleFloat (in->dist); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +CMod_LoadLeafBrushes +================= +*/ +void CMod_LoadLeafBrushes (lump_t *l, clipMap_t &cm) +{ + int i; + int *out; + int *in; + int count; + + in = (int *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + cm.leafbrushes = (int *)Hunk_Alloc( (count + BOX_BRUSHES) * sizeof( *cm.leafbrushes ), h_high ); + cm.numLeafBrushes = count; + + out = cm.leafbrushes; + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + cm.leafsurfaces = (int *)Hunk_Alloc( count * sizeof( *cm.leafsurfaces ), h_high ); + cm.numLeafSurfaces = count; + + out = cm.leafsurfaces; + + for ( i=0 ; ifileofs); + if ( l->filelen % sizeof(*in) ) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = l->filelen / sizeof(*in); + + cm.brushsides = (cbrushside_t *)Hunk_Alloc( ( BOX_SIDES + count ) * sizeof( *cm.brushsides ), h_high ); + cm.numBrushSides = count; + + out = cm.brushsides; + + for ( i=0 ; iplaneNum ); + out->plane = &cm.planes[num]; + out->shaderNum = LittleLong( in->shaderNum ); + if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushSides: bad shaderNum: %i", out->shaderNum ); + } + } +} + + +/* +================= +CMod_LoadEntityString +================= +*/ +void CMod_LoadEntityString( lump_t *l, clipMap_t &cm ) { + cm.entityString = (char *)Hunk_Alloc( l->filelen, h_high ); + cm.numEntityChars = l->filelen; + Com_Memcpy (cm.entityString, cmod_base + l->fileofs, l->filelen); +} + +/* +================= +CMod_LoadVisibility +================= +*/ +#define VIS_HEADER 8 +void CMod_LoadVisibility( lump_t *l, clipMap_t &cm ) { + int len; + byte *buf; + + len = l->filelen; + if ( !len ) { + cm.clusterBytes = ( cm.numClusters + 31 ) & ~31; + cm.visibility = (unsigned char *)Hunk_Alloc( cm.clusterBytes, h_high ); + Com_Memset( cm.visibility, 255, cm.clusterBytes ); + return; + } + buf = cmod_base + l->fileofs; + + cm.vised = qtrue; + cm.visibility = (unsigned char *)Hunk_Alloc( len, h_high ); + cm.numClusters = LittleLong( ((int *)buf)[0] ); + cm.clusterBytes = LittleLong( ((int *)buf)[1] ); + Com_Memcpy (cm.visibility, buf + VIS_HEADER, len - VIS_HEADER ); +} + +//================================================================== + + +/* +================= +CMod_LoadPatches +================= +*/ +#define MAX_PATCH_VERTS 1024 +void CMod_LoadPatches( lump_t *surfs, lump_t *verts, clipMap_t &cm ) { + drawVert_t *dv, *dv_p; + dsurface_t *in; + int count; + int i, j; + int c; + cPatch_t *patch; + vec3_t points[MAX_PATCH_VERTS]; + int width, height; + int shaderNum; + + in = (dsurface_t *)(cmod_base + surfs->fileofs); + if (surfs->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + cm.numSurfaces = count = surfs->filelen / sizeof(*in); + cm.surfaces = (cPatch_t ** )Hunk_Alloc( cm.numSurfaces * sizeof( cm.surfaces[0] ), h_high ); + + dv = (drawVert_t *)(cmod_base + verts->fileofs); + if (verts->filelen % sizeof(*dv)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + + // scan through all the surfaces, but only load patches, + // not planar faces + for ( i = 0 ; i < count ; i++, in++ ) { + if ( LittleLong( in->surfaceType ) != MST_PATCH ) { + continue; // ignore other surfaces + } + // FIXME: check for non-colliding patches + + cm.surfaces[ i ] = patch = (cPatch_t *)Hunk_Alloc( sizeof( *patch ), h_high ); + + // load the full drawverts onto the stack + width = LittleLong( in->patchWidth ); + height = LittleLong( in->patchHeight ); + c = width * height; + if ( c > MAX_PATCH_VERTS ) { + Com_Error( ERR_DROP, "ParseMesh: MAX_PATCH_VERTS" ); + } + + dv_p = dv + LittleLong( in->firstVert ); + for ( j = 0 ; j < c ; j++, dv_p++ ) { + points[j][0] = LittleFloat( dv_p->xyz[0] ); + points[j][1] = LittleFloat( dv_p->xyz[1] ); + points[j][2] = LittleFloat( dv_p->xyz[2] ); + } + + shaderNum = LittleLong( in->shaderNum ); + patch->contents = cm.shaders[shaderNum].contentFlags; + patch->surfaceFlags = cm.shaders[shaderNum].surfaceFlags; + + // create the internal facet structure + patch->pc = CM_GeneratePatchCollide( width, height, points ); + } +} + +//================================================================== + +unsigned CM_LumpChecksum(lump_t *lump) { + return LittleLong (Com_BlockChecksum (cmod_base + lump->fileofs, lump->filelen)); +} + +unsigned CM_Checksum(dheader_t *header) { + unsigned checksums[16]; + checksums[0] = CM_LumpChecksum(&header->lumps[LUMP_SHADERS]); + checksums[1] = CM_LumpChecksum(&header->lumps[LUMP_LEAFS]); + checksums[2] = CM_LumpChecksum(&header->lumps[LUMP_LEAFBRUSHES]); + checksums[3] = CM_LumpChecksum(&header->lumps[LUMP_LEAFSURFACES]); + checksums[4] = CM_LumpChecksum(&header->lumps[LUMP_PLANES]); + checksums[5] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHSIDES]); + checksums[6] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHES]); + checksums[7] = CM_LumpChecksum(&header->lumps[LUMP_MODELS]); + checksums[8] = CM_LumpChecksum(&header->lumps[LUMP_NODES]); + checksums[9] = CM_LumpChecksum(&header->lumps[LUMP_SURFACES]); + checksums[10] = CM_LumpChecksum(&header->lumps[LUMP_DRAWVERTS]); + + return LittleLong(Com_BlockChecksum(checksums, 11 * 4)); +} + +/* +================== +CM_LoadMap + +Loads in the map and all submodels +================== +*/ +void *gpvCachedMapDiskImage = NULL; +char gsCachedMapDiskImage[MAX_QPATH]; +qboolean gbUsingCachedMapDataRightNow = qfalse; // if true, signifies that you can't delete this at the moment!! (used during z_malloc()-fail recovery attempt) + +// called in response to a "devmapbsp blah" or "devmapall blah" command, do NOT use inside CM_Load unless you pass in qtrue +// +// new bool return used to see if anything was freed, used during z_malloc failure re-try +// +qboolean CM_DeleteCachedMap(qboolean bGuaranteedOkToDelete) +{ + qboolean bActuallyFreedSomething = qfalse; + + if (bGuaranteedOkToDelete || !gbUsingCachedMapDataRightNow) + { + // dump cached disk image... + // + if (gpvCachedMapDiskImage) + { + Z_Free( gpvCachedMapDiskImage ); + gpvCachedMapDiskImage = NULL; + + bActuallyFreedSomething = qtrue; + } + gsCachedMapDiskImage[0] = '\0'; + + // force map loader to ignore cached internal BSP structures for next level CM_LoadMap() call... + // + cmg.name[0] = '\0'; + } + + return bActuallyFreedSomething; +} + + + + + +static void CM_LoadMap_Actual( const char *name, qboolean clientload, int *checksum, clipMap_t &cm ) +{ //rwwRMG - function needs heavy modification + int *buf; + int i; + dheader_t header; + static unsigned last_checksum; + char origName[MAX_OSPATH]; + void *newBuff = 0; + + if ( !name || !name[0] ) { + Com_Error( ERR_DROP, "CM_LoadMap: NULL name" ); + } + +#ifndef BSPC + cm_noAreas = Cvar_Get ("cm_noAreas", "0", CVAR_CHEAT); + cm_noCurves = Cvar_Get ("cm_noCurves", "0", CVAR_CHEAT); + cm_playerCurveClip = Cvar_Get ("cm_playerCurveClip", "1", CVAR_ARCHIVE|CVAR_CHEAT ); +#endif + Com_DPrintf( "CM_LoadMap( %s, %i )\n", name, clientload ); + + if ( !strcmp( cm.name, name ) && clientload ) { + *checksum = last_checksum; + return; + } + + strcpy(origName, name); + + if (&cm == &cmg) + { + // free old stuff + CM_ClearMap(); + CM_ClearLevelPatches(); + } + + // free old stuff + Com_Memset( &cm, 0, sizeof( cm ) ); + + if ( !name[0] ) { + cm.numLeafs = 1; + cm.numClusters = 1; + cm.numAreas = 1; + cm.cmodels = (struct cmodel_s *)Hunk_Alloc( sizeof( *cm.cmodels ), h_high ); + *checksum = 0; + return; + } + + // + // load the file + // + //rww - Doesn't this sort of defeat the purpose? We're clearing it even if the map is the same as the last one! + //Not touching it though in case I'm just overlooking something. + if (gpvCachedMapDiskImage && &cm == &cmg) // MP code: this'll only be NZ if we got an ERR_DROP during last map load, + { // so it's really just a safety measure. + Z_Free( gpvCachedMapDiskImage); + gpvCachedMapDiskImage = NULL; + } + +#ifndef BSPC + // + // load the file into a buffer that we either discard as usual at the bottom, or if we've got enough memory + // then keep it long enough to save the renderer re-loading it (if not dedicated server), + // then discard it after that... + // + buf = NULL; + fileHandle_t h; + const int iBSPLen = FS_FOpenFileRead( name, &h, qfalse ); + if (h) + { + newBuff = Z_Malloc( iBSPLen, TAG_BSP_DISKIMAGE ); + FS_Read( newBuff, iBSPLen, h); + FS_FCloseFile( h ); + + buf = (int*) newBuff; // so the rest of the code works as normal + if (&cm == &cmg) + { + gpvCachedMapDiskImage = newBuff; + newBuff = 0; + } + + // carry on as before... + // + } +#else + const int iBSPLen = LoadQuakeFile((quakefile_t *) name, (void **)&buf); +#endif + + if ( !buf ) { + Com_Error (ERR_DROP, "Couldn't load %s", name); + } + + last_checksum = LittleLong (Com_BlockChecksum (buf, iBSPLen)); + *checksum = last_checksum; + + header = *(dheader_t *)buf; + for (i=0 ; iinteger +// || we're on a big-endian machine + ) + { + Z_Free( gpvCachedMapDiskImage ); + gpvCachedMapDiskImage = NULL; + } + else + { + // ... do nothing, and let the renderer free it after it's finished playing with it... + // + } +#else + FS_FreeFile (buf); +#endif + + CM_FloodAreaConnections (cm); + + // allow this to be cached if it is loaded by the server + if ( !clientload ) { + Q_strncpyz( cm.name, origName, sizeof( cm.name ) ); + } +} + + +// need a wrapper function around this because of multiple returns, need to ensure bool is correct... +// +void CM_LoadMap( const char *name, qboolean clientload, int *checksum ) +{ + gbUsingCachedMapDataRightNow = qtrue; // !!!!!!!!!!!!!!!!!! + + CM_LoadMap_Actual( name, clientload, checksum, cmg ); + + gbUsingCachedMapDataRightNow = qfalse; // !!!!!!!!!!!!!!!!!! +} + + + +/* +================== +CM_ClearMap +================== +*/ +void CM_ClearMap( void ) +{ + int i; + +#if !defined(BSPC) + CM_ShutdownShaderProperties(); +// MAT_Shutdown(); +#endif + + if (TheRandomMissionManager) + { + delete TheRandomMissionManager; + TheRandomMissionManager = 0; + } + + if (cmg.landScape) + { + delete cmg.landScape; + cmg.landScape = NULL; + } + + Com_Memset( &cmg, 0, sizeof( cmg ) ); + CM_ClearLevelPatches(); + + for(i = 0; i < NumSubBSP; i++) + { + memset(&SubBSP[i], 0, sizeof(SubBSP[0])); + } + NumSubBSP = 0; + TotalSubModels = 0; +} + +/* +================== +CM_ClipHandleToModel +================== +*/ +cmodel_t *CM_ClipHandleToModel( clipHandle_t handle, clipMap_t **clipMap ) { + int i; + int count; + + if ( handle < 0 ) + { + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle ); + } + if ( handle < cmg.numSubModels ) + { + if (clipMap) + { + *clipMap = &cmg; + } + return &cmg.cmodels[handle]; + } + if ( handle == BOX_MODEL_HANDLE ) + { + if (clipMap) + { + *clipMap = &cmg; + } + return &box_model; + } + + count = cmg.numSubModels; + for(i = 0; i < NumSubBSP; i++) + { + if (handle < count + SubBSP[i].numSubModels) + { + if (clipMap) + { + *clipMap = &SubBSP[i]; + } + return &SubBSP[i].cmodels[handle - count]; + } + count += SubBSP[i].numSubModels; + } + + if ( handle < MAX_SUBMODELS ) + { + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i < %i < %i", + cmg.numSubModels, handle, MAX_SUBMODELS ); + } + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle + MAX_SUBMODELS ); + + return NULL; +} + +/* +================== +CM_InlineModel +================== +*/ +clipHandle_t CM_InlineModel( int index ) { + if ( index < 0 || index >= TotalSubModels ) + { + Com_Error (ERR_DROP, "CM_InlineModel: bad number: %d > %d", index, TotalSubModels); + } + return index; +} + +int CM_NumClusters( void ) { + return cmg.numClusters; +} + +int CM_NumInlineModels( void ) { + return cmg.numSubModels; +} + +char *CM_EntityString( void ) { + return cmg.entityString; +} + +char *CM_SubBSPEntityString( int index ) +{ + return SubBSP[index].entityString; +} + +int CM_LeafCluster( int leafnum ) { + if (leafnum < 0 || leafnum >= cmg.numLeafs) { + Com_Error (ERR_DROP, "CM_LeafCluster: bad number"); + } + return cmg.leafs[leafnum].cluster; +} + +int CM_LeafArea( int leafnum ) { + if ( leafnum < 0 || leafnum >= cmg.numLeafs ) { + Com_Error (ERR_DROP, "CM_LeafArea: bad number"); + } + return cmg.leafs[leafnum].area; +} + +//======================================================================= + + +/* +=================== +CM_InitBoxHull + +Set up the planes and nodes so that the six floats of a bounding box +can just be stored out and get a proper clipping hull structure. +=================== +*/ +void CM_InitBoxHull (void) +{ + int i; + int side; + cplane_t *p; + cbrushside_t *s; + + box_planes = &cmg.planes[cmg.numPlanes]; + + box_brush = &cmg.brushes[cmg.numBrushes]; + box_brush->numsides = 6; + box_brush->sides = cmg.brushsides + cmg.numBrushSides; + box_brush->contents = CONTENTS_BODY; + + box_model.firstNode = -1; + box_model.leaf.numLeafBrushes = 1; +// box_model.leaf.firstLeafBrush = cmg.numBrushes; + box_model.leaf.firstLeafBrush = cmg.numLeafBrushes; + cmg.leafbrushes[cmg.numLeafBrushes] = cmg.numBrushes; + + for (i=0 ; i<6 ; i++) + { + side = i&1; + + // brush sides + s = &cmg.brushsides[cmg.numBrushSides+i]; + s->plane = cmg.planes + (cmg.numPlanes+i*2+side); + s->shaderNum = cmg.numShaders; + + // planes + p = &box_planes[i*2]; + p->type = i>>1; + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = 1; + + p = &box_planes[i*2+1]; + p->type = 3 + (i>>1); + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = -1; + + SetPlaneSignbits( p ); + } +} + +/* +=================== +CM_TempBoxModel + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +Capsules are handled differently though. +=================== +*/ +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ) { + + VectorCopy( mins, box_model.mins ); + VectorCopy( maxs, box_model.maxs ); + + if ( capsule ) { + return CAPSULE_MODEL_HANDLE; + } + + box_planes[0].dist = maxs[0]; + box_planes[1].dist = -maxs[0]; + box_planes[2].dist = mins[0]; + box_planes[3].dist = -mins[0]; + box_planes[4].dist = maxs[1]; + box_planes[5].dist = -maxs[1]; + box_planes[6].dist = mins[1]; + box_planes[7].dist = -mins[1]; + box_planes[8].dist = maxs[2]; + box_planes[9].dist = -maxs[2]; + box_planes[10].dist = mins[2]; + box_planes[11].dist = -mins[2]; + + VectorCopy( mins, box_brush->bounds[0] ); + VectorCopy( maxs, box_brush->bounds[1] ); + + return BOX_MODEL_HANDLE; +} + +/* +=================== +CM_ModelBounds +=================== +*/ +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + VectorCopy( cmod->mins, mins ); + VectorCopy( cmod->maxs, maxs ); +} + +/* +=================== +CM_RegisterTerrain + +Allows physics to examine the terrain data. +=================== +*/ +#if !defined(BSPC) +CCMLandScape *CM_RegisterTerrain(const char *config, bool server) +{ + CCMLandScape *ls; + + if(cmg.landScape) + { + // Already spawned so just return + ls = cmg.landScape; + ls->IncreaseRefCount(); + return(ls); + } + // Doesn't exist so create and link in + ls = CM_InitTerrain(config, 0, server); + + // Increment for the next instance + if (cmg.landScape) + { + Com_Error(ERR_DROP, "You cannot have more than one terrain brush.\n"); + } + cmg.landScape = ls; + return(ls); +} + +/* +=================== +CM_ShutdownTerrain +=================== +*/ + +void CM_ShutdownTerrain( thandle_t terrainId) +{ + CCMLandScape *landscape; + + landscape = cmg.landScape; + + if (landscape) + { + landscape->DecreaseRefCount(); + if(landscape->GetRefCount() <= 0) + { + delete landscape; + cmg.landScape = NULL; + } + } +} +#endif + +int CM_LoadSubBSP(const char *name, qboolean clientload) +{ + int i; + int checksum; + int count; + + count = cmg.numSubModels; + for(i = 0; i < NumSubBSP; i++) + { + if (!stricmp(name, SubBSP[i].name)) + { + return count; + } + count += SubBSP[i].numSubModels; + } + + if (NumSubBSP == MAX_SUB_BSP) + { + Com_Error (ERR_DROP, "CM_LoadSubBSP: too many unique sub BSPs"); + } + + CM_LoadMap_Actual(name, clientload, &checksum, SubBSP[NumSubBSP] ); + NumSubBSP++; + + return count; +} + +int CM_FindSubBSP(int modelIndex) +{ + int i; + int count; + + count = cmg.numSubModels; + if (modelIndex < count) + { // belongs to the main bsp + return -1; + } + + for(i = 0; i < NumSubBSP; i++) + { + count += SubBSP[i].numSubModels; + if (modelIndex < count) + { + return i; + } + } + return -1; +} + +void CM_GetWorldBounds ( vec3_t mins, vec3_t maxs ) +{ + VectorCopy ( cmg.cmodels[0].mins, mins ); + VectorCopy ( cmg.cmodels[0].maxs, maxs ); +} + +int CM_ModelContents_Actual( clipHandle_t model, clipMap_t *cm ) +{ + cmodel_t *cmod; + int contents = 0; + int i; + + if (!cm) + { + cm = &cmg; + } + + cmod = CM_ClipHandleToModel( model, &cm ); + + //MCG ADDED - return the contents, too + if( cmod->leaf.numLeafBrushes ) // check for brush + { + int brushNum; + for ( i = cmod->leaf.firstLeafBrush; i < cmod->leaf.firstLeafBrush+cmod->leaf.numLeafBrushes; i++ ) + { + brushNum = cm->leafbrushes[i]; + contents |= cm->brushes[brushNum].contents; + } + } + if( cmod->leaf.numLeafSurfaces ) // if not brush, check for patch + { + int surfaceNum; + for ( i = cmod->leaf.firstLeafSurface; i < cmod->leaf.firstLeafSurface+cmod->leaf.numLeafSurfaces; i++ ) + { + surfaceNum = cm->leafsurfaces[i]; + if ( cm->surfaces[surfaceNum] != NULL ) + {//HERNH? How could we have a null surf within our cmod->leaf.numLeafSurfaces? + contents |= cm->surfaces[surfaceNum]->contents; + } + } + } + return contents; +} + +int CM_ModelContents( clipHandle_t model, int subBSPIndex ) +{ + if (subBSPIndex < 0) + { + return CM_ModelContents_Actual(model, NULL); + } + + return CM_ModelContents_Actual(model, &SubBSP[subBSPIndex]); +} diff --git a/codemp/qcommon/cm_load_xbox.cpp b/codemp/qcommon/cm_load_xbox.cpp new file mode 100644 index 0000000..4f6f9d9 --- /dev/null +++ b/codemp/qcommon/cm_load_xbox.cpp @@ -0,0 +1,1155 @@ +// cmodel.c -- model loading +#include "../qcommon/exe_headers.h" + +#include "cm_local.h" +#include "cm_landscape.h" //rwwRMG - include +#include "cm_patch.h" +//#include "../rmg/rm_headers.h" //rwwRMG - include +#include "../renderer/tr_local.h" + +#include "sparc.h" +#include "../zlib/zlib.h" + +static SPARC visData; + +void *SparcAllocator(unsigned int size) +{ + return Z_Malloc(size, TAG_BSP, false); +} + +void SparcDeallocator(void *ptr) +{ + Z_Free(ptr); +} + + +void CM_LoadShaderText(bool forceReload); + +#ifdef BSPC + +#include "../bspc/l_qfiles.h" + +void SetPlaneSignbits (cplane_t *out) { + int bits, j; + + // for fast box on planeside test + bits = 0; + for (j=0 ; j<3 ; j++) { + if (out->normal[j] < 0) { + bits |= 1<signbits = bits; +} +#endif //BSPC + +// to allow boxes to be treated as brush models, we allocate +// some extra indexes along with those needed by the map +#define BOX_BRUSHES 1 +#define BOX_SIDES 6 +#define BOX_LEAFS 2 +#define BOX_PLANES 12 + +#define LL(x) x=LittleLong(x) + +clipMap_t cmg; +int c_pointcontents; +int c_traces, c_brush_traces, c_patch_traces; + + +byte *cmod_base; + +#ifndef BSPC +cvar_t *cm_noAreas; +cvar_t *cm_noCurves; +cvar_t *cm_playerCurveClip; +#endif + +cmodel_t box_model; +cplane_t *box_planes; +cbrush_t *box_brush; + + + +void CM_InitBoxHull (void); +void CM_FloodAreaConnections (void); + +/* +clipMap_t SubBSP[MAX_SUB_BSP]; +int NumSubBSP; +*/ +int TotalSubModels; + +/* +=============================================================================== + + MAP LOADING + +=============================================================================== +*/ + +/* +================= +CMod_LoadShaders +================= +*/ +void CMod_LoadShaders( void *data, int len ) { + dshader_t *in; + int i, count; + CCMShader *out; + + in = (dshader_t *)(data); + if (len % sizeof(*in)) { + Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size"); + } + count = len / sizeof(*in); + + if (count < 1) { + Com_Error (ERR_DROP, "Map with no shaders"); + } + cmg.shaders = (CCMShader *)Hunk_Alloc( (1+count) * sizeof( *cmg.shaders ), h_high ); + cmg.numShaders = count; + + out = cmg.shaders; + for ( i = 0; i < count; i++, in++, out++ ) + { + Q_strncpyz(out->shader, in->shader, MAX_QPATH); + out->contentFlags = in->contentFlags; + out->surfaceFlags = in->surfaceFlags; + } +} + + +/* +================= +CMod_LoadSubmodels +================= +*/ +void CMod_LoadSubmodels( void *data, int len ) { + dmodel_t *in; + cmodel_t *out; + int i, j, count; + int *indexes; + + in = (dmodel_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "CMod_LoadSubmodels: funny lump size"); + count = len / sizeof(*in); + + if (count < 1) { + Com_Error (ERR_DROP, "Map with no models"); + } + + if ( count > MAX_SUBMODELS ) { + Com_Error( ERR_DROP, "MAX_SUBMODELS (%d) exceeded by %d", MAX_SUBMODELS, count-MAX_SUBMODELS ); + } + + cmg.cmodels = (struct cmodel_s *)Hunk_Alloc( count * sizeof( *cmg.cmodels ), h_high ); + cmg.numSubModels = count; + + for ( i=0 ; imins[j] = in->mins[j] - 1; + out->maxs[j] = in->maxs[j] + 1; + } + + if ( i == 0 ) { + out->firstNode = 0; + continue; // world model doesn't need other info + } + + out->firstNode = -1; + + // make a "leaf" just to hold the model's brushes and surfaces + out->leaf.numLeafBrushes = in->numBrushes; + indexes = (int *)Hunk_Alloc( out->leaf.numLeafBrushes * 4, h_high ); + out->leaf.firstLeafBrush = indexes - cmg.leafbrushes; + for ( j = 0 ; j < out->leaf.numLeafBrushes ; j++ ) { + indexes[j] = in->firstBrush + j; + } + + out->leaf.numLeafSurfaces = in->numSurfaces; + indexes = (int *)Hunk_Alloc( out->leaf.numLeafSurfaces * 4, h_high ); + out->leaf.firstLeafSurface = indexes - cmg.leafsurfaces; + for ( j = 0 ; j < out->leaf.numLeafSurfaces ; j++ ) { + indexes[j] = in->firstSurface + j; + } + } +} + +/* +================= +CMod_LoadNodes + +================= +*/ +void CMod_LoadNodes( void *data, int len ) { + dnode_t *in; + cNode_t *out; + int i, count; + + in = (dnode_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = len / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map has no nodes"); + cmg.nodes = (cNode_t *)Hunk_Alloc( count * sizeof( *cmg.nodes ), h_high ); + cmg.numNodes = count; + + out = cmg.nodes; + + for (i=0 ; iplaneNum = in->planeNum; + out->children[0] = in->children[0]; + out->children[1] = in->children[1]; + } +} + +/* +================= +CM_BoundBrush + +================= +*/ +void CM_BoundBrush( cbrush_t *b ) { + b->bounds[0][0] = -cmg.planes[b->sides[0].planeNum.GetValue()].dist; + b->bounds[1][0] = cmg.planes[b->sides[1].planeNum.GetValue()].dist; + + b->bounds[0][1] = -cmg.planes[b->sides[2].planeNum.GetValue()].dist; + b->bounds[1][1] = cmg.planes[b->sides[3].planeNum.GetValue()].dist; + + b->bounds[0][2] = -cmg.planes[b->sides[4].planeNum.GetValue()].dist; + b->bounds[1][2] = cmg.planes[b->sides[5].planeNum.GetValue()].dist; +} + + +/* +================= +CMod_LoadBrushes + +================= +*/ +void CMod_LoadBrushes( void *data, int len ) { + dbrush_t *in; + cbrush_t *out; + int i, count; + + in = (dbrush_t *)(data); + if (len % sizeof(*in)) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = len / sizeof(*in); + + cmg.brushes = (cbrush_t *)Hunk_Alloc( ( BOX_BRUSHES + count ) * sizeof( *cmg.brushes ), h_high ); + cmg.numBrushes = count; + + out = cmg.brushes; + + for ( i=0 ; isides = cmg.brushsides + in->firstSide; + out->numsides = in->numSides; + + out->shaderNum = in->shaderNum; + if ( out->shaderNum < 0 || out->shaderNum >= cmg.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum ); + } + out->contents = cmg.shaders[out->shaderNum].contentFlags; + + CM_BoundBrush( out ); + } + +} + +/* +================= +CMod_LoadLeafs +================= +*/ +void CMod_LoadLeafs (void *data, int len) +{ + int i; + cLeaf_t *out; + dleaf_t *in; + int count; + + in = (dleaf_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = len / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no leafs"); + + cmg.leafs = (cLeaf_t *)Hunk_Alloc( ( BOX_LEAFS + count ) * sizeof( *cmg.leafs ), h_high ); + cmg.numLeafs = count; + out = cmg.leafs; + + for ( i=0 ; icluster = in->cluster; + out->area = in->area; + out->firstLeafBrush = in->firstLeafBrush; + out->numLeafBrushes = in->numLeafBrushes; + out->firstLeafSurface = in->firstLeafSurface; + out->numLeafSurfaces = in->numLeafSurfaces; + + if (out->cluster >= cmg.numClusters) + cmg.numClusters = out->cluster + 1; + if (out->area >= cmg.numAreas) + cmg.numAreas = out->area + 1; + } + + cmg.areas = (cArea_t *)Hunk_Alloc( cmg.numAreas * sizeof( *cmg.areas ), h_high ); + cmg.areaPortals = (int *)Hunk_Alloc( cmg.numAreas * cmg.numAreas * sizeof( *cmg.areaPortals ), h_high ); +} + +/* +================= +CMod_LoadPlanes +================= +*/ +void CMod_LoadPlanes (void *data, int len) +{ + int i, j; + cplane_t *out; + dplane_t *in; + int count; + int bits; + + in = (dplane_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = len / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no planes"); + cmg.planes = (struct cplane_s *)Hunk_Alloc( ( BOX_PLANES + count ) * sizeof( *cmg.planes ), h_high ); + cmg.numPlanes = count; + + out = cmg.planes; + + for ( i=0 ; inormal[j] = in->normal[j]; + if (out->normal[j] < 0) + bits |= 1<dist = in->dist; + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } + + RE_SetPlaneData(cmg.planes, cmg.numPlanes); +} + +/* +================= +CMod_LoadLeafBrushes +================= +*/ +void CMod_LoadLeafBrushes (void *data, int len) +{ + int *out; + int *in; + int count; + + in = (int *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = len / sizeof(*in); + + cmg.leafbrushes = (int *)Hunk_Alloc( (count + BOX_BRUSHES) * sizeof( *cmg.leafbrushes ), h_high ); + cmg.numLeafBrushes = count; + + out = cmg.leafbrushes; + + memcpy(out, in, len); +} + + +void CMod_LoadLeafSurfaces( void *data, int len ) +{ + int i; + int *out; + int *in; + int count; + + in = (int *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = len / sizeof(*in); + + cmg.leafsurfaces = (int *)Hunk_Alloc( count * sizeof( *cmg.leafsurfaces ), h_high ); + cmg.numLeafSurfaces = count; + + out = cmg.leafsurfaces; + + memcpy(out, in, len); +} + +/* +================= +CMod_LoadBrushSides +================= +*/ +void CMod_LoadBrushSides (void *data, int len) +{ + int i; + cbrushside_t *out; + dbrushside_t *in; + int count; + + in = (dbrushside_t *)(data); + if ( len % sizeof(*in) ) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = len / sizeof(*in); + + cmg.brushsides = (cbrushside_t *)Hunk_Alloc( ( BOX_SIDES + count ) * sizeof( *cmg.brushsides ), h_high ); + cmg.numBrushSides = count; + + out = cmg.brushsides; + + for ( i=0 ; iplaneNum = in->planeNum; + assert(in->planeNum == out->planeNum.GetValue()); + + out->shaderNum = in->shaderNum; + if ( out->shaderNum < 0 || out->shaderNum >= cmg.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushSides: bad shaderNum: %i", out->shaderNum ); + } + } +} + + +/* +================= +CMod_LoadEntityString +================= +*/ +void CMod_LoadEntityString( void *data, int len ) { + cmg.entityString = (char *)Hunk_Alloc( len, h_high ); + cmg.numEntityChars = len; + memcpy (cmg.entityString, data, len); +} + +/* +================= +CMod_LoadVisibility +================= +*/ +void RE_SetWorldVisData( SPARC *vis ); + +#define VIS_HEADER 8 +void CMod_LoadVisibility( void *data, int len ) { + char *buf; + + if ( !len ) { + cmg.visibility = NULL; + return; + } + buf = (char*)data; + + visData.SetAllocator(SparcAllocator, SparcDeallocator); + + cmg.vised = qtrue; + cmg.numClusters = ((int *)buf)[0]; + cmg.clusterBytes = ((int *)buf)[1]; + visData.Load(buf + VIS_HEADER, len - VIS_HEADER); + cmg.visibility = &visData; + RE_SetWorldVisData(&visData); +} + +//================================================================== + + +/* +================= +CMod_LoadPatches +================= +*/ +#define MAX_PATCH_VERTS 1024 + +void CMod_LoadPatches( void *verts, int vertlen, void *surfaces, int surfacelen, int numsurfs ) { + mapVert_t *dv, *dv_p; + dpatch_t *in; + int count; + int i, j; + int c; + cPatch_t *patch; + vec3_t points[MAX_PATCH_VERTS]; + int width, height; + int shaderNum; + + count = surfacelen / sizeof(*in); + + cmg.numSurfaces = numsurfs; + cmg.surfaces = (cPatch_t **)Hunk_Alloc( cmg.numSurfaces * sizeof( cmg.surfaces[0] ), h_high ); + + dv = (mapVert_t *)(verts); + if (vertlen % sizeof(*dv)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + + unsigned char* patchScratch = (unsigned char*)Z_Malloc( sizeof( *patch ) * count, TAG_BSP, qtrue); + + extern void CM_GridAlloc(); + extern void CM_PatchCollideFromGridTempAlloc(); + extern void CM_PreparePatchCollide(int num); + extern void CM_TempPatchPlanesAlloc(); + CM_GridAlloc(); + CM_PatchCollideFromGridTempAlloc(); + CM_PreparePatchCollide(count); + CM_TempPatchPlanesAlloc(); + + facetLoad_t *facetbuf = (facetLoad_t*)Z_Malloc( + MAX_PATCH_PLANES*sizeof(facetLoad_t), TAG_TEMP_WORKSPACE, qfalse); + + int *gridbuf = (int*)Z_Malloc( + CM_MAX_GRID_SIZE*CM_MAX_GRID_SIZE*2*sizeof(int), TAG_TEMP_WORKSPACE, qfalse); + + for ( i = 0 ; i < count ; i++) { + in = (dpatch_t *)surfaces + i; + + cmg.surfaces[ in->code ] = patch = (cPatch_t *) patchScratch; + patchScratch += sizeof( *patch ); + + // load the full drawverts onto the stack + width = in->patchWidth; + height = in->patchHeight; + c = width * height; + if ( c > MAX_PATCH_VERTS ) { + Com_Error( ERR_DROP, "ParseMesh: MAX_PATCH_VERTS" ); + } + + dv_p = dv + (in->verts >> 12); + for ( j = 0 ; j < c ; j++, dv_p++ ) { + points[j][0] = dv_p->xyz[0]; + points[j][1] = dv_p->xyz[1]; + points[j][2] = dv_p->xyz[2]; + } + + shaderNum = in->shaderNum; + patch->contents = cmg.shaders[shaderNum].contentFlags; + patch->surfaceFlags = cmg.shaders[shaderNum].surfaceFlags; + + // create the internal facet structure + patch->pc = CM_GeneratePatchCollide( width, height, points, facetbuf, gridbuf ); + } + + extern void CM_GridDealloc(); + extern void CM_PatchCollideFromGridTempDealloc(); + extern void CM_TempPatchPlanesDealloc(); + CM_PatchCollideFromGridTempDealloc(); + CM_GridDealloc(); + CM_TempPatchPlanesDealloc(); + + Z_Free(gridbuf); + Z_Free(facetbuf); +} + + +/* +================== +CM_LoadMap + +Loads in the map and all submodels +================== +*/ +void *gpvCachedMapDiskImage = NULL; +char gsCachedMapDiskImage[MAX_QPATH]; +qboolean gbUsingCachedMapDataRightNow = qfalse; // if true, signifies that you can't delete this at the moment!! (used during z_malloc()-fail recovery attempt) + +// called in response to a "devmapbsp blah" or "devmapall blah" command, do NOT use inside CM_Load unless you pass in qtrue +// +// new bool return used to see if anything was freed, used during z_malloc failure re-try +// +qboolean CM_DeleteCachedMap(qboolean bGuaranteedOkToDelete) +{ + qboolean bActuallyFreedSomething = qfalse; + + if (bGuaranteedOkToDelete || !gbUsingCachedMapDataRightNow) + { + // dump cached disk image... + // + if (gpvCachedMapDiskImage) + { + Z_Free( gpvCachedMapDiskImage ); + gpvCachedMapDiskImage = NULL; + + bActuallyFreedSomething = qtrue; + } + gsCachedMapDiskImage[0] = '\0'; + + // force map loader to ignore cached internal BSP structures for next level CM_LoadMap() call... + // + cmg.name[0] = '\0'; + } + + return bActuallyFreedSomething; +} + +void CM_Free(void) +{ + CM_ClearLevelPatches(); + visData.Release(); + Z_TagFree(TAG_BSP); +} + + +static void CM_LoadMap_Actual( const char *name, qboolean clientload, int *checksum ) { + const int *buf = NULL; + const int *surfBuf = NULL; + static unsigned last_checksum; + char lmName[MAX_QPATH]; + char stripName[MAX_QPATH]; + Lump outputLump; + + if ( !name || !name[0] ) { + Com_Error( ERR_DROP, "CM_LoadMap: NULL name" ); + } + +#ifndef BSPC + cm_noAreas = Cvar_Get ("cm_noAreas", "0", CVAR_CHEAT); + cm_noCurves = Cvar_Get ("cm_noCurves", "0", CVAR_CHEAT); + cm_playerCurveClip = Cvar_Get ("cm_playerCurveClip", "1", CVAR_ARCHIVE|CVAR_CHEAT ); +#endif + Com_DPrintf( "CM_LoadMap( %s, %i )\n", name, clientload ); + + if ( !strcmp( cmg.name, name ) && clientload ) { + *checksum = last_checksum; + return; + } + + CM_ClearMap(); + CM_ClearLevelPatches(); + + // free old stuff + memset( &cmg, 0, sizeof( cmg ) ); + + if ( !name[0] ) { + cmg.numLeafs = 1; + cmg.numClusters = 1; + cmg.numAreas = 1; + cmg.cmodels = (struct cmodel_s *) Z_Malloc( sizeof( *cmg.cmodels ), TAG_BSP, qtrue ); + *checksum = 0; + return; + } + + last_checksum = crc32(0, (const Bytef *)name, strlen(name)); + COM_StripExtension(name, stripName); + + // load into heap + outputLump.load(stripName, "shaders"); + CMod_LoadShaders( outputLump.data, outputLump.len ); + + outputLump.load(stripName, "leafs"); + CMod_LoadLeafs (outputLump.data, outputLump.len); + + outputLump.load(stripName, "leafbrushes"); + CMod_LoadLeafBrushes (outputLump.data, outputLump.len); + + outputLump.load(stripName, "leafsurfaces"); + CMod_LoadLeafSurfaces (outputLump.data, outputLump.len); + + outputLump.load(stripName, "planes"); + CMod_LoadPlanes (outputLump.data, outputLump.len); + + outputLump.load(stripName, "brushsides"); + CMod_LoadBrushSides (outputLump.data, outputLump.len); + + outputLump.load(stripName, "brushes"); + CMod_LoadBrushes (outputLump.data, outputLump.len); + + outputLump.load(stripName, "models"); + CMod_LoadSubmodels (outputLump.data, outputLump.len); + + outputLump.load(stripName, "nodes"); + CMod_LoadNodes (outputLump.data, outputLump.len); + + outputLump.load(stripName, "entities"); + CMod_LoadEntityString (outputLump.data, outputLump.len); + + outputLump.load(stripName, "visibility"); + CMod_LoadVisibility( outputLump.data, outputLump.len); + + Lump misc; + misc.load(stripName, "misc"); + + int num_surfs = *(int*)misc.data; + misc.clear(); + + Lump verts; + verts.load(stripName, "verts"); + + Lump patches; + patches.load(stripName, "patches"); + CMod_LoadPatches(verts.data, verts.len, patches.data, patches.len, num_surfs ); + patches.clear(); + + TotalSubModels += cmg.numSubModels; + +#if !defined(BSPC) + CM_LoadShaderText(qfalse); +#endif + CM_InitBoxHull (); +#if !defined(BSPC) + CM_SetupShaderProperties(); +#endif + + *checksum = last_checksum; + + // do this whether or not the map was cached from last load... + // + CM_FloodAreaConnections (); + + // allow this to be cached if it is loaded by the server + if ( !clientload ) { + Q_strncpyz( cmg.name, name, sizeof( cmg.name ) ); + } +} + +// need a wrapper function around this because of multiple returns, need to ensure bool is correct... +// +void CM_LoadMap( const char *name, qboolean clientload, int *checksum ) +{ + CM_LoadMap_Actual( name, clientload, checksum ); +} + + +/* +================== +CM_ClearMap +================== +*/ +void CM_ClearMap( void ) +{ + int i; + +#if !defined(BSPC) + CM_ShutdownShaderProperties(); +// MAT_Shutdown(); +#endif + +/* + if (TheRandomMissionManager) + { + delete TheRandomMissionManager; + TheRandomMissionManager = 0; + } +*/ + +/* + if (cmg.landScape) + { + delete cmg.landScape; + cmg.landScape = 0; + } +*/ + + Com_Memset( &cmg, 0, sizeof( cmg ) ); + CM_ClearLevelPatches(); + +/* + for(i = 0; i < NumSubBSP; i++) + { + memset(&SubBSP[i], 0, sizeof(SubBSP[0])); + } + NumSubBSP = 0; +*/ + TotalSubModels = 0; +} + +/* +================== +CM_ClipHandleToModel +================== +*/ +cmodel_t *CM_ClipHandleToModel( clipHandle_t handle, clipMap_t **clipMap ) +{ + int i; + int count; + + if ( handle < 0 ) + { + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle ); + } + if ( handle < cmg.numSubModels ) + { + if (clipMap) + { + *clipMap = &cmg; + } + return &cmg.cmodels[handle]; + } + if ( handle == BOX_MODEL_HANDLE ) + { + if (clipMap) + { + *clipMap = &cmg; + } + return &box_model; + } + +/* + count = cmg.numSubModels; + for(i = 0; i < NumSubBSP; i++) + { + if (handle < count + SubBSP[i].numSubModels) + { + if (clipMap) + { + *clipMap = &SubBSP[i]; + } + return &SubBSP[i].cmodels[handle - count]; + } + count += SubBSP[i].numSubModels; + } +*/ + + if ( handle < MAX_SUBMODELS ) + { + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i < %i < %i", + cmg.numSubModels, handle, MAX_SUBMODELS ); + } + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle + MAX_SUBMODELS ); + + return NULL; +} +/* +================== +CM_InlineModel +================== +*/ +clipHandle_t CM_InlineModel( int index ) { + if ( index < 0 || index >= TotalSubModels ) { + Com_Error (ERR_DROP, "CM_InlineModel: bad number (may need to re-BSP map?)"); + } + return index; +} + +int CM_NumClusters( void ) { + return cmg.numClusters; +} + +int CM_NumInlineModels( void ) { + return cmg.numSubModels; +} + +char *CM_EntityString( void ) { + return cmg.entityString; +} + +/* +char *CM_SubBSPEntityString( int index ) +{ + return SubBSP[index].entityString; +} +*/ + +int CM_LeafCluster( int leafnum ) { + if (leafnum < 0 || leafnum >= cmg.numLeafs) { + Com_Error (ERR_DROP, "CM_LeafCluster: bad number"); + } + return cmg.leafs[leafnum].cluster; +} + +int CM_LeafArea( int leafnum ) { + if ( leafnum < 0 || leafnum >= cmg.numLeafs ) { + Com_Error (ERR_DROP, "CM_LeafArea: bad number"); + } + return cmg.leafs[leafnum].area; +} + +//======================================================================= + + +/* +=================== +CM_InitBoxHull + +Set up the planes and nodes so that the six floats of a bounding box +can just be stored out and get a proper clipping hull structure. +=================== +*/ +void CM_InitBoxHull (void) +{ + int i; + int side; + cplane_t *p; + cbrushside_t *s; + + box_planes = &cmg.planes[cmg.numPlanes]; + + box_brush = &cmg.brushes[cmg.numBrushes]; + box_brush->numsides = 6; + box_brush->sides = cmg.brushsides + cmg.numBrushSides; + box_brush->contents = CONTENTS_BODY; + + box_model.firstNode = -1; + box_model.leaf.numLeafBrushes = 1; +// box_model.leaf.firstLeafBrush = cmg.numBrushes; + box_model.leaf.firstLeafBrush = cmg.numLeafBrushes; + cmg.leafbrushes[cmg.numLeafBrushes] = cmg.numBrushes; + + for (i=0 ; i<6 ; i++) + { + side = i&1; + + // brush sides + s = &cmg.brushsides[cmg.numBrushSides+i]; + s->planeNum = cmg.numPlanes+i*2+side; + s->shaderNum = cmg.numShaders; + + // planes + p = &box_planes[i*2]; + p->type = i>>1; + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = 1; + + p = &box_planes[i*2+1]; + p->type = 3 + (i>>1); + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = -1; + + SetPlaneSignbits( p ); + } +} + + + +/* +=================== +CM_HeadnodeForBox + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +=================== +*/ +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ) { + VectorCopy( mins, box_model.mins ); + VectorCopy( maxs, box_model.maxs ); + + if ( capsule ) { + return CAPSULE_MODEL_HANDLE; + } + + box_planes[0].dist = maxs[0]; + box_planes[1].dist = -maxs[0]; + box_planes[2].dist = mins[0]; + box_planes[3].dist = -mins[0]; + box_planes[4].dist = maxs[1]; + box_planes[5].dist = -maxs[1]; + box_planes[6].dist = mins[1]; + box_planes[7].dist = -mins[1]; + box_planes[8].dist = maxs[2]; + box_planes[9].dist = -maxs[2]; + box_planes[10].dist = mins[2]; + box_planes[11].dist = -mins[2]; + + VectorCopy( mins, box_brush->bounds[0] ); + VectorCopy( maxs, box_brush->bounds[1] ); + + return BOX_MODEL_HANDLE; +} + + + +/* +=================== +CM_ModelBounds +=================== +*/ +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + VectorCopy( cmod->mins, mins ); + VectorCopy( cmod->maxs, maxs ); +} + +/* +=================== +CM_RegisterTerrain + +Allows physics to examine the terrain data. +=================== +*/ +#if !defined(BSPC) +/* +CCMLandScape *CM_RegisterTerrain(const char *config, bool server) +{ + CCMLandScape *ls; + + if(cmg.landScape) + { + // Already spawned so just return + ls = cmg.landScape; + ls->IncreaseRefCount(); + return(ls); + } + // Doesn't exist so create and link in + ls = CM_InitTerrain(config, 0, server); + + // Increment for the next instance + if (cmg.landScape) + { + Com_Error(ERR_DROP, "You cannot have more than one terrain brush.\n"); + } + cmg.landScape = ls; + return(ls); +} +*/ + +/* +=================== +CM_ShutdownTerrain +=================== +*/ + +/* +void CM_ShutdownTerrain( thandle_t terrainId) +{ + CCMLandScape *landscape; + + landscape = cmg.landScape; + if (landscape) + { + landscape->DecreaseRefCount(); + if(landscape->GetRefCount() <= 0) + { + delete landscape; + cmg.landScape = NULL; + } + } +} +*/ +#endif + +/* +int CM_LoadSubBSP(const char *name, qboolean clientload) +{ + int i; + int checksum; + int count; + + count = cmg.numSubModels; + for(i = 0; i < NumSubBSP; i++) + { + if (!stricmp(name, SubBSP[i].name)) + { + return count; + } + count += SubBSP[i].numSubModels; + } + + if (NumSubBSP == MAX_SUB_BSP) + { + Com_Error (ERR_DROP, "CM_LoadSubBSP: too many unique sub BSPs"); + } + +#ifdef _XBOX + assert(0); // MATT! - testing now - fix this later! +#else + CM_LoadMap_Actual( name, clientload, &checksum, SubBSP[NumSubBSP] ); +#endif + NumSubBSP++; + + return count; +} +*/ + +/* +int CM_FindSubBSP(int modelIndex) +{ + int i; + int count; + + count = cmg.numSubModels; + if (modelIndex < count) + { // belongs to the main bsp + return -1; + } + + for(i = 0; i < NumSubBSP; i++) + { + count += SubBSP[i].numSubModels; + if (modelIndex < count) + { + return i; + } + } + return -1; +} +*/ + +void CM_GetWorldBounds ( vec3_t mins, vec3_t maxs ) +{ + VectorCopy ( cmg.cmodels[0].mins, mins ); + VectorCopy ( cmg.cmodels[0].maxs, maxs ); +} + +int CM_ModelContents_Actual( clipHandle_t model, clipMap_t *cm ) +{ + cmodel_t *cmod; + int contents = 0; + int i; + + if (!cm) + { + cm = &cmg; + } + + cmod = CM_ClipHandleToModel( model, &cm ); + + //MCG ADDED - return the contents, too + if( cmod->leaf.numLeafBrushes ) // check for brush + { + int brushNum; + for ( i = cmod->leaf.firstLeafBrush; i < cmod->leaf.firstLeafBrush+cmod->leaf.numLeafBrushes; i++ ) + { + brushNum = cm->leafbrushes[i]; + contents |= cm->brushes[brushNum].contents; + } + } + if( cmod->leaf.numLeafSurfaces ) // if not brush, check for patch + { + int surfaceNum; + for ( i = cmod->leaf.firstLeafSurface; i < cmod->leaf.firstLeafSurface+cmod->leaf.numLeafSurfaces; i++ ) + { + surfaceNum = cm->leafsurfaces[i]; + if ( cm->surfaces[surfaceNum] != NULL ) + {//HERNH? How could we have a null surf within our cmod->leaf.numLeafSurfaces? + contents |= cm->surfaces[surfaceNum]->contents; + } + } + } + return contents; +} + +int CM_ModelContents( clipHandle_t model, int subBSPIndex ) +{ +/* + if (subBSPIndex < 0) + { +*/ + return CM_ModelContents_Actual(model, NULL); +/* + } + + return CM_ModelContents_Actual(model, &SubBSP[subBSPIndex]); +*/ +} + diff --git a/codemp/qcommon/cm_local.h b/codemp/qcommon/cm_local.h new file mode 100644 index 0000000..90fdced --- /dev/null +++ b/codemp/qcommon/cm_local.h @@ -0,0 +1,310 @@ +#pragma once +#if !defined(CM_LOCAL_H_INC) +#define CM_LOCAL_H_INC //rwwRMG - include guard + +#include "cm_polylib.h" +#include "cm_landscape.h" //rwwRMG - include + +#ifdef _XBOX +#include "sparc.h" +#endif + +#define MAX_SUBMODELS 512 +#define BOX_MODEL_HANDLE (MAX_SUBMODELS-1) +#define CAPSULE_MODEL_HANDLE (MAX_SUBMODELS-2) + + +#ifdef _XBOX +#pragma pack(push, 1) +typedef struct { + int planeNum; + short children[2]; // negative numbers are leafs +} cNode_t; +#pragma pack(pop) + +#else // _XBOX + +typedef struct { + cplane_t *plane; + int children[2]; // negative numbers are leafs +} cNode_t; + +#endif // _XBOX + +typedef struct { + int cluster; + int area; + + int firstLeafBrush; + int numLeafBrushes; + + int firstLeafSurface; + int numLeafSurfaces; +} cLeaf_t; + +typedef struct cmodel_s { + vec3_t mins, maxs; + cLeaf_t leaf; // submodels don't reference the main tree + int firstNode; // only for cmodel[0] (for the main and bsp instances) +} cmodel_t; + +#ifdef _XBOX +#pragma pack (push, 1) +typedef struct cbrushside_s { + NotSoShort planeNum; + unsigned char shaderNum; +} cbrushside_t; +#pragma pack(pop) + +#else // _XBOX + +typedef struct cbrushside_s { + cplane_t *plane; + int shaderNum; +} cbrushside_t; + +#endif // _XBOX + +typedef struct cbrush_s { + int shaderNum; // the shader that determined the contents + int contents; + vec3_t bounds[2]; + cbrushside_t *sides; + unsigned short numsides; + unsigned short checkcount; // to avoid repeated testings +} cbrush_t; + +class CCMShader +{ +public: + char shader[MAX_QPATH]; + class CCMShader *mNext; + int surfaceFlags; + int contentFlags; + + const char *GetName(void) const { return(shader); } + class CCMShader *GetNext(void) const { return(mNext); } + void SetNext(class CCMShader *next) { mNext = next; } + void Destroy(void) { } +}; + +typedef struct { + int checkcount; // to avoid repeated testings + int surfaceFlags; + int contents; + struct patchCollide_s *pc; +} cPatch_t; + + +typedef struct { + int floodnum; + int floodvalid; +} cArea_t; + +#ifdef _XBOX +template +class SPARC; +typedef struct { + char name[MAX_QPATH]; + + int numShaders; + CCMShader *shaders; + + int numBrushSides; + cbrushside_t *brushsides; + + int numPlanes; + cplane_t *planes; + + int numNodes; + cNode_t *nodes; + + int numLeafs; + cLeaf_t *leafs; + + int numLeafBrushes; + int *leafbrushes; + + int numLeafSurfaces; + int *leafsurfaces; + + int numSubModels; + cmodel_t *cmodels; + + int numBrushes; + cbrush_t *brushes; + + int numClusters; + int clusterBytes; + SPARC *visibility; + qboolean vised; // if false, visibility is just a single cluster of ffs + + int numEntityChars; + char *entityString; + + int numAreas; + cArea_t *areas; + int *areaPortals; // [ numAreas*numAreas ] reference counts + + int numSurfaces; + cPatch_t **surfaces; // non-patches will be NULL + + int floodvalid; + int checkcount; // incremented on each trace + +// CCMLandScape *landScape; + qboolean haswater; +} clipMap_t; + +#else // _XBOX + +typedef struct { + char name[MAX_QPATH]; + + int numShaders; + CCMShader *shaders; + + int numBrushSides; + cbrushside_t *brushsides; + + int numPlanes; + cplane_t *planes; + + int numNodes; + cNode_t *nodes; + + int numLeafs; + cLeaf_t *leafs; + + int numLeafBrushes; + int *leafbrushes; + + int numLeafSurfaces; + int *leafsurfaces; + + int numSubModels; + cmodel_t *cmodels; + + int numBrushes; + cbrush_t *brushes; + + int numClusters; + int clusterBytes; + byte *visibility; + qboolean vised; // if false, visibility is just a single cluster of ffs + + int numEntityChars; + char *entityString; + + int numAreas; + cArea_t *areas; + int *areaPortals; // [ numAreas*numAreas ] reference counts + + int numSurfaces; + cPatch_t **surfaces; // non-patches will be NULL + + int floodvalid; + int checkcount; // incremented on each trace + + //rwwRMG - added: +// CCMLandScape *landScape; +} clipMap_t; + +#endif // _XBOX + + +// keep 1/8 unit away to keep the position valid before network snapping +// and to avoid various numeric issues +#define SURFACE_CLIP_EPSILON (0.125) + +extern clipMap_t cmg; //rwwRMG - changed from cm +extern int c_pointcontents; +extern int c_traces, c_brush_traces, c_patch_traces; +extern cvar_t *cm_noAreas; +extern cvar_t *cm_noCurves; +extern cvar_t *cm_playerCurveClip; + +// cm_test.c + +// Used for oriented capsule collision detection +typedef struct +{ + qboolean use; + float radius; + float halfheight; + vec3_t offset; +} sphere_t; + +typedef struct traceWork_s { //rwwRMG - modified + vec3_t start; + vec3_t end; + vec3_t size[2]; // size of the box being swept through the model + vec3_t offsets[8]; // [signbits][x] = either size[0][x] or size[1][x] + float maxOffset; // longest corner length from origin + vec3_t extents; // greatest of abs(size[0]) and abs(size[1]) + vec3_t modelOrigin;// origin of the model tracing through + int contents; // ored contents of the model tracing through + qboolean isPoint; // optimized case +// trace_t trace; // returned from trace call + sphere_t sphere; // sphere for oriendted capsule collision + + //rwwRMG - added: + vec3pair_t bounds; // enclosing box of start and end surrounding by size + vec3pair_t localBounds; // enclosing box of start and end surrounding by size for a segment + + float baseEnterFrac; // global enter fraction (before processing subsections of the brush) + float baseLeaveFrac; // global leave fraction (before processing subsections of the brush) + float enterFrac; // fraction where the ray enters the brush + float leaveFrac; // fraction where the ray leaves the brush + cbrushside_t *leadside; + cplane_t *clipplane; + bool startout; + bool getout; + +} traceWork_t; + +typedef struct leafList_s { + int count; + int maxcount; + qboolean overflowed; + int *list; + vec3_t bounds[2]; + int lastLeaf; // for overflows where each leaf can't be stored individually + void (*storeLeafs)( struct leafList_s *ll, int nodenum ); +} leafList_t; + + +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **boxList, int listsize ); +//rwwRMG - changed to boxList to not conflict with list type + +bool CM_CullWorldBox (const cplane_t *frustum, const vec3pair_t bounds); //rwwRMG - added + +void CM_StoreLeafs( leafList_t *ll, int nodenum ); +void CM_StoreBrushes( leafList_t *ll, int nodenum ); + +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ); + +cmodel_t *CM_ClipHandleToModel( clipHandle_t handle, clipMap_t **clipMap = 0 ); + +// cm_patch.c + +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, trace_t &trace, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_ClearLevelPatches( void ); + +//rwwRMG - added +//CCMLandScape *CM_RegisterTerrain(const char *config, bool server); +//void CM_ShutdownTerrain( thandle_t terrainId ); + +// cm_shader.cpp +void CM_SetupShaderProperties( void ); +void CM_ShutdownShaderProperties(void); +CCMShader *CM_GetShaderInfo( const char *name ); +CCMShader *CM_GetShaderInfo( int shaderNum ); +void CM_GetModelFormalName ( const char* model, const char* skin, char* name, int size ); + +// cm_load.cpp +void CM_GetWorldBounds ( vec3_t mins, vec3_t maxs ); + +#endif diff --git a/codemp/qcommon/cm_patch.cpp b/codemp/qcommon/cm_patch.cpp new file mode 100644 index 0000000..f3fc726 --- /dev/null +++ b/codemp/qcommon/cm_patch.cpp @@ -0,0 +1,1809 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "cm_local.h" +#include "cm_patch.h" + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +Issues for collision against curved surfaces: + +Surface edges need to be handled differently than surface planes + +Plane expansion causes raw surfaces to expand past expanded bounding box + +Position test of a volume against a surface is tricky. + +Position test of a point against a surface is not well defined, because the surface has no volume. + + +Tracing leading edge points instead of volumes? +Position test by tracing corner to corner? (8*7 traces -- ouch) + +coplanar edges +triangulated patches +degenerate patches + + endcaps + degenerate + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + +/* +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4+6+16]; + int borderInward[4+6+16]; + qboolean borderNoAdjust[4+6+16]; +} facet_t; + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + + +#define MAX_GRID_SIZE 129 + +typedef struct { + int width; + int height; + qboolean wrapWidth; + qboolean wrapHeight; + vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height] +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 +*/ + +int c_totalPatchBlocks; +int c_totalPatchSurfaces; +int c_totalPatchEdges; + +static const patchCollide_t *debugPatchCollide; +static const facet_t *debugFacet; +static qboolean debugBlock; +static vec3_t debugBlockPoints[4]; + +#if defined(BSPC) +extern void *Hunk_Alloc( int size ); + +static void *Hunk_Alloc( int size, ha_pref preference ) +{ + return Hunk_Alloc( size ); +} +#endif + +/* +================= +CM_ClearLevelPatches +================= +*/ +void CM_ClearLevelPatches( void ) { + debugPatchCollide = NULL; + debugFacet = NULL; +} + +/* +================= +CM_SignbitsForNormal +================= +*/ +static inline int CM_SignbitsForNormal( vec3_t normal ) { + int bits, j; + + bits = 0; + for (j=0 ; j<3 ; j++) { + if ( normal[j] < 0 ) { + bits |= 1<= SUBDIVIDE_DISTANCE * SUBDIVIDE_DISTANCE); +} + +/* +=============== +CM_Subdivide + +a, b, and c are control points. +the subdivided sequence will be: a, out1, out2, out3, c +=============== +*/ +static void CM_Subdivide( vec3_t a, vec3_t b, vec3_t c, vec3_t out1, vec3_t out2, vec3_t out3 ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + out1[i] = 0.5 * (a[i] + b[i]); + out3[i] = 0.5 * (b[i] + c[i]); + out2[i] = 0.5 * (out1[i] + out3[i]); + } +} + +/* +================= +CM_TransposeGrid + +Swaps the rows and columns in place +================= +*/ +static void CM_TransposeGrid( cGrid_t *grid ) { + int i, j, l; + vec3_t temp; + qboolean tempWrap; + + if ( grid->width > grid->height ) { + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = i + 1 ; j < grid->width ; j++ ) { + if ( j < grid->height ) { + // swap the value + VectorCopy( grid->points[i][j], temp ); + VectorCopy( grid->points[j][i], grid->points[i][j] ); + VectorCopy( temp, grid->points[j][i] ); + } else { + // just copy + VectorCopy( grid->points[j][i], grid->points[i][j] ); + } + } + } + } else { + for ( i = 0 ; i < grid->width ; i++ ) { + for ( j = i + 1 ; j < grid->height ; j++ ) { + if ( j < grid->width ) { + // swap the value + VectorCopy( grid->points[j][i], temp ); + VectorCopy( grid->points[i][j], grid->points[j][i] ); + VectorCopy( temp, grid->points[i][j] ); + } else { + // just copy + VectorCopy( grid->points[i][j], grid->points[j][i] ); + } + } + } + } + + l = grid->width; + grid->width = grid->height; + grid->height = l; + + tempWrap = grid->wrapWidth; + grid->wrapWidth = grid->wrapHeight; + grid->wrapHeight = tempWrap; +} + +/* +=================== +CM_SetGridWrapWidth + +If the left and right columns are exactly equal, set grid->wrapWidth qtrue +=================== +*/ +static void CM_SetGridWrapWidth( cGrid_t *grid ) { + int i, j; + float d; + + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + d = grid->points[0][i][j] - grid->points[grid->width-1][i][j]; + if ( d < -WRAP_POINT_EPSILON || d > WRAP_POINT_EPSILON ) { + break; + } + } + if ( j != 3 ) { + break; + } + } + if ( i == grid->height ) { + grid->wrapWidth = qtrue; + } else { + grid->wrapWidth = qfalse; + } +} + + +/* +================= +CM_SubdivideGridColumns + +Adds columns as necessary to the grid until +all the aproximating points are within SUBDIVIDE_DISTANCE +from the true curve +================= +*/ +static void CM_SubdivideGridColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 2 ; ) { + // grid->points[i][x] is an interpolating control point + // grid->points[i+1][x] is an aproximating control point + // grid->points[i+2][x] is an interpolating control point + + // + // first see if we can collapse the aproximating collumn away + // + for ( j = 0 ; j < grid->height ; j++ ) { + if ( CM_NeedsSubdivision( grid->points[i][j], grid->points[i+1][j], grid->points[i+2][j] ) ) { + break; + } + } + if ( j == grid->height ) { + // all of the points were close enough to the linear midpoints + // that we can collapse the entire column away + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k-1][j] ); + } + } + + grid->width--; + + // go to the next curve segment + i++; + continue; + } + + // + // we need to subdivide the curve + // + for ( j = 0 ; j < grid->height ; j++ ) { + vec3_t prev, mid, next; + + // save the control points now + VectorCopy( grid->points[i][j], prev ); + VectorCopy( grid->points[i+1][j], mid ); + VectorCopy( grid->points[i+2][j], next ); + + // make room for two additional columns in the grid + // columns i+1 will be replaced, column i+2 will become i+4 + // i+1, i+2, and i+3 will be generated + for ( k = grid->width - 1 ; k > i + 1 ; k-- ) { + VectorCopy( grid->points[k][j], grid->points[k+2][j] ); + } + + // generate the subdivided points + CM_Subdivide( prev, mid, next, grid->points[i+1][j], grid->points[i+2][j], grid->points[i+3][j] ); + } + + grid->width += 2; + + // the new aproximating point at i+1 may need to be removed + // or subdivided farther, so don't advance i + } +} + + +/* +====================== +CM_ComparePoints +====================== +*/ +#define POINT_EPSILON 0.1 +static qboolean CM_ComparePoints( float *a, float *b ) { + float d; + + d = a[0] - b[0]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + d = a[1] - b[1]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + d = a[2] - b[2]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + return qtrue; +} + +/* +================= +CM_RemoveDegenerateColumns + +If there are any identical columns, remove them +================= +*/ +static void CM_RemoveDegenerateColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height ; j++ ) { + if ( !CM_ComparePoints( grid->points[i][j], grid->points[i+1][j] ) ) { + break; + } + } + + if ( j != grid->height ) { + continue; // not degenerate + } + + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k-1][j] ); + } + } + grid->width--; + + // check against the next column + i--; + } +} + +/* +================================================================================ + +PATCH COLLIDE GENERATION + +================================================================================ +*/ + +static int numPlanes; +static patchPlane_t planes[MAX_PATCH_PLANES]; + +//static int numFacets; +//static facet_t facets[MAX_PATCH_PLANES]; //maybe MAX_FACETS ?? +static facet_t *facets = NULL; + +#define NORMAL_EPSILON 0.0001 +#define DIST_EPSILON 0.02 + +static inline int CM_PlaneEqual(patchPlane_t *p, float plane[4], int *flipped) { + float invplane[4]; + + if ( + Q_fabs(p->plane[0] - plane[0]) < NORMAL_EPSILON + && Q_fabs(p->plane[1] - plane[1]) < NORMAL_EPSILON + && Q_fabs(p->plane[2] - plane[2]) < NORMAL_EPSILON + && Q_fabs(p->plane[3] - plane[3]) < DIST_EPSILON ) + { + *flipped = qfalse; + return qtrue; + } + + VectorNegate(plane, invplane); + invplane[3] = -plane[3]; + + if ( + Q_fabs(p->plane[0] - invplane[0]) < NORMAL_EPSILON + && Q_fabs(p->plane[1] - invplane[1]) < NORMAL_EPSILON + && Q_fabs(p->plane[2] - invplane[2]) < NORMAL_EPSILON + && Q_fabs(p->plane[3] - invplane[3]) < DIST_EPSILON ) + { + *flipped = qtrue; + return qtrue; + } + + return qfalse; +} + +static inline void CM_SnapVector(vec3_t normal) { + int i; + + for (i=0 ; i<3 ; i++) + { + if ( Q_fabs(normal[i] - 1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + break; + } + if ( Q_fabs(normal[i] - -1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + break; + } + } +} + +static inline int CM_FindPlane2(float plane[4], int *flipped) { + int i; + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if (CM_PlaneEqual(&planes[i], plane, flipped)) return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + *flipped = qfalse; + + return numPlanes-1; +} + +/* +================== +CM_FindPlane +================== +*/ +static inline int CM_FindPlane( float *p1, float *p2, float *p3 ) { + float plane[4]; + int i; + float d; + + if ( !CM_PlaneFromPoints( plane, p1, p2, p3 ) ) { + return -1; + } + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if ( DotProduct( plane, planes[i].plane ) < 0 ) { + continue; // allow backwards planes? + } + + d = DotProduct( p1, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p2, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p3, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + // found it + return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + return numPlanes-1; +} + + +/* +================== +CM_PointOnPlaneSide +================== +*/ +static inline int CM_PointOnPlaneSide( float *p, int planeNum ) { + float *plane; + float d; + + if ( planeNum == -1 ) { + return SIDE_ON; + } + plane = planes[ planeNum ].plane; + + d = DotProduct( p, plane ) - plane[3]; + + if ( d > PLANE_TRI_EPSILON ) { + return SIDE_FRONT; + } + + if ( d < -PLANE_TRI_EPSILON ) { + return SIDE_BACK; + } + + return SIDE_ON; +} + +static inline int CM_GridPlane( int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int tri ) { + int p; + + p = gridPlanes[i][j][tri]; + if ( p != -1 ) { + return p; + } + p = gridPlanes[i][j][!tri]; + if ( p != -1 ) { + return p; + } + + // should never happen + Com_Printf( "WARNING: CM_GridPlane unresolvable\n" ); + return -1; +} + + +/* +================== +CM_EdgePlaneNum +================== +*/ +static inline int CM_EdgePlaneNum( cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], int i, int j, int k ) { + float *p1, *p2; + vec3_t up; + int p; + + switch ( k ) { + case 0: // top border + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 2: // bottom border + p1 = grid->points[i][j+1]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 3: // left border + p1 = grid->points[i][j]; + p2 = grid->points[i][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 1: // right border + p1 = grid->points[i+1][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 4: // diagonal out of triangle 0 + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 5: // diagonal out of triangle 1 + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + } + + Com_Error( ERR_DROP, "CM_EdgePlaneNum: bad k" ); + return -1; +} + + +/* +=================== +CM_SetBorderInward +=================== +*/ +static inline void CM_SetBorderInward( facet_t *facet, cGrid_t *grid, int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2], + int i, int j, int which ) { + int k, l; + float *points[4]; + int numPoints; + + switch ( which ) { + case -1: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + points[3] = grid->points[i][j+1]; + numPoints = 4; + break; + case 0: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + numPoints = 3; + break; + case 1: + points[0] = grid->points[i+1][j+1]; + points[1] = grid->points[i][j+1]; + points[2] = grid->points[i][j]; + numPoints = 3; + break; + default: + Com_Error( ERR_FATAL, "CM_SetBorderInward: bad parameter" ); + numPoints = 0; + break; + } + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + int front, back; + + front = 0; + back = 0; + + for ( l = 0 ; l < numPoints ; l++ ) { + int side; + + side = CM_PointOnPlaneSide( points[l], facet->borderPlanes[k] ); + if ( side == SIDE_FRONT ) { + front++; + } if ( side == SIDE_BACK ) { + back++; + } + } + + if ( front && !back ) { + facet->borderInward[k] = qtrue; + } else if ( back && !front ) { + facet->borderInward[k] = qfalse; + } else if ( !front && !back ) { + // flat side border + facet->borderPlanes[k] = -1; + } else { + // bisecting side border +#ifndef BSPC + Com_DPrintf( "WARNING: CM_SetBorderInward: mixed plane sides\n" ); +#endif + facet->borderInward[k] = qfalse; + if ( !debugBlock ) { + debugBlock = qtrue; + VectorCopy( grid->points[i][j], debugBlockPoints[0] ); + VectorCopy( grid->points[i+1][j], debugBlockPoints[1] ); + VectorCopy( grid->points[i+1][j+1], debugBlockPoints[2] ); + VectorCopy( grid->points[i][j+1], debugBlockPoints[3] ); + } + } + } +} + +/* +================== +CM_ValidateFacet + +If the facet isn't bounded by its borders, we screwed up. +================== +*/ +static inline qboolean CM_ValidateFacet( facet_t *facet ) { + float plane[4]; + int j; + winding_t *w; + vec3_t bounds[2]; + + if ( facet->surfacePlane == -1 ) { + return qfalse; + } + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if ( facet->borderPlanes[j] == -1 ) { + FreeWinding(w); + return qfalse; + } + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + + if ( !w ) { + return qfalse; // winding was completely chopped away + } + + // see if the facet is unreasonably large + WindingBounds( w, bounds[0], bounds[1] ); + FreeWinding( w ); + + for ( j = 0 ; j < 3 ; j++ ) { + if ( bounds[1][j] - bounds[0][j] > MAX_MAP_BOUNDS ) { + return qfalse; // we must be missing a plane + } + if ( bounds[0][j] >= MAX_MAP_BOUNDS ) { + return qfalse; + } + if ( bounds[1][j] <= -MAX_MAP_BOUNDS ) { + return qfalse; + } + } + return qtrue; // winding is fine +} + +/* +================== +CM_AddFacetBevels +================== +*/ +static inline void CM_AddFacetBevels( facet_t *facet ) { + + int i, j, k, l; + int axis, dir, order, flipped; + float plane[4], d, newplane[4]; + winding_t *w, *w2; + vec3_t mins, maxs, vec, vec2; + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if (facet->borderPlanes[j] == facet->surfacePlane) continue; + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( !w ) { + return; + } + + WindingBounds(w, mins, maxs); + + // add the axial planes + order = 0; + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2, order++ ) + { + VectorClear(plane); + plane[axis] = dir; + if (dir == 1) { + plane[3] = maxs[axis]; + } + else { + plane[3] = -mins[axis]; + } + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) + break; + } + + if ( i == facet->numBorders ) { + if (facet->numBorders > 4 + 6 + 16) Com_Printf("ERROR: too many bevels\n"); + facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); + facet->borderNoAdjust[facet->numBorders] = (qboolean)0; + facet->borderInward[facet->numBorders] = flipped; + facet->numBorders++; + } + } + } + // + // add the edge bevels + // + // test the non-axial plane edges + for ( j = 0 ; j < w->numpoints ; j++ ) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + //if it's a degenerate edge + if (VectorNormalize (vec) < 0.5) + continue; + CM_SnapVector(vec); + for ( k = 0; k < 3 ; k++ ) + if ( vec[k] == -1 || vec[k] == 1 ) + break; // axial + if ( k < 3 ) + continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2 ) + { + // construct a plane + VectorClear (vec2); + vec2[axis] = dir; + CrossProduct (vec, vec2, plane); + if (VectorNormalize (plane) < 0.5) + continue; + plane[3] = DotProduct (w->p[j], plane); + + // if all the points of the facet winding are + // behind this plane, it is a proper edge bevel + for ( l = 0 ; l < w->numpoints ; l++ ) + { + d = DotProduct (w->p[l], plane) - plane[3]; + if (d > 0.1) + break; // point in front + } + if ( l < w->numpoints ) + continue; + + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) { + break; + } + } + + if ( i == facet->numBorders ) { + if (facet->numBorders > 4 + 6 + 16) Com_Printf("ERROR: too many bevels\n"); + facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + if (facet->borderPlanes[facet->numBorders] == + facet->borderPlanes[k]) Com_Printf("WARNING: bevel plane already used\n"); + } + + facet->borderNoAdjust[facet->numBorders] = (qboolean)0; + facet->borderInward[facet->numBorders] = flipped; + // + w2 = CopyWinding(w); + Vector4Copy(planes[facet->borderPlanes[facet->numBorders]].plane, newplane); + if (!facet->borderInward[facet->numBorders]) + { + VectorNegate(newplane, newplane); + newplane[3] = -newplane[3]; + } //end if + ChopWindingInPlace( &w2, newplane, newplane[3], 0.1f ); + if (!w2) { +#ifndef BSPC + Com_DPrintf("WARNING: CM_AddFacetBevels... invalid bevel\n"); +#endif + continue; + } + else { + FreeWinding(w2); + } + // + facet->numBorders++; + //already got a bevel +// break; + } + } + } + } + FreeWinding( w ); + +#ifndef BSPC + //add opposite plane + facet->borderPlanes[facet->numBorders] = facet->surfacePlane; + facet->borderNoAdjust[facet->numBorders] = (qboolean)0; + facet->borderInward[facet->numBorders] = qtrue; + facet->numBorders++; +#endif //BSPC + +} + + +typedef enum { + EN_TOP, + EN_RIGHT, + EN_BOTTOM, + EN_LEFT +} edgeName_t; + +/* +================== +CM_PatchCollideFromGrid +================== +*/ +static inline void CM_PatchCollideFromGrid( cGrid_t *grid, patchCollide_t *pf ) { + int i, j; + float *p1, *p2, *p3; + MAC_STATIC int gridPlanes[MAX_GRID_SIZE][MAX_GRID_SIZE][2]; + facet_t *facet; + int borders[4]; + int noAdjust[4]; + + int numFacets; + facets = (facet_t*) Z_Malloc(MAX_FACETS*sizeof(facet_t), TAG_TEMP_WORKSPACE, qfalse, 4); + + numPlanes = 0; + numFacets = 0; + + // find the planes for each triangle of the grid + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p3 = grid->points[i+1][j+1]; + gridPlanes[i][j][0] = CM_FindPlane( p1, p2, p3 ); + + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j+1]; + p3 = grid->points[i][j]; + gridPlanes[i][j][1] = CM_FindPlane( p1, p2, p3 ); + } + } + + // create the borders for each facet + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + + borders[EN_TOP] = -1; + if ( j > 0 ) { + borders[EN_TOP] = gridPlanes[i][j-1][1]; + } else if ( grid->wrapHeight ) { + borders[EN_TOP] = gridPlanes[i][grid->height-2][1]; + } + noAdjust[EN_TOP] = ( borders[EN_TOP] == gridPlanes[i][j][0] ); + if ( borders[EN_TOP] == -1 || noAdjust[EN_TOP] ) { + borders[EN_TOP] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 0 ); + } + + borders[EN_BOTTOM] = -1; + if ( j < grid->height - 2 ) { + borders[EN_BOTTOM] = gridPlanes[i][j+1][0]; + } else if ( grid->wrapHeight ) { + borders[EN_BOTTOM] = gridPlanes[i][0][0]; + } + noAdjust[EN_BOTTOM] = ( borders[EN_BOTTOM] == gridPlanes[i][j][1] ); + if ( borders[EN_BOTTOM] == -1 || noAdjust[EN_BOTTOM] ) { + borders[EN_BOTTOM] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 2 ); + } + + borders[EN_LEFT] = -1; + if ( i > 0 ) { + borders[EN_LEFT] = gridPlanes[i-1][j][0]; + } else if ( grid->wrapWidth ) { + borders[EN_LEFT] = gridPlanes[grid->width-2][j][0]; + } + noAdjust[EN_LEFT] = ( borders[EN_LEFT] == gridPlanes[i][j][1] ); + if ( borders[EN_LEFT] == -1 || noAdjust[EN_LEFT] ) { + borders[EN_LEFT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 3 ); + } + + borders[EN_RIGHT] = -1; + if ( i < grid->width - 2 ) { + borders[EN_RIGHT] = gridPlanes[i+1][j][1]; + } else if ( grid->wrapWidth ) { + borders[EN_RIGHT] = gridPlanes[0][j][1]; + } + noAdjust[EN_RIGHT] = ( borders[EN_RIGHT] == gridPlanes[i][j][0] ); + if ( borders[EN_RIGHT] == -1 || noAdjust[EN_RIGHT] ) { + borders[EN_RIGHT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 1 ); + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + Com_Memset( facet, 0, sizeof( *facet ) ); + + if ( gridPlanes[i][j][0] == gridPlanes[i][j][1] ) { + if ( gridPlanes[i][j][0] == -1 ) { + continue; // degenrate + } + facet->surfacePlane = gridPlanes[i][j][0]; + facet->numBorders = 4; + facet->borderPlanes[0] = borders[EN_TOP]; + facet->borderNoAdjust[0] = (qboolean)noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + facet->borderNoAdjust[1] = (qboolean)noAdjust[EN_RIGHT]; + facet->borderPlanes[2] = borders[EN_BOTTOM]; + facet->borderNoAdjust[2] = (qboolean)noAdjust[EN_BOTTOM]; + facet->borderPlanes[3] = borders[EN_LEFT]; + facet->borderNoAdjust[3] = (qboolean)noAdjust[EN_LEFT]; + CM_SetBorderInward( facet, grid, gridPlanes, i, j, -1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } else { + // two seperate triangles + facet->surfacePlane = gridPlanes[i][j][0]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_TOP]; + facet->borderNoAdjust[0] = (qboolean)noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + facet->borderNoAdjust[1] = (qboolean)noAdjust[EN_RIGHT]; + facet->borderPlanes[2] = gridPlanes[i][j][1]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_BOTTOM]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 4 ); + } + } + CM_SetBorderInward( facet, grid, gridPlanes, i, j, 0 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + Com_Memset( facet, 0, sizeof( *facet ) ); + + facet->surfacePlane = gridPlanes[i][j][1]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_BOTTOM]; + facet->borderNoAdjust[0] = (qboolean)noAdjust[EN_BOTTOM]; + facet->borderPlanes[1] = borders[EN_LEFT]; + facet->borderNoAdjust[1] = (qboolean)noAdjust[EN_LEFT]; + facet->borderPlanes[2] = gridPlanes[i][j][0]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_TOP]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 5 ); + } + } + CM_SetBorderInward( facet, grid, gridPlanes, i, j, 1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } + } + } + + // copy the results out + pf->numPlanes = numPlanes; + pf->numFacets = numFacets; + if (numFacets) + { + pf->facets = (facet_t *)Hunk_Alloc( numFacets * sizeof( *pf->facets ), h_high ); + Com_Memcpy( pf->facets, facets, numFacets * sizeof( *pf->facets ) ); + } + else + { + pf->facets = 0; + } + pf->planes = (patchPlane_t *)Hunk_Alloc( numPlanes * sizeof( *pf->planes ), h_high ); + Com_Memcpy( pf->planes, planes, numPlanes * sizeof( *pf->planes ) ); + + Z_Free(facets); +} + + +/* +=================== +CM_GeneratePatchCollide + +Creates an internal structure that will be used to perform +collision detection with a patch mesh. + +Points is packed as concatenated rows. +=================== +*/ +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ) { + patchCollide_t *pf; + MAC_STATIC cGrid_t grid; + int i, j; + + if ( width <= 2 || height <= 2 || !points ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: bad parameters: (%i, %i, %p)", + width, height, points ); + } + + if ( !(width & 1) || !(height & 1) ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: even sizes are invalid for quadratic meshes" ); + } + + if ( width > MAX_GRID_SIZE || height > MAX_GRID_SIZE ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: source is > MAX_GRID_SIZE" ); + } + + // build a grid + grid.width = width; + grid.height = height; + grid.wrapWidth = qfalse; + grid.wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + VectorCopy( points[j*width + i], grid.points[i][j] ); + } + } + + // subdivide the grid + CM_SetGridWrapWidth( &grid ); + CM_SubdivideGridColumns( &grid ); + CM_RemoveDegenerateColumns( &grid ); + + CM_TransposeGrid( &grid ); + + CM_SetGridWrapWidth( &grid ); + CM_SubdivideGridColumns( &grid ); + CM_RemoveDegenerateColumns( &grid ); + + // we now have a grid of points exactly on the curve + // the aproximate surface defined by these points will be + // collided against + pf = (struct patchCollide_s *)Hunk_Alloc( sizeof( *pf ), h_high ); + ClearBounds( pf->bounds[0], pf->bounds[1] ); + for ( i = 0 ; i < grid.width ; i++ ) { + for ( j = 0 ; j < grid.height ; j++ ) { + AddPointToBounds( grid.points[i][j], pf->bounds[0], pf->bounds[1] ); + } + } + + c_totalPatchBlocks += ( grid.width - 1 ) * ( grid.height - 1 ); + + // generate a bsp tree for the surface + CM_PatchCollideFromGrid( &grid, pf ); + + // expand by one unit for epsilon purposes + pf->bounds[0][0] -= 1; + pf->bounds[0][1] -= 1; + pf->bounds[0][2] -= 1; + + pf->bounds[1][0] += 1; + pf->bounds[1][1] += 1; + pf->bounds[1][2] += 1; + + return pf; +} + +/* +================================================================================ + +TRACE TESTING + +================================================================================ +*/ + +/* +==================== +CM_TracePointThroughPatchCollide + + special case for point traces because the patch collide "brushes" have no volume +==================== +*/ +static inline void CM_TracePointThroughPatchCollide( traceWork_t *tw, trace_t &trace, const struct patchCollide_s *pc ) { + qboolean frontFacing[MAX_PATCH_PLANES]; + float intersection[MAX_PATCH_PLANES]; + float intersect; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d1, d2; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + +#ifndef BSPC + if ( !cm_playerCurveClip->integer || !tw->isPoint ) { + return; + } +#endif + + // determine the trace's relationship to all planes + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + if ( d1 <= 0 ) { + frontFacing[i] = qfalse; + } else { + frontFacing[i] = qtrue; + } + if ( d1 == d2 ) { + intersection[i] = 99999; + } else { + intersection[i] = d1 / ( d1 - d2 ); + if ( intersection[i] <= 0 ) { + intersection[i] = 99999; + } + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + if ( !frontFacing[facet->surfacePlane] ) { + continue; + } + intersect = intersection[facet->surfacePlane]; + if ( intersect < 0 ) { + continue; // surface is behind the starting point + } + if ( intersect > trace.fraction ) { + continue; // already hit something closer + } + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->borderPlanes[j]; + if ( frontFacing[k] ^ facet->borderInward[j] ) { + if ( intersection[k] > intersect ) { + break; + } + } else { + if ( intersection[k] < intersect ) { + break; + } + } + } + if ( j == facet->numBorders ) { + // we hit this facet +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif //BSPC + planes = &pc->planes[facet->surfacePlane]; + + // calculate intersection with a slight pushoff + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + trace.fraction = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if ( trace.fraction < 0 ) { + trace.fraction = 0; + } + + VectorCopy( planes->plane, trace.plane.normal ); + trace.plane.dist = planes->plane[3]; + } + } +} + +/* +==================== +CM_CheckFacetPlane +==================== +*/ +static inline int CM_CheckFacetPlane(float *plane, vec3_t start, vec3_t end, float *enterFrac, float *leaveFrac, int *hit) { + float d1, d2, f; + + *hit = qfalse; + + d1 = DotProduct( start, plane ) - plane[3]; + d2 = DotProduct( end, plane ) - plane[3]; + + // if completely in front of face, no intersection with the entire facet + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return qfalse; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + return qtrue; + } + + // crosses face + if (d1 > d2) { // enter + f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f < 0 ) { + f = 0; + } + //always favor previous plane hits and thus also the surface plane hit + if (f > *enterFrac) { + *enterFrac = f; + *hit = qtrue; + } + } else { // leave + f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f > 1 ) { + f = 1; + } + if (f < *leaveFrac) { + *leaveFrac = f; + } + } + return qtrue; +} + +/* +==================== +CM_TraceThroughPatchCollide +==================== +*/ +void CM_TraceThroughPatchCollide( traceWork_t *tw, trace_t &trace, const struct patchCollide_s *pc ) +{ + int i, j, hit, hitnum; + float offset, enterFrac, leaveFrac, t; + patchPlane_t *planes; + facet_t *facet; + float plane[4], bestplane[4]; + vec3_t startp, endp; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + +#ifndef CULL_BBOX + // I'm not sure if test is strictly correct. Are all + // bboxes axis aligned? Do I care? It seems to work + // good enough... + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw->bounds[0][i] > pc->bounds[1][i] + || tw->bounds[1][i] < pc->bounds[0][i] ) { + return; + } + } +#endif + + if (tw->isPoint) { + CM_TracePointThroughPatchCollide( tw, trace, pc ); + return; + } + // + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + enterFrac = -1.0; + leaveFrac = 1.0; + hitnum = -1; + // + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] -= offset; + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + // + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) + continue; + if (hit) { + Vector4Copy(plane, bestplane); + } + // + for ( j = 0 ; j < facet->numBorders ; j++ ) { + planes = &pc->planes[ facet->borderPlanes[j] ]; + if (facet->borderInward[j]) { + VectorNegate(planes->plane, plane); + plane[3] = -planes->plane[3]; + } + else { + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + } + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + // NOTE: this works even though the plane might be flipped because the bbox is centered + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] += fabs(offset); + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + // + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) + break; + if (hit) { + hitnum = j; + Vector4Copy(plane, bestplane); + } + } + if (j < facet->numBorders) continue; + //never clip against the back side + if (hitnum == facet->numBorders - 1) continue; + // + if (enterFrac < leaveFrac && enterFrac >= 0) { + if (enterFrac < trace.fraction) { + if (enterFrac < 0) { + enterFrac = 0; + } +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv && cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif // BSPC + + trace.fraction = enterFrac; + VectorCopy( bestplane, trace.plane.normal ); + trace.plane.dist = bestplane[3]; + } + } + } +} + + +/* +======================================================================= + +POSITION DETECTION + +======================================================================= +*/ + +/* +==================== +CM_PositionTestInPatchCollide + +Modifies tr->tr if any of the facets effect the trace +==================== +*/ +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + int i, j; + float offset, t; + patchPlane_t *planes; + facet_t *facet; + float plane[4]; + vec3_t startp; + + if (tw->isPoint) { + return qfalse; + } + // + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0 ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + } + else { + VectorAdd( tw->start, tw->sphere.offset, startp ); + } + } + else { + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] -= offset; + VectorCopy( tw->start, startp ); + } + + if ( DotProduct( plane, startp ) - plane[3] > 0.0f ) { + continue; + } + + for ( j = 0; j < facet->numBorders; j++ ) { + planes = &pc->planes[ facet->borderPlanes[j] ]; + if (facet->borderInward[j]) { + VectorNegate(planes->plane, plane); + plane[3] = -planes->plane[3]; + } + else { + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + } + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + } + else { + VectorAdd( tw->start, tw->sphere.offset, startp ); + } + } + else { + // NOTE: this works even though the plane might be flipped because the bbox is centered + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] += fabs(offset); + VectorCopy( tw->start, startp ); + } + + if ( DotProduct( plane, startp ) - plane[3] > 0.0f ) { + break; + } + } + if (j < facet->numBorders) { + continue; + + } + // inside this patch facet + return qtrue; + } + + return qfalse; +} + + +/* +======================================================================= + +DEBUGGING + +======================================================================= +*/ + + +/* +================== +CM_DrawDebugSurface + +Called from the renderer +================== +*/ +#ifndef BSPC +void BotDrawDebugPolygons(void (*drawPoly)(int color, int numPoints, float *points), int value); +#endif + +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ) { + static cvar_t *cv; +#ifndef BSPC + static cvar_t *cv2; +#endif + const patchCollide_t *pc; + facet_t *facet; + winding_t *w; + int i, j, k, n; + int curplanenum, planenum, curinward, inward; + float plane[4]; + vec3_t mins = {-15, -15, -28}, maxs = {15, 15, 28}; + //vec3_t mins = {0, 0, 0}, maxs = {0, 0, 0}; + vec3_t v1, v2; + +#ifndef BSPC + if ( !cv2 ) + { + cv2 = Cvar_Get( "r_debugSurface", "0", 0 ); + } + + if (cv2->integer != 1) + { + BotDrawDebugPolygons(drawPoly, cv2->integer); + return; + } +#endif + + if ( !debugPatchCollide ) { + return; + } + +#ifndef BSPC + if ( !cv ) { + cv = Cvar_Get( "cm_debugSize", "2", 0 ); + } +#endif + pc = debugPatchCollide; + + for ( i = 0, facet = pc->facets ; i < pc->numFacets ; i++, facet++ ) { + + for ( k = 0 ; k < facet->numBorders + 1; k++ ) { + // + if (k < facet->numBorders) { + planenum = facet->borderPlanes[k]; + inward = facet->borderInward[k]; + } + else { + planenum = facet->surfacePlane; + inward = qfalse; + //continue; + } + + Vector4Copy( pc->planes[ planenum ].plane, plane ); + + //planenum = facet->surfacePlane; + if ( inward ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + plane[3] += cv->value; + //* + for (n = 0; n < 3; n++) + { + if (plane[n] > 0) v1[n] = maxs[n]; + else v1[n] = mins[n]; + } //end for + VectorNegate(plane, v2); + plane[3] += fabs(DotProduct(v1, v2)); + //*/ + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders + 1 && w; j++ ) { + // + if (j < facet->numBorders) { + curplanenum = facet->borderPlanes[j]; + curinward = facet->borderInward[j]; + } + else { + curplanenum = facet->surfacePlane; + curinward = qfalse; + //continue; + } + // + if (curplanenum == planenum) continue; + + Vector4Copy( pc->planes[ curplanenum ].plane, plane ); + if ( !curinward ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + // if ( !facet->borderNoAdjust[j] ) { + plane[3] -= cv->value; + // } + for (n = 0; n < 3; n++) + { + if (plane[n] > 0) v1[n] = maxs[n]; + else v1[n] = mins[n]; + } //end for + VectorNegate(plane, v2); + plane[3] -= fabs(DotProduct(v1, v2)); + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( w ) { + if ( facet == debugFacet ) { + drawPoly( 4, w->numpoints, w->p[0] ); + //Com_Printf("blue facet has %d border planes\n", facet->numBorders); + } else { + drawPoly( 1, w->numpoints, w->p[0] ); + } + FreeWinding( w ); + } + else + Com_Printf("winding chopped away by border planes\n"); + } + } + + // draw the debug block + { + vec3_t v[3]; + + VectorCopy( debugBlockPoints[0], v[0] ); + VectorCopy( debugBlockPoints[1], v[1] ); + VectorCopy( debugBlockPoints[2], v[2] ); + drawPoly( 2, 3, v[0] ); + + VectorCopy( debugBlockPoints[2], v[0] ); + VectorCopy( debugBlockPoints[3], v[1] ); + VectorCopy( debugBlockPoints[0], v[2] ); + drawPoly( 2, 3, v[0] ); + } + +#if 0 + vec3_t v[4]; + + v[0][0] = pc->bounds[1][0]; + v[0][1] = pc->bounds[1][1]; + v[0][2] = pc->bounds[1][2]; + + v[1][0] = pc->bounds[1][0]; + v[1][1] = pc->bounds[0][1]; + v[1][2] = pc->bounds[1][2]; + + v[2][0] = pc->bounds[0][0]; + v[2][1] = pc->bounds[0][1]; + v[2][2] = pc->bounds[1][2]; + + v[3][0] = pc->bounds[0][0]; + v[3][1] = pc->bounds[1][1]; + v[3][2] = pc->bounds[1][2]; + + drawPoly( 4, v[0] ); +#endif +} + + + diff --git a/codemp/qcommon/cm_patch.h b/codemp/qcommon/cm_patch.h new file mode 100644 index 0000000..29f03c1 --- /dev/null +++ b/codemp/qcommon/cm_patch.h @@ -0,0 +1,128 @@ + +//#define CULL_BBOX + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +Issues for collision against curved surfaces: + +Surface edges need to be handled differently than surface planes + +Plane expansion causes raw surfaces to expand past expanded bounding box + +Position test of a volume against a surface is tricky. + +Position test of a point against a surface is not well defined, because the surface has no volume. + + +Tracing leading edge points instead of volumes? +Position test by tracing corner to corner? (8*7 traces -- ouch) + +coplanar edges +triangulated patches +degenerate patches + + endcaps + degenerate + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + + +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +#ifdef _XBOX +//Facets are now two structures - a maximum sized version that's used +//temporarily during load time, and smaller version that only allocates +//as much memory as needed. The load version is copied into the small +//version after it's been assembled. +#pragma pack(push, 1) +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + short borderPlanes[4+6+16]; + unsigned char borderInward[4+6+16]; + unsigned char borderNoAdjust[4+6+16]; +} facetLoad_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + char *data; + + short *GetBorderPlanes(void) { return (short*)data; } + char *GetBorderInward(void) { return data + (numBorders * 2); } + char *GetBorderNoAdjust(void) + { return data + (numBorders * 2) + numBorders; } + + const short *GetBorderPlanes(void) const { return (short*)data; } + const char *GetBorderInward(void) const { return data + (numBorders * 2); } + const char *GetBorderNoAdjust(void) const + { return data + (numBorders * 2) + numBorders; } +} facet_t; +#pragma pack(pop) + +#else // _XBOX + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4+6+16]; + int borderInward[4+6+16]; + qboolean borderNoAdjust[4+6+16]; +} facet_t; + +#endif // _XBOX + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + +#ifdef _XBOX +#define CM_MAX_GRID_SIZE 129 +#else +#define MAX_GRID_SIZE 129 +#endif + +typedef struct { + int width; + int height; + qboolean wrapWidth; + qboolean wrapHeight; +#ifdef _XBOX + vec3_t points[CM_MAX_GRID_SIZE][CM_MAX_GRID_SIZE]; // [width][height] +#else + vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height] +#endif +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 + +#ifdef _XBOX +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points, + facetLoad_t *facetbuf, int *gridbuf ); +#else +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ); +#endif diff --git a/codemp/qcommon/cm_patch_xbox.cpp b/codemp/qcommon/cm_patch_xbox.cpp new file mode 100644 index 0000000..e2ce853 --- /dev/null +++ b/codemp/qcommon/cm_patch_xbox.cpp @@ -0,0 +1,1760 @@ +#include "../qcommon/exe_headers.h" + +#include "cm_local.h" +#include "cm_patch.h" + +//#define CULL_BBOX + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +Issues for collision against curved surfaces: + +Surface edges need to be handled differently than surface planes + +Plane expansion causes raw surfaces to expand past expanded bounding box + +Position test of a volume against a surface is tricky. + +Position test of a point against a surface is not well defined, because the surface has no volume. + + +Tracing leading edge points instead of volumes? +Position test by tracing corner to corner? (8*7 traces -- ouch) + +coplanar edges +triangulated patches +degenerate patches + + endcaps + degenerate + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + +/* +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4+6+16]; + int borderInward[4+6+16]; + qboolean borderNoAdjust[4+6+16]; +} facet_t; + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + + +#define MAX_GRID_SIZE 129 + +typedef struct { + int width; + int height; + qboolean wrapWidth; + qboolean wrapHeight; + vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height] +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 +*/ + +#define ADDBEVELS + +int c_totalPatchBlocks; +int c_totalPatchSurfaces; +int c_totalPatchEdges; + +static const patchCollide_t *debugPatchCollide; +static const facet_t *debugFacet; +static qboolean debugBlock; +static vec3_t debugBlockPoints[4]; + +/* +================= +CM_ClearLevelPatches +================= +*/ +void CM_ClearLevelPatches( void ) { + debugPatchCollide = NULL; + debugFacet = NULL; +} + +/* +================= +CM_SignbitsForNormal +================= +*/ +static int CM_SignbitsForNormal( vec3_t normal ) { + int bits, j; + + bits = 0; + for (j=0 ; j<3 ; j++) { + if ( normal[j] < 0 ) { + bits |= 1<= SUBDIVIDE_DISTANCE * SUBDIVIDE_DISTANCE; +} + +/* +=============== +CM_Subdivide + +a, b, and c are control points. +the subdivided sequence will be: a, out1, out2, out3, c +=============== +*/ +static void CM_Subdivide( vec3_t a, vec3_t b, vec3_t c, vec3_t out1, vec3_t out2, vec3_t out3 ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + out1[i] = 0.5 * (a[i] + b[i]); + out3[i] = 0.5 * (b[i] + c[i]); + out2[i] = 0.5 * (out1[i] + out3[i]); + } +} + +/* +================= +CM_TransposeGrid + +Swaps the rows and columns in place +================= +*/ +static void CM_TransposeGrid( cGrid_t *grid ) { + int i, j, l; + vec3_t temp; + qboolean tempWrap; + + if ( grid->width > grid->height ) { + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = i + 1 ; j < grid->width ; j++ ) { + if ( j < grid->height ) { + // swap the value + VectorCopy( grid->points[i][j], temp ); + VectorCopy( grid->points[j][i], grid->points[i][j] ); + VectorCopy( temp, grid->points[j][i] ); + } else { + // just copy + VectorCopy( grid->points[j][i], grid->points[i][j] ); + } + } + } + } else { + for ( i = 0 ; i < grid->width ; i++ ) { + for ( j = i + 1 ; j < grid->height ; j++ ) { + if ( j < grid->width ) { + // swap the value + VectorCopy( grid->points[j][i], temp ); + VectorCopy( grid->points[i][j], grid->points[j][i] ); + VectorCopy( temp, grid->points[i][j] ); + } else { + // just copy + VectorCopy( grid->points[i][j], grid->points[j][i] ); + } + } + } + } + + l = grid->width; + grid->width = grid->height; + grid->height = l; + + tempWrap = grid->wrapWidth; + grid->wrapWidth = grid->wrapHeight; + grid->wrapHeight = tempWrap; +} + +/* +=================== +CM_SetGridWrapWidth + +If the left and right columns are exactly equal, set grid->wrapWidth qtrue +=================== +*/ +static void CM_SetGridWrapWidth( cGrid_t *grid ) { + int i, j; + float d; + + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + d = grid->points[0][i][j] - grid->points[grid->width-1][i][j]; + if ( d < -WRAP_POINT_EPSILON || d > WRAP_POINT_EPSILON ) { + break; + } + } + if ( j != 3 ) { + break; + } + } + if ( i == grid->height ) { + grid->wrapWidth = qtrue; + } else { + grid->wrapWidth = qfalse; + } +} + + +/* +================= +CM_SubdivideGridColumns + +Adds columns as necessary to the grid until +all the aproximating points are within SUBDIVIDE_DISTANCE +from the true curve +================= +*/ +static void CM_SubdivideGridColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 2 ; ) { + // grid->points[i][x] is an interpolating control point + // grid->points[i+1][x] is an aproximating control point + // grid->points[i+2][x] is an interpolating control point + + // + // first see if we can collapse the aproximating collumn away + // + for ( j = 0 ; j < grid->height ; j++ ) { + if ( CM_NeedsSubdivision( grid->points[i][j], grid->points[i+1][j], grid->points[i+2][j] ) ) { + break; + } + } + if ( j == grid->height ) { + // all of the points were close enough to the linear midpoints + // that we can collapse the entire column away + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k-1][j] ); + } + } + + grid->width--; + + // go to the next curve segment + i++; + continue; + } + + // + // we need to subdivide the curve + // + for ( j = 0 ; j < grid->height ; j++ ) { + vec3_t prev, mid, next; + + // save the control points now + VectorCopy( grid->points[i][j], prev ); + VectorCopy( grid->points[i+1][j], mid ); + VectorCopy( grid->points[i+2][j], next ); + + // make room for two additional columns in the grid + // columns i+1 will be replaced, column i+2 will become i+4 + // i+1, i+2, and i+3 will be generated + for ( k = grid->width - 1 ; k > i + 1 ; k-- ) { + VectorCopy( grid->points[k][j], grid->points[k+2][j] ); + } + + // generate the subdivided points + CM_Subdivide( prev, mid, next, grid->points[i+1][j], grid->points[i+2][j], grid->points[i+3][j] ); + } + + grid->width += 2; + + // the new aproximating point at i+1 may need to be removed + // or subdivided farther, so don't advance i + } +} + + +#define POINT_EPSILON 0.1 +/* +====================== +CM_ComparePoints +====================== +*/ +static qboolean CM_ComparePoints( float *a, float *b ) { + float d; + + d = a[0] - b[0]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + d = a[1] - b[1]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + d = a[2] - b[2]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + return qtrue; +} + +/* +================= +CM_RemoveDegenerateColumns + +If there are any identical columns, remove them +================= +*/ +static void CM_RemoveDegenerateColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height ; j++ ) { + if ( !CM_ComparePoints( grid->points[i][j], grid->points[i+1][j] ) ) { + break; + } + } + + if ( j != grid->height ) { + continue; // not degenerate + } + + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k-1][j] ); + } + } + grid->width--; + + // check against the next column + i--; + } +} + +/* +================================================================================ + +PATCH COLLIDE GENERATION + +================================================================================ +*/ + +static int numPlanes; +static patchPlane_t *planes = NULL; +void CM_TempPatchPlanesAlloc(void) +{ + if(!planes) { + planes = (patchPlane_t*)Z_Malloc(MAX_PATCH_PLANES*sizeof(patchPlane_t), + TAG_TEMP_WORKSPACE, qfalse); + } +} + +void CM_TempPatchPlanesDealloc(void) +{ + if(planes) { + Z_Free(planes); + planes = NULL; + } +} + +static facet_t *facets = NULL; + +#define NORMAL_EPSILON 0.0001 +#define DIST_EPSILON 0.02 + +int CM_PlaneEqual(patchPlane_t *p, float plane[4], int *flipped) { + float invplane[4]; + + if ( + Q_fabs(p->plane[0] - plane[0]) < NORMAL_EPSILON + && Q_fabs(p->plane[1] - plane[1]) < NORMAL_EPSILON + && Q_fabs(p->plane[2] - plane[2]) < NORMAL_EPSILON + && Q_fabs(p->plane[3] - plane[3]) < DIST_EPSILON ) + { + *flipped = qfalse; + return qtrue; + } + + VectorNegate(plane, invplane); + invplane[3] = -plane[3]; + + if ( + Q_fabs(p->plane[0] - invplane[0]) < NORMAL_EPSILON + && Q_fabs(p->plane[1] - invplane[1]) < NORMAL_EPSILON + && Q_fabs(p->plane[2] - invplane[2]) < NORMAL_EPSILON + && Q_fabs(p->plane[3] - invplane[3]) < DIST_EPSILON ) + { + *flipped = qtrue; + return qtrue; + } + + return qfalse; +} + +void CM_SnapVector(vec3_t normal) { + int i; + + for (i=0 ; i<3 ; i++) + { + if ( Q_fabs(normal[i] - 1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + break; + } + if ( Q_fabs(normal[i] - -1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + break; + } + } +} + +int CM_FindPlane2(float plane[4], int *flipped) { + int i; + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if (CM_PlaneEqual(&planes[i], plane, flipped)) return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES reached (%d)", MAX_PATCH_PLANES ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + *flipped = qfalse; + + return numPlanes-1; +} + +/* +================== +CM_FindPlane +================== +*/ +static int CM_FindPlane( float *p1, float *p2, float *p3 ) { + float plane[4]; + int i; + float d; + + if ( !CM_PlaneFromPoints( plane, p1, p2, p3 ) ) { + return -1; + } + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if ( DotProduct( plane, planes[i].plane ) < 0 ) { + continue; // allow backwards planes? + } + + d = DotProduct( p1, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p2, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p3, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + // found it + return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + return numPlanes-1; +} + + +/* +================== +CM_PointOnPlaneSide +================== +*/ +static int CM_PointOnPlaneSide( float *p, int planeNum ) { + float *plane; + float d; + + if ( planeNum == -1 ) { + return SIDE_ON; + } + plane = planes[ planeNum ].plane; + + d = DotProduct( p, plane ) - plane[3]; + + if ( d > PLANE_TRI_EPSILON ) { + return SIDE_FRONT; + } + + if ( d < -PLANE_TRI_EPSILON ) { + return SIDE_BACK; + } + + return SIDE_ON; +} + + +static int CM_GridPlane( int* gridPlanes, int i, int j, int tri ) { + int p; + + p = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+tri]; + if ( p != -1 ) { + return p; + } + p = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+(!tri)]; + if ( p != -1 ) { + return p; + } + + // should never happen +#ifndef FINAL_BUILD + Com_Printf( "WARNING: CM_GridPlane unresolvable\n" ); +#endif + return -1; +} + +/* +================== +CM_EdgePlaneNum +================== +*/ +static int CM_EdgePlaneNum( cGrid_t *grid, int* gridPlanes/*[PATCH_MAX_GRID_SIZE][PATCH_MAX_GRID_SIZE][2]*/, int i, int j, int k ) { + float *p1, *p2; + vec3_t up; + int p; + + switch ( k ) { + case 0: // top border + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 2: // bottom border + p1 = grid->points[i][j+1]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 3: // left border + p1 = grid->points[i][j]; + p2 = grid->points[i][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 1: // right border + p1 = grid->points[i+1][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 4: // diagonal out of triangle 0 + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 5: // diagonal out of triangle 1 + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + } + + Com_Error( ERR_DROP, "CM_EdgePlaneNum: bad k" ); + return -1; +} + +/* +=================== +CM_SetBorderInward +=================== +*/ +static void CM_SetBorderInward( facetLoad_t *facet, cGrid_t *grid, + int i, int j, int which ) { + int k, l; + float *points[4]; + int numPoints; + + switch ( which ) { + case -1: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + points[3] = grid->points[i][j+1]; + numPoints = 4; + break; + case 0: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + numPoints = 3; + break; + case 1: + points[0] = grid->points[i+1][j+1]; + points[1] = grid->points[i][j+1]; + points[2] = grid->points[i][j]; + numPoints = 3; + break; + default: + Com_Error( ERR_FATAL, "CM_SetBorderInward: bad parameter" ); + numPoints = 0; + break; + } + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + int front, back; + + front = 0; + back = 0; + + for ( l = 0 ; l < numPoints ; l++ ) { + int side; + + side = CM_PointOnPlaneSide( points[l], facet->borderPlanes[k] ); + if ( side == SIDE_FRONT ) { + front++; + } if ( side == SIDE_BACK ) { + back++; + } + } + + if ( front && !back ) { + facet->borderInward[k] = qtrue; + } else if ( back && !front ) { + facet->borderInward[k] = qfalse; + } else if ( !front && !back ) { + // flat side border + facet->borderPlanes[k] = -1; + } else { + // bisecting side border + Com_DPrintf( "WARNING: CM_SetBorderInward: mixed plane sides\n" ); + facet->borderInward[k] = qfalse; + if ( !debugBlock ) { + debugBlock = qtrue; + VectorCopy( grid->points[i][j], debugBlockPoints[0] ); + VectorCopy( grid->points[i+1][j], debugBlockPoints[1] ); + VectorCopy( grid->points[i+1][j+1], debugBlockPoints[2] ); + VectorCopy( grid->points[i][j+1], debugBlockPoints[3] ); + } + } + } +} + +/* +================== +CM_ValidateFacet + +If the facet isn't bounded by its borders, we screwed up. +================== +*/ +static qboolean CM_ValidateFacet( facetLoad_t *facet ) { + float plane[4]; + int j; + winding_t *w; + vec3_t bounds[2]; + + if ( facet->surfacePlane == -1 ) { + return qfalse; + } + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if ( facet->borderPlanes[j] == -1 ) { + FreeWinding(w); + return qfalse; + } + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + + if ( !w ) { + return qfalse; // winding was completely chopped away + } + + // see if the facet is unreasonably large + WindingBounds( w, bounds[0], bounds[1] ); + FreeWinding( w ); + + for ( j = 0 ; j < 3 ; j++ ) { + if ( bounds[1][j] - bounds[0][j] > MAX_MAP_BOUNDS ) { + return qfalse; // we must be missing a plane + } + if ( bounds[0][j] >= MAX_MAP_BOUNDS ) { + return qfalse; + } + if ( bounds[1][j] <= -MAX_MAP_BOUNDS ) { + return qfalse; + } + } + return qtrue; // winding is fine +} + +/* +================== +CM_AddFacetBevels +================== +*/ +void CM_AddFacetBevels( facetLoad_t *facet ) { + + int i, j, k, l; + int axis, dir, order, flipped; + float plane[4], d, newplane[4]; + winding_t *w, *w2; + vec3_t mins, maxs, vec, vec2; + +#ifndef ADDBEVELS + return; +#endif + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if (facet->borderPlanes[j] == facet->surfacePlane) continue; + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( !w ) { + return; + } + + WindingBounds(w, mins, maxs); + + // add the axial planes + order = 0; + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2, order++ ) + { + VectorClear(plane); + plane[axis] = dir; + if (dir == 1) { + plane[3] = maxs[axis]; + } + else { + plane[3] = -mins[axis]; + } + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) + break; + } + + if ( i == facet->numBorders ) { + if (facet->numBorders > 4 + 6 + 16) Com_Printf(S_COLOR_RED"ERROR: too many bevels\n"); + int num = CM_FindPlane2(plane, &flipped); + assert(num > -32768 && num < 32768); + facet->borderPlanes[facet->numBorders] = num; + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = flipped; + facet->numBorders++; + } + } + } + // + // add the edge bevels + // + // test the non-axial plane edges + for ( j = 0 ; j < w->numpoints ; j++ ) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + //if it's a degenerate edge + if (VectorNormalize (vec) < 0.5) + continue; + CM_SnapVector(vec); + for ( k = 0; k < 3 ; k++ ) + if ( vec[k] == -1 || vec[k] == 1 ) + break; // axial + if ( k < 3 ) + continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2 ) + { + // construct a plane + VectorClear (vec2); + vec2[axis] = dir; + CrossProduct (vec, vec2, plane); + if (VectorNormalize (plane) < 0.5) + continue; + plane[3] = DotProduct (w->p[j], plane); + + // if all the points of the facet winding are + // behind this plane, it is a proper edge bevel + for ( l = 0 ; l < w->numpoints ; l++ ) + { + d = DotProduct (w->p[l], plane) - plane[3]; + if (d > 0.1) + break; // point in front + } + if ( l < w->numpoints ) + continue; + + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) { + break; + } + } + + if ( i == facet->numBorders ) { + if (facet->numBorders > 4 + 6 + 16) Com_Printf(S_COLOR_RED"ERROR: too many bevels\n"); + int num = CM_FindPlane2(plane, &flipped); + assert(num > -32768 && num < 32768); + facet->borderPlanes[facet->numBorders] = num; + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + if (facet->borderPlanes[facet->numBorders] == + facet->borderPlanes[k]) Com_Printf("WARNING: bevel plane already used\n"); + } + + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = flipped; + // + w2 = CopyWinding(w); + Vector4Copy(planes[facet->borderPlanes[facet->numBorders]].plane, newplane); + if (!facet->borderInward[facet->numBorders]) + { + VectorNegate(newplane, newplane); + newplane[3] = -newplane[3]; + } //end if + ChopWindingInPlace( &w2, newplane, newplane[3], 0.1f ); + if (!w2) { + Com_DPrintf("WARNING: CM_AddFacetBevels... invalid bevel\n"); + continue; + } + else { + FreeWinding(w2); + } + // + facet->numBorders++; + //already got a bevel +// break; + } + } + } + } + FreeWinding( w ); + +#ifndef BSPC + //add opposite plane + assert(facet->surfacePlane > -32768 && facet->surfacePlane < 32768); + facet->borderPlanes[facet->numBorders] = facet->surfacePlane; + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = qtrue; + facet->numBorders++; +#endif //BSPC + +} + + +typedef enum { + EN_TOP, + EN_RIGHT, + EN_BOTTOM, + EN_LEFT +} edgeName_t; + + +facetLoad_t* cm_facets = 0; +int* cm_gridPlanes = 0; +void CM_PatchCollideFromGridTempAlloc() +{ + if (!cm_facets) + { + cm_facets = (facetLoad_t*)Z_Malloc(MAX_PATCH_PLANES*sizeof(facetLoad_t), TAG_TEMP_WORKSPACE, qfalse); + } + if (!cm_gridPlanes) + { + cm_gridPlanes = (int*)Z_Malloc(CM_MAX_GRID_SIZE*CM_MAX_GRID_SIZE*2*sizeof(int), TAG_TEMP_WORKSPACE, qfalse); + } +} + +void CM_PatchCollideFromGridTempDealloc() +{ + Z_Free(cm_gridPlanes); + Z_Free(cm_facets); + cm_gridPlanes = 0; + cm_facets = 0; +} + +/* +================== +CM_PatchCollideFromGrid +================== +*/ +int min1 = 0, max1 = 0, min2 = 0, max2 = 0; +static void CM_PatchCollideFromGrid( cGrid_t *grid, patchCollide_t *pf, + facetLoad_t *facetbuf, int *gridbuf ) { + int i, j; + float *p1, *p2, *p3; + int *gridPlanes; + facetLoad_t *facet; + int borders[4]; + int noAdjust[4]; + facetLoad_t *facets; + int numFacets; + + facets = cm_facets; + if (facets == 0) + { + facets = facetbuf; + } + gridPlanes = cm_gridPlanes; + if (gridPlanes == 0) + { + gridPlanes = gridbuf; + } + + numPlanes = 0; + numFacets = 0; + + // find the planes for each triangle of the grid + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p3 = grid->points[i+1][j+1]; + gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] = CM_FindPlane( p1, p2, p3 ); + + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j+1]; + p3 = grid->points[i][j]; + gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] = CM_FindPlane( p1, p2, p3 ); + } + } + + // create the borders for each facet + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + + borders[EN_TOP] = -1; + if ( j > 0 ) { + borders[EN_TOP] = gridPlanes[i*CM_MAX_GRID_SIZE*2+(j-1)*2+1]; + } else if ( grid->wrapHeight ) { + borders[EN_TOP] = gridPlanes[i*CM_MAX_GRID_SIZE*2+(grid->height-2)*2+1]; + } + noAdjust[EN_TOP] = ( borders[EN_TOP] == gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] ); + if ( borders[EN_TOP] == -1 || noAdjust[EN_TOP] ) { + borders[EN_TOP] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 0 ); + } + + borders[EN_BOTTOM] = -1; + if ( j < grid->height - 2 ) { + borders[EN_BOTTOM] = gridPlanes[i*CM_MAX_GRID_SIZE*2+(j+1)*2+0]; + } else if ( grid->wrapHeight ) { + borders[EN_BOTTOM] = gridPlanes[i*CM_MAX_GRID_SIZE*2+0*2+0]; + } + noAdjust[EN_BOTTOM] = ( borders[EN_BOTTOM] == gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] ); + if ( borders[EN_BOTTOM] == -1 || noAdjust[EN_BOTTOM] ) { + borders[EN_BOTTOM] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 2 ); + } + + borders[EN_LEFT] = -1; + if ( i > 0 ) { + borders[EN_LEFT] = gridPlanes[(i-1)*CM_MAX_GRID_SIZE*2+j*2+0]; + } else if ( grid->wrapWidth ) { + borders[EN_LEFT] = gridPlanes[(grid->width-2)*CM_MAX_GRID_SIZE*2+j*2+0]; + } + noAdjust[EN_LEFT] = ( borders[EN_LEFT] == gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] ); + if ( borders[EN_LEFT] == -1 || noAdjust[EN_LEFT] ) { + borders[EN_LEFT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 3 ); + } + + borders[EN_RIGHT] = -1; + if ( i < grid->width - 2 ) { + borders[EN_RIGHT] = gridPlanes[(i+1)*CM_MAX_GRID_SIZE*2+j*2+1]; + } else if ( grid->wrapWidth ) { + borders[EN_RIGHT] = gridPlanes[0*CM_MAX_GRID_SIZE*2+j*2+1]; + } + noAdjust[EN_RIGHT] = ( borders[EN_RIGHT] == gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] ); + if ( borders[EN_RIGHT] == -1 || noAdjust[EN_RIGHT] ) { + borders[EN_RIGHT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 1 ); + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + memset( facet, 0, sizeof( *facet ) ); + + if ( gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] == gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] ) { + if ( gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] == -1 ) { + continue; // degenrate + } + facet->surfacePlane = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0]; + facet->numBorders = 4; + facet->borderPlanes[0] = borders[EN_TOP]; + assert(borders[EN_TOP] > -32768 && borders[EN_TOP] < 32768); + facet->borderNoAdjust[0] = noAdjust[EN_TOP]; + assert(noAdjust[EN_TOP] >= 0 && noAdjust[EN_TOP] < 256); + facet->borderPlanes[1] = borders[EN_RIGHT]; + assert(borders[EN_RIGHT] > -32768 && borders[EN_RIGHT] < 32768); + facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; + assert(noAdjust[EN_RIGHT] >= 0 && noAdjust[EN_RIGHT] < 256); + facet->borderPlanes[2] = borders[EN_BOTTOM]; + assert(borders[EN_BOTTOM] > -32768 && + borders[EN_BOTTOM] < 32768); + facet->borderNoAdjust[2] = noAdjust[EN_BOTTOM]; + assert(noAdjust[EN_BOTTOM] >= 0 && noAdjust[EN_BOTTOM] < 256); + facet->borderPlanes[3] = borders[EN_LEFT]; + assert(borders[EN_LEFT] > -32768 && borders[EN_LEFT] < 32768); + facet->borderNoAdjust[3] = noAdjust[EN_LEFT]; + assert(noAdjust[EN_LEFT] >= 0 && noAdjust[EN_LEFT] < 256); + CM_SetBorderInward( facet, grid, i, j, -1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } else { + // two seperate triangles + facet->surfacePlane = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_TOP]; + assert(borders[EN_TOP] > -32768 && borders[EN_TOP] < 32768); + assert(noAdjust[EN_TOP] >= 0 && noAdjust[EN_TOP] < 256); + facet->borderNoAdjust[0] = noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + assert(borders[EN_RIGHT] > -32768 && borders[EN_RIGHT] < 32768); + facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; + assert(noAdjust[EN_RIGHT] >= 0 && noAdjust[EN_RIGHT] < 256); + facet->borderPlanes[2] = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1]; + assert(gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] > -32768 && + gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] < 32768); + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_BOTTOM]; + assert(borders[EN_BOTTOM] > -32768 && + borders[EN_BOTTOM] < 32768); + if ( facet->borderPlanes[2] == -1 ) { + int num = CM_EdgePlaneNum( grid, gridPlanes, i, j, 4 ); + assert(num > -32768 && num < 32768); + facet->borderPlanes[2] = num; + } + } + CM_SetBorderInward( facet, grid, i, j, 0 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + memset( facet, 0, sizeof( *facet ) ); + + facet->surfacePlane = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_BOTTOM]; + assert(borders[EN_BOTTOM] > -32768 && + borders[EN_BOTTOM] < 32768); + facet->borderNoAdjust[0] = noAdjust[EN_BOTTOM]; + assert(noAdjust[EN_BOTTOM] >= 0 && noAdjust[EN_BOTTOM] < 256); + facet->borderPlanes[1] = borders[EN_LEFT]; + assert(borders[EN_LEFT] > -32768 && borders[EN_LEFT] < 32768); + facet->borderNoAdjust[1] = noAdjust[EN_LEFT]; + assert(noAdjust[EN_LEFT] >= 0 && noAdjust[EN_LEFT] < 256); + facet->borderPlanes[2] = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0]; + assert(gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] > -32768 && + gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] < 32768); + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_TOP]; + assert(borders[EN_TOP] > -32768 && + borders[EN_TOP] < 32768); + if ( facet->borderPlanes[2] == -1 ) { + int num = CM_EdgePlaneNum( grid, gridPlanes, i, j, 5 ); + assert(num > -32768 && num < 32768); + facet->borderPlanes[2] = num; + } + } + CM_SetBorderInward( facet, grid, i, j, 1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } + } + } + + // copy the results out + pf->numPlanes = numPlanes; + pf->numFacets = numFacets; + if (numFacets) + { + pf->facets = (facet_t *) Z_Malloc( numFacets * sizeof( *pf->facets ), TAG_BSP, qfalse); + for(i=0; ifacets[i].data = (char*)Z_Malloc(facets[i].numBorders * 4, + TAG_BSP, qfalse); + pf->facets[i].surfacePlane = facets[i].surfacePlane; + pf->facets[i].numBorders = facets[i].numBorders; + short *bp = pf->facets[i].GetBorderPlanes(); + char *bi = pf->facets[i].GetBorderInward(); + char *bna = pf->facets[i].GetBorderNoAdjust(); + for(j=0; jfacets = 0; + } + pf->planes = (patchPlane_t *) Z_Malloc( numPlanes * sizeof( *pf->planes ), TAG_BSP, qfalse); + memcpy( pf->planes, planes, numPlanes * sizeof( *pf->planes ) ); +} + +static patchCollide_t *pfScratch = 0; +void CM_PreparePatchCollide(int num) +{ + pfScratch = (patchCollide_t *) Z_Malloc( sizeof( *pfScratch ) * num, TAG_BSP, qfalse ); +} + +cGrid_t *cm_grid = 0; +void CM_GridAlloc() +{ + if (cm_grid) return; + cm_grid = (cGrid_t*)Z_Malloc(sizeof(cGrid_t), TAG_TEMP_WORKSPACE, qfalse); +} + +void CM_GridDealloc() +{ + Z_Free(cm_grid); + cm_grid = 0; +} + +/* +=================== +CM_GeneratePatchCollide + +Creates an internal structure that will be used to perform +collision detection with a patch mesh. + +Points is packed as concatenated rows. +=================== +*/ +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points, + facetLoad_t *facetbuf, int *gridbuf ) { + patchCollide_t *pf; +// --AAA--AAA-- +// cGrid_t *grid = new cGrid_t; + cGrid_t *grid = cm_grid; +// --AAA--AAA-- + int i, j; + + memset(grid, 0, sizeof(cGrid_t)); + if ( width <= 2 || height <= 2 || !points ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: bad parameters: (%i, %i, %p)", + width, height, points ); + } + + if ( !(width & 1) || !(height & 1) ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: even sizes are invalid for quadratic meshes" ); + } + + if ( width > CM_MAX_GRID_SIZE || height > CM_MAX_GRID_SIZE ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: source is > CM_MAX_GRID_SIZE" ); + } + + // build a grid + grid->width = width; + grid->height = height; + grid->wrapWidth = qfalse; + grid->wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + VectorCopy( points[j*width + i], grid->points[i][j] ); + } + } + + // subdivide the grid + CM_SetGridWrapWidth( grid ); + CM_SubdivideGridColumns( grid ); + CM_RemoveDegenerateColumns( grid ); + + CM_TransposeGrid( grid ); + + CM_SetGridWrapWidth( grid ); + CM_SubdivideGridColumns( grid ); + CM_RemoveDegenerateColumns( grid ); + + // we now have a grid of points exactly on the curve + // the aproximate surface defined by these points will be + // collided against + + // --AAA--AAA-- +// pf = (patchCollide_t *) Z_Malloc( sizeof( *pf ), TAG_BSP, qfalse ); + pf = pfScratch++; + // --AAA--AAA-- + + ClearBounds( pf->bounds[0], pf->bounds[1] ); + for ( i = 0 ; i < grid->width ; i++ ) { + for ( j = 0 ; j < grid->height ; j++ ) { + AddPointToBounds( grid->points[i][j], pf->bounds[0], pf->bounds[1] ); + } + } + + c_totalPatchBlocks += ( grid->width - 1 ) * ( grid->height - 1 ); + + // generate a bsp tree for the surface + CM_PatchCollideFromGrid( grid, pf, facetbuf, gridbuf ); + + // expand by one unit for epsilon purposes + pf->bounds[0][0] -= 1; + pf->bounds[0][1] -= 1; + pf->bounds[0][2] -= 1; + + pf->bounds[1][0] += 1; + pf->bounds[1][1] += 1; + pf->bounds[1][2] += 1; + +// --AAA--AAA-- +// delete grid; +// --AAA--AAA-- + + return pf; +} + + +/* +================================================================================ + +TRACE TESTING + +================================================================================ +*/ + + +/* +==================== +CM_TracePointThroughPatchCollide + + special case for point traces because the patch collide "brushes" have no volume +==================== +*/ +void CM_TracePointThroughPatchCollide( traceWork_t *tw, trace_t &trace, const struct patchCollide_s *pc ) { + qboolean frontFacing[MAX_PATCH_PLANES]; + float intersection[MAX_PATCH_PLANES]; + float intersect; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d1, d2; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + +#ifndef BSPC + if ( !cm_playerCurveClip->integer && !tw->isPoint ) { + return; // FIXME: until I get player sized clipping working right + } +#endif + + if (!pc->numFacets) + { //not gonna do anything anyhow? + return; + } + // determine the trace's relationship to all planes + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + if ( d1 <= 0 ) { + frontFacing[i] = qfalse; + } else { + frontFacing[i] = qtrue; + } + if ( d1 == d2 ) { + intersection[i] = WORLD_SIZE; + } else { + intersection[i] = d1 / ( d1 - d2 ); + if ( intersection[i] <= 0 ) { + intersection[i] = WORLD_SIZE; + } + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + if ( !frontFacing[facet->surfacePlane] ) { + continue; + } + intersect = intersection[facet->surfacePlane]; + if ( intersect < 0 ) { + continue; // surface is behind the starting point + } + if ( intersect > trace.fraction ) { + continue; // already hit something closer + } + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->GetBorderPlanes()[j]; + if ( frontFacing[k] ^ facet->GetBorderInward()[j] ) { + if ( intersection[k] > intersect ) { + break; + } + } else { + if ( intersection[k] < intersect ) { + break; + } + } + } + if ( j == facet->numBorders ) { + // we hit this facet +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif //BSPC + planes = &pc->planes[facet->surfacePlane]; + + // calculate intersection with a slight pushoff + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + trace.fraction = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if ( trace.fraction < 0 ) { + trace.fraction = 0; + } + + VectorCopy( planes->plane, trace.plane.normal ); + trace.plane.dist = planes->plane[3]; + } + } +} + +/* +==================== +CM_CheckFacetPlane +==================== +*/ +int CM_CheckFacetPlane(float *plane, vec3_t start, vec3_t end, float *enterFrac, float *leaveFrac, int *hit) { + float d1, d2, f; + + *hit = qfalse; + + d1 = DotProduct( start, plane ) - plane[3]; + d2 = DotProduct( end, plane ) - plane[3]; + + // if completely in front of face, no intersection with the entire facet + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return qfalse; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + return qtrue; + } + + // crosses face + if (d1 > d2) { // enter + f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f < 0 ) { + f = 0; + } + //always favor previous plane hits and thus also the surface plane hit + if (f > *enterFrac) { + *enterFrac = f; + *hit = qtrue; + } + } else { // leave + f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f > 1 ) { + f = 1; + } + if (f < *leaveFrac) { + *leaveFrac = f; + } + } + return qtrue; +} + +/* +==================== +CM_TraceThroughPatchCollide +==================== +*/ +void CM_TraceThroughPatchCollide( traceWork_t *tw, trace_t &trace, const struct patchCollide_s *pc ) +{ + int i, j, hit, hitnum; + float offset, enterFrac, leaveFrac, t; + patchPlane_t *planes; + facet_t *facet; + float plane[4], bestplane[4]; + vec3_t startp, endp; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + +#ifndef CULL_BBOX + // I'm not sure if test is strictly correct. Are all + // bboxes axis aligned? Do I care? It seems to work + // good enough... + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw->bounds[0][i] > pc->bounds[1][i] + || tw->bounds[1][i] < pc->bounds[0][i] ) { + return; + } + } +#endif + + if (tw->isPoint) { + + CM_TracePointThroughPatchCollide( tw, trace, pc ); + return; + } +#ifndef ADDBEVELS + CM_TracePointThroughPatchCollide( tw, pc ); + return; +#endif + // + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + enterFrac = -1.0; + leaveFrac = 1.0; + hitnum = -1; + // + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] -= offset; + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { + continue; + } + if (hit) { + Vector4Copy(plane, bestplane); + } + for ( j = 0 ; j < facet->numBorders ; j++ ) { + planes = &pc->planes[ facet->GetBorderPlanes()[j] ]; + if (facet->GetBorderInward()[j]) { + VectorNegate(planes->plane, plane); + plane[3] = -planes->plane[3]; + } + else { + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + } + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + // NOTE: this works even though the plane might be flipped because the bbox is centered + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] += Q_fabs(offset); + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { + break; + } + if (hit) { + hitnum = j; + Vector4Copy(plane, bestplane); + } + } + if (j < facet->numBorders) continue; + //never clip against the back side + if (hitnum == facet->numBorders - 1) continue; + if (enterFrac < leaveFrac && enterFrac >= 0) { + if (enterFrac < trace.fraction) { + if (enterFrac < 0) { + enterFrac = 0; + } +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv && cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif // BSPC + + trace.fraction = enterFrac; + VectorCopy( bestplane, trace.plane.normal ); + trace.plane.dist = bestplane[3]; + } + } + } +} + +/* +======================================================================= + +POSITION TEST + +======================================================================= +*/ + +#define BOX_FRONT 0 +#define BOX_BACK 1 +#define BOX_CROSS 2 + +/* +==================== +CM_PositionTestInPatchCollide + +Modifies tr->tr if any of the facets effect the trace +==================== +*/ +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + int cross[MAX_PATCH_PLANES]; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d; + +//return qfalse; + +#ifndef CULL_BBOX + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw->bounds[0][i] > pc->bounds[1][i] + || tw->bounds[1][i] < pc->bounds[0][i] ) { + return qfalse; + } + } +#endif + + // determine if the box is in front, behind, or crossing each plane + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + d = DotProduct( tw->start, planes->plane ) - planes->plane[3]; + offset = Q_fabs( DotProduct( tw->offsets[ planes->signbits ], planes->plane ) ); + if ( d < -offset ) { + cross[i] = BOX_FRONT; + } else if ( d > offset ) { + cross[i] = BOX_BACK; + } else { + cross[i] = BOX_CROSS; + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + // the facet plane must be in a cross state + if ( cross[facet->surfacePlane] != BOX_CROSS ) { + continue; + } + // all of the boundaries must be either cross or back + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->GetBorderPlanes()[j]; + if ( cross[ k ] == BOX_CROSS ) { + continue; + } + if ( cross[k] ^ facet->GetBorderInward()[j] ) { + break; + } + } + // if we passed all borders, we are definately in this facet + if ( j == facet->numBorders ) { + return qtrue; + } + } + + return qfalse; +} + + +/* +======================================================================= + +DEBUGGING + +======================================================================= +*/ + + +/* +================== +CM_DrawDebugSurface + +Called from the renderer +================== +*/ +#ifndef BSPC +void BotDrawDebugPolygons(void (*drawPoly)(int color, int numPoints, float *points), int value); +#endif + +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ) { +} \ No newline at end of file diff --git a/codemp/qcommon/cm_polylib.cpp b/codemp/qcommon/cm_polylib.cpp new file mode 100644 index 0000000..81c99d1 --- /dev/null +++ b/codemp/qcommon/cm_polylib.cpp @@ -0,0 +1,713 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// this is only used for visualization tools in cm_ debug functions + + +#include "cm_local.h" + + +// counters are only bumped when running single threaded, +// because they are an awefull coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + + s = sizeof(vec_t)*3*points + sizeof(int); + w = (winding_t *)Z_Malloc (s, TAG_BSP, qtrue); +// Com_Memset (w, 0, s); // qtrue param in Z_Malloc does this + return w; +} + +void FreeWinding (winding_t *w) +{ + if (*(unsigned *)w == 0xdeaddead) + Com_Error (ERR_FATAL, "FreeWinding: freed a freed winding"); + *(unsigned *)w = 0xdeaddead; + + c_active_windings--; + Z_Free (w); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize2(v1,v1); + VectorNormalize2(v2,v2); + if (DotProduct(v1, v2) < 0.999) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + c_removed += w->numpoints - nump; + w->numpoints = nump; + Com_Memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ + vec3_t v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + VectorSubtract (w->p[2], w->p[0], v2); + CrossProduct (v2, v1, normal); + VectorNormalize2(normal, normal); + *dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea (winding_t *w) +{ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += 0.5 * VectorLength ( cross ); + } + return total; +} + +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = MAX_MAP_BOUNDS; + maxs[0] = maxs[1] = maxs[2] = -MAX_MAP_BOUNDS; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, vec3_t center) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist) +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -MAX_MAP_BOUNDS; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Com_Error (ERR_DROP, "BaseWindingForPlane: no axis found"); + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize2(vup, vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, MAX_MAP_BOUNDS, vup); + VectorScale (vright, MAX_MAP_BOUNDS, vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + c = AllocWinding (w->numpoints); + size = (int)((winding_t *)0)->p[w->numpoints]; + Com_Memcpy (c, w, size); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding (winding_t *w) +{ + int i; + winding_t *c; + + c = AllocWinding (w->numpoints); + for (i=0 ; inumpoints ; i++) + { + VectorCopy (w->p[w->numpoints-1-i], c->p[i]); + } + c->numpoints = w->numpoints; + return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + FreeWinding (in); + *inout = NULL; + return; + } + if (!counts[1]) + return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING"); + + FreeWinding (in); + *inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist) +{ + winding_t *f, *b; + + ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); + FreeWinding (in); + if (b) + FreeWinding (b); + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Com_Error (ERR_DROP, "CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Com_Error (ERR_DROP, "CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > MAX_MAP_BOUNDS || p1[j] < -MAX_MAP_BOUNDS) + Com_Error (ERR_DROP, "CheckFace: BUGUS_RANGE: %f",p1[j]); + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Com_Error (ERR_DROP, "CheckWinding: point off plane"); + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Com_Error (ERR_DROP, "CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize2 (edgenormal, edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Com_Error (ERR_DROP, "CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = qfalse; + back = qfalse; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = qtrue; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = qtrue; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + + +/* +================= +AddWindingToConvexHull + +Both w and *hull are on the same plane +================= +*/ +#define MAX_HULL_POINTS 128 +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) { + int i, j, k; + float *p, *copy; + vec3_t dir; + float d; + int numHullPoints, numNew; + vec3_t hullPoints[MAX_HULL_POINTS]; + vec3_t newHullPoints[MAX_HULL_POINTS]; + vec3_t hullDirs[MAX_HULL_POINTS]; + qboolean hullSide[MAX_HULL_POINTS]; + qboolean outside; + + if ( !*hull ) { + *hull = CopyWinding( w ); + return; + } + + numHullPoints = (*hull)->numpoints; + Com_Memcpy( hullPoints, (*hull)->p, numHullPoints * sizeof(vec3_t) ); + + for ( i = 0 ; i < w->numpoints ; i++ ) { + p = w->p[i]; + + // calculate hull side vectors + for ( j = 0 ; j < numHullPoints ; j++ ) { + k = ( j + 1 ) % numHullPoints; + + VectorSubtract( hullPoints[k], hullPoints[j], dir ); + VectorNormalize2( dir, dir ); + CrossProduct( normal, dir, hullDirs[j] ); + } + + outside = qfalse; + for ( j = 0 ; j < numHullPoints ; j++ ) { + VectorSubtract( p, hullPoints[j], dir ); + d = DotProduct( dir, hullDirs[j] ); + if ( d >= ON_EPSILON ) { + outside = qtrue; + } + if ( d >= -ON_EPSILON ) { + hullSide[j] = qtrue; + } else { + hullSide[j] = qfalse; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + continue; + } + + // find the back side to front side transition + for ( j = 0 ; j < numHullPoints ; j++ ) { + if ( !hullSide[ j % numHullPoints ] && hullSide[ (j + 1) % numHullPoints ] ) { + break; + } + } + if ( j == numHullPoints ) { + continue; + } + + // insert the point here + VectorCopy( p, newHullPoints[0] ); + numNew = 1; + + // copy over all points that aren't double fronts + j = (j+1)%numHullPoints; + for ( k = 0 ; k < numHullPoints ; k++ ) { + if ( hullSide[ (j+k) % numHullPoints ] && hullSide[ (j+k+1) % numHullPoints ] ) { + continue; + } + copy = hullPoints[ (j+k+1) % numHullPoints ]; + VectorCopy( copy, newHullPoints[numNew] ); + numNew++; + } + + numHullPoints = numNew; + Com_Memcpy( hullPoints, newHullPoints, numHullPoints * sizeof(vec3_t) ); + } + + FreeWinding( *hull ); + w = AllocWinding( numHullPoints ); + w->numpoints = numHullPoints; + *hull = w; + Com_Memcpy( w->p, hullPoints, numHullPoints * sizeof(vec3_t) ); +} + + diff --git a/codemp/qcommon/cm_polylib.h b/codemp/qcommon/cm_polylib.h new file mode 100644 index 0000000..af182e9 --- /dev/null +++ b/codemp/qcommon/cm_polylib.h @@ -0,0 +1,47 @@ + +// this is only used for visualization tools in cm_ debug functions + +typedef struct +{ + int numpoints; + vec3_t p[4]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 64 + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +#define CLIP_EPSILON 0.1f + +#define MAX_MAP_BOUNDS 65535 + +// you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1f +#endif + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, vec3_t center); +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back); +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *ReverseWinding (winding_t *w); +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); + +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ); + +void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); +// frees the original if clipped + +void pw(winding_t *w); diff --git a/codemp/qcommon/cm_public.h b/codemp/qcommon/cm_public.h new file mode 100644 index 0000000..e0d4eb7 --- /dev/null +++ b/codemp/qcommon/cm_public.h @@ -0,0 +1,74 @@ +#include "../game/q_shared.h" +#include "qfiles.h" + +#ifdef _XBOX +void CM_LoadMap( const char *name, qboolean clientload, int *checksum); +#else +void CM_LoadMap( const char *name, qboolean clientload, int *checksum); +#endif + +void CM_ClearMap( void ); +clipHandle_t CM_InlineModel( int index ); // 0 = world, 1 + are bmodels +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int capsule ); + +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); + +int CM_NumClusters (void); +int CM_NumInlineModels( void ); +char *CM_EntityString (void); + +// returns an ORed contents mask +int CM_PointContents( const vec3_t p, clipHandle_t model ); +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); + +void CM_BoxTrace ( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, int capsule ); +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles, int capsule ); + +#ifdef _XBOX +const byte *CM_ClusterPVS (int cluster); +#else +byte *CM_ClusterPVS (int cluster); +#endif + +int CM_PointLeafnum( const vec3_t p ); + +// only returns non-solid leafs +// overflow if return listsize and if *lastLeaf != list[listsize-1] +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *boxList, + int listsize, int *lastLeaf ); +//rwwRMG - changed to boxList to not conflict with list type + +int CM_LeafCluster (int leafnum); +int CM_LeafArea (int leafnum); + +void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ); +qboolean CM_AreasConnected( int area1, int area2 ); + +int CM_WriteAreaBits( byte *buffer, int area ); + +//rwwRMG - added: +bool CM_GenericBoxCollide(const vec3pair_t abounds, const vec3pair_t bbounds); +void CM_HandlePatchCollision(struct traceWork_s *tw, trace_t &trace, const vec3_t tStart, const vec3_t tEnd, class CCMPatch *patch, int checkcount); +void CM_CalcExtents(const vec3_t start, const vec3_t end, const struct traceWork_s *tw, vec3pair_t bounds); + +// cm_tag.c +int CM_LerpTag( orientation_t *tag, clipHandle_t model, int startFrame, int endFrame, + float frac, const char *tagName ); + + +// cm_marks.c +int CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + +// cm_patch.c +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ); + +// cm_shader.cpp +const char *CM_GetShaderText(const char *key); +void CM_FreeShaderText(void); +void CM_LoadShaderText(qboolean forceReload); diff --git a/codemp/qcommon/cm_randomterrain.cpp b/codemp/qcommon/cm_randomterrain.cpp new file mode 100644 index 0000000..a6596e6 --- /dev/null +++ b/codemp/qcommon/cm_randomterrain.cpp @@ -0,0 +1,1091 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "cm_local.h" +#include "cm_patch.h" +#include "cm_landscape.h" +#include "../qcommon/GenericParser2.h" +#include "cm_randomterrain.h" + + +#define NOISE_SIZE 256 +#define NOISE_MASK (NOISE_SIZE - 1) + +static float noiseTable[NOISE_SIZE]; +static int noisePerm[NOISE_SIZE]; + +#if 0 +static void CM_NoiseInit( CCMLandScape *landscape ) +{ + int i; + + for ( i = 0; i < NOISE_SIZE; i++ ) + { + noiseTable[i] = landscape->flrand(-1.0f, 1.0f); + noisePerm[i] = (byte)landscape->irand(0, 255); + } +} +#endif + +#define VAL( a ) noisePerm[ ( a ) & ( NOISE_MASK )] +#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) ) + +#define LERP( a, b, w ) ( a * ( 1.0f - w ) + b * w ) + +static float GetNoiseValue( int x, int y, int z, int t ) +{ + int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t ); + + return noiseTable[index]; +} + +#if 0 +static float GetNoiseTime( int t ) +{ + int index = VAL( t ); + + return (1 + noiseTable[index]); +} +#endif + +static float CM_NoiseGet4f( float x, float y, float z, float t ) +{ + int i; + int ix, iy, iz, it; + float fx, fy, fz, ft; + float front[4]; + float back[4]; + float fvalue, bvalue, value[2], finalvalue; + + ix = ( int ) floor( x ); + fx = x - ix; + iy = ( int ) floor( y ); + fy = y - iy; + iz = ( int ) floor( z ); + fz = z - iz; + it = ( int ) floor( t ); + ft = t - it; + + for ( i = 0; i < 2; i++ ) + { + front[0] = GetNoiseValue( ix, iy, iz, it + i ); + front[1] = GetNoiseValue( ix+1, iy, iz, it + i ); + front[2] = GetNoiseValue( ix, iy+1, iz, it + i ); + front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i ); + + back[0] = GetNoiseValue( ix, iy, iz + 1, it + i ); + back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i ); + back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i ); + back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i ); + + fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy ); + bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy ); + + value[i] = LERP( fvalue, bvalue, fz ); + } + + finalvalue = LERP( value[0], value[1], ft ); + return finalvalue; +} + + + + + + + + +/****** lincrv.c ******/ +/* Ken Shoemake, 1994 */ + +/* Perform a generic vector unary operation. */ +#define V_Op(vdst,gets,vsrc,n) {register int V_i;\ + for(V_i=(n)-1;V_i>=0;V_i--) (vdst)[V_i] gets ((vsrc)[V_i]);} + +static void lerp(float t, float a0, float a1, vec4_t p0, vec4_t p1, int m, vec4_t p) +{ + register float t0=(a1-t)/(a1-a0), t1=1-t0; + register int i; + for (i=m-1; i>=0; i--) p[i] = t0*p0[i] + t1*p1[i]; +} + +/* DialASpline(t,a,p,m,n,work,Cn,interp,val) computes a point val at parameter + t on a spline with knot values a and control points p. The curve will have + Cn continuity, and if interp is TRUE it will interpolate the control points. + Possibilities include Langrange interpolants, Bezier curves, Catmull-Rom + interpolating splines, and B-spline curves. Points have m coordinates, and + n+1 of them are provided. The work array must have room for n+1 points. + */ +static int DialASpline(float t, float a[], vec4_t p[], int m, int n, vec4_t work[], + unsigned int Cn, bool interp, vec4_t val) +{ + register int i, j, k, h, lo, hi; + + if (Cn>n-1) Cn = n-1; /* Anything greater gives one polynomial */ + for (k=0; t> a[k]; k++); /* Find enclosing knot interval */ + for (h=k; t==a[k]; k++); /* May want to use fewer legs */ + if (k>n) {k = n; if (h>k) h = k;} + h = 1+Cn - (k-h); k--; + lo = k-Cn; hi = k+1+Cn; + + if (interp) { /* Lagrange interpolation steps */ + int drop=0; + if (lo<0) {lo = 0; drop += Cn-k; + if (hi-lon) {hi = n; drop += k+1+Cn-n; + if (hi-lo=j; i--) { + lerp(t,a[lo+i],a[lo+i+tmp],work[lo+i],work[lo+i+1],m,work[lo+i+1]); + } + } + V_Op(val,=,work[lo+h],m); + return (k); +} + +#define BIG (1.0e12) + +static vec_t Vector2Normalize( vec2_t v ) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1]; + length = sqrt (length); + + if ( length ) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + } + + return length; +} + +CPathInfo::CPathInfo(CCMLandScape *landscape, int numPoints, float bx, float by, float ex, float ey, + float minWidth, float maxWidth, float depth, float deviation, float breadth, + CPathInfo *Connected, unsigned CreationFlags) : + mNumPoints(numPoints), + mMinWidth(minWidth), + mMaxWidth(maxWidth), + mDepth(depth), + mDeviation(deviation), + mBreadth(breadth) +{ + int i, numConnected, index; + float position, goal, deltaGoal; +// float random, delta; + bool horizontal; + float *point; + float currentWidth; + float currentPosition; + vec2_t testPoint, percPoint, diffPoint, normalizedPath; + float distance, length; + + CreateCircle(); + + numConnected = -1; + if (Connected) + { // we are connecting to an existing spline + numConnected = Connected->GetNumPoints(); + if (numConnected >= SPLINE_MERGE_SIZE) + { // plenty of points to choose from + mNumPoints += SPLINE_MERGE_SIZE; + } + else + { // the existing spline doesn't have enough points + mNumPoints += numConnected; + } + } + + mPoints = (vec4_t *)malloc(sizeof(vec4_t) * mNumPoints); + mWork = (vec4_t *)malloc(sizeof(vec4_t) * (mNumPoints+1)); + mWeights = (vec_t *)malloc(sizeof(vec_t) * (mNumPoints+1)); + + length = sqrt((ex-bx)*(ex-bx) + (ey-by)*(ey-by)); + if (fabs(ex - bx) >= fabs(ey - by)) + { // this appears to be a horizontal path + mInc = 1.0 / fabs(ex - bx); + horizontal = true; + position = by; + goal = ey; + deltaGoal = (ey-by) / (numPoints-1); + } + else + { // this appears to be a vertical path + mInc = 1.0 / fabs(ey - by); + horizontal = false; + position = bx; + goal = ex; + deltaGoal = (ex-bx) / (numPoints-1); + } + normalizedPath[0] = (ex-bx); + normalizedPath[1] = (ey-by); + Vector2Normalize(normalizedPath); + // approx calculate how much we need to iterate through the spline to hit every point + mInc /= 16; + + currentWidth = landscape->flrand(minWidth, maxWidth); + currentPosition = 0.0; + + for(i=0;iGetPoint(index); + mPoints[i][0] = point[0]; + mPoints[i][1] = point[1]; + mPoints[i][3] = point[3]; + } + else + { + if (horizontal) + { // we appear to be going horizontal, so spread the randomness across the vertical + mPoints[i][0] = ((ex - bx) * currentPosition) + bx; + mPoints[i][1] = position; + } + else + { // we appear to be going vertical, so spread the randomness across the horizontal + mPoints[i][0] = position; + mPoints[i][1] = ((ey - by) * currentPosition) + by; + } + currentPosition += 1.0 / (numPoints-1); + + // set the width of the spline + mPoints[i][3] = currentWidth; + currentWidth += landscape->flrand(-0.10, 0.10); + if (currentWidth < minWidth) + { + currentWidth = minWidth; + } + else if (currentWidth > maxWidth) + { + currentWidth = maxWidth; + } + + // see how far we are from the goal +/* delta = (goal - position) * currentPosition; + // calculate the randomness we are allowed at this place + random = landscape->flrand(-mDeviation/1.0, mDeviation/1.0) * (1.0 - currentPosition); + position += delta + random;*/ + + if (i == mNumPoints-2) + { // -2 because we are calculating for the next point + position = goal; + } + else + { + if (i == 0) + { + position += deltaGoal + landscape->flrand(-mDeviation/10.0, mDeviation/10.0); + } + else + { + position += deltaGoal + landscape->flrand(-mDeviation*1.5, mDeviation*1.5); + } + } + + + if (position > 0.9) + { // too far over, so move back a bit + position = 0.9 - landscape->flrand(0.02, 0.1); + } + if (position < 0.1) + { // too near, so move bakc a bit + position = 0.1 + landscape->flrand(0.02, 0.1); + } + + // check our deviation from the straight line to the end + if (horizontal) + { + testPoint[0] = ((ex - bx) * currentPosition) + bx; + testPoint[1] = position; + } + else + { + testPoint[0] = position; + testPoint[1] = ((ey - by) * currentPosition) + by; + } + // dot product of the normal of the path to the point we are at + distance = ((testPoint[0]-bx)*normalizedPath[0]) + ((testPoint[1]-by)*normalizedPath[1]); + // find the perpendicular place that is intersected by the point and the path + percPoint[0] = (distance * normalizedPath[0]) + bx; + percPoint[1] = (distance * normalizedPath[1]) + by; + // calculate the difference between the perpendicular point and the test point + diffPoint[0] = testPoint[0] - percPoint[0]; + diffPoint[1] = testPoint[1] - percPoint[1]; + // calculate the distance + distance = sqrt((diffPoint[0]*diffPoint[0]) + (diffPoint[1]*diffPoint[1])); + if (distance > mDeviation) + { // we are beyond our allowed deviation, so head back + if (horizontal) + { + position = (ey-by) * currentPosition + by; + } + else + { + position = (ex-bx) * currentPosition + bx; + } + position += landscape->flrand(-mDeviation/2.0, mDeviation/2.0); + } + } + } + mWeights[mNumPoints] = BIG; +} + +CPathInfo::~CPathInfo(void) +{ + free(mWeights); + free(mWork); + free(mPoints); +} + +void CPathInfo::CreateCircle(void) +{ + int x, y; + float r, d; + + memset(mCircleStamp, 0, sizeof(mCircleStamp)); + r = CIRCLE_STAMP_SIZE; + for(x=0;x r) + { + mCircleStamp[y][x] = 255; + } + else + { + mCircleStamp[y][x] = pow(sin(d / r * M_PI / 2), mBreadth) * 255; + } + } + } +} + +void CPathInfo::Stamp(int x, int y, int size, int depth, unsigned char *Data, int DataWidth, int DataHeight) +{ +// int xPos; +// float yPos; + int dx, dy, fx, fy; + float offset; + byte value; + byte invDepth; + + offset = (float)(CIRCLE_STAMP_SIZE-1) / size; + invDepth = 255-depth; + + for(dx = -size; dx <= size; dx++) + { + for ( dy = -size; dy <= size; dy ++ ) + { + float d; + + d = dx * dx + dy * dy ; + if ( d > size * size ) + { + continue; + } + + fx = x + dx; + if (fx < 2 || fx > DataWidth-2) + { + continue; + } + + fy = y + dy; + if (fy < 2 || fy > DataHeight-2) + { + continue; + } + + value = pow ( sin ( d / (size * size) * M_PI / 2), mBreadth ) * invDepth + depth; + if (value < Data[(fy * DataWidth) + fx]) + { + Data[(fy * DataWidth) + fx] = value; + } + } + } +/* + + fx = x + dx; + if (fx < 2 || fx > DataWidth-2) + { + continue; + } + xPos = abs((int)(dx*offset)); + yPos = offset*size + offset; + for(dy = -size; dy < 0; dy++) + { + yPos -= offset; + fy = y + dy; + if (fy < 2 || fy > DataHeight-2) + { + continue; + } + + value = (invDepth * mCircleStamp[(int)yPos][xPos] / 256) + depth; + if (value < Data[(fy * DataWidth) + fx]) + { + Data[(fy * DataWidth) + fx] = value; + } + } + + yPos = -offset; + for(; dy <= size; dy++) + { + yPos += offset; + + fy = y + dy; + if (fy < 2 || fy > DataHeight-2) + { + continue; + } + + value = (invDepth * mCircleStamp[(int)yPos][xPos] / 256) + depth; + if (value < Data[(fy * DataWidth) + fx]) + { + Data[(fy * DataWidth) + fx] = value; + } + } + } +*/ +} + +void CPathInfo::GetInfo(float PercentInto, vec4_t Coord, vec4_t Vector) +{ + vec4_t before, after; + float testPercent; + + DialASpline(PercentInto, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, Coord); + + testPercent = PercentInto - 0.01; + if (testPercent < 0) + { + testPercent = 0; + } + DialASpline(testPercent, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, before); + + testPercent = PercentInto + 0.01; + if (testPercent > 1.0) + { + testPercent = 1.0; + } + DialASpline(testPercent, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, after); + + Coord[2] = mDepth; + + Vector[0] = after[0] - before[0]; + Vector[1] = after[1] - before[1]; +} + +void CPathInfo::DrawPath(unsigned char *Data, int DataWidth, int DataHeight ) +{ + float t; + vec4_t val, vector;//, perp; + int size; + float inc; + int x, y, lastX, lastY; + float depth; + + inc = mInc / DataWidth; + + lastX = lastY = -999; + + for (t=0.0; t<=1.0; t+=inc) + { + GetInfo(t, val, vector); + +/* perp[0] = -vector[1]; + perp[1] = vector[0]; + + if (fabs(perp[0]) > fabs(perp[1])) + { + perp[1] /= fabs(perp[0]); + perp[0] /= fabs(perp[0]); + } + else + { + perp[0] /= fabs(perp[1]); + perp[1] /= fabs(perp[1]); + } +*/ + x = val[0] * DataWidth; + y = val[1] * DataHeight; + + if (x == lastX && y == lastY) + { + continue; + } + + lastX = x; + lastY = y; + + size = val[3] * DataWidth; + + depth = mDepth * 255.0f; + + Stamp(x, y, size, (int)depth, Data, DataWidth, DataHeight); + } +} + + + + + + + +CRandomTerrain::CRandomTerrain(void) +{ + memset(mPaths, 0, sizeof(mPaths)); +} + +CRandomTerrain::~CRandomTerrain(void) +{ + Shutdown(); +} + +void CRandomTerrain::Init(CCMLandScape *landscape, byte *grid, int width, int height) +{ + Shutdown(); + mLandScape = landscape; + mWidth = width; + mHeight = height; + mArea = mWidth * mHeight; + mBorder = (width + height) >> 6; + mGrid = grid; +} + +void CRandomTerrain::ClearPaths(void) +{ + int i; + + for(i=0;i= MAX_RANDOM_PATHS || mPaths[PathID]) + { + return false; + } + + if (ConnectedID >= 0 && ConnectedID < MAX_RANDOM_PATHS) + { + connected = mPaths[ConnectedID]; + } + + mPaths[PathID] = new CPathInfo(mLandScape, numPoints, bx, by, ex, ey, + minWidth, maxWidth, depth, deviation, breadth, + connected, CreationFlags ); + + return true; +} + +bool CRandomTerrain::GetPathInfo(int PathNum, float PercentInto, vec4_t Coord, vec4_t Vector) +{ + if (PathNum < 0 || PathNum >= MAX_RANDOM_PATHS || !mPaths[PathNum]) + { + return false; + } + + mPaths[PathNum]->GetInfo(PercentInto, Coord, Vector); + + return true; +} + +void CRandomTerrain::ParseGenerate(const char *GenerateFile) +{ +} + +void CRandomTerrain::Smooth ( void ) +{ + // Scale down to 1/4 size then back up to smooth out the terrain + byte *temp; + int x, y, o; + + temp = mLandScape->GetFlattenMap ( ); + + // Copy over anything in the flatten map + for ( o = 0; o < mHeight * mWidth; o++) + { + if ( temp[o] > 0 ) + { + mGrid[o] = (byte)temp[o] & 0x7F; + } + } + + temp = (byte *)Z_Malloc(mWidth * mHeight, TAG_RESAMPLE); +#if 1 + unsigned total, count; + for(x=1;x> 1, mHeight >> 1, 1); + R_Resample(temp, mWidth >> 1, mHeight >> 1, mGrid, mWidth, mHeight, 1); + + // now lets filter it. + memcpy(temp, mGrid, mWidth * mHeight); + + for (dy = -KERNEL_SIZE; dy <= KERNEL_SIZE; dy++) + { + for (dx = -KERNEL_SIZE; dx <= KERNEL_SIZE; dx++) + { + smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE] = + 1.0f / (1.0f + fabs(float(dx) * float(dx) * float(dx)) + fabs(float(dy) * float(dy) * float(dy))); + } + } + + for (y = 0; y < mHeight; y++) + { + for (x = 0; x < mWidth; x++) + { + total = 0.0f; + num = 0.0f; + for (dy = -KERNEL_SIZE; dy <= KERNEL_SIZE; dy++) + { + for (dx = -KERNEL_SIZE; dx <= KERNEL_SIZE; dx++) + { + xx = x + dx; + if (xx >= 0 && xx < mWidth) + { + yy = y + dy; + if (yy >= 0 && yy < mHeight) + { + total += smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE] * (float)temp[yy * mWidth + xx]; + num += smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE]; + } + } + } + } + total /= num; + mGrid[y * mWidth + x] = (byte)Com_Clamp(0, 255, (int)Round(total)); + } + } +#endif + + Z_Free(temp); + +/* Uncomment to see the symmetry line on the map + + for ( x = 0; x < mWidth; x ++ ) + { + mGrid[x * mWidth + x] = 255; + } +*/ +} + +void CRandomTerrain::Generate(int symmetric) +{ + int i,j; + int x, y; + + // Clear out all existing data + memset(mGrid, 255, mArea); + + // make landscape a little bumpy + float t1 = mLandScape->flrand(0, 2); +#if 0 + float t2 = mLandScape->flrand(0, 2); + float t3 = mLandScape->flrand(0, 2); +#endif + +/* + CM_NoiseInit(mLandScape); + + for (y = 0; y < mHeight; y++) + for (x = 0; x < mWidth; x++) + { + i = x + y*mWidth; + byte val = (byte)Com_Clamp(0, 255, (int)(220 + (CM_NoiseGet4f( x*0.25, y*0.25, 0, t3 ) * 20)) + (CM_NoiseGet4f( x*0.5, y*0.5, 0, t2 ) * 15)); + mGrid[i] = val; + } +*/ + + for ( i = 0; mPaths[i] != 0; i ++ ) + { + mPaths[i]->DrawPath(mGrid, mWidth, mHeight); + } + + for (y = 0; y < mHeight; y++) + for (x = 0; x < mWidth; x++) + { + i = x + y*mWidth; + byte val = (byte)Com_Clamp(0, 255, (int)(mGrid[i] + (CM_NoiseGet4f( x, y, 0, t1 ) * 5))); + mGrid[i] = val; + } + + // if symmetric, do this now + if (symmetric) + { + assert (mWidth == mHeight); // must be square + + for (y = 0; y < mHeight; y++) + for (x = 0; x < (mWidth-y); x++) + { + i = x + y*mWidth; + j = (mWidth-1 - x) + (mHeight-1 - y)*mWidth; + byte val = mGrid[i] < mGrid[j] ? mGrid[i] : mGrid[j]; + mGrid[i] = mGrid[j] = val; + } + } +} + + + + +typedef enum +{ + CP_NONE = -1, + CP_CONSONANT, + CP_COMPLEX_CONSONANT, + CP_VOWEL, + CP_COMPLEX_VOWEL, + CP_ENDING, + + CP_NUM_PIECES, +} ECPType; + +typedef struct SCharacterPiece +{ + char *mPiece; + int mCommonality; +} TCharacterPiece; + +static TCharacterPiece Consonants[] = +{ + { "b", 6 }, + { "c", 8 }, + { "d", 6 }, + { "f", 5 }, + { "g", 4 }, + { "h", 5 }, + { "j", 2 }, + { "k", 4 }, + { "l", 4 }, + { "m", 7 }, + { "n", 7 }, + { "r", 6 }, + { "s", 10 }, + { "t", 10 }, + { "v", 1 }, + { "w", 2 }, + { "x", 1 }, + { "z", 1 }, + + { 0, 0 } +}; + +static TCharacterPiece ComplexConsonants[] = +{ + { "st", 10 }, + { "ck", 10 }, + { "ss", 10 }, + { "tt", 7 }, + { "ll", 8 }, + { "nd", 10 }, + { "rn", 6 }, + { "nc", 6 }, + { "mp", 4 }, + { "sc", 10 }, + { "sl", 10 }, + { "tch", 6 }, + { "th", 4 }, + { "rn", 5 }, + { "cl", 10 }, + { "sp", 10 }, + { "st", 10 }, + { "fl", 4 }, + { "sh", 7 }, + { "ng", 4 }, +// { "" }, + + { 0, 0 } +}; + +static TCharacterPiece Vowels[] = +{ + { "a", 10 }, + { "e", 10 }, + { "i", 10 }, + { "o", 10 }, + { "u", 2 }, +// { "" }, + + { 0, 0 } +}; + +static TCharacterPiece ComplexVowels[] = +{ + { "ea", 10 }, + { "ue", 3 }, + { "oi", 10 }, + { "ai", 8 }, + { "oo", 10 }, + { "io", 10 }, + { "oe", 10 }, + { "au", 3 }, + { "ee", 7 }, + { "ei", 7 }, + { "ou", 7 }, + { "ia", 4 }, +// { "" }, + + { 0, 0 } +}; + +static TCharacterPiece Endings[] = +{ + { "ing", 10 }, + { "ed", 10 }, + { "ute", 10 }, + { "ance", 10 }, + { "ey", 10 }, + { "ation", 10 }, + { "ous", 10 }, + { "ent", 10 }, + { "ate", 10 }, + { "ible", 10 }, + { "age", 10 }, + { "ity", 10 }, + { "ist", 10 }, + { "ism", 10 }, + { "ime", 10 }, + { "ic", 10 }, + { "ant", 10 }, + { "etry", 10 }, + { "ious", 10 }, + { "ative", 10 }, + { "er", 10 }, + { "ize", 10 }, + { "able", 10 }, + { "itude", 10 }, +// { "" }, + + { 0, 0 } +}; + +static void FindPiece(ECPType type, char *&pos) +{ + TCharacterPiece *search, *start; + int count = 0; + + switch(type) + { + case CP_CONSONANT: + default: + start = Consonants; + break; + + case CP_COMPLEX_CONSONANT: + start = ComplexConsonants; + break; + + case CP_VOWEL: + start = Vowels; + break; + + case CP_COMPLEX_VOWEL: + start = ComplexVowels; + break; + + case CP_ENDING: + start = Endings; + break; + } + + search = start; + while(search->mPiece) + { + count += search->mCommonality; + search++; + } + + count = irand(0, count-1); + search = start; + while(count > search->mCommonality) + { + count -= search->mCommonality; + search++; + } + + strcpy(pos, search->mPiece); + pos += strlen(search->mPiece); +} + +unsigned RMG_CreateSeed(char *TextSeed) +{ + int Length; + char Ending[256], *pos; + int ComplexVowelChance, ComplexConsonantChance; + ECPType LookingFor; + unsigned SeedValue = 0, high; + + Length = irand(4, 9); + + if (irand(0, 100) < 20) + { + LookingFor = CP_VOWEL; + } + else + { + LookingFor = CP_CONSONANT; + } + + Ending[0] = 0; + + if (irand(0, 100) < 55) + { + pos = Ending; + FindPiece(CP_ENDING, pos); + Length -= (pos - Ending); + } + + pos = TextSeed; + *pos = 0; + + ComplexVowelChance = -1; + ComplexConsonantChance = -1; + + while((pos - TextSeed) < Length || LookingFor == CP_CONSONANT) + { + if (LookingFor == CP_VOWEL) + { + if (irand(0, 100) < ComplexVowelChance) + { + ComplexVowelChance = -1; + LookingFor = CP_COMPLEX_VOWEL; + } + else + { + ComplexVowelChance += 10; + } + + FindPiece(LookingFor, pos); + LookingFor = CP_CONSONANT; + } + else + { + if (irand(0, 100) < ComplexConsonantChance) + { + ComplexConsonantChance = -1; + LookingFor = CP_COMPLEX_CONSONANT; + } + else + { + ComplexConsonantChance += 45; + } + + FindPiece(LookingFor, pos); + LookingFor = CP_VOWEL; + } + } + + if (Ending[0]) + { + strcpy(pos, Ending); + } + + pos = TextSeed; + while(*pos) + { + high = SeedValue >> 28; + SeedValue ^= (SeedValue << 4) + ((*pos)-'a'); + SeedValue ^= high; + pos++; + } + + return SeedValue; +} diff --git a/codemp/qcommon/cm_randomterrain.h b/codemp/qcommon/cm_randomterrain.h new file mode 100644 index 0000000..f50a9db --- /dev/null +++ b/codemp/qcommon/cm_randomterrain.h @@ -0,0 +1,89 @@ +#pragma once +#if !defined(CM_RANDOMTERRAIN_H_INC) +#define CM_RANDOMTERRAIN_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including cm_randomterrain.h") +#endif + +//class CPathInfo; + +#define SPLINE_MERGE_SIZE 3 +#define CIRCLE_STAMP_SIZE 128 + + +class CPathInfo +{ +private: + vec4_t *mPoints, *mWork; + vec_t *mWeights; + int mNumPoints; + float mMinWidth, mMaxWidth; + float mInc; + float mDepth, mBreadth; + float mDeviation; + byte mCircleStamp[CIRCLE_STAMP_SIZE][CIRCLE_STAMP_SIZE]; + + void CreateCircle(void); + void Stamp(int x, int y, int size, int depth, unsigned char *Data, int DataWidth, int DataHeight); + +public: + CPathInfo(CCMLandScape *landscape, int numPoints, float bx, float by, float ex, float ey, + float minWidth, float maxWidth, float depth, float deviation, float breadth, + CPathInfo *Connected, unsigned CreationFlags); + ~CPathInfo(void); + + int GetNumPoints(void) { return mNumPoints; } + float *GetPoint(int index) { return mPoints[index]; } + float GetWidth(int index) { return mPoints[index][3]; } + + void GetInfo(float PercentInto, vec4_t Coord, vec4_t Vector); + void DrawPath(unsigned char *Data, int DataWidth, int DataHeight ); +}; + + +const int MAX_RANDOM_PATHS = 30; + +// Path Creation Flags +#define PATH_CREATION_CONNECT_FRONT 0x00000001 + + + +class CRandomTerrain +{ +private: + + class CCMLandScape *mLandScape; + int mWidth; + int mHeight; + int mArea; + int mBorder; + byte *mGrid; + CPathInfo *mPaths[MAX_RANDOM_PATHS]; + +public: + CRandomTerrain(void); + ~CRandomTerrain(void); + + CCMLandScape *GetLandScape(void) { return mLandScape; } + const vec3pair_t &GetBounds(void) const { return mLandScape->GetBounds(); } + void rand_seed(int seed) { mLandScape->rand_seed(seed); } + int get_rand_seed(void) { return mLandScape->get_rand_seed();} + float flrand(float min, float max) { return mLandScape->flrand(min, max); } + int irand(int min, int max) { return mLandScape->irand(min, max); } + + void Init(class CCMLandScape *landscape, byte *data, int width, int height); + void Shutdown(void); + bool CreatePath(int PathID, int ConnectedID, unsigned CreationFlags, int numPoints, + float bx, float by, float ex, float ey, + float minWidth, float maxWidth, float depth, float deviation, float breadth ); + bool GetPathInfo(int PathNum, float PercentInto, vec2_t Coord, vec2_t Vector); + void ParseGenerate(const char *GenerateFile); + void Smooth ( void ); + void Generate(int symmetric); + void ClearPaths(void); +}; + +unsigned RMG_CreateSeed(char *TextSeed); + +#endif // CM_RANDOM_H_INC diff --git a/codemp/qcommon/cm_shader.cpp b/codemp/qcommon/cm_shader.cpp new file mode 100644 index 0000000..c000b0e --- /dev/null +++ b/codemp/qcommon/cm_shader.cpp @@ -0,0 +1,522 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "cm_local.h" +#include "memory.h" +#include "chash.h" + +class CCMShaderText +{ +private: + char mName[MAX_QPATH]; + class CCMShaderText *mNext; + const char *mData; +public: + // Constructors + CCMShaderText(const char *name, const char *data) { Q_strncpyz(mName, name, MAX_QPATH); mNext = NULL; mData = data; } + ~CCMShaderText(void) {} + + // Accessors + const char *GetName(void) const { return(mName); } + class CCMShaderText *GetNext(void) const { return(mNext); } + void SetNext(class CCMShaderText *next) { mNext = next; } + void Destroy(void) { delete this; } + + const char *GetData(void) const { return(mData); } +}; + +char *shaderText = NULL; // THIS IS THE ONLY COPY - IT IS NEVER FREED! +CHash shaderTextTable; +CHash cmShaderTable; + +/* +==================== +CM_CreateShaderTextHash +===================== +*/ +void CM_CreateShaderTextHash(void) +{ + const char *p; + qboolean hasNewLines; + char *token; + CCMShaderText *shader; + + p = shaderText; + // look for label + while (p) + { + p = SkipWhitespace(p, &hasNewLines); + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + break; + } + shader = new CCMShaderText(token, p); + shaderTextTable.insert(shader); + + SkipBracedSection(&p); + } +} + +/* +==================== +CM_LoadShaderFiles + +Finds and loads all .shader files, combining them into +a single large text block that can be scanned for shader names +===================== +*/ +#define MAX_SHADER_FILES 1024 + +void CM_LoadShaderFiles( void ) +{ + if( !shaderText ) + { + char **shaderFiles1; + int numShaders1; + char *buffers[MAX_SHADER_FILES]; + int numShaders; + int i; + int sum = 0; + + // scan for shader files + shaderFiles1 = FS_ListFiles( "shaders", ".shader", &numShaders1 ); + + if ( !shaderFiles1 || !numShaders1 ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: no shader files found\n" ); + return; + } + + numShaders = numShaders1; + if ( numShaders > MAX_SHADER_FILES ) + { + numShaders = MAX_SHADER_FILES; + } + + // load and parse shader files + for ( i = 0; i < numShaders1; i++ ) + { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "shaders/%s", shaderFiles1[i] ); + Com_DPrintf( "...loading '%s'\n", filename ); + FS_ReadFile( filename, (void **)&buffers[i] ); + if ( !buffers[i] ) + { + Com_Error( ERR_FATAL, "Couldn't load %s", filename ); + } + sum += COM_Compress( buffers[i] ); + } + + // build single large buffer + shaderText = (char *)Z_Malloc( sum + numShaders * 2, TAG_SHADERTEXT, qtrue); + + // free in reverse order, so the temp files are all dumped + for ( i = numShaders - 1; i >= 0 ; i-- ) + { + strcat( shaderText, "\n" ); + strcat( shaderText, buffers[i] ); + FS_FreeFile( buffers[i] ); + } + + // free up memory + FS_FreeFileList( shaderFiles1 ); + } +} + +/* +================== +CM_GetShaderText +================== +*/ + +const char *CM_GetShaderText(const char *key) +{ + CCMShaderText *st; + + st = shaderTextTable[key]; + if(st) + { + return(st->GetData()); + } + return(NULL); +} + +/* +================== +CM_FreeShaderText +================== +*/ + +void CM_FreeShaderText(void) +{ + shaderTextTable.clear(); + // We NEVER free the shadertext anymore! +// if(shaderText) +// { +// Z_Free(shaderText); +// shaderText = NULL; +// } +} + +/* +================== +CM_LoadShaderText + + Loads in all the .shader files so it can be accessed by the server and the renderer + Creates a hash table to quickly access the shader text +================== +*/ + +void CM_LoadShaderText(qboolean forceReload) +{ + if(forceReload) + { + CM_FreeShaderText(); + } + // Above no longer blows away shader text, so we DO need to re-make the hash tables: +/* + if(shaderText) + { + return; + } +*/ +// Com_Printf("Loading shader text .....\n"); + CM_LoadShaderFiles(); + CM_CreateShaderTextHash(); + + //Com_Printf("..... %d shader definitions loaded\n", shaderTextTable.count()); +} + +/* +=============== +ParseSurfaceParm + +surfaceparm +=============== +*/ + +typedef struct +{ + char *name; + int clearSolid, surfaceFlags, contents; +} infoParm_t; + +infoParm_t svInfoParms[] = +{ + // Game surface flags + {"sky", -1, SURF_SKY, 0 }, // emit light from an environment map + {"slick", -1, SURF_SLICK, 0 }, + + {"nodamage", -1, SURF_NODAMAGE, 0 }, + {"noimpact", -1, SURF_NOIMPACT, 0 }, // don't make impact explosions or marks + {"nomarks", -1, SURF_NOMARKS, 0 }, // don't make impact marks, but still explode + {"nodraw", -1, SURF_NODRAW, 0 }, // don't generate a drawsurface (or a lightmap) + {"nosteps", -1, SURF_NOSTEPS, 0 }, + {"nodlight", -1, SURF_NODLIGHT, 0 }, // don't ever add dynamic lights + + // Game content flags + {"nonsolid", ~CONTENTS_SOLID, 0, 0 }, // special hack to clear solid flag + {"nonopaque", ~CONTENTS_OPAQUE, 0, 0 }, // special hack to clear opaque flag + {"lava", ~CONTENTS_SOLID, 0, CONTENTS_LAVA }, // very damaging + {"water", ~CONTENTS_SOLID, 0, CONTENTS_WATER }, + {"fog", ~CONTENTS_SOLID, 0, CONTENTS_FOG}, // carves surfaces entering + {"playerclip", ~CONTENTS_SOLID, 0, CONTENTS_PLAYERCLIP }, + {"monsterclip", ~CONTENTS_SOLID, 0, CONTENTS_MONSTERCLIP }, + {"botclip", ~CONTENTS_SOLID, 0, CONTENTS_BOTCLIP }, // for bots + {"shotclip", ~CONTENTS_SOLID, 0, CONTENTS_SHOTCLIP }, + {"trigger", ~CONTENTS_SOLID, 0, CONTENTS_TRIGGER }, + {"nodrop", ~CONTENTS_SOLID, 0, CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc) + {"terrain", ~CONTENTS_SOLID, 0, CONTENTS_TERRAIN }, // use special terrain collsion + {"ladder", ~CONTENTS_SOLID, 0, CONTENTS_LADDER }, // climb up in it like water + {"abseil", ~CONTENTS_SOLID, 0, CONTENTS_ABSEIL }, // can abseil down this brush + {"outside", ~CONTENTS_SOLID, 0, CONTENTS_OUTSIDE }, // volume is considered to be in the outside (i.e. not indoors) + {"inside", ~CONTENTS_SOLID, 0, CONTENTS_INSIDE }, // volume is considered to be inside (i.e. indoors) + + {"detail", -1, 0, CONTENTS_DETAIL }, // don't include in structural bsp + {"trans", -1, 0, CONTENTS_TRANSLUCENT }, // surface has an alpha component +}; + +void SV_ParseSurfaceParm( CCMShader * shader, const char **text ) +{ + char *token; + int numsvInfoParms = sizeof(svInfoParms) / sizeof(svInfoParms[0]); + int i; + + token = COM_ParseExt( text, qfalse ); + for ( i = 0 ; i < numsvInfoParms ; i++ ) + { + if ( !Q_stricmp( token, svInfoParms[i].name ) ) + { + shader->surfaceFlags |= svInfoParms[i].surfaceFlags; + shader->contentFlags |= svInfoParms[i].contents; + shader->contentFlags &= svInfoParms[i].clearSolid; + break; + } + } +} + +/* +================= +ParseMaterial +================= +*/ +const char *svMaterialNames[MATERIAL_LAST] = +{ + MATERIALS +}; + +void SV_ParseMaterial( CCMShader *shader, const char **text ) +{ + char *token; + int i; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: missing material in shader '%s'\n", shader->shader ); + return; + } + for(i = 0; i < MATERIAL_LAST; i++) + { + if ( !Q_stricmp( token, svMaterialNames[i] ) ) + { + shader->surfaceFlags |= i; + break; + } + } +} + +/* +=============== +ParseVector +=============== +*/ +static qboolean CM_ParseVector( CCMShader *shader, const char **text, int count, float *v ) +{ + char *token; + int i; + + // FIXME: spaces are currently required after parens, should change parseext... + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "(" ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW "WARNING: missing parenthesis in shader '%s'\n", shader->shader ); +#endif + return qfalse; + } + + for ( i = 0 ; i < count ; i++ ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: missing vector element in shader '%s'\n", shader->shader ); + return qfalse; + } + v[i] = atof( token ); + } + + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, ")" ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_YELLOW "WARNING: missing parenthesis in shader '%s'\n", shader->shader ); +#endif + return qfalse; + } + return qtrue; +} + +/* +================= +CM_ParseShader + +The current text pointer is at the explicit text definition of the +shader. Parse it into the global shader variable. + +This extracts all the info from the shader required for physics and collision +It is designed to *NOT* load any image files and not require any of the renderer to +be initialised. +================= +*/ +static void CM_ParseShader( CCMShader *shader, const char **text ) +{ + char *token; + + token = COM_ParseExt( text, qtrue ); + if ( token[0] != '{' ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader->shader ); + return; + } + + while ( true ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: no concluding '}' in shader %s\n", shader->shader ); + return; + } + + // end of shader definition + if ( token[0] == '}' ) + { + break; + } + // stage definition + else if ( token[0] == '{' ) + { + SkipBracedSection( text ); + continue; + } + // material deprecated as of 11 Jan 01 + // material undeprecated as of 7 May 01 - q3map_material deprecated + else if ( !Q_stricmp( token, "material" ) || !Q_stricmp( token, "q3map_material" ) ) + { + SV_ParseMaterial( shader, text ); + } + // sun parms + // q3map_sun deprecated as of 11 Jan 01 + else if ( !Q_stricmp( token, "sun" ) || !Q_stricmp( token, "q3map_sun" ) ) + { +// float a, b; + + token = COM_ParseExt( text, qfalse ); +// shader->sunLight[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); +// shader->sunLight[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); +// shader->sunLight[2] = atof( token ); + +// VectorNormalize( shader->sunLight ); + + token = COM_ParseExt( text, qfalse ); +// a = atof( token ); +// VectorScale( shader->sunLight, a, shader->sunLight); + + token = COM_ParseExt( text, qfalse ); +// a = DEG2RAD(atof( token )); + + token = COM_ParseExt( text, qfalse ); +// b = DEG2RAD(atof( token )); + +// shader->sunDirection[0] = cos( a ) * cos( b ); +// shader->sunDirection[1] = sin( a ) * cos( b ); +// shader->sunDirection[2] = sin( b ); + } + else if ( !Q_stricmp( token, "surfaceParm" ) ) + { + SV_ParseSurfaceParm( shader, text ); + continue; + } + else if ( !Q_stricmp( token, "fogParms" ) ) + { + vec3_t fogColor; + if ( !CM_ParseVector( shader, text, 3, fogColor ) ) + { + return; + } + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader->shader ); + continue; + } +// shader->depthForOpaque = atof( token ); + + // skip any old gradient directions + SkipRestOfLine( (const char **)text ); + continue; + } + } + return; +} + +/* +================= +CM_SetupShaderProperties + + Scans thru the shaders loaded for the map, parses the text of that shader and + extracts the interesting info *WITHOUT* loading up any images or requiring + the renderer to be active. +================= +*/ + +void CM_SetupShaderProperties(void) +{ + int i; + const char *def; + CCMShader *shader; + + // Add all basic shaders to the cmShaderTable + for(i = 0; i < cmg.numShaders; i++) + { + cmShaderTable.insert(CM_GetShaderInfo(i)); + } + // Go through and parse evaluate shader names to shadernums + for(i = 0; i < cmg.numShaders; i++) + { + shader = CM_GetShaderInfo(i); + def = CM_GetShaderText(shader->shader); + if(def) + { + CM_ParseShader(shader, &def); + } + } +} + +void CM_ShutdownShaderProperties(void) +{ + if(cmShaderTable.count()) + { +// Com_Printf("Shutting down cmShaderTable .....\n"); + cmShaderTable.clear(); + } +} + +CCMShader *CM_GetShaderInfo( const char *name ) +{ + CCMShader *out; + const char *def; + + out = cmShaderTable[name]; + if(out) + { + return(out); + } + + // Create a new CCMShader class + out = (CCMShader *)Hunk_Alloc( sizeof( CCMShader ), h_high ); + // Set defaults + Q_strncpyz(out->shader, name, MAX_QPATH); + out->contentFlags = CONTENTS_SOLID | CONTENTS_OPAQUE; + + // Parse in any text if it exists + def = CM_GetShaderText(name); + if(def) + { + CM_ParseShader(out, &def); + } + + cmShaderTable.insert(out); + return(out); +} + +CCMShader *CM_GetShaderInfo( int shaderNum ) +{ + CCMShader *out; + + if((shaderNum < 0) || (shaderNum >= cmg.numShaders)) + { + return(NULL); + } + out = cmg.shaders + shaderNum; + return(out); +} + +// end diff --git a/codemp/qcommon/cm_terrain.cpp b/codemp/qcommon/cm_terrain.cpp new file mode 100644 index 0000000..79e0123 --- /dev/null +++ b/codemp/qcommon/cm_terrain.cpp @@ -0,0 +1,1720 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "cm_local.h" +#include "cm_patch.h" +#include "cm_landscape.h" +#include "../qcommon/GenericParser2.h" +#include "cm_randomterrain.h" + +#ifdef _WIN32 +#pragma optimize("p", on) +#endif + +void R_LoadDataImage ( const char *name, byte **pic, int *width, int *height); +void R_InvertImage ( byte *data, int width, int height, int depth); +void R_Resample ( byte *source, int swidth, int sheight, byte *dest, int dwidth, int dheight, int components); + +#define _SMOOTH_TERXEL_BRUSH + +#ifdef _SMOOTH_TERXEL_BRUSH +#define BRUSH_SIDES_PER_TERXEL 8 +#else +#define BRUSH_SIDES_PER_TERXEL 5 +#endif + +void CCMLandScape::SetShaders(int height, CCMShader *shader) +{ + int i; + + for(i = height; shader && (i < HEIGHT_RESOLUTION); i++) + { + if(!mHeightDetails[i].GetSurfaceFlags()) + { + mHeightDetails[i].SetFlags(shader->contentFlags, shader->surfaceFlags); + } + } +} + +void CCMLandScape::LoadTerrainDef(const char *td) +{ + char terrainDef[MAX_QPATH]; + CGenericParser2 parse; + CGPGroup *basegroup, *classes, *items; + + Com_sprintf(terrainDef, MAX_QPATH, "ext_data/RMG/%s.terrain", Info_ValueForKey(td, "terrainDef")); + Com_DPrintf("CM_Terrain: Loading and parsing terrainDef %s.....\n", Info_ValueForKey(td, "terrainDef")); + + if(!Com_ParseTextFile(terrainDef, parse)) + { + Com_sprintf(terrainDef, MAX_QPATH, "ext_data/arioche/%s.terrain", Info_ValueForKey(td, "terrainDef")); + if(!Com_ParseTextFile(terrainDef, parse)) + { + Com_Printf("Could not open %s\n", terrainDef); + return; + } + } + // The whole file.... + basegroup = parse.GetBaseParseGroup(); + + // The root { } struct + classes = basegroup->GetSubGroups(); + while(classes) + { + items = classes->GetSubGroups(); + while(items) + { + if(!stricmp(items->GetName(), "altitudetexture")) + { + int height; + const char *shaderName; + CCMShader *shader; + + // Height must exist - the rest are optional + height = atol(items->FindPairValue("height", "0")); + + // Shader for this height + shaderName = items->FindPairValue("shader", ""); + if(strlen(shaderName)) + { + shader = CM_GetShaderInfo(shaderName); + if(shader) + { + SetShaders(height, shader); + } + } + } + else if(!stricmp(items->GetName(), "water")) + { + const char *shaderName; + CCMShader *shader; + + // Grab the height of the water + mBaseWaterHeight = atol(items->FindPairValue("height", "0")); + SetRealWaterHeight(mBaseWaterHeight); + + // Grab the material of the water + shaderName = items->FindPairValue("shader", ""); + shader = CM_GetShaderInfo(shaderName); + if(shader) + { + mWaterContents = shader->contentFlags; + mWaterSurfaceFlags = shader->surfaceFlags; + } + } + items = (CGPGroup *)items->GetNext(); + } + classes = (CGPGroup *)classes->GetNext(); + } + Com_ParseTextFileDestroy(parse); +} + +CCMPatch::~CCMPatch(void) +{ +} + +CCMLandScape::CCMLandScape(const char *configstring, bool server) +{ + int numPatches, numBrushesPerPatch, size, seed; + char heightMap[MAX_QPATH]; + char *ptr; + + holdrand = 0x89abcdef; + + // Clear out the height details + memset(mHeightDetails, 0, sizeof(CCMHeightDetails) * HEIGHT_RESOLUTION); + mBaseWaterHeight = 0; + mWaterHeight = 0.0f; + + // When constructed, referenced once + mRefCount = 1; + + // Extract the relevant data from the config string + Com_sprintf(heightMap, MAX_QPATH, "%s", Info_ValueForKey(configstring, "heightMap")); + numPatches = atol(Info_ValueForKey(configstring, "numPatches")); + mTerxels = atol(Info_ValueForKey(configstring, "terxels")); + mHasPhysics = !!atol(Info_ValueForKey(configstring, "physics")); + seed = strtoul(Info_ValueForKey(configstring, "seed"), &ptr, 10); + + mBounds[0][0] = (float)atof(Info_ValueForKey(configstring, "minx")); + mBounds[0][1] = (float)atof(Info_ValueForKey(configstring, "miny")); + mBounds[0][2] = (float)atof(Info_ValueForKey(configstring, "minz")); + mBounds[1][0] = (float)atof(Info_ValueForKey(configstring, "maxx")); + mBounds[1][1] = (float)atof(Info_ValueForKey(configstring, "maxy")); + mBounds[1][2] = (float)atof(Info_ValueForKey(configstring, "maxz")); + + // Calculate size of the brush + VectorSubtract(mBounds[1], mBounds[0], mSize); + + // Work out the dimensions of the brush in blocks - the object is to make the blocks as square as possible + mBlockWidth = Round(sqrtf(numPatches * mSize[0] / mSize[1])); + mBlockHeight = Round(sqrtf(numPatches * mSize[1] / mSize[0])); + + // ...which lets us get the size of the heightmap + mWidth = mBlockWidth * mTerxels; + mHeight = mBlockHeight * mTerxels; + + mHeightMap = (byte *)Z_Malloc(GetRealArea(), TAG_CM_TERRAIN); + mFlattenMap = (byte *)Z_Malloc(GetRealArea(), TAG_CM_TERRAIN); + + // Zero means unused. + memset ( mFlattenMap, 0, GetRealArea() ); + + if(strlen(heightMap)) + { + byte *imageData; + int iWidth, iHeight; + + Com_DPrintf("CM_Terrain: Loading heightmap %s.....\n", heightMap); + mRandomTerrain = 0; +#ifdef DEDICATED + imageData=NULL; +#else + R_LoadDataImage(heightMap, &imageData, &iWidth, &iHeight); + if(imageData) + { + if(strstr(heightMap, "random_")) + { + mRandomTerrain = CreateRandomTerrain ( configstring, this, mHeightMap, GetRealWidth(), GetRealHeight()); + } + else + { + // Flip to make the same as GenSurf + R_InvertImage(imageData, iWidth, iHeight, 1); + R_Resample(imageData, iWidth, iHeight, mHeightMap, GetRealWidth(), GetRealHeight(), 1); + } + Z_Free(imageData); + } +#endif + } + else + { + Com_Error(ERR_FATAL, "Terrain has no heightmap specified\n"); + } + + // Work out the dimensions of the terxel - should be almost square + mTerxelSize[0] = mSize[0] / mWidth; + mTerxelSize[1] = mSize[1] / mHeight; + mTerxelSize[2] = mSize[2] / 255.0f; + + // Work out the patchsize + mPatchSize[0] = mSize[0] / mBlockWidth; + mPatchSize[1] = mSize[1] / mBlockHeight; + mPatchSize[2] = 1.0f; + mPatchScalarSize = VectorLength(mPatchSize); + + // Loads in the water height and properties + // Gets the shader properties for the blended shaders + LoadTerrainDef(configstring); + + Com_DPrintf("CM_Terrain: Creating patches.....\n"); + mPatches = (CCMPatch *)Z_Malloc(sizeof(CCMPatch) * GetBlockCount(), TAG_CM_TERRAIN); + + numBrushesPerPatch = mTerxels * mTerxels * 2; + size = (numBrushesPerPatch * sizeof(cbrush_t)) + (numBrushesPerPatch * BRUSH_SIDES_PER_TERXEL * 2 * (sizeof(cbrushside_t) + sizeof(cplane_t))); + mPatchBrushData = (byte *)Z_Malloc(size * GetBlockCount(), TAG_CM_TERRAIN); + + // Initialize all terrain patches + UpdatePatches(); +} + +// Initialise a plane from 3 coords + +void CCMPatch::InitPlane(struct cbrushside_s *side, cplane_t *plane, vec3_t p0, vec3_t p1, vec3_t p2) +{ + vec3_t dx, dy; + + VectorSubtract(p1, p0, dx); + VectorSubtract(p2, p0, dy); + CrossProduct(dx, dy, plane->normal); + VectorNormalize(plane->normal); + + plane->dist = DotProduct(p0, plane->normal); + plane->type = PlaneTypeForNormal(plane->normal); + SetPlaneSignbits(plane); + +#ifdef _XBOX + cmg.planes[side->planeNum.GetValue()] = *plane; +#else + side->plane = plane; +#endif +} + +// Create the planes required for collision detection +// 2 brushes per terxel - each brush has 5 sides and 5 planes + +void* CCMPatch::GetAdjacentBrushY ( int x, int y ) +{ + int yo1 = y % owner->GetTerxels(); + int yo2 = (y-1) % owner->GetTerxels(); + int xo = x % owner->GetTerxels(); + CCMPatch* patch; + + // Different patch + if ( yo2 > yo1 ) + { + patch = owner->GetPatch ( x / owner->GetTerxels(), (y-1) / owner->GetTerxels() ); + } + else + { + patch = this; + } + + cbrush_t *brush; + + brush = patch->mPatchBrushData; + brush += ((yo2 * owner->GetTerxels ( ) + xo) * 2); + brush ++; + + return brush; +} + +void* CCMPatch::GetAdjacentBrushX ( int x, int y ) +{ + int xo1 = x % owner->GetTerxels(); + int xo2 = (x-1) % owner->GetTerxels(); + int yo = y % owner->GetTerxels(); + CCMPatch* patch; + + // Different patch + if ( xo2 > xo1 ) + { + patch = owner->GetPatch ( (x-1) / owner->GetTerxels(), y / owner->GetTerxels() ); + } + else + { + patch = this; + } + + cbrush_t *brush; + + brush = patch->mPatchBrushData; + brush += ((yo * owner->GetTerxels ( ) + xo2) * 2); + + if ( ! ((x+y) & 1) ) + { + brush ++; + } + + return brush; +} + +void CCMPatch::CreatePatchPlaneData(void) +{ +#ifndef PRE_RELEASE_DEMO + int realWidth; + int x, y, i, j; +#if 0 + int n; +#endif + cbrush_t *brush; + cbrushside_t *side; + cplane_t *plane; + vec3_t *coords; + vec3_t localCoords[8]; + + mNumBrushes = owner->GetTerxels() * owner->GetTerxels() * 2; + realWidth = owner->GetRealWidth(); + coords = owner->GetCoords(); + + brush = mPatchBrushData; + side = (cbrushside_t *)(mPatchBrushData + mNumBrushes); + plane = (cplane_t *)(side + (mNumBrushes * BRUSH_SIDES_PER_TERXEL * 2)); + for(y = mHy; y < mHy + owner->GetTerxels(); y++) + { + for(x = mHx; x < mHx + owner->GetTerxels(); x++) + { + int offsets[4]; + + if ( (x+y)&1 ) + { + offsets[0] = (y * realWidth) + x; // TL + offsets[1] = (y * realWidth) + x + 1; // TR + offsets[2] = ((y + 1) * realWidth) + x; // BL + offsets[3] = ((y + 1) * realWidth) + x + 1; // BR + } + else + { + offsets[2] = (y * realWidth) + x; // TL + offsets[0] = (y * realWidth) + x + 1; // TR + offsets[3] = ((y + 1) * realWidth) + x; // BL + offsets[1] = ((y + 1) * realWidth) + x + 1; // BR + } + + for(i = 0; i < 4; i++) + { + VectorCopy(coords[offsets[i]], localCoords[i]); + VectorCopy(coords[offsets[i]], localCoords[i + 4]); + // Set z of base of brush to bottom of landscape brush + localCoords[i + 4][2] = owner->GetMins()[2]; + } + + // Set the bounds of the terxel + VectorSet(brush[0].bounds[0], MAX_WORLD_COORD, MAX_WORLD_COORD, MAX_WORLD_COORD); + VectorSet(brush[0].bounds[1], MIN_WORLD_COORD, MIN_WORLD_COORD, MIN_WORLD_COORD); + + for(i = 0; i < 8; i++) + { + for(j = 0; j < 3; j++) + { + // mins + if(localCoords[i][j] < brush[0].bounds[0][j]) + { + brush[0].bounds[0][j] = localCoords[i][j]; + } + // maxs + if(localCoords[i][j] > brush[0].bounds[1][j]) + { + brush[0].bounds[1][j] = localCoords[i][j]; + } + } + } + VectorDec(brush[0].bounds[0]); + VectorInc(brush[0].bounds[1]); + VectorCopy(brush[0].bounds[0], brush[1].bounds[0]); + VectorCopy(brush[0].bounds[1], brush[1].bounds[1]); + + brush[0].contents = mContentFlags; + brush[1].contents = mContentFlags; + +#ifndef _SMOOTH_TERXEL_BRUSH + + // Set up sides of the brushes + brush[0].numsides = 5; + brush[0].sides = side; + brush[1].numsides = 5; + brush[1].sides = side + 5; + + for ( i = 0; i < 8 ; i ++ ) + { + localCoords[i][0] = (int)localCoords[i][0]; + localCoords[i][1] = (int)localCoords[i][1]; + localCoords[i][2] = (int)localCoords[i][2]; + } + + // Create the planes of the 2 triangles that make up the tops of the brushes + InitPlane(side + 0, plane + 0, localCoords[0], localCoords[1], localCoords[2]); + InitPlane(side + 5, plane + 5, localCoords[3], localCoords[2], localCoords[1]); + + // Create the bottom face of the brushes + InitPlane(side + 1, plane + 1, localCoords[6], localCoords[5], localCoords[4]); + InitPlane(side + 6, plane + 6, localCoords[5], localCoords[6], localCoords[7]); + + // Create the 3 vertical faces + InitPlane(side + 2, plane + 2, localCoords[0], localCoords[2], localCoords[4]); + InitPlane(side + 7, plane + 7, localCoords[3], localCoords[1], localCoords[7]); + + InitPlane(side + 3, plane + 3, localCoords[0], localCoords[4], localCoords[1]); + InitPlane(side + 8, plane + 8, localCoords[3], localCoords[7], localCoords[2]); + + InitPlane(side + 4, plane + 4, localCoords[2], localCoords[1], localCoords[6]); + InitPlane(side + 9, plane + 9, localCoords[5], localCoords[1], localCoords[6]); + + // Increment to next terxel + brush += 2; + side += 10; + plane += 10; + + + +#else + + // Set up sides of the brushes + brush[0].numsides = 5; + brush[0].sides = side; + brush[1].numsides = 5; + brush[1].sides = side + 8; + + + // Create the planes of the 2 triangles that make up the tops of the brushes + InitPlane(side + 0, plane + 0, localCoords[0], localCoords[1], localCoords[2]); + InitPlane(side + 8, plane + 8, localCoords[3], localCoords[2], localCoords[1]); + + // Create the bottom face of the brushes + InitPlane(side + 1, plane + 1, localCoords[4], localCoords[6], localCoords[5]); + InitPlane(side + 9, plane + 9, localCoords[7], localCoords[5], localCoords[6]); + + // Create the 3 vertical faces + InitPlane(side + 2, plane + 2, localCoords[0], localCoords[2], localCoords[4]); + InitPlane(side + 10, plane + 10, localCoords[3], localCoords[1], localCoords[7]); + + InitPlane(side + 3, plane + 3, localCoords[0], localCoords[4], localCoords[1]); + InitPlane(side + 11, plane + 11, localCoords[3], localCoords[7], localCoords[2]); + + InitPlane(side + 4, plane + 4, localCoords[2], localCoords[1], localCoords[6]); + InitPlane(side + 12, plane + 12, localCoords[5], localCoords[1], localCoords[6]); + + float V = DotProduct ( (plane + 8)->normal, localCoords[0] ) - (plane + 8)->dist; + + if ( V < 0 ) + { + InitPlane ( brush[0].sides + brush[0].numsides, plane + brush[0].numsides, localCoords[3], localCoords[2], localCoords[1]); + brush[0].numsides++; + + InitPlane ( brush[1].sides + brush[1].numsides, plane + 8 + brush[1].numsides, localCoords[0], localCoords[1], localCoords[2]); + brush[1].numsides++; + } + + // Determine if we need to smooth the brush transition from the brush above us + if ( y > 0 && y < owner->GetPatchHeight ( ) - 1 ) + { + cbrush_t* abovebrush = (cbrush_t*)GetAdjacentBrushY ( x, y ); +#ifdef _XBOX + cplane_t* aboveplane = &cmg.planes[abovebrush->sides->planeNum.GetValue()]; +#else + cplane_t* aboveplane = abovebrush->sides->plane; +#endif + + V = DotProduct ( aboveplane->normal, ((y+x)&1)?(localCoords[2]):(localCoords[1]) ) - aboveplane->dist; + + if ( V < 0 ) + { + memcpy ( brush[0].sides + brush[0].numsides, abovebrush->sides, sizeof(cbrushside_t) ); + brush[0].numsides++; + + memcpy ( abovebrush->sides + abovebrush->numsides, side + 0, sizeof(cbrushside_t) ); + abovebrush->numsides++; + } + } + + // Determine if we need to smooth the brush transition from the brush to the left of us + if ( x > 0 && x < owner->GetPatchWidth ( ) - 1 ) + { + cbrush_t* abovebrush = (cbrush_t*)GetAdjacentBrushX ( x, y ); + +#ifdef _XBOX + cplane_t* aboveplane = &cmg.planes[abovebrush->sides->planeNum.GetValue()]; +#else + cplane_t* aboveplane = abovebrush->sides->plane; +#endif + + V = DotProduct ( aboveplane->normal, localCoords[1] ) - aboveplane->dist; + + if ( V < 0 ) + { + if ( (x+y)&1 ) + { + memcpy ( brush[0].sides + brush[0].numsides, abovebrush->sides, sizeof(cbrushside_t) ); + brush[0].numsides++; + + memcpy ( abovebrush->sides + abovebrush->numsides, side + 0, sizeof(cbrushside_t) ); + abovebrush->numsides++; + } + else + { + memcpy ( brush[1].sides + brush[1].numsides, abovebrush->sides, sizeof(cbrushside_t) ); + brush[1].numsides++; + + memcpy ( abovebrush->sides + abovebrush->numsides, side + 8, sizeof(cbrushside_t) ); + abovebrush->numsides++; + } + } + } + + // Increment to next terxel + brush += 2; + side += 16; + plane += 16; +#endif + } + } +#endif // PRE_RELEASE_DEMO +} + +void CCMPatch::Init(CCMLandScape *ls, int heightX, int heightY, vec3_t world, byte *hMap, byte *patchBrushData) +{ +#ifndef PRE_RELEASE_DEMO + int min, max, x, y, height; + + // Set owning landscape + owner = ls; + + // Store the base of the top left corner + VectorCopy(world, mWorldCoords); + + // Store pointer to first byte of the height data for this patch. + mHx = heightX; + mHy = heightY; + mHeightMap = hMap + ((heightY * owner->GetRealWidth()) + heightX); + + // Calculate the bounds for culling + // Use the dimensions 1 terxel outside the patch to allow for sloping of edge terxels + min = 256; + max = -1; + for(y = heightY - 1; y < heightY + owner->GetTerxels() + 1; y++) + { + if(y >= 0) + { + for(x = heightX - 1; x < heightX + owner->GetTerxels() + 1; x++) + { + if(x >= 0) + { + height = hMap[(y * owner->GetRealWidth()) + x]; + + if(height > max) + { + max = height; + } + if(height < min) + { + min = height; + } + } + } + } + } + + // Mins + mBounds[0][0] = world[0]; + mBounds[0][1] = world[1]; + mBounds[0][2] = world[2] + (min * owner->GetTerxelSize()[2]); + + // Maxs + mBounds[1][0] = world[0] + (owner->GetPatchSize()[0]); + mBounds[1][1] = world[1] + (owner->GetPatchSize()[1]); + mBounds[1][2] = world[2] + (max * owner->GetTerxelSize()[2]); + + // Corner heights + mCornerHeights[0] = mHeightMap[0]; + mCornerHeights[1] = mHeightMap[owner->GetTerxels()]; + mCornerHeights[2] = mHeightMap[owner->GetTerxels() * owner->GetRealWidth()]; + mCornerHeights[3] = mHeightMap[(owner->GetTerxels() * owner->GetRealWidth()) + owner->GetTerxels()]; + + // Set the surfaceFlags using average height (may want a more complex algo here) + mSurfaceFlags = owner->GetSurfaceFlags((min + max) >> 1); + mContentFlags = owner->GetContentFlags((min + max) >> 1); + + // Set base of brush data from big array + mPatchBrushData = (cbrush_t *)patchBrushData; + CreatePatchPlaneData(); +#endif // PRE_RELEASE_DEMO +} + +CCMPatch *CCMLandScape::GetPatch(int x, int y) +{ + return(mPatches + ((y * mBlockWidth) + x)); +} + +extern cvar_t *com_newtrace; + +void CCMLandScape::PatchCollide(struct traceWork_s *tw, trace_t &trace, const vec3_t start, const vec3_t end, int checkcount) +{ + vec3pair_t tBounds; + + // Convert to valid bounding box + CM_CalcExtents(start, end, tw, tBounds); + + //if (com_newtrace->integer) + if (1) + { + float slope, offset; + float startPatchLoc, endPatchLoc, startPos, endPos; + float patchDirection = 1; + float checkDirection = 1; + int countPatches, count; + CCMPatch *patch; + float fraction = trace.fraction; + + if (fabs(end[0]-start[0]) >= fabs(fabs(end[1]-start[1]))) + { // x travels more than y + // calculate line slope and offset + if (end[0] - start[0]) + { + slope = (end[1] - start[1]) / (end[0] - start[0]); + } + else + { + slope = 0; + } + offset = start[1] - (start[0] * slope); + + // find the starting + startPatchLoc = floor((start[0] - mBounds[0][0]) / mPatchSize[0]); + endPatchLoc = floor((end[0] - mBounds[0][0]) / mPatchSize[0]); + + if (startPatchLoc <= endPatchLoc) + { // moving along slope in a positive direction + endPatchLoc++; + startPatchLoc--; + countPatches = endPatchLoc - startPatchLoc + 1; + } + else + { // moving along slope in a negative direction + endPatchLoc--; + startPatchLoc++; + patchDirection = -1; + countPatches = startPatchLoc - endPatchLoc + 1; + } + if (slope < 0.0) + { + checkDirection = -1; + } + + // first calculate the real world location + startPos = ((startPatchLoc * mPatchSize[0] + mBounds[0][0]) * slope) + offset; + // calculate it back into patch coords + startPos = floor((startPos - mBounds[0][1] + tw->size[0][1]) / mPatchSize[1]); + do + { + if (startPatchLoc >= 0 && startPatchLoc < mBlockWidth) + { // valid location + // first calculate the real world location + endPos = (((startPatchLoc+patchDirection) * mPatchSize[0] + mBounds[0][0]) * slope) + offset; + // calculate it back into patch coords + endPos = floor((endPos - mBounds[0][1] + tw->size[1][1]) / mPatchSize[1]); + + if (checkDirection < 0) + { + startPos++; + endPos--; + } + else + { + startPos--; + endPos++; + } + count = fabs(endPos - startPos) + 1; + while(count) + { + if (startPos >= 0 && startPos < mBlockHeight) + { // valid location + patch = GetPatch(startPatchLoc, startPos); + // Collide with every patch to find the minimum fraction + CM_HandlePatchCollision(tw, trace, tBounds[0], tBounds[1], patch, checkcount); + + if (trace.fraction <= 0.0) + { + return; + } + } + startPos += checkDirection; + count--; + } + + if (trace.fraction < fraction) + { + return; + } + } + // move to the next spot + // we still stay one behind, to get the opposite edge of the terrain patch + startPos = ((startPatchLoc * mPatchSize[0] + mBounds[0][0]) * slope) + offset; + startPatchLoc += patchDirection; + // first calculate the real world location + // calculate it back into patch coords + startPos = floor((startPos - mBounds[0][1] + tw->size[0][1]) / mPatchSize[1]); + + countPatches--; + } + while (countPatches); + } + else + { + // calculate line slope and offset + slope = (end[0] - start[0]) / (end[1] - start[1]); + offset = start[0] - (start[1] * slope); + + // find the starting + startPatchLoc = floor((start[1] - mBounds[0][1]) / mPatchSize[1]); + endPatchLoc = floor((end[1] - mBounds[0][1]) / mPatchSize[1]); + + if (startPatchLoc <= endPatchLoc) + { // moving along slope in a positive direction + endPatchLoc++; + startPatchLoc--; + countPatches = endPatchLoc - startPatchLoc + 1; + } + else + { // moving along slope in a negative direction + endPatchLoc--; + startPatchLoc++; + patchDirection = -1; + countPatches = startPatchLoc - endPatchLoc + 1; + } + if (slope < 0.0) + { + checkDirection = -1; + } + + // first calculate the real world location + startPos = ((startPatchLoc * mPatchSize[1] + mBounds[0][1]) * slope) + offset; + // calculate it back into patch coords + startPos = floor((startPos - mBounds[0][0] + tw->size[0][0]) / mPatchSize[0]); + do + { + if (startPatchLoc >= 0 && startPatchLoc < mBlockHeight) + { // valid location + // first calculate the real world location + endPos = (((startPatchLoc+patchDirection) * mPatchSize[1] + mBounds[0][1]) * slope) + offset; + // calculate it back into patch coords + endPos = floor((endPos - mBounds[0][0] + tw->size[1][0]) / mPatchSize[0]); + + if (checkDirection < 0) + { + startPos++; + endPos--; + } + else + { + startPos--; + endPos++; + } + + count = fabs(endPos - startPos) + 1; + while(count) + { + if (startPos >= 0 && startPos < mBlockWidth) + { // valid location + patch = GetPatch(startPos, startPatchLoc); + // Collide with every patch to find the minimum fraction + CM_HandlePatchCollision(tw, trace, tBounds[0], tBounds[1], patch, checkcount); + + if (trace.fraction <= 0.0) + { + return; + } + } + startPos += checkDirection; + count--; + } + + if (trace.fraction < fraction) + { + return; + } + } + + // move to the next spot + // we still stay one behind, to get the opposite edge of the terrain patch + startPos = ((startPatchLoc * mPatchSize[1] + mBounds[0][1]) * slope) + offset; + startPatchLoc += patchDirection; + // first calculate the real world location + // calculate it back into patch coords + startPos = floor((startPos - mBounds[0][0] + tw->size[0][0]) / mPatchSize[0]); + countPatches--; + } + while (countPatches); + } + } + else + { + int x, y; + vec3_t tWork; + vec3_t pStart, pEnd; + int minx, maxx, miny, maxy; + CCMPatch *patch; + + // Work out and grab the relevant patches + VectorSubtract(tBounds[0], mBounds[0], tWork); + VectorInverseScaleVector(tWork, mPatchSize, pStart); + VectorSubtract(tBounds[1], mBounds[0], tWork); + VectorInverseScaleVector(tWork, mPatchSize, pEnd); + + minx = Com_Clamp(0, mBlockWidth - 1, floorf(pStart[0])); + maxx = Com_Clamp(0, mBlockWidth - 1, ceilf(pEnd[0])); + miny = Com_Clamp(0, mBlockHeight - 1, floorf(pStart[1])); + maxy = Com_Clamp(0, mBlockHeight - 1, ceilf(pEnd[1])); + + // generic box collide with each one + for(y = miny; y <= maxy; y++) + { + for(x = minx; x <= maxx; x++) + { + patch = GetPatch(x, y); + // Collide with every patch to find the minimum fraction + CM_HandlePatchCollision(tw, trace, tBounds[0], tBounds[1], patch, checkcount); + + if (trace.fraction <= 0.0) + { + break; + } + } + } + } +} + +float CCMLandScape::WaterCollide(const vec3_t begin, const vec3_t end, float fraction) const +{ + // Check for completely above water + if((begin[2] > mWaterHeight) && (end[2] > mWaterHeight)) + { + return(fraction); + } + // Check for completely below water + if((begin[2] < mWaterHeight) && (end[2] < mWaterHeight)) + { + return(fraction); + } + // Check for starting in water and leaving + if(begin[2] < mWaterHeight - SURFACE_CLIP_EPSILON) + { + fraction = ((mWaterHeight - SURFACE_CLIP_EPSILON) - begin[2]) / (end[2] - begin[2]); + return(fraction); + } + // Now the trace must be entering the water + if(begin[2] > mWaterHeight + SURFACE_CLIP_EPSILON) + { + fraction = (begin[2] - (mWaterHeight + SURFACE_CLIP_EPSILON)) / (begin[2] - end[2]); + } + return(fraction); +} + +void CCMLandScape::GetTerxelLocalCoords ( int x, int y, vec3_t localCoords[8] ) +{ + int realWidth; + vec3_t* coords; + int offsets[4]; + int i; + + coords = GetCoords ( ); + realWidth = GetRealWidth ( ); + + if ( (x+y)&1 ) + { + offsets[0] = (y * realWidth) + x; // TL + offsets[1] = (y * realWidth) + x + 1; // TR + offsets[2] = ((y + 1) * realWidth) + x; // BL + offsets[3] = ((y + 1) * realWidth) + x + 1; // BR + } + else + { + offsets[2] = (y * realWidth) + x; // TL + offsets[0] = (y * realWidth) + x + 1; // TR + offsets[3] = ((y + 1) * realWidth) + x; // BL + offsets[1] = ((y + 1) * realWidth) + x + 1; // BR + } + + for( i = 0; i < 4; i++ ) + { + VectorCopy(coords[offsets[i]], localCoords[i]); + VectorCopy(coords[offsets[i]], localCoords[i + 4]); + + // Set z of base of brush to bottom of landscape brush + localCoords[i + 4][2] = GetMins()[2]; + } +} + + +void CCMLandScape::UpdatePatches(void) +{ + CCMPatch *patch; + int x, y, ix, iy, numBrushesPerPatch; + vec3_t world; + int size; + +/* for(y=0;yInit(this, x, y, world, mHeightMap, mPatchBrushData + (size * (ix + (iy * mBlockWidth)))); + } + } + +/* + for ( y = mTerxels; y < mHeight - mTerxels; y ++ ) + { + for ( x = mTerxels; x < mWidth - mTerxels; x ++ ) + { + int xo = x % mTerxels; + int yo = y % mTerxels; + int xor = (x + 1) % mTerxels; + int yob = (y + 1) % mTerxels; + + CCMPatch* patch = mPatches + (mWidth / mTerxels) * y + (x / mTerxels); + CCMPatch* rpatch = mPatches + (mWidth / mTerxels) * y + ((x+1) / mTerxels); + CCMPatch* bpatch = mPatches + (mWidth / mTerxels) * (y + 1) + (x / mTerxels); + + int offsets[4]; + vec3_t localCoords[8]; + vec3_t localCoordsR[8]; + vec3_t localCoordsL[8]; + + GetTerxelLocalCoords ( x, y, localCoords ); + GetTerxelLocalCoords ( x + 1, y, localCoordsR ); + GetTerxelLocalCoords ( x, y + 1, localCoordsB ); + + brush = patch->GetCollisionData ( );; + side = (cbrushside_t *)(mPatchBrushData + patch->GetNumBrushes ( ) ); + plane = (cplane_t *)(side + (mNumBrushes * BRUSH_SIDES_PER_TERXEL * 2)); + + + float V = DotProduct ( (plane + 8)->normal, localCoords[0] ) + plane->dist; + + if ( V < 0 ) + { + InitPlane ( brush[0].sides + brush[0].numsides, plane + brush[0].numsides, localCoords[3], localCoords[2], localCoords[1]); + brush[0].numsides++; + + InitPlane ( brush[1].sides + brush[1].numsides, plane + 8 + brush[1].numsides, localCoords[0], localCoords[1], localCoords[2]); + brush[1].numsides++; + } + } + } +*/ + + // Cleanup coord array + Z_Free(mCoords); +} + +void CCMLandScape::CalcRealCoords(void) +{ + int x, y; + + mCoords = (vec3_t *)Z_Malloc(sizeof(vec3_t) * GetRealWidth() * GetRealHeight(), TAG_CM_TERRAIN_TEMP); + + // Work out the real world coordinates of each heightmap entry + for(y = 0; y < GetRealHeight(); y++) + { + for(x = 0; x < GetRealWidth(); x++) + { + ivec3_t icoords; + int offset; + + offset = (y * GetRealWidth()) + x; + + VectorSet(icoords, x, y, mHeightMap[offset]); + VectorScaleVectorAdd(GetMins(), icoords, GetTerxelSize(), mCoords[offset]); + } + } +} + +void CCMLandScape::TerrainPatchIterate(void (*IterateFunc)( CCMPatch *, void * ), void *userdata) const +{ + int i; + CCMPatch *patch; + + patch = mPatches; + for(i = 0; i < GetBlockCount(); i++, patch++) + { + IterateFunc(patch, userdata); + } +} + +#define LERP(t, a, b) (((b)-(a))*(t) + (a)) + +float CCMLandScape::GetWorldHeight(vec3_t origin, const vec3pair_t bounds, bool aboveGround) const +{ + vec3_t work; + int minx, maxx, miny, maxy; + int TL, TR, BL, BR; + int final; + + VectorSubtract(origin, mBounds[0], work); + VectorInverseScaleVector(work, mTerxelSize, work); + + // Presume the bases of all misc models are less than 1 terxel square + minx = Com_Clamp(0, GetWidth(), (int)floorf(work[0])); + maxx = Com_Clamp(0, GetWidth(), (int)ceilf(work[0])); + miny = Com_Clamp(0, GetHeight(), (int)floorf(work[1])); + maxy = Com_Clamp(0, GetHeight(), (int)ceilf(work[1])); + + TL = mHeightMap[(miny * GetRealWidth()) + minx]; + TR = mHeightMap[(miny * GetRealWidth()) + maxx]; + BL = mHeightMap[(maxy * GetRealWidth()) + minx]; + BR = mHeightMap[(maxy * GetRealWidth()) + maxx]; + + if(aboveGround) + { +// int max1, max2; +// max1 = maximum(TL, TR); +// max2 = maximum(BL, BR); +// final = maximum(max1, max2); + float h1, h2; + float tx, ty; + tx = (work[0] - minx)/((float)(maxx-minx)); + ty = (work[1] - miny)/((float)(maxy-miny)); + h1 = LERP(tx, TL, TR); + h2 = LERP(tx, BL, BR); + final = LERP(ty, h1, h2); + } + else + { + int min1, min2; + + min1 = minimum(TL, TR); + min2 = minimum(BL, BR); + final = minimum(min1, min2); + } + origin[2] = (final * mTerxelSize[2]) + mBounds[0][2]; + + // compute slope at this spot + if (maxx == minx) + maxx = Com_Clamp(0, GetWidth(), minx+1); + if (maxy == miny) + maxy = Com_Clamp(0, GetHeight(), miny+1); + BR = mHeightMap[(maxy * GetRealWidth()) + maxx]; + + // rise over run + return (fabs((float)(BR - TL)) * mTerxelSize[2]) / mTerxelSize[0]; +} + +void CM_CircularIterate(byte *data, int width, int height, int xo, int yo, int insideRadius, int outsideRadius, int *user, void (*callback)(byte *, float, int *)) +{ + int x, y, offset; + byte *work; + + for(y = -outsideRadius; y < outsideRadius + 1; y++) + { + if(y + yo >= 0 && y + yo < height) + { + offset = sqrtf((outsideRadius * outsideRadius) - (y * y)); + for(x = -offset; x < offset + 1; x++) + { + if(x + xo >= 0 && x + xo < width) + { + float radius = sqrt((float)(x*x+y*y)); + + if ( radius >= insideRadius ) + { + work = data + (x + xo) + ((y + yo) * width); + callback( work, (radius - (float)insideRadius) / (float)(outsideRadius - insideRadius), user); + } + } + } + } + } +} + +void CM_ForceHeight( byte *work, float lerp, int *user) +{ + *work = (byte)Com_Clamp(0, 255, (int)*user); +} + + +void CM_GetAverage( byte *work, float lerp, int *user) +{ + user[0] += *work; + user[1]++; +} + +void CM_Smooth ( byte* work, float lerp, int *user ) +{ + float smooth = sin ( M_PI/2*3 + (1.0f-lerp) * (M_PI / 2) ) + 1.0f; +// float smooth = (1.0f - lerp); + + *work = *work + (int)((float)(*user - *work) * smooth); +} + +void CM_MakeAverage( byte *work, float lerp, int *user) +{ + int height, diff; + + height = (int)*work; + diff = *user - height; + if(abs(diff) > 3) + { + diff >>= 2; + } + height += diff; + *work = (byte)Com_Clamp(0, 255, height); +} + +void CCMLandScape::SaveArea(CArea *area) +{ + mAreas.push_back(area); +} + +void CCMLandScape::CarveLine ( vec3_t start, vec3_t end, int depth, int width ) +{ + int x, x1, x2, deltax; + int y, y1, y2, deltay; + int xinc1, xinc2; + int yinc1, yinc2; + int den, num; + int count, add; + int i; + float heightStart; + float heightEnd; + float heightStep; + + x1 = (int) start[0]; + y1 = (int) start[1]; + x2 = (int) end[0]; + y2 = (int) end[1]; + + deltax = abs(x2 - x1); + deltay = abs(y2 - y1); + x = x1; + y = y1; + + // The x-values are increasing + if (x2 >= x1) + { + xinc1 = 1; + xinc2 = 1; + } + // The x-values are decreasing + else + { + xinc1 = -1; + xinc2 = -1; + } + + // The y-values are increasing + if (y2 >= y1) + { + yinc1 = 1; + yinc2 = 1; + } + // The y-values are decreasing + else + { + yinc1 = -1; + yinc2 = -1; + } + + if (deltax >= deltay) // There is at least one x-value for every y-value + { + xinc1 = 0; // Don't change the x when numerator >= denominator + yinc2 = 0; // Don't change the y for every iteration + den = deltax; + num = deltax / 2; + add = deltay; + count = deltax; // There are more x-values than y-values + } + else // There is at least one y-value for every x-value + { + xinc2 = 0; // Don't change the x for every iteration + yinc1 = 0; // Don't change the y when numerator >= denominator + den = deltay; + num = deltay / 2; + add = deltax; + count = deltay; // There are more y-values than x-values + } + + vec3_t pt; + vec3_t bounds[2] = {{-1,-1,-1},{1,1,1}}; + + pt[0] = start[0]; + pt[1] = start[1]; + GetWorldHeight ( pt, bounds, false ); + heightStart = pt[2]; + + pt[0] = end[0]; + pt[1] = end[1]; + GetWorldHeight ( pt, bounds, false ); + heightEnd = pt[2]; + + heightStep = (heightEnd-heightStart) / count; + + for ( i = 0; i <= count; i++ ) + { + // Flatten the current location + CArea area; + + pt[0] = x; + pt[1] = y; + area.Init ( pt, width / 2 + (irand(0, width/2)) ); + FlattenArea ( &area, heightStart + (heightStep * i) - (depth/2 - (irand(0, depth/2))), false, true, true ); + + // Increase the numerator by the top of the fraction + num += add; + + if (num >= den) + { + // Calculate the new numerator value + num -= den; + + // Change the x and y as appropriate + x += xinc1; + y += yinc1; + } + + // Change the x and y as appropriate + x += xinc2; + y += yinc2; + } +} + +void CCMLandScape::CarveBezierCurve ( int numCtlPoints, vec3_t* ctlPoints, int steps, int depth, int size ) +{ + int i; + int choose; + int n; + float u; + float t; + float tt; + float t1; + float step; + vec3_t pt; + vec3_t lastpt; + vec3_t b[10]; + + n = numCtlPoints - 1; + choose = 1; + + for ( i = 1; i <= n; i ++ ) + { + if ( i == 1 ) + choose = n; + else + choose = choose * (n-i+1) / i; + + (*(ctlPoints+i))[0] *= choose; + (*(ctlPoints+i))[1] *= choose; + } + + step = 1.0f / (float)steps; + for ( choose = 0, t = step; t < 1; t += step, choose++ ) + { + b[0][0] = (*(ctlPoints+0))[0]; + b[0][1] = (*(ctlPoints+0))[1]; + + for ( u = t, i = 1; i <= n; i ++ ) + { + b[i][0] = (*(ctlPoints+i))[0] * u; + b[i][1] = (*(ctlPoints+i))[1] * u; + + u = u * t; + } + + pt[0] = b[n][0]; + pt[1] = b[n][1]; + + t1 = 1 - t; + tt = t1; + + for ( i = n - 1; i >= 0; i -- ) + { + pt[0] += b[i][0] * tt; + pt[1] += b[i][1] * tt; + + tt = tt * t1; + } + + if ( choose != 0 ) + { + CarveLine ( lastpt, pt, depth, size ); + } + + // Save this point for next time around + lastpt[0] = pt[0]; + lastpt[1] = pt[1]; + } +} + +void CCMLandScape::FlattenArea(CArea *area, int height, bool save, bool forceHeight, bool smooth ) +{ + vec3_t temp; + ivec3_t icoords; + int radius; + int height2; + + if(save) + { + SaveArea(area); + // mAreas.push_back(*area); + } + + // Work out coords in the heightmap + VectorSubtract(area->GetPosition(), mBounds[0], temp); + icoords[0] = temp[0] / (mBounds[1][0] - mBounds[0][0]) * (float)GetRealWidth ( ); + icoords[1] = temp[1] / (mBounds[1][1] - mBounds[0][1]) * (float)GetRealHeight ( ); + +// VectorInverseScaleVector(temp, mTerxelSize, icoords); + + // round up, we'd rather have a little more area flattened than have less then what was requested + radius = (int)ceilf( (area->GetRadius() / mTerxelSize[1]) ); + + // Work out the average height of the surrounding terrain + height2 = height; + if(height < 0) + { + ivec3_t info; + + info[0] = 0; + info[1] = 0; + CM_CircularIterate(mHeightMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], 0, radius, info, CM_GetAverage); + if(info[1]) + { + height = info[0] / info[1]; + } + } + else + { + height = height & 0x7F; + } + + if ( smooth ) + { + CM_CircularIterate(mHeightMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], radius, radius * 3, &height, CM_Smooth); + } + + if ( forceHeight ) + { + CM_CircularIterate(mHeightMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], 0, radius + 1, &height, CM_ForceHeight ); + CM_CircularIterate(mFlattenMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], 0, radius + 1, &height2, CM_ForceHeight ); + } + else if ( smooth ) + { + CM_CircularIterate(mHeightMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], 0, radius, &height, CM_Smooth); + } +} + +void CM_BelowLevel(byte *data, float lerp, int *info) +{ + info[1]++; + if(*data < info[2]) + { + info[0]++; + } +} + +float CCMLandScape::FractionBelowLevel(CArea *area, int height) +{ + vec3_t temp; + ivec3_t icoords, info; + int count; + float level; + + // Work out coords in the heightmap + VectorSubtract(area->GetPosition(), mBounds[0], temp); + VectorInverseScaleVector(temp, mTerxelSize, icoords); + + // Work out radius of area in heightmap entries + count = area->GetRadius() / mTerxelSize[1]; + + info[0] = 0; + info[1] = 0; + + info[2] = height; + if(height < 0) + { + info[2] = mBaseWaterHeight; + } + CM_CircularIterate(mHeightMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], 0, count, info, CM_BelowLevel); + + level = 0.0f; + if(info[1]) + { + level = (float)info[0] / info[1]; + } + + return(level); +} + +CArea *CCMLandScape::GetFirstArea(void) +{ + if(!mAreas.size()) + { + return(NULL); + } + mAreasIt = mAreas.begin(); + return (*mAreasIt); +} + +CArea *CCMLandScape::GetFirstObjectiveArea(void) +{ + if(!mAreas.size()) + { + return(NULL); + } + mAreasIt = mAreas.begin(); + + while (mAreasIt != mAreas.end()) + { + // run through the areas to find the player area + if((*mAreasIt)->GetType() == AT_OBJECTIVE) + { + return (*mAreasIt); + } + mAreasIt++; + } + return(NULL); +} + +CArea *CCMLandScape::GetPlayerArea(void) +{ // do me + if(!mAreas.size()) + { + return(NULL); + } + mAreasIt = mAreas.begin(); + + while (mAreasIt != mAreas.end()) + { + // run through the areas to find the player area + if((*mAreasIt)->GetType() == AT_PLAYER) + { + return (*mAreasIt); + } + mAreasIt++; + } + return(NULL); +} + +CArea *CCMLandScape::GetNextArea(void) +{ + mAreasIt++; + if(mAreasIt == mAreas.end()) + { + return(NULL); + } + return (*mAreasIt); +} + +CArea *CCMLandScape::GetNextObjectiveArea(void) +{ + mAreasIt++; + + while (mAreasIt != mAreas.end()) + { + // run through the areas to find the player area + if((*mAreasIt)->GetType() == AT_OBJECTIVE) + { + return (*mAreasIt); + } + mAreasIt++; + } + return(NULL); +} + +bool CCMLandScape::AreaCollision(CArea *area, int *areaTypes, int areaTypeCount) +{ + CArea *areas; + int i; + float segment; + bool collision; + + areas = GetFirstArea(); + while(areas) + { + collision = false; + + if(area->GetVillageID() == areas->GetVillageID()) + { + // Check for being too close angularly + if(area->GetAngleDiff() && areas->GetAngleDiff()) + { + segment = areas->GetAngle() - area->GetAngle(); + if(segment < M_PI) + { + segment += 2 * M_PI; + } + if(segment > M_PI) + { + segment -= 2 * M_PI; + } + if(fabsf(segment) < areas->GetAngleDiff() + area->GetAngleDiff()) + { + collision = true; + } + } + } + + // Check for buildings being too close together + if(Distance(areas->GetPosition(), area->GetPosition()) < areas->GetRadius() + area->GetRadius()) + { + collision = true; + } + + if(collision) + { + // If no area type list was specified then all areas are fair game + if ( !areaTypes ) + { + return true; + } + + for(i = 0; i < areaTypeCount; i++) + { + if(areas->GetType() == areaTypes[i]) + { + return(true); + } + } + } + areas = GetNextArea(); + } + return(false); +} + +void CCMLandScape::rand_seed(int seed) +{ + holdrand = seed; + Com_Printf("rand_seed = %d\n", holdrand); +} + +float CCMLandScape::flrand(float min, float max) +{ + float result; + + assert((max - min) < 32768); + + holdrand = (holdrand * 214013L) + 2531011L; + result = (float)(holdrand >> 17); // 0 - 32767 range + result = ((result * (max - min)) / 32768.0F) + min; +// Com_Printf("flrand: Seed = %d\n", holdrand); + + return(result); +} + +int CCMLandScape::irand(int min, int max) +{ + int result; + + assert((max - min) < 32768); + + max++; + holdrand = (holdrand * 214013L) + 2531011L; + result = holdrand >> 17; + result = ((result * (max - min)) >> 15) + min; +// Com_Printf("irand: Seed = %d\n", holdrand); + + return(result); +} + +CCMLandScape::~CCMLandScape(void) +{ + if(mHeightMap) + { + Z_Free(mHeightMap); + mHeightMap = NULL; + } + if(mFlattenMap) + { + Z_Free(mFlattenMap); + mFlattenMap = NULL; + } + if(mPatchBrushData) + { + Z_Free(mPatchBrushData); + mPatchBrushData = NULL; + } + if(mPatches) + { + Z_Free(mPatches); + mPatches = NULL; + } + if (mRandomTerrain) + { + delete mRandomTerrain; + } + + for(mAreasIt=mAreas.begin(); mAreasIt != mAreas.end(); mAreasIt++) + { + delete (*mAreasIt); + } + + mAreas.clear(); +} + +class CCMLandScape *CM_InitTerrain(const char *configstring, thandle_t terrainId, bool server) +{ + CCMLandScape *ls; + + ls = new CCMLandScape(configstring, server); + ls->SetTerrainId(terrainId); + + return(ls); +} + +void CM_TerrainPatchIterate(const class CCMLandScape *landscape, void (*IterateFunc)( CCMPatch *, void * ), void *userdata) +{ + landscape->TerrainPatchIterate(IterateFunc, userdata); +} + +float CM_GetWorldHeight(const CCMLandScape *landscape, vec3_t origin, const vec3pair_t bounds, bool aboveGround) +{ + return landscape->GetWorldHeight(origin, bounds, aboveGround); +} + +void CM_FlattenArea(CCMLandScape *landscape, CArea *area, int height, bool save, bool forceHeight, bool smooth ) +{ + landscape->FlattenArea(area, height, save, forceHeight, smooth ); +} + +void CM_CarveBezierCurve(CCMLandScape *landscape, int numCtls, vec3_t* ctls, int steps, int depth, int size ) +{ + landscape->CarveBezierCurve(numCtls, ctls, steps, depth, size ); +} + +void CM_SaveArea(CCMLandScape *landscape, CArea *area) +{ + landscape->SaveArea(area); +} + +float CM_FractionBelowLevel(CCMLandScape *landscape, CArea *area, int height) +{ + return(landscape->FractionBelowLevel(area, height)); +} + +bool CM_AreaCollision(class CCMLandScape *landscape, class CArea *area, int *areaTypes, int areaTypeCount) +{ + return(landscape->AreaCollision(area, areaTypes, areaTypeCount)); +} + +CArea *CM_GetFirstArea(CCMLandScape *landscape) +{ + return(landscape->GetFirstArea()); +} + +CArea *CM_GetFirstObjectiveArea(CCMLandScape *landscape) +{ + return(landscape->GetFirstObjectiveArea()); +} + +CArea *CM_GetPlayerArea(CCMLandScape *landscape) +{ + return(landscape->GetPlayerArea()); +} + +CArea *CM_GetNextArea(CCMLandScape *landscape) +{ + return(landscape->GetNextArea()); +} + +CArea *CM_GetNextObjectiveArea(CCMLandScape *landscape) +{ + return(landscape->GetNextObjectiveArea()); +} + +CRandomTerrain *CreateRandomTerrain(const char *config, CCMLandScape *landscape, byte *heightmap, int width, int height) +{ + CRandomTerrain *RandomTerrain = 0; + +#ifndef PRE_RELEASE_DEMO + char *ptr; + unsigned long seed; + + seed = strtoul(Info_ValueForKey(config, "seed"), &ptr, 10); + + landscape->rand_seed(seed); + + RandomTerrain = new CRandomTerrain; + RandomTerrain->Init(landscape, heightmap, width, height); +#endif // #ifndef PRE_RELEASE_DEMO + +/* + RandomTerrain->CreatePath(0, -1, 0, 9, 0.1, 0.5, 0.5, 0.5, 0.05, 0.08, 0.31, 0.1, 3); + RandomTerrain->CreatePath(1, 0, 0, 6, 0.5, 0.5, 0.9, 0.1, 0.08, 0.1, 0.31, 0.1, 0.9); + RandomTerrain->CreatePath(2, 0, 0, 6, 0.5, 0.5, 0.9, 0.9, 0.08, 0.1, 0.31, 0.1, 0.9); + + RandomTerrain->Generate(); +*/ + + return RandomTerrain; +} + + +// end + +#ifdef _WIN32 +#pragma optimize("p", off) +#endif diff --git a/codemp/qcommon/cm_terrainmap.cpp b/codemp/qcommon/cm_terrainmap.cpp new file mode 100644 index 0000000..5f2bc98 --- /dev/null +++ b/codemp/qcommon/cm_terrainmap.cpp @@ -0,0 +1,497 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "cm_local.h" +#include "cm_patch.h" +#include "cm_landscape.h" +#include "../qcommon/GenericParser2.h" +//#include "image.h" +//#include "../qcommon/q_imath.h" +#include "cm_terrainmap.h" +#include "cm_draw.h" +#include "../png/png.h" + +static CTerrainMap *TerrainMap = 0; + +// Hack. This shouldn't be here, but it's easier than including tr_local.h +typedef unsigned int GLenum; + +#ifdef _XBOX +void R_LoadImage( const char *shortname, byte **pic, int *width, int *height, int *mipcount, GLenum *format ); +#else +void R_LoadImage( const char *name, byte **pic, int *width, int *height, GLenum *format ) ; +#endif + +void R_CreateAutomapImage( const char *name, const byte *pic, int width, int height, + qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ); + +// simple function for getting a proper color for a side +inline CPixel32 SideColor(int side) +{ + CPixel32 col(255,255,255); + switch (side) + { + default: + break; + case SIDE_BLUE: + col = CPixel32(0,0,192); + break; + case SIDE_RED: + col = CPixel32(192,0,0); + break; + } + return col; +} + +CTerrainMap::CTerrainMap(CCMLandScape *landscape) : + mLandscape(landscape) +{ + ApplyBackground(); + ApplyHeightmap(); + + CDraw32 draw; + draw.SetBuffer((CPixel32*) mImage); + draw.SetBufferSize(TM_WIDTH,TM_HEIGHT,TM_WIDTH); + + // create version with paths and water shown + int x,y; + int water; + int land; + + for (y=0; yGetBaseWaterHeight() - cp.a)*4, 0, 255); + cp.a = 255; + + if (x > TM_BORDER && x < (TM_WIDTH-TM_BORDER) && + y > TM_BORDER && y < (TM_WIDTH-TM_BORDER)) + { + cp = ALPHA_PIX (CPixel32(0,0,0), cp, land, 256-land); + if (water > 0) + cp = ALPHA_PIX (CPixel32(0,0,255), cp, water, 256-water); + } + + draw.PutPix(x, y, cp); + } + + // Load icons for symbols on map + GLenum format; +#ifdef _XBOX + int mipcount; + + R_LoadImage("gfx/menus/rmg/start", (byte**)&mSymStart, &mSymStartWidth, &mSymStartHeight, &mipcount, &format); + R_LoadImage("gfx/menus/rmg/end", (byte**)&mSymEnd, &mSymEndWidth, &mSymEndHeight, &mipcount, &format); + R_LoadImage("gfx/menus/rmg/objective", (byte**)&mSymObjective, &mSymObjectiveWidth, &mSymObjectiveHeight, &mipcount, &format); + + R_LoadImage("gfx/menus/rmg/building", (byte**)&mSymBld, &mSymBldWidth, &mSymBldHeight, &mipcount, &format); +#else + R_LoadImage("gfx/menus/rmg/start", (byte**)&mSymStart, &mSymStartWidth, &mSymStartHeight, &format); + R_LoadImage("gfx/menus/rmg/end", (byte**)&mSymEnd, &mSymEndWidth, &mSymEndHeight, &format); + R_LoadImage("gfx/menus/rmg/objective", (byte**)&mSymObjective, &mSymObjectiveWidth, &mSymObjectiveHeight, &format); + + R_LoadImage("gfx/menus/rmg/building", (byte**)&mSymBld, &mSymBldWidth, &mSymBldHeight, &format); +#endif +} + +CTerrainMap::~CTerrainMap() +{ + if (mSymStart) + { + Z_Free(mSymStart); + mSymStart = NULL; + } + + if (mSymEnd) + { + Z_Free(mSymEnd); + mSymEnd = NULL; + } + + if (mSymBld) + { + Z_Free(mSymBld); + mSymBld = NULL; + } + + if (mSymObjective) + { + Z_Free(mSymObjective); + mSymObjective = NULL; + } + + CDraw32::CleanUp(); +} + +void CTerrainMap::ApplyBackground(void) +{ + int x, y; + byte *outPos; + float xRel, yRel, xInc, yInc; + byte *backgroundImage; + int backgroundWidth, backgroundHeight, backgroundDepth; + int pos; + GLenum format; + + memset(mImage, 255, sizeof(mBufImage)); +// R_LoadImage("textures\\kamchatka\\ice", &backgroundImage, &backgroundWidth, &backgroundHeight, &format);0 + backgroundDepth = 4; +#ifdef _XBOX + int mipcount; + + R_LoadImage("gfx\\menus\\rmg\\01_bg", &backgroundImage, &backgroundWidth, &backgroundHeight, &mipcount, &format); +#else + R_LoadImage("gfx\\menus\\rmg\\01_bg", &backgroundImage, &backgroundWidth, &backgroundHeight, &format); +#endif + if (backgroundImage) + { + outPos = (byte *)mBufImage; + xInc = (float)backgroundWidth / (float)TM_WIDTH; + yInc = (float)backgroundHeight / (float)TM_HEIGHT; + + yRel = 0.0; + for(y=0;yGetHeightMap(); + int width = mLandscape->GetRealWidth(); + int height = mLandscape->GetRealHeight(); + byte *outPos; + unsigned tempColor; + float xRel, yRel, xInc, yInc; + int count; + + outPos = (byte *)mBufImage; + outPos += (((TM_BORDER * TM_WIDTH) + TM_BORDER) * 4); + xInc = (float)width / (float)(TM_REAL_WIDTH); + yInc = (float)height / (float)(TM_REAL_HEIGHT); + + // add in height map as alpha + yRel = 0.0; + for(y=0;y= 1.0) + { + tempColor += inPos[(((int)(yRel-0.5))*width) + ((int)xRel)]; + count++; + } + if (yRel <= height-2) + { + tempColor += inPos[(((int)(yRel+0.5))*width) + ((int)xRel)]; + count++; + } + if (xRel >= 1.0) + { + tempColor += inPos[(((int)(yRel))*width) + ((int)(xRel-0.5))]; + count++; + } + if (xRel <= width-2) + { + tempColor += inPos[(((int)(yRel))*width) + ((int)(xRel+0.5))]; + count++; + } + tempColor /= count; + + outPos[3] = tempColor; + outPos += 4; + + // x is flipped! + xRel -= xInc; + } + outPos += TM_BORDER * 4 * 2; + + yRel += yInc; + } +} + +// Convert position in game coords to automap coords +void CTerrainMap::ConvertPos(int& x, int& y) +{ + x = ((x - mLandscape->GetMins()[0]) / mLandscape->GetSize()[0]) * TM_REAL_WIDTH; + y = ((y - mLandscape->GetMins()[1]) / mLandscape->GetSize()[1]) * TM_REAL_HEIGHT; + + // x is flipped! + x = TM_REAL_WIDTH - x - 1; + + // border + x += TM_BORDER; + y += TM_BORDER; +} + +void CTerrainMap::AddStart(int x, int y, int side) +{ + ConvertPos(x, y); + + CDraw32 draw; + draw.BlitColor(x-mSymStartWidth/2, y-mSymStartHeight/2, mSymStartWidth, mSymStartHeight, + (CPixel32*)mSymStart, 0, 0, mSymStartWidth, SideColor(side)); +} + +void CTerrainMap::AddEnd(int x, int y, int side) +{ + ConvertPos(x, y); + + CDraw32 draw; + draw.BlitColor(x-mSymEndWidth/2, y-mSymEndHeight/2, mSymEndWidth, mSymEndHeight, + (CPixel32*)mSymEnd, 0, 0, mSymEndWidth, SideColor(side)); +} + +void CTerrainMap::AddObjective(int x, int y, int side) +{ + ConvertPos(x, y); + + CDraw32 draw; + draw.BlitColor(x-mSymObjectiveWidth/2, y-mSymObjectiveHeight/2, mSymObjectiveWidth, mSymObjectiveHeight, + (CPixel32*)mSymObjective, 0, 0, mSymObjectiveWidth, SideColor(side)); +} + +void CTerrainMap::AddBuilding(int x, int y, int side) +{ + ConvertPos(x, y); + + CDraw32 draw; + draw.BlitColor(x-mSymBldWidth/2, y-mSymBldHeight/2, mSymBldWidth, mSymBldHeight, + (CPixel32*)mSymBld, 0, 0, mSymBldWidth, SideColor(side)); +} + +void CTerrainMap::AddNPC(int x, int y, bool friendly) +{ + ConvertPos(x, y); + + CDraw32 draw; + if (friendly) + draw.DrawCircle(x,y,3, CPixel32(0,192,0), CPixel32(0,0,0,0)); + else + draw.DrawCircle(x,y,3, CPixel32(192,0,0), CPixel32(0,0,0,0)); +} + +void CTerrainMap::AddNode(int x, int y) +{ + ConvertPos(x, y); + + CDraw32 draw; + draw.DrawCircle(x,y,20, CPixel32(255,255,255), CPixel32(0,0,0,0)); +} + +void CTerrainMap::AddWallRect(int x, int y, int side) +{ + ConvertPos(x, y); + + CDraw32 draw; + switch (side) + { + default: + draw.DrawBox(x-1,y-1,3,3,CPixel32(192,192,192,128)); + break; + case SIDE_BLUE: + draw.DrawBox(x-1,y-1,3,3,CPixel32(0,0,192,128)); + break; + case SIDE_RED: + draw.DrawBox(x-1,y-1,3,3,CPixel32(192,0,0,128)); + break; + } +} + +void CTerrainMap::AddPlayer(vec3_t origin, vec3_t angles) +{ + // draw player start on automap + CDraw32 draw; + + vec3_t up; + vec3_t pt[4] = {{0,0,0},{-5,-5,0},{10,0,0},{-5,5,0}}; + vec3_t p; + int x,y,i; + float facing; + POINT poly[4]; + + facing = angles[1]; + + up[0] = 0; + up[1] = 0; + up[2] = 1; + + x = (int)origin[0]; + y = (int)origin[1]; + ConvertPos(x, y); + x++; y++; + + for (i=0; i<4; i++) + { + RotatePointAroundVector( p, up, pt[i], facing ); + poly[i].x = (int)(-p[0] + x); + poly[i].y = (int)(p[1] + y); + } + + // draw arrowhead shadow + draw.DrawPolygon(4, poly, CPixel32(0,0,0,128), CPixel32(0,0,0,128)); + + // draw arrowhead + for (i=0; i<4; i++) + { + poly[i].x--; + poly[i].y--; + } + draw.DrawPolygon(4, poly, CPixel32(255,255,255), CPixel32(255,255,255)); +} + +void CTerrainMap::Upload(vec3_t player_origin, vec3_t player_angles) +{ + CDraw32 draw; + + // copy completed map to mBufImage + draw.SetBuffer((CPixel32*) mBufImage); + draw.SetBufferSize(TM_WIDTH,TM_HEIGHT,TM_WIDTH); + + draw.Blit(0, 0, TM_WIDTH, TM_HEIGHT, + (CPixel32*)mImage, 0, 0, TM_WIDTH); + + // now draw player's location on map + if (player_origin) + { + AddPlayer(player_origin, player_angles); + } + + draw.SetAlphaBuffer(255); + + R_CreateAutomapImage("*automap", (unsigned char *)draw.buffer, TM_WIDTH, TM_HEIGHT, qfalse, qfalse, qtrue, qfalse); + + draw.SetBuffer((CPixel32*) mImage); +} + +void CTerrainMap::SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed) +{ + //ri.COM_SavePNG(va("save/%s_%s_%s.png", terrainName, missionName, seed), + // (unsigned char *)mImage, TM_WIDTH, TM_HEIGHT, 4); + PNG_Save(va("save/%s_%s_%s.png", terrainName, missionName, seed), + (unsigned char *)mImage, TM_WIDTH, TM_HEIGHT, 4); +} + +void CM_TM_Create(CCMLandScape *landscape) +{ + if (TerrainMap) + { + CM_TM_Free(); + } + + TerrainMap = new CTerrainMap(landscape); +} + +void CM_TM_Free(void) +{ + if (TerrainMap) + { + delete TerrainMap; + TerrainMap = 0; + } +} + +void CM_TM_AddStart(int x, int y, int side) +{ + if (TerrainMap) + { + TerrainMap->AddStart(x, y, side); + } +} + +void CM_TM_AddEnd(int x, int y, int side) +{ + if (TerrainMap) + { + TerrainMap->AddEnd(x, y, side); + } +} + +void CM_TM_AddObjective(int x, int y, int side) +{ + if (TerrainMap) + { + TerrainMap->AddObjective(x, y, side); + } +} + +void CM_TM_AddNPC(int x, int y, bool friendly) +{ + if (TerrainMap) + { + TerrainMap->AddNPC(x, y, friendly); + } +} + +void CM_TM_AddNode(int x, int y) +{ + if (TerrainMap) + { + TerrainMap->AddNode(x, y); + } +} + +void CM_TM_AddBuilding(int x, int y, int side) +{ + if (TerrainMap) + { + TerrainMap->AddBuilding(x, y, side); + } +} + +void CM_TM_AddWallRect(int x, int y, int side) +{ + if (TerrainMap) + { + TerrainMap->AddWallRect(x, y, side); + } +} + +void CM_TM_Upload(vec3_t player_origin, vec3_t player_angles) +{ + if (TerrainMap) + { + TerrainMap->Upload(player_origin, player_angles); + } +} + +void CM_TM_SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed) +{ + if (TerrainMap) + { // write out automap + TerrainMap->SaveImageToDisk(terrainName, missionName, seed); + } +} + +void CM_TM_ConvertPosition(int &x, int &y, int Width, int Height) +{ + if (TerrainMap) + { + TerrainMap->ConvertPos(x, y); + x = x * Width / TM_WIDTH; + y = y * Height / TM_HEIGHT; + } +} + diff --git a/codemp/qcommon/cm_terrainmap.h b/codemp/qcommon/cm_terrainmap.h new file mode 100644 index 0000000..6899207 --- /dev/null +++ b/codemp/qcommon/cm_terrainmap.h @@ -0,0 +1,83 @@ +#pragma once +#if !defined(CM_TERRAINMAP_H_INC) +#define CM_TERRAINMAP_H_INC + +#ifdef _XBOX +#define TM_WIDTH 64 +#define TM_HEIGHT 64 +#define TM_BORDER 4 +#else +#define TM_WIDTH 512 +#define TM_HEIGHT 512 +#define TM_BORDER 16 +#endif +#define TM_REAL_WIDTH (TM_WIDTH-TM_BORDER-TM_BORDER) +#define TM_REAL_HEIGHT (TM_HEIGHT-TM_BORDER-TM_BORDER) + +class CTerrainMap +{ +private: + byte mImage[TM_HEIGHT][TM_WIDTH][4]; // image to output + byte mBufImage[TM_HEIGHT][TM_WIDTH][4]; // src data for image, color and bump + + byte* mSymBld; + int mSymBldWidth; + int mSymBldHeight; + + byte* mSymStart; + int mSymStartWidth; + int mSymStartHeight; + + byte* mSymEnd; + int mSymEndWidth; + int mSymEndHeight; + + byte* mSymObjective; + int mSymObjectiveWidth; + int mSymObjectiveHeight; + + CCMLandScape *mLandscape; + + void ApplyBackground(void); + void ApplyHeightmap(void); + +public: + CTerrainMap(CCMLandScape *landscape); + ~CTerrainMap(); + + void ConvertPos(int& x, int& y); + void AddBuilding(int x, int y, int side); + void AddStart(int x, int y, int side); + void AddEnd(int x, int y, int side); + void AddObjective(int x, int y, int side); + void AddNPC(int x, int y, bool friendly); + void AddWallRect(int x, int y, int side); + void AddNode(int x, int y); + void AddPlayer(vec3_t origin, vec3_t angles); + + void Upload(vec3_t player_origin, vec3_t player_angles); + void SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed); +}; + +enum +{ + SIDE_NONE =0, + SIDE_BLUE =1, + SIDE_RED = 2 +}; + +void CM_TM_Create(CCMLandScape *landscape); +void CM_TM_Free(void); +void CM_TM_AddStart(int x, int y, int side = SIDE_NONE); +void CM_TM_AddEnd(int x, int y, int side = SIDE_NONE); +void CM_TM_AddObjective(int x, int y, int side = SIDE_NONE); +void CM_TM_AddNPC(int x, int y, bool friendly); +void CM_TM_AddWallRect(int x, int y, int side = SIDE_NONE); +void CM_TM_AddNode(int x, int y); +void CM_TM_AddBuilding(int x, int y, int side = SIDE_NONE); +void CM_TM_Upload(vec3_t player_origin, vec3_t player_angles); +void CM_TM_SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed); +void CM_TM_ConvertPosition(int &x, int &y, int Width, int Height); + +#endif CM_TERRAINMAP_H_INC + diff --git a/codemp/qcommon/cm_test.cpp b/codemp/qcommon/cm_test.cpp new file mode 100644 index 0000000..bbaeb12 --- /dev/null +++ b/codemp/qcommon/cm_test.cpp @@ -0,0 +1,575 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "cm_local.h" + +#ifdef _XBOX +#include "../renderer/tr_local.h" +#endif + +/* +================== +CM_PointLeafnum_r + +================== +*/ +int CM_PointLeafnum_r( const vec3_t p, int num, clipMap_t *local ) { + float d; + cNode_t *node; + cplane_t *plane; + + while (num >= 0) + { + node = local->nodes + num; + +#ifdef _XBOX + plane = cmg.planes + node->planeNum;//tr.world->nodes[num].planeNum; +#else + plane = node->plane; +#endif + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->children[1]; + else + num = node->children[0]; + } + + c_pointcontents++; // optimize counter + + return -1 - num; +} + +int CM_PointLeafnum( const vec3_t p ) { + if ( !cmg.numNodes ) { // map not loaded + return 0; + } + return CM_PointLeafnum_r (p, 0, &cmg); +} + + +/* +====================================================================== + +LEAF LISTING + +====================================================================== +*/ + + +void CM_StoreLeafs( leafList_t *ll, int nodenum ) { + int leafNum; + + leafNum = -1 - nodenum; + + // store the lastLeaf even if the list is overflowed + if ( cmg.leafs[ leafNum ].cluster != -1 ) { + ll->lastLeaf = leafNum; + } + + if ( ll->count >= ll->maxcount) { + ll->overflowed = qtrue; + return; + } + ll->list[ ll->count++ ] = leafNum; +} + +void CM_StoreBrushes( leafList_t *ll, int nodenum ) { + int i, k; + int leafnum; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + + leafnum = -1 - nodenum; + + leaf = &cmg.leafs[leafnum]; + + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = cmg.leafbrushes[leaf->firstLeafBrush+k]; + b = &cmg.brushes[brushnum]; + if ( b->checkcount == cmg.checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = cmg.checkcount; + for ( i = 0 ; i < 3 ; i++ ) { + if ( b->bounds[0][i] >= ll->bounds[1][i] || b->bounds[1][i] <= ll->bounds[0][i] ) { + break; + } + } + if ( i != 3 ) { + continue; + } + if ( ll->count >= ll->maxcount) { + ll->overflowed = qtrue; + return; + } + ((cbrush_t **)ll->list)[ ll->count++ ] = b; + } +#if 0 + // store patches? + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstleafsurface + k ] ]; + if ( !patch ) { + continue; + } + } +#endif +} + +/* +============= +CM_BoxLeafnums + +Fills in a list of all the leafs touched +============= +*/ +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ) { + cplane_t *plane; + cNode_t *node; + int s; + + while (1) { + if (nodenum < 0) { + ll->storeLeafs( ll, nodenum ); + return; + } + + node = &cmg.nodes[nodenum]; + +#ifdef _XBOX + plane = cmg.planes + node->planeNum;//tr.world->nodes[nodenum].planeNum; +#else + plane = node->plane; +#endif + + s = BoxOnPlaneSide( ll->bounds[0], ll->bounds[1], plane ); + if (s == 1) { + nodenum = node->children[0]; + } else if (s == 2) { + nodenum = node->children[1]; + } else { + // go down both + CM_BoxLeafnums_r( ll, node->children[0] ); + nodenum = node->children[1]; + } + + } +} + +/* +================== +CM_BoxLeafnums +================== +*/ +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *boxList, int listsize, int *lastLeaf) { + //rwwRMG - changed to boxList to not conflict with list type + leafList_t ll; + + cmg.checkcount++; + + VectorCopy( mins, ll.bounds[0] ); + VectorCopy( maxs, ll.bounds[1] ); + ll.count = 0; + ll.maxcount = listsize; + ll.list = boxList; + ll.storeLeafs = CM_StoreLeafs; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + CM_BoxLeafnums_r( &ll, 0 ); + + *lastLeaf = ll.lastLeaf; + return ll.count; +} + +/* +================== +CM_BoxBrushes +================== +*/ +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **boxList, int listsize ) { + //rwwRMG - changed to boxList to not conflict with list type + leafList_t ll; + + cmg.checkcount++; + + VectorCopy( mins, ll.bounds[0] ); + VectorCopy( maxs, ll.bounds[1] ); + ll.count = 0; + ll.maxcount = listsize; + ll.list = (int *)boxList; + ll.storeLeafs = CM_StoreBrushes; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + CM_BoxLeafnums_r( &ll, 0 ); + + return ll.count; +} + + +//==================================================================== + + +/* +================== +CM_PointContents + +================== +*/ +int CM_PointContents( const vec3_t p, clipHandle_t model ) { + int leafnum; + int i, k; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + int contents; + float d; + cmodel_t *clipm; + clipMap_t *local; + + if (!cmg.numNodes) { // map not loaded + return 0; + } + + if ( model ) + { + clipm = CM_ClipHandleToModel( model, &local ); + if (clipm->firstNode != -1) + { + leafnum = CM_PointLeafnum_r (p, 0, local); + leaf = &local->leafs[leafnum]; + } + else + { + leaf = &clipm->leaf; + } + } + else + { + local = &cmg; + leafnum = CM_PointLeafnum_r (p, 0, &cmg); + leaf = &local->leafs[leafnum]; + } + + contents = 0; + for (k=0 ; knumLeafBrushes ; k++) { + brushnum = local->leafbrushes[leaf->firstLeafBrush+k]; + b = &local->brushes[brushnum]; + + // see if the point is in the brush + for ( i = 0 ; i < b->numsides ; i++ ) { +#ifdef _XBOX + d = DotProduct( p, cmg.planes[b->sides[i].planeNum.GetValue()].normal ); +#else + d = DotProduct( p, b->sides[i].plane->normal ); +#endif +// FIXME test for Cash +// if ( d >= b->sides[i].plane->dist ) { +#ifdef _XBOX + if ( d > cmg.planes[b->sides[i].planeNum.GetValue()].dist ) { +#else + if ( d > b->sides[i].plane->dist ) { +#endif + break; + } + } + + if ( i == b->numsides ) + { + contents |= b->contents; +/* + if(cmg.landScape && (contents & CONTENTS_TERRAIN)) + { + if(p[2] < cmg.landScape->GetWaterHeight()) + { + contents |= cmg.landScape->GetWaterContents(); + } + } +*/ + } + } + + return contents; +} + +/* +================== +CM_TransformedPointContents + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles) { + vec3_t p_l; + vec3_t temp; + vec3_t forward, right, up; + + // subtract origin offset + VectorSubtract (p, origin, p_l); + + // rotate start and end into the models frame of reference + if ( model != BOX_MODEL_HANDLE && + (angles[0] || angles[1] || angles[2]) ) + { + AngleVectors (angles, forward, right, up); + + VectorCopy (p_l, temp); + p_l[0] = DotProduct (temp, forward); + p_l[1] = -DotProduct (temp, right); + p_l[2] = DotProduct (temp, up); + } + + return CM_PointContents( p_l, model ); +} + + + +/* +=============================================================================== + +PVS + +=============================================================================== +*/ +#ifdef _XBOX +extern trGlobals_t tr; +const byte *CM_ClusterPVS (int cluster) { + if (cluster < 0 || cluster >= cmg.numClusters || !cmg.vised ) { + return NULL; + } + + return cmg.visibility->Decompress(cluster * cmg.clusterBytes, + cmg.numClusters); +} + +#else + +byte *CM_ClusterPVS (int cluster) { + if (cluster < 0 || cluster >= cmg.numClusters || !cmg.vised ) { + return cmg.visibility; + } + + return cmg.visibility + cluster * cmg.clusterBytes; +} + +#endif // _XBOX + + + +/* +=============================================================================== + +AREAPORTALS + +=============================================================================== +*/ +#ifdef _XBOX +void CM_FloodArea_r( int areaNum, int floodnum) { + int i; + cArea_t *area; + int *con; + + area = &cmg.areas[ areaNum ]; + + if ( area->floodvalid == cmg.floodvalid ) { + if (area->floodnum == floodnum) + return; + Com_Error (ERR_DROP, "FloodArea_r: reflooded"); + } + + area->floodnum = floodnum; + area->floodvalid = cmg.floodvalid; + con = cmg.areaPortals + areaNum * cmg.numAreas; + for ( i=0 ; i < cmg.numAreas ; i++ ) { + if ( con[i] > 0 ) { + CM_FloodArea_r( i, floodnum ); + } + } +} +#else // _XBOX + +void CM_FloodArea_r( int areaNum, int floodnum, clipMap_t &cm ) { + int i; + cArea_t *area; + int *con; + + area = &cm.areas[ areaNum ]; + + if ( area->floodvalid == cm.floodvalid ) { + if (area->floodnum == floodnum) + return; + Com_Error (ERR_DROP, "FloodArea_r: reflooded"); + } + + area->floodnum = floodnum; + area->floodvalid = cm.floodvalid; + con = cm.areaPortals + areaNum * cm.numAreas; + for ( i=0 ; i < cm.numAreas ; i++ ) { + if ( con[i] > 0 ) { + CM_FloodArea_r( i, floodnum, cm ); + } + } +} + +#endif // _XBOX + +/* +==================== +CM_FloodAreaConnections + +==================== +*/ +#ifdef _XBOX +void CM_FloodAreaConnections( void ) { + int i; + cArea_t *area; + int floodnum; + + // all current floods are now invalid + cmg.floodvalid++; + floodnum = 0; + + for (i = 0 ; i < cmg.numAreas ; i++) { + area = &cmg.areas[i]; + if (area->floodvalid == cmg.floodvalid) { + continue; // already flooded into + } + floodnum++; + CM_FloodArea_r (i, floodnum); + } + +} +#else // _XBOX + +void CM_FloodAreaConnections( clipMap_t &cm ) { + int i; + cArea_t *area; + int floodnum; + + // all current floods are now invalid + cm.floodvalid++; + floodnum = 0; + + for (i = 0 ; i < cm.numAreas ; i++) { + area = &cm.areas[i]; + if (area->floodvalid == cm.floodvalid) { + continue; // already flooded into + } + floodnum++; + CM_FloodArea_r (i, floodnum, cm); + } + +} + +#endif // _XBOX + +/* +==================== +CM_AdjustAreaPortalState + +==================== +*/ +void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ) { + if ( area1 < 0 || area2 < 0 ) { + return; + } + + if ( area1 >= cmg.numAreas || area2 >= cmg.numAreas ) { + Com_Error (ERR_DROP, "CM_ChangeAreaPortalState: bad area number"); + } + + if ( open ) { + cmg.areaPortals[ area1 * cmg.numAreas + area2 ]++; + cmg.areaPortals[ area2 * cmg.numAreas + area1 ]++; + } else { + cmg.areaPortals[ area1 * cmg.numAreas + area2 ]--; + cmg.areaPortals[ area2 * cmg.numAreas + area1 ]--; + if ( cmg.areaPortals[ area2 * cmg.numAreas + area1 ] < 0 ) { + Com_Error (ERR_DROP, "CM_AdjustAreaPortalState: negative reference count"); + } + } + +#ifdef _XBOX + CM_FloodAreaConnections (); +#else + CM_FloodAreaConnections (cmg); +#endif +} + +/* +==================== +CM_AreasConnected + +==================== +*/ +qboolean CM_AreasConnected( int area1, int area2 ) { +#ifndef BSPC + if ( cm_noAreas->integer ) { + return qtrue; + } +#endif + + if ( area1 < 0 || area2 < 0 ) { + return qfalse; + } + + if (area1 >= cmg.numAreas || area2 >= cmg.numAreas) { + Com_Error (ERR_DROP, "area >= cmg.numAreas"); + } + + if (cmg.areas[area1].floodnum == cmg.areas[area2].floodnum) { + return qtrue; + } + return qfalse; +} + + +/* +================= +CM_WriteAreaBits + +Writes a bit vector of all the areas +that are in the same flood as the area parameter +Returns the number of bytes needed to hold all the bits. + +The bits are OR'd in, so you can CM_WriteAreaBits from multiple +viewpoints and get the union of all visible areas. + +This is used to cull non-visible entities from snapshots +================= +*/ +int CM_WriteAreaBits (byte *buffer, int area) +{ + int i; + int floodnum; + int bytes; + + bytes = (cmg.numAreas+7)>>3; + +#ifndef BSPC + if (cm_noAreas->integer || area == -1) +#else + if ( area == -1) +#endif + { // for debugging, send everything + Com_Memset (buffer, 255, bytes); + } + else + { + floodnum = cmg.areas[area].floodnum; + for (i=0 ; i>3] |= 1<<(i&7); + } + } + + return bytes; +} + diff --git a/codemp/qcommon/cm_trace.cpp b/codemp/qcommon/cm_trace.cpp new file mode 100644 index 0000000..8043f30 --- /dev/null +++ b/codemp/qcommon/cm_trace.cpp @@ -0,0 +1,2002 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "cm_local.h" +#include "cm_landscape.h" + +#ifdef _XBOX +#include "../renderer/tr_local.h" +#endif + +// always use bbox vs. bbox collision and never capsule vs. bbox or vice versa +//#define ALWAYS_BBOX_VS_BBOX +// always use capsule vs. capsule collision and never capsule vs. bbox or vice versa +//#define ALWAYS_CAPSULE_VS_CAPSULE + +//#define CAPSULE_DEBUG + +void CM_TraceThroughTerrain( traceWork_t *tw, trace_t &trace, cbrush_t *brush ); + +//#define TEST_TERRAIN_PHYSICS + +#ifdef TEST_TERRAIN_PHYSICS + +Be sure to un-link entity in void SP_terrain(gentity_t *ent) (yeah, left this uncommented to cause error / attention ) + + + void CM_TraceThroughTerrain( traceWork_t *tw, trace_t &trace, CCMLandScape *landscape); +#endif + +/* +=============================================================================== + +BASIC MATH + +=============================================================================== +*/ + +/* +================ +RotatePoint +================ +*/ +void RotatePoint(vec3_t point, /*const*/ vec3_t matrix[3]) { // bk: FIXME + vec3_t tvec; + + VectorCopy(point, tvec); + point[0] = DotProduct(matrix[0], tvec); + point[1] = DotProduct(matrix[1], tvec); + point[2] = DotProduct(matrix[2], tvec); +} + +/* +================ +TransposeMatrix +================ +*/ +void TransposeMatrix(/*const*/ vec3_t matrix[3], vec3_t transpose[3]) { // bk: FIXME + int i, j; + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + transpose[i][j] = matrix[j][i]; + } + } +} + +/* +================ +CreateRotationMatrix +================ +*/ +void CreateRotationMatrix(const vec3_t angles, vec3_t matrix[3]) { + AngleVectors(angles, matrix[0], matrix[1], matrix[2]); + VectorInverse(matrix[1]); +} + +/* +================ +CM_ProjectPointOntoVector +================ +*/ +void CM_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vDir, vec3_t vProj ) +{ + vec3_t pVec; + + VectorSubtract( point, vStart, pVec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vDir ), vDir, vProj ); +} + +/* +================ +CM_DistanceFromLineSquared +================ +*/ +float CM_DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2, vec3_t dir) { + vec3_t proj, t; + int j; + + CM_ProjectPointOntoVector(p, lp1, dir, proj); + for (j = 0; j < 3; j++) + if ((proj[j] > lp1[j] && proj[j] > lp2[j]) || + (proj[j] < lp1[j] && proj[j] < lp2[j])) + break; + if (j < 3) { + if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j])) + VectorSubtract(p, lp1, t); + else + VectorSubtract(p, lp2, t); + return VectorLengthSquared(t); + } + VectorSubtract(p, proj, t); + return VectorLengthSquared(t); +} + +/* +================ +CM_VectorDistanceSquared +================ +*/ +float CM_VectorDistanceSquared(vec3_t p1, vec3_t p2) { + vec3_t dir; + + VectorSubtract(p2, p1, dir); + return VectorLengthSquared(dir); +} + +/* +================ +SquareRootFloat +================ +*/ +float SquareRootFloat(float number) { + long i; + float x, y; + const float f = 1.5F; + + x = number * 0.5F; + y = number; + i = * ( long * ) &y; + i = 0x5f3759df - ( i >> 1 ); + y = * ( float * ) &i; + y = y * ( f - ( x * y * y ) ); + y = y * ( f - ( x * y * y ) ); + return number * y; +} + + +/* +=============================================================================== + +POSITION TESTING + +=============================================================================== +*/ + +/* +================ +CM_TestBoxInBrush +================ +*/ +void CM_TestBoxInBrush( traceWork_t *tw, trace_t &trace, cbrush_t *brush ) { + int i; + cplane_t *plane; + float dist; + float d1; + cbrushside_t *side; + float t; + vec3_t startp; + + if (!brush->numsides) { + return; + } + + // special test for axial + if ( tw->bounds[0][0] > brush->bounds[1][0] + || tw->bounds[0][1] > brush->bounds[1][1] + || tw->bounds[0][2] > brush->bounds[1][2] + || tw->bounds[1][0] < brush->bounds[0][0] + || tw->bounds[1][1] < brush->bounds[0][1] + || tw->bounds[1][2] < brush->bounds[0][2] + ) { + return; + } + + if ( tw->sphere.use ) { + // the first six planes are the axial planes, so we only + // need to test the remainder + for ( i = 6 ; i < brush->numsides ; i++ ) { + side = brush->sides + i; + +#ifdef _XBOX + plane = &cmg.planes[side->planeNum.GetValue()]; +#else + plane = side->plane; +#endif + + // adjust the plane distance apropriately for radius + dist = plane->dist + tw->sphere.radius; + // find the closest point on the capsule to the plane + t = DotProduct( plane->normal, tw->sphere.offset ); + if ( t > 0 ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + } + d1 = DotProduct( startp, plane->normal ) - dist; + // if completely in front of face, no intersection + if ( d1 > 0 ) { + return; + } + } + } else { + // the first six planes are the axial planes, so we only + // need to test the remainder + for ( i = 6 ; i < brush->numsides ; i++ ) { + side = brush->sides + i; + +#ifdef _XBOX + plane = &cmg.planes[side->planeNum.GetValue()]; +#else + plane = side->plane; +#endif + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + + // if completely in front of face, no intersection + if ( d1 > 0 ) { + return; + } + } + } + + // inside this brush + trace.startsolid = trace.allsolid = qtrue; + trace.fraction = 0; + trace.contents = brush->contents; +} + + +#ifdef _XBOX +static int CM_GetSurfaceIndex(int firstLeafSurface) +{ + if(firstLeafSurface > tr.world->nummarksurfaces || firstLeafSurface < 0) { + return cmg.leafsurfaces[ firstLeafSurface ] ; + } else { + return tr.world->marksurfaces[firstLeafSurface] - tr.world->surfaces; + } +} +#endif + +/* +================ +CM_TestInLeaf +================ +*/ +void CM_TestInLeaf( traceWork_t *tw, trace_t &trace, cLeaf_t *leaf, clipMap_t *local ) +{ + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // test box position against all brushes in the leaf + for (k=0 ; knumLeafBrushes ; k++) { + brushnum = local->leafbrushes[leaf->firstLeafBrush+k]; + b = &local->brushes[brushnum]; + if (b->checkcount == local->checkcount) { + continue; // already checked this brush in another leaf + } + b->checkcount = local->checkcount; + + if ( !(b->contents & tw->contents)) { + continue; + } + +#ifndef BSPC +/* + if (com_terrainPhysics->integer && cmg.landScape && (b->contents & CONTENTS_TERRAIN) ) + { + // Invalidate the checkcount for terrain as the terrain brush has to be processed + // many times. + b->checkcount--; + + CM_TraceThroughTerrain( tw, trace, b ); + // If inside a terrain brush don't bother with regular brush collision + continue; + } +*/ +#endif + CM_TestBoxInBrush( tw, trace, b ); + if ( trace.allsolid ) { + return; + } + } + + // test against all patches +#ifdef BSPC + if (1) { +#else + if ( !cm_noCurves->integer ) { +#endif //BSPC + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { +//#ifdef _XBOX +// int index = CM_GetSurfaceIndex(leaf->firstLeafSurface + k); +// patch = local->surfaces[ index ]; +//#else + patch = local->surfaces[ local->leafsurfaces[ leaf->firstLeafSurface + k ] ]; +//#endif + if ( !patch ) { + continue; + } + if ( patch->checkcount == local->checkcount ) { + continue; // already checked this brush in another leaf + } + patch->checkcount = local->checkcount; + + if ( !(patch->contents & tw->contents)) { + continue; + } + + if ( CM_PositionTestInPatchCollide( tw, patch->pc ) ) { + trace.startsolid = trace.allsolid = qtrue; + trace.fraction = 0; + trace.contents = patch->contents; + return; + } + } + } +} + +/* +================== +CM_TestCapsuleInCapsule + +capsule inside capsule check +================== +*/ +void CM_TestCapsuleInCapsule( traceWork_t *tw, trace_t &trace, clipHandle_t model ) { + int i; + vec3_t mins, maxs; + vec3_t top, bottom; + vec3_t p1, p2, tmp; + vec3_t offset, symetricSize[2]; + float radius, halfwidth, halfheight, offs, r; + + CM_ModelBounds(model, mins, maxs); + + VectorAdd(tw->start, tw->sphere.offset, top); + VectorSubtract(tw->start, tw->sphere.offset, bottom); + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + } + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + offs = halfheight - radius; + + r = Square(tw->sphere.radius + radius); + // check if any of the spheres overlap + VectorCopy(offset, p1); + p1[2] += offs; + VectorSubtract(p1, top, tmp); + if ( VectorLengthSquared(tmp) < r ) { + trace.startsolid = trace.allsolid = qtrue; + trace.fraction = 0; + } + VectorSubtract(p1, bottom, tmp); + if ( VectorLengthSquared(tmp) < r ) { + trace.startsolid = trace.allsolid = qtrue; + trace.fraction = 0; + } + VectorCopy(offset, p2); + p2[2] -= offs; + VectorSubtract(p2, top, tmp); + if ( VectorLengthSquared(tmp) < r ) { + trace.startsolid = trace.allsolid = qtrue; + trace.fraction = 0; + } + VectorSubtract(p2, bottom, tmp); + if ( VectorLengthSquared(tmp) < r ) { + trace.startsolid = trace.allsolid = qtrue; + trace.fraction = 0; + } + // if between cylinder up and lower bounds + if ( (top[2] >= p1[2] && top[2] <= p2[2]) || + (bottom[2] >= p1[2] && bottom[2] <= p2[2]) ) { + // 2d coordinates + top[2] = p1[2] = 0; + // if the cylinders overlap + VectorSubtract(top, p1, tmp); + if ( VectorLengthSquared(tmp) < r ) { + trace.startsolid = trace.allsolid = qtrue; + trace.fraction = 0; + } + } +} + +/* +================== +CM_TestBoundingBoxInCapsule + +bounding box inside capsule check +================== +*/ +void CM_TestBoundingBoxInCapsule( traceWork_t *tw, trace_t &trace, clipHandle_t model ) { + vec3_t mins, maxs, offset, size[2]; + clipHandle_t h; + cmodel_t *cmod; + int i; + + // mins maxs of the capsule + CM_ModelBounds(model, mins, maxs); + + // offset for capsule center + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + size[0][i] = mins[i] - offset[i]; + size[1][i] = maxs[i] - offset[i]; + tw->start[i] -= offset[i]; + tw->end[i] -= offset[i]; + } + + // replace the bounding box with the capsule + tw->sphere.use = qtrue; + tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0]; + tw->sphere.halfheight = size[1][2]; + VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius ); + + // replace the capsule with the bounding box + h = CM_TempBoxModel(tw->size[0], tw->size[1], qfalse); + // calculate collision + cmod = CM_ClipHandleToModel( h ); + CM_TestInLeaf( tw, trace, &cmod->leaf, &cmg ); +} + +/* +================== +CM_PositionTest +================== +*/ +#define MAX_POSITION_LEAFS 1024 +void CM_PositionTest( traceWork_t *tw, trace_t &trace ) { + int leafs[MAX_POSITION_LEAFS]; + int i; + leafList_t ll; + + // identify the leafs we are touching + VectorAdd( tw->start, tw->size[0], ll.bounds[0] ); + VectorAdd( tw->start, tw->size[1], ll.bounds[1] ); + + for (i=0 ; i<3 ; i++) { + ll.bounds[0][i] -= 1; + ll.bounds[1][i] += 1; + } + + ll.count = 0; + ll.maxcount = MAX_POSITION_LEAFS; + ll.list = leafs; + ll.storeLeafs = CM_StoreLeafs; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + cmg.checkcount++; + + CM_BoxLeafnums_r( &ll, 0 ); + + + cmg.checkcount++; + + // test the contents of the leafs + for (i=0 ; i < ll.count ; i++) { + CM_TestInLeaf( tw, trace, &cmg.leafs[leafs[i]], &cmg ); + if ( trace.allsolid ) { + break; + } + } +} + +/* +=============================================================================== + +TRACING + +=============================================================================== +*/ + + +/* +================ +CM_TraceThroughPatch +================ +*/ + +void CM_TraceThroughPatch( traceWork_t *tw, trace_t &trace, cPatch_t *patch ) { + float oldFrac; + + c_patch_traces++; + + oldFrac = trace.fraction; + + CM_TraceThroughPatchCollide( tw, trace, patch->pc ); + + if ( trace.fraction < oldFrac ) { + trace.surfaceFlags = patch->surfaceFlags; + trace.contents = patch->contents; + } +} + +/* +================ +CM_PlaneCollision + + Returns false for a quick getout +================ +*/ + +bool CM_PlaneCollision(traceWork_t *tw, cbrushside_t *side) +{ + float dist, f; + float d1, d2; + +#ifdef _XBOX + cplane_t *plane = &cmg.planes[side->planeNum.GetValue()]; +#else + cplane_t *plane = side->plane; +#endif + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + d2 = DotProduct( tw->end, plane->normal ) - dist; + + if (d2 > 0.0f) + { + // endpoint is not in solid + tw->getout = true; + } + if (d1 > 0.0f) + { + // startpoint is not in solid + tw->startout = true; + } + + // if completely in front of face, no intersection with the entire brush + if ((d1 > 0.0f) && ( (d2 >= SURFACE_CLIP_EPSILON) || (d2 >= d1) ) ) + { + return(false); + } + + // if it doesn't cross the plane, the plane isn't relevent + if ((d1 <= 0.0f) && (d2 <= 0.0f)) + { + return(true); + } + // crosses face + if (d1 > d2) + { // enter + f = (d1 - SURFACE_CLIP_EPSILON); + if ( f < 0.0f ) + { + f = 0.0f; + if (f > tw->enterFrac) + { + tw->enterFrac = f; + tw->clipplane = plane; + tw->leadside = side; + } + } + else if (f > tw->enterFrac * (d1 - d2) ) + { + tw->enterFrac = f / (d1 - d2); + tw->clipplane = plane; + tw->leadside = side; + } + } + else + { // leave + f = (d1 + SURFACE_CLIP_EPSILON); + if ( f < (d1 - d2) ) + { + f = 1.0f; + if (f < tw->leaveFrac) + { + tw->leaveFrac = f; + } + } + else if (f > tw->leaveFrac * (d1 - d2) ) + { + tw->leaveFrac = f / (d1 - d2); + } + } + return(true); +} + +/* +================ +CM_TraceThroughBrush +================ +*/ +void CM_TraceThroughBrush( traceWork_t *tw, trace_t &trace, cbrush_t *brush, bool infoOnly ) +{ + int i; + cbrushside_t *side; + + tw->enterFrac = -1.0f; + tw->leaveFrac = 1.0f; + tw->clipplane = NULL; + + if ( !brush->numsides ) + { + return; + } + + // I'm not sure if test is strictly correct. Are all + // bboxes axis aligned? Do I care? It seems to work + // good enough... + if ( tw->bounds[0][0] > brush->bounds[1][0] + || tw->bounds[0][1] > brush->bounds[1][1] + || tw->bounds[0][2] > brush->bounds[1][2] + || tw->bounds[1][0] < brush->bounds[0][0] + || tw->bounds[1][1] < brush->bounds[0][1] + || tw->bounds[1][2] < brush->bounds[0][2] + ) { + return; + } + + tw->getout = false; + tw->startout = false; + tw->leadside = NULL; + + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for (i = 0; i < brush->numsides; i++) + { + side = brush->sides + i; + + if(!CM_PlaneCollision(tw, side)) + { + return; + } + } + + // + // all planes have been checked, and the trace was not + // completely outside the brush + // + if (!tw->startout) + { + if(!infoOnly) + { + // original point was inside brush + trace.startsolid = qtrue; + if (!tw->getout) + { + trace.allsolid = qtrue; + trace.fraction = 0.0f; + } + } + tw->enterFrac = 0.0f; + return; + } + + if (tw->enterFrac < tw->leaveFrac) + { + if ((tw->enterFrac > -1.0f) && (tw->enterFrac < trace.fraction)) + { + if (tw->enterFrac < 0.0f) + { + tw->enterFrac = 0.0f; + } + if(!infoOnly) + { + trace.fraction = tw->enterFrac; + trace.plane = *tw->clipplane; + trace.surfaceFlags = cmg.shaders[tw->leadside->shaderNum].surfaceFlags; + trace.contents = brush->contents; + } + } + } +} + +/* +================ +CM_TraceThroughTerrain + + During this routine the fraction is internal to the brush + and converted to a global fraction on exit. +================ +*/ + +#ifndef BSPC + +/* +void CM_TraceThroughTerrain( traceWork_t *tw, trace_t &trace, cbrush_t *brush ) +{ + CCMLandScape *landscape; + vec3_t tBegin, tEnd, tDistance, tStep; + vec3_t baseStart; + vec3_t baseEnd; + int count; + int i; + float fraction; + + // At this point we know we may be colliding with a terrain brush (and we know we have a valid terrain structure) + landscape = (CCMLandScape *)cmg.landScape; + + // Check for absolutely no connection + if(!CM_GenericBoxCollide(tw->bounds, landscape->GetBounds())) + { + return; + } + // Now we know that at least some part of the trace needs to collide with the terrain + // The regular brush collision is handled elsewhere, so advance the ray to an edge in the terrain brush + CM_TraceThroughBrush( tw, trace, brush, true ); + + // Remember the base entering and leaving fractions + tw->baseEnterFrac = tw->enterFrac; + tw->baseLeaveFrac = tw->leaveFrac; + // Reset to full spread within the brush + tw->enterFrac = -1.0f; + tw->leaveFrac = 1.0f; + + // Work out the corners of the AABB when the trace first hits the terrain brush and when it leaves + VectorAdvance(tw->start, tw->baseEnterFrac, tw->end, tBegin); + VectorAdvance(tw->start, tw->baseLeaveFrac, tw->end, tEnd); + VectorSubtract(tEnd, tBegin, tDistance); + + // Calculate number of iterations to process + count = ceilf(VectorLength(tDistance) / (landscape->GetPatchScalarSize() * TERRAIN_STEP_MAGIC)); + count = 1; + fraction = trace.fraction; + VectorScale(tDistance, 1.0f / count, tStep); + + // Save the base start and end vectors + VectorCopy ( tw->start, baseStart ); + VectorCopy ( tw->end, baseEnd ); + + // Use the terrain vectors. Start both at the beginning since the + // step will be added to the end as the first step of the loop + VectorCopy ( tBegin, tw->start ); + VectorCopy ( tBegin, tw->end ); + + // Step thru terrain patches moving on about 1 patch at a time + for ( i = 0; i < count; i ++ ) + { + // Add the step to the end + VectorAdd(tw->end, tStep, tw->end); + + CM_CalcExtents(tBegin, tw->end, tw, tw->localBounds); + + landscape->PatchCollide(tw, trace, tw->start, tw->end, brush->checkcount); + + // If collision with something closer than water then just stop here + if ( trace.fraction < fraction ) + { + // Convert the fraction of this sub tract into the full trace's fraction + trace.fraction = i * (1.0f / count) + (1.0f / count) * trace.fraction; + break; + } + + // Move the end to the start so the next trace starts + // where this one left off + VectorCopy(tw->end, tw->start); + } + + // Put the original start and end back + VectorCopy ( baseStart, tw->start ); + VectorCopy ( baseEnd, tw->end ); + + // Convert to global fraction only if something was hit along the way + if ( trace.fraction != 1.0 ) + { + trace.fraction = tw->baseEnterFrac + ((tw->baseLeaveFrac - tw->baseEnterFrac) * trace.fraction); + trace.contents = brush->contents; + } + + // Collide with any water + if ( tw->contents & CONTENTS_WATER ) + { + fraction = landscape->WaterCollide(tw->start, tw->end, trace.fraction); + if( fraction < trace.fraction ) + { + VectorSet(trace.plane.normal, 0.0f, 0.0f, 1.0f); + trace.contents = landscape->GetWaterContents(); + trace.fraction = fraction; + trace.surfaceFlags = landscape->GetWaterSurfaceFlags(); + } + } +} +*/ + +#ifdef TEST_TERRAIN_PHYSICS + +/* +void CM_TraceThroughTerrain( traceWork_t *tw, trace_t &trace, CCMLandScape *landscape) +{ + vec3_t tBegin, tEnd, tDistance, tStep; + vec3_t baseStart; + vec3_t baseEnd; + int count; + int i; + float fraction; + + // Check for absolutely no connection + if(!CM_GenericBoxCollide(tw->bounds, landscape->GetBounds())) + { + return; + } + + tw->enterFrac = 0.0f; + tw->leaveFrac = 1.0f; + tw->clipplane = NULL; + tw->getout = false; + tw->startout = false; + tw->leadside = NULL; + + // Remember the base entering and leaving fractions + tw->baseEnterFrac = tw->enterFrac; + tw->baseLeaveFrac = tw->leaveFrac; + // Reset to full spread within the brush + tw->enterFrac = -1.0f; + tw->leaveFrac = 1.0f; + + // Work out the corners of the AABB when the trace first hits the terrain brush and when it leaves + VectorAdvance(tw->start, tw->baseEnterFrac, tw->end, tBegin); + VectorAdvance(tw->start, tw->baseLeaveFrac, tw->end, tEnd); + VectorSubtract(tEnd, tBegin, tDistance); + + // Calculate number of iterations to process + count = ceilf(VectorLength(tDistance) / (landscape->GetPatchScalarSize() * TERRAIN_STEP_MAGIC)); + count = 1; + fraction = trace.fraction; + VectorScale(tDistance, 1.0f / count, tStep); + + // Save the base start and end vectors + VectorCopy ( tw->start, baseStart ); + VectorCopy ( tw->end, baseEnd ); + + // Use the terrain vectors. Start both at the beginning since the + // step will be added to the end as the first step of the loop + VectorCopy ( tBegin, tw->start ); + VectorCopy ( tBegin, tw->end ); + + // Step thru terrain patches moving on about 1 patch at a time + for ( i = 0; i < count; i ++ ) + { + // Add the step to the end + VectorAdd(tw->end, tStep, tw->end); + + CM_CalcExtents(tBegin, tw->end, tw, tw->localBounds); + + landscape->PatchCollide(tw, trace, tw->start, tw->end, cmg.checkcount); + + // If collision with something closer than water then just stop here + if ( trace.fraction < fraction ) + { + // Convert the fraction of this sub tract into the full trace's fraction + trace.fraction = i * (1.0f / count) + (1.0f / count) * trace.fraction; + break; + } + + // Move the end to the start so the next trace starts + // where this one left off + VectorCopy(tw->end, tw->start); + } + + // Put the original start and end back + VectorCopy ( baseStart, tw->start ); + VectorCopy ( baseEnd, tw->end ); + + // Convert to global fraction only if something was hit along the way + if ( trace.fraction != 1.0 ) + { +// trace.fraction = tw->baseEnterFrac + ((tw->baseLeaveFrac - tw->baseEnterFrac) * trace.fraction); + trace.contents = CONTENTS_TERRAIN | CONTENTS_OUTSIDE; + } + + // Collide with any water + if ( tw->contents & CONTENTS_WATER ) + { + fraction = landscape->WaterCollide(tw->start, tw->end, trace.fraction); + if( fraction < trace.fraction ) + { + VectorSet(trace.plane.normal, 0.0f, 0.0f, 1.0f); + trace.contents = landscape->GetWaterContents(); + trace.fraction = fraction; + trace.surfaceFlags = landscape->GetWaterSurfaceFlags(); + } + } +} +*/ + +#endif // #ifdef TEST_TERRAIN_PHYSICS + +#endif + +/* +================ +CM_PatchCollide + + By the time we get here we know the AABB is within the patch AABB ie there is a chance of collision + The collision data is made up of bounds, 2 triangle planes + There is an BB check for the terxel check to see if it is worth checking the planes. + Collide with both triangles to find the shortest fraction +================ +*/ + +void CM_HandlePatchCollision(struct traceWork_s *tw, trace_t &trace, const vec3_t tStart, const vec3_t tEnd, CCMPatch *patch, int checkcount) +{ + int numBrushes, i; + cbrush_t *brush; + + // Get the collision data + brush = patch->GetCollisionData(); + numBrushes = patch->GetNumBrushes(); + + for(i = 0; i < numBrushes; i++, brush++) + { + if(brush->checkcount == checkcount) + { + return; + } + + // Generic collision of terxel bounds to line segment bounds + if(!CM_GenericBoxCollide(brush->bounds, tw->localBounds)) + { + continue; + } + + brush->checkcount = checkcount; + + CM_TraceThroughBrush(tw, trace, brush, false ); + if (trace.fraction <= 0.0) + { + break; + } + } +} + +/* +================ +CM_GenericBoxCollide +================ +*/ + +bool CM_GenericBoxCollide(const vec3pair_t abounds, const vec3pair_t bbounds) +{ + int i; + + // Check for completely no intersection + for(i = 0; i < 3; i++) + { + if(abounds[1][i] < bbounds[0][i]) + { + return(false); + } + if(abounds[0][i] > bbounds[1][i]) + { + return(false); + } + } + return(true); +} + +/* +================ +CM_TraceThroughLeaf +================ +*/ +void CM_TraceThroughLeaf( traceWork_t *tw, trace_t &trace, clipMap_t *local, cLeaf_t *leaf ) { + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // trace line against all brushes in the leaf + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = local->leafbrushes[leaf->firstLeafBrush+k]; + + b = &local->brushes[brushnum]; + if ( b->checkcount == local->checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = local->checkcount; + + if ( !(b->contents & tw->contents) ) { + continue; + } + +#ifndef BSPC +/* + if (com_terrainPhysics->integer && cmg.landScape && (b->contents & CONTENTS_TERRAIN) ) + { + // Invalidate the checkcount for terrain as the terrain brush has to be processed + // many times. + b->checkcount--; + + CM_TraceThroughTerrain( tw, trace, b ); + } + else +*/ +#endif + { + CM_TraceThroughBrush( tw, trace, b, false ); + } + + if ( !trace.fraction ) { + return; + } + } + + // trace line against all patches in the leaf +#ifdef BSPC + if (1) { +#else + if ( !cm_noCurves->integer ) { +#endif + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { +//#ifdef _XBOX +// int index = CM_GetSurfaceIndex(leaf->firstLeafSurface + k); +// patch = local->surfaces[ index ]; +//#else + patch = local->surfaces[ local->leafsurfaces[ leaf->firstLeafSurface + k ] ]; +//#endif + if ( !patch ) { + continue; + } + if ( patch->checkcount == local->checkcount ) { + continue; // already checked this patch in another leaf + } + patch->checkcount = local->checkcount; + + if ( !(patch->contents & tw->contents) ) { + continue; + } + + CM_TraceThroughPatch( tw, trace, patch ); + if ( !trace.fraction ) { + return; + } + } + } +} + +#define RADIUS_EPSILON 1.0f + +/* +================ +CM_TraceThroughSphere + +get the first intersection of the ray with the sphere +================ +*/ +void CM_TraceThroughSphere( traceWork_t *tw, trace_t &trace, vec3_t origin, float radius, vec3_t start, vec3_t end ) { + float l1, l2, length, scale, fraction; + float a, b, c, d, sqrtd; + vec3_t v1, dir, intersection; + + // if inside the sphere + VectorSubtract(start, origin, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + trace.fraction = 0; + trace.startsolid = qtrue; + // test for allsolid + VectorSubtract(end, origin, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + trace.allsolid = qtrue; + } + return; + } + // + VectorSubtract(end, start, dir); + length = VectorNormalize(dir); + // + l1 = CM_DistanceFromLineSquared(origin, start, end, dir); + VectorSubtract(end, origin, v1); + l2 = VectorLengthSquared(v1); + // if no intersection with the sphere and the end point is at least an epsilon away + if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) { + return; + } + // + // | origin - (start + t * dir) | = radius + // a = dir[0]^2 + dir[1]^2 + dir[2]^2; + // b = 2 * (dir[0] * (start[0] - origin[0]) + dir[1] * (start[1] - origin[1]) + dir[2] * (start[2] - origin[2])); + // c = (start[0] - origin[0])^2 + (start[1] - origin[1])^2 + (start[2] - origin[2])^2 - radius^2; + // + VectorSubtract(start, origin, v1); + // dir is normalized so a = 1 + a = 1.0f;//dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]; + b = 2.0f * (dir[0] * v1[0] + dir[1] * v1[1] + dir[2] * v1[2]); + c = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON); + + d = b * b - 4.0f * c;// * a; + if (d > 0) { + sqrtd = SquareRootFloat(d); + // = (- b + sqrtd) * 0.5f; // / (2.0f * a); + fraction = (- b - sqrtd) * 0.5f; // / (2.0f * a); + // + if (fraction < 0) { + fraction = 0; + } + else { + fraction /= length; + } + if ( fraction < trace.fraction ) { + trace.fraction = fraction; + VectorSubtract(end, start, dir); + VectorMA(start, fraction, dir, intersection); + VectorSubtract(intersection, origin, dir); + #ifdef CAPSULE_DEBUG + l2 = VectorLength(dir); + if (l2 < radius) { + int bah = 1; + } + #endif + scale = 1 / (radius+RADIUS_EPSILON); + VectorScale(dir, scale, dir); + VectorCopy(dir, trace.plane.normal); + VectorAdd( tw->modelOrigin, intersection, intersection); + trace.plane.dist = DotProduct(trace.plane.normal, intersection); + trace.contents = CONTENTS_BODY; + } + } + else if (d == 0) { + //t1 = (- b ) / 2; + // slide along the sphere + } + // no intersection at all +} + +/* +================ +CM_TraceThroughVerticalCylinder + +get the first intersection of the ray with the cylinder +the cylinder extends halfheight above and below the origin +================ +*/ +void CM_TraceThroughVerticalCylinder( traceWork_t *tw, trace_t &trace, vec3_t origin, float radius, float halfheight, vec3_t start, vec3_t end) { + float length, scale, fraction, l1, l2; + float a, b, c, d, sqrtd; + vec3_t v1, dir, start2d, end2d, org2d, intersection; + + // 2d coordinates + VectorSet(start2d, start[0], start[1], 0); + VectorSet(end2d, end[0], end[1], 0); + VectorSet(org2d, origin[0], origin[1], 0); + // if between lower and upper cylinder bounds + if (start[2] <= origin[2] + halfheight && + start[2] >= origin[2] - halfheight) { + // if inside the cylinder + VectorSubtract(start2d, org2d, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + trace.fraction = 0; + trace.startsolid = qtrue; + VectorSubtract(end2d, org2d, dir); + l1 = VectorLengthSquared(dir); + if (l1 < Square(radius)) { + trace.allsolid = qtrue; + } + return; + } + } + // + VectorSubtract(end2d, start2d, dir); + length = VectorNormalize(dir); + // + l1 = CM_DistanceFromLineSquared(org2d, start2d, end2d, dir); + VectorSubtract(end2d, org2d, v1); + l2 = VectorLengthSquared(v1); + // if no intersection with the cylinder and the end point is at least an epsilon away + if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) { + return; + } + // + // + // (start[0] - origin[0] - t * dir[0]) ^ 2 + (start[1] - origin[1] - t * dir[1]) ^ 2 = radius ^ 2 + // (v1[0] + t * dir[0]) ^ 2 + (v1[1] + t * dir[1]) ^ 2 = radius ^ 2; + // v1[0] ^ 2 + 2 * v1[0] * t * dir[0] + (t * dir[0]) ^ 2 + + // v1[1] ^ 2 + 2 * v1[1] * t * dir[1] + (t * dir[1]) ^ 2 = radius ^ 2 + // t ^ 2 * (dir[0] ^ 2 + dir[1] ^ 2) + t * (2 * v1[0] * dir[0] + 2 * v1[1] * dir[1]) + + // v1[0] ^ 2 + v1[1] ^ 2 - radius ^ 2 = 0 + // + VectorSubtract(start, origin, v1); + // dir is normalized so we can use a = 1 + a = 1.0f;// * (dir[0] * dir[0] + dir[1] * dir[1]); + b = 2.0f * (v1[0] * dir[0] + v1[1] * dir[1]); + c = v1[0] * v1[0] + v1[1] * v1[1] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON); + + d = b * b - 4.0f * c;// * a; + if (d > 0) { + sqrtd = SquareRootFloat(d); + // = (- b + sqrtd) * 0.5f;// / (2.0f * a); + fraction = (- b - sqrtd) * 0.5f;// / (2.0f * a); + // + if (fraction < 0) { + fraction = 0; + } + else { + fraction /= length; + } + if ( fraction < trace.fraction ) { + VectorSubtract(end, start, dir); + VectorMA(start, fraction, dir, intersection); + // if the intersection is between the cylinder lower and upper bound + if (intersection[2] <= origin[2] + halfheight && + intersection[2] >= origin[2] - halfheight) { + // + trace.fraction = fraction; + VectorSubtract(intersection, origin, dir); + dir[2] = 0; + #ifdef CAPSULE_DEBUG + l2 = VectorLength(dir); + if (l2 <= radius) { + int bah = 1; + } + #endif + scale = 1 / (radius+RADIUS_EPSILON); + VectorScale(dir, scale, dir); + VectorCopy(dir, trace.plane.normal); + VectorAdd( tw->modelOrigin, intersection, intersection); + trace.plane.dist = DotProduct(trace.plane.normal, intersection); + trace.contents = CONTENTS_BODY; + } + } + } + else if (d == 0) { + //t[0] = (- b ) / 2 * a; + // slide along the cylinder + } + // no intersection at all +} + +/* +================ +CM_TraceCapsuleThroughCapsule + +capsule vs. capsule collision (not rotated) +================ +*/ +void CM_TraceCapsuleThroughCapsule( traceWork_t *tw, trace_t &trace, clipHandle_t model ) { + int i; + vec3_t mins, maxs; + vec3_t top, bottom, starttop, startbottom, endtop, endbottom; + vec3_t offset, symetricSize[2]; + float radius, halfwidth, halfheight, offs, h; + + CM_ModelBounds(model, mins, maxs); + // test trace bounds vs. capsule bounds + if ( tw->bounds[0][0] > maxs[0] + RADIUS_EPSILON + || tw->bounds[0][1] > maxs[1] + RADIUS_EPSILON + || tw->bounds[0][2] > maxs[2] + RADIUS_EPSILON + || tw->bounds[1][0] < mins[0] - RADIUS_EPSILON + || tw->bounds[1][1] < mins[1] - RADIUS_EPSILON + || tw->bounds[1][2] < mins[2] - RADIUS_EPSILON + ) { + return; + } + // top origin and bottom origin of each sphere at start and end of trace + VectorAdd(tw->start, tw->sphere.offset, starttop); + VectorSubtract(tw->start, tw->sphere.offset, startbottom); + VectorAdd(tw->end, tw->sphere.offset, endtop); + VectorSubtract(tw->end, tw->sphere.offset, endbottom); + + // calculate top and bottom of the capsule spheres to collide with + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + } + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + offs = halfheight - radius; + VectorCopy(offset, top); + top[2] += offs; + VectorCopy(offset, bottom); + bottom[2] -= offs; + // expand radius of spheres + radius += tw->sphere.radius; + // if there is horizontal movement + if ( tw->start[0] != tw->end[0] || tw->start[1] != tw->end[1] ) { + // height of the expanded cylinder is the height of both cylinders minus the radius of both spheres + h = halfheight + tw->sphere.halfheight - radius; + // if the cylinder has a height + if ( h > 0 ) { + // test for collisions between the cylinders + CM_TraceThroughVerticalCylinder(tw, trace, offset, radius, h, tw->start, tw->end); + } + } + // test for collision between the spheres + CM_TraceThroughSphere(tw, trace, top, radius, startbottom, endbottom); + CM_TraceThroughSphere(tw, trace, bottom, radius, starttop, endtop); +} + +/* +================ +CM_TraceBoundingBoxThroughCapsule + +bounding box vs. capsule collision +================ +*/ +void CM_TraceBoundingBoxThroughCapsule( traceWork_t *tw, trace_t &trace, clipHandle_t model ) { + vec3_t mins, maxs, offset, size[2]; + clipHandle_t h; + cmodel_t *cmod; + int i; + + // mins maxs of the capsule + CM_ModelBounds(model, mins, maxs); + + // offset for capsule center + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + size[0][i] = mins[i] - offset[i]; + size[1][i] = maxs[i] - offset[i]; + tw->start[i] -= offset[i]; + tw->end[i] -= offset[i]; + } + + // replace the bounding box with the capsule + tw->sphere.use = qtrue; + tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0]; + tw->sphere.halfheight = size[1][2]; + VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius ); + + // replace the capsule with the bounding box + h = CM_TempBoxModel(tw->size[0], tw->size[1], qfalse); + // calculate collision + cmod = CM_ClipHandleToModel( h ); + CM_TraceThroughLeaf( tw, trace, &cmg, &cmod->leaf ); +} + +//========================================================================================= + +/* +================ +CM_TraceToLeaf +================ +*/ +void CM_TraceToLeaf( traceWork_t *tw, trace_t &trace, cLeaf_t *leaf, clipMap_t *local ) +{ + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // trace line against all brushes in the leaf + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) + { + brushnum = local->leafbrushes[leaf->firstLeafBrush + k]; + + b = &local->brushes[brushnum]; + if ( b->checkcount == local->checkcount ) + { + continue; // already checked this brush in another leaf + } + b->checkcount = local->checkcount; + + if ( !(b->contents & tw->contents) ) + { + continue; + } + +#ifndef BSPC +/* + if ( com_terrainPhysics->integer && cmg.landScape && (b->contents & CONTENTS_TERRAIN) ) + { + // Invalidate the checkcount for terrain as the terrain brush has to be processed + // many times. + b->checkcount--; + + CM_TraceThroughTerrain( tw, trace, b ); + // If inside a terrain brush don't bother with regular brush collision + continue; + } +*/ +#endif + + CM_TraceThroughBrush( tw, trace, b, false); + if ( !trace.fraction ) + { + return; + } + } + + // trace line against all patches in the leaf +#ifdef BSPC + if (1) { +#else + if ( !cm_noCurves->integer ) { +#endif + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = local->surfaces[ local->leafsurfaces[ leaf->firstLeafSurface + k ] ]; + if ( !patch ) { + continue; + } + if ( patch->checkcount == local->checkcount ) { + continue; // already checked this patch in another leaf + } + patch->checkcount = local->checkcount; + + if ( !(patch->contents & tw->contents) ) { + continue; + } + + CM_TraceThroughPatch( tw, trace, patch ); + if ( !trace.fraction ) { + return; + } + } + } +} + +/* +================== +CM_TraceThroughTree + +Traverse all the contacted leafs from the start to the end position. +If the trace is a point, they will be exactly in order, but for larger +trace volumes it is possible to hit something in a later leaf with +a smaller intercept fraction. +================== +*/ +void CM_TraceThroughTree( traceWork_t *tw, trace_t &trace, clipMap_t *local, int num, float p1f, float p2f, vec3_t p1, vec3_t p2) { + cNode_t *node; + cplane_t *plane; + float t1, t2, offset; + float frac, frac2; + float idist; + vec3_t mid; + int side; + float midf; + + if (trace.fraction <= p1f) { + return; // already hit something nearer + } + + // if < 0, we are in a leaf node + if (num < 0) { + CM_TraceThroughLeaf( tw, trace, local, &local->leafs[-1-num] ); + return; + } + + // + // find the point distances to the seperating plane + // and the offset for the size of the box + // + node = local->nodes + num; +#ifdef _XBOX + plane = cmg.planes + node->planeNum;//tr.world->nodes[num].planeNum; +#else + plane = node->plane; +#endif + + // adjust the plane distance apropriately for mins/maxs + if ( plane->type < 3 ) { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + offset = tw->extents[plane->type]; + } else { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + if ( tw->isPoint ) { + offset = 0; + } else { +#if 0 // bk010201 - DEAD + // an axial brush right behind a slanted bsp plane + // will poke through when expanded, so adjust + // by sqrt(3) + offset = fabs(tw->extents[0]*plane->normal[0]) + + fabs(tw->extents[1]*plane->normal[1]) + + fabs(tw->extents[2]*plane->normal[2]); + + offset *= 2; + offset = tw->maxOffset; +#endif + // this is silly + offset = 2048; + } + } + + // see which sides we need to consider + if ( t1 >= offset + 1 && t2 >= offset + 1 ) { + CM_TraceThroughTree( tw, trace, local, node->children[0], p1f, p2f, p1, p2 ); + return; + } + if ( t1 < -offset - 1 && t2 < -offset - 1 ) { + CM_TraceThroughTree( tw, trace, local, node->children[1], p1f, p2f, p1, p2 ); + return; + } + + // put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side + if ( t1 < t2 ) { + idist = 1.0/(t1-t2); + side = 1; + frac2 = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; + frac = (t1 - offset + SURFACE_CLIP_EPSILON)*idist; + } else if (t1 > t2) { + idist = 1.0/(t1-t2); + side = 0; + frac2 = (t1 - offset - SURFACE_CLIP_EPSILON)*idist; + frac = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; + } else { + side = 0; + frac = 1; + frac2 = 0; + } + + // move up to the node + if ( frac < 0 ) { + frac = 0; + } + if ( frac > 1 ) { + frac = 1; + } + + midf = p1f + (p2f - p1f)*frac; + + mid[0] = p1[0] + frac*(p2[0] - p1[0]); + mid[1] = p1[1] + frac*(p2[1] - p1[1]); + mid[2] = p1[2] + frac*(p2[2] - p1[2]); + + CM_TraceThroughTree( tw, trace, local, node->children[side], p1f, midf, p1, mid ); + + + // go past the node + if ( frac2 < 0 ) { + frac2 = 0; + } + if ( frac2 > 1 ) { + frac2 = 1; + } + + midf = p1f + (p2f - p1f)*frac2; + + mid[0] = p1[0] + frac2*(p2[0] - p1[0]); + mid[1] = p1[1] + frac2*(p2[1] - p1[1]); + mid[2] = p1[2] + frac2*(p2[2] - p1[2]); + + CM_TraceThroughTree( tw, trace, local, node->children[side^1], midf, p2f, mid, p2 ); +} + +void CM_CalcExtents(const vec3_t start, const vec3_t end, const traceWork_t *tw, vec3pair_t bounds) +{ + int i; + + for ( i = 0 ; i < 3 ; i++ ) + { + if ( start[i] < end[i] ) + { + bounds[0][i] = start[i] + tw->size[0][i]; + bounds[1][i] = end[i] + tw->size[1][i]; + } + else + { + bounds[0][i] = end[i] + tw->size[0][i]; + bounds[1][i] = start[i] + tw->size[1][i]; + } + } +} + +//====================================================================== + + +/* +================== +CM_Trace +================== +*/ +void CM_Trace( trace_t *trace, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, const vec3_t origin, int brushmask, int capsule, sphere_t *sphere ) { + int i; + traceWork_t tw; + vec3_t offset; + cmodel_t *cmod; + clipMap_t *local = 0; + + cmod = CM_ClipHandleToModel( model, &local ); + + local->checkcount++; // for multi-check avoidance + + c_traces++; // for statistics, may be zeroed + + // fill in a default trace + Com_Memset( &tw, 0, sizeof(tw) ); + memset(trace, 0, sizeof(*trace)); + trace->fraction = 1; // assume it goes the entire distance until shown otherwise + VectorCopy(origin, tw.modelOrigin); + + if (!local->numNodes) { + return; // map not loaded, shouldn't happen + } + + // allow NULL to be passed in for 0,0,0 + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + // set basic parms + tw.contents = brushmask; + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + tw.size[0][i] = mins[i] - offset[i]; + tw.size[1][i] = maxs[i] - offset[i]; + tw.start[i] = start[i] + offset[i]; + tw.end[i] = end[i] + offset[i]; + } + + // if a sphere is already specified + if ( sphere ) { + tw.sphere = *sphere; + } + else { + tw.sphere.use = (qboolean)capsule; + tw.sphere.radius = ( tw.size[1][0] > tw.size[1][2] ) ? tw.size[1][2]: tw.size[1][0]; + tw.sphere.halfheight = tw.size[1][2]; + VectorSet( tw.sphere.offset, 0, 0, tw.size[1][2] - tw.sphere.radius ); + } + + tw.maxOffset = tw.size[1][0] + tw.size[1][1] + tw.size[1][2]; + + // tw.offsets[signbits] = vector to apropriate corner from origin + tw.offsets[0][0] = tw.size[0][0]; + tw.offsets[0][1] = tw.size[0][1]; + tw.offsets[0][2] = tw.size[0][2]; + + tw.offsets[1][0] = tw.size[1][0]; + tw.offsets[1][1] = tw.size[0][1]; + tw.offsets[1][2] = tw.size[0][2]; + + tw.offsets[2][0] = tw.size[0][0]; + tw.offsets[2][1] = tw.size[1][1]; + tw.offsets[2][2] = tw.size[0][2]; + + tw.offsets[3][0] = tw.size[1][0]; + tw.offsets[3][1] = tw.size[1][1]; + tw.offsets[3][2] = tw.size[0][2]; + + tw.offsets[4][0] = tw.size[0][0]; + tw.offsets[4][1] = tw.size[0][1]; + tw.offsets[4][2] = tw.size[1][2]; + + tw.offsets[5][0] = tw.size[1][0]; + tw.offsets[5][1] = tw.size[0][1]; + tw.offsets[5][2] = tw.size[1][2]; + + tw.offsets[6][0] = tw.size[0][0]; + tw.offsets[6][1] = tw.size[1][1]; + tw.offsets[6][2] = tw.size[1][2]; + + tw.offsets[7][0] = tw.size[1][0]; + tw.offsets[7][1] = tw.size[1][1]; + tw.offsets[7][2] = tw.size[1][2]; + + // + // calculate bounds + // + if ( tw.sphere.use ) { + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius; + tw.bounds[1][i] = tw.end[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius; + } else { + tw.bounds[0][i] = tw.end[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius; + tw.bounds[1][i] = tw.start[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius; + } + } + } + else { + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] + tw.size[0][i]; + tw.bounds[1][i] = tw.end[i] + tw.size[1][i]; + } else { + tw.bounds[0][i] = tw.end[i] + tw.size[0][i]; + tw.bounds[1][i] = tw.start[i] + tw.size[1][i]; + } + } + } + + // + // check for position test special case + // + if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2] && + tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0) + { + if ( model && cmod->firstNode == -1) + { +#ifdef ALWAYS_BBOX_VS_BBOX // bk010201 - FIXME - compile time flag? + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) + { + tw.sphere.use = qfalse; + CM_TestInLeaf( &tw, &cmod->leaf ); + } + else +#elif defined(ALWAYS_CAPSULE_VS_CAPSULE) + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) + { + CM_TestCapsuleInCapsule( &tw, model ); + } + else +#endif + if ( model == CAPSULE_MODEL_HANDLE ) + { + if ( tw.sphere.use ) + { + CM_TestCapsuleInCapsule( &tw, *trace, model ); + } + else + { + CM_TestBoundingBoxInCapsule( &tw, *trace, model ); + } + } + else + { + CM_TestInLeaf( &tw, *trace, &cmod->leaf, local ); + } + } +#ifdef TEST_TERRAIN_PHYSICS + else if (cmg.landScape && !model && !cmod->firstNode) + { + CM_TraceThroughTerrain( &tw, *trace, cmg.landScape ); + } +#endif // #ifdef TEST_TERRAIN_PHYSICS + else if (cmod->firstNode == -1) + { + CM_PositionTest( &tw, *trace ); + } + else + { + CM_TraceThroughTree( &tw, *trace, local, cmod->firstNode, 0, 1, tw.start, tw.end ); + } + } + else + { + // + // check for point special case + // + if ( tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0 ) + { + tw.isPoint = qtrue; + VectorClear( tw.extents ); + } + else + { + tw.isPoint = qfalse; + tw.extents[0] = tw.size[1][0]; + tw.extents[1] = tw.size[1][1]; + tw.extents[2] = tw.size[1][2]; + } + + // + // general sweeping through world + // + if ( model && cmod->firstNode == -1) + { +#ifdef ALWAYS_BBOX_VS_BBOX + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) + { + tw.sphere.use = qfalse; + CM_TraceThroughLeaf( &tw, &cmod->leaf ); + } + else +#elif defined(ALWAYS_CAPSULE_VS_CAPSULE) + if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE) + { + CM_TraceCapsuleThroughCapsule( &tw, model ); + } + else +#endif + if ( model == CAPSULE_MODEL_HANDLE ) + { + if ( tw.sphere.use ) + { + CM_TraceCapsuleThroughCapsule( &tw, *trace, model ); + } + else + { + CM_TraceBoundingBoxThroughCapsule( &tw, *trace, model ); + } + } + else + { + CM_TraceThroughLeaf( &tw, *trace, local, &cmod->leaf ); + } + } +#ifdef TEST_TERRAIN_PHYSICS + else if (cmg.landScape && !model && !cmod->firstNode) + { + CM_TraceThroughTerrain( &tw, *trace, cmg.landScape ); + } +#endif // #ifdef TEST_TERRAIN_PHYSICS + else + { + CM_TraceThroughTree( &tw, *trace, local, cmod->firstNode, 0, 1, tw.start, tw.end ); + } + } + + // generate endpos from the original, unmodified start/end + if ( trace->fraction == 1 ) { + VectorCopy (end, trace->endpos); + } else { + for ( i=0 ; i<3 ; i++ ) { + trace->endpos[i] = start[i] + trace->fraction * (end[i] - start[i]); + } + } + + // If allsolid is set (was entirely inside something solid), the plane is not valid. + // If fraction == 1.0, we never hit anything, and thus the plane is not valid. + // Otherwise, the normal on the plane should have unit length + assert(trace->allsolid || + trace->fraction == 1.0 || + VectorLengthSquared(trace->plane.normal) > 0.9999); +} + +/* +================== +CM_BoxTrace +================== +*/ +void CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, int capsule ) { + CM_Trace( results, start, end, mins, maxs, model, vec3_origin, brushmask, capsule, NULL ); +} + +/* +================== +CM_TransformedBoxTrace + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +void CM_TransformedBoxTrace( trace_t *trace, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles, int capsule ) { + vec3_t start_l, end_l; + qboolean rotated; + vec3_t offset; + vec3_t symetricSize[2]; + vec3_t matrix[3], transpose[3]; + int i; + float halfwidth; + float halfheight; + float t; + sphere_t sphere; + + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + start_l[i] = start[i] + offset[i]; + end_l[i] = end[i] + offset[i]; + } + + // subtract origin offset + VectorSubtract( start_l, origin, start_l ); + VectorSubtract( end_l, origin, end_l ); + + // rotate start and end into the models frame of reference + if ( model != BOX_MODEL_HANDLE && + (angles[0] || angles[1] || angles[2]) ) { + rotated = qtrue; + } else { + rotated = qfalse; + } + + halfwidth = symetricSize[ 1 ][ 0 ]; + halfheight = symetricSize[ 1 ][ 2 ]; + + sphere.use = (qboolean)capsule; + sphere.radius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + sphere.halfheight = halfheight; + t = halfheight - sphere.radius; + + if (rotated) { + // rotation on trace line (start-end) instead of rotating the bmodel + // NOTE: This is still incorrect for bounding boxes because the actual bounding + // box that is swept through the model is not rotated. We cannot rotate + // the bounding box or the bmodel because that would make all the brush + // bevels invalid. + // However this is correct for capsules since a capsule itself is rotated too. + CreateRotationMatrix(angles, matrix); + RotatePoint(start_l, matrix); + RotatePoint(end_l, matrix); + // rotated sphere offset for capsule + sphere.offset[0] = matrix[0][ 2 ] * t; + sphere.offset[1] = -matrix[1][ 2 ] * t; + sphere.offset[2] = matrix[2][ 2 ] * t; + } + else { + VectorSet( sphere.offset, 0, 0, t ); + } + + // sweep the box through the model + CM_Trace( trace, start_l, end_l, symetricSize[0], symetricSize[1], model, origin, brushmask, capsule, &sphere ); + + // if the bmodel was rotated and there was a collision + if ( rotated && trace->fraction != 1.0 ) { + // rotation of bmodel collision plane + TransposeMatrix(matrix, transpose); + RotatePoint(trace->plane.normal, transpose); + } + + // re-calculate the end position of the trace because the trace.endpos + // calculated by CM_Trace could be rotated and have an offset + trace->endpos[0] = start[0] + trace->fraction * (end[0] - start[0]); + trace->endpos[1] = start[1] + trace->fraction * (end[1] - start[1]); + trace->endpos[2] = start[2] + trace->fraction * (end[2] - start[2]); +} + +/* +================= +CM_CullBox + +Returns true if culled out +================= +*/ + +bool CM_CullBox(const cplane_t *frustum, const vec3_t transformed[8]) +{ + int i, j; + const cplane_t *frust; + + // check against frustum planes + for (i=0, frust=frustum; i<4 ; i++, frust++) + { + for (j=0 ; j<8 ; j++) + { + if (DotProduct(transformed[j], frust->normal) > frust->dist) + { // a point is in front + break; + } + } + + if (j == 8) + { // all points were behind one of the planes + return true; + } + } + return false; +} + +/* +================= +CM_CullWorldBox + +Returns true if culled out +================= +*/ + +bool CM_CullWorldBox (const cplane_t *frustum, const vec3pair_t bounds) +{ + int i; + vec3_t transformed[8]; + + for (i = 0 ; i < 8 ; i++) + { + transformed[i][0] = bounds[i & 1][0]; + transformed[i][1] = bounds[(i >> 1) & 1][1]; + transformed[i][2] = bounds[(i >> 2) & 1][2]; + } + + return(CM_CullBox(frustum, transformed)); +} diff --git a/codemp/qcommon/cmd_common.cpp b/codemp/qcommon/cmd_common.cpp new file mode 100644 index 0000000..b720b96 --- /dev/null +++ b/codemp/qcommon/cmd_common.cpp @@ -0,0 +1,651 @@ +// cmd.c -- Quake script command processing module + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +#define MAX_CMD_BUFFER 16384 +#define MAX_CMD_LINE 1024 + +typedef struct { + byte *data; + int maxsize; + int cursize; +} cmd_t; + +int cmd_wait; +cmd_t cmd_text; +byte cmd_text_buf[MAX_CMD_BUFFER]; + + +//============================================================================= + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame. This allows commands like: +bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster" +============ +*/ +void Cmd_Wait_f( void ) { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + if ( Cmd_Argc() == 2 ) { + ClientManager::ActiveClient().cmd_wait = atoi( Cmd_Argv( 1 ) ); + } else { + ClientManager::ActiveClient().cmd_wait = 1; + } + } + else + { +#endif + if ( Cmd_Argc() == 2 ) { + cmd_wait = atoi( Cmd_Argv( 1 ) ); + } else { + cmd_wait = 1; + } +#ifdef _XBOX + } +#endif +} + + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init (void) +{ +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + CM_START_LOOP(); + MSG_Init (&ClientManager::ActiveClient().cmd_text, ClientManager::ActiveClient().cmd_text_buf, + sizeof(ClientManager::ActiveClient().cmd_text_buf)); + CM_END_LOOP(); + } + else + { +#endif + + cmd_text.data = cmd_text_buf; + cmd_text.maxsize = MAX_CMD_BUFFER; + cmd_text.cursize = 0; + +#ifdef _XBOX + } +#endif +} + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer, does NOT add a final \n +============ +*/ +void Cbuf_AddText( const char *text ) { + int l; + + l = strlen (text); + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + if (ClientManager::ActiveClient().cmd_text.cursize + l >= ClientManager::ActiveClient().cmd_text.maxsize) + { + Com_Printf ("Cbuf_AddText: overflow\n"); + return; + } + Com_Memcpy (&ClientManager::ActiveClient().cmd_text.data[ClientManager::ActiveClient().cmd_text.cursize], text, strlen (text)); + ClientManager::ActiveClient().cmd_text.cursize += l; + } + else + { +#endif + + if (cmd_text.cursize + l >= cmd_text.maxsize) + { + Com_Printf ("Cbuf_AddText: overflow\n"); + return; + } + Com_Memcpy(&cmd_text.data[cmd_text.cursize], text, l); + cmd_text.cursize += l; + +#ifdef _XBOX + } +#endif +} + + +/* +============ +Cbuf_InsertText + +Adds command text immediately after the current command +Adds a \n to the text +============ +*/ +void Cbuf_InsertText( const char *text ) { + int len; + int i; + + len = strlen( text ) + 1; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + if ( len + ClientManager::ActiveClient().cmd_text.cursize > ClientManager::ActiveClient().cmd_text.maxsize ) { + Com_Printf( "Cbuf_InsertText overflowed\n" ); + return; + } + + // move the existing command text + for ( i = ClientManager::ActiveClient().cmd_text.cursize - 1 ; i >= 0 ; i-- ) { + ClientManager::ActiveClient().cmd_text.data[ i + len ] = ClientManager::ActiveClient().cmd_text.data[ i ]; + } + + // copy the new text in + memcpy( ClientManager::ActiveClient().cmd_text.data, text, len - 1 ); + + // add a \n + ClientManager::ActiveClient().cmd_text.data[ len - 1 ] = '\n'; + + ClientManager::ActiveClient().cmd_text.cursize += len; + } + else + { +#endif + + if ( len + cmd_text.cursize > cmd_text.maxsize ) { + Com_Printf( "Cbuf_InsertText overflowed\n" ); + return; + } + + // move the existing command text + for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) { + cmd_text.data[ i + len ] = cmd_text.data[ i ]; + } + + // copy the new text in + Com_Memcpy( cmd_text.data, text, len - 1 ); + + // add a \n + cmd_text.data[ len - 1 ] = '\n'; + + cmd_text.cursize += len; + +#ifdef _XBOX + } +#endif +} + + +/* +============ +Cbuf_ExecuteText +============ +*/ +void Cbuf_ExecuteText (int exec_when, const char *text) +{ + switch (exec_when) + { + case EXEC_NOW: + if (text && strlen(text) > 0) { + Cmd_ExecuteString (text); + } else { + Cbuf_Execute(); + } + break; + case EXEC_INSERT: + Cbuf_InsertText (text); + break; + case EXEC_APPEND: + Cbuf_AddText (text); + break; + default: + Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when"); + } +} + +/* +============ +Cbuf_Execute +============ +*/ +void Cbuf_Execute (void) +{ + int i; + char *text; + char line[MAX_CMD_LINE]; + int quotes; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + CM_START_LOOP(); + while (ClientManager::ActiveClient().cmd_text.cursize) + { + if ( ClientManager::ActiveClient().cmd_wait ) { + // skip out while text still remains in buffer, leaving it + // for next frame + ClientManager::ActiveClient().cmd_wait--; + break; + } + + // find a \n or ; line break + text = (char *)ClientManager::ActiveClient().cmd_text.data; + + quotes = 0; + for (i=0 ; i< ClientManager::ActiveClient().cmd_text.cursize ; i++) + { + if (text[i] == '"') + quotes++; + if ( !(quotes&1) && text[i] == ';') + break; // don't break if inside a quoted string + if (text[i] == '\n' || text[i] == '\r' ) + break; + } + + + memcpy (line, text, i); + line[i] = 0; + + // delete the text from the command buffer and move remaining commands down + // this is necessary because commands (exec) can insert data at the + // beginning of the text buffer + + if (i == ClientManager::ActiveClient().cmd_text.cursize) + ClientManager::ActiveClient().cmd_text.cursize = 0; + else + { + i++; + ClientManager::ActiveClient().cmd_text.cursize -= i; + memmove (text, text+i, ClientManager::ActiveClient().cmd_text.cursize); + } + + // execute the command line + Cmd_ExecuteString (line); + } + CM_END_LOOP(); + } + else + { +#endif + + while (cmd_text.cursize) + { + if ( cmd_wait ) { + // skip out while text still remains in buffer, leaving it + // for next frame + cmd_wait--; + break; + } + + // find a \n or ; line break + text = (char *)cmd_text.data; + + quotes = 0; + for (i=0 ; i< cmd_text.cursize ; i++) + { + if (text[i] == '"') + quotes++; + if ( !(quotes&1) && text[i] == ';') + break; // don't break if inside a quoted string + if (text[i] == '\n' || text[i] == '\r' ) + break; + } + + if( i >= (MAX_CMD_LINE - 1)) { + i = MAX_CMD_LINE - 1; + } + + Com_Memcpy (line, text, i); + line[i] = 0; + +// delete the text from the command buffer and move remaining commands down +// this is necessary because commands (exec) can insert data at the +// beginning of the text buffer + + if (i == cmd_text.cursize) + cmd_text.cursize = 0; + else + { + i++; + cmd_text.cursize -= i; + memmove (text, text+i, cmd_text.cursize); + } + +// execute the command line + + Cmd_ExecuteString (line); + } + +#ifdef _XBOX + } +#endif +} + + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ + + +/* +=============== +Cmd_Exec_f +=============== +*/ +void Cmd_Exec_f( void ) { + char *f; + int len; + char filename[MAX_QPATH]; + + if (Cmd_Argc () != 2) { + Com_Printf ("exec : execute a script file\n"); + return; + } + + Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + len = FS_ReadFile( filename, (void **)&f); + if (!f) { + Com_Printf ("couldn't exec %s\n",Cmd_Argv(1)); + return; + } +#ifndef FINAL_BUILD + Com_Printf ("execing %s\n",Cmd_Argv(1)); +#endif + + Cbuf_InsertText (f); + + FS_FreeFile (f); +} + + +/* +=============== +Cmd_Vstr_f + +Inserts the current value of a variable as command text +=============== +*/ +void Cmd_Vstr_f( void ) { + char *v; + + if (Cmd_Argc () != 2) { + Com_Printf ("vstr : execute a variable command\n"); + return; + } + + v = Cvar_VariableString( Cmd_Argv( 1 ) ); + Cbuf_InsertText( va("%s\n", v ) ); +} + + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void Cmd_Echo_f (void) +{ + int i; + + for (i=1 ; i= cmd_argc ) { + return ""; + } + return cmd_argv[arg]; +} + +/* +============ +Cmd_ArgvBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) { + Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength ); +} + + +/* +============ +Cmd_Args + +Returns a single string containing argv(1) to argv(argc()-1) +============ +*/ +char *Cmd_Args( void ) { + static char cmd_args[MAX_STRING_CHARS]; + int i; + + cmd_args[0] = 0; + for ( i = 1 ; i < cmd_argc ; i++ ) { + strcat( cmd_args, cmd_argv[i] ); + if ( i != cmd_argc-1 ) { + strcat( cmd_args, " " ); + } + } + + return cmd_args; +} + +/* +============ +Cmd_Args + +Returns a single string containing argv(arg) to argv(argc()-1) +============ +*/ +char *Cmd_ArgsFrom( int arg ) { + static char cmd_args[BIG_INFO_STRING]; + int i; + + cmd_args[0] = 0; + if (arg < 0) + arg = 0; + for ( i = arg ; i < cmd_argc ; i++ ) { + strcat( cmd_args, cmd_argv[i] ); + if ( i != cmd_argc-1 ) { + strcat( cmd_args, " " ); + } + } + + return cmd_args; +} + +/* +============ +Cmd_ArgsBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_ArgsBuffer( char *buffer, int bufferLength ) { + Q_strncpyz( buffer, Cmd_Args(), bufferLength ); +} + + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +The text is copied to a seperate buffer and 0 characters +are inserted in the apropriate place, The argv array +will point into this temporary buffer. +============ +*/ +void Cmd_TokenizeString( const char *text_in ) { + const char *text; + char *textOut; + + // clear previous args + cmd_argc = 0; + + if ( !text_in ) { + return; + } + + text = text_in; + textOut = cmd_tokenized; + + while ( 1 ) { + if ( cmd_argc == MAX_STRING_TOKENS ) { + return; // this is usually something malicious + } + + while ( 1 ) { + // skip whitespace + while ( *text && *text <= ' ' ) { + text++; + } + if ( !*text ) { + return; // all tokens parsed + } + + // skip // comments + if ( text[0] == '/' && text[1] == '/' ) { + return; // all tokens parsed + } + + // skip /* */ comments + if ( text[0] == '/' && text[1] =='*' ) { + while ( *text && ( text[0] != '*' || text[1] != '/' ) ) { + text++; + } + if ( !*text ) { + return; // all tokens parsed + } + text += 2; + } else { + break; // we are ready to parse a token + } + } + + // handle quoted strings + if ( *text == '"' ) { + cmd_argv[cmd_argc] = textOut; + cmd_argc++; + text++; + while ( *text && *text != '"' ) { + *textOut++ = *text++; + } + *textOut++ = 0; + if ( !*text ) { + return; // all tokens parsed + } + text++; + continue; + } + + // regular token + cmd_argv[cmd_argc] = textOut; + cmd_argc++; + + // skip until whitespace, quote, or command + while ( *(const unsigned char* /*eurofix*/)text > ' ' ) + { + if ( text[0] == '"' ) { + break; + } + + if ( text[0] == '/' && text[1] == '/' ) { + break; + } + + // skip /* */ comments + if ( text[0] == '/' && text[1] =='*' ) { + break; + } + + *textOut++ = *text++; + } + + *textOut++ = 0; + + if ( !*text ) { + return; // all tokens parsed + } + } + +} + + + +/* +============ +Cmd_Init +============ +*/ +extern void Cmd_List_f(void); +void Cmd_Init (void) { + Cmd_AddCommand ("cmdlist",Cmd_List_f); + Cmd_AddCommand ("exec",Cmd_Exec_f); + Cmd_AddCommand ("vstr",Cmd_Vstr_f); + Cmd_AddCommand ("echo",Cmd_Echo_f); + Cmd_AddCommand ("wait", Cmd_Wait_f); +} + diff --git a/codemp/qcommon/cmd_console.cpp b/codemp/qcommon/cmd_console.cpp new file mode 100644 index 0000000..e9da5f1 --- /dev/null +++ b/codemp/qcommon/cmd_console.cpp @@ -0,0 +1,165 @@ +#include "../qcommon/exe_headers.h" + +#define CMD_MAX_NUM 512 +#define CMD_MAX_NAME 32 + +typedef struct cmd_function_s +{ + char name[CMD_MAX_NAME]; + xcommand_t function; +} cmd_function_t; + + +static cmd_function_t cmd_functions[CMD_MAX_NUM] = {0}; // possible commands to execute + + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) { + cmd_function_t *cmd; + cmd_function_t *add = NULL; + int i; + + // fail if the command already exists + for (i=0; iname ) ) { + // allow completion-only commands to be silently doubled + if ( function != NULL ) { + Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); + } + return; + } + + if(add == NULL && cmd->name[0] == 0) { + add = cmd; + } + } + + if(!add) { + Com_Printf("Cmd_AddCommand: Too many commands registered\n"); + return; + } + + if(strlen(cmd_name) >= CMD_MAX_NAME - 1) { + Com_Printf("Cmd_AddCommand: Excessively long command name\n"); + } else { + Q_strncpyz(add->name, cmd_name, CMD_MAX_NAME); + add->function = function; + } +} + +/* +============ +Cmd_RemoveCommand +============ +*/ +void Cmd_RemoveCommand( const char *cmd_name ) { + cmd_function_t *cmd; + int i; + + for(i=0; iname ) ) { + cmd->name[0] = 0; + return; + } + } +} + + + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +============ +*/ +void Cmd_ExecuteString( const char *text ) { + int i; + + // execute the command line + Cmd_TokenizeString( text ); + if ( !Cmd_Argc() ) { + return; // no tokens + } + + // check registered command functions + for(i=0; iinteger && CL_GameCommand() ) { + return; + } + + // check server game commands + if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) { + return; + } + + // check ui commands + if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) { + return; + } + + // send it as a server command if we are connected + // this will usually result in a chat message + //CL_ForwardCommandToServer ( text ); + CL_ForwardCommandToServer ( text ); +} + + +/* +============ +Cmd_List_f +============ +*/ +void Cmd_List_f (void) +{ + cmd_function_t *cmd; + int i; + char *match; + + if ( Cmd_Argc() > 1 ) { + match = Cmd_Argv( 1 ); + } else { + match = NULL; + } + + i = 0; + for(int c=0; cname, qfalse)) continue; + + Com_Printf ("%s\n", cmd->name); + i++; + } + Com_Printf ("%i commands\n", i); +} + diff --git a/codemp/qcommon/cmd_pc.cpp b/codemp/qcommon/cmd_pc.cpp new file mode 100644 index 0000000..6ba9b9f --- /dev/null +++ b/codemp/qcommon/cmd_pc.cpp @@ -0,0 +1,174 @@ +#include "../qcommon/exe_headers.h" + +typedef struct cmd_function_s +{ + struct cmd_function_s *next; + char *name; + xcommand_t function; +} cmd_function_t; + + +static cmd_function_t *cmd_functions; // possible commands to execute + + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) { + cmd_function_t *cmd; + + // fail if the command already exists + for ( cmd = cmd_functions ; cmd ; cmd=cmd->next ) { + if ( !strcmp( cmd_name, cmd->name ) ) { + // allow completion-only commands to be silently doubled + if ( function != NULL ) { + Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); + } + return; + } + } + + // use a small malloc to avoid zone fragmentation + cmd = (struct cmd_function_s *)S_Malloc (sizeof(cmd_function_t)); + cmd->name = CopyString( cmd_name ); + cmd->function = function; + cmd->next = cmd_functions; + cmd_functions = cmd; +} + +/* +============ +Cmd_RemoveCommand +============ +*/ +void Cmd_RemoveCommand( const char *cmd_name ) { + cmd_function_t *cmd, **back; + + back = &cmd_functions; + while( 1 ) { + cmd = *back; + if ( !cmd ) { + // command wasn't active + return; + } + if ( !strcmp( cmd_name, cmd->name ) ) { + *back = cmd->next; + if (cmd->name) { + Z_Free(cmd->name); + } + Z_Free (cmd); + return; + } + back = &cmd->next; + } +} + + + +/* +============ +Cmd_CommandCompletion +============ +*/ +void Cmd_CommandCompletion( void(*callback)(const char *s) ) { + cmd_function_t *cmd; + + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { + callback( cmd->name ); + } +} + + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +============ +*/ +void Cmd_ExecuteString( const char *text ) { + cmd_function_t *cmd, **prev; + + // execute the command line + Cmd_TokenizeString( text ); + if ( !Cmd_Argc() ) { + return; // no tokens + } + + // check registered command functions + for ( prev = &cmd_functions ; *prev ; prev = &cmd->next ) { + cmd = *prev; + if ( !Q_stricmp( Cmd_Argv(0), cmd->name ) ) { + // rearrange the links so that the command will be + // near the head of the list next time it is used + *prev = cmd->next; + cmd->next = cmd_functions; + cmd_functions = cmd; + + // perform the action + if ( !cmd->function ) { + // let the cgame or game handle it + break; + } else { + cmd->function (); + } + return; + } + } + + // check cvars + if ( Cvar_Command() ) { + return; + } + + // check client game commands + if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) { + return; + } + + // check server game commands + if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) { + return; + } + + // check ui commands + if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) { + return; + } + + // send it as a server command if we are connected + // this will usually result in a chat message + //CL_ForwardCommandToServer ( text ); + CL_ForwardCommandToServer ( text ); +} + + +/* +============ +Cmd_List_f +============ +*/ +void Cmd_List_f (void) +{ + cmd_function_t *cmd; + int i; + char *match; + + if ( Cmd_Argc() > 1 ) { + match = Cmd_Argv( 1 ); + } else { + match = NULL; + } + + i = 0; + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { + if (match && !Com_Filter(match, cmd->name, qfalse)) continue; + + Com_Printf ("%s\n", cmd->name); + i++; + } + Com_Printf ("%i commands\n", i); +} + diff --git a/codemp/qcommon/cnetprofile.cpp b/codemp/qcommon/cnetprofile.cpp new file mode 100644 index 0000000..8563278 --- /dev/null +++ b/codemp/qcommon/cnetprofile.cpp @@ -0,0 +1,97 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#ifdef _DONETPROFILE_ + +#pragma warning( disable : 4786) +#pragma warning( disable : 4100) +#pragma warning( disable : 4663) + +#include +#include +#include +#include "hstring.h" +#include "INetProfile.h" + +using namespace std; + +class CNetProfile : public INetProfile +{ + float mElapsedTime; + map mFieldCounts; + float mFrameCount; + +public: + void Reset(void) + { + mFieldCounts.clear(); + mFrameCount=0; + } + + void AddField(char *fieldName,int sizeBytes) + { + assert(sizeBytes>=0); + if(sizeBytes==0) + { + return; + } + map::iterator f=mFieldCounts.find(fieldName); + if(f==mFieldCounts.end()) + { + mFieldCounts[fieldName]=(unsigned int)sizeBytes; + } + else + { + mFieldCounts[fieldName]+=(unsigned int)sizeBytes; + } + } + + void IncTime(int msec) + { + mElapsedTime+=msec; + } + + void ShowTotals(void) + { + float totalBytes=0; + multimap sort; + map::iterator f; + for(f=mFieldCounts.begin();f!=mFieldCounts.end();f++) + { + sort.insert(pair ((*f).second,(*f).first)); + totalBytes+=(*f).second; + } + + multimap::iterator j; + char msg[1024]; + float percent; + sprintf(msg, + "******** Totals: bytes %d : bytes per sec %d ********\n", + (unsigned int)totalBytes, + (unsigned int)((totalBytes/mElapsedTime)*1000)); + Sleep(10); + OutputDebugString(msg); + for(j=sort.begin();j!=sort.end();j++) + { + percent=(((float)(*j).first)/totalBytes)*100.0f; + assert(strlen((*j).second.c_str())<1024); + sprintf(msg,"%36s : %3.4f percent : %d bytes \n",(*j).second.c_str(),percent,(*j).first); + Sleep(10); + OutputDebugString(msg); + } + } +}; + +INetProfile &ClReadProf(void) +{ + static CNetProfile theClReadProf; + return(theClReadProf); +} + +INetProfile &ClSendProf(void) +{ + static CNetProfile theClSendProf; + return(theClSendProf); +} + +#endif // _DONETPROFILE_ \ No newline at end of file diff --git a/codemp/qcommon/common.cpp b/codemp/qcommon/common.cpp new file mode 100644 index 0000000..0af55f6 --- /dev/null +++ b/codemp/qcommon/common.cpp @@ -0,0 +1,2400 @@ +// common.c -- misc functions used in client and server + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "GenericParser2.h" +#include "stringed_ingame.h" +#include "../qcommon/game_version.h" +#ifndef __linux__ +//#include +#include "../qcommon/platform.h" +#endif + +#ifdef _XBOX +#include "../xbox/XBLive.h" +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +#define MAXPRINTMSG 4096 + +#define MAX_NUM_ARGVS 50 + +int com_argc; +char *com_argv[MAX_NUM_ARGVS+1]; + + +#ifdef USE_CD_KEY + +extern char cl_cdkey[34]; + +#endif // USE_CD_KEY + +FILE *debuglogfile; +fileHandle_t logfile; +fileHandle_t com_journalFile; // events are written here +fileHandle_t com_journalDataFile; // config files are written here + +// Global language setting - this should be used instead of the myriad language +// cvars. Will be one of the Xbox values: XC_LANGUAGE_(ENGLISH|FRENCH|GERMAN) +DWORD g_dwLanguage; + +cvar_t *com_viewlog; +cvar_t *com_speeds; +cvar_t *com_developer; +cvar_t *com_vmdebug; +cvar_t *com_dedicated; +cvar_t *com_timescale; +cvar_t *com_fixedtime; +cvar_t *com_dropsim; // 0.0 to 1.0, simulated packet drops +cvar_t *com_journal; +cvar_t *com_maxfps; +cvar_t *com_timedemo; +cvar_t *com_sv_running; +cvar_t *com_cl_running; +cvar_t *com_logfile; // 1 = buffer log, 2 = flush after each print +cvar_t *com_showtrace; + +#ifdef G2_PERFORMANCE_ANALYSIS +cvar_t *com_G2Report; +#endif + +cvar_t *com_terrainPhysics; //rwwRMG - added + +cvar_t *com_version; +cvar_t *com_blood; +cvar_t *com_buildScript; // for automated data building scripts +cvar_t *com_introPlayed; +cvar_t *cl_paused; +cvar_t *sv_paused; +cvar_t *com_cameraMode; +#if defined(_WIN32) && defined(_DEBUG) +cvar_t *com_noErrorInterrupt; +#endif + +cvar_t *com_RMG; + +// com_speeds times +int time_game; +int time_frontend; // renderer frontend time +int time_backend; // renderer backend time + +int com_frameTime; +int com_frameMsec; +int com_frameNumber; + +qboolean com_errorEntered; +qboolean com_fullyInitialized; + +char com_errorMessage[MAXPRINTMSG]; + +void Com_WriteConfig_f( void ); + +//============================================================================ + +static char *rd_buffer; +static int rd_buffersize; +static void (*rd_flush)( char *buffer ); + +void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)( char *) ) +{ + if (!buffer || !buffersize || !flush) + return; + rd_buffer = buffer; + rd_buffersize = buffersize; + rd_flush = flush; + + *rd_buffer = 0; +} + +void Com_EndRedirect (void) +{ + if ( rd_flush ) { + rd_flush(rd_buffer); + } + + rd_buffer = NULL; + rd_buffersize = 0; + rd_flush = NULL; +} + +void QDECL Com_PrintfAlways( const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + CL_ConsolePrint( msg, 0 ); + + // echo to dedicated console and early console +#ifndef FINAL_BUILD + Sys_Print( msg ); +#endif +} + +/* +============= +Com_Printf + +Both client and server can use this, and it will output +to the apropriate place. + +A raw string should NEVER be passed as fmt, because of "%f" type crashers. +============= +*/ +void QDECL Com_Printf( const char *fmt, ... ) { +#ifdef _DEBUG + va_list argptr; + char msg[MAXPRINTMSG]; + qboolean silent; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + if ( rd_buffer ) { + if ((strlen (msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) { + rd_flush(rd_buffer); + *rd_buffer = 0; + } + Q_strcat(rd_buffer, rd_buffersize, msg); + rd_flush(rd_buffer); + *rd_buffer = 0; + return; + } + + // * means dont draw this console message on the player screen + // but put it on the console + silent = qfalse; + if ( msg[0] == '*' ) + { + strcpy ( msg, msg + 1 ); + + if ( msg[1] != '*' ) + { + silent = qtrue; + } + } + + // echo to console if we're not a dedicated server + if ( com_dedicated && !com_dedicated->integer ) { + CL_ConsolePrint( msg, silent ); + } + + // echo to dedicated console and early console + Sys_Print( msg ); + + // logfile +#ifndef _XBOX + if ( com_logfile && com_logfile->integer ) { + if ( !logfile && FS_Initialized() ) { + struct tm *newtime; + time_t aclock; + + time( &aclock ); + newtime = localtime( &aclock ); + + logfile = FS_FOpenFileWrite( "qconsole.log" ); + Com_Printf( "logfile opened on %s\n", asctime( newtime ) ); + if ( com_logfile->integer > 1 ) { + // force it to not buffer so we get valid + // data even if we are crashing + FS_ForceFlush(logfile); + } + } + if ( logfile && FS_Initialized()) { + FS_Write(msg, strlen(msg), logfile); + } + } +#endif + +#if defined(_WIN32) && defined(_DEBUG) && !defined(_XBOX) + if ( *msg ) + { + OutputDebugString ( Q_CleanStr(msg) ); + OutputDebugString ("\n"); + } +#endif +#endif //_DEBUG +} + + +/* +================ +Com_DPrintf + +A Com_Printf that only shows up if the "developer" cvar is set +================ +*/ +void QDECL Com_DPrintf( const char *fmt, ...) { + va_list argptr; + char msg[MAXPRINTMSG]; + + if ( !com_developer || !com_developer->integer ) { + return; // don't confuse non-developers with techie stuff... + } + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + Com_Printf ("%s", msg); +} + +// Outputs to the VC / Windows Debug window (only in debug compile) +void QDECL Com_OPrintf( const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); +#ifndef __linux__ + OutputDebugString(msg); +#else + printf(msg); +#endif +} + +/* +============= +Com_Error + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ + +namespace ui +{ + extern qboolean inHandler; +} + +bool bComErrorLostConnection = false; + +void QDECL Com_Error( int code, const char *fmt, ... ) { + va_list argptr; + static int lastErrorTime; + static int errorCount; + int currentTime; +#ifdef _XBOX + int wasRunningServer = com_sv_running->integer; +#endif + + ui::inHandler = qfalse; + + // We need to know if we're being called because the connection to Live was lost. + // If so, we'll later provide the option to go to the dashboard + if( Q_stricmp( fmt, "@MENUS_XBOX_LOST_CONNECTION" ) == 0 ) + bComErrorLostConnection = true; + else + bComErrorLostConnection = false; + +#if defined(_WIN32) && defined(_DEBUG) + if ( code != ERR_DISCONNECT && code != ERR_NEED_CD ) { + if (com_noErrorInterrupt && !com_noErrorInterrupt->integer) { + __asm { + int 0x03 + } + } + } +#endif + + // when we are running automated scripts, make sure we + // know if anything failed + if ( com_buildScript && com_buildScript->integer ) { + code = ERR_FATAL; + } + + // make sure we can get at our local stuff + FS_PureServerSetLoadedPaks( "", "" ); + + // if we are getting a solid stream of ERR_DROP, do an ERR_FATAL + currentTime = Sys_Milliseconds(); + if ( currentTime - lastErrorTime < 100 ) { + if ( ++errorCount > 3 ) { + code = ERR_FATAL; + } + } else { + errorCount = 0; + } + lastErrorTime = currentTime; + + if ( com_errorEntered ) { + Sys_Error( "recursive error after: %s", com_errorMessage ); + } + com_errorEntered = qtrue; + + va_start (argptr,fmt); + vsprintf (com_errorMessage,fmt,argptr); + va_end (argptr); + + Cvar_Set("com_errorMessage", ""); + if ( code != ERR_DISCONNECT ) { + Cvar_Get("com_errorMessage", "", CVAR_ROM); //give com_errorMessage a default so it won't come back to life after a resetDefaults + Cvar_Set("com_errorMessage", com_errorMessage); + } + + if ( code == ERR_SERVERDISCONNECT ) { + CL_Disconnect( qtrue ); + CL_FlushMemory( ); + com_errorEntered = qfalse; + +#ifdef _XBOX + // I THINK this only happens on client + Net_XboxDisconnect(); +#endif + + throw ("DISCONNECTED\n"); + } else if ( code == ERR_DROP || code == ERR_DISCONNECT ) { + Com_Printf ("********************\nERROR: %s\n********************\n", com_errorMessage); + SV_Shutdown (va("Server crashed: %s\n", com_errorMessage)); + CL_Disconnect( qtrue ); + CL_FlushMemory( ); + com_errorEntered = qfalse; + +#ifdef _XBOX + // Clients (only) need to do connection cleanup now + if (!wasRunningServer) + Net_XboxDisconnect(); +#endif + + throw ("DROPPED\n"); + } else if ( code == ERR_NEED_CD ) { + SV_Shutdown( "Server didn't have CD\n" ); + if ( com_cl_running && com_cl_running->integer ) { + CL_Disconnect( qtrue ); + CL_FlushMemory( ); + com_errorEntered = qfalse; + } else { + Com_Printf("Server didn't have CD\n" ); + } + throw ("NEED CD\n"); + } else { + CL_Shutdown (); + SV_Shutdown (va("Server fatal crashed: %s\n", com_errorMessage)); + } + + Com_Shutdown (); + + Sys_Error ("%s", com_errorMessage); +} + + +/* +============= +Com_Quit_f + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ +void Com_Quit_f( void ) { + // don't try to shutdown if we are in a recursive error + if ( !com_errorEntered ) { + SV_Shutdown ("Server quit\n"); + CL_Shutdown (); + Com_Shutdown (); + FS_Shutdown(qtrue); + } + Sys_Quit (); +} + + + +/* +============================================================================ + +COMMAND LINE FUNCTIONS + ++ characters seperate the commandLine string into multiple console +command lines. + +All of these are valid: + +quake3 +set test blah +map test +quake3 set test blah+map test +quake3 set test blah + map test + +============================================================================ +*/ + +#define MAX_CONSOLE_LINES 32 +int com_numConsoleLines; +char *com_consoleLines[MAX_CONSOLE_LINES]; + +/* +================== +Com_ParseCommandLine + +Break it up into multiple console lines +================== +*/ +void Com_ParseCommandLine( char *commandLine ) { + com_consoleLines[0] = commandLine; + com_numConsoleLines = 1; + + while ( *commandLine ) { + // look for a + seperating character + // if commandLine came from a file, we might have real line seperators + if ( *commandLine == '+' || *commandLine == '\n' ) { + if ( com_numConsoleLines == MAX_CONSOLE_LINES ) { + return; + } + com_consoleLines[com_numConsoleLines] = commandLine + 1; + com_numConsoleLines++; + *commandLine = 0; + } + commandLine++; + } +} + + +/* +=================== +Com_SafeMode + +Check for "safe" on the command line, which will +skip loading of jampconfig.cfg +=================== +*/ +qboolean Com_SafeMode( void ) { + int i; + + for ( i = 0 ; i < com_numConsoleLines ; i++ ) { + Cmd_TokenizeString( com_consoleLines[i] ); + if ( !Q_stricmp( Cmd_Argv(0), "safe" ) + || !Q_stricmp( Cmd_Argv(0), "cvar_restart" ) ) { + com_consoleLines[i][0] = 0; + return qtrue; + } + } + return qfalse; +} + + +/* +=============== +Com_StartupVariable + +Searches for command line parameters that are set commands. +If match is not NULL, only that cvar will be looked for. +That is necessary because cddir and basedir need to be set +before the filesystem is started, but all other sets shouls +be after execing the config and default. +=============== +*/ +void Com_StartupVariable( const char *match ) { + int i; + char *s; + cvar_t *cv; + + for (i=0 ; i < com_numConsoleLines ; i++) { + Cmd_TokenizeString( com_consoleLines[i] ); + if ( strcmp( Cmd_Argv(0), "set" ) ) { + continue; + } + + s = Cmd_Argv(1); + if ( !match || !strcmp( s, match ) ) { + Cvar_Set( s, Cmd_Argv(2) ); + cv = Cvar_Get( s, "", 0 ); + cv->flags |= CVAR_USER_CREATED; +// com_consoleLines[i] = 0; + } + } +} + + +/* +================= +Com_AddStartupCommands + +Adds command line parameters as script statements +Commands are seperated by + signs + +Returns qtrue if any late commands were added, which +will keep the demoloop from immediately starting +================= +*/ +qboolean Com_AddStartupCommands( void ) { + int i; + qboolean added; + + added = qfalse; + // quote every token, so args with semicolons can work + for (i=0 ; i < com_numConsoleLines ; i++) { + if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) { + continue; + } + + // set commands won't override menu startup + if ( Q_stricmpn( com_consoleLines[i], "set", 3 ) ) { + added = qtrue; + } + Cbuf_AddText( com_consoleLines[i] ); + Cbuf_AddText( "\n" ); + } + + return added; +} + + +//============================================================================ + +void Info_Print( const char *s ) { + char key[512]; + char value[512]; + char *o; + int l; + + if (*s == '\\') + s++; + while (*s) + { + o = key; + while (*s && *s != '\\') + *o++ = *s++; + + l = o - key; + if (l < 20) + { + Com_Memset (o, ' ', 20-l); + key[20] = 0; + } + else + *o = 0; + Com_Printf ("%s", key); + + if (!*s) + { + Com_Printf ("MISSING VALUE\n"); + return; + } + + o = value; + s++; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (*s) + s++; + Com_Printf ("%s\n", value); + } +} + +/* +============ +Com_StringContains +============ +*/ +char *Com_StringContains(char *str1, char *str2, int casesensitive) { + int len, i, j; + + len = strlen(str1) - strlen(str2); + for (i = 0; i <= len; i++, str1++) { + for (j = 0; str2[j]; j++) { + if (casesensitive) { + if (str1[j] != str2[j]) { + break; + } + } + else { + if (toupper(str1[j]) != toupper(str2[j])) { + break; + } + } + } + if (!str2[j]) { + return str1; + } + } + return NULL; +} + +/* +============ +Com_Filter +============ +*/ +int Com_Filter(char *filter, char *name, int casesensitive) +{ + char buf[MAX_TOKEN_CHARS]; + char *ptr; + int i, found; + + while(*filter) { + if (*filter == '*') { + filter++; + for (i = 0; *filter; i++) { + if (*filter == '*' || *filter == '?') break; + buf[i] = *filter; + filter++; + } + buf[i] = '\0'; + if (strlen(buf)) { + ptr = Com_StringContains(name, buf, casesensitive); + if (!ptr) return qfalse; + name = ptr + strlen(buf); + } + } + else if (*filter == '?') { + filter++; + name++; + } + else if (*filter == '[' && *(filter+1) == '[') { + filter++; + } + else if (*filter == '[') { + filter++; + found = qfalse; + while(*filter && !found) { + if (*filter == ']' && *(filter+1) != ']') break; + if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { + if (casesensitive) { + if (*name >= *filter && *name <= *(filter+2)) found = qtrue; + } + else { + if (toupper(*name) >= toupper(*filter) && + toupper(*name) <= toupper(*(filter+2))) found = qtrue; + } + filter += 3; + } + else { + if (casesensitive) { + if (*filter == *name) found = qtrue; + } + else { + if (toupper(*filter) == toupper(*name)) found = qtrue; + } + filter++; + } + } + if (!found) return qfalse; + while(*filter) { + if (*filter == ']' && *(filter+1) != ']') break; + filter++; + } + filter++; + name++; + } + else { + if (casesensitive) { + if (*filter != *name) return qfalse; + } + else { + if (toupper(*filter) != toupper(*name)) return qfalse; + } + filter++; + name++; + } + } + return qtrue; +} + +/* +============ +Com_FilterPath +============ +*/ +int Com_FilterPath(char *filter, char *name, int casesensitive) +{ + int i; + char new_filter[MAX_QPATH]; + char new_name[MAX_QPATH]; + + for (i = 0; i < MAX_QPATH-1 && filter[i]; i++) { + if ( filter[i] == '\\' || filter[i] == ':' ) { + new_filter[i] = '/'; + } + else { + new_filter[i] = filter[i]; + } + } + new_filter[i] = '\0'; + for (i = 0; i < MAX_QPATH-1 && name[i]; i++) { + if ( name[i] == '\\' || name[i] == ':' ) { + new_name[i] = '/'; + } + else { + new_name[i] = name[i]; + } + } + new_name[i] = '\0'; + return Com_Filter(new_filter, new_name, casesensitive); +} + +/* +============ +Com_HashKey +============ +*/ +int Com_HashKey(char *string, int maxlen) { + int register hash, i; + + hash = 0; + for (i = 0; i < maxlen && string[i] != '\0'; i++) { + hash += string[i] * (119 + i); + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + return hash; +} + +/* +================ +Com_RealTime +================ +*/ +int Com_RealTime(qtime_t *qtime) { + time_t t; + struct tm *tms; + + t = time(NULL); + if (!qtime) + return t; + tms = localtime(&t); + if (tms) { + qtime->tm_sec = tms->tm_sec; + qtime->tm_min = tms->tm_min; + qtime->tm_hour = tms->tm_hour; + qtime->tm_mday = tms->tm_mday; + qtime->tm_mon = tms->tm_mon; + qtime->tm_year = tms->tm_year; + qtime->tm_wday = tms->tm_wday; + qtime->tm_yday = tms->tm_yday; + qtime->tm_isdst = tms->tm_isdst; + } + return t; +} + + +/* +=================================================================== + +EVENTS AND JOURNALING + +In addition to these events, .cfg files are also copied to the +journaled file +=================================================================== +*/ + +// bk001129 - here we go again: upped from 64 +#define MAX_PUSHED_EVENTS 1024 + +#ifdef _XBOX +static int com_pushedEventsHead[2] = {0, 0}; +static int com_pushedEventsTail[2] = {0, 0}; +static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS][2]; +#else +// bk001129 - init, also static +static int com_pushedEventsHead = 0; +static int com_pushedEventsTail = 0; +// bk001129 - static +static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS]; +#endif // _XBOX + +/* +================= +Com_InitJournaling +================= +*/ +void Com_InitJournaling( void ) { + Com_StartupVariable( "journal" ); + com_journal = Cvar_Get ("journal", "0", CVAR_INIT); + if ( !com_journal->integer ) { + return; + } + + if ( com_journal->integer == 1 ) { + Com_Printf( "Journaling events\n"); + com_journalFile = FS_FOpenFileWrite( "journal.dat" ); + com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" ); + } else if ( com_journal->integer == 2 ) { + Com_Printf( "Replaying journaled events\n"); + FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue ); + FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, qtrue ); + } + + if ( !com_journalFile || !com_journalDataFile ) { + Cvar_Set( "com_journal", "0" ); + com_journalFile = 0; + com_journalDataFile = 0; + Com_Printf( "Couldn't open journal files\n" ); + } +} + +/* +================= +Com_GetRealEvent +================= +*/ +sysEvent_t Com_GetRealEvent( void ) { + int r; + sysEvent_t ev; + + // either get an event from the system or the journal file + if ( com_journal->integer == 2 ) { + r = FS_Read( &ev, sizeof(ev), com_journalFile ); + if ( r != sizeof(ev) ) { + Com_Error( ERR_FATAL, "Error reading from journal file" ); + } + if ( ev.evPtrLength ) { + ev.evPtr = Z_Malloc( ev.evPtrLength, TAG_EVENT, qtrue ); + r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile ); + if ( r != ev.evPtrLength ) { + Com_Error( ERR_FATAL, "Error reading from journal file" ); + } + } + } else { + ev = Sys_GetEvent(); + + // write the journal value out if needed + if ( com_journal->integer == 1 ) { + r = FS_Write( &ev, sizeof(ev), com_journalFile ); + if ( r != sizeof(ev) ) { + Com_Error( ERR_FATAL, "Error writing to journal file" ); + } + if ( ev.evPtrLength ) { + r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile ); + if ( r != ev.evPtrLength ) { + Com_Error( ERR_FATAL, "Error writing to journal file" ); + } + } + } + } + + return ev; +} + + +/* +================= +Com_InitPushEvent +================= +*/ +// bk001129 - added +void Com_InitPushEvent( void ) { + // clear the static buffer array + // this requires SE_NONE to be accepted as a valid but NOP event + memset( com_pushedEvents, 0, sizeof(com_pushedEvents) ); + // reset counters while we are at it + // beware: GetEvent might still return an SE_NONE from the buffer +#ifdef _XBOX + com_pushedEventsHead[0] = com_pushedEventsHead[1] = 0; + com_pushedEventsTail[0] = com_pushedEventsTail[1] = 0; +#else + com_pushedEventsHead = 0; + com_pushedEventsTail = 0; +#endif +} + + +/* +================= +Com_PushEvent +================= +*/ +void Com_PushEvent( sysEvent_t *event ) { + sysEvent_t *ev; + static int printedWarning = 0; // bk001129 - init, bk001204 - explicit int + +#ifdef _XBOX + ev = &com_pushedEvents[ com_pushedEventsHead[ClientManager::ActiveClientNum()] & (MAX_PUSHED_EVENTS-1) ][ClientManager::ActiveClientNum()]; + + if ( com_pushedEventsHead[ClientManager::ActiveClientNum()] - com_pushedEventsTail[ClientManager::ActiveClientNum()] >= MAX_PUSHED_EVENTS ) { + + // don't print the warning constantly, or it can give time for more... + if ( !printedWarning ) { + printedWarning = qtrue; + Com_Printf( "WARNING: Com_PushEvent overflow\n" ); + } + + if ( ev->evPtr ) { + Z_Free( ev->evPtr ); + } + com_pushedEventsTail[ClientManager::ActiveClientNum()]++; + } else { + printedWarning = qfalse; + } + + *ev = *event; + com_pushedEventsHead[ClientManager::ActiveClientNum()]++; +#else + ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ]; + + if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) { + + // don't print the warning constantly, or it can give time for more... + if ( !printedWarning ) { + printedWarning = qtrue; + Com_Printf( "WARNING: Com_PushEvent overflow\n" ); + } + + if ( ev->evPtr ) { + Z_Free( ev->evPtr ); + } + com_pushedEventsTail++; + } else { + printedWarning = qfalse; + } + + *ev = *event; + com_pushedEventsHead++; +#endif // _XBOX +} + +/* +================= +Com_GetEvent +================= +*/ +sysEvent_t Com_GetEvent( void ) { +#ifdef _XBOX + if ( com_pushedEventsHead[ClientManager::ActiveClientNum()] > com_pushedEventsTail[ClientManager::ActiveClientNum()] ) { + com_pushedEventsTail[ClientManager::ActiveClientNum()]++; + return com_pushedEvents[ (com_pushedEventsTail[ClientManager::ActiveClientNum()]-1) & (MAX_PUSHED_EVENTS-1) ][ClientManager::ActiveClientNum()]; + } +#else + if ( com_pushedEventsHead > com_pushedEventsTail ) { + com_pushedEventsTail++; + return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ]; + } +#endif + return Com_GetRealEvent(); +} + +/* +================= +Com_RunAndTimeServerPacket +================= +*/ +void Com_RunAndTimeServerPacket( netadr_t *evFrom, msg_t *buf ) { + int t1, t2, msec; + + t1 = 0; + + if ( com_speeds->integer ) { + t1 = Sys_Milliseconds (); + } + + SV_PacketEvent( *evFrom, buf ); + + if ( com_speeds->integer ) { + t2 = Sys_Milliseconds (); + msec = t2 - t1; + if ( com_speeds->integer == 3 ) { + Com_Printf( "SV_PacketEvent time: %i\n", msec ); + } + } +} + +/* +================= +Com_EventLoop + +Returns last event time +================= +*/ +int Com_EventLoop( void ) { + sysEvent_t ev; + netadr_t evFrom; + static byte bufData[MAX_MSGLEN]; + msg_t buf; + + MSG_Init( &buf, bufData, sizeof( bufData ) ); + + while ( 1 ) { + ev = Com_GetEvent(); + + // if no more events are available + if ( ev.evType == SE_NONE ) { + // manually send packet events for the loopback channel + while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) { + CL_PacketEvent( evFrom, &buf ); + } + + while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) { + // if the server just shut down, flush the events + if ( com_sv_running->integer ) { + Com_RunAndTimeServerPacket( &evFrom, &buf ); + } + } + + return ev.evTime; + } + + + switch ( ev.evType ) { + default: + // bk001129 - was ev.evTime + Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType ); + break; + case SE_NONE: + break; + case SE_KEY: + CL_KeyEvent( ev.evValue, (qboolean)ev.evValue2, ev.evTime ); + break; + case SE_CHAR: + CL_CharEvent( ev.evValue ); + break; + case SE_MOUSE: + CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_JOYSTICK_AXIS: + CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_CONSOLE: + if ( ((char *)ev.evPtr)[0] == '\\' || ((char *)ev.evPtr)[0] == '/' ) + { + Cbuf_AddText( (char *)ev.evPtr+1 ); + } + else + { + Cbuf_AddText( (char *)ev.evPtr ); + } + Cbuf_AddText( "\n" ); + break; + case SE_PACKET: + // this cvar allows simulation of connections that + // drop a lot of packets. Note that loopback connections + // don't go through here at all. + if ( com_dropsim->value > 0 ) { + static int seed; + + if ( Q_random( &seed ) < com_dropsim->value ) { + break; // drop this packet + } + } + + evFrom = *(netadr_t *)ev.evPtr; + buf.cursize = ev.evPtrLength - sizeof( evFrom ); + + // we must copy the contents of the message out, because + // the event buffers are only large enough to hold the + // exact payload, but channel messages need to be large + // enough to hold fragment reassembly + if ( (unsigned)buf.cursize > buf.maxsize ) { + Com_Printf("Com_EventLoop: oversize packet\n"); + continue; + } + Com_Memcpy( buf.data, (byte *)((netadr_t *)ev.evPtr + 1), buf.cursize ); + if ( com_sv_running->integer ) { + Com_RunAndTimeServerPacket( &evFrom, &buf ); + } else { + CL_PacketEvent( evFrom, &buf ); + } + break; + case SE_BROADCAST_PACKET: + // Bwa ha ha! Take that, evil UDP broadcast users! +// evFrom = *(netadr_t *)ev.evPtr; +// memset(&evFrom, 0, sizeof(evFrom)); + buf.cursize = ev.evPtrLength;// - sizeof( evFrom ); + + // we must copy the contents of the message out, because + // the event buffers are only large enough to hold the + // exact payload, but channel messages need to be large + // enough to hold fragment reassembly + if ( (unsigned)buf.cursize > buf.maxsize ) { + Com_Printf("Com_EventLoop: oversize packet\n"); + continue; + } + Com_Memcpy( buf.data, ev.evPtr, buf.cursize ); + + // The ONLY broadcast packets we should EVER see are from system link servers! + Syslink_PacketEvent( &buf ); + break; + } + + // free any block data + if ( ev.evPtr ) { + Z_Free( ev.evPtr ); + } + } + + return 0; // never reached +} + +/* +================ +Com_Milliseconds + +Can be used for profiling, but will be journaled accurately +================ +*/ +int Com_Milliseconds (void) { + sysEvent_t ev; + + // get events and push them until we get a null event with the current time + do { + + ev = Com_GetRealEvent(); + if ( ev.evType != SE_NONE ) { + Com_PushEvent( &ev ); + } + } while ( ev.evType != SE_NONE ); + + return ev.evTime; +} + +//============================================================================ + +/* +============= +Com_Error_f + +Just throw a fatal error to +test error shutdown procedures +============= +*/ +static void Com_Error_f (void) { + if ( Cmd_Argc() > 1 ) { + Com_Error( ERR_DROP, "Testing drop error" ); + } else { + Com_Error( ERR_FATAL, "Testing fatal error" ); + } +} + + +/* +============= +Com_Freeze_f + +Just freeze in place for a given number of seconds to test +error recovery +============= +*/ +static void Com_Freeze_f (void) { + float s; + int start, now; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "freeze \n" ); + return; + } + s = atof( Cmd_Argv(1) ); + + start = Com_Milliseconds(); + + while ( 1 ) { + now = Com_Milliseconds(); + if ( ( now - start ) * 0.001 > s ) { + break; + } + } +} + +/* +================= +Com_Crash_f + +A way to force a bus error for development reasons +================= +*/ +static void Com_Crash_f( void ) { + * ( int * ) 0 = 0x12345678; +} + +#ifdef USE_CD_KEY + +qboolean CL_CDKeyValidate( const char *key, const char *checksum ); + +/* +================= +Com_ReadCDKey +================= +*/ +void Com_ReadCDKey( const char *filename ) { + fileHandle_t f; + char buffer[33]; + char fbuffer[MAX_OSPATH]; + + sprintf(fbuffer, "%s/q3key", filename); + + FS_SV_FOpenFileRead( fbuffer, &f ); + if ( !f ) { + Q_strncpyz( cl_cdkey, " ", 17 ); + return; + } + + Com_Memset( buffer, 0, sizeof(buffer) ); + + FS_Read( buffer, 16, f ); + FS_FCloseFile( f ); + + if (CL_CDKeyValidate(buffer, NULL)) { + Q_strncpyz( cl_cdkey, buffer, 17 ); + } else { + Q_strncpyz( cl_cdkey, " ", 17 ); + } +} + +/* +================= +Com_AppendCDKey +================= +*/ +void Com_AppendCDKey( const char *filename ) { + fileHandle_t f; + char buffer[33]; + char fbuffer[MAX_OSPATH]; + + sprintf(fbuffer, "%s/q3key", filename); + + FS_SV_FOpenFileRead( fbuffer, &f ); + if (!f) { + Q_strncpyz( &cl_cdkey[16], " ", 17 ); + return; + } + + Com_Memset( buffer, 0, sizeof(buffer) ); + + FS_Read( buffer, 16, f ); + FS_FCloseFile( f ); + + if (CL_CDKeyValidate(buffer, NULL)) { + strcat( &cl_cdkey[16], buffer ); + } else { + Q_strncpyz( &cl_cdkey[16], " ", 17 ); + } +} + +#ifndef DEDICATED // bk001204 +/* +================= +Com_WriteCDKey +================= +*/ +static void Com_WriteCDKey( const char *filename, const char *ikey ) { +#ifndef _XBOX + fileHandle_t f; + char fbuffer[MAX_OSPATH]; + char key[17]; + + + sprintf(fbuffer, "%s/q3key", filename); + + + Q_strncpyz( key, ikey, 17 ); + + if(!CL_CDKeyValidate(key, NULL) ) { + return; + } + + f = FS_SV_FOpenFileWrite( fbuffer ); + if ( !f ) { + Com_Printf ("Couldn't write %s.\n", filename ); + return; + } + + FS_Write( key, 16, f ); + + FS_Printf( f, "\n// generated by jamp, do not modify\r\n" ); + FS_Printf( f, "// Do not give this file to ANYONE.\r\n" ); + FS_Printf( f, "// id Software and Activision will NOT ask you to send this file to them.\r\n"); + + FS_FCloseFile( f ); +#endif +} +#endif + +#endif // USE_CD_KEY + + +#ifdef MEM_DEBUG + void SH_Register(void); +#endif + +/* +================= +Com_Init +================= +*/ +void Com_Init( char *commandLine ) { + char *s; + + Com_Printf( "%s %s %s\n", Q3_VERSION, CPUSTRING, __DATE__ ); + + try + { + // Grab the user's langauge preference from the dashboard right away! + // We only support french/german/english (with english as default) + g_dwLanguage = XGetLanguage(); + if( g_dwLanguage != XC_LANGUAGE_FRENCH && g_dwLanguage != XC_LANGUAGE_GERMAN ) + g_dwLanguage = XC_LANGUAGE_ENGLISH; + + // bk001129 - do this before anything else decides to push events + Com_InitPushEvent(); + + Cvar_Init (); + + // prepare enough of the subsystems to handle + // cvar and command buffer management + Com_ParseCommandLine( commandLine ); + + // Swap_Init (); + Cbuf_Init (); + + Com_InitZoneMemory(); + +#ifdef _XBOX + // We get a big head-start on getting our IP address (which can take a while) + // Our version no lnoger blocks during DHCP negotiation, and we use another + // function to force the process to finish later (in main()) + NET_Init(); + + extern void WF_Init(); + WF_Init(); + + // Init client manager stuff + ClientManager::Init(1); + + ClientManager::ActivateClient(0); + ClientManager::SetMainClient(0); + + ClientManager::splitScreenMode = qfalse; +#endif + + Cmd_Init (); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + // Seed the random number generator + Rand_Init(Sys_Milliseconds(true)); + + // get the developer cvar set as early as possible + Com_StartupVariable( "developer" ); + + // done early so bind command exists + CL_InitKeyCommands(); + +#ifdef _XBOX + extern void Sys_InitFileCodes(); + extern void Sys_FilecodeScan_f(); + Sys_InitFileCodes(); + Cmd_AddCommand("filecodes", Sys_FilecodeScan_f); + + extern void Sys_StreamInit(); + Sys_StreamInit(); +#endif + + FS_InitFilesystem (); + + Com_InitJournaling(); + + Cbuf_AddText ("exec mpdefault.cfg\n"); + + // skip the jampconfig.cfg if "safe" is on the command line + if ( !Com_SafeMode() ) { +#ifdef DEDICATED + Cbuf_AddText ("exec jampserver.cfg\n"); +#else + Cbuf_AddText ("exec jampconfig.cfg\n"); +#endif + } + + Cbuf_AddText ("exec autoexec.cfg\n"); + + Cbuf_Execute (); + + // Start sound super-early. This allocates all kinds of crap using new + // that never gets freed. + if ( !cls.soundStarted ) { + cls.soundStarted = qtrue; + S_Init(); + } + if ( !cls.soundRegistered ) { + cls.soundRegistered = qtrue; + S_BeginRegistration(ClientManager::NumClients()); + } + + // Similarly, get the shadertext loaded nice and early. + // Flag the call to not bother making the hash tables + extern void ScanAndLoadShaderFiles( const char *path, bool doHash ); + ScanAndLoadShaderFiles( "shaders", false ); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + // get dedicated here for proper hunk megs initialization + #ifdef DEDICATED + com_dedicated = Cvar_Get ("dedicated", "2", CVAR_ROM); + #else + com_dedicated = Cvar_Get ("dedicated", "0", CVAR_LATCH); + #endif + // allocate the stack based hunk allocator + Com_InitHunkMemory(); + + // if any archived cvars are modified after this, we will trigger a writing + // of the config file + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + // + // init commands and vars + // + com_maxfps = Cvar_Get ("com_maxfps", "85", CVAR_ARCHIVE); + com_blood = Cvar_Get ("com_blood", "1", CVAR_ARCHIVE); + + com_developer = Cvar_Get ("developer", "0", CVAR_TEMP ); + com_vmdebug = Cvar_Get ("vmdebug", "0", CVAR_TEMP ); + com_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP ); + + com_timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO ); + com_fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT); + com_showtrace = Cvar_Get ("com_showtrace", "0", CVAR_CHEAT); + + com_terrainPhysics = Cvar_Get ("com_terrainPhysics", "1", CVAR_CHEAT); + + com_dropsim = Cvar_Get ("com_dropsim", "0", CVAR_CHEAT); + com_viewlog = Cvar_Get( "viewlog", "0", CVAR_CHEAT ); + com_speeds = Cvar_Get ("com_speeds", "0", 0); + com_timedemo = Cvar_Get ("timedemo", "0", 0); + com_cameraMode = Cvar_Get ("com_cameraMode", "0", CVAR_CHEAT); + + cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM); + sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM); + com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM); + com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM); + com_buildScript = Cvar_Get( "com_buildScript", "0", 0 ); + +#ifdef G2_PERFORMANCE_ANALYSIS + com_G2Report = Cvar_Get("com_G2Report", "0", 0); +#endif + + com_RMG = Cvar_Get("RMG", "0", 0); + + Cvar_Get ("RMG_seed", "0", 0); + Cvar_Get ("RMG_time", "day", 0); + Cvar_Get ("RMG_soundset", "", 0); + + Cvar_Get ("RMG_textseed", "0", CVAR_SYSTEMINFO|CVAR_ARCHIVE); + Cvar_Get ("RMG_map", "small", CVAR_ARCHIVE|CVAR_SYSTEMINFO); + Cvar_Get ("RMG_timefile", "day", CVAR_ARCHIVE); + Cvar_Get ("RMG_terrain", "grassyhills", CVAR_ARCHIVE); + + Cvar_Get ("RMG_sky", "", CVAR_SYSTEMINFO ); + Cvar_Get ("RMG_fog", "", CVAR_SYSTEMINFO ); + Cvar_Get ("RMG_weather", "", CVAR_SYSTEMINFO|CVAR_SERVERINFO|CVAR_CHEAT ); + Cvar_Get ("RMG_instances", "colombia", CVAR_SYSTEMINFO ); + Cvar_Get ("RMG_miscents", "deciduous", 0); + Cvar_Get ("RMG_music", "music/dm_kam1", 0); + Cvar_Get ("RMG_mission", "ctf", CVAR_SYSTEMINFO ); + Cvar_Get ("RMG_course", "standard", CVAR_SYSTEMINFO ); + Cvar_Get ("RMG_distancecull", "5000", CVAR_CHEAT ); + + com_introPlayed = Cvar_Get( "com_introplayed", "0", CVAR_ARCHIVE); + + #if defined(_WIN32) && defined(_DEBUG) + com_noErrorInterrupt = Cvar_Get( "com_noErrorInterrupt", "0", 0 ); + #endif + + if ( com_dedicated->integer ) { + if ( !com_viewlog->integer ) { + Cvar_Set( "viewlog", "1" ); + } + } + + if ( com_developer && com_developer->integer ) { + Cmd_AddCommand ("error", Com_Error_f); + Cmd_AddCommand ("crash", Com_Crash_f ); + Cmd_AddCommand ("freeze", Com_Freeze_f); + } + Cmd_AddCommand ("quit", Com_Quit_f); + Cmd_AddCommand ("changeVectors", MSG_ReportChangeVectors_f ); + Cmd_AddCommand ("writeconfig", Com_WriteConfig_f ); + + s = va("%s %s %s", Q3_VERSION, CPUSTRING, __DATE__ ); + com_version = Cvar_Get ("version", s, CVAR_ROM | CVAR_SERVERINFO ); + + SE_Init(); + + Sys_Init(); + Netchan_Init( Com_Milliseconds() & 0xffff ); // pick a port value that should be nice and random + VM_Init(); + SV_Init(); +#ifdef _XBOX + //Load this earlier so it doesn't create a fragment in the middle of + //the zone. + extern int PC_LoadGlobalDefines(const char*); + PC_LoadGlobalDefines("ui/jamp/menudef.h"); +#endif + + com_dedicated->modified = qfalse; + if ( !com_dedicated->integer ) { + CL_Init(); + Sys_ShowConsole( com_viewlog->integer, qfalse ); + } + + // set com_frameTime so that if a map is started on the + // command line it will still be able to count on com_frameTime + // being random enough for a serverid + com_frameTime = Com_Milliseconds(); + + + // add + commands from command line + if ( !Com_AddStartupCommands() ) + { + // if the user didn't give any commands, run default action + if ( !com_dedicated->integer ) + { +#ifndef _DEBUG + Cbuf_AddText ("cinematic openinglogos.roq\n"); +#endif + // intro.roq is iD's. +// if( !com_introPlayed->integer ) { +// Cvar_Set( com_introPlayed->name, "1" ); +// Cvar_Set( "nextmap", "cinematic intro.RoQ" ); +// } + } + } + + // start in full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + CL_StartHunkUsers(); + + // make sure single player is off by default + Cvar_Set("ui_singlePlayerActive", "0"); + +#ifdef MEM_DEBUG + SH_Register(); +#endif + + com_fullyInitialized = qtrue; + Com_Printf ("--- Common Initialization Complete ---\n"); + + } + + catch (const char* reason) { + Sys_Error ("Error during initialization: %s", reason); + } +} + +//================================================================== + +void Com_WriteConfigToFile( const char *filename ) { +#ifndef _XBOX + fileHandle_t f; + + f = FS_FOpenFileWrite( filename ); + if ( !f ) { + Com_Printf ("Couldn't write %s.\n", filename ); + return; + } + + FS_Printf (f, "// generated by Star Wars Jedi Academy MP, do not modify\n"); + Key_WriteBindings (f); + Cvar_WriteVariables (f); + FS_FCloseFile( f ); +#endif +} + + +/* +=============== +Com_WriteConfiguration + +Writes key bindings and archived cvars to config file if modified +=============== +*/ +void Com_WriteConfiguration( void ) { +#ifndef DEDICATED // bk001204 +#ifdef USE_CD_KEY + cvar_t *fs; +#endif // USE_CD_KEY +#endif + // if we are quiting without fully initializing, make sure + // we don't write out anything + if ( !com_fullyInitialized ) { + return; + } + + if ( !(cvar_modifiedFlags & CVAR_ARCHIVE ) ) { + return; + } + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + +#ifdef DEDICATED + Com_WriteConfigToFile( "jampserver.cfg" ); +#else + Com_WriteConfigToFile( "jampconfig.cfg" ); +#endif + + // bk001119 - tentative "not needed for dedicated" +#ifndef DEDICATED +#ifdef USE_CD_KEY + fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) { + Com_WriteCDKey( fs->string, &cl_cdkey[16] ); + } else { + Com_WriteCDKey( "base", cl_cdkey ); + } +#endif // USE_CD_KEY +#endif +} + + +/* +=============== +Com_WriteConfig_f + +Write the config file to a specific name +=============== +*/ +void Com_WriteConfig_f( void ) { + char filename[MAX_QPATH]; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: writeconfig \n" ); + return; + } + + Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + Com_Printf( "Writing %s.\n", filename ); + Com_WriteConfigToFile( filename ); +} + +/* +================ +Com_ModifyMsec +================ +*/ +int Com_ModifyMsec( int msec ) { + int clampTime; + + // + // modify time for debugging values + // + if ( com_fixedtime->integer ) { + msec = com_fixedtime->integer; + } else if ( com_timescale->value ) { + msec *= com_timescale->value; + } else if (com_cameraMode->integer) { + msec *= com_timescale->value; + } + + // don't let it scale below 1 msec + if ( msec < 1 && com_timescale->value) { + msec = 1; + } + + if ( com_dedicated->integer ) { + // dedicated servers don't want to clamp for a much longer + // period, because it would mess up all the client's views + // of time. + if ( msec > 500 ) { + Com_Printf( "Hitch warning: %i msec frame time\n", msec ); + } + clampTime = 5000; + } else + if ( !com_sv_running->integer ) { + // clients of remote servers do not want to clamp time, because + // it would skew their view of the server's time temporarily + clampTime = 5000; + } else { + // for local single player gaming + // we may want to clamp the time to prevent players from + // flying off edges when something hitches. + clampTime = 200; + } + + if ( msec > clampTime ) { + msec = clampTime; + } + + return msec; +} + +#ifdef G2_PERFORMANCE_ANALYSIS +#include "../qcommon/timing.h" +void G2Time_ResetTimers(void); +void G2Time_ReportTimers(void); +extern timing_c G2PerformanceTimer_PreciseFrame; +extern int G2Time_PreciseFrame; +#endif + +/* +================= +Com_Frame +================= +*/ +void Com_Frame( void ) { + +try +{ +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceTimer_PreciseFrame.Start(); +#endif + int msec, minMsec; + static int lastTime; + int key; + + int timeBeforeFirstEvents; + int timeBeforeServer; + int timeBeforeEvents; + int timeBeforeClient; + int timeAfter; + + + // bk001204 - init to zero. + // also: might be clobbered by `longjmp' or `vfork' + timeBeforeFirstEvents =0; + timeBeforeServer =0; + timeBeforeEvents =0; + timeBeforeClient = 0; + timeAfter = 0; + + + // old net chan encryption key + key = 0x87243987; + + // write config file if anything changed + Com_WriteConfiguration(); + + // if "viewlog" has been modified, show or hide the log console + if ( com_viewlog->modified ) { + if ( !com_dedicated->value ) { + Sys_ShowConsole( com_viewlog->integer, qfalse ); + } + com_viewlog->modified = qfalse; + } + + // + // main event loop + // + if ( com_speeds->integer ) { + timeBeforeFirstEvents = Sys_Milliseconds (); + } + + // we may want to spin here if things are going too fast + if ( !com_dedicated->integer && com_maxfps->integer > 0 && !com_timedemo->integer ) { + minMsec = 1000 / com_maxfps->integer; + } else { + minMsec = 1; + } + do { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + CM_START_LOOP(); + com_frameTime = Com_EventLoop(); + CM_END_LOOP(); + } + else +#endif + com_frameTime = Com_EventLoop(); + if ( lastTime > com_frameTime ) { + lastTime = com_frameTime; // possible on first frame + } + msec = com_frameTime - lastTime; + } while ( msec < minMsec ); + Cbuf_Execute (); + + lastTime = com_frameTime; + + // mess with msec if needed + com_frameMsec = msec; + msec = Com_ModifyMsec( msec ); + + // + // server side + // + if ( com_speeds->integer ) { + timeBeforeServer = Sys_Milliseconds (); + } + + SV_Frame( msec ); + + // if "dedicated" has been modified, start up + // or shut down the client system. + // Do this after the server may have started, + // but before the client tries to auto-connect + // XBOX: Nope, our "dedicated" server still needs some client, for UI and such +/* + if ( com_dedicated->modified ) { + // get the latched value + Cvar_Get( "dedicated", "0", 0 ); + com_dedicated->modified = qfalse; + if ( !com_dedicated->integer ) { + CL_Init(); + Sys_ShowConsole( com_viewlog->integer, qfalse ); + CL_StartHunkUsers(); //fire up the UI! + } else { + CL_Shutdown(); + Sys_ShowConsole( 1, qtrue ); + } + } +*/ + + // + // client system + // + if ( !com_dedicated->integer ) { + // + // run event loop a second time to get server to client packets + // without a frame of latency + // + if ( com_speeds->integer ) { + timeBeforeEvents = Sys_Milliseconds (); + } +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + CM_START_LOOP(); + Com_EventLoop(); + CM_END_LOOP(); + } + else +#endif + Com_EventLoop(); + Cbuf_Execute (); + + + // + // client side + // + if ( com_speeds->integer ) { + timeBeforeClient = Sys_Milliseconds (); + } + + CL_Frame( msec ); + + if ( com_speeds->integer ) { + timeAfter = Sys_Milliseconds (); + } + } + else + { + CL_Frame( msec ); + } + + // + // report timing information + // + if ( com_speeds->integer ) { + int all, sv, ev, cl; + + all = timeAfter - timeBeforeServer; + sv = timeBeforeEvents - timeBeforeServer; + ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents; + cl = timeAfter - timeBeforeClient; + sv -= time_game; + cl -= time_frontend + time_backend; + + Com_Printf ("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n", + com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend ); + } + + // + // trace optimization tracking + // + if ( com_showtrace->integer ) { + + extern int c_traces, c_brush_traces, c_patch_traces; + extern int c_pointcontents; + + Com_Printf ("%4i traces (%ib %ip) %4i points\n", c_traces, + c_brush_traces, c_patch_traces, c_pointcontents); + c_traces = 0; + c_brush_traces = 0; + c_patch_traces = 0; + c_pointcontents = 0; + } + + // old net chan encryption key + key = lastTime * 0x87243987; + + com_frameNumber++; + +#ifdef _XBOX + // Need to do Xbox Live frame here, because it can trigger an ERR_DROP + if(ClientManager::splitScreenMode == false) + XBL_Tick(); +#endif + +}//try + catch (const char* reason) { + Com_Printf (reason); + return; // an ERR_DROP was thrown + } + +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_PreciseFrame += G2PerformanceTimer_PreciseFrame.End(); + + if (com_G2Report && com_G2Report->integer) + { + G2Time_ReportTimers(); + } + + G2Time_ResetTimers(); +#endif +} + +/* +================= +Com_Shutdown +================= +*/ +void MSG_shutdownHuffman(); +void Com_Shutdown (void) +{ + CM_ClearMap(); + + if (logfile) { + FS_FCloseFile (logfile); + logfile = 0; + com_logfile->integer = 0;//don't open up the log file again!! + } + + if ( com_journalFile ) { + FS_FCloseFile( com_journalFile ); + com_journalFile = 0; + } + + MSG_shutdownHuffman(); +/* + // Only used for testing changes to huffman frequency table when tuning. + { + extern float Huff_GetCR(void); + char mess[256]; + sprintf(mess,"Eff. CR = %f\n",Huff_GetCR()); + OutputDebugString(mess); + } +*/ +} + +#if !( defined __linux__ || defined __FreeBSD__ ) // r010123 - include FreeBSD +#if defined(_XBOX) || ((!id386) && (!defined __i386__)) // rcg010212 - for PPC + +void Com_Memcpy (void* dest, const void* src, const size_t count) +{ + memcpy(dest, src, count); +} + +void Com_Memset (void* dest, const int val, const size_t count) +{ + memset(dest, val, count); +} + +#else + +typedef enum +{ + PRE_READ, // prefetch assuming that buffer is used for reading only + PRE_WRITE, // prefetch assuming that buffer is used for writing only + PRE_READ_WRITE // prefetch assuming that buffer is used for both reading and writing +} e_prefetch; + +void Com_Prefetch (const void *s, const unsigned int bytes, e_prefetch type); + +#define EMMS_INSTRUCTION __asm emms + +void _copyDWord (unsigned int* dest, const unsigned int constant, const unsigned int count) { + __asm + { + mov edx,dest + mov eax,constant + mov ecx,count + and ecx,~7 + jz padding + sub ecx,8 + jmp loopu + align 16 +loopu: + test [edx+ecx*4 + 28],ebx // fetch next block destination to L1 cache + mov [edx+ecx*4 + 0],eax + mov [edx+ecx*4 + 4],eax + mov [edx+ecx*4 + 8],eax + mov [edx+ecx*4 + 12],eax + mov [edx+ecx*4 + 16],eax + mov [edx+ecx*4 + 20],eax + mov [edx+ecx*4 + 24],eax + mov [edx+ecx*4 + 28],eax + sub ecx,8 + jge loopu +padding: mov ecx,count + mov ebx,ecx + and ecx,7 + jz outta + and ebx,~7 + lea edx,[edx+ebx*4] // advance dest pointer + test [edx+0],eax // fetch destination to L1 cache + cmp ecx,4 + jl skip4 + mov [edx+0],eax + mov [edx+4],eax + mov [edx+8],eax + mov [edx+12],eax + add edx,16 + sub ecx,4 +skip4: cmp ecx,2 + jl skip2 + mov [edx+0],eax + mov [edx+4],eax + add edx,8 + sub ecx,2 +skip2: cmp ecx,1 + jl outta + mov [edx+0],eax +outta: + } +} + +// optimized memory copy routine that handles all alignment +// cases and block sizes efficiently +void Com_Memcpy (void* dest, const void* src, const size_t count) { + Com_Prefetch (src, count, PRE_READ); + __asm + { + push edi + push esi + mov ecx,count + cmp ecx,0 // count = 0 check (just to be on the safe side) + je outta + mov edx,dest + mov ebx,src + cmp ecx,32 // padding only? + jl padding + + mov edi,ecx + and edi,~31 // edi = count&~31 + sub edi,32 + + align 16 +loopMisAligned: + mov eax,[ebx + edi + 0 + 0*8] + mov esi,[ebx + edi + 4 + 0*8] + mov [edx+edi+0 + 0*8],eax + mov [edx+edi+4 + 0*8],esi + mov eax,[ebx + edi + 0 + 1*8] + mov esi,[ebx + edi + 4 + 1*8] + mov [edx+edi+0 + 1*8],eax + mov [edx+edi+4 + 1*8],esi + mov eax,[ebx + edi + 0 + 2*8] + mov esi,[ebx + edi + 4 + 2*8] + mov [edx+edi+0 + 2*8],eax + mov [edx+edi+4 + 2*8],esi + mov eax,[ebx + edi + 0 + 3*8] + mov esi,[ebx + edi + 4 + 3*8] + mov [edx+edi+0 + 3*8],eax + mov [edx+edi+4 + 3*8],esi + sub edi,32 + jge loopMisAligned + + mov edi,ecx + and edi,~31 + add ebx,edi // increase src pointer + add edx,edi // increase dst pointer + and ecx,31 // new count + jz outta // if count = 0, get outta here + +padding: + cmp ecx,16 + jl skip16 + mov eax,dword ptr [ebx] + mov dword ptr [edx],eax + mov eax,dword ptr [ebx+4] + mov dword ptr [edx+4],eax + mov eax,dword ptr [ebx+8] + mov dword ptr [edx+8],eax + mov eax,dword ptr [ebx+12] + mov dword ptr [edx+12],eax + sub ecx,16 + add ebx,16 + add edx,16 +skip16: + cmp ecx,8 + jl skip8 + mov eax,dword ptr [ebx] + mov dword ptr [edx],eax + mov eax,dword ptr [ebx+4] + sub ecx,8 + mov dword ptr [edx+4],eax + add ebx,8 + add edx,8 +skip8: + cmp ecx,4 + jl skip4 + mov eax,dword ptr [ebx] // here 4-7 bytes + add ebx,4 + sub ecx,4 + mov dword ptr [edx],eax + add edx,4 +skip4: // 0-3 remaining bytes + cmp ecx,2 + jl skip2 + mov ax,word ptr [ebx] // two bytes + cmp ecx,3 // less than 3? + mov word ptr [edx],ax + jl outta + mov al,byte ptr [ebx+2] // last byte + mov byte ptr [edx+2],al + jmp outta +skip2: + cmp ecx,1 + jl outta + mov al,byte ptr [ebx] + mov byte ptr [edx],al +outta: + pop esi + pop edi + } +} + +void Com_Memset (void* dest, const int val, const size_t count) +{ + unsigned int fillval; + + if (count < 8) + { + __asm + { + mov edx,dest + mov eax, val + mov ah,al + mov ebx,eax + and ebx, 0xffff + shl eax,16 + add eax,ebx // eax now contains pattern + mov ecx,count + cmp ecx,4 + jl skip4 + mov [edx],eax // copy first dword + add edx,4 + sub ecx,4 + skip4: cmp ecx,2 + jl skip2 + mov word ptr [edx],ax // copy 2 bytes + add edx,2 + sub ecx,2 + skip2: cmp ecx,0 + je skip1 + mov byte ptr [edx],al // copy single byte + skip1: + } + return; + } + + fillval = val; + + fillval = fillval|(fillval<<8); + fillval = fillval|(fillval<<16); // fill dword with 8-bit pattern + + _copyDWord ((unsigned int*)(dest),fillval, count/4); + + __asm // padding of 0-3 bytes + { + mov ecx,count + mov eax,ecx + and ecx,3 + jz skipA + and eax,~3 + mov ebx,dest + add ebx,eax + mov eax,fillval + cmp ecx,2 + jl skipB + mov word ptr [ebx],ax + cmp ecx,2 + je skipA + mov byte ptr [ebx+2],al + jmp skipA +skipB: + cmp ecx,0 + je skipA + mov byte ptr [ebx],al +skipA: + } +} + +qboolean Com_Memcmp (const void *src0, const void *src1, const unsigned int count) +{ + unsigned int i; + // MMX version anyone? + + if (count >= 16) + { + unsigned int *dw = (unsigned int*)(src0); + unsigned int *sw = (unsigned int*)(src1); + + unsigned int nm2 = count/16; + for (i = 0; i < nm2; i+=4) + { + unsigned int tmp = (dw[i+0]-sw[i+0])|(dw[i+1]-sw[i+1])| + (dw[i+2]-sw[i+2])|(dw[i+3]-sw[i+3]); + if (tmp) + return qfalse; + } + } + if (count & 15) + { + byte *d = (byte*)src0; + byte *s = (byte*)src1; + for (i = count & 0xfffffff0; i < count; i++) + if (d[i]!=s[i]) + return qfalse; + } + + return qtrue; +} + +void Com_Prefetch (const void *s, const unsigned int bytes, e_prefetch type) +{ + // write buffer prefetching is performed only if + // the processor benefits from it. Read and read/write + // prefetching is always performed. + + switch (type) + { + case PRE_WRITE : break; + case PRE_READ: + case PRE_READ_WRITE: + + __asm + { + mov ebx,s + mov ecx,bytes + cmp ecx,4096 // clamp to 4kB + jle skipClamp + mov ecx,4096 +skipClamp: + add ecx,0x1f + shr ecx,5 // number of cache lines + jz skip + jmp loopie + + align 16 + loopie: test byte ptr [ebx],al + add ebx,32 + dec ecx + jnz loopie + skip: + } + + break; + } +} + +#endif +#endif // bk001208 - memset/memcpy assembly, Q_acos needed (RC4) +//------------------------------------------------------------------------ + + +/* +===================== +Q_acos + +the msvc acos doesn't always return a value between -PI and PI: + +int i; +i = 1065353246; +acos(*(float*) &i) == -1.#IND0 + + This should go in q_math but it is too late to add new traps + to game and ui +===================== +*/ +float Q_acos(float c) { + float angle; + + angle = acos(c); + + if (angle > M_PI) { + return (float)M_PI; + } + if (angle < -M_PI) { + return (float)M_PI; + } + return angle; +} + +float Q_asin(float c) +{ + float angle; + + angle = asin(c); + + if (angle > M_PI) { + return (float)M_PI; + } + if (angle < -M_PI) { + return (float)M_PI; + } + return angle; +} + +//rwwRMG: Inserted: +/* +============ +ParseTextFile +============ +*/ + +bool Com_ParseTextFile(const char *file, class CGenericParser2 &parser, bool cleanFirst) +{ + fileHandle_t f; + int length = 0; + char *buf = 0, *bufParse = 0; + + length = FS_FOpenFileByMode( file, &f, FS_READ ); + if (!f || !length) + { + return false; + } + + buf = new char [length + 1]; + FS_Read( buf, length, f ); + buf[length] = 0; + + bufParse = buf; + parser.Parse(&bufParse, cleanFirst); + delete buf; + + FS_FCloseFile( f ); + + return true; +} + +void Com_ParseTextFileDestroy(class CGenericParser2 &parser) +{ + parser.Clean(); +} + +CGenericParser2 *Com_ParseTextFile(const char *file, bool cleanFirst, bool writeable) +{ + fileHandle_t f; + int length = 0; + char *buf = 0, *bufParse = 0; + CGenericParser2 *parse; + + length = FS_FOpenFileByMode( file, &f, FS_READ ); + if (!f || !length) + { + return 0; + } + + buf = new char [length + 1]; + FS_Read( buf, length, f ); + FS_FCloseFile( f ); + buf[length] = 0; + + bufParse = buf; + + parse = new CGenericParser2; + if (!parse->Parse(&bufParse, cleanFirst, writeable)) + { + delete parse; + parse = 0; + } + + delete buf; + + return parse; +} + diff --git a/codemp/qcommon/cvar.cpp b/codemp/qcommon/cvar.cpp new file mode 100644 index 0000000..52c7f06 --- /dev/null +++ b/codemp/qcommon/cvar.cpp @@ -0,0 +1,1031 @@ +// cvar.c -- dynamic variable tracking + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" + +cvar_t *cvar_vars; +cvar_t *cvar_cheats; +int cvar_modifiedFlags; + +#define MAX_CVARS 1224 +cvar_t cvar_indexes[MAX_CVARS]; +int cvar_numIndexes; + +#define FILE_HASH_SIZE 256 +static cvar_t* hashTable[FILE_HASH_SIZE]; + +cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force); + + +static char *lastMemPool = NULL; +static int memPoolSize; + + +//If the string came from the memory pool, don't really free it. The entire +//memory pool will be wiped during the next level load. +static void Cvar_FreeString(char *string) +{ + if(!lastMemPool || string < lastMemPool || + string >= lastMemPool + memPoolSize) { + Z_Free(string); + } +} + + + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower((unsigned char)fname[i]); + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +/* +============ +Cvar_ValidateString +============ +*/ +static qboolean Cvar_ValidateString( const char *s ) { + if ( !s ) { + return qfalse; + } + if ( strchr( s, '\\' ) ) { + return qfalse; + } + if ( strchr( s, '\"' ) ) { + return qfalse; + } + if ( strchr( s, ';' ) ) { + return qfalse; + } + return qtrue; +} + +/* +============ +Cvar_FindVar +============ +*/ +static cvar_t *Cvar_FindVar( const char *var_name ) { + cvar_t *var; + long hash; + + hash = generateHashValue(var_name); + + for (var=hashTable[hash] ; var ; var=var->hashNext) { + if (!Q_stricmp(var_name, var->name)) { + return var; + } + } + + return NULL; +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValue( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return 0; + return var->value; +} + + +/* +============ +Cvar_VariableIntegerValue +============ +*/ +int Cvar_VariableIntegerValue( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return 0; + return var->integer; +} + + +/* +============ +Cvar_VariableString +============ +*/ +char *Cvar_VariableString( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return ""; + return var->string; +} + + +/* +============ +Cvar_VariableStringBuffer +============ +*/ +void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) { + *buffer = 0; + } + else { + Q_strncpyz( buffer, var->string, bufsize ); + } +} + + +/* +============ +Cvar_CommandCompletion +============ +*/ +void Cvar_CommandCompletion( void(*callback)(const char *s) ) { + cvar_t *cvar; + + for ( cvar = cvar_vars ; cvar ; cvar = cvar->next ) { + // Dont show internal cvars + if ( cvar->flags & CVAR_INTERNAL ) + { + continue; + } + callback( cvar->name ); + } +} + + +/* +============ +Cvar_Get + +If the variable already exists, the value will not be set unless CVAR_ROM +The flags will be or'ed in if the variable exists. +============ +*/ +cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) { + cvar_t *var; + long hash; + + if ( !var_name || ! var_value ) { + Com_Error( ERR_FATAL, "Cvar_Get: NULL parameter" ); + } + + if ( !Cvar_ValidateString( var_name ) ) { + Com_Printf("invalid cvar name string: %s\n", var_name ); + var_name = "BADNAME"; + } + +#if 0 // FIXME: values with backslash happen + if ( !Cvar_ValidateString( var_value ) ) { + Com_Printf("invalid cvar value string: %s\n", var_value ); + var_value = "BADVALUE"; + } +#endif + + var = Cvar_FindVar (var_name); + if ( var ) { + // if the C code is now specifying a variable that the user already + // set a value for, take the new value as the reset value + if ( ( var->flags & CVAR_USER_CREATED ) && !( flags & CVAR_USER_CREATED ) + && var_value[0] ) { + var->flags &= ~CVAR_USER_CREATED; + Cvar_FreeString( var->resetString ); + var->resetString = CopyString( var_value ); + + // ZOID--needs to be set so that cvars the game sets as + // SERVERINFO get sent to clients + if (flags & CVAR_USERINFO) + ClientManager::ActiveClient().cvar_modifiedFlags |= CVAR_USERINFO; + else + cvar_modifiedFlags |= flags; + } + + var->flags |= flags; + // only allow one non-empty reset string without a warning + if ( !var->resetString[0] ) { + // we don't have a reset string yet + Cvar_FreeString( var->resetString ); + var->resetString = CopyString( var_value ); + } else if ( var_value[0] && strcmp( var->resetString, var_value ) ) { + Com_DPrintf( "Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n", + var_name, var->resetString, var_value ); + } + // if we have a latched string, take that value now + if ( var->latchedString ) { + char *s; + + s = var->latchedString; + var->latchedString = NULL; // otherwise cvar_set2 would free it + Cvar_Set2( var_name, s, qtrue ); + Cvar_FreeString( s ); + } + +// use a CVAR_SET for rom sets, get won't override +#if 0 + // CVAR_ROM always overrides + if ( flags & CVAR_ROM ) { + Cvar_Set2( var_name, var_value, qtrue ); + } +#endif + return var; + } + + // + // allocate a new cvar + // + if ( cvar_numIndexes >= MAX_CVARS ) { + Com_Error( ERR_FATAL, "MAX_CVARS" ); + } + var = &cvar_indexes[cvar_numIndexes]; + cvar_numIndexes++; + var->name = CopyString (var_name); + var->string = CopyString (var_value); + var->modified = qtrue; + var->modificationCount = 1; + var->value = atof (var->string); + var->integer = atoi(var->string); + var->resetString = CopyString( var_value ); + + // link the variable in + var->next = cvar_vars; + cvar_vars = var; + + var->flags = flags; + + hash = generateHashValue(var_name); + var->hashNext = hashTable[hash]; + hashTable[hash] = var; + + return var; +} + +/* +============ +Cvar_Set2 +============ +*/ +cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) { + cvar_t *var; + + if ( !Cvar_ValidateString( var_name ) ) { + Com_Printf("invalid cvar name string: %s\n", var_name ); + var_name = "BADNAME"; + } + +#if 0 // FIXME + if ( value && !Cvar_ValidateString( value ) ) { + Com_Printf("invalid cvar value string: %s\n", value ); + var_value = "BADVALUE"; + } +#endif + + var = Cvar_FindVar (var_name); + if (!var) { + if ( !value ) { + return NULL; + } + // create it + if ( !force ) { + return Cvar_Get( var_name, value, CVAR_USER_CREATED ); + } else { + return Cvar_Get (var_name, value, 0); + } + } + + // Dont display the update when its internal + if ( !(var->flags & CVAR_INTERNAL) ) + { + Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value ); + } + + if (!value ) { + value = var->resetString; + } + + if (!strcmp(value,var->string)) { + return var; + } + // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) + + if (var->flags & CVAR_USERINFO) + ClientManager::ActiveClient().cvar_modifiedFlags |=CVAR_USERINFO; + else + cvar_modifiedFlags |= var->flags; + + if (!force) + { + if (var->flags & CVAR_ROM) + { + Com_Printf ("%s is read only.\n", var_name); + return var; + } + + if (var->flags & CVAR_INIT) + { + Com_Printf ("%s is write protected.\n", var_name); + return var; + } + + if (var->flags & CVAR_LATCH) + { + if (var->latchedString) + { + if (strcmp(value, var->latchedString) == 0) + return var; + Cvar_FreeString (var->latchedString); + } + else + { + if (strcmp(value, var->string) == 0) + return var; + } + + Com_Printf ("%s will be changed upon restarting.\n", var_name); + var->latchedString = CopyString(value); + var->modified = qtrue; + var->modificationCount++; + return var; + } + + if ( (var->flags & CVAR_CHEAT) && !cvar_cheats->integer ) + { + Com_Printf ("%s is cheat protected.\n", var_name); + return var; + } + + } + else + { + if (var->latchedString) + { + Cvar_FreeString (var->latchedString); + var->latchedString = NULL; + } + } + + if (!strcmp(value, var->string)) + return var; // not changed + + var->modified = qtrue; + var->modificationCount++; + + Cvar_FreeString (var->string); // free the old value string + + var->string = CopyString(value); + var->value = atof (var->string); + var->integer = atoi (var->string); + + return var; +} + +/* +============ +Cvar_Set +============ +*/ +void Cvar_Set( const char *var_name, const char *value) { + Cvar_Set2 (var_name, value, qtrue); +} + +/* +============ +Cvar_SetLatched +============ +*/ +void Cvar_SetLatched( const char *var_name, const char *value) { + Cvar_Set2 (var_name, value, qfalse); +} + +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValue( const char *var_name, float value) { + char val[32]; + + if ( value == (int)value ) { + Com_sprintf (val, sizeof(val), "%i",(int)value); + } else { + Com_sprintf (val, sizeof(val), "%f",value); + } + Cvar_Set (var_name, val); +} + + +/* +============ +Cvar_Reset +============ +*/ +void Cvar_Reset( const char *var_name ) { + Cvar_Set2( var_name, NULL, qfalse ); +} + + +/* +============ +Cvar_SetCheatState + +Any testing variables will be reset to the safe values +============ +*/ +void Cvar_SetCheatState( void ) { + cvar_t *var; + + // set all default vars to the safe value + for ( var = cvar_vars ; var ; var = var->next ) { + if ( var->flags & CVAR_CHEAT ) { + // the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here + // because of a different var->latchedString + if (var->latchedString) + { + Cvar_FreeString(var->latchedString); + var->latchedString = NULL; + } + if (strcmp(var->resetString,var->string)) { + Cvar_Set( var->name, var->resetString ); + } + } + } +} + +/* +============ +Cvar_Command + +Handles variable inspection and changing from the console +============ +*/ +qboolean Cvar_Command( void ) { + cvar_t *v; + + // check variables + v = Cvar_FindVar (Cmd_Argv(0)); + if (!v) { + return qfalse; + } + + // perform a variable print or set + if ( Cmd_Argc() == 1 ) + { +/* if (v->flags & CVAR_INTERNAL) // don't display + { + return qtrue; + } +*/ + Com_Printf ("\"%s\" is:\"%s" S_COLOR_WHITE "\" default:\"%s" S_COLOR_WHITE "\"\n", v->name, v->string, v->resetString ); + if ( v->latchedString ) { + Com_Printf( "latched: \"%s\"\n", v->latchedString ); + } + return qtrue; + } + +//JFM toggle test + char *value; + value = Cmd_Argv(1); + if (value[0] =='!') //toggle + { + char buff[5]; + sprintf(buff,"%i",!v->value); + Cvar_Set2 (v->name, buff, qfalse);// toggle the value + } + else + { + Cvar_Set2 (v->name, value, qfalse);// set the value if forcing isn't required + } + + return qtrue; +} + + +/* +============ +Cvar_Toggle_f + +Toggles a cvar for easy single key binding +============ +*/ +void Cvar_Toggle_f( void ) { + int v; + + if ( Cmd_Argc() != 2 ) { + Com_Printf ("usage: toggle \n"); + return; + } + + v = Cvar_VariableValue( Cmd_Argv( 1 ) ); + v = !v; + + Cvar_Set2 (Cmd_Argv(1), va("%i", v), qfalse); +} + +/* +============ +Cvar_Set_f + +Allows setting and defining of arbitrary cvars from console, even if they +weren't declared in C code. +============ +*/ +void Cvar_Set_f( void ) { + int i, c, l, len; + char combined[MAX_STRING_TOKENS]; + + c = Cmd_Argc(); + if ( c < 3 ) { + Com_Printf ("usage: set \n"); + return; + } + + combined[0] = 0; + l = 0; + for ( i = 2 ; i < c ; i++ ) { + len = strlen ( Cmd_Argv( i ) + 1 ); + if ( l + len >= MAX_STRING_TOKENS - 2 ) { + break; + } + strcat( combined, Cmd_Argv( i ) ); + if ( i != c-1 ) { + strcat( combined, " " ); + } + l += len; + } + Cvar_Set2 (Cmd_Argv(1), combined, qfalse); +} + +/* +============ +Cvar_SetU_f + +As Cvar_Set, but also flags it as userinfo +============ +*/ +void Cvar_SetU_f( void ) { + cvar_t *v; + + if ( Cmd_Argc() != 3 ) { + Com_Printf ("usage: setu \n"); + return; + } + Cvar_Set_f(); + v = Cvar_FindVar( Cmd_Argv( 1 ) ); + if ( !v ) { + return; + } + v->flags |= CVAR_USERINFO; +} + +/* +============ +Cvar_SetS_f + +As Cvar_Set, but also flags it as userinfo +============ +*/ +void Cvar_SetS_f( void ) { + cvar_t *v; + + if ( Cmd_Argc() != 3 ) { + Com_Printf ("usage: sets \n"); + return; + } + Cvar_Set_f(); + v = Cvar_FindVar( Cmd_Argv( 1 ) ); + if ( !v ) { + return; + } + v->flags |= CVAR_SERVERINFO; +} + +/* +============ +Cvar_SetA_f + +As Cvar_Set, but also flags it as archived +============ +*/ +void Cvar_SetA_f( void ) { + cvar_t *v; + + if ( Cmd_Argc() != 3 ) { + Com_Printf ("usage: seta \n"); + return; + } + Cvar_Set_f(); + v = Cvar_FindVar( Cmd_Argv( 1 ) ); + if ( !v ) { + return; + } + v->flags |= CVAR_ARCHIVE; +} + +/* +============ +Cvar_Reset_f +============ +*/ +void Cvar_Reset_f( void ) { + if ( Cmd_Argc() != 2 ) { + Com_Printf ("usage: reset \n"); + return; + } + Cvar_Reset( Cmd_Argv( 1 ) ); +} + +/* +============ +Cvar_WriteVariables + +Appends lines containing "set variable value" for all variables +with the archive flag set to qtrue. +============ +*/ +void Cvar_WriteVariables( fileHandle_t f ) { + cvar_t *var; + char buffer[1024]; + + for (var = cvar_vars ; var ; var = var->next) { +#ifdef USE_CD_KEY + if( Q_stricmp( var->name, "cl_cdkey" ) == 0 ) { + continue; + } +#endif // USE_CD_KEY + if( var->flags & CVAR_ARCHIVE ) { + // write the latched value, even if it hasn't taken effect yet + if ( var->latchedString ) { + Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latchedString); + } else { + Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string); + } + FS_Printf (f, "%s", buffer); + } + } +} + +/* +============ +Cvar_List_f +============ +*/ +void Cvar_List_f( void ) { + cvar_t *var; + int i; + char *match; + + if ( Cmd_Argc() > 1 ) { + match = Cmd_Argv( 1 ); + } else { + match = NULL; + } + + i = 0; + for (var = cvar_vars ; var ; var = var->next, i++) + { + // Dont show internal cvars + if ( var->flags & CVAR_INTERNAL ) + { + continue; + } + + if (match && !Com_Filter(match, var->name, qfalse)) continue; + + if (var->flags & CVAR_SERVERINFO) { + Com_Printf("S"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_USERINFO) { + Com_Printf("U"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_ROM) { + Com_Printf("R"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_INIT) { + Com_Printf("I"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_ARCHIVE) { + Com_Printf("A"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_LATCH) { + Com_Printf("L"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_CHEAT) { + Com_Printf("C"); + } else { + Com_Printf(" "); + } + + Com_Printf (" %s \"%s\"\n", var->name, var->string); + } + + Com_Printf ("\n%i total cvars\n", i); + Com_Printf ("%i cvar indexes\n", cvar_numIndexes); +} + +/* +============ +Cvar_Restart_f + +Resets all cvars to their hardcoded values +============ +*/ +void Cvar_Restart_f( void ) { + cvar_t *var; + cvar_t **prev; + + prev = &cvar_vars; + while ( 1 ) { + var = *prev; + if ( !var ) { + break; + } + + // don't mess with rom values, or some inter-module + // communication will get broken (com_cl_running, etc) + if ( var->flags & ( CVAR_ROM | CVAR_INIT | CVAR_NORESTART ) ) { + prev = &var->next; + continue; + } + + // throw out any variables the user created + if ( var->flags & CVAR_USER_CREATED ) { + *prev = var->next; + if ( var->name ) { + Cvar_FreeString( var->name ); + } + if ( var->string ) { + Cvar_FreeString( var->string ); + } + if ( var->latchedString ) { + Cvar_FreeString( var->latchedString ); + } + if ( var->resetString ) { + Cvar_FreeString( var->resetString ); + } + // clear the var completely, since we + // can't remove the index from the list + Com_Memset( var, 0, sizeof( var ) ); + continue; + } + + Cvar_Set( var->name, var->resetString ); + + prev = &var->next; + } +} + + + +/* +===================== +Cvar_InfoString +===================== +*/ +char *Cvar_InfoString( int bit ) { + static char info[MAX_INFO_STRING]; + + cvar_t *var; + + info[0] = 0; + + for (var = cvar_vars ; var ; var = var->next) + { + if (!(var->flags & CVAR_INTERNAL) && + (var->flags & bit)) + { + Info_SetValueForKey (info, var->name, var->string); + } + } + + + + /* + for (var = cvar_vars ; var ; var = var->next) + { + if ((var->flags & CVAR_INTERNAL) && + (var->flags & bit) && + !Q_stricmp(var->name, "g_debugMelee")) + { //this one must go first + Info_SetValueForKey (info, var->name, var->string); + kungFuSafety = true; + break; + } + } + if (!kungFuSafety) + { //even if it was not found, it must be in the info string + Info_SetValueForKey (info, "g_debugMelee", "1"); + } + */ + + return info; +} + +/* +===================== +Cvar_InfoString_Big + + handles large info strings ( CS_SYSTEMINFO ) +===================== +*/ +char *Cvar_InfoString_Big( int bit ) { + static char info[BIG_INFO_STRING]; + cvar_t *var; + + info[0] = 0; + + for (var = cvar_vars ; var ; var = var->next) + { + if (!(var->flags & CVAR_INTERNAL) && + (var->flags & bit)) + { + Info_SetValueForKey_Big (info, var->name, var->string); + } + } + return info; +} + + + +/* +===================== +Cvar_InfoStringBuffer +===================== +*/ +void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) { + Q_strncpyz(buff,Cvar_InfoString(bit),buffsize); +} + +/* +===================== +Cvar_Register + +basically a slightly modified Cvar_Get for the interpreted modules +===================== +*/ +void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { + cvar_t *cv; + + cv = Cvar_Get( varName, defaultValue, flags ); + if ( !vmCvar ) { + return; + } + vmCvar->handle = cv - cvar_indexes; + vmCvar->modificationCount = -1; + Cvar_Update( vmCvar ); +} + + +/* +===================== +Cvar_Register + +updates an interpreted modules' version of a cvar +===================== +*/ +void Cvar_Update( vmCvar_t *vmCvar ) { + cvar_t *cv = NULL; // bk001129 + assert(vmCvar); // bk + + if ( (unsigned)vmCvar->handle >= cvar_numIndexes ) { + Com_Error( ERR_DROP, "Cvar_Update: handle out of range" ); + } + + cv = cvar_indexes + vmCvar->handle; + + if ( cv->modificationCount == vmCvar->modificationCount ) { + return; + } + if ( !cv->string ) { + return; // variable might have been cleared by a cvar_restart + } + vmCvar->modificationCount = cv->modificationCount; + // bk001129 - mismatches. + if ( strlen(cv->string)+1 > MAX_CVAR_VALUE_STRING ) + Com_Error( ERR_DROP, "Cvar_Update: src %s length %d exceeds MAX_CVAR_VALUE_STRING", + cv->string, + strlen(cv->string), + sizeof(vmCvar->string) ); + // bk001212 - Q_strncpyz guarantees zero padding and dest[MAX_CVAR_VALUE_STRING-1]==0 + // bk001129 - paranoia. Never trust the destination string. + // bk001129 - beware, sizeof(char*) is always 4 (for cv->string). + // sizeof(vmCvar->string) always MAX_CVAR_VALUE_STRING + //Q_strncpyz( vmCvar->string, cv->string, sizeof( vmCvar->string ) ); // id + Q_strncpyz( vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING ); + + vmCvar->value = cv->value; + vmCvar->integer = cv->integer; +} + + +/* +============ +Cvar_Init + +Reads in all archived cvars +============ +*/ +void Cvar_Init (void) { + cvar_cheats = Cvar_Get("sv_cheats", "0", CVAR_ROM | CVAR_SYSTEMINFO ); + + Cmd_AddCommand ("toggle", Cvar_Toggle_f); + Cmd_AddCommand ("set", Cvar_Set_f); + Cmd_AddCommand ("sets", Cvar_SetS_f); + Cmd_AddCommand ("setu", Cvar_SetU_f); + Cmd_AddCommand ("seta", Cvar_SetA_f); + Cmd_AddCommand ("reset", Cvar_Reset_f); + Cmd_AddCommand ("cvarlist", Cvar_List_f); + Cmd_AddCommand ("cvar_restart", Cvar_Restart_f); +} + + +static void Cvar_Realloc(char **string, char *memPool, int &memPoolUsed) +{ + if(string && *string) + { + char *temp = memPool + memPoolUsed; + strcpy(temp, *string); + memPoolUsed += strlen(*string) + 1; + Cvar_FreeString(*string); + *string = temp; + } +} + + +//Turns many small allocation blocks into one big one. +void Cvar_Defrag(void) +{ + cvar_t *var; + int totalMem = 0; + int nextMemPoolSize; + + for (var = cvar_vars; var; var = var->next) + { + if (var->name) { + totalMem += strlen(var->name) + 1; + } + if (var->string) { + totalMem += strlen(var->string) + 1; + } + if (var->resetString) { + totalMem += strlen(var->resetString) + 1; + } + if (var->latchedString) { + totalMem += strlen(var->latchedString) + 1; + } + } + + char *mem = (char*)Z_Malloc(totalMem, TAG_SMALL, qfalse); + nextMemPoolSize = totalMem; + totalMem = 0; + + for (var = cvar_vars; var; var = var->next) + { + Cvar_Realloc(&var->name, mem, totalMem); + Cvar_Realloc(&var->string, mem, totalMem); + Cvar_Realloc(&var->resetString, mem, totalMem); + Cvar_Realloc(&var->latchedString, mem, totalMem); + } + + if(lastMemPool) { + Z_Free(lastMemPool); + } + lastMemPool = mem; + memPoolSize = nextMemPoolSize; +} + diff --git a/codemp/qcommon/disablewarnings.h b/codemp/qcommon/disablewarnings.h new file mode 100644 index 0000000..1d62566 --- /dev/null +++ b/codemp/qcommon/disablewarnings.h @@ -0,0 +1,38 @@ +// hide these nasty warnings + +#ifdef _WIN32 + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4032) +#pragma warning(disable : 4051) +#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4115) +#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4136) +#pragma warning(disable : 4152) // nonstandard extension, function/data pointer conversion in expression +#pragma warning(disable : 4201) +#pragma warning(disable : 4214) +#pragma warning(disable : 4244) // conversion from double to float +#pragma warning(disable : 4284) // return type not UDT +#pragma warning(disable : 4305) // truncation from const double to float +#pragma warning(disable : 4310) // cast truncates constant value +#pragma warning(disable : 4389) // signed/unsigned mismatch +#pragma warning(disable : 4503) // decorated name length truncated +//#pragma warning(disable: 4505)!!!remove these to reduce vm size!! // unreferenced local function has been removed +#pragma warning(disable : 4511) //copy ctor could not be genned +#pragma warning(disable : 4512) //assignment op could not be genned +#pragma warning(disable : 4514) // unreffed inline removed +#pragma warning(disable : 4663) // c++ lang change +#pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4710) // not inlined +#pragma warning(disable : 4711) // selected for automatic inline expansion +#pragma warning(disable : 4220) // varargs matches remaining parameters +#pragma warning(disable : 4786) //identifier was truncated + +//rww (for vc.net, warning numbers changed apparently): +#pragma warning(disable : 4213) //nonstandard extension used : cast on l-value +#pragma warning(disable : 4245) //signed/unsigned mismatch + +#endif diff --git a/codemp/qcommon/exe_headers.cpp b/codemp/qcommon/exe_headers.cpp new file mode 100644 index 0000000..d53f6e6 --- /dev/null +++ b/codemp/qcommon/exe_headers.cpp @@ -0,0 +1,3 @@ +//This file creates the PCH for the rest of the project to use + +#include "../qcommon/exe_headers.h" diff --git a/codemp/qcommon/exe_headers.h b/codemp/qcommon/exe_headers.h new file mode 100644 index 0000000..3997916 --- /dev/null +++ b/codemp/qcommon/exe_headers.h @@ -0,0 +1,5 @@ +//This is shared by client and server so it's best to keep that in mind +//for the sake of ds builds. + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" diff --git a/codemp/qcommon/files.h b/codemp/qcommon/files.h new file mode 100644 index 0000000..d381d84 --- /dev/null +++ b/codemp/qcommon/files.h @@ -0,0 +1,158 @@ +#ifndef __FILES_H +#define __FILES_H + +/* + Structures local to the files_* modules. +*/ + +#ifdef _XBOX +#include "../goblib/goblib.h" + +typedef int wfhandle_t; +#else +#include "../zlib32/zip.h" +#include "unzip.h" +#endif + +#define BASEGAME "base" +#define DEMOGAME "demo" + +// every time a new demo pk3 file is built, this checksum must be updated. +// the easiest way to get it is to just run the game and see what it spits out +#define DEMO_PAK_CHECKSUM 437558517u + +// if this is defined, the executable positively won't work with any paks other +// than the demo pak, even if productid is present. This is only used for our +// last demo release to prevent the mac and linux users from using the demo +// executable with the production windows pak before the mac/linux products +// hit the shelves a little later +// NOW defined in build files +//#define PRE_RELEASE_TADEMO + +#define MAX_ZPATH 256 +#define MAX_SEARCH_PATHS 4096 +#define MAX_FILEHASH_SIZE 1024 + +typedef struct fileInPack_s { + char *name; // name of the file + unsigned long pos; // file info position in zip + struct fileInPack_s* next; // next file in the hash +} fileInPack_t; + +typedef struct { + char pakFilename[MAX_OSPATH]; // c:\quake3\base\pak0.pk3 + char pakBasename[MAX_OSPATH]; // pak0 + char pakGamename[MAX_OSPATH]; // base +#ifndef _XBOX + unzFile handle; // handle to zip file +#endif + int checksum; // regular checksum + int pure_checksum; // checksum for pure + int numfiles; // number of files in pk3 + int referenced; // referenced file flags + int hashSize; // hash table size (power of 2) + fileInPack_t* *hashTable; // hash table + fileInPack_t* buildBuffer; // buffer with the filenames etc. +} pack_t; + +typedef struct { + char path[MAX_OSPATH]; // c:\jk2 + char gamedir[MAX_OSPATH]; // base +} directory_t; + +typedef struct searchpath_s { + struct searchpath_s *next; + + pack_t *pack; // only one of pack / dir will be non NULL + directory_t *dir; +} searchpath_t; + + +typedef union qfile_gus { + FILE* o; +#ifndef _XBOX + unzFile z; +#endif +} qfile_gut; + +typedef struct qfile_us { + qfile_gut file; + qboolean unique; +} qfile_ut; + + +typedef struct { + qfile_ut handleFiles; + qboolean handleSync; + int baseOffset; + int fileSize; + int zipFilePos; + qboolean zipFile; + qboolean streamed; + char name[MAX_ZPATH]; + +#ifdef _XBOX + GOBHandle ghandle; + qboolean gob; + qboolean used; + wfhandle_t whandle; +#endif +} fileHandleData_t; + + +extern char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators +extern cvar_t *fs_debug; +extern cvar_t *fs_homepath; +extern cvar_t *fs_basepath; +extern cvar_t *fs_basegame; +extern cvar_t *fs_cdpath; +extern cvar_t *fs_copyfiles; +extern cvar_t *fs_gamedirvar; +extern cvar_t *fs_restrict; +extern cvar_t *fs_dirbeforepak; //rww - when building search path, keep directories at top and insert pk3's under them +extern searchpath_t *fs_searchpaths; +extern int fs_readCount; // total bytes read +extern int fs_loadCount; // total files read +extern int fs_loadStack; // total files in memory +extern int fs_packFiles; // total number of files in packs + +extern int fs_fakeChkSum; +extern int fs_checksumFeed; + +extern fileHandleData_t fsh[MAX_FILE_HANDLES]; + +extern qboolean initialized; + +// never load anything from pk3 files that are not present at the server when pure +extern int fs_numServerPaks; +extern int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums +extern char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// only used for autodownload, to make sure the client has at least +// all the pk3 files that are referenced at the server side +extern int fs_numServerReferencedPaks; +extern int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums +extern char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// last valid game folder used +extern char lastValidBase[MAX_OSPATH]; +extern char lastValidGame[MAX_OSPATH]; + +#ifdef FS_MISSING +extern FILE* missingFiles; +#endif + + +void FS_Startup( const char *gameName ); +qboolean FS_CreatePath(char *OSPath); +char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ); +char *FS_BuildOSPath( const char *qpath ); +fileHandle_t FS_HandleForFile(void); +qboolean FS_FilenameCompare( const char *s1, const char *s2 ); +int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ); +void FS_Shutdown( void ); +void FS_SetRestrictions(void); +void FS_CheckInit(void); +void FS_ReplaceSeparators( char *path ); + +#endif diff --git a/codemp/qcommon/files_common.cpp b/codemp/qcommon/files_common.cpp new file mode 100644 index 0000000..9f7ceff --- /dev/null +++ b/codemp/qcommon/files_common.cpp @@ -0,0 +1,512 @@ +/***************************************************************************** + * name: files.c + * + * desc: handle based filesystem for Quake III Arena + * + *****************************************************************************/ + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "../client/client.h" +//#include "../zlib32/zip.h" +//#include "unzip.h" +#include "files.h" + +//#include //rww - included to make fs_copyfiles 2 related functions happy. +#include "platform.h" + +/* +============================================================================= + +QUAKE3 FILESYSTEM + +All of Quake's data access is through a hierarchical file system, but the contents of +the file system can be transparently merged from several sources. + +A "qpath" is a reference to game file data. MAX_ZPATH is 256 characters, which must include +a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any +references outside the quake directory system. + +The "base path" is the path to the directory holding all the game directories and usually +the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3" +command line to allow code debugging in a different directory. Basepath cannot +be modified at all after startup. Any files that are created (demos, screenshots, +etc) will be created reletive to the base path, so base path should usually be writable. + +The "cd path" is the path to an alternate hierarchy that will be searched if a file +is not located in the base path. A user can do a partial install that copies some +data to a base path created on their hard drive and leave the rest on the cd. Files +are never writen to the cd path. It defaults to a value set by the installer, like +"e:\quake3", but it can be overridden with "+set ds_cdpath g:\quake3". + +If a user runs the game directly from a CD, the base path would be on the CD. This +should still function correctly, but all file writes will fail (harmlessly). + +The "home path" is the path used for all write access. On win32 systems we have "base path" +== "home path", but on *nix systems the base installation is usually readonly, and +"home path" points to ~/.q3a or similar + +The user can also install custom mods and content in "home path", so it should be searched +along with "home path" and "cd path" for game content. + + +The "base game" is the directory under the paths where data comes from by default, and +can be either "base" or "demo". + +The "current game" may be the same as the base game, or it may be the name of another +directory under the paths that should be searched for files before looking in the base game. +This is the basis for addons. + +Clients automatically set the game directory after receiving a gamestate from a server, +so only servers need to worry about +set fs_game. + +No other directories outside of the base game and current game will ever be referenced by +filesystem functions. + +To save disk space and speed loading, directory trees can be collapsed into zip files. +The files use a ".pk3" extension to prevent users from unzipping them accidentally, but +otherwise the are simply normal uncompressed zip files. A game directory can have multiple +zip files of the form "pak0.pk3", "pak1.pk3", etc. Zip files are searched in decending order +from the highest number to the lowest, and will always take precedence over the filesystem. +This allows a pk3 distributed as a patch to override all existing data. + +Because we will have updated executables freely available online, there is no point to +trying to restrict demo / oem versions of the game with code changes. Demo / oem versions +should be exactly the same executables as release versions, but with different data that +automatically restricts where game media can come from to prevent add-ons from working. + +After the paths are initialized, quake will look for the product.txt file. If not +found and verified, the game will run in restricted mode. In restricted mode, only +files contained in demoq3/pak0.pk3 will be available for loading, and only if the zip header is +verified to not have been modified. A single exception is made for jampconfig.cfg. Files +can still be written out in restricted mode, so screenshots and demos are allowed. +Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even +if there is a valid product.txt under the basepath or cdpath. + +If not running in restricted mode, and a file is not found in any local filesystem, +an attempt will be made to download it and save it under the base path. + +If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd +path, it will be copied over to the base path. This is a development aid to help build +test releases and to copy working sets over slow network links. +(If set to 2, copying will only take place if the two filetimes are NOT EQUAL) + +File search order: when FS_FOpenFileRead gets called it will go through the fs_searchpaths +structure and stop on the first successful hit. fs_searchpaths is built with successive +calls to FS_AddGameDirectory + +Additionaly, we search in several subdirectories: +current game is the current mode +base game is a variable to allow mods based on other mods +(such as base + missionpack content combination in a mod for instance) +BASEGAME is the hardcoded base game ("base") + +e.g. the qpath "sound/newstuff/test.wav" would be searched for in the following places: + +home path + current game's zip files +home path + current game's directory +base path + current game's zip files +base path + current game's directory +cd path + current game's zip files +cd path + current game's directory + +home path + base game's zip file +home path + base game's directory +base path + base game's zip file +base path + base game's directory +cd path + base game's zip file +cd path + base game's directory + +home path + BASEGAME's zip file +home path + BASEGAME's directory +base path + BASEGAME's zip file +base path + BASEGAME's directory +cd path + BASEGAME's zip file +cd path + BASEGAME's directory + +server download, to be written to home path + current game's directory + + +The filesystem can be safely shutdown and reinitialized with different +basedir / cddir / game combinations, but all other subsystems that rely on it +(sound, video) must also be forced to restart. + +Because the same files are loaded by both the clip model (CM_) and renderer (TR_) +subsystems, a simple single-file caching scheme is used. The CM_ subsystems will +load the file with a request to cache. Only one file will be kept cached at a time, +so any models that are going to be referenced by both subsystems should alternate +between the CM_ load function and the ref load function. + +TODO: A qpath that starts with a leading slash will always refer to the base game, even if another +game is currently active. This allows character models, skins, and sounds to be downloaded +to a common directory no matter which game is active. + +How to prevent downloading zip files? +Pass pk3 file names in systeminfo, and download before FS_Restart()? + +Aborting a download disconnects the client from the server. + +How to mark files as downloadable? Commercial add-ons won't be downloadable. + +Non-commercial downloads will want to download the entire zip file. +the game would have to be reset to actually read the zip in + +Auto-update information + +Path separators + +Casing + + separate server gamedir and client gamedir, so if the user starts + a local game after having connected to a network game, it won't stick + with the network game. + + allow menu options for game selection? + +Read / write config to floppy option. + +Different version coexistance? + +When building a pak file, make sure a jampconfig.cfg isn't present in it, +or configs will never get loaded from disk! + + todo: + + downloading (outside fs?) + game directory passing and restarting + +============================================================================= + +*/ + +char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators +cvar_t *fs_debug; +cvar_t *fs_homepath; +cvar_t *fs_basepath; +cvar_t *fs_basegame; +cvar_t *fs_cdpath; +cvar_t *fs_copyfiles; +cvar_t *fs_gamedirvar; +cvar_t *fs_restrict; +cvar_t *fs_dirbeforepak; //rww - when building search path, keep directories at top and insert pk3's under them +searchpath_t *fs_searchpaths; +int fs_readCount; // total bytes read +int fs_loadCount; // total files read +int fs_loadStack; // total files in memory +int fs_packFiles; // total number of files in packs + +int fs_fakeChkSum; +int fs_checksumFeed; + +fileHandleData_t fsh[MAX_FILE_HANDLES]; + + +// never load anything from pk3 files that are not present at the server when pure +int fs_numServerPaks; +int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums +char *fs_serverPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// only used for autodownload, to make sure the client has at least +// all the pk3 files that are referenced at the server side +int fs_numServerReferencedPaks; +int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums +char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// last valid game folder used +char lastValidBase[MAX_OSPATH]; +char lastValidGame[MAX_OSPATH]; + +#ifdef FS_MISSING +FILE* missingFiles = NULL; +#endif + +qboolean initialized = qfalse; + +/* + Extra utility for checking that FS is up and running +*/ +void FS_CheckInit(void) +{ + if (!initialized) + { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } +} + +/* +============== +FS_Initialized +============== +*/ + +qboolean FS_Initialized() { + return (qboolean)(fs_searchpaths != NULL); +} + +/* +================= +FS_LoadStack +return load stack +================= +*/ +int FS_LoadStack() +{ + return fs_loadStack; +} + +fileHandle_t FS_HandleForFile(void) { + int i; + + for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { + if ( fsh[i].handleFiles.file.o == NULL ) { + return i; + } + } + Com_Error( ERR_DROP, "FS_HandleForFile: none free" ); + return 0; +} + +/* +==================== +FS_ReplaceSeparators + +Fix things up differently for win/unix/mac +==================== +*/ +void FS_ReplaceSeparators( char *path ) { + char *s; + + for ( s = path ; *s ; s++ ) { + if ( *s == '/' || *s == '\\' ) { + *s = PATH_SEP; + } + } +} + +/* +=================== +FS_BuildOSPath + +Qpath may have either forward or backwards slashes +=================== +*/ +char *FS_BuildOSPath( const char *qpath ) +{ + char temp[MAX_OSPATH]; + static char ospath[2][MAX_OSPATH]; + static int toggle; + + toggle ^= 1; // flip-flop to allow two returns without clash + + // Fix for filenames that are given to FS with a leading "/" (/botfiles/Foo) + if (qpath[0] == '\\' || qpath[0] == '/') + qpath++; + + // FIXME VVFIXME Holy crap this is wrong. +// Com_sprintf( temp, sizeof(temp), "/%s/%s", fs_gamedirvar->string, qpath ); + Com_sprintf( temp, sizeof(temp), "/%s/%s", "base", qpath ); + + FS_ReplaceSeparators( temp ); + Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", + fs_basepath->string, temp ); + + return ospath[toggle]; +} + +char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) { + char temp[MAX_OSPATH]; + static char ospath[4][MAX_OSPATH]; + static int toggle; + + //pre-fs_cf2 + //toggle ^= 1; // flip-flop to allow two returns without clash + //post-fs_cf2 + toggle = (++toggle)&3; // allows four returns without clash (increased from 2 during fs_copyfiles 2 enhancement) + + if( !game || !game[0] ) { + game = fs_gamedir; + } + + Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath ); + FS_ReplaceSeparators( temp ); + Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp ); + + return ospath[toggle]; +} + +/* +=========== +FS_FilenameCompare + +Ignore case and seprator char distinctions +=========== +*/ +qboolean FS_FilenameCompare( const char *s1, const char *s2 ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if (c1 >= 'a' && c1 <= 'z') { + c1 -= ('a' - 'A'); + } + if (c2 >= 'a' && c2 <= 'z') { + c2 -= ('a' - 'A'); + } + + if ( c1 == '\\' || c1 == ':' ) { + c1 = '/'; + } + if ( c2 == '\\' || c2 == ':' ) { + c2 = '/'; + } + + if (c1 != c2) { + return (qboolean)-1; // strings not equal + } + } while (c1); + + return (qboolean)0; // strings are equal +} + +#define MAXPRINTMSG 4096 +void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + FS_Write(msg, strlen(msg), h); +} + +/* +====================================================================================== + +CONVENIENCE FUNCTIONS FOR ENTIRE FILES + +====================================================================================== +*/ + +/* +============ +FS_WriteFile + +Filename are reletive to the quake search path +============ +*/ +void FS_WriteFile( const char *qpath, const void *buffer, int size ) { + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !qpath || !buffer ) { + Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" ); + } + + f = FS_FOpenFileWrite( qpath ); + if ( !f ) { + Com_Printf( "Failed to open %s\n", qpath ); + return; + } + + FS_Write( buffer, size, f ); + + FS_FCloseFile( f ); +} + +/* +================ +FS_Shutdown + +Frees all resources and closes all files +================ +*/ +void FS_Shutdown( qboolean closemfp ) { + searchpath_t *p, *next; + int i; + + for(i = 0; i < MAX_FILE_HANDLES; i++) { + if (fsh[i].fileSize) { + FS_FCloseFile(i); + } + } + + // free everything + for ( p = fs_searchpaths ; p ; p = next ) { + next = p->next; + + if ( p->pack ) { +#ifndef _XBOX + unzClose(p->pack->handle); +#endif + Z_Free( p->pack->buildBuffer ); + Z_Free( p->pack ); + } + if ( p->dir ) { + Z_Free( p->dir ); + } + Z_Free( p ); + } + + // any FS_ calls will now be an error until reinitialized + fs_searchpaths = NULL; + + Cmd_RemoveCommand( "path" ); + Cmd_RemoveCommand( "dir" ); + Cmd_RemoveCommand( "fdir" ); + Cmd_RemoveCommand( "touchFile" ); + +#ifdef FS_MISSING + if (closemfp) { + fclose(missingFiles); + } +#endif +} + +/* +================ +FS_InitFilesystem + +Called only at inital startup, not when the filesystem +is resetting due to a game change +================ +*/ +void FS_InitFilesystem( void ) { + // allow command line parms to override our defaults + // we have to specially handle this, because normal command + // line variable sets don't happen until after the filesystem + // has already been initialized + Com_StartupVariable( "fs_cdpath" ); + Com_StartupVariable( "fs_basepath" ); + Com_StartupVariable( "fs_homepath" ); + Com_StartupVariable( "fs_game" ); + Com_StartupVariable( "fs_copyfiles" ); + Com_StartupVariable( "fs_restrict" ); + + // try to start up normally + FS_Startup( BASEGAME ); + initialized = qtrue; + + // see if we are going to allow add-ons + FS_SetRestrictions(); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if ( FS_ReadFile( "mpdefault.cfg", NULL ) <= 0 ) { + Com_Error( ERR_FATAL, "Couldn't load mpdefault.cfg" ); + // bk001208 - SafeMode see below, FIXME? + } + + Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase)); + Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); + + // bk001208 - SafeMode see below, FIXME? +} + diff --git a/codemp/qcommon/files_console.cpp b/codemp/qcommon/files_console.cpp new file mode 100644 index 0000000..4bb4297 --- /dev/null +++ b/codemp/qcommon/files_console.cpp @@ -0,0 +1,1068 @@ + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "files.h" +#include "../win32/win_file.h" +#include "../zlib/zlib.h" + + +//#define GOB_PROFILE +//#define CUSTOM_MP_GOBS + +static cvar_t *fs_openorder; + + +// Zlib Tech Ref says decompression should use about 44kb. I'll +// go with 64kb as a safety factor... +#define ZI_STACKSIZE (64*1024) + +static char* zi_stackTop = NULL; +static char* zi_stackBase = NULL; + + + +//GOB stuff +//=========================================================================== + +struct gi_handleTable +{ + wfhandle_t file; + bool used; +}; + +static gi_handleTable *gi_handles = NULL; +static int gi_cacheHandle = 0; + +static GOBFSHandle gi_open(GOBChar* name, GOBAccessType type) +{ + if (type != GOBACCESS_READ) return (GOBFSHandle)0xFFFFFFFF; + + int f; + for (f = 0; f < MAX_FILE_HANDLES; ++f) + { + if (!gi_handles[f].used) break; + } + + if (f == MAX_FILE_HANDLES) return (GOBFSHandle)0xFFFFFFFF; + +#ifdef CUSTOM_MP_GOBS + gi_handles[f].file = WF_Open(name, true, strstr(name, "assets_mp.gob") ? true : false); +#else + gi_handles[f].file = WF_Open(name, true, strstr(name, "assets.gob") ? true : false); +#endif + if (gi_handles[f].file < 0) return (GOBFSHandle)0xFFFFFFFF; + gi_handles[f].used = true; + + return (GOBFSHandle)f; +} + +static GOBBool gi_close(GOBFSHandle* handle) +{ + WF_Close(gi_handles[(int)*handle].file); + gi_handles[(int)*handle].used = false; + return GOB_TRUE; +} + +static GOBInt32 gi_read(GOBFSHandle handle, GOBVoid* buffer, GOBInt32 size) +{ + return WF_Read(buffer, size, gi_handles[(int)handle].file); +} + +static GOBInt32 gi_seek(GOBFSHandle handle, GOBInt32 offset, GOBSeekType type) +{ + int _type; + switch (type) { + case GOBSEEK_START: _type = SEEK_SET; break; + case GOBSEEK_CURRENT: _type = SEEK_CUR; break; + case GOBSEEK_END: _type = SEEK_END; break; + default: assert(0); _type = SEEK_SET; break; + } + + return WF_Seek(offset, _type, gi_handles[(int)handle].file); +} + +static GOBVoid* gi_alloc(GOBUInt32 size) +{ + return Z_Malloc(size, TAG_FILESYS, qfalse, 32); +} + +static GOBVoid gi_free(GOBVoid* ptr) +{ + Z_Free(ptr); +} + +static GOBBool cache_open(GOBUInt32 size) +{ + for (gi_cacheHandle = 0; gi_cacheHandle < MAX_FILE_HANDLES; ++gi_cacheHandle) + { + if (!gi_handles[gi_cacheHandle].used) break; + } + + if (gi_cacheHandle == MAX_FILE_HANDLES) return GOB_FALSE; + + gi_handles[gi_cacheHandle].file = WF_Open("z:\\jedi.swap", false, true); + if (gi_handles[gi_cacheHandle].file < 0) return GOB_FALSE; + + if (!WF_Resize(size, gi_handles[gi_cacheHandle].file)) + { + WF_Close(gi_handles[gi_cacheHandle].file); + return GOB_FALSE; + } + + gi_handles[gi_cacheHandle].used = true; + + return GOB_TRUE; +} + +static GOBBool cache_close(GOBVoid) +{ + WF_Close(gi_handles[gi_cacheHandle].file); + gi_handles[gi_cacheHandle].used = false; + return GOB_TRUE; +} + +static GOBInt32 cache_read(GOBVoid* buffer, GOBInt32 size) +{ + return WF_Read(buffer, size, gi_handles[gi_cacheHandle].file); +} + +static GOBInt32 cache_write(GOBVoid* buffer, GOBInt32 size) +{ + return WF_Write(buffer, size, gi_handles[gi_cacheHandle].file); +} + +static GOBInt32 cache_seek(GOBInt32 offset) +{ + return WF_Seek(offset, SEEK_SET, gi_handles[gi_cacheHandle].file); +} + +static voidpf zi_alloc(voidpf opaque, uInt items, uInt size) +{ + voidpf ret = zi_stackTop; + + zi_stackTop += items * size; + assert(zi_stackTop < zi_stackBase + ZI_STACKSIZE); + + return ret; +} + +static void zi_free(voidpf opaque, voidpf address) +{ +} + +static GOBInt32 gi_decompress_zlib(GOBVoid* source, GOBUInt32 sourceLen, + GOBVoid* dest, GOBUInt32* destLen) +{ + // Copied and modified version of zlib's uncompress()... + + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + + stream.next_out = (Bytef*)dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = zi_alloc; + stream.zfree = zi_free; + zi_stackTop = zi_stackBase; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} + +GOBInt32 gi_decompress_null(GOBVoid* source, GOBUInt32 sourceLen, + GOBVoid* dest, GOBUInt32* destLen) +{ + if (sourceLen > *destLen) return -1; + *destLen = sourceLen; + + memcpy(dest, source, sourceLen); + return 0; +} + +#ifdef GOB_PROFILE +static GOBVoid gi_profileread(GOBUInt32 code) +{ + code = LittleLong(code); + Sys_Log("gob-prof-mp.dat", &code, sizeof(code), true); +} +#endif + +//=========================================================================== + + + + +static void FS_CheckUsed(fileHandle_t f) +{ + if (!fsh[f].used) + { + Com_Error( ERR_FATAL, "Filesystem call attempting to use invalid handle\n" ); + } +} + + +int FS_filelength( fileHandle_t f ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if (fsh[f].gob) + { + GOBUInt32 cur, end, crap; + GOBSeek(fsh[f].ghandle, 0, GOBSEEK_CURRENT, &cur); + GOBSeek(fsh[f].ghandle, 0, GOBSEEK_END, &end); + GOBSeek(fsh[f].ghandle, cur, GOBSEEK_START, &crap); + + return end; + } + else + { + int pos = WF_Tell(fsh[f].whandle); + WF_Seek(0, SEEK_END, fsh[f].whandle); + int end = WF_Tell(fsh[f].whandle); + WF_Seek(pos, SEEK_SET, fsh[f].whandle); + + return end; + } +} + + +void FS_FCloseFile( fileHandle_t f ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if (fsh[f].gob) + GOBClose(fsh[f].ghandle); + else + WF_Close(fsh[f].whandle); + + fsh[f].used = qfalse; +} + + +fileHandle_t FS_FOpenFileWrite( const char *filename ) +{ + FS_CheckInit(); + + fileHandle_t f = FS_HandleForFile(); + + char* osname = FS_BuildOSPath( filename ); + fsh[f].whandle = WF_Open(osname, false, false); + if (fsh[f].whandle >= 0) + { + fsh[f].used = qtrue; + fsh[f].gob = qfalse; + return f; + } + + return 0; +} + + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ + +static int FS_FOpenFileReadOS( const char *filename, fileHandle_t f ) +{ + if (Sys_GetFileCode(filename) != -1) + { + char* osname = FS_BuildOSPath( filename ); + fsh[f].whandle = WF_Open(osname, true, false); + if (fsh[f].whandle >= 0) + { + fsh[f].used = qtrue; + fsh[f].gob = qfalse; + return FS_filelength(f); + } + } + return -1; +} + + +/* +=================== +FS_BuildGOBPath + +Qpath may have either forward or backwards slashes +=================== +*/ +static char *FS_BuildGOBPath(const char *qpath ) +{ + static char path[2][MAX_OSPATH]; + static int toggle; + + toggle ^= 1; // flip-flop to allow two returns without clash + + if (qpath[0] == '\\' || qpath[0] == '/') + { + Com_sprintf( path[toggle], sizeof( path[0] ), ".%s", qpath ); + } + else + { + Com_sprintf( path[toggle], sizeof( path[0] ), ".\\%s", qpath ); + } + +// FS_ReplaceSeparators( path[toggle], '\\' ); + FS_ReplaceSeparators( path[toggle] ); + + return path[toggle]; +} + + +static int FS_FOpenFileReadGOB( const char *filename, fileHandle_t f ) +{ + char* gobname = FS_BuildGOBPath( filename ); + if (GOBOpen(gobname, &fsh[f].ghandle) == GOBERR_OK) + { + fsh[f].used = qtrue; + fsh[f].gob = qtrue; + return FS_filelength(f); + } + return -1; +} + + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ +int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) +{ + FS_CheckInit(); + + if ( file == NULL ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'file' parameter passed\n" ); + } + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + *file = FS_HandleForFile(); + + int len; + + if (fs_openorder->integer == 0) + { + // Release mode -- read from GOB first + len = FS_FOpenFileReadGOB(filename, *file); + if (len < 0) len = FS_FOpenFileReadOS(filename, *file); + } + else + { + // Debug mode -- external files override GOB + len = FS_FOpenFileReadOS(filename, *file); + if (len < 0) len = FS_FOpenFileReadGOB(filename, *file); + } + + if (len >= 0) return len; + + Com_DPrintf ("Can't find %s\n", filename); + + *file = 0; + return -1; +} + + +/* +================= +FS_Read + +Properly handles partial reads +================= +*/ +int FS_Read( void *buffer, int len, fileHandle_t f ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if ( !f ) + { + return 0; + } + + if ( f <= 0 || f >= MAX_FILE_HANDLES ) + { + Com_Error( ERR_FATAL, "FS_Read: Invalid handle %d\n", f ); + } + + if (fsh[f].gob) + { + GOBUInt32 size = GOBRead(buffer, len, fsh[f].ghandle); + if (size == GOB_INVALID_SIZE) + { +#if defined(FINAL_BUILD) + extern void ERR_DiscFail(bool); + ERR_DiscFail(false); +#else + Com_Error( ERR_FATAL, "Failed to read from GOB" ); +#endif + } + return size; + } + else + { + return WF_Read(buffer, len, fsh[f].whandle); + } +} + +/* + MP has FS_Read2 which is supposed to do some extra logic. + We don't care, and just call FS_Read() +*/ +int FS_Read2( void *buffer, int len, fileHandle_t f ) +{ + return FS_Read(buffer, len, f); +} + +/* +================= +FS_Write +================= +*/ +int FS_Write( const void *buffer, int len, fileHandle_t f ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if ( !f ) + { + return 0; + } + + if ( f <= 0 || f >= MAX_FILE_HANDLES ) + { + Com_Error( ERR_FATAL, "FS_Read: Invalid handle %d\n", f ); + } + + if (fsh[f].gob) + { + Com_Error( ERR_FATAL, "FS_Write: Cannot write to GOB files %d\n", f ); + } + else + { + return WF_Write(buffer, len, fsh[f].whandle); + } + + return 0; +} + + +/* +================= +FS_Seek + +================= +*/ +int FS_Seek( fileHandle_t f, long offset, int origin ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if (fsh[f].gob) + { + int _origin; + switch( origin ) { + case FS_SEEK_CUR: _origin = GOBSEEK_CURRENT; break; + case FS_SEEK_END: _origin = GOBSEEK_END; break; + case FS_SEEK_SET: _origin = GOBSEEK_START; break; + default: + _origin = GOBSEEK_CURRENT; + Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); + break; + } + + GOBUInt32 pos; + GOBSeek(fsh[f].ghandle, offset, _origin, &pos); + return pos; + } + else + { + int _origin; + switch( origin ) { + case FS_SEEK_CUR: _origin = SEEK_CUR; break; + case FS_SEEK_END: _origin = SEEK_END; break; + case FS_SEEK_SET: _origin = SEEK_SET; break; + default: + _origin = SEEK_CUR; + Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); + break; + } + + return WF_Seek(offset, _origin, fsh[f].whandle); + } +} + + +/* +================= +FS_Access +================= +*/ +qboolean FS_Access( const char *filename ) +{ + GOBBool status; + + FS_CheckInit(); + + char* gobname = FS_BuildGOBPath( filename ); + if (GOBAccess(gobname, &status) != GOBERR_OK || status != GOB_TRUE) + { + return Sys_GetFileCode( filename ) != -1; + } + + return qtrue; +} + + +/* +====================================================================================== + +CONVENIENCE FUNCTIONS FOR ENTIRE FILES + +====================================================================================== +*/ + +#ifdef _JK2MP +int FS_FileIsInPAK(const char *filename, int *pChecksum) +#else +int FS_FileIsInPAK(const char *filename) +#endif +{ + FS_CheckInit(); + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + GOBBool exists; + GOBAccess(const_cast(filename), &exists); + +#ifdef _JK2MP + *pChecksum = 0; +#endif + + return exists ? 1 : -1; +} + +static bool sbLargeRead = false; + +// Warn the filesystem that a large read is coming (GLM), so it can use +// TempAlloc to get space! +void FS_LargeRead( void ) +{ + sbLargeRead = true; +} +void FS_CancelLargeRead( void ) +{ + sbLargeRead = false; +} + +extern void *BonePoolTempAlloc( unsigned long size ); +extern void BonePoolTempFree( void *p ); + +/* +============ +FS_ReadFile + +Filename are relative to the quake search path +a null buffer will just return the file length without loading +============ +*/ +int FS_ReadFile( const char *qpath, void **buffer ) +{ + FS_CheckInit(); + + if ( !qpath || !qpath[0] ) { + Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" ); + } + + // stop sounds from repeating + S_ClearSoundBuffer(); + + fileHandle_t h; + int len = FS_FOpenFileRead( qpath, &h, qfalse ); + if ( h == 0 ) + { + if ( buffer ) *buffer = NULL; + return -1; + } + + if ( !buffer ) + { + FS_FCloseFile(h); + return len; + } + + byte *buf; + // Try to TempAlloc if we've got the hint that this could fail: + if( sbLargeRead ) + buf = (byte *)BonePoolTempAlloc( len+1 ); + + // If that didn't work, or wasn't suggested: + if( !sbLargeRead || !buf ) + buf = (byte*)Z_Malloc( len+1, TAG_TEMP_WORKSPACE, qfalse, 32); + + buf[len]='\0'; + +// Z_Label(buf, qpath); + + FS_Read(buf, len, h); + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + FS_FCloseFile( h ); + + *buffer = buf; + return len; +} + + +/* +============= +FS_FreeFile +============= +*/ +void FS_FreeFile( void *buffer ) +{ + FS_CheckInit(); + + if ( !buffer ) { + Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" ); + } + + // If this was read in with sbLargeRead true, then it might not be from + // the zone! + extern bool IsBonePoolPointer( void *p ); + if( IsBonePoolPointer( buffer ) ) + BonePoolTempFree( buffer ); + else + Z_Free( buffer ); +} + + +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) +{ + FS_CheckInit(); + + if (mode != FS_READ) + { + Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" ); + return -1; + } + + return FS_FOpenFileRead( qpath, f, qtrue ); +} + + +int FS_FTell( fileHandle_t f ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if (fsh[f].gob) + { + GOBUInt32 pos; + GOBSeek(fsh[f].ghandle, 0, GOBSEEK_CURRENT, &pos); + return pos; + } + else + { + return WF_Tell(fsh[f].whandle); + } +} + +/* +================ +FS_Startup +================ +*/ +void FS_Startup( const char *gameName ) +{ + Com_Printf( "----- FS_Startup -----\n" ); + + fs_openorder = Cvar_Get( "fs_openorder", "0", 0 ); + fs_debug = Cvar_Get( "fs_debug", "0", 0 ); + fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT ); + fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT ); + fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultBasePath(), CVAR_INIT ); + fs_gamedirvar = Cvar_Get ("fs_game", "base", CVAR_INIT|CVAR_SERVERINFO ); + fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT ); + + gi_handles = new gi_handleTable[MAX_FILE_HANDLES]; + for (int f = 0; f < MAX_FILE_HANDLES; ++f) + { + fsh[f].used = false; + gi_handles[f].used = false; + } + + zi_stackBase = (char*)Z_Malloc(ZI_STACKSIZE, TAG_FILESYS, qfalse); + + GOBMemoryFuncSet mem; + mem.alloc = gi_alloc; + mem.free = gi_free; + + GOBFileSysFuncSet file; + file.close = gi_close; + file.open = gi_open; + file.read = gi_read; + file.seek = gi_seek; + file.write = NULL; + + GOBCacheFileFuncSet cache; + cache.close = cache_close; + cache.open = cache_open; + cache.read = cache_read; + cache.seek = cache_seek; + cache.write = cache_write; + + GOBCodecFuncSet codec = { + 2, // codecs + { + { // Codec 0 - zlib + 'z', GOB_INFINITE_RATIO, // tag, ratio (ratio is meaningless for decomp) + NULL, + gi_decompress_zlib, + }, + { // Codec 1 - null + '0', GOB_INFINITE_RATIO, // tag, ratio (ratio is meaningless for decomp) + NULL, + gi_decompress_null, + }, + } + }; + + if ( +#ifdef _XBOX + GOBInit(&mem, &file, &codec, &cache) +#else + GOBInit(&mem, &file, &codec, NULL) +#endif + != GOBERR_OK) + { + Com_Error( ERR_FATAL, "Could not initialize GOB" ); + } + +#ifdef CUSTOM_MP_GOBS + char* archive = FS_BuildOSPath( "assets_mp" ); +#else + char* archive = FS_BuildOSPath( "assets" ); +#endif + if (GOBArchiveOpen(archive, GOBACCESS_READ, GOB_FALSE, GOB_TRUE) != GOBERR_OK) + { +#if defined(FINAL_BUILD) + extern void ERR_DiscFail(bool); + ERR_DiscFail(false); +#else + //Com_Error( ERR_FATAL, "Could not initialize GOB" ); + Cvar_Set("fs_openorder", "1"); +#endif + } + + GOBSetCacheSize(1); + GOBSetReadBufferSize(32 * 1024); + +#ifdef GOB_PROFILE + GOBProfileFuncSet profile = { + gi_profileread + }; + GOBSetProfileFuncs(&profile); + GOBStartProfile(); +#endif + + Com_Printf( "----------------------\n" ); +} + +/* +============================ + +DIRECTORY SCANNING FUCNTIONS + +============================ +*/ + +#define MAX_FOUND_FILES 0x1000 + +/* +================== +FS_AddFileToList +================== +*/ +static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) { + int i; + + if ( nfiles == MAX_FOUND_FILES - 1 ) { + return nfiles; + } + for ( i = 0 ; i < nfiles ; i++ ) { + if ( !stricmp( name, list[i] ) ) { + return nfiles; // allready in list + } + } +// list[nfiles] = CopyString( name ); + list[nfiles] = (char *) Z_Malloc( strlen(name) + 1, TAG_LISTFILES, qfalse ); + strcpy(list[nfiles], name); + nfiles++; + + return nfiles; +} + +/* +=============== +FS_ListFiles + +Returns a uniqued list of files that match the given criteria +from all search paths +=============== +*/ +char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) +{ + char *netpath; + int numSysFiles; + char **sysFiles; + char *name; + int nfiles = 0; + char **listCopy; + char *list[MAX_FOUND_FILES]; + int i; + + FS_CheckInit(); + + if ( !path ) { + *numfiles = 0; + return NULL; + } + + // We don't do any fancy searchpath magic here, it's all in the meta-file + // that Sys_ListFiles will return + netpath = FS_BuildOSPath( path ); +#ifdef _JK2MP + sysFiles = Sys_ListFiles( netpath, extension, NULL, &numSysFiles, qfalse ); +#else + sysFiles = Sys_ListFiles( netpath, extension, &numSysFiles, qfalse ); +#endif + for ( i = 0 ; i < numSysFiles ; i++ ) { + // unique the match + name = sysFiles[i]; + nfiles = FS_AddFileToList( name, list, nfiles ); + } + Sys_FreeFileList( sysFiles ); + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ), TAG_LISTFILES, qfalse); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + + +/* +================= +FS_FreeFileList +================= +*/ +void FS_FreeFileList( char **filelist ) +{ + int i; + + FS_CheckInit(); + + if ( !filelist ) { + return; + } + + for ( i = 0 ; filelist[i] ; i++ ) { + Z_Free( filelist[i] ); + } + + Z_Free( filelist ); +} + +/* +=============== +FS_AddFileToListBuf +=============== +*/ +static int FS_AddFileToListBuf( char *name, char *listbuf, int bufsize, int nfiles ) +{ + char *p; + + if ( nfiles == MAX_FOUND_FILES - 1 ) { + return nfiles; + } + + if (name[0] == '/' || name[0] == '\\') { + name++; + } + + p = listbuf; + while ( *p ) { + if ( !stricmp( name, p ) ) { + return nfiles; // already in list + } + p += strlen( p ) + 1; + } + + if ( ( p + strlen( name ) + 2 - listbuf ) > bufsize ) { + return nfiles; // list is full + } + + strcpy( p, name ); + p += strlen( p ) + 1; + *p = 0; + + return nfiles + 1; +} + +/* +================ +FS_GetFileList + +Returns a uniqued list of files that match the given criteria +from all search paths +================ +*/ +int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) +{ + int nfiles = 0; + int i; + char *netpath; + int numSysFiles; + char **sysFiles; + char *name; + + FS_CheckInit(); + + if ( !path ) { + return 0; + } + if ( !extension ) { + extension = ""; + } + + // Prime the file list buffer + listbuf[0] = '\0'; + netpath = FS_BuildOSPath( path ); +#ifdef _JK2MP + sysFiles = Sys_ListFiles( netpath, extension, NULL, &numSysFiles, qfalse ); +#else + sysFiles = Sys_ListFiles( netpath, extension, &numSysFiles, qfalse ); +#endif + for ( i = 0 ; i < numSysFiles ; i++ ) { + // unique the match + name = sysFiles[i]; + nfiles = FS_AddFileToListBuf( name, listbuf, bufsize, nfiles ); + } + Sys_FreeFileList( sysFiles ); + + return nfiles; +} + +/* +================= + Filesytem STUBS +================= +*/ + +qboolean FS_ConditionalRestart(int checksumFeed) +{ + return qfalse; +} + +void FS_ClearPakReferences(int flags) +{ + return; +} + +const char *FS_LoadedPakNames(void) +{ + return ""; +} + +const char *FS_ReferencedPakNames(void) +{ + return ""; +} + +void FS_SetRestrictions(void) +{ + return; +} + +#ifdef _JK2MP +void FS_Restart(int checksumFeed) +#else +void FS_Restart(void) +#endif +{ + return; +} + +qboolean FS_FileExists(const char *file) +{ + assert(!"FS_FileExists not implemented on Xbox"); + return qfalse; +} + +void FS_UpdateGamedir(void) +{ + return; +} + +void FS_PureServerSetReferencedPaks(const char *pakSums, const char *pakNames) +{ + return; +} + +void FS_PureServerSetLoadedPaks(const char *pakSums, const char *pakNames) +{ + return; +} + +const char *FS_ReferencedPakChecksums(void) +{ + return ""; +} + +const char *FS_LoadedPakChecksums(void) +{ + return ""; +} diff --git a/codemp/qcommon/files_pc.cpp b/codemp/qcommon/files_pc.cpp new file mode 100644 index 0000000..36f496a --- /dev/null +++ b/codemp/qcommon/files_pc.cpp @@ -0,0 +1,3125 @@ +/***************************************************************************** + * name: files_pc.cpp + * + * desc: PC-specific file code + * + *****************************************************************************/ + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "../client/client.h" +//#include "../zlib32/zip.h" +//#include "unzip.h" +#include "files.h" + +//#include //rww - included to make fs_copyfiles 2 related functions happy. +#include "platform.h" + +// TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 +// wether we did a reorder on the current search path when joining the server +static qboolean fs_reordered; + +// productId: This file is copyright 2003 Raven Software, and may not be duplicated except during a licensed installation of the full commercial version of Star Wars: Jedi Academy +static const byte fs_scrambledProductId[] = { +42, 143, 149, 190, 10, 197, 225, 133, 243, 63, 189, 182, 226, 56, 143, 17, 215, 37, 197, 218, 50, 103, 24, 235, 246, 191, 183, 149, 160, 170, +230, 52, 176, 231, 15, 194, 236, 247, 159, 168, 132, 154, 24, 133, 67, 85, 36, 97, 99, 86, 117, 189, 212, 156, 236, 153, 68, 10, 196, 241, +39, 219, 156, 88, 93, 198, 200, 232, 142, 67, 45, 209, 53, 186, 228, 241, 162, 127, 213, 83, 7, 121, 11, 93, 123, 243, 148, 240, 229, 42, +42, 6, 215, 239, 112, 120, 240, 244, 104, 12, 38, 47, 201, 253, 223, 208, 154, 69, 141, 157, 32, 117, 166, 146, 236, 59, 15, 223, 52, 89, +133, 64, 201, 56, 119, 25, 211, 152, 159, 11, 92, 59, 207, 81, 123, 0, 121, 241, 116, 42, 36, 251, 51, 149, 79, 165, 12, 106, 187, 225, +203, 99, 102, 69, 97, 81, 27, 107, 81, 178, 63, 35, 185, 64, 115 +}; + + +/* +================= +FS_PakIsPure +================= +*/ +qboolean FS_PakIsPure( pack_t *pack ) { + int i; + + if ( fs_numServerPaks ) { + // NOTE TTimo we are matching checksums without checking the pak names + // this means you can have the same pk3 as the server under a different name, you will still get through sv_pure validation + // (what happens when two pk3's have the same checkums? is it a likely situation?) + // also, if there's a wrong checksumed pk3 and autodownload is enabled, the checksum will be appended to the downloaded pk3 name + for ( i = 0 ; i < fs_numServerPaks ; i++ ) { + // FIXME: also use hashed file names + if ( pack->checksum == fs_serverPaks[i] ) { + return qtrue; // on the aproved list + } + } + return qfalse; // not on the pure server pak list + } + return qtrue; +} + + +/* +================ +return a hash value for the filename +================ +*/ +static long FS_HashFileName( const char *fname, int hashSize ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + if (letter == PATH_SEP) letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + hash &= (hashSize-1); + return hash; +} + + +static FILE *FS_FileForHandle( fileHandle_t f ) { + if ( f < 0 || f > MAX_FILE_HANDLES ) { + Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" ); + } + if (fsh[f].zipFile == qtrue) { + Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" ); + } + if ( ! fsh[f].handleFiles.file.o ) { + Com_Error( ERR_DROP, "FS_FileForHandle: NULL" ); + } + + return fsh[f].handleFiles.file.o; +} + + +void FS_ForceFlush( fileHandle_t f ) { + FILE *file; + + file = FS_FileForHandle(f); + setvbuf( file, NULL, _IONBF, 0 ); +} + + +/* +================ +FS_filelength + +If this is called on a non-unique FILE (from a pak file), +it will return the size of the pak file, not the expected +size of the file. +================ +*/ +int FS_filelength( fileHandle_t f ) { + int pos; + int end; + FILE* h; + + h = FS_FileForHandle(f); + pos = ftell (h); + fseek (h, 0, SEEK_END); + end = ftell (h); + fseek (h, pos, SEEK_SET); + + return end; +} + +/* +============ +FS_CreatePath + +Creates any directories needed to store the given filename +============ +*/ +qboolean FS_CreatePath (char *OSPath) { + char *ofs; + + // make absolutely sure that it can't back up the path + // FIXME: is c: allowed??? + if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) { + Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath ); + return qtrue; + } + + for (ofs = OSPath+1 ; *ofs ; ofs++) { + if (*ofs == PATH_SEP) { + // create the directory + *ofs = 0; + Sys_Mkdir (OSPath); + *ofs = PATH_SEP; + } + } + return qfalse; +} + +/* +================= +FS_CopyFile + +Copy a fully specified file from one place to another +================= +*/ +void FS_CopyFile( char *fromOSPath, char *toOSPath ) { + FILE *f; + int len; + byte *buf; + + Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath ); + + if (strstr(fromOSPath, "journal.dat") || strstr(fromOSPath, "journaldata.dat")) { + Com_Printf( "Ignoring journal files\n"); + return; + } + + f = fopen( fromOSPath, "rb" ); + if ( !f ) { + return; + } + fseek (f, 0, SEEK_END); + len = ftell (f); + fseek (f, 0, SEEK_SET); + + // we are using direct malloc instead of Z_Malloc here, so it + // probably won't work on a mac... Its only for developers anyway... + buf = (unsigned char *)malloc( len ); + if (fread( buf, 1, len, f ) != len) + Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" ); + fclose( f ); + + if( FS_CreatePath( toOSPath ) ) { + return; + } + + f = fopen( toOSPath, "wb" ); + if ( !f ) { + return; + } + if (fwrite( buf, 1, len, f ) != len) + Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" ); + fclose( f ); + free( buf ); +} + +/* +=========== +FS_Remove + +=========== +*/ +void FS_Remove( const char *osPath ) { + remove( osPath ); +} + +/* +================ +FS_FileExists + +Tests if the file exists in the current gamedir, this DOES NOT +search the paths. This is to determine if opening a file to write +(which always goes into the current gamedir) will cause any overwrites. +NOTE TTimo: this goes with FS_FOpenFileWrite for opening the file afterwards +================ +*/ +qboolean FS_FileExists( const char *file ) +{ + FILE *f; + char *testpath; + + testpath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, file ); + + f = fopen( testpath, "rb" ); + if (f) { + fclose( f ); + return qtrue; + } + return qfalse; +} + +/* +================ +FS_SV_FileExists + +Tests if the file exists +================ +*/ +qboolean FS_SV_FileExists( const char *file ) +{ + FILE *f; + char *testpath; + + testpath = FS_BuildOSPath( fs_homepath->string, file, ""); + testpath[strlen(testpath)-1] = '\0'; + + f = fopen( testpath, "rb" ); + if (f) { + fclose( f ); + return qtrue; + } + return qfalse; +} + + +/* +=========== +FS_SV_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_SV_FOpenFileWrite( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + ospath = FS_BuildOSPath( fs_homepath->string, filename, "" ); + ospath[strlen(ospath)-1] = '\0'; + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_FOpenFileWrite: %s\n", ospath ); + } + + if( FS_CreatePath( ospath ) ) { + return 0; + } + + Com_DPrintf( "writing to: %s\n", ospath ); + fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) { + f = 0; + } + return f; +} + +/* +=========== +FS_SV_FOpenFileRead +search for a file somewhere below the home path, base path or cd path +we search in that order, matching FS_SV_FOpenFileRead order +=========== +*/ +int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) { + char *ospath; + fileHandle_t f = 0; // bk001129 - from cvs1.17 + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + // don't let sound stutter + S_ClearSoundBuffer(); + + // search homepath + ospath = FS_BuildOSPath( fs_homepath->string, filename, "" ); + // remove trailing slash + ospath[strlen(ospath)-1] = '\0'; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_FOpenFileRead (fs_homepath): %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) + { + // NOTE TTimo on non *nix systems, fs_homepath == fs_basepath, might want to avoid + if (Q_stricmp(fs_homepath->string,fs_basepath->string)) + { + // search basepath + ospath = FS_BuildOSPath( fs_basepath->string, filename, "" ); + ospath[strlen(ospath)-1] = '\0'; + + if ( fs_debug->integer ) + { + Com_Printf( "FS_SV_FOpenFileRead (fs_basepath): %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + + if ( !fsh[f].handleFiles.file.o ) + { + f = 0; + } + } + } + + if (!fsh[f].handleFiles.file.o) { + // search cd path + ospath = FS_BuildOSPath( fs_cdpath->string, filename, "" ); + ospath[strlen(ospath)-1] = '\0'; + + if (fs_debug->integer) + { + Com_Printf( "FS_SV_FOpenFileRead (fs_cdpath) : %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + + if( !fsh[f].handleFiles.file.o ) { + f = 0; + } + } + + *fp = f; + if (f) { + return FS_filelength(f); + } + return 0; +} + +/* +=========== +FS_SV_Rename + +=========== +*/ +void FS_SV_Rename( const char *from, const char *to ) { + char *from_ospath, *to_ospath; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + // don't let sound stutter + S_ClearSoundBuffer(); + + from_ospath = FS_BuildOSPath( fs_homepath->string, from, "" ); + to_ospath = FS_BuildOSPath( fs_homepath->string, to, "" ); + from_ospath[strlen(from_ospath)-1] = '\0'; + to_ospath[strlen(to_ospath)-1] = '\0'; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_Rename: %s --> %s\n", from_ospath, to_ospath ); + } + + if (rename( from_ospath, to_ospath )) { + // Failed, try copying it and deleting the original + FS_CopyFile ( from_ospath, to_ospath ); + FS_Remove ( from_ospath ); + } +} + +/* +=========== +FS_Rename + +=========== +*/ +void FS_Rename( const char *from, const char *to ) { + char *from_ospath, *to_ospath; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + // don't let sound stutter + S_ClearSoundBuffer(); + + from_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, from ); + to_ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, to ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_Rename: %s --> %s\n", from_ospath, to_ospath ); + } + + if (rename( from_ospath, to_ospath )) { + // Failed, try copying it and deleting the original + FS_CopyFile ( from_ospath, to_ospath ); + FS_Remove ( from_ospath ); + } +} + +/* +============== +FS_FCloseFile + +If the FILE pointer is an open pak file, leave it open. + +For some reason, other dll's can't just cal fclose() +on files returned by FS_FOpenFile... +============== +*/ +void FS_FCloseFile( fileHandle_t f ) { + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if (fsh[f].streamed) { + Sys_EndStreamedFile(f); + } + if (fsh[f].zipFile == qtrue) { + unzCloseCurrentFile( fsh[f].handleFiles.file.z ); + if ( fsh[f].handleFiles.unique ) { + unzClose( fsh[f].handleFiles.file.z ); + } + Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) ); + return; + } + + // we didn't find it as a pak, so close it as a unique file + if (fsh[f].handleFiles.file.o) { + fclose (fsh[f].handleFiles.file.o); + } + Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) ); +} + +/* +=========== +FS_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_FOpenFileWrite( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileWrite: %s\n", ospath ); + } + + if( FS_CreatePath( ospath ) ) { + return 0; + } + + // enabling the following line causes a recursive function call loop + // when running with +set logfile 1 +set developer 1 + //Com_DPrintf( "writing to: %s\n", ospath ); + fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) { + f = 0; + } + return f; +} + +/* +=========== +FS_FOpenFileAppend + +=========== +*/ +fileHandle_t FS_FOpenFileAppend( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + // don't let sound stutter + S_ClearSoundBuffer(); + + ospath = FS_BuildOSPath( fs_homepath->string, fs_gamedir, filename ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileAppend: %s\n", ospath ); + } + + if( FS_CreatePath( ospath ) ) { + return 0; + } + + fsh[f].handleFiles.file.o = fopen( ospath, "ab" ); + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) { + f = 0; + } + return f; +} + +bool Sys_GetFileTime(LPCSTR psFileName, FILETIME &ft) +{ + bool bSuccess = false; + HANDLE hFile = INVALID_HANDLE_VALUE; + + hFile = CreateFile( psFileName, // LPCTSTR lpFileName, // pointer to name of the file + GENERIC_READ, // DWORD dwDesiredAccess, // access (read-write) mode + FILE_SHARE_READ, // DWORD dwShareMode, // share mode + NULL, // LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes + OPEN_EXISTING, // DWORD dwCreationDisposition, // how to create + FILE_FLAG_NO_BUFFERING,// DWORD dwFlagsAndAttributes, // file attributes + NULL // HANDLE hTemplateFile // handle to file with attributes to + ); + + if (hFile != INVALID_HANDLE_VALUE) + { + if (GetFileTime(hFile, // handle to file + NULL, // LPFILETIME lpCreationTime + NULL, // LPFILETIME lpLastAccessTime + &ft // LPFILETIME lpLastWriteTime + ) + ) + { + bSuccess = true; + } + + CloseHandle(hFile); + } + + return bSuccess; +} + +bool Sys_FileOutOfDate( LPCSTR psFinalFileName /* dest */, LPCSTR psDataFileName /* src */ ) +{ + FILETIME ftFinalFile, ftDataFile; + + if (Sys_GetFileTime(psFinalFileName, ftFinalFile) && Sys_GetFileTime(psDataFileName, ftDataFile)) + { + // timer res only accurate to within 2 seconds on FAT, so can't do exact compare... + // + //LONG l = CompareFileTime( &ftFinalFile, &ftDataFile ); + if ( (abs(ftFinalFile.dwLowDateTime - ftDataFile.dwLowDateTime) <= 20000000 ) && + ftFinalFile.dwHighDateTime == ftDataFile.dwHighDateTime + ) + { + return false; // file not out of date, ie use it. + } + return true; // flag return code to copy over a replacement version of this file + } + + + // extra error check, report as suspicious if you find a file locally but not out on the net.,. + // + if (com_developer->integer) + { + if (!Sys_GetFileTime(psDataFileName, ftDataFile)) + { + Com_Printf( "Sys_FileOutOfDate: reading %s but it's not on the net!\n", psFinalFileName); + } + } + + return false; +} + +bool FS_FileCacheable(const char* const filename) +{ + extern cvar_t *com_buildScript; + if (com_buildScript && com_buildScript->integer) + { + return true; + } + return( strchr(filename, '/') != 0 ); +} + +/* +=========== +FS_ShiftedStrStr +=========== +*/ +char *FS_ShiftedStrStr(const char *string, const char *substring, int shift) { + char buf[MAX_STRING_TOKENS]; + int i; + + for (i = 0; substring[i]; i++) { + buf[i] = substring[i] + shift; + } + buf[i] = '\0'; + return strstr(string, buf); +} + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ +extern qboolean com_fullyInitialized; + +int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) { + searchpath_t *search; + char *netpath; + pack_t *pak; + fileInPack_t *pakFile; + directory_t *dir; + long hash; + unz_s *zfi; + ZIP_FILE *temp; + int l; + char demoExt[16]; + + hash = 0; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( file == NULL ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'file' parameter passed\n" ); + } + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + Com_sprintf (demoExt, sizeof(demoExt), ".dm_%d",PROTOCOL_VERSION ); + // qpaths are not supposed to have a leading slash + if ( filename[0] == '/' || filename[0] == '\\' ) { + filename++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { + *file = 0; + return -1; + } + + // make sure the q3key file is only readable by the quake3.exe at initialization + // any other time the key should only be accessed in memory using the provided functions + if( com_fullyInitialized && strstr( filename, "q3key" ) ) { + *file = 0; + return -1; + } + + // + // search through the path, one element at a time + // + + *file = FS_HandleForFile(); + fsh[*file].handleFiles.unique = uniqueFILE; + + // this new bool is in for an optimisation, if you (eg) opened a BSP file under fs_copyfiles==2, + // then it triggered a copy operation to update your local HD version, then this will re-open the + // file handle on your local version, not the net build. This uses a bit more CPU to re-do the loop + // logic, but should read faster than accessing the net version a second time. + // + qboolean bFasterToReOpenUsingNewLocalFile = qfalse; + + do + { + bFasterToReOpenUsingNewLocalFile = qfalse; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // + if ( search->pack ) { + hash = FS_HashFileName(filename, search->pack->hashSize); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + // disregard if it doesn't match one of the allowed pure pak files + if ( !FS_PakIsPure(search->pack) ) { + continue; + } + + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + // found it! + + // mark the pak as having been referenced and mark specifics on cgame and ui + // shaders, txt, arena files by themselves do not count as a reference as + // these are loaded from all pk3s + // from every pk3 file.. + l = strlen( filename ); + if ( !(pak->referenced & FS_GENERAL_REF)) { + if ( Q_stricmp(filename + l - 7, ".shader") != 0 && + Q_stricmp(filename + l - 4, ".txt") != 0 && + Q_stricmp(filename + l - 4, ".str") != 0 && + Q_stricmp(filename + l - 4, ".cfg") != 0 && + Q_stricmp(filename + l - 4, ".fcf") != 0 && + Q_stricmp(filename + l - 7, ".config") != 0 && + strstr(filename, "levelshots") == NULL && + Q_stricmp(filename + l - 4, ".bot") != 0 && + Q_stricmp(filename + l - 6, ".arena") != 0 && + Q_stricmp(filename + l - 5, ".menu") != 0) { + pak->referenced |= FS_GENERAL_REF; + } + } + + /* + FS_ShiftedStrStr(filename, "jampgamex86.dll", -13); + //]^&`cZT`Xk+)!W__ + FS_ShiftedStrStr(filename, "cgamex86.dll", -7); + //\`Zf^q1/']ee + FS_ShiftedStrStr(filename, "uix86.dll", -5); + //pds31)_gg + */ + + // jampgame.qvm - 13 + // ]^&`cZT`X!di` + if (!(pak->referenced & FS_QAGAME_REF)) + { + if (FS_ShiftedStrStr(filename, "]T`cZT`X!di`", 13) || + FS_ShiftedStrStr(filename, "]T`cZT`Xk+)!W__", 13)) + { + pak->referenced |= FS_QAGAME_REF; + } + } + // cgame.qvm - 7 + // \`Zf^'jof + if (!(pak->referenced & FS_CGAME_REF)) + { + if (FS_ShiftedStrStr(filename , "\\`Zf^'jof", 7) || + FS_ShiftedStrStr(filename , "\\`Zf^q1/']ee", 7)) + { + pak->referenced |= FS_CGAME_REF; + } + } + // ui.qvm - 5 + // pd)lqh + if (!(pak->referenced & FS_UI_REF)) + { + if (FS_ShiftedStrStr(filename , "pd)lqh", 5) || + FS_ShiftedStrStr(filename , "pds31)_gg", 5)) + { + pak->referenced |= FS_UI_REF; + } + } + + if ( uniqueFILE ) { + // open a new file on the pakfile + fsh[*file].handleFiles.file.z = unzReOpen (pak->pakFilename, pak->handle); + if (fsh[*file].handleFiles.file.z == NULL) { + Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->pakFilename); + } + } else { + fsh[*file].handleFiles.file.z = pak->handle; + } + Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); + fsh[*file].zipFile = qtrue; + zfi = (unz_s *)fsh[*file].handleFiles.file.z; + // in case the file was new + temp = zfi->file; + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition(pak->handle, pakFile->pos); + // copy the file info into the unzip structure + Com_Memcpy( zfi, pak->handle, sizeof(unz_s) ); + // we copy this back into the structure + zfi->file = temp; + // open the file in the zip + unzOpenCurrentFile( fsh[*file].handleFiles.file.z ); + fsh[*file].zipFilePos = pakFile->pos; + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", + filename, pak->pakFilename ); + } + #ifndef DEDICATED + #ifndef FINAL_BUILD + // Check for unprecached files when in game but not in the menus + if((cls.state == CA_ACTIVE) && !(cls.keyCatchers & KEYCATCH_UI)) + { + Com_Printf(S_COLOR_YELLOW "WARNING: File %s not precached\n", filename); + } + #endif + #endif // DEDICATED + return zfi->cur_file_info.uncompressed_size; + } + pakFile = pakFile->next; + } while(pakFile != NULL); + } else if ( search->dir ) { + // check a file in the directory tree + + // if we are running restricted, the only files we + // will allow to come from the directory are .cfg files + l = strlen( filename ); + // FIXME TTimo I'm not sure about the fs_numServerPaks test + // if you are using FS_ReadFile to find out if a file exists, + // this test can make the search fail although the file is in the directory + // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8 + // turned out I used FS_FileExists instead + if ( fs_restrict->integer || fs_numServerPaks ) { + + if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files + && Q_stricmp( filename + l - 4, ".fcf" ) // force configuration files + && Q_stricmp( filename + l - 5, ".menu" ) // menu files + && Q_stricmp( filename + l - 5, ".game" ) // menu files + && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files + && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files + continue; + } + } + + dir = search->dir; + + netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); + fsh[*file].handleFiles.file.o = fopen (netpath, "rb"); + if ( !fsh[*file].handleFiles.file.o ) { + continue; + } + + if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files + && Q_stricmp( filename + l - 4, ".fcf" ) // force configuration files + && Q_stricmp( filename + l - 5, ".menu" ) // menu files + && Q_stricmp( filename + l - 5, ".game" ) // menu files + && Q_stricmp( filename + l - strlen(demoExt), demoExt ) // menu files + && Q_stricmp( filename + l - 4, ".dat" ) ) { // for journal files + fs_fakeChkSum = random(); + } + + // if running with fs_copyfiles 2, and search path == local, then we need to fail to open + // if the time/date stamp != the network version (so it'll loop round again and use the network path, + // which comes later in the search order) + // + if ( fs_copyfiles->integer == 2 && fs_cdpath->string[0] && !Q_stricmp( dir->path, fs_basepath->string ) + && FS_FileCacheable(filename) ) + { + if ( Sys_FileOutOfDate( netpath, FS_BuildOSPath( fs_cdpath->string, dir->gamedir, filename ) )) + { + fclose(fsh[*file].handleFiles.file.o); + fsh[*file].handleFiles.file.o = 0; + continue; //carry on to find the cdpath version. + } + } + + Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); + fsh[*file].zipFile = qfalse; + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename, + dir->path, dir->gamedir ); + } + + // if we are getting it from the cdpath, optionally copy it + // to the basepath + if ( fs_copyfiles->integer && !Q_stricmp( dir->path, fs_cdpath->string ) ) { + char *copypath; + + copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename ); + switch ( fs_copyfiles->integer ) + { + default: + case 1: + { + FS_CopyFile( netpath, copypath ); + } + break; + + case 2: + { + + if (FS_FileCacheable(filename) ) + { + // maybe change this to Com_DPrintf? On the other hand... + // + Com_Printf( "fs_copyfiles(2), Copying: %s to %s\n", netpath, copypath ); + + FS_CreatePath( copypath ); + + bool bOk = true; + if (!CopyFile( netpath, copypath, FALSE )) + { + DWORD dwAttrs = GetFileAttributes(copypath); + SetFileAttributes(copypath, dwAttrs & ~FILE_ATTRIBUTE_READONLY); + bOk = !!CopyFile( netpath, copypath, FALSE ); + } + + if (bOk) + { + // clear this handle and setup for re-opening of the new local copy... + // + bFasterToReOpenUsingNewLocalFile = qtrue; + fclose(fsh[*file].handleFiles.file.o); + fsh[*file].handleFiles.file.o = NULL; + } + } + } + break; + } + } + + if (bFasterToReOpenUsingNewLocalFile) + { + break; // and re-read the local copy, not the net version + } + + #ifndef DEDICATED + #ifndef FINAL_BUILD + // Check for unprecached files when in game but not in the menus + if((cls.state == CA_ACTIVE) && !(cls.keyCatchers & KEYCATCH_UI)) + { + Com_Printf(S_COLOR_YELLOW "WARNING: File %s not precached\n", filename); + } + #endif + #endif // dedicated + return FS_filelength (*file); + } + } + } + while ( bFasterToReOpenUsingNewLocalFile ); + + Com_DPrintf ("Can't find %s\n", filename); +#ifdef FS_MISSING + if (missingFiles) { + fprintf(missingFiles, "%s\n", filename); + } +#endif + *file = 0; + return -1; +} + + +/* +================= +FS_Read + +Properly handles partial reads +================= +*/ +int FS_Read2( void *buffer, int len, fileHandle_t f ) { + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !f ) { + return 0; + } + if (fsh[f].streamed) { + int r; + fsh[f].streamed = qfalse; + r = Sys_StreamedRead( buffer, len, 1, f); + fsh[f].streamed = qtrue; + return r; + } else { + return FS_Read( buffer, len, f); + } +} + +int FS_Read( void *buffer, int len, fileHandle_t f ) { + int block, remaining; + int read; + byte *buf; + int tries; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !f ) { + return 0; + } + + buf = (byte *)buffer; + fs_readCount += len; + + if (fsh[f].zipFile == qfalse) { + remaining = len; + tries = 0; + while (remaining) { + block = remaining; + read = fread (buf, 1, block, fsh[f].handleFiles.file.o); + if (read == 0) { + // we might have been trying to read from a CD, which + // sometimes returns a 0 read on windows + if (!tries) { + tries = 1; + } else { + return len-remaining; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read"); + } + } + + if (read == -1) { + Com_Error (ERR_FATAL, "FS_Read: -1 bytes read"); + } + + remaining -= read; + buf += read; + } + return len; + } else { + return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len); + } +} + +/* +================= +FS_Write + +Properly handles partial writes +================= +*/ +int FS_Write( const void *buffer, int len, fileHandle_t h ) { + int block, remaining; + int written; + byte *buf; + int tries; + FILE *f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !h ) { + return 0; + } + + f = FS_FileForHandle(h); + buf = (byte *)buffer; + + remaining = len; + tries = 0; + while (remaining) { + block = remaining; + written = fwrite (buf, 1, block, f); + if (written == 0) { + if (!tries) { + tries = 1; + } else { + Com_Printf( "FS_Write: 0 bytes written\n" ); + return 0; + } + } + + if (written == -1) { + Com_Printf( "FS_Write: -1 bytes written\n" ); + return 0; + } + + remaining -= written; + buf += written; + } + if ( fsh[h].handleSync ) { + fflush( f ); + } + return len; +} + +/* +================= +FS_Seek + +================= +*/ +int FS_Seek( fileHandle_t f, long offset, int origin ) { + int _origin; + char foo[65536]; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + return -1; + } + + if (fsh[f].streamed) { + fsh[f].streamed = qfalse; + Sys_StreamSeek( f, offset, origin ); + fsh[f].streamed = qtrue; + } + + if (fsh[f].zipFile == qtrue) { + if (offset == 0 && origin == FS_SEEK_SET) { + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos); + return unzOpenCurrentFile(fsh[f].handleFiles.file.z); + } else if (offset<65536) { + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos); + unzOpenCurrentFile(fsh[f].handleFiles.file.z); + return FS_Read(foo, offset, f); + } else { + Com_Error( ERR_FATAL, "ZIP FILE FSEEK NOT YET IMPLEMENTED\n" ); + return -1; + } + } else { + FILE *file; + file = FS_FileForHandle(f); + switch( origin ) { + case FS_SEEK_CUR: + _origin = SEEK_CUR; + break; + case FS_SEEK_END: + _origin = SEEK_END; + break; + case FS_SEEK_SET: + _origin = SEEK_SET; + break; + default: + _origin = SEEK_CUR; + Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); + break; + } + + return fseek( file, offset, _origin ); + } +} + +/* +====================================================================================== + +CONVENIENCE FUNCTIONS FOR ENTIRE FILES + +====================================================================================== +*/ + +int FS_FileIsInPAK(const char *filename, int *pChecksum ) { + searchpath_t *search; + pack_t *pak; + fileInPack_t *pakFile; + long hash = 0; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + // qpaths are not supposed to have a leading slash + if ( filename[0] == '/' || filename[0] == '\\' ) { + filename++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { + return -1; + } + + // + // search through the path, one element at a time + // + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // + if (search->pack) { + hash = FS_HashFileName(filename, search->pack->hashSize); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + // disregard if it doesn't match one of the allowed pure pak files + if ( !FS_PakIsPure(search->pack) ) { + continue; + } + + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + if (pChecksum) { + *pChecksum = pak->pure_checksum; + } + return 1; + } + pakFile = pakFile->next; + } while(pakFile != NULL); + } + } + return -1; +} + +/* +============ +FS_ReadFile + +Filename are relative to the quake search path +a null buffer will just return the file length without loading +============ +*/ +int FS_ReadFile( const char *qpath, void **buffer ) { + fileHandle_t h; + byte* buf; + qboolean isConfig; + int len; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !qpath || !qpath[0] ) { + Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" ); + } + + buf = NULL; // quiet compiler warning + + // if this is a .cfg file and we are playing back a journal, read + // it from the journal file + if ( strstr( qpath, ".cfg" ) ) { + isConfig = qtrue; + if ( com_journal && com_journal->integer == 2 ) { + int r; + + Com_DPrintf( "Loading %s from journal file.\n", qpath ); + r = FS_Read( &len, sizeof( len ), com_journalDataFile ); + if ( r != sizeof( len ) ) { + if (buffer != NULL) *buffer = NULL; + return -1; + } + // if the file didn't exist when the journal was created + if (!len) { + if (buffer == NULL) { + return 1; // hack for old journal files + } + *buffer = NULL; + return -1; + } + if (buffer == NULL) { + return len; + } + + buf = (unsigned char *)Hunk_AllocateTempMemory(len+1); + *buffer = buf; + + r = FS_Read( buf, len, com_journalDataFile ); + if ( r != len ) { + Com_Error( ERR_FATAL, "Read from journalDataFile failed" ); + } + + fs_loadCount++; + fs_loadStack++; + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + + return len; + } + } else { + isConfig = qfalse; + } + + // look for it in the filesystem or pack files + len = FS_FOpenFileRead( qpath, &h, qfalse ); + if ( h == 0 ) { + if ( buffer ) { + *buffer = NULL; + } + // if we are journalling and it is a config file, write a zero to the journal file + if ( isConfig && com_journal && com_journal->integer == 1 ) { + Com_DPrintf( "Writing zero for %s to journal file.\n", qpath ); + len = 0; + FS_Write( &len, sizeof( len ), com_journalDataFile ); + FS_Flush( com_journalDataFile ); + } + return -1; + } + + if ( !buffer ) { + if ( isConfig && com_journal && com_journal->integer == 1 ) { + Com_DPrintf( "Writing len for %s to journal file.\n", qpath ); + FS_Write( &len, sizeof( len ), com_journalDataFile ); + FS_Flush( com_journalDataFile ); + } + FS_FCloseFile( h); + return len; + } + + fs_loadCount++; +/* fs_loadStack++; + + buf = (unsigned char *)Hunk_AllocateTempMemory(len+1); + *buffer = buf;*/ + + buf = (byte*)Z_Malloc( len+1, TAG_FILESYS, qfalse); + buf[len]='\0'; // because we're not calling Z_Malloc with optional trailing 'bZeroIt' bool + *buffer = buf; + +// Z_Label(buf, qpath); + + FS_Read (buf, len, h); + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + FS_FCloseFile( h ); + + // if we are journalling and it is a config file, write it to the journal file + if ( isConfig && com_journal && com_journal->integer == 1 ) { + Com_DPrintf( "Writing %s to journal file.\n", qpath ); + FS_Write( &len, sizeof( len ), com_journalDataFile ); + FS_Write( buf, len, com_journalDataFile ); + FS_Flush( com_journalDataFile ); + } + return len; +} + +/* +============= +FS_FreeFile +============= +*/ +void FS_FreeFile( void *buffer ) { + /* + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + if ( !buffer ) { + Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" ); + } + fs_loadStack--; + + Hunk_FreeTempMemory( buffer ); + + // if all of our temp files are free, clear all of our space + if ( fs_loadStack == 0 ) { + Hunk_ClearTempMemory(); + } + */ + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + if ( !buffer ) { + Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" ); + } + + Z_Free( buffer ); +} + +/* +========================================================================== + +ZIP FILE LOADING + +========================================================================== +*/ + +/* +================= +FS_LoadZipFile + +Creates a new pak_t in the search chain for the contents +of a zip file. +================= +*/ +static pack_t *FS_LoadZipFile( char *zipfile, const char *basename ) +{ + fileInPack_t *buildBuffer; + pack_t *pack; + unzFile uf; + int err; + unz_global_info gi; + char filename_inzip[MAX_ZPATH]; + unz_file_info file_info; + int i, len; + long hash; + int fs_numHeaderLongs; + int *fs_headerLongs; + char *namePtr; + + fs_numHeaderLongs = 0; + + uf = unzOpen(zipfile); + err = unzGetGlobalInfo (uf,&gi); + + if (err != UNZ_OK) + return NULL; + + fs_packFiles += gi.number_entry; + + len = 0; + unzGoToFirstFile(uf); + for (i = 0; i < gi.number_entry; i++) + { + err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); + if (err != UNZ_OK) { + break; + } + len += strlen(filename_inzip) + 1; + unzGoToNextFile(uf); + } + + buildBuffer = (struct fileInPack_s *)Z_Malloc( (gi.number_entry * sizeof( fileInPack_t )) + len, TAG_FILESYS, qtrue ); + namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t ); + fs_headerLongs = (int *)Z_Malloc( gi.number_entry * sizeof(int), TAG_FILESYS, qtrue ); + + // get the hash table size from the number of files in the zip + // because lots of custom pk3 files have less than 32 or 64 files + for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) { + if (i > gi.number_entry) { + break; + } + } + + pack = (pack_t *)Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *), TAG_FILESYS, qtrue ); + pack->hashSize = i; + pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t )); + for(i = 0; i < pack->hashSize; i++) { + pack->hashTable[i] = NULL; + } + + Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) ); + Q_strncpyz( pack->pakBasename, basename, sizeof( pack->pakBasename ) ); + + // strip .pk3 if needed + if ( strlen( pack->pakBasename ) > 4 && !Q_stricmp( pack->pakBasename + strlen( pack->pakBasename ) - 4, ".pk3" ) ) { + pack->pakBasename[strlen( pack->pakBasename ) - 4] = 0; + } + + pack->handle = uf; + pack->numfiles = gi.number_entry; + unzGoToFirstFile(uf); + + for (i = 0; i < gi.number_entry; i++) + { + err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); + if (err != UNZ_OK) { + break; + } + if (file_info.uncompressed_size > 0) { + fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc); + } + Q_strlwr( filename_inzip ); + hash = FS_HashFileName(filename_inzip, pack->hashSize); + buildBuffer[i].name = namePtr; + strcpy( buildBuffer[i].name, filename_inzip ); + namePtr += strlen(filename_inzip) + 1; + // store the file position in the zip + unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos); + // + buildBuffer[i].next = pack->hashTable[hash]; + pack->hashTable[hash] = &buildBuffer[i]; + unzGoToNextFile(uf); + } + + pack->checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs ); + pack->pure_checksum = Com_BlockChecksumKey( fs_headerLongs, 4 * fs_numHeaderLongs, LittleLong(fs_checksumFeed) ); + pack->checksum = LittleLong( pack->checksum ); + pack->pure_checksum = LittleLong( pack->pure_checksum ); + + Z_Free(fs_headerLongs); + + pack->buildBuffer = buildBuffer; + return pack; +} + +/* +================================================================================= + +DIRECTORY SCANNING FUNCTIONS + +================================================================================= +*/ + +#define MAX_FOUND_FILES 0x1000 + +static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) { + int len, at, newdep; + + newdep = 0; + zpath[0] = 0; + len = 0; + at = 0; + + while(zname[at] != 0) + { + if (zname[at]=='/' || zname[at]=='\\') { + len = at; + newdep++; + } + at++; + } + strcpy(zpath, zname); + zpath[len] = 0; + *depth = newdep; + + return len; +} + +/* +================== +FS_AddFileToList +================== +*/ +static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) { + int i; + + if ( nfiles == MAX_FOUND_FILES - 1 ) { + return nfiles; + } + for ( i = 0 ; i < nfiles ; i++ ) { + if ( !Q_stricmp( name, list[i] ) ) { + return nfiles; // allready in list + } + } + list[nfiles] = CopyString( name ); + nfiles++; + + return nfiles; +} + +/* +=============== +FS_ListFilteredFiles + +Returns a uniqued list of files that match the given criteria +from all search paths +=============== +*/ +char **FS_ListFilteredFiles( const char *path, const char *extension, char *filter, int *numfiles ) { + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + searchpath_t *search; + int i; + int pathLength; + int extensionLength; + int length, pathDepth, temp; + pack_t *pak; + fileInPack_t *buildBuffer; + char zpath[MAX_ZPATH]; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !path ) { + *numfiles = 0; + return NULL; + } + if ( !extension ) { + extension = ""; + } + + pathLength = strlen( path ); + if ( path[pathLength-1] == '\\' || path[pathLength-1] == '/' ) { + pathLength--; + } + extensionLength = strlen( extension ); + nfiles = 0; + FS_ReturnPath(path, zpath, &pathDepth); + + // + // search through the path, one element at a time, adding to list + // + for (search = fs_searchpaths ; search ; search = search->next) { + // is the element a pak file? + if (search->pack) { + + //ZOID: If we are pure, don't search for files on paks that + // aren't on the pure list + if ( !FS_PakIsPure(search->pack) ) { + continue; + } + + // look through all the pak file elements + pak = search->pack; + buildBuffer = pak->buildBuffer; + for (i = 0; i < pak->numfiles; i++) { + char *name; + int zpathLen, depth; + + // check for directory match + name = buildBuffer[i].name; + // + if (filter) { + // case insensitive + if (!Com_FilterPath( filter, name, qfalse )) + continue; + // unique the match + nfiles = FS_AddFileToList( name, list, nfiles ); + } + else { + + zpathLen = FS_ReturnPath(name, zpath, &depth); + + if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) { + continue; + } + + // check for extension match + length = strlen( name ); + if ( length < extensionLength ) { + continue; + } + + if ( Q_stricmp( name + length - extensionLength, extension ) ) { + continue; + } + // unique the match + + temp = pathLength; + if (pathLength) { + temp++; // include the '/' + } + nfiles = FS_AddFileToList( name + temp, list, nfiles ); + } + } + } else if (search->dir) { // scan for files in the filesystem + char *netpath; + int numSysFiles; + char **sysFiles; + char *name; + + // don't scan directories for files if we are pure or restricted + if ( (fs_restrict->integer || fs_numServerPaks) && + (!extension || Q_stricmp(extension, "fcf") || fs_restrict->integer) ) + { + //rww - allow scanning for fcf files outside of pak even if pure + continue; + } + else + { + netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path ); + sysFiles = Sys_ListFiles( netpath, extension, filter, &numSysFiles, qfalse ); + for ( i = 0 ; i < numSysFiles ; i++ ) { + // unique the match + name = sysFiles[i]; + nfiles = FS_AddFileToList( name, list, nfiles ); + } + Sys_FreeFileList( sysFiles ); + } + } + } + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = (char **)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ), TAG_FILESYS ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + +/* +================= +FS_ListFiles +================= +*/ +char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) { + return FS_ListFilteredFiles( path, extension, NULL, numfiles ); +} + +/* +================= +FS_FreeFileList +================= +*/ +void FS_FreeFileList( char **fileList ) { + //rwwRMG - changed to fileList to not conflict with list type + int i; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !fileList ) { + return; + } + + for ( i = 0 ; fileList[i] ; i++ ) { + Z_Free( fileList[i] ); + } + + Z_Free( fileList ); +} + + +/* +================ +FS_GetFileList +================ +*/ +int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { + int nFiles, i, nTotal, nLen; + char **pFiles = NULL; + + *listbuf = 0; + nFiles = 0; + nTotal = 0; + + if (Q_stricmp(path, "$modlist") == 0) { + return FS_GetModList(listbuf, bufsize); + } + + pFiles = FS_ListFiles(path, extension, &nFiles); + + for (i =0; i < nFiles; i++) { + nLen = strlen(pFiles[i]) + 1; + if (nTotal + nLen + 1 < bufsize) { + strcpy(listbuf, pFiles[i]); + listbuf += nLen; + nTotal += nLen; + } + else { + nFiles = i; + break; + } + } + + FS_FreeFileList(pFiles); + + return nFiles; +} + +// NOTE: could prolly turn out useful for the win32 version too, but it's used only by linux and Mac OS X +//#if defined(__linux__) || defined(MACOS_X) +/* +======================= +Sys_ConcatenateFileLists + +mkv: Naive implementation. Concatenates three lists into a + new list, and frees the old lists from the heap. +bk001129 - from cvs1.17 (mkv) + +FIXME TTimo those two should move to common.c next to Sys_ListFiles +======================= + */ +static unsigned int Sys_CountFileList(char **list) +{ + int i = 0; + + if (list) + { + while (*list) + { + list++; + i++; + } + } + return i; +} + +static char** Sys_ConcatenateFileLists( char **list0, char **list1, char **list2 ) +{ + int totalLength = 0; + char** cat = NULL, **dst, **src; + + totalLength += Sys_CountFileList(list0); + totalLength += Sys_CountFileList(list1); + totalLength += Sys_CountFileList(list2); + + /* Create new list. */ + dst = cat = (char **)Z_Malloc( ( totalLength + 1 ) * sizeof( char* ), TAG_FILESYS, qtrue ); + + /* Copy over lists. */ + if (list0) { + for (src = list0; *src; src++, dst++) + *dst = *src; + } + if (list1) { + for (src = list1; *src; src++, dst++) + *dst = *src; + } + if (list2) { + for (src = list2; *src; src++, dst++) + *dst = *src; + } + + // Terminate the list + *dst = NULL; + + // Free our old lists. + // NOTE: not freeing their content, it's been merged in dst and still being used + if (list0) Z_Free( list0 ); + if (list1) Z_Free( list1 ); + if (list2) Z_Free( list2 ); + + return cat; +} +//#endif + +/* +================ +FS_GetModList + +Returns a list of mod directory names +A mod directory is a peer to base with a pk3 in it +The directories are searched in base path, cd path and home path +================ +*/ +int FS_GetModList( char *listbuf, int bufsize ) { + int nMods, i, j, nTotal, nLen, nPaks, nPotential, nDescLen; + char **pFiles = NULL; + char **pPaks = NULL; + char *name, *path; + char descPath[MAX_OSPATH]; + fileHandle_t descHandle; + + int dummy; + char **pFiles0 = NULL; + char **pFiles1 = NULL; + char **pFiles2 = NULL; + qboolean bDrop = qfalse; + + *listbuf = 0; + nMods = nPotential = nTotal = 0; + + pFiles0 = Sys_ListFiles( fs_homepath->string, NULL, NULL, &dummy, qtrue ); + pFiles1 = Sys_ListFiles( fs_basepath->string, NULL, NULL, &dummy, qtrue ); + pFiles2 = Sys_ListFiles( fs_cdpath->string, NULL, NULL, &dummy, qtrue ); + // we searched for mods in the three paths + // it is likely that we have duplicate names now, which we will cleanup below + pFiles = Sys_ConcatenateFileLists( pFiles0, pFiles1, pFiles2 ); + nPotential = Sys_CountFileList(pFiles); + + for ( i = 0 ; i < nPotential ; i++ ) { + name = pFiles[i]; + // NOTE: cleaner would involve more changes + // ignore duplicate mod directories + if (i!=0) { + bDrop = qfalse; + for(j=0; jstring, name, "" ); + nPaks = 0; + pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse); + Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present + + /* Try on cd path */ + if( nPaks <= 0 ) { + path = FS_BuildOSPath( fs_cdpath->string, name, "" ); + nPaks = 0; + pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); + Sys_FreeFileList( pPaks ); + } + + /* try on home path */ + if ( nPaks <= 0 ) + { + path = FS_BuildOSPath( fs_homepath->string, name, "" ); + nPaks = 0; + pPaks = Sys_ListFiles( path, ".pk3", NULL, &nPaks, qfalse ); + Sys_FreeFileList( pPaks ); + } + + if (nPaks > 0) { + nLen = strlen(name) + 1; + // nLen is the length of the mod path + // we need to see if there is a description available + descPath[0] = '\0'; + strcpy(descPath, name); + strcat(descPath, "/description.txt"); + nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle ); + if ( nDescLen > 0 && descHandle) { + FILE *file; + file = FS_FileForHandle(descHandle); + Com_Memset( descPath, 0, sizeof( descPath ) ); + nDescLen = fread(descPath, 1, 48, file); + if (nDescLen >= 0) { + descPath[nDescLen] = '\0'; + } + FS_FCloseFile(descHandle); + } else { + strcpy(descPath, name); + } + nDescLen = strlen(descPath) + 1; + + if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) { + strcpy(listbuf, name); + listbuf += nLen; + strcpy(listbuf, descPath); + listbuf += nDescLen; + nTotal += nLen + nDescLen; + nMods++; + } + else { + break; + } + } + } + } + Sys_FreeFileList( pFiles ); + + return nMods; +} + + + + +//============================================================================ + +/* +================ +FS_Dir_f +================ +*/ +void FS_Dir_f( void ) { + char *path; + char *extension; + char **dirnames; + int ndirs; + int i; + + if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) { + Com_Printf( "usage: dir [extension]\n" ); + return; + } + + if ( Cmd_Argc() == 2 ) { + path = Cmd_Argv( 1 ); + extension = ""; + } else { + path = Cmd_Argv( 1 ); + extension = Cmd_Argv( 2 ); + } + + Com_Printf( "Directory of %s %s\n", path, extension ); + Com_Printf( "---------------\n" ); + + dirnames = FS_ListFiles( path, extension, &ndirs ); + + for ( i = 0; i < ndirs; i++ ) { + Com_Printf( "%s\n", dirnames[i] ); + } + FS_FreeFileList( dirnames ); +} + +/* +=========== +FS_ConvertPath +=========== +*/ +void FS_ConvertPath( char *s ) { + while (*s) { + if ( *s == '\\' || *s == ':' ) { + *s = '/'; + } + s++; + } +} + +/* +=========== +FS_PathCmp + +Ignore case and seprator char distinctions +=========== +*/ +int FS_PathCmp( const char *s1, const char *s2 ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if (c1 >= 'a' && c1 <= 'z') { + c1 -= ('a' - 'A'); + } + if (c2 >= 'a' && c2 <= 'z') { + c2 -= ('a' - 'A'); + } + + if ( c1 == '\\' || c1 == ':' ) { + c1 = '/'; + } + if ( c2 == '\\' || c2 == ':' ) { + c2 = '/'; + } + + if (c1 < c2) { + return -1; // strings not equal + } + if (c1 > c2) { + return 1; + } + } while (c1); + + return 0; // strings are equal +} + +/* +================ +FS_SortFileList +================ +*/ +void FS_SortFileList(char **filelist, int numfiles) { + int i, j, k, numsortedfiles; + char **sortedlist; + + sortedlist = (char **)Z_Malloc( ( numfiles + 1 ) * sizeof( *sortedlist ), TAG_FILESYS, qtrue ); + sortedlist[0] = NULL; + numsortedfiles = 0; + for (i = 0; i < numfiles; i++) { + for (j = 0; j < numsortedfiles; j++) { + if (FS_PathCmp(filelist[i], sortedlist[j]) < 0) { + break; + } + } + for (k = numsortedfiles; k > j; k--) { + sortedlist[k] = sortedlist[k-1]; + } + sortedlist[j] = filelist[i]; + numsortedfiles++; + } + Com_Memcpy(filelist, sortedlist, numfiles * sizeof( *filelist ) ); + Z_Free(sortedlist); +} + +/* +================ +FS_NewDir_f +================ +*/ +void FS_NewDir_f( void ) { + char *filter; + char **dirnames; + int ndirs; + int i; + + if ( Cmd_Argc() < 2 ) { + Com_Printf( "usage: fdir \n" ); + Com_Printf( "example: fdir *q3dm*.bsp\n"); + return; + } + + filter = Cmd_Argv( 1 ); + + Com_Printf( "---------------\n" ); + + dirnames = FS_ListFilteredFiles( "", "", filter, &ndirs ); + + FS_SortFileList(dirnames, ndirs); + + for ( i = 0; i < ndirs; i++ ) { + FS_ConvertPath(dirnames[i]); + Com_Printf( "%s\n", dirnames[i] ); + } + Com_Printf( "%d files listed\n", ndirs ); + FS_FreeFileList( dirnames ); +} + +/* +============ +FS_Path_f + +============ +*/ +void FS_Path_f( void ) { + searchpath_t *s; + int i; + + Com_Printf ("Current search path:\n"); + for (s = fs_searchpaths; s; s = s->next) { + if (s->pack) { + Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles); + if ( fs_numServerPaks ) { + if ( !FS_PakIsPure(s->pack) ) { + Com_Printf( " not on the pure list\n" ); + } else { + Com_Printf( " on the pure list\n" ); + } + } + } else { + Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir ); + } + } + + + Com_Printf( "\n" ); + for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { + if ( fsh[i].handleFiles.file.o ) { + Com_Printf( "handle %i: %s\n", i, fsh[i].name ); + } + } +} + +/* +============ +FS_TouchFile_f + +The only purpose of this function is to allow game script files to copy +arbitrary files furing an "fs_copyfiles 1" run. +============ +*/ +void FS_TouchFile_f( void ) { + fileHandle_t f; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: touchFile \n" ); + return; + } + + FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse ); + if ( f ) { + FS_FCloseFile( f ); + } +} + +//=========================================================================== + + +static int QDECL paksort( const void *a, const void *b ) { + char *aa, *bb; + + aa = *(char **)a; + bb = *(char **)b; + + return FS_PathCmp( aa, bb ); +} + +/* +================ +FS_AddGameDirectory + +Sets fs_gamedir, adds the directory to the head of the path, +then loads the zip headers +================ +*/ +#define MAX_PAKFILES 1024 +static void FS_AddGameDirectory( const char *path, const char *dir ) { + searchpath_t *sp; + int i; + searchpath_t *search; + searchpath_t *thedir; + pack_t *pak; + char *pakfile; + int numfiles; + char **pakfiles; + char *sorted[MAX_PAKFILES]; + + // this fixes the case where fs_basepath is the same as fs_cdpath + // which happens on full installs + for ( sp = fs_searchpaths ; sp ; sp = sp->next ) { + if ( sp->dir && !Q_stricmp(sp->dir->path, path) && !Q_stricmp(sp->dir->gamedir, dir)) { + return; // we've already got this one + } + } + + Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) ); + + // + // add the directory to the search path + // + search = (struct searchpath_s *)Z_Malloc (sizeof(searchpath_t), TAG_FILESYS, qtrue); + search->dir = (directory_t *)Z_Malloc( sizeof( *search->dir ), TAG_FILESYS, qtrue ); + + Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) ); + Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) ); + search->next = fs_searchpaths; + fs_searchpaths = search; + + thedir = search; + + // find all pak files in this directory + pakfile = FS_BuildOSPath( path, dir, "" ); + pakfile[ strlen(pakfile) - 1 ] = 0; // strip the trailing slash + + pakfiles = Sys_ListFiles( pakfile, ".pk3", NULL, &numfiles, qfalse ); + + // sort them so that later alphabetic matches override + // earlier ones. This makes pak1.pk3 override pak0.pk3 + if ( numfiles > MAX_PAKFILES ) { + numfiles = MAX_PAKFILES; + } + for ( i = 0 ; i < numfiles ; i++ ) { + sorted[i] = pakfiles[i]; + } + + qsort( sorted, numfiles, 4, paksort ); + + for ( i = 0 ; i < numfiles ; i++ ) { + pakfile = FS_BuildOSPath( path, dir, sorted[i] ); + if ( ( pak = FS_LoadZipFile( pakfile, sorted[i] ) ) == 0 ) + continue; + // store the game name for downloading + strcpy(pak->pakGamename, dir); + + search = (searchpath_s *)Z_Malloc (sizeof(searchpath_t), TAG_FILESYS, qtrue); + search->pack = pak; + + if (fs_dirbeforepak && fs_dirbeforepak->integer && thedir) + { + searchpath_t *oldnext = thedir->next; + thedir->next = search; + + while (oldnext) + { + search->next = oldnext; + search = search->next; + oldnext = oldnext->next; + } + } + else + { + search->next = fs_searchpaths; + fs_searchpaths = search; + } + } + + // done + Sys_FreeFileList( pakfiles ); +} + +/* +================ +FS_idPak +================ +*/ +qboolean FS_idPak( char *pak, char *base ) { + int i; + + for (i = 0; i < NUM_ID_PAKS; i++) { + if ( !FS_FilenameCompare(pak, va("%s/assets%d", base, i)) ) { + break; + } + } + if (i < NUM_ID_PAKS) { + return qtrue; + } + return qfalse; +} + +/* +================ +FS_ComparePaks + +if dlstring == qtrue + +Returns a list of pak files that we should download from the server. They all get stored +in the current gamedir and an FS_Restart will be fired up after we download them all. + +The string is the format: + +@remotename@localname [repeat] + +static int fs_numServerReferencedPaks; +static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; +static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; + +---------------- +dlstring == qfalse + +we are not interested in a download string format, we want something human-readable +(this is used for diagnostics while connecting to a pure server) +================ +*/ +qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ) { + searchpath_t *sp; + qboolean havepak, badchecksum; + int i; + + if ( !fs_numServerReferencedPaks ) { + return qfalse; // Server didn't send any pack information along + } + + *neededpaks = 0; + + for ( i = 0 ; i < fs_numServerReferencedPaks ; i++ ) { + // Ok, see if we have this pak file + badchecksum = qfalse; + havepak = qfalse; + + // never autodownload any of the id paks + if ( FS_idPak(fs_serverReferencedPakNames[i], "base") || FS_idPak(fs_serverReferencedPakNames[i], "missionpack") ) { + continue; + } + + for ( sp = fs_searchpaths ; sp ; sp = sp->next ) { + if ( sp->pack && sp->pack->checksum == fs_serverReferencedPaks[i] ) { + havepak = qtrue; // This is it! + break; + } + } + + if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) { + // Don't got it + + if (dlstring) + { + // Remote name + Q_strcat( neededpaks, len, "@"); + Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); + Q_strcat( neededpaks, len, ".pk3" ); + + // Local name + Q_strcat( neededpaks, len, "@"); + // Do we have one with the same name? + if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) { + char st[MAX_ZPATH]; + // We already have one called this, we need to download it to another name + // Make something up with the checksum in it + Com_sprintf( st, sizeof( st ), "%s.%08x.pk3", fs_serverReferencedPakNames[i], fs_serverReferencedPaks[i] ); + Q_strcat( neededpaks, len, st ); + } else { + Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); + Q_strcat( neededpaks, len, ".pk3" ); + } + } + else + { + Q_strcat( neededpaks, len, fs_serverReferencedPakNames[i] ); + Q_strcat( neededpaks, len, ".pk3" ); + // Do we have one with the same name? + if ( FS_SV_FileExists( va( "%s.pk3", fs_serverReferencedPakNames[i] ) ) ) + { + Q_strcat( neededpaks, len, " (local file exists with wrong checksum)"); + } + Q_strcat( neededpaks, len, "\n"); + } + } + } + if ( *neededpaks ) { + return qtrue; + } + + return qfalse; // We have them all +} + +#ifdef USE_CD_KEY + +void Com_AppendCDKey( const char *filename ); +void Com_ReadCDKey( const char *filename ); + +#endif // USE_CD_KEY + +//rww - add search paths in for received svc_setgame +void FS_UpdateGamedir(void) +{ + if ( fs_gamedirvar->string[0] && Q_stricmp( fs_gamedirvar->string, BASEGAME ) ) + { + if (fs_cdpath->string[0]) + { + FS_AddGameDirectory(fs_cdpath->string, fs_gamedirvar->string); + } + if (fs_basepath->string[0]) + { + FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string); + } + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) + { + FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string); + } + } +} + +/* +================ +FS_ReorderPurePaks +NOTE TTimo: the reordering that happens here is not reflected in the cvars (\cvarlist *pak*) + this can lead to misleading situations, see https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 +================ +*/ +static void FS_ReorderPurePaks() +{ + searchpath_t *s; + int i; + searchpath_t **p_insert_index, // for linked list reordering + **p_previous; // when doing the scan + + // only relevant when connected to pure server + if ( !fs_numServerPaks ) + return; + + fs_reordered = qfalse; + + p_insert_index = &fs_searchpaths; // we insert in order at the beginning of the list + for ( i = 0 ; i < fs_numServerPaks ; i++ ) { + p_previous = p_insert_index; // track the pointer-to-current-item + for (s = *p_insert_index; s; s = s->next) { + // the part of the list before p_insert_index has been sorted already + if (s->pack && fs_serverPaks[i] == s->pack->checksum) { + fs_reordered = qtrue; + // move this element to the insert list + *p_previous = s->next; + s->next = *p_insert_index; + *p_insert_index = s; + // increment insert list + p_insert_index = &s->next; + break; // iterate to next server pack + } + p_previous = &s->next; + } + } +} + +/* +================ +FS_Startup +================ +*/ +void FS_Startup( const char *gameName ) { + const char *homePath; +#ifdef USE_CD_KEY + cvar_t *fs; +#endif // USE_CD_KEY + + Com_Printf( "----- FS_Startup -----\n" ); + + fs_debug = Cvar_Get( "fs_debug", "0", 0 ); + fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT ); + fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT ); + fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT ); + fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT ); + homePath = Sys_DefaultHomePath(); + if (!homePath || !homePath[0]) { + homePath = fs_basepath->string; + } + fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT ); + fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT ); + + fs_dirbeforepak = Cvar_Get("fs_dirbeforepak", "0", CVAR_INIT); + + // add search path elements in reverse priority order + if (fs_cdpath->string[0]) { + FS_AddGameDirectory( fs_cdpath->string, gameName ); + } + if (fs_basepath->string[0]) { + FS_AddGameDirectory( fs_basepath->string, gameName ); + } + // fs_homepath is somewhat particular to *nix systems, only add if relevant + // NOTE: same filtering below for mods and basegame + if (fs_basepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { + FS_AddGameDirectory ( fs_homepath->string, gameName ); + } + + // check for additional base game so mods can be based upon other mods + if ( fs_basegame->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_basegame->string, gameName ) ) { + if (fs_cdpath->string[0]) { + FS_AddGameDirectory(fs_cdpath->string, fs_basegame->string); + } + if (fs_basepath->string[0]) { + FS_AddGameDirectory(fs_basepath->string, fs_basegame->string); + } + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { + FS_AddGameDirectory(fs_homepath->string, fs_basegame->string); + } + } + + // check for additional game folder for mods + if ( fs_gamedirvar->string[0] && !Q_stricmp( gameName, BASEGAME ) && Q_stricmp( fs_gamedirvar->string, gameName ) ) { + if (fs_cdpath->string[0]) { + FS_AddGameDirectory(fs_cdpath->string, fs_gamedirvar->string); + } + if (fs_basepath->string[0]) { + FS_AddGameDirectory(fs_basepath->string, fs_gamedirvar->string); + } + if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { + FS_AddGameDirectory(fs_homepath->string, fs_gamedirvar->string); + } + } + +#ifdef USE_CD_KEY + Com_ReadCDKey( "base" ); + fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + if (fs && fs->string[0] != 0) { + Com_AppendCDKey( fs->string ); + } +#endif // USE_CD_KEY + + // add our commands + Cmd_AddCommand ("path", FS_Path_f); + Cmd_AddCommand ("dir", FS_Dir_f ); + Cmd_AddCommand ("fdir", FS_NewDir_f ); + Cmd_AddCommand ("touchFile", FS_TouchFile_f ); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506 + // reorder the pure pk3 files according to server order + FS_ReorderPurePaks(); + + // print the current search paths + FS_Path_f(); + + fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified + + Com_Printf( "----------------------\n" ); + +#ifdef FS_MISSING + if (missingFiles == NULL) { + missingFiles = fopen( "\\missing.txt", "ab" ); + } +#endif + Com_Printf( "%d files in pk3 files\n", fs_packFiles ); +} + + +/* +=================== +FS_SetRestrictions + +Looks for product keys and restricts media add on ability +if the full version is not found +=================== +*/ +void FS_SetRestrictions( void ) { + searchpath_t *path; + +#ifndef PRE_RELEASE_DEMO + char *productId; + + // if fs_restrict is set, don't even look for the id file, + // which allows the demo release to be tested even if + // the full game is present + if ( !fs_restrict->integer ) { + // look for the full game id + FS_ReadFile( "productid.txt", (void **)&productId ); + if ( productId ) { + // check against the hardcoded string + int seed, i; + + seed = 102270; + for ( i = 0 ; i < sizeof( fs_scrambledProductId ) ; i++ ) { + if ( ( fs_scrambledProductId[i] ^ (seed&255) ) != productId[i] ) { + break; + } + seed = (69069 * seed + 1); + } + + FS_FreeFile( productId ); + + if ( i == sizeof( fs_scrambledProductId ) ) { + return; // no restrictions + } + Com_Error( ERR_FATAL, "Invalid product identification" ); + } + } +#endif + Cvar_Set( "fs_restrict", "1" ); + + Com_Printf( "\nRunning in restricted demo mode.\n\n" ); + + // restart the filesystem with just the demo directory + FS_Shutdown(qfalse); + FS_Startup( DEMOGAME ); + + // make sure that the pak file has the header checksum we expect + for ( path = fs_searchpaths ; path ; path = path->next ) { + if ( path->pack ) { + // a tiny attempt to keep the checksum from being scannable from the exe + if ( (path->pack->checksum ^ 0x02261994u) != (DEMO_PAK_CHECKSUM ^ 0x02261994u) ) { + Com_Error( ERR_FATAL, "Corrupted pak0.pk3: %u", path->pack->checksum ); + } + } + } +} + +/* +===================== +FS_GamePureChecksum + +Returns the checksum of the pk3 from which the server loaded the qagame.qvm +===================== +*/ +const char *FS_GamePureChecksum( void ) { + static char info[MAX_STRING_TOKENS]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if (search->pack->referenced & FS_QAGAME_REF) { + Com_sprintf(info, sizeof(info), "%d", search->pack->checksum); + } + } + } + + return info; +} + +/* +===================== +FS_LoadedPakChecksums + +Returns a space separated string containing the checksums of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. +===================== +*/ +const char *FS_LoadedPakChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) ); + } + + return info; +} + +/* +===================== +FS_LoadedPakNames + +Returns a space separated string containing the names of all loaded pk3 files. +Servers with sv_pure set will get this string and pass it to clients. +===================== +*/ +const char *FS_LoadedPakNames( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + if (*info) { + Q_strcat(info, sizeof( info ), " " ); + } + Q_strcat( info, sizeof( info ), search->pack->pakBasename ); + } + + return info; +} + +/* +===================== +FS_LoadedPakPureChecksums + +Returns a space separated string containing the pure checksums of all loaded pk3 files. +Servers with sv_pure use these checksums to compare with the checksums the clients send +back to the server. +===================== +*/ +const char *FS_LoadedPakPureChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( !search->pack ) { + continue; + } + + Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) ); + } + + return info; +} + +/* +===================== +FS_ReferencedPakChecksums + +Returns a space separated string containing the checksums of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded. +===================== +*/ +const char *FS_ReferencedPakChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { + Q_strcat( info, sizeof( info ), va("%i ", search->pack->checksum ) ); + } + } + } + + return info; +} + +/* +===================== +FS_ReferencedPakPureChecksums + +Returns a space separated string containing the pure checksums of all referenced pk3 files. +Servers with sv_pure set will get this string back from clients for pure validation + +The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..." +===================== +*/ +const char *FS_ReferencedPakPureChecksums( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + int nFlags, numPaks, checksum; + + info[0] = 0; + + checksum = fs_checksumFeed; + numPaks = 0; + for (nFlags = FS_CGAME_REF; nFlags; nFlags = nFlags >> 1) { + if (nFlags & FS_GENERAL_REF) { + // add a delimter between must haves and general refs + //Q_strcat(info, sizeof(info), "@ "); + info[strlen(info)+1] = '\0'; + info[strlen(info)+2] = '\0'; + info[strlen(info)] = '@'; + info[strlen(info)] = ' '; + } + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file and has it been referenced based on flag? + if ( search->pack && (search->pack->referenced & nFlags)) { + Q_strcat( info, sizeof( info ), va("%i ", search->pack->pure_checksum ) ); + if (nFlags & (FS_CGAME_REF | FS_UI_REF)) { + break; + } + checksum ^= search->pack->pure_checksum; + numPaks++; + } + } + if (fs_fakeChkSum != 0) { + // only added if a non-pure file is referenced + Q_strcat( info, sizeof( info ), va("%i ", fs_fakeChkSum ) ); + } + } + // last checksum is the encoded number of referenced pk3s + checksum ^= numPaks; + Q_strcat( info, sizeof( info ), va("%i ", checksum ) ); + + return info; +} + +/* +===================== +FS_ReferencedPakNames + +Returns a space separated string containing the names of all referenced pk3 files. +The server will send this to the clients so they can check which files should be auto-downloaded. +===================== +*/ +const char *FS_ReferencedPakNames( void ) { + static char info[BIG_INFO_STRING]; + searchpath_t *search; + + info[0] = 0; + + // we want to return ALL pk3's from the fs_game path + // and referenced one's from base + for ( search = fs_searchpaths ; search ; search = search->next ) { + // is the element a pak file? + if ( search->pack ) { + if (*info) { + Q_strcat(info, sizeof( info ), " " ); + } + if (search->pack->referenced || Q_stricmpn(search->pack->pakGamename, BASEGAME, strlen(BASEGAME))) { + Q_strcat( info, sizeof( info ), search->pack->pakGamename ); + Q_strcat( info, sizeof( info ), "/" ); + Q_strcat( info, sizeof( info ), search->pack->pakBasename ); + } + } + } + + return info; +} + +/* +===================== +FS_ClearPakReferences +===================== +*/ +void FS_ClearPakReferences( int flags ) { + searchpath_t *search; + + if ( !flags ) { + flags = -1; + } + for ( search = fs_searchpaths; search; search = search->next ) { + // is the element a pak file and has it been referenced? + if ( search->pack ) { + search->pack->referenced &= ~flags; + } + } +} + + +/* +===================== +FS_PureServerSetLoadedPaks + +If the string is empty, all data sources will be allowed. +If not empty, only pk3 files that match one of the space +separated checksums will be checked for files, with the +exception of .cfg and .dat files. +===================== +*/ +void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ) { + int i, c, d; + + Cmd_TokenizeString( pakSums ); + + c = Cmd_Argc(); + if ( c > MAX_SEARCH_PATHS ) { + c = MAX_SEARCH_PATHS; + } + + fs_numServerPaks = c; + + for ( i = 0 ; i < c ; i++ ) { + fs_serverPaks[i] = atoi( Cmd_Argv( i ) ); + } + + if (fs_numServerPaks) { + Com_DPrintf( "Connected to a pure server.\n" ); + } + else + { + if (fs_reordered) + { + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 + // force a restart to make sure the search order will be correct + Com_DPrintf( "FS search reorder is required\n" ); + FS_Restart(fs_checksumFeed); + return; + } + } + + for ( i = 0 ; i < c ; i++ ) { + if (fs_serverPakNames[i]) { + Z_Free(fs_serverPakNames[i]); + } + fs_serverPakNames[i] = NULL; + } + if ( pakNames && *pakNames ) { + Cmd_TokenizeString( pakNames ); + + d = Cmd_Argc(); + if ( d > MAX_SEARCH_PATHS ) { + d = MAX_SEARCH_PATHS; + } + + for ( i = 0 ; i < d ; i++ ) { + fs_serverPakNames[i] = CopyString( Cmd_Argv( i ) ); + } + } +} + +/* +===================== +FS_PureServerSetReferencedPaks + +The checksums and names of the pk3 files referenced at the server +are sent to the client and stored here. The client will use these +checksums to see if any pk3 files need to be auto-downloaded. +===================== +*/ +void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) { + int i, c, d; + + Cmd_TokenizeString( pakSums ); + + c = Cmd_Argc(); + if ( c > MAX_SEARCH_PATHS ) { + c = MAX_SEARCH_PATHS; + } + + fs_numServerReferencedPaks = c; + + for ( i = 0 ; i < c ; i++ ) { + fs_serverReferencedPaks[i] = atoi( Cmd_Argv( i ) ); + } + + for ( i = 0 ; i < c ; i++ ) { + if (fs_serverReferencedPakNames[i]) { + Z_Free(fs_serverReferencedPakNames[i]); + } + fs_serverReferencedPakNames[i] = NULL; + } + if ( pakNames && *pakNames ) { + Cmd_TokenizeString( pakNames ); + + d = Cmd_Argc(); + if ( d > MAX_SEARCH_PATHS ) { + d = MAX_SEARCH_PATHS; + } + + for ( i = 0 ; i < d ; i++ ) { + fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) ); + } + } +} + +/* +================ +FS_Restart +================ +*/ +void FS_Restart( int checksumFeed ) { + + // free anything we currently have loaded + FS_Shutdown(qfalse); + + // set the checksum feed + fs_checksumFeed = checksumFeed; + + // clear pak references + FS_ClearPakReferences(0); + + // try to start up normally + FS_Startup( BASEGAME ); + + // see if we are going to allow add-ons + FS_SetRestrictions(); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if ( FS_ReadFile( "mpdefault.cfg", NULL ) <= 0 ) { + // this might happen when connecting to a pure server not using BASEGAME/pak0.pk3 + // (for instance a TA demo server) + if (lastValidBase[0]) { + FS_PureServerSetLoadedPaks("", ""); + Cvar_Set("fs_basepath", lastValidBase); + Cvar_Set("fs_gamedirvar", lastValidGame); + lastValidBase[0] = '\0'; + lastValidGame[0] = '\0'; + Cvar_Set( "fs_restrict", "0" ); + FS_Restart(checksumFeed); + Com_Error( ERR_DROP, "Invalid game folder\n" ); + return; + } + Com_Error( ERR_FATAL, "Couldn't load mpdefault.cfg" ); + } + + // bk010116 - new check before safeMode + if ( Q_stricmp(fs_gamedirvar->string, lastValidGame) ) { + // skip the jampconfig.cfg if "safe" is on the command line + if ( !Com_SafeMode() ) { +#ifdef DEDICATED + Cbuf_AddText ("exec jampserver.cfg\n"); +#else + Cbuf_AddText ("exec jampconfig.cfg\n"); +#endif + } + } + + Q_strncpyz(lastValidBase, fs_basepath->string, sizeof(lastValidBase)); + Q_strncpyz(lastValidGame, fs_gamedirvar->string, sizeof(lastValidGame)); + +} + +/* +================= +FS_ConditionalRestart +restart if necessary +================= +*/ +qboolean FS_ConditionalRestart( int checksumFeed ) { + if( fs_gamedirvar->modified || checksumFeed != fs_checksumFeed ) { + FS_Restart( checksumFeed ); + return qtrue; + } + return qfalse; +} + +/* +======================================================================================== + +Handle based file calls for virtual machines + +======================================================================================== +*/ + +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + int r; + qboolean sync; + + sync = qfalse; + + switch( mode ) { + case FS_READ: + r = FS_FOpenFileRead( qpath, f, qtrue ); + break; + case FS_WRITE: + *f = FS_FOpenFileWrite( qpath ); + r = 0; + if (*f == 0) { + r = -1; + } + break; + case FS_APPEND_SYNC: + sync = qtrue; + case FS_APPEND: + *f = FS_FOpenFileAppend( qpath ); + r = 0; + if (*f == 0) { + r = -1; + } + break; + default: + Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" ); + return -1; + } + + if (!f) { + return r; + } + + if ( *f ) { + if (fsh[*f].zipFile == qtrue) { + fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z); + } else { + fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o); + } + fsh[*f].fileSize = r; + fsh[*f].streamed = qfalse; + + if (mode == FS_READ) { + Sys_BeginStreamedFile( *f, 0x4000 ); + fsh[*f].streamed = qtrue; + } + } + fsh[*f].handleSync = sync; + + return r; +} + +int FS_FTell( fileHandle_t f ) { + int pos; + if (fsh[f].zipFile == qtrue) { + pos = unztell(fsh[f].handleFiles.file.z); + } else { + pos = ftell(fsh[f].handleFiles.file.o); + } + return pos; +} + +void FS_Flush( fileHandle_t f ) { + fflush(fsh[f].handleFiles.file.o); +} diff --git a/codemp/qcommon/fixedmap.h b/codemp/qcommon/fixedmap.h new file mode 100644 index 0000000..0e0957c --- /dev/null +++ b/codemp/qcommon/fixedmap.h @@ -0,0 +1,167 @@ +#ifndef __FIXEDMAP_H +#define __FIXEDMAP_H + + +/* + An STL map-like container. Quickly thrown together to replace STL maps + in specific instances. Many gotchas. Use with caution. +*/ + + +#include + + +template < class T, class U > +class VVFixedMap +{ +private: + struct Data { + T data; + U key; + }; + + Data *items; + unsigned int numItems; + unsigned int maxItems; + + VVFixedMap(void) {} + +public: + VVFixedMap(unsigned int maxItems) + { + items = new Data[maxItems]; + numItems = 0; + this->maxItems = maxItems; + } + + + ~VVFixedMap(void) + { + items -= ( maxItems - numItems ); + delete [] items; + numItems = 0; + } + + + bool Insert(const T &newItem, const U &key) + { + Data *storage = NULL; + + //Check for fullness. + if(numItems >= maxItems) { + assert( 0 ); + return false; + } + + //Check for reuse. + if(!FindUnsorted(key, storage)) { + storage = items + numItems; + numItems++; + } + + storage->data = newItem; + storage->key = key; + + return true; + } + + // Faster version of Insert(), but it doesn't check for dupes. Used + // by the filecode cache (when we know the data we're inserting is good). + bool InsertUnsafe(const T &newItem, const U &key) + { + //Check for fullness. + if(numItems >= maxItems) { + return false; + } + + Data *storage = items + numItems; + numItems++; + + storage->data = newItem; + storage->key = key; + + return true; + } + + + void Sort(void) + { + qsort(items, numItems, sizeof(Data), + VVFixedMap< T, U >::FixedMapSorter); + } + + + //Binary search, items must have been sorted! + T *Find(const U &key) + { + int i; + int high; + int low; + + for(low = -1, high = numItems; high - low > 1; ) { + i = (high + low) / 2; + if(key < items[i].key) { + high = i; + } else if(key > items[i].key) { + low = i; + } else { + return &items[i].data; + } + } + + if(items[i+1].key == key) { + return &items[i+1].data; + } else if(items[i-1].key == key) { + return &items[i-1].data; + } + + return NULL; + } + + + //Slower, but don't need to call sort first. + T *FindUnsorted(const U &key, Data *&storage) + { + int i; + + for(i=0; ikey > ((Data*)b)->key) { + return 1; + } else if(((Data*)a)->key == ((Data*)b)->key) { + return 0; + } else { + return -1; + } + } +}; + + +#endif diff --git a/codemp/qcommon/game_version.h b/codemp/qcommon/game_version.h new file mode 100644 index 0000000..c0d12b3 --- /dev/null +++ b/codemp/qcommon/game_version.h @@ -0,0 +1,14 @@ +// Copyright (C) 2000-2002 Raven Software, Inc. +// +#include "../win32/AutoVersion.h" + +// Current version of the multi player game +#ifdef _DEBUG + #define Q3_VERSION "(debug)JAmp: v"VERSION_STRING_DOTTED +#elif defined FINAL_BUILD + #define Q3_VERSION "JAmp: v"VERSION_STRING_DOTTED +#else + #define Q3_VERSION "(internal)JAmp: v"VERSION_STRING_DOTTED +#endif + +//end diff --git a/codemp/qcommon/genericparser2.cpp b/codemp/qcommon/genericparser2.cpp new file mode 100644 index 0000000..f5e00a9 --- /dev/null +++ b/codemp/qcommon/genericparser2.cpp @@ -0,0 +1,1203 @@ +// this include must remain at the top of every CPP file + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#if !defined(GENERICPARSER2_H_INC) + #include "GenericParser2.h" +#endif + +#define _EXE + +#define MAX_TOKEN_SIZE 1024 +static char token[MAX_TOKEN_SIZE]; + +static char *GetToken(char **text, bool allowLineBreaks, bool readUntilEOL = false) +{ + char *pointer = *text; + int length = 0; + int c = 0; + bool foundLineBreak; + + token[0] = 0; + if (!pointer) + { + return token; + } + + while(1) + { + foundLineBreak = false; + while(1) + { + c = *pointer; + if (c > ' ') + { + break; + } + if (!c) + { + *text = 0; + return token; + } + if (c == '\n') + { + foundLineBreak = true; + } + pointer++; + } + if (foundLineBreak && !allowLineBreaks) + { + *text = pointer; + return token; + } + + c = *pointer; + + // skip single line comment + if (c == '/' && pointer[1] == '/') + { + pointer += 2; + while (*pointer && *pointer != '\n') + { + pointer++; + } + } + // skip multi line comments + else if (c == '/' && pointer[1] == '*') + { + pointer += 2; + while (*pointer && (*pointer != '*' || pointer[1] != '/')) + { + pointer++; + } + if (*pointer) + { + pointer += 2; + } + } + else + { // found the start of a token + break; + } + } + + if (c == '\"') + { // handle a string + pointer++; + while (1) + { + c = *pointer++; + if (c == '\"') + { +// token[length++] = c; + break; + } + else if (!c) + { + break; + } + else if (length < MAX_TOKEN_SIZE) + { + token[length++] = c; + } + } + } + else if (readUntilEOL) + { + // absorb all characters until EOL + while(c != '\n' && c != '\r') + { + if (c == '/' && ((*(pointer+1)) == '/' || (*(pointer+1)) == '*')) + { + break; + } + + if (length < MAX_TOKEN_SIZE) + { + token[length++] = c; + } + pointer++; + c = *pointer; + } + // remove trailing white space + while(length && token[length-1] < ' ') + { + length--; + } + } + else + { + while(c > ' ') + { + if (length < MAX_TOKEN_SIZE) + { + token[length++] = c; + } + pointer++; + c = *pointer; + } + } + + if (token[0] == '\"') + { // remove start quote + length--; + memmove(token, token+1, length); + + if (length && token[length-1] == '\"') + { // remove end quote + length--; + } + } + + if (length >= MAX_TOKEN_SIZE) + { + length = 0; + } + token[length] = 0; + *text = (char *)pointer; + + return token; +} + + + + +CTextPool::CTextPool(int initSize) : + mNext(0), + mSize(initSize), + mUsed(0) +{ +#ifdef _EXE +// mPool = (char *)Z_Malloc(mSize, TAG_GP2); + mPool = (char *)Z_Malloc(mSize, TAG_TEXTPOOL, qtrue); +#else + mPool = (char *)trap_Z_Malloc(mSize, TAG_GP2); +#endif +} + +CTextPool::~CTextPool(void) +{ +#ifdef _EXE + Z_Free(mPool); +#else + trap_Z_Free(mPool); +#endif +} + +char *CTextPool::AllocText(char *text, bool addNULL, CTextPool **poolPtr) +{ + int length = strlen(text) + (addNULL ? 1 : 0); + + if (mUsed + length + 1> mSize) + { // extra 1 to put a null on the end + if (poolPtr) + { + (*poolPtr)->SetNext(new CTextPool(mSize)); + *poolPtr = (*poolPtr)->GetNext(); + + return (*poolPtr)->AllocText(text, addNULL); + } + + return 0; + } + + strcpy(mPool + mUsed, text); + mUsed += length; + mPool[mUsed] = 0; + + return mPool + mUsed - length; +} + +void CleanTextPool(CTextPool *pool) +{ + CTextPool *next; + + while(pool) + { + next = pool->GetNext(); + delete pool; + pool = next; + } +} + + + + + + + +CGPObject::CGPObject(const char *initName) : + mName(initName), + mNext(0), + mInOrderNext(0), + mInOrderPrevious(0) +{ +} + +bool CGPObject::WriteText(CTextPool **textPool, const char *text) +{ + if (strchr(text, ' ') || !text[0]) + { + (*textPool)->AllocText("\"", false, textPool); + (*textPool)->AllocText((char *)text, false, textPool); + (*textPool)->AllocText("\"", false, textPool); + } + else + { + (*textPool)->AllocText((char *)text, false, textPool); + } + + return true; +} + + + + + + + + + + + + + + +CGPValue::CGPValue(const char *initName, const char *initValue) : + CGPObject(initName), + mList(0) +{ + if (initValue) + { + AddValue(initValue); + } +} + +CGPValue::~CGPValue(void) +{ + CGPObject *next; + + while(mList) + { + next = mList->GetNext(); + delete mList; + mList = next; + } +} + +CGPValue *CGPValue::Duplicate(CTextPool **textPool) +{ + CGPValue *newValue; + CGPObject *iterator; + char *name; + + if (textPool) + { + name = (*textPool)->AllocText((char *)mName, true, textPool); + } + else + { + name = (char *)mName; + } + + newValue = new CGPValue(name); + iterator = mList; + while(iterator) + { + if (textPool) + { + name = (*textPool)->AllocText((char *)iterator->GetName(), true, textPool); + } + else + { + name = (char *)iterator->GetName(); + } + newValue->AddValue(name); + iterator = iterator->GetNext(); + } + + return newValue; +} + +bool CGPValue::IsList(void) +{ + if (!mList || !mList->GetNext()) + { + return false; + } + + return true; +} + +const char *CGPValue::GetTopValue(void) +{ + if (mList) + { + return mList->GetName(); + } + + return 0; +} + +void CGPValue::AddValue(const char *newValue, CTextPool **textPool) +{ + if (textPool) + { + newValue = (*textPool)->AllocText((char *)newValue, true, textPool); + } + + if (mList == 0) + { + mList = new CGPObject(newValue); + mList->SetInOrderNext(mList); + } + else + { + mList->GetInOrderNext()->SetNext(new CGPObject(newValue)); + mList->SetInOrderNext(mList->GetInOrderNext()->GetNext()); + } +} + +bool CGPValue::Parse(char **dataPtr, CTextPool **textPool) +{ + char *token; + char *value; + + while(1) + { + token = GetToken(dataPtr, true, true); + + if (!token[0]) + { // end of data - error! + return false; + } + else if (Q_stricmp(token, "]") == 0) + { // ending brace for this list + break; + } + + value = (*textPool)->AllocText(token, true, textPool); + AddValue(value); + } + + return true; +} + +bool CGPValue::Write(CTextPool **textPool, int depth) +{ + int i; + CGPObject *next; + + if (!mList) + { + return true; + } + + for(i=0;iAllocText("\t", false, textPool); + } + + WriteText(textPool, mName); + + if (!mList->GetNext()) + { + (*textPool)->AllocText("\t\t", false, textPool); + mList->WriteText(textPool, mList->GetName()); + (*textPool)->AllocText("\r\n", false, textPool); + } + else + { + (*textPool)->AllocText("\r\n", false, textPool); + + for(i=0;iAllocText("\t", false, textPool); + } + (*textPool)->AllocText("[\r\n", false, textPool); + + next = mList; + while(next) + { + for(i=0;iAllocText("\t", false, textPool); + } + mList->WriteText(textPool, next->GetName()); + (*textPool)->AllocText("\r\n", false, textPool); + + next = next->GetNext(); + } + + for(i=0;iAllocText("\t", false, textPool); + } + (*textPool)->AllocText("]\r\n", false, textPool); + } + + return true; +} + + + + + + + + + + + + + + + + +CGPGroup::CGPGroup(const char *initName, CGPGroup *initParent) : + CGPObject(initName), + mPairs(0), + mInOrderPairs(0), + mCurrentPair(0), + mSubGroups(0), + mInOrderSubGroups(0), + mCurrentSubGroup(0), + mParent(initParent), + mWriteable(false) +{ +} + +CGPGroup::~CGPGroup(void) +{ + Clean(); +} + +int CGPGroup::GetNumSubGroups(void) +{ + int count; + CGPGroup *group; + + count = 0; + group = mSubGroups; + do + { + count++; + group = (CGPGroup *)group->GetNext(); + } + while(group); + + return(count); +} + +int CGPGroup::GetNumPairs(void) +{ + int count; + CGPValue *pair; + + count = 0; + pair = mPairs; + do + { + count++; + pair = (CGPValue *)pair->GetNext(); + } + while(pair); + + return(count); +} + +void CGPGroup::Clean(void) +{ + while(mPairs) + { + mCurrentPair = (CGPValue *)mPairs->GetNext(); + delete mPairs; + mPairs = mCurrentPair; + } + + while(mSubGroups) + { + mCurrentSubGroup = (CGPGroup *)mSubGroups->GetNext(); + delete mSubGroups; + mSubGroups = mCurrentSubGroup; + } + + mPairs = mInOrderPairs = mCurrentPair = 0; + mSubGroups = mInOrderSubGroups = mCurrentSubGroup = 0; + mParent = 0; + mWriteable = false; +} + +CGPGroup *CGPGroup::Duplicate(CTextPool **textPool, CGPGroup *initParent) +{ + CGPGroup *newGroup, *subSub, *newSub; + CGPValue *newPair, *subPair; + char *name; + + if (textPool) + { + name = (*textPool)->AllocText((char *)mName, true, textPool); + } + else + { + name = (char *)mName; + } + + newGroup = new CGPGroup(name); + + subSub = mSubGroups; + while(subSub) + { + newSub = subSub->Duplicate(textPool, newGroup); + newGroup->AddGroup(newSub); + + subSub = (CGPGroup *)subSub->GetNext(); + } + + subPair = mPairs; + while(subPair) + { + newPair = subPair->Duplicate(textPool); + newGroup->AddPair(newPair); + + subPair = (CGPValue *)subPair->GetNext(); + } + + return newGroup; +} + +void CGPGroup::SortObject(CGPObject *object, CGPObject **unsortedList, CGPObject **sortedList, + CGPObject **lastObject) +{ + CGPObject *test, *last; + + if (!*unsortedList) + { + *unsortedList = *sortedList = object; + } + else + { + (*lastObject)->SetNext(object); + + test = *sortedList; + last = 0; + while(test) + { + if (Q_stricmp(object->GetName(), test->GetName()) < 0) + { + break; + } + + last = test; + test = test->GetInOrderNext(); + } + + if (test) + { + test->SetInOrderPrevious(object); + object->SetInOrderNext(test); + } + if (last) + { + last->SetInOrderNext(object); + object->SetInOrderPrevious(last); + } + else + { + *sortedList = object; + } + } + + *lastObject = object; +} + +CGPValue *CGPGroup::AddPair(const char *name, const char *value, CTextPool **textPool) +{ + CGPValue *newPair; + + if (textPool) + { + name = (*textPool)->AllocText((char *)name, true, textPool); + if (value) + { + value = (*textPool)->AllocText((char *)value, true, textPool); + } + } + + newPair = new CGPValue(name, value); + + AddPair(newPair); + + return newPair; +} + +void CGPGroup::AddPair(CGPValue *NewPair) +{ + SortObject(NewPair, (CGPObject **)&mPairs, (CGPObject **)&mInOrderPairs, + (CGPObject **)&mCurrentPair); +} + +CGPGroup *CGPGroup::AddGroup(const char *name, CTextPool **textPool) +{ + CGPGroup *newGroup; + + if (textPool) + { + name = (*textPool)->AllocText((char *)name, true, textPool); + } + + newGroup = new CGPGroup(name, this); + + AddGroup(newGroup); + + return newGroup; +} + +void CGPGroup::AddGroup(CGPGroup *NewGroup) +{ + SortObject(NewGroup, (CGPObject **)&mSubGroups, (CGPObject **)&mInOrderSubGroups, + (CGPObject **)&mCurrentSubGroup); +} + +CGPGroup *CGPGroup::FindSubGroup(const char *name) +{ + CGPGroup *group; + + group = mSubGroups; + while(group) + { + if(!stricmp(name, group->GetName())) + { + return(group); + } + group = (CGPGroup *)group->GetNext(); + } + return(NULL); +} + +bool CGPGroup::Parse(char **dataPtr, CTextPool **textPool) +{ + char *token; + char lastToken[MAX_TOKEN_SIZE]; + CGPGroup *newSubGroup; + CGPValue *newPair; + + while(1) + { + token = GetToken(dataPtr, true); + + if (!token[0]) + { // end of data - error! + if (mParent) + { + return false; + } + else + { + break; + } + } + else if (Q_stricmp(token, "}") == 0) + { // ending brace for this group + break; + } + + strcpy(lastToken, token); + + // read ahead to see what we are doing + token = GetToken(dataPtr, true, true); + if (Q_stricmp(token, "{") == 0) + { // new sub group + newSubGroup = AddGroup(lastToken, textPool); + newSubGroup->SetWriteable(mWriteable); + if (!newSubGroup->Parse(dataPtr, textPool)) + { + return false; + } + } + else if (Q_stricmp(token, "[") == 0) + { // new pair list + newPair = AddPair(lastToken, 0, textPool); + if (!newPair->Parse(dataPtr, textPool)) + { + return false; + } + } + else + { // new pair + AddPair(lastToken, token, textPool); + } + } + + return true; +} + +bool CGPGroup::Write(CTextPool **textPool, int depth) +{ + int i; + CGPValue *mPair = mPairs; + CGPGroup *mSubGroup = mSubGroups; + + if (depth >= 0) + { + for(i=0;iAllocText("\t", false, textPool); + } + WriteText(textPool, mName); + (*textPool)->AllocText("\r\n", false, textPool); + + for(i=0;iAllocText("\t", false, textPool); + } + (*textPool)->AllocText("{\r\n", false, textPool); + } + + while(mPair) + { + mPair->Write(textPool, depth+1); + mPair = (CGPValue *)mPair->GetNext(); + } + + while(mSubGroup) + { + mSubGroup->Write(textPool, depth+1); + mSubGroup = (CGPGroup *)mSubGroup->GetNext(); + } + + if (depth >= 0) + { + for(i=0;iAllocText("\t", false, textPool); + } + (*textPool)->AllocText("}\r\n", false, textPool); + } + + return true; +} + +/************************************************************************************************ + * CGPGroup::FindPair + * This function will search for the pair with the specified key name. Multiple keys may be + * searched if you specify "||" inbetween each key name in the string. The first key to be + * found (from left to right) will be returned. + * + * Input + * key: the name of the key(s) to be searched for. + * + * Output / Return + * the group belonging to the first key found or 0 if no group was found. + * + ************************************************************************************************/ +CGPValue *CGPGroup::FindPair(const char *key) +{ + CGPValue *pair; + int length; + const char *pos, *separator, *next; + + pos = key; + while(pos[0]) + { + separator = strstr(pos, "||"); + if (separator) + { + length = separator - pos; + next = separator + 2; + } + else + { + length = strlen(pos); + next = pos + length; + } + + pair = mPairs; + while(pair) + { + if (strlen(pair->GetName()) == length && + Q_stricmpn(pair->GetName(), pos, length) == 0) + { + return pair; + } + + pair = pair->GetNext(); + } + + pos = next; + } + + return 0; +} + +const char *CGPGroup::FindPairValue(const char *key, const char *defaultVal) +{ + CGPValue *pair = FindPair(key); + + if (pair) + { + return pair->GetTopValue(); + } + + return defaultVal; +} + + + + + + + + + + + + + + + +CGenericParser2::CGenericParser2(void) : + mTextPool(0), + mWriteable(false) +{ +} + +CGenericParser2::~CGenericParser2(void) +{ + Clean(); +} + +bool CGenericParser2::Parse(char **dataPtr, bool cleanFirst, bool writeable) +{ + CTextPool *topPool; + +#ifdef _XBOX + // Parsers are temporary structures. They exist mainly at load time. + extern void Z_SetNewDeleteTemporary(bool bTemp); + Z_SetNewDeleteTemporary(true); +#endif + + if (cleanFirst) + { + Clean(); + } + + if (!mTextPool) + { + mTextPool = new CTextPool; + } + + SetWriteable(writeable); + mTopLevel.SetWriteable(writeable); + topPool = mTextPool; + bool ret = mTopLevel.Parse(dataPtr, &topPool); + +#ifdef _XBOX + Z_SetNewDeleteTemporary(false); +#endif + + return ret; +} + +void CGenericParser2::Clean(void) +{ + mTopLevel.Clean(); + + CleanTextPool(mTextPool); + mTextPool = 0; +} + +bool CGenericParser2::Write(CTextPool *textPool) +{ + return mTopLevel.Write(&textPool, -1); +} + + + + + + + + + +// The following groups of routines are used for a C interface into GP2. +// C++ users should just use the objects as normally and not call these routines below +// +// CGenericParser2 (void *) routines +TGenericParser2 GP_Parse(char **dataPtr, bool cleanFirst, bool writeable) +{ + CGenericParser2 *parse; + + parse = new CGenericParser2; + if (parse->Parse(dataPtr, cleanFirst, writeable)) + { + return parse; + } + + delete parse; + return 0; +} + +void GP_Clean(TGenericParser2 GP2) +{ + if (!GP2) + { + return; + } + + ((CGenericParser2 *)GP2)->Clean(); +} + +void GP_Delete(TGenericParser2 *GP2) +{ + if (!GP2 || !(*GP2)) + { + return; + } + + delete ((CGenericParser2 *)(*GP2)); + (*GP2) = 0; +} + +TGPGroup GP_GetBaseParseGroup(TGenericParser2 GP2) +{ + if (!GP2) + { + return 0; + } + + return ((CGenericParser2 *)GP2)->GetBaseParseGroup(); +} + + + + +// CGPGroup (void *) routines +const char *GPG_GetName(TGPGroup GPG) +{ + if (!GPG) + { + return ""; + } + + return ((CGPGroup *)GPG)->GetName(); +} + +bool GPG_GetName(TGPGroup GPG, char *Value) +{ + if (!GPG) + { + Value[0] = 0; + return false; + } + + strcpy(Value, ((CGPGroup *)GPG)->GetName()); + return true; +} + +TGPGroup GPG_GetNext(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetNext(); +} + +TGPGroup GPG_GetInOrderNext(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetInOrderNext(); +} + +TGPGroup GPG_GetInOrderPrevious(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetInOrderPrevious(); +} + +TGPGroup GPG_GetPairs(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetPairs(); +} + +TGPGroup GPG_GetInOrderPairs(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetInOrderPairs(); +} + +TGPGroup GPG_GetSubGroups(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetSubGroups(); +} + +TGPGroup GPG_GetInOrderSubGroups(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetInOrderSubGroups(); +} + +TGPGroup GPG_FindSubGroup(TGPGroup GPG, const char *name) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->FindSubGroup(name); +} + +TGPValue GPG_FindPair(TGPGroup GPG, const char *key) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->FindPair(key); +} + +const char *GPG_FindPairValue(TGPGroup GPG, const char *key, const char *defaultVal) +{ + if (!GPG) + { + return defaultVal; + } + + return ((CGPGroup *)GPG)->FindPairValue(key, defaultVal); +} + +bool GPG_FindPairValue(TGPGroup GPG, const char *key, const char *defaultVal, char *Value) +{ + strcpy(Value, GPG_FindPairValue(GPG, key, defaultVal)); + + return true; +} + + + + +// CGPValue (void *) routines +const char *GPV_GetName(TGPValue GPV) +{ + if (!GPV) + { + return ""; + } + + return ((CGPValue *)GPV)->GetName(); +} + +bool GPV_GetName(TGPValue GPV, char *Value) +{ + if (!GPV) + { + Value[0] = 0; + return false; + } + + strcpy(Value, ((CGPValue *)GPV)->GetName()); + return true; +} + +TGPValue GPV_GetNext(TGPValue GPV) +{ + if (!GPV) + { + return 0; + } + + return ((CGPValue *)GPV)->GetNext(); +} + +TGPValue GPV_GetInOrderNext(TGPValue GPV) +{ + if (!GPV) + { + return 0; + } + + return ((CGPValue *)GPV)->GetInOrderNext(); +} + +TGPValue GPV_GetInOrderPrevious(TGPValue GPV) +{ + if (!GPV) + { + return 0; + } + + return ((CGPValue *)GPV)->GetInOrderPrevious(); +} + +bool GPV_IsList(TGPValue GPV) +{ + if (!GPV) + { + return 0; + } + + return ((CGPValue *)GPV)->IsList(); +} + +const char *GPV_GetTopValue(TGPValue GPV) +{ + if (!GPV) + { + return ""; + } + + return ((CGPValue *)GPV)->GetTopValue(); +} + +bool GPV_GetTopValue(TGPValue GPV, char *Value) +{ + if (!GPV) + { + Value[0] = 0; + return false; + } + + strcpy(Value, ((CGPValue *)GPV)->GetTopValue()); + + return true; +} + +TGPValue GPV_GetList(TGPValue GPV) +{ + if (!GPV) + { + return 0; + } + + return ((CGPValue *)GPV)->GetList(); +} diff --git a/codemp/qcommon/genericparser2.h b/codemp/qcommon/genericparser2.h new file mode 100644 index 0000000..fa16bdc --- /dev/null +++ b/codemp/qcommon/genericparser2.h @@ -0,0 +1,204 @@ +#pragma once +#if !defined(GENERICPARSER2_H_INC) +#define GENERICPARSER2_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including GenericParser2.h") +#endif + +#include "disablewarnings.h" + +#ifdef USE_LOCAL_GENERICPARSER +#include +#include +#include + +#define trap_Z_Malloc(x, y) malloc(x) +#define trap_Z_Free(x) free(x) + +#endif + +class CTextPool; +class CGPObject; + +class CTextPool +{ +private: + char *mPool; + CTextPool *mNext; + int mSize, mUsed; + +public: + CTextPool(int initSize = 10240); + ~CTextPool(void); + + CTextPool *GetNext(void) { return mNext; } + void SetNext(CTextPool *which) { mNext = which; } + char *GetPool(void) { return mPool; } + int GetUsed(void) { return mUsed; } + + char *AllocText(char *text, bool addNULL = true, CTextPool **poolPtr = 0); +}; + +void CleanTextPool(CTextPool *pool); + +class CGPObject +{ +protected: + const char *mName; + CGPObject *mNext, *mInOrderNext, *mInOrderPrevious; + +public: + CGPObject(const char *initName); + + const char *GetName(void) { return mName; } + + CGPObject *GetNext(void) { return mNext; } + void SetNext(CGPObject *which) { mNext = which; } + CGPObject *GetInOrderNext(void) { return mInOrderNext; } + void SetInOrderNext(CGPObject *which) { mInOrderNext = which; } + CGPObject *GetInOrderPrevious(void) { return mInOrderPrevious; } + void SetInOrderPrevious(CGPObject *which) { mInOrderPrevious = which; } + + bool WriteText(CTextPool **textPool, const char *text); +}; + + + +class CGPValue : public CGPObject +{ +private: + CGPObject *mList; + +public: + CGPValue(const char *initName, const char *initValue = 0); + ~CGPValue(void); + + CGPValue *GetNext(void) { return (CGPValue *)mNext; } + + CGPValue *Duplicate(CTextPool **textPool = 0); + + bool IsList(void); + const char *GetTopValue(void); + CGPObject *GetList(void) { return mList; } + void AddValue(const char *newValue, CTextPool **textPool = 0); + + bool Parse(char **dataPtr, CTextPool **textPool); + + bool Write(CTextPool **textPool, int depth); +}; + + + +class CGPGroup : public CGPObject +{ +private: + CGPValue *mPairs, *mInOrderPairs; + CGPValue *mCurrentPair; + CGPGroup *mSubGroups, *mInOrderSubGroups; + CGPGroup *mCurrentSubGroup; + CGPGroup *mParent; + bool mWriteable; + + void SortObject(CGPObject *object, CGPObject **unsortedList, CGPObject **sortedList, + CGPObject **lastObject); + +public: + CGPGroup(const char *initName = "Top Level", CGPGroup *initParent = 0); + ~CGPGroup(void); + + CGPGroup *GetParent(void) { return mParent; } + CGPGroup *GetNext(void) { return (CGPGroup *)mNext; } + int GetNumSubGroups(void); + int GetNumPairs(void); + + void Clean(void); + CGPGroup *Duplicate(CTextPool **textPool = 0, CGPGroup *initParent = 0); + + void SetWriteable(const bool writeable) { mWriteable = writeable; } + CGPValue *GetPairs(void) { return mPairs; } + CGPValue *GetInOrderPairs(void) { return mInOrderPairs; } + CGPGroup *GetSubGroups(void) { return mSubGroups; } + CGPGroup *GetInOrderSubGroups(void) { return mInOrderSubGroups; } + + CGPValue *AddPair(const char *name, const char *value, CTextPool **textPool = 0); + void AddPair(CGPValue *NewPair); + CGPGroup *AddGroup(const char *name, CTextPool **textPool = 0); + void AddGroup(CGPGroup *NewGroup); + CGPGroup *FindSubGroup(const char *name); + bool Parse(char **dataPtr, CTextPool **textPool); + bool Write(CTextPool **textPool, int depth); + + CGPValue *FindPair(const char *key); + const char *FindPairValue(const char *key, const char *defaultVal = 0); +}; + +class CGenericParser2 +{ +private: + CGPGroup mTopLevel; + CTextPool *mTextPool; + bool mWriteable; + +public: + CGenericParser2(void); + ~CGenericParser2(void); + + void SetWriteable(const bool writeable) { mWriteable = writeable; } + CGPGroup *GetBaseParseGroup(void) { return &mTopLevel; } + + bool Parse(char **dataPtr, bool cleanFirst = true, bool writeable = false); + bool Parse(char *dataPtr, bool cleanFirst = true, bool writeable = false) + { + return Parse(&dataPtr, cleanFirst, writeable); + } + void Clean(void); + + bool Write(CTextPool *textPool); +}; + + + +// The following groups of routines are used for a C interface into GP2. +// C++ users should just use the objects as normally and not call these routines below +// + +typedef void *TGenericParser2; +typedef void *TGPGroup; +typedef void *TGPValue; + +// CGenericParser2 (void *) routines +TGenericParser2 GP_Parse(char **dataPtr, bool cleanFirst, bool writeable); +void GP_Clean(TGenericParser2 GP2); +void GP_Delete(TGenericParser2 *GP2); +TGPGroup GP_GetBaseParseGroup(TGenericParser2 GP2); + +// CGPGroup (void *) routines +const char *GPG_GetName(TGPGroup GPG); +bool GPG_GetName(TGPGroup GPG, char *Value); +TGPGroup GPG_GetNext(TGPGroup GPG); +TGPGroup GPG_GetInOrderNext(TGPGroup GPG); +TGPGroup GPG_GetInOrderPrevious(TGPGroup GPG); +TGPGroup GPG_GetPairs(TGPGroup GPG); +TGPGroup GPG_GetInOrderPairs(TGPGroup GPG); +TGPGroup GPG_GetSubGroups(TGPGroup GPG); +TGPGroup GPG_GetInOrderSubGroups(TGPGroup GPG); +TGPGroup GPG_FindSubGroup(TGPGroup GPG, const char *name); +TGPValue GPG_FindPair(TGPGroup GPG, const char *key); +const char *GPG_FindPairValue(TGPGroup GPG, const char *key, const char *defaultVal); +bool GPG_FindPairValue(TGPGroup GPG, const char *key, const char *defaultVal, char *Value); + +// CGPValue (void *) routines +const char *GPV_GetName(TGPValue GPV); +bool GPV_GetName(TGPValue GPV, char *Value); +TGPValue GPV_GetNext(TGPValue GPV); +TGPValue GPV_GetInOrderNext(TGPValue GPV); +TGPValue GPV_GetInOrderPrevious(TGPValue GPV); +bool GPV_IsList(TGPValue GPV); +const char *GPV_GetTopValue(TGPValue GPV); +bool GPV_GetTopValue(TGPValue GPV, char *Value); +TGPValue GPV_GetList(TGPValue GPV); + + + +#endif // GENERICPARSER2_H_INC diff --git a/codemp/qcommon/hstring.cpp b/codemp/qcommon/hstring.cpp new file mode 100644 index 0000000..4a97ca7 --- /dev/null +++ b/codemp/qcommon/hstring.cpp @@ -0,0 +1,501 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#ifdef _DONETPROFILE_ +#include +#include "hstring.h" + +using namespace std; + +// mapPoolBlockCount is defined differently in the executable (sv_main.cpp) and the game dll (g_main.cpp) cuz +//we likely don't need as many blocks in the executable as we do in the game +extern int mapPoolBlockCount; + +// Used to fool optimizer during compilation of mem touch routines. +int HaHaOptimizer2=0; + +CMapPoolLow &GetMapPool() +{ + // this may need to be ifdefed to be different for different modules + static CMapPoolLow thePool; + return thePool; +} + +#define MAPBLOCK_SIZE_NODES (1024) +#define MAPNODE_FREE (0xa1) +#define MAPNODE_INUSE (0x94) + +struct SMapNode +{ + unsigned char mData[MAP_NODE_SIZE-2]; + unsigned char mMapBlockNum; + unsigned char mTag; +}; + +class CMapBlock +{ + int mId; + char mRaw[(MAPBLOCK_SIZE_NODES+1)*MAP_NODE_SIZE]; + SMapNode *mNodes; + int mLastNode; + +public: + CMapBlock(int id,vector &freeList) : + mLastNode(0) + { + // Alloc node storage for MAPBLOCK_SIZE_NODES worth of nodes. + mNodes=(SMapNode *)((((unsigned long)mRaw)+MAP_NODE_SIZE)&~(unsigned long)0x1f); + // Set all nodes to initially be free. + int i; + for(i=0;i=&mNodes[0])&&(((SMapNode *)node)<&mNodes[MAPBLOCK_SIZE_NODES])); + } +}; + +CMapPoolLow::CMapPoolLow() +{ + mLastBlockNum=-1; +} + +CMapPoolLow::~CMapPoolLow() +{ +#if _DEBUG +#if _GAME + if(mFreeList.size()mTag==MAPNODE_FREE); + assert((((SMapNode *)node)->mMapBlockNum)>=0); + assert((((SMapNode *)node)->mMapBlockNum)<256); + assert((((SMapNode *)node)->mMapBlockNum)<=mLastBlockNum); + assert(mMapBlocks[((SMapNode *)node)->mMapBlockNum]->bOwnsNode(node)); + + // Ok, mark the node as in use. + ((SMapNode *)node)->mTag=MAPNODE_INUSE; + + return(node); +} + +void CMapPoolLow::Free(void *p) +{ + // Validate that someone isn't trying to double free this node and also + // that the end marker is intact. + assert(((SMapNode *)p)->mTag==MAPNODE_INUSE); + assert((((SMapNode *)p)->mMapBlockNum)>=0); + assert((((SMapNode *)p)->mMapBlockNum)<256); + assert((((SMapNode *)p)->mMapBlockNum)<=mLastBlockNum); + assert(mMapBlocks[((SMapNode *)p)->mMapBlockNum]->bOwnsNode(p)); + + // Ok, mark the the node as free. + ((SMapNode *)p)->mTag=MAPNODE_FREE; + + // Add a new freelist entry to point at this node. + mFreeList.push_back(p); +} + +void CMapPoolLow::TouchMem() +{ + int i,j; + unsigned char *memory; + int totSize=0; + for(i=0;i=0&&hash=0&&mFindPtr(BLOCK_SIZE-mBytesUsed)) + { + return(0); + } + + // Return the pointer to the start of allocated space. + char *ret=&mRaw[mBytesUsed]; + mBytesUsed+=sizeBytes; + return ret; + } + + bool operator== (const CHSBlock &block) const + { + if(!memcmp(mRaw,block.mRaw,BLOCK_SIZE)) + { + return(true); + } + return(false); + } +}; + +class CPool +{ + vector mBlockVec; + +public: + int mNextStringId; + int mLastBlockNum; + + CPool(void) : + mNextStringId(1), + mLastBlockNum(-1) + { + memset(gCharPtrs,0,MAX_STRINGS*4); + } + + ~CPool(void) + { + int i; + for (i=0;i=0) + { + // Get the pointer to the start of allocated space in the current block. + raw=mBlockVec[mLastBlockNum]->Alloc(sizeBytes); + } + if(!raw) + { + // Ok, make a new empty block and append it. + CHSBlock *block=new(CHSBlock); + mBlockVec.push_back(block); + mLastBlockNum++; + raw=mBlockVec[mLastBlockNum]->Alloc(sizeBytes); + } + // Should never really happen!! + assert(raw); + + id=mNextStringId; + gCharPtrs[mNextStringId]=raw; + mNextStringId++; + + return(raw); + } + + bool operator== (const CPool &pool) const + { + int i; + for(i=0;i0&&id0&&mId0&&mId +#include +#include +#include +#include + +using namespace std; + +class hstring +{ + int mId; + void Init(const char *str); +public: + hstring() + { + mId=0; + } + hstring(const char *str) + { + Init(str); + } + hstring(const string &str) + { + Init(str.c_str()); + } + hstring(const hstring &str) + { + mId=str.mId; + } + + operator string () const + { + return str(); + } + + const char *c_str(void) const; + string str(void) const; + + hstring& operator= (const char *str) + { + Init(str); + return *this; + } + hstring& operator= (const string &str) + { + Init(str.c_str()); + return *this; + } + hstring& operator= (const hstring &str) + { + mId=str.mId; + return *this; + } + + bool operator== (const hstring &str) const + { + return((mId==str.mId)?true:false); + } + + int compare(const hstring &str) const + { + return strcmp(c_str(),str.c_str()); + } + + bool operator< (const hstring &str) const + { + return((mId mMapBlocks; + vector mFreeList; + int mLastBlockNum; + +public: + CMapPoolLow(); + ~CMapPoolLow(); + void *Alloc(); + void Free(void *p); + void TouchMem(); +}; + +CMapPoolLow &GetMapPool(); + +template +class CMapPool +{ + CMapPoolLow &mPool; +public: + CMapPool() : mPool(GetMapPool()) + { + + } + template + CMapPool(const U&) : mPool(GetMapPool()) + { + } + ~CMapPool() + { + } + + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + template + struct rebind + { + typedef CMapPool other; + }; + + // return address of values + pointer address (reference value) const + { + return &value; + } + const_pointer address (const_reference value) const + { + return &value; + } + + // return maximum number of elements that can be allocated + size_type max_size () const + { + return 0xfffffff; + } + + // allocate but don't initialize num elements of type T + pointer allocate (size_type num, const void* = 0) + { + assert(sizeof(T)<=(MAP_NODE_SIZE-2)); // to big for this pool + assert(num==1); //allocator not design for this + return (T*)mPool.Alloc(); + } + void *_Charalloc(size_type size) + { + assert(size<=(MAP_NODE_SIZE-2)); // to big for this pool + return mPool.Alloc(); + } + + // initialize elements of allocated storage p with value value + void construct (pointer p, const T& value) + { + // initialize memory with placement new + new((void*)p)T(value); + } + + // destroy elements of initialized storage p + void destroy (pointer p) + { + // destroy objects by calling their destructor + p->~T(); + } + + // deallocate storage p of deleted elements + template + void deallocate (U *p, size_type num) + { + assert(num==1); //allocator not design for this + mPool.Free(p); + } +}; + + +template +bool operator== (const CMapPool&, + const CMapPool&) +{ + return false; +} +template +bool operator!= (const CMapPool&, + const CMapPool&) +{ + return true; +} + +template > +class hmap : public map >{}; + +template > +class hmultimap : public multimap >{}; + +template > +class hset : public set >{}; + +template > +class hmultiset : public multiset >{}; + +template +class hlist : public list >{}; + +// General purpose allocator/deallocator. +template X *XAlloc(void) +{ + assert(sizeof(X)<=(MAP_NODE_SIZE-2)); + return((X *)GetMapPool().Alloc()); +} + +template void XFree(X *x) +{ + GetMapPool().Free((void *)x); +} +#endif // hString_H + +#endif // _DONETPROFILE_ \ No newline at end of file diff --git a/codemp/qcommon/huffman.cpp b/codemp/qcommon/huffman.cpp new file mode 100644 index 0000000..6d2d3f1 --- /dev/null +++ b/codemp/qcommon/huffman.cpp @@ -0,0 +1,541 @@ + +/* This is based on the Adaptive Huffman algorithm described in Sayood's Data + * Compression book. The ranks are not actually stored, but implicitly defined + * by the location of a node within a doubly-linked list */ + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + + +static int bloc = 0; + +void Huff_putBit( int bit, byte *fout, int *offset) { + bloc = *offset; + if ((bloc&7) == 0) { + fout[(bloc>>3)] = 0; + } + fout[(bloc>>3)] |= bit << (bloc&7); + bloc++; + *offset = bloc; +} + +int Huff_getBit( byte *fin, int *offset) { + int t; + bloc = *offset; + t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1; + bloc++; + *offset = bloc; + return t; +} + +/* Add a bit to the output file (buffered) */ +static void add_bit (char bit, byte *fout) { + if ((bloc&7) == 0) { + fout[(bloc>>3)] = 0; + } + fout[(bloc>>3)] |= bit << (bloc&7); + bloc++; +} + +/* Receive one bit from the input file (buffered) */ +static int get_bit (byte *fin) { + int t; + t = (fin[(bloc>>3)] >> (bloc&7)) & 0x1; + bloc++; + return t; +} + +static node_t **get_ppnode(huff_t* huff) { + node_t **tppnode; + if (!huff->freelist) { + return &(huff->nodePtrs[huff->blocPtrs++]); + } else { + tppnode = huff->freelist; + huff->freelist = (node_t **)*tppnode; + return tppnode; + } +} + +static void free_ppnode(huff_t* huff, node_t **ppnode) { + *ppnode = (node_t *)huff->freelist; + huff->freelist = ppnode; +} + +/* Swap the location of these two nodes in the tree */ +static void swap (huff_t* huff, node_t *node1, node_t *node2) { + node_t *par1, *par2; + + par1 = node1->parent; + par2 = node2->parent; + + if (par1) { + if (par1->left == node1) { + par1->left = node2; + } else { + par1->right = node2; + } + } else { + huff->tree = node2; + } + + if (par2) { + if (par2->left == node2) { + par2->left = node1; + } else { + par2->right = node1; + } + } else { + huff->tree = node1; + } + + node1->parent = par2; + node2->parent = par1; +} + +/* Swap these two nodes in the linked list (update ranks) */ +static void swaplist(node_t *node1, node_t *node2) { + node_t *par1; + + par1 = node1->next; + node1->next = node2->next; + node2->next = par1; + + par1 = node1->prev; + node1->prev = node2->prev; + node2->prev = par1; + + if (node1->next == node1) { + node1->next = node2; + } + if (node2->next == node2) { + node2->next = node1; + } + if (node1->next) { + node1->next->prev = node1; + } + if (node2->next) { + node2->next->prev = node2; + } + if (node1->prev) { + node1->prev->next = node1; + } + if (node2->prev) { + node2->prev->next = node2; + } +} + +/* Do the increments */ +static void increment(huff_t* huff, node_t *node) { + node_t *lnode; + + if (!node) { + return; + } + + if (node->next != NULL && node->next->weight == node->weight) { + lnode = *node->head; + if (lnode != node->parent) { + swap(huff, lnode, node); + } + swaplist(lnode, node); + } + if (node->prev && node->prev->weight == node->weight) { + *node->head = node->prev; + } else { + *node->head = NULL; + free_ppnode(huff, node->head); + } + node->weight++; + if (node->next && node->next->weight == node->weight) { + node->head = node->next->head; + } else { + node->head = get_ppnode(huff); + *node->head = node; + } + if (node->parent) { + increment(huff, node->parent); + if (node->prev == node->parent) { + swaplist(node, node->parent); + if (*node->head == node) { + *node->head = node->parent; + } + } + } +} + +void Huff_addRef(huff_t* huff, byte ch) { + node_t *tnode, *tnode2; + if (huff->loc[ch] == NULL) { /* if this is the first transmission of this node */ + tnode = &(huff->nodeList[huff->blocNode++]); + tnode2 = &(huff->nodeList[huff->blocNode++]); + + tnode2->symbol = INTERNAL_NODE; + tnode2->weight = 1; + tnode2->next = huff->lhead->next; + if (huff->lhead->next) { + huff->lhead->next->prev = tnode2; + if (huff->lhead->next->weight == 1) { + tnode2->head = huff->lhead->next->head; + } else { + tnode2->head = get_ppnode(huff); + *tnode2->head = tnode2; + } + } else { + tnode2->head = get_ppnode(huff); + *tnode2->head = tnode2; + } + huff->lhead->next = tnode2; + tnode2->prev = huff->lhead; + + tnode->symbol = ch; + tnode->weight = 1; + tnode->next = huff->lhead->next; + if (huff->lhead->next) { + huff->lhead->next->prev = tnode; + if (huff->lhead->next->weight == 1) { + tnode->head = huff->lhead->next->head; + } else { + /* this should never happen */ + tnode->head = get_ppnode(huff); + *tnode->head = tnode2; + } + } else { + /* this should never happen */ + tnode->head = get_ppnode(huff); + *tnode->head = tnode; + } + huff->lhead->next = tnode; + tnode->prev = huff->lhead; + tnode->left = tnode->right = NULL; + + if (huff->lhead->parent) { + if (huff->lhead->parent->left == huff->lhead) { /* lhead is guaranteed to by the NYT */ + huff->lhead->parent->left = tnode2; + } else { + huff->lhead->parent->right = tnode2; + } + } else { + huff->tree = tnode2; + } + + tnode2->right = tnode; + tnode2->left = huff->lhead; + + tnode2->parent = huff->lhead->parent; + huff->lhead->parent = tnode->parent = tnode2; + + huff->loc[ch] = tnode; + + increment(huff, tnode2->parent); + } else { + increment(huff, huff->loc[ch]); + } +} + +/* Get a symbol */ +int Huff_Receive (node_t *node, int *ch, byte *fin) { + while (node && node->symbol == INTERNAL_NODE) { + if (get_bit(fin)) { + node = node->right; + } else { + node = node->left; + } + } + if (!node) { + return 0; +// Com_Error(ERR_DROP, "Illegal tree!\n"); + } + return (*ch = node->symbol); +} + +/* Get a symbol */ +void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset) { + bloc = *offset; + while (node && node->symbol == INTERNAL_NODE) { + if (get_bit(fin)) { + node = node->right; + } else { + node = node->left; + } + } + if (!node) { + *ch = 0; + return; +// Com_Error(ERR_DROP, "Illegal tree!\n"); + } + *ch = node->symbol; + *offset = bloc; +} + +/* Send the prefix code for this node */ +static void send(node_t *node, node_t *child, byte *fout) { + if (node->parent) { + send(node->parent, node, fout); + } + if (child) { + if (node->right == child) { + add_bit(1, fout); + } else { + add_bit(0, fout); + } + } +} + +/* Send a symbol */ +void Huff_transmit (huff_t *huff, int ch, byte *fout) { + int i; + if (huff->loc[ch] == NULL) { + /* node_t hasn't been transmitted, send a NYT, then the symbol */ + Huff_transmit(huff, NYT, fout); + for (i = 7; i >= 0; i--) { + add_bit((char)((ch >> i) & 0x1), fout); + } + } else { + send(huff->loc[ch], NULL, fout); + } +} + +void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset) { + bloc = *offset; + send(huff->loc[ch], NULL, fout); + *offset = bloc; +} + +void Huff_Decompress(msg_t *mbuf, int offset) { + int ch, cch, i, j, size; + byte seq[65536]; + byte* buffer; + huff_t huff; + + size = mbuf->cursize - offset; + buffer = mbuf->data + offset; + + if ( size <= 0 ) { + return; + } + + Com_Memset(&huff, 0, sizeof(huff_t)); + // Initialize the tree & list with the NYT node + huff.tree = huff.lhead = huff.ltail = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]); + huff.tree->symbol = NYT; + huff.tree->weight = 0; + huff.lhead->next = huff.lhead->prev = NULL; + huff.tree->parent = huff.tree->left = huff.tree->right = NULL; + + cch = buffer[0]*256 + buffer[1]; + // don't overflow with bad messages + if ( cch > mbuf->maxsize - offset ) { + cch = mbuf->maxsize - offset; + } + bloc = 16; + + for ( j = 0; j < cch; j++ ) { + ch = 0; + // don't overflow reading from the messages + // FIXME: would it be better to have a overflow check in get_bit ? + if ( (bloc >> 3) > size ) { + seq[j] = 0; + break; + } + Huff_Receive(huff.tree, &ch, buffer); /* Get a character */ + if ( ch == NYT ) { /* We got a NYT, get the symbol associated with it */ + ch = 0; + for ( i = 0; i < 8; i++ ) { + ch = (ch<<1) + get_bit(buffer); + } + } + + seq[j] = ch; /* Write symbol */ + + Huff_addRef(&huff, (byte)ch); /* Increment node */ + } + mbuf->cursize = cch + offset; + Com_Memcpy(mbuf->data + offset, seq, cch); +} + +extern int oldsize; + +void Huff_Compress(msg_t *mbuf, int offset) { + int i, ch, size; + byte seq[65536]; + byte* buffer; + huff_t huff; + + size = mbuf->cursize - offset; + buffer = mbuf->data+ + offset; + + if (size<=0) { + return; + } + + Com_Memset(&huff, 0, sizeof(huff_t)); + // Add the NYT (not yet transmitted) node into the tree/list */ + huff.tree = huff.lhead = huff.loc[NYT] = &(huff.nodeList[huff.blocNode++]); + huff.tree->symbol = NYT; + huff.tree->weight = 0; + huff.lhead->next = huff.lhead->prev = NULL; + huff.tree->parent = huff.tree->left = huff.tree->right = NULL; + huff.loc[NYT] = huff.tree; + + seq[0] = (size>>8); + seq[1] = size&0xff; + + bloc = 16; + + for (i=0; icursize = (bloc>>3) + offset; + Com_Memcpy(mbuf->data+offset, seq, (bloc>>3)); +} + +void Huff_Init(huffman_t *huff) { + + Com_Memset(&huff->compressor, 0, sizeof(huff_t)); + Com_Memset(&huff->decompressor, 0, sizeof(huff_t)); + + // Initialize the tree & list with the NYT node + huff->decompressor.tree = huff->decompressor.lhead = huff->decompressor.ltail = huff->decompressor.loc[NYT] = &(huff->decompressor.nodeList[huff->decompressor.blocNode++]); + huff->decompressor.tree->symbol = NYT; + huff->decompressor.tree->weight = 0; + huff->decompressor.lhead->next = huff->decompressor.lhead->prev = NULL; + huff->decompressor.tree->parent = huff->decompressor.tree->left = huff->decompressor.tree->right = NULL; + + // Add the NYT (not yet transmitted) node into the tree/list */ + huff->compressor.tree = huff->compressor.lhead = huff->compressor.loc[NYT] = &(huff->compressor.nodeList[huff->compressor.blocNode++]); + huff->compressor.tree->symbol = NYT; + huff->compressor.tree->weight = 0; + huff->compressor.lhead->next = huff->compressor.lhead->prev = NULL; + huff->compressor.tree->parent = huff->compressor.tree->left = huff->compressor.tree->right = NULL; + huff->compressor.loc[NYT] = huff->compressor.tree; +} + +#ifdef _XBOX +// This huffman code is horrendously slow. +// Let's see if we can make it suck slightly less. + +void Huff_LoadPointer( huff_t *huff, void **ptr ) +{ + *ptr = (*ptr) ? (void *)(((unsigned long)(*ptr) + (char *)huff) - 1) : 0; +} + +void Huff_DecodeHuff( huff_t *huff ) +{ + // This just does pointer fixup for all pointers within the structure: + + // Do all the nodes: + for( int i = 0; i < 768; ++i ) + { + node_t *node = &huff->nodeList[i]; + + Huff_LoadPointer( huff, (void **)&node->left ); + Huff_LoadPointer( huff, (void **)&node->right ); + Huff_LoadPointer( huff, (void **)&node->parent ); + Huff_LoadPointer( huff, (void **)&node->next ); + Huff_LoadPointer( huff, (void **)&node->prev ); + Huff_LoadPointer( huff, (void **)&node->head ); + } + + // Do all the pointers in the top level structure: + Huff_LoadPointer( huff, (void **)&huff->tree ); + Huff_LoadPointer( huff, (void **)&huff->lhead ); + Huff_LoadPointer( huff, (void **)&huff->ltail ); + Huff_LoadPointer( huff, (void **)&huff->freelist ); + + // The node pointers in the loc array: + for( i = 0; i < HMAX+1; ++i ) + Huff_LoadPointer( huff, (void **)&huff->loc[i] ); + + // The node pointers in the nodePtrs array + for( i = 0; i < 768; ++i ) + Huff_LoadPointer( huff, (void **)&huff->nodePtrs[i] ); +} + +bool Huff_Load( huffman_t *huff ) +{ + FILE *in = fopen( "d:\\huff_table", "rb" ); + if( !in ) + return false; + + if( !fread( &huff->compressor, sizeof(huff->compressor), 1, in ) || + !fread( &huff->decompressor, sizeof(huff->decompressor), 1, in ) ) + { + fclose( in ); + return false; + } + + Huff_DecodeHuff( &huff->compressor ); + Huff_DecodeHuff( &huff->decompressor ); + + fclose( in ); + return true; +} + +#ifndef FINAL_BUILD // Saving of encoded tables not supported once we're on DVD + +void Huff_SavePointer( huff_t *huff, void **ptr ) +{ + *ptr = (*ptr) ? (void *)(((char *)(*ptr) - (char *)huff) + 1) : 0; +} + +void Huff_EncodeHuff( huff_t *huff ) +{ + // This just does pointer encoding for all pointers within the structure: + + // Fix all pointers inside nodes: + for( int i = 0; i < 768; ++i ) + { + node_t *node = &huff->nodeList[i]; + + Huff_SavePointer( huff, (void **)&node->left ); + Huff_SavePointer( huff, (void **)&node->right ); + Huff_SavePointer( huff, (void **)&node->parent ); + Huff_SavePointer( huff, (void **)&node->next ); + Huff_SavePointer( huff, (void **)&node->prev ); + Huff_SavePointer( huff, (void **)&node->head ); + } + + // Do all the pointers in the top level structure: + Huff_SavePointer( huff, (void **)&huff->tree ); + Huff_SavePointer( huff, (void **)&huff->lhead ); + Huff_SavePointer( huff, (void **)&huff->ltail ); + Huff_SavePointer( huff, (void **)&huff->freelist ); + + // The node pointers in the loc array: + for( int i = 0; i < HMAX+1; ++i ) + Huff_SavePointer( huff, (void **)&huff->loc[i] ); + + // The node pointers in the nodePtrs array + for( int i = 0; i < 768; ++i ) + Huff_SavePointer( huff, (void **)&huff->nodePtrs[i] ); +} + +void Huff_Save( huffman_t *huff ) +{ + FILE *out = fopen( "d:\\huff_table", "wb" ); + if( !out ) + return; + + // Convert all pointers + Huff_EncodeHuff( &huff->compressor ); + Huff_EncodeHuff( &huff->decompressor ); + + // Write out the structures + fwrite( &huff->compressor, sizeof(huff->compressor), 1, out ); + fwrite( &huff->decompressor, sizeof(huff->decompressor), 1, out ); + + // Undo pointer conversion + Huff_DecodeHuff( &huff->compressor ); + Huff_DecodeHuff( &huff->decompressor ); + + fclose( out ); + return; +} + +#endif // FINAL_BUILD +#endif // _XBOX diff --git a/codemp/qcommon/inetprofile.h b/codemp/qcommon/inetprofile.h new file mode 100644 index 0000000..4626a3f --- /dev/null +++ b/codemp/qcommon/inetprofile.h @@ -0,0 +1,20 @@ +#ifdef _DONETPROFILE_ + +#define _INETPROFILE_H_ +#ifdef _INETPROFILE_H_ + +class INetProfile +{ +public: + virtual void Reset(void)=0; + virtual void AddField(char *fieldName,int sizeBytes)=0; + virtual void IncTime(int msec)=0; + virtual void ShowTotals(void)=0; +}; + +INetProfile &ClReadProf(void); +INetProfile &ClSendProf(void); + +#endif // _INETPROFILE_H_ + +#endif // _DONETPROFILE_ \ No newline at end of file diff --git a/codemp/qcommon/md4.cpp b/codemp/qcommon/md4.cpp new file mode 100644 index 0000000..c199ce2 --- /dev/null +++ b/codemp/qcommon/md4.cpp @@ -0,0 +1,296 @@ +/* GLOBAL.H - RSAREF types and constants */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include +#if defined(_WIN32) +#pragma warning(disable : 4711) // selected for automatic inline expansion +#endif + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + + +/* MD4.H - header file for MD4C.C */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. + +All rights reserved. + +License to copy and use this software is granted provided that it is identified as the “RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as “derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided “as is” without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* MD4 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +void MD4Init (MD4_CTX *); +void MD4Update (MD4_CTX *, const unsigned char *, unsigned int); +void MD4Final (unsigned char [16], MD4_CTX *); + +void Com_Memset (void* dest, const int val, const size_t count); +void Com_Memcpy (void* dest, const void* src, const size_t count); + +/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm */ +/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + +License to copy and use this software is granted provided that it is identified as the +RSA Data Security, Inc. MD4 Message-Digest Algorithm + in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as +derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm +in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided +as is without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* Constants for MD4Transform routine. */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static void MD4Transform (UINT4 [4], const unsigned char [64]); +static void Encode (unsigned char *, UINT4 *, unsigned int); +static void Decode (UINT4 *, const unsigned char *, unsigned int); + +static unsigned char PADDING[64] = { +0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G and H are basic MD4 functions. */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));} + +#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));} + +#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = ROTATE_LEFT ((a), (s));} + + +/* MD4 initialization. Begins an MD4 operation, writing a new context. */ +void MD4Init (MD4_CTX *context) +{ + context->count[0] = context->count[1] = 0; + +/* Load magic initialization constants.*/ +context->state[0] = 0x67452301; +context->state[1] = 0xefcdab89; +context->state[2] = 0x98badcfe; +context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ +void MD4Update (MD4_CTX *context, const unsigned char *input, unsigned int inputLen) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3)) + context->count[1]++; + + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible.*/ + if (inputLen >= partLen) + { + Com_Memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD4Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD4Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + Com_Memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); +} + + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */ +void MD4Final (unsigned char digest[16], MD4_CTX *context) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64.*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD4Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD4Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information.*/ + Com_Memset ((POINTER)context, 0, sizeof (*context)); +} + + +/* MD4 basic transformation. Transforms state based on block. */ +static void MD4Transform (UINT4 state[4], const unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + +/* Round 1 */ +FF (a, b, c, d, x[ 0], S11); /* 1 */ +FF (d, a, b, c, x[ 1], S12); /* 2 */ +FF (c, d, a, b, x[ 2], S13); /* 3 */ +FF (b, c, d, a, x[ 3], S14); /* 4 */ +FF (a, b, c, d, x[ 4], S11); /* 5 */ +FF (d, a, b, c, x[ 5], S12); /* 6 */ +FF (c, d, a, b, x[ 6], S13); /* 7 */ +FF (b, c, d, a, x[ 7], S14); /* 8 */ +FF (a, b, c, d, x[ 8], S11); /* 9 */ +FF (d, a, b, c, x[ 9], S12); /* 10 */ +FF (c, d, a, b, x[10], S13); /* 11 */ +FF (b, c, d, a, x[11], S14); /* 12 */ +FF (a, b, c, d, x[12], S11); /* 13 */ +FF (d, a, b, c, x[13], S12); /* 14 */ +FF (c, d, a, b, x[14], S13); /* 15 */ +FF (b, c, d, a, x[15], S14); /* 16 */ + +/* Round 2 */ +GG (a, b, c, d, x[ 0], S21); /* 17 */ +GG (d, a, b, c, x[ 4], S22); /* 18 */ +GG (c, d, a, b, x[ 8], S23); /* 19 */ +GG (b, c, d, a, x[12], S24); /* 20 */ +GG (a, b, c, d, x[ 1], S21); /* 21 */ +GG (d, a, b, c, x[ 5], S22); /* 22 */ +GG (c, d, a, b, x[ 9], S23); /* 23 */ +GG (b, c, d, a, x[13], S24); /* 24 */ +GG (a, b, c, d, x[ 2], S21); /* 25 */ +GG (d, a, b, c, x[ 6], S22); /* 26 */ +GG (c, d, a, b, x[10], S23); /* 27 */ +GG (b, c, d, a, x[14], S24); /* 28 */ +GG (a, b, c, d, x[ 3], S21); /* 29 */ +GG (d, a, b, c, x[ 7], S22); /* 30 */ +GG (c, d, a, b, x[11], S23); /* 31 */ +GG (b, c, d, a, x[15], S24); /* 32 */ + +/* Round 3 */ +HH (a, b, c, d, x[ 0], S31); /* 33 */ +HH (d, a, b, c, x[ 8], S32); /* 34 */ +HH (c, d, a, b, x[ 4], S33); /* 35 */ +HH (b, c, d, a, x[12], S34); /* 36 */ +HH (a, b, c, d, x[ 2], S31); /* 37 */ +HH (d, a, b, c, x[10], S32); /* 38 */ +HH (c, d, a, b, x[ 6], S33); /* 39 */ +HH (b, c, d, a, x[14], S34); /* 40 */ +HH (a, b, c, d, x[ 1], S31); /* 41 */ +HH (d, a, b, c, x[ 9], S32); /* 42 */ +HH (c, d, a, b, x[ 5], S33); /* 43 */ +HH (b, c, d, a, x[13], S34); /* 44 */ +HH (a, b, c, d, x[ 3], S31); /* 45 */ +HH (d, a, b, c, x[11], S32); /* 46 */ +HH (c, d, a, b, x[ 7], S33); /* 47 */ +HH (b, c, d, a, x[15], S34); /* 48 */ + +state[0] += a; +state[1] += b; +state[2] += c; +state[3] += d; + + /* Zeroize sensitive information.*/ + Com_Memset ((POINTER)x, 0, sizeof (x)); +} + + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ +static void Encode (unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ +static void Decode (UINT4 *output, const unsigned char *input, unsigned int len) +{ +unsigned int i, j; + +for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +//=================================================================== + +unsigned Com_BlockChecksum (const void *buffer, int length) +{ + int digest[4]; + unsigned val; + MD4_CTX ctx; + + MD4Init (&ctx); + MD4Update (&ctx, (unsigned char *)buffer, length); + MD4Final ( (unsigned char *)digest, &ctx); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} + +unsigned Com_BlockChecksumKey (void *buffer, int length, int key) +{ + int digest[4]; + unsigned val; + MD4_CTX ctx; + + MD4Init (&ctx); + MD4Update (&ctx, (unsigned char *)&key, 4); + MD4Update (&ctx, (unsigned char *)buffer, length); + MD4Final ( (unsigned char *)digest, &ctx); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/codemp/qcommon/miniheap.h b/codemp/qcommon/miniheap.h new file mode 100644 index 0000000..1348a58 --- /dev/null +++ b/codemp/qcommon/miniheap.h @@ -0,0 +1,57 @@ +#if !defined(MINIHEAP_H_INC) +#define MINIHEAP_H_INC + + +class CMiniHeap +{ +private: + char *mHeap; + char *mCurrentHeap; + int mSize; +public: + +// reset the heap back to the start +void ResetHeap() +{ + mCurrentHeap = mHeap; +} + +// initialise the heap +CMiniHeap(int size) +{ + mHeap = (char *)malloc(size); + mSize = size; + if (mHeap) + { + ResetHeap(); + } +} + +// free up the heap +~CMiniHeap() +{ + if (mHeap) + { + free(mHeap); + } +} + +// give me some space from the heap please +char *MiniHeapAlloc(int size) +{ + if (size < (mSize - ((int)mCurrentHeap - (int)mHeap))) + { + char *tempAddress = mCurrentHeap; + mCurrentHeap += size; + return tempAddress; + } + return NULL; +} + +}; + +extern CMiniHeap *G2VertSpaceServer; +extern CMiniHeap *G2VertSpaceClient; + + +#endif //MINIHEAP_H_INC diff --git a/codemp/qcommon/msg.cpp b/codemp/qcommon/msg.cpp new file mode 100644 index 0000000..222fd8f --- /dev/null +++ b/codemp/qcommon/msg.cpp @@ -0,0 +1,3097 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#ifdef _DONETPROFILE_ +#include "INetProfile.h" +#endif + +// rjr: this is only used when cl_shownet is turned on and the server and client are in the same session +#include "../game/g_public.h" +#include "../server/server.h" + +extern cvar_t *cl_shownet; + + + +//#define _NEWHUFFTABLE_ // Build "c:\\netchan.bin" +//#define _USINGNEWHUFFTABLE_ // Build a new frequency table to cut and paste. + +static huffman_t msgHuff; + +static qboolean msgInit = qfalse; +static FILE *fp=0; + +/* +============================================================================== + + MESSAGE IO FUNCTIONS + +Handles byte ordering and avoids alignment errors +============================================================================== +*/ + +#ifndef FINAL_BUILD + int gLastBitIndex = 0; +#endif + +int oldsize = 0; + +#ifndef _XBOX // No mods on Xbox +bool g_nOverrideChecked = false; +void MSG_CheckNETFPSFOverrides(qboolean psfOverrides); +#endif + +void MSG_initHuffman(); + +void MSG_Init( msg_t *buf, byte *data, int length ) { +#ifndef _XBOX // No mods on Xbox + if (!g_nOverrideChecked) + { + //Check for netf overrides + MSG_CheckNETFPSFOverrides(qfalse); + + //Then for psf overrides + MSG_CheckNETFPSFOverrides(qtrue); + + g_nOverrideChecked = true; + } +#endif + + if (!msgInit) + { + MSG_initHuffman(); + } + + Com_Memset (buf, 0, sizeof(*buf)); + buf->data = data; + buf->maxsize = length; +} + +void MSG_InitOOB( msg_t *buf, byte *data, int length ) { +#ifndef _XBOX // No mods on Xbox + if (!g_nOverrideChecked) + { + //Check for netf overrides + MSG_CheckNETFPSFOverrides(qfalse); + + //Then for psf overrides + MSG_CheckNETFPSFOverrides(qtrue); + + g_nOverrideChecked = true; + } +#endif // _XBOX + + if (!msgInit) + { + MSG_initHuffman(); + } + Com_Memset (buf, 0, sizeof(*buf)); + buf->data = data; + buf->maxsize = length; + buf->oob = qtrue; +} + +void MSG_Clear( msg_t *buf ) { + buf->cursize = 0; + buf->overflowed = qfalse; + buf->bit = 0; //<- in bits +} + + +void MSG_Bitstream( msg_t *buf ) { + buf->oob = qfalse; +} + +void MSG_BeginReading( msg_t *msg ) { + msg->readcount = 0; + msg->bit = 0; + msg->oob = qfalse; +} + +void MSG_BeginReadingOOB( msg_t *msg ) { + msg->readcount = 0; + msg->bit = 0; + msg->oob = qtrue; +} + + +/* +============================================================================= + +bit functions + +============================================================================= +*/ + +int overflows; + +// negative bit values include signs +void MSG_WriteBits( msg_t *msg, int value, int bits ) { + int i; + + oldsize += bits; + + // this isn't an exact overflow check, but close enough + if ( msg->maxsize - msg->cursize < 4 ) { + msg->overflowed = qtrue; + return; + } + + if ( bits == 0 || bits < -31 || bits > 32 ) { + Com_Error( ERR_DROP, "MSG_WriteBits: bad bits %i", bits ); + } + + // check for overflows + if ( bits != 32 ) { + if ( bits > 0 ) { + if ( value > ( ( 1 << bits ) - 1 ) || value < 0 ) { + overflows++; +#ifndef FINAL_BUILD +// Com_Printf ("MSG_WriteBits: overflow writing %d in %d bits [index %i]\n", value, bits, gLastBitIndex); +#endif + } + } else { + int r; + + r = 1 << (bits-1); + + if ( value > r - 1 || value < -r ) { + overflows++; +#ifndef FINAL_BUILD +// Com_Printf ("MSG_WriteBits: overflow writing %d in %d bits [index %i]\n", value, bits, gLastBitIndex); +#endif + } + } + } + if ( bits < 0 ) { + bits = -bits; + } + if (msg->oob) { + if (bits==8) { + msg->data[msg->cursize] = value; + msg->cursize += 1; + msg->bit += 8; + } else if (bits==16) { + unsigned short *sp = (unsigned short *)&msg->data[msg->cursize]; + *sp = LittleShort(value); + msg->cursize += 2; + msg->bit += 16; + } else if (bits==32) { + unsigned int *ip = (unsigned int *)&msg->data[msg->cursize]; + *ip = LittleLong(value); + msg->cursize += 4; + msg->bit += 8; + } else { + Com_Error(ERR_DROP, "can't read %d bits\n", bits); + } + } else { + value &= (0xffffffff>>(32-bits)); + if (bits&7) { + int nbits; + nbits = bits&7; + for(i=0;idata, &msg->bit); + value = (value>>1); + } + bits = bits - nbits; + } + if (bits) { + for(i=0;idata, &msg->bit); + value = (value>>8); + } + } + msg->cursize = (msg->bit>>3)+1; + } +} + +int MSG_ReadBits( msg_t *msg, int bits ) { + int value; + int get; + qboolean sgn; + int i, nbits; + value = 0; + + if ( bits < 0 ) { + bits = -bits; + sgn = qtrue; + } else { + sgn = qfalse; + } + + if (msg->oob) { + if (bits==8) { + value = msg->data[msg->readcount]; + msg->readcount += 1; + msg->bit += 8; + } else if (bits==16) { + unsigned short *sp = (unsigned short *)&msg->data[msg->readcount]; + value = LittleShort(*sp); + msg->readcount += 2; + msg->bit += 16; + } else if (bits==32) { + unsigned int *ip = (unsigned int *)&msg->data[msg->readcount]; + value = LittleLong(*ip); + msg->readcount += 4; + msg->bit += 32; + } else { + Com_Error(ERR_DROP, "can't read %d bits\n", bits); + } + } else { + nbits = 0; + if (bits&7) { + nbits = bits&7; + for(i=0;idata, &msg->bit)<data, &msg->bit); +#ifdef _NEWHUFFTABLE_ + fwrite(&get, 1, 1, fp); +#endif // _NEWHUFFTABLE_ + value |= (get<<(i+nbits)); + } + } + msg->readcount = (msg->bit>>3)+1; + } + if ( sgn ) { + if ( value & ( 1 << ( bits - 1 ) ) ) { + value |= -1 ^ ( ( 1 << bits ) - 1 ); + } + } + + return value; +} + + + +//================================================================================ + +// +// writing functions +// + +void MSG_WriteChar( msg_t *sb, int c ) { +#ifdef PARANOID + if (c < -128 || c > 127) + Com_Error (ERR_FATAL, "MSG_WriteChar: range error"); +#endif + + MSG_WriteBits( sb, c, 8 ); +} + +void MSG_WriteByte( msg_t *sb, int c ) { +#ifdef PARANOID + if (c < 0 || c > 255) + Com_Error (ERR_FATAL, "MSG_WriteByte: range error"); +#endif + + MSG_WriteBits( sb, c, 8 ); +} + +void MSG_WriteData( msg_t *buf, const void *data, int length ) { + int i; + for(i=0;i (short)0x7fff) + Com_Error (ERR_FATAL, "MSG_WriteShort: range error"); +#endif + + MSG_WriteBits( sb, c, 16 ); +} + +void MSG_WriteLong( msg_t *sb, int c ) { + MSG_WriteBits( sb, c, 32 ); +} + +void MSG_WriteFloat( msg_t *sb, float f ) { + union { + float f; + int l; + } dat; + + dat.f = f; + MSG_WriteBits( sb, dat.l, 32 ); +} + +void MSG_WriteString( msg_t *sb, const char *s ) { + if ( !s ) { + MSG_WriteData (sb, "", 1); + } else { + int l; + char string[MAX_STRING_CHARS]; + + l = strlen( s ); + if ( l >= MAX_STRING_CHARS ) { + Com_Printf( "MSG_WriteString: MAX_STRING_CHARS" ); + MSG_WriteData (sb, "", 1); + return; + } + Q_strncpyz( string, s, sizeof( string ) ); + +// eurofix: eliminating this means you can chat in european languages. WTF are "old clients" anyway? -ste +// +// // get rid of 0xff chars, because old clients don't like them +// for ( int i = 0 ; i < l ; i++ ) { +// if ( ((byte *)string)[i] > 127 ) { +// string[i] = '.'; +// } +// } + + MSG_WriteData (sb, string, l+1); + } +} + +void MSG_WriteBigString( msg_t *sb, const char *s ) { + if ( !s ) { + MSG_WriteData (sb, "", 1); + } else { + int l; + char string[BIG_INFO_STRING]; + + l = strlen( s ); + if ( l >= BIG_INFO_STRING ) { + Com_Printf( "MSG_WriteString: BIG_INFO_STRING" ); + MSG_WriteData (sb, "", 1); + return; + } + Q_strncpyz( string, s, sizeof( string ) ); + +// eurofix: remove this so we can chat in european languages... -ste +/* + // get rid of 0xff chars, because old clients don't like them + for ( int i = 0 ; i < l ; i++ ) { + if ( ((byte *)string)[i] > 127 ) { + string[i] = '.'; + } + } +*/ + + MSG_WriteData (sb, string, l+1); + } +} + +void MSG_WriteAngle( msg_t *sb, float f ) { + MSG_WriteByte (sb, (int)(f*256/360) & 255); +} + +void MSG_WriteAngle16( msg_t *sb, float f ) { + MSG_WriteShort (sb, ANGLE2SHORT(f)); +} + + +//============================================================ + +// +// reading functions +// + +// returns -1 if no more characters are available +int MSG_ReadChar (msg_t *msg ) { + int c; + + c = (signed char)MSG_ReadBits( msg, 8 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + + return c; +} + +int MSG_ReadByte( msg_t *msg ) { + int c; + + c = (unsigned char)MSG_ReadBits( msg, 8 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + return c; +} + +int MSG_ReadShort( msg_t *msg ) { + int c; + + c = (short)MSG_ReadBits( msg, 16 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + + return c; +} + +int MSG_ReadLong( msg_t *msg ) { + int c; + + c = MSG_ReadBits( msg, 32 ); + if ( msg->readcount > msg->cursize ) { + c = -1; + } + + return c; +} + +float MSG_ReadFloat( msg_t *msg ) { + union { + byte b[4]; + float f; + int l; + } dat; + + dat.l = MSG_ReadBits( msg, 32 ); + if ( msg->readcount > msg->cursize ) { + dat.f = -1; + } + + return dat.f; +} + +char *MSG_ReadString( msg_t *msg ) { + static char string[MAX_STRING_CHARS]; + int l,c; + + l = 0; + do { + c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if ( c == -1 || c == 0 ) { + break; + } + // translate all fmt spec to avoid crash bugs + if ( c == '%' ) { + c = '.'; + } +// eurofix: remove this so we can chat in european languages... -ste +// +// // don't allow higher ascii values +// if ( c > 127 ) { +// c = '.'; +// } + + string[l] = c; + l++; + } while (l <= sizeof(string)-1); + + // some bonus protection, shouldn't occur cause server doesn't write such things + if (l <= sizeof(string)-1) + { + string[l] = 0; + } + else + { + string[sizeof(string)-1] = 0; + } + + return string; +} + +char *MSG_ReadBigString( msg_t *msg ) { + static char string[BIG_INFO_STRING]; + int l,c; + + l = 0; + do { + c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if ( c == -1 || c == 0 ) { + break; + } + // translate all fmt spec to avoid crash bugs + if ( c == '%' ) { + c = '.'; + } + + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +char *MSG_ReadStringLine( msg_t *msg ) { + static char string[MAX_STRING_CHARS]; + int l,c; + + l = 0; + do { + c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if (c == -1 || c == 0 || c == '\n') { + break; + } + // translate all fmt spec to avoid crash bugs + if ( c == '%' ) { + c = '.'; + } + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +float MSG_ReadAngle16( msg_t *msg ) { + return SHORT2ANGLE(MSG_ReadShort(msg)); +} + +void MSG_ReadData( msg_t *msg, void *data, int len ) { + int i; + + for (i=0 ; iinteger == 4 ) { Com_Printf("%s ", x ); }; + +void MSG_WriteDelta( msg_t *msg, int oldV, int newV, int bits ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, newV, bits ); +} + +int MSG_ReadDelta( msg_t *msg, int oldV, int bits ) { + if ( MSG_ReadBits( msg, 1 ) ) { + return MSG_ReadBits( msg, bits ); + } + return oldV; +} + +void MSG_WriteDeltaFloat( msg_t *msg, float oldV, float newV ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *(int *)&newV, 32 ); +} + +float MSG_ReadDeltaFloat( msg_t *msg, float oldV ) { + if ( MSG_ReadBits( msg, 1 ) ) { + float newV; + + *(int *)&newV = MSG_ReadBits( msg, 32 ); + return newV; + } + return oldV; +} + +/* +============================================================================= + +delta functions with keys + +============================================================================= +*/ + +int kbitmask[32] = { + 0x00000001, 0x00000003, 0x00000007, 0x0000000F, + 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, + 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, + 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, + 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, 0x000FFFFF, + 0x001FFFFf, 0x003FFFFF, 0x007FFFFF, 0x00FFFFFF, + 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF, + 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF, +}; + +void MSG_WriteDeltaKey( msg_t *msg, int key, int oldV, int newV, int bits ) +{ + if ( oldV == newV ) + { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, (newV ^ key) & ((1 << bits) - 1), bits ); +} + +int MSG_ReadDeltaKey( msg_t *msg, int key, int oldV, int bits ) { + if ( MSG_ReadBits( msg, 1 ) ) { + return MSG_ReadBits( msg, bits ) ^ (key & kbitmask[bits]); + } + return oldV; +} + +void MSG_WriteDeltaKeyFloat( msg_t *msg, int key, float oldV, float newV ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, (*(int *)&newV) ^ key, 32 ); +} + +float MSG_ReadDeltaKeyFloat( msg_t *msg, int key, float oldV ) { + if ( MSG_ReadBits( msg, 1 ) ) { + float newV; + + *(int *)&newV = MSG_ReadBits( msg, 32 ) ^ key; + return newV; + } + return oldV; +} + + +/* +============================================================================ + +usercmd_t communication + +============================================================================ +*/ + +// ms is allways sent, the others are optional +#define CM_ANGLE1 (1<<0) +#define CM_ANGLE2 (1<<1) +#define CM_ANGLE3 (1<<2) +#define CM_FORWARD (1<<3) +#define CM_SIDE (1<<4) +#define CM_UP (1<<5) +#define CM_BUTTONS (1<<6) +#define CM_WEAPON (1<<7) +//rww - these are new +#define CM_FORCE (1<<8) +#define CM_INVEN (1<<9) + +/* +===================== +MSG_WriteDeltaUsercmd +===================== +*/ +void MSG_WriteDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) { + if ( to->serverTime - from->serverTime < 256 ) { + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 ); + } else { + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, to->serverTime, 32 ); + } + MSG_WriteDelta( msg, from->angles[0], to->angles[0], 16 ); + MSG_WriteDelta( msg, from->angles[1], to->angles[1], 16 ); + MSG_WriteDelta( msg, from->angles[2], to->angles[2], 16 ); + MSG_WriteDelta( msg, from->forwardmove, to->forwardmove, 8 ); + MSG_WriteDelta( msg, from->rightmove, to->rightmove, 8 ); + MSG_WriteDelta( msg, from->upmove, to->upmove, 8 ); + MSG_WriteDelta( msg, from->buttons, to->buttons, 16 ); + MSG_WriteDelta( msg, from->weapon, to->weapon, 8 ); + + MSG_WriteDelta( msg, from->forcesel, to->forcesel, 8 ); + MSG_WriteDelta( msg, from->invensel, to->invensel, 8 ); + + MSG_WriteDelta( msg, from->generic_cmd, to->generic_cmd, 8 ); +} + + +/* +===================== +MSG_ReadDeltaUsercmd +===================== +*/ +void MSG_ReadDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) { + if ( MSG_ReadBits( msg, 1 ) ) { + to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 ); + } else { + to->serverTime = MSG_ReadBits( msg, 32 ); + } + to->angles[0] = MSG_ReadDelta( msg, from->angles[0], 16); + to->angles[1] = MSG_ReadDelta( msg, from->angles[1], 16); + to->angles[2] = MSG_ReadDelta( msg, from->angles[2], 16); + to->forwardmove = MSG_ReadDelta( msg, from->forwardmove, 8); + to->rightmove = MSG_ReadDelta( msg, from->rightmove, 8); + to->upmove = MSG_ReadDelta( msg, from->upmove, 8); + to->buttons = MSG_ReadDelta( msg, from->buttons, 16); + to->weapon = MSG_ReadDelta( msg, from->weapon, 8); + + to->forcesel = MSG_ReadDelta( msg, from->forcesel, 8); + to->invensel = MSG_ReadDelta( msg, from->invensel, 8); + + to->generic_cmd = MSG_ReadDelta( msg, from->generic_cmd, 8); +} + +/* +===================== +MSG_WriteDeltaUsercmd +===================== +*/ +void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ) { + if ( to->serverTime - from->serverTime < 256 ) { + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, to->serverTime - from->serverTime, 8 ); + } else { + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, to->serverTime, 32 ); + } + if (from->angles[0] == to->angles[0] && + from->angles[1] == to->angles[1] && + from->angles[2] == to->angles[2] && + from->forwardmove == to->forwardmove && + from->rightmove == to->rightmove && + from->upmove == to->upmove && + from->buttons == to->buttons && + from->weapon == to->weapon && + from->forcesel == to->forcesel && + from->invensel == to->invensel && + from->generic_cmd == to->generic_cmd) { + MSG_WriteBits( msg, 0, 1 ); // no change + oldsize += 7; + return; + } + key ^= to->serverTime; + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteDeltaKey( msg, key, from->angles[0], to->angles[0], 16 ); + MSG_WriteDeltaKey( msg, key, from->angles[1], to->angles[1], 16 ); + MSG_WriteDeltaKey( msg, key, from->angles[2], to->angles[2], 16 ); + MSG_WriteDeltaKey( msg, key, from->forwardmove, to->forwardmove, 8 ); + MSG_WriteDeltaKey( msg, key, from->rightmove, to->rightmove, 8 ); + MSG_WriteDeltaKey( msg, key, from->upmove, to->upmove, 8 ); + MSG_WriteDeltaKey( msg, key, from->buttons, to->buttons, 16 ); + MSG_WriteDeltaKey( msg, key, from->weapon, to->weapon, 8 ); + + MSG_WriteDeltaKey( msg, key, from->forcesel, to->forcesel, 8 ); + MSG_WriteDeltaKey( msg, key, from->invensel, to->invensel, 8 ); + + MSG_WriteDeltaKey( msg, key, from->generic_cmd, to->generic_cmd, 8 ); +} + + +/* +===================== +MSG_ReadDeltaUsercmd +===================== +*/ +void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ) { + if ( MSG_ReadBits( msg, 1 ) ) { + to->serverTime = from->serverTime + MSG_ReadBits( msg, 8 ); + } else { + to->serverTime = MSG_ReadBits( msg, 32 ); + } + if ( MSG_ReadBits( msg, 1 ) ) { + key ^= to->serverTime; + to->angles[0] = MSG_ReadDeltaKey( msg, key, from->angles[0], 16); + to->angles[1] = MSG_ReadDeltaKey( msg, key, from->angles[1], 16); + to->angles[2] = MSG_ReadDeltaKey( msg, key, from->angles[2], 16); + to->forwardmove = MSG_ReadDeltaKey( msg, key, from->forwardmove, 8); + to->rightmove = MSG_ReadDeltaKey( msg, key, from->rightmove, 8); + to->upmove = MSG_ReadDeltaKey( msg, key, from->upmove, 8); + to->buttons = MSG_ReadDeltaKey( msg, key, from->buttons, 16); + to->weapon = MSG_ReadDeltaKey( msg, key, from->weapon, 8); + + to->forcesel = MSG_ReadDeltaKey( msg, key, from->forcesel, 8); + to->invensel = MSG_ReadDeltaKey( msg, key, from->invensel, 8); + + to->generic_cmd = MSG_ReadDeltaKey( msg, key, from->generic_cmd, 8); + } else { + to->angles[0] = from->angles[0]; + to->angles[1] = from->angles[1]; + to->angles[2] = from->angles[2]; + to->forwardmove = from->forwardmove; + to->rightmove = from->rightmove; + to->upmove = from->upmove; + to->buttons = from->buttons; + to->weapon = from->weapon; + + to->forcesel = from->forcesel; + to->invensel = from->invensel; + + to->generic_cmd = from->generic_cmd; + } +} + +/* +============================================================================= + +entityState_t communication + +============================================================================= +*/ + + +typedef struct { + char *name; + int offset; +#ifdef _XBOX + int realSize; // in bytes (1, 2, 4) +#endif + int bits; // 0 = float +#ifndef FINAL_BUILD + unsigned mCount; +#endif + +} netField_t; + +// using the stringizing operator to save typing... +#ifdef _XBOX +#define NETF(x) #x,(int)&((entityState_t*)0)->x,sizeof(((entityState_t*)0)->x) +#else +#define NETF(x) #x,(int)&((entityState_t*)0)->x +#endif + +//rww - Remember to update ext_data/MP/netf_overrides.txt if you change any of this! +//(for the sake of being consistent) + +// BTO - This was mis-documented before. We do allow datatypes less than 32 bits on Xbox +// now, but our macros and such handle it all automagically. No need to be anal about +// keeping q_shared.h in sync with this. +netField_t entityStateFields[] = +{ +{ NETF(pos.trTime), 32 }, +{ NETF(pos.trBase[1]), 0 }, +{ NETF(pos.trBase[0]), 0 }, +{ NETF(apos.trBase[1]), 0 }, +{ NETF(pos.trBase[2]), 0 }, +{ NETF(apos.trBase[0]), 0 }, +{ NETF(pos.trDelta[0]), 0 }, +{ NETF(pos.trDelta[1]), 0 }, +{ NETF(eType), 8 }, +{ NETF(angles[1]), 0 }, +{ NETF(pos.trDelta[2]), 0 }, +{ NETF(origin[0]), 0 }, +{ NETF(origin[1]), 0 }, +{ NETF(origin[2]), 0 }, +// does this need to be 8 bits? +{ NETF(weapon), 8 }, +{ NETF(apos.trType), 8 }, +// changed from 12 to 16 +{ NETF(legsAnim), 16 }, // Maximum number of animation sequences is 2048. Top bit is reserved for the togglebit +// suspicious +{ NETF(torsoAnim), 16 }, // Maximum number of animation sequences is 2048. Top bit is reserved for the togglebit +// large use beyond GENTITYNUM_BITS - should use generic1 insead +{ NETF(genericenemyindex), 32 }, //Do not change to GENTITYNUM_BITS, used as a time offset for seeker +{ NETF(eFlags), 32 }, +{ NETF(pos.trDuration), 32 }, +// might be able to reduce +{ NETF(teamowner), 8 }, +{ NETF(groundEntityNum), GENTITYNUM_BITS }, +{ NETF(pos.trType), 8 }, +{ NETF(angles[2]), 0 }, +{ NETF(angles[0]), 0 }, +{ NETF(solid), 24 }, +// flag states barely used - could be moved elsewhere +{ NETF(fireflag), 2 }, +{ NETF(event), 10 }, // There is a maximum of 256 events (8 bits transmission, 2 high bits for uniqueness) +// used mostly for players and npcs - appears to be static / never changing +{ NETF(customRGBA[3]), 8 }, //0-255 +// used mostly for players and npcs - appears to be static / never changing +{ NETF(customRGBA[0]), 8 }, //0-255 +// only used in fx system (which rick did) and chunks +{ NETF(speed), 0 }, +// why are npc's clientnum's that big? +{ NETF(clientNum), GENTITYNUM_BITS }, //with npc's clientnum can be > MAX_CLIENTS so use entnum bits now instead. +{ NETF(apos.trBase[2]), 0 }, +{ NETF(apos.trTime), 32 }, +// used mostly for players and npcs - appears to be static / never changing +{ NETF(customRGBA[1]), 8 }, //0-255 +// used mostly for players and npcs - appears to be static / never changing +{ NETF(customRGBA[2]), 8 }, //0-255 +// multiple meanings +{ NETF(saberEntityNum), GENTITYNUM_BITS }, +// could probably just eliminate and assume a big number +{ NETF(g2radius), 8 }, +{ NETF(otherEntityNum2), GENTITYNUM_BITS }, +// used all over the place +{ NETF(owner), GENTITYNUM_BITS }, +{ NETF(modelindex2), 8 }, +// why was this changed from 0 to 8 ? +{ NETF(eventParm), 8 }, +// unknown about size? +{ NETF(saberMove), 8 }, +{ NETF(apos.trDelta[1]), 0 }, +{ NETF(boneAngles1[1]), 0 }, +// why raised from 8 to -16? +{ NETF(modelindex), -16 }, +// barely used, could probably be replaced +{ NETF(emplacedOwner), 32 }, //As above, also used as a time value (for electricity render time) +{ NETF(apos.trDelta[0]), 0 }, +{ NETF(apos.trDelta[2]), 0 }, +// shouldn't these be better off as flags? otherwise, they may consume more bits this way +{ NETF(torsoFlip), 1 }, +{ NETF(angles2[1]), 0 }, +// used mostly in saber and npc +{ NETF(lookTarget), GENTITYNUM_BITS }, +{ NETF(origin2[2]), 0 }, +// randomly used, not sure why this was used instead of svc_noclient +// if (cent->currentState.modelGhoul2 == 127) +// { //not ready to be drawn or initialized.. +// return; +// } +{ NETF(modelGhoul2), 8 }, +{ NETF(loopSound), 8 }, +{ NETF(origin2[0]), 0 }, +// multiple purpose bit flag +{ NETF(shouldtarget), 1 }, +// widely used, does not appear that they have to be 16 bits +{ NETF(trickedentindex), 16 }, //See note in PSF +{ NETF(otherEntityNum), GENTITYNUM_BITS }, +{ NETF(origin2[1]), 0 }, +{ NETF(time2), 32 }, +{ NETF(legsFlip), 1 }, +// fully used +{ NETF(bolt2), GENTITYNUM_BITS }, +{ NETF(constantLight), 32 }, +{ NETF(time), 32 }, +// why doesn't lookTarget just indicate this? +{ NETF(hasLookTarget), 1 }, +{ NETF(boneAngles1[2]), 0 }, +// used for both force pass and an emplaced gun - gun is just a flag indicator +{ NETF(activeForcePass), 6 }, +// used to indicate health +{ NETF(health), 10 }, //if something's health exceeds 1024, then.. too bad! +// appears to have multiple means, could be eliminated by indicating a sound set differently +{ NETF(loopIsSoundset), 1 }, +{ NETF(saberHolstered), 2 }, +//NPC-SPECIFIC: +// both are used for NPCs sabers, though limited +{ NETF(npcSaber1), 9 }, +{ NETF(maxhealth), 10 }, +{ NETF(trickedentindex2), 16 }, +// appear to only be 18 powers? +{ NETF(forcePowersActive), 32 }, +// used, doesn't appear to be flexible +{ NETF(iModelScale), 10 }, //0-1024 (guess it's gotta be increased if we want larger allowable scale.. but 1024% is pretty big) +// full bits used +{ NETF(powerups), 16 }, +// can this be reduced? +{ NETF(soundSetIndex), 8 }, //rww - if MAX_AMBIENT_SETS is changed from 256, REMEMBER TO CHANGE THIS +// looks like this can be reduced to 4? (ship parts = 4, people parts = 2) +{ NETF(brokenLimbs), 8 }, //up to 8 limbs at once (not that that many are used) +{ NETF(csSounds_Std), 8 }, //soundindex must be 8 unless max sounds is changed +// used extensively +{ NETF(saberInFlight), 1 }, +{ NETF(angles2[0]), 0 }, +{ NETF(frame), 16 }, +{ NETF(angles2[2]), 0 }, +// why not use torsoAnim and set a flag to do the same thing as forceFrame (saberLockFrame) +{ NETF(forceFrame), 16 }, //if you have over 65536 frames, then this will explode. Of course if you have that many things then lots of things will probably explode. +{ NETF(generic1), 8 }, +// do we really need 4 indexes? +{ NETF(boneIndex1), 6 }, //up to 64 bones can be accessed by this indexing method +// only 54 classes, could cut down 2 bits +{ NETF(NPC_class), 8 }, +{ NETF(apos.trDuration), 32 }, +// there appears to be only 2 different version of parms passed - a flag would better be suited +{ NETF(boneOrient), 9 }, //3 bits per orientation dir +// this looks to be a single bit flag +{ NETF(bolt1), 8 }, +{ NETF(trickedentindex3), 16 }, +// in use for vehicles +{ NETF(m_iVehicleNum), GENTITYNUM_BITS }, // 10 bits fits all possible entity nums (2^10 = 1024). - AReis +{ NETF(trickedentindex4), 16 }, +// but why is there an opposite state of surfaces field? +{ NETF(surfacesOff), 32 }, +{ NETF(eFlags2), 10 }, +// should be bit field +//{ NETF(isJediMaster), 1 }, +// should be bit field +{ NETF(isPortalEnt), 1 }, +// possible multiple definitions +{ NETF(heldByClient), 6 }, +// this does not appear to be used in any production or non-cheat fashion - REMOVE +{ NETF(ragAttach), GENTITYNUM_BITS }, +// used only in one spot for seige +{ NETF(boltToPlayer), 6 }, +{ NETF(npcSaber2), 9 }, +{ NETF(csSounds_Combat), 8 }, +{ NETF(csSounds_Extra), 8 }, +{ NETF(csSounds_Jedi), 8 }, +// used only for surfaces on NPCs +{ NETF(surfacesOn), 32 }, //allow up to 32 surfaces in the bitflag +{ NETF(boneIndex2), 6 }, +//{ NETF(boneIndex3), 6 }, +//{ NETF(boneIndex4), 6 }, +{ NETF(boneAngles1[0]), 0 }, +{ NETF(boneAngles2[0]), 0 }, +{ NETF(boneAngles2[1]), 0 }, +{ NETF(boneAngles2[2]), 0 }, +//{ NETF(boneAngles3[0]), 0 }, +//{ NETF(boneAngles3[1]), 0 }, +//{ NETF(boneAngles3[2]), 0 }, +//{ NETF(boneAngles4[0]), 0 }, +//{ NETF(boneAngles4[1]), 0 }, +//{ NETF(boneAngles4[2]), 0 }, + +//rww - for use by mod authors only +#ifndef _XBOX +{ NETF(userInt1), 1 }, +{ NETF(userInt2), 1 }, +{ NETF(userInt3), 1 }, +{ NETF(userFloat1), 1 }, +{ NETF(userFloat2), 1 }, +{ NETF(userFloat3), 1 }, +{ NETF(userVec1[0]), 1 }, +{ NETF(userVec1[1]), 1 }, +{ NETF(userVec1[2]), 1 }, +{ NETF(userVec2[0]), 1 }, +{ NETF(userVec2[1]), 1 }, +{ NETF(userVec2[2]), 1 } +#endif +}; + +// if (int)f == f and (int)f + ( 1<<(FLOAT_INT_BITS-1) ) < ( 1 << FLOAT_INT_BITS ) +// the float will be sent with FLOAT_INT_BITS, otherwise all 32 bits will be sent +#define FLOAT_INT_BITS 13 +#define FLOAT_INT_BIAS (1<<(FLOAT_INT_BITS-1)) + +/* +================== +MSG_WriteDeltaEntity + +Writes part of a packetentities message, including the entity number. +Can delta from either a baseline or a previous packet_entity +If to is NULL, a remove entity update will be sent +If force is not set, then nothing at all will be generated if the entity is +identical, under the assumption that the in-order delta code will catch it. +================== +*/ +void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to, + qboolean force ) { + int i, lc; + int numFields; + netField_t *field; + int trunc; + float fullFloat; + int *fromF, *toF; + + numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]); + + // all fields should be 32 bits to avoid any compiler packing issues + // the "number" field is not part of the field list + // if this assert fails, someone added a field to the entityState_t + // struct without updating the message fields +#ifndef _XBOX // No longer true, although we should keep some kind of check. + assert( numFields + 1 == sizeof( *from )/4 ); +#endif + + // a NULL to is a delta remove message + if ( to == NULL ) { + if ( from == NULL ) { + return; + } + MSG_WriteBits( msg, from->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 1, 1 ); + return; + } + + if ( to->number < 0 || to->number >= MAX_GENTITIES ) { + Com_Error (ERR_FATAL, "MSG_WriteDeltaEntity: Bad entity number: %i", to->number ); + } + + lc = 0; + // build the change vector as bytes so it is endien independent + // TODO: OPTIMIZE: How about we do this in reverse order so we can + // just break out at the first changed field we find? + for ( i = 0, field = entityStateFields ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); +#ifdef _XBOX + if (((field->realSize == 4) && (*fromF != *toF)) || + ((field->realSize == 2) && (*(short *)fromF != *(short *)toF)) || + ((field->realSize == 1) && (*(char *)fromF != *(char *)toF))) + { + lc = i+1; + } +#else + if ( *fromF != *toF ) { + lc = i+1; +#ifndef FINAL_BUILD + field->mCount++; +#endif + } +#endif + } + + if ( lc == 0 ) { + // nothing at all changed + if ( !force ) { + return; // nothing at all + } + // write two bits for no change + MSG_WriteBits( msg, to->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 0, 1 ); // not removed + MSG_WriteBits( msg, 0, 1 ); // no delta + return; + } + + MSG_WriteBits( msg, to->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 0, 1 ); // not removed + MSG_WriteBits( msg, 1, 1 ); // we have a delta + + MSG_WriteByte( msg, lc ); // # of changes + + oldsize += numFields; + + for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + +#ifdef _XBOX + if (((field->realSize == 4) && (*fromF == *toF)) || + ((field->realSize == 2) && (*(short *)fromF == *(short *)toF)) || + ((field->realSize == 1) && (*(char *)fromF == *(char *)toF))) + { + MSG_WriteBits( msg, 0, 1 ); // no change + continue; + } +#else + if ( *fromF == *toF ) { + MSG_WriteBits( msg, 0, 1 ); // no change + continue; + } +#endif + + MSG_WriteBits( msg, 1, 1 ); // changed + + if ( field->bits == 0 ) { + // float + fullFloat = *(float *)toF; + trunc = (int)fullFloat; + + if (fullFloat == 0.0f) { + MSG_WriteBits( msg, 0, 1 ); + oldsize += FLOAT_INT_BITS; + } else { + MSG_WriteBits( msg, 1, 1 ); + if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && + trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) { + // send as small integer + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS ); + } else { + // send as full floating point value + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *toF, 32 ); + } + } + } else { +#ifdef _XBOX + if (((field->realSize == 4) && (*toF == 0)) || + ((field->realSize == 2) && (*(short *)toF == 0)) || + ((field->realSize == 1) && (*(char *)toF == 0))) { + MSG_WriteBits( msg, 0, 1 ); + } else { + MSG_WriteBits( msg, 1, 1 ); + // integer + MSG_WriteBits( msg, + (field->realSize == 4) ? *toF : + (field->realSize == 2) ? *(short *)toF : *(char *)toF, + field->bits ); + } +#else + if (*toF == 0) { + MSG_WriteBits( msg, 0, 1 ); + } else { + MSG_WriteBits( msg, 1, 1 ); + // integer + MSG_WriteBits( msg, *toF, field->bits ); + } +#endif + } + } +} + +/* +================== +MSG_ReadDeltaEntity + +The entity number has already been read from the message, which +is how the from state is identified. + +If the delta removes the entity, entityState_t->number will be set to MAX_GENTITIES-1 + +Can go from either a baseline or a previous packet_entity +================== +*/ + +void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, + int number) { + int i, lc; + int numFields; + netField_t *field; + int *fromF, *toF; + int print; + int trunc; + int startBit, endBit; + + if ( number < 0 || number >= MAX_GENTITIES) { + Com_Error( ERR_DROP, "Bad delta entity number: %i", number ); + } + + startBit = msg->bit; + + // check for a remove + if ( MSG_ReadBits( msg, 1 ) == 1 ) { + Com_Memset( to, 0, sizeof( *to ) ); + to->number = MAX_GENTITIES - 1; + if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) { + Com_Printf( "%3i: #%-3i remove\n", msg->readcount, number ); + } + return; + } + + // check for no delta + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *to = *from; + to->number = number; + return; + } + + numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]); + lc = MSG_ReadByte(msg); + + // shownet 2/3 will interleave with other printed info, -1 will + // just print the delta records` + if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) { + print = 1; + if (sv.state) + { + Com_Printf( "%3i: #%-3i (%s) ", msg->readcount, number, SV_GentityNum(number)->classname ); + } + else + { + Com_Printf( "%3i: #%-3i ", msg->readcount, number ); + } + } else { + print = 0; + } + + to->number = number; + +#ifdef _DONETPROFILE_ + int startBytes,endBytes; +#endif + + for ( i = 0, field = entityStateFields ; i < lc ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + +#ifdef _DONETPROFILE_ + startBytes=msg->readcount; +#endif + if ( ! MSG_ReadBits( msg, 1 ) ) { + // no change +#ifdef _XBOX + if (field->realSize == 4) + *toF = *fromF; + else if (field->realSize == 2) + *(short *)toF = *(short *)fromF; + else + *(char *)toF = *(char *)fromF; +#else + *toF = *fromF; +#endif + } else { + if ( field->bits == 0 ) { + // float + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *(float *)toF = 0.0f; + } else { + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + // integral float + trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + if ( print ) { + Com_Printf( "%s:%i ", field->name, trunc ); + } + } else { + // full floating point value + *toF = MSG_ReadBits( msg, 32 ); + if ( print ) { + Com_Printf( "%s:%f ", field->name, *(float *)toF ); + } + } + } + } else { + if ( MSG_ReadBits( msg, 1 ) == 0 ) { +#ifdef _XBOX + if (field->realSize == 4) + *toF = 0; + else if (field->realSize == 2) + *(short *)toF = 0; + else + *(char *)toF = 0; +#else + *toF = 0; +#endif + } else { + // integer +#ifdef _XBOX + if (field->realSize == 4) + *toF = MSG_ReadBits( msg, field->bits ); + else if (field->realSize == 2) + *(short *)toF = MSG_ReadBits( msg, field->bits ); + else + *(char *)toF = MSG_ReadBits( msg, field->bits ); +#else + *toF = MSG_ReadBits( msg, field->bits ); +#endif + if ( print ) { + Com_Printf( "%s:%i ", field->name, *toF ); + } + } + } + } +#ifdef _DONETPROFILE_ + endBytes=msg->readcount; + ClReadProf().AddField(field->name,endBytes-startBytes); +#endif + } + for ( i = lc, field = &entityStateFields[lc] ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + // no change +#ifdef _XBOX + if (field->realSize == 4) + *toF = *fromF; + else if (field->realSize == 2) + *(short *)toF = *(short *)fromF; + else + *(char *)toF = *(char *)fromF; +#else + *toF = *fromF; +#endif + } + + if ( print ) { + endBit = msg->bit; + Com_Printf( " (%i bits)\n", endBit - startBit ); + } +} + +/* +============================================================================ + +plyer_state_t communication + +============================================================================ +*/ + +// using the stringizing operator to save typing... +#ifdef _XBOX +#define PSF(x) #x,(int)&((playerState_t*)0)->x,sizeof(((playerState_t*)0)->x) +#else +#define PSF(x) #x,(int)&((playerState_t*)0)->x +#endif + +//rww - Remember to update ext_data/MP/psf_overrides.txt if you change any of this! +//(for the sake of being consistent) + +netField_t playerStateFields[] = +{ +{ PSF(commandTime), 32 }, +{ PSF(origin[1]), 0 }, +{ PSF(origin[0]), 0 }, +{ PSF(viewangles[1]), 0 }, +{ PSF(viewangles[0]), 0 }, +{ PSF(origin[2]), 0 }, +{ PSF(velocity[0]), 0 }, +{ PSF(velocity[1]), 0 }, +{ PSF(velocity[2]), 0 }, +{ PSF(bobCycle), 8 }, +{ PSF(weaponTime), -16 }, +{ PSF(delta_angles[1]), 16 }, +{ PSF(speed), 0 }, //sadly, the vehicles require negative speed values, so.. +{ PSF(legsAnim), 16 }, // Maximum number of animation sequences is 2048. Top bit is reserved for the togglebit +{ PSF(delta_angles[0]), 16 }, +{ PSF(torsoAnim), 16 }, // Maximum number of animation sequences is 2048. Top bit is reserved for the togglebit +{ PSF(groundEntityNum), GENTITYNUM_BITS }, +{ PSF(eFlags), 32 }, +{ PSF(fd.forcePower), 8 }, +{ PSF(eventSequence), 16 }, +{ PSF(torsoTimer), 16 }, +{ PSF(legsTimer), 16 }, +{ PSF(viewheight), -8 }, +{ PSF(fd.saberAnimLevel), 4 }, +{ PSF(rocketLockIndex), GENTITYNUM_BITS }, +{ PSF(fd.saberDrawAnimLevel), 4 }, +{ PSF(genericEnemyIndex), 32 }, //NOTE: This isn't just an index all the time, it's often used as a time value, and thus needs 32 bits +{ PSF(events[0]), 10 }, // There is a maximum of 256 events (8 bits transmission, 2 high bits for uniqueness) +{ PSF(events[1]), 10 }, // There is a maximum of 256 events (8 bits transmission, 2 high bits for uniqueness) +{ PSF(customRGBA[0]), 8 }, //0-255 +{ PSF(movementDir), 4 }, +{ PSF(saberEntityNum), GENTITYNUM_BITS }, //Also used for channel tracker storage, but should never exceed entity number +{ PSF(customRGBA[3]), 8 }, //0-255 +{ PSF(weaponstate), 4 }, +{ PSF(saberMove), 32 }, //This value sometimes exceeds the max LS_ value and gets set to a crazy amount, so it needs 32 bits +{ PSF(standheight), 10 }, +{ PSF(crouchheight), 10 }, +{ PSF(basespeed), -16 }, +{ PSF(pm_flags), 16 }, +{ PSF(jetpackFuel), 8 }, +{ PSF(cloakFuel), 8 }, +{ PSF(pm_time), -16 }, +{ PSF(customRGBA[1]), 8 }, //0-255 +{ PSF(clientNum), GENTITYNUM_BITS }, +{ PSF(duelIndex), GENTITYNUM_BITS }, +{ PSF(customRGBA[2]), 8 }, //0-255 +{ PSF(gravity), 16 }, +{ PSF(weapon), 8 }, +{ PSF(delta_angles[2]), 16 }, +{ PSF(saberCanThrow), 1 }, +{ PSF(viewangles[2]), 0 }, +{ PSF(fd.forcePowersKnown), 32 }, +{ PSF(fd.forcePowerLevel[FP_LEVITATION]), 2 }, //unfortunately we need this for fall damage calculation (client needs to know the distance for the fall noise) +{ PSF(fd.forcePowerDebounce[FP_LEVITATION]), 32 }, +{ PSF(fd.forcePowerSelected), 8 }, +{ PSF(torsoFlip), 1 }, +{ PSF(externalEvent), 10 }, +{ PSF(damageYaw), 8 }, +{ PSF(damageCount), 8 }, +{ PSF(inAirAnim), 1 }, //just transmit it for the sake of knowing right when on the client to play a land anim, it's only 1 bit +{ PSF(eventParms[1]), 8 }, +{ PSF(fd.forceSide), 2 }, //so we know if we should apply greyed out shaders to dark/light force enlightenment +{ PSF(saberAttackChainCount), 4 }, +{ PSF(pm_type), 8 }, +{ PSF(externalEventParm), 8 }, +{ PSF(eventParms[0]), -16 }, +{ PSF(lookTarget), GENTITYNUM_BITS }, +//{ PSF(vehOrientation[0]), 0 }, +{ PSF(weaponChargeSubtractTime), 32 }, //? really need 32 bits?? +//{ PSF(vehOrientation[1]), 0 }, +//{ PSF(moveDir[1]), 0 }, +//{ PSF(moveDir[0]), 0 }, +{ PSF(weaponChargeTime), 32 }, //? really need 32 bits?? +//{ PSF(vehOrientation[2]), 0 }, +{ PSF(legsFlip), 1 }, +{ PSF(damageEvent), 8 }, +//{ PSF(moveDir[2]), 0 }, +{ PSF(rocketTargetTime), 32 }, +{ PSF(activeForcePass), 6 }, +{ PSF(electrifyTime), 32 }, +{ PSF(fd.forceJumpZStart), 0 }, +{ PSF(loopSound), 16 }, //rwwFIXMEFIXME: max sounds is 256, doesn't this only need to be 8? +{ PSF(hasLookTarget), 1 }, +{ PSF(saberBlocked), 8 }, +{ PSF(damageType), 2 }, +{ PSF(rocketLockTime), 32 }, +{ PSF(forceHandExtend), 8 }, +{ PSF(saberHolstered), 2 }, +{ PSF(fd.forcePowersActive), 32 }, +{ PSF(damagePitch), 8 }, +{ PSF(m_iVehicleNum), GENTITYNUM_BITS }, // 10 bits fits all possible entity nums (2^10 = 1024). - AReis +//{ PSF(vehTurnaroundTime), 32 },//only used by vehicle? +{ PSF(generic1), 8 }, +{ PSF(jumppad_ent), 10 }, +{ PSF(hasDetPackPlanted), 1 }, +{ PSF(saberInFlight), 1 }, +{ PSF(forceDodgeAnim), 16 }, +{ PSF(zoomMode), 2 }, // NOTENOTE Are all of these necessary? +{ PSF(hackingTime), 32 }, +{ PSF(zoomTime), 32 }, // NOTENOTE Are all of these necessary? +{ PSF(brokenLimbs), 8 }, //up to 8 limbs at once (not that that many are used) +{ PSF(zoomLocked), 1 }, // NOTENOTE Are all of these necessary? +{ PSF(zoomFov), 0 }, // NOTENOTE Are all of these necessary? +{ PSF(fd.forceRageRecoveryTime), 32 }, +{ PSF(fallingToDeath), 32 }, +{ PSF(fd.forceMindtrickTargetIndex), 16 }, //NOTE: Not just an index, used as a (1 << val) bitflag for up to 16 clients +{ PSF(fd.forceMindtrickTargetIndex2), 16 }, //NOTE: Not just an index, used as a (1 << val) bitflag for up to 16 clients +//{ PSF(vehWeaponsLinked), 1 },//only used by vehicle? +{ PSF(lastHitLoc[2]), 0 }, +//{ PSF(hyperSpaceTime), 32 },//only used by vehicle? +{ PSF(fd.forceMindtrickTargetIndex3), 16 }, //NOTE: Not just an index, used as a (1 << val) bitflag for up to 16 clients +{ PSF(lastHitLoc[0]), 0 }, +{ PSF(eFlags2), 10 }, +{ PSF(fd.forceMindtrickTargetIndex4), 16 }, //NOTE: Not just an index, used as a (1 << val) bitflag for up to 16 clients +//{ PSF(hyperSpaceAngles[1]), 0 },//only used by vehicle? +{ PSF(lastHitLoc[1]), 0 }, //currently only used so client knows to orient disruptor disintegration.. seems a bit much for just that though. +//{ PSF(vehBoarding), 1 }, //only used by vehicle? not like the normal boarding value, this is a simple "1 or 0" value +{ PSF(fd.sentryDeployed), 1 }, +{ PSF(saberLockTime), 32 }, +{ PSF(saberLockFrame), 16 }, +//{ PSF(vehTurnaroundIndex), GENTITYNUM_BITS },//only used by vehicle? +//{ PSF(vehSurfaces), 16 }, //only used by vehicle? allow up to 16 surfaces in the flag I guess +{ PSF(fd.forcePowerLevel[FP_SEE]), 2 }, //needed for knowing when to display players through walls +{ PSF(saberLockEnemy), GENTITYNUM_BITS }, +{ PSF(fd.forceGripCripple), 1 }, //should only be 0 or 1 ever +{ PSF(emplacedIndex), GENTITYNUM_BITS }, +//{ PSF(isJediMaster), 1 }, +{ PSF(forceRestricted), 1 }, +{ PSF(trueJedi), 1 }, +{ PSF(trueNonJedi), 1 }, +{ PSF(duelTime), 32 }, +{ PSF(duelInProgress), 1 }, +{ PSF(saberLockAdvance), 1 }, +{ PSF(heldByClient), 6 }, +{ PSF(ragAttach), GENTITYNUM_BITS }, +{ PSF(iModelScale), 10 }, //0-1024 (guess it's gotta be increased if we want larger allowable scale.. but 1024% is pretty big) +{ PSF(hackingBaseTime), 16 }, //up to 65536ms, over 10 seconds would just be silly anyway +}; + +// PILOT FIELDS +netField_t pilotPlayerStateFields[] = +{ +{ PSF(commandTime), 32 }, +{ PSF(origin[1]), 0 }, +{ PSF(origin[0]), 0 }, +{ PSF(viewangles[1]), 0 }, +{ PSF(viewangles[0]), 0 }, +{ PSF(origin[2]), 0 }, +{ PSF(weaponTime), -16 }, +{ PSF(delta_angles[1]), 16 }, +{ PSF(delta_angles[0]), 16 }, +{ PSF(eFlags), 32 }, +{ PSF(eventSequence), 16 }, +{ PSF(rocketLockIndex), GENTITYNUM_BITS }, +{ PSF(events[0]), 10 }, // There is a maximum of 256 events (8 bits transmission, 2 high bits for uniqueness) +{ PSF(events[1]), 10 }, // There is a maximum of 256 events (8 bits transmission, 2 high bits for uniqueness) +{ PSF(weaponstate), 4 }, +{ PSF(pm_flags), 16 }, +{ PSF(pm_time), -16 }, +{ PSF(clientNum), GENTITYNUM_BITS }, +{ PSF(weapon), 8 }, +{ PSF(delta_angles[2]), 16 }, +{ PSF(viewangles[2]), 0 }, +{ PSF(externalEvent), 10 }, +{ PSF(eventParms[1]), 8 }, +{ PSF(pm_type), 8 }, +{ PSF(externalEventParm), 8 }, +{ PSF(eventParms[0]), -16 }, +{ PSF(weaponChargeSubtractTime), 32 }, //? really need 32 bits?? +{ PSF(weaponChargeTime), 32 }, //? really need 32 bits?? +{ PSF(rocketTargetTime), 32 }, +{ PSF(fd.forceJumpZStart), 0 }, +{ PSF(rocketLockTime), 32 }, +{ PSF(m_iVehicleNum), GENTITYNUM_BITS }, // 10 bits fits all possible entity nums (2^10 = 1024). - AReis +{ PSF(generic1), 8 },//used by passengers +{ PSF(eFlags2), 10 }, + +//===THESE SHOULD NOT BE CHANGING OFTEN==================================================================== +{ PSF(legsAnim), 16 }, // Maximum number of animation sequences is 2048. Top bit is reserved for the togglebit +{ PSF(torsoAnim), 16 }, // Maximum number of animation sequences is 2048. Top bit is reserved for the togglebit +{ PSF(torsoTimer), 16 }, +{ PSF(legsTimer), 16 }, +{ PSF(jetpackFuel), 8 }, +{ PSF(cloakFuel), 8 }, +{ PSF(saberCanThrow), 1 }, +{ PSF(fd.forcePowerDebounce[FP_LEVITATION]), 32 }, +{ PSF(torsoFlip), 1 }, +{ PSF(legsFlip), 1 }, +{ PSF(fd.forcePowersActive), 32 }, +{ PSF(hasDetPackPlanted), 1 }, +{ PSF(fd.forceRageRecoveryTime), 32 }, +{ PSF(saberInFlight), 1 }, +{ PSF(fd.forceMindtrickTargetIndex), 16 }, //NOTE: Not just an index, used as a (1 << val) bitflag for up to 16 clients +{ PSF(fd.forceMindtrickTargetIndex2), 16 }, //NOTE: Not just an index, used as a (1 << val) bitflag for up to 16 clients +{ PSF(fd.forceMindtrickTargetIndex3), 16 }, //NOTE: Not just an index, used as a (1 << val) bitflag for up to 16 clients +{ PSF(fd.forceMindtrickTargetIndex4), 16 }, //NOTE: Not just an index, used as a (1 << val) bitflag for up to 16 clients +{ PSF(fd.sentryDeployed), 1 }, +{ PSF(fd.forcePowerLevel[FP_SEE]), 2 }, //needed for knowing when to display players through walls +{ PSF(fd.forcePower), 8 }, + +//===THE REST OF THESE SHOULD NOT BE RELEVANT, BUT, FOR SAFETY, INCLUDE THEM ANYWAY, JUST AT THE BOTTOM=============================================================== +{ PSF(velocity[0]), 0 }, +{ PSF(velocity[1]), 0 }, +{ PSF(velocity[2]), 0 }, +{ PSF(bobCycle), 8 }, +{ PSF(speed), 0 }, //sadly, the vehicles require negative speed values, so.. +{ PSF(groundEntityNum), GENTITYNUM_BITS }, +{ PSF(viewheight), -8 }, +{ PSF(fd.saberAnimLevel), 4 }, +{ PSF(fd.saberDrawAnimLevel), 4 }, +{ PSF(genericEnemyIndex), 32 }, //NOTE: This isn't just an index all the time, it's often used as a time value, and thus needs 32 bits +{ PSF(customRGBA[0]), 8 }, //0-255 +{ PSF(movementDir), 4 }, +{ PSF(saberEntityNum), GENTITYNUM_BITS }, //Also used for channel tracker storage, but should never exceed entity number +{ PSF(customRGBA[3]), 8 }, //0-255 +{ PSF(saberMove), 32 }, //This value sometimes exceeds the max LS_ value and gets set to a crazy amount, so it needs 32 bits +{ PSF(standheight), 10 }, +{ PSF(crouchheight), 10 }, +{ PSF(basespeed), -16 }, +{ PSF(customRGBA[1]), 8 }, //0-255 +{ PSF(duelIndex), GENTITYNUM_BITS }, +{ PSF(customRGBA[2]), 8 }, //0-255 +{ PSF(gravity), 16 }, +{ PSF(fd.forcePowersKnown), 32 }, +{ PSF(fd.forcePowerLevel[FP_LEVITATION]), 2 }, //unfortunately we need this for fall damage calculation (client needs to know the distance for the fall noise) +{ PSF(fd.forcePowerSelected), 8 }, +{ PSF(damageYaw), 8 }, +{ PSF(damageCount), 8 }, +{ PSF(inAirAnim), 1 }, //just transmit it for the sake of knowing right when on the client to play a land anim, it's only 1 bit +{ PSF(fd.forceSide), 2 }, //so we know if we should apply greyed out shaders to dark/light force enlightenment +{ PSF(saberAttackChainCount), 4 }, +{ PSF(lookTarget), GENTITYNUM_BITS }, +{ PSF(moveDir[1]), 0 }, +{ PSF(moveDir[0]), 0 }, +{ PSF(damageEvent), 8 }, +{ PSF(moveDir[2]), 0 }, +{ PSF(activeForcePass), 6 }, +{ PSF(electrifyTime), 32 }, +{ PSF(damageType), 2 }, +{ PSF(loopSound), 16 }, //rwwFIXMEFIXME: max sounds is 256, doesn't this only need to be 8? +{ PSF(hasLookTarget), 1 }, +{ PSF(saberBlocked), 8 }, +{ PSF(forceHandExtend), 8 }, +{ PSF(saberHolstered), 2 }, +{ PSF(damagePitch), 8 }, +{ PSF(jumppad_ent), 10 }, +{ PSF(forceDodgeAnim), 16 }, +{ PSF(zoomMode), 2 }, // NOTENOTE Are all of these necessary? +{ PSF(hackingTime), 32 }, +{ PSF(zoomTime), 32 }, // NOTENOTE Are all of these necessary? +{ PSF(brokenLimbs), 8 }, //up to 8 limbs at once (not that that many are used) +{ PSF(zoomLocked), 1 }, // NOTENOTE Are all of these necessary? +{ PSF(zoomFov), 0 }, // NOTENOTE Are all of these necessary? +{ PSF(fallingToDeath), 32 }, +{ PSF(lastHitLoc[2]), 0 }, +{ PSF(lastHitLoc[0]), 0 }, +{ PSF(lastHitLoc[1]), 0 }, //currently only used so client knows to orient disruptor disintegration.. seems a bit much for just that though. +{ PSF(saberLockTime), 32 }, +{ PSF(saberLockFrame), 16 }, +{ PSF(saberLockEnemy), GENTITYNUM_BITS }, +{ PSF(fd.forceGripCripple), 1 }, //should only be 0 or 1 ever +{ PSF(emplacedIndex), GENTITYNUM_BITS }, +//{ PSF(isJediMaster), 1 }, +{ PSF(forceRestricted), 1 }, +{ PSF(trueJedi), 1 }, +{ PSF(trueNonJedi), 1 }, +{ PSF(duelTime), 32 }, +{ PSF(duelInProgress), 1 }, +{ PSF(saberLockAdvance), 1 }, +{ PSF(heldByClient), 6 }, +{ PSF(ragAttach), GENTITYNUM_BITS }, +{ PSF(iModelScale), 10 }, //0-1024 (guess it's gotta be increased if we want larger allowable scale.. but 1024% is pretty big) +{ PSF(hackingBaseTime), 16 }, //up to 65536ms, over 10 seconds would just be silly anyway +}; + +// VEHICLE FIELDS +netField_t vehPlayerStateFields[] = +{ +{ PSF(commandTime), 32 }, +{ PSF(origin[1]), 0 }, +{ PSF(origin[0]), 0 }, +{ PSF(viewangles[1]), 0 }, +{ PSF(viewangles[0]), 0 }, +{ PSF(origin[2]), 0 }, +{ PSF(velocity[0]), 0 }, +{ PSF(velocity[1]), 0 }, +{ PSF(velocity[2]), 0 }, +{ PSF(weaponTime), -16 }, +{ PSF(delta_angles[1]), 16 }, +{ PSF(speed), 0 }, //sadly, the vehicles require negative speed values, so.. +{ PSF(legsAnim), 16 }, // Maximum number of animation sequences is 2048. Top bit is reserved for the togglebit +{ PSF(delta_angles[0]), 16 }, +{ PSF(groundEntityNum), GENTITYNUM_BITS }, +{ PSF(eFlags), 32 }, +{ PSF(eventSequence), 16 }, +{ PSF(legsTimer), 16 }, +{ PSF(rocketLockIndex), GENTITYNUM_BITS }, +//{ PSF(genericEnemyIndex), 32 }, //NOTE: This isn't just an index all the time, it's often used as a time value, and thus needs 32 bits +{ PSF(events[0]), 10 }, // There is a maximum of 256 events (8 bits transmission, 2 high bits for uniqueness) +{ PSF(events[1]), 10 }, // There is a maximum of 256 events (8 bits transmission, 2 high bits for uniqueness) +//{ PSF(customRGBA[0]), 8 }, //0-255 +//{ PSF(movementDir), 4 }, +//{ PSF(customRGBA[3]), 8 }, //0-255 +{ PSF(weaponstate), 4 }, +//{ PSF(basespeed), -16 }, +{ PSF(pm_flags), 16 }, +{ PSF(pm_time), -16 }, +//{ PSF(customRGBA[1]), 8 }, //0-255 +{ PSF(clientNum), GENTITYNUM_BITS }, +//{ PSF(duelIndex), GENTITYNUM_BITS }, +//{ PSF(customRGBA[2]), 8 }, //0-255 +{ PSF(gravity), 16 }, +{ PSF(weapon), 8 }, +{ PSF(delta_angles[2]), 16 }, +{ PSF(viewangles[2]), 0 }, +{ PSF(externalEvent), 10 }, +{ PSF(eventParms[1]), 8 }, +{ PSF(pm_type), 8 }, +{ PSF(externalEventParm), 8 }, +{ PSF(eventParms[0]), -16 }, +{ PSF(vehOrientation[0]), 0 }, +{ PSF(vehOrientation[1]), 0 }, +{ PSF(moveDir[1]), 0 }, +{ PSF(moveDir[0]), 0 }, +{ PSF(vehOrientation[2]), 0 }, +{ PSF(moveDir[2]), 0 }, +{ PSF(rocketTargetTime), 32 }, +//{ PSF(activeForcePass), 6 },//actually, you only need to know this for other vehicles, not your own +{ PSF(electrifyTime), 32 }, +//{ PSF(fd.forceJumpZStart), 0 },//set on rider by vehicle, but not used by vehicle +{ PSF(loopSound), 16 }, //rwwFIXMEFIXME: max sounds is 256, doesn't this only need to be 8? +{ PSF(rocketLockTime), 32 }, +{ PSF(m_iVehicleNum), GENTITYNUM_BITS }, // 10 bits fits all possible entity nums (2^10 = 1024). - AReis +{ PSF(vehTurnaroundTime), 32 }, +//{ PSF(generic1), 8 },//used by passengers of vehicles, but not vehicles themselves +{ PSF(hackingTime), 32 }, +{ PSF(brokenLimbs), 8 }, //up to 8 limbs at once (not that that many are used) +{ PSF(vehWeaponsLinked), 1 }, +{ PSF(hyperSpaceTime), 32 }, +{ PSF(eFlags2), 10 }, +{ PSF(hyperSpaceAngles[1]), 0 }, +{ PSF(vehBoarding), 1 }, //not like the normal boarding value, this is a simple "1 or 0" value +{ PSF(vehTurnaroundIndex), GENTITYNUM_BITS }, +{ PSF(vehSurfaces), 16 }, //allow up to 16 surfaces in the flag I guess +{ PSF(hyperSpaceAngles[0]), 0 }, +{ PSF(hyperSpaceAngles[2]), 0 }, + +}; + +#ifndef _XBOX // No mods on Xbox +typedef struct bitStorage_s bitStorage_t; + +struct bitStorage_s +{ + bitStorage_t *next; + int bits; +}; + +static bitStorage_t *g_netfBitStorage = NULL; +static bitStorage_t *g_psfBitStorage = NULL; + +//rww - Check the overrides files to see if the mod wants anything changed +void MSG_CheckNETFPSFOverrides(qboolean psfOverrides) +{ + char overrideFile[4096]; + char entryName[4096]; + char bits[4096]; + char *fileName; + int ibits; + int i = 0; + int j; + int len; + int numFields; + fileHandle_t f; + bitStorage_t **bitStorage; + + if (psfOverrides) + { //do PSF overrides instead of NETF + fileName = "psf_overrides.txt"; + bitStorage = &g_psfBitStorage; + numFields = sizeof(playerStateFields)/sizeof(playerStateFields[0]); + } + else + { + fileName = "netf_overrides.txt"; + bitStorage = &g_netfBitStorage; + numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]); + } + + if (*bitStorage) + { //if we have saved off the defaults before we want to stuff them all back in now + bitStorage_t *restore = *bitStorage; + + while (i < numFields) + { + assert(restore); + + if (psfOverrides) + { + playerStateFields[i].bits = restore->bits; + } + else + { + entityStateFields[i].bits = restore->bits; + } + + i++; + restore = restore->next; + } + } + + len = FS_FOpenFileRead(va("ext_data/MP/%s", fileName), &f, qfalse); + + if (!f) + { //silently exit since this file is not needed to proceed. + return; + } + + if (len >= 4096) + { + Com_Printf("WARNING: %s is >= 4096 bytes and is being ignored\n", fileName); + FS_FCloseFile(f); + return; + } + + //Get contents of the file + FS_Read(overrideFile, len, f); + FS_FCloseFile(f); + + //because FS_Read does not do this for us. + overrideFile[len] = 0; + + //If we haven't saved off the initial stuff yet then stuff it all into + //a list. + if (!*bitStorage) + { + i = 0; + + while (i < numFields) + { + //Alloc memory for this new ptr + *bitStorage = (bitStorage_t *)Z_Malloc(sizeof(bitStorage_t), TAG_GENERAL, qtrue); + + if (psfOverrides) + { + (*bitStorage)->bits = playerStateFields[i].bits; + } + else + { + (*bitStorage)->bits = entityStateFields[i].bits; + } + + //Point to the ->next of the existing current ptr + bitStorage = &(*bitStorage)->next; + i++; + } + } + + i = 0; + //Now parse through. Lines beginning with ; are disabled. + while (overrideFile[i]) + { + if (overrideFile[i] == ';') + { //parse to end of the line + while (overrideFile[i] != '\n') + { + i++; + } + } + + if (overrideFile[i] != ';' && + overrideFile[i] != '\n' && + overrideFile[i] != '\r') + { //on a valid char I guess, parse it + j = 0; + + while (overrideFile[i] && overrideFile[i] != ',') + { + entryName[j] = overrideFile[i]; + j++; + i++; + } + entryName[j] = 0; + + if (!overrideFile[i]) + { //just give up, this shouldn't happen + Com_Printf("WARNING: Parsing error for %s\n", fileName); + return; + } + + while (overrideFile[i] == ',' || overrideFile[i] == ' ') + { //parse to the start of the value + i++; + } + + j = 0; + while (overrideFile[i] != '\n' && overrideFile[i] != '\r') + { //now read the value in + bits[j] = overrideFile[i]; + j++; + i++; + } + bits[j] = 0; + + if (bits[0]) + { + if (!strcmp(bits, "GENTITYNUM_BITS")) + { //special case + ibits = GENTITYNUM_BITS; + } + else + { + ibits = atoi(bits); + } + + j = 0; + + //Now go through all the fields and see if we can find a match + while (j < numFields) + { + if (psfOverrides) + { //check psf fields + if (!strcmp(playerStateFields[j].name, entryName)) + { //found it, set the bits + playerStateFields[j].bits = ibits; + break; + } + } + else + { //otherwise check netf fields + if (!strcmp(entityStateFields[j].name, entryName)) + { //found it, set the bits + entityStateFields[j].bits = ibits; + break; + } + } + j++; + } + + if (j == numFields) + { //failed to find the value + Com_Printf("WARNING: Value '%s' from %s is not valid\n", entryName, fileName); + } + } + else + { //also should not happen + Com_Printf("WARNING: Parsing error for %s\n", fileName); + return; + } + } + + i++; + } +} +#endif // Xbox - No mods on Xbox + +//MAKE SURE THIS MATCHES THE ENUM IN BG_PUBLIC.H!!! +//This is in caps, because it is important. +#define STAT_WEAPONS 4 + +/* +============= +MSG_WriteDeltaPlayerstate + +============= +*/ +#ifdef _ONEBIT_COMBO +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to, int *bitComboDelta, int *bitNumDelta, qboolean isVehiclePS ) { +#else +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to, qboolean isVehiclePS ) { +#endif + int i; + playerState_t dummy; + int statsbits; + int persistantbits; + int ammobits; + int powerupbits; + int numFields; + int c; + netField_t *field; + netField_t *PSFields = playerStateFields; + int *fromF, *toF; + float fullFloat; + int trunc, lc; +#ifdef _ONEBIT_COMBO + int bitComboMask = 0; + int numBitsInMask = 0; +#endif + + if (!from) { + from = &dummy; + Com_Memset (&dummy, 0, sizeof(dummy)); + } + + c = msg->cursize; + +//=====_OPTIMIZED_VEHICLE_NETWORKING======================================================================= +#ifdef _OPTIMIZED_VEHICLE_NETWORKING + if ( isVehiclePS ) + {//a vehicle playerstate + numFields = sizeof( vehPlayerStateFields ) / sizeof( vehPlayerStateFields[0] ); + PSFields = vehPlayerStateFields; + } + else + {//regular client playerstate + if ( to->m_iVehicleNum + && (to->eFlags&EF_NODRAW) ) + {//pilot riding *inside* a vehicle! + MSG_WriteBits( msg, 1, 1 ); // Pilot player state + numFields = sizeof( pilotPlayerStateFields ) / sizeof( pilotPlayerStateFields[0] ) - 82; + PSFields = pilotPlayerStateFields; + } + else + {//normal client + MSG_WriteBits( msg, 0, 1 ); // Normal player state + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); + } + } +//=====_OPTIMIZED_VEHICLE_NETWORKING======================================================================= +#else// _OPTIMIZED_VEHICLE_NETWORKING + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); +#endif// _OPTIMIZED_VEHICLE_NETWORKING + + lc = 0; + for ( i = 0, field = PSFields ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + if ( *fromF != *toF ) { + lc = i+1; +#ifndef FINAL_BUILD + field->mCount++; +#endif + } + } + + MSG_WriteByte( msg, lc ); // # of changes + +#ifndef FINAL_BUILD + gLastBitIndex = lc; +#endif + + oldsize += numFields - lc; + + for ( i = 0, field = PSFields ; i < lc ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + +#ifdef _ONEBIT_COMBO + if (numBitsInMask < 32 && + field->bits == 1) + { + bitComboMask |= (*toF)<bits == 0 ) { + // float + fullFloat = *(float *)toF; + trunc = (int)fullFloat; + + if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && + trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) { + // send as small integer + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS ); + } else { + // send as full floating point value + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *toF, 32 ); + } + } else { + // integer + MSG_WriteBits( msg, *toF, field->bits ); + } + } + c = msg->cursize - c; + + + // + // send the arrays + // + statsbits = 0; + for (i=0 ; i<16 ; i++) { + if (to->stats[i] != from->stats[i]) { + statsbits |= 1<persistant[i] != from->persistant[i]) { + persistantbits |= 1<ammo[i] != from->ammo[i]) { + ammobits |= 1<powerups[i] != from->powerups[i]) { + powerupbits |= 1<stats[i], MAX_WEAPONS); + } + else + { + MSG_WriteShort (msg, to->stats[i]); + } + } + } + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + + + if ( persistantbits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteShort( msg, persistantbits ); + for (i=0 ; i<16 ; i++) + if (persistantbits & (1<persistant[i]); + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + + + if ( ammobits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteShort( msg, ammobits ); + for (i=0 ; i<16 ; i++) + if (ammobits & (1<ammo[i]); + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + + + if ( powerupbits ) { + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteShort( msg, powerupbits ); + for (i=0 ; i<16 ; i++) + if (powerupbits & (1<powerups[i] ); + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + +#ifdef _ONEBIT_COMBO +sendBitMask: + if (numBitsInMask) + { //don't need to send at all if we didn't pass any 1bit values + if (!bitComboDelta || + bitComboMask != *bitComboDelta || + numBitsInMask != *bitNumDelta) + { //send the mask, it changed + MSG_WriteBits(msg, 1, 1); + MSG_WriteBits(msg, bitComboMask, numBitsInMask); + if (bitComboDelta) + { + *bitComboDelta = bitComboMask; + *bitNumDelta = numBitsInMask; + } + } + else + { //send 1 bit 0 to indicate no change + MSG_WriteBits(msg, 0, 1); + } + } +#endif +} + + +/* +=================== +MSG_ReadDeltaPlayerstate +=================== +*/ +void MSG_ReadDeltaPlayerstate (msg_t *msg, playerState_t *from, playerState_t *to, qboolean isVehiclePS ) { + int i, lc; + int bits; + netField_t *field; + netField_t *PSFields = playerStateFields; + int numFields; + int startBit, endBit; + int print; + int *fromF, *toF; + int trunc; +#ifdef _ONEBIT_COMBO + int numBitsInMask = 0; +#endif + playerState_t dummy; + + if ( !from ) { + from = &dummy; + Com_Memset( &dummy, 0, sizeof( dummy ) ); + } + *to = *from; + + if ( msg->bit == 0 ) { + startBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // shownet 2/3 will interleave with other printed info, -2 will + // just print the delta records + if ( cl_shownet->integer >= 2 || cl_shownet->integer == -2 ) { + print = 1; + Com_Printf( "%3i: playerstate ", msg->readcount ); + } else { + print = 0; + } + +//=====_OPTIMIZED_VEHICLE_NETWORKING======================================================================= +#ifdef _OPTIMIZED_VEHICLE_NETWORKING + if ( isVehiclePS ) + {//a vehicle playerstate + numFields = sizeof( vehPlayerStateFields ) / sizeof( vehPlayerStateFields[0] ); + PSFields = vehPlayerStateFields; + } + else + { + int isPilot = MSG_ReadBits( msg, 1 ); + if ( isPilot ) + {//pilot riding *inside* a vehicle! + numFields = sizeof( pilotPlayerStateFields ) / sizeof( pilotPlayerStateFields[0] ) - 82; + PSFields = pilotPlayerStateFields; + } + else + {//normal client + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); + } + } +//=====_OPTIMIZED_VEHICLE_NETWORKING======================================================================= +#else//_OPTIMIZED_VEHICLE_NETWORKING + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); +#endif//_OPTIMIZED_VEHICLE_NETWORKING + + lc = MSG_ReadByte(msg); + +#ifdef _DONETPROFILE_ + int startBytes,endBytes; +#endif + + for ( i = 0, field = PSFields ; i < lc ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + +#ifdef _ONEBIT_COMBO + if (numBitsInMask < 32 && + field->bits == 1) + { + *toF = *fromF; + numBitsInMask++; + continue; + } +#endif + +#ifdef _DONETPROFILE_ + startBytes=msg->readcount; +#endif + if ( ! MSG_ReadBits( msg, 1 ) ) { + // no change + *toF = *fromF; + } else { + if ( field->bits == 0 ) { + // float + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + // integral float + trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + if ( print ) { + Com_Printf( "%s:%i ", field->name, trunc ); + } + } else { + // full floating point value + *toF = MSG_ReadBits( msg, 32 ); + if ( print ) { + Com_Printf( "%s:%f ", field->name, *(float *)toF ); + } + } + } else { + // integer + *toF = MSG_ReadBits( msg, field->bits ); + if ( print ) { + Com_Printf( "%s:%i ", field->name, *toF ); + } + } + } +#ifdef _DONETPROFILE_ + endBytes=msg->readcount; + ClReadProf().AddField(field->name,endBytes-startBytes); +#endif + } + for ( i=lc,field = &PSFields[lc];ioffset ); + toF = (int *)( (byte *)to + field->offset ); + // no change + *toF = *fromF; + } + + // read the arrays +#ifdef _DONETPROFILE_ + startBytes=msg->readcount; +#endif + if (MSG_ReadBits( msg, 1 ) ) { + // parse stats + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_STATS"); + bits = MSG_ReadShort (msg); + for (i=0 ; i<16 ; i++) { + if (bits & (1<stats[i] = MSG_ReadBits(msg, MAX_WEAPONS); + } + else + { + to->stats[i] = MSG_ReadShort(msg); + } + } + } + } +#ifdef _DONETPROFILE_ + endBytes=msg->readcount; + ClReadProf().AddField("PS_STATS",endBytes-startBytes); +#endif + + // parse persistant stats +#ifdef _DONETPROFILE_ + startBytes=msg->readcount; +#endif + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_PERSISTANT"); + bits = MSG_ReadShort (msg); + for (i=0 ; i<16 ; i++) { + if (bits & (1<persistant[i] = MSG_ReadShort(msg); + } + } + } +#ifdef _DONETPROFILE_ + endBytes=msg->readcount; + ClReadProf().AddField("PS_PERSISTANT",endBytes-startBytes); +#endif + + // parse ammo +#ifdef _DONETPROFILE_ + startBytes=msg->readcount; +#endif + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_AMMO"); + bits = MSG_ReadShort (msg); + for (i=0 ; i<16 ; i++) { + if (bits & (1<ammo[i] = MSG_ReadShort(msg); + } + } + } +#ifdef _DONETPROFILE_ + endBytes=msg->readcount; + ClReadProf().AddField("PS_AMMO",endBytes-startBytes); +#endif + + // parse powerups +#ifdef _DONETPROFILE_ + startBytes=msg->readcount; +#endif + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_POWERUPS"); + bits = MSG_ReadShort (msg); + for (i=0 ; i<16 ; i++) { + if (bits & (1<powerups[i] = MSG_ReadLong(msg); + } + } + } + } +#ifdef _DONETPROFILE_ + endBytes=msg->readcount; + ClReadProf().AddField("PS_POWERUPS",endBytes-startBytes); +#endif + + if ( print ) { + if ( msg->bit == 0 ) { + endBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf( " (%i bits)\n", endBit - startBit ); + } + +#ifdef _ONEBIT_COMBO + if (numBitsInMask && + MSG_ReadBits( msg, 1 )) + { //mask changed... + int newBitMask = MSG_ReadBits(msg, numBitsInMask); + int nOneBit = 0; + + //we have to go through all the fields again now to match the values + for ( i = 0, field = PSFields ; i < lc ; i++, field++ ) + { + if (field->bits == 1) + { //a 1 bit value, get the sent value from the mask + toF = (int *)( (byte *)to + field->offset ); + *toF = (newBitMask>>nOneBit)&1; + nOneBit++; + } + } + } +#endif +} + +/* +// New data gathered to tune Q3 to JK2MP. Takes longer to crunch and gain was minimal. +int msg_hData[256] = +{ + 3163878, // 0 + 473992, // 1 + 564019, // 2 + 136497, // 3 + 129559, // 4 + 283019, // 5 + 75812, // 6 + 179836, // 7 + 85958, // 8 + 168542, // 9 + 78898, // 10 + 82007, // 11 + 48613, // 12 + 138741, // 13 + 35482, // 14 + 47433, // 15 + 65214, // 16 + 51636, // 17 + 63741, // 18 + 52823, // 19 + 42464, // 20 + 44495, // 21 + 45347, // 22 + 40260, // 23 + 59168, // 24 + 44990, // 25 + 52957, // 26 + 42700, // 27 + 42414, // 28 + 36451, // 29 + 45653, // 30 + 44667, // 31 + 125336, // 32 + 38435, // 33 + 53658, // 34 + 42621, // 35 + 40932, // 36 + 33409, // 37 + 35470, // 38 + 40769, // 39 + 33813, // 40 + 32480, // 41 + 33664, // 42 + 32303, // 43 + 32394, // 44 + 34822, // 45 + 37724, // 46 + 48016, // 47 + 94212, // 48 + 53774, // 49 + 54522, // 50 + 44044, // 51 + 42800, // 52 + 47597, // 53 + 29742, // 54 + 30237, // 55 + 34291, // 56 + 106496, // 57 + 20963, // 58 + 19342, // 59 + 20603, // 60 + 19568, // 61 + 23013, // 62 + 23939, // 63 + 44995, // 64 + 37128, // 65 + 44264, // 66 + 46636, // 67 + 56400, // 68 + 32746, // 69 + 23458, // 70 + 29702, // 71 + 25305, // 72 + 20159, // 73 + 19645, // 74 + 20593, // 75 + 21729, // 76 + 19362, // 77 + 24760, // 78 + 22788, // 79 + 25085, // 80 + 21074, // 81 + 97271, // 82 + 22048, // 83 + 24131, // 84 + 19287, // 85 + 20296, // 86 + 20131, // 87 + 86477, // 88 + 25352, // 89 + 20872, // 90 + 21382, // 91 + 38744, // 92 + 137256, // 93 + 26025, // 94 + 22243, // 95 + 23974, // 96 + 43305, // 97 + 28191, // 98 + 34638, // 99 + 37613, // 100 + 46003, // 101 + 31415, // 102 + 25746, // 103 + 28338, // 104 + 34689, // 105 + 24948, // 106 + 27110, // 107 + 39950, // 108 + 32793, // 109 + 42639, // 110 + 47883, // 111 + 37439, // 112 + 23875, // 113 + 36092, // 114 + 46471, // 115 + 37392, // 116 + 33063, // 117 + 29604, // 118 + 42140, // 119 + 61745, // 120 + 45618, // 121 + 51779, // 122 + 49684, // 123 + 57644, // 124 + 65021, // 125 + 67318, // 126 + 88197, // 127 + 258378, // 128 + 76806, // 129 + 72430, // 130 + 64936, // 131 + 62196, // 132 + 56461, // 133 + 166474, // 134 + 70036, // 135 + 40735, // 136 + 29598, // 137 + 26966, // 138 + 26093, // 139 + 25853, // 140 + 26065, // 141 + 26176, // 142 + 26777, // 143 + 26684, // 144 + 23880, // 145 + 22932, // 146 + 24566, // 147 + 24305, // 148 + 26399, // 149 + 23487, // 150 + 24485, // 151 + 25956, // 152 + 26065, // 153 + 26151, // 154 + 23111, // 155 + 23900, // 156 + 22128, // 157 + 24096, // 158 + 20863, // 159 + 24298, // 160 + 22572, // 161 + 22364, // 162 + 20813, // 163 + 21414, // 164 + 21570, // 165 + 20799, // 166 + 20971, // 167 + 22485, // 168 + 20397, // 169 + 88096, // 170 + 17802, // 171 + 20091, // 172 + 84250, // 173 + 21953, // 174 + 21406, // 175 + 23401, // 176 + 19546, // 177 + 19180, // 178 + 18843, // 179 + 20673, // 180 + 19918, // 181 + 20640, // 182 + 20326, // 183 + 21174, // 184 + 21736, // 185 + 22511, // 186 + 20290, // 187 + 23303, // 188 + 19800, // 189 + 25465, // 190 + 22801, // 191 + 28831, // 192 + 26663, // 193 + 36485, // 194 + 45768, // 195 + 49795, // 196 + 36026, // 197 + 24119, // 198 + 18543, // 199 + 19261, // 200 + 17137, // 201 + 19435, // 202 + 23672, // 203 + 22988, // 204 + 18107, // 205 + 18734, // 206 + 19847, // 207 + 101897, // 208 + 18405, // 209 + 21260, // 210 + 17818, // 211 + 18971, // 212 + 19317, // 213 + 19112, // 214 + 19395, // 215 + 20688, // 216 + 18438, // 217 + 18945, // 218 + 29309, // 219 + 19666, // 220 + 18735, // 221 + 87691, // 222 + 18478, // 223 + 22634, // 224 + 20984, // 225 + 20079, // 226 + 18624, // 227 + 20045, // 228 + 18369, // 229 + 19014, // 230 + 83179, // 231 + 20899, // 232 + 17854, // 233 + 19332, // 234 + 17875, // 235 + 28647, // 236 + 17465, // 237 + 20277, // 238 + 18994, // 239 + 22192, // 240 + 17443, // 241 + 20243, // 242 + 28174, // 243 + 134871, // 244 + 17753, // 245 + 18924, // 246 + 18281, // 247 + 18937, // 248 + 17419, // 249 + 20679, // 250 + 17865, // 251 + 17984, // 252 + 58615, // 253 + 35506, // 254 + 123499, // 255 +}; +*/ + +// Q3 TA freq. table. +int msg_hData[256] = { +250315, // 0 +41193, // 1 +6292, // 2 +7106, // 3 +3730, // 4 +3750, // 5 +6110, // 6 +23283, // 7 +33317, // 8 +6950, // 9 +7838, // 10 +9714, // 11 +9257, // 12 +17259, // 13 +3949, // 14 +1778, // 15 +8288, // 16 +1604, // 17 +1590, // 18 +1663, // 19 +1100, // 20 +1213, // 21 +1238, // 22 +1134, // 23 +1749, // 24 +1059, // 25 +1246, // 26 +1149, // 27 +1273, // 28 +4486, // 29 +2805, // 30 +3472, // 31 +21819, // 32 +1159, // 33 +1670, // 34 +1066, // 35 +1043, // 36 +1012, // 37 +1053, // 38 +1070, // 39 +1726, // 40 +888, // 41 +1180, // 42 +850, // 43 +960, // 44 +780, // 45 +1752, // 46 +3296, // 47 +10630, // 48 +4514, // 49 +5881, // 50 +2685, // 51 +4650, // 52 +3837, // 53 +2093, // 54 +1867, // 55 +2584, // 56 +1949, // 57 +1972, // 58 +940, // 59 +1134, // 60 +1788, // 61 +1670, // 62 +1206, // 63 +5719, // 64 +6128, // 65 +7222, // 66 +6654, // 67 +3710, // 68 +3795, // 69 +1492, // 70 +1524, // 71 +2215, // 72 +1140, // 73 +1355, // 74 +971, // 75 +2180, // 76 +1248, // 77 +1328, // 78 +1195, // 79 +1770, // 80 +1078, // 81 +1264, // 82 +1266, // 83 +1168, // 84 +965, // 85 +1155, // 86 +1186, // 87 +1347, // 88 +1228, // 89 +1529, // 90 +1600, // 91 +2617, // 92 +2048, // 93 +2546, // 94 +3275, // 95 +2410, // 96 +3585, // 97 +2504, // 98 +2800, // 99 +2675, // 100 +6146, // 101 +3663, // 102 +2840, // 103 +14253, // 104 +3164, // 105 +2221, // 106 +1687, // 107 +3208, // 108 +2739, // 109 +3512, // 110 +4796, // 111 +4091, // 112 +3515, // 113 +5288, // 114 +4016, // 115 +7937, // 116 +6031, // 117 +5360, // 118 +3924, // 119 +4892, // 120 +3743, // 121 +4566, // 122 +4807, // 123 +5852, // 124 +6400, // 125 +6225, // 126 +8291, // 127 +23243, // 128 +7838, // 129 +7073, // 130 +8935, // 131 +5437, // 132 +4483, // 133 +3641, // 134 +5256, // 135 +5312, // 136 +5328, // 137 +5370, // 138 +3492, // 139 +2458, // 140 +1694, // 141 +1821, // 142 +2121, // 143 +1916, // 144 +1149, // 145 +1516, // 146 +1367, // 147 +1236, // 148 +1029, // 149 +1258, // 150 +1104, // 151 +1245, // 152 +1006, // 153 +1149, // 154 +1025, // 155 +1241, // 156 +952, // 157 +1287, // 158 +997, // 159 +1713, // 160 +1009, // 161 +1187, // 162 +879, // 163 +1099, // 164 +929, // 165 +1078, // 166 +951, // 167 +1656, // 168 +930, // 169 +1153, // 170 +1030, // 171 +1262, // 172 +1062, // 173 +1214, // 174 +1060, // 175 +1621, // 176 +930, // 177 +1106, // 178 +912, // 179 +1034, // 180 +892, // 181 +1158, // 182 +990, // 183 +1175, // 184 +850, // 185 +1121, // 186 +903, // 187 +1087, // 188 +920, // 189 +1144, // 190 +1056, // 191 +3462, // 192 +2240, // 193 +4397, // 194 +12136, // 195 +7758, // 196 +1345, // 197 +1307, // 198 +3278, // 199 +1950, // 200 +886, // 201 +1023, // 202 +1112, // 203 +1077, // 204 +1042, // 205 +1061, // 206 +1071, // 207 +1484, // 208 +1001, // 209 +1096, // 210 +915, // 211 +1052, // 212 +995, // 213 +1070, // 214 +876, // 215 +1111, // 216 +851, // 217 +1059, // 218 +805, // 219 +1112, // 220 +923, // 221 +1103, // 222 +817, // 223 +1899, // 224 +1872, // 225 +976, // 226 +841, // 227 +1127, // 228 +956, // 229 +1159, // 230 +950, // 231 +7791, // 232 +954, // 233 +1289, // 234 +933, // 235 +1127, // 236 +3207, // 237 +1020, // 238 +927, // 239 +1355, // 240 +768, // 241 +1040, // 242 +745, // 243 +952, // 244 +805, // 245 +1073, // 246 +740, // 247 +1013, // 248 +805, // 249 +1008, // 250 +796, // 251 +996, // 252 +1057, // 253 +11457, // 254 +13504, // 255 +}; + +#ifndef _USINGNEWHUFFTABLE_ + +void MSG_initHuffman() { + +#ifdef _XBOX + // Try to use pre-built table: + extern bool Huff_Load( huffman_t *huff ); + if( Huff_Load( &msgHuff ) ) + { + msgInit = qtrue; + return; + } +#endif + + int i,j; + +#ifdef _NEWHUFFTABLE_ + fp=fopen("c:\\netchan.bin", "a"); +#endif // _NEWHUFFTABLE_ + + msgInit = qtrue; + Huff_Init(&msgHuff); + for(i=0;i<256;i++) { + for (j=0;jname, field->mCount); + field->mCount = 0; + } + + Com_Printf("\nPlayer State Fields:\n"); + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); + for ( i = 0, field = playerStateFields ; i < numFields ; i++, field++ ) + { + Com_Printf("%s\t\t%d\n", field->name, field->mCount); + field->mCount = 0; + } + +#endif // FINAL_BUILD +#endif +} + +//=========================================================================== diff --git a/codemp/qcommon/net_chan.cpp b/codemp/qcommon/net_chan.cpp new file mode 100644 index 0000000..cb47319 --- /dev/null +++ b/codemp/qcommon/net_chan.cpp @@ -0,0 +1,703 @@ + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif +/* + +packet header +------------- +4 outgoing sequence. high bit will be set if this is a fragmented message +[2 qport (only for client to server)] +[2 fragment start byte] +[2 fragment length. if < FRAGMENT_SIZE, this is the last fragment] + +if the sequence number is -1, the packet should be handled as an out-of-band +message instead of as part of a netcon. + +All fragments will have the same sequence numbers. + +The qport field is a workaround for bad address translating routers that +sometimes remap the client's source port on a packet during gameplay. + +If the base part of the net address matches and the qport matches, then the +channel matches even if the IP port differs. The IP port should be updated +to the new value before sending out any replies. + +*/ + +#ifdef _XBOX +#define MAX_PACKETLEN 1359 // UDP total packet size +#define FRAGMENT_SIZE (MAX_PACKETLEN - 55 - 10) // 55 is packet overhead ||| 10 is fudge factor - needed due to huffman? +#else +#define MAX_PACKETLEN 1400 // max size of a network packet +#define FRAGMENT_SIZE (MAX_PACKETLEN - 100) +#define PACKET_HEADER 10 // two ints and a short +#endif + +#define FRAGMENT_BIT (1<<31) + +cvar_t *showpackets; +cvar_t *showdrop; +cvar_t *qport; +cvar_t *net_killdroppedfragments; + +static char *netsrcString[2] = { + "client", + "server" +}; + +/* +=============== +Netchan_Init + +=============== +*/ +void Netchan_Init( int port ) { +#ifdef _XBOX + CM_START_LOOP(); + if (!ClientManager::ActiveClient().loopbacks) + { + Z_PushNewDeleteTag( TAG_CLIENT_MANAGER ); + + ClientManager::ActiveClient().loopbacks = new loopback_t[2]; + memset(ClientManager::ActiveClient().loopbacks, 0, sizeof(loopback_t) * 2); + + Z_PopNewDeleteTag(); + } + CM_END_LOOP(); +#endif + port &= 0xffff; + showpackets = Cvar_Get ("showpackets", "0", CVAR_TEMP ); + showdrop = Cvar_Get ("showdrop", "0", CVAR_TEMP ); + qport = Cvar_Get ("net_qport", va("%i", port), CVAR_INIT ); + net_killdroppedfragments = Cvar_Get ("net_killdroppedfragments", "0", CVAR_TEMP); +} + +/* +============== +Netchan_Setup + +called to open a channel to a remote system +============== +*/ +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) { + Com_Memset (chan, 0, sizeof(*chan)); + + chan->sock = sock; + chan->remoteAddress = adr; + chan->qport = qport; + chan->incomingSequence = 0; + chan->outgoingSequence = 1; +} + +/* +================= +Netchan_TransmitNextFragment + +Send one fragment of the current message +================= +*/ +bool Netchan_TransmitNextFragment( netchan_t *chan ) { + msg_t send; + byte send_buf[MAX_PACKETLEN]; + int fragmentLength; + + // write the packet header + MSG_InitOOB (&send, send_buf, sizeof(send_buf)); // <-- only do the oob here + + MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT ); + + // send the qport if we are a client + if ( chan->sock == NS_CLIENT ) { + if(ClientManager::splitScreenMode == qtrue) + MSG_WriteShort( &send, ClientManager::ActivePort() ); + else + MSG_WriteShort( &send, qport->integer ); + } + + // copy the reliable message to the packet first + fragmentLength = FRAGMENT_SIZE; + if ( chan->unsentFragmentStart + fragmentLength > chan->unsentLength ) { + fragmentLength = chan->unsentLength - chan->unsentFragmentStart; + } + + MSG_WriteShort( &send, chan->unsentFragmentStart ); + MSG_WriteShort( &send, fragmentLength ); + MSG_WriteData( &send, chan->unsentBuffer + chan->unsentFragmentStart, fragmentLength ); + + // send the datagram + bool retVal = NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + + chan->unsentFragmentStart += fragmentLength; + + // this exit condition is a little tricky, because a packet + // that is exactly the fragment length still needs to send + // a second packet of zero length so that the other side + // can tell there aren't more to follow + if ( chan->unsentFragmentStart == chan->unsentLength && fragmentLength != FRAGMENT_SIZE ) { + chan->outgoingSequence++; + chan->unsentFragments = qfalse; + } + + return retVal; +} + + +/* +=============== +Netchan_Transmit + +Sends a message to a connection, fragmenting if necessary +A 0 length will still generate a packet. +================ +*/ +bool Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { + msg_t send; + byte send_buf[MAX_PACKETLEN]; + + if ( length > MAX_MSGLEN ) { + Com_Error( ERR_DROP, "Netchan_Transmit: length = %i", length ); + } + chan->unsentFragmentStart = 0; + + if (chan->unsentFragments) + { + Com_Printf("[ISM] Stomping Unsent Fragments %s\n",netsrcString[ chan->sock ]); + } + // fragment large reliable messages + if ( length >= FRAGMENT_SIZE ) + { + chan->unsentFragments = qtrue; + chan->unsentLength = length; + Com_Memcpy( chan->unsentBuffer, data, length ); + + // only send the first fragment now + return Netchan_TransmitNextFragment( chan ); + } + + // write the packet header + MSG_InitOOB (&send, send_buf, sizeof(send_buf)); + + MSG_WriteLong( &send, chan->outgoingSequence ); + chan->outgoingSequence++; + + // send the qport if we are a client + if ( chan->sock == NS_CLIENT ) { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + MSG_WriteShort( &send, ClientManager::ActivePort() ); + } + else +#endif + MSG_WriteShort( &send, qport->integer ); + } + + MSG_WriteData( &send, data, length ); + + // send the datagram + return NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); +/* + if ( showpackets->integer ) { + Com_Printf( "%s send %4i : s=%i ack=%i\n" + , netsrcString[ chan->sock ] + , send.cursize + , chan->outgoingSequence - 1 + , chan->incomingSequence ); + } +*/ +} + +/* +================= +Netchan_Process + +Returns qfalse if the message should not be processed due to being +out of order or a fragment. + +Msg must be large enough to hold MAX_MSGLEN, because if this is the +final fragment of a multi-part message, the entire thing will be +copied out. +================= +*/ +qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { + int sequence; + int qport; + int fragmentStart, fragmentLength; + qboolean fragmented; + + // get sequence numbers + MSG_BeginReadingOOB( msg ); + sequence = MSG_ReadLong( msg ); + + // check for fragment information + if ( sequence & FRAGMENT_BIT ) { + sequence &= ~FRAGMENT_BIT; + fragmented = qtrue; + } else { + fragmented = qfalse; + } + + // read the qport if we are a server + if ( chan->sock == NS_SERVER ) { + qport = MSG_ReadShort( msg ); + } + + // read the fragment information + if ( fragmented ) { + fragmentStart = (unsigned short)MSG_ReadShort( msg ); + fragmentLength = (unsigned short)MSG_ReadShort( msg ); + } else { + fragmentStart = 0; // stop warning message + fragmentLength = 0; + } + + if ( showpackets->integer ) { + if ( fragmented ) { + Com_Printf( "%s recv %4i : s=%i fragment=%i,%i\n" + , netsrcString[ chan->sock ] + , msg->cursize + , sequence + , fragmentStart, fragmentLength ); + } else { + Com_Printf( "%s recv %4i : s=%i\n" + , netsrcString[ chan->sock ] + , msg->cursize + , sequence ); + } + } + + // + // discard out of order or duplicated packets + // + if ( sequence <= chan->incomingSequence ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Out of order packet %i at %i\n" + , NET_AdrToString( chan->remoteAddress ) + , sequence + , chan->incomingSequence ); + } + return qfalse; + } + + // + // dropped packets don't keep the message from being used + // + chan->dropped = sequence - (chan->incomingSequence+1); + if ( chan->dropped > 0 ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Dropped %i packets at %i\n" + , NET_AdrToString( chan->remoteAddress ) + , chan->dropped + , sequence ); + } + } + + + // + // if this is the final framgent of a reliable message, + // bump incoming_reliable_sequence + // + if ( fragmented ) { + // make sure we + if ( sequence != chan->fragmentSequence ) { + chan->fragmentSequence = sequence; + chan->fragmentLength = 0; + } + + // if we missed a fragment, dump the message + if ( fragmentStart != chan->fragmentLength ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Dropped a message fragment\n" + , NET_AdrToString( chan->remoteAddress ) + , sequence); + } + // we can still keep the part that we have so far, + // so we don't need to clear chan->fragmentLength + return qfalse; + } + + // copy the fragment to the fragment buffer + if ( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize || + chan->fragmentLength + fragmentLength > sizeof( chan->fragmentBuffer ) ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf ("%s:illegal fragment length\n" + , NET_AdrToString (chan->remoteAddress ) ); + } + return qfalse; + } + + Com_Memcpy( chan->fragmentBuffer + chan->fragmentLength, + msg->data + msg->readcount, fragmentLength ); + + chan->fragmentLength += fragmentLength; + + // if this wasn't the last fragment, don't process anything + if ( fragmentLength == FRAGMENT_SIZE ) { + return qfalse; + } + + if ( chan->fragmentLength+4 > msg->maxsize ) { + Com_Printf( "%s:fragmentLength %i > msg->maxsize\n" + , NET_AdrToString (chan->remoteAddress ), + chan->fragmentLength+4 ); + return qfalse; + } + + // copy the full message over the partial fragment + + // make sure the sequence number is still there + *(int *)msg->data = LittleLong( sequence ); + + Com_Memcpy( msg->data + 4, chan->fragmentBuffer, chan->fragmentLength ); + msg->cursize = chan->fragmentLength + 4; + chan->fragmentLength = 0; + msg->readcount = 4; // past the sequence number + msg->bit = 32; // past the sequence number + + // but I am a wuss -mw + // chan->incomingSequence = sequence; // lets not accept any more with this sequence number -gil + return qtrue; + } + + // + // the message can now be read from the current message pointer + // + chan->incomingSequence = sequence; + + return qtrue; +} + + +//============================================================================== + +/* +=================== +NET_CompareBaseAdr + +Compares without the port +=================== +*/ +qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b) +{ + if (a.type != b.type) + return qfalse; + + if (a.type == NA_LOOPBACK) + return qtrue; + + if (a.type == NA_IP) + { + if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3]) + return qtrue; + return qfalse; + } + +#ifndef _XBOX // No IPX + if (a.type == NA_IPX) + { + if ((memcmp(a.ipx, b.ipx, 10) == 0)) + return qtrue; + return qfalse; + } +#endif + + Com_Printf ("NET_CompareBaseAdr: bad address type\n"); + return qfalse; +} + +const char *NET_AdrToString (netadr_t a) +{ + static char s[64]; + + if (a.type == NA_LOOPBACK) { + Com_sprintf (s, sizeof(s), "loopback"); + } else if (a.type == NA_BOT) { + Com_sprintf (s, sizeof(s), "bot"); + } else if (a.type == NA_IP) { + Com_sprintf (s, sizeof(s), "%i.%i.%i.%i:%i", + a.ip[0], a.ip[1], a.ip[2], a.ip[3], BigShort(a.port)); + } else if (a.type == NA_BAD) { + Com_sprintf (s, sizeof(s), "BAD"); + } else { + Com_sprintf (s, sizeof(s), "%02x%02x%02x%02x.%02x%02x%02x%02x%02x%02x:%i", + a.ipx[0], a.ipx[1], a.ipx[2], a.ipx[3], a.ipx[4], a.ipx[5], a.ipx[6], a.ipx[7], a.ipx[8], a.ipx[9], + BigShort(a.port)); + } + + return s; +} + + +qboolean NET_CompareAdr (netadr_t a, netadr_t b) +{ + if (a.type != b.type) + return qfalse; + + if (a.type == NA_LOOPBACK) + return qtrue; + + if (a.type == NA_IP) + { + if (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] && a.port == b.port) + return qtrue; + return qfalse; + } + +#ifndef _XBOX // No IPX + if (a.type == NA_IPX) + { + if ((memcmp(a.ipx, b.ipx, 10) == 0) && a.port == b.port) + return qtrue; + return qfalse; + } +#endif + + Com_Printf ("NET_CompareAdr: bad address type\n"); + return qfalse; +} + + +qboolean NET_IsLocalAddress( netadr_t adr ) { + return (qboolean)(adr.type == NA_LOOPBACK); +} + + + +/* +============================================================================= + +LOOPBACK BUFFERS FOR LOCAL PLAYER + +============================================================================= +*/ + +// there needs to be enough loopback messages to hold a complete +// gamestate of maximum size +//#define MAX_LOOPBACK 16 + +//typedef struct { +// byte data[MAX_PACKETLEN]; +// int datalen; +//} loopmsg_t; + +//typedef struct { +// loopmsg_t msgs[MAX_LOOPBACK]; +// int get, send; +//} loopback_t; + +//loopback_t loopbacks[2]; + + +qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message) +{ + int i; + loopback_t *loop; + + loop = (loopback_t*)&ClientManager::ActiveClient().loopbacks[sock]; + + if (loop->send - loop->get > MAX_LOOPBACK) + loop->get = loop->send - MAX_LOOPBACK; + + if (loop->get >= loop->send) + return qfalse; + + i = loop->get & (MAX_LOOPBACK-1); + loop->get++; + + Com_Memcpy (net_message->data, loop->msgs[i].data, loop->msgs[i].datalen); + net_message->cursize = loop->msgs[i].datalen; + Com_Memset (net_from, 0, sizeof(*net_from)); + net_from->type = NA_LOOPBACK; +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + if (sock == NS_CLIENT) + net_from->port = 0;// // server is on port 0; + else if (sock == NS_SERVER) + net_from->port = ClientManager::ActiveClientNum(); + else + assert(0 && "what happened????"); + } +#endif + return qtrue; + +} + + +void NET_SendLoopPacket (netsrc_t sock, int length, const void *data, netadr_t to) +{ + int i; + loopback_t *loop; + + loop = (loopback_t*)&ClientManager::ActiveClient().loopbacks[sock^1]; + + i = loop->send & (MAX_LOOPBACK-1); + loop->send++; + + Com_Memcpy (loop->msgs[i].data, data, length); + loop->msgs[i].datalen = length; +} + +//============================================================================= + + +bool NET_SendPacket( netsrc_t sock, int length, const void *data, netadr_t to ) { + + // sequenced packets are shown in netchan, so just show oob + if ( showpackets->integer && *(int *)data == -1 ) { + Com_Printf ("send packet %4i\n", length); + } + + if ( to.type == NA_LOOPBACK ) { +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + to.port = ClientManager::ActiveClientNum(); +#endif + NET_SendLoopPacket (sock, length, data, to); + return true; + } + if ( to.type == NA_BOT ) { + return true; + } + if ( to.type == NA_BAD ) { + return false; + } + + return Sys_SendPacket( length, data, to ); +} + + +/* +=============== +NET_OutOfBandPrint + +Sends a text message in an out-of-band datagram +================ +*/ +void QDECL NET_OutOfBandPrint( netsrc_t sock, netadr_t adr, const char *format, ... ) { + va_list argptr; + char string[MAX_MSGLEN]; + + + // set the header + string[0] = -1; + string[1] = -1; + string[2] = -1; + string[3] = -1; + + va_start( argptr, format ); + vsprintf( string+4, format, argptr ); + va_end( argptr ); + + // send the datagram + NET_SendPacket( sock, strlen( string ), string, adr ); +} + +// Used for packets that need to be broadcast. The only such +// packets are "infoResponse" +void QDECL NET_BroadcastPrint( netsrc_t sock, const char *format, ... ) +{ + va_list argptr; + char string[MAX_MSGLEN]; + + // set the header + string[0] = -1; + string[1] = -1; + string[2] = -1; + string[3] = -1; + + va_start( argptr, format ); + vsprintf( string+4, format, argptr ); + va_end( argptr ); + + // send the datagram + Sys_SendBroadcastPacket( strlen( string ), string ); +} + + + +/* +=============== +NET_OutOfBandPrint + +Sends a data message in an out-of-band datagram (only used for "connect") +================ +*/ +bool QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len ) { + byte string[MAX_MSGLEN+4]; + int i; + msg_t mbuf; + + // set the header + string[0] = 0xff; + string[1] = 0xff; + string[2] = 0xff; + string[3] = 0xff; + + for(i=0;itype = NA_LOOPBACK; + return qtrue; + } + + // look for a port number + Q_strncpyz( base, s, sizeof( base ) ); + port = strstr( base, ":" ); + if ( port ) { + *port = 0; + port++; + } + + r = Sys_StringToAdr( base, a ); + + if ( !r ) { + a->type = NA_BAD; + return qfalse; + } + + // inet_addr returns this if out of range + if ( a->ip[0] == 255 && a->ip[1] == 255 && a->ip[2] == 255 && a->ip[3] == 255 ) { + a->type = NA_BAD; + return qfalse; + } + + if ( port ) { + a->port = BigShort( (short)atoi( port ) ); + } else { + a->port = BigShort( PORT_SERVER ); + } + + return qtrue; +} + diff --git a/codemp/qcommon/platform.h b/codemp/qcommon/platform.h new file mode 100644 index 0000000..1517b28 --- /dev/null +++ b/codemp/qcommon/platform.h @@ -0,0 +1,22 @@ +// Simple header file to dispatch to the relevant platform API headers +#ifndef _PLATFORM_H +#define _PLATFORM_H + +#if defined(_XBOX) +#include +#endif + +#if defined(_WINDOWS) +#include +#endif + +#if defined (__linux__) +typedef const char *LPCTSTR; +typedef const char *LPCSTR; +typedef unsigned long DWORD; +typedef unsigned int UINT; +typedef void* HANDLE; +typedef DWORD COLORREF; +typedef unsigned char BYTE; +#endif +#endif diff --git a/codemp/qcommon/q_math.cpp b/codemp/qcommon/q_math.cpp new file mode 100644 index 0000000..9f2f6e3 --- /dev/null +++ b/codemp/qcommon/q_math.cpp @@ -0,0 +1,4 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "../game/q_math.c" diff --git a/codemp/qcommon/q_shared.cpp b/codemp/qcommon/q_shared.cpp new file mode 100644 index 0000000..3bfd84d --- /dev/null +++ b/codemp/qcommon/q_shared.cpp @@ -0,0 +1,4 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "../game/q_shared.c" diff --git a/codemp/qcommon/qcommon.h b/codemp/qcommon/qcommon.h new file mode 100644 index 0000000..26290ca --- /dev/null +++ b/codemp/qcommon/qcommon.h @@ -0,0 +1,1158 @@ +// qcommon.h -- definitions common between client and server, but not game.or ref modules +#ifndef _QCOMMON_H_ +#define _QCOMMON_H_ + +#include "../qcommon/cm_public.h" +#include "../game/q_shared.h" + +//#define PRE_RELEASE_DEMO + +//#define USE_CD_KEY + +//============================================================================ + +// +// msg.c +// +typedef struct { + qboolean allowoverflow; // if false, do a Com_Error + qboolean overflowed; // set to true if the buffer size failed (with allowoverflow set) + qboolean oob; // set to true if the buffer size failed (with allowoverflow set) + byte *data; + int maxsize; + int cursize; + int readcount; + int bit; // for bitwise reads and writes +} msg_t; + +void MSG_Init (msg_t *buf, byte *data, int length); +void MSG_InitOOB( msg_t *buf, byte *data, int length ); +void MSG_Clear (msg_t *buf); +void MSG_WriteData (msg_t *buf, const void *data, int length); +void MSG_Bitstream( msg_t *buf ); + + +struct usercmd_s; +struct entityState_s; +struct playerState_s; + +void MSG_WriteBits( msg_t *msg, int value, int bits ); + +void MSG_WriteChar (msg_t *sb, int c); +void MSG_WriteByte (msg_t *sb, int c); +void MSG_WriteShort (msg_t *sb, int c); +void MSG_WriteLong (msg_t *sb, int c); +void MSG_WriteFloat (msg_t *sb, float f); +void MSG_WriteString (msg_t *sb, const char *s); +void MSG_WriteBigString (msg_t *sb, const char *s); +void MSG_WriteAngle16 (msg_t *sb, float f); + +void MSG_BeginReading (msg_t *sb); +void MSG_BeginReadingOOB(msg_t *sb); + +int MSG_ReadBits( msg_t *msg, int bits ); + +int MSG_ReadChar (msg_t *sb); +int MSG_ReadByte (msg_t *sb); +int MSG_ReadShort (msg_t *sb); +int MSG_ReadLong (msg_t *sb); +float MSG_ReadFloat (msg_t *sb); +char *MSG_ReadString (msg_t *sb); +char *MSG_ReadBigString (msg_t *sb); +char *MSG_ReadStringLine (msg_t *sb); +float MSG_ReadAngle16 (msg_t *sb); +void MSG_ReadData (msg_t *sb, void *buffer, int size); + + +void MSG_WriteDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to ); +void MSG_ReadDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to ); + +void MSG_WriteDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ); +void MSG_ReadDeltaUsercmdKey( msg_t *msg, int key, usercmd_t *from, usercmd_t *to ); + +void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to + , qboolean force ); +void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, + int number ); + +#ifdef _ONEBIT_COMBO +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to, int *bitComboDelta, int *bitNumDelta, qboolean isVehiclePS = qfalse ); +#else +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to, qboolean isVehiclePS = qfalse ); +#endif +void MSG_ReadDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to, qboolean isVehiclePS = qfalse ); + + +void MSG_ReportChangeVectors_f( void ); + +//============================================================================ + +/* +============================================================== + +NET + +============================================================== +*/ + +#define PACKET_BACKUP 32 // number of old messages that must be kept on client and + // server for delta comrpession and ping estimation +#define PACKET_MASK (PACKET_BACKUP-1) + +#define MAX_PACKET_USERCMDS 32 // max number of usercmd_t in a packet + +#define PORT_ANY -1 + +//#define MAX_RELIABLE_COMMANDS 128 // max string commands buffered for restransmit +#define MAX_RELIABLE_COMMANDS 64 // max string commands buffered for restransmit + +typedef enum { + NA_BOT, + NA_BAD, // an address lookup failed + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, + NA_IPX, + NA_BROADCAST_IPX +} netadrtype_t; + +typedef enum { + NS_CLIENT, + NS_SERVER +} netsrc_t; + +typedef struct { + netadrtype_t type; + + byte ip[4]; + byte ipx[10]; + + unsigned short port; +} netadr_t; + +void NET_Init( void ); +void NET_Shutdown( void ); +void NET_Restart( void ); +void NET_Config( qboolean enableNetworking ); + +bool NET_SendPacket (netsrc_t sock, int length, const void *data, netadr_t to); +void QDECL NET_OutOfBandPrint( netsrc_t net_socket, netadr_t adr, const char *format, ...); +void QDECL NET_BroadcastPrint( netsrc_t net_socket, const char *format, ...); +bool QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len ); + +qboolean NET_CompareAdr (netadr_t a, netadr_t b); +qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b); +qboolean NET_IsLocalAddress (netadr_t adr); +const char *NET_AdrToString (netadr_t a); +qboolean NET_StringToAdr ( const char *s, netadr_t *a); +qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message); +void NET_Sleep(int msec); + +extern byte broadcast_nonce[8]; + +//#define MAX_MSGLEN 49152 // max length of a message, which may + // be fragmented into multiple packets + +// What the fuck? I haven't seen a message bigger than 9k. Let's increase when we NEED to, eh? +#define MAX_MSGLEN 16384 // max length of a message, which may + // be fragmented into multiple packets + +//rww - 6/28/02 - Changed from 16384 to match sof2's. This does seem rather huge, but I guess it doesn't really hurt anything. + +#define MAX_DOWNLOAD_WINDOW 8 // max of eight download frames +#define MAX_DOWNLOAD_BLKSIZE 2048 // 2048 byte block chunks + + +/* +Netchan handles packet fragmentation and out of order / duplicate suppression +*/ + +typedef struct { + netsrc_t sock; + + int dropped; // between last packet and previous + + netadr_t remoteAddress; + int qport; // qport value to write when transmitting + + // sequencing variables + int incomingSequence; + int outgoingSequence; + + // incoming fragment assembly buffer + int fragmentSequence; + int fragmentLength; + byte fragmentBuffer[MAX_MSGLEN]; + + // outgoing fragment buffer + // we need to space out the sending of large fragmented messages + qboolean unsentFragments; + int unsentFragmentStart; + int unsentLength; + byte unsentBuffer[MAX_MSGLEN]; +} netchan_t; + +void Netchan_Init( int qport ); +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ); + +bool Netchan_Transmit( netchan_t *chan, int length, const byte *data ); +bool Netchan_TransmitNextFragment( netchan_t *chan ); + +qboolean Netchan_Process( netchan_t *chan, msg_t *msg ); + + +/* +============================================================== + +PROTOCOL + +============================================================== +*/ + +#define PROTOCOL_VERSION 25 + +#ifndef _XBOX // No gethostbyname(), and can't really use this stuff +#define UPDATE_SERVER_NAME "updatejk3.ravensoft.com" +#define MASTER_SERVER_NAME "masterjk3.ravensoft.com" + +#ifdef USE_CD_KEY +#define AUTHORIZE_SERVER_NAME "authorizejk3.ravensoft.com" +#endif +#endif // _XBOX + +#ifdef _XBOX // Use port number 1000 for less bandwidth! +#define PORT_SERVER 1000 +#define NUM_SERVER_PORTS 1 +// Some random port number in the 1000-1255 range for session discovery: +#define PORT_BROADCAST 1007 +#else +#define PORT_MASTER 29060 +#define PORT_UPDATE 29061 +//#define PORT_AUTHORIZE 29062 +#define PORT_SERVER 29070 //...+9 more for multiple servers +#define NUM_SERVER_PORTS 4 // broadcast scan this many ports after + // PORT_SERVER so a single machine can + // run multiple servers +#endif + +// the svc_strings[] array in cl_parse.c should mirror this +// +// server to client +// +enum svc_ops_e { + svc_bad, + svc_nop, + svc_gamestate, + svc_configstring, // [short] [string] only in gamestate messages + svc_baseline, // only in gamestate messages + svc_serverCommand, // [string] to be executed by client game module + svc_download, // [short] size [size bytes] + svc_snapshot, + svc_setgame, + svc_mapchange, +#ifdef _XBOX + svc_newpeer, //jsw//inform current clients about new player + svc_removepeer, //jsw//inform current clients about dying player + svc_xbInfo, //jsw//update client with current server xbOnlineInfo + svc_plyrPos0, // BTO - All client positions, used for voice proximity + svc_plyrPos1, // BTO - All client positions, used for voice proximity + svc_plyrPos2, // BTO - All client positions, used for voice proximity + svc_plyrPos3, // BTO - All client positions, used for voice proximity + svc_plyrPos4, // BTO - All client positions, used for voice proximity + svc_plyrPos5, // BTO - All client positions, used for voice proximity + svc_plyrPos6, // BTO - All client positions, used for voice proximity + svc_plyrPos7, // BTO - All client positions, used for voice proximity + svc_plyrPos8, // BTO - All client positions, used for voice proximity + svc_plyrPos9, // BTO - All client positions, used for voice proximity +#endif + svc_EOF +}; + + +// +// client to server +// +enum clc_ops_e { + clc_bad, + clc_nop, + clc_move, // [[usercmd_t] + clc_moveNoDelta, // [[usercmd_t] + clc_clientCommand, // [string] message + clc_EOF +}; + +/* +============================================================== + +VIRTUAL MACHINE + +============================================================== +*/ + +typedef struct vm_s vm_t; + +typedef enum { + VMI_NATIVE, + VMI_BYTECODE, + VMI_COMPILED +} vmInterpret_t; + +typedef enum { + TRAP_MEMSET = 100, + TRAP_MEMCPY, + TRAP_STRNCPY, + TRAP_SIN, + TRAP_COS, + TRAP_ATAN2, + TRAP_SQRT, + TRAP_MATRIXMULTIPLY, + TRAP_ANGLEVECTORS, + TRAP_PERPENDICULARVECTOR, + TRAP_FLOOR, + TRAP_CEIL, + + TRAP_TESTPRINTINT, + TRAP_TESTPRINTFLOAT, + + TRAP_ACOS, + TRAP_ASIN +} sharedTraps_t; + +void VM_Init( void ); +vm_t *VM_Create( const char *module, int (*systemCalls)(int *), + vmInterpret_t interpret ); +// module should be bare: "cgame", not "cgame.dll" or "vm/cgame.qvm" + +void VM_Free( vm_t *vm ); +void VM_Clear(void); +vm_t *VM_Restart( vm_t *vm ); + +int QDECL VM_Call( vm_t *vm, int callNum, ... ); + +void VM_Debug( int level ); + +void VM_Shifted_Alloc(void **ptr, int size); +void VM_Shifted_Free(void **ptr); + +void *VM_ArgPtr( int intValue ); +void *VM_ExplicitArgPtr( vm_t *vm, int intValue ); + +/* +============================================================== + +CMD + +Command text buffering and command execution + +============================================================== +*/ + +/* + +Any number of commands can be added in a frame, from several different sources. +Most commands come from either keybindings or console line input, but entire text +files can be execed. + +*/ + +void Cbuf_Init (void); +// allocates an initial text buffer that will grow as needed + +void Cbuf_AddText( const char *text ); +// Adds command text at the end of the buffer, does NOT add a final \n + +void Cbuf_ExecuteText( int exec_when, const char *text ); +// this can be used in place of either Cbuf_AddText or Cbuf_InsertText + +void Cbuf_Execute (void); +// Pulls off \n terminated lines of text from the command buffer and sends +// them through Cmd_ExecuteString. Stops when the buffer is empty. +// Normally called once per frame, but may be explicitly invoked. +// Do not call inside a command function, or current args will be destroyed. + +//=========================================================================== + +/* + +Command execution takes a null terminated string, breaks it into tokens, +then searches for a command or variable that matches the first token. + +*/ + +typedef void (*xcommand_t) (void); + +void Cmd_Init (void); + +void Cmd_AddCommand( const char *cmd_name, xcommand_t function ); +// called by the init functions of other parts of the program to +// register commands and functions to call for them. +// The cmd_name is referenced later, so it should not be in temp memory +// if function is NULL, the command will be forwarded to the server +// as a clc_clientCommand instead of executed locally + +void Cmd_RemoveCommand( const char *cmd_name ); + +void Cmd_CommandCompletion( void(*callback)(const char *s) ); +// callback with each valid string + +int Cmd_Argc (void); +char *Cmd_Argv (int arg); +void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ); +char *Cmd_Args (void); +char *Cmd_ArgsFrom( int arg ); +void Cmd_ArgsBuffer( char *buffer, int bufferLength ); +// The functions that execute commands get their parameters with these +// functions. Cmd_Argv () will return an empty string, not a NULL +// if arg > argc, so string operations are allways safe. + +void Cmd_TokenizeString( const char *text ); +// Takes a null terminated string. Does not need to be /n terminated. +// breaks the string up into arg tokens. + +void Cmd_ExecuteString( const char *text ); +// Parses a single line of text into arguments and tries to execute it +// as if it was typed at the console + + +/* +============================================================== + +CVAR + +============================================================== +*/ + +/* + +cvar_t variables are used to hold scalar or string variables that can be changed +or displayed at the console or prog code as well as accessed directly +in C code. + +The user can access cvars from the console in three ways: +r_draworder prints the current value +r_draworder 0 sets the current value to 0 +set r_draworder 0 as above, but creates the cvar if not present + +Cvars are restricted from having the same names as commands to keep this +interface from being ambiguous. + +The are also occasionally used to communicated information between different +modules of the program. + +*/ + +cvar_t *Cvar_Get( const char *var_name, const char *value, int flags ); +// creates the variable if it doesn't exist, or returns the existing one +// if it exists, the value will not be changed, but flags will be ORed in +// that allows variables to be unarchived without needing bitflags +// if value is "", the value will not override a previously set value. + +void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +// basically a slightly modified Cvar_Get for the interpreted modules + +void Cvar_Update( vmCvar_t *vmCvar ); +// updates an interpreted modules' version of a cvar + +void Cvar_Set( const char *var_name, const char *value ); +// will create the variable with no flags if it doesn't exist + +void Cvar_SetLatched( const char *var_name, const char *value); +// don't set the cvar immediately + +void Cvar_SetValue( const char *var_name, float value ); +// expands value to a string and calls Cvar_Set + +float Cvar_VariableValue( const char *var_name ); +int Cvar_VariableIntegerValue( const char *var_name ); +// returns 0 if not defined or non numeric + +char *Cvar_VariableString( const char *var_name ); +void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +// returns an empty string if not defined + +void Cvar_CommandCompletion( void(*callback)(const char *s) ); +// callback with each valid string + +void Cvar_Reset( const char *var_name ); + +void Cvar_SetCheatState( void ); +// reset all testing vars to a safe value + +qboolean Cvar_Command( void ); +// called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known +// command. Returns true if the command was a variable reference that +// was handled. (print or change) + +void Cvar_WriteVariables( fileHandle_t f ); +// writes lines containing "set variable value" for all variables +// with the archive flag set to true. + +void Cvar_Init( void ); + +char *Cvar_InfoString( int bit ); +char *Cvar_InfoString_Big( int bit ); +// returns an info string containing all the cvars that have the given bit set +// in their flags ( CVAR_USERINFO, CVAR_SERVERINFO, CVAR_SYSTEMINFO, etc ) +void Cvar_InfoStringBuffer( int bit, char *buff, int buffsize ); + +void Cvar_Restart_f( void ); + +extern int cvar_modifiedFlags; +// whenever a cvar is modifed, its flags will be OR'd into this, so +// a single check can determine if any CVAR_USERINFO, CVAR_SERVERINFO, +// etc, variables have been modified since the last check. The bit +// can then be cleared to allow another change detection. + +/* +============================================================== + +FILESYSTEM + +No stdio calls should be used by any part of the game, because +we need to deal with all sorts of directory and seperator char +issues. +============================================================== +*/ + +// referenced flags +// these are in loop specific order so don't change the order +#define FS_GENERAL_REF 0x01 +#define FS_UI_REF 0x02 +#define FS_CGAME_REF 0x04 +#define FS_QAGAME_REF 0x08 +// number of id paks that will never be autodownloaded from base +#define NUM_ID_PAKS 9 + +#ifdef _XBOX +#define MAX_FILE_HANDLES 16 +#else +#define MAX_FILE_HANDLES 64 +#endif + +qboolean FS_Initialized(); + +void FS_InitFilesystem (void); +void FS_Shutdown( qboolean closemfp ); + +qboolean FS_ConditionalRestart( int checksumFeed ); +void FS_Restart( int checksumFeed ); +// shutdown and restart the filesystem so changes to fs_gamedir can take effect + +char **FS_ListFiles( const char *directory, const char *extension, int *numfiles ); +// directory should not have either a leading or trailing / +// if extension is "/", only subdirectories will be returned +// the returned files will not include any directories or / + +void FS_FreeFileList( char **fileList ); +//rwwRMG - changed to fileList to not conflict with list type + +qboolean FS_FileExists( const char *file ); + +int FS_LoadStack(); + +int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +int FS_GetModList( char *listbuf, int bufsize ); + +fileHandle_t FS_FOpenFileWrite( const char *qpath ); +// will properly create any needed paths and deal with seperater character issues + +int FS_filelength( fileHandle_t f ); +fileHandle_t FS_SV_FOpenFileWrite( const char *filename ); +int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ); +void FS_SV_Rename( const char *from, const char *to ); +int FS_FOpenFileRead( const char *qpath, fileHandle_t *file, qboolean uniqueFILE ); +// if uniqueFILE is true, then a new FILE will be fopened even if the file +// is found in an already open pak file. If uniqueFILE is false, you must call +// FS_FCloseFile instead of fclose, otherwise the pak FILE would be improperly closed +// It is generally safe to always set uniqueFILE to true, because the majority of +// file IO goes through FS_ReadFile, which Does The Right Thing already. + +int FS_FileIsInPAK(const char *filename, int *pChecksum ); +// returns 1 if a file is in the PAK file, otherwise -1 + +int FS_Write( const void *buffer, int len, fileHandle_t f ); + +int FS_Read2( void *buffer, int len, fileHandle_t f ); +int FS_Read( void *buffer, int len, fileHandle_t f ); +// properly handles partial reads and reads from other dlls + +void FS_FCloseFile( fileHandle_t f ); +// note: you can't just fclose from another DLL, due to MS libc issues + +int FS_ReadFile( const char *qpath, void **buffer ); +// returns the length of the file +// a null buffer will just return the file length without loading +// as a quick check for existance. -1 length == not present +// A 0 byte will always be appended at the end, so string ops are safe. +// the buffer should be considered read-only, because it may be cached +// for other uses. + +void FS_ForceFlush( fileHandle_t f ); +// forces flush on files we're writing to. + +void FS_FreeFile( void *buffer ); +// frees the memory returned by FS_ReadFile + +void FS_WriteFile( const char *qpath, const void *buffer, int size ); +// writes a complete file, creating any subdirectories needed + +int FS_filelength( fileHandle_t f ); +// doesn't work for files that are opened from a pack file + +int FS_FTell( fileHandle_t f ); +// where are we? + +void FS_Flush( fileHandle_t f ); + +void QDECL FS_Printf( fileHandle_t f, const char *fmt, ... ); +// like fprintf + +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ); +// opens a file for reading, writing, or appending depending on the value of mode + +int FS_Seek( fileHandle_t f, long offset, int origin ); +// seek on a file (doesn't work for zip files!!!!!!!!) + +qboolean FS_FilenameCompare( const char *s1, const char *s2 ); + +const char *FS_GamePureChecksum( void ); +// Returns the checksum of the pk3 from which the server loaded the qagame.qvm + +const char *FS_LoadedPakNames( void ); +const char *FS_LoadedPakChecksums( void ); +const char *FS_LoadedPakPureChecksums( void ); +// Returns a space separated string containing the checksums of all loaded pk3 files. +// Servers with sv_pure set will get this string and pass it to clients. + +const char *FS_ReferencedPakNames( void ); +const char *FS_ReferencedPakChecksums( void ); +const char *FS_ReferencedPakPureChecksums( void ); +// Returns a space separated string containing the checksums of all loaded +// AND referenced pk3 files. Servers with sv_pure set will get this string +// back from clients for pure validation + +void FS_ClearPakReferences( int flags ); +// clears referenced booleans on loaded pk3s + +void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ); +void FS_PureServerSetLoadedPaks( const char *pakSums, const char *pakNames ); +// If the string is empty, all data sources will be allowed. +// If not empty, only pk3 files that match one of the space +// separated checksums will be checked for files, with the +// sole exception of .cfg files. + +qboolean FS_idPak( char *pak, char *base ); +qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring ); +void FS_Rename( const char *from, const char *to ); + +/* +============================================================== + +MISC + +============================================================== +*/ + +// NOTE NOTE NOTE!!!!!!!!!!!!! +// +// Any CPUID_XXXX defined as higher than CPUID_INTEL_MMX *must* have MMX support (eg like CPUID_AMD_3DNOW (0x30) has), +// this allows convenient MMX capability checking. If you for some reason want to support some new processor that does +// *NOT* have MMX (yeah, right), then define it as a lower number. -slc +// +// ( These values are returned by Sys_GetProcessorId ) +// +#define CPUID_GENERIC 0 // any unrecognized processor + +#define CPUID_AXP 0x10 + +#define CPUID_INTEL_UNSUPPORTED 0x20 // Intel 386/486 +#define CPUID_INTEL_PENTIUM 0x21 // Intel Pentium or PPro +#define CPUID_INTEL_MMX 0x22 // Intel Pentium/MMX or P2/MMX +#define CPUID_INTEL_KATMAI 0x23 // Intel Katmai +#define CPUID_INTEL_WILLIAMETTE 0x24 // Intel Williamette + +#define CPUID_AMD_3DNOW 0x30 // AMD K6 3DNOW! +// +//========================================================== + +#define RoundUp(N, M) ((N) + ((unsigned int)(M)) - (((unsigned int)(N)) % ((unsigned int)(M)))) +#define RoundDown(N, M) ((N) - (((unsigned int)(N)) % ((unsigned int)(M)))) + +char *CopyString( const char *in ); +void Info_Print( const char *s ); + +void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)(char *)); +void Com_EndRedirect( void ); +void QDECL Com_Printf( const char *fmt, ... ); +void QDECL Com_PrintfAlways( const char *fmt, ... ); +void QDECL Com_DPrintf( const char *fmt, ... ); +void QDECL Com_OPrintf( const char *fmt, ...); // Outputs to the VC / Windows Debug window (only in debug compile) +void QDECL Com_Error( int code, const char *fmt, ... ); +void Com_Quit_f( void ); +int Com_EventLoop( void ); +int Com_Milliseconds( void ); // will be journaled properly +unsigned Com_BlockChecksum( const void *buffer, int length ); +unsigned Com_BlockChecksumKey (void *buffer, int length, int key); +int Com_HashKey(char *string, int maxlen); +int Com_Filter(char *filter, char *name, int casesensitive); +int Com_FilterPath(char *filter, char *name, int casesensitive); +int Com_RealTime(qtime_t *qtime); +qboolean Com_SafeMode( void ); + +void Com_StartupVariable( const char *match ); +// checks for and removes command line "+set var arg" constructs +// if match is NULL, all set commands will be executed, otherwise +// only a set with the exact name. Only used during startup. + + +extern cvar_t *com_developer; +extern cvar_t *com_vmdebug; +extern cvar_t *com_dedicated; +extern cvar_t *com_speeds; +extern cvar_t *com_timescale; +extern cvar_t *com_sv_running; +extern cvar_t *com_cl_running; +extern cvar_t *com_viewlog; // 0 = hidden, 1 = visible, 2 = minimized +extern cvar_t *com_version; +extern cvar_t *com_blood; +extern cvar_t *com_buildScript; // for building release pak files +extern cvar_t *com_journal; +extern cvar_t *com_cameraMode; + +#ifdef G2_PERFORMANCE_ANALYSIS +extern cvar_t *com_G2Report; +#endif + +extern cvar_t *com_RMG; + +// both client and server must agree to pause +extern cvar_t *cl_paused; +extern cvar_t *sv_paused; + +// com_speeds times +extern int time_game; +extern int time_frontend; +extern int time_backend; // renderer backend time + +extern int com_frameTime; +extern int com_frameMsec; + +extern qboolean com_errorEntered; + + +#ifndef _XBOX +extern fileHandle_t logfile; +extern fileHandle_t com_journalFile; +extern fileHandle_t com_journalDataFile; +#endif + +/* +typedef enum { + TAG_FREE, + TAG_GENERAL, + TAG_BOTLIB, + TAG_RENDERER, + TAG_SMALL, + TAG_STATIC +} memtag_t; +*/ + +/* + +--- low memory ---- +server vm +server clipmap +---mark--- +renderer initialization (shaders, etc) +UI vm +cgame vm +renderer map +renderer models + +---free--- + +temp file loading +--- high memory --- + +*/ + +#if defined(_DEBUG) && !defined(BSPC) + #define DEBUG_ZONE_ALLOCS +#endif + +/* +#ifdef DEBUG_ZONE_ALLOCS + #define Z_TagMalloc(size, tag) Z_TagMallocDebug(size, tag, #size, __FILE__, __LINE__) + #define Z_Malloc(size) Z_MallocDebug(size, #size, __FILE__, __LINE__) + #define S_Malloc(size) S_MallocDebug(size, #size, __FILE__, __LINE__) + void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line ); // NOT 0 filled memory + void *Z_MallocDebug( int size, char *label, char *file, int line ); // returns 0 filled memory + void *S_MallocDebug( int size, char *label, char *file, int line ); // returns 0 filled memory +#else + void *Z_TagMalloc( int size, int tag ); // NOT 0 filled memory + void *Z_Malloc( int size ); // returns 0 filled memory + void *S_Malloc( int size ); // NOT 0 filled memory only for small allocations +#endif +void Z_Free( void *ptr ); +void Z_FreeTags( int tag ); +int Z_AvailableMemory( void ); +void Z_LogHeap( void ); +*/ + +// later on I'll re-implement __FILE__, __LINE__ etc, but for now... +// +#ifdef DEBUG_ZONE_ALLOCS +void *Z_Malloc ( int iSize, memtag_t eTag, qboolean bZeroit = qfalse, int iAlign = 4); // return memory NOT zero-filled by default +void *S_Malloc ( int iSize ); // NOT 0 filled memory only for small allocations +#else +void *Z_Malloc ( int iSize, memtag_t eTag, qboolean bZeroit = qfalse, int iAlign = 4); // return memory NOT zero-filled by default +void *S_Malloc ( int iSize ); // NOT 0 filled memory only for small allocations +#endif +void Z_MorphMallocTag( void *pvBuffer, memtag_t eDesiredTag ); +void Z_Validate( void ); +int Z_MemSize ( memtag_t eTag ); +void Z_TagFree ( memtag_t eTag ); +void Z_Free ( void *ptr ); +int Z_Size ( void *pvAddress); + +void Z_PushNewDeleteTag( memtag_t eTag ); +void Z_PopNewDeleteTag( void ); + +void Com_InitZoneMemory(void); +void Com_InitHunkMemory(void); +void Com_ShutdownZoneMemory(void); +void Com_ShutdownHunkMemory(void); + +void Hunk_Clear( void ); +void Hunk_ClearToMark( void ); +void Hunk_SetMark( void ); +qboolean Hunk_CheckMark( void ); +void Hunk_ClearTempMemory( void ); +void *Hunk_AllocateTempMemory( int size ); +void Hunk_FreeTempMemory( void *buf ); +int Hunk_MemoryRemaining( void ); +void Hunk_Log( void); +void Hunk_Trash( void ); + +void Com_TouchMemory( void ); + +// commandLine should not include the executable name (argv[0]) +void Com_Init( char *commandLine ); +void Com_Frame( void ); +void Com_Shutdown( void ); +//rwwRMG: Inserted: +bool Com_ParseTextFile(const char *file, class CGenericParser2 &parser, bool cleanFirst = true); +CGenericParser2 *Com_ParseTextFile(const char *file, bool cleanFirst, bool writeable); +void Com_ParseTextFileDestroy(class CGenericParser2 &parser); + + +/* +============================================================== + +CLIENT / SERVER SYSTEMS + +============================================================== +*/ + +// +// client interface +// +void CL_InitKeyCommands( void ); +// the keyboard binding interface must be setup before execing +// config files, but the rest of client startup will happen later + +void CL_Init( void ); +void CL_Disconnect( qboolean showMainMenu, qboolean deleteTextures = qtrue ); +void CL_Shutdown( void ); +void CL_Frame( int msec ); +qboolean CL_GameCommand( void ); +void CL_KeyEvent (int key, qboolean down, unsigned time); + +void CL_CharEvent( int key ); +// char events are for field typing, not game control + +void CL_MouseEvent( int dx, int dy, int time ); + +void CL_JoystickEvent( int axis, int value, int time ); + +void CL_PacketEvent( netadr_t from, msg_t *msg ); + +void CL_ConsolePrint( const char *text, qboolean silent ); + +void CL_MapLoading( void ); +// do a screen update before starting to load a map +// when the server is going to load a new map, the entire hunk +// will be cleared, so the client must shutdown cgame, ui, and +// the renderer + +void CL_ForwardCommandToServer( const char *string ); +// adds the current command line as a clc_clientCommand to the client message. +// things like godmode, noclip, etc, are commands directed to the server, +// so when they are typed in at the console, they will need to be forwarded. + +void CL_ShutdownAll( void ); +// shutdown all the client stuff + +void CL_FlushMemory( void ); +// dump all memory on an error + +void CL_StartHunkUsers( void ); +// start all the client stuff using the hunk + +void Key_WriteBindings( fileHandle_t f ); +// for writing the config files + +void S_ClearSoundBuffer( void ); +// call before filesystem access + +void SCR_DebugGraph (float value, int color); // FIXME: move logging to common? + + +// +// server interface +// +void SV_Init( void ); +void SV_Shutdown( char *finalmsg ); +void SV_Frame( int msec ); +void SV_PacketEvent( netadr_t from, msg_t *msg ); +qboolean SV_GameCommand( void ); + + +// +// UI interface +// +qboolean UI_GameCommand( void ); +qboolean UI_usesUniqueCDKey(); + +/* +============================================================== + +NON-PORTABLE SYSTEM SERVICES + +============================================================== +*/ + +typedef enum { + AXIS_SIDE, + AXIS_FORWARD, + AXIS_UP, + AXIS_ROLL, + AXIS_YAW, + AXIS_PITCH, + MAX_JOYSTICK_AXIS +} joystickAxis_t; + +typedef enum { + // bk001129 - make sure SE_NONE is zero + SE_NONE = 0, // evTime is still valid + SE_KEY, // evValue is a key code, evValue2 is the down flag + SE_CHAR, // evValue is an ascii char + SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves + SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127) + SE_CONSOLE, // evPtr is a char* + SE_PACKET, // evPtr is a netadr_t followed by data bytes to evPtrLength + SE_BROADCAST_PACKET // same as SE_PACKET - but came from our broadcast socket +} sysEventType_t; + +typedef struct { + int evTime; + sysEventType_t evType; + int evValue, evValue2; + int evPtrLength; // bytes of data pointed to by evPtr, for journaling + void *evPtr; // this must be manually freed if not NULL +} sysEvent_t; + +sysEvent_t Sys_GetEvent( void ); + +void Sys_Init (void); + +// general development dll loading for virtual machine testing +void * QDECL Sys_LoadDll( const char *name, int (QDECL **entryPoint)(int, ...), + int (QDECL *systemcalls)(int, ...) ); +void Sys_UnloadDll( void *dllHandle ); + +void Sys_UnloadGame( void ); +void *Sys_GetGameAPI( void *parms ); + +void Sys_UnloadCGame( void ); +void *Sys_GetCGameAPI( void ); + +void Sys_UnloadUI( void ); +void *Sys_GetUIAPI( void ); + +//bot libraries +void Sys_UnloadBotLib( void ); +void *Sys_GetBotLibAPI( void *parms ); + +char *Sys_GetCurrentUser( void ); + +void QDECL Sys_Error( const char *error, ...); +void Sys_Quit (void); +char *Sys_GetClipboardData( void ); // note that this isn't journaled... + +void Sys_Print( const char *msg ); +#ifdef _XBOX +void Sys_Log( const char *file, const char *msg ); +void Sys_Log( const char *file, const void *buffer, int size, bool flush ); +#endif + +// Sys_Milliseconds should only be used for profiling purposes, +// any game related timing information should come from event timestamps +int Sys_Milliseconds (bool baseTime = false); + +#if __linux__ +extern "C" void Sys_SnapVector( float *v ); + +#else +void Sys_SnapVector( float *v ); +#endif + +// the system console is shown when a dedicated server is running +void Sys_DisplaySystemConsole( qboolean show ); + +int Sys_GetProcessorId( void ); +int Sys_GetCPUSpeed( void ); +int Sys_GetPhysicalMemory(void); + +void Sys_BeginStreamedFile( fileHandle_t f, int readahead ); +void Sys_EndStreamedFile( fileHandle_t f ); +int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ); +void Sys_StreamSeek( fileHandle_t f, int offset, int origin ); + +void Sys_ShowConsole( int level, qboolean quitOnClose ); +void Sys_SetErrorText( const char *text ); + +bool Sys_SendPacket( int length, const void *data, netadr_t to ); +void Sys_SendBroadcastPacket( int length, const void *data ); +#ifdef _XBOX +void Sys_SendVoicePacket( int length, const void *data, netadr_t to ); +#endif + +qboolean Sys_StringToAdr( const char *s, netadr_t *a ); +//Does NOT parse port numbers, only base addresses. + +qboolean Sys_IsLANAddress (netadr_t adr); +void Sys_ShowIP(void); + +qboolean Sys_CheckCD( void ); + +void Sys_Mkdir( const char *path ); +char *Sys_Cwd( void ); +void Sys_SetDefaultCDPath(const char *path); +char *Sys_DefaultCDPath(void); +void Sys_SetDefaultInstallPath(const char *path); +char *Sys_DefaultInstallPath(void); +void Sys_SetDefaultHomePath(const char *path); +char *Sys_DefaultHomePath(void); +char *Sys_DefaultBasePath(void); + +char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs ); +void Sys_FreeFileList( char **fileList ); +//rwwRMG - changed to fileList to not conflict with list type + +void Sys_BeginProfiling( void ); +void Sys_EndProfiling( void ); + +int Sys_FunctionCmp(void *f1, void *f2); +int Sys_FunctionCheckSum(void *f1); + +qboolean Sys_LowPhysicalMemory(); +unsigned int Sys_ProcessorCount(); + +int Sys_MonkeyShouldBeSpanked( void ); + +/* This is based on the Adaptive Huffman algorithm described in Sayood's Data + * Compression book. The ranks are not actually stored, but implicitly defined + * by the location of a node within a doubly-linked list */ + +#define NYT HMAX /* NYT = Not Yet Transmitted */ +#define INTERNAL_NODE (HMAX+1) + +typedef struct nodetype { + struct nodetype *left, *right, *parent; /* tree structure */ + struct nodetype *next, *prev; /* doubly-linked list */ + struct nodetype **head; /* highest ranked node in block */ + int weight; + int symbol; +} node_t; + +#define HMAX 256 /* Maximum symbol */ + +typedef struct { + int blocNode; + int blocPtrs; + + node_t* tree; + node_t* lhead; + node_t* ltail; + node_t* loc[HMAX+1]; + node_t** freelist; + + node_t nodeList[768]; + node_t* nodePtrs[768]; +} huff_t; + +typedef struct { + huff_t compressor; + huff_t decompressor; +} huffman_t; + +void Huff_Compress(msg_t *buf, int offset); +void Huff_Decompress(msg_t *buf, int offset); +void Huff_Init(huffman_t *huff); +void Huff_addRef(huff_t* huff, byte ch); +int Huff_Receive (node_t *node, int *ch, byte *fin); +void Huff_transmit (huff_t *huff, int ch, byte *fout); +void Huff_offsetReceive (node_t *node, int *ch, byte *fin, int *offset); +void Huff_offsetTransmit (huff_t *huff, int ch, byte *fout, int *offset); +void Huff_putBit( int bit, byte *fout, int *offset); +int Huff_getBit( byte *fout, int *offset); + +extern huffman_t clientHuffTables; + +#define SV_ENCODE_START 4 +#define SV_DECODE_START 12 +#define CL_ENCODE_START 12 +#define CL_DECODE_START 4 + +inline int Round(float value) +{ + return((int)floorf(value + 0.5f)); +} + +#ifdef _XBOX +////////////////////////////// +// +// Map Lump Loader +// +struct Lump +{ + void* data; + int len; + + Lump() : data(NULL), len(0) {} + ~Lump() { clear(); } + + void load(const char* map, const char* lump) + { + clear(); + + char path[MAX_QPATH]; + Com_sprintf(path, MAX_QPATH, "%s/%s.mle", map, lump); + + len = FS_ReadFile(path, &data); + if (len < 0) len = 0; + } + + void clear(void) + { + if (data) + { + FS_FreeFile(data); + data = NULL; + } + } +}; +#endif _XBOX + +#endif // _QCOMMON_H_ diff --git a/codemp/qcommon/qfiles.h b/codemp/qcommon/qfiles.h new file mode 100644 index 0000000..01b818e --- /dev/null +++ b/codemp/qcommon/qfiles.h @@ -0,0 +1,607 @@ +#ifndef __QFILES_H__ +#define __QFILES_H__ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) + + +// the maximum size of game relative pathnames +#define MAX_QPATH 64 + +/* +======================================================================== + +QVM files + +======================================================================== +*/ + +#define VM_MAGIC 0x12721444 +typedef struct { + int vmMagic; + + int instructionCount; + + int codeOffset; + int codeLength; + + int dataOffset; + int dataLength; + int litLength; // ( dataLength - litLength ) should be byteswapped on load + int bssLength; // zero filled memory appended to datalength +} vmHeader_t; + + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + + + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD3_VERSION 15 + +// limits +#define MD3_MAX_LODS 3 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 + 32 // per model +#define MD3_MAX_TAGS 16 // per frame + +// vertex scales +#define MD3_XYZ_SCALE (1.0/64) + +typedef struct md3Frame_s { + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_QPATH]; // tag name + vec3_t origin; + vec3_t axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ +typedef struct { + int ident; // + + char name[MAX_QPATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_QPATH]; + int shaderIndex; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + + +// little-endian "RBSP" +#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'R') + +#define BSP_VERSION 1 + + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x800 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_SHADERS 0x400 + +#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define MAX_MAP_FOGS 0x100 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x20000 +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x20000 +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x800000 +#define MAX_MAP_LIGHTGRID 65535 +#define MAX_MAP_LIGHTGRID_ARRAY 0x100000 +#define MAX_MAP_VISIBILITY 0x600000 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 + + +// key / value pair sizes in the entities lump +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + +//============================================================================= + +#ifdef _XBOX + +#pragma pack(push, 1) + +typedef struct { + float mins[3], maxs[3]; + int firstSurface; + unsigned short numSurfaces; + int firstBrush; + unsigned short numBrushes; +} dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; +} dnode_t; + +typedef struct { + short cluster; // -1 = opaque cluster (do I still store these?) + signed char area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstLeafSurface; + unsigned short numLeafSurfaces; + + unsigned short firstLeafBrush; + unsigned short numLeafBrushes; +} dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + byte shaderNum; +} dbrushside_t; + +typedef struct { + int firstSide; + byte numSides; + unsigned short shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +// Light Style Constants +#define MAXLIGHTMAPS 4 +#define LS_NORMAL 0x00 +#define LS_UNUSED 0xfe +#define LS_LSNONE 0xff +#define MAX_LIGHT_STYLES 64 + +typedef struct { + float lightmap[MAXLIGHTMAPS][2]; + float st[2]; + short xyz[3]; + short normal[3]; + byte color[MAXLIGHTMAPS][4]; +} mapVert_t; + +#define DRAWVERT_LIGHTMAP_SCALE 32768.0f +// Change texture coordinates for TriSurfs to be even more fine grain. +// See below for note about keeping MIN_ST and MAX_ST up to date with +// ST_SCALE. These are in 4.12. OK, how about 5.11? Ick, 7.9 +//#define DRAWVERT_ST_SCALE 4096.0f +//#define DRAWVERT_ST_SCALE 2048.0f +#define DRAWVERT_ST_SCALE 512.0f + +// We use a slightly different format for the fixed point texture +// coords in Grid/Mesh drawverts: 10.6 rather than 12.4 +// To be sure that this is ok, keep the max and min values equal to +// the largest and smallest whole numbers that can be stored using the +// format. (ie: Don't change GRID_DRAWVERT_ST_SCALE without changing +// the other two!) (And don't forget that we're using a bit for sign.) +#define GRID_DRAWVERT_ST_SCALE 64.0f + +typedef struct { + vec3_t xyz; + short dvst[2]; + short dvlightmap[MAXLIGHTMAPS][2]; + vec3_t normal; +#ifdef _XBOX + vec3_t tangent; +#endif + byte dvcolor[MAXLIGHTMAPS][2]; +} drawVert_t; + +typedef struct { + byte flags; + byte latLong[2]; +} dgrid_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + unsigned int verts; // high 20 bits are first vert, low 12 are num verts + unsigned int indexes; // high 20 bits are first index, low 12 are num indices + + byte lightmapStyles[MAXLIGHTMAPS]; + byte lightmapNum[MAXLIGHTMAPS]; + + short lightmapVecs[3]; +} dface_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + unsigned int verts; // high 20 bits are first vert, low 12 are num verts + + byte lightmapStyles[MAXLIGHTMAPS]; + byte lightmapNum[MAXLIGHTMAPS]; + + short lightmapVecs[2][3]; // for patches, [0] and [1] are lodbounds + + byte patchWidth; + byte patchHeight; +} dpatch_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + unsigned int verts; // high 20 bits are first vert, low 12 are num verts + unsigned int indexes; // high 20 bits are first index, low 12 are num indices + + byte lightmapStyles[MAXLIGHTMAPS]; +} dtrisurf_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + short origin[3]; + short normal[3]; + byte color[3]; +} dflare_t; + +#pragma pack(pop) + +#else // _XBOX + +typedef struct { + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define LUMP_LIGHTARRAY 17 +#define HEADER_LUMPS 18 + +typedef struct { + int ident; + int version; + + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (do I still store these?) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int firstLeafSurface; + int numLeafSurfaces; + + int firstLeafBrush; + int numLeafBrushes; +} dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; + int drawSurfNum; +} dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +// Light Style Constants +#define MAXLIGHTMAPS 4 +#define LS_NORMAL 0x00 +#define LS_UNUSED 0xfe +#define LS_LSNONE 0xff //rww - changed name because it unhappily conflicts with a lightsaber state name and changing this is just easier +#define MAX_LIGHT_STYLES 64 + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[MAXLIGHTMAPS][2]; + vec3_t normal; + byte color[MAXLIGHTMAPS][4]; +} mapVert_t; + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[MAXLIGHTMAPS][2]; + vec3_t normal; + byte color[MAXLIGHTMAPS][4]; +} drawVert_t; + +typedef struct +{ + byte ambientLight[MAXLIGHTMAPS][3]; + byte directLight[MAXLIGHTMAPS][3]; + byte styles[MAXLIGHTMAPS]; + byte latLong[2]; +} dgrid_t; + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE +} mapSurfaceType_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + byte lightmapStyles[MAXLIGHTMAPS], vertexStyles[MAXLIGHTMAPS]; + int lightmapNum[MAXLIGHTMAPS]; + int lightmapX[MAXLIGHTMAPS], lightmapY[MAXLIGHTMAPS]; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} dsurface_t; + +#endif // _XBOX + +///////////////////////////////////////////////////////////// +// +// Defines and structures required for fonts + +#define GLYPH_COUNT 256 + +// Must match define in stmparse.h +#define STYLE_DROPSHADOW 0x80000000 +#define STYLE_BLINK 0x40000000 +#define SET_MASK 0x00ffffff + +typedef struct +{ + short width; // number of pixels wide + short height; // number of scan lines + short horizAdvance; // number of pixels to advance to the next char + short horizOffset; // x offset into space to render glyph + int baseline; // y offset + float s; // x start tex coord + float t; // y start tex coord + float s2; // x end tex coord + float t2; // y end tex coord +} glyphInfo_t; + + +// this file corresponds 1:1 with the "*.fontdat" files, so don't change it unless you're going to +// recompile the fontgen util and regenerate all the fonts! +// +typedef struct dfontdat_s +{ + glyphInfo_t mGlyphs[GLYPH_COUNT]; + + short mPointSize; + short mHeight; // max height of font + short mAscender; + short mDescender; + + short mKoreanHack; +} dfontdat_t; + +/////////////////// fonts end //////////////////////////////////// + + +#endif diff --git a/codemp/qcommon/roffsystem.cpp b/codemp/qcommon/roffsystem.cpp new file mode 100644 index 0000000..b8e62be --- /dev/null +++ b/codemp/qcommon/roffsystem.cpp @@ -0,0 +1,1040 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "RoffSystem.h" +#include "../client/client.h" + +// The one and only instance... +CROFFSystem theROFFSystem; + +//--------------------------------------------------------------------------- +// CROFFSystem::CROFF::CROFF +// Simple constructor for CROFF object +// +// INPUTS: +// pass in the filepath and the id of the roff object to create +// +// RETURN: +// none +//--------------------------------------------------------------------------- +CROFFSystem::CROFF::CROFF( const char *file, int id ) +{ + strcpy( mROFFFilePath, file ); + + mID = id; + mMoveRotateList = NULL; + mNoteTrackIndexes = 0; + mUsedByClient = mUsedByServer = qfalse; +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::CROFF::~CROFF() +// Frees any resources when the CROFF object dies +// +// INPUTS: +// none +// +// RETURN: +// none +//--------------------------------------------------------------------------- +CROFFSystem::CROFF::~CROFF() +{ + if ( mMoveRotateList ) + { + delete [] mMoveRotateList; + } + + if (mNoteTrackIndexes) + { + delete mNoteTrackIndexes[0]; + delete [] mNoteTrackIndexes; + } +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::Restart +// Cleans up the roff system, not sure how useful this really is +// +// INPUTS: +// none +// +// RETURN: +// success or failure +//--------------------------------------------------------------------------- +qboolean CROFFSystem::Restart() +{ + TROFFList::iterator itr = mROFFList.begin(); + + // remove everything from the list + while( itr != mROFFList.end() ) + { + delete ((CROFF *)(*itr).second); + + mROFFList.erase( itr ); + itr = mROFFList.begin(); + } + + // clear CROFFSystem unique ID counter + mID = 0; + + return qtrue; +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::IsROFF +// Makes sure that the requested file is actually a ROFF +// +// INPUTS: +// pass in the file data +// +// RETURN: +// returns test success or failure +//--------------------------------------------------------------------------- +qboolean CROFFSystem::IsROFF( unsigned char *data ) +{ + TROFFHeader *hdr = (TROFFHeader *)data; + TROFF2Header *hdr2 = (TROFF2Header *)data; + + if ( !strcmp( hdr->mHeader, ROFF_STRING )) + { // bad header + return qfalse; + } + + if (hdr->mVersion != ROFF_VERSION && hdr->mVersion != ROFF_NEW_VERSION) + { // bad version + return qfalse; + } + + if (hdr->mVersion == ROFF_VERSION && hdr->mCount <= 0.0) + { // bad count + return qfalse; + } + + if (hdr->mVersion == ROFF_NEW_VERSION && hdr2->mCount <= 0) + { // bad count + return qfalse; + } + + return qtrue; +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::InitROFF +// Handles stuffing the roff data in the CROFF object +// +// INPUTS: +// pass in the file data and the object to stuff the data into. +// +// RETURN: +// returns initialization success or failure +//--------------------------------------------------------------------------- +qboolean CROFFSystem::InitROFF( unsigned char *data, CROFF *obj ) +{ + int i; + + TROFFHeader *hdr = (TROFFHeader *)data; + + if (hdr->mVersion == ROFF_NEW_VERSION) + { + return InitROFF2(data, obj); + } + + obj->mROFFEntries = hdr->mCount; + obj->mMoveRotateList = new TROFF2Entry[((int)hdr->mCount)]; + obj->mFrameTime = 1000 / ROFF_SAMPLE_RATE; // default 10 hz + obj->mLerp = ROFF_SAMPLE_RATE; + obj->mNumNoteTracks = 0; + obj->mNoteTrackIndexes = 0; + + if ( obj->mMoveRotateList != 0 ) + { // Step past the header to get to the goods + TROFFEntry *roff_data = ( TROFFEntry *)&hdr[1]; + + // Copy all of the goods into our ROFF cache + for ( i = 0; i < hdr->mCount; i++ ) + { + VectorCopy( roff_data[i].mOriginOffset, obj->mMoveRotateList[i].mOriginOffset ); + VectorCopy( roff_data[i].mRotateOffset, obj->mMoveRotateList[i].mRotateOffset ); + obj->mMoveRotateList[i].mStartNote = -1; + obj->mMoveRotateList[i].mNumNotes = 0; + } + + FixBadAngles(obj); + } + else + { + return qfalse; + } + + return qtrue; +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::InitROFF2 +// Handles stuffing the roff data in the CROFF object for version 2 +// +// INPUTS: +// pass in the file data and the object to stuff the data into. +// +// RETURN: +// returns initialization success or failure +//--------------------------------------------------------------------------- +qboolean CROFFSystem::InitROFF2( unsigned char *data, CROFF *obj ) +{ + int i; + + TROFF2Header *hdr = (TROFF2Header *)data; + + obj->mROFFEntries = hdr->mCount; + obj->mMoveRotateList = new TROFF2Entry[(hdr->mCount)]; + obj->mFrameTime = hdr->mFrameRate; + obj->mLerp = 1000 / hdr->mFrameRate; + obj->mNumNoteTracks = hdr->mNumNotes; + + if ( obj->mMoveRotateList != 0 ) + { // Step past the header to get to the goods + TROFF2Entry *roff_data = ( TROFF2Entry *)&hdr[1]; + + // Copy all of the goods into our ROFF cache + for ( i = 0; i < hdr->mCount; i++ ) + { + VectorCopy( roff_data[i].mOriginOffset, obj->mMoveRotateList[i].mOriginOffset ); + VectorCopy( roff_data[i].mRotateOffset, obj->mMoveRotateList[i].mRotateOffset ); + obj->mMoveRotateList[i].mStartNote = roff_data[i].mStartNote; + obj->mMoveRotateList[i].mNumNotes = roff_data[i].mNumNotes; + } + + FixBadAngles(obj); + + if (obj->mNumNoteTracks) + { + int size; + char *ptr, *start; + + ptr = start = (char *)&roff_data[i]; + size = 0; + + for(i=0;imNumNoteTracks;i++) + { + size += strlen(ptr) + 1; + ptr += strlen(ptr) + 1; + } + + obj->mNoteTrackIndexes = new char *[obj->mNumNoteTracks]; + ptr = obj->mNoteTrackIndexes[0] = new char[size]; + memcpy(obj->mNoteTrackIndexes[0], start, size); + + for(i=1;imNumNoteTracks;i++) + { + ptr += strlen(ptr) + 1; + obj->mNoteTrackIndexes[i] = ptr; + } + } + } + else + { + return qfalse; + } + + return qtrue; +} + +/************************************************************************************************ + * CROFFSystem::FixBadAngles * + * This function will attempt to fix bad angles (large) that come in from the exporter. * + * * + * Input * + * obj: the ROFF object * + * * + * Output / Return * + * none * + * * + ************************************************************************************************/ +void CROFFSystem::FixBadAngles(CROFF *obj) +{ +// Ideally we would fix the ROFF exporter, if that doesn't happen, this may be an adequate solution +#ifdef ROFF_AUTO_FIX_BAD_ANGLES + int index, t; + + // Attempt to fix bad angles + + for(index=0;indexmROFFEntries;index++) + { + for ( t = 0; t < 3; t++ ) + { + if ( obj->mMoveRotateList[index].mRotateOffset[t] > 180.0f ) + { // found a bad angle + // Com_Printf( S_COLOR_YELLOW"Fixing bad roff angle\n <%6.2f> changed to <%6.2f>.\n", + // roff_data[i].mRotateOffset[t], roff_data[i].mRotateOffset[t] - 360.0f ); + obj->mMoveRotateList[index].mRotateOffset[t] -= 360.0f; + } + else if ( obj->mMoveRotateList[index].mRotateOffset[t] < -180.0f ) + { // found a bad angle + // Com_Printf( S_COLOR_YELLOW"Fixing bad roff angle\n <%6.2f> changed to <%6.2f>.\n", + // roff_data[i].mRotateOffset[t], roff_data[i].mRotateOffset[t] + 360.0f ); + obj->mMoveRotateList[index].mRotateOffset[t] += 360.0f; + } + } + } +#endif // ROFF_AUTO_FIX_BAD_ANGLES +} + +//--------------------------------------------------------------------------- +// CROFFSystem::Cache +// Pre-caches roff data to avoid file hits during gameplay. Disallows +// repeated caches of existing roffs. +// +// INPUTS: +// pass in the filepath of the roff to cache +// +// RETURN: +// returns ID of the roff, whether its an existing one or new one. +//--------------------------------------------------------------------------- +int CROFFSystem::Cache( const char *file, qboolean isClient ) +{ + // See if this item is already cached + int len; + int id = GetID( file ); + unsigned char *data; + CROFF *cROFF; + + if ( id ) + { +#ifdef _DEBUG + Com_Printf( S_COLOR_YELLOW"Ignoring. File '%s' already cached.\n", file ); +#endif + } + else + { // Read the file in one fell swoop + len = FS_ReadFile( file, (void**) &data); + + if ( len <= 0 ) + { + char otherPath[1024]; + COM_StripExtension(file, otherPath); + len = FS_ReadFile( va("scripts/%s.rof", otherPath), (void**) &data); + if (len <= 0) + { + Com_Printf( S_COLOR_RED"Could not open .ROF file '%s'\n", file ); + return 0; + } + } + + // Make sure that the file is roff + if ( !IsROFF( data ) ) + { + Com_Printf( S_COLOR_RED"cache failed: roff <%s> does not exist or is not a valid roff\n", file ); + FS_FreeFile( data ); + + return 0; + } + + // Things are looking good so far, so create a new CROFF object + id = NewID(); + + cROFF = new CROFF( file, id ); + + mROFFList[id] = cROFF; + + if ( !InitROFF( data, cROFF ) ) + { // something failed, so get rid of the object + Unload( id ); + id = 0; + } + + FS_FreeFile( data ); + } + + cROFF = (*mROFFList.find( id )).second; + if (isClient) + { + cROFF->mUsedByClient = qtrue; + } + else + { + cROFF->mUsedByServer = qtrue; + } + + // If we haven't requested a new ID, we'll just be returning the ID of the existing roff + return id; +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::GetID +// Finds the associated (internal) ID of the specified roff file +// +// INPUTS: +// pass in the roff file path +// +// RETURN: +// returns ID if there is one, zero if nothing was found +//--------------------------------------------------------------------------- +int CROFFSystem::GetID( const char *file ) +{ + TROFFList::iterator itr; + + // Attempt to find the requested roff + for ( itr = mROFFList.begin(); itr != mROFFList.end(); ++itr ) + { + if ( !strcmp( ((CROFF *)((*itr).second))->mROFFFilePath, file ) ) + { // return the ID to this roff + return (*itr).first; + } + } + + // Not found + return 0; +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::Unload +// Removes the roff from the list, deleting it to free up any used resources +// +// INPUTS: +// pass in the id of the roff to delete, use GetID if you only know the roff +// filepath +// +// RETURN: +// qtrue if item was in the list, qfalse otherwise +//--------------------------------------------------------------------------- +qboolean CROFFSystem::Unload( int id ) +{ + TROFFList::iterator itr; + + itr = mROFFList.find( id ); + + if ( itr != mROFFList.end() ) + { // requested item found in the list, free mem, then remove from list + delete ((CROFF *)(*itr).second); + +#ifndef __linux__ + itr = mROFFList.erase( itr ); +#else + // darn stl differences + TROFFList::iterator titr; + titr = itr; + itr++; + mROFFList.erase(titr); +#endif + +#ifdef _DEBUG + Com_Printf( S_COLOR_GREEN"roff unloaded\n" ); +#endif + + return qtrue; + } + else + { // not found + +#ifdef _DEBUG + Com_Printf( S_COLOR_RED"unload failed: roff <%i> does not exist\n", id ); +#endif + return qfalse; + } +} + +//--------------------------------------------------------------------------- +// CROFFSystem::Clean +// Cleans out all Roffs, freeing up any used resources +// +// INPUTS: +// none +// +// RETURN: +// success of operation +//--------------------------------------------------------------------------- +qboolean CROFFSystem::Clean(qboolean isClient) +{ +#if 0 + TROFFList::iterator itr, next; + TROFFEntList::iterator entI, nextEnt; + itr = mROFFList.begin(); + while ( itr != mROFFList.end() ) + { + next = itr; + next++; + + if (isClient) + { + (*itr).second->mUsedByClient = qfalse; + } + else + { + (*itr).second->mUsedByServer = qfalse; + } + if ((*itr).second->mUsedByClient == qfalse && (*itr).second->mUsedByServer == qfalse) + { // we are not used on both client and server, so unload + Unload( (*itr).first ); + } + + itr = next; + } + + entI = mROFFEntList.begin(); + while ( entI != mROFFEntList.end() ) + { + nextEnt = entI; + nextEnt++; + + if ((*entI)->mIsClient == isClient) + { + delete (*entI); + mROFFEntList.erase( entI ); + } + + entI = nextEnt; + } + mROFFEntList.clear(); + + return qtrue; +#else + TROFFList::iterator itr; + + itr = mROFFList.begin(); + + while ( itr != mROFFList.end() ) + { + Unload( (*itr).first ); + + itr = mROFFList.begin(); + } + return qtrue; +#endif +} + +//--------------------------------------------------------------------------- +// CROFFSystem::List +// Dumps the file path to the current set of cached roffs, for debug purposes +// +// INPUTS: +// none +// +// RETURN: +// none +//--------------------------------------------------------------------------- +void CROFFSystem::List() +{ + TROFFList::iterator itr; + + Com_Printf( S_COLOR_GREEN"\n--Cached ROFF files--\n" ); + Com_Printf( S_COLOR_GREEN"ID FILE\n" ); + + for ( itr = mROFFList.begin(); itr != mROFFList.end(); ++itr ) + { + Com_Printf( S_COLOR_GREEN"%2i - %s\n", (*itr).first, ((CROFF *)((*itr).second))->mROFFFilePath ); + } + + Com_Printf( S_COLOR_GREEN"\nFiles: %i\n", mROFFList.size() ); +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::List +// Overloaded version of List, dumps the specified roff data to the console +// +// INPUTS: +// id of roff to display +// +// RETURN: +// success or failure of operation +//--------------------------------------------------------------------------- +qboolean CROFFSystem::List( int id ) +{ + TROFFList::iterator itr; + + itr = mROFFList.find( id ); + + if ( itr != mROFFList.end() ) + { // requested item found in the list + CROFF *obj = ((CROFF *)((*itr).second)); + TROFF2Entry *dat = obj->mMoveRotateList; + + Com_Printf( S_COLOR_GREEN"File: %s\n", obj->mROFFFilePath ); + Com_Printf( S_COLOR_GREEN"ID: %i\n", id ); + Com_Printf( S_COLOR_GREEN"Entries: %i\n\n", obj->mROFFEntries ); + + Com_Printf( S_COLOR_GREEN"MOVE ROTATE\n" ); + + for ( int i = 0; i < obj->mROFFEntries; i++ ) + { + Com_Printf( S_COLOR_GREEN"%6.2f %6.2f %6.2f %6.2f %6.2f %6.2f\n", + dat[i].mOriginOffset[0], dat[i].mOriginOffset[1], dat[i].mOriginOffset[2], + dat[i].mRotateOffset[0], dat[i].mRotateOffset[1], dat[i].mRotateOffset[2] ); + } + + return qtrue; + } + + Com_Printf( S_COLOR_YELLOW"ROFF not found: id <%d>\n", id ); + + return qfalse; +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::Play +// Start roff playback on an entity +// +// INPUTS: +// the id of the entity that will be roffed +// the id of the roff to play +// +// RETURN: +// success or failure of add operation +//--------------------------------------------------------------------------- +qboolean CROFFSystem::Play( int entID, int id, qboolean doTranslation, qboolean isClient ) +{ + sharedEntity_t *ent = SV_GentityNum( entID ); + + ent->r.mIsRoffing = qtrue; +/*rjr if(ent->GetPhysics() == PHYSICS_TYPE_NONE) + { + ent->SetPhysics(PHYSICS_TYPE_BRUSHMODEL); + }*/ + //bjg TODO: reset this latter? + + if ( ent == 0 ) + { // shame on you.. + return qfalse; + } + + SROFFEntity *roffing_ent = new SROFFEntity; + + roffing_ent->mEntID = entID; + roffing_ent->mROFFID = id; + roffing_ent->mNextROFFTime = svs.time; + roffing_ent->mROFFFrame = 0; + roffing_ent->mKill = qfalse; + roffing_ent->mSignal = qtrue; // TODO: hook up the real signal code + roffing_ent->mTranslated = doTranslation; + roffing_ent->mIsClient = isClient; + + VectorCopy(ent->s.apos.trBase, roffing_ent->mStartAngles); + + mROFFEntList.push_back( roffing_ent ); + + return qtrue; +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::ListEnts +// List all of the ents in the roff system +// +// INPUTS: +// none +// +// RETURN: +// none +//--------------------------------------------------------------------------- +void CROFFSystem::ListEnts() +{ +/* char *name, *file; + int id; + + TROFFEntList::iterator itr = mROFFEntList.begin(); + TROFFList::iterator itrRoff; + + Com_Printf( S_COLOR_GREEN"\n--ROFFing Entities--\n" ); + Com_Printf( S_COLOR_GREEN"EntID EntName RoffFile\n" ); + + // display everything in the end list + for ( itr = mROFFEntList.begin(); itr != mROFFEntList.end(); ++itr ) + { + // Entity ID + id = ((SROFFEntity *)(*itr))->mEntID; + // Entity Name + name = entitySystem->GetEntityFromID( id )->GetName(); + // ROFF object that will contain the roff file name + itrRoff = mROFFList.find( ((SROFFEntity *)(*itr))->mROFFID ); + + if ( itrRoff != mROFFList.end() ) + { // grab our filename + file = ((CROFF *)((*itrRoff).second ))->mROFFFilePath; + } + else + { // roff filename not found == bad + file = "Error: Unknown"; + } + + Com_Printf( S_COLOR_GREEN"%3i %s %s\n", id, name, file ); + } + + Com_Printf( S_COLOR_GREEN"\nEntities: %i\n", mROFFEntList.size() );*/ +} + + +//--------------------------------------------------------------------------- +// CROFFSystem::PurgeEnt +// Prematurely purge an entity from the roff system +// +// INPUTS: +// the id of the entity to purge +// +// RETURN: +// success or failure of purge operation +//--------------------------------------------------------------------------- +qboolean CROFFSystem::PurgeEnt( int entID, qboolean isClient ) +{ + TROFFEntList::iterator itr = mROFFEntList.begin(); + + for ( itr = mROFFEntList.begin(); itr != mROFFEntList.end(); ++itr ) + { + if ( (*itr)->mIsClient == isClient && (*itr)->mEntID == entID) + { + // Make sure it won't stay lerping + ClearLerp( (*itr) ); + + delete (*itr); + + mROFFEntList.erase( itr ); + return qtrue; + } + } + + Com_Printf( S_COLOR_RED"Purge failed: Entity <%i> not found\n", entID ); + + return qfalse; +} + + + +//--------------------------------------------------------------------------- +// CROFFSystem::PurgeEnt +// Prematurely purge an entity from the roff system +// +// INPUTS: +// the name fo the entity to purge +// +// RETURN: +// success or failure of purge operation +//--------------------------------------------------------------------------- +qboolean CROFFSystem::PurgeEnt( char *name ) +{ +/* rjr CEntity *ent = entitySystem->GetEntityFromName( NULL, name ); + + if ( ent && ent->GetInUse() == qtrue ) + { + return PurgeEnt( ent->GetID() ); + } + else + { + Com_Printf( S_COLOR_RED"Entity <%s> not found or not in use\n", name ); + return qfalse; + }*/ + + return qfalse; +} + +//--------------------------------------------------------------------------- +// CROFFSystem::UpdateEntities +// Update all of the entities in the system +// +// INPUTS: +// none +// +// RETURN: +// none +//--------------------------------------------------------------------------- +void CROFFSystem::UpdateEntities(qboolean isClient) +{ + TROFFEntList::iterator itr = mROFFEntList.begin(); + TROFFList::iterator itrRoff; + + // display everything in the entity list + for ( itr = mROFFEntList.begin(); itr != mROFFEntList.end(); ++itr ) + { + if ((*itr)->mIsClient != isClient) + { + continue; + } + + // Get this entities ROFF object + itrRoff = mROFFList.find( ((SROFFEntity *)(*itr))->mROFFID ); + + if ( itrRoff != mROFFList.end() ) + { // roff that baby! + if ( !ApplyROFF( ((SROFFEntity *)(*itr)), ((CROFF *)((*itrRoff).second )))) + { // done roffing, mark for death + ((SROFFEntity *)(*itr))->mKill = qtrue; + } + } + else + { // roff not found == bad, dump an error message and purge this ent + Com_Printf( S_COLOR_RED"ROFF System Error:\n" ); +// Com_Printf( S_COLOR_RED" -ROFF not found for entity <%s>\n", +// entitySystem->GetEntityFromID(((SROFFEntity *)(*itr))->mEntID)->GetName() ); + + ((SROFFEntity *)(*itr))->mKill = qtrue; + + ClearLerp( (*itr) ); + } + } + + itr = mROFFEntList.begin(); + + // Delete killed ROFFers from the list + // Man, there just has to be a better way to do this + while ( itr != mROFFEntList.end() ) + { + if ((*itr)->mIsClient != isClient) + { + itr++; + continue; + } + + if ( ((SROFFEntity *)(*itr))->mKill == qtrue ) + { + //make sure ICARUS knows ROFF is stopped +// CICARUSGameInterface::TaskIDComplete( +// entitySystem->GetEntityFromID(((SROFFEntity *)(*itr))->mEntID), TID_MOVE); + // trash this guy from the list + delete (*itr); + mROFFEntList.erase( itr ); + itr = mROFFEntList.begin(); + } + else + { + itr++; + } + } +} + +//--------------------------------------------------------------------------- +// CROFFSystem::ApplyROFF +// Does the dirty work of applying the raw ROFF data +// +// INPUTS: +// The the roff_entity struct and the raw roff data +// +// RETURN: +// True == success; False == roff playback complete or failure +//--------------------------------------------------------------------------- +qboolean CROFFSystem::ApplyROFF( SROFFEntity *roff_ent, CROFFSystem::CROFF *roff ) +{ + vec3_t f, r, u, result; + sharedEntity_t *ent = NULL; + trajectory_t *originTrajectory, *angleTrajectory; + vec_t *origin, *angle; + + + if ( svs.time < roff_ent->mNextROFFTime ) + { // Not time to roff yet + return qtrue; + } + + if (roff_ent->mIsClient) + { +#ifndef DEDICATED + vec3_t originTemp, angleTemp; + originTrajectory = (trajectory_t *)VM_Call( cgvm, CG_GET_ORIGIN_TRAJECTORY, roff_ent->mEntID ); + angleTrajectory = (trajectory_t *)VM_Call( cgvm, CG_GET_ANGLE_TRAJECTORY, roff_ent->mEntID ); + VM_Call( cgvm, CG_GET_ORIGIN, roff_ent->mEntID, originTemp ); + origin = originTemp; + VM_Call( cgvm, CG_GET_ANGLES, roff_ent->mEntID, angleTemp ); + angle = angleTemp; +#endif + } + else + { + // Find the entity to apply the roff to + ent = SV_GentityNum( roff_ent->mEntID ); + + if ( ent == 0 ) + { // bad stuff + return qfalse; + } + + originTrajectory = &ent->s.pos; + angleTrajectory = &ent->s.apos; + origin = ent->r.currentOrigin; + angle = ent->r.currentAngles; + } + + + if ( roff_ent->mROFFFrame >= roff->mROFFEntries ) + { // we are done roffing, so stop moving and flag this ent to be removed + SetLerp( originTrajectory, TR_STATIONARY, origin, NULL, svs.time, roff->mLerp ); + SetLerp( angleTrajectory, TR_STATIONARY, angle, NULL, svs.time, roff->mLerp ); + if (!roff_ent->mIsClient) + { + ent->r.mIsRoffing = qfalse; + } + return qfalse; + } + + if (roff_ent->mTranslated) + { + AngleVectors(roff_ent->mStartAngles, f, r, u ); + VectorScale(f, roff->mMoveRotateList[roff_ent->mROFFFrame].mOriginOffset[0], result); + VectorMA(result, -roff->mMoveRotateList[roff_ent->mROFFFrame].mOriginOffset[1], r, result); + VectorMA(result, roff->mMoveRotateList[roff_ent->mROFFFrame].mOriginOffset[2], u, result); + } + else + { + VectorCopy(roff->mMoveRotateList[roff_ent->mROFFFrame].mOriginOffset, result); + } + + // Set up our origin interpolation + SetLerp( originTrajectory, TR_LINEAR, origin, result, svs.time, roff->mLerp ); + + // Set up our angle interpolation + SetLerp( angleTrajectory, TR_LINEAR, angle, + roff->mMoveRotateList[roff_ent->mROFFFrame].mRotateOffset, svs.time, roff->mLerp ); + + if (roff->mMoveRotateList[roff_ent->mROFFFrame].mStartNote >= 0) + { + int i; + + for(i=0;imMoveRotateList[roff_ent->mROFFFrame].mNumNotes;i++) + { + ProcessNote(roff_ent, roff->mNoteTrackIndexes[roff->mMoveRotateList[roff_ent->mROFFFrame].mStartNote + i]); + } + } + + // Advance ROFF frames and lock to a 10hz cycle + roff_ent->mROFFFrame++; + roff_ent->mNextROFFTime = svs.time + roff->mFrameTime; + + //rww - npcs need to know when they're getting roff'd + ent->next_roff_time = roff_ent->mNextROFFTime; + + + return qtrue; +} + + +/************************************************************************************************ + * CROFFSystem::ProcessNote * + * This function will send the note to the client. It will parse through the note for * + * leading or trailing white space (thus making each line feed a separate function call). * + * * + * Input * + * ent: the entity for which the roff is being played * + * note: the note that should be passed on * + * * + * Output / Return * + * none * + * * + ************************************************************************************************/ +void CROFFSystem::ProcessNote(SROFFEntity *roff_ent, char *note) +{ + char temp[1024]; + int pos, size; + + pos = 0; + while(note[pos]) + { + size = 0; + while(note[pos] && note[pos] < ' ') + { + pos++; + } + + while(note[pos] && note[pos] >= ' ') + { + temp[size++] = note[pos++]; + } + temp[size] = 0; + + if (size) + { + if (roff_ent->mIsClient) + { +#ifndef DEDICATED + VM_Call( cgvm, CG_ROFF_NOTETRACK_CALLBACK, roff_ent->mEntID, temp ); +#endif + } + else + { + VM_Call( gvm, GAME_ROFF_NOTETRACK_CALLBACK, roff_ent->mEntID, temp ); + } + } + } +} + +//--------------------------------------------------------------------------- +// CROFFSystem::ClearLerp +// Helper function to clear a given entities lerp fields +// +// INPUTS: +// The ID of the entity to clear +// +// RETURN: +// success or failure of the operation +//--------------------------------------------------------------------------- +qboolean CROFFSystem::ClearLerp( SROFFEntity *roff_ent ) +{ + sharedEntity_t *ent; + trajectory_t *originTrajectory, *angleTrajectory; + vec_t *origin, *angle; + + if (roff_ent->mIsClient) + { +#ifndef DEDICATED + vec3_t originTemp, angleTemp; + originTrajectory = (trajectory_t *)VM_Call( cgvm, CG_GET_ORIGIN_TRAJECTORY, roff_ent->mEntID ); + angleTrajectory = (trajectory_t *)VM_Call( cgvm, CG_GET_ANGLE_TRAJECTORY, roff_ent->mEntID ); + VM_Call( cgvm, CG_GET_ORIGIN, roff_ent->mEntID, originTemp ); + origin = originTemp; + VM_Call( cgvm, CG_GET_ANGLES, roff_ent->mEntID, angleTemp ); + angle = angleTemp; +#endif + } + else + { + // Find the entity to apply the roff to + ent = SV_GentityNum( roff_ent->mEntID ); + + if ( ent == 0 ) + { // bad stuff + return qfalse; + } + + originTrajectory = &ent->s.pos; + angleTrajectory = &ent->s.apos; + origin = ent->r.currentOrigin; + angle = ent->r.currentAngles; + } + + SetLerp( originTrajectory, TR_STATIONARY, origin, NULL, svs.time, ROFF_SAMPLE_RATE ); + SetLerp( angleTrajectory, TR_STATIONARY, angle, NULL, svs.time, ROFF_SAMPLE_RATE ); + + return qtrue; +} + +//--------------------------------------------------------------------------- +// CROFFSystem::SetLerp +// Helper function to set up a positional or angular interpolation +// +// INPUTS: +// The entity trajectory field to modify, the interpolation type, the base origin, +// and the interpolation start time +// +// RETURN: +// none +//--------------------------------------------------------------------------- +void CROFFSystem::SetLerp( trajectory_t *tr, trType_t type, vec3_t origin, vec3_t delta, int time, int rate) +{ + tr->trType = type; + tr->trTime = time; + VectorCopy( origin, tr->trBase ); + + // Check for a NULL delta + if ( delta ) + { + VectorScale( delta, rate, tr->trDelta ); + } + else + { + VectorClear( tr->trDelta ); + } +} + diff --git a/codemp/qcommon/roffsystem.h b/codemp/qcommon/roffsystem.h new file mode 100644 index 0000000..ca1ad1e --- /dev/null +++ b/codemp/qcommon/roffsystem.h @@ -0,0 +1,185 @@ +#if defined (_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif + +#if !defined(CROFFSYSTEM_H_INC) +#define CROFFSYSTEM_H_INC + +#ifndef __Q_SHARED_H + #include "../game/q_shared.h" //needs to be in here for entityState_t +#endif + +#if !defined(SERVER_H_INC) + #include "../server/server.h" +#endif + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include +#include +#pragma warning (pop) +using namespace std; + +// ROFF Defines +//------------------- +#define ROFF_VERSION 1 +#define ROFF_NEW_VERSION 2 +#define ROFF_STRING "ROFF" +#define ROFF_SAMPLE_RATE 10 // 10hz +#define ROFF_AUTO_FIX_BAD_ANGLES // exporter can mess up angles, + // defining this attempts to detect and fix these problems + + +// The CROFFSystem object provides all of the functionality of ROFF +// caching, playback, and clean-up, plus some useful debug features. +//-------------------------------------- +class CROFFSystem +//-------------------------------------- +{ +private: +//------ + + // forward declarations + class CROFF; + struct SROFFEntity; + + typedef map TROFFList; + typedef vector TROFFEntList; + + TROFFList mROFFList; // List of cached roffs + int mID; // unique ID generator for new roff objects + + TROFFEntList mROFFEntList; // List of roffing entities + + // ROFF Header file definition, nothing else needs to see this + typedef struct tROFFHeader + //------------------------------- + { + char mHeader[4]; // should match roff_string defined above + long mVersion; // version num, supported version defined above + float mCount; // I think this is a float because of a limitation of the roff exporter + + } TROFFHeader; + + // ROFF Entry, nothing else needs to see this + typedef struct tROFFEntry + //------------------------------- + { + float mOriginOffset[3]; + float mRotateOffset[3]; + } TROFFEntry; + + typedef struct tROFF2Header + //------------------------------- + { + char mHeader[4]; // should match roff_string defined above + long mVersion; // version num, supported version defined above + int mCount; // I think this is a float because of a limitation of the roff exporter + int mFrameRate; // Frame rate the roff should be played at + int mNumNotes; // number of notes (null terminated strings) after the roff data + + } TROFF2Header; + + // ROFF Entry, nothing else needs to see this + typedef struct tROFF2Entry + //------------------------------- + { + float mOriginOffset[3]; + float mRotateOffset[3]; + int mStartNote, mNumNotes; // note track info + } TROFF2Entry; + + // An individual ROFF object, + // contains actual rotation/offset information + //-------------------------------------- + class CROFF + //-------------------------------------- + { + public: + //------ + + int mID; // id for this roff file + char mROFFFilePath[MAX_QPATH]; // roff file path + int mROFFEntries; // count of move/rotate commands + int mFrameTime; // frame rate + int mLerp; // Lerp rate (FPS) + TROFF2Entry *mMoveRotateList; // move rotate/command list + int mNumNoteTracks; + char **mNoteTrackIndexes; + qboolean mUsedByClient; + qboolean mUsedByServer; + + CROFF() + { + mUsedByClient = mUsedByServer = qfalse; + } + CROFF( const char *file, int id ); + ~CROFF(); + + }; // class CROFF + + + // The roff system tracks entities that are + // roffing, so this is the internal structure + // that represents these objects. + //-------------------------------------- + struct SROFFEntity + //-------------------------------------- + { + int mEntID; // the entity that is currently roffing + + int mROFFID; // the roff to be applied to that entity + int mNextROFFTime; // next time we should roff + int mROFFFrame; // current roff frame we are applying + + qboolean mKill; // flag to kill a roffing ent + qboolean mSignal; // TODO: Need to implement some sort of signal to Icarus when roff is done. + qboolean mTranslated; // should this roff be "rotated" to fit the entity's initial position? + qboolean mIsClient; + vec3_t mStartAngles; // initial angle of the entity + }; // struct SROFFEntity + + + qboolean IsROFF( byte *file ); // Makes sure the file is a valid roff file + qboolean InitROFF( byte *file, CROFF *obj ); // Handles stashing raw roff data into the roff object + qboolean InitROFF2( byte *file, CROFF *obj ); // Handles stashing raw roff data into the roff object + void FixBadAngles(CROFF *obj); + int NewID() { return ++mID; } // Increment before return so we can use zero as failed return val + qboolean ApplyROFF( SROFFEntity *roff_ent, + CROFFSystem::CROFF *roff ); // True = success; False = roff complete + + void ProcessNote(SROFFEntity *roff_ent, char *note); + + void SetLerp( trajectory_t *tr, + trType_t, vec3_t origin, + vec3_t delta, int time, int rate ); + + qboolean ClearLerp( SROFFEntity *roff_ent ); // Clears out the angular and position lerp fields + +public: +//------ + + CROFFSystem() { mID = 0; mROFFEntList.clear(); } + ~CROFFSystem() { Restart(); } + + + qboolean Restart(); // Free up all system resources and reset the ID counter + + int Cache( const char *file, qboolean isClient ); // roffs should be precached at the start of each level + int GetID( const char *file ); // find the roff id by filename + qboolean Unload( int id ); // when a roff is done, it can be removed to free up resources + qboolean Clean(qboolean isClient); // should be called when level is done, frees all roff resources + void List(void); // dumps a list of all cached roff files to the console + qboolean List( int id ); // dumps the contents of the specified roff to the console + + qboolean Play( int entID, int roffID, qboolean doTranslation, qboolean isClient); // TODO: implement signal on playback completion. + void ListEnts(); // List the entities that are currently roffing + qboolean PurgeEnt( int entID, qboolean isClient ); // Purge the specified entity from the entity list by id + qboolean PurgeEnt( char *file ); // Purge the specified entity from the entity list by name + void UpdateEntities(qboolean isClient); // applys roff data to roffing entities. + +}; // class CROFFSystem + + +extern CROFFSystem theROFFSystem; + +#endif // CROFFSYSTEM_H_INC diff --git a/codemp/qcommon/sparc.h b/codemp/qcommon/sparc.h new file mode 100644 index 0000000..5b22c18 --- /dev/null +++ b/codemp/qcommon/sparc.h @@ -0,0 +1,725 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +/* + +AUTHOR: Dave Calvin +CREATED: 2002-05-07 + +SParse ARray Compressor. Given an array, this class reduces the memory +needed to store the array by eliminating the most-frequently used element. +The remaining elements are increased in size by one integer. + +If the compressed data would be larger than the original data, the +original data is stored as is. + +Compression is O(2N) where N is the number of elements to compress. + +Decompression is O(log M + N) where M is the number of elements after +compression (CompressedLength()) and N is the number of elements to decompress. +Decompression is O(1) when the same or smaller amount of data is requested as +the last decompression. + +The pointer returned by Decompress() is valid until the class is destroyed +or a new call is made to Compress() or Decompress(). + +Elements must define operator==, operator!=, and sizeof. + +*/ + +#ifndef __SPARC_H +#define __SPARC_H + +#ifdef _GAMECUBE +#define SPARC_BIG_ENDIAN +#endif + +//Bigger than a short, smaller than an int. +#pragma pack(push, 1) +struct NotSoShort +{ + unsigned char bytes[3]; + + NotSoShort(void) {} + + NotSoShort(unsigned int source) { +#ifdef SPARC_BIG_ENDIAN + bytes[2] = source & 0xFF; + bytes[1] = (source >> 8) & 0xFF; + bytes[0] = (source >> 16) & 0xFF; +#else + bytes[0] = source & 0xFF; + bytes[1] = (source >> 8) & 0xFF; + bytes[2] = (source >> 16) & 0xFF; +#endif + } + + inline unsigned int GetValue(void) { +#ifdef SPARC_BIG_ENDIAN + return (bytes[0] << 16) | (bytes[1] << 8) | bytes[2]; +#else + return (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]; +#endif + } + + inline bool operator==(unsigned int cmp) { +#ifdef SPARC_BIG_ENDIAN + return cmp == ((*(unsigned int*)bytes) >> 8); +#else + return cmp == ((*(unsigned int*)bytes) & 0x00FFFFFF); +#endif + } + + bool operator<(unsigned int cmp) { + unsigned int tmp = *(unsigned int*)bytes; +#ifdef SPARC_BIG_ENDIAN + tmp >>= 8; +#else + tmp &= 0x00FFFFFF; +#endif + return tmp < cmp; + } + + bool operator<=(unsigned int cmp) { + unsigned int tmp = *(unsigned int*)bytes; +#ifdef SPARC_BIG_ENDIAN + tmp >>= 8; +#else + tmp &= 0x00FFFFFF; +#endif + return tmp <= cmp; + } + + bool operator>(unsigned int cmp) { + unsigned int tmp = *(unsigned int*)bytes; +#ifdef SPARC_BIG_ENDIAN + tmp >>= 8; +#else + tmp &= 0x00FFFFFF; +#endif + return tmp > cmp; + } +}; + +//Compressed data is made up of these elements. +template +struct SPARCElement +{ + T data; + U offset; +}; +#pragma pack(pop) + + +inline unsigned int SPARC_SWAP32(unsigned int x, bool doSwap) { + if (doSwap) { + return ((unsigned int)( ( (x & 0xff000000) >> 24) + + ( (x & 0x00ff0000) >> 8 ) + + ( (x & 0x0000ff00) << 8 ) + + ( (x & 0x000000ff) << 24 ) )); + } + return x; +} + +inline NotSoShort SPARC_SWAP24(NotSoShort x, bool doSwap) { + if (doSwap) { + x.bytes[0] ^= x.bytes[2]; + x.bytes[2] ^= x.bytes[0]; + x.bytes[0] ^= x.bytes[2]; + } + return x; +} + +inline unsigned short SPARC_SWAP16(unsigned short x, bool doSwap) { + if (doSwap) { + return ((unsigned short)( ( (x & 0xff00) >> 8) + + ( (x & 0x00ff) << 8 ) )); + } + return x; +} + + +//The core of the SPARC system. T is the data type to be compressed. +//U is the data type needed to store offsets information in the compressed +//data. Smaller U makes for better compression but bigger data requires +//larger U. +template +class SPARCCore +{ +private: + //Using compression or just storing clear data? + bool compressionUsed; + + //Compressed data and its length. + SPARCElement *compressedData; + unsigned int compressedLength; + + //Decompression cache. + T *decompressedData; + unsigned int decompressedOffset; + unsigned int decompressedLength; + + //Element which was removed to compress. + T removedElement; + + //Length of original data before compression. + unsigned int originalLength; + + //Memory allocators. + void* (*Allocator)(unsigned int size); + void (*Deallocator)(void *ptr); + + //Destroy all allocated memory. + void Cleanup(void) { + if(compressedData) { + if(Deallocator) { + Deallocator(compressedData); + } else { + delete [] compressedData; + } + compressedData = NULL; + } + + if(decompressedData) { + if(Deallocator) { + Deallocator(decompressedData); + } else { + delete [] decompressedData; + } + decompressedData = NULL; + } + } + + void Init(void) { + compressionUsed = false; + compressedData = NULL; + originalLength = 0; + compressedLength = 0; + decompressedData = NULL; + decompressedOffset = 0; + decompressedLength = 0; + } + + + //Binary search for the compressed element most closely matching 'offset'. + SPARCElement *FindDecompStart(unsigned int offset) + { + unsigned int startPoint = compressedLength / 2; + unsigned int divisor = 4; + unsigned int leap; + while(1) { + if(compressedData[startPoint].offset <= offset && + compressedData[startPoint+1].offset > offset) { + if(compressedData[startPoint].offset == offset) { + return &compressedData[startPoint]; + } else { + return &compressedData[startPoint+1]; + } + } + + leap = compressedLength / divisor; + if(leap < 1) { + leap = 1; + } else { + divisor *= 2; + } + if(compressedData[startPoint].offset > offset) { + startPoint -= leap; + } else { + startPoint += leap; + } + } + } + +public: + SPARCCore(void) { + Init(); + Allocator = NULL; + Deallocator = NULL; + } + + ~SPARCCore(void) { + Cleanup(); + } + + void SetAllocator(void* (*alloc)(unsigned int size), + void (*dealloc)(void *ptr)) { + Allocator = alloc; + Deallocator = dealloc; + } + + //Just store the array without compression. + unsigned int Store(const T *array, unsigned int length) { + //Destroy old data. + Cleanup(); + Init(); + + //Allocate memory and copy array. + if(Allocator) { + decompressedData = (T*)Allocator(length * sizeof(T)); + } else { + decompressedData = new T[length]; + } + compressedLength = length; + memcpy(decompressedData, array, sizeof(T) * length); + + //Set length. + originalLength = length; + + return CompressedSize(); + } + + //Load compressed data directly. + unsigned int Load(const char *array, unsigned int length) { + //Destroy old data. + Cleanup(); + Init(); + + //Restore some attributes. + compressionUsed = (bool)*array++; + + assert(sizeof(T) == 1); //For now only support characters. + removedElement = *(T*)array; + array += sizeof(T); + + originalLength = *(unsigned int*)array; + array += sizeof(unsigned int); + + compressedLength = *(unsigned int*)array; + array += sizeof(unsigned int); + + //Allocate memory and copy array. + if (compressionUsed) { + if(Allocator) { + compressedData = (SPARCElement*) + Allocator(compressedLength * sizeof(SPARCElement)); + } else { + compressedData = new SPARCElement[compressedLength]; + } + memcpy(compressedData, array, + compressedLength * sizeof(SPARCElement)); + } + else { + if(Allocator) { + decompressedData = (T*)Allocator( + compressedLength * sizeof(T)); + } else { + decompressedData = new T[compressedLength]; + } + memcpy(decompressedData, array, compressedLength * sizeof(T)); + } + + return CompressedSize(); + } + + //Save state for later restoration. + unsigned int Save(char *array, unsigned int length, bool doSwap) { + //Figure out how much space is needed. + unsigned int size = sizeof(char) + sizeof(T) + + sizeof(unsigned int) + sizeof(unsigned int); + + if (compressionUsed) { + size += compressedLength * sizeof(SPARCElement); + } + else { + size += compressedLength * sizeof(T); + } + + assert(length >= size); + + //Save some attributes. + *array++ = (char)compressionUsed; + + assert(sizeof(T) == 1); //For now only support characters. + *(T*)array = removedElement; + array += sizeof(T); + + *(unsigned int*)array = SPARC_SWAP32(originalLength, doSwap); + array += sizeof(unsigned int); + + *(unsigned int*)array = SPARC_SWAP32(compressedLength, doSwap); + array += sizeof(unsigned int); + + //Store compressed data (or uncompressed data if none exists) + if (compressionUsed) { + for (unsigned int i = 0; i < compressedLength; ++i) { + //Copy the data element. For now only support characters. + ((SPARCElement *)array)[i].data = compressedData[i].data; + + //Copy the offset to the next unique element. + if (sizeof(U) == 1) { + ((SPARCElement *)array)[i].offset = + compressedData[i].offset; + } + else if (sizeof(U) == 2) { + ((SPARCElement *)array)[i].offset = + SPARC_SWAP16(*(unsigned short*)&compressedData[i].offset, + doSwap); + } + else if (sizeof(U) == 3) { + ((SPARCElement *)array)[i].offset = + SPARC_SWAP24(*(NotSoShort*)&compressedData[i].offset, + doSwap); + } + else if (sizeof(U) == 4) { + ((SPARCElement *)array)[i].offset = + SPARC_SWAP32(*(unsigned int*)&compressedData[i].offset, + doSwap); + } + } + } + else { + memcpy(array, decompressedData, compressedLength * sizeof(T)); + } + + return size; + } + + //Compresses this array, returns the compressed size. Compresses + //by eliminating the given element. + unsigned int Compress(const T *array, unsigned int length, T removal) { + + unsigned int i; + unsigned int numRemove = 0; + SPARCElement *compress; + + //Destroy old data. + Cleanup(); + Init(); + + //Count number of elements to remove. Can't remove first or + //last element (prevents boundary conditions). + for(i=1; i) * compressedLength >= + sizeof(T) * length) { + Store(array, length); + return CompressedSize(); + } + + //Allocate memory for compressed elements. + if(Allocator) { + compressedData = (SPARCElement*) + Allocator(compressedLength * sizeof(SPARCElement)); + } else { + compressedData = new SPARCElement[compressedLength]; + } + compressionUsed = true; + + //Fill compressed array. First and last elements go in no matter + //what. + compressedData[0].data = array[0]; + compressedData[0].offset = 0; + compress = &compressedData[1]; + for(i=1; idata = array[i]; + compress->offset = i; + compress++; + } + } + compress->data = array[i]; + compress->offset = i; + + //Store removal value for decompression purposes. + removedElement = removal; + + //Store original length for bounds checking. + originalLength = length; + + //Return the compressed size. + return CompressedSize(); + } + + + //Get the compressed data size in bytes, or 0 if nothing stored. + unsigned int CompressedSize(void) { + return compressedLength * sizeof(SPARCElement); + } + + //Get the decompressed data starting at offset and ending at + //offset + length. Returns NULL on error. + const T *Decompress(unsigned int offset, unsigned int length) { + + SPARCElement *decomp = NULL; + unsigned int i; + + //If data isn't compressed, just return a pointers. + if(!compressionUsed) { + return decompressedData + offset; + } + + //If last decompression falls within offset and length, just return + //a pointer. + if(decompressedData && decompressedOffset <= offset && + decompressedOffset + decompressedLength >= offset + length) { + return decompressedData + offset - decompressedOffset; + } + + + + //Allocate new space for decompression if length has changed. + if(length != decompressedLength) { + //Destroy old data first. + if(decompressedData) { + if(Deallocator) { + Deallocator(decompressedData); + } else { + delete [] decompressedData; + } + } + + if(Allocator) { + decompressedData = (T*)Allocator(length * sizeof(T)); + } else { + decompressedData = new T[length]; + } + } + decompressedOffset = offset; + decompressedLength = length; + + //Find position to start decompressing from. + decomp = FindDecompStart(offset); + + if(!decomp) { //should never happen + assert(0); + return NULL; + } + + //Decompress the data. + for(i=0; i < length; i++) { + if(decomp->offset == i + offset) { + decompressedData[i] = decomp->data; + decomp++; + } else { + decompressedData[i] = removedElement; + } + } + + return decompressedData; + } +}; + + +//The user-interface to SPARC. Automatically selects the best core based +//on data size. +template +class SPARC +{ +private: + void *core; + unsigned char offsetBytes; + + //Memory allocators. + void* (*Allocator)(unsigned int size); + void (*Deallocator)(void *ptr); + +public: + SPARC(void) { + core = NULL; + offsetBytes = 0; + Allocator = NULL; + Deallocator = NULL; + } + + ~SPARC(void) { + Release(); + }; + + void SetAllocator(void* (*alloc)(unsigned int size), + void (*dealloc)(void *ptr)) { + Allocator = alloc; + Deallocator = dealloc; + } + + //Select a core, cast it to the right type and return the size. + unsigned int CompressedSize(void) { + if(!core) { + return 0; + } + + switch(offsetBytes) { + case 1: + return ((SPARCCore*)core)->CompressedSize(); + case 2: + return ((SPARCCore*)core)->CompressedSize(); + case 3: + return ((SPARCCore*)core)->CompressedSize(); + case 4: + return ((SPARCCore*)core)->CompressedSize(); + } + + return 0; + } + + //Always use the same core type since we won't be compressing. + unsigned int Store(const T *array, unsigned int length) + { + Release(); + offsetBytes = 1; + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> Store(array, length); + } + + //Load compressed data directly. + unsigned int Load(const char *array, unsigned int length) { + Release(); + + offsetBytes = *array++; + + switch (offsetBytes) { + case 1: + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Load(array, length-1); + case 2: + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Load(array, length-1); + case 3: + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Load(array, length-1); + case 4: + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Load(array, length-1); + default: + assert(false); + return 0; + } + } + + //Save compressed data into array. + unsigned int Save(char *array, unsigned int length, bool doSwap) { + *array++ = offsetBytes; + + switch (offsetBytes) { + case 1: + return ((SPARCCore*)core)-> + Save(array, length-1, doSwap); + case 2: + return ((SPARCCore*)core)-> + Save(array, length-1, doSwap); + case 3: + return ((SPARCCore*)core)-> + Save(array, length-1, doSwap); + case 4: + return ((SPARCCore*)core)-> + Save(array, length-1, doSwap); + default: + assert(false); + return 0; + } + } + + //Create the smallest core possible for the given data. + unsigned int Compress(const T *array, unsigned int length, T removal) { + Release(); + + if(length < 256) { + offsetBytes = 1; + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Compress(array, length, removal); + } else if(length < 65536) { + offsetBytes = 2; + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Compress(array, length, removal); + } else if(length < 16777216) { + offsetBytes = 3; + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Compress(array, length, removal); + } else { + offsetBytes = 4; + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Compress(array, length, removal); + } + } + + //Cast to the correct core type and decompress. + const T *Decompress(unsigned int offset, unsigned int length) { + if(!core) { + return NULL; + } + + switch(offsetBytes) { + case 1: + return ((SPARCCore*)core)-> + Decompress(offset, length); + case 2: + return ((SPARCCore*)core)-> + Decompress(offset, length); + case 3: + return ((SPARCCore*)core)-> + Decompress(offset, length); + case 4: + return ((SPARCCore*)core)-> + Decompress(offset, length); + } + + return NULL; + } + + //Destroy all compressed data and the current decompressed buffer. + void Release(void) { + if(core) { + switch(offsetBytes) { + case 1: + delete (SPARCCore*)core; + break; + case 2: + delete (SPARCCore*)core; + break; + case 3: + delete (SPARCCore*)core; + break; + case 4: + delete (SPARCCore*)core; + break; + } + core = NULL; + } + } +}; + +#endif diff --git a/codemp/qcommon/sstring.h b/codemp/qcommon/sstring.h new file mode 100644 index 0000000..083adfc --- /dev/null +++ b/codemp/qcommon/sstring.h @@ -0,0 +1,120 @@ +// Filename:- sstring.h +// +// Gil's string template, used to replace Microsoft's vrsion which doesn't compile under certain stl map<> +// conditions... + + +#ifndef SSTRING_H +#define SSTRING_H + + +template +class sstring +{ + struct SStorage + { + char data[MaxSize]; + }; + SStorage mStorage; +public: +/* don't figure we need this + template + sstring(const sstring &o) + { + assert(strlen(o.mStorage.data) &o) + { + //strcpy(mStorage.data,o.mStorage.data); + Q_strncpyz(mStorage.data,o.mStorage.data,sizeof(mStorage.data)); + } + sstring(const char *s) + { + //assert(strlen(s) + sstring & operator =(const sstring &o) + { + assert(strlen(o.mStorage.data) & operator=(const sstring &o) + { + //strcpy(mStorage.data,o.mStorage.data); + Q_strncpyz(mStorage.data,o.mStorage.data,sizeof(mStorage.data)); + return *this; + } + sstring & operator=(const char *s) + { + assert(strlen(s) &o) const + { + if (!Q_stricmp(mStorage.data,o.mStorage.data)) + { + return true; + } + return false; + } + bool operator!=(const sstring &o) const + { + if (Q_stricmp(mStorage.data,o.mStorage.data)!=0) + { + return true; + } + return false; + } + bool operator<(const sstring &o) const + { + if (Q_stricmp(mStorage.data,o.mStorage.data)<0) + { + return true; + } + return false; + } + bool operator>(const sstring &o) const + { + if (Q_stricmp(mStorage.data,o.mStorage.data)>0) + { + return true; + } + return false; + } +}; + +typedef sstring sstring_t; + +#endif // #ifndef SSTRING_H + +/////////////////// eof //////////////////// + diff --git a/codemp/qcommon/stringed_ingame.cpp b/codemp/qcommon/stringed_ingame.cpp new file mode 100644 index 0000000..a3048d4 --- /dev/null +++ b/codemp/qcommon/stringed_ingame.cpp @@ -0,0 +1,985 @@ +// Filename:- stringed_ingame.cpp +// +// This file is designed to be pasted into each game project that uses the StringEd package's files. +// You can alter the way it does things by (eg) replacing STL with RATL, BUT LEAVE THE OVERALL +// FUNCTIONALITY THE SAME, or if I ever make any funadamental changes to the way the package works +// then you're going to be SOOL (shit out of luck ;-)... +// + +////////////////////////////////////////////////// +// +// stuff common to all qcommon files... +#include "../server/server.h" +#include "../game/q_shared.h" +#include "qcommon.h" +#include "../qcommon/fixedmap.h" +#include "../zlib/zlib.h" + +// +////////////////////////////////////////////////// + + +#pragma warning ( disable : 4511 ) // copy constructor could not be generated +#pragma warning ( disable : 4512 ) // assignment operator could not be generated +#pragma warning ( disable : 4663 ) // C++ language change: blah blah template crap blah blah +#include "stringed_ingame.h" +#include "stringed_interface.h" + +// Needed for DWORD and XC_LANGUAGE defines: +#include + +/////////////////////////////////////////////// + +// some STL stuff... +#include +using namespace std; + +/////////////////////////////////////////////// + +cvar_t *se_language = NULL; + +// Yeah, it's hardcoded. I don't give a shit. +#define MAX_STRING_ENTRIES 4096 + + +typedef struct SE_Entry_s +{ + string m_strString; +} SE_Entry_t; + + +//typedef map mapStringEntries_t; + +class CStringEdPackage +{ +private: + + SE_BOOL m_bEndMarkerFound_ParseOnly; + string m_strCurrentEntryRef_ParseOnly; + string m_strCurrentEntryEnglish_ParseOnly; + string m_strCurrentFileRef_ParseOnly; + string m_strLoadingLanguage_ParseOnly; // eg "german" + SE_BOOL m_bLoadingEnglish_ParseOnly; + +public: + + CStringEdPackage() + { + Z_PushNewDeleteTag( TAG_STRINGED ); + m_Strings = new VVFixedMap< char *, unsigned long >( MAX_STRING_ENTRIES ); + Z_PopNewDeleteTag(); + + Clear( SE_FALSE ); + } + + ~CStringEdPackage() + { + Clear( SE_FALSE ); + } + + // Text entries, indexed by crc32 of reference: + VVFixedMap< char *, unsigned long > *m_Strings; + +// mapStringEntries_t m_StringEntries; // needs to be in public space now + + void Clear( SE_BOOL bChangingLanguages ); + void SetupNewFileParse( LPCSTR psFileName ); + SE_BOOL ReadLine( LPCSTR &psParsePos, char *psDest ); + LPCSTR ParseLine( LPCSTR psLine ); + LPCSTR ExtractLanguageFromPath( LPCSTR psFileName ); + SE_BOOL EndMarkerFoundDuringParse( void ) + { + return m_bEndMarkerFound_ParseOnly; + } + +private: + + void AddEntry( LPCSTR psLocalReference ); + int GetNumStrings(void); + void SetString( LPCSTR psLocalReference, LPCSTR psNewString, SE_BOOL bEnglishDebug ); + SE_BOOL SetReference( int iIndex, LPCSTR psNewString ); + LPCSTR GetCurrentFileName(void); + LPCSTR GetCurrentReference_ParseOnly( void ); + SE_BOOL CheckLineForKeyword( LPCSTR psKeyword, LPCSTR &psLine); + LPCSTR InsideQuotes( LPCSTR psLine ); + LPCSTR ConvertCRLiterals_Read( LPCSTR psString ); + void REMKill( char *psBuffer ); + char *Filename_PathOnly( LPCSTR psFilename ); + char *Filename_WithoutPath(LPCSTR psFilename); + char *Filename_WithoutExt(LPCSTR psFilename); +}; + +CStringEdPackage TheStringPackage; + + +void CStringEdPackage::Clear( SE_BOOL bChangingLanguages ) +{ +// m_StringEntries.clear(); + + m_bEndMarkerFound_ParseOnly = SE_FALSE; + m_strCurrentEntryRef_ParseOnly = ""; + m_strCurrentEntryEnglish_ParseOnly = ""; + // + // the other vars are cleared in SetupNewFileParse(), and are ok to not do here. + // +} + + + +// loses anything after the path (if any), (eg) "dir/name.bmp" becomes "dir" +// (copes with either slash-scheme for names) +// +// (normally I'd call another function for this, but this is supposed to be engine-independant, +// so a certain amount of re-invention of the wheel is to be expected...) +// +char *CStringEdPackage::Filename_PathOnly(LPCSTR psFilename) +{ + static char sString[ iSE_MAX_FILENAME_LENGTH ]; + + strcpy(sString,psFilename); + + char *p1= strrchr(sString,'\\'); + char *p2= strrchr(sString,'/'); + char *p = (p1>p2)?p1:p2; + if (p) + *p=0; + + return sString; +} + + +// returns (eg) "dir/name" for "dir/name.bmp" +// (copes with either slash-scheme for names) +// +// (normally I'd call another function for this, but this is supposed to be engine-independant, +// so a certain amount of re-invention of the wheel is to be expected...) +// +char *CStringEdPackage::Filename_WithoutExt(LPCSTR psFilename) +{ + static char sString[ iSE_MAX_FILENAME_LENGTH ]; + + strcpy(sString,psFilename); + + char *p = strrchr(sString,'.'); + char *p2= strrchr(sString,'\\'); + char *p3= strrchr(sString,'/'); + + // special check, make sure the first suffix we found from the end wasn't just a directory suffix (eg on a path'd filename with no extension anyway) + // + if (p && + (p2==0 || (p2 && p>p2)) && + (p3==0 || (p3 && p>p3)) + ) + *p=0; + + return sString; +} + +// returns actual filename only, no path +// (copes with either slash-scheme for names) +// +// (normally I'd call another function for this, but this is supposed to be engine-independant, +// so a certain amount of re-invention of the wheel is to be expected...) +// +char *CStringEdPackage::Filename_WithoutPath(LPCSTR psFilename) +{ + static char sString[ iSE_MAX_FILENAME_LENGTH ]; + + LPCSTR psCopyPos = psFilename; + + while (*psFilename) + { + if (*psFilename == '/' || *psFilename == '\\') + psCopyPos = psFilename+1; + psFilename++; + } + + strcpy(sString,psCopyPos); + + return sString; +} + + +LPCSTR CStringEdPackage::ExtractLanguageFromPath( LPCSTR psFileName ) +{ + return Filename_WithoutPath( Filename_PathOnly( psFileName ) ); +} + + +void CStringEdPackage::SetupNewFileParse( LPCSTR psFileName ) +{ + char sString[ iSE_MAX_FILENAME_LENGTH ]; + + strcpy(sString, Filename_WithoutPath( Filename_WithoutExt( psFileName ) )); + Q_strupr(sString); + + m_strCurrentFileRef_ParseOnly = sString; // eg "OBJECTIVES" + m_strLoadingLanguage_ParseOnly = ExtractLanguageFromPath( psFileName ); + m_bLoadingEnglish_ParseOnly = (!stricmp( m_strLoadingLanguage_ParseOnly.c_str(), "english" )) ? SE_TRUE : SE_FALSE; +} + + +// returns SE_TRUE if supplied keyword found at line start (and advances supplied ptr past any whitespace to next arg (or line end if none), +// +// else returns SE_FALSE... +// +SE_BOOL CStringEdPackage::CheckLineForKeyword( LPCSTR psKeyword, LPCSTR &psLine) +{ + if (!Q_stricmpn(psKeyword, psLine, strlen(psKeyword)) ) + { + psLine += strlen(psKeyword); + + // skip whitespace to arrive at next item... + // + while ( *psLine == '\t' || *psLine == ' ' ) + { + psLine++; + } + return SE_TRUE; + } + + return SE_FALSE; +} + +// change "\n" to '\n' (i.e. 2-byte char-string to 1-byte ctrl-code)... +// (or "\r\n" in editor) +// +LPCSTR CStringEdPackage::ConvertCRLiterals_Read( LPCSTR psString ) +{ + static string str; + str = psString; + int iLoc; + while ( (iLoc = str.find("\\n")) != -1 ) + { + str[iLoc ] = '\n'; + str.erase( iLoc+1,1 ); + } + + return str.c_str(); +} + + +// kill off any "//" onwards part in the line, but NOT if it's inside a quoted string... +// +void CStringEdPackage::REMKill( char *psBuffer ) +{ + char *psScanPos = psBuffer; + char *p; + int iDoubleQuotesSoFar = 0; + + // scan forwards in case there are more than one (and the first is inside quotes)... + // + while ( (p=strstr(psScanPos,"//")) != NULL) + { + // count the number of double quotes before this point, if odd number, then we're inside quotes... + // + int iDoubleQuoteCount = iDoubleQuotesSoFar; + + for (int i=0; i=0 && isspace(psScanPos[iWhiteSpaceScanPos])) + { + psScanPos[iWhiteSpaceScanPos--] = '\0'; + } + } + + return; + } + else + { + // inside quotes (blast), oh well, skip past and keep scanning... + // + psScanPos = p+1; + iDoubleQuotesSoFar = iDoubleQuoteCount; + } + } +} + +// returns true while new lines available to be read... +// +SE_BOOL CStringEdPackage::ReadLine( LPCSTR &psParsePos, char *psDest ) +{ + if (psParsePos[0]) + { + LPCSTR psLineEnd = strchr(psParsePos, '\n'); + if (psLineEnd) + { + int iCharsToCopy = (psLineEnd - psParsePos); + strncpy(psDest, psParsePos, iCharsToCopy); + psDest[iCharsToCopy] = '\0'; + psParsePos += iCharsToCopy; + while (*psParsePos && strchr("\r\n",*psParsePos)) + { + psParsePos++; // skip over CR or CR/LF pairs + } + } + else + { + // last line... + // + strcpy(psDest, psParsePos); + psParsePos += strlen(psParsePos); + } + + // clean up the line... + // + if (psDest[0]) + { + int iWhiteSpaceScanPos = strlen(psDest)-1; + while (iWhiteSpaceScanPos>=0 && isspace(psDest[iWhiteSpaceScanPos])) + { + psDest[iWhiteSpaceScanPos--] = '\0'; + } + + REMKill( psDest ); + } + return SE_TRUE; + } + + return SE_FALSE; +} + +// remove any outside quotes from this supplied line, plus any leading or trailing whitespace... +// +LPCSTR CStringEdPackage::InsideQuotes( LPCSTR psLine ) +{ + // I *could* replace this string object with a declared array, but wasn't sure how big to leave it, and it'd have to + // be static as well, hence permanent. (problem on consoles?) + // + static string str; + str = ""; // do NOT join to above line + + // skip any leading whitespace... + // + while (*psLine == ' ' || *psLine == '\t') + { + psLine++; + } + + // skip any leading quote... + // + if (*psLine == '"') + { + psLine++; + } + + // assign it... + // + str = psLine; + + if (psLine[0]) + { + // lose any trailing whitespace... + // + while ( str.c_str()[ strlen(str.c_str()) -1 ] == ' ' || + str.c_str()[ strlen(str.c_str()) -1 ] == '\t' + ) + { + str.erase( strlen(str.c_str()) -1, 1); + } + + // lose any trailing quote... + // + if (str.c_str()[ strlen(str.c_str()) -1 ] == '"') + { + str.erase( strlen(str.c_str()) -1, 1); + } + } + + // and return it... + // + return str.c_str(); +} + + + +// this copes with both foreigners using hi-char values (eg the french using 0x92 instead of 0x27 +// for a "'" char), as well as the fact that our buggy fontgen program writes out zeroed glyph info for +// some fonts anyway (though not all, just as a gotcha). +// +// New bit, instead of static buffer (since XBox guys are desperately short of mem) I return a malloc'd buffer now, +// so remember to free it! +// +static char *CopeWithDumbStringData( LPCSTR psSentence, LPCSTR psThisLanguage ) +{ + const int iBufferSize = strlen(psSentence)*3; // *3 to allow for expansion of anything even stupid string consisting entirely of elipsis chars + char *psNewString = (char *) Z_Malloc(iBufferSize, TAG_TEMP_WORKSPACE, qfalse); + Q_strncpyz(psNewString, psSentence, iBufferSize); + + // this is annoying, I have to just guess at which languages to do it for (ie NOT ASIAN/MBCS!!!) since the + // string system was deliberately (and correctly) designed to not know or care whether it was doing SBCS + // or MBCS languages, because it was never envisioned that I'd have to clean up other people's mess. + // + // Ok, bollocks to it, this will have to do. Any other languages that come later and have bugs in their text can + // get fixed by them typing it in properly in the first place... + // + if (!stricmp(psThisLanguage,"ENGLISH") || + !stricmp(psThisLanguage,"FRENCH") || + !stricmp(psThisLanguage,"GERMAN") || + !stricmp(psThisLanguage,"ITALIAN") || + !stricmp(psThisLanguage,"SPANISH") || + !stricmp(psThisLanguage,"POLISH") || + !stricmp(psThisLanguage,"RUSSIAN") + ) + { + char *p; + + // strXLS_Speech.Replace(va("%c",0x92),va("%c",0x27)); // "'" + while ((p=strchr(psNewString,0x92))!=NULL) // "rich" (and illegal) apostrophe + { + *p = 0x27; + } + + // strXLS_Speech.Replace(va("%c",0x93),"\""); // smart quotes -> '"' + while ((p=strchr(psNewString,0x93))!=NULL) + { + *p = '"'; + } + + // strXLS_Speech.Replace(va("%c",0x94),"\""); // smart quotes -> '"' + while ((p=strchr(psNewString,0x94))!=NULL) + { + *p = '"'; + } + + // strXLS_Speech.Replace(va("%c",0x0B),"."); // full stop + while ((p=strchr(psNewString,0x0B))!=NULL) + { + *p = '.'; + } + + // strXLS_Speech.Replace(va("%c",0x85),"..."); // "..."-char -> 3-char "..." + while ((p=strchr(psNewString,0x85))!=NULL) // "rich" (and illegal) apostrophe + { + memmove(p+2,p,strlen(p)); + *p++ = '.'; + *p++ = '.'; + *p = '.'; + } + + // strXLS_Speech.Replace(va("%c",0x91),va("%c",0x27)); // "'" + while ((p=strchr(psNewString,0x91))!=NULL) + { + *p = 0x27; + } + + // strXLS_Speech.Replace(va("%c",0x96),va("%c",0x2D)); // "-" + while ((p=strchr(psNewString,0x96))!=NULL) + { + *p = 0x2D; + } + + // strXLS_Speech.Replace(va("%c",0x97),va("%c",0x2D)); // "-" + while ((p=strchr(psNewString,0x97))!=NULL) + { + *p = 0x2D; + } + + // bug fix for picky grammatical errors, replace "?." with "? " + // + while ((p=strstr(psNewString,"?."))!=NULL) + { + p[1] = ' '; + } + + // StripEd and our print code don't support tabs... + // + while ((p=strchr(psNewString,0x09))!=NULL) + { + *p = ' '; + } + } + + return psNewString; +} + +// return is either NULL for good else error message to display... +// +LPCSTR CStringEdPackage::ParseLine( LPCSTR psLine ) +{ + LPCSTR psErrorMessage = NULL; + + if (psLine) + { + if (CheckLineForKeyword( sSE_KEYWORD_VERSION, psLine )) + { + // VERSION "1" + // + LPCSTR psVersionNumber = InsideQuotes( psLine ); + int iVersionNumber = atoi( psVersionNumber ); + + if (iVersionNumber != iSE_VERSION) + { + psErrorMessage = va("Unexpected version number %d, expecting %d!\n", iVersionNumber, iSE_VERSION); + } + } + else + if ( CheckLineForKeyword(sSE_KEYWORD_CONFIG, psLine) + || CheckLineForKeyword(sSE_KEYWORD_FILENOTES, psLine) + || CheckLineForKeyword(sSE_KEYWORD_NOTES, psLine) + ) + { + // not used ingame, but need to absorb the token + } + else + if (CheckLineForKeyword(sSE_KEYWORD_REFERENCE, psLine)) + { + // REFERENCE GUARD_GOOD_TO_SEE_YOU + // + LPCSTR psLocalReference = InsideQuotes( psLine ); + AddEntry( psLocalReference ); + } + else + if (CheckLineForKeyword(sSE_KEYWORD_ENDMARKER, psLine)) + { + // ENDMARKER + // + m_bEndMarkerFound_ParseOnly = SE_TRUE; // the only major error checking I bother to do (for file truncation) + } + else + if (!Q_stricmpn(sSE_KEYWORD_LANG, psLine, strlen(sSE_KEYWORD_LANG))) + { + // LANG_ENGLISH "GUARD: Good to see you, sir. Taylor is waiting for you in the clean tent. We need to get you suited up. " + // + LPCSTR psReference = GetCurrentReference_ParseOnly(); + if ( psReference[0] ) + { + psLine += strlen(sSE_KEYWORD_LANG); + + // what language is this?... + // + LPCSTR psWordEnd = psLine; + while (*psWordEnd && *psWordEnd != ' ' && *psWordEnd != '\t') + { + psWordEnd++; + } + char sThisLanguage[1024]={0}; + int iCharsToCopy = psWordEnd - psLine; + if (iCharsToCopy > sizeof(sThisLanguage)-1) + { + iCharsToCopy = sizeof(sThisLanguage)-1; + } + strncpy(sThisLanguage, psLine, iCharsToCopy); // already declared as {0} so no need to zero-cap dest buffer + + psLine += strlen(sThisLanguage); + LPCSTR _psSentence = ConvertCRLiterals_Read( InsideQuotes( psLine ) ); + + // Dammit, I hate having to do crap like this just because other people mess up and put + // stupid data in their text, so I have to cope with it. + // + // note hackery with _psSentence and psSentence because of const-ness. bleurgh. Just don't ask. + // + char *psSentence = CopeWithDumbStringData( _psSentence, sThisLanguage ); + + if ( m_bLoadingEnglish_ParseOnly ) + { + // if loading just "english", then go ahead and store it... + // + SetString( psReference, psSentence, SE_FALSE ); + } + else + { + // if loading a foreign language... + // + SE_BOOL bSentenceIsEnglish = (!stricmp(sThisLanguage,"english")) ? SE_TRUE: SE_FALSE; // see whether this is the english master or not + + // this check can be omitted, I'm just being extra careful here... + // + if ( !bSentenceIsEnglish ) + { + // basically this is just checking that an .STE file override is the same language as the .STR... + // + if (stricmp( m_strLoadingLanguage_ParseOnly.c_str(), sThisLanguage )) + { + psErrorMessage = va("Language \"%s\" found when expecting \"%s\"!\n", sThisLanguage, m_strLoadingLanguage_ParseOnly.c_str()); + } + } + + if (!psErrorMessage) + { + SetString( psReference, psSentence, bSentenceIsEnglish ); + } + } + + Z_Free( psSentence ); + } + else + { + psErrorMessage = "Error parsing file: Unexpected \"" sSE_KEYWORD_LANG "\"\n"; + } + } + else + { + psErrorMessage = va("Unknown keyword at linestart: \"%s\"\n", psLine); + } + } + + return psErrorMessage; +} + +// returns reference of string being parsed, else "" for none. +// +LPCSTR CStringEdPackage::GetCurrentReference_ParseOnly( void ) +{ + return m_strCurrentEntryRef_ParseOnly.c_str(); +} + +// add new string entry (during parse) +// +void CStringEdPackage::AddEntry( LPCSTR psLocalReference ) +{ + // the reason I don't just assign it anyway is because the optional .STE override files don't contain flags, + // and therefore would wipe out the parsed flags of the .STR file... + // +/* + mapStringEntries_t::iterator itEntry = m_StringEntries.find( va("%s_%s",m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference) ); + if (itEntry == m_StringEntries.end()) + { + SE_Entry_t SE_Entry; + m_StringEntries[ va("%s_%s", m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference) ] = SE_Entry; + } +*/ + m_strCurrentEntryRef_ParseOnly = psLocalReference; + +} + + +void CStringEdPackage::SetString( LPCSTR psLocalReference, LPCSTR psNewString, SE_BOOL bEnglishDebug ) +{ + const char *ref = va("%s_%s", m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference); + unsigned long refCrc = crc32( 0, (const Bytef *)ref, strlen(ref) ); + + if ( bEnglishDebug ) + { + // This is the leading english text of a foreign sentence pair (so it's the debug-key text): + // don't store, just make a note in-case #same shows up: + m_strCurrentEntryEnglish_ParseOnly = psNewString; + } + else if ( m_bLoadingEnglish_ParseOnly ) + { + // It's the english text, and we're loading english. Add it! + int len = strlen( psNewString ); + char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse ); + strcpy( strData, psNewString ); + + m_Strings->Insert( strData, refCrc ); + } + else + { + // It's foreign text, we're going to add it, but we need to check for #same + if (!stricmp(psNewString, sSE_EXPORT_SAME)) + { + // If it's #same, then copy the stored english version: + int len = m_strCurrentEntryEnglish_ParseOnly.length(); + char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse ); + strcpy( strData, m_strCurrentEntryEnglish_ParseOnly.c_str() ); + + m_Strings->Insert( strData, refCrc ); + } + else + { + // Explicit foreign text. Add it! + int len = strlen( psNewString ); + char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse ); + strcpy( strData, psNewString ); + + m_Strings->Insert( strData, refCrc ); + } + } +} + + + +// filename is local here, eg: "strings/german/obj.str" +// +// return is either NULL for good else error message to display... +// +static LPCSTR SE_Load_Actual( LPCSTR psFileName, SE_BOOL bSpeculativeLoad ) +{ + LPCSTR psErrorMessage = NULL; + + unsigned char *psLoadedData = SE_LoadFileData( psFileName ); + if ( psLoadedData ) + { + // now parse the data... + // + char *psParsePos = (char *) psLoadedData; + + TheStringPackage.SetupNewFileParse( psFileName ); + + char sLineBuffer[16384]; // should be enough for one line of text (some of them can be BIG though) + while ( !psErrorMessage && TheStringPackage.ReadLine((LPCSTR &) psParsePos, sLineBuffer ) ) + { + if (strlen(sLineBuffer)) + { + psErrorMessage = TheStringPackage.ParseLine( sLineBuffer ); + } + } + + SE_FreeFileDataAfterLoad( psLoadedData); + + if (!psErrorMessage && !TheStringPackage.EndMarkerFoundDuringParse()) + { + psErrorMessage = va("Truncated file, failed to find \"%s\" at file end!", sSE_KEYWORD_ENDMARKER); + } + } + else + { + if ( bSpeculativeLoad ) + { + // then it's ok to not find the file, so do nothing... + } + else + { + psErrorMessage = va("Unable to load \"%s\"!", psFileName); + } + } + + return psErrorMessage; +} + +static LPCSTR SE_GetFoundFile( string &strResult ) +{ + static char sTemp[1024/*MAX_PATH*/]; + + if (!strlen(strResult.c_str())) + return NULL; + + strncpy(sTemp,strResult.c_str(),sizeof(sTemp)-1); + sTemp[sizeof(sTemp)-1]='\0'; + + char *psSemiColon = strchr(sTemp,';'); + if ( psSemiColon) + { + *psSemiColon = '\0'; + + strResult.erase(0,(psSemiColon-sTemp)+1); + } + else + { + // no semicolon found, probably last entry? (though i think even those have them on, oh well) + // + strResult.erase(); + } + +// strlwr(sTemp); // just for consistancy and set<> -> set<> erasure checking etc + + return sTemp; +} + +//////////// API entry points from rest of game.... ////////////////////////////// + +// filename is local here, eg: "strings/german/obj.str" +// +// return is either NULL for good else error message to display... +// +LPCSTR SE_Load( LPCSTR psFileName, SE_BOOL bFailIsCritical = SE_TRUE ) +{ + //////////////////////////////////////////////////// + // + // ingame here tends to pass in names without paths, but I expect them when doing a language load, so... + // + char sTemp[1000]={0}; + if (!strchr(psFileName,'/')) + { + strcpy(sTemp,sSE_STRINGS_DIR); + strcat(sTemp,"/"); + if (se_language) + { + strcat(sTemp,se_language->string); + strcat(sTemp,"/"); + } + } + strcat(sTemp,psFileName); + COM_DefaultExtension( sTemp, sizeof(sTemp), sSE_INGAME_FILE_EXTENSION); + psFileName = &sTemp[0]; + // + //////////////////////////////////////////////////// + + + LPCSTR psErrorMessage = SE_Load_Actual( psFileName, SE_FALSE ); + + // check for any corresponding / overriding .STE files and load them afterwards... + // + if ( !psErrorMessage ) + { + char sFileName[ iSE_MAX_FILENAME_LENGTH ]; + strncpy( sFileName, psFileName, sizeof(sFileName)-1 ); + sFileName[ sizeof(sFileName)-1 ] = '\0'; + char *p = strrchr( sFileName, '.' ); + if (p && strlen(p) == strlen(sSE_EXPORT_FILE_EXTENSION)) + { + strcpy( p, sSE_EXPORT_FILE_EXTENSION ); + + psErrorMessage = SE_Load_Actual( sFileName, SE_TRUE ); + } + } + + if (psErrorMessage) + { + if (bFailIsCritical) + { + // TheStringPackage.Clear(TRUE); // Will we want to do this? Any errors that arise should be fixed immediately + Com_Error( ERR_DROP, "SE_Load(): Couldn't load \"%s\"!\n\nError: \"%s\"\n", psFileName, psErrorMessage ); + } + else + { + Com_DPrintf(S_COLOR_YELLOW "SE_Load(): Couldn't load \"%s\"!\n", psFileName ); + } + } + + return psErrorMessage; +} + + +// convenience-function for the main GetString call... +// +LPCSTR SE_GetString( LPCSTR psPackageReference, LPCSTR psStringReference) +{ + char sReference[256]; // will always be enough, I've never seen one more than about 30 chars long + + sprintf(sReference,"%s_%s", psPackageReference, psStringReference); + + return SE_GetString( Q_strupr(sReference) ); +} + + +LPCSTR SE_GetString( LPCSTR psPackageAndStringReference ) +{ + int len = strlen( psPackageAndStringReference ); + char sReference[256]; // will always be enough, I've never seen one more than about 30 chars long + assert( len < sizeof(sReference) ); + + Q_strncpyz( sReference, psPackageAndStringReference, sizeof(sReference) ); + Q_strupr( sReference ); + + unsigned long refCrc = crc32( 0, (const Bytef *)sReference, len ); + char **strData = TheStringPackage.m_Strings->Find( refCrc ); + + if( !strData ) + return ""; + else + return *strData; +} + + +void SE_NewLanguage(void) +{ + TheStringPackage.Clear( SE_TRUE ); +} + + + +// these two functions aren't needed other than to make Quake-type games happy and/or stop memory managers +// complaining about leaks if they report them before the global StringEd package object calls it's own dtor. +// +// but here they are for completeness's sake I guess... +// +void SE_Init(void) +{ + Z_PushNewDeleteTag( TAG_STRINGED ); + + TheStringPackage.Clear( SE_FALSE ); + +// se_language = Cvar_Get("se_language", "english", CVAR_ARCHIVE | CVAR_NORESTART); + extern DWORD g_dwLanguage; + switch( g_dwLanguage ) + { + case XC_LANGUAGE_FRENCH: + se_language = Cvar_Get("se_language", "french", CVAR_NORESTART); + break; + case XC_LANGUAGE_GERMAN: + se_language = Cvar_Get("se_language", "german", CVAR_NORESTART); + break; + case XC_LANGUAGE_ENGLISH: + default: + se_language = Cvar_Get("se_language", "english", CVAR_NORESTART); + break; + } + + // Rather than calling SE_LoadLanguage directly, do this. Otherwise, + // se_langauge->modified doesn't get cleared, and we parse the string files + // twice. Gah. + SE_CheckForLanguageUpdates(); + + Z_PopNewDeleteTag(); +} + +// returns error message else NULL for ok. +// +// Any errors that result from this should probably be treated as game-fatal, since an asset file is fuxored. +// +LPCSTR SE_LoadLanguage( LPCSTR psLanguage ) +{ + LPCSTR psErrorMessage = NULL; + + if (psLanguage && psLanguage[0]) + { + SE_NewLanguage(); + + string strResults; + /*int iFilesFound = */SE_BuildFileList( + #ifdef _STRINGED + va("C:\\Source\\Tools\\StringEd\\test_data\\%s",sSE_STRINGS_DIR) + #else + sSE_STRINGS_DIR + #endif + , strResults + ); + + LPCSTR p; + while ( (p=SE_GetFoundFile (strResults)) != NULL && !psErrorMessage ) + { + LPCSTR psThisLang = TheStringPackage.ExtractLanguageFromPath( p ); + + if ( !stricmp( psLanguage, psThisLang ) ) + { + psErrorMessage = SE_Load( p ); + } + } + } + else + { + assert( 0 && "SE_LoadLanguage(): Bad language name!" ); + } + + return psErrorMessage; +} + + +// called in Com_Frame, so don't take up any time! (can also be called during dedicated) +// +// instead of re-loading just the files we've already loaded I'm going to load the whole language (simpler) +// +void SE_CheckForLanguageUpdates(void) +{ + if (se_language && se_language->modified) + { + LPCSTR psErrorMessage = SE_LoadLanguage( se_language->string ); + if ( psErrorMessage ) + { + Com_Error( ERR_DROP, psErrorMessage ); + } + TheStringPackage.m_Strings->Sort(); + se_language->modified = SE_FALSE; + } +} + + +///////////////////////// eof ////////////////////////// diff --git a/codemp/qcommon/stringed_ingame.h b/codemp/qcommon/stringed_ingame.h new file mode 100644 index 0000000..05d1124 --- /dev/null +++ b/codemp/qcommon/stringed_ingame.h @@ -0,0 +1,53 @@ +// Filename:- stringed_ingame.h +// + +#ifndef STRINGED_INGAME_H +#define STRINGED_INGAME_H + + +// alter these to suit your own game... +// +#define SE_BOOL qboolean +#define SE_TRUE qtrue +#define SE_FALSE qfalse +#define iSE_MAX_FILENAME_LENGTH MAX_QPATH +#define sSE_STRINGS_DIR "strings" + +extern cvar_t *se_language; + +// some needed text-equates, do not alter these under any circumstances !!!! (unless you're me. Which you're not) +// +#define iSE_VERSION 1 +#define sSE_KEYWORD_VERSION "VERSION" +#define sSE_KEYWORD_CONFIG "CONFIG" +#define sSE_KEYWORD_FILENOTES "FILENOTES" +#define sSE_KEYWORD_REFERENCE "REFERENCE" +#define sSE_KEYWORD_NOTES "NOTES" +#define sSE_KEYWORD_LANG "LANG_" +#define sSE_KEYWORD_ENDMARKER "ENDMARKER" +#define sSE_FILE_EXTENSION ".st" // editor-only NEVER used ingame, but I wanted all extensions together +#define sSE_EXPORT_FILE_EXTENSION ".ste" +#define sSE_INGAME_FILE_EXTENSION ".str" +#define sSE_EXPORT_SAME "#same" + + + +// available API calls... +// +typedef const char *LPCSTR; + +void SE_Init ( void ); +void SE_CheckForLanguageUpdates(void); +LPCSTR SE_LoadLanguage ( LPCSTR psLanguage ); +void SE_NewLanguage ( void ); +// +// for convenience, two ways of getting at the same data... +// +LPCSTR SE_GetString ( LPCSTR psPackageReference, LPCSTR psStringReference); +LPCSTR SE_GetString ( LPCSTR psPackageAndStringReference); + + +#endif // #ifndef STRINGED_INGAME_H + +/////////////////// eof //////////////// + diff --git a/codemp/qcommon/stringed_interface.cpp b/codemp/qcommon/stringed_interface.cpp new file mode 100644 index 0000000..079ca56 --- /dev/null +++ b/codemp/qcommon/stringed_interface.cpp @@ -0,0 +1,215 @@ +// Filename:- stringed_interface.cpp +// +// This file contains functions that StringEd wants to call to do things like load/save, they can be modified +// for use ingame, but must remain functionally the same... +// +// Please try and put modifications for whichever games this is used for inside #defines, so I can copy the same file +// into each project. +// + + +////////////////////////////////////////////////// +// +// stuff common to all qcommon files... +#include "../server/server.h" +#include "../game/q_shared.h" +#include "qcommon.h" +// +////////////////////////////////////////////////// + + +#pragma warning ( disable : 4511 ) // copy constructor could not be generated +#pragma warning ( disable : 4512 ) // assignment operator could not be generated +#pragma warning ( disable : 4663 ) // C++ language change: blah blah template crap blah blah +#include "stringed_interface.h" +#include "stringed_ingame.h" + +#include +using namespace std; + +#ifdef _STRINGED +#include +#include +#include "generic.h" +#endif + + +// this just gets the binary of the file into memory, so I can parse it. Called by main SGE loader +// +// returns either char * of loaded file, else NULL for failed-to-open... +// +unsigned char *SE_LoadFileData( const char *psFileName, int *piLoadedLength /* = 0 */) +{ + unsigned char *psReturn = NULL; + if ( piLoadedLength ) + { + *piLoadedLength = 0; + } + +#ifdef _STRINGED + if (psFileName[1] == ':') + { + // full-path filename... + // + FILE *fh = fopen( psFileName, "rb" ); + if (fh) + { + long lLength = filesize(fh); + + if (lLength > 0) + { + psReturn = (unsigned char *) malloc( lLength + 1); + if (psReturn) + { + int iBytesRead = fread( psReturn, 1, lLength, fh ); + if (iBytesRead != lLength) + { + // error reading file!!!... + // + free(psReturn); + psReturn = NULL; + } + else + { + psReturn[ lLength ] = '\0'; + if ( piLoadedLength ) + { + *piLoadedLength = iBytesRead; + } + } + fclose(fh); + } + } + } + } + else +#endif + { + // local filename, so prepend the base dir etc according to game and load it however (from PAK?) + // + unsigned char *pvLoadedData; + int iLen = FS_ReadFile( psFileName, (void **)&pvLoadedData ); + + if (iLen>0) + { + psReturn = pvLoadedData; + if ( piLoadedLength ) + { + *piLoadedLength = iLen; + } + } + } + + return psReturn; +} + + +// called by main SGE code after loaded data has been parsedinto internal structures... +// +void SE_FreeFileDataAfterLoad( unsigned char *psLoadedFile ) +{ +#ifdef _STRINGED + if ( psLoadedFile ) + { + free( psLoadedFile ); + } +#else + if ( psLoadedFile ) + { + FS_FreeFile( psLoadedFile ); + } +#endif +} + + + + + +#ifndef _STRINGED +// quake-style method of doing things since their file-list code doesn't have a 'recursive' flag... +// +int giFilesFound; +static void SE_R_ListFiles( const char *psExtension, const char *psDir, string &strResults ) +{ +// Com_Printf(va("Scanning Dir: %s\n",psDir)); + + char **sysFiles, **dirFiles; + int numSysFiles, i, numdirs; + + dirFiles = FS_ListFiles( psDir, "/", &numdirs); + for (i=0;i +using namespace std; + +unsigned char * SE_LoadFileData ( const char *psFileName, int *piLoadedLength = 0); +void SE_FreeFileDataAfterLoad( unsigned char *psLoadedFile ); +int SE_BuildFileList ( const char *psStartDir, string &strResults ); + +#endif // #ifndef STRINGED_INTERFACE_H + + +////////////////// eof /////////////////// + diff --git a/codemp/qcommon/strip.cpp b/codemp/qcommon/strip.cpp new file mode 100644 index 0000000..2c6d417 --- /dev/null +++ b/codemp/qcommon/strip.cpp @@ -0,0 +1,1776 @@ +// this include must remain at the top of every CPP file +#include "../server/server.h" +#include "../game/q_shared.h" +#include "qcommon.h" + +//#ifdef _DEBUG +//#include +//#endif + +/* +#if !defined(G_LOCAL_H_INC) + #include "..\game\g_local.h" +#endif +*/ + +//#include "stdafx.h" + +#ifndef _STRIPED_ +// #include "parental.h" + #include "strip.h" +// #include "../qcommon/palette.h" +#endif + + + +#ifdef _STRIPED_ + + #include "resource.h" // main symbols + #include "SP_ConfigFile.h" + +cStringPackageED *StringPackage = NULL; + +char LanguageList[LANGUAGELIST_MAX][LANGUAGE_LENGTH]; +char FlagList[FLAGLIST_MAX][FLAG_LENGTH]; + +#else + +/*static*/ cvar_t *sp_language; +static cvar_t *sp_show_strip; + +#endif + +/* +void LogFile(char *Text, ...) +{ + char Buffer[16384]; + va_list argptr; + FILE *FH; + + va_start (argptr,Text); + vsprintf (Buffer,Text,argptr); + va_end (argptr); + + FH = fopen("c:\\striped.log", "r+"); + if (!FH) + { + FH = fopen("c:\\striped.log", "w"); + } + else + { + fseek(FH, 0, SEEK_END); + } + fprintf(FH, "%s", Buffer); + fclose(FH); +} +*/ + +enum +{ + TK_INVALID = -1, + TK_VERSION = 0, + TK_ID, + TK_REFERENCE, + TK_DESCRIPTION, + TK_COUNT, + TK_FLAGS, + TK_SP_FLAG1, + TK_SP_FLAG2, + TK_SP_FLAG3, + TK_SP_FLAG4, + TK_SP_FLAG5, + TK_SP_FLAG6, + TK_SP_FLAG7, + TK_SP_FLAG8, + TK_SP_FLAG9, + TK_SP_FLAG_ORIGINAL, + TK_LEFT_BRACE, + TK_RIGHT_BRACE, + TK_INDEX, + TK_NOTES, + TK_CONFIG, + TK_TEXT_LANGUAGE1, + TK_TEXT_LANGUAGE2, + TK_TEXT_LANGUAGE3, + TK_TEXT_LANGUAGE4, + TK_TEXT_LANGUAGE5, + TK_TEXT_LANGUAGE6, + TK_TEXT_LANGUAGE7, + TK_TEXT_LANGUAGE8, + TK_TEXT_LANGUAGE9, + TK_TEXT_LANGUAGE10, + TK_END +}; + + +char *Tokens[TK_END] = +{ + "VERSION", + "ID", + "REFERENCE", + "DESCRIPTION", + "COUNT", + "FLAGS", + "SP_FLAG1", + "SP_FLAG2", + "SP_FLAG3", + "SP_FLAG4", + "SP_FLAG5", + "SP_FLAG6", + "SP_FLAG7", + "SP_FLAG8", + "SP_FLAG9", + "SP_FLAG_ORIGINAL", + "{", + "}", + "INDEX", + "NOTES", + "CONFIG", + "TEXT_LANGUAGE1", + "TEXT_LANGUAGE2", + "TEXT_LANGUAGE3", + "TEXT_LANGUAGE4", + "TEXT_LANGUAGE5", + "TEXT_LANGUAGE6", + "TEXT_LANGUAGE7", + "TEXT_LANGUAGE8", + "TEXT_LANGUAGE9", + "TEXT_LANGUAGE10" +}; + + +sFlagPair FlagPairs[] = +{ + { TK_SP_FLAG1, SP_FLAG1 }, + { TK_SP_FLAG2, SP_FLAG2 }, + { TK_SP_FLAG3, SP_FLAG3 }, + { TK_SP_FLAG4, SP_FLAG4 }, + { TK_SP_FLAG5, SP_FLAG5 }, + { TK_SP_FLAG6, SP_FLAG6 }, + { TK_SP_FLAG7, SP_FLAG7 }, + { TK_SP_FLAG8, SP_FLAG8 }, + { TK_SP_FLAG9, SP_FLAG9 }, + { TK_SP_FLAG_ORIGINAL, SP_FLAG_ORIGINAL }, + { TK_INVALID, 0 } +}; + +sFlagPair LanguagePairs[] = +{ + { TK_TEXT_LANGUAGE1, SP_LANGUAGE_ENGLISH }, + { TK_TEXT_LANGUAGE2, SP_LANGUAGE_FRENCH }, + { TK_TEXT_LANGUAGE3, SP_LANGUAGE_GERMAN }, + { TK_TEXT_LANGUAGE4, SP_LANGUAGE_BRITISH }, + { TK_TEXT_LANGUAGE5, SP_LANGUAGE_KOREAN }, + { TK_TEXT_LANGUAGE6, SP_LANGUAGE_TAIWANESE }, + { TK_TEXT_LANGUAGE7, SP_LANGUAGE_ITALIAN }, + { TK_TEXT_LANGUAGE8, SP_LANGUAGE_SPANISH }, + { TK_TEXT_LANGUAGE9, SP_LANGUAGE_JAPANESE }, + { TK_TEXT_LANGUAGE10, SP_LANGUAGE_10}, + { TK_INVALID, 0 } +}; + +/************************************************************************************************ + * FindToken + * + * inputs: + * token string + * flag indicating if token string is partial or not + * + * return: + * token enum + * + ************************************************************************************************/ +int FindToken(char *token, bool whole) +{ + int token_value; + int i; + + for(token_value = 0; token_value != TK_END; token_value++) + { + if (whole) + { + if (Q_stricmp(token, Tokens[token_value]) == 0) + { + return token_value; + } + } + else + { + if (Q_stricmpn(token, Tokens[token_value], strlen(Tokens[token_value])) == 0) + { + i = strlen(Tokens[token_value]); + while(token[i] == ' ') + { + i++; + } + memmove(token, &token[i], strlen(token)-i+1); + + return token_value; + } + } + } + + return TK_INVALID; +} + +/************************************************************************************************ + * ReadData + * + * inputs: + * + * return: + * + ************************************************************************************************/ +bool ReadData(char *&Data, int &Size, char *Result, int Result_Size) +{ + char *pos; + + Result[0] = 0; + + if (Size <= 0) + { + return false; + } + pos = Result; + do + { + *pos = *Data; + pos++; + Data++; + Size--; + Result_Size--; + } while(Size > 0 && Result_Size > 0 && *(Data-1) != '\n'); + + *pos = 0; + + return true; +} + +/************************************************************************************************ + * GetLine + * + * inputs: + * + * return: + * + ************************************************************************************************/ +void GetLine(char *&Data, int &Size, int &token, char *&data) +{ + static char save_data[8192]; + char temp_data[8192]; + char *test_token, *pos; + + save_data[0] = 0; + token = TK_INVALID; + data = save_data; + + if (!ReadData(Data, Size, temp_data, sizeof(temp_data))) + { + return; + } + +// strcpy(temp_data, " DATA \"test of the data\ntest test\ndfa dfd"); +// strcpy(temp_data, " DATA"); + + pos = temp_data; + while((*pos) && strchr(" \n\r", *pos)) + { // remove white space + pos++; + } + test_token = pos; + + while((*pos) && !strchr(" \n\r", *pos)) + { // scan until end of white space + pos++; + } + + if ((*pos)) + { + *pos = 0; + pos++; + } + token = FindToken(test_token, true); + + while((*pos) && strchr(" \n\r", *pos)) + { // remove white space + pos++; + } + + if ((*pos) == '\"') + { + pos++; + test_token = save_data; + memset(save_data, 0, sizeof(save_data)); + + while(((*pos) != '\"' || !strchr("\n\r", (*(pos+1)))) && (*pos)) + { + if ((*pos) == '\\' && (*(pos+1)) == 'n') + { +#ifdef _STRIPED_ + *test_token = '\r'; + test_token++; +#endif + *test_token = '\n'; + test_token++; + pos+=2; + continue; + } + + *test_token = *pos; + test_token++; + pos++; + } + + if ((*pos) == '\"') + { + *pos = 0; + } + } + else + { + test_token = pos; + while((*pos) && !strchr("\n\r", *pos)) + { // scan until end of white space + pos++; + } + *pos = 0; + + strcpy(save_data, test_token); + } +} + + +#ifdef _STRIPED_ + +/************************************************************************************************ + * SaveString + * + * inputs: + * + * return: + * + ************************************************************************************************/ +void SaveString(FILE *FH, char *data) +{ + fputc('\"', FH); + + if (data) + { + for(;*data;data++) + { + if ((*data) == '\r') + { + } + else if ((*data) == '\n') + { + fputc('\\', FH); + fputc('n', FH); + } + else + { + fputc(*data, FH); + } + } + } + + fputc('\"', FH); +} + +#endif + + +//====================================================================== + + +/************************************************************************************************ + * cCriteria + * + * inputs: + * + * return: + * + ************************************************************************************************/ +cCriteria::cCriteria(int initWhichLanguage) +{ + WhichLanguage = initWhichLanguage; +} + + +#ifdef _STRIPED_ + +/************************************************************************************************ + * cCriteriaED + * + * inputs: + * + * return: + * + ************************************************************************************************/ +cCriteriaED::cCriteriaED(int initWhichLanguage, cStringPackageED *initMerge) +:cCriteria(initWhichLanguage) +{ + Merge = initMerge; +} + +#endif + + +//====================================================================== + +/************************************************************************************************ + * cStrings + * + * inputs: + * + * return: + * + ************************************************************************************************/ +cStrings::cStrings(unsigned int initFlags, char *initReference) +{ + Flags = initFlags; + Reference = NULL; + + SetReference(initReference); +} + +/************************************************************************************************ + * ~cStrings + * + * inputs: + * + * return: + * + ************************************************************************************************/ +cStrings::~cStrings(void) +{ + Clear(); +} + +/************************************************************************************************ + * Clear + * + * inputs: + * + * return: + * + ************************************************************************************************/ +void cStrings::Clear(void) +{ + Flags = 0; + + if (Reference) + { + delete Reference; + Reference = NULL; + } +} + +/************************************************************************************************ + * SetFlags + * + * inputs: + * + * return: + * + ************************************************************************************************/ +void cStrings::SetFlags(unsigned int newFlags) +{ + Flags = newFlags; +} + + +void cStrings::SetReference(char *newReference) +{ + if (Reference) + { + delete Reference; + Reference = NULL; + } + + if (!newReference || !newReference[0]) + { + return; + } + + Reference = new char[strlen(newReference)+1]; + strcpy(Reference, newReference); +} + +bool cStrings::UnderstandToken(int token, char *data) +{ + sFlagPair *FlagPair; + + switch(token) + { + case TK_FLAGS: + while(token != TK_INVALID) + { + token = FindToken(data, false); + for(FlagPair = FlagPairs; FlagPair->Name != TK_INVALID; FlagPair++) + { + if (FlagPair->Name == token) + { + Flags |= FlagPair->Value; + break; + } + } + } + return true; + + case TK_REFERENCE: + SetReference(data); + return true; + + case TK_RIGHT_BRACE: + return false; + } + + if (token == TK_INVALID) + { + return false; + } + + return true; +} + +bool cStrings::SubSave(FILE *FH) +{ + sFlagPair *FlagPair; + + if (Flags) + { + fprintf(FH, " %s", Tokens[TK_FLAGS]); + for(FlagPair = FlagPairs; FlagPair->Name != TK_INVALID; FlagPair++) + { + if (Flags & FlagPair->Value) + { + fprintf(FH, " %s", Tokens[FlagPair->Name]); + } + } + fprintf(FH,"\n"); + } + + if (Reference) + { + fprintf(FH, " %s %s\n", Tokens[TK_REFERENCE], Reference); + } + + return true; +} + +bool cStrings::Save(FILE *FH) +{ + fprintf(FH,"%s\n", Tokens[TK_LEFT_BRACE]); + + SubSave(FH); + + fprintf(FH,"%s\n", Tokens[TK_RIGHT_BRACE]); + + return true; +} + +bool cStrings::Load(char *&Data, int &Size) +{ + int token; + char *data; + + Clear(); + + GetLine(Data, Size, token, data); + if (token != TK_LEFT_BRACE) + { + return false; + } + + GetLine(Data, Size, token, data); + while (UnderstandToken(token, data)) + { + GetLine(Data, Size, token, data); + } + + if (token != TK_RIGHT_BRACE) + { + return false; + } + + return true; +} + + + + + + + +#ifdef _STRIPED_ + +cStringsED::cStringsED(unsigned int initFlags, char *initReference, char *initNotes) +:cStrings(initFlags, initReference) +{ + int i; + + Used = false; + Notes = NULL; + + for(i=0;iName != TK_INVALID; LanguagePair++) + { + if (LanguagePair->Name == token) + { + SetText(LanguagePair->Value, data); + return true; + } + } + + return cStrings::UnderstandToken(token, data, Criteria); + } +} + +bool cStringsED::SubSave(FILE *FH, cCriteria &Criteria) +{ + int i; + sFlagPair *LanguagePair; + + cStrings::SubSave(FH, Criteria); + + if (Notes) + { + fprintf(FH, " %s ", Tokens[TK_NOTES]); + SaveString(FH, Notes); + fprintf(FH, "\n"); + } + + for(i=0;iName != TK_INVALID; LanguagePair++) + { + if (i == (int)LanguagePair->Value) + { + fprintf(FH, " %s ",Tokens[LanguagePair->Name]); + SaveString(FH, Text[i]); + fprintf(FH, "\n"); + } + } + } + } + + return true; +} + +bool cStringsED::Load(char *&Data, int &Size, cCriteria &Criteria) +{ + if (cStrings::Load(Data, Size, Criteria)) + { + Used = true; + return true; + } + + return false; +} + +#endif + + + +#ifndef _STRIPED_ + +cStringsSingle::cStringsSingle(unsigned int initFlags, char *initReference) +:cStrings(initFlags, initReference) +{ + Text = NULL; +} + +cStringsSingle::~cStringsSingle() +{ + Clear(); +} + +void cStringsSingle::Clear(void) +{ + cStrings::Clear(); + + if (Text) + { + delete Text; + Text = NULL; + } +} + +void cStringsSingle::SetText(const char *newText) +{ + int length; + char *Dest; + + if (Text) + { + delete Text; + Text = NULL; + } + + if (!newText || !newText[0]) + { + return; + } + + length = strlen(newText)+1; + +#ifndef _STRIPED_ + // Following is for TESTING for SOF. + if(sp_show_strip->value) + { + Dest = Text = new char[length + 6]; + strcpy(Dest,"SP:"); + Dest += strlen(Dest); + } + else +#endif + { + Dest = Text = new char[length]; + } + strcpy(Dest, newText); +} + +// fix problems caused by fucking morons entering clever "rich" chars in to new text files *after* the auto-stripper +// removed them all in the first place... +// +// ONLY DO THIS FOR EUROPEAN LANGUAGES, OR IT BREAKS ASIAN STRINGS!!!!!!!!!!!!!!!!!!!!! +// +static void FixIllegalChars(char *psText) +{ + char *p; + +// strXLS_Speech.Replace(va("%c",0x92),va("%c",0x27)); // "'" + while ((p=strchr(psText,0x92))!=NULL) // "rich" (and illegal) apostrophe + { + *p = 0x27; + } + +// strXLS_Speech.Replace(va("%c",0x93),"\""); // smart quotes -> '"' + while ((p=strchr(psText,0x93))!=NULL) // "rich" (and illegal) apostrophe + { + *p = '"'; + } + +// strXLS_Speech.Replace(va("%c",0x94),"\""); // smart quotes -> '"' + while ((p=strchr(psText,0x94))!=NULL) // "rich" (and illegal) apostrophe + { + *p = '"'; + } + +// strXLS_Speech.Replace(va("%c",0x0B),"."); // full stop + while ((p=strchr(psText,0x0B))!=NULL) // "rich" (and illegal) apostrophe + { + *p = '.'; + } + +// strXLS_Speech.Replace(va("%c",0x85),"..."); // "..."-char -> 3-char "..." + while ((p=strchr(psText,0x85))!=NULL) // "rich" (and illegal) apostrophe + { + *p = '.'; // can't do in-string replace of "." with "...", so just forget it + } + +// strXLS_Speech.Replace(va("%c",0x91),va("%c",0x27)); // "'" + while ((p=strchr(psText,0x91))!=NULL) // "rich" (and illegal) apostrophe + { + *p = 0x27; + } + +// strXLS_Speech.Replace(va("%c",0x96),va("%c",0x2D)); // "-" + while ((p=strchr(psText,0x96))!=NULL) + { + *p = 0x2D; + } + +// strXLS_Speech.Replace(va("%c",0x97),va("%c",0x2D)); // "-" + while ((p=strchr(psText,0x97))!=NULL) + { + *p = 0x2D; + } + + // StripEd and our print code don't support tabs... + // + while ((p=strchr(psText,0x09))!=NULL) + { + *p = ' '; + } +} + +bool cStringsSingle::UnderstandToken(int token, char *data) +{ + sFlagPair *LanguagePair; + +// switch(token) +// { +// default: + + for(LanguagePair = LanguagePairs; LanguagePair->Name != TK_INVALID; LanguagePair++) + { + if (LanguagePair->Name == TK_TEXT_LANGUAGE1 && token == TK_TEXT_LANGUAGE1 && !Text) + { // default to english in case there is no foreign + if (LanguagePair->Name == TK_TEXT_LANGUAGE1 || + LanguagePair->Name == TK_TEXT_LANGUAGE2 || + LanguagePair->Name == TK_TEXT_LANGUAGE3 || + LanguagePair->Name == TK_TEXT_LANGUAGE8 + ) + { + FixIllegalChars(data); + } + SetText(data); + return true; + } + else if (LanguagePair->Name == token && LanguagePair->Value == (int)sp_language->value) + { + if (LanguagePair->Name == TK_TEXT_LANGUAGE1 || + LanguagePair->Name == TK_TEXT_LANGUAGE2 || + LanguagePair->Name == TK_TEXT_LANGUAGE3 || + LanguagePair->Name == TK_TEXT_LANGUAGE8 + ) + { + FixIllegalChars(data); + } + SetText(data); + return true; + } + } + + return cStrings::UnderstandToken(token, data); +// } +} + +#endif + + + + + + + + +cStringPackage::cStringPackage(const char *in, unsigned char initID, char *initDescription, char *initReference) +{ + ID = initID; + Registration = 0; + name = in; + Reference = NULL; + + SetReference(initReference); +} + +cStringPackage::~cStringPackage(void) +{ + if (Reference) + { + delete Reference; + Reference = NULL; + } +} + +void cStringPackage::SetReference(char *newReference) +{ + if (Reference) + { + delete Reference; + Reference = NULL; + } + + if (!newReference || !newReference[0]) + { + return; + } + + Reference = new char[strlen(newReference)+1]; + strcpy(Reference, newReference); +} + +#ifndef _STRIPED_ + +bool cStringPackage::UnderstandToken(char *&Data, int &Size, int token, char *data ) +{ + cCriteria FullCriteria; + + switch(token) + { + case TK_ID: + ID = (unsigned char)atol(data); + return true; + + case TK_CONFIG: +#ifdef _STRIPED_ + ConfigFile.Load(data,FullCriteria); +#endif + return true; + + case TK_REFERENCE: +#ifdef _STRIPED_ + if (Reference) + { + char temp[1024]; + + sprintf(temp, "Please get RJ about this error! Do NOT save or do anything! OldRefeence: %s, New: %s", Reference, data); + MessageBox(NULL, temp, "RJ Error!", MB_OK); + } +#endif + SetReference(data); + return true; + } + + if (token == TK_INVALID) + { + return false; + } + + return true; +} + +#endif + +bool cStringPackage::SubSave(FILE *FH ) +{ + fprintf(FH,"%s %d\n",Tokens[TK_ID], ID); + + if (Reference) + { + fprintf(FH,"%s %s\n", Tokens[TK_REFERENCE], Reference); + } + + return true; +} + +#ifdef _STRIPED_ + +bool cStringPackage::Save(char *FileName) +{ + FILE *FH; + + FH = fopen(FileName,"w"); + if (!FH) + { + return false; + } + + fprintf(FH,"%s %d\n",Tokens[TK_VERSION], STRIP_VERSION); + fprintf(FH,"%s %s\n",Tokens[TK_CONFIG], ConfigFile.fileName); + + SubSave(FH); + + fclose(FH); + + return true; +} +#endif + +bool cStringPackage::Load(char *FileName) +{ + FILE *FH; + int Size; + char *buffer; + + FH = fopen(FileName,"rb"); + if (!FH) + { + return false; + } + + fseek(FH, 0, SEEK_END); + Size = ftell(FH); + fseek(FH, 0, SEEK_SET); + + buffer = new char[Size]; + fread(buffer, 1, Size, FH); + fclose(FH); + + Load(buffer, Size); + + delete buffer; + + return true; +} + +bool cStringPackage::Load(char *Data, int &Size) +{ + char *token_data; + int token; + + GetLine(Data, Size, token, token_data); + if (token != TK_VERSION || atol(token_data) != STRIP_VERSION) + { + return false; + } + + GetLine(Data, Size, token, token_data); + while (UnderstandToken(Data, Size, token, token_data )) + { + GetLine(Data, Size, token, token_data); + } + + return true; +} + + + +#ifdef _STRIPED_ + + +cStringPackageED::cStringPackageED(unsigned char initID, char *initDescription, char *initReference) +:cStringPackage("", initID, initReference) +{ + Description = NULL; + + SetDescription(initDescription); +} + +cStringPackageED::~cStringPackageED(void) +{ + if (Description) + { + delete Description; + Description = NULL; + } +} + +void cStringPackageED::SetDescription(char *newDescription) +{ + if (Description) + { + delete Description; + Description = NULL; + } + + if (!newDescription || !newDescription[0]) + { + return; + } + + Description = new char[strlen(newDescription)+1]; + strcpy(Description, newDescription); +} + +cStringsED *cStringPackageED::FindString(int &index) +{ + if (index == -1) + { + for(index=0;index::iterator i; + int size; + + if (!Reference) + { + return -1; + } + + size = strlen(Reference); + if (strlen(ReferenceLookup) < size+2) + { + return -1; + } + + if (strncmp(ReferenceLookup, Reference, size)) + { + return -1; + } + + i = ReferenceTable.find(string(ReferenceLookup + size + 1)); + if (i != ReferenceTable.end()) + { + return (*i).second; + } + + +//#ifdef _DEBUG +// // findmeste +// for (map::iterator it = ReferenceTable.begin(); it != ReferenceTable.end(); ++it) +// { +// OutputDebugString(va("%s\n",(*it).first.c_str())); +// } +//#endif + + return -1; +} + +bool cStringPackageSingle::UnderstandToken(char *&Data, int &Size, int token, char *data ) +{ + int count, i, pos; + char *ReferenceLookup; + + switch(token) + { + case TK_COUNT: + count = atol(data); + + for(i=0;i SP_ListByName; +map SP_ListByID; + + +// Registration +cStringPackageSingle *SP_Register(const char *inPackage, unsigned char Registration) +{ + char *buffer; + char Package[MAX_QPATH]; + int size; + cStringPackageSingle *new_sp; + map::iterator i; + + + assert(SP_ListByName.size() == SP_ListByID.size()); + + Q_strncpyz(Package, inPackage, MAX_QPATH); + Q_strupr(Package); + + i = SP_ListByName.find(Package); + if (i != SP_ListByName.end()) + { + new_sp = (*i).second; + } + else + { + size = FS_ReadFile(va("strip/%s.sp", Package), (void **)&buffer); + if (size == -1) + { + if ( Registration & SP_REGISTER_REQUIRED ) + { + Com_Error(ERR_FATAL, "Could not open string package '%s'", Package); + } + return NULL; + } + + // Create the new string package + new_sp = new cStringPackageSingle(Package); + new_sp->Load(buffer, size ); + FS_FreeFile(buffer); + + if (Registration & SP_REGISTER_CLIENT) + { + Com_DPrintf(S_COLOR_YELLOW "SP_Register: Registered client string package '%s' with ID %02x\n", Package, (int)new_sp->GetID()); + } + else + { + Com_DPrintf(S_COLOR_YELLOW "SP_Register: Registered string package '%s' with ID %02x\n", Package, (int)new_sp->GetID()); + } + + // Insert into the name vs package map + SP_ListByName[Package] = new_sp; + // Insert into the id vs package map + SP_ListByID[new_sp->GetID()] = new_sp; + } + // Or in the new registration data + new_sp->Register(Registration); + + return new_sp; +} + +// Update configstrings array on clients and server +qboolean SP_RegisterServer(const char *Package) +{ + cStringPackageSingle *sp; + + sp = SP_Register(Package, SP_REGISTER_SERVER); + if (sp) + { + SV_AddConfigstring(Package,CS_STRING_PACKAGES,MAX_STRING_PACKAGES); + return qtrue; + } + + return qfalse; +} + +// Unload all packages with the relevant registration bits +void SP_Unload(unsigned char Registration) +{ + map::iterator i, next; + map::iterator id; + + assert(SP_ListByName.size() == SP_ListByID.size()); + + for(i = SP_ListByName.begin(); i != SP_ListByName.end(); i = next) + { + next = i; + next++; + + if ((*i).second->UnRegister(Registration)) + { + Com_DPrintf(S_COLOR_YELLOW "SP_UnRegister: Package '%s' with ID %02x\n", (*i).first.c_str(), (int)(*i).second->GetID()); + + id = SP_ListByID.find((*i).second->GetID()); + SP_ListByID.erase(id); + delete (*i).second; + SP_ListByName.erase(i); + } + } + +} + +// Direct string functions + +int SP_GetStringID(const char *inReference) +{ + map::iterator i; + int ID; + char Reference[MAX_QPATH]; + Q_strncpyz(Reference, inReference, MAX_QPATH); + Q_strupr(Reference); + + for(i = SP_ListByID.begin(); i != SP_ListByID.end(); i++) + { + ID = (*i).second->FindStringID(Reference); + if (ID >= 0) + { + ID |= ((int)(*i).first) << 8; + return ID; + } + } + return -1; +} + +/************************************************************************************************ + * SP_GetString + * + * inputs: + * ID of the string package + * + * return: + * pointer to desired String Package + * + ************************************************************************************************/ +cStringsSingle *SP_GetString(unsigned short ID) +{ + cStringPackageSingle *sp; + cStringsSingle *string; + map::iterator i; + + i = SP_ListByID.find(SP_GET_PACKAGE(ID)); + if (i == SP_ListByID.end()) + { + Com_Error(ERR_DROP, "String package not registered for ID %04x", ID); + + return NULL; + } + + sp = (*i).second; + string = sp->FindString(ID & SP_STRING); + + if (!string) + { + Com_Error(ERR_DROP, "String ID %04x not defined\n", ID); + } + + return string; +} + +cStringsSingle *SP_GetString(const char *Reference) +{ + int index; + + index = SP_GetStringID(Reference); + if (index == -1) + { + return NULL; + } + + return SP_GetString(index); +} + + +const char *SP_GetStringText(unsigned short ID) +{ + cStringsSingle *string; + char *value; + + string = SP_GetString(ID); + + value = string->GetText(); + if (!value) + { + value = ""; + } + + return value; +} + +const char *SP_GetStringTextString(const char *Reference) +{ + int index; + + index = SP_GetStringID(Reference); + if (index == -1) + { + return ""; + } + + return SP_GetStringText(index); +} + + +static void SP_UpdateLanguage(void) +{ + map::iterator it; + list sps; + list::iterator spit; + + // Grab all SP ids + for(it = SP_ListByID.begin(); it != SP_ListByID.end(); it++) + { + sps.push_back(cStringPackageID((*it).second->GetName(), (*it).second->GetRegistration())); + } + // Clear out all pointers + SP_Unload(SP_REGISTER_CLIENT | SP_REGISTER_SERVER | SP_REGISTER_MENU | SP_REGISTER_REQUIRED); + + // Reinitialise with new language + for(spit = sps.begin(); spit != sps.end(); spit++) + { + SP_Register((*spit).GetName(), (*spit).GetReg()); + } + sps.clear(); +} + +void SP_Init(void) +{ + sp_language = Cvar_Get("sp_language", va("%d", SP_LANGUAGE_ENGLISH), CVAR_ARCHIVE | CVAR_NORESTART); + sp_show_strip = Cvar_Get ("sp_show_strip", "0", 0); + + SP_UpdateLanguage(); + sp_language->modified = qfalse; + + // Register_StringPackets... + // + SP_Register("con_text", SP_REGISTER_REQUIRED); //reference is CON_TEXT + SP_Register("mp_ingame",SP_REGISTER_REQUIRED); //reference is INGAMETEXT + SP_Register("mp_svgame",SP_REGISTER_REQUIRED); //reference is SVINGAME + SP_Register("sp_ingame",SP_REGISTER_REQUIRED); //reference is INGAME , needed for item pickups + SP_Register("keynames", 0/*SP_REGISTER_REQUIRED*/); //reference is KEYNAMES +} + +// called in Com_Frame, so don't take up any time! (can also be called during dedicated) +// +void SP_CheckForLanguageUpdates(void) +{ + if (sp_language && sp_language->modified) + { + SP_Init(); // force language package to reload + sp_language->modified = qfalse; + } +} + + +int Language_GetIntegerValue(void) +{ + if (sp_language) + { + return sp_language->integer; + } + + return 0; +} + + +// query function from font code +// +qboolean Language_IsKorean(void) +{ + return (sp_language && sp_language->integer == SP_LANGUAGE_KOREAN) ? qtrue : qfalse; +} + +qboolean Language_IsTaiwanese(void) +{ + return (sp_language && sp_language->integer == SP_LANGUAGE_TAIWANESE) ? qtrue : qfalse; +} + +qboolean Language_IsJapanese(void) +{ + return (sp_language && sp_language->integer == SP_LANGUAGE_JAPANESE) ? qtrue : qfalse; +} + + +#endif + diff --git a/codemp/qcommon/strip.h b/codemp/qcommon/strip.h new file mode 100644 index 0000000..ff1bd0c --- /dev/null +++ b/codemp/qcommon/strip.h @@ -0,0 +1,312 @@ +#ifndef __STRIP_H +#define __STRIP_H +/* +#ifndef _SOF_ + +#pragma warning(disable:4786) // Or STL will generate ugly warnings. + +#include + +using namespace std; +#endif +*/ + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include +#include +#include +#pragma warning (pop) + +using namespace std; + + +#define STRIP_VERSION 1 + +#define MAX_LANGUAGES 10 +#define MAX_STRINGS 256 +#define MAX_ID 255 + +enum +{ + SP_LANGUAGE_ENGLISH = 0, + SP_LANGUAGE_FRENCH, + SP_LANGUAGE_GERMAN, + SP_LANGUAGE_BRITISH, + SP_LANGUAGE_KOREAN, + SP_LANGUAGE_TAIWANESE, + SP_LANGUAGE_ITALIAN, + SP_LANGUAGE_SPANISH, + SP_LANGUAGE_JAPANESE, + SP_LANGUAGE_10, + SP_LANGUGAGE_MAX, + SP_LANGUAGE_ALL = 255 +}; + + +#define SP_PACKAGE 0xff00 +#define SP_STRING 0x00ff + +#define SP_GET_PACKAGE(x) ( (x & SP_PACKAGE) >> 8 ) + +// Flags +#define SP_FLAG1 0x00000001 // CENTERED +#define SP_FLAG2 0x00000002 +#define SP_FLAG3 0x00000004 +#define SP_FLAG4 0x00000008 +#define SP_FLAG5 0x00000010 +#define SP_FLAG6 0x00000020 +#define SP_FLAG7 0x00000040 +#define SP_FLAG8 0x00000080 +#define SP_FLAG9 0x00000100 +#define SP_FLAG_ORIGINAL 0x00000200 + +// Registration +#define SP_REGISTER_CLIENT (0x01) +#define SP_REGISTER_SERVER (0x02) +#define SP_REGISTER_MENU (0x04) +#define SP_REGISTER_REQUIRED (0x08) + + +class cCriteria +{ +public: + int WhichLanguage; + + cCriteria(int initWhichLanguage = SP_LANGUAGE_ALL); +}; + +#ifdef _STRIPED_ + +class cStringPackageED; + +class cCriteriaED : public cCriteria +{ +public: + cStringPackageED *Merge; + + cCriteriaED(int initWhichLanguage = SP_LANGUAGE_ALL, cStringPackageED *initMerge = NULL); +}; + + +#endif + + + +class cStrings +{ +private: + unsigned int Flags; + char *Reference; + +public: + cStrings(unsigned int initFlags = 0, char *initReference = NULL); + virtual ~cStrings(void); + + virtual void Clear(void); + + void SetFlags(unsigned int newFlags); + void SetReference(char *newReference); + + unsigned int GetFlags(void) { return Flags; } + char *GetReference(void) { return Reference; } + + virtual bool UnderstandToken(int token, char *data ); + virtual bool Load(char *&Data, int &Size); + + virtual bool SubSave(FILE *FH); + bool Save(FILE *FH ); +}; + +#ifdef _STRIPED_ + +class cStringsED : public cStrings +{ +private: + char *Reference; + char *Text[MAX_LANGUAGES]; + char *Notes; + bool Used; + +public: + cStringsED(unsigned int initFlags = 0, char *initReference = NULL, char *initNotes = NULL); + virtual ~cStringsED(); + + virtual void Clear(void); + + void SetUsed(bool newUsed = true) { Used = newUsed; } + void SetText(int index, char *newText); + void SetNotes(char *newNotes); + + bool GetUsed(void) { return Used; } + char *GetText(int index) { return Text[index]; } + char *GetNotes(void) { return Notes; } + + virtual bool UnderstandToken(int token, char *data, cCriteria &Criteria); + virtual bool SubSave(FILE *FH, cCriteria &Criteria); + virtual bool Load(char *&Data, int &Size, cCriteria &Criteria); +}; + +#endif + +#ifndef _STRIPED_ + +class cStringsSingle : public cStrings +{ +private: + char *Text; + + virtual void Clear(void); + void SetText(const char *newText); + +public: + cStringsSingle(unsigned int initFlags = 0, char *initReference = NULL); + virtual ~cStringsSingle(); + + char *GetText(void) { return Text; } + virtual bool UnderstandToken(int token, char *data); +}; + + + + + +//====================================================================== + +class cStringPackageID +{ +private: + string name; + byte reg; +public: + cStringPackageID(const char *in_name, byte in_reg) { name = in_name; reg = in_reg; } + const char *GetName(void) const { return(name.c_str()); } + byte GetReg(void) const { return(reg); } +}; +#endif + +class cStringPackage +{ +protected: + unsigned char ID; + unsigned char Registration; + string name; + char *Reference; + +public: + cStringPackage(const char *in, unsigned char initID = 0, char *initDescription = NULL, char *initReference = NULL); + ~cStringPackage(void); + + void Register(unsigned char newRegistration) { Registration |= newRegistration; } + bool UnRegister(unsigned char oldRegistration) { Registration &= ~oldRegistration; return (Registration == 0); } + bool RegisteredOnServer(void) const { return(!!(Registration & SP_REGISTER_SERVER)); } + byte GetRegistration(void) const { return(Registration); } + + void SetID(unsigned char newID) { ID = newID; } + void SetReference(char *newReference); + + unsigned char GetID(void) { return ID; } + char *GetReference(void) { return Reference; } + const char *GetName(void) const { return(name.c_str()); } + + virtual bool UnderstandToken(char *&Data, int &Size, int token, char *data); + virtual bool SubSave(FILE *FH); + bool Save(char *FileName); + virtual bool Load(char *FileName); + virtual bool Load(char *Data, int &Size); +}; + +#ifdef _STRIPED_ + +class cStringPackageED : public cStringPackage +{ +private: + cStringsED Strings[MAX_STRINGS]; + char *Description; + +public: + cStringPackageED(unsigned char initID = 0, char *initDescription = NULL, char *initReference = NULL); + ~cStringPackageED(void); + + void SetDescription(char *newDescription); + + char *GetDescription(void) { return Description; } + + cStringsED *FindString(int &index); + void ClearString(int index); + + virtual bool UnderstandToken(char *&Data, int &Size, int token, char *data, cCriteria &Criteria); + virtual bool SubSave(FILE *FH, cCriteria &Criteria); + bool GenerateCHeader(char *FileName); + bool GenerateDSHeader(char *FileName); +}; + +#endif + +#ifndef _STRIPED_ + +class cStringPackageSingle : public cStringPackage +{ +private: + cStringsSingle Strings[MAX_STRINGS]; + map ReferenceTable; + +public: + cStringPackageSingle(const char *in, unsigned char initID = 0, char *initReference = NULL); + ~cStringPackageSingle(void); + + cStringsSingle *FindString(int index) { return &Strings[index]; } + cStringsSingle *FindString(char *ReferenceLookup); + int FindStringID(const char *ReferenceLookup); + + virtual bool UnderstandToken(char *&Data, int &Size, int token, char *data ); +}; + +#endif + + + +typedef struct sFlagPair +{ + int Name; + unsigned long Value; +} tFlagPair; + +extern sFlagPair FlagPairs[]; +extern sFlagPair LanguagePairs[]; + +#ifdef _STRIPED_ + +#define LANGUAGELIST_MAX 16 +#define LANGUAGE_LENGTH 64 + +#define FLAGLIST_MAX 16 +#define FLAG_LENGTH 32 + +extern cStringPackageED *StringPackage; +extern char LanguageList[LANGUAGELIST_MAX][LANGUAGE_LENGTH]; +extern char FlagList[FLAGLIST_MAX][FLAG_LENGTH]; + +#endif + +#ifndef _STRIPED_ + +// Registration +cStringPackageSingle *SP_Register(const char *Package, unsigned char Registration); +qboolean SP_RegisterServer(const char *Package); +void SP_Unload(unsigned char Registration); + +// Direct string functions +int SP_GetStringID(const char *Reference); +cStringsSingle *SP_GetString(unsigned short ID); +cStringsSingle *SP_GetString(const char *Reference); +const char *SP_GetStringText(unsigned short ID); +const char *SP_GetStringTextString(const char *Reference); + +// Initialization +void SP_Init(void); + +#endif + +extern int Language_GetIntegerValue(void); + +#endif // __STRIP_H diff --git a/codemp/qcommon/tags.h b/codemp/qcommon/tags.h new file mode 100644 index 0000000..30cee4b --- /dev/null +++ b/codemp/qcommon/tags.h @@ -0,0 +1,57 @@ +// Filename:- tags.h + +// do NOT include-protect this file, or add any fields or labels, because it's included within enums and tables +// +// these macro args get "TAG_" prepended on them for enum purposes, and appear as literal strings for "meminfo" command + + TAGDEF(ALL), + TAGDEF(BOTLIB), + TAGDEF(CLIENTS), // Memory used for client info + + TAGDEF(HUNK_MARK1), //hunk allocations before the mark is set + TAGDEF(HUNK_MARK2), //hunk allocations after the mark is set + TAGDEF(EVENT), + TAGDEF(FILESYS), // general filesystem usage + TAGDEF(GHOUL2), // Ghoul2 stuff + TAGDEF(LISTFILES), // for "*.blah" lists + TAGDEF(AMBIENTSET), + TAGDEF(STATIC), // special usage for 1-byte allocations from 0..9 to avoid CopyString() slowdowns during cvar value copies + TAGDEF(SMALL), // used by S_Malloc, but probably more of a hint now. Will be dumped later + TAGDEF(MODEL_MD3), // specific model types' disk images + TAGDEF(MODEL_GLM), // " + TAGDEF(MODEL_GLA), // " + TAGDEF(ICARUS), // Memory used internally by the Icarus scripting system + //sorry, I don't want to have to keep adding these and recompiling, so there may be more than I need + TAGDEF(ICARUS2), //for debugging mem leaks in icarus -rww + TAGDEF(ICARUS3), //for debugging mem leaks in icarus -rww + TAGDEF(ICARUS4), //for debugging mem leaks in icarus -rww + TAGDEF(ICARUS5), //for debugging mem leaks in icarus -rww + TAGDEF(SHADERTEXT), + TAGDEF(SND_RAWDATA), // raw sound data, either MP3 or WAV + TAGDEF(TEMP_WORKSPACE), // anything like file loading or image workspace that's only temporary + TAGDEF(TEMP_PNG), // image workspace that's only temporary + TAGDEF(TEXTPOOL), // for some special text-pool class thingy + TAGDEF(IMAGE_T), // an image_t struct (no longer on the hunk because of cached texture stuff) + TAGDEF(BSP), // guess. + TAGDEF(GRIDMESH), // some specific temp workspace that only seems to be in the MP codebase + TAGDEF(POINTCACHE), // weather system + + TAGDEF(VM_ALLOCATED), // allocated by game or cgame via memory shifting + + TAGDEF(TEMP_HUNKALLOC), + TAGDEF(NEWDEL), // new / delete -> Z_Malloc on Xbox + TAGDEF(UI_ALLOC), // UI DLL calls to UI_Alloc + TAGDEF(CG_UI_ALLOC), // Cgame DLL calls to UI_Alloc + TAGDEF(BG_ALLOC), + TAGDEF(BINK), + TAGDEF(XBL_FRIENDS), // friends list + TAGDEF(STRINGED), + TAGDEF(CLIENT_MANAGER), + + TAGDEF(CLIENT_MANAGER_SPECIAL), // Special: Use HeapAlloc() for second client data to re-use spare model memory + + TAGDEF(COUNT) + + +//////////////// eof ////////////// + diff --git a/codemp/qcommon/timing.h b/codemp/qcommon/timing.h new file mode 100644 index 0000000..ddee390 --- /dev/null +++ b/codemp/qcommon/timing.h @@ -0,0 +1,61 @@ + +class timing_c +{ +private: + __int64 start; + __int64 end; + + int reset; +public: + timing_c(void) + { + } + void Start() + { + const __int64 *s = &start; + __asm + { + push eax + push ebx + push edx + + rdtsc + mov ebx, s + mov [ebx], eax + mov [ebx + 4], edx + + pop edx + pop ebx + pop eax + } + } + int End() + { + const __int64 *e = &end; + __int64 time; +#ifndef __linux__ + __asm + { + push eax + push ebx + push edx + + rdtsc + mov ebx, e + mov [ebx], eax + mov [ebx + 4], edx + + pop edx + pop ebx + pop eax + } +#endif + time = end - start; + if (time < 0) + { + time = 0; + } + return((int)time); + } +}; +// end \ No newline at end of file diff --git a/codemp/qcommon/unzip.cpp b/codemp/qcommon/unzip.cpp new file mode 100644 index 0000000..1f6214c --- /dev/null +++ b/codemp/qcommon/unzip.cpp @@ -0,0 +1,1337 @@ +/***************************************************************************** + * name: unzip.c + * + * desc: IO on .zip files using portions of zlib + * + *****************************************************************************/ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "../client/client.h" + +#define ZIP_fopen fopen +#define ZIP_fclose fclose +#define ZIP_fseek fseek +#define ZIP_fread fread +#define ZIP_ftell ftell + +#include "../zlib32/zip.h" +#include "unzip.h" + +/* unzip.h -- IO for uncompress .zip files using zlib + Version 0.15 beta, Mar 19th, 1998, + + Copyright (C) 1998 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Encryption and multi volume ZipFile (span) are not supported. + Old compressions used by old PKZip 1.x are not supported + + THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE + CAN CHANGE IN FUTURE VERSION !! + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +#define OF(args) args +#endif + +typedef unsigned char Byte; /* 8 bits */ +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ +typedef Byte *voidp; + +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +typedef voidp gzFile; + +gzFile gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h". (See the description + of deflateInit2 for more information about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +gzFile gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +int gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +int gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +int gzwrite OF((gzFile file, + const voidp buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +int QDECL gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). +*/ + +int gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +char * gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +int gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +int gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +int gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +long gzseek OF((gzFile file, + long offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +int gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +long gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +int gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +int gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +const char * gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + +#if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) && \ + !defined(CASESENSITIVITYDEFAULT_NO) +#define CASESENSITIVITYDEFAULT_NO +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (65536) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (Z_Malloc(size, TAG_FILESYS, qfalse)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) Z_Free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +static int unzlocal_getShort (ZIP_FILE* fin, uLong *pX) +{ + short v; + + ZIP_fread( &v, sizeof(v), 1, fin ); + + *pX = LittleShort( v); + return UNZ_OK; +} + +static int unzlocal_getLong (ZIP_FILE *fin, uLong *pX) +{ + int v; + + ZIP_fread( &v, sizeof(v), 1, fin ); + + *pX = LittleLong( v); + return UNZ_OK; +} + + +/* My own strcmpi / strcasecmp */ +static int strcmpcasenosensitive_internal (const char* fileName1,const char* fileName2) +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int unzStringFileNameCompare (const char* fileName1,const char* fileName2,int iCaseSensitivity) +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#define BUFREADCOMMENT (0x400) + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +static uLong unzlocal_SearchCentralDir(ZIP_FILE *fin) +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (ZIP_fseek(fin,0,SEEK_END) != 0) + return 0; + + + uSizeFile = ZIP_ftell( fin ); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (ZIP_fseek(fin,uReadPos,SEEK_SET)!=0) + break; + + if (ZIP_fread(buf,(uInt)uReadSize,1,fin)!=1) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +extern unzFile unzReOpen (const char* path, unzFile file) +{ + unz_s *s; + ZIP_FILE * fin; + + fin=ZIP_fopen(path,"rb"); + if (fin==NULL) + return NULL; + + s=(unz_s*)ALLOC(sizeof(unz_s)); + Com_Memcpy(s, (unz_s*)file, sizeof(unz_s)); + + s->file = fin; + return (unzFile)s; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib109.zip" or on an Unix computer + "zlib/zlib109.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +extern unzFile unzOpen (const char* path) +{ + unz_s us; + unz_s *s; + uLong central_pos,uL; + ZIP_FILE * fin ; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + fin=ZIP_fopen(path,"rb"); + if (fin==NULL) + return NULL; + + central_pos = unzlocal_SearchCentralDir(fin); + if (central_pos==0) + err=UNZ_ERRNO; + + if (ZIP_fseek(fin,central_pos,SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unzlocal_getLong(fin,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unzlocal_getShort(fin,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unzlocal_getShort(fin,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unzlocal_getShort(fin,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir */ + if (unzlocal_getShort(fin,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unzlocal_getLong(fin,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unzlocal_getLong(fin,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* zipfile comment length */ + if (unzlocal_getShort(fin,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + ZIP_fclose(s->file); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int unzGetGlobalInfo (unzFile file,unz_global_info *pglobal_info) +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + + +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +static void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm) +{ + uLong uDate; + uDate = (uLong)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +static int unzlocal_GetCurrentFileInfoInternal (unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize) +{ + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (ZIP_fseek(s->file,s->pos_in_central_dir+s->byte_before_the_zipfile,SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) { + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + } + if (unzlocal_getShort(s->file,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unzlocal_getLong(s->file,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (ZIP_fread(szFileName,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + + if ((err==UNZ_OK) && (extraField!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_extrafile,lSeek,SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) { + if (ZIP_fread(extraField,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + } + lSeek += file_info.size_file_extra - uSizeRead; + } + else + lSeek+=file_info.size_file_extra; + + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentfile,lSeek,SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) { + if (ZIP_fread(szComment,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + } + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int unzGetCurrentFileInfo ( unzFile file, unz_file_info *pfile_info, + char *szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char *szComment, uLong commentBufferSize) +{ + return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int unzGoToFirstFile (unzFile file) +{ + int err=UNZ_OK; + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int unzGoToNextFile (unzFile file) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Get the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ) +{ + unz_s* s; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + *pos = s->pos_in_central_dir; + return UNZ_OK; +} + +/* + Set the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + s->pos_in_central_dir = pos; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return UNZ_OK; +} + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) +{ + unz_s* s; + int err; + + + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + unzGetCurrentFileInfo(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + return err; +} + + +/* + Read the static header of the current zipfile + Check the coherency of the static header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in static header + (filename and size of extra field data) +*/ +static int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar, + uLong *poffset_local_extrafield, + uInt *psize_local_extrafield) +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (ZIP_fseek(s->file,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) { + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + } + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unzlocal_getShort(s->file,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=ZF_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + + if (unzlocal_getShort(s->file,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unzlocal_getShort(s->file,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int unzOpenCurrentFile (unzFile file) +{ + int err=UNZ_OK; + int Store; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; /* offset of the static extra field */ + uInt size_local_extrafield; /* size of the static extra field */ + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, + &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) ALLOC(sizeof(file_in_zip_read_info_s)); + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + + pfile_in_zip_read_info->stream_initialised=0; + + if ((s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=ZF_DEFLATED)) + err=UNZ_BADZIPFILE; + Store = s->cur_file_info.compression_method==0; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->compression_method = + s->cur_file_info.compression_method; + pfile_in_zip_read_info->file=s->file; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if (!Store) + { + err=inflateInit(&pfile_in_zip_read_info->stream, Z_SYNC_FLUSH, 1); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=1; + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + + s->pfile_in_zip_read = pfile_in_zip_read_info; + return UNZ_OK; +} + + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int unzReadCurrentFile (unzFile file, void *buf, unsigned len) +{ + int err=UNZ_OK; + uInt iRead = 0; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->read_buffer == NULL)) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Byte*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if (len>pfile_in_zip_read_info->rest_read_uncompressed) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (s->cur_file_info.compressed_size == pfile_in_zip_read_info->rest_read_compressed) + if (ZIP_fseek(pfile_in_zip_read_info->file, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile,SEEK_SET)!=0) + return UNZ_ERRNO; + if (ZIP_fread(pfile_in_zip_read_info->read_buffer,uReadThis,1, + pfile_in_zip_read_info->file)!=1) + return UNZ_ERRNO; + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Byte*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if (pfile_in_zip_read_info->compression_method==0) + { + uInt uDoCopy,i ; + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + +// pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, +// pfile_in_zip_read_info->stream.next_out, +// uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else + { + uLong uTotalOutBefore,uTotalOutAfter; + const Byte *bufBefore; + uLong uOutThis; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream); + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + +// pfile_in_zip_read_info->crc32 = +// crc32(pfile_in_zip_read_info->crc32,bufBefore, +// (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern long unztell (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (long)pfile_in_zip_read_info->stream.total_out; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int unzeof (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the static-header version of the extra field (sometimes, there is + more info in the static-header version than in the central-header) + + if buf==NULL, it return the size of the static extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int unzGetLocalExtrafield (unzFile file,void *buf,unsigned len) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (ZIP_fseek(pfile_in_zip_read_info->file, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield,SEEK_SET)!=0) + return UNZ_ERRNO; + + if (ZIP_fread(buf,(uInt)size_to_read,1,pfile_in_zip_read_info->file)!=1) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int unzCloseCurrentFile (unzFile file) +{ + int err=UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + +/* + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } +*/ + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd(&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int unzGetGlobalComment (unzFile file, char *szComment, uLong uSizeBuf) +{ + unz_s* s; + uLong uReadThis ; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (ZIP_fseek(s->file,s->central_pos+22,SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (ZIP_fread(szComment,(uInt)uReadThis,1,s->file)!=1) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +// end diff --git a/codemp/qcommon/unzip.h b/codemp/qcommon/unzip.h new file mode 100644 index 0000000..8215fb1 --- /dev/null +++ b/codemp/qcommon/unzip.h @@ -0,0 +1,289 @@ +//unzip.h + +#define ZIP_FILE FILE + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef void* unzFile; +#endif + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + unsigned int tm_sec; /* seconds after the minute - [0,59] */ + unsigned int tm_min; /* minutes after the hour - [0,59] */ + unsigned int tm_hour; /* hours since midnight - [0,23] */ + unsigned int tm_mday; /* day of the month - [1,31] */ + unsigned int tm_mon; /* months since January - [0,11] */ + unsigned int tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + unsigned long number_entry; /* total number of entries in the central dir on this disk */ + unsigned long size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + unsigned long version; /* version made by 2 unsigned chars */ + unsigned long version_needed; /* version needed to extract 2 unsigned chars */ + unsigned long flag; /* general purpose bit flag 2 unsigned chars */ + unsigned long compression_method; /* compression method 2 unsigned chars */ + unsigned long dosDate; /* last mod file date in Dos fmt 4 unsigned chars */ + unsigned long crc; /* crc-32 4 unsigned chars */ + unsigned long compressed_size; /* compressed size 4 unsigned chars */ + unsigned long uncompressed_size; /* uncompressed size 4 unsigned chars */ + unsigned long size_filename; /* filename length 2 unsigned chars */ + unsigned long size_file_extra; /* extra field length 2 unsigned chars */ + unsigned long size_file_comment; /* file comment length 2 unsigned chars */ + + unsigned long disk_num_start; /* disk number start 2 unsigned chars */ + unsigned long internal_fa; /* internal file attributes 2 unsigned chars */ + unsigned long external_fa; /* external file attributes 4 unsigned chars */ + + tm_unz tmu_date; +} unz_file_info; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ + unsigned long offset_curfile;/* relative offset of static header 4 unsigned chars */ +} unz_file_info_internal; + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + + unsigned long pos_in_zipfile; /* position in unsigned char on the zipfile, for fseek*/ + unsigned long stream_initialised; /* flag set if stream structure is initialised*/ + + unsigned long offset_local_extrafield;/* offset of the static extra field */ + unsigned int size_local_extrafield;/* size of the static extra field */ + unsigned long pos_local_extrafield; /* position in the static extra field in read*/ + + unsigned long crc32; /* crc32 of all data uncompressed */ + unsigned long crc32_wait; /* crc32 we must obtain after decompress all */ + unsigned long rest_read_compressed; /* number of unsigned char to be decompressed */ + unsigned long rest_read_uncompressed;/*number of unsigned char to be obtained after decomp*/ + ZIP_FILE *file; /* io structore of the zipfile */ + unsigned long compression_method; /* compression method (0==store) */ + unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ + ZIP_FILE* file; /* io structore of the zipfile */ + unz_global_info gi; /* public global information */ + unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ + unsigned long num_file; /* number of the current file in the zipfile*/ + unsigned long pos_in_central_dir; /* pos of the current file in the central dir*/ + unsigned long current_file_ok; /* flag about the usability of the current file*/ + unsigned long central_pos; /* position of the beginning of the central dir*/ + + unsigned long size_central_dir; /* size of the central directory */ + unsigned long offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info cur_file_info; /* public info about the current file in zip*/ + unz_file_info_internal cur_file_info_internal; /* private info about it*/ + file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ + unsigned char* tmpFile; + int tmpPos,tmpSize; +} unz_s; + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_DATA_ERROR) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +#define UNZ_CASESENSITIVE 1 +#define UNZ_NOTCASESENSITIVE 2 +#define UNZ_OSDEFAULTCASE 0 + +extern int unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity); + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + +extern unzFile unzOpen (const char *path); +extern unzFile unzReOpen (const char* path, unzFile file); + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer + "zlib/zlib111.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern int unzClose (unzFile file); + +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info); + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int unzGetGlobalComment (unzFile file, char *szComment, unsigned long uSizeBuf); + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of unsigned char copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int unzGoToFirstFile (unzFile file); + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int unzGoToNextFile (unzFile file); + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ); + +/* + Get the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ + +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ); + +/* + Set the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ + +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity); + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +extern int unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, unsigned long fileNameBufferSize, void *extraField, unsigned long extraFieldBufferSize, char *szComment, unsigned long commentBufferSize); + +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int unzOpenCurrentFile (unzFile file); + +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int unzCloseCurrentFile (unzFile file); + +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + + +extern int unzReadCurrentFile (unzFile file, void* buf, unsigned len); + +/* + Read unsigned chars from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of unsigned char copied if somes unsigned chars are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern long unztell(unzFile file); + +/* + Give the current position in uncompressed data +*/ + +extern int unzeof (unzFile file); + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int unzGetLocalExtrafield (unzFile file, void* buf, unsigned len); + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of unsigned chars copied in buf, or (if <0) + the error code +*/ diff --git a/codemp/qcommon/vm.cpp b/codemp/qcommon/vm.cpp new file mode 100644 index 0000000..9ccbb42 --- /dev/null +++ b/codemp/qcommon/vm.cpp @@ -0,0 +1,954 @@ +// vm.c -- virtual machine + +/* + + +intermix code and data +symbol table + +a dll has one imported function: VM_SystemCall +and one exported function: Perform + + +*/ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "vm_local.h" + +#ifdef CRAZY_SYMBOL_MAP +symbolVMMap_t g_vmMap; +symbolMap_t *g_symbolMap; +#endif + +vm_t *currentVM = NULL; // bk001212 +vm_t *lastVM = NULL; // bk001212 +int vm_debugLevel; + +#define MAX_VM 3 +vm_t vmTable[MAX_VM]; + +void VM_VmInfo_f( void ); +void VM_VmProfile_f( void ); + + +// converts a VM pointer to a C pointer and +// checks to make sure that the range is acceptable +void *VM_VM2C( vmptr_t p, int length ) { + return (void *)p; +} + +void VM_Debug( int level ) { + vm_debugLevel = level; +} + +/* +============== +VM_Init +============== +*/ +void VM_Init( void ) { + Cvar_Get( "vm_cgame", "0", CVAR_SYSTEMINFO|CVAR_ARCHIVE ); // default to DLLs now instead. Our VMs are getting too HUGE. + Cvar_Get( "vm_game", "0", CVAR_SYSTEMINFO|CVAR_ARCHIVE ); // + Cvar_Get( "vm_ui", "0", CVAR_SYSTEMINFO|CVAR_ARCHIVE ); // + //client wants to know if the server is using vm's for certain modules, + //so if pure we can force the same method (be it vm or dll) -rww + + Cmd_AddCommand ("vmprofile", VM_VmProfile_f ); + Cmd_AddCommand ("vminfo", VM_VmInfo_f ); + + Com_Memset( vmTable, 0, sizeof( vmTable ) ); +} + +/* +=============== +VM_ValueToSymbol + +Assumes a program counter value +=============== +*/ +const char *VM_ValueToSymbol( vm_t *vm, int value ) { + vmSymbol_t *sym; + static char text[MAX_TOKEN_CHARS]; + +#ifdef CRAZY_SYMBOL_MAP + sym = (*g_symbolMap)[value]; + + if (!sym) + { +#endif + sym = vm->symbols; + if ( !sym ) { + return "NO SYMBOLS"; + } + + // find the symbol + while ( sym->next && sym->next->symValue <= value ) { + sym = sym->next; + } +#ifdef CRAZY_SYMBOL_MAP + if (sym) + { //for instant recollection next time + (*g_symbolMap)[value] = sym; + } + } +#endif + + if ( value == sym->symValue ) { + return sym->symName; + } + + Com_sprintf( text, sizeof( text ), "%s+%i", sym->symName, value - sym->symValue ); + + return text; +} + +/* +=============== +VM_ValueToFunctionSymbol + +For profiling, find the symbol behind this value +=============== +*/ +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ) { + vmSymbol_t *sym; + static vmSymbol_t nullSym; + +#ifdef CRAZY_SYMBOL_MAP + sym = (*g_symbolMap)[value]; + + if ( !sym ) + { +#endif + sym = vm->symbols; + if ( !sym ) { + return &nullSym; + } + + while ( sym->next && sym->next->symValue <= value ) { + sym = sym->next; + } +#ifdef CRAZY_SYMBOL_MAP + if (sym) + { //for instant recollection next time + (*g_symbolMap)[value] = sym; + } + } +#endif + + return sym; +} + + +/* +=============== +VM_SymbolToValue +=============== +*/ +int VM_SymbolToValue( vm_t *vm, const char *symbol ) { + vmSymbol_t *sym; + + for ( sym = vm->symbols ; sym ; sym = sym->next ) { + if ( !strcmp( symbol, sym->symName ) ) { + return sym->symValue; + } + } + return 0; +} + + +/* +===================== +VM_SymbolForCompiledPointer +===================== +*/ +const char *VM_SymbolForCompiledPointer( vm_t *vm, void *code ) { + int i; + + if ( code < (void *)vm->codeBase ) { + return "Before code block"; + } + if ( code >= (void *)(vm->codeBase + vm->codeLength) ) { + return "After code block"; + } + + // find which original instruction it is after + for ( i = 0 ; i < vm->codeLength ; i++ ) { + if ( (void *)vm->instructionPointers[i] > code ) { + break; + } + } + i--; + + // now look up the bytecode instruction pointer +#ifdef CRAZY_SYMBOL_MAP + VM_SetSymbolMap(vm); +#endif + return VM_ValueToSymbol( vm, i ); +} + + + +/* +=============== +ParseHex +=============== +*/ +int ParseHex( const char *text ) { + int value; + int c; + + value = 0; + while ( ( c = *text++ ) != 0 ) { + if ( c >= '0' && c <= '9' ) { + value = value * 16 + c - '0'; + continue; + } + if ( c >= 'a' && c <= 'f' ) { + value = value * 16 + 10 + c - 'a'; + continue; + } + if ( c >= 'A' && c <= 'F' ) { + value = value * 16 + 10 + c - 'A'; + continue; + } + } + + return value; +} + +/* +=============== +VM_Alloc + +Convenience function for changing the way VMs are allocated. +=============== +*/ +void *VM_Alloc( int size ) +{ + return Hunk_Alloc(size, h_high); + //return Z_Malloc(size, TAG_ALL, qtrue); +} + +/* +=============== +VM_LoadSymbols +=============== +*/ +void VM_LoadSymbols( vm_t *vm ) { + int len; + char *mapfile, *token; + const char *text_p; + char name[MAX_QPATH]; + char symbols[MAX_QPATH]; + vmSymbol_t **prev, *sym; + int count; + int value; + int chars; + int segment; + int numInstructions; + + // don't load symbols if not developer + if ( !com_developer->integer ) { + return; + } + + COM_StripExtension( vm->name, name ); + Com_sprintf( symbols, sizeof( symbols ), "vm/%s.map", name ); + len = FS_ReadFile( symbols, (void **)&mapfile ); + if ( !mapfile ) { + Com_Printf( "Couldn't load symbol file: %s\n", symbols ); + return; + } + + numInstructions = vm->instructionPointersLength >> 2; + + // parse the symbols + text_p = mapfile; + prev = &vm->symbols; + count = 0; + +#ifdef CRAZY_SYMBOL_MAP + VM_SetSymbolMap(vm); +#endif + + while ( 1 ) { + token = COM_Parse( &text_p ); + if ( !token[0] ) { + break; + } + segment = ParseHex( token ); + if ( segment ) { + COM_Parse( &text_p ); + COM_Parse( &text_p ); + continue; // only load code segment values + } + + token = COM_Parse( &text_p ); + if ( !token[0] ) { + Com_Printf( "WARNING: incomplete line at end of file\n" ); + break; + } + value = ParseHex( token ); + + token = COM_Parse( &text_p ); + if ( !token[0] ) { + Com_Printf( "WARNING: incomplete line at end of file\n" ); + break; + } + chars = strlen( token ); + sym = (struct vmSymbol_s *)VM_Alloc( sizeof( *sym ) + chars ); + *prev = sym; + prev = &sym->next; + sym->next = NULL; + + // convert value from an instruction number to a code offset + if ( value >= 0 && value < numInstructions ) { + value = vm->instructionPointers[value]; + } + + sym->symValue = value; + Q_strncpyz( sym->symName, token, chars + 1 ); + +#ifdef CRAZY_SYMBOL_MAP + (*g_symbolMap)[value] = sym; +#endif + + count++; + } + + vm->numSymbols = count; + Com_Printf( "%i symbols parsed from %s\n", count, symbols ); + FS_FreeFile( mapfile ); +} + +/* +============ +VM_DllSyscall + +Dlls will call this directly + + rcg010206 The horror; the horror. + + The syscall mechanism relies on stack manipulation to get it's args. + This is likely due to C's inability to pass "..." parameters to + a function in one clean chunk. On PowerPC Linux, these parameters + are not necessarily passed on the stack, so while (&arg[0] == arg) + is true, (&arg[1] == 2nd function parameter) is not necessarily + accurate, as arg's value might have been stored to the stack or + other piece of scratch memory to give it a valid address, but the + next parameter might still be sitting in a register. + + Quake's syscall system also assumes that the stack grows downward, + and that any needed types can be squeezed, safely, into a signed int. + + This hack below copies all needed values for an argument to a + array in memory, so that Quake can get the correct values. This can + also be used on systems where the stack grows upwards, as the + presumably standard and safe stdargs.h macros are used. + + As for having enough space in a signed int for your datatypes, well, + it might be better to wait for DOOM 3 before you start porting. :) + + The original code, while probably still inherently dangerous, seems + to work well enough for the platforms it already works on. Rather + than add the performance hit for those platforms, the original code + is still in use there. + + For speed, we just grab 15 arguments, and don't worry about exactly + how many the syscall actually needs; the extra is thrown away. + +============ +*/ +int QDECL VM_DllSyscall( int arg, ... ) { +#if ((defined __linux__) && (defined __powerpc__)) + // rcg010206 - see commentary above + int args[16]; + int i; + va_list ap; + + args[0] = arg; + + va_start(ap, arg); + for (i = 1; i < sizeof (args) / sizeof (args[i]); i++) + args[i] = va_arg(ap, int); + va_end(ap); + + return currentVM->systemCall( args ); +#else // original id code + return currentVM->systemCall( &arg ); +#endif +} + +/* +================= +VM_Restart + +Reload the data, but leave everything else in place +This allows a server to do a map_restart without changing memory allocation +================= +*/ +vm_t *VM_Restart( vm_t *vm ) { + vmHeader_t *header; + int length; + int dataLength; + int i; + char filename[MAX_QPATH]; + + // DLL's can't be restarted in place + if ( vm->dllHandle ) { + char name[MAX_QPATH]; + int (*systemCall)( int *parms ); + + systemCall = vm->systemCall; + Q_strncpyz( name, vm->name, sizeof( name ) ); + + VM_Free( vm ); + + vm = VM_Create( name, systemCall, VMI_NATIVE ); + return vm; + } + + // load the image + Com_Printf( "VM_Restart()\n", filename ); + Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name ); + Com_Printf( "Loading vm file %s.\n", filename ); + length = FS_ReadFile( filename, (void **)&header ); + if ( !header ) { + Com_Error( ERR_DROP, "VM_Restart failed.\n" ); + } + + // byte swap the header + for ( i = 0 ; i < sizeof( *header ) / 4 ; i++ ) { + ((int *)header)[i] = LittleLong( ((int *)header)[i] ); + } + + // validate + if ( header->vmMagic != VM_MAGIC + || header->bssLength < 0 + || header->dataLength < 0 + || header->litLength < 0 + || header->codeLength <= 0 ) { + VM_Free( vm ); + Com_Error( ERR_FATAL, "%s has bad header", filename ); + } + + // round up to next power of 2 so all data operations can + // be mask protected + dataLength = header->dataLength + header->litLength + header->bssLength; + for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { + } + dataLength = 1 << i; + + // clear the data + Com_Memset( vm->dataBase, 0, dataLength ); + + // copy the intialized data + Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength ); + + // byte swap the longs + for ( i = 0 ; i < header->dataLength ; i += 4 ) { + *(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) ); + } + + // free the original file + FS_FreeFile( header ); + + return vm; +} + +/* +================ +VM_Create + +If image ends in .qvm it will be interpreted, otherwise +it will attempt to load as a system dll +================ +*/ + +#define STACK_SIZE 0x20000 + +vm_t *VM_Create( const char *module, int (*systemCalls)(int *), + vmInterpret_t interpret ) { + vm_t *vm; + vmHeader_t *header; + int length; + int dataLength; + int i; + char filename[MAX_QPATH]; + + if ( !module || !module[0] || !systemCalls ) { + Com_Error( ERR_FATAL, "VM_Create: bad parms" ); + } + + // see if we already have the VM + for ( i = 0 ; i < MAX_VM ; i++ ) { + if (!Q_stricmp(vmTable[i].name, module)) { + vm = &vmTable[i]; + return vm; + } + } + + // find a free vm + for ( i = 0 ; i < MAX_VM ; i++ ) { + if ( !vmTable[i].name[0] ) { + break; + } + } + + if ( i == MAX_VM ) { + Com_Error( ERR_FATAL, "VM_Create: no free vm_t" ); + } + + vm = &vmTable[i]; + + Q_strncpyz( vm->name, module, sizeof( vm->name ) ); + vm->systemCall = systemCalls; + + // never allow dll loading with a demo + if ( interpret == VMI_NATIVE ) { + if ( Cvar_VariableValue( "fs_restrict" ) ) { + interpret = VMI_COMPILED; + } + } + + if ( interpret == VMI_NATIVE ) { + // try to load as a system dll + Com_Printf( "Loading dll file %s.\n", vm->name ); + vm->dllHandle = Sys_LoadDll( module, &vm->entryPoint, VM_DllSyscall ); + if ( vm->dllHandle ) { + return vm; + } + + Com_Printf( "Failed to load dll, looking for qvm.\n" ); + interpret = VMI_COMPILED; + } + + // load the image + Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", vm->name ); + Com_Printf( "Loading vm file %s.\n", filename ); + length = FS_ReadFile( filename, (void **)&header ); + if ( !header ) { + Com_Printf( "Failed.\n" ); + VM_Free( vm ); + return NULL; + } + + // byte swap the header + for ( i = 0 ; i < sizeof( *header ) / 4 ; i++ ) { + ((int *)header)[i] = LittleLong( ((int *)header)[i] ); + } + + // validate + if ( header->vmMagic != VM_MAGIC + || header->bssLength < 0 + || header->dataLength < 0 + || header->litLength < 0 + || header->codeLength <= 0 ) { + VM_Free( vm ); + Com_Error( ERR_FATAL, "%s has bad header", filename ); + } + + // round up to next power of 2 so all data operations can + // be mask protected + dataLength = header->dataLength + header->litLength + header->bssLength; + for ( i = 0 ; dataLength > ( 1 << i ) ; i++ ) { + } + dataLength = 1 << i; + + // allocate zero filled space for initialized and uninitialized data + vm->dataBase = (unsigned char *)VM_Alloc( dataLength ); + vm->dataMask = dataLength - 1; + + // copy the intialized data + Com_Memcpy( vm->dataBase, (byte *)header + header->dataOffset, header->dataLength + header->litLength ); + + // byte swap the longs + for ( i = 0 ; i < header->dataLength ; i += 4 ) { + *(int *)(vm->dataBase + i) = LittleLong( *(int *)(vm->dataBase + i ) ); + } + + // allocate space for the jump targets, which will be filled in by the compile/prep functions + vm->instructionPointersLength = header->instructionCount * 4; + vm->instructionPointers = (int *)VM_Alloc( vm->instructionPointersLength ); + + // copy or compile the instructions + vm->codeLength = header->codeLength; + + if ( interpret >= VMI_COMPILED ) { + vm->compiled = qtrue; + VM_Compile( vm, header ); + } else { + vm->compiled = qfalse; + VM_PrepareInterpreter( vm, header ); + } + + // free the original file + FS_FreeFile( header ); + + // load the map file + VM_LoadSymbols( vm ); + + // the stack is implicitly at the end of the image + vm->programStack = vm->dataMask + 1; + vm->stackBottom = vm->programStack - STACK_SIZE; + + return vm; +} + + +/* +============== +VM_Free +============== +*/ +void VM_Free( vm_t *vm ) { + + if ( vm->dllHandle ) { + Sys_UnloadDll( vm->dllHandle ); + Com_Memset( vm, 0, sizeof( *vm ) ); + } +#if 0 // now automatically freed by hunk + if ( vm->codeBase ) { + Z_Free( vm->codeBase ); + } + if ( vm->dataBase ) { + Z_Free( vm->dataBase ); + } + if ( vm->instructionPointers ) { + Z_Free( vm->instructionPointers ); + } +#endif + Com_Memset( vm, 0, sizeof( *vm ) ); + + currentVM = NULL; + lastVM = NULL; +} + +void VM_Clear(void) { + int i; + for (i=0;ientryPoint ) { + return (void *)(currentVM->dataBase + intValue); + } + else { + return (void *)(currentVM->dataBase + (intValue & currentVM->dataMask)); + } +} + +extern vm_t *gvm; +void *BotVMShift( int ptr ) +{ + if ( !ptr ) + { + return NULL; + } + + if (!gvm) + { //always using the game vm here. + return NULL; + } + + if ( gvm->entryPoint ) + { + return (void *)(gvm->dataBase + ptr); + } + else + { + return (void *)(gvm->dataBase + (ptr & gvm->dataMask)); + } +} + +void VM_Shifted_Alloc(void **ptr, int size) +{ + void *mem; + + if (!currentVM) + { + assert(0); + *ptr = NULL; + return; + } + + //first allocate our desired memory, up front + mem = Z_Malloc(size+1, TAG_VM_ALLOCATED, qfalse); + + if (!mem) + { + assert(0); + *ptr = NULL; + return; + } + + memset(mem, 0, size+1); + + //This can happen.. if a free chunk of memory is found before the vm alloc pointer, commonly happens + //when allocating like 4 bytes or whatever. However it seems to actually be handled which I didn't + //think it would be.. so hey. +#if 0 + if ((int)mem < (int)currentVM->dataBase) + { + assert(!"Unspeakably bad thing has occured (mem ptr < vm base ptr)"); + *ptr = NULL; + return; + } +#endif + + //Alright, subtract the database from the memory pointer to get a memory address relative to the VM. + //When the VM modifies it it should be modifying the same chunk of memory we have allocated in the engine. + *ptr = (void *)((int)mem - (int)currentVM->dataBase); +} + +void VM_Shifted_Free(void **ptr) +{ + void *mem; + + if (!currentVM) + { + assert(0); + return; + } + + //Shift the VM memory pointer back to get the same pointer we initially allocated in real memory space. + mem = (void *)((int)currentVM->dataBase + (int)*ptr); + + if (!mem) + { + assert(0); + return; + } + + Z_Free(mem); + *ptr = NULL; //go ahead and clear the pointer for the game. +} + +void *VM_ExplicitArgPtr( vm_t *vm, int intValue ) { + if ( !intValue ) { + return NULL; + } + + // bk010124 - currentVM is missing on reconnect here as well? + if ( currentVM==NULL ) + return NULL; + + // + if ( vm->entryPoint ) { + return (void *)(vm->dataBase + intValue); + } + else { + return (void *)(vm->dataBase + (intValue & vm->dataMask)); + } +} + + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32 parm1 +sp+28 parm0 +sp+24 return value +sp+20 return address +sp+16 local1 +sp+14 local0 +sp+12 arg1 +sp+8 arg0 +sp+4 return stack +sp return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ +#define MAX_STACK 256 +#define STACK_MASK (MAX_STACK-1) + +int QDECL VM_Call( vm_t *vm, int callnum, ... ) { + vm_t *oldVM; + int r; + int i; + int args[16]; + va_list ap; + + + if ( !vm ) { + Com_Error( ERR_FATAL, "VM_Call with NULL vm" ); + } + + oldVM = currentVM; + currentVM = vm; + lastVM = vm; + + if ( vm_debugLevel ) { + Com_Printf( "VM_Call( %i )\n", callnum ); + } + + // if we have a dll loaded, call it directly + if ( vm->entryPoint ) { + //rcg010207 - see dissertation at top of VM_DllSyscall() in this file. + va_start(ap, callnum); + for (i = 0; i < sizeof (args) / sizeof (args[i]); i++) { + args[i] = va_arg(ap, int); + } + va_end(ap); + + r = vm->entryPoint( callnum, args[0], args[1], args[2], args[3], + args[4], args[5], args[6], args[7], + args[8], args[9], args[10], args[11], + args[12], args[13], args[14], args[15]); + } else if ( vm->compiled ) { + r = VM_CallCompiled( vm, &callnum ); + } else { + r = VM_CallInterpreted( vm, &callnum ); + } + + if ( oldVM != NULL ) // bk001220 - assert(currentVM!=NULL) for oldVM==NULL + currentVM = oldVM; + return r; +} + +//================================================================= + +static int QDECL VM_ProfileSort( const void *a, const void *b ) { + vmSymbol_t *sa, *sb; + + sa = *(vmSymbol_t **)a; + sb = *(vmSymbol_t **)b; + + if ( sa->profileCount < sb->profileCount ) { + return -1; + } + if ( sa->profileCount > sb->profileCount ) { + return 1; + } + return 0; +} + +/* +============== +VM_VmProfile_f + +============== +*/ +void VM_VmProfile_f( void ) { + vm_t *vm; + vmSymbol_t **sorted, *sym; + int i; + double total; + + if ( !lastVM ) { + return; + } + + vm = lastVM; + + if ( !vm->numSymbols ) { + return; + } + + sorted = (struct vmSymbol_s **)Z_Malloc( vm->numSymbols * sizeof( *sorted ), TAG_VM, qtrue ); + sorted[0] = vm->symbols; + total = sorted[0]->profileCount; + for ( i = 1 ; i < vm->numSymbols ; i++ ) { + sorted[i] = sorted[i-1]->next; + total += sorted[i]->profileCount; + } + + qsort( sorted, vm->numSymbols, sizeof( *sorted ), VM_ProfileSort ); + + for ( i = 0 ; i < vm->numSymbols ; i++ ) { + int perc; + + sym = sorted[i]; + + perc = 100 * (float) sym->profileCount / total; + Com_Printf( "%2i%% %9i %s\n", perc, sym->profileCount, sym->symName ); + sym->profileCount = 0; + } + + Com_Printf(" %9.0f total\n", total ); + + Z_Free( sorted ); +} + +/* +============== +VM_VmInfo_f + +============== +*/ +void VM_VmInfo_f( void ) { + vm_t *vm; + int i; + + Com_Printf( "Registered virtual machines:\n" ); + for ( i = 0 ; i < MAX_VM ; i++ ) { + vm = &vmTable[i]; + if ( !vm->name[0] ) { + break; + } + Com_Printf( "%s : ", vm->name ); + if ( vm->dllHandle ) { + Com_Printf( "native\n" ); + continue; + } + if ( vm->compiled ) { + Com_Printf( "compiled on load\n" ); + } else { + Com_Printf( "interpreted\n" ); + } + Com_Printf( " code length : %7i\n", vm->codeLength ); + Com_Printf( " table length: %7i\n", vm->instructionPointersLength ); + Com_Printf( " data length : %7i\n", vm->dataMask + 1 ); + } +} + +/* +=============== +VM_LogSyscalls + +Insert calls to this while debugging the vm compiler +=============== +*/ +void VM_LogSyscalls( int *args ) { + static int callnum; + static FILE *f; + + if ( !f ) { + f = fopen("syscalls.log", "w" ); + } + callnum++; + fprintf(f, "%i: %i (%i) = %i %i %i %i\n", callnum, args - (int *)currentVM->dataBase, + args[0], args[1], args[2], args[3], args[4] ); +} + + + +#ifdef oDLL_ONLY // bk010215 - for DLL_ONLY dedicated servers/builds w/o VM +int VM_CallCompiled( vm_t *vm, int *args ) { + return(0); +} + +void VM_Compile( vm_t *vm, vmHeader_t *header ) {} +#endif // DLL_ONLY diff --git a/codemp/qcommon/vm_console.cpp b/codemp/qcommon/vm_console.cpp new file mode 100644 index 0000000..0e8293c --- /dev/null +++ b/codemp/qcommon/vm_console.cpp @@ -0,0 +1,229 @@ +#include "../qcommon/exe_headers.h" +#include "vm_local.h" + +vm_t *currentVM = NULL; +vm_t *lastVM = NULL; + + +#define MAX_VM 3 +vm_t vmTable[MAX_VM]; + + +/* +============ +VM_DllSyscall + +Dlls will call this directly + + rcg010206 The horror; the horror. + + The syscall mechanism relies on stack manipulation to get it's args. + This is likely due to C's inability to pass "..." parameters to + a function in one clean chunk. On PowerPC Linux, these parameters + are not necessarily passed on the stack, so while (&arg[0] == arg) + is true, (&arg[1] == 2nd function parameter) is not necessarily + accurate, as arg's value might have been stored to the stack or + other piece of scratch memory to give it a valid address, but the + next parameter might still be sitting in a register. + + Quake's syscall system also assumes that the stack grows downward, + and that any needed types can be squeezed, safely, into a signed int. + + This hack below copies all needed values for an argument to a + array in memory, so that Quake can get the correct values. This can + also be used on systems where the stack grows upwards, as the + presumably standard and safe stdargs.h macros are used. + + As for having enough space in a signed int for your datatypes, well, + it might be better to wait for DOOM 3 before you start porting. :) + + The original code, while probably still inherently dangerous, seems + to work well enough for the platforms it already works on. Rather + than add the performance hit for those platforms, the original code + is still in use there. + + For speed, we just grab 15 arguments, and don't worry about exactly + how many the syscall actually needs; the extra is thrown away. + +============ +*/ +int QDECL VM_DllSyscall( int arg, ... ) +{ + return currentVM->systemCall( &arg ); +} + +/* +================ +VM_Create + +If image ends in .qvm it will be interpreted, otherwise +it will attempt to load as a system dll +================ +*/ + +//#define STACK_SIZE 0x20000 + +#define UI_VM_INDEX 0 +#define CG_VM_INDEX 1 +#define G_VM_INDEX 2 + +namespace cgame +{ + extern int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ); + void dllEntry( int (QDECL *syscallptr)( int arg,... ) ); +}; + +namespace game +{ + extern int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ); + void dllEntry( int (QDECL *syscallptr)( int arg,... ) ); +}; + +namespace ui +{ + extern int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ); + void dllEntry( int (QDECL *syscallptr)( int arg,... ) ); +}; + +vm_t *VM_Create( const char *module, int (*systemCalls)(int *), + vmInterpret_t interpret ) { + if (!Q_stricmp("ui", module)) + { + // UI VM + vmTable[UI_VM_INDEX].entryPoint = (int (*)(int,...)) ui::vmMain; + vmTable[UI_VM_INDEX].systemCall = systemCalls; + ui::dllEntry(VM_DllSyscall); + return &vmTable[UI_VM_INDEX]; + } + else if (!Q_stricmp("cgame", module)) + { + // CG VM + vmTable[CG_VM_INDEX].entryPoint = (int (*)(int,...)) cgame::vmMain; + vmTable[CG_VM_INDEX].systemCall = systemCalls; + cgame::dllEntry(VM_DllSyscall); + return &vmTable[CG_VM_INDEX]; + } + else if (!Q_stricmp("jampgame", module)) + { + // G VM + vmTable[G_VM_INDEX].entryPoint = (int (*)(int,...)) game::vmMain; + vmTable[G_VM_INDEX].systemCall = systemCalls; + game::dllEntry(VM_DllSyscall); + return &vmTable[G_VM_INDEX]; + } + else + return NULL; +} + + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32 parm1 +sp+28 parm0 +sp+24 return value +sp+20 return address +sp+16 local1 +sp+14 local0 +sp+12 arg1 +sp+8 arg0 +sp+4 return stack +sp return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ +#define MAX_STACK 256 +#define STACK_MASK (MAX_STACK-1) + +int QDECL VM_Call( vm_t *vm, int callnum, ... ) +{ + // Remember what the current VM was when we started. + vm_t *oldVM = currentVM; + // Change current VM so that VMA() crap works + currentVM = vm; + + // Forward the call to the vm's vmMain function, passing through more data than + // we should. I'm going to be sick. +#if defined(_GAMECUBE) + int i; + int args[16]; + va_list ap; + va_start(ap, callnum); + for (i = 0; i < sizeof (args) / sizeof (args[i]); i++) + args[i] = va_arg(ap, int); + va_end(ap); + + int r = vm->entryPoint( callnum, args[0], args[1], args[2], args[3], + args[4], args[5], args[6], args[7], + args[8], args[9], args[10], args[11], + args[12], args[13], args[14], args[15]); +#else + int r = vm->entryPoint( (&callnum)[0], (&callnum)[1], (&callnum)[2], (&callnum)[3], + (&callnum)[4], (&callnum)[5], (&callnum)[6], (&callnum)[7], + (&callnum)[8], (&callnum)[9], (&callnum)[10], (&callnum)[11], (&callnum)[12] ); +#endif + + + // Restore VM pointer XXX: Why does the below code check for non-NULL? + currentVM = oldVM; + return r; +} + +// This function seems really suspect. Let's cross our fingers... +void *BotVMShift( int ptr ) +{ + return (void *)ptr; +} + +// Functions to support dynamic memory allocation by VMs. +// I don't really trust these. Oh well. +void VM_Shifted_Alloc(void **ptr, int size) +{ + if (!currentVM) + { + assert(0); + *ptr = NULL; + return; + } + + //first allocate our desired memory, up front + *ptr = Z_Malloc(size, TAG_VM_ALLOCATED, qtrue); +} + +void VM_Shifted_Free(void **ptr) +{ + if (!currentVM) + { + assert(0); + return; + } + + Z_Free(*ptr); + *ptr = NULL; //go ahead and clear the pointer for the game. +} + +// Stupid casting function. We can't do this in the macros, because sv_game calls this +// directly now. +void *VM_ArgPtr( int intValue ) +{ + return (void *)intValue; +} + +void VM_Free(vm_t *) {} + +void VM_Debug(int) {} + +void VM_Clear(void) {} + +void VM_Init(void) {} + +void *VM_ExplicitArgPtr(vm_t *, int) { return NULL; } + +vm_t *VM_Restart(vm_t *vm) { return vm; } diff --git a/codemp/qcommon/vm_interpreted.cpp b/codemp/qcommon/vm_interpreted.cpp new file mode 100644 index 0000000..0c2aada --- /dev/null +++ b/codemp/qcommon/vm_interpreted.cpp @@ -0,0 +1,905 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "vm_local.h" + +#define DEBUG_VM + +#ifdef DEBUG_VM // bk001204 +static char *opnames[256] = { + "OP_UNDEF", + + "OP_IGNORE", + + "OP_BREAK", + + "OP_ENTER", + "OP_LEAVE", + "OP_CALL", + "OP_PUSH", + "OP_POP", + + "OP_CONST", + + "OP_LOCAL", + + "OP_JUMP", + + //------------------- + + "OP_EQ", + "OP_NE", + + "OP_LTI", + "OP_LEI", + "OP_GTI", + "OP_GEI", + + "OP_LTU", + "OP_LEU", + "OP_GTU", + "OP_GEU", + + "OP_EQF", + "OP_NEF", + + "OP_LTF", + "OP_LEF", + "OP_GTF", + "OP_GEF", + + //------------------- + + "OP_LOAD1", + "OP_LOAD2", + "OP_LOAD4", + "OP_STORE1", + "OP_STORE2", + "OP_STORE4", + "OP_ARG", + + "OP_BLOCK_COPY", + + //------------------- + + "OP_SEX8", + "OP_SEX16", + + "OP_NEGI", + "OP_ADD", + "OP_SUB", + "OP_DIVI", + "OP_DIVU", + "OP_MODI", + "OP_MODU", + "OP_MULI", + "OP_MULU", + + "OP_BAND", + "OP_BOR", + "OP_BXOR", + "OP_BCOM", + + "OP_LSH", + "OP_RSHI", + "OP_RSHU", + + "OP_NEGF", + "OP_ADDF", + "OP_SUBF", + "OP_DIVF", + "OP_MULF", + + "OP_CVIF", + "OP_CVFI" +}; +#endif + +#if idppc + #if defined(__GNUC__) + static inline unsigned int loadWord(void *addr) { + unsigned int word; + + asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr)); + return word; + } + #else + #define loadWord(addr) __lwbrx(addr,0) + #endif +#else + #define loadWord(addr) *((int *)addr) +#endif + +char *VM_Indent( vm_t *vm ) { + static char *string = " "; + if ( vm->callLevel > 20 ) { + return string; + } + return string + 2 * ( 20 - vm->callLevel ); +} + +void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) { + int count; + + count = 0; + do { + Com_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) ); + programStack = *(int *)&vm->dataBase[programStack+4]; + programCounter = *(int *)&vm->dataBase[programStack]; + } while ( programCounter != -1 && ++count < 32 ); + +} + + +/* +==================== +VM_PrepareInterpreter +==================== +*/ +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ) { + int op; + int pc; + byte *code; + int instruction; + int *codeBase; + + vm->codeBase = (unsigned char *)Hunk_Alloc( vm->codeLength*4, h_high ); // we're now int aligned +// memcpy( vm->codeBase, (byte *)header + header->codeOffset, vm->codeLength ); + + // we don't need to translate the instructions, but we still need + // to find each instructions starting point for jumps + pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + codeBase = (int *)vm->codeBase; + + while ( instruction < header->instructionCount ) { + vm->instructionPointers[ instruction ] = pc; + instruction++; + + op = code[ pc ]; + codeBase[pc] = op; + if ( pc > header->codeLength ) { + Com_Error( ERR_FATAL, "VM_PrepareInterpreter: pc > header->codeLength" ); + } + + pc++; + + // these are the only opcodes that aren't a single byte + switch ( op ) { + case OP_ENTER: + case OP_CONST: + case OP_LOCAL: + case OP_LEAVE: + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + case OP_BLOCK_COPY: + codeBase[pc+0] = loadWord(&code[pc]); + pc += 4; + break; + case OP_ARG: + codeBase[pc+0] = code[pc]; + pc += 1; + break; + default: + break; + } + + } + pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + codeBase = (int *)vm->codeBase; + + while ( instruction < header->instructionCount ) { + op = code[ pc ]; + instruction++; + pc++; + switch ( op ) { + case OP_ENTER: + case OP_CONST: + case OP_LOCAL: + case OP_LEAVE: + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + case OP_BLOCK_COPY: + switch(op) { + case OP_EQ: + case OP_NE: + case OP_LTI: + case OP_LEI: + case OP_GTI: + case OP_GEI: + case OP_LTU: + case OP_LEU: + case OP_GTU: + case OP_GEU: + case OP_EQF: + case OP_NEF: + case OP_LTF: + case OP_LEF: + case OP_GTF: + case OP_GEF: + codeBase[pc] = vm->instructionPointers[codeBase[pc]]; + break; + default: + break; + } + pc += 4; + break; + case OP_ARG: + pc += 1; + break; + default: + break; + } + + } +} + +/* +============== +VM_Call + + +Upon a system call, the stack will look like: + +sp+32 parm1 +sp+28 parm0 +sp+24 return stack +sp+20 return address +sp+16 local1 +sp+14 local0 +sp+12 arg1 +sp+8 arg0 +sp+4 return stack +sp return address + +An interpreted function will immediately execute +an OP_ENTER instruction, which will subtract space for +locals from sp +============== +*/ +#define MAX_STACK 256 +#define STACK_MASK (MAX_STACK-1) + +#define DEBUGSTR va("%s%i", VM_Indent(vm), opStack-stack ) + +int VM_CallInterpreted( vm_t *vm, int *args ) { + int stack[MAX_STACK]; + int *opStack; + int programCounter; + int programStack; + int stackOnEntry; + byte *image; + int *codeImage; + int v1; + int dataMask; +#ifdef DEBUG_VM + vmSymbol_t *profileSymbol = NULL; +#endif + + // interpret the code + vm->currentlyInterpreting = qtrue; +#ifdef CRAZY_SYMBOL_MAP + VM_SetSymbolMap(vm); +#endif + + // we might be called recursively, so this might not be the very top + programStack = stackOnEntry = vm->programStack; + +#ifdef DEBUG_VM + if (com_vmdebug->integer) + { + profileSymbol = VM_ValueToFunctionSymbol( vm, 0 ); + } + // uncomment this for debugging breakpoints + vm->breakFunction = 0; +#endif + // set up the stack frame + + image = vm->dataBase; + codeImage = (int *)vm->codeBase; + dataMask = vm->dataMask; + + // leave a free spot at start of stack so + // that as long as opStack is valid, opStack-1 will + // not corrupt anything + opStack = stack; + programCounter = 0; + + programStack -= 48; + + *(int *)&image[ programStack + 44] = args[9]; + *(int *)&image[ programStack + 40] = args[8]; + *(int *)&image[ programStack + 36] = args[7]; + *(int *)&image[ programStack + 32] = args[6]; + *(int *)&image[ programStack + 28] = args[5]; + *(int *)&image[ programStack + 24] = args[4]; + *(int *)&image[ programStack + 20] = args[3]; + *(int *)&image[ programStack + 16] = args[2]; + *(int *)&image[ programStack + 12] = args[1]; + *(int *)&image[ programStack + 8 ] = args[0]; + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + vm->callLevel = 0; + + VM_Debug(0); + +// vm_debugLevel=2; + // main interpreter loop, will exit when a LEAVE instruction + // grabs the -1 program counter + +#define r2 codeImage[programCounter] + + while ( 1 ) { + int opcode, r0, r1; +// unsigned int r2; + +nextInstruction: + r0 = ((int *)opStack)[0]; + r1 = ((int *)opStack)[-1]; +nextInstruction2: + opcode = codeImage[ programCounter++ ]; +#ifdef DEBUG_VM + if (com_vmdebug->integer > 1) + { + if ( (unsigned)programCounter > vm->codeLength ) { + Com_Error( ERR_DROP, "VM pc out of range" ); + } + + if ( opStack < stack ) { + Com_Error( ERR_DROP, "VM opStack underflow" ); + } + if ( opStack >= stack+MAX_STACK ) { + Com_Error( ERR_DROP, "VM opStack overflow" ); + } + + if ( programStack <= vm->stackBottom ) { + Com_Error( ERR_DROP, "VM stack overflow" ); + } + + if ( programStack & 3 ) { + Com_Error( ERR_DROP, "VM program stack misaligned" ); + } + + if ( vm_debugLevel > 1 ) { + Com_Printf( "%s %s\n", DEBUGSTR, opnames[opcode] ); + } + profileSymbol->profileCount++; + } +#endif + + switch ( opcode ) { +#ifdef DEBUG_VM + default: + Com_Error( ERR_DROP, "Bad VM instruction" ); // this should be scanned on load! +#endif + case OP_BREAK: + vm->breakCount++; + goto nextInstruction2; + case OP_CONST: + opStack++; + r1 = r0; + r0 = *opStack = r2; + + programCounter += 4; + goto nextInstruction2; + case OP_LOCAL: + opStack++; + r1 = r0; + r0 = *opStack = r2+programStack; + + programCounter += 4; + goto nextInstruction2; + + case OP_LOAD4: +#ifdef DEBUG_VM + if (com_vmdebug->integer > 1) + { + if ( *opStack & 3 ) { + Com_Error( ERR_DROP, "OP_LOAD4 misaligned" ); + } + } +#endif + r0 = *opStack = *(int *)&image[ r0&dataMask ]; + goto nextInstruction2; + case OP_LOAD2: + r0 = *opStack = *(unsigned short *)&image[ r0&dataMask ]; + goto nextInstruction2; + case OP_LOAD1: + r0 = *opStack = image[ r0&dataMask ]; + goto nextInstruction2; + + case OP_STORE4: + *(int *)&image[ r1&(dataMask & ~3) ] = r0; + opStack -= 2; + goto nextInstruction; + case OP_STORE2: + *(short *)&image[ r1&(dataMask & ~1) ] = r0; + opStack -= 2; + goto nextInstruction; + case OP_STORE1: + image[ r1&dataMask ] = r0; + opStack -= 2; + goto nextInstruction; + + case OP_ARG: + // single byte offset from programStack + *(int *)&image[ codeImage[programCounter] + programStack ] = r0; + opStack--; + programCounter += 1; + goto nextInstruction; + + case OP_BLOCK_COPY: + { + int *src, *dest; + int i, count, srci, desti; + + count = r2; + // MrE: copy range check + srci = r0 & dataMask; + desti = r1 & dataMask; + count = ((srci + count) & dataMask) - srci; + count = ((desti + count) & dataMask) - desti; + + src = (int *)&image[ r0&dataMask ]; + dest = (int *)&image[ r1&dataMask ]; + if ( ( (int)src | (int)dest | count ) & 3 ) { + Com_Error( ERR_DROP, "OP_BLOCK_COPY not dword aligned" ); + } + count >>= 2; + for ( i = count-1 ; i>= 0 ; i-- ) { + dest[i] = src[i]; + } + programCounter += 4; + opStack -= 2; + } + goto nextInstruction; + + case OP_CALL: + // save current program counter + *(int *)&image[ programStack ] = programCounter; + + // jump to the location on the stack + programCounter = r0; + opStack--; + if ( programCounter < 0 ) { + // system call + int r; + int temp; +#ifdef DEBUG_VM + int stomped = 0; + + if (com_vmdebug->integer > 1) + { + if ( vm_debugLevel ) { + Com_Printf( "%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter ); + } + } +#endif + // save the stack to allow recursive VM entry + temp = vm->callLevel; + vm->programStack = programStack - 4; +#ifdef DEBUG_VM + if (com_vmdebug->integer > 1) + { + stomped = *(int *)&image[ programStack + 4 ]; + } +#endif + *(int *)&image[ programStack + 4 ] = -1 - programCounter; + +//VM_LogSyscalls( (int *)&image[ programStack + 4 ] ); + r = vm->systemCall( (int *)&image[ programStack + 4 ] ); + +#ifdef DEBUG_VM + if (com_vmdebug->integer > 1) + { + // this is just our stack frame pointer, only needed + // for debugging + *(int *)&image[ programStack + 4 ] = stomped; + } +#endif + + // save return value + opStack++; + *opStack = r; + programCounter = *(int *)&image[ programStack ]; + vm->callLevel = temp; +#ifdef DEBUG_VM + if (com_vmdebug->integer > 1) + { + if ( vm_debugLevel ) { + Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); + } + } +#endif + } else { + programCounter = vm->instructionPointers[ programCounter ]; + } + goto nextInstruction; + + // push and pop are only needed for discarded or bad function return values + case OP_PUSH: + opStack++; + goto nextInstruction; + case OP_POP: + opStack--; + goto nextInstruction; + + case OP_ENTER: +#ifdef DEBUG_VM + if (com_vmdebug->integer) + { + profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); + } +#endif + // get size of stack frame + v1 = r2; + + programCounter += 4; + programStack -= v1; +#ifdef DEBUG_VM + if (com_vmdebug->integer > 1) + { + // save old stack frame for debugging traces + *(int *)&image[programStack+4] = programStack + v1; + if ( vm_debugLevel ) { + Com_Printf( "%s---> %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter - 5 ) ); + if ( vm->breakFunction && programCounter - 5 == vm->breakFunction ) { + // this is to allow setting breakpoints here in the debugger + vm->breakCount++; + // vm_debugLevel = 2; + // VM_StackTrace( vm, programCounter, programStack ); + } + vm->callLevel++; + } + } +#endif + goto nextInstruction; + case OP_LEAVE: + // remove our stack frame + v1 = r2; + + programStack += v1; + + // grab the saved program counter + programCounter = *(int *)&image[ programStack ]; +#ifdef DEBUG_VM + if (com_vmdebug->integer) + { + profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); + if ( vm_debugLevel ) { + vm->callLevel--; + Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); + } + } +#endif + // check for leaving the VM + if ( programCounter == -1 ) { + goto done; + } + goto nextInstruction; + + /* + =================================================================== + BRANCHES + =================================================================== + */ + + case OP_JUMP: + programCounter = r0; + programCounter = vm->instructionPointers[ programCounter ]; + opStack--; + goto nextInstruction; + + case OP_EQ: + opStack -= 2; + if ( r1 == r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_NE: + opStack -= 2; + if ( r1 != r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_LTI: + opStack -= 2; + if ( r1 < r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_LEI: + opStack -= 2; + if ( r1 <= r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_GTI: + opStack -= 2; + if ( r1 > r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_GEI: + opStack -= 2; + if ( r1 >= r0 ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_LTU: + opStack -= 2; + if ( ((unsigned)r1) < ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_LEU: + opStack -= 2; + if ( ((unsigned)r1) <= ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_GTU: + opStack -= 2; + if ( ((unsigned)r1) > ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_GEU: + opStack -= 2; + if ( ((unsigned)r1) >= ((unsigned)r0) ) { + programCounter = r2; //vm->instructionPointers[r2]; + goto nextInstruction; + } else { + programCounter += 4; + goto nextInstruction; + } + + case OP_EQF: + if ( ((float *)opStack)[-1] == *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + case OP_NEF: + if ( ((float *)opStack)[-1] != *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + case OP_LTF: + if ( ((float *)opStack)[-1] < *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + case OP_LEF: + if ( ((float *)opStack)[-1] <= *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + case OP_GTF: + if ( ((float *)opStack)[-1] > *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + case OP_GEF: + if ( ((float *)opStack)[-1] >= *(float *)opStack ) { + programCounter = r2; //vm->instructionPointers[r2]; + opStack -= 2; + goto nextInstruction; + } else { + programCounter += 4; + opStack -= 2; + goto nextInstruction; + } + + + //=================================================================== + + case OP_NEGI: + *opStack = -r0; + goto nextInstruction; + case OP_ADD: + opStack[-1] = r1 + r0; + opStack--; + goto nextInstruction; + case OP_SUB: + opStack[-1] = r1 - r0; + opStack--; + goto nextInstruction; + case OP_DIVI: + opStack[-1] = r1 / r0; + opStack--; + goto nextInstruction; + case OP_DIVU: + opStack[-1] = ((unsigned)r1) / ((unsigned)r0); + opStack--; + goto nextInstruction; + case OP_MODI: + opStack[-1] = r1 % r0; + opStack--; + goto nextInstruction; + case OP_MODU: + opStack[-1] = ((unsigned)r1) % (unsigned)r0; + opStack--; + goto nextInstruction; + case OP_MULI: + opStack[-1] = r1 * r0; + opStack--; + goto nextInstruction; + case OP_MULU: + opStack[-1] = ((unsigned)r1) * ((unsigned)r0); + opStack--; + goto nextInstruction; + + case OP_BAND: + opStack[-1] = ((unsigned)r1) & ((unsigned)r0); + opStack--; + goto nextInstruction; + case OP_BOR: + opStack[-1] = ((unsigned)r1) | ((unsigned)r0); + opStack--; + goto nextInstruction; + case OP_BXOR: + opStack[-1] = ((unsigned)r1) ^ ((unsigned)r0); + opStack--; + goto nextInstruction; + case OP_BCOM: + opStack[-1] = ~ ((unsigned)r0); + goto nextInstruction; + + case OP_LSH: + opStack[-1] = r1 << r0; + opStack--; + goto nextInstruction; + case OP_RSHI: + opStack[-1] = r1 >> r0; + opStack--; + goto nextInstruction; + case OP_RSHU: + opStack[-1] = ((unsigned)r1) >> r0; + opStack--; + goto nextInstruction; + + case OP_NEGF: + *(float *)opStack = -*(float *)opStack; + goto nextInstruction; + case OP_ADDF: + *(float *)(opStack-1) = *(float *)(opStack-1) + *(float *)opStack; + opStack--; + goto nextInstruction; + case OP_SUBF: + *(float *)(opStack-1) = *(float *)(opStack-1) - *(float *)opStack; + opStack--; + goto nextInstruction; + case OP_DIVF: + *(float *)(opStack-1) = *(float *)(opStack-1) / *(float *)opStack; + opStack--; + goto nextInstruction; + case OP_MULF: + *(float *)(opStack-1) = *(float *)(opStack-1) * *(float *)opStack; + opStack--; + goto nextInstruction; + + case OP_CVIF: + *(float *)opStack = (float)*opStack; + goto nextInstruction; + case OP_CVFI: + *opStack = (int) *(float *)opStack; + goto nextInstruction; + case OP_SEX8: + *opStack = (signed char)*opStack; + goto nextInstruction; + case OP_SEX16: + *opStack = (short)*opStack; + goto nextInstruction; + } + } + +done: + vm->currentlyInterpreting = qfalse; + + if ( opStack != &stack[1] ) { + Com_Error( ERR_DROP, "Interpreter error: opStack = %i", opStack - stack ); + } + + vm->programStack = stackOnEntry; + + // return the result + return *opStack; +} diff --git a/codemp/qcommon/vm_local.h b/codemp/qcommon/vm_local.h new file mode 100644 index 0000000..48ec219 --- /dev/null +++ b/codemp/qcommon/vm_local.h @@ -0,0 +1,182 @@ +//rww - so that I may utilize vm debugging features WITHOUT DROPPING TO 0.1FPS +#ifndef _XBOX +#define CRAZY_SYMBOL_MAP +#endif + +#ifdef CRAZY_SYMBOL_MAP +#include +#endif + +typedef enum { + OP_UNDEF, + + OP_IGNORE, + + OP_BREAK, + + OP_ENTER, + OP_LEAVE, + OP_CALL, + OP_PUSH, + OP_POP, + + OP_CONST, + OP_LOCAL, + + OP_JUMP, + + //------------------- + + OP_EQ, + OP_NE, + + OP_LTI, + OP_LEI, + OP_GTI, + OP_GEI, + + OP_LTU, + OP_LEU, + OP_GTU, + OP_GEU, + + OP_EQF, + OP_NEF, + + OP_LTF, + OP_LEF, + OP_GTF, + OP_GEF, + + //------------------- + + OP_LOAD1, + OP_LOAD2, + OP_LOAD4, + OP_STORE1, + OP_STORE2, + OP_STORE4, // *(stack[top-1]) = stack[top] + OP_ARG, + + OP_BLOCK_COPY, + + //------------------- + + OP_SEX8, + OP_SEX16, + + OP_NEGI, + OP_ADD, + OP_SUB, + OP_DIVI, + OP_DIVU, + OP_MODI, + OP_MODU, + OP_MULI, + OP_MULU, + + OP_BAND, + OP_BOR, + OP_BXOR, + OP_BCOM, + + OP_LSH, + OP_RSHI, + OP_RSHU, + + OP_NEGF, + OP_ADDF, + OP_SUBF, + OP_DIVF, + OP_MULF, + + OP_CVIF, + OP_CVFI +} opcode_t; + + + +typedef int vmptr_t; + +typedef struct vmSymbol_s { + struct vmSymbol_s *next; + int symValue; + int profileCount; + char symName[1]; // variable sized +} vmSymbol_t; + +#define VM_OFFSET_PROGRAM_STACK 0 +#define VM_OFFSET_SYSTEM_CALL 4 + +struct vm_s { + // DO NOT MOVE OR CHANGE THESE WITHOUT CHANGING THE VM_OFFSET_* DEFINES + // USED BY THE ASM CODE + int programStack; // the vm may be recursively entered + int (*systemCall)( int *parms ); + + //------------------------------------ + + char name[MAX_QPATH]; + + // for dynamic linked modules + void *dllHandle; + int (QDECL *entryPoint)( int callNum, ... ); + + // for interpreted modules + qboolean currentlyInterpreting; + + qboolean compiled; + byte *codeBase; + int codeLength; + + int *instructionPointers; + int instructionPointersLength; + + byte *dataBase; + int dataMask; + + int stackBottom; // if programStack < stackBottom, error + + int numSymbols; + struct vmSymbol_s *symbols; + + int callLevel; // for debug indenting + int breakFunction; // increment breakCount on function entry to this + int breakCount; +}; + +#ifdef CRAZY_SYMBOL_MAP +typedef std::map symbolMap_t; +typedef std::map symbolVMMap_t; + +extern symbolVMMap_t g_vmMap; +extern symbolMap_t *g_symbolMap; + +/* +Set the symbol map based on the VM currently +being in interpreted. This is done so that we +do not have to do a map lookup for the VM with +each symbol request. +-rww +*/ +inline void VM_SetSymbolMap(vm_t *vm) +{ + g_symbolMap = &g_vmMap[vm]; +} +#endif + + +extern vm_t *currentVM; +extern int vm_debugLevel; + +void VM_Compile( vm_t *vm, vmHeader_t *header ); +int VM_CallCompiled( vm_t *vm, int *args ); + +void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ); +int VM_CallInterpreted( vm_t *vm, int *args ); + +vmSymbol_t *VM_ValueToFunctionSymbol( vm_t *vm, int value ); +int VM_SymbolToValue( vm_t *vm, const char *symbol ); +const char *VM_ValueToSymbol( vm_t *vm, int value ); +void VM_LogSyscalls( int *args ); + diff --git a/codemp/qcommon/vm_ppc.cpp b/codemp/qcommon/vm_ppc.cpp new file mode 100644 index 0000000..062939f --- /dev/null +++ b/codemp/qcommon/vm_ppc.cpp @@ -0,0 +1,1274 @@ +// vm_ppc.c +// ppc dynamic compiler + +#include "vm_local.h" + +#pragma opt_pointer_analysis off + + +typedef enum { + R_REAL_STACK = 1, + // registers 3-11 are the parameter passing registers + + // state + R_STACK = 3, // local + R_OPSTACK, // global + + // constants + R_MEMBASE, // global + R_MEMMASK, + R_ASMCALL, // global + R_INSTRUCTIONS, // global + R_NUM_INSTRUCTIONS, // global + R_CVM, // currentVM + + // temps + R_TOP = 12, + R_SECOND = 13, + R_EA = 14 // effective address calculation + +} regNums_t; + +#define RG_REAL_STACK r1 +#define RG_STACK r3 +#define RG_OPSTACK r4 +#define RG_MEMBASE r5 +#define RG_MEMMASK r6 +#define RG_ASMCALL r7 +#define RG_INSTRUCTIONS r8 +#define RG_NUM_INSTRUCTIONS r9 +#define RG_CVM r10 +#define RG_TOP r12 +#define RG_SECOND r13 +#define RG_EA r14 + +// this doesn't have the low order bits set for instructions i'm not using... +typedef enum { + PPC_TDI = 0x08000000, + PPC_TWI = 0x0c000000, + PPC_MULLI = 0x1c000000, + PPC_SUBFIC = 0x20000000, + PPC_CMPI = 0x28000000, + PPC_CMPLI = 0x2c000000, + PPC_ADDIC = 0x30000000, + PPC_ADDIC_ = 0x34000000, + PPC_ADDI = 0x38000000, + PPC_ADDIS = 0x3c000000, + PPC_BC = 0x40000000, + PPC_SC = 0x44000000, + PPC_B = 0x48000000, + + PPC_MCRF = 0x4c000000, + PPC_BCLR = 0x4c000020, + PPC_RFID = 0x4c000000, + PPC_CRNOR = 0x4c000000, + PPC_RFI = 0x4c000000, + PPC_CRANDC = 0x4c000000, + PPC_ISYNC = 0x4c000000, + PPC_CRXOR = 0x4c000000, + PPC_CRNAND = 0x4c000000, + PPC_CREQV = 0x4c000000, + PPC_CRORC = 0x4c000000, + PPC_CROR = 0x4c000000, +//------------ + PPC_BCCTR = 0x4c000420, + PPC_RLWIMI = 0x50000000, + PPC_RLWINM = 0x54000000, + PPC_RLWNM = 0x5c000000, + PPC_ORI = 0x60000000, + PPC_ORIS = 0x64000000, + PPC_XORI = 0x68000000, + PPC_XORIS = 0x6c000000, + PPC_ANDI_ = 0x70000000, + PPC_ANDIS_ = 0x74000000, + PPC_RLDICL = 0x78000000, + PPC_RLDICR = 0x78000000, + PPC_RLDIC = 0x78000000, + PPC_RLDIMI = 0x78000000, + PPC_RLDCL = 0x78000000, + PPC_RLDCR = 0x78000000, + PPC_CMP = 0x7c000000, + PPC_TW = 0x7c000000, + PPC_SUBFC = 0x7c000010, + PPC_MULHDU = 0x7c000000, + PPC_ADDC = 0x7c000014, + PPC_MULHWU = 0x7c000000, + PPC_MFCR = 0x7c000000, + PPC_LWAR = 0x7c000000, + PPC_LDX = 0x7c000000, + PPC_LWZX = 0x7c00002e, + PPC_SLW = 0x7c000030, + PPC_CNTLZW = 0x7c000000, + PPC_SLD = 0x7c000000, + PPC_AND = 0x7c000038, + PPC_CMPL = 0x7c000040, + PPC_SUBF = 0x7c000050, + PPC_LDUX = 0x7c000000, +//------------ + PPC_DCBST = 0x7c000000, + PPC_LWZUX = 0x7c00006c, + PPC_CNTLZD = 0x7c000000, + PPC_ANDC = 0x7c000000, + PPC_TD = 0x7c000000, + PPC_MULHD = 0x7c000000, + PPC_MULHW = 0x7c000000, + PPC_MTSRD = 0x7c000000, + PPC_MFMSR = 0x7c000000, + PPC_LDARX = 0x7c000000, + PPC_DCBF = 0x7c000000, + PPC_LBZX = 0x7c0000ae, + PPC_NEG = 0x7c000000, + PPC_MTSRDIN = 0x7c000000, + PPC_LBZUX = 0x7c000000, + PPC_NOR = 0x7c0000f8, + PPC_SUBFE = 0x7c000000, + PPC_ADDE = 0x7c000000, + PPC_MTCRF = 0x7c000000, + PPC_MTMSR = 0x7c000000, + PPC_STDX = 0x7c000000, + PPC_STWCX_ = 0x7c000000, + PPC_STWX = 0x7c00012e, + PPC_MTMSRD = 0x7c000000, + PPC_STDUX = 0x7c000000, + PPC_STWUX = 0x7c00016e, + PPC_SUBFZE = 0x7c000000, + PPC_ADDZE = 0x7c000000, + PPC_MTSR = 0x7c000000, + PPC_STDCX_ = 0x7c000000, + PPC_STBX = 0x7c0001ae, + PPC_SUBFME = 0x7c000000, + PPC_MULLD = 0x7c000000, +//------------ + PPC_ADDME = 0x7c000000, + PPC_MULLW = 0x7c0001d6, + PPC_MTSRIN = 0x7c000000, + PPC_DCBTST = 0x7c000000, + PPC_STBUX = 0x7c000000, + PPC_ADD = 0x7c000214, + PPC_DCBT = 0x7c000000, + PPC_LHZX = 0x7c00022e, + PPC_EQV = 0x7c000000, + PPC_TLBIE = 0x7c000000, + PPC_ECIWX = 0x7c000000, + PPC_LHZUX = 0x7c000000, + PPC_XOR = 0x7c000278, + PPC_MFSPR = 0x7c0002a6, + PPC_LWAX = 0x7c000000, + PPC_LHAX = 0x7c000000, + PPC_TLBIA = 0x7c000000, + PPC_MFTB = 0x7c000000, + PPC_LWAUX = 0x7c000000, + PPC_LHAUX = 0x7c000000, + PPC_STHX = 0x7c00032e, + PPC_ORC = 0x7c000338, + PPC_SRADI = 0x7c000000, + PPC_SLBIE = 0x7c000000, + PPC_ECOWX = 0x7c000000, + PPC_STHUX = 0x7c000000, + PPC_OR = 0x7c000378, + PPC_DIVDU = 0x7c000000, + PPC_DIVWU = 0x7c000396, + PPC_MTSPR = 0x7c0003a6, + PPC_DCBI = 0x7c000000, + PPC_NAND = 0x7c000000, + PPC_DIVD = 0x7c000000, +//------------ + PPC_DIVW = 0x7c0003d6, + PPC_SLBIA = 0x7c000000, + PPC_MCRXR = 0x7c000000, + PPC_LSWX = 0x7c000000, + PPC_LWBRX = 0x7c000000, + PPC_LFSX = 0x7c000000, + PPC_SRW = 0x7c000430, + PPC_SRD = 0x7c000000, + PPC_TLBSYNC = 0x7c000000, + PPC_LFSUX = 0x7c000000, + PPC_MFSR = 0x7c000000, + PPC_LSWI = 0x7c000000, + PPC_SYNC = 0x7c000000, + PPC_LFDX = 0x7c000000, + PPC_LFDUX = 0x7c000000, + PPC_MFSRIN = 0x7c000000, + PPC_STSWX = 0x7c000000, + PPC_STWBRX = 0x7c000000, + PPC_STFSX = 0x7c000000, + PPC_STFSUX = 0x7c000000, + PPC_STSWI = 0x7c000000, + PPC_STFDX = 0x7c000000, + PPC_DCBA = 0x7c000000, + PPC_STFDUX = 0x7c000000, + PPC_LHBRX = 0x7c000000, + PPC_SRAW = 0x7c000630, + PPC_SRAD = 0x7c000000, + PPC_SRAWI = 0x7c000000, + PPC_EIEIO = 0x7c000000, + PPC_STHBRX = 0x7c000000, + PPC_EXTSH = 0x7c000734, + PPC_EXTSB = 0x7c000774, + PPC_ICBI = 0x7c000000, +//------------ + PPC_STFIWX = 0x7c0007ae, + PPC_EXTSW = 0x7c000000, + PPC_DCBZ = 0x7c000000, + PPC_LWZ = 0x80000000, + PPC_LWZU = 0x84000000, + PPC_LBZ = 0x88000000, + PPC_LBZU = 0x8c000000, + PPC_STW = 0x90000000, + PPC_STWU = 0x94000000, + PPC_STB = 0x98000000, + PPC_STBU = 0x9c000000, + PPC_LHZ = 0xa0000000, + PPC_LHZU = 0xa4000000, + PPC_LHA = 0xa8000000, + PPC_LHAU = 0xac000000, + PPC_STH = 0xb0000000, + PPC_STHU = 0xb4000000, + PPC_LMW = 0xb8000000, + PPC_STMW = 0xbc000000, + PPC_LFS = 0xc0000000, + PPC_LFSU = 0xc4000000, + PPC_LFD = 0xc8000000, + PPC_LFDU = 0xcc000000, + PPC_STFS = 0xd0000000, + PPC_STFSU = 0xd4000000, + PPC_STFD = 0xd8000000, + PPC_STFDU = 0xdc000000, + PPC_LD = 0xe8000000, + PPC_LDU = 0xe8000001, + PPC_LWA = 0xe8000002, + PPC_FDIVS = 0xec000024, + PPC_FSUBS = 0xec000028, + PPC_FADDS = 0xec00002a, +//------------ + PPC_FSQRTS = 0xec000000, + PPC_FRES = 0xec000000, + PPC_FMULS = 0xec000032, + PPC_FMSUBS = 0xec000000, + PPC_FMADDS = 0xec000000, + PPC_FNMSUBS = 0xec000000, + PPC_FNMADDS = 0xec000000, + PPC_STD = 0xf8000000, + PPC_STDU = 0xf8000001, + PPC_FCMPU = 0xfc000000, + PPC_FRSP = 0xfc000018, + PPC_FCTIW = 0xfc000000, + PPC_FCTIWZ = 0xfc00001e, + PPC_FDIV = 0xfc000000, + PPC_FSUB = 0xfc000028, + PPC_FADD = 0xfc000000, + PPC_FSQRT = 0xfc000000, + PPC_FSEL = 0xfc000000, + PPC_FMUL = 0xfc000000, + PPC_FRSQRTE = 0xfc000000, + PPC_FMSUB = 0xfc000000, + PPC_FMADD = 0xfc000000, + PPC_FNMSUB = 0xfc000000, + PPC_FNMADD = 0xfc000000, + PPC_FCMPO = 0xfc000000, + PPC_MTFSB1 = 0xfc000000, + PPC_FNEG = 0xfc000050, + PPC_MCRFS = 0xfc000000, + PPC_MTFSB0 = 0xfc000000, + PPC_FMR = 0xfc000000, + PPC_MTFSFI = 0xfc000000, + PPC_FNABS = 0xfc000000, + PPC_FABS = 0xfc000000, +//------------ + PPC_MFFS = 0xfc000000, + PPC_MTFSF = 0xfc000000, + PPC_FCTID = 0xfc000000, + PPC_FCTIDZ = 0xfc000000, + PPC_FCFID = 0xfc000000 + +} ppcOpcodes_t; + + +// the newly generated code +static unsigned *buf; +static int compiledOfs; // in dwords + +// fromt the original bytecode +static byte *code; +static int pc; + +void AsmCall( void ); + +double itofConvert[2]; + +static int Constant4( void ) { + int v; + + v = code[pc] | (code[pc+1]<<8) | (code[pc+2]<<16) | (code[pc+3]<<24); + pc += 4; + return v; +} + +static int Constant1( void ) { + int v; + + v = code[pc]; + pc += 1; + return v; +} + +static void Emit4( int i ) { + buf[ compiledOfs ] = i; + compiledOfs++; +} + +static void Inst( int opcode, int destReg, int aReg, int bReg ) { + unsigned r; + + r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( bReg << 11 ) ; + buf[ compiledOfs ] = r; + compiledOfs++; +} + +static void Inst4( int opcode, int destReg, int aReg, int bReg, int cReg ) { + unsigned r; + + r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( bReg << 11 ) | ( cReg << 6 ); + buf[ compiledOfs ] = r; + compiledOfs++; +} + +static void InstImm( int opcode, int destReg, int aReg, int immediate ) { + unsigned r; + + if ( immediate > 32767 || immediate < -32768 ) { + Com_Error( ERR_FATAL, "VM_Compile: immediate value %i out of range, opcode %x,%d,%d", immediate, opcode, destReg, aReg ); + } + r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( immediate & 0xffff ); + buf[ compiledOfs ] = r; + compiledOfs++; +} + +static void InstImmU( int opcode, int destReg, int aReg, int immediate ) { + unsigned r; + + if ( immediate > 0xffff || immediate < 0 ) { + Com_Error( ERR_FATAL, "VM_Compile: immediate value %i out of range", immediate ); + } + r = opcode | ( destReg << 21 ) | ( aReg << 16 ) | ( immediate & 0xffff ); + buf[ compiledOfs ] = r; + compiledOfs++; +} + +static qboolean rtopped; +static int pop0, pop1, oc0, oc1; +static vm_t *tvm; +static int instruction; +static byte *jused; +static int pass; + +static void ltop() { + if (rtopped == qfalse) { + InstImm( PPC_LWZ, R_TOP, R_OPSTACK, 0 ); // get value from opstack + } +} + +static void ltopandsecond() { + if (pass>=0 && buf[compiledOfs-1] == (PPC_STWU | R_TOP<<21 | R_OPSTACK<<16 | 4 ) && jused[instruction]==0 ) { + compiledOfs--; + if (!pass) { + tvm->instructionPointers[instruction] = compiledOfs * 4; + } + InstImm( PPC_LWZ, R_SECOND, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -4 ); + } else if (pass>=0 && buf[compiledOfs-1] == (PPC_STW | R_TOP<<21 | R_OPSTACK<<16 | 0 ) && jused[instruction]==0 ) { + compiledOfs--; + if (!pass) { + tvm->instructionPointers[instruction] = compiledOfs * 4; + } + InstImm( PPC_LWZ, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -8 ); + } else { + ltop(); // get value from opstack + InstImm( PPC_LWZ, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -8 ); + } + rtopped = qfalse; +} + +// TJW: Unused +#if 0 +static void fltop() { + if (rtopped == qfalse) { + InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 ); // get value from opstack + } +} +#endif + +static void fltopandsecond() { + InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_LFS, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -8 ); + rtopped = qfalse; + return; +} + +/* +================= +VM_Compile +================= +*/ +void VM_Compile( vm_t *vm, vmHeader_t *header ) { + int op; + int maxLength; + int v; + int i; + + // set up the into-to-float variables + ((int *)itofConvert)[0] = 0x43300000; + ((int *)itofConvert)[1] = 0x80000000; + ((int *)itofConvert)[2] = 0x43300000; + + // allocate a very large temp buffer, we will shrink it later + maxLength = header->codeLength * 8; + buf = Z_Malloc( maxLength,qfalse ); + jused = Z_Malloc(header->instructionCount + 2,qfalse); + Com_Memset(jused, 0, header->instructionCount+2); + + // compile everything twice, so the second pass will have valid instruction + // pointers for branches + for ( pass = -1 ; pass < 2 ; pass++ ) { + + rtopped = qfalse; + // translate all instructions + pc = 0; + + pop0 = 343545; + pop1 = 2443545; + oc0 = -2343535; + oc1 = 24353454; + tvm = vm; + + code = (byte *)header + header->codeOffset; + compiledOfs = 0; +#ifndef __GNUC__ + // metrowerks seems to require this header in front of functions + Emit4( (int)(buf+2) ); + Emit4( 0 ); +#endif + + for ( instruction = 0 ; instruction < header->instructionCount ; instruction++ ) { + if ( compiledOfs*4 > maxLength - 16 ) { + Com_Error( ERR_DROP, "VM_Compile: maxLength exceeded" ); + } + + op = code[ pc ]; + if ( !pass ) { + vm->instructionPointers[ instruction ] = compiledOfs * 4; + } + pc++; + switch ( op ) { + case 0: + break; + case OP_BREAK: + InstImmU( PPC_ADDI, R_TOP, 0, 0 ); + InstImm( PPC_LWZ, R_TOP, R_TOP, 0 ); // *(int *)0 to crash to debugger + rtopped = qfalse; + break; + case OP_ENTER: + InstImm( PPC_ADDI, R_STACK, R_STACK, -Constant4() ); // sub R_STACK, R_STACK, imm + rtopped = qfalse; + break; + case OP_CONST: + v = Constant4(); + if (code[pc] == OP_LOAD4 || code[pc] == OP_LOAD2 || code[pc] == OP_LOAD1) { + v &= vm->dataMask; + } + if ( v < 32768 && v >= -32768 ) { + InstImmU( PPC_ADDI, R_TOP, 0, v & 0xffff ); + } else { + InstImmU( PPC_ADDIS, R_TOP, 0, (v >> 16)&0xffff ); + if ( v & 0xffff ) { + InstImmU( PPC_ORI, R_TOP, R_TOP, v & 0xffff ); + } + } + if (code[pc] == OP_LOAD4) { + Inst( PPC_LWZX, R_TOP, R_TOP, R_MEMBASE ); // load from memory base + pc++; + instruction++; + } else if (code[pc] == OP_LOAD2) { + Inst( PPC_LHZX, R_TOP, R_TOP, R_MEMBASE ); // load from memory base + pc++; + instruction++; + } else if (code[pc] == OP_LOAD1) { + Inst( PPC_LBZX, R_TOP, R_TOP, R_MEMBASE ); // load from memory base + pc++; + instruction++; + } + if (code[pc] == OP_STORE4) { + InstImm( PPC_LWZ, R_SECOND, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -4 ); + //Inst( PPC_AND, R_MEMMASK, R_SECOND, R_SECOND ); // mask it + Inst( PPC_STWX, R_TOP, R_SECOND, R_MEMBASE ); // store from memory base + pc++; + instruction++; + rtopped = qfalse; + break; + } else if (code[pc] == OP_STORE2) { + InstImm( PPC_LWZ, R_SECOND, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -4 ); + //Inst( PPC_AND, R_MEMMASK, R_SECOND, R_SECOND ); // mask it + Inst( PPC_STHX, R_TOP, R_SECOND, R_MEMBASE ); // store from memory base + pc++; + instruction++; + rtopped = qfalse; + break; + } else if (code[pc] == OP_STORE1) { + InstImm( PPC_LWZ, R_SECOND, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -4 ); + //Inst( PPC_AND, R_MEMMASK, R_SECOND, R_SECOND ); // mask it + Inst( PPC_STBX, R_TOP, R_SECOND, R_MEMBASE ); // store from memory base + pc++; + instruction++; + rtopped = qfalse; + break; + } + if (code[pc] == OP_JUMP) { + jused[v] = 1; + } + InstImm( PPC_STWU, R_TOP, R_OPSTACK, 4 ); + rtopped = qtrue; + break; + case OP_LOCAL: + oc0 = oc1; + oc1 = Constant4(); + if (code[pc] == OP_LOAD4 || code[pc] == OP_LOAD2 || code[pc] == OP_LOAD1) { + oc1 &= vm->dataMask; + } + InstImm( PPC_ADDI, R_TOP, R_STACK, oc1 ); + if (code[pc] == OP_LOAD4) { + Inst( PPC_LWZX, R_TOP, R_TOP, R_MEMBASE ); // load from memory base + pc++; + instruction++; + } else if (code[pc] == OP_LOAD2) { + Inst( PPC_LHZX, R_TOP, R_TOP, R_MEMBASE ); // load from memory base + pc++; + instruction++; + } else if (code[pc] == OP_LOAD1) { + Inst( PPC_LBZX, R_TOP, R_TOP, R_MEMBASE ); // load from memory base + pc++; + instruction++; + } + if (code[pc] == OP_STORE4) { + InstImm( PPC_LWZ, R_SECOND, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -4 ); + //Inst( PPC_AND, R_MEMMASK, R_SECOND, R_SECOND ); // mask it + Inst( PPC_STWX, R_TOP, R_SECOND, R_MEMBASE ); // store from memory base + pc++; + instruction++; + rtopped = qfalse; + break; + } else if (code[pc] == OP_STORE2) { + InstImm( PPC_LWZ, R_SECOND, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -4 ); + //Inst( PPC_AND, R_MEMMASK, R_SECOND, R_SECOND ); // mask it + Inst( PPC_STHX, R_TOP, R_SECOND, R_MEMBASE ); // store from memory base + pc++; + instruction++; + rtopped = qfalse; + break; + } else if (code[pc] == OP_STORE1) { + InstImm( PPC_LWZ, R_SECOND, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -4 ); + //Inst( PPC_AND, R_MEMMASK, R_SECOND, R_SECOND ); // mask it + Inst( PPC_STBX, R_TOP, R_SECOND, R_MEMBASE ); // store from memory base + pc++; + instruction++; + rtopped = qfalse; + break; + } + InstImm( PPC_STWU, R_TOP, R_OPSTACK, 4 ); + rtopped = qtrue; + break; + case OP_ARG: + ltop(); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -4 ); + InstImm( PPC_ADDI, R_EA, R_STACK, Constant1() ); // location to put it + Inst( PPC_STWX, R_TOP, R_EA, R_MEMBASE ); + rtopped = qfalse; + break; + case OP_CALL: + Inst( PPC_MFSPR, R_SECOND, 8, 0 ); // move from link register + InstImm( PPC_STWU, R_SECOND, R_REAL_STACK, -16 ); // save off the old return address + + Inst( PPC_MTSPR, R_ASMCALL, 9, 0 ); // move to count register + Inst( PPC_BCCTR | 1, 20, 0, 0 ); // jump and link to the count register + + InstImm( PPC_LWZ, R_SECOND, R_REAL_STACK, 0 ); // fetch the old return address + InstImm( PPC_ADDI, R_REAL_STACK, R_REAL_STACK, 16 ); + Inst( PPC_MTSPR, R_SECOND, 8, 0 ); // move to link register + rtopped = qfalse; + break; + case OP_PUSH: + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, 4 ); + rtopped = qfalse; + break; + case OP_POP: + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -4 ); + rtopped = qfalse; + break; + case OP_LEAVE: + InstImm( PPC_ADDI, R_STACK, R_STACK, Constant4() ); // add R_STACK, R_STACK, imm + Inst( PPC_BCLR, 20, 0, 0 ); // branch unconditionally to link register + rtopped = qfalse; + break; + case OP_LOAD4: + ltop(); // get value from opstack + //Inst( PPC_AND, R_MEMMASK, R_TOP, R_TOP ); // mask it + Inst( PPC_LWZX, R_TOP, R_TOP, R_MEMBASE ); // load from memory base + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); + rtopped = qtrue; + break; + case OP_LOAD2: + ltop(); // get value from opstack + //Inst( PPC_AND, R_MEMMASK, R_TOP, R_TOP ); // mask it + Inst( PPC_LHZX, R_TOP, R_TOP, R_MEMBASE ); // load from memory base + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); + rtopped = qtrue; + break; + case OP_LOAD1: + ltop(); // get value from opstack + //Inst( PPC_AND, R_MEMMASK, R_TOP, R_TOP ); // mask it + Inst( PPC_LBZX, R_TOP, R_TOP, R_MEMBASE ); // load from memory base + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); + rtopped = qtrue; + break; + case OP_STORE4: + ltopandsecond(); // get value from opstack + //Inst( PPC_AND, R_MEMMASK, R_SECOND, R_SECOND ); // mask it + Inst( PPC_STWX, R_TOP, R_SECOND, R_MEMBASE ); // store from memory base + rtopped = qfalse; + break; + case OP_STORE2: + ltopandsecond(); // get value from opstack + //Inst( PPC_AND, R_MEMMASK, R_SECOND, R_SECOND ); // mask it + Inst( PPC_STHX, R_TOP, R_SECOND, R_MEMBASE ); // store from memory base + rtopped = qfalse; + break; + case OP_STORE1: + ltopandsecond(); // get value from opstack + //Inst( PPC_AND, R_MEMMASK, R_SECOND, R_SECOND ); // mask it + Inst( PPC_STBX, R_TOP, R_SECOND, R_MEMBASE ); // store from memory base + rtopped = qfalse; + break; + + case OP_EQ: + ltopandsecond(); // get value from opstack + Inst( PPC_CMP, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + InstImm( PPC_BC, 4, 2, 8 ); + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + Emit4(PPC_B | (v&0x3ffffff) ); + rtopped = qfalse; + break; + case OP_NE: + ltopandsecond(); // get value from opstack + Inst( PPC_CMP, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + InstImm( PPC_BC, 12, 2, 8 ); + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + Emit4(PPC_B | (unsigned int)(v&0x3ffffff) ); +// InstImm( PPC_BC, 4, 2, v ); + + rtopped = qfalse; + break; + case OP_LTI: + ltopandsecond(); // get value from opstack + Inst( PPC_CMP, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + InstImm( PPC_BC, 4, 0, 8 ); + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + Emit4(PPC_B | (unsigned int)(v&0x3ffffff) ); +// InstImm( PPC_BC, 12, 0, v ); + rtopped = qfalse; + break; + case OP_LEI: + ltopandsecond(); // get value from opstack + Inst( PPC_CMP, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + InstImm( PPC_BC, 12, 1, 8 ); + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + Emit4(PPC_B | (unsigned int)(v&0x3ffffff) ); +// InstImm( PPC_BC, 4, 1, v ); + rtopped = qfalse; + break; + case OP_GTI: + ltopandsecond(); // get value from opstack + Inst( PPC_CMP, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + InstImm( PPC_BC, 4, 1, 8 ); + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + Emit4(PPC_B | (unsigned int)(v&0x3ffffff) ); +// InstImm( PPC_BC, 12, 1, v ); + rtopped = qfalse; + break; + case OP_GEI: + ltopandsecond(); // get value from opstack + Inst( PPC_CMP, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 4, 0, v ); + rtopped = qfalse; + break; + case OP_LTU: + ltopandsecond(); // get value from opstack + Inst( PPC_CMPL, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 12, 0, v ); + rtopped = qfalse; + break; + case OP_LEU: + ltopandsecond(); // get value from opstack + Inst( PPC_CMPL, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 4, 1, v ); + rtopped = qfalse; + break; + case OP_GTU: + ltopandsecond(); // get value from opstack + Inst( PPC_CMPL, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 12, 1, v ); + rtopped = qfalse; + break; + case OP_GEU: + ltopandsecond(); // get value from opstack + Inst( PPC_CMPL, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 4, 0, v ); + rtopped = qfalse; + break; + + case OP_EQF: + fltopandsecond(); // get value from opstack + Inst( PPC_FCMPU, 0, R_TOP, R_SECOND ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 12, 2, v ); + rtopped = qfalse; + break; + case OP_NEF: + fltopandsecond(); // get value from opstack + Inst( PPC_FCMPU, 0, R_TOP, R_SECOND ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 4, 2, v ); + rtopped = qfalse; + break; + case OP_LTF: + fltopandsecond(); // get value from opstack + Inst( PPC_FCMPU, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 12, 0, v ); + rtopped = qfalse; + break; + case OP_LEF: + fltopandsecond(); // get value from opstack + Inst( PPC_FCMPU, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 4, 1, v ); + rtopped = qfalse; + break; + case OP_GTF: + fltopandsecond(); // get value from opstack + Inst( PPC_FCMPU, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 12, 1, v ); + rtopped = qfalse; + break; + case OP_GEF: + fltopandsecond(); // get value from opstack + Inst( PPC_FCMPU, 0, R_SECOND, R_TOP ); + i = Constant4(); + jused[i] = 1; + if ( pass==1 ) { + v = vm->instructionPointers[ i ] - (int)&buf[compiledOfs]; + } else { + v = 0; + } + InstImm( PPC_BC, 4, 0, v ); + rtopped = qfalse; + break; + + case OP_NEGI: + ltop(); // get value from opstack + InstImm( PPC_SUBFIC, R_TOP, R_TOP, 0 ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_ADD: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_ADD, R_TOP, R_TOP, R_SECOND ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_SUB: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_SUBF, R_TOP, R_TOP, R_SECOND ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_DIVI: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_DIVW, R_TOP, R_SECOND, R_TOP ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_DIVU: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_DIVWU, R_TOP, R_SECOND, R_TOP ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_MODI: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_DIVW, R_EA, R_SECOND, R_TOP ); + Inst( PPC_MULLW, R_EA, R_TOP, R_EA ); + Inst( PPC_SUBF, R_TOP, R_EA, R_SECOND ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_MODU: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_DIVWU, R_EA, R_SECOND, R_TOP ); + Inst( PPC_MULLW, R_EA, R_TOP, R_EA ); + Inst( PPC_SUBF, R_TOP, R_EA, R_SECOND ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_MULI: + case OP_MULU: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_MULLW, R_TOP, R_SECOND, R_TOP ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_BAND: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_AND, R_SECOND, R_TOP, R_TOP ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_BOR: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_OR, R_SECOND, R_TOP, R_TOP ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_BXOR: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_XOR, R_SECOND, R_TOP, R_TOP ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_BCOM: + ltop(); // get value from opstack + Inst( PPC_NOR, R_TOP, R_TOP, R_TOP ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_LSH: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_SLW, R_SECOND, R_TOP, R_TOP ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_RSHI: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_SRAW, R_SECOND, R_TOP, R_TOP ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + case OP_RSHU: + ltop(); // get value from opstack + InstImm( PPC_LWZU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_SRW, R_SECOND, R_TOP, R_TOP ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qtrue; + break; + + case OP_NEGF: + InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 ); // get value from opstack + Inst( PPC_FNEG, R_TOP, 0, R_TOP ); + InstImm( PPC_STFS, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qfalse; + break; + case OP_ADDF: + InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_LFSU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_FADDS, R_TOP, R_SECOND, R_TOP ); + InstImm( PPC_STFS, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qfalse; + break; + case OP_SUBF: + InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_LFSU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_FSUBS, R_TOP, R_SECOND, R_TOP ); + InstImm( PPC_STFS, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qfalse; + break; + case OP_DIVF: + InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_LFSU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst( PPC_FDIVS, R_TOP, R_SECOND, R_TOP ); + InstImm( PPC_STFS, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qfalse; + break; + case OP_MULF: + InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 ); // get value from opstack + InstImm( PPC_LFSU, R_SECOND, R_OPSTACK, -4 ); // get value from opstack + Inst4( PPC_FMULS, R_TOP, R_SECOND, 0, R_TOP ); + InstImm( PPC_STFS, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qfalse; + break; + + case OP_CVIF: + v = (int)&itofConvert; + InstImmU( PPC_ADDIS, R_EA, 0, (v >> 16)&0xffff ); + InstImmU( PPC_ORI, R_EA, R_EA, v & 0xffff ); + InstImm( PPC_LWZ, R_TOP, R_OPSTACK, 0 ); // get value from opstack + InstImmU( PPC_XORIS, R_TOP, R_TOP, 0x8000 ); + InstImm( PPC_STW, R_TOP, R_EA, 12 ); + InstImm( PPC_LFD, R_TOP, R_EA, 0 ); + InstImm( PPC_LFD, R_SECOND, R_EA, 8 ); + Inst( PPC_FSUB, R_TOP, R_SECOND, R_TOP ); + // Inst( PPC_FRSP, R_TOP, 0, R_TOP ); + InstImm( PPC_STFS, R_TOP, R_OPSTACK, 0 ); // save value to opstack + rtopped = qfalse; + break; + case OP_CVFI: + InstImm( PPC_LFS, R_TOP, R_OPSTACK, 0 ); // get value from opstack + Inst( PPC_FCTIWZ, R_TOP, 0, R_TOP ); + Inst( PPC_STFIWX, R_TOP, 0, R_OPSTACK ); // save value to opstack + rtopped = qfalse; + break; + case OP_SEX8: + ltop(); // get value from opstack + Inst( PPC_EXTSB, R_TOP, R_TOP, 0 ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); + rtopped = qtrue; + break; + case OP_SEX16: + ltop(); // get value from opstack + Inst( PPC_EXTSH, R_TOP, R_TOP, 0 ); + InstImm( PPC_STW, R_TOP, R_OPSTACK, 0 ); + rtopped = qtrue; + break; + + case OP_BLOCK_COPY: + v = Constant4() >> 2; + ltop(); // source + InstImm( PPC_LWZ, R_SECOND, R_OPSTACK, -4 ); // dest + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -8 ); + InstImmU( PPC_ADDI, R_EA, 0, v ); // count + // FIXME: range check + Inst( PPC_MTSPR, R_EA, 9, 0 ); // move to count register + + Inst( PPC_ADD, R_TOP, R_TOP, R_MEMBASE ); + InstImm( PPC_ADDI, R_TOP, R_TOP, -4 ); + Inst( PPC_ADD, R_SECOND, R_SECOND, R_MEMBASE ); + InstImm( PPC_ADDI, R_SECOND, R_SECOND, -4 ); + + InstImm( PPC_LWZU, R_EA, R_TOP, 4 ); // source + InstImm( PPC_STWU, R_EA, R_SECOND, 4 ); // dest + Inst( PPC_BC | 0xfff8 , 16, 0, 0 ); // loop + rtopped = qfalse; + break; + + case OP_JUMP: + ltop(); // get value from opstack + InstImm( PPC_ADDI, R_OPSTACK, R_OPSTACK, -4 ); + Inst( PPC_RLWINM | ( 29 << 1 ), R_TOP, R_TOP, 2 ); + // FIXME: range check + Inst( PPC_LWZX, R_TOP, R_TOP, R_INSTRUCTIONS ); + Inst( PPC_MTSPR, R_TOP, 9, 0 ); // move to count register + Inst( PPC_BCCTR, 20, 0, 0 ); // jump to the count register + rtopped = qfalse; + break; + default: + Com_Error( ERR_DROP, "VM_CompilePPC: bad opcode %i at instruction %i, offset %i", op, instruction, pc ); + } + pop0 = pop1; + pop1 = op; + } + + Com_Printf( "VM file %s pass %d compiled to %i bytes of code\n", vm->name, (pass+1), compiledOfs*4 ); + + if ( pass == 0 ) { + // copy to an exact size buffer on the hunk + vm->codeLength = compiledOfs * 4; + vm->codeBase = Hunk_Alloc( vm->codeLength, h_low ); + Com_Memcpy( vm->codeBase, buf, vm->codeLength ); + Z_Free( buf ); + + // offset all the instruction pointers for the new location + for ( i = 0 ; i < header->instructionCount ; i++ ) { + vm->instructionPointers[i] += (int)vm->codeBase; + } + + // go back over it in place now to fixup reletive jump targets + buf = (unsigned *)vm->codeBase; + } + } + Z_Free( jused ); +} + +/* +============== +VM_CallCompiled + +This function is called directly by the generated code +============== +*/ +int VM_CallCompiled( vm_t *vm, int *args ) { + int stack[1024]; + int programStack; + int stackOnEntry; + byte *image; + + currentVM = vm; + + // interpret the code + vm->currentlyInterpreting = qtrue; + + // we might be called recursively, so this might not be the very top + programStack = vm->programStack; + stackOnEntry = programStack; + image = vm->dataBase; + + // set up the stack frame + programStack -= 48; + + *(int *)&image[ programStack + 44] = args[9]; + *(int *)&image[ programStack + 40] = args[8]; + *(int *)&image[ programStack + 36] = args[7]; + *(int *)&image[ programStack + 32] = args[6]; + *(int *)&image[ programStack + 28] = args[5]; + *(int *)&image[ programStack + 24] = args[4]; + *(int *)&image[ programStack + 20] = args[3]; + *(int *)&image[ programStack + 16] = args[2]; + *(int *)&image[ programStack + 12] = args[1]; + *(int *)&image[ programStack + 8 ] = args[0]; + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + // off we go into generated code... + // the PPC calling standard says the parms will all go into R3 - R11, so + // no special asm code is needed here +#ifdef __GNUC__ + ((void(*)(int, int, int, int, int, int, int, int))(vm->codeBase))( + programStack, (int)&stack, + (int)image, vm->dataMask, (int)&AsmCall, + (int)vm->instructionPointers, vm->instructionPointersLength, + (int)vm ); +#else + ((void(*)(int, int, int, int, int, int, int, int))(vm->codeBase))( + programStack, (int)&stack, + (int)image, vm->dataMask, *(int *)&AsmCall /* skip function pointer header */, + (int)vm->instructionPointers, vm->instructionPointersLength, + (int)vm ); +#endif + vm->programStack = stackOnEntry; + + vm->currentlyInterpreting = qfalse; + + return stack[1]; +} + + +/* +================== +AsmCall + +Put this at end of file because gcc messes up debug line numbers +================== +*/ +#ifdef __GNUC__ + +void AsmCall( void ) { +asm (" + // pop off the destination instruction + lwz r12,0(r4) // RG_TOP, 0(RG_OPSTACK) + addi r4,r4,-4 // RG_OPSTACK, RG_OPSTACK, -4 + + // see if it is a system trap + cmpwi r12,0 // RG_TOP, 0 + bc 12,0, systemTrap + + // calling another VM function, so lookup in instructionPointers + slwi r12,r12,2 // RG_TOP,RG_TOP,2 + // FIXME: range check + lwzx r12, r8, r12 // RG_TOP, RG_INSTRUCTIONS(RG_TOP) + mtctr r12 // RG_TOP +"); + + +#if defined(MACOS_X) && defined(__OPTIMIZE__) + // On Mac OS X, gcc doesn't push a frame when we are optimized, so trying to tear it down results in grave disorder. +#warning Mac OS X optimization on, not popping GCC AsmCall frame +#else + // Mac OS X Server and unoptimized compiles include a GCC AsmCall frame + asm (" + lwz r1,0(r1) // pop off the GCC AsmCall frame + lmw r30,-8(r1) +"); +#endif + +asm (" + bcctr 20,0 // when it hits a leave, it will branch to the current link register + + // calling a system trap +systemTrap: + // convert to positive system call number + subfic r12,r12,-1 + + // save all our registers, including the current link register + mflr r13 // RG_SECOND // copy off our link register + addi r1,r1,-92 // required 24 byets of linkage, 32 bytes of parameter, plus our saves + stw r3,56(r1) // RG_STACK, -36(REAL_STACK) + stw r4,60(r1) // RG_OPSTACK, 4(RG_REAL_STACK) + stw r5,64(r1) // RG_MEMBASE, 8(RG_REAL_STACK) + stw r6,68(r1) // RG_MEMMASK, 12(RG_REAL_STACK) + stw r7,72(r1) // RG_ASMCALL, 16(RG_REAL_STACK) + stw r8,76(r1) // RG_INSTRUCTIONS, 20(RG_REAL_STACK) + stw r9,80(r1) // RG_NUM_INSTRUCTIONS, 24(RG_REAL_STACK) + stw r10,84(r1) // RG_VM, 28(RG_REAL_STACK) + stw r13,88(r1) // RG_SECOND, 32(RG_REAL_STACK) // link register + + // save the vm stack position to allow recursive VM entry + addi r13,r3,-4 // RG_TOP, RG_STACK, -4 + stw r13,0(r10) //RG_TOP, VM_OFFSET_PROGRAM_STACK(RG_VM) + + // save the system call number as the 0th parameter + add r3,r3,r5 // r3, RG_STACK, RG_MEMBASE // r3 is the first parameter to vm->systemCalls + stwu r12,4(r3) // RG_TOP, 4(r3) + + // make the system call with the address of all the VM parms as a parameter + // vm->systemCalls( &parms ) + lwz r12,4(r10) // RG_TOP, VM_OFFSET_SYSTEM_CALL(RG_VM) + mtctr r12 // RG_TOP + bcctrl 20,0 + mr r12,r3 // RG_TOP, r3 + + // pop our saved registers + lwz r3,56(r1) // RG_STACK, 0(RG_REAL_STACK) + lwz r4,60(r1) // RG_OPSTACK, 4(RG_REAL_STACK) + lwz r5,64(r1) // RG_MEMBASE, 8(RG_REAL_STACK) + lwz r6,68(r1) // RG_MEMMASK, 12(RG_REAL_STACK) + lwz r7,72(r1) // RG_ASMCALL, 16(RG_REAL_STACK) + lwz r8,76(r1) // RG_INSTRUCTIONS, 20(RG_REAL_STACK) + lwz r9,80(r1) // RG_NUM_INSTRUCTIONS, 24(RG_REAL_STACK) + lwz r10,84(r1) // RG_VM, 28(RG_REAL_STACK) + lwz r13,88(r1) // RG_SECOND, 32(RG_REAL_STACK) + addi r1,r1,92 // RG_REAL_STACK, RG_REAL_STACK, 36 + + // restore the old link register + mtlr r13 // RG_SECOND + + // save off the return value + stwu r12,4(r4) // RG_TOP, 0(RG_OPSTACK) + + // GCC adds its own prolog / epliog code +" ); +} + +#endif diff --git a/codemp/qcommon/vm_x86.cpp b/codemp/qcommon/vm_x86.cpp new file mode 100644 index 0000000..41e497c --- /dev/null +++ b/codemp/qcommon/vm_x86.cpp @@ -0,0 +1,1166 @@ +// vm_x86.c -- load time compiler and execution environment for x86 +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "vm_local.h" + +#ifdef __FreeBSD__ // rb0101023 +#include +#endif + +#ifndef _WIN32 +#include // for PROT_ stuff +#endif + +/* + + eax scratch + ebx scratch + ecx scratch (required for shifts) + edx scratch (required for divisions) + esi program stack + edi opstack + +*/ + +// TTimo: initialised the statics, this fixes a crash when entering a compiled VM +static byte *buf = NULL; +static byte *jused = NULL; +static int compiledOfs = 0; +static byte *code = NULL; +static int pc = 0; + +static int *instructionPointers = NULL; + +//#undef FTOL_PTR // bk001213 +#define FTOL_PTR + +#ifdef _WIN32 + +#if defined( FTOL_PTR ) +extern "C" int _ftol(float); +static int ftolPtr = (int)_ftol; +#endif + +void AsmCall( void ); +static int asmCallPtr = (int)AsmCall; + +#else // _WIN32 + +#if defined( FTOL_PTR ) +// bk001213 - BEWARE: does not work! UI menu etc. broken - stack! +// bk001119 - added: int gftol( float x ) { return (int)x; } + +extern "C" { +int qftol( void ); // bk001213 - label, see unix/ftol.nasm +int qftol027F( void ); // bk001215 - fixed FPU control variants +int qftol037F( void ); +int qftol0E7F( void ); // bk010102 - fixed bogus bits (duh) +int qftol0F7F( void ); +} + +static int ftolPtr = (int)qftol0F7F; +#endif // FTOL_PTR + +extern "C" void doAsmCall( void ); +static int asmCallPtr = (int)doAsmCall; +#endif // !_WIN32 + + +static int callMask = 0; // bk001213 - init + +static int instruction, pass, lastConst; +static int oc0, oc1, pop0, pop1; + +typedef enum +{ + LAST_COMMAND_NONE = 0, + LAST_COMMAND_MOV_EDI_EAX, + LAST_COMMAND_SUB_DI_4, + LAST_COMMAND_SUB_DI_8, +} ELastCommand; + +static ELastCommand LastCommand; + + /* +================= +AsmCall +================= +*/ +#ifdef _WIN32 +__declspec( naked ) void AsmCall( void ) { +int programStack; +int *opStack; +int syscallNum; +vm_t* savedVM; + +__asm { + mov eax, dword ptr [edi] + sub edi, 4 + or eax,eax + jl systemCall + // calling another vm function + shl eax,2 + add eax, dword ptr [instructionPointers] + call dword ptr [eax] + mov eax, dword ptr [edi] + and eax, [callMask] + ret +systemCall: + + // convert negative num to system call number + // and store right before the first arg + neg eax + dec eax + + push ebp + mov ebp, esp + sub esp, __LOCAL_SIZE + + mov dword ptr syscallNum, eax // so C code can get at it + mov dword ptr programStack, esi // so C code can get at it + mov dword ptr opStack, edi + + push ecx + push esi // we may call recursively, so the + push edi // statics aren't guaranteed to be around +} + + savedVM = currentVM; + + // save the stack to allow recursive VM entry + currentVM->programStack = programStack - 4; + *(int *)((byte *)currentVM->dataBase + programStack + 4) = syscallNum; +//VM_LogSyscalls( (int *)((byte *)currentVM->dataBase + programStack + 4) ); + *(opStack+1) = currentVM->systemCall( (int *)((byte *)currentVM->dataBase + programStack + 4) ); + + currentVM = savedVM; + +_asm { + pop edi + pop esi + pop ecx + add edi, 4 // we added the return value + + mov esp, ebp + pop ebp + + ret +} + +} + +#else //!_WIN32 + +static int callProgramStack; +static int *callOpStack; +static int callSyscallNum; + +extern "C" void callAsmCall(void) { + // save the stack to allow recursive VM entry + currentVM->programStack = callProgramStack - 4; + *(int *)((byte *)currentVM->dataBase + callProgramStack + 4) = callSyscallNum; +//VM_LogSyscalls( (int *)((byte *)currentVM->dataBase + programStack + 4) ); + *(callOpStack+1) = currentVM->systemCall( (int *)((byte *)currentVM->dataBase + callProgramStack + 4) ); +} + +void AsmCall( void ) { + __asm__("doAsmCall: \n\t" \ + " movl (%%edi),%%eax \n\t" \ + " subl $4,%%edi \n\t" \ + " orl %%eax,%%eax \n\t" \ + " jl systemCall \n\t" \ + " shll $2,%%eax \n\t" \ + " addl %3,%%eax \n\t" \ + " call *(%%eax) \n\t" \ + " movl (%%edi),%%eax \n\t" \ + " andl callMask, %%eax \n\t" \ + " jmp doret \n\t" \ + "systemCall: \n\t" \ + " negl %%eax \n\t" \ + " decl %%eax \n\t" \ + " movl %%eax,%0 \n\t" \ + " movl %%esi,%1 \n\t" \ + " movl %%edi,%2 \n\t" \ + " pushl %%ecx \n\t" \ + " pushl %%esi \n\t" \ + " pushl %%edi \n\t" \ + " call callAsmCall \n\t" \ + " popl %%edi \n\t" \ + " popl %%esi \n\t" \ + " popl %%ecx \n\t" \ + " addl $4,%%edi \n\t" \ + "doret: \n\t" \ + " ret \n\t" \ + : "=rm" (callSyscallNum), "=rm" (callProgramStack), "=rm" (callOpStack) \ + : "rm" (instructionPointers) \ + : "ax", "di", "si", "cx" \ + ); +} +#endif + +static int Constant4( void ) { + int v; + + v = code[pc] | (code[pc+1]<<8) | (code[pc+2]<<16) | (code[pc+3]<<24); + pc += 4; + return v; +} + +static int Constant1( void ) { + int v; + + v = code[pc]; + pc += 1; + return v; +} + +static void Emit1( int v ) +{ + buf[ compiledOfs ] = v; + compiledOfs++; + + LastCommand = LAST_COMMAND_NONE; +} + +#if 0 +static void Emit2( int v ) { + Emit1( v & 255 ); + Emit1( ( v >> 8 ) & 255 ); +} +#endif + +static void Emit4( int v ) { + Emit1( v & 255 ); + Emit1( ( v >> 8 ) & 255 ); + Emit1( ( v >> 16 ) & 255 ); + Emit1( ( v >> 24 ) & 255 ); +} + +static int Hex( int c ) { + if ( c >= 'a' && c <= 'f' ) { + return 10 + c - 'a'; + } + if ( c >= 'A' && c <= 'F' ) { + return 10 + c - 'A'; + } + if ( c >= '0' && c <= '9' ) { + return c - '0'; + } + + Com_Error( ERR_DROP, "Hex: bad char '%c'", c ); + + return 0; +} +static void EmitString( const char *string ) { + int c1, c2; + int v; + + while ( 1 ) { + c1 = string[0]; + c2 = string[1]; + + v = ( Hex( c1 ) << 4 ) | Hex( c2 ); + Emit1( v ); + + if ( !string[2] ) { + break; + } + string += 3; + } +} + + + +static void EmitCommand(ELastCommand command) +{ + switch(command) + { + case LAST_COMMAND_MOV_EDI_EAX: + EmitString( "89 07" ); // mov dword ptr [edi], eax + break; + + case LAST_COMMAND_SUB_DI_4: + EmitString( "83 EF 04" ); // sub edi, 4 + break; + + case LAST_COMMAND_SUB_DI_8: + EmitString( "83 EF 08" ); // sub edi, 8 + break; + } + LastCommand = command; +} + +static void EmitAddEDI4(vm_t *vm) { + if (LastCommand == LAST_COMMAND_SUB_DI_4 && jused[instruction-1] == 0) + { // sub di,4 + compiledOfs -= 3; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + return; + } + if (LastCommand == LAST_COMMAND_SUB_DI_8 && jused[instruction-1] == 0) + { // sub di,8 + compiledOfs -= 3; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + EmitString( "83 EF 04" ); // sub edi,4 + return; + } + EmitString( "83 C7 04" ); // add edi,4 +} + +static void EmitMovEAXEDI(vm_t *vm) { + if (LastCommand == LAST_COMMAND_MOV_EDI_EAX) + { // mov [edi], eax + compiledOfs -= 2; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + return; + } + if (pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU || + pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1 ) + { + return; + } + if (pop1 == OP_CONST && buf[compiledOfs-6] == 0xC7 && buf[compiledOfs-5] == 0x07 ) + { // mov edi, 0x123456 + compiledOfs -= 6; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + EmitString( "B8" ); // mov eax, 0x12345678 + Emit4( lastConst ); + return; + } + EmitString( "8B 07" ); // mov eax, dword ptr [edi] +} + +qboolean EmitMovEBXEDI(vm_t *vm, int andit) { + if (LastCommand == LAST_COMMAND_MOV_EDI_EAX) + { // mov [edi], eax + compiledOfs -= 2; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + EmitString( "8B D8"); // mov bx, eax + return qfalse; + } + if (pop1 == OP_DIVI || pop1 == OP_DIVU || pop1 == OP_MULI || pop1 == OP_MULU || + pop1 == OP_STORE4 || pop1 == OP_STORE2 || pop1 == OP_STORE1 ) + { + EmitString( "8B D8"); // mov bx, eax + return qfalse; + } + if (pop1 == OP_CONST && buf[compiledOfs-6] == 0xC7 && buf[compiledOfs-5] == 0x07 ) + { // mov edi, 0x123456 + compiledOfs -= 6; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + EmitString( "BB" ); // mov ebx, 0x12345678 + if (andit) { + Emit4( lastConst & andit ); + } else { + Emit4( lastConst ); + } + return qtrue; + } + + EmitString( "8B 1F" ); // mov ebx, dword ptr [edi] + return qfalse; +} + +/* +================= +VM_Compile +================= +*/ +void VM_Compile( vm_t *vm, vmHeader_t *header ) { + int op; + int maxLength; + int v; + int i; + qboolean opt; + + // allocate a very large temp buffer, we will shrink it later + maxLength = header->codeLength * 8; + buf = (unsigned char *)Z_Malloc( maxLength, TAG_VM, qtrue ); + jused = (unsigned char *)Z_Malloc(header->instructionCount + 2, TAG_VM, qtrue ); + + Com_Memset(jused, 0, header->instructionCount+2); + + for(pass=0;pass<2;pass++) { + oc0 = -23423; + oc1 = -234354; + pop0 = -43435; + pop1 = -545455; + + // translate all instructions + pc = 0; + instruction = 0; + code = (byte *)header + header->codeOffset; + compiledOfs = 0; + + LastCommand = LAST_COMMAND_NONE; + + while ( instruction < header->instructionCount ) { + if ( compiledOfs > maxLength - 16 ) { + Com_Error( ERR_FATAL, "VM_CompileX86: maxLength exceeded" ); + } + + vm->instructionPointers[ instruction ] = compiledOfs; + instruction++; + + if ( pc > header->codeLength ) { + Com_Error( ERR_FATAL, "VM_CompileX86: pc > header->codeLength" ); + } + + op = code[ pc ]; + pc++; + switch ( op ) { + case 0: + break; + case OP_BREAK: + EmitString( "CC" ); // int 3 + break; + case OP_ENTER: + EmitString( "81 EE" ); // sub esi, 0x12345678 + Emit4( Constant4() ); + break; + case OP_CONST: + if (code[pc+4] == OP_LOAD4) { + EmitAddEDI4(vm); + EmitString( "BB" ); // mov ebx, 0x12345678 + Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase); + EmitString( "8B 03" ); // mov eax, dword ptr [ebx] + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + pc++; // OP_LOAD4 + instruction += 1; + break; + } + if (code[pc+4] == OP_LOAD2) { + EmitAddEDI4(vm); + EmitString( "BB" ); // mov ebx, 0x12345678 + Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase); + EmitString( "0F B7 03" ); // movzx eax, word ptr [ebx] + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + pc++; // OP_LOAD4 + instruction += 1; + break; + } + if (code[pc+4] == OP_LOAD1) { + EmitAddEDI4(vm); + EmitString( "BB" ); // mov ebx, 0x12345678 + Emit4( (Constant4()&vm->dataMask) + (int)vm->dataBase); + EmitString( "0F B6 03" ); // movzx eax, byte ptr [ebx] + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + pc++; // OP_LOAD4 + instruction += 1; + break; + } + if (code[pc+4] == OP_STORE4) { + opt = EmitMovEBXEDI(vm, (vm->dataMask & ~3)); + EmitString( "B8" ); // mov eax, 0x12345678 + Emit4( Constant4() ); +// if (!opt) { +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask & ~3 ); +// } + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + pc++; // OP_STORE4 + instruction += 1; + break; + } + if (code[pc+4] == OP_STORE2) { + opt = EmitMovEBXEDI(vm, (vm->dataMask & ~1)); + EmitString( "B8" ); // mov eax, 0x12345678 + Emit4( Constant4() ); +// if (!opt) { +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask & ~1 ); +// } + EmitString( "66 89 83" ); // mov word ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + pc++; // OP_STORE4 + instruction += 1; + break; + } + if (code[pc+4] == OP_STORE1) { + opt = EmitMovEBXEDI(vm, vm->dataMask); + EmitString( "B8" ); // mov eax, 0x12345678 + Emit4( Constant4() ); +// if (!opt) { +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask ); +// } + EmitString( "88 83" ); // mov byte ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + pc++; // OP_STORE4 + instruction += 1; + break; + } + if (code[pc+4] == OP_ADD) { + EmitString( "81 07" ); // add dword ptr [edi], 0x1234567 + Emit4( Constant4() ); + pc++; // OP_ADD + instruction += 1; + break; + } + if (code[pc+4] == OP_SUB) { + EmitString( "81 2F" ); // sub dword ptr [edi], 0x1234567 + Emit4( Constant4() ); + pc++; // OP_ADD + instruction += 1; + break; + } + EmitAddEDI4(vm); + EmitString( "C7 07" ); // mov dword ptr [edi], 0x12345678 + lastConst = Constant4(); + Emit4( lastConst ); + if (code[pc] == OP_JUMP) { + jused[lastConst] = 1; + } + break; + case OP_LOCAL: + EmitAddEDI4(vm); + EmitString( "8D 86" ); // lea eax, [0x12345678 + esi] + oc0 = oc1; + oc1 = Constant4(); + Emit4( oc1 ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + case OP_ARG: + EmitMovEAXEDI(vm); // mov eax,dword ptr [edi] + EmitString( "89 86" ); // mov dword ptr [esi+database],eax + // FIXME: range check + Emit4( Constant1() + (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_CALL: + EmitString( "C7 86" ); // mov dword ptr [esi+database],0x12345678 + Emit4( (int)vm->dataBase ); + Emit4( pc ); + EmitString( "FF 15" ); // call asmCallPtr + Emit4( (int)&asmCallPtr ); + break; + case OP_PUSH: + EmitAddEDI4(vm); + break; + case OP_POP: + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_LEAVE: + v = Constant4(); + EmitString( "81 C6" ); // add esi, 0x12345678 + Emit4( v ); + EmitString( "C3" ); // ret + break; + case OP_LOAD4: + if (code[pc] == OP_CONST && code[pc+5] == OP_ADD && code[pc+6] == OP_STORE4) { + if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + compiledOfs -= 11; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + } + pc++; // OP_CONST + v = Constant4(); + EmitMovEBXEDI(vm, vm->dataMask); + if (v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + EmitString( "FF 83"); // inc dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + } else { + EmitString( "8B 83" ); // mov eax, dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitString( "05" ); // add eax, const + Emit4( v ); + if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + } else { + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "8B 1F" ); // mov ebx, dword ptr [edi] + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + } + } + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + pc++; // OP_ADD + pc++; // OP_STORE + instruction += 3; + break; + } + + if (code[pc] == OP_CONST && code[pc+5] == OP_SUB && code[pc+6] == OP_STORE4) { + if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + compiledOfs -= 11; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + } + EmitMovEBXEDI(vm, vm->dataMask); + EmitString( "8B 83" ); // mov eax, dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + pc++; // OP_CONST + v = Constant4(); + if (v == 1 && oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + EmitString( "FF 8B"); // dec dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + } else { + EmitString( "2D" ); // sub eax, const + Emit4( v ); + if (oc0 == oc1 && pop0 == OP_LOCAL && pop1 == OP_LOCAL) { + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + } else { + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "8B 1F" ); // mov ebx, dword ptr [edi] + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + } + } + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + pc++; // OP_SUB + pc++; // OP_STORE + instruction += 3; + break; + } + + if (buf[compiledOfs-2] == 0x89 && buf[compiledOfs-1] == 0x07) { + compiledOfs -= 2; + vm->instructionPointers[ instruction-1 ] = compiledOfs; + EmitString( "8B 80"); // mov eax, dword ptr [eax + 0x1234567] + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + } + EmitMovEBXEDI(vm, vm->dataMask); + EmitString( "8B 83" ); // mov eax, dword ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + case OP_LOAD2: + EmitMovEBXEDI(vm, vm->dataMask); + EmitString( "0F B7 83" ); // movzx eax, word ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + case OP_LOAD1: + EmitMovEBXEDI(vm, vm->dataMask); + EmitString( "0F B6 83" ); // movzx eax, byte ptr [ebx + 0x12345678] + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + case OP_STORE4: + EmitMovEAXEDI(vm); + EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4] +// if (pop1 != OP_CALL) { +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask & ~3 ); +// } + EmitString( "89 83" ); // mov dword ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + break; + case OP_STORE2: + EmitMovEAXEDI(vm); + EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4] +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask & ~1 ); + EmitString( "66 89 83" ); // mov word ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + break; + case OP_STORE1: + EmitMovEAXEDI(vm); + EmitString( "8B 5F FC" ); // mov ebx, dword ptr [edi-4] +// EmitString( "81 E3" ); // and ebx, 0x12345678 +// Emit4( vm->dataMask ); + EmitString( "88 83" ); // mov byte ptr [ebx+0x12345678], eax + Emit4( (int)vm->dataBase ); + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + break; + + case OP_EQ: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_NE: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LTI: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7D 06" ); // jnl +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LEI: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7F 06" ); // jnle +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GTI: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7E 06" ); // jng +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GEI: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "7C 06" ); // jnge +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LTU: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "73 06" ); // jnb +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LEU: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "77 06" ); // jnbe +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GTU: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "76 06" ); // jna +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GEU: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "8B 47 04" ); // mov eax, dword ptr [edi+4] + EmitString( "3B 47 08" ); // cmp eax, dword ptr [edi+8] + EmitString( "72 06" ); // jnae +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_EQF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 40" ); // test ah,0x40 + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_NEF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 40" ); // test ah,0x40 + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LTF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 01" ); // test ah,0x01 + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_LEF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 41" ); // test ah,0x41 + EmitString( "74 06" ); // je +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GTF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 41" ); // test ah,0x41 + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_GEF: + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + EmitString( "D9 47 04" ); // fld dword ptr [edi+4] + EmitString( "D8 5F 08" ); // fcomp dword ptr [edi+8] + EmitString( "DF E0" ); // fnstsw ax + EmitString( "F6 C4 01" ); // test ah,0x01 + EmitString( "75 06" ); // jne +6 + EmitString( "FF 25" ); // jmp [0x12345678] + v = Constant4(); + jused[v] = 1; + Emit4( (int)vm->instructionPointers + v*4 ); + break; + case OP_NEGI: + EmitString( "F7 1F" ); // neg dword ptr [edi] + break; + case OP_ADD: + EmitMovEAXEDI(vm); // mov eax, dword ptr [edi] + EmitString( "01 47 FC" ); // add dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_SUB: + EmitMovEAXEDI(vm); // mov eax, dword ptr [edi] + EmitString( "29 47 FC" ); // sub dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_DIVI: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "99" ); // cdq + EmitString( "F7 3F" ); // idiv dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_DIVU: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "33 D2" ); // xor edx, edx + EmitString( "F7 37" ); // div dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_MODI: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "99" ); // cdq + EmitString( "F7 3F" ); // idiv dword ptr [edi] + EmitString( "89 57 FC" ); // mov dword ptr [edi-4],edx + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_MODU: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "33 D2" ); // xor edx, edx + EmitString( "F7 37" ); // div dword ptr [edi] + EmitString( "89 57 FC" ); // mov dword ptr [edi-4],edx + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_MULI: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "F7 2F" ); // imul dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_MULU: + EmitString( "8B 47 FC" ); // mov eax,dword ptr [edi-4] + EmitString( "F7 27" ); // mul dword ptr [edi] + EmitString( "89 47 FC" ); // mov dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_BAND: + EmitMovEAXEDI(vm); // mov eax, dword ptr [edi] + EmitString( "21 47 FC" ); // and dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_BOR: + EmitMovEAXEDI(vm); // mov eax, dword ptr [edi] + EmitString( "09 47 FC" ); // or dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_BXOR: + EmitMovEAXEDI(vm); // mov eax, dword ptr [edi] + EmitString( "31 47 FC" ); // xor dword ptr [edi-4],eax + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_BCOM: + EmitString( "F7 17" ); // not dword ptr [edi] + break; + case OP_LSH: + EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] + EmitString( "D3 67 FC" ); // shl dword ptr [edi-4], cl + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_RSHI: + EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] + EmitString( "D3 7F FC" ); // sar dword ptr [edi-4], cl + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_RSHU: + EmitString( "8B 0F" ); // mov ecx, dword ptr [edi] + EmitString( "D3 6F FC" ); // shr dword ptr [edi-4], cl + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_NEGF: + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D9 E0" ); // fchs + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_ADDF: + EmitString( "D9 47 FC" ); // fld dword ptr [edi-4] + EmitString( "D8 07" ); // fadd dword ptr [edi] + EmitString( "D9 5F FC" ); // fstp dword ptr [edi-4] + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + break; + case OP_SUBF: + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D8 67 04" ); // fsub dword ptr [edi+4] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_DIVF: + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D8 77 04" ); // fdiv dword ptr [edi+4] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_MULF: + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "D8 4f 04" ); // fmul dword ptr [edi+4] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_CVIF: + EmitString( "DB 07" ); // fild dword ptr [edi] + EmitString( "D9 1F" ); // fstp dword ptr [edi] + break; + case OP_CVFI: +#ifndef FTOL_PTR // WHENHELLISFROZENOVER // bk001213 - was used in 1.17 + // not IEEE complient, but simple and fast + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "DB 1F" ); // fistp dword ptr [edi] +#else // FTOL_PTR + // call the library conversion function + EmitString( "D9 07" ); // fld dword ptr [edi] + EmitString( "FF 15" ); // call ftolPtr + Emit4( (int)&ftolPtr ); + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax +#endif + break; + case OP_SEX8: + EmitString( "0F BE 07" ); // movsx eax, byte ptr [edi] + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + case OP_SEX16: + EmitString( "0F BF 07" ); // movsx eax, word ptr [edi] + EmitCommand(LAST_COMMAND_MOV_EDI_EAX); // mov dword ptr [edi], eax + break; + + case OP_BLOCK_COPY: + // FIXME: range check + EmitString( "56" ); // push esi + EmitString( "57" ); // push edi + EmitString( "8B 37" ); // mov esi,[edi] + EmitString( "8B 7F FC" ); // mov edi,[edi-4] + EmitString( "B9" ); // mov ecx,0x12345678 + Emit4( Constant4() >> 2 ); + EmitString( "B8" ); // mov eax, datamask + Emit4( vm->dataMask ); + EmitString( "BB" ); // mov ebx, database + Emit4( (int)vm->dataBase ); + EmitString( "23 F0" ); // and esi, eax + EmitString( "03 F3" ); // add esi, ebx + EmitString( "23 F8" ); // and edi, eax + EmitString( "03 FB" ); // add edi, ebx + EmitString( "F3 A5" ); // rep movsd + EmitString( "5F" ); // pop edi + EmitString( "5E" ); // pop esi + EmitCommand(LAST_COMMAND_SUB_DI_8); // sub edi, 8 + break; + + case OP_JUMP: + EmitCommand(LAST_COMMAND_SUB_DI_4); // sub edi, 4 + EmitString( "8B 47 04" ); // mov eax,dword ptr [edi+4] + // FIXME: range check + EmitString( "FF 24 85" ); // jmp dword ptr [instructionPointers + eax * 4] + Emit4( (int)vm->instructionPointers ); + break; + default: + Com_Error( ERR_DROP, "VM_CompileX86: bad opcode %i at offset %i", op, pc ); + } + pop0 = pop1; + pop1 = op; + } + } + + // copy to an exact size buffer on the hunk + vm->codeLength = compiledOfs; + vm->codeBase = (unsigned char *)Hunk_Alloc( compiledOfs, h_low ); + Com_Memcpy( vm->codeBase, buf, compiledOfs ); + Z_Free( buf ); + Z_Free( jused ); + Com_Printf( "VM file %s compiled to %i bytes of code\n", vm->name, compiledOfs); + + // offset all the instruction pointers for the new location + for ( i = 0 ; i < header->instructionCount ; i++ ) { + vm->instructionPointers[i] += (int)vm->codeBase; + } + +#if 0 // ndef _WIN32 + // Must make the newly generated code executable + { + int r; + unsigned long addr; + int psize = getpagesize(); + + addr = ((int)vm->codeBase & ~(psize-1)) - psize; + + r = mprotect((char*)addr, vm->codeLength + (int)vm->codeBase - addr + psize, + PROT_READ | PROT_WRITE | PROT_EXEC ); + + if (r < 0) + Com_Error( ERR_FATAL, "mprotect failed to change PROT_EXEC" ); + } +#endif + +} + +/* +============== +VM_CallCompiled + +This function is called directly by the generated code +============== +*/ +#ifndef DLL_ONLY // bk010215 - for DLL_ONLY dedicated servers/builds w/o VM +int VM_CallCompiled( vm_t *vm, int *args ) { + int stack[1024]; + int programCounter; + int programStack; + int stackOnEntry; + byte *image; + void *entryPoint; + void *opStack; + int *oldInstructionPointers; + + oldInstructionPointers = instructionPointers; + + currentVM = vm; + instructionPointers = vm->instructionPointers; + + // interpret the code + vm->currentlyInterpreting = qtrue; + + callMask = vm->dataMask; + + // we might be called recursively, so this might not be the very top + programStack = vm->programStack; + stackOnEntry = programStack; + + // set up the stack frame + image = vm->dataBase; + + programCounter = 0; + + programStack -= 48; + + *(int *)&image[ programStack + 44] = args[9]; + *(int *)&image[ programStack + 40] = args[8]; + *(int *)&image[ programStack + 36] = args[7]; + *(int *)&image[ programStack + 32] = args[6]; + *(int *)&image[ programStack + 28] = args[5]; + *(int *)&image[ programStack + 24] = args[4]; + *(int *)&image[ programStack + 20] = args[3]; + *(int *)&image[ programStack + 16] = args[2]; + *(int *)&image[ programStack + 12] = args[1]; + *(int *)&image[ programStack + 8 ] = args[0]; + *(int *)&image[ programStack + 4 ] = 0; // return stack + *(int *)&image[ programStack ] = -1; // will terminate the loop on return + + // off we go into generated code... + entryPoint = vm->codeBase; + opStack = &stack; + +#ifdef _WIN32 + __asm { + pushad + mov esi, programStack; + mov edi, opStack + call entryPoint + mov programStack, esi + mov opStack, edi + popad + } +#else + { + static int memProgramStack; + static void *memOpStack; + static void *memEntryPoint; + + memProgramStack = programStack; + memOpStack = opStack; + memEntryPoint = entryPoint; + + __asm__(" pushal \r\n" \ + " movl %0,%%esi \r\n" \ + " movl %1,%%edi \r\n" \ + " call *%2 \r\n" \ + " movl %%esi,%0 \r\n" \ + " movl %%edi,%1 \r\n" \ + " popal \r\n" \ + : "=m" (memProgramStack), "=m" (memOpStack) \ + : "m" (memEntryPoint), "0" (memProgramStack), "1" (memOpStack) \ + : "si", "di" \ + ); + + programStack = memProgramStack; + opStack = memOpStack; + } +#endif + + if ( opStack != &stack[1] ) { + Com_Error( ERR_DROP, "opStack corrupted in compiled code" ); + } + if ( programStack != stackOnEntry - 48 ) { + Com_Error( ERR_DROP, "programStack corrupted in compiled code" ); + } + + vm->programStack = stackOnEntry; + + // in case we were recursively called by another vm + instructionPointers = oldInstructionPointers; + + return *(int *)opStack; +} +#endif // !DLL_ONLY + diff --git a/codemp/qcommon/xb_settings.cpp b/codemp/qcommon/xb_settings.cpp new file mode 100644 index 0000000..b3d0052 --- /dev/null +++ b/codemp/qcommon/xb_settings.cpp @@ -0,0 +1,342 @@ + +#include "xb_settings.h" +#include +#include "../game/q_shared.h" +#include "qcommon.h" +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" + +#define SETTINGS_VERSION 0x00082877 +#define SETTINGS_DIRNAME "Settings" +#define SETTINGS_FILENAME "settings.dat" +#define SETTINGS_IMAGE "saveimage.xbx" +#define SETTINGS_IMAGE_SRC "d:\\base\\media\\settings.xbx" + +// The one copy of Settings: +XBSettings Settings; +const DWORD settingsSize = sizeof(Settings); +const DWORD sigSize = sizeof(XCALCSIG_SIGNATURE); + +// This isn't user data, don't put it in XBSettings! +enum XBSettingsStatus +{ + SETTINGS_OK, // Everything is ok + SETTINGS_MISSING, // File is not on disk + SETTINGS_CORRUPT, // File on disk is corrupt + SETTINGS_FAILED, // General error +}; +XBSettingsStatus SettingsStatus; + +bool settingsDisabled = false; + +const char *buttonConfigStrings[3] = { + "weaponsbias", + "forcebias", + "southpaw", +}; + +const char *triggerConfigStrings[2] = { + "default", + "southpaw", +}; + +XBSettings::XBSettings( void ) +{ + version = SETTINGS_VERSION; + + // Defaults: + invertAim[0] = invertAim[1] = false; + + thumbstickMode[0] = thumbstickMode[1] = 0; + buttonMode[0] = buttonMode[1] = 0; + triggerMode[0] = triggerMode[1] = 0; + + rumble[0] = rumble[1] = 1; + autolevel[0] = autolevel[0] = 0; + autoswitch[0] = autoswitch[1] = 1; + sensitivityX[0] = sensitivityX[1] = 2.0f; + sensitivityY[0] = sensitivityY[1] = 2.0f; + + hotswapSP[0] = hotswapSP[1] = hotswapSP[2] = -1; + hotswapMP[0] = hotswapMP[1] = -1; + hotswapMP[2] = hotswapMP[3] = -1; + + effectsVolume = 1.0f; + musicVolume = 0.25f; + voiceVolume = 1.0f; + + subtitles = 0; + + voiceMode = 2; + voiceMask = 0; + appearOffline = 0; + + brightness = 6.0f; +} + +// Write the current stored settings to the HD: +bool XBSettings::Save( void ) +{ + // Do nothing if user chose "Continue Without Saving" + if( settingsDisabled ) + return true; + + char settingsPath[128]; + char *pathEnd; + DWORD dwWritten; + + // Build the settings directory: + unsigned short wideName[128]; + mbstowcs( wideName, SETTINGS_DIRNAME, sizeof(wideName) ); + + // Open/create the settings directory: + if (XCreateSaveGame( "U:\\", wideName, OPEN_ALWAYS, 0, settingsPath, sizeof(settingsPath) ) != ERROR_SUCCESS ) + { + SettingsStatus = SETTINGS_FAILED; + return false; + } + + // Build path to settings file: + pathEnd = settingsPath + strlen( settingsPath ); + strcpy( pathEnd, SETTINGS_FILENAME ); + + // Open/create the settings file: + HANDLE hFile = CreateFile( settingsPath, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + if( hFile == INVALID_HANDLE_VALUE ) + { + SettingsStatus = SETTINGS_FAILED; + return false; + } + + // Write the data: + if( !WriteFile( hFile, this, settingsSize, &dwWritten, NULL ) || (dwWritten != settingsSize) ) + { + SettingsStatus = SETTINGS_FAILED; + CloseHandle( hFile ); + return false; + } + + // Sign the data: + XCALCSIG_SIGNATURE xsig; + if( !Sign( &xsig ) ) + { + SettingsStatus = SETTINGS_FAILED; + CloseHandle( hFile ); + return false; + } + + // Write signature: + if( !WriteFile( hFile, &xsig, sigSize, &dwWritten, NULL ) || (dwWritten != sigSize) ) + { + SettingsStatus = SETTINGS_FAILED; + CloseHandle( hFile ); + return false; + } + + // Truncate and close file: + SetEndOfFile( hFile ); + CloseHandle( hFile ); + + // Copy the save image over: + strcpy( pathEnd, SETTINGS_IMAGE ); + CopyFile( SETTINGS_IMAGE_SRC, settingsPath, FALSE ); + + return true; +} + +// Read saved settings from the HD: +bool XBSettings::Load( void ) +{ + // Do nothing if user chose "Continue Without Saving" + if( settingsDisabled ) + return true; + + char settingsPath[128]; + char *pathEnd; + DWORD dwRead; + + // Build the settings directory: + unsigned short wideName[128]; + mbstowcs( wideName, SETTINGS_DIRNAME, sizeof(wideName) ); + + // Open the settings directory: + if( XCreateSaveGame( "U:\\", wideName, OPEN_EXISTING, 0, settingsPath, sizeof(settingsPath) ) != ERROR_SUCCESS ) + { + SettingsStatus = SETTINGS_MISSING; + return false; + } + + // Build path to settings file: + pathEnd = settingsPath + strlen( settingsPath ); + strcpy( pathEnd, SETTINGS_FILENAME ); + + HANDLE hFile = CreateFile( settingsPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); + if( hFile == INVALID_HANDLE_VALUE ) + { + SettingsStatus = SETTINGS_CORRUPT; + return false; + } + + // Verify file size: + if( GetFileSize( hFile, NULL ) != (settingsSize + sigSize) ) + { + SettingsStatus = SETTINGS_CORRUPT; + CloseHandle( hFile ); + return false; + } + + // Temp struct to read data into: + XBSettings temp; + if( !ReadFile( hFile, &temp, settingsSize, &dwRead, NULL ) || (dwRead != settingsSize) ) + { + SettingsStatus = SETTINGS_CORRUPT; + CloseHandle( hFile ); + return false; + } + + // Calculate signature over the read-in data: + XCALCSIG_SIGNATURE xsig; + if( !temp.Sign( &xsig ) ) + { + SettingsStatus = SETTINGS_CORRUPT; + CloseHandle( hFile ); + return false; + } + + // Read in stored signature: + XCALCSIG_SIGNATURE storedSig; + if( !ReadFile( hFile, &storedSig, sigSize, &dwRead, NULL ) || (dwRead != sigSize) ) + { + SettingsStatus = SETTINGS_CORRUPT; + CloseHandle( hFile ); + return false; + } + + // We're done with the file: + CloseHandle( hFile ); + + // Compare signatures: + if( memcmp( &xsig, &storedSig, sigSize ) != 0 ) + { + SettingsStatus = SETTINGS_CORRUPT; + return false; + } + + // Lastly, verify that the version number is right: + if( temp.version != SETTINGS_VERSION ) + { + SettingsStatus = SETTINGS_CORRUPT; + return false; + } + + // OK. The data checks out! + *this = temp; + + // TODO: Range-check all the values? + + return true; +} + +void XBSettings::Delete( void ) +{ + // Build the settings directory: + unsigned short wideName[128]; + mbstowcs( wideName, SETTINGS_DIRNAME, sizeof(wideName) ); + + // Delete the game: + XDeleteSaveGame( "U:\\", wideName ); +} + +bool XBSettings::Corrupt( void ) +{ + return (SettingsStatus == SETTINGS_CORRUPT); +} + +bool XBSettings::Missing( void ) +{ + return (SettingsStatus == SETTINGS_MISSING); +} + +// Copy all stored settings into cvars +void XBSettings::SetAll( void ) +{ + int clNum = ClientManager::ActiveClientNum(); + + ClientManager::ActiveClient().cg_pitch = invertAim[clNum] ? 0.022f : -0.022f; + + Cbuf_ExecuteText( EXEC_APPEND, va("exec cfg/uibuttonConfig%d.cfg\n", buttonMode[clNum]) ); + Cbuf_ExecuteText( EXEC_APPEND, va("exec cfg/triggersConfig%d.cfg\n", triggerMode[clNum]) ); + + // Do both of these, easier than checking: + Cvar_SetValue( "in_useRumble", rumble[0] ); + Cvar_SetValue( "in_useRumble2", rumble[1] ); + + ClientManager::ActiveClient().cg_autolevel = autolevel[clNum]; + ClientManager::ActiveClient().cg_autoswitch = autoswitch[clNum]; + + ClientManager::ActiveClient().cg_sensitivity = sensitivityX[clNum]; + ClientManager::ActiveClient().cg_sensitivityY = sensitivityY[clNum]; + + if( hotswapMP[0] >= 0 ) + Cvar_SetValue( "hotswap0", hotswapMP[0] ); + else + Cvar_Set( "hotswap0", "" ); + + if( hotswapMP[1] >= 0 ) + Cvar_SetValue( "hotswap1", hotswapMP[1] ); + else + Cvar_Set( "hotswap1", "" ); + + if( hotswapMP[2] >= 0 ) + Cvar_SetValue( "hotswap2", hotswapMP[2] ); + else + Cvar_Set( "hotswap2", "" ); + + if( hotswapMP[3] >= 0 ) + Cvar_SetValue( "hotswap3", hotswapMP[3] ); + else + Cvar_Set( "hotswap3", "" ); + + Cvar_SetValue( "s_effects_volume", effectsVolume ); + Cvar_SetValue( "s_music_volume", musicVolume ); + Cvar_SetValue( "s_voice_volume", voiceVolume ); + Cvar_SetValue( "s_brightness_volume", brightness ); + extern void GLimp_SetGamma(float); + GLimp_SetGamma(Cvar_VariableValue( "s_brightness_volume" ) / 5.0f); + + + + // Online options stuff is grabbed when it's needed +} + +// Utility - signs the current contents of this XBSettings into the supplied struct: +bool XBSettings::Sign( XCALCSIG_SIGNATURE *pSig ) +{ + // Start the signature: + HANDLE hSig = XCalculateSignatureBegin( 0 ); + if( hSig == INVALID_HANDLE_VALUE ) + return false; + + // Build the signature + if( XCalculateSignatureUpdate( hSig, (BYTE *) this, sizeof(*this) ) != ERROR_SUCCESS ) + return false; + + // Finish the signature: + if( XCalculateSignatureEnd( hSig, pSig ) != ERROR_SUCCESS ) + return false; + + // Done! + return true; +} + +// Master switch for turning off settings when user picks +// "Continue Without Saving" +void XBSettings::Disable( void ) +{ + settingsDisabled = true; +} + +bool XBSettings::IsDisabled( void ) +{ + return settingsDisabled; +} diff --git a/codemp/qcommon/xb_settings.h b/codemp/qcommon/xb_settings.h new file mode 100644 index 0000000..2d92ab9 --- /dev/null +++ b/codemp/qcommon/xb_settings.h @@ -0,0 +1,83 @@ + +#ifndef __XB_SETTINGS_H +#define __XB_SETTINGS_H + +#include + +enum XBStartupState +{ + STARTUP_LOAD_SETTINGS, + STARTUP_COMBINED_SPACE_CHECK, + STARTUP_GAME_SPACE_CHECK, + STARTUP_INVITE_CHECK, + STARTUP_FINISH, +}; + +// Minimum save size on Xbox. Bleh: +#define SETTINGS_NUM_BLOCKS 4 + +struct XBSettings +{ + // Magic number/revision stamp: + unsigned long version; + + // Controls, etc... One for SP/P1 in MP, other for P2 in MP: + bool invertAim[2]; + int thumbstickMode[2]; + int buttonMode[2]; + int triggerMode[2]; + int rumble[2]; + int autolevel[2]; + int autoswitch[2]; + float sensitivityX[2]; + float sensitivityY[2]; + + // Black/White/X assignments, SP: + int hotswapSP[3]; + + // Black/White for players one & two, MP: + int hotswapMP[4]; + + // A/V settings, Global: + float effectsVolume; + float musicVolume; + float voiceVolume; + float brightness; + + // Subtitles, only used in SP: + int subtitles; + + // Voice/Live options, only used in MP: + int voiceMode; + int voiceMask; + int appearOffline; + +// INTERFACE: + + XBSettings( void ); + + bool Save( void ); + bool Load( void ); + void Delete( void ); + + // For determining why a Save/Load failed: + bool Missing( void ); + bool Corrupt( void ); + + // This copies all settings from the Settings struct to their various cvars + void SetAll( void ); + + // Turn off the settings file completely: + void Disable( void ); + + // Has the user turned off saving (by choosing "Continue Without Saving")? + bool IsDisabled( void ); + +private: + bool Sign( XCALCSIG_SIGNATURE *pSig ); +}; + +// One global copy (declared in xb_settings.cpp) +extern XBSettings Settings; + +#endif diff --git a/codemp/qcommon/z_memman_console.cpp b/codemp/qcommon/z_memman_console.cpp new file mode 100644 index 0000000..2d6201c --- /dev/null +++ b/codemp/qcommon/z_memman_console.cpp @@ -0,0 +1,1899 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +/* + * ZONE MEMORY MANAGER + * + * Goals: + * 1. Minimize overhead + * 2. Minimize fragmentation + * + * Constraints: + * 1. Maximum allocated block size is 32MB + * 2. Maximum 64 different memory tags supported + * 3. Maximum 256 byte alignment + * + * All memory required by the manager is allocated at startup in + * the form of one large pool. + * + * Allocated blocks require a 4 byte header to store size, tag, and + * alignment information. Blocks that need to support the Z_TagFree() + * feature require an additional 8 byte link list structure. + * + * Free blocks require a 16 bytes of tracking information. If possible + * this information is stored directly in the block (which is in the + * pool.) If the free block is not large enough, its information is + * stored in an overflow buffer. + * + * In an effort to reduce fragmentation, blocks allocated for a short + * period of time at the end of the pool. All other blocks are allocated + * at the start. Allocation is first fit. + * + */ + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "../renderer/qgl_console.h" + +#ifdef _GAMECUBE +#include +#endif + +#ifdef _WINDOWS +#include +#endif + +#ifdef _XBOX +#include +#include "../cgame/cg_local.h" +#include "../renderer/modelmem.h" +#include "../win32/xbox_texture_man.h" +#endif + +// Where do hunk allocations go? +static memtag_t hunk_tag; + +// Used to mark the start and end of blocks in debug mode +#define ZONE_MAGIC 0xfe + +// Size of the free block overflow buffer +#define ZONE_FREE_OVERFLOW 4096 + +// Indicates whether or not special (slow) debug code should be enabled +#define ZONE_DEBUG 0 + +//Amount of memory left out of the zone for use in the model memory manager. +#define MODEL_MEM ( \ + 786740 + \ + 786740 + \ + 765912 + \ + 757852 + \ + 747412 + \ + 744960 + \ + 730924 + \ + 405040 + \ + 360840 + \ + 357468 + \ + 328984 + \ + 15000 + \ + 32000) //slack for paging memory, rounding, etc. +/* Round up last two model slots to equal sizeof(clientActive_t) */ + +// Allocate all available memory minus this amount - texture pools are +// allocated before this, so just leave enough for framebuffer, etc... +#ifdef FINAL_BUILD +# define ZONE_HEAP_FREE (1024*1024*7 + MODEL_MEM) +#else +# define ZONE_HEAP_FREE (1024*1024*16 + 16*1024*1024 + MODEL_MEM) +#endif + +#define STATIC_TEXTURE_POOL_SIZE (10*1024*1024) +#define MODEL_TEXTURE_POOL_SIZE 4*1024*1024 + +// Should we emulate the smaller memory footprint of actual release systems? +#define ZONE_EMULATE_SPACE 0 + +// All standard header data is crammed into 4 bytes +typedef unsigned int ZoneHeader; + +// Debug markers to check for overflow/underflow +typedef unsigned int ZoneDebugHeader; +typedef unsigned char ZoneDebugFooter; + +// Extended header information for memory freed with TagFree() +struct ZoneLinkHeader +{ + ZoneLinkHeader* m_Next; + ZoneLinkHeader* m_Prev; +}; + +static ZoneLinkHeader* s_LinkBase; + +// Free memory block tracking information +struct ZoneFreeBlock +{ + unsigned int m_Address; + unsigned int m_Size; + ZoneFreeBlock* m_Next; + ZoneFreeBlock* m_Prev; +}; + +// Buffer to hold free memory information that we can't +// fit directly in the pool +static ZoneFreeBlock s_FreeOverflow[ZONE_FREE_OVERFLOW]; +static int s_LastOverflowIndex; + +static ZoneFreeBlock s_FreeStart; +static ZoneFreeBlock s_FreeEnd; + +// Various stats collected at runtime +struct ZoneStats +{ + int m_CountAlloc; + int m_SizeAlloc; + int m_OverheadAlloc; + int m_PeakAlloc; + int m_CountFree; + int m_SizeFree; + int m_SizesPerTag[TAG_COUNT]; + int m_CountsPerTag[TAG_COUNT]; +}; + +static ZoneStats s_Stats; + +// Special empty block for zero size allocations +struct ZoneEmptyBlock +{ + ZoneHeader header; +#ifdef _DEBUG + ZoneDebugHeader start; + ZoneDebugFooter end; +#endif +}; + +#ifdef _DEBUG +static ZoneEmptyBlock s_EmptyBlock = {TAG_STATIC << 25, ZONE_MAGIC, ZONE_MAGIC}; +#else +static ZoneEmptyBlock s_EmptyBlock = {TAG_STATIC << 25}; +#endif + +// Free block jump table for fast memory deallocation +#define Z_JUMP_TABLE_SIZE 64 +static ZoneFreeBlock* s_FreeJumpTable[Z_JUMP_TABLE_SIZE]; +static unsigned int s_FreeJumpResolution; + +static void* s_PoolBase; +static bool s_Initialized = false; + +static memtag_t s_newDeleteTagStack[32] = { TAG_NEWDEL }; +static int s_newDeleteTagStackTop = 0; + +#ifndef _GAMECUBE +static HANDLE s_Mutex = INVALID_HANDLE_VALUE; +#endif + +static void Z_Stats_f(void); +void Z_Details_f(void); +void Z_DumpMemMap_f(void); +void Z_CompactStats(void); + +void Z_PushNewDeleteTag( memtag_t eTag ) +{ + assert( s_newDeleteTagStackTop < 31 ); + s_newDeleteTagStack[++s_newDeleteTagStackTop] = eTag; +} + +void Z_PopNewDeleteTag( void ) +{ + assert( s_newDeleteTagStackTop ); + --s_newDeleteTagStackTop; +} + +#ifdef _XBOX +void ShowOSMemory(void) +{ +#ifndef FINAL_BUILD + MEMORYSTATUS stat; + GlobalMemoryStatus(&stat); + Com_PrintfAlways(" total mem: %d, free mem: %d\n", stat.dwTotalPhys / 1024, + stat.dwAvailPhys / 1024); + FILE *out = fopen("d:\\osmem.txt", "a"); + if(out) { + fprintf(out, "total mem: %d, free mem: %d\n", stat.dwTotalPhys / 1024, + stat.dwAvailPhys / 1024); + fclose(out); + } +#endif +} +#endif + + +int Z_MemFree(void) +{ + return s_Stats.m_SizeFree; +} + + +void Com_InitZoneMemory(void) +{ +// assert(!s_Initialized); + // Zone now initializes on first use, can't reliably assume anything here + if (s_Initialized) + return; + + Com_Printf("Initialising zone memory .....\n"); + + // Clear some globals + memset(&s_Stats, 0, sizeof(s_Stats)); + memset(s_FreeOverflow, 0, sizeof(s_FreeOverflow)); + s_LastOverflowIndex = 0; + s_LinkBase = NULL; + + // Alloc the pool + MEMORYSTATUS status; + GlobalMemoryStatus(&status); + + // BTO : VVFIXME - Extra little note to see how much memory + // is being used by globals/statics +#ifndef FINAL_BUILD + Com_PrintfAlways("*** PhysRAM: %d used, %d free\n", + status.dwTotalPhys-status.dwAvailPhys, + status.dwAvailPhys); +#endif + + // Allocate the two texture pools: + gStaticTextures.Initialize( STATIC_TEXTURE_POOL_SIZE ); + gSkinTextures.Initialize( MODEL_TEXTURE_POOL_SIZE ); + + GlobalMemoryStatus(&status); + + // BTO : VVFIXME - Extra little note to see how much memory + // is being used by globals/statics +#ifndef FINAL_BUILD + Com_PrintfAlways("*** PhysRAM: %d used, %d free\n", + status.dwTotalPhys-status.dwAvailPhys, + status.dwAvailPhys); +#endif + + SIZE_T size; +# if ZONE_EMULATE_SPACE +#ifdef _DEBUG + //Emulated space is always about 6 megs off from release build. Try + //to compensate. This number may need tweaking in the future. + SIZE_T exe = 6500 * 1024; +#else + SIZE_T exe = 0; //Exe size is already reflected in GlobalMemoryStatus(). +#endif + size = 0x4000000 - (exe + ZONE_HEAP_FREE); +# else + size = status.dwAvailPhys - ZONE_HEAP_FREE; +# endif + +#ifdef FINAL_BUILD + // Add in the memory that's being used up by the framebuffer from PersistDisplay: + size += (640 * 480 * 4); +#endif + + s_PoolBase = GlobalAlloc(0, size); + + // Setup the initial free block + ZoneFreeBlock* base = (ZoneFreeBlock*)s_PoolBase; + base->m_Address = (unsigned int)s_PoolBase; + base->m_Size = size; + base->m_Next = &s_FreeEnd; + base->m_Prev = &s_FreeStart; + + // Init the free block jump table + memset(s_FreeJumpTable, 0, Z_JUMP_TABLE_SIZE * sizeof(ZoneFreeBlock*)); + s_FreeJumpResolution = (size / Z_JUMP_TABLE_SIZE) + 1; + s_FreeJumpTable[0] = base; + + // Setup free block dummies + s_FreeStart.m_Address = 0; + s_FreeStart.m_Size = 0; + s_FreeStart.m_Next = base; + s_FreeStart.m_Prev = NULL; + + s_FreeEnd.m_Address = 0xFFFFFFFF; + s_FreeEnd.m_Size = 0; + s_FreeEnd.m_Next = NULL; + s_FreeEnd.m_Prev = base; + + s_Stats.m_CountFree = 1; + s_Stats.m_SizeFree = size; + + s_Initialized = true; + + // Add some commands + Cmd_AddCommand("zone_stats", Z_Stats_f); + Cmd_AddCommand("zone_details", Z_Details_f); + Cmd_AddCommand("zone_memmap", Z_DumpMemMap_f); + Cmd_AddCommand("zone_cstats", Z_CompactStats); + +#ifdef _XBOX + ModelMem.AllocateModelSlots(); +#endif + +#ifndef _GAMECUBE + s_Mutex = CreateMutex(NULL, FALSE, NULL); +#endif +} + +void Com_ShutdownZoneMemory(void) +{ + assert(s_Initialized); + + // Remove commands + Cmd_RemoveCommand("zone_stats"); + Cmd_RemoveCommand("zone_details"); + Cmd_RemoveCommand("zone_memmap"); + + if (s_Stats.m_CountAlloc) + { + // Free all memory +// CM_ReleaseVisData(); + Z_TagFree(TAG_ALL); + } + + // Clear some globals + memset(&s_Stats, 0, sizeof(s_Stats)); + memset(s_FreeOverflow, 0, sizeof(s_FreeOverflow)); + s_LastOverflowIndex = 0; + s_LinkBase = NULL; + + // Free the pool +#ifndef _GAMECUBE + GlobalFree(s_PoolBase); + CloseHandle(s_Mutex); +#endif + + s_PoolBase = NULL; + s_Initialized = false; +} + + +// Determine if a tag should only be allocated for a very +// short period of time. +static bool Z_IsTagTemp(memtag_t eTag) +{ + return + eTag == TAG_TEMP_WORKSPACE || + eTag == TAG_SND_RAWDATA || + eTag == TAG_ICARUS || + eTag == TAG_TEXTPOOL || + eTag == TAG_TEMP_HUNKALLOC || + eTag == TAG_LISTFILES; +} + +// Determine if a tag needs TagFree() support. +static bool Z_IsTagLinked(memtag_t eTag) +{ + return + eTag == TAG_BSP || + eTag == TAG_CG_UI_ALLOC || + eTag == TAG_BG_ALLOC || + eTag == TAG_HUNK_MARK1 || + eTag == TAG_HUNK_MARK2 || + eTag == TAG_TEMP_HUNKALLOC || + eTag == TAG_UI_ALLOC; +} + +static int Z_CalcAlignmentPad(int iAlign, unsigned int iAddress, unsigned int iOffset, + unsigned int iSize, unsigned int iHeaderSize, unsigned int iFooterSize) +{ + int align_size; + + if (iAlign == 0) return 0; + + if (iOffset == 0) + { + // Align data at low end of block + align_size = iAlign - + ((iAddress + iHeaderSize) % iAlign); + } + else + { + // Align data at high end of block + unsigned int block_start = iAddress + iOffset - + iSize + iHeaderSize; + align_size = block_start % iAlign; + } + + if (align_size == iAlign) + { + return 0; + } + + return align_size; +} + +static ZoneFreeBlock* Z_GetOverflowBlock(void) +{ + for (int i = s_LastOverflowIndex; i < ZONE_FREE_OVERFLOW; ++i) + { + if (s_FreeOverflow[i].m_Address == 0) + { + s_LastOverflowIndex = i; + return &s_FreeOverflow[i]; + } + } + + for (int j = 0; j < s_LastOverflowIndex; ++j) + { + if (s_FreeOverflow[j].m_Address == 0) + { + s_LastOverflowIndex = j; + return &s_FreeOverflow[j]; + } + } + + return NULL; +} + +static inline bool Z_IsFreeBlockLargeEnough(ZoneFreeBlock* pBlock, int iSize, + int iHeaderSize, int iFooterSize, int iAlign, bool bLow, int& iAlignPad) +{ + // Is the block large enough? + if (pBlock->m_Size >= iSize) + { + if (iAlign > 0) + { + // If we need some aligment, we need to check size + // against that as well. + iAlignPad = Z_CalcAlignmentPad(iAlign, + pBlock->m_Address, !bLow ? pBlock->m_Size : 0, + iSize, iHeaderSize, iFooterSize); + + if (pBlock->m_Size < iAlignPad + iSize) + { + return false; + } + } + return true; + } + return false; +} + +static ZoneFreeBlock* Z_FindFirstFree(int iSize, int iHeaderSize, + int iFooterSize, int iAlign, int& iAlignPad) +{ + for (ZoneFreeBlock* block = s_FreeStart.m_Next; block; block = block->m_Next) + { + if (Z_IsFreeBlockLargeEnough(block, iSize, iHeaderSize, iFooterSize, + iAlign, true, iAlignPad)) + { + return block; + } + } + return NULL; +} + +static ZoneFreeBlock* Z_FindLastFree(int iSize, int iHeaderSize, + int iFooterSize, int iAlign, int& iAlignPad) +{ + for (ZoneFreeBlock* block = s_FreeEnd.m_Prev; block; block = block->m_Prev) + { + if (Z_IsFreeBlockLargeEnough(block, iSize, iHeaderSize, iFooterSize, + iAlign, false, iAlignPad)) + { + return block; + } + } + return NULL; +} + +static bool Z_ValidateFree(void) +{ +#if ZONE_DEBUG + // Make sure no free blocks are overlapping + for (ZoneFreeBlock* a = &s_FreeStart; a; a = a->m_Next) + { + if (a->m_Address == 0 && a->m_Size != 0) + { + return false; + } + + for (ZoneFreeBlock* b = &s_FreeStart; b; b = b->m_Next) + { + if (a != b && + a->m_Address >= b->m_Address && + a->m_Address < b->m_Address + b->m_Size) + { + return false; + } + } + } +#endif + + return true; +} + +static bool Z_ValidateLinks(void) +{ +#if ZONE_DEBUG + // Make sure links are sane + for (ZoneLinkHeader* a = s_LinkBase; a; a = a->m_Next) + { + if ((a->m_Next && a != a->m_Next->m_Prev) || + (a->m_Prev && a != a->m_Prev->m_Next)) + { + return false; + } + } +#endif + + return true; +} + +static int Z_GetJumpTableIndex(unsigned int iAddress) +{ + int index = (iAddress - (unsigned int)s_PoolBase) / s_FreeJumpResolution; + if (index < 0) return 0; + if (index >= Z_JUMP_TABLE_SIZE) return Z_JUMP_TABLE_SIZE - 1; + return index; +} + +static ZoneFreeBlock* Z_GetFreeBlockBefore(unsigned int iAddress) +{ + // Find this block's position in the jump table + int index = Z_GetJumpTableIndex(iAddress) - 1; + + // Find a valid jump table entry + while (index >= 0 && !s_FreeJumpTable[index]) --index; + + if (index < 0) return &s_FreeStart; + return s_FreeJumpTable[index]; +} + +static void Z_RemoveFromJumpTable(ZoneFreeBlock* pBlock) +{ + // Is this block in the jump table? + int index = Z_GetJumpTableIndex(pBlock->m_Address); + if (s_FreeJumpTable[index] == pBlock) + { + // See if the next block will fit in our slot + if (pBlock->m_Next != &s_FreeEnd) + { + int nindex = Z_GetJumpTableIndex(pBlock->m_Next->m_Address); + if (nindex == index) + { + s_FreeJumpTable[index] = pBlock->m_Next; + return; + } + } + + // See if the previous block will fit in our slot + if (pBlock->m_Prev != &s_FreeStart) + { + int pindex = Z_GetJumpTableIndex(pBlock->m_Prev->m_Address); + if (pindex == index) + { + s_FreeJumpTable[index] = pBlock->m_Prev; + return; + } + } + + // No other free blocks fit here, give up + s_FreeJumpTable[index] = NULL; + } +} + +static void Z_LinkFreeBlock(ZoneFreeBlock* pBlock) +{ + ZoneFreeBlock* cur = Z_GetFreeBlockBefore(pBlock->m_Address); + for (; cur; cur = cur->m_Next) + { + // Find the correct position, ordered by address + if (cur->m_Address > pBlock->m_Address) + { + // Link up the block + pBlock->m_Next = cur; + pBlock->m_Prev = cur->m_Prev; + cur->m_Prev->m_Next = pBlock; + cur->m_Prev = pBlock; + + // Update the jump table if necessary + int index = Z_GetJumpTableIndex(pBlock->m_Address); + if (!s_FreeJumpTable[index]) + { + s_FreeJumpTable[index] = pBlock; + } + + s_Stats.m_CountFree++; + s_Stats.m_SizeFree += pBlock->m_Size; + + assert(Z_ValidateFree()); + break; + } + } +} + +static void* Z_SplitFree(ZoneFreeBlock* pBlock, int iSize, bool bLow) +{ + assert(pBlock->m_Size >= iSize); + + Z_RemoveFromJumpTable(pBlock); + + // Delink the free block + ZoneFreeBlock fblock = *pBlock; + pBlock->m_Prev->m_Next = pBlock->m_Next; + pBlock->m_Next->m_Prev = pBlock->m_Prev; + pBlock->m_Address = 0; + + s_Stats.m_CountFree--; + s_Stats.m_SizeFree -= pBlock->m_Size; + assert(Z_ValidateFree()); + + if (fblock.m_Size > iSize) + { + // Split the block into an allocated and free portion + int remainder = fblock.m_Size - iSize; + + if (remainder < sizeof(ZoneFreeBlock)) + { + // Free portion is not large to hold free info -- + // we're going to have to use the overflow buffer. + ZoneFreeBlock* nblock = Z_GetOverflowBlock(); + + if (nblock == NULL) + { + Z_Details_f(); + Com_Error(ERR_FATAL, "Zone free overflow buffer overflowed!"); + } + + // Split the block + void* ret; + if (bLow) + { + ret = (void*)fblock.m_Address; + nblock->m_Address = fblock.m_Address + iSize; + } + else + { + ret = (void*)(fblock.m_Address + remainder); + nblock->m_Address = fblock.m_Address; + } + + nblock->m_Size = remainder; + Z_LinkFreeBlock(nblock); + + return ret; + } + else + { + // Free portion is large enough -- split it + void* ret; + ZoneFreeBlock* nblock; + if (bLow) + { + ret = (void*)fblock.m_Address; + nblock = (ZoneFreeBlock*)(fblock.m_Address + iSize); + } + else + { + ret = (void*)(fblock.m_Address + remainder); + nblock = (ZoneFreeBlock*)fblock.m_Address; + } + + nblock->m_Address = (unsigned int)nblock; + nblock->m_Size = remainder; + + Z_LinkFreeBlock(nblock); + + return ret; + } + } + else + { + // No need to split, just return block. + return (void*)fblock.m_Address; + } +} + +static void Z_SetupAlignmentPad(void* pBlock, int iAlignPad, bool bLow) +{ + // Clear alignment bytes + memset(pBlock, 0, iAlignPad); + + // If we have more than 1 alignment byte, the first align byte + // tells us how many additional bytes we have. + if (iAlignPad > 1) + { + assert(iAlignPad < 256); + unsigned char* ptr; + if (bLow) + { + ptr = (unsigned char*)pBlock + (iAlignPad - 1); + } + else + { + ptr = (unsigned char*)pBlock; + } + *ptr = iAlignPad - 1; + } +} + +void Z_MallocFail(const char* pMessage, int iSize, memtag_t eTag) +{ + // Report the error +// Com_Printf("Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); + Com_PrintfAlways("Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); + Z_Details_f(); + Z_DumpMemMap_f(); +// Com_Printf("(Repeat): Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); + Com_PrintfAlways("(Repeat): Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); + + // Clear the screen blue to indicate out of memory + for (;;) + { + qglBeginFrame(); + qglClearColor(0, 0, 1, 1); + qglClear(GL_COLOR_BUFFER_BIT); + qglEndFrame(); + } +} + +void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int iAlign) +{ +// assert(s_Initialized); + // Zone now initializes on first use. (During static constructors) + if (!s_Initialized) + Com_InitZoneMemory(); + + if (iSize == 0) + { +#ifdef _DEBUG + return (void*)(&s_EmptyBlock.start + 1); +#else + return (void*)(&s_EmptyBlock.header + 1); +#endif + } + + if (iSize < 0) + { + Z_MallocFail("Negative size", iSize, eTag); + return NULL; + } + +#ifndef _GAMECUBE + WaitForSingleObject(s_Mutex, INFINITE); +#endif + + // Make new/delete memory temporary if requested + if (eTag == TAG_NEWDEL ) + { + eTag = s_newDeleteTagStack[s_newDeleteTagStackTop]; + } + + // HAQ! + if (eTag == TAG_CLIENT_MANAGER_SPECIAL) + { + void *retVal = HeapAlloc(GetProcessHeap(), 0, iSize); + if (!retVal) + Z_MallocFail("ClientManagerSpecial Failed", iSize, eTag); + ReleaseMutex(s_Mutex); + return retVal; + } + + // Determine how much space we need with headers and footers + int header_size = sizeof(ZoneHeader); + int footer_size = 0; + if (Z_IsTagLinked(eTag)) + { + header_size += sizeof(ZoneLinkHeader); + } +#ifdef _DEBUG + header_size += sizeof(ZoneDebugHeader); + footer_size += sizeof(ZoneDebugFooter); +#endif + int real_size = iSize + header_size + footer_size; + int align_pad = 0; + + // Get a bit of free memory. Temporary memory is allocated + // from the end. More permanent allocations are done at the + // begining of the pool. + ZoneFreeBlock* fblock; + if (Z_IsTagTemp(eTag)) + { + fblock = Z_FindLastFree(real_size, header_size, footer_size, + iAlign, align_pad); + } + else + { + fblock = Z_FindFirstFree(real_size, header_size, footer_size, + iAlign, align_pad); + } + + // Did we actually find some memory? + if (!fblock) + { +#ifndef _GAMECUBE + ReleaseMutex(s_Mutex); +#endif +// if(eTag == TAG_TEMP_SND_RAWDATA) { + if(eTag == TAG_SND_RAWDATA) { + return NULL; + } + + Z_MallocFail("Out of memory", iSize, eTag); + return NULL; + } + + // Add any alignment bytes + real_size += align_pad; + + // Split the free block and get a pointer to the start + // allocated space. + void* ablock; + if (Z_IsTagTemp(eTag)) + { + ablock = Z_SplitFree(fblock, real_size, false); + + // Append align pad to end of block + Z_SetupAlignmentPad( + (void*)((char*)ablock + real_size - align_pad), + align_pad, false); + } + else + { + ablock = Z_SplitFree(fblock, real_size, true); + + // Insert align pad at block start + Z_SetupAlignmentPad(ablock, align_pad, true); + ablock = (void*)((char*)ablock + align_pad); + } + + if (!ablock) + { + Z_MallocFail("Failed to split", iSize, eTag); + } + + // Add linking header if necessary + if (Z_IsTagLinked(eTag)) + { + ZoneLinkHeader* linked = (ZoneLinkHeader*)ablock; + linked->m_Next = s_LinkBase; + linked->m_Prev = NULL; + if (s_LinkBase) + { + s_LinkBase->m_Prev = linked; + } + s_LinkBase = linked; + + assert(Z_ValidateLinks()); + + // Next... + ablock = (void*)((char*)ablock + sizeof(ZoneLinkHeader)); + } + + // Setup the header: + // 31 - alignment flag + // 25-30 - tag + // 0-24 - size without headers/footers + assert(iSize >= 0 && iSize < (1 << 25)); + assert(eTag >= 0 && eTag < 64); + ZoneHeader* header = (ZoneHeader*)ablock; + *header = + (((unsigned int)eTag) << 25) | + ((unsigned int)iSize); + + if (align_pad) + { + *header |= (1 << 31); + } + + // Next... + ablock = (void*)((char*)ablock + sizeof(ZoneHeader)); + +#ifdef _DEBUG + { + // Setup the debug markers + ZoneDebugHeader* debug_header = (ZoneDebugHeader*)ablock; + + ZoneDebugFooter* debug_footer = (ZoneDebugFooter*)((char*)debug_header + + (sizeof(ZoneDebugHeader) + iSize)); + + *debug_header = ZONE_MAGIC; + *debug_footer = ZONE_MAGIC; + + // Next... + ablock = (void*)((char*)ablock + sizeof(ZoneDebugHeader)); + } +#endif + + // Update the stats + s_Stats.m_SizeAlloc += iSize; + s_Stats.m_OverheadAlloc += header_size + footer_size + align_pad; + s_Stats.m_SizesPerTag[eTag] += iSize; + s_Stats.m_CountAlloc++; + s_Stats.m_CountsPerTag[eTag]++; + + if (s_Stats.m_SizeAlloc + s_Stats.m_OverheadAlloc > s_Stats.m_PeakAlloc) + { + s_Stats.m_PeakAlloc = s_Stats.m_SizeAlloc + s_Stats.m_OverheadAlloc; + } + + // Return a pointer to data memory + if (bZeroit) + { + memset(ablock, 0, iSize); + } + + assert(iAlign == 0 || (unsigned int)ablock % iAlign == 0); + + /* + This is useful for figuring out who's allocating a certain block of + memory. Please don't remove it. + if(eTag == TAG_NEWDEL && (unsigned int)ablock >= 0x806c0000 && + (unsigned int)ablock <= 0x806c1000 && iSize == 24) { + int suck = 0; + } + if(eTag == TAG_SMALL && (iSize == 7 || iSize == 96)) { + int suck = 0; + } + if(eTag == TAG_CLIENTS) { + int suck = 0; + } + + if ((unsigned)ablock >= 0x26e0000 && (unsigned)ablock <= 0x26e1000 && iSize == 720) + { + int suck = 0; + } + */ + +#ifndef _GAMECUBE + ReleaseMutex(s_Mutex); +#endif + + return ablock; +} + +static memtag_t Z_GetTag(const ZoneHeader* header) +{ + return (*header & 0x7E000000) >> 25; +} + +static unsigned int Z_GetSize(const ZoneHeader* header) +{ + return *header & 0x1FFFFFF; +} + +static int Z_GetAlign(const ZoneHeader* header) +{ + if (*header & (1 << 31)) + { + unsigned char* ptr = (unsigned char*)header; + memtag_t tag = Z_GetTag(header); + + // point to the first alignment block + if (Z_IsTagTemp(tag)) + { + ptr += sizeof(ZoneHeader) + Z_GetSize(header); +#ifdef _DEBUG + ptr += sizeof(ZoneDebugHeader) + sizeof(ZoneDebugFooter); +#endif + } + else + { + if (Z_IsTagLinked(tag)) + { + // skip the link header + ptr -= sizeof(ZoneLinkHeader); + } + ptr -= 1; + } + + return *ptr + 1; + } + return 0; +} + +int Z_Size(void *pvAddress) +{ + assert(s_Initialized); + +#ifdef _DEBUG + ZoneDebugHeader* debug = (ZoneDebugHeader*)pvAddress - 1; + + if (*debug != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Size(): Not a valid zone header!"); + return 0; // won't get here + } + + pvAddress = (void*)debug; +#endif + + ZoneHeader* header = (ZoneHeader*)pvAddress - 1; + + if (Z_GetTag(header) == TAG_STATIC) + { + return 0; // kind of + } + + return Z_GetSize(header); +} + +static void Z_Coalasce(ZoneFreeBlock* pBlock) +{ + unsigned int size = 0; + + // Find later free blocks adjacent to us + ZoneFreeBlock* end; + for (end = pBlock->m_Next; + end->m_Next; + end = end->m_Next) + { + if (end->m_Address != + end->m_Prev->m_Address + end->m_Prev->m_Size) + { + break; + } + + size += end->m_Size; + + Z_RemoveFromJumpTable(end); + + end->m_Address = 0; // invalidate block + s_Stats.m_CountFree--; + } + + // Find previous free blocks adjacent to us + ZoneFreeBlock* start; + for (start = pBlock; + start->m_Prev; + start = start->m_Prev) + { + if (start->m_Prev->m_Address + start->m_Prev->m_Size != + start->m_Address) + { + break; + } + + size += start->m_Size; + + Z_RemoveFromJumpTable(start); + + start->m_Address = 0; // invalidate block + s_Stats.m_CountFree--; + } + + // Do we need to coalesce some blocks? + if (start->m_Next != end) + { + start->m_Next = end; + end->m_Prev = start; + start->m_Size += size; + } +} + +// Return type of Z_Free differs in SP/MP. Macro hack to wrap it up +#ifdef _JK2MP + void Z_Free(void *pvAddress) + #define Z_FREE_RETURN(x) return +#else + int Z_Free(void *pvAddress) + #define Z_FREE_RETURN(x) return (x) +#endif +{ +#ifdef _WINDOWS + if (!s_Initialized) return; +#endif + + assert(s_Initialized); + + // HAQ! + if (s_newDeleteTagStack[s_newDeleteTagStackTop] == TAG_CLIENT_MANAGER_SPECIAL) + { + if( !HeapFree( GetProcessHeap(), 0, pvAddress ) ) + Z_MallocFail("CMSpecialFree Failed", 0, 0); + return; + } + +#ifdef _DEBUG + // check the header magic + ZoneDebugHeader* debug_header = (ZoneDebugHeader*)pvAddress - 1; + + if (*debug_header != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone header!"); + Z_FREE_RETURN( 0 ); + } + + ZoneHeader* header = (ZoneHeader*)debug_header - 1; + + // check the footer magic + ZoneDebugFooter* debug_footer = (ZoneDebugFooter*)((char*)pvAddress + + Z_GetSize(header)); + + if (*debug_footer != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone footer!"); + Z_FREE_RETURN( 0 ); + } +#else + ZoneHeader* header = (ZoneHeader*)pvAddress - 1; +#endif + + memtag_t tag = Z_GetTag(header); + + if (tag != TAG_STATIC) + { +#ifndef _GAMECUBE + WaitForSingleObject(s_Mutex, INFINITE); +#endif + + // Determine size of header and footer + int header_size = sizeof(ZoneHeader); + int align_size = Z_GetAlign(header); + int footer_size = 0; + int data_size = Z_GetSize(header); + if (Z_IsTagLinked(tag)) + { + header_size += sizeof(ZoneLinkHeader); + } + if (Z_IsTagTemp(tag)) + { + footer_size += align_size; + } + else + { + header_size += align_size; + } +#ifdef _DEBUG + header_size += sizeof(ZoneDebugHeader); + footer_size += sizeof(ZoneDebugFooter); +#endif + int real_size = data_size + header_size + footer_size; + + // Update the stats + s_Stats.m_SizeAlloc -= data_size; + s_Stats.m_OverheadAlloc -= header_size + footer_size; + s_Stats.m_SizesPerTag[tag] -= data_size; + s_Stats.m_CountAlloc--; + s_Stats.m_CountsPerTag[tag]--; + + // Delink block + if (Z_IsTagLinked(tag)) + { + ZoneLinkHeader* linked = (ZoneLinkHeader*)header - 1; + + if (linked == s_LinkBase) + { + s_LinkBase = linked->m_Next; + if (s_LinkBase) + { + s_LinkBase->m_Prev = NULL; + } + } + else + { + if (linked->m_Next) + { + linked->m_Next->m_Prev = linked->m_Prev; + } + linked->m_Prev->m_Next = linked->m_Next; + } + + assert(Z_ValidateLinks()); + } + + // Clear the block header for safety + *header = 0; + + // Add block to free list + ZoneFreeBlock* nblock = NULL; + if (real_size < sizeof(ZoneFreeBlock)) + { + // Not enough space in block to put free information -- + // use overflow buffer. + nblock = Z_GetOverflowBlock(); + + if (nblock == NULL) + { + Z_Details_f(); + Com_Error(ERR_FATAL, "Zone free overflow buffer overflowed!"); + } + } + else + { + // Place free information in block + nblock = (ZoneFreeBlock*)((char*)pvAddress - header_size); + } + + nblock->m_Address = (unsigned int)pvAddress - header_size; + nblock->m_Size = real_size; + Z_LinkFreeBlock(nblock); + + // Coalesce any adjacent free blocks + Z_Coalasce(nblock); +#ifndef _GAMECUBE + ReleaseMutex(s_Mutex); +#endif + } + + Z_FREE_RETURN( 0 ); +} + + +int Z_MemSize(memtag_t eTag) +{ + return s_Stats.m_SizesPerTag[eTag]; +} + +#if ZONE_DEBUG +void Z_FindLeak(void) +{ + assert(s_Initialized); + + static int cycle_count = 0; + const memtag_t tag = TAG_NEWDEL; + + struct PointerInfo + { + void* data; + int counter; + bool mark; + }; + + const int max_pointers = 32768; + static PointerInfo pointers[max_pointers]; + static int num_pointers = 0; + + // Clear pointer existance + for (int i = 0; i < num_pointers; ++i) + { + pointers[i].mark = false; + } + + // Add all known pointers + int start_num = num_pointers; + for (ZoneLinkHeader* link = s_LinkBase; link;) + { + ZoneHeader* header = (ZoneHeader*)(link + 1); + link = link->m_Next; + + if (Z_GetTag(header) == tag) + { + // See if the pointer already is in the array + bool found = false; + for (int k = start_num; k < num_pointers; ++k) + { + if (pointers[k].data == header) + { + ++pointers[k].counter; + pointers[k].mark = true; + found = true; + break; + } + } + + // If the pointer is not in the array, add it + if (!found) + { + assert(num_pointers < max_pointers); + pointers[num_pointers].data = header; + pointers[num_pointers].counter = 0; + pointers[num_pointers].mark = true; + ++num_pointers; + } + } + } + + // Remove pointers that are no longer used + for (int j = 0; j < num_pointers; ++j) + { + if (pointers[j].mark) + { + if (pointers[j].counter != cycle_count && + pointers[j].counter != cycle_count - 1 && + pointers[j].counter != 0) + { + Com_Printf("Memory leak: %p\n", pointers[j].data); + } + } + else + { + int k; + for (k = j; k < num_pointers; ++k) + { + if (pointers[k].mark) break; + } + + if (k == num_pointers) break; + + memmove(pointers + j, pointers + k, (num_pointers - k) * sizeof(PointerInfo)); + num_pointers -= k - j; + } + } + + ++cycle_count; +} +#endif + +void Z_TagPointers(memtag_t eTag) +{ + assert(s_Initialized); + +#ifndef _GAMECUBE + WaitForSingleObject(s_Mutex, INFINITE); +#endif + + Sys_Log( "pointers.txt", va("Pointers for tag %d:\n", eTag) ); + + for (ZoneLinkHeader* link = s_LinkBase; link;) + { + ZoneHeader* header = (ZoneHeader*)(link + 1); + link = link->m_Next; + + if (eTag == TAG_ALL || Z_GetTag(header) == eTag) + { +#ifdef _DEBUG + Sys_Log( "pointers.txt", + va("%x - %d\n", ((void*)((char*)header + + sizeof(ZoneHeader) + sizeof(ZoneDebugHeader))), + Z_Size(((void*)((char*)header + + sizeof(ZoneHeader) + sizeof(ZoneDebugHeader)))))); +#else + Sys_Log( "pointers.txt", + va("%x - %d\n", (void*)(header + 1), + Z_Size((void*)(header + 1)))); +#endif + } + } + +#ifndef _GAMECUBE + ReleaseMutex(s_Mutex); +#endif +} + +void Z_TagFree(memtag_t eTag) +{ + assert(s_Initialized); + + for (ZoneLinkHeader* link = s_LinkBase; link;) + { + ZoneHeader* header = (ZoneHeader*)(link + 1); + link = link->m_Next; + + if (eTag == TAG_ALL || Z_GetTag(header) == eTag) + { +#ifdef _DEBUG + Z_Free((void*)((char*)header + sizeof(ZoneHeader) + + sizeof(ZoneDebugHeader))); +#else + Z_Free((void*)(header + 1)); +#endif + } + } +} + +void Z_SetNewDeleteTemporary(bool bTemp) +{ + if( bTemp ) + Z_PushNewDeleteTag( TAG_TEMP_WORKSPACE ); + else + Z_PopNewDeleteTag(); +} + +void *S_Malloc( int iSize ) +{ + return Z_Malloc(iSize, TAG_SMALL, qfalse, 0); +} + +int Z_GetLevelMemory(void) +{ +#ifdef _JK2MP + return s_Stats.m_SizesPerTag[TAG_BSP]; +#else + return s_Stats.m_SizesPerTag[TAG_HUNKALLOC] + + s_Stats.m_SizesPerTag[TAG_HUNKMISCMODELS] + + s_Stats.m_SizesPerTag[TAG_BSP]; +#endif +} + +#ifdef _JK2MP +int Z_GetHunkMemory(void) +{ + return s_Stats.m_SizesPerTag[TAG_HUNK_MARK1] + + s_Stats.m_SizesPerTag[TAG_HUNK_MARK2] + + s_Stats.m_SizesPerTag[TAG_TEMP_HUNKALLOC]; +} +#endif + +int Z_GetMiscMemory(void) +{ + return s_Stats.m_SizeAlloc - + (Z_GetLevelMemory() + +#ifdef _JK2MP + Z_GetHunkMemory() + +#endif + s_Stats.m_SizesPerTag[TAG_MODEL_GLM] + + s_Stats.m_SizesPerTag[TAG_MODEL_GLA] + + s_Stats.m_SizesPerTag[TAG_MODEL_MD3] + + s_Stats.m_SizesPerTag[TAG_BINK] + + s_Stats.m_SizesPerTag[TAG_SND_RAWDATA]); +} + +#ifdef _GAMECUBE +static int texMemSize = 0; +#else +extern int texMemSize; +#endif +void Z_CompactStats(void) +{ + assert(s_Initialized); + + // Quick report on all tags: + for( int t = 0; t < TAG_COUNT; ++t ) + Com_PrintfAlways("%d\n", s_Stats.m_SizesPerTag[t]); + + //This report is conservative. Divides by 1000 instead of 1024 and + //then rounds up. + static int printHeader = 1; + if (printHeader) + { + Sys_Log("memory-map.txt", "**Z_CompactStats Start**\n\n"); + Sys_Log("memory-map.txt", "Map:\tOV:\tLVL:\tGLM:\tGLA:\tMD3:\tSND:\tTEX:\tHNK:\tTHNK:\tMSC:\tFrZN:\tFrPH:\n"); + printHeader = 0; + } + + MEMORYSTATUS stat; + GlobalMemoryStatus(&stat); + + Sys_Log("memory-map.txt", va("%s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", + Cvar_VariableString( "mapname" ), + (s_Stats.m_OverheadAlloc / 1000) + 1, + (Z_GetLevelMemory() / 1000) + 1, + (s_Stats.m_SizesPerTag[TAG_MODEL_GLM] / 1000) + 1, + (s_Stats.m_SizesPerTag[TAG_MODEL_GLA] / 1000) + 1, + (s_Stats.m_SizesPerTag[TAG_MODEL_MD3] / 1000) + 1, +// (Z_GetTerrainMemory() / 1000) + 1, + (s_Stats.m_SizesPerTag[TAG_SND_RAWDATA] / 1000) + 1, + (texMemSize / 1000) + 1, +// texturePoint, +// (s_Stats.m_SizesPerTag[TAG_BINK] / 1000) + 1, + ((s_Stats.m_SizesPerTag[TAG_HUNK_MARK1] + s_Stats.m_SizesPerTag[TAG_HUNK_MARK2]) / 1000) + 1, + (s_Stats.m_SizesPerTag[TAG_TEMP_HUNKALLOC] / 1000) + 1, + (Z_GetMiscMemory() / 1000) + 1, + s_Stats.m_SizeFree, + stat.dwAvailPhys)); + + + //Sys_Log("memory-map.txt", va("Free Zone: %d\n", s_Stats.m_SizeFree)); + +} + + +static void Z_Stats_f(void) +{ +#ifndef FINAL_BUILD + assert(s_Initialized); + // Display some memory usage summary information... + + Com_PrintfAlways("\nThe zone is using %d bytes (%.2fMB) in %d memory blocks\n", + s_Stats.m_SizeAlloc, + (float)s_Stats.m_SizeAlloc / 1024.0f / 1024.0f, + s_Stats.m_CountAlloc); + + Com_PrintfAlways("Free memory is %d bytes (%.2fMB) in %d memory blocks\n", + s_Stats.m_SizeFree, + (float)s_Stats.m_SizeFree / 1024.0f / 1024.0f, + s_Stats.m_CountFree); + + Com_PrintfAlways("The zone peaked at %d bytes (%.2fMB)\n", + s_Stats.m_PeakAlloc, + (float)s_Stats.m_PeakAlloc / 1024.0f / 1024.0f); + + Com_PrintfAlways("The zone overhead is %d bytes (%.2fMB)\n", + s_Stats.m_OverheadAlloc, + (float)s_Stats.m_OverheadAlloc / 1024.0f / 1024.0f); +#endif +} + +void Z_Details_f(void) +{ +#ifndef FINAL_BUILD + assert(s_Initialized); + // Display some tag specific information... + + Com_PrintfAlways("---------------------------------------------------------------------------\n"); + Com_PrintfAlways("%20s %9s\n","Zone Tag","Bytes"); + Com_PrintfAlways("%20s %9s\n","--------","-----"); + for (int i=0; im_Next) + { + while (fblock->m_Address > cur + 1024) + { + WRITECHAR("*"); + } + + if (fblock->m_Address > cur && fblock->m_Address < cur + 1024) + { + WRITECHAR("+"); + } + + while (fblock->m_Address + fblock->m_Size > cur + 1024) + { + WRITECHAR("-"); + } + + if (fblock->m_Address + fblock->m_Size > cur && + fblock->m_Address + fblock->m_Size < cur + 1024) + { + WRITECHAR("+"); + } + } + + Sys_Log("memmap.txt", "\n"); +} + +void Z_DisplayLevelMemory(int size, int surf, int block) +{ + Z_DumpMemMap_f(); + + //Yes, it should be divided by 1024, but I'm going for a safety margin + //by rounding down. + //Com_Printf("level memory used: %d KB\n", size / 1000); + //Z_CompactStats(size, surf, block); + Z_CompactStats(); +} + +void Z_DisplayLevelMemory(void) +{ +#ifdef _GAMECUBE + extern void R_SurfMramUsed(int &surface, int &block); + int surface, block; + R_SurfMramUsed(surface, block); + Z_DisplayLevelMemory(Z_GetLevelMemory(), surface, block); +#else + Z_DisplayLevelMemory(Z_GetLevelMemory(), 0, 0); +#endif +} + + +/* +======================== +CopyString + + NOTE: never write over the memory CopyString returns because + memory from a memstatic_t might be returned +======================== +*/ +char *CopyString( const char *in ) +{ + struct ZoneSingleChar + { + ZoneHeader header; +#ifdef _DEBUG + ZoneDebugHeader start; +#endif + char data[2]; +#ifdef _DEBUG + ZoneDebugFooter end; +#endif + }; + +#ifdef _DEBUG + static ZoneSingleChar empty = {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "\0", ZONE_MAGIC}; + static ZoneSingleChar numbers[10] = + { + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "0", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "1", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "2", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "3", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "4", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "5", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "6", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "7", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "8", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "9", ZONE_MAGIC}, + }; +#else + static ZoneSingleChar empty = {(TAG_STATIC << 25) | 2, "\0"}; + static ZoneSingleChar numbers[10] = + { + {(TAG_STATIC << 25) | 2, "0"}, + {(TAG_STATIC << 25) | 2, "1"}, + {(TAG_STATIC << 25) | 2, "2"}, + {(TAG_STATIC << 25) | 2, "3"}, + {(TAG_STATIC << 25) | 2, "4"}, + {(TAG_STATIC << 25) | 2, "5"}, + {(TAG_STATIC << 25) | 2, "6"}, + {(TAG_STATIC << 25) | 2, "7"}, + {(TAG_STATIC << 25) | 2, "8"}, + {(TAG_STATIC << 25) | 2, "9"}, + }; +#endif + + char *out; + + if (!in[0]) + { + return empty.data; + } + else if (!in[1]) + { + if (in[0] >= '0' && in[0] <= '9') + { + return numbers[in[0]-'0'].data; + } + } + + out = (char *) S_Malloc (strlen(in)+1); + strcpy (out, in); + +// Z_Label(out,in); + + return out; +} + +void Com_TouchMemory(void) +{ + // Stub function. Do nothing. + return; +} + +qboolean Z_IsFromZone(void *pvAddress, memtag_t eTag) +{ + assert(s_Initialized); + +#ifdef _DEBUG + ZoneDebugHeader* debug = (ZoneDebugHeader*)pvAddress - 1; + + if (*debug != ZONE_MAGIC) + { + return qfalse; + } + + pvAddress = (void*)debug; +#endif + + ZoneHeader* header = (ZoneHeader*)pvAddress - 1; + + if (Z_GetTag(header) != eTag) + { + return qfalse; + } + + return Z_GetSize(header); +} + + +/* + Hunk emulation - PC switched to system similar to ours. I made the remaining + changes so that the two are identical. +*/ +#ifdef _JK2MP + +qboolean Com_TheHunkMarkHasBeenMade(void) +{ + if (hunk_tag == TAG_HUNK_MARK2) + { + return qtrue; + } + return qfalse; +} + +/* +================= +Com_InitHunkMemory +================= +*/ +void Com_InitHunkMemory(void) +{ + hunk_tag = TAG_HUNK_MARK1; + Hunk_Clear(); +} + +/* +==================== +Hunk_MemoryRemaining +==================== +*/ +int Hunk_MemoryRemaining(void) +{ + return 0; +} + +/* +=================== +Hunk_SetMark + +The server calls this after the level and game VM have been loaded +=================== +*/ +void Hunk_SetMark(void) +{ + hunk_tag = TAG_HUNK_MARK2; +} + +/* +================= +Hunk_ClearToMark + +The client calls this before starting a vid_restart or snd_restart +================= +*/ +void Hunk_ClearToMark(void) +{ + assert(hunk_tag == TAG_HUNK_MARK2); //if this is not true then no mark has been made + Z_TagFree(TAG_HUNK_MARK2); +} + +/* +================= +Hunk_CheckMark +================= +*/ +qboolean Hunk_CheckMark( void ) +{ + if (hunk_tag != TAG_HUNK_MARK1) + { + return qtrue; + } + return qfalse; +} + +/* +================= +Hunk_Clear + +The server calls this before shutting down or loading a new map +VVFIXME - PC version does lots of other things in here. +================= +*/ +void R_HunkClearCrap(void); +void Hunk_Clear(void) +{ + hunk_tag = TAG_HUNK_MARK1; + Z_TagFree(TAG_HUNK_MARK1); + Z_TagFree(TAG_HUNK_MARK2); + + R_HunkClearCrap(); +/* + Z_TagFree(TAG_HUNKALLOC); + Z_TagFree(TAG_BSP_HUNK); + Z_TagFree(TAG_BOT_HUNK); + Z_TagFree(TAG_RENDERER_HUNK); + Z_TagFree(TAG_SKELETON); + Z_TagFree(TAG_MODEL_OTHER); + Z_TagFree(TAG_MODEL_CHAR); + VM_Clear(); +*/ +} + +/* +================= +Hunk_Alloc + +Allocate permanent (until the hunk is cleared) memory +================= +*/ +void *Hunk_Alloc(int size, ha_pref preference) +{ + return Z_Malloc(size, hunk_tag, qtrue); +} + +/* +================= +Hunk_AllocateTempMemory + +This is used by the file loading system. +Multiple files can be loaded in temporary memory. +When the files-in-use count reaches zero, all temp memory will be deleted +================= +*/ +void *Hunk_AllocateTempMemory(int size) +{ + // don't bother clearing, because we are going to load a file over it + return Z_Malloc(size, TAG_TEMP_HUNKALLOC, qfalse); +} + +/* +================== +Hunk_FreeTempMemory +================== +*/ +void Hunk_FreeTempMemory(void *buf) +{ + Z_Free(buf); +} + +/* +================= +Hunk_ClearTempMemory + +The temp space is no longer needed. If we have left more +touched but unused memory on this side, have future +permanent allocs use this side. +================= +*/ +void Hunk_ClearTempMemory(void) +{ + Z_TagFree(TAG_TEMP_HUNKALLOC); +} + +#endif // _JK2MP + +/* + XTL Replacement functions + XMemAlloc + XMemFree + XMemSize + + Replacing these lets us intercept ALL memory allocation done by the XTL, and lets the + Zone take pretty much all available memory at startup +*/ +/* This still doesn't work. Numrous allocations still use internal functions, so there's + little benefit right now. + +XBOXAPI +LPVOID +WINAPI +XMemAlloc(SIZE_T dwSize, DWORD dwAllocAttributes) +{ + // We always give XTL 16 byte aligned memory + return Z_Malloc(dwSize, TAG_XTL, ((PXALLOC_ATTRIBUTES)&dwAllocAttributes)->dwZeroInitialize, 16); +} + +XBOXAPI +VOID +WINAPI +XMemFree(PVOID pAddress, DWORD dwAllocAttributes) +{ + Z_Free(pAddress); +} + +XBOXAPI +SIZE_T +WINAPI +XMemSize(PVOID pAddress, DWORD dwAllocAttributes) +{ + return Z_Size(pAddress); +} + +*/ + + +void PrintMem(void) +{ + Com_PrintfAlways("free mem: %d\n", (s_Stats.m_SizeFree + s_Stats.m_SizesPerTag[TAG_SND_RAWDATA]) / 1000); +} diff --git a/codemp/qcommon/z_memman_pc.cpp b/codemp/qcommon/z_memman_pc.cpp new file mode 100644 index 0000000..f2ae864 --- /dev/null +++ b/codemp/qcommon/z_memman_pc.cpp @@ -0,0 +1,832 @@ +// Created 3/13/03 by Brian Osman (VV) - Split Zone/Hunk from common + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "platform.h" + +//////////////////////////////////////////////// +// +#ifdef TAGDEF // itu? +#undef TAGDEF +#endif +#define TAGDEF(blah) #blah +const static char *psTagStrings[TAG_COUNT+1]= // +1 because TAG_COUNT will itself become a string here. Oh well. +{ + #include "../qcommon/tags.h" +}; +// +//////////////////////////////////////////////// + +static void Z_Details_f(void); +void CIN_CloseAllVideos(); + + +// This handles zone memory allocation. +// It is a wrapper around malloc with a tag id and a magic number at the start + +#define ZONE_MAGIC 0x21436587 + +typedef struct zoneHeader_s +{ + int iMagic; + memtag_t eTag; + int iSize; +struct zoneHeader_s *pNext; +struct zoneHeader_s *pPrev; +} zoneHeader_t; + +typedef struct +{ + int iMagic; + +} zoneTail_t; + +static inline zoneTail_t *ZoneTailFromHeader(zoneHeader_t *pHeader) +{ + return (zoneTail_t*) ( (char*)pHeader + sizeof(*pHeader) + pHeader->iSize ); +} + +#ifdef DETAILED_ZONE_DEBUG_CODE +map mapAllocatedZones; +#endif + + +typedef struct zoneStats_s +{ + int iCount; + int iCurrent; + int iPeak; + + // I'm keeping these updated on the fly, since it's quicker for cache-pool + // purposes rather than recalculating each time... + // + int iSizesPerTag [TAG_COUNT]; + int iCountsPerTag[TAG_COUNT]; + +} zoneStats_t; + +typedef struct zone_s +{ + zoneStats_t Stats; + zoneHeader_t Header; +} zone_t; + +cvar_t *com_validateZone; + +zone_t TheZone = {0}; + + +// Scans through the linked list of mallocs and makes sure no data has been overwritten + +void Z_Validate(void) +{ + if(!com_validateZone || !com_validateZone->integer) + { + return; + } + + zoneHeader_t *pMemory = TheZone.Header.pNext; + while (pMemory) + { + #ifdef DETAILED_ZONE_DEBUG_CODE + // this won't happen here, but wtf? + int& iAllocCount = mapAllocatedZones[pMemory]; + if (iAllocCount <= 0) + { + Com_Error(ERR_FATAL, "Z_Validate(): Bad block allocation count!"); + return; + } + #endif + + if(pMemory->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone header!"); + return; + } + + if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone tail!"); + return; + } + + pMemory = pMemory->pNext; + } +} + + + +// static mem blocks to reduce a lot of small zone overhead +// +#pragma pack(push) +#pragma pack(1) +typedef struct +{ + zoneHeader_t Header; +// byte mem[0]; + zoneTail_t Tail; +} StaticZeroMem_t; + +typedef struct +{ + zoneHeader_t Header; + byte mem[2]; + zoneTail_t Tail; +} StaticMem_t; +#pragma pack(pop) + +StaticZeroMem_t gZeroMalloc = + { {ZONE_MAGIC, TAG_STATIC,0,NULL,NULL},{ZONE_MAGIC}}; +StaticMem_t gEmptyString = + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'\0','\0',{ZONE_MAGIC}}; +StaticMem_t gNumberString[] = { + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'0','\0',{ZONE_MAGIC}}, + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'1','\0',{ZONE_MAGIC}}, + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'2','\0',{ZONE_MAGIC}}, + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'3','\0',{ZONE_MAGIC}}, + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'4','\0',{ZONE_MAGIC}}, + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'5','\0',{ZONE_MAGIC}}, + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'6','\0',{ZONE_MAGIC}}, + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'7','\0',{ZONE_MAGIC}}, + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'8','\0',{ZONE_MAGIC}}, + { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'9','\0',{ZONE_MAGIC}}, +}; + +qboolean gbMemFreeupOccured = qfalse; +void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit /* = qfalse */, int iUnusedAlign /* = 4 */) +{ + gbMemFreeupOccured = qfalse; + + if (iSize == 0) + { + zoneHeader_t *pMemory = (zoneHeader_t *) &gZeroMalloc; + return &pMemory[1]; + } + + // Add in tracking info + // + int iRealSize = (iSize + sizeof(zoneHeader_t) + sizeof(zoneTail_t)); + + // Allocate a chunk... + // + zoneHeader_t *pMemory = NULL; + while (pMemory == NULL) + { + #ifdef _WIN32 + if (gbMemFreeupOccured) + { + Sleep(1000); // sleep for a second, so Windows has a chance to shuffle mem to de-swiss-cheese it + } + #endif + + if (bZeroit) { + pMemory = (zoneHeader_t *) calloc ( iRealSize, 1 ); + } else { + pMemory = (zoneHeader_t *) malloc ( iRealSize ); + } + if (!pMemory) + { + // new bit, if we fail to malloc memory, try dumping some of the cached stuff that's non-vital and try again... + // + + // ditch the BSP cache... + // + extern qboolean CM_DeleteCachedMap(qboolean bGuaranteedOkToDelete); + if (CM_DeleteCachedMap(qfalse)) + { + gbMemFreeupOccured = qtrue; + continue; // we've just ditched a whole load of memory, so try again with the malloc + } + + + // ditch any sounds not used on this level... + // + extern qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel); + if (SND_RegisterAudio_LevelLoadEnd(qtrue)) + { + gbMemFreeupOccured = qtrue; + continue; // we've dropped at least one sound, so try again with the malloc + } + +#ifndef DEDICATED + // ditch any image_t's (and associated GL memory) not used on this level... + // + extern qboolean RE_RegisterImages_LevelLoadEnd(void); + if (RE_RegisterImages_LevelLoadEnd()) + { + gbMemFreeupOccured = qtrue; + continue; // we've dropped at least one image, so try again with the malloc + } +#endif + + // ditch the model-binaries cache... (must be getting desperate here!) + // + extern qboolean RE_RegisterModels_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel); + if (RE_RegisterModels_LevelLoadEnd(qtrue)) + { + gbMemFreeupOccured = qtrue; + continue; + } + + // as a last panic measure, dump all the audio memory, but not if we're in the audio loader + // (which is annoying, but I'm not sure how to ensure we're not dumping any memory needed by the sound + // currently being loaded if that was the case)... + // + // note that this keeps querying until it's freed up as many bytes as the requested size, but freeing + // several small blocks might not mean that one larger one is satisfiable after freeup, however that'll + // just make it go round again and try for freeing up another bunch of blocks until the total is satisfied + // again (though this will have freed twice the requested amount in that case), so it'll either work + // eventually or not free up enough and drop through to the final ERR_DROP. No worries... + // + extern qboolean gbInsideLoadSound; + extern int SND_FreeOldestSound(); + if (!gbInsideLoadSound) + { + int iBytesFreed = SND_FreeOldestSound(); + if (iBytesFreed) + { + int iTheseBytesFreed = 0; + while ( (iTheseBytesFreed = SND_FreeOldestSound()) != 0) + { + iBytesFreed += iTheseBytesFreed; + if (iBytesFreed >= iRealSize) + break; // early opt-out since we've managed to recover enough (mem-contiguity issues aside) + } + gbMemFreeupOccured = qtrue; + continue; + } + } + + // sigh, dunno what else to try, I guess we'll have to give up and report this as an out-of-mem error... + // + // findlabel: "recovermem" + + Com_Printf(S_COLOR_RED"Z_Malloc(): Failed to alloc %d bytes (TAG_%s) !!!!!\n", iSize, psTagStrings[eTag]); + Z_Details_f(); + Com_Error(ERR_FATAL,"(Repeat): Z_Malloc(): Failed to alloc %d bytes (TAG_%s) !!!!!\n", iSize, psTagStrings[eTag]); + return NULL; + } + } + + // Link in + pMemory->iMagic = ZONE_MAGIC; + pMemory->eTag = eTag; + pMemory->iSize = iSize; + pMemory->pNext = TheZone.Header.pNext; + TheZone.Header.pNext = pMemory; + if (pMemory->pNext) + { + pMemory->pNext->pPrev = pMemory; + } + pMemory->pPrev = &TheZone.Header; + // + // add tail... + // + ZoneTailFromHeader(pMemory)->iMagic = ZONE_MAGIC; + + // Update stats... + // + TheZone.Stats.iCurrent += iSize; + TheZone.Stats.iCount++; + TheZone.Stats.iSizesPerTag [eTag] += iSize; + TheZone.Stats.iCountsPerTag [eTag]++; + + if (TheZone.Stats.iCurrent > TheZone.Stats.iPeak) + { + TheZone.Stats.iPeak = TheZone.Stats.iCurrent; + } + +#ifdef DETAILED_ZONE_DEBUG_CODE + mapAllocatedZones[pMemory]++; +#endif + + Z_Validate(); // check for corruption + + void *pvReturnMem = &pMemory[1]; + return pvReturnMem; +} + +// used during model cacheing to save an extra malloc, lets us morph the disk-load buffer then +// just not fs_freefile() it afterwards. +// +void Z_MorphMallocTag( void *pvAddress, memtag_t eDesiredTag ) +{ + zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; + + if (pMemory->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_MorphMallocTag(): Not a valid zone header!"); + return; // won't get here + } + + // DEC existing tag stats... + // +// TheZone.Stats.iCurrent - unchanged +// TheZone.Stats.iCount - unchanged + TheZone.Stats.iSizesPerTag [pMemory->eTag] -= pMemory->iSize; + TheZone.Stats.iCountsPerTag [pMemory->eTag]--; + + // morph... + // + pMemory->eTag = eDesiredTag; + + // INC new tag stats... + // +// TheZone.Stats.iCurrent - unchanged +// TheZone.Stats.iCount - unchanged + TheZone.Stats.iSizesPerTag [pMemory->eTag] += pMemory->iSize; + TheZone.Stats.iCountsPerTag [pMemory->eTag]++; +} + +static void Zone_FreeBlock(zoneHeader_t *pMemory) +{ + if (pMemory->eTag != TAG_STATIC) // belt and braces, should never hit this though + { + // Update stats... + // + TheZone.Stats.iCount--; + TheZone.Stats.iCurrent -= pMemory->iSize; + TheZone.Stats.iSizesPerTag [pMemory->eTag] -= pMemory->iSize; + TheZone.Stats.iCountsPerTag [pMemory->eTag]--; + + // Sanity checks... + // + assert(pMemory->pPrev->pNext == pMemory); + assert(!pMemory->pNext || (pMemory->pNext->pPrev == pMemory)); + + // Unlink and free... + // + pMemory->pPrev->pNext = pMemory->pNext; + if(pMemory->pNext) + { + pMemory->pNext->pPrev = pMemory->pPrev; + } + free (pMemory); + + + #ifdef DETAILED_ZONE_DEBUG_CODE + // this has already been checked for in execution order, but wtf? + int& iAllocCount = mapAllocatedZones[pMemory]; + if (iAllocCount == 0) + { + Com_Error(ERR_FATAL, "Zone_FreeBlock(): Double-freeing block!"); + return; + } + iAllocCount--; + #endif + } +} + +// stats-query function to ask how big a malloc is... +// +int Z_Size(void *pvAddress) +{ + zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; + + if (pMemory->eTag == TAG_STATIC) + { + return 0; // kind of + } + + if (pMemory->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Size(): Not a valid zone header!"); + return 0; // won't get here + } + + return pMemory->iSize; +} + + +// Frees a block of memory... +// +void Z_Free(void *pvAddress) +{ + if (pvAddress == NULL) // I've put this in as a safety measure because of some bits of #ifdef BSPC stuff -Ste. + { + //Com_Error(ERR_FATAL, "Z_Free(): NULL arg"); + return; + } + + zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; + + if (pMemory->eTag == TAG_STATIC) + { + return; + } + + #ifdef DETAILED_ZONE_DEBUG_CODE + // + // check this error *before* barfing on bad magics... + // + int& iAllocCount = mapAllocatedZones[pMemory]; + if (iAllocCount <= 0) + { + Com_Error(ERR_FATAL, "Z_Free(): Block already-freed, or not allocated through Z_Malloc!"); + return; + } + #endif + + if (pMemory->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone header!"); + return; + } + if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone tail!"); + return; + } + + Zone_FreeBlock(pMemory); +} + + +int Z_MemSize(memtag_t eTag) +{ + return TheZone.Stats.iSizesPerTag[eTag]; +} + +// Frees all blocks with the specified tag... +// +void Z_TagFree(memtag_t eTag) +{ +//#ifdef _DEBUG +// int iZoneBlocks = TheZone.Stats.iCount; +//#endif + + zoneHeader_t *pMemory = TheZone.Header.pNext; + while (pMemory) + { + zoneHeader_t *pNext = pMemory->pNext; + if ( (eTag == TAG_ALL) || (pMemory->eTag == eTag)) + { + Zone_FreeBlock(pMemory); + } + pMemory = pNext; + } + +// these stupid pragmas don't work here???!?!?! +// +//#ifdef _DEBUG +//#pragma warning( disable : 4189) +// int iBlocksFreed = iZoneBlocks - TheZone.Stats.iCount; +//#pragma warning( default : 4189) +//#endif +} + + +void *S_Malloc( int iSize ) { + return Z_Malloc( iSize, TAG_SMALL ); +} + + +#ifdef _DEBUG +static void Z_MemRecoverTest_f(void) +{ + // needs to be in _DEBUG only, not good for final game! + // fixme: findmeste: Remove this sometime + // + int iTotalMalloc = 0; + while (1) + { + int iThisMalloc = 5* (1024 * 1024); + Z_Malloc(iThisMalloc, TAG_SPECIAL_MEM_TEST, qfalse); // and lose, just to consume memory + iTotalMalloc += iThisMalloc; + + if (gbMemFreeupOccured) + break; + } + + Z_TagFree(TAG_SPECIAL_MEM_TEST); +} +#endif + + + +// Gives a summary of the zone memory usage + +static void Z_Stats_f(void) +{ + Com_Printf("\nThe zone is using %d bytes (%.2fMB) in %d memory blocks\n", + TheZone.Stats.iCurrent, + (float)TheZone.Stats.iCurrent / 1024.0f / 1024.0f, + TheZone.Stats.iCount + ); + + Com_Printf("The zone peaked at %d bytes (%.2fMB)\n", + TheZone.Stats.iPeak, + (float)TheZone.Stats.iPeak / 1024.0f / 1024.0f + ); +} + +// Gives a detailed breakdown of the memory blocks in the zone + +static void Z_Details_f(void) +{ + Com_Printf("---------------------------------------------------------------------------\n"); + Com_Printf("%20s %9s\n","Zone Tag","Bytes"); + Com_Printf("%20s %9s\n","--------","-----"); + for (int i=0; i= '0' && in[0] <= '9') { + return ((char *)&gNumberString[in[0]-'0']) + sizeof(zoneHeader_t); + } + } + + out = (char *) S_Malloc (strlen(in)+1); + strcpy (out, in); + return out; +} + + + +static memtag_t hunk_tag; + + +/* +=============== +Com_TouchMemory + +Touch all known used data to make sure it is paged in +=============== +*/ +void Com_TouchMemory( void ) { +// int start, end; + int i, j; + int sum; + +// start = Sys_Milliseconds(); + Z_Validate(); + + sum = 0; + + zoneHeader_t *pMemory = TheZone.Header.pNext; + while (pMemory) + { + byte *pMem = (byte *) &pMemory[1]; + j = pMemory->iSize >> 2; + for (i=0; ipNext; + } + +// end = Sys_Milliseconds(); +// Com_Printf( "Com_TouchMemory: %i msec\n", end - start ); +} + + + +qboolean Com_TheHunkMarkHasBeenMade(void) +{ + if (hunk_tag == TAG_HUNK_MARK2) + { + return qtrue; + } + return qfalse; +} + +/* +================= +Com_InitHunkMemory +================= +*/ +void Com_InitHunkMemory( void ) { + hunk_tag = TAG_HUNK_MARK1; + Hunk_Clear(); +} + +void Com_ShutdownHunkMemory(void) +{ + //Er, ok. Clear it then I guess. + Z_TagFree(TAG_HUNK_MARK1); + Z_TagFree(TAG_HUNK_MARK2); +} + +/* +==================== +Hunk_MemoryRemaining +==================== +*/ +int Hunk_MemoryRemaining( void ) { + return (64*1024*1024) - (Z_MemSize(TAG_HUNK_MARK1)+Z_MemSize(TAG_HUNK_MARK2)); //Yeah. Whatever. We've got no size now. +} + +/* +=================== +Hunk_SetMark + +The server calls this after the level and game VM have been loaded +=================== +*/ +void Hunk_SetMark( void ) { + hunk_tag = TAG_HUNK_MARK2; +} + +/* +================= +Hunk_ClearToMark + +The client calls this before starting a vid_restart or snd_restart +================= +*/ +void Hunk_ClearToMark( void ) { + assert(hunk_tag == TAG_HUNK_MARK2); //if this is not true then no mark has been made + Z_TagFree(TAG_HUNK_MARK2); +} + +/* +================= +Hunk_CheckMark +================= +*/ +qboolean Hunk_CheckMark( void ) { + //if( hunk_low.mark || hunk_high.mark ) { + if (hunk_tag != TAG_HUNK_MARK1) + { + return qtrue; + } + return qfalse; +} + +void CL_ShutdownCGame( void ); +void CL_ShutdownUI( void ); +void SV_ShutdownGameProgs( void ); + +/* +================= +Hunk_Clear + +The server calls this before shutting down or loading a new map +================= +*/ +void R_HunkClearCrap(void); +#ifdef _FULL_G2_LEAK_CHECKING +void G2_DEBUG_ReportLeaks(void); +#endif + +void Hunk_Clear( void ) { + +#ifndef DEDICATED + CL_ShutdownCGame(); + CL_ShutdownUI(); +#endif + SV_ShutdownGameProgs(); + +#ifndef DEDICATED + CIN_CloseAllVideos(); +#endif + + hunk_tag = TAG_HUNK_MARK1; + Z_TagFree(TAG_HUNK_MARK1); + Z_TagFree(TAG_HUNK_MARK2); + + R_HunkClearCrap(); + +// Com_Printf( "Hunk_Clear: reset the hunk ok\n" ); + VM_Clear(); + +//See if any ghoul2 stuff was leaked, at this point it should be all cleaned up. +#ifdef _FULL_G2_LEAK_CHECKING + assert(g_Ghoul2Allocations == 0 && g_G2ClientAlloc == 0 && g_G2ServerAlloc == 0); + if (g_Ghoul2Allocations) + { + Com_Printf("%i bytes leaked by ghoul2 routines (%i client, %i server)\n", g_Ghoul2Allocations, g_G2ClientAlloc, g_G2ServerAlloc); + G2_DEBUG_ReportLeaks(); + } +#endif +} + +/* +================= +Hunk_Alloc + +Allocate permanent (until the hunk is cleared) memory +================= +*/ +void *Hunk_Alloc( int size, ha_pref preference ) { + return Z_Malloc(size, hunk_tag, qtrue); +} + +/* +================= +Hunk_AllocateTempMemory + +This is used by the file loading system. +Multiple files can be loaded in temporary memory. +When the files-in-use count reaches zero, all temp memory will be deleted +================= +*/ +void *Hunk_AllocateTempMemory( int size ) { + // don't bother clearing, because we are going to load a file over it + return Z_Malloc(size, TAG_TEMP_HUNKALLOC, qfalse); +} + + +/* +================== +Hunk_FreeTempMemory +================== +*/ +void Hunk_FreeTempMemory( void *buf ) +{ + Z_Free(buf); +} + + +/* +================= +Hunk_ClearTempMemory + +The temp space is no longer needed. If we have left more +touched but unused memory on this side, have future +permanent allocs use this side. +================= +*/ +void Hunk_ClearTempMemory( void ) { + Z_TagFree(TAG_TEMP_HUNKALLOC); +} diff --git a/codemp/ratl/bits_vs.h b/codemp/ratl/bits_vs.h new file mode 100644 index 0000000..d0bde48 --- /dev/null +++ b/codemp/ratl/bits_vs.h @@ -0,0 +1,218 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Bit Field +// --------- +// The bits class is a bit field of any length which supports all the +// standard bitwize operations in addition to some operators for adding & removing +// individual bits by their integer indicies and a string conversion method. +// +// +// +// NOTES: +// - The SIZE template variable determines how many BITS are available in this template, +// not how much memory (number of ints) were used to store it. +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_BITS_INC) +#define RATL_BITS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +namespace ratl +{ + +//////////////////////////////////////////////////////////////////////////////////////// +// The Bit Field Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class bits_vs : public bits_base +{ + //////////////////////////////////////////////////////////////////////////////////// + // Call This Function To Set All Bits Beyond SIZE to Zero + //////////////////////////////////////////////////////////////////////////////////// + void clear_trailing_bits() + { + for (int i=SIZE; i>BITS_SHIFT] &= ~(1<<(i&BITS_AND)); + } + } + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + SIZE = SZ, + CAPACITY = SZ, + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Standard Constructor + //////////////////////////////////////////////////////////////////////////////////// + bits_vs(bool init=true,bool initValue=false) : bits_base(init,initValue) + { + } + //////////////////////////////////////////////////////////////////////////////////// + // Copy Constructor + //////////////////////////////////////////////////////////////////////////////////// + bits_vs(const bits_vs &B) + { + mem::cpy(mV, B.mV,BYTE_SIZE); + } + + //////////////////////////////////////////////////////////////////////////////////// + // String Constructor (Format: "100010100101") + //////////////////////////////////////////////////////////////////////////////////// + bits_vs(const char* Str) + { + clear(); + + for (int b=0; b=0 && i < SIZE); + return ( (mV[i>>BITS_SHIFT] & (1<<(i&BITS_AND)))!=0 ); + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Checks If There Are Any Values At All In This Bit Field + //////////////////////////////////////////////////////////////////////////////////////// + bool operator!() const + { + return empty(); + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Equality Operator + //////////////////////////////////////////////////////////////////////////////////////// + bool operator==(const bits_vs &B) const + { + return (mem::eql(mV, B.mV,BYTE_SIZE)); + } + + //////////////////////////////////////////////////////////////////////////////////////// + // InEquality Operator + //////////////////////////////////////////////////////////////////////////////////////// + bool operator!=(const bits_vs &B) const + { + return !(operator==(B)); + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Or In From Another Bits Object + //////////////////////////////////////////////////////////////////////////////////////// + void operator|=(const bits_vs &B) + { + for (int i=0; i +#define ASSERT_H_INC +#endif + +#if !defined(STRING_H_INC) +#include +#define STRING_H_INC +#endif + + +//////////////////////////////////////////////////////////////////////////////////////// +// Forward Dec. +//////////////////////////////////////////////////////////////////////////////////////// +class hfile; + + + +// I don't know why this needs to be in the global namespace, but it does +class TRatlNew; +inline void *operator new(size_t,TRatlNew *where) +{ + return where; +} + +inline void operator delete(void *, TRatlNew *) +{ + return; +} + +namespace ratl +{ + + + +#ifdef _DEBUG +extern int HandleSaltValue; //this is used in debug for global uniqueness of handles +extern int FoolTheOptimizer; //this is used to make sure certain things aren't optimized out +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// All Raven Template Library Internal Memory Operations +// +// This is mostly for future use. For now, they only provide a simple interface with +// a couple extra functions (eql and clr). +//////////////////////////////////////////////////////////////////////////////////////// +namespace mem +{ +//////////////////////////////////////////////////////////////////////////////////////// +// The Align Struct Is The Root Memory Structure for Inheritance and Object Semantics +// +// In most cases, we just want a simple int. However, sometimes we need to use an +// unsigned character array +// +//////////////////////////////////////////////////////////////////////////////////////// +#if defined(_MSC_VER) && !defined(__MWERKS__) + struct alignStruct + { + int space; + }; +#else + struct alignStruct + { + unsigned char space[16]; + } __attribute__ ((aligned(16))); +#endif + + inline void* cpy( void *dest, const void *src, size_t count ) + { + return memcpy(dest, src, count); + } + inline void* set( void *dest, int c, size_t count ) + { + return memset(dest, c, count); + } + inline int cmp( const void *buf1, const void *buf2, size_t count ) + { + return memcmp( buf1, buf2, count ); + } + inline bool eql( const void *buf1, const void *buf2, size_t count ) + { + return (memcmp( buf1, buf2, count )==0); + } + inline void* zero( void *dest, size_t count ) + { + return memset(dest, 0, count); + } + + template + inline void cpy( T *dest, const T *src) + { + cpy(dest, src, sizeof(T)); + } + template + inline void set(T *dest, int c) + { + set(dest, c, sizeof(T)); + } + + template + inline void swap(T *s1, T *s2) + { + unsigned char temp[sizeof(T)]; + cpy((T *)temp,s1); + cpy(s1,s2); + cpy(s2,(T *)temp); + } + + template + inline int cmp( const T *buf1, const T *buf2) + { + return cmp( buf1, buf2, sizeof(T) ); + } + + template + inline bool eql( const T *buf1, const T *buf2) + { + return cmp( buf1, buf2,sizeof(T))==0; + } + + template + inline void zero( T *dest ) + { + return set(dest, 0, sizeof(T)); + } +} + +namespace str +{ + inline int len(const char *src) + { + return strlen(src); + } + + inline void cpy(char *dest,const char *src) + { + strcpy(dest,src); + } + + inline void ncpy(char *dest,const char *src,int destBufferLen) + { + strncpy(dest,src,destBufferLen); + } + + inline void cat(char *dest,const char *src) + { + strcat(dest,src); + } + + inline void ncat(char *dest,const char *src,int destBufferLen) + { + ncpy(dest+len(dest),src,destBufferLen-len(dest)); + } + + inline int cmp(const char *s1,const char *s2) + { + return strcmp(s1,s2); + } + inline bool eql(const char *s1,const char *s2) + { + return !strcmp(s1,s2); + } + inline int icmp(const char *s1,const char *s2) + { + return stricmp(s1,s2); + } + inline int cmpi(const char *s1,const char *s2) + { + return stricmp(s1,s2); + } + inline bool ieql(const char *s1,const char *s2) + { + return !stricmp(s1,s2); + } + inline bool eqli(const char *s1,const char *s2) + { + return !stricmp(s1,s2); + } + + inline char *tok(char *s,const char *gap) + { + return strtok(s,gap); + } + + void to_upper(char *dest); + void to_lower(char *dest); + void printf(char *dest,const char *formatS, ...); +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Raven Template Library Compile Assert +// +// If, during compile time the stuff under (condition) is zero, this code will not +// compile. +//////////////////////////////////////////////////////////////////////////////////////// +template +class compile_assert +{ +#ifdef _DEBUG + int junk[(1 - (2 * !condition))]; // Look At Where This Was Being Compiled +public: + compile_assert() + { + assert(condition); + junk[0]=FoolTheOptimizer++; + } + int operator()() + { + assert(condition); + FoolTheOptimizer++; + return junk[0]; + } +#else +public: + int operator()() + { + return 1; + } +#endif; +}; + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Raven Template Library Base Class +// +// This is the base class for all the Raven Template Library container classes like +// vector_vs and pool_vs. +// +// This class might be a good place to put memory profile code in the future. +// +//////////////////////////////////////////////////////////////////////////////////////// +class ratl_base +{ +public: +#ifndef _XBOX + void save(hfile& file); + void load(hfile& file); +#endif + + void ProfilePrint(const char * format, ...); + +public: + static void* OutputPrint; +}; + + +//////////////////////////////////////////////////////////////////////////////////////// +// this is a simplified version of bits_vs +//////////////////////////////////////////////////////////////////////////////////////// +template +class bits_base +{ +protected: + enum + { + BITS_SHIFT = 5, // 5. Such A Nice Number + BITS_INT_SIZE = 32, // Size Of A Single Word + BITS_AND = (BITS_INT_SIZE - 1), // Used For And Operation + ARRAY_SIZE = ((SZ + BITS_AND)/(BITS_INT_SIZE)), // Num Words Used + BYTE_SIZE = (ARRAY_SIZE*sizeof(unsigned int)), // Num Bytes Used + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + unsigned int mV[ARRAY_SIZE]; +public: + enum + { + SIZE = SZ, + CAPACITY = SZ, + }; + + bits_base(bool init=true,bool initValue=false) + { + if (init) + { + if (initValue) + { + set(); + } + else + { + clear(); + } + } + } + void clear() + { + mem::zero(&mV,BYTE_SIZE); + } + void set() + { + mem::set(&mV, 0xff,BYTE_SIZE); + } + + void set_bit(const int i) + { + assert(i>=0 && i < SIZE); + mV[i>>BITS_SHIFT] |= (1<<(i&BITS_AND)); + } + void clear_bit(const int i) + { + assert(i>=0 && i < SIZE); + mV[i>>BITS_SHIFT] &= ~(1<<(i&BITS_AND)); + } + void mark_bit(const int i, bool set) + { + assert(i>=0 && i < SIZE); + if (set) + { + mV[i>>BITS_SHIFT] |= (1<<(i&BITS_AND)); + } + else + { + mV[i>>BITS_SHIFT] &= ~(1<<(i&BITS_AND)); + } + } + bool operator[](const int i) const + { + assert(i>=0 && i < SIZE); + return (mV[i>>BITS_SHIFT] & (1<<(i&BITS_AND)))!=0; + } + int next_bit(int start=0,bool onBit=true) const + { + assert(start>=0&&start<=SIZE); //we have to accept start==size for the way the loops are done + if (start>=SIZE) + { + return SIZE; // Did Not Find + } + // Get The Word Which Contains The Start Bit & Mask Out Everything Before The Start Bit + //-------------------------------------------------------------------------------------- + unsigned int v = mV[start>>BITS_SHIFT]; + if (!onBit) + { + v= (~v); + } + v >>= (start&31); + + + // Search For The First Non Zero Word In The Array + //------------------------------------------------- + while(!v) + { + start = (start & (~(BITS_INT_SIZE-1))) + BITS_INT_SIZE; + if (start>=SIZE) + { + return SIZE; // Did Not Find + } + v = mV[start>>BITS_SHIFT]; + if (!onBit) + { + v= (~v); + } + } + + + // So, We've Found A Non Zero Word, So Start Masking Against Parts To Skip Over Bits + //----------------------------------------------------------------------------------- + if (!(v&0xffff)) + { + start+=16; + v>>=16; + } + if (!(v&0xff)) + { + start+=8; + v>>=8; + } + if (!(v&0xf)) + { + start+=4; + v>>=4; + } + + // Time To Search Each Bit + //------------------------- + while(!(v&1)) + { + start++; + v>>=1; + } + if (start>=SIZE) + { + return SIZE; + } + return start; + } +}; + + +//////////////////////////////////////////////////////////////////////////////////////// +// Raven Standard Compare Class +//////////////////////////////////////////////////////////////////////////////////////// +struct ratl_compare +{ + float mCost; + int mHandle; + + bool operator<(const ratl_compare& t) const + { + return (mCost + struct value_semantics + { + enum + { + CAPACITY = SIZE, + }; + typedef T TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + typedef T TStorage; // this is what we make our array of + + typedef bits_true TConstructed; + typedef TStorage TArray[SIZE]; + + + enum + { + NEEDS_CONSTRUCT=0, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=sizeof(TStorage), + }; + static void construct(TStorage *) + { + + } + static void construct(TStorage *me,const TValue &v) + { + *me=v; + } + static void destruct(TStorage *) + { + + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)me; + } + static T * ptr(TStorage *me) + { + return me; + } + static const T * ptr(const TStorage *me) + { + return me; + } + static T & ref(TStorage *me) + { + return *me; + } + static const T & ref(const TStorage *me) + { + return *me; + } + static T *raw_array(TStorage *me) + { + return me; + } + static const T *raw_array(const TStorage *me) + { + return me; + } + static void swap(TStorage *s1,TStorage *s2) + { + mem::swap(ptr(s1),ptr(s2)); + } + static int pointer_to_index(const void *s1,const void *s2) + { + return ((TStorage *)s1)-((TStorage *)s2); + } + }; + template + struct object_semantics + { + enum + { + CAPACITY = SIZE, + }; + typedef mem::alignStruct TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + + typedef bits_base TConstructed; + + struct TStorage + { + TAlign mMemory[((sizeof(T) + sizeof(TAlign) -1 )/sizeof(TAlign))]; + }; + typedef TStorage TArray[SIZE]; + + enum + { + NEEDS_CONSTRUCT=1, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=sizeof(TStorage), + }; + + static void construct(TStorage *me) + { + new(raw(me)) TValue(); + } + static void construct(TStorage *me,const TValue &v) + { + new(raw(me)) TValue(v); + } + static void destruct(TStorage *me) + { + ptr(me)->~T(); + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)me; + } + static T * ptr(TStorage *me) + { + return (T *)me; + } + static const T * ptr(const TStorage *me) + { + return (const T *)me; + } + static T & ref(TStorage *me) + { + return *(T *)me; + } + static const T & ref(const TStorage *me) + { + return *(const T *)me; + } + static void swap(TStorage *s1,TStorage *s2) + { + TValue temp(ref(s1)); + ref(s1)=ref(s2); + ref(s2)=temp; + } + static int pointer_to_index(const void *s1,const void *s2) + { + return ((TStorage *)s1)-((TStorage *)s2); + } + }; + template + struct virtual_semantics + { + enum + { + CAPACITY = SIZE, + }; + typedef mem::alignStruct TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + + typedef bits_base TConstructed; + + struct TStorage + { + TAlign mMemory[((MAX_CLASS_SIZE + sizeof(TAlign) -1 )/sizeof(TAlign))]; + }; + typedef TStorage TArray[SIZE]; + + enum + { + NEEDS_CONSTRUCT=1, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=MAX_CLASS_SIZE, + }; + + static void construct(TStorage *me) + { + new(raw(me)) TValue(); + } + static void destruct(TStorage *me) + { + ptr(me)->~T(); + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)me; + } + static T * ptr(TStorage *me) + { + return (T *)me; + } + static const T * ptr(const TStorage *me) + { + return (const T *)me; + } + static T & ref(TStorage *me) + { + return *(T *)me; + } + static const T & ref(const TStorage *me) + { + return *(const T *)me; + } + // this is a bit suspicious, we are forced to do a memory swap, and for a class, that, say + // stores a pointer to itself, it won't work right + static void swap(TStorage *s1,TStorage *s2) + { + mem::swap(s1,s2); + } + static int pointer_to_index(const void *s1,const void *s2) + { + return ((TStorage *)s1)-((TStorage *)s2); + } + template + static CAST_TO *verify_alloc(CAST_TO *p) + { +#ifdef _DEBUG + assert(p); + assert(dynamic_cast(p)); + T *ptr=p; // if this doesn't compile, you are trying to alloc something that is not derived from base + assert(dynamic_cast(ptr)); + int i=VALUE_SIZE; + int k=MAX_CLASS_SIZE; + int j=sizeof(CAST_TO); + compile_assert(); + assert(sizeof(CAST_TO)<=MAX_CLASS_SIZE); +#endif + return p; + } + }; + + // The below versions are for nodes + + template + struct value_semantics_node + { + enum + { + CAPACITY = SIZE, + }; + struct SNode + { + NODE nodeData; + T value; + }; + typedef SNode TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + typedef SNode TStorage; // this is what we make our array of + + typedef bits_true TConstructed; + typedef TStorage TArray[SIZE]; + + enum + { + NEEDS_CONSTRUCT=0, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=sizeof(TValue), + }; + static void construct(TStorage *) + { + + } + static void construct(TStorage *me,const TValue &v) + { + me->value=v; + } + static void destruct(TStorage *) + { + + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)&me->value; + } + static T * ptr(TStorage *me) + { + return &me->value; + } + static const T * ptr(const TStorage *me) + { + return &me->value; + } + static T & ref(TStorage *me) + { + return me->value; + } + static const T & ref(const TStorage *me) + { + return me->value; + } + // this ugly unsafe cast-hack is a backhanded way of getting the node data from the value data + // this is so node support does not need to be added to the primitive containers + static NODE & node(TValue &v) + { + return *(NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + static const NODE & node(const TValue &v) + { + return *(const NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + static void swap(TStorage *s1,TStorage *s2) + { + mem::swap(&s1->value,&s2->value); + } + // this is hideous + static int pointer_to_index(const void *s1,const void *s2) + { + return + ((TStorage *)(((unsigned char *)s1)-int(&((TStorage *)0)->value))) - + ((TStorage *)(((unsigned char *)s2)-int(&((TStorage *)0)->value))); + } + }; + + template + struct object_semantics_node + { + enum + { + CAPACITY = SIZE, + }; + typedef mem::alignStruct TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + + typedef bits_base TConstructed; + + struct TValueStorage + { + TAlign mMemory[((sizeof(T) + sizeof(TAlign) -1 )/sizeof(TAlign))]; + }; + struct SNode + { + NODE nodeData; + TValueStorage value; + }; + typedef SNode TStorage; // this is what we make our array of + typedef TStorage TArray[SIZE]; + + + enum + { + NEEDS_CONSTRUCT=0, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=sizeof(TValueStorage), + }; + + static void construct(TStorage *me) + { + new(raw(me)) TValue(); + } + static void construct(TStorage *me,const TValue &v) + { + new(raw(me)) TValue(v); + } + static void destruct(TStorage *me) + { + ptr(me)->~T(); + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)&me->value; + } + static T * ptr(TStorage *me) + { + return (T *)&me->value; + } + static const T * ptr(const TStorage *me) + { + return (const T *)&me->value; + } + static T & ref(TStorage *me) + { + return *(T *)&me->value; + } + static const T & ref(const TStorage *me) + { + return *(const T *)&me->value; + } + static NODE & node(TStorage *me) + { + return me->nodeData; + } + static const NODE & node(const TStorage *me) + { + return me->nodeData; + } + // this ugly unsafe cast-hack is a backhanded way of getting the node data from the value data + // this is so node support does not need to be added to the primitive containers + static NODE & node(TValue &v) + { + return *(NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + static const NODE & node(const TValue &v) + { + return *(const NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + static void swap(TStorage *s1,TStorage *s2) + { + TValue temp(ref(s1)); + ref(s1)=ref(s2); + ref(s2)=temp; + } + // this is hideous + static int pointer_to_index(const void *s1,const void *s2) + { + return + ((TStorage *)(((unsigned char *)s1)-int(&((TStorage *)0)->value))) - + ((TStorage *)(((unsigned char *)s2)-int(&((TStorage *)0)->value))); + } + }; + template + struct virtual_semantics_node + { + enum + { + CAPACITY = SIZE, + }; + typedef mem::alignStruct TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + + typedef bits_base TConstructed; + + struct TValueStorage + { + TAlign mMemory[((MAX_CLASS_SIZE + sizeof(TAlign) -1 )/sizeof(TAlign))]; + }; + struct SNode + { + NODE nodeData; + TValueStorage value; + }; + typedef SNode TStorage; // this is what we make our array of + typedef TStorage TArray[SIZE]; + + enum + { + NEEDS_CONSTRUCT=1, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=sizeof(TValueStorage), + }; + + static void construct(TStorage *me) + { + new(raw(me)) TValue(); + } + static void destruct(TStorage *me) + { + ptr(me)->~T(); + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)&me->value; + } + static T * ptr(TStorage *me) + { + return (T *)&me->value; + } + static const T * ptr(const TStorage *me) + { + return (const T *)&me->value; + } + static T & ref(TStorage *me) + { + return *(T *)&me->value; + } + static const T & ref(const TStorage *me) + { + return *(const T *)&me->value; + } + static NODE & node(TStorage *me) + { + return me->nodeData; + } + static const NODE & node(const TStorage *me) + { + return me->nodeData; + } + // this ugly unsafe cast-hack is a backhanded way of getting the node data from the value data + // this is so node support does not need to be added to the primitive containers + static NODE & node(TValue &v) + { + return *(NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + static const NODE & node(const TValue &v) + { + return *(const NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + // this is a bit suspicious, we are forced to do a memory swap, and for a class, that, say + // stores a pointer to itself, it won't work right + static void swap(TStorage *s1,TStorage *s2) + { + mem::swap(&s1->value,&s2->value); + } + // this is hideous + static int pointer_to_index(const void *s1,const void *s2) + { + return + ((TStorage *)(((unsigned char *)s1)-int(&((TStorage *)0)->value))) - + ((TStorage *)(((unsigned char *)s2)-int(&((TStorage *)0)->value))); + } + template + static CAST_TO *verify_alloc(CAST_TO *p) + { +#ifdef _DEBUG + assert(p); + assert(dynamic_cast(p)); + T *ptr=p; // if this doesn't compile, you are trying to alloc something that is not derived from base + assert(dynamic_cast(ptr)); + int i=VALUE_SIZE; + int k=MAX_CLASS_SIZE; + int j=sizeof(CAST_TO); + compile_assert(); + assert(sizeof(CAST_TO)<=MAX_CLASS_SIZE); +#endif + return p; + } + }; + +} + +//////////////////////////////////////////////////////////////////////////////////////// +// The Array Base Class, used for most containers +//////////////////////////////////////////////////////////////////////////////////////// +template +class array_base : public ratl_base +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY, + SIZE = T::CAPACITY, + }; + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + typedef typename T TStorageTraits; + typedef typename T::TArray TTArray; + typedef typename T::TValue TTValue; + typedef typename T::TConstructed TTConstructed; + +private: + TTArray mArray; + TTConstructed mConstructed; + +public: + + array_base() + { + } + + ~array_base() + { + clear(); + } + + void clear() + { + if (T::NEEDS_CONSTRUCT) + { + int i=mConstructed.next_bit(); + while (i=0 && index=0 && index=0 && i=0 && i=0 && j=0 && i=0 && i=0 && index=0 && index + CAST_TO *verify_alloc(CAST_TO *p) const + { + return T::verify_alloc(p); + } + +}; + +} +#endif \ No newline at end of file diff --git a/codemp/ratl/vector_vs.h b/codemp/ratl/vector_vs.h new file mode 100644 index 0000000..5c33175 --- /dev/null +++ b/codemp/ratl/vector_vs.h @@ -0,0 +1,757 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Vector +// ------ +// The vector class is a simple addition to the array. It supports some useful additions +// like sort and binary search, as well as keeping track of the number of objects +// contained within. +// +// +// +// +// +// NOTES: +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_VECTOR_VS_INC) +#define RATL_VECTOR_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +namespace ratl +{ + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Vector Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class vector_base : public ratl_base +{ +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY + }; +private: + array_base mArray; // The Memory + int mSize; +public: + + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + vector_base() + { + mSize = 0; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Copy Constructor + //////////////////////////////////////////////////////////////////////////////////// + vector_base(const vector_base &B) + { + for (int i=0; i=0&&mSize<=CAPACITY); + return (CAPACITY); + } + + //////////////////////////////////////////////////////////////////////////////////// + // How Many Objects Have Been Added To This Vector? + //////////////////////////////////////////////////////////////////////////////////// + int size() const + { + assert(mSize>=0&&mSize<=CAPACITY); + return (mSize); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Have Any Objects Have Been Added To This Vector? + //////////////////////////////////////////////////////////////////////////////////// + bool empty() const + { + assert(mSize>=0&&mSize<=CAPACITY); + return (!mSize); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Have Any Objects Have Been Added To This Vector? + //////////////////////////////////////////////////////////////////////////////////// + bool full() const + { + assert(mSize>=0&&mSize<=CAPACITY); + return (mSize==CAPACITY); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Clear Out Entire Array + //////////////////////////////////////////////////////////////////////////////////// + void clear() + { + mArray.clear(); + mSize = 0; + } + // Constant Access Operator + //////////////////////////////////////////////////////////////////////////////////// + const TTValue& operator[](int index) const + { + assert(index>=0&&index=0&&index=0&&mSize=0&&mSize=0&&mSize0); + mSize--; + mArray.destruct(mSize); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Resizes The Array. If New Elements Are Needed, It Uses The (value) Param + //////////////////////////////////////////////////////////////////////////////////// + void resize(int nSize, const TTValue& value) + { + int i; + for (i=(mSize-1); i>=nSize; i--) + { + mArray.destruct(i); + mSize--; + } + for (i=mSize; i=nSize; i--) + { + mArray.destruct(i); + mSize--; + } + for (i=mSize; i=0 && Index0; HeapSize--) + { + // Swap The End And Front Of The "Heap" Half Of The Array + //-------------------------------------------------------- + mArray.swap(0, HeapSize); + + // We Now Have A Bogus Element At The Root, So Fix The Heap + //---------------------------------------------------------- + Pos = 0; + Compare = largest_child(Pos, HeapSize); + while (mArray[Pos]; + friend class const_iterator; + // Data + //------ + int mLoc; + vector_base* mOwner; + + public: + // Constructors + //-------------- + iterator() : mOwner(0), mLoc(0) + {} + iterator(vector_base* p, int t) : mOwner(p), mLoc(t) + {} + + iterator(const iterator &t) : mOwner(t.mOwner), mLoc(t.mLoc) + {} + + // Assignment Operator + //--------------------- + void operator= (const iterator &t) + { + mOwner = t.mOwner; + mLoc = t.mLoc; + } + + + // Equality Operators + //-------------------- + bool operator!=(const iterator &t) const + { + return (mLoc!=t.mLoc || mOwner!=t.mOwner); + } + bool operator==(const iterator &t) const + { + return (mLoc==t.mLoc && mOwner==t.mOwner); + } + + // DeReference Operator + //---------------------- + TTValue& operator* () const + { + assert(mLoc>=0 && mLocmSize); + return (mOwner->mArray[mLoc]); + } + // DeReference Operator + //---------------------- + TTValue& value() const + { + assert(mLoc>=0 && mLocmSize); + return (mOwner->mArray[mLoc]); + } + + // DeReference Operator + //---------------------- + TTValue* operator-> () const + { + assert(mLoc>=0 && mLocmSize); + return (&mOwner->mArray[mLoc]); + } + + // Inc Operator + //-------------- + iterator operator++(int) //postfix + { + assert(mLoc>=0 && mLocmSize); + iterator old(*this); + mLoc ++; + return old; + } + + // Inc Operator + //-------------- + iterator operator++() + { + assert(mLoc>=0 && mLocmSize); + mLoc ++; + return *this; + } + + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Constant Iterator + //////////////////////////////////////////////////////////////////////////////////// + class const_iterator + { + friend class vector_base; + + int mLoc; + const vector_base* mOwner; + + public: + // Constructors + //-------------- + const_iterator() : mOwner(0), mLoc(0) + {} + const_iterator(const vector_base* p, int t) : mOwner(p), mLoc(t) + {} + const_iterator(const const_iterator &t) : mOwner(t.mOwner), mLoc(t.mLoc) + {} + const_iterator(const iterator &t) : mOwner(t.mOwner), mLoc(t.mLoc) + {} + + // Assignment Operator + //--------------------- + void operator= (const const_iterator &t) + { + mOwner = t.mOwner; + mLoc = t.mLoc; + } + // Assignment Operator + //--------------------- + void operator= (const iterator &t) + { + mOwner = t.mOwner; + mLoc = t.mLoc; + } + + + + // Equality Operators + //-------------------- + bool operator!=(const iterator &t) const + { + return (mLoc!=t.mLoc || mOwner!=t.mOwner); + } + bool operator==(const iterator &t) const + { + return (mLoc==t.mLoc && mOwner==t.mOwner); + } + + // Equality Operators + //-------------------- + bool operator!=(const const_iterator &t) const + { + return (mLoc!=t.mLoc || mOwner!=t.mOwner); + } + bool operator==(const const_iterator &t) const + { + return (mLoc==t.mLoc && mOwner==t.mOwner); + } + + // DeReference Operator + //---------------------- + const TTValue& operator* () const + { + assert(mLoc>=0 && mLocmSize); + return (mOwner->mArray[mLoc]); + } + + // DeReference Operator + //---------------------- + const TTValue& value() const + { + assert(mLoc>=0 && mLocmSize); + return (mOwner->mArray[mLoc]); + } + + // DeReference Operator + //---------------------- + const TTValue* operator-> () const + { + assert(mLoc>=0 && mLocmSize); + return (&mOwner->mArray[mLoc]); + } + + // Inc Operator + //-------------- + const_iterator operator++(int) + { + assert(mLoc>=0 && mLocmSize); + const_iterator old(*this); + mLoc ++; + return old; + } + + // Inc Operator + //-------------- + const_iterator operator++() + { + assert(mLoc>=0 && mLocmSize); + mLoc ++; + return *this; + } + + + }; + friend class iterator; + friend class const_iterator; + + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Begin (Starts At Address 0) + //////////////////////////////////////////////////////////////////////////////////// + iterator begin() + { + return iterator(this, 0); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator End (Set To Address mSize) + //////////////////////////////////////////////////////////////////////////////////// + iterator end() + { + return iterator(this, mSize); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Begin (Starts At Address 0) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator begin() const + { + return const_iterator(this, 0); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator End (Set To Address mSize) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator end() const + { + return const_iterator(this, mSize); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Find (If Fails To Find, Returns iterator end() + //////////////////////////////////////////////////////////////////////////////////// + iterator find(const TTValue& value) + { + int index = find_index(value); // Call Find By Index + if (index=0 && it.mLocmSize); + if (it.mLoc != mSize - 1) + { + mArray.swap(it.mLoc, mSize - 1); + } + pop_back(); + return it; + } + template + CAST_TO *verify_alloc(CAST_TO *p) const + { + return mArray.verify_alloc(p); + } +}; + +template +class vector_vs : public vector_base > +{ +public: + typedef typename storage::value_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + vector_vs() {} +}; + +template +class vector_os : public vector_base > +{ +public: + typedef typename storage::object_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + vector_os() {} +}; + +template +class vector_is : public vector_base > +{ +public: + typedef typename storage::virtual_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + vector_is() {} +}; + +} +#endif diff --git a/codemp/ravl/CVec.h b/codemp/ravl/CVec.h new file mode 100644 index 0000000..2e35a9d --- /dev/null +++ b/codemp/ravl/CVec.h @@ -0,0 +1,1002 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Vector Library +// -------------- +// The base implimention of the Raven Vector object attempts to solve a number of +// high level problems as efficiently as possible. Where ever feasible, functions have +// been included in the .h file so the compiler can inline them. +// +// The vectors define the following operations: +// - Construction +// - Initialization +// - Member Access +// - Equality / Inequality Operators +// - Arithimitic Operators +// - Length & Distance +// - Normalization (Standard, Safe, Angular) +// - Dot & Cross Product +// - Perpendicular Vector +// - Truncation +// - Min & Max Element Analisis +// - Interpolation +// - Angle / Vector Conversion +// - Translation & Rotation +// - Point and Line Intersection Tests +// - Left / Right Line Test +// - String Operations +// - Debug Routines +// - "Standard" Vectors As Static Memebers +// +// As necessary, some projects may #define special faster versions of these routines to +// make better use of native hardware / software implimentations. +// +// +// +// +// NOTES: +// 05/29/02 - CREATED +// 05/30/02 - RotatePoint() is currently unimplimented. Waiting for Matrix Library +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RAVL_VEC_INC) +#define RAVL_VEC_INC +//namespace ravl +//{ + + +template T Min(const T& a, const T& b) {return (a T Max(const T& a, const T& b) {return (b Radians +#define RAVL_VEC_RADTODEG( a ) ( (a) * RAVL_VEC_RADTODEGCONST ) // Quick Macro For Radians -> Degrees + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Enums And Typedefs +//////////////////////////////////////////////////////////////////////////////////////// +enum ESide +{ + Side_None = 0, + Side_Left = 1, + Side_Right = 2, + Side_In = 3, + Side_Out = 4, + Side_AllIn = 5 +}; + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The 4 Dimensional Vector +//////////////////////////////////////////////////////////////////////////////////////// +class CVec4 +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructors + //////////////////////////////////////////////////////////////////////////////////// +#ifndef _DEBUG + CVec4() {} +#else + CVec4() {v[0]=v[1]=v[2]=v[3]=RAVL_VEC_UDF;} // DEBUG INITIALIZATION +#endif + CVec4(const float val) {v[0]=val; v[1]=val; v[2]=val; v[3]=val;} + CVec4(const float x,const float y,const float z, const float r) {v[0]=x; v[1]=y; v[2]=z; v[3]=r;} + CVec4(const CVec4& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2]; v[3]=t.v[3];} + CVec4(const float *t) {v[0]=t[0]; v[1]=t[1]; v[2]=t[2]; v[3]=t[3];} + + //////////////////////////////////////////////////////////////////////////////////// + // Initializers + //////////////////////////////////////////////////////////////////////////////////// + void Set(const float t) {v[0]=t; v[1]=t; v[2]=t; v[3]=t;} + void Set(const float *t) {v[0]=t[0]; v[1]=t[1]; v[2]=t[2]; v[3]=t[3];} + void Set(const float x,const float y,const float z, const float r) {v[0]=x; v[1]=y; v[2]=z; v[3]=r;} + void Clear() {v[0]=0; v[1]=0; v[2]=0; v[3]=0;} + + //////////////////////////////////////////////////////////////////////////////////// + // Member Accessors + //////////////////////////////////////////////////////////////////////////////////// + const float& operator[](int i) const {return v[i];} + float& operator[](int i) {return v[i];} + float& pitch() {return v[0];} + float& yaw() {return v[1];} + float& roll() {return v[2];} + float& radius() {return v[3];} + + //////////////////////////////////////////////////////////////////////////////////// + // Equality / Inequality Operators + //////////////////////////////////////////////////////////////////////////////////// + bool operator! () const {return !(v[0] && v[1] && v[2] && v[3] );} + bool operator== (const CVec4& t) const {return (v[0]==t.v[0] && v[1]==t.v[1] && v[2]==t.v[2] && v[3]==t.v[3]);} + bool operator!= (const CVec4& t) const {return !(v[0]==t.v[0] && v[1]==t.v[1] && v[2]==t.v[2] && v[3]==t.v[3]);} + bool operator< (const CVec4& t) const {return (v[0]< t.v[0] && v[1]< t.v[1] && v[2]< t.v[2] && v[3]< t.v[3]);} + bool operator> (const CVec4& t) const {return (v[0]> t.v[0] && v[1]> t.v[1] && v[2]> t.v[2] && v[3]> t.v[3]);} + bool operator<= (const CVec4& t) const {return (v[0]<=t.v[0] && v[1]<=t.v[1] && v[2]<=t.v[2] && v[3]<=t.v[3]);} + bool operator>= (const CVec4& t) const {return (v[0]>=t.v[0] && v[1]>=t.v[1] && v[2]>=t.v[2] && v[3]>=t.v[3]);} + + //////////////////////////////////////////////////////////////////////////////////// + // Basic Arithimitic Operators + //////////////////////////////////////////////////////////////////////////////////// + const CVec4 &operator= (const float d) {v[0]=d; v[1]=d; v[2]=d; v[3]=d; return *this;} + const CVec4 &operator= (const CVec4& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2]; v[3]=t.v[3]; return *this;} + + const CVec4 &operator+= (const float d) {v[0]+=d; v[1]+=d; v[2]+=d; v[3]+=d; return *this;} + const CVec4 &operator+= (const CVec4& t) {v[0]+=t.v[0]; v[1]+=t.v[1]; v[2]+=t.v[2]; v[3]+=t.v[3];return *this;} + + const CVec4 &operator-= (const float d) {v[0]-=d; v[1]-=d; v[2]-=d; v[3]-=d; return *this;} + const CVec4 &operator-= (const CVec4& t) {v[0]-=t.v[0]; v[1]-=t.v[1]; v[2]-=t.v[2]; v[3]-=t.v[3];return *this;} + + const CVec4 &operator*= (const float d) {v[0]*=d; v[1]*=d; v[2]*=d; v[3]*=d; return *this;} + const CVec4 &operator*= (const CVec4& t) {v[0]*=t.v[0]; v[1]*=t.v[1]; v[2]*=t.v[2]; v[3]*=t.v[3];return *this;} + + const CVec4 &operator/= (const float d) {v[0]/=d; v[1]/=d; v[2]/=d; v[3]/=d; return *this;} + const CVec4 &operator/= (const CVec4& t) {v[0]/=t.v[0]; v[1]/=t.v[1]; v[2]/=t.v[2]; v[3]/=t.v[3];return *this;} + + inline CVec4 operator+ (const CVec4 &t) const {return CVec4(v[0]+t.v[0], v[1]+t.v[1], v[2]+t.v[2], v[3]+t.v[3]);} + inline CVec4 operator- (const CVec4 &t) const {return CVec4(v[0]-t.v[0], v[1]-t.v[1], v[2]-t.v[2], v[3]-t.v[3]);} + inline CVec4 operator* (const CVec4 &t) const {return CVec4(v[0]*t.v[0], v[1]*t.v[1], v[2]*t.v[2], v[3]*t.v[3]);} + inline CVec4 operator/ (const CVec4 &t) const {return CVec4(v[0]/t.v[0], v[1]/t.v[1], v[2]/t.v[2], v[3]/t.v[3]);} + + + //////////////////////////////////////////////////////////////////////////////////// + // Length And Distance Calculations + //////////////////////////////////////////////////////////////////////////////////// + float Len() const; + float Len2() const {return (v[0]*v[0]+v[1]*v[1]+v[2]*v[2]+v[3]*v[3]);} + + float Dist(const CVec4& t) const; + float Dist2(const CVec4& t) const {return ((t.v[0]-v[0])*(t.v[0]-v[0]) + (t.v[1]-v[1])*(t.v[1]-v[1]) + (t.v[2]-v[2])*(t.v[2]-v[2]) + (t.v[3]-v[3])*(t.v[3]-v[3]) );} + + + //////////////////////////////////////////////////////////////////////////////////// + // Normalization + //////////////////////////////////////////////////////////////////////////////////// + float Norm(); + float SafeNorm(); + void AngleNorm(); + + + //////////////////////////////////////////////////////////////////////////////////// + // Dot, Cross & Perpendicular Vector + //////////////////////////////////////////////////////////////////////////////////// + float Dot(const CVec4& t) const {return (v[0]*t.v[0] + v[1]*t.v[1] + v[2]*t.v[2] + v[3]*t.v[3]);} + void Cross(const CVec4& t) + { + CVec4 temp(*this); + v[0] = (temp.v[1]*t.v[2]) - (temp.v[2]*t.v[1]); + v[1] = (temp.v[2]*t.v[0]) - (temp.v[0]*t.v[2]); + v[2] = (temp.v[0]*t.v[1]) - (temp.v[1]*t.v[0]); + v[3] = 0; + } + void Perp(); + + + //////////////////////////////////////////////////////////////////////////////////// + // Truncation & Element Analysis + //////////////////////////////////////////////////////////////////////////////////// + void Min(const CVec4& t) + { + if (t.v[0]v[0]) v[0]=t.v[0]; + if (t.v[1]>v[1]) v[1]=t.v[1]; + if (t.v[2]>v[2]) v[2]=t.v[2]; + if (t.v[3]>v[3]) v[3]=t.v[3]; + } + float MaxElement() const + { + return v[MaxElementIndex()]; + } + int MaxElementIndex() const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Interpolation + //////////////////////////////////////////////////////////////////////////////////// + void Interp(const CVec4 &v1, const CVec4 &v2, const float t) + { + (*this)=v1; + (*this)-=v2; + (*this)*=t; + (*this)+=v2; + } + void ScaleAdd(const CVec4& t, const float scale) + { + v[0] += (scale * t.v[0]); + v[1] += (scale * t.v[1]); + v[2] += (scale * t.v[2]); + v[3] += (scale * t.v[3]); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Angle To Vector (Angle In Degrees) + //////////////////////////////////////////////////////////////////////////////////// + void VecToAng(); + void AngToVec(); + void AngToVec(CVec4& Right, CVec4& Up); + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Angle To Vector (Angle In Radians) + //////////////////////////////////////////////////////////////////////////////////// + void VecToAngRad(); + void AngToVecRad(); + void AngToVecRad(CVec4& Right, CVec4& Up); + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Between Radians And Degrees + //////////////////////////////////////////////////////////////////////////////////// + void ToRadians(); + void ToDegrees(); + + + + //////////////////////////////////////////////////////////////////////////////////// + // Project + // + // Standard projection function. Take the (this) and project it onto the vector + // (U). Imagine drawing a line perpendicular to U from the endpoint of the (this) + // Vector. That then becomes the new vector. + // + // The value returned is the scale of the new vector with respect to the one passed + // to the function. If the scale is less than (1.0) then the new vector is shorter + // than (U). If the scale is negative, then the vector is going in the opposite + // direction of (U). + // + // _ (U) + // /| + // / _ (this) + // / RESULTS-> /| + // / / + // / __\ (this) / + // /___--- / / + // + //////////////////////////////////////////////////////////////////////////////////// + float Project(const CVec4 &U) + { + float Scale = (Dot(U) / U.Len2()); // Find the scale of this vector on U + (*this)=U; // Copy U onto this vector + (*this)*=Scale; // Use the previously calculated scale to get the right length. + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Project To Line + // + // This function takes two other points in space as the start and end of a line + // segment and projects the (this) point onto the line defined by (Start)->(Stop) + // + // RETURN VALUES: + // (-INF, 0.0) : (this) landed on the line before (Start) + // (0.0, 1.0) : (this) landed in the line segment between (Start) and (Stop) + // (1.0, INF) : (this) landed on the line beyond (End) + // + // (Stop) + // / + // / + // o _ + // / |\ + // / (this) + // / + // (Start) + // + //////////////////////////////////////////////////////////////////////////////////// + float ProjectToLine(const CVec4 &Start, const CVec4 &Stop) + { + (*this) -= Start; + float Scale = Project(Stop - Start); + (*this) += Start; + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Project To Line Seg + // + // Same As Project To Line, Except It Will Clamp To Start And Stop + //////////////////////////////////////////////////////////////////////////////////// + float ProjectToLineSeg(const CVec4 &Start, const CVec4 &Stop) + { + float Scale = ProjectToLine(Start, Stop); + if (Scale<0.0f) + { + (*this) = Start; + } + else if (Scale>1.0f) + { + (*this) = Stop; + } + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance To Line + // + // Uses project to line and than calculates distance to the new point + //////////////////////////////////////////////////////////////////////////////////// + float DistToLine(const CVec4 &Start, const CVec4 &Stop) const + { + CVec4 P(*this); + P.ProjectToLineSeg(Start, Stop); + + return Dist(P); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance To Line + // + // Uses project to line and than calculates distance to the new point + //////////////////////////////////////////////////////////////////////////////////// + float DistToLine2(const CVec4 &Start, const CVec4 &Stop) const + { + CVec4 P(*this); + P.ProjectToLineSeg(Start, Stop); + + return Dist2(P); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Translation & Rotation (2D) + //////////////////////////////////////////////////////////////////////////////////// + void RotatePoint(const CVec4 &Angle, const CVec4 &Origin); + void Reposition(const CVec4 &Translation, float RotationDegrees=0.0); + + + + //////////////////////////////////////////////////////////////////////////////////// + // Area Of The Parallel Pipid (2D) + // + // Given two more points, this function calculates the area of the parallel pipid + // formed. + // + // Note: This function CAN return a negative "area" if (this) is above or right of + // (A) and (B)... We do not take the abs because the sign of the "area" is needed + // for the left right test (see below) + // + // + // ___---( ... ) + // (A)---/ / + // / / + // / / + // / / + // / ___---(B) + // (this)---/ + // + //////////////////////////////////////////////////////////////////////////////////// + float AreaParallelPipid(const CVec4 &A, const CVec4 &B) const + { + return ((A.v[0]*B.v[1] - A.v[1]*B.v[0]) + + (B.v[0]* v[1] - v[0]*B.v[1]) + + ( v[0]*A.v[1] - A.v[0]* v[1])); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Area Of The Triangle (2D) + // + // Given two more points, this function calculates the area of the triangle formed. + // + // (A) + // / \__ + // / \__ + // / \_ + // / ___---(B) + // (this)---/ + // + //////////////////////////////////////////////////////////////////////////////////// + float AreaTriange(const CVec4 &A, const CVec4 &B) const + { + return (AreaParallelPipid(A, B) * 0.5f); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // The Left Right Test (2D) + // + // Given a line segment (Start->End) and a tolerance for *right on*, this function + // evaluates which side the point is of the line. (Side_Left in this example) + // + // + // + // (this) ___---/(End) + // ___---/ + // ___---/ + // (Start)/ + // + //////////////////////////////////////////////////////////////////////////////////// + ESide LRTest(const CVec4 &Start, const CVec4 &End, float Tolerance=0.0) const + { + float Area = AreaParallelPipid(Start, End); + if (Area>Tolerance) + { + return Side_Left; + } + if (Area<(Tolerance*-1)) + { + return Side_Right; + } + return Side_None; + + } + + //////////////////////////////////////////////////////////////////////////////////// + // Point In Circumscribed Circle (True/False) + // + // Returns true if the given point is within the circumscribed + // circle of the given ABC Triangle: + // _____ + // / B \ + // / / \ \ + // | / \ | + // |A---------C| + // \ Pt / + // \_______/ + // + //////////////////////////////////////////////////////////////////////////////////// + bool PtInCircle(const CVec4 &A, const CVec4 &B, const CVec4 &C) const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Point In Standard Circle (True/False) + // + // Returns true if the given point is within the Circle + // _____ + // / \ + // / \ + // | Circle | + // | | + // \ Pt / + // \_______/ + // + //////////////////////////////////////////////////////////////////////////////////// + bool PtInCircle(const CVec4 &Circle, float Radius) const; + + //////////////////////////////////////////////////////////////////////////////////// + // Line Intersects Circle (True/False) + // + // r - Radius Of The Circle + // A - Start Of Line Segment + // B - End Of Line Segment + // + // P - Projected Position Of Origin Onto Line AB + // + // + // (Stop) + // / + // / + // (P) + // / \ \ + // / (this)-r->| + // / / + // (Start) + // + //////////////////////////////////////////////////////////////////////////////////// + bool LineInCircle(const CVec4 &Start, const CVec4 &Stop, float Radius); + bool LineInCircle(const CVec4 &Start, const CVec4 &Stop, float Radius, CVec4 &PointOnLine); + + + + //////////////////////////////////////////////////////////////////////////////////// + // String Operations + //////////////////////////////////////////////////////////////////////////////////// + void FromStr(const char *s); + void ToStr(char* s) const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Debug Routines + //////////////////////////////////////////////////////////////////////////////////// +#ifdef _DEBUG + bool IsFinite(); + bool IsInitialized(); +#endif + + + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// +private: + float v[4]; + + +public: + static const CVec4 mX; + static const CVec4 mY; + static const CVec4 mZ; + static const CVec4 mW; + static const CVec4 mZero; +}; + + + + + + + + + + + + + + + + + + + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The 3 Dimensional Vector +//////////////////////////////////////////////////////////////////////////////////////// +class CVec3 +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructors + //////////////////////////////////////////////////////////////////////////////////// +#ifndef _DEBUG + CVec3() {} +#else + CVec3() {v[0]=v[1]=v[2]=RAVL_VEC_UDF;} // DEBUG INITIALIZATION +#endif + CVec3(const float val) {v[0]=val; v[1]=val; v[2]=val; } + CVec3(const float x,const float y,const float z) {v[0]=x; v[1]=y; v[2]=z; } + CVec3(const CVec3& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2];} + CVec3(const float *t) {v[0]=t[0]; v[1]=t[1]; v[2]=t[2]; } + + float x() const {return v[0];} + float y() const {return v[1];} + float z() const {return v[2];} + + //////////////////////////////////////////////////////////////////////////////////// + // Initializers + //////////////////////////////////////////////////////////////////////////////////// + void Set(const float t) {v[0]=t; v[1]=t; v[2]=t; } + void Set(const float *t) {v[0]=t[0]; v[1]=t[1]; v[2]=t[2]; } + void Set(const float x,const float y,const float z) {v[0]=x; v[1]=y; v[2]=z; } + void Clear() {v[0]=0; v[1]=0; v[2]=0; } + + //////////////////////////////////////////////////////////////////////////////////// + // Member Accessors + //////////////////////////////////////////////////////////////////////////////////// + const float& operator[](int i) const {return v[i];} + float& operator[](int i) {return v[i];} + float& pitch() {return v[0];} + float& yaw() {return v[1];} + float& roll() {return v[2];} + float& radius() {return v[3];} + + //////////////////////////////////////////////////////////////////////////////////// + // Equality / Inequality Operators + //////////////////////////////////////////////////////////////////////////////////// + bool operator! () const {return !(v[0] && v[1] && v[2] );} + bool operator== (const CVec3& t) const {return (v[0]==t.v[0] && v[1]==t.v[1] && v[2]==t.v[2]);} + bool operator!= (const CVec3& t) const {return !(v[0]==t.v[0] && v[1]==t.v[1] && v[2]==t.v[2]);} + bool operator< (const CVec3& t) const {return (v[0]< t.v[0] && v[1]< t.v[1] && v[2]< t.v[2]);} + bool operator> (const CVec3& t) const {return (v[0]> t.v[0] && v[1]> t.v[1] && v[2]> t.v[2]);} + bool operator<= (const CVec3& t) const {return (v[0]<=t.v[0] && v[1]<=t.v[1] && v[2]<=t.v[2]);} + bool operator>= (const CVec3& t) const {return (v[0]>=t.v[0] && v[1]>=t.v[1] && v[2]>=t.v[2]);} + + //////////////////////////////////////////////////////////////////////////////////// + // Basic Arithimitic Operators + //////////////////////////////////////////////////////////////////////////////////// + const CVec3 &operator= (const float d) {v[0]=d; v[1]=d; v[2]=d; return *this;} + const CVec3 &operator= (const CVec3& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2]; return *this;} + + const CVec3 &operator+= (const float d) {v[0]+=d; v[1]+=d; v[2]+=d; return *this;} + const CVec3 &operator+= (const CVec3& t) {v[0]+=t.v[0]; v[1]+=t.v[1]; v[2]+=t.v[2];return *this;} + + const CVec3 &operator-= (const float d) {v[0]-=d; v[1]-=d; v[2]-=d; return *this;} + const CVec3 &operator-= (const CVec3& t) {v[0]-=t.v[0]; v[1]-=t.v[1]; v[2]-=t.v[2];return *this;} + + const CVec3 &operator*= (const float d) {v[0]*=d; v[1]*=d; v[2]*=d; return *this;} + const CVec3 &operator*= (const CVec3& t) {v[0]*=t.v[0]; v[1]*=t.v[1]; v[2]*=t.v[2];return *this;} + + const CVec3 &operator/= (const float d) {v[0]/=d; v[1]/=d; v[2]/=d; return *this;} + const CVec3 &operator/= (const CVec3& t) {v[0]/=t.v[0]; v[1]/=t.v[1]; v[2]/=t.v[2];return *this;} + + inline CVec3 operator+ (const CVec3 &t) const {return CVec3(v[0]+t.v[0], v[1]+t.v[1], v[2]+t.v[2]);} + inline CVec3 operator- (const CVec3 &t) const {return CVec3(v[0]-t.v[0], v[1]-t.v[1], v[2]-t.v[2]);} + inline CVec3 operator* (const CVec3 &t) const {return CVec3(v[0]*t.v[0], v[1]*t.v[1], v[2]*t.v[2]);} + inline CVec3 operator/ (const CVec3 &t) const {return CVec3(v[0]/t.v[0], v[1]/t.v[1], v[2]/t.v[2]);} + + + //////////////////////////////////////////////////////////////////////////////////// + // Length And Distance Calculations + //////////////////////////////////////////////////////////////////////////////////// + float Len() const; + float Len2() const {return (v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);} + + float Dist(const CVec3& t) const; + float Dist2(const CVec3& t) const {return ((t.v[0]-v[0])*(t.v[0]-v[0]) + (t.v[1]-v[1])*(t.v[1]-v[1]) + (t.v[2]-v[2])*(t.v[2]-v[2]));} + + + //////////////////////////////////////////////////////////////////////////////////// + // Normalization + //////////////////////////////////////////////////////////////////////////////////// + float Norm(); + float SafeNorm(); + void AngleNorm(); + float Truncate(float maxlen); + + + //////////////////////////////////////////////////////////////////////////////////// + // Dot, Cross & Perpendicular Vector + //////////////////////////////////////////////////////////////////////////////////// + float Dot(const CVec3& t) const {return (v[0]*t.v[0] + v[1]*t.v[1] + v[2]*t.v[2]);} + void Cross(const CVec3& t) + { + CVec3 temp(*this); + v[0] = (temp.v[1]*t.v[2]) - (temp.v[2]*t.v[1]); + v[1] = (temp.v[2]*t.v[0]) - (temp.v[0]*t.v[2]); + v[2] = (temp.v[0]*t.v[1]) - (temp.v[1]*t.v[0]); + } + void Perp(); + + + //////////////////////////////////////////////////////////////////////////////////// + // Truncation & Element Analysis + //////////////////////////////////////////////////////////////////////////////////// + void Min(const CVec3& t) + { + if (t.v[0]v[0]) v[0]=t.v[0]; + if (t.v[1]>v[1]) v[1]=t.v[1]; + if (t.v[2]>v[2]) v[2]=t.v[2]; + } + float MaxElement() const + { + return v[MaxElementIndex()]; + } + int MaxElementIndex() const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Interpolation + //////////////////////////////////////////////////////////////////////////////////// + void Interp(const CVec3 &v1, const CVec3 &v2, const float t) + { + (*this)=v1; + (*this)-=v2; + (*this)*=t; + (*this)+=v2; + } + void ScaleAdd(const CVec3& t, const float scale) + { + v[0] += (scale * t.v[0]); + v[1] += (scale * t.v[1]); + v[2] += (scale * t.v[2]); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Angle To Vector (Angle In Degrees) + //////////////////////////////////////////////////////////////////////////////////// + void VecToAng(); + void AngToVec(); + void AngToVec(CVec3& Right, CVec3& Up); + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Angle To Vector (Angle In Radians) + //////////////////////////////////////////////////////////////////////////////////// + void VecToAngRad(); + void AngToVecRad(); + void AngToVecRad(CVec3& Right, CVec3& Up); + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Between Radians And Degrees + //////////////////////////////////////////////////////////////////////////////////// + void ToRadians(); + void ToDegrees(); + + + + //////////////////////////////////////////////////////////////////////////////////// + // Project + // + // Standard projection function. Take the (this) and project it onto the vector + // (U). Imagine drawing a line perpendicular to U from the endpoint of the (this) + // Vector. That then becomes the new vector. + // + // The value returned is the scale of the new vector with respect to the one passed + // to the function. If the scale is less than (1.0) then the new vector is shorter + // than (U). If the scale is negative, then the vector is going in the opposite + // direction of (U). + // + // _ (U) + // /| + // / _ (this) + // / RESULTS-> /| + // / / + // / __\ (this) / + // /___--- / / + // + //////////////////////////////////////////////////////////////////////////////////// + float Project(const CVec3 &U) + { + float Scale = (Dot(U) / U.Len2()); // Find the scale of this vector on U + (*this)=U; // Copy U onto this vector + (*this)*=Scale; // Use the previously calculated scale to get the right length. + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Project To Line + // + // This function takes two other points in space as the start and end of a line + // segment and projects the (this) point onto the line defined by (Start)->(Stop) + // + // RETURN VALUES: + // (-INF, 0.0) : (this) landed on the line before (Start) + // (0.0, 1.0) : (this) landed in the line segment between (Start) and (Stop) + // (1.0, INF) : (this) landed on the line beyond (End) + // + // (Stop) + // / + // / + // o _ + // / |\ + // / (this) + // / + // (Start) + // + //////////////////////////////////////////////////////////////////////////////////// + float ProjectToLine(const CVec3 &Start, const CVec3 &Stop) + { + (*this) -= Start; + float Scale = Project(Stop - Start); + (*this) += Start; + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Project To Line Seg + // + // Same As Project To Line, Except It Will Clamp To Start And Stop + //////////////////////////////////////////////////////////////////////////////////// + float ProjectToLineSeg(const CVec3 &Start, const CVec3 &Stop) + { + float Scale = ProjectToLine(Start, Stop); + if (Scale<0.0f) + { + (*this) = Start; + } + else if (Scale>1.0f) + { + (*this) = Stop; + } + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance To Line + // + // Uses project to line and than calculates distance to the new point + //////////////////////////////////////////////////////////////////////////////////// + float DistToLine(const CVec3 &Start, const CVec3 &Stop) const + { + CVec3 P(*this); + P.ProjectToLineSeg(Start, Stop); + + return Dist(P); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance To Line + // + // Uses project to line and than calculates distance to the new point + //////////////////////////////////////////////////////////////////////////////////// + float DistToLine2(const CVec3 &Start, const CVec3 &Stop) const + { + CVec3 P(*this); + P.ProjectToLineSeg(Start, Stop); + + return Dist2(P); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Translation & Rotation (2D) + //////////////////////////////////////////////////////////////////////////////////// + void RotatePoint(const CVec3 &Angle, const CVec3 &Origin); + void Reposition(const CVec3 &Translation, float RotationDegrees=0.0); + + + + //////////////////////////////////////////////////////////////////////////////////// + // Area Of The Parallel Pipid (2D) + // + // Given two more points, this function calculates the area of the parallel pipid + // formed. + // + // Note: This function CAN return a negative "area" if (this) is above or right of + // (A) and (B)... We do not take the abs because the sign of the "area" is needed + // for the left right test (see below) + // + // + // ___---( ... ) + // (A)---/ / + // / / + // / / + // / / + // / ___---(B) + // (this)---/ + // + //////////////////////////////////////////////////////////////////////////////////// + float AreaParallelPipid(const CVec3 &A, const CVec3 &B) const + { + return ((A.v[0]*B.v[1] - A.v[1]*B.v[0]) + + (B.v[0]* v[1] - v[0]*B.v[1]) + + ( v[0]*A.v[1] - A.v[0]* v[1])); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Area Of The Triangle (2D) + // + // Given two more points, this function calculates the area of the triangle formed. + // + // (A) + // / \__ + // / \__ + // / \_ + // / ___---(B) + // (this)---/ + // + //////////////////////////////////////////////////////////////////////////////////// + float AreaTriange(const CVec3 &A, const CVec3 &B) const + { + return (AreaParallelPipid(A, B) * 0.5f); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // The Left Right Test (2D) + // + // Given a line segment (Start->End) and a tolerance for *right on*, this function + // evaluates which side the point is of the line. (Side_Left in this example) + // + // + // + // (this) ___---/(End) + // ___---/ + // ___---/ + // (Start)/ + // + //////////////////////////////////////////////////////////////////////////////////// + ESide LRTest(const CVec3 &Start, const CVec3 &End, float Tolerance=0.0) const + { + float Area = AreaParallelPipid(Start, End); + if (Area>Tolerance) + { + return Side_Left; + } + if (Area<(Tolerance*-1)) + { + return Side_Right; + } + return Side_None; + + } + + //////////////////////////////////////////////////////////////////////////////////// + // Point In Circumscribed Circle (True/False) + // + // Returns true if the given point is within the circumscribed + // circle of the given ABC Triangle: + // _____ + // / B \ + // / / \ \ + // | / \ | + // |A---------C| + // \ Pt / + // \_______/ + // + //////////////////////////////////////////////////////////////////////////////////// + bool PtInCircle(const CVec3 &A, const CVec3 &B, const CVec3 &C) const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Point In Standard Circle (True/False) + // + // Returns true if the given point is within the Circle + // _____ + // / \ + // / \ + // | Circle | + // | | + // \ Pt / + // \_______/ + // + //////////////////////////////////////////////////////////////////////////////////// + bool PtInCircle(const CVec3 &Circle, float Radius) const; + + //////////////////////////////////////////////////////////////////////////////////// + // Line Intersects Circle (True/False) + // + // r - Radius Of The Circle + // A - Start Of Line Segment + // B - End Of Line Segment + // + // P - Projected Position Of Origin Onto Line AB + // + // + // (Stop) + // / + // / + // (P) + // / \ \ + // / (this)-r->| + // / / + // (Start) + // + //////////////////////////////////////////////////////////////////////////////////// + bool LineInCircle(const CVec3 &Start, const CVec3 &Stop, float Radius); + bool LineInCircle(const CVec3 &Start, const CVec3 &Stop, float Radius, CVec3 &PointOnLine); + + + + //////////////////////////////////////////////////////////////////////////////////// + // String Operations + //////////////////////////////////////////////////////////////////////////////////// + void FromStr(const char *s); + void ToStr(char* s) const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Debug Routines + //////////////////////////////////////////////////////////////////////////////////// +#ifdef _DEBUG + bool IsFinite(); + bool IsInitialized(); +#endif + + + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + +public: + float v[3]; + static const CVec3 mX; + static const CVec3 mY; + static const CVec3 mZ; + static const CVec3 mZero; +}; + + + +//}; +#endif \ No newline at end of file diff --git a/codemp/renderer/glext.h b/codemp/renderer/glext.h new file mode 100644 index 0000000..e1b2487 --- /dev/null +++ b/codemp/renderer/glext.h @@ -0,0 +1,3037 @@ +#ifndef __glext_h_ +#define __glext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif + +/*************************************************************/ + +/* Header file version number, required by OpenGL ABI for Linux */ +#define GL_GLEXT_VERSION 7 + +#ifndef GL_VERSION_1_2 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#endif + +#ifndef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +#endif + +#ifndef GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif + +#ifndef GL_ARB_texture_compression +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef GL_EXT_blend_color +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef GL_EXT_texture +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef GL_EXT_histogram +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef GL_EXT_convolution +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +#endif + +#ifndef GL_SGI_color_matrix +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif + +#ifndef GL_SGI_color_table +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef GL_EXT_cmyka +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif + +#ifndef GL_EXT_texture_object +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif + +#ifndef GL_SGIS_multisample +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef GL_SGIX_shadow +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_FfdMaskSGIX +#define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 +#define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 +#define GL_TEXTURE_DEFORMATION_SGIX 0x8195 +#define GL_DEFORMATIONS_MASK_SGIX 0x8196 +#define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef GL_EXT_light_texture +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_SGIX_async +#define GL_ASYNC_MARKER_SGIX 0x8329 +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif + +#ifndef GL_EXT_secondary_color +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef GL_EXT_fog_coord +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE3_RGB_EXT 0x8583 +#define GL_SOURCE4_RGB_EXT 0x8584 +#define GL_SOURCE5_RGB_EXT 0x8585 +#define GL_SOURCE6_RGB_EXT 0x8586 +#define GL_SOURCE7_RGB_EXT 0x8587 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_SOURCE3_ALPHA_EXT 0x858B +#define GL_SOURCE4_ALPHA_EXT 0x858C +#define GL_SOURCE5_ALPHA_EXT 0x858D +#define GL_SOURCE6_ALPHA_EXT 0x858E +#define GL_SOURCE7_ALPHA_EXT 0x858F +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND3_RGB_EXT 0x8593 +#define GL_OPERAND4_RGB_EXT 0x8594 +#define GL_OPERAND5_RGB_EXT 0x8595 +#define GL_OPERAND6_RGB_EXT 0x8596 +#define GL_OPERAND7_RGB_EXT 0x8597 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#define GL_OPERAND3_ALPHA_EXT 0x859B +#define GL_OPERAND4_ALPHA_EXT 0x859C +#define GL_OPERAND5_ALPHA_EXT 0x859D +#define GL_OPERAND6_ALPHA_EXT 0x859E +#define GL_OPERAND7_ALPHA_EXT 0x859F +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef GL_SUN_triangle_list +#define GL_RESTART_SUN 0x01 +#define GL_REPLACE_MIDDLE_SUN 0x02 +#define GL_REPLACE_OLDEST_SUN 0x03 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +#endif + +#ifndef GL_INGR_color_clamp +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef GL_EXT_texture_cube_map +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW_MATRIX1_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +#endif + +#ifndef GL_NV_register_combiners +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef GL_EXT_multisample +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + + +/*************************************************************/ + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glBlendColor (GLclampf, GLclampf, GLclampf, GLclampf); +extern void APIENTRY glBlendEquation (GLenum); +extern void APIENTRY glDrawRangeElements (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +extern void APIENTRY glColorTable (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glColorTableParameterfv (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glColorTableParameteriv (GLenum, GLenum, const GLint *); +extern void APIENTRY glCopyColorTable (GLenum, GLenum, GLint, GLint, GLsizei); +extern void APIENTRY glGetColorTable (GLenum, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetColorTableParameterfv (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetColorTableParameteriv (GLenum, GLenum, GLint *); +extern void APIENTRY glColorSubTable (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glCopyColorSubTable (GLenum, GLsizei, GLint, GLint, GLsizei); +extern void APIENTRY glConvolutionFilter1D (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glConvolutionFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glConvolutionParameterf (GLenum, GLenum, GLfloat); +extern void APIENTRY glConvolutionParameterfv (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glConvolutionParameteri (GLenum, GLenum, GLint); +extern void APIENTRY glConvolutionParameteriv (GLenum, GLenum, const GLint *); +extern void APIENTRY glCopyConvolutionFilter1D (GLenum, GLenum, GLint, GLint, GLsizei); +extern void APIENTRY glCopyConvolutionFilter2D (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +extern void APIENTRY glGetConvolutionFilter (GLenum, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetConvolutionParameterfv (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetConvolutionParameteriv (GLenum, GLenum, GLint *); +extern void APIENTRY glGetSeparableFilter (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +extern void APIENTRY glSeparableFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +extern void APIENTRY glGetHistogram (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetHistogramParameterfv (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetHistogramParameteriv (GLenum, GLenum, GLint *); +extern void APIENTRY glGetMinmax (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetMinmaxParameterfv (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetMinmaxParameteriv (GLenum, GLenum, GLint *); +extern void APIENTRY glHistogram (GLenum, GLsizei, GLenum, GLboolean); +extern void APIENTRY glMinmax (GLenum, GLenum, GLboolean); +extern void APIENTRY glResetHistogram (GLenum); +extern void APIENTRY glResetMinmax (GLenum); +extern void APIENTRY glTexImage3D (GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glCopyTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLBLENDCOLORPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +typedef void (APIENTRY * PFNGLBLENDEQUATIONPROC) (GLenum mode); +typedef void (APIENTRY * PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (APIENTRY * PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRY * PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRY * PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRY * PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +typedef void (APIENTRY * PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRY * PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRY * PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRY * PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRY * PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRY * PFNGLRESETMINMAXPROC) (GLenum target); +typedef void (APIENTRY * PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRY * PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRY * PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glActiveTextureARB (GLenum); +extern void APIENTRY glClientActiveTextureARB (GLenum); +extern void APIENTRY glMultiTexCoord1dARB (GLenum, GLdouble); +extern void APIENTRY glMultiTexCoord1dvARB (GLenum, const GLdouble *); +extern void APIENTRY glMultiTexCoord1fARB (GLenum, GLfloat); +extern void APIENTRY glMultiTexCoord1fvARB (GLenum, const GLfloat *); +extern void APIENTRY glMultiTexCoord1iARB (GLenum, GLint); +extern void APIENTRY glMultiTexCoord1ivARB (GLenum, const GLint *); +extern void APIENTRY glMultiTexCoord1sARB (GLenum, GLshort); +extern void APIENTRY glMultiTexCoord1svARB (GLenum, const GLshort *); +extern void APIENTRY glMultiTexCoord2dARB (GLenum, GLdouble, GLdouble); +extern void APIENTRY glMultiTexCoord2dvARB (GLenum, const GLdouble *); +extern void APIENTRY glMultiTexCoord2fARB (GLenum, GLfloat, GLfloat); +extern void APIENTRY glMultiTexCoord2fvARB (GLenum, const GLfloat *); +extern void APIENTRY glMultiTexCoord2iARB (GLenum, GLint, GLint); +extern void APIENTRY glMultiTexCoord2ivARB (GLenum, const GLint *); +extern void APIENTRY glMultiTexCoord2sARB (GLenum, GLshort, GLshort); +extern void APIENTRY glMultiTexCoord2svARB (GLenum, const GLshort *); +extern void APIENTRY glMultiTexCoord3dARB (GLenum, GLdouble, GLdouble, GLdouble); +extern void APIENTRY glMultiTexCoord3dvARB (GLenum, const GLdouble *); +extern void APIENTRY glMultiTexCoord3fARB (GLenum, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glMultiTexCoord3fvARB (GLenum, const GLfloat *); +extern void APIENTRY glMultiTexCoord3iARB (GLenum, GLint, GLint, GLint); +extern void APIENTRY glMultiTexCoord3ivARB (GLenum, const GLint *); +extern void APIENTRY glMultiTexCoord3sARB (GLenum, GLshort, GLshort, GLshort); +extern void APIENTRY glMultiTexCoord3svARB (GLenum, const GLshort *); +extern void APIENTRY glMultiTexCoord4dARB (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +extern void APIENTRY glMultiTexCoord4dvARB (GLenum, const GLdouble *); +extern void APIENTRY glMultiTexCoord4fARB (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glMultiTexCoord4fvARB (GLenum, const GLfloat *); +extern void APIENTRY glMultiTexCoord4iARB (GLenum, GLint, GLint, GLint, GLint); +extern void APIENTRY glMultiTexCoord4ivARB (GLenum, const GLint *); +extern void APIENTRY glMultiTexCoord4sARB (GLenum, GLshort, GLshort, GLshort, GLshort); +extern void APIENTRY glMultiTexCoord4svARB (GLenum, const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *); +extern void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *); +extern void APIENTRY glMultTransposeMatrixfARB (const GLfloat *); +extern void APIENTRY glMultTransposeMatrixdARB (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRY * PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRY * PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRY * PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSampleCoverageARB (GLclampf, GLboolean); +extern void APIENTRY glSamplePassARB (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSAMPLECOVERAGEARBPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRY * PFNGLSAMPLEPASSARBPROC) (GLenum pass); +#endif + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glCompressedTexImage3DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +extern void APIENTRY glCompressedTexImage2DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +extern void APIENTRY glCompressedTexImage1DARB (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +extern void APIENTRY glCompressedTexSubImage3DARB (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void APIENTRY glCompressedTexSubImage2DARB (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void APIENTRY glCompressedTexSubImage1DARB (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void APIENTRY glGetCompressedTexImageARB (GLenum, GLint, void *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, void *img); +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glBlendColorEXT (GLclampf, GLclampf, GLclampf, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLBLENDCOLOREXTPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glPolygonOffsetEXT (GLfloat, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTexImage3DEXT (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRY * PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glGetTexFilterFuncSGIS (GLenum, GLenum, GLfloat *); +extern void APIENTRY glTexFilterFuncSGIS (GLenum, GLenum, GLsizei, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRY * PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTexSubImage1DEXT (GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRY * PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glCopyTexImage1DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLint); +extern void APIENTRY glCopyTexImage2DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint); +extern void APIENTRY glCopyTexSubImage1DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei); +extern void APIENTRY glCopyTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +extern void APIENTRY glCopyTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRY * PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRY * PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRY * PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glGetHistogramEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetHistogramParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetHistogramParameterivEXT (GLenum, GLenum, GLint *); +extern void APIENTRY glGetMinmaxEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetMinmaxParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetMinmaxParameterivEXT (GLenum, GLenum, GLint *); +extern void APIENTRY glHistogramEXT (GLenum, GLsizei, GLenum, GLboolean); +extern void APIENTRY glMinmaxEXT (GLenum, GLenum, GLboolean); +extern void APIENTRY glResetHistogramEXT (GLenum); +extern void APIENTRY glResetMinmaxEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRY * PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRY * PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRY * PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRY * PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRY * PFNGLRESETMINMAXEXTPROC) (GLenum target); +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glConvolutionFilter1DEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glConvolutionFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glConvolutionParameterfEXT (GLenum, GLenum, GLfloat); +extern void APIENTRY glConvolutionParameterfvEXT (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glConvolutionParameteriEXT (GLenum, GLenum, GLint); +extern void APIENTRY glConvolutionParameterivEXT (GLenum, GLenum, const GLint *); +extern void APIENTRY glCopyConvolutionFilter1DEXT (GLenum, GLenum, GLint, GLint, GLsizei); +extern void APIENTRY glCopyConvolutionFilter2DEXT (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +extern void APIENTRY glGetConvolutionFilterEXT (GLenum, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetConvolutionParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetConvolutionParameterivEXT (GLenum, GLenum, GLint *); +extern void APIENTRY glGetSeparableFilterEXT (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +extern void APIENTRY glSeparableFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRY * PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRY * PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +#endif + +#ifndef GL_EXT_color_matrix +#define GL_EXT_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glColorTableSGI (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glColorTableParameterfvSGI (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glColorTableParameterivSGI (GLenum, GLenum, const GLint *); +extern void APIENTRY glCopyColorTableSGI (GLenum, GLenum, GLint, GLint, GLsizei); +extern void APIENTRY glGetColorTableSGI (GLenum, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetColorTableParameterfvSGI (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetColorTableParameterivSGI (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRY * PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glPixelTexGenSGIX (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glPixelTexGenParameteriSGIS (GLenum, GLint); +extern void APIENTRY glPixelTexGenParameterivSGIS (GLenum, const GLint *); +extern void APIENTRY glPixelTexGenParameterfSGIS (GLenum, GLfloat); +extern void APIENTRY glPixelTexGenParameterfvSGIS (GLenum, const GLfloat *); +extern void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum, GLint *); +extern void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTexImage4DSGIS (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glTexSubImage4DSGIS (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRY * PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#ifdef GL_GLEXT_PROTOTYPES +extern GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei, const GLuint *, GLboolean *); +extern void APIENTRY glBindTextureEXT (GLenum, GLuint); +extern void APIENTRY glDeleteTexturesEXT (GLsizei, const GLuint *); +extern void APIENTRY glGenTexturesEXT (GLsizei, GLuint *); +extern GLboolean APIENTRY glIsTextureEXT (GLuint); +extern void APIENTRY glPrioritizeTexturesEXT (GLsizei, const GLuint *, const GLclampf *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRY * PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRY * PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRY * PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRY * PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRY * PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRY * PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glDetailTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +extern void APIENTRY glGetDetailTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRY * PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSharpenTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +extern void APIENTRY glGetSharpenTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRY * PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSampleMaskSGIS (GLclampf, GLboolean); +extern void APIENTRY glSamplePatternSGIS (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRY * PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glArrayElementEXT (GLint); +extern void APIENTRY glColorPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +extern void APIENTRY glDrawArraysEXT (GLenum, GLint, GLsizei); +extern void APIENTRY glEdgeFlagPointerEXT (GLsizei, GLsizei, const GLboolean *); +extern void APIENTRY glGetPointervEXT (GLenum, GLvoid* *); +extern void APIENTRY glIndexPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +extern void APIENTRY glNormalPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +extern void APIENTRY glTexCoordPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +extern void APIENTRY glVertexPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRY * PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRY * PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRY * PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRY * PFNGLGETPOINTERVEXTPROC) (GLenum pname, GLvoid* *params); +typedef void (APIENTRY * PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRY * PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRY * PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRY * PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glBlendEquationEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSpriteParameterfSGIX (GLenum, GLfloat); +extern void APIENTRY glSpriteParameterfvSGIX (GLenum, const GLfloat *); +extern void APIENTRY glSpriteParameteriSGIX (GLenum, GLint); +extern void APIENTRY glSpriteParameterivSGIX (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glPointParameterfEXT (GLenum, GLfloat); +extern void APIENTRY glPointParameterfvEXT (GLenum, const GLfloat *); +extern void APIENTRY glPointParameterfSGIS (GLenum, GLfloat); +extern void APIENTRY glPointParameterfvSGIS (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#ifdef GL_GLEXT_PROTOTYPES +extern GLint APIENTRY glGetInstrumentsSGIX (void); +extern void APIENTRY glInstrumentsBufferSGIX (GLsizei, GLint *); +extern GLint APIENTRY glPollInstrumentsSGIX (GLint *); +extern void APIENTRY glReadInstrumentsSGIX (GLint); +extern void APIENTRY glStartInstrumentsSGIX (void); +extern void APIENTRY glStopInstrumentsSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLint (APIENTRY * PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRY * PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRY * PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRY * PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRY * PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRY * PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFrameZoomSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTagSampleBufferSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_SGIX_polynomial_ffd 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glDeformationMap3dSGIX (GLenum, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, const GLdouble *); +extern void APIENTRY glDeformationMap3fSGIX (GLenum, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, const GLfloat *); +extern void APIENTRY glDeformSGIX (GLbitfield); +extern void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +typedef void (APIENTRY * PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +typedef void (APIENTRY * PFNGLDEFORMSGIXPROC) (GLbitfield mask); +typedef void (APIENTRY * PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glReferencePlaneSGIX (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFlushRasterSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFLUSHRASTERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFogFuncSGIS (GLsizei, const GLfloat *); +extern void APIENTRY glGetFogFuncSGIS (const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRY * PFNGLGETFOGFUNCSGISPROC) (const GLfloat *points); +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glImageTransformParameteriHP (GLenum, GLenum, GLint); +extern void APIENTRY glImageTransformParameterfHP (GLenum, GLenum, GLfloat); +extern void APIENTRY glImageTransformParameterivHP (GLenum, GLenum, const GLint *); +extern void APIENTRY glImageTransformParameterfvHP (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glGetImageTransformParameterivHP (GLenum, GLenum, GLint *); +extern void APIENTRY glGetImageTransformParameterfvHP (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glColorSubTableEXT (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glCopyColorSubTableEXT (GLenum, GLsizei, GLint, GLint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glHintPGI (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glColorTableEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glGetColorTableEXT (GLenum, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetColorTableParameterivEXT (GLenum, GLenum, GLint *); +extern void APIENTRY glGetColorTableParameterfvEXT (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRY * PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *data); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glGetListParameterfvSGIX (GLuint, GLenum, GLfloat *); +extern void APIENTRY glGetListParameterivSGIX (GLuint, GLenum, GLint *); +extern void APIENTRY glListParameterfSGIX (GLuint, GLenum, GLfloat); +extern void APIENTRY glListParameterfvSGIX (GLuint, GLenum, const GLfloat *); +extern void APIENTRY glListParameteriSGIX (GLuint, GLenum, GLint); +extern void APIENTRY glListParameterivSGIX (GLuint, GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glIndexMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glIndexFuncEXT (GLenum, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glLockArraysEXT (GLint, GLsizei); +extern void APIENTRY glUnlockArraysEXT (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRY * PFNGLUNLOCKARRAYSEXTPROC) (void); +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glCullParameterdvEXT (GLenum, GLdouble *); +extern void APIENTRY glCullParameterfvEXT (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRY * PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFragmentColorMaterialSGIX (GLenum, GLenum); +extern void APIENTRY glFragmentLightfSGIX (GLenum, GLenum, GLfloat); +extern void APIENTRY glFragmentLightfvSGIX (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glFragmentLightiSGIX (GLenum, GLenum, GLint); +extern void APIENTRY glFragmentLightivSGIX (GLenum, GLenum, const GLint *); +extern void APIENTRY glFragmentLightModelfSGIX (GLenum, GLfloat); +extern void APIENTRY glFragmentLightModelfvSGIX (GLenum, const GLfloat *); +extern void APIENTRY glFragmentLightModeliSGIX (GLenum, GLint); +extern void APIENTRY glFragmentLightModelivSGIX (GLenum, const GLint *); +extern void APIENTRY glFragmentMaterialfSGIX (GLenum, GLenum, GLfloat); +extern void APIENTRY glFragmentMaterialfvSGIX (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glFragmentMaterialiSGIX (GLenum, GLenum, GLint); +extern void APIENTRY glFragmentMaterialivSGIX (GLenum, GLenum, const GLint *); +extern void APIENTRY glGetFragmentLightfvSGIX (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetFragmentLightivSGIX (GLenum, GLenum, GLint *); +extern void APIENTRY glGetFragmentMaterialfvSGIX (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetFragmentMaterialivSGIX (GLenum, GLenum, GLint *); +extern void APIENTRY glLightEnviSGIX (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glDrawRangeElementsEXT (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glApplyTextureEXT (GLenum); +extern void APIENTRY glTextureLightEXT (GLenum); +extern void APIENTRY glTextureMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRY * PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRY * PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_SGIX_async +#define GL_SGIX_async 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glAsyncMarkerSGIX (GLuint); +extern GLint APIENTRY glFinishAsyncSGIX (GLuint *); +extern GLint APIENTRY glPollAsyncSGIX (GLuint *); +extern GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei); +extern void APIENTRY glDeleteAsyncMarkersSGIX (GLuint, GLsizei); +extern GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (APIENTRY * PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); +typedef GLint (APIENTRY * PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); +typedef GLuint (APIENTRY * PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef void (APIENTRY * PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLboolean (APIENTRY * PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glVertexPointervINTEL (GLint, GLenum, const GLvoid* *); +extern void APIENTRY glNormalPointervINTEL (GLenum, const GLvoid* *); +extern void APIENTRY glColorPointervINTEL (GLint, GLenum, const GLvoid* *); +extern void APIENTRY glTexCoordPointervINTEL (GLint, GLenum, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRY * PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const GLvoid* *pointer); +typedef void (APIENTRY * PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRY * PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glPixelTransformParameteriEXT (GLenum, GLenum, GLint); +extern void APIENTRY glPixelTransformParameterfEXT (GLenum, GLenum, GLfloat); +extern void APIENTRY glPixelTransformParameterivEXT (GLenum, GLenum, const GLint *); +extern void APIENTRY glPixelTransformParameterfvEXT (GLenum, GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSecondaryColor3bEXT (GLbyte, GLbyte, GLbyte); +extern void APIENTRY glSecondaryColor3bvEXT (const GLbyte *); +extern void APIENTRY glSecondaryColor3dEXT (GLdouble, GLdouble, GLdouble); +extern void APIENTRY glSecondaryColor3dvEXT (const GLdouble *); +extern void APIENTRY glSecondaryColor3fEXT (GLfloat, GLfloat, GLfloat); +extern void APIENTRY glSecondaryColor3fvEXT (const GLfloat *); +extern void APIENTRY glSecondaryColor3iEXT (GLint, GLint, GLint); +extern void APIENTRY glSecondaryColor3ivEXT (const GLint *); +extern void APIENTRY glSecondaryColor3sEXT (GLshort, GLshort, GLshort); +extern void APIENTRY glSecondaryColor3svEXT (const GLshort *); +extern void APIENTRY glSecondaryColor3ubEXT (GLubyte, GLubyte, GLubyte); +extern void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *); +extern void APIENTRY glSecondaryColor3uiEXT (GLuint, GLuint, GLuint); +extern void APIENTRY glSecondaryColor3uivEXT (const GLuint *); +extern void APIENTRY glSecondaryColor3usEXT (GLushort, GLushort, GLushort); +extern void APIENTRY glSecondaryColor3usvEXT (const GLushort *); +extern void APIENTRY glSecondaryColorPointerEXT (GLint, GLenum, GLsizei, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTextureNormalEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glMultiDrawArraysEXT (GLenum, GLint *, GLsizei *, GLsizei); +extern void APIENTRY glMultiDrawElementsEXT (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (APIENTRY * PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFogCoordfEXT (GLfloat); +extern void APIENTRY glFogCoordfvEXT (const GLfloat *); +extern void APIENTRY glFogCoorddEXT (GLdouble); +extern void APIENTRY glFogCoorddvEXT (const GLdouble *); +extern void APIENTRY glFogCoordPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRY * PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRY * PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRY * PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRY * PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTangent3bEXT (GLbyte, GLbyte, GLbyte); +extern void APIENTRY glTangent3bvEXT (const GLbyte *); +extern void APIENTRY glTangent3dEXT (GLdouble, GLdouble, GLdouble); +extern void APIENTRY glTangent3dvEXT (const GLdouble *); +extern void APIENTRY glTangent3fEXT (GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTangent3fvEXT (const GLfloat *); +extern void APIENTRY glTangent3iEXT (GLint, GLint, GLint); +extern void APIENTRY glTangent3ivEXT (const GLint *); +extern void APIENTRY glTangent3sEXT (GLshort, GLshort, GLshort); +extern void APIENTRY glTangent3svEXT (const GLshort *); +extern void APIENTRY glBinormal3bEXT (GLbyte, GLbyte, GLbyte); +extern void APIENTRY glBinormal3bvEXT (const GLbyte *); +extern void APIENTRY glBinormal3dEXT (GLdouble, GLdouble, GLdouble); +extern void APIENTRY glBinormal3dvEXT (const GLdouble *); +extern void APIENTRY glBinormal3fEXT (GLfloat, GLfloat, GLfloat); +extern void APIENTRY glBinormal3fvEXT (const GLfloat *); +extern void APIENTRY glBinormal3iEXT (GLint, GLint, GLint); +extern void APIENTRY glBinormal3ivEXT (const GLint *); +extern void APIENTRY glBinormal3sEXT (GLshort, GLshort, GLshort); +extern void APIENTRY glBinormal3svEXT (const GLshort *); +extern void APIENTRY glTangentPointerEXT (GLenum, GLsizei, const GLvoid *); +extern void APIENTRY glBinormalPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRY * PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRY * PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRY * PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRY * PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRY * PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRY * PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRY * PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRY * PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRY * PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRY * PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRY * PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRY * PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRY * PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRY * PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRY * PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFinishTextureSUNX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFINISHTEXTURESUNXPROC) (void); +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glGlobalAlphaFactorbSUN (GLbyte); +extern void APIENTRY glGlobalAlphaFactorsSUN (GLshort); +extern void APIENTRY glGlobalAlphaFactoriSUN (GLint); +extern void APIENTRY glGlobalAlphaFactorfSUN (GLfloat); +extern void APIENTRY glGlobalAlphaFactordSUN (GLdouble); +extern void APIENTRY glGlobalAlphaFactorubSUN (GLubyte); +extern void APIENTRY glGlobalAlphaFactorusSUN (GLushort); +extern void APIENTRY glGlobalAlphaFactoruiSUN (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glReplacementCodeuiSUN (GLuint); +extern void APIENTRY glReplacementCodeusSUN (GLushort); +extern void APIENTRY glReplacementCodeubSUN (GLubyte); +extern void APIENTRY glReplacementCodeuivSUN (const GLuint *); +extern void APIENTRY glReplacementCodeusvSUN (const GLushort *); +extern void APIENTRY glReplacementCodeubvSUN (const GLubyte *); +extern void APIENTRY glReplacementCodePointerSUN (GLenum, GLsizei, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const GLvoid* *pointer); +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glColor4ubVertex2fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat); +extern void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *, const GLfloat *); +extern void APIENTRY glColor4ubVertex3fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *, const GLfloat *); +extern void APIENTRY glColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glColor3fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void APIENTRY glNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord2fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord4fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat, GLfloat, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *, const GLubyte *, const GLfloat *); +extern void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLenum *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLenum, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLenum *, const GLubyte *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRY * PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRY * PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRY * PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLenum rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLenum rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLenum *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLenum rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLenum rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLenum rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLenum rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLenum rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLenum rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glBlendFuncSeparateEXT (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glVertexWeightfEXT (GLfloat); +extern void APIENTRY glVertexWeightfvEXT (const GLfloat *); +extern void APIENTRY glVertexWeightPointerEXT (GLsizei, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRY * PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRY * PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLsizei size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFlushVertexArrayRangeNV (void); +extern void APIENTRY glVertexArrayRangeNV (GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRY * PFNGLVERTEXARRAYRANGENVPROC) (GLsizei size, const GLvoid *pointer); +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glCombinerParameterfvNV (GLenum, const GLfloat *); +extern void APIENTRY glCombinerParameterfNV (GLenum, GLfloat); +extern void APIENTRY glCombinerParameterivNV (GLenum, const GLint *); +extern void APIENTRY glCombinerParameteriNV (GLenum, GLint); +extern void APIENTRY glCombinerInputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum); +extern void APIENTRY glCombinerOutputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean); +extern void APIENTRY glFinalCombinerInputNV (GLenum, GLenum, GLenum, GLenum); +extern void APIENTRY glGetCombinerInputParameterfvNV (GLenum, GLenum, GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetCombinerInputParameterivNV (GLenum, GLenum, GLenum, GLenum, GLint *); +extern void APIENTRY glGetCombinerOutputParameterfvNV (GLenum, GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetCombinerOutputParameterivNV (GLenum, GLenum, GLenum, GLint *); +extern void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRY * PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRY * PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRY * PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glResizeBuffersMESA (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLRESIZEBUFFERSMESAPROC) (void); +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glWindowPos2dMESA (GLdouble, GLdouble); +extern void APIENTRY glWindowPos2dvMESA (const GLdouble *); +extern void APIENTRY glWindowPos2fMESA (GLfloat, GLfloat); +extern void APIENTRY glWindowPos2fvMESA (const GLfloat *); +extern void APIENTRY glWindowPos2iMESA (GLint, GLint); +extern void APIENTRY glWindowPos2ivMESA (const GLint *); +extern void APIENTRY glWindowPos2sMESA (GLshort, GLshort); +extern void APIENTRY glWindowPos2svMESA (const GLshort *); +extern void APIENTRY glWindowPos3dMESA (GLdouble, GLdouble, GLdouble); +extern void APIENTRY glWindowPos3dvMESA (const GLdouble *); +extern void APIENTRY glWindowPos3fMESA (GLfloat, GLfloat, GLfloat); +extern void APIENTRY glWindowPos3fvMESA (const GLfloat *); +extern void APIENTRY glWindowPos3iMESA (GLint, GLint, GLint); +extern void APIENTRY glWindowPos3ivMESA (const GLint *); +extern void APIENTRY glWindowPos3sMESA (GLshort, GLshort, GLshort); +extern void APIENTRY glWindowPos3svMESA (const GLshort *); +extern void APIENTRY glWindowPos4dMESA (GLdouble, GLdouble, GLdouble, GLdouble); +extern void APIENTRY glWindowPos4dvMESA (const GLdouble *); +extern void APIENTRY glWindowPos4fMESA (GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glWindowPos4fvMESA (const GLfloat *); +extern void APIENTRY glWindowPos4iMESA (GLint, GLint, GLint, GLint); +extern void APIENTRY glWindowPos4ivMESA (const GLint *); +extern void APIENTRY glWindowPos4sMESA (GLshort, GLshort, GLshort, GLshort); +extern void APIENTRY glWindowPos4svMESA (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRY * PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRY * PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRY * PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRY * PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRY * PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRY * PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRY * PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRY * PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRY * PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRY * PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRY * PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRY * PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRY * PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glMultiModeDrawArraysIBM (GLenum, const GLint *, const GLsizei *, GLsizei, GLint); +extern void APIENTRY glMultiModeDrawElementsIBM (const GLenum *, const GLsizei *, GLenum, const GLvoid* *, GLsizei, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLMULTIMODEDRAWARRAYSIBMPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRY * PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount, GLint modestride); +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glSecondaryColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glEdgeFlagPointerListIBM (GLint, const GLboolean* *, GLint); +extern void APIENTRY glFogCoordPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glIndexPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glNormalPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glTexCoordPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glVertexPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTbufferMask3DFX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSampleMaskEXT (GLclampf, GLboolean); +extern void APIENTRY glSamplePatternEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRY * PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#endif + +#ifndef GL_SGI_vertex_preclip +#define GL_SGI_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTextureColorMaskSGIS (GLboolean, GLboolean, GLboolean, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif + +#ifndef GL_SGIX_igloo_interface +#define GL_SGIX_igloo_interface 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glIglooInterfaceSGIX (GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const GLvoid *params); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/codemp/renderer/glext_console.h b/codemp/renderer/glext_console.h new file mode 100644 index 0000000..588dfbc --- /dev/null +++ b/codemp/renderer/glext_console.h @@ -0,0 +1,2521 @@ +#ifndef __glext_h_ +#define __glext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +/*************************************************************/ + +/* Header file version number, required by OpenGL ABI for Linux */ +#define GL_GLEXT_VERSION 6 + +#ifndef GL_VERSION_1_2 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_VSYNC 0x813F +#endif + +#ifndef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +#endif + +#ifndef GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif + +#ifndef GL_ARB_texture_compression +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef GL_EXT_blend_color +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef GL_EXT_texture +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#define GL_TPL4_EXT 0x9991 +#define GL_TPL8_EXT 0x9992 +#define GL_TPL16_EXT 0x9993 +#define GL_TPL32_EXT 0x9994 +#define GL_DDS1_EXT 0x9995 +#define GL_DDS5_EXT 0x9996 +#define GL_DDS_RGB16_EXT 0x9997 +#define GL_DDS_RGBA32_EXT 0x9998 +#define GL_RGB_SWIZZLE_EXT 0x9999 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef GL_EXT_histogram +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef GL_EXT_convolution +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +#endif + +#ifndef GL_SGI_color_matrix +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif + +#ifndef GL_SGI_color_table +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef GL_EXT_cmyka +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif + +#ifndef GL_EXT_texture_object +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif + +#ifndef GL_SGIS_multisample +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef GL_SGIX_shadow +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef GL_EXT_light_texture +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif + +#ifndef GL_EXT_secondary_color +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef GL_EXT_fog_coord +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE3_RGB_EXT 0x8583 +#define GL_SOURCE4_RGB_EXT 0x8584 +#define GL_SOURCE5_RGB_EXT 0x8585 +#define GL_SOURCE6_RGB_EXT 0x8586 +#define GL_SOURCE7_RGB_EXT 0x8587 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_SOURCE3_ALPHA_EXT 0x858B +#define GL_SOURCE4_ALPHA_EXT 0x858C +#define GL_SOURCE5_ALPHA_EXT 0x858D +#define GL_SOURCE6_ALPHA_EXT 0x858E +#define GL_SOURCE7_ALPHA_EXT 0x858F +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND3_RGB_EXT 0x8593 +#define GL_OPERAND4_RGB_EXT 0x8594 +#define GL_OPERAND5_RGB_EXT 0x8595 +#define GL_OPERAND6_RGB_EXT 0x8596 +#define GL_OPERAND7_RGB_EXT 0x8597 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#define GL_OPERAND3_ALPHA_EXT 0x859B +#define GL_OPERAND4_ALPHA_EXT 0x859C +#define GL_OPERAND5_ALPHA_EXT 0x859D +#define GL_OPERAND6_ALPHA_EXT 0x859E +#define GL_OPERAND7_ALPHA_EXT 0x859F +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef GL_SUN_triangle_list +#define GL_RESTART_SUN 0x01 +#define GL_REPLACE_MIDDLE_SUN 0x02 +#define GL_REPLACE_OLDEST_SUN 0x03 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +#endif + +#ifndef GL_INGR_color_clamp +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef GL_EXT_texture_cube_map +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW_MATRIX1_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +#endif + +#ifndef GL_NV_register_combiners +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef GL_EXT_multisample +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + + +/*************************************************************/ + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glBlendColor (GLclampf, GLclampf, GLclampf, GLclampf); +extern void glBlendEquation (GLenum); +extern void glDrawRangeElements (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +extern void glColorTable (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glColorTableParameterfv (GLenum, GLenum, const GLfloat *); +extern void glColorTableParameteriv (GLenum, GLenum, const GLint *); +extern void glCopyColorTable (GLenum, GLenum, GLint, GLint, GLsizei); +extern void glGetColorTable (GLenum, GLenum, GLenum, GLvoid *); +extern void glGetColorTableParameterfv (GLenum, GLenum, GLfloat *); +extern void glGetColorTableParameteriv (GLenum, GLenum, GLint *); +extern void glColorSubTable (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glCopyColorSubTable (GLenum, GLsizei, GLint, GLint, GLsizei); +extern void glConvolutionFilter1D (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glConvolutionFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glConvolutionParameterf (GLenum, GLenum, GLfloat); +extern void glConvolutionParameterfv (GLenum, GLenum, const GLfloat *); +extern void glConvolutionParameteri (GLenum, GLenum, GLint); +extern void glConvolutionParameteriv (GLenum, GLenum, const GLint *); +extern void glCopyConvolutionFilter1D (GLenum, GLenum, GLint, GLint, GLsizei); +extern void glCopyConvolutionFilter2D (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +extern void glGetConvolutionFilter (GLenum, GLenum, GLenum, GLvoid *); +extern void glGetConvolutionParameterfv (GLenum, GLenum, GLfloat *); +extern void glGetConvolutionParameteriv (GLenum, GLenum, GLint *); +extern void glGetSeparableFilter (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +extern void glSeparableFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +extern void glGetHistogram (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void glGetHistogramParameterfv (GLenum, GLenum, GLfloat *); +extern void glGetHistogramParameteriv (GLenum, GLenum, GLint *); +extern void glGetMinmax (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void glGetMinmaxParameterfv (GLenum, GLenum, GLfloat *); +extern void glGetMinmaxParameteriv (GLenum, GLenum, GLint *); +extern void glHistogram (GLenum, GLsizei, GLenum, GLboolean); +extern void glMinmax (GLenum, GLenum, GLboolean); +extern void glResetHistogram (GLenum); +extern void glResetMinmax (GLenum); +extern void glTexImage3D (GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void glTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glCopyTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glActiveTextureARB (GLenum); +extern void glClientActiveTextureARB (GLenum); +extern void glMultiTexCoord1dARB (GLenum, GLdouble); +extern void glMultiTexCoord1dvARB (GLenum, const GLdouble *); +extern void glMultiTexCoord1fARB (GLenum, GLfloat); +extern void glMultiTexCoord1fvARB (GLenum, const GLfloat *); +extern void glMultiTexCoord1iARB (GLenum, GLint); +extern void glMultiTexCoord1ivARB (GLenum, const GLint *); +extern void glMultiTexCoord1sARB (GLenum, GLshort); +extern void glMultiTexCoord1svARB (GLenum, const GLshort *); +extern void glMultiTexCoord2dARB (GLenum, GLdouble, GLdouble); +extern void glMultiTexCoord2dvARB (GLenum, const GLdouble *); +extern void glMultiTexCoord2fARB (GLenum, GLfloat, GLfloat); +extern void glMultiTexCoord2fvARB (GLenum, const GLfloat *); +extern void glMultiTexCoord2iARB (GLenum, GLint, GLint); +extern void glMultiTexCoord2ivARB (GLenum, const GLint *); +extern void glMultiTexCoord2sARB (GLenum, GLshort, GLshort); +extern void glMultiTexCoord2svARB (GLenum, const GLshort *); +extern void glMultiTexCoord3dARB (GLenum, GLdouble, GLdouble, GLdouble); +extern void glMultiTexCoord3dvARB (GLenum, const GLdouble *); +extern void glMultiTexCoord3fARB (GLenum, GLfloat, GLfloat, GLfloat); +extern void glMultiTexCoord3fvARB (GLenum, const GLfloat *); +extern void glMultiTexCoord3iARB (GLenum, GLint, GLint, GLint); +extern void glMultiTexCoord3ivARB (GLenum, const GLint *); +extern void glMultiTexCoord3sARB (GLenum, GLshort, GLshort, GLshort); +extern void glMultiTexCoord3svARB (GLenum, const GLshort *); +extern void glMultiTexCoord4dARB (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +extern void glMultiTexCoord4dvARB (GLenum, const GLdouble *); +extern void glMultiTexCoord4fARB (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glMultiTexCoord4fvARB (GLenum, const GLfloat *); +extern void glMultiTexCoord4iARB (GLenum, GLint, GLint, GLint, GLint); +extern void glMultiTexCoord4ivARB (GLenum, const GLint *); +extern void glMultiTexCoord4sARB (GLenum, GLshort, GLshort, GLshort, GLshort); +extern void glMultiTexCoord4svARB (GLenum, const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glLoadTransposeMatrixfARB (const GLfloat *); +extern void glLoadTransposeMatrixdARB (const GLdouble *); +extern void glMultTransposeMatrixfARB (const GLfloat *); +extern void glMultTransposeMatrixdARB (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSampleCoverageARB (GLclampf, GLboolean); +extern void glSamplePassARB (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glCompressedTexImage3DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +extern void glCompressedTexImage2DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +extern void glCompressedTexImage1DARB (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +extern void glCompressedTexSubImage3DARB (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void glCompressedTexSubImage2DARB (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void glCompressedTexSubImage1DARB (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void glGetCompressedTexImageARB (GLenum, GLint, void *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glBlendColorEXT (GLclampf, GLclampf, GLclampf, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glPolygonOffsetEXT (GLfloat, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTexImage3DEXT (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void glTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glGetTexFilterFuncSGIS (GLenum, GLenum, GLfloat *); +extern void glTexFilterFuncSGIS (GLenum, GLenum, GLsizei, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTexSubImage1DEXT (GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glCopyTexImage1DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLint); +extern void glCopyTexImage2DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint); +extern void glCopyTexSubImage1DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei); +extern void glCopyTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +extern void glCopyTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glGetHistogramEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void glGetHistogramParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void glGetHistogramParameterivEXT (GLenum, GLenum, GLint *); +extern void glGetMinmaxEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void glGetMinmaxParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void glGetMinmaxParameterivEXT (GLenum, GLenum, GLint *); +extern void glHistogramEXT (GLenum, GLsizei, GLenum, GLboolean); +extern void glMinmaxEXT (GLenum, GLenum, GLboolean); +extern void glResetHistogramEXT (GLenum); +extern void glResetMinmaxEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glConvolutionFilter1DEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glConvolutionFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glConvolutionParameterfEXT (GLenum, GLenum, GLfloat); +extern void glConvolutionParameterfvEXT (GLenum, GLenum, const GLfloat *); +extern void glConvolutionParameteriEXT (GLenum, GLenum, GLint); +extern void glConvolutionParameterivEXT (GLenum, GLenum, const GLint *); +extern void glCopyConvolutionFilter1DEXT (GLenum, GLenum, GLint, GLint, GLsizei); +extern void glCopyConvolutionFilter2DEXT (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +extern void glGetConvolutionFilterEXT (GLenum, GLenum, GLenum, GLvoid *); +extern void glGetConvolutionParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void glGetConvolutionParameterivEXT (GLenum, GLenum, GLint *); +extern void glGetSeparableFilterEXT (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +extern void glSeparableFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_color_matrix +#define GL_EXT_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glColorTableSGI (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glColorTableParameterfvSGI (GLenum, GLenum, const GLfloat *); +extern void glColorTableParameterivSGI (GLenum, GLenum, const GLint *); +extern void glCopyColorTableSGI (GLenum, GLenum, GLint, GLint, GLsizei); +extern void glGetColorTableSGI (GLenum, GLenum, GLenum, GLvoid *); +extern void glGetColorTableParameterfvSGI (GLenum, GLenum, GLfloat *); +extern void glGetColorTableParameterivSGI (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glPixelTexGenSGIX (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glPixelTexGenParameteriSGIS (GLenum, GLint); +extern void glPixelTexGenParameterivSGIS (GLenum, const GLint *); +extern void glPixelTexGenParameterfSGIS (GLenum, GLfloat); +extern void glPixelTexGenParameterfvSGIS (GLenum, const GLfloat *); +extern void glGetPixelTexGenParameterivSGIS (GLenum, GLint *); +extern void glGetPixelTexGenParameterfvSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTexImage4DSGIS (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void glTexSubImage4DSGIS (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#ifdef GL_GLEXT_PROTOTYPES +extern GLboolean glAreTexturesResidentEXT (GLsizei, const GLuint *, GLboolean *); +extern void glBindTextureEXT (GLenum, GLuint); +extern void glDeleteTexturesEXT (GLsizei, const GLuint *); +extern void glGenTexturesEXT (GLsizei, GLuint *); +extern GLboolean glIsTextureEXT (GLuint); +extern void glPrioritizeTexturesEXT (GLsizei, const GLuint *, const GLclampf *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glDetailTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +extern void glGetDetailTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSharpenTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +extern void glGetSharpenTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSampleMaskSGIS (GLclampf, GLboolean); +extern void glSamplePatternSGIS (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glArrayElementEXT (GLint); +extern void glColorPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +extern void glDrawArraysEXT (GLenum, GLint, GLsizei); +extern void glEdgeFlagPointerEXT (GLsizei, GLsizei, const GLboolean *); +extern void glGetPointervEXT (GLenum, GLvoid* *); +extern void glIndexPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +extern void glNormalPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +extern void glTexCoordPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +extern void glVertexPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glBlendEquationEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSpriteParameterfSGIX (GLenum, GLfloat); +extern void glSpriteParameterfvSGIX (GLenum, const GLfloat *); +extern void glSpriteParameteriSGIX (GLenum, GLint); +extern void glSpriteParameterivSGIX (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glPointParameterfEXT (GLenum, GLfloat); +extern void glPointParameterfvEXT (GLenum, const GLfloat *); +extern void glPointParameterfSGIS (GLenum, GLfloat); +extern void glPointParameterfvSGIS (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#ifdef GL_GLEXT_PROTOTYPES +extern GLint glGetInstrumentsSGIX (void); +extern void glInstrumentsBufferSGIX (GLsizei, GLint *); +extern GLint glPollInstrumentsSGIX (GLint *); +extern void glReadInstrumentsSGIX (GLint); +extern void glStartInstrumentsSGIX (void); +extern void glStopInstrumentsSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFrameZoomSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTagSampleBufferSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glReferencePlaneSGIX (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFlushRasterSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFogFuncSGIS (GLsizei, const GLfloat *); +extern void glGetFogFuncSGIS (const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glImageTransformParameteriHP (GLenum, GLenum, GLint); +extern void glImageTransformParameterfHP (GLenum, GLenum, GLfloat); +extern void glImageTransformParameterivHP (GLenum, GLenum, const GLint *); +extern void glImageTransformParameterfvHP (GLenum, GLenum, const GLfloat *); +extern void glGetImageTransformParameterivHP (GLenum, GLenum, GLint *); +extern void glGetImageTransformParameterfvHP (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glColorSubTableEXT (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glCopyColorSubTableEXT (GLenum, GLsizei, GLint, GLint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glHintPGI (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glColorTableEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glGetColorTableEXT (GLenum, GLenum, GLenum, GLvoid *); +extern void glGetColorTableParameterivEXT (GLenum, GLenum, GLint *); +extern void glGetColorTableParameterfvEXT (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glGetListParameterfvSGIX (GLuint, GLenum, GLfloat *); +extern void glGetListParameterivSGIX (GLuint, GLenum, GLint *); +extern void glListParameterfSGIX (GLuint, GLenum, GLfloat); +extern void glListParameterfvSGIX (GLuint, GLenum, const GLfloat *); +extern void glListParameteriSGIX (GLuint, GLenum, GLint); +extern void glListParameterivSGIX (GLuint, GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glIndexMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glIndexFuncEXT (GLenum, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glLockArraysEXT (GLint, GLsizei); +extern void glUnlockArraysEXT (void); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glCullParameterdvEXT (GLenum, GLdouble *); +extern void glCullParameterfvEXT (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFragmentColorMaterialSGIX (GLenum, GLenum); +extern void glFragmentLightfSGIX (GLenum, GLenum, GLfloat); +extern void glFragmentLightfvSGIX (GLenum, GLenum, const GLfloat *); +extern void glFragmentLightiSGIX (GLenum, GLenum, GLint); +extern void glFragmentLightivSGIX (GLenum, GLenum, const GLint *); +extern void glFragmentLightModelfSGIX (GLenum, GLfloat); +extern void glFragmentLightModelfvSGIX (GLenum, const GLfloat *); +extern void glFragmentLightModeliSGIX (GLenum, GLint); +extern void glFragmentLightModelivSGIX (GLenum, const GLint *); +extern void glFragmentMaterialfSGIX (GLenum, GLenum, GLfloat); +extern void glFragmentMaterialfvSGIX (GLenum, GLenum, const GLfloat *); +extern void glFragmentMaterialiSGIX (GLenum, GLenum, GLint); +extern void glFragmentMaterialivSGIX (GLenum, GLenum, const GLint *); +extern void glGetFragmentLightfvSGIX (GLenum, GLenum, GLfloat *); +extern void glGetFragmentLightivSGIX (GLenum, GLenum, GLint *); +extern void glGetFragmentMaterialfvSGIX (GLenum, GLenum, GLfloat *); +extern void glGetFragmentMaterialivSGIX (GLenum, GLenum, GLint *); +extern void glLightEnviSGIX (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glDrawRangeElementsEXT (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glApplyTextureEXT (GLenum); +extern void glTextureLightEXT (GLenum); +extern void glTextureMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glVertexPointervINTEL (GLint, GLenum, const GLvoid* *); +extern void glNormalPointervINTEL (GLenum, const GLvoid* *); +extern void glColorPointervINTEL (GLint, GLenum, const GLvoid* *); +extern void glTexCoordPointervINTEL (GLint, GLenum, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glPixelTransformParameteriEXT (GLenum, GLenum, GLint); +extern void glPixelTransformParameterfEXT (GLenum, GLenum, GLfloat); +extern void glPixelTransformParameterivEXT (GLenum, GLenum, const GLint *); +extern void glPixelTransformParameterfvEXT (GLenum, GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSecondaryColor3bEXT (GLbyte, GLbyte, GLbyte); +extern void glSecondaryColor3bvEXT (const GLbyte *); +extern void glSecondaryColor3dEXT (GLdouble, GLdouble, GLdouble); +extern void glSecondaryColor3dvEXT (const GLdouble *); +extern void glSecondaryColor3fEXT (GLfloat, GLfloat, GLfloat); +extern void glSecondaryColor3fvEXT (const GLfloat *); +extern void glSecondaryColor3iEXT (GLint, GLint, GLint); +extern void glSecondaryColor3ivEXT (const GLint *); +extern void glSecondaryColor3sEXT (GLshort, GLshort, GLshort); +extern void glSecondaryColor3svEXT (const GLshort *); +extern void glSecondaryColor3ubEXT (GLubyte, GLubyte, GLubyte); +extern void glSecondaryColor3ubvEXT (const GLubyte *); +extern void glSecondaryColor3uiEXT (GLuint, GLuint, GLuint); +extern void glSecondaryColor3uivEXT (const GLuint *); +extern void glSecondaryColor3usEXT (GLushort, GLushort, GLushort); +extern void glSecondaryColor3usvEXT (const GLushort *); +extern void glSecondaryColorPointerEXT (GLint, GLenum, GLsizei, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTextureNormalEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glMultiDrawArraysEXT (GLenum, GLint *, GLsizei *, GLsizei); +extern void glMultiDrawElementsEXT (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFogCoordfEXT (GLfloat); +extern void glFogCoordfvEXT (const GLfloat *); +extern void glFogCoorddEXT (GLdouble); +extern void glFogCoorddvEXT (const GLdouble *); +extern void glFogCoordPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTangent3bEXT (GLbyte, GLbyte, GLbyte); +extern void glTangent3bvEXT (const GLbyte *); +extern void glTangent3dEXT (GLdouble, GLdouble, GLdouble); +extern void glTangent3dvEXT (const GLdouble *); +extern void glTangent3fEXT (GLfloat, GLfloat, GLfloat); +extern void glTangent3fvEXT (const GLfloat *); +extern void glTangent3iEXT (GLint, GLint, GLint); +extern void glTangent3ivEXT (const GLint *); +extern void glTangent3sEXT (GLshort, GLshort, GLshort); +extern void glTangent3svEXT (const GLshort *); +extern void glBinormal3bEXT (GLbyte, GLbyte, GLbyte); +extern void glBinormal3bvEXT (const GLbyte *); +extern void glBinormal3dEXT (GLdouble, GLdouble, GLdouble); +extern void glBinormal3dvEXT (const GLdouble *); +extern void glBinormal3fEXT (GLfloat, GLfloat, GLfloat); +extern void glBinormal3fvEXT (const GLfloat *); +extern void glBinormal3iEXT (GLint, GLint, GLint); +extern void glBinormal3ivEXT (const GLint *); +extern void glBinormal3sEXT (GLshort, GLshort, GLshort); +extern void glBinormal3svEXT (const GLshort *); +extern void glTangentPointerEXT (GLenum, GLsizei, const GLvoid *); +extern void glBinormalPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFinishTextureSUNX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glGlobalAlphaFactorbSUN (GLbyte); +extern void glGlobalAlphaFactorsSUN (GLshort); +extern void glGlobalAlphaFactoriSUN (GLint); +extern void glGlobalAlphaFactorfSUN (GLfloat); +extern void glGlobalAlphaFactordSUN (GLdouble); +extern void glGlobalAlphaFactorubSUN (GLubyte); +extern void glGlobalAlphaFactorusSUN (GLushort); +extern void glGlobalAlphaFactoruiSUN (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glReplacementCodeuiSUN (GLuint); +extern void glReplacementCodeusSUN (GLushort); +extern void glReplacementCodeubSUN (GLubyte); +extern void glReplacementCodeuivSUN (const GLuint *); +extern void glReplacementCodeusvSUN (const GLushort *); +extern void glReplacementCodeubvSUN (const GLubyte *); +extern void glReplacementCodePointerSUN (GLenum, GLsizei, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glColor4ubVertex2fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat); +extern void glColor4ubVertex2fvSUN (const GLubyte *, const GLfloat *); +extern void glColor4ubVertex3fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void glColor4ubVertex3fvSUN (const GLubyte *, const GLfloat *); +extern void glColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glColor3fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void glNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void glColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void glTexCoord2fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord2fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void glTexCoord4fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord4fVertex4fvSUN (const GLfloat *, const GLfloat *); +extern void glTexCoord2fColor4ubVertex3fSUN (GLfloat, GLfloat, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *, const GLubyte *, const GLfloat *); +extern void glTexCoord2fColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord2fColor3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void glTexCoord2fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiVertex3fvSUN (const GLenum *, const GLfloat *); +extern void glReplacementCodeuiColor4ubVertex3fSUN (GLenum, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiColor4ubVertex3fvSUN (const GLenum *, const GLubyte *, const GLfloat *); +extern void glReplacementCodeuiColor3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiColor3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiTexCoord2fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glBlendFuncSeparateEXT (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glVertexWeightfEXT (GLfloat); +extern void glVertexWeightfvEXT (const GLfloat *); +extern void glVertexWeightPointerEXT (GLsizei, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFlushVertexArrayRangeNV (void); +extern void glVertexArrayRangeNV (GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glCombinerParameterfvNV (GLenum, const GLfloat *); +extern void glCombinerParameterfNV (GLenum, GLfloat); +extern void glCombinerParameterivNV (GLenum, const GLint *); +extern void glCombinerParameteriNV (GLenum, GLint); +extern void glCombinerInputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum); +extern void glCombinerOutputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean); +extern void glFinalCombinerInputNV (GLenum, GLenum, GLenum, GLenum); +extern void glGetCombinerInputParameterfvNV (GLenum, GLenum, GLenum, GLenum, GLfloat *); +extern void glGetCombinerInputParameterivNV (GLenum, GLenum, GLenum, GLenum, GLint *); +extern void glGetCombinerOutputParameterfvNV (GLenum, GLenum, GLenum, GLfloat *); +extern void glGetCombinerOutputParameterivNV (GLenum, GLenum, GLenum, GLint *); +extern void glGetFinalCombinerInputParameterfvNV (GLenum, GLenum, GLfloat *); +extern void glGetFinalCombinerInputParameterivNV (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glResizeBuffersMESA (void); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glWindowPos2dMESA (GLdouble, GLdouble); +extern void glWindowPos2dvMESA (const GLdouble *); +extern void glWindowPos2fMESA (GLfloat, GLfloat); +extern void glWindowPos2fvMESA (const GLfloat *); +extern void glWindowPos2iMESA (GLint, GLint); +extern void glWindowPos2ivMESA (const GLint *); +extern void glWindowPos2sMESA (GLshort, GLshort); +extern void glWindowPos2svMESA (const GLshort *); +extern void glWindowPos3dMESA (GLdouble, GLdouble, GLdouble); +extern void glWindowPos3dvMESA (const GLdouble *); +extern void glWindowPos3fMESA (GLfloat, GLfloat, GLfloat); +extern void glWindowPos3fvMESA (const GLfloat *); +extern void glWindowPos3iMESA (GLint, GLint, GLint); +extern void glWindowPos3ivMESA (const GLint *); +extern void glWindowPos3sMESA (GLshort, GLshort, GLshort); +extern void glWindowPos3svMESA (const GLshort *); +extern void glWindowPos4dMESA (GLdouble, GLdouble, GLdouble, GLdouble); +extern void glWindowPos4dvMESA (const GLdouble *); +extern void glWindowPos4fMESA (GLfloat, GLfloat, GLfloat, GLfloat); +extern void glWindowPos4fvMESA (const GLfloat *); +extern void glWindowPos4iMESA (GLint, GLint, GLint, GLint); +extern void glWindowPos4ivMESA (const GLint *); +extern void glWindowPos4sMESA (GLshort, GLshort, GLshort, GLshort); +extern void glWindowPos4svMESA (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glMultiModeDrawArraysIBM (GLenum, const GLint *, const GLsizei *, GLsizei, GLint); +extern void glMultiModeDrawElementsIBM (const GLenum *, const GLsizei *, GLenum, const GLvoid* *, GLsizei, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void glSecondaryColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void glEdgeFlagPointerListIBM (GLint, const GLboolean* *, GLint); +extern void glFogCoordPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void glIndexPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void glNormalPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void glTexCoordPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void glVertexPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTbufferMask3DFX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSampleMaskEXT (GLclampf, GLboolean); +extern void glSamplePatternEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGI_vertex_preclip +#define GL_SGI_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTextureColorMaskSGIS (GLboolean, GLboolean, GLboolean, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/codemp/renderer/matcomp.c b/codemp/renderer/matcomp.c new file mode 100644 index 0000000..499f9d6 --- /dev/null +++ b/codemp/renderer/matcomp.c @@ -0,0 +1,293 @@ +#include "matcomp.h" +#include +#include +#include +#include // for memcpy + +#define MC_MASK_X ((1<<(MC_BITS_X))-1) +#define MC_MASK_Y ((1<<(MC_BITS_Y))-1) +#define MC_MASK_Z ((1<<(MC_BITS_Z))-1) +#define MC_MASK_VECT ((1<<(MC_BITS_VECT))-1) + +#define MC_SCALE_VECT (1.0f/(float)((1<<(MC_BITS_VECT-1))-2)) + +#define MC_POS_X (0) +#define MC_SHIFT_X (0) + +#define MC_POS_Y ((((MC_BITS_X))/8)) +#define MC_SHIFT_Y ((((MC_BITS_X)%8))) + +#define MC_POS_Z ((((MC_BITS_X+MC_BITS_Y))/8)) +#define MC_SHIFT_Z ((((MC_BITS_X+MC_BITS_Y)%8))) + +#define MC_POS_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z))/8)) +#define MC_SHIFT_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z)%8))) + +#define MC_POS_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT))/8)) +#define MC_SHIFT_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT)%8))) + +#define MC_POS_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2))/8)) +#define MC_SHIFT_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2)%8))) + +#define MC_POS_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3))/8)) +#define MC_SHIFT_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3)%8))) + +#define MC_POS_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4))/8)) +#define MC_SHIFT_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4)%8))) + +#define MC_POS_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5))/8)) +#define MC_SHIFT_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5)%8))) + +#define MC_POS_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6))/8)) +#define MC_SHIFT_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6)%8))) + +#define MC_POS_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7))/8)) +#define MC_SHIFT_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7)%8))) + +#define MC_POS_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8))/8)) +#define MC_SHIFT_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8)%8))) + +void MC_Compress(const float mat[3][4],unsigned char * _comp) +{ + char comp[MC_COMP_BYTES*2]; + + int i,val; + for (i=0;i=(1<=(1<=(1<=(1<=(1<=(1<=(1<=(1<=(1<=(1<=(1<=(1<_info.txt" file written out by carcass any changes made in here that +// introduce new data should also be reflected in the info-output) + +// 32 bit-flags for ghoul2 bone properties... (all undefined fields will be blank) +// +#define G2BONEFLAG_ALWAYSXFORM 0x00000001 + +// same thing but for surfaces... (Carcass will only generate 1st 2 flags, others are ingame +// +#define G2SURFACEFLAG_ISBOLT 0x00000001 +#define G2SURFACEFLAG_OFF 0x00000002 // saves strcmp()ing for "_off" in surface names +#define G2SURFACEFLAG_SPARE0 0x00000004 // future-expansion fields, saves invalidating models if we add more +#define G2SURFACEFLAG_SPARE1 0x00000008 // +#define G2SURFACEFLAG_SPARE2 0x00000010 // +#define G2SURFACEFLAG_SPARE3 0x00000020 // +#define G2SURFACEFLAG_SPARE4 0x00000040 // +#define G2SURFACEFLAG_SPARE5 0x00000080 // +// +#define G2SURFACEFLAG_NODESCENDANTS 0x00000100 // ingame-stuff, never generated by Carcass.... +#define G2SURFACEFLAG_GENERATED 0x00000200 // + + + +// triangle side-ordering stuff for tags... +// +#define iG2_TRISIDE_MIDDLE 1 +#define iG2_TRISIDE_LONGEST 0 +#define iG2_TRISIDE_SHORTEST 2 + +#define fG2_BONEWEIGHT_RECIPROCAL_MULT ((float)(1.0f/1023.0f)) +#define iG2_BITS_PER_BONEREF 5 +#define iMAX_G2_BONEREFS_PER_SURFACE (1<... + // (note that I've defined it using '>' internally, so it sorts with higher weights being "less", for distance weight-culling + // + #ifdef __cplusplus + bool operator < (const mdxmWeight_t& _X) const {return (boneWeight>_X.boneWeight);} + #endif +} +#ifndef __cplusplus +mdxmWeight_t +#endif +; +*/ +/* +#ifdef __cplusplus +struct mdxaCompBone_t +#else +typedef struct +#endif +{ + unsigned char Comp[24]; // MC_COMP_BYTES is in MatComp.h, but don't want to couple + + // I'm defining this '<' operator so this struct can be used as an STL key... + // + #ifdef __cplusplus + bool operator < (const mdxaCompBone_t& _X) const {return (memcmp(Comp,_X.Comp,sizeof(Comp))<0);} + #endif +} +#ifndef __cplusplus +mdxaCompBone_t +#endif +; +*/ +#ifdef __cplusplus +struct mdxaCompQuatBone_t +#else +typedef struct +#endif +{ + unsigned char Comp[14]; + + // I'm defining this '<' operator so this struct can be used as an STL key... + // + #ifdef __cplusplus + bool operator < (const mdxaCompQuatBone_t& _X) const {return (memcmp(Comp,_X.Comp,sizeof(Comp))<0);} + #endif +} +#ifndef __cplusplus +mdxaCompQuatBone_t +#endif +; + + +#ifndef MDXABONEDEF +typedef struct { + float matrix[3][4]; +} mdxaBone_t; +#endif + +//////////////////////////////////// + + + + + + +// mdxHeader_t - this contains the header for the file, with sanity checking and version checking, plus number of lod's to be expected +// +typedef struct { + // + // ( first 3 fields are same format as MD3/MDR so we can apply easy model-format-type checks ) + // + int ident; // "IDP3" = MD3, "RDM5" = MDR, "2LGM"(GL2 Mesh) = MDX (cruddy char order I know, but I'm following what was there in other versions) + int version; // 1,2,3 etc as per format revision + char name[MAX_QPATH]; // model name (eg "models/players/marine.glm") // note: extension supplied + char animName[MAX_QPATH];// name of animation file this mesh requires // note: extension missing + int animIndex; // filled in by game (carcass defaults it to 0) + + int numBones; // (for ingame version-checks only, ensure we don't ref more bones than skel file has) + + int numLODs; + int ofsLODs; + + int numSurfaces; // now that surfaces are drawn hierarchically, we have same # per LOD + int ofsSurfHierarchy; + + int ofsEnd; // EOF, which of course gives overall file size +} mdxmHeader_t; + + +// for each surface (doesn't actually need a struct for this, just makes source clearer) +// { + typedef struct + { + int offsets[1]; // variable sized (mdxmHeader_t->numSurfaces), each offset points to a mdxmSurfHierarchy_t below + } mdxmHierarchyOffsets_t; +// } + +// for each surface... (mdxmHeader_t->numSurfaces) +// { + // mdxmSurfHierarchy_t - contains hierarchical info for surfaces... + + typedef struct { + char name[MAX_QPATH]; + unsigned int flags; + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use (carcass defaults to 0) + int parentIndex; // this points to the index in the file of the parent surface. -1 if null/root + int numChildren; // number of surfaces which are children of this one + int childIndexes[1]; // [mdxmSurfHierarch_t->numChildren] (variable sized) + } mdxmSurfHierarchy_t; // struct size = (int)( &((mdxmSurfHierarch_t *)0)->childIndexes[ mdxmSurfHierarch_t->numChildren ] ); +// } + + +// for each LOD... (mdxmHeader_t->numLODs) +// { + // mdxLOD_t - this contains the header for this LOD. Contains num of surfaces, offset to surfaces and offset to next LOD. Surfaces are shader sorted, so each surface = 1 shader + + typedef struct { + // (used to contain numSurface/ofsSurfaces fields, but these are same per LOD level now) + // + int ofsEnd; // offset to next LOD + } mdxmLOD_t; + + + typedef struct { // added in GLM version 3 for ingame use at Jake's request + int offsets[1]; // variable sized (mdxmHeader_t->numSurfaces), each offset points to surfaces below + } mdxmLODSurfOffset_t; + + + // for each surface... (mdxmHeader_t->numSurfaces) + // { + // mdxSurface_t - reuse of header format containing surface name, number of bones, offset to poly data and number of polys, offset to vertex information, and number of verts. NOTE offsets are relative to this header. + + typedef struct { + int ident; // this one field at least should be kept, since the game-engine may switch-case (but currently=0 in carcass) + + int thisSurfaceIndex; // 0...mdxmHeader_t->numSurfaces-1 (because of how ingame renderer works) + + int ofsHeader; // this will be a negative number, pointing back to main header + + int numVerts; + int ofsVerts; + + int numTriangles; + int ofsTriangles; + + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + // + int numBoneReferences; + int ofsBoneReferences; + + int ofsEnd; // next surface follows + + } mdxmSurface_t; + + + // for each triangle... (mdxmSurface_t->numTriangles) + // { + // mdxTriangle_t - contains indexes into verts. One struct entry per poly. + + typedef struct { + int indexes[3]; + } mdxmTriangle_t; + // } + + + // for each vert... (mdxmSurface_t->numVerts) + // { + // mdxVertex_t - this is an array with number of verts from the surface definition as its bounds. It contains normal info, texture coors and number of weightings for this bone + // (this is now kept at 32 bytes for cache-aligning) + typedef struct { +#ifdef _XBOX + //short normal[3]; + unsigned int normal; + short vertCoords[3]; + unsigned int tangent; +#else + vec3_t normal; + vec3_t vertCoords; +#endif + + // packed int... + unsigned int uiNmWeightsAndBoneIndexes; // 32 bits. format: + // 31 & 30: 0..3 (= 1..4) weight count + // 29 & 28 (spare) + // 2 bit pairs at 20,22,24,26 are 2-bit overflows from 4 BonWeights below (20=[0], 22=[1]) etc) + // 5-bits each (iG2_BITS_PER_BONEREF) for boneweights + // effectively a packed int, each bone weight converted from 0..1 float to 0..255 int... + // promote each entry to float and multiply by fG2_BONEWEIGHT_RECIPROCAL_MULT to convert. + byte BoneWeightings[iMAX_G2_BONEWEIGHTS_PER_VERT]; // 4 + + } mdxmVertex_t; + + // } vert + +#ifdef __cplusplus + +// these are convenience functions that I can invoked in code. Do NOT change them (because this is a shared file), +// but if you want to copy the logic out and use your own versions then fine... +// +static inline int G2_GetVertWeights( const mdxmVertex_t *pVert ) +{ + int iNumWeights = (pVert->uiNmWeightsAndBoneIndexes >> 30)+1; // 1..4 count + + return iNumWeights; +} + +static inline int G2_GetVertBoneIndex( const mdxmVertex_t *pVert, const int iWeightNum) +{ + int iBoneIndex = (pVert->uiNmWeightsAndBoneIndexes>>(iG2_BITS_PER_BONEREF*iWeightNum))&((1<BoneWeightings[iWeightNum]; + iTemp|= (pVert->uiNmWeightsAndBoneIndexes >> (iG2_BONEWEIGHT_TOPBITS_SHIFT+(iWeightNum*2)) ) & iG2_BONEWEIGHT_TOPBITS_AND; + + fBoneWeight = fG2_BONEWEIGHT_RECIPROCAL_MULT * iTemp; + fTotalWeight += fBoneWeight; + } + + return fBoneWeight; +} +#endif + // for each vert... (mdxmSurface_t->numVerts) (seperated from mdxmVertex_t struct for cache reasons) + // { + // mdxVertex_t - this is an array with number of verts from the surface definition as its bounds. It contains normal info, texture coors and number of weightings for this bone + + typedef struct { +#ifdef _XBOX + short texCoords[2]; +#else + vec2_t texCoords; +#endif + } mdxmVertexTexCoord_t; + + // } vert + + + // } surface +// } LOD + + + +//---------------------------------------------------------------------------- +// seperate file here for animation data... +// + + +// mdxaHeader_t - this contains the header for the file, with sanity checking and version checking, plus number of lod's to be expected +// +typedef struct { + // + // ( first 3 fields are same format as MD3/MDR so we can apply easy model-format-type checks ) + // + int ident; // "IDP3" = MD3, "RDM5" = MDR, "2LGA"(GL2 Anim) = MDXA + int version; // 1,2,3 etc as per format revision + // + char name[MAX_QPATH]; // GLA name (eg "skeletons/marine") // note: extension missing + float fScale; // will be zero if build before this field was defined, else scale it was built with + + // frames and bones are shared by all levels of detail + // + int numFrames; + int ofsFrames; // points at mdxaFrame_t array + int numBones; // (no offset to these since they're inside the frames array) + int ofsCompBonePool; // offset to global compressed-bone pool that all frames use + int ofsSkel; // offset to mdxaSkel_t info + + int ofsEnd; // EOF, which of course gives overall file size + +} mdxaHeader_t; + + +// for each bone... (doesn't actually need a struct for this, just makes source clearer) +// { + typedef struct + { + int offsets[1]; // variable sized (mdxaHeader_t->numBones), each offset points to an mdxaSkel_t below + } mdxaSkelOffsets_t; +// } + + + +// for each bone... (mdxaHeader_t->numBones) +// { + // mdxaSkel_t - contains hierarchical info only... + + typedef struct { + char name[MAX_QPATH]; // name of bone + unsigned int flags; + int parent; // index of bone that is parent to this one, -1 = NULL/root + mdxaBone_t BasePoseMat; // base pose + mdxaBone_t BasePoseMatInv; // inverse, to save run-time calc + int numChildren; // number of children bones + int children[1]; // [mdxaSkel_t->numChildren] (variable sized) + } mdxaSkel_t; // struct size = (int)( &((mdxaSkel_t *)0)->children[ mdxaSkel_t->numChildren ] ); +// } + + + // (offset @ mdxaHeader_t->ofsFrames) + // + // array of 3 byte indices here (hey, 25% saving over 4-byte really adds up)... + // + // + // access as follows to get the index for a given + // + // (iFrameNum * mdxaHeader_t->numBones * 3) + (iBoneNum * 3) + // + // then read the int at that location and AND it with 0x00FFFFFF. I use the struct below simply for easy searches + typedef struct + { + int iIndex; // this struct for pointing purposes, need to and with 0x00FFFFFF to be meaningful + } mdxaIndex_t; + // + // (note that there's then an alignement-pad here to get the next struct back onto 32-bit alignement) + // + // this index then points into the following... + + +// Compressed-bone pool that all frames use (mdxaHeader_t->ofsCompBonePool) (defined at end because size unknown until end) +// for each bone in pool (unknown number, no actual total stored at the moment)... +// { + // mdxaCompBone_t (defined at file top because of struct dependancy) +// } + +//--------------------------------------------------------------------------- + + +#endif // #ifndef MDX_FORMAT_H + +//////////////////////// eof /////////////////////// + + + diff --git a/codemp/renderer/modelmem.h b/codemp/renderer/modelmem.h new file mode 100644 index 0000000..99575d4 --- /dev/null +++ b/codemp/renderer/modelmem.h @@ -0,0 +1,310 @@ +// +// ModelMem.h +// + +// OK, here's the deal with this class... +// At game initialization time, this class sets aside a certain amount of memory +// strictly for use by player models. 6 of these slots are of the size of the +// largest jedi player model, and the other 6 are of the size of the largest +// non-jedi player model (since there are only 6 jedi_xx models). Whenever a +// player model is allocated/deallocated, it uses only the memory that is managed +// by this class. This is done to reduce memory fragmentation that would +// normally occour when many players join and leave a multiplayer game. +// Only the first 8 slots are allocated at first (only 8 players in a normal +// multiplayer game), with the other 4 being allocated only if a connection +// to a dedicated server is detected. + + +//#define MAX_MODEL_JEDI_SIZE 786740 //Amount used for UI slot. +#define MAX_MODEL_SLOTS 11 + +#include "../client/cl_data.h" +#include "../renderer/qgl_console.h" + +extern void RE_RemoveModelFromHash(const char *name); + +extern int uiClientNum; + +typedef struct modelSlot_s +{ + void *memory; + int allocatedSize; + bool inUse; + int modelID; +// int refCount; + char name[64]; +}modelSlot_t; + +class ModelMemoryManager +{ + modelSlot_s modelSlot[MAX_MODEL_SLOTS]; + int numUsedSlots; + bool NPCMode; + +private: + + void FreeModelSlot(int index) + { + assert(index >=0 && index < MAX_MODEL_SLOTS); + + if(modelSlot[index].memory) { + HeapFree(GetProcessHeap(), 0, modelSlot[index].memory); + numUsedSlots--; + } + memset(&modelSlot[index], 0, sizeof(modelSlot[index])); + + assert(numUsedSlots >= 0); + } + + + void AllocateModelSlot(int index, int size, int ID, const char *name) + { + assert(index >=0 && index < MAX_MODEL_SLOTS); + + if(modelSlot[index].memory) { + FreeModelSlot(index); + } + + modelSlot[index].memory = HeapAlloc(GetProcessHeap(), 0, size); + + if(!modelSlot[index].memory) { + assert(0); + //Something used all our heap memory. That's bad. Make the + //screen green. + Com_PrintfAlways("Model manager out of memory trying to allocate %d bytes for %s\n", size, name); + for (;;) + { + qglBeginFrame(); + qglClearColor(0, 1, 0, 1); + qglClear(GL_COLOR_BUFFER_BIT); + qglEndFrame(); + } + } + modelSlot[index].allocatedSize = size; + modelSlot[index].inUse = true; + modelSlot[index].modelID = ID; +// modelSlot[index].refCount = 1; + strcpy(modelSlot[index].name, name); + numUsedSlots++; + + assert(numUsedSlots <= MAX_MODEL_SLOTS); + } + + +public: + char uiName[64]; + char uiSkin[64]; + + ModelMemoryManager(void) + { + memset(modelSlot, 0, sizeof(modelSlot[0]) * MAX_MODEL_SLOTS); + uiName[0] = 0; + uiSkin[0] = 0; + } + + void AllocateModelSlots() + { + numUsedSlots = 0; + } + + void SetNPCMode(bool npcMode) + { + NPCMode = npcMode; + } + + bool IsNPCMode() + { + return NPCMode; + } + + void* GetModelMemory(int size, int ID, const char *name) + { + // Find the first non-used slot with enough memory + // Work backwards to try and use smaller slots first + for(int i = 0; i < MAX_MODEL_SLOTS; i++) + { + if((strcmp(name, modelSlot[i].name) == 0) && modelSlot[i].inUse == true) + { + // The only time this will happen is if there is a holdover model + // left from the UI - so don't increase the refcount + return modelSlot[i].memory; + } + } + + // No slot found, so scan thru the client info to see if a slot can be killed + bool bFound; + for(i = 0; i < MAX_MODEL_SLOTS; i++) + { + // The server NEVER throws out kyle + if(com_sv_running->integer && !strcmp(modelSlot[i].name, "models/players/kyle/model.glm")) + continue; + + bFound = false; + for(int j = 0; j < cgs.maxclients; j++) + { + if(strlen(cgs.clientinfo[j].modelName)) + { + if(!strcmp(modelSlot[i].name, va("models/players/%s/model.glm", cgs.clientinfo[j].modelName))) + { + bFound = true; + break; + } + } + } + + if(strlen(uiName) && !strcmp(modelSlot[i].name, uiName)) + bFound = true; + + if(!bFound && modelSlot[i].inUse) + { + // This model slot is not listed in the clientinfo, kill it + RE_RemoveModelFromHash(modelSlot[i].name); + + FreeModelSlot(i); + } + } + + for(i = 0; i < MAX_MODEL_SLOTS; i++) + { + if(modelSlot[i].inUse == false) + { + AllocateModelSlot(i, size, ID, name); + return modelSlot[i].memory; + } + } + // Something horrible happened if we got here. All model slots are + // in use by active clients and we're trying to allocate another + // one. Find out why and prevent that. + assert(0); + + // Make some debug spew with the hopes of finding the problem if it + // gets to QA. + Com_PrintfAlways("Hi, I'm about to crash. Here's why. I was told \ + to allocate memory for a new model: %s. But all my model \ + slots are already in use. Here's what's using them. Good \ + luck!\n\n", name); + for(i=0; iinteger && !strcmp(modelSlot[i].name, "models/players/kyle/model.glm") ) + timesFound++; + + if( !timesFound ) + Com_Error( ERR_DROP, "FreeModelMemory by name: refCount is negative" ); + + if( timesFound == 1 ) + { + RE_RemoveModelFromHash(name); + FreeModelSlot(i); + } + } + } + } + + // Returns a bool to indicate whether or not an erase was done on the map<> + bool ClearModelMemory(int ID) + { + bool bRemovedFromHash = false; + + for(int i = 0; i < MAX_MODEL_SLOTS; i++) + { + if(modelSlot[i].modelID == ID && modelSlot[i].inUse == true) + { + RE_RemoveModelFromHash(modelSlot[i].name); + + FreeModelSlot(i); + bRemovedFromHash = true; + } + } + + return bRemovedFromHash; + } + + void SetUIName( const char *name ) + { + if( name ) + strcpy( uiName, name ); + else + uiName[0] = 0; + } + + void SetUISkin( const char *name ) + { + if( name ) + strcpy( uiSkin, name ); + else + uiSkin[0] = 0; + } + + const char *GetUISkin( void ) + { + return uiSkin; + } + + void ClearAll() + { + for(int i = 0; i < MAX_MODEL_SLOTS; i++) + { + FreeModelSlot(i); + } + + numUsedSlots = 0; + uiName[0] = 0; + uiSkin[0] = 0; + } +}; + +extern ModelMemoryManager ModelMem; + diff --git a/codemp/renderer/qgl.h b/codemp/renderer/qgl.h new file mode 100644 index 0000000..979c868 --- /dev/null +++ b/codemp/renderer/qgl.h @@ -0,0 +1,757 @@ +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + +#if defined( __LINT__ ) + +#include + +#elif defined( _WIN32 ) + +#pragma warning (disable: 4201) +#pragma warning (disable: 4214) +#pragma warning (disable: 4514) +#pragma warning (disable: 4032) +#pragma warning (disable: 4201) +#pragma warning (disable: 4214) +#include +#include + +#elif defined(MACOS_X) + +#include "macosx_glimp.h" + +#elif defined( __linux__ ) + +#include +#include +// bk001129 - from cvs1.17 (mkv) +#if defined(__FX__) +#include +#endif + +#elif defined( __FreeBSD__ ) // rb010123 + +#include +#include +#if defined(__FX__) +#include +#endif + +#else + +#include + +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef WINAPI +#define WINAPI +#endif + + +//=========================================================================== + +/* +** multitexture extension definitions +*/ +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_ACTIVE_TEXTURES_ARB 0x84E2 + +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 + +#define GL_TEXTURE_RECTANGLE_EXT 0x84F5 + +// TTimo: FIXME +// linux needs those prototypes +// GL_VERSION_1_2 is defined after #include +#if !defined(GL_VERSION_1_2) || defined(__linux__) +typedef void (APIENTRY * PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum target); +typedef void (APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum target); +#endif + + +// Steps to adding a new extension: +// - Add the typedef and function pointer externs here. +// - Define the function pointer in tr_init.cpp and possibly add a cvar to track your ext status. +// - Load the extension in win_glimp.cpp. + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Register Combiner extension definitions. - AReis +/***********************************************************************************************************/ +// NOTE: These are obviously not all the regcom flags. I'm only including the ones I use (to reduce code clutter), so +// if you need any of the other flags, just add them. +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_DISCARD_NV 0x8530 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 + +typedef void (APIENTRY *PFNGLCOMBINERPARAMETERFVNV) (GLenum pname,const GLfloat *params); +typedef void (APIENTRY *PFNGLCOMBINERPARAMETERIVNV) (GLenum pname,const GLint *params); +typedef void (APIENTRY *PFNGLCOMBINERPARAMETERFNV) (GLenum pname,GLfloat param); +typedef void (APIENTRY *PFNGLCOMBINERPARAMETERINV) (GLenum pname,GLint param); +typedef void (APIENTRY *PFNGLCOMBINERINPUTNV) (GLenum stage,GLenum portion,GLenum variable,GLenum input,GLenum mapping, + GLenum componentUsage); +typedef void (APIENTRY *PFNGLCOMBINEROUTPUTNV) (GLenum stage,GLenum portion,GLenum abOutput,GLenum cdOutput,GLenum sumOutput, + GLenum scale, GLenum bias,GLboolean abDotProduct,GLboolean cdDotProduct, + GLboolean muxSum); +typedef void (APIENTRY *PFNGLFINALCOMBINERINPUTNV) (GLenum variable,GLenum input,GLenum mapping,GLenum componentUsage); + +typedef void (APIENTRY *PFNGLGETCOMBINERINPUTPARAMETERFVNV) (GLenum stage,GLenum portion,GLenum variable,GLenum pname,GLfloat *params); +typedef void (APIENTRY *PFNGLGETCOMBINERINPUTPARAMETERIVNV) (GLenum stage,GLenum portion,GLenum variable,GLenum pname,GLint *params); +typedef void (APIENTRY *PFNGLGETCOMBINEROUTPUTPARAMETERFVNV) (GLenum stage,GLenum portion,GLenum pname,GLfloat *params); +typedef void (APIENTRY *PFNGLGETCOMBINEROUTPUTPARAMETERIVNV) (GLenum stage,GLenum portion,GLenum pname,GLint *params); +typedef void (APIENTRY *PFNGLGETFINALCOMBINERINPUTPARAMETERFVNV) (GLenum variable,GLenum pname,GLfloat *params); +typedef void (APIENTRY *PFNGLGETFINALCOMBINERINPUTPARAMETERIVNV) (GLenum variable,GLenum pname,GLfloat *params); +/***********************************************************************************************************/ + +// Declare Register Combiners function pointers. +extern PFNGLCOMBINERPARAMETERFVNV qglCombinerParameterfvNV; +extern PFNGLCOMBINERPARAMETERIVNV qglCombinerParameterivNV; +extern PFNGLCOMBINERPARAMETERFNV qglCombinerParameterfNV; +extern PFNGLCOMBINERPARAMETERINV qglCombinerParameteriNV; +extern PFNGLCOMBINERINPUTNV qglCombinerInputNV; +extern PFNGLCOMBINEROUTPUTNV qglCombinerOutputNV; +extern PFNGLFINALCOMBINERINPUTNV qglFinalCombinerInputNV; +extern PFNGLGETCOMBINERINPUTPARAMETERFVNV qglGetCombinerInputParameterfvNV; +extern PFNGLGETCOMBINERINPUTPARAMETERIVNV qglGetCombinerInputParameterivNV; +extern PFNGLGETCOMBINEROUTPUTPARAMETERFVNV qglGetCombinerOutputParameterfvNV; +extern PFNGLGETCOMBINEROUTPUTPARAMETERIVNV qglGetCombinerOutputParameterivNV; +extern PFNGLGETFINALCOMBINERINPUTPARAMETERFVNV qglGetFinalCombinerInputParameterfvNV; +extern PFNGLGETFINALCOMBINERINPUTPARAMETERIVNV qglGetFinalCombinerInputParameterivNV; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Pixel Format extension definitions. - AReis +/***********************************************************************************************************/ +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 + +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +/***********************************************************************************************************/ + +// Declare Pixel Format function pointers. +extern PFNWGLGETPIXELFORMATATTRIBIVARBPROC qwglGetPixelFormatAttribivARB; +extern PFNWGLGETPIXELFORMATATTRIBFVARBPROC qwglGetPixelFormatAttribfvARB; +extern PFNWGLCHOOSEPIXELFORMATARBPROC qwglChoosePixelFormatARB; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Pixel Buffer extension definitions. - AReis +/***********************************************************************************************************/ +DECLARE_HANDLE(HPBUFFERARB); + +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_DRAW_TO_PBUFFER_ARB 0x202D +#define WGL_PBUFFER_WIDTH_ARB 0x2034 +#define WGL_PBUFFER_HEIGHT_ARB 0x2035 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 + +typedef HPBUFFERARB (WINAPI * PFNWGLCREATEPBUFFERARBPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer, HDC hDC); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFERARBPROC) (HPBUFFERARB hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFERARBPROC) (HPBUFFERARB hPbuffer, int iAttribute, int *piValue); +/***********************************************************************************************************/ + +// Declare Pixel Buffer function pointers. +extern PFNWGLCREATEPBUFFERARBPROC qwglCreatePbufferARB; +extern PFNWGLGETPBUFFERDCARBPROC qwglGetPbufferDCARB; +extern PFNWGLRELEASEPBUFFERDCARBPROC qwglReleasePbufferDCARB; +extern PFNWGLDESTROYPBUFFERARBPROC qwglDestroyPbufferARB; +extern PFNWGLQUERYPBUFFERARBPROC qwglQueryPbufferARB; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Render-Texture extension definitions. - AReis +/***********************************************************************************************************/ +#define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 +#define WGL_TEXTURE_FORMAT_ARB 0x2072 +#define WGL_TEXTURE_TARGET_ARB 0x2073 +#define WGL_TEXTURE_RGB_ARB 0x2075 +#define WGL_TEXTURE_RGBA_ARB 0x2076 +#define WGL_TEXTURE_2D_ARB 0x207A +#define WGL_FRONT_LEFT_ARB 0x2083 + +typedef BOOL (WINAPI * PFNWGLBINDTEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLRELEASETEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLSETPBUFFERATTRIBARBPROC) (HPBUFFERARB hPbuffer, const int * piAttribList); +/***********************************************************************************************************/ + +// Declare Render-Texture function pointers. +extern PFNWGLBINDTEXIMAGEARBPROC qwglBindTexImageARB; +extern PFNWGLRELEASETEXIMAGEARBPROC qwglReleaseTexImageARB; +extern PFNWGLSETPBUFFERATTRIBARBPROC qwglSetPbufferAttribARB; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Vertex and Fragment Program extension definitions. - AReis +/***********************************************************************************************************/ +#ifndef GL_ARB_fragment_program +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#endif + +// NOTE: These are obviously not all the vertex program flags (have you seen how many there actually are!). I'm +// only including the ones I use (to reduce code clutter), so if you need any of the other flags, just add them. +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 + +typedef void (APIENTRY * PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const GLvoid *string); +typedef void (APIENTRY * PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRY * PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRY * PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRY * PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRY * PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRY * PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRY * PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRY * PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRY * PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRY * PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRY * PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRY * PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRY * PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRY * PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRY * PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRY * PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, GLvoid *string); +typedef GLboolean (APIENTRY * PFNGLISPROGRAMARBPROC) (GLuint program); +/***********************************************************************************************************/ + +// Declare Vertex and Fragment Program function pointers. +extern PFNGLPROGRAMSTRINGARBPROC qglProgramStringARB; +extern PFNGLBINDPROGRAMARBPROC qglBindProgramARB; +extern PFNGLDELETEPROGRAMSARBPROC qglDeleteProgramsARB; +extern PFNGLGENPROGRAMSARBPROC qglGenProgramsARB; +extern PFNGLPROGRAMENVPARAMETER4DARBPROC qglProgramEnvParameter4dARB; +extern PFNGLPROGRAMENVPARAMETER4DVARBPROC qglProgramEnvParameter4dvARB; +extern PFNGLPROGRAMENVPARAMETER4FARBPROC qglProgramEnvParameter4fARB; +extern PFNGLPROGRAMENVPARAMETER4FVARBPROC qglProgramEnvParameter4fvARB; +extern PFNGLPROGRAMLOCALPARAMETER4DARBPROC qglProgramLocalParameter4dARB; +extern PFNGLPROGRAMLOCALPARAMETER4DVARBPROC qglProgramLocalParameter4dvARB; +extern PFNGLPROGRAMLOCALPARAMETER4FARBPROC qglProgramLocalParameter4fARB; +extern PFNGLPROGRAMLOCALPARAMETER4FVARBPROC qglProgramLocalParameter4fvARB; +extern PFNGLGETPROGRAMENVPARAMETERDVARBPROC qglGetProgramEnvParameterdvARB; +extern PFNGLGETPROGRAMENVPARAMETERFVARBPROC qglGetProgramEnvParameterfvARB; +extern PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC qglGetProgramLocalParameterdvARB; +extern PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC qglGetProgramLocalParameterfvARB; +extern PFNGLGETPROGRAMIVARBPROC qglGetProgramivARB; +extern PFNGLGETPROGRAMSTRINGARBPROC qglGetProgramStringARB; +extern PFNGLISPROGRAMARBPROC qglIsProgramARB; + + +/* +** extension constants +*/ + + +// S3TC compression constants +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 + + +// extensions will be function pointers on all platforms + +extern void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +extern void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +extern void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +extern void ( APIENTRY * qglLockArraysEXT) (GLint, GLint); +extern void ( APIENTRY * qglUnlockArraysEXT) (void); + +extern void ( APIENTRY * qglPointParameterfEXT)( GLenum, GLfloat); +extern void ( APIENTRY * qglPointParameterfvEXT)( GLenum, GLfloat *); + +//3d textures -rww +extern void ( APIENTRY * qglTexImage3DEXT) (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void ( APIENTRY * qglTexSubImage3DEXT) (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); + +//=========================================================================== + +// non-windows systems will just redefine qgl* to gl* +#if !defined( _WIN32 ) && !defined(MACOS_X) && !defined( __linux__ ) && !defined( __FreeBSD__ ) // rb010123 + +#include "qgl_linked.h" + +#elif defined(MACOS_X) +// This includes #ifdefs for optional logging and GL error checking after every GL call as well as #defines to prevent incorrect usage of the non-'qgl' versions of the GL API. +#include "macosx_qgl.h" + +#else + +// windows systems use a function pointer for each call so we can load minidrivers + +extern void ( APIENTRY * qglAccum )(GLenum op, GLfloat value); +extern void ( APIENTRY * qglAlphaFunc )(GLenum func, GLclampf ref); +extern GLboolean ( APIENTRY * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +extern void ( APIENTRY * qglArrayElement )(GLint i); +extern void ( APIENTRY * qglBegin )(GLenum mode); +extern void ( APIENTRY * qglBindTexture )(GLenum target, GLuint texture); +extern void ( APIENTRY * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +extern void ( APIENTRY * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +extern void ( APIENTRY * qglCallList )(GLuint list); +extern void ( APIENTRY * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +extern void ( APIENTRY * qglClear )(GLbitfield mask); +extern void ( APIENTRY * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( APIENTRY * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +extern void ( APIENTRY * qglClearDepth )(GLclampd depth); +extern void ( APIENTRY * qglClearIndex )(GLfloat c); +extern void ( APIENTRY * qglClearStencil )(GLint s); +extern void ( APIENTRY * qglClipPlane )(GLenum plane, const GLdouble *equation); +extern void ( APIENTRY * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +extern void ( APIENTRY * qglColor3bv )(const GLbyte *v); +extern void ( APIENTRY * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +extern void ( APIENTRY * qglColor3dv )(const GLdouble *v); +extern void ( APIENTRY * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +extern void ( APIENTRY * qglColor3fv )(const GLfloat *v); +extern void ( APIENTRY * qglColor3i )(GLint red, GLint green, GLint blue); +extern void ( APIENTRY * qglColor3iv )(const GLint *v); +extern void ( APIENTRY * qglColor3s )(GLshort red, GLshort green, GLshort blue); +extern void ( APIENTRY * qglColor3sv )(const GLshort *v); +extern void ( APIENTRY * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +extern void ( APIENTRY * qglColor3ubv )(const GLubyte *v); +extern void ( APIENTRY * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +extern void ( APIENTRY * qglColor3uiv )(const GLuint *v); +extern void ( APIENTRY * qglColor3us )(GLushort red, GLushort green, GLushort blue); +extern void ( APIENTRY * qglColor3usv )(const GLushort *v); +extern void ( APIENTRY * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +extern void ( APIENTRY * qglColor4bv )(const GLbyte *v); +extern void ( APIENTRY * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +extern void ( APIENTRY * qglColor4dv )(const GLdouble *v); +extern void ( APIENTRY * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( APIENTRY * qglColor4fv )(const GLfloat *v); +extern void ( APIENTRY * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +extern void ( APIENTRY * qglColor4iv )(const GLint *v); +extern void ( APIENTRY * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +extern void ( APIENTRY * qglColor4sv )(const GLshort *v); +extern void ( APIENTRY * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern void ( APIENTRY * qglColor4ubv )(const GLubyte *v); +extern void ( APIENTRY * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +extern void ( APIENTRY * qglColor4uiv )(const GLuint *v); +extern void ( APIENTRY * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +extern void ( APIENTRY * qglColor4usv )(const GLushort *v); +extern void ( APIENTRY * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +extern void ( APIENTRY * qglColorMaterial )(GLenum face, GLenum mode); +extern void ( APIENTRY * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +extern void ( APIENTRY * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +extern void ( APIENTRY * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +extern void ( APIENTRY * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern void ( APIENTRY * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( APIENTRY * qglCullFace )(GLenum mode); +extern void ( APIENTRY * qglDeleteLists )(GLuint list, GLsizei range); +extern void ( APIENTRY * qglDeleteTextures )(GLsizei n, const GLuint *textures); +extern void ( APIENTRY * qglDepthFunc )(GLenum func); +extern void ( APIENTRY * qglDepthMask )(GLboolean flag); +extern void ( APIENTRY * qglDepthRange )(GLclampd zNear, GLclampd zFar); +extern void ( APIENTRY * qglDisable )(GLenum cap); +extern void ( APIENTRY * qglDisableClientState )(GLenum array); +extern void ( APIENTRY * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +extern void ( APIENTRY * qglDrawBuffer )(GLenum mode); +extern void ( APIENTRY * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +extern void ( APIENTRY * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglEdgeFlag )(GLboolean flag); +extern void ( APIENTRY * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglEdgeFlagv )(const GLboolean *flag); +extern void ( APIENTRY * qglEnable )(GLenum cap); +extern void ( APIENTRY * qglEnableClientState )(GLenum array); +extern void ( APIENTRY * qglEnd )(void); +extern void ( APIENTRY * qglEndList )(void); +extern void ( APIENTRY * qglEvalCoord1d )(GLdouble u); +extern void ( APIENTRY * qglEvalCoord1dv )(const GLdouble *u); +extern void ( APIENTRY * qglEvalCoord1f )(GLfloat u); +extern void ( APIENTRY * qglEvalCoord1fv )(const GLfloat *u); +extern void ( APIENTRY * qglEvalCoord2d )(GLdouble u, GLdouble v); +extern void ( APIENTRY * qglEvalCoord2dv )(const GLdouble *u); +extern void ( APIENTRY * qglEvalCoord2f )(GLfloat u, GLfloat v); +extern void ( APIENTRY * qglEvalCoord2fv )(const GLfloat *u); +extern void ( APIENTRY * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +extern void ( APIENTRY * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +extern void ( APIENTRY * qglEvalPoint1 )(GLint i); +extern void ( APIENTRY * qglEvalPoint2 )(GLint i, GLint j); +extern void ( APIENTRY * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +extern void ( APIENTRY * qglFinish )(void); +extern void ( APIENTRY * qglFlush )(void); +extern void ( APIENTRY * qglFogf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglFogfv )(GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglFogi )(GLenum pname, GLint param); +extern void ( APIENTRY * qglFogiv )(GLenum pname, const GLint *params); +extern void ( APIENTRY * qglFrontFace )(GLenum mode); +extern void ( APIENTRY * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern GLuint ( APIENTRY * qglGenLists )(GLsizei range); +extern void ( APIENTRY * qglGenTextures )(GLsizei n, GLuint *textures); +extern void ( APIENTRY * qglGetBooleanv )(GLenum pname, GLboolean *params); +extern void ( APIENTRY * qglGetClipPlane )(GLenum plane, GLdouble *equation); +extern void ( APIENTRY * qglGetDoublev )(GLenum pname, GLdouble *params); +extern GLenum ( APIENTRY * qglGetError )(void); +extern void ( APIENTRY * qglGetFloatv )(GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetIntegerv )(GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +extern void ( APIENTRY * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +extern void ( APIENTRY * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +extern void ( APIENTRY * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetPixelMapfv )(GLenum map, GLfloat *values); +extern void ( APIENTRY * qglGetPixelMapuiv )(GLenum map, GLuint *values); +extern void ( APIENTRY * qglGetPixelMapusv )(GLenum map, GLushort *values); +extern void ( APIENTRY * qglGetPointerv )(GLenum pname, GLvoid* *params); +extern void ( APIENTRY * qglGetPolygonStipple )(GLubyte *mask); +extern const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +extern void ( APIENTRY * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +extern void ( APIENTRY * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +extern void ( APIENTRY * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +extern void ( APIENTRY * qglHint )(GLenum target, GLenum mode); +extern void ( APIENTRY * qglIndexMask )(GLuint mask); +extern void ( APIENTRY * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglIndexd )(GLdouble c); +extern void ( APIENTRY * qglIndexdv )(const GLdouble *c); +extern void ( APIENTRY * qglIndexf )(GLfloat c); +extern void ( APIENTRY * qglIndexfv )(const GLfloat *c); +extern void ( APIENTRY * qglIndexi )(GLint c); +extern void ( APIENTRY * qglIndexiv )(const GLint *c); +extern void ( APIENTRY * qglIndexs )(GLshort c); +extern void ( APIENTRY * qglIndexsv )(const GLshort *c); +extern void ( APIENTRY * qglIndexub )(GLubyte c); +extern void ( APIENTRY * qglIndexubv )(const GLubyte *c); +extern void ( APIENTRY * qglInitNames )(void); +extern void ( APIENTRY * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +extern GLboolean ( APIENTRY * qglIsEnabled )(GLenum cap); +extern GLboolean ( APIENTRY * qglIsList )(GLuint ilist); +extern GLboolean ( APIENTRY * qglIsTexture )(GLuint texture); +extern void ( APIENTRY * qglLightModelf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglLightModelfv )(GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglLightModeli )(GLenum pname, GLint param); +extern void ( APIENTRY * qglLightModeliv )(GLenum pname, const GLint *params); +extern void ( APIENTRY * qglLightf )(GLenum light, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglLighti )(GLenum light, GLenum pname, GLint param); +extern void ( APIENTRY * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglLineStipple )(GLint factor, GLushort pattern); +extern void ( APIENTRY * qglLineWidth )(GLfloat width); +extern void ( APIENTRY * qglListBase )(GLuint base); +extern void ( APIENTRY * qglLoadIdentity )(void); +extern void ( APIENTRY * qglLoadMatrixd )(const GLdouble *m); +extern void ( APIENTRY * qglLoadMatrixf )(const GLfloat *m); +extern void ( APIENTRY * qglLoadName )(GLuint name); +extern void ( APIENTRY * qglLogicOp )(GLenum opcode); +extern void ( APIENTRY * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +extern void ( APIENTRY * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +extern void ( APIENTRY * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +extern void ( APIENTRY * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +extern void ( APIENTRY * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +extern void ( APIENTRY * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +extern void ( APIENTRY * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +extern void ( APIENTRY * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +extern void ( APIENTRY * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglMateriali )(GLenum face, GLenum pname, GLint param); +extern void ( APIENTRY * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglMatrixMode )(GLenum mode); +extern void ( APIENTRY * qglMultMatrixd )(const GLdouble *m); +extern void ( APIENTRY * qglMultMatrixf )(const GLfloat *m); +extern void ( APIENTRY * qglNewList )(GLuint list, GLenum mode); +extern void ( APIENTRY * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +extern void ( APIENTRY * qglNormal3bv )(const GLbyte *v); +extern void ( APIENTRY * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +extern void ( APIENTRY * qglNormal3dv )(const GLdouble *v); +extern void ( APIENTRY * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +extern void ( APIENTRY * qglNormal3fv )(const GLfloat *v); +extern void ( APIENTRY * qglNormal3i )(GLint nx, GLint ny, GLint nz); +extern void ( APIENTRY * qglNormal3iv )(const GLint *v); +extern void ( APIENTRY * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +extern void ( APIENTRY * qglNormal3sv )(const GLshort *v); +extern void ( APIENTRY * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern void ( APIENTRY * qglPassThrough )(GLfloat token); +extern void ( APIENTRY * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +extern void ( APIENTRY * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +extern void ( APIENTRY * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +extern void ( APIENTRY * qglPixelStoref )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglPixelStorei )(GLenum pname, GLint param); +extern void ( APIENTRY * qglPixelTransferf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglPixelTransferi )(GLenum pname, GLint param); +extern void ( APIENTRY * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +extern void ( APIENTRY * qglPointSize )(GLfloat size); +extern void ( APIENTRY * qglPolygonMode )(GLenum face, GLenum mode); +extern void ( APIENTRY * qglPolygonOffset )(GLfloat factor, GLfloat units); +extern void ( APIENTRY * qglPolygonStipple )(const GLubyte *mask); +extern void ( APIENTRY * qglPopAttrib )(void); +extern void ( APIENTRY * qglPopClientAttrib )(void); +extern void ( APIENTRY * qglPopMatrix )(void); +extern void ( APIENTRY * qglPopName )(void); +extern void ( APIENTRY * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +extern void ( APIENTRY * qglPushAttrib )(GLbitfield mask); +extern void ( APIENTRY * qglPushClientAttrib )(GLbitfield mask); +extern void ( APIENTRY * qglPushMatrix )(void); +extern void ( APIENTRY * qglPushName )(GLuint name); +extern void ( APIENTRY * qglRasterPos2d )(GLdouble x, GLdouble y); +extern void ( APIENTRY * qglRasterPos2dv )(const GLdouble *v); +extern void ( APIENTRY * qglRasterPos2f )(GLfloat x, GLfloat y); +extern void ( APIENTRY * qglRasterPos2fv )(const GLfloat *v); +extern void ( APIENTRY * qglRasterPos2i )(GLint x, GLint y); +extern void ( APIENTRY * qglRasterPos2iv )(const GLint *v); +extern void ( APIENTRY * qglRasterPos2s )(GLshort x, GLshort y); +extern void ( APIENTRY * qglRasterPos2sv )(const GLshort *v); +extern void ( APIENTRY * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglRasterPos3dv )(const GLdouble *v); +extern void ( APIENTRY * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglRasterPos3fv )(const GLfloat *v); +extern void ( APIENTRY * qglRasterPos3i )(GLint x, GLint y, GLint z); +extern void ( APIENTRY * qglRasterPos3iv )(const GLint *v); +extern void ( APIENTRY * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +extern void ( APIENTRY * qglRasterPos3sv )(const GLshort *v); +extern void ( APIENTRY * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( APIENTRY * qglRasterPos4dv )(const GLdouble *v); +extern void ( APIENTRY * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( APIENTRY * qglRasterPos4fv )(const GLfloat *v); +extern void ( APIENTRY * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( APIENTRY * qglRasterPos4iv )(const GLint *v); +extern void ( APIENTRY * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( APIENTRY * qglRasterPos4sv )(const GLshort *v); +extern void ( APIENTRY * qglReadBuffer )(GLenum mode); +extern void ( APIENTRY * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +extern void ( APIENTRY * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +extern void ( APIENTRY * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +extern void ( APIENTRY * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +extern void ( APIENTRY * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +extern void ( APIENTRY * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +extern void ( APIENTRY * qglRectiv )(const GLint *v1, const GLint *v2); +extern void ( APIENTRY * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +extern void ( APIENTRY * qglRectsv )(const GLshort *v1, const GLshort *v2); +extern GLint ( APIENTRY * qglRenderMode )(GLenum mode); +extern void ( APIENTRY * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( APIENTRY * qglSelectBuffer )(GLsizei size, GLuint *buffer); +extern void ( APIENTRY * qglShadeModel )(GLenum mode); +extern void ( APIENTRY * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +extern void ( APIENTRY * qglStencilMask )(GLuint mask); +extern void ( APIENTRY * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +extern void ( APIENTRY * qglTexCoord1d )(GLdouble s); +extern void ( APIENTRY * qglTexCoord1dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord1f )(GLfloat s); +extern void ( APIENTRY * qglTexCoord1fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord1i )(GLint s); +extern void ( APIENTRY * qglTexCoord1iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord1s )(GLshort s); +extern void ( APIENTRY * qglTexCoord1sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoord2d )(GLdouble s, GLdouble t); +extern void ( APIENTRY * qglTexCoord2dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord2f )(GLfloat s, GLfloat t); +extern void ( APIENTRY * qglTexCoord2fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord2i )(GLint s, GLint t); +extern void ( APIENTRY * qglTexCoord2iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord2s )(GLshort s, GLshort t); +extern void ( APIENTRY * qglTexCoord2sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +extern void ( APIENTRY * qglTexCoord3dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +extern void ( APIENTRY * qglTexCoord3fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord3i )(GLint s, GLint t, GLint r); +extern void ( APIENTRY * qglTexCoord3iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +extern void ( APIENTRY * qglTexCoord3sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +extern void ( APIENTRY * qglTexCoord4dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void ( APIENTRY * qglTexCoord4fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +extern void ( APIENTRY * qglTexCoord4iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +extern void ( APIENTRY * qglTexCoord4sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +extern void ( APIENTRY * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +extern void ( APIENTRY * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglVertex2d )(GLdouble x, GLdouble y); +extern void ( APIENTRY * qglVertex2dv )(const GLdouble *v); +extern void ( APIENTRY * qglVertex2f )(GLfloat x, GLfloat y); +extern void ( APIENTRY * qglVertex2fv )(const GLfloat *v); +extern void ( APIENTRY * qglVertex2i )(GLint x, GLint y); +extern void ( APIENTRY * qglVertex2iv )(const GLint *v); +extern void ( APIENTRY * qglVertex2s )(GLshort x, GLshort y); +extern void ( APIENTRY * qglVertex2sv )(const GLshort *v); +extern void ( APIENTRY * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglVertex3dv )(const GLdouble *v); +extern void ( APIENTRY * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglVertex3fv )(const GLfloat *v); +extern void ( APIENTRY * qglVertex3i )(GLint x, GLint y, GLint z); +extern void ( APIENTRY * qglVertex3iv )(const GLint *v); +extern void ( APIENTRY * qglVertex3s )(GLshort x, GLshort y, GLshort z); +extern void ( APIENTRY * qglVertex3sv )(const GLshort *v); +extern void ( APIENTRY * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( APIENTRY * qglVertex4dv )(const GLdouble *v); +extern void ( APIENTRY * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( APIENTRY * qglVertex4fv )(const GLfloat *v); +extern void ( APIENTRY * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( APIENTRY * qglVertex4iv )(const GLint *v); +extern void ( APIENTRY * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( APIENTRY * qglVertex4sv )(const GLshort *v); +extern void ( APIENTRY * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +#if defined( _WIN32 ) + +extern BOOL ( WINAPI * qwglCopyContext)(HGLRC, HGLRC, UINT); +extern HGLRC ( WINAPI * qwglCreateContext)(HDC); +extern HGLRC ( WINAPI * qwglCreateLayerContext)(HDC, int); +extern BOOL ( WINAPI * qwglDeleteContext)(HGLRC); +extern HGLRC ( WINAPI * qwglGetCurrentContext)(VOID); +extern HDC ( WINAPI * qwglGetCurrentDC)(VOID); +extern PROC ( WINAPI * qwglGetProcAddress)(LPCSTR); +extern BOOL ( WINAPI * qwglMakeCurrent)(HDC, HGLRC); +extern BOOL ( WINAPI * qwglShareLists)(HGLRC, HGLRC); +extern BOOL ( WINAPI * qwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); + +extern BOOL ( WINAPI * qwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, + FLOAT, int, LPGLYPHMETRICSFLOAT); + +extern BOOL ( WINAPI * qwglDescribeLayerPlane)(HDC, int, int, UINT, + LPLAYERPLANEDESCRIPTOR); +extern int ( WINAPI * qwglSetLayerPaletteEntries)(HDC, int, int, int, + CONST COLORREF *); +extern int ( WINAPI * qwglGetLayerPaletteEntries)(HDC, int, int, int, + COLORREF *); +extern BOOL ( WINAPI * qwglRealizeLayerPalette)(HDC, int, BOOL); +extern BOOL ( WINAPI * qwglSwapLayerBuffers)(HDC, UINT); + +extern BOOL ( WINAPI * qwglSwapIntervalEXT)( int interval ); + +#endif // _WIN32 + +#if ( (defined __linux__ ) || (defined __FreeBSD__ ) ) // rb010123 + +//FX Mesa Functions +// bk001129 - from cvs1.17 (mkv) +#if defined (__FX__) +extern fxMesaContext (*qfxMesaCreateContext)(GLuint win, GrScreenResolution_t, GrScreenRefresh_t, const GLint attribList[]); +extern fxMesaContext (*qfxMesaCreateBestContext)(GLuint win, GLint width, GLint height, const GLint attribList[]); +extern void (*qfxMesaDestroyContext)(fxMesaContext ctx); +extern void (*qfxMesaMakeCurrent)(fxMesaContext ctx); +extern fxMesaContext (*qfxMesaGetCurrentContext)(void); +extern void (*qfxMesaSwapBuffers)(void); +#endif + +//GLX Functions +extern XVisualInfo * (*qglXChooseVisual)( Display *dpy, int screen, int *attribList ); +extern GLXContext (*qglXCreateContext)( Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct ); +extern void (*qglXDestroyContext)( Display *dpy, GLXContext ctx ); +extern Bool (*qglXMakeCurrent)( Display *dpy, GLXDrawable drawable, GLXContext ctx); +extern void (*qglXCopyContext)( Display *dpy, GLXContext src, GLXContext dst, GLuint mask ); +extern void (*qglXSwapBuffers)( Display *dpy, GLXDrawable drawable ); + +#endif // __linux__ || __FreeBSD__ // rb010123 + +#endif // _WIN32 && __linux__ + +#endif \ No newline at end of file diff --git a/codemp/renderer/qgl_console.h b/codemp/renderer/qgl_console.h new file mode 100644 index 0000000..df74f89 --- /dev/null +++ b/codemp/renderer/qgl_console.h @@ -0,0 +1,1205 @@ +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + +#ifdef _WINDOWS +#include +#include +#endif + +#ifdef _XBOX + +#include +#endif + +#ifdef _GAMECUBE +#include +#include +#endif + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef int GLsizei; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned int GLuint; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef void GLvoid; + +#undef APIENTRY +#define APIENTRY + +#define GL_ACCUM 0x0100 +#define GL_LOAD 0x0101 +#define GL_RETURN 0x0102 +#define GL_MULT 0x0103 +#define GL_ADD 0x0104 + +/* AlphaFunction */ +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 + +/* AttribMask */ +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000fffff + +/* BeginMode */ +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 + +/* BlendingFactorDest */ +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 + +/* BlendingFactorSrc */ +/* GL_ZERO */ +/* GL_ONE */ +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 + +/* Boolean */ +#define GL_TRUE 1 +#define GL_FALSE 0 + +/* ClipPlaneName */ +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 + +/* DataType */ +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 +#define GL_DOUBLE 0x140A + +/* DrawBufferMode */ +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C + +/* ErrorCode */ +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +/* FeedBackMode */ +#define GL_2D 0x0600 +#define GL_3D 0x0601 +#define GL_3D_COLOR 0x0602 +#define GL_3D_COLOR_TEXTURE 0x0603 +#define GL_4D_COLOR_TEXTURE 0x0604 + +/* FeedBackToken */ +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_LINE_RESET_TOKEN 0x0707 + +/* FogMode */ +/* GL_LINEAR */ +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 + +/* FrontFaceDirection */ +#define GL_CW 0x0900 +#define GL_CCW 0x0901 + +/* GetMapTarget */ +#define GL_COEFF 0x0A00 +#define GL_ORDER 0x0A01 +#define GL_DOMAIN 0x0A02 + +/* GetTarget */ +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_POINT_SMOOTH 0x0B10 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_RANGE 0x0B12 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_RANGE 0x0B22 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LIST_MODE 0x0B30 +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_EDGE_FLAG 0x0B43 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_LIGHTING 0x0B50 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_SHADE_MODEL 0x0B54 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_NORMALIZE 0x0BA1 +#define GL_VIEWPORT 0x0BA2 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_FUNC 0x0BC1 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_DITHER 0x0BD0 +#define GL_BLEND_DST 0x0BE0 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND 0x0BE2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_INDEX_MODE 0x0C30 +#define GL_RGBA_MODE 0x0C31 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_RENDER_MODE 0x0C40 +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#define GL_PIXEL_MAP_I_TO_R 0x0C72 +#define GL_PIXEL_MAP_I_TO_G 0x0C73 +#define GL_PIXEL_MAP_I_TO_B 0x0C74 +#define GL_PIXEL_MAP_I_TO_A 0x0C75 +#define GL_PIXEL_MAP_R_TO_R 0x0C76 +#define GL_PIXEL_MAP_G_TO_G 0x0C77 +#define GL_PIXEL_MAP_B_TO_B 0x0C78 +#define GL_PIXEL_MAP_A_TO_A 0x0C79 +#define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 +#define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 +#define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 +#define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 +#define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 +#define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 +#define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 +#define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAP_COLOR 0x0D10 +#define GL_MAP_STENCIL 0x0D11 +#define GL_INDEX_SHIFT 0x0D12 +#define GL_INDEX_OFFSET 0x0D13 +#define GL_RED_SCALE 0x0D14 +#define GL_RED_BIAS 0x0D15 +#define GL_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 +#define GL_GREEN_SCALE 0x0D18 +#define GL_GREEN_BIAS 0x0D19 +#define GL_BLUE_SCALE 0x0D1A +#define GL_BLUE_BIAS 0x0D1B +#define GL_ALPHA_SCALE 0x0D1C +#define GL_ALPHA_BIAS 0x0D1D +#define GL_DEPTH_SCALE 0x0D1E +#define GL_DEPTH_BIAS 0x0D1F +#define GL_MAX_EVAL_ORDER 0x0D30 +#define GL_MAX_LIGHTS 0x0D31 +#define GL_MAX_CLIP_PLANES 0x0D32 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_PIXEL_MAP_TABLE 0x0D34 +#define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 +#define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 +#define GL_MAX_NAME_STACK_DEPTH 0x0D37 +#define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 +#define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_INDEX_BITS 0x0D51 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_NAME_STACK_DEPTH 0x0D70 +#define GL_AUTO_NORMAL 0x0D80 +#define GL_MAP1_COLOR_4 0x0D90 +#define GL_MAP1_INDEX 0x0D91 +#define GL_MAP1_NORMAL 0x0D92 +#define GL_MAP1_TEXTURE_COORD_1 0x0D93 +#define GL_MAP1_TEXTURE_COORD_2 0x0D94 +#define GL_MAP1_TEXTURE_COORD_3 0x0D95 +#define GL_MAP1_TEXTURE_COORD_4 0x0D96 +#define GL_MAP1_VERTEX_3 0x0D97 +#define GL_MAP1_VERTEX_4 0x0D98 +#define GL_MAP2_COLOR_4 0x0DB0 +#define GL_MAP2_INDEX 0x0DB1 +#define GL_MAP2_NORMAL 0x0DB2 +#define GL_MAP2_TEXTURE_COORD_1 0x0DB3 +#define GL_MAP2_TEXTURE_COORD_2 0x0DB4 +#define GL_MAP2_TEXTURE_COORD_3 0x0DB5 +#define GL_MAP2_TEXTURE_COORD_4 0x0DB6 +#define GL_MAP2_VERTEX_3 0x0DB7 +#define GL_MAP2_VERTEX_4 0x0DB8 +#define GL_MAP1_GRID_DOMAIN 0x0DD0 +#define GL_MAP1_GRID_SEGMENTS 0x0DD1 +#define GL_MAP2_GRID_DOMAIN 0x0DD2 +#define GL_MAP2_GRID_SEGMENTS 0x0DD3 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 + +/* GetTextureParameter */ +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_BORDER 0x1005 + +/* HintMode */ +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +/* LightName */ +#define GL_LIGHT0 0x4000 +#define GL_LIGHT1 0x4001 +#define GL_LIGHT2 0x4002 +#define GL_LIGHT3 0x4003 +#define GL_LIGHT4 0x4004 +#define GL_LIGHT5 0x4005 +#define GL_LIGHT6 0x4006 +#define GL_LIGHT7 0x4007 + +/* LightParameter */ +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#define GL_SPOT_EXPONENT 0x1205 +#define GL_SPOT_CUTOFF 0x1206 +#define GL_CONSTANT_ATTENUATION 0x1207 +#define GL_LINEAR_ATTENUATION 0x1208 +#define GL_QUADRATIC_ATTENUATION 0x1209 + +/* ListMode */ +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 + +/* LogicOp */ +#define GL_CLEAR 0x1500 +#define GL_AND 0x1501 +#define GL_AND_REVERSE 0x1502 +#define GL_COPY 0x1503 +#define GL_AND_INVERTED 0x1504 +#define GL_NOOP 0x1505 +#define GL_XOR 0x1506 +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_EQUIV 0x1509 +#define GL_INVERT 0x150A +#define GL_OR_REVERSE 0x150B +#define GL_COPY_INVERTED 0x150C +#define GL_OR_INVERTED 0x150D +#define GL_NAND 0x150E +#define GL_SET 0x150F + +/* MaterialParameter */ +#define GL_EMISSION 0x1600 +#define GL_SHININESS 0x1601 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 + +/* MatrixMode */ +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE0 0x1702 +#define GL_TEXTURE1 0x1703 +#define GL_TEXTURE2 0x1704 +#define GL_TEXTURE3 0x1705 + +/* PixelCopyType */ +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 + +/* PixelFormat */ +#define GL_COLOR_INDEX 0x1900 +#define GL_STENCIL_INDEX 0x1901 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A + +/* PixelType */ +#define GL_BITMAP 0x1A00 + +/* PolygonMode */ +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 + +/* RenderingMode */ +#define GL_RENDER 0x1C00 +#define GL_FEEDBACK 0x1C01 +#define GL_SELECT 0x1C02 + +/* ShadingModel */ +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 + +/* StencilOp */ +/* GL_ZERO */ +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +/* GL_INVERT */ + +/* StringName */ +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +/* TextureCoordName */ +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 + +/* TextureEnvMode */ +#define GL_MODULATE 0x2100 +#define GL_DECAL 0x2101 + +/* TextureEnvParameter */ +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 + +/* TextureEnvTarget */ +#define GL_TEXTURE_ENV 0x2300 + +/* TextureGenMode */ +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 + +/* TextureGenParameter */ +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 + +/* TextureMagFilter */ +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 + +/* TextureMinFilter */ +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 + +/* TextureParameterName */ +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 + +// PORT: Anisotropy stuff +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + +//PORT - TPL stuff +#define GL_TPL4_EXT 0x9991 +#define GL_TPL8_EXT 0x9992 +#define GL_TPL16_EXT 0x9993 +#define GL_TPL32_EXT 0x9994 + +// PORT: DDS Stuff +#define GL_DDS_RGBA_EXT 0x9998 +#define GL_RGB_SWIZZLE_EXT 0x9999 + +/* TextureWrapMode */ +#define GL_CLAMP 0x2900 +#define GL_REPEAT 0x2901 + +/* ClientAttribMask */ +#define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 +#define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 +#define GL_CLIENT_ALL_ATTRIB_BITS 0xffffffff + +/* polygon_offset */ +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + +/* texture */ +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 + +/* texture_object */ +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 + +/* vertex_array */ +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 +#define GL_VERTEX_ARRAY_SIZE 0x807A +#define GL_VERTEX_ARRAY_TYPE 0x807B +#define GL_VERTEX_ARRAY_STRIDE 0x807C +#define GL_NORMAL_ARRAY_TYPE 0x807E +#define GL_NORMAL_ARRAY_STRIDE 0x807F +#define GL_COLOR_ARRAY_SIZE 0x8081 +#define GL_COLOR_ARRAY_TYPE 0x8082 +#define GL_COLOR_ARRAY_STRIDE 0x8083 +#define GL_INDEX_ARRAY_TYPE 0x8085 +#define GL_INDEX_ARRAY_STRIDE 0x8086 +#define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GL_VERTEX_ARRAY_POINTER 0x808E +#define GL_NORMAL_ARRAY_POINTER 0x808F +#define GL_COLOR_ARRAY_POINTER 0x8090 +#define GL_INDEX_ARRAY_POINTER 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GL_V2F 0x2A20 +#define GL_V3F 0x2A21 +#define GL_C4UB_V2F 0x2A22 +#define GL_C4UB_V3F 0x2A23 +#define GL_C3F_V3F 0x2A24 +#define GL_N3F_V3F 0x2A25 +#define GL_C4F_N3F_V3F 0x2A26 +#define GL_T2F_V3F 0x2A27 +#define GL_T4F_V4F 0x2A28 +#define GL_T2F_C4UB_V3F 0x2A29 +#define GL_T2F_C3F_V3F 0x2A2A +#define GL_T2F_N3F_V3F 0x2A2B +#define GL_T2F_C4F_N3F_V3F 0x2A2C +#define GL_T4F_C4F_N3F_V4F 0x2A2D + +/* Extensions */ +#define GL_EXT_vertex_array 1 +#define GL_EXT_bgra 1 +#define GL_EXT_paletted_texture 1 + +/* EXT_vertex_array */ +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#define GL_DOUBLE_EXT GL_DOUBLE + +/* EXT_bgra */ +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 + +/* EXT_paletted_texture */ + +/* These must match the GL_COLOR_TABLE_*_SGI enumerants */ +#define GL_COLOR_TABLE_FORMAT_EXT 0x80D8 +#define GL_COLOR_TABLE_WIDTH_EXT 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_EXT 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_EXT 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_EXT 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_EXT 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_EXT 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_EXT 0x80DF + +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 + +// VVFIXME New Constants from Jedi +#define GL_VSYNC 0x813F +#define GL_DDS_RGB16_EXT 0x9997 +#define GL_DDS_RGBA32_EXT 0x9998 +#define GL_RGB_SWIZZLE_EXT 0x9999 + +// VVFIXME - New constants for linear format textures. +// These numbers are just made up. This is awful. +#define GL_LIN_RGBA8 0x8E01 +#define GL_LIN_RGBA 0x8E02 +#define GL_LIN_RGB8 0x8E03 +#define GL_LIN_RGB 0x8E04 + +//=========================================================================== + +/* +** multitexture extension definitions +*/ +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_ACTIVE_TEXTURES_ARB 0x84E2 + +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 + +typedef void ( * PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void ( * PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void ( * PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void ( * PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void ( * PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void ( * PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void ( * PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void ( * PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void ( * PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void ( * PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void ( * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void ( * PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void ( * PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void ( * PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void ( * PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void ( * PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void ( * PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void ( * PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void ( * PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void ( * PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void ( * PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void ( * PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void ( * PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void ( * PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void ( * PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void ( * PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void ( * PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void ( * PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void ( * PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void ( * PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void ( * PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void ( * PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +typedef void ( * PFNGLACTIVETEXTUREARBPROC) (GLenum target); +typedef void ( * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum target); + +/* +** extension constants +*/ +extern void ( * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +extern void ( * qglActiveTextureARB )( GLenum texture ); +extern void ( * qglClientActiveTextureARB )( GLenum texture ); + +extern void ( * qglLockArraysEXT) (GLint, GLint); +extern void ( * qglUnlockArraysEXT) (void); + +//----(SA) from Raven +extern void ( * qglPointParameterfEXT)( GLenum, GLfloat); +extern void ( * qglPointParameterfvEXT)( GLenum, GLfloat *); +//----(SA) end + + + +// S3TC compression constants +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +// More, grabbed from wolf code PORT +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + +// And more, also from old wolf code: +// GR - update enumerants +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 + +extern void ( * qglPNTrianglesiATI)(GLenum pname, GLint param); +extern void ( * qglPNTrianglesfATI)(GLenum pname, GLfloat param); + +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C + +//=========================================================================== + + +extern void ( * qglAccum )(GLenum op, GLfloat value); +extern void ( * qglAlphaFunc )(GLenum func, GLclampf ref); +extern GLboolean ( * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +extern void ( * qglArrayElement )(GLint i); +extern void ( * qglBegin )(GLenum mode); +extern void ( * qglBeginEXT )(GLenum mode, GLint verts, GLint colors, GLint normals, GLint tex0, GLint tex1);//, GLint tex2, GLint tex3); +extern GLboolean ( * qglBeginFrame )(void); +extern void ( * qglBeginShadow )(void); +extern void ( * qglBindTexture )(GLenum target, GLuint texture); +extern void ( * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +extern void ( * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +extern void ( * qglCallList )(GLuint list); +extern void ( * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +extern void ( * qglClear )(GLbitfield mask); +extern void ( * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +extern void ( * qglClearDepth )(GLclampd depth); +extern void ( * qglClearIndex )(GLfloat c); +extern void ( * qglClearStencil )(GLint s); +extern void ( * qglClipPlane )(GLenum plane, const GLdouble *equation); +extern void ( * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +extern void ( * qglColor3bv )(const GLbyte *v); +extern void ( * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +extern void ( * qglColor3dv )(const GLdouble *v); +extern void ( * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +extern void ( * qglColor3fv )(const GLfloat *v); +extern void ( * qglColor3i )(GLint red, GLint green, GLint blue); +extern void ( * qglColor3iv )(const GLint *v); +extern void ( * qglColor3s )(GLshort red, GLshort green, GLshort blue); +extern void ( * qglColor3sv )(const GLshort *v); +extern void ( * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +extern void ( * qglColor3ubv )(const GLubyte *v); +extern void ( * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +extern void ( * qglColor3uiv )(const GLuint *v); +extern void ( * qglColor3us )(GLushort red, GLushort green, GLushort blue); +extern void ( * qglColor3usv )(const GLushort *v); +extern void ( * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +extern void ( * qglColor4bv )(const GLbyte *v); +extern void ( * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +extern void ( * qglColor4dv )(const GLdouble *v); +extern void ( * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( * qglColor4fv )(const GLfloat *v); +extern void ( * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +extern void ( * qglColor4iv )(const GLint *v); +extern void ( * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +extern void ( * qglColor4sv )(const GLshort *v); +extern void ( * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern void ( * qglColor4ubv )(const GLubyte *v); +extern void ( * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +extern void ( * qglColor4uiv )(const GLuint *v); +extern void ( * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +extern void ( * qglColor4usv )(const GLushort *v); +extern void ( * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +extern void ( * qglColorMaterial )(GLenum face, GLenum mode); +extern void ( * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +extern void ( * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +extern void ( * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +extern void ( * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern void ( * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( * qglCullFace )(GLenum mode); +extern void ( * qglDeleteLists )(GLuint list, GLsizei range); +extern void ( * qglDeleteTextures )(GLsizei n, const GLuint *textures); +extern void ( * qglDepthFunc )(GLenum func); +extern void ( * qglDepthMask )(GLboolean flag); +extern void ( * qglDepthRange )(GLclampd zNear, GLclampd zFar); +extern void ( * qglDisable )(GLenum cap); +extern void ( * qglDisableClientState )(GLenum array); +extern void ( * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +extern void ( * qglDrawBuffer )(GLenum mode); +extern void ( * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +extern void ( * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglEdgeFlag )(GLboolean flag); +extern void ( * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +extern void ( * qglEdgeFlagv )(const GLboolean *flag); +extern void ( * qglEnable )(GLenum cap); +extern void ( * qglEnableClientState )(GLenum array); +extern void ( * qglEnd )(void); +extern void ( * qglEndFrame )(void); +extern void ( * qglEndShadow )(void); +extern void ( * qglEndList )(void); +extern void ( * qglEvalCoord1d )(GLdouble u); +extern void ( * qglEvalCoord1dv )(const GLdouble *u); +extern void ( * qglEvalCoord1f )(GLfloat u); +extern void ( * qglEvalCoord1fv )(const GLfloat *u); +extern void ( * qglEvalCoord2d )(GLdouble u, GLdouble v); +extern void ( * qglEvalCoord2dv )(const GLdouble *u); +extern void ( * qglEvalCoord2f )(GLfloat u, GLfloat v); +extern void ( * qglEvalCoord2fv )(const GLfloat *u); +extern void ( * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +extern void ( * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +extern void ( * qglEvalPoint1 )(GLint i); +extern void ( * qglEvalPoint2 )(GLint i, GLint j); +extern void ( * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +extern void ( * qglFinish )(void); +extern void ( * qglFlush )(void); +extern void ( * qglFlushShadow )(void); +extern void ( * qglFogf )(GLenum pname, GLfloat param); +extern void ( * qglFogfv )(GLenum pname, const GLfloat *params); +extern void ( * qglFogi )(GLenum pname, GLint param); +extern void ( * qglFogiv )(GLenum pname, const GLint *params); +extern void ( * qglFrontFace )(GLenum mode); +extern void ( * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern GLuint ( * qglGenLists )(GLsizei range); +extern void ( * qglGenTextures )(GLsizei n, GLuint *textures); +extern void ( * qglGetBooleanv )(GLenum pname, GLboolean *params); +extern void ( * qglGetClipPlane )(GLenum plane, GLdouble *equation); +extern void ( * qglGetDoublev )(GLenum pname, GLdouble *params); +extern GLenum ( * qglGetError )(void); +extern void ( * qglGetFloatv )(GLenum pname, GLfloat *params); +extern void ( * qglGetIntegerv )(GLenum pname, GLint *params); +extern void ( * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +extern void ( * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +extern void ( * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +extern void ( * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +extern void ( * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +extern void ( * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +extern void ( * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +extern void ( * qglGetPixelMapfv )(GLenum map, GLfloat *values); +extern void ( * qglGetPixelMapuiv )(GLenum map, GLuint *values); +extern void ( * qglGetPixelMapusv )(GLenum map, GLushort *values); +extern void ( * qglGetPointerv )(GLenum pname, GLvoid* *params); +extern void ( * qglGetPolygonStipple )(GLubyte *mask); +extern const GLubyte * ( * qglGetString )(GLenum name); +extern void ( * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +extern void ( * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +extern void ( * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +extern void ( * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +extern void ( * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +extern void ( * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +extern void ( * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +extern void ( * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +extern void ( * qglHint )(GLenum target, GLenum mode); +extern void ( * qglIndexedTriToStrip )(GLsizei count, const GLushort *indices); +extern void ( * qglIndexMask )(GLuint mask); +extern void ( * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( * qglIndexd )(GLdouble c); +extern void ( * qglIndexdv )(const GLdouble *c); +extern void ( * qglIndexf )(GLfloat c); +extern void ( * qglIndexfv )(const GLfloat *c); +extern void ( * qglIndexi )(GLint c); +extern void ( * qglIndexiv )(const GLint *c); +extern void ( * qglIndexs )(GLshort c); +extern void ( * qglIndexsv )(const GLshort *c); +extern void ( * qglIndexub )(GLubyte c); +extern void ( * qglIndexubv )(const GLubyte *c); +extern void ( * qglInitNames )(void); +extern void ( * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +extern GLboolean ( * qglIsEnabled )(GLenum cap); +extern GLboolean ( * qglIsList )(GLuint listArg); +extern GLboolean ( * qglIsTexture )(GLuint texture); +extern void ( * qglLightModelf )(GLenum pname, GLfloat param); +extern void ( * qglLightModelfv )(GLenum pname, const GLfloat *params); +extern void ( * qglLightModeli )(GLenum pname, GLint param); +extern void ( * qglLightModeliv )(GLenum pname, const GLint *params); +extern void ( * qglLightf )(GLenum light, GLenum pname, GLfloat param); +extern void ( * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +extern void ( * qglLighti )(GLenum light, GLenum pname, GLint param); +extern void ( * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +extern void ( * qglLineStipple )(GLint factor, GLushort pattern); +extern void ( * qglLineWidth )(GLfloat width); +extern void ( * qglListBase )(GLuint base); +extern void ( * qglLoadIdentity )(void); +extern void ( * qglLoadMatrixd )(const GLdouble *m); +extern void ( * qglLoadMatrixf )(const GLfloat *m); +extern void ( * qglLoadName )(GLuint name); +extern void ( * qglLogicOp )(GLenum opcode); +extern void ( * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +extern void ( * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +extern void ( * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +extern void ( * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +extern void ( * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +extern void ( * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +extern void ( * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +extern void ( * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +extern void ( * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +extern void ( * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +extern void ( * qglMateriali )(GLenum face, GLenum pname, GLint param); +extern void ( * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +extern void ( * qglMatrixMode )(GLenum mode); +extern void ( * qglMultMatrixd )(const GLdouble *m); +extern void ( * qglMultMatrixf )(const GLfloat *m); +extern void ( * qglNewList )(GLuint list, GLenum mode); +extern void ( * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +extern void ( * qglNormal3bv )(const GLbyte *v); +extern void ( * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +extern void ( * qglNormal3dv )(const GLdouble *v); +extern void ( * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +extern void ( * qglNormal3fv )(const GLfloat *v); +extern void ( * qglNormal3i )(GLint nx, GLint ny, GLint nz); +extern void ( * qglNormal3iv )(const GLint *v); +extern void ( * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +extern void ( * qglNormal3sv )(const GLshort *v); +extern void ( * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern void ( * qglPassThrough )(GLfloat token); +extern void ( * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +extern void ( * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +extern void ( * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +extern void ( * qglPixelStoref )(GLenum pname, GLfloat param); +extern void ( * qglPixelStorei )(GLenum pname, GLint param); +extern void ( * qglPixelTransferf )(GLenum pname, GLfloat param); +extern void ( * qglPixelTransferi )(GLenum pname, GLint param); +extern void ( * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +extern void ( * qglPointSize )(GLfloat size); +extern void ( * qglPolygonMode )(GLenum face, GLenum mode); +extern void ( * qglPolygonOffset )(GLfloat factor, GLfloat units); +extern void ( * qglPolygonStipple )(const GLubyte *mask); +extern void ( * qglPopAttrib )(void); +extern void ( * qglPopClientAttrib )(void); +extern void ( * qglPopMatrix )(void); +extern void ( * qglPopName )(void); +extern void ( * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +extern void ( * qglPushAttrib )(GLbitfield mask); +extern void ( * qglPushClientAttrib )(GLbitfield mask); +extern void ( * qglPushMatrix )(void); +extern void ( * qglPushName )(GLuint name); +extern void ( * qglRasterPos2d )(GLdouble x, GLdouble y); +extern void ( * qglRasterPos2dv )(const GLdouble *v); +extern void ( * qglRasterPos2f )(GLfloat x, GLfloat y); +extern void ( * qglRasterPos2fv )(const GLfloat *v); +extern void ( * qglRasterPos2i )(GLint x, GLint y); +extern void ( * qglRasterPos2iv )(const GLint *v); +extern void ( * qglRasterPos2s )(GLshort x, GLshort y); +extern void ( * qglRasterPos2sv )(const GLshort *v); +extern void ( * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( * qglRasterPos3dv )(const GLdouble *v); +extern void ( * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( * qglRasterPos3fv )(const GLfloat *v); +extern void ( * qglRasterPos3i )(GLint x, GLint y, GLint z); +extern void ( * qglRasterPos3iv )(const GLint *v); +extern void ( * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +extern void ( * qglRasterPos3sv )(const GLshort *v); +extern void ( * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( * qglRasterPos4dv )(const GLdouble *v); +extern void ( * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( * qglRasterPos4fv )(const GLfloat *v); +extern void ( * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( * qglRasterPos4iv )(const GLint *v); +extern void ( * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( * qglRasterPos4sv )(const GLshort *v); +extern void ( * qglReadBuffer )(GLenum mode); +extern void ( * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei twidth, GLsizei theight, GLvoid *pixels); +extern void ( * qglCopyBackBufferToTexEXT )(float width, float height, float u1, float v1, float u2, float v2); +extern void ( * qglCopyBackBufferToTex )(void); +extern void ( * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +extern void ( * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +extern void ( * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +extern void ( * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +extern void ( * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +extern void ( * qglRectiv )(const GLint *v1, const GLint *v2); +extern void ( * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +extern void ( * qglRectsv )(const GLshort *v1, const GLshort *v2); +extern GLint ( * qglRenderMode )(GLenum mode); +extern void ( * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +extern void ( * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +extern void ( * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +extern void ( * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( * qglSelectBuffer )(GLsizei size, GLuint *buffer); +extern void ( * qglShadeModel )(GLenum mode); +extern void ( * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +extern void ( * qglStencilMask )(GLuint mask); +extern void ( * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +extern void ( * qglTexCoord1d )(GLdouble s); +extern void ( * qglTexCoord1dv )(const GLdouble *v); +extern void ( * qglTexCoord1f )(GLfloat s); +extern void ( * qglTexCoord1fv )(const GLfloat *v); +extern void ( * qglTexCoord1i )(GLint s); +extern void ( * qglTexCoord1iv )(const GLint *v); +extern void ( * qglTexCoord1s )(GLshort s); +extern void ( * qglTexCoord1sv )(const GLshort *v); +extern void ( * qglTexCoord2d )(GLdouble s, GLdouble t); +extern void ( * qglTexCoord2dv )(const GLdouble *v); +extern void ( * qglTexCoord2f )(GLfloat s, GLfloat t); +extern void ( * qglTexCoord2fv )(const GLfloat *v); +extern void ( * qglTexCoord2i )(GLint s, GLint t); +extern void ( * qglTexCoord2iv )(const GLint *v); +extern void ( * qglTexCoord2s )(GLshort s, GLshort t); +extern void ( * qglTexCoord2sv )(const GLshort *v); +extern void ( * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +extern void ( * qglTexCoord3dv )(const GLdouble *v); +extern void ( * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +extern void ( * qglTexCoord3fv )(const GLfloat *v); +extern void ( * qglTexCoord3i )(GLint s, GLint t, GLint r); +extern void ( * qglTexCoord3iv )(const GLint *v); +extern void ( * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +extern void ( * qglTexCoord3sv )(const GLshort *v); +extern void ( * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +extern void ( * qglTexCoord4dv )(const GLdouble *v); +extern void ( * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void ( * qglTexCoord4fv )(const GLfloat *v); +extern void ( * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +extern void ( * qglTexCoord4iv )(const GLint *v); +extern void ( * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +extern void ( * qglTexCoord4sv )(const GLshort *v); +extern void ( * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +extern void ( * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +extern void ( * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +extern void ( * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +extern void ( * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +extern void ( * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +extern void ( * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +extern void ( * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +extern void ( * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +extern void ( * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglTexImage2DEXT )(GLenum target, GLint level, GLint numlevels, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +extern void ( * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +extern void ( * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +extern void ( * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +extern void ( * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( * qglVertex2d )(GLdouble x, GLdouble y); +extern void ( * qglVertex2dv )(const GLdouble *v); +extern void ( * qglVertex2f )(GLfloat x, GLfloat y); +extern void ( * qglVertex2fv )(const GLfloat *v); +extern void ( * qglVertex2i )(GLint x, GLint y); +extern void ( * qglVertex2iv )(const GLint *v); +extern void ( * qglVertex2s )(GLshort x, GLshort y); +extern void ( * qglVertex2sv )(const GLshort *v); +extern void ( * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( * qglVertex3dv )(const GLdouble *v); +extern void ( * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( * qglVertex3fv )(const GLfloat *v); +extern void ( * qglVertex3i )(GLint x, GLint y, GLint z); +extern void ( * qglVertex3iv )(const GLint *v); +extern void ( * qglVertex3s )(GLshort x, GLshort y, GLshort z); +extern void ( * qglVertex3sv )(const GLshort *v); +extern void ( * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( * qglVertex4dv )(const GLdouble *v); +extern void ( * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( * qglVertex4fv )(const GLfloat *v); +extern void ( * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( * qglVertex4iv )(const GLint *v); +extern void ( * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( * qglVertex4sv )(const GLshort *v); +extern void ( * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +#endif diff --git a/codemp/renderer/tr_animation.cpp b/codemp/renderer/tr_animation.cpp new file mode 100644 index 0000000..77e74da --- /dev/null +++ b/codemp/renderer/tr_animation.cpp @@ -0,0 +1,16 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +/* + +All bones should be an identity orientation to display the mesh exactly +as it is specified. + +For all other frames, the bones represent the transformation from the +orientation of the bone in the base frame to the orientation in this +frame. + +*/ + diff --git a/codemp/renderer/tr_arioche.cpp b/codemp/renderer/tr_arioche.cpp new file mode 100644 index 0000000..df6d11c --- /dev/null +++ b/codemp/renderer/tr_arioche.cpp @@ -0,0 +1,117 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" +#include "tr_worldeffects.h" + +// Patches up the loaded map to handle the parameters passed from the UI + +// Remap sky to contents of the cvar ar_sky +// Grab sunlight properties from the indirected sky + +void R_RMGInit(void) +{ + char newSky[MAX_QPATH]; + char newFog[MAX_QPATH]; + shader_t *sky; + shader_t *fog; + fog_t *gfog; + mgrid_t *grid; + char temp[MAX_QPATH]; + int i; + unsigned short *pos; + + Cvar_VariableStringBuffer("RMG_sky", newSky, MAX_QPATH); + // Get sunlight - this should set up all the sunlight data + sky = R_FindShader( newSky, lightmapsNone, stylesDefault, qfalse ); + + // Remap sky + R_RemapShader("textures/tools/_sky", newSky, NULL); + + // Fill in the lightgrid with sunlight + if(tr.world->lightGridData) + { +#ifdef _XBOX + byte *memory = (byte *)tr.world->lightGridData; + + byte *array; + array = memory; + memory += 3; + + array[0] = (byte)Com_Clamp(0, 255, tr.sunAmbient[0] * 255.0f); + array[1] = (byte)Com_Clamp(0, 255, tr.sunAmbient[1] * 255.0f); + array[2] = (byte)Com_Clamp(0, 255, tr.sunAmbient[2] * 255.0f); + + array[3] = (byte)Com_Clamp(0, 255, tr.sunLight[0]); + array[4] = (byte)Com_Clamp(0, 255, tr.sunLight[1]); + array[5] = (byte)Com_Clamp(0, 255, tr.sunLight[2]); + + NormalToLatLong(tr.sunDirection, grid->latLong); +#else // _XBOX + grid = tr.world->lightGridData; + grid->ambientLight[0][0] = (byte)Com_Clampi(0, 255, tr.sunAmbient[0] * 255.0f); + grid->ambientLight[0][1] = (byte)Com_Clampi(0, 255, tr.sunAmbient[1] * 255.0f); + grid->ambientLight[0][2] = (byte)Com_Clampi(0, 255, tr.sunAmbient[2] * 255.0f); + R_ColorShiftLightingBytes(grid->ambientLight[0], grid->ambientLight[0]); + + grid->directLight[0][0] = (byte)Com_Clampi(0, 255, tr.sunLight[0]); + grid->directLight[0][1] = (byte)Com_Clampi(0, 255, tr.sunLight[1]); + grid->directLight[0][2] = (byte)Com_Clampi(0, 255, tr.sunLight[2]); + R_ColorShiftLightingBytes(grid->directLight[0], grid->directLight[0]); + + NormalToLatLong(tr.sunDirection, grid->latLong); +#endif // _XBOX + + pos = tr.world->lightGridArray; + for(i=0;inumGridArrayElements;i++) + { + *pos = 0; + pos++; + } + } + + // Override the global fog with the defined one + if(tr.world->globalFog != -1) + { + Cvar_VariableStringBuffer("RMG_fog", newFog, MAX_QPATH); + fog = R_FindShader( newFog, lightmapsNone, stylesDefault, qfalse); + if (fog != tr.defaultShader) + { + gfog = tr.world->fogs + tr.world->globalFog; + gfog->parms = *fog->fogParms; + if (gfog->parms.depthForOpaque) + { + gfog->tcScale = 1.0f / ( gfog->parms.depthForOpaque * 8.0f ); + tr.distanceCull = gfog->parms.depthForOpaque; + tr.distanceCullSquared = tr.distanceCull * tr.distanceCull; + Cvar_Set("RMG_distancecull", va("%f", tr.distanceCull)); + } + else + { + gfog->tcScale = 1.0f; + } + gfog->colorInt = ColorBytes4 ( gfog->parms.color[0], + gfog->parms.color[1], + gfog->parms.color[2], 1.0f ); + } + } + + Cvar_VariableStringBuffer("RMG_weather", temp, MAX_QPATH); + + // Set up any weather effects + switch(atol(temp)) + { + case 0: + break; + case 1: + R_WorldEffectCommand("rain init 1000"); + R_WorldEffectCommand("rain outside"); + break; + case 2: + R_WorldEffectCommand("snow init 1000 outside"); + R_WorldEffectCommand("snow outside"); + break; + } +} + +// end diff --git a/codemp/renderer/tr_backend.cpp b/codemp/renderer/tr_backend.cpp new file mode 100644 index 0000000..9465f28 --- /dev/null +++ b/codemp/renderer/tr_backend.cpp @@ -0,0 +1,2211 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +#ifndef DEDICATED +#if !defined __TR_WORLDEFFECTS_H + #include "tr_WorldEffects.h" +#endif +#endif + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#include "../win32/win_highdynamicrange.h" +#endif + +backEndData_t *backEndData; +backEndState_t backEnd; + +bool tr_stencilled = false; +extern qboolean tr_distortionPrePost; //tr_shadows.cpp +extern qboolean tr_distortionNegate; //tr_shadows.cpp +extern void RB_CaptureScreenImage(void); //tr_shadows.cpp +extern void RB_DistortionFill(void); //tr_shadows.cpp +static void RB_DrawGlowOverlay(); +static void RB_BlurGlowTexture(); + +// Whether we are currently rendering only glowing objects or not. +bool g_bRenderGlowingObjects = false; + +// Whether the current hardware supports dynamic glows/flares. +bool g_bDynamicGlowSupported = false; + +extern void R_RotateForViewer(void); +extern void R_SetupFrustum(void); + +static const float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) +#if defined (_XBOX) + 0, 0, 1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +#else + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +#endif +}; + +#ifndef DEDICATED + +/* +** GL_Bind +*/ +void GL_Bind( image_t *image ) { + int texnum; + + if ( !image ) { + Com_Printf (S_COLOR_YELLOW "GL_Bind: NULL image\n" ); + texnum = tr.defaultImage->texnum; + } else { + texnum = image->texnum; + } + + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum = tr.dlightImage->texnum; + } + + if ( glState.currenttextures[glState.currenttmu] != texnum ) { +#ifndef _XBOX + image->frameUsed = tr.frameCount; +#endif + glState.currenttextures[glState.currenttmu] = texnum; + qglBindTexture (GL_TEXTURE_2D, texnum); + } +} + +//bind 3D texture -rww +void GL_Bind3D( image_t *image ) +{ + int texnum; + + if ( !image ) { + Com_Printf (S_COLOR_YELLOW "GL_Bind: NULL image\n" ); + texnum = tr.defaultImage->texnum; + } else { + texnum = image->texnum; + } + + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum = tr.dlightImage->texnum; + } + + if ( glState.currenttextures[glState.currenttmu] != texnum ) { +#ifndef _XBOX + image->frameUsed = tr.frameCount; +#endif + glState.currenttextures[glState.currenttmu] = texnum; + qglBindTexture (GL_TEXTURE_3D, texnum); + } +} + +/* +** GL_SelectTexture +*/ +void GL_SelectTexture( int unit ) +{ + if ( glState.currenttmu == unit ) + { + return; + } + + if ( unit == 0 ) + { + qglActiveTextureARB( GL_TEXTURE0_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE0_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE0_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE0_ARB )\n" ); + } + else if ( unit == 1 ) + { + qglActiveTextureARB( GL_TEXTURE1_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE1_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE1_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE1_ARB )\n" ); + } + else if ( unit == 2 ) + { + qglActiveTextureARB( GL_TEXTURE2_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE2_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE2_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE2_ARB )\n" ); + } + else if ( unit == 3 ) + { + qglActiveTextureARB( GL_TEXTURE3_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE3_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE3_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE3_ARB )\n" ); + } + else { + Com_Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit ); + } + + glState.currenttmu = unit; +} + + +/* +** GL_Cull +*/ +void GL_Cull( int cullType ) { + if ( glState.faceCulling == cullType ) { + return; + } + glState.faceCulling = cullType; + if (backEnd.projection2D){ //don't care, we're in 2d when it's always disabled + return; + } + + if ( cullType == CT_TWO_SIDED ) + { + qglDisable( GL_CULL_FACE ); + } + else + { + qglEnable( GL_CULL_FACE ); + + if ( cullType == CT_BACK_SIDED ) + { + if ( backEnd.viewParms.isMirror ) + { + qglCullFace( GL_FRONT ); + } + else + { + qglCullFace( GL_BACK ); + } + } + else + { + if ( backEnd.viewParms.isMirror ) + { + qglCullFace( GL_BACK ); + } + else + { + qglCullFace( GL_FRONT ); + } + } + } +} + +/* +** GL_TexEnv +*/ +void GL_TexEnv( int env ) +{ + if ( env == glState.texEnv[glState.currenttmu] ) + { + return; + } + + glState.texEnv[glState.currenttmu] = env; + + + switch ( env ) + { + case GL_MODULATE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + break; + case GL_REPLACE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + break; + case GL_DECAL: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); + break; + case GL_ADD: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); + break; +#ifdef _XBOX + case GL_NONE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_NONE ); + break; +#endif + default: + Com_Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed\n", env ); + break; + } +} + +/* +** GL_State +** +** This routine is responsible for setting the most commonly changed state +** in Q3. +*/ +void GL_State( unsigned long stateBits ) +{ + unsigned long diff = stateBits ^ glState.glStateBits; + + if ( !diff ) + { + return; + } + + // + // check depthFunc bits + // + if ( diff & GLS_DEPTHFUNC_EQUAL ) + { + if ( stateBits & GLS_DEPTHFUNC_EQUAL ) + { + qglDepthFunc( GL_EQUAL ); + } + else + { + qglDepthFunc( GL_LEQUAL ); + } + } + + // + // check blend bits + // + if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + GLenum srcFactor, dstFactor; + + if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + switch ( stateBits & GLS_SRCBLEND_BITS ) + { + case GLS_SRCBLEND_ZERO: + srcFactor = GL_ZERO; + break; + case GLS_SRCBLEND_ONE: + srcFactor = GL_ONE; + break; + case GLS_SRCBLEND_DST_COLOR: + srcFactor = GL_DST_COLOR; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: + srcFactor = GL_ONE_MINUS_DST_COLOR; + break; + case GLS_SRCBLEND_SRC_ALPHA: + srcFactor = GL_SRC_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: + srcFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_SRCBLEND_DST_ALPHA: + srcFactor = GL_DST_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: + srcFactor = GL_ONE_MINUS_DST_ALPHA; + break; + case GLS_SRCBLEND_ALPHA_SATURATE: + srcFactor = GL_SRC_ALPHA_SATURATE; + break; + default: + srcFactor = GL_ONE; // to get warning to shut up + Com_Error( ERR_DROP, "GL_State: invalid src blend state bits\n" ); + break; + } + + switch ( stateBits & GLS_DSTBLEND_BITS ) + { + case GLS_DSTBLEND_ZERO: + dstFactor = GL_ZERO; + break; + case GLS_DSTBLEND_ONE: + dstFactor = GL_ONE; + break; + case GLS_DSTBLEND_SRC_COLOR: + dstFactor = GL_SRC_COLOR; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: + dstFactor = GL_ONE_MINUS_SRC_COLOR; + break; + case GLS_DSTBLEND_SRC_ALPHA: + dstFactor = GL_SRC_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_DSTBLEND_DST_ALPHA: + dstFactor = GL_DST_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: + dstFactor = GL_ONE_MINUS_DST_ALPHA; + break; + default: + dstFactor = GL_ONE; // to get warning to shut up + Com_Error( ERR_DROP, "GL_State: invalid dst blend state bits\n" ); + break; + } + + qglEnable( GL_BLEND ); + qglBlendFunc( srcFactor, dstFactor ); + } + else + { + qglDisable( GL_BLEND ); + } + } + + // + // check depthmask + // + if ( diff & GLS_DEPTHMASK_TRUE ) + { + if ( stateBits & GLS_DEPTHMASK_TRUE ) + { + qglDepthMask( GL_TRUE ); + } + else + { + qglDepthMask( GL_FALSE ); + } + } + + // + // fill/line mode + // + if ( diff & GLS_POLYMODE_LINE ) + { + if ( stateBits & GLS_POLYMODE_LINE ) + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + } + else + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + + // + // depthtest + // + if ( diff & GLS_DEPTHTEST_DISABLE ) + { + if ( stateBits & GLS_DEPTHTEST_DISABLE ) + { + qglDisable( GL_DEPTH_TEST ); + } + else + { + qglEnable( GL_DEPTH_TEST ); + } + } + + // + // alpha test + // + if ( diff & GLS_ATEST_BITS ) + { + switch ( stateBits & GLS_ATEST_BITS ) + { + case 0: + qglDisable( GL_ALPHA_TEST ); + break; + case GLS_ATEST_GT_0: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GREATER, 0.0f ); + break; + case GLS_ATEST_LT_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_LESS, 0.5f ); + break; + case GLS_ATEST_GE_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GEQUAL, 0.5f ); + break; + case GLS_ATEST_GE_C0: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GEQUAL, 0.75f ); + break; + default: + assert( 0 ); + break; + } + } + + glState.glStateBits = stateBits; +} + + + +/* +================ +RB_Hyperspace + +A player has predicted a teleport, but hasn't arrived yet +================ +*/ +static void RB_Hyperspace( void ) { + float c; + + if ( !backEnd.isHyperspace ) { + // do initialization shit + } + + c = ( backEnd.refdef.time & 255 ) / 255.0f; + qglClearColor( c, c, c, 1 ); + qglClear( GL_COLOR_BUFFER_BIT ); + + backEnd.isHyperspace = qtrue; +} + + +void SetViewportAndScissor( void ) { + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf( backEnd.viewParms.projectionMatrix ); + qglMatrixMode(GL_MODELVIEW); + + // set the window clipping + qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); +} + +/* +================= +RB_BeginDrawingView + +Any mirrored or portaled views have already been drawn, so prepare +to actually render the visible surfaces for this view +================= +*/ +void RB_BeginDrawingView (void) { + int clearBits = GL_DEPTH_BUFFER_BIT; + + // sync with gl if needed + if ( r_finish->integer == 1 && !glState.finishCalled ) { + qglFinish (); + glState.finishCalled = qtrue; + } + if ( r_finish->integer == 0 ) { + glState.finishCalled = qtrue; + } + + // we will need to change the projection matrix before drawing + // 2D images again + backEnd.projection2D = qfalse; + + // + // set the modelview matrix for the viewer + // + SetViewportAndScissor(); + + // ensures that depth writes are enabled for the depth clear + GL_State( GLS_DEFAULT ); + + // clear relevant buffers + if ( r_measureOverdraw->integer || r_shadows->integer == 2 || tr_stencilled ) + { + clearBits |= GL_STENCIL_BUFFER_BIT; + tr_stencilled = false; + } + + if (skyboxportal) + { + if ( backEnd.refdef.rdflags & RDF_SKYBOXPORTAL ) + { // portal scene, clear whatever is necessary + if (r_fastsky->integer || (backEnd.refdef.rdflags & RDF_NOWORLDMODEL) ) + { // fastsky: clear color + // try clearing first with the portal sky fog color, then the world fog color, then finally a default + clearBits |= GL_COLOR_BUFFER_BIT; + //rwwFIXMEFIXME: Clear with fog color if there is one + qglClearColor ( 0.5, 0.5, 0.5, 1.0 ); + } + } + } + else + { + if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) && !g_bRenderGlowingObjects ) + { + clearBits |= GL_COLOR_BUFFER_BIT; // FIXME: only if sky shaders have been used +#ifdef _DEBUG + qglClearColor( 0.8f, 0.7f, 0.4f, 1.0f ); // FIXME: get color of sky +#else + qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); // FIXME: get color of sky +#endif + } + } + + if ( tr.refdef.rdflags & RDF_AUTOMAP || (!( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) && r_DynamicGlow->integer && !g_bRenderGlowingObjects ) ) + { + if (tr.world && tr.world->globalFog != -1) + { //this is because of a bug in multiple scenes I think, it needs to clear for the second scene but it doesn't normally. + const fog_t *fog = &tr.world->fogs[tr.world->globalFog]; + + clearBits |= GL_COLOR_BUFFER_BIT; + qglClearColor(fog->parms.color[0], fog->parms.color[1], fog->parms.color[2], 1.0f ); + } + } + + // If this pass is to just render the glowing objects, don't clear the depth buffer since + // we're sharing it with the main scene (since the main scene has already been rendered). -AReis + if ( g_bRenderGlowingObjects ) + { + clearBits &= ~GL_DEPTH_BUFFER_BIT; + } + + if (clearBits) + { + qglClear( clearBits ); + } + + if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) ) + { + RB_Hyperspace(); + return; + } + else + { + backEnd.isHyperspace = qfalse; + } + + glState.faceCulling = -1; // force face culling to set next time + + // we will only draw a sun if there was sky rendered in this view + backEnd.skyRenderedThisView = qfalse; + + // clip to the plane of the portal + if ( backEnd.viewParms.isPortal ) { + float plane[4]; + double plane2[4]; + + plane[0] = backEnd.viewParms.portalPlane.normal[0]; + plane[1] = backEnd.viewParms.portalPlane.normal[1]; + plane[2] = backEnd.viewParms.portalPlane.normal[2]; + plane[3] = backEnd.viewParms.portalPlane.dist; + + plane2[0] = DotProduct (backEnd.viewParms.ori.axis[0], plane); + plane2[1] = DotProduct (backEnd.viewParms.ori.axis[1], plane); + plane2[2] = DotProduct (backEnd.viewParms.ori.axis[2], plane); + plane2[3] = DotProduct (plane, backEnd.viewParms.ori.origin) - plane[3]; + + qglLoadMatrixf( s_flipMatrix ); + qglClipPlane (GL_CLIP_PLANE0, plane2); + qglEnable (GL_CLIP_PLANE0); + } else { + qglDisable (GL_CLIP_PLANE0); + } +} + +#define MAC_EVENT_PUMP_MSEC 5 + +//used by RF_DISTORTION +static inline bool R_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y) +{ + int xcenter, ycenter; + vec3_t local, transformed; + vec3_t vfwd; + vec3_t vright; + vec3_t vup; + float xzi; + float yzi; + + xcenter = glConfig.vidWidth / 2; + ycenter = glConfig.vidHeight / 2; + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + ycenter = 240 / 2; +#endif + + //AngleVectors (tr.refdef.viewangles, vfwd, vright, vup); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) { + VectorCopy(backEnd.refdef.viewaxis[0], vfwd); + VectorCopy(backEnd.refdef.viewaxis[1], vright); + VectorCopy(backEnd.refdef.viewaxis[2], vup); + + VectorSubtract (worldCoord, backEnd.refdef.vieworg, local); + } + else { +#endif + VectorCopy(tr.refdef.viewaxis[0], vfwd); + VectorCopy(tr.refdef.viewaxis[1], vright); + VectorCopy(tr.refdef.viewaxis[2], vup); + + VectorSubtract (worldCoord, tr.refdef.vieworg, local); +#ifdef _XBOX + } +#endif + + transformed[0] = DotProduct(local,vright); + transformed[1] = DotProduct(local,vup); + transformed[2] = DotProduct(local,vfwd); + + // Make sure Z is not negative. + if(transformed[2] < 0.01) + { + return false; + } + + xzi = xcenter / transformed[2] * (90.0/tr.refdef.fov_x); + yzi = ycenter / transformed[2] * (90.0/tr.refdef.fov_y); + + *x = xcenter + xzi * transformed[0]; + *y = ycenter - yzi * transformed[1]; + + return true; +} + +//used by RF_DISTORTION +static inline bool R_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y ) +{ + float xF, yF; + bool retVal = R_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF ); + *x = (int)xF; + *y = (int)yF; + return retVal; +} + +/* +================== +RB_RenderDrawSurfList +================== +*/ +//number of possible surfs we can postrender. +//note that postrenders lack much of the optimization that the standard sort-render crap does, +//so it's slower. +#define MAX_POST_RENDERS 128 + +typedef struct +{ + int fogNum; + int entNum; + int dlighted; + int depthRange; + drawSurf_t *drawSurf; + shader_t *shader; + qboolean eValid; +} postRender_t; + +static postRender_t g_postRenders[MAX_POST_RENDERS]; +static int g_numPostRenders = 0; + +//get the "average" (ideally center) position of a surface on the tess. +//this is a kind of lame method because I can't think correctly right now. +static inline bool R_AverageTessXYZ(vec3_t dest) +{ + int i = 1; + float bd = 0.0f; + float d = 0.0f; + int b = -1; + vec3_t v; + + while (i < tess.numVertexes) + { + VectorSubtract(tess.xyz[i], tess.xyz[i], v); + d = VectorLength(v); + if (b == -1 || d < bd) + { + b = i; + bd = d; + } + i++; + } + if (b != -1) + { + VectorSubtract(tess.xyz[0], tess.xyz[b], v); + + VectorScale(v, 0.5f, dest); + VectorAdd(dest, tess.xyz[0], dest); + + return true; + } + + return false; +} + +void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader, *oldShader; + int fogNum, oldFogNum; + int entityNum, oldEntityNum; + int dlighted, oldDlighted; + int depthRange, oldDepthRange; + int i; + drawSurf_t *drawSurf; + unsigned int oldSort; + float originalTime; + trRefEntity_t *curEnt; + postRender_t *pRender; + bool didShadowPass = false; +#ifdef __MACOS__ + int macEventTime; + + Sys_PumpEvents(); // crutch up the mac's limited buffer queue size + + // we don't want to pump the event loop too often and waste time, so + // we are going to check every shader change + macEventTime = Sys_Milliseconds()*com_timescale->value + MAC_EVENT_PUMP_MSEC; +#endif + + if (g_bRenderGlowingObjects) + { //only shadow on initial passes + didShadowPass = true; + } + + // save original time for entity shader offsets + originalTime = backEnd.refdef.floatTime; + + // clear the z buffer, set the modelview, etc + RB_BeginDrawingView (); + + // draw everything + oldEntityNum = -1; + backEnd.currentEntity = &tr.worldEntity; + oldShader = NULL; + oldFogNum = -1; + oldDepthRange = qfalse; + oldDlighted = qfalse; + oldSort = (unsigned int) -1; + depthRange = qfalse; + + backEnd.pc.c_surfaces += numDrawSurfs; + + for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) + { + if ( drawSurf->sort == oldSort ) + { + // fast path, same as previous sort + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + continue; + } + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted ); + +#ifndef _XBOX // GLOWXXX + // If we're rendering glowing objects, but this shader has no stages with glow, skip it! + if ( g_bRenderGlowingObjects && !shader->hasGlow ) + { + shader = oldShader; + entityNum = oldEntityNum; + fogNum = oldFogNum; + dlighted = oldDlighted; + continue; + } +#endif + oldSort = drawSurf->sort; + + // + // change the tess parameters if needed + // a "entityMergable" shader is a shader that can have surfaces from seperate + // entities merged into a single batch, like smoke and blood puff sprites + if (entityNum != TR_WORLDENT && + g_numPostRenders < MAX_POST_RENDERS) + { + if ( (backEnd.refdef.entities[entityNum].e.renderfx & RF_DISTORTION) || + (backEnd.refdef.entities[entityNum].e.renderfx & RF_FORCEPOST) || + (backEnd.refdef.entities[entityNum].e.renderfx & RF_FORCE_ENT_ALPHA) ) + { //must render last + curEnt = &backEnd.refdef.entities[entityNum]; + pRender = &g_postRenders[g_numPostRenders]; + + g_numPostRenders++; + + depthRange = 0; + //figure this stuff out now and store it + if ( curEnt->e.renderfx & RF_NODEPTH ) + { + depthRange = 2; + } + else if ( curEnt->e.renderfx & RF_DEPTHHACK ) + { + depthRange = 1; + } + pRender->depthRange = depthRange; + + //It is not necessary to update the old* values because + //we are not updating now with the current values. + depthRange = oldDepthRange; + + //store off the ent num + pRender->entNum = entityNum; + + //remember the other values necessary for rendering this surf + pRender->drawSurf = drawSurf; + pRender->dlighted = dlighted; + pRender->fogNum = fogNum; + pRender->shader = shader; + + /* + if (shader == tr.distortionShader) + { + pRender->eValid = qfalse; + } + else + */ + { + pRender->eValid = qtrue; + } + + //assure the info is back to the last set state + shader = oldShader; + entityNum = oldEntityNum; + fogNum = oldFogNum; + dlighted = oldDlighted; + + oldSort = -20; //invalidate this thing, cause we may want to postrender more surfs of the same sort + + //continue without bothering to begin a draw surf + continue; + } + } + + if (shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted + || ( entityNum != oldEntityNum && !shader->entityMergable ) ) + { + if (oldShader != NULL) { +#ifdef __MACOS__ // crutch up the mac's limited buffer queue size + int t; + + t = Sys_Milliseconds()*com_timescale->value; + if ( t > macEventTime ) { + macEventTime = t + MAC_EVENT_PUMP_MSEC; + Sys_PumpEvents(); + } +#endif + RB_EndSurface(); + +/* + if (!didShadowPass && shader && shader->sort > SS_BANNER) + { + RB_ShadowFinish(); + didShadowPass = true; + } +*/ + } + RB_BeginSurface( shader, fogNum ); + oldShader = shader; + oldFogNum = fogNum; + oldDlighted = dlighted; + } + + // + // change the modelview matrix if needed + // + if ( entityNum != oldEntityNum ) { + depthRange = 0; + + if ( entityNum != TR_WORLDENT ) { + backEnd.currentEntity = &backEnd.refdef.entities[entityNum]; + backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime; + // we have to reset the shaderTime as well otherwise image animations start + // from the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + + // set up the transformation matrix + R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.ori ); + + // set up the dynamic lighting if needed + if ( backEnd.currentEntity->needDlights ) { + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori ); + } + + if ( backEnd.currentEntity->e.renderfx & RF_NODEPTH ) { + // No depth at all, very rare but some things for seeing through walls + depthRange = 2; + } + else if ( backEnd.currentEntity->e.renderfx & RF_DEPTHHACK ) { + // hack the depth range to prevent view model from poking into walls + depthRange = 1; + } + } else { + backEnd.currentEntity = &tr.worldEntity; + backEnd.refdef.floatTime = originalTime; + backEnd.ori = backEnd.viewParms.world; + // we have to reset the shaderTime as well otherwise image animations on + // the world (like water) continue with the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori ); + } + + qglLoadMatrixf( backEnd.ori.modelMatrix ); + + // + // change depthrange if needed + // + if ( oldDepthRange != depthRange ) { + switch ( depthRange ) { + default: + case 0: + qglDepthRange (0, 1); + break; + + case 1: + qglDepthRange (0, .3); + break; + + case 2: + qglDepthRange (0, 0); + break; + } + + oldDepthRange = depthRange; + } + + oldEntityNum = entityNum; + } + + // add the triangles for this surface + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + } + + backEnd.refdef.floatTime = originalTime; + + // draw the contents of the last shader batch + //assert(entityNum < MAX_GENTITIES); + + if (oldShader != NULL) { + RB_EndSurface(); + } + +#ifdef _CRAZY_ATTRIB_DEBUG + qglPopAttrib(); + glState.glStateBits = -1; +#endif + + if (tr_stencilled && tr_distortionPrePost) + { //ok, cap it now + RB_CaptureScreenImage(); + RB_DistortionFill(); + } + + //render distortion surfs (or anything else that needs to be post-rendered) + if (g_numPostRenders > 0) + { + int lastPostEnt = -1; + + while (g_numPostRenders > 0) + { + g_numPostRenders--; + pRender = &g_postRenders[g_numPostRenders]; + + RB_BeginSurface( pRender->shader, pRender->fogNum ); + + backEnd.currentEntity = &backEnd.refdef.entities[pRender->entNum]; + + backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime; + // we have to reset the shaderTime as well otherwise image animations start + // from the wrong frame + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + + // set up the transformation matrix + R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.ori ); + + // set up the dynamic lighting if needed + if ( backEnd.currentEntity->needDlights ) + { + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori ); + } + + qglLoadMatrixf( backEnd.ori.modelMatrix ); + + depthRange = pRender->depthRange; + switch ( depthRange ) + { + default: + case 0: + qglDepthRange (0, 1); + break; + + case 1: + qglDepthRange (0, .3); + break; + + case 2: + qglDepthRange (0, 0); + break; + } + + if (!pRender->eValid) + { + } + else if ((backEnd.refdef.entities[pRender->entNum].e.renderfx & RF_DISTORTION) && + lastPostEnt != pRender->entNum) + { //do the capture now, we only need to do it once per ent + int x, y; + int rad = backEnd.currentEntity->e.radius; + + // Hack - prevent this from using + if( rad > SCREEN_IMAGE_MAX_HEIGHT ) + { +#ifndef FINAL_BUILD + Com_Printf( "WARNING: Shrinking screenImage\n" ); +#endif + rad = SCREEN_IMAGE_MAX_HEIGHT; + } + + //We are going to just bind this, and then the CopyTexImage is going to + //stomp over this texture num in texture memory. + GL_Bind( tr.screenImage ); + + if (R_WorldCoordToScreenCoord( backEnd.currentEntity->e.origin, &x, &y )) + { + int cX, cY; + cX = glConfig.vidWidth-x-(rad/2); + cY = glConfig.vidHeight-y-(rad/2); + +#ifdef _XBOX + cY = 240 - y - (rad / 2); +#endif + + if (cX+rad > glConfig.vidWidth) + { //would it go off screen? + cX = glConfig.vidWidth-rad; + } + else if (cX < 0) + { //cap it off at 0 + cX = 0; + } + +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + { + if (cY+rad > 240) + { + cY = 240 - rad; + } + else if (cY < 0) + { + cY = 0; + } + } + else { +#endif + if (cY+rad > glConfig.vidHeight) + { //would it go off screen? + cY = glConfig.vidHeight-rad; + } + else if (cY < 0) + { //cap it off at 0 + cY = 0; + } +#ifdef _XBOX + } +#endif + + //now copy a portion of the screen to this texture +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue) + qglCopyBackBufferToTexEXT(rad, rad, cX, (backEnd.refdef.y + 240) - cY, (cX + rad), (backEnd.refdef.y + 240) - (cY + rad)); + else + qglCopyBackBufferToTexEXT(rad, rad, cX, (480 - cY), (cX + rad), (480 - (cY + rad))); +#else + qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, cX, cY, rad, rad, 0); +#endif + + lastPostEnt = pRender->entNum; + } + } + + rb_surfaceTable[ *pRender->drawSurf->surface ]( pRender->drawSurf->surface ); + RB_EndSurface(); + } + } + + // go back to the world modelview matrix + qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + if ( depthRange ) { + qglDepthRange (0, 1); + } + +#if 0 + RB_DrawSun(); +#endif + if (tr_stencilled && !tr_distortionPrePost) + { //draw in the stencil buffer's cutout + RB_DistortionFill(); + } +/* + if (!didShadowPass) + { + // darken down any stencil shadows + RB_ShadowFinish(); + didShadowPass = true; + } +*/ +#ifdef _XBOX + if (Cvar_VariableIntegerValue("r_hdreffect")) + { +// HDREffect.Render(); + } +#endif + + // add light flares on lights that aren't obscured + + // rww - 9-13-01 [1-26-01-sof2] +// RB_RenderFlares(); + +#ifdef __MACOS__ + Sys_PumpEvents(); // crutch up the mac's limited buffer queue size +#endif +} + + +/* +============================================================================ + +RENDER BACK END THREAD FUNCTIONS + +============================================================================ +*/ + +/* +================ +RB_SetGL2D + +================ +*/ +#ifdef _XBOX +extern int glcfgWidth, glcfgHeight, glcfgX, glcfgY; +void RB_SetGL2D (void) { + backEnd.projection2D = qtrue; + + if(cg->widescreen) + glcfgWidth = 720; + + // set 2D virtual screen size + qglViewport( glcfgX, glcfgY, glcfgWidth, glcfgHeight ); + qglScissor( glcfgX, glcfgY, glcfgWidth, glcfgHeight ); + qglMatrixMode(GL_PROJECTION); + qglLoadIdentity (); + if(cg->widescreen && !(VM_Call( uivm, UI_IS_FULLSCREEN)) && cls.state == CA_ACTIVE) + qglOrtho (0, 720, 0, 480, 0, 1); + else + qglOrtho (0, 640, 0, 480, 0, 1); + + qglMatrixMode(GL_MODELVIEW); + qglLoadIdentity (); + + GL_State( GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglDisable( GL_CULL_FACE ); + qglDisable( GL_CLIP_PLANE0 ); + + // set time for 2D shaders + backEnd.refdef.time = Sys_Milliseconds()*com_timescale->value; + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001; +} +#else +void RB_SetGL2D (void) { + backEnd.projection2D = qtrue; + + // set 2D virtual screen size + qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglMatrixMode(GL_PROJECTION); + qglLoadIdentity (); + qglOrtho (0, 640, 480, 0, 0, 1); + qglMatrixMode(GL_MODELVIEW); + qglLoadIdentity (); + + GL_State( GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglDisable( GL_CULL_FACE ); + qglDisable( GL_CLIP_PLANE0 ); + + // set time for 2D shaders + backEnd.refdef.time = Sys_Milliseconds()*com_timescale->value; + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f; +} +#endif + + +/* +============= +RE_StretchRaw + +FIXME: not exactly backend +Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. +Used for cinematics. +============= +*/ +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) +{ + assert( 0 ); + return; +/* + int start, end; + + if ( !tr.registered ) { + return; + } + R_SyncRenderThread(); + + // we definately want to sync every frame for the cinematics + qglFinish(); + + start = end = 0; + if ( r_speeds->integer ) { + start = Sys_Milliseconds()*com_timescale->value; + } + + // make sure rows and cols are powers of 2 + if ( (cols&(cols-1)) || (rows&(rows-1)) ) + { + Com_Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows); + } + + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { + tr.scratchImage[client]->width = cols; + tr.scratchImage[client]->height = rows; + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + } else { + if (dirty) { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } + + if ( r_speeds->integer ) { + end = Sys_Milliseconds()*com_timescale->value; + Com_Printf ("qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start ); + } + + RB_SetGL2D(); + + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + + qglBegin (GL_QUADS); + qglTexCoord2f ( 0.5f / cols, 0.5f / rows ); + qglVertex2f (x, y); + qglTexCoord2f ( ( cols - 0.5f ) / cols , 0.5f / rows ); + qglVertex2f (x+w, y); + qglTexCoord2f ( ( cols - 0.5f ) / cols, ( rows - 0.5f ) / rows ); + qglVertex2f (x+w, y+h); + qglTexCoord2f ( 0.5f / cols, ( rows - 0.5f ) / rows ); + qglVertex2f (x, y+h); + qglEnd (); +*/ +} + +void RE_UploadCinematic (int cols, int rows, const byte *data, int client, qboolean dirty) { +/* + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { + tr.scratchImage[client]->width = tr.scratchImage[client]->width = cols; + tr.scratchImage[client]->height = tr.scratchImage[client]->height = rows; +#ifdef _XBOX + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB5, cols, rows, 0, GL_RGB_SWIZZLE_EXT, GL_UNSIGNED_BYTE, data ); +#else + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); +#endif + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + } else { + if (dirty) { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression +#ifdef _XBOX + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGB_SWIZZLE_EXT, GL_UNSIGNED_BYTE, data ); +#else + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); +#endif + } + } +*/ +} + + +/* +============= +RB_SetColor + +============= +*/ +const void *RB_SetColor( const void *data ) { + const setColorCommand_t *cmd; + + cmd = (const setColorCommand_t *)data; + + backEnd.color2D[0] = cmd->color[0] * 255; + backEnd.color2D[1] = cmd->color[1] * 255; + backEnd.color2D[2] = cmd->color[2] * 255; + backEnd.color2D[3] = cmd->color[3] * 255; + + return (const void *)(cmd + 1); +} + +/* +============= +RB_StretchPic +============= +*/ +const void *RB_StretchPic ( const void *data ) { + const stretchPicCommand_t *cmd; + shader_t *shader; + int numVerts, numIndexes; + + cmd = (const stretchPicCommand_t *)data; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + shader = cmd->shader; + if ( shader != tess.shader ) { + if ( tess.numIndexes ) { + RB_EndSurface(); + } + backEnd.currentEntity = &backEnd.entity2D; + RB_BeginSurface( shader, 0 ); + } + + RB_CHECKOVERFLOW( 4, 6 ); + numVerts = tess.numVertexes; + numIndexes = tess.numIndexes; + + tess.numVertexes += 4; + tess.numIndexes += 6; + + tess.indexes[ numIndexes ] = numVerts + 3; + tess.indexes[ numIndexes + 1 ] = numVerts + 0; + tess.indexes[ numIndexes + 2 ] = numVerts + 2; + tess.indexes[ numIndexes + 3 ] = numVerts + 2; + tess.indexes[ numIndexes + 4 ] = numVerts + 0; + tess.indexes[ numIndexes + 5 ] = numVerts + 1; + + *(int *)tess.vertexColors[ numVerts ] = + *(int *)tess.vertexColors[ numVerts + 1 ] = + *(int *)tess.vertexColors[ numVerts + 2 ] = + *(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)backEnd.color2D; + + tess.xyz[ numVerts ][0] = cmd->x; + tess.xyz[ numVerts ][1] = cmd->y; + tess.xyz[ numVerts ][2] = 0; + + tess.texCoords[ numVerts ][0][0] = cmd->s1; + tess.texCoords[ numVerts ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 1 ][1] = cmd->y; + tess.xyz[ numVerts + 1 ][2] = 0; + + tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 2 ][2] = 0; + + tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2; + + tess.xyz[ numVerts + 3 ][0] = cmd->x; + tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 3 ][2] = 0; + + tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1; + tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2; + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawRotatePic +============= +*/ +const void *RB_RotatePic ( const void *data ) +{ + const rotatePicCommand_t *cmd; + image_t *image; + shader_t *shader; + + cmd = (const rotatePicCommand_t *)data; + + shader = cmd->shader; + image = &shader->stages[0].bundle[0].image[0]; + + if ( image ) { + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + qglColor4ubv( backEnd.color2D ); + qglPushMatrix(); + + qglTranslatef(cmd->x+cmd->w,cmd->y,0); + qglRotatef(cmd->a, 0.0, 0.0, 1.0); + + GL_Bind( image ); +#ifdef _XBOX + qglBeginEXT (GL_QUADS, 4, 0, 0, 4, 0); +#else + qglBegin (GL_QUADS); +#endif + qglTexCoord2f( cmd->s1, cmd->t1); + qglVertex2f( -cmd->w, 0 ); + qglTexCoord2f( cmd->s2, cmd->t1 ); + qglVertex2f( 0, 0 ); + qglTexCoord2f( cmd->s2, cmd->t2 ); + qglVertex2f( 0, cmd->h ); + qglTexCoord2f( cmd->s1, cmd->t2 ); + qglVertex2f( -cmd->w, cmd->h ); + qglEnd(); + + qglPopMatrix(); + } + + return (const void *)(cmd + 1); +} + +/* +============= +RB_DrawRotatePic2 +============= +*/ +const void *RB_RotatePic2 ( const void *data ) +{ + const rotatePicCommand_t *cmd; + image_t *image; + shader_t *shader; + + cmd = (const rotatePicCommand_t *)data; + + shader = cmd->shader; + + if ( shader->numUnfoggedPasses ) + { + image = &shader->stages[0].bundle[0].image[0]; + + if ( image ) + { + if ( !backEnd.projection2D ) + { + RB_SetGL2D(); + } + + // Get our current blend mode, etc. + GL_State( shader->stages[0].stateBits ); + + qglColor4ubv( backEnd.color2D ); + qglPushMatrix(); + + // rotation point is going to be around the center of the passed in coordinates + qglTranslatef( cmd->x, cmd->y, 0 ); + qglRotatef( cmd->a, 0.0, 0.0, 1.0 ); + + GL_Bind( image ); +#ifdef _XBOX + qglBeginEXT( GL_QUADS, 4, 0, 0, 4, 0); +#else + qglBegin( GL_QUADS ); +#endif + qglTexCoord2f( cmd->s1, cmd->t1); + qglVertex2f( -cmd->w * 0.5f, -cmd->h * 0.5f ); + + qglTexCoord2f( cmd->s2, cmd->t1 ); + qglVertex2f( cmd->w * 0.5f, -cmd->h * 0.5f ); + + qglTexCoord2f( cmd->s2, cmd->t2 ); + qglVertex2f( cmd->w * 0.5f, cmd->h * 0.5f ); + + qglTexCoord2f( cmd->s1, cmd->t2 ); + qglVertex2f( -cmd->w * 0.5f, cmd->h * 0.5f ); + qglEnd(); + + qglPopMatrix(); + + // Hmmm, this is not too cool + GL_State( GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + } + } + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawSurfs + +============= +*/ +const void *RB_DrawSurfs( const void *data ) { + const drawSurfsCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + cmd = (const drawSurfsCommand_t *)data; + + backEnd.refdef = cmd->refdef; + backEnd.viewParms = cmd->viewParms; + + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + + // Dynamic Glow/Flares: + /* + The basic idea is to render the glowing parts of the scene to an offscreen buffer, then take + that buffer and blur it. After it is sufficiently blurred, re-apply that image back to + the normal screen using a additive blending. To blur the scene I use a vertex program to supply + four texture coordinate offsets that allow 'peeking' into adjacent pixels. In the register + combiner (pixel shader), I combine the adjacent pixels using a weighting factor. - Aurelio + */ + + // Render dynamic glowing/flaring objects. +#ifndef _XBOX // GLOWXXX + if ( !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && g_bDynamicGlowSupported && r_DynamicGlow->integer ) + { + // Copy the normal scene to texture. + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.sceneImage ); + qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + + // Just clear colors, but leave the depth buffer intact so we can 'share' it. + qglClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); + qglClear( GL_COLOR_BUFFER_BIT ); + + // Render the glowing objects. + g_bRenderGlowingObjects = true; + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + g_bRenderGlowingObjects = false; + + qglFinish(); + + // Copy the glow scene to texture. + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.screenGlow ); + qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + + // Resize the viewport to the blur texture size. + const int oldViewWidth = backEnd.viewParms.viewportWidth; + const int oldViewHeight = backEnd.viewParms.viewportHeight; + backEnd.viewParms.viewportWidth = r_DynamicGlowWidth->integer; + backEnd.viewParms.viewportHeight = r_DynamicGlowHeight->integer; + SetViewportAndScissor(); + + // Blur the scene. + RB_BlurGlowTexture(); + + // Copy the finished glow scene back to texture. + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage ); + qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + + // Set the viewport back to normal. + backEnd.viewParms.viewportWidth = oldViewWidth; + backEnd.viewParms.viewportHeight = oldViewHeight; + SetViewportAndScissor(); + qglClear( GL_COLOR_BUFFER_BIT ); + + // Draw the glow additively over the screen. + RB_DrawGlowOverlay(); + } +#endif // _XBOX + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawBuffer + +============= +*/ +const void *RB_DrawBuffer( const void *data ) { + const drawBufferCommand_t *cmd; + + cmd = (const drawBufferCommand_t *)data; + + qglDrawBuffer( cmd->buffer ); + + return (const void *)(cmd + 1); +} + +/* +=============== +RB_ShowImages + +Draw all the images to the screen, on top of whatever +was there. This is used to test for texture thrashing. + +Also called by RE_EndRegistration +=============== +*/ +void RB_ShowImages( void ) { + image_t *image; + float x, y, w, h; +// int start, end; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + qglClear( GL_COLOR_BUFFER_BIT ); + + qglFinish(); + +// start = Sys_Milliseconds()*com_timescale->value; + + + int i=0; + R_Images_StartIteration(); + while ( (image = R_Images_GetNextIteration()) != NULL) + { + w = glConfig.vidWidth / 20; + h = glConfig.vidHeight / 15; + x = i % 20 * w; + y = i / 20 * h; + + // show in proportional size in mode 2 + if ( r_showImages->integer == 2 ) { + w *= image->width / 512.0; + h *= image->height / 512.0; + } + + GL_Bind( image ); +#ifdef _XBOX + qglBeginEXT (GL_QUADS, 4, 0, 0, 4, 0); +#else + qglBegin (GL_QUADS); +#endif + qglTexCoord2f( 0, 0 ); + qglVertex2f( x, y ); + qglTexCoord2f( 1, 0 ); + qglVertex2f( x + w, y ); + qglTexCoord2f( 1, 1 ); + qglVertex2f( x + w, y + h ); + qglTexCoord2f( 0, 1 ); + qglVertex2f( x, y + h ); + qglEnd(); + i++; + } + + qglFinish(); + +// end = Sys_Milliseconds()*com_timescale->value; +// Com_Printf ("%i msec to draw all images\n", end - start ); +} + + +/* +============= +RB_SwapBuffers + +============= +*/ +const void *RB_SwapBuffers( const void *data ) { + const swapBuffersCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + // texture swapping test + if ( r_showImages->integer ) { + RB_ShowImages(); + } + + cmd = (const swapBuffersCommand_t *)data; + + // we measure overdraw by reading back the stencil buffer and + // counting up the number of increments that have happened +#ifndef _XBOX + if ( r_measureOverdraw->integer ) { + int i; + long sum = 0; + unsigned char *stencilReadback; + + stencilReadback = (unsigned char *)Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight ); + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) { + sum += stencilReadback[i]; + } + + backEnd.pc.c_overDraw += sum; + Hunk_FreeTempMemory( stencilReadback ); + } +#endif + + if ( !glState.finishCalled ) { + qglFinish(); + } + + GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" ); + + GLimp_EndFrame(); + + backEnd.projection2D = qfalse; + + return (const void *)(cmd + 1); +} + +const void *RB_WorldEffects( const void *data ) +{ + const drawBufferCommand_t *cmd; + + cmd = (const drawBufferCommand_t *)data; + + // Always flush the tess buffer + if ( tess.shader && tess.numIndexes ) + { + RB_EndSurface(); + } + RB_RenderWorldEffects(); + + if(tess.shader) + { + RB_BeginSurface( tess.shader, tess.fogNum ); + } + + return (const void *)(cmd + 1); +} + +/* +==================== +RB_ExecuteRenderCommands + +This function will be called syncronously if running without +smp extensions, or asyncronously by another thread. +==================== +*/ +extern const void *R_DrawWireframeAutomap(const void *data); //tr_world.cpp +void RB_ExecuteRenderCommands( const void *data ) { + int t1, t2; + + t1 = Sys_Milliseconds()*com_timescale->value; + + while ( 1 ) { + switch ( *(const int *)data ) { + case RC_SET_COLOR: + data = RB_SetColor( data ); + break; + case RC_STRETCH_PIC: + data = RB_StretchPic( data ); + break; + case RC_ROTATE_PIC: + data = RB_RotatePic( data ); + break; + case RC_ROTATE_PIC2: + data = RB_RotatePic2( data ); + break; + case RC_DRAW_SURFS: + data = RB_DrawSurfs( data ); + break; + case RC_DRAW_BUFFER: + data = RB_DrawBuffer( data ); + break; + case RC_SWAP_BUFFERS: + data = RB_SwapBuffers( data ); + break; + case RC_WORLD_EFFECTS: + data = RB_WorldEffects( data ); + break; + case RC_AUTO_MAP: + data = R_DrawWireframeAutomap(data); + break; + case RC_END_OF_LIST: + default: + // stop rendering on this thread + t2 = Sys_Milliseconds()*com_timescale->value; + backEnd.pc.msec = t2 - t1; + return; + } + } + +} + +#ifndef _XBOX // GLOWXXX + +// What Pixel Shader type is currently active (regcoms or fragment programs). +GLuint g_uiCurrentPixelShaderType = 0x0; + +// Begin using a Pixel Shader. +void BeginPixelShader( GLuint uiType, GLuint uiID ) +{ + switch ( uiType ) + { + // Using Register Combiners, so call the Display List that stores it. + case GL_REGISTER_COMBINERS_NV: + { + // Just in case... + if ( !qglCombinerParameterfvNV ) + return; + + // Call the list with the regcom in it. + qglEnable( GL_REGISTER_COMBINERS_NV ); + qglCallList( uiID ); + + g_uiCurrentPixelShaderType = GL_REGISTER_COMBINERS_NV; + } + return; + + // Using Fragment Programs, so call the program. + case GL_FRAGMENT_PROGRAM_ARB: + { + // Just in case... + if ( !qglGenProgramsARB ) + return; + + qglEnable( GL_FRAGMENT_PROGRAM_ARB ); + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, uiID ); + + g_uiCurrentPixelShaderType = GL_FRAGMENT_PROGRAM_ARB; + } + return; + } +} + +// Stop using a Pixel Shader and return states to normal. +void EndPixelShader() +{ + if ( g_uiCurrentPixelShaderType == 0x0 ) + return; + + qglDisable( g_uiCurrentPixelShaderType ); +} + +// Hack variable for deciding which kind of texture rectangle thing to do (for some +// reason it acts different on radeon! It's against the spec!). +extern bool g_bTextureRectangleHack; + +static inline void RB_BlurGlowTexture() +{ + qglDisable (GL_CLIP_PLANE0); + GL_Cull( CT_TWO_SIDED ); + qglDisable( GL_DEPTH_TEST ); + + // Go into orthographic 2d mode. + qglMatrixMode(GL_PROJECTION); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho(0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight, 0, -1, 1); + qglMatrixMode(GL_MODELVIEW); + qglPushMatrix(); + qglLoadIdentity(); + + GL_State(0); + + ///////////////////////////////////////////////////////// + // Setup vertex and pixel programs. + ///////////////////////////////////////////////////////// + + // NOTE: The 0.25 is because we're blending 4 textures (so = 1.0) and we want a relatively normalized pixel + // intensity distribution, but this won't happen anyways if intensity is higher than 1.0. + float fBlurDistribution = r_DynamicGlowIntensity->value * 0.25f; + float fBlurWeight[4] = { fBlurDistribution, fBlurDistribution, fBlurDistribution, 1.0f }; + + // Enable and set the Vertex Program. + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, tr.glowVShader ); + + // Apply Pixel Shaders. + if ( qglCombinerParameterfvNV ) + { + BeginPixelShader( GL_REGISTER_COMBINERS_NV, tr.glowPShader ); + + // Pass the blur weight to the regcom. + qglCombinerParameterfvNV( GL_CONSTANT_COLOR0_NV, (float*)&fBlurWeight ); + } + else if ( qglProgramEnvParameter4fARB ) + { + BeginPixelShader( GL_FRAGMENT_PROGRAM_ARB, tr.glowPShader ); + + // Pass the blur weight to the Fragment Program. + qglProgramEnvParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 0, fBlurWeight[0], fBlurWeight[1], fBlurWeight[2], fBlurWeight[3] ); + } + + ///////////////////////////////////////////////////////// + // Set the blur texture to the 4 texture stages. + ///////////////////////////////////////////////////////// + + // How much to offset each texel by. + float fTexelWidthOffset = 0.1f, fTexelHeightOffset = 0.1f; + + GLuint uiTex = tr.screenGlow; + + qglActiveTextureARB( GL_TEXTURE3_ARB ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + + qglActiveTextureARB( GL_TEXTURE2_ARB ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + + qglActiveTextureARB( GL_TEXTURE1_ARB ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + + qglActiveTextureARB(GL_TEXTURE0_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + + ///////////////////////////////////////////////////////// + // Draw the blur passes (each pass blurs it more, increasing the blur radius ). + ///////////////////////////////////////////////////////// + + //int iTexWidth = backEnd.viewParms.viewportWidth, iTexHeight = backEnd.viewParms.viewportHeight; + int iTexWidth = glConfig.vidWidth, iTexHeight = glConfig.vidHeight; + + for ( int iNumBlurPasses = 0; iNumBlurPasses < r_DynamicGlowPasses->integer; iNumBlurPasses++ ) + { + // Load the Texel Offsets into the Vertex Program. + qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 0, -fTexelWidthOffset, -fTexelWidthOffset, 0.0f, 0.0f ); + qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 1, -fTexelWidthOffset, fTexelWidthOffset, 0.0f, 0.0f ); + qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 2, fTexelWidthOffset, -fTexelWidthOffset, 0.0f, 0.0f ); + qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 3, fTexelWidthOffset, fTexelWidthOffset, 0.0f, 0.0f ); + + // After first pass put the tex coords to the viewport size. + if ( iNumBlurPasses == 1 ) + { + // OK, very weird, but dependent on which texture rectangle extension we're using, the + // texture either needs to be always texure correct or view correct... + if ( !g_bTextureRectangleHack ) + { + iTexWidth = backEnd.viewParms.viewportWidth; + iTexHeight = backEnd.viewParms.viewportHeight; + } + + uiTex = tr.blurImage; + qglActiveTextureARB( GL_TEXTURE3_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + qglActiveTextureARB( GL_TEXTURE2_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + qglActiveTextureARB( GL_TEXTURE1_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + qglActiveTextureARB(GL_TEXTURE0_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + + // Copy the current image over. + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + } + + // Draw the fullscreen quad. + qglBegin( GL_QUADS ); + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0, iTexHeight ); + qglVertex2f( 0, 0 ); + + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0, 0 ); + qglVertex2f( 0, backEnd.viewParms.viewportHeight ); + + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, iTexWidth, 0 ); + qglVertex2f( backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, iTexWidth, iTexHeight ); + qglVertex2f( backEnd.viewParms.viewportWidth, 0 ); + qglEnd(); + + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage ); + qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + + // Increase the texel offsets. + // NOTE: This is possibly the most important input to the effect. Even by using an exponential function I've been able to + // make it look better (at a much higher cost of course). This is cheap though and still looks pretty great. In the future + // I might want to use an actual gaussian equation to correctly calculate the pixel coefficients and attenuates, texel + // offsets, gaussian amplitude and radius... + fTexelWidthOffset += r_DynamicGlowDelta->value; + fTexelHeightOffset += r_DynamicGlowDelta->value; + } + + // Disable multi-texturing. + qglActiveTextureARB( GL_TEXTURE3_ARB ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + + qglActiveTextureARB( GL_TEXTURE2_ARB ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + + qglActiveTextureARB( GL_TEXTURE1_ARB ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + + qglActiveTextureARB(GL_TEXTURE0_ARB ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + + qglDisable( GL_VERTEX_PROGRAM_ARB ); + EndPixelShader(); + + qglMatrixMode(GL_PROJECTION); + qglPopMatrix(); + qglMatrixMode(GL_MODELVIEW); + qglPopMatrix(); + + qglDisable( GL_BLEND ); + qglEnable( GL_DEPTH_TEST ); + + glState.currenttmu = 0; //this matches the last one we activated +} + +// Draw the glow blur over the screen additively. +static inline void RB_DrawGlowOverlay() +{ + qglDisable (GL_CLIP_PLANE0); + GL_Cull( CT_TWO_SIDED ); + qglDisable( GL_DEPTH_TEST ); + + // Go into orthographic 2d mode. + qglMatrixMode(GL_PROJECTION); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho(0, glConfig.vidWidth, glConfig.vidHeight, 0, -1, 1); + qglMatrixMode(GL_MODELVIEW); + qglPushMatrix(); + qglLoadIdentity(); + + GL_State(0); + + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + + // For debug purposes. + if ( r_DynamicGlow->integer != 2 ) + { + // Render the normal scene texture. + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.sceneImage ); + qglBegin(GL_QUADS); + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + qglTexCoord2f( 0, glConfig.vidHeight ); + qglVertex2f( 0, 0 ); + + qglTexCoord2f( 0, 0 ); + qglVertex2f( 0, glConfig.vidHeight ); + + qglTexCoord2f( glConfig.vidWidth, 0 ); + qglVertex2f( glConfig.vidWidth, glConfig.vidHeight ); + + qglTexCoord2f( glConfig.vidWidth, glConfig.vidHeight ); + qglVertex2f( glConfig.vidWidth, 0 ); + qglEnd(); + } + + // One and Inverse Src Color give a very soft addition, while one one is a bit stronger. With one one we can + // use additive blending through multitexture though. + if ( r_DynamicGlowSoft->integer ) + { + qglBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_COLOR ); + } + else + { + qglBlendFunc( GL_ONE, GL_ONE ); + } + qglEnable( GL_BLEND ); + + // Now additively render the glow texture. + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage ); + qglBegin(GL_QUADS); + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + qglTexCoord2f( 0, r_DynamicGlowHeight->integer ); + qglVertex2f( 0, 0 ); + + qglTexCoord2f( 0, 0 ); + qglVertex2f( 0, glConfig.vidHeight ); + + qglTexCoord2f( r_DynamicGlowWidth->integer, 0 ); + qglVertex2f( glConfig.vidWidth, glConfig.vidHeight ); + + qglTexCoord2f( r_DynamicGlowWidth->integer, r_DynamicGlowHeight->integer ); + qglVertex2f( glConfig.vidWidth, 0 ); + qglEnd(); + + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + qglBlendFunc( GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR ); + qglDisable( GL_BLEND ); + + // NOTE: Multi-texture wasn't that much faster (we're obviously not bottlenecked by transform pipeline), + // and besides, soft glow looks better anyways. +/* else + { + int iTexWidth = glConfig.vidWidth, iTexHeight = glConfig.vidHeight; + if ( GL_TEXTURE_RECTANGLE_EXT == GL_TEXTURE_RECTANGLE_NV ) + { + iTexWidth = r_DynamicGlowWidth->integer; + iTexHeight = r_DynamicGlowHeight->integer; + } + + qglActiveTextureARB( GL_TEXTURE1_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.screenGlow ); + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); + + qglActiveTextureARB(GL_TEXTURE0_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.sceneImage ); + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); + + qglBegin(GL_QUADS); + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, 0, iTexHeight ); + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0, glConfig.vidHeight ); + qglVertex2f( 0, 0 ); + + qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, 0, 0 ); + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0, 0 ); + qglVertex2f( 0, glConfig.vidHeight ); + + qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, iTexWidth, 0 ); + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, glConfig.vidWidth, 0 ); + qglVertex2f( glConfig.vidWidth, glConfig.vidHeight ); + + qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, iTexWidth, iTexHeight ); + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, glConfig.vidWidth, glConfig.vidHeight ); + qglVertex2f( glConfig.vidWidth, 0 ); + qglEnd(); + + qglActiveTextureARB( GL_TEXTURE1_ARB ); + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + + qglActiveTextureARB(GL_TEXTURE0_ARB ); + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + }*/ + + qglMatrixMode(GL_PROJECTION); + qglPopMatrix(); + qglMatrixMode(GL_MODELVIEW); + qglPopMatrix(); + + qglEnable( GL_DEPTH_TEST ); +} +#endif //XBOX + +#endif //!DEDICATED diff --git a/codemp/renderer/tr_bsp.cpp b/codemp/renderer/tr_bsp.cpp new file mode 100644 index 0000000..02b4db0 --- /dev/null +++ b/codemp/renderer/tr_bsp.cpp @@ -0,0 +1,2123 @@ +// tr_map.c +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +/* + +Loads and prepares a map file for scene rendering. + +A single entry point: + +void RE_LoadWorldMap( const char *name ); + +*/ + +static world_t s_worldData; +static byte *fileBase; + +int c_subdivisions; +int c_gridVerts; + +void R_RMGInit(void); + +//=============================================================================== + +static void HSVtoRGB( float h, float s, float v, float rgb[3] ) +{ + int i; + float f; + float p, q, t; + + h *= 5; + + i = floor( h ); + f = h - i; + + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch ( i ) + { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) { //rwwRMG - modified + int shift=0, r, g, b; + + // should NOT do it if overbrightBits is 0 + if (tr.overbrightBits) + shift = 1 - tr.overbrightBits; + + if (!shift) + { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; + return; + } + + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = in[3]; +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +static void R_ColorShiftLightingBytes( byte in[3]) +{ + int shift=0, r, g, b; + + // should NOT do it if overbrightBits is 0 + if (tr.overbrightBits) + shift = 1 - tr.overbrightBits; + + if (!shift) + { + return; + } + + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + in[0] = r; + in[1] = g; + in[2] = b; +} + +/* +=============== +R_LoadLightmaps + +=============== +*/ +#define LIGHTMAP_SIZE 128 +static void R_LoadLightmaps( lump_t *l, const char *psMapName, world_t &worldData ) { + byte *buf, *buf_p; + int len; + MAC_STATIC byte image[LIGHTMAP_SIZE*LIGHTMAP_SIZE*4]; + int i, j; + float maxIntensity = 0; + double sumIntensity = 0; + + if (&worldData == &s_worldData) + { + tr.numLightmaps = 0; + } + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + // we are about to upload textures + R_SyncRenderThread(); + + // create all the lightmaps + tr.numLightmaps = len / (LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3); + + // if we are in r_vertexLight mode, we don't need the lightmaps at all + if ( r_vertexLight->integer ) { + return; + } + + char sMapName[MAX_QPATH]; + COM_StripExtension(psMapName,sMapName); // will already by MAX_QPATH legal, so no length check + + for ( i = 0 ; i < tr.numLightmaps ; i++ ) { + // expand the 24 bit on-disk to 32 bit + buf_p = buf + i * LIGHTMAP_SIZE*LIGHTMAP_SIZE * 3; + + if ( r_lightmap->integer == 2 ) + { // color code by intensity as development tool (FIXME: check range) + for ( j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) + { + float r = buf_p[j*3+0]; + float g = buf_p[j*3+1]; + float b = buf_p[j*3+2]; + float intensity; + float out[3]; + + intensity = 0.33f * r + 0.685f * g + 0.063f * b; + + if ( intensity > 255 ) + intensity = 1.0f; + else + intensity /= 255.0f; + + if ( intensity > maxIntensity ) + maxIntensity = intensity; + + HSVtoRGB( intensity, 1.00, 0.50, out ); + + image[j*4+0] = out[0] * 255; + image[j*4+1] = out[1] * 255; + image[j*4+2] = out[2] * 255; + image[j*4+3] = 255; + + sumIntensity += intensity; + } + } else { + for ( j = 0 ; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) { + R_ColorShiftLightingBytes( &buf_p[j*3], &image[j*4] ); + image[j*4+3] = 255; + } + } + tr.lightmaps[i] = R_CreateImage( va("*%s/lightmap%d",sMapName,i), image, + LIGHTMAP_SIZE, LIGHTMAP_SIZE, GL_RGBA, qfalse, qfalse, (qboolean)r_ext_compressed_lightmaps->integer, GL_CLAMP ); + } + + if ( r_lightmap->integer == 2 ) { + Com_Printf ("Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) ); + } +} + + +/* +================= +RE_SetWorldVisData + +This is called by the clipmodel subsystem so we can share the 1.8 megs of +space in big maps... +================= +*/ +void RE_SetWorldVisData( const byte *vis ) { + tr.externalVisData = vis; +} + + +/* +================= +R_LoadVisibility +================= +*/ +static void R_LoadVisibility( lump_t *l, world_t &worldData ) { + int len; + byte *buf; + + len = ( worldData.numClusters + 63 ) & ~63; + worldData.novis = (unsigned char *)Hunk_Alloc( len, h_low ); + Com_Memset( worldData.novis, 0xff, len ); + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + worldData.numClusters = LittleLong( ((int *)buf)[0] ); + worldData.clusterBytes = LittleLong( ((int *)buf)[1] ); + + // CM_Load should have given us the vis data to share, so + // we don't need to allocate another copy + if ( tr.externalVisData ) { + worldData.vis = tr.externalVisData; + } else { + byte *dest; + + dest = (unsigned char *)Hunk_Alloc( len - 8, h_low ); + Com_Memcpy( dest, buf + 8, len - 8 ); + worldData.vis = dest; + } +} + +//=============================================================================== + +qhandle_t R_GetShaderByNum(int shaderNum, world_t &worldData) +{ + qhandle_t shader; + + if ( (shaderNum < 0) || (shaderNum >= worldData.numShaders) ) + { + Com_Printf( "Warning: Bad index for R_GetShaderByNum - %i", shaderNum ); + return(0); + } + shader = RE_RegisterShader(worldData.shaders[ shaderNum ].shader); + return(shader); +} + +/* +=============== +ShaderForShaderNum +=============== +*/ +static shader_t *ShaderForShaderNum( int shaderNum, const int *lightmapNum, const byte *lightmapStyles, const byte *vertexStyles, world_t &worldData ) +{ + shader_t *shader; + dshader_t *dsh; + const byte *styles; + + styles = lightmapStyles; + + shaderNum = LittleLong( shaderNum ); + if ( shaderNum < 0 || shaderNum >= worldData.numShaders ) { + Com_Error( ERR_DROP, "ShaderForShaderNum: bad num %i", shaderNum ); + } + dsh = &worldData.shaders[ shaderNum ]; + + if (lightmapNum[0] == LIGHTMAP_BY_VERTEX) + { + styles = vertexStyles; + } + + if ( r_vertexLight->integer ) + { + lightmapNum = lightmapsVertex; + styles = vertexStyles; + } + + shader = R_FindShader( dsh->shader, lightmapNum, styles, qtrue ); + + // if the shader had errors, just use default shader + if ( shader->defaultShader ) { + return tr.defaultShader; + } + + return shader; +} + +/* +=============== +ParseFace +=============== +*/ +static void ParseFace( dsurface_t *ds, mapVert_t *verts, msurface_t *surf, int *indexes, world_t &worldData, int index ) { + int i, j, k; + srfSurfaceFace_t *cv; + int numPoints, numIndexes; + int lightmapNum[MAXLIGHTMAPS]; + int sfaceSize, ofsIndexes; + + for(i = 0; i < MAXLIGHTMAPS; i++) + { + lightmapNum[i] = LittleLong( ds->lightmapNum[i] ); + } + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + if (index && !surf->fogIndex && tr.world->globalFog != -1) + { + surf->fogIndex = worldData.globalFog; + } + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum, ds->lightmapStyles, ds->vertexStyles, worldData ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + numPoints = LittleLong( ds->numVerts ); + if (numPoints > MAX_FACE_POINTS) { + Com_Printf (S_COLOR_YELLOW "WARNING: MAX_FACE_POINTS exceeded: %i\n", numPoints); + numPoints = MAX_FACE_POINTS; + surf->shader = tr.defaultShader; + } + + numIndexes = LittleLong( ds->numIndexes ); + + // create the srfSurfaceFace_t + sfaceSize = ( int ) &((srfSurfaceFace_t *)0)->points[numPoints]; + ofsIndexes = sfaceSize; + sfaceSize += sizeof( int ) * numIndexes; + + cv = (srfSurfaceFace_t *)Hunk_Alloc( sfaceSize, h_low ); + cv->surfaceType = SF_FACE; + cv->numPoints = numPoints; + cv->numIndices = numIndexes; + cv->ofsIndices = ofsIndexes; + + verts += LittleLong( ds->firstVert ); + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + cv->points[i][j] = LittleFloat( verts[i].xyz[j] ); + } + for ( j = 0 ; j < 2 ; j++ ) { + cv->points[i][3+j] = LittleFloat( verts[i].st[j] ); + for(k=0;kpoints[i][VERTEX_LM+j+(k*2)] = LittleFloat( verts[i].lightmap[k][j] ); + } + } + for(k=0;kpoints[i][VERTEX_COLOR+k] ); + } + } + + indexes += LittleLong( ds->firstIndex ); + for ( i = 0 ; i < numIndexes ; i++ ) { + ((int *)((byte *)cv + cv->ofsIndices ))[i] = LittleLong( indexes[ i ] ); + } + + // take the plane information from the lightmap vector + for ( i = 0 ; i < 3 ; i++ ) { + cv->plane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } + cv->plane.dist = DotProduct( cv->points[0], cv->plane.normal ); + SetPlaneSignbits( &cv->plane ); + cv->plane.type = PlaneTypeForNormal( cv->plane.normal ); + + surf->data = (surfaceType_t *)cv; +} + + +/* +=============== +ParseMesh +=============== +*/ +static void ParseMesh ( dsurface_t *ds, mapVert_t *verts, msurface_t *surf, world_t &worldData, int index ) { + srfGridMesh_t *grid; + int i, j, k; + int width, height, numPoints; + MAC_STATIC drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE]; + int lightmapNum[MAXLIGHTMAPS]; + vec3_t bounds[2]; + vec3_t tmpVec; + static surfaceType_t skipData = SF_SKIP; + + for(i=0;ilightmapNum[i] ); + } + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + if (index && !surf->fogIndex && tr.world->globalFog != -1) + { + surf->fogIndex = worldData.globalFog; + } + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum, ds->lightmapStyles, ds->vertexStyles, worldData ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + // we may have a nodraw surface, because they might still need to + // be around for movement clipping + if ( worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) { + surf->data = &skipData; + return; + } + + width = LittleLong( ds->patchWidth ); + height = LittleLong( ds->patchHeight ); + + verts += LittleLong( ds->firstVert ); + numPoints = width * height; + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + points[i].xyz[j] = LittleFloat( verts[i].xyz[j] ); + points[i].normal[j] = LittleFloat( verts[i].normal[j] ); + } + for ( j = 0 ; j < 2 ; j++ ) { + points[i].st[j] = LittleFloat( verts[i].st[j] ); + for(k=0;kdata = (surfaceType_t *)grid; + + // copy the level of detail origin, which is the center + // of the group of all curves that must subdivide the same + // to avoid cracking + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] ); + bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] ); + } + VectorAdd( bounds[0], bounds[1], bounds[1] ); + VectorScale( bounds[1], 0.5f, grid->lodOrigin ); + VectorSubtract( bounds[0], grid->lodOrigin, tmpVec ); + grid->lodRadius = VectorLength( tmpVec ); +} + +/* +=============== +ParseTriSurf +=============== +*/ +static void ParseTriSurf( dsurface_t *ds, mapVert_t *verts, msurface_t *surf, int *indexes, world_t &worldData, int index ) { + srfTriangles_t *tri; + int i, j, k; + int numVerts, numIndexes; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + if (index && !surf->fogIndex && tr.world->globalFog != -1) + { + surf->fogIndex = worldData.globalFog; + } + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapsVertex, ds->lightmapStyles, ds->vertexStyles, worldData ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + numVerts = LittleLong( ds->numVerts ); + numIndexes = LittleLong( ds->numIndexes ); + + if ( numVerts >= SHADER_MAX_VERTEXES ) { + Com_Error(ERR_DROP, "ParseTriSurf: verts > MAX (%d > %d) on misc_model %s", numVerts, SHADER_MAX_VERTEXES, surf->shader->name ); + } + if ( numIndexes >= SHADER_MAX_INDEXES ) { + Com_Error(ERR_DROP, "ParseTriSurf: indices > MAX (%d > %d) on misc_model %s", numIndexes, SHADER_MAX_INDEXES, surf->shader->name ); + } + + tri = (srfTriangles_t *)Hunk_Alloc( sizeof( *tri ) + numVerts * sizeof( tri->verts[0] ) + + numIndexes * sizeof( tri->indexes[0] ), h_low ); + tri->surfaceType = SF_TRIANGLES; + tri->numVerts = numVerts; + tri->numIndexes = numIndexes; + tri->verts = (drawVert_t *)(tri + 1); + tri->indexes = (int *)(tri->verts + tri->numVerts ); + + surf->data = (surfaceType_t *)tri; + + // copy vertexes + ClearBounds( tri->bounds[0], tri->bounds[1] ); + verts += LittleLong( ds->firstVert ); + for ( i = 0 ; i < numVerts ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + tri->verts[i].xyz[j] = LittleFloat( verts[i].xyz[j] ); + tri->verts[i].normal[j] = LittleFloat( verts[i].normal[j] ); + } + AddPointToBounds( tri->verts[i].xyz, tri->bounds[0], tri->bounds[1] ); + for ( j = 0 ; j < 2 ; j++ ) { + tri->verts[i].st[j] = LittleFloat( verts[i].st[j] ); + for(k=0;kverts[i].lightmap[k][j] = LittleFloat( verts[i].lightmap[k][j] ); + } + } + + for(k=0;kverts[i].color[k] ); + } + } + + // copy indexes + indexes += LittleLong( ds->firstIndex ); + for ( i = 0 ; i < numIndexes ; i++ ) { + tri->indexes[i] = LittleLong( indexes[i] ); + if ( tri->indexes[i] < 0 || tri->indexes[i] >= numVerts ) { + Com_Error( ERR_DROP, "Bad index in triangle surface" ); + } + } +} + +/* +=============== +ParseFlare +=============== +*/ +static void ParseFlare( dsurface_t *ds, mapVert_t *verts, msurface_t *surf, int *indexes, world_t &worldData, int index ) { + srfFlare_t *flare; + int i; + int lightmaps[MAXLIGHTMAPS] = { LIGHTMAP_BY_VERTEX }; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + if (index && !surf->fogIndex && tr.world->globalFog != -1) + { + surf->fogIndex = worldData.globalFog; + } + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmaps, ds->lightmapStyles, ds->vertexStyles, worldData ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + flare = (struct srfFlare_s *)Hunk_Alloc( sizeof( *flare ), h_low ); + flare->surfaceType = SF_FLARE; + + surf->data = (surfaceType_t *)flare; + + for ( i = 0 ; i < 3 ; i++ ) { + flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] ); + flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] ); + flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } +} + + +/* +================= +R_MergedWidthPoints + +returns true if there are grid points merged on a width edge +================= +*/ +int R_MergedWidthPoints(srfGridMesh_t *grid, int offset) { + int i, j; + + for (i = 1; i < grid->width-1; i++) { + for (j = i + 1; j < grid->width-1; j++) { + if ( fabs(grid->verts[i + offset].xyz[0] - grid->verts[j + offset].xyz[0]) > .1) continue; + if ( fabs(grid->verts[i + offset].xyz[1] - grid->verts[j + offset].xyz[1]) > .1) continue; + if ( fabs(grid->verts[i + offset].xyz[2] - grid->verts[j + offset].xyz[2]) > .1) continue; + return qtrue; + } + } + return qfalse; +} + +/* +================= +R_MergedHeightPoints + +returns true if there are grid points merged on a height edge +================= +*/ +int R_MergedHeightPoints(srfGridMesh_t *grid, int offset) { + int i, j; + + for (i = 1; i < grid->height-1; i++) { + for (j = i + 1; j < grid->height-1; j++) { + if ( fabs(grid->verts[grid->width * i + offset].xyz[0] - grid->verts[grid->width * j + offset].xyz[0]) > .1) continue; + if ( fabs(grid->verts[grid->width * i + offset].xyz[1] - grid->verts[grid->width * j + offset].xyz[1]) > .1) continue; + if ( fabs(grid->verts[grid->width * i + offset].xyz[2] - grid->verts[grid->width * j + offset].xyz[2]) > .1) continue; + return qtrue; + } + } + return qfalse; +} + +/* +================= +R_FixSharedVertexLodError_r + +NOTE: never sync LoD through grid edges with merged points! + +FIXME: write generalized version that also avoids cracks between a patch and one that meets half way? +================= +*/ +void R_FixSharedVertexLodError_r( int start, srfGridMesh_t *grid1, world_t &worldData ) { + int j, k, l, m, n, offset1, offset2, touch; + srfGridMesh_t *grid2; + + for ( j = start; j < worldData.numsurfaces; j++ ) { + // + grid2 = (srfGridMesh_t *) worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) continue; + // if the LOD errors are already fixed for this patch + if ( grid2->lodFixed == 2 ) continue; + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) continue; + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue; + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue; + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue; + // + touch = qfalse; + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) continue; + for (k = 1; k < grid1->width-1; k++) { + for (m = 0; m < 2; m++) { + + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + if (R_MergedWidthPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->width-1; l++) { + // + if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->widthLodError[k]; + touch = qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (m) offset2 = grid2->width-1; + else offset2 = 0; + if (R_MergedHeightPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->height-1; l++) { + // + if ( fabs(grid1->verts[k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->widthLodError[k]; + touch = qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) continue; + for (k = 1; k < grid1->height-1; k++) { + for (m = 0; m < 2; m++) { + + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + if (R_MergedWidthPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->width-1; l++) { + // + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->widthLodError[l] = grid1->heightLodError[k]; + touch = qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (m) offset2 = grid2->width-1; + else offset2 = 0; + if (R_MergedHeightPoints(grid2, offset2)) continue; + for ( l = 1; l < grid2->height-1; l++) { + // + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[0] - grid2->verts[grid2->width * l + offset2].xyz[0]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[1] - grid2->verts[grid2->width * l + offset2].xyz[1]) > .1) continue; + if ( fabs(grid1->verts[grid1->width * k + offset1].xyz[2] - grid2->verts[grid2->width * l + offset2].xyz[2]) > .1) continue; + // ok the points are equal and should have the same lod error + grid2->heightLodError[l] = grid1->heightLodError[k]; + touch = qtrue; + } + } + } + } + if (touch) { + grid2->lodFixed = 2; + R_FixSharedVertexLodError_r ( start, grid2, worldData ); + //NOTE: this would be correct but makes things really slow + //grid2->lodFixed = 1; + } + } +} + +/* +================= +R_FixSharedVertexLodError + +This function assumes that all patches in one group are nicely stitched together for the highest LoD. +If this is not the case this function will still do its job but won't fix the highest LoD cracks. +================= +*/ +void R_FixSharedVertexLodError( world_t &worldData ) { + int i; + srfGridMesh_t *grid1; + + for ( i = 0; i < worldData.numsurfaces; i++ ) { + // + grid1 = (srfGridMesh_t *) worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) + continue; + // + if ( grid1->lodFixed ) + continue; + // + grid1->lodFixed = 2; + // recursively fix other patches in the same LOD group + R_FixSharedVertexLodError_r( i + 1, grid1, worldData); + } +} + + +/* +=============== +R_StitchPatches +=============== +*/ +int R_StitchPatches( int grid1num, int grid2num, world_t &worldData ) { + int k, l, m, n, offset1, offset2, row, column; + srfGridMesh_t *grid1, *grid2; + float *v1, *v2; + + grid1 = (srfGridMesh_t *) worldData.surfaces[grid1num].data; + grid2 = (srfGridMesh_t *) worldData.surfaces[grid2num].data; + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) + continue; + for (k = 0; k < grid1->width-2; k += 2) { + + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + //if (R_MergedWidthPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //Com_Printf ("found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = qfalse; + worldData.surfaces[grid2num].data = (surfaceType_t *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + //if (R_MergedHeightPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k + 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //Com_Printf ("found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[k + 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = qfalse; + worldData.surfaces[grid2num].data = (surfaceType_t *) grid2; + return qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) + continue; + for (k = 0; k < grid1->height-2; k += 2) { + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + //if (R_MergedWidthPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //Com_Printf ("found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + worldData.surfaces[grid2num].data = (surfaceType_t *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + //if (R_MergedHeightPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k + 2) + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //Com_Printf ("found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[grid1->width * (k + 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + worldData.surfaces[grid2num].data = (surfaceType_t *) grid2; + return qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = (grid1->height-1) * grid1->width; + else offset1 = 0; + if (R_MergedWidthPoints(grid1, offset1)) + continue; + for (k = grid1->width-1; k > 1; k -= 2) { + + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + //if (R_MergedWidthPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //Com_Printf ("found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]); + grid2->lodStitched = qfalse; + worldData.surfaces[grid2num].data = (surfaceType_t *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + //if (R_MergedHeightPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[k - 2 + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //Com_Printf ("found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[k - 1 + offset1].xyz, grid1->widthLodError[k+1]); + if (!grid2) + break; + grid2->lodStitched = qfalse; + worldData.surfaces[grid2num].data = (surfaceType_t *) grid2; + return qtrue; + } + } + } + } + for (n = 0; n < 2; n++) { + // + if (n) offset1 = grid1->width-1; + else offset1 = 0; + if (R_MergedHeightPoints(grid1, offset1)) + continue; + for (k = grid1->height-1; k > 1; k -= 2) { + for (m = 0; m < 2; m++) { + + if ( grid2->width >= MAX_GRID_SIZE ) + break; + if (m) offset2 = (grid2->height-1) * grid2->width; + else offset2 = 0; + //if (R_MergedWidthPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->width-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz; + v2 = grid2->verts[l + 1 + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[l + offset2].xyz; + v2 = grid2->verts[(l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //Com_Printf ("found highest LoD crack between two patches\n" ); + // insert column into grid2 right after after column l + if (m) row = grid2->height-1; + else row = 0; + grid2 = R_GridInsertColumn( grid2, l+1, row, + grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + worldData.surfaces[grid2num].data = (surfaceType_t *) grid2; + return qtrue; + } + } + for (m = 0; m < 2; m++) { + + if (grid2->height >= MAX_GRID_SIZE) + break; + if (m) offset2 = grid2->width-1; + else offset2 = 0; + //if (R_MergedHeightPoints(grid2, offset2)) + // continue; + for ( l = 0; l < grid2->height-1; l++) { + // + v1 = grid1->verts[grid1->width * k + offset1].xyz; + v2 = grid2->verts[grid2->width * l + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + + v1 = grid1->verts[grid1->width * (k - 2) + offset1].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) > .1) + continue; + if ( fabs(v1[1] - v2[1]) > .1) + continue; + if ( fabs(v1[2] - v2[2]) > .1) + continue; + // + v1 = grid2->verts[grid2->width * l + offset2].xyz; + v2 = grid2->verts[grid2->width * (l + 1) + offset2].xyz; + if ( fabs(v1[0] - v2[0]) < .01 && + fabs(v1[1] - v2[1]) < .01 && + fabs(v1[2] - v2[2]) < .01) + continue; + // + //Com_Printf ("found highest LoD crack between two patches\n" ); + // insert row into grid2 right after after row l + if (m) column = grid2->width-1; + else column = 0; + grid2 = R_GridInsertRow( grid2, l+1, column, + grid1->verts[grid1->width * (k - 1) + offset1].xyz, grid1->heightLodError[k+1]); + grid2->lodStitched = qfalse; + worldData.surfaces[grid2num].data = (surfaceType_t *) grid2; + return qtrue; + } + } + } + } + return qfalse; +} + +/* +=============== +R_TryStitchPatch + +This function will try to stitch patches in the same LoD group together for the highest LoD. + +Only single missing vertice cracks will be fixed. + +Vertices will be joined at the patch side a crack is first found, at the other side +of the patch (on the same row or column) the vertices will not be joined and cracks +might still appear at that side. +=============== +*/ +int R_TryStitchingPatch( int grid1num, world_t &worldData ) { + int j, numstitches; + srfGridMesh_t *grid1, *grid2; + + numstitches = 0; + grid1 = (srfGridMesh_t *) worldData.surfaces[grid1num].data; + for ( j = 0; j < worldData.numsurfaces; j++ ) { + // + grid2 = (srfGridMesh_t *) worldData.surfaces[j].data; + // if this surface is not a grid + if ( grid2->surfaceType != SF_GRID ) continue; + // grids in the same LOD group should have the exact same lod radius + if ( grid1->lodRadius != grid2->lodRadius ) continue; + // grids in the same LOD group should have the exact same lod origin + if ( grid1->lodOrigin[0] != grid2->lodOrigin[0] ) continue; + if ( grid1->lodOrigin[1] != grid2->lodOrigin[1] ) continue; + if ( grid1->lodOrigin[2] != grid2->lodOrigin[2] ) continue; + // + while (R_StitchPatches(grid1num, j, worldData)) + { + numstitches++; + } + } + return numstitches; +} + +/* +=============== +R_StitchAllPatches +=============== +*/ +void R_StitchAllPatches( world_t &worldData ) { + int i, stitched, numstitches; + srfGridMesh_t *grid1; + + numstitches = 0; + do + { + stitched = qfalse; + for ( i = 0; i < worldData.numsurfaces; i++ ) { + // + grid1 = (srfGridMesh_t *) worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid1->surfaceType != SF_GRID ) + continue; + // + if ( grid1->lodStitched ) + continue; + // + grid1->lodStitched = qtrue; + stitched = qtrue; + // + numstitches += R_TryStitchingPatch( i, worldData ); + } + } + while (stitched); +// Com_Printf ("stitched %d LoD cracks\n", numstitches ); +} + +/* +=============== +R_MovePatchSurfacesToHunk +=============== +*/ +void R_MovePatchSurfacesToHunk(world_t &worldData) { + int i, size; + srfGridMesh_t *grid, *hunkgrid; + + for ( i = 0; i < worldData.numsurfaces; i++ ) { + // + grid = (srfGridMesh_t *) worldData.surfaces[i].data; + // if this surface is not a grid + if ( grid->surfaceType != SF_GRID ) + continue; + // + size = (grid->width * grid->height - 1) * sizeof( drawVert_t ) + sizeof( *grid ); + hunkgrid = (struct srfGridMesh_s *)Hunk_Alloc( size, h_low ); + Com_Memcpy(hunkgrid, grid, size); + + hunkgrid->widthLodError = (float *)Hunk_Alloc( grid->width * 4, h_low ); + Com_Memcpy( hunkgrid->widthLodError, grid->widthLodError, grid->width * 4 ); + + hunkgrid->heightLodError = (float *)Hunk_Alloc( grid->height * 4, h_low ); + Com_Memcpy( grid->heightLodError, grid->heightLodError, grid->height * 4 ); + + R_FreeSurfaceGridMesh( grid ); + + worldData.surfaces[i].data = (surfaceType_t *) hunkgrid; + } +} + +/* +=============== +R_LoadSurfaces +=============== +*/ +static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump, world_t &worldData, int index ) { + dsurface_t *in; + msurface_t *out; + mapVert_t *dv; + int *indexes; + int count; + int numFaces, numMeshes, numTriSurfs, numFlares; + int i; + + numFaces = 0; + numMeshes = 0; + numTriSurfs = 0; + numFlares = 0; + + in = (dsurface_t *)(fileBase + surfs->fileofs); + if (surfs->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + count = surfs->filelen / sizeof(*in); + + dv = (mapVert_t *)(fileBase + verts->fileofs); + if (verts->filelen % sizeof(*dv)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + + indexes = (int *)(fileBase + indexLump->fileofs); + if ( indexLump->filelen % sizeof(*indexes)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + + out = (struct msurface_s *)Hunk_Alloc ( count * sizeof(*out), h_low ); + + worldData.surfaces = out; + worldData.numsurfaces = count; + + for ( i = 0 ; i < count ; i++, in++, out++ ) { + switch ( LittleLong( in->surfaceType ) ) { + case MST_PATCH: + ParseMesh ( in, dv, out, worldData, index ); + numMeshes++; + break; + case MST_TRIANGLE_SOUP: + ParseTriSurf( in, dv, out, indexes, worldData, index ); + numTriSurfs++; + break; + case MST_PLANAR: + ParseFace( in, dv, out, indexes, worldData, index ); + numFaces++; + break; + case MST_FLARE: + ParseFlare( in, dv, out, indexes, worldData, index ); + numFlares++; + break; + default: + Com_Error( ERR_DROP, "Bad surfaceType" ); + } + } + +#ifdef PATCH_STITCHING + R_StitchAllPatches(worldData); +#endif + + R_FixSharedVertexLodError(worldData); + +#ifdef PATCH_STITCHING + R_MovePatchSurfacesToHunk(worldData); +#endif + +// Com_Printf ("...loaded %d faces, %i meshes, %i trisurfs, %i flares\n", numFaces, numMeshes, numTriSurfs, numFlares ); +} + + + +/* +================= +R_LoadSubmodels +================= +*/ +static void R_LoadSubmodels( lump_t *l, world_t &worldData, int index ) { + dmodel_t *in; + bmodel_t *out; + int i, j, count; + + in = (dmodel_t *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + count = l->filelen / sizeof(*in); + + worldData.bmodels = out = (bmodel_t *)Hunk_Alloc( count * sizeof(*out), h_low ); + + for ( i=0 ; itype = MOD_BRUSH; + model->bmodel = out; + if (index) + { + Com_sprintf( model->name, sizeof( model->name ), "*%d-%d", index, i ); + model->bspInstance = qtrue; + } + else + { + Com_sprintf( model->name, sizeof( model->name ), "*%d", i); + } + + for (j=0 ; j<3 ; j++) { + out->bounds[0][j] = LittleFloat (in->mins[j]); + out->bounds[1][j] = LittleFloat (in->maxs[j]); + } +/* +Ghoul2 Insert Start +*/ + + RE_InsertModelIntoHash(model->name, model); +/* +Ghoul2 Insert End +*/ + out->firstSurface = worldData.surfaces + LittleLong( in->firstSurface ); + out->numSurfaces = LittleLong( in->numSurfaces ); + } +} + + + +//================================================================== + +/* +================= +R_SetParent +================= +*/ +static void R_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents != -1) + return; + R_SetParent (node->children[0], node); + R_SetParent (node->children[1], node); +} + +/* +================= +R_LoadNodesAndLeafs +================= +*/ +static void R_LoadNodesAndLeafs (lump_t *nodeLump, lump_t *leafLump, world_t &worldData) { + int i, j, p; + dnode_t *in; + dleaf_t *inLeaf; + mnode_t *out; + int numNodes, numLeafs; + + in = (dnode_t *)(fileBase + nodeLump->fileofs); + if (nodeLump->filelen % sizeof(dnode_t) || + leafLump->filelen % sizeof(dleaf_t) ) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + } + numNodes = nodeLump->filelen / sizeof(dnode_t); + numLeafs = leafLump->filelen / sizeof(dleaf_t); + + out = (struct mnode_s *)Hunk_Alloc ( (numNodes + numLeafs) * sizeof(*out), h_low); + + worldData.nodes = out; + worldData.numnodes = numNodes + numLeafs; + worldData.numDecisionNodes = numNodes; + + // load nodes + for ( i=0 ; imins[j] = LittleLong (in->mins[j]); + out->maxs[j] = LittleLong (in->maxs[j]); + } + + p = LittleLong(in->planeNum); + out->plane = worldData.planes + p; + + out->contents = CONTENTS_NODE; // differentiate from leafs + + for (j=0 ; j<2 ; j++) + { + p = LittleLong (in->children[j]); + if (p >= 0) + out->children[j] = worldData.nodes + p; + else + out->children[j] = worldData.nodes + numNodes + (-1 - p); + } + } + + // load leafs + inLeaf = (dleaf_t *)(fileBase + leafLump->fileofs); + for ( i=0 ; imins[j] = LittleLong (inLeaf->mins[j]); + out->maxs[j] = LittleLong (inLeaf->maxs[j]); + } + + out->cluster = LittleLong(inLeaf->cluster); + out->area = LittleLong(inLeaf->area); + + if ( out->cluster >= worldData.numClusters ) { + worldData.numClusters = out->cluster + 1; + } + + out->firstmarksurface = worldData.marksurfaces + + LittleLong(inLeaf->firstLeafSurface); + out->nummarksurfaces = LittleLong(inLeaf->numLeafSurfaces); + } + + // chain decendants + R_SetParent (worldData.nodes, NULL); +} + +//============================================================================= + +/* +================= +R_LoadShaders +================= +*/ +static void R_LoadShaders( lump_t *l, world_t &worldData ) { + int i, count; + dshader_t *in, *out; + + in = (dshader_t *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + count = l->filelen / sizeof(*in); + out = (dshader_t *)Hunk_Alloc ( count*sizeof(*out), h_low ); + + worldData.shaders = out; + worldData.numShaders = count; + + Com_Memcpy( out, in, count*sizeof(*out) ); + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + count = l->filelen / sizeof(*in); + out = (struct msurface_s **)Hunk_Alloc ( count*sizeof(*out), h_low); + + worldData.marksurfaces = out; + worldData.nummarksurfaces = count; + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + count = l->filelen / sizeof(*in); + out = (struct cplane_s *)Hunk_Alloc ( count*2*sizeof(*out), h_low); + + worldData.planes = out; + worldData.numplanes = count; + + for ( i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) { + bits |= 1<dist = LittleFloat (in->dist); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +R_LoadFogs + +================= +*/ +static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump, world_t &worldData, int index ) { + int i; + fog_t *out; + dfog_t *fogs; + dbrush_t *brushes, *brush; + dbrushside_t *sides; + int count, brushesCount, sidesCount; + int sideNum; + int planeNum; + shader_t *shader; + float d; + int firstSide=0; + int lightmaps[MAXLIGHTMAPS] = { LIGHTMAP_NONE } ; + + fogs = (dfog_t *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*fogs)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + } + count = l->filelen / sizeof(*fogs); + + // create fog strucutres for them + worldData.numfogs = count + 1; + worldData.fogs = (fog_t *)Hunk_Alloc ( worldData.numfogs*sizeof(*out), h_low); + worldData.globalFog = -1; + out = worldData.fogs + 1; + + // Copy the global fog from the main world into the bsp instance + if(index) + { + if(tr.world && (tr.world->globalFog != -1)) + { + // Use the nightvision fog slot + worldData.fogs[worldData.numfogs] = tr.world->fogs[tr.world->globalFog]; + worldData.globalFog = worldData.numfogs; + worldData.numfogs++; + } + } + + if ( !count ) { + return; + } + + brushes = (dbrush_t *)(fileBase + brushesLump->fileofs); + if (brushesLump->filelen % sizeof(*brushes)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + } + brushesCount = brushesLump->filelen / sizeof(*brushes); + + sides = (dbrushside_t *)(fileBase + sidesLump->fileofs); + if (sidesLump->filelen % sizeof(*sides)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",worldData.name); + } + sidesCount = sidesLump->filelen / sizeof(*sides); + + for ( i=0 ; ioriginalBrushNumber = LittleLong( fogs->brushNum ); + + if (out->originalBrushNumber == -1) + { + out->bounds[0][0] = out->bounds[0][1] = out->bounds[0][2] = MIN_WORLD_COORD; + out->bounds[1][0] = out->bounds[1][1] = out->bounds[1][2] = MAX_WORLD_COORD; + firstSide = -1; + worldData.globalFog = i+1; + } + else + { + if ( (unsigned)out->originalBrushNumber >= brushesCount ) { + Com_Error( ERR_DROP, "fog brushNumber out of range" ); + } + brush = brushes + out->originalBrushNumber; + + firstSide = LittleLong( brush->firstSide ); + + if ( (unsigned)firstSide > sidesCount - 6 ) { + Com_Error( ERR_DROP, "fog brush sideNumber out of range" ); + } + + // brushes are always sorted with the axial sides first + sideNum = firstSide + 0; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][0] = -worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 1; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][0] = worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 2; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][1] = -worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 3; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][1] = worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 4; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][2] = -worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 5; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][2] = worldData.planes[ planeNum ].dist; + } + + // get information from the shader for fog parameters + shader = R_FindShader( fogs->shader, lightmaps, stylesDefault, qtrue ); + + if (!shader->fogParms) + {//bad shader!! + assert(shader->fogParms); + out->parms.color[0] = 1.0f; + out->parms.color[1] = 0.0f; + out->parms.color[2] = 0.0f; + out->parms.color[3] = 0.0f; + out->parms.depthForOpaque = 250.0f; + } + else + { + out->parms = *shader->fogParms; + } + + out->colorInt = ColorBytes4 ( out->parms.color[0] * tr.identityLight, + out->parms.color[1] * tr.identityLight, + out->parms.color[2] * tr.identityLight, 1.0 ); + d = out->parms.depthForOpaque < 1 ? 1 : out->parms.depthForOpaque; + out->tcScale = 1.0f / ( d * 8 ); + + // set the gradient vector + sideNum = LittleLong( fogs->visibleSide ); + + if ( sideNum == -1 ) { + //rww - we need to set this to qtrue for global fog as well + out->hasSurface = qtrue; + } else { + out->hasSurface = qtrue; + planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum ); + VectorSubtract( vec3_origin, worldData.planes[ planeNum ].normal, out->surface ); + out->surface[3] = -worldData.planes[ planeNum ].dist; + } + + out++; + } + +} + + +/* +================ +R_LoadLightGrid + +================ +*/ +void R_LoadLightGrid( lump_t *l, world_t &worldData ) { + int i, j; + vec3_t maxs; + world_t *w; + float *wMins, *wMaxs; + + w = &worldData; + + w->lightGridInverseSize[0] = 1.0 / w->lightGridSize[0]; + w->lightGridInverseSize[1] = 1.0 / w->lightGridSize[1]; + w->lightGridInverseSize[2] = 1.0 / w->lightGridSize[2]; + + wMins = w->bmodels[0].bounds[0]; + wMaxs = w->bmodels[0].bounds[1]; + + for ( i = 0 ; i < 3 ; i++ ) { + w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] ); + maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] ); + w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1; + } + + int numGridDataElements = l->filelen / sizeof(*w->lightGridData); + + w->lightGridData = (mgrid_t *)Hunk_Alloc( l->filelen, h_low ); + memcpy( w->lightGridData, (void *)(fileBase + l->fileofs), l->filelen ); + + // deal with overbright bits + for ( i = 0 ; i < numGridDataElements ; i++ ) + { + for(j=0;jlightGridData[i].ambientLight[j]); + R_ColorShiftLightingBytes(w->lightGridData[i].directLight[j]); + } + } +} + +/* +================ +R_LoadLightGridArray + +================ +*/ +void R_LoadLightGridArray( lump_t *l, world_t &worldData ) { + world_t *w; + + w = &worldData; + + w->numGridArrayElements = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2]; + + if ( l->filelen != w->numGridArrayElements * sizeof(*w->lightGridArray) ) { + Com_Printf (S_COLOR_YELLOW "WARNING: light grid array mismatch\n" ); + w->lightGridData = NULL; + return; + } + + w->lightGridArray = (unsigned short *)Hunk_Alloc( l->filelen, h_low ); + memcpy( w->lightGridArray, (void *)(fileBase + l->fileofs), l->filelen ); +} + +/* +================ +R_LoadEntities +================ +*/ +void R_LoadEntities( lump_t *l, world_t &worldData ) { + const char *p; + char *token, *s; + char keyname[MAX_TOKEN_CHARS]; + char value[MAX_TOKEN_CHARS]; + world_t *w; + float ambient = 1; + + w = &worldData; + w->lightGridSize[0] = 64; + w->lightGridSize[1] = 64; + w->lightGridSize[2] = 128; + + VectorSet(tr.sunAmbient, 1, 1, 1); + tr.distanceCull = 6000;//DEFAULT_DISTANCE_CULL; + + p = (char *)(fileBase + l->fileofs); + + // store for reference by the cgame + w->entityString = (char *)Hunk_Alloc( l->filelen + 1, h_low ); + strcpy( w->entityString, p ); + w->entityParsePoint = w->entityString; + + token = COM_ParseExt( &p, qtrue ); + if (!*token || *token != '{') { + return; + } + + // only parse the world spawn + while ( 1 ) { + // parse key + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(keyname, token, sizeof(keyname)); + + // parse value + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(value, token, sizeof(value)); + + // check for remapping of shaders for vertex lighting + s = "vertexremapshader"; + if (!Q_strncmp(keyname, s, strlen(s)) ) { + s = strchr(value, ';'); + if (!s) { + Com_Printf (S_COLOR_YELLOW "WARNING: no semi colon in vertexshaderremap '%s'\n", value ); + break; + } + *s++ = 0; + if (r_vertexLight->integer) { + R_RemapShader(value, s, "0"); + } + continue; + } + // check for remapping of shaders + s = "remapshader"; + if (!Q_strncmp(keyname, s, strlen(s)) ) { + s = strchr(value, ';'); + if (!s) { + Com_Printf (S_COLOR_YELLOW "WARNING: no semi colon in shaderremap '%s'\n", value ); + break; + } + *s++ = 0; + R_RemapShader(value, s, "0"); + continue; + } + if (!Q_stricmp(keyname, "distanceCull")) { + sscanf(value, "%f", &tr.distanceCull ); + continue; + } + // check for a different grid size + if (!Q_stricmp(keyname, "gridsize")) { + sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] ); + continue; + } + // find the optional world ambient for arioche + if (!Q_stricmp(keyname, "_color")) { + sscanf(value, "%f %f %f", &tr.sunAmbient[0], &tr.sunAmbient[1], &tr.sunAmbient[2] ); + continue; + } + if (!Q_stricmp(keyname, "ambient")) { + sscanf(value, "%f", &ambient); + continue; + } + } + //both default to 1 so no harm if not present. + VectorScale( tr.sunAmbient, ambient, tr.sunAmbient); +} + +/* +================= +R_GetEntityToken +================= +*/ +qboolean R_GetEntityToken( char *buffer, int size ) { + const char *s; + + if (size == -1) + { //force reset + s_worldData.entityParsePoint = s_worldData.entityString; + return qtrue; + } + + s = COM_Parse( (const char **) &s_worldData.entityParsePoint ); + Q_strncpyz( buffer, s, size ); + if ( !s_worldData.entityParsePoint || !s[0] ) { + return qfalse; + } else { + return qtrue; + } +} + +/* +================= +RE_LoadWorldMap + +Called directly from cgame +================= +*/ +void RE_LoadWorldMap_Actual( const char *name, world_t &worldData, int index ) +{ + int i; + dheader_t *header; + byte *buffer; + byte *startMarker; + + if ( tr.worldMapLoaded && !index ) { + Com_Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" ); + } + + if (!index) + { + skyboxportal = 0; + + // set default sun direction to be used if it isn't + // overridden by a shader + tr.sunDirection[0] = 0.45f; + tr.sunDirection[1] = 0.3f; + tr.sunDirection[2] = 0.9f; + + VectorNormalize( tr.sunDirection ); + + tr.worldMapLoaded = qtrue; + + // clear tr.world so if the level fails to load, the next + // try will not look at the partially loaded version + tr.world = NULL; + } + + // check for cached disk file from the server first... + // + extern void *gpvCachedMapDiskImage; + if (gpvCachedMapDiskImage) + { + buffer = (byte *)gpvCachedMapDiskImage; + } + else + { + // still needs loading... + // + FS_ReadFile( name, (void **)&buffer ); + if ( !buffer ) { + Com_Error (ERR_DROP, "RE_LoadWorldMap: %s not found", name); + } + } + + Com_Memset( &worldData, 0, sizeof( worldData ) ); + Q_strncpyz( worldData.name, name, sizeof( worldData.name ) ); + + Q_strncpyz( worldData.baseName, COM_SkipPath( worldData.name ), sizeof( worldData.name ) ); + COM_StripExtension( worldData.baseName, worldData.baseName ); + + startMarker = (unsigned char *)Hunk_Alloc(0, h_low); + c_gridVerts = 0; + + header = (dheader_t *)buffer; + fileBase = (byte *)header; + + i = LittleLong (header->version); + if ( i != BSP_VERSION ) { + Com_Error (ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)", + name, i, BSP_VERSION); + } + + // swap all the lumps + for (i=0 ; ilumps[LUMP_SHADERS], worldData ); + R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS], name, worldData ); + R_LoadPlanes (&header->lumps[LUMP_PLANES], worldData); + R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES], worldData, index ); + R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES], worldData, index ); + R_LoadMarksurfaces (&header->lumps[LUMP_LEAFSURFACES], worldData); + R_LoadNodesAndLeafs (&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS], worldData); + R_LoadSubmodels (&header->lumps[LUMP_MODELS], worldData, index); + R_LoadVisibility( &header->lumps[LUMP_VISIBILITY], worldData ); + + worldData.dataSize = (byte *)Hunk_Alloc(0, h_low) - startMarker; + + if (!index) + { + R_LoadEntities( &header->lumps[LUMP_ENTITIES], worldData ); + R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID], worldData ); + R_LoadLightGridArray( &header->lumps[LUMP_LIGHTARRAY], worldData ); + + // only set tr.world now that we know the entire level has loaded properly + tr.world = &worldData; + + if (com_RMG && com_RMG->integer) + { + R_RMGInit(); + } + } + + if (gpvCachedMapDiskImage) + { + Z_Free( gpvCachedMapDiskImage ); + gpvCachedMapDiskImage = NULL; + } + else + { + FS_FreeFile( buffer ); + } +} + + +// new wrapper used for convenience to tell z_malloc()-fail recovery code whether it's safe to dump the cached-bsp or not. +// +extern qboolean gbUsingCachedMapDataRightNow; +void RE_LoadWorldMap( const char *name ) +{ + gbUsingCachedMapDataRightNow = qtrue; // !!!!!!!!!!!! + + RE_LoadWorldMap_Actual( name, s_worldData, 0 ); + + gbUsingCachedMapDataRightNow = qfalse; // !!!!!!!!!!!! +} diff --git a/codemp/renderer/tr_bsp_xbox.cpp b/codemp/renderer/tr_bsp_xbox.cpp new file mode 100644 index 0000000..bd49184 --- /dev/null +++ b/codemp/renderer/tr_bsp_xbox.cpp @@ -0,0 +1,1820 @@ +// tr_map.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "tr_local.h" + +#include "../qcommon/cm_local.h" + +/* + +Loads and prepares a map file for scene rendering. + +A single entry point: + +void RE_LoadWorldMap( const char *name ); + +*/ + +static world_t s_worldData; +byte *fileBase; +int c_subdivisions; +int c_gridVerts; + +static int flareNum = 0; + +static cplane_t *externalPlaneData = NULL; +int externalPlaneCount = 0; + +void R_RMGInit(void); +//=============================================================================== + +// We use a special hack to prevent slight differences in channels +// from exploding into big differences, as it causes lighting problems +// later on. This is the maximum channel separation for which we +// enable the hack. +#define MAX_GREYSCALE_CHANNEL_DIFF 15 + +static void R_ColorShiftLightingBytes16( const byte in[4], byte out[2] ) { + // What's the largest separation between the red, green, and blue + // channels? + int chanDiff = max(in[0],max(in[1],in[2])) - + min(in[0],min(in[1],in[2])); + if (chanDiff <= MAX_GREYSCALE_CHANNEL_DIFF) + { + // Ensure that all color channels compress to the same value + byte channelAvg = (in[0] + in[1] + in[2] + 1) / 3; + out[0] = channelAvg & 0xF0; + out[0] |= (channelAvg & 0xF0) >> 4; + out[1] = channelAvg & 0xF0; + out[1] |= (in[3] & 0xF0) >> 4; + + if (channelAvg % 16 >= 8) + { + out[0] |= 0x10; + out[0] |= 0x01; + out[1] |= 0x10; + } + if (in[4] % 16 >= 8) + { + out[1] |= 0x01; + } + return; + } + + // Normal case for vertex colors that are not "near" greyscale + out[0] = in[0] & 0xF0; + out[0] |= (in[1] & 0xF0) >> 4; + out[1] = in[2] & 0xF0; + out[1] |= (in[3] & 0xF0) >> 4; + + if(in[0] % 16 >= 8) { + out[0] |= 0x10; + } + if(in[1] % 16 >= 8) { + out[0] |= 0x1; + } + if(in[2] % 16 >= 8) { + out[1] |= 0x10; + } + if(in[3] % 16 >= 8) { + out[1] |= 0x1; + } +} + + +static void HSVtoRGB( float h, float s, float v, float rgb[3] ) +{ + int i; + float f; + float p, q, t; + + h *= 5; + + i = floor( h ); + f = h - i; + + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch ( i ) + { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) { + int shift=0, r, g, b; + + // should NOT do it if overbrightBits is 0 + if (tr.overbrightBits) + shift = 1 - tr.overbrightBits; + + if (!shift) + { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; + return; + } + + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = in[3]; +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +static void R_ColorShiftLightingBytes( byte in[3]) +{ + int shift=0, r, g, b; + + // should NOT do it if overbrightBits is 0 + if (tr.overbrightBits) + shift = 1 - tr.overbrightBits; + + if (!shift) { + return; //no need if not overbright + } + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + in[0] = r; + in[1] = g; + in[2] = b; +} + + +/* +=============== +R_LoadLightmaps + +=============== +*/ +#define LIGHTMAP_SIZE 128 +void R_LoadLightmaps( void *data, int len, const char *psMapName ) { + byte *buf, *buf_p; + int i; + + if ( !len ) { + return; + } + buf = (byte *)data + sizeof(int); + + tr.numLightmaps = 0; + + // we are about to upload textures + R_SyncRenderThread(); + + // create all the lightmaps + int size = *(int*)data; + tr.numLightmaps = len / size; + + byte* image = (byte*)Z_Malloc(size, TAG_BSP, qfalse, 32); + + char sMapName[MAX_QPATH]; + COM_StripExtension(psMapName,sMapName); // will already by MAX_QPATH legal, so no length check + + for ( i = 0 ; i < tr.numLightmaps ; i++ ) { + buf_p = buf + i * size; + memcpy(image, buf_p, size); + + char lmapName[MAX_QPATH + 32]; + Com_sprintf(lmapName, MAX_QPATH + 32, "*%s/lightmap%d",sMapName,i); + tr.lightmaps[i] = R_CreateImage( lmapName, image, + LIGHTMAP_SIZE, LIGHTMAP_SIZE, + GL_DDS_RGB16_EXT, + qfalse, 0, GL_CLAMP); + } + + Z_Free(image); +} + + +/* +================= +RE_SetWorldVisData + +This is called by the clipmodel subsystem so we can share the 1.8 megs of +space in big maps... +================= +*/ +void RE_SetWorldVisData( SPARC *vis ) { + tr.externalVisData = vis; +} + + +void RE_SetPlaneData(cplane_t *planes, int count) +{ + externalPlaneData = planes; + externalPlaneCount = count; +} + + +/* +================= +R_LoadVisibility +================= +*/ +static void R_LoadVisibility( void *data, int len ) { + int length; + char *buf; + + length = ( s_worldData.numClusters + 63 ) & ~63; + s_worldData.novis = ( unsigned char *) Hunk_Alloc( length, h_low ); + memset( s_worldData.novis, 0xff, length ); + + if ( !len ) { + s_worldData.vis = NULL; + return; + } + buf = (char*)data; + + s_worldData.numClusters = ((int *)buf)[0]; + s_worldData.clusterBytes = ((int *)buf)[1]; + + // CM_Load should have given us the vis data to share, so + // we don't need to allocate another copy + if ( tr.externalVisData ) { + s_worldData.vis = tr.externalVisData; + } else { + assert(0); + } +} + +//=============================================================================== + +qhandle_t R_GetShaderByNum(int shaderNum, world_t &worldData) +{ + qhandle_t shader; + + if ( (shaderNum < 0) || (shaderNum >= worldData.numShaders) ) + { + Com_Printf( "Warning: Bad index for R_GetShaderByNum - %i", shaderNum ); + return(0); + } + shader = RE_RegisterShader(worldData.shaders[ shaderNum ].shader); + return(shader); +} + +/* +=============== +ShaderForShaderNum +=============== +*/ +static shader_t *ShaderForShaderNum( int shaderNum, const short *lightmapNum, const byte *lightmapStyles ) { + shader_t *shader; + dshader_t *dsh; + + shaderNum = shaderNum; + if ( shaderNum < 0 || shaderNum >= s_worldData.numShaders ) { + Com_Error( ERR_DROP, "ShaderForShaderNum: bad num %i", shaderNum ); + } + dsh = &s_worldData.shaders[ shaderNum ]; + + shader = R_FindShader( dsh->shader, lightmapNum, lightmapStyles, qtrue ); + + // if the shader had errors, just use default shader + if ( shader->defaultShader ) { + return tr.defaultShader; + } + + return shader; +} + +bool NeedVertexColors(shader_t *shader) +{ + int i; + shaderStage_t *stage; + + for(i=0; inumUnfoggedPasses; i++) { + stage = &shader->stages[i]; + switch(stage->rgbGen) { + case CGEN_EXACT_VERTEX: + case CGEN_VERTEX: + case CGEN_ONE_MINUS_VERTEX: + return true; + } + switch(stage->alphaGen) { + case AGEN_VERTEX: + case AGEN_ONE_MINUS_VERTEX: + return true; + } + } + + return false; +} + +int NumLightMaps(shader_t *shader) +{ + int count = 0; + int i; + + for(i=0; ilightmapIndex[i] >= 0) { + count++; + } else { + return count; + } + } + + return count; +} + +int SurfaceFaceSize(int numVerts, int numLightMaps, bool needVertexColors, + int numIndexes) +{ + int sfaceSize = ( int ) &((srfSurfaceFace_t *)0)->srfPoints + + 4 /*sizeof srfPoints*/ + + (numVerts * sizeof(unsigned short) * + (VERTEX_LM + numLightMaps * 2 + + (int)needVertexColors * 4)); + + // Add in tangent size -- NO! It's in VERTEX_LM +// sfaceSize += sizeof(vec3_t) * numVerts; + + //Indices stored in 8 bits now. + sfaceSize += numIndexes; + + return sfaceSize; +} + + +void BuildDrawVertTangents( drawVert_t *verts, int *indexes, int numIndexes, int numVertexes ) +{ + int i = 0; + + for(i = 0; i < numVertexes; i++) + { + verts[i].tangent[0] = 0.0f; + verts[i].tangent[1] = 0.0f; + verts[i].tangent[2] = 0.0f; + } + + for(i = 0; i < numIndexes; i += 3) + { + vec3_t vec1, vec2, du, dv, cp; + float st0[2], st1[2], st2[2]; + + Q_CastShort2FloatScale(&st0[0], &verts[indexes[i]].dvst[0], 1.f / DRAWVERT_ST_SCALE); + Q_CastShort2FloatScale(&st0[1], &verts[indexes[i]].dvst[1], 1.f / DRAWVERT_ST_SCALE); + + Q_CastShort2FloatScale(&st1[0], &verts[indexes[i+1]].dvst[0], 1.f / DRAWVERT_ST_SCALE); + Q_CastShort2FloatScale(&st1[1], &verts[indexes[i+1]].dvst[1], 1.f / DRAWVERT_ST_SCALE); + + Q_CastShort2FloatScale(&st2[0], &verts[indexes[i+2]].dvst[0], 1.f / DRAWVERT_ST_SCALE); + Q_CastShort2FloatScale(&st2[1], &verts[indexes[i+2]].dvst[1], 1.f / DRAWVERT_ST_SCALE); + + vec1[0] = verts[indexes[i+1]].xyz[0] - verts[indexes[i]].xyz[0]; + vec1[1] = st1[0] - st0[0]; + vec1[2] = st1[1] - st0[1]; + + vec2[0] = verts[indexes[i+2]].xyz[0] - verts[indexes[i]].xyz[0]; + vec2[1] = st2[0] - st0[0]; + vec2[2] = st2[1] - st0[1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[0] = -cp[1] / cp[0]; + dv[0] = -cp[2] / cp[0]; + + vec1[0] = verts[indexes[i+1]].xyz[1] - verts[indexes[i]].xyz[1]; + + vec2[0] = verts[indexes[i+2]].xyz[1] - verts[indexes[i]].xyz[1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[1] = -cp[1] / cp[0]; + dv[1] = -cp[2] / cp[0]; + + vec1[0] = verts[indexes[i+1]].xyz[2] - verts[indexes[i]].xyz[2]; + + vec2[0] = verts[indexes[i+2]].xyz[2] - verts[indexes[i]].xyz[2]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[2] = -cp[1] / cp[0]; + dv[2] = -cp[2] / cp[0]; + + verts[indexes[i]].tangent[0] += du[0]; + verts[indexes[i]].tangent[1] += du[1]; + verts[indexes[i]].tangent[2] += du[2]; + + verts[indexes[i+1]].tangent[0] += du[0]; + verts[indexes[i+1]].tangent[1] += du[1]; + verts[indexes[i+1]].tangent[2] += du[2]; + + verts[indexes[i+2]].tangent[0] += du[0]; + verts[indexes[i+2]].tangent[1] += du[1]; + verts[indexes[i+2]].tangent[2] += du[2]; + } + + for(i = 0; i < numVertexes; i++) + { + VectorNormalizeFast(verts[i].tangent); + } +} + + +void BuildMapVertTangents( mapVert_t *verts, vec3_t *tangents, short *indexes, int numIndexes, int numVertexes ) +{ + int i = 0; + + for(i = 0; i < numVertexes; i++) + { + tangents[i][0] = 0.0f; + tangents[i][1] = 0.0f; + tangents[i][2] = 0.0f; + } + + for(i = 0; i < numIndexes; i += 3) + { + vec3_t vec1, vec2, du, dv, cp; + + vec1[0] = verts[indexes[i+1]].xyz[0] - verts[indexes[i]].xyz[0]; + vec1[1] = (verts[indexes[i+1]].st[0] * POINTS_ST_SCALE) - + (verts[indexes[i]].st[0] * POINTS_ST_SCALE); + vec1[2] = (verts[indexes[i+1]].st[1] * POINTS_ST_SCALE) - + (verts[indexes[i]].st[1] * POINTS_ST_SCALE); + + vec2[0] = verts[indexes[i+2]].xyz[0] - verts[indexes[i]].xyz[0]; + vec2[1] = (verts[indexes[i+2]].st[0] * POINTS_ST_SCALE) - + (verts[indexes[i]].st[0] * POINTS_ST_SCALE); + vec2[2] = (verts[indexes[i+2]].st[1]* POINTS_ST_SCALE) - + (verts[indexes[i]].st[1] * POINTS_ST_SCALE); + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[0] = -cp[1] / cp[0]; + dv[0] = -cp[2] / cp[0]; + + vec1[0] = verts[indexes[i+1]].xyz[1] - verts[indexes[i]].xyz[1]; + + vec2[0] = verts[indexes[i+2]].xyz[1] - verts[indexes[i]].xyz[1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[1] = -cp[1] / cp[0]; + dv[1] = -cp[2] / cp[0]; + + vec1[0] = verts[indexes[i+1]].xyz[2] - verts[indexes[i]].xyz[2]; + + vec2[0] = verts[indexes[i+2]].xyz[2] - verts[indexes[i]].xyz[2]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[2] = -cp[1] / cp[0]; + dv[2] = -cp[2] / cp[0]; + + tangents[indexes[i]][0] += du[0]; + tangents[indexes[i]][1] += du[1]; + tangents[indexes[i]][2] += du[2]; + + tangents[indexes[i+1]][0] += du[0]; + tangents[indexes[i+1]][1] += du[1]; + tangents[indexes[i+1]][2] += du[2]; + + tangents[indexes[i+2]][0] += du[0]; + tangents[indexes[i+2]][1] += du[1]; + tangents[indexes[i+2]][2] += du[2]; + } + + for(i = 0; i < numVertexes; i++) + { + VectorNormalizeFast(tangents[i]); + } +} + +/* +=============== +ParseFace +=============== +*/ +static void ParseFace( dface_t *ds, mapVert_t *verts, msurface_t *surf, short *indexes, byte *&pFaceDataBuffer) +{ + int i, j, k; + srfSurfaceFace_t *cv; + int numPoints, numIndexes; + short lightmapNum[MAXLIGHTMAPS]; + int sfaceSize, ofsIndexes; + vec3_t tangents[1000]; + + for(i=0;ilightmapNum[i] - 4; + } + + // get fog volume + surf->fogIndex = ds->fogNum + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum, ds->lightmapStyles ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + bool needVertexColors = NeedVertexColors(surf->shader); + int numLightMaps = NumLightMaps(surf->shader); + assert(numLightMaps <= 0x7F); + + numPoints = ds->verts & 0xFFF; + if (numPoints > MAX_FACE_POINTS) { + Com_Printf (S_COLOR_YELLOW "WARNING: MAX_FACE_POINTS exceeded: %i\n", numPoints); + } + + numIndexes = ds->indexes & 0xFFF; + + // create the srfSurfaceFace_t + sfaceSize = SurfaceFaceSize(numPoints, + numLightMaps, needVertexColors, numIndexes); + ofsIndexes = sfaceSize - numIndexes; + + cv = (srfSurfaceFace_t *) pFaceDataBuffer;//ri.Hunk_Alloc( sfaceSize ); + pFaceDataBuffer += sfaceSize; // :-) + + cv->surfaceType = SF_FACE; + cv->numPoints = numPoints; + cv->numIndices = numIndexes; + cv->ofsIndices = ofsIndexes; + cv->srfPoints = (unsigned short *)(((byte*)cv) + ( int ) &((srfSurfaceFace_t *)0)->srfPoints + 4); + if(needVertexColors) { + cv->flags = 1 << 7; + } else { + cv->flags = 0; + } + cv->flags |= (numLightMaps & 0x7F); + + //Make sure we don't overflow storage. + assert(numPoints < 256); + assert(numIndexes < 65536); + assert(ofsIndexes < 65536); + + int nextSurfPoint = NEXT_SURFPOINT(cv->flags); + verts += ds->verts >> 12; + indexes += ds->indexes >> 12; + + BuildMapVertTangents(verts, tangents, indexes, numIndexes, numPoints); + + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + *(cv->srfPoints + i * nextSurfPoint + j) = verts[i].xyz[j]; + } + for ( j = 0; j < 3 ; j++ ) { + assert(tangents[i][j] >= -1 && tangents[i][j] <= 1); + *(cv->srfPoints + i * nextSurfPoint + 3 + j) = (short)(tangents[i][j] * 32767.0f); + } + for ( j = 0 ; j < 2 ; j++ ) { + *(cv->srfPoints + i * nextSurfPoint + 6 + j) = + (short)(verts[i].st[j] * POINTS_ST_SCALE); + + for(k=0;ksrfPoints + i * nextSurfPoint + VERTEX_LM+j+(k*2)) = + verts[i].lightmap[k][j]; + } + } + if(needVertexColors) { + for(k=0;ksrfPoints + i * nextSurfPoint + + VERTEX_COLOR(cv->flags) + k)); + } + } + } + + unsigned char *indexStorage = ((unsigned char*)cv) + cv->ofsIndices; + for ( i = 0 ; i < numIndexes ; i++ ) { + indexStorage[i] = indexes[ i ]; + } + + // take the plane information from the lightmap vector + for ( i = 0 ; i < 3 ; i++ ) { + cv->plane.normal[i] = (float)ds->lightmapVecs[i] / 32767.f; + } + vec3_t fVec; + fVec[0] = (float)((short)cv->srfPoints[0]); + fVec[1] = (float)((short)cv->srfPoints[1]); + fVec[2] = (float)((short)cv->srfPoints[2]); + cv->plane.dist = DotProduct( fVec, cv->plane.normal ); + SetPlaneSignbits( &cv->plane ); + cv->plane.type = PlaneTypeForNormal( cv->plane.normal ); + + surf->data = (surfaceType_t *)cv; +} + + +/* +=============== +ParseMesh +=============== +*/ +static void ParseMesh ( dpatch_t *ds, mapVert_t *verts, msurface_t *surf, + drawVert_t* points, drawVert_t* ctrl, float* errorTable ) { + srfGridMesh_t *grid; + int i, j, k; + int width, height, numPoints; + short lightmapNum[MAXLIGHTMAPS]; + vec3_t bounds[2]; + vec3_t tmpVec; + static surfaceType_t skipData = SF_SKIP; + + for(i=0;ilightmapNum[i] - 4; + } + + // get fog volume + surf->fogIndex = ds->fogNum + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum, ds->lightmapStyles ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + // we may have a nodraw surface, because they might still need to + // be around for movement clipping + if ( s_worldData.shaders[ ds->shaderNum ].surfaceFlags & SURF_NODRAW ) { + surf->data = &skipData; + return; + } + + width = ds->patchWidth; + height = ds->patchHeight; + + verts += ds->verts >> 12; + numPoints = width * height; + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + points[i].xyz[j] = (float)verts[i].xyz[j]; + points[i].normal[j] = (float)verts[i].normal[j] / 32767.f; + } + for ( j = 0 ; j < 2 ; j++ ) { + // Sanity check that alternate fixed point representation + // is good enough + assert( verts[i].st[j] * GRID_DRAWVERT_ST_SCALE < 32767 && + verts[i].st[j] * GRID_DRAWVERT_ST_SCALE >= -32768 ); + points[i].dvst[j] = verts[i].st[j] * GRID_DRAWVERT_ST_SCALE; + for(k=0;kdata = (surfaceType_t *)grid; + + // copy the level of detail origin, which is the center + // of the group of all curves that must subdivide the same + // to avoid cracking + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = ds->lightmapVecs[0][i]; + bounds[1][i] = ds->lightmapVecs[1][i]; + } + VectorAdd( bounds[0], bounds[1], bounds[1] ); + VectorScale( bounds[1], 0.5f, grid->lodOrigin ); + VectorSubtract( bounds[0], grid->lodOrigin, tmpVec ); + grid->lodRadius = VectorLength( tmpVec ); +} + +/* +=============== +ParseTriSurf +=============== +*/ +static void ParseTriSurf( dtrisurf_t *ds, mapVert_t *verts, msurface_t *surf, short *indexes ) { + srfTriangles_t *tri; + int i, j, k; + int numVerts, numIndexes; + + // get fog volume + surf->fogIndex = ds->fogNum + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapsVertex, ds->lightmapStyles ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + numVerts = ds->verts & 0xFFF; + numIndexes = ds->indexes & 0xFFF; + + tri = (srfTriangles_t *) Hunk_Alloc( sizeof( *tri ) + numVerts * sizeof( tri->verts[0] ) + + numIndexes * sizeof( tri->indexes[0] ), h_low ); + tri->surfaceType = SF_TRIANGLES; + tri->numVerts = numVerts; + tri->numIndexes = numIndexes; + tri->verts = (drawVert_t *)(tri + 1); + tri->indexes = (int *)(tri->verts + tri->numVerts ); + + surf->data = (surfaceType_t *)tri; + + // copy vertexes + verts += ds->verts >> 12; + ClearBounds( tri->bounds[0], tri->bounds[1] ); + for ( i = 0 ; i < numVerts ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + tri->verts[i].xyz[j] = (float)verts[i].xyz[j]; + tri->verts[i].normal[j] = (float)verts[i].normal[j] / 32767.f; + } + AddPointToBounds( tri->verts[i].xyz, tri->bounds[0], tri->bounds[1] ); + for ( j = 0 ; j < 2 ; j++ ) { + // Sanity check that alternate fixed point representation + // is good enough + // MATT! - double check this! + assert( verts[i].st[j] * DRAWVERT_ST_SCALE <= 32767 && + verts[i].st[j] * DRAWVERT_ST_SCALE >= -32768 ); + tri->verts[i].dvst[j] = verts[i].st[j] * DRAWVERT_ST_SCALE; + for(k=0;kverts[i].dvlightmap[k][j] = + ((float)verts[i].lightmap[k][j] / POINTS_LIGHT_SCALE) * + DRAWVERT_LIGHTMAP_SCALE; + } + } + for(k=0;kverts[i].dvcolor[k]); + } + } + + // copy indexes + indexes += ds->indexes >> 12; + for ( i = 0 ; i < numIndexes ; i++ ) { + tri->indexes[i] = indexes[i]; + if ( tri->indexes[i] < 0 || tri->indexes[i] >= numVerts ) { + Com_Error( ERR_DROP, "Bad index in triangle surface" ); + } + } + + // Build the tangent vectors + BuildDrawVertTangents(tri->verts, tri->indexes, numIndexes, numVerts); +} + + +/* +=============== +ParseFlare +=============== +*/ +static void ParseFlare( dflare_t *df, msurface_t *surf ) +{ + srfFlare_t *flare; + int i; + + surf->fogIndex = df->fogNum + 1; + + // get shader + surf->shader = ShaderForShaderNum( df->shaderNum, lightmapsVertex, stylesDefault ); + + flare = (srfFlare_t *) Hunk_Alloc( sizeof( *flare ), h_low ); + flare->surfaceType = SF_FLARE; + + for ( i = 0 ; i < 3 ; i++ ) { + flare->origin[i] = df->origin[i]; + flare->color[i] = df->color[i]; + flare->normal[i] = df->normal[i]; + } + + assert(flareNum <= 255); + flare->number = flareNum++; + flare->visible = -1; + + surf->data = (surfaceType_t *)flare; +} + + +void R_LoadFlares( void *surfaces, int surfacelen ) { + int count, i; + dflare_t *in = NULL; + msurface_t *out; + + count = surfacelen / sizeof(*in); + + flareNum = 0; + + for ( i = 0 ; i < count ; i++ ) { + in = (dflare_t *)surfaces + i; + out = s_worldData.surfaces + in->code; + ParseFlare( in, out ); + } +} + + +/* +=============== +R_LoadSurfaces +=============== +*/ +void R_LoadSurfaces( int count ) { + s_worldData.surfaces = (struct msurface_s *) + Hunk_Alloc ( count * sizeof(msurface_s), h_low ); + s_worldData.numsurfaces = count; +} + + +/* +=============== +R_LoadPatches +=============== +*/ +void R_LoadPatches( void *verts, int vertlen, + void *surfaces, int surfacelen ) { + dpatch_t *in = NULL; + msurface_t *out; + mapVert_t *dv; + int count; + int i; + + if (surfacelen == 0) { + return; + } + + count = surfacelen / sizeof(*in); + + dv = (mapVert_t *)(verts); + if (vertlen % sizeof(*dv)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + drawVert_t* points = (drawVert_t*)Z_Malloc( + MAX_PATCH_SIZE*MAX_PATCH_SIZE*sizeof(drawVert_t), + TAG_TEMP_WORKSPACE, qfalse); + + drawVert_t* ctrl = (drawVert_t*)Z_Malloc( + MAX_GRID_SIZE*MAX_GRID_SIZE*sizeof(drawVert_t), + TAG_TEMP_WORKSPACE, qfalse); + + float* errorTable = (float*)Z_Malloc( + 2*MAX_GRID_SIZE*sizeof(float), + TAG_TEMP_WORKSPACE, qfalse); + + for ( i = 0 ; i < count ; i++ ) { + in = (dpatch_t *)surfaces + i; + out = s_worldData.surfaces + in->code; + ParseMesh ( in, dv, out, points, ctrl, errorTable ); + } + + Z_Free(errorTable); + Z_Free(ctrl); + Z_Free(points); + +// Com_Printf( "...loaded %i meshes\n", count ); +} + + + /* +=============== +R_LoadTriSurfs +=============== +*/ +void R_LoadTriSurfs( void *indexdata, int indexlen, + void *verts, int vertlen, + void *surfaces, int surfacelen ) { + dtrisurf_t *in = NULL; + msurface_t *out; + mapVert_t *dv; + short *indexes; + int count; + int i; + + if (surfacelen == 0) { + return; + } + + count = surfacelen / sizeof(*in); + + dv = (mapVert_t *)(verts); + if (vertlen % sizeof(*dv)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + indexes = (short *)(indexdata); + if ( indexlen % sizeof(*indexes)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + for ( i = 0 ; i < count ; i++ ) { + in = (dtrisurf_t *)surfaces + i; + out = s_worldData.surfaces + in->code; + ParseTriSurf( in, dv, out, indexes ); + } + +// Com_Printf( "...loaded %i trisurfs\n", count ); +} + + +/* +=============== +R_LoadFaces +=============== +*/ +void R_LoadFaces( void *indexdata, int indexlen, + void *verts, int vertlen, + void *surfaces, int surfacelen ) { + dface_t *in = NULL; + msurface_t *out; + mapVert_t *dv; + short *indexes; + int count; + int i; + + if (surfacelen == 0) { + return; + } + + count = surfacelen / sizeof(*in); + + dv = (mapVert_t *)(verts); + if (vertlen % sizeof(*dv)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + indexes = (short *)(indexdata); + if ( indexlen % sizeof(*indexes)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + // new bit, the face code on our biggest map requires over 15,000 mallocs, which was no problem on the hunk, + // bit hits the zone pretty bad (even the tagFree takes about 9 seconds for that many memblocks), + // so special-case pre-alloc enough space for this data (the patches etc can stay as they are)... + // + int nTimes = count / 100; + int nToGo = nTimes; + int iFaceDataSizeRequired = 0; + for ( i = 0 ; i < count ; i++) + { + in = (dface_t *)surfaces + i; + + short lightmapNum[MAXLIGHTMAPS]; + for(int j=0; j<4; j++) { + lightmapNum[j] = (int)in->lightmapNum[j] - 4; + } + shader_t *shader = ShaderForShaderNum( in->shaderNum, lightmapNum, in->lightmapStyles ); + bool needVertexColors = NeedVertexColors(shader); + int numLightMaps = NumLightMaps(shader); + + int sfaceSize = SurfaceFaceSize(in->verts & 0xFFF, + numLightMaps, needVertexColors, + in->indexes & 0xFFF); + + iFaceDataSizeRequired += sfaceSize; + assert(sfaceSize < 100 * 1024); + if (--nToGo <= 0) + { + nToGo = nTimes; + } + } + in -= count; // back it up, ready for loop-proper + + // since this ptr is to hunk data, I can pass it in and have it advanced without worrying about losing + // the original alloc ptr... + // + byte *orgFaceData; + byte *pFaceDataBuffer = (byte *)Hunk_Alloc( iFaceDataSizeRequired, h_low ); + orgFaceData = pFaceDataBuffer; + + // now do regular loop... + // + for ( i = 0 ; i < count ; i++ ) { + in = (dface_t *)surfaces + i; + out = s_worldData.surfaces + in->code; + ParseFace( in, dv, out, indexes, pFaceDataBuffer ); + if (--nToGo <= 0) + { + nToGo = nTimes; + } + } + +// Com_Printf( "...loaded %d faces\n", count ); +} + + +/* +================= +R_LoadSubmodels +================= +*/ +static void R_LoadSubmodels( void *data, int len ) { + dmodel_t *in; + bmodel_t *out; + int i, j, count; + + in = (dmodel_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = len / sizeof(*in); + + s_worldData.bmodels = out = (bmodel_t *) Hunk_Alloc( count * sizeof(*out), h_low ); + + for ( i=0 ; itype = MOD_BRUSH; + model->bmodel = out; + Com_sprintf( model->name, sizeof( model->name ), "*%d", i ); + + for (j=0 ; j<3 ; j++) { + out->bounds[0][j] = in->mins[j]; + out->bounds[1][j] = in->maxs[j]; + } + + RE_InsertModelIntoHash(model->name, model); + + out->firstSurface = s_worldData.surfaces + in->firstSurface; + out->numSurfaces = in->numSurfaces; + } +} + +//================================================================== + +/* +================= +R_SetParent +================= +*/ +static void R_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents != -1) + return; + R_SetParent (node->children[0], node); + R_SetParent (node->children[1], node); +} + +/* +================= +R_LoadNodesAndLeafs +================= +*/ +static void R_LoadNodesAndLeafs (void *nodes, int nodelen, void *leafs, int leaflen) { + int i, j, p; + dnode_t *in; + dleaf_t *inLeaf; + mnode_t *outNode; + mleaf_s *outLeaf; + int numNodes, numLeafs; + + in = (dnode_t *)(nodes); + if (nodelen % sizeof(dnode_t) || + leaflen % sizeof(dleaf_t) ) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + numNodes = nodelen / sizeof(dnode_t); + numLeafs = leaflen / sizeof(dleaf_t); + + outNode = (struct mnode_s *) Hunk_Alloc ( (numNodes) * sizeof(*outNode), h_low ); + outLeaf = (struct mleaf_s *) Hunk_Alloc ( (numLeafs) * sizeof(*outLeaf), h_low ); + + s_worldData.nodes = outNode; + s_worldData.leafs = outLeaf; + s_worldData.numnodes = numNodes; + s_worldData.numleafs = numLeafs; + + // load nodes + for ( i=0 ; imins[j] = in->mins[j]; + outNode->maxs[j] = in->maxs[j]; + } + + outNode->planeNum = in->planeNum; + outNode->contents = CONTENTS_NODE; // differentiate from leafs + + for (j=0 ; j<2 ; j++) + { + p = in->children[j]; + if (p >= 0) { + if(p < numNodes) { + outNode->children[j] = s_worldData.nodes + p; + } else { + outNode->children[j] = (mnode_s*) + (s_worldData.leafs + (p - numNodes)); + } + } else { + if(numNodes + (-1 - p) < numNodes) { + outNode->children[j] = s_worldData.nodes + numNodes + (-1 - p); + } else { + outNode->children[j] = (mnode_s*) + (s_worldData.leafs + (-1 - p)); + } + } + } + } + + // load leafs + inLeaf = (dleaf_t *)(leafs); + for ( i=0 ; imins[j] = inLeaf->mins[j]; + outLeaf->maxs[j] = inLeaf->maxs[j]; + } + + outLeaf->cluster = inLeaf->cluster; + outLeaf->area = inLeaf->area; + + if ( outLeaf->cluster >= s_worldData.numClusters ) { + s_worldData.numClusters = outLeaf->cluster + 1; + } + + outLeaf->firstMarkSurfNum = inLeaf->firstLeafSurface; + outLeaf->nummarksurfaces = inLeaf->numLeafSurfaces; + } + + // chain decendants + R_SetParent (s_worldData.nodes, NULL); +} + +//============================================================================= + +/* +================= +R_LoadShaders +================= +*/ +void R_LoadShaders( void *data, int len) { + dshader_t *in, *out; + int i, count; + + in = (dshader_t *)(data); + if (len % sizeof(*in)) { + Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size"); + } + count = len / sizeof(*in); + + if (count < 1) { + Com_Error (ERR_DROP, "Map with no shaders"); + } + out = (dshader_t *)Hunk_Alloc( count*sizeof(*out), h_low); + + s_worldData.shaders = out; + s_worldData.numShaders = count; + + Com_Memcpy( out, in, count*sizeof(*out) ); + + for ( i = 0; i < count; i++, in++, out++ ) + { +// Q_strncpyz(out->shader, in->shader, MAX_QPATH); + out->contentFlags = in->contentFlags; + out->surfaceFlags = in->surfaceFlags; + } +} + +/* +================= +R_LoadMarksurfaces +================= +*/ +static void R_LoadMarksurfaces (void *data, int len) +{ + int i, count; + int *in; + msurface_t **out; + + in = (int *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = len / sizeof(*in); + out = (struct msurface_s **) Hunk_Alloc ( count*sizeof(*out), h_low ); + + s_worldData.marksurfaces = out; + s_worldData.nummarksurfaces = count; + + for ( i=0 ; i s_worldData.numsurfaces) + assert(0); + + out[i] = s_worldData.surfaces + in[i]; + + if (out[i]->shader && out[i]->shader->sort == SS_PORTAL) + { + s_worldData.portalPresent = qtrue; + } + } +} + +/* +================= +R_LoadPlanes +================= +*/ +static void R_LoadPlanes( void *data, int len ) { + int i, j; + cplane_t *out; + dplane_t *in; + int count; + int bits; + + //If plane data has been set by the local server, use it. + if(externalPlaneData) { + s_worldData.planes = externalPlaneData; + s_worldData.numplanes = externalPlaneCount; + + externalPlaneData = NULL; + externalPlaneCount = 0; + return; + } + + + in = (dplane_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size"); + count = len / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no planes"); + +// out = (struct cplane_s *) Hunk_Alloc( count * 2 * sizeof( *out ), h_low); + out = (struct cplane_s *) Hunk_Alloc( count * sizeof( *out ), h_low); + + s_worldData.planes = out; + s_worldData.numplanes = count; + + for ( i=0 ; inormal[j] = in->normal[j]; + if (out->normal[j] < 0) + bits |= 1<dist = in->dist; + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +R_LoadFogs + +================= +*/ +static void R_LoadFogs( void *fogdata, int foglen, + void *brushdata, int brushlen, + void *sidedata, int sidelen ) { + int i; + fog_t *out; + dfog_t *fogs; + dbrush_t *brushes, *brush; + dbrushside_t *sides; + int count, brushesCount, sidesCount; + int sideNum; + int planeNum; + shader_t *shader; + float d; + int firstSide=0; + short lightmaps[MAXLIGHTMAPS] = { LIGHTMAP_NONE } ; + + fogs = (dfog_t *)(fogdata); + if (foglen % sizeof(*fogs)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + count = foglen / sizeof(*fogs); + + // create fog structres for them + // NOTE: we allocate memory for an extra one so that the LA goggles can turn on their own fog + s_worldData.numfogs = count + 1; + s_worldData.fogs = (fog_t *)Hunk_Alloc (( s_worldData.numfogs + 1)*sizeof(*out), h_low ); + s_worldData.globalFog = -1; + out = s_worldData.fogs + 1; + + if ( !count ) { + return; + } + + brushes = (dbrush_t *)(brushdata); + if (brushlen % sizeof(*brushes)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + brushesCount = brushlen / sizeof(*brushes); + + sides = (dbrushside_t *)(sidedata); + if (sidelen % sizeof(*sides)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + sidesCount = sidelen / sizeof(*sides); + + for ( i=0 ; ioriginalBrushNumber = fogs->brushNum; + if (out->originalBrushNumber == -1) + { + out->bounds[0][0] = out->bounds[0][1] = out->bounds[0][2] = MIN_WORLD_COORD; + out->bounds[1][0] = out->bounds[1][1] = out->bounds[1][2] = MAX_WORLD_COORD; + s_worldData.globalFog = i+1; + } + else + { + if ( (unsigned)out->originalBrushNumber >= brushesCount ) { + Com_Error( ERR_DROP, "fog brushNumber out of range" ); + } + brush = brushes + out->originalBrushNumber; + + firstSide = brush->firstSide; + + if ( (unsigned)firstSide > sidesCount - 6 ) { + Com_Error( ERR_DROP, "fog brush sideNumber out of range" ); + } + + // brushes are always sorted with the axial sides first + sideNum = firstSide + 0; + planeNum = sides[ sideNum ].planeNum; + out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 1; + planeNum = sides[ sideNum ].planeNum; + out->bounds[1][0] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 2; + planeNum = sides[ sideNum ].planeNum; + out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 3; + planeNum = sides[ sideNum ].planeNum; + out->bounds[1][1] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 4; + planeNum = sides[ sideNum ].planeNum; + out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 5; + planeNum = sides[ sideNum ].planeNum; + out->bounds[1][2] = s_worldData.planes[ planeNum ].dist; + } + + // get information from the shader for fog parameters + shader = R_FindShader( fogs->shader, lightmaps, stylesDefault, qtrue ); + + if (!shader->fogParms) + {//bad shader!! + assert(shader->fogParms); + out->parms.color[0] = 1.0f; + out->parms.color[1] = 0.0f; + out->parms.color[2] = 0.0f; + out->parms.color[3] = 0.0f; + out->parms.depthForOpaque = 250.0f; + } + else + { + out->parms = *shader->fogParms; + } + + out->colorInt = ColorBytes4 ( out->parms.color[0] * tr.identityLight, + out->parms.color[1] * tr.identityLight, + out->parms.color[2] * tr.identityLight, 1.0 ); + + d = out->parms.depthForOpaque < 1 ? 1 : out->parms.depthForOpaque; + out->tcScale = 1.0 / ( d * 8 ); + + // set the gradient vector + sideNum = fogs->visibleSide; + + if ( sideNum == -1 ) { + out->hasSurface = qfalse; + } else { + out->hasSurface = qtrue; + planeNum = sides[ firstSide + sideNum ].planeNum; + VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface ); + out->surface[3] = -s_worldData.planes[ planeNum ].dist; + } + + out++; + } + + // Initialise the last fog so we can use it with the LA Goggles + // NOTE: We are might appear to be off the end of the array, but we allocated an extra memory slot above but [purposely] didn't + // increment the total world numFogs to match our array size + VectorSet(out->bounds[0], MIN_WORLD_COORD, MIN_WORLD_COORD, MIN_WORLD_COORD); + VectorSet(out->bounds[1], MAX_WORLD_COORD, MAX_WORLD_COORD, MAX_WORLD_COORD); + out->originalBrushNumber = -1; + out->parms.color[0] = 0.0f; + out->parms.color[1] = 0.0f; + out->parms.color[2] = 0.0f; + out->parms.color[3] = 0.0f; + out->parms.depthForOpaque = 0.0f; + out->colorInt = 0x00000000; + out->tcScale = 0.0f; + out->hasSurface = false; +} + +/* +================ +R_LoadLightGrid + +================ +*/ +void R_LoadLightGrid( void *data, int len ) { + vec3_t maxs; + world_t *w; + int i; + float *wMins, *wMaxs; + + w = &s_worldData; + + w->lightGridInverseSize[0] = 1.0 / w->lightGridSize[0]; + w->lightGridInverseSize[1] = 1.0 / w->lightGridSize[1]; + w->lightGridInverseSize[2] = 1.0 / w->lightGridSize[2]; + + wMins = w->bmodels[0].bounds[0]; + wMaxs = w->bmodels[0].bounds[1]; + + for ( i = 0 ; i < 3 ; i++ ) { + w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] ); + maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] ); + w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1; + } + + w->lightGridData = (mgrid_t *)Hunk_Alloc( len, h_low ); + memcpy( w->lightGridData, data, len ); +} + +/* +================ +R_LoadLightGridArray + +================ +*/ +void R_LoadLightGridArray( void *data, int len ) { + world_t *w; + + w = &s_worldData; + + w->numGridArrayElements = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2]; + + if ( len != w->numGridArrayElements * sizeof(*w->lightGridArray) ) { + if (len>0)//don't warn if not even lit + Com_Printf( "WARNING: light grid array mismatch\n" ); + w->lightGridData = NULL; + return; + } + + w->lightGridArray = (unsigned short *)Hunk_Alloc( len, h_low ); + memcpy( w->lightGridArray, data, len ); +} + +/* +================ +R_LoadEntities +================ +*/ +void R_LoadEntities( void *data, int len ) { + const char *p, *token; + char keyname[MAX_TOKEN_CHARS]; + char value[MAX_TOKEN_CHARS]; + world_t *w; + + w = &s_worldData; + w->lightGridSize[0] = 64; + w->lightGridSize[1] = 64; + w->lightGridSize[2] = 128; + + VectorSet(tr.sunAmbient, 1, 1, 1); + tr.distanceCull = 6000;//DEFAULT_DISTANCE_CULL; + + p = (char *)(data); + + // store for reference by the cgame + w->entityString = (char *)Hunk_Alloc( len + 1, h_low ); + strcpy( w->entityString, p ); + w->entityParsePoint = w->entityString; + + token = COM_ParseExt( &p, qtrue ); + if (!*token || *token != '{') { + return; + } + + // only parse the world spawn + while ( 1 ) { + // parse key + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(keyname, token, sizeof(keyname)); + + // parse value + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(value, token, sizeof(value)); + + if (!Q_stricmp(keyname, "distanceCull")) { + sscanf(value, "%f", &tr.distanceCull ); + continue; + } + + // check for a different grid size + if (!Q_stricmp(keyname, "gridsize")) { + sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] ); + continue; + } + + // find the optional world ambient for arioche + if (!Q_stricmp(keyname, "_color")) { + sscanf(value, "%f %f %f", &tr.sunAmbient[0], &tr.sunAmbient[1], &tr.sunAmbient[2] ); + continue; + } + } +} + +/* +================= +R_GetEntityToken +================= +*/ +qboolean R_GetEntityToken( char *buffer, int size ) { + const char *s; + + if (size == -1) + { //force reset + s_worldData.entityParsePoint = s_worldData.entityString; + return qtrue; + } + + s = COM_Parse( (const char **) &s_worldData.entityParsePoint ); + Q_strncpyz( buffer, s, size ); + if ( !s_worldData.entityParsePoint || !s[0] ) { + return qfalse; + } else { + return qtrue; + } +} + + +/* +================= +RE_LoadWorldMap + +Called directly from cgame +================= +*/ +void RE_LoadWorldMap_Actual( const char *name, world_t &worldData, int index ) { + char stripName[MAX_QPATH]; + Lump outputLumps[3]; + + if ( tr.worldMapLoaded ) { +// Com_Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" ); + return; + } + + skyboxportal = 0; + + // set default sun direction to be used if it isn't + // overridden by a shader + tr.sunDirection[0] = 0.45f; + tr.sunDirection[1] = 0.3f; + tr.sunDirection[2] = 0.9f; + + Cvar_SetValue( "r_sundir_x", tr.sunDirection[0] ); + Cvar_SetValue( "r_sundir_y", tr.sunDirection[1] ); + Cvar_SetValue( "r_sundir_z", tr.sunDirection[2] ); + + VectorNormalize( tr.sunDirection ); + + tr.worldMapLoaded = qtrue; + + // clear tr.world so if the level fails to load, the next + // try will not look at the partially loaded version + tr.world = NULL; + + memset( &s_worldData, 0, sizeof( s_worldData ) ); + Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); + + Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) ); + COM_StripExtension( s_worldData.baseName, s_worldData.baseName ); + + COM_StripExtension(name, stripName); + + c_gridVerts = 0; + + // load into heap + outputLumps[0].load(stripName, "shaders"); + R_LoadShaders(outputLumps[0].data, outputLumps[0].len); + + outputLumps[0].load(stripName, "lightmaps"); + R_LoadLightmaps(outputLumps[0].data, outputLumps[0].len, name); + + outputLumps[0].load(stripName, "planes"); + R_LoadPlanes(outputLumps[0].data, outputLumps[0].len); + + outputLumps[0].load(stripName, "fogs"); + outputLumps[1].load(stripName, "brushes"); + outputLumps[2].load(stripName, "brushsides"); + R_LoadFogs( outputLumps[0].data, outputLumps[0].len, + outputLumps[1].data, outputLumps[1].len, + outputLumps[2].data, outputLumps[2].len ); + outputLumps[2].clear(); + outputLumps[1].clear(); + + Lump misc; + misc.load(stripName, "misc"); + + int num_surfs = *(int*)misc.data; + misc.clear(); + + R_LoadSurfaces(num_surfs); + + Lump verts; + verts.load(stripName, "verts"); + + Lump patches; + patches.load(stripName, "patches"); + R_LoadPatches(verts.data, verts.len, patches.data, patches.len); + patches.clear(); + + Lump indexes; + indexes.load(stripName, "indexes"); + + Lump trisurfs; + trisurfs.load(stripName, "trisurfs"); + R_LoadTriSurfs(indexes.data, indexes.len, verts.data, verts.len, trisurfs.data, trisurfs.len); + trisurfs.clear(); + + Lump faces; + faces.load(stripName, "faces"); + R_LoadFaces(indexes.data, indexes.len, verts.data, verts.len, faces.data, faces.len); + faces.clear(); + indexes.clear(); + verts.clear(); + + Lump flares; + flares.load(stripName, "flares"); + R_LoadFlares(flares.data, flares.len); + flares.clear(); + + outputLumps[0].load(stripName, "leafsurfaces"); + R_LoadMarksurfaces (outputLumps[0].data, outputLumps[0].len); + + outputLumps[0].load(stripName, "nodes"); + outputLumps[1].load(stripName, "leafs"); + R_LoadNodesAndLeafs (outputLumps[0].data, outputLumps[0].len, + outputLumps[1].data, outputLumps[1].len); + outputLumps[1].clear(); + + outputLumps[0].load(stripName, "models"); + R_LoadSubmodels (outputLumps[0].data, outputLumps[0].len); + + outputLumps[0].load(stripName, "visibility"); + R_LoadVisibility(outputLumps[0].data, outputLumps[0].len); + + outputLumps[0].load(stripName, "entities"); + R_LoadEntities( outputLumps[0].data, outputLumps[0].len ); + + outputLumps[0].load(stripName, "lightgrid"); + R_LoadLightGrid( outputLumps[0].data, outputLumps[0].len ); + + outputLumps[0].load(stripName, "lightarray"); + R_LoadLightGridArray( outputLumps[0].data, outputLumps[0].len ); + + // only set tr.world now that we know the entire level has loaded properly + tr.world = &s_worldData; +} + + +// new wrapper used for convenience to tell z_malloc()-fail recovery code whether it's safe to dump the cached-bsp or not. +// +extern qboolean gbUsingCachedMapDataRightNow; +void RE_LoadWorldMap( const char *name ) +{ + gbUsingCachedMapDataRightNow = qtrue; // !!!!!!!!!!!! + + RE_LoadWorldMap_Actual( name, s_worldData, 0 ); + + gbUsingCachedMapDataRightNow = qfalse; // !!!!!!!!!!!! +} + + +//A nasty looking function which loops through all images used by all surfaces +//and returns the number of matches for the given image. +#ifndef FINAL_BUILD +int R_SurfaceImageCount(const image_t *image1) +{ + int count = 0; + + for(int i=0; inumUnfoggedPasses; j++){ + for(int k=0; kstages[j].bundle[k].image; + if(image2 != NULL && !Q_stricmp(image1->imgName, image2->imgName)) { + count++; + } + + } + } + } + + return count; +} +#endif diff --git a/codemp/renderer/tr_cmds.cpp b/codemp/renderer/tr_cmds.cpp new file mode 100644 index 0000000..e389903 --- /dev/null +++ b/codemp/renderer/tr_cmds.cpp @@ -0,0 +1,484 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +/* +===================== +R_PerformanceCounters +===================== +*/ +void R_PerformanceCounters( void ) { +#ifndef _XBOX + if ( !r_speeds->integer ) { + // clear the counters even if we aren't printing + Com_Memset( &tr.pc, 0, sizeof( tr.pc ) ); + Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); + return; + } + + if (r_speeds->integer == 1) { + const float texSize = R_SumOfUsedImages( qfalse )/(8*1048576.0f)*(r_texturebits->integer?r_texturebits->integer:glConfig.colorBits); + Com_Printf ( "%i/%i shdrs/srfs %i leafs %i vrts %i/%i tris %.2fMB tex %.2f dc\n", + backEnd.pc.c_shaders, backEnd.pc.c_surfaces, tr.pc.c_leafs, backEnd.pc.c_vertexes, + backEnd.pc.c_indexes/3, backEnd.pc.c_totalIndexes/3, + texSize, backEnd.pc.c_overDraw / (float)(glConfig.vidWidth * glConfig.vidHeight) ); + } else if (r_speeds->integer == 2) { + Com_Printf ( "(patch) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_patch_in, tr.pc.c_sphere_cull_patch_clip, tr.pc.c_sphere_cull_patch_out, + tr.pc.c_box_cull_patch_in, tr.pc.c_box_cull_patch_clip, tr.pc.c_box_cull_patch_out ); + Com_Printf ( "(md3) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_md3_in, tr.pc.c_sphere_cull_md3_clip, tr.pc.c_sphere_cull_md3_out, + tr.pc.c_box_cull_md3_in, tr.pc.c_box_cull_md3_clip, tr.pc.c_box_cull_md3_out ); + } else if (r_speeds->integer == 3) { + Com_Printf ( "viewcluster: %i\n", tr.viewCluster ); + } else if (r_speeds->integer == 4) { + if ( backEnd.pc.c_dlightVertexes ) { + Com_Printf ( "dlight srf:%i culled:%i verts:%i tris:%i\n", + tr.pc.c_dlightSurfaces, tr.pc.c_dlightSurfacesCulled, + backEnd.pc.c_dlightVertexes, backEnd.pc.c_dlightIndexes / 3 ); + } + } + else if (r_speeds->integer == 5 ) + { + Com_Printf ("zFar: %.0f\n", tr.viewParms.zFar ); + } + else if (r_speeds->integer == 6 ) + { + Com_Printf ("flare adds:%i tests:%i renders:%i\n", + backEnd.pc.c_flareAdds, backEnd.pc.c_flareTests, backEnd.pc.c_flareRenders ); + } + else if (r_speeds->integer == 7) { + const float texSize = R_SumOfUsedImages(qtrue) / (1048576.0f); + const float backBuff= glConfig.vidWidth * glConfig.vidHeight * glConfig.colorBits / (8.0f * 1024*1024); + const float depthBuff= glConfig.vidWidth * glConfig.vidHeight * glConfig.depthBits / (8.0f * 1024*1024); + const float stencilBuff= glConfig.vidWidth * glConfig.vidHeight * glConfig.stencilBits / (8.0f * 1024*1024); + Com_Printf ( "Tex MB %.2f + buffers %.2f MB = Total %.2fMB\n", + texSize, backBuff*2+depthBuff+stencilBuff, texSize+backBuff*2+depthBuff+stencilBuff); + } +#endif + + Com_Memset( &tr.pc, 0, sizeof( tr.pc ) ); + Com_Memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); +} + + +/* +==================== +R_InitCommandBuffers +==================== +*/ +void R_InitCommandBuffers( void ) { +} + +/* +==================== +R_ShutdownCommandBuffers +==================== +*/ +void R_ShutdownCommandBuffers( void ) { +} + +/* +==================== +R_IssueRenderCommands +==================== +*/ +void R_IssueRenderCommands( qboolean runPerformanceCounters ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData->commands; + assert(cmdList); // bk001205 + // add an end-of-list command + *(int *)(cmdList->cmds + cmdList->used) = RC_END_OF_LIST; + + // clear it out, in case this is a sync and not a buffer flip + cmdList->used = 0; + + // at this point, the back end thread is idle, so it is ok + // to look at it's performance counters + if ( runPerformanceCounters ) { + R_PerformanceCounters(); + } + + // actually start the commands going + if ( !r_skipBackEnd->integer ) { + // let it start on the new batch + RB_ExecuteRenderCommands( cmdList->cmds ); + } +} + + +/* +==================== +R_SyncRenderThread + +Issue any pending commands and wait for them to complete. +After exiting, the render thread will have completed its work +and will remain idle and the main thread is free to issue +OpenGL calls until R_IssueRenderCommands is called. +==================== +*/ +void R_SyncRenderThread( void ) { +#ifndef _XBOX + if ( !tr.registered ) { + return; + } + R_IssueRenderCommands( qfalse ); +#endif +} + +/* +============ +R_GetCommandBuffer + +make sure there is enough command space, waiting on the +render thread if needed. +============ +*/ +void *R_GetCommandBuffer( int bytes ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData->commands; + + // always leave room for the end of list command + if ( cmdList->used + bytes + 4 > MAX_RENDER_COMMANDS ) { +#if defined(_DEBUG) && defined(_XBOX) + Com_Printf(S_COLOR_RED"Command buffer overflow! Tell Brian.\n"); +#endif + if ( bytes > MAX_RENDER_COMMANDS - 4 ) { + Com_Error( ERR_FATAL, "R_GetCommandBuffer: bad size %i", bytes ); + } + // if we run out of room, just start dropping commands + return NULL; + } + + cmdList->used += bytes; + + return cmdList->cmds + cmdList->used - bytes; +} + + +/* +============= +R_AddDrawSurfCmd + +============= +*/ +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ) { + drawSurfsCommand_t *cmd; + + cmd = (drawSurfsCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_DRAW_SURFS; + + cmd->drawSurfs = drawSurfs; + cmd->numDrawSurfs = numDrawSurfs; + + cmd->refdef = tr.refdef; + cmd->viewParms = tr.viewParms; + +#ifdef _XBOX + cmd->clientNum = ClientManager::ActiveClientNum(); +#endif +} + + +/* +============= +RE_SetColor + +Passing NULL will set the color to white +============= +*/ +void RE_SetColor( const float *rgba ) { + setColorCommand_t *cmd; + + cmd = (setColorCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SET_COLOR; + if ( !rgba ) { + static float colorWhite[4] = { 1, 1, 1, 1 }; + + rgba = colorWhite; + } + + cmd->color[0] = rgba[0]; + cmd->color[1] = rgba[1]; + cmd->color[2] = rgba[2]; + cmd->color[3] = rgba[3]; +} + + +/* +============= +RE_StretchPic +============= +*/ +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + stretchPicCommand_t *cmd; + + cmd = (stretchPicCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_STRETCH_PIC; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; +} + +/* +============= +RE_RotatePic +============= +*/ +void RE_RotatePic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ) { + rotatePicCommand_t *cmd; + + cmd = (rotatePicCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_ROTATE_PIC; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; + cmd->a = a; +} + +/* +============= +RE_RotatePic2 +============= +*/ +void RE_RotatePic2 ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ) { + rotatePicCommand_t *cmd; + + cmd = (rotatePicCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_ROTATE_PIC2; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; + cmd->a = a; +} + +void RE_RenderWorldEffects(void) +{ + drawBufferCommand_t *cmd; + + cmd = (drawBufferCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_WORLD_EFFECTS; +} + +void RE_RenderAutoMap(void) +{ + drawBufferCommand_t *cmd; + + cmd = (drawBufferCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) + { + return; + } + cmd->commandId = RC_AUTO_MAP; +} + +/* +==================== +RE_BeginFrame + +If running in stereo, RE_BeginFrame will be called twice +for each RE_EndFrame +==================== +*/ +void RE_BeginFrame( stereoFrame_t stereoFrame ) { + drawBufferCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + glState.finishCalled = qfalse; + + tr.frameCount++; + tr.frameSceneNum = 0; + + // + // do overdraw measurement + // +#ifndef _XBOX + if ( r_measureOverdraw->integer ) + { + if ( glConfig.stencilBits < 4 ) + { + Com_Printf ("Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits ); + Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } + else if ( r_shadows->integer == 2 ) + { + Com_Printf ("Warning: stencil shadows and overdraw measurement are mutually exclusive\n" ); + Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } + else + { + R_SyncRenderThread(); + qglEnable( GL_STENCIL_TEST ); + qglStencilMask( ~0U ); + qglClearStencil( 0U ); + qglStencilFunc( GL_ALWAYS, 0U, ~0U ); + qglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + } + r_measureOverdraw->modified = qfalse; + } + else + { + // this is only reached if it was on and is now off + if ( r_measureOverdraw->modified ) { + R_SyncRenderThread(); + qglDisable( GL_STENCIL_TEST ); + } + r_measureOverdraw->modified = qfalse; + } +#endif + + // + // texturemode stuff + // + if ( r_textureMode->modified || r_ext_texture_filter_anisotropic->modified) { + R_SyncRenderThread(); + GL_TextureMode( r_textureMode->string ); + r_textureMode->modified = qfalse; + r_ext_texture_filter_anisotropic->modified = qfalse; + } + + // + // gamma stuff + // + if ( r_gamma->modified ) { + r_gamma->modified = qfalse; + + R_SyncRenderThread(); + R_SetColorMappings(); + } + + // check for errors + if ( !r_ignoreGLErrors->integer ) { + int err; + + R_SyncRenderThread(); + if ( ( err = qglGetError() ) != GL_NO_ERROR ) { + Com_Error( ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!\n", err ); + } + } + + // + // draw buffer stuff + // + cmd = (drawBufferCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_DRAW_BUFFER; + + if ( glConfig.stereoEnabled ) { + if ( stereoFrame == STEREO_LEFT ) { + cmd->buffer = (int)GL_BACK_LEFT; + } else if ( stereoFrame == STEREO_RIGHT ) { + cmd->buffer = (int)GL_BACK_RIGHT; + } else { + Com_Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + } + } else { + if ( stereoFrame != STEREO_CENTER ) { + Com_Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame ); + } +// if ( !Q_stricmp( r_drawBuffer->string, "GL_FRONT" ) ) { +// cmd->buffer = (int)GL_FRONT; +// } else + { + cmd->buffer = (int)GL_BACK; + } + } +} + + +/* +============= +RE_EndFrame + +Returns the number of msec spent in the back end +============= +*/ +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) { + swapBuffersCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = (swapBuffersCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SWAP_BUFFERS; + +#ifdef _XBOX + if (!qglBeginFrame()) return; +#endif + + R_IssueRenderCommands( qtrue ); + +#ifdef _XBOX + qglEndFrame(); +#endif + + // use the other buffers next frame, because another CPU + // may still be rendering into the current ones + R_ToggleSmpFrame(); + + if ( frontEndMsec ) { + *frontEndMsec = tr.frontEndMsec; + } + tr.frontEndMsec = 0; + if ( backEndMsec ) { + *backEndMsec = backEnd.pc.msec; + } + backEnd.pc.msec = 0; +} + diff --git a/codemp/renderer/tr_curve.cpp b/codemp/renderer/tr_curve.cpp new file mode 100644 index 0000000..9e418ee --- /dev/null +++ b/codemp/renderer/tr_curve.cpp @@ -0,0 +1,612 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +/* + +This file does all of the processing necessary to turn a raw grid of points +read from the map file into a srfGridMesh_t ready for rendering. + +The level of detail solution is direction independent, based only on subdivided +distance from the true curve. + +Only a single entry point: + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + +*/ + + +/* +============ +LerpDrawVert +============ +*/ +static void LerpDrawVert( drawVert_t *a, drawVert_t *b, drawVert_t *out ) +{ + int k; + + out->xyz[0] = 0.5 * (a->xyz[0] + b->xyz[0]); + out->xyz[1] = 0.5 * (a->xyz[1] + b->xyz[1]); + out->xyz[2] = 0.5 * (a->xyz[2] + b->xyz[2]); + + out->st[0] = 0.5 * (a->st[0] + b->st[0]); + out->st[1] = 0.5 * (a->st[1] + b->st[1]); + + out->normal[0] = 0.5 * (a->normal[0] + b->normal[0]); + out->normal[1] = 0.5 * (a->normal[1] + b->normal[1]); + out->normal[2] = 0.5 * (a->normal[2] + b->normal[2]); + + for(k=0;klightmap[k][0] = 0.5 * (a->lightmap[k][0] + b->lightmap[k][0]); + out->lightmap[k][1] = 0.5 * (a->lightmap[k][1] + b->lightmap[k][1]); + + out->color[k][0] = (a->color[k][0] + b->color[k][0]) >> 1; + out->color[k][1] = (a->color[k][1] + b->color[k][1]) >> 1; + out->color[k][2] = (a->color[k][2] + b->color[k][2]) >> 1; + out->color[k][3] = (a->color[k][3] + b->color[k][3]) >> 1; + } +} + +/* +============ +Transpose +============ +*/ +static void Transpose( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + drawVert_t temp; + + if ( width > height ) { + for ( i = 0 ; i < height ; i++ ) { + for ( j = i + 1 ; j < width ; j++ ) { + if ( j < height ) { + // swap the value + temp = ctrl[j][i]; + ctrl[j][i] = ctrl[i][j]; + ctrl[i][j] = temp; + } else { + // just copy + ctrl[j][i] = ctrl[i][j]; + } + } + } + } else { + for ( i = 0 ; i < width ; i++ ) { + for ( j = i + 1 ; j < height ; j++ ) { + if ( j < width ) { + // swap the value + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[j][i]; + ctrl[j][i] = temp; + } else { + // just copy + ctrl[i][j] = ctrl[j][i]; + } + } + } + } + +} + + +/* +================= +MakeMeshNormals + +Handles all the complicated wrapping and degenerate cases +================= +*/ +static void MakeMeshNormals( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count; + vec3_t base; + vec3_t delta; + int x, y; + drawVert_t *dv; + vec3_t around[8], temp; + qboolean good[8]; + qboolean wrapWidth, wrapHeight; + float len; +static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + wrapWidth = qfalse; + for ( i = 0 ; i < height ; i++ ) { + VectorSubtract( ctrl[i][0].xyz, ctrl[i][width-1].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == height ) { + wrapWidth = qtrue; + } + + wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + VectorSubtract( ctrl[0][i].xyz, ctrl[height-1][i].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == width) { + wrapHeight = qtrue; + } + + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + count = 0; + dv = &ctrl[j][i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = qfalse; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + VectorSubtract( ctrl[y][x].xyz, base, temp ); + if ( VectorNormalize2( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } else { + good[k] = qtrue; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + CrossProduct( around[(k+1)&7], around[k], normal ); + if ( VectorNormalize2( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + if ( count == 0 ) { +//printf("bad normal\n"); + count = 1; + } + VectorNormalize2( sum, dv->normal ); + } + } +} + +/* +============ +InvertCtrl +============ +*/ +static void InvertCtrl( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + drawVert_t temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width/2 ; j++ ) { + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[i][width-1-j]; + ctrl[i][width-1-j] = temp; + } + } +} + +/* +================= +InvertErrorTable +================= +*/ +static void InvertErrorTable( float errorTable[2][MAX_GRID_SIZE], int width, int height ) { + int i; + float copy[2][MAX_GRID_SIZE]; + + Com_Memcpy( copy, errorTable, sizeof( copy ) ); + + for ( i = 0 ; i < width ; i++ ) { + errorTable[1][i] = copy[0][i]; //[width-1-i]; + } + + for ( i = 0 ; i < height ; i++ ) { + errorTable[0][i] = copy[1][height-1-i]; + } + +} + +/* +================== +PutPointsOnCurve +================== +*/ +static void PutPointsOnCurve( drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], + int width, int height ) { + int i, j; + drawVert_t prev, next; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 1 ; j < height ; j += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j+1][i], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j-1][i], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } + + + for ( j = 0 ; j < height ; j++ ) { + for ( i = 1 ; i < width ; i += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j][i+1], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j][i-1], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } +} + +/* +================= +R_CreateSurfaceGridMesh +================= +*/ +srfGridMesh_t *R_CreateSurfaceGridMesh(int width, int height, + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE] ) { + int i, j, size; + drawVert_t *vert; + vec3_t tmpVec; + srfGridMesh_t *grid; + + // copy the results out to a grid + size = (width * height - 1) * sizeof( drawVert_t ) + sizeof( *grid ); + +#ifdef PATCH_STITCHING + grid = (struct srfGridMesh_s *)/*Hunk_Alloc*/ Z_Malloc( size, TAG_GRIDMESH, qfalse ); + Com_Memset(grid, 0, size); + + grid->widthLodError = (float *)/*Hunk_Alloc*/ Z_Malloc( width * 4, TAG_GRIDMESH, qfalse ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = (float *)/*Hunk_Alloc*/ Z_Malloc( height * 4, TAG_GRIDMESH, qfalse ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); +#else + grid = Hunk_Alloc( size ); + Com_Memset(grid, 0, size); + + grid->widthLodError = Hunk_Alloc( width * 4 ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = Hunk_Alloc( height * 4 ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); +#endif + + grid->width = width; + grid->height = height; + grid->surfaceType = SF_GRID; + ClearBounds( grid->meshBounds[0], grid->meshBounds[1] ); + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + vert = &grid->verts[j*width+i]; + *vert = ctrl[j][i]; + AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] ); + } + } + + // compute local origin and bounds + VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin ); + VectorScale( grid->localOrigin, 0.5f, grid->localOrigin ); + VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec ); + grid->meshRadius = VectorLength( tmpVec ); + + VectorCopy( grid->localOrigin, grid->lodOrigin ); + grid->lodRadius = grid->meshRadius; + // + return grid; +} + +/* +================= +R_FreeSurfaceGridMesh +================= +*/ +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ) { + Z_Free(grid->widthLodError); + Z_Free(grid->heightLodError); + Z_Free(grid); +} + +/* +================= +R_SubdividePatchToGrid +================= +*/ +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + int i, j, k, l; + drawVert_t prev, next, mid; + float len, maxLen; + int dir; + int t; + MAC_STATIC drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + ctrl[j][i] = points[j*width+i]; + } + } + + for ( dir = 0 ; dir < 2 ; dir++ ) { + + for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) { + errorTable[dir][j] = 0; + } + + // horizontal subdivisions + for ( j = 0 ; j + 2 < width ; j += 2 ) { + // check subdivided midpoints against control points + + // FIXME: also check midpoints of adjacent patches against the control points + // this would basically stitch all patches in the same LOD group together. + + maxLen = 0; + for ( i = 0 ; i < height ; i++ ) { + vec3_t midxyz; + vec3_t dir; + vec3_t projected; + float d; + + // calculate the point on the curve + for ( l = 0 ; l < 3 ; l++ ) { + midxyz[l] = (ctrl[i][j].xyz[l] + ctrl[i][j+1].xyz[l] * 2 + + ctrl[i][j+2].xyz[l] ) * 0.25f; + } + + // see how far off the line it is + // using dist-from-line will not account for internal + // texture warping, but it gives a lot less polygons than + // dist-from-midpoint + VectorSubtract( midxyz, ctrl[i][j].xyz, midxyz ); + VectorSubtract( ctrl[i][j+2].xyz, ctrl[i][j].xyz, dir ); + VectorNormalize( dir ); + + d = DotProduct( midxyz, dir ); + VectorScale( dir, d, projected ); + VectorSubtract( midxyz, projected, midxyz); + len = VectorLengthSquared( midxyz ); // we will do the sqrt later + + if ( len > maxLen ) { + maxLen = len; + } + } + + maxLen = sqrt(maxLen); + // if all the points are on the lines, remove the entire columns + if ( maxLen < 0.1f ) { + errorTable[dir][j+1] = 999; + continue; + } + + // see if we want to insert subdivided columns + if ( width + 2 > MAX_GRID_SIZE ) { + errorTable[dir][j+1] = 1.0f/maxLen; + continue; // can't subdivide any more + } + + if ( maxLen <= r_subdivisions->value ) { + errorTable[dir][j+1] = 1.0f/maxLen; + continue; // didn't need subdivision + } + + errorTable[dir][j+2] = 1.0f/maxLen; + + // insert two columns and replace the peak + width += 2; + for ( i = 0 ; i < height ; i++ ) { + LerpDrawVert( &ctrl[i][j], &ctrl[i][j+1], &prev ); + LerpDrawVert( &ctrl[i][j+1], &ctrl[i][j+2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = width - 1 ; k > j + 3 ; k-- ) { + ctrl[i][k] = ctrl[i][k-2]; + } + ctrl[i][j + 1] = prev; + ctrl[i][j + 2] = mid; + ctrl[i][j + 3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + Transpose( width, height, ctrl ); + t = width; + width = height; + height = t; + } + + + // put all the aproximating points on the curve + PutPointsOnCurve( ctrl, width, height ); + + // cull out any rows or columns that are colinear + for ( i = 1 ; i < width-1 ; i++ ) { + if ( errorTable[0][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < width ; j++ ) { + for ( k = 0 ; k < height ; k++ ) { + ctrl[k][j-1] = ctrl[k][j]; + } + errorTable[0][j-1] = errorTable[0][j]; + } + width--; + } + + for ( i = 1 ; i < height-1 ; i++ ) { + if ( errorTable[1][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < height ; j++ ) { + for ( k = 0 ; k < width ; k++ ) { + ctrl[j-1][k] = ctrl[j][k]; + } + errorTable[1][j-1] = errorTable[1][j]; + } + height--; + } + +#if 1 + // flip for longest tristrips as an optimization + // the results should be visually identical with or + // without this step + if ( height > width ) { + Transpose( width, height, ctrl ); + InvertErrorTable( errorTable, width, height ); + t = width; + width = height; + height = t; + InvertCtrl( width, height, ctrl ); + } +#endif + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + return R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); +} + +/* +=============== +R_GridInsertColumn +=============== +*/ +srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ) { + int i, j; + int width, height, oldwidth; + MAC_STATIC drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + + oldwidth = 0; + width = grid->width + 1; + if (width > MAX_GRID_SIZE) + return NULL; + height = grid->height; + for (i = 0; i < width; i++) { + if (i == column) { + //insert new column + for (j = 0; j < grid->height; j++) { + LerpDrawVert( &grid->verts[j * grid->width + i-1], &grid->verts[j * grid->width + i], &ctrl[j][i] ); + if (j == row) + VectorCopy(point, ctrl[j][i].xyz); + } + errorTable[0][i] = loderror; + continue; + } + errorTable[0][i] = grid->widthLodError[oldwidth]; + for (j = 0; j < grid->height; j++) { + ctrl[j][i] = grid->verts[j * grid->width + oldwidth]; + } + oldwidth++; + } + for (j = 0; j < grid->height; j++) { + errorTable[1][j] = grid->heightLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + VectorCopy(grid->lodOrigin, lodOrigin); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMesh(grid); + // create a new grid + grid = R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); + grid->lodRadius = lodRadius; + VectorCopy(lodOrigin, grid->lodOrigin); + return grid; +} + +/* +=============== +R_GridInsertRow +=============== +*/ +srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ) { + int i, j; + int width, height, oldheight; + MAC_STATIC drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + float lodRadius; + vec3_t lodOrigin; + + oldheight = 0; + width = grid->width; + height = grid->height + 1; + if (height > MAX_GRID_SIZE) + return NULL; + for (i = 0; i < height; i++) { + if (i == row) { + //insert new row + for (j = 0; j < grid->width; j++) { + LerpDrawVert( &grid->verts[(i-1) * grid->width + j], &grid->verts[i * grid->width + j], &ctrl[i][j] ); + if (j == column) + VectorCopy(point, ctrl[i][j].xyz); + } + errorTable[1][i] = loderror; + continue; + } + errorTable[1][i] = grid->heightLodError[oldheight]; + for (j = 0; j < grid->width; j++) { + ctrl[i][j] = grid->verts[oldheight * grid->width + j]; + } + oldheight++; + } + for (j = 0; j < grid->width; j++) { + errorTable[0][j] = grid->widthLodError[j]; + } + // put all the aproximating points on the curve + //PutPointsOnCurve( ctrl, width, height ); + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + VectorCopy(grid->lodOrigin, lodOrigin); + lodRadius = grid->lodRadius; + // free the old grid + R_FreeSurfaceGridMesh(grid); + // create a new grid + grid = R_CreateSurfaceGridMesh( width, height, ctrl, errorTable ); + grid->lodRadius = lodRadius; + VectorCopy(lodOrigin, grid->lodOrigin); + return grid; +} diff --git a/codemp/renderer/tr_curve_xbox.cpp b/codemp/renderer/tr_curve_xbox.cpp new file mode 100644 index 0000000..ac25643 --- /dev/null +++ b/codemp/renderer/tr_curve_xbox.cpp @@ -0,0 +1,536 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +/* + +This file does all of the processing necessary to turn a raw grid of points +read from the map file into a srfGridMesh_t ready for rendering. + +The level of detail solution is direction independent, based only on subdivided +distance from the true curve. + +Only a single entry point: + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + +*/ + + +/* +============ +LerpDrawVert +============ +*/ +static void LerpDrawVert( drawVert_t *a, drawVert_t *b, drawVert_t *out ) { + int k; + out->xyz[0] = 0.5 * (a->xyz[0] + b->xyz[0]); + out->xyz[1] = 0.5 * (a->xyz[1] + b->xyz[1]); + out->xyz[2] = 0.5 * (a->xyz[2] + b->xyz[2]); + + out->dvst[0] = (short)(0.5 * (float)(a->dvst[0] + b->dvst[0])); + out->dvst[1] = (short)(0.5 * (float)(a->dvst[1] + b->dvst[1])); + + out->normal[0] = 0.5 * (a->normal[0] + b->normal[0]); + out->normal[1] = 0.5 * (a->normal[1] + b->normal[1]); + out->normal[2] = 0.5 * (a->normal[2] + b->normal[2]); + + for(k=0;kdvlightmap[k][0] = (short)(0.5 * (float)(a->dvlightmap[k][0] + b->dvlightmap[k][0])); + out->dvlightmap[k][1] = (short)(0.5 * (float)(a->dvlightmap[k][1] + b->dvlightmap[k][1])); + + // Need to do averaging per every four bits + for (int j = 0; j < 2; ++j) + { + byte ah, al, bh, bl; + ah = a->dvcolor[k][j] >> 4; + al = a->dvcolor[k][j] & 0x0F; + bh = b->dvcolor[k][j] >> 4; + bl = b->dvcolor[k][j] & 0x0F; + out->dvcolor[k][j] = (((ah+bh) / 2) << 4) | ((al+bl) / 2); + } + } +} + +/* +============ +Transpose +============ +*/ +static void Transpose( int width, int height, drawVert_t* ctrl/*[MAX_GRID_SIZE][MAX_GRID_SIZE]*/ ) { + int i, j; + drawVert_t temp; + + if ( width > height ) { + for ( i = 0 ; i < height ; i++ ) { + for ( j = i + 1 ; j < width ; j++ ) { + if ( j < height ) { + // swap the value + temp = ctrl[j*MAX_GRID_SIZE+i]; + ctrl[j*MAX_GRID_SIZE+i] = ctrl[i*MAX_GRID_SIZE+j]; + ctrl[i*MAX_GRID_SIZE+j] = temp; + } else { + // just copy + ctrl[j*MAX_GRID_SIZE+i] = ctrl[i*MAX_GRID_SIZE+j]; + } + } + } + } else { + for ( i = 0 ; i < width ; i++ ) { + for ( j = i + 1 ; j < height ; j++ ) { + if ( j < width ) { + // swap the value + temp = ctrl[i*MAX_GRID_SIZE+j]; + ctrl[i*MAX_GRID_SIZE+j] = ctrl[j*MAX_GRID_SIZE+i]; + ctrl[j*MAX_GRID_SIZE+i] = temp; + } else { + // just copy + ctrl[i*MAX_GRID_SIZE+j] = ctrl[j*MAX_GRID_SIZE+i]; + } + } + } + } + +} + +/* +================= +MakeMeshNormals + +Handles all the complicated wrapping and degenerate cases +================= +*/ +static void MakeMeshNormals( int width, int height, drawVert_t* ctrl/*[MAX_GRID_SIZE][MAX_GRID_SIZE]*/ ) { + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count; + vec3_t base; + vec3_t delta; + int x, y; + drawVert_t *dv; + vec3_t around[8], temp; + qboolean good[8]; + qboolean wrapWidth, wrapHeight; + float len; + static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + wrapWidth = qfalse; + for ( i = 0 ; i < height ; i++ ) { + VectorSubtract( ctrl[i*MAX_GRID_SIZE+0].xyz, ctrl[i*MAX_GRID_SIZE+width-1].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == height ) { + wrapWidth = qtrue; + } + + wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + VectorSubtract( ctrl[0*MAX_GRID_SIZE+i].xyz, ctrl[(height-1)*MAX_GRID_SIZE+i].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == width) { + wrapHeight = qtrue; + } + + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + count = 0; + dv = &ctrl[j*MAX_GRID_SIZE+i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = qfalse; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + VectorSubtract( ctrl[y*MAX_GRID_SIZE+x].xyz, base, temp ); + if ( VectorNormalize2( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } else { + good[k] = qtrue; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + CrossProduct( around[(k+1)&7], around[k], normal ); + if ( VectorNormalize2( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + if ( count == 0 ) { +//printf("bad normal\n"); + count = 1; + } + VectorNormalize2( sum, dv->normal ); + } + } +} + +/* +============ +InvertCtrl +============ +*/ +static void InvertCtrl( int width, int height, drawVert_t* ctrl/*[MAX_GRID_SIZE][MAX_GRID_SIZE]*/ ) { + int i, j; + drawVert_t temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width/2 ; j++ ) { + temp = ctrl[i*MAX_GRID_SIZE+j]; + ctrl[i*MAX_GRID_SIZE+j] = ctrl[i*MAX_GRID_SIZE+width-1-j]; + ctrl[i*MAX_GRID_SIZE+width-1-j] = temp; + } + } +} + +/* +================= +InvertErrorTable +================= +*/ +static void InvertErrorTable( float* errorTable/*[2][MAX_GRID_SIZE]*/, int width, int height ) { + int i; + float copy[2][MAX_GRID_SIZE]; + + memcpy( copy, errorTable, sizeof( copy ) ); + + for ( i = 0 ; i < width ; i++ ) { + errorTable[1*MAX_GRID_SIZE+i] = copy[0][i]; //[width-1-i]; + } + + for ( i = 0 ; i < height ; i++ ) { + errorTable[0*MAX_GRID_SIZE+i] = copy[1][height-1-i]; + } + +} + +/* +================== +PutPointsOnCurve +================== +*/ +static void PutPointsOnCurve( drawVert_t* ctrl/*[MAX_GRID_SIZE][MAX_GRID_SIZE]*/, + int width, int height ) { + int i, j; + drawVert_t prev, next; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 1 ; j < height ; j += 2 ) { + LerpDrawVert( &ctrl[j*MAX_GRID_SIZE+i], &ctrl[(j+1)*MAX_GRID_SIZE+i], &prev ); + LerpDrawVert( &ctrl[j*MAX_GRID_SIZE+i], &ctrl[(j-1)*MAX_GRID_SIZE+i], &next ); + LerpDrawVert( &prev, &next, &ctrl[j*MAX_GRID_SIZE+i] ); + } + } + + + for ( j = 0 ; j < height ; j++ ) { + for ( i = 1 ; i < width ; i += 2 ) { + LerpDrawVert( &ctrl[j*MAX_GRID_SIZE+i], &ctrl[j*MAX_GRID_SIZE+i+1], &prev ); + LerpDrawVert( &ctrl[j*MAX_GRID_SIZE+i], &ctrl[j*MAX_GRID_SIZE+i-1], &next ); + LerpDrawVert( &prev, &next, &ctrl[j*MAX_GRID_SIZE+i] ); + } + } +} + +/* +================= +R_CreateSurfaceGridMesh +================= +*/ +srfGridMesh_t *R_CreateSurfaceGridMesh(int width, int height, + drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE] ) { + int i, j, size; + drawVert_t *vert; + vec3_t tmpVec; + srfGridMesh_t *grid; + + // copy the results out to a grid + size = (width * height - 1) * sizeof( drawVert_t ) + sizeof( *grid ); + +#ifdef PATCH_STITCHING + grid = (struct srfGridMesh_s *)/*Hunk_Alloc*/ Z_Malloc( size, TAG_GRIDMESH, qfalse ); + Com_Memset(grid, 0, size); + + grid->widthLodError = (float *)/*Hunk_Alloc*/ Z_Malloc( width * 4, TAG_GRIDMESH, qfalse ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = (float *)/*Hunk_Alloc*/ Z_Malloc( height * 4, TAG_GRIDMESH, qfalse ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); +#else + grid = Hunk_Alloc( size ); + Com_Memset(grid, 0, size); + + grid->widthLodError = Hunk_Alloc( width * 4 ); + Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = Hunk_Alloc( height * 4 ); + Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); +#endif + + grid->width = width; + grid->height = height; + grid->surfaceType = SF_GRID; + ClearBounds( grid->meshBounds[0], grid->meshBounds[1] ); + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + vert = &grid->verts[j*width+i]; + *vert = ctrl[j][i]; + AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] ); + } + } + + // compute local origin and bounds + VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin ); + VectorScale( grid->localOrigin, 0.5f, grid->localOrigin ); + VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec ); + grid->meshRadius = VectorLength( tmpVec ); + + VectorCopy( grid->localOrigin, grid->lodOrigin ); + grid->lodRadius = grid->meshRadius; + // + return grid; +} + +/* +================= +R_FreeSurfaceGridMesh +================= +*/ +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ) { + Z_Free(grid->widthLodError); + Z_Free(grid->heightLodError); + Z_Free(grid); +} + +/* +================= +R_SubdividePatchToGrid +================= +*/ +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, drawVert_t* points, + drawVert_t* ctrl, float* errorTable ) { + int i, j, k, l; + drawVert_t prev, next, mid; + float len, maxLen; + int dir; + int t; + srfGridMesh_t *grid; + drawVert_t *vert; + vec3_t tmpVec; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + ctrl[j*MAX_GRID_SIZE+i] = points[j*width+i]; + } + } + + for ( dir = 0 ; dir < 2 ; dir++ ) { + + for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) { + errorTable[dir*MAX_GRID_SIZE+j] = 0; + } + + // horizontal subdivisions + for ( j = 0 ; j + 2 < width ; j += 2 ) { + // check subdivided midpoints against control points + maxLen = 0; + for ( i = 0 ; i < height ; i++ ) { + vec3_t midxyz; + vec3_t dir; + vec3_t projected; + float d; + + // calculate the point on the curve + for ( l = 0 ; l < 3 ; l++ ) { + midxyz[l] = (ctrl[i*MAX_GRID_SIZE+j].xyz[l] + + ctrl[i*MAX_GRID_SIZE+j+1].xyz[l] * 2 + + ctrl[i*MAX_GRID_SIZE+j+2].xyz[l] ) * 0.25; + } + + // see how far off the line it is + // using dist-from-line will not account for internal + // texture warping, but it gives a lot less polygons than + // dist-from-midpoint + VectorSubtract( midxyz, ctrl[i*MAX_GRID_SIZE+j].xyz, midxyz ); + VectorSubtract( ctrl[i*MAX_GRID_SIZE+j+2].xyz, ctrl[i*MAX_GRID_SIZE+j].xyz, dir ); + VectorNormalize( dir ); + + d = DotProduct( midxyz, dir ); + VectorScale( dir, d, projected ); + VectorSubtract( midxyz, projected, midxyz); + len = VectorLengthSquared( midxyz ); + + if ( len > maxLen ) { + maxLen = len; + } + } + maxLen = sqrt(maxLen); + + // if all the points are on the lines, remove the entire columns + if ( maxLen < 0.1 ) { + errorTable[dir*MAX_GRID_SIZE+j+1] = 999; + continue; + } + + // see if we want to insert subdivided columns + if ( width + 2 > MAX_GRID_SIZE ) { + errorTable[dir*MAX_GRID_SIZE+j+1] = 1.0/maxLen; + continue; // can't subdivide any more + } + + if ( maxLen <= r_subdivisions->value ) { + errorTable[dir*MAX_GRID_SIZE+j+1] = 1.0/maxLen; + continue; // didn't need subdivision + } + + errorTable[dir*MAX_GRID_SIZE+j+2] = 1.0/maxLen; + + // insert two columns and replace the peak + width += 2; + for ( i = 0 ; i < height ; i++ ) { + LerpDrawVert( &ctrl[i*MAX_GRID_SIZE+j], &ctrl[i*MAX_GRID_SIZE+j+1], &prev ); + LerpDrawVert( &ctrl[i*MAX_GRID_SIZE+j+1], &ctrl[i*MAX_GRID_SIZE+j+2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = width - 1 ; k > j + 3 ; k-- ) { + ctrl[i*MAX_GRID_SIZE+k] = ctrl[i*MAX_GRID_SIZE+k-2]; + } + ctrl[i*MAX_GRID_SIZE+j + 1] = prev; + ctrl[i*MAX_GRID_SIZE+j + 2] = mid; + ctrl[i*MAX_GRID_SIZE+j + 3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + Transpose( width, height, ctrl ); + t = width; + width = height; + height = t; + } + + + // put all the aproximating points on the curve + PutPointsOnCurve( ctrl, width, height ); + + // cull out any rows or columns that are colinear + for ( i = 1 ; i < width-1 ; i++ ) { + if ( errorTable[0*MAX_GRID_SIZE+i] != 999 ) { + continue; + } + for ( j = i+1 ; j < width ; j++ ) { + for ( k = 0 ; k < height ; k++ ) { + ctrl[k*MAX_GRID_SIZE+j-1] = ctrl[k*MAX_GRID_SIZE+j]; + } + errorTable[0*MAX_GRID_SIZE+j-1] = errorTable[0*MAX_GRID_SIZE+j]; + } + width--; + } + + for ( i = 1 ; i < height-1 ; i++ ) { + if ( errorTable[1*MAX_GRID_SIZE+i] != 999 ) { + continue; + } + for ( j = i+1 ; j < height ; j++ ) { + for ( k = 0 ; k < width ; k++ ) { + ctrl[(j-1)*MAX_GRID_SIZE+k] = ctrl[j*MAX_GRID_SIZE+k]; + } + errorTable[1*MAX_GRID_SIZE+j-1] = errorTable[1*MAX_GRID_SIZE+j]; + } + height--; + } + +#if 1 + // flip for longest tristrips as an optimization + // the results should be visually identical with or + // without this step + if ( height > width ) { + Transpose( width, height, ctrl ); + InvertErrorTable( errorTable, width, height ); + t = width; + width = height; + height = t; + InvertCtrl( width, height, ctrl ); + } +#endif + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + // copy the results out to a grid + grid = (struct srfGridMesh_s *) Hunk_Alloc( (width * height - 1) * sizeof( drawVert_t ) + sizeof( *grid ) + width * 4 + height * 4, h_low ); + + grid->widthLodError = (float*)(((char*)grid) + (width * height - 1) * + sizeof(drawVert_t) + sizeof(*grid)); + memcpy( grid->widthLodError, &errorTable[0*MAX_GRID_SIZE], width * 4 ); + + grid->heightLodError = (float*)(((char*)grid->widthLodError) + width * 4); + memcpy( grid->heightLodError, &errorTable[1*MAX_GRID_SIZE], height * 4 ); + + grid->width = width; + grid->height = height; + grid->surfaceType = SF_GRID; + ClearBounds( grid->meshBounds[0], grid->meshBounds[1] ); + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + vert = &grid->verts[j*width+i]; + *vert = ctrl[j*MAX_GRID_SIZE+i]; + AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] ); + } + } + + // compute local origin and bounds + VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin ); + VectorScale( grid->localOrigin, 0.5f, grid->localOrigin ); + VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec ); + grid->meshRadius = VectorLength( tmpVec ); + + VectorCopy( grid->localOrigin, grid->lodOrigin ); + grid->lodRadius = grid->meshRadius; + + return grid; +} diff --git a/codemp/renderer/tr_flares.cpp b/codemp/renderer/tr_flares.cpp new file mode 100644 index 0000000..78f050d --- /dev/null +++ b/codemp/renderer/tr_flares.cpp @@ -0,0 +1,433 @@ +// tr_flares.c +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +/* +============================================================================= + +LIGHT FLARES + +A light flare is an effect that takes place inside the eye when bright light +sources are visible. The size of the flare reletive to the screen is nearly +constant, irrespective of distance, but the intensity should be proportional to the +projected area of the light source. + +A surface that has been flagged as having a light flare will calculate the depth +buffer value that it's midpoint should have when the surface is added. + +After all opaque surfaces have been rendered, the depth buffer is read back for +each flare in view. If the point has not been obscured by a closer surface, the +flare should be drawn. + +Surfaces that have a repeated texture should never be flagged as flaring, because +there will only be a single flare added at the midpoint of the polygon. + +To prevent abrupt popping, the intensity of the flare is interpolated up and +down as it changes visibility. This involves scene to scene state, unlike almost +all other aspects of the renderer, and is complicated by the fact that a single +frame may have multiple scenes. + +RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially +up to five or more times in a frame with 3D status bar icons). + +============================================================================= +*/ + + +// flare states maintain visibility over multiple frames for fading +// layers: view, mirror, menu +typedef struct flare_s { + struct flare_s *next; // for active chain + + int addedFrame; + + qboolean inPortal; // true if in a portal view of the scene + int frameSceneNum; + void *surface; + int fogNum; + + int fadeTime; + + qboolean visible; // state of last test + float drawIntensity; // may be non 0 even if !visible due to fading + + int windowX, windowY; + float eyeZ; + + vec3_t color; +} flare_t; + +#define MAX_FLARES 128 + +flare_t r_flareStructs[MAX_FLARES]; +flare_t *r_activeFlares, *r_inactiveFlares; + +/* +================== +R_ClearFlares +================== +*/ +void R_ClearFlares( void ) { + int i; + + Com_Memset( r_flareStructs, 0, sizeof( r_flareStructs ) ); + r_activeFlares = NULL; + r_inactiveFlares = NULL; + + for ( i = 0 ; i < MAX_FLARES ; i++ ) { + r_flareStructs[i].next = r_inactiveFlares; + r_inactiveFlares = &r_flareStructs[i]; + } +} + + +/* +================== +RB_AddFlare + +This is called at surface tesselation time +================== +*/ +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ) { + int i; + flare_t *f, *oldest; + vec3_t local; + float d; + vec4_t eye, clip, normalized, window; + + backEnd.pc.c_flareAdds++; + + // if the point is off the screen, don't bother adding it + // calculate screen coordinates and depth + R_TransformModelToClip( point, backEnd.ori.modelMatrix, + backEnd.viewParms.projectionMatrix, eye, clip ); + + // check to see if the point is completely off screen + for ( i = 0 ; i < 3 ; i++ ) { + if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) { + return; + } + } + + R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window ); + + if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth + || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) { + return; // shouldn't happen, since we check the clip[] above, except for FP rounding + } + + // see if a flare with a matching surface, scene, and view exists + oldest = r_flareStructs; + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + break; + } + } + + // allocate a new one + if (!f ) { + if ( !r_inactiveFlares ) { + // the list is completely full + return; + } + f = r_inactiveFlares; + r_inactiveFlares = r_inactiveFlares->next; + f->next = r_activeFlares; + r_activeFlares = f; + + f->surface = surface; + f->frameSceneNum = backEnd.viewParms.frameSceneNum; + f->inPortal = backEnd.viewParms.isPortal; + f->addedFrame = -1; + } + + if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) { + f->visible = qfalse; + f->fadeTime = backEnd.refdef.time - 2000; + } + + f->addedFrame = backEnd.viewParms.frameCount; + f->fogNum = fogNum; + + VectorCopy( color, f->color ); + + // fade the intensity of the flare down as the + // light surface turns away from the viewer + if ( normal ) { + VectorSubtract( backEnd.viewParms.ori.origin, point, local ); + VectorNormalizeFast( local ); + d = DotProduct( local, normal ); + VectorScale( f->color, d, f->color ); + } + + // save info needed to test + f->windowX = backEnd.viewParms.viewportX + window[0]; + f->windowY = backEnd.viewParms.viewportY + window[1]; + + f->eyeZ = eye[2]; +} + +/* +================== +RB_AddDlightFlares +================== +*/ +void RB_AddDlightFlares( void ) { + dlight_t *l; + int i, j, k; + fog_t *fog; + + if ( !r_flares->integer ) { + return; + } + + l = backEnd.refdef.dlights; + fog = tr.world->fogs; + for (i=0 ; inumfogs ; j++ ) { + fog = &tr.world->fogs[j]; + for ( k = 0 ; k < 3 ; k++ ) { + if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j == tr.world->numfogs ) { + j = 0; + } + + RB_AddFlare( (void *)l, j, l->origin, l->color, NULL ); + } +} + +/* +=============================================================================== + +FLARE BACK END + +=============================================================================== +*/ + +/* +================== +RB_TestFlare +================== +*/ +void RB_TestFlare( flare_t *f ) { + float depth; + qboolean visible; + float fade; + float screenZ; + + backEnd.pc.c_flareTests++; + + // doing a readpixels is as good as doing a glFinish(), so + // don't bother with another sync + glState.finishCalled = qfalse; + + // read back the z buffer contents + qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); + + screenZ = backEnd.viewParms.projectionMatrix[14] / + ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + visible = (qboolean)(( -f->eyeZ - -screenZ ) < 24); + + if ( visible ) { + if ( !f->visible ) { + f->visible = qtrue; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value; + } else { + if ( f->visible ) { + f->visible = qfalse; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = 1.0f - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value; + } + + if ( fade < 0 ) { + fade = 0; + } + if ( fade > 1 ) { + fade = 1; + } + + f->drawIntensity = fade; +} + + +/* +================== +RB_RenderFlare +================== +*/ +void RB_RenderFlare( flare_t *f ) { + float size; + vec3_t color; + int iColor[3]; + + backEnd.pc.c_flareRenders++; + + VectorScale( f->color, f->drawIntensity*tr.identityLight, color ); + iColor[0] = color[0] * 255; + iColor[1] = color[1] * 255; + iColor[2] = color[2] * 255; + +#ifdef _XBOX + if(glw_state->isWidescreen) + size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/720.0f + 8 / -f->eyeZ ); + else +#endif + size = backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0f + 8 / -f->eyeZ ); + + RB_BeginSurface( tr.flareShader, f->fogNum ); + + // FIXME: use quadstamp? + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); +} + +/* +================== +RB_RenderFlares + +Because flares are simulating an occular effect, they should be drawn after +everything (all views) in the entire frame has been drawn. + +Because of the way portals use the depth buffer to mark off areas, the +needed information would be lost after each view, so we are forced to draw +flares after each view. + +The resulting artifact is that flares in mirrors or portals don't dim properly +when occluded by something in the main view, and portal flares that should +extend past the portal edge will be overwritten. +================== +*/ +void RB_RenderFlares (void) { + flare_t *f; + flare_t **prev; + qboolean draw; + + if ( !r_flares->integer ) { + return; + } + +// RB_AddDlightFlares(); + + // perform z buffer readback on each flare in this view + draw = qfalse; + prev = &r_activeFlares; + while ( ( f = *prev ) != NULL ) { + // throw out any flares that weren't added last frame + if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) { + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + + // don't draw any here that aren't from this scene / portal + f->drawIntensity = 0; + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + RB_TestFlare( f ); + if ( f->drawIntensity ) { + draw = qtrue; + } else { + // this flare has completely faded out, so remove it from the chain + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + } + + prev = &f->next; + } + + if ( !draw ) { + return; // none visible + } + + if ( backEnd.viewParms.isPortal ) { + qglDisable (GL_CLIP_PLANE0); + } + + qglPushMatrix(); + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, + backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, + -99999, 99999 ); + + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal + && f->drawIntensity ) { + RB_RenderFlare( f ); + } + } + + qglPopMatrix(); + qglMatrixMode( GL_MODELVIEW ); + qglPopMatrix(); +} + diff --git a/codemp/renderer/tr_font.cpp b/codemp/renderer/tr_font.cpp new file mode 100644 index 0000000..34f0544 --- /dev/null +++ b/codemp/renderer/tr_font.cpp @@ -0,0 +1,1747 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "../qcommon/sstring.h" // stl string class won't compile in here (MS shite), so use Gil's. +#include "tr_local.h" +#include "tr_font.h" + +#include "../qcommon/stringed_ingame.h" + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// This file is shared in the single and multiplayer codebases, so be CAREFUL WHAT YOU ADD/CHANGE!!!!! +// +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +// These should be consecutive, big, and in order: +#define XB_GLYPH_A 10000 +#define XB_GLYPH_B 10001 +#define XB_GLYPH_W 10002 +#define XB_GLYPH_X 10003 +#define XB_GLYPH_Y 10004 + +const char* xbGlyphShaders[] = { + "gfx/menus/newFront/A", + "gfx/menus/newFront/B", + "gfx/menus/newFront/W", + "gfx/menus/newFront/X", + "gfx/menus/newFront/Y", +}; + +typedef enum +{ + eWestern, // ( I only care about asian languages in here at the moment ) + eRussian, // .. but now I need to care about this, since it uses a different TP + ePolish, // ditto + eKorean, + eTaiwanese, // 15x15 glyphs tucked against BR of 16x16 space + eJapanese, // 15x15 glyphs tucked against TL of 16x16 space + eChinese, // 15x15 glyphs tucked against TL of 16x16 space + eThai, // 16x16 cells with glyphs against left edge, special file (tha_widths.dat) for variable widths +} Language_e; + +// this is to cut down on all the stupid string compares I've been doing, and convert asian stuff to switch-case +// +Language_e GetLanguageEnum() +{ + return eWestern; +/* + static int iSE_Language_ModificationCount = -1234; // any old silly value that won't match the cvar mod count + static Language_e eLanguage = eWestern; + + // only re-strcmp() when language string has changed from what we knew it as... + // + if (iSE_Language_ModificationCount != se_language->modificationCount ) + { + iSE_Language_ModificationCount = se_language->modificationCount; + + if ( Language_IsRussian() ) eLanguage = eRussian; + else if ( Language_IsPolish() ) eLanguage = ePolish; + else if ( Language_IsKorean() ) eLanguage = eKorean; + else if ( Language_IsTaiwanese() ) eLanguage = eTaiwanese; + else if ( Language_IsJapanese() ) eLanguage = eJapanese; + else if ( Language_IsChinese() ) eLanguage = eChinese; + else if ( Language_IsThai() ) eLanguage = eThai; + else eLanguage = eWestern; + } + + return eLanguage; +*/ +} + +struct SBCSOverrideLanguages_t +{ + LPCSTR m_psName; + Language_e m_eLanguage; +}; + +// so I can do some stuff with for-next loops when I add polish etc... +// +SBCSOverrideLanguages_t g_SBCSOverrideLanguages[]= +{ + {"russian", eRussian}, + {"polish", ePolish}, + {NULL, eWestern} +}; + + + +//================================================ +// + +#define sFILENAME_THAI_WIDTHS "fonts/tha_widths.dat" +#define sFILENAME_THAI_CODES "fonts/tha_codes.dat" + +struct ThaiCodes_t +{ + map m_mapValidCodes; + vector m_viGlyphWidths; + string m_strInitFailureReason; // so we don't have to keep retrying to work this out + + void Clear( void ) + { + m_mapValidCodes.clear(); + m_viGlyphWidths.clear(); + m_strInitFailureReason = ""; // if blank, never failed, else says don't bother re-trying + } + + ThaiCodes_t() + { + Clear(); + } + + // convert a supplied 1,2 or 3-byte multiplied-up integer into a valid 0..n index, else -1... + // + int GetValidIndex( int iCode ) + { + map ::iterator it = m_mapValidCodes.find( iCode ); + if (it != m_mapValidCodes.end()) + { + return (*it).second; + } + + return -1; + } + + int GetWidth( int iGlyphIndex ) + { + if (iGlyphIndex < m_viGlyphWidths.size()) + { + return m_viGlyphWidths[ iGlyphIndex ]; + } + + assert(0); + return 0; + } + + // return is error message to display, or NULL for success + const char *Init(void) + { + if (m_mapValidCodes.empty() && m_viGlyphWidths.empty()) + { + if (m_strInitFailureReason.empty()) // never tried and failed already? + { + int *piData = NULL; // note , not , for []-access + // + // read the valid-codes table in... + // + int iBytesRead = FS_ReadFile( sFILENAME_THAI_CODES, (void **) &piData ); + if (iBytesRead > 0 && !(iBytesRead&3)) // valid length and multiple of 4 bytes long + { + int iTableEntries = iBytesRead / sizeof(int); + + for (int i=0; i < iTableEntries; i++) + { + m_mapValidCodes[ piData[i] ] = i; // convert MBCS code to sequential index... + } + FS_FreeFile( piData ); // dispose of original + + // now read in the widths... (I'll keep these in a simple STL vector, so they'all disappear when the entries do... + // + iBytesRead = FS_ReadFile( sFILENAME_THAI_WIDTHS, (void **) &piData ); + if (iBytesRead > 0 && !(iBytesRead&3) && iBytesRead>>2/*sizeof(int)*/ == iTableEntries) + { + for (int i=0; iwestern scaling info for all glyphs + int m_iAsianGlyphsAcross; // needed to dynamically calculate S,T coords + int m_iAsianPagesLoaded; + bool m_bAsianLastPageHalfHeight; + int m_iLanguageModificationCount; // doesn't matter what this is, so long as it's comparable as being changed + + ThaiCodes_t *m_pThaiData; + +public: + char m_sFontName[MAX_QPATH]; // eg "fonts/lcd" // needed for korean font-hint if we need >1 hangul set + int mPointSize; + int mHeight; + int mAscender; + int mDescender; + + bool mbRoundCalcs; // trying to make this !@#$%^ thing work with scaling + int m_iThisFont; // handle to itself + int m_iAltSBCSFont; // -1 == NULL // alternative single-byte font for languages like russian/polish etc that need to override high characters ? + int m_iOriginalFontWhenSBCSOverriden; + float m_fAltSBCSFontScaleFactor; // -1, else amount to adjust returned values by to make them fit the master western font they're substituting for + bool m_bIsFakeAlienLanguage; // ... if true, don't process as MBCS or override as SBCS etc + + CFontInfo(const char *fontName); +// CFontInfo(int fill) { memset(this, fill, sizeof(*this)); } // wtf? + ~CFontInfo(void) {} + + const int GetPointSize(void) const { return(mPointSize); } + const int GetHeight(void) const { return(mHeight); } + const int GetAscender(void) const { return(mAscender); } + const int GetDescender(void) const { return(mDescender); } + + const glyphInfo_t *GetLetter(const unsigned int uiLetter, int *piShader = NULL); + const int GetCollapsedAsianCode(ulong uiLetter) const; + + const int GetLetterWidth(const unsigned int uiLetter); + const int GetLetterHorizAdvance(const unsigned int uiLetter); + const int GetShader(void) const { return(mShader); } + + void FlagNoAsianGlyphs(void) { m_hAsianShaders[0] = 0; m_iLanguageModificationCount = -1; } // used during constructor + bool AsianGlyphsAvailable(void) const { return !!(m_hAsianShaders[0]); } + + void UpdateAsianIfNeeded( bool bForceReEval = false); +}; + +//================================================ + + + + +// round float to one decimal place... +// +float RoundTenth( float fValue ) +{ + return ( floorf( (fValue*10.0f) + 0.5f) ) / 10.0f; +} + + +int g_iCurrentFontIndex; // entry 0 is reserved index for missing/invalid, else ++ with each new font registered +vector g_vFontArray; +typedef map FontIndexMap_t; + FontIndexMap_t g_mapFontIndexes; +int g_iNonScaledCharRange; // this is used with auto-scaling of asian fonts, anything below this number is preserved in scale, anything above is scaled down by 0.75f + +//paletteRGBA_c lastcolour; + +// =============================== some korean stuff ======================================= + +#define KSC5601_HANGUL_HIBYTE_START 0xB0 // range is... +#define KSC5601_HANGUL_HIBYTE_STOP 0xC8 // ... inclusive +#define KSC5601_HANGUL_LOBYTE_LOBOUND 0xA0 // range is... +#define KSC5601_HANGUL_LOBYTE_HIBOUND 0xFF // ...bounding (ie only valid in between these points, but NULLs in charsets for these codes) +#define KSC5601_HANGUL_CODES_PER_ROW 96 // 2 more than the number of glyphs + +extern qboolean Language_IsKorean( void ); + +static inline bool Korean_ValidKSC5601Hangul( byte _iHi, byte _iLo ) +{ + return (_iHi >=KSC5601_HANGUL_HIBYTE_START && + _iHi <=KSC5601_HANGUL_HIBYTE_STOP && + _iLo > KSC5601_HANGUL_LOBYTE_LOBOUND && + _iLo < KSC5601_HANGUL_LOBYTE_HIBOUND + ); +} + +static inline bool Korean_ValidKSC5601Hangul( unsigned int uiCode ) +{ + return Korean_ValidKSC5601Hangul( uiCode >> 8, uiCode & 0xFF ); +} + + +// takes a KSC5601 double-byte hangul code and collapses down to a 0..n glyph index... +// Assumes rows are 96 wide (glyph slots), not 94 wide (actual glyphs), so I can ignore boundary markers +// +// (invalid hangul codes will return 0) +// +static int Korean_CollapseKSC5601HangulCode(unsigned int uiCode) +{ + if (Korean_ValidKSC5601Hangul( uiCode )) + { + uiCode -= (KSC5601_HANGUL_HIBYTE_START * 256) + KSC5601_HANGUL_LOBYTE_LOBOUND; // sneaky maths on both bytes, reduce to 0x0000 onwards + uiCode = ((uiCode >> 8) * KSC5601_HANGUL_CODES_PER_ROW) + (uiCode & 0xFF); + return uiCode; + } + return 0; +} + +static int Korean_InitFields(int &iGlyphTPs, LPCSTR &psLang) +{ + psLang = "kor"; + iGlyphTPs = GLYPH_MAX_KOREAN_SHADERS; + g_iNonScaledCharRange = 255; + return 32; // m_iAsianGlyphsAcross +} + +// ======================== some taiwanese stuff ============================== + +// (all ranges inclusive for Big5)... +// +#define BIG5_HIBYTE_START0 0xA1 // (misc chars + level 1 hanzi) +#define BIG5_HIBYTE_STOP0 0xC6 // +#define BIG5_HIBYTE_START1 0xC9 // (level 2 hanzi) +#define BIG5_HIBYTE_STOP1 0xF9 // +#define BIG5_LOBYTE_LOBOUND0 0x40 // +#define BIG5_LOBYTE_HIBOUND0 0x7E // +#define BIG5_LOBYTE_LOBOUND1 0xA1 // +#define BIG5_LOBYTE_HIBOUND1 0xFE // +#define BIG5_CODES_PER_ROW 160 // 3 more than the number of glyphs + +extern qboolean Language_IsTaiwanese( void ); + +static bool Taiwanese_ValidBig5Code( unsigned int uiCode ) +{ + const byte _iHi = (uiCode >> 8)&0xFF; + if ( (_iHi >= BIG5_HIBYTE_START0 && _iHi <= BIG5_HIBYTE_STOP0) + || (_iHi >= BIG5_HIBYTE_START1 && _iHi <= BIG5_HIBYTE_STOP1) + ) + { + const byte _iLo = uiCode & 0xFF; + + if ( (_iLo >= BIG5_LOBYTE_LOBOUND0 && _iLo <= BIG5_LOBYTE_HIBOUND0) || + (_iLo >= BIG5_LOBYTE_LOBOUND1 && _iLo <= BIG5_LOBYTE_HIBOUND1) + ) + { + return true; + } + } + + return false; +} + + +// only call this when Taiwanese_ValidBig5Code() has already returned true... +// +static bool Taiwanese_IsTrailingPunctuation( unsigned int uiCode ) +{ + // so far I'm just counting the first 21 chars, those seem to be all the basic punctuation... + // + if ( uiCode >= ((BIG5_HIBYTE_START0<<8)|BIG5_LOBYTE_LOBOUND0) && + uiCode < ((BIG5_HIBYTE_START0<<8)|BIG5_LOBYTE_LOBOUND0+20) + ) + { + return true; + } + + return false; +} + + +// takes a BIG5 double-byte code (including level 2 hanzi) and collapses down to a 0..n glyph index... +// Assumes rows are 160 wide (glyph slots), not 157 wide (actual glyphs), so I can ignore boundary markers +// +// (invalid big5 codes will return 0) +// +static int Taiwanese_CollapseBig5Code( unsigned int uiCode ) +{ + if (Taiwanese_ValidBig5Code( uiCode )) + { + uiCode -= (BIG5_HIBYTE_START0 * 256) + BIG5_LOBYTE_LOBOUND0; // sneaky maths on both bytes, reduce to 0x0000 onwards + if ( (uiCode & 0xFF) >= (BIG5_LOBYTE_LOBOUND1-1)-BIG5_LOBYTE_LOBOUND0) + { + uiCode -= ((BIG5_LOBYTE_LOBOUND1-1) - (BIG5_LOBYTE_HIBOUND0+1)) -1; + } + uiCode = ((uiCode >> 8) * BIG5_CODES_PER_ROW) + (uiCode & 0xFF); + return uiCode; + } + return 0; +} + +static int Taiwanese_InitFields(int &iGlyphTPs, LPCSTR &psLang) +{ + psLang = "tai"; + iGlyphTPs = GLYPH_MAX_TAIWANESE_SHADERS; + g_iNonScaledCharRange = 255; + return 64; // m_iAsianGlyphsAcross +} + +// ======================== some Japanese stuff ============================== + + +// ( all ranges inclusive for Shift-JIS ) +// +#define SHIFTJIS_HIBYTE_START0 0x81 +#define SHIFTJIS_HIBYTE_STOP0 0x9F +#define SHIFTJIS_HIBYTE_START1 0xE0 +#define SHIFTJIS_HIBYTE_STOP1 0xEF +// +#define SHIFTJIS_LOBYTE_START0 0x40 +#define SHIFTJIS_LOBYTE_STOP0 0x7E +#define SHIFTJIS_LOBYTE_START1 0x80 +#define SHIFTJIS_LOBYTE_STOP1 0xFC +#define SHIFTJIS_CODES_PER_ROW (((SHIFTJIS_LOBYTE_STOP0-SHIFTJIS_LOBYTE_START0)+1)+((SHIFTJIS_LOBYTE_STOP1-SHIFTJIS_LOBYTE_START1)+1)) + + +extern qboolean Language_IsJapanese( void ); + +static bool Japanese_ValidShiftJISCode( byte _iHi, byte _iLo ) +{ + if ( (_iHi >= SHIFTJIS_HIBYTE_START0 && _iHi <= SHIFTJIS_HIBYTE_STOP0) + || (_iHi >= SHIFTJIS_HIBYTE_START1 && _iHi <= SHIFTJIS_HIBYTE_STOP1) + ) + { + if ( (_iLo >= SHIFTJIS_LOBYTE_START0 && _iLo <= SHIFTJIS_LOBYTE_STOP0) || + (_iLo >= SHIFTJIS_LOBYTE_START1 && _iLo <= SHIFTJIS_LOBYTE_STOP1) + ) + { + return true; + } + } + + return false; +} + +static inline bool Japanese_ValidShiftJISCode( unsigned int uiCode ) +{ + return Japanese_ValidShiftJISCode( uiCode >> 8, uiCode & 0xFF ); +} + + +// only call this when Japanese_ValidShiftJISCode() has already returned true... +// +static bool Japanese_IsTrailingPunctuation( unsigned int uiCode ) +{ + // so far I'm just counting the first 18 chars, those seem to be all the basic punctuation... + // + if ( uiCode >= ((SHIFTJIS_HIBYTE_START0<<8)|SHIFTJIS_LOBYTE_START0) && + uiCode < ((SHIFTJIS_HIBYTE_START0<<8)|SHIFTJIS_LOBYTE_START0+18) + ) + { + return true; + } + + return false; +} + + +// takes a ShiftJIS double-byte code and collapse down to a 0..n glyph index... +// +// (invalid codes will return 0) +// +static int Japanese_CollapseShiftJISCode( unsigned int uiCode ) +{ + if (Japanese_ValidShiftJISCode( uiCode )) + { + uiCode -= ((SHIFTJIS_HIBYTE_START0<<8)|SHIFTJIS_LOBYTE_START0); // sneaky maths on both bytes, reduce to 0x0000 onwards + + if ( (uiCode & 0xFF) >= (SHIFTJIS_LOBYTE_START1)-SHIFTJIS_LOBYTE_START0) + { + uiCode -= ((SHIFTJIS_LOBYTE_START1)-SHIFTJIS_LOBYTE_STOP0)-1; + } + + if ( ((uiCode>>8)&0xFF) >= (SHIFTJIS_HIBYTE_START1)-SHIFTJIS_HIBYTE_START0) + { + uiCode -= (((SHIFTJIS_HIBYTE_START1)-SHIFTJIS_HIBYTE_STOP0)-1) << 8; + } + + uiCode = ((uiCode >> 8) * SHIFTJIS_CODES_PER_ROW) + (uiCode & 0xFF); + + return uiCode; + } + return 0; +} + + +static int Japanese_InitFields(int &iGlyphTPs, LPCSTR &psLang) +{ + psLang = "jap"; + iGlyphTPs = GLYPH_MAX_JAPANESE_SHADERS; + g_iNonScaledCharRange = 255; + return 64; // m_iAsianGlyphsAcross +} + +// ======================== some Chinese stuff ============================== + +#define GB_HIBYTE_START 0xA1 // range is... +#define GB_HIBYTE_STOP 0xF7 // ... inclusive +#define GB_LOBYTE_LOBOUND 0xA0 // range is... +#define GB_LOBYTE_HIBOUND 0xFF // ...bounding (ie only valid in between these points, but NULLs in charsets for these codes) +#define GB_CODES_PER_ROW 95 // 1 more than the number of glyphs + +extern qboolean Language_IsChinese( void ); + +static inline bool Chinese_ValidGBCode( byte _iHi, byte _iLo ) +{ + return (_iHi >=GB_HIBYTE_START && + _iHi <=GB_HIBYTE_STOP && + _iLo > GB_LOBYTE_LOBOUND && + _iLo < GB_LOBYTE_HIBOUND + ); +} + +static inline bool Chinese_ValidGBCode( unsigned int uiCode) +{ + return Chinese_ValidGBCode( uiCode >> 8, uiCode & 0xFF ); +} + + +// only call this when Chinese_ValidGBCode() has already returned true... +// +static bool Chinese_IsTrailingPunctuation( unsigned int uiCode ) +{ + // so far I'm just counting the first 13 chars, those seem to be all the basic punctuation... + // + if ( uiCode > ((GB_HIBYTE_START<<8)|GB_LOBYTE_LOBOUND) && + uiCode < ((GB_HIBYTE_START<<8)|GB_LOBYTE_LOBOUND+14) + ) + { + return true; + } + + return false; +} + + +// takes a GB double-byte code and collapses down to a 0..n glyph index... +// Assumes rows are 96 wide (glyph slots), not 94 wide (actual glyphs), so I can ignore boundary markers +// +// (invalid GB codes will return 0) +// +static int Chinese_CollapseGBCode( unsigned int uiCode ) +{ + if (Chinese_ValidGBCode( uiCode )) + { + uiCode -= (GB_HIBYTE_START * 256) + GB_LOBYTE_LOBOUND; // sneaky maths on both bytes, reduce to 0x0000 onwards + uiCode = ((uiCode >> 8) * GB_CODES_PER_ROW) + (uiCode & 0xFF); + return uiCode; + } + + return 0; +} + +static int Chinese_InitFields(int &iGlyphTPs, LPCSTR &psLang) +{ + psLang = "chi"; + iGlyphTPs = GLYPH_MAX_CHINESE_SHADERS; + g_iNonScaledCharRange = 255; + return 64; // m_iAsianGlyphsAcross +} + +// ======================== some Thai stuff ============================== + +//TIS 620-2533 + +#define TIS_GLYPHS_START 160 +#define TIS_SARA_AM 0xD3 // special case letter, both a new letter and a trailing accent for the prev one +ThaiCodes_t g_ThaiCodes; // the one and only instance of this object + +extern qboolean Language_IsThai( void ); + +/* +static int Thai_IsAccentChar( unsigned int uiCode ) +{ + switch (uiCode) + { + case 209: + case 212: case 213: case 214: case 215: case 216: case 217: case 218: + case 231: case 232: case 233: case 234: case 235: case 236: case 237: case 238: + return true; + } + + return false; +} +*/ + +// returns a valid Thai code (or 0), based on taking 1,2 or 3 bytes from the supplied byte stream +// Fills in with 1,2 or 3 +static int Thai_ValidTISCode( const byte *psString, int &iThaiBytes ) +{ + // try a 1-byte code first... + // + if (psString[0] >= 160) // so western letters drop through and use normal font + { + // this code is heavily little-endian, so someone else will need to port for Mac etc... (not my problem ;-) + // + union CodeToTry_t + { + char sChars[4]; + unsigned int uiCode; + }; + + CodeToTry_t CodeToTry; + CodeToTry.uiCode = 0; // important that we clear all 4 bytes in sChars here + + // thai codes can be up to 3 bytes long, so see how high we can get... + // + for (int i=0; i<3; i++) + { + CodeToTry.sChars[i] = psString[i]; + + int iIndex = g_ThaiCodes.GetValidIndex( CodeToTry.uiCode ); + if (iIndex == -1) + { + // failed, so return previous-longest code... + // + CodeToTry.sChars[i] = 0; + break; + } + } + iThaiBytes = i; + assert(i); // if 'i' was 0, then this may be an error, trying to get a thai accent as standalone char? + return CodeToTry.uiCode; + } + + return 0; +} + +// special case, thai can only break on certain letters, and since the rules are complicated then +// we tell the translators to put an underscore ('_') between each word even though in Thai they're +// all jammed together at final output onscreen... +// +static inline bool Thai_IsTrailingPunctuation( unsigned int uiCode ) +{ + return uiCode == '_'; +} + +// takes a TIS 1,2 or 3 byte code and collapse down to a 0..n glyph index... +// +// (invalid codes will return 0) +// +static int Thai_CollapseTISCode( unsigned int uiCode ) +{ + if (uiCode >= TIS_GLYPHS_START) // so western letters drop through as invalid + { + int iCollapsedIndex = g_ThaiCodes.GetValidIndex( uiCode ); + if (iCollapsedIndex != -1) + { + return iCollapsedIndex; + } + } + + return 0; +} + +static int Thai_InitFields(int &iGlyphTPs, LPCSTR &psLang) +{ + psLang = "tha"; + iGlyphTPs = GLYPH_MAX_THAI_SHADERS; + g_iNonScaledCharRange = INT_MAX; // in other words, don't scale any thai chars down + return 32; // m_iAsianGlyphsAcross +} + + +// ============================================================================ + +// takes char *, returns integer char at that point, and advances char * on by enough bytes to move +// past the letter (either western 1 byte or Asian multi-byte)... +// +// looks messy, but the actual execution route is quite short, so it's fast... +// +// Note that I have to have this 3-param form instead of advancing a passed-in "const char **psText" because of VM-crap where you can only change ptr-contents, not ptrs themselves. Bleurgh. Ditto the qtrue:qfalse crap instead of just returning stuff straight through. +// +unsigned int AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation /* = NULL */) +{ + const byte *psString = (const byte *) psText; // avoid sign-promote bug + unsigned int uiLetter; + +/* + switch ( GetLanguageEnum() ) + { + case eKorean: + { + if ( Korean_ValidKSC5601Hangul( psString[0], psString[1] )) + { + uiLetter = (psString[0] * 256) + psString[1]; + *piAdvanceCount = 2; + + // not going to bother testing for korean punctuation here, since korean already + // uses spaces, and I don't have the punctuation glyphs defined, only the basic 2350 hanguls + // + if ( pbIsTrailingPunctuation) + { + *pbIsTrailingPunctuation = qfalse; + } + + return uiLetter; + } + } + break; + + case eTaiwanese: + { + if ( Taiwanese_ValidBig5Code( (psString[0] * 256) + psString[1] )) + { + uiLetter = (psString[0] * 256) + psString[1]; + *piAdvanceCount = 2; + + // need to ask if this is a trailing (ie like a comma or full-stop) punctuation?... + // + if ( pbIsTrailingPunctuation) + { + *pbIsTrailingPunctuation = Taiwanese_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse; + } + + return uiLetter; + } + } + break; + + case eJapanese: + { + if ( Japanese_ValidShiftJISCode( psString[0], psString[1] )) + { + uiLetter = (psString[0] * 256) + psString[1]; + *piAdvanceCount = 2; + + // need to ask if this is a trailing (ie like a comma or full-stop) punctuation?... + // + if ( pbIsTrailingPunctuation) + { + *pbIsTrailingPunctuation = Japanese_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse; + } + + return uiLetter; + } + } + break; + + case eChinese: + { + if ( Chinese_ValidGBCode( (psString[0] * 256) + psString[1] )) + { + uiLetter = (psString[0] * 256) + psString[1]; + *piAdvanceCount = 2; + + // need to ask if this is a trailing (ie like a comma or full-stop) punctuation?... + // + if ( pbIsTrailingPunctuation) + { + *pbIsTrailingPunctuation = Chinese_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse; + } + + return uiLetter; + } + } + break; + + case eThai: + { + int iThaiBytes; + uiLetter = Thai_ValidTISCode( psString, iThaiBytes ); + if ( uiLetter ) + { + *piAdvanceCount = iThaiBytes; + + if ( pbIsTrailingPunctuation ) + { + *pbIsTrailingPunctuation = Thai_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse; + } + + return uiLetter; + } + } + break; + } +*/ + + // ... must not have been an MBCS code... + // + if( psString[0] == '^' ) + { + // Handle button prompt magic: + *piAdvanceCount = 2; + switch( psString[1] ) + { + case 'A': + uiLetter = XB_GLYPH_A; break; + case 'B': + uiLetter = XB_GLYPH_B; break; + case 'X': + uiLetter = XB_GLYPH_X; break; + case 'Y': + uiLetter = XB_GLYPH_Y; break; + case 'W': + uiLetter = XB_GLYPH_W; break; + default: + *piAdvanceCount = 1; + uiLetter = '^'; break; + } + } + else + { + uiLetter = psString[0]; + *piAdvanceCount = 1; + } + + if (pbIsTrailingPunctuation) + { + *pbIsTrailingPunctuation = (uiLetter == '!' || + uiLetter == '?' || + uiLetter == ',' || + uiLetter == '.' || + uiLetter == ';' || + uiLetter == ':' + ) ? qtrue : qfalse; + } + + return uiLetter; +} + + +// needed for subtitle printing since original code no longer worked once camera bar height was changed to 480/10 +// rather than refdef height / 10. I now need to bodge the coords to come out right. +// +qboolean Language_IsAsian(void) +{ + switch ( GetLanguageEnum() ) + { + case eKorean: + case eTaiwanese: + case eJapanese: + case eChinese: + case eThai: // this is asian, but the query is normally used for scaling + return qtrue; + } + + return qfalse; +} + +qboolean Language_UsesSpaces(void) +{ + // ( korean uses spaces ) + switch ( GetLanguageEnum() ) + { + case eTaiwanese: + case eJapanese: + case eChinese: + case eThai: + return qfalse; + } + + return qtrue; +} + +// ====================================================================== +// name is (eg) "ergo" or "lcd", no extension. +// +// If path present, it's a special language hack for SBCS override languages, eg: "lcd/russian", which means +// just treat the file as "russian", but with the "lcd" part ensuring we don't find a different registered russian font +// +CFontInfo::CFontInfo(const char *_fontName) +{ + int len, i; + void *buff; + dfontdat_t *fontdat; + + // remove any special hack name insertions... + // + char fontName[MAX_QPATH]; + sprintf(fontName,"fonts/%s.fontdat",COM_SkipPath(const_cast(_fontName))); // COM_SkipPath should take a const char *, but it's just possible people use it as a char * I guess, so I have to hack around like this + + // clear some general things... + // + m_pThaiData = NULL; + m_iAltSBCSFont = -1; + m_iThisFont = -1; + m_iOriginalFontWhenSBCSOverriden = -1; + m_fAltSBCSFontScaleFactor = -1; + m_bIsFakeAlienLanguage = !strcmp(_fontName,"aurabesh"); // dont try and make SBCS or asian overrides for this + + len = FS_ReadFile(fontName, NULL); + if (len == sizeof(dfontdat_t)) + { + FS_ReadFile(fontName, &buff); + fontdat = (dfontdat_t *)buff; + + for(i = 0; i < GLYPH_COUNT; i++) + { + mGlyphs[i] = fontdat->mGlyphs[i]; + } + mPointSize = fontdat->mPointSize; + mHeight = fontdat->mHeight; + mAscender = fontdat->mAscender; + mDescender = fontdat->mDescender; +// mAsianHack = fontdat->mKoreanHack; // ignore this crap, it's some junk in the fontdat file that no-one uses + mbRoundCalcs = !!strstr(fontName,"ergo"); + + // cope with bad fontdat headers... + // + if (mHeight == 0) + { + mHeight = mPointSize; + mAscender = mPointSize - Round( ((float)mPointSize/10.0f)+2 ); // have to completely guess at the baseline... sigh. + mDescender = mHeight - mAscender; + } + + FS_FreeFile(buff); + } + else + { + mHeight = 0; + mShader = 0; + } + + Q_strncpyz(m_sFontName, fontName, sizeof(m_sFontName)); + COM_StripExtension( m_sFontName, m_sFontName ); // so we get better error printing if failed to load shader (ie lose ".fontdat") + mShader = RE_RegisterShaderNoMip(m_sFontName); + + FlagNoAsianGlyphs(); + UpdateAsianIfNeeded(true); + + // finished... + g_vFontArray.resize(g_iCurrentFontIndex + 1); + g_vFontArray[g_iCurrentFontIndex++] = this; + + + extern cvar_t *com_buildScript; + if (com_buildScript->integer == 2) + { + Com_Printf( "com_buildScript(2): Registering foreign fonts...\n" ); + static qboolean bDone = qfalse; // Do this once only (for speed)... + if (!bDone) + { + bDone = qtrue; + + char sTemp[MAX_QPATH]; + int iGlyphTPs = 0; + const char *psLang = NULL; + + // SBCS override languages... + // + fileHandle_t f; + for (int i=0; g_SBCSOverrideLanguages[i].m_psName ;i++) + { + char sTemp[MAX_QPATH]; + + sprintf(sTemp,"fonts/%s.tga", g_SBCSOverrideLanguages[i].m_psName ); + FS_FOpenFileRead( sTemp, &f, qfalse ); + if (f) FS_FCloseFile( f ); + + sprintf(sTemp,"fonts/%s.fontdat", g_SBCSOverrideLanguages[i].m_psName ); + FS_FOpenFileRead( sTemp, &f, qfalse ); + if (f) FS_FCloseFile( f ); + } + + // asian MBCS override languages... + // + for (int iLang=0; iLang<5; iLang++) + { + switch (iLang) + { + case 0: m_iAsianGlyphsAcross = Korean_InitFields (iGlyphTPs, psLang); break; + case 1: m_iAsianGlyphsAcross = Taiwanese_InitFields (iGlyphTPs, psLang); break; + case 2: m_iAsianGlyphsAcross = Japanese_InitFields (iGlyphTPs, psLang); break; + case 3: m_iAsianGlyphsAcross = Chinese_InitFields (iGlyphTPs, psLang); break; + case 4: m_iAsianGlyphsAcross = Thai_InitFields (iGlyphTPs, psLang); + { + // additional files needed for Thai language... + // + FS_FOpenFileRead( sFILENAME_THAI_WIDTHS , &f, qfalse ); + if (f) { + FS_FCloseFile( f ); + } + + FS_FOpenFileRead( sFILENAME_THAI_CODES, &f, qfalse ); + if (f) { + FS_FCloseFile( f ); + } + } + break; + } + + for (int i=0; imodificationCount || !AsianGlyphsAvailable() || bForceReEval) + { + m_iLanguageModificationCount = se_language->modificationCount; + + int iGlyphTPs = 0; + const char *psLang = NULL; + + switch ( eLanguage ) + { + case eKorean: m_iAsianGlyphsAcross = Korean_InitFields(iGlyphTPs, psLang); break; + case eTaiwanese: m_iAsianGlyphsAcross = Taiwanese_InitFields(iGlyphTPs, psLang); break; + case eJapanese: m_iAsianGlyphsAcross = Japanese_InitFields(iGlyphTPs, psLang); break; + case eChinese: m_iAsianGlyphsAcross = Chinese_InitFields(iGlyphTPs, psLang); break; + case eThai: + { + m_iAsianGlyphsAcross = Thai_InitFields(iGlyphTPs, psLang); + + if (!m_pThaiData) + { + LPCSTR psFailureReason = g_ThaiCodes.Init(); + if (!psFailureReason[0]) + { + m_pThaiData = &g_ThaiCodes; + } + else + { + // failed to load a needed file, reset to English... + // + Cvar_Set("se_language", "english"); + Com_Error( ERR_DROP, psFailureReason ); + } + } + } + break; + } + + // textures need loading... + // + if (m_sFontName[0]) + { + // Use this sometime if we need to do logic to load alternate-height glyphs to better fit other fonts. + // (but for now, we just use the one glyph set) + // + } + + for (int i = 0; i < iGlyphTPs; i++) + { + // (Note!! assumption for S,T calculations: all Asian glyph textures pages are square except for last one) + // + char sTemp[MAX_QPATH]; + Com_sprintf(sTemp,sizeof(sTemp), "fonts/%s_%d_1024_%d", psLang, 1024/m_iAsianGlyphsAcross, i); + // + // returning 0 here will automatically inhibit Asian glyph calculations at runtime... + // + m_hAsianShaders[i] = RE_RegisterShaderNoMip( sTemp ); + } + + // for now I'm hardwiring these, but if we ever have more than one glyph set per language then they'll be changed... + // + m_iAsianPagesLoaded = iGlyphTPs; // not necessarily true, but will be safe, and show up obvious if something missing + m_bAsianLastPageHalfHeight = true; + + bForceReEval = true; + } + + if (bForceReEval) + { + // now init the Asian member glyph fields to make them come out the same size as the western ones + // that they serve as an alternative for... + // + m_AsianGlyph.width = iCappedHeight; // square Asian chars same size as height of western set + m_AsianGlyph.height = iCappedHeight; // "" + switch (eLanguage) + { + default: m_AsianGlyph.horizAdvance = iCappedHeight; break; + case eKorean: m_AsianGlyph.horizAdvance = iCappedHeight - 1;break; // korean has a small amount of space at the edge of the glyph + + case eTaiwanese: + case eJapanese: + case eChinese: m_AsianGlyph.horizAdvance = iCappedHeight + 3; // need to force some spacing for these +// case eThai: // this is done dynamically elsewhere, since Thai glyphs are variable width + } + m_AsianGlyph.horizOffset = 0; // "" + m_AsianGlyph.baseline = mAscender + ((iCappedHeight - mHeight) >> 1); + } + } + else + { + // not using Asian... + // + FlagNoAsianGlyphs(); + } + } + else + { + // no western glyphs available, so don't attempt to match asian... + // + FlagNoAsianGlyphs(); + } +} + +static CFontInfo *GetFont_Actual(int index) +{ + index &= SET_MASK; + if((index >= 1) && (index < g_iCurrentFontIndex)) + { + CFontInfo *pFont = g_vFontArray[index]; + + if (pFont) + { + pFont->UpdateAsianIfNeeded(); + } + + return pFont; + } + return(NULL); +} + + +// needed to add *piShader param because of multiple TPs, +// if not passed in, then I also skip S,T calculations for re-usable static asian glyphinfo struct... +// +const glyphInfo_t *CFontInfo::GetLetter(const unsigned int uiLetter, int *piShader /* = NULL */) +{ + if ( AsianGlyphsAvailable() ) + { + int iCollapsedAsianCode = GetCollapsedAsianCode( uiLetter ); + if (iCollapsedAsianCode) + { + if (piShader) + { + // (Note!! assumption for S,T calculations: all asian glyph textures pages are square except for last one + // which may or may not be half height) - but not for Thai + // + int iTexturePageIndex = iCollapsedAsianCode / (m_iAsianGlyphsAcross * m_iAsianGlyphsAcross); + + if (iTexturePageIndex > m_iAsianPagesLoaded) + { + assert(0); // should never happen + iTexturePageIndex = 0; + } + + int iOriginalCollapsedAsianCode = iCollapsedAsianCode; // need to back this up (if Thai) for later + iCollapsedAsianCode -= iTexturePageIndex * (m_iAsianGlyphsAcross * m_iAsianGlyphsAcross); + + const int iColumn = iCollapsedAsianCode % m_iAsianGlyphsAcross; + const int iRow = iCollapsedAsianCode / m_iAsianGlyphsAcross; + const bool bHalfT = (iTexturePageIndex == (m_iAsianPagesLoaded - 1) && m_bAsianLastPageHalfHeight); + const int iAsianGlyphsDown = (bHalfT) ? m_iAsianGlyphsAcross / 2 : m_iAsianGlyphsAcross; + + switch ( GetLanguageEnum() ) + { + case eKorean: + default: + { + m_AsianGlyph.s = (float)( iColumn ) / (float)m_iAsianGlyphsAcross; + m_AsianGlyph.t = (float)( iRow ) / (float) iAsianGlyphsDown; + m_AsianGlyph.s2 = (float)( iColumn + 1) / (float)m_iAsianGlyphsAcross; + m_AsianGlyph.t2 = (float)( iRow + 1 ) / (float) iAsianGlyphsDown; + } + break; + + case eTaiwanese: + { + m_AsianGlyph.s = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn ))+1) / 1024.0f; + m_AsianGlyph.t = (float)(((1024 / iAsianGlyphsDown ) * ( iRow ))+1) / 1024.0f; + m_AsianGlyph.s2 = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn+1 )) ) / 1024.0f; + m_AsianGlyph.t2 = (float)(((1024 / iAsianGlyphsDown ) * ( iRow+1 )) ) / 1024.0f; + } + break; + + case eJapanese: + case eChinese: + { + m_AsianGlyph.s = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn )) ) / 1024.0f; + m_AsianGlyph.t = (float)(((1024 / iAsianGlyphsDown ) * ( iRow )) ) / 1024.0f; + m_AsianGlyph.s2 = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn+1 ))-1) / 1024.0f; + m_AsianGlyph.t2 = (float)(((1024 / iAsianGlyphsDown ) * ( iRow+1 ))-1) / 1024.0f; + } + break; + + case eThai: + { + int iGlyphXpos = (1024 / m_iAsianGlyphsAcross) * ( iColumn ); + int iGlyphWidth = g_ThaiCodes.GetWidth( iOriginalCollapsedAsianCode ); + + // very thai-specific language-code... + // + if (uiLetter == TIS_SARA_AM) + { + iGlyphXpos += 9; // these are pixel coords on the source TP, so don't affect scaled output + iGlyphWidth= 20; // + } + m_AsianGlyph.s = (float)(iGlyphXpos) / 1024.0f; + m_AsianGlyph.t = (float)(((1024 / iAsianGlyphsDown ) * ( iRow )) ) / 1024.0f; + // technically this .s2 line should be modified to blit only the correct width, but since + // all Thai glyphs are up against the left edge of their cells and have blank to the cell + // boundary then it's better to keep these calculations simpler... + + m_AsianGlyph.s2 = (float)(iGlyphXpos+iGlyphWidth) / 1024.0f; + m_AsianGlyph.t2 = (float)(((1024 / iAsianGlyphsDown ) * ( iRow+1 ))-1) / 1024.0f; + + // special addition for Thai, need to bodge up the width and advance fields... + // + m_AsianGlyph.width = iGlyphWidth; + m_AsianGlyph.horizAdvance = iGlyphWidth + 1; + } + break; + } + *piShader = m_hAsianShaders[ iTexturePageIndex ]; + } + return &m_AsianGlyph; + } + } + + if (piShader) + { + *piShader = GetShader(); + } + + const glyphInfo_t *pGlyph = &mGlyphs[ uiLetter & 0xff ]; + // + // SBCS language substitution?... + // + if ( m_fAltSBCSFontScaleFactor != -1 ) + { + // sod it, use the asian glyph, that's fine... + // + memcpy(&m_AsianGlyph,pGlyph,sizeof(m_AsianGlyph)); // *before* changin pGlyph! + +// CFontInfo *pOriginalFont = GetFont_Actual( this->m_iOriginalFontWhenSBCSOverriden ); +// pGlyph = &pOriginalFont->mGlyphs[ uiLetter & 0xff ]; + + #define ASSIGN_WITH_ROUNDING(_dst,_src) _dst = mbRoundCalcs ? Round( m_fAltSBCSFontScaleFactor * _src ) : m_fAltSBCSFontScaleFactor * (float)_src; + + ASSIGN_WITH_ROUNDING( m_AsianGlyph.baseline, pGlyph->baseline ); + ASSIGN_WITH_ROUNDING( m_AsianGlyph.height, pGlyph->height ); + ASSIGN_WITH_ROUNDING( m_AsianGlyph.horizAdvance,pGlyph->horizAdvance ); +// m_AsianGlyph.horizOffset = /*Round*/( m_fAltSBCSFontScaleFactor * pGlyph->horizOffset ); + ASSIGN_WITH_ROUNDING( m_AsianGlyph.width, pGlyph->width ); + + pGlyph = &m_AsianGlyph; + } + + return pGlyph; +} + +const int CFontInfo::GetCollapsedAsianCode(ulong uiLetter) const +{ + int iCollapsedAsianCode = 0; + + if (AsianGlyphsAvailable()) + { + switch ( GetLanguageEnum() ) + { + case eKorean: iCollapsedAsianCode = Korean_CollapseKSC5601HangulCode( uiLetter ); break; + case eTaiwanese: iCollapsedAsianCode = Taiwanese_CollapseBig5Code( uiLetter ); break; + case eJapanese: iCollapsedAsianCode = Japanese_CollapseShiftJISCode( uiLetter ); break; + case eChinese: iCollapsedAsianCode = Chinese_CollapseGBCode( uiLetter ); break; + case eThai: iCollapsedAsianCode = Thai_CollapseTISCode( uiLetter ); break; + default: assert(0); /* unhandled asian language */ break; + } + } + + return iCollapsedAsianCode; +} + +const int CFontInfo::GetLetterWidth(unsigned int uiLetter) +{ + const glyphInfo_t *pGlyph = GetLetter( uiLetter ); + return pGlyph->width ? pGlyph->width : mGlyphs['.'].width; +} + +const int CFontInfo::GetLetterHorizAdvance(unsigned int uiLetter) +{ + const glyphInfo_t *pGlyph = GetLetter( uiLetter ); + return pGlyph->horizAdvance ? pGlyph->horizAdvance : mGlyphs['.'].horizAdvance; +} + +// ensure any GetFont calls that need SBCS overriding (such as when playing in Russian) have the appropriate stuff done... +// +static CFontInfo *GetFont_SBCSOverride(CFontInfo *pFont, Language_e eLanguageSBCS, LPCSTR psLanguageNameSBCS ) +{ + if ( !pFont->m_bIsFakeAlienLanguage ) + { + if ( GetLanguageEnum() == eLanguageSBCS ) + { + if ( pFont->m_iAltSBCSFont == -1 ) // no reg attempted yet? + { + // need to register this alternative SBCS font... + // + int iAltFontIndex = RE_RegisterFont( va("%s/%s",COM_SkipPath(pFont->m_sFontName),psLanguageNameSBCS) ); // ensure unique name (eg: "lcd/russian") + CFontInfo *pAltFont = GetFont_Actual( iAltFontIndex ); + if ( pAltFont ) + { + // work out the scaling factor for this font's glyphs...( round it to 1 decimal place to cut down on silly scale factors like 0.53125 ) + // + pAltFont->m_fAltSBCSFontScaleFactor = RoundTenth((float)pFont->GetPointSize() / (float)pAltFont->GetPointSize()); + // + // then override with the main properties of the original font... + // + pAltFont->mPointSize = pFont->GetPointSize();//(float) pAltFont->GetPointSize() * pAltFont->m_fAltSBCSFontScaleFactor; + pAltFont->mHeight = pFont->GetHeight();//(float) pAltFont->GetHeight() * pAltFont->m_fAltSBCSFontScaleFactor; + pAltFont->mAscender = pFont->GetAscender();//(float) pAltFont->GetAscender() * pAltFont->m_fAltSBCSFontScaleFactor; + pAltFont->mDescender = pFont->GetDescender();//(float) pAltFont->GetDescender() * pAltFont->m_fAltSBCSFontScaleFactor; + +// pAltFont->mPointSize = (float) pAltFont->GetPointSize() * pAltFont->m_fAltSBCSFontScaleFactor; +// pAltFont->mHeight = (float) pAltFont->GetHeight() * pAltFont->m_fAltSBCSFontScaleFactor; +// pAltFont->mAscender = (float) pAltFont->GetAscender() * pAltFont->m_fAltSBCSFontScaleFactor; +// pAltFont->mDescender = (float) pAltFont->GetDescender() * pAltFont->m_fAltSBCSFontScaleFactor; + + pAltFont->mbRoundCalcs = true; + pAltFont->m_iOriginalFontWhenSBCSOverriden = pFont->m_iThisFont; + } + pFont->m_iAltSBCSFont = iAltFontIndex; + } + + if ( pFont->m_iAltSBCSFont > 0) + { + return GetFont_Actual( pFont->m_iAltSBCSFont ); + } + } + } + + return NULL; +} + + + +CFontInfo *GetFont(int index) +{ + CFontInfo *pFont = GetFont_Actual( index ); + + if (pFont) + { + // any SBCS overrides? (this has to be pretty quick, and is (sort of))... + // + for (int i=0; g_SBCSOverrideLanguages[i].m_psName; i++) + { + CFontInfo *pAltFont = GetFont_SBCSOverride( pFont, g_SBCSOverrideLanguages[i].m_eLanguage, g_SBCSOverrideLanguages[i].m_psName ); + if (pAltFont) + { + return pAltFont; + } + } + } + + return pFont; +} + + +int RE_Font_StrLenPixels(const char *psText, const int iFontHandle, const float fScale) +{ + int iMaxWidth = 0; + int iThisWidth= 0; + CFontInfo *curfont; + + curfont = GetFont(iFontHandle); + if(!curfont) + { + return(0); + } + + float fScaleA = fScale; + if (Language_IsAsian() && fScale > 0.7f ) + { + fScaleA = fScale * 0.75f; + } + + while(*psText) + { + int iAdvanceCount; + unsigned int uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL ); + psText += iAdvanceCount; + + if (uiLetter == '^' ) + { + if (*psText >= '0' && + *psText <= '9') + { + uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL ); + psText += iAdvanceCount; + continue; + } + } + + if (uiLetter == 0x0A) + { + iThisWidth = 0; + } + else if (uiLetter >= XB_GLYPH_A && uiLetter <= XB_GLYPH_Y) + { + // These are all ALWAYS drawn at 24x24 + iThisWidth += 24; + if (iThisWidth > iMaxWidth) + iMaxWidth = iThisWidth; + } + else + { + int iPixelAdvance = curfont->GetLetterHorizAdvance( uiLetter ); + + float fValue = iPixelAdvance * ((uiLetter > g_iNonScaledCharRange) ? fScaleA : fScale); + iThisWidth += curfont->mbRoundCalcs ? Round( fValue ) : fValue; + if (iThisWidth > iMaxWidth) + { + iMaxWidth = iThisWidth; + } + } + } + + return iMaxWidth; +} + +// not really a font function, but keeps naming consistant... +// +int RE_Font_StrLenChars(const char *psText) +{ + // logic for this function's letter counting must be kept same in this function and RE_Font_DrawString() + // + int iCharCount = 0; + + while ( *psText ) + { + // in other words, colour codes and CR/LF don't count as chars, all else does... + // + int iAdvanceCount; + unsigned int uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL ); + psText += iAdvanceCount; + + switch (uiLetter) + { + case '^': + if (*psText >= '0' && + *psText <= '9') + { + psText++; + } + else + { + iCharCount++; + } + break; // colour code (note next-char skip) + case 10: break; // linefeed + case 13: break; // return + case '_': iCharCount += (GetLanguageEnum() == eThai && (((unsigned char *)psText)[0] >= TIS_GLYPHS_START))?0:1; break; // special word-break hack + default: iCharCount++; break; + } + } + + return iCharCount; +} + +int RE_Font_HeightPixels(const int iFontHandle, const float fScale) +{ + CFontInfo *curfont; + + curfont = GetFont(iFontHandle); + if(curfont) + { + float fValue = curfont->GetPointSize() * fScale; + return curfont->mbRoundCalcs ? Round(fValue) : fValue; + } + return(0); +} + +// iMaxPixelWidth is -1 for "all of string", else pixel display count... +// +void RE_Font_DrawString(int ox, int oy, const char *psText, const float *rgba, const int iFontHandle, int iMaxPixelWidth, const float fScale) +{ + static qboolean gbInShadow = qfalse; // MUST default to this + int x, y, colour, offset; + const glyphInfo_t *pLetter; + qhandle_t hShader; + + assert (psText); + + if(iFontHandle & STYLE_BLINK) + { + if((Sys_Milliseconds() >> 7) & 1) + { + return; + } + } + + CFontInfo *curfont = GetFont(iFontHandle); + if(!curfont || !psText) + { + return; + } + + float fScaleA = fScale; + int iAsianYAdjust = 0; + if (Language_IsAsian() && fScale > 0.7f) + { + fScaleA = fScale * 0.75f; + iAsianYAdjust = /*Round*/((((float)curfont->GetPointSize() * fScale) - ((float)curfont->GetPointSize() * fScaleA))/2); + } + + // Draw a dropshadow if required + if(iFontHandle & STYLE_DROPSHADOW) + { + offset = Round(curfont->GetPointSize() * fScale * 0.075f); + + static const vec4_t v4DKGREY2 = {0.15f, 0.15f, 0.15f, 1}; + + gbInShadow = qtrue; + RE_Font_DrawString(ox + offset, oy + offset, psText, v4DKGREY2, iFontHandle & SET_MASK, iMaxPixelWidth, fScale); + gbInShadow = qfalse; + } + + RE_SetColor( rgba ); + + x = ox; + oy += Round((curfont->GetHeight() - (curfont->GetDescender() >> 1)) * fScale); + + qboolean bNextTextWouldOverflow = qfalse; + while (*psText && !bNextTextWouldOverflow) + { + int iAdvanceCount; + unsigned int uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL ); + psText += iAdvanceCount; + + switch( uiLetter ) + { + case 10: //linefeed + x = ox; + oy += Round(curfont->GetPointSize() * fScale); + if (Language_IsAsian()) + { + oy += 4; // this only comes into effect when playing in asian for "A long time ago in a galaxy" etc, all other text is line-broken in feeder functions + } + break; + case 13: // Return + break; + case 32: // Space + pLetter = curfont->GetLetter(' '); + x += Round(pLetter->horizAdvance * fScale); + bNextTextWouldOverflow = ( iMaxPixelWidth != -1 && ((x-ox)>iMaxPixelWidth) ) ? qtrue : qfalse; // yeuch + break; + case XB_GLYPH_A: + case XB_GLYPH_B: + case XB_GLYPH_W: + case XB_GLYPH_X: + case XB_GLYPH_Y: + bNextTextWouldOverflow = ( iMaxPixelWidth != -1 && (((x+24)-ox)>iMaxPixelWidth) ) ? qtrue : qfalse; + if( !bNextTextWouldOverflow) + { + y = oy - 16; + + hShader = RE_RegisterShaderNoMip( xbGlyphShaders[uiLetter - XB_GLYPH_A] ); + + RE_StretchPic ( x, // float x + y, // float y + 24.0f, // float w + 24.0f, // float h + 0.0f, // float s1 + 0.0f, // float t1 + 1.0f, // float s2 + 1.0f, // float t2 + //lastcolour.c, + hShader // qhandle_t hShader + ); + + x += 24; + } + break; + case '_': // has a special word-break usage if in Thai (and followed by a thai char), and should not be displayed, else treat as normal + if (GetLanguageEnum()== eThai && ((unsigned char *)psText)[0] >= TIS_GLYPHS_START) + { + break; + } + // else drop through and display as normal... + case '^': + if (uiLetter != '_') // necessary because of fallthrough above + { + if (*psText >= '0' && + *psText <= '9') + { + colour = ColorIndex(*psText++); + if (!gbInShadow) + { + RE_SetColor( g_color_table[colour] ); + } + break; + } + } + //purposely falls thrugh + default: + pLetter = curfont->GetLetter( uiLetter, &hShader ); // Description of pLetter + if(!pLetter->width) + { + pLetter = curfont->GetLetter('.'); + } + + float fThisScale = uiLetter > g_iNonScaledCharRange ? fScaleA : fScale; + + // sigh, super-language-specific hack... + // + if (uiLetter == TIS_SARA_AM && GetLanguageEnum() == eThai) + { + x -= Round(7 * fThisScale); + } + + int iAdvancePixels = Round(pLetter->horizAdvance * fThisScale); + bNextTextWouldOverflow = ( iMaxPixelWidth != -1 && (((x+iAdvancePixels)-ox)>iMaxPixelWidth) ) ? qtrue : qfalse; // yeuch + if (!bNextTextWouldOverflow) + { + // this 'mbRoundCalcs' stuff is crap, but the only way to make the font code work. Sigh... + // + y = oy - (curfont->mbRoundCalcs ? Round(pLetter->baseline * fThisScale) : pLetter->baseline * fThisScale); + if (curfont->m_fAltSBCSFontScaleFactor != -1) + { + y+=3; // I'm sick and tired of going round in circles trying to do this legally, so bollocks to it + } + + RE_StretchPic ( x + Round(pLetter->horizOffset * fScale), // float x + (uiLetter > g_iNonScaledCharRange) ? y - iAsianYAdjust : y, // float y + curfont->mbRoundCalcs ? Round(pLetter->width * fThisScale) : pLetter->width * fThisScale, // float w + curfont->mbRoundCalcs ? Round(pLetter->height * fThisScale) : pLetter->height * fThisScale, // float h + pLetter->s, // float s1 + pLetter->t, // float t1 + pLetter->s2, // float s2 + pLetter->t2, // float t2 + //lastcolour.c, + hShader // qhandle_t hShader + ); + + x += iAdvancePixels; + } + break; + } + } + //let it remember the old color //RE_SetColor(NULL);; +} + +int RE_RegisterFont(const char *psName) +{ + FontIndexMap_t::iterator it = g_mapFontIndexes.find(psName); + if (it != g_mapFontIndexes.end() ) + { + int iFontIndex = (*it).second; + return iFontIndex; + } + + // not registered, so... + // + { + CFontInfo *pFont = new CFontInfo(psName); + if (pFont->GetPointSize() > 0) + { + int iFontIndex = g_iCurrentFontIndex - 1; + g_mapFontIndexes[psName] = iFontIndex; + pFont->m_iThisFont = iFontIndex; + return iFontIndex; + } + else + { + g_mapFontIndexes[psName] = 0; // missing/invalid + } + } + + return 0; +} + +void R_InitFonts(void) +{ + g_iCurrentFontIndex = 1; // entry 0 is reserved for "missing/invalid" + g_iNonScaledCharRange = INT_MAX; // default all chars to have no special scaling (other than user supplied) +} + +void R_ShutdownFonts(void) +{ + for(int i = 1; i < g_iCurrentFontIndex; i++) // entry 0 is reserved for "missing/invalid" + { + delete g_vFontArray[i]; + } + g_mapFontIndexes.clear(); + g_vFontArray.clear(); + g_iCurrentFontIndex = 1; // entry 0 is reserved for "missing/invalid" + + g_ThaiCodes.Clear(); +} + +// this is only really for debugging while tinkering with fonts, but harmless to leave in... +// +void R_ReloadFonts_f(void) +{ + // first, grab all the currently-registered fonts IN THE ORDER THEY WERE REGISTERED... + // + vector vstrFonts; + + for (int iFontToFind = 1; iFontToFind < g_iCurrentFontIndex; iFontToFind++) + { + for (FontIndexMap_t::iterator it = g_mapFontIndexes.begin(); it != g_mapFontIndexes.end(); ++it) + { + if (iFontToFind == (*it).second) + { + vstrFonts.push_back( (*it).first ); + break; + } + } + if ( it == g_mapFontIndexes.end() ) + { + break; // couldn't find this font + } + } + if ( iFontToFind == g_iCurrentFontIndex ) // found all of them? + { + // now restart the font system... + // + R_ShutdownFonts(); + R_InitFonts(); + // + // and re-register our fonts in the same order as before (note that some menu items etc cache the string lengths so really a vid_restart is better, but this is just for my testing) + // + for (int iFont = 0; iFont < vstrFonts.size(); iFont++) + { +#ifdef _DEBUG + int iNewFontHandle = RE_RegisterFont( vstrFonts[iFont].c_str() ); + assert( iNewFontHandle == iFont+1 ); +#else + RE_RegisterFont( vstrFonts[iFont].c_str() ); +#endif + } + Com_Printf( "Done.\n" ); + } + else + { + Com_Printf( "Problem encountered finding current fonts, ignoring.\n" ); // poo. Oh well, forget it. + } +} + + +// end diff --git a/codemp/renderer/tr_font.h b/codemp/renderer/tr_font.h new file mode 100644 index 0000000..e078783 --- /dev/null +++ b/codemp/renderer/tr_font.h @@ -0,0 +1,34 @@ +// Filename:- tr_font.h +// +// font support + +// This file is shared in the single and multiplayer codebases, so be CAREFUL WHAT YOU ADD/CHANGE!!!!! + +#ifndef TR_FONT_H +#define TR_FONT_H + + +void R_ShutdownFonts(void); +void R_InitFonts(void); +int RE_RegisterFont(const char *psName); +int RE_Font_StrLenPixels(const char *psText, const int iFontHandle, const float fScale = 1.0f); +int RE_Font_StrLenChars(const char *psText); +int RE_Font_HeightPixels(const int iFontHandle, const float fScale = 1.0f); +void RE_Font_DrawString(int ox, int oy, const char *psText, const float *rgba, const int iFontHandle, int iMaxPixelWidth, const float fScale = 1.0f); + +// Dammit, I can't use this more elegant form because of !@#@!$%% VM code... (can't alter passed in ptrs, only contents of) +// +//unsigned int AnyLanguage_ReadCharFromString( const char **ppsText, qboolean *pbIsTrailingPunctuation = NULL); +// +// so instead we have to use this messier method... +// +unsigned int AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation = NULL); + +qboolean Language_IsAsian(void); +qboolean Language_UsesSpaces(void); + + +#endif // #ifndef TR_FONT_H + +// end + diff --git a/codemp/renderer/tr_ghoul2.cpp b/codemp/renderer/tr_ghoul2.cpp new file mode 100644 index 0000000..bd13037 --- /dev/null +++ b/codemp/renderer/tr_ghoul2.cpp @@ -0,0 +1,5441 @@ +// leave this as first line for PCH reasons... +// +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + + +#include "../client/client.h" //FIXME!! EVIL - just include the definitions needed + +#ifdef _XBOX +#include "../qcommon/miniheap.h" +#endif + +#if !defined(TR_LOCAL_H) + #include "tr_local.h" +#endif + +#include "matcomp.h" +#if !defined(_QCOMMON_H_) + #include "../qcommon/qcommon.h" +#endif +#if !defined(G2_H_INC) + #include "../ghoul2/G2.h" +#endif +#include "../ghoul2/G2_local.h" +#ifdef _G2_GORE +#include "../ghoul2/G2_gore.h" +#endif +#include "matcomp.h" + +#include "../cgame/cg_local.h" // Very evil - needed to get cg->time in GLA virtual memory manager +#ifdef _XBOX +#include "../win32/glw_win_dx8.h" +#endif + +#pragma warning (disable: 4512) //default assignment operator could not be gened +#include "../qcommon/disablewarnings.h" + +#define LL(x) x=LittleLong(x) + +#ifdef G2_PERFORMANCE_ANALYSIS +#include "../qcommon/timing.h" + +timing_c G2PerformanceTimer_RenderSurfaces; +timing_c G2PerformanceTimer_R_AddGHOULSurfaces; +timing_c G2PerformanceTimer_G2_TransformGhoulBones; +timing_c G2PerformanceTimer_G2_ProcessGeneratedSurfaceBolts; +timing_c G2PerformanceTimer_ProcessModelBoltSurfaces; +timing_c G2PerformanceTimer_G2_ConstructGhoulSkeleton; +timing_c G2PerformanceTimer_RB_SurfaceGhoul; +timing_c G2PerformanceTimer_G2_SetupModelPointers; +timing_c G2PerformanceTimer_PreciseFrame; + +int G2PerformanceCounter_G2_TransformGhoulBones = 0; + +int G2Time_RenderSurfaces = 0; +int G2Time_R_AddGHOULSurfaces = 0; +int G2Time_G2_TransformGhoulBones = 0; +int G2Time_G2_ProcessGeneratedSurfaceBolts = 0; +int G2Time_ProcessModelBoltSurfaces = 0; +int G2Time_G2_ConstructGhoulSkeleton = 0; +int G2Time_RB_SurfaceGhoul = 0; +int G2Time_G2_SetupModelPointers = 0; +int G2Time_PreciseFrame = 0; + +void G2Time_ResetTimers(void) +{ + G2Time_RenderSurfaces = 0; + G2Time_R_AddGHOULSurfaces = 0; + G2Time_G2_TransformGhoulBones = 0; + G2Time_G2_ProcessGeneratedSurfaceBolts = 0; + G2Time_ProcessModelBoltSurfaces = 0; + G2Time_G2_ConstructGhoulSkeleton = 0; + G2Time_RB_SurfaceGhoul = 0; + G2Time_G2_SetupModelPointers = 0; + G2Time_PreciseFrame = 0; + G2PerformanceCounter_G2_TransformGhoulBones = 0; +} + +void G2Time_ReportTimers(void) +{ + Com_Printf("\n---------------------------------\nRenderSurfaces: %i\nR_AddGhoulSurfaces: %i\nG2_TransformGhoulBones: %i\nG2_ProcessGeneratedSurfaceBolts: %i\nProcessModelBoltSurfaces: %i\nG2_ConstructGhoulSkeleton: %i\nRB_SurfaceGhoul: %i\nG2_SetupModelPointers: %i\n\nPrecise frame time: %i\nTransformGhoulBones calls: %i\n---------------------------------\n\n", + G2Time_RenderSurfaces, + G2Time_R_AddGHOULSurfaces, + G2Time_G2_TransformGhoulBones, + G2Time_G2_ProcessGeneratedSurfaceBolts, + G2Time_ProcessModelBoltSurfaces, + G2Time_G2_ConstructGhoulSkeleton, + G2Time_RB_SurfaceGhoul, + G2Time_G2_SetupModelPointers, + G2Time_PreciseFrame, + G2PerformanceCounter_G2_TransformGhoulBones + ); +} +#endif + +//rww - RAGDOLL_BEGIN +#ifdef __linux__ +#include +#else +#include +#endif + +//rww - RAGDOLL_END + +bool HackadelicOnClient=false; // means this is a render traversal + +qboolean G2_SetupModelPointers(CGhoul2Info *ghlInfo); +qboolean G2_SetupModelPointers(CGhoul2Info_v &ghoul2); + +extern cvar_t *r_Ghoul2AnimSmooth; +extern cvar_t *r_Ghoul2UnSqashAfterSmooth; + +static inline int G2_Find_Bone_ByNum(const model_t *mod, boneInfo_v &blist, const int boneNum) +{ + int i = 0; + + while (i < blist.size()) + { + if (blist[i].boneNumber == boneNum) + { + return i; + } + i++; + } + + return -1; +} + +const static mdxaBone_t identityMatrix = +{ + 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f +}; + +// I hate doing this, but this is the simplest way to get this into the routines it needs to be +mdxaBone_t worldMatrix; +mdxaBone_t worldMatrixInv; +#ifdef _G2_GORE +qhandle_t goreShader=-1; +#endif + +class CConstructBoneList +{ +public: + int surfaceNum; + int *boneUsedList; + surfaceInfo_v &rootSList; + model_t *currentModel; + boneInfo_v &boneList; + + CConstructBoneList( + int initsurfaceNum, + int *initboneUsedList, + surfaceInfo_v &initrootSList, + model_t *initcurrentModel, + boneInfo_v &initboneList): + + surfaceNum(initsurfaceNum), + boneUsedList(initboneUsedList), + rootSList(initrootSList), + currentModel(initcurrentModel), + boneList(initboneList) { } + +}; + +class CTransformBone +{ +public: +#ifdef _XBOX + float renderMatrix[16]; +#endif + int touch; // for minimal recalculation + //rww - RAGDOLL_BEGIN + int touchRender; + //rww - RAGDOLL_END + mdxaBone_t boneMatrix; //final matrix + int parent; // only set once +#ifdef _XBOX + // This shouldn't be done like this. use declspec(aligned)?! + int pad[1]; // must be 16-byte aligned! +#endif + CTransformBone() + { + touch=0; + //rww - RAGDOLL_BEGIN + touchRender = 0; + //rww - RAGDOLL_END + } + +}; + +struct SBoneCalc +{ + int newFrame; + int currentFrame; + float backlerp; + float blendFrame; + int blendOldFrame; + bool blendMode; + float blendLerp; +}; + +class CBoneCache; +void G2_TransformBone(int index,CBoneCache &CB); + +class CBoneCache +{ + void SetRenderMatrix(CTransformBone *bone) + { +#ifdef _XBOX + float *src = bone->boneMatrix.matrix[0]; + float *dst = bone->renderMatrix; + + dst[0] = src[0]; + dst[1] = src[4]; + dst[2] = src[8]; + dst[3] = 0; + + dst[4] = src[1]; + dst[5] = src[5]; + dst[6] = src[9]; + dst[7] = 0; + + dst[8] = src[2]; + dst[9] = src[6]; + dst[10] = src[10]; + dst[11] = 0; + + dst[12] = src[3]; + dst[13] = src[7]; + dst[14] = src[11]; + dst[15] = 1; +#endif + } + + void EvalLow(int index) + { + assert(index>=0&&index=0&&mFinalBones[index].parent=0&&mFinalBones[index].parent=0) + { + EvalLow(mFinalBones[index].parent); // make sure parent is evaluated + SBoneCalc &par=mBones[mFinalBones[index].parent]; + mBones[index].newFrame=par.newFrame; + mBones[index].currentFrame=par.currentFrame; + mBones[index].backlerp=par.backlerp; + mBones[index].blendFrame=par.blendFrame; + mBones[index].blendOldFrame=par.blendOldFrame; + mBones[index].blendMode=par.blendMode; + mBones[index].blendLerp=par.blendLerp; + } + G2_TransformBone(index,*this); +#ifdef _XBOX + SetRenderMatrix(mFinalBones + index); +#endif + mFinalBones[index].touch=mCurrentTouch; + } + } +//rww - RAGDOLL_BEGIN + void SmoothLow(int index) + { + if (mSmoothBones[index].touch==mLastTouch) + { + int i; + float *oldM=&mSmoothBones[index].boneMatrix.matrix[0][0]; + float *newM=&mFinalBones[index].boneMatrix.matrix[0][0]; + for (i=0;i<12;i++,oldM++,newM++) + { + *oldM=mSmoothFactor*(*oldM-*newM)+*newM; + } + } + else + { + memcpy(&mSmoothBones[index].boneMatrix,&mFinalBones[index].boneMatrix,sizeof(mdxaBone_t)); + } + mdxaSkelOffsets_t *offsets = (mdxaSkelOffsets_t *)((byte *)header + sizeof(mdxaHeader_t)); + mdxaSkel_t *skel = (mdxaSkel_t *)((byte *)header + sizeof(mdxaHeader_t) + offsets->offsets[index]); + mdxaBone_t tempMatrix; + Multiply_3x4Matrix(&tempMatrix,&mSmoothBones[index].boneMatrix, &skel->BasePoseMat); + float maxl; + maxl=VectorLength(&skel->BasePoseMat.matrix[0][0]); + VectorNormalize(&tempMatrix.matrix[0][0]); + VectorNormalize(&tempMatrix.matrix[1][0]); + VectorNormalize(&tempMatrix.matrix[2][0]); + + VectorScale(&tempMatrix.matrix[0][0],maxl,&tempMatrix.matrix[0][0]); + VectorScale(&tempMatrix.matrix[1][0],maxl,&tempMatrix.matrix[1][0]); + VectorScale(&tempMatrix.matrix[2][0],maxl,&tempMatrix.matrix[2][0]); + Multiply_3x4Matrix(&mSmoothBones[index].boneMatrix,&tempMatrix,&skel->BasePoseMatInv); +#ifdef _XBOX + // Added by BTO (VV) - I hope this is right. + SetRenderMatrix(mSmoothBones + index); +#endif + mSmoothBones[index].touch=mCurrentTouch; +#ifdef _DEBUG + for ( int i = 0; i < 3; i++ ) + { + for ( int j = 0; j < 4; j++ ) + { + assert( !_isnan(mSmoothBones[index].boneMatrix.matrix[i][j])); + } + } +#endif// _DEBUG + } +//rww - RAGDOLL_END +public: + int frameSize; + const mdxaHeader_t *header; + const model_t *mod; + + // these are split for better cpu cache behavior + vector mBones; +#ifdef _XBOX + CTransformBone* mFinalBones; + CTransformBone* mSmoothBones; +#else + vector mFinalBones; + + vector mSmoothBones; // for render smoothing +#endif + //vector mSkels; + +#ifdef _XBOX + int mNumBones; +#endif + + boneInfo_v *rootBoneList; + mdxaBone_t rootMatrix; + int incomingTime; + + int mCurrentTouch; + //rww - RAGDOLL_BEGIN + int mCurrentTouchRender; + int mLastTouch; + int mLastLastTouch; + //rww - RAGDOLL_END + + // for render smoothing + bool mSmoothingActive; + bool mUnsquash; + float mSmoothFactor; + + CBoneCache(const model_t *amod,const mdxaHeader_t *aheader) : + mod(amod), + header(aheader) + { + assert(amod); + assert(aheader); + mSmoothingActive=false; + mUnsquash=false; + mSmoothFactor=0.0f; + +#ifdef _XBOX + mNumBones = header->numBones; +#endif + int numBones=header->numBones; + mBones.resize(numBones); +#ifdef _XBOX + mFinalBones = (CTransformBone*)Z_Malloc(sizeof(CTransformBone) * mNumBones, TAG_GHOUL2, qtrue, 16); + mSmoothBones = (CTransformBone*)Z_Malloc(sizeof(CTransformBone) * mNumBones, TAG_GHOUL2, qtrue, 16); +#else + mFinalBones.resize(numBones); + mSmoothBones.resize(numBones); +#endif +// mSkels.resize(numBones); + //rww - removed mSkels + mdxaSkelOffsets_t *offsets; + mdxaSkel_t *skel; + offsets = (mdxaSkelOffsets_t *)((byte *)header + sizeof(mdxaHeader_t)); + + int i; + for (i=0;ioffsets[i]); + //mSkels[i]=skel; + //ditto + mFinalBones[i].parent=skel->parent; + } + mCurrentTouch=3; +//rww - RAGDOLL_BEGIN + mLastTouch=2; + mLastLastTouch=1; +//rww - RAGDOLL_END + } +#ifdef _XBOX + ~CBoneCache () + { + // Alignment + Z_Free(mFinalBones); + Z_Free(mSmoothBones); + } +#endif + + SBoneCalc &Root() + { + assert(mBones.size()); + return mBones[0]; + } + const mdxaBone_t &EvalUnsmooth(int index) + { + EvalLow(index); + if (mSmoothingActive&&mSmoothBones[index].touch) + { + return mSmoothBones[index].boneMatrix; + } + return mFinalBones[index].boneMatrix; + } + const mdxaBone_t &Eval(int index) + { + /* + bool wasEval=EvalLow(index); + if (mSmoothingActive) + { + if (mSmoothBones[index].touch!=incomingTime||wasEval) + { + float dif=float(incomingTime)-float(mSmoothBones[index].touch); + if (mSmoothBones[index].touch&&dif<300.0f) + { + + if (dif<16.0f) // 60 fps + { + dif=16.0f; + } + if (dif>100.0f) // 10 fps + { + dif=100.0f; + } + float f=1.0f-pow(1.0f-mSmoothFactor,16.0f/dif); + + int i; + float *oldM=&mSmoothBones[index].boneMatrix.matrix[0][0]; + float *newM=&mFinalBones[index].boneMatrix.matrix[0][0]; + for (i=0;i<12;i++,oldM++,newM++) + { + *oldM=f*(*oldM-*newM)+*newM; + } + if (mUnsquash) + { + mdxaBone_t tempMatrix; + Multiply_3x4Matrix(&tempMatrix,&mSmoothBones[index].boneMatrix, &mSkels[index]->BasePoseMat); + float maxl; + maxl=VectorLength(&mSkels[index]->BasePoseMat.matrix[0][0]); + VectorNormalize(&tempMatrix.matrix[0][0]); + VectorNormalize(&tempMatrix.matrix[1][0]); + VectorNormalize(&tempMatrix.matrix[2][0]); + + VectorScale(&tempMatrix.matrix[0][0],maxl,&tempMatrix.matrix[0][0]); + VectorScale(&tempMatrix.matrix[1][0],maxl,&tempMatrix.matrix[1][0]); + VectorScale(&tempMatrix.matrix[2][0],maxl,&tempMatrix.matrix[2][0]); + Multiply_3x4Matrix(&mSmoothBones[index].boneMatrix,&tempMatrix,&mSkels[index]->BasePoseMatInv); + } + } + else + { + memcpy(&mSmoothBones[index].boneMatrix,&mFinalBones[index].boneMatrix,sizeof(mdxaBone_t)); + } + mSmoothBones[index].touch=incomingTime; + } + return mSmoothBones[index].boneMatrix; + } + return mFinalBones[index].boneMatrix; + */ + + //Hey, this is what sof2 does. Let's try it out. + assert(index>=0&&index=0&&index=0&&index=0&&indexEval(index); +} + +//rww - RAGDOLL_BEGIN +const mdxaHeader_t *G2_GetModA(CGhoul2Info &ghoul2) +{ + if (!ghoul2.mBoneCache) + { + return 0; + } + + CBoneCache &boneCache=*ghoul2.mBoneCache; + return boneCache.header; +} + +int G2_GetBoneDependents(CGhoul2Info &ghoul2,int boneNum,int *tempDependents,int maxDep) +{ + // fixme, these should be precomputed + if (!ghoul2.mBoneCache||!maxDep) + { + return 0; + } + + CBoneCache &boneCache=*ghoul2.mBoneCache; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + int i; + int ret=0; + for (i=0;inumChildren;i++) + { + if (!maxDep) + { + return i; // number added + } + *tempDependents=skel->children[i]; + assert(*tempDependents>0&&*tempDependentsnumBones); + maxDep--; + tempDependents++; + ret++; + } + for (i=0;inumChildren;i++) + { + int num=G2_GetBoneDependents(ghoul2,skel->children[i],tempDependents,maxDep); + tempDependents+=num; + ret+=num; + maxDep-=num; + assert(maxDep>=0); + if (!maxDep) + { + break; + } + } + return ret; +} + +bool G2_WasBoneRendered(CGhoul2Info &ghoul2,int boneNum) +{ + if (!ghoul2.mBoneCache) + { + return false; + } + CBoneCache &boneCache=*ghoul2.mBoneCache; + + return boneCache.WasRendered(boneNum); +} + +void G2_GetBoneBasepose(CGhoul2Info &ghoul2,int boneNum,mdxaBone_t *&retBasepose,mdxaBone_t *&retBaseposeInv) +{ + if (!ghoul2.mBoneCache) + { + // yikes + retBasepose=const_cast(&identityMatrix); + retBaseposeInv=const_cast(&identityMatrix); + return; + } + assert(ghoul2.mBoneCache); + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + assert(boneNum>=0&&boneNumnumBones); + + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + retBasepose=&skel->BasePoseMat; + retBaseposeInv=&skel->BasePoseMatInv; +} + +char *G2_GetBoneNameFromSkel(CGhoul2Info &ghoul2, int boneNum) +{ + if (!ghoul2.mBoneCache) + { + return NULL; + } + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + assert(boneNum>=0&&boneNumnumBones); + + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + + return skel->name; +} + +void G2_RagGetBoneBasePoseMatrixLow(CGhoul2Info &ghoul2, int boneNum, mdxaBone_t &boneMatrix, mdxaBone_t &retMatrix, vec3_t scale) +{ + assert(ghoul2.mBoneCache); + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + assert(boneNum>=0&&boneNumnumBones); + + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + Multiply_3x4Matrix(&retMatrix, &boneMatrix, &skel->BasePoseMat); + + if (scale[0]) + { + retMatrix.matrix[0][3] *= scale[0]; + } + if (scale[1]) + { + retMatrix.matrix[1][3] *= scale[1]; + } + if (scale[2]) + { + retMatrix.matrix[2][3] *= scale[2]; + } + + VectorNormalize((float*)&retMatrix.matrix[0]); + VectorNormalize((float*)&retMatrix.matrix[1]); + VectorNormalize((float*)&retMatrix.matrix[2]); +} + +void G2_GetBoneMatrixLow(CGhoul2Info &ghoul2,int boneNum,const vec3_t scale,mdxaBone_t &retMatrix,mdxaBone_t *&retBasepose,mdxaBone_t *&retBaseposeInv) +{ + if (!ghoul2.mBoneCache) + { + retMatrix=identityMatrix; + // yikes + retBasepose=const_cast(&identityMatrix); + retBaseposeInv=const_cast(&identityMatrix); + return; + } + mdxaBone_t bolt; + assert(ghoul2.mBoneCache); + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + assert(boneNum>=0&&boneNumnumBones); + + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + Multiply_3x4Matrix(&bolt, (mdxaBone_t *)&boneCache.Eval(boneNum), &skel->BasePoseMat); // DEST FIRST ARG + retBasepose=&skel->BasePoseMat; + retBaseposeInv=&skel->BasePoseMatInv; + + if (scale[0]) + { + bolt.matrix[0][3] *= scale[0]; + } + if (scale[1]) + { + bolt.matrix[1][3] *= scale[1]; + } + if (scale[2]) + { + bolt.matrix[2][3] *= scale[2]; + } + VectorNormalize((float*)&bolt.matrix[0]); + VectorNormalize((float*)&bolt.matrix[1]); + VectorNormalize((float*)&bolt.matrix[2]); + + Multiply_3x4Matrix(&retMatrix,&worldMatrix, &bolt); + +#ifdef _DEBUG + for ( int i = 0; i < 3; i++ ) + { + for ( int j = 0; j < 4; j++ ) + { + assert( !_isnan(retMatrix.matrix[i][j])); + } + } +#endif// _DEBUG +} + +int G2_GetParentBoneMatrixLow(CGhoul2Info &ghoul2,int boneNum,const vec3_t scale,mdxaBone_t &retMatrix,mdxaBone_t *&retBasepose,mdxaBone_t *&retBaseposeInv) +{ + int parent=-1; + if (ghoul2.mBoneCache) + { + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + assert(boneNum>=0&&boneNumnumBones); + parent=boneCache.GetParent(boneNum); + if (parent<0||parent>=boneCache.header->numBones) + { + parent=-1; + retMatrix=identityMatrix; + // yikes + retBasepose=const_cast(&identityMatrix); + retBaseposeInv=const_cast(&identityMatrix); + } + else + { + G2_GetBoneMatrixLow(ghoul2,parent,scale,retMatrix,retBasepose,retBaseposeInv); + } + } + return parent; +} +//rww - RAGDOLL_END + +class CRenderSurface +{ +public: + int surfaceNum; + surfaceInfo_v &rootSList; + shader_t *cust_shader; + int fogNum; + qboolean personalModel; + CBoneCache *boneCache; + int renderfx; + skin_t *skin; + model_t *currentModel; + int lod; + boltInfo_v &boltList; +#ifdef _G2_GORE + shader_t *gore_shader; + CGoreSet *gore_set; +#endif + + CRenderSurface( + int initsurfaceNum, + surfaceInfo_v &initrootSList, + shader_t *initcust_shader, + int initfogNum, + qboolean initpersonalModel, + CBoneCache *initboneCache, + int initrenderfx, + skin_t *initskin, + model_t *initcurrentModel, + int initlod, +#ifdef _G2_GORE + boltInfo_v &initboltList, + shader_t *initgore_shader, + CGoreSet *initgore_set): +#else + boltInfo_v &initboltList): +#endif + + surfaceNum(initsurfaceNum), + rootSList(initrootSList), + cust_shader(initcust_shader), + fogNum(initfogNum), + personalModel(initpersonalModel), + boneCache(initboneCache), + renderfx(initrenderfx), + skin(initskin), + currentModel(initcurrentModel), + lod(initlod), +#ifdef _G2_GORE + boltList(initboltList), + gore_shader(initgore_shader), + gore_set(initgore_set) +#else + boltList(initboltList) +#endif + {} +}; + +#ifdef _G2_GORE +#define MAX_RENDER_SURFACES (2048) +static CRenderableSurface RSStorage[MAX_RENDER_SURFACES]; +static unsigned int NextRS=0; + +CRenderableSurface *AllocRS() +{ + CRenderableSurface *ret=&RSStorage[NextRS]; + ret->Init(); + NextRS++; + NextRS%=MAX_RENDER_SURFACES; + return ret; +} +#endif + +/* + +All bones should be an identity orientation to display the mesh exactly +as it is specified. + +For all other frames, the bones represent the transformation from the +orientation of the bone in the base frame to the orientation in this +frame. + +*/ + + +/* +============= +R_ACullModel +============= +*/ +static int R_GCullModel( trRefEntity_t *ent ) { + + // scale the radius if need be + float largestScale = ent->e.modelScale[0]; + + if (ent->e.modelScale[1] > largestScale) + { + largestScale = ent->e.modelScale[1]; + } + if (ent->e.modelScale[2] > largestScale) + { + largestScale = ent->e.modelScale[2]; + } + if (!largestScale) + { + largestScale = 1; + } + + // cull bounding sphere + switch ( R_CullLocalPointAndRadius( vec3_origin, ent->e.radius * largestScale) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + return CULL_IN; + } + return CULL_IN; +} + + +/* +================= +R_AComputeFogNum + +================= +*/ +static int R_GComputeFogNum( trRefEntity_t *ent ) { + + int i, j; + fog_t *fog; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) { + break; + } + if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +// work out lod for this entity. +static int G2_ComputeLOD( trRefEntity_t *ent, const model_t *currentModel, int lodBias ) +{ + float flod, lodscale; + float projectedRadius; + int lod; + + if ( currentModel->numLods < 2 ) + { // model has only 1 LOD level, skip computations and bias + return(0); + } + + if ( r_lodbias->integer > lodBias ) + { + lodBias = r_lodbias->integer; + } + + // scale the radius if need be + float largestScale = ent->e.modelScale[0]; + + if (ent->e.modelScale[1] > largestScale) + { + largestScale = ent->e.modelScale[1]; + } + if (ent->e.modelScale[2] > largestScale) + { + largestScale = ent->e.modelScale[2]; + } + if (!largestScale) + { + largestScale = 1; + } + + if ( ( projectedRadius = ProjectRadius( 0.75*largestScale*ent->e.radius, ent->e.origin ) ) != 0 ) //we reduce the radius to make the LOD match other model types which use the actual bound box size + { + lodscale = (r_lodscale->value+r_autolodscalevalue->value); + if ( lodscale > 20 ) + { + lodscale = 20; + } + else if ( lodscale < 0 ) + { + lodscale = 0; + } + flod = 1.0f - projectedRadius * lodscale; + } + else + { + // object intersects near view plane, e.g. view weapon + flod = 0; + } +#ifdef DEDICATED +#define myftol(x) ((int)(x)) +#endif + flod *= currentModel->numLods; + lod = myftol( flod ); + + if ( lod < 0 ) + { + lod = 0; + } + else if ( lod >= currentModel->numLods ) + { + lod = currentModel->numLods - 1; + } + + + lod += lodBias; + + if ( lod >= currentModel->numLods ) + lod = currentModel->numLods - 1; + if ( lod < 0 ) + lod = 0; + + return lod; +} + +//====================================================================== +// +// Bone Manipulation code + + +void G2_CreateQuaterion(mdxaBone_t *mat, vec4_t quat) +{ + // this is revised for the 3x4 matrix we use in G2. + float t = 1 + mat->matrix[0][0] + mat->matrix[1][1] + mat->matrix[2][2]; + float s; + + //If the trace of the matrix is greater than zero, then + //perform an "instant" calculation. + //Important note wrt. rouning errors: + //Test if ( T > 0.00000001 ) to avoid large distortions! + if (t > 0.00000001) + { + s = sqrt(t) * 2; + quat[0] = ( mat->matrix[1][2] - mat->matrix[2][1] ) / s; + quat[1] = ( mat->matrix[2][0] - mat->matrix[0][2] ) / s; + quat[2] = ( mat->matrix[0][1] - mat->matrix[1][0] ) / s; + quat[3] = 0.25 * s; + } + else + { + //If the trace of the matrix is equal to zero then identify + //which major diagonal element has the greatest value. + + //Depending on this, calculate the following: + + if ( mat->matrix[0][0] > mat->matrix[1][1] && mat->matrix[0][0] > mat->matrix[2][2] ) { // Column 0: + s = sqrt( 1.0 + mat->matrix[0][0] - mat->matrix[1][1] - mat->matrix[2][2])* 2; + quat[0] = 0.25 * s; + quat[1] = (mat->matrix[0][1] + mat->matrix[1][0] ) / s; + quat[2] = (mat->matrix[2][0] + mat->matrix[0][2] ) / s; + quat[3] = (mat->matrix[1][2] - mat->matrix[2][1] ) / s; + + } else if ( mat->matrix[1][1] > mat->matrix[2][2] ) { // Column 1: + s = sqrt( 1.0 + mat->matrix[1][1] - mat->matrix[0][0] - mat->matrix[2][2] ) * 2; + quat[0] = (mat->matrix[0][1] + mat->matrix[1][0] ) / s; + quat[1] = 0.25 * s; + quat[2] = (mat->matrix[1][2] + mat->matrix[2][1] ) / s; + quat[3] = (mat->matrix[2][0] - mat->matrix[0][2] ) / s; + + } else { // Column 2: + s = sqrt( 1.0 + mat->matrix[2][2] - mat->matrix[0][0] - mat->matrix[1][1] ) * 2; + quat[0] = (mat->matrix[2][0]+ mat->matrix[0][2] ) / s; + quat[1] = (mat->matrix[1][2] + mat->matrix[2][1] ) / s; + quat[2] = 0.25 * s; + quat[3] = (mat->matrix[0][1] - mat->matrix[1][0] ) / s; + } + } +} + +void G2_CreateMatrixFromQuaterion(mdxaBone_t *mat, vec4_t quat) +{ + + float xx = quat[0] * quat[0]; + float xy = quat[0] * quat[1]; + float xz = quat[0] * quat[2]; + float xw = quat[0] * quat[3]; + + float yy = quat[1] * quat[1]; + float yz = quat[1] * quat[2]; + float yw = quat[1] * quat[3]; + + float zz = quat[2] * quat[2]; + float zw = quat[2] * quat[3]; + + mat->matrix[0][0] = 1 - 2 * ( yy + zz ); + mat->matrix[1][0] = 2 * ( xy - zw ); + mat->matrix[2][0] = 2 * ( xz + yw ); + + mat->matrix[0][1] = 2 * ( xy + zw ); + mat->matrix[1][1] = 1 - 2 * ( xx + zz ); + mat->matrix[2][1] = 2 * ( yz - xw ); + + mat->matrix[0][2] = 2 * ( xz - yw ); + mat->matrix[1][2] = 2 * ( yz + xw ); + mat->matrix[2][2] = 1 - 2 * ( xx + yy ); + + mat->matrix[0][3] = mat->matrix[1][3] = mat->matrix[2][3] = 0; +} + +// nasty little matrix multiply going on here.. +void Multiply_3x4Matrix(mdxaBone_t *out, mdxaBone_t *in2, mdxaBone_t *in) +{ + // first row of out + out->matrix[0][0] = (in2->matrix[0][0] * in->matrix[0][0]) + (in2->matrix[0][1] * in->matrix[1][0]) + (in2->matrix[0][2] * in->matrix[2][0]); + out->matrix[0][1] = (in2->matrix[0][0] * in->matrix[0][1]) + (in2->matrix[0][1] * in->matrix[1][1]) + (in2->matrix[0][2] * in->matrix[2][1]); + out->matrix[0][2] = (in2->matrix[0][0] * in->matrix[0][2]) + (in2->matrix[0][1] * in->matrix[1][2]) + (in2->matrix[0][2] * in->matrix[2][2]); + out->matrix[0][3] = (in2->matrix[0][0] * in->matrix[0][3]) + (in2->matrix[0][1] * in->matrix[1][3]) + (in2->matrix[0][2] * in->matrix[2][3]) + in2->matrix[0][3]; + // second row of outf out + out->matrix[1][0] = (in2->matrix[1][0] * in->matrix[0][0]) + (in2->matrix[1][1] * in->matrix[1][0]) + (in2->matrix[1][2] * in->matrix[2][0]); + out->matrix[1][1] = (in2->matrix[1][0] * in->matrix[0][1]) + (in2->matrix[1][1] * in->matrix[1][1]) + (in2->matrix[1][2] * in->matrix[2][1]); + out->matrix[1][2] = (in2->matrix[1][0] * in->matrix[0][2]) + (in2->matrix[1][1] * in->matrix[1][2]) + (in2->matrix[1][2] * in->matrix[2][2]); + out->matrix[1][3] = (in2->matrix[1][0] * in->matrix[0][3]) + (in2->matrix[1][1] * in->matrix[1][3]) + (in2->matrix[1][2] * in->matrix[2][3]) + in2->matrix[1][3]; + // third row of out out + out->matrix[2][0] = (in2->matrix[2][0] * in->matrix[0][0]) + (in2->matrix[2][1] * in->matrix[1][0]) + (in2->matrix[2][2] * in->matrix[2][0]); + out->matrix[2][1] = (in2->matrix[2][0] * in->matrix[0][1]) + (in2->matrix[2][1] * in->matrix[1][1]) + (in2->matrix[2][2] * in->matrix[2][1]); + out->matrix[2][2] = (in2->matrix[2][0] * in->matrix[0][2]) + (in2->matrix[2][1] * in->matrix[1][2]) + (in2->matrix[2][2] * in->matrix[2][2]); + out->matrix[2][3] = (in2->matrix[2][0] * in->matrix[0][3]) + (in2->matrix[2][1] * in->matrix[1][3]) + (in2->matrix[2][2] * in->matrix[2][3]) + in2->matrix[2][3]; +} + + +static int G2_GetBonePoolIndex( const mdxaHeader_t *pMDXAHeader, int iFrame, int iBone) +{ + const int iOffsetToIndex = (iFrame * pMDXAHeader->numBones * 3) + (iBone * 3); + + mdxaIndex_t *pIndex = (mdxaIndex_t *) ((byte*) pMDXAHeader + pMDXAHeader->ofsFrames + iOffsetToIndex); + + return pIndex->iIndex & 0x00FFFFFF; // this will cause problems for big-endian machines... ;-) +} + + +/* + Let the nastiness begin: Virtual memory for GLAs! We swap the bonePool for large + GLAs (humanoid and cinematic) to the HD, and then manage a pool of pages, swapping + in on demand. The theory is that a small portion of the animations are ever used, + or used on one level, and we can get away with this. I hope I'm right. +*/ +struct vvBonePoolPage; +struct vvBonePoolPageTableEntry; + +#define QUATS_PER_PAGE 1024 +#define PAGES_IN_RAM 100 // 1.4 MB - perhaps too small? + +struct vvBonePoolPage +{ + mdxaCompQuatBone_t quats[QUATS_PER_PAGE]; // Data + vvBonePoolPageTableEntry *owner; // Bookkeeping + unsigned long touch; // For LRU +}; + +struct vvBonePoolPageTableEntry +{ + vvBonePoolPageTableEntry() : page(NULL) { } + + vvBonePoolPage *page; // Data, or NULL if not in memory +}; + +struct vvBonePoolClient +{ + // Constructor takes the original size of the compBonePool from the GLA, + // and decides how many pages it will need and such: + vvBonePoolClient( mdxaHeader_t *mdxa, bool bRancor ) + { + // How big is the original bone pool, and how many pages do we need: + int bonePoolSize = mdxa->ofsEnd - mdxa->ofsCompBonePool; + int numQuats = bonePoolSize / sizeof(mdxaCompQuatBone_t); + assert( !(bonePoolSize % sizeof(mdxaCompQuatBone_t)) ); + + numPages = numQuats / QUATS_PER_PAGE; + if( numQuats % QUATS_PER_PAGE ) + numPages++; + + // Allocate our table that tracks which pages are in memory, and where: + Z_PushNewDeleteTag( TAG_MODEL_GLA ); + pages = new vvBonePoolPageTableEntry[numPages]; + Z_PopNewDeleteTag(); + + // Make the swap file: + h = CreateFile( bRancor ? "Z:\\rancorglaswap" : "Z:\\humanoidglaswap" , + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_FLAG_RANDOM_ACCESS, + NULL ); + if( h == INVALID_HANDLE_VALUE ) + assert( !"Couldn't create gla swap file" ); + + // Dump out the bone pool now, for later retrieval: + byte *pCompBonePool = (byte *)mdxa + mdxa->ofsCompBonePool; + DWORD dwWritten = 0; + if( !WriteFile( h, pCompBonePool, bonePoolSize, &dwWritten, NULL ) || dwWritten != bonePoolSize ) + assert( !"Couldn't write gla swap file" ); + + // Truncate the mdxa: + mdxa->ofsEnd = mdxa->ofsCompBonePool; + } + + ~vvBonePoolClient( void ) + { + // Close our file, and remove the page table: + CloseHandle( h ); + delete [] pages; + } + + vvBonePoolPageTableEntry *pages; + int numPages; + HANDLE h; // We always keep our page file open +}; + +// This is getting silly. +class vvBonePoolManager +{ +public: + vvBonePoolManager( void ) + { + clients[0] = clients[1] = NULL; + glaPages = PAGES_IN_RAM; + Clear(); + } + + void Register( mdxaHeader_t *mdxa ) + { + bool bRancor = strstr( mdxa->name, "rancor" ); + int clientIndex = bRancor ? 1 : 0; + + Z_PushNewDeleteTag( TAG_MODEL_GLA ); + clients[clientIndex] = new vvBonePoolClient( mdxa, bRancor ); + Z_PopNewDeleteTag(); + + // We're responsible for tagging the mdxa header so we know that we're in charge (0/-1): + mdxa->ofsCompBonePool = -clientIndex; + } + + vvBonePoolPage *getFreePage( void ) + { + unsigned long minAge = pages[0].touch; + int minIndex = 0; + + // Find oldest page, or any page not in use: + for( int i = 0; i < glaPages; ++i ) + { + // Return an unused page right away: + if( !pages[i].owner ) + return &pages[i]; + + if( pages[i].touch < minAge ) + { + minIndex = i; + minAge = pages[i].touch; + } + } + + // Page was in use - update records: + pages[minIndex].owner->page = NULL; + pages[minIndex].owner = NULL; + return &pages[minIndex]; + } + + // Returns a pointer to the specified bone for the specified client + mdxaCompQuatBone_t *fetchBone( int clientIndex, int index ) + { + assert( clientIndex <= 0 && clientIndex >= -1 ); + vvBonePoolClient *client = clients[-clientIndex]; + + int pageIndex = index / QUATS_PER_PAGE; + assert( pageIndex <= client->numPages ); + int pageOffset = index % QUATS_PER_PAGE; + + // Is the data already in memory? + if( client->pages[pageIndex].page ) + { + vvBonePoolPage *p = client->pages[pageIndex].page; + p->touch = cg->time; + return &p->quats[pageOffset]; + } + + // Need to load from disk: +#ifndef FINAL_BUILD + OutputDebugString( "*" ); +#endif + vvBonePoolPage *p = getFreePage(); + DWORD dwRead = 0; + SetFilePointer( client->h, pageIndex * sizeof(p->quats), NULL, FILE_BEGIN ); + if( !ReadFile( client->h, p->quats, sizeof(p->quats), &dwRead, NULL ) ) //|| dwRead != sizeof(p->quats) ) (fails on last page) + assert( !"Couldn't read from gla swap file" ); + p->touch = cg->time; + p->owner = &client->pages[pageIndex]; + p->owner->page = p; + + return &p->quats[pageOffset]; + } + + // Called between levels to clean up after ourselves: + void Clear( void ) + { + int i; + + for( i = 0; i < PAGES_IN_RAM; ++i ) + pages[i].owner = NULL; + + for( i = 0; i < 2; ++i ) + if( clients[i] ) + { + delete clients[i]; + clients[i] = NULL; + } + } + + // Used by other code (Bink) to turn part of the pool into a temp buffer: + void *TempAlloc( unsigned long size ) + { + // Make sure that we haven't already been called: + assert( glaPages == PAGES_IN_RAM ); + if( glaPages != PAGES_IN_RAM ) + return NULL; + + // Make sure the requested allocation isn't too large for us: + int tempPages = (size / sizeof(vvBonePoolPage)) + 1; + assert( tempPages < glaPages ); + if( tempPages >= glaPages ) + return NULL; + + // Move our end-of-valid-pages marker down: + glaPages -= tempPages; + // Invalidate all the pages that we're going to steal: + for( int i = glaPages; i < glaPages + tempPages; ++i ) + if( pages[i].owner ) + pages[i].owner->page = NULL; + + // And return the start of the block: + return &pages[glaPages]; + } + + // Free the currently allocated TempAlloc space: + void TempFree( void *p ) + { + // Sanity checks: + assert( (glaPages != PAGES_IN_RAM) && (p == &pages[glaPages]) ); + + // Fix up the data, who knows what kind of crap is in there now: + for( int i = glaPages; i < PAGES_IN_RAM; ++i ) + pages[i].owner = NULL; + + // And start re-using those pages again: + glaPages = PAGES_IN_RAM; + } + + // To check if a pointer came from TempAlloc + bool IsTempPointer( void *p ) + { + return (p >= &pages[0] && p <= &pages[PAGES_IN_RAM-1]); + } + +private: + vvBonePoolClient *clients[2]; // One for _humanoid, one for cinematic + vvBonePoolPage pages[PAGES_IN_RAM]; + int curTouch; + + // How many pages are being used for GLA. Will be PAGES_IN_RAM, unless TempAlloc + // has been handed out, in which case it will be smaller: + int glaPages; +}; + +// Grand-unified bone pool manager thingy: +vvBonePoolManager TheBonePool; + +void ClearTheBonePool( void ) +{ + TheBonePool.Clear(); +} + +void *BonePoolTempAlloc( unsigned long size ) +{ + return TheBonePool.TempAlloc( size ); +} + +void BonePoolTempFree( void *p ) +{ + TheBonePool.TempFree( p ); +} + +bool IsBonePoolPointer( void *p ) +{ + return TheBonePool.IsTempPointer( p ); +} + +/*******************************************************************************************************************/ + + +/*static inline*/ void UnCompressBone(float mat[3][4], int iBoneIndex, const mdxaHeader_t *pMDXAHeader, int iFrame) +{ + // Check for GLAs that are swapped out: + if( pMDXAHeader->ofsCompBonePool <= 0 ) + MC_UnCompressQuat(mat, TheBonePool.fetchBone(pMDXAHeader->ofsCompBonePool, G2_GetBonePoolIndex( pMDXAHeader, iFrame, iBoneIndex ))->Comp); + else + { + mdxaCompQuatBone_t *pCompBonePool = (mdxaCompQuatBone_t *) ((byte *)pMDXAHeader + pMDXAHeader->ofsCompBonePool); + MC_UnCompressQuat(mat, pCompBonePool[ G2_GetBonePoolIndex( pMDXAHeader, iFrame, iBoneIndex ) ].Comp); + } +} + + + +#define DEBUG_G2_TIMING (0) +#define DEBUG_G2_TIMING_RENDER_ONLY (1) + +void G2_TimingModel(boneInfo_t &bone,int currentTime,int numFramesInFile,int ¤tFrame,int &newFrame,float &lerp) +{ + assert(bone.startFrame>=0); + assert(bone.startFrame<=numFramesInFile); + assert(bone.endFrame>=0); + assert(bone.endFrame<=numFramesInFile); + + // yes - add in animation speed to current frame + float animSpeed = bone.animSpeed; + float time; + if (bone.pauseTime) + { + time = (bone.pauseTime - bone.startTime) / 50.0f; + } + else + { + time = (currentTime - bone.startTime) / 50.0f; + } + if (time<0.0f) + { + time=0.0f; + } + float newFrame_g = bone.startFrame + (time * animSpeed); + + int animSize = bone.endFrame - bone.startFrame; + float endFrame = (float)bone.endFrame; + // we are supposed to be animating right? + if (animSize) + { + // did we run off the end? + if (((animSpeed > 0.0f) && (newFrame_g > endFrame - 1)) || + ((animSpeed < 0.0f) && (newFrame_g < endFrame+1))) + { + // yep - decide what to do + if (bone.flags & BONE_ANIM_OVERRIDE_LOOP) + { + // get our new animation frame back within the bounds of the animation set + if (animSpeed < 0.0f) + { + // we don't use this case, or so I am told + // if we do, let me know, I need to insure the mod works + + // should we be creating a virtual frame? + if ((newFrame_g < endFrame+1) && (newFrame_g >= endFrame)) + { + // now figure out what we are lerping between + // delta is the fraction between this frame and the next, since the new anim is always at a .0f; + lerp = float(endFrame+1)-newFrame_g; + // frames are easy to calculate + currentFrame = endFrame; + assert(currentFrame>=0&¤tFrame=0&&newFrame=0&¤tFrame=0&&newFrame=0&&newFrame endFrame - 1) && (newFrame_g < endFrame)) + { + // now figure out what we are lerping between + // delta is the fraction between this frame and the next, since the new anim is always at a .0f; + lerp = (newFrame_g - (int)newFrame_g); + // frames are easy to calculate + currentFrame = (int)newFrame_g; + assert(currentFrame>=0&¤tFrame=0&&newFrame= endFrame) + { + newFrame_g=endFrame+fmod(newFrame_g-endFrame,animSize)-animSize; + } + // now figure out what we are lerping between + // delta is the fraction between this frame and the next, since the new anim is always at a .0f; + lerp = (newFrame_g - (int)newFrame_g); + // frames are easy to calculate + currentFrame = (int)newFrame_g; + assert(currentFrame>=0&¤tFrame= endFrame - 1) + { + newFrame = bone.startFrame; + assert(newFrame>=0&&newFrame=0&&newFrame= bone.startFrame) || (animSize < 10)); + } + else + { + if (((bone.flags & (BONE_ANIM_OVERRIDE_FREEZE)) == (BONE_ANIM_OVERRIDE_FREEZE))) + { + // if we are supposed to reset the default anim, then do so + if (animSpeed > 0.0f) + { + currentFrame = bone.endFrame - 1; + assert(currentFrame>=0&¤tFrame=0&¤tFrame=0&&newFrame 0.0) + { + // frames are easy to calculate + currentFrame = (int)newFrame_g; + + // figure out the difference between the two frames - we have to decide what frame and what percentage of that + // frame we want to display + lerp = (newFrame_g - currentFrame); + + assert(currentFrame>=0&¤tFrame= (int)endFrame) + { + // we only want to lerp with the first frame of the anim if we are looping + if (bone.flags & BONE_ANIM_OVERRIDE_LOOP) + { + newFrame = bone.startFrame; + assert(newFrame>=0&&newFrame=0&&newFrame=0&&newFramebone.startFrame) + { + currentFrame=bone.startFrame; + newFrame = currentFrame; + lerp=0.0f; + } + else + { + newFrame=currentFrame-1; + // are we now on the end frame? + if (newFrame < endFrame+1) + { + // we only want to lerp with the first frame of the anim if we are looping + if (bone.flags & BONE_ANIM_OVERRIDE_LOOP) + { + newFrame = bone.startFrame; + assert(newFrame>=0&&newFrame=0&&newFrame=0&¤tFrame=0&&newFrame=0&¤tFrame=0&&newFrame=0&¤tFrame=0&&newFrame=0.0f&&lerp<=1.0f); +} + +#ifdef _RAG_PRINT_TEST +void G2_RagPrintMatrix(mdxaBone_t *mat); +#endif +//basically construct a seperate skeleton with full hierarchy to store a matrix +//off which will give us the desired settling position given the frame in the skeleton +//that should be used -rww +int G2_Add_Bone (const model_t *mod, boneInfo_v &blist, const char *boneName); +int G2_Find_Bone(const model_t *mod, boneInfo_v &blist, const char *boneName); +void G2_RagGetAnimMatrix(CGhoul2Info &ghoul2, const int boneNum, mdxaBone_t &matrix, const int frame) +{ + mdxaBone_t animMatrix; + mdxaSkel_t *skel; + mdxaSkel_t *pskel; + mdxaSkelOffsets_t *offsets; + int parent; + int bListIndex; + int parentBlistIndex; +#ifdef _RAG_PRINT_TEST + bool actuallySet = false; +#endif + + assert(ghoul2.mBoneCache); + assert(ghoul2.animModel); + + offsets = (mdxaSkelOffsets_t *)((byte *)ghoul2.mBoneCache->header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)ghoul2.mBoneCache->header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + + //find/add the bone in the list + if (!skel->name || !skel->name[0]) + { + bListIndex = -1; + } + else + { + bListIndex = G2_Find_Bone(ghoul2.animModel, ghoul2.mBlist, skel->name); + if (bListIndex == -1) + { +#ifdef _RAG_PRINT_TEST + Com_Printf("Attempting to add %s\n", skel->name); +#endif + bListIndex = G2_Add_Bone(ghoul2.animModel, ghoul2.mBlist, skel->name); + } + } + + assert(bListIndex != -1); + + boneInfo_t &bone = ghoul2.mBlist[bListIndex]; + + if (bone.hasAnimFrameMatrix == frame) + { //already calculated so just grab it + matrix = bone.animFrameMatrix; + return; + } + + //get the base matrix for the specified frame + UnCompressBone(animMatrix.matrix, boneNum, ghoul2.mBoneCache->header, frame); + + parent = skel->parent; + if (boneNum > 0 && parent > -1) + { + //recursively call to assure all parent matrices are set up + G2_RagGetAnimMatrix(ghoul2, parent, matrix, frame); + + //assign the new skel ptr for our parent + pskel = (mdxaSkel_t *)((byte *)ghoul2.mBoneCache->header + sizeof(mdxaHeader_t) + offsets->offsets[parent]); + + //taking bone matrix for the skeleton frame and parent's animFrameMatrix into account, determine our final animFrameMatrix + if (!pskel->name || !pskel->name[0]) + { + parentBlistIndex = -1; + } + else + { + parentBlistIndex = G2_Find_Bone(ghoul2.animModel, ghoul2.mBlist, pskel->name); + if (parentBlistIndex == -1) + { + parentBlistIndex = G2_Add_Bone(ghoul2.animModel, ghoul2.mBlist, pskel->name); + } + } + + assert(parentBlistIndex != -1); + + boneInfo_t &pbone = ghoul2.mBlist[parentBlistIndex]; + + assert(pbone.hasAnimFrameMatrix == frame); //this should have been calc'd in the recursive call + + Multiply_3x4Matrix(&bone.animFrameMatrix, &pbone.animFrameMatrix, &animMatrix); + +#ifdef _RAG_PRINT_TEST + if (parentBlistIndex != -1 && bListIndex != -1) + { + actuallySet = true; + } + else + { + Com_Printf("BAD LIST INDEX: %s, %s [%i]\n", skel->name, pskel->name, parent); + } +#endif + } + else + { //root + Multiply_3x4Matrix(&bone.animFrameMatrix, &ghoul2.mBoneCache->rootMatrix, &animMatrix); +#ifdef _RAG_PRINT_TEST + if (bListIndex != -1) + { + actuallySet = true; + } + else + { + Com_Printf("BAD LIST INDEX: %s\n", skel->name); + } +#endif + //bone.animFrameMatrix = ghoul2.mBoneCache->mFinalBones[boneNum].boneMatrix; + //Maybe use this for the root, so that the orientation is in sync with the current + //root matrix? However this would require constant recalculation of this base + //skeleton which I currently do not want. + } + + //never need to figure it out again + bone.hasAnimFrameMatrix = frame; + +#ifdef _RAG_PRINT_TEST + if (!actuallySet) + { + Com_Printf("SET FAILURE\n"); + G2_RagPrintMatrix(&bone.animFrameMatrix); + } +#endif + + matrix = bone.animFrameMatrix; +} + +void G2_TransformBone (int child,CBoneCache &BC) +{ + SBoneCalc &TB=BC.mBones[child]; + static mdxaBone_t tbone[6]; +// mdxaFrame_t *aFrame=0; +// mdxaFrame_t *bFrame=0; +// mdxaFrame_t *aoldFrame=0; +// mdxaFrame_t *boldFrame=0; + static mdxaSkel_t *skel; + static mdxaSkelOffsets_t *offsets; + boneInfo_v &boneList = *BC.rootBoneList; + static int j, boneListIndex; + int angleOverride = 0; + +#if DEBUG_G2_TIMING + bool printTiming=false; +#endif + // should this bone be overridden by a bone in the bone list? + boneListIndex = G2_Find_Bone_In_List(boneList, child); + if (boneListIndex != -1) + { + // we found a bone in the list - we need to override something here. + + // do we override the rotational angles? + if ((boneList[boneListIndex].flags) & (BONE_ANGLES_TOTAL)) + { + angleOverride = (boneList[boneListIndex].flags) & (BONE_ANGLES_TOTAL); + } + + // set blending stuff if we need to + if (boneList[boneListIndex].flags & BONE_ANIM_BLEND) + { + float blendTime = BC.incomingTime - boneList[boneListIndex].blendStart; + // only set up the blend anim if we actually have some blend time left on this bone anim - otherwise we might corrupt some blend higher up the hiearchy + if (blendTime>=0.0f&&blendTime < boneList[boneListIndex].blendTime) + { + TB.blendFrame = boneList[boneListIndex].blendFrame; + TB.blendOldFrame = boneList[boneListIndex].blendLerpFrame; + TB.blendLerp = (blendTime / boneList[boneListIndex].blendTime); + TB.blendMode = true; + } + else + { + TB.blendMode = false; + } + } + else if (/*r_Ghoul2NoBlend->integer||*/((boneList[boneListIndex].flags) & (BONE_ANIM_OVERRIDE_LOOP | BONE_ANIM_OVERRIDE))) + // turn off blending if we are just doing a straing animation override + { + TB.blendMode = false; + } + + // should this animation be overridden by an animation in the bone list? + if ((boneList[boneListIndex].flags) & (BONE_ANIM_OVERRIDE_LOOP | BONE_ANIM_OVERRIDE)) + { + G2_TimingModel(boneList[boneListIndex],BC.incomingTime,BC.header->numFrames,TB.currentFrame,TB.newFrame,TB.backlerp); + } +#if DEBUG_G2_TIMING + printTiming=true; +#endif + /* + if ((r_Ghoul2NoLerp->integer)||((boneList[boneListIndex].flags) & (BONE_ANIM_NO_LERP))) + { + TB.backlerp = 0.0f; + } + */ + //rwwFIXMEFIXME: Use? + } + // figure out where the location of the bone animation data is + assert(TB.newFrame>=0&&TB.newFramenumFrames); + if (!(TB.newFrame>=0&&TB.newFramenumFrames)) + { + TB.newFrame=0; + } +// aFrame = (mdxaFrame_t *)((byte *)BC.header + BC.header->ofsFrames + TB.newFrame * BC.frameSize ); + assert(TB.currentFrame>=0&&TB.currentFramenumFrames); + if (!(TB.currentFrame>=0&&TB.currentFramenumFrames)) + { + TB.currentFrame=0; + } +// aoldFrame = (mdxaFrame_t *)((byte *)BC.header + BC.header->ofsFrames + TB.currentFrame * BC.frameSize ); + + // figure out where the location of the blended animation data is + assert(!(TB.blendFrame < 0.0 || TB.blendFrame >= (BC.header->numFrames+1))); + if (TB.blendFrame < 0.0 || TB.blendFrame >= (BC.header->numFrames+1) ) + { + TB.blendFrame=0.0; + } +// bFrame = (mdxaFrame_t *)((byte *)BC.header + BC.header->ofsFrames + (int)TB.blendFrame * BC.frameSize ); + assert(TB.blendOldFrame>=0&&TB.blendOldFramenumFrames); + if (!(TB.blendOldFrame>=0&&TB.blendOldFramenumFrames)) + { + TB.blendOldFrame=0; + } +#if DEBUG_G2_TIMING + +#if DEBUG_G2_TIMING_RENDER_ONLY + if (!HackadelicOnClient) + { + printTiming=false; + } +#endif + if (printTiming) + { + char mess[1000]; + if (TB.blendMode) + { + sprintf(mess,"b %2d %5d %4d %4d %4d %4d %f %f\n",boneListIndex,BC.incomingTime,(int)TB.newFrame,(int)TB.currentFrame,(int)TB.blendFrame,(int)TB.blendOldFrame,TB.backlerp,TB.blendLerp); + } + else + { + sprintf(mess,"a %2d %5d %4d %4d %f\n",boneListIndex,BC.incomingTime,TB.newFrame,TB.currentFrame,TB.backlerp); + } + OutputDebugString(mess); + const boneInfo_t &bone=boneList[boneListIndex]; + if (bone.flags&BONE_ANIM_BLEND) + { + sprintf(mess," bfb[%2d] %5d %5d (%5d-%5d) %4.2f %4x bt(%5d-%5d) %7.2f %5d\n", + boneListIndex, + BC.incomingTime, + bone.startTime, + bone.startFrame, + bone.endFrame, + bone.animSpeed, + bone.flags, + bone.blendStart, + bone.blendStart+bone.blendTime, + bone.blendFrame, + bone.blendLerpFrame + ); + } + else + { + sprintf(mess," bfa[%2d] %5d %5d (%5d-%5d) %4.2f %4x\n", + boneListIndex, + BC.incomingTime, + bone.startTime, + bone.startFrame, + bone.endFrame, + bone.animSpeed, + bone.flags + ); + } +// OutputDebugString(mess); + } +#endif +// boldFrame = (mdxaFrame_t *)((byte *)BC.header + BC.header->ofsFrames + TB.blendOldFrame * BC.frameSize ); + +// mdxaCompBone_t *compBonePointer = (mdxaCompBone_t *)((byte *)BC.header + BC.header->ofsCompBonePool); + + assert(child>=0&&childnumBones); +// assert(bFrame->boneIndexes[child]>=0); +// assert(boldFrame->boneIndexes[child]>=0); +// assert(aFrame->boneIndexes[child]>=0); +// assert(aoldFrame->boneIndexes[child]>=0); + + // decide where the transformed bone is going + + // are we blending with another frame of anim? + if (TB.blendMode) + { + float backlerp = TB.blendFrame - (int)TB.blendFrame; + float frontlerp = 1.0 - backlerp; + +// MC_UnCompress(tbone[3].matrix,compBonePointer[bFrame->boneIndexes[child]].Comp); +// MC_UnCompress(tbone[4].matrix,compBonePointer[boldFrame->boneIndexes[child]].Comp); + UnCompressBone(tbone[3].matrix, child, BC.header, TB.blendFrame); + UnCompressBone(tbone[4].matrix, child, BC.header, TB.blendOldFrame); + + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&tbone[5])[j] = (backlerp * ((float *)&tbone[3])[j]) + + (frontlerp * ((float *)&tbone[4])[j]); + } + } + + // + // lerp this bone - use the temp space on the ref entity to put the bone transforms into + // + if (!TB.backlerp) + { +// MC_UnCompress(tbone[2].matrix,compBonePointer[aoldFrame->boneIndexes[child]].Comp); + UnCompressBone(tbone[2].matrix, child, BC.header, TB.currentFrame); + + // blend in the other frame if we need to + if (TB.blendMode) + { + float blendFrontlerp = 1.0 - TB.blendLerp; + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&tbone[2])[j] = (TB.blendLerp * ((float *)&tbone[2])[j]) + + (blendFrontlerp * ((float *)&tbone[5])[j]); + } + } + + if (!child) + { + // now multiply by the root matrix, so we can offset this model should we need to + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.rootMatrix, &tbone[2]); + } + } + else + { + float frontlerp = 1.0 - TB.backlerp; +// MC_UnCompress(tbone[0].matrix,compBonePointer[aFrame->boneIndexes[child]].Comp); +// MC_UnCompress(tbone[1].matrix,compBonePointer[aoldFrame->boneIndexes[child]].Comp); + UnCompressBone(tbone[0].matrix, child, BC.header, TB.newFrame); + UnCompressBone(tbone[1].matrix, child, BC.header, TB.currentFrame); + + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&tbone[2])[j] = (TB.backlerp * ((float *)&tbone[0])[j]) + + (frontlerp * ((float *)&tbone[1])[j]); + } + + // blend in the other frame if we need to + if (TB.blendMode) + { + float blendFrontlerp = 1.0 - TB.blendLerp; + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&tbone[2])[j] = (TB.blendLerp * ((float *)&tbone[2])[j]) + + (blendFrontlerp * ((float *)&tbone[5])[j]); + } + } + + if (!child) + { + // now multiply by the root matrix, so we can offset this model should we need to + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.rootMatrix, &tbone[2]); + } + } + // figure out where the bone hirearchy info is + offsets = (mdxaSkelOffsets_t *)((byte *)BC.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)BC.header + sizeof(mdxaHeader_t) + offsets->offsets[child]); +// skel = BC.mSkels[child]; + //rww - removed mSkels + + int parent=BC.mFinalBones[child].parent; + assert((parent==-1&&child==0)||(parent>=0&&parentBasePoseMat); + float matrixScale = VectorLength((float*)&temp); + static mdxaBone_t toMatrix = + { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f + }; + toMatrix.matrix[0][0]=matrixScale; + toMatrix.matrix[1][1]=matrixScale; + toMatrix.matrix[2][2]=matrixScale; + toMatrix.matrix[0][3]=temp.matrix[0][3]; + toMatrix.matrix[1][3]=temp.matrix[1][3]; + toMatrix.matrix[2][3]=temp.matrix[2][3]; + + Multiply_3x4Matrix(&temp, &toMatrix,&skel->BasePoseMatInv); //dest first arg + + float blendTime = BC.incomingTime - boneList[boneListIndex].boneBlendStart; + float blendLerp = (blendTime / boneList[boneListIndex].boneBlendTime); + if (blendLerp>0.0f) + { + // has started + if (blendLerp>1.0f) + { + // done +// Multiply_3x4Matrix(&bone, &BC.mFinalBones[parent].boneMatrix,&temp); + memcpy (&bone,&temp, sizeof(mdxaBone_t)); + } + else + { +// mdxaBone_t lerp; + // now do the blend into the destination + float blendFrontlerp = 1.0 - blendLerp; + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&bone)[j] = (blendLerp * ((float *)&temp)[j]) + + (blendFrontlerp * ((float *)&tbone[2])[j]); + } +// Multiply_3x4Matrix(&bone, &BC.mFinalBones[parent].boneMatrix,&lerp); + } + } + } + else + { + mdxaBone_t temp, firstPass; + + // give us the matrix the animation thinks we should have, so we can get the correct X&Y coors + Multiply_3x4Matrix(&firstPass, &BC.mFinalBones[parent].boneMatrix, &tbone[2]); + + // are we attempting to blend with the base animation? and still within blend time? + if (boneOverride.boneBlendTime && (((boneOverride.boneBlendTime + boneOverride.boneBlendStart) < BC.incomingTime))) + { + // ok, we are supposed to be blending. Work out lerp + float blendTime = BC.incomingTime - boneList[boneListIndex].boneBlendStart; + float blendLerp = (blendTime / boneList[boneListIndex].boneBlendTime); + + if (blendLerp <= 1) + { + if (blendLerp < 0) + { + assert(0); + } + + // now work out the matrix we want to get *to* - firstPass is where we are coming *from* + Multiply_3x4Matrix(&temp, &firstPass, &skel->BasePoseMat); + + float matrixScale = VectorLength((float*)&temp); + + mdxaBone_t newMatrixTemp; + + if (HackadelicOnClient) + { + for (int i=0; i<3;i++) + { + for(int x=0;x<3; x++) + { + newMatrixTemp.matrix[i][x] = boneOverride.newMatrix.matrix[i][x]*matrixScale; + } + } + + newMatrixTemp.matrix[0][3] = temp.matrix[0][3]; + newMatrixTemp.matrix[1][3] = temp.matrix[1][3]; + newMatrixTemp.matrix[2][3] = temp.matrix[2][3]; + } + else + { + for (int i=0; i<3;i++) + { + for(int x=0;x<3; x++) + { + newMatrixTemp.matrix[i][x] = boneOverride.matrix.matrix[i][x]*matrixScale; + } + } + + newMatrixTemp.matrix[0][3] = temp.matrix[0][3]; + newMatrixTemp.matrix[1][3] = temp.matrix[1][3]; + newMatrixTemp.matrix[2][3] = temp.matrix[2][3]; + } + + Multiply_3x4Matrix(&temp, &newMatrixTemp,&skel->BasePoseMatInv); + + // now do the blend into the destination + float blendFrontlerp = 1.0 - blendLerp; + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&bone)[j] = (blendLerp * ((float *)&temp)[j]) + + (blendFrontlerp * ((float *)&firstPass)[j]); + } + } + else + { + bone = firstPass; + } + } + // no, so just override it directly + else + { + + Multiply_3x4Matrix(&temp,&firstPass, &skel->BasePoseMat); + float matrixScale = VectorLength((float*)&temp); + + mdxaBone_t newMatrixTemp; + + if (HackadelicOnClient) + { + for (int i=0; i<3;i++) + { + for(int x=0;x<3; x++) + { + newMatrixTemp.matrix[i][x] = boneOverride.newMatrix.matrix[i][x]*matrixScale; + } + } + + newMatrixTemp.matrix[0][3] = temp.matrix[0][3]; + newMatrixTemp.matrix[1][3] = temp.matrix[1][3]; + newMatrixTemp.matrix[2][3] = temp.matrix[2][3]; + } + else + { + for (int i=0; i<3;i++) + { + for(int x=0;x<3; x++) + { + newMatrixTemp.matrix[i][x] = boneOverride.matrix.matrix[i][x]*matrixScale; + } + } + + newMatrixTemp.matrix[0][3] = temp.matrix[0][3]; + newMatrixTemp.matrix[1][3] = temp.matrix[1][3]; + newMatrixTemp.matrix[2][3] = temp.matrix[2][3]; + } + + Multiply_3x4Matrix(&bone, &newMatrixTemp,&skel->BasePoseMatInv); + } + } + } + else if (angleOverride & BONE_ANGLES_PREMULT) + { + if ((angleOverride&BONE_ANGLES_RAGDOLL) || (angleOverride&BONE_ANGLES_IK)) + { + mdxaBone_t tmp; + if (!child) + { + if (HackadelicOnClient) + { + Multiply_3x4Matrix(&tmp, &BC.rootMatrix, &boneList[boneListIndex].newMatrix); + } + else + { + Multiply_3x4Matrix(&tmp, &BC.rootMatrix, &boneList[boneListIndex].matrix); + } + } + else + { + if (HackadelicOnClient) + { + Multiply_3x4Matrix(&tmp, &BC.mFinalBones[parent].boneMatrix, &boneList[boneListIndex].newMatrix); + } + else + { + Multiply_3x4Matrix(&tmp, &BC.mFinalBones[parent].boneMatrix, &boneList[boneListIndex].matrix); + } + } + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix,&tmp, &tbone[2]); + } + else + { + if (!child) + { + // use the in coming root matrix as our basis + if (HackadelicOnClient) + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.rootMatrix, &boneList[boneListIndex].newMatrix); + } + else + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.rootMatrix, &boneList[boneListIndex].matrix); + } + } + else + { + // convert from 3x4 matrix to a 4x4 matrix + if (HackadelicOnClient) + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.mFinalBones[parent].boneMatrix, &boneList[boneListIndex].newMatrix); + } + else + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.mFinalBones[parent].boneMatrix, &boneList[boneListIndex].matrix); + } + } + } + } + else + // now transform the matrix by it's parent, asumming we have a parent, and we aren't overriding the angles absolutely + if (child) + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.mFinalBones[parent].boneMatrix, &tbone[2]); + } + + // now multiply our resulting bone by an override matrix should we need to + if (angleOverride & BONE_ANGLES_POSTMULT) + { + mdxaBone_t tempMatrix; + memcpy (&tempMatrix,&BC.mFinalBones[child].boneMatrix, sizeof(mdxaBone_t)); + if (HackadelicOnClient) + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &tempMatrix, &boneList[boneListIndex].newMatrix); + } + else + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &tempMatrix, &boneList[boneListIndex].matrix); + } + } + /* + if (r_Ghoul2UnSqash->integer) + { + mdxaBone_t tempMatrix; + Multiply_3x4Matrix(&tempMatrix,&BC.mFinalBones[child].boneMatrix, &skel->BasePoseMat); + float maxl; + maxl=VectorLength(&skel->BasePoseMat.matrix[0][0]); + VectorNormalize(&tempMatrix.matrix[0][0]); + VectorNormalize(&tempMatrix.matrix[1][0]); + VectorNormalize(&tempMatrix.matrix[2][0]); + + VectorScale(&tempMatrix.matrix[0][0],maxl,&tempMatrix.matrix[0][0]); + VectorScale(&tempMatrix.matrix[1][0],maxl,&tempMatrix.matrix[1][0]); + VectorScale(&tempMatrix.matrix[2][0],maxl,&tempMatrix.matrix[2][0]); + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix,&tempMatrix,&skel->BasePoseMatInv); + } + */ + //rwwFIXMEFIXME: Care? + +} + +void G2_SetUpBolts( mdxaHeader_t *header, CGhoul2Info &ghoul2, mdxaBone_v &bonePtr, boltInfo_v &boltList) +{ + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)header + sizeof(mdxaHeader_t)); + + for (int i=0; ioffsets[boltList[i].boneNumber]); + Multiply_3x4Matrix(&boltList[i].position, &bonePtr[boltList[i].boneNumber].second, &skel->BasePoseMat); + } + } +} + +//rww - RAGDOLL_BEGIN +#define GHOUL2_RAG_STARTED 0x0010 +//rww - RAGDOLL_END +//rwwFIXMEFIXME: Move this into the stupid header or something. + +void G2_TransformGhoulBones(boneInfo_v &rootBoneList,mdxaBone_t &rootMatrix, CGhoul2Info &ghoul2, int time,bool smooth=true) +{ +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceTimer_G2_TransformGhoulBones.Start(); + G2PerformanceCounter_G2_TransformGhoulBones++; +#endif + + /* + model_t *currentModel; + model_t *animModel; + mdxaHeader_t *aHeader; + + //currentModel = R_GetModelByHandle(RE_RegisterModel(ghoul2.mFileName)); + currentModel = R_GetModelByHandle(ghoul2.mModel); + assert(currentModel); + assert(currentModel->mdxm); + + animModel = R_GetModelByHandle(currentModel->mdxm->animIndex); + assert(animModel); + aHeader = animModel->mdxa; + assert(aHeader); + */ + model_t *currentModel = (model_t *)ghoul2.currentModel; + mdxaHeader_t *aHeader = (mdxaHeader_t *)ghoul2.aHeader; + + + assert(ghoul2.aHeader); + assert(ghoul2.currentModel); + assert(ghoul2.currentModel->mdxm); + if (!aHeader->numBones) + { + assert(0); // this would be strange + return; + } + if (!ghoul2.mBoneCache) + { + ghoul2.mBoneCache=new CBoneCache(currentModel,aHeader); + +#ifdef _FULL_G2_LEAK_CHECKING + g_Ghoul2Allocations += sizeof(*ghoul2.mBoneCache); +#endif + } + ghoul2.mBoneCache->mod=currentModel; + ghoul2.mBoneCache->header=aHeader; + assert(ghoul2.mBoneCache->mBones.size()==aHeader->numBones); + + ghoul2.mBoneCache->mSmoothingActive=false; + ghoul2.mBoneCache->mUnsquash=false; + + // master smoothing control + if (HackadelicOnClient && smooth && !com_dedicated->integer) + { + ghoul2.mBoneCache->mLastTouch=ghoul2.mBoneCache->mLastLastTouch; + /* + float val=r_Ghoul2AnimSmooth->value; + if (smooth&&val>0.0f&&val<1.0f) + { + // if (HackadelicOnClient) + // { + ghoul2.mBoneCache->mLastTouch=ghoul2.mBoneCache->mLastLastTouch; + // } + + ghoul2.mBoneCache->mSmoothFactor=val; + ghoul2.mBoneCache->mSmoothingActive=true; + if (r_Ghoul2UnSqashAfterSmooth->integer) + { + ghoul2.mBoneCache->mUnsquash=true; + } + } + else + { + ghoul2.mBoneCache->mSmoothFactor=1.0f; + } + */ + + // master smoothing control + float val=r_Ghoul2AnimSmooth->value; + if (val>0.0f&&val<1.0f) + { + //if (ghoul2.mFlags&GHOUL2_RESERVED_FOR_RAGDOLL) +#if 1 + if(ghoul2.mFlags & GHOUL2_CRAZY_SMOOTH) + { + val = 0.9f; + } + else if(ghoul2.mFlags & GHOUL2_RAG_STARTED) + { + int k; + for (k=0;ktime-250 && + bone.firstCollisionTime time) + { + val = 0.2f; + } + else + { + val = 0.8f; + } + break; + } + } + } +#endif + +// ghoul2.mBoneCache->mSmoothFactor=(val + 1.0f-pow(1.0f-val,50.0f/dif))/2.0f; // meaningless formula + ghoul2.mBoneCache->mSmoothFactor=val; // meaningless formula + ghoul2.mBoneCache->mSmoothingActive=true; + + if (r_Ghoul2UnSqashAfterSmooth->integer) + { + ghoul2.mBoneCache->mUnsquash=true; + } + } + } + else + { + ghoul2.mBoneCache->mSmoothFactor=1.0f; + } + + ghoul2.mBoneCache->mCurrentTouch++; + +//rww - RAGDOLL_BEGIN + if (HackadelicOnClient) + { + ghoul2.mBoneCache->mLastLastTouch=ghoul2.mBoneCache->mCurrentTouch; + ghoul2.mBoneCache->mCurrentTouchRender=ghoul2.mBoneCache->mCurrentTouch; + } + else + { + ghoul2.mBoneCache->mCurrentTouchRender=0; + } +//rww - RAGDOLL_END + + ghoul2.mBoneCache->frameSize = 0;// can be deleted in new G2 format //(int)( &((mdxaFrame_t *)0)->boneIndexes[ ghoul2.aHeader->numBones ] ); + + ghoul2.mBoneCache->rootBoneList=&rootBoneList; + ghoul2.mBoneCache->rootMatrix=rootMatrix; + ghoul2.mBoneCache->incomingTime=time; + + SBoneCalc &TB=ghoul2.mBoneCache->Root(); + TB.newFrame=0; + TB.currentFrame=0; + TB.backlerp=0.0f; + TB.blendFrame=0; + TB.blendOldFrame=0; + TB.blendMode=false; + TB.blendLerp=0; + +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_G2_TransformGhoulBones += G2PerformanceTimer_G2_TransformGhoulBones.End(); +#endif +} + + +#define MDX_TAG_ORIGIN 2 + +//====================================================================== +// +// Surface Manipulation code + + +// We've come across a surface that's designated as a bolt surface, process it and put it in the appropriate bolt place +void G2_ProcessSurfaceBolt(mdxaBone_v &bonePtr, mdxmSurface_t *surface, int boltNum, boltInfo_v &boltList, surfaceInfo_t *surfInfo, model_t *mod) +{ + mdxmVertex_t *v, *vert0, *vert1, *vert2; + vec3_t axes[3], sides[3]; + float pTri[3][3], d; + int j, k; + + // now there are two types of tag surface - model ones and procedural generated types - lets decide which one we have here. + if (surfInfo && surfInfo->offFlags == G2SURFACEFLAG_GENERATED) + { + int surfNumber = surfInfo->genPolySurfaceIndex & 0x0ffff; + int polyNumber = (surfInfo->genPolySurfaceIndex >> 16) & 0x0ffff; + + // find original surface our original poly was in. + mdxmSurface_t *originalSurf = (mdxmSurface_t *)G2_FindSurface((void*)mod, surfNumber, surfInfo->genLod); + mdxmTriangle_t *originalTriangleIndexes = (mdxmTriangle_t *)((byte*)originalSurf + originalSurf->ofsTriangles); + + // get the original polys indexes + int index0 = originalTriangleIndexes[polyNumber].indexes[0]; + int index1 = originalTriangleIndexes[polyNumber].indexes[1]; + int index2 = originalTriangleIndexes[polyNumber].indexes[2]; + + // decide where the original verts are + + vert0 = (mdxmVertex_t *) ((byte *)originalSurf + originalSurf->ofsVerts); + vert0+= index0; + + vert1 = (mdxmVertex_t *) ((byte *)originalSurf + originalSurf->ofsVerts); + vert1+= index1; + + vert2 = (mdxmVertex_t *) ((byte *)originalSurf + originalSurf->ofsVerts); + vert2+= index2; + + // clear out the triangle verts to be + VectorClear( pTri[0] ); + VectorClear( pTri[1] ); + VectorClear( pTri[2] ); + +// mdxmWeight_t *w; + + int *piBoneRefs = (int*) ((byte*)originalSurf + originalSurf->ofsBoneReferences); + + // now go and transform just the points we need from the surface that was hit originally +// w = vert0->weights; + float fTotalWeight = 0.0f; + int iNumWeights = G2_GetVertWeights( vert0 ); + for ( k = 0 ; k < iNumWeights ; k++ ) + { + int iBoneIndex = G2_GetVertBoneIndex( vert0, k ); + float fBoneWeight = G2_GetVertBoneWeight( vert0, k, fTotalWeight, iNumWeights ); + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &vert0->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert0->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert0->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[0][0] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0][3] ); + pTri[0][1] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1][3] ); + pTri[0][2] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2][3] ); +#else + pTri[0][0] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0], vert0->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0][3] ); + pTri[0][1] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1], vert0->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1][3] ); + pTri[0][2] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2], vert0->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2][3] ); +#endif + } +// w = vert1->weights; + fTotalWeight = 0.0f; + iNumWeights = G2_GetVertWeights( vert1 ); + for ( k = 0 ; k < iNumWeights ; k++ ) + { + int iBoneIndex = G2_GetVertBoneIndex( vert1, k ); + float fBoneWeight = G2_GetVertBoneWeight( vert1, k, fTotalWeight, iNumWeights ); + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &vert1->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert1->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert1->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[1][0] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0][3] ); + pTri[1][1] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1][3] ); + pTri[1][2] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2][3] ); +#else + pTri[1][0] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0], vert1->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0][3] ); + pTri[1][1] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1], vert1->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1][3] ); + pTri[1][2] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2], vert1->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2][3] ); +#endif + } +// w = vert2->weights; + fTotalWeight = 0.0f; + iNumWeights = G2_GetVertWeights( vert2 ); + for ( k = 0 ; k < iNumWeights ; k++ ) + { + int iBoneIndex = G2_GetVertBoneIndex( vert2, k ); + float fBoneWeight = G2_GetVertBoneWeight( vert2, k, fTotalWeight, iNumWeights ); + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &vert2->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert2->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert2->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[2][0] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0][3] ); + pTri[2][1] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1][3] ); + pTri[2][2] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2][3] ); +#else + pTri[2][0] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0], vert2->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0][3] ); + pTri[2][1] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1], vert2->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1][3] ); + pTri[2][2] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2], vert2->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2][3] ); +#endif + } + + vec3_t normal; + vec3_t up; + vec3_t right; + vec3_t vec0, vec1; + // work out baryCentricK + float baryCentricK = 1.0 - (surfInfo->genBarycentricI + surfInfo->genBarycentricJ); + + // now we have the model transformed into model space, now generate an origin. + boltList[boltNum].position.matrix[0][3] = (pTri[0][0] * surfInfo->genBarycentricI) + (pTri[1][0] * surfInfo->genBarycentricJ) + (pTri[2][0] * baryCentricK); + boltList[boltNum].position.matrix[1][3] = (pTri[0][1] * surfInfo->genBarycentricI) + (pTri[1][1] * surfInfo->genBarycentricJ) + (pTri[2][1] * baryCentricK); + boltList[boltNum].position.matrix[2][3] = (pTri[0][2] * surfInfo->genBarycentricI) + (pTri[1][2] * surfInfo->genBarycentricJ) + (pTri[2][2] * baryCentricK); + + // generate a normal to this new triangle + VectorSubtract(pTri[0], pTri[1], vec0); + VectorSubtract(pTri[2], pTri[1], vec1); + + CrossProduct(vec0, vec1, normal); + VectorNormalize(normal); + + // forward vector + boltList[boltNum].position.matrix[0][0] = normal[0]; + boltList[boltNum].position.matrix[1][0] = normal[1]; + boltList[boltNum].position.matrix[2][0] = normal[2]; + + // up will be towards point 0 of the original triangle. + // so lets work it out. Vector is hit point - point 0 + up[0] = boltList[boltNum].position.matrix[0][3] - pTri[0][0]; + up[1] = boltList[boltNum].position.matrix[1][3] - pTri[0][1]; + up[2] = boltList[boltNum].position.matrix[2][3] - pTri[0][2]; + + // normalise it + VectorNormalize(up); + + // that's the up vector + boltList[boltNum].position.matrix[0][1] = up[0]; + boltList[boltNum].position.matrix[1][1] = up[1]; + boltList[boltNum].position.matrix[2][1] = up[2]; + + // right is always straight + + CrossProduct( normal, up, right ); + // that's the up vector + boltList[boltNum].position.matrix[0][2] = right[0]; + boltList[boltNum].position.matrix[1][2] = right[1]; + boltList[boltNum].position.matrix[2][2] = right[2]; + + + } + // no, we are looking at a normal model tag + else + { + int *piBoneRefs = (int*) ((byte*)surface + surface->ofsBoneReferences); + + // whip through and actually transform each vertex + v = (mdxmVertex_t *) ((byte *)surface + surface->ofsVerts); + for ( j = 0; j < 3; j++ ) + { +// mdxmWeight_t *w; + + VectorClear( pTri[j] ); + // w = v->weights; + + const int iNumWeights = G2_GetVertWeights( v ); + float fTotalWeight = 0.0f; + for ( k = 0 ; k < iNumWeights ; k++ ) + { + int iBoneIndex = G2_GetVertBoneIndex( v, k ); + float fBoneWeight = G2_GetVertBoneWeight( v, k, fTotalWeight, iNumWeights ); + + //bone = bonePtr + piBoneRefs[w->boneIndex]; +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &v->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &v->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &v->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[j][0] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0][3] ); + pTri[j][1] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1][3] ); + pTri[j][2] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2], vec ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2][3] ); +#else + pTri[j][0] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0], v->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[0][3] ); + pTri[j][1] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1], v->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[1][3] ); + pTri[j][2] += fBoneWeight * ( DotProduct( bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2], v->vertCoords ) + bonePtr[piBoneRefs[iBoneIndex]].second.matrix[2][3] ); +#endif + } + + v++;// = (mdxmVertex_t *)&v->weights[/*v->numWeights*/surface->maxVertBoneWeights]; + } + + // clear out used arrays + memset( axes, 0, sizeof( axes ) ); + memset( sides, 0, sizeof( sides ) ); + + // work out actual sides of the tag triangle + for ( j = 0; j < 3; j++ ) + { + sides[j][0] = pTri[(j+1)%3][0] - pTri[j][0]; + sides[j][1] = pTri[(j+1)%3][1] - pTri[j][1]; + sides[j][2] = pTri[(j+1)%3][2] - pTri[j][2]; + } + + // do math trig to work out what the matrix will be from this triangle's translated position + VectorNormalize2( sides[iG2_TRISIDE_LONGEST], axes[0] ); + VectorNormalize2( sides[iG2_TRISIDE_SHORTEST], axes[1] ); + + // project shortest side so that it is exactly 90 degrees to the longer side + d = DotProduct( axes[0], axes[1] ); + VectorMA( axes[0], -d, axes[1], axes[0] ); + VectorNormalize2( axes[0], axes[0] ); + + CrossProduct( sides[iG2_TRISIDE_LONGEST], sides[iG2_TRISIDE_SHORTEST], axes[2] ); + VectorNormalize2( axes[2], axes[2] ); + + // set up location in world space of the origin point in out going matrix + boltList[boltNum].position.matrix[0][3] = pTri[MDX_TAG_ORIGIN][0]; + boltList[boltNum].position.matrix[1][3] = pTri[MDX_TAG_ORIGIN][1]; + boltList[boltNum].position.matrix[2][3] = pTri[MDX_TAG_ORIGIN][2]; + + // copy axis to matrix - do some magic to orient minus Y to positive X and so on so bolt on stuff is oriented correctly + boltList[boltNum].position.matrix[0][0] = axes[1][0]; + boltList[boltNum].position.matrix[0][1] = axes[0][0]; + boltList[boltNum].position.matrix[0][2] = -axes[2][0]; + + boltList[boltNum].position.matrix[1][0] = axes[1][1]; + boltList[boltNum].position.matrix[1][1] = axes[0][1]; + boltList[boltNum].position.matrix[1][2] = -axes[2][1]; + + boltList[boltNum].position.matrix[2][0] = axes[1][2]; + boltList[boltNum].position.matrix[2][1] = axes[0][2]; + boltList[boltNum].position.matrix[2][2] = -axes[2][2]; + } + +} + + +// now go through all the generated surfaces that aren't included in the model surface hierarchy and create the correct bolt info for them +void G2_ProcessGeneratedSurfaceBolts(CGhoul2Info &ghoul2, mdxaBone_v &bonePtr, model_t *mod_t) +{ +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceTimer_G2_ProcessGeneratedSurfaceBolts.Start(); +#endif + // look through the surfaces off the end of the pre-defined model surfaces + for (int i=0; i< ghoul2.mSlist.size(); i++) + { + // only look for bolts if we are actually a generated surface, and not just an overriden one + if (ghoul2.mSlist[i].offFlags & G2SURFACEFLAG_GENERATED) + { + // well alrighty then. Lets see if there is a bolt that is attempting to use it + int boltNum = G2_Find_Bolt_Surface_Num(ghoul2.mBltlist, i, G2SURFACEFLAG_GENERATED); + // yes - ok, processing time. + if (boltNum != -1) + { + G2_ProcessSurfaceBolt(bonePtr, NULL, boltNum, ghoul2.mBltlist, &ghoul2.mSlist[i], mod_t); + } + } + } +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_G2_ProcessGeneratedSurfaceBolts += G2PerformanceTimer_G2_ProcessGeneratedSurfaceBolts.End(); +#endif +} + +#ifndef DEDICATED +void RenderSurfaces(CRenderSurface &RS) //also ended up just ripping right from SP. +{ +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceTimer_RenderSurfaces.Start(); +#endif + int i; + const shader_t *shader = 0; + int offFlags = 0; +#ifdef _G2_GORE + bool drawGore = true; +#endif + + assert(RS.currentModel); + assert(RS.currentModel->mdxm); + // back track and get the surfinfo struct for this surface + mdxmSurface_t *surface = (mdxmSurface_t *)G2_FindSurface(RS.currentModel, RS.surfaceNum, RS.lod); + mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)RS.currentModel->mdxm + sizeof(mdxmHeader_t)); + mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surface->thisSurfaceIndex]); + + // see if we have an override surface in the surface list + const surfaceInfo_t *surfOverride = G2_FindOverrideSurface(RS.surfaceNum, RS.rootSList); + + // really, we should use the default flags for this surface unless it's been overriden + offFlags = surfInfo->flags; + + // set the off flags if we have some + if (surfOverride) + { + offFlags = surfOverride->offFlags; + } + + // if this surface is not off, add it to the shader render list + if (!offFlags) + { + if ( RS.cust_shader ) + { + shader = RS.cust_shader; + } + else if ( RS.skin ) + { + int j; + + // match the surface name to something in the skin file + shader = tr.defaultShader; + for ( j = 0 ; j < RS.skin->numSurfaces ; j++ ) + { + // the names have both been lowercased + if ( !strcmp( RS.skin->surfaces[j]->name, surfInfo->name ) ) + { + shader = RS.skin->surfaces[j]->shader; + break; + } + } + } + else + { + shader = R_GetShaderByHandle( surfInfo->shaderIndex ); + } + + //rww - catch surfaces with bad shaders + //assert(shader != tr.defaultShader); + //Alright, this is starting to annoy me because of the state of the assets. Disabling for now. + // we will add shadows even if the main object isn't visible in the view + // stencil shadows can't do personal models unless I polyhedron clip + //using z-fail now so can do personal models -rww + if ( /*!RS.personalModel + && */r_shadows->integer == 2 +// && RS.fogNum == 0 + && (RS.renderfx & RF_SHADOW_PLANE ) + && !(RS.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) + { // set the surface info to point at the where the transformed bone list is going to be for when the surface gets rendered out + CRenderableSurface *newSurf = new CRenderableSurface; +#ifdef _XBOX //testing performance benefit vs ugliness + // On Xbox, we always use the lowest LOD + mdxmSurface_t *lowsurface = (mdxmSurface_t *)G2_FindSurface(RS.currentModel, RS.surfaceNum, RS.currentModel->numLods-1); + newSurf->surfaceData = lowsurface; +#else + if (surface->numVerts >= SHADER_MAX_VERTEXES/2) + { //we need numVerts*2 xyz slots free in tess to do shadow, if this surf is going to exceed that then let's try the lowest lod -rww + mdxmSurface_t *lowsurface = (mdxmSurface_t *)G2_FindSurface(RS.currentModel, RS.surfaceNum, RS.currentModel->numLods-1); + newSurf->surfaceData = lowsurface; + } + else + { + newSurf->surfaceData = surface; + } +#endif + newSurf->boneCache = RS.boneCache; + R_AddDrawSurf( (surfaceType_t *)newSurf, tr.shadowShader, 0, qfalse ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 +// && RS.fogNum == 0 + && (RS.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) + { // set the surface info to point at the where the transformed bone list is going to be for when the surface gets rendered out + CRenderableSurface *newSurf = new CRenderableSurface; + newSurf->surfaceData = surface; + newSurf->boneCache = RS.boneCache; + R_AddDrawSurf( (surfaceType_t *)newSurf, tr.projectionShadowShader, 0, qfalse ); + } + + // don't add third_person objects if not viewing through a portal + if ( !RS.personalModel ) + { // set the surface info to point at the where the transformed bone list is going to be for when the surface gets rendered out + CRenderableSurface *newSurf = new CRenderableSurface; + newSurf->surfaceData = surface; + newSurf->boneCache = RS.boneCache; + R_AddDrawSurf( (surfaceType_t *)newSurf, (shader_t *)shader, RS.fogNum, qfalse ); + +#ifdef _G2_GORE + if (RS.gore_set && drawGore) + { + int curTime = G2API_GetTime(tr.refdef.time); + pair::iterator,multimap::iterator> range= + RS.gore_set->mGoreRecords.equal_range(RS.surfaceNum); + multimap::iterator k,kcur; + CRenderableSurface *last=newSurf; + for (k=range.first;k!=range.second;) + { + kcur=k; + k++; + GoreTextureCoordinates *tex=FindGoreRecord((*kcur).second.mGoreTag); + if (!tex || // it is gone, lets get rid of it + (*kcur).second.mDeleteTime && curTime>=(*kcur).second.mDeleteTime) // out of time + { + if (tex) + { + (*tex).~GoreTextureCoordinates(); + //I don't know what's going on here, it should call the destructor for + //this when it erases the record but sometimes it doesn't. -rww + } + + RS.gore_set->mGoreRecords.erase(kcur); + } + else if (tex->tex[RS.lod]) + { + CRenderableSurface *newSurf2 = AllocRS(); + *newSurf2=*newSurf; + newSurf2->goreChain=0; + newSurf2->alternateTex=tex->tex[RS.lod]; + newSurf2->scale=1.0f; + newSurf2->fade=1.0f; + newSurf2->impactTime=1.0f; // done with + int magicFactor42=500; // ms, impact time + if (curTime>(*kcur).second.mGoreGrowStartTime && curTime<(*kcur).second.mGoreGrowStartTime+magicFactor42) + { + newSurf2->impactTime=float(curTime-(*kcur).second.mGoreGrowStartTime)/float(magicFactor42); // linear + } + if (curTime<(*kcur).second.mGoreGrowEndTime) + { + newSurf2->scale=1.0f/((curTime-(*kcur).second.mGoreGrowStartTime)*(*kcur).second.mGoreGrowFactor + (*kcur).second.mGoreGrowOffset); + if (newSurf2->scale<1.0f) + { + newSurf2->scale=1.0f; + } + } + shader_t *gshader; + if ((*kcur).second.shader) + { + gshader=R_GetShaderByHandle((*kcur).second.shader); + } + else + { + gshader=R_GetShaderByHandle(goreShader); + } + + // Set fade on surf. + //Only if we have a fade time set, and let us fade on rgb if we want -rww + if ((*kcur).second.mDeleteTime && (*kcur).second.mFadeTime) + { + if ((*kcur).second.mDeleteTime - curTime < (*kcur).second.mFadeTime) + { + newSurf2->fade=(float)((*kcur).second.mDeleteTime - curTime)/(*kcur).second.mFadeTime; + if ((*kcur).second.mFadeRGB) + { //RGB fades are scaled from 2.0f to 3.0f (simply to differentiate) + newSurf2->fade += 2.0f; + + if (newSurf2->fade < 2.01f) + { + newSurf2->fade = 2.01f; + } + } + } + } + + last->goreChain=newSurf2; + last=newSurf2; + R_AddDrawSurf( (surfaceType_t *)newSurf2,gshader, RS.fogNum, qfalse ); + } + } + } +#endif + } + } + + // if we are turning off all descendants, then stop this recursion now + if (offFlags & G2SURFACEFLAG_NODESCENDANTS) + { + return; + } + + // now recursively call for the children + for (i=0; i< surfInfo->numChildren; i++) + { + RS.surfaceNum = surfInfo->childIndexes[i]; + RenderSurfaces(RS); + } + +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_RenderSurfaces += G2PerformanceTimer_RenderSurfaces.End(); +#endif +} +#endif //!DEDICATED + +// Go through the model and deal with just the surfaces that are tagged as bolt on points - this is for the server side skeleton construction +void ProcessModelBoltSurfaces(int surfaceNum, surfaceInfo_v &rootSList, + mdxaBone_v &bonePtr, model_t *currentModel, int lod, boltInfo_v &boltList) +{ +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceTimer_ProcessModelBoltSurfaces.Start(); +#endif + int i; + int offFlags = 0; + + // back track and get the surfinfo struct for this surface + mdxmSurface_t *surface = (mdxmSurface_t *)G2_FindSurface((void *)currentModel, surfaceNum, 0); + mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)currentModel->mdxm + sizeof(mdxmHeader_t)); + mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surface->thisSurfaceIndex]); + + // see if we have an override surface in the surface list + surfaceInfo_t *surfOverride = G2_FindOverrideSurface(surfaceNum, rootSList); + + // really, we should use the default flags for this surface unless it's been overriden + offFlags = surfInfo->flags; + + // set the off flags if we have some + if (surfOverride) + { + offFlags = surfOverride->offFlags; + } + + // is this surface considered a bolt surface? + if (surfInfo->flags & G2SURFACEFLAG_ISBOLT) + { + // well alrighty then. Lets see if there is a bolt that is attempting to use it + int boltNum = G2_Find_Bolt_Surface_Num(boltList, surfaceNum, 0); + // yes - ok, processing time. + if (boltNum != -1) + { + G2_ProcessSurfaceBolt(bonePtr, surface, boltNum, boltList, surfOverride, currentModel); + } + } + + // if we are turning off all descendants, then stop this recursion now + if (offFlags & G2SURFACEFLAG_NODESCENDANTS) + { + return; + } + + // now recursively call for the children + for (i=0; i< surfInfo->numChildren; i++) + { + ProcessModelBoltSurfaces(surfInfo->childIndexes[i], rootSList, bonePtr, currentModel, lod, boltList); + } + +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_ProcessModelBoltSurfaces += G2PerformanceTimer_ProcessModelBoltSurfaces.End(); +#endif +} + + +// build the used bone list so when doing bone transforms we can determine if we need to do it or not +void G2_ConstructUsedBoneList(CConstructBoneList &CBL) +{ + int i, j; + int offFlags = 0; + + // back track and get the surfinfo struct for this surface + const mdxmSurface_t *surface = (mdxmSurface_t *)G2_FindSurface((void *)CBL.currentModel, CBL.surfaceNum, 0); + const mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)CBL.currentModel->mdxm + sizeof(mdxmHeader_t)); + const mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surface->thisSurfaceIndex]); + const model_t *mod_a = R_GetModelByHandle(CBL.currentModel->mdxm->animIndex); + const mdxaSkelOffsets_t *offsets = (mdxaSkelOffsets_t *)((byte *)mod_a->mdxa + sizeof(mdxaHeader_t)); + const mdxaSkel_t *skel, *childSkel; + + // see if we have an override surface in the surface list + const surfaceInfo_t *surfOverride = G2_FindOverrideSurface(CBL.surfaceNum, CBL.rootSList); + + // really, we should use the default flags for this surface unless it's been overriden + offFlags = surfInfo->flags; + + // set the off flags if we have some + if (surfOverride) + { + offFlags = surfOverride->offFlags; + } + + // if this surface is not off, add it to the shader render list + if (!(offFlags & G2SURFACEFLAG_OFF)) + { + int *bonesReferenced = (int *)((byte*)surface + surface->ofsBoneReferences); + // now whip through the bones this surface uses + for (i=0; inumBoneReferences;i++) + { + int iBoneIndex = bonesReferenced[i]; + CBL.boneUsedList[iBoneIndex] = 1; + + // now go and check all the descendant bones attached to this bone and see if any have the always flag on them. If so, activate them + skel = (mdxaSkel_t *)((byte *)mod_a->mdxa + sizeof(mdxaHeader_t) + offsets->offsets[iBoneIndex]); + + // for every child bone... + for (j=0; j< skel->numChildren; j++) + { + // get the skel data struct for each child bone of the referenced bone + childSkel = (mdxaSkel_t *)((byte *)mod_a->mdxa + sizeof(mdxaHeader_t) + offsets->offsets[skel->children[j]]); + + // does it have the always on flag on? + if (childSkel->flags & G2BONEFLAG_ALWAYSXFORM) + { + // yes, make sure it's in the list of bones to be transformed. + CBL.boneUsedList[skel->children[j]] = 1; + } + } + + // now we need to ensure that the parents of this bone are actually active... + // + int iParentBone = skel->parent; + while (iParentBone != -1) + { + if (CBL.boneUsedList[iParentBone]) // no need to go higher + break; + CBL.boneUsedList[iParentBone] = 1; + skel = (mdxaSkel_t *)((byte *)mod_a->mdxa + sizeof(mdxaHeader_t) + offsets->offsets[iParentBone]); + iParentBone = skel->parent; + } + } + } + else + // if we are turning off all descendants, then stop this recursion now + if (offFlags & G2SURFACEFLAG_NODESCENDANTS) + { + return; + } + + // now recursively call for the children + for (i=0; i< surfInfo->numChildren; i++) + { + CBL.surfaceNum = surfInfo->childIndexes[i]; + G2_ConstructUsedBoneList(CBL); + } +} + + +// sort all the ghoul models in this list so if they go in reference order. This will ensure the bolt on's are attached to the right place +// on the previous model, since it ensures the model being attached to is built and rendered first. + +// NOTE!! This assumes at least one model will NOT have a parent. If it does - we are screwed +static void G2_Sort_Models(CGhoul2Info_v &ghoul2, int * const modelList, int * const modelCount) +{ + int startPoint, endPoint; + int i, boltTo, j; + + *modelCount = 0; + + // first walk all the possible ghoul2 models, and stuff the out array with those with no parents + for (i=0; i> MODEL_SHIFT) & MODEL_AND; + // is it any of the models we just added to the list? + for (j=startPoint; jmdxm); + + // point at first lod list + byte *current = (byte*)((int)mod->mdxm + (int)mod->mdxm->ofsLODs); + int i; + + //walk the lods + assert(lod>=0&&lodmdxm->numLODs); + for (i=0; iofsEnd; + } + + // avoid the lod pointer data structure + current += sizeof(mdxmLOD_t); + + mdxmLODSurfOffset_t *indexes = (mdxmLODSurfOffset_t *)current; + // we are now looking at the offset array + assert(index>=0&&indexmdxm->numSurfaces); + current += indexes->offsets[index]; + + return (void *)current; +} + +//#define G2EVALRENDER + +// We've come across a surface that's designated as a bolt surface, process it and put it in the appropriate bolt place +void G2_ProcessSurfaceBolt2(CBoneCache &boneCache, const mdxmSurface_t *surface, int boltNum, boltInfo_v &boltList, const surfaceInfo_t *surfInfo, const model_t *mod,mdxaBone_t &retMatrix) +{ + mdxmVertex_t *v, *vert0, *vert1, *vert2; + vec3_t axes[3], sides[3]; + float pTri[3][3], d; + int j, k; + + // now there are two types of tag surface - model ones and procedural generated types - lets decide which one we have here. + if (surfInfo && surfInfo->offFlags == G2SURFACEFLAG_GENERATED) + { + int surfNumber = surfInfo->genPolySurfaceIndex & 0x0ffff; + int polyNumber = (surfInfo->genPolySurfaceIndex >> 16) & 0x0ffff; + + // find original surface our original poly was in. + mdxmSurface_t *originalSurf = (mdxmSurface_t *)G2_FindSurface_BC(mod, surfNumber, surfInfo->genLod); + mdxmTriangle_t *originalTriangleIndexes = (mdxmTriangle_t *)((byte*)originalSurf + originalSurf->ofsTriangles); + + // get the original polys indexes + int index0 = originalTriangleIndexes[polyNumber].indexes[0]; + int index1 = originalTriangleIndexes[polyNumber].indexes[1]; + int index2 = originalTriangleIndexes[polyNumber].indexes[2]; + + // decide where the original verts are + vert0 = (mdxmVertex_t *) ((byte *)originalSurf + originalSurf->ofsVerts); + vert0+=index0; + + vert1 = (mdxmVertex_t *) ((byte *)originalSurf + originalSurf->ofsVerts); + vert1+=index1; + + vert2 = (mdxmVertex_t *) ((byte *)originalSurf + originalSurf->ofsVerts); + vert2+=index2; + + // clear out the triangle verts to be + VectorClear( pTri[0] ); + VectorClear( pTri[1] ); + VectorClear( pTri[2] ); + int *piBoneReferences = (int*) ((byte*)originalSurf + originalSurf->ofsBoneReferences); + +// mdxmWeight_t *w; + + // now go and transform just the points we need from the surface that was hit originally +// w = vert0->weights; + float fTotalWeight = 0.0f; + int iNumWeights = G2_GetVertWeights( vert0 ); + for ( k = 0 ; k < iNumWeights ; k++ ) + { + int iBoneIndex = G2_GetVertBoneIndex( vert0, k ); + float fBoneWeight = G2_GetVertBoneWeight( vert0, k, fTotalWeight, iNumWeights ); + +#ifdef G2EVALRENDER + const mdxaBone_t &bone=boneCache.EvalRender(piBoneReferences[iBoneIndex]); +#else + const mdxaBone_t &bone=boneCache.Eval(piBoneReferences[iBoneIndex]); +#endif + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &vert0->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert0->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert0->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[0][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vec ) + bone.matrix[0][3] ); + pTri[0][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vec ) + bone.matrix[1][3] ); + pTri[0][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vec ) + bone.matrix[2][3] ); +#else + pTri[0][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vert0->vertCoords ) + bone.matrix[0][3] ); + pTri[0][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vert0->vertCoords ) + bone.matrix[1][3] ); + pTri[0][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vert0->vertCoords ) + bone.matrix[2][3] ); +#endif + } + +// w = vert1->weights; + fTotalWeight = 0.0f; + iNumWeights = G2_GetVertWeights( vert1 ); + for ( k = 0 ; k < iNumWeights ; k++) + { + int iBoneIndex = G2_GetVertBoneIndex( vert1, k ); + float fBoneWeight = G2_GetVertBoneWeight( vert1, k, fTotalWeight, iNumWeights ); + +#ifdef G2EVALRENDER + const mdxaBone_t &bone=boneCache.EvalRender(piBoneReferences[iBoneIndex]); +#else + const mdxaBone_t &bone=boneCache.Eval(piBoneReferences[iBoneIndex]); +#endif + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &vert1->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert1->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert1->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[1][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vec ) + bone.matrix[0][3] ); + pTri[1][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vec ) + bone.matrix[1][3] ); + pTri[1][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vec ) + bone.matrix[2][3] ); +#else + pTri[1][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vert1->vertCoords ) + bone.matrix[0][3] ); + pTri[1][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vert1->vertCoords ) + bone.matrix[1][3] ); + pTri[1][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vert1->vertCoords ) + bone.matrix[2][3] ); +#endif + } + +// w = vert2->weights; + fTotalWeight = 0.0f; + iNumWeights = G2_GetVertWeights( vert2 ); + for ( k = 0 ; k < iNumWeights ; k++) + { + int iBoneIndex = G2_GetVertBoneIndex( vert2, k ); + float fBoneWeight = G2_GetVertBoneWeight( vert2, k, fTotalWeight, iNumWeights ); + +#ifdef G2EVALRENDER + const mdxaBone_t &bone=boneCache.EvalRender(piBoneReferences[iBoneIndex]); +#else + const mdxaBone_t &bone=boneCache.Eval(piBoneReferences[iBoneIndex]); +#endif + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &vert2->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert2->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert2->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[2][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vec ) + bone.matrix[0][3] ); + pTri[2][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vec ) + bone.matrix[1][3] ); + pTri[2][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vec ) + bone.matrix[2][3] ); +#else + pTri[2][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vert2->vertCoords ) + bone.matrix[0][3] ); + pTri[2][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vert2->vertCoords ) + bone.matrix[1][3] ); + pTri[2][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vert2->vertCoords ) + bone.matrix[2][3] ); +#endif + } + + vec3_t normal; + vec3_t up; + vec3_t right; + vec3_t vec0, vec1; + // work out baryCentricK + float baryCentricK = 1.0 - (surfInfo->genBarycentricI + surfInfo->genBarycentricJ); + + // now we have the model transformed into model space, now generate an origin. + retMatrix.matrix[0][3] = (pTri[0][0] * surfInfo->genBarycentricI) + (pTri[1][0] * surfInfo->genBarycentricJ) + (pTri[2][0] * baryCentricK); + retMatrix.matrix[1][3] = (pTri[0][1] * surfInfo->genBarycentricI) + (pTri[1][1] * surfInfo->genBarycentricJ) + (pTri[2][1] * baryCentricK); + retMatrix.matrix[2][3] = (pTri[0][2] * surfInfo->genBarycentricI) + (pTri[1][2] * surfInfo->genBarycentricJ) + (pTri[2][2] * baryCentricK); + + // generate a normal to this new triangle + VectorSubtract(pTri[0], pTri[1], vec0); + VectorSubtract(pTri[2], pTri[1], vec1); + + CrossProduct(vec0, vec1, normal); + VectorNormalize(normal); + + // forward vector + retMatrix.matrix[0][0] = normal[0]; + retMatrix.matrix[1][0] = normal[1]; + retMatrix.matrix[2][0] = normal[2]; + + // up will be towards point 0 of the original triangle. + // so lets work it out. Vector is hit point - point 0 + up[0] = retMatrix.matrix[0][3] - pTri[0][0]; + up[1] = retMatrix.matrix[1][3] - pTri[0][1]; + up[2] = retMatrix.matrix[2][3] - pTri[0][2]; + + // normalise it + VectorNormalize(up); + + // that's the up vector + retMatrix.matrix[0][1] = up[0]; + retMatrix.matrix[1][1] = up[1]; + retMatrix.matrix[2][1] = up[2]; + + // right is always straight + + CrossProduct( normal, up, right ); + // that's the up vector + retMatrix.matrix[0][2] = right[0]; + retMatrix.matrix[1][2] = right[1]; + retMatrix.matrix[2][2] = right[2]; + + + } + // no, we are looking at a normal model tag + else + { + // whip through and actually transform each vertex + v = (mdxmVertex_t *) ((byte *)surface + surface->ofsVerts); + int *piBoneReferences = (int*) ((byte*)surface + surface->ofsBoneReferences); + for ( j = 0; j < 3; j++ ) + { +// mdxmWeight_t *w; + + VectorClear( pTri[j] ); + // w = v->weights; + + const int iNumWeights = G2_GetVertWeights( v ); + + float fTotalWeight = 0.0f; + for ( k = 0 ; k < iNumWeights ; k++) + { + int iBoneIndex = G2_GetVertBoneIndex( v, k ); + float fBoneWeight = G2_GetVertBoneWeight( v, k, fTotalWeight, iNumWeights ); + +#ifdef G2EVALRENDER + const mdxaBone_t &bone=boneCache.EvalRender(piBoneReferences[iBoneIndex]); +#else + const mdxaBone_t &bone=boneCache.Eval(piBoneReferences[iBoneIndex]); +#endif + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &v->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &v->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &v->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[j][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vec ) + bone.matrix[0][3] ); + pTri[j][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vec ) + bone.matrix[1][3] ); + pTri[j][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vec ) + bone.matrix[2][3] ); +#else + pTri[j][0] += fBoneWeight * ( DotProduct( bone.matrix[0], v->vertCoords ) + bone.matrix[0][3] ); + pTri[j][1] += fBoneWeight * ( DotProduct( bone.matrix[1], v->vertCoords ) + bone.matrix[1][3] ); + pTri[j][2] += fBoneWeight * ( DotProduct( bone.matrix[2], v->vertCoords ) + bone.matrix[2][3] ); +#endif + } + + v++;// = (mdxmVertex_t *)&v->weights[/*v->numWeights*/surface->maxVertBoneWeights]; + } + + // clear out used arrays + memset( axes, 0, sizeof( axes ) ); + memset( sides, 0, sizeof( sides ) ); + + // work out actual sides of the tag triangle + for ( j = 0; j < 3; j++ ) + { + sides[j][0] = pTri[(j+1)%3][0] - pTri[j][0]; + sides[j][1] = pTri[(j+1)%3][1] - pTri[j][1]; + sides[j][2] = pTri[(j+1)%3][2] - pTri[j][2]; + } + + // do math trig to work out what the matrix will be from this triangle's translated position + VectorNormalize2( sides[iG2_TRISIDE_LONGEST], axes[0] ); + VectorNormalize2( sides[iG2_TRISIDE_SHORTEST], axes[1] ); + + // project shortest side so that it is exactly 90 degrees to the longer side + d = DotProduct( axes[0], axes[1] ); + VectorMA( axes[0], -d, axes[1], axes[0] ); + VectorNormalize2( axes[0], axes[0] ); + + CrossProduct( sides[iG2_TRISIDE_LONGEST], sides[iG2_TRISIDE_SHORTEST], axes[2] ); + VectorNormalize2( axes[2], axes[2] ); + + // set up location in world space of the origin point in out going matrix + retMatrix.matrix[0][3] = pTri[MDX_TAG_ORIGIN][0]; + retMatrix.matrix[1][3] = pTri[MDX_TAG_ORIGIN][1]; + retMatrix.matrix[2][3] = pTri[MDX_TAG_ORIGIN][2]; + + // copy axis to matrix - do some magic to orient minus Y to positive X and so on so bolt on stuff is oriented correctly + retMatrix.matrix[0][0] = axes[1][0]; + retMatrix.matrix[0][1] = axes[0][0]; + retMatrix.matrix[0][2] = -axes[2][0]; + + retMatrix.matrix[1][0] = axes[1][1]; + retMatrix.matrix[1][1] = axes[0][1]; + retMatrix.matrix[1][2] = -axes[2][1]; + + retMatrix.matrix[2][0] = axes[1][2]; + retMatrix.matrix[2][1] = axes[0][2]; + retMatrix.matrix[2][2] = -axes[2][2]; + } + +} + +void G2_GetBoltMatrixLow(CGhoul2Info &ghoul2,int boltNum,const vec3_t scale,mdxaBone_t &retMatrix) +{ + if (!ghoul2.mBoneCache) + { + retMatrix=identityMatrix; + return; + } + assert(ghoul2.mBoneCache); + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + boltInfo_v &boltList=ghoul2.mBltlist; + assert(boltNum>=0&&boltNum= boltList.size()) + { + char fName[MAX_QPATH]; + char mName[MAX_QPATH]; + int bLink = ghoul2.mModelBoltLink; + + if (ghoul2.currentModel) + { + strcpy(mName, ghoul2.currentModel->name); + } + else + { + strcpy(mName, "NULL!"); + } + + if (ghoul2.mFileName && ghoul2.mFileName[0]) + { + strcpy(fName, ghoul2.mFileName); + } + else + { + strcpy(fName, "None?!"); + } + + Com_Error(ERR_DROP, "Write down or save this error message, show it to Rich\nBad bolt index on model %s (filename %s), index %i boltlink %i\n", mName, fName, boltNum, bLink); + } +#endif + if (boltList[boltNum].boneNumber>=0) + { + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boltList[boltNum].boneNumber]); + Multiply_3x4Matrix(&retMatrix, (mdxaBone_t *)&boneCache.EvalUnsmooth(boltList[boltNum].boneNumber), &skel->BasePoseMat); + } + else if (boltList[boltNum].surfaceNumber>=0) + { + const surfaceInfo_t *surfInfo=0; + { + int i; + for (i=0;isurface<10000) + { + surface = (mdxmSurface_t *)G2_FindSurface_BC(boneCache.mod,surfInfo->surface, 0); + } + G2_ProcessSurfaceBolt2(boneCache,surface,boltNum,boltList,surfInfo,(model_t *)boneCache.mod,retMatrix); + } + else + { + // we have a bolt without a bone or surface, not a huge problem but we ought to at least clear the bolt matrix + retMatrix=identityMatrix; + } +} + +static void RootMatrix(CGhoul2Info_v &ghoul2,int time,const vec3_t scale,mdxaBone_t &retMatrix) +{ + int i; + for (i=0; ivalue); +} + +/* +============== +R_AddGHOULSurfaces +============== +*/ +void R_AddGhoulSurfaces( trRefEntity_t *ent ) { +#ifndef DEDICATED +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceTimer_R_AddGHOULSurfaces.Start(); +#endif + shader_t *cust_shader = 0; +#ifdef _G2_GORE + shader_t *gore_shader = 0; +#endif + int fogNum = 0; + qboolean personalModel; + int cull; + int i, whichLod, j; + skin_t *skin; + int modelCount; + mdxaBone_t rootMatrix; + CGhoul2Info_v &ghoul2 = *((CGhoul2Info_v *)ent->e.ghoul2); + + if ( !ghoul2.IsValid() ) + { + return; + } + // if we don't want server ghoul2 models and this is one, or we just don't want ghoul2 models at all, then return + if (r_noServerGhoul2->integer) + { + return; + } + if (!G2_SetupModelPointers(ghoul2)) + { + return; + } + + int currentTime=G2API_GetTime(tr.refdef.time); + + + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + cull = R_GCullModel (ent ); + if ( cull == CULL_OUT ) + { + return; + } + HackadelicOnClient=true; + // are any of these models setting a new origin? + RootMatrix(ghoul2,currentTime, ent->e.modelScale,rootMatrix); + + // don't add third_person objects if not in a portal + personalModel = (qboolean)((ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal); + + int modelList[256]; + assert(ghoul2.size()<=255); + modelList[255]=548; + + // set up lighting now that we know we aren't culled + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // see if we are in a fog volume + fogNum = R_GComputeFogNum( ent ); + + // order sort the ghoul 2 models so bolt ons get bolted to the right model + G2_Sort_Models(ghoul2, modelList, &modelCount); + assert(modelList[255]==548); + +#ifdef _G2_GORE + if (goreShader == -1) + { + goreShader=RE_RegisterShader("gfx/damage/burnmark1"); + } +#endif + + // construct a world matrix for this entity + G2_GenerateWorldMatrix(ent->e.angles, ent->e.origin); + + // walk each possible model for this entity and try rendering it out + for (j=0; je.customShader) + { + cust_shader = R_GetShaderByHandle(ent->e.customShader ); + } + else + { + cust_shader = NULL; + // figure out the custom skin thing + if (ghoul2[i].mCustomSkin) + { + skin = R_GetSkinByHandle(ghoul2[i].mCustomSkin ); + } + else if (ent->e.customSkin) + { + skin = R_GetSkinByHandle(ent->e.customSkin ); + } + else if ( ghoul2[i].mSkin > 0 && ghoul2[i].mSkin < tr.numSkins ) + { + skin = R_GetSkinByHandle( ghoul2[i].mSkin ); + } + } + + if (j&&ghoul2[i].mModelBoltLink != -1) + { + int boltMod = (ghoul2[i].mModelBoltLink >> MODEL_SHIFT) & MODEL_AND; + int boltNum = (ghoul2[i].mModelBoltLink >> BOLT_SHIFT) & BOLT_AND; + mdxaBone_t bolt; + G2_GetBoltMatrixLow(ghoul2[boltMod],boltNum,ent->e.modelScale,bolt); + G2_TransformGhoulBones(ghoul2[i].mBlist,bolt, ghoul2[i],currentTime); + } + else + { + G2_TransformGhoulBones(ghoul2[i].mBlist, rootMatrix, ghoul2[i],currentTime); + } + whichLod = G2_ComputeLOD( ent, ghoul2[i].currentModel, ghoul2[i].mLodBias ); + G2_FindOverrideSurface(-1,ghoul2[i].mSlist); //reset the quick surface override lookup; + +#ifdef _G2_GORE + CGoreSet *gore=0; + if (ghoul2[i].mGoreSetTag) + { + gore=FindGoreSet(ghoul2[i].mGoreSetTag); + if (!gore) // my gore is gone, so remove it + { + ghoul2[i].mGoreSetTag=0; + } + } + + CRenderSurface RS(ghoul2[i].mSurfaceRoot, ghoul2[i].mSlist, cust_shader, fogNum, personalModel, ghoul2[i].mBoneCache, ent->e.renderfx, skin, (model_t *)ghoul2[i].currentModel, whichLod, ghoul2[i].mBltlist, gore_shader, gore); +#else + CRenderSurface RS(ghoul2[i].mSurfaceRoot, ghoul2[i].mSlist, cust_shader, fogNum, personalModel, ghoul2[i].mBoneCache, ent->e.renderfx, skin, (model_t *)ghoul2[i].currentModel, whichLod, ghoul2[i].mBltlist); +#endif + if (!personalModel && (RS.renderfx & RF_SHADOW_PLANE) && !bInShadowRange(ent->e.origin)) + { + RS.renderfx |= RF_NOSHADOW; + } + RenderSurfaces(RS); + } + } + HackadelicOnClient=false; + +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_R_AddGHOULSurfaces += G2PerformanceTimer_R_AddGHOULSurfaces.End(); +#endif +#endif //!DEDICATED +} + +#ifdef _G2_LISTEN_SERVER_OPT +qboolean G2API_OverrideServerWithClientData(CGhoul2Info *serverInstance); +#endif + +bool G2_NeedsRecalc(CGhoul2Info *ghlInfo,int frameNum) +{ + G2_SetupModelPointers(ghlInfo); + // not sure if I still need this test, probably + if (ghlInfo->mSkelFrameNum!=frameNum|| + !ghlInfo->mBoneCache|| + ghlInfo->mBoneCache->mod!=ghlInfo->currentModel) + { +#ifdef _G2_LISTEN_SERVER_OPT + if (ghlInfo->entityNum != ENTITYNUM_NONE && + G2API_OverrideServerWithClientData(ghlInfo)) + { //if we can manage this, then we don't have to reconstruct + return false; + } +#endif + ghlInfo->mSkelFrameNum=frameNum; + return true; + } + return false; +} + +/* +============== +G2_ConstructGhoulSkeleton - builds a complete skeleton for all ghoul models in a CGhoul2Info_v class - using LOD 0 +============== +*/ +void G2_ConstructGhoulSkeleton( CGhoul2Info_v &ghoul2,const int frameNum,bool checkForNewOrigin,const vec3_t scale) +{ +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceTimer_G2_ConstructGhoulSkeleton.Start(); +#endif + int i, j; + int modelCount; + mdxaBone_t rootMatrix; + + int modelList[256]; + assert(ghoul2.size()<=255); + modelList[255]=548; + + if (checkForNewOrigin) + { + RootMatrix(ghoul2,frameNum,scale,rootMatrix); + } + else + { + rootMatrix = identityMatrix; + } + + G2_Sort_Models(ghoul2, modelList, &modelCount); + assert(modelList[255]==548); + + for (j=0; j> MODEL_SHIFT) & MODEL_AND; + int boltNum = (ghoul2[i].mModelBoltLink >> BOLT_SHIFT) & BOLT_AND; + + mdxaBone_t bolt; + G2_GetBoltMatrixLow(ghoul2[boltMod],boltNum,scale,bolt); + G2_TransformGhoulBones(ghoul2[i].mBlist,bolt,ghoul2[i],frameNum,checkForNewOrigin); + } +#ifdef _G2_LISTEN_SERVER_OPT + else if (ghoul2[i].entityNum == ENTITYNUM_NONE || ghoul2[i].mSkelFrameNum != frameNum) +#else + else +#endif + { + G2_TransformGhoulBones(ghoul2[i].mBlist,rootMatrix,ghoul2[i],frameNum,checkForNewOrigin); + } + } + } +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_G2_ConstructGhoulSkeleton += G2PerformanceTimer_G2_ConstructGhoulSkeleton.End(); +#endif +} + +#ifndef DEDICATED + +static inline float G2_GetVertBoneWeightNotSlow( const mdxmVertex_t *pVert, const int iWeightNum) +{ + float fBoneWeight; + + int iTemp = pVert->BoneWeightings[iWeightNum]; + + iTemp|= (pVert->uiNmWeightsAndBoneIndexes >> (iG2_BONEWEIGHT_TOPBITS_SHIFT+(iWeightNum*2)) ) & iG2_BONEWEIGHT_TOPBITS_AND; + + fBoneWeight = fG2_BONEWEIGHT_RECIPROCAL_MULT * iTemp; + + return fBoneWeight; +} + +#ifdef _XBOX + +static inline void VertTransform(float *out_vert, const float *mat, const float *in_vert) +{ + __asm + { + push ESI + push EDI + push EAX + + mov ESI, in_vert + mov EDI, out_vert + mov EAX, mat + + movaps XMM4, [EAX + 0] // Load matrix columns + movaps XMM5, [EAX + 16] + movaps XMM6, [EAX + 32] + movaps XMM7, [EAX + 48] + + movss XMM0, [ESI + 0] // Compute x * column 0 + shufps XMM0, XMM0, 0x0 + mulps XMM0, XMM4 + + movss XMM1, [ESI + 4] // Compute y * column 1 + shufps XMM1, XMM1, 0x0 + mulps XMM1, XMM5 + + movss XMM2, [ESI + 8] // Compute z * column 2 + shufps XMM2, XMM2, 0x0 + mulps XMM2, XMM6 + + addps XMM0, XMM1 // Add dot products + addps XMM0, XMM2 + addps XMM0, XMM7 // Add translation + + movaps [EDI], XMM0 // Store result + + pop EAX + pop EDI + pop ESI + } +} + +static inline void VertTransformSR(float *out_vert, const float *mat, const float *in_vert) +{ + __asm + { + push ESI + push EDI + push EAX + + mov ESI, in_vert + mov EDI, out_vert + mov EAX, mat + + movaps XMM4, [EAX + 0] // Load matrix columns + movaps XMM5, [EAX + 16] + movaps XMM6, [EAX + 32] + + movss XMM0, [ESI + 0] // Compute x * column 0 + shufps XMM0, XMM0, 0x0 + mulps XMM0, XMM4 + + movss XMM1, [ESI + 4] // Compute y * column 1 + shufps XMM1, XMM1, 0x0 + mulps XMM1, XMM5 + + movss XMM2, [ESI + 8] // Compute z * column 2 + shufps XMM2, XMM2, 0x0 + mulps XMM2, XMM6 + + addps XMM0, XMM1 // Add dot products + addps XMM0, XMM2 + + movaps [EDI], XMM0 // Store result + + pop EAX + pop EDI + pop ESI + } +} + +static inline void VertTransformWeighted(float *out_vert, const float *mat, const float *in_vert, const float *weight) +{ + __asm + { + push ESI + push EDI + push EAX + push EDX + + mov ESI, in_vert + mov EDI, out_vert + mov EAX, mat + mov EDX, weight + + movaps XMM4, [EAX + 0] // Load matrix columns + movaps XMM5, [EAX + 16] + movaps XMM6, [EAX + 32] + movaps XMM7, [EAX + 48] + + movss XMM0, [ESI + 0] // Compute x * column 0 + shufps XMM0, XMM0, 0x0 + mulps XMM0, XMM4 + + movss XMM1, [ESI + 4] // Compute y * column 1 + shufps XMM1, XMM1, 0x0 + mulps XMM1, XMM5 + + movss XMM2, [ESI + 8] // Compute z * column 2 + shufps XMM2, XMM2, 0x0 + mulps XMM2, XMM6 + + addps XMM0, XMM1 // Add dot products + addps XMM0, XMM2 + addps XMM0, XMM7 // Add translation + + movss XMM4, [EDX] // Weight the resulting vector + shufps XMM4, XMM4, 0x0 + mulps XMM0, XMM4 + + movaps XMM5, [EDI] // Add the weighted vector to the current + addps XMM0, XMM5 + + movaps [EDI], XMM0 // Store result + + pop EDX + pop EAX + pop EDI + pop ESI + } +} + +static inline void VertTransformSRWeighted(float *out_vert, const float *mat, const float *in_vert, const float *weight) +{ + __asm + { + push ESI + push EDI + push EAX + push EDX + + mov ESI, in_vert + mov EDI, out_vert + mov EAX, mat + mov EDX, weight + + movaps XMM4, [EAX + 0] // Load matrix columns + movaps XMM5, [EAX + 16] + movaps XMM6, [EAX + 32] + + movss XMM0, [ESI + 0] // Compute x * column 0 + shufps XMM0, XMM0, 0x0 + mulps XMM0, XMM4 + + movss XMM1, [ESI + 4] // Compute y * column 1 + shufps XMM1, XMM1, 0x0 + mulps XMM1, XMM5 + + movss XMM2, [ESI + 8] // Compute z * column 2 + shufps XMM2, XMM2, 0x0 + mulps XMM2, XMM6 + + addps XMM0, XMM1 // Add dot products + addps XMM0, XMM2 + + movss XMM7, [EDX] // Weight the resulting vector + shufps XMM7, XMM7, 0x0 + mulps XMM0, XMM7 + + movaps XMM4, [EDI] // Add the weighted vector to the current + addps XMM0, XMM4 + + movaps [EDI], XMM0 // Store result + + pop EDX + pop EAX + pop EDI + pop ESI + } +} + +static void TransformRenderSurface(const mdxmSurface_t *surf, CBoneCache *bones, shaderCommands_t *out) +{ + const int *boneRefs = (int*) ((byte*)surf + surf->ofsBoneReferences); + int numVerts = surf->numVerts; + const mdxmVertex_t *vert = (mdxmVertex_t *) ((byte *)surf + surf->ofsVerts); + const mdxmVertexTexCoord_t *texCoord = (mdxmVertexTexCoord_t *) &vert[numVerts]; + + int boneIndex = -1; + const float *bone = NULL; + + int baseVert = out->numVertexes; + + while(numVerts--) + { + __declspec (align(16)) vec4_t vec; + __declspec (align(16)) vec4_t nrm; + +#ifdef _XBOX + __declspec (align(16)) vec4_t tan; + + Q_CastShort2FloatScale(&vec[0], &vert->vertCoords[0], 1.f / GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert->vertCoords[1], 1.f / GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert->vertCoords[2], 1.f / GLM_COMP_SIZE); + + if(tess.shader->needsNormal || tess.dlightBits) + { + nrm[0] = (((vert->normal & 0x00FF0000) >> 16) - 128.f) / 127.0f; + nrm[1] = (((vert->normal & 0x0000FF00) >> 8) - 128.f) / 127.0f; + nrm[2] = (((vert->normal & 0x000000FF) >> 0) - 128.f) / 127.0f; + } + + /*if(tess.shader->needsTangent || tess.dlightBits) + { + tan[0] = (((vert->tangent & 0x00FF0000) >> 16) - 128.f) / 127.0f; + tan[1] = (((vert->tangent & 0x0000FF00) >> 8) - 128.f) / 127.0f; + tan[2] = (((vert->tangent & 0x000000FF) >> 0) - 128.f) / 127.0f; + + out->setTangents = true; + }*/ +#else + VectorCopy(vert->vertCoords, vec); + VectorCopy(vert->normal, nrm); +#endif + + const int numWeights = G2_GetVertWeights( vert ); + + if (numWeights == 1) + { + // Slightly faster single weight path + int index = G2_GetVertBoneIndex( vert, 0 ); + + if ( index != boneIndex ) + { + CTransformBone *tbone = bones->EvalFull(boneRefs[index]); + bone = tbone->renderMatrix; + boneIndex = index; + } + + VertTransform(out->xyz[baseVert], bone, vec); +#ifdef _XBOX + if(tess.shader->needsNormal || tess.dlightBits) + VertTransformSR(out->normal[baseVert], bone, nrm); + + /*if(tess.shader->needsTangent || tess.dlightBits) + VertTransformSR(out->tangent[baseVert], bone, tan);*/ +#else + VertTransformSR(out->normal[baseVert], bone, nrm); +#endif + } + else + { + // Multi-weight blending path + VectorClear( out->xyz[baseVert] ); + + // Special case for first weight, as it's the only one we use for the normals + boneIndex = G2_GetVertBoneIndex( vert, 0 ); + CTransformBone *tbone = bones->EvalFull(boneRefs[boneIndex]); + bone = tbone->renderMatrix; + + __declspec (align(16)) float weight = G2_GetVertBoneWeightNotSlow( vert, 0 ); + + VertTransformWeighted(out->xyz[baseVert], bone, vec, &weight); +#ifdef _XBOX + if(tess.shader->needsNormal || tess.dlightBits) + VertTransformSR(out->normal[baseVert], bone, nrm); + + /*if(tess.shader->needsTangent || tess.dlightBits) + VertTransformSR(out->tangent[baseVert], bone, tan);*/ +#else + VertTransformSR(out->normal[baseVert], bone, nrm); +#endif + + for (int k = 1; k < numWeights; ++k) + { + boneIndex = G2_GetVertBoneIndex( vert, k ); + + tbone = bones->EvalFull(boneRefs[boneIndex]); + bone = tbone->renderMatrix; + + weight = G2_GetVertBoneWeightNotSlow( vert, k ); + + VertTransformWeighted(out->xyz[baseVert], bone, vec, &weight); + } + } + +#ifdef _XBOX + Q_CastShort2FloatScale(&out->texCoords[baseVert][0][0], &texCoord->texCoords[0], 1.f / GLM_COMP_UV_SIZE); + Q_CastShort2FloatScale(&out->texCoords[baseVert][0][1], &texCoord->texCoords[1], 1.f / GLM_COMP_UV_SIZE); +#else + out->texCoords[baseVert][0][0] = texCoord->texCoords[0]; + out->texCoords[baseVert][0][1] = texCoord->texCoords[1]; +#endif + + ++vert; + ++texCoord; + ++baseVert; + } + + // VVFIXME - BTO - commented this out, as it's still being done in SurfaceGhoul now. + // Really, I ought to move the Gore surfacing in here. + // out->numVertexes += surf->numVerts; +} + +static void TransformCollideSurface(const mdxmSurface_t *surf, CBoneCache *bones, vec3_t scale, float *out) +{ + const int *boneRefs = (int*) ((byte*)surf + surf->ofsBoneReferences); + int numVerts = surf->numVerts; + const mdxmVertex_t *vert = (mdxmVertex_t *) ((byte *)surf + surf->ofsVerts); + const mdxmVertexTexCoord_t *texCoord = (mdxmVertexTexCoord_t *) &vert[numVerts]; + + int boneIndex = -1; + const float *bone = NULL; + +#ifdef _XBOX + vec3_t scl; + scl[0] = scale[0] * 1.f / GLM_COMP_SIZE; + scl[1] = scale[1] * 1.f / GLM_COMP_SIZE; + scl[2] = scale[2] * 1.f / GLM_COMP_SIZE; +#endif + + while(numVerts--) + { + __declspec (align(16)) vec4_t vec; + +#ifdef _XBOX + Q_CastShort2FloatScale(&vec[0], &vert->vertCoords[0], scl[0]); + Q_CastShort2FloatScale(&vec[1], &vert->vertCoords[1], scl[1]); + Q_CastShort2FloatScale(&vec[2], &vert->vertCoords[2], scl[2]); +#else + VectorCopy(vert->vertCoords, vec); +#endif + + const int numWeights = G2_GetVertWeights( vert ); + + if (numWeights == 1) + { + // Slightly faster single weight path + int index = G2_GetVertBoneIndex( vert, 0 ); + + if ( index != boneIndex ) + { + CTransformBone *tbone = bones->EvalFull(boneRefs[index]); + bone = tbone->renderMatrix; + boneIndex = index; + } + + __declspec (align(16)) vec4_t temp; + + VertTransform(temp, bone, vec); + + out[0] = temp[0]; + out[1] = temp[1]; + out[2] = temp[2]; + } + else + { + // Multi-weight blending path + float totalWeight = 0.0f; + + __declspec (align(16)) vec4_t temp; + temp[0] = 0; + temp[1] = 0; + temp[2] = 0; + + for (int k = 0; k < numWeights; ++k) + { + boneIndex = G2_GetVertBoneIndex( vert, k ); + + CTransformBone *tbone = bones->EvalFull(boneRefs[boneIndex]); + bone = tbone->renderMatrix; + + __declspec (align(16)) float weight = + G2_GetVertBoneWeight( vert, k, totalWeight, numWeights ); + + VertTransformWeighted(temp, bone, vec, &weight); + } + + out[0] = temp[0]; + out[1] = temp[1]; + out[2] = temp[2]; + } + +#ifdef _XBOX + Q_CastShort2FloatScale(out + 3, &texCoord->texCoords[0], 1.f / GLM_COMP_UV_SIZE); + Q_CastShort2FloatScale(out + 4, &texCoord->texCoords[1], 1.f / GLM_COMP_UV_SIZE); +#else + out[3] = texCoord->texCoords[0]; + out[4] = texCoord->texCoords[1]; +#endif + + ++vert; + ++texCoord; + out += 5; + } +} + +void R_TransformEachSurface( const mdxmSurface_t *surface, vec3_t scale, CMiniHeap *G2VertSpace, int *TransformedVertsArray,CBoneCache *boneCache) +{ + float *TransformedVerts; + + // alloc some space for the transformed verts to get put in + TransformedVerts = (float *)G2VertSpace->MiniHeapAlloc(surface->numVerts * 5 * 4); + TransformedVertsArray[surface->thisSurfaceIndex] = (int)TransformedVerts; + if (!TransformedVerts) + { + assert(0); + Com_Error(ERR_DROP, "Ran out of transform space for Ghoul2 Models. Adjust MiniHeapSize in SV_SpawnServer.\n"); + } + + TransformCollideSurface(surface, boneCache, scale, TransformedVerts); +} + +#endif + + +//This is a slightly mangled version of the same function from the sof2sp base. +//It provides a pretty significant performance increase over the existing one. +void RB_SurfaceGhoul( CRenderableSurface *surf ) +{ +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceTimer_RB_SurfaceGhoul.Start(); +#endif + + static int j, k; + static int baseIndex, baseVertex; + static int numVerts; + static mdxmVertex_t *v; + static int *triangles; + static int indexes; + static glIndex_t *tessIndexes; + static mdxmVertexTexCoord_t *pTexCoords; + static int *piBoneReferences; + + if(surf->boneCache->mNumBones < 1 || surf->boneCache->mBones.size() < 1 || surf->boneCache->mNumBones > 100) + return; + +#ifdef _XBOX + if(glw_state->viewport.Y == 240) { // Can't use ActiveClientNum in render phase... + if( backEnd.currentEntity->e.skipForPlayer2 ) + return; + } +#endif + + // grab the pointer to the surface info within the loaded mesh file + mdxmSurface_t *surface = surf->surfaceData; + + CBoneCache *bones = surf->boneCache; + + // Set any dynamic lighting needed + if(backEnd.currentEntity->dlightBits) + tess.dlightBits = backEnd.currentEntity->dlightBits; + + // first up, sanity check our numbers + RB_CheckOverflow( surface->numVerts, surface->numTriangles ); + + // + // deform the vertexes by the lerped bones + // + + // first up, sanity check our numbers + baseVertex = tess.numVertexes; + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + baseIndex = tess.numIndexes; + + indexes = surface->numTriangles; //*3; //unrolled 3 times, don't multiply + tessIndexes = &tess.indexes[baseIndex]; + for (j = 0 ; j < indexes ; j++) { + *tessIndexes++ = baseVertex + *triangles++; + *tessIndexes++ = baseVertex + *triangles++; + *tessIndexes++ = baseVertex + *triangles++; + } + tess.numIndexes += indexes*3; + + numVerts = surface->numVerts; + + TransformRenderSurface(surface, surf->boneCache, &tess); + + tess.numVertexes += surface->numVerts; + + delete surf; + +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_RB_SurfaceGhoul += G2PerformanceTimer_RB_SurfaceGhoul.End(); +#endif +} +#endif // !DEDICATED + +/* +================= +R_LoadMDXM - load a Ghoul 2 Mesh file +================= +*/ + +/* + +Some information used in the creation of the JK2 - JKA bone remap table + +These are the old bones: +Complete list of all 72 bones: + +*/ + +int OldToNewRemapTable[72] = { +0,// Bone 0: "model_root": Parent: "" (index -1) +1,// Bone 1: "pelvis": Parent: "model_root" (index 0) +2,// Bone 2: "Motion": Parent: "pelvis" (index 1) +3,// Bone 3: "lfemurYZ": Parent: "pelvis" (index 1) +4,// Bone 4: "lfemurX": Parent: "pelvis" (index 1) +5,// Bone 5: "ltibia": Parent: "pelvis" (index 1) +6,// Bone 6: "ltalus": Parent: "pelvis" (index 1) +6,// Bone 7: "ltarsal": Parent: "pelvis" (index 1) +7,// Bone 8: "rfemurYZ": Parent: "pelvis" (index 1) +8,// Bone 9: "rfemurX": Parent: "pelvis" (index 1) +9,// Bone10: "rtibia": Parent: "pelvis" (index 1) +10,// Bone11: "rtalus": Parent: "pelvis" (index 1) +10,// Bone12: "rtarsal": Parent: "pelvis" (index 1) +11,// Bone13: "lower_lumbar": Parent: "pelvis" (index 1) +12,// Bone14: "upper_lumbar": Parent: "lower_lumbar" (index 13) +13,// Bone15: "thoracic": Parent: "upper_lumbar" (index 14) +14,// Bone16: "cervical": Parent: "thoracic" (index 15) +15,// Bone17: "cranium": Parent: "cervical" (index 16) +16,// Bone18: "ceyebrow": Parent: "face_always_" (index 71) +17,// Bone19: "jaw": Parent: "face_always_" (index 71) +18,// Bone20: "lblip2": Parent: "face_always_" (index 71) +19,// Bone21: "leye": Parent: "face_always_" (index 71) +20,// Bone22: "rblip2": Parent: "face_always_" (index 71) +21,// Bone23: "ltlip2": Parent: "face_always_" (index 71) +22,// Bone24: "rtlip2": Parent: "face_always_" (index 71) +23,// Bone25: "reye": Parent: "face_always_" (index 71) +24,// Bone26: "rclavical": Parent: "thoracic" (index 15) +25,// Bone27: "rhumerus": Parent: "thoracic" (index 15) +26,// Bone28: "rhumerusX": Parent: "thoracic" (index 15) +27,// Bone29: "rradius": Parent: "thoracic" (index 15) +28,// Bone30: "rradiusX": Parent: "thoracic" (index 15) +29,// Bone31: "rhand": Parent: "thoracic" (index 15) +29,// Bone32: "mc7": Parent: "thoracic" (index 15) +34,// Bone33: "r_d5_j1": Parent: "thoracic" (index 15) +35,// Bone34: "r_d5_j2": Parent: "thoracic" (index 15) +35,// Bone35: "r_d5_j3": Parent: "thoracic" (index 15) +30,// Bone36: "r_d1_j1": Parent: "thoracic" (index 15) +31,// Bone37: "r_d1_j2": Parent: "thoracic" (index 15) +31,// Bone38: "r_d1_j3": Parent: "thoracic" (index 15) +32,// Bone39: "r_d2_j1": Parent: "thoracic" (index 15) +33,// Bone40: "r_d2_j2": Parent: "thoracic" (index 15) +33,// Bone41: "r_d2_j3": Parent: "thoracic" (index 15) +32,// Bone42: "r_d3_j1": Parent: "thoracic" (index 15) +33,// Bone43: "r_d3_j2": Parent: "thoracic" (index 15) +33,// Bone44: "r_d3_j3": Parent: "thoracic" (index 15) +34,// Bone45: "r_d4_j1": Parent: "thoracic" (index 15) +35,// Bone46: "r_d4_j2": Parent: "thoracic" (index 15) +35,// Bone47: "r_d4_j3": Parent: "thoracic" (index 15) +36,// Bone48: "rhang_tag_bone": Parent: "thoracic" (index 15) +37,// Bone49: "lclavical": Parent: "thoracic" (index 15) +38,// Bone50: "lhumerus": Parent: "thoracic" (index 15) +39,// Bone51: "lhumerusX": Parent: "thoracic" (index 15) +40,// Bone52: "lradius": Parent: "thoracic" (index 15) +41,// Bone53: "lradiusX": Parent: "thoracic" (index 15) +42,// Bone54: "lhand": Parent: "thoracic" (index 15) +42,// Bone55: "mc5": Parent: "thoracic" (index 15) +43,// Bone56: "l_d5_j1": Parent: "thoracic" (index 15) +44,// Bone57: "l_d5_j2": Parent: "thoracic" (index 15) +44,// Bone58: "l_d5_j3": Parent: "thoracic" (index 15) +43,// Bone59: "l_d4_j1": Parent: "thoracic" (index 15) +44,// Bone60: "l_d4_j2": Parent: "thoracic" (index 15) +44,// Bone61: "l_d4_j3": Parent: "thoracic" (index 15) +45,// Bone62: "l_d3_j1": Parent: "thoracic" (index 15) +46,// Bone63: "l_d3_j2": Parent: "thoracic" (index 15) +46,// Bone64: "l_d3_j3": Parent: "thoracic" (index 15) +45,// Bone65: "l_d2_j1": Parent: "thoracic" (index 15) +46,// Bone66: "l_d2_j2": Parent: "thoracic" (index 15) +46,// Bone67: "l_d2_j3": Parent: "thoracic" (index 15) +47,// Bone68: "l_d1_j1": Parent: "thoracic" (index 15) +48,// Bone69: "l_d1_j2": Parent: "thoracic" (index 15) +48,// Bone70: "l_d1_j3": Parent: "thoracic" (index 15) +52// Bone71: "face_always_": Parent: "cranium" (index 17) +}; + + +/* + +Bone 0: "model_root": + Parent: "" (index -1) + #Kids: 1 + Child 0: (index 1), name "pelvis" + +Bone 1: "pelvis": + Parent: "model_root" (index 0) + #Kids: 4 + Child 0: (index 2), name "Motion" + Child 1: (index 3), name "lfemurYZ" + Child 2: (index 7), name "rfemurYZ" + Child 3: (index 11), name "lower_lumbar" + +Bone 2: "Motion": + Parent: "pelvis" (index 1) + #Kids: 0 + +Bone 3: "lfemurYZ": + Parent: "pelvis" (index 1) + #Kids: 3 + Child 0: (index 4), name "lfemurX" + Child 1: (index 5), name "ltibia" + Child 2: (index 49), name "ltail" + +Bone 4: "lfemurX": + Parent: "lfemurYZ" (index 3) + #Kids: 0 + +Bone 5: "ltibia": + Parent: "lfemurYZ" (index 3) + #Kids: 1 + Child 0: (index 6), name "ltalus" + +Bone 6: "ltalus": + Parent: "ltibia" (index 5) + #Kids: 0 + +Bone 7: "rfemurYZ": + Parent: "pelvis" (index 1) + #Kids: 3 + Child 0: (index 8), name "rfemurX" + Child 1: (index 9), name "rtibia" + Child 2: (index 50), name "rtail" + +Bone 8: "rfemurX": + Parent: "rfemurYZ" (index 7) + #Kids: 0 + +Bone 9: "rtibia": + Parent: "rfemurYZ" (index 7) + #Kids: 1 + Child 0: (index 10), name "rtalus" + +Bone 10: "rtalus": + Parent: "rtibia" (index 9) + #Kids: 0 + +Bone 11: "lower_lumbar": + Parent: "pelvis" (index 1) + #Kids: 1 + Child 0: (index 12), name "upper_lumbar" + +Bone 12: "upper_lumbar": + Parent: "lower_lumbar" (index 11) + #Kids: 1 + Child 0: (index 13), name "thoracic" + +Bone 13: "thoracic": + Parent: "upper_lumbar" (index 12) + #Kids: 5 + Child 0: (index 14), name "cervical" + Child 1: (index 24), name "rclavical" + Child 2: (index 25), name "rhumerus" + Child 3: (index 37), name "lclavical" + Child 4: (index 38), name "lhumerus" + +Bone 14: "cervical": + Parent: "thoracic" (index 13) + #Kids: 1 + Child 0: (index 15), name "cranium" + +Bone 15: "cranium": + Parent: "cervical" (index 14) + #Kids: 1 + Child 0: (index 52), name "face_always_" + +Bone 16: "ceyebrow": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 17: "jaw": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 18: "lblip2": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 19: "leye": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 20: "rblip2": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 21: "ltlip2": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 22: "rtlip2": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 23: "reye": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 24: "rclavical": + Parent: "thoracic" (index 13) + #Kids: 0 + +Bone 25: "rhumerus": + Parent: "thoracic" (index 13) + #Kids: 2 + Child 0: (index 26), name "rhumerusX" + Child 1: (index 27), name "rradius" + +Bone 26: "rhumerusX": + Parent: "rhumerus" (index 25) + #Kids: 0 + +Bone 27: "rradius": + Parent: "rhumerus" (index 25) + #Kids: 9 + Child 0: (index 28), name "rradiusX" + Child 1: (index 29), name "rhand" + Child 2: (index 30), name "r_d1_j1" + Child 3: (index 31), name "r_d1_j2" + Child 4: (index 32), name "r_d2_j1" + Child 5: (index 33), name "r_d2_j2" + Child 6: (index 34), name "r_d4_j1" + Child 7: (index 35), name "r_d4_j2" + Child 8: (index 36), name "rhang_tag_bone" + +Bone 28: "rradiusX": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 29: "rhand": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 30: "r_d1_j1": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 31: "r_d1_j2": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 32: "r_d2_j1": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 33: "r_d2_j2": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 34: "r_d4_j1": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 35: "r_d4_j2": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 36: "rhang_tag_bone": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 37: "lclavical": + Parent: "thoracic" (index 13) + #Kids: 0 + +Bone 38: "lhumerus": + Parent: "thoracic" (index 13) + #Kids: 2 + Child 0: (index 39), name "lhumerusX" + Child 1: (index 40), name "lradius" + +Bone 39: "lhumerusX": + Parent: "lhumerus" (index 38) + #Kids: 0 + +Bone 40: "lradius": + Parent: "lhumerus" (index 38) + #Kids: 9 + Child 0: (index 41), name "lradiusX" + Child 1: (index 42), name "lhand" + Child 2: (index 43), name "l_d4_j1" + Child 3: (index 44), name "l_d4_j2" + Child 4: (index 45), name "l_d2_j1" + Child 5: (index 46), name "l_d2_j2" + Child 6: (index 47), name "l_d1_j1" + Child 7: (index 48), name "l_d1_j2" + Child 8: (index 51), name "lhang_tag_bone" + +Bone 41: "lradiusX": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 42: "lhand": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 43: "l_d4_j1": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 44: "l_d4_j2": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 45: "l_d2_j1": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 46: "l_d2_j2": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 47: "l_d1_j1": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 48: "l_d1_j2": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 49: "ltail": + Parent: "lfemurYZ" (index 3) + #Kids: 0 + +Bone 50: "rtail": + Parent: "rfemurYZ" (index 7) + #Kids: 0 + +Bone 51: "lhang_tag_bone": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 52: "face_always_": + Parent: "cranium" (index 15) + #Kids: 8 + Child 0: (index 16), name "ceyebrow" + Child 1: (index 17), name "jaw" + Child 2: (index 18), name "lblip2" + Child 3: (index 19), name "leye" + Child 4: (index 20), name "rblip2" + Child 5: (index 21), name "ltlip2" + Child 6: (index 22), name "rtlip2" + Child 7: (index 23), name "reye" + + + +*/ + + +qboolean R_LoadMDXM( model_t *mod, void *buffer, const char *mod_name, qboolean &bAlreadyCached ) { + int i,l, j; + mdxmHeader_t *pinmodel, *mdxm; + mdxmLOD_t *lod; + mdxmSurface_t *surf; + int version; + int size; + mdxmSurfHierarchy_t *surfInfo; + +#ifndef _M_IX86 + int k; + int frameSize; + mdxmTag_t *tag; + mdxmTriangle_t *tri; + mdxmVertex_t *v; + mdxmFrame_t *cframe; + int *boneRef; +#endif + + pinmodel= (mdxmHeader_t *)buffer; + // + // read some fields from the binary, but only LittleLong() them when we know this wasn't an already-cached model... + // + version = (pinmodel->version); + size = (pinmodel->ofsEnd); + + if (!bAlreadyCached) + { + version = LittleLong(version); + size = LittleLong(size); + } + + if (version != MDXM_VERSION) { + Com_Printf (S_COLOR_YELLOW "R_LoadMDXM: %s has wrong version (%i should be %i)\n", + mod_name, version, MDXM_VERSION); + return qfalse; + } + + mod->type = MOD_MDXM; + mod->dataSize += size; + + qboolean bAlreadyFound = qfalse; +#ifdef _XBOX + bool useModelMem = strstr(pinmodel->animName, "_humanoid"); + mdxm = mod->mdxm = (mdxmHeader_t*) //Hunk_Alloc( size ); + RE_RegisterModels_Malloc(size, buffer, mod_name, &bAlreadyFound, TAG_MODEL_GLM, mod->index, useModelMem); +#else + mdxm = mod->mdxm = (mdxmHeader_t*) //Hunk_Alloc( size ); + RE_RegisterModels_Malloc(size, buffer, mod_name, &bAlreadyFound, TAG_MODEL_GLM); +#endif + + assert(bAlreadyCached == bAlreadyFound); + + if (!bAlreadyFound) + { + // horrible new hackery, if !bAlreadyFound then we've just done a tag-morph, so we need to set the + // bool reference passed into this function to true, to tell the caller NOT to do an FS_Freefile since + // we've hijacked that memory block... + // + // Aaaargh. Kill me now... + // +#ifdef _XBOX // Can't re-tag allocated memory! + memcpy( mdxm, buffer, size ); // and don't do this now, since it's the same thing +#else + bAlreadyCached = qtrue; + assert( mdxm == buffer ); +#endif + + LL(mdxm->ident); + LL(mdxm->version); + LL(mdxm->numLODs); + LL(mdxm->ofsLODs); + LL(mdxm->numSurfaces); + LL(mdxm->ofsSurfHierarchy); + LL(mdxm->ofsEnd); + } + + // first up, go load in the animation file we need that has the skeletal animation info for this model + mdxm->animIndex = RE_RegisterModel(va ("%s.gla",mdxm->animName)); + + if (!mdxm->animIndex) + { + Com_Printf (S_COLOR_YELLOW "R_LoadMDXM: missing animation file %s for mesh %s\n", mdxm->animName, mdxm->name); + return qfalse; + } + + mod->numLods = mdxm->numLODs -1 ; //copy this up to the model for ease of use - it wil get inced after this. + + if (bAlreadyFound) + { + return qtrue; // All done. Stop, go no further, do not LittleLong(), do not pass Go... + } + + bool isAnOldModelFile = false; + if (mdxm->numBones == 72 && strstr(mdxm->animName,"_humanoid") ) + { + isAnOldModelFile = true; + } + + surfInfo = (mdxmSurfHierarchy_t *)( (byte *)mdxm + mdxm->ofsSurfHierarchy); + for ( i = 0 ; i < mdxm->numSurfaces ; i++) + { + LL(surfInfo->numChildren); + LL(surfInfo->parentIndex); + + Q_strlwr(surfInfo->name); //just in case + if ( !strcmp( &surfInfo->name[strlen(surfInfo->name)-4],"_off") ) + { + surfInfo->name[strlen(surfInfo->name)-4]=0; //remove "_off" from name + } + + // do all the children indexs + for (j=0; jnumChildren; j++) + { + LL(surfInfo->childIndexes[j]); + } +#ifdef DEDICATED + surfInfo->shaderIndex = 0; +#else + shader_t *sh; + // get the shader name + sh = R_FindShader( surfInfo->shader, lightmapsNone, stylesDefault, qtrue ); + // insert it in the surface list + if ( sh->defaultShader ) + { + surfInfo->shaderIndex = 0; + } + else + { + surfInfo->shaderIndex = sh->index; + } +#endif + + RE_RegisterModels_StoreShaderRequest(mod_name, &surfInfo->shader[0], &surfInfo->shaderIndex); + + // find the next surface + surfInfo = (mdxmSurfHierarchy_t *)( (byte *)surfInfo + (int)( &((mdxmSurfHierarchy_t *)0)->childIndexes[ surfInfo->numChildren ] )); + } + + // swap all the LOD's (we need to do the middle part of this even for intel, because of shader reg and err-check) + lod = (mdxmLOD_t *) ( (byte *)mdxm + mdxm->ofsLODs ); + for ( l = 0 ; l < mdxm->numLODs ; l++) + { + int triCount = 0; + + LL(lod->ofsEnd); + // swap all the surfaces + surf = (mdxmSurface_t *) ( (byte *)lod + sizeof (mdxmLOD_t) + (mdxm->numSurfaces * sizeof(mdxmLODSurfOffset_t)) ); + for ( i = 0 ; i < mdxm->numSurfaces ; i++) + { + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsVerts); + LL(surf->ofsEnd); + LL(surf->ofsHeader); + LL(surf->numBoneReferences); + LL(surf->ofsBoneReferences); +// LL(surf->maxVertBoneWeights); + + triCount += surf->numTriangles; + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + Com_Error (ERR_DROP, "R_LoadMDXM: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + Com_Error (ERR_DROP, "R_LoadMDXM: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = SF_MDX; + // register the shaders +#ifndef _M_IX86 +// +// optimisation, we don't bother doing this for standard intel case since our data's already in that format... +// + // FIXME - is this correct? + // do all the bone reference data + boneRef = (int *) ( (byte *)surf + surf->ofsBoneReferences ); + for ( j = 0 ; j < surf->numBoneReferences ; j++ ) + { + LL(boneRef[j]); + } + + // swap all the triangles + tri = (mdxmTriangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) + { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the vertexes + v = (mdxmVertex_t *) ( (byte *)surf + surf->ofsVerts ); + for ( j = 0 ; j < surf->numVerts ; j++ ) + { + v->normal[0] = LittleFloat( v->normal[0] ); + v->normal[1] = LittleFloat( v->normal[1] ); + v->normal[2] = LittleFloat( v->normal[2] ); + + v->texCoords[0] = LittleFloat( v->texCoords[0] ); + v->texCoords[1] = LittleFloat( v->texCoords[1] ); + + v->numWeights = LittleLong( v->numWeights ); + v->offset[0] = LittleFloat( v->offset[0] ); + v->offset[1] = LittleFloat( v->offset[1] ); + v->offset[2] = LittleFloat( v->offset[2] ); + + for ( k = 0 ; k < /*v->numWeights*/surf->maxVertBoneWeights ; k++ ) + { + v->weights[k].boneIndex = LittleLong( v->weights[k].boneIndex ); + v->weights[k].boneWeight = LittleFloat( v->weights[k].boneWeight ); + } + v = (mdxmVertex_t *)&v->weights[/*v->numWeights*/surf->maxVertBoneWeights]; + } +#endif + + if (isAnOldModelFile) + { + int *boneRef = (int *) ( (byte *)surf + surf->ofsBoneReferences ); + for ( j = 0 ; j < surf->numBoneReferences ; j++ ) + { + assert(boneRef[j] >= 0 && boneRef[j] < 72); + if (boneRef[j] >= 0 && boneRef[j] < 72) + { + boneRef[j]=OldToNewRemapTable[boneRef[j]]; + } + else + { + boneRef[j]=0; + } + } + } + // find the next surface + surf = (mdxmSurface_t *)( (byte *)surf + surf->ofsEnd ); + } + // find the next LOD + lod = (mdxmLOD_t *)( (byte *)lod + lod->ofsEnd ); + } + return qtrue; +} + +//#define CREATE_LIMB_HIERARCHY + +#ifdef CREATE_LIMB_HIERARCHY + +#define NUM_ROOTPARENTS 4 +#define NUM_OTHERPARENTS 12 +#define NUM_BOTTOMBONES 4 + +#define CHILD_PADDING 4 //I don't know, I guess this can be changed. + +static const char *rootParents[NUM_ROOTPARENTS] = +{ + "rfemurYZ", + "rhumerus", + "lfemurYZ", + "lhumerus" +}; + +static const char *otherParents[NUM_OTHERPARENTS] = +{ + "rhumerusX", + "rradius", + "rradiusX", + "lhumerusX", + "lradius", + "lradiusX", + "rfemurX", + "rtibia", + "rtalus", + "lfemurX", + "ltibia", + "ltalus" +}; + +static const char *bottomBones[NUM_BOTTOMBONES] = +{ + "rtarsal", + "rhand", + "ltarsal", + "lhand" +}; + +qboolean BoneIsRootParent(char *name) +{ + int i = 0; + + while (i < NUM_ROOTPARENTS) + { + if (!Q_stricmp(name, rootParents[i])) + { + return qtrue; + } + + i++; + } + + return qfalse; +} + +qboolean BoneIsOtherParent(char *name) +{ + int i = 0; + + while (i < NUM_OTHERPARENTS) + { + if (!Q_stricmp(name, otherParents[i])) + { + return qtrue; + } + + i++; + } + + return qfalse; +} + +qboolean BoneIsBottom(char *name) +{ + int i = 0; + + while (i < NUM_BOTTOMBONES) + { + if (!Q_stricmp(name, bottomBones[i])) + { + return qtrue; + } + + i++; + } + + return qfalse; +} + +void ShiftMemoryDown(mdxaSkelOffsets_t *offsets, mdxaHeader_t *mdxa, int boneIndex, byte **endMarker) +{ + int i = 0; + + //where the next bone starts + byte *nextBone = ((byte *)mdxa + sizeof(mdxaHeader_t) + offsets->offsets[boneIndex+1]); + int size = (*endMarker - nextBone); + + memmove((nextBone+CHILD_PADDING), nextBone, size); + memset(nextBone, 0, CHILD_PADDING); + *endMarker += CHILD_PADDING; + //Move the whole thing down CHILD_PADDING amount in memory, clear the new preceding space, and increment the end pointer. + + i = boneIndex+1; + + //Now add CHILD_PADDING amount to every offset beginning at the offset of the bone that was moved. + while (i < mdxa->numBones) + { + offsets->offsets[i] += CHILD_PADDING; + i++; + } + + mdxa->ofsFrames += CHILD_PADDING; + mdxa->ofsCompBonePool += CHILD_PADDING; + mdxa->ofsEnd += CHILD_PADDING; + //ofsSkel does not need to be updated because we are only moving memory after that point. +} + +//Proper/desired hierarchy list +static const char *BoneHierarchyList[] = +{ + "lfemurYZ", + "lfemurX", + "ltibia", + "ltalus", + "ltarsal", + + "rfemurYZ", + "rfemurX", + "rtibia", + "rtalus", + "rtarsal", + + "lhumerus", + "lhumerusX", + "lradius", + "lradiusX", + "lhand", + + "rhumerus", + "rhumerusX", + "rradius", + "rradiusX", + "rhand", + + 0 +}; + +//Gets the index of a child or parent. If child is passed as qfalse then parent is assumed. +int BoneParentChildIndex(mdxaHeader_t *mdxa, mdxaSkelOffsets_t *offsets, mdxaSkel_t *boneInfo, qboolean child) +{ + int i = 0; + int matchindex = -1; + mdxaSkel_t *bone; + const char *match = NULL; + + while (BoneHierarchyList[i]) + { + if (!Q_stricmp(boneInfo->name, BoneHierarchyList[i])) + { //we have a match, the slot above this will be our desired parent. (or below for child) + if (child) + { + match = BoneHierarchyList[i+1]; + } + else + { + match = BoneHierarchyList[i-1]; + } + break; + } + i++; + } + + if (!match) + { //no good + return -1; + } + + i = 0; + + while (i < mdxa->numBones) + { + bone = (mdxaSkel_t *)((byte *)mdxa + sizeof(mdxaHeader_t) + offsets->offsets[i]); + + if (bone && !Q_stricmp(bone->name, match)) + { //this is the one + matchindex = i; + break; + } + + i++; + } + + return matchindex; +} +#endif //CREATE_LIMB_HIERARCHY + +/* +================= +R_LoadMDXA - load a Ghoul 2 animation file +================= +*/ +qboolean R_LoadMDXA( model_t *mod, void *buffer, const char *mod_name, qboolean &bAlreadyCached ) { + + mdxaHeader_t *pinmodel, *mdxa; + int version; + int size; +#ifdef CREATE_LIMB_HIERARCHY + int oSize = 0; + byte *sizeMarker; +#endif + +#ifndef _M_IX86 + int j, k, i; + int frameSize; + mdxaFrame_t *cframe; + mdxaSkel_t *boneInfo; +#endif + + pinmodel = (mdxaHeader_t *)buffer; + // + // read some fields from the binary, but only LittleLong() them when we know this wasn't an already-cached model... + // + version = (pinmodel->version); + size = (pinmodel->ofsEnd); + + if (!bAlreadyCached) + { + version = LittleLong(version); + size = LittleLong(size); + } + + if (version != MDXA_VERSION) { + Com_Printf (S_COLOR_YELLOW "R_LoadMDXA: %s has wrong version (%i should be %i)\n", + mod_name, version, MDXA_VERSION); + return qfalse; + } + + // VV Hackx0ring! Humanoid and cinematic animations have some "fixups" done to them. Bwa ha ha! + if( pinmodel->ofsCompBonePool > 0 && (strstr(pinmodel->name, "_humanoid") || strstr(pinmodel->name, "rancor")) ) + { + TheBonePool.Register( pinmodel ); + + // This number just changed: + size = pinmodel->ofsEnd; + } + + mod->type = MOD_MDXA; + mod->dataSize += size; + + qboolean bAlreadyFound = qfalse; + +#ifdef CREATE_LIMB_HIERARCHY + oSize = size; + + int childNumber = (NUM_ROOTPARENTS + NUM_OTHERPARENTS); + size += (childNumber*(CHILD_PADDING*8)); //Allocate us some extra space so we can shift memory down. +#endif //CREATE_LIMB_HIERARCHY + +#ifdef _XBOX + mdxa = mod->mdxa = (mdxaHeader_t*) //Hunk_Alloc( size ); + RE_RegisterModels_Malloc(size, + #ifdef CREATE_LIMB_HIERARCHY + NULL, // I think this'll work, can't really test on PC + #else + buffer, + #endif + mod_name, &bAlreadyFound, TAG_MODEL_GLA, 0, false); +#else + mdxa = mod->mdxa = (mdxaHeader_t*) //Hunk_Alloc( size ); + RE_RegisterModels_Malloc(size, + #ifdef CREATE_LIMB_HIERARCHY + NULL, // I think this'll work, can't really test on PC + #else + buffer, + #endif + mod_name, &bAlreadyFound, TAG_MODEL_GLA); +#endif + + assert(bAlreadyCached == bAlreadyFound); // I should probably eliminate 'bAlreadyFound', but wtf? + + if (!bAlreadyFound) + { +#ifdef CREATE_LIMB_HIERARCHY + memcpy( mdxa, buffer, oSize ); +#else + // horrible new hackery, if !bAlreadyFound then we've just done a tag-morph, so we need to set the + // bool reference passed into this function to true, to tell the caller NOT to do an FS_Freefile since + // we've hijacked that memory block... + // + // Aaaargh. Kill me now... + // +#ifdef _XBOX // Can't re-tag allocated memory! + memcpy( mdxa, buffer, size ); // and don't do this now, since it's the same thing +#else + bAlreadyCached = qtrue; + assert( mdxa == buffer ); +#endif +#endif + LL(mdxa->ident); + LL(mdxa->version); + LL(mdxa->numFrames); + LL(mdxa->numBones); + LL(mdxa->ofsFrames); + LL(mdxa->ofsEnd); + } + +#ifdef CREATE_LIMB_HIERARCHY + if (!bAlreadyFound) + { + mdxaSkel_t *boneParent; +#ifdef _M_IX86 + mdxaSkel_t *boneInfo; + int i, k; +#endif + + sizeMarker = (byte *)mdxa + mdxa->ofsEnd; + + //rww - This is probably temporary until we put actual hierarchy in for the models. + //It is necessary for the correct operation of ragdoll. + mdxaSkelOffsets_t *offsets = (mdxaSkelOffsets_t *)((byte *)mdxa + sizeof(mdxaHeader_t)); + + for ( i = 0 ; i < mdxa->numBones ; i++) + { + boneInfo = (mdxaSkel_t *)((byte *)mdxa + sizeof(mdxaHeader_t) + offsets->offsets[i]); + + if (boneInfo) + { + char *bname = boneInfo->name; + + if (BoneIsRootParent(bname)) + { //These are the main parent bones. We don't want to change their parents, but we want to give them children. + ShiftMemoryDown(offsets, mdxa, i, &sizeMarker); + + boneInfo = (mdxaSkel_t *)((byte *)mdxa + sizeof(mdxaHeader_t) + offsets->offsets[i]); + + int newChild = BoneParentChildIndex(mdxa, offsets, boneInfo, qtrue); + + if (newChild != -1) + { + boneInfo->numChildren++; + boneInfo->children[boneInfo->numChildren-1] = newChild; + } + else + { + assert(!"Failed to find matching child for bone in hierarchy creation"); + } + } + else if (BoneIsOtherParent(bname) || BoneIsBottom(bname)) + { + if (!BoneIsBottom(bname)) + { //unless it's last in the chain it has the next bone as a child. + ShiftMemoryDown(offsets, mdxa, i, &sizeMarker); + + boneInfo = (mdxaSkel_t *)((byte *)mdxa + sizeof(mdxaHeader_t) + offsets->offsets[i]); + + int newChild = BoneParentChildIndex(mdxa, offsets, boneInfo, qtrue); + + if (newChild != -1) + { + boneInfo->numChildren++; + boneInfo->children[boneInfo->numChildren-1] = newChild; + } + else + { + assert(!"Failed to find matching child for bone in hierarchy creation"); + } + } + + //Before we set the parent we want to remove this as a child for whoever was parenting it. + int oldParent = boneInfo->parent; + + if (oldParent > -1) + { + boneParent = (mdxaSkel_t *)((byte *)mdxa + sizeof(mdxaHeader_t) + offsets->offsets[oldParent]); + } + else + { + boneParent = NULL; + } + + if (boneParent) + { + k = 0; + + while (k < boneParent->numChildren) + { + if (boneParent->children[k] == i) + { //this bone is the child + k++; + while (k < boneParent->numChildren) + { + boneParent->children[k-1] = boneParent->children[k]; + k++; + } + boneParent->children[k-1] = 0; + boneParent->numChildren--; + break; + } + k++; + } + } + + //Now that we have cleared the original parent of ownership, mark the bone's new parent. + int newParent = BoneParentChildIndex(mdxa, offsets, boneInfo, qfalse); + + if (newParent != -1) + { + boneInfo->parent = newParent; + } + else + { + assert(!"Failed to find matching parent for bone in hierarchy creation"); + } + } + } + } + } +#endif //CREATE_LIMB_HIERARCHY + + if ( mdxa->numFrames < 1 ) { + Com_Printf (S_COLOR_YELLOW "R_LoadMDXA: %s has no frames\n", mod_name ); + return qfalse; + } + + if (bAlreadyFound) + { + return qtrue; // All done, stop here, do not LittleLong() etc. Do not pass go... + } + +#ifndef _M_IX86 + + // + // optimisation, we don't bother doing this for standard intel case since our data's already in that format... + // + + // swap all the skeletal info + boneInfo = (mdxaSkel_t *)( (byte *)mdxa + mdxa->ofsSkel); + for ( i = 0 ; i < mdxa->numBones ; i++) + { + LL(boneInfo->numChildren); + LL(boneInfo->parent); + for (k=0; knumChildren; k++) + { + LL(boneInfo->children[k]); + } + + // get next bone + boneInfo += (int)( &((mdxaSkel_t *)0)->children[ boneInfo->numChildren ] ); + } + + + // swap all the frames + frameSize = (int)( &((mdxaFrame_t *)0)->bones[ mdxa->numBones ] ); + for ( i = 0 ; i < mdxa->numFrames ; i++) + { + cframe = (mdxaFrame_t *) ( (byte *)mdxa + mdxa->ofsFrames + i * frameSize ); + cframe->radius = LittleFloat( cframe->radius ); + for ( j = 0 ; j < 3 ; j++ ) + { + cframe->bounds[0][j] = LittleFloat( cframe->bounds[0][j] ); + cframe->bounds[1][j] = LittleFloat( cframe->bounds[1][j] ); + cframe->localOrigin[j] = LittleFloat( cframe->localOrigin[j] ); + } + for ( j = 0 ; j < mdxa->numBones * sizeof( mdxaBone_t ) / 2 ; j++ ) + { + ((short *)cframe->bones)[j] = LittleShort( ((short *)cframe->bones)[j] ); + } + } +#endif + return qtrue; +} + + + + + + + diff --git a/codemp/renderer/tr_image.cpp b/codemp/renderer/tr_image.cpp new file mode 100644 index 0000000..31db6b8 --- /dev/null +++ b/codemp/renderer/tr_image.cpp @@ -0,0 +1,3365 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_image.c +#include "tr_local.h" +#ifndef DEDICATED +#include "glext.h" +#endif + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include +#pragma warning (pop) +using namespace std; + + +/* + * Include file for users of JPEG library. + * You will need to have included system headers that define at least + * the typedefs FILE and size_t before you can include jpeglib.h. + * (stdio.h is sufficient on ANSI-conforming systems.) + * You may also wish to include "jerror.h". + */ + + +#define JPEG_INTERNALS +#include "../jpeg-6/jpeglib.h" +#include "../png/png.h" + +#ifndef DEDICATED + +static void LoadTGA( const char *name, byte **pic, int *width, int *height ); +static void LoadJPG( const char *name, byte **pic, int *width, int *height ); + + +static byte s_intensitytable[256]; +static unsigned char s_gammatable[256]; + +int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST; +int gl_filter_max = GL_LINEAR; + +//#define FILE_HASH_SIZE 1024 // actually the shader code still needs this (from another module, great), +//static image_t* hashTable[FILE_HASH_SIZE]; + +/* +** R_GammaCorrect +*/ +void R_GammaCorrect( byte *buffer, int bufSize ) { + int i; + + for ( i = 0; i < bufSize; i++ ) { + buffer[i] = s_gammatable[buffer[i]]; + } +} + +typedef struct { + char *name; + int minimize, maximize; +} textureMode_t; + +textureMode_t modes[] = { + {"GL_NEAREST", GL_NEAREST, GL_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} +}; + + +// makeup a nice clean, consistant name to query for and file under, for map<> usage... +// +static char *GenerateImageMappingName( const char *name ) +{ + static char sName[MAX_QPATH]; + int i=0; + char letter; + + while (name[i] != '\0' && ivalue > glConfig.maxTextureFilterAnisotropy ) + { + Cvar_Set( "r_ext_texture_filter_anisotropic", va("%f",glConfig.maxTextureFilterAnisotropy) ); + } + // change all the existing mipmap texture objects + R_Images_StartIteration(); + while ( (glt = R_Images_GetNextIteration()) != NULL) + { + if ( glt->mipmap ) { + GL_Bind (glt); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + + if(glConfig.maxTextureFilterAnisotropy>0) { + if(r_ext_texture_filter_anisotropic->integer>1) { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_ext_texture_filter_anisotropic->value); + } else { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + } + } + } + } +} + +static float R_BytesPerTex (int format) +{ + switch ( format ) { + case 1: + //"I " + return 1; + break; + case 2: + //"IA " + return 2; + break; + case 3: + //"RGB " + return glConfig.colorBits/8.0f; + break; + case 4: + //"RGBA " + return glConfig.colorBits/8.0f; + break; + + case GL_RGBA4: + //"RGBA4" + return 2; + break; + case GL_RGB5: + //"RGB5 " + return 2; + break; + + case GL_RGBA8: + //"RGBA8" + return 4; + break; + case GL_RGB8: + //"RGB8" + return 4; + break; + + case GL_RGB4_S3TC: + //"S3TC " + return 0.33333f; + break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + //"DXT1 " + return 0.33333f; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + //"DXT5 " + return 1; + break; + default: + //"???? " + return 4; + } +} + +/* +=============== +R_SumOfUsedImages +=============== +*/ +float R_SumOfUsedImages( qboolean bUseFormat ) +{ + int total = 0; + image_t *pImage; + + R_Images_StartIteration(); + while ( (pImage = R_Images_GetNextIteration()) != NULL) + { + if ( pImage->frameUsed == tr.frameCount- 1 ) {//it has already been advanced for the next frame, so... + if (bUseFormat) + { + float bytePerTex = R_BytesPerTex (pImage->internalFormat); + total += bytePerTex * (pImage->width * pImage->height); + } + else + { + total += pImage->width * pImage->height; + } + } + } + + return total; +} + +/* +=============== +R_ImageList_f +=============== +*/ +void R_ImageList_f( void ) { + int i=0; + image_t *image; + int texels=0; + float texBytes = 0.0f; + const char *yesno[] = {"no ", "yes"}; + + Com_Printf ( "\n -w-- -h-- -mm- -if-- wrap --name-------\n"); + + int iNumImages = R_Images_StartIteration(); + while ( (image = R_Images_GetNextIteration()) != NULL) + { + texels += image->width*image->height; + texBytes += image->width*image->height * R_BytesPerTex (image->internalFormat); + Com_Printf ( "%4i: %4i %4i %s ", + i, image->width, image->height, yesno[image->mipmap] ); + switch ( image->internalFormat ) { + case 1: + Com_Printf ("I " ); + break; + case 2: + Com_Printf ("IA " ); + break; + case 3: + Com_Printf ("RGB " ); + break; + case 4: + Com_Printf ("RGBA " ); + break; + case GL_RGBA8: + Com_Printf ("RGBA8" ); + break; + case GL_RGB8: + Com_Printf ("RGB8" ); + break; + case GL_RGB4_S3TC: + Com_Printf ("S3TC " ); + break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + Com_Printf ("DXT1 " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + Com_Printf ("DXT5 " ); + break; + case GL_RGBA4: + Com_Printf ("RGBA4" ); + break; + case GL_RGB5: + Com_Printf ("RGB5 " ); + break; + default: + Com_Printf ("???? " ); + } + + switch ( image->wrapClampMode ) { + case GL_REPEAT: + Com_Printf ("rept " ); + break; + case GL_CLAMP: + Com_Printf ("clmp " ); + break; + case GL_CLAMP_TO_EDGE: + Com_Printf ("clpE " ); + break; + default: + Com_Printf ("%4i ", image->wrapClampMode ); + break; + } + + Com_Printf ("%s\n", image->imgName ); + i++; + } + Com_Printf ( " ---------\n"); + Com_Printf ( " -w-- -h-- -mm- -if- wrap --name-------\n"); + Com_Printf ( " %i total texels (not including mipmaps)\n", texels ); + Com_Printf ( " %.2fMB total texture mem (not including mipmaps)\n", texBytes/1048576.0f ); + Com_Printf ( " %i total images\n\n", iNumImages ); +} + +//======================================================================= + + +/* +================ +R_LightScaleTexture + +Scale up the pixel values in a texture to increase the +lighting range +================ +*/ +void R_LightScaleTexture (unsigned *in, int inwidth, int inheight, qboolean only_gamma ) +{ + if ( only_gamma ) + { + if ( !glConfig.deviceSupportsGamma ) + { + int i, c; + byte *p; + + p = (byte *)in; + + c = inwidth*inheight; + for (i=0 ; i> 1; + outHeight = inHeight >> 1; + temp = (unsigned int *)Hunk_AllocateTempMemory( outWidth * outHeight * 4 ); + + inWidthMask = inWidth - 1; + inHeightMask = inHeight - 1; + + for ( i = 0 ; i < outHeight ; i++ ) { + for ( j = 0 ; j < outWidth ; j++ ) { + outpix = (byte *) ( temp + i * outWidth + j ); + for ( k = 0 ; k < 4 ; k++ ) { + total = + 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k]; + outpix[k] = total / 36; + } + } + } + + Com_Memcpy( in, temp, outWidth * outHeight * 4 ); + Hunk_FreeTempMemory( temp ); +} + +/* +================ +R_MipMap + +Operates in place, quartering the size of the texture +================ +*/ +static void R_MipMap (byte *in, int width, int height) { + int i, j; + byte *out; + int row; + + if ( !r_simpleMipMaps->integer ) { + R_MipMap2( (unsigned *)in, width, height ); + return; + } + + if ( width == 1 && height == 1 ) { + return; + } + + row = width * 4; + out = in; + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + for (i=0 ; i>1; + out[1] = ( in[1] + in[5] )>>1; + out[2] = ( in[2] + in[6] )>>1; + out[3] = ( in[3] + in[7] )>>1; + } + return; + } + + for (i=0 ; i>2; + out[1] = (in[1] + in[5] + in[row+1] + in[row+5])>>2; + out[2] = (in[2] + in[6] + in[row+2] + in[row+6])>>2; + out[3] = (in[3] + in[7] + in[row+3] + in[row+7])>>2; + } + } +} + + +/* +================== +R_BlendOverTexture + +Apply a color blend over a set of pixels +================== +*/ +static void R_BlendOverTexture( byte *data, int pixelCount, byte blend[4] ) { + int i; + int inverseAlpha; + int premult[3]; + + inverseAlpha = 255 - blend[3]; + premult[0] = blend[0] * blend[3]; + premult[1] = blend[1] * blend[3]; + premult[2] = blend[2] * blend[3]; + + for ( i = 0 ; i < pixelCount ; i++, data+=4 ) { + data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9; + data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9; + data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9; + } +} + +byte mipBlendColors[16][4] = { + {0,0,0,0}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, +}; + + + + + +class CStringComparator +{ +public: + bool operator()(const char *s1, const char *s2) const { return(strcmp(s1, s2) < 0); } +}; + +typedef map AllocatedImages_t; + AllocatedImages_t AllocatedImages; + AllocatedImages_t::iterator itAllocatedImages; +int giTextureBindNum = 1024; // will be set to this anyway at runtime, but wtf? + + +// return = number of images in the list, for those interested +// +int R_Images_StartIteration(void) +{ + itAllocatedImages = AllocatedImages.begin(); + return AllocatedImages.size(); +} + +image_t *R_Images_GetNextIteration(void) +{ + if (itAllocatedImages == AllocatedImages.end()) + return NULL; + + image_t *pImage = (*itAllocatedImages).second; + ++itAllocatedImages; + return pImage; +} + +// clean up anything to do with an image_t struct, but caller will have to clear the internal to an image_t struct ready for either struct free() or overwrite... +// +// (avoid using ri.xxxx stuff here in case running on dedicated) +// +static void R_Images_DeleteImageContents( image_t *pImage ) +{ + assert(pImage); // should never be called with NULL + if (pImage) + { + if (qglDeleteTextures) { //won't have one if we switched to dedicated. + qglDeleteTextures( 1, &pImage->texnum ); + } + Z_Free(pImage); + } +} + + + + + +/* +=============== +Upload32 + +=============== +*/ +extern qboolean charSet; +static void Upload32( unsigned *data, + GLenum format, + qboolean mipmap, + qboolean picmip, + qboolean isLightmap, + qboolean allowTC, + int *pformat, + USHORT *pUploadWidth, USHORT *pUploadHeight, bool bRectangle = false ) +{ + GLuint uiTarget = GL_TEXTURE_2D; + if ( bRectangle ) + { + uiTarget = GL_TEXTURE_RECTANGLE_EXT; + } + + if (format == GL_RGBA) + { + int samples; + int i, c; + byte *scan; + float rMax = 0, gMax = 0, bMax = 0; + int width = *pUploadWidth; + int height = *pUploadHeight; + + // + // perform optional picmip operation + // + if ( picmip ) { + for(i = 0; i < r_picmip->integer; i++) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + } + } + + // + // clamp to the current upper OpenGL limit + // scale both axis down equally so we don't have to + // deal with a half mip resampling + // + while ( width > glConfig.maxTextureSize || height > glConfig.maxTextureSize ) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + } + + // + // scan the texture for each channel's max values + // and verify if the alpha channel is being used or not + // + c = width*height; + scan = ((byte *)data); + samples = 3; + for ( i = 0; i < c; i++ ) + { + if ( scan[i*4+0] > rMax ) + { + rMax = scan[i*4+0]; + } + if ( scan[i*4+1] > gMax ) + { + gMax = scan[i*4+1]; + } + if ( scan[i*4+2] > bMax ) + { + bMax = scan[i*4+2]; + } + if ( scan[i*4 + 3] != 255 ) + { + samples = 4; + break; + } + } + + // select proper internal format + if ( samples == 3 ) + { + if ( glConfig.textureCompression == TC_S3TC && allowTC ) + { + *pformat = GL_RGB4_S3TC; + } + else if ( glConfig.textureCompression == TC_S3TC_DXT && allowTC ) + { // Compress purely color - no alpha + if ( r_texturebits->integer == 16 ) { + *pformat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; //this format cuts to 16 bit + } + else {//if we aren't using 16 bit then, use 32 bit compression + *pformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + } + else if ( isLightmap && r_texturebitslm->integer > 0 ) + { + // Allow different bit depth when we are a lightmap + if ( r_texturebitslm->integer == 16 ) + { + *pformat = GL_RGB5; + } + else if ( r_texturebitslm->integer == 32 ) + { + *pformat = GL_RGB8; + } + } + else if ( r_texturebits->integer == 16 ) + { + *pformat = GL_RGB5; + } + else if ( r_texturebits->integer == 32 ) + { + *pformat = GL_RGB8; + } + else + { + *pformat = 3; + } + } + else if ( samples == 4 ) + { + if ( glConfig.textureCompression == TC_S3TC_DXT && allowTC) + { // Compress both alpha and color + *pformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + else if ( r_texturebits->integer == 16 ) + { + *pformat = GL_RGBA4; + } + else if ( r_texturebits->integer == 32 ) + { + *pformat = GL_RGBA8; + } + else + { + *pformat = 4; + } + } + + *pUploadWidth = width; + *pUploadHeight = height; + + // copy or resample data as appropriate for first MIP level + if (!mipmap) + { + qglTexImage2D( uiTarget, 0, *pformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + goto done; + } + + R_LightScaleTexture (data, width, height, (qboolean)!mipmap ); + + qglTexImage2D( uiTarget, 0, *pformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + + if (mipmap) + { + int miplevel; + + miplevel = 0; + while (width > 1 || height > 1) + { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + if (width < 1) + width = 1; + if (height < 1) + height = 1; + miplevel++; + + if ( r_colorMipLevels->integer ) + { + R_BlendOverTexture( (byte *)data, width * height, mipBlendColors[miplevel] ); + } + + qglTexImage2D( uiTarget, miplevel, *pformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } + } + else + { + } + +done: + + if (mipmap) + { + qglTexParameterf(uiTarget, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(uiTarget, GL_TEXTURE_MAG_FILTER, gl_filter_max); + if(r_ext_texture_filter_anisotropic->integer>1 && glConfig.maxTextureFilterAnisotropy>0) + { + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_ext_texture_filter_anisotropic->value ); + } + } + else + { + qglTexParameterf(uiTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(uiTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + GL_CheckErrors(); +} + +#if 0 +//3d tex version -rww +static void Upload32_3D( unsigned *data, + int img_depth, + qboolean mipmap, + qboolean picmip, + qboolean isLightmap, + qboolean allowTC, + int *pformat, + USHORT *pUploadWidth, USHORT *pUploadHeight ) +{ + int samples; + int i, c; + byte *scan; + float rMax = 0, gMax = 0, bMax = 0; + int width = *pUploadWidth; + int height = *pUploadHeight; + int depth = img_depth; + + // + // perform optional picmip operation + // + if ( picmip ) { + for(i = 0; i < r_picmip->integer; i++) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + } + } + + // + // clamp to the current upper OpenGL limit + // scale both axis down equally so we don't have to + // deal with a half mip resampling + // + while ( width > glConfig.maxTextureSize || height > glConfig.maxTextureSize ) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + } + + // + // scan the texture for each channel's max values + // and verify if the alpha channel is being used or not + // + c = width*height; + scan = ((byte *)data); + samples = 3; + for ( i = 0; i < c; i++ ) + { + if ( scan[i*4+0] > rMax ) + { + rMax = scan[i*4+0]; + } + if ( scan[i*4+1] > gMax ) + { + gMax = scan[i*4+1]; + } + if ( scan[i*4+2] > bMax ) + { + bMax = scan[i*4+2]; + } + if ( scan[i*4 + 3] != 255 ) + { + samples = 4; + break; + } + } + + // select proper internal format + if ( samples == 3 ) + { + if ( glConfig.textureCompression == TC_S3TC && allowTC ) + { + *pformat = GL_RGB4_S3TC; + } + else if ( glConfig.textureCompression == TC_S3TC_DXT && allowTC ) + { // Compress purely color - no alpha + if ( r_texturebits->integer == 16 ) { + *pformat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; //this format cuts to 16 bit + } + else {//if we aren't using 16 bit then, use 32 bit compression + *pformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + } + else if ( isLightmap && r_texturebitslm->integer > 0 ) + { + // Allow different bit depth when we are a lightmap + if ( r_texturebitslm->integer == 16 ) + { + *pformat = GL_RGB5; + } + else if ( r_texturebitslm->integer == 32 ) + { + *pformat = GL_RGB8; + } + } + else if ( r_texturebits->integer == 16 ) + { + *pformat = GL_RGB5; + } + else if ( r_texturebits->integer == 32 ) + { + *pformat = GL_RGB8; + } + else + { + *pformat = 3; + } + } + else if ( samples == 4 ) + { + if ( glConfig.textureCompression == TC_S3TC_DXT && allowTC) + { // Compress both alpha and color + *pformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + } + else if ( r_texturebits->integer == 16 ) + { + *pformat = GL_RGBA4; + } + else if ( r_texturebits->integer == 32 ) + { + *pformat = GL_RGBA8; + } + else + { + *pformat = 4; + } + } + + *pUploadWidth = width; + *pUploadHeight = height; + + // copy or resample data as appropriate for first MIP level + if (!mipmap) + { + qglTexImage3DEXT (GL_TEXTURE_3D, 0, *pformat, width, height, depth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + goto done; + } + + R_LightScaleTexture (data, width, height, (qboolean)!mipmap ); + + qglTexImage3DEXT (GL_TEXTURE_3D, 0, *pformat, width, height, depth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + + if (mipmap) + { + int miplevel; + + miplevel = 0; + while (width > 1 || height > 1) + { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + if (width < 1) + width = 1; + if (height < 1) + height = 1; + miplevel++; + + if ( r_colorMipLevels->integer ) + { + R_BlendOverTexture( (byte *)data, width * height, mipBlendColors[miplevel] ); + } + + qglTexImage2D (GL_TEXTURE_2D, miplevel, *pformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } +done: + + if (mipmap) + { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + if(r_ext_texture_filter_anisotropic->integer>1 && glConfig.maxTextureFilterAnisotropy>0) { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f); + } + } + else + { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + GL_CheckErrors(); +} +#endif + +static void GL_ResetBinds(void) +{ + memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) ); + if ( qglBindTexture ) + { + if ( qglActiveTextureARB ) + { + GL_SelectTexture( 1 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + GL_SelectTexture( 0 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + } + else + { + qglBindTexture( GL_TEXTURE_2D, 0 ); + } + } +} + + +// special function used in conjunction with "devmapbsp"... +// +// (avoid using ri.xxxx stuff here in case running on dedicated) +// +void R_Images_DeleteLightMaps(void) +{ + qboolean bEraseOccured = qfalse; + for (AllocatedImages_t::iterator itImage = AllocatedImages.begin(); itImage != AllocatedImages.end(); bEraseOccured?itImage:++itImage) + { + bEraseOccured = qfalse; + + image_t *pImage = (*itImage).second; + + if (pImage->imgName[0] == '*' && strstr(pImage->imgName,"lightmap")) // loose check, but should be ok + { + R_Images_DeleteImageContents(pImage); +#ifndef __linux__ + itImage = AllocatedImages.erase(itImage); + bEraseOccured = qtrue; +#else + // MS & Dinkimware got the map::erase return wrong (it's null) + AllocatedImages_t::iterator itTemp = itImage; + itImage++; + AllocatedImages.erase(itTemp); +#endif + } + } + + GL_ResetBinds(); +} + +// special function currently only called by Dissolve code... +// +void R_Images_DeleteImage(image_t *pImage) +{ + // Even though we supply the image handle, we need to get the corresponding iterator entry... + // + AllocatedImages_t::iterator itImage = AllocatedImages.find(pImage->imgName); + if (itImage != AllocatedImages.end()) + { + R_Images_DeleteImageContents(pImage); + AllocatedImages.erase(itImage); + } + else + { + assert(0); + } +} + +// called only at app startup, vid_restart, app-exit +// +void R_Images_Clear(void) +{ + image_t *pImage; + // int iNumImages = + R_Images_StartIteration(); + while ( (pImage = R_Images_GetNextIteration()) != NULL) + { + R_Images_DeleteImageContents(pImage); + } + + AllocatedImages.clear(); + + giTextureBindNum = 1024; +} + + +void RE_RegisterImages_Info_f( void ) +{ + image_t *pImage = NULL; + int iImage = 0; + int iTexels = 0; + + int iNumImages = R_Images_StartIteration(); + while ( (pImage = R_Images_GetNextIteration()) != NULL) + { + Com_Printf ("%d: (%4dx%4dy) \"%s\"",iImage, pImage->width, pImage->height, pImage->imgName); + Com_DPrintf (S_COLOR_RED ", levused %d",pImage->iLastLevelUsedOn); + Com_Printf ("\n"); + + iTexels += pImage->width * pImage->height; + iImage++; + } + Com_Printf ("%d Images. %d (%.2fMB) texels total, (not including mipmaps)\n",iNumImages, iTexels, (float)iTexels / 1024.0f / 1024.0f); + Com_DPrintf (S_COLOR_RED "RE_RegisterMedia_GetLevel(): %d",RE_RegisterMedia_GetLevel()); +} + + +// implement this if you need to, do a find for the caller. I don't need it though, so far. +// +//void RE_RegisterImages_LevelLoadBegin(const char *psMapName); + + +// currently, this just goes through all the images and dumps any not referenced on this level... +// +qboolean RE_RegisterImages_LevelLoadEnd(void) +{ + Com_DPrintf (S_COLOR_RED "RE_RegisterImages_LevelLoadEnd():\n"); + +// int iNumImages = AllocatedImages.size(); // more for curiosity, really. + + qboolean bEraseOccured = qfalse; + for (AllocatedImages_t::iterator itImage = AllocatedImages.begin(); itImage != AllocatedImages.end(); bEraseOccured?itImage:++itImage) + { + bEraseOccured = qfalse; + + image_t *pImage = (*itImage).second; + + // don't un-register system shaders (*fog, *dlight, *white, *default), but DO de-register lightmaps ("*/lightmap%d") + if (pImage->imgName[0] != '*' || strchr(pImage->imgName,'/')) + { + // image used on this level? + // + if ( pImage->iLastLevelUsedOn != RE_RegisterMedia_GetLevel() ) + { + // nope, so dump it... + // + Com_DPrintf (S_COLOR_RED "Dumping image \"%s\"\n",pImage->imgName); + + R_Images_DeleteImageContents(pImage); +#ifndef __linux__ + itImage = AllocatedImages.erase(itImage); + bEraseOccured = qtrue; +#else + AllocatedImages_t::iterator itTemp = itImage; + itImage++; + AllocatedImages.erase(itTemp); +#endif + } + } + } + + + // this check can be deleted AFAIC, it seems to be just a quake thing... + // +// iNumImages = R_Images_StartIteration(); +// if (iNumImages > MAX_DRAWIMAGES) +// { +// Com_Printf (S_COLOR_YELLOW "Level uses %d images, old limit was MAX_DRAWIMAGES (%d)\n", iNumImages, MAX_DRAWIMAGES); +// } + + Com_DPrintf (S_COLOR_RED "RE_RegisterImages_LevelLoadEnd(): Ok\n"); + + GL_ResetBinds(); + + return bEraseOccured; +} + + + +// returns image_t struct if we already have this, else NULL. No disk-open performed +// (important for creating default images). +// +// This is called by both R_FindImageFile and anything that creates default images... +// +static image_t *R_FindImageFile_NoLoad(const char *name, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ) +{ + if (!name) { + return NULL; + } + + char *pName = GenerateImageMappingName(name); + + // + // see if the image is already loaded + // + AllocatedImages_t::iterator itAllocatedImage = AllocatedImages.find(pName); + if (itAllocatedImage != AllocatedImages.end()) + { + image_t *pImage = (*itAllocatedImage).second; + + // the white image can be used with any set of parms, but other mismatches are errors... + // + if ( strcmp( pName, "*white" ) ) { + if ( pImage->mipmap != !!mipmap ) { + Com_Printf (S_COLOR_YELLOW "WARNING: reused image %s with mixed mipmap parm\n", pName ); + } + if ( pImage->allowPicmip != !!allowPicmip ) { + Com_Printf (S_COLOR_YELLOW "WARNING: reused image %s with mixed allowPicmip parm\n", pName ); + } + if ( pImage->wrapClampMode != glWrapClampMode ) { + Com_Printf (S_COLOR_YELLOW "WARNING: reused image %s with mixed glWrapClampMode parm\n", pName ); + } + } + + pImage->iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); + + return pImage; + } + + return NULL; +} + + + +/* +================ +R_CreateImage + +This is the only way any image_t are created +================ +*/ +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, + GLenum format, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode, bool bRectangle ) +{ + image_t *image; + qboolean isLightmap = qfalse; + + if (strlen(name) >= MAX_QPATH ) { + Com_Error (ERR_DROP, "R_CreateImage: \"%s\" is too long\n", name); + } + + if(glConfig.clampToEdgeAvailable && glWrapClampMode == GL_CLAMP) { + glWrapClampMode = GL_CLAMP_TO_EDGE; + } + + if (name[0] == '*') + { + char *psLightMapNameSearchPos = strrchr(name,'/'); + if ( psLightMapNameSearchPos && !strncmp( psLightMapNameSearchPos+1, "lightmap", 8 ) ) { + isLightmap = qtrue; + } + } + + if ( (width&(width-1)) || (height&(height-1)) ) + { + Com_Error( ERR_FATAL, "R_CreateImage: %s dimensions (%i x %i) not power of 2!\n",name,width,height); + } + + image = R_FindImageFile_NoLoad(name, mipmap, allowPicmip, allowTC, glWrapClampMode ); + if (image) { + return image; + } + + image = (image_t*) Z_Malloc( sizeof( image_t ), TAG_IMAGE_T, qtrue ); +// memset(image,0,sizeof(*image)); // qtrue above does this + + image->texnum = 1024 + giTextureBindNum++; // ++ is of course staggeringly important... + + // record which map it was used on... + // + image->iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); + + image->mipmap = !!mipmap; + image->allowPicmip = !!allowPicmip; + + Q_strncpyz(image->imgName, name, sizeof(image->imgName)); + + image->width = width; + image->height = height; + image->wrapClampMode = glWrapClampMode; + + if ( qglActiveTextureARB ) { + GL_SelectTexture( 0 ); + } + + GLuint uiTarget = GL_TEXTURE_2D; + if ( bRectangle ) + { + qglDisable( uiTarget ); + uiTarget = GL_TEXTURE_RECTANGLE_EXT; + qglEnable( uiTarget ); + glWrapClampMode = GL_CLAMP_TO_EDGE; // default mode supported by rectangle. + qglBindTexture( uiTarget, image->texnum ); + } + else + { + GL_Bind(image); + } + + Upload32( (unsigned *)pic, format, + (qboolean)image->mipmap, + allowPicmip, + isLightmap, + allowTC, + &image->internalFormat, + &image->width, + &image->height, bRectangle ); + + qglTexParameterf( uiTarget, GL_TEXTURE_WRAP_S, glWrapClampMode ); + qglTexParameterf( uiTarget, GL_TEXTURE_WRAP_T, glWrapClampMode ); + + qglBindTexture( uiTarget, 0 ); //jfm: i don't know why this is here, but it breaks lightmaps when there's only 1 + glState.currenttextures[glState.currenttmu] = 0; //mark it not bound + + LPCSTR psNewName = GenerateImageMappingName(name); + Q_strncpyz(image->imgName, psNewName, sizeof(image->imgName)); + AllocatedImages[ image->imgName ] = image; + + if ( bRectangle ) + { + qglDisable( uiTarget ); + qglEnable( GL_TEXTURE_2D ); + } + + return image; +} + +//rwwRMG - added +void R_CreateAutomapImage( const char *name, const byte *pic, int width, int height, + qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ) +{ + R_CreateImage(name, pic, width, height, GL_RGBA, mipmap, allowPicmip, allowTC, glWrapClampMode); +} + +/* +========================================================= + +TARGA LOADING + +========================================================= +*/ +/* +Ghoul2 Insert Start +*/ + +bool LoadTGAPalletteImage ( const char *name, byte **pic, int *width, int *height) +{ + int columns, rows, numPixels; + byte *buf_p; + byte *buffer; + TargaHeader targa_header; + byte *dataStart; + + *pic = NULL; + + // + // load the file + // + FS_ReadFile ( ( char * ) name, (void **)&buffer); + if (!buffer) { + return false; + } + + buf_p = buffer; + + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_length = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_size = *buf_p++; + targa_header.x_origin = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.y_origin = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.width = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.height = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + + if (targa_header.image_type!=1 ) + { + Com_Error (ERR_DROP, "LoadTGAPalletteImage: Only type 1 (uncompressed pallettised) TGA images supported\n"); + } + + if ( targa_header.colormap_type == 0 ) + { + Com_Error( ERR_DROP, "LoadTGAPalletteImage: colormaps ONLY supported\n" ); + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows; + + if (width) + *width = columns; + if (height) + *height = rows; + + *pic = (unsigned char *) Z_Malloc (numPixels, TAG_TEMP_WORKSPACE, qfalse ); + if (targa_header.id_length != 0) + { + buf_p += targa_header.id_length; // skip TARGA image comment + } + dataStart = buf_p + (targa_header.colormap_length * (targa_header.colormap_size / 4)); + memcpy(*pic, dataStart, numPixels); + FS_FreeFile (buffer); + + return true; +} + +#endif // #ifndef DEDICATED + +// My TGA loader... +// +//--------------------------------------------------- +#pragma pack(push,1) +typedef struct +{ + byte byIDFieldLength; // must be 0 + byte byColourmapType; // 0 = truecolour, 1 = paletted, else bad + byte byImageType; // 1 = colour mapped (palette), uncompressed, 2 = truecolour, uncompressed, else bad + word w1stColourMapEntry; // must be 0 + word wColourMapLength; // 256 for 8-bit palettes, else 0 for true-colour + byte byColourMapEntrySize; // 24 for 8-bit palettes, else 0 for true-colour + word wImageXOrigin; // ignored + word wImageYOrigin; // ignored + word wImageWidth; // in pixels + word wImageHeight; // in pixels + byte byImagePlanes; // bits per pixel (8 for paletted, else 24 for true-colour) + byte byScanLineOrder; // Image descriptor bytes + // bits 0-3 = # attr bits (alpha chan) + // bits 4-5 = pixel order/dir + // bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way) +} TGAHeader_t; +#pragma pack(pop) + + +// *pic == pic, else NULL for failed. +// +// returns false if found but had a format error, else true for either OK or not-found (there's a reason for this) +// + +void LoadTGA ( const char *name, byte **pic, int *width, int *height) +{ + char sErrorString[1024]; + bool bFormatErrors = false; + + // these don't need to be declared or initialised until later, but the compiler whines that 'goto' skips them. + // + byte *pRGBA = NULL; + byte *pOut = NULL; + byte *pIn = NULL; + + + *pic = NULL; + +#define TGA_FORMAT_ERROR(blah) {sprintf(sErrorString,blah); bFormatErrors = true; goto TGADone;} +//#define TGA_FORMAT_ERROR(blah) Com_Error( ERR_DROP, blah ); + + // + // load the file + // + byte *pTempLoadedBuffer = 0; + FS_ReadFile ( ( char * ) name, (void **)&pTempLoadedBuffer); + if (!pTempLoadedBuffer) { + return; + } + + TGAHeader_t *pHeader = (TGAHeader_t *) pTempLoadedBuffer; + + if (pHeader->byColourmapType!=0) + { + TGA_FORMAT_ERROR("LoadTGA: colourmaps not supported\n" ); + } + + if (pHeader->byImageType != 2 && pHeader->byImageType != 3 && pHeader->byImageType != 10) + { + TGA_FORMAT_ERROR("LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RLE-RGB) images supported\n"); + } + + if (pHeader->w1stColourMapEntry != 0) + { + TGA_FORMAT_ERROR("LoadTGA: colourmaps not supported\n" ); + } + + if (pHeader->wColourMapLength !=0 && pHeader->wColourMapLength != 256) + { + TGA_FORMAT_ERROR("LoadTGA: ColourMapLength must be either 0 or 256\n" ); + } + + if (pHeader->byColourMapEntrySize != 0 && pHeader->byColourMapEntrySize != 24) + { + TGA_FORMAT_ERROR("LoadTGA: ColourMapEntrySize must be either 0 or 24\n" ); + } + + if ( ( pHeader->byImagePlanes != 24 && pHeader->byImagePlanes != 32) && (pHeader->byImagePlanes != 8 && pHeader->byImageType != 3)) + { + TGA_FORMAT_ERROR("LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n"); + } + + if ((pHeader->byScanLineOrder&0x30)!=0x00 && + (pHeader->byScanLineOrder&0x30)!=0x10 && + (pHeader->byScanLineOrder&0x30)!=0x20 && + (pHeader->byScanLineOrder&0x30)!=0x30 + ) + { + TGA_FORMAT_ERROR("LoadTGA: ScanLineOrder must be either 0x00,0x10,0x20, or 0x30\n"); + } + + + + // these last checks are so i can use ID's RLE-code. I don't dare fiddle with it or it'll probably break... + // + if ( pHeader->byImageType == 10) + { + if ((pHeader->byScanLineOrder & 0x30) != 0x00) + { + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB Images (type 10) must be in bottom-to-top format\n"); + } + if (pHeader->byImagePlanes != 24 && pHeader->byImagePlanes != 32) // probably won't happen, but avoids compressed greyscales? + { + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB Images (type 10) must be 24 or 32 bit\n"); + } + } + + // now read the actual bitmap in... + // + // Image descriptor bytes + // bits 0-3 = # attr bits (alpha chan) + // bits 4-5 = pixel order/dir + // bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way) + // + int iYStart,iXStart,iYStep,iXStep; + + switch(pHeader->byScanLineOrder & 0x30) + { + default: // default case stops the compiler complaining about using uninitialised vars + case 0x00: // left to right, bottom to top + + iXStart = 0; + iXStep = 1; + + iYStart = pHeader->wImageHeight-1; + iYStep = -1; + + break; + + case 0x10: // right to left, bottom to top + + iXStart = pHeader->wImageWidth-1; + iXStep = -1; + + iYStart = pHeader->wImageHeight-1; + iYStep = -1; + + break; + + case 0x20: // left to right, top to bottom + + iXStart = 0; + iXStep = 1; + + iYStart = 0; + iYStep = 1; + + break; + + case 0x30: // right to left, top to bottom + + iXStart = pHeader->wImageWidth-1; + iXStep = -1; + + iYStart = 0; + iYStep = 1; + + break; + } + + // feed back the results... + // + if (width) + *width = pHeader->wImageWidth; + if (height) + *height = pHeader->wImageHeight; + + pRGBA = (byte *) Z_Malloc (pHeader->wImageWidth * pHeader->wImageHeight * 4, TAG_TEMP_WORKSPACE, qfalse); + *pic = pRGBA; + pOut = pRGBA; + pIn = pTempLoadedBuffer + sizeof(*pHeader); + + // I don't know if this ID-thing here is right, since comments that I've seen are at the end of the file, + // with a zero in this field. However, may as well... + // + if (pHeader->byIDFieldLength != 0) + pIn += pHeader->byIDFieldLength; // skip TARGA image comment + + byte red,green,blue,alpha; + + if ( pHeader->byImageType == 2 || pHeader->byImageType == 3 ) // RGB or greyscale + { + for (int y=iYStart, iYCount=0; iYCountwImageHeight; y+=iYStep, iYCount++) + { + pOut = pRGBA + y * pHeader->wImageWidth *4; + for (int x=iXStart, iXCount=0; iXCountwImageWidth; x+=iXStep, iXCount++) + { + switch (pHeader->byImagePlanes) + { + case 8: + blue = *pIn++; + green = blue; + red = blue; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = 255; + break; + + case 24: + blue = *pIn++; + green = *pIn++; + red = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = 255; + break; + + case 32: + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = alpha; + break; + + default: + assert(0); // if we ever hit this, someone deleted a header check higher up + TGA_FORMAT_ERROR("LoadTGA: Image can only have 8, 24 or 32 planes for RGB/greyscale\n"); + break; + } + } + } + } + else + if (pHeader->byImageType == 10) // RLE-RGB + { + // I've no idea if this stuff works, I normally reject RLE targas, but this is from ID's code + // so maybe I should try and support it... + // + byte packetHeader, packetSize, j; + + for (int y = pHeader->wImageHeight-1; y >= 0; y--) + { + pOut = pRGBA + y * pHeader->wImageWidth *4; + for (int x=0; xwImageWidth;) + { + packetHeader = *pIn++; + packetSize = 1 + (packetHeader & 0x7f); + if (packetHeader & 0x80) // run-length packet + { + switch (pHeader->byImagePlanes) + { + case 24: + + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = 255; + break; + + case 32: + + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = *pIn++; + break; + + default: + assert(0); // if we ever hit this, someone deleted a header check higher up + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB can only have 24 or 32 planes\n"); + break; + } + + for (j=0; jwImageWidth) // run spans across rows + { + x = 0; + if (y > 0) + y--; + else + goto breakOut; + pOut = pRGBA + y * pHeader->wImageWidth * 4; + } + } + } + else + { // non run-length packet + + for (j=0; jbyImagePlanes) + { + case 24: + + blue = *pIn++; + green = *pIn++; + red = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = 255; + break; + + case 32: + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = alpha; + break; + + default: + assert(0); // if we ever hit this, someone deleted a header check higher up + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB can only have 24 or 32 planes\n"); + break; + } + x++; + if (x == pHeader->wImageWidth) // pixel packet run spans across rows + { + x = 0; + if (y > 0) + y--; + else + goto breakOut; + pOut = pRGBA + y * pHeader->wImageWidth * 4; + } + } + } + } + breakOut:; + } + } + +TGADone: + + FS_FreeFile (pTempLoadedBuffer); + + if (bFormatErrors) + { + Com_Error( ERR_DROP, "%s( File: \"%s\" )\n",sErrorString,name); + } +} + +#ifndef DEDICATED +static void LoadJPG( const char *filename, unsigned char **pic, int *width, int *height ) { + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + unsigned char *out; + byte *fbuffer; + byte *bbuf; + + /* In this example we want to open the input file before doing anything else, + * so that the setjmp() error recovery below can assume the file is open. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to read binary files. + */ + + fileHandle_t h; + const int len = FS_FOpenFileRead(filename, &h, qfalse); + if (!h) + { + return; + } + + fbuffer = (byte *)Z_Malloc(len + 4096, TAG_TEMP_WORKSPACE); + FS_Read(fbuffer, len, h); + FS_FCloseFile(h); + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src(&cinfo, fbuffer); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, TRUE); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + + // rww - 9-13-01 [1-26-01-sof2] + if (cinfo.output_components != 4 && cinfo.output_components != 1) { + Com_Printf("JPG %s is unsupported color depth (%d)\n",filename,cinfo.output_components); + } + + out = (unsigned char *)Z_Malloc(cinfo.output_width*cinfo.output_height*4, TAG_TEMP_WORKSPACE, qfalse ); + + *pic = out; + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + bbuf = ((out+(row_stride*cinfo.output_scanline))); + buffer = &bbuf; + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + } + + if (cinfo.output_components == 1) + { + byte *pbDest = (*pic + (cinfo.output_width * cinfo.output_height * 4))-1; + byte *pbSrc = (*pic + (cinfo.output_width * cinfo.output_height ))-1; + int iPixels = cinfo.output_width * cinfo.output_height; + + for (int i=0; idest; + + dest->pub.next_output_byte = dest->outfile; + dest->pub.free_in_buffer = dest->size; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +boolean empty_output_buffer (j_compress_ptr cinfo) +{ + return TRUE; +} + + +/* + * Compression initialization. + * Before calling this, all parameters and a data destination must be set up. + * + * We require a write_all_tables parameter as a failsafe check when writing + * multiple datastreams from the same compression object. Since prior runs + * will have left all the tables marked sent_table=TRUE, a subsequent run + * would emit an abbreviated stream (no tables) by default. This may be what + * is wanted, but for safety's sake it should not be the default behavior: + * programmers should have to make a deliberate choice to emit abbreviated + * images. Therefore the documentation and examples should encourage people + * to pass write_all_tables=TRUE; then it will take active thought to do the + * wrong thing. + */ + +GLOBAL void +jpeg_start_compress (j_compress_ptr cinfo, boolean write_all_tables) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (write_all_tables) + jpeg_suppress_tables(cinfo, FALSE); /* mark all tables to be written */ + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Perform master selection of active modules */ + jinit_compress_master(cinfo); + /* Set up for the first pass */ + (*cinfo->master->prepare_for_pass) (cinfo); + /* Ready for application to drive first pass through jpeg_write_scanlines + * or jpeg_write_raw_data. + */ + cinfo->next_scanline = 0; + cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING); +} + + +/* + * Write some scanlines of data to the JPEG compressor. + * + * The return value will be the number of lines actually written. + * This should be less than the supplied num_lines only in case that + * the data destination module has requested suspension of the compressor, + * or if more than image_height scanlines are passed in. + * + * Note: we warn about excess calls to jpeg_write_scanlines() since + * this likely signals an application programmer error. However, + * excess scanlines passed in the last valid call are *silently* ignored, + * so that the application need not adjust num_lines for end-of-image + * when using a multiple-scanline buffer. + */ + +GLOBAL JDIMENSION +jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines) +{ + JDIMENSION row_ctr, rows_left; + + if (cinfo->global_state != CSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->next_scanline >= cinfo->image_height) + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_scanlines. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_scanlines. + */ + if (cinfo->master->call_pass_startup) + (*cinfo->master->pass_startup) (cinfo); + + /* Ignore any extra scanlines at bottom of image. */ + rows_left = cinfo->image_height - cinfo->next_scanline; + if (num_lines > rows_left) + num_lines = rows_left; + + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines); + cinfo->next_scanline += row_ctr; + return row_ctr; +} + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +static int hackSize; + +void term_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + size_t datacount = dest->size - dest->pub.free_in_buffer; + hackSize = datacount; +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +void jpegDest (j_compress_ptr cinfo, byte* outfile, int size) +{ + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(my_destination_mgr)); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; + dest->size = size; +} + +void SaveJPG(char * filename, int quality, int image_width, int image_height, unsigned char *image_buffer) { + /* This struct contains the JPEG compression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + * It is possible to have several such structures, representing multiple + * compression/decompression processes, in existence at once. We refer + * to any one struct (and its associated working data) as a "JPEG object". + */ + struct jpeg_compress_struct cinfo; + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + int row_stride; /* physical row width in image buffer */ + unsigned char *out; + + /* Step 1: allocate and initialize JPEG compression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress(&cinfo); + + /* Step 2: specify data destination (eg, a file) */ + /* Note: steps 2 and 3 can be done in either order. */ + + /* Here we use the library-supplied code to send compressed data to a + * stdio stream. You can also write your own code to do something else. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to write binary files. + */ + out = (unsigned char *)Hunk_AllocateTempMemory(image_width*image_height*4); + jpegDest(&cinfo, out, image_width*image_height*4); + + /* Step 3: set parameters for compression */ + + /* First we supply a description of the input image. + * Four fields of the cinfo struct must be filled in: + */ + cinfo.image_width = image_width; /* image width and height, in pixels */ + cinfo.image_height = image_height; + cinfo.input_components = 4; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + /* Now use the library's routine to set default compression parameters. + * (You must set at least cinfo.in_color_space before calling this, + * since the defaults depend on the source color space.) + */ + jpeg_set_defaults(&cinfo); + /* Now you can set any non-default parameters you wish to. + * Here we just illustrate the use of quality (quantization table) scaling: + */ + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + + /* Step 4: Start compressor */ + + /* TRUE ensures that we will write a complete interchange-JPEG file. + * Pass TRUE unless you are very sure of what you're doing. + */ + jpeg_start_compress(&cinfo, TRUE); + + /* Step 5: while (scan lines remain to be written) */ + /* jpeg_write_scanlines(...); */ + + /* Here we use the library's state variable cinfo.next_scanline as the + * loop counter, so that we don't have to keep track ourselves. + * To keep things simple, we pass one scanline per call; you can pass + * more if you wish, though. + */ + row_stride = image_width * 4; /* JSAMPLEs per row in image_buffer */ + + while (cinfo.next_scanline < cinfo.image_height) { + /* jpeg_write_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could pass + * more than one scanline at a time if that's more convenient. + */ + row_pointer[0] = & image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride]; + (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + /* Step 6: Finish compression */ + + jpeg_finish_compress(&cinfo); + /* After finish_compress, we can close the output file. */ + FS_WriteFile( filename, out, hackSize ); + + Hunk_FreeTempMemory(out); + + /* Step 7: release JPEG compression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_compress(&cinfo); + + /* And we're done! */ +} + +//=================================================================== + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. +================= +*/ +void R_LoadImage( const char *shortname, byte **pic, int *width, int *height, GLenum *format ) { + int bytedepth; + char name[MAX_QPATH]; + + *pic = NULL; + *width = 0; + *height = 0; + *format = GL_RGBA; + COM_StripExtension(shortname,name); + COM_DefaultExtension(name, sizeof(name), ".jpg"); + LoadJPG( name, pic, width, height ); + if (*pic) { + return; + } + + COM_StripExtension(shortname,name); + COM_DefaultExtension(name, sizeof(name), ".png"); + LoadPNG32( name, pic, width, height, &bytedepth ); // try png first + if (*pic){ + return; + } + + COM_StripExtension(shortname,name); + COM_DefaultExtension(name, sizeof(name), ".tga"); + LoadTGA( name, pic, width, height ); // try tga first + if (*pic){ + return; + } +} + + +void R_LoadDataImage( const char *name, byte **pic, int *width, int *height) +{ + int len; + char work[MAX_QPATH]; + + *pic = NULL; + *width = 0; + *height = 0; + + len = strlen(name); + if(len >= MAX_QPATH) + { + return; + } + if (len < 5) + { + return; + } +// MD_PushTag(TAG_DATA_IMAGE_LOAD); + + strcpy(work, name); + + COM_DefaultExtension( work, sizeof( work ), ".png" ); + LoadPNG8( work, pic, width, height ); + + if (!pic || !*pic) + { //png load failed, try jpeg + strcpy(work, name); + COM_DefaultExtension( work, sizeof( work ), ".jpg" ); + LoadJPG( work, pic, width, height ); + } + + if (!pic || !*pic) + { //both png and jpeg failed, try targa + strcpy(work, name); + COM_DefaultExtension( work, sizeof( work ), ".tga" ); + LoadTGA( work, pic, width, height ); + } + + if(*pic) + { +// MD_PopTag(); + return; + } + // Dataimage loading failed + Com_Printf("Couldn't read %s -- dataimage load failed\n", name); +// MD_PopTag(); +} + +#endif // !DEDICATED + +void R_InvertImage(byte *data, int width, int height, int depth) +{ + byte *newData; + byte *oldData; + byte *saveData; + int y, stride; + + stride = width * depth; + + oldData = data + ((height - 1) * stride); + newData = (byte *)Z_Malloc(height * stride, TAG_TEMP_IMAGE, qfalse ); + saveData = newData; + + for(y = 0; y < height; y++) + { + memcpy(newData, oldData, stride); + newData += stride; + oldData -= stride; + } + memcpy(data, saveData, height * stride); + Z_Free(saveData); +} + +// Lanczos3 image resampling. Better than bicubic, based on sin(x)/x algorithm + +#define LANCZOS3 (3.0f) +#define M_PI_OVER_3 (M_PI / 3.0f) + +typedef struct +{ + int pixel; + float weight; +} contrib_t; + +typedef struct +{ + int n; // number of contributors + contrib_t *p; // pointer to list of contributions +} contrib_list_t; + +// sin(x)/x * sin(x/3)/(x/3) + +float Lanczos3(float t) +{ + if(!t) + { + return(1.0f); + } + t = (float)fabs(t); + if(t < 3.0f) + { + return(sinf(t * M_PI) * sinf(t * M_PI_OVER_3) / (t * M_PI * t * M_PI_OVER_3)); + } + return(0.0f); +} + +void R_Resample(byte *source, int swidth, int sheight, byte *dest, int dwidth, int dheight, int components) +{ + int i, j, k, l, count, left, right, num; + int pixel; + byte *raster; + float center, weight, scale, width, height; + contrib_list_t *contributors; + +// MD_PushTag(TAG_RESAMPLE); + + byte *work = (byte *)Z_Malloc(dwidth * sheight * components, TAG_RESAMPLE); + + // Pre calculate filter contributions for rows + contributors = (contrib_list_t *)Z_Malloc(sizeof(contrib_list_t) * dwidth, TAG_RESAMPLE); + + float xscale = (float)dwidth / (float)swidth; + + if(xscale < 1.0f) + { + width = ceilf(LANCZOS3 / xscale); + scale = xscale; + } + else + { + width = LANCZOS3; + scale = 1.0f; + } + num = ((int)width * 2) + 1; + + for(i = 0; i < dwidth; i++) + { + contributors[i].n = 0; + contributors[i].p = (contrib_t *)Z_Malloc(num * sizeof(contrib_t), TAG_RESAMPLE); + + center = (float)i / xscale; + left = (int)ceilf(center - width); + right = (int)floorf(center + width); + + for(j = left; j <= right; j++) + { + weight = Lanczos3((center - (float)j) * scale) * scale; + if(j < 0) + { + pixel = -j; + } + else if(j >= swidth) + { + pixel = (swidth - j) + swidth - 1; + } + else + { + pixel = j; + } + count = contributors[i].n++; + contributors[i].p[count].pixel = pixel; + contributors[i].p[count].weight = weight; + } + } + // Apply filters to zoom horizontally from source to work + for(k = 0; k < sheight; k++) + { + raster = source + (k * swidth * components); + for(i = 0; i < dwidth; i++) + { + for(l = 0; l < components; l++) + { + weight = 0.0f; + for(j = 0; j < contributors[i].n; j++) + { + weight += raster[(contributors[i].p[j].pixel * components) + l] * contributors[i].p[j].weight; + } + pixel = (byte)Com_Clamp(0.0f, 255.0f, weight); + work[(k * dwidth * components) + (i * components) + l] = pixel; + } + } + } + // Clean up + for(i = 0; i < dwidth; i++) + { + Z_Free(contributors[i].p); + } + Z_Free(contributors); + + // Columns + contributors = (contrib_list_t *)Z_Malloc(sizeof(contrib_list_t) * dheight, TAG_RESAMPLE); + + float yscale = (float)dheight / (float)sheight; + if(yscale < 1.0f) + { + height = ceilf(LANCZOS3 / yscale); + scale = yscale; + } + else + { + height = LANCZOS3; + scale = 1.0f; + } + num = ((int)height * 2) + 1; + + for(i = 0; i < dheight; i++) + { + contributors[i].n = 0; + contributors[i].p = (contrib_t *)Z_Malloc(num * sizeof(contrib_t), TAG_RESAMPLE); + + center = (float)i / yscale; + left = (int)ceilf(center - height); + right = (int)floorf(center + height); + + for(j = left; j <= right; j++) + { + weight = Lanczos3((center - (float)j) * scale) * scale; + if(j < 0) + { + pixel = -j; + } + else if(j >= sheight) + { + pixel = (sheight - j) + sheight - 1; + } + else + { + pixel = j; + } + count = contributors[i].n++; + contributors[i].p[count].pixel = pixel; + contributors[i].p[count].weight = weight; + } + } + // Apply filter to columns + for(k = 0; k < dwidth; k++) + { + for(l = 0; l < components; l++) + { + for(i = 0; i < dheight; i++) + { + weight = 0.0f; + for(j = 0; j < contributors[i].n; j++) + { + weight += work[(contributors[i].p[j].pixel * dwidth * components) + (k * components) + l] * contributors[i].p[j].weight; + } + pixel = (byte)Com_Clamp(0.0f, 255.0f, weight); + dest[(i * dwidth * components) + (k * components) + l] = pixel; + } + } + } + // Clean up + for(i = 0; i < dheight; i++) + { + Z_Free(contributors[i].p); + } + Z_Free(contributors); + Z_Free(work); + +// MD_PopTag(); +} + +#ifndef DEDICATED + +/* +=============== +R_FindImageFile + +Finds or loads the given image. +Returns NULL if it fails, not a default image. +============== +*/ +image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ) { + image_t *image; + int width, height; + byte *pic; + GLenum format; + + if (!name + || com_dedicated->integer // stop ghoul2 horribleness as regards image loading from server + ) + { + return NULL; + } + + // need to do this here as well as in R_CreateImage, or R_FindImageFile_NoLoad() may complain about + // different clamp parms used... + // + if(glConfig.clampToEdgeAvailable && glWrapClampMode == GL_CLAMP) { + glWrapClampMode = GL_CLAMP_TO_EDGE; + } + + image = R_FindImageFile_NoLoad(name, mipmap, allowPicmip, allowTC, glWrapClampMode ); + if (image) { + return image; + } + + // + // load the pic from disk + // + R_LoadImage( name, &pic, &width, &height, &format ); + if ( pic == NULL ) { // if we dont get a successful load + return NULL; // bail + } + + + // refuse to find any files not power of 2 dims... + // + if ( (width&(width-1)) || (height&(height-1)) ) + { + Com_Printf ("Refusing to load non-power-2-dims(%d,%d) pic \"%s\"...\n", width,height,name ); + return NULL; + } + + image = R_CreateImage( ( char * ) name, pic, width, height, format, mipmap, allowPicmip, allowTC, glWrapClampMode ); + Z_Free( pic ); + return image; +} + + +/* +================ +R_CreateDlightImage +================ +*/ +#define DLIGHT_SIZE 16 +static void R_CreateDlightImage( void ) +{ + int width, height; + byte *pic; + GLenum format; + + R_LoadImage("gfx/2d/dlight", &pic, &width, &height, &format); + if (pic) + { + tr.dlightImage = R_CreateImage("*dlight", pic, width, height, GL_RGBA, qfalse, qfalse, qfalse, GL_CLAMP ); + Z_Free(pic); + } + else + { // if we dont get a successful load + int x,y; + byte data[DLIGHT_SIZE][DLIGHT_SIZE][4]; + int b; + + // make a centered inverse-square falloff blob for dynamic lighting + for (x=0 ; x 255) { + b = 255; + } else if ( b < 75 ) { + b = 0; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = b; + data[y][x][3] = 255; + } + } + tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, GL_RGBA, qfalse, qfalse, qfalse, GL_CLAMP ); + } +} + + +/* +================= +R_InitFogTable +================= +*/ +void R_InitFogTable( void ) { + int i; + float d; + float exp; + + exp = 0.5; + + for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) { + d = pow ( (float)i/(FOG_TABLE_SIZE-1), exp ); + + tr.fogTable[i] = d; + } +} + +/* +================ +R_FogFactor + +Returns a 0.0 to 1.0 fog density value +This is called for each texel of the fog texture on startup +and for each vertex of transparent shaders in fog dynamically +================ +*/ +float R_FogFactor( float s, float t ) { + float d; + + s -= 1.0/512; + if ( s < 0 ) { + return 0; + } + if ( t < 1.0/32 ) { + return 0; + } + if ( t < 31.0/32 ) { + s *= (t - 1.0f/32.0f) / (30.0f/32.0f); + } + + // we need to leave a lot of clamp range + s *= 8; + + if ( s > 1.0 ) { + s = 1.0; + } + + d = tr.fogTable[ (int)(s * (FOG_TABLE_SIZE-1)) ]; + + return d; +} + +/* +================ +R_CreateFogImage +================ +*/ +#define FOG_S 256 +#define FOG_T 32 +static void R_CreateFogImage( void ) { + int x,y; + byte *data; + float g; + float d; + float borderColor[4]; + + data = (unsigned char *)Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 ); + + g = 2.0; + + // S is distance, T is depth + for (x=0 ; xinteger > glConfig.vidWidth ) + { + r_DynamicGlowWidth->integer = glConfig.vidWidth; + } + if ( r_DynamicGlowHeight->integer > glConfig.vidHeight ) + { + r_DynamicGlowHeight->integer = glConfig.vidHeight; + } + tr.blurImage = 1024 + giTextureBindNum++; + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage ); + qglTexImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA16, r_DynamicGlowWidth->integer, r_DynamicGlowHeight->integer, 0, GL_RGB, GL_FLOAT, 0 ); + qglTexParameteri( GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameteri( GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameteri( GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP ); + qglTexParameteri( GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + + + // with overbright bits active, we need an image which is some fraction of full color, + // for default lightmaps, etc + for (x=0 ; xinteger; + if ( !glConfig.deviceSupportsGamma ) { + tr.overbrightBits = 0; // need hardware gamma for overbright + } + + // never overbright in windowed mode + if ( !glConfig.isFullscreen ) + { + tr.overbrightBits = 0; + } + + if ( tr.overbrightBits > 1 ) { + tr.overbrightBits = 1; + } + + if ( tr.overbrightBits < 0 ) { + tr.overbrightBits = 0; + } + + tr.identityLight = 1.0f / ( 1 << tr.overbrightBits ); + tr.identityLightByte = 255 * tr.identityLight; + + + if ( r_intensity->value < 1.0f ) { + Cvar_Set( "r_intensity", "1" ); + } + + if ( r_gamma->value < 0.5f ) { + Cvar_Set( "r_gamma", "0.5" ); + } else if ( r_gamma->value > 3.0f ) { + Cvar_Set( "r_gamma", "3.0" ); + } + + g = r_gamma->value; + + shift = tr.overbrightBits; + + for ( i = 0; i < 256; i++ ) { + if ( g == 1 ) { + inf = i; + } else { + inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f; + } + inf <<= shift; + if (inf < 0) { + inf = 0; + } + if (inf > 255) { + inf = 255; + } + s_gammatable[i] = inf; + } + + for (i=0 ; i<256 ; i++) { + j = i * r_intensity->value; + if (j > 255) { + j = 255; + } + s_intensitytable[i] = j; + } + + if ( glConfig.deviceSupportsGamma ) + { + GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable ); + } +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) { + //memset(hashTable, 0, sizeof(hashTable)); // DO NOT DO THIS NOW (because of image cacheing) -ste. + // build brightness translation tables + R_SetColorMappings(); + + // create default texture and white texture + R_CreateBuiltinImages(); +} + +/* +=============== +R_DeleteTextures +=============== +*/ +// (only gets called during vid_restart now (and app exit), not during map load) +// +void R_DeleteTextures( void ) { + + R_Images_Clear(); + GL_ResetBinds(); +} + +/* +============================================================================ + +SKINS + +============================================================================ +*/ + +static char *CommaParse( char **data_p ); +//can't be dec'd here since we need it for non-dedicated builds now as well. + +/* +=============== +RE_RegisterSkin + +=============== +*/ + +#endif // !DEDICATED +bool gServerSkinHack = false; + + +shader_t *R_FindServerShader( const char *name, const short *lightmapIndex, const byte *styles, qboolean mipRawImage ); +char *CommaParse( char **data_p ); +/* +=============== +RE_SplitSkins +input = skinname, possibly being a macro for three skins +return= true if three part skins found +output= qualified names to three skins if return is true, undefined if false +=============== +*/ +bool RE_SplitSkins(const char *INname, char *skinhead, char *skintorso, char *skinlower) +{ //INname= "models/players/jedi_tf/|head01_skin1|torso01|lower01"; + if (strchr(INname, '|')) + { + char name[MAX_QPATH]; + strcpy(name, INname); + char *p = strchr(name, '|'); + *p=0; + p++; + //fill in the base path + strcpy (skinhead, name); + strcpy (skintorso, name); + strcpy (skinlower, name); + + //now get the the individual files + + //advance to second + char *p2 = strchr(p, '|'); + assert(p2); + *p2=0; + p2++; + strcat (skinhead, p); + strcat (skinhead, ".skin"); + + + //advance to third + p = strchr(p2, '|'); + assert(p); + *p=0; + p++; + strcat (skintorso,p2); + strcat (skintorso, ".skin"); + + strcat (skinlower,p); + strcat (skinlower, ".skin"); + + return true; + } + return false; +} + +// given a name, go get the skin we want and return +qhandle_t RE_RegisterIndividualSkin( const char *name , qhandle_t hSkin) +{ + skin_t *skin; + skinSurface_t *surf; + char *text, *text_p; + char *token; + char surfName[MAX_QPATH]; + + // load and parse the skin file + FS_ReadFile( name, (void **)&text ); + if ( !text ) { +#ifndef FINAL_BUILD + Com_Printf( "WARNING: RE_RegisterSkin( '%s' ) failed to load!\n", name ); +#endif + return 0; + } + + assert (tr.skins[hSkin]); //should already be setup, but might be an 3part append + + skin = tr.skins[hSkin]; + + text_p = text; + while ( text_p && *text_p ) { + // get surface name + token = CommaParse( &text_p ); + Q_strncpyz( surfName, token, sizeof( surfName ) ); + + if ( !token[0] ) { + break; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surfName ); + + if ( *text_p == ',' ) { + text_p++; + } + + if ( !strncmp( token, "tag_", 4 ) ) { //these aren't in there, but just in case you load an id style one... + continue; + } + + // parse the shader name + token = CommaParse( &text_p ); + + if ( !strcmp( &surfName[strlen(surfName)-4], "_off") ) + { + if ( !strcmp( token ,"*off" ) ) + { + continue; //don't need these double offs + } + surfName[strlen(surfName)-4] = 0; //remove the "_off" + } + if (sizeof( skin->surfaces) / sizeof( skin->surfaces[0] ) <= skin->numSurfaces) + { + assert( sizeof( skin->surfaces) / sizeof( skin->surfaces[0] ) > skin->numSurfaces ); + Com_Printf( "WARNING: RE_RegisterSkin( '%s' ) more than %d surfaces!\n", name, sizeof( skin->surfaces) / sizeof( skin->surfaces[0] ) ); + break; + } + surf = skin->surfaces[ skin->numSurfaces ] = (skinSurface_t *) Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); + Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); + + if (gServerSkinHack) + { + surf->shader = R_FindServerShader( token, lightmapsNone, stylesDefault, qtrue ); + } + else + { + surf->shader = R_FindShader( token, lightmapsNone, stylesDefault, qtrue ); + } + skin->numSurfaces++; + } + + FS_FreeFile( text ); + + + // never let a skin have 0 shaders + if ( skin->numSurfaces == 0 ) { + return 0; // use default skin + } + + return hSkin; +} + +qhandle_t RE_RegisterSkin( const char *name ) { + qhandle_t hSkin; + skin_t *skin; + + if ( !name || !name[0] ) { + Com_Printf( "Empty name passed to RE_RegisterSkin\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Skin name exceeds MAX_QPATH\n" ); + return 0; + } + + // see if the skin is already loaded + for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) { + skin = tr.skins[hSkin]; + if ( !Q_stricmp( skin->name, name ) ) { + if( skin->numSurfaces == 0 ) { + return 0; // default skin + } + return hSkin; + } + } + + // allocate a new skin + if ( tr.numSkins == MAX_SKINS ) { + Com_Printf( "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name ); + return 0; + } + tr.numSkins++; + skin = (struct skin_s *)Hunk_Alloc( sizeof( skin_t ), h_low ); + tr.skins[hSkin] = skin; + Q_strncpyz( skin->name, name, sizeof( skin->name ) ); + skin->numSurfaces = 0; + + // make sure the render thread is stopped + R_SyncRenderThread(); + + // If not a .skin file, load as a single shader + if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { +/* skin->numSurfaces = 1; + skin->surfaces[0] = (skinSurface_t *)Hunk_Alloc( sizeof(skin->surfaces[0]), h_low ); + skin->surfaces[0]->shader = R_FindShader( name, lightmapsNone, stylesDefault, qtrue ); + return hSkin; +*/ + } + + char skinhead[MAX_QPATH]={0}; + char skintorso[MAX_QPATH]={0}; + char skinlower[MAX_QPATH]={0}; + if ( RE_SplitSkins(name, (char*)&skinhead, (char*)&skintorso, (char*)&skinlower ) ) + {//three part + hSkin = RE_RegisterIndividualSkin(skinhead, hSkin); + if (hSkin) + { + hSkin = RE_RegisterIndividualSkin(skintorso, hSkin); + if (hSkin) + { + hSkin = RE_RegisterIndividualSkin(skinlower, hSkin); + } + } + } + else + {//single skin + hSkin = RE_RegisterIndividualSkin(name, hSkin); + } + return(hSkin); +} + + + +/* +================== +CommaParse + +This is unfortunate, but the skin files aren't +compatable with our normal parsing rules. +================== +*/ +static char *CommaParse( char **data_p ) { + int c = 0, len; + char *data; + static char com_token[MAX_TOKEN_CHARS]; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return com_token; + } + + while ( 1 ) { + // skip whitespace + while( (c = *data) <= ' ') { + if( !c ) { + break; + } + data++; + } + + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) + { + while (*data && *data != '\n') + data++; + } + // skip /* */ comments + else if ( c=='/' && data[1] == '*' ) + { + while ( *data && ( *data != '*' || data[1] != '/' ) ) + { + data++; + } + if ( *data ) + { + data += 2; + } + } + else + { + break; + } + } + + if ( c == 0 ) { + return ""; + } + + // handle quoted strings + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = ( char * ) 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 && c != ',' ); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + +/* +=============== +RE_RegisterServerSkin + +Mangled version of the above function to load .skin files on the server. +=============== +*/ +extern qboolean Com_TheHunkMarkHasBeenMade(void); +extern qboolean ShaderHashTableExists(void); +qhandle_t RE_RegisterServerSkin( const char *name ) { + qhandle_t r; + + if (com_cl_running && + com_cl_running->integer && + Com_TheHunkMarkHasBeenMade() && + ShaderHashTableExists()) + { //If the client is running then we can go straight into the normal registerskin func + return RE_RegisterSkin(name); + } + + gServerSkinHack = true; + r = RE_RegisterSkin(name); + gServerSkinHack = false; + + return r; +} + +/* +=============== +R_InitSkins +=============== +*/ +void R_InitSkins( void ) { + skin_t *skin; + + tr.numSkins = 1; + + // make the default skin have all default shaders + skin = tr.skins[0] = (struct skin_s *)/*ri.*/Hunk_Alloc( sizeof( skin_t ), h_low ); + Q_strncpyz( skin->name, "", sizeof( skin->name ) ); + skin->numSurfaces = 1; + skin->surfaces[0] = (skinSurface_t *)/*ri.*/Hunk_Alloc( sizeof( skinSurface_t ), h_low ); + skin->surfaces[0]->shader = tr.defaultShader; +} + +/* +=============== +R_GetSkinByHandle +=============== +*/ +skin_t *R_GetSkinByHandle( qhandle_t hSkin ) { + if ( hSkin < 1 || hSkin >= tr.numSkins ) { + return tr.skins[0]; + } + return tr.skins[ hSkin ]; +} + +#ifndef DEDICATED +/* +=============== +R_SkinList_f +=============== +*/ +void R_SkinList_f( void ) { + int i, j; + skin_t *skin; + + Com_Printf ( "------------------\n"); + + for ( i = 0 ; i < tr.numSkins ; i++ ) { + skin = tr.skins[i]; + + Com_Printf ("%3i:%s\n", i, skin->name ); + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + Com_Printf (" %s = %s\n", + skin->surfaces[j]->name, skin->surfaces[j]->shader->name ); + } + } + Com_Printf ( "------------------\n"); +} + +#endif // !DEDICATED diff --git a/codemp/renderer/tr_image_xbox.cpp b/codemp/renderer/tr_image_xbox.cpp new file mode 100644 index 0000000..73e0770 --- /dev/null +++ b/codemp/renderer/tr_image_xbox.cpp @@ -0,0 +1,2644 @@ +// tr_image.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "tr_local.h" +#include "../qcommon/sstring.h" +#include "../zlib/zlib.h" +#include "../png/png.h" +#include "../qcommon/sstring.h" +#include "../win32/xbox_texture_man.h" +#include "../cgame/cg_local.h" +#include "modelmem.h" + +static byte s_intensitytable[256]; +static unsigned char s_gammatable[256]; + +int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST; +int gl_filter_max = GL_LINEAR; + +#define FILE_HASH_SIZE 1024 // actually, the shader code needs this (from another module, great). +//static image_t* hashTable[FILE_HASH_SIZE]; + +/* +** R_GammaCorrect +*/ +void R_GammaCorrect( byte *buffer, int bufSize ) { + int i; + + for ( i = 0; i < bufSize; i++ ) { + buffer[i] = s_gammatable[buffer[i]]; + } +} + +typedef struct { + char *name; + int minimize, maximize; +} textureMode_t; + +textureMode_t modes[] = { + {"GL_NEAREST", GL_NEAREST, GL_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} +}; + +/* +================ +return a hash value for the filename +================ +*/ +long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +// makeup a nice clean, consistant name to query for and file under, for map<> usage... +// +char *GenerateImageMappingName( const char *name ) +{ + static char sName[MAX_QPATH]; + int i=0; + char letter; + + while (name[i] != '\0' && imipcount ) { + GL_Bind (glt); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + + if(glConfig.maxTextureFilterAnisotropy) { + if(r_ext_texture_filter_anisotropic->integer>1) { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_ext_texture_filter_anisotropic->value); // 2.0f + } else { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + } + } + } + } +} + +static float R_BytesPerTex (int format) +{ + switch ( format ) { + case 1: + //"I " + return 1; + break; + case 2: + //"IA " + return 2; + break; + case 3: + //"RGB " + return glConfig.colorBits/8.0f; + break; + case 4: + //"RGBA " + return glConfig.colorBits/8.0f; + break; + + case GL_RGBA4: + //"RGBA4" + return 2; + break; + case GL_RGB5: + //"RGB5 " + return 2; + break; + + case GL_RGBA8: + //"RGBA8" + return 4; + break; + case GL_RGB8: + //"RGB8" + return 4; + break; + + case GL_RGB4_S3TC: + //"S3TC " + return 0.33333f; + break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + //"DXT1 " + return 0.33333f; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + //"DXT5 " + return 1; + break; + case GL_DDS1_EXT: + //"DDS1 " + return 0.5f; + break; + case GL_DDS5_EXT: + //"DDS5 " + return 1; + break; + case GL_DDS_RGB16_EXT: + //"DDS16" + return 2; + break; + case GL_DDS_RGBA32_EXT: + //"DDS32" + return 4; + break; + default: + //"???? " + return 4; + } +} + +/* +=============== +R_ImageList_f +=============== +*/ +void R_ImageList_f( void ) { + int i=0; + image_t *image; + int texels = 0; + float texBytes = 0.0f; + const char *yesno[] = {"no ", "yes"}; + int curTexels; + unsigned curBytes, cb2; + unsigned slack = 0; + + Com_Printf ( "\n -w-- -h-- -mm- -if-- --size-- surfs --name-------\n"); + + int iNumImages = R_Images_StartIteration(); + while ( (image = R_Images_GetNextIteration()) != NULL) + { + curTexels = image->width * image->height; + texels += curTexels; + + curBytes = curTexels * R_BytesPerTex(image->internalFormat); + if( image->mipcount ) + curBytes *= 1.3; + cb2 = (curBytes + 127) & ~127; + slack += cb2 - curBytes; +// curBytes = (curBytes + 4095) & ~4095; + curBytes = cb2; + texBytes += curBytes; + Com_Printf ( "%4i: %4i %4i %s ", + i, image->width, image->height, yesno[image->mipcount?1:0] ); + switch ( image->internalFormat ) { + case 1: + Com_Printf( "I " ); + break; + case 2: + Com_Printf( "IA " ); + break; + case 3: + Com_Printf( "RGB " ); + break; + case 4: + Com_Printf( "RGBA " ); + break; + case GL_RGBA8: + Com_Printf( "RGBA8" ); + break; + case GL_RGB8: + Com_Printf( "RGB8 " ); + break; + case GL_RGB4_S3TC: + Com_Printf( "S3TC " ); + break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + Com_Printf( "DXT1 " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + Com_Printf( "DXT5 " ); + break; + case GL_RGBA4: + Com_Printf( "RGBA4" ); + break; + case GL_RGB5: + Com_Printf( "RGB5 " ); + break; + case GL_DDS1_EXT: + Com_Printf( "DDS1 " ); + break; + case GL_DDS5_EXT: + Com_Printf( "DDS5 " ); + break; + case GL_DDS_RGB16_EXT: + Com_Printf( "DDS16" ); + break; + case GL_DDS_RGBA32_EXT: + Com_Printf( "DDS32" ); + break; + default: + Com_Printf( "???? " ); + } + + +#ifndef FINAL_BUILD + Com_Printf( " %8u %4i %s\n", curBytes, R_SurfaceImageCount(image), + image->imgName ); +#else + Com_Printf( " %8u %u\n", curBytes, image->imgCode ); +#endif + i++; + } +// Com_Printf (" ---------\n"); +// Com_Printf (" -w-- -h-- -mm- -if- wrap --name-------\n"); + Com_Printf (" %i total texels (not including mipmaps)\n", texels ); + Com_Printf (" %.2fMB total texture mem (not including mipmaps)\n", texBytes/1048576.0f ); + Com_Printf (" %i total images\n\n", iNumImages ); + + Com_Printf("D3D is wasting: %u bytes\n", slack); +} + +//======================================================================= + + +/* +================ +R_LightScaleTexture + +Scale up the pixel values in a texture to increase the +lighting range +================ +*/ +static void R_LightScaleTexture (unsigned *in, int inwidth, int inheight, qboolean only_gamma ) +{ + if ( only_gamma ) + { + if ( !glConfig.deviceSupportsGamma ) + { + int i, c; + byte *p; + + p = (byte *)in; + + c = inwidth*inheight; + for (i=0 ; i> 1; + outHeight = inHeight >> 1; + temp = (unsigned int *) Z_Malloc( outWidth * outHeight * 4, TAG_TEMP_WORKSPACE, qfalse ); + + inWidthMask = inWidth - 1; + inHeightMask = inHeight - 1; + + for ( i = 0 ; i < outHeight ; i++ ) { + for ( j = 0 ; j < outWidth ; j++ ) { + outpix = (byte *) ( temp + i * outWidth + j ); + for ( k = 0 ; k < 4 ; k++ ) { + total = + 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k]; + outpix[k] = total / 36; + } + } + } + + memcpy( in, temp, outWidth * outHeight * 4 ); + Z_Free( temp ); +} + +/* +================ +R_MipMap + +Operates in place, quartering the size of the texture +================ +*/ +static void R_MipMap (byte *in, int width, int height) { + int i, j; + byte *out; + int row; + + if ( width == 1 && height == 1 ) { + return; + } + + if ( !r_simpleMipMaps->integer ) { + R_MipMap2( (unsigned *)in, width, height ); + return; + } + + row = width * 4; + out = in; + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + for (i=0 ; i>1; + out[1] = ( in[1] + in[5] )>>1; + out[2] = ( in[2] + in[6] )>>1; + out[3] = ( in[3] + in[7] )>>1; + } + return; + } + + for (i=0 ; i>2; + out[1] = (in[1] + in[5] + in[row+1] + in[row+5])>>2; + out[2] = (in[2] + in[6] + in[row+2] + in[row+6])>>2; + out[3] = (in[3] + in[7] + in[row+3] + in[row+7])>>2; + } + } +} + + +/* +================== +R_BlendOverTexture + +Apply a color blend over a set of pixels +================== +*/ +static void R_BlendOverTexture( byte *data, int pixelCount, byte blend[4] ) { + int i; + int inverseAlpha; + int premult[3]; + + inverseAlpha = 255 - blend[3]; + premult[0] = blend[0] * blend[3]; + premult[1] = blend[1] * blend[3]; + premult[2] = blend[2] * blend[3]; + + for ( i = 0 ; i < pixelCount ; i++, data+=4 ) { + data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9; + data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9; + data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9; + } +} + +byte mipBlendColors[16][4] = { + {0,0,0,0}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, +}; + + +/* +=============== +Upload32 + +=============== +*/ +static void Upload32( unsigned *data, + int img_width, int img_height, + GLenum format, + int mipcount, + qboolean picmip, + qboolean isLightmap, + int *pformat ) +{ + if (format == GL_RGBA) + { + int samples; + int i, c; + byte *scan; + int width = img_width; + int height = img_height; + + // + // perform optional picmip operation + // + if ( picmip ) { + for(i = 0; i < r_picmip->integer; i++) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + } + } + + // + // clamp to the current upper OpenGL limit + // scale both axis down equally so we don't have to + // deal with a half mip resampling + // + while ( width > glConfig.maxTextureSize || height > glConfig.maxTextureSize ) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + } + + // + // scan the texture for each channel's max values + // and verify if the alpha channel is being used or not + // + c = width*height; + scan = ((byte *)data); + samples = 3; + for ( i = 0; i < c; i++ ) + { + if ( scan[i*4 + 3] != 255 ) + { + samples = 4; + break; + } + } + + // select proper internal format + if ( samples == 3 ) + { + if ( isLightmap && r_texturebitslm->integer > 0 ) + { + // Allow different bit depth when we are a lightmap + if ( r_texturebitslm->integer == 16 ) + { + *pformat = GL_RGB5; + } + else if ( r_texturebitslm->integer == 32 ) + { + *pformat = GL_RGB8; + } + } + else if ( r_texturebits->integer == 16 ) + { + *pformat = GL_RGB5; + } + else if ( r_texturebits->integer == 32 ) + { + *pformat = GL_RGB8; + } + else + { + *pformat = 3; + } + } + else if ( samples == 4 ) + { + if ( r_texturebits->integer == 16 ) + { + *pformat = GL_RGBA4; + } + else if ( r_texturebits->integer == 32 ) + { + *pformat = GL_RGBA8; + } + else + { + *pformat = 4; + } + } + + // copy or resample data as appropriate for first MIP level + if (!mipcount) + { + qglTexImage2D (GL_TEXTURE_2D, 0, *pformat, width, height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, data); + } + else + { + if (mipcount) + { + int miplevel = 0; + int total = 1; + int n = width; + if (height > n) n = height; + while (n > 1) + { + n >>= 1; + ++total; + } + + qglTexImage2DEXT (GL_TEXTURE_2D, 0, total, *pformat, width, height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } + } + else + { + *pformat = format; + + qglTexImage2DEXT (GL_TEXTURE_2D, 0, mipcount, + format, img_width, img_height, 0, format, + GL_UNSIGNED_BYTE, data); + } + + if (mipcount) + { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + if(r_ext_texture_filter_anisotropic->integer>1 && glConfig.maxTextureFilterAnisotropy>0) + { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_ext_texture_filter_anisotropic->value); // 2.0f + } + } + else + { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + GL_CheckErrors(); +} + + +typedef tmap (int, image_t *) AllocatedImages_t; + AllocatedImages_t* AllocatedImages = NULL; + AllocatedImages_t::iterator itAllocatedImages; + +int giTextureBindNum = 1024; // will be set to this anyway at runtime, but wtf? + + +// return = number of images in the list, for those interested +// +int R_Images_StartIteration(void) +{ + if(!AllocatedImages) + return 0; + + itAllocatedImages = AllocatedImages->begin(); + return AllocatedImages->size(); +} + +image_t *R_Images_GetNextIteration(void) +{ + if(!AllocatedImages) + return NULL; + + if (itAllocatedImages == AllocatedImages->end()) + return NULL; + + image_t *pImage = (*itAllocatedImages).second; + ++itAllocatedImages; + return pImage; +} + + +// clean up anything to do with an image_t struct, but caller will have to clear the internal to an image_t struct ready for either struct free() or overwrite... +// +static void R_Images_DeleteImageContents( image_t *pImage ) +{ + assert(pImage); // should never be called with NULL + if (pImage) + { + qglDeleteTextures( 1, &pImage->texnum ); + Z_Free(pImage); + } +} + +static void GL_ResetBinds(void) +{ + memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) ); + if ( qglBindTexture ) + { + if ( qglActiveTextureARB ) + { + GL_SelectTexture( 1 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + GL_SelectTexture( 0 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + } + else + { + qglBindTexture( GL_TEXTURE_2D, 0 ); + } + } +} + +// special function used in conjunction with "devmapbsp"... +// +void R_Images_DeleteLightMaps(void) +{ + assert( 0 && "This function will wreak havoc on texture pool!" ); + qboolean bEraseOccured = qfalse; + for (AllocatedImages_t::iterator itImage = AllocatedImages->begin(); itImage != AllocatedImages->end(); bEraseOccured?itImage:++itImage) + { + bEraseOccured = qfalse; + + image_t *pImage = (*itImage).second; + + if (pImage->isLightmap) + { + R_Images_DeleteImageContents(pImage); + + AllocatedImages->erase(itImage++); + bEraseOccured = qtrue; + } + } + + GL_ResetBinds(); +} + + +// special function currently only called by Dissolve code... +// +void R_Images_DeleteImage(image_t *pImage) +{ + // Even though we supply the image handle, we need to get the corresponding iterator entry... + // + AllocatedImages_t::iterator itImage = AllocatedImages->find(pImage->imgCode); + if (itImage != AllocatedImages->end()) + { + R_Images_DeleteImageContents(pImage); + AllocatedImages->erase(itImage); + } + else + { + assert(0); + } +} + + +// called only at app startup, vid_restart, app-exit +// +void R_Images_Clear(void) +{ + image_t *pImage; + // int iNumImages = + R_Images_StartIteration(); + while ( (pImage = R_Images_GetNextIteration()) != NULL) + { + R_Images_DeleteImageContents(pImage); + } + + if( AllocatedImages ) + { + AllocatedImages->clear(); + delete AllocatedImages; + AllocatedImages = NULL; + } + + giTextureBindNum = 1024; + glw_state->textureBindNum = 1; + + gStaticTextures.Reset(); + gSkinTextures.Reset(); +} + + +void RE_RegisterImages_Info_f( void ) +{ + +} + + +// implement this if you need to, do a find for the caller. I don't need it though, so far. +// +//void RE_RegisterImages_LevelLoadBegin(const char *psMapName); + + +// currently, this just goes through all the images and dumps any not referenced on this level... +// +qboolean RE_RegisterImages_LevelLoadEnd(void) +{ + qboolean bEraseOccured = qfalse; + for (AllocatedImages_t::iterator itImage = AllocatedImages->begin(); itImage != AllocatedImages->end(); bEraseOccured?itImage:++itImage) + { + bEraseOccured = qfalse; + + image_t *pImage = (*itImage).second; + + // don't un-register system shaders (*fog, *dlight, *white, *default), but DO de-register lightmaps ("$/lightmap%d") + if (!pImage->isSystem) + { + // image used on this level? + // + if ( pImage->iLastLevelUsedOn != RE_RegisterMedia_GetLevel() ) + { // nope, so dump it... + assert( 0 && "This will wreak havoc on texture pool!" ); + //Com_Printf( PRINT_DEVELOPER, "Dumping image \"%s\"\n",pImage->imgName); + R_Images_DeleteImageContents(pImage); + itImage = AllocatedImages->erase(itImage); + bEraseOccured = qtrue; + } + } + } + + GL_ResetBinds(); + + return bEraseOccured; +} + + +// returns image_t struct if we already have this, else NULL. No disk-open performed +// (important for creating default images). +// +// This is called by both R_FindImageFile and anything that creates default images... +// +static image_t *R_FindImageFile_NoLoad(const char *name, int mipcount, qboolean allowPicmip, int glWrapClampMode ) +{ + if (!name) { + return NULL; + } + + char *pName = GenerateImageMappingName(name); + + // + // see if the image is already loaded + // + int code = crc32(0, (const Bytef *)pName, strlen(pName)); + AllocatedImages_t::iterator itAllocatedImage = AllocatedImages->find(code); + if (itAllocatedImage != AllocatedImages->end()) + { + image_t *pImage = (*itAllocatedImage).second; + + // the white image can be used with any set of parms, but other mismatches are errors... + // + if ( strcmp( pName, "*white" ) ) { + if ( !pImage->mipcount != !mipcount ) { + // Test is more lax, but also prevents tons of false positives +#ifndef FINAL_BUILD +// Com_Printf( "WARNING: reused image %s with mixed mipmap parm\n", pName ); +#endif + } +// if ( pImage->allowPicmip != !!allowPicmip ) { +// Com_Printf( "WARNING: reused image %s with mixed allowPicmip parm\n", pName ); +// } + } + + pImage->iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); + + return pImage; + } + + return NULL; +} + + +/* +================ +R_CreateImage + +This is the only way any image_t are created +================ +*/ +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, + GLenum format, int mipcount, qboolean allowPicmip, + int glWrapClampMode ) +{ + image_t *image; + qboolean isLightmap = qfalse; + + if (strlen(name) >= MAX_QPATH ) { + Com_Error (ERR_DROP, "R_CreateImage: \"%s\" is too long\n", name); + } + + if(glConfig.clampToEdgeAvailable && glWrapClampMode == GL_CLAMP) { + glWrapClampMode = GL_CLAMP_TO_EDGE; + } + + if (name[0] == '$') + { + isLightmap = qtrue; + } + + if ( (width&(width-1)) || (height&(height-1)) ) + { + Com_Error( ERR_FATAL, "R_CreateImage: %s dimensions (%i x %i) not power of 2!\n",name,width,height); + } + + image = R_FindImageFile_NoLoad(name, mipcount, allowPicmip, glWrapClampMode ); + if (image) { + return image; + } + + image = (image_t*) Z_Malloc( sizeof( image_t ), TAG_IMAGE_T, qtrue ); + + qglGenTextures(1, (GLuint*)&image->texnum); + + image->iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); + + image->mipcount = mipcount; +// image->allowPicmip = allowPicmip; + + image->imgCode = crc32(0, (const Bytef *)name, strlen(name)); +#ifndef FINAL_BUILD + Q_strncpyz(image->imgName, name, sizeof(image->imgName)); +#endif + + image->width = width; + image->height = height; + + image->isSystem = (name[0] == '*'); + image->isLightmap = isLightmap; + + GL_SelectTexture( 0 ); + + GL_Bind(image); + + Upload32( (unsigned *)pic, image->width, image->height, + format, + image->mipcount, + allowPicmip, + isLightmap, + &image->internalFormat ); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode ); + + qglBindTexture( GL_TEXTURE_2D, 0 ); //jfm: i don't know why this is here, but it breaks lightmaps when there's only 1 + glState.currenttextures[glState.currenttmu] = 0; //mark it not bound + + const char* psNewName = GenerateImageMappingName(name); + image->imgCode = crc32(0, (const Bytef *)psNewName, strlen(psNewName)); + + (*AllocatedImages)[ image->imgCode ] = image; + + return image; +} + + +void R_CreateAutomapImage( const char *name, const byte *pic, int width, int height, + qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ) +{ + R_CreateImage(name, pic, width, height, GL_RGBA, mipmap, allowPicmip, glWrapClampMode); +} + +/* +========================================================= + +TARGA LOADING + +========================================================= +*/ + +/* +Ghoul2 Insert Start +*/ +/* +bool LoadTGAPalletteImage ( const char *name, byte **pic, int *width, int *height) +{ + int columns, rows, numPixels; + byte *buf_p; + byte *buffer; + TargaHeader targa_header; + byte *dataStart; + + *pic = NULL; + + // + // load the file + // + FS_ReadFile ( ( char * ) name, (void **)&buffer); + if (!buffer) { + return false; + } + + buf_p = buffer; + + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_length = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_size = *buf_p++; + targa_header.x_origin = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.y_origin = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.width = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.height = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + + if (targa_header.image_type!=1 ) + { + Com_Error (ERR_DROP, "LoadTGAPalletteImage: Only type 1 (uncompressed pallettised) TGA images supported\n"); + } + + if ( targa_header.colormap_type == 0 ) + { + Com_Error( ERR_DROP, "LoadTGAPalletteImage: colormaps ONLY supported\n" ); + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows; + + if (width) + *width = columns; + if (height) + *height = rows; + + *pic = (unsigned char *) Z_Malloc (numPixels, TAG_TEMP_WORKSPACE, qfalse); + if (targa_header.id_length != 0) + { + buf_p += targa_header.id_length; // skip TARGA image comment + } + dataStart = buf_p + (targa_header.colormap_length * (targa_header.colormap_size / 4)); + memcpy(*pic, dataStart, numPixels); + FS_FreeFile (buffer); + + return true; +} +*/ + +/* +Ghoul2 Insert End +*/ + +/* +// My TGA loader... +// +//--------------------------------------------------- +#pragma pack(push,1) +typedef struct +{ + byte byIDFieldLength; // must be 0 + byte byColourmapType; // 0 = truecolour, 1 = paletted, else bad + byte byImageType; // 1 = colour mapped (palette), uncompressed, 2 = truecolour, uncompressed, else bad + word w1stColourMapEntry; // must be 0 + word wColourMapLength; // 256 for 8-bit palettes, else 0 for true-colour + byte byColourMapEntrySize; // 24 for 8-bit palettes, else 0 for true-colour + word wImageXOrigin; // ignored + word wImageYOrigin; // ignored + word wImageWidth; // in pixels + word wImageHeight; // in pixels + byte byImagePlanes; // bits per pixel (8 for paletted, else 24 for true-colour) + byte byScanLineOrder; // Image descriptor bytes + // bits 0-3 = # attr bits (alpha chan) + // bits 4-5 = pixel order/dir + // bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way) +} TGAHeader_t; +#pragma pack(pop) + + +// *pic == pic, else NULL for failed. +// +// returns false if found but had a format error, else true for either OK or not-found (there's a reason for this) +// + +void LoadTGA ( const char *name, byte **pic, int *width, int *height) +{ + char sErrorString[1024]; + bool bFormatErrors = false; + + // these don't need to be declared or initialised until later, but the compiler whines that 'goto' skips them. + // + byte *pRGBA = NULL; + byte *pOut = NULL; + byte *pIn = NULL; + + + *pic = NULL; + +#define TGA_FORMAT_ERROR(blah) {sprintf(sErrorString,blah); bFormatErrors = true; goto TGADone;} +//#define TGA_FORMAT_ERROR(blah) Com_Error( ERR_DROP, blah ); + + // + // load the file + // + byte *pTempLoadedBuffer = 0; + FS_ReadFile ( ( char * ) name, (void **)&pTempLoadedBuffer); + if (!pTempLoadedBuffer) { + return; + } + + TGAHeader_t *pHeader = (TGAHeader_t *) pTempLoadedBuffer; + + if (pHeader->byColourmapType!=0) + { + TGA_FORMAT_ERROR("LoadTGA: colourmaps not supported\n" ); + } + + if (pHeader->byImageType != 2 && pHeader->byImageType != 3 && pHeader->byImageType != 10) + { + TGA_FORMAT_ERROR("LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RLE-RGB) images supported\n"); + } + + if (pHeader->w1stColourMapEntry != 0) + { + TGA_FORMAT_ERROR("LoadTGA: colourmaps not supported\n" ); + } + + if (pHeader->wColourMapLength !=0 && pHeader->wColourMapLength != 256) + { + TGA_FORMAT_ERROR("LoadTGA: ColourMapLength must be either 0 or 256\n" ); + } + + if (pHeader->byColourMapEntrySize != 0 && pHeader->byColourMapEntrySize != 24) + { + TGA_FORMAT_ERROR("LoadTGA: ColourMapEntrySize must be either 0 or 24\n" ); + } + + if ( ( pHeader->byImagePlanes != 24 && pHeader->byImagePlanes != 32) && (pHeader->byImagePlanes != 8 && pHeader->byImageType != 3)) + { + TGA_FORMAT_ERROR("LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n"); + } + + if ((pHeader->byScanLineOrder&0x30)!=0x00 && + (pHeader->byScanLineOrder&0x30)!=0x10 && + (pHeader->byScanLineOrder&0x30)!=0x20 && + (pHeader->byScanLineOrder&0x30)!=0x30 + ) + { + TGA_FORMAT_ERROR("LoadTGA: ScanLineOrder must be either 0x00,0x10,0x20, or 0x30\n"); + } + + + + // these last checks are so i can use ID's RLE-code. I don't dare fiddle with it or it'll probably break... + // + if ( pHeader->byImageType == 10) + { + if ((pHeader->byScanLineOrder & 0x30) != 0x00) + { + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB Images (type 10) must be in bottom-to-top format\n"); + } + if (pHeader->byImagePlanes != 24 && pHeader->byImagePlanes != 32) // probably won't happen, but avoids compressed greyscales? + { + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB Images (type 10) must be 24 or 32 bit\n"); + } + } + + // now read the actual bitmap in... + // + // Image descriptor bytes + // bits 0-3 = # attr bits (alpha chan) + // bits 4-5 = pixel order/dir + // bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way) + // + int iYStart,iXStart,iYStep,iXStep; + + switch(pHeader->byScanLineOrder & 0x30) + { + default: // default case stops the compiler complaining about using uninitialised vars + case 0x00: // left to right, bottom to top + + iXStart = 0; + iXStep = 1; + + iYStart = pHeader->wImageHeight-1; + iYStep = -1; + + break; + + case 0x10: // right to left, bottom to top + + iXStart = pHeader->wImageWidth-1; + iXStep = -1; + + iYStart = pHeader->wImageHeight-1; + iYStep = -1; + + break; + + case 0x20: // left to right, top to bottom + + iXStart = 0; + iXStep = 1; + + iYStart = 0; + iYStep = 1; + + break; + + case 0x30: // right to left, top to bottom + + iXStart = pHeader->wImageWidth-1; + iXStep = -1; + + iYStart = 0; + iYStep = 1; + + break; + } + + // feed back the results... + // + if (width) + *width = pHeader->wImageWidth; + if (height) + *height = pHeader->wImageHeight; + + pRGBA = (byte *) Z_Malloc (pHeader->wImageWidth * pHeader->wImageHeight * 4, TAG_TEMP_WORKSPACE, qfalse); + *pic = pRGBA; + pOut = pRGBA; + pIn = pTempLoadedBuffer + sizeof(*pHeader); + + // I don't know if this ID-thing here is right, since comments that I've seen are at the end of the file, + // with a zero in this field. However, may as well... + // + if (pHeader->byIDFieldLength != 0) + pIn += pHeader->byIDFieldLength; // skip TARGA image comment + + byte red,green,blue,alpha; + + if ( pHeader->byImageType == 2 || pHeader->byImageType == 3 ) // RGB or greyscale + { + for (int y=iYStart, iYCount=0; iYCountwImageHeight; y+=iYStep, iYCount++) + { + pOut = pRGBA + y * pHeader->wImageWidth *4; + for (int x=iXStart, iXCount=0; iXCountwImageWidth; x+=iXStep, iXCount++) + { + switch (pHeader->byImagePlanes) + { + case 8: + blue = *pIn++; + green = blue; + red = blue; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = 255; + break; + + case 24: + blue = *pIn++; + green = *pIn++; + red = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = 255; + break; + + case 32: + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = alpha; + break; + + default: + assert(0); // if we ever hit this, someone deleted a header check higher up + TGA_FORMAT_ERROR("LoadTGA: Image can only have 8, 24 or 32 planes for RGB/greyscale\n"); + break; + } + } + } + } + else + if (pHeader->byImageType == 10) // RLE-RGB + { + // I've no idea if this stuff works, I normally reject RLE targas, but this is from ID's code + // so maybe I should try and support it... + // + byte packetHeader, packetSize, j; + + for (int y = pHeader->wImageHeight-1; y >= 0; y--) + { + pOut = pRGBA + y * pHeader->wImageWidth *4; + for (int x=0; xwImageWidth;) + { + packetHeader = *pIn++; + packetSize = 1 + (packetHeader & 0x7f); + if (packetHeader & 0x80) // run-length packet + { + switch (pHeader->byImagePlanes) + { + case 24: + + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = 255; + break; + + case 32: + + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = *pIn++; + break; + + default: + assert(0); // if we ever hit this, someone deleted a header check higher up + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB can only have 24 or 32 planes\n"); + break; + } + + for (j=0; jwImageWidth) // run spans across rows + { + x = 0; + if (y > 0) + y--; + else + goto breakOut; + pOut = pRGBA + y * pHeader->wImageWidth * 4; + } + } + } + else + { // non run-length packet + + for (j=0; jbyImagePlanes) + { + case 24: + + blue = *pIn++; + green = *pIn++; + red = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = 255; + break; + + case 32: + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = alpha; + break; + + default: + assert(0); // if we ever hit this, someone deleted a header check higher up + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB can only have 24 or 32 planes\n"); + break; + } + x++; + if (x == pHeader->wImageWidth) // pixel packet run spans across rows + { + x = 0; + if (y > 0) + y--; + else + goto breakOut; + pOut = pRGBA + y * pHeader->wImageWidth * 4; + } + } + } + } + breakOut:; + } + } + +TGADone: + + FS_FreeFile (pTempLoadedBuffer); + + if (bFormatErrors) + { + Com_Error( ERR_DROP, "%s( File: \"%s\" )\n",sErrorString,name); + } +} +*/ + +/* +========================================================= + +DDS LOADING + +========================================================= +*/ + +void LoadDDS ( const char *name, byte **pic, int *width, int *height, int *mipcount, GLenum *format ) +{ + fileHandle_t h; + int len = FS_FOpenFileRead( name, &h, qfalse ); + if ( h == 0 ) + { + return; + } + + *pic = (byte*)Z_Malloc( len, TAG_TEMP_WORKSPACE, qfalse , 32); + FS_Read( *pic, len, h ); + FS_FCloseFile( h ); + + DWORD dds = MAKEFOURCC('D', 'D', 'S', ' '); + if (*(DWORD*)(*pic) != dds) + { + FS_FreeFile (*pic); + *pic = NULL; + return; + } + + DDS_HEADER *desc = (DDS_HEADER *)(*pic + sizeof(DWORD)); + DWORD dxt1 = MAKEFOURCC('D', 'X', 'T', '1'); + DWORD dxt5 = MAKEFOURCC('D', 'X', 'T', '5'); + + if (desc->ddspf.dwFourCC == dxt1) + { + *format = GL_DDS1_EXT; + } + else if (desc->ddspf.dwFourCC == dxt5) + { + *format = GL_DDS5_EXT; + } + else if (desc->ddspf.dwRGBBitCount == 16) + { + *format = GL_DDS_RGB16_EXT; + } + else if (desc->ddspf.dwRGBBitCount == 32) + { + *format = GL_DDS_RGBA32_EXT; + } + else + { + FS_FreeFile (*pic); + *pic = NULL; + return; + } + + *width = desc->dwWidth; + *height = desc->dwHeight; + *mipcount = desc->dwMipMapCount; +} + + +//=================================================================== + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. +================= +*/ +void R_LoadImage( const char *shortname, byte **pic, int *width, int *height, int *mipcount, GLenum *format ) { + char name[MAX_QPATH]; + + *pic = NULL; + *width = 0; + *height = 0; + + //handle external LMs + if (shortname[0] == '$') { + Q_strncpyz( name, shortname+1, sizeof( name ) ); + } else { + Q_strncpyz( name, shortname, sizeof( name ) ); + } + + // Try DDS first - saves a ton of failed GOB checks on startup: + COM_StripExtension(name, name); + COM_DefaultExtension(name, sizeof(name), ".dds"); + LoadDDS( name, pic, width, height, mipcount, format ); + if( *pic ) + return; +/* + // OK. Now look for TGA: + *format = GL_RGBA; + *mipcount = 1; + + COM_StripExtension(name, name); + COM_DefaultExtension(name, sizeof(name), ".tga"); + LoadTGA( name, pic, width, height ); + + if (*pic) + { + int j = (*width) * (*height) * 4; + byte *buf = *pic; + byte swap; + for (int i = 0 ; i < j ; i+=4 ) { + swap = buf[i]; + buf[i] = buf[i+2]; + buf[i+2] = swap; + } + return; + } +*/ + + // Return whether or not it worked + return; +} + + +#ifndef _XBOX // Only used for terrain +void R_LoadDataImage( const char *name, byte **pic, int *width, int *height) +{ + int len; + char work[MAX_QPATH]; + + *pic = NULL; + *width = 0; + *height = 0; + + len = strlen(name); + if(len >= MAX_QPATH) + { + return; + } + if (len < 5) + { + return; + } +// MD_PushTag(TAG_DATA_IMAGE_LOAD); + + strcpy(work, name); + + COM_DefaultExtension( work, sizeof( work ), ".png" ); + LoadPNG8( work, pic, width, height ); + + if (!pic || !*pic) + { //both png and jpeg failed, try targa + strcpy(work, name); + COM_DefaultExtension( work, sizeof( work ), ".tga" ); + LoadTGA( work, pic, width, height ); + } + + if(*pic) + { +// MD_PopTag(); + return; + } + // Dataimage loading failed + Com_Printf("Couldn't read %s -- dataimage load failed\n", name); +// MD_PopTag(); +} +#endif + +void R_InvertImage(byte *data, int width, int height, int depth) +{ + byte *newData; + byte *oldData; + byte *saveData; + int y, stride; + + stride = width * depth; + + oldData = data + ((height - 1) * stride); + newData = (byte *)Z_Malloc(height * stride, TAG_TEMP_WORKSPACE, qfalse ); + saveData = newData; + + for(y = 0; y < height; y++) + { + memcpy(newData, oldData, stride); + newData += stride; + oldData -= stride; + } + memcpy(data, saveData, height * stride); + Z_Free(saveData); +} + +// Lanczos3 image resampling. Better than bicubic, based on sin(x)/x algorithm + +#define LANCZOS3 (3.0f) +#define M_PI_OVER_3 (M_PI / 3.0f) + +typedef struct +{ + int pixel; + float weight; +} contrib_t; + +typedef struct +{ + int n; // number of contributors + contrib_t *p; // pointer to list of contributions +} contrib_list_t; + +// sin(x)/x * sin(x/3)/(x/3) + +float Lanczos3(float t) +{ + if(!t) + { + return(1.0f); + } + t = (float)fabs(t); + if(t < 3.0f) + { + return(sinf(t * M_PI) * sinf(t * M_PI_OVER_3) / (t * M_PI * t * M_PI_OVER_3)); + } + return(0.0f); +} + +void R_Resample(byte *source, int swidth, int sheight, byte *dest, int dwidth, int dheight, int components) +{ + int i, j, k, l, count, left, right, num; + int pixel; + byte *raster; + float center, weight, scale, width, height; + contrib_list_t *contributors; + const memtag_t usedTag = TAG_TEMP_WORKSPACE; + + byte *work = (byte *)Z_Malloc(dwidth * sheight * components, usedTag, qfalse); + + // Pre calculate filter contributions for rows + contributors = (contrib_list_t *)Z_Malloc(sizeof(contrib_list_t) * dwidth, usedTag, qfalse); + + float xscale = (float)dwidth / (float)swidth; + + if(xscale < 1.0f) + { + width = ceilf(LANCZOS3 / xscale); + scale = xscale; + } + else + { + width = LANCZOS3; + scale = 1.0f; + } + num = ((int)width * 2) + 1; + + for(i = 0; i < dwidth; i++) + { + contributors[i].n = 0; + contributors[i].p = (contrib_t *)Z_Malloc(num * sizeof(contrib_t), usedTag, qfalse); + + center = (float)i / xscale; + left = (int)ceilf(center - width); + right = (int)floorf(center + width); + + for(j = left; j <= right; j++) + { + weight = Lanczos3((center - (float)j) * scale) * scale; + if(j < 0) + { + pixel = -j; + } + else if(j >= swidth) + { + pixel = (swidth - j) + swidth - 1; + } + else + { + pixel = j; + } + count = contributors[i].n++; + contributors[i].p[count].pixel = pixel; + contributors[i].p[count].weight = weight; + } + } + // Apply filters to zoom horizontally from source to work + for(k = 0; k < sheight; k++) + { + raster = source + (k * swidth * components); + for(i = 0; i < dwidth; i++) + { + for(l = 0; l < components; l++) + { + weight = 0.0f; + for(j = 0; j < contributors[i].n; j++) + { + weight += raster[(contributors[i].p[j].pixel * components) + l] * contributors[i].p[j].weight; + } + pixel = (byte)Com_Clamp(0.0f, 255.0f, weight); + work[(k * dwidth * components) + (i * components) + l] = pixel; + } + } + } + // Clean up + for(i = 0; i < dwidth; i++) + { + Z_Free(contributors[i].p); + } + Z_Free(contributors); + + // Columns + contributors = (contrib_list_t *)Z_Malloc(sizeof(contrib_list_t) * dheight, usedTag, qfalse); + + float yscale = (float)dheight / (float)sheight; + if(yscale < 1.0f) + { + height = ceilf(LANCZOS3 / yscale); + scale = yscale; + } + else + { + height = LANCZOS3; + scale = 1.0f; + } + num = ((int)height * 2) + 1; + + for(i = 0; i < dheight; i++) + { + contributors[i].n = 0; + contributors[i].p = (contrib_t *)Z_Malloc(num * sizeof(contrib_t), usedTag, qfalse); + + center = (float)i / yscale; + left = (int)ceilf(center - height); + right = (int)floorf(center + height); + + for(j = left; j <= right; j++) + { + weight = Lanczos3((center - (float)j) * scale) * scale; + if(j < 0) + { + pixel = -j; + } + else if(j >= sheight) + { + pixel = (sheight - j) + sheight - 1; + } + else + { + pixel = j; + } + count = contributors[i].n++; + contributors[i].p[count].pixel = pixel; + contributors[i].p[count].weight = weight; + } + } + // Apply filter to columns + for(k = 0; k < dwidth; k++) + { + for(l = 0; l < components; l++) + { + for(i = 0; i < dheight; i++) + { + weight = 0.0f; + for(j = 0; j < contributors[i].n; j++) + { + weight += work[(contributors[i].p[j].pixel * dwidth * components) + (k * components) + l] * contributors[i].p[j].weight; + } + pixel = (byte)Com_Clamp(0.0f, 255.0f, weight); + dest[(i * dwidth * components) + (k * components) + l] = pixel; + } + } + } + // Clean up + for(i = 0; i < dheight; i++) + { + Z_Free(contributors[i].p); + } + Z_Free(contributors); + Z_Free(work); + +// MD_PopTag(); +} + +/* +=============== +R_FindImageFile + +Finds or loads the given image. +Returns NULL if it fails, not a default image. +============== +*/ +image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ) { + image_t *image; + int width, height; + int mipcount; + byte *pic; + GLenum format; + + if (!name) { + return NULL; + } + + // need to do this here as well as in R_CreateImage, or R_FindImageFile_NoLoad() may complain about + // different clamp parms used... + // + if(glConfig.clampToEdgeAvailable && glWrapClampMode == GL_CLAMP) { + glWrapClampMode = GL_CLAMP_TO_EDGE; + } + + image = R_FindImageFile_NoLoad(name, mipmap, allowPicmip, glWrapClampMode ); + if (image) { + return image; + } + + // + // load the pic from disk + // + R_LoadImage( name, &pic, &width, &height, &mipcount, &format ); + if ( !pic ) { + return NULL; + } + + image = R_CreateImage( ( char * ) name, pic, width, height, format, mipcount, allowPicmip, glWrapClampMode ); + Z_Free( pic ); + return image; +} + +/* +================ +R_CreateDlightImage +================ +*/ +#define DLIGHT_SIZE 16 +static void R_CreateDlightImage( void ) +{ + int width, height, mipcount; + byte *pic; + GLenum format; + + R_LoadImage("gfx/2d/dlight", &pic, &width, &height, &mipcount, &format); + if (pic) + { + tr.dlightImage = R_CreateImage("*dlight", pic, width, height, GL_DDS5_EXT, mipcount, qfalse, GL_CLAMP ); + Z_Free(pic); + } + else + { // if we dont get a successful load + int x,y; + byte data[DLIGHT_SIZE][DLIGHT_SIZE][4]; + int b; + + // make a centered inverse-square falloff blob for dynamic lighting + for (x=0 ; x 255) { + b = 255; + } else if ( b < 75 ) { + b = 0; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = b; + data[y][x][3] = 255; + } + } + tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, GL_DDS5_EXT, mipcount, qfalse, GL_CLAMP ); + } +} + +/* +================= +R_InitFogTable +================= +*/ +void R_InitFogTable( void ) { + int i; + float d; + float exp; + + exp = 0.5; + + for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) { + d = pow ( (float)i/(FOG_TABLE_SIZE-1), exp ); + + tr.fogTable[i] = d; + } +} + +/* +================ +R_FogFactor + +Returns a 0.0 to 1.0 fog density value +This is called for each texel of the fog texture on startup +and for each vertex of transparent shaders in fog dynamically +================ +*/ +float R_FogFactor( float s, float t ) { + float d; + + s -= 1.0/512; + if ( s < 0 ) { + return 0; + } + if ( t < 1.0/32 ) { + return 0; + } + if ( t < 31.0/32 ) { + s *= (t - 1.0/32) / (30.0/32); + } + + // we need to leave a lot of clamp range + s *= 8; + + if ( s > 1.0 ) { + s = 1.0; + } + + d = tr.fogTable[ (int)(s * (FOG_TABLE_SIZE-1)) ]; + + return d; +} + +/* +================ +R_CreateFogImage +================ +*/ +#define FOG_S 256 +#define FOG_T 32 +static void R_CreateFogImage( void ) { + int x,y; + byte *data; + float g; + float d; + float borderColor[4]; + + data = (byte*) Z_Malloc( FOG_S * FOG_T * 4, TAG_TEMP_WORKSPACE, qfalse ); + + g = 2.0; + + // S is distance, T is depth + for (x=0 ; xinteger; + if ( !glConfig.deviceSupportsGamma ) { + tr.overbrightBits = 0; // need hardware gamma for overbright + } + + // never overbright in windowed mode + if ( !glConfig.isFullscreen ) + { + tr.overbrightBits = 0; + } + + if ( tr.overbrightBits > 1 ) { + tr.overbrightBits = 1; + } + if ( tr.overbrightBits < 0 ) { + tr.overbrightBits = 0; + } + + tr.identityLight = 1.0 / ( 1 << tr.overbrightBits ); + tr.identityLightByte = 255 * tr.identityLight; + + + if ( r_intensity->value < 1.0f ) { + Cvar_Set( "r_intensity", "1.0" ); + } + + if ( r_gamma->value < 0.5f ) { + Cvar_Set( "r_gamma", "0.5" ); + } else if ( r_gamma->value > 3.0f ) { + Cvar_Set( "r_gamma", "3.0" ); + } + + g = r_gamma->value; + + shift = tr.overbrightBits; + + for ( i = 0; i < 256; i++ ) { + if ( g == 1 ) { + inf = i; + } else { + inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f; + } + inf <<= shift; + if (inf < 0) { + inf = 0; + } + if (inf > 255) { + inf = 255; + } + s_gammatable[i] = inf; + } + + for (i=0 ; i<256 ; i++) { + j = i * r_intensity->value; + if (j > 255) { + j = 255; + } + s_intensitytable[i] = j; + } +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) { + //memset(hashTable, 0, sizeof(hashTable)); // DO NOT DO THIS NOW (because of image cacheing) -ste. + if (!AllocatedImages) + { + AllocatedImages = new AllocatedImages_t; + } + + // build brightness translation tables + R_SetColorMappings(); + + // create default texture and white texture + R_CreateBuiltinImages(); +} + +/* +=============== +R_DeleteTextures +=============== +*/ +// (only gets called during vid_restart now (and app exit), not during map load) +// +void R_DeleteTextures( void ) { + + R_Images_Clear(); + GL_ResetBinds(); +} + +/* +============================================================================ + +SKINS + +============================================================================ +*/ + +/* +================== +CommaParse + +This is unfortunate, but the skin files aren't +compatable with our normal parsing rules. +================== +*/ +static char *CommaParse( char **data_p ) { + int c = 0, len; + char *data; + static char com_token[MAX_TOKEN_CHARS]; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return com_token; + } + + while ( 1 ) { + // skip whitespace + while( (c = *data) <= ' ') { + if( !c ) { + break; + } + data++; + } + + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) + { + while (*data && *data != '\n') + data++; + } + // skip /* */ comments + else if ( c=='/' && data[1] == '*' ) + { + while ( *data && ( *data != '*' || data[1] != '/' ) ) + { + data++; + } + if ( *data ) + { + data += 2; + } + } + else + { + break; + } + } + + if ( c == 0 ) { + return ""; + } + + // handle quoted strings + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = ( char * ) 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 && c != ',' ); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + +bool gServerSkinHack = false; + +shader_t *R_FindServerShader( const char *name, const short *lightmapIndex, const byte *styles, qboolean mipRawImage ); + +/* +=============== +RE_SplitSkins +input = skinname, possibly being a macro for three skins +return= true if three part skins found +output= qualified names to three skins if return is true, undefined if false +=============== +*/ +bool RE_SplitSkins(const char *INname, char *skinhead, char *skintorso, char *skinlower) +{ //INname= "models/players/jedi_tf/|head01_skin1|torso01|lower01"; + if (strchr(INname, '|')) + { + char name[MAX_QPATH]; + strcpy(name, INname); + char *p = strchr(name, '|'); + *p=0; + p++; + //fill in the base path + strcpy (skinhead, name); + strcpy (skintorso, name); + strcpy (skinlower, name); + + //now get the the individual files + + //advance to second + char *p2 = strchr(p, '|'); + assert(p2); + *p2=0; + p2++; + strcat (skinhead, p); + strcat (skinhead, ".skin"); + + + //advance to third + p = strchr(p2, '|'); + assert(p); + *p=0; + p++; + strcat (skintorso,p2); + strcat (skintorso, ".skin"); + + strcat (skinlower,p); + strcat (skinlower, ".skin"); + + return true; + } + return false; +} + + +qhandle_t RE_RegisterIndividualSkin( const char *name , qhandle_t hSkin); +/* +=============== +RE_RegisterSkin + +=============== +*/ +qhandle_t RE_RegisterSkin( const char *name) { + qhandle_t hSkin; + skin_t *skin; + + if (!cls.cgameStarted && !cls.uiStarted) + //rww - added uiStarted exception because we want ghoul2 models in the menus. + return 1; // cope with Ghoul2's calling-the-renderer-before-its-even-started hackery, must be any NZ amount here to trigger configstring setting + + if ( !name || !name[0] ) { + Com_Printf( "Empty name passed to RE_RegisterSkin\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Skin name exceeds MAX_QPATH\n" ); + return 0; + } + + if (gServerSkinHack) + { //This can happen on the server. + if (!tr.numSkins) + { //skins haven't been init'd yet, should start off at 1. + R_InitSkins(); + } + } + + // see if the skin is already loaded + for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) { + skin = tr.skins[hSkin]; + if ( !Q_stricmp( skin->name, name ) ) { + if( skin->numSurfaces == 0 ) { + return 0; // default skin + } + return(hSkin); + } + } + + if ( tr.numSkins == MAX_SKINS ) { + Com_Printf( "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name ); + return 0; + } + + //If we're trying to allocate a customized character skin, try to reuse + //one that isn't in use. + skin = NULL; + if(strstr(name, "|")) { + int i, j; + bool found; + + //Loop through all skins and look for one which has a bar. + for(i=0; iname && strstr(tr.skins[i]->name, "|")) { + //Found one. Loop through all clients and see if this skin + //is used. + found = false; + for(j=0; jname, + cgs.clientinfo[j].skinName)) { + found = true; + break; + } + } + + if(strcmp(tr.skins[i]->name, ModelMem.GetUISkin()) == 0) + found = true; + + //Didn't find a match. Reuse this slot. + if(!found) { + skin = tr.skins[i]; + hSkin = i; + break; + } + } + } + } + + // Funky code above couldn't find a skin to reuse? Allocate a new one. + if(!skin) { + tr.numSkins++; + skin = (skin_t*) Hunk_Alloc( sizeof( skin_t ), h_low ); + } else { + //Free old skin pointers. + for(int i=0; inumSurfaces; i++) { + Z_Free(skin->surfaces[i]); + } + memset(skin, 0, sizeof(skin_t)); + } + + + tr.skins[hSkin] = skin; + Q_strncpyz( skin->name, name, sizeof( skin->name ) ); //always make one so it won't search for it again + + // make sure the render thread is stopped + R_SyncRenderThread(); + + // If not a .skin file, load as a single shader - then return + if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { +/* skin->numSurfaces = 1; + skin->surfaces[0] = (skinSurface_t *) Hunk_Alloc( sizeof(skin->surfaces[0]), qtrue ); + skin->surfaces[0]->shader = R_FindShader( name, lightmapsNone, stylesDefault, qtrue ); + return hSkin; +*/ + } + + // Redirect all texture allocations to the swapping pool + bool playerSkin = strstr(name, "models/players"); + if( playerSkin ) + BeginSkinTextures(); + + char skinhead[MAX_QPATH]={0}; + char skintorso[MAX_QPATH]={0}; + char skinlower[MAX_QPATH]={0}; + if ( RE_SplitSkins(name, (char*)&skinhead, (char*)&skintorso, (char*)&skinlower ) ) + {//three part + hSkin = RE_RegisterIndividualSkin(skinhead, hSkin); + if (hSkin) + { + hSkin = RE_RegisterIndividualSkin(skintorso, hSkin); + if (hSkin) + { + hSkin = RE_RegisterIndividualSkin(skinlower, hSkin); + } + } + } + else + {//single skin + hSkin = RE_RegisterIndividualSkin(name, hSkin); + } + + EndSkinTextures(); + + return(hSkin); +} + +// given a name, go get the skin we want and return +qhandle_t RE_RegisterIndividualSkin( const char *name , qhandle_t hSkin) +{ + skin_t *skin; + skinSurface_t *surf; + char *text, *text_p; + char *token; + char surfName[MAX_QPATH]; + + // load and parse the skin file + FS_ReadFile( name, (void **)&text ); + if ( !text ) { + Com_Printf( "WARNING: RE_RegisterSkin( '%s' ) failed to load!\n", name ); + return 0; + } + + assert (tr.skins[hSkin]); //should already be setup, but might be an 3part append + + skin = tr.skins[hSkin]; + + text_p = text; + while ( text_p && *text_p ) { + // get surface name + token = CommaParse( &text_p ); + Q_strncpyz( surfName, token, sizeof( surfName ) ); + + if ( !token[0] ) { + break; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surfName ); + + if ( *text_p == ',' ) { + text_p++; + } + + if ( !strncmp( token, "tag_", 4 ) ) { //these aren't in there, but just in case you load an id style one... + continue; + } + + // parse the shader name + token = CommaParse( &text_p ); + + if ( !strcmp( &surfName[strlen(surfName)-4], "_off") ) + { + if ( !strcmp( token ,"*off" ) ) + { + continue; //don't need these double offs + } + surfName[strlen(surfName)-4] = 0; //remove the "_off" + } + if (sizeof( skin->surfaces) / sizeof( skin->surfaces[0] ) <= skin->numSurfaces) + { + assert( sizeof( skin->surfaces) / sizeof( skin->surfaces[0] ) > skin->numSurfaces ); + Com_Printf( "WARNING: RE_RegisterSkin( '%s' ) more than %d surfaces!\n", name, sizeof( skin->surfaces) / sizeof( skin->surfaces[0] ) ); + break; + } + surf = skin->surfaces[ skin->numSurfaces ] = (skinSurface_t *) Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); + Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); + + if (gServerSkinHack) + { + surf->shader = R_FindServerShader( token, lightmapsNone, stylesDefault, qtrue ); + } + else + { + surf->shader = R_FindShader( token, lightmapsNone, stylesDefault, qtrue ); + } + skin->numSurfaces++; + } + + FS_FreeFile( text ); + + + // never let a skin have 0 shaders + if ( skin->numSurfaces == 0 ) { + return 0; // use default skin + } + + return hSkin; +} + + +/* +=============== +RE_RegisterServerSkin + +Mangled version of the above function to load .skin files on the server. +=============== +*/ +qhandle_t RE_RegisterServerSkin( const char *name ) { + qhandle_t r; + + if (com_cl_running && + com_cl_running->integer) + { //If the client is running then we can go straight into the normal registerskin func + return RE_RegisterSkin(name); + } + + gServerSkinHack = true; + r = RE_RegisterSkin(name); + gServerSkinHack = false; + + return r; +} + + + +/* +=============== +R_InitSkins +=============== +*/ +void R_InitSkins( void ) { + skin_t *skin; + + tr.numSkins = 1; + + // make the default skin have all default shaders + skin = tr.skins[0] = (skin_t*) Hunk_Alloc( sizeof( skin_t ), h_low ); + Q_strncpyz( skin->name, "", sizeof( skin->name ) ); + skin->numSurfaces = 1; + skin->surfaces[0] = (skinSurface_t *) Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); + skin->surfaces[0]->shader = tr.defaultShader; +} + +/* +=============== +R_GetSkinByHandle +=============== +*/ +skin_t *R_GetSkinByHandle( qhandle_t hSkin ) { + if ( hSkin < 1 || hSkin >= tr.numSkins ) { + return tr.skins[0]; + } + return tr.skins[ hSkin ]; +} + +/* +=============== +R_SkinList_f +=============== +*/ +void R_SkinList_f (void) { + int i, j; + skin_t *skin; + + Com_Printf ("------------------\n"); + + for ( i = 0 ; i < tr.numSkins ; i++ ) { + skin = tr.skins[i]; + Com_Printf( "%3i:%s\n", i, skin->name ); + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + Com_Printf( " %s = %s\n", + skin->surfaces[j]->name, skin->surfaces[j]->shader->name ); + } + } + Com_Printf ("------------------\n"); +} diff --git a/codemp/renderer/tr_init.cpp b/codemp/renderer/tr_init.cpp new file mode 100644 index 0000000..c669543 --- /dev/null +++ b/codemp/renderer/tr_init.cpp @@ -0,0 +1,1554 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_init.c -- functions that are not called every frame + +#include "tr_local.h" + +#ifndef DEDICATED +#if !defined __TR_WORLDEFFECTS_H + #include "tr_WorldEffects.h" +#endif +#endif //!DEDICATED + +#include "tr_font.h" + +#if !defined (MINIHEAP_H_INC) + #include "../qcommon/MiniHeap.h" + +#include "../ghoul2/G2_local.h" +#endif + + +//#ifdef __USEA3D +//// Defined in snd_a3dg_refcommon.c +//void RE_A3D_RenderGeometry (void *pVoidA3D, void *pVoidGeom, void *pVoidMat, void *pVoidGeomStatus); +//#endif + +#define G2_VERT_SPACE_SERVER_SIZE 256 +CMiniHeap *G2VertSpaceServer = NULL; +CMiniHeap CMiniHeap_singleton(G2_VERT_SPACE_SERVER_SIZE * 1024); + +#ifndef DEDICATED +glconfig_t glConfig; +glstate_t glState; +static void GfxInfo_f( void ); + +#endif + + +cvar_t *r_verbose; +cvar_t *r_ignore; + +cvar_t *r_displayRefresh; + +cvar_t *r_detailTextures; + +cvar_t *r_znear; + +cvar_t *r_skipBackEnd; + +cvar_t *r_ignorehwgamma; +cvar_t *r_measureOverdraw; + +cvar_t *r_inGameVideo; +cvar_t *r_fastsky; +cvar_t *r_drawSun; +cvar_t *r_dynamiclight; +// rjr - removed for hacking cvar_t *r_dlightBacks; + +cvar_t *r_lodbias; +cvar_t *r_lodscale; +cvar_t *r_autolodscalevalue; + +cvar_t *r_norefresh; +cvar_t *r_drawentities; +cvar_t *r_drawworld; +cvar_t *r_drawfog; +cvar_t *r_speeds; +cvar_t *r_fullbright; +cvar_t *r_novis; +cvar_t *r_nocull; +cvar_t *r_facePlaneCull; +cvar_t *r_cullRoofFaces; //attempted smart method of culling out upwards facing surfaces on roofs for automap shots -rww +cvar_t *r_roofCullCeilDist; //ceiling distance cull tolerance -rww +cvar_t *r_roofCullFloorDist; //floor distance cull tolerance -rww +cvar_t *r_showcluster; +cvar_t *r_nocurves; + +cvar_t *r_autoMap; //automap renderside toggle for debugging -rww +cvar_t *r_autoMapBackAlpha; //alpha of automap bg -rww +cvar_t *r_autoMapDisable; //don't calc it (since it's slow in debug) -rww + +cvar_t *r_dlightStyle; +cvar_t *r_surfaceSprites; +cvar_t *r_surfaceWeather; + +cvar_t *r_windSpeed; +cvar_t *r_windAngle; +cvar_t *r_windGust; +cvar_t *r_windDampFactor; +cvar_t *r_windPointForce; +cvar_t *r_windPointX; +cvar_t *r_windPointY; + +cvar_t *r_allowExtensions; + +cvar_t *r_ext_compressed_textures; +cvar_t *r_ext_compressed_lightmaps; +cvar_t *r_ext_preferred_tc_method; +cvar_t *r_ext_gamma_control; +cvar_t *r_ext_multitexture; +cvar_t *r_ext_compiled_vertex_array; +cvar_t *r_ext_texture_env_add; +cvar_t *r_ext_texture_filter_anisotropic; + +cvar_t *r_DynamicGlow; +cvar_t *r_DynamicGlowPasses; +cvar_t *r_DynamicGlowDelta; +cvar_t *r_DynamicGlowIntensity; +cvar_t *r_DynamicGlowSoft; +cvar_t *r_DynamicGlowWidth; +cvar_t *r_DynamicGlowHeight; + +cvar_t *r_ignoreGLErrors; +cvar_t *r_logFile; + +cvar_t *r_stencilbits; +cvar_t *r_depthbits; +cvar_t *r_colorbits; +cvar_t *r_stereo; +cvar_t *r_primitives; +cvar_t *r_texturebits; +cvar_t *r_texturebitslm; + +cvar_t *r_lightmap; +cvar_t *r_vertexLight; +cvar_t *r_uiFullScreen; +cvar_t *r_shadows; +cvar_t *r_shadowRange; +cvar_t *r_flares; +cvar_t *r_mode; +cvar_t *r_nobind; +cvar_t *r_singleShader; +cvar_t *r_colorMipLevels; +cvar_t *r_picmip; +cvar_t *r_showtris; +cvar_t *r_showsky; +cvar_t *r_shownormals; +cvar_t *r_finish; +cvar_t *r_clear; +cvar_t *r_swapInterval; +cvar_t *r_markcount; +cvar_t *r_textureMode; +cvar_t *r_offsetFactor; +cvar_t *r_offsetUnits; +cvar_t *r_gamma; +#ifdef _XBOX +cvar_t *s_brightness_volume; +#endif +cvar_t *r_intensity; +cvar_t *r_lockpvs; +cvar_t *r_noportals; +cvar_t *r_portalOnly; + +cvar_t *r_subdivisions; +cvar_t *r_lodCurveError; + +cvar_t *r_fullscreen = 0; + +cvar_t *r_customwidth; +cvar_t *r_customheight; + +cvar_t *r_overBrightBits; + +cvar_t *r_debugSurface; +cvar_t *r_simpleMipMaps; + +cvar_t *r_showImages; + +cvar_t *r_ambientScale; +cvar_t *r_directedScale; +cvar_t *r_debugLight; +cvar_t *r_debugSort; + +cvar_t *r_maxpolys; +int max_polys; +cvar_t *r_maxpolyverts; +int max_polyverts; + +cvar_t *r_modelpoolmegs; + +#ifdef _XBOX +cvar_t *r_hdreffect; +cvar_t *r_sundir_x; +cvar_t *r_sundir_y; +cvar_t *r_sundir_z; +cvar_t *r_hdrbloom; +#endif + + +/* +Ghoul2 Insert Start +*/ +#ifdef _DEBUG +cvar_t *r_noPrecacheGLA; +#endif + +cvar_t *r_noServerGhoul2; +cvar_t *r_Ghoul2AnimSmooth=0; +cvar_t *r_Ghoul2UnSqashAfterSmooth=0; +//cvar_t *r_Ghoul2UnSqash; +//cvar_t *r_Ghoul2TimeBase=0; from single player +//cvar_t *r_Ghoul2NoLerp; +//cvar_t *r_Ghoul2NoBlend; +//cvar_t *r_Ghoul2BlendMultiplier=0; + +cvar_t *broadsword=0; +cvar_t *broadsword_kickbones=0; +cvar_t *broadsword_kickorigin=0; +cvar_t *broadsword_playflop=0; +cvar_t *broadsword_dontstopanim=0; +cvar_t *broadsword_waitforshot=0; +cvar_t *broadsword_smallbbox=0; +cvar_t *broadsword_extra1=0; +cvar_t *broadsword_extra2=0; + +cvar_t *broadsword_effcorr=0; +cvar_t *broadsword_ragtobase=0; +cvar_t *broadsword_dircap=0; + +/* +Ghoul2 Insert End +*/ + +#ifndef DEDICATED +void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +void ( APIENTRY * qglLockArraysEXT)( GLint, GLint); +void ( APIENTRY * qglUnlockArraysEXT) ( void ); + +void ( APIENTRY * qglPointParameterfEXT)( GLenum, GLfloat); +void ( APIENTRY * qglPointParameterfvEXT)( GLenum, GLfloat *); + +//3d textures -rww +void ( APIENTRY * qglTexImage3DEXT) (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +void ( APIENTRY * qglTexSubImage3DEXT) (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); + + +#ifndef _XBOX // GLOWXXX +// Declare Register Combiners function pointers. +PFNGLCOMBINERPARAMETERFVNV qglCombinerParameterfvNV = NULL; +PFNGLCOMBINERPARAMETERIVNV qglCombinerParameterivNV = NULL; +PFNGLCOMBINERPARAMETERFNV qglCombinerParameterfNV = NULL; +PFNGLCOMBINERPARAMETERINV qglCombinerParameteriNV = NULL; +PFNGLCOMBINERINPUTNV qglCombinerInputNV = NULL; +PFNGLCOMBINEROUTPUTNV qglCombinerOutputNV = NULL; +PFNGLFINALCOMBINERINPUTNV qglFinalCombinerInputNV = NULL; +PFNGLGETCOMBINERINPUTPARAMETERFVNV qglGetCombinerInputParameterfvNV = NULL; +PFNGLGETCOMBINERINPUTPARAMETERIVNV qglGetCombinerInputParameterivNV = NULL; +PFNGLGETCOMBINEROUTPUTPARAMETERFVNV qglGetCombinerOutputParameterfvNV = NULL; +PFNGLGETCOMBINEROUTPUTPARAMETERIVNV qglGetCombinerOutputParameterivNV = NULL; +PFNGLGETFINALCOMBINERINPUTPARAMETERFVNV qglGetFinalCombinerInputParameterfvNV = NULL; +PFNGLGETFINALCOMBINERINPUTPARAMETERIVNV qglGetFinalCombinerInputParameterivNV = NULL; + +// Declare Pixel Format function pointers. +PFNWGLGETPIXELFORMATATTRIBIVARBPROC qwglGetPixelFormatAttribivARB = NULL; +PFNWGLGETPIXELFORMATATTRIBFVARBPROC qwglGetPixelFormatAttribfvARB = NULL; +PFNWGLCHOOSEPIXELFORMATARBPROC qwglChoosePixelFormatARB = NULL; + +// Declare Pixel Buffer function pointers. +PFNWGLCREATEPBUFFERARBPROC qwglCreatePbufferARB = NULL; +PFNWGLGETPBUFFERDCARBPROC qwglGetPbufferDCARB = NULL; +PFNWGLRELEASEPBUFFERDCARBPROC qwglReleasePbufferDCARB = NULL; +PFNWGLDESTROYPBUFFERARBPROC qwglDestroyPbufferARB = NULL; +PFNWGLQUERYPBUFFERARBPROC qwglQueryPbufferARB = NULL; + +// Declare Render-Texture function pointers. +PFNWGLBINDTEXIMAGEARBPROC qwglBindTexImageARB = NULL; +PFNWGLRELEASETEXIMAGEARBPROC qwglReleaseTexImageARB = NULL; +PFNWGLSETPBUFFERATTRIBARBPROC qwglSetPbufferAttribARB = NULL; + +// Declare Vertex and Fragment Program function pointers. +PFNGLPROGRAMSTRINGARBPROC qglProgramStringARB = NULL; +PFNGLBINDPROGRAMARBPROC qglBindProgramARB = NULL; +PFNGLDELETEPROGRAMSARBPROC qglDeleteProgramsARB = NULL; +PFNGLGENPROGRAMSARBPROC qglGenProgramsARB = NULL; +PFNGLPROGRAMENVPARAMETER4DARBPROC qglProgramEnvParameter4dARB = NULL; +PFNGLPROGRAMENVPARAMETER4DVARBPROC qglProgramEnvParameter4dvARB = NULL; +PFNGLPROGRAMENVPARAMETER4FARBPROC qglProgramEnvParameter4fARB = NULL; +PFNGLPROGRAMENVPARAMETER4FVARBPROC qglProgramEnvParameter4fvARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4DARBPROC qglProgramLocalParameter4dARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4DVARBPROC qglProgramLocalParameter4dvARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4FARBPROC qglProgramLocalParameter4fARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4FVARBPROC qglProgramLocalParameter4fvARB = NULL; +PFNGLGETPROGRAMENVPARAMETERDVARBPROC qglGetProgramEnvParameterdvARB = NULL; +PFNGLGETPROGRAMENVPARAMETERFVARBPROC qglGetProgramEnvParameterfvARB = NULL; +PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC qglGetProgramLocalParameterdvARB = NULL; +PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC qglGetProgramLocalParameterfvARB = NULL; +PFNGLGETPROGRAMIVARBPROC qglGetProgramivARB = NULL; +PFNGLGETPROGRAMSTRINGARBPROC qglGetProgramStringARB = NULL; +PFNGLISPROGRAMARBPROC qglIsProgramARB = NULL; +#endif + + +void RE_SetLightStyle(int style, int color); + +void RE_GetBModelVerts( int bmodelIndex, vec3_t *verts, vec3_t normal ); + +#endif // !DEDICATED + +static void AssertCvarRange( cvar_t *cv, float minVal, float maxVal, qboolean shouldBeIntegral ) +{ + if ( shouldBeIntegral ) + { + if ( ( int ) cv->value != cv->integer ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: cvar '%s' must be integral (%f)\n", cv->name, cv->value ); + Cvar_Set( cv->name, va( "%d", cv->integer ) ); + } + } + + if ( cv->value < minVal ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: cvar '%s' out of range (%f < %f)\n", cv->name, cv->value, minVal ); + Cvar_Set( cv->name, va( "%f", minVal ) ); + } + else if ( cv->value > maxVal ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: cvar '%s' out of range (%f > %f)\n", cv->name, cv->value, maxVal ); + Cvar_Set( cv->name, va( "%f", maxVal ) ); + } +} + +#ifndef DEDICATED + +void R_Splash() +{ +#ifndef _XBOX + image_t *pImage; +/* const char* s = Cvar_VariableString("se_language"); + if (stricmp(s,"english")) + { + pImage = R_FindImageFile( "menu/splash_eur", qfalse, qfalse, qfalse, GL_CLAMP); + } + else + { + pImage = R_FindImageFile( "menu/splash", qfalse, qfalse, qfalse, GL_CLAMP); + } +*/ + pImage = R_FindImageFile( "menu/splash", qfalse, qfalse, qfalse, GL_CLAMP); + extern void RB_SetGL2D (void); + RB_SetGL2D(); + if (pImage ) + {//invalid paths? + GL_Bind( pImage ); + } + GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO); + +#ifdef _XBOX + int width; + if(glw_state->isWidescreen) + width = 720; + else + width = 640; +#else + const int width = 640; +#endif + const int height = 480; +#ifdef _XBOX + float x1, x2; + if(glw_state->isWidescreen) + { + x1 = 360 - width / 2; + x2 = 360 + width / 2; + } + else + { + x1 = 320 - width / 2; + x2 = 320 + width / 2; + } +#else + const float x1 = 320 - width / 2; + const float x2 = 320 + width / 2; +#endif + const float y1 = 240 - height / 2; + const float y2 = 240 + height / 2; + + + qglBegin (GL_TRIANGLE_STRIP); + qglTexCoord2f( 0, 0 ); + qglVertex2f(x1, y1); + qglTexCoord2f( 1 , 0 ); + qglVertex2f(x2, y1); + qglTexCoord2f( 0, 1 ); + qglVertex2f(x1, y2); + qglTexCoord2f( 1, 1 ); + qglVertex2f(x2, y2); + qglEnd(); + + GLimp_EndFrame(); +#endif +} + +/* +** InitOpenGL +** +** This function is responsible for initializing a valid OpenGL subsystem. This +** is done by calling GLimp_Init (which gives us a working OGL subsystem) then +** setting variables, checking GL constants, and reporting the gfx system config +** to the user. +*/ +static void InitOpenGL( void ) +{ + // + // initialize OS specific portions of the renderer + // + // GLimp_Init directly or indirectly references the following cvars: + // - r_fullscreen + // - r_mode + // - r_(color|depth|stencil)bits + // - r_ignorehwgamma + // - r_gamma + // + + if ( glConfig.vidWidth == 0 ) + { + GLimp_Init(); + // print info the first time only + GL_SetDefaultState(); + R_Splash(); //get something on screen asap + GfxInfo_f(); + } + else + { + // set default state + GL_SetDefaultState(); + } + // init command buffers and SMP + R_InitCommandBuffers(); +} + +/* +================== +GL_CheckErrors +================== +*/ +void GL_CheckErrors( void ) { + int err; + char s[64]; + + err = qglGetError(); + if ( err == GL_NO_ERROR ) { + return; + } + if ( r_ignoreGLErrors->integer ) { + return; + } + switch( err ) { + case GL_INVALID_ENUM: + strcpy( s, "GL_INVALID_ENUM" ); + break; + case GL_INVALID_VALUE: + strcpy( s, "GL_INVALID_VALUE" ); + break; + case GL_INVALID_OPERATION: + strcpy( s, "GL_INVALID_OPERATION" ); + break; + case GL_STACK_OVERFLOW: + strcpy( s, "GL_STACK_OVERFLOW" ); + break; + case GL_STACK_UNDERFLOW: + strcpy( s, "GL_STACK_UNDERFLOW" ); + break; + case GL_OUT_OF_MEMORY: + strcpy( s, "GL_OUT_OF_MEMORY" ); + break; + default: + Com_sprintf( s, sizeof(s), "%i", err); + break; + } + + Com_Error( ERR_FATAL, "GL_CheckErrors: %s", s ); +} + +#endif //!DEDICATED + +#ifndef _XBOX + +/* +** R_GetModeInfo +*/ +typedef struct vidmode_s +{ + const char *description; + int width, height; +} vidmode_t; + +const vidmode_t r_vidModes[] = +{ + { "Mode 0: 320x240", 320, 240 }, + { "Mode 1: 400x300", 400, 300 }, + { "Mode 2: 512x384", 512, 384 }, + { "Mode 3: 640x480", 640, 480 }, + { "Mode 4: 800x600", 800, 600 }, + { "Mode 5: 960x720", 960, 720 }, + { "Mode 6: 1024x768", 1024, 768 }, + { "Mode 7: 1152x864", 1152, 864 }, + { "Mode 8: 1280x1024", 1280, 1024 }, + { "Mode 9: 1600x1200", 1600, 1200 }, + { "Mode 10: 2048x1536", 2048, 1536 }, + { "Mode 11: 856x480 (wide)", 856, 480 }, + { "Mode 12: 2400x600(surround)",2400,600 } +}; +static const int s_numVidModes = ( sizeof( r_vidModes ) / sizeof( r_vidModes[0] ) ); + +qboolean R_GetModeInfo( int *width, int *height, int mode ) { + const vidmode_t *vm; + + if ( mode < -1 ) { + return qfalse; + } + if ( mode >= s_numVidModes ) { + return qfalse; + } + + if ( mode == -1 ) { + *width = r_customwidth->integer; + *height = r_customheight->integer; + return qtrue; + } + + vm = &r_vidModes[mode]; + + *width = vm->width; + *height = vm->height; + + return qtrue; +} + +/* +** R_ModeList_f +*/ +static void R_ModeList_f( void ) +{ + int i; + + Com_Printf ("\n" ); + for ( i = 0; i < s_numVidModes; i++ ) + { + Com_Printf ("%s\n", r_vidModes[i].description ); + } + Com_Printf ("\n" ); +} + +#endif // _XBOX + +/* +============================================================================== + + SCREEN SHOTS + +============================================================================== +*/ +#ifndef DEDICATED +/* +================== +R_TakeScreenshot +================== +*/ +void R_TakeScreenshot( int x, int y, int width, int height, char *fileName ) { +#ifndef _XBOX + byte *buffer; + int i, c, temp; + + buffer = (unsigned char *)Hunk_AllocateTempMemory(glConfig.vidWidth*glConfig.vidHeight*3+18); + + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + qglReadPixels( x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer+18 ); + + // swap rgb to bgr + c = 18 + width * height * 3; + for (i=18 ; i 0 ) && glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer + 18, glConfig.vidWidth * glConfig.vidHeight * 3 ); + } + + FS_WriteFile( fileName, buffer, c ); + + Hunk_FreeTempMemory( buffer ); +#endif +} + +/* +================== +R_TakeScreenshot +================== +*/ +void R_TakeScreenshotJPEG( int x, int y, int width, int height, char *fileName ) { +#ifndef _XBOX + byte *buffer; + + buffer = (unsigned char *)Hunk_AllocateTempMemory(glConfig.vidWidth*glConfig.vidHeight*4); + + qglReadPixels( x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer ); + + // gamma correct + if ( ( tr.overbrightBits > 0 ) && glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer, glConfig.vidWidth * glConfig.vidHeight * 4 ); + } + + FS_WriteFile( fileName, buffer, 1 ); // create path + SaveJPG( fileName, 95, glConfig.vidWidth, glConfig.vidHeight, buffer); + + Hunk_FreeTempMemory( buffer ); +#endif +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilename( int lastNumber, char *fileName, const char *psExt ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999%s",psExt ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a*1000; + b = lastNumber / 100; + lastNumber -= b*100; + c = lastNumber / 10; + lastNumber -= c*10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i%s" + , a, b, c, d, psExt ); +} + +/* +==================== +R_LevelShot + +levelshots are specialized 256*256 thumbnails for +the menu system, sampled down from full screen distorted images +==================== +*/ +#define LEVELSHOTSIZE 256 +static void R_LevelShot( void ) { +#ifndef _XBOX + char checkname[MAX_OSPATH]; + byte *buffer; + byte *source; + byte *src, *dst; + int x, y; + int r, g, b; + float xScale, yScale; + int xx, yy; + + sprintf( checkname, "levelshots/%s.tga", tr.world->baseName ); + + source = (unsigned char *)Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight * 3 ); + + buffer = (unsigned char *)Hunk_AllocateTempMemory( LEVELSHOTSIZE * LEVELSHOTSIZE*3 + 18); + Com_Memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = LEVELSHOTSIZE & 255; + buffer[13] = LEVELSHOTSIZE >> 8; + buffer[14] = LEVELSHOTSIZE & 255; + buffer[15] = LEVELSHOTSIZE >> 8; + buffer[16] = 24; // pixel size + + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGB, GL_UNSIGNED_BYTE, source ); + + // resample from source + xScale = glConfig.vidWidth / (4.0*LEVELSHOTSIZE); + yScale = glConfig.vidHeight / (3.0*LEVELSHOTSIZE); + for ( y = 0 ; y < LEVELSHOTSIZE ; y++ ) { + for ( x = 0 ; x < LEVELSHOTSIZE ; x++ ) { + r = g = b = 0; + for ( yy = 0 ; yy < 3 ; yy++ ) { + for ( xx = 0 ; xx < 4 ; xx++ ) { + src = source + 3 * ( glConfig.vidWidth * (int)( (y*3+yy)*yScale ) + (int)( (x*4+xx)*xScale ) ); + r += src[0]; + g += src[1]; + b += src[2]; + } + } + dst = buffer + 18 + 3 * ( y * LEVELSHOTSIZE + x ); + dst[0] = b / 12; + dst[1] = g / 12; + dst[2] = r / 12; + } + } + + // gamma correct + if ( ( tr.overbrightBits > 0 ) && glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer + 18, LEVELSHOTSIZE * LEVELSHOTSIZE * 3 ); + } + + FS_WriteFile( checkname, buffer, LEVELSHOTSIZE * LEVELSHOTSIZE*3 + 18 ); + + Hunk_FreeTempMemory( buffer ); + Hunk_FreeTempMemory( source ); + + Com_Printf ("Wrote %s\n", checkname ); +#endif +} + +/* +================== +R_ScreenShotTGA_f + +screenshot +screenshot [silent] +screenshot [levelshot] +screenshot [filename] + +Doesn't print the pacifier message if there is a second arg +================== +*/ +void R_ScreenShotTGA_f (void) { +#ifndef _XBOX + char checkname[MAX_OSPATH]; + static int lastNumber = -1; + qboolean silent; + + if ( !strcmp( Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + + if ( !strcmp( Cmd_Argv(1), "silent" ) ) { + silent = qtrue; + } else { + silent = qfalse; + } + + if ( Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.tga", Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilename( lastNumber, checkname, ".tga" ); + + if (!FS_FileExists( checkname )) + { + break; // file doesn't exist + } + } + + if ( lastNumber >= 9999 ) { + Com_Printf ( "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname ); + + if ( !silent ) { + Com_Printf ( "Wrote %s\n", checkname); + } +#endif +} + +//jpeg vession +void R_ScreenShot_f (void) { +#ifndef _XBOX + char checkname[MAX_OSPATH]; + static int lastNumber = -1; + qboolean silent; + + if ( !strcmp( Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + if ( !strcmp( Cmd_Argv(1), "silent" ) ) { + silent = qtrue; + } else { + silent = qfalse; + } + + if ( Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.jpg", Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + lastNumber = 0; + } + // scan for a free number + for ( ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilename( lastNumber, checkname, ".jpg" ); + + if (!FS_FileExists( checkname )) + { + break; // file doesn't exist + } + } + + if ( lastNumber == 10000 ) { + Com_Printf ( "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + + R_TakeScreenshotJPEG( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname ); + + if ( !silent ) { + Com_Printf ( "Wrote %s\n", checkname); + } +#endif +} + +//============================================================================ + +/* +** GL_SetDefaultState +*/ +void GL_SetDefaultState( void ) +{ + qglClearDepth( 1.0f ); + + qglCullFace(GL_FRONT); + + qglColor4f (1,1,1,1); + + // initialize downstream texture unit if we're running + // in a multitexture environment + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + qglDisable( GL_TEXTURE_2D ); + GL_SelectTexture( 0 ); + } + + qglEnable(GL_TEXTURE_2D); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + + qglShadeModel( GL_SMOOTH ); + qglDepthFunc( GL_LEQUAL ); + + // the vertex array is always enabled, but the color and texture + // arrays are enabled and disabled around the compiled vertex array call + qglEnableClientState (GL_VERTEX_ARRAY); + + // + // make sure our GL state vector is set correctly + // + glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE; + + qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + qglDepthMask( GL_TRUE ); + qglDisable( GL_DEPTH_TEST ); + qglEnable( GL_SCISSOR_TEST ); + qglDisable( GL_CULL_FACE ); + qglDisable( GL_BLEND ); +#ifdef _XBOX + qglDisable( GL_LIGHTING ); +#endif +} + + +/* +================ +GfxInfo_f +================ +*/ +void GfxInfo_f( void ) +{ + cvar_t *sys_cpustring = Cvar_Get( "sys_cpustring", "", CVAR_ROM ); + const char *enablestrings[] = + { + "disabled", + "enabled" + }; + const char *fsstrings[] = + { + "windowed", + "fullscreen" + }; + + const char *tc_table[] = + { + "None", + "GL_S3_s3tc", + "GL_EXT_texture_compression_s3tc", + }; + + Com_Printf ("\nGL_VENDOR: %s\n", glConfig.vendor_string ); + Com_Printf ("GL_RENDERER: %s\n", glConfig.renderer_string ); + Com_Printf ("GL_VERSION: %s\n", glConfig.version_string ); + Com_Printf ("GL_EXTENSIONS: %s\n", glConfig.extensions_string ); + Com_Printf ("GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); + Com_Printf ("GL_MAX_ACTIVE_TEXTURES_ARB: %d\n", glConfig.maxActiveTextures ); + Com_Printf ("\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits ); + Com_Printf ("MODE: %d, %d x %d %s hz:", r_mode->integer, glConfig.vidWidth, glConfig.vidHeight, fsstrings[r_fullscreen->integer == 1] ); + if ( glConfig.displayFrequency ) + { + Com_Printf ("%d\n", glConfig.displayFrequency ); + } + else + { + Com_Printf ("N/A\n" ); + } + if ( glConfig.deviceSupportsGamma ) + { + Com_Printf ("GAMMA: hardware w/ %d overbright bits\n", tr.overbrightBits ); + } + else + { + Com_Printf ("GAMMA: software w/ %d overbright bits\n", tr.overbrightBits ); + } + Com_Printf ("CPU: %s @ %s MHz\n", sys_cpustring->string, Cvar_VariableString("sys_cpuspeed") ); + + // rendering primitives + { + int primitives; + + // default is to use triangles if compiled vertex arrays are present + Com_Printf ("rendering primitives: " ); + primitives = r_primitives->integer; + if ( primitives == 0 ) { + if ( qglLockArraysEXT ) { + primitives = 2; + } else { + primitives = 1; + } + } + if ( primitives == -1 ) { + Com_Printf ("none\n" ); + } else if ( primitives == 2 ) { + Com_Printf ("single glDrawElements\n" ); + } else if ( primitives == 1 ) { + Com_Printf ("multiple glArrayElement\n" ); + } else if ( primitives == 3 ) { + Com_Printf ("multiple glColor4ubv + glTexCoord2fv + glVertex3fv\n" ); + } + } + + Com_Printf ("texturemode: %s\n", r_textureMode->string ); + Com_Printf ("picmip: %d\n", r_picmip->integer ); + Com_Printf ("texture bits: %d\n", r_texturebits->integer ); + Com_Printf ("lightmap texture bits: %d\n", r_texturebitslm->integer ); + Com_Printf ("multitexture: %s\n", enablestrings[qglActiveTextureARB != 0] ); + Com_Printf ("compiled vertex arrays: %s\n", enablestrings[qglLockArraysEXT != 0 ] ); + Com_Printf ("texenv add: %s\n", enablestrings[glConfig.textureEnvAddAvailable != 0] ); + Com_Printf ("compressed textures: %s\n", enablestrings[glConfig.textureCompression != TC_NONE] ); + Com_Printf ("compressed lightmaps: %s\n", enablestrings[(r_ext_compressed_lightmaps->integer != 0 && glConfig.textureCompression != TC_NONE)] ); + Com_Printf ("texture compression method: %s\n", tc_table[glConfig.textureCompression] ); + Com_Printf ("anisotropic filtering: %s ", enablestrings[(r_ext_texture_filter_anisotropic->integer != 0) && glConfig.maxTextureFilterAnisotropy] ); + Com_Printf ("(%f of %f)\n", r_ext_texture_filter_anisotropic->value, glConfig.maxTextureFilterAnisotropy ); + Com_Printf ("Dynamic Glow: %s\n", enablestrings[r_DynamicGlow->integer] ); + + if ( r_finish->integer ) { + Com_Printf ("Forcing glFinish\n" ); + } + if ( r_displayRefresh ->integer ) { + Com_Printf ("Display refresh set to %d\n", r_displayRefresh->integer ); + } + if (tr.world) + { + Com_Printf ("Light Grid size set to (%.2f %.2f %.2f)\n", tr.world->lightGridSize[0], tr.world->lightGridSize[1], tr.world->lightGridSize[2] ); + } +} + +#endif // !DEDICATED +/* +=============== +R_Register +=============== +*/ +void R_Register( void ) +{ + // + // latched and archived variables + // + r_allowExtensions = Cvar_Get( "r_allowExtensions", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compressed_textures = Cvar_Get( "r_ext_compress_textures", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compressed_lightmaps = Cvar_Get( "r_ext_compress_lightmaps", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_preferred_tc_method = Cvar_Get( "r_ext_preferred_tc_method", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_gamma_control = Cvar_Get( "r_ext_gamma_control", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multitexture = Cvar_Get( "r_ext_multitexture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compiled_vertex_array = Cvar_Get( "r_ext_compiled_vertex_array", "1", CVAR_ARCHIVE | CVAR_LATCH); +#ifdef __linux__ // broken on linux + r_ext_texture_env_add = Cvar_Get( "r_ext_texture_env_add", "0", CVAR_ARCHIVE | CVAR_LATCH); +#else + r_ext_texture_env_add = Cvar_Get( "r_ext_texture_env_add", "1", CVAR_ARCHIVE | CVAR_LATCH); +#endif + r_ext_texture_filter_anisotropic = Cvar_Get( "r_ext_texture_filter_anisotropic", "16", CVAR_ARCHIVE ); + + r_DynamicGlow = Cvar_Get( "r_DynamicGlow", "1", CVAR_ARCHIVE ); + r_DynamicGlowPasses = Cvar_Get( "r_DynamicGlowPasses", "5", CVAR_CHEAT ); + r_DynamicGlowDelta = Cvar_Get( "r_DynamicGlowDelta", "0.8f", CVAR_CHEAT ); + r_DynamicGlowIntensity = Cvar_Get( "r_DynamicGlowIntensity", "1.13f", CVAR_CHEAT ); + r_DynamicGlowSoft = Cvar_Get( "r_DynamicGlowSoft", "1", CVAR_CHEAT ); + r_DynamicGlowWidth = Cvar_Get( "r_DynamicGlowWidth", "320", CVAR_CHEAT | CVAR_LATCH ); + r_DynamicGlowHeight = Cvar_Get( "r_DynamicGlowHeight", "240", CVAR_CHEAT | CVAR_LATCH ); + + r_picmip = Cvar_Get ("r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorMipLevels = Cvar_Get ("r_colorMipLevels", "0", CVAR_LATCH ); + AssertCvarRange( r_picmip, 0, 16, qtrue ); + r_detailTextures = Cvar_Get( "r_detailtextures", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_texturebits = Cvar_Get( "r_texturebits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_texturebitslm = Cvar_Get( "r_texturebitslm", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorbits = Cvar_Get( "r_colorbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_stereo = Cvar_Get( "r_stereo", "0", CVAR_ARCHIVE | CVAR_LATCH ); +#ifdef __linux__ + r_stencilbits = Cvar_Get( "r_stencilbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); +#else + r_stencilbits = Cvar_Get( "r_stencilbits", "8", CVAR_ARCHIVE | CVAR_LATCH ); +#endif + r_depthbits = Cvar_Get( "r_depthbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_overBrightBits = Cvar_Get ("r_overBrightBits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ignorehwgamma = Cvar_Get( "r_ignorehwgamma", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_mode = Cvar_Get( "r_mode", "4", CVAR_ARCHIVE | CVAR_LATCH ); + r_fullscreen = Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_customwidth = Cvar_Get( "r_customwidth", "1600", CVAR_ARCHIVE | CVAR_LATCH ); + r_customheight = Cvar_Get( "r_customheight", "1024", CVAR_ARCHIVE | CVAR_LATCH ); + r_simpleMipMaps = Cvar_Get( "r_simpleMipMaps", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_vertexLight = Cvar_Get( "r_vertexLight", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_uiFullScreen = Cvar_Get( "r_uifullscreen", "0", 0); + r_subdivisions = Cvar_Get ("r_subdivisions", "4", CVAR_ARCHIVE | CVAR_LATCH); + + // + // temporary latched variables that can only change over a restart + // + r_displayRefresh = Cvar_Get( "r_displayRefresh", "0", CVAR_LATCH ); + AssertCvarRange( r_displayRefresh, 0, 200, qtrue ); + r_fullbright = Cvar_Get ("r_fullbright", "0", CVAR_CHEAT ); + r_intensity = Cvar_Get ("r_intensity", "1", CVAR_LATCH ); + r_singleShader = Cvar_Get ("r_singleShader", "0", CVAR_CHEAT | CVAR_LATCH ); + + // + // archived variables that can change at any time + // + r_lodCurveError = Cvar_Get( "r_lodCurveError", "250", CVAR_ARCHIVE ); + r_lodbias = Cvar_Get( "r_lodbias", "0", CVAR_ARCHIVE ); + r_autolodscalevalue = Cvar_Get( "r_autolodscalevalue", "0", CVAR_ROM ); + + r_flares = Cvar_Get ("r_flares", "1", CVAR_ARCHIVE ); +#ifdef _XBOX + r_znear = Cvar_Get( "r_znear", "2", CVAR_CHEAT ); +#else + r_znear = Cvar_Get( "r_znear", "4", CVAR_CHEAT ); +#endif + AssertCvarRange( r_znear, 0.001f, 200, qtrue ); + r_ignoreGLErrors = Cvar_Get( "r_ignoreGLErrors", "1", CVAR_ARCHIVE ); + r_fastsky = Cvar_Get( "r_fastsky", "0", CVAR_ARCHIVE ); + r_inGameVideo = Cvar_Get( "r_inGameVideo", "1", CVAR_ARCHIVE ); + r_drawSun = Cvar_Get( "r_drawSun", "0", CVAR_ARCHIVE ); + r_dynamiclight = Cvar_Get( "r_dynamiclight", "1", CVAR_ARCHIVE ); +// rjr - removed for hacking r_dlightBacks = Cvar_Get( "r_dlightBacks", "1", CVAR_CHEAT ); + r_finish = Cvar_Get ("r_finish", "0", CVAR_ARCHIVE); + r_textureMode = Cvar_Get( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST", CVAR_ARCHIVE ); + r_swapInterval = Cvar_Get( "r_swapInterval", "0", CVAR_ARCHIVE ); + r_markcount = Cvar_Get( "r_markcount", "100", CVAR_ARCHIVE ); +#ifdef __MACOS__ + r_gamma = Cvar_Get( "r_gamma", "1.2", CVAR_ARCHIVE ); +#else + r_gamma = Cvar_Get( "r_gamma", "1", CVAR_ARCHIVE ); +#endif + +#ifdef _XBOX + s_brightness_volume = Cvar_Get( "s_brightness_volume", "1", CVAR_ARCHIVE ); +#endif + + r_facePlaneCull = Cvar_Get ("r_facePlaneCull", "1", CVAR_ARCHIVE ); + + r_cullRoofFaces = Cvar_Get ("r_cullRoofFaces", "0", CVAR_CHEAT ); //attempted smart method of culling out upwards facing surfaces on roofs for automap shots -rww + r_roofCullCeilDist = Cvar_Get ("r_roofCullCeilDist", "256", CVAR_CHEAT ); //attempted smart method of culling out upwards facing surfaces on roofs for automap shots -rww + r_roofCullFloorDist = Cvar_Get ("r_roofCeilFloorDist", "128", CVAR_CHEAT ); //attempted smart method of culling out upwards facing surfaces on roofs for automap shots -rww + + r_primitives = Cvar_Get( "r_primitives", "0", CVAR_ARCHIVE ); + + r_ambientScale = Cvar_Get( "r_ambientScale", "0.6", CVAR_CHEAT ); + r_directedScale = Cvar_Get( "r_directedScale", "1", CVAR_CHEAT ); + + r_autoMap = Cvar_Get( "r_autoMap", "0", CVAR_ARCHIVE ); //automap renderside toggle for debugging -rww + r_autoMapBackAlpha = Cvar_Get( "r_autoMapBackAlpha", "0", 0 ); //alpha of automap bg -rww + r_autoMapDisable = Cvar_Get( "r_autoMapDisable", "1", 0 ); + + // + // temporary variables that can change at any time + // + r_showImages = Cvar_Get( "r_showImages", "0", CVAR_CHEAT ); + + r_debugLight = Cvar_Get( "r_debuglight", "0", CVAR_TEMP ); + r_debugSort = Cvar_Get( "r_debugSort", "0", CVAR_CHEAT ); + + r_dlightStyle = Cvar_Get ("r_dlightStyle", "1", CVAR_TEMP); + r_surfaceSprites = Cvar_Get ("r_surfaceSprites", "1", CVAR_TEMP); + r_surfaceWeather = Cvar_Get ("r_surfaceWeather", "0", CVAR_TEMP); + + r_windSpeed = Cvar_Get ("r_windSpeed", "0", 0); + r_windAngle = Cvar_Get ("r_windAngle", "0", 0); + r_windGust = Cvar_Get ("r_windGust", "0", 0); + r_windDampFactor = Cvar_Get ("r_windDampFactor", "0.1", 0); + r_windPointForce = Cvar_Get ("r_windPointForce", "0", 0); + r_windPointX = Cvar_Get ("r_windPointX", "0", 0); + r_windPointY = Cvar_Get ("r_windPointY", "0", 0); + + r_nocurves = Cvar_Get ("r_nocurves", "0", CVAR_CHEAT ); + r_drawworld = Cvar_Get ("r_drawworld", "1", CVAR_CHEAT ); + r_drawfog = Cvar_Get ("r_drawfog", "2", CVAR_CHEAT ); + r_lightmap = Cvar_Get ("r_lightmap", "0", CVAR_CHEAT ); + r_portalOnly = Cvar_Get ("r_portalOnly", "0", CVAR_CHEAT ); + + r_skipBackEnd = Cvar_Get ("r_skipBackEnd", "0", CVAR_CHEAT); + + r_measureOverdraw = Cvar_Get( "r_measureOverdraw", "0", CVAR_CHEAT ); + r_lodscale = Cvar_Get( "r_lodscale", "5", 0 ); + r_norefresh = Cvar_Get ("r_norefresh", "0", CVAR_CHEAT); + r_drawentities = Cvar_Get ("r_drawentities", "1", CVAR_CHEAT ); + r_ignore = Cvar_Get( "r_ignore", "1", CVAR_CHEAT ); + r_nocull = Cvar_Get ("r_nocull", "0", CVAR_CHEAT); + r_novis = Cvar_Get ("r_novis", "0", CVAR_CHEAT); + r_showcluster = Cvar_Get ("r_showcluster", "0", CVAR_CHEAT); + r_speeds = Cvar_Get ("r_speeds", "0", CVAR_CHEAT); + r_verbose = Cvar_Get( "r_verbose", "0", CVAR_CHEAT ); + r_logFile = Cvar_Get( "r_logFile", "0", CVAR_CHEAT ); + r_debugSurface = Cvar_Get ("r_debugSurface", "0", CVAR_CHEAT); + r_nobind = Cvar_Get ("r_nobind", "0", CVAR_CHEAT); + r_showtris = Cvar_Get ("r_showtris", "0", CVAR_CHEAT); + r_showsky = Cvar_Get ("r_showsky", "0", CVAR_CHEAT); + r_shownormals = Cvar_Get ("r_shownormals", "0", CVAR_CHEAT); + r_clear = Cvar_Get ("r_clear", "0", CVAR_CHEAT); + r_offsetFactor = Cvar_Get( "r_offsetfactor", "-1", CVAR_CHEAT ); + r_offsetUnits = Cvar_Get( "r_offsetunits", "-2", CVAR_CHEAT ); + r_lockpvs = Cvar_Get ("r_lockpvs", "0", CVAR_CHEAT); + r_noportals = Cvar_Get ("r_noportals", "0", CVAR_CHEAT); + r_shadows = Cvar_Get( "cg_shadows", "1", 0 ); + r_shadowRange = Cvar_Get( "r_shadowRange", "1000", 0 ); + +#ifdef _XBOX + r_hdreffect = Cvar_Get( "r_hdreffect", "0", 0 ); + r_sundir_x = Cvar_Get( "r_sundir_x", "0.45", 0 ); + r_sundir_y = Cvar_Get( "r_sundir_y", "0.3", 0 ); + r_sundir_z = Cvar_Get( "r_sundir_z", "0.9", 0 ); + r_hdrbloom = Cvar_Get( "r_hdrbloom", "1.0", 0 ); +#endif + + r_maxpolys = Cvar_Get( "r_maxpolys", va("%d", MAX_POLYS), 0); + r_maxpolyverts = Cvar_Get( "r_maxpolyverts", va("%d", MAX_POLYVERTS), 0); +/* +Ghoul2 Insert Start +*/ +#ifdef _DEBUG + r_noPrecacheGLA = Cvar_Get( "r_noPrecacheGLA", "0", CVAR_CHEAT); +#endif + + r_noServerGhoul2 = Cvar_Get( "r_noserverghoul2", "0", CVAR_CHEAT); + + r_Ghoul2AnimSmooth = Cvar_Get( "r_ghoul2animsmooth", "0.3", 0 ); + r_Ghoul2UnSqashAfterSmooth = Cvar_Get( "r_ghoul2unsqashaftersmooth", "1", 0 ); + + broadsword = Cvar_Get( "broadsword", "0", 0); + broadsword_kickbones = Cvar_Get( "broadsword_kickbones", "1", 0); + broadsword_kickorigin = Cvar_Get( "broadsword_kickorigin", "1", 0); + broadsword_dontstopanim = Cvar_Get( "broadsword_dontstopanim", "0", 0); + broadsword_waitforshot = Cvar_Get( "broadsword_waitforshot", "0", 0); + broadsword_playflop = Cvar_Get( "broadsword_playflop", "1", 0); + broadsword_smallbbox = Cvar_Get( "broadsword_smallbbox", "0", 0); + broadsword_extra1 = Cvar_Get( "broadsword_extra1", "0", 0); + broadsword_extra2 = Cvar_Get( "broadsword_extra2", "0", 0); + broadsword_effcorr = Cvar_Get( "broadsword_effcorr", "1", 0); + broadsword_ragtobase = Cvar_Get( "broadsword_ragtobase", "2", 0); + broadsword_dircap = Cvar_Get( "broadsword_dircap", "64", 0); +/* +Ghoul2 Insert End +*/ +extern qboolean Sys_LowPhysicalMemory(); + r_modelpoolmegs = Cvar_Get("r_modelpoolmegs", "20", CVAR_ARCHIVE); + if (Sys_LowPhysicalMemory() ) + { + Cvar_Set("r_modelpoolmegs", "0"); + } + + // make sure all the commands added here are also + // removed in R_Shutdown +#ifndef DEDICATED + Cmd_AddCommand( "imagelist", R_ImageList_f ); + Cmd_AddCommand( "shaderlist", R_ShaderList_f ); + Cmd_AddCommand( "skinlist", R_SkinList_f ); + Cmd_AddCommand( "screenshot", R_ScreenShot_f ); + Cmd_AddCommand( "screenshot_tga", R_ScreenShotTGA_f ); + Cmd_AddCommand( "gfxinfo", GfxInfo_f ); + Cmd_AddCommand( "r_we", R_WorldEffect_f); + Cmd_AddCommand( "imagecacheinfo", RE_RegisterImages_Info_f); +#endif + Cmd_AddCommand( "modellist", R_Modellist_f ); +#ifndef _XBOX + Cmd_AddCommand( "modelist", R_ModeList_f ); +#endif + Cmd_AddCommand( "modelcacheinfo", RE_RegisterModels_Info_f); + +} + + +/* +=============== +R_Init +=============== +*/ +extern void R_InitWorldEffects(void); //tr_WorldEffects.cpp +void R_Init( void ) { + int i; + byte *ptr; + +// Com_Printf ("----- R_Init -----\n" ); +#ifdef _XBOX + /* + Hunk_Clear(); + + extern void CM_Free(void); + CM_Free(); + */ + + //Save visibility info as it has already been set. + SPARC *vis = tr.externalVisData; +#endif + + // clear all our internal state + Com_Memset( &tr, 0, sizeof( tr ) ); + Com_Memset( &backEnd, 0, sizeof( backEnd ) ); +#ifndef DEDICATED + Com_Memset( &tess, 0, sizeof( tess ) ); +#endif + +#ifdef _XBOX + //Restore visibility info. + tr.externalVisData = vis; +#endif + +// Swap_Init(); + +#ifndef DEDICATED +#ifndef FINAL_BUILD + if ( (int)tess.xyz & 15 ) { + Com_Printf( "WARNING: tess.xyz not 16 byte aligned (%x)\n",(int)tess.xyz & 15 ); + } +#endif +#endif + // + // init function tables + // + for ( i = 0; i < FUNCTABLE_SIZE; i++ ) + { + tr.sinTable[i] = sin( DEG2RAD( i * 360.0f / ( ( float ) ( FUNCTABLE_SIZE - 1 ) ) ) ); + tr.squareTable[i] = ( i < FUNCTABLE_SIZE/2 ) ? 1.0f : -1.0f; + tr.sawToothTable[i] = (float)i / FUNCTABLE_SIZE; + tr.inverseSawToothTable[i] = 1.0f - tr.sawToothTable[i]; + + if ( i < FUNCTABLE_SIZE / 2 ) + { + if ( i < FUNCTABLE_SIZE / 4 ) + { + tr.triangleTable[i] = ( float ) i / ( FUNCTABLE_SIZE / 4 ); + } + else + { + tr.triangleTable[i] = 1.0f - tr.triangleTable[i-FUNCTABLE_SIZE / 4]; + } + } + else + { + tr.triangleTable[i] = -tr.triangleTable[i-FUNCTABLE_SIZE/2]; + } + } +#ifndef DEDICATED + R_InitFogTable(); + + R_NoiseInit(); +#endif + R_Register(); + + max_polys = r_maxpolys->integer; + if (max_polys < MAX_POLYS) + max_polys = MAX_POLYS; + + max_polyverts = r_maxpolyverts->integer; + if (max_polyverts < MAX_POLYVERTS) + max_polyverts = MAX_POLYVERTS; + + ptr = (unsigned char *)Hunk_Alloc( sizeof( *backEndData ) + sizeof(srfPoly_t) * max_polys + sizeof(polyVert_t) * max_polyverts, h_low); + backEndData = (backEndData_t *) ptr; + backEndData->polys = (srfPoly_t *) ((char *) ptr + sizeof( *backEndData )); + backEndData->polyVerts = (polyVert_t *) ((char *) ptr + sizeof( *backEndData ) + sizeof(srfPoly_t) * max_polys); +#ifndef DEDICATED + R_ToggleSmpFrame(); + + for(i = 0; i < MAX_LIGHT_STYLES; i++) + { + RE_SetLightStyle(i, -1); + } + InitOpenGL(); + + R_InitImages(); + R_InitShaders(qfalse); + R_InitSkins(); + +/* + R_TerrainInit(); //rwwRMG - added +*/ + + R_InitFonts(); +#endif + R_ModelInit(); + G2VertSpaceServer = &CMiniHeap_singleton; +#ifndef DEDICATED + R_InitDecals ( ); + + R_InitWorldEffects(); + + int err = qglGetError(); + if ( err != GL_NO_ERROR ) + Com_Printf ( "glGetError() = 0x%x\n", err); +#endif +// Com_Printf ("----- finished R_Init -----\n" ); +} + +/* +=============== +RE_Shutdown +=============== +*/ +void RE_Shutdown( qboolean destroyWindow ) { + +// Com_Printf ("RE_Shutdown( %i )\n", destroyWindow ); + + Cmd_RemoveCommand ("imagelist"); + Cmd_RemoveCommand ("shaderlist"); + Cmd_RemoveCommand ("skinlist"); + Cmd_RemoveCommand ("screenshot"); + Cmd_RemoveCommand ("screenshot_tga"); + Cmd_RemoveCommand ("gfxinfo"); + Cmd_RemoveCommand ("r_we"); + Cmd_RemoveCommand ("imagecacheinfo"); + Cmd_RemoveCommand ("modellist"); + Cmd_RemoveCommand ("modelist"); + Cmd_RemoveCommand ("modelcacheinfo"); +#ifndef DEDICATED + +#ifndef _XBOX // GLOWXXX + if ( r_DynamicGlow && r_DynamicGlow->integer ) + { + // Release the Glow Vertex Shader. + if ( tr.glowVShader ) + { + qglDeleteProgramsARB( 1, &tr.glowVShader ); + } + + // Release Pixel Shader. + if ( tr.glowPShader ) + { + if ( qglCombinerParameteriNV ) + { + // Release the Glow Regcom call list. + qglDeleteLists( tr.glowPShader, 1 ); + } + else if ( qglGenProgramsARB ) + { + // Release the Glow Fragment Shader. + qglDeleteProgramsARB( 1, &tr.glowPShader ); + } + } + + // Release the scene glow texture. + qglDeleteTextures( 1, &tr.screenGlow ); + + // Release the scene texture. + qglDeleteTextures( 1, &tr.sceneImage ); + + // Release the blur texture. + qglDeleteTextures( 1, &tr.blurImage ); + } +#endif + +/* + R_TerrainShutdown(); //rwwRMG - added +*/ + + R_ShutdownFonts(); + if ( tr.registered ) { + R_SyncRenderThread(); + R_ShutdownCommandBuffers(); +//#ifndef _XBOX + if (destroyWindow) +//#endif + { + R_DeleteTextures(); // only do this for vid_restart now, not during things like map load + } + } + + // shut down platform specific OpenGL stuff + if ( destroyWindow ) { + GLimp_Shutdown(); + } +#endif //!DEDICATED + + tr.registered = qfalse; +} + +#ifndef DEDICATED + +/* +============= +RE_EndRegistration + +Touch all images to make sure they are resident +============= +*/ +void RE_EndRegistration( void ) { + R_SyncRenderThread(); + if (!Sys_LowPhysicalMemory()) { +#ifndef _XBOX + RB_ShowImages(); +#endif + } +} + +void RE_GetLightStyle(int style, color4ub_t color) +{ + if (style >= MAX_LIGHT_STYLES) + { + Com_Error( ERR_FATAL, "RE_GetLightStyle: %d is out of range", (int)style ); + return; + } + + *(int *)color = *(int *)styleColors[style]; +} + +void RE_SetLightStyle(int style, int color) +{ + if (style >= MAX_LIGHT_STYLES) + { + Com_Error( ERR_FATAL, "RE_SetLightStyle: %d is out of range", (int)style ); + return; + } + + if (*(int*)styleColors[style] != color) + { + *(int *)styleColors[style] = color; + } +} + +#endif //!DEDICATED +/* +@@@@@@@@@@@@@@@@@@@@@ +GetRefAPI + +@@@@@@@@@@@@@@@@@@@@@ +*/ +refexport_t *GetRefAPI ( int apiVersion ) { + static refexport_t re; + + Com_Memset( &re, 0, sizeof( re ) ); + + if ( apiVersion != REF_API_VERSION ) { + Com_Printf ( "Mismatched REF_API_VERSION: expected %i, got %i\n", + REF_API_VERSION, apiVersion ); + return NULL; + } + + // the RE_ functions are Renderer Entry points + + re.Shutdown = RE_Shutdown; +#ifndef DEDICATED + re.BeginRegistration = RE_BeginRegistration; + re.RegisterModel = RE_RegisterModel; + re.RegisterSkin = RE_RegisterSkin; + re.RegisterShader = RE_RegisterShader; + re.RegisterShaderNoMip = RE_RegisterShaderNoMip; + re.ShaderNameFromIndex = RE_ShaderNameFromIndex; + re.LoadWorld = RE_LoadWorldMap; + re.SetWorldVisData = RE_SetWorldVisData; + re.EndRegistration = RE_EndRegistration; + + re.BeginFrame = RE_BeginFrame; + re.EndFrame = RE_EndFrame; + + re.MarkFragments = R_MarkFragments; + re.LerpTag = R_LerpTag; + re.ModelBounds = R_ModelBounds; + + re.DrawRotatePic = RE_RotatePic; + re.DrawRotatePic2 = RE_RotatePic2; + + re.ClearScene = RE_ClearScene; + re.ClearDecals = RE_ClearDecals; + re.AddRefEntityToScene = RE_AddRefEntityToScene; + re.AddMiniRefEntityToScene = RE_AddMiniRefEntityToScene; + re.AddPolyToScene = RE_AddPolyToScene; + re.AddDecalToScene = RE_AddDecalToScene; + re.LightForPoint = R_LightForPoint; + re.AddLightToScene = RE_AddLightToScene; + re.AddAdditiveLightToScene = RE_AddAdditiveLightToScene; + re.RenderScene = RE_RenderScene; + + re.SetColor = RE_SetColor; + re.DrawStretchPic = RE_StretchPic; + re.DrawStretchRaw = RE_StretchRaw; + re.UploadCinematic = RE_UploadCinematic; + + re.RegisterFont = RE_RegisterFont; + re.Font_StrLenPixels = RE_Font_StrLenPixels; + re.Font_StrLenChars = RE_Font_StrLenChars; + re.Font_HeightPixels = RE_Font_HeightPixels; + re.Font_DrawString = RE_Font_DrawString; + re.Language_IsAsian = Language_IsAsian; + re.Language_UsesSpaces = Language_UsesSpaces; + re.AnyLanguage_ReadCharFromString = AnyLanguage_ReadCharFromString; + + re.RemapShader = R_RemapShader; + re.GetEntityToken = R_GetEntityToken; + re.inPVS = R_inPVS; + + re.GetLightStyle = RE_GetLightStyle; + re.SetLightStyle = RE_SetLightStyle; + + re.GetBModelVerts = RE_GetBModelVerts; +#endif //!DEDICATED + return &re; +} + diff --git a/codemp/renderer/tr_landscape.h b/codemp/renderer/tr_landscape.h new file mode 100644 index 0000000..691ef99 --- /dev/null +++ b/codemp/renderer/tr_landscape.h @@ -0,0 +1,191 @@ +#ifndef _INC_LANDSCAPE_H +#define _INC_LANDSCAPE_H + +// Number of TriTreeNodes available +#define POOL_SIZE (50000) + +#define TEXTURE_ALPHA_TL 0x000000ff +#define TEXTURE_ALPHA_TR 0x0000ff00 +#define TEXTURE_ALPHA_BL 0x00ff0000 +#define TEXTURE_ALPHA_BR 0x000000ff + +#define INDEX_TL 0 +#define INDEX_TR 1 +#define INDEX_BL 2 +#define INDEX_BR 3 + +#define VARIANCE_MIN 0.0f +#define VARIANCE_MAX 2000.0f +#define SPLIT_VARIANCE_SIZE 20 +#define SPLIT_VARIANCE_STEP (VARIANCE_MAX / SPLIT_VARIANCE_SIZE) + +class CTerVert +{ +public: + vec3_t coords; // real world coords of terxel + vec3_t normal; // required to calculate lighting and used in physics + color4ub_t tint; // tint at this terxel + float tex[2]; // texture coordinates at this terxel + int height; // Copy of heightmap data + int tessIndex; // Index of the vert in the tess array + int tessRegistration; // ...... for the tess with this registration + + CTerVert( void ) { memset(this, 0, sizeof(*this)); } + ~CTerVert( void ) { } +}; + +class CTRHeightDetails +{ +private: + qhandle_t mShader; +public: + CTRHeightDetails( void ) { } + ~CTRHeightDetails( void ) { } + + const qhandle_t GetShader( void ) const { return(mShader); } + void SetShader(const qhandle_t shader) { mShader = shader; } +}; + +// +// Information of each patch (tessellated area) of a CTRLandScape +// +class CTRPatch +{ +private: + class CCMLandScape *owner; + class CTRLandScape *localowner; + + CCMPatch *common; + vec3_t mCenter; // Real world center of the patch +// vec3_t mNormal[2]; +// float mDistance[2]; + + CTerVert *mRenderMap; // Modulation value and texture coords per vertex + shader_t *mTLShader; // Dynamically created blended shader for the top left triangle + shader_t *mBRShader; // Dynamically created blended shader for the bottom right triangle + + bool misVisible; // Is this patch visible in the current frame? + +public: + CTRPatch(void) { } + ~CTRPatch(void) { } + + // Accessors + const vec3_t &GetWorld(void) const { return(common->GetWorld()); } + const vec3_t &GetMins(void) const { return(common->GetMins()); } + const vec3_t &GetMaxs(void) const { return(common->GetMaxs()); } + const vec3pair_t &GetBounds(void) const { return(common->GetBounds()); } + shader_t *GetTLShader(void) { return mTLShader; } + shader_t *GetBRShader(void) { return mBRShader; } + + void SetCommon(CCMPatch *in) { common = in; } + const CCMPatch *GetCommon(void) const { return(common); } + bool isVisible(void) { return(misVisible); } + void SetTLShader(qhandle_t in) { mTLShader = R_GetShaderByHandle(in); } + void SetBRShader(qhandle_t in) { mBRShader = R_GetShaderByHandle(in); } + void SetOwner(CCMLandScape *in) { owner = in; } + void SetLocalOwner(CTRLandScape *in) { localowner = in; } + void Clear(void) { memset(this, 0, sizeof(*this)); } + void SetCenter(void) { VectorAverage(common->GetMins(), common->GetMaxs(), mCenter); } + void CalcNormal(void); + + // Prototypes + void SetVisibility(bool visCheck); + void RenderCorner(ivec5_t corner); + void Render(int Part); + void RecurseRender(int depth, ivec5_t left, ivec5_t right, ivec5_t apex); + void SetRenderMap(const int x, const int y); + int RenderWaterVert(int x, int y); + void RenderWater(void); + const bool HasWater(void) const; +}; + + +#define PI_TOP 1 +#define PI_BOTTOM 2 +#define PI_BOTH 3 + +typedef struct SPatchInfo +{ + CTRPatch *mPatch; + shader_t *mShader; + int mPart; +} TPatchInfo; + +// +// The master class used to define an area of terrain +// + +class CTRLandScape +{ +private: + const CCMLandScape *common; + CTRPatch *mTRPatches; // Local patch info + TPatchInfo *mSortedPatches; + + int mPatchMinx, mPatchMaxx; + int mPatchMiny, mPatchMaxy; + int mMaxNode; // terxels * terxels = exit condition for splitting + int mSortedCount; + + float mPatchSize; + + shader_t *mShader; // shader the terrain got its contents from + + CTerVert *mRenderMap; // modulation value and texture coords per vertex + float mTextureScale; // Scale of texture mapped to terrain + + float mScalarSize; + + shader_t *mWaterShader; // Water shader + qhandle_t mFlatShader; // Flat ground shader + + CTRHeightDetails mHeightDetails[HEIGHT_RESOLUTION]; // Array of info specific to height +#if _DEBUG + int mCycleCount; +#endif +public: + CTRLandScape(const char *configstring); + ~CTRLandScape(void); + + // Accessors + const int GetBlockWidth(void) const { return(common->GetBlockWidth()); } + const int GetBlockHeight(void) const { return(common->GetBlockHeight()); } + const vec3_t &GetMins(void) const { return(common->GetMins()); } + const vec3_t &GetMaxs(void) const { return(common->GetMaxs()); } + const vec3_t &GetTerxelSize(void) const { return(common->GetTerxelSize()); } + const vec3_t &GetPatchSize(void) const { return(common->GetPatchSize()); } + const int GetWidth(void) const { return(common->GetWidth()); } + const int GetHeight(void) const { return(common->GetHeight()); } + const int GetRealWidth(void) const { return(common->GetRealWidth()); } + const int GetRealHeight(void) const { return(common->GetRealHeight()); } + + void SetCommon(const CCMLandScape *landscape) { common = landscape; } + const CCMLandScape *GetCommon( void ) const { return(common); } + const thandle_t GetCommonId( void ) const { return(common->GetTerrainId()); } + shader_t *GetShader(void) const { return(mShader); } + CTerVert *GetRenderMap(const int x, const int y) const { return(mRenderMap + x + (y * common->GetRealWidth())); } + CTRPatch *GetPatch(const int x, const int y) const { return(mTRPatches + (common->GetBlockWidth() * y) + x); } + const CTRHeightDetails *GetHeightDetail(int height) const { return(mHeightDetails + height); } + const float GetScalarSize(void) const { return(mScalarSize); } + const int GetMaxNode(void) const { return(mMaxNode); } + + // Prototypes + void CalculateRegion(void); + void Reset(bool visCheck = true); + void Render(void); + void CalculateRealCoords(void); + void CalculateNormals(void); + void CalculateTextureCoords(void); + void CalculateLighting(void); + void CalculateShaders(void); + qhandle_t GetBlendedShader(qhandle_t a, qhandle_t b, qhandle_t c, bool surfaceSprites); + void LoadTerrainDef(const char *td); + void CopyHeightMap(void); + void SetShaders(const int height, const qhandle_t shader); +}; + +void R_CalcTerrainVisBounds(CTRLandScape *landscape); +void R_AddTerrainSurfaces(void); + +#endif //INC_LANDSCAPE_H diff --git a/codemp/renderer/tr_light.cpp b/codemp/renderer/tr_light.cpp new file mode 100644 index 0000000..f5d928a --- /dev/null +++ b/codemp/renderer/tr_light.cpp @@ -0,0 +1,475 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_light.c + +#include "tr_local.h" + +#define DLIGHT_AT_RADIUS 16 +// at the edge of a dlight's influence, this amount of light will be added + +#define DLIGHT_MINIMUM_RADIUS 16 +// never calculate a range less than this to prevent huge light numbers + + +/* +=============== +R_TransformDlights + +Transforms the origins of an array of dlights. +Used by both the front end (for DlightBmodel) and +the back end (before doing the lighting calculation) +=============== +*/ +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *ori) { + int i; + vec3_t temp; + + for ( i = 0 ; i < count ; i++, dl++ ) { + VectorSubtract( dl->origin, ori->origin, temp ); + dl->transformed[0] = DotProduct( temp, ori->axis[0] ); + dl->transformed[1] = DotProduct( temp, ori->axis[1] ); + dl->transformed[2] = DotProduct( temp, ori->axis[2] ); + } +} + +/* +============= +R_DlightBmodel + +Determine which dynamic lights may effect this bmodel +============= +*/ +void R_DlightBmodel( bmodel_t *bmodel, bool NoLight ) +{ //rwwRMG - modified args + int i, j; + dlight_t *dl; + int mask; + msurface_t *surf; + + // transform all the lights + R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.ori ); + + mask = 0; + if (!NoLight) + { + for ( i=0 ; itransformed[j] - bmodel->bounds[1][j] > dl->radius ) { + break; + } + if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) { + break; + } + } + if ( j < 3 ) { + continue; + } + + // we need to check this light + mask |= 1 << i; + } + } + + tr.currentEntity->needDlights = (qboolean)(mask != 0); + tr.currentEntity->dlightBits = mask; + + // set the dlight bits in all the surfaces + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + surf = bmodel->firstSurface + i; + if ( *surf->data == SF_FACE ) { + ((srfSurfaceFace_t *)surf->data)->dlightBits = mask; + } else if ( *surf->data == SF_GRID ) { + ((srfGridMesh_t *)surf->data)->dlightBits = mask; + } else if ( *surf->data == SF_TRIANGLES ) { + ((srfTriangles_t *)surf->data)->dlightBits = mask; + } + } +} + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +extern cvar_t *r_ambientScale; +extern cvar_t *r_directedScale; +extern cvar_t *r_debugLight; + +//rwwRMG - VectorScaleVector is now a #define + +/* +================= +R_SetupEntityLightingGrid + +================= +*/ +static void R_SetupEntityLightingGrid( trRefEntity_t *ent ) { + vec3_t lightOrigin; + int pos[3]; + int i, j; + float frac[3]; + int gridStep[3]; + vec3_t direction; + float totalFactor; + unsigned short *startGridPos; +#ifdef _XBOX + byte zeroArray[3]; + byte style; + + zeroArray[0] = zeroArray[1] = zeroArray[2] = 0; +#endif + + + if (r_fullbright->integer) + { + ent->ambientLight[0] = ent->ambientLight[1] = ent->ambientLight[2] = 255.0; + ent->directedLight[0] = ent->directedLight[1] = ent->directedLight[2] = 255.0; + VectorCopy( tr.sunDirection, ent->lightDir ); + return; + } + + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + VectorSubtract( lightOrigin, tr.world->lightGridOrigin, lightOrigin ); + for ( i = 0 ; i < 3 ; i++ ) { + float v; + + v = lightOrigin[i]*tr.world->lightGridInverseSize[i]; + pos[i] = floor( v ); + frac[i] = v - pos[i]; + if ( pos[i] < 0 ) { + pos[i] = 0; + } else if ( pos[i] >= tr.world->lightGridBounds[i] - 1 ) { + pos[i] = tr.world->lightGridBounds[i] - 1; + } + } + + VectorClear( ent->ambientLight ); + VectorClear( ent->directedLight ); + VectorClear( direction ); + + // trilerp the light value + gridStep[0] = 1; + gridStep[1] = tr.world->lightGridBounds[0]; + gridStep[2] = tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1]; + startGridPos = tr.world->lightGridArray + (pos[0] * gridStep[0] + pos[1] * gridStep[1] + pos[2] * gridStep[2]); + + totalFactor = 0; + for ( i = 0 ; i < 8 ; i++ ) { + float factor; + mgrid_t *data; + unsigned short *gridPos; + int lat, lng; + vec3_t normal; + + factor = 1.0; + gridPos = startGridPos; + for ( j = 0 ; j < 3 ; j++ ) { + if ( i & (1<= tr.world->lightGridArray + tr.world->numGridArrayElements) + {//we've gone off the array somehow + continue; + } + data = tr.world->lightGridData + *gridPos; + +#ifdef _XBOX + const byte *memory = (const byte *)tr.world->lightGridData + data->data; + + style = data->flags & (1 << 4) ? memory[0] : LS_LSNONE; + if ( style == LS_LSNONE ) + { + continue; // ignore samples in walls + } + + totalFactor += factor; + + const byte *array; + + for(j=0;jflags & (1 << (j + 4))) { + style = *memory; + memory++; + } else { + style = LS_LSNONE; + } + + if (style != LS_LSNONE) + { + if(data->flags & (1 << j)) { + array = memory; + memory += 3; + } else { + array = zeroArray; + } + + ent->ambientLight[0] += factor * array[0] * styleColors[style][0] / 255.0f; + ent->ambientLight[1] += factor * array[1] * styleColors[style][1] / 255.0f; + ent->ambientLight[2] += factor * array[2] * styleColors[style][2] / 255.0f; + + if(array != zeroArray) { + array = memory; + memory += 3; + } + + ent->directedLight[0] += factor * array[0] * styleColors[style][0] / 255.0f; + ent->directedLight[1] += factor * array[1] * styleColors[style][1] / 255.0f; + ent->directedLight[2] += factor * array[2] * styleColors[style][2] / 255.0f; + } + else + { + break; + } + } + +#else // _XBOX + + if ( data->styles[0] == LS_LSNONE ) + { + continue; // ignore samples in walls + } + + totalFactor += factor; + + for(j=0;jstyles[j] != LS_LSNONE) + { + const byte style= data->styles[j]; + + ent->ambientLight[0] += factor * data->ambientLight[j][0] * styleColors[style][0] / 255.0f; + ent->ambientLight[1] += factor * data->ambientLight[j][1] * styleColors[style][1] / 255.0f; + ent->ambientLight[2] += factor * data->ambientLight[j][2] * styleColors[style][2] / 255.0f; + + ent->directedLight[0] += factor * data->directLight[j][0] * styleColors[style][0] / 255.0f; + ent->directedLight[1] += factor * data->directLight[j][1] * styleColors[style][1] / 255.0f; + ent->directedLight[2] += factor * data->directLight[j][2] * styleColors[style][2] / 255.0f; + } + else + { + break; + } + } +#endif // _XBOX + + lat = data->latLong[1]; + lng = data->latLong[0]; + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + normal[0] = tr.sinTable[(lat + (FUNCTABLE_SIZE / 4)) & FUNCTABLE_MASK] * tr.sinTable[lng]; + normal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + normal[2] = tr.sinTable[(lng + (FUNCTABLE_SIZE / 4)) & FUNCTABLE_MASK]; + + VectorMA( direction, factor, normal, direction ); + } + + if ( totalFactor > 0 && totalFactor < 0.99 ) + { + totalFactor = 1.0 / totalFactor; + VectorScale( ent->ambientLight, totalFactor, ent->ambientLight ); + VectorScale( ent->directedLight, totalFactor, ent->directedLight ); + } + + VectorScale( ent->ambientLight, r_ambientScale->value, ent->ambientLight ); + VectorScale( ent->directedLight, r_directedScale->value, ent->directedLight ); + + VectorNormalize2( direction, ent->lightDir ); +} + + +/* +=============== +LogLight +=============== +*/ +static void LogLight( trRefEntity_t *ent ) { + int max1, max2; + + if ( !(ent->e.renderfx & RF_FIRST_PERSON ) ) { + return; + } + + max1 = ent->ambientLight[0]; + if ( ent->ambientLight[1] > max1 ) { + max1 = ent->ambientLight[1]; + } else if ( ent->ambientLight[2] > max1 ) { + max1 = ent->ambientLight[2]; + } + + max2 = ent->directedLight[0]; + if ( ent->directedLight[1] > max2 ) { + max2 = ent->directedLight[1]; + } else if ( ent->directedLight[2] > max2 ) { + max2 = ent->directedLight[2]; + } + + Com_Printf ("amb:%i dir:%i\n", max1, max2 ); +} + +/* +================= +R_SetupEntityLighting + +Calculates all the lighting values that will be used +by the Calc_* functions +================= +*/ +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) { + int i; + dlight_t *dl; + float power; + vec3_t dir; + float d; + vec3_t lightDir; + vec3_t lightOrigin; + + // lighting calculations + if ( ent->lightingCalculated ) { + return; + } + ent->lightingCalculated = qtrue; + + // + // trace a sample point down to find ambient light + // + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + // if NOWORLDMODEL, only use dynamic lights (menu system, etc) + if ( !(refdef->rdflags & RDF_NOWORLDMODEL ) + && tr.world->lightGridData ) { + R_SetupEntityLightingGrid( ent ); + } else { + ent->ambientLight[0] = ent->ambientLight[1] = + ent->ambientLight[2] = tr.identityLight * 150; + ent->directedLight[0] = ent->directedLight[1] = + ent->directedLight[2] = tr.identityLight * 150; + VectorCopy( tr.sunDirection, ent->lightDir ); + } + + // bonus items and view weapons have a fixed minimum add + if ( 1 /* ent->e.renderfx & RF_MINLIGHT */ ) { + // give everything a minimum light add + ent->ambientLight[0] += tr.identityLight * 32; + ent->ambientLight[1] += tr.identityLight * 32; + ent->ambientLight[2] += tr.identityLight * 32; + } + + if (ent->e.renderfx & RF_MINLIGHT) + { //the minlight flag is now for items rotating on their holo thing + if (ent->e.shaderRGBA[0] == 255 && + ent->e.shaderRGBA[1] == 255 && + ent->e.shaderRGBA[2] == 0) + { + ent->ambientLight[0] += tr.identityLight * 255; + ent->ambientLight[1] += tr.identityLight * 255; + ent->ambientLight[2] += tr.identityLight * 0; + } + else + { + ent->ambientLight[0] += tr.identityLight * 16; + ent->ambientLight[1] += tr.identityLight * 96; + ent->ambientLight[2] += tr.identityLight * 150; + } + } + + // + // modify the light by dynamic lights + // + d = VectorLength( ent->directedLight ); + VectorScale( ent->lightDir, d, lightDir ); + + for ( i = 0 ; i < refdef->num_dlights ; i++ ) { + dl = &refdef->dlights[i]; + VectorSubtract( dl->origin, lightOrigin, dir ); + d = VectorNormalize( dir ); + + power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius ); + if ( d < DLIGHT_MINIMUM_RADIUS ) { + d = DLIGHT_MINIMUM_RADIUS; + } + d = power / ( d * d ); + + VectorMA( ent->directedLight, d, dl->color, ent->directedLight ); + VectorMA( lightDir, d, dir, lightDir ); + } + + // clamp ambient + for ( i = 0 ; i < 3 ; i++ ) { + if ( ent->ambientLight[i] > tr.identityLightByte ) { + ent->ambientLight[i] = tr.identityLightByte; + } + } + + if ( r_debugLight->integer ) { + LogLight( ent ); + } + + // save out the byte packet version + ((byte *)&ent->ambientLightInt)[0] = myftol( ent->ambientLight[0] ); + ((byte *)&ent->ambientLightInt)[1] = myftol( ent->ambientLight[1] ); + ((byte *)&ent->ambientLightInt)[2] = myftol( ent->ambientLight[2] ); + ((byte *)&ent->ambientLightInt)[3] = 0xff; + + // transform the direction to local space + VectorNormalize( lightDir ); + ent->lightDir[0] = DotProduct( lightDir, ent->e.axis[0] ); + ent->lightDir[1] = DotProduct( lightDir, ent->e.axis[1] ); + ent->lightDir[2] = DotProduct( lightDir, ent->e.axis[2] ); +} + +/* +================= +R_LightForPoint +================= +*/ +int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) +{ + trRefEntity_t ent; + + // bk010103 - this segfaults with -nolight maps + if ( tr.world->lightGridData == NULL ) + return qfalse; + + Com_Memset(&ent, 0, sizeof(ent)); + VectorCopy( point, ent.e.origin ); + R_SetupEntityLightingGrid( &ent ); + VectorCopy(ent.ambientLight, ambientLight); + VectorCopy(ent.directedLight, directedLight); + VectorCopy(ent.lightDir, lightDir); + + return qtrue; +} diff --git a/codemp/renderer/tr_lightmanager.cpp b/codemp/renderer/tr_lightmanager.cpp new file mode 100644 index 0000000..2dec3f0 --- /dev/null +++ b/codemp/renderer/tr_lightmanager.cpp @@ -0,0 +1,882 @@ +/* +** tr_lightmanager.cpp +*/ + +#ifdef VV_LIGHTING + +#include "../server/exe_headers.h" +#include "tr_local.h" + +#include "../win32/glw_win_dx8.h" +#include "../win32/win_lighteffects.h" + +#include "../cgame/cg_local.h" +#include "modelmem.h" + +extern int r_numdlights; +extern int r_firstSceneDlight; + +VVLightManager VVLightMan; + + +VVLightManager::VVLightManager() +{ +} + + +void VVLightManager::RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + VVdlight_t *dl; + + if ( !tr.registered ) { + return; + } + if ( r_numdlights >= MAX_DLIGHTS ) { + return; + } + if ( intensity <= 0 ) { + return; + } + + dl = &backEndData->dlights[r_numdlights++]; + + VectorCopy (org, dl->origin); + dl->type = LT_POINT; + dl->radius = intensity;// * 2.0f; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; +} + +void VVLightManager::RE_AddLightToScene( VVdlight_t *light ) +{ + VVdlight_t *dl; + + if ( !tr.registered ) { + return; + } + + if( r_numdlights >= MAX_DLIGHTS ) { + return; + } + + dl = &backEndData->dlights[r_numdlights++]; + + VectorCopy(light->origin, dl->origin); + VectorCopy(light->direction, dl->direction); + VectorCopy(light->color, dl->color); + dl->attenuation = light->attenuation; + dl->type = light->type; + dl->radius = light->radius; +} + + +//void VVLightManager::RE_AddStaticLightToScene( VVslight_t *light ) +//{ +// VVslight_t *sl; +// +// if( !tr.registered ) { +// return; +// } +// +// if( num_slights >= MAX_NUM_STATIC_LIGHTS ) { +// return; +// } +// +// sl = &slights[num_slights++]; +// +// VectorCopy(light->origin, sl->origin); +// VectorCopy(light->color, sl->color); +// sl->radius = light->radius;// * 2.0f; +//} + + + +void VVLightManager::R_TransformDlights( int count, VVdlight_t *dl, orientationr_t *ori) { + int i; + vec3_t temp; + + for ( i = 0; i < count; i++ ) { + VectorSubtract( dl->origin, ori->origin, temp ); + dl->transformed[0] = DotProduct( temp, ori->axis[0] ); + dl->transformed[1] = DotProduct( temp, ori->axis[1] ); + dl->transformed[2] = DotProduct( temp, ori->axis[2] ); + } +} + + + +/* +============= +R_DlightBmodel + +Determine which dynamic lights may effect this bmodel +============= +*/ +void VVLightManager::R_DlightBmodel( bmodel_t *bmodel, qboolean NoLight ) { + int i, j; + VVdlight_t *dl; + int mask; + msurface_t *surf; + + mask = 0; + + // transform all the lights + R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.ori ); + + if (!NoLight) + { + for ( i = 0; i < tr.refdef.num_dlights; i++ ) { + dl = &tr.refdef.dlights[i]; + + // see if the point is close enough to the bounds to matter + for ( j = 0 ; j < 3 ; j++ ) { + if ( dl->transformed[j] - bmodel->bounds[1][j] > dl->radius ) { + break; + } + if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) { + break; + } + } + // Directional lights are always considered (MATT - change that?) + if ( j < 3 && dl->type != LT_DIRECTIONAL ) { + continue; + } + + // we need to check this light + mask |= 1 << i; + } + } + + tr.currentEntity->needDlights = (mask != 0); + + // set the dlight bits in all the surfaces + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + surf = bmodel->firstSurface + i; + + if ( *surf->data == SF_FACE ) { + ((srfSurfaceFace_t *)surf->data)->dlightBits = mask; + } else if ( *surf->data == SF_GRID ) { + ((srfGridMesh_t *)surf->data)->dlightBits = mask; + } else if ( *surf->data == SF_TRIANGLES ) { + ((srfTriangles_t *)surf->data)->dlightBits = mask; + } + } +} + + +int VVLightManager::R_DlightFace( srfSurfaceFace_t *face, int dlightBits ) { + float d; + int i; + VVdlight_t *dl; + + for ( i = 0; i < tr.refdef.num_dlights; i++ ) { + + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + + dl = &tr.refdef.dlights[i]; + d = DotProduct( dl->origin, face->plane.normal ) - face->plane.dist; + // Directional lights are always considered (MATT - change that?) + if ( d < -dl->radius || d > dl->radius ) { + // dlight doesn't reach the plane + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + face->dlightBits = dlightBits; + return dlightBits; +} + + +//void VVLightManager::R_SlightFace( srfSurfaceFace_t *face ) { +// float d; +// int i, count = 0; +// VVslight_t *sl; +// +// for ( i = 0; i < num_slights; i++ ) { +// +// if(count > MAX_STATIC_LIGHTS_SURFACE - 1) +// break; +// +// sl = &slights[i]; +// d = DotProduct( sl->origin, face->plane.normal ) - face->plane.dist; +// +// if ( d > -sl->radius && d < sl->radius ) { +// face->slightBits[count++] = i; +// } +// } +//} + + +int VVLightManager::R_DlightGrid( srfGridMesh_t *grid, int dlightBits ) { + int i; + VVdlight_t *dl; + + for ( i = 0; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + // Directional lights are always considered (MATT - change that?) + if (( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) + && dl->type != LT_DIRECTIONAL ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + dlightBits |= (1 << i ); + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits = dlightBits; + return dlightBits; +} + + +//void VVLightManager::R_SlightGrid( srfGridMesh_t *grid ) { +// int i, count = 0; +// VVslight_t *sl; +// +// for ( i = 0 ; i < num_slights ; i++ ) { +// +// if(count > MAX_STATIC_LIGHTS_SURFACE - 1) +// break; +// +// sl = &slights[i]; +// +// if ( sl->origin[0] - sl->radius > grid->meshBounds[1][0] +// || sl->origin[0] + sl->radius < grid->meshBounds[0][0] +// || sl->origin[1] - sl->radius > grid->meshBounds[1][1] +// || sl->origin[1] + sl->radius < grid->meshBounds[0][1] +// || sl->origin[2] - sl->radius > grid->meshBounds[1][2] +// || sl->origin[2] + sl->radius < grid->meshBounds[0][2] ) { +// // slight doesn't reach the bounds +// } +// else +// { +// grid->slightBits[count++]= i; +// } +// } +//} + + +int VVLightManager::R_DlightTrisurf( srfTriangles_t *surf, int dlightBits ) { + // FIXME: more dlight culling to trisurfs... + surf->dlightBits = dlightBits; + return dlightBits; +} + + +//void VVLightManager::R_SlightTrisurf( srfTriangles_t *surf ) { +// /*int i; +// +// for( i = 0; i < num_slights; i++ ) +// { +// slightBits[i] = 1; +// }*/ +//} + +/* +==================== +R_DlightSurface + +The given surface is going to be drawn, and it touches a leaf +that is touched by one or more dlights, so try to throw out +more dlights if possible. +==================== +*/ +int VVLightManager::R_DlightSurface( msurface_t *surf, int dlightBits ) { + if ( *surf->data == SF_FACE ) { + dlightBits = VVLightManager::R_DlightFace( (srfSurfaceFace_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_GRID ) { + dlightBits = VVLightManager::R_DlightGrid( (srfGridMesh_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_TRIANGLES ) { + dlightBits = VVLightManager::R_DlightTrisurf( (srfTriangles_t *)surf->data, dlightBits ); + } else { + dlightBits = 0; + } + + if ( dlightBits ) { + tr.pc.c_dlightSurfaces++; + } + + return dlightBits; +} + + +/* +================= +R_SetupEntityLighting + +Calculates all the lighting values that will be used +by the Calc_* functions +================= +*/ + +#define DLIGHT_AT_RADIUS 16 +#define DLIGHT_MINIMUM_RADIUS 16 + +void VVLightManager::R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) { + int i; + VVdlight_t *dl; + float power; + vec3_t dir; + float d; + vec3_t lightDir; + vec3_t lightOrigin; + + // lighting calculations + if ( ent->lightingCalculated ) { + return; + } + ent->lightingCalculated = qtrue; + + // + // trace a sample point down to find ambient light + // + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + // if NOWORLDMODEL, only use dynamic lights (menu system, etc) + if ( !(refdef->rdflags & RDF_NOWORLDMODEL ) + && tr.world->lightGridData ) { + R_SetupEntityLightingGrid( ent ); + } else { + ent->ambientLight[0] = ent->ambientLight[1] = + ent->ambientLight[2] = tr.identityLight * 150; + ent->directedLight[0] = ent->directedLight[1] = + ent->directedLight[2] = tr.identityLight * 150; + // BTO - Fix for UI model rendering. tr.sunDirection is invalid + // pick an arbitrary light direction +// VectorCopy( tr.sunDirection, ent->lightDir ); + ent->lightDir[0] = ent->lightDir[1] = 0.0f; + ent->lightDir[2] = 1.0f; + } + + // give everything a minimum light add + ent->ambientLight[0] += tr.identityLight * 32; + ent->ambientLight[1] += tr.identityLight * 32; + ent->ambientLight[2] += tr.identityLight * 32; + + if (ent->e.renderfx & RF_MINLIGHT) + { //the minlight flag is now for items rotating on their holo thing + if (ent->e.shaderRGBA[0] == 255 && + ent->e.shaderRGBA[1] == 255 && + ent->e.shaderRGBA[2] == 0) + { + ent->ambientLight[0] += tr.identityLight * 255; + ent->ambientLight[1] += tr.identityLight * 255; + ent->ambientLight[2] += tr.identityLight * 0; + } + else + { + ent->ambientLight[0] += tr.identityLight * 16; + ent->ambientLight[1] += tr.identityLight * 96; + ent->ambientLight[2] += tr.identityLight * 150; + } + } + + // + // modify the light by dynamic lights + // + d = VectorLength( ent->directedLight ); + VectorScale( ent->lightDir, d, lightDir ); + + for ( i = 0; i < refdef->num_dlights; i++ ) { + dl = &refdef->dlights[i]; + VectorSubtract( dl->origin, lightOrigin, dir ); + d = VectorNormalize( dir ); + + power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius ); + if ( d < DLIGHT_MINIMUM_RADIUS ) { + d = DLIGHT_MINIMUM_RADIUS; + } + d = power / ( d * d ); + + VectorMA( ent->directedLight, d, dl->color, ent->directedLight ); + VectorMA( lightDir, d, dir, lightDir ); + } + + // clamp + for ( i = 0 ; i < 3 ; i++ ) { + if ( ent->ambientLight[i] > tr.identityLightByte ) { + ent->ambientLight[i] = tr.identityLightByte; + } + if ( ent->directedLight[i] > tr.identityLightByte ) { + ent->directedLight[i] = tr.identityLightByte; + } + } + + // save out the byte packet version + ((byte *)&ent->ambientLightInt)[0] = myftol( ent->ambientLight[0] ); + ((byte *)&ent->ambientLightInt)[1] = myftol( ent->ambientLight[1] ); + ((byte *)&ent->ambientLightInt)[2] = myftol( ent->ambientLight[2] ); + ((byte *)&ent->ambientLightInt)[3] = 0xff; + + // transform the direction to local space + VectorNormalize( lightDir ); + + ent->lightDir[0] = DotProduct( lightDir, ent->e.axis[0] ); + ent->lightDir[1] = DotProduct( lightDir, ent->e.axis[1] ); + ent->lightDir[2] = DotProduct( lightDir, ent->e.axis[2] ); + + VectorNormalize( ent->lightDir ); +} + +inline void Short2Float(float *f, const short *s) +{ + *f = ((float)*s); +} + +void VVLightManager::ShortToVec3(const short in[3], vec3_t &out) +{ + Short2Float(&out[0], &in[0]); + Short2Float(&out[1], &in[1]); + Short2Float(&out[2], &in[2]); +} + +int VVLightManager::BoxOnPlaneSide (const short emins[3], const short emaxs[3], struct cplane_s *p) +{ + vec3_t mins; + vec3_t maxs; + ShortToVec3(emins, mins); + ShortToVec3(emaxs, maxs); + return ::BoxOnPlaneSide(mins, maxs, &tr.viewParms.frustum[0]); +} + + +void VVLightManager::R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ) { + + do { + int newDlights[2]; + + // if the node wasn't marked as potentially visible, exit + if (node->visframe != tr.visCount) { + return; + } + + // if the bounding volume is outside the frustum, nothing + // inside can be visible OPTIMIZE: don't do this all the way to leafs? + + if ( !r_nocull->integer ) { + int r; + + if ( planeBits & 1 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~1; // all descendants will also be in front + } + } + + if ( planeBits & 2 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~2; // all descendants will also be in front + } + } + + if ( planeBits & 4 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~4; // all descendants will also be in front + } + } + + if ( planeBits & 8 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~8; // all descendants will also be in front + } + } + + } + + if ( node->contents != -1 ) { + break; + } + + // node is just a decision point, so go down both sides + // since we don't care about sort orders, just go positive to negative + + // determine which dlights are needed + newDlights[0] = 0; + newDlights[1] = 0; + if ( dlightBits ) { + int i; + + for ( i = 0; i < tr.refdef.num_dlights; i++ ) { + VVdlight_t *dl; + float dist; + + if ( dlightBits & ( 1 << i ) ) { + dl = &tr.refdef.dlights[i]; + dist = DotProduct( dl->origin, + tr.world->planes[node->planeNum].normal ) - + tr.world->planes[node->planeNum].dist; + + if ( dist > -dl->radius ) { + newDlights[0] |= ( 1 << i ); + } + if ( dist < dl->radius ) { + newDlights[1] |= ( 1 << i ); + } + } + } + } + + // recurse down the children, front side first + R_RecursiveWorldNode (node->children[0], planeBits, newDlights[0] ); + + // tail recurse + node = node->children[1]; + dlightBits = newDlights[1]; + } while ( 1 ); + + { + // leaf node, so add mark surfaces + int c; + msurface_t *surf, **mark; + mleaf_s *leaf; + + tr.pc.c_leafs++; + + // add to z buffer bounds + if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) { + tr.viewParms.visBounds[0][0] = node->mins[0]; + } + if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) { + tr.viewParms.visBounds[0][1] = node->mins[1]; + } + if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) { + tr.viewParms.visBounds[0][2] = node->mins[2]; + } + + if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) { + tr.viewParms.visBounds[1][0] = node->maxs[0]; + } + if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) { + tr.viewParms.visBounds[1][1] = node->maxs[1]; + } + if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) { + tr.viewParms.visBounds[1][2] = node->maxs[2]; + } + + // add the individual surfaces + leaf = (mleaf_s*)node; + mark = tr.world->marksurfaces + leaf->firstMarkSurfNum; + c = leaf->nummarksurfaces; + while (c--) { + // the surface may have already been added if it + // spans multiple leafs + surf = *mark; + + R_AddWorldSurface( surf, dlightBits ); + mark++; + } + } + +} + + +void VVLightManager::RB_CalcDiffuseColor( DWORD *colors ) +{ + trRefEntity_t *ent; + + ent = backEnd.currentEntity; + + // Make sure to turn lighting on.... + qglEnable(GL_LIGHTING); + + qglLightfv(0, GL_AMBIENT, ent->ambientLight); + qglLightfv(0, GL_DIFFUSE, ent->directedLight); + + if(VectorLengthSquared(ent->lightDir) <= 0.0001f) + { + ent->lightDir[0] = 0.0f; + ent->lightDir[1] = 1.0f; + ent->lightDir[2] = 0.0f; + } + + qglLightfv(0, GL_SPOT_DIRECTION, ent->lightDir); + + /*if(VectorLengthSquared(ent->dlightDir) > 0.0f && ModelMem.inUI == false) + { + qglLightfv(1, GL_AMBIENT, ent->ambientLight); + qglLightfv(1, GL_DIFFUSE, ent->dynamicLight); + qglLightfv(1, GL_SPOT_DIRECTION, ent->dlightDir); + }*/ + + memset(colors, 0xffffffff, sizeof(DWORD) * tess.numVertexes); +} + + +void VVLightManager::RB_CalcDiffuseEntityColor( DWORD *colors ) +{ + if ( !backEnd.currentEntity ) + {//error, use the normal lighting + RB_CalcDiffuseColor(colors); + } + + trRefEntity_t *ent; + + ent = backEnd.currentEntity; + + // Make sure to turn lighting on.... + qglEnable(GL_LIGHTING); + + // Modulate ambient by entity color: + vec3_t ambient; + ambient[0] = ent->ambientLight[0] * (ent->e.shaderRGBA[0]/255.0); + ambient[1] = ent->ambientLight[1] * (ent->e.shaderRGBA[1]/255.0); + ambient[2] = ent->ambientLight[2] * (ent->e.shaderRGBA[2]/255.0); + qglLightfv(0, GL_AMBIENT, ambient); + qglLightfv(0, GL_DIFFUSE, ent->directedLight); + + VectorNormalize(ent->lightDir); + + if(VectorLengthSquared(ent->lightDir) <= 0.0001f) + { + ent->lightDir[0] = 0.0f; + ent->lightDir[1] = 1.0f; + ent->lightDir[2] = 0.0f; + } + + qglLightfv(0, GL_SPOT_DIRECTION, ent->lightDir); + + /*if(VectorLengthSquared(ent->dlightDir) > 0.0f && ModelMem.inUI == false) + { + qglLightfv(1, GL_AMBIENT, ent->ambientLight); + qglLightfv(1, GL_DIFFUSE, ent->dynamicLight); + qglLightfv(1, GL_SPOT_DIRECTION, ent->dlightDir); + }*/ + + DWORD color = D3DCOLOR_RGBA(backEnd.currentEntity->e.shaderRGBA[0], + backEnd.currentEntity->e.shaderRGBA[1], + backEnd.currentEntity->e.shaderRGBA[2], + backEnd.currentEntity->e.shaderRGBA[3]); + + memset(colors, color, sizeof(DWORD) * tess.numVertexes); +} + + +//void R_LoadLevelLightdef(const char *filename) +//{ +// const char *text; +// const char *curText; +// char *token; +// VVslight_t light; +// +// VVLightMan.num_slights = 0; +// +// if ( ri.FS_ReadFile( filename, (void**)&curText ) <= 0 ) +// { +// ri.Printf( PRINT_WARNING, "WARNING: no lightdef file found\n" ); +// return; +// } +// +// text = curText; +// +// while(1) +// { +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// +// // Skip to the light's origin +// while(strcmp(token, "origin")) +// { +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// } +// +// // Write the origin +// // X +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// light.origin[0] = atof(token); +// +// // Y +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// light.origin[1] = atof(token); +// +// // Z +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// light.origin[2] = atof(token); +// +// // Skip to the light's range +// while(strcmp(token, "light")) +// { +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// } +// +// // Write the light range +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// light.radius = atof(token); +// +// // Default color for now +// light.color[0] = 1.0f; +// light.color[1] = 1.0f; +// light.color[2] = 1.0f; +// +// VVLightMan.RE_AddStaticLightToScene(&light); +// } +// +// ri.FS_FreeFile( (void*)curText ); +//} + +#define MAX_LIGHT_TABLE 55 + +static levelLightParm_t _levelLightParms[MAX_LIGHT_TABLE]; +static bool isLightInit = false; + +static void ClearLightParmTable(void) +{ + memset(_levelLightParms, 0, sizeof(levelLightParm_t) * MAX_LIGHT_TABLE); + isLightInit = false; +} + +/* +** +** R_GetLightParmsForLevel +** +*/ +void R_GetLightParmsForLevel() +{ + if(!isLightInit) + return; + + char levelname[64]; + + COM_StripExtension(tr.world->baseName, levelname); + + for(int i = 0; i < MAX_LIGHT_TABLE; i++) + { + if(Q_stricmp(COM_SkipPath(levelname), _levelLightParms[i].levelName) == 0) + { + if(VectorLength(_levelLightParms[i].sundir)) + { + Cvar_SetValue("r_sundir_x", _levelLightParms[i].sundir[0]); + Cvar_SetValue("r_sundir_y", _levelLightParms[i].sundir[1]); + Cvar_SetValue("r_sundir_z", _levelLightParms[i].sundir[2]); + } + + if(_levelLightParms[i].hdrEnable) + Cvar_Set("r_hdreffect", "1"); + else + Cvar_Set("r_hdreffect", "0"); + Cvar_SetValue("r_hdrbloom", _levelLightParms[i].hdrBloom); + } + } +} + +/* +** +** R_LoadLevelFogTable +** +*/ +void R_LoadLevelLightParms() +{ + const char *lightText; + const char *curText; + char *token; + int level = 0; + + if ( FS_ReadFile( "shaders/lightparms.txt", (void**)&curText ) <= 0 ) + { + Com_Printf( "WARNING: no light parms file found\n" ); + return; + } + + ClearLightParmTable(); + + lightText = curText; + + while(1) + { + // Level name + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + strcpy( _levelLightParms[level].levelName, token ); + + // Sun dir X + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].sundir[0] = atof(token); + + // Sun dir Y + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].sundir[1] = atof(token); + + // Sun dir Z + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].sundir[2] = atof(token); + + // HDR enable + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].hdrEnable = atof(token); + + // HDR bloom + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].hdrBloom = atof(token); + + level++; + if(level >= MAX_LIGHT_TABLE) + break; + } + + isLightInit = true; + + FS_FreeFile( (void*)curText ); +} + +#endif //VV_LIGHTING \ No newline at end of file diff --git a/codemp/renderer/tr_lightmanager.h b/codemp/renderer/tr_lightmanager.h new file mode 100644 index 0000000..cf8a203 --- /dev/null +++ b/codemp/renderer/tr_lightmanager.h @@ -0,0 +1,34 @@ +/* +** tr_lightmanager.h +*/ + +#ifndef TR_LIGHTMANAGER_H +#define TR_LIGHTMANAGER_H + +#include "tr_local.h" + + +class VVLightManager { +public: + + VVLightManager(); + void R_TransformDlights( int count, VVdlight_t *dl, orientationr_t *ori); + void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); + void RE_AddLightToScene( VVdlight_t *light ); + void R_DlightBmodel( bmodel_t *bmodel, qboolean NoLight ); + int R_DlightFace( srfSurfaceFace_t *face, int dlightBits ); + int R_DlightGrid( srfGridMesh_t *grid, int dlightBits ); + int R_DlightTrisurf( srfTriangles_t *surf, int dlightBits ); + int R_DlightSurface( msurface_t *surf, int dlightBits ); + void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ); + void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ); + void RB_CalcDiffuseColor( DWORD *colors ); + void RB_CalcDiffuseEntityColor( DWORD *colors ); + void ShortToVec3(const short in[3], vec3_t &out); + int BoxOnPlaneSide (const short emins[3], const short emaxs[3], struct cplane_s *p); +}; + +extern VVLightManager VVLightMan; + + +#endif \ No newline at end of file diff --git a/codemp/renderer/tr_local.h b/codemp/renderer/tr_local.h new file mode 100644 index 0000000..1f09e78 --- /dev/null +++ b/codemp/renderer/tr_local.h @@ -0,0 +1,2351 @@ +#ifndef TR_LOCAL_H +#define TR_LOCAL_H + +#ifdef DEDICATED +typedef const char * LPCSTR; +typedef unsigned short USHORT; +typedef unsigned int GLuint; +#endif + +#include "../qcommon/qfiles.h" +#include "tr_public.h" +#if defined(_XBOX) +#include "qgl_console.h" +#include "glext_console.h" +#else +#ifndef DEDICATED +#include "qgl.h" +#endif +#endif +#include "../ghoul2/ghoul2_shared.h" //rwwRMG - added + +#ifdef _XBOX +#define GL_INDEX_TYPE GL_UNSIGNED_SHORT +typedef unsigned short glIndex_t; +#else +#define GL_INDEX_TYPE GL_UNSIGNED_INT +typedef unsigned int glIndex_t; +#endif + +// fast float to int conversion +#if id386 && !( (defined __linux__ || defined __FreeBSD__ ) && (defined __i386__ ) ) // rb010123 +inline long myftol( float f ); +#else +#define myftol(x) ((int)(x)) +#endif + +//for 3d textures -rww +#define GL_TEXTURE_3D 0x806F + +// 14 bits +// see QSORT_SHADERNUM_SHIFT +#ifdef _XBOX +#define MAX_SHADERS 2048 +#else +#define MAX_SHADERS 16384 +#endif +// can't be increased without changing bit packing for drawsurfs + +#define MAX_SHADER_STATES 2048 +#define MAX_STATES_PER_SHADER 32 +#define MAX_STATE_NAME 32 + +typedef enum +{ + DLIGHT_VERTICAL = 0, + DLIGHT_PROJECTED +} eDLightTypes; + +typedef struct dlight_s { + eDLightTypes mType; + + vec3_t origin; + vec3_t mProjOrigin; // projected light's origin + + vec3_t color; // range from 0.0 to 1.0, should be color normalized + + float radius; + float mProjRadius; // desired radius of light + + int additive; // texture detail is lost tho when the lightmap is dark + + vec3_t transformed; // origin in local coordinate system + vec3_t mProjTransformed; // projected light's origin in local coordinate system + + vec3_t mDirection; + vec3_t mBasis2; + vec3_t mBasis3; + + vec3_t mTransDirection; + vec3_t mTransBasis2; + vec3_t mTransBasis3; +} dlight_t; + +// a trMiniRefEntity_t has all the information passed in by +// the client game, other info will come from it's parent main ref entity +typedef struct +{ + miniRefEntity_t e; +} trMiniRefEntity_t; + +// a trRefEntity_t has all the information passed in by +// the client game, as well as some locally derived info +typedef struct { + refEntity_t e; + +// float axisLength; // compensate for non-normalized axis + + qboolean needDlights; // true for bmodels that touch a dlight + qboolean lightingCalculated; + vec3_t lightDir; // normalized direction towards light + vec3_t ambientLight; // color normalized to 0-255 + int ambientLightInt; // 32 bit rgba packed + vec3_t directedLight; +#ifdef _XBOX + vec3_t dlightDir; + vec3_t dynamicLight; +#endif + int dlightBits; +} trRefEntity_t; + + +typedef struct { + vec3_t origin; // in world coordinates + vec3_t axis[3]; // orientation in world + vec3_t viewOrigin; // viewParms->or.origin in local coordinates + float modelMatrix[16]; +} orientationr_t; + +typedef struct image_s { + int imgCode; + +#ifndef FINAL_BUILD + char imgName[MAX_QPATH]; // Only in debug, so imagelist cmd is useful +#endif + + USHORT width, height; // source image + + GLuint texnum; // gl texture binding + int internalFormat; + + bool isLightmap; + bool isSystem; + short mipcount; + +// bool allowPicmip; + short iLastLevelUsedOn; +} image_t; + + +//=============================================================================== + +typedef enum { + SS_BAD, + SS_PORTAL, // mirrors, portals, viewscreens + SS_ENVIRONMENT, // sky box + SS_OPAQUE, // opaque + + SS_DECAL, // scorch marks, etc. + SS_SEE_THROUGH, // ladders, grates, grills that may have small blended edges + // in addition to alpha test + SS_BANNER, + + SS_INSIDE, // inside body parts (i.e. heart) + SS_MID_INSIDE, + SS_MIDDLE, + SS_MID_OUTSIDE, + SS_OUTSIDE, // outside body parts (i.e. ribs) + + SS_FOG, + + SS_UNDERWATER, // for items that should be drawn in front of the water plane + + SS_BLEND0, // regular transparency and filters + SS_BLEND1, // generally only used for additive type effects + SS_BLEND2, + SS_BLEND3, + + SS_BLEND6, + SS_STENCIL_SHADOW, + SS_ALMOST_NEAREST, // gun smoke puffs + + SS_NEAREST // blood blobs +} shaderSort_t; + + +#define MAX_SHADER_STAGES 8 + +typedef enum { + GF_NONE, + + GF_SIN, + GF_SQUARE, + GF_TRIANGLE, + GF_SAWTOOTH, + GF_INVERSE_SAWTOOTH, + + GF_NOISE, + GF_RAND + +} genFunc_t; + + +typedef enum { + DEFORM_NONE, + DEFORM_WAVE, + DEFORM_NORMALS, + DEFORM_BULGE, + DEFORM_MOVE, + DEFORM_PROJECTION_SHADOW, + DEFORM_AUTOSPRITE, + DEFORM_AUTOSPRITE2, + DEFORM_TEXT0, + DEFORM_TEXT1, + DEFORM_TEXT2, + DEFORM_TEXT3, + DEFORM_TEXT4, + DEFORM_TEXT5, + DEFORM_TEXT6, + DEFORM_TEXT7 +} deform_t; + +typedef enum { + AGEN_IDENTITY, + AGEN_SKIP, + AGEN_ENTITY, + AGEN_ONE_MINUS_ENTITY, + AGEN_VERTEX, + AGEN_ONE_MINUS_VERTEX, + AGEN_LIGHTING_SPECULAR, + AGEN_WAVEFORM, + AGEN_PORTAL, + AGEN_BLEND, + AGEN_CONST, + AGEN_DOT, + AGEN_ONE_MINUS_DOT +} alphaGen_t; + +typedef enum { + CGEN_BAD, + CGEN_IDENTITY_LIGHTING, // tr.identityLight + CGEN_IDENTITY, // always (1,1,1,1) + CGEN_ENTITY, // grabbed from entity's modulate field + CGEN_ONE_MINUS_ENTITY, // grabbed from 1 - entity.modulate + CGEN_EXACT_VERTEX, // tess.vertexColors + CGEN_VERTEX, // tess.vertexColors * tr.identityLight + CGEN_ONE_MINUS_VERTEX, + CGEN_WAVEFORM, // programmatically generated + CGEN_LIGHTING_DIFFUSE, + CGEN_LIGHTING_DIFFUSE_ENTITY, //diffuse lighting * entity + CGEN_FOG, // standard fog + CGEN_CONST, // fixed color + CGEN_LIGHTMAPSTYLE, +} colorGen_t; + +typedef enum { + TCGEN_BAD, + TCGEN_IDENTITY, // clear to 0,0 + TCGEN_LIGHTMAP, + TCGEN_LIGHTMAP1, + TCGEN_LIGHTMAP2, + TCGEN_LIGHTMAP3, + TCGEN_TEXTURE, + TCGEN_ENVIRONMENT_MAPPED, + TCGEN_FOG, + TCGEN_VECTOR // S and T from world coordinates +} texCoordGen_t; + +typedef enum { + ACFF_NONE, + ACFF_MODULATE_RGB, + ACFF_MODULATE_RGBA, + ACFF_MODULATE_ALPHA +} acff_t; + +typedef enum { + GLFOGOVERRIDE_NONE = 0, + GLFOGOVERRIDE_BLACK, + GLFOGOVERRIDE_WHITE, + + GLFOGOVERRIDE_MAX +} EGLFogOverride; + +typedef struct { + genFunc_t func; + + float base; + float amplitude; + float phase; + float frequency; +} waveForm_t; + +#define TR_MAX_TEXMODS 4 + +typedef enum { + TMOD_NONE, + TMOD_TRANSFORM, + TMOD_TURBULENT, + TMOD_SCROLL, + TMOD_SCALE, + TMOD_STRETCH, + TMOD_ROTATE, + TMOD_ENTITY_TRANSLATE +} texMod_t; + +#define MAX_SHADER_DEFORMS 3 +typedef struct { + deform_t deformation; // vertex coordinate modification type + + vec3_t moveVector; + waveForm_t deformationWave; + float deformationSpread; + + float bulgeWidth; + float bulgeHeight; + float bulgeSpeed; +} deformStage_t; + + +typedef struct { + texMod_t type; + + // used for TMOD_TURBULENT and TMOD_STRETCH + waveForm_t wave; + + // used for TMOD_TRANSFORM + float matrix[2][2]; // s' = s * m[0][0] + t * m[1][0] + trans[0] + float translate[2]; // t' = s * m[0][1] + t * m[0][1] + trans[1] + + // used for TMOD_SCALE + //(moved to translate) +// float scale[2]; // s *= scale[0] + // t *= scale[1] + + // used for TMOD_SCROLL + //(moved to translate) +// float scroll[2]; // s' = s + scroll[0] * time + // t' = t + scroll[1] * time + + // + = clockwise + // - = counterclockwise + ////(moved to translate[0]) +// float rotateSpeed; + +} texModInfo_t; + + +#define SURFSPRITE_NONE 0 +#define SURFSPRITE_VERTICAL 1 +#define SURFSPRITE_ORIENTED 2 +#define SURFSPRITE_EFFECT 3 +#define SURFSPRITE_WEATHERFX 4 + +#define SURFSPRITE_FACING_NORMAL 0 +#define SURFSPRITE_FACING_UP 1 +#define SURFSPRITE_FACING_DOWN 2 +#define SURFSPRITE_FACING_ANY 3 + + +typedef struct surfaceSprite_s +{ + int surfaceSpriteType; + float width, height, density, wind, windIdle, fadeDist, fadeMax, fadeScale; + float fxAlphaStart, fxAlphaEnd, fxDuration, vertSkew; + vec2_t variance, fxGrow; + int facing; // Hangdown on vertical sprites, faceup on others. +} surfaceSprite_t; + +typedef struct { + image_t *image; + + vec3_t *tcGenVectors; + + texModInfo_t *texMods; + short numTexMods; + short numImageAnimations; + float imageAnimationSpeed; + +// texCoordGen_t tcGen; + byte tcGen; + byte isLightmap; + byte oneShotAnimMap; + byte vertexLightmap; +// byte isVideoMap; + +// int videoMapHandle; +} textureBundle_t; + + +#define NUM_TEXTURE_BUNDLES 2 + +typedef struct { + byte active; + byte isDetail; + byte isEnvironment; +// byte isSpecular; + byte isBumpMap; + + byte index; // index of stage + byte lightmapStyle; + byte alphaGen; + byte rgbGen; + + byte adjustColorsForFog; + byte mGLFogColorOverride; + + // Whether this object emits a glow or not. +// byte glow; + + textureBundle_t bundle[NUM_TEXTURE_BUNDLES]; + + waveForm_t rgbWave; + waveForm_t alphaWave; + + byte constantColor[4]; // for CGEN_CONST and AGEN_CONST + + unsigned int stateBits; // GLS_xxxx mask + + surfaceSprite_t *ss; + +} shaderStage_t; + +struct shaderCommands_s; + +#define LIGHTMAP_2D -4 // shader is for 2D rendering +#define LIGHTMAP_BY_VERTEX -3 // pre-lit triangle models +#define LIGHTMAP_WHITEIMAGE -2 +#define LIGHTMAP_NONE -1 + +typedef enum { + CT_FRONT_SIDED, + CT_BACK_SIDED, + CT_TWO_SIDED +} cullType_t; + +typedef enum { + FP_NONE, // surface is translucent and will just be adjusted properly + FP_EQUAL, // surface is opaque but possibly alpha tested + FP_LE, // surface is trnaslucent, but still needs a fog pass (fog surface) + FP_GLFOG +} fogPass_t; + +typedef struct { + float cloudHeight; + image_t *outerbox[6]; +} skyParms_t; + +typedef struct { + vec3_t color; + float depthForOpaque; +} fogParms_t; + +typedef struct shader_s { + char name[MAX_QPATH]; // game path, including extension + short lightmapIndex[MAXLIGHTMAPS]; // for a shader to match, both name and lightmapIndex must match + byte styles[MAXLIGHTMAPS]; + + short index; // this shader == tr.shaders[index] + short sortedIndex; // this shader == tr.sortedShaders[sortedIndex] + + float sort; // lower numbered shaders draw before higher numbered + + int surfaceFlags; // if explicitlyDefined, this will have SURF_* flags + int contentFlags; + + char defaultShader; // we want to return index 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + char explicitlyDefined; // found in a .shader file + char entityMergable; // merge across entites optimizable (smoke, blood) + + char isBumpMap; + + skyParms_t *sky; + fogParms_t *fogParms; + + float portalRange; // distance to fog out at + + int multitextureEnv; // 0, GL_MODULATE, GL_ADD (FIXME: put in stage) + + cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED + char polygonOffset; // set for decals and other items that must be offset + char noMipMaps; // for console fonts, 2D elements, etc. +// bool noPicMip; // for images that must always be full resolution +// bool noTC; // for images that don't want to be texture compressed (eg skies) +#ifdef _XBOX + char needsNormal; + char needsTangent; +#endif + + + fogPass_t fogPass; // draw a blended pass, possibly with depth test equals + +// vec3_t bumpVector; // The given light vector for bump-mapping + + deformStage_t *deforms[MAX_SHADER_DEFORMS]; + short numDeforms; + + short numUnfoggedPasses; + shaderStage_t *stages; + +// float clampTime; // time this shader is clamped to + float timeOffset; // current time offset for this shader + +#ifndef _XBOX // GLOWXXX + // True if this shader has a stage with glow in it (just an optimization). + bool hasGlow; +#endif + +/* + int numStates; // if non-zero this is a state shader + struct shader_s *currentShader; // current state if this is a state shader + struct shader_s *parentShader; // current state if this is a state shader + int currentState; // current state index for cycle purposes + long expireTime; // time in milliseconds this expires + + int shaderStates[MAX_STATES_PER_SHADER]; // index to valid shader states +*/ + + struct shader_s *remappedShader; // current shader this one is remapped too + struct shader_s *next; +} shader_t; + +typedef struct shaderState_s { + char shaderName[MAX_QPATH]; // name of shader this state belongs to + char name[MAX_STATE_NAME]; // name of this state + char stateShader[MAX_QPATH]; // shader this name invokes + int cycleTime; // time this cycle lasts, <= 0 is forever + shader_t *shader; +} shaderState_t; + +/* +Ghoul2 Insert Start +*/ + // bogus little registration system for hit and location based damage files in hunk memory +typedef struct +{ + byte *loc; + int width; + int height; + char name[MAX_QPATH]; +} hitMatReg_t; + +#define MAX_HITMAT_ENTRIES 1000 + +extern hitMatReg_t hitMatReg[MAX_HITMAT_ENTRIES]; + +/* +Ghoul2 Insert End +*/ + + +// trRefdef_t holds everything that comes in refdef_t, +// as well as the locally generated scene information +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + int time; // time in milliseconds for shader effects and other time dependent rendering issues + int frametime; + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + qboolean areamaskModified; // qtrue if areamask changed since last scene + + float floatTime; // tr.refdef.time / 1000.0 + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; + + int num_entities; + trRefEntity_t *entities; + trMiniRefEntity_t *miniEntities; + + int num_dlights; + struct dlight_s *dlights; + + int numPolys; + struct srfPoly_s *polys; + + int numDrawSurfs; + struct drawSurf_s *drawSurfs; + + +} trRefdef_t; + + +//================================================================================= + +// skins allow models to be retextured without modifying the model file +typedef struct { + char name[MAX_QPATH]; + shader_t *shader; +} skinSurface_t; + +typedef struct skin_s { + char name[MAX_QPATH]; // game path, including extension + int numSurfaces; + skinSurface_t *surfaces[128]; +} skin_t; + + +typedef struct { + int originalBrushNumber; + vec3_t bounds[2]; + + unsigned colorInt; // in packed byte format + float tcScale; // texture coordinate vector scales + fogParms_t parms; + + // for clipping distance in fog when outside + qboolean hasSurface; + float surface[4]; +} fog_t; + +typedef struct { + orientationr_t ori; // Can't use "or" as it is a reserved word with gcc DREWS 2/2/2002 + orientationr_t world; + vec3_t pvsOrigin; // may be different than or.origin for portals + qboolean isPortal; // true if this view is through a portal + qboolean isMirror; // the portal is a mirror, invert the face culling + int frameSceneNum; // copied from tr.frameSceneNum + int frameCount; // copied from tr.frameCount + cplane_t portalPlane; // clip anything behind this if mirroring + int viewportX, viewportY, viewportWidth, viewportHeight; + float fovX, fovY; + float projectionMatrix[16]; + cplane_t frustum[4]; + vec3_t visBounds[2]; + float zFar; +} viewParms_t; + + +/* +============================================================================== + +SURFACES + +============================================================================== +*/ + +// any changes in surfaceType must be mirrored in rb_surfaceTable[] +typedef enum { + SF_BAD, + SF_SKIP, // ignore + SF_FACE, + SF_GRID, + SF_TRIANGLES, + SF_POLY, + SF_TERRAIN, //rwwRMG - added + SF_MD3, +/* +Ghoul2 Insert Start +*/ + SF_MDX, +/* +Ghoul2 Insert End +*/ + SF_FLARE, + SF_ENTITY, // beams, rails, lightning, etc that can be determined by entity + SF_DISPLAY_LIST, + + SF_NUM_SURFACE_TYPES, + SF_MAX = 0xffffffff // ensures that sizeof( surfaceType_t ) == sizeof( int ) +} surfaceType_t; + +typedef struct drawSurf_s { + unsigned sort; // bit combination for fast compares + surfaceType_t *surface; // any of surface*_t +} drawSurf_t; + +#define MAX_FACE_POINTS 64 + +#define MAX_PATCH_SIZE 32 // max dimensions of a patch mesh in map file +#define MAX_GRID_SIZE 65 // max dimensions of a grid mesh in memory + +// when cgame directly specifies a polygon, it becomes a srfPoly_t +// as soon as it is called +typedef struct srfPoly_s { + surfaceType_t surfaceType; + qhandle_t hShader; + int fogIndex; + int numVerts; + polyVert_t *verts; +} srfPoly_t; + +typedef struct srfDisplayList_s { + surfaceType_t surfaceType; + int listNum; +} srfDisplayList_t; + +#ifdef _XBOX +typedef struct srfFlare_s { + surfaceType_t surfaceType; + unsigned short origin[3]; + unsigned short normal[3]; + byte color[3]; + byte number; + int visible; +} srfFlare_t; + +#else // _XBOX + +typedef struct srfFlare_s { + surfaceType_t surfaceType; + vec3_t origin; + vec3_t normal; + vec3_t color; +} srfFlare_t; + +#endif + +#ifdef _XBOX +// Added tangent size in here +#define VERTEXSIZE (9+(MAXLIGHTMAPS*3)) +#define VERTEX_LM 8 +#define VERTEX_COLOR(flags) (VERTEX_LM + (((flags) & 0x7F) * 2)) +#else +#define VERTEXSIZE (6+(MAXLIGHTMAPS*3)) +#define VERTEX_LM 5 +#define VERTEX_COLOR (5+(MAXLIGHTMAPS*2)) +#endif + +#define VERTEX_FINAL_COLOR (5+(MAXLIGHTMAPS*3)) + +#ifdef _XBOX +#define NEXT_SURFPOINT(flags) (VERTEX_LM + (((flags) & 0x7F) * 2) + ((((flags) & 0x80) >> 7) * 4)); +#define POINTS_ST_SCALE 128.0f +#define POINTS_LIGHT_SCALE 65536.0f +#define GLM_COMP_SIZE 64.0f +#define GLM_COMP_UV_SIZE 16384.0f +#endif // _XBOX + +typedef struct srfTerrain_s +{ + surfaceType_t surfaceType; + class CTRLandScape *landscape; +} srfTerrain_t; + +typedef struct srfGridMesh_s { + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits; + + // culling information + vec3_t meshBounds[2]; + vec3_t localOrigin; + float meshRadius; + + // lod information, which may be different + // than the culling information to allow for + // groups of curves that LOD as a unit + vec3_t lodOrigin; + float lodRadius; + int lodFixed; + int lodStitched; + + // vertexes + int width, height; + float *widthLodError; + float *heightLodError; + drawVert_t verts[1]; // variable sized +} srfGridMesh_t; + +#ifdef _XBOX +#pragma pack (push, 1) +typedef struct { + surfaceType_t surfaceType; + cplane_t plane; + + // dynamic lighting information + int dlightBits; + + // triangle definitions (no normals at points) + unsigned char numPoints; + unsigned short numIndices; + unsigned short ofsIndices; + unsigned char flags; //highest bit - true if face uses vertex colors, + //low 7 bits - number of light maps +// vec3_t *tangents; + unsigned short *srfPoints; // variable sized + // there is a variable length list of indices here also +} srfSurfaceFace_t; +#pragma pack (pop) + +#else // _XBOX + +typedef struct { + surfaceType_t surfaceType; + cplane_t plane; + + // dynamic lighting information + int dlightBits; + + // triangle definitions (no normals at points) + int numPoints; + int numIndices; + int ofsIndices; + float points[1][VERTEXSIZE]; // variable sized + // there is a variable length list of indices here also +} srfSurfaceFace_t; + +#endif // _XBOX + + +// misc_models in maps are turned into direct geometry by q3map +typedef struct { + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits; + + // culling information (FIXME: use this!) + vec3_t bounds[2]; +// vec3_t localOrigin; +// float radius; + + // triangle definitions + int numIndexes; + int *indexes; + + int numVerts; + drawVert_t *verts; +// vec3_t *tangents; +} srfTriangles_t; + + +extern void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])(void *); + +/* +============================================================================== + +TERRAIN DATA + +============================================================================== +*/ + +void RE_InitRendererTerrain( const char *info ); +void RB_SurfaceTerrain( surfaceInfo_t *surface ); +void R_TerrainInit (void); +void R_TerrainShutdown(void); + + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + +// +// in memory representation +// + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + +typedef struct msurface_s { + int viewCount; // if == tr.viewCount, already added + struct shader_s *shader; + int fogIndex; + + surfaceType_t *data; // any of srf*_t +} msurface_t; + + + +#define CONTENTS_NODE -1 + +#ifdef _XBOX +#pragma pack (push, 1) +typedef struct mnode_s { + // common with leaf and node + signed char contents; // -1 for nodes, to differentiate from leafs + int visframe; // node needs to be traversed if current + short mins[3], maxs[3]; // for bounding box culling + struct mnode_s *parent; + + // node specific + unsigned int planeNum; + struct mnode_s *children[2]; + +} mnode_t; + +struct mleaf_s { + // common with leaf and node + signed char contents; // -1 for nodes, to differentiate from leafs + int visframe; // node needs to be traversed if current + short mins[3], maxs[3]; // for bounding box culling + struct mnode_s *parent; + + // leaf specific + short cluster; + signed char area; + + unsigned short firstMarkSurfNum; + short nummarksurfaces; +}; +#pragma pack (pop) + +#else // _XBOX + +typedef struct mnode_s { + // common with leaf and node + int contents; // -1 for nodes, to differentiate from leafs + int visframe; // node needs to be traversed if current + vec3_t mins, maxs; // for bounding box culling + struct mnode_s *parent; + + // node specific + cplane_t *plane; + struct mnode_s *children[2]; + + // leaf specific + int cluster; + int area; + + msurface_t **firstmarksurface; + int nummarksurfaces; +} mnode_t; + +#endif // _XBOX + +typedef struct { + vec3_t bounds[2]; // for culling + msurface_t *firstSurface; + int numSurfaces; +} bmodel_t; + +#ifdef _XBOX +#pragma pack(push, 1) +typedef struct { + byte flags; + byte latLong[2]; + int data; + + /* + flags has the following bits: + 0 - ambientLight[0] and directLight[0] are not all zeros + 1 - ambientLight[1] and directLight[1] are not all zeros + 2 - ambientLight[2] and directLight[2] are not all zeros + 3 - ambientLight[3] and directLight[3] are not all zeros + 4 - styles[0] is not LS_NONE + 5 - styles[1] is not LS_NONE + 6 - styles[2] is not LS_NONE + 7 - styles[3] is not LS_NONE + + data points to memory which stores ambientLight, directLight and + styles when they are not 0 or LS_NONE. + */ +} mgrid_t; +#pragma pack(pop) + +#else // _XBOX + +typedef struct +{ + byte ambientLight[MAXLIGHTMAPS][3]; + byte directLight[MAXLIGHTMAPS][3]; + byte styles[MAXLIGHTMAPS]; + byte latLong[2]; +// byte pad[2]; // to align to a cache line +} mgrid_t; + +#endif // _XBOX + + +#ifdef _XBOX +template +class SPARC; +typedef struct { + char name[MAX_QPATH]; // ie: maps/tim_dm2.bsp + char baseName[MAX_QPATH]; // ie: tim_dm2 + + int numShaders; + dshader_t *shaders; + + bmodel_t *bmodels; + + int numplanes; + cplane_t *planes; + + int numnodes; // includes leafs + int numDecisionNodes; + mnode_t *nodes; + + int numleafs; + mleaf_s *leafs; + + int numsurfaces; + msurface_t *surfaces; + + int nummarksurfaces; + msurface_t **marksurfaces; + + int numfogs; + fog_t *fogs; + int globalFog; + + int startLightMapIndex; + + vec3_t lightGridOrigin; + vec3_t lightGridSize; + vec3_t lightGridInverseSize; + int lightGridBounds[3]; + mgrid_t *lightGridData; + unsigned short *lightGridArray; + int numGridArrayElements; + + int numClusters; + int clusterBytes; + + SPARC *vis; + + byte *novis; // clusterBytes of 0xff + + qboolean portalPresent; + + char *entityString; + char *entityParsePoint; +} world_t; + +#else // _XBOX + +typedef struct { + char name[MAX_QPATH]; // ie: maps/tim_dm2.bsp + char baseName[MAX_QPATH]; // ie: tim_dm2 + + int dataSize; + + int numShaders; + dshader_t *shaders; + + bmodel_t *bmodels; + + int numplanes; + cplane_t *planes; + + int numnodes; // includes leafs + int numDecisionNodes; + mnode_t *nodes; + + int numsurfaces; + msurface_t *surfaces; + + int nummarksurfaces; + msurface_t **marksurfaces; + + int numfogs; + fog_t *fogs; + int globalFog; + + + vec3_t lightGridOrigin; + vec3_t lightGridSize; + vec3_t lightGridInverseSize; + int lightGridBounds[3]; + + int lightGridOffsets[8]; + + vec3_t lightGridStep; + + mgrid_t *lightGridData; + word *lightGridArray; + int numGridArrayElements; + + + int numClusters; + int clusterBytes; + const byte *vis; // may be passed in by CM_LoadMap to save space + + byte *novis; // clusterBytes of 0xff + + char *entityString; + char *entityParsePoint; +} world_t; + +#endif // _XBOX + +//====================================================================== +/* +Ghoul2 Insert Start +*/ +#define MDXABONEDEF +#include "mdx_format.h" +/* +Ghoul2 Insert End +*/ +typedef enum { + MOD_BAD, + MOD_BRUSH, + MOD_MESH, +/* +Ghoul2 Insert Start +*/ + MOD_MDXM, + MOD_MDXA +/* +Ghoul2 Insert End +*/ +} modtype_t; + +typedef struct model_s { + char name[MAX_QPATH]; + modtype_t type; + int index; // model = tr.models[model->index] + + int dataSize; // just for listing purposes + bmodel_t *bmodel; // only if type == MOD_BRUSH + md3Header_t *md3[MD3_MAX_LODS]; // only if type == MOD_MESH +/* +Ghoul2 Insert Start +*/ + mdxmHeader_t *mdxm; // only if type == MOD_GL2M which is a GHOUL II Mesh file NOT a GHOUL II animation file + mdxaHeader_t *mdxa; // only if type == MOD_GL2A which is a GHOUL II Animation file +/* +Ghoul2 Insert End +*/ + int numLods; + qboolean bspInstance; +} model_t; + + +#define MAX_MOD_KNOWN 1024 + +void R_ModelInit (void); +void R_InitDecals (void); + +model_t *R_GetModelByHandle( qhandle_t hModel ); +int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ); +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ); + +void R_Modellist_f (void); + +//==================================================== + + +// An offscreen buffer used for secondary rendering and render-to-texture support (RTT). - AReis +#ifndef _XBOX // GLOWXXX +#ifndef DEDICATED +class CPBUFFER +{ +private: + // Pixel Buffer Rendering and Device Contexts. + HGLRC m_hRC; + HDC m_hDC; + + // The render and device contexts for the previous render target. + HGLRC m_hOldRC; + HDC m_hOldDC; + + // Buffer handle. + HPBUFFERARB m_hBuffer; + + // Buffer Dimensions. + int m_iWidth, m_iHeight; + + // Color, depth, and stencil bits for this buffer. + int m_iColorBits, m_iDepthBits, m_iStencilBits; + +public: + // Texture used for displaying the pbuffer. + GLuint m_uiPBufferTexture; + + // Constructor. + CPBUFFER() {} + + // Destructor. + ~CPBUFFER() {} + + // Allocate and create a new PBuffer. + bool Create( int iWidth, int iHeight, int iColorBits, int iDepthBits, int iStencilBits ); + + // Destroy and deallocate a PBuffer. + void Destroy(); + + // Make this PBuffer the current render device. + bool Begin(); + + // Restore the previous render device. + bool End(); +}; +#endif // DEDICATED +#endif // _XBOX + + +#define MAX_DRAWIMAGES 2048 +#define MAX_LIGHTMAPS 256 +#define MAX_SKINS 1024 + + +#ifdef _XBOX +#define MAX_DRAWSURFS 0x4000 +#else +#define MAX_DRAWSURFS 0x10000 +#endif +#define DRAWSURF_MASK (MAX_DRAWSURFS-1) + +/* + +the drawsurf sort data is packed into a single 32 bit value so it can be +compared quickly during the qsorting process + +the bits are allocated as follows: + +18-31 : sorted shader index +7-17 : entity index +2-6 : fog index +0-1 : dlightmap index +*/ +#define QSORT_SHADERNUM_SHIFT 18 +#define QSORT_ENTITYNUM_SHIFT 7 +#define QSORT_FOGNUM_SHIFT 2 + +extern int gl_filter_min, gl_filter_max; + +/* +** performanceCounters_t +*/ +typedef struct { + int c_sphere_cull_patch_in, c_sphere_cull_patch_clip, c_sphere_cull_patch_out; + int c_box_cull_patch_in, c_box_cull_patch_clip, c_box_cull_patch_out; + int c_sphere_cull_md3_in, c_sphere_cull_md3_clip, c_sphere_cull_md3_out; + int c_box_cull_md3_in, c_box_cull_md3_clip, c_box_cull_md3_out; + + int c_leafs; + int c_dlightSurfaces; + int c_dlightSurfacesCulled; +} frontEndCounters_t; + +#define FOG_TABLE_SIZE 256 +#define FUNCTABLE_SIZE 1024 +#define FUNCTABLE_SIZE2 10 +#define FUNCTABLE_MASK (FUNCTABLE_SIZE-1) + + +// the renderer front end should never modify glstate_t +typedef struct { + int currenttextures[2]; + int currenttmu; + qboolean finishCalled; + int texEnv[2]; + int faceCulling; + unsigned long glStateBits; +} glstate_t; + + +typedef struct { + int c_surfaces, c_shaders, c_vertexes, c_indexes, c_totalIndexes; + float c_overDraw; + + int c_dlightVertexes; + int c_dlightIndexes; + + int c_flareAdds; + int c_flareTests; + int c_flareRenders; + + int msec; // total msec for backend run +} backEndCounters_t; + +// all state modified by the back end is seperated +// from the front end state +typedef struct { + trRefdef_t refdef; + viewParms_t viewParms; + orientationr_t ori; // Can't use or as it is a c++ reserved word DREWS 2/2/2002 + backEndCounters_t pc; + qboolean isHyperspace; + trRefEntity_t *currentEntity; + qboolean skyRenderedThisView; // flag for drawing sun + + qboolean projection2D; // if qtrue, drawstretchpic doesn't need to change modes + byte color2D[4]; + qboolean vertexes2D; // shader needs to be finished + trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering +} backEndState_t; + +/* +** trGlobals_t +** +** Most renderer globals are defined here. +** backend functions should never modify any of these fields, +** but may read fields that aren't dynamically modified +** by the frontend. +*/ + +#ifdef _XBOX +#define NUM_SCRATCH_IMAGES 1 +#else +#define NUM_SCRATCH_IMAGES 16 +#endif + +#define SCREEN_IMAGE_MAX_WIDTH 512 +#define SCREEN_IMAGE_MAX_HEIGHT 256 + +typedef struct { + qboolean registered; // cleared at shutdown, set at beginRegistration + + int visCount; // incremented every time a new vis cluster is entered + int frameCount; // incremented every frame + int sceneCount; // incremented every scene + int viewCount; // incremented every view (twice a scene if portaled) + // and every R_MarkFragments call + + int frameSceneNum; // zeroed at RE_BeginFrame + + qboolean worldMapLoaded; + world_t *world; + +#ifdef _XBOX + SPARC *externalVisData; // from RE_SetWorldVisData, shared with CM_Load +#else + const byte *externalVisData; // from RE_SetWorldVisData, shared with CM_Load +#endif + + image_t *defaultImage; +// image_t *scratchImage[NUM_SCRATCH_IMAGES]; + image_t *fogImage; + image_t *dlightImage; // inverse-quare highlight for projective adding + image_t *flareImage; + image_t *whiteImage; // full of 0xff +// image_t *identityLightImage; // full of tr.identityLightByte + + image_t *screenImage; //reserve us a gl texnum to use with RF_DISTORTION + +#ifndef _XBOX // GLOWXXX + // Handle to the Glow Effect Vertex Shader. - AReis + GLuint glowVShader; + + // Handle to the Glow Effect Pixel Shader. - AReis + GLuint glowPShader; + + // Image the glowing objects are rendered to. - AReis + GLuint screenGlow; + + // A rectangular texture representing the normally rendered scene. + GLuint sceneImage; + + // Image used to downsample and blur scene to. - AReis + GLuint blurImage; +#endif + + shader_t *defaultShader; + shader_t *shadowShader; + shader_t *distortionShader; + shader_t *projectionShadowShader; + + shader_t *sunShader; + + int numLightmaps; + image_t *lightmaps[MAX_LIGHTMAPS]; + + trRefEntity_t *currentEntity; + trRefEntity_t worldEntity; // point currentEntity at this when rendering world + int currentEntityNum; + int shiftedEntityNum; // currentEntityNum << QSORT_ENTITYNUM_SHIFT + model_t *currentModel; + + viewParms_t viewParms; + + float identityLight; // 1.0 / ( 1 << overbrightBits ) + int identityLightByte; // identityLight * 255 + int overbrightBits; // r_overbrightBits->integer, but set to 0 if no hw gamma + + orientationr_t ori; // for current entity + + trRefdef_t refdef; + + int viewCluster; + + vec3_t sunLight; // from the sky shader for this level + vec3_t sunDirection; + int sunSurfaceLight; // from the sky shader for this level + vec3_t sunAmbient; // from the sky shader (only used for John's terrain system) + + frontEndCounters_t pc; + int frontEndMsec; // not in pc due to clearing issue + + // + // put large tables at the end, so most elements will be + // within the +/32K indexed range on risc processors + // + model_t *models[MAX_MOD_KNOWN]; + int numModels; + +/* + world_t bspModels[MAX_SUB_BSP]; + int numBSPModels; +*/ + + // shader indexes from other modules will be looked up in tr.shaders[] + // shader indexes from drawsurfs will be looked up in sortedShaders[] + // lower indexed sortedShaders must be rendered first (opaque surfaces before translucent) + int numShaders; + shader_t *shaders[MAX_SHADERS]; + shader_t *sortedShaders[MAX_SHADERS]; + + int numSkins; + skin_t *skins[MAX_SKINS]; + + float sinTable[FUNCTABLE_SIZE]; + float squareTable[FUNCTABLE_SIZE]; + float triangleTable[FUNCTABLE_SIZE]; + float sawToothTable[FUNCTABLE_SIZE]; + float inverseSawToothTable[FUNCTABLE_SIZE]; + float fogTable[FOG_TABLE_SIZE]; + + float rangedFog; + float distanceCull, distanceCullSquared; //rwwRMG - added + + srfTerrain_t landScape; //rwwRMG - added +} trGlobals_t; + + +int R_Images_StartIteration(void); +image_t *R_Images_GetNextIteration(void); +void R_Images_Clear(void); +void R_Images_DeleteLightMaps(void); +void R_Images_DeleteImage(image_t *pImage); + + +extern backEndState_t backEnd; +extern trGlobals_t tr; +extern glconfig_t glConfig; // outside of TR since it shouldn't be cleared during ref re-init +extern glstate_t glState; // outside of TR since it shouldn't be cleared during ref re-init + + +// +// cvars +// +extern cvar_t *r_ignore; // used for debugging anything +extern cvar_t *r_verbose; // used for verbose debug spew + +extern cvar_t *r_znear; // near Z clip plane + +extern cvar_t *r_stencilbits; // number of desired stencil bits +extern cvar_t *r_depthbits; // number of desired depth bits +extern cvar_t *r_colorbits; // number of desired color bits, only relevant for fullscreen +extern cvar_t *r_stereo; // desired pixelformat stereo flag +extern cvar_t *r_texturebits; // number of desired texture bits + // 0 = use framebuffer depth + // 16 = use 16-bit textures + // 32 = use 32-bit textures + // all else = error +extern cvar_t *r_texturebitslm; // number of desired lightmap texture bits + +extern cvar_t *r_measureOverdraw; // enables stencil buffer overdraw measurement + +extern cvar_t *r_lodbias; // push/pull LOD transitions +extern cvar_t *r_lodscale; +extern cvar_t *r_autolodscalevalue; + +extern cvar_t *r_primitives; // "0" = based on compiled vertex array existance + // "1" = glDrawElemet tristrips + // "2" = glDrawElements triangles + // "-1" = no drawing + +extern cvar_t *r_inGameVideo; // controls whether in game video should be draw +extern cvar_t *r_fastsky; // controls whether sky should be cleared or drawn +extern cvar_t *r_drawSun; // controls drawing of sun quad +extern cvar_t *r_dynamiclight; // dynamic lights enabled/disabled +// rjr - removed for hacking extern cvar_t *r_dlightBacks; // dlight non-facing surfaces for continuity + +extern cvar_t *r_norefresh; // bypasses the ref rendering +extern cvar_t *r_drawentities; // disable/enable entity rendering +extern cvar_t *r_drawworld; // disable/enable world rendering +extern cvar_t *r_drawfog; // disable/enable fog rendering +extern cvar_t *r_speeds; // various levels of information display +extern cvar_t *r_detailTextures; // enables/disables detail texturing stages +extern cvar_t *r_novis; // disable/enable usage of PVS +extern cvar_t *r_nocull; +extern cvar_t *r_facePlaneCull; // enables culling of planar surfaces with back side test +extern cvar_t *r_cullRoofFaces; //attempted smart method of culling out upwards facing surfaces on roofs for automap shots -rww +extern cvar_t *r_roofCullCeilDist; //ceiling distance cull tolerance -rww +extern cvar_t *r_roofCullFloorDist; //floor distance cull tolerance -rww +extern cvar_t *r_nocurves; +extern cvar_t *r_showcluster; + +extern cvar_t *r_autoMap; //automap renderside toggle for debugging -rww +extern cvar_t *r_autoMapBackAlpha; //alpha of automap bg -rww +extern cvar_t *r_autoMapDisable; + +extern cvar_t *r_dlightStyle; +extern cvar_t *r_surfaceSprites; +extern cvar_t *r_surfaceWeather; + +extern cvar_t *r_windSpeed; +extern cvar_t *r_windAngle; +extern cvar_t *r_windGust; +extern cvar_t *r_windDampFactor; +extern cvar_t *r_windPointForce; +extern cvar_t *r_windPointX; +extern cvar_t *r_windPointY; + +extern cvar_t *r_mode; // video mode +extern cvar_t *r_fullscreen; +extern cvar_t *r_gamma; +#ifdef _XBOX +extern cvar_t *s_brightness_volume; +#endif +extern cvar_t *r_displayRefresh; // optional display refresh option +extern cvar_t *r_ignorehwgamma; // overrides hardware gamma capabilities + +extern cvar_t *r_allowExtensions; // global enable/disable of OpenGL extensions +extern cvar_t *r_ext_compressed_textures; // these control use of specific extensions +extern cvar_t *r_ext_compressed_lightmaps; // turns on compression of lightmaps, off by default +extern cvar_t *r_ext_preferred_tc_method; +extern cvar_t *r_ext_gamma_control; +extern cvar_t *r_ext_texenv_op; +extern cvar_t *r_ext_multitexture; +extern cvar_t *r_ext_compiled_vertex_array; +extern cvar_t *r_ext_texture_env_add; +extern cvar_t *r_ext_texture_filter_anisotropic; + +extern cvar_t *r_DynamicGlow; +extern cvar_t *r_DynamicGlowPasses; +extern cvar_t *r_DynamicGlowDelta; +extern cvar_t *r_DynamicGlowIntensity; +extern cvar_t *r_DynamicGlowSoft; +extern cvar_t *r_DynamicGlowWidth; +extern cvar_t *r_DynamicGlowHeight; + +extern cvar_t *r_nobind; // turns off binding to appropriate textures +extern cvar_t *r_singleShader; // make most world faces use default shader +extern cvar_t *r_colorMipLevels; // development aid to see texture mip usage +extern cvar_t *r_picmip; // controls picmip values +extern cvar_t *r_finish; +extern cvar_t *r_swapInterval; +extern cvar_t *r_markcount; +extern cvar_t *r_textureMode; +extern cvar_t *r_offsetFactor; +extern cvar_t *r_offsetUnits; + +extern cvar_t *r_fullbright; // avoid lightmap pass +extern cvar_t *r_lightmap; // render lightmaps only +extern cvar_t *r_vertexLight; // vertex lighting mode for better performance +extern cvar_t *r_uiFullScreen; // ui is running fullscreen + +extern cvar_t *r_logFile; // number of frames to emit GL logs +extern cvar_t *r_showtris; // enables wireframe rendering of the world +extern cvar_t *r_showsky; // forces sky in front of all surfaces +extern cvar_t *r_shownormals; // draws wireframe normals +extern cvar_t *r_clear; // force screen clear every frame + +extern cvar_t *r_shadows; // controls shadows: 0 = none, 1 = blur, 2 = stencil, 3 = black planar projection +extern cvar_t *r_flares; // light flares + +extern cvar_t *r_intensity; + +extern cvar_t *r_lockpvs; +extern cvar_t *r_noportals; +extern cvar_t *r_portalOnly; + +extern cvar_t *r_subdivisions; +extern cvar_t *r_lodCurveError; +extern cvar_t *r_skipBackEnd; + +extern cvar_t *r_ignoreGLErrors; + +extern cvar_t *r_overBrightBits; + +extern cvar_t *r_debugSurface; +extern cvar_t *r_simpleMipMaps; + +extern cvar_t *r_showImages; +extern cvar_t *r_debugSort; + +#ifdef _XBOX +extern cvar_t *r_hdreffect; +extern cvar_t *r_sundir_x; +extern cvar_t *r_sundir_y; +extern cvar_t *r_sundir_z; +extern cvar_t *r_hdrbloom; +#endif + +/* +Ghoul2 Insert Start +*/ +#ifdef _DEBUG +extern cvar_t *r_noPrecacheGLA; +#endif + +extern cvar_t *r_noServerGhoul2; +/* +Ghoul2 Insert End +*/ +//==================================================================== + +float R_NoiseGet4f( float x, float y, float z, float t ); +void R_NoiseInit( void ); + +void R_SwapBuffers( int ); + +void R_RenderView( viewParms_t *parms ); + +void R_AddMD3Surfaces( trRefEntity_t *e ); +void R_AddNullModelSurfaces( trRefEntity_t *e ); +void R_AddBeamSurfaces( trRefEntity_t *e ); +void R_AddRailSurfaces( trRefEntity_t *e, qboolean isUnderwater ); +void R_AddLightningBoltSurfaces( trRefEntity_t *e ); + +void R_AddPolygonSurfaces( void ); + +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap ); + +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, int fogIndex, int dlightMap ); + + +#define CULL_IN 0 // completely unclipped +#define CULL_CLIP 1 // clipped by one or more planes +#define CULL_OUT 2 // completely outside the clipping planes +void R_LocalNormalToWorld (const vec3_t local, vec3_t world); +void R_LocalPointToWorld (const vec3_t local, vec3_t world); +void R_WorldNormalToEntity (const vec3_t localVec, vec3_t world); +int R_CullLocalBox ( const vec3_t bounds[2]); +int R_CullPointAndRadius( const vec3_t origin, float radius ); +int R_CullLocalPointAndRadius( const vec3_t origin, float radius ); + +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, orientationr_t *ori ); + +/* +** GL wrapper/helper functions +*/ +void GL_Bind( image_t *image ); +void GL_Bind3D( image_t *image ); +void GL_SetDefaultState (void); +void GL_SelectTexture( int unit ); +void GL_TextureMode( const char *string ); +void GL_CheckErrors( void ); +void GL_State( unsigned long stateVector ); +void GL_TexEnv( int env ); +void GL_Cull( int cullType ); + +#define GLS_SRCBLEND_ZERO 0x00000001 +#define GLS_SRCBLEND_ONE 0x00000002 +#define GLS_SRCBLEND_DST_COLOR 0x00000003 +#define GLS_SRCBLEND_ONE_MINUS_DST_COLOR 0x00000004 +#define GLS_SRCBLEND_SRC_ALPHA 0x00000005 +#define GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA 0x00000006 +#define GLS_SRCBLEND_DST_ALPHA 0x00000007 +#define GLS_SRCBLEND_ONE_MINUS_DST_ALPHA 0x00000008 +#define GLS_SRCBLEND_ALPHA_SATURATE 0x00000009 +#define GLS_SRCBLEND_BITS 0x0000000f + +#define GLS_DSTBLEND_ZERO 0x00000010 +#define GLS_DSTBLEND_ONE 0x00000020 +#define GLS_DSTBLEND_SRC_COLOR 0x00000030 +#define GLS_DSTBLEND_ONE_MINUS_SRC_COLOR 0x00000040 +#define GLS_DSTBLEND_SRC_ALPHA 0x00000050 +#define GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA 0x00000060 +#define GLS_DSTBLEND_DST_ALPHA 0x00000070 +#define GLS_DSTBLEND_ONE_MINUS_DST_ALPHA 0x00000080 +#define GLS_DSTBLEND_BITS 0x000000f0 + +#define GLS_DEPTHMASK_TRUE 0x00000100 + +#define GLS_POLYMODE_LINE 0x00001000 + +#define GLS_DEPTHTEST_DISABLE 0x00010000 +#define GLS_DEPTHFUNC_EQUAL 0x00020000 + +#define GLS_ATEST_GT_0 0x10000000 +#define GLS_ATEST_LT_80 0x20000000 +#define GLS_ATEST_GE_80 0x40000000 +#define GLS_ATEST_GE_C0 0x80000000 +#define GLS_ATEST_BITS 0xF0000000 + +#define GLS_DEFAULT GLS_DEPTHMASK_TRUE +#define GLS_ALPHA (GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA) + +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty); +void RE_UploadCinematic (int cols, int rows, const byte *data, int client, qboolean dirty); + +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_BeginRegistration( glconfig_t *glconfig ); +void R_ColorShiftLightingBytes( byte in[4], byte out[4] ); //rwwRMG - added +void RE_LoadWorldMap( const char *mapname ); + +#ifdef _XBOX +void RE_SetWorldVisData( SPARC *vis ); +#else +void RE_SetWorldVisData( const byte *vis ); +#endif +void RE_SetPlaneData(cplane_t *planes, int count); + +qhandle_t RE_RegisterServerModel( const char *name ); +qhandle_t RE_RegisterModel( const char *name ); +qhandle_t RE_RegisterSkin( const char *name ); +void RE_Shutdown( qboolean destroyWindow ); + +void RE_RegisterMedia_LevelLoadBegin(const char *psMapName, ForceReload_e eForceReload); +void RE_RegisterMedia_LevelLoadEnd(void); +int RE_RegisterMedia_GetLevel(void); +// +//void RE_RegisterModels_LevelLoadBegin(const char *psMapName); +qboolean RE_RegisterModels_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel = qfalse); +#ifdef _XBOX +void* RE_RegisterModels_Malloc(int iSize, void *pvDiskBufferIfJustLoaded, const char *psModelFileName, qboolean *pqbAlreadyFound, memtag_t eTag, int modindex, bool useModelMem); +#else +void* RE_RegisterModels_Malloc(int iSize, void *pvDiskBufferIfJustLoaded, const char *psModelFileName, qboolean *pqbAlreadyFound, memtag_t eTag); +#endif +void RE_RegisterModels_StoreShaderRequest(const char *psModelFileName, const char *psShaderName, int *piShaderIndexPoke); +void RE_RegisterModels_Info_f(void); +// +//void RE_RegisterImages_LevelLoadBegin(const char *psMapName); +qboolean RE_RegisterImages_LevelLoadEnd(void); +void RE_RegisterImages_Info_f(void); + + +qboolean R_GetEntityToken( char *buffer, int size ); + +model_t *R_AllocModel( void ); + +void R_Init( void ); + +#ifndef DEDICATED +#ifdef _XBOX +void R_LoadImage( const char *shortname, byte **pic, int *width, int *height, int *mipcount, GLenum *format ); +#else +void R_LoadImage( const char *name, byte **pic, int *width, int *height, GLenum *format ); +#endif +#endif // DEDICATED + +image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ); + +#ifndef DEDICATED +#ifdef _XBOX +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, GLenum format, qboolean mipmap, qboolean allowPicmip, int wrapClampMode); +#else +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, GLenum format, qboolean mipmap + , qboolean allowPicmip, qboolean allowTC, int wrapClampMode, bool bRectangle = false ); +#endif // _XBOX +#endif // DEDICATED + +qboolean R_GetModeInfo( int *width, int *height, int mode ); + +void R_SetColorMappings( void ); +void R_GammaCorrect( byte *buffer, int bufSize ); + +void R_ImageList_f( void ); +void R_SkinList_f( void ); +void R_ScreenShot_f( void ); + +void R_InitFogTable( void ); +float R_FogFactor( float s, float t ); +void R_InitImages( void ); +void R_DeleteTextures( void ); +float R_SumOfUsedImages( qboolean bUseFormat ); +void R_InitSkins( void ); +skin_t *R_GetSkinByHandle( qhandle_t hSkin ); + + +// +// tr_shader.c +// +extern const short lightmapsNone[MAXLIGHTMAPS]; +extern const short lightmaps2d[MAXLIGHTMAPS]; +extern const short lightmapsVertex[MAXLIGHTMAPS]; +extern const short lightmapsFullBright[MAXLIGHTMAPS]; +extern const byte stylesDefault[MAXLIGHTMAPS]; + +qhandle_t RE_RegisterShaderLightMap( const char *name, const short *lightmapIndex, const byte *styles ) ; +qhandle_t RE_RegisterShader( const char *name ); +qhandle_t RE_RegisterShaderNoMip( const char *name ); +const char *RE_ShaderNameFromIndex(int index); +qhandle_t RE_RegisterShaderFromImage(const char *name, short *lightmapIndex, byte *styles, image_t *image, qboolean mipRawImage); + +shader_t *R_FindShader( const char *name, const short *lightmapIndex, const byte *styles, qboolean mipRawImage ); +shader_t *R_GetShaderByHandle( qhandle_t hShader ); +shader_t *R_GetShaderByState( int index, long *cycleTime ); +shader_t *R_FindShaderByName( const char *name ); +void R_InitShaders(qboolean server); +void R_ShaderList_f( void ); +void R_RemapShader(const char *oldShader, const char *newShader, const char *timeOffset); +//rwwRMG: Added: +qhandle_t R_GetShaderByNum(int index, world_t &worldData); +qhandle_t R_CreateBlendedShader(qhandle_t a, qhandle_t b, qhandle_t c, bool surfaceSprites ); + + +/* +==================================================================== + +IMPLEMENTATION SPECIFIC FUNCTIONS + +==================================================================== +*/ + +void GLimp_Init( void ); +void GLimp_Shutdown( void ); +void GLimp_EndFrame( void ); + +void GLimp_LogComment( char *comment ); + +#ifndef _XBOX +void GLimp_SetGamma( unsigned char red[256], + unsigned char green[256], + unsigned char blue[256] ); +#endif + + +/* +==================================================================== + +TESSELATOR/SHADER DECLARATIONS + +==================================================================== +*/ +typedef byte color4ub_t[4]; + +typedef struct stageVars +{ +#ifdef _XBOX + unsigned long colors[SHADER_MAX_VERTEXES]; +#else + color4ub_t colors[SHADER_MAX_VERTEXES]; +#endif + vec2_t texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES]; +} stageVars_t; + +#define NUM_TEX_COORDS (MAXLIGHTMAPS+1) + +struct shaderCommands_s +{ + glIndex_t indexes[SHADER_MAX_INDEXES]; + vec4_t xyz[SHADER_MAX_VERTEXES]; + vec4_t normal[SHADER_MAX_VERTEXES]; +#ifdef _XBOX + vec4_t tangent[SHADER_MAX_VERTEXES]; +#endif + vec2_t texCoords[SHADER_MAX_VERTEXES][NUM_TEX_COORDS]; + color4ub_t vertexColors[SHADER_MAX_VERTEXES]; + byte vertexAlphas[SHADER_MAX_VERTEXES][4]; //rwwRMG - added support + int vertexDlightBits[SHADER_MAX_VERTEXES]; + + stageVars_t svars; + + shader_t *shader; + float shaderTime; + int fogNum; + + int dlightBits; // or together of all vertexDlightBits + + int numIndexes; + int numVertexes; + + // info extracted from current shader + int numPasses; +#ifdef _XBOX + int currentPass; + bool setTangents; +#endif + void (*currentStageIteratorFunc)( void ); + shaderStage_t *xstages; + + int registration; + + qboolean SSInitializedWind; + + //rww - doing a fade, don't compute shader color/alpha overrides + bool fading; +}; +#ifndef DEDICATED +typedef __declspec(align(16)) shaderCommands_s shaderCommands_t; +extern shaderCommands_t tess; +#endif +extern color4ub_t styleColors[MAX_LIGHT_STYLES]; + +void RB_BeginSurface(shader_t *shader, int fogNum ); +void RB_EndSurface(void); +void RB_CheckOverflow( int verts, int indexes ); +#define RB_CHECKOVERFLOW(v,i) if (tess.numVertexes + (v) >= SHADER_MAX_VERTEXES || tess.numIndexes + (i) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow(v,i);} + +void RB_StageIteratorGeneric( void ); +void RB_StageIteratorSky( void ); + +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ); +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ); + +void RB_ShowImages( void ); + + +/* +============================================================ + +WORLD MAP + +============================================================ +*/ + +void R_AddBrushModelSurfaces( trRefEntity_t *e ); +void R_AddWorldSurfaces( void ); +qboolean R_inPVS( const vec3_t p1, const vec3_t p2, byte *mask ); + + +/* +============================================================ + +FLARES + +============================================================ +*/ + +void R_ClearFlares( void ); + +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal ); +void RB_AddDlightFlares( void ); +void RB_RenderFlares (void); + +/* +============================================================ + +LIGHTS + +============================================================ +*/ + +void R_DlightBmodel( bmodel_t *bmodel, bool NoLight ); +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ); +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *ori ); +int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); + + +/* +============================================================ + +SHADOWS + +============================================================ +*/ + +void RB_ShadowTessEnd( void ); +void RB_ShadowFinish( void ); +void RB_ProjectionShadowDeform( void ); + +/* +============================================================ + +SKIES + +============================================================ +*/ +#ifndef DEDICATED +void R_BuildCloudData( shaderCommands_t *shader ); +void R_InitSkyTexCoords( float cloudLayerHeight ); +void R_DrawSkyBox( shaderCommands_t *shader ); +void RB_DrawSun( void ); +void RB_ClipSkyPolygons( shaderCommands_t *shader ); +#endif +/* +============================================================ + +CURVE TESSELATION + +============================================================ +*/ + +#define PATCH_STITCHING + +#ifdef _XBOX +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t* points, + drawVert_t* ctl, float* errorTable ); +#else +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ); +#endif // _XBOX + +srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ); +srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ); +void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ); +/* +Ghoul2 Insert Start +*/ + +float ProjectRadius( float r, vec3_t location ); +/* +Ghoul2 Insert End +*/ +/* +============================================================ + +MARKERS, POLYGON PROJECTION ON WORLD POLYGONS + +============================================================ +*/ + +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + +/* +============================================================ + +SCENE GENERATION + +============================================================ +*/ + +void R_ToggleSmpFrame( void ); + +void RE_ClearScene( void ); +void RE_ClearDecals ( void ); +void RE_AddRefEntityToScene( const refEntity_t *ent ); +void RE_AddMiniRefEntityToScene( const miniRefEntity_t *ent ); +void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ); +void RE_AddDecalToScene ( qhandle_t shader, const vec3_t origin, const vec3_t dir, float orientation, float r, float g, float b, float a, qboolean alphaFade, float radius, qboolean temporary ); +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_RenderScene( const refdef_t *fd ); + +/* +============================================================= + +ANIMATED MODELS + +============================================================= +*/ + +void R_MakeAnimModel( model_t *model ); +void R_AddAnimSurfaces( trRefEntity_t *ent ); +/* +Ghoul2 Insert Start +*/ +#pragma warning (disable: 4512) //default assignment operator could not be gened +class CRenderableSurface +{ +public: +#ifdef _G2_GORE + int ident; +#else + const int ident; // ident of this surface - required so the materials renderer knows what sort of surface this refers to +#endif + CBoneCache *boneCache; + mdxmSurface_t *surfaceData; // pointer to surface data loaded into file - only used by client renderer DO NOT USE IN GAME SIDE - if there is a vid restart this will be out of wack on the game +#ifdef _G2_GORE + float *alternateTex; // alternate texture coordinates. + void *goreChain; + + float scale; + float fade; + float impactTime; // this is a number between 0 and 1 that dictates the progression of the bullet impact +#endif + +#ifdef _G2_GORE + CRenderableSurface& operator= ( const CRenderableSurface& src ) + { + ident = src.ident; + boneCache = src.boneCache; + surfaceData = src.surfaceData; + alternateTex = src.alternateTex; + goreChain = src.goreChain; + + return *this; + } +#endif + +CRenderableSurface(): + ident(SF_MDX), + boneCache(0), +#ifdef _G2_GORE + surfaceData(0), + alternateTex(0), + goreChain(0) +#else + surfaceData(0) +#endif + {} + +#ifdef _G2_GORE + void Init() + { + ident = SF_MDX; + boneCache=0; + surfaceData=0; + alternateTex=0; + goreChain=0; + } +#endif +}; + +void R_AddGhoulSurfaces( trRefEntity_t *ent ); +void RB_SurfaceGhoul( CRenderableSurface *surface ); +/* +Ghoul2 Insert End +*/ +/* +============================================================= +============================================================= +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ); +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ); + +void RB_DeformTessGeometry( void ); + +void RB_CalcEnvironmentTexCoords( float *dstTexCoords ); +void RB_CalcFogTexCoords( float *dstTexCoords ); +void RB_CalcScrollTexCoords( const float scroll[2], float *dstTexCoords ); +void RB_CalcRotateTexCoords( float rotSpeed, float *dstTexCoords ); +void RB_CalcScaleTexCoords( const float scale[2], float *dstTexCoords ); +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *dstTexCoords ); +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *dstTexCoords ); +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *texCoords ); +#ifdef _XBOX +void RB_CalcWaveColor( const waveForm_t *wf, DWORD *dstColors ); +void RB_CalcColorFromEntity( DWORD *dstColors ); +void RB_CalcColorFromOneMinusEntity( DWORD *dstColors ); +void RB_CalcWaveAlpha( const waveForm_t *wf, DWORD *dstColors ); +void RB_CalcSpecularAlpha( DWORD *alphas ); +void RB_CalcAlphaFromEntity( DWORD *dstColors ); +void RB_CalcAlphaFromOneMinusEntity( DWORD *dstColors ); +void RB_CalcModulateColorsByFog( DWORD *dstColors ); +void RB_CalcModulateAlphasByFog( DWORD *dstColors ); +void RB_CalcModulateRGBAsByFog( DWORD *dstColors ); +void RB_CalcDisintegrateColors( DWORD *colors ); +void RB_CalcDiffuseColor( DWORD *colors ); +void RB_CalcDiffuseEntityColor( DWORD *colors ); +#else +void RB_CalcModulateColorsByFog( unsigned char *dstColors ); +void RB_CalcModulateAlphasByFog( unsigned char *dstColors ); +void RB_CalcModulateRGBAsByFog( unsigned char *dstColors ); +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ); +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ); +void RB_CalcAlphaFromEntity( unsigned char *dstColors ); +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcColorFromEntity( unsigned char *dstColors ); +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcSpecularAlpha( unsigned char *alphas ); +void RB_CalcDisintegrateColors( unsigned char *colors ); +void RB_CalcDiffuseColor( unsigned char *colors ); +void RB_CalcDiffuseEntityColor( unsigned char *colors ); +#endif // _XBOX + +void RB_CalcDisintegrateVertDeform( void ); + +/* +============================================================= + +RENDERER BACK END FUNCTIONS + +============================================================= +*/ + +void RB_RenderThread( void ); +void RB_ExecuteRenderCommands( const void *data ); + +/* +============================================================= + +RENDERER BACK END COMMAND QUEUE + +============================================================= +*/ + +#ifdef _XBOX +#define MAX_RENDER_COMMANDS 0x18000 +#else +#define MAX_RENDER_COMMANDS 0x40000 +#endif + +typedef struct { + byte cmds[MAX_RENDER_COMMANDS]; + int used; +} renderCommandList_t; + +typedef struct { + int commandId; + float color[4]; +} setColorCommand_t; + +typedef struct { + int commandId; + int buffer; +} drawBufferCommand_t; + +typedef struct { + int commandId; + image_t *image; + int width; + int height; + void *data; +} subImageCommand_t; + +typedef struct { + int commandId; +} swapBuffersCommand_t; + +typedef struct { + int commandId; + int buffer; +} endFrameCommand_t; + +typedef struct { + int commandId; + shader_t *shader; + float x, y; + float w, h; + float s1, t1; + float s2, t2; +} stretchPicCommand_t; + +typedef struct { + int commandId; + shader_t *shader; + float x, y; + float w, h; + float s1, t1; + float s2, t2; + float a; +} rotatePicCommand_t; + +typedef struct { + int commandId; + trRefdef_t refdef; + viewParms_t viewParms; + drawSurf_t *drawSurfs; + int numDrawSurfs; +#ifdef _XBOX + int clientNum; +#endif +} drawSurfsCommand_t; + +typedef enum { + RC_END_OF_LIST, + RC_SET_COLOR, + RC_STRETCH_PIC, + RC_ROTATE_PIC, + RC_ROTATE_PIC2, + RC_DRAW_SURFS, + RC_DRAW_BUFFER, + RC_SWAP_BUFFERS, + RC_WORLD_EFFECTS, + RC_AUTO_MAP +} renderCommand_t; + + +// these are sort of arbitrary limits. +// the limits apply to the sum of all scenes in a frame -- +// the main view, all the 3D icons, etc +#define MAX_POLYS 600 +#define MAX_POLYVERTS 3000 + +// all of the information needed by the back end must be +// contained in a backEndData_t. This entire structure is +// duplicated so the front and back end can run in parallel +// on an SMP machine +typedef struct { + drawSurf_t drawSurfs[MAX_DRAWSURFS]; + dlight_t dlights[MAX_DLIGHTS]; + trRefEntity_t entities[MAX_ENTITIES]; + trMiniRefEntity_t miniEntities[MAX_MINI_ENTITIES]; + srfPoly_t *polys;//[MAX_POLYS]; + polyVert_t *polyVerts;//[MAX_POLYVERTS]; + renderCommandList_t commands; +} backEndData_t; + +extern int max_polys; +extern int max_polyverts; + +extern backEndData_t *backEndData; + + +void *R_GetCommandBuffer( int bytes ); +void RB_ExecuteRenderCommands( const void *data ); + +void R_InitCommandBuffers( void ); +void R_ShutdownCommandBuffers( void ); + +void R_SyncRenderThread( void ); + +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ); + +void RE_SetColor( const float *rgba ); +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void RE_RotatePic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ); +void RE_RotatePic2 ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ); +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ); +void SaveJPG(char * filename, int quality, int image_width, int image_height, unsigned char *image_buffer); + +int R_SurfaceImageCount(const image_t*); + +/* +Ghoul2 Insert Start +*/ +// tr_ghoul2.cpp +void Create_Matrix(const float *angle, mdxaBone_t *matrix); +void Multiply_3x4Matrix(mdxaBone_t *out, mdxaBone_t *in2, mdxaBone_t *in); +extern qboolean R_LoadMDXM (model_t *mod, void *buffer, const char *name, qboolean &bAlreadyCached ); +extern qboolean R_LoadMDXA (model_t *mod, void *buffer, const char *name, qboolean &bAlreadyCached ); +bool LoadTGAPalletteImage ( const char *name, byte **pic, int *width, int *height); +void RE_InsertModelIntoHash(const char *name, model_t *mod); +/* +Ghoul2 Insert End +*/ + +#define MAX_VERTS_ON_DECAL_POLY 10 +//#define MAX_DECAL_POLYS 500 +#define MAX_DECAL_POLYS 100 + +typedef struct decalPoly_s +{ + int time; + int fadetime; + qhandle_t shader; + float color[4]; + poly_t poly; + polyVert_t verts[MAX_VERTS_ON_DECAL_POLY]; + +} decalPoly_t; + +#ifndef DEDICATED +// tr_surfacesprites +void RB_DrawSurfaceSprites( shaderStage_t *stage, shaderCommands_t *input); +#endif + +#ifdef _XBOX +struct DDS_PIXELFORMAT +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwFourCC; + DWORD dwRGBBitCount; + DWORD dwRBitMask; + DWORD dwGBitMask; + DWORD dwBBitMask; + DWORD dwABitMask; +}; + +struct DDS_HEADER +{ + DWORD dwSize; + DWORD dwHeaderFlags; + DWORD dwHeight; + DWORD dwWidth; + DWORD dwPitchOrLinearSize; + DWORD dwDepth; + DWORD dwMipMapCount; + DWORD dwReserved1[11]; + DDS_PIXELFORMAT ddspf; + DWORD dwSurfaceFlags; + DWORD dwCubemapFlags; + DWORD dwReserved2[3]; +}; +#endif + +#endif //TR_LOCAL_H diff --git a/codemp/renderer/tr_main.cpp b/codemp/renderer/tr_main.cpp new file mode 100644 index 0000000..6fff6e2 --- /dev/null +++ b/codemp/renderer/tr_main.cpp @@ -0,0 +1,1646 @@ +// tr_main.c -- main control flow for each frame +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" +#include "../ghoul2/G2_local.h" +// Yeah, this might be kind of bad, but no linux version is planned so far :-) - AReis +// Gee- thanks guys - jdrews, the linux porter... +#ifndef _XBOX +#ifndef __linux__ +#include "../win32/glw_win.h" +#endif +#endif + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +trGlobals_t tr; + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) +#if defined (_XBOX) + 0, 0, 1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +#else + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +#endif +}; + +void R_AddTerrainSurfaces(void); + +#ifndef DEDICATED + +// entities that will have procedurally generated surfaces will just +// point at this for their sorting surface +surfaceType_t entitySurface = SF_ENTITY; + +/* +================= +R_CullLocalBox + +Returns CULL_IN, CULL_CLIP, or CULL_OUT +================= +*/ +int R_CullLocalBox (const vec3_t bounds[2]) { + int i, j; + vec3_t transformed[8]; + float dists[8]; + vec3_t v; + cplane_t *frust; + int anyBack; + int front, back; + + if ( r_nocull->integer==1 ) { + return CULL_CLIP; + } + + // transform into world space + for (i = 0 ; i < 8 ; i++) { + v[0] = bounds[i&1][0]; + v[1] = bounds[(i>>1)&1][1]; + v[2] = bounds[(i>>2)&1][2]; + + VectorCopy( tr.ori.origin, transformed[i] ); + VectorMA( transformed[i], v[0], tr.ori.axis[0], transformed[i] ); + VectorMA( transformed[i], v[1], tr.ori.axis[1], transformed[i] ); + VectorMA( transformed[i], v[2], tr.ori.axis[2], transformed[i] ); + } + + // check against frustum planes + anyBack = 0; + for (i = 0 ; i < 4 ; i++) { + frust = &tr.viewParms.frustum[i]; + + front = back = 0; + for (j = 0 ; j < 8 ; j++) { + dists[j] = DotProduct(transformed[j], frust->normal); + if ( dists[j] > frust->dist ) { + front = 1; + if ( back ) { + break; // a point is in front + } + } else { + back = 1; + } + } + if ( !front ) { + // all points were behind one of the planes + return CULL_OUT; + } + anyBack |= back; + } + + if ( !anyBack ) { + return CULL_IN; // completely inside frustum + } + + return CULL_CLIP; // partially clipped +} + +#endif // !DEDICATED + +/* +** R_CullLocalPointAndRadius +*/ +int R_CullLocalPointAndRadius( const vec3_t pt, float radius ) +{ + vec3_t transformed; + + R_LocalPointToWorld( pt, transformed ); + + return R_CullPointAndRadius( transformed, radius ); +} + +/* +** R_CullPointAndRadius +*/ +int R_CullPointAndRadius( const vec3_t pt, float radius ) +{ + int i; + float dist; + cplane_t *frust; + qboolean mightBeClipped = qfalse; + + if ( r_nocull->integer==1 ) { + return CULL_CLIP; + } + + // check against frustum planes + for (i = 0 ; i < 4 ; i++) + { + frust = &tr.viewParms.frustum[i]; + + dist = DotProduct( pt, frust->normal) - frust->dist; + if ( dist < -radius ) + { + return CULL_OUT; + } + else if ( dist <= radius ) + { + mightBeClipped = qtrue; + } + } + + if ( mightBeClipped ) + { + return CULL_CLIP; + } + + return CULL_IN; // completely inside frustum +} + +#ifndef DEDICATED + +/* +================= +R_LocalNormalToWorld + +================= +*/ +void R_LocalNormalToWorld (const vec3_t local, vec3_t world) { + world[0] = local[0] * tr.ori.axis[0][0] + local[1] * tr.ori.axis[1][0] + local[2] * tr.ori.axis[2][0]; + world[1] = local[0] * tr.ori.axis[0][1] + local[1] * tr.ori.axis[1][1] + local[2] * tr.ori.axis[2][1]; + world[2] = local[0] * tr.ori.axis[0][2] + local[1] * tr.ori.axis[1][2] + local[2] * tr.ori.axis[2][2]; +} + +#endif // !DEDICATED +/* +================= +R_LocalPointToWorld + +================= +*/ +void R_LocalPointToWorld (const vec3_t local, vec3_t world) { + world[0] = local[0] * tr.ori.axis[0][0] + local[1] * tr.ori.axis[1][0] + local[2] * tr.ori.axis[2][0] + tr.ori.origin[0]; + world[1] = local[0] * tr.ori.axis[0][1] + local[1] * tr.ori.axis[1][1] + local[2] * tr.ori.axis[2][1] + tr.ori.origin[1]; + world[2] = local[0] * tr.ori.axis[0][2] + local[1] * tr.ori.axis[1][2] + local[2] * tr.ori.axis[2][2] + tr.ori.origin[2]; +} + +#ifndef DEDICATED + +float preTransEntMatrix[16]; + +/* +================= +R_WorldNormalToEntity + +================= +*/ +void R_WorldNormalToEntity (const vec3_t worldvec, vec3_t entvec) +{ + entvec[0] = -worldvec[0] * preTransEntMatrix[0] - worldvec[1] * preTransEntMatrix[4] + worldvec[2] * preTransEntMatrix[8]; + entvec[1] = -worldvec[0] * preTransEntMatrix[1] - worldvec[1] * preTransEntMatrix[5] + worldvec[2] * preTransEntMatrix[9]; + entvec[2] = -worldvec[0] * preTransEntMatrix[2] - worldvec[1] * preTransEntMatrix[6] + worldvec[2] * preTransEntMatrix[10]; +} + +/* +================= +R_WorldPointToEntity + +================= +*/ +/*void R_WorldPointToEntity (vec3_t worldvec, vec3_t entvec) +{ + entvec[0] = worldvec[0] * preTransEntMatrix[0] + worldvec[1] * preTransEntMatrix[4] + worldvec[2] * preTransEntMatrix[8]+preTransEntMatrix[12]; + entvec[1] = worldvec[0] * preTransEntMatrix[1] + worldvec[1] * preTransEntMatrix[5] + worldvec[2] * preTransEntMatrix[9]+preTransEntMatrix[13]; + entvec[2] = worldvec[0] * preTransEntMatrix[2] + worldvec[1] * preTransEntMatrix[6] + worldvec[2] * preTransEntMatrix[10]+preTransEntMatrix[14]; +} +*/ + +/* +================= +R_WorldToLocal + +================= +*/ +void R_WorldToLocal (vec3_t world, vec3_t local) { + local[0] = DotProduct(world, tr.ori.axis[0]); + local[1] = DotProduct(world, tr.ori.axis[1]); + local[2] = DotProduct(world, tr.ori.axis[2]); +} + +/* +========================== +R_TransformModelToClip + +========================== +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ) { + int i; + + for ( i = 0 ; i < 4 ; i++ ) { + eye[i] = + src[0] * modelMatrix[ i + 0 * 4 ] + + src[1] * modelMatrix[ i + 1 * 4 ] + + src[2] * modelMatrix[ i + 2 * 4 ] + + 1 * modelMatrix[ i + 3 * 4 ]; + } + + for ( i = 0 ; i < 4 ; i++ ) { + dst[i] = + eye[0] * projectionMatrix[ i + 0 * 4 ] + + eye[1] * projectionMatrix[ i + 1 * 4 ] + + eye[2] * projectionMatrix[ i + 2 * 4 ] + + eye[3] * projectionMatrix[ i + 3 * 4 ]; + } +} + +/* +========================== +R_TransformClipToWindow + +========================== +*/ +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) { + normalized[0] = clip[0] / clip[3]; + normalized[1] = clip[1] / clip[3]; + normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] ); + + window[0] = 0.5f * ( 1.0f + normalized[0] ) * view->viewportWidth; + window[1] = 0.5f * ( 1.0f + normalized[1] ) * view->viewportHeight; + window[2] = normalized[2]; + + window[0] = (int) ( window[0] + 0.5 ); + window[1] = (int) ( window[1] + 0.5 ); +} + + +/* +========================== +myGlMultMatrix + +========================== +*/ +void myGlMultMatrix( const float *a, const float *b, float *out ) { + int i, j; + + for ( i = 0 ; i < 4 ; i++ ) { + for ( j = 0 ; j < 4 ; j++ ) { + out[ i * 4 + j ] = + a [ i * 4 + 0 ] * b [ 0 * 4 + j ] + + a [ i * 4 + 1 ] * b [ 1 * 4 + j ] + + a [ i * 4 + 2 ] * b [ 2 * 4 + j ] + + a [ i * 4 + 3 ] * b [ 3 * 4 + j ]; + } + } +} + +/* +================= +R_RotateForEntity + +Generates an orientation for an entity and viewParms +Does NOT produce any GL calls +Called by both the front end and the back end +================= +*/ +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, + orientationr_t *ori ) { +// float glMatrix[16]; + vec3_t delta; + float axisLength; + + if ( ent->e.reType != RT_MODEL ) { + *ori = viewParms->world; + return; + } + + VectorCopy( ent->e.origin, ori->origin ); + + VectorCopy( ent->e.axis[0], ori->axis[0] ); + VectorCopy( ent->e.axis[1], ori->axis[1] ); + VectorCopy( ent->e.axis[2], ori->axis[2] ); + + preTransEntMatrix[0] = ori->axis[0][0]; + preTransEntMatrix[4] = ori->axis[1][0]; + preTransEntMatrix[8] = ori->axis[2][0]; + preTransEntMatrix[12] = ori->origin[0]; + + preTransEntMatrix[1] = ori->axis[0][1]; + preTransEntMatrix[5] = ori->axis[1][1]; + preTransEntMatrix[9] = ori->axis[2][1]; + preTransEntMatrix[13] = ori->origin[1]; + + preTransEntMatrix[2] = ori->axis[0][2]; + preTransEntMatrix[6] = ori->axis[1][2]; + preTransEntMatrix[10] = ori->axis[2][2]; + preTransEntMatrix[14] = ori->origin[2]; + + preTransEntMatrix[3] = 0; + preTransEntMatrix[7] = 0; + preTransEntMatrix[11] = 0; + preTransEntMatrix[15] = 1; + + myGlMultMatrix( preTransEntMatrix, viewParms->world.modelMatrix, ori->modelMatrix ); + + // calculate the viewer origin in the model's space + // needed for fog, specular, and environment mapping + VectorSubtract( viewParms->ori.origin, ori->origin, delta ); + + // compensate for scale in the axes if necessary + if ( ent->e.nonNormalizedAxes ) { + axisLength = VectorLength( ent->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + } else { + axisLength = 1.0f; + } + + ori->viewOrigin[0] = DotProduct( delta, ori->axis[0] ) * axisLength; + ori->viewOrigin[1] = DotProduct( delta, ori->axis[1] ) * axisLength; + ori->viewOrigin[2] = DotProduct( delta, ori->axis[2] ) * axisLength; +} + +/* +================= +R_RotateForViewer + +Sets up the modelview matrix for a given viewParm +================= +*/ +void R_RotateForViewer (void) +{ + float viewerMatrix[16]; + vec3_t origin; + + Com_Memset (&tr.ori, 0, sizeof(tr.ori)); + tr.ori.axis[0][0] = 1; + tr.ori.axis[1][1] = 1; + tr.ori.axis[2][2] = 1; + VectorCopy (tr.viewParms.ori.origin, tr.ori.viewOrigin); + + // transform by the camera placement + VectorCopy( tr.viewParms.ori.origin, origin ); + + viewerMatrix[0] = tr.viewParms.ori.axis[0][0]; + viewerMatrix[4] = tr.viewParms.ori.axis[0][1]; + viewerMatrix[8] = tr.viewParms.ori.axis[0][2]; + viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8]; + + viewerMatrix[1] = tr.viewParms.ori.axis[1][0]; + viewerMatrix[5] = tr.viewParms.ori.axis[1][1]; + viewerMatrix[9] = tr.viewParms.ori.axis[1][2]; + viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9]; + + viewerMatrix[2] = tr.viewParms.ori.axis[2][0]; + viewerMatrix[6] = tr.viewParms.ori.axis[2][1]; + viewerMatrix[10] = tr.viewParms.ori.axis[2][2]; + viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10]; + + viewerMatrix[3] = 0; + viewerMatrix[7] = 0; + viewerMatrix[11] = 0; + viewerMatrix[15] = 1; + + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.ori.modelMatrix ); + + tr.viewParms.world = tr.ori; + +} + +/* +** SetFarClip +*/ +static void SetFarClip( void ) +{ + float farthestCornerDistance = 0; + int i; + + // if not rendering the world (icons, menus, etc) + // set a 2k far clip plane + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + if (tr.refdef.rdflags & RDF_AUTOMAP) + { //override the zfar then + tr.viewParms.zFar = 32768.0f; + } + else + { + tr.viewParms.zFar = 2048.0f; + } + return; + } + + // + // set far clipping planes dynamically + // + for ( i = 0; i < 8; i++ ) + { + vec3_t v; + float distance; + + if ( i & 1 ) + { + v[0] = tr.viewParms.visBounds[0][0]; + } + else + { + v[0] = tr.viewParms.visBounds[1][0]; + } + + if ( i & 2 ) + { + v[1] = tr.viewParms.visBounds[0][1]; + } + else + { + v[1] = tr.viewParms.visBounds[1][1]; + } + + if ( i & 4 ) + { + v[2] = tr.viewParms.visBounds[0][2]; + } + else + { + v[2] = tr.viewParms.visBounds[1][2]; + } + + distance = DistanceSquared(tr.viewParms.ori.origin, v); + + if ( distance > farthestCornerDistance ) + { + farthestCornerDistance = distance; + } + } + // Bring in the zFar to the distanceCull distance + // The sky renders at zFar so need to move it out a little + // ...and make sure there is a minimum zfar to prevent problems + tr.viewParms.zFar = Com_Clamp(2048.0f, tr.distanceCull * (1.732), sqrtf( farthestCornerDistance )); + + /* + if (r_shadows->integer == 2) + { //volume caps need an "infinite" far clipping plane. So I'm using this semi-arbitrary massive number. + tr.viewParms.zFar = 524288.0f; + } + */ +} + + +/* +=============== +R_SetupProjection +=============== +*/ +void R_SetupProjection( void ) { + float xmin, xmax, ymin, ymax; + float width, height, depth; + float zNear, zFar; + + // dynamically compute far clip plane distance + SetFarClip(); + + // + // set up projection matrix + // + zNear = r_znear->value; + zFar = tr.viewParms.zFar; + + ymax = zNear * tan( tr.refdef.fov_y * M_PI / 360.0f ); + ymin = -ymax; + + xmax = zNear * tan( tr.refdef.fov_x * M_PI / 360.0f ); + xmin = -xmax; + + width = xmax - xmin; + height = ymax - ymin; + depth = zFar - zNear; + +#if defined (_XBOX) + tr.viewParms.projectionMatrix[0] = 2 * zNear / width; + tr.viewParms.projectionMatrix[4] = 0; + tr.viewParms.projectionMatrix[8] = ( xmax + xmin ) / width; // normally 0 + tr.viewParms.projectionMatrix[12] = 0; + + tr.viewParms.projectionMatrix[1] = 0; + tr.viewParms.projectionMatrix[5] = 2 * zNear / height; + tr.viewParms.projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0 + tr.viewParms.projectionMatrix[13] = 0; + + tr.viewParms.projectionMatrix[2] = 0; + tr.viewParms.projectionMatrix[6] = 0; + tr.viewParms.projectionMatrix[10] = ( zFar + zNear ) / depth; + tr.viewParms.projectionMatrix[14] = -2 * zFar * zNear / depth; + + tr.viewParms.projectionMatrix[3] = 0; + tr.viewParms.projectionMatrix[7] = 0; + tr.viewParms.projectionMatrix[11] = 1; + tr.viewParms.projectionMatrix[15] = 0; +#else + tr.viewParms.projectionMatrix[0] = 2 * zNear / width; + tr.viewParms.projectionMatrix[4] = 0; + tr.viewParms.projectionMatrix[8] = ( xmax + xmin ) / width; // normally 0 + tr.viewParms.projectionMatrix[12] = 0; + + tr.viewParms.projectionMatrix[1] = 0; + tr.viewParms.projectionMatrix[5] = 2 * zNear / height; + tr.viewParms.projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0 + tr.viewParms.projectionMatrix[13] = 0; + + tr.viewParms.projectionMatrix[2] = 0; + tr.viewParms.projectionMatrix[6] = 0; + tr.viewParms.projectionMatrix[10] = -( zFar + zNear ) / depth; + tr.viewParms.projectionMatrix[14] = -2 * zFar * zNear / depth; + + tr.viewParms.projectionMatrix[3] = 0; + tr.viewParms.projectionMatrix[7] = 0; + tr.viewParms.projectionMatrix[11] = -1; + tr.viewParms.projectionMatrix[15] = 0; +#endif +} + +/* +================= +R_SetupFrustum + +Setup that culling frustum planes for the current view +================= +*/ +void R_SetupFrustum (void) { + int i; + float xs, xc; + float ang; + + ang = tr.viewParms.fovX / 180 * M_PI * 0.5f; + xs = sin( ang ); + xc = cos( ang ); + + VectorScale( tr.viewParms.ori.axis[0], xs, tr.viewParms.frustum[0].normal ); + VectorMA( tr.viewParms.frustum[0].normal, xc, tr.viewParms.ori.axis[1], tr.viewParms.frustum[0].normal ); + + VectorScale( tr.viewParms.ori.axis[0], xs, tr.viewParms.frustum[1].normal ); + VectorMA( tr.viewParms.frustum[1].normal, -xc, tr.viewParms.ori.axis[1], tr.viewParms.frustum[1].normal ); + + ang = tr.viewParms.fovY / 180 * M_PI * 0.5f; + xs = sin( ang ); + xc = cos( ang ); + + VectorScale( tr.viewParms.ori.axis[0], xs, tr.viewParms.frustum[2].normal ); + VectorMA( tr.viewParms.frustum[2].normal, xc, tr.viewParms.ori.axis[2], tr.viewParms.frustum[2].normal ); + + VectorScale( tr.viewParms.ori.axis[0], xs, tr.viewParms.frustum[3].normal ); + VectorMA( tr.viewParms.frustum[3].normal, -xc, tr.viewParms.ori.axis[2], tr.viewParms.frustum[3].normal ); + + for (i=0 ; i<4 ; i++) { + tr.viewParms.frustum[i].type = PLANE_NON_AXIAL; + tr.viewParms.frustum[i].dist = DotProduct (tr.viewParms.ori.origin, tr.viewParms.frustum[i].normal); + SetPlaneSignbits( &tr.viewParms.frustum[i] ); + } +} + + +/* +================= +R_MirrorPoint +================= +*/ +void R_MirrorPoint (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + vec3_t local; + vec3_t transformed; + float d; + + VectorSubtract( in, surface->origin, local ); + + VectorClear( transformed ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(local, surface->axis[i]); + VectorMA( transformed, d, camera->axis[i], transformed ); + } + + VectorAdd( transformed, camera->origin, out ); +} + +void R_MirrorVector (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + float d; + + VectorClear( out ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(in, surface->axis[i]); + VectorMA( out, d, camera->axis[i], out ); + } +} + + +/* +============= +R_PlaneForSurface +============= +*/ +void R_PlaneForSurface (surfaceType_t *surfType, cplane_t *plane) { + srfTriangles_t *tri; + srfPoly_t *poly; + drawVert_t *v1, *v2, *v3; + vec4_t plane4; + + if (!surfType) { + Com_Memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } + switch (*surfType) { + case SF_FACE: + *plane = ((srfSurfaceFace_t *)surfType)->plane; + return; + case SF_TRIANGLES: + tri = (srfTriangles_t *)surfType; + v1 = tri->verts + tri->indexes[0]; + v2 = tri->verts + tri->indexes[1]; + v3 = tri->verts + tri->indexes[2]; + PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + case SF_POLY: + poly = (srfPoly_t *)surfType; + PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + default: + Com_Memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } +} + +/* +================= +R_GetPortalOrientation + +entityNum is the entity that the portal surface is a part of, which may +be moving and rotating. + +Returns qtrue if it should be mirrored +================= +*/ +qboolean R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum, + orientation_t *surface, orientation_t *camera, + vec3_t pvsOrigin, qboolean *mirror ) { + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + vec3_t transformed; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != TR_WORLDENT ) { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.ori ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.ori.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.ori.origin ); + } else { + plane = originalPlane; + } + + VectorCopy( plane.normal, surface->axis[0] ); + PerpendicularVector( surface->axis[1], surface->axis[0] ); + CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] ); + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // get the pvsOrigin from the entity + VectorCopy( e->e.oldorigin, pvsOrigin ); + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) { + VectorScale( plane.normal, plane.dist, surface->origin ); + VectorCopy( surface->origin, camera->origin ); + VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] ); + VectorCopy( surface->axis[1], camera->axis[1] ); + VectorCopy( surface->axis[2], camera->axis[2] ); + + *mirror = qtrue; + return qtrue; + } + + // project the origin onto the surface plane to get + // an origin point we can rotate around + d = DotProduct( e->e.origin, plane.normal ) - plane.dist; + VectorMA( e->e.origin, -d, surface->axis[0], surface->origin ); + + // now get the camera origin and orientation + VectorCopy( e->e.oldorigin, camera->origin ); + AxisCopy( e->e.axis, camera->axis ); + VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] ); + VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] ); + + // optionally rotate + if ( e->e.oldframe ) { + // if a speed is specified + if ( e->e.frame ) { + // continuous rotate + d = (tr.refdef.time/1000.0f) * e->e.frame; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } else { + // bobbing rotate, with skinNum being the rotation offset + d = sin( tr.refdef.time * 0.003f ); + d = e->e.skinNum + d * 4; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + } + else if ( e->e.skinNum ) { + d = e->e.skinNum; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + *mirror = qfalse; + return qtrue; + } + + // if we didn't locate a portal entity, don't render anything. + // We don't want to just treat it as a mirror, because without a + // portal entity the server won't have communicated a proper entity set + // in the snapshot + + // unfortunately, with local movement prediction it is easily possible + // to see a surface before the server has communicated the matching + // portal surface entity, so we don't want to print anything here... + + //Com_Printf ("Portal surface without a portal entity\n" ); + + return qfalse; +} + +static qboolean IsMirror( const drawSurf_t *drawSurf, int entityNum ) +{ + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != TR_WORLDENT ) + { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.ori ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.ori.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.ori.origin ); + } + else + { + plane = originalPlane; + } + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) + { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) + { + return qtrue; + } + + return qfalse; + } + return qfalse; +} +#ifndef DEDICATED +/* +** SurfIsOffscreen +** +** Determines if a surface is completely offscreen. +*/ +static qboolean SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128] ) { + float shortest = 100000000; + int entityNum; + int numTriangles; + shader_t *shader; + int fogNum; + int dlighted; + vec4_t clip, eye; + int i; + unsigned int pointOr = 0; + unsigned int pointAnd = (unsigned int)~0; + + R_RotateForViewer(); + + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted ); + RB_BeginSurface( shader, fogNum ); + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + assert( tess.numVertexes < 128 ); + for ( i = 0; i < tess.numVertexes; i++ ) + { + int j; + unsigned int pointFlags = 0; + + R_TransformModelToClip( tess.xyz[i], tr.ori.modelMatrix, tr.viewParms.projectionMatrix, eye, clip ); + + for ( j = 0; j < 3; j++ ) + { + if ( clip[j] >= clip[3] ) + { + pointFlags |= (1 << (j*2)); + } + else if ( clip[j] <= -clip[3] ) + { + pointFlags |= ( 1 << (j*2+1)); + } + } + pointAnd &= pointFlags; + pointOr |= pointFlags; + } + + // trivially reject + if ( pointAnd ) + { + return qtrue; + } + + // determine if this surface is backfaced and also determine the distance + // to the nearest vertex so we can cull based on portal range. Culling + // based on vertex distance isn't 100% correct (we should be checking for + // range to the surface), but it's good enough for the types of portals + // we have in the game right now. + numTriangles = tess.numIndexes / 3; + + for ( i = 0; i < tess.numIndexes; i += 3 ) + { + vec3_t normal; + float dot; + float len; + + VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.ori.origin, normal ); + + len = VectorLengthSquared( normal ); // lose the sqrt + if ( len < shortest ) + { + shortest = len; + } + + if ( ( dot = DotProduct( normal, tess.normal[tess.indexes[i]] ) ) >= 0 ) + { + numTriangles--; + } + } + if ( !numTriangles ) + { + return qtrue; + } + + // mirrors can early out at this point, since we don't do a fade over distance + // with them (although we could) + if ( IsMirror( drawSurf, entityNum ) ) + { + return qfalse; + } + + if ( shortest > (tess.shader->portalRange*tess.shader->portalRange) ) + { + return qtrue; + } + + return qfalse; +} + +#endif // DEDICATED +/* +======================== +R_MirrorViewBySurface + +Returns qtrue if another view has been rendered +======================== +*/ +qboolean R_MirrorViewBySurface (drawSurf_t *drawSurf, int entityNum) { + vec4_t clipDest[128]; + viewParms_t newParms; + viewParms_t oldParms; + orientation_t surface, camera; + + // don't recursively mirror + if (tr.viewParms.isPortal) { + Com_DPrintf (S_COLOR_RED "WARNING: recursive mirror/portal found\n" ); + return qfalse; + } + + if ( r_noportals->integer || (r_fastsky->integer == 1) ) { + return qfalse; + } + + // trivially reject portal/mirror + if ( SurfIsOffscreen( drawSurf, clipDest ) ) { + return qfalse; + } + + // save old viewParms so we can return to it after the mirror view + oldParms = tr.viewParms; + + newParms = tr.viewParms; + newParms.isPortal = qtrue; + if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera, + newParms.pvsOrigin, &newParms.isMirror ) ) { + return qfalse; // bad portal, no portalentity + } + + R_MirrorPoint (oldParms.ori.origin, &surface, &camera, newParms.ori.origin ); + + VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal ); + newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal ); + + R_MirrorVector (oldParms.ori.axis[0], &surface, &camera, newParms.ori.axis[0]); + R_MirrorVector (oldParms.ori.axis[1], &surface, &camera, newParms.ori.axis[1]); + R_MirrorVector (oldParms.ori.axis[2], &surface, &camera, newParms.ori.axis[2]); + + // OPTIMIZE: restrict the viewport on the mirrored view + + // render the mirror view + R_RenderView (&newParms); + + tr.viewParms = oldParms; + + return qtrue; +} + +/* +================= +R_SpriteFogNum + +See if a sprite is inside a fog volume +================= +*/ +int R_SpriteFogNum( trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) { + break; + } + if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +========================================================================================== + +DRAWSURF SORTING + +========================================================================================== +*/ + +/* +================= +qsort replacement + +================= +*/ +#define SWAP_DRAW_SURF(a,b) temp=((int *)a)[0];((int *)a)[0]=((int *)b)[0];((int *)b)[0]=temp; temp=((int *)a)[1];((int *)a)[1]=((int *)b)[1];((int *)b)[1]=temp; + +/* this parameter defines the cutoff between using quick sort and + insertion sort for arrays; arrays with lengths shorter or equal to the + below value use insertion sort */ + +#define CUTOFF 8 /* testing shows that this is good value */ + +static void shortsort( drawSurf_t *lo, drawSurf_t *hi ) { + drawSurf_t *p, *max; + int temp; + + while (hi > lo) { + max = lo; + for (p = lo + 1; p <= hi; p++ ) { + if ( p->sort > max->sort ) { + max = p; + } + } + SWAP_DRAW_SURF(max, hi); + hi--; + } +} + + +/* sort the array between lo and hi (inclusive) +FIXME: this was lifted and modified from the microsoft lib source... + */ + +void qsortFast ( + void *base, + unsigned num, + unsigned width + ) +{ + char *lo, *hi; /* ends of sub-array currently sorting */ + char *mid; /* points to middle of subarray */ + char *loguy, *higuy; /* traveling pointers for partition step */ + unsigned size; /* size of the sub-array */ + char *lostk[30], *histk[30]; + int stkptr; /* stack for saving sub-array to be processed */ + int temp; + + if ( sizeof(drawSurf_t) != 8 ) { + Com_Error( ERR_DROP, "change SWAP_DRAW_SURF macro" ); + } + + /* Note: the number of stack entries required is no more than + 1 + log2(size), so 30 is sufficient for any array */ + + if (num < 2 || width == 0) + return; /* nothing to do */ + + stkptr = 0; /* initialize stack */ + + lo = (char *)base; + hi = (char *)base + width * (num-1); /* initialize limits */ + + /* this entry point is for pseudo-recursion calling: setting + lo and hi and jumping to here is like recursion, but stkptr is + prserved, locals aren't, so we preserve stuff on the stack */ +recurse: + + size = (hi - lo) / width + 1; /* number of el's to sort */ + + /* below a certain size, it is faster to use a O(n^2) sorting method */ + if (size <= CUTOFF) { + shortsort((drawSurf_t *)lo, (drawSurf_t *)hi); + } + else { + /* First we pick a partititioning element. The efficiency of the + algorithm demands that we find one that is approximately the + median of the values, but also that we select one fast. Using + the first one produces bad performace if the array is already + sorted, so we use the middle one, which would require a very + wierdly arranged array for worst case performance. Testing shows + that a median-of-three algorithm does not, in general, increase + performance. */ + + mid = lo + (size / 2) * width; /* find middle element */ + SWAP_DRAW_SURF(mid, lo); /* swap it to beginning of array */ + + /* We now wish to partition the array into three pieces, one + consisiting of elements <= partition element, one of elements + equal to the parition element, and one of element >= to it. This + is done below; comments indicate conditions established at every + step. */ + + loguy = lo; + higuy = hi + width; + + /* Note that higuy decreases and loguy increases on every iteration, + so loop must terminate. */ + for (;;) { + /* lo <= loguy < hi, lo < higuy <= hi + 1, + A[i] <= A[lo] for lo <= i <= loguy, + A[i] >= A[lo] for higuy <= i <= hi */ + + do { + loguy += width; + } while (loguy <= hi && + ( ((drawSurf_t *)loguy)->sort <= ((drawSurf_t *)lo)->sort ) ); + + /* lo < loguy <= hi+1, A[i] <= A[lo] for lo <= i < loguy, + either loguy > hi or A[loguy] > A[lo] */ + + do { + higuy -= width; + } while (higuy > lo && + ( ((drawSurf_t *)higuy)->sort >= ((drawSurf_t *)lo)->sort ) ); + + /* lo-1 <= higuy <= hi, A[i] >= A[lo] for higuy < i <= hi, + either higuy <= lo or A[higuy] < A[lo] */ + + if (higuy < loguy) + break; + + /* if loguy > hi or higuy <= lo, then we would have exited, so + A[loguy] > A[lo], A[higuy] < A[lo], + loguy < hi, highy > lo */ + + SWAP_DRAW_SURF(loguy, higuy); + + /* A[loguy] < A[lo], A[higuy] > A[lo]; so condition at top + of loop is re-established */ + } + + /* A[i] >= A[lo] for higuy < i <= hi, + A[i] <= A[lo] for lo <= i < loguy, + higuy < loguy, lo <= higuy <= hi + implying: + A[i] >= A[lo] for loguy <= i <= hi, + A[i] <= A[lo] for lo <= i <= higuy, + A[i] = A[lo] for higuy < i < loguy */ + + SWAP_DRAW_SURF(lo, higuy); /* put partition element in place */ + + /* OK, now we have the following: + A[i] >= A[higuy] for loguy <= i <= hi, + A[i] <= A[higuy] for lo <= i < higuy + A[i] = A[lo] for higuy <= i < loguy */ + + /* We've finished the partition, now we want to sort the subarrays + [lo, higuy-1] and [loguy, hi]. + We do the smaller one first to minimize stack usage. + We only sort arrays of length 2 or more.*/ + + if ( higuy - 1 - lo >= hi - loguy ) { + if (lo + width < higuy) { + lostk[stkptr] = lo; + histk[stkptr] = higuy - width; + ++stkptr; + } /* save big recursion for later */ + + if (loguy < hi) { + lo = loguy; + goto recurse; /* do small recursion */ + } + } + else { + if (loguy < hi) { + lostk[stkptr] = loguy; + histk[stkptr] = hi; + ++stkptr; /* save big recursion for later */ + } + + if (lo + width < higuy) { + hi = higuy - width; + goto recurse; /* do small recursion */ + } + } + } + + /* We have sorted the array, except for any pending sorts on the stack. + Check if there are any, and do them. */ + + --stkptr; + if (stkptr >= 0) { + lo = lostk[stkptr]; + hi = histk[stkptr]; + goto recurse; /* pop subarray from stack */ + } + else + return; /* all subarrays done */ +} + + +//========================================================================================== + +/* +================= +R_AddDrawSurf +================= +*/ +void R_AddDrawSurf( surfaceType_t *surface, shader_t *shader, + int fogIndex, int dlightMap ) { + int index; + + if (tr.refdef.rdflags & RDF_NOFOG) + { + fogIndex = 0; + } + else + { + fogIndex = fogIndex; + } + + // instead of checking for overflow, we just mask the index + // so it wraps around + index = tr.refdef.numDrawSurfs & DRAWSURF_MASK; + // the sort data is packed into a single 32 bit value so it can be + // compared quickly during the qsorting process + tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT) + | tr.shiftedEntityNum | ( fogIndex << QSORT_FOGNUM_SHIFT ) | (int)dlightMap; + tr.refdef.drawSurfs[index].surface = surface; + tr.refdef.numDrawSurfs++; +} + +/* +================= +R_DecomposeSort +================= +*/ +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap ) { + *fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31; + *shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ]; + *entityNum = ( sort >> QSORT_ENTITYNUM_SHIFT ) & (MAX_ENTITIES-1); + *dlightMap = sort & 3; +} + +/* +================= +R_SortDrawSurfs +================= +*/ +void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader; + int fogNum; + int entityNum; + int dlighted; + int i; + + // it is possible for some views to not have any surfaces + if ( numDrawSurfs < 1 ) { + // we still need to add it for hyperspace cases + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); + return; + } + + // if we overflowed MAX_DRAWSURFS, the drawsurfs + // wrapped around in the buffer and we will be missing + // the first surfaces, not the last ones + if ( numDrawSurfs > MAX_DRAWSURFS ) { + numDrawSurfs = MAX_DRAWSURFS; +#if defined(_DEBUG) && defined(_XBOX) + Com_Printf(S_COLOR_RED"Draw surface overflow! Tell Brian.\n"); +#endif + } + +#ifndef _XBOX + // sort the drawsurfs by sort type, then orientation, then shader + qsortFast (drawSurfs, numDrawSurfs, sizeof(drawSurf_t) ); +#endif + + // check for any pass through drawing, which + // may cause another view to be rendered first + for ( i = 0 ; i < numDrawSurfs ; i++ ) { + R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum, &dlighted ); + + if ( shader->sort > SS_PORTAL ) { + break; + } + + // no shader should ever have this sort type + if ( shader->sort == SS_BAD ) { + Com_Error (ERR_DROP, "Shader '%s'with sort == SS_BAD", shader->name ); + } + + // if the mirror was completely clipped away, we may need to check another surface + if ( R_MirrorViewBySurface( (drawSurfs+i), entityNum) ) { + // this is a debug option to see exactly what is being mirrored + if ( r_portalOnly->integer ) { + return; + } + break; // only one mirror view at a time + } + } + +#ifdef _XBOX + qsortFast (drawSurfs, numDrawSurfs, sizeof(drawSurf_t) ); +#endif + + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); +} + +/* +============= +R_AddEntitySurfaces +============= +*/ +void R_AddEntitySurfaces (void) { + trRefEntity_t *ent; + shader_t *shader; + + if ( !r_drawentities->integer ) { + return; + } + + for ( tr.currentEntityNum = 0; + tr.currentEntityNum < tr.refdef.num_entities; + tr.currentEntityNum++ ) { + ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum]; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 1) + if(ent->e.skipForPlayer2 == true) + continue; +#endif + + assert(ent->e.renderfx >= 0); + + ent->needDlights = qfalse; + + // preshift the value we are going to OR into the drawsurf sort + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + // + // the weapon model must be handled special -- + // we don't want the hacked weapon position showing in + // mirrors, because the true body position will already be drawn + // + if ( (ent->e.renderfx & RF_FIRST_PERSON) && tr.viewParms.isPortal) { + continue; + } + + // simple generated models, like sprites and beams, are not culled + switch ( ent->e.reType ) { + case RT_PORTALSURFACE: + break; // don't draw anything + case RT_SPRITE: + case RT_BEAM: + case RT_ORIENTED_QUAD: + case RT_ELECTRICITY: + case RT_LINE: + case RT_ORIENTEDLINE: + case RT_CYLINDER: + case RT_SABER_GLOW: + // self blood sprites, talk balloons, etc should not be drawn in the primary + // view. We can't just do this check for all entities, because md3 + // entities may still want to cast shadows from them + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) { + continue; + } + shader = R_GetShaderByHandle( ent->e.customShader ); + R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), 0 ); + break; + + case RT_MODEL: + // we must set up parts of tr.ori for model culling + R_RotateForEntity( ent, &tr.viewParms, &tr.ori ); + + tr.currentModel = R_GetModelByHandle( ent->e.hModel ); + if (!tr.currentModel) { + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 ); + } else { + switch ( tr.currentModel->type ) { + case MOD_MESH: + R_AddMD3Surfaces( ent ); + break; + case MOD_BRUSH: + R_AddBrushModelSurfaces( ent ); + break; +/* +Ghoul2 Insert Start +*/ + case MOD_MDXM: + //g2r + if (ent->e.ghoul2) + { + R_AddGhoulSurfaces( ent); + } + break; + case MOD_BAD: // null model axis + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) + { + if (!(ent->e.renderfx & RF_SHADOW_ONLY)) + { + break; + } + } + + if (ent->e.ghoul2 && G2API_HaveWeGhoul2Models(*((CGhoul2Info_v *)ent->e.ghoul2))) + { + R_AddGhoulSurfaces( ent); + break; + } + + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, false ); + break; +/* +Ghoul2 Insert End +*/ + default: + Com_Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" ); + break; + } + } + break; + + case RT_ENT_CHAIN: + shader = R_GetShaderByHandle( ent->e.customShader ); + R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), false ); + break; + + default: + Com_Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" ); + } + } + +} + + +/* +==================== +R_GenerateDrawSurfs +==================== +*/ +#ifdef _XBOX +extern void R_MarkLeaves(mleaf_s*); +void R_GenerateDrawSurfs( bool isPortal ) { + // determine which leaves are in the PVS / areamask + if ( !(tr.refdef.rdflags & RDF_NOWORLDMODEL) ) { + R_MarkLeaves (NULL); + } + + R_AddWorldSurfaces (); + + R_AddPolygonSurfaces(); + +/* + R_AddTerrainSurfaces(); +*/ + + // set the projection matrix with the minimum zfar + // now that we have the world bounded + // this needs to be done before entities are + // added, because they use the projection + // matrix for lod calculation + R_SetupProjection (); + + R_AddEntitySurfaces (); +} + +#else + +void R_GenerateDrawSurfs( void ) { + R_AddWorldSurfaces (); + + R_AddPolygonSurfaces(); + +/* + R_AddTerrainSurfaces(); //rwwRMG - added +*/ + + // set the projection matrix with the minimum zfar + // now that we have the world bounded + // this needs to be done before entities are + // added, because they use the projection + // matrix for lod calculation + R_SetupProjection (); + + R_AddEntitySurfaces (); +} + +#endif + +/* +================ +R_DebugPolygon +================ +*/ +void R_DebugPolygon( int color, int numPoints, float *points ) { + int i; + + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + // draw solid shade + + qglColor3f( color&1, (color>>1)&1, (color>>2)&1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + + // draw wireframe outline + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + qglDepthRange( 0, 0 ); + qglColor3f( 1, 1, 1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + qglDepthRange( 0, 1 ); +} + +/* +==================== +R_DebugGraphics + +Visualization aid for movement clipping debugging +==================== +*/ +void R_DebugGraphics( void ) { + if ( !r_debugSurface->integer ) { + return; + } + + // the render thread can't make callbacks to the main thread + R_SyncRenderThread(); + + GL_Bind( tr.whiteImage); + GL_Cull( CT_FRONT_SIDED ); + CM_DrawDebugSurface( R_DebugPolygon ); +} + + +/* +================ +R_RenderView + +A view may be either the actual camera view, +or a mirror / remote location +================ +*/ +void R_RenderView (viewParms_t *parms) { + int firstDrawSurf; + + if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) { + return; + } + + tr.viewCount++; + + tr.viewParms = *parms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer (); + + R_SetupFrustum (); + +#ifdef _XBOX + R_GenerateDrawSurfs(parms->isPortal); +#else + R_GenerateDrawSurfs(); +#endif + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf ); + + // draw main system development information (surface outlines, etc) + R_DebugGraphics(); +} + + +#endif // !DEDICATED + diff --git a/codemp/renderer/tr_marks.cpp b/codemp/renderer/tr_marks.cpp new file mode 100644 index 0000000..4eecc28 --- /dev/null +++ b/codemp/renderer/tr_marks.cpp @@ -0,0 +1,449 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_marks.c -- polygon projection on the world polygons + +#include "tr_local.h" +//#include "assert.h" + +#define MAX_VERTS_ON_POLY 64 + +#define MARKER_OFFSET 0 // 1 + +/* +============= +R_ChopPolyBehindPlane + +Out must have space for two more vertexes than in +============= +*/ +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +static void R_ChopPolyBehindPlane( int numInPoints, vec3_t inPoints[MAX_VERTS_ON_POLY], + int *numOutPoints, vec3_t outPoints[MAX_VERTS_ON_POLY], + vec3_t normal, vec_t dist, vec_t epsilon) { + float dists[MAX_VERTS_ON_POLY+4]; + int sides[MAX_VERTS_ON_POLY+4]; + int counts[3]; + float dot; + int i, j; + float *p1, *p2, *clip; + float d; + + // don't clip if it might overflow + if ( numInPoints >= MAX_VERTS_ON_POLY - 2 ) { + *numOutPoints = 0; + return; + } + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0 ; i < numInPoints ; i++ ) { + dot = DotProduct( inPoints[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *numOutPoints = 0; + + if ( !counts[0] ) { + return; + } + if ( !counts[1] ) { + *numOutPoints = numInPoints; + Com_Memcpy( outPoints, inPoints, numInPoints * sizeof(vec3_t) ); + return; + } + + for ( i = 0 ; i < numInPoints ; i++ ) { + p1 = inPoints[i]; + clip = outPoints[ *numOutPoints ]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + clip = outPoints[ *numOutPoints ]; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = inPoints[ (i+1) % numInPoints ]; + + d = dists[i] - dists[i+1]; + if ( d == 0 ) { + dot = 0; + } else { + dot = dists[i] / d; + } + + // clip xyz + + for (j=0 ; j<3 ; j++) { + clip[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + (*numOutPoints)++; + } +} + +/* +================= +R_BoxSurfaces_r + +================= +*/ +void R_BoxSurfaces_r(mnode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir) { + + int s, c; + msurface_t *surf, **mark; + + // do the tail recursion in a loop + while ( node->contents == -1 ) { +#ifdef _XBOX + s = BoxOnPlaneSide( mins, maxs, tr.world->planes + node->planeNum ); +#else + s = BoxOnPlaneSide( mins, maxs, node->plane ); +#endif + if (s == 1) { + node = node->children[0]; + } else if (s == 2) { + node = node->children[1]; + } else { + R_BoxSurfaces_r(node->children[0], mins, maxs, list, listsize, listlength, dir); + node = node->children[1]; + } + } + + // add the individual surfaces +#ifdef _XBOX + mleaf_s *leaf = (mleaf_s*)node; + mark = tr.world->marksurfaces + leaf->firstMarkSurfNum; + c = leaf->nummarksurfaces; +#else + mark = node->firstmarksurface; + c = node->nummarksurfaces; +#endif + while (c--) { + // + if (*listlength >= listsize) break; + // + surf = *mark; + // check if the surface has NOIMPACT or NOMARKS set + if ( ( surf->shader->surfaceFlags & ( SURF_NOIMPACT | SURF_NOMARKS ) ) + || ( surf->shader->contentFlags & CONTENTS_FOG ) ) { + surf->viewCount = tr.viewCount; + } + // extra check for surfaces to avoid list overflows + else if (*(surf->data) == SF_FACE) { + // the face plane should go through the box + s = BoxOnPlaneSide( mins, maxs, &(( srfSurfaceFace_t * ) surf->data)->plane ); + if (s == 1 || s == 2) { + surf->viewCount = tr.viewCount; + } else if (DotProduct((( srfSurfaceFace_t * ) surf->data)->plane.normal, dir) > -0.5) { + // don't add faces that make sharp angles with the projection direction + surf->viewCount = tr.viewCount; + } + } + else if (*(surfaceType_t *) (surf->data) != SF_GRID) surf->viewCount = tr.viewCount; + // check the viewCount because the surface may have + // already been added if it spans multiple leafs + if (surf->viewCount != tr.viewCount) { + surf->viewCount = tr.viewCount; + list[*listlength] = (surfaceType_t *) surf->data; + (*listlength)++; + } + mark++; + } +} + +/* +================= +R_AddMarkFragments + +================= +*/ +void R_AddMarkFragments(int numClipPoints, vec3_t clipPoints[2][MAX_VERTS_ON_POLY], + int numPlanes, vec3_t *normals, float *dists, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer, + int *returnedPoints, int *returnedFragments, + vec3_t mins, vec3_t maxs) { + int pingPong, i; + markFragment_t *mf; + + // chop the surface by all the bounding planes of the to be projected polygon + pingPong = 0; + + for ( i = 0 ; i < numPlanes ; i++ ) { + + R_ChopPolyBehindPlane( numClipPoints, clipPoints[pingPong], + &numClipPoints, clipPoints[!pingPong], + normals[i], dists[i], 0.5 ); + pingPong ^= 1; + if ( numClipPoints == 0 ) { + break; + } + } + // completely clipped away? + if ( numClipPoints == 0 ) { + return; + } + + // add this fragment to the returned list + if ( numClipPoints + (*returnedPoints) > maxPoints ) { + return; // not enough space for this polygon + } + /* + // all the clip points should be within the bounding box + for ( i = 0 ; i < numClipPoints ; i++ ) { + int j; + for ( j = 0 ; j < 3 ; j++ ) { + if (clipPoints[pingPong][i][j] < mins[j] - 0.5) break; + if (clipPoints[pingPong][i][j] > maxs[j] + 0.5) break; + } + if (j < 3) break; + } + if (i < numClipPoints) return; + */ + + mf = fragmentBuffer + (*returnedFragments); + mf->firstPoint = (*returnedPoints); + mf->numPoints = numClipPoints; + Com_Memcpy( pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t) ); + + (*returnedPoints) += numClipPoints; + (*returnedFragments)++; +} + +/* +================= +R_MarkFragments + +================= +*/ +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { + int numsurfaces, numPlanes; + int i, j, k, m, n; + surfaceType_t *surfaces[64]; + vec3_t mins, maxs; + int returnedFragments; + int returnedPoints; + vec3_t normals[MAX_VERTS_ON_POLY+2]; + float dists[MAX_VERTS_ON_POLY+2]; + vec3_t clipPoints[2][MAX_VERTS_ON_POLY]; + int numClipPoints; + float *v; + srfSurfaceFace_t *surf; + srfGridMesh_t *cv; + drawVert_t *dv; + vec3_t normal; + vec3_t projectionDir; + vec3_t v1, v2; + int *indexes; + + //increment view count for double check prevention + tr.viewCount++; + + // + VectorNormalize2( projection, projectionDir ); + // find all the brushes that are to be considered + ClearBounds( mins, maxs ); + for ( i = 0 ; i < numPoints ; i++ ) { + vec3_t temp; + + AddPointToBounds( points[i], mins, maxs ); + VectorAdd( points[i], projection, temp ); + AddPointToBounds( temp, mins, maxs ); + // make sure we get all the leafs (also the one(s) in front of the hit surface) + VectorMA( points[i], -20, projectionDir, temp ); + AddPointToBounds( temp, mins, maxs ); + } + + if (numPoints > MAX_VERTS_ON_POLY) numPoints = MAX_VERTS_ON_POLY; + // create the bounding planes for the to be projected polygon + for ( i = 0 ; i < numPoints ; i++ ) { + VectorSubtract(points[(i+1)%numPoints], points[i], v1); + VectorAdd(points[i], projection, v2); + VectorSubtract(points[i], v2, v2); + CrossProduct(v1, v2, normals[i]); + VectorNormalizeFast(normals[i]); + dists[i] = DotProduct(normals[i], points[i]); + } + // add near and far clipping planes for projection + VectorCopy(projectionDir, normals[numPoints]); + dists[numPoints] = DotProduct(normals[numPoints], points[0]) - 32; + VectorCopy(projectionDir, normals[numPoints+1]); + VectorInverse(normals[numPoints+1]); + dists[numPoints+1] = DotProduct(normals[numPoints+1], points[0]) - 20; + numPlanes = numPoints + 2; + + numsurfaces = 0; + R_BoxSurfaces_r(tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir); + //assert(numsurfaces <= 64); + //assert(numsurfaces != 64); + + returnedPoints = 0; + returnedFragments = 0; + + for ( i = 0 ; i < numsurfaces ; i++ ) { + + if (*surfaces[i] == SF_GRID) { + + cv = (srfGridMesh_t *) surfaces[i]; + for ( m = 0 ; m < cv->height - 1 ; m++ ) { + for ( n = 0 ; n < cv->width - 1 ; n++ ) { + // We triangulate the grid and chop all triangles within + // the bounding planes of the to be projected polygon. + // LOD is not taken into account, not such a big deal though. + // + // It's probably much nicer to chop the grid itself and deal + // with this grid as a normal SF_GRID surface so LOD will + // be applied. However the LOD of that chopped grid must + // be synced with the LOD of the original curve. + // One way to do this; the chopped grid shares vertices with + // the original curve. When LOD is applied to the original + // curve the unused vertices are flagged. Now the chopped curve + // should skip the flagged vertices. This still leaves the + // problems with the vertices at the chopped grid edges. + // + // To avoid issues when LOD applied to "hollow curves" (like + // the ones around many jump pads) we now just add a 2 unit + // offset to the triangle vertices. + // The offset is added in the vertex normal vector direction + // so all triangles will still fit together. + // The 2 unit offset should avoid pretty much all LOD problems. + + numClipPoints = 3; + + dv = cv->verts + m * cv->width + n; + + VectorCopy(dv[0].xyz, clipPoints[0][0]); + VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); + VectorCopy(dv[1].xyz, clipPoints[0][2]); + VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.1) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + + VectorCopy(dv[1].xyz, clipPoints[0][0]); + VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); + VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]); + VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[cv->width+1].normal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.05) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + } + } + else if (*surfaces[i] == SF_FACE) { + + surf = ( srfSurfaceFace_t * ) surfaces[i]; + // check the normal of this face + if (DotProduct(surf->plane.normal, projectionDir) > -0.5) { + continue; + } + + /* + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalize(normal); + if (DotProduct(normal, projectionDir) > -0.5) continue; + */ +#ifdef _XBOX + const unsigned char * const indexes = (unsigned char *)( (byte *)surf + surf->ofsIndices ); + int nextSurfPoint = NEXT_SURFPOINT(surf->flags); +#else + indexes = (int *)( (byte *)surf + surf->ofsIndices ); +#endif + for ( k = 0 ; k < surf->numIndices ; k += 3 ) { + for ( j = 0 ; j < 3 ; j++ ) { +#ifdef _XBOX + const unsigned short* v = surf->srfPoints + nextSurfPoint * indexes[k+j]; + float fVec[3]; + Q_CastShort2Float(&fVec[0], (short*)v + 0); + Q_CastShort2Float(&fVec[1], (short*)v + 1); + Q_CastShort2Float(&fVec[2], (short*)v + 2); + VectorMA( fVec, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] ); +#else + v = surf->points[0] + VERTEXSIZE * indexes[k+j];; + VectorMA( v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] ); +#endif + } + // add the fragments of this face + R_AddMarkFragments( 3 , clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + continue; + } + else { + // ignore all other world surfaces + // might be cool to also project polygons on a triangle soup + // however this will probably create huge amounts of extra polys + // even more than the projection onto curves + continue; + } + } + return returnedFragments; +} + diff --git a/codemp/renderer/tr_mesh.cpp b/codemp/renderer/tr_mesh.cpp new file mode 100644 index 0000000..48d8eb3 --- /dev/null +++ b/codemp/renderer/tr_mesh.cpp @@ -0,0 +1,409 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_mesh.c: triangle model functions + +#include "tr_local.h" + +float ProjectRadius( float r, vec3_t location ) +{ + float pr; + float dist; + float c; + vec3_t p; + float width; + float depth; + + c = DotProduct( tr.viewParms.ori.axis[0], tr.viewParms.ori.origin ); + dist = DotProduct( tr.viewParms.ori.axis[0], location ) - c; + + if ( dist <= 0 ) + return 0; + + p[0] = 0; + p[1] = Q_fabs( r ); + p[2] = -dist; + + width = p[0] * tr.viewParms.projectionMatrix[1] + + p[1] * tr.viewParms.projectionMatrix[5] + + p[2] * tr.viewParms.projectionMatrix[9] + + tr.viewParms.projectionMatrix[13]; + + depth = p[0] * tr.viewParms.projectionMatrix[3] + + p[1] * tr.viewParms.projectionMatrix[7] + + p[2] * tr.viewParms.projectionMatrix[11] + + tr.viewParms.projectionMatrix[15]; + + pr = width / depth; +#if defined (_XBOX) + pr = -pr; +#endif + + if ( pr > 1.0f ) + pr = 1.0f; + + return pr; +} + +#ifndef DEDICATED +/* +============= +R_CullModel +============= +*/ +static int R_CullModel( md3Header_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + md3Frame_t *oldFrame, *newFrame; + int i; + + // compute frame pointers + newFrame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + oldFrame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.oldframe; + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + +/* +================= +RE_GetModelBounds + + Returns the bounds of the current model + (qhandle_t)hModel and (int)frame need to be set +================= +*/ +//rwwRMG - added +void RE_GetModelBounds(refEntity_t *refEnt, vec3_t bounds1, vec3_t bounds2) +{ + md3Frame_t *frame; + md3Header_t *header; + model_t *model; + + assert(refEnt); + + model = R_GetModelByHandle( refEnt->hModel ); + assert(model); + header = model->md3[0]; + assert(header); + frame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + refEnt->frame; + assert(frame); + + VectorCopy(frame->bounds[0], bounds1); + VectorCopy(frame->bounds[1], bounds2); +} + +/* +================= +R_ComputeLOD + +================= +*/ +int R_ComputeLOD( trRefEntity_t *ent ) { + float radius; + float flod, lodscale; + float projectedRadius; + md3Frame_t *frame; + int lod; + + if ( tr.currentModel->numLods < 2 ) + { + // model has only 1 LOD level, skip computations and bias + lod = 0; + } + else + { + // multiple LODs exist, so compute projected bounding sphere + // and use that as a criteria for selecting LOD + + frame = ( md3Frame_t * ) ( ( ( unsigned char * ) tr.currentModel->md3[0] ) + tr.currentModel->md3[0]->ofsFrames ); + + frame += ent->e.frame; + + radius = RadiusFromBounds( frame->bounds[0], frame->bounds[1] ); + + if ( ( projectedRadius = ProjectRadius( radius, ent->e.origin ) ) != 0 ) + { + lodscale = (r_lodscale->value+r_autolodscalevalue->value); + if ( lodscale > 20 ) + { + lodscale = 20; + } + else if ( lodscale < 0 ) + { + lodscale = 0; + } + flod = 1.0f - projectedRadius * lodscale; + } + else + { + // object intersects near view plane, e.g. view weapon + flod = 0; + } + + flod *= tr.currentModel->numLods; + lod = myftol( flod ); + + if ( lod < 0 ) + { + lod = 0; + } + else if ( lod >= tr.currentModel->numLods ) + { + lod = tr.currentModel->numLods - 1; + } + } + + lod += r_lodbias->integer; + + if ( lod >= tr.currentModel->numLods ) + lod = tr.currentModel->numLods - 1; + if ( lod < 0 ) + lod = 0; + + return lod; +} + +/* +================= +R_ComputeFogNum + +================= +*/ +int R_ComputeFogNum( md3Header_t *header, trRefEntity_t *ent ) { + int i, j; + fog_t *fog; + md3Frame_t *md3Frame; + vec3_t localOrigin; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + // FIXME: non-normalized axis issues + md3Frame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + VectorAdd( ent->e.origin, md3Frame->localOrigin, localOrigin ); + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + for ( j = 0 ; j < 3 ; j++ ) { + if ( localOrigin[j] - md3Frame->radius >= fog->bounds[1][j] ) { + break; + } + if ( localOrigin[j] + md3Frame->radius <= fog->bounds[0][j] ) { + break; + } + } + if ( j == 3 ) { + return i; + } + } + + return 0; +} + +/* +================= +R_AddMD3Surfaces + +================= +*/ +void R_AddMD3Surfaces( trRefEntity_t *ent ) { + int i; + md3Header_t *header = 0; + md3Surface_t *surface = 0; + md3Shader_t *md3Shader = 0; + shader_t *shader = 0; + int cull; + int lod; + int fogNum; + qboolean personalModel; + + // don't add third_person objects if not in a portal + personalModel = (qboolean)((ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal); + + if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= tr.currentModel->md3[0]->numFrames; + ent->e.oldframe %= tr.currentModel->md3[0]->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( (ent->e.frame >= tr.currentModel->md3[0]->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= tr.currentModel->md3[0]->numFrames) + || (ent->e.oldframe < 0) ) { + Com_DPrintf (S_COLOR_RED "R_AddMD3Surfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // compute LOD + // + lod = R_ComputeLOD( ent ); + + header = tr.currentModel->md3[lod]; + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullModel ( header, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeFogNum( header, ent ); + + // + // draw all surfaces + // + surface = (md3Surface_t *)( (byte *)header + header->ofsSurfaces ); + for ( i = 0 ; i < header->numSurfaces ; i++ ) { + + if ( ent->e.customShader ) { + shader = R_GetShaderByHandle( ent->e.customShader ); + } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { + skin_t *skin; + int j; + + skin = R_GetSkinByHandle( ent->e.customSkin ); + + // match the surface name to something in the skin file + shader = tr.defaultShader; + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + // the names have both been lowercased + if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + if (shader == tr.defaultShader) { + Com_DPrintf (S_COLOR_RED "WARNING: no shader for surface %s in skin %s\n", surface->name, skin->name); + } + else if (shader->defaultShader) { + Com_DPrintf (S_COLOR_RED "WARNING: shader %s in skin %s not found\n", shader->name, skin->name); + } + } else if ( surface->numShaders <= 0 ) { + shader = tr.defaultShader; + } else { + md3Shader = (md3Shader_t *) ( (byte *)surface + surface->ofsShaders ); + md3Shader += ent->e.skinNum % surface->numShaders; + shader = tr.shaders[ md3Shader->shaderIndex ]; + } + + + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && fogNum == 0 + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t *)surface, tr.shadowShader, 0, qfalse ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t *)surface, tr.projectionShadowShader, 0, qfalse ); + } + + // don't add third_person objects if not viewing through a portal + if ( !personalModel ) { + R_AddDrawSurf( (surfaceType_t *)surface, shader, fogNum, qfalse ); + } + + surface = (md3Surface_t *)( (byte *)surface + surface->ofsEnd ); + } + +} + +#endif // !DEDICATED + diff --git a/codemp/renderer/tr_model.cpp b/codemp/renderer/tr_model.cpp new file mode 100644 index 0000000..2e44818 --- /dev/null +++ b/codemp/renderer/tr_model.cpp @@ -0,0 +1,2042 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_models.c -- model loading and caching + +#include "tr_local.h" + + +#include "../qcommon/disablewarnings.h" + + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include "../qcommon/sstring.h" // #include +#include +#include +#pragma warning (pop) + +using namespace std; + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "modelmem.h" +ModelMemoryManager ModelMem; + +#include "../zlib/zlib.h" +#endif + + +#define LL(x) x=LittleLong(x) + +static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *name, qboolean &bAlreadyCached ); +/* +Ghoul2 Insert Start +*/ + +//typedef struct modelHash_s +//{ +// char name[MAX_QPATH]; +// qhandle_t handle; +// struct modelHash_s *next; +// +//}modelHash_t; +// +//#define FILE_HASH_SIZE 2048 +//static modelHash_t *mhHashTable[FILE_HASH_SIZE]; + + +typedef map HashTable; +typedef HashTable::iterator HashTableIterator; +HashTable *mhHashTable = NULL; + +/* +Ghoul2 Insert End +*/ + + +// This stuff looks a bit messy, but it's kept here as black box, and nothing appears in any .H files for other +// modules to worry about. I may make another module for this sometime. +// +typedef pair StringOffsetAndShaderIndexDest_t; +typedef vector ShaderRegisterData_t; +struct CachedEndianedModelBinary_s +{ + void *pModelDiskImage; + int iAllocSize; // may be useful for mem-query, but I don't actually need it + ShaderRegisterData_t ShaderRegisterData; + int iLastLevelUsedOn; + int iPAKFileCheckSum; // else -1 if not from PAK +#ifdef _XBOX + int ID; +#endif + + + CachedEndianedModelBinary_s() + { + pModelDiskImage = 0; + iAllocSize = 0; + ShaderRegisterData.clear(); + iLastLevelUsedOn = -1; + iPAKFileCheckSum = -1; +#ifdef _XBOX + ID = -1; +#endif + } +}; +typedef struct CachedEndianedModelBinary_s CachedEndianedModelBinary_t; +typedef map CachedModels_t; +CachedModels_t *CachedModels = NULL; // the important cache item. + +void RE_RegisterModels_StoreShaderRequest(const char *psModelFileName, const char *psShaderName, int *piShaderIndexPoke) +{ + char sModelName[MAX_QPATH]; + + assert(CachedModels); + + Q_strncpyz(sModelName,psModelFileName,sizeof(sModelName)); + Q_strlwr (sModelName); + + CachedEndianedModelBinary_t &ModelBin = (*CachedModels)[sModelName]; + + if (ModelBin.pModelDiskImage == NULL) + { + assert(0); // should never happen, means that we're being called on a model that wasn't loaded + } + else + { + int iNameOffset = psShaderName - (char *)ModelBin.pModelDiskImage; + int iPokeOffset = (char*) piShaderIndexPoke - (char *)ModelBin.pModelDiskImage; + + ModelBin.ShaderRegisterData.push_back( StringOffsetAndShaderIndexDest_t( iNameOffset,iPokeOffset) ); + } +} + + +static const byte FakeGLAFile[] = +{ +0x32, 0x4C, 0x47, 0x41, 0x06, 0x00, 0x00, 0x00, 0x2A, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x01, 0x00, 0x00, 0x00, +0x14, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, +0x26, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4D, 0x6F, 0x64, 0x56, 0x69, 0x65, 0x77, 0x20, +0x69, 0x6E, 0x74, 0x65, 0x72, 0x6E, 0x61, 0x6C, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, +0x00, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, +0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, +0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, +0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xBF, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, +0x00, 0x80, 0x00, 0x80, 0x00, 0x80 +}; + +void RE_LoadWorldMap_Actual( const char *name, world_t &worldData, int index ); + +// returns qtrue if loaded, and sets the supplied qbool to true if it was from cache (instead of disk) +// (which we need to know to avoid LittleLong()ing everything again (well, the Mac needs to know anyway)... +// +// don't use ri.xxx functions in case running on dedicated... +// +qboolean RE_RegisterModels_GetDiskFile( const char *psModelFileName, void **ppvBuffer, qboolean *pqbAlreadyCached) +{ + char sModelName[MAX_QPATH]; + + assert(CachedModels); + + Q_strncpyz(sModelName,psModelFileName,sizeof(sModelName)); + Q_strlwr (sModelName); + + CachedEndianedModelBinary_t &ModelBin = (*CachedModels)[sModelName]; + + if (ModelBin.pModelDiskImage == NULL) + { + // didn't have it cached, so try the disk... + // + + // special case intercept first... + // + if (!strcmp(sDEFAULT_GLA_NAME ".gla" , psModelFileName)) + { + // return fake params as though it was found on disk... + // + void *pvFakeGLAFile = Z_Malloc( sizeof(FakeGLAFile), TAG_FILESYS, qfalse ); + memcpy(pvFakeGLAFile, &FakeGLAFile[0], sizeof(FakeGLAFile)); + *ppvBuffer = pvFakeGLAFile; + *pqbAlreadyCached = qfalse; // faking it like this should mean that it works fine on the Mac as well + return qtrue; + } + + FS_ReadFile( sModelName, ppvBuffer ); + *pqbAlreadyCached = qfalse; + qboolean bSuccess = !!(*ppvBuffer)?qtrue:qfalse; + + if (bSuccess) + { + Com_DPrintf( "RE_RegisterModels_GetDiskFile(): Disk-loading \"%s\"\n",psModelFileName); + } + + return bSuccess; + } + else + { + *ppvBuffer = ModelBin.pModelDiskImage; + *pqbAlreadyCached = qtrue; + return qtrue; + } +} + + +// if return == true, no further action needed by the caller... +// +// don't use ri.xxx functions in case running on dedicated +// +extern cvar_t *sv_pure; +#ifdef _XBOX +void *RE_RegisterModels_Malloc(int iSize, void *pvDiskBufferIfJustLoaded, const char *psModelFileName, qboolean *pqbAlreadyFound, memtag_t eTag, int modindex, bool useModelMem) +#else +void *RE_RegisterModels_Malloc(int iSize, void *pvDiskBufferIfJustLoaded, const char *psModelFileName, qboolean *pqbAlreadyFound, memtag_t eTag) +#endif +{ + char sModelName[MAX_QPATH]; + + assert(CachedModels); + +#ifdef _XBOX + static int modelCount = 0; +#endif + + Q_strncpyz(sModelName,psModelFileName,sizeof(sModelName)); + Q_strlwr (sModelName); + + CachedEndianedModelBinary_t &ModelBin = (*CachedModels)[sModelName]; + + if (ModelBin.pModelDiskImage == NULL) + { + // ... then this entry has only just been created, ie we need to load it fully... + // + // new, instead of doing a Z_Malloc and assigning that we just morph the disk buffer alloc + // then don't thrown it away on return - cuts down on mem overhead + // + // ... groan, but not if doing a limb hierarchy creation (some VV stuff?), in which case it's NULL + // +#ifndef _XBOX // Can't re-tag allocated memory! + if ( pvDiskBufferIfJustLoaded ) + { + Z_MorphMallocTag( pvDiskBufferIfJustLoaded, eTag ); + } + else +#endif + { +#ifdef _XBOX +// if(strstr(sModelName, "players") && eTag == TAG_MODEL_GLM && ModelMem.IsNPCMode() == false) { + if(useModelMem) { + pvDiskBufferIfJustLoaded = ModelMem.GetModelMemory(iSize, modindex, sModelName); + ModelBin.ID = modindex; + } + else +#endif + pvDiskBufferIfJustLoaded = Z_Malloc(iSize,eTag, qfalse ); + } + + ModelBin.pModelDiskImage = pvDiskBufferIfJustLoaded; + ModelBin.iAllocSize = iSize; + + int iCheckSum; + if (FS_FileIsInPAK(sModelName, &iCheckSum) == 1) + { + ModelBin.iPAKFileCheckSum = iCheckSum; // else ModelBin's constructor will leave it as -1 + } + + *pqbAlreadyFound = qfalse; + } + else + { +#ifndef DEDICATED + // if we already had this model entry, then re-register all the shaders it wanted... + // + int iEntries = ModelBin.ShaderRegisterData.size(); + for (int i=0; idefaultShader ) + { + *piShaderPokePtr = 0; + } else { + *piShaderPokePtr = sh->index; + } + } +#endif //!DEDICATED + *pqbAlreadyFound = qtrue; // tell caller not to re-Endian or re-Shader this binary + } + + ModelBin.iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); + + return ModelBin.pModelDiskImage; +} + +// Unfortunately the dedicated server also hates shader loading. So we need an alternate of this func. +// +void *RE_RegisterServerModels_Malloc(int iSize, void *pvDiskBufferIfJustLoaded, const char *psModelFileName, qboolean *pqbAlreadyFound, memtag_t eTag, int modindex) +{ + char sModelName[MAX_QPATH]; + + assert(CachedModels); + + Q_strncpyz(sModelName,psModelFileName,sizeof(sModelName)); + Q_strlwr (sModelName); + + CachedEndianedModelBinary_t &ModelBin = (*CachedModels)[sModelName]; + + if (ModelBin.pModelDiskImage == NULL) + { + // new, instead of doing a Z_Malloc and assigning that we just morph the disk buffer alloc + // then don't thrown it away on return - cuts down on mem overhead + // + // ... groan, but not if doing a limb hierarchy creation (some VV stuff?), in which case it's NULL + // +#ifndef _XBOX // Can't re-tag allocated memory! + if ( pvDiskBufferIfJustLoaded ) + { + Z_MorphMallocTag( pvDiskBufferIfJustLoaded, eTag ); + } + else +#endif + { + if(strstr(sModelName, "players") && eTag == TAG_MODEL_GLM && ModelMem.IsNPCMode() == false) { + pvDiskBufferIfJustLoaded = ModelMem.GetModelMemory(iSize, modindex, sModelName); + ModelBin.ID = modindex; + } + else + pvDiskBufferIfJustLoaded = Z_Malloc(iSize,eTag, qfalse ); + } + + ModelBin.pModelDiskImage = pvDiskBufferIfJustLoaded; + ModelBin.iAllocSize = iSize; + + int iCheckSum; + if (FS_FileIsInPAK(sModelName, &iCheckSum) == 1) + { + ModelBin.iPAKFileCheckSum = iCheckSum; // else ModelBin's constructor will leave it as -1 + } + + *pqbAlreadyFound = qfalse; + } + else + { + // if we already had this model entry, then re-register all the shaders it wanted... + // + /* + int iEntries = ModelBin.ShaderRegisterData.size(); + for (int i=0; idefaultShader ) + { + *piShaderPokePtr = 0; + } else { + *piShaderPokePtr = sh->index; + } + } + */ + //No. Bad. + *pqbAlreadyFound = qtrue; // tell caller not to re-Endian or re-Shader this binary + } + + ModelBin.iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); + + return ModelBin.pModelDiskImage; +} + +// dump any models not being used by this level if we're running low on memory... +// +static int GetModelDataAllocSize(void) +{ + return Z_MemSize( TAG_MODEL_MD3) + + Z_MemSize( TAG_MODEL_GLM) + + Z_MemSize( TAG_MODEL_GLA); +} +extern cvar_t *r_modelpoolmegs; +// +// return qtrue if at least one cached model was freed (which tells z_malloc()-fail recoveryt code to try again) +// +extern qboolean gbInsideRegisterModel; +qboolean RE_RegisterModels_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel /* = qfalse */) +{ + qboolean bAtLeastoneModelFreed = qfalse; + + assert(CachedModels); + + Com_DPrintf (S_COLOR_RED "RE_RegisterModels_LevelLoadEnd():\n"); + + if (gbInsideRegisterModel) + { + Com_DPrintf( "(Inside RE_RegisterModel (z_malloc recovery?), exiting...\n"); + } + else + { + int iLoadedModelBytes = GetModelDataAllocSize(); + const int iMaxModelBytes= r_modelpoolmegs->integer * 1024 * 1024; + + qboolean bEraseOccured = qfalse; + for (CachedModels_t::iterator itModel = CachedModels->begin(); itModel != CachedModels->end() && ( bDeleteEverythingNotUsedThisLevel || iLoadedModelBytes > iMaxModelBytes ); bEraseOccured?itModel:++itModel) + { + bEraseOccured = qfalse; + + CachedEndianedModelBinary_t &CachedModel = (*itModel).second; + + qboolean bDeleteThis = qfalse; + + if (bDeleteEverythingNotUsedThisLevel) + { + bDeleteThis = (CachedModel.iLastLevelUsedOn != RE_RegisterMedia_GetLevel()) ? qtrue : qfalse; + } + else + { + bDeleteThis = (CachedModel.iLastLevelUsedOn < RE_RegisterMedia_GetLevel()) ? qtrue : qfalse; + } + + // if it wasn't used on this level, dump it... + // + if (bDeleteThis) + { + Com_Error( ERR_DROP, "Trying to delete from CachedModels in LevelLoadEnd" ); +#if 0 + LPCSTR psModelName = (*itModel).first.c_str(); + Com_DPrintf (S_COLOR_RED "Dumping \"%s\"", psModelName); + + #ifdef _DEBUG + Com_DPrintf (S_COLOR_RED ", used on lvl %d\n",CachedModel.iLastLevelUsedOn); + #endif + + if (CachedModel.pModelDiskImage) { +#ifdef _XBOX + if(CachedModel.ID != -1) { + ModelMem.FreeModelMemory(CachedModel.ID); + } + else +#endif + Z_Free(CachedModel.pModelDiskImage); + + //CachedModel.pModelDiskImage = NULL; // REM for reference, erase() call below negates the need for it. + bAtLeastoneModelFreed = qtrue; + } +#ifndef __linux__ + itModel = CachedModels->erase(itModel); + bEraseOccured = qtrue; +#else + // Both MS and Dinkumware got the map::erase wrong + // The STL has the return type as a void + CachedModels_t::iterator itTemp; + itTemp = itModel; + itModel++; + CachedModels->erase(itTemp); + +#endif + + iLoadedModelBytes = GetModelDataAllocSize(); +#endif + } + } + } + + Com_DPrintf (S_COLOR_RED "RE_RegisterModels_LevelLoadEnd(): Ok\n"); + + return bAtLeastoneModelFreed; +} + + + +// scan through all loaded models and see if their PAK checksums are still valid with the current pure PAK lists, +// dump any that aren't (so people can't cheat by using models with huge spikes that show through walls etc) +// +// (avoid using ri.xxxx stuff here in case running on dedicated) +// +#if 0 +static void RE_RegisterModels_DumpNonPure(void) +{ + Com_DPrintf( "RE_RegisterModels_DumpNonPure():\n"); + + if(!CachedModels) { + return; + } + qboolean bEraseOccured = qfalse; + for (CachedModels_t::iterator itModel = CachedModels->begin(); itModel != CachedModels->end(); bEraseOccured?itModel:++itModel) + { + bEraseOccured = qfalse; + + LPCSTR psModelName = (*itModel).first.c_str(); + CachedEndianedModelBinary_t &CachedModel = (*itModel).second; + + int iCheckSum = -1; + int iInPak = FS_FileIsInPAK(psModelName, &iCheckSum); + + if (iInPak == -1 || iCheckSum != CachedModel.iPAKFileCheckSum) + { + if (stricmp(sDEFAULT_GLA_NAME ".gla" , psModelName)) // don't dump "*default.gla", that's program internal anyway + { + // either this is not from a PAK, or it's from a non-pure one, so ditch it... + // + Com_DPrintf( "Dumping none pure model \"%s\"", psModelName); + + if (CachedModel.pModelDiskImage) { +#ifdef _XBOX + if(CachedModel.ID != -1) { + ModelMem.FreeModelMemory(CachedModel.ID); + } + else +#endif + Z_Free(CachedModel.pModelDiskImage); + //CachedModel.pModelDiskImage = NULL; // REM for reference, erase() call below negates the need for it. + } +#ifndef __linux__ + itModel = CachedModels->erase(itModel); + bEraseOccured = qtrue; +#else + // Both MS and Dinkumware got the map::erase wrong + // The STL has the return type as a void + CachedModels_t::iterator itTemp; + itTemp = itModel; + itModel++; + CachedModels->erase(itTemp); + +#endif + } + } + } + + Com_DPrintf( "RE_RegisterModels_DumpNonPure(): Ok\n"); +} +#endif + +void RE_RegisterModels_Info_f( void ) +{ + int iTotalBytes = 0; + if(!CachedModels) { + Com_Printf ("%d bytes total (%.2fMB)\n",iTotalBytes, (float)iTotalBytes / 1024.0f / 1024.0f); + return; + } + + int iModels = CachedModels->size(); + int iModel = 0; + + for (CachedModels_t::iterator itModel = CachedModels->begin(); itModel != CachedModels->end(); ++itModel,iModel++) + { + CachedEndianedModelBinary_t &CachedModel = (*itModel).second; + + Com_Printf ("%d/%d: \"%s\" (%d bytes)",iModel,iModels,(*itModel).first.c_str(),CachedModel.iAllocSize ); + + #ifdef _DEBUG + Com_Printf (", lvl %d\n",CachedModel.iLastLevelUsedOn); + #endif + + iTotalBytes += CachedModel.iAllocSize; + } + Com_Printf ("%d bytes total (%.2fMB)\n",iTotalBytes, (float)iTotalBytes / 1024.0f / 1024.0f); +} + + +// (don't use ri.xxx functions since the renderer may not be running here)... +// +static void RE_RegisterModels_DeleteAll(void) +{ + if(!CachedModels) { + return; //argh! + } + +#ifndef __linux__ + for (CachedModels_t::iterator itModel = CachedModels->begin(); itModel != CachedModels->end(); ) + { + CachedEndianedModelBinary_t &CachedModel = (*itModel).second; + + if (CachedModel.pModelDiskImage) + { + if(CachedModel.ID != -1) + { // This already removes the entry from CachedModels! + if( ModelMem.ClearModelMemory(CachedModel.ID) ) + itModel = CachedModels->begin(); + else + itModel = CachedModels->erase(itModel); + } + else + { + Z_Free(CachedModel.pModelDiskImage); + itModel = CachedModels->erase(itModel); + } + } + else + { + itModel = CachedModels->erase(itModel); + } + } +#else + CachedModels->erase(CachedModels->begin(),CachedModels->end()); +#endif + +#ifdef _XBOX + // Just in case, clear all model memory slots that might have been missed above... + ModelMem.ClearAll(); +#endif +} + + +// do not use ri.xxx functions in here, the renderer may not be running (ie. if on a dedicated server)... +// +static int giRegisterMedia_CurrentLevel=0; +void RE_RegisterMedia_LevelLoadBegin(const char *psMapName, ForceReload_e eForceReload) +{ + // for development purposes we may want to ditch certain media just before loading a map... + // + bool bDeleteModels = eForceReload == eForceReload_MODELS || eForceReload == eForceReload_ALL; +// bool bDeleteBSP = eForceReload == eForceReload_BSP || eForceReload == eForceReload_ALL; + + if (bDeleteModels) + { + RE_RegisterModels_DeleteAll(); + } + else + { + /* + if (sv_pure->integer) + { + RE_RegisterModels_DumpNonPure(); + } + */ + } + +/* + tr.numBSPModels = 0; +*/ + +#ifndef DEDICATED +// not used in MP codebase... +// +// if (bDeleteBSP) +// { +// CM_DeleteCachedMap(); +// R_Images_DeleteLightMaps(); // always do this now, makes no real load time difference, and lets designers work ok +// } +#endif + + // at some stage I'll probably want to put some special logic here, like not incrementing the level number + // when going into a map like "brig" or something, so returning to the previous level doesn't require an + // asset reload etc, but for now... + // + // only bump level number if we're not on the same level. + // Note that this will hide uncached models, which is perhaps a bad thing?... + // + static char sPrevMapName[MAX_QPATH]={0}; + if (Q_stricmp( psMapName,sPrevMapName )) + { + Q_strncpyz( sPrevMapName, psMapName, sizeof(sPrevMapName) ); + giRegisterMedia_CurrentLevel++; + } +} + +int RE_RegisterMedia_GetLevel(void) +{ + return giRegisterMedia_CurrentLevel; +} + +// this is now only called by the client, so should be ok to dump media... +// +extern qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel /* 99% qfalse */); +extern void S_RestartMusic(void); +void RE_RegisterMedia_LevelLoadEnd(void) +{ + RE_RegisterModels_LevelLoadEnd(qfalse); +#ifndef DEDICATED + RE_RegisterImages_LevelLoadEnd(); + SND_RegisterAudio_LevelLoadEnd(qfalse); +// RE_InitDissolve(); + S_RestartMusic(); +#endif +} + + + +/* +** R_GetModelByHandle +*/ +model_t *R_GetModelByHandle( qhandle_t index ) +{ + // invalid gets the defualt model + if( index < 1 || index >= MAX_MOD_KNOWN || !tr.models[index] ) + return tr.models[0]; + + return tr.models[index]; +} + +//=============================================================================== + +/* +** R_AllocModel +*/ +model_t *R_AllocModel( void ) { + model_t *mod; + + if ( tr.numModels == MAX_MOD_KNOWN ) { + return NULL; + } + + mod = (struct model_s *)Hunk_Alloc( sizeof( *tr.models[0] ), h_low ); + + int index = 0; + for( index = 0; index < MAX_MOD_KNOWN; ++index ) + if( !tr.models[index] ) + break; + + if( index == MAX_MOD_KNOWN ) + return NULL; + + mod->index = index; + tr.models[index] = mod; + tr.numModels++; + + return mod; +} + +/* +Ghoul2 Insert Start +*/ + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname, const int size ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower((unsigned char)fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (size-1); + return hash; +} + +void RE_InsertModelIntoHash(const char *name, model_t *mod) +{ + //int hash; + //modelHash_t *mh; + + //hash = generateHashValue(name, FILE_HASH_SIZE); + + //// insert this file into the hash table so we can look it up faster later + //mh = (modelHash_t*)Hunk_Alloc( sizeof( modelHash_t ), h_low ); + + //mh->next = mhHashTable[hash]; + //mh->handle = mod->index; + //strcpy(mh->name, name); + //mhHashTable[hash] = mh; + unsigned long crc = crc32(0, (const byte*)name, strlen(name)); + (*mhHashTable)[crc] = mod->index; +} + +#ifdef _XBOX +void RE_RemoveModelFromHash(const char *name) +{ + /*int hash; + modelHash_t *mh; + + hash = generateHashValue(name, FILE_HASH_SIZE); + + mh = mhHashTable[hash]; + + if(Q_stricmp(mh->name, name) == 0) { + Z_Free( mh ); + mhHashTable[hash] = NULL; + }*/ + + unsigned long crc = crc32(0, (const byte*)name, strlen(name)); + mhHashTable->erase(crc); + + // Yank this model out of the CachedModels list also + if(!CachedModels) { + return; + } + qboolean bEraseOccured = qfalse; + for (CachedModels_t::iterator itModel = CachedModels->begin(); itModel != CachedModels->end(); bEraseOccured?itModel:++itModel) + { + bEraseOccured = qfalse; + + LPCSTR psModelName = (*itModel).first.c_str(); + CachedEndianedModelBinary_t &CachedModel = (*itModel).second; + + if (stricmp(name, psModelName) == 0) + { + if (CachedModel.pModelDiskImage) { + if(CachedModel.ID != -1) { + int ID = CachedModel.ID; + + itModel = CachedModels->erase(itModel); + + if(tr.models[ID]) + Z_Free(tr.models[ID]); + tr.models[ID] = NULL; + tr.numModels--; + + bEraseOccured = qtrue; + break; + } + } + } + } +} +#endif +/* +Ghoul2 Insert End +*/ + +//rww - Please forgive me for all of the below. Feel free to destroy it and replace it with something better. +//You obviously can't touch anything relating to shaders or ri. functions here in case a dedicated +//server is running, which is the entire point of having these seperate functions. If anything major +//is changed in the non-server-only versions of these functions it would be wise to incorporate it +//here as well. + +/* +================= +ServerLoadMDXA - load a Ghoul 2 animation file +================= +*/ +qboolean ServerLoadMDXA( model_t *mod, void *buffer, const char *mod_name, qboolean &bAlreadyCached ) { + + mdxaHeader_t *pinmodel, *mdxa; + int version; + int size; + +#ifndef _M_IX86 + int j, k, i; + int frameSize; + mdxaFrame_t *cframe; + mdxaSkel_t *boneInfo; +#endif + + pinmodel = (mdxaHeader_t *)buffer; + // + // read some fields from the binary, but only LittleLong() them when we know this wasn't an already-cached model... + // + version = (pinmodel->version); + size = (pinmodel->ofsEnd); + + if (!bAlreadyCached) + { + version = LittleLong(version); + size = LittleLong(size); + } + + if (version != MDXA_VERSION) { + return qfalse; + } + + mod->type = MOD_MDXA; + mod->dataSize += size; + + qboolean bAlreadyFound = qfalse; + mdxa = mod->mdxa = (mdxaHeader_t*) //Hunk_Alloc( size ); + RE_RegisterServerModels_Malloc(size, buffer, mod_name, &bAlreadyFound, TAG_MODEL_GLA, mod->index); + + assert(bAlreadyCached == bAlreadyFound); // I should probably eliminate 'bAlreadyFound', but wtf? + + if (!bAlreadyFound) + { + // horrible new hackery, if !bAlreadyFound then we've just done a tag-morph, so we need to set the + // bool reference passed into this function to true, to tell the caller NOT to do an FS_Freefile since + // we've hijacked that memory block... + // + // Aaaargh. Kill me now... + // +#ifdef _XBOX // Can't re-tag allocated memory! + memcpy( mdxa, buffer, size ); // and don't do this now, since it's the same thing +#else + bAlreadyCached = qtrue; + assert( mdxa == buffer ); +#endif + + LL(mdxa->ident); + LL(mdxa->version); + LL(mdxa->numFrames); + LL(mdxa->numBones); + LL(mdxa->ofsFrames); + LL(mdxa->ofsEnd); + } + + if ( mdxa->numFrames < 1 ) { + return qfalse; + } + + if (bAlreadyFound) + { + return qtrue; // All done, stop here, do not LittleLong() etc. Do not pass go... + } + +#ifndef _M_IX86 + + // + // optimisation, we don't bother doing this for standard intel case since our data's already in that format... + // + + // swap all the skeletal info + boneInfo = (mdxaSkel_t *)( (byte *)mdxa + mdxa->ofsSkel); + for ( i = 0 ; i < mdxa->numBones ; i++) + { + LL(boneInfo->numChildren); + LL(boneInfo->parent); + for (k=0; knumChildren; k++) + { + LL(boneInfo->children[k]); + } + + // get next bone + boneInfo += (int)( &((mdxaSkel_t *)0)->children[ boneInfo->numChildren ] ); + } + + + // swap all the frames + frameSize = (int)( &((mdxaFrame_t *)0)->bones[ mdxa->numBones ] ); + for ( i = 0 ; i < mdxa->numFrames ; i++) + { + cframe = (mdxaFrame_t *) ( (byte *)mdxa + mdxa->ofsFrames + i * frameSize ); + cframe->radius = LittleFloat( cframe->radius ); + for ( j = 0 ; j < 3 ; j++ ) + { + cframe->bounds[0][j] = LittleFloat( cframe->bounds[0][j] ); + cframe->bounds[1][j] = LittleFloat( cframe->bounds[1][j] ); + cframe->localOrigin[j] = LittleFloat( cframe->localOrigin[j] ); + } + for ( j = 0 ; j < mdxa->numBones * sizeof( mdxaBone_t ) / 2 ; j++ ) + { + ((short *)cframe->bones)[j] = LittleShort( ((short *)cframe->bones)[j] ); + } + } +#endif + return qtrue; +} + +/* +================= +ServerLoadMDXM - load a Ghoul 2 Mesh file +================= +*/ +qboolean ServerLoadMDXM( model_t *mod, void *buffer, const char *mod_name, qboolean &bAlreadyCached ) { + int i,l, j; + mdxmHeader_t *pinmodel, *mdxm; + mdxmLOD_t *lod; + mdxmSurface_t *surf; + int version; + int size; + shader_t *sh; + mdxmSurfHierarchy_t *surfInfo; + +#ifndef _M_IX86 + int k; + int frameSize; + mdxmTag_t *tag; + mdxmTriangle_t *tri; + mdxmVertex_t *v; + mdxmFrame_t *cframe; + int *boneRef; +#endif + + pinmodel= (mdxmHeader_t *)buffer; + // + // read some fields from the binary, but only LittleLong() them when we know this wasn't an already-cached model... + // + version = (pinmodel->version); + size = (pinmodel->ofsEnd); + + if (!bAlreadyCached) + { + version = LittleLong(version); + size = LittleLong(size); + } + + if (version != MDXM_VERSION) { + return qfalse; + } + + mod->type = MOD_MDXM; + mod->dataSize += size; + + qboolean bAlreadyFound = qfalse; + mdxm = mod->mdxm = (mdxmHeader_t*) //Hunk_Alloc( size ); + RE_RegisterServerModels_Malloc(size, buffer, mod_name, &bAlreadyFound, TAG_MODEL_GLM, mod->index); + + assert(bAlreadyCached == bAlreadyFound); // I should probably eliminate 'bAlreadyFound', but wtf? + + if (!bAlreadyFound) + { + // horrible new hackery, if !bAlreadyFound then we've just done a tag-morph, so we need to set the + // bool reference passed into this function to true, to tell the caller NOT to do an FS_Freefile since + // we've hijacked that memory block... + // + // Aaaargh. Kill me now... + // +#ifdef _XBOX // Can't re-tag allocated memory! + memcpy( mdxm, buffer, size ); // and don't do this now, since it's the same thing +#else + bAlreadyCached = qtrue; + assert( mdxm == buffer ); +#endif + + LL(mdxm->ident); + LL(mdxm->version); + LL(mdxm->numLODs); + LL(mdxm->ofsLODs); + LL(mdxm->numSurfaces); + LL(mdxm->ofsSurfHierarchy); + LL(mdxm->ofsEnd); + } + + // first up, go load in the animation file we need that has the skeletal animation info for this model + mdxm->animIndex = RE_RegisterServerModel(va ("%s.gla",mdxm->animName)); + if (!mdxm->animIndex) + { + return qfalse; + } + + mod->numLods = mdxm->numLODs -1 ; //copy this up to the model for ease of use - it wil get inced after this. + + if (bAlreadyFound) + { + return qtrue; // All done. Stop, go no further, do not LittleLong(), do not pass Go... + } + + surfInfo = (mdxmSurfHierarchy_t *)( (byte *)mdxm + mdxm->ofsSurfHierarchy); + for ( i = 0 ; i < mdxm->numSurfaces ; i++) + { + LL(surfInfo->numChildren); + LL(surfInfo->parentIndex); + + // do all the children indexs + for (j=0; jnumChildren; j++) + { + LL(surfInfo->childIndexes[j]); + } + + // We will not be using shaders on the server. + sh = 0; + // insert it in the surface list + + surfInfo->shaderIndex = 0; + + RE_RegisterModels_StoreShaderRequest(mod_name, &surfInfo->shader[0], &surfInfo->shaderIndex); + + // find the next surface + surfInfo = (mdxmSurfHierarchy_t *)( (byte *)surfInfo + (int)( &((mdxmSurfHierarchy_t *)0)->childIndexes[ surfInfo->numChildren ] )); + } + + // swap all the LOD's (we need to do the middle part of this even for intel, because of shader reg and err-check) + lod = (mdxmLOD_t *) ( (byte *)mdxm + mdxm->ofsLODs ); + for ( l = 0 ; l < mdxm->numLODs ; l++) + { + int triCount = 0; + + LL(lod->ofsEnd); + // swap all the surfaces + surf = (mdxmSurface_t *) ( (byte *)lod + sizeof (mdxmLOD_t) + (mdxm->numSurfaces * sizeof(mdxmLODSurfOffset_t)) ); + for ( i = 0 ; i < mdxm->numSurfaces ; i++) + { + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsVerts); + LL(surf->ofsEnd); + LL(surf->ofsHeader); + LL(surf->numBoneReferences); + LL(surf->ofsBoneReferences); +// LL(surf->maxVertBoneWeights); + + triCount += surf->numTriangles; + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + return qfalse; + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + return qfalse; + } + + // change to surface identifier + surf->ident = SF_MDX; + + // register the shaders +#ifndef _M_IX86 +// +// optimisation, we don't bother doing this for standard intel case since our data's already in that format... +// + // FIXME - is this correct? + // do all the bone reference data + boneRef = (int *) ( (byte *)surf + surf->ofsBoneReferences ); + for ( j = 0 ; j < surf->numBoneReferences ; j++ ) + { + LL(boneRef[j]); + } + + + // swap all the triangles + tri = (mdxmTriangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) + { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the vertexes + v = (mdxmVertex_t *) ( (byte *)surf + surf->ofsVerts ); + for ( j = 0 ; j < surf->numVerts ; j++ ) + { + v->normal[0] = LittleFloat( v->normal[0] ); + v->normal[1] = LittleFloat( v->normal[1] ); + v->normal[2] = LittleFloat( v->normal[2] ); + + v->texCoords[0] = LittleFloat( v->texCoords[0] ); + v->texCoords[1] = LittleFloat( v->texCoords[1] ); + + v->numWeights = LittleLong( v->numWeights ); + v->offset[0] = LittleFloat( v->offset[0] ); + v->offset[1] = LittleFloat( v->offset[1] ); + v->offset[2] = LittleFloat( v->offset[2] ); + + for ( k = 0 ; k < /*v->numWeights*/surf->maxVertBoneWeights ; k++ ) + { + v->weights[k].boneIndex = LittleLong( v->weights[k].boneIndex ); + v->weights[k].boneWeight = LittleFloat( v->weights[k].boneWeight ); + } + v = (mdxmVertex_t *)&v->weights[/*v->numWeights*/surf->maxVertBoneWeights]; + } +#endif + + // find the next surface + surf = (mdxmSurface_t *)( (byte *)surf + surf->ofsEnd ); + } + + // find the next LOD + lod = (mdxmLOD_t *)( (byte *)lod + lod->ofsEnd ); + } + + return qtrue; +} + +/* +==================== +RE_RegisterServerModel + +Same as RE_RegisterModel, except used by the server to handle ghoul2 instance models. +==================== +*/ +qhandle_t RE_RegisterServerModel( const char *name ) { + model_t *mod; + unsigned *buf; + int lod; + int ident; + qboolean loaded; +// qhandle_t hModel; + int numLoaded; +/* +Ghoul2 Insert Start +*/ + /*int hash; + modelHash_t *mh;*/ +/* +Ghoul2 Insert End +*/ + + if (!r_noServerGhoul2) + { //keep it from choking when it gets to these checks in the g2 code. Registering all r_ cvars for the server would be a Bad Thing though. + r_noServerGhoul2 = Cvar_Get( "r_noserverghoul2", "0", 0); + } + + if ( !name || !name[0] ) { + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + return 0; + } + + //hash = generateHashValue(name, FILE_HASH_SIZE); + + //// + //// see if the model is already loaded + //// + //for (mh=mhHashTable[hash]; mh; mh=mh->next) { + // if (Q_stricmp(mh->name, name) == 0) { + // return mh->handle; + // } + //} + unsigned long crc = crc32(0, (const byte*)name, strlen(name)); + HashTableIterator iter = mhHashTable->find(crc); + if (iter != mhHashTable->end()) + return (*iter).second; + + if ( ( mod = R_AllocModel() ) == NULL ) { + return 0; + } + + // only set the name after the model has been successfully loaded + Q_strncpyz( mod->name, name, sizeof( mod->name ) ); + +#ifndef DEDICATED + // make sure the render thread is stopped + R_SyncRenderThread(); +#endif + + int iLODStart = 0; + if (strstr (name, ".md3")) { + iLODStart = MD3_MAX_LODS-1; // this loads the md3s in reverse so they can be biased + } + mod->numLods = 0; + + // + // load the files + // + numLoaded = 0; + + for ( lod = iLODStart; lod >= 0 ; lod-- ) { + char filename[1024]; + + strcpy( filename, name ); + + if ( lod != 0 ) { + char namebuf[80]; + + if ( strrchr( filename, '.' ) ) { + *strrchr( filename, '.' ) = 0; + } + sprintf( namebuf, "_%d.md3", lod ); + strcat( filename, namebuf ); + } + + qboolean bAlreadyCached = qfalse; + if (!RE_RegisterModels_GetDiskFile(filename, (void **)&buf, &bAlreadyCached)) + { + continue; + } + + //loadmodel = mod; // this seems to be fairly pointless + + // important that from now on we pass 'filename' instead of 'name' to all model load functions, + // because 'filename' accounts for any LOD mangling etc so guarantees unique lookups for yet more + // internal caching... + // + ident = *(unsigned *)buf; + if (!bAlreadyCached) + { + ident = LittleLong(ident); + } + + switch (ident) + { //if you're trying to register anything else as a model type on the server, you are out of luck + + case MDXA_IDENT: + loaded = ServerLoadMDXA( mod, buf, filename, bAlreadyCached ); + break; + case MDXM_IDENT: + loaded = ServerLoadMDXM( mod, buf, filename, bAlreadyCached ); + break; + default: + goto fail; + } + + if (!bAlreadyCached){ // important to check!! + FS_FreeFile (buf); + } + + if ( !loaded ) { + if ( lod == 0 ) { + goto fail; + } else { + break; + } + } else { + mod->numLods++; + numLoaded++; + } + } + + if ( numLoaded ) { + // duplicate into higher lod spots that weren't + // loaded, in case the user changes r_lodbias on the fly + for ( lod-- ; lod >= 0 ; lod-- ) { + mod->numLods++; + mod->md3[lod] = mod->md3[lod+1]; + } + +/* +Ghoul2 Insert Start +*/ + + RE_InsertModelIntoHash(name, mod); + return mod->index; +/* +Ghoul2 Insert End +*/ + } + +fail: + // we still keep the model_t around, so if the model name is asked for + // again, we won't bother scanning the filesystem + mod->type = MOD_BAD; + RE_InsertModelIntoHash(name, mod); + return 0; +} + +extern void FS_CancelLargeRead( void ); + +/* +==================== +RE_RegisterModel + +Loads in a model for the given name + +Zero will be returned if the model fails to load. +An entry will be retained for failed models as an +optimization to prevent disk rescanning if they are +asked for again. +==================== +*/ +static qhandle_t RE_RegisterModel_Actual( const char *name ) { + model_t *mod; + unsigned *buf; + int lod; + int ident; + qboolean loaded; +// qhandle_t hModel; + int numLoaded; +/* +Ghoul2 Insert Start +*/ + /*int hash; + modelHash_t *mh;*/ +/* +Ghoul2 Insert End +*/ + + if ( !name || !name[0] ) { + Com_Printf ("RE_RegisterModel: NULL name\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_DPrintf (S_COLOR_RED "Model name exceeds MAX_QPATH\n" ); + return 0; + } + +/* +Ghoul2 Insert Start +*/ +// if (!tr.registered) { +// Com_Printf (S_COLOR_YELLOW "RE_RegisterModel (%s) called before ready!\n",name ); +// return 0; +// } + // + // search the currently loaded models + // + //hash = generateHashValue(name, FILE_HASH_SIZE); + + // + // see if the model is already loaded + // + /*for (mh=mhHashTable[hash]; mh; mh=mh->next) { + if (Q_stricmp(mh->name, name) == 0) { + return mh->handle; + } + }*/ + unsigned long crc = crc32(0, (const byte*)name, strlen(name)); + HashTableIterator iter = mhHashTable->find(crc); + if (iter != mhHashTable->end()) + return (*iter).second; + +// for ( hModel = 1 ; hModel < tr.numModels; hModel++ ) { +// mod = tr.models[hModel]; +// if ( !strcmp( mod->name, name ) ) { +// if( mod->type == MOD_BAD ) { +// return 0; +// } +// return hModel; +// } +// } + +/* + if (name[0] == '#') + { + char temp[MAX_QPATH]; + + tr.numBSPModels++; +#ifndef DEDICATED + RE_LoadWorldMap_Actual(va("maps/%s.bsp", name + 1), tr.bspModels[tr.numBSPModels - 1], tr.numBSPModels); +#endif + Com_sprintf(temp, MAX_QPATH, "*%d-0", tr.numBSPModels); + hash = generateHashValue(temp, FILE_HASH_SIZE); + for (mh=mhHashTable[hash]; mh; mh=mh->next) + { + if (Q_stricmp(mh->name, temp) == 0) + { + return mh->handle; + } + } + + return 0; + } +*/ + + if (name[0] == '*') + { // don't create a bad model for a bsp model + if (Q_stricmp(name, "*default.gla")) + { + return 0; + } + } + +/* +Ghoul2 Insert End +*/ + + // allocate a new model_t + + if ( ( mod = R_AllocModel() ) == NULL ) { + Com_Printf (S_COLOR_YELLOW "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name); + return 0; + } + + // only set the name after the model has been successfully loaded + Q_strncpyz( mod->name, name, sizeof( mod->name ) ); + +#ifndef DEDICATED + // make sure the render thread is stopped + R_SyncRenderThread(); +#endif + + int iLODStart = 0; + if (strstr (name, ".md3")) { + iLODStart = MD3_MAX_LODS-1; // this loads the md3s in reverse so they can be biased + } + mod->numLods = 0; + + // + // load the files + // + numLoaded = 0; + + for ( lod = iLODStart; lod >= 0 ; lod-- ) { + char filename[1024]; + + strcpy( filename, name ); + + if ( lod != 0 ) { + char namebuf[80]; + + if ( strrchr( filename, '.' ) ) { + *strrchr( filename, '.' ) = 0; + } + sprintf( namebuf, "_%d.md3", lod ); + strcat( filename, namebuf ); + } + + // Warn the filesystem that a big GLM is about to be read: + extern void FS_LargeRead( void ); + if( strstr(filename, "players") && strstr(filename, "glm") ) + FS_LargeRead(); + else + FS_CancelLargeRead(); // Because of recursive calls GLM->GLA + + qboolean bAlreadyCached = qfalse; + if (!RE_RegisterModels_GetDiskFile(filename, (void **)&buf, &bAlreadyCached)) + { + continue; + } + + //loadmodel = mod; // this seems to be fairly pointless + + // important that from now on we pass 'filename' instead of 'name' to all model load functions, + // because 'filename' accounts for any LOD mangling etc so guarantees unique lookups for yet more + // internal caching... + // + ident = *(unsigned *)buf; + if (!bAlreadyCached) + { + ident = LittleLong(ident); + } + + switch (ident) + { + // if you add any new types of model load in this switch-case, tell me, + // or copy what I've done with the cache scheme (-ste). + // + case MDXA_IDENT: + loaded = R_LoadMDXA( mod, buf, filename, bAlreadyCached ); + break; + + case MDXM_IDENT: + loaded = R_LoadMDXM( mod, buf, filename, bAlreadyCached ); + break; + + case MD3_IDENT: + loaded = R_LoadMD3( mod, lod, buf, filename, bAlreadyCached ); + break; + + default: + Com_Printf (S_COLOR_YELLOW"RE_RegisterModel: unknown fileid for %s\n", filename); + goto fail; + } + + if (!bAlreadyCached){ // important to check!! + FS_FreeFile (buf); + } + + if ( !loaded ) { + if ( lod == 0 ) { + goto fail; + } else { + break; + } + } else { + mod->numLods++; + numLoaded++; + // if we have a valid model and are biased + // so that we won't see any higher detail ones, + // stop loading them + if ( lod <= r_lodbias->integer ) { + break; + } + } + } + + // Stop using the GLA space for GLM reads + FS_CancelLargeRead(); + + if ( numLoaded ) { + // duplicate into higher lod spots that weren't + // loaded, in case the user changes r_lodbias on the fly + for ( lod-- ; lod >= 0 ; lod-- ) { + mod->numLods++; + mod->md3[lod] = mod->md3[lod+1]; + } + +/* +Ghoul2 Insert Start +*/ + +#ifdef _DEBUG + if (r_noPrecacheGLA && r_noPrecacheGLA->integer && ident == MDXA_IDENT) + { //I expect this will cause leaks, but I don't care because it's a debugging utility. + return mod->index; + } +#endif + + RE_InsertModelIntoHash(name, mod); + return mod->index; +/* +Ghoul2 Insert End +*/ + } +#ifdef _DEBUG + else { + Com_Printf (S_COLOR_YELLOW"RE_RegisterModel: couldn't load %s\n", name); + } +#endif + +fail: + // we still keep the model_t around, so if the model name is asked for + // again, we won't bother scanning the filesystem + mod->type = MOD_BAD; + RE_InsertModelIntoHash(name, mod); + return 0; +} + + +// wrapper function needed to avoid problems with mid-function returns so I can safely use this bool to tell the +// z_malloc-fail recovery code whether it's safe to ditch any model caches... +// +qboolean gbInsideRegisterModel = qfalse; +qhandle_t RE_RegisterModel( const char *name ) +{ + const qboolean bWhatitwas = gbInsideRegisterModel; + gbInsideRegisterModel = qtrue; // !!!!!!!!!!!!!! + + qhandle_t q = RE_RegisterModel_Actual( name ); + + gbInsideRegisterModel = bWhatitwas; + + return q; +} + + + + +/* +================= +R_LoadMD3 +================= +*/ +static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_name, qboolean &bAlreadyCached ) { + int i, j; + md3Header_t *pinmodel; + md3Surface_t *surf; + int version; + int size; + +#ifndef _M_IX86 + md3Frame_t *frame; + md3Triangle_t *tri; + md3St_t *st; + md3XyzNormal_t *xyz; + md3Tag_t *tag; +#endif + + + pinmodel= (md3Header_t *)buffer; + // + // read some fields from the binary, but only LittleLong() them when we know this wasn't an already-cached model... + // + version = pinmodel->version; + size = pinmodel->ofsEnd; + + if (!bAlreadyCached) + { + version = LittleLong(version); + size = LittleLong(size); + } + + if (version != MD3_VERSION) { + Com_Printf (S_COLOR_YELLOW "R_LoadMD3: %s has wrong version (%i should be %i)\n", + mod_name, version, MD3_VERSION); + return qfalse; + } + + mod->type = MOD_MESH; + mod->dataSize += size; + + qboolean bAlreadyFound = qfalse; +#ifdef _XBOX + mod->md3[lod] = (md3Header_t *) //Hunk_Alloc( size ); + RE_RegisterModels_Malloc(size, buffer, mod_name, &bAlreadyFound, TAG_MODEL_MD3, 0, false); +#else + mod->md3[lod] = (md3Header_t *) //Hunk_Alloc( size ); + RE_RegisterModels_Malloc(size, buffer, mod_name, &bAlreadyFound, TAG_MODEL_MD3); +#endif + + assert(bAlreadyCached == bAlreadyFound); // I should probably eliminate 'bAlreadyFound', but wtf? + + if (!bAlreadyFound) + { + // horrible new hackery, if !bAlreadyFound then we've just done a tag-morph, so we need to set the + // bool reference passed into this function to true, to tell the caller NOT to do an FS_Freefile since + // we've hijacked that memory block... + // + // Aaaargh. Kill me now... + // +#ifdef _XBOX // Can't re-tag allocated memory! + memcpy( mod->md3[lod], buffer, size ); // and don't do this now, since it's the same thing +#else + bAlreadyCached = qtrue; + assert( mod->md3[lod] == buffer ); +#endif + + LL(mod->md3[lod]->ident); + LL(mod->md3[lod]->version); + LL(mod->md3[lod]->numFrames); + LL(mod->md3[lod]->numTags); + LL(mod->md3[lod]->numSurfaces); + LL(mod->md3[lod]->ofsFrames); + LL(mod->md3[lod]->ofsTags); + LL(mod->md3[lod]->ofsSurfaces); + LL(mod->md3[lod]->ofsEnd); + } + + if ( mod->md3[lod]->numFrames < 1 ) { + Com_Printf (S_COLOR_YELLOW "R_LoadMD3: %s has no frames\n", mod_name ); + return qfalse; + } + + if (bAlreadyFound) + { + return qtrue; // All done. Stop, go no further, do not pass Go... + } + +#ifndef _M_IX86 + // + // optimisation, we don't bother doing this for standard intel case since our data's already in that format... + // + + // swap all the frames + frame = (md3Frame_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames ); + for ( i = 0 ; i < mod->md3[lod]->numFrames ; i++, frame++) { + frame->radius = LittleFloat( frame->radius ); + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } + + // swap all the tags + tag = (md3Tag_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsTags ); + for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->numFrames ; i++, tag++) { + for ( j = 0 ; j < 3 ; j++ ) { + tag->origin[j] = LittleFloat( tag->origin[j] ); + tag->axis[0][j] = LittleFloat( tag->axis[0][j] ); + tag->axis[1][j] = LittleFloat( tag->axis[1][j] ); + tag->axis[2][j] = LittleFloat( tag->axis[2][j] ); + } + } +#endif + + // swap all the surfaces + surf = (md3Surface_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces ); + for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++) { + LL(surf->flags); + LL(surf->numFrames); + LL(surf->numShaders); + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsShaders); + LL(surf->ofsSt); + LL(surf->ofsXyzNormals); + LL(surf->ofsEnd); + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + Com_Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + Com_Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = SF_MD3; + + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen( surf->name ); + if ( j > 2 && surf->name[j-2] == '_' ) { + surf->name[j-2] = 0; + } +#ifndef DEDICATED + // register the shaders + md3Shader_t *shader; + shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + shader_t *sh; + + sh = R_FindShader( shader->name, lightmapsNone, stylesDefault, qtrue ); + if ( sh->defaultShader ) { + shader->shaderIndex = 0; + } else { + shader->shaderIndex = sh->index; + } + RE_RegisterModels_StoreShaderRequest(mod_name, &shader->name[0], &shader->shaderIndex); + } +#endif + +#ifndef _M_IX86 +// +// optimisation, we don't bother doing this for standard intel case since our data's already in that format... +// + + // swap all the triangles + tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the ST + st = (md3St_t *) ( (byte *)surf + surf->ofsSt ); + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + st->st[0] = LittleFloat( st->st[0] ); + st->st[1] = LittleFloat( st->st[1] ); + } + + // swap all the XyzNormals + xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals ); + for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ ) + { + xyz->xyz[0] = LittleShort( xyz->xyz[0] ); + xyz->xyz[1] = LittleShort( xyz->xyz[1] ); + xyz->xyz[2] = LittleShort( xyz->xyz[2] ); + + xyz->normal = LittleShort( xyz->normal ); + } +#endif + + // find the next surface + surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd ); + } + + return qtrue; +} + + +//============================================================================= +#ifndef DEDICATED +/* +** RE_BeginRegistration +*/ +void RE_BeginRegistration( glconfig_t *glconfigOut ) { + + R_Init(); + + *glconfigOut = glConfig; + + R_SyncRenderThread(); + + tr.viewCluster = -1; // force markleafs to regenerate + + // rww - 9-13-01 [1-26-01-sof2] + //R_ClearFlares(); + + RE_ClearScene(); + + tr.registered = qtrue; + + // NOTE: this sucks, for some reason the first stretch pic is never drawn + // without this we'd see a white flash on a level load because the very + // first time the level shot would not be drawn + RE_StretchPic(0, 0, 0, 0, 0, 0, 1, 1, 0); +} + +//============================================================================= + +#endif // !DEDICATED +void R_SVModelInit() +{ + R_ModelInit(); +} + +/* +=============== +R_ModelInit +=============== +*/ +void R_ModelInit( void ) +{ + model_t *mod; + + if(!CachedModels) + { + CachedModels = new CachedModels_t; + } + + // leave a space for NULL model + tr.numModels = 0; + // Need to clear this now, because they're not in consecutive slots: + memset( tr.models, 0, sizeof(tr.models) ); + +// memset(mhHashTable, 0, sizeof(mhHashTable)); + if(mhHashTable) + { + mhHashTable->clear(); + delete mhHashTable; + } + + mhHashTable = new HashTable; + + mod = R_AllocModel(); + mod->type = MOD_BAD; +} + +extern void KillTheShaderHashTable(void); +void R_HunkClearCrap(void) +{ //get your dirty sticky assets off me, you damn dirty hunk! + KillTheShaderHashTable(); + tr.numModels = 0; + memset(tr.models, 0, sizeof(tr.models)); +// memset(mhHashTable, 0, sizeof(mhHashTable)); + if(mhHashTable) + { + mhHashTable->clear(); + } + tr.numShaders = 0; + tr.numSkins = 0; +} + +void R_ModelFree(void) +{ + if(CachedModels) { + RE_RegisterModels_DeleteAll(); + delete CachedModels; + CachedModels = NULL; + } + + if(mhHashTable) + { + mhHashTable->clear(); + delete mhHashTable; + mhHashTable = NULL; + } +} + + + +/* +================ +R_Modellist_f +================ +*/ +void R_Modellist_f( void ) { + int i, j; + model_t *mod; + int total; + int lods; + + total = 0; + for ( i = 1 ; i < MAX_MOD_KNOWN; i++ ) { + if( !tr.models[i] ) + continue; + mod = tr.models[i]; + lods = 1; + for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) { + if ( mod->md3[j] && mod->md3[j] != mod->md3[j-1] ) { + lods++; + } + } + Com_Printf ("%8i : (%i) %s\n",mod->dataSize, lods, mod->name ); + total += mod->dataSize; + } + Com_Printf ("%8i : Total models\n", total ); + +#if 0 // not working right with new hunk + if ( tr.world ) { + Com_Printf ("\n%8i : %s\n", tr.world->dataSize, tr.world->name ); + } +#endif +} + + +//============================================================================= + + +/* +================ +R_GetTag +================ +*/ +static md3Tag_t *R_GetTag( md3Header_t *mod, int frame, const char *tagName ) { + md3Tag_t *tag; + int i; + + if ( frame >= mod->numFrames ) { + // it is possible to have a bad frame while changing models, so don't error + frame = mod->numFrames - 1; + } + + tag = (md3Tag_t *)((byte *)mod + mod->ofsTags) + frame * mod->numTags; + for ( i = 0 ; i < mod->numTags ; i++, tag++ ) { + if ( !strcmp( tag->name, tagName ) ) { + return tag; // found it + } + } + + return NULL; +} + +/* +================ +R_LerpTag +================ +*/ +int R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ) { + md3Tag_t *start, *end; + int i; + float frontLerp, backLerp; + model_t *model; + + model = R_GetModelByHandle( handle ); + if ( !model->md3[0] ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return qfalse; + } + + start = R_GetTag( model->md3[0], startFrame, tagName ); + end = R_GetTag( model->md3[0], endFrame, tagName ); + if ( !start || !end ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return qfalse; + } + + frontLerp = frac; + backLerp = 1.0f - frac; + + for ( i = 0 ; i < 3 ; i++ ) { + tag->origin[i] = start->origin[i] * backLerp + end->origin[i] * frontLerp; + tag->axis[0][i] = start->axis[0][i] * backLerp + end->axis[0][i] * frontLerp; + tag->axis[1][i] = start->axis[1][i] * backLerp + end->axis[1][i] * frontLerp; + tag->axis[2][i] = start->axis[2][i] * backLerp + end->axis[2][i] * frontLerp; + } + VectorNormalize( tag->axis[0] ); + VectorNormalize( tag->axis[1] ); + VectorNormalize( tag->axis[2] ); + return qtrue; +} + + +/* +==================== +R_ModelBounds +==================== +*/ +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) { + model_t *model; + md3Header_t *header; + md3Frame_t *frame; + + model = R_GetModelByHandle( handle ); + + if ( model->bmodel ) { + VectorCopy( model->bmodel->bounds[0], mins ); + VectorCopy( model->bmodel->bounds[1], maxs ); + return; + } + + if ( !model->md3[0] ) { + VectorClear( mins ); + VectorClear( maxs ); + return; + } + + header = model->md3[0]; + + frame = (md3Frame_t *)( (byte *)header + header->ofsFrames ); + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); +} + + diff --git a/codemp/renderer/tr_noise.cpp b/codemp/renderer/tr_noise.cpp new file mode 100644 index 0000000..8b2cb55 --- /dev/null +++ b/codemp/renderer/tr_noise.cpp @@ -0,0 +1,84 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_noise.c +#include "tr_local.h" + +#define NOISE_SIZE 256 +#define NOISE_MASK ( NOISE_SIZE - 1 ) + +#define VAL( a ) s_noise_perm[ ( a ) & ( NOISE_MASK )] +#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) ) + +static float s_noise_table[NOISE_SIZE]; +static int s_noise_perm[NOISE_SIZE]; + +#define LERP( a, b, w ) ( a * ( 1.0f - w ) + b * w ) + +static float GetNoiseValue( int x, int y, int z, int t ) +{ + int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t ); + + return s_noise_table[index]; +} + +float GetNoiseTime( int t ) +{ + int index = VAL( t ); + + return (1 + s_noise_table[index]); +} + +void R_NoiseInit( void ) +{ + int i; + + srand( 1001 ); + + for ( i = 0; i < NOISE_SIZE; i++ ) + { + s_noise_table[i] = ( float ) ( ( ( rand() / ( float ) RAND_MAX ) * 2.0 - 1.0 ) ); + s_noise_perm[i] = ( unsigned char ) ( rand() / ( float ) RAND_MAX * 255 ); + } +} + +float R_NoiseGet4f( float x, float y, float z, float t ) +{ + int i; + int ix, iy, iz, it; + float fx, fy, fz, ft; + float front[4]; + float back[4]; + float fvalue, bvalue, value[2], finalvalue; + + ix = ( int ) floor( x ); + fx = x - ix; + iy = ( int ) floor( y ); + fy = y - iy; + iz = ( int ) floor( z ); + fz = z - iz; + it = ( int ) floor( t ); + ft = t - it; + + for ( i = 0; i < 2; i++ ) + { + front[0] = GetNoiseValue( ix, iy, iz, it + i ); + front[1] = GetNoiseValue( ix+1, iy, iz, it + i ); + front[2] = GetNoiseValue( ix, iy+1, iz, it + i ); + front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i ); + + back[0] = GetNoiseValue( ix, iy, iz + 1, it + i ); + back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i ); + back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i ); + back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i ); + + fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy ); + bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy ); + + value[i] = LERP( fvalue, bvalue, fz ); + } + + finalvalue = LERP( value[0], value[1], ft ); + + return finalvalue; +} diff --git a/codemp/renderer/tr_public.h b/codemp/renderer/tr_public.h new file mode 100644 index 0000000..7546228 --- /dev/null +++ b/codemp/renderer/tr_public.h @@ -0,0 +1,116 @@ +#ifndef __TR_PUBLIC_H +#define __TR_PUBLIC_H + +#include "../cgame/tr_types.h" + +#define REF_API_VERSION 8 + +// +// these are the functions exported by the refresh module +// +#ifdef _XBOX +template class SPARC; +#endif +typedef struct { + // called before the library is unloaded + // if the system is just reconfiguring, pass destroyWindow = qfalse, + // which will keep the screen from flashing to the desktop. + void (*Shutdown)( qboolean destroyWindow ); + + // All data that will be used in a level should be + // registered before rendering any frames to prevent disk hits, + // but they can still be registered at a later time + // if necessary. + // + // BeginRegistration makes any existing media pointers invalid + // and returns the current gl configuration, including screen width + // and height, which can be used by the client to intelligently + // size display elements + void (*BeginRegistration)( glconfig_t *config ); + qhandle_t (*RegisterModel)( const char *name ); + qhandle_t (*RegisterSkin)( const char *name ); + qhandle_t (*RegisterShader)( const char *name ); + qhandle_t (*RegisterShaderNoMip)( const char *name ); + const char *(*ShaderNameFromIndex)( int index ); + void (*LoadWorld)( const char *name ); + + // the vis data is a large enough block of data that we go to the trouble + // of sharing it with the clipmodel subsystem +#ifdef _XBOX + void (*SetWorldVisData)( SPARC *vis ); +#else + void (*SetWorldVisData)( const byte *vis ); +#endif + + // EndRegistration will draw a tiny polygon with each texture, forcing + // them to be loaded into card memory + void (*EndRegistration)( void ); + + // a scene is built up by calls to R_ClearScene and the various R_Add functions. + // Nothing is drawn until R_RenderScene is called. + void (*ClearScene)( void ); + void (*ClearDecals) ( void ); + void (*AddRefEntityToScene)( const refEntity_t *re ); + void (*AddMiniRefEntityToScene)( const miniRefEntity_t *re ); + void (*AddPolyToScene)( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ); + void (*AddDecalToScene)(qhandle_t shader, const vec3_t origin, const vec3_t dir, float orientation, float r, float g, float b, float a, qboolean alphaFade, float radius, qboolean temporary ); + int (*LightForPoint)( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); + void (*AddLightToScene)( const vec3_t org, float intensity, float r, float g, float b ); + void (*AddAdditiveLightToScene)( const vec3_t org, float intensity, float r, float g, float b ); + void (*RenderScene)( const refdef_t *fd ); + + void (*SetColor)( const float *rgba ); // NULL = 1,1,1,1 + void (*DrawStretchPic) ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); // 0 = white + void (*DrawRotatePic) ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, float a1, qhandle_t hShader ); // 0 = white + void (*DrawRotatePic2) ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, float a1, qhandle_t hShader ); // 0 = white + + // Draw images for cinematic rendering, pass as 32 bit rgba + void (*DrawStretchRaw) (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty); + void (*UploadCinematic) (int cols, int rows, const byte *data, int client, qboolean dirty); + + void (*BeginFrame)( stereoFrame_t stereoFrame ); + + // if the pointers are not NULL, timing info will be returned + void (*EndFrame)( int *frontEndMsec, int *backEndMsec ); + + + int (*MarkFragments)( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + int (*LerpTag)( orientation_t *tag, qhandle_t model, int startFrame, int endFrame, + float frac, const char *tagName ); + void (*ModelBounds)( qhandle_t model, vec3_t mins, vec3_t maxs ); + +#ifdef __USEA3D + void (*A3D_RenderGeometry) (void *pVoidA3D, void *pVoidGeom, void *pVoidMat, void *pVoidGeomStatus); +#endif + + qhandle_t (*RegisterFont)( const char *fontName ); + int (*Font_StrLenPixels) (const char *text, const int iFontIndex, const float scale); + int (*Font_StrLenChars) (const char *text); + int (*Font_HeightPixels)(const int iFontIndex, const float scale); + void (*Font_DrawString)(int ox, int oy, const char *text, const float *rgba, const int setIndex, int iCharLimit, const float scale); + qboolean (*Language_IsAsian)(void); + qboolean (*Language_UsesSpaces)(void); + unsigned int (*AnyLanguage_ReadCharFromString)( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation/* = NULL*/ ); + + void (*RemapShader)(const char *oldShader, const char *newShader, const char *offsetTime); + qboolean (*GetEntityToken)( char *buffer, int size ); + qboolean (*inPVS)( const vec3_t p1, const vec3_t p2, byte *mask ); + + void (*GetLightStyle)(int style, color4ub_t color); + void (*SetLightStyle)(int style, int color); + + void (*GetBModelVerts)( int bmodelIndex, vec3_t *vec, vec3_t normal ); +} refexport_t; + + +// this is the only function actually exported at the linker level +// If the module can't init to a valid rendering state, NULL will be +// returned. +refexport_t*GetRefAPI( int apiVersion ); + +#endif // __TR_PUBLIC_H diff --git a/codemp/renderer/tr_quicksprite.cpp b/codemp/renderer/tr_quicksprite.cpp new file mode 100644 index 0000000..1ab7d41 --- /dev/null +++ b/codemp/renderer/tr_quicksprite.cpp @@ -0,0 +1,222 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_QuickSprite.cpp: implementation of the CQuickSpriteSystem class. +// +////////////////////////////////////////////////////////////////////// +//#include "../server/exe_headers.h" +#include "tr_local.h" + +#include "tr_QuickSprite.h" + +void R_BindAnimatedImage( textureBundle_t *bundle ); + + +////////////////////////////////////////////////////////////////////// +// Singleton System +////////////////////////////////////////////////////////////////////// +CQuickSpriteSystem SQuickSprite; + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CQuickSpriteSystem::CQuickSpriteSystem() +{ + int i; + + for (i=0; iinteger == 2 && + mFogIndex == tr.world->globalFog) + { //enable hardware fog when we draw this thing if applicable -rww + fog_t *fog = tr.world->fogs + mFogIndex; + +#ifdef _XBOX + qglFogi(GL_FOG_MODE, GL_EXP2); +#else + qglFogf(GL_FOG_MODE, GL_EXP2); +#endif + qglFogf(GL_FOG_DENSITY, logtestExp2 / fog->parms.depthForOpaque); + qglFogfv(GL_FOG_COLOR, fog->parms.color); + qglEnable(GL_FOG); + } + */ + //this should not be needed, since I just wait to disable fog for the surface til after surface sprites are done + + // + // render the main pass + // + R_BindAnimatedImage( mTexBundle ); + GL_State(mGLStateBits); + + // + // set arrays and lock + // + qglTexCoordPointer( 2, GL_FLOAT, 0, mTextureCoords ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + + qglEnableClientState( GL_COLOR_ARRAY); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, mColors ); + + qglVertexPointer (3, GL_FLOAT, 16, mVerts); + + if ( qglLockArraysEXT ) + { + qglLockArraysEXT(0, mNextVert); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + qglDrawArrays(GL_QUADS, 0, mNextVert); + + backEnd.pc.c_vertexes += mNextVert; + backEnd.pc.c_indexes += mNextVert; + backEnd.pc.c_totalIndexes += mNextVert; + + //only for software fog pass (global soft/volumetric) -rww + if (mUseFog && (r_drawfog->integer != 2 || mFogIndex != tr.world->globalFog)) + { + fog_t *fog = tr.world->fogs + mFogIndex; + + // + // render the fog pass + // + GL_Bind( tr.fogImage ); + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + + // + // set arrays and lock + // + qglTexCoordPointer( 2, GL_FLOAT, 0, mFogTextureCoords); +// qglEnableClientState( GL_TEXTURE_COORD_ARRAY); // Done above + + qglDisableClientState( GL_COLOR_ARRAY ); + qglColor4ubv((GLubyte *)&fog->colorInt); + +// qglVertexPointer (3, GL_FLOAT, 16, mVerts); // Done above + + qglDrawArrays(GL_QUADS, 0, mNextVert); + + // Second pass from fog + backEnd.pc.c_totalIndexes += mNextVert; + } + + // + // unlock arrays + // + if (qglUnlockArraysEXT) + { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + + mNextVert=0; +} + + +void CQuickSpriteSystem::StartGroup(textureBundle_t *bundle, unsigned long glbits, int fogIndex ) +{ + mNextVert = 0; + + mTexBundle = bundle; + mGLStateBits = glbits; + if (fogIndex != -1) + { + mUseFog = qtrue; + mFogIndex = fogIndex; + } + else + { + mUseFog = qfalse; + } + + qglDisable(GL_CULL_FACE); +} + + +void CQuickSpriteSystem::EndGroup(void) +{ + Flush(); + + qglColor4ub(255,255,255,255); + qglEnable(GL_CULL_FACE); +} + + + + +void CQuickSpriteSystem::Add(float *pointdata, color4ub_t color, vec2_t fog) +{ + float *curcoord; + float *curfogtexcoord; + unsigned long *curcolor; + + if (mNextVert>SHADER_MAX_VERTEXES-4) + { + Flush(); + } + + curcoord = mVerts[mNextVert]; + memcpy(curcoord, pointdata, 4*sizeof(vec4_t)); + + // Set up color + curcolor = &mColors[mNextVert]; + *curcolor++ = *(unsigned long *)color; + *curcolor++ = *(unsigned long *)color; + *curcolor++ = *(unsigned long *)color; + *curcolor++ = *(unsigned long *)color; + + if (fog) + { + curfogtexcoord = &mFogTextureCoords[mNextVert][0]; + *curfogtexcoord++ = fog[0]; + *curfogtexcoord++ = fog[1]; + + *curfogtexcoord++ = fog[0]; + *curfogtexcoord++ = fog[1]; + + *curfogtexcoord++ = fog[0]; + *curfogtexcoord++ = fog[1]; + + *curfogtexcoord++ = fog[0]; + *curfogtexcoord++ = fog[1]; + + mUseFog=qtrue; + } + else + { + mUseFog=qfalse; + } + + mNextVert+=4; +} diff --git a/codemp/renderer/tr_quicksprite.h b/codemp/renderer/tr_quicksprite.h new file mode 100644 index 0000000..a1ec226 --- /dev/null +++ b/codemp/renderer/tr_quicksprite.h @@ -0,0 +1,47 @@ +// this include must remain at the top of every CPP file +//#include "../game/q_math.h" +//#include "tr_headers.h" + +// tr_QuickSprite.h: interface for the CQuickSprite class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_TR_QUICKSPRITE_H__6F05EB85_A1ED_4537_9EC0_9F5D82A5D99A__INCLUDED_) +#define AFX_TR_QUICKSPRITE_H__6F05EB85_A1ED_4537_9EC0_9F5D82A5D99A__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +class CQuickSpriteSystem +{ +private: + textureBundle_t *mTexBundle; + unsigned long mGLStateBits; + int mFogIndex; + qboolean mUseFog; + vec4_t mVerts[SHADER_MAX_VERTEXES]; + unsigned int mIndexes[SHADER_MAX_VERTEXES]; // Ideally this would be static, cause it never changes + vec2_t mTextureCoords[SHADER_MAX_VERTEXES]; // Ideally this would be static, cause it never changes + vec2_t mFogTextureCoords[SHADER_MAX_VERTEXES]; + unsigned long mColors[SHADER_MAX_VERTEXES]; + int mNextVert; + + void Flush(void); + +public: + CQuickSpriteSystem(); + virtual ~CQuickSpriteSystem(); + + void StartGroup(textureBundle_t *bundle, unsigned long glbits, int fogIndex = -1); + void EndGroup(void); + + void Add(float *pointdata, color4ub_t color, vec2_t fog=NULL); +}; + +extern CQuickSpriteSystem SQuickSprite; + + +#endif // !defined(AFX_TR_QUICKSPRITE_H__6F05EB85_A1ED_4537_9EC0_9F5D82A5D99A__INCLUDED_) + + diff --git a/codemp/renderer/tr_scene.cpp b/codemp/renderer/tr_scene.cpp new file mode 100644 index 0000000..bdb3d14 --- /dev/null +++ b/codemp/renderer/tr_scene.cpp @@ -0,0 +1,883 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +#if !defined(G2_H_INC) + #include "../ghoul2/G2.h" +#endif +#include "../ghoul2/G2_local.h" +#include "MatComp.h" + +#ifdef _XBOX +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +#pragma warning (disable: 4512) //default assignment operator could not be gened +#include "../qcommon/disablewarnings.h" + +static int r_firstSceneDrawSurf; + +int r_numdlights; +int r_firstSceneDlight; + +static int r_numentities; +static int r_firstSceneEntity; +static int r_numminientities; +static int r_firstSceneMiniEntity; +static int refEntParent = -1; + +static int r_numpolys; +static int r_firstScenePoly; + +static int r_numpolyverts; + +int skyboxportal; +int drawskyboxportal; + +/* +==================== +R_ToggleSmpFrame + +==================== +*/ +void R_ToggleSmpFrame( void ) { + backEndData->commands.used = 0; + + r_firstSceneDrawSurf = 0; + + r_numdlights = 0; + r_firstSceneDlight = 0; + + r_numentities = 0; + r_firstSceneEntity = 0; + refEntParent = -1; + r_numminientities = 0; + r_firstSceneMiniEntity = 0; + + r_numpolys = 0; + r_firstScenePoly = 0; + + r_numpolyverts = 0; +} + + +/* +==================== +RE_ClearScene + +==================== +*/ +void RE_ClearScene( void ) { + r_firstSceneDlight = r_numdlights; + r_firstSceneEntity = r_numentities; + r_firstScenePoly = r_numpolys; + refEntParent = -1; + r_firstSceneMiniEntity = r_numminientities; +} + +/* +=========================================================================== + +DISCRETE POLYS + +=========================================================================== +*/ + +/* +===================== +R_AddPolygonSurfaces + +Adds all the scene's polys into this view's drawsurf list +===================== +*/ +void R_AddPolygonSurfaces( void ) { + int i; + shader_t *sh; + srfPoly_t *poly; + + tr.currentEntityNum = TR_WORLDENT; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + for ( i = 0, poly = tr.refdef.polys; i < tr.refdef.numPolys ; i++, poly++ ) { + sh = R_GetShaderByHandle( poly->hShader ); + R_AddDrawSurf( (surfaceType_t *)poly, sh, poly->fogIndex, qfalse ); + } +} + +/* +===================== +RE_AddPolyToScene + +===================== +*/ +void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ) { + srfPoly_t *poly; + int i, j; + int fogIndex; + fog_t *fog; + vec3_t bounds[2]; + + if ( !tr.registered ) { + return; + } + + if ( !hShader ) { + Com_Printf (S_COLOR_YELLOW "WARNING: RE_AddPolyToScene: NULL poly shader\n"); + return; + } + + for ( j = 0; j < numPolys; j++ ) { + if ( r_numpolyverts + numVerts > max_polyverts || r_numpolys >= max_polys ) { + Com_Printf (S_COLOR_YELLOW "WARNING: RE_AddPolyToScene: r_max_polys or r_max_polyverts reached\n"); + return; + } + + poly = &backEndData->polys[r_numpolys]; + poly->surfaceType = SF_POLY; + poly->hShader = hShader; + poly->numVerts = numVerts; + poly->verts = &backEndData->polyVerts[r_numpolyverts]; + + Com_Memcpy( poly->verts, &verts[numVerts*j], numVerts * sizeof( *verts ) ); + + // done. + r_numpolys++; + r_numpolyverts += numVerts; + + // if no world is loaded + if ( tr.world == NULL ) { + fogIndex = 0; + } + // see if it is in a fog volume + else if ( tr.world->numfogs == 1 ) { + fogIndex = 0; + } else { + // find which fog volume the poly is in + VectorCopy( poly->verts[0].xyz, bounds[0] ); + VectorCopy( poly->verts[0].xyz, bounds[1] ); + for ( i = 1 ; i < poly->numVerts ; i++ ) { + AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] ); + } + for ( fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++ ) { + fog = &tr.world->fogs[fogIndex]; + if ( bounds[1][0] >= fog->bounds[0][0] + && bounds[1][1] >= fog->bounds[0][1] + && bounds[1][2] >= fog->bounds[0][2] + && bounds[0][0] <= fog->bounds[1][0] + && bounds[0][1] <= fog->bounds[1][1] + && bounds[0][2] <= fog->bounds[1][2] ) { + break; + } + } + if ( fogIndex == tr.world->numfogs ) { + fogIndex = 0; + } + } + poly->fogIndex = fogIndex; + } +} + + +//================================================================================= + + +/* +===================== +RE_AddRefEntityToScene + +===================== +*/ +void RE_AddRefEntityToScene( const refEntity_t *ent ) { + if ( !tr.registered ) { + return; + } + + assert(!ent || ent->renderfx >= 0); + + if (ent->reType == RT_ENT_CHAIN) + { //minirefents must die. + return; + } + +#ifdef _DEBUG + if (ent->reType == RT_MODEL) + { + assert(ent->hModel || ent->ghoul2 || ent->customShader); + } +#endif + + if ( r_numentities >= TR_WORLDENT ) + { +#ifndef FINAL_BUILD + Com_Printf( "WARNING: RE_AddRefEntityToScene: too many entities\n"); +#endif + return; + } + if ( ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) { + Com_Error( ERR_DROP, "RE_AddRefEntityToScene: bad reType %i", ent->reType ); + } + + backEndData->entities[r_numentities].e = *ent; + backEndData->entities[r_numentities].lightingCalculated = qfalse; + + if (ent->ghoul2) + { + CGhoul2Info_v &ghoul2 = *((CGhoul2Info_v *)ent->ghoul2); + + if (!ghoul2[0].mModel) + { +#ifdef _DEBUG + CGhoul2Info &g2 = ghoul2[0]; +#endif + //DebugBreak(); + Com_Printf("Your ghoul2 instance has no model!\n"); + } + } + + /* + if (ent->reType == RT_ENT_CHAIN) + { + refEntParent = r_numentities; + backEndData->entities[r_numentities].e.uRefEnt.uMini.miniStart = r_numminientities - r_firstSceneMiniEntity; + backEndData->entities[r_numentities].e.uRefEnt.uMini.miniCount = 0; + } + else + { + */ + refEntParent = -1; + //} + + r_numentities++; +} + + +/************************************************************************************************ + * RE_AddMiniRefEntityToScene * + * Adds a mini ref ent to the scene. If the input parameter is null, it signifies the end * + * of the chain. Otherwise, if there is a valid chain parent, it will be added to that. * + * If there is no parent, it will be added as a regular ref ent. * + * * + * Input * + * ent: the mini ref ent to be added * + * * + * Output / Return * + * none * + * * + ************************************************************************************************/ +void RE_AddMiniRefEntityToScene( const miniRefEntity_t *ent ) +{ +#if 0 + refEntity_t *parent; +#endif + + if ( !tr.registered ) + { + return; + } + if (!ent) + { + refEntParent = -1; + return; + } + +#if 1 //i hate you minirefent! + refEntity_t tempEnt; + + memcpy(&tempEnt, ent, sizeof(*ent)); + memset(((char *)&tempEnt)+sizeof(*ent), 0, sizeof(tempEnt) - sizeof(*ent)); +#ifdef _XBOX + if(ClientManager::splitScreenMode == qtrue && ClientManager::ActiveClientNum() == 0) + tempEnt.skipForPlayer2 = true; +#endif + RE_AddRefEntityToScene(&tempEnt); +#else + + if ( ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) + { + Com_Error( ERR_DROP, "RE_AddMiniRefEntityToScene: bad reType %i", ent->reType ); + } + + if (!r_numentities || refEntParent == -1 || r_numminientities >= MAX_MINI_ENTITIES) + { //rww - add it as a refent also if we run out of minis +// Com_Error( ERR_DROP, "RE_AddMiniRefEntityToScene: mini without parent ref ent"); + refEntity_t tempEnt; + + memcpy(&tempEnt, ent, sizeof(*ent)); + memset(((char *)&tempEnt)+sizeof(*ent), 0, sizeof(tempEnt) - sizeof(*ent)); + RE_AddRefEntityToScene(&tempEnt); + return; + } + + parent = &backEndData->entities[refEntParent].e; + parent->uRefEnt.uMini.miniCount++; + + backEndData->miniEntities[r_numminientities].e = *ent; + r_numminientities++; +#endif +} + +/* +===================== +RE_AddDynamicLightToScene + +===================== +*/ +void RE_AddDynamicLightToScene( const vec3_t org, float intensity, float r, float g, float b, int additive ) { + dlight_t *dl; + + if ( !tr.registered ) { + return; + } + if ( r_numdlights >= MAX_DLIGHTS ) { + return; + } + if ( intensity <= 0 ) { + return; + } + dl = &backEndData->dlights[r_numdlights++]; + VectorCopy (org, dl->origin); + dl->radius = intensity; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; + dl->additive = additive; +} + +/* +===================== +RE_AddLightToScene + +===================== +*/ +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, qfalse ); +} + +/* +===================== +RE_AddAdditiveLightToScene + +===================== +*/ +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, qtrue ); +} + + +enum +{ + DECALPOLY_TYPE_NORMAL, + DECALPOLY_TYPE_FADE, + DECALPOLY_TYPE_MAX +}; + +#define DECAL_FADE_TIME 1000 + +decalPoly_t* RE_AllocDecal ( int type ); + +static decalPoly_t re_decalPolys[DECALPOLY_TYPE_MAX][MAX_DECAL_POLYS]; + +static int re_decalPolyHead[DECALPOLY_TYPE_MAX]; +static int re_decalPolyTotal[DECALPOLY_TYPE_MAX]; + +/* +=================== +RE_ClearDecals + +This is called to remove all decals from the world +=================== +*/ + +void RE_ClearDecals ( void ) +{ + memset( re_decalPolys, 0, sizeof(re_decalPolys) ); + memset( re_decalPolyHead, 0, sizeof(re_decalPolyHead) ); + memset( re_decalPolyTotal, 0, sizeof(re_decalPolyTotal) ); +} + +void R_InitDecals ( void ) +{ + RE_ClearDecals ( ); +} + +void RE_FreeDecal ( int type, int index ) +{ + if ( !re_decalPolys[type][index].time ) + { + return; + } + + if ( type == DECALPOLY_TYPE_NORMAL ) + { + decalPoly_t* fade; + + fade = RE_AllocDecal ( DECALPOLY_TYPE_FADE ); + + memcpy ( fade, &re_decalPolys[type][index], sizeof(decalPoly_t) ); + + fade->time = tr.refdef.time; + fade->fadetime = tr.refdef.time + DECAL_FADE_TIME; + } + + re_decalPolys[type][index].time = 0; + + re_decalPolyTotal[type]--; +} + +/* +=================== +RE_AllocDecal + +Will allways succeed, even if it requires freeing an old active mark +=================== +*/ +decalPoly_t* RE_AllocDecal( int type ) +{ + decalPoly_t *le; + + // See if the cvar changed + if ( re_decalPolyTotal[type] > r_markcount->integer ) + { + RE_ClearDecals ( ); + } + + le = &re_decalPolys[type][re_decalPolyHead[type]]; + + // If it has no time its the first occasion its been used + if ( le->time ) + { + if ( le->time != tr.refdef.time ) + { + int i = re_decalPolyHead[type]; + + // since we are killing one that existed before, make sure we + // kill all the other marks that belong to the group + do + { + i++; + if ( i >= r_markcount->integer ) + { + i = 0; + } + + // Break out on the first one thats not part of the group + if ( re_decalPolys[type][i].time != le->time ) + { + break; + } + + RE_FreeDecal ( type, i ); + } + while ( i != re_decalPolyHead[type] ); + + RE_FreeDecal ( type, re_decalPolyHead[type] ); + } + else + { + RE_FreeDecal ( type, re_decalPolyHead[type] ); + } + } + + memset ( le, 0, sizeof(decalPoly_t) ); + le->time = tr.refdef.time; + + re_decalPolyTotal[type]++; + + // Move on to the next decal poly and wrap around if need be + re_decalPolyHead[type]++; + if ( re_decalPolyHead[type] >= r_markcount->integer ) + { + re_decalPolyHead[type] = 0; + } + + return le; +} + + +/* +================= +RE_AddDecalToScene + +origin should be a point within a unit of the plane +dir should be the plane normal + +temporary marks will not be stored or randomly oriented, but immediately +passed to the renderer. +================= +*/ +#define MAX_DECAL_FRAGMENTS 128 +#define MAX_DECAL_POINTS 384 + +void RE_AddDecalToScene ( qhandle_t decalShader, const vec3_t origin, const vec3_t dir, float orientation, float red, float green, float blue, float alpha, qboolean alphaFade, float radius, qboolean temporary ) +{ + vec3_t axis[3]; + float texCoordScale; + vec3_t originalPoints[4]; + byte colors[4]; + int i, j; + int numFragments; + markFragment_t markFragments[MAX_DECAL_FRAGMENTS], *mf; + vec3_t markPoints[MAX_DECAL_POINTS]; + vec3_t projection; + + assert(decalShader); + + if ( r_markcount->integer <= 0 && !temporary ) + { + return; + } + + if ( radius <= 0 ) + { + Com_Error( ERR_FATAL, "RE_AddDecalToScene: called with <= 0 radius" ); + } + + // create the texture axis + VectorNormalize2( dir, axis[0] ); + PerpendicularVector( axis[1], axis[0] ); + RotatePointAroundVector( axis[2], axis[0], axis[1], orientation ); + CrossProduct( axis[0], axis[2], axis[1] ); + + texCoordScale = 0.5 * 1.0 / radius; + + // create the full polygon + for ( i = 0 ; i < 3 ; i++ ) + { + originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; + originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; + originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; + originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; + } + + // get the fragments + VectorScale( dir, -20, projection ); + numFragments = R_MarkFragments( 4, (const vec3_t*)originalPoints, + projection, MAX_DECAL_POINTS, markPoints[0], + MAX_DECAL_FRAGMENTS, markFragments ); + + colors[0] = red * 255; + colors[1] = green * 255; + colors[2] = blue * 255; + colors[3] = alpha * 255; + + for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) + { + polyVert_t *v; + polyVert_t verts[MAX_VERTS_ON_DECAL_POLY]; + decalPoly_t *decal; + + // we have an upper limit on the complexity of polygons + // that we store persistantly + if ( mf->numPoints > MAX_VERTS_ON_DECAL_POLY ) + { + mf->numPoints = MAX_VERTS_ON_DECAL_POLY; + } + + for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) + { + vec3_t delta; + + VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); + + VectorSubtract( v->xyz, origin, delta ); + v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; + v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; + + *(int *)v->modulate = *(int *)colors; + } + + // if it is a temporary (shadow) mark, add it immediately and forget about it + if ( temporary ) + { + RE_AddPolyToScene( decalShader, mf->numPoints, verts, 1 ); + continue; + } + + // otherwise save it persistantly + decal = RE_AllocDecal( DECALPOLY_TYPE_NORMAL ); + decal->time = tr.refdef.time; + decal->shader = decalShader; + decal->poly.numVerts = mf->numPoints; + decal->color[0] = red; + decal->color[1] = green; + decal->color[2] = blue; + decal->color[3] = alpha; + memcpy( decal->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + } +} + +/* +=============== +R_AddDecals +=============== +*/ +static inline void R_AddDecals ( void ) +{ + int decalPoly; + int type; + static int lastMarkCount = -1; + + if ( r_markcount->integer != lastMarkCount ) + { + if ( lastMarkCount != -1 ) + { + RE_ClearDecals ( ); + } + + lastMarkCount = r_markcount->integer; + } + + if ( r_markcount->integer <= 0 ) + { + return; + } + + for ( type = DECALPOLY_TYPE_NORMAL; type < DECALPOLY_TYPE_MAX; type ++ ) + { + decalPoly = re_decalPolyHead[type]; + + do + { + decalPoly_t* p = &re_decalPolys[type][decalPoly]; + + if ( p->time ) + { + if ( p->fadetime ) + { + int t; + + // fade all marks out with time + t = tr.refdef.time - p->time; + if ( t < DECAL_FADE_TIME ) + { + float fade; + int j; + + fade = 255.0f * (1.0f - ((float)t / DECAL_FADE_TIME)); + + for ( j = 0 ; j < p->poly.numVerts ; j++ ) + { + p->verts[j].modulate[3] = fade; + } + + RE_AddPolyToScene( p->shader, p->poly.numVerts, p->verts, 1 ); + } + else + { + RE_FreeDecal ( type, decalPoly ); + } + } + else + { + RE_AddPolyToScene( p->shader, p->poly.numVerts, p->verts, 1 ); + } + } + + decalPoly++; + if ( decalPoly >= r_markcount->integer ) + { + decalPoly = 0; + } + } + while ( decalPoly != re_decalPolyHead[type] ); + } +} + + +/* +@@@@@@@@@@@@@@@@@@@@@ +RE_RenderScene + +Draw a 3D view into a part of the window, then return +to 2D drawing. + +Rendering a scene may require multiple views to be rendered +to handle mirrors, +@@@@@@@@@@@@@@@@@@@@@ +*/ +void RE_RenderWorldEffects(void); +void RE_RenderAutoMap(void); +void RE_RenderScene( const refdef_t *fd ) { + viewParms_t parms; + int startTime; + static int lastTime = 0; + + if ( !tr.registered ) { + return; + } + GLimp_LogComment( "====== RE_RenderScene =====\n" ); + + if ( r_norefresh->integer ) { + return; + } + + startTime = Sys_Milliseconds()*com_timescale->value; + + if (!tr.world && !( fd->rdflags & RDF_NOWORLDMODEL ) ) { + Com_Error (ERR_DROP, "R_RenderScene: NULL worldmodel"); + } + + Com_Memcpy( tr.refdef.text, fd->text, sizeof( tr.refdef.text ) ); + + tr.refdef.x = fd->x; + tr.refdef.y = fd->y; + tr.refdef.width = fd->width; + tr.refdef.height = fd->height; + tr.refdef.fov_x = fd->fov_x; + tr.refdef.fov_y = fd->fov_y; + + VectorCopy( fd->vieworg, tr.refdef.vieworg ); + VectorCopy( fd->viewaxis[0], tr.refdef.viewaxis[0] ); + VectorCopy( fd->viewaxis[1], tr.refdef.viewaxis[1] ); + VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] ); + + tr.refdef.time = fd->time; + tr.refdef.frametime = fd->time - lastTime; + lastTime = fd->time; + + if (fd->rdflags & RDF_SKYBOXPORTAL) + { + skyboxportal = 1; + } + + if (fd->rdflags & RDF_DRAWSKYBOX) + { + drawskyboxportal = 1; + } + else + { + drawskyboxportal = 0; + } + + if (tr.refdef.frametime > 500) + { + tr.refdef.frametime = 500; + } + else if (tr.refdef.frametime < 0) + { + tr.refdef.frametime = 0; + } + tr.refdef.rdflags = fd->rdflags; + + // copy the areamask data over and note if it has changed, which + // will force a reset of the visible leafs even if the view hasn't moved + tr.refdef.areamaskModified = qfalse; + if ( ! (tr.refdef.rdflags & RDF_NOWORLDMODEL) ) { + int areaDiff; + int i; + + // compare the area bits + areaDiff = 0; + for (i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++) { + areaDiff |= ((int *)tr.refdef.areamask)[i] ^ ((int *)fd->areamask)[i]; + ((int *)tr.refdef.areamask)[i] = ((int *)fd->areamask)[i]; + } + + if ( areaDiff ) { + // a door just opened or something + tr.refdef.areamaskModified = qtrue; + } + } + + + // derived info + + tr.refdef.floatTime = tr.refdef.time * 0.001f; + + tr.refdef.numDrawSurfs = r_firstSceneDrawSurf; + tr.refdef.drawSurfs = backEndData->drawSurfs; + + tr.refdef.num_entities = r_numentities - r_firstSceneEntity; + tr.refdef.entities = &backEndData->entities[r_firstSceneEntity]; + tr.refdef.miniEntities = &backEndData->miniEntities[r_firstSceneMiniEntity]; + + tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight; + tr.refdef.dlights = &backEndData->dlights[r_firstSceneDlight]; + + // Add the decals here because decals add polys and we need to ensure + // that the polys are added before the the renderer is prepared + if ( !(tr.refdef.rdflags & RDF_NOWORLDMODEL) ) + { + R_AddDecals ( ); + } + + tr.refdef.numPolys = r_numpolys - r_firstScenePoly; + tr.refdef.polys = &backEndData->polys[r_firstScenePoly]; + + // turn off dynamic lighting globally by clearing all the + // dlights if it needs to be disabled or if vertex lighting is enabled + if ( r_dynamiclight->integer == 0 || + r_vertexLight->integer == 1 ) { + tr.refdef.num_dlights = 0; + } + + // a single frame may have multiple scenes draw inside it -- + // a 3D game view, 3D status bar renderings, 3D menus, etc. + // They need to be distinguished by the light flare code, because + // the visibility state for a given surface may be different in + // each scene / view. + tr.frameSceneNum++; + tr.sceneCount++; + + // setup view parms for the initial view + // + // set up viewport + // The refdef takes 0-at-the-top y coordinates, so + // convert to GL's 0-at-the-bottom space + // + Com_Memset( &parms, 0, sizeof( parms ) ); + parms.viewportX = tr.refdef.x; + parms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.refdef.height ); + parms.viewportWidth = tr.refdef.width; + parms.viewportHeight = tr.refdef.height; + parms.isPortal = qfalse; + + parms.fovX = tr.refdef.fov_x; + parms.fovY = tr.refdef.fov_y; + + VectorCopy( fd->vieworg, parms.ori.origin ); + VectorCopy( fd->viewaxis[0], parms.ori.axis[0] ); + VectorCopy( fd->viewaxis[1], parms.ori.axis[1] ); + VectorCopy( fd->viewaxis[2], parms.ori.axis[2] ); + + VectorCopy( fd->vieworg, parms.pvsOrigin ); + + R_RenderView( &parms ); + + // the next scene rendered in this frame will tack on after this one + r_firstSceneDrawSurf = tr.refdef.numDrawSurfs; +#ifdef _XBOX + const char *cstr = CG_ConfigString(CS_SKYBOXORG); + if(ClientManager::splitScreenMode == qfalse || + (ClientManager::splitScreenMode == qtrue && (cstr && cstr[0]))) +#endif + r_firstSceneEntity = r_numentities; + r_firstSceneMiniEntity = r_numminientities; + r_firstSceneDlight = r_numdlights; + r_firstScenePoly = r_numpolys; + + refEntParent = -1; + + tr.frontEndMsec += Sys_Milliseconds()*com_timescale->value - startTime; + + RE_RenderWorldEffects(); + + if (tr.refdef.rdflags & RDF_AUTOMAP) + { + RE_RenderAutoMap(); + } +} + +#if 0 //rwwFIXMEFIXME: Disable this before release!!!!!! I am just trying to find a crash bug. +int R_GetRNumEntities(void) +{ + return r_numentities; +} + +void R_SetRNumEntities(int num) +{ + r_numentities = num; +} +#endif diff --git a/codemp/renderer/tr_shade.cpp b/codemp/renderer/tr_shade.cpp new file mode 100644 index 0000000..4b01c4b --- /dev/null +++ b/codemp/renderer/tr_shade.cpp @@ -0,0 +1,2482 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_shade.c + +#include "tr_local.h" + +#ifdef VV_LIGHTING +#include "../win32/glw_win_dx8.h" +#include "../win32/win_lighteffects.h" +#endif + +#include "tr_QuickSprite.h" + +/* + + THIS ENTIRE FILE IS BACK END + + This file deals with applying shaders to surface data in the tess struct. +*/ + +shaderCommands_t tess; +static qboolean setArraysOnce; + +color4ub_t styleColors[MAX_LIGHT_STYLES]; + +extern bool g_bRenderGlowingObjects; + +/* +================ +R_ArrayElementDiscrete + +This is just for OpenGL conformance testing, it should never be the fastest +================ +*/ +static void APIENTRY R_ArrayElementDiscrete( GLint index ) { +#ifndef _XBOX + qglColor4ubv( tess.svars.colors[ index ] ); + if ( glState.currenttmu ) { + qglMultiTexCoord2fARB( 0, tess.svars.texcoords[ 0 ][ index ][0], tess.svars.texcoords[ 0 ][ index ][1] ); + qglMultiTexCoord2fARB( 1, tess.svars.texcoords[ 1 ][ index ][0], tess.svars.texcoords[ 1 ][ index ][1] ); + } else { + qglTexCoord2fv( tess.svars.texcoords[ 0 ][ index ] ); + } + qglVertex3fv( tess.xyz[ index ] ); +#endif +} + +/* +=================== +R_DrawStripElements + +=================== +*/ +static int c_vertexes; // for seeing how long our average strips are +static int c_begins; +static void R_DrawStripElements( int numIndexes, const glIndex_t *indexes, void ( APIENTRY *element )(GLint) ) { + int i; + glIndex_t last[3]; + qboolean even; + + c_begins++; + + if ( numIndexes <= 0 ) { + return; + } + + qglBegin( GL_TRIANGLE_STRIP ); + + // prime the strip + element( indexes[0] ); + element( indexes[1] ); + element( indexes[2] ); + c_vertexes += 3; + + last[0] = indexes[0]; + last[1] = indexes[1]; + last[2] = indexes[2]; + + even = qfalse; + + for ( i = 3; i < numIndexes; i += 3 ) + { + // odd numbered triangle in potential strip + if ( !even ) + { + // check previous triangle to see if we're continuing a strip + if ( ( indexes[i+0] == last[2] ) && ( indexes[i+1] == last[1] ) ) + { + element( indexes[i+2] ); + c_vertexes++; + assert( indexes[i+2] < tess.numVertexes ); + even = qtrue; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + qglEnd(); + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + element( indexes[i+0] ); + element( indexes[i+1] ); + element( indexes[i+2] ); + + c_vertexes += 3; + + even = qfalse; + } + } + else + { + // check previous triangle to see if we're continuing a strip + if ( ( last[2] == indexes[i+1] ) && ( last[0] == indexes[i+0] ) ) + { + element( indexes[i+2] ); + c_vertexes++; + + even = qfalse; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + qglEnd(); + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + element( indexes[i+0] ); + element( indexes[i+1] ); + element( indexes[i+2] ); + c_vertexes += 3; + + even = qfalse; + } + } + + // cache the last three vertices + last[0] = indexes[i+0]; + last[1] = indexes[i+1]; + last[2] = indexes[i+2]; + } + + qglEnd(); +} + + + +/* +================== +R_DrawElements + +Optionally performs our own glDrawElements that looks for strip conditions +instead of using the single glDrawElements call that may be inefficient +without compiled vertex arrays. +================== +*/ +static void R_DrawElements( int numIndexes, const glIndex_t *indexes ) { + int primitives; + + primitives = r_primitives->integer; + + // default is to use triangles if compiled vertex arrays are present + if ( primitives == 0 ) { + if ( qglLockArraysEXT ) { + primitives = 2; + } else { + primitives = 1; + } + } + + + if ( primitives == 2 ) { + qglDrawElements( GL_TRIANGLES, + numIndexes, + GL_INDEX_TYPE, + indexes ); + return; + } + +#ifdef _XBOX + if (primitives == 1 || primitives == 3) + { +// if (tess.useConstantColor) +// { +// qglDisableClientState( GL_COLOR_ARRAY ); +// qglColor4ubv( tess.constantColor ); +// } + qglDrawElements( GL_TRIANGLES, + numIndexes, + GL_INDEX_TYPE, + indexes ); +#if 1 // VVFIXME : Temporary solution to try and increase framerate +// qglIndexedTriToStrip( numIndexes, indexes ); +#endif + + return; + } +#else // _XBOX + if ( primitives == 1 ) { + R_DrawStripElements( numIndexes, indexes, qglArrayElement ); + return; + } + + if ( primitives == 3 ) { + R_DrawStripElements( numIndexes, indexes, R_ArrayElementDiscrete ); + return; + } +#endif // _XBOX + + // anything else will cause no drawing +} + + + + +/* +============================================================= + +SURFACE SHADERS + +============================================================= +*/ + +/* +================= +R_BindAnimatedImage + +================= +*/ +e_status CIN_RunCinematic (int handle); //cl_cin.cpp +void CIN_UploadCinematic(int handle); + +// de-static'd because tr_quicksprite wants it +void R_BindAnimatedImage( textureBundle_t *bundle ) { + int index; + +/* + if ( bundle->isVideoMap ) { + CIN_RunCinematic(bundle->videoMapHandle); + CIN_UploadCinematic(bundle->videoMapHandle); + return; + } +*/ + + if ((r_fullbright->value /*|| tr.refdef.doFullbright */) && bundle->isLightmap) + { + GL_Bind( tr.whiteImage ); + return; + } + + if ( bundle->numImageAnimations <= 1 ) { + GL_Bind( bundle->image ); + return; + } + + if (backEnd.currentEntity->e.renderfx & RF_SETANIMINDEX ) + { + index = backEnd.currentEntity->e.skinNum; + } + else + { + // it is necessary to do this messy calc to make sure animations line up + // exactly with waveforms of the same frequency + index = myftol( tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE ); + index >>= FUNCTABLE_SIZE2; + + if ( index < 0 ) { + index = 0; // may happen with shader time offsets + } + } + + if ( bundle->oneShotAnimMap ) + { + if ( index >= bundle->numImageAnimations ) + { + // stick on last frame + index = bundle->numImageAnimations - 1; + } + } + else + { + // loop + index %= bundle->numImageAnimations; + } + + GL_Bind( *((image_t**)bundle->image + index) ); +} + +/* +================ +DrawTris + +Draws triangle outlines for debugging +================ +*/ +static void DrawTris (shaderCommands_t *input) { + GL_Bind( tr.whiteImage ); + qglColor3f (1,1,1); + + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + qglDepthRange( 0, 0 ); + + qglDisableClientState (GL_COLOR_ARRAY); + qglDisableClientState (GL_TEXTURE_COORD_ARRAY); + + qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD + + if (qglLockArraysEXT) { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + R_DrawElements( input->numIndexes, input->indexes ); + + if (qglUnlockArraysEXT) { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + qglDepthRange( 0, 1 ); +} + + +/* +================ +DrawNormals + +Draws vertex normals for debugging +================ +*/ +static void DrawNormals (shaderCommands_t *input) { + int i; + vec3_t temp; + + GL_Bind( tr.whiteImage ); + qglColor3f (1,1,1); + qglDepthRange( 0, 0 ); // never occluded + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + + qglBegin (GL_LINES); + for (i = 0 ; i < input->numVertexes ; i++) { + qglVertex3fv (input->xyz[i]); + VectorMA (input->xyz[i], 2, input->normal[i], temp); + qglVertex3fv (temp); + } + qglEnd (); + + qglDepthRange( 0, 1 ); +} + +/* +============== +RB_BeginSurface + +We must set some things up before beginning any tesselation, +because a surface may be forced to perform a RB_End due +to overflow. +============== +*/ +void RB_BeginSurface( shader_t *shader, int fogNum ) { + shader_t *state = (shader->remappedShader) ? shader->remappedShader : shader; + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.shader = state; + tess.fogNum = fogNum; + tess.dlightBits = 0; // will be OR'd in by surface functions + tess.xstages = state->stages; + tess.numPasses = state->numUnfoggedPasses; + tess.currentStageIteratorFunc = shader->sky ? RB_StageIteratorSky : RB_StageIteratorGeneric; + + tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; +// if (tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime) { +// tess.shaderTime = tess.shader->clampTime; +// } + + tess.fading = false; + +#ifdef _XBOX + tess.setTangents = false; +#endif + + tess.registration++; +} + +/* +=================== +DrawMultitextured + +output = t0 * t1 or t0 + t1 + +t0 = most upstream according to spec +t1 = most downstream according to spec +=================== +*/ +static void DrawMultitextured( shaderCommands_t *input, int stage ) { + shaderStage_t *pStage; + + pStage = &tess.xstages[stage]; + + GL_State( pStage->stateBits ); + + // this is an ugly hack to work around a GeForce driver + // bug with multitexture and clip planes + if ( backEnd.viewParms.isPortal ) { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + + // + // base + // + GL_SelectTexture( 0 ); + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + R_BindAnimatedImage( &pStage->bundle[0] ); + + // + // lightmap/secondary pass + // + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + + if ( r_lightmap->integer ) { + GL_TexEnv( GL_REPLACE ); + } else { + GL_TexEnv( tess.shader->multitextureEnv ); + } + + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[1] ); + + R_BindAnimatedImage( &pStage->bundle[1] ); + + R_DrawElements( input->numIndexes, input->indexes ); + + // + // disable texturing on TEXTURE1, then select TEXTURE0 + // + //qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + qglDisable( GL_TEXTURE_2D ); +#ifdef _XBOX + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); +#endif + + GL_SelectTexture( 0 ); +} + + +#ifdef VV_LIGHTING +static void BuildTangentVectors( void ) { + + memset(tess.tangent, 0, sizeof(vec4_t) * SHADER_MAX_VERTEXES); + + for(int i = 0; i < tess.numIndexes; i += 3) + { + vec3_t vec1, vec2, du, dv, cp; + + vec1[0] = tess.xyz[tess.indexes[i+1]][0] - tess.xyz[tess.indexes[i]][0]; + vec1[1] = tess.svars.texcoords[0][tess.indexes[i+1]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec1[2] = tess.svars.texcoords[0][tess.indexes[i+1]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + vec2[0] = tess.xyz[tess.indexes[i+2]][0] - tess.xyz[tess.indexes[i]][0]; + vec2[1] = tess.svars.texcoords[0][tess.indexes[i+2]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec2[2] = tess.svars.texcoords[0][tess.indexes[i+2]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[0] = -cp[1] / cp[0]; + dv[0] = -cp[2] / cp[0]; + + vec1[0] = tess.xyz[tess.indexes[i+1]][1] - tess.xyz[tess.indexes[i]][1]; + vec1[1] = tess.svars.texcoords[0][tess.indexes[i+1]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec1[2] = tess.svars.texcoords[0][tess.indexes[i+1]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + vec2[0] = tess.xyz[tess.indexes[i+2]][1] - tess.xyz[tess.indexes[i]][1]; + vec2[1] = tess.svars.texcoords[0][tess.indexes[i+2]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec2[2] = tess.svars.texcoords[0][tess.indexes[i+2]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[1] = -cp[1] / cp[0]; + dv[1] = -cp[2] / cp[0]; + + vec1[0] = tess.xyz[tess.indexes[i+1]][2] - tess.xyz[tess.indexes[i]][2]; + vec1[1] = tess.svars.texcoords[0][tess.indexes[i+1]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec1[2] = tess.svars.texcoords[0][tess.indexes[i+1]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + vec2[0] = tess.xyz[tess.indexes[i+2]][2] - tess.xyz[tess.indexes[i]][2]; + vec2[1] = tess.svars.texcoords[0][tess.indexes[i+2]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec2[2] = tess.svars.texcoords[0][tess.indexes[i+2]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[2] = -cp[1] / cp[0]; + dv[2] = -cp[2] / cp[0]; + + tess.tangent[tess.indexes[i]][0] += du[0]; + tess.tangent[tess.indexes[i]][1] += du[1]; + tess.tangent[tess.indexes[i]][2] += du[2]; + + tess.tangent[tess.indexes[i+1]][0] += du[0]; + tess.tangent[tess.indexes[i+1]][1] += du[1]; + tess.tangent[tess.indexes[i+1]][2] += du[2]; + + tess.tangent[tess.indexes[i+2]][0] += du[0]; + tess.tangent[tess.indexes[i+2]][1] += du[1]; + tess.tangent[tess.indexes[i+2]][2] += du[2]; + } + + for(i = 0; i < tess.numVertexes; i++) + { + VectorNormalizeFast(tess.tangent[i]); + } +} +#endif // VV_LIGHTING + +/* +=================== +ProjectDlightTexture + +Perform dynamic lighting with another rendering pass +=================== +*/ +#ifndef VV_LIGHTING +static void ProjectDlightTexture2( void ) { + int i, l; + vec3_t origin; + byte clipBits[SHADER_MAX_VERTEXES]; + MAC_STATIC float texCoordsArray[SHADER_MAX_VERTEXES][2]; + MAC_STATIC float oldTexCoordsArray[SHADER_MAX_VERTEXES][2]; + MAC_STATIC float vertCoordsArray[SHADER_MAX_VERTEXES][4]; + unsigned int colorArray[SHADER_MAX_VERTEXES]; + glIndex_t hitIndexes[SHADER_MAX_INDEXES]; + int numIndexes; + float radius; + int fogging; + shaderStage_t *dStage; + vec3_t posa; + vec3_t posb; + vec3_t posc; + vec3_t dist; + vec3_t e1; + vec3_t e2; + vec3_t normal; + float fac,modulate; + vec3_t floatColor; + byte colorTemp[4]; + + int needResetVerts=0; + + if ( !backEnd.refdef.num_dlights ) + { + return; + } + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) + { + dlight_t *dl; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + + dl = &backEnd.refdef.dlights[l]; + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + + int clipall = 63; + for ( i = 0 ; i < tess.numVertexes ; i++) + { + int clip; + VectorSubtract( origin, tess.xyz[i], dist ); + + clip = 0; + if ( dist[0] < -radius ) + { + clip |= 1; + } + else if ( dist[0] > radius ) + { + clip |= 2; + } + if ( dist[1] < -radius ) + { + clip |= 4; + } + else if ( dist[1] > radius ) + { + clip |= 8; + } + if ( dist[2] < -radius ) + { + clip |= 16; + } + else if ( dist[2] > radius ) + { + clip |= 32; + } + + clipBits[i] = clip; + clipall &= clip; + } + if ( clipall ) + { + continue; // this surface doesn't have any of this light + } + floatColor[0] = dl->color[0] * 255.0f; + floatColor[1] = dl->color[1] * 255.0f; + floatColor[2] = dl->color[2] * 255.0f; + + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) + { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i+1]; + c = tess.indexes[i+2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) + { + continue; // not lighted + } + + // copy the vertex positions + VectorCopy(tess.xyz[a],posa); + VectorCopy(tess.xyz[b],posb); + VectorCopy(tess.xyz[c],posc); + + VectorSubtract( posa, posb,e1); + VectorSubtract( posc, posb,e2); + CrossProduct(e1,e2,normal); +// rjr - removed for hacking if ( (!r_dlightBacks->integer && DotProduct(normal,origin)-DotProduct(normal,posa) <= 0.0f) || // backface + if ( DotProduct(normal,origin)-DotProduct(normal,posa) <= 0.0f || // backface + DotProduct(normal,normal) < 1E-8f) // junk triangle + { + continue; + } + VectorNormalize(normal); + fac=DotProduct(normal,origin)-DotProduct(normal,posa); + if (fac >= radius) // out of range + { + continue; + } + modulate = 1.0f-((fac*fac) / (radius*radius)); + fac = 0.5f/sqrtf(radius*radius - fac*fac); + + // save the verts + VectorCopy(posa,vertCoordsArray[numIndexes]); + VectorCopy(posb,vertCoordsArray[numIndexes+1]); + VectorCopy(posc,vertCoordsArray[numIndexes+2]); + + // now we need e1 and e2 to be an orthonormal basis + if (DotProduct(e1,e1) > DotProduct(e2,e2)) + { + VectorNormalize(e1); + CrossProduct(e1,normal,e2); + } + else + { + VectorNormalize(e2); + CrossProduct(normal,e2,e1); + } + VectorScale(e1,fac,e1); + VectorScale(e2,fac,e2); + + VectorSubtract( posa, origin,dist); + texCoordsArray[numIndexes][0]=DotProduct(dist,e1)+0.5f; + texCoordsArray[numIndexes][1]=DotProduct(dist,e2)+0.5f; + + VectorSubtract( posb, origin,dist); + texCoordsArray[numIndexes+1][0]=DotProduct(dist,e1)+0.5f; + texCoordsArray[numIndexes+1][1]=DotProduct(dist,e2)+0.5f; + + VectorSubtract( posc, origin,dist); + texCoordsArray[numIndexes+2][0]=DotProduct(dist,e1)+0.5f; + texCoordsArray[numIndexes+2][1]=DotProduct(dist,e2)+0.5f; + + if ((texCoordsArray[numIndexes][0] < 0.0f && texCoordsArray[numIndexes+1][0] < 0.0f && texCoordsArray[numIndexes+2][0] < 0.0f) || + (texCoordsArray[numIndexes][0] > 1.0f && texCoordsArray[numIndexes+1][0] > 1.0f && texCoordsArray[numIndexes+2][0] > 1.0f) || + (texCoordsArray[numIndexes][1] < 0.0f && texCoordsArray[numIndexes+1][1] < 0.0f && texCoordsArray[numIndexes+2][1] < 0.0f) || + (texCoordsArray[numIndexes][1] > 1.0f && texCoordsArray[numIndexes+1][1] > 1.0f && texCoordsArray[numIndexes+2][1] > 1.0f) ) + { + continue; // didn't end up hitting this tri + } + /* old code, get from the svars = wrong + oldTexCoordsArray[numIndexes][0]=tess.svars.texcoords[0][a][0]; + oldTexCoordsArray[numIndexes][1]=tess.svars.texcoords[0][a][1]; + oldTexCoordsArray[numIndexes+1][0]=tess.svars.texcoords[0][b][0]; + oldTexCoordsArray[numIndexes+1][1]=tess.svars.texcoords[0][b][1]; + oldTexCoordsArray[numIndexes+2][0]=tess.svars.texcoords[0][c][0]; + oldTexCoordsArray[numIndexes+2][1]=tess.svars.texcoords[0][c][1]; + */ + oldTexCoordsArray[numIndexes][0]=tess.texCoords[a][0][0]; + oldTexCoordsArray[numIndexes][1]=tess.texCoords[a][0][1]; + oldTexCoordsArray[numIndexes+1][0]=tess.texCoords[b][0][0]; + oldTexCoordsArray[numIndexes+1][1]=tess.texCoords[b][0][1]; + oldTexCoordsArray[numIndexes+2][0]=tess.texCoords[c][0][0]; + oldTexCoordsArray[numIndexes+2][1]=tess.texCoords[c][0][1]; + + colorTemp[0] = myftol(floatColor[0] * modulate); + colorTemp[1] = myftol(floatColor[1] * modulate); + colorTemp[2] = myftol(floatColor[2] * modulate); + colorTemp[3] = 255; + colorArray[numIndexes]=*(unsigned int *)colorTemp; + colorArray[numIndexes+1]=*(unsigned int *)colorTemp; + colorArray[numIndexes+2]=*(unsigned int *)colorTemp; + + hitIndexes[numIndexes] = numIndexes; + hitIndexes[numIndexes+1] = numIndexes+1; + hitIndexes[numIndexes+2] = numIndexes+2; + numIndexes += 3; + + if (numIndexes>=SHADER_MAX_VERTEXES-3) + { + break; // we are out of space, so we are done :) + } + } + + if ( !numIndexes ) { + continue; + } + + //don't have fog enabled when we redraw with alpha test, or it will double over + //and screw the tri up -rww + if (r_drawfog->value == 2 && + tr.world && + (tess.fogNum == tr.world->globalFog || tess.fogNum == tr.world->numfogs)) + { + fogging = qglIsEnabled(GL_FOG); + + if (fogging) + { + qglDisable(GL_FOG); + } + } + else + { + fogging = 0; + } + + + dStage = NULL; + if (tess.shader) + { + int i = 0; + while (i < tess.shader->numUnfoggedPasses) + { + const int blendBits = (GLS_SRCBLEND_BITS+GLS_DSTBLEND_BITS); + if (((tess.shader->stages[i].bundle[0].image && !tess.shader->stages[i].bundle[0].isLightmap && !tess.shader->stages[i].bundle[0].numTexMods && tess.shader->stages[i].bundle[0].tcGen != TCGEN_ENVIRONMENT_MAPPED && tess.shader->stages[i].bundle[0].tcGen != TCGEN_FOG) || + (tess.shader->stages[i].bundle[1].image && !tess.shader->stages[i].bundle[1].isLightmap && !tess.shader->stages[i].bundle[1].numTexMods && tess.shader->stages[i].bundle[1].tcGen != TCGEN_ENVIRONMENT_MAPPED && tess.shader->stages[i].bundle[1].tcGen != TCGEN_FOG)) && + (tess.shader->stages[i].stateBits & blendBits) == 0 ) + { //only use non-lightmap opaque stages + dStage = &tess.shader->stages[i]; + break; + } + i++; + } + } + if (!needResetVerts) + { + needResetVerts=1; + if (qglUnlockArraysEXT) + { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + } + qglVertexPointer (3, GL_FLOAT, 16, vertCoordsArray); // padded for SIMD + + if (dStage) + { + GL_SelectTexture( 0 ); + GL_State(0); + qglTexCoordPointer( 2, GL_FLOAT, 0, oldTexCoordsArray[0] ); + if (dStage->bundle[0].image && !dStage->bundle[0].isLightmap && !dStage->bundle[0].numTexMods && dStage->bundle[0].tcGen != TCGEN_ENVIRONMENT_MAPPED && dStage->bundle[0].tcGen != TCGEN_FOG) + { + R_BindAnimatedImage( &dStage->bundle[0] ); + } + else + { + R_BindAnimatedImage( &dStage->bundle[1] ); + } + + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + GL_Bind( tr.dlightImage ); + GL_TexEnv( GL_MODULATE ); + + + GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL);// | GLS_ATEST_GT_0); + + R_DrawElements( numIndexes, hitIndexes ); + + qglDisable( GL_TEXTURE_2D ); + GL_SelectTexture(0); + } + else + { + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + + GL_Bind( tr.dlightImage ); + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + if ( dl->additive ) { + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + else { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + + R_DrawElements( numIndexes, hitIndexes ); + } + + if (fogging) + { + qglEnable(GL_FOG); + } + + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } + if (needResetVerts) + { + qglVertexPointer (3, GL_FLOAT, 16, tess.xyz); // padded for SIMD + if (qglLockArraysEXT) + { + qglLockArraysEXT(0, tess.numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + } +} + +static void ProjectDlightTexture( void ) { + int i, l; + vec3_t origin; + float *texCoords; + byte *colors; + byte clipBits[SHADER_MAX_VERTEXES]; + MAC_STATIC float texCoordsArray[SHADER_MAX_VERTEXES][2]; + byte colorArray[SHADER_MAX_VERTEXES][4]; + glIndex_t hitIndexes[SHADER_MAX_INDEXES]; + int numIndexes; + float scale; + float radius; + int fogging; + vec3_t floatColor; + shaderStage_t *dStage; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + + texCoords = texCoordsArray[0]; + colors = colorArray[0]; + + dl = &backEnd.refdef.dlights[l]; + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + scale = 1.0f / radius; + + floatColor[0] = dl->color[0] * 255.0f; + floatColor[1] = dl->color[1] * 255.0f; + floatColor[2] = dl->color[2] * 255.0f; + + for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) { + vec3_t dist; + int clip; + float modulate; + + backEnd.pc.c_dlightVertexes++; + + VectorSubtract( origin, tess.xyz[i], dist ); + + int l = 1; + int bestIndex = 0; + float greatest = tess.normal[i][0]; + if (greatest < 0.0f) + { + greatest = -greatest; + } + + if (VectorCompare(tess.normal[i], vec3_origin)) + { //damn you terrain! + bestIndex = 2; + } + else + { + while (l < 3) + { + if ((tess.normal[i][l] > greatest && tess.normal[i][l] > 0.0f) || + (tess.normal[i][l] < -greatest && tess.normal[i][l] < 0.0f)) + { + greatest = tess.normal[i][l]; + if (greatest < 0.0f) + { + greatest = -greatest; + } + bestIndex = l; + } + l++; + } + } + + float dUse = 0.0f; + const float maxScale = 1.5f; + const float maxGroundScale = 1.4f; + const float lightScaleTolerance = 0.1f; + + if (bestIndex == 2) + { + dUse = origin[2]-tess.xyz[i][2]; + if (dUse < 0.0f) + { + dUse = -dUse; + } + dUse = (radius*0.5f)/dUse; + if (dUse > maxGroundScale) + { + dUse = maxGroundScale; + } + else if (dUse < 0.1f) + { + dUse = 0.1f; + } + + if (VectorCompare(tess.normal[i], vec3_origin) || + tess.normal[i][0] > lightScaleTolerance || + tess.normal[i][0] < -lightScaleTolerance || + tess.normal[i][1] > lightScaleTolerance || + tess.normal[i][1] < -lightScaleTolerance) + { //if not perfectly flat, we must use a constant dist + scale = 1.0f / radius; + } + else + { + scale = 1.0f / (radius*dUse); + } + + texCoords[0] = 0.5f + dist[0] * scale; + texCoords[1] = 0.5f + dist[1] * scale; + } + else if (bestIndex == 1) + { + dUse = origin[1]-tess.xyz[i][1]; + if (dUse < 0.0f) + { + dUse = -dUse; + } + dUse = (radius*0.5f)/dUse; + if (dUse > maxScale) + { + dUse = maxScale; + } + else if (dUse < 0.1f) + { + dUse = 0.1f; + } + if (tess.normal[i][0] > lightScaleTolerance || + tess.normal[i][0] < -lightScaleTolerance || + tess.normal[i][2] > lightScaleTolerance || + tess.normal[i][2] < -lightScaleTolerance) + { //if not perfectly flat, we must use a constant dist + scale = 1.0f / radius; + } + else + { + scale = 1.0f / (radius*dUse); + } + + texCoords[0] = 0.5f + dist[0] * scale; + texCoords[1] = 0.5f + dist[2] * scale; + } + else + { + dUse = origin[0]-tess.xyz[i][0]; + if (dUse < 0.0f) + { + dUse = -dUse; + } + dUse = (radius*0.5f)/dUse; + if (dUse > maxScale) + { + dUse = maxScale; + } + else if (dUse < 0.1f) + { + dUse = 0.1f; + } + if (tess.normal[i][2] > lightScaleTolerance || + tess.normal[i][2] < -lightScaleTolerance || + tess.normal[i][1] > lightScaleTolerance || + tess.normal[i][1] < -lightScaleTolerance) + { //if not perfectly flat, we must use a constant dist + scale = 1.0f / radius; + } + else + { + scale = 1.0f / (radius*dUse); + } + + texCoords[0] = 0.5f + dist[1] * scale; + texCoords[1] = 0.5f + dist[2] * scale; + } + + clip = 0; + if ( texCoords[0] < 0.0f ) { + clip |= 1; + } else if ( texCoords[0] > 1.0f ) { + clip |= 2; + } + if ( texCoords[1] < 0.0f ) { + clip |= 4; + } else if ( texCoords[1] > 1.0f ) { + clip |= 8; + } + // modulate the strength based on the height and color + if ( dist[bestIndex] > radius ) { + clip |= 16; + modulate = 0.0f; + } else if ( dist[bestIndex] < -radius ) { + clip |= 32; + modulate = 0.0f; + } else { + dist[bestIndex] = Q_fabs(dist[bestIndex]); + if ( dist[bestIndex] < radius * 0.5f ) { + modulate = 1.0f; + } else { + modulate = 2.0f * (radius - dist[bestIndex]) * scale; + } + } + clipBits[i] = clip; + + colors[0] = myftol(floatColor[0] * modulate); + colors[1] = myftol(floatColor[1] * modulate); + colors[2] = myftol(floatColor[2] * modulate); + colors[3] = 255; + } + + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i+1]; + c = tess.indexes[i+2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) { + continue; // not lighted + } + hitIndexes[numIndexes] = a; + hitIndexes[numIndexes+1] = b; + hitIndexes[numIndexes+2] = c; + numIndexes += 3; + } + + if ( !numIndexes ) { + continue; + } + + //don't have fog enabled when we redraw with alpha test, or it will double over + //and screw the tri up -rww + if (r_drawfog->value == 2 && + tr.world && + (tess.fogNum == tr.world->globalFog || tess.fogNum == tr.world->numfogs)) + { + fogging = qglIsEnabled(GL_FOG); + + if (fogging) + { + qglDisable(GL_FOG); + } + } + else + { + fogging = 0; + } + + + dStage = NULL; + if (tess.shader && qglActiveTextureARB) + { + int i = 0; + while (i < tess.shader->numUnfoggedPasses) + { + const int blendBits = (GLS_SRCBLEND_BITS+GLS_DSTBLEND_BITS); + if (((tess.shader->stages[i].bundle[0].image && !tess.shader->stages[i].bundle[0].isLightmap && !tess.shader->stages[i].bundle[0].numTexMods) || + (tess.shader->stages[i].bundle[1].image && !tess.shader->stages[i].bundle[1].isLightmap && !tess.shader->stages[i].bundle[1].numTexMods)) && + (tess.shader->stages[i].stateBits & blendBits) == 0 ) + { //only use non-lightmap opaque stages + dStage = &tess.shader->stages[i]; + break; + } + i++; + } + } + + if (dStage) + { + GL_SelectTexture( 0 ); + GL_State(0); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + if (dStage->bundle[0].image && !dStage->bundle[0].isLightmap && !dStage->bundle[0].numTexMods) + { + R_BindAnimatedImage( &dStage->bundle[0] ); + } + else + { + R_BindAnimatedImage( &dStage->bundle[1] ); + } + + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + GL_Bind( tr.dlightImage ); + GL_TexEnv( GL_MODULATE ); + + GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL);// | GLS_ATEST_GT_0); + + R_DrawElements( numIndexes, hitIndexes ); + + qglDisable( GL_TEXTURE_2D ); + GL_SelectTexture(0); + } + else + { + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + + GL_Bind( tr.dlightImage ); + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + if ( dl->additive ) { + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + else { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + + R_DrawElements( numIndexes, hitIndexes ); + } + + if (fogging) + { + qglEnable(GL_FOG); + } + + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } +} +#endif // VV_LIGHTING + +/* +=================== +RB_FogPass + +Blends a fog texture on top of everything else +=================== +*/ +static void RB_FogPass( void ) { + fog_t *fog; + int i; + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + * ( int * )&tess.svars.colors[i] = fog->colorInt; + } + + RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[0] ); + + GL_Bind( tr.fogImage ); + + if ( tess.shader->fogPass == FP_EQUAL ) { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + } else { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + } + + R_DrawElements( tess.numIndexes, tess.indexes ); +} + +/* +=============== +ComputeColors +=============== +*/ +#ifdef _XBOX +static void ComputeColors( shaderStage_t *pStage, int forceRGBGen ) +{ + int i; + qboolean killGen = qfalse; + alphaGen_t forceAlphaGen = (alphaGen_t) pStage->alphaGen;//set this up so we can override below + + if ( tess.shader != tr.projectionShadowShader && tess.shader != tr.shadowShader && + ( backEnd.currentEntity->e.renderfx & (RF_DISINTEGRATE1|RF_DISINTEGRATE2))) + { + RB_CalcDisintegrateColors( tess.svars.colors ); + RB_CalcDisintegrateVertDeform(); + + // We've done some custom alpha and color stuff, so we can skip the rest. Let it do fog though + killGen = qtrue; + } + + // + // rgbGen + // + if ( !forceRGBGen ) + { + forceRGBGen = pStage->rgbGen; + } + + if ( backEnd.currentEntity->e.renderfx & RF_VOLUMETRIC ) // does not work for rotated models, technically, this should also be a CGEN type, but that would entail adding new shader commands....which is too much work for one thing + { + int i; + float *normal, dot; + DWORD *color; + int numVertexes; + + normal = tess.normal[0]; + color = tess.svars.colors; + + numVertexes = tess.numVertexes; + + for ( i = 0 ; i < numVertexes ; i++, normal += 4, color ++) + { + dot = DotProduct( normal, backEnd.refdef.viewaxis[0] ); + + dot *= dot * dot * dot; + + if ( dot < 0.2f ) // so low, so just clamp it + { + dot = 0.0f; + } + + *color = D3DCOLOR_RGBA( (int)(backEnd.currentEntity->e.shaderRGBA[0] * (1-dot)), + (int)(backEnd.currentEntity->e.shaderRGBA[0] * (1-dot)), + (int)(backEnd.currentEntity->e.shaderRGBA[0] * (1-dot)), + (int)(backEnd.currentEntity->e.shaderRGBA[0] * (1-dot)) ); + } + + killGen = qtrue; + } + + if (killGen) + { + goto avoidGen; + } + + DWORD color; + + switch ( forceRGBGen ) + { + case CGEN_IDENTITY: + memset( tess.svars.colors, 0xffffffff, sizeof(DWORD) * tess.numVertexes ); + break; + default: + case CGEN_IDENTITY_LIGHTING: + color = ((tr.identityLightByte & 0xff) << 24 | + (tr.identityLightByte & 0xff) << 16 | + (tr.identityLightByte & 0xff) << 8 | + (tr.identityLightByte & 0xff) << 0); + memset( tess.svars.colors, color, sizeof(DWORD) * tess.numVertexes ); + break; + case CGEN_LIGHTING_DIFFUSE: + RB_CalcDiffuseColor( tess.svars.colors ); + break; + case CGEN_LIGHTING_DIFFUSE_ENTITY: + RB_CalcDiffuseEntityColor( tess.svars.colors ); + if ( forceAlphaGen == AGEN_IDENTITY && + backEnd.currentEntity->e.shaderRGBA[3] == 0xff + ) + { + forceAlphaGen = AGEN_SKIP; //already got it in this set since it does all 4 components + } + break; + case CGEN_EXACT_VERTEX: + for( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = D3DCOLOR_RGBA( (int)(tess.vertexColors[i][0]), + (int)(tess.vertexColors[i][1]), + (int)(tess.vertexColors[i][2]), + (int)(tess.vertexColors[i][3]) ); + } + break; + case CGEN_CONST: + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i] = D3DCOLOR_RGBA( (int)(pStage->constantColor[0]), + (int)(pStage->constantColor[1]), + (int)(pStage->constantColor[2]), + (int)(pStage->constantColor[3]) ); + } + break; + case CGEN_VERTEX: + if ( tr.identityLight == 1 ) + { + for( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = D3DCOLOR_RGBA( (int)(tess.vertexColors[i][0]), + (int)(tess.vertexColors[i][1]), + (int)(tess.vertexColors[i][2]), + (int)(tess.vertexColors[i][3])); + } + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = D3DCOLOR_RGBA( (int)(tess.vertexColors[i][0] * tr.identityLight), + (int)(tess.vertexColors[i][1] * tr.identityLight), + (int)(tess.vertexColors[i][2] * tr.identityLight), + (int)(tess.vertexColors[i][3])); + } + } + break; + case CGEN_ONE_MINUS_VERTEX: + if ( tr.identityLight == 1 ) + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = D3DCOLOR_XRGB( (int)(255 - tess.vertexColors[i][0]), + (int)(255 - tess.vertexColors[i][1]), + (int)(255 - tess.vertexColors[i][2])); + } + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = D3DCOLOR_XRGB( (int)((255 - tess.vertexColors[i][0]) * tr.identityLight), + (int)((255 - tess.vertexColors[i][1]) * tr.identityLight), + (int)((255 - tess.vertexColors[i][2]) * tr.identityLight)); + } + } + break; + case CGEN_FOG: + { + fog_t *fog; + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + * ( int * )&tess.svars.colors[i] = fog->colorInt; + } + } + break; + case CGEN_WAVEFORM: + RB_CalcWaveColor( &pStage->rgbWave, tess.svars.colors ); + break; + case CGEN_ENTITY: + RB_CalcColorFromEntity( tess.svars.colors ); + if ( forceAlphaGen == AGEN_IDENTITY && + backEnd.currentEntity->e.shaderRGBA[3] == 0xff + ) + { + forceAlphaGen = AGEN_SKIP; //already got it in this set since it does all 4 components + } + break; + case CGEN_ONE_MINUS_ENTITY: + RB_CalcColorFromOneMinusEntity( tess.svars.colors ); + break; + case CGEN_LIGHTMAPSTYLE: + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = *(DWORD *)styleColors[pStage->lightmapStyle]; + } + break; + } + + // + // alphaGen + // + DWORD rgb; + switch ( forceAlphaGen ) + { + case AGEN_SKIP: + break; + case AGEN_IDENTITY: + if ( forceRGBGen != CGEN_IDENTITY && forceRGBGen != CGEN_LIGHTING_DIFFUSE ) { + if ( ( forceRGBGen == CGEN_VERTEX && tr.identityLight != 1 ) || + forceRGBGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | ((255 & 0xff) << 24); + } + } + } + break; + case AGEN_CONST: + if ( forceRGBGen != CGEN_CONST ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | ((pStage->constantColor[3] & 0xff) << 24); + } + } + break; + case AGEN_WAVEFORM: + RB_CalcWaveAlpha( &pStage->alphaWave, tess.svars.colors ); + break; + case AGEN_LIGHTING_SPECULAR: + RB_CalcSpecularAlpha( tess.svars.colors ); + break; + case AGEN_ENTITY: + if ( forceRGBGen != CGEN_ENTITY ) { //already got it in the CGEN_entity since it does all 4 components + RB_CalcAlphaFromEntity( tess.svars.colors ); + } + break; + case AGEN_ONE_MINUS_ENTITY: + RB_CalcAlphaFromOneMinusEntity( tess.svars.colors ); + break; + case AGEN_VERTEX: + if ( forceRGBGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | ((tess.vertexColors[i][3] & 0xff) << 24); + } + } + break; + case AGEN_ONE_MINUS_VERTEX: + for ( i = 0; i < tess.numVertexes; i++ ) + { + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | (((255 - tess.vertexColors[i][3]) & 0xff) << 24); + } + break; + case AGEN_PORTAL: + { + unsigned char alpha; + + for ( i = 0; i < tess.numVertexes; i++ ) + { + float len; + vec3_t v; + + VectorSubtract( tess.xyz[i], backEnd.viewParms.ori.origin, v ); + len = VectorLength( v ); + + len /= tess.shader->portalRange; + + if ( len < 0 ) + { + alpha = 0; + } + else if ( len > 1 ) + { + alpha = 0xff; + } + else + { + alpha = len * 0xff; + } + + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | ((alpha & 0xff) << 24); + } + } + break; + case AGEN_BLEND: + if ( forceRGBGen != CGEN_VERTEX ) + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | ((tess.vertexAlphas[i][pStage->index] & 0xff) << 24); + } + } + break; + } + +avoidGen: + // + // fog adjustment for colors to fade out as fog increases + // + if ( tess.fogNum ) + { + switch ( pStage->adjustColorsForFog ) + { + case ACFF_MODULATE_RGB: + RB_CalcModulateColorsByFog( tess.svars.colors ); + break; + case ACFF_MODULATE_ALPHA: + RB_CalcModulateAlphasByFog( tess.svars.colors ); + break; + case ACFF_MODULATE_RGBA: + RB_CalcModulateRGBAsByFog( tess.svars.colors ); + break; + case ACFF_NONE: + break; + } + } +} +#else // _XBOX +static void ComputeColors( shaderStage_t *pStage, int forceRGBGen ) +{ + int i; + color4ub_t *colors = tess.svars.colors; + qboolean killGen = qfalse; + alphaGen_t forceAlphaGen = pStage->alphaGen;//set this up so we can override below + + if ( tess.shader != tr.projectionShadowShader && tess.shader != tr.shadowShader && + ( backEnd.currentEntity->e.renderfx & (RF_DISINTEGRATE1|RF_DISINTEGRATE2))) + { + RB_CalcDisintegrateColors( (unsigned char *)tess.svars.colors ); + RB_CalcDisintegrateVertDeform(); + + // We've done some custom alpha and color stuff, so we can skip the rest. Let it do fog though + killGen = qtrue; + } + + // + // rgbGen + // + if ( !forceRGBGen ) + { + forceRGBGen = pStage->rgbGen; + } + + if ( backEnd.currentEntity->e.renderfx & RF_VOLUMETRIC ) // does not work for rotated models, technically, this should also be a CGEN type, but that would entail adding new shader commands....which is too much work for one thing + { + int i; + float *normal, dot; + unsigned char *color; + int numVertexes; + + normal = tess.normal[0]; + color = tess.svars.colors[0]; + + numVertexes = tess.numVertexes; + + for ( i = 0 ; i < numVertexes ; i++, normal += 4, color += 4) + { + dot = DotProduct( normal, backEnd.refdef.viewaxis[0] ); + + dot *= dot * dot * dot; + + if ( dot < 0.2f ) // so low, so just clamp it + { + dot = 0.0f; + } + + color[0] = color[1] = color[2] = color[3] = myftol( backEnd.currentEntity->e.shaderRGBA[0] * (1-dot) ); + } + + killGen = qtrue; + } + + if (killGen) + { + goto avoidGen; + } + + // + // rgbGen + // + switch ( forceRGBGen ) + { + case CGEN_IDENTITY: + Com_Memset( tess.svars.colors, 0xff, tess.numVertexes * 4 ); + break; + default: + case CGEN_IDENTITY_LIGHTING: + Com_Memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 ); + break; + case CGEN_LIGHTING_DIFFUSE: + RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_LIGHTING_DIFFUSE_ENTITY: + RB_CalcDiffuseEntityColor( ( unsigned char * ) tess.svars.colors ); + if ( forceAlphaGen == AGEN_IDENTITY && + backEnd.currentEntity->e.shaderRGBA[3] == 0xff + ) + { + forceAlphaGen = AGEN_SKIP; //already got it in this set since it does all 4 components + } + break; + case CGEN_EXACT_VERTEX: + Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); + break; + case CGEN_CONST: + for ( i = 0; i < tess.numVertexes; i++ ) { + *(int *)tess.svars.colors[i] = *(int *)pStage->constantColor; + } + break; + case CGEN_VERTEX: + if ( tr.identityLight == 1 ) + { + Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight; + tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight; + tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight; + tess.svars.colors[i][3] = tess.vertexColors[i][3]; + } + } + break; + case CGEN_ONE_MINUS_VERTEX: + if ( tr.identityLight == 1 ) + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = 255 - tess.vertexColors[i][0]; + tess.svars.colors[i][1] = 255 - tess.vertexColors[i][1]; + tess.svars.colors[i][2] = 255 - tess.vertexColors[i][2]; + } + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = ( 255 - tess.vertexColors[i][0] ) * tr.identityLight; + tess.svars.colors[i][1] = ( 255 - tess.vertexColors[i][1] ) * tr.identityLight; + tess.svars.colors[i][2] = ( 255 - tess.vertexColors[i][2] ) * tr.identityLight; + } + } + break; + case CGEN_FOG: + { + fog_t *fog; + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + * ( int * )&tess.svars.colors[i] = fog->colorInt; + } + } + break; + case CGEN_WAVEFORM: + RB_CalcWaveColor( &pStage->rgbWave, ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_ENTITY: + RB_CalcColorFromEntity( ( unsigned char * ) tess.svars.colors ); + if ( forceAlphaGen == AGEN_IDENTITY && + backEnd.currentEntity->e.shaderRGBA[3] == 0xff + ) + { + forceAlphaGen = AGEN_SKIP; //already got it in this set since it does all 4 components + } + break; + case CGEN_ONE_MINUS_ENTITY: + RB_CalcColorFromOneMinusEntity( ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_LIGHTMAPSTYLE: + for ( i = 0; i < tess.numVertexes; i++ ) + { + *(unsigned *)&colors[i] = *(unsigned *)styleColors[pStage->lightmapStyle]; + } + break; + } + + // + // alphaGen + // + switch ( pStage->alphaGen ) + { + case AGEN_SKIP: + break; + case AGEN_IDENTITY: + if ( forceRGBGen != CGEN_IDENTITY ) { + if ( ( forceRGBGen == CGEN_VERTEX && tr.identityLight != 1 ) || + forceRGBGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = 0xff; + } + } + } + break; + case AGEN_CONST: + if ( forceRGBGen != CGEN_CONST ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = pStage->constantColor[3]; + } + } + break; + case AGEN_WAVEFORM: + RB_CalcWaveAlpha( &pStage->alphaWave, ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_LIGHTING_SPECULAR: + RB_CalcSpecularAlpha( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_ENTITY: + RB_CalcAlphaFromEntity( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_ONE_MINUS_ENTITY: + RB_CalcAlphaFromOneMinusEntity( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_VERTEX: + if ( forceRGBGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = tess.vertexColors[i][3]; + } + } + break; + case AGEN_ONE_MINUS_VERTEX: + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][3] = 255 - tess.vertexColors[i][3]; + } + break; + case AGEN_PORTAL: + { + unsigned char alpha; + + for ( i = 0; i < tess.numVertexes; i++ ) + { + float len; + vec3_t v; + + VectorSubtract( tess.xyz[i], backEnd.viewParms.ori.origin, v ); + len = VectorLength( v ); + + len /= tess.shader->portalRange; + + if ( len < 0 ) + { + alpha = 0; + } + else if ( len > 1 ) + { + alpha = 0xff; + } + else + { + alpha = len * 0xff; + } + + tess.svars.colors[i][3] = alpha; + } + } + break; + case AGEN_BLEND: + if ( forceRGBGen != CGEN_VERTEX ) + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + colors[i][3] = tess.vertexAlphas[i][pStage->index]; //rwwRMG - added support + } + } + break; + } +avoidGen: + // + // fog adjustment for colors to fade out as fog increases + // + if ( tess.fogNum ) + { + switch ( pStage->adjustColorsForFog ) + { + case ACFF_MODULATE_RGB: + RB_CalcModulateColorsByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_MODULATE_ALPHA: + RB_CalcModulateAlphasByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_MODULATE_RGBA: + RB_CalcModulateRGBAsByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_NONE: + break; + } + } +} +#endif + +/* +=============== +ComputeTexCoords +=============== +*/ +static void ComputeTexCoords( shaderStage_t *pStage ) { + int i; + int b; + float *texcoords; + + for ( b = 0; b < NUM_TEXTURE_BUNDLES; b++ ) { + int tm; + + texcoords = (float *)tess.svars.texcoords[b]; + // + // generate the texture coordinates + // + switch ( pStage->bundle[b].tcGen ) + { + case TCGEN_IDENTITY: + Com_Memset( tess.svars.texcoords[b], 0, sizeof( float ) * 2 * tess.numVertexes ); + break; + case TCGEN_TEXTURE: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][0][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][0][1]; + } + break; + case TCGEN_LIGHTMAP: + for ( i = 0 ; i < tess.numVertexes ; i++,texcoords+=2 ) { + texcoords[0] = tess.texCoords[i][1][0]; + texcoords[1] = tess.texCoords[i][1][1]; + } + break; + case TCGEN_LIGHTMAP1: + for ( i = 0 ; i < tess.numVertexes ; i++,texcoords+=2 ) { + texcoords[0] = tess.texCoords[i][2][0]; + texcoords[1] = tess.texCoords[i][2][1]; + } + break; + case TCGEN_LIGHTMAP2: + for ( i = 0 ; i < tess.numVertexes ; i++,texcoords+=2 ) { + texcoords[0] = tess.texCoords[i][3][0]; + texcoords[1] = tess.texCoords[i][3][1]; + } + break; + case TCGEN_LIGHTMAP3: + for ( i = 0 ; i < tess.numVertexes ; i++,texcoords+=2 ) { + texcoords[0] = tess.texCoords[i][4][0]; + texcoords[1] = tess.texCoords[i][4][1]; + } + break; + case TCGEN_VECTOR: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[0] ); + tess.svars.texcoords[b][i][1] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[1] ); + } + break; + case TCGEN_FOG: + RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[b] ); + break; + case TCGEN_ENVIRONMENT_MAPPED: +//#ifdef VV_LIGHTING +// tess.shader->stages[tess.currentPass].isEnvironment = qtrue; +//#else + RB_CalcEnvironmentTexCoords( ( float * ) tess.svars.texcoords[b] ); +//#endif + break; + case TCGEN_BAD: + return; + } + + // + // alter texture coordinates + // + for ( tm = 0; tm < pStage->bundle[b].numTexMods ; tm++ ) { + switch ( pStage->bundle[b].texMods[tm].type ) + { + case TMOD_NONE: + tm = TR_MAX_TEXMODS; // break out of for loop + break; + + case TMOD_TURBULENT: + RB_CalcTurbulentTexCoords( &pStage->bundle[b].texMods[tm].wave, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_ENTITY_TRANSLATE: + RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_SCROLL: + RB_CalcScrollTexCoords( pStage->bundle[b].texMods[tm].translate, //scroll unioned + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_SCALE: + RB_CalcScaleTexCoords( pStage->bundle[b].texMods[tm].translate, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_STRETCH: + RB_CalcStretchTexCoords( &pStage->bundle[b].texMods[tm].wave, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_TRANSFORM: + RB_CalcTransformTexCoords( &pStage->bundle[b].texMods[tm], + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_ROTATE: + RB_CalcRotateTexCoords( pStage->bundle[b].texMods[tm].translate[0], + ( float * ) tess.svars.texcoords[b] ); + break; + + default: + Com_Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'\n", pStage->bundle[b].texMods[tm].type, tess.shader->name ); + break; + } + } + } +} + +void ForceAlpha(unsigned char *dstColors, int TR_ForceEntAlpha) +{ + int i; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = TR_ForceEntAlpha; + } +} + +/* +** RB_IterateStagesGeneric +*/ +static vec4_t GLFogOverrideColors[GLFOGOVERRIDE_MAX] = +{ + { 0.0, 0.0, 0.0, 1.0 }, // GLFOGOVERRIDE_NONE + { 0.0, 0.0, 0.0, 1.0 }, // GLFOGOVERRIDE_BLACK + { 1.0, 1.0, 1.0, 1.0 } // GLFOGOVERRIDE_WHITE +}; + +static const float logtestExp2 = (sqrt( -log( 1.0 / 255.0 ) )); +extern bool tr_stencilled; //tr_backend.cpp +static void RB_IterateStagesGeneric( shaderCommands_t *input ) +{ + int stage; + bool UseGLFog = false; + bool FogColorChange = false; + fog_t *fog = NULL; + + if (tess.fogNum && tess.shader->fogPass && (tess.fogNum == tr.world->globalFog || tess.fogNum == tr.world->numfogs) + && r_drawfog->value == 2) + { // only gl fog global fog and the "special fog" + fog = tr.world->fogs + tess.fogNum; + + if (tr.rangedFog) + { //ranged fog, used for sniper scope + float fStart = fog->parms.depthForOpaque; + + if (tr.rangedFog < 0.0f) + { //special designer override + fStart = -tr.rangedFog; + } + else + { + //the greater tr.rangedFog is, the more fog we will get between the view point and cull distance + if ((tr.distanceCull-fStart) < tr.rangedFog) + { //assure a minimum range between fog beginning and cutoff distance + fStart = tr.distanceCull-tr.rangedFog; + + if (fStart < 16.0f) + { + fStart = 16.0f; + } + } + } + + qglFogi(GL_FOG_MODE, GL_LINEAR); + + qglFogf(GL_FOG_START, fStart); + qglFogf(GL_FOG_END, tr.distanceCull); + } + else + { + qglFogi(GL_FOG_MODE, GL_EXP2); + qglFogf(GL_FOG_DENSITY, logtestExp2 / fog->parms.depthForOpaque); + } + if ( g_bRenderGlowingObjects ) + { + const float fogColor[3] = { 0.0f, 0.0f, 0.0f }; + qglFogfv(GL_FOG_COLOR, fogColor ); + } + else + { + qglFogfv(GL_FOG_COLOR, fog->parms.color); + } + qglEnable(GL_FOG); + UseGLFog = true; + } + + for ( stage = 0; stage < input->shader->numUnfoggedPasses; stage++ ) + { + shaderStage_t *pStage = &tess.xstages[stage]; + int forceRGBGen = 0; + int stateBits = 0; + + if ( !pStage->active ) + { + assert(pStage->active);//wtf? + break; + } + + // Reject this stage if it's not a glow stage but we are doing a glow pass. +/* + if ( g_bRenderGlowingObjects && !pStage->glow ) + { + continue; + } +*/ + +#ifdef _XBOX + tess.currentPass = stage; +#endif + + if ( stage && r_lightmap->integer && !( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap || pStage->bundle[0].vertexLightmap ) ) + { + break; + } + + stateBits = pStage->stateBits; + + if ( backEnd.currentEntity ) + { + assert(backEnd.currentEntity->e.renderfx >= 0); + + if ( backEnd.currentEntity->e.renderfx & RF_DISINTEGRATE1 ) + { + // we want to be able to rip a hole in the thing being disintegrated, and by doing the depth-testing it avoids some kinds of artefacts, but will probably introduce others? + stateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHMASK_TRUE | GLS_ATEST_GE_C0; + } + + if ( backEnd.currentEntity->e.renderfx & RF_RGB_TINT ) + {//want to use RGBGen from ent + forceRGBGen = CGEN_ENTITY; + } + } + + if (pStage->ss && pStage->ss->surfaceSpriteType) + { + // We check for surfacesprites AFTER drawing everything else + continue; + } + + if (UseGLFog) + { + if (pStage->mGLFogColorOverride) + { + qglFogfv(GL_FOG_COLOR, GLFogOverrideColors[pStage->mGLFogColorOverride]); + FogColorChange = true; + } + else if (FogColorChange && fog) + { + FogColorChange = false; + qglFogfv(GL_FOG_COLOR, fog->parms.color); + } + } + +#ifdef _XBOX + qglDisable(GL_LIGHTING); +#endif + + if (!input->fading) + { //this means ignore this, while we do a fade-out + ComputeColors( pStage, forceRGBGen ); + } + ComputeTexCoords( pStage ); + + if ( !setArraysOnce ) + { + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, input->svars.colors ); + } + +#ifdef VV_LIGHTING + if(pStage->rgbGen == CGEN_LIGHTING_DIFFUSE || + pStage->rgbGen == CGEN_LIGHTING_DIFFUSE_ENTITY) + { + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer(GL_FLOAT, 16, tess.normal ); + } + + /*if(pStage->isSpecular) + { + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer(GL_FLOAT, 16, tess.normal ); + if(!tess.setTangents) + BuildTangentVectors(); + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + R_BindAnimatedImage( &pStage->bundle[0] ); + GL_State( stateBits ); + glw_state->lightEffects->RenderSpecular(); + qglDisableClientState( GL_NORMAL_ARRAY ); + continue; + }*/ + if(pStage->isEnvironment) + { + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer( GL_FLOAT, 16, tess.normal ); + R_BindAnimatedImage( &pStage->bundle[0] ); + GL_State( stateBits ); + glw_state->lightEffects->RenderEnvironment(); + qglDisableClientState( GL_NORMAL_ARRAY ); + continue; + } + if(pStage->isBumpMap) + { + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer( GL_FLOAT, 16, tess.normal ); + if(!tess.setTangents) + BuildTangentVectors(); + GL_SelectTexture( 0 ); + R_BindAnimatedImage( &pStage->bundle[0] ); + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + R_BindAnimatedImage( &pStage->bundle[1] ); + GL_State( stateBits ); + glw_state->lightEffects->RenderBump(); + qglDisable( GL_TEXTURE_2D ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + GL_SelectTexture( 0 ); + qglDisableClientState( GL_NORMAL_ARRAY ); + continue; + } +#endif // VV_LIGHTING + + // + // do multitexture + // + if ( pStage->bundle[1].image != 0 ) + { + DrawMultitextured( input, stage ); + } + else + { + static bool lStencilled = false; + + if ( !setArraysOnce ) + { + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + } + + // + // set state + // + if ( (tess.shader == tr.distortionShader) || + (backEnd.currentEntity && (backEnd.currentEntity->e.renderfx & RF_DISTORTION)) ) + { //special distortion effect -rww + //tr.screenImage should have been set for this specific entity before we got in here. + GL_Bind( tr.screenImage ); + GL_Cull(CT_TWO_SIDED); + } + else if ( pStage->bundle[0].vertexLightmap && ( r_vertexLight->integer && !r_uiFullScreen->integer ) && r_lightmap->integer ) + { + GL_Bind( tr.whiteImage ); + } + else + R_BindAnimatedImage( &pStage->bundle[0] ); + + if (tess.shader == tr.distortionShader && + glConfig.stencilBits >= 4) + { //draw it to the stencil buffer! + tr_stencilled = true; + lStencilled = true; + qglEnable(GL_STENCIL_TEST); + qglStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF); + qglStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + //don't depthmask, don't blend.. don't do anything + GL_State(0); + } + else if (backEnd.currentEntity && (backEnd.currentEntity->e.renderfx & RF_FORCE_ENT_ALPHA)) + { + ForceAlpha((unsigned char *) tess.svars.colors, backEnd.currentEntity->e.shaderRGBA[3]); + if (backEnd.currentEntity->e.renderfx & RF_ALPHA_DEPTH) + { //depth write, so faces through the model will be stomped over by nearer ones. this works because + //we draw RF_FORCE_ENT_ALPHA stuff after everything else, including standard alpha surfs. + GL_State(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHMASK_TRUE); + } + else + { + GL_State(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA); + } + } + else + { + GL_State( stateBits ); + } + + // + // draw + // + R_DrawElements( input->numIndexes, input->indexes ); + + if (lStencilled) + { //re-enable the color buffer, disable stencil test + lStencilled = false; + qglDisable(GL_STENCIL_TEST); + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } + } + +#ifdef VV_LIGHTING + // Lighting may have been turned on above + qglDisable(GL_LIGHTING); + qglDisableClientState( GL_NORMAL_ARRAY ); +#endif + } + if (FogColorChange) + { + qglFogfv(GL_FOG_COLOR, fog->parms.color); + } +} + + +/* +** RB_StageIteratorGeneric +*/ +void RB_StageIteratorGeneric( void ) +{ + shaderCommands_t *input; + int stage; + + input = &tess; + + RB_DeformTessGeometry(); + + // + // log this call + // +#ifndef _XBOX + if ( r_logFile->integer ) + { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) ); + } +#endif + + // + // set face culling appropriately + // + GL_Cull( input->shader->cullType ); + + // set polygon offset if necessary + if ( input->shader->polygonOffset ) + { + qglEnable( GL_POLYGON_OFFSET_FILL ); + qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value ); + } + + // + // if there is only a single pass then we can enable color + // and texture arrays before we compile, otherwise we need + // to avoid compiling those arrays since they will change + // during multipass rendering + // + if ( tess.numPasses > 1 || input->shader->multitextureEnv ) + { + setArraysOnce = qfalse; + qglDisableClientState (GL_COLOR_ARRAY); + qglDisableClientState (GL_TEXTURE_COORD_ARRAY); + } + else + { + setArraysOnce = qtrue; + + qglEnableClientState( GL_COLOR_ARRAY); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + } + + // + // lock XYZ + // + qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD + if (qglLockArraysEXT) + { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + // + // enable color and texcoord arrays after the lock if necessary + // + if ( !setArraysOnce ) + { + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglEnableClientState( GL_COLOR_ARRAY ); + } + + // + // call shader function + // + RB_IterateStagesGeneric( input ); + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE + && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) { +#ifdef VV_LIGHTING + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer(GL_FLOAT, 16, tess.normal ); + if(!tess.setTangents) + BuildTangentVectors(); + glw_state->lightEffects->RenderDynamicLights(); + qglDisableClientState( GL_NORMAL_ARRAY ); +#else + if (r_dlightStyle->integer>0) + { + ProjectDlightTexture2(); + } + else + { + ProjectDlightTexture(); + } +#endif + } + + // + // now do fog + // + if (tr.world && (tess.fogNum != tr.world->globalFog || r_drawfog->value != 2) && r_drawfog->value && tess.fogNum && tess.shader->fogPass) + { + RB_FogPass(); + } + + // + // unlock arrays + // + if (qglUnlockArraysEXT) + { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + + // + // reset polygon offset + // + if ( input->shader->polygonOffset ) + { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } + + // Now check for surfacesprites. + if (r_surfaceSprites->integer) + { + for ( stage = 1; stage < tess.shader->numUnfoggedPasses; stage++ ) + { + if (tess.xstages[stage].ss && tess.xstages[stage].ss->surfaceSpriteType) + { // Draw the surfacesprite + RB_DrawSurfaceSprites(&tess.xstages[stage], input); + } + } + } + + //don't disable the hardware fog til after we do surface sprites + if (r_drawfog->value == 2 && + tess.fogNum && tess.shader->fogPass && + (tess.fogNum == tr.world->globalFog || tess.fogNum == tr.world->numfogs)) + { + qglDisable(GL_FOG); + } +} + + +/* +** RB_EndSurface +*/ +void RB_EndSurface( void ) { + shaderCommands_t *input; + + input = &tess; + + if (input->numIndexes == 0) { + return; + } + + if (input->indexes[SHADER_MAX_INDEXES-1] != 0) { + Com_Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit"); + } + if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) { + Com_Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit"); + } + +/* + if ( tess.shader == tr.shadowShader ) { + RB_ShadowTessEnd(); + return; + } +*/ + + // for debugging of sort order issues, stop rendering after a given sort value + if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) { + return; + } + + if ( skyboxportal ) + { + // world + if(!(backEnd.refdef.rdflags & RDF_SKYBOXPORTAL)) + { + if(tess.currentStageIteratorFunc == RB_StageIteratorSky) + { // don't process these tris at all + return; + } + } + // portal sky + else + { + if(!drawskyboxportal) + { + if( !(tess.currentStageIteratorFunc == RB_StageIteratorSky)) + { // /only/ process sky tris + return; + } + } + } + } + + // + // update performance counters + // + backEnd.pc.c_shaders++; + backEnd.pc.c_vertexes += tess.numVertexes; + backEnd.pc.c_indexes += tess.numIndexes; + backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses; + if (tess.fogNum && tess.shader->fogPass && r_drawfog->value == 1) + { + backEnd.pc.c_totalIndexes += tess.numIndexes; + } + + // + // call off to shader specific tess end function + // + tess.currentStageIteratorFunc(); + +#ifdef _XBOX + tess.currentPass = 0; +#endif + + // + // draw debugging stuff + // + if ( r_showtris->integer && com_developer->integer ) { + DrawTris (input); + } + if ( r_shownormals->integer && com_developer->integer && com_sv_running->integer ) { + DrawNormals (input); + } + // clear shader so we can tell we don't have any unclosed surfaces + tess.numIndexes = 0; + + GLimp_LogComment( "----------\n" ); +} + diff --git a/codemp/renderer/tr_shade_calc.cpp b/codemp/renderer/tr_shade_calc.cpp new file mode 100644 index 0000000..7daefe3 --- /dev/null +++ b/codemp/renderer/tr_shade_calc.cpp @@ -0,0 +1,1801 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_shade_calc.c + +#include "tr_local.h" + + +#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ myftol( ( ( (phase) + tess.shaderTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) + +static float *TableForFunc( genFunc_t func ) +{ + switch ( func ) + { + case GF_SIN: + return tr.sinTable; + case GF_TRIANGLE: + return tr.triangleTable; + case GF_SQUARE: + return tr.squareTable; + case GF_SAWTOOTH: + return tr.sawToothTable; + case GF_INVERSE_SAWTOOTH: + return tr.inverseSawToothTable; + case GF_NONE: + default: + break; + } + + Com_Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'\n", func, tess.shader->name ); + return NULL; +} + +/* +** EvalWaveForm +** +** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly +*/ +extern float GetNoiseTime( int t ); //from tr_noise, returns 0 to 2 +static float EvalWaveForm( const waveForm_t *wf ) +{ + float *table; + + if ( wf->func == GF_NOISE ) { + return ( wf->base + R_NoiseGet4f( 0, 0, 0, ( backEnd.refdef.floatTime + wf->phase ) * wf->frequency ) * wf->amplitude ); + } else if (wf->func == GF_RAND) { + if( GetNoiseTime( backEnd.refdef.time + wf->phase ) <= wf->frequency ) { + return (wf->base + wf->amplitude); + } else { + return wf->base; + } + } + table = TableForFunc( wf->func ); + + return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency ); +} + +static float EvalWaveFormClamped( const waveForm_t *wf ) +{ + float glow = EvalWaveForm( wf ); + + if ( glow < 0 ) + { + return 0; + } + + if ( glow > 1 ) + { + return 1; + } + + return glow; +} + +/* +** RB_CalcStretchTexCoords +*/ +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *st ) +{ + float p; + texModInfo_t tmi; + + p = 1.0f / EvalWaveForm( wf ); + + tmi.matrix[0][0] = p; + tmi.matrix[1][0] = 0; + tmi.translate[0] = 0.5f - 0.5f * p; + + tmi.matrix[0][1] = 0; + tmi.matrix[1][1] = p; + tmi.translate[1] = 0.5f - 0.5f * p; + + RB_CalcTransformTexCoords( &tmi, st ); +} + +/* +==================================================================== + +DEFORMATIONS + +==================================================================== +*/ + +/* +======================== +RB_CalcDeformVertexes + +======================== +*/ +void RB_CalcDeformVertexes( deformStage_t *ds ) +{ + int i; + vec3_t offset; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float *table; + + if ( ds->deformationWave.frequency == 0 ) + { + scale = EvalWaveForm( &ds->deformationWave ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } + else + { + table = TableForFunc( ds->deformationWave.func ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread; + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase + off, + ds->deformationWave.frequency ); + + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } +} + +/* +========================= +RB_CalcDeformNormals + +Wiggle the normals for wavy environment mapping +========================= +*/ +void RB_CalcDeformNormals( deformStage_t *ds ) { + int i; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) { + scale = 0.98f; + scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 0 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 1 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + tess.shaderTime * ds->deformationWave.frequency ); + normal[ 2 ] += ds->deformationWave.amplitude * scale; + + VectorNormalizeFast( normal ); + } +} + +/* +======================== +RB_CalcBulgeVertexes + +======================== +*/ +void RB_CalcBulgeVertexes( deformStage_t *ds ) +{ + //Old bulge code: + /* + int i; + const float *st = ( const float * ) tess.texCoords[0]; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float now; + + now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 2 * NUM_TEX_COORDS, normal += 4 ) { + int off; + float scale; + + off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now ); + + scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight; + + xyz[0] += normal[0] * scale; + xyz[1] += normal[1] * scale; + xyz[2] += normal[2] * scale; + } + */ + + int i; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float scale; + + if ( ds->bulgeSpeed == 0.0f && ds->bulgeWidth == 0.0f ) + { + // We don't have a speed and width, so just use height to expand uniformly + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + xyz[0] += normal[0] * ds->bulgeHeight; + xyz[1] += normal[1] * ds->bulgeHeight; + xyz[2] += normal[2] * ds->bulgeHeight; + } + } + else + { + // I guess do some extra dumb stuff..the fact that it uses ST seems bad though because skin pages may be set up in certain ways that can cause + // very noticeable seams on sufaces ( like on the huge ion_cannon ). + const float *st = ( const float * ) tess.texCoords[0]; + float now; + int off; + + now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 2 * NUM_TEX_COORDS, normal += 4 ) + { + off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now ); + + scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight; + + xyz[0] += normal[0] * scale; + xyz[1] += normal[1] * scale; + xyz[2] += normal[2] * scale; + } + } +} + + +/* +====================== +RB_CalcMoveVertexes + +A deformation that can move an entire surface along a wave path +====================== +*/ +void RB_CalcMoveVertexes( deformStage_t *ds ) { + int i; + float *xyz; + float *table; + float scale; + vec3_t offset; + + table = TableForFunc( ds->deformationWave.func ); + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase, + ds->deformationWave.frequency ); + + VectorScale( ds->moveVector, scale, offset ); + + xyz = ( float * ) tess.xyz; + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + VectorAdd( xyz, offset, xyz ); + } +} + + +/* +============= +DeformText + +Change a polygon into a bunch of text polygons +============= +*/ +void DeformText( const char *text ) { + int i; + vec3_t origin, width, height; + int len; + int ch; + byte color[4]; + float bottom, top; + vec3_t mid; + + height[0] = 0; + height[1] = 0; + height[2] = -1; + CrossProduct( tess.normal[0], height, width ); + + // find the midpoint of the box + VectorClear( mid ); + bottom = 999999; + top = -999999; + for ( i = 0 ; i < 4 ; i++ ) { + VectorAdd( tess.xyz[i], mid, mid ); + if ( tess.xyz[i][2] < bottom ) { + bottom = tess.xyz[i][2]; + } + if ( tess.xyz[i][2] > top ) { + top = tess.xyz[i][2]; + } + } + VectorScale( mid, 0.25f, origin ); + + // determine the individual character size + height[0] = 0; + height[1] = 0; + height[2] = ( top - bottom ) * 0.5f; + + VectorScale( width, height[2] * -0.75f, width ); + + // determine the starting position + len = strlen( text ); + VectorMA( origin, (len-1), width, origin ); + + // clear the shader indexes + tess.numIndexes = 0; + tess.numVertexes = 0; + + color[0] = color[1] = color[2] = color[3] = 255; + + // draw each character + for ( i = 0 ; i < len ; i++ ) { + ch = text[i]; + ch &= 255; + + if ( ch != ' ' ) { + int row, col; + float frow, fcol, size; + + row = ch>>4; + col = ch&15; + + frow = row*0.0625f; + fcol = col*0.0625f; + size = 0.0625f; + + RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size ); + } + VectorMA( origin, -2, width, origin ); + } +} + +/* +================== +GlobalVectorToLocal +================== +*/ +static void GlobalVectorToLocal( const vec3_t in, vec3_t out ) { + out[0] = DotProduct( in, backEnd.ori.axis[0] ); + out[1] = DotProduct( in, backEnd.ori.axis[1] ); + out[2] = DotProduct( in, backEnd.ori.axis[2] ); +} + +/* +===================== +AutospriteDeform + +Assuming all the triangles for this shader are independant +quads, rebuild them as forward facing sprites +===================== +*/ +static void AutospriteDeform( void ) { + int i; + int oldVerts; + float *xyz; + vec3_t mid, delta; + float radius; + vec3_t left, up; + vec3_t leftDir, upDir; + + if ( tess.numVertexes & 3 ) { + Com_Printf (S_COLOR_YELLOW "Autosprite shader %s had odd vertex count", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + Com_Printf (S_COLOR_YELLOW "Autosprite shader %s had odd index count", tess.shader->name ); + } + + oldVerts = tess.numVertexes; + tess.numVertexes = 0; + tess.numIndexes = 0; + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.ori.axis[1], leftDir ); + GlobalVectorToLocal( backEnd.viewParms.ori.axis[2], upDir ); + } else { + VectorCopy( backEnd.viewParms.ori.axis[1], leftDir ); + VectorCopy( backEnd.viewParms.ori.axis[2], upDir ); + } + + for ( i = 0 ; i < oldVerts ; i+=4 ) { + // find the midpoint + xyz = tess.xyz[i]; + + mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]); + mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]); + mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]); + + VectorSubtract( xyz, mid, delta ); + radius = VectorLength( delta ) * 0.707f; // / sqrt(2) + + VectorScale( leftDir, radius, left ); + VectorScale( upDir, radius, up ); + + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + // compensate for scale in the axes if necessary + if ( backEnd.currentEntity->e.nonNormalizedAxes ) { + float axisLength; + axisLength = VectorLength( backEnd.currentEntity->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0f / axisLength; + } + VectorScale(left, axisLength, left); + VectorScale(up, axisLength, up); + } + + RB_AddQuadStamp( mid, left, up, tess.vertexColors[i] ); + } +} + + +/* +===================== +Autosprite2Deform + +Autosprite2 will pivot a rectangular quad along the center of its long axis +===================== +*/ +int edgeVerts[6][2] = { + { 0, 1 }, + { 0, 2 }, + { 0, 3 }, + { 1, 2 }, + { 1, 3 }, + { 2, 3 } +}; + +static void Autosprite2Deform( void ) { + int i, j, k; + int indexes; + float *xyz; + vec3_t forward; + + if ( tess.numVertexes & 3 ) { + Com_Printf (S_COLOR_YELLOW "Autosprite2 shader %s had odd vertex count", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + Com_Printf (S_COLOR_YELLOW "Autosprite2 shader %s had odd index count", tess.shader->name ); + } + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.ori.axis[0], forward ); + } else { + VectorCopy( backEnd.viewParms.ori.axis[0], forward ); + } + + // this is a lot of work for two triangles... + // we could precalculate a lot of it is an issue, but it would mess up + // the shader abstraction + for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i+=4, indexes+=6 ) { + float lengths[2]; + int nums[2]; + vec3_t mid[2]; + vec3_t major, minor; + float *v1, *v2; + + // find the midpoint + xyz = tess.xyz[i]; + + // identify the two shortest edges + nums[0] = nums[1] = 0; + lengths[0] = lengths[1] = 999999; + + for ( j = 0 ; j < 6 ; j++ ) { + float l; + vec3_t temp; + + v1 = xyz + 4 * edgeVerts[j][0]; + v2 = xyz + 4 * edgeVerts[j][1]; + + VectorSubtract( v1, v2, temp ); + + l = DotProduct( temp, temp ); + if ( l < lengths[0] ) { + nums[1] = nums[0]; + lengths[1] = lengths[0]; + nums[0] = j; + lengths[0] = l; + } else if ( l < lengths[1] ) { + nums[1] = j; + lengths[1] = l; + } + } + + for ( j = 0 ; j < 2 ; j++ ) { + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + mid[j][0] = 0.5f * (v1[0] + v2[0]); + mid[j][1] = 0.5f * (v1[1] + v2[1]); + mid[j][2] = 0.5f * (v1[2] + v2[2]); + } + + // find the vector of the major axis + VectorSubtract( mid[1], mid[0], major ); + + // cross this with the view direction to get minor axis + CrossProduct( major, forward, minor ); + VectorNormalize( minor ); + + // re-project the points + for ( j = 0 ; j < 2 ; j++ ) { + float l; + + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + l = 0.5 * sqrt( lengths[j] ); + + // we need to see which direction this edge + // is used to determine direction of projection + for ( k = 0 ; k < 5 ; k++ ) { + if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0] + && tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) { + break; + } + } + + if ( k == 5 ) { + VectorMA( mid[j], l, minor, v1 ); + VectorMA( mid[j], -l, minor, v2 ); + } else { + VectorMA( mid[j], -l, minor, v1 ); + VectorMA( mid[j], l, minor, v2 ); + } + } + } +} + + +/* +===================== +RB_DeformTessGeometry + +===================== +*/ +void RB_DeformTessGeometry( void ) { + int i; + deformStage_t *ds; + + for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) { + ds = tess.shader->deforms[ i ]; + + switch ( ds->deformation ) { + case DEFORM_NONE: + break; + case DEFORM_NORMALS: + RB_CalcDeformNormals( ds ); + break; + case DEFORM_WAVE: + RB_CalcDeformVertexes( ds ); + break; + case DEFORM_BULGE: + RB_CalcBulgeVertexes( ds ); + break; + case DEFORM_MOVE: + RB_CalcMoveVertexes( ds ); + break; + case DEFORM_PROJECTION_SHADOW: +/* + RB_ProjectionShadowDeform(); +*/ + break; + case DEFORM_AUTOSPRITE: + AutospriteDeform(); + break; + case DEFORM_AUTOSPRITE2: + Autosprite2Deform(); + break; + case DEFORM_TEXT0: + case DEFORM_TEXT1: + case DEFORM_TEXT2: + case DEFORM_TEXT3: + case DEFORM_TEXT4: + case DEFORM_TEXT5: + case DEFORM_TEXT6: + case DEFORM_TEXT7: + DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] ); + break; + } + } +} + +/* +==================================================================== + +COLORS + +==================================================================== +*/ + + +/* +** RB_CalcColorFromEntity +*/ +#ifdef _XBOX +void RB_CalcColorFromEntity( DWORD *dstColors ) +{ + int i; + DWORD *pColors = dstColors; + + if ( !backEnd.currentEntity ) + return; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = D3DCOLOR_RGBA((int)(backEnd.currentEntity->e.shaderRGBA[0]), + (int)(backEnd.currentEntity->e.shaderRGBA[1]), + (int)(backEnd.currentEntity->e.shaderRGBA[2]), + (int)(backEnd.currentEntity->e.shaderRGBA[3])); + } +} +#else +void RB_CalcColorFromEntity( unsigned char *dstColors ) +{ + int i; + int *pColors = ( int * ) dstColors; + int c; + + if ( !backEnd.currentEntity ) + return; + + c = * ( int * ) backEnd.currentEntity->e.shaderRGBA; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = c; + } +} +#endif // _XBOX + +/* +** RB_CalcColorFromOneMinusEntity +*/ +#ifdef _XBOX +void RB_CalcColorFromOneMinusEntity( DWORD *dstColors ) +{ + int i; + DWORD *pColors = dstColors; + unsigned char invModulate[3]; + + if ( !backEnd.currentEntity ) + return; + + invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0]; + invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1]; + invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2]; + invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3]; // this trashes alpha, but the AGEN block fixes it + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = D3DCOLOR_RGBA((int)invModulate[0], + (int)invModulate[1], + (int)invModulate[2], + (int)invModulate[3]); + } +} +#else +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ) +{ + int i; + int *pColors = ( int * ) dstColors; + unsigned char invModulate[3]; + int c; + + if ( !backEnd.currentEntity ) + return; + + invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0]; + invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1]; + invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2]; + invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3]; // this trashes alpha, but the AGEN block fixes it + + c = * ( int * ) invModulate; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = * ( int * ) invModulate; + } +} +#endif // _XBOX + +/* +** RB_CalcAlphaFromEntity +*/ +#ifdef _XBOX +void RB_CalcAlphaFromEntity( DWORD *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + for ( i = 0; i < tess.numVertexes; i++, dstColors ++ ) + { + DWORD rgb = (DWORD)((*dstColors) & 0x00ffffff); + *dstColors = rgb | ((backEnd.currentEntity->e.shaderRGBA[3] & 0xff) << 24); + } +} +#else +void RB_CalcAlphaFromEntity( unsigned char *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = backEnd.currentEntity->e.shaderRGBA[3]; + } +} +#endif + +/* +** RB_CalcAlphaFromOneMinusEntity +*/ +#ifdef _XBOX +void RB_CalcAlphaFromOneMinusEntity( DWORD *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + for ( i = 0; i < tess.numVertexes; i++, dstColors ++ ) + { + DWORD rgb = (DWORD)((*dstColors) & 0x00ffffff); + *dstColors = rgb | (((255 - backEnd.currentEntity->e.shaderRGBA[3]) & 0xff) << 24); + } +} +#else +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = 0xff - backEnd.currentEntity->e.shaderRGBA[3]; + } +} +#endif // _XBOX + +/* +** RB_CalcWaveColor +*/ +#ifdef _XBOX +void RB_CalcWaveColor( const waveForm_t *wf, DWORD *dstColors ) +{ + int i; + int v; + float glow; + DWORD *colors = dstColors; + byte color[4]; + + if ( wf->func == GF_NOISE ) { + glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( backEnd.refdef.floatTime + wf->phase ) * wf->frequency ) * wf->amplitude; + } else { + glow = EvalWaveForm( wf ) * tr.identityLight; + } + + if ( glow < 0 ) { + glow = 0; + } + else if ( glow > 1 ) { + glow = 1; + } + + v = myftol( 255 * glow ); + color[0] = color[1] = color[2] = v; + color[3] = 255; + + for ( i = 0; i < tess.numVertexes; i++, colors++ ) { + *colors = D3DCOLOR_RGBA(color[0], color[1], color[2], color[3]); + } +} +#else // _XBOX +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ) +{ + int i; + int v; + float glow; + int *colors = ( int * ) dstColors; + byte color[4]; + + + if ( wf->func == GF_NOISE ) { + glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude; + } else { + glow = EvalWaveForm( wf ) * tr.identityLight; + } + + if ( glow < 0 ) { + glow = 0; + } + else if ( glow > 1 ) { + glow = 1; + } + + v = myftol( 255 * glow ); + color[0] = color[1] = color[2] = v; + color[3] = 255; + v = *(int *)color; + + for ( i = 0; i < tess.numVertexes; i++, colors++ ) { + *colors = v; + } +} +#endif + +/* +** RB_CalcWaveAlpha +*/ +#ifdef _XBOX +void RB_CalcWaveAlpha( const waveForm_t *wf, DWORD *dstColors ) +{ + int i; + int v; + float glow; + + glow = EvalWaveFormClamped( wf ); + + v = 255 * glow; + + for ( i = 0; i < tess.numVertexes; i++, dstColors ++ ) + { + DWORD rgb = (DWORD)((*dstColors) & 0x00ffffff); + *dstColors = rgb | ((v & 0xff) << 24); + } +} +#else +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ) +{ + int i; + int v; + float glow; + + glow = EvalWaveFormClamped( wf ); + + v = 255 * glow; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + dstColors[3] = v; + } +} +#endif + +/* +** RB_CalcModulateColorsByFog +*/ +#ifdef _XBOX +void RB_CalcModulateColorsByFog( DWORD *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors ++ ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + DWORD a, r, g, b; + a = (*colors & 0xff000000) >> 24; + r = ((*colors & 0x00ff0000) >> 16) * f; + g = ((*colors & 0x0000ff00) >> 8) * f; + b = (*colors & 0x000000ff) * f; + *colors = (DWORD)((a << 24) | (r << 16) | (g << 8) | b); + } +} +#else +void RB_CalcModulateColorsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + } +} +#endif + +/* +** RB_CalcModulateAlphasByFog +*/ +#ifdef _XBOX +void RB_CalcModulateAlphasByFog( DWORD *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors ++ ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + DWORD rgb = *colors & 0x00ffffff; + DWORD alpha = ((*colors & 0xff000000) >> 24) * f; + *colors = (alpha << 24) | rgb; + } +} +#else +void RB_CalcModulateAlphasByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[3] *= f; + } +} +#endif + +/* +** RB_CalcModulateRGBAsByFog +*/ +#ifdef _XBOX +void RB_CalcModulateRGBAsByFog( DWORD *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors ++ ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + DWORD a, r, g, b; + a = ((*colors & 0xff000000) >> 24) * f; + r = ((*colors & 0x00ff0000) >> 16) * f; + g = ((*colors & 0x0000ff00) >> 8) * f; + b = (*colors & 0x000000ff) * f; + *colors = (DWORD)((a << 24) | (r << 16) | (g << 8) | b); + } +} +#else +void RB_CalcModulateRGBAsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + colors[3] *= f; + } +} +#endif + + +/* +==================================================================== + +TEX COORDS + +==================================================================== +*/ + +/* +======================== +RB_CalcFogTexCoords + +To do the clipped fog plane really correctly, we should use +projected textures, but I don't trust the drivers and it +doesn't fit our shader data. +======================== +*/ +void RB_CalcFogTexCoords( float *st ) { + int i; + float *v; + float s, t; + float eyeT; + qboolean eyeOutside; + fog_t *fog; + vec3_t localVec; + vec4_t fogDistanceVector, fogDepthVector; + + fog = tr.world->fogs + tess.fogNum; + + // all fogging distance is based on world Z units + VectorSubtract( backEnd.ori.origin, backEnd.viewParms.ori.origin, localVec ); +#ifdef _XBOX + fogDistanceVector[0] = backEnd.ori.modelMatrix[2]; + fogDistanceVector[1] = backEnd.ori.modelMatrix[6]; + fogDistanceVector[2] = backEnd.ori.modelMatrix[10]; +#else + fogDistanceVector[0] = -backEnd.ori.modelMatrix[2]; + fogDistanceVector[1] = -backEnd.ori.modelMatrix[6]; + fogDistanceVector[2] = -backEnd.ori.modelMatrix[10]; +#endif + fogDistanceVector[3] = DotProduct( localVec, backEnd.viewParms.ori.axis[0] ); + + // scale the fog vectors based on the fog's thickness + fogDistanceVector[0] *= fog->tcScale; + fogDistanceVector[1] *= fog->tcScale; + fogDistanceVector[2] *= fog->tcScale; + fogDistanceVector[3] *= fog->tcScale; + + // rotate the gradient vector for this orientation + if ( fog->hasSurface ) { + fogDepthVector[0] = fog->surface[0] * backEnd.ori.axis[0][0] + + fog->surface[1] * backEnd.ori.axis[0][1] + fog->surface[2] * backEnd.ori.axis[0][2]; + fogDepthVector[1] = fog->surface[0] * backEnd.ori.axis[1][0] + + fog->surface[1] * backEnd.ori.axis[1][1] + fog->surface[2] * backEnd.ori.axis[1][2]; + fogDepthVector[2] = fog->surface[0] * backEnd.ori.axis[2][0] + + fog->surface[1] * backEnd.ori.axis[2][1] + fog->surface[2] * backEnd.ori.axis[2][2]; + fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.ori.origin, fog->surface ); + + eyeT = DotProduct( backEnd.ori.viewOrigin, fogDepthVector ) + fogDepthVector[3]; + } else { + eyeT = 1; // non-surface fog always has eye inside + + fogDepthVector[0] = fogDepthVector[1] = fogDepthVector[2] = 0.0f; + fogDepthVector[3] = 1.0f; + } + + // see if the viewpoint is outside + // this is needed for clipping distance even for constant fog + + if ( eyeT < 0 ) { + eyeOutside = qtrue; + } else { + eyeOutside = qfalse; + } + + fogDistanceVector[3] += 1.0/512; + + // calculate density for each point + for (i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4) { + // calculate the length in fog + s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3]; + t = DotProduct( v, fogDepthVector ) + fogDepthVector[3]; + + // partially clipped fogs use the T axis + if ( eyeOutside ) { + if ( t < 1.0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 1.0/32 + 30.0/32 * t / ( t - eyeT ); // cut the distance at the fog plane + } + } else { + if ( t < 0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 31.0/32; + } + } + + st[0] = s; + st[1] = t; + st += 2; + } +} + + + +/* +** RB_CalcEnvironmentTexCoords +*/ +void RB_CalcEnvironmentTexCoords( float *st ) +{ + int i; + float *v, *normal; + vec3_t viewer, reflected; + float d; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + for (i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 ) + { + VectorSubtract (backEnd.ori.viewOrigin, v, viewer); + VectorNormalizeFast (viewer); + + d = DotProduct (normal, viewer); + + reflected[0] = normal[0]*2*d - viewer[0]; + reflected[1] = normal[1]*2*d - viewer[1]; + reflected[2] = normal[2]*2*d - viewer[2]; + + st[0] = 0.5 + reflected[1] * 0.5; + st[1] = 0.5 - reflected[2] * 0.5; + } +} + +/* +** RB_CalcTurbulentTexCoords +*/ +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st ) +{ + int i; + float now; + + now = ( wf->phase + tess.shaderTime * wf->frequency ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s + tr.sinTable[ ( ( int ) ( ( ( tess.xyz[i][0] + tess.xyz[i][2] )* 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + st[1] = t + tr.sinTable[ ( ( int ) ( ( tess.xyz[i][1] * 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + } +} + +/* +** RB_CalcScaleTexCoords +*/ +void RB_CalcScaleTexCoords( const float scale[2], float *st ) +{ + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] *= scale[0]; + st[1] *= scale[1]; + } +} + +/* +** RB_CalcScrollTexCoords +*/ +void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st ) +{ + int i; + float timeScale = tess.shaderTime; + float adjustedScrollS, adjustedScrollT; + + adjustedScrollS = scrollSpeed[0] * timeScale; + adjustedScrollT = scrollSpeed[1] * timeScale; + + // clamp so coordinates don't continuously get larger, causing problems + // with hardware limits + adjustedScrollS = adjustedScrollS - floor( adjustedScrollS ); + adjustedScrollT = adjustedScrollT - floor( adjustedScrollT ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] += adjustedScrollS; + st[1] += adjustedScrollT; + } +} + +/* +** RB_CalcTransformTexCoords +*/ +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st ) +{ + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s * tmi->matrix[0][0] + t * tmi->matrix[1][0] + tmi->translate[0]; + st[1] = s * tmi->matrix[0][1] + t * tmi->matrix[1][1] + tmi->translate[1]; + } +} + +/* +** RB_CalcRotateTexCoords +*/ +void RB_CalcRotateTexCoords( float degsPerSecond, float *st ) +{ + float timeScale = tess.shaderTime; + float degs; + int index; + float sinValue, cosValue; + texModInfo_t tmi; + + degs = -degsPerSecond * timeScale; + index = degs * ( FUNCTABLE_SIZE / 360.0f ); + + sinValue = tr.sinTable[ index & FUNCTABLE_MASK ]; + cosValue = tr.sinTable[ ( index + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ]; + + tmi.matrix[0][0] = cosValue; + tmi.matrix[1][0] = -sinValue; + tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue; + + tmi.matrix[0][1] = sinValue; + tmi.matrix[1][1] = cosValue; + tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue; + + RB_CalcTransformTexCoords( &tmi, st ); +} + + + + + + +#if id386 && !( (defined __linux__ || defined __FreeBSD__ ) && (defined __i386__ ) ) // rb010123 +#pragma warning (disable: 4035)//no return value +inline long myftol( float f ) { + static int tmp; + __asm fld f + __asm fistp tmp + __asm mov eax, tmp +} +#pragma warning (default: 4035) + +#endif + +/* +** RB_CalcSpecularAlpha +** +** Calculates specular coefficient and places it in the alpha channel +*/ +vec3_t lightOrigin = { -960, 1980, 96 }; // FIXME: track dynamically + +#ifdef _XBOX +void RB_CalcSpecularAlpha( DWORD *alphas ) { + int i; + float *v, *normal; + vec3_t viewer, reflected; + float l, d; + int a; + vec3_t lightDir; + int numVertexes; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas ++) { + float ilength; + + if (backEnd.currentEntity && + (backEnd.currentEntity->e.hModel||backEnd.currentEntity->e.ghoul2) ) //this is a model so we can use world lights instead fake light + { + VectorCopy (backEnd.currentEntity->lightDir, lightDir); + } else { + VectorSubtract( lightOrigin, v, lightDir ); + VectorNormalizeFast( lightDir ); + } + // calculate the specular color + d = 2 * DotProduct (normal, lightDir); + + // we don't optimize for the d < 0 case since this tends to + // cause visual artifacts such as faceted "snapping" + reflected[0] = normal[0]*d - lightDir[0]; + reflected[1] = normal[1]*d - lightDir[1]; + reflected[2] = normal[2]*d - lightDir[2]; + + VectorSubtract (backEnd.ori.viewOrigin, v, viewer); + ilength = Q_rsqrt( DotProduct( viewer, viewer ) ); + l = DotProduct (reflected, viewer); + l *= ilength; + + if (l < 0) { + a = 0; + } else { + l = l*l; + l = l*l; + a = l * 255; + if (a > 255) { + a = 255; + } + } + DWORD rgb = (DWORD)((*alphas) & 0x00ffffff); + + *alphas = rgb | (a & 0xff) << 24; + } +} +#else // _XBOX +void RB_CalcSpecularAlpha( unsigned char *alphas ) { + int i; + float *v, *normal; + vec3_t viewer, reflected; + float l, d; + int b; + vec3_t lightDir; + int numVertexes; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + alphas += 3; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4) { + float ilength; + + if (backEnd.currentEntity && + (backEnd.currentEntity->e.hModel||backEnd.currentEntity->e.ghoul2) ) //this is a model so we can use world lights instead fake light + { + VectorCopy (backEnd.currentEntity->lightDir, lightDir); + } else { + VectorSubtract( lightOrigin, v, lightDir ); + VectorNormalizeFast( lightDir ); + } + // calculate the specular color + d = 2 * DotProduct (normal, lightDir); + + // we don't optimize for the d < 0 case since this tends to + // cause visual artifacts such as faceted "snapping" + reflected[0] = normal[0]*d - lightDir[0]; + reflected[1] = normal[1]*d - lightDir[1]; + reflected[2] = normal[2]*d - lightDir[2]; + + VectorSubtract (backEnd.ori.viewOrigin, v, viewer); + ilength = Q_rsqrt( DotProduct( viewer, viewer ) ); + l = DotProduct (reflected, viewer); + l *= ilength; + + if (l < 0) { + b = 0; + } else { + l = l*l; + l = l*l; + b = l * 255; + if (b > 255) { + b = 255; + } + } + + *alphas = b; + } +} +#endif // _XBOX + +/* +** RB_CalcDiffuseColor +** +** The basic vertex lighting calc +*/ +#ifdef _XBOX +void RB_CalcDiffuseColor( DWORD *colors ) +{ + trRefEntity_t *ent; + + ent = backEnd.currentEntity; + + // Make sure to turn lighting on.... + qglEnable(GL_LIGHTING); + + qglLightfv(0, GL_AMBIENT, ent->ambientLight); + qglLightfv(0, GL_DIFFUSE, ent->directedLight); + + if(VectorLengthSquared(ent->lightDir) <= 0.0001f) + { + ent->lightDir[0] = 0.0f; + ent->lightDir[1] = 1.0f; + ent->lightDir[2] = 0.0f; + } + + qglLightfv(0, GL_SPOT_DIRECTION, ent->lightDir); + + /*if(VectorLengthSquared(ent->dlightDir) > 0.0f && ModelMem.inUI == false) + { + qglLightfv(1, GL_AMBIENT, ent->ambientLight); + qglLightfv(1, GL_DIFFUSE, ent->dynamicLight); + qglLightfv(1, GL_SPOT_DIRECTION, ent->dlightDir); + }*/ + + memset(colors, 0xffffffff, sizeof(DWORD) * tess.numVertexes); +} +#else +void RB_CalcDiffuseColor( unsigned char *colors ) +{ + int i, j; + float *v, *normal; + float incoming; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + int numVertexes; + + ent = backEnd.currentEntity; + ambientLightInt = ent->ambientLightInt; + VectorCopy( ent->ambientLight, ambientLight ); + VectorCopy( ent->directedLight, directedLight ); + VectorCopy( ent->lightDir, lightDir ); + + v = tess.xyz[0]; + normal = tess.normal[0]; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + *(int *)&colors[i*4] = ambientLightInt; + continue; + } + j = myftol( ambientLight[0] + incoming * directedLight[0] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+0] = j; + + j = myftol( ambientLight[1] + incoming * directedLight[1] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+1] = j; + + j = myftol( ambientLight[2] + incoming * directedLight[2] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+2] = j; + + colors[i*4+3] = 255; + } +} +#endif + +/* +** RB_CalcDiffuseColorEntity +** +** The basic vertex lighting calc * Entity Color +*/ +#ifdef _XBOX +void RB_CalcDiffuseEntityColor( DWORD *colors ) +{ + if ( !backEnd.currentEntity ) + {//error, use the normal lighting + RB_CalcDiffuseColor(colors); + } + + trRefEntity_t *ent; + + ent = backEnd.currentEntity; + + // Make sure to turn lighting on.... + qglEnable(GL_LIGHTING); + + // Modulate ambient by entity color: + vec3_t ambient; + ambient[0] = ent->ambientLight[0] * (ent->e.shaderRGBA[0]/255.0); + ambient[1] = ent->ambientLight[1] * (ent->e.shaderRGBA[1]/255.0); + ambient[2] = ent->ambientLight[2] * (ent->e.shaderRGBA[2]/255.0); + qglLightfv(0, GL_AMBIENT, ambient); + qglLightfv(0, GL_DIFFUSE, ent->directedLight); + + VectorNormalize(ent->lightDir); + + if(VectorLengthSquared(ent->lightDir) <= 0.0001f) + { + ent->lightDir[0] = 0.0f; + ent->lightDir[1] = 1.0f; + ent->lightDir[2] = 0.0f; + } + + qglLightfv(0, GL_SPOT_DIRECTION, ent->lightDir); + + /*if(VectorLengthSquared(ent->dlightDir) > 0.0f && ModelMem.inUI == false) + { + qglLightfv(1, GL_AMBIENT, ent->ambientLight); + qglLightfv(1, GL_DIFFUSE, ent->dynamicLight); + qglLightfv(1, GL_SPOT_DIRECTION, ent->dlightDir); + }*/ + + DWORD color = D3DCOLOR_RGBA(backEnd.currentEntity->e.shaderRGBA[0], + backEnd.currentEntity->e.shaderRGBA[1], + backEnd.currentEntity->e.shaderRGBA[2], + backEnd.currentEntity->e.shaderRGBA[3]); + + memset(colors, color, sizeof(DWORD) * tess.numVertexes); +} +#else +void RB_CalcDiffuseEntityColor( unsigned char *colors ) +{ + int i; + float *v, *normal; + float incoming; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + int numVertexes; + float j,r,g,b; + + if ( !backEnd.currentEntity ) + {//error, use the normal lighting + RB_CalcDiffuseColor(colors); + } + + ent = backEnd.currentEntity; + VectorCopy( ent->ambientLight, ambientLight ); + VectorCopy( ent->directedLight, directedLight ); + VectorCopy( ent->lightDir, lightDir ); + + r = backEnd.currentEntity->e.shaderRGBA[0]/255.0f; + g = backEnd.currentEntity->e.shaderRGBA[1]/255.0f; + b = backEnd.currentEntity->e.shaderRGBA[2]/255.0f; + + ((byte *)&ambientLightInt)[0] = myftol( r*ent->ambientLight[0] ); + ((byte *)&ambientLightInt)[1] = myftol( g*ent->ambientLight[1] ); + ((byte *)&ambientLightInt)[2] = myftol( b*ent->ambientLight[2] ); + ((byte *)&ambientLightInt)[3] = backEnd.currentEntity->e.shaderRGBA[3]; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + numVertexes = tess.numVertexes; + + for ( i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) + { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + *(int *)&colors[i*4] = ambientLightInt; + continue; + } + j = ( ambientLight[0] + incoming * directedLight[0] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+0] = myftol(j*r); + + j = ( ambientLight[1] + incoming * directedLight[1] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+1] = myftol(j*g); + + j = ( ambientLight[2] + incoming * directedLight[2] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+2] = myftol(j*b); + + colors[i*4+3] = backEnd.currentEntity->e.shaderRGBA[3]; + } +} +#endif + +//--------------------------------------------------------- +#ifdef _XBOX +void RB_CalcDisintegrateColors( DWORD *colors ) +{ + int i, numVertexes; + float dis, threshold; + float *v; + vec3_t temp; + refEntity_t *ent; + DWORD rgb; + + ent = &backEnd.currentEntity->e; + v = tess.xyz[0]; + + // calculate the burn threshold at the given time, anything that passes the threshold will get burnt + threshold = (backEnd.refdef.time - ent->endTime) * 0.045f; // endTime is really the start time, maybe I should just use a completely meaningless substitute? + + numVertexes = tess.numVertexes; + + if ( ent->renderfx & RF_DISINTEGRATE1 ) + { + // this handles the blacken and fading out of the regular player model + for ( i = 0 ; i < numVertexes ; i++, v += 4 ) + { + rgb = colors[i] & 0x00ffffff; + + VectorSubtract( backEnd.currentEntity->e.oldorigin, v, temp ); + + dis = VectorLengthSquared( temp ); + + if ( dis < threshold * threshold ) + { + // completely disintegrated + colors[i] = rgb | (0x00 << 24); + } + else if ( dis < threshold * threshold + 60 ) + { + // blacken before fading out + colors[i] = D3DCOLOR_RGBA(0x00, 0x00, 0x00, 0xff); + } + else if ( dis < threshold * threshold + 150 ) + { + // darken more + colors[i] = D3DCOLOR_RGBA(0x6f, 0x6f, 0x6f, 0xff); + } + else if ( dis < threshold * threshold + 180 ) + { + // darken at edge of burn + colors[i] = D3DCOLOR_RGBA(0xaf, 0xaf, 0xaf, 0xff); + } + else + { + // not burning at all yet + colors[i] = D3DCOLOR_RGBA(0xff, 0xff, 0xff, 0xff); + } + } + } + else if ( ent->renderfx & RF_DISINTEGRATE2 ) + { + // this handles the glowing, burning bit that scales away from the model + for ( i = 0 ; i < numVertexes ; i++, v += 4 ) + { + VectorSubtract( backEnd.currentEntity->e.oldorigin, v, temp ); + + dis = VectorLengthSquared( temp ); + + if ( dis < threshold * threshold ) + { + // done burning + colors[i] = D3DCOLOR_RGBA(0x00, 0x00, 0x00, 0x00); + } + else + { + // still full burn + colors[i] = D3DCOLOR_RGBA(0xff, 0xff, 0xff, 0xff); + } + } + } +} +#else // _XBOX +void RB_CalcDisintegrateColors( unsigned char *colors ) +{ + int i, numVertexes; + float dis, threshold; + float *v; + vec3_t temp; + refEntity_t *ent; + + ent = &backEnd.currentEntity->e; + v = tess.xyz[0]; + + // calculate the burn threshold at the given time, anything that passes the threshold will get burnt + threshold = (backEnd.refdef.time - ent->endTime) * 0.045f; // endTime is really the start time, maybe I should just use a completely meaningless substitute? + + numVertexes = tess.numVertexes; + + if ( ent->renderfx & RF_DISINTEGRATE1 ) + { + // this handles the blacken and fading out of the regular player model + for ( i = 0 ; i < numVertexes ; i++, v += 4 ) + { + VectorSubtract( backEnd.currentEntity->e.oldorigin, v, temp ); + + dis = VectorLengthSquared( temp ); + + if ( dis < threshold * threshold ) + { + // completely disintegrated + colors[i*4+3] = 0x00; + } + else if ( dis < threshold * threshold + 60 ) + { + // blacken before fading out + colors[i*4+0] = 0x0; + colors[i*4+1] = 0x0; + colors[i*4+2] = 0x0; + colors[i*4+3] = 0xff; + } + else if ( dis < threshold * threshold + 150 ) + { + // darken more + colors[i*4+0] = 0x6f; + colors[i*4+1] = 0x6f; + colors[i*4+2] = 0x6f; + colors[i*4+3] = 0xff; + } + else if ( dis < threshold * threshold + 180 ) + { + // darken at edge of burn + colors[i*4+0] = 0xaf; + colors[i*4+1] = 0xaf; + colors[i*4+2] = 0xaf; + colors[i*4+3] = 0xff; + } + else + { + // not burning at all yet + colors[i*4+0] = 0xff; + colors[i*4+1] = 0xff; + colors[i*4+2] = 0xff; + colors[i*4+3] = 0xff; + } + } + } + else if ( ent->renderfx & RF_DISINTEGRATE2 ) + { + // this handles the glowing, burning bit that scales away from the model + for ( i = 0 ; i < numVertexes ; i++, v += 4 ) + { + VectorSubtract( backEnd.currentEntity->e.oldorigin, v, temp ); + + dis = VectorLengthSquared( temp ); + + if ( dis < threshold * threshold ) + { + // done burning + colors[i*4+0] = 0x00; + colors[i*4+1] = 0x00; + colors[i*4+2] = 0x00; + colors[i*4+3] = 0x00; + } + else + { + // still full burn + colors[i*4+0] = 0xff; + colors[i*4+1] = 0xff; + colors[i*4+2] = 0xff; + colors[i*4+3] = 0xff; + } + } + } +} +#endif // _XBOX + +//--------------------------------------------------------- +void RB_CalcDisintegrateVertDeform( void ) +{ + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float scale; + vec3_t temp; + + if ( backEnd.currentEntity->e.renderfx & RF_DISINTEGRATE2 ) + { + float threshold = (backEnd.refdef.time - backEnd.currentEntity->e.endTime) * 0.045f; + + for ( int i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + VectorSubtract( backEnd.currentEntity->e.oldorigin, xyz, temp ); + + scale = VectorLengthSquared( temp ); + + if ( scale < threshold * threshold ) + { + xyz[0] += normal[0] * 2.0f; + xyz[1] += normal[1] * 2.0f; + xyz[2] += normal[2] * 0.5f; + } + else if ( scale < threshold * threshold + 50 ) + { + xyz[0] += normal[0] * 1.0f; + xyz[1] += normal[1] * 1.0f; +// xyz[2] += normal[2] * 1; + } + } + } +} diff --git a/codemp/renderer/tr_shader.cpp b/codemp/renderer/tr_shader.cpp new file mode 100644 index 0000000..30ae3a8 --- /dev/null +++ b/codemp/renderer/tr_shader.cpp @@ -0,0 +1,4351 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +// tr_shader.c -- this file deals with the parsing and definition of shaders +int CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); //cl_cin + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Vertex and Pixel Shader definitions. - AReis +/***********************************************************************************************************/ +// This vertex shader basically passes through most values and calculates no lighting. The only +// unusual thing it does is add the inputed texel offsets to all four texture units (this allows +// nearest neighbor pixel peeking). +const unsigned char g_strGlowVShaderARB[] = +{ + "!!ARBvp1.0\ + \ + # Input.\n\ + ATTRIB iPos = vertex.position;\ + ATTRIB iColor = vertex.color;\ + ATTRIB iTex0 = vertex.texcoord[0];\ + ATTRIB iTex1 = vertex.texcoord[1];\ + ATTRIB iTex2 = vertex.texcoord[2];\ + ATTRIB iTex3 = vertex.texcoord[3];\ + \ + # Output.\n\ + OUTPUT oPos = result.position;\ + OUTPUT oColor = result.color;\ + OUTPUT oTex0 = result.texcoord[0];\ + OUTPUT oTex1 = result.texcoord[1];\ + OUTPUT oTex2 = result.texcoord[2];\ + OUTPUT oTex3 = result.texcoord[3];\ + \ + # Constants.\n\ + PARAM ModelViewProj[4]= { state.matrix.mvp };\ + PARAM TexelOffset0 = program.env[0];\ + PARAM TexelOffset1 = program.env[1];\ + PARAM TexelOffset2 = program.env[2];\ + PARAM TexelOffset3 = program.env[3];\ + \ + # Main.\n\ + DP4 oPos.x, ModelViewProj[0], iPos;\ + DP4 oPos.y, ModelViewProj[1], iPos;\ + DP4 oPos.z, ModelViewProj[2], iPos;\ + DP4 oPos.w, ModelViewProj[3], iPos;\ + MOV oColor, iColor;\ + # Notice the optimization of using one texture coord instead of all four.\n\ + ADD oTex0, iTex0, TexelOffset0;\ + ADD oTex1, iTex0, TexelOffset1;\ + ADD oTex2, iTex0, TexelOffset2;\ + ADD oTex3, iTex0, TexelOffset3;\ + \ + END" +}; + +// This Pixel Shader loads four texture units and adds them all together (with a modifier +// multiplied to each in the process). The final output is r0 = t0 + t1 + t2 + t3. +const unsigned char g_strGlowPShaderARB[] = +{ + "!!ARBfp1.0\ + \ + # Input.\n\ + ATTRIB iColor = fragment.color.primary;\ + \ + # Output.\n\ + OUTPUT oColor = result.color;\ + \ + # Constants.\n\ + PARAM Weight = program.env[0];\ + TEMP t0;\ + TEMP t1;\ + TEMP t2;\ + TEMP t3;\ + TEMP r0;\ + \ + # Main.\n\ + TEX t0, fragment.texcoord[0], texture[0], RECT;\ + TEX t1, fragment.texcoord[1], texture[1], RECT;\ + TEX t2, fragment.texcoord[2], texture[2], RECT;\ + TEX t3, fragment.texcoord[3], texture[3], RECT;\ + \ + MUL r0, t0, Weight;\ + MAD r0, t1, Weight, r0;\ + MAD r0, t2, Weight, r0;\ + MAD r0, t3, Weight, r0;\ + \ + MOV oColor, r0;\ + \ + END" +}; +/***********************************************************************************************************/ + + +//static char *s_shaderText; +extern char *shaderText; // ONLY ONE FUCKING COPY OF THIS FUCKING SHIT! + +// the shader is parsed into these global variables, then copied into +// dynamically allocated memory if it is valid. +static shaderStage_t stages[MAX_SHADER_STAGES]; +static shader_t shader; +static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS]; +static qboolean deferLoad; + +#define FILE_HASH_SIZE 1024 +static shader_t* hashTable[FILE_HASH_SIZE]; + +#define MAX_SHADERTEXT_HASH 2048 +static char **shaderTextHashTable[MAX_SHADERTEXT_HASH] = { 0 }; + +void KillTheShaderHashTable(void) +{ + memset(shaderTextHashTable, 0, sizeof(shaderTextHashTable)); +} + +qboolean ShaderHashTableExists(void) +{ + if (shaderTextHashTable[0]) + { + return qtrue; + } + return qfalse; +} + +const short lightmapsNone[MAXLIGHTMAPS] = +{ + LIGHTMAP_NONE, + LIGHTMAP_NONE, + LIGHTMAP_NONE, + LIGHTMAP_NONE +}; + +const short lightmaps2d[MAXLIGHTMAPS] = +{ + LIGHTMAP_2D, + LIGHTMAP_2D, + LIGHTMAP_2D, + LIGHTMAP_2D +}; + +const short lightmapsVertex[MAXLIGHTMAPS] = +{ + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX +}; + +const short lightmapsFullBright[MAXLIGHTMAPS] = +{ + LIGHTMAP_WHITEIMAGE, + LIGHTMAP_WHITEIMAGE, + LIGHTMAP_WHITEIMAGE, + LIGHTMAP_WHITEIMAGE +}; + +const byte stylesDefault[MAXLIGHTMAPS] = +{ + LS_NORMAL, + LS_LSNONE, + LS_LSNONE, + LS_LSNONE +}; + +/* +Ghoul2 Insert Start +*/ + +/* +=============== +R_CreateExtendedName + + Creates a unique shader name taking into account lightstyles +=============== +*/ +//rwwRMG - added +void R_CreateExtendedName(char *extendedName, const char *name, const short *lightmapIndex, const byte *styles) +{ + int i; + + // Set the basename + COM_StripExtension( name, extendedName ); + + // Add in lightmaps + if(lightmapIndex && styles) + { + if(lightmapIndex == lightmapsNone) + { + strcat(extendedName, "_nolightmap"); + } + else if(lightmapIndex == lightmaps2d) + { + strcat(extendedName, "_2d"); + } + else if(lightmapIndex == lightmapsVertex) + { + strcat(extendedName, "_vertex"); + } + else if(lightmapIndex == lightmapsFullBright) + { + strcat(extendedName, "_fullbright"); + } + else + { + for(i = 0; (i < 4) && (styles[i] != 255); i++) + { + switch(lightmapIndex[i]) + { + case LIGHTMAP_NONE: + strcat(extendedName, va("_style(%d,none)", styles[i])); + break; + case LIGHTMAP_2D: + strcat(extendedName, va("_style(%d,2d)", styles[i])); + break; + case LIGHTMAP_BY_VERTEX: + strcat(extendedName, va("_style(%d,vert)", styles[i])); + break; + case LIGHTMAP_WHITEIMAGE: + strcat(extendedName, va("_style(%d,fb)", styles[i])); + break; + default: + strcat(extendedName, va("_style(%d,%d)", styles[i], lightmapIndex[i])); + break; + } + } + } + } +} + +/* +Ghoul2 Insert End +*/ + +static void ClearGlobalShader(void) +{ + int i; + + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + stages[i].mGLFogColorOverride = GLFOGOVERRIDE_NONE; + } + + shader.contentFlags = CONTENTS_SOLID | CONTENTS_OPAQUE; +} + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname, const int size ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower((unsigned char)fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + if (letter == PATH_SEP) letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + hash &= (size-1); + return hash; +} + +void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh, *sh2; + qhandle_t h; + + sh = R_FindShaderByName( shaderName ); + if (sh == NULL || sh == tr.defaultShader) { + h = RE_RegisterShaderLightMap(shaderName, lightmapsNone, stylesDefault); + sh = R_GetShaderByHandle(h); + } + if (sh == NULL || sh == tr.defaultShader) { + Com_Printf (S_COLOR_YELLOW "WARNING: R_RemapShader: shader %s not found\n", shaderName ); + return; + } + + sh2 = R_FindShaderByName( newShaderName ); + if (sh2 == NULL || sh2 == tr.defaultShader) { + h = RE_RegisterShaderLightMap(newShaderName, lightmapsNone, stylesDefault); + sh2 = R_GetShaderByHandle(h); + } + + if (sh2 == NULL || sh2 == tr.defaultShader) { + Com_Printf (S_COLOR_YELLOW "WARNING: R_RemapShader: new shader %s not found\n", newShaderName ); + return; + } + + // remap all the shaders with the given name + // even tho they might have different lightmaps + COM_StripExtension( shaderName, strippedName ); + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + for (sh = hashTable[hash]; sh; sh = sh->next) { + if (Q_stricmp(sh->name, strippedName) == 0) { + if (sh != sh2) { + sh->remappedShader = sh2; + } else { + sh->remappedShader = NULL; + } + } + } + if (timeOffset) { + sh2->timeOffset = atof(timeOffset); + } +} + +/* +=============== +ParseVector +=============== +*/ +qboolean ParseVector( const char **text, int count, float *v ) { + char *token; + int i; + + // FIXME: spaces are currently required after parens, should change parseext... + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "(" ) ) { +#ifndef FINAL_BUILD + Com_Printf (S_COLOR_YELLOW "WARNING: missing parenthesis in shader '%s'\n", shader.name ); +#endif + return qfalse; + } + + for ( i = 0 ; i < count ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + Com_Printf (S_COLOR_YELLOW "WARNING: missing vector element in shader '%s'\n", shader.name ); + return qfalse; + } + v[i] = atof( token ); + } + + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, ")" ) ) { +#ifndef FINAL_BUILD + Com_Printf (S_COLOR_YELLOW "WARNING: missing parenthesis in shader '%s'\n", shader.name ); +#endif + return qfalse; + } + + return qtrue; +} + + +/* +=============== +NameToAFunc +=============== +*/ +static unsigned NameToAFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "GT0" ) ) + { + return GLS_ATEST_GT_0; + } + else if ( !Q_stricmp( funcname, "LT128" ) ) + { + return GLS_ATEST_LT_80; + } + else if ( !Q_stricmp( funcname, "GE128" ) ) + { + return GLS_ATEST_GE_80; + } + else if ( !Q_stricmp( funcname, "GE192" ) ) + { + return GLS_ATEST_GE_C0; + } + + Com_Printf (S_COLOR_YELLOW "WARNING: invalid alphaFunc name '%s' in shader '%s'\n", funcname, shader.name ); + return 0; +} + + +/* +=============== +NameToSrcBlendMode +=============== +*/ +static int NameToSrcBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_SRCBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_SRCBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_DST_COLOR" ) ) + { + return GLS_SRCBLEND_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_COLOR" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + return GLS_SRCBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA_SATURATE" ) ) + { + return GLS_SRCBLEND_ALPHA_SATURATE; + } + + Com_Printf (S_COLOR_YELLOW "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_SRCBLEND_ONE; +} + +/* +=============== +NameToDstBlendMode +=============== +*/ +static int NameToDstBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_DSTBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_DSTBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + return GLS_DSTBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_SRC_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR; + } + + Com_Printf (S_COLOR_YELLOW "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_DSTBLEND_ONE; +} + +/* +=============== +NameToGenFunc +=============== +*/ +static genFunc_t NameToGenFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "sin" ) ) + { + return GF_SIN; + } + else if ( !Q_stricmp( funcname, "square" ) ) + { + return GF_SQUARE; + } + else if ( !Q_stricmp( funcname, "triangle" ) ) + { + return GF_TRIANGLE; + } + else if ( !Q_stricmp( funcname, "sawtooth" ) ) + { + return GF_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "inversesawtooth" ) ) + { + return GF_INVERSE_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "noise" ) ) + { + return GF_NOISE; + } + else if ( !Q_stricmp( funcname, "random" ) ) + { + return GF_RAND; + } + + Com_Printf (S_COLOR_YELLOW "WARNING: invalid genfunc name '%s' in shader '%s'\n", funcname, shader.name ); + return GF_SIN; +} + + +/* +=================== +ParseWaveForm +=================== +*/ +static void ParseWaveForm( const char **text, waveForm_t *wave ) +{ + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->func = NameToGenFunc( token ); + + // BASE, AMP, PHASE, FREQ + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->frequency = atof( token ); +} + + +/* +=================== +ParseTexMod +=================== +*/ +static void ParseTexMod( const char *_text, shaderStage_t *stage ) +{ + const char *token; + const char **text = &_text; + texModInfo_t *tmi; + + if ( stage->bundle[0].numTexMods == TR_MAX_TEXMODS ) { + Com_Error( ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name ); + return; + } + + tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; + stage->bundle[0].numTexMods++; + + token = COM_ParseExt( text, qfalse ); + + // + // turb + // + if ( !Q_stricmp( token, "turb" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing tcMod turb parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_TURBULENT; + } + // + // scale + // + else if ( !Q_stricmp( token, "scale" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); //scale unioned + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); //scale unioned + tmi->type = TMOD_SCALE; + } + // + // scroll + // + else if ( !Q_stricmp( token, "scroll" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); //scroll unioned + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); //scroll unioned + tmi->type = TMOD_SCROLL; + } + // + // stretch + // + else if ( !Q_stricmp( token, "stretch" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.func = NameToGenFunc( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_STRETCH; + } + // + // transform + // + else if ( !Q_stricmp( token, "transform" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); + + tmi->type = TMOD_TRANSFORM; + } + // + // rotate + // + else if ( !Q_stricmp( token, "rotate" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing tcMod rotate parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0]= atof( token ); //rotateSpeed unioned + tmi->type = TMOD_ROTATE; + } + // + // entityTranslate + // + else if ( !Q_stricmp( token, "entityTranslate" ) ) + { + tmi->type = TMOD_ENTITY_TRANSLATE; + } + else + { + Com_Printf (S_COLOR_YELLOW "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name ); + } +} + + + +/* +/////===== Part of the VERTIGON system =====///// +=================== +ParseSurfaceSprites +=================== +*/ +// surfaceSprites +// +// NOTE: This parsing function used to be 12 pages long and very complex. The new version of surfacesprites +// utilizes optional parameters parsed in ParseSurfaceSpriteOptional. +static void ParseSurfaceSprites( const char *_text, shaderStage_t *stage ) +{ + const char *token; + const char **text = &_text; + float width, height, density, fadedist; + int sstype=SURFSPRITE_NONE; + + // + // spritetype + // + token = COM_ParseExt( text, qfalse ); + + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfaceSprites params in shader '%s'\n", shader.name ); + return; + } + + if (!Q_stricmp(token, "vertical")) + { + sstype = SURFSPRITE_VERTICAL; + } + else if (!Q_stricmp(token, "oriented")) + { + sstype = SURFSPRITE_ORIENTED; + } + else if (!Q_stricmp(token, "effect")) + { + sstype = SURFSPRITE_EFFECT; + } + else + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid type in shader '%s'\n", shader.name ); + return; + } + + // + // width + // + token = COM_ParseExt( text, qfalse ); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfaceSprites params in shader '%s'\n", shader.name ); + return; + } + width=atof(token); + if (width <= 0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid width in shader '%s'\n", shader.name ); + return; + } + + // + // height + // + token = COM_ParseExt( text, qfalse ); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfaceSprites params in shader '%s'\n", shader.name ); + return; + } + height=atof(token); + if (height <= 0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid height in shader '%s'\n", shader.name ); + return; + } + + // + // density + // + token = COM_ParseExt( text, qfalse ); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfaceSprites params in shader '%s'\n", shader.name ); + return; + } + density=atof(token); + if (density <= 0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid density in shader '%s'\n", shader.name ); + return; + } + + // + // fadedist + // + token = COM_ParseExt( text, qfalse ); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfaceSprites params in shader '%s'\n", shader.name ); + return; + } + fadedist=atof(token); + if (fadedist < 32) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid fadedist (%f < 32) in shader '%s'\n", fadedist, shader.name ); + return; + } + + if (!stage->ss) + { + stage->ss = (surfaceSprite_t *)Hunk_Alloc( sizeof( surfaceSprite_t ), h_low ); + } + + // These are all set by the command lines. + stage->ss->surfaceSpriteType = sstype; + stage->ss->width = width; + stage->ss->height = height; + stage->ss->density = density; + stage->ss->fadeDist = fadedist; + +#ifdef _XBOX + shader.needsNormal = true; +#endif + + // These are defaults that can be overwritten. + stage->ss->fadeMax = fadedist*1.33; + stage->ss->fadeScale = 0.0; + stage->ss->wind = 0.0; + stage->ss->windIdle = 0.0; + stage->ss->variance[0] = 0.0; + stage->ss->variance[1] = 0.0; + stage->ss->facing = SURFSPRITE_FACING_NORMAL; + + // A vertical parameter that needs a default regardless + stage->ss->vertSkew; + + // These are effect parameters that need defaults nonetheless. + stage->ss->fxDuration = 1000; // 1 second + stage->ss->fxGrow[0] = 0.0; + stage->ss->fxGrow[1] = 0.0; + stage->ss->fxAlphaStart = 1.0; + stage->ss->fxAlphaEnd = 0.0; +} + + + + +/* +/////===== Part of the VERTIGON system =====///// +=========================== +ParseSurfaceSpritesOptional +=========================== +*/ +// +// ssFademax +// ssFadescale +// ssVariance +// ssHangdown +// ssAnyangle +// ssFaceup +// ssWind +// ssWindIdle +// ssVertSkew +// ssFXDuration +// ssFXGrow +// ssFXAlphaRange +// ssFXWeather +// +// Optional parameters that will override the defaults set in the surfacesprites command above. +// +static void ParseSurfaceSpritesOptional( const char *param, const char *_text, shaderStage_t *stage ) +{ + const char *token; + const char **text = &_text; + float value; + + if (!stage->ss) + { + stage->ss = (surfaceSprite_t *)Hunk_Alloc( sizeof( surfaceSprite_t ), h_low ); + } + // + // fademax + // + if (!Q_stricmp(param, "ssFademax")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite fademax in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value <= stage->ss->fadeDist) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite fademax (%.2f <= fadeDist(%.2f)) in shader '%s'\n", value, stage->ss->fadeDist, shader.name ); + return; + } + stage->ss->fadeMax=value; + return; + } + + // + // fadescale + // + if (!Q_stricmp(param, "ssFadescale")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite fadescale in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + stage->ss->fadeScale=value; + return; + } + + // + // variance + // + if (!Q_stricmp(param, "ssVariance")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite variance width in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite variance width in shader '%s'\n", shader.name ); + return; + } + stage->ss->variance[0]=value; + + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite variance height in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite variance height in shader '%s'\n", shader.name ); + return; + } + stage->ss->variance[1]=value; + return; + } + + // + // hangdown + // + if (!Q_stricmp(param, "ssHangdown")) + { + if (stage->ss->facing != SURFSPRITE_FACING_NORMAL) + { + Com_Printf (S_COLOR_YELLOW "WARNING: Hangdown facing overrides previous facing in shader '%s'\n", shader.name ); + return; + } + stage->ss->facing=SURFSPRITE_FACING_DOWN; + return; + } + + // + // anyangle + // + if (!Q_stricmp(param, "ssAnyangle")) + { + if (stage->ss->facing != SURFSPRITE_FACING_NORMAL) + { + Com_Printf (S_COLOR_YELLOW "WARNING: Anyangle facing overrides previous facing in shader '%s'\n", shader.name ); + return; + } + stage->ss->facing=SURFSPRITE_FACING_ANY; + return; + } + + // + // faceup + // + if (!Q_stricmp(param, "ssFaceup")) + { + if (stage->ss->facing != SURFSPRITE_FACING_NORMAL) + { + Com_Printf (S_COLOR_YELLOW "WARNING: Faceup facing overrides previous facing in shader '%s'\n", shader.name ); + return; + } + stage->ss->facing=SURFSPRITE_FACING_UP; + return; + } + + // + // wind + // + if (!Q_stricmp(param, "ssWind")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite wind in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0.0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite wind in shader '%s'\n", shader.name ); + return; + } + stage->ss->wind=value; + if (stage->ss->windIdle <= 0) + { // Also override the windidle, it usually is the same as wind + stage->ss->windIdle = value; + } + return; + } + + // + // windidle + // + if (!Q_stricmp(param, "ssWindidle")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite windidle in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0.0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite windidle in shader '%s'\n", shader.name ); + return; + } + stage->ss->windIdle=value; + return; + } + + // + // vertskew + // + if (!Q_stricmp(param, "ssVertskew")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite vertskew in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0.0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite vertskew in shader '%s'\n", shader.name ); + return; + } + stage->ss->vertSkew=value; + return; + } + + // + // fxduration + // + if (!Q_stricmp(param, "ssFXDuration")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite duration in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value <= 0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite duration in shader '%s'\n", shader.name ); + return; + } + stage->ss->fxDuration=value; + return; + } + + // + // fxgrow + // + if (!Q_stricmp(param, "ssFXGrow")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite grow width in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite grow width in shader '%s'\n", shader.name ); + return; + } + stage->ss->fxGrow[0]=value; + + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite grow height in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite grow height in shader '%s'\n", shader.name ); + return; + } + stage->ss->fxGrow[1]=value; + return; + } + + // + // fxalpharange + // + if (!Q_stricmp(param, "ssFXAlphaRange")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite fxalpha start in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0 || value > 1.0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite fxalpha start in shader '%s'\n", shader.name ); + return; + } + stage->ss->fxAlphaStart=value; + + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing surfacesprite fxalpha end in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0 || value > 1.0) + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid surfacesprite fxalpha end in shader '%s'\n", shader.name ); + return; + } + stage->ss->fxAlphaEnd=value; + return; + } + + // + // fxweather + // + if (!Q_stricmp(param, "ssFXWeather")) + { + if (stage->ss->surfaceSpriteType != SURFSPRITE_EFFECT) + { + Com_Printf (S_COLOR_YELLOW "WARNING: weather applied to non-effect surfacesprite in shader '%s'\n", shader.name ); + return; + } + stage->ss->surfaceSpriteType = SURFSPRITE_WEATHERFX; + return; + } + + // + // invalid ss command. + // + Com_Printf (S_COLOR_YELLOW "WARNING: invalid optional surfacesprite param '%s' in shader '%s'\n", param, shader.name ); + return; +} + + +/* +=================== +ParseStage +=================== +*/ +static qboolean ParseStage( shaderStage_t *stage, const char **text ) +{ + char *token; + int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0; + qboolean depthMaskExplicit = qfalse; + + stage->active = qtrue; + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: no matching '}' found\n" ); + return qfalse; + } + + if ( token[0] == '}' ) + { + break; + } + // + // map + // + else if ( !Q_stricmp( token, "map" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + if ( !Q_stricmp( token, "$whiteimage" ) ) + { + stage->bundle[0].image = tr.whiteImage; + continue; + } + else if ( !Q_stricmp( token, "$lightmap" ) ) + { + stage->bundle[0].isLightmap = qtrue; + if ( shader.lightmapIndex[0] < 0 || shader.lightmapIndex[0] >= tr.numLightmaps ) + { +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_RED"Lightmap requested but none available for shader %s\n", shader.name); +#endif + stage->bundle[0].image = tr.whiteImage; + } + else + { + stage->bundle[0].image = tr.lightmaps[shader.lightmapIndex[0]]; + } + continue; + } + else + { +#ifdef DEDICATED + stage->bundle[0].image = NULL; + return qfalse; +#else + stage->bundle[0].image = R_FindImageFile( token, (qboolean)!shader.noMipMaps, qfalse, qfalse, GL_REPEAT ); + if ( !stage->bundle[0].image ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } +#endif // !DEDICATED + } + } +#ifdef VV_LIGHTING + // + // specularmap + // + else if ( !Q_stricmp( token, "specularmap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: missing parameter for 'specularmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + stage->bundle[0].image = R_FindImageFile( token, !shader.noMipMaps, qfalse, qfalse, GL_REPEAT ); + if ( !stage->bundle[0].image ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + +// stage->isSpecular = qtrue; + + shader.needsNormal = true; + shader.needsTangent = true; + } +#endif // VV_LIGHTING + // + // clampmap + // + else if ( !Q_stricmp( token, "clampmap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } +#ifdef DEDICATED + stage->bundle[0].image = NULL; + return qfalse; +#else + stage->bundle[0].image = R_FindImageFile( token, (qboolean)!shader.noMipMaps, qfalse, qfalse, GL_CLAMP ); + if ( !stage->bundle[0].image ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } +#endif + } + // + // animMap .... + // + else if ( !Q_stricmp( token, "animMap" ) || !Q_stricmp( token, "clampanimMap" ) || !Q_stricmp( token, "oneshotanimMap" )) + { + #define MAX_IMAGE_ANIMATIONS 32 + image_t *images[MAX_IMAGE_ANIMATIONS]; + bool bClamp = !Q_stricmp( token, "clampanimMap" ); + bool oneShot = !Q_stricmp( token, "oneshotanimMap" ); + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parameter for '%s' keyword in shader '%s'\n", (bClamp ? "animMap":"clampanimMap"), shader.name ); + return qfalse; + } + stage->bundle[0].imageAnimationSpeed = atof( token ); + stage->bundle[0].oneShotAnimMap = oneShot; + + // parse up to MAX_IMAGE_ANIMATIONS animations + while ( 1 ) { + int num; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + break; + } + num = stage->bundle[0].numImageAnimations; + if ( num < MAX_IMAGE_ANIMATIONS ) { +#ifdef DEDICATED + stage->bundle[0].image = NULL; + return qfalse; +#else + images[num] = R_FindImageFile( token, (qboolean)!shader.noMipMaps, qfalse, qfalse, bClamp?GL_CLAMP:GL_REPEAT ); + if ( !images[num] ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + stage->bundle[0].numImageAnimations++; +#endif + } + } + // Copy image ptrs into an array of ptrs + stage->bundle[0].image = (image_t*) Hunk_Alloc( stage->bundle[0].numImageAnimations * sizeof( image_t* ), h_low ); + memcpy( stage->bundle[0].image, images, stage->bundle[0].numImageAnimations * sizeof( image_t* ) ); + } +/* + else if ( !Q_stricmp( token, "videoMap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parameter for 'videoMap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } +#ifdef DEDICATED + stage->bundle[0].videoMapHandle = -1; +#else + stage->bundle[0].videoMapHandle = CIN_PlayCinematic( token, 0, 0, 256, 256, (CIN_loop | CIN_silent | CIN_shader)); +#endif + if (stage->bundle[0].videoMapHandle != -1) { + stage->bundle[0].isVideoMap = qtrue; + assert (stage->bundle[0].videoMapHandlebundle[0].image = tr.scratchImage[stage->bundle[0].videoMapHandle]; + } + } +*/ +#ifdef _XBOX + // + // bumpmap + // + else if ( !Q_stricmp( token, "bumpmap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if( !token[0] ) + { + Com_Printf( S_COLOR_YELLOW, "WARNING: missing parameter for 'bumpmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + stage->bundle[0].image = R_FindImageFile( token, !shader.noMipMaps, qfalse, 0, GL_REPEAT ); + if ( !stage->bundle[0].image ) + { + Com_Printf( S_COLOR_YELLOW, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + + stage->isBumpMap = qtrue; + shader.isBumpMap = qtrue; + + shader.needsNormal = true; + shader.needsTangent = true; + } +#endif + // + // alphafunc + // + else if ( !Q_stricmp( token, "alphaFunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parameter for 'alphaFunc' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + atestBits = NameToAFunc( token ); + } + // + // depthFunc + // + else if ( !Q_stricmp( token, "depthfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + + if ( !token[0] ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + if ( !Q_stricmp( token, "lequal" ) ) + { + depthFuncBits = 0; + } + else if ( !Q_stricmp( token, "equal" ) ) + { + depthFuncBits = GLS_DEPTHFUNC_EQUAL; + } + else if ( !Q_stricmp( token, "disable" ) ) + { + depthFuncBits = GLS_DEPTHTEST_DISABLE; + } + else + { + Com_Printf (S_COLOR_YELLOW "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // detail + // + else if ( !Q_stricmp( token, "detail" ) ) + { + stage->isDetail = qtrue; + } + // + // blendfunc + // or blendfunc + // + else if ( !Q_stricmp( token, "blendfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + // check for "simple" blends first + if ( !Q_stricmp( token, "add" ) ) { + blendSrcBits = GLS_SRCBLEND_ONE; + blendDstBits = GLS_DSTBLEND_ONE; + } else if ( !Q_stricmp( token, "filter" ) ) { + blendSrcBits = GLS_SRCBLEND_DST_COLOR; + blendDstBits = GLS_DSTBLEND_ZERO; + } else if ( !Q_stricmp( token, "blend" ) ) { + blendSrcBits = GLS_SRCBLEND_SRC_ALPHA; + blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else { + // complex double blends + blendSrcBits = NameToSrcBlendMode( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + blendDstBits = NameToDstBlendMode( token ); + } + + // clear depth mask for blended surfaces + if ( !depthMaskExplicit ) + { + depthMaskBits = 0; + } + } + // + // rgbGen + // + else if ( !Q_stricmp( token, "rgbGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->rgbWave ); + stage->rgbGen = CGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + vec3_t color; + + ParseVector( text, 3, color ); + stage->constantColor[0] = 255 * color[0]; + stage->constantColor[1] = 255 * color[1]; + stage->constantColor[2] = 255 * color[2]; + + stage->rgbGen = CGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->rgbGen = CGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "identityLighting" ) ) + { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->rgbGen = CGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->rgbGen = CGEN_VERTEX; + if ( stage->alphaGen == 0 ) { + stage->alphaGen = AGEN_VERTEX; + } + } + else if ( !Q_stricmp( token, "exactVertex" ) ) + { + stage->rgbGen = CGEN_EXACT_VERTEX; + } + else if ( !Q_stricmp( token, "lightingDiffuse" ) ) + { + stage->rgbGen = CGEN_LIGHTING_DIFFUSE; + +#ifdef _XBOX + shader.needsNormal = true; +#endif + } + else if ( !Q_stricmp( token, "lightingDiffuseEntity" ) ) + { + if (shader.lightmapIndex[0] != LIGHTMAP_NONE) + { + Com_Printf( S_COLOR_RED "ERROR: rgbGen lightingDiffuseEntity used on a misc_model! in shader '%s'\n", shader.name ); + } + stage->rgbGen = CGEN_LIGHTING_DIFFUSE_ENTITY; + +#ifdef _XBOX + shader.needsNormal = true; +#endif + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_VERTEX; + } + else + { + Com_Printf (S_COLOR_YELLOW "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // alphaGen + // + else if ( !Q_stricmp( token, "alphaGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->alphaWave ); + stage->alphaGen = AGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + token = COM_ParseExt( text, qfalse ); + stage->constantColor[3] = 255 * atof( token ); + stage->alphaGen = AGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->alphaGen = AGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->alphaGen = AGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->alphaGen = AGEN_VERTEX; + } + else if ( !Q_stricmp( token, "lightingSpecular" ) ) + { + stage->alphaGen = AGEN_LIGHTING_SPECULAR; +#ifdef _XBOX + shader.needsNormal = true; +#endif + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_VERTEX; + } + else if ( !Q_stricmp( token, "dot" ) ) + { + stage->alphaGen = AGEN_DOT; + } + else if ( !Q_stricmp( token, "oneMinusDot" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_DOT; + } + else if ( !Q_stricmp( token, "portal" ) ) + { + stage->alphaGen = AGEN_PORTAL; + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + shader.portalRange = 256; + Com_Printf (S_COLOR_YELLOW "WARNING: missing range parameter for alphaGen portal in shader '%s', defaulting to 256\n", shader.name ); + } + else + { + shader.portalRange = atof( token ); + } + } + else + { + Com_Printf (S_COLOR_YELLOW "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // tcGen + // + else if ( !Q_stricmp(token, "texgen") || !Q_stricmp( token, "tcGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing texgen parm in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "environment" ) ) + { + stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED; +#ifdef _XBOX + shader.needsNormal = true; +#endif + } + else if ( !Q_stricmp( token, "lightmap" ) ) + { + stage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) ) + { + stage->bundle[0].tcGen = TCGEN_TEXTURE; + } + else if ( !Q_stricmp( token, "vector" ) ) + { + stage->bundle[0].tcGenVectors = ( vec3_t *) Hunk_Alloc( 2 * sizeof( vec3_t ), h_low ); + ParseVector( text, 3, stage->bundle[0].tcGenVectors[0] ); + ParseVector( text, 3, stage->bundle[0].tcGenVectors[1] ); + + stage->bundle[0].tcGen = TCGEN_VECTOR; + } + else + { + Com_Printf (S_COLOR_YELLOW "WARNING: unknown texgen parm in shader '%s'\n", shader.name ); + } + } + // + // tcMod <...> + // + else if ( !Q_stricmp( token, "tcMod" ) ) + { + char buffer[1024] = ""; + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + strcat( buffer, token ); + strcat( buffer, " " ); + } + + ParseTexMod( buffer, stage ); + + continue; + } + // + // depthmask + // + else if ( !Q_stricmp( token, "depthwrite" ) ) + { + depthMaskBits = GLS_DEPTHMASK_TRUE; + depthMaskExplicit = qtrue; + + continue; + } + // If this stage has glow... GLOWXXX + else if ( Q_stricmp( token, "glow" ) == 0 ) + { + // VVFIXME GLOWXXX +// stage->glow = true; + + continue; + } + // + // surfaceSprites ... + // + else if ( !Q_stricmp( token, "surfaceSprites" ) ) + { + char buffer[1024] = ""; + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + strcat( buffer, token ); + strcat( buffer, " " ); + } + + ParseSurfaceSprites( buffer, stage ); + + continue; + } + // + // ssFademax + // ssFadescale + // ssVariance + // ssHangdown + // ssAnyangle + // ssFaceup + // ssWind + // ssWindIdle + // ssDuration + // ssGrow + // ssWeather + // + else if (!Q_stricmpn(token, "ss", 2)) // <--- NOTE ONLY COMPARING FIRST TWO LETTERS + { + char buffer[1024] = ""; + char param[128]; + strcpy(param,token); + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + strcat( buffer, token ); + strcat( buffer, " " ); + } + + ParseSurfaceSpritesOptional( param, buffer, stage ); + + continue; + } + else + { + Com_Printf (S_COLOR_YELLOW "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + + // + // if cgen isn't explicitly specified, use either identity or identitylighting + // + if ( stage->rgbGen == CGEN_BAD ) { + if ( //blendSrcBits == 0 || + blendSrcBits == GLS_SRCBLEND_ONE || + blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } else { + stage->rgbGen = CGEN_IDENTITY; + } + } + + + // + // implicitly assume that a GL_ONE GL_ZERO blend mask disables blending + // + if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && + ( blendDstBits == GLS_DSTBLEND_ZERO ) ) + { + blendDstBits = blendSrcBits = 0; + depthMaskBits = GLS_DEPTHMASK_TRUE; + } + + // decide which agens we can skip + if ( stage->alphaGen == AGEN_IDENTITY ) { + if ( stage->rgbGen == CGEN_IDENTITY + || stage->rgbGen == CGEN_LIGHTING_DIFFUSE ) { + stage->alphaGen = AGEN_SKIP; + } + } + + // + // compute state bits + // + stage->stateBits = depthMaskBits | + blendSrcBits | blendDstBits | + atestBits | + depthFuncBits; + + return qtrue; +} + +/* +=============== +ParseDeform + +deformVertexes wave +deformVertexes normal +deformVertexes move +deformVertexes bulge +deformVertexes projectionShadow +deformVertexes autoSprite +deformVertexes autoSprite2 +deformVertexes text[0-7] +=============== +*/ +static void ParseDeform( const char **text ) { + char *token; + deformStage_t *ds; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing deform parm in shader '%s'\n", shader.name ); + return; + } + + if ( shader.numDeforms == MAX_SHADER_DEFORMS ) { + Com_Printf (S_COLOR_YELLOW "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name ); + return; + } + + shader.deforms[ shader.numDeforms ] = (deformStage_t *)Hunk_Alloc( sizeof( deformStage_t ), h_low ); + + ds = shader.deforms[ shader.numDeforms ]; + shader.numDeforms++; + + if ( !Q_stricmp( token, "projectionShadow" ) ) { + ds->deformation = DEFORM_PROJECTION_SHADOW; + return; + } + + if ( !Q_stricmp( token, "autosprite" ) ) { + ds->deformation = DEFORM_AUTOSPRITE; + return; + } + + if ( !Q_stricmp( token, "autosprite2" ) ) { + ds->deformation = DEFORM_AUTOSPRITE2; + return; + } + + if ( !Q_stricmpn( token, "text", 4 ) ) { + int n; + + n = token[4] - '0'; + if ( n < 0 || n > 7 ) { + n = 0; + } + ds->deformation = (deform_t)(DEFORM_TEXT0 + n); + return; + } + + if ( !Q_stricmp( token, "bulge" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeWidth = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeHeight = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeSpeed = atof( token ); + + ds->deformation = DEFORM_BULGE; + return; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + + if ( atof( token ) != 0 ) + { + ds->deformationSpread = 1.0f / atof( token ); + } + else + { + ds->deformationSpread = 100.0f; + Com_Printf (S_COLOR_YELLOW "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_WAVE; + return; + } + + if ( !Q_stricmp( token, "normal" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.frequency = atof( token ); + + ds->deformation = DEFORM_NORMALS; + return; + } + + if ( !Q_stricmp( token, "move" ) ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + Com_Printf (S_COLOR_YELLOW "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->moveVector[i] = atof( token ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_MOVE; + return; + } + + Com_Printf (S_COLOR_YELLOW "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name ); +} + + +/* +=============== +ParseSkyParms + +skyParms +=============== +*/ +static void ParseSkyParms( const char **text ) { + char *token; + const char *suf[6] = {"rt", "lf", "bk", "ft", "up", "dn"}; + char pathname[MAX_QPATH]; + int i; + + shader.sky = (skyParms_t *)Hunk_Alloc( sizeof( skyParms_t ), h_low ); + + // outerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + Com_Printf (S_COLOR_YELLOW "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for (i=0 ; i<6 ; i++) { + Com_sprintf( pathname, sizeof(pathname), "%s_%s", token, suf[i] ); +#ifdef DEDICATED + shader.sky->outerbox[i] = NULL; +#else + shader.sky->outerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, qfalse, GL_CLAMP ); + if ( !shader.sky->outerbox[i] ) { + if (i) { + shader.sky->outerbox[i] = shader.sky->outerbox[i-1];//not found, so let's use the previous image + }else{ + shader.sky->outerbox[i] = tr.defaultImage; + } + } +#endif + } + } + + // cloudheight + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + Com_Printf (S_COLOR_YELLOW "WARNING: 'skyParms' missing cloudheight in shader '%s'\n", shader.name ); + return; + } + shader.sky->cloudHeight = atof( token ); + if ( !shader.sky->cloudHeight ) { + shader.sky->cloudHeight = 512; + } +#ifndef DEDICATED + R_InitSkyTexCoords( shader.sky->cloudHeight ); +#endif + + // innerbox + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "-" ) ) { + Com_Printf (S_COLOR_YELLOW "WARNING: in shader '%s' 'skyParms', innerbox is not supported!", shader.name); + } +} + + +/* +================= +ParseSort +================= +*/ +static void ParseSort( const char **text ) { + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + Com_Printf (S_COLOR_YELLOW "WARNING: missing sort parameter in shader '%s'\n", shader.name ); + return; + } + + if ( !Q_stricmp( token, "portal" ) ) { + shader.sort = SS_PORTAL; + } else if ( !Q_stricmp( token, "sky" ) ) { + shader.sort = SS_ENVIRONMENT; + } else if ( !Q_stricmp( token, "opaque" ) ) { + shader.sort = SS_OPAQUE; + } else if ( !Q_stricmp( token, "decal" ) ) { + shader.sort = SS_DECAL; + } else if ( !Q_stricmp( token, "seeThrough" ) ) { + shader.sort = SS_SEE_THROUGH; + } else if ( !Q_stricmp( token, "banner" ) ) { + shader.sort = SS_BANNER; + } else if ( !Q_stricmp( token, "additive" ) ) { + shader.sort = SS_BLEND1; + } else if ( !Q_stricmp( token, "nearest" ) ) { + shader.sort = SS_NEAREST; + } else if ( !Q_stricmp( token, "underwater" ) ) { + shader.sort = SS_UNDERWATER; + } else if ( !Q_stricmp( token, "inside" ) ) { + shader.sort = SS_INSIDE; + } else if ( !Q_stricmp( token, "mid_inside" ) ) { + shader.sort = SS_MID_INSIDE; + } else if ( !Q_stricmp( token, "middle" ) ) { + shader.sort = SS_MIDDLE; + } else if ( !Q_stricmp( token, "mid_outside" ) ) { + shader.sort = SS_MID_OUTSIDE; + } else if ( !Q_stricmp( token, "outside" ) ) { + shader.sort = SS_OUTSIDE; + } + else { + shader.sort = atof( token ); + } +} + +/* +================= +ParseMaterial +================= +*/ +const char *materialNames[MATERIAL_LAST] = +{ + MATERIALS +}; + +void ParseMaterial( const char **text ) +{ + char *token; + int i; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing material in shader '%s'\n", shader.name ); + return; + } + for(i = 0; i < MATERIAL_LAST; i++) + { + if ( !Q_stricmp( token, materialNames[i] ) ) + { + shader.surfaceFlags |= i; + break; + } + } +} + + +// this table is also present in q3map + +typedef struct { + char *name; + int clearSolid, surfaceFlags, contents; +} infoParm_t; + + +infoParm_t infoParms[] = { + // Game content Flags + {"nonsolid", ~CONTENTS_SOLID, 0, 0 }, // special hack to clear solid flag + {"nonopaque", ~CONTENTS_OPAQUE, 0, 0 }, // special hack to clear opaque flag + {"lava", ~CONTENTS_SOLID, 0, CONTENTS_LAVA }, // very damaging + {"slime", ~CONTENTS_SOLID, 0, CONTENTS_SLIME }, // mildly damaging + {"water", ~CONTENTS_SOLID, 0, CONTENTS_WATER }, + {"fog", ~CONTENTS_SOLID, 0, CONTENTS_FOG}, // carves surfaces entering + {"shotclip", ~CONTENTS_SOLID, 0, CONTENTS_SHOTCLIP }, /* block shots, but not people */ + {"playerclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_PLAYERCLIP }, /* block only the player */ + {"monsterclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_MONSTERCLIP }, + {"botclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_BOTCLIP }, /* for bots */ + {"trigger", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_TRIGGER }, + {"nodrop", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc) + {"terrain", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_TERRAIN }, /* use special terrain collsion */ + {"ladder", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_LADDER }, // climb up in it like water + {"abseil", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_ABSEIL }, // can abseil down this brush + {"outside", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_OUTSIDE }, // volume is considered to be in the outside (i.e. not indoors) + {"inside", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_INSIDE }, // volume is considered to be inside (i.e. indoors) + + + {"detail", -1, 0, CONTENTS_DETAIL }, // don't include in structural bsp + {"trans", -1, 0, CONTENTS_TRANSLUCENT }, // surface has an alpha component + + /* Game surface flags */ + {"sky", -1, SURF_SKY, 0 }, /* emit light from an environment map */ + {"slick", -1, SURF_SLICK, 0 }, + + {"nodamage", -1, SURF_NODAMAGE, 0 }, + {"noimpact", -1, SURF_NOIMPACT, 0 }, /* don't make impact explosions or marks */ + {"nomarks", -1, SURF_NOMARKS, 0 }, /* don't make impact marks, but still explode */ + {"nodraw", -1, SURF_NODRAW, 0 }, /* don't generate a drawsurface (or a lightmap) */ + {"nosteps", -1, SURF_NOSTEPS, 0 }, + {"nodlight", -1, SURF_NODLIGHT, 0 }, /* don't ever add dynamic lights */ + {"metalsteps", -1, SURF_METALSTEPS,0 }, + {"nomiscents", -1, SURF_NOMISCENTS,0 }, /* No misc ents on this surface */ + {"forcefield", -1, SURF_FORCEFIELD,0 }, +}; + + +/* +=============== +ParseSurfaceParm + +surfaceparm +=============== +*/ +static void ParseSurfaceParm( const char **text ) { + char *token; + int numInfoParms = sizeof(infoParms) / sizeof(infoParms[0]); + int i; + + token = COM_ParseExt( text, qfalse ); + for ( i = 0 ; i < numInfoParms ; i++ ) { + if ( !Q_stricmp( token, infoParms[i].name ) ) { + shader.surfaceFlags |= infoParms[i].surfaceFlags; + shader.contentFlags |= infoParms[i].contents; + shader.contentFlags &= infoParms[i].clearSolid; + break; + } + } +} + +/* +================= +ParseShader + +The current text pointer is at the explicit text definition of the +shader. Parse it into the global shader variable. Later functions +will optimize it. +================= +*/ +static qboolean ParseShader( const char **text ) +{ + char *token; + int s; + + s = 0; + +#ifdef _XBOX + shader.needsNormal = false; + shader.needsTangent = false; +#endif + + token = COM_ParseExt( text, qtrue ); + if ( token[0] != '{' ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name ); + return qfalse; + } + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: no concluding '}' in shader %s\n", shader.name ); + return qfalse; + } + + // end of shader definition + if ( token[0] == '}' ) + { + break; + } + // stage definition + else if ( token[0] == '{' ) + { + if ( !ParseStage( &stages[s], text ) ) + { + return qfalse; + } + stages[s].active = qtrue; +#ifndef _XBOX // GLOWXXX + if ( stages[s].glow ) + { + shader.hasGlow = true; + } +#endif + s++; + continue; + } + // skip stuff that only the QuakeEdRadient needs + else if ( !Q_stricmpn( token, "qer", 3 ) ) { + SkipRestOfLine( text ); + continue; + } + // material deprecated as of 11 Jan 01 + // material undeprecated as of 7 May 01 - q3map_material deprecated + else if ( !Q_stricmp( token, "material" ) || !Q_stricmp( token, "q3map_material" ) ) + { + ParseMaterial( text ); + } + // sun parms + else if ( !Q_stricmp( token, "sun" ) || !Q_stricmp( token, "q3map_sun" ) ) + { + token = COM_ParseExt( text, qfalse ); + tr.sunLight[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[2] = atof( token ); + + VectorNormalize( tr.sunLight ); + + token = COM_ParseExt( text, qfalse ); + float a = atof( token ); + VectorScale( tr.sunLight, a, tr.sunLight); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + a = a / 180 * M_PI; + + token = COM_ParseExt( text, qfalse ); + float b = atof( token ); + b = b / 180 * M_PI; + + tr.sunDirection[0] = cos( a ) * cos( b ); + tr.sunDirection[1] = sin( a ) * cos( b ); + tr.sunDirection[2] = sin( b ); + } + // q3map_surfacelight deprecated as of 16 Jul 01 + else if ( !Q_stricmp( token, "surfacelight" ) || !Q_stricmp( token, "q3map_surfacelight" ) ) + { + token = COM_ParseExt( text, qfalse ); + tr.sunSurfaceLight = atoi( token ); + } + else if ( !Q_stricmp( token, "lightColor" ) ) + { + /* + if ( !ParseVector( text, 3, tr.sunAmbient ) ) + { + return qfalse; + } + */ + //SP skips this so I'm skipping it here too. + SkipRestOfLine( text ); + continue; + } + else if ( !Q_stricmp( token, "deformvertexes" ) || !Q_stricmp( token, "deform" )) { + ParseDeform( text ); + continue; + } + else if ( !Q_stricmp( token, "tesssize" ) ) { + SkipRestOfLine( text ); + continue; + } + else if ( !Q_stricmp( token, "clampTime" ) ) { + assert( 0 ); +// token = COM_ParseExt( text, qfalse ); +// if (token[0]) { +// shader.clampTime = atof(token); +// } + } + // skip stuff that only the q3map needs + else if ( !Q_stricmpn( token, "q3map", 5 ) ) { + SkipRestOfLine( text ); + continue; + } + // skip stuff that only q3map or the server needs + else if ( !Q_stricmp( token, "surfaceParm" ) ) { + ParseSurfaceParm( text ); + continue; + } + // no mip maps + else if ( !Q_stricmp( token, "nomipmaps" ) ) + { + shader.noMipMaps = true; +// shader.noPicMip = true; + continue; + } + // no picmip adjustment + else if ( !Q_stricmp( token, "nopicmip" ) ) + { +// shader.noPicMip = true; + continue; + } + else if ( !Q_stricmp( token, "noglfog" ) ) + { + shader.fogPass = FP_NONE; + continue; + } + // polygonOffset + else if ( !Q_stricmp( token, "polygonOffset" ) ) + { + shader.polygonOffset = true; + continue; + } + else if ( !Q_stricmp( token, "noTC" ) ) + { +// shader.noTC = true; + continue; + } + // entityMergable, allowing sprite surfaces from multiple entities + // to be merged into one batch. This is a savings for smoke + // puffs and blood, but can't be used for anything where the + // shader calcs (not the surface function) reference the entity color or scroll + else if ( !Q_stricmp( token, "entityMergable" ) ) + { + shader.entityMergable = true; + continue; + } + // fogParms + else if ( !Q_stricmp( token, "fogParms" ) ) + { + shader.fogParms = (fogParms_t *)Hunk_Alloc( sizeof( fogParms_t ), h_low ); + if ( !ParseVector( text, 3, shader.fogParms->color ) ) { + return qfalse; + } + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name ); + continue; + } + shader.fogParms->depthForOpaque = atof( token ); + + // skip any old gradient directions + SkipRestOfLine( text ); + continue; + } + // portal + else if ( !Q_stricmp(token, "portal") ) + { + shader.sort = SS_PORTAL; + continue; + } + // skyparms + else if ( !Q_stricmp( token, "skyparms" ) ) + { + ParseSkyParms( text ); + continue; + } + // light determines flaring in q3map, not needed here + else if ( !Q_stricmp(token, "light") ) + { + token = COM_ParseExt( text, qfalse ); + continue; + } + // cull + else if ( !Q_stricmp( token, "cull") ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf (S_COLOR_YELLOW "WARNING: missing cull parms in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "none" ) || !Q_stricmp( token, "twosided" ) || !Q_stricmp( token, "disable" ) ) + { + shader.cullType = CT_TWO_SIDED; + } + else if ( !Q_stricmp( token, "back" ) || !Q_stricmp( token, "backside" ) || !Q_stricmp( token, "backsided" ) ) + { + shader.cullType = CT_BACK_SIDED; + } + else + { + Com_Printf (S_COLOR_YELLOW "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name ); + } + continue; + } + // sort + else if ( !Q_stricmp( token, "sort" ) ) + { + ParseSort( text ); + continue; + } + else + { + Com_Printf (S_COLOR_YELLOW "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name ); + return qfalse; + } + } + + // + // ignore shaders that don't have any stages, unless it is a sky or fog + // + if ( s == 0 && !shader.sky && !(shader.contentFlags & CONTENTS_FOG ) ) { + return qfalse; + } + + shader.explicitlyDefined = true; + + return qtrue; +} + +/* +======================================================================================== + +SHADER OPTIMIZATION AND FOGGING + +======================================================================================== +*/ + +typedef struct { + int blendA; + int blendB; + + int multitextureEnv; + int multitextureBlend; +} collapse_t; + +#ifndef DEDICATED +static collapse_t collapse[] = { + { 0, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, 0 }, + + { 0, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, 0 }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { 0, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, 0 }, + + { GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE }, +#if 0 + { 0, GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_SRCBLEND_SRC_ALPHA, + GL_DECAL, 0 }, +#endif + { -1 } +}; +#endif // !DEDICATED +/* +================ +CollapseMultitexture + +Attempt to combine two stages into a single multitexture stage +FIXME: I think modulated add + modulated add collapses incorrectly +================= +*/ +static qboolean CollapseMultitexture( void ) { +#ifdef DEDICATED + return qfalse; +#else + int abits, bbits; + int i; + textureBundle_t tmpBundle; + if ( !qglActiveTextureARB ) { + return qfalse; + } + + // make sure both stages are active + if ( !stages[0].active || !stages[1].active ) { + return qfalse; + } + + abits = stages[0].stateBits; + bbits = stages[1].stateBits; + + // make sure that both stages have identical state other than blend modes + if ( ( abits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) != + ( bbits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) ) { + return qfalse; + } + + abits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + bbits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + + // search for a valid multitexture blend function + for ( i = 0; collapse[i].blendA != -1 ; i++ ) { + if ( abits == collapse[i].blendA + && bbits == collapse[i].blendB ) { + break; + } + } + + // nothing found + if ( collapse[i].blendA == -1 ) { + return qfalse; + } + + // GL_ADD is a separate extension + if ( collapse[i].multitextureEnv == GL_ADD && !glConfig.textureEnvAddAvailable ) { + return qfalse; + } + + // make sure waveforms have identical parameters + if ( ( stages[0].rgbGen != stages[1].rgbGen ) || + ( stages[0].alphaGen != stages[1].alphaGen ) ) { + return qfalse; + } + + // an add collapse can only have identity colors + if ( collapse[i].multitextureEnv == GL_ADD && stages[0].rgbGen != CGEN_IDENTITY ) { + return qfalse; + } + + if ( stages[0].rgbGen == CGEN_WAVEFORM ) + { + if ( memcmp( &stages[0].rgbWave, + &stages[1].rgbWave, + sizeof( stages[0].rgbWave ) ) ) + { + return qfalse; + } + } + if ( stages[0].alphaGen == CGEN_WAVEFORM ) + { + if ( memcmp( &stages[0].alphaWave, + &stages[1].alphaWave, + sizeof( stages[0].alphaWave ) ) ) + { + return qfalse; + } + } + + + // make sure that lightmaps are in bundle 1 for 3dfx + if ( stages[0].bundle[0].isLightmap ) + { + tmpBundle = stages[0].bundle[0]; + stages[0].bundle[0] = stages[1].bundle[0]; + stages[0].bundle[1] = tmpBundle; + } + else + { + stages[0].bundle[1] = stages[1].bundle[0]; + } + + // set the new blend state bits + shader.multitextureEnv = collapse[i].multitextureEnv; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= collapse[i].multitextureBlend; + + // + // move down subsequent shaders + // + memmove( &stages[1], &stages[2], sizeof( stages[0] ) * ( MAX_SHADER_STAGES - 2 ) ); + Com_Memset( &stages[MAX_SHADER_STAGES-1], 0, sizeof( stages[0] ) ); +#endif //!DEDICATED + return qtrue; +} + + +/* +============== +SortNewShader + +Positions the most recently created shader in the tr.sortedShaders[] +array so that the shader->sort key is sorted reletive to the other +shaders. + +Sets shader->sortedIndex +============== +*/ +static void SortNewShader( void ) { + int i; + float sort; + shader_t *newShader; + + newShader = tr.shaders[ tr.numShaders - 1 ]; + sort = newShader->sort; + + for ( i = tr.numShaders - 2 ; i >= 0 ; i-- ) { + if ( tr.sortedShaders[ i ]->sort <= sort ) { + break; + } + tr.sortedShaders[i+1] = tr.sortedShaders[i]; + tr.sortedShaders[i+1]->sortedIndex++; + } + + newShader->sortedIndex = i+1; + tr.sortedShaders[i+1] = newShader; +} + + +/* +==================== +GeneratePermanentShader +==================== +*/ +static shader_t *GeneratePermanentShader( void ) { + shader_t *newShader; + int i, b; + int size; + + if ( tr.numShaders == MAX_SHADERS ) { + //Com_Printf (S_COLOR_YELLOW "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n"); + Com_Printf( "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n"); + return tr.defaultShader; + } + + newShader = (struct shader_s *)/*ri.*/Hunk_Alloc( sizeof( shader_t ), h_low ); + + *newShader = shader; + + if ( shader.sort <= /*SS_OPAQUE*/SS_SEE_THROUGH ) { + newShader->fogPass = FP_EQUAL; + } else if ( shader.contentFlags & CONTENTS_FOG ) { + newShader->fogPass = FP_LE; + } + + tr.shaders[ tr.numShaders ] = newShader; + newShader->index = tr.numShaders; + + tr.sortedShaders[ tr.numShaders ] = newShader; + newShader->sortedIndex = tr.numShaders; + + tr.numShaders++; + + size = newShader->numUnfoggedPasses ? newShader->numUnfoggedPasses * sizeof( stages[0] ) : sizeof( stages[0] ); + newShader->stages = (shaderStage_t *) Hunk_Alloc( size, h_low ); + + for ( i = 0 ; i < newShader->numUnfoggedPasses ; i++ ) { + if ( !stages[i].active ) { + break; + } + newShader->stages[i] = stages[i]; + + for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) { + if (newShader->stages[i].bundle[b].numTexMods) + { + size = newShader->stages[i].bundle[b].numTexMods * sizeof( texModInfo_t ); + newShader->stages[i].bundle[b].texMods = (texModInfo_t *)Hunk_Alloc( size, h_low ); + Com_Memcpy( newShader->stages[i].bundle[b].texMods, stages[i].bundle[b].texMods, size ); + } + else + { + newShader->stages[i].bundle[b].texMods = 0; //clear the globabl ptr jic + } + } + } + + SortNewShader(); + + const int hash = generateHashValue(newShader->name, FILE_HASH_SIZE); + newShader->next = hashTable[hash]; + hashTable[hash] = newShader; + + return newShader; +} + +/* +================= +VertexLightingCollapse + +If vertex lighting is enabled, only render a single +pass, trying to guess which is the correct one to best aproximate +what it is supposed to look like. + + OUTPUT: Number of stages after the collapse (in the case of surfacesprites this isn't one). +================= +*/ +//rww - no longer used, at least for now. destroys alpha shaders completely. +#if 0 +static int VertexLightingCollapse( void ) { + int stage, nextopenstage; + shaderStage_t *bestStage; + int bestImageRank; + int rank; + int finalstagenum=1; + + // if we aren't opaque, just use the first pass + if ( shader.sort == SS_OPAQUE ) { + + // pick the best texture for the single pass + bestStage = &stages[0]; + bestImageRank = -999999; + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + rank = 0; + + if ( pStage->bundle[0].isLightmap ) { + rank -= 100; + } + if ( pStage->bundle[0].tcGen != TCGEN_TEXTURE ) { + rank -= 5; + } + if ( pStage->bundle[0].numTexMods ) { + rank -= 5; + } + if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING ) { + rank -= 3; + } + + // SurfaceSprites are most certainly NOT desireable as the collapsed surface texture. + if ( pStage->ss && pstage->ss->surfaceSpriteType) + { + rank -= 1000; + } + + if ( rank > bestImageRank ) { + bestImageRank = rank; + bestStage = pStage; + } + } + + stages[0].bundle[0] = bestStage->bundle[0]; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= GLS_DEPTHMASK_TRUE; + if ( shader.lightmapIndex[0] == LIGHTMAP_NONE ) { + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; +#ifdef _XBOX + shader.needsNormal = true; +#endif + } else { + stages[0].rgbGen = CGEN_EXACT_VERTEX; + } + stages[0].alphaGen = AGEN_SKIP; + } else { + // don't use a lightmap (tesla coils) + if ( stages[0].bundle[0].isLightmap ) { + stages[0] = stages[1]; + } + + // if we were in a cross-fade cgen, hack it to normal + if ( stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + } + + for ( stage=1, nextopenstage=1; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + if ( pStage->ss && pstage->ss->surfaceSpriteType) + { + // Copy this stage to the next open stage list (that is, we don't want any inactive stages before this one) + if (nextopenstage != stage) + { + stages[nextopenstage] = *pStage; + stages[nextopenstage].bundle[0] = pStage->bundle[0]; + } + nextopenstage++; + finalstagenum++; + continue; + } + + Com_Memset( pStage, 0, sizeof( *pStage ) ); + } + + return finalstagenum; +} +#endif + +/* +========================= +FinishShader + +Returns a freshly allocated shader with all the needed info +from the current global working shader +========================= +*/ +static shader_t *FinishShader( void ) { + int stage, lmStage, stageIndex; //rwwRMG - stageIndex for AGEN_BLEND + qboolean hasLightmapStage; + qboolean vertexLightmap; + + hasLightmapStage = qfalse; + vertexLightmap = qfalse; + + // + // set sky stuff appropriate + // + if ( shader.sky ) { + shader.sort = SS_ENVIRONMENT; + } + + // + // set polygon offset + // + if ( shader.polygonOffset && !shader.sort ) { + shader.sort = SS_DECAL; + } + + for(lmStage = 0; lmStage < MAX_SHADER_STAGES; lmStage++) + { + shaderStage_t *pStage = &stages[lmStage]; + if (pStage->active && pStage->bundle[0].isLightmap) + { + break; + } + } + + if (lmStage < MAX_SHADER_STAGES) + { + if (shader.lightmapIndex[0] == LIGHTMAP_BY_VERTEX) + { + if (lmStage == 0) //< MAX_SHADER_STAGES-1) + {//copy the rest down over the lightmap slot + memmove(&stages[lmStage], &stages[lmStage+1], sizeof(shaderStage_t) * (MAX_SHADER_STAGES-lmStage-1)); + memset(&stages[MAX_SHADER_STAGES-1], 0, sizeof(shaderStage_t)); + //change blending on the moved down stage + stages[lmStage].stateBits = GLS_DEFAULT; + } + //change anything that was moved down (or the *white if LM is first) to use vertex color + stages[lmStage].rgbGen = CGEN_EXACT_VERTEX; + stages[lmStage].alphaGen = AGEN_SKIP; + lmStage = MAX_SHADER_STAGES; //skip the style checking below + } + } + + if (lmStage < MAX_SHADER_STAGES)// && !r_fullbright->value) + { + int numStyles; + int i; + + for(numStyles=0;numStyles= LS_UNUSED) + { + break; + } + } + numStyles--; + if (numStyles > 0) + { + for(i=MAX_SHADER_STAGES-1;i>lmStage+numStyles;i--) + { + stages[i] = stages[i-numStyles]; + } + + for(i=0;iactive ) { + break; + } + + // check for a missing texture + if ( !pStage->bundle[0].image ) { + Com_Printf (S_COLOR_YELLOW "Shader %s has a stage with no image\n", shader.name ); + pStage->active = qfalse; + continue; + } + + // + // ditch this stage if it's detail and detail textures are disabled + // + if ( pStage->isDetail && !r_detailTextures->integer ) { + if ( stage < ( MAX_SHADER_STAGES - 1 ) ) { + memmove( pStage, pStage + 1, sizeof( *pStage ) * ( MAX_SHADER_STAGES - stage - 1 ) ); + + // rww - 9-13-01 [1-26-01-sof2] + memset( pStage + ( MAX_SHADER_STAGES - stage - 1 ), 0, sizeof( *pStage ) ); //clear the last one moved down + stage--; //look at this stage next time around + } + continue; + } + + pStage->index = stageIndex; //rwwRMG - needed for AGEN_BLEND + + // + // default texture coordinate generation + // + if ( pStage->bundle[0].isLightmap ) { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + hasLightmapStage = qtrue; + } else { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_TEXTURE; + } + } + + + // not a true lightmap but we want to leave existing + // behaviour in place and not print out a warning + //if (pStage->rgbGen == CGEN_VERTEX) { + // vertexLightmap = qtrue; + //} + + + + // + // determine sort order and fog color adjustment + // + if ( ( pStage->stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) && + ( stages[0].stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) ) { + int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS; + int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS; + + // fog color adjustment only works for blend modes that have a contribution + // that aproaches 0 as the modulate values aproach 0 -- + // GL_ONE, GL_ONE + // GL_ZERO, GL_ONE_MINUS_SRC_COLOR + // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA + + // modulate, additive + if ( ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE ) ) || + ( ( blendSrcBits == GLS_SRCBLEND_ZERO ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR ) ) ) { + pStage->adjustColorsForFog = ACFF_MODULATE_RGB; + } + // strict blend + else if ( ( blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA; + } + // premultiplied alpha + else if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_RGBA; + } else { + // we can't adjust this one correctly, so it won't be exactly correct in fog + } + + // don't screw with sort order if this is a portal or environment + if ( !shader.sort ) { + // see through item, like a grill or grate + if ( pStage->stateBits & GLS_DEPTHMASK_TRUE ) + { + shader.sort = SS_SEE_THROUGH; + } + else + { + /* + if (( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE )) + { + // GL_ONE GL_ONE needs to come a bit later + shader.sort = SS_BLEND2; + } + else if (( blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA )) + { //rww - Pushed SS_BLEND1 up to SS_BLEND2, inserting this so that saber glow will render above water and things. + //Unfortunately it still affects other shaders with the same blend settings, but it seems more or less alright. + shader.sort = SS_BLEND1; + } + else + { + shader.sort = SS_BLEND0; + } + */ + if (( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE )) + { + // GL_ONE GL_ONE needs to come a bit later + shader.sort = SS_BLEND1; + } + else + { + shader.sort = SS_BLEND0; + } + } + } + } + + //rww - begin hw fog + if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_ONE|GLS_DSTBLEND_ONE)) + { + pStage->mGLFogColorOverride = GLFOGOVERRIDE_BLACK; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_SRC_ALPHA|GLS_DSTBLEND_ONE) && + pStage->alphaGen == AGEN_LIGHTING_SPECULAR && stage) + { + pStage->mGLFogColorOverride = GLFOGOVERRIDE_BLACK; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_ZERO|GLS_DSTBLEND_ZERO)) + { + pStage->mGLFogColorOverride = GLFOGOVERRIDE_WHITE; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_ONE|GLS_DSTBLEND_ZERO)) + { + pStage->mGLFogColorOverride = GLFOGOVERRIDE_WHITE; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == 0 && stage) + { // + pStage->mGLFogColorOverride = GLFOGOVERRIDE_WHITE; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == 0 && pStage->bundle[0].isLightmap && stage < MAX_SHADER_STAGES-1 && + stages[stage+1].bundle[0].isLightmap) + { // multiple light map blending + pStage->mGLFogColorOverride = GLFOGOVERRIDE_WHITE; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_DST_COLOR|GLS_DSTBLEND_ZERO) && pStage->bundle[0].isLightmap) + { //I don't know, it works. -rww + pStage->mGLFogColorOverride = GLFOGOVERRIDE_WHITE; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_DST_COLOR|GLS_DSTBLEND_ZERO)) + { //I don't know, it works. -rww + pStage->mGLFogColorOverride = GLFOGOVERRIDE_BLACK; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_ONE|GLS_DSTBLEND_ONE_MINUS_SRC_COLOR)) + { //I don't know, it works. -rww + pStage->mGLFogColorOverride = GLFOGOVERRIDE_BLACK; + } + else + { + pStage->mGLFogColorOverride = GLFOGOVERRIDE_NONE; + } + //rww - end hw fog + + stageIndex++; //rwwRMG - needed for AGEN_BLEND + } + + // there are times when you will need to manually apply a sort to + // opaque alpha tested shaders that have later blend passes + if ( !shader.sort ) { + shader.sort = SS_OPAQUE; + } + + // + // if we are in r_vertexLight mode, never use a lightmap texture + // + if ( stage > 1 && (r_vertexLight->integer && !r_uiFullScreen->integer) ) { + //stage = VertexLightingCollapse(); + //rww - since this does bad things, I am commenting it out for now. If you want to attempt a fix, feel free. + hasLightmapStage = qfalse; + } + + // + // look for multitexture potential + // + if ( stage > 1 && CollapseMultitexture() ) { + stage--; + } + +#ifdef _XBOX + for(int i = 0; i < MAX_SHADER_STAGES; i++) + { + if(stages[i].isBumpMap) + { + // Bumpmap can't be the first stage + assert(i > 0); + + if(stages[i - 1].bundle[1].image) + { + // Previous stage has already been collapsed + stages[i].bundle[1] = stages[i].bundle[0]; + stages[i].bundle[0] = stages[i - 1].bundle[0]; + } + else + { + stages[i - 1].bundle[1] = stages[i].bundle[0]; + stages[i - 1].isBumpMap = qtrue; + + // move down subsequent shaders + memmove( &stages[i], &stages[i+1], sizeof( stages[i-1] ) * ( MAX_SHADER_STAGES - 2 ) ); + memset( &stages[MAX_SHADER_STAGES-1], 0, sizeof( stages[i-1] ) ); + + stage--; + } + } + } +#endif + + if ( shader.lightmapIndex[0] >= 0 && !hasLightmapStage ) + { + if (vertexLightmap) + { +// ri.DPrintf( "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name ); + } + else + { + Com_Printf ( "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name ); + memcpy(shader.lightmapIndex, lightmapsNone, sizeof(shader.lightmapIndex)); + memcpy(shader.styles, stylesDefault, sizeof(shader.styles)); + } + } + + + // + // compute number of passes + // + shader.numUnfoggedPasses = stage; + + // fogonly shaders don't have any normal passes + if ( stage == 0 ) { + shader.sort = SS_FOG; + } + + for ( stage = 1; stage < shader.numUnfoggedPasses; stage++ ) + { + // Make sure stage is non detail and active + if(stages[stage].isDetail || !stages[stage].active) + { + break; + } + // MT lightmaps are always in bundle 1 + if(stages[stage].bundle[0].isLightmap) + { + continue; + } + } + + return GeneratePermanentShader(); +} + +//======================================================================================== + +/* +==================== +FindShaderInShaderText + +Scans the combined text description of all the shader files for +the given shader name. + +return NULL if not found + +If found, it will return a valid shader +===================== +*/ +static const char *FindShaderInShaderText( const char *shadername ) { + + char *token; + const char *p; + + int i, hash; + + hash = generateHashValue(shadername, MAX_SHADERTEXT_HASH); + + for (i = 0; shaderTextHashTable[hash][i]; i++) { + p = shaderTextHashTable[hash][i]; + token = COM_ParseExt(&p, qtrue); + if ( !Q_stricmp( token, shadername ) ) { + return p; + } + } + +#ifdef _XBOX + // Raven: You might want to look into doing this. I could be crazy, but it seems + // IMPOSSIBLE for a shader to be found after this point, as EVERY shader is put + // in the hash table in ScanAndLoadShaderFiles() + return NULL; +#endif + + p = shaderText; + + if ( !p ) { + return NULL; + } + + // look for label + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + if ( !Q_stricmp( token, shadername ) ) { + return p; + } + else { + // skip the definition + SkipBracedSection( &p ); + } + } + + return NULL; +} + + +/* +================== +R_FindShaderByName + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. +================== +*/ +shader_t *R_FindShaderByName( const char *name ) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh; + + if ( (name==NULL) || (name[0] == 0) ) { // bk001205 + return tr.defaultShader; + } + + COM_StripExtension( name, strippedName ); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh=hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if (Q_stricmp(sh->name, strippedName) == 0) { + // match found + return sh; + } + } + + return tr.defaultShader; +} + + +inline qboolean IsShader(shader_t *sh, const char *name, const short *lightmapIndex, const byte *styles) +{ + int i; + + if (Q_stricmp(sh->name, name)) + { + return qfalse; + } + + if (!sh->defaultShader) + { + for(i=0;ilightmapIndex[i] != lightmapIndex[i]) + { + return qfalse; + } + if (sh->styles[i] != styles[i]) + { + return qfalse; + } + } + } + + return qtrue; +} + +/* +=============== +R_FindShader + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. + +In the interest of not requiring an explicit shader text entry to +be defined for every single image used in the game, three default +shader behaviors can be auto-created for any image: + +If lightmapIndex == LIGHTMAP_NONE, then the image will have +dynamic diffuse lighting applied to it, as apropriate for most +entity skin surfaces. + +If lightmapIndex == LIGHTMAP_2D, then the image will be used +for 2D rendering unless an explicit shader is found + +If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use +the vertex rgba modulate values, as apropriate for misc_model +pre-lit surfaces. + +Other lightmapIndex values will have a lightmap stage created +and src*dest blending applied with the texture, as apropriate for +most world construction surfaces. + +=============== +*/ +shader_t *R_FindShader( const char *name, const short *lightmapIndex, const byte *styles, qboolean mipRawImage ) +{ + char strippedName[MAX_QPATH]; + char fileName[MAX_QPATH]; + int hash; + const char *shaderText; + image_t *image; + shader_t *sh; + + if ( name[0] == 0 ) { + return tr.defaultShader; + } + + // use (fullbright) vertex lighting if the bsp file doesn't have + // lightmaps + if ( lightmapIndex[0] >= 0 && lightmapIndex[0] >= tr.numLightmaps ) + { + lightmapIndex = lightmapsVertex; + } + + COM_StripExtension( name, strippedName ); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh = hashTable[hash]; sh; sh = sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if (IsShader(sh, strippedName, lightmapIndex, styles)) + { + return sh; + } + } + + // clear the global shader + ClearGlobalShader(); + Q_strncpyz(shader.name, strippedName, sizeof(shader.name)); + memcpy(shader.lightmapIndex, lightmapIndex, sizeof(shader.lightmapIndex)); + memcpy(shader.styles, styles, sizeof(shader.styles)); + + // + // attempt to define shader from an explicit parameter file + // + shaderText = FindShaderInShaderText( strippedName ); + if ( shaderText ) { + if ( !ParseShader( &shaderText ) ) { + // had errors, so use default shader + shader.defaultShader = true; + } + sh = FinishShader(); + return sh; + } + + + // + // if not defined in the in-memory shader descriptions, + // look for a single TGA, BMP, or PCX + // + COM_StripExtension(name,fileName); +#ifdef DEDICATED + shader.defaultShader = qtrue; + return FinishShader(); +#else + image = R_FindImageFile( fileName, mipRawImage, mipRawImage, qtrue, mipRawImage ? GL_REPEAT : GL_CLAMP ); + if ( !image ) { + Com_DPrintf (S_COLOR_RED "Couldn't find image for shader %s\n", name ); + shader.defaultShader = true; + return FinishShader(); + } +#endif //!DEDICATED + // + // create the default shading commands + // + if ( shader.lightmapIndex[0] == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; +#ifdef _XBOX + shader.needsNormal = true; +#endif + } else if ( shader.lightmapIndex[0] == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex[0] == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex[0] == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image = tr.whiteImage; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image = tr.lightmaps[shader.lightmapIndex[0]]; + stages[0].bundle[0].isLightmap = qtrue; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + return FinishShader(); +} + +void ScanAndLoadShaderFiles( const char *path, bool doHash ); +shader_t *R_FindServerShader( const char *name, const short *lightmapIndex, const byte *styles, qboolean mipRawImage ) +{ + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh; + + if ( name[0] == 0 ) { + return tr.defaultShader; + } + + COM_StripExtension( name, strippedName ); + + hash = generateHashValue(strippedName, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh = hashTable[hash]; sh; sh = sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if (IsShader(sh, strippedName, lightmapIndex, styles)) + { + return sh; + } + } + + // clear the global shader + ClearGlobalShader(); + Q_strncpyz(shader.name, strippedName, sizeof(shader.name)); + memcpy(shader.lightmapIndex, lightmapIndex, sizeof(shader.lightmapIndex)); + memcpy(shader.styles, styles, sizeof(shader.styles)); + + shader.defaultShader = true; + return FinishShader(); +} + +qhandle_t RE_RegisterShaderFromImage(const char *name, short *lightmapIndex, byte *styles, image_t *image, qboolean mipRawImage) { + int i, hash; + shader_t *sh; + + hash = generateHashValue(name, FILE_HASH_SIZE); + + // + // see if the shader is already loaded + // + for (sh=hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if (IsShader(sh, name, lightmapIndex, styles)) + { + return sh->index; + } + } + + // clear the global shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + Q_strncpyz(shader.name, name, sizeof(shader.name)); + memcpy(shader.lightmapIndex, lightmapIndex, sizeof(shader.lightmapIndex)); + memcpy(shader.styles, styles, sizeof(shader.styles)); + + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + } + + // + // create the default shading commands + // + if ( shader.lightmapIndex[0] == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex[0] == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex[0] == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image = image; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex[0] == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image = tr.whiteImage; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image = tr.lightmaps[shader.lightmapIndex[0]]; + stages[0].bundle[0].isLightmap = qtrue; + stages[0].active = qtrue; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image = image; + stages[1].active = qtrue; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + sh = FinishShader(); + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShaderLightMap( const char *name, const short *lightmapIndex, const byte *styles ) +{ + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, lightmapIndex, styles, qtrue ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShader( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, lightmaps2d, stylesDefault, qtrue ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShaderNoMip + +For menu graphics that should never be picmiped +==================== +*/ +qhandle_t RE_RegisterShaderNoMip( const char *name ) { + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, lightmaps2d, stylesDefault, qfalse ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +//added for ui -rww +const char *RE_ShaderNameFromIndex(int index) +{ + assert(index >= 0 && index < tr.numShaders && tr.shaders[index]); + return tr.shaders[index]->name; +} + + +/* +==================== +R_GetShaderByHandle + +When a handle is passed in by another module, this range checks +it and returns a valid (possibly default) shader_t to be used internally. +==================== +*/ +shader_t *R_GetShaderByHandle( qhandle_t hShader ) { + if ( hShader < 0 ) { + Com_Printf (S_COLOR_YELLOW "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); // bk: FIXME name + return tr.defaultShader; + } + if ( hShader >= tr.numShaders ) { + Com_Printf (S_COLOR_YELLOW "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + return tr.shaders[hShader]; +} + +#ifndef DEDICATED +/* +=============== +R_ShaderList_f + +Dump information on all valid shaders to the console +A second parameter will cause it to print in sorted order +=============== +*/ +void R_ShaderList_f (void) { + int i; + int count; + shader_t *shader; + + Com_Printf ( "-----------------------\n"); + + count = 0; + for ( i = 0 ; i < tr.numShaders ; i++ ) { + if ( Cmd_Argc() > 1 ) { + shader = tr.sortedShaders[i]; + } else { + shader = tr.shaders[i]; + } + + Com_Printf ("%i ", shader->numUnfoggedPasses ); + + if (shader->lightmapIndex[0] >= 0 ) { + Com_Printf ( "L "); + } else { + Com_Printf ( " "); + } + if ( shader->multitextureEnv == GL_ADD ) { + Com_Printf ("MT(a) " ); + } else if ( shader->multitextureEnv == GL_MODULATE ) { + Com_Printf ("MT(m) " ); + } else if ( shader->multitextureEnv == GL_DECAL ) { + Com_Printf ("MT(d) " ); + } else { + Com_Printf (" " ); + } + if ( shader->explicitlyDefined ) { + Com_Printf ("E " ); + } else { + Com_Printf (" " ); + } + + if ( shader->sky ) + { + Com_Printf ("sky " ); + } else { + Com_Printf ("gen " ); + } + if ( shader->defaultShader ) { + Com_Printf ( ": %s (DEFAULTED)\n", shader->name); + } else { + Com_Printf ( ": %s\n", shader->name); + } + count++; + } + Com_Printf ( "%i total shaders\n", count); + Com_Printf ( "------------------\n"); +} + + +/* +==================== +ScanAndLoadShaderFiles + +Finds and loads all .shader files, combining them into +a single large text block that can be scanned for shader names + +rww - Do not access any ri or render data stuff that doesn't get init'd on +a dedicated server in here. I am calling it for dedicateds now because +we need all this shader BS for looking up surface indicators in skin +files if we want to be like SP. + +bto (VV) - Rather than keeping all the buffer pointers around forever and +creating more bugs, do the hash creation with the finalized shadertext. +Previous code only really worked if FS_ReadFile returned contiguous buffers +in ascending order on consecutive calls. +===================== +*/ +#define MAX_SHADER_FILES 4096 + +void ScanAndLoadShaderFiles( const char *path, bool doHash ) +{ + char *p; + int i; + char *oldp, *token, *hashMem; + int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size; + + // Shader text is only read in the first time it's needed! Gah! + if( !shaderText ) + { + int numShaders; + char **shaderFiles; + char *buffers[MAX_SHADER_FILES]; + int bufferSizes[MAX_SHADER_FILES]; // Optimization + + long sum = 0; + // scan for shader files + shaderFiles = /*ri.*/FS_ListFiles( path, ".shader", &numShaders ); + + if ( !shaderFiles || !numShaders ) + { + Com_Error(ERR_FATAL, "ERROR: no shader files found\n"); + return; + } + + if ( numShaders > MAX_SHADER_FILES ) { + numShaders = MAX_SHADER_FILES; + } + + // load and parse shader files + for ( i = 0; i < numShaders; i++ ) + { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "%s/%s", path, shaderFiles[i] ); + //Com_Printf( "...loading '%s'\n", filename ); + /*ri.*/FS_ReadFile( filename, (void **)&buffers[i] ); + if ( !buffers[i] ) { + /*ri.*/Com_Error( ERR_DROP, "Couldn't load %s", filename ); + } + sum += (bufferSizes[i] = COM_Compress( buffers[i] )); + } + + // free up memory + /*ri.*/FS_FreeFileList( shaderFiles ); + + // build single large buffer +// s_shaderText = (char *)/*ri.*/Hunk_Alloc( sum + numShaders*2, h_low ); + shaderText = (char *) Z_Malloc( sum + numShaders * 2, TAG_SHADERTEXT, qfalse, 4 ); + + // Optimization: reuse sum from here on to count how much data we've appended + sum = 0; + shaderText[0] = 0; + + // free in reverse order, so the temp files are all dumped + for ( i = numShaders - 1; i >= 0 ; i-- ) { + strcat( shaderText + sum, "\n" ); + strcat( shaderText + sum, buffers[i] ); + /*ri.*/FS_FreeFile( (void*) buffers[i] ); + // Start next strcat after this one (including \n) + sum += (bufferSizes[i] + 1); + } + } + + // If we're just calling this function to be sure that the shadertext + // doesn't fragment, then don't do anything else! + if( !doHash ) + return; + + // We'll let the renderer re-build the hash tables every time for now. VVFIXME - OPTIMIZE! + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + size = 0; + p = shaderText; + + // look for label + while ( 1 ) { + token = COM_ParseExt( (const char **)&p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTableSizes[hash]++; + size++; + SkipBracedSection((const char **)&p); + } + + size += MAX_SHADERTEXT_HASH; + + hashMem = (char *)/*ri.*/Hunk_Alloc( size * sizeof(char *), h_low ); + + for (i = 0; i < MAX_SHADERTEXT_HASH; i++) { + shaderTextHashTable[i] = (char **) hashMem; + hashMem = ((char *) hashMem) + ((shaderTextHashTableSizes[i] + 1) * sizeof(char *)); + } + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + p = shaderText; + // look for label + while ( 1 ) { + oldp = p; + token = COM_ParseExt( (const char **)&p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp; + + SkipBracedSection((const char **)&p); + } + + return; +} + +/* +==================== +R_CreateBlendedShader + + This takes 4 shaders (one per corner of a quad) and creates a blended shader the fades the textures over + eg. + if [A][A] + [B][B] + then the shader would be texture A at the top fading to texture B at the bottom + + This is highly biased towards terrain shaders ie vertex lit surfaces +==================== +*/ +//rwwRMG: Added: + +static void R_CopyStage(shaderStage_t *orig, shaderStage_t *stage) +{ + // Assumption: this stage has not been collapsed + *stage = *orig; //Just copy the whole thing! +} + +static void R_CreateBlendedStage(qhandle_t handle, int idx) +{ + shader_t *work; + + work = R_GetShaderByHandle(handle); + R_CopyStage(work->stages, stages + idx); + stages[idx].rgbGen = CGEN_EXACT_VERTEX; + stages[idx].alphaGen = AGEN_BLEND; + stages[idx].stateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHMASK_TRUE; + + if (stages[idx].ss) + { + stages[idx].ss->density *= 0.33f; + } +} + +static qhandle_t R_MergeShaders(const char *blendedName, qhandle_t a, qhandle_t b, qhandle_t c, bool surfaceSprites) +{ + shader_t *blended; + shader_t *work; + int current, i; + + R_SyncRenderThread(); + + // Set up default parameters + ClearGlobalShader(); + Q_strncpyz(shader.name, blendedName, sizeof(shader.name)); + memcpy(shader.lightmapIndex, lightmapsVertex, sizeof(shader.lightmapIndex)); + memcpy(shader.styles, stylesDefault, sizeof(shader.styles)); + shader.fogPass = FP_EQUAL; + + // Get the top left shader and set it up as pass 0 - it should be completely opaque + work = R_GetShaderByHandle(c); + stages[0].active = qtrue; + R_CopyStage(work->stages, stages); + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_BLEND; + stages[0].stateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ZERO | GLS_DEPTHMASK_TRUE; + shader.multitextureEnv = work->multitextureEnv; //jic + + // Go through the other verts and add a pass + R_CreateBlendedStage(a, 1); + R_CreateBlendedStage(b, 2); + + if ( surfaceSprites ) + { + current = 3; + work = R_GetShaderByHandle(a); + for(i=1;(inumUnfoggedPasses && currentstages[i].ss) + { + stages[current] = work->stages[i]; + // stages[current].ss->density *= 0.33f; + stages[current].ss->density *= 3; + current++; + } + } + + work = R_GetShaderByHandle(b); + for(i=1;(inumUnfoggedPasses && currentstages[i].ss) + { + stages[current] = work->stages[i]; + // stages[current].ss->density *= 0.33f; + stages[current].ss->density *= 3; + current++; + } + } + + work = R_GetShaderByHandle(c); + for(i=1;(inumUnfoggedPasses && currentstages[i].ss) + { + stages[current] = work->stages[i]; + // stages[current].ss->density *= 0.33f; + stages[current].ss->density *= 3; + current++; + } + } + } + + blended = FinishShader(); + return(blended->index); +} + + +// Create a 3 pass shader - the last 2 passes are alpha'd out + +qhandle_t R_CreateBlendedShader(qhandle_t a, qhandle_t b, qhandle_t c, bool surfaceSprites ) +{ + qhandle_t blended; + shader_t *work; + char blendedName[MAX_QPATH]; + char extendedName[MAX_QPATH + MAX_QPATH]; + + Com_sprintf(blendedName, MAX_QPATH, "blend(%d,%d,%d)", a, b, c); + if (!surfaceSprites) + { + strcat(blendedName, "noSS"); + } + + // Find if this shader has already been created + R_CreateExtendedName(extendedName, blendedName, lightmapsVertex, stylesDefault); + work = hashTable[generateHashValue(extendedName, FILE_HASH_SIZE)]; + for ( ; work; work = work->next) + { + if (Q_stricmp(work->name, extendedName) == 0) + { + return work->index; + } + } + + // Create new shader if it doesn't already exist + blended = R_MergeShaders(extendedName, a, b, c, surfaceSprites); + return(blended); +} + +/* +==================== +CreateInternalShaders +==================== +*/ +static void CreateInternalShaders( void ) { + tr.numShaders = 0; + + // init the default shader + Com_Memset( &shader, 0, sizeof( shader ) ); + Com_Memset( &stages, 0, sizeof( stages ) ); + + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + + memcpy(shader.lightmapIndex, lightmapsNone, sizeof(shader.lightmapIndex)); + memcpy(shader.styles, stylesDefault, sizeof(shader.styles)); + for ( int i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + } + + stages[0].bundle[0].image = tr.defaultImage; + stages[0].active = qtrue; + stages[0].stateBits = GLS_DEFAULT; + tr.defaultShader = FinishShader(); + + // shadow shader is just a marker + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + shader.sort = SS_BANNER; //SS_STENCIL_SHADOW; + tr.shadowShader = FinishShader(); + + // distortion shader is just a marker + Q_strncpyz( shader.name, "internal_distortion", sizeof( shader.name ) ); + shader.sort = SS_BLEND0; + shader.defaultShader = qfalse; + tr.distortionShader = FinishShader(); + shader.defaultShader = qtrue; + + +#ifndef _XBOX // GLOWXXX + #define GL_PROGRAM_ERROR_STRING_ARB 0x8874 + #define GL_PROGRAM_ERROR_POSITION_ARB 0x864B + + // Allocate and Load the global 'Glow' Vertex Program. - AReis + if ( qglGenProgramsARB ) + { + qglGenProgramsARB( 1, &tr.glowVShader ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, tr.glowVShader ); + qglProgramStringARB( GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen( ( char * ) g_strGlowVShaderARB ), g_strGlowVShaderARB ); + +// const GLubyte *strErr = qglGetString( GL_PROGRAM_ERROR_STRING_ARB ); + int iErrPos = 0; + qglGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, &iErrPos ); + assert( iErrPos == -1 ); + } + + // NOTE: I make an assumption here. If you have (current) nvidia hardware, you obviously support register combiners instead of fragment + // programs, so use those. The problem with this is that nv30 WILL support fragment shaders, breaking this logic. The good thing is that + // if you always ask for regcoms before fragment shaders, you'll always just use regcoms (problem solved... for now). - AReis + + // Load Pixel Shaders (either regcoms or fragprogs). + if ( qglCombinerParameteriNV ) + { + // The purpose of this regcom is to blend all the pixels together from the 4 texture units, but with their + // texture coordinates offset by 1 (or more) texels, effectively letting us blend adjoining pixels. The weight is + // used to either strengthen or weaken the pixel intensity. The more it diffuses (the higher the radius of the glow), + // the higher the intensity should be for a noticable effect. + // Regcom result is: ( tex1 * fBlurWeight ) + ( tex2 * fBlurWeight ) + ( tex2 * fBlurWeight ) + ( tex2 * fBlurWeight ) + + // VV guys, this is the pixel shader you would use instead :-) + /* + // c0 is the blur weight. + ps 1.1 + tex t0 + tex t1 + tex t2 + tex t3 + + mul r0, c0, t0; + madd r0, c0, t1, r0; + madd r0, c0, t2, r0; + madd r0, c0, t3, r0; + */ + tr.glowPShader = qglGenLists( 1 ); + qglNewList( tr.glowPShader, GL_COMPILE ); + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 2 ); + + // spare0 = fBlend * tex0 + fBlend * tex1. + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE1_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE0_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // spare1 = fBlend * tex2 + fBlend * tex3. + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE2_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE3_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_D_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER1_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE1_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // ( A * B ) + ( ( 1 - A ) * C ) + D = ( spare0 * 1 ) + ( ( 1 - spare0 ) * 0 ) + spare1 == spare0 + spare1. + qglFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglEndList(); + } + else if ( qglGenProgramsARB ) + { + qglGenProgramsARB( 1, &tr.glowPShader ); + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, tr.glowPShader ); + qglProgramStringARB( GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen( ( char * ) g_strGlowPShaderARB ), g_strGlowPShaderARB ); + +// const GLubyte *strErr = qglGetString( GL_PROGRAM_ERROR_STRING_ARB ); + int iErrPos = 0; + qglGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, &iErrPos ); + assert( iErrPos == -1 ); + } +#endif +} + +static void CreateExternalShaders( void ) { + tr.projectionShadowShader = R_FindShader( "projectionShadow", lightmapsNone, stylesDefault, qtrue ); + tr.projectionShadowShader->sort = SS_STENCIL_SHADOW; + tr.sunShader = R_FindShader( "sun", lightmapsNone, stylesDefault, qtrue ); +} + +#endif // !DEDICATED +/* +================== +R_InitShaders +================== +*/ +void R_InitShaders(qboolean server) +{ + //Com_Printf ("Initializing Shaders\n" ); + + Com_Memset(hashTable, 0, sizeof(hashTable)); + + deferLoad = qfalse; + +#ifndef DEDICATED + if (!server) + { + CreateInternalShaders(); + + ScanAndLoadShaderFiles("shaders", true); + + CreateExternalShaders(); + } +#endif +} diff --git a/codemp/renderer/tr_shadows.cpp b/codemp/renderer/tr_shadows.cpp new file mode 100644 index 0000000..c54add8 --- /dev/null +++ b/codemp/renderer/tr_shadows.cpp @@ -0,0 +1,724 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + + +/* + + for a projection shadow: + + point[x] += light vector * ( z - shadow plane ) + point[y] += + point[z] = shadow plane + + 1 0 light[x] / light[z] + +*/ +#ifndef _XBOX + +#define _STENCIL_REVERSE + +typedef struct { + int i2; + int facing; +} edgeDef_t; + +#define MAX_EDGE_DEFS 32 + +static edgeDef_t edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS]; +static int numEdgeDefs[SHADER_MAX_VERTEXES]; +static int facing[SHADER_MAX_INDEXES/3]; + +void R_AddEdgeDef( int i1, int i2, int facing ) { + int c; + + c = numEdgeDefs[ i1 ]; + if ( c == MAX_EDGE_DEFS ) { + return; // overflow + } + edgeDefs[ i1 ][ c ].i2 = i2; + edgeDefs[ i1 ][ c ].facing = facing; + + numEdgeDefs[ i1 ]++; +} + +void R_RenderShadowEdges( void ) { + int i; + int c; + int j; + int i2; + int c_edges, c_rejected; +#if 0 + int c2, k; + int hit[2]; +#endif +#ifdef _STENCIL_REVERSE + int numTris; + int o1, o2, o3; +#endif + + // an edge is NOT a silhouette edge if its face doesn't face the light, + // or if it has a reverse paired edge that also faces the light. + // A well behaved polyhedron would have exactly two faces for each edge, + // but lots of models have dangling edges or overfanned edges + c_edges = 0; + c_rejected = 0; + + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + c = numEdgeDefs[ i ]; + for ( j = 0 ; j < c ; j++ ) { + if ( !edgeDefs[ i ][ j ].facing ) { + continue; + } + + //with this system we can still get edges shared by more than 2 tris which + //produces artifacts including seeing the shadow through walls. So for now + //we are going to render all edges even though it is a tiny bit slower. -rww +#if 1 + i2 = edgeDefs[ i ][ j ].i2; + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i ] ); + qglVertex3fv( tess.xyz[ i + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); + qglEnd(); +#else + hit[0] = 0; + hit[1] = 0; + + i2 = edgeDefs[ i ][ j ].i2; + c2 = numEdgeDefs[ i2 ]; + for ( k = 0 ; k < c2 ; k++ ) { + if ( edgeDefs[ i2 ][ k ].i2 == i ) { + hit[ edgeDefs[ i2 ][ k ].facing ]++; + } + } + + // if it doesn't share the edge with another front facing + // triangle, it is a sil edge + if (hit[1] != 1) + { + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i ] ); + qglVertex3fv( tess.xyz[ i + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); + qglEnd(); + c_edges++; + } else { + c_rejected++; + } +#endif + } + } + +#ifdef _STENCIL_REVERSE + //Carmack Reverse method requires that volumes + //be capped properly -rww + numTris = tess.numIndexes / 3; + + for ( i = 0 ; i < numTris ; i++ ) + { + if ( !facing[i] ) + { + continue; + } + + o1 = tess.indexes[ i*3 + 0 ]; + o2 = tess.indexes[ i*3 + 1 ]; + o3 = tess.indexes[ i*3 + 2 ]; + + qglBegin(GL_TRIANGLES); + qglVertex3fv(tess.xyz[o1]); + qglVertex3fv(tess.xyz[o2]); + qglVertex3fv(tess.xyz[o3]); + qglEnd(); + qglBegin(GL_TRIANGLES); + qglVertex3fv(tess.xyz[o3 + tess.numVertexes]); + qglVertex3fv(tess.xyz[o2 + tess.numVertexes]); + qglVertex3fv(tess.xyz[o1 + tess.numVertexes]); + qglEnd(); + } +#endif +} + +//#define _DEBUG_STENCIL_SHADOWS + +/* +================= +RB_ShadowTessEnd + +triangleFromEdge[ v1 ][ v2 ] + + + set triangle from edge( v1, v2, tri ) + if ( facing[ triangleFromEdge[ v1 ][ v2 ] ] && !facing[ triangleFromEdge[ v2 ][ v1 ] ) { + } +================= +*/ +void RB_DoShadowTessEnd( vec3_t lightPos ); +void RB_ShadowTessEnd( void ) +{ +#if 0 + if (backEnd.currentEntity && + (backEnd.currentEntity->directedLight[0] || + backEnd.currentEntity->directedLight[1] || + backEnd.currentEntity->directedLight[2])) + { //an ent that has its light set for it + RB_DoShadowTessEnd(NULL); + return; + } + +// if (!tess.dlightBits) +// { +// return; +// } + + int i = 0; + dlight_t *dl; + + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori ); +/* while (i < tr.refdef.num_dlights) + { + if (tess.dlightBits & (1 << i)) + { + dl = &tr.refdef.dlights[i]; + + RB_DoShadowTessEnd(dl->transformed); + } + + i++; + } + */ + dl = &tr.refdef.dlights[0]; + + RB_DoShadowTessEnd(dl->transformed); + +#else //old ents-only way + RB_DoShadowTessEnd(NULL); +#endif +} + +void RB_DoShadowTessEnd( vec3_t lightPos ) +{ + int i; + int numTris; + vec3_t lightDir; + + // we can only do this if we have enough space in the vertex buffers + if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) { + return; + } + + if ( glConfig.stencilBits < 4 ) { + return; + } + +#if 1 //controlled method - try to keep shadows in range so they don't show through so much -rww + vec3_t worldxyz; + vec3_t entLight; + float groundDist; + + VectorCopy( backEnd.currentEntity->lightDir, entLight ); + entLight[2] = 0.0f; + VectorNormalize(entLight); + + //Oh well, just cast them straight down no matter what onto the ground plane. + //This presets no chance of screwups and still looks better than a stupid + //shader blob. + VectorSet(lightDir, entLight[0]*0.3f, entLight[1]*0.3f, 1.0f); + // project vertexes away from light direction + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + //add or.origin to vert xyz to end up with world oriented coord, then figure + //out the ground pos for the vert to project the shadow volume to + VectorAdd(tess.xyz[i], backEnd.ori.origin, worldxyz); + groundDist = worldxyz[2] - backEnd.currentEntity->e.shadowPlane; + groundDist += 16.0f; //fudge factor + VectorMA( tess.xyz[i], -groundDist, lightDir, tess.xyz[i+tess.numVertexes] ); + } +#else + if (lightPos) + { + for ( i = 0 ; i < tess.numVertexes ; i++ ) + { + tess.xyz[i+tess.numVertexes][0] = tess.xyz[i][0]+(( tess.xyz[i][0]-lightPos[0] )*128.0f); + tess.xyz[i+tess.numVertexes][1] = tess.xyz[i][1]+(( tess.xyz[i][1]-lightPos[1] )*128.0f); + tess.xyz[i+tess.numVertexes][2] = tess.xyz[i][2]+(( tess.xyz[i][2]-lightPos[2] )*128.0f); + } + } + else + { + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + + // project vertexes away from light direction + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + VectorMA( tess.xyz[i], -512, lightDir, tess.xyz[i+tess.numVertexes] ); + } + } +#endif + // decide which triangles face the light + Com_Memset( numEdgeDefs, 0, 4 * tess.numVertexes ); + + numTris = tess.numIndexes / 3; + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + vec3_t d1, d2, normal; + float *v1, *v2, *v3; + float d; + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + v1 = tess.xyz[ i1 ]; + v2 = tess.xyz[ i2 ]; + v3 = tess.xyz[ i3 ]; + + if (!lightPos) + { + VectorSubtract( v2, v1, d1 ); + VectorSubtract( v3, v1, d2 ); + CrossProduct( d1, d2, normal ); + + d = DotProduct( normal, lightDir ); + } + else + { + float planeEq[4]; + planeEq[0] = v1[1]*(v2[2]-v3[2]) + v2[1]*(v3[2]-v1[2]) + v3[1]*(v1[2]-v2[2]); + planeEq[1] = v1[2]*(v2[0]-v3[0]) + v2[2]*(v3[0]-v1[0]) + v3[2]*(v1[0]-v2[0]); + planeEq[2] = v1[0]*(v2[1]-v3[1]) + v2[0]*(v3[1]-v1[1]) + v3[0]*(v1[1]-v2[1]); + planeEq[3] = -( v1[0]*( v2[1]*v3[2] - v3[1]*v2[2] ) + + v2[0]*(v3[1]*v1[2] - v1[1]*v3[2]) + + v3[0]*(v1[1]*v2[2] - v2[1]*v1[2]) ); + + d = planeEq[0]*lightPos[0]+ + planeEq[1]*lightPos[1]+ + planeEq[2]*lightPos[2]+ + planeEq[3]; + } + + if ( d > 0 ) { + facing[ i ] = 1; + } else { + facing[ i ] = 0; + } + + // create the edges + R_AddEdgeDef( i1, i2, facing[ i ] ); + R_AddEdgeDef( i2, i3, facing[ i ] ); + R_AddEdgeDef( i3, i1, facing[ i ] ); + } + + GL_Bind( tr.whiteImage ); + //qglEnable( GL_CULL_FACE ); + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + +#ifndef _DEBUG_STENCIL_SHADOWS + qglColor3f( 0.2f, 0.2f, 0.2f ); + + // don't write to the color buffer + qglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_ALWAYS, 1, 255 ); +#else + qglColor3f( 1.0f, 0.0f, 0.0f ); + qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + //qglDisable(GL_DEPTH_TEST); +#endif + +#ifdef _STENCIL_REVERSE + qglDepthFunc(GL_LESS); + + //now using the Carmack Reverse -rww + if ( backEnd.viewParms.isMirror ) { + //qglCullFace( GL_BACK ); + GL_Cull(CT_BACK_SIDED); + qglStencilOp( GL_KEEP, GL_INCR, GL_KEEP ); + + R_RenderShadowEdges(); + + //qglCullFace( GL_FRONT ); + GL_Cull(CT_FRONT_SIDED); + qglStencilOp( GL_KEEP, GL_DECR, GL_KEEP ); + + R_RenderShadowEdges(); + } else { + //qglCullFace( GL_FRONT ); + GL_Cull(CT_FRONT_SIDED); + qglStencilOp( GL_KEEP, GL_INCR, GL_KEEP ); + + R_RenderShadowEdges(); + + //qglCullFace( GL_BACK ); + GL_Cull(CT_BACK_SIDED); + qglStencilOp( GL_KEEP, GL_DECR, GL_KEEP ); + + R_RenderShadowEdges(); + } + + qglDepthFunc(GL_LEQUAL); +#else + // mirrors have the culling order reversed + if ( backEnd.viewParms.isMirror ) { + qglCullFace( GL_FRONT ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + qglCullFace( GL_BACK ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + } else { + qglCullFace( GL_BACK ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + qglCullFace( GL_FRONT ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + } +#endif + + // reenable writing to the color buffer + qglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); + +#ifdef _DEBUG_STENCIL_SHADOWS + qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} + + +/* +================= +RB_ShadowFinish + +Darken everything that is is a shadow volume. +We have to delay this until everything has been shadowed, +because otherwise shadows from different body parts would +overlap and double darken. +================= +*/ +void RB_ShadowFinish( void ) { + if ( r_shadows->integer != 2 ) { + return; + } + if ( glConfig.stencilBits < 4 ) { + return; + } + +#ifdef _DEBUG_STENCIL_SHADOWS + return; +#endif + + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_NOTEQUAL, 0, 255 ); + + qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); + + bool planeZeroBack = false; + if (qglIsEnabled(GL_CLIP_PLANE0)) + { + planeZeroBack = true; + qglDisable (GL_CLIP_PLANE0); + } + GL_Cull(CT_TWO_SIDED); + //qglDisable (GL_CULL_FACE); + + GL_Bind( tr.whiteImage ); + + qglPushMatrix(); + qglLoadIdentity (); + +// qglColor3f( 0.6f, 0.6f, 0.6f ); +// GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO ); + +// qglColor3f( 1, 0, 0 ); +// GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + + qglColor4f( 0.0f, 0.0f, 0.0f, 0.5f ); + //GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglBegin( GL_QUADS ); + qglVertex3f( -100, 100, -10 ); + qglVertex3f( 100, 100, -10 ); + qglVertex3f( 100, -100, -10 ); + qglVertex3f( -100, -100, -10 ); + qglEnd (); + + qglColor4f(1,1,1,1); + qglDisable( GL_STENCIL_TEST ); + if (planeZeroBack) + { + qglEnable (GL_CLIP_PLANE0); + } + qglPopMatrix(); +} + + +/* +================= +RB_ProjectionShadowDeform + +================= +*/ +void RB_ProjectionShadowDeform( void ) { + float *xyz; + int i; + float h; + vec3_t ground; + vec3_t light; + float groundDist; + float d; + vec3_t lightDir; + + xyz = ( float * ) tess.xyz; + + ground[0] = backEnd.ori.axis[0][2]; + ground[1] = backEnd.ori.axis[1][2]; + ground[2] = backEnd.ori.axis[2][2]; + + groundDist = backEnd.ori.origin[2] - backEnd.currentEntity->e.shadowPlane; + + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + d = DotProduct( lightDir, ground ); + // don't let the shadows get too long or go negative + if ( d < 0.5 ) { + VectorMA( lightDir, (0.5 - d), ground, lightDir ); + d = DotProduct( lightDir, ground ); + } + d = 1.0 / d; + + light[0] = lightDir[0] * d; + light[1] = lightDir[1] * d; + light[2] = lightDir[2] * d; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + h = DotProduct( xyz, ground ) + groundDist; + + xyz[0] -= light[0] * h; + xyz[1] -= light[1] * h; + xyz[2] -= light[2] * h; + } +} + +#endif // _XBOX + +//update tr.screenImage +void RB_CaptureScreenImage(void) +{ + int radX = 2048; + int radY = 2048; + int x = glConfig.vidWidth/2; + int y = glConfig.vidHeight/2; + int cX, cY; + + GL_Bind( tr.screenImage ); + //using this method, we could pixel-filter the texture and all sorts of crazy stuff. + //but, it is slow as hell. + /* + static byte *tmp = NULL; + if (!tmp) + { + tmp = (byte *)Z_Malloc((sizeof(byte)*4)*(glConfig.vidWidth*glConfig.vidHeight), TAG_ICARUS, qtrue); + } + qglReadPixels(0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_UNSIGNED_BYTE, tmp); + qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmp); + */ + + if (radX > glConfig.maxTextureSize) + { + radX = glConfig.maxTextureSize; + } + if (radY > glConfig.maxTextureSize) + { + radY = glConfig.maxTextureSize; + } + + while (glConfig.vidWidth < radX) + { + radX /= 2; + } + while (glConfig.vidHeight < radY) + { + radY /= 2; + } + + cX = x-(radX/2); + cY = y-(radY/2); + + if (cX+radX > glConfig.vidWidth) + { //would it go off screen? + cX = glConfig.vidWidth-radX; + } + else if (cX < 0) + { //cap it off at 0 + cX = 0; + } + + if (cY+radY > glConfig.vidHeight) + { //would it go off screen? + cY = glConfig.vidHeight-radY; + } + else if (cY < 0) + { //cap it off at 0 + cY = 0; + } + +#ifndef _XBOX + qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, cX, cY, radX, radY, 0); +#else + qglCopyBackBufferToTexEXT(radX, radY, cX, cY, (cX + radX), (cY + radY)); +#endif // _XBOX +} + + +//yeah.. not really shadow-related.. but it's stencil-related. -rww +float tr_distortionAlpha = 1.0f; //opaque +float tr_distortionStretch = 0.0f; //no stretch override +qboolean tr_distortionPrePost = qfalse; //capture before postrender phase? +qboolean tr_distortionNegate = qfalse; //negative blend mode +void RB_DistortionFill(void) +{ + float alpha = tr_distortionAlpha; + float spost = 0.0f; + float spost2 = 0.0f; + + if ( glConfig.stencilBits < 4 ) + { + return; + } + + //ok, cap the stupid thing now I guess + if (!tr_distortionPrePost) + { + RB_CaptureScreenImage(); + } + + qglEnable(GL_STENCIL_TEST); + qglStencilFunc(GL_NOTEQUAL, 0, 0xFFFFFFFF); + qglStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + + qglDisable (GL_CLIP_PLANE0); + GL_Cull( CT_TWO_SIDED ); + + //reset the view matrices and go into ortho mode + qglMatrixMode(GL_PROJECTION); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho(0, glConfig.vidWidth, glConfig.vidHeight, 32, -1, 1); + qglMatrixMode(GL_MODELVIEW); + qglPushMatrix(); + qglLoadIdentity(); + + if (tr_distortionStretch) + { //override + spost = tr_distortionStretch; + spost2 = tr_distortionStretch; + } + else + { //do slow stretchy effect + spost = sin(tr.refdef.time*0.0005f); + if (spost < 0.0f) + { + spost = -spost; + } + spost *= 0.2f; + + spost2 = sin(tr.refdef.time*0.0005f); + if (spost2 < 0.0f) + { + spost2 = -spost2; + } + spost2 *= 0.08f; + } + + if (alpha != 1.0f) + { //blend + GL_State(GLS_SRCBLEND_SRC_ALPHA|GLS_DSTBLEND_SRC_ALPHA); + } + else + { //be sure to reset the draw state + GL_State(0); + } + +#ifdef _XBOX + qglBeginEXT(GL_QUADS, 4, 0, 0, 4, 0); +#else + qglBegin(GL_QUADS); +#endif // _XBOX + qglColor4f(1.0f, 1.0f, 1.0f, alpha); + qglTexCoord2f(0+spost2, 1-spost); + qglVertex2f(0, 0); + + qglTexCoord2f(0+spost2, 0+spost); + qglVertex2f(0, glConfig.vidHeight); + + qglTexCoord2f(1-spost2, 0+spost); + qglVertex2f(glConfig.vidWidth, glConfig.vidHeight); + + qglTexCoord2f(1-spost2, 1-spost); + qglVertex2f(glConfig.vidWidth, 0); + qglEnd(); + + if (tr_distortionAlpha == 1.0f && tr_distortionStretch == 0.0f) + { //no overrides + if (tr_distortionNegate) + { //probably the crazy alternate saber trail + alpha = 0.8f; + GL_State(GLS_SRCBLEND_ZERO|GLS_DSTBLEND_ONE_MINUS_SRC_COLOR); + } + else + { + alpha = 0.5f; + GL_State(GLS_SRCBLEND_SRC_ALPHA|GLS_DSTBLEND_SRC_ALPHA); + } + + spost = sin(tr.refdef.time*0.0008f); + if (spost < 0.0f) + { + spost = -spost; + } + spost *= 0.08f; + + spost2 = sin(tr.refdef.time*0.0008f); + if (spost2 < 0.0f) + { + spost2 = -spost2; + } + spost2 *= 0.2f; + +#ifdef _XBOX + qglBeginEXT(GL_QUADS, 4, 0, 0, 4, 0); +#else + qglBegin(GL_QUADS); +#endif // _XBOX + qglColor4f(1.0f, 1.0f, 1.0f, alpha); + qglTexCoord2f(0+spost2, 1-spost); + qglVertex2f(0, 0); + + qglTexCoord2f(0+spost2, 0+spost); + qglVertex2f(0, glConfig.vidHeight); + + qglTexCoord2f(1-spost2, 0+spost); + qglVertex2f(glConfig.vidWidth, glConfig.vidHeight); + + qglTexCoord2f(1-spost2, 1-spost); + qglVertex2f(glConfig.vidWidth, 0); + qglEnd(); + } + + //pop the view matrices back + qglMatrixMode(GL_PROJECTION); + qglPopMatrix(); + qglMatrixMode(GL_MODELVIEW); + qglPopMatrix(); + + qglDisable( GL_STENCIL_TEST ); +} diff --git a/codemp/renderer/tr_sky.cpp b/codemp/renderer/tr_sky.cpp new file mode 100644 index 0000000..7e68385 --- /dev/null +++ b/codemp/renderer/tr_sky.cpp @@ -0,0 +1,849 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_sky.c +#include "tr_local.h" + +#define SKY_SUBDIVISIONS 8 +#define HALF_SKY_SUBDIVISIONS (SKY_SUBDIVISIONS/2) + +static float s_cloudTexCoords[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; +static float s_cloudTexP[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; + +extern bool g_bRenderGlowingObjects; + +/* +=================================================================================== + +POLYGON TO BOX SIDE PROJECTION + +=================================================================================== +*/ + +static vec3_t sky_clip[6] = +{ + {1,1,0}, + {1,-1,0}, + {0,-1,1}, + {0,1,1}, + {1,0,1}, + {-1,0,1} +}; + +static float sky_mins[2][6], sky_maxs[2][6]; +static float sky_min, sky_max; + +/* +================ +AddSkyPolygon +================ +*/ +static void AddSkyPolygon (int nump, vec3_t vecs) +{ + int i,j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; + // s = [0]/[2], t = [1]/[2] + static int vec_to_st[6][3] = + { + {-2,3,1}, + {2,3,-1}, + + {1,3,2}, + {-1,3,-2}, + + {-2,-1,3}, + {-2,1,-3} + + // {-1,2,3}, + // {1,2,-3} + }; + + // decide which face it maps to + VectorCopy (vec3_origin, v); + for (i=0, vp=vecs ; i av[1] && av[0] > av[2]) + { + if (v[0] < 0) + axis = 1; + else + axis = 0; + } + else if (av[1] > av[2] && av[1] > av[0]) + { + if (v[1] < 0) + axis = 3; + else + axis = 2; + } + else + { + if (v[2] < 0) + axis = 5; + else + axis = 4; + } + + // project new texture coords + for (i=0 ; i 0) + dv = vecs[j - 1]; + else + dv = -vecs[-j - 1]; + if (dv < 0.001) + continue; // don't divide by zero + j = vec_to_st[axis][0]; + if (j < 0) + s = -vecs[-j -1] / dv; + else + s = vecs[j-1] / dv; + j = vec_to_st[axis][1]; + if (j < 0) + t = -vecs[-j -1] / dv; + else + t = vecs[j-1] / dv; + + if (s < sky_mins[0][axis]) + sky_mins[0][axis] = s; + if (t < sky_mins[1][axis]) + sky_mins[1][axis] = t; + if (s > sky_maxs[0][axis]) + sky_maxs[0][axis] = s; + if (t > sky_maxs[1][axis]) + sky_maxs[1][axis] = t; + } +} + +#define ON_EPSILON 0.1f // point on plane side epsilon +#define MAX_CLIP_VERTS 64 +/* +================ +ClipSkyPolygon +================ +*/ +static void ClipSkyPolygon (int nump, vec3_t vecs, int stage) +{ + float *norm; + float *v; + qboolean front, back; + float d, e; + float dists[MAX_CLIP_VERTS]; + int sides[MAX_CLIP_VERTS]; + vec3_t newv[2][MAX_CLIP_VERTS]; + int newc[2]; + int i, j; + + if (nump > MAX_CLIP_VERTS-2) + Com_Error (ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS"); + if (stage == 6) + { // fully clipped, so draw it + AddSkyPolygon (nump, vecs); + return; + } + + front = back = qfalse; + norm = sky_clip[stage]; + for (i=0, v = vecs ; i ON_EPSILON) + { + front = qtrue; + sides[i] = SIDE_FRONT; + } + else if (d < -ON_EPSILON) + { + back = qtrue; + sides[i] = SIDE_BACK; + } + else + sides[i] = SIDE_ON; + dists[i] = d; + } + + if (!front || !back) + { // not clipped + ClipSkyPolygon (nump, vecs, stage+1); + return; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + VectorCopy (vecs, (vecs+(i*3)) ); + newc[0] = newc[1] = 0; + + for (i=0, v = vecs ; inumIndexes; i += 3 ) + { + for (j = 0 ; j < 3 ; j++) + { + VectorSubtract( input->xyz[input->indexes[i+j]], + backEnd.viewParms.ori.origin, + p[j] ); + } + ClipSkyPolygon( 3, p[0], 0 ); + } +} + +/* +=================================================================================== + +CLOUD VERTEX GENERATION + +=================================================================================== +*/ + +/* +** MakeSkyVec +** +** Parms: s, t range from -1 to 1 +*/ +static void MakeSkyVec( float s, float t, int axis, float outSt[2], vec3_t outXYZ ) +{ + // 1 = s, 2 = t, 3 = 2048 + static int st_to_vec[6][3] = + { + {3,-1,2}, + {-3,1,2}, + + {1,3,2}, + {-1,-3,2}, + + {-2,-1,3}, // 0 degrees yaw, look straight up + {2,-1,-3} // look straight down + }; + + vec3_t b; + int j, k; + float boxSize; + + boxSize = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + b[0] = s*boxSize; + b[1] = t*boxSize; + b[2] = boxSize; + + for (j=0 ; j<3 ; j++) + { + k = st_to_vec[axis][j]; + if (k < 0) + { + outXYZ[j] = -b[-k - 1]; + } + else + { + outXYZ[j] = b[k - 1]; + } + } + + // avoid bilerp seam + s = (s+1)*0.5; + t = (t+1)*0.5; + if (s < sky_min) + { + s = sky_min; + } + else if (s > sky_max) + { + s = sky_max; + } + + if (t < sky_min) + { + t = sky_min; + } + else if (t > sky_max) + { + t = sky_max; + } + + t = 1.0 - t; + + + if ( outSt ) + { + outSt[0] = s; + outSt[1] = t; + } +} + +static vec3_t s_skyPoints[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; +static float s_skyTexCoords[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; + +static void DrawSkySide( struct image_s *image, const int mins[2], const int maxs[2] ) +{ + int s, t; + + GL_Bind( image ); + +#ifdef _XBOX + int verts = ((maxs[0]+HALF_SKY_SUBDIVISIONS) - (mins[0]+HALF_SKY_SUBDIVISIONS)) * 2 + 2; +#endif + + for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t < maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { +#ifdef _XBOX + qglBeginEXT( GL_TRIANGLE_STRIP, verts, 0, 0, verts, 0); +#else + qglBegin( GL_TRIANGLE_STRIP ); +#endif + + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + qglTexCoord2fv( s_skyTexCoords[t][s] ); + qglVertex3fv( s_skyPoints[t][s] ); + + qglTexCoord2fv( s_skyTexCoords[t+1][s] ); + qglVertex3fv( s_skyPoints[t+1][s] ); + } + + qglEnd(); + } +} + +static void DrawSkyBox( shader_t *shader ) +{ + int i; + + sky_min = 0; + sky_max = 1; + + Com_Memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) ); + + for (i=0 ; i<6 ; i++) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS; + sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS; + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + s_skyTexCoords[t][s], + s_skyPoints[t][s] ); + } + } + + DrawSkySide( shader->sky->outerbox[i], + sky_mins_subd, + sky_maxs_subd ); + } + +} + +static void FillCloudySkySide( const int mins[2], const int maxs[2], qboolean addIndexes ) +{ + int s, t; + int vertexStart = tess.numVertexes; + int tHeight, sWidth; + + tHeight = maxs[1] - mins[1] + 1; + sWidth = maxs[0] - mins[0] + 1; + + for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t <= maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + VectorAdd( s_skyPoints[t][s], backEnd.viewParms.ori.origin, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = s_skyTexCoords[t][s][0]; + tess.texCoords[tess.numVertexes][0][1] = s_skyTexCoords[t][s][1]; + + tess.numVertexes++; + + if ( tess.numVertexes >= SHADER_MAX_VERTEXES ) + { + Com_Error( ERR_DROP, "SHADER_MAX_VERTEXES hit in FillCloudySkySide()\n" ); + } + } + } + + // only add indexes for one pass, otherwise it would draw multiple times for each pass + if ( addIndexes ) { + for ( t = 0; t < tHeight-1; t++ ) + { + for ( s = 0; s < sWidth-1; s++ ) + { + tess.indexes[tess.numIndexes] = vertexStart + s + t * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + } + } + } +} + +static void FillCloudBox( const shader_t *shader, int stage ) +{ + int i; + + for ( i =0; i < 6; i++ ) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + float MIN_T; + + if ( 1 ) // FIXME? shader->sky->fullClouds ) + { + MIN_T = -HALF_SKY_SUBDIVISIONS; + + // still don't want to draw the bottom, even if fullClouds + if ( i == 5 ) + continue; + } + else + { + switch( i ) + { + case 0: + case 1: + case 2: + case 3: + MIN_T = -1; + break; + case 5: + // don't draw clouds beneath you + continue; + case 4: // top + default: + MIN_T = -HALF_SKY_SUBDIVISIONS; + break; + } + } + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = myftol( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ); + sky_mins_subd[1] = myftol( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ); + sky_maxs_subd[0] = myftol( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ); + sky_maxs_subd[1] = myftol( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ); + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < MIN_T ) + sky_mins_subd[1] = MIN_T; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < MIN_T ) + sky_maxs_subd[1] = MIN_T; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + s_skyPoints[t][s] ); + + s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0]; + s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1]; + } + } + + // only add indexes for first stage + FillCloudySkySide( sky_mins_subd, sky_maxs_subd, (qboolean)( stage == 0 ) ); + } +} + +/* +** R_BuildCloudData +*/ +void R_BuildCloudData( shaderCommands_t *input ) +{ + int i; + shader_t *shader; + + shader = input->shader; + + assert( shader->sky ); + + sky_min = 1.0 / 256.0f; // FIXME: not correct? + sky_max = 255.0 / 256.0f; + + // set up for drawing + tess.numIndexes = 0; + tess.numVertexes = 0; + + if ( input->shader->sky->cloudHeight ) + { + for ( i = 0; i < input->shader->numUnfoggedPasses; i++ ) + { + FillCloudBox( input->shader, i ); + } + } +} + +/* +** R_InitSkyTexCoords +** Called when a sky shader is parsed +*/ +#define SQR( a ) ((a)*(a)) +void R_InitSkyTexCoords( float heightCloud ) +{ + int i, s, t; + float radiusWorld = 4096; + float p; + float sRad, tRad; + vec3_t skyVec; + vec3_t v; + + // init zfar so MakeSkyVec works even though + // a world hasn't been bounded + backEnd.viewParms.zFar = 1024; + + for ( i = 0; i < 6; i++ ) + { + for ( t = 0; t <= SKY_SUBDIVISIONS; t++ ) + { + for ( s = 0; s <= SKY_SUBDIVISIONS; s++ ) + { + // compute vector from view origin to sky side integral point + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + skyVec ); + + // compute parametric value 'p' that intersects with cloud layer + p = ( 1.0f / ( 2 * DotProduct( skyVec, skyVec ) ) ) * + ( -2 * skyVec[2] * radiusWorld + + 2 * sqrt( SQR( skyVec[2] ) * SQR( radiusWorld ) + + 2 * SQR( skyVec[0] ) * radiusWorld * heightCloud + + SQR( skyVec[0] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[1] ) * radiusWorld * heightCloud + + SQR( skyVec[1] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[2] ) * radiusWorld * heightCloud + + SQR( skyVec[2] ) * SQR( heightCloud ) ) ); + + s_cloudTexP[i][t][s] = p; + + // compute intersection point based on p + VectorScale( skyVec, p, v ); + v[2] += radiusWorld; + + // compute vector from world origin to intersection point 'v' + VectorNormalize( v ); + + sRad = Q_acos( v[0] ); + tRad = Q_acos( v[1] ); + + s_cloudTexCoords[i][t][s][0] = sRad; + s_cloudTexCoords[i][t][s][1] = tRad; + } + } + } +} + +//====================================================================================== + +/* +** RB_DrawSun +*/ +void RB_DrawSun( void ) { + float size; + float dist; + vec3_t origin, vec1, vec2; + vec3_t temp; + + if ( !backEnd.skyRenderedThisView ) { + return; + } + if ( !r_drawSun->integer ) { + return; + } + qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + qglTranslatef (backEnd.viewParms.ori.origin[0], backEnd.viewParms.ori.origin[1], backEnd.viewParms.ori.origin[2]); + + dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + size = dist * 0.4; + + VectorScale( tr.sunDirection, dist, origin ); + PerpendicularVector( vec1, tr.sunDirection ); + CrossProduct( tr.sunDirection, vec1, vec2 ); + + VectorScale( vec1, size, vec1 ); + VectorScale( vec2, size, vec2 ); + + // farthest depth range + qglDepthRange( 1.0, 1.0 ); + + // FIXME: use quad stamp + RB_BeginSurface( tr.sunShader, tess.fogNum ); + VectorCopy( origin, temp ); + VectorSubtract( temp, vec1, temp ); + VectorSubtract( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorAdd( temp, vec1, temp ); + VectorSubtract( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorAdd( temp, vec1, temp ); + VectorAdd( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorSubtract( temp, vec1, temp ); + VectorAdd( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); +} + + + + +/* +================ +RB_StageIteratorSky + +All of the visible sky triangles are in tess + +Other things could be stuck in here, like birds in the sky, etc +================ +*/ +void RB_StageIteratorSky( void ) +{ + if ( g_bRenderGlowingObjects ) + return; + + if ( r_fastsky->integer ) { + return; + } + + if (skyboxportal && !(backEnd.refdef.rdflags & RDF_SKYBOXPORTAL)) + { + return; + } + + // go through all the polygons and project them onto + // the sky box to see which blocks on each side need + // to be drawn + RB_ClipSkyPolygons( &tess ); + + // r_showsky will let all the sky blocks be drawn in + // front of everything to allow developers to see how + // much sky is getting sucked in + if ( r_showsky->integer ) { + qglDepthRange( 0.0, 0.0 ); + } else { +#ifdef _XBOX + qglDepthRange( 0.99, 1.0 ); +#else + qglDepthRange( 1.0, 1.0 ); +#endif + } + + // draw the outer skybox + if ( tess.shader->sky->outerbox[0] && tess.shader->sky->outerbox[0] != tr.defaultImage ) { + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + + qglPushMatrix (); + GL_State( 0 ); + qglTranslatef (backEnd.viewParms.ori.origin[0], backEnd.viewParms.ori.origin[1], backEnd.viewParms.ori.origin[2]); + + DrawSkyBox( tess.shader ); + + qglPopMatrix(); + } + + // generate the vertexes for all the clouds, which will be drawn + // by the generic shader routine + R_BuildCloudData( &tess ); + + if (tess.numIndexes && tess.numVertexes) + { + RB_StageIteratorGeneric(); + } + + // draw the inner skybox + + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); + + // note that sky was drawn so we will draw a sun later + backEnd.skyRenderedThisView = qtrue; +} + diff --git a/codemp/renderer/tr_surface.cpp b/codemp/renderer/tr_surface.cpp new file mode 100644 index 0000000..5eb7776 --- /dev/null +++ b/codemp/renderer/tr_surface.cpp @@ -0,0 +1,2100 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_surf.c +#include "tr_local.h" + +#ifdef _XBOX +#include "../win32/glw_win_dx8.h" +#include "../cgame/cg_local.h" +#include "../client/cl_data.h" +#endif + +/* + + THIS ENTIRE FILE IS BACK END + +backEnd.currentEntity will be valid. + +Tess_Begin has already been called for the surface's shader. + +The modelview matrix will be set. + +It is safe to actually issue drawing commands here if you don't want to +use the shader system. +*/ + + +//============================================================================ + + +/* +============== +RB_CheckOverflow +============== +*/ +void RB_CheckOverflow( int verts, int indexes ) { + if ( tess.shader == tr.shadowShader ) { + if (tess.numVertexes + verts < SHADER_MAX_VERTEXES/2 + && tess.numIndexes + indexes < SHADER_MAX_INDEXES) { + return; + } + } else + if (tess.numVertexes + verts < SHADER_MAX_VERTEXES + && tess.numIndexes + indexes < SHADER_MAX_INDEXES) { + return; + } + + RB_EndSurface(); + + if ( verts >= SHADER_MAX_VERTEXES ) { + Com_Error(ERR_DROP, "RB_CheckOverflow: verts > MAX (%d > %d)", verts, SHADER_MAX_VERTEXES ); + } + if ( indexes >= SHADER_MAX_INDEXES ) { + Com_Error(ERR_DROP, "RB_CheckOverflow: indices > MAX (%d > %d)", indexes, SHADER_MAX_INDEXES ); + } + + RB_BeginSurface(tess.shader, tess.fogNum ); +} + + +/* +============== +RB_AddQuadStampExt +============== +*/ +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ) { + vec3_t normal; + int ndx; + + RB_CHECKOVERFLOW( 4, 6 ); + + ndx = tess.numVertexes; + + // triangle indexes for a simple quad + tess.indexes[ tess.numIndexes ] = ndx; + tess.indexes[ tess.numIndexes + 1 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 2 ] = ndx + 3; + + tess.indexes[ tess.numIndexes + 3 ] = ndx + 3; + tess.indexes[ tess.numIndexes + 4 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 5 ] = ndx + 2; + + tess.xyz[ndx][0] = origin[0] + left[0] + up[0]; + tess.xyz[ndx][1] = origin[1] + left[1] + up[1]; + tess.xyz[ndx][2] = origin[2] + left[2] + up[2]; + + tess.xyz[ndx+1][0] = origin[0] - left[0] + up[0]; + tess.xyz[ndx+1][1] = origin[1] - left[1] + up[1]; + tess.xyz[ndx+1][2] = origin[2] - left[2] + up[2]; + + tess.xyz[ndx+2][0] = origin[0] - left[0] - up[0]; + tess.xyz[ndx+2][1] = origin[1] - left[1] - up[1]; + tess.xyz[ndx+2][2] = origin[2] - left[2] - up[2]; + + tess.xyz[ndx+3][0] = origin[0] + left[0] - up[0]; + tess.xyz[ndx+3][1] = origin[1] + left[1] - up[1]; + tess.xyz[ndx+3][2] = origin[2] + left[2] - up[2]; + + + // constant normal all the way around + VectorSubtract( vec3_origin, backEnd.viewParms.ori.axis[0], normal ); + + tess.normal[ndx][0] = tess.normal[ndx+1][0] = tess.normal[ndx+2][0] = tess.normal[ndx+3][0] = normal[0]; + tess.normal[ndx][1] = tess.normal[ndx+1][1] = tess.normal[ndx+2][1] = tess.normal[ndx+3][1] = normal[1]; + tess.normal[ndx][2] = tess.normal[ndx+1][2] = tess.normal[ndx+2][2] = tess.normal[ndx+3][2] = normal[2]; + + // standard square texture coordinates + tess.texCoords[ndx][0][0] = tess.texCoords[ndx][1][0] = s1; + tess.texCoords[ndx][0][1] = tess.texCoords[ndx][1][1] = t1; + + tess.texCoords[ndx+1][0][0] = tess.texCoords[ndx+1][1][0] = s2; + tess.texCoords[ndx+1][0][1] = tess.texCoords[ndx+1][1][1] = t1; + + tess.texCoords[ndx+2][0][0] = tess.texCoords[ndx+2][1][0] = s2; + tess.texCoords[ndx+2][0][1] = tess.texCoords[ndx+2][1][1] = t2; + + tess.texCoords[ndx+3][0][0] = tess.texCoords[ndx+3][1][0] = s1; + tess.texCoords[ndx+3][0][1] = tess.texCoords[ndx+3][1][1] = t2; + + // constant color all the way around + // should this be identity and let the shader specify from entity? + * ( unsigned int * ) &tess.vertexColors[ndx] = + * ( unsigned int * ) &tess.vertexColors[ndx+1] = + * ( unsigned int * ) &tess.vertexColors[ndx+2] = + * ( unsigned int * ) &tess.vertexColors[ndx+3] = + * ( unsigned int * )color; + + + tess.numVertexes += 4; + tess.numIndexes += 6; +} + +/* +============== +RB_AddQuadStamp +============== +*/ +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ) { + RB_AddQuadStampExt( origin, left, up, color, 0, 0, 1, 1 ); +} + +/* +============== +RB_SurfaceSprite +============== +*/ +static void RB_SurfaceSprite( void ) { + vec3_t left, up; + float radius; + + // calculate the xyz locations for the four corners + radius = backEnd.currentEntity->e.radius; + if ( backEnd.currentEntity->e.rotation == 0 ) { + VectorScale( backEnd.viewParms.ori.axis[1], radius, left ); + VectorScale( backEnd.viewParms.ori.axis[2], radius, up ); + } else { + float s, c; + float ang; + + ang = M_PI * backEnd.currentEntity->e.rotation / 180; + s = sin( ang ); + c = cos( ang ); + + VectorScale( backEnd.viewParms.ori.axis[1], c * radius, left ); + VectorMA( left, -s * radius, backEnd.viewParms.ori.axis[2], left ); + + VectorScale( backEnd.viewParms.ori.axis[2], c * radius, up ); + VectorMA( up, s * radius, backEnd.viewParms.ori.axis[1], up ); + } + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( backEnd.currentEntity->e.origin, left, up, backEnd.currentEntity->e.shaderRGBA ); +} + + +/* +======================= +RB_SurfaceOrientedQuad +======================= +*/ +static void RB_SurfaceOrientedQuad( void ) +{ + vec3_t left, up; + float radius; + + // calculate the xyz locations for the four corners + radius = backEnd.currentEntity->e.radius; +// MakeNormalVectors( backEnd.currentEntity->e.axis[0], left, up ); + VectorCopy( backEnd.currentEntity->e.axis[1], left ); + VectorCopy( backEnd.currentEntity->e.axis[2], up ); + + if ( backEnd.currentEntity->e.rotation == 0 ) + { + VectorScale( left, radius, left ); + VectorScale( up, radius, up ); + } + else + { + vec3_t tempLeft, tempUp; + float s, c; + float ang; + + ang = M_PI * backEnd.currentEntity->e.rotation / 180; + s = sin( ang ); + c = cos( ang ); + + // Use a temp so we don't trash the values we'll need later + VectorScale( left, c * radius, tempLeft ); + VectorMA( tempLeft, -s * radius, up, tempLeft ); + + VectorScale( up, c * radius, tempUp ); + VectorMA( tempUp, s * radius, left, up ); // no need to use the temp anymore, so copy into the dest vector ( up ) + + // This was copied for safekeeping, we're done, so we can move it back to left + VectorCopy( tempLeft, left ); + } + + if ( backEnd.viewParms.isMirror ) + { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( backEnd.currentEntity->e.origin, left, up, backEnd.currentEntity->e.shaderRGBA ); +} + +/* +============= +RB_SurfacePolychain +============= +*/ +void RB_SurfacePolychain( srfPoly_t *p ) { + int i; + int numv; + + RB_CHECKOVERFLOW( p->numVerts, 3*(p->numVerts - 2) ); + + // fan triangles into the tess array + numv = tess.numVertexes; + for ( i = 0; i < p->numVerts; i++ ) { + VectorCopy( p->verts[i].xyz, tess.xyz[numv] ); + tess.texCoords[numv][0][0] = p->verts[i].st[0]; + tess.texCoords[numv][0][1] = p->verts[i].st[1]; + *(int *)&tess.vertexColors[numv] = *(int *)p->verts[ i ].modulate; + + numv++; + } + + // generate fan indexes into the tess array + for ( i = 0; i < p->numVerts-2; i++ ) { + tess.indexes[tess.numIndexes + 0] = tess.numVertexes; + tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1; + tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2; + tess.numIndexes += 3; + } + + tess.numVertexes = numv; +} + +inline static ulong ComputeFinalVertexColor(const byte *colors) +{ + int k; + byte result[4]; + ulong r, g, b; + + *(int *)result = *(int *)colors; + if (tess.shader->lightmapIndex[0] != LIGHTMAP_BY_VERTEX ) + { + return *(ulong *)result; + } + if (r_fullbright->integer) + { + result[0] = 255; + result[1] = 255; + result[2] = 255; + return *(ulong *)result; + } + // an optimization could be added here to compute the style[0] (which is always the world normal light) + r = g = b = 0; + for(k = 0; k < MAXLIGHTMAPS; k++) + { + if (tess.shader->styles[k] < LS_UNUSED) + { + byte *styleColor = styleColors[tess.shader->styles[k]]; + + r += (ulong)(*colors++) * (ulong)(*styleColor++); + g += (ulong)(*colors++) * (ulong)(*styleColor++); + b += (ulong)(*colors++) * (ulong)(*styleColor); + colors++; + } + else + { + break; + } + } + result[0] = Com_Clamp(0, 255, r >> 8); + result[1] = Com_Clamp(0, 255, g >> 8); + result[2] = Com_Clamp(0, 255, b >> 8); + + return *(ulong *)result; +} + +#ifdef _XBOX +//16 bits in, 32 bits out +inline ulong ComputeFinalVertexColor16(const byte *colors) +{ + int k; + byte result[4]; + byte color32[4]; + ulong r, g, b; + + result[0] = colors[0] & 0xF0; + result[1] = colors[0] << 4; + result[2] = colors[1] & 0xF0; + result[3] = colors[1] << 4; + if (tess.shader->lightmapIndex[0] != LIGHTMAP_BY_VERTEX || r_fullbright->integer ) + { + result[0] = 255; + result[1] = 255; + result[2] = 255; + return *(ulong *)result; + } + // an optimization could be added here to compute the style[0] (which is always the world normal light) + r = g = b = 0; + for(k = 0; k < MAXLIGHTMAPS; k++) + { + if (tess.shader->styles[k] < LS_UNUSED) + { + byte *styleColor = styleColors[tess.shader->styles[k]]; + + color32[0] = colors[k * 2] & 0xF0; + color32[1] = colors[k * 2] << 4; + color32[2] = colors[k * 2 + 1] & 0xF0; + + r += (ulong)(color32[0]) * (ulong)(*styleColor++); + g += (ulong)(color32[1]) * (ulong)(*styleColor++); + b += (ulong)(color32[2]) * (ulong)(*styleColor); + } + else + { + break; + } + } + result[0] = Com_Clamp(0, 255, r >> 8); + result[1] = Com_Clamp(0, 255, g >> 8); + result[2] = Com_Clamp(0, 255, b >> 8); + + return *(ulong *)result; +} +#endif + + +/* +============= +RB_SurfaceTriangles +============= +*/ +void RB_SurfaceTriangles( srfTriangles_t *srf ) { + int i, k; + drawVert_t *dv; + float *xyz, *normal, *texCoords; +#ifdef _XBOX + float *tangent; +#endif + byte *color; + int dlightBits; + + dlightBits = srf->dlightBits; + tess.dlightBits |= dlightBits; + + RB_CHECKOVERFLOW( srf->numVerts, srf->numIndexes ); + + for ( i = 0 ; i < srf->numIndexes ; i += 3 ) { + tess.indexes[ tess.numIndexes + i + 0 ] = tess.numVertexes + srf->indexes[ i + 0 ]; + tess.indexes[ tess.numIndexes + i + 1 ] = tess.numVertexes + srf->indexes[ i + 1 ]; + tess.indexes[ tess.numIndexes + i + 2 ] = tess.numVertexes + srf->indexes[ i + 2 ]; + } + tess.numIndexes += srf->numIndexes; + + dv = srf->verts; + xyz = tess.xyz[ tess.numVertexes ]; + normal = tess.normal[ tess.numVertexes ]; + texCoords = tess.texCoords[ tess.numVertexes ][0]; + color = tess.vertexColors[ tess.numVertexes ]; +#ifdef _XBOX + tangent = tess.tangent[ tess.numVertexes ]; +#endif + + for ( i = 0 ; i < srf->numVerts ; i++, dv++) + { +#ifdef _XBOX + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + xyz += 4; + + if ( tess.shader->needsNormal || tess.dlightBits ) + { + normal[0] = (float)dv->normal[0] / 32767.f; + normal[1] = (float)dv->normal[1] / 32767.f; + normal[2] = (float)dv->normal[2] / 32767.f; + normal += 4; + } + + if( tess.shader->needsTangent || tess.dlightBits ) + { + tangent[0] = dv->tangent[0]; + tangent[1] = dv->tangent[1]; + tangent[2] = dv->tangent[2]; + tangent += 4; + + tess.setTangents = true; + } + + Q_CastShort2FloatScale(&texCoords[0], &dv->dvst[0], 1.f / DRAWVERT_ST_SCALE); + Q_CastShort2FloatScale(&texCoords[1], &dv->dvst[1], 1.f / DRAWVERT_ST_SCALE); + + for(k=0;klightmapIndex[k] >= 0) + { + Q_CastShort2FloatScale(&texCoords[2+(k*2)+0], + &dv->dvlightmap[k][0], 1.f / DRAWVERT_LIGHTMAP_SCALE); + Q_CastShort2FloatScale(&texCoords[2+(k*2)+1], + &dv->dvlightmap[k][1], 1.f / DRAWVERT_LIGHTMAP_SCALE); + } + else + { // can't have an empty slot in the middle, so we are done + break; + } + } + texCoords += NUM_TEX_COORDS*2; + + *(unsigned int*)color = ComputeFinalVertexColor16((byte *)dv->dvcolor); + color += 4; +#else + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + xyz += 4; + + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + normal += 4; + + texCoords[0] = dv->st[0]; + texCoords[1] = dv->st[1]; + + for(k=0;klightmapIndex[k] >= 0) + { + texCoords[2+(k*2)] = dv->lightmap[k][0]; + texCoords[2+(k*2)+1] = dv->lightmap[k][1]; + } + else + { // can't have an empty slot in the middle, so we are done + break; + } + } + texCoords += NUM_TEX_COORDS*2; + + *(unsigned *)color = ComputeFinalVertexColor((byte *)dv->color); + color += 4; +#endif // _XBOX + } + + for ( i = 0 ; i < srf->numVerts ; i++ ) { + tess.vertexDlightBits[ tess.numVertexes + i] = dlightBits; + } + + tess.numVertexes += srf->numVerts; +} + + + +/* +============== +RB_SurfaceBeam +============== +*/ +static void RB_SurfaceBeam( void ) +{ +#define NUM_BEAM_SEGS 6 + refEntity_t *e; + int i; + vec3_t perpvec; + vec3_t direction, normalized_direction; + vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS]; + vec3_t oldorigin, origin; + + e = &backEnd.currentEntity->e; + + oldorigin[0] = e->oldorigin[0]; + oldorigin[1] = e->oldorigin[1]; + oldorigin[2] = e->oldorigin[2]; + + origin[0] = e->origin[0]; + origin[1] = e->origin[1]; + origin[2] = e->origin[2]; + + normalized_direction[0] = direction[0] = oldorigin[0] - origin[0]; + normalized_direction[1] = direction[1] = oldorigin[1] - origin[1]; + normalized_direction[2] = direction[2] = oldorigin[2] - origin[2]; + + if ( VectorNormalize( normalized_direction ) == 0 ) + return; + + PerpendicularVector( perpvec, normalized_direction ); + + VectorScale( perpvec, 4, perpvec ); + + for ( i = 0; i < NUM_BEAM_SEGS ; i++ ) + { + RotatePointAroundVector( start_points[i], normalized_direction, perpvec, (360.0/NUM_BEAM_SEGS)*i ); +// VectorAdd( start_points[i], origin, start_points[i] ); + VectorAdd( start_points[i], direction, end_points[i] ); + } + + GL_Bind( tr.whiteImage ); + + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + qglColor3f( 1, 0, 0 ); + + qglBegin( GL_TRIANGLE_STRIP ); + for ( i = 0; i <= NUM_BEAM_SEGS; i++ ) { + qglVertex3fv( start_points[ i % NUM_BEAM_SEGS] ); + qglVertex3fv( end_points[ i % NUM_BEAM_SEGS] ); + } + qglEnd(); +} + +//------------------ +// DoSprite +//------------------ +static void DoSprite( vec3_t origin, float radius, float rotation ) +{ + float s, c; + float ang; + vec3_t left, up; + + ang = M_PI * rotation / 180.0f; + s = sin( ang ); + c = cos( ang ); + + VectorScale( backEnd.viewParms.ori.axis[1], c * radius, left ); + VectorMA( left, -s * radius, backEnd.viewParms.ori.axis[2], left ); + + VectorScale( backEnd.viewParms.ori.axis[2], c * radius, up ); + VectorMA( up, s * radius, backEnd.viewParms.ori.axis[1], up ); + + if ( backEnd.viewParms.isMirror ) + { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( origin, left, up, backEnd.currentEntity->e.shaderRGBA ); +} + +//------------------ +// RB_SurfaceSaber +//------------------ +static void RB_SurfaceSaberGlow() +{ + vec3_t end; + refEntity_t *e; + + e = &backEnd.currentEntity->e; + + // Render the glow part of the blade + for ( float i = e->saberLength; i > 0; i -= e->radius * 0.65f ) + { + VectorMA( e->origin, i, e->axis[0], end ); + + DoSprite( end, e->radius, 0.0f );//random() * 360.0f ); + e->radius += 0.017f; + } + + // Big hilt sprite + // Please don't kill me Pat...I liked the hilt glow blob, but wanted a subtle pulse.:) Feel free to ditch it if you don't like it. --Jeff + // Please don't kill me Jeff... The pulse is good, but now I want the halo bigger if the saber is shorter... --Pat + DoSprite( e->origin, 5.5f + random() * 0.25f, 0.0f );//random() * 360.0f ); +} + +/* +============== +RB_SurfaceLine +============== +*/ +// +// Values for a proper line render primitive... +// Width +// STScale (how many times to loop a texture) +// alpha +// RGB +// +// Values for proper line object... +// lifetime +// dscale +// startalpha, endalpha +// startRGB, endRGB +// + +static void DoLine( const vec3_t start, const vec3_t end, const vec3_t up, float spanWidth ) +{ + float spanWidth2; + int vbase; + + RB_CHECKOVERFLOW( 4, 6 ); + + vbase = tess.numVertexes; + + spanWidth2 = -spanWidth; + + VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0];// * 0.25;//wtf??not sure why the code would be doing this + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1];// * 0.25; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2];// * 0.25; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3];// * 0.25; + tess.numVertexes++; + + VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1;//backEnd.currentEntity->e.shaderTexCoord[0]; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] ); + + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1;//backEnd.currentEntity->e.shaderTexCoord[1]; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1;//backEnd.currentEntity->e.shaderTexCoord[0]; + tess.texCoords[tess.numVertexes][0][1] = 1;//backEnd.currentEntity->e.shaderTexCoord[1]; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; +} + +static void DoLine2( const vec3_t start, const vec3_t end, const vec3_t up, float spanWidth, float spanWidth2 ) +{ + int vbase; + + RB_CHECKOVERFLOW( 4, 6 ); + + vbase = tess.numVertexes; + + VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0];// * 0.25;//wtf??not sure why the code would be doing this + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1];// * 0.25; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2];// * 0.25; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3];// * 0.25; + tess.numVertexes++; + + VectorMA( start, -spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1;//backEnd.currentEntity->e.shaderTexCoord[0]; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] ); + + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1;//backEnd.currentEntity->e.shaderTexCoord[1]; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + VectorMA( end, -spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1;//backEnd.currentEntity->e.shaderTexCoord[0]; + tess.texCoords[tess.numVertexes][0][1] = 1;//backEnd.currentEntity->e.shaderTexCoord[1]; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; +} + +static void DoLine_Oriented( const vec3_t start, const vec3_t end, const vec3_t up, float spanWidth ) +{ + float spanWidth2; + int vbase; + + vbase = tess.numVertexes; + + spanWidth2 = -spanWidth; + + // FIXME: use quad stamp? + VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0];// * 0.25; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1];// * 0.25; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2];// * 0.25; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3];// * 0.25; + tess.numVertexes++; + + VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3];// * 0.25; + tess.numVertexes++; + + VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] ); + + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = backEnd.currentEntity->e.data.line.stscale; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3];// * 0.25; + tess.numVertexes++; + + VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = backEnd.currentEntity->e.data.line.stscale; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3];// * 0.25; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; +} + +//----------------- +// RB_SurfaceLine +//----------------- +static void RB_SurfaceLine( void ) +{ + refEntity_t *e; + vec3_t right; + vec3_t start, end; + vec3_t v1, v2; + + e = &backEnd.currentEntity->e; + +#ifdef _XBOX + if(ClientManager::ActiveClientNum() == 1) + if(e->skipForPlayer2 == true) + return; +#endif + + VectorCopy( e->oldorigin, end ); + VectorCopy( e->origin, start ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.ori.origin, v1 ); + VectorSubtract( end, backEnd.viewParms.ori.origin, v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + DoLine( start, end, right, e->radius); +} + +static void RB_SurfaceOrientedLine( void ) +{ + refEntity_t *e; + vec3_t right; + vec3_t start, end; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, end ); + VectorCopy( e->origin, start ); + + // compute side vector + VectorNormalize( e->axis[1] ); + VectorCopy(e->axis[1], right); + DoLine_Oriented( start, end, right, e->data.line.width*0.5 ); +} + +/* +============== +RB_SurfaceCylinder +============== +*/ + +#define NUM_CYLINDER_SEGMENTS 32 + +// FIXME: use quad stamp? +static void DoCylinderPart(polyVert_t *verts) +{ + int vbase; + int i; + + RB_CHECKOVERFLOW( 4, 6 ); + + vbase = tess.numVertexes; + + for (i=0; i<4; i++) + { + VectorCopy( verts->xyz, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = verts->st[0]; + tess.texCoords[tess.numVertexes][0][1] = verts->st[1]; + tess.vertexColors[tess.numVertexes][0] = verts->modulate[0]; + tess.vertexColors[tess.numVertexes][1] = verts->modulate[1]; + tess.vertexColors[tess.numVertexes][2] = verts->modulate[2]; + tess.vertexColors[tess.numVertexes][3] = verts->modulate[3]; + tess.numVertexes++; + verts++; + } + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 3; + tess.indexes[tess.numIndexes++] = vbase; +} + +// e->origin holds the bottom point +// e->oldorigin holds the top point +// e->radius holds the radius + +static void RB_SurfaceCylinder( void ) +{ + static polyVert_t lower_points[NUM_CYLINDER_SEGMENTS], upper_points[NUM_CYLINDER_SEGMENTS], verts[4]; + vec3_t vr, vu, midpoint, v1; + float detail, length; + int i; + int segments; + refEntity_t *e; + int nextSegment; + + e = &backEnd.currentEntity->e; + + //Work out the detail level of this cylinder + VectorAdd( e->origin, e->oldorigin, midpoint ); + VectorScale(midpoint, 0.5f, midpoint); // Average start and end + + VectorSubtract( midpoint, backEnd.viewParms.ori.origin, midpoint ); + length = VectorNormalize( midpoint ); + + // this doesn't need to be perfect....just a rough compensation for zoom level is enough + length *= (backEnd.viewParms.fovX / 90.0f); + + detail = 1 - ((float) length / 1024 ); + segments = NUM_CYLINDER_SEGMENTS * detail; + + // 3 is the absolute minimum, but the pop between 3-8 is too noticeable + if ( segments < 8 ) + { + segments = 8; + } + + if ( segments > NUM_CYLINDER_SEGMENTS ) + { + segments = NUM_CYLINDER_SEGMENTS; + } + + //Get the direction vector + MakeNormalVectors( e->axis[0], vr, vu ); + + VectorScale( vu, e->radius, v1 ); // size1 + VectorScale( vu, e->rotation, vu ); // size2 + + // Calculate the step around the cylinder + detail = 360.0f / (float)segments; + + for ( i = 0; i < segments; i++ ) + { + //Upper ring + RotatePointAroundVector( upper_points[i].xyz, e->axis[0], vu, detail * i ); + VectorAdd( upper_points[i].xyz, e->origin, upper_points[i].xyz ); + + //Lower ring + RotatePointAroundVector( lower_points[i].xyz, e->axis[0], v1, detail * i ); + VectorAdd( lower_points[i].xyz, e->oldorigin, lower_points[i].xyz ); + } + + // Calculate the texture coords so the texture can wrap around the whole cylinder + detail = 1.0f / (float)segments; + + for ( i = 0; i < segments; i++ ) + { + if ( i + 1 < segments ) + nextSegment = i + 1; + else + nextSegment = 0; + + VectorCopy( upper_points[i].xyz, verts[0].xyz ); + verts[0].st[1] = 1.0f; + verts[0].st[0] = detail * i; + verts[0].modulate[0] = (byte)(e->shaderRGBA[0]); + verts[0].modulate[1] = (byte)(e->shaderRGBA[1]); + verts[0].modulate[2] = (byte)(e->shaderRGBA[2]); + verts[0].modulate[3] = (byte)(e->shaderRGBA[3]); + + VectorCopy( lower_points[i].xyz, verts[1].xyz ); + verts[1].st[1] = 0.0f; + verts[1].st[0] = detail * i; + verts[1].modulate[0] = (byte)(e->shaderRGBA[0]); + verts[1].modulate[1] = (byte)(e->shaderRGBA[1]); + verts[1].modulate[2] = (byte)(e->shaderRGBA[2]); + verts[1].modulate[3] = (byte)(e->shaderRGBA[3]); + + VectorCopy( lower_points[nextSegment].xyz, verts[2].xyz ); + verts[2].st[1] = 0.0f; + verts[2].st[0] = detail * ( i + 1 ); + verts[2].modulate[0] = (byte)(e->shaderRGBA[0]); + verts[2].modulate[1] = (byte)(e->shaderRGBA[1]); + verts[2].modulate[2] = (byte)(e->shaderRGBA[2]); + verts[2].modulate[3] = (byte)(e->shaderRGBA[3]); + + VectorCopy( upper_points[nextSegment].xyz, verts[3].xyz ); + verts[3].st[1] = 1.0f; + verts[3].st[0] = detail * ( i + 1 ); + verts[3].modulate[0] = (byte)(e->shaderRGBA[0]); + verts[3].modulate[1] = (byte)(e->shaderRGBA[1]); + verts[3].modulate[2] = (byte)(e->shaderRGBA[2]); + verts[3].modulate[3] = (byte)(e->shaderRGBA[3]); + + DoCylinderPart(verts); + } +} + +static vec3_t sh1, sh2; +static float f_count; + +#define LIGHTNING_RECURSION_LEVEL 1 // was 2 + +// these functions are pretty crappy in terms of returning a nice range of rnd numbers, but it's probably good enough? +/*static int Q_rand( int *seed ) { + *seed = (69069 * *seed + 1); + return *seed; +} + +static float Q_random( int *seed ) { + return ( Q_rand( seed ) & 0xffff ) / (float)0x10000; +} + +static float Q_crandom( int *seed ) { + return 2.0F * ( Q_random( seed ) - 0.5f ); +} +*/ +// Up front, we create a random "shape", then apply that to each line segment...and then again to each of those segments...kind of like a fractal +//---------------------------------------------------------------------------- +static void CreateShape() +//---------------------------------------------------------------------------- +{ + VectorSet( sh1, 0.66f + crandom() * 0.1f, // fwd + 0.07f + crandom() * 0.025f, + 0.07f + crandom() * 0.025f ); + + // it seems to look best to have a point on one side of the ideal line, then the other point on the other side. + VectorSet( sh2, 0.33f + crandom() * 0.1f, // fwd + -sh1[1] + crandom() * 0.02f, // forcing point to be on the opposite side of the line -- right + -sh1[2] + crandom() * 0.02f );// up +} + +//---------------------------------------------------------------------------- +static void ApplyShape( vec3_t start, vec3_t end, vec3_t right, float sradius, float eradius, int count ) +//---------------------------------------------------------------------------- +{ + vec3_t point1, point2, fwd; + vec3_t rt, up; + float perc, dis; + + if ( count < 1 ) + { + // done recursing + DoLine2( start, end, right, sradius, eradius ); + return; + } + + CreateShape(); + + VectorSubtract( end, start, fwd ); + dis = VectorNormalize( fwd ) * 0.7f; + MakeNormalVectors( fwd, rt, up ); + + perc = sh1[0]; + + VectorScale( start, perc, point1 ); + VectorMA( point1, 1.0f - perc, end, point1 ); + VectorMA( point1, dis * sh1[1], rt, point1 ); + VectorMA( point1, dis * sh1[2], up, point1 ); + + // do a quick and dirty interpolation of the radius at that point + float rads1, rads2; + + rads1 = sradius * 0.666f + eradius * 0.333f; + rads2 = sradius * 0.333f + eradius * 0.666f; + + // recursion + ApplyShape( start, point1, right, sradius, rads1, count - 1 ); + + perc = sh2[0]; + + VectorScale( start, perc, point2 ); + VectorMA( point2, 1.0f - perc, end, point2 ); + VectorMA( point2, dis * sh2[1], rt, point2 ); + VectorMA( point2, dis * sh2[2], up, point2 ); + + // recursion + ApplyShape( point2, point1, right, rads1, rads2, count - 1 ); + ApplyShape( point2, end, right, rads2, eradius, count - 1 ); +} + +//---------------------------------------------------------------------------- +static void DoBoltSeg( vec3_t start, vec3_t end, vec3_t right, float radius ) +//---------------------------------------------------------------------------- +{ + refEntity_t *e; + vec3_t fwd, old; + vec3_t cur, off={10,10,10}; + vec3_t rt, up; + vec3_t temp; + int i; + float dis, oldPerc = 0.0f, perc, oldRadius, newRadius; + + e = &backEnd.currentEntity->e; + + VectorSubtract( end, start, fwd ); + dis = VectorNormalize( fwd ); + + MakeNormalVectors( fwd, rt, up ); + + VectorCopy( start, old ); + + oldRadius = newRadius = radius; + + for ( i = 20; i <= dis; i+= 20 ) + { + // because of our large step size, we may not actually draw to the end. In this case, fudge our percent so that we are basically complete + if ( i + 20 > dis ) + { + perc = 1.0f; + } + else + { + // percentage of the amount of line completed + perc = (float)i / dis; + } + + // create our level of deviation for this point + VectorScale( fwd, Q_crandom(&e->frame) * 3.0f, temp ); // move less in fwd direction, chaos also does not affect this + VectorMA( temp, Q_crandom(&e->frame) * 7.0f * e->axis[0][0], rt, temp ); // move more in direction perpendicular to line, angles is really the chaos + VectorMA( temp, Q_crandom(&e->frame) * 7.0f * e->axis[0][0], up, temp ); // move more in direction perpendicular to line + + // track our total level of offset from the ideal line + VectorAdd( off, temp, off ); + + // Move from start to end, always adding our current level of offset from the ideal line + // Even though we are adding a random offset.....by nature, we always move from exactly start....to end + VectorAdd( start, off, cur ); + VectorScale( cur, 1.0f - perc, cur ); + VectorMA( cur, perc, end, cur ); + + if ( e->renderfx & RF_TAPERED ) + { + // This does pretty close to perfect tapering since apply shape interpolates the old and new as it goes along. + // by using one minus the square, the radius stays fairly constant, then drops off quickly at the very point of the bolt + oldRadius = radius * (1.0f-oldPerc*oldPerc); + newRadius = radius * (1.0f-perc*perc); + } + + // Apply the random shape to our line seg to give it some micro-detail-jaggy-coolness. + ApplyShape( cur, old, right, newRadius, oldRadius, LIGHTNING_RECURSION_LEVEL ); + + // randomly split off to create little tendrils, but don't do it too close to the end and especially if we are not even of the forked variety + if ( ( e->renderfx & RF_FORKED ) && f_count > 0 && Q_random(&e->frame) > 0.94f && radius * (1.0f - perc) > 0.2f ) + { + vec3_t newDest; + + f_count--; + + // Pick a point somewhere between the current point and the final endpoint + VectorAdd( cur, e->oldorigin, newDest ); + VectorScale( newDest, 0.5f, newDest ); + + // And then add some crazy offset + for ( int t = 0; t < 3; t++ ) + { + newDest[t] += Q_crandom(&e->frame) * 80; + } + + // we could branch off using OLD and NEWDEST, but that would allow multiple forks...whereas, we just want simpler brancing + DoBoltSeg( cur, newDest, right, newRadius ); + } + + // Current point along the line becomes our new old attach point + VectorCopy( cur, old ); + oldPerc = perc; + } +} + +//------------------------------------------ +static void RB_SurfaceElectricity() +//------------------------------------------ +{ + refEntity_t *e; + vec3_t right, fwd; + vec3_t start, end; + vec3_t v1, v2; + float radius, perc = 1.0f, dis; + + e = &backEnd.currentEntity->e; + radius = e->radius; + + VectorCopy( e->origin, start ); + + VectorSubtract( e->oldorigin, start, fwd ); + dis = VectorNormalize( fwd ); + + // see if we should grow from start to end + if ( e->renderfx & RF_GROW ) + { +// perc = 1.0f - ( e->axis[0][2]/*endTime*/ - tr.refdef.time ) / e->axis[0][1]/*duration*/; + // Hack to make this effect non-framerate dependant + perc = 1.0f - ( Q_irand(0, 5) ) / e->axis[0][1]; + + if ( perc > 1.0f ) + { + perc = 1.0f; + } + else if ( perc < 0.0f ) + { + perc = 0.0f; + } + } + + VectorMA( start, perc * dis, fwd, e->oldorigin ); + VectorCopy( e->oldorigin, end ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.ori.origin, v1 ); + VectorSubtract( end, backEnd.viewParms.ori.origin, v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + DoBoltSeg( start, end, right, radius ); +} + +//================================================================================ + + +/* +** VectorArrayNormalize +* +* The inputs to this routing seem to always be close to length = 1.0 (about 0.6 to 2.0) +* This means that we don't have to worry about zero length or enormously long vectors. +*/ +static void VectorArrayNormalize(vec4_t *normals, unsigned int count) +{ +// assert(count); + +#if idppc + { + register float half = 0.5; + register float one = 1.0; + float *components = (float *)normals; + + // Vanilla PPC code, but since PPC has a reciprocal square root estimate instruction, + // runs *much* faster than calling sqrt(). We'll use a single Newton-Raphson + // refinement step to get a little more precision. This seems to yeild results + // that are correct to 3 decimal places and usually correct to at least 4 (sometimes 5). + // (That is, for the given input range of about 0.6 to 2.0). + do { + float x, y, z; + float B, y0, y1; + + x = components[0]; + y = components[1]; + z = components[2]; + components += 4; + B = x*x + y*y + z*z; + +#ifdef __GNUC__ + asm("frsqrte %0,%1" : "=f" (y0) : "f" (B)); +#else + y0 = __frsqrte(B); +#endif + y1 = y0 + half*y0*(one - B*y0*y0); + + x = x * y1; + y = y * y1; + components[-4] = x; + z = z * y1; + components[-3] = y; + components[-2] = z; + } while(count--); + } +#else // No assembly version for this architecture, or C_ONLY defined + // given the input, it's safe to call VectorNormalizeFast + while (count--) { + VectorNormalizeFast(normals[0]); + normals++; + } +#endif + +} + + + +/* +** LerpMeshVertexes +*/ +static void LerpMeshVertexes (md3Surface_t *surf, float backlerp) +{ + short *oldXyz, *newXyz, *oldNormals, *newNormals; + float *outXyz, *outNormal; + float oldXyzScale, newXyzScale; + float oldNormalScale, newNormalScale; + int vertNum; + unsigned lat, lng; + int numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + newXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.frame * surf->numVerts * 4); + newNormals = newXyz + 3; + + newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp); + newNormalScale = 1.0 - backlerp; + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + // + // just copy the vertexes + // + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + newXyz += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + + outXyz[0] = newXyz[0] * newXyzScale; + outXyz[1] = newXyz[1] * newXyzScale; + outXyz[2] = newXyz[2] * newXyzScale; + + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) +#ifdef _XBOX + if( tess.shader->needsNormal || tess.dlightBits ) + { + outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + } +#else + outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; +#endif + } + } else { + // + // interpolate and copy the vertex and normal + // + oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.oldframe * surf->numVerts * 4); + oldNormals = oldXyz + 3; + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + oldNormalScale = backlerp; + + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + vec3_t uncompressedOldNormal, uncompressedNewNormal; + + // interpolate the xyz + outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + +#ifdef _XBOX + if(tess.shader->needsNormal || tess.dlightBits ) + { +#endif + // FIXME: interpolate lat/long instead? + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + lat = ( oldNormals[0] >> 8 ) & 0xff; + lng = ( oldNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale; + outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale; + outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale; + +// VectorNormalize (outNormal); +#ifdef _XBOX + } +#endif + } + VectorArrayNormalize((vec4_t *)tess.normal[tess.numVertexes], numVerts); + } +} + +/* +============= +RB_SurfaceMesh +============= +*/ +void RB_SurfaceMesh(md3Surface_t *surface) { + int j; + float backlerp; + int *triangles; + float *texCoords; + int indexes; + int Bob, Doug; + int numVerts; + +#ifdef _XBOX + if(glw_state->viewport.Y == 240) { // Can't use ActiveClientNum in render phase... + if( backEnd.currentEntity->e.skipForPlayer2 ) + return; + } +#endif + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + } + +#ifdef VV_LIGHTING + if(backEnd.currentEntity->dlightBits) + tess.dlightBits = backEnd.currentEntity->dlightBits; +#endif + + RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles*3 ); + + LerpMeshVertexes (surface, backlerp); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + Bob = tess.numIndexes; + Doug = tess.numVertexes; + for (j = 0 ; j < indexes ; j++) { + tess.indexes[Bob + j] = Doug + triangles[j]; + } + tess.numIndexes += indexes; + + texCoords = (float *) ((byte *)surface + surface->ofsSt); + + numVerts = surface->numVerts; + for ( j = 0; j < numVerts; j++ ) { + tess.texCoords[Doug + j][0][0] = texCoords[j*2+0]; + tess.texCoords[Doug + j][0][1] = texCoords[j*2+1]; + // FIXME: fill in lightmapST for completeness? + } + + tess.numVertexes += surface->numVerts; + +} + + +/* +============== +RB_SurfaceFace +============== +*/ +void RB_SurfaceFace( srfSurfaceFace_t *surf ) { + int i, k; + // VVFIXME : Sooper hack. Indices in the surface are still 32-bit, we need to make them 16 bit here. +#ifdef _XBOX + unsigned char *indices; + unsigned short *tessIndexes; + unsigned short *v; +#else + unsigned int *indices; + glIndex_t *tessIndexes; + float *v; +#endif + float *normal; + int ndx; + int Bob; + int numPoints; + int dlightBits; + + RB_CHECKOVERFLOW( surf->numPoints, surf->numIndices ); + + dlightBits = surf->dlightBits; + tess.dlightBits |= dlightBits; + +#ifdef _XBOX + indices = ( unsigned char * ) ( ( ( char * ) surf ) + surf->ofsIndices ); +#else + indices = ( unsigned * ) ( ( ( char * ) surf ) + surf->ofsIndices ); +#endif + + Bob = tess.numVertexes; + tessIndexes = tess.indexes + tess.numIndexes; + for ( i = surf->numIndices-1 ; i >= 0 ; i-- ) { + tessIndexes[i] = indices[i] + Bob; + } + + tess.numIndexes += surf->numIndices; + +#ifdef _XBOX + ndx = tess.numVertexes; + + numPoints = surf->numPoints; + + if ( tess.shader->needsNormal || tess.dlightBits) { + normal = surf->plane.normal; + for ( i = 0, ndx = tess.numVertexes; i < numPoints; i++, ndx++ ) { + VectorCopy( normal, tess.normal[ndx] ); + } + } + + int nextSurfPoint = NEXT_SURFPOINT(surf->flags); + int numLightMaps = surf->flags & 0x7F; + for ( i = 0, v = surf->srfPoints, ndx = tess.numVertexes; i < numPoints; i++, v += nextSurfPoint, ndx++ ) { + Q_CastShort2Float(&tess.xyz[ndx][0], (short*)&v[0]); + Q_CastShort2Float(&tess.xyz[ndx][1], (short*)&v[1]); + Q_CastShort2Float(&tess.xyz[ndx][2], (short*)&v[2]); + + Q_CastShort2Float(&tess.tangent[ndx][0], (short*)&v[3]); + Q_CastShort2Float(&tess.tangent[ndx][1], (short*)&v[4]); + Q_CastShort2Float(&tess.tangent[ndx][2], (short*)&v[5]); + + tess.tangent[ndx][0] /= 32767.0f; + tess.tangent[ndx][1] /= 32767.0f; + tess.tangent[ndx][2] /= 32767.0f; + + tess.setTangents = true; + + Q_CastShort2FloatScale(&tess.texCoords[ndx][0][0], (short*)&v[6], 1.f / POINTS_ST_SCALE); + Q_CastShort2FloatScale(&tess.texCoords[ndx][0][1], (short*)&v[7], 1.f / POINTS_ST_SCALE); + for(k=0;klightmapIndex[k] >= 0) + { + Q_CastUShort2FloatScale(&tess.texCoords[ndx][k+1][0], + &v[VERTEX_LM+(k*2)+0], 1.f / POINTS_LIGHT_SCALE); + Q_CastUShort2FloatScale(&tess.texCoords[ndx][k+1][1], + &v[VERTEX_LM+(k*2)+1], 1.f / POINTS_LIGHT_SCALE); + } + else + { + //This causes problems. See bug 57. + //assert(0); + break; + } + } + if((surf->flags & 0x80) >> 7) { + *(unsigned int*) &tess.vertexColors[ndx] = ComputeFinalVertexColor16((byte *)&v[VERTEX_COLOR(surf->flags)]); + } + } +#else // _XBOX + + v = surf->points[0]; + + ndx = tess.numVertexes; + + numPoints = surf->numPoints; + + //if ( tess.shader->needsNormal ) + { + normal = surf->plane.normal; + for ( i = 0, ndx = tess.numVertexes; i < numPoints; i++, ndx++ ) { + VectorCopy( normal, tess.normal[ndx] ); + } + } + + for ( i = 0, v = surf->points[0], ndx = tess.numVertexes; i < numPoints; i++, v += VERTEXSIZE, ndx++ ) + { + VectorCopy( v, tess.xyz[ndx]); + tess.texCoords[ndx][0][0] = v[3]; + tess.texCoords[ndx][0][1] = v[4]; + for(k=0;klightmapIndex[k] >= 0) + { + tess.texCoords[ndx][k+1][0] = v[VERTEX_LM+(k*2)]; + tess.texCoords[ndx][k+1][1] = v[VERTEX_LM+(k*2)+1]; + } + else + { + break; + } + } + *(unsigned int*) &tess.vertexColors[ndx] = ComputeFinalVertexColor((byte *)&v[VERTEX_COLOR]); + tess.vertexDlightBits[ndx] = dlightBits; + } +#endif + + tess.numVertexes += surf->numPoints; +} + + +static float LodErrorForVolume( vec3_t local, float radius ) { + vec3_t world; + float d; + + // never let it go negative + if ( r_lodCurveError->value < 0 ) { + return 0; + } + + world[0] = local[0] * backEnd.ori.axis[0][0] + local[1] * backEnd.ori.axis[1][0] + + local[2] * backEnd.ori.axis[2][0] + backEnd.ori.origin[0]; + world[1] = local[0] * backEnd.ori.axis[0][1] + local[1] * backEnd.ori.axis[1][1] + + local[2] * backEnd.ori.axis[2][1] + backEnd.ori.origin[1]; + world[2] = local[0] * backEnd.ori.axis[0][2] + local[1] * backEnd.ori.axis[1][2] + + local[2] * backEnd.ori.axis[2][2] + backEnd.ori.origin[2]; + + VectorSubtract( world, backEnd.viewParms.ori.origin, world ); + d = DotProduct( world, backEnd.viewParms.ori.axis[0] ); + + if ( d < 0 ) { + d = -d; + } + d -= radius; + if ( d < 1 ) { + d = 1; + } + + return r_lodCurveError->value / d; +} + +/* +============= +RB_SurfaceGrid + +Just copy the grid of points and triangulate +============= +*/ +void RB_SurfaceGrid( srfGridMesh_t *cv ) { + int i, j, k; + float *xyz; + float *texCoords; + float *normal; + unsigned char *color; + drawVert_t *dv; + int rows, irows, vrows; + int used; + int widthTable[MAX_GRID_SIZE]; + int heightTable[MAX_GRID_SIZE]; + float lodError; + int lodWidth, lodHeight; + int numVertexes; + int dlightBits; + int *vDlightBits; + + dlightBits = cv->dlightBits; + tess.dlightBits |= dlightBits; + + // determine the allowable discrepance + lodError = LodErrorForVolume( cv->lodOrigin, cv->lodRadius ); + + // determine which rows and columns of the subdivision + // we are actually going to use + widthTable[0] = 0; + lodWidth = 1; + for ( i = 1 ; i < cv->width-1 ; i++ ) { + if ( cv->widthLodError[i] <= lodError ) { + widthTable[lodWidth] = i; + lodWidth++; + } + } + widthTable[lodWidth] = cv->width-1; + lodWidth++; + + heightTable[0] = 0; + lodHeight = 1; + for ( i = 1 ; i < cv->height-1 ; i++ ) { + if ( cv->heightLodError[i] <= lodError ) { + heightTable[lodHeight] = i; + lodHeight++; + } + } + heightTable[lodHeight] = cv->height-1; + lodHeight++; + + + // very large grids may have more points or indexes than can be fit + // in the tess structure, so we may have to issue it in multiple passes + + used = 0; + rows = 0; + while ( used < lodHeight - 1 ) { + // see how many rows of both verts and indexes we can add without overflowing + do { + vrows = ( SHADER_MAX_VERTEXES - tess.numVertexes ) / lodWidth; + irows = ( SHADER_MAX_INDEXES - tess.numIndexes ) / ( lodWidth * 6 ); + + // if we don't have enough space for at least one strip, flush the buffer + if ( vrows < 2 || irows < 1 ) { + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum ); + } else { + break; + } + } while ( 1 ); + + rows = irows; + if ( vrows < irows + 1 ) { + rows = vrows - 1; + } + if ( used + rows > lodHeight ) { + rows = lodHeight - used; + } + + numVertexes = tess.numVertexes; + + xyz = tess.xyz[numVertexes]; + normal = tess.normal[numVertexes]; + texCoords = tess.texCoords[numVertexes][0]; + color = ( unsigned char * ) &tess.vertexColors[numVertexes]; + vDlightBits = &tess.vertexDlightBits[numVertexes]; + +#ifdef _XBOX + for ( i = 0 ; i < rows ; i++ ) { + for ( j = 0 ; j < lodWidth ; j++ ) { + dv = cv->verts + heightTable[ used + i ] * cv->width + + widthTable[ j ]; + + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + xyz += 4; + + Q_CastShort2FloatScale(&texCoords[0], &dv->dvst[0], 1.f / GRID_DRAWVERT_ST_SCALE); + Q_CastShort2FloatScale(&texCoords[1], &dv->dvst[1], 1.f / GRID_DRAWVERT_ST_SCALE); + + for(k=0;kdvlightmap[k][0], 1.f / DRAWVERT_LIGHTMAP_SCALE); + Q_CastShort2FloatScale(&texCoords[2+(k*2)+1], + &dv->dvlightmap[k][1], 1.f / DRAWVERT_LIGHTMAP_SCALE); + } + texCoords += NUM_TEX_COORDS*2; + + if ( tess.shader->needsNormal || tess.dlightBits) + { + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + normal += 4; + } + + *(unsigned *)color = ComputeFinalVertexColor16((byte *)dv->dvcolor); + color += 4; + *vDlightBits++ = dlightBits; + } + } +#else // _XBOX + + for ( i = 0 ; i < rows ; i++ ) { + for ( j = 0 ; j < lodWidth ; j++ ) { + dv = cv->verts + heightTable[ used + i ] * cv->width + + widthTable[ j ]; + + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + xyz += 4; + + texCoords[0] = dv->st[0]; + texCoords[1] = dv->st[1]; + for(k=0;klightmap[k][0]; + texCoords[2+(k*2)+1]= dv->lightmap[k][1]; + } + texCoords += NUM_TEX_COORDS*2; + + //if ( needsNormal ) + { + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + } + normal += 4; + + *(unsigned *)color = ComputeFinalVertexColor((byte *)dv->color); + color += 4; + *vDlightBits++ = dlightBits; + } + } +#endif // _XBOX + + // add the indexes + { + int numIndexes; + int w, h; + + h = rows - 1; + w = lodWidth - 1; + numIndexes = tess.numIndexes; + for (i = 0 ; i < h ; i++) { + for (j = 0 ; j < w ; j++) { + int v1, v2, v3, v4; + + // vertex order to be reckognized as tristrips + v1 = numVertexes + i*lodWidth + j + 1; + v2 = v1 - 1; + v3 = v2 + lodWidth; + v4 = v3 + 1; + + tess.indexes[numIndexes] = v2; + tess.indexes[numIndexes+1] = v3; + tess.indexes[numIndexes+2] = v1; + + tess.indexes[numIndexes+3] = v1; + tess.indexes[numIndexes+4] = v3; + tess.indexes[numIndexes+5] = v4; + numIndexes += 6; + } + } + + tess.numIndexes = numIndexes; + } + + tess.numVertexes += rows * lodWidth; + + used += rows - 1; + } +} + + +/* +=========================================================================== + +NULL MODEL + +=========================================================================== +*/ + +/* +=================== +RB_SurfaceAxis + +Draws x/y/z lines from the origin for orientation debugging +=================== +*/ +static void RB_SurfaceAxis( void ) { + GL_Bind( tr.whiteImage ); + qglLineWidth( 3 ); +#ifdef _XBOX + qglBeginEXT( GL_LINES, 6, 3, 0, 0, 0); +#else + qglBegin( GL_LINES ); +#endif + qglColor3f( 1,0,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 16,0,0 ); + qglColor3f( 0,1,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,16,0 ); + qglColor3f( 0,0,1 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,0,16 ); + qglEnd(); + qglLineWidth( 1 ); +} + +//=========================================================================== + +/* +==================== +RB_SurfaceEntity + +Entities that have a single procedurally generated surface +==================== +*/ +void RB_SurfaceEntity( surfaceType_t *surfType ) { + switch( backEnd.currentEntity->e.reType ) { + case RT_SPRITE: + RB_SurfaceSprite(); + break; + case RT_ORIENTED_QUAD: + RB_SurfaceOrientedQuad(); + break; + case RT_BEAM: + RB_SurfaceBeam(); + break; + case RT_ELECTRICITY: + RB_SurfaceElectricity(); + break; + case RT_LINE: + RB_SurfaceLine(); + break; + case RT_ORIENTEDLINE: + RB_SurfaceOrientedLine(); + break; + case RT_SABER_GLOW: + RB_SurfaceSaberGlow(); + break; + case RT_CYLINDER: + RB_SurfaceCylinder(); + break; + case RT_ENT_CHAIN: + { + assert ( 0 ); +/* + int i, count, start; + static trRefEntity_t tempEnt = *backEnd.currentEntity; + //rww - if not static then currentEntity is garbage because + //this is a local. This was not static in sof2.. but I guess + //they never check ce.renderfx so it didn't show up. + + start = backEnd.currentEntity->e.uRefEnt.uMini.miniStart; + count = backEnd.currentEntity->e.uRefEnt.uMini.miniCount; + assert(count > 0); + backEnd.currentEntity = &tempEnt; + + assert(backEnd.currentEntity->e.renderfx >= 0); + + for(i=0;ie, &backEnd.refdef.miniEntities[start+i], sizeof(backEnd.refdef.miniEntities[start+i])); + + assert(backEnd.currentEntity->e.renderfx >= 0); + + RB_SurfaceEntity(surfType); + } +*/ + } + break; + default: + RB_SurfaceAxis(); + break; + } + return; +} + +void RB_SurfaceBad( surfaceType_t *surfType ) { + Com_Printf ("Bad surface tesselated.\n" ); +} + +/* +================== +RB_TestZFlare + +This is called at surface tesselation time +================== +*/ +#ifdef _XBOX +static bool RB_TestZFlare( vec3_t point, srfFlare_t *surf ) { +#else +static bool RB_TestZFlare( vec3_t point) { +#endif + int i; + vec4_t eye, clip, normalized, window; + + // if the point is off the screen, don't bother adding it + // calculate screen coordinates and depth + R_TransformModelToClip( point, backEnd.ori.modelMatrix, + backEnd.viewParms.projectionMatrix, eye, clip ); + + // check to see if the point is completely off screen + for ( i = 0 ; i < 3 ; i++ ) { + if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) { + return qfalse; + } + } + + R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window ); + + if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth + || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) { + return qfalse; // shouldn't happen, since we check the clip[] above, except for FP rounding + } + +//do test + // read back the z buffer contents +#ifdef _XBOX + UINT result; + HRESULT hr; + DWORD zwrite, colorwrite; + + // Get the visibility test from the last frame + if(surf->visible == -1) + surf->visible = 0; + else { + hr = glw_state->device->GetVisibilityTestResult( (int)surf->number, &result, NULL ); + if( hr == D3D_OK) + surf->visible = (int)result; + } + + glw_state->device->GetRenderState(D3DRS_ZWRITEENABLE, &zwrite); + glw_state->device->GetRenderState(D3DRS_COLORWRITEENABLE, &colorwrite); + + glw_state->device->SetRenderState(D3DRS_ZWRITEENABLE, false); + glw_state->device->SetRenderState(D3DRS_COLORWRITEENABLE, 0); + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + glw_state->device->SetTransform(D3DTS_VIEW, + glw_state->matrixStack[glwstate_t::MatrixMode_Model]->GetTop()); + glw_state->device->SetVertexShader(D3DFVF_XYZ); + + glw_state->device->BeginVisibilityTest(); + glw_state->device->Begin(D3DPT_POINTLIST); + glw_state->device->SetVertexData4f(D3DVSDE_VERTEX, point[0], point[1], point[2], 1.0f); + glw_state->device->End(); + glw_state->device->EndVisibilityTest((int)surf->number); + + glw_state->device->SetRenderState(D3DRS_ZWRITEENABLE, zwrite); + glw_state->device->SetRenderState(D3DRS_COLORWRITEENABLE, colorwrite); + + return (surf->visible > 0); +#else + float depth = 0.0f; + bool visible; + float screenZ; + + if ( r_flares->integer !=1 ) { //skipping the the z-test + return true; + } + // doing a readpixels is as good as doing a glFinish(), so + // don't bother with another sync + glState.finishCalled = qfalse; + qglReadPixels( backEnd.viewParms.viewportX + window[0],backEnd.viewParms.viewportY + window[1], 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); + + screenZ = backEnd.viewParms.projectionMatrix[14] / + ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + visible = ( -eye[2] - -screenZ ) < 24; + return visible; +#endif +} + +void RB_SurfaceFlare( srfFlare_t *surf ) { + vec3_t left, up; + float radius; + byte color[4]; + vec3_t dir; + vec3_t origin; + float d, dist; + + if ( !r_flares->integer ) { + return; + } + +#ifdef _XBOX + vec3_t sorigin, snormal; + + Q_CastShort2Float(&sorigin[0], (short*)&surf->origin[0]); + Q_CastShort2Float(&sorigin[1], (short*)&surf->origin[1]); + Q_CastShort2Float(&sorigin[2], (short*)&surf->origin[2]); + + Q_CastShort2Float(&snormal[0], (short*)&surf->normal[0]); + Q_CastShort2Float(&snormal[1], (short*)&surf->normal[1]); + Q_CastShort2Float(&snormal[2], (short*)&surf->normal[2]); + snormal[0] /= 32767.0f; + snormal[1] /= 32767.0f; + snormal[2] /= 32767.0f; + + // Pull the vertex toward the viewer so we don't get any z-fighting on the vis test + VectorSubtract( backEnd.viewParms.ori.origin, sorigin, dir ); + dist = VectorNormalize( dir ); + VectorMA( sorigin, 20, dir, origin ); + + if (!RB_TestZFlare( origin, surf ) ) { + return; + } + + // calculate the xyz locations for the four corners + VectorMA( sorigin, 3, snormal, origin ); +#else + if (!RB_TestZFlare( surf->origin ) ) { + return; + } + + // calculate the xyz locations for the four corners + VectorMA( surf->origin, 3, surf->normal, origin ); + float* snormal = surf->normal; +#endif // _XBOX + + VectorSubtract( origin, backEnd.viewParms.ori.origin, dir ); + dist = VectorNormalize( dir ); + + d = -DotProduct( dir, snormal ); + if ( d < 0 ) { + d = -d; + } + + // fade the intensity of the flare down as the + // light surface turns away from the viewer + color[0] = d * 255; + color[1] = d * 255; + color[2] = d * 255; + color[3] = 255; //only gets used if the shader has cgen exact_vertex! + + radius = tess.shader->portalRange ? tess.shader->portalRange: 30; + if (dist < 512.0f) + { + radius = radius * dist / 512.0f; + } + if (radius<5.0f) + { + radius = 5.0f; + } + VectorScale( backEnd.viewParms.ori.axis[1], radius, left ); + VectorScale( backEnd.viewParms.ori.axis[2], radius, up ); + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( origin, left, up, color ); +} + + +void RB_SurfaceDisplayList( srfDisplayList_t *surf ) { + // all apropriate state must be set in RB_BeginSurface + // this isn't implemented yet... + qglCallList( surf->listNum ); +} + +void RB_SurfaceSkip( void *surf ) { +} + + +void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])( void *) = { + (void(*)(void*))RB_SurfaceBad, // SF_BAD, + (void(*)(void*))RB_SurfaceSkip, // SF_SKIP, + (void(*)(void*))RB_SurfaceFace, // SF_FACE, + (void(*)(void*))RB_SurfaceGrid, // SF_GRID, + (void(*)(void*))RB_SurfaceTriangles, // SF_TRIANGLES, + (void(*)(void*))RB_SurfacePolychain, // SF_POLY, + //(void(*)(void*))RB_SurfaceTerrain, // SF_TERRAIN, //rwwRMG - added + (void(*)(void*))NULL, // SF_TERRAIN, //rwwRMG - added + (void(*)(void*))RB_SurfaceMesh, // SF_MD3, +/* +Ghoul2 Insert Start +*/ + (void(*)(void*))RB_SurfaceGhoul, // SF_MDX, +/* +Ghoul2 Insert End +*/ + (void(*)(void*))RB_SurfaceFlare, // SF_FLARE, + (void(*)(void*))RB_SurfaceEntity, // SF_ENTITY + (void(*)(void*))RB_SurfaceDisplayList // SF_DISPLAY_LIST +}; diff --git a/codemp/renderer/tr_surfacesprites.cpp b/codemp/renderer/tr_surfacesprites.cpp new file mode 100644 index 0000000..24b5d64 --- /dev/null +++ b/codemp/renderer/tr_surfacesprites.cpp @@ -0,0 +1,1463 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_shade.c + +#include "tr_local.h" + +#include "tr_QuickSprite.h" +#include "tr_WorldEffects.h" + + +/////===== Part of the VERTIGON system =====///// +// The surfacesprites are a simple system. When a polygon with this shader stage on it is drawn, +// there are randomly distributed images (defined by the shader stage) placed on the surface. +// these are capable of doing effects, grass, or simple oriented sprites. +// They usually stick vertically off the surface, hence the term vertigons. + +// The vertigons are applied as part of the renderer backend. That is, they access OpenGL calls directly. + + +unsigned char randomindex, randominterval; +const float randomchart[256] = { + 0.6554f, 0.6909f, 0.4806f, 0.6218f, 0.5717f, 0.3896f, 0.0677f, 0.7356f, + 0.8333f, 0.1105f, 0.4445f, 0.8161f, 0.4689f, 0.0433f, 0.7152f, 0.0336f, + 0.0186f, 0.9140f, 0.1626f, 0.6553f, 0.8340f, 0.7094f, 0.2020f, 0.8087f, + 0.9119f, 0.8009f, 0.1339f, 0.8492f, 0.9173f, 0.5003f, 0.6012f, 0.6117f, + 0.5525f, 0.5787f, 0.1586f, 0.3293f, 0.9273f, 0.7791f, 0.8589f, 0.4985f, + 0.0883f, 0.8545f, 0.2634f, 0.4727f, 0.3624f, 0.1631f, 0.7825f, 0.0662f, + 0.6704f, 0.3510f, 0.7525f, 0.9486f, 0.4685f, 0.1535f, 0.1545f, 0.1121f, + 0.4724f, 0.8483f, 0.3833f, 0.1917f, 0.8207f, 0.3885f, 0.9702f, 0.9200f, + 0.8348f, 0.7501f, 0.6675f, 0.4994f, 0.0301f, 0.5225f, 0.8011f, 0.1696f, + 0.5351f, 0.2752f, 0.2962f, 0.7550f, 0.5762f, 0.7303f, 0.2835f, 0.4717f, + 0.1818f, 0.2739f, 0.6914f, 0.7748f, 0.7640f, 0.8355f, 0.7314f, 0.5288f, + 0.7340f, 0.6692f, 0.6813f, 0.2810f, 0.8057f, 0.0648f, 0.8749f, 0.9199f, + 0.1462f, 0.5237f, 0.3014f, 0.4994f, 0.0278f, 0.4268f, 0.7238f, 0.5107f, + 0.1378f, 0.7303f, 0.7200f, 0.3819f, 0.2034f, 0.7157f, 0.5552f, 0.4887f, + 0.0871f, 0.3293f, 0.2892f, 0.4545f, 0.0088f, 0.1404f, 0.0275f, 0.0238f, + 0.0515f, 0.4494f, 0.7206f, 0.2893f, 0.6060f, 0.5785f, 0.4182f, 0.5528f, + 0.9118f, 0.8742f, 0.3859f, 0.6030f, 0.3495f, 0.4550f, 0.9875f, 0.6900f, + 0.6416f, 0.2337f, 0.7431f, 0.9788f, 0.6181f, 0.2464f, 0.4661f, 0.7621f, + 0.7020f, 0.8203f, 0.8869f, 0.2145f, 0.7724f, 0.6093f, 0.6692f, 0.9686f, + 0.5609f, 0.0310f, 0.2248f, 0.2950f, 0.2365f, 0.1347f, 0.2342f, 0.1668f, + 0.3378f, 0.4330f, 0.2775f, 0.9901f, 0.7053f, 0.7266f, 0.4840f, 0.2820f, + 0.5733f, 0.4555f, 0.6049f, 0.0770f, 0.4760f, 0.6060f, 0.4159f, 0.3427f, + 0.1234f, 0.7062f, 0.8569f, 0.1878f, 0.9057f, 0.9399f, 0.8139f, 0.1407f, + 0.1794f, 0.9123f, 0.9493f, 0.2827f, 0.9934f, 0.0952f, 0.4879f, 0.5160f, + 0.4118f, 0.4873f, 0.3642f, 0.7470f, 0.0866f, 0.5172f, 0.6365f, 0.2676f, + 0.2407f, 0.7223f, 0.5761f, 0.1143f, 0.7137f, 0.2342f, 0.3353f, 0.6880f, + 0.2296f, 0.6023f, 0.6027f, 0.4138f, 0.5408f, 0.9859f, 0.1503f, 0.7238f, + 0.6054f, 0.2477f, 0.6804f, 0.1432f, 0.4540f, 0.9776f, 0.8762f, 0.7607f, + 0.9025f, 0.9807f, 0.0652f, 0.8661f, 0.7663f, 0.2586f, 0.3994f, 0.0335f, + 0.7328f, 0.0166f, 0.9589f, 0.4348f, 0.5493f, 0.7269f, 0.6867f, 0.6614f, + 0.6800f, 0.7804f, 0.5591f, 0.8381f, 0.0910f, 0.7573f, 0.8985f, 0.3083f, + 0.3188f, 0.8481f, 0.2356f, 0.6736f, 0.4770f, 0.4560f, 0.6266f, 0.4677f +}; + +#define WIND_DAMP_INTERVAL 50 +#define WIND_GUST_TIME 2500.0 +#define WIND_GUST_DECAY (1.0 / WIND_GUST_TIME) + +int lastSSUpdateTime = 0; +float curWindSpeed=0; +float curWindGust=5; +float curWeatherAmount=1; +vec3_t curWindBlowVect={0,0,0}, targetWindBlowVect={0,0,0}; +vec3_t curWindGrassDir={0,0,0}, targetWindGrassDir={0,0,0}; +int totalsurfsprites=0, sssurfaces=0; + +qboolean curWindPointActive=qfalse; +float curWindPointForce = 0; +vec3_t curWindPoint; +int nextGustTime=0; +float gustLeft=0; + +qboolean standardfovinitialized=qfalse; +float standardfovx = 90, standardscalex = 1.0; +float rangescalefactor=1.0; + +vec3_t ssrightvectors[4]; +vec3_t ssfwdvector; +int rightvectorcount; + +trRefEntity_t *ssLastEntityDrawn=NULL; +vec3_t ssViewOrigin, ssViewRight, ssViewUp; + + +static void R_SurfaceSpriteFrameUpdate(void) +{ + float dtime, dampfactor; // Time since last update and damping time for wind changes + float ratio; + vec3_t ang, diff, retwindvec; + float targetspeed; + vec3_t up={0,0,1}; + + if (backEnd.refdef.time == lastSSUpdateTime) + return; + + if (backEnd.refdef.time < lastSSUpdateTime) + { // Time is BEFORE the last update time, so reset everything. + curWindGust = 5; + curWindSpeed = r_windSpeed->value; + nextGustTime = 0; + gustLeft = 0; + } + + // Reset the last entity drawn, since this is a new frame. + ssLastEntityDrawn = NULL; + + // Adjust for an FOV. If things look twice as wide on the screen, pretend the shaders have twice the range. + // ASSUMPTION HERE IS THAT "standard" fov is the first one rendered. + + if (!standardfovinitialized) + { // This isn't initialized yet. + if (backEnd.refdef.fov_x > 50 && backEnd.refdef.fov_x < 135) // I don't consider anything below 50 or above 135 to be "normal". + { + standardfovx = backEnd.refdef.fov_x; + standardscalex = tan(standardfovx * 0.5 * (M_PI/180.0f)); + standardfovinitialized = qtrue; + } + else + { + standardfovx = 90; + standardscalex = tan(standardfovx * 0.5 * (M_PI/180.0f)); + } + rangescalefactor = 1.0; // Don't multiply the shader range by anything. + } + else if (standardfovx == backEnd.refdef.fov_x) + { // This is the standard FOV (or higher), don't multiply the shader range. + rangescalefactor = 1.0; + } + else + { // We are using a non-standard FOV. We need to multiply the range of the shader by a scale factor. + if (backEnd.refdef.fov_x > 135) + { + rangescalefactor = standardscalex / tan(135.0f * 0.5f * (M_PI/180.0f)); + } + else + { + rangescalefactor = standardscalex / tan(backEnd.refdef.fov_x * 0.5 * (M_PI/180.0f)); + } + } + + // Create a set of four right vectors so that vertical sprites aren't always facing the same way. + // First generate a HORIZONTAL forward vector (important). + CrossProduct(ssViewRight, up, ssfwdvector); + + // Right Zero has a nudge forward (10 degrees). + VectorScale(ssViewRight, 0.985f, ssrightvectors[0]); + VectorMA(ssrightvectors[0], 0.174f, ssfwdvector, ssrightvectors[0]); + + // Right One has a big nudge back (30 degrees). + VectorScale(ssViewRight, 0.866f, ssrightvectors[1]); + VectorMA(ssrightvectors[1], -0.5f, ssfwdvector, ssrightvectors[1]); + + + // Right two has a big nudge forward (30 degrees). + VectorScale(ssViewRight, 0.866f, ssrightvectors[2]); + VectorMA(ssrightvectors[2], 0.5f, ssfwdvector, ssrightvectors[2]); + + + // Right three has a nudge back (10 degrees). + VectorScale(ssViewRight, 0.985f, ssrightvectors[3]); + VectorMA(ssrightvectors[3], -0.174f, ssfwdvector, ssrightvectors[3]); + + + // Update the wind. + // If it is raining, get the windspeed from the rain system rather than the cvar + if (R_IsRaining() || R_IsPuffing()) + { + curWeatherAmount = 1.0; + } + else + { + curWeatherAmount = r_surfaceWeather->value; + } + + if (R_GetWindSpeed(targetspeed)) + { // We successfully got a speed from the rain system. + // Set the windgust to 5, since that looks pretty good. + targetspeed *= 0.3f; + if (targetspeed >= 1.0) + { + curWindGust = 300/targetspeed; + } + else + { + curWindGust = 0; + } + } + else + { // Use the cvar. + targetspeed = r_windSpeed->value; // Minimum gust delay, in seconds. + curWindGust = r_windGust->value; + } + + if (targetspeed > 0 && curWindGust) + { + if (gustLeft > 0) + { // We are gusting + // Add an amount to the target wind speed + targetspeed *= 1.0 + gustLeft; + + gustLeft -= (float)(backEnd.refdef.time - lastSSUpdateTime)*WIND_GUST_DECAY; + if (gustLeft <= 0) + { + nextGustTime = backEnd.refdef.time + (curWindGust*1000)*flrand(1.0f,4.0f); + } + } + else if (backEnd.refdef.time >= nextGustTime) + { // See if there is another right now + // Gust next time, mano + gustLeft = flrand(0.75f,1.5f); + } + } + + // See if there is a weather system that will tell us a windspeed. + if (R_GetWindVector(retwindvec)) + { + retwindvec[2]=0; + VectorScale(retwindvec, -1.0f, retwindvec); + vectoangles(retwindvec, ang); + } + else + { // Calculate the target wind vector based off cvars + ang[YAW] = r_windAngle->value; + } + + ang[PITCH] = -90.0 + targetspeed; + if (ang[PITCH]>-45.0) + { + ang[PITCH] = -45.0; + } + ang[ROLL] = 0; + + if (targetspeed>0) + { +// ang[YAW] += cos(tr.refdef.time*0.01+flrand(-1.0,1.0))*targetspeed*0.5; +// ang[PITCH] += sin(tr.refdef.time*0.01+flrand(-1.0,1.0))*targetspeed*0.5; + } + + // Get the grass wind vector first + AngleVectors(ang, targetWindGrassDir, NULL, NULL); + targetWindGrassDir[2]-=1.0; +// VectorScale(targetWindGrassDir, targetspeed, targetWindGrassDir); + + // Now get the general wind vector (no pitch) + ang[PITCH]=0; + AngleVectors(ang, targetWindBlowVect, NULL, NULL); + + // Start calculating a smoothing factor so wind doesn't change abruptly between speeds. + dampfactor = 1.0-r_windDampFactor->value; // We must exponent the amount LEFT rather than the amount bled off + dtime = (float)(backEnd.refdef.time - lastSSUpdateTime) * (1.0/(float)WIND_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1". + + // Note that since there are a finite number of "practical" delta millisecond values possible, + // the ratio should be initialized into a chart ultimately. + ratio = pow(dampfactor, dtime); + + // Apply this ratio to the windspeed... + curWindSpeed = targetspeed - (ratio * (targetspeed-curWindSpeed)); + + // Use the curWindSpeed to calculate the final target wind vector (with speed) + VectorScale(targetWindBlowVect, curWindSpeed, targetWindBlowVect); + VectorSubtract(targetWindBlowVect, curWindBlowVect, diff); + VectorMA(targetWindBlowVect, -ratio, diff, curWindBlowVect); + + // Update the grass vector now + VectorSubtract(targetWindGrassDir, curWindGrassDir, diff); + VectorMA(targetWindGrassDir, -ratio, diff, curWindGrassDir); + + lastSSUpdateTime = backEnd.refdef.time; + + curWindPointForce = r_windPointForce->value - (ratio * (r_windPointForce->value - curWindPointForce)); + if (curWindPointForce < 0.01) + { + curWindPointActive = qfalse; + } + else + { + curWindPointActive = qtrue; + curWindPoint[0] = r_windPointX->value; + curWindPoint[1] = r_windPointY->value; + curWindPoint[2] = 0; + } + + if (r_surfaceSprites->integer >= 2) + { + Com_Printf("Surfacesprites Drawn: %d, on %d surfaces\n", totalsurfsprites, sssurfaces); + } + + totalsurfsprites=0; + sssurfaces=0; +} + + + +///////////////////////////////////////////// +// Surface sprite calculation and drawing. +///////////////////////////////////////////// + +#define FADE_RANGE 250.0 +#define WINDPOINT_RADIUS 750.0 + +float SSVertAlpha[SHADER_MAX_VERTEXES]; +float SSVertWindForce[SHADER_MAX_VERTEXES]; +vec2_t SSVertWindDir[SHADER_MAX_VERTEXES]; + +qboolean SSAdditiveTransparency=qfalse; +qboolean SSUsingFog=qfalse; + + +///////////////////////////////////////////// +// Vertical surface sprites + +static void RB_VerticalSurfaceSprite(vec3_t loc, float width, float height, byte light, + byte alpha, float wind, float windidle, vec2_t fog, int hangdown, vec2_t skew) +{ + vec3_t loc2, right; + float angle; + float windsway; + float points[16]; + color4ub_t color; + + angle = ((loc[0]+loc[1])*0.02+(tr.refdef.time*0.0015)); + + if (windidle>0.0) + { + windsway = (height*windidle*0.075); + loc2[0] = loc[0]+skew[0]+cos(angle)*windsway; + loc2[1] = loc[1]+skew[1]+sin(angle)*windsway; + + if (hangdown) + { + loc2[2] = loc[2]-height; + } + else + { + loc2[2] = loc[2]+height; + } + } + else + { + loc2[0] = loc[0]+skew[0]; + loc2[1] = loc[1]+skew[1]; + if (hangdown) + { + loc2[2] = loc[2]-height; + } + else + { + loc2[2] = loc[2]+height; + } + } + + if (wind>0.0 && curWindSpeed > 0.001) + { + windsway = (height*wind*0.075); + + // Add the angle + VectorMA(loc2, height*wind, curWindGrassDir, loc2); + // Bob up and down + if (curWindSpeed < 40.0) + { + windsway *= curWindSpeed*(1.0/100.0); + } + else + { + windsway *= 0.4f; + } + loc2[2] += sin(angle*2.5)*windsway; + } + + VectorScale(ssrightvectors[rightvectorcount], width*0.5, right); + + color[0]=light; + color[1]=light; + color[2]=light; + color[3]=alpha; + + // Bottom right +// VectorAdd(loc, right, point); + points[0] = loc[0] + right[0]; + points[1] = loc[1] + right[1]; + points[2] = loc[2] + right[2]; + points[3] = 0; + + // Top right +// VectorAdd(loc2, right, point); + points[4] = loc2[0] + right[0]; + points[5] = loc2[1] + right[1]; + points[6] = loc2[2] + right[2]; + points[7] = 0; + + // Top left +// VectorSubtract(loc2, right, point); + points[8] = loc2[0] - right[0] + ssfwdvector[0] * width * 0.2; + points[9] = loc2[1] - right[1] + ssfwdvector[1] * width * 0.2; + points[10] = loc2[2] - right[2]; + points[11] = 0; + + // Bottom left +// VectorSubtract(loc, right, point); + points[12] = loc[0] - right[0]; + points[13] = loc[1] - right[1]; + points[14] = loc[2] - right[2]; + points[15] = 0; + + // Add the sprite to the render list. + SQuickSprite.Add(points, color, fog); +} + +static void RB_VerticalSurfaceSpriteWindPoint(vec3_t loc, float width, float height, byte light, + byte alpha, float wind, float windidle, vec2_t fog, + int hangdown, vec2_t skew, vec2_t winddiff, float windforce) +{ + vec3_t loc2, right; + float angle; + float windsway; + float points[16]; + color4ub_t color; + + if (windforce > 1) + windforce = 1; + +// wind += 1.0-windforce; + + angle = (loc[0]+loc[1])*0.02+(tr.refdef.time*0.0015); + + if (curWindSpeed <80.0) + { + windsway = (height*windidle*0.1)*(1.0+windforce); + loc2[0] = loc[0]+skew[0]+cos(angle)*windsway; + loc2[1] = loc[1]+skew[1]+sin(angle)*windsway; + } + else + { + loc2[0] = loc[0]+skew[0]; + loc2[1] = loc[1]+skew[1]; + } + if (hangdown) + { + loc2[2] = loc[2]-height; + } + else + { + loc2[2] = loc[2]+height; + } + + if (curWindSpeed > 0.001) + { + // Add the angle + VectorMA(loc2, height*wind, curWindGrassDir, loc2); + } + + loc2[0] += height*winddiff[0]*windforce; + loc2[1] += height*winddiff[1]*windforce; + loc2[2] -= height*windforce*(0.75 + 0.15*sin((tr.refdef.time + 500*windforce)*0.01)); + + VectorScale(ssrightvectors[rightvectorcount], width*0.5, right); + + color[0]=light; + color[1]=light; + color[2]=light; + color[3]=alpha; + + // Bottom right +// VectorAdd(loc, right, point); + points[0] = loc[0] + right[0]; + points[1] = loc[1] + right[1]; + points[2] = loc[2] + right[2]; + points[3] = 0; + + // Top right +// VectorAdd(loc2, right, point); + points[4] = loc2[0] + right[0]; + points[5] = loc2[1] + right[1]; + points[6] = loc2[2] + right[2]; + points[7] = 0; + + // Top left +// VectorSubtract(loc2, right, point); + points[8] = loc2[0] - right[0] + ssfwdvector[0] * width * 0.15; + points[9] = loc2[1] - right[1] + ssfwdvector[1] * width * 0.15; + points[10] = loc2[2] - right[2]; + points[11] = 0; + + // Bottom left +// VectorSubtract(loc, right, point); + points[12] = loc[0] - right[0]; + points[13] = loc[1] - right[1]; + points[14] = loc[2] - right[2]; + points[15] = 0; + + // Add the sprite to the render list. + SQuickSprite.Add(points, color, fog); +} + +static void RB_DrawVerticalSurfaceSprites( shaderStage_t *stage, shaderCommands_t *input) +{ + int curindex, curvert; + vec3_t dist; + float triarea; + vec2_t vec1to2, vec1to3; + + vec3_t v1,v2,v3; + float a1,a2,a3; + float l1,l2,l3; + vec2_t fog1, fog2, fog3; + vec2_t winddiff1, winddiff2, winddiff3; + float windforce1, windforce2, windforce3; + + float posi, posj; + float step; + float fa,fb,fc; + + vec3_t curpoint; + float width, height; + float alpha, alphapos, thisspritesfadestart, light; + + byte randomindex2; + + vec2_t skew={0,0}; + vec2_t fogv; + vec2_t winddiffv; + float windforce=0; + qboolean usewindpoint = (qboolean) !! (curWindPointActive && stage->ss->wind > 0); + + float cutdist=stage->ss->fadeMax*rangescalefactor, cutdist2=cutdist*cutdist; + float fadedist=stage->ss->fadeDist*rangescalefactor, fadedist2=fadedist*fadedist; + + assert(cutdist2 != fadedist2); + float inv_fadediff = 1.0/(cutdist2-fadedist2); + + // The faderange is the fraction amount it takes for these sprites to fade out, assuming an ideal fade range of 250 + float faderange = FADE_RANGE/(cutdist-fadedist); + + if (faderange > 1.0) + { // Don't want to force a new fade_rand + faderange = 1.0; + } + + // Quickly calc all the alphas and windstuff for each vertex + for (curvert=0; curvertnumVertexes; curvert++) + { + VectorSubtract(ssViewOrigin, input->xyz[curvert], dist); + SSVertAlpha[curvert] = 1.0 - (VectorLengthSquared(dist) - fadedist2) * inv_fadediff; + } + + // Wind only needs initialization once per tess. + if (usewindpoint && !tess.SSInitializedWind) + { + for (curvert=0; curvertnumVertexes;curvert++) + { // Calc wind at each point + dist[0]=input->xyz[curvert][0] - curWindPoint[0]; + dist[1]=input->xyz[curvert][1] - curWindPoint[1]; + step = (dist[0]*dist[0] + dist[1]*dist[1]); // dist squared + + if (step >= (float)(WINDPOINT_RADIUS*WINDPOINT_RADIUS)) + { // No wind + SSVertWindDir[curvert][0] = 0; + SSVertWindDir[curvert][1] = 0; + SSVertWindForce[curvert]=0; // Should be < 1 + } + else + { + if (step<1) + { // Don't want to divide by zero + SSVertWindDir[curvert][0] = 0; + SSVertWindDir[curvert][1] = 0; + SSVertWindForce[curvert] = curWindPointForce * stage->ss->wind; + } + else + { + step = Q_rsqrt(step); // Equals 1 over the distance. + SSVertWindDir[curvert][0] = dist[0] * step; + SSVertWindDir[curvert][1] = dist[1] * step; + step = 1.0 - (1.0 / (step * WINDPOINT_RADIUS)); // 1- (dist/maxradius) = a scale from 0 to 1 linearly dropping off + SSVertWindForce[curvert] = curWindPointForce * stage->ss->wind * step; // *step means divide by the distance. + } + } + } + tess.SSInitializedWind = qtrue; + } + + for (curindex=0; curindexnumIndexes-2; curindex+=3) + { + curvert = input->indexes[curindex]; + VectorCopy(input->xyz[curvert], v1); + if (stage->ss->facing) + { // Hang down + if (input->normal[curvert][2] > -0.5) + { + continue; + } + } + else + { // Point up + if (input->normal[curvert][2] < 0.5) + { + continue; + } + } + l1 = input->vertexColors[curvert][2]; + a1 = SSVertAlpha[curvert]; + fog1[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog1[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + winddiff1[0] = SSVertWindDir[curvert][0]; + winddiff1[1] = SSVertWindDir[curvert][1]; + windforce1 = SSVertWindForce[curvert]; + + curvert = input->indexes[curindex+1]; + VectorCopy(input->xyz[curvert], v2); + if (stage->ss->facing) + { // Hang down + if (input->normal[curvert][2] > -0.5) + { + continue; + } + } + else + { // Point up + if (input->normal[curvert][2] < 0.5) + { + continue; + } + } + l2 = input->vertexColors[curvert][2]; + a2 = SSVertAlpha[curvert]; + fog2[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog2[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + winddiff2[0] = SSVertWindDir[curvert][0]; + winddiff2[1] = SSVertWindDir[curvert][1]; + windforce2 = SSVertWindForce[curvert]; + + curvert = input->indexes[curindex+2]; + VectorCopy(input->xyz[curvert], v3); + if (stage->ss->facing) + { // Hang down + if (input->normal[curvert][2] > -0.5) + { + continue; + } + } + else + { // Point up + if (input->normal[curvert][2] < 0.5) + { + continue; + } + } + l3 = input->vertexColors[curvert][2]; + a3 = SSVertAlpha[curvert]; + fog3[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog3[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + winddiff3[0] = SSVertWindDir[curvert][0]; + winddiff3[1] = SSVertWindDir[curvert][1]; + windforce3 = SSVertWindForce[curvert]; + + if (a1 <= 0.0 && a2 <= 0.0 && a3 <= 0.0) + { + continue; + } + + // Find the area in order to calculate the stepsize + vec1to2[0] = v2[0] - v1[0]; + vec1to2[1] = v2[1] - v1[1]; + vec1to3[0] = v3[0] - v1[0]; + vec1to3[1] = v3[1] - v1[1]; + + // Now get the cross product of this sum. + triarea = vec1to3[0]*vec1to2[1] - vec1to3[1]*vec1to2[0]; + triarea=fabs(triarea); + if (triarea <= 1.0) + { // Insanely small abhorrent triangle. + continue; + } + step = stage->ss->density * Q_rsqrt(triarea); + + randomindex = (byte)(v1[0]+v1[1]+v2[0]+v2[1]+v3[0]+v3[1]); + randominterval = (byte)(v1[0]+v2[1]+v3[2])|0x03; // Make sure the interval is at least 3, and always odd + rightvectorcount = 0; + + for (posi=0; posi<1.0; posi+=step) + { + for (posj=0; posj<(1.0-posi); posj+=step) + { + fa=posi+randomchart[randomindex]*step; + randomindex += randominterval; + + fb=posj+randomchart[randomindex]*step; + randomindex += randominterval; + + rightvectorcount=(rightvectorcount+1)&3; + + if (fa>1.0) + continue; + + if (fb>(1.0-fa)) + continue; + + fc = 1.0-fa-fb; + + // total alpha, minus random factor so some things fade out sooner. + alphapos = a1*fa + a2*fb + a3*fc; + + // Note that the alpha at this point is a value from 1.0 to 0.0, but represents when to START fading + thisspritesfadestart = faderange + (1.0-faderange) * randomchart[randomindex]; + randomindex += randominterval; + + // Find where the alpha is relative to the fadestart, and calc the real alpha to draw at. + alpha = 1.0 - ((thisspritesfadestart-alphapos)/faderange); + if (alpha > 0.0) + { + if (alpha > 1.0) + alpha=1.0; + + if (SSUsingFog) + { + fogv[0] = fog1[0]*fa + fog2[0]*fb + fog3[0]*fc; + fogv[1] = fog1[1]*fa + fog2[1]*fb + fog3[1]*fc; + } + + if (usewindpoint) + { + winddiffv[0] = winddiff1[0]*fa + winddiff2[0]*fb + winddiff3[0]*fc; + winddiffv[1] = winddiff1[1]*fa + winddiff2[1]*fb + winddiff3[1]*fc; + windforce = windforce1*fa + windforce2*fb + windforce3*fc; + } + + VectorScale(v1, fa, curpoint); + VectorMA(curpoint, fb, v2, curpoint); + VectorMA(curpoint, fc, v3, curpoint); + + light = l1*fa + l2*fb + l3*fc; + if (SSAdditiveTransparency) + { // Additive transparency, scale light value +// light *= alpha; + light = (128 + (light*0.5))*alpha; + alpha = 1.0; + } + + randomindex2 = randomindex; + width = stage->ss->width*(1.0 + (stage->ss->variance[0]*randomchart[randomindex2])); + height = stage->ss->height*(1.0 + (stage->ss->variance[1]*randomchart[randomindex2++])); + if (randomchart[randomindex2++]>0.5) + { + width = -width; + } + if (stage->ss->fadeScale!=0 && alphapos < 1.0) + { + width *= 1.0 + (stage->ss->fadeScale*(1.0-alphapos)); + } + + if (stage->ss->vertSkew != 0) + { // flrand(-vertskew, vertskew) + skew[0] = height * ((stage->ss->vertSkew*2.0f*randomchart[randomindex2++])-stage->ss->vertSkew); + skew[1] = height * ((stage->ss->vertSkew*2.0f*randomchart[randomindex2++])-stage->ss->vertSkew); + } + + if (usewindpoint && windforce > 0 && stage->ss->wind > 0.0) + { + if (SSUsingFog) + { + RB_VerticalSurfaceSpriteWindPoint(curpoint, width, height, (byte)light, (byte)(alpha*255.0), + stage->ss->wind, stage->ss->windIdle, fogv, stage->ss->facing, skew, + winddiffv, windforce); + } + else + { + RB_VerticalSurfaceSpriteWindPoint(curpoint, width, height, (byte)light, (byte)(alpha*255.0), + stage->ss->wind, stage->ss->windIdle, NULL, stage->ss->facing, skew, + winddiffv, windforce); + } + } + else + { + if (SSUsingFog) + { + RB_VerticalSurfaceSprite(curpoint, width, height, (byte)light, (byte)(alpha*255.0), + stage->ss->wind, stage->ss->windIdle, fogv, stage->ss->facing, skew); + } + else + { + RB_VerticalSurfaceSprite(curpoint, width, height, (byte)light, (byte)(alpha*255.0), + stage->ss->wind, stage->ss->windIdle, NULL, stage->ss->facing, skew); + } + } + + totalsurfsprites++; + } + } + } + } +} + + +///////////////////////////////////////////// +// Oriented surface sprites + +static void RB_OrientedSurfaceSprite(vec3_t loc, float width, float height, byte light, byte alpha, vec2_t fog, int faceup) +{ + vec3_t loc2, right; + float points[16]; + color4ub_t color; + + color[0]=light; + color[1]=light; + color[2]=light; + color[3]=alpha; + + if (faceup) + { + width *= 0.5; + height *= 0.5; + + // Bottom right + // VectorAdd(loc, right, point); + points[0] = loc[0] + width; + points[1] = loc[1] - width; + points[2] = loc[2] + 1.0; + points[3] = 0; + + // Top right + // VectorAdd(loc, right, point); + points[4] = loc[0] + width; + points[5] = loc[1] + width; + points[6] = loc[2] + 1.0; + points[7] = 0; + + // Top left + // VectorSubtract(loc, right, point); + points[8] = loc[0] - width; + points[9] = loc[1] + width; + points[10] = loc[2] + 1.0; + points[11] = 0; + + // Bottom left + // VectorSubtract(loc, right, point); + points[12] = loc[0] - width; + points[13] = loc[1] - width; + points[14] = loc[2] + 1.0; + points[15] = 0; + } + else + { + VectorMA(loc, height, ssViewUp, loc2); + VectorScale(ssViewRight, width*0.5, right); + + // Bottom right + // VectorAdd(loc, right, point); + points[0] = loc[0] + right[0]; + points[1] = loc[1] + right[1]; + points[2] = loc[2] + right[2]; + points[3] = 0; + + // Top right + // VectorAdd(loc2, right, point); + points[4] = loc2[0] + right[0]; + points[5] = loc2[1] + right[1]; + points[6] = loc2[2] + right[2]; + points[7] = 0; + + // Top left + // VectorSubtract(loc2, right, point); + points[8] = loc2[0] - right[0]; + points[9] = loc2[1] - right[1]; + points[10] = loc2[2] - right[2]; + points[11] = 0; + + // Bottom left + // VectorSubtract(loc, right, point); + points[12] = loc[0] - right[0]; + points[13] = loc[1] - right[1]; + points[14] = loc[2] - right[2]; + points[15] = 0; + } + + // Add the sprite to the render list. + SQuickSprite.Add(points, color, fog); +} + +static void RB_DrawOrientedSurfaceSprites( shaderStage_t *stage, shaderCommands_t *input) +{ + int curindex, curvert; + vec3_t dist; + float triarea, minnormal; + vec2_t vec1to2, vec1to3; + + vec3_t v1,v2,v3; + float a1,a2,a3; + float l1,l2,l3; + vec2_t fog1, fog2, fog3; + + float posi, posj; + float step; + float fa,fb,fc; + + vec3_t curpoint; + float width, height; + float alpha, alphapos, thisspritesfadestart, light; + byte randomindex2; + vec2_t fogv; + + float cutdist=stage->ss->fadeMax*rangescalefactor, cutdist2=cutdist*cutdist; + float fadedist=stage->ss->fadeDist*rangescalefactor, fadedist2=fadedist*fadedist; + + assert(cutdist2 != fadedist2); + float inv_fadediff = 1.0/(cutdist2-fadedist2); + + // The faderange is the fraction amount it takes for these sprites to fade out, assuming an ideal fade range of 250 + float faderange = FADE_RANGE/(cutdist-fadedist); + + if (faderange > 1.0) + { // Don't want to force a new fade_rand + faderange = 1.0; + } + + if (stage->ss->facing) + { // Faceup sprite. + minnormal = 0.99f; + } + else + { // Normal oriented sprite + minnormal = 0.5f; + } + + // Quickly calc all the alphas for each vertex + for (curvert=0; curvertnumVertexes; curvert++) + { + // Calc alpha at each point + VectorSubtract(ssViewOrigin, input->xyz[curvert], dist); + SSVertAlpha[curvert] = 1.0 - (VectorLengthSquared(dist) - fadedist2) * inv_fadediff; + } + + for (curindex=0; curindexnumIndexes-2; curindex+=3) + { + curvert = input->indexes[curindex]; + VectorCopy(input->xyz[curvert], v1); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l1 = input->vertexColors[curvert][2]; + a1 = SSVertAlpha[curvert]; + fog1[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog1[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + + curvert = input->indexes[curindex+1]; + VectorCopy(input->xyz[curvert], v2); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l2 = input->vertexColors[curvert][2]; + a2 = SSVertAlpha[curvert]; + fog2[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog2[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + + curvert = input->indexes[curindex+2]; + VectorCopy(input->xyz[curvert], v3); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l3 = input->vertexColors[curvert][2]; + a3 = SSVertAlpha[curvert]; + fog3[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog3[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + + if (a1 <= 0.0 && a2 <= 0.0 && a3 <= 0.0) + { + continue; + } + + // Find the area in order to calculate the stepsize + vec1to2[0] = v2[0] - v1[0]; + vec1to2[1] = v2[1] - v1[1]; + vec1to3[0] = v3[0] - v1[0]; + vec1to3[1] = v3[1] - v1[1]; + + // Now get the cross product of this sum. + triarea = vec1to3[0]*vec1to2[1] - vec1to3[1]*vec1to2[0]; + triarea=fabs(triarea); + if (triarea <= 1.0) + { // Insanely small abhorrent triangle. + continue; + } + step = stage->ss->density * Q_rsqrt(triarea); + + randomindex = (byte)(v1[0]+v1[1]+v2[0]+v2[1]+v3[0]+v3[1]); + randominterval = (byte)(v1[0]+v2[1]+v3[2])|0x03; // Make sure the interval is at least 3, and always odd + + for (posi=0; posi<1.0; posi+=step) + { + for (posj=0; posj<(1.0-posi); posj+=step) + { + fa=posi+randomchart[randomindex]*step; + randomindex += randominterval; + if (fa>1.0) + continue; + + fb=posj+randomchart[randomindex]*step; + randomindex += randominterval; + if (fb>(1.0-fa)) + continue; + + fc = 1.0-fa-fb; + + // total alpha, minus random factor so some things fade out sooner. + alphapos = a1*fa + a2*fb + a3*fc; + + // Note that the alpha at this point is a value from 1.0 to 0.0, but represents when to START fading + thisspritesfadestart = faderange + (1.0-faderange) * randomchart[randomindex]; + randomindex += randominterval; + + // Find where the alpha is relative to the fadestart, and calc the real alpha to draw at. + alpha = 1.0 - ((thisspritesfadestart-alphapos)/faderange); + + randomindex += randominterval; + if (alpha > 0.0) + { + if (alpha > 1.0) + alpha=1.0; + + if (SSUsingFog) + { + fogv[0] = fog1[0]*fa + fog2[0]*fb + fog3[0]*fc; + fogv[1] = fog1[1]*fa + fog2[1]*fb + fog3[1]*fc; + } + + VectorScale(v1, fa, curpoint); + VectorMA(curpoint, fb, v2, curpoint); + VectorMA(curpoint, fc, v3, curpoint); + + light = l1*fa + l2*fb + l3*fc; + if (SSAdditiveTransparency) + { // Additive transparency, scale light value +// light *= alpha; + light = (128 + (light*0.5))*alpha; + alpha = 1.0; + } + + randomindex2 = randomindex; + width = stage->ss->width*(1.0 + (stage->ss->variance[0]*randomchart[randomindex2])); + height = stage->ss->height*(1.0 + (stage->ss->variance[1]*randomchart[randomindex2++])); + if (randomchart[randomindex2++]>0.5) + { + width = -width; + } + if (stage->ss->fadeScale!=0 && alphapos < 1.0) + { + width *= 1.0 + (stage->ss->fadeScale*(1.0-alphapos)); + } + + if (SSUsingFog) + { + RB_OrientedSurfaceSprite(curpoint, width, height, (byte)light, (byte)(alpha*255.0), fogv, stage->ss->facing); + } + else + { + RB_OrientedSurfaceSprite(curpoint, width, height, (byte)light, (byte)(alpha*255.0), NULL, stage->ss->facing); + } + + totalsurfsprites++; + } + } + } + } +} + + +///////////////////////////////////////////// +// Effect surface sprites + +static void RB_EffectSurfaceSprite(vec3_t loc, float width, float height, byte light, byte alpha, float life, int faceup) +{ + vec3_t loc2, right; + float points[16]; + color4ub_t color; + + color[0]=light; //light; + color[1]=light; //light; + color[2]=light; //light; + color[3]=alpha; //alpha; + + if (faceup) + { + width *= 0.5; + height *= 0.5; + + // Bottom right + // VectorAdd(loc, right, point); + points[0] = loc[0] + width; + points[1] = loc[1] - width; + points[2] = loc[2] + 1.0; + points[3] = 0; + + // Top right + // VectorAdd(loc, right, point); + points[4] = loc[0] + width; + points[5] = loc[1] + width; + points[6] = loc[2] + 1.0; + points[7] = 0; + + // Top left + // VectorSubtract(loc, right, point); + points[8] = loc[0] - width; + points[9] = loc[1] + width; + points[10] = loc[2] + 1.0; + points[11] = 0; + + // Bottom left + // VectorSubtract(loc, right, point); + points[12] = loc[0] - width; + points[13] = loc[1] - width; + points[14] = loc[2] + 1.0; + points[15] = 0; + } + else + { + VectorMA(loc, height, ssViewUp, loc2); + VectorScale(ssViewRight, width*0.5, right); + + // Bottom right + // VectorAdd(loc, right, point); + points[0] = loc[0] + right[0]; + points[1] = loc[1] + right[1]; + points[2] = loc[2] + right[2]; + points[3] = 0; + + // Top right + // VectorAdd(loc2, right, point); + points[4] = loc2[0] + right[0]; + points[5] = loc2[1] + right[1]; + points[6] = loc2[2] + right[2]; + points[7] = 0; + + // Top left + // VectorSubtract(loc2, right, point); + points[8] = loc2[0] - right[0]; + points[9] = loc2[1] - right[1]; + points[10] = loc2[2] - right[2]; + points[11] = 0; + + // Bottom left + // VectorSubtract(loc, right, point); + points[12] = loc[0] - right[0]; + points[13] = loc[1] - right[1]; + points[14] = loc[2] - right[2]; + points[15] = 0; + } + + // Add the sprite to the render list. + SQuickSprite.Add(points, color, NULL); +} + +static void RB_DrawEffectSurfaceSprites( shaderStage_t *stage, shaderCommands_t *input) +{ + int curindex, curvert; + vec3_t dist; + float triarea, minnormal; + vec2_t vec1to2, vec1to3; + + vec3_t v1,v2,v3; + float a1,a2,a3; + float l1,l2,l3; + + float posi, posj; + float step; + float fa,fb,fc; + float effecttime, effectpos; + float density; + + vec3_t curpoint; + float width, height; + float alpha, alphapos, thisspritesfadestart, light; + byte randomindex2; + + float cutdist=stage->ss->fadeMax*rangescalefactor, cutdist2=cutdist*cutdist; + float fadedist=stage->ss->fadeDist*rangescalefactor, fadedist2=fadedist*fadedist; + + float fxalpha = stage->ss->fxAlphaEnd - stage->ss->fxAlphaStart; + qboolean fadeinout=qfalse; + + assert(cutdist2 != fadedist2); + float inv_fadediff = 1.0/(cutdist2-fadedist2); + + // The faderange is the fraction amount it takes for these sprites to fade out, assuming an ideal fade range of 250 + float faderange = FADE_RANGE/(cutdist-fadedist); + if (faderange > 1.0f) + { // Don't want to force a new fade_rand + faderange = 1.0f; + } + + if (stage->ss->facing) + { // Faceup sprite. + minnormal = 0.99f; + } + else + { // Normal oriented sprite + minnormal = 0.5f; + } + + // Make the object fade in. + if (stage->ss->fxAlphaEnd < 0.05 && stage->ss->height >= 0.1 && stage->ss->width >= 0.1) + { // The sprite fades out, and it doesn't start at a pinpoint. Let's fade it in. + fadeinout=qtrue; + } + + if (stage->ss->surfaceSpriteType == SURFSPRITE_WEATHERFX) + { // This effect is affected by weather settings. + if (curWeatherAmount < 0.01) + { // Don't show these effects + return; + } + else + { + density = stage->ss->density / curWeatherAmount; + } + } + else + { + density = stage->ss->density; + } + + // Quickly calc all the alphas for each vertex + for (curvert=0; curvertnumVertexes; curvert++) + { + // Calc alpha at each point + VectorSubtract(ssViewOrigin, input->xyz[curvert], dist); + SSVertAlpha[curvert] = 1.0f - (VectorLengthSquared(dist) - fadedist2) * inv_fadediff; + + // Note this is the proper equation, but isn't used right now because it would be just a tad slower. + // Formula for alpha is 1.0f - ((len-fade)/(cut-fade)) + // Which is equal to (1.0+fade/(cut-fade)) - (len/(cut-fade)) + // So mult=1/(cut-fade), and base=(1+fade*mult). + // SSVertAlpha[curvert] = fadebase - (VectorLength(dist) * fademult); + + } + + for (curindex=0; curindexnumIndexes-2; curindex+=3) + { + curvert = input->indexes[curindex]; + VectorCopy(input->xyz[curvert], v1); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l1 = input->vertexColors[curvert][2]; + a1 = SSVertAlpha[curvert]; + + curvert = input->indexes[curindex+1]; + VectorCopy(input->xyz[curvert], v2); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l2 = input->vertexColors[curvert][2]; + a2 = SSVertAlpha[curvert]; + + curvert = input->indexes[curindex+2]; + VectorCopy(input->xyz[curvert], v3); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l3 = input->vertexColors[curvert][2]; + a3 = SSVertAlpha[curvert]; + + if (a1 <= 0.0f && a2 <= 0.0f && a3 <= 0.0f) + { + continue; + } + + // Find the area in order to calculate the stepsize + vec1to2[0] = v2[0] - v1[0]; + vec1to2[1] = v2[1] - v1[1]; + vec1to3[0] = v3[0] - v1[0]; + vec1to3[1] = v3[1] - v1[1]; + + // Now get the cross product of this sum. + triarea = vec1to3[0]*vec1to2[1] - vec1to3[1]*vec1to2[0]; + triarea=fabs(triarea); + if (triarea <= 1.0f) + { // Insanely small abhorrent triangle. + continue; + } + step = density * Q_rsqrt(triarea); + + randomindex = (byte)(v1[0]+v1[1]+v2[0]+v2[1]+v3[0]+v3[1]); + randominterval = (byte)(v1[0]+v2[1]+v3[2])|0x03; // Make sure the interval is at least 3, and always odd + + for (posi=0; posi<1.0f; posi+=step) + { + for (posj=0; posj<(1.0-posi); posj+=step) + { + effecttime = (tr.refdef.time+10000.0*randomchart[randomindex])/stage->ss->fxDuration; + effectpos = (float)effecttime - (int)effecttime; + + randomindex2 = randomindex+effecttime; + randomindex += randominterval; + fa=posi+randomchart[randomindex2++]*step; + if (fa>1.0f) + continue; + + fb=posj+randomchart[randomindex2++]*step; + if (fb>(1.0-fa)) + continue; + + fc = 1.0-fa-fb; + + // total alpha, minus random factor so some things fade out sooner. + alphapos = a1*fa + a2*fb + a3*fc; + + // Note that the alpha at this point is a value from 1.0f to 0.0, but represents when to START fading + thisspritesfadestart = faderange + (1.0-faderange) * randomchart[randomindex2]; + randomindex2 += randominterval; + + // Find where the alpha is relative to the fadestart, and calc the real alpha to draw at. + alpha = 1.0f - ((thisspritesfadestart-alphapos)/faderange); + if (alpha > 0.0f) + { + if (alpha > 1.0f) + alpha=1.0f; + + VectorScale(v1, fa, curpoint); + VectorMA(curpoint, fb, v2, curpoint); + VectorMA(curpoint, fc, v3, curpoint); + + light = l1*fa + l2*fb + l3*fc; + randomindex2 = randomindex; + width = stage->ss->width*(1.0f + (stage->ss->variance[0]*randomchart[randomindex2])); + height = stage->ss->height*(1.0f + (stage->ss->variance[1]*randomchart[randomindex2++])); + + width = width + (effectpos*stage->ss->fxGrow[0]*width); + height = height + (effectpos*stage->ss->fxGrow[1]*height); + + // If we want to fade in and out, that's different than a straight fade. + if (fadeinout) + { + if (effectpos > 0.5) + { // Fade out + alpha = alpha*(stage->ss->fxAlphaStart+(fxalpha*(effectpos-0.5)*2.0)); + } + else + { // Fade in + alpha = alpha*(stage->ss->fxAlphaStart+(fxalpha*(0.5-effectpos)*2.0)); + } + } + else + { // Normal fade + alpha = alpha*(stage->ss->fxAlphaStart+(fxalpha*effectpos)); + } + + if (SSAdditiveTransparency) + { // Additive transparency, scale light value +// light *= alpha; + light = (128 + (light*0.5))*alpha; + alpha = 1.0; + } + + if (randomchart[randomindex2]>0.5f) + { + width = -width; + } + if (stage->ss->fadeScale!=0 && alphapos < 1.0f) + { + width *= 1.0f + (stage->ss->fadeScale*(1.0-alphapos)); + } + + if (stage->ss->wind>0.0f && curWindSpeed > 0.001) + { + vec3_t drawpoint; + + VectorMA(curpoint, effectpos*stage->ss->wind, curWindBlowVect, drawpoint); + RB_EffectSurfaceSprite(drawpoint, width, height, (byte)light, (byte)(alpha*255.0f), stage->ss->fxDuration, stage->ss->facing); + } + else + { + RB_EffectSurfaceSprite(curpoint, width, height, (byte)light, (byte)(alpha*255.0f), stage->ss->fxDuration, stage->ss->facing); + } + + totalsurfsprites++; + } + } + } + } +} + +extern void R_WorldToLocal (vec3_t world, vec3_t localVec) ; +extern float preTransEntMatrix[16], invEntMatrix[16]; +extern void R_InvertMatrix(float *sourcemat, float *destmat); + +void RB_DrawSurfaceSprites( shaderStage_t *stage, shaderCommands_t *input) +{ + unsigned long glbits=stage->stateBits; + + R_SurfaceSpriteFrameUpdate(); + + // + // Check fog + // + if ( tess.fogNum && tess.shader->fogPass && r_drawfog->value) + { + SSUsingFog = qtrue; + SQuickSprite.StartGroup(&stage->bundle[0], glbits, tess.fogNum); + } + else + { + SSUsingFog = qfalse; + SQuickSprite.StartGroup(&stage->bundle[0], glbits); + } + + // Special provision in case the transparency is additive. + if ((glbits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_ONE|GLS_DSTBLEND_ONE)) + { // Additive transparency, scale light value + SSAdditiveTransparency=qtrue; + } + else + { + SSAdditiveTransparency=qfalse; + } + + + //Check if this is a new entity transformation (incl. world entity), and update the appropriate vectors if so. + if (backEnd.currentEntity != ssLastEntityDrawn) + { + if (backEnd.currentEntity == &tr.worldEntity) + { // Drawing the world, so our job is dead-easy, in the viewparms + VectorCopy(backEnd.viewParms.ori.origin, ssViewOrigin); + VectorCopy(backEnd.viewParms.ori.axis[1], ssViewRight); + VectorCopy(backEnd.viewParms.ori.axis[2], ssViewUp); + } + else + { // Drawing an entity, so we need to transform the viewparms to the model's coordinate system +// R_WorldPointToEntity (backEnd.viewParms.ori.origin, ssViewOrigin); + R_WorldNormalToEntity (backEnd.viewParms.ori.axis[1], ssViewRight); + R_WorldNormalToEntity (backEnd.viewParms.ori.axis[2], ssViewUp); + VectorCopy(backEnd.ori.viewOrigin, ssViewOrigin); +// R_WorldToLocal(backEnd.viewParms.ori.axis[1], ssViewRight); +// R_WorldToLocal(backEnd.viewParms.ori.axis[2], ssViewUp); + } + ssLastEntityDrawn = backEnd.currentEntity; + } + + switch(stage->ss->surfaceSpriteType) + { + case SURFSPRITE_VERTICAL: + RB_DrawVerticalSurfaceSprites(stage, input); + break; + case SURFSPRITE_ORIENTED: + RB_DrawOrientedSurfaceSprites(stage, input); + break; + case SURFSPRITE_EFFECT: + case SURFSPRITE_WEATHERFX: + RB_DrawEffectSurfaceSprites(stage, input); + break; + } + + SQuickSprite.EndGroup(); + + sssurfaces++; +} + diff --git a/codemp/renderer/tr_terrain.cpp b/codemp/renderer/tr_terrain.cpp new file mode 100644 index 0000000..bb79092 --- /dev/null +++ b/codemp/renderer/tr_terrain.cpp @@ -0,0 +1,1056 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// this include must remain at the top of every CPP file +#include "tr_local.h" + +#if !defined(GENERICPARSER2_H_INC) + #include "../qcommon/GenericParser2.h" +#endif + +// To do: +// Alter variance dependent on global distance from player (colour code this for cg_terrainCollisionDebug) +// Improve texture blending on edge conditions +// Link to neightbouring terrains or architecture (edge conditions) +// Post process generated light data to make sure there are no bands within a patch + +#include "../qcommon/cm_landscape.h" +#include "tr_landscape.h" + +cvar_t *r_drawTerrain; +cvar_t *r_showFrameVariance; +cvar_t *r_terrainTessellate; +cvar_t *r_terrainWaterOffset; + +static int TerrainFog = 0; +static float TerrainDistanceCull; + +// +// Render the tree. +// +void CTRPatch::RenderCorner(ivec5_t corner) +{ + if((corner[3] < 0) || (tess.registration != corner[4])) + { + CTerVert *vert; + + vert = mRenderMap + (corner[1] * owner->GetRealWidth()) + corner[0]; + + VectorCopy(vert->coords, tess.xyz[tess.numVertexes]); + VectorCopy(vert->normal, tess.normal[tess.numVertexes]); + + *(ulong *)tess.vertexColors[tess.numVertexes] = *(ulong *)vert->tint; + *(ulong *)tess.vertexAlphas[tess.numVertexes] = corner[2]; + + tess.texCoords[tess.numVertexes][0][0] = vert->tex[0]; //rwwRMG - reverse coords array from sof2 + tess.texCoords[tess.numVertexes][0][1] = vert->tex[1]; + + tess.indexes[tess.numIndexes++] = tess.numVertexes; + corner[3] = tess.numVertexes++; + corner[4] = tess.registration; + } + else + { + tess.indexes[tess.numIndexes++] = corner[3]; + } +} + +void CTRPatch::RecurseRender(int depth, ivec5_t left, ivec5_t right, ivec5_t apex) +{ + // All non-leaf nodes have both children, so just check for one + if (depth >= 0) + { + ivec5_t center; + byte *centerAlphas; + byte *leftAlphas; + byte *rightAlphas; + + // Work out the centre of the hypoteneuse + center[0] = (left[0] + right[0]) >> 1; + center[1] = (left[1] + right[1]) >> 1; + + // Work out the relevant texture coefficients at that point + leftAlphas = (byte *)&left[2]; + rightAlphas = (byte *)&right[2]; + centerAlphas = (byte *)¢er[2]; + + centerAlphas[0] = (leftAlphas[0] + rightAlphas[0]) >> 1; + centerAlphas[1] = (leftAlphas[1] + rightAlphas[1]) >> 1; + centerAlphas[2] = (leftAlphas[2] + rightAlphas[2]) >> 1; + centerAlphas[3] = (leftAlphas[3] + rightAlphas[3]) >> 1; + + // Make sure the vert index and tesselation registration are not set + center[3] = -1; + center[4] = 0; + + if (apex[0] == left[0] && apex[0] == center[0]) + { + depth = 0; + } + + RecurseRender(depth-1, apex, left, center); + RecurseRender(depth-1, right, apex, center); + } + else + { + if (left[0] == right[0] && left[0] == apex[0]) + { + return; + } + if (left[1] == right[1] && left[1] == apex[1]) + { + return; + } + // A leaf node! Output a triangle to be rendered. + RB_CheckOverflow(4, 4); + +// assert(left[0] != right[0] || left[1] != right[1]); +// assert(left[0] != apex[0] || left[1] != apex[1]); + + RenderCorner(left); + RenderCorner(right); + RenderCorner(apex); + } +} + +// +// Render the mesh. +// +// The order of triangles is critical to the subdivision working + +void CTRPatch::Render(int Part) +{ + ivec5_t TL, TR, BL, BR; + + VectorSet5(TL, 0, 0, TEXTURE_ALPHA_TL, -1, 0); + VectorSet5(TR, owner->GetTerxels(), 0, TEXTURE_ALPHA_TR, -1, 0); + VectorSet5(BL, 0, owner->GetTerxels(), TEXTURE_ALPHA_BL, -1, 0); + VectorSet5(BR, owner->GetTerxels(), owner->GetTerxels(), TEXTURE_ALPHA_BR, -1, 0); + + if ((Part & PI_TOP) && mTLShader) + { +/* float d; + + d = DotProduct (backEnd.refdef.vieworg, mNormal[0]) - mDistance[0]; + + if (d <= 0.0)*/ + { + RecurseRender(r_terrainTessellate->integer, BL, TR, TL); + } + } + + if ((Part & PI_BOTTOM) && mBRShader) + { +/* float d; + + d = DotProduct (backEnd.refdef.vieworg, mNormal[1]) - mDistance[1]; + + if (d >= 0.0)*/ + { + RecurseRender(r_terrainTessellate->integer, TR, BL, BR); + } + } +} + +// +// At this point the patch is visible and at least part of it is below water level +// +int CTRPatch::RenderWaterVert(int x, int y) +{ + CTerVert *vert; + + vert = mRenderMap + x + (y * owner->GetRealWidth()); + + if(vert->tessRegistration == tess.registration) + { + return(vert->tessIndex); + } + tess.xyz[tess.numVertexes][0] = vert->coords[0]; + tess.xyz[tess.numVertexes][1] = vert->coords[1]; + tess.xyz[tess.numVertexes][2] = owner->GetWaterHeight(); + + *(ulong *)tess.vertexColors[tess.numVertexes] = 0xffffffff; + + tess.texCoords[tess.numVertexes][0][0] = vert->tex[0]; //rwwRMG - reverse coords from sof2mp + tess.texCoords[tess.numVertexes][0][1] = vert->tex[1]; + + vert->tessIndex = tess.numVertexes; + vert->tessRegistration = tess.registration; + + tess.numVertexes++; + return(vert->tessIndex); +} + +void CTRPatch::RenderWater(void) +{ + RB_CheckOverflow(4, 6); + + // Get the neighbouring patches + int TL = RenderWaterVert(0, 0); + int TR = RenderWaterVert(owner->GetTerxels(), 0); + int BL = RenderWaterVert(0, owner->GetTerxels()); + int BR = RenderWaterVert(owner->GetTerxels(), owner->GetTerxels()); + + // TL + tess.indexes[tess.numIndexes++] = BL; + tess.indexes[tess.numIndexes++] = TR; + tess.indexes[tess.numIndexes++] = TL; + + // BR + tess.indexes[tess.numIndexes++] = TR; + tess.indexes[tess.numIndexes++] = BL; + tess.indexes[tess.numIndexes++] = BR; +} + +const bool CTRPatch::HasWater(void) const +{ + owner->SetRealWaterHeight( owner->GetBaseWaterHeight() + r_terrainWaterOffset->integer ); + return(common->GetMins()[2] < owner->GetWaterHeight()); +} + +extern bool CM_CullWorldBox (const cplane_t *frustum, const vec3pair_t bounds); //rwwRMG - added (cm_trace.cpp) + +void CTRPatch::SetVisibility(bool visCheck) +{ + if(visCheck) + { + if(DistanceSquared(mCenter, backEnd.refdef.vieworg) > TerrainDistanceCull) + { + misVisible = false; + } + else + { + // Set the visibility of the patch + misVisible = !CM_CullWorldBox(backEnd.viewParms.frustum, GetBounds()); + } + } + else + { + misVisible = true; + } +} + +/* +void CTRPatch::CalcNormal(void) +{ + CTerVert *vert1, *vert2, *vert3; + ivec5_t TL, TR, BL, BR; + vec3_t v1, v2; + + VectorSet5(TL, 0, 0, TEXTURE_ALPHA_TL, -1, 0); + VectorSet5(TR, owner->GetTerxels(), 0, TEXTURE_ALPHA_TR, -1, 0); + VectorSet5(BL, 0, owner->GetTerxels(), TEXTURE_ALPHA_BL, -1, 0); + VectorSet5(BR, owner->GetTerxels(), owner->GetTerxels(), TEXTURE_ALPHA_BR, -1, 0); + + vert1 = mRenderMap + (BL[1] * owner->GetRealWidth()) + BL[0]; + vert2 = mRenderMap + (TR[1] * owner->GetRealWidth()) + TR[0]; + vert3 = mRenderMap + (TL[1] * owner->GetRealWidth()) + TL[0]; + VectorSubtract(vert2->coords, vert1->coords, v1); + VectorSubtract(vert3->coords, vert1->coords, v2); + CrossProduct(v1, v2, mNormal[0]); + VectorNormalize(mNormal[0]); + mDistance[0] = DotProduct (vert1->coords, mNormal[0]); + + vert1 = mRenderMap + (BL[1] * owner->GetRealWidth()) + BL[0]; + vert2 = mRenderMap + (TR[1] * owner->GetRealWidth()) + TR[0]; + vert3 = mRenderMap + (BR[1] * owner->GetRealWidth()) + BR[0]; + VectorSubtract(vert2->coords, vert1->coords, v1); + VectorSubtract(vert3->coords, vert1->coords, v2); + CrossProduct(v1, v2, mNormal[1]); + VectorNormalize(mNormal[1]); + mDistance[1] = DotProduct (vert1->coords, mNormal[1]); +} +*/ +// +// Reset all patches, recompute variance if needed +// +void CTRLandScape::Reset(bool visCheck) +{ + int x, y; + CTRPatch *patch; + + TerrainDistanceCull = tr.distanceCull + mPatchSize; + TerrainDistanceCull *= TerrainDistanceCull; + + // Go through the patches performing resets, compute variances, and linking. + for(y = mPatchMiny; y < mPatchMaxy; y++) + { + for(x = mPatchMinx; x < mPatchMaxx; x++, patch++) + { + patch = GetPatch(x, y); + patch->SetVisibility(visCheck); + } + } +} + + +// +// Render each patch of the landscape & adjust the frame variance. +// + +void CTRLandScape::Render(void) +{ + int x, y; + CTRPatch *patch; + TPatchInfo *current; + int i; + + // Render all the visible patches + current = mSortedPatches; + for(i=0;imPatch->isVisible()) + { + if (tess.shader != current->mShader) + { + RB_EndSurface(); + RB_BeginSurface(current->mShader, TerrainFog); + } + current->mPatch->Render(current->mPart); + } + current++; + } + RB_EndSurface(); + + // Render all the water for visible patches + // Done as a separate iteration to reduce the number of tesses created + if(mWaterShader && (mWaterShader != tr.defaultShader)) + { + RB_BeginSurface( mWaterShader, tr.world->globalFog ); + + for(y = mPatchMiny; y < mPatchMaxy; y++ ) + { + for(x = mPatchMinx; x < mPatchMaxx; x++ ) + { + patch = GetPatch(x, y); + if(patch->isVisible() && patch->HasWater()) + { + patch->RenderWater(); + } + } + } + RB_EndSurface(); + } +} + +void CTRLandScape::CalculateRegion(void) +{ + vec3_t mins, maxs, size, offset; + +#if _DEBUG + mCycleCount++; +#endif + VectorCopy(GetPatchSize(), size); + VectorCopy(GetMins(), offset); + + mins[0] = backEnd.refdef.vieworg[0] - tr.distanceCull - (size[0] * 2.0f) - offset[0]; + mins[1] = backEnd.refdef.vieworg[1] - tr.distanceCull - (size[1] * 2.0f) - offset[1]; + + maxs[0] = backEnd.refdef.vieworg[0] + tr.distanceCull + (size[0] * 2.0f) - offset[0]; + maxs[1] = backEnd.refdef.vieworg[1] + tr.distanceCull + (size[1] * 2.0f) - offset[1]; + + mPatchMinx = Com_Clampi(0, GetBlockWidth(), floorf(mins[0] / size[0])); + mPatchMaxx = Com_Clampi(0, GetBlockWidth(), ceilf(maxs[0] / size[0])); + + mPatchMiny = Com_Clampi(0, GetBlockHeight(), floorf(mins[1] / size[1])); + mPatchMaxy = Com_Clampi(0, GetBlockHeight(), ceilf(maxs[1] / size[1])); +} + +void CTRLandScape::CalculateRealCoords(void) +{ + int x, y; + + // Work out the real world coordinates of each heightmap entry + for(y = 0; y < GetRealHeight(); y++) + { + for(x = 0; x < GetRealWidth(); x++) + { + ivec3_t icoords; + int offset; + + offset = (y * GetRealWidth()) + x; + + VectorSet(icoords, x, y, mRenderMap[offset].height); + VectorScaleVectorAdd(GetMins(), icoords, GetTerxelSize(), mRenderMap[offset].coords); + } + } +} + +void CTRLandScape::CalculateNormals(void) +{ + int x, y, offset = 0; + + // Work out the normals for every face + for(y = 0; y < GetHeight(); y++) + { + for(x = 0; x < GetWidth(); x++) + { + vec3_t vcenter, vleft; + + offset = (y * GetRealWidth()) + x; + + VectorSubtract(mRenderMap[offset].coords, mRenderMap[offset + 1].coords, vcenter); + VectorSubtract(mRenderMap[offset].coords, mRenderMap[offset + GetRealWidth()].coords, vleft); + + CrossProduct(vcenter, vleft, mRenderMap[offset].normal); + VectorNormalize(mRenderMap[offset].normal); + } + // Duplicate right edge condition + VectorCopy(mRenderMap[offset].normal, mRenderMap[offset + 1].normal); + } + // Duplicate bottom line + offset = GetHeight() * GetRealWidth(); + for(x = 0; x < GetRealWidth(); x++) + { + VectorCopy(mRenderMap[offset - GetRealWidth() + x].normal, mRenderMap[offset + x].normal); + } +} + +void CTRLandScape::CalculateLighting(void) +{ + int x, y, offset = 0; + + // Work out the vertex normal (average of every attached face normal) and apply to the direction of the light + for(y = 0; y < GetHeight(); y++) + { + for(x = 0; x < GetWidth(); x++) + { + vec3_t ambient; + vec3_t directed, direction; + vec3_t total, tint; + vec_t dp; + + offset = (y * GetRealWidth()) + x; + + // Work out average normal + VectorCopy(GetRenderMap(x, y)->normal, total); + VectorAdd(total, GetRenderMap(x + 1, y)->normal, total); + VectorAdd(total, GetRenderMap(x + 1, y + 1)->normal, total); + VectorAdd(total, GetRenderMap(x, y + 1)->normal, total); + VectorNormalize(total); + + if (!R_LightForPoint(mRenderMap[offset].coords, ambient, directed, direction)) + { + mRenderMap[offset].tint[0] = + mRenderMap[offset].tint[1] = + mRenderMap[offset].tint[2] = 255 >> tr.overbrightBits; + mRenderMap[offset].tint[3] = 255; + continue; + } + + if(mRenderMap[offset].coords[2] < common->GetBaseWaterHeight()) + { + VectorScale(ambient, 0.75f, ambient); + } + + // Both normalised, so -1.0 < dp < 1.0 + dp = Com_Clampi(0.0f, 1.0f, DotProduct(direction, total)); + dp = powf(dp, 3); + VectorScale(ambient, (1.0 - dp) * 0.5, ambient); + VectorMA(ambient, dp, directed, tint); + + mRenderMap[offset].tint[0] = (byte)Com_Clampi(0.0f, 255.0f, tint[0] ) >> tr.overbrightBits; + mRenderMap[offset].tint[1] = (byte)Com_Clampi(0.0f, 255.0f, tint[1] ) >> tr.overbrightBits; + mRenderMap[offset].tint[2] = (byte)Com_Clampi(0.0f, 255.0f, tint[2] ) >> tr.overbrightBits; + mRenderMap[offset].tint[3] = 0xff; + + /* + mRenderMap[offset].tint[0] += tr.identityLight * 32; + mRenderMap[offset].tint[1] += tr.identityLight * 32; + mRenderMap[offset].tint[2] += tr.identityLight * 32; + */ + } + mRenderMap[offset + 1].tint[0] = mRenderMap[offset].tint[0]; + mRenderMap[offset + 1].tint[1] = mRenderMap[offset].tint[1]; + mRenderMap[offset + 1].tint[2] = mRenderMap[offset].tint[2]; + mRenderMap[offset + 1].tint[3] = 0xff; + } + // Duplicate bottom line + offset = GetHeight() * GetRealWidth(); + for(x = 0; x < GetRealWidth(); x++) + { + mRenderMap[offset + x].tint[0] = mRenderMap[offset - GetRealWidth() + x].tint[0]; + mRenderMap[offset + x].tint[1] = mRenderMap[offset - GetRealWidth() + x].tint[1]; + mRenderMap[offset + x].tint[2] = mRenderMap[offset - GetRealWidth() + x].tint[2]; + mRenderMap[offset + x].tint[3] = 0xff; + } +} + +void CTRLandScape::CalculateTextureCoords(void) +{ + int x, y; + + for(y = 0; y < GetRealHeight(); y++) + { + for(x = 0; x < GetRealWidth(); x++) + { + int offset = (y * GetRealWidth()) + x; + + mRenderMap[offset].tex[0] = x * mTextureScale * GetTerxelSize()[0]; + mRenderMap[offset].tex[1] = y * mTextureScale * GetTerxelSize()[1]; + } + } +} + +void CTRLandScape::SetShaders(const int height, const qhandle_t shader) +{ + int i; + + for(i = height; shader && (i < HEIGHT_RESOLUTION); i++) + { + if(!mHeightDetails[i].GetShader()) + { + mHeightDetails[i].SetShader(shader); + } + } +} + +void CTRLandScape::LoadTerrainDef(const char *td) +{ +#ifndef PRE_RELEASE_DEMO + char terrainDef[MAX_QPATH]; + CGenericParser2 parse; + CGPGroup *basegroup, *classes, *items; + + Com_sprintf(terrainDef, MAX_QPATH, "ext_data/RMG/%s.terrain", td); + Com_Printf("R_Terrain: Loading and parsing terrainDef %s.....\n", td); + + mWaterShader = NULL; + mFlatShader = NULL; + + if(!Com_ParseTextFile(terrainDef, parse)) + { + Com_sprintf(terrainDef, MAX_QPATH, "ext_data/arioche/%s.terrain", td); + if(!Com_ParseTextFile(terrainDef, parse)) + { + Com_Printf("Could not open %s\n", terrainDef); + return; + } + } + // The whole file.... + basegroup = parse.GetBaseParseGroup(); + + // The root { } struct + classes = basegroup->GetSubGroups(); + while(classes) + { + items = classes->GetSubGroups(); + while(items) + { + const char* type = items->GetName ( ); + + if(!stricmp( type, "altitudetexture")) + { + int height; + const char *shaderName; + qhandle_t shader; + + // Height must exist - the rest are optional + height = atol(items->FindPairValue("height", "0")); + + // Shader for this height + shaderName = items->FindPairValue("shader", ""); + if(strlen(shaderName)) + { + shader = RE_RegisterShader(shaderName); + if(shader) + { + SetShaders(height, shader); + } + } + } + else if(!stricmp(type, "water")) + { + mWaterShader = R_GetShaderByHandle(RE_RegisterShader(items->FindPairValue("shader", ""))); + } + else if(!stricmp(type, "flattexture")) + { + mFlatShader = RE_RegisterShader ( items->FindPairValue("shader", "") ); + } + + items = (CGPGroup *)items->GetNext(); + } + classes = (CGPGroup *)classes->GetNext(); + } + + Com_ParseTextFileDestroy(parse); +#endif // PRE_RELEASE_DEMO +} + +qhandle_t CTRLandScape::GetBlendedShader(qhandle_t a, qhandle_t b, qhandle_t c, bool surfaceSprites) +{ + qhandle_t blended; + + // Special case single pass shader + if((a == b) && (a == c)) + { + return(a); + } + + blended = R_CreateBlendedShader(a, b, c, surfaceSprites ); + return(blended); +} + +static int ComparePatchInfo(const TPatchInfo *arg1, const TPatchInfo *arg2) +{ + shader_t *s1, *s2; + + if ((arg1->mPart & PI_TOP)) + { + s1 = arg1->mPatch->GetTLShader(); + } + else + { + s1 = arg1->mPatch->GetBRShader(); + } + + if ((arg2->mPart & PI_TOP)) + { + s2 = arg2->mPatch->GetTLShader(); + } + else + { + s2 = arg2->mPatch->GetBRShader(); + } + + if (s1 < s2) + { + return -1; + } + else if (s1 > s2) + { + return 1; + } + + return 0; +} + +void CTRLandScape::CalculateShaders(void) +{ +#ifndef PRE_RELEASE_DEMO + int x, y; + int width, height; + int offset; +// int offsets[4]; + qhandle_t handles[4]; + CTRPatch *patch; + qhandle_t *shaders; + TPatchInfo *current = mSortedPatches; + + width = GetWidth ( ) / common->GetTerxels ( ); + height = GetHeight ( ) / common->GetTerxels ( ); + + shaders = new qhandle_t [ (width+1) * (height+1) ]; + + // On the first pass determine all of the shaders for the entire + // terrain assuming no flat ground + offset = 0; + for ( y = 0; y < height + 1; y ++ ) + { + if ( y <= height ) + { + offset = common->GetTerxels ( ) * y * GetRealWidth ( ); + } + else + { + offset = common->GetTerxels ( ) * (y-1) * GetRealWidth ( ); + offset += GetRealWidth ( ); + } + + for ( x = 0; x < width + 1; x ++, offset += common->GetTerxels ( ) ) + { + // Save the shader + shaders[y * width + x] = GetHeightDetail(mRenderMap[offset].height)->GetShader ( ); + } + } + + // On the second pass determine flat ground and replace the shader + // at that point with the flat ground shader + if ( mFlatShader ) + { + for ( y = 1; y < height; y ++ ) + { + for ( x = 1; x < width; x ++ ) + { + int offset; + int xx; + int yy; + byte* flattenMap = common->GetFlattenMap ( ); + bool flat = false; + + offset = (x) * common->GetTerxels ( ); + offset += (y) * common->GetTerxels ( ) * GetRealWidth(); + + offset -= GetRealWidth(); + offset -= 1; + + for ( yy = 0; yy < 3 && !flat; yy++ ) + { + for ( xx = 0; xx < 3 && !flat; xx++ ) + { + if ( flattenMap [ offset + xx] & 0x80) + { + flat = true; + break; + } + } + + offset += GetRealWidth(); + } + +/* + // Calculate the height map offset + offset = x * common->GetTerxels ( ); + offset += (y * common->GetTerxels ( ) * GetRealWidth()); + + // Calculate the offsets around this particular shader location + offsets[INDEX_TL] = offset - 1 - GetRealWidth(); + offsets[INDEX_TR] = offsets[INDEX_TL] + 1; + offsets[INDEX_BL] = offsets[INDEX_TL] + GetRealWidth(); + offsets[INDEX_BR] = offsets[INDEX_BL] + 1; + + // If not equal to the top left one then skip + if ( mRenderMap[offset].height != mRenderMap[offsets[INDEX_TL]].height ) + { + continue; + } + + // If not equal to the top right one then skip + if ( mRenderMap[offset].height != mRenderMap[offsets[INDEX_TR]].height ) + { + continue; + } + + // If not equal to the bottom left one then skip + if ( mRenderMap[offset].height != mRenderMap[offsets[INDEX_BL]].height ) + { + continue; + } + + // If not equal to the bottom right one then skip + if ( mRenderMap[offset].height != mRenderMap[offsets[INDEX_BR]].height ) + { + continue; + } + */ + + // This shader is now a flat shader + if ( flat ) + { + shaders[y * width + x] = mFlatShader; + } + +#ifdef _DEBUG + OutputDebugString ( va("Flat Area: %f %f\n", + GetMins()[0] + (GetMaxs()[0]-GetMins()[0])/width * x, + GetMins()[1] + (GetMaxs()[1]-GetMins()[1])/height * y) ); +#endif + } + } + } + + // Now that the shaders have been determined, set them for each patch + patch = mTRPatches; + mSortedCount = 0; + for ( y = 0; y < height; y ++ ) + { + for ( x = 0; x < width; x ++, patch++ ) + { + bool surfaceSprites = true; + + handles[INDEX_TL] = shaders[ x + y * width ]; + handles[INDEX_TR] = shaders[ x + 1 + y * width ]; + handles[INDEX_BL] = shaders[ x + (y + 1) * width ]; + handles[INDEX_BR] = shaders[ x + 1 + (y + 1) * width ]; + + if ( handles[INDEX_TL] == mFlatShader || + handles[INDEX_TR] == mFlatShader || + handles[INDEX_BL] == mFlatShader || + handles[INDEX_BR] == mFlatShader ) + { + surfaceSprites = false; + } + + patch->SetTLShader(GetBlendedShader(handles[INDEX_TR], handles[INDEX_BL], handles[INDEX_TL], surfaceSprites)); + current->mPatch = patch; + current->mShader = patch->GetTLShader(); + current->mPart = PI_TOP; + + patch->SetBRShader(GetBlendedShader(handles[INDEX_TR], handles[INDEX_BL], handles[INDEX_BR], surfaceSprites)); + if (patch->GetBRShader() == current->mShader) + { + current->mPart |= PI_BOTTOM; + } + else + { + mSortedCount++; + current++; + + current->mPatch = patch; + current->mShader = patch->GetBRShader(); + current->mPart = PI_BOTTOM; + } + mSortedCount++; + current++; + } + } + + // Cleanup our temporary array + delete[] shaders; + + qsort(mSortedPatches, mSortedCount, sizeof(*mSortedPatches), (int (__cdecl *)(const void *,const void *))ComparePatchInfo); + +#endif // PRE_RELEASE_DEMO +} + +void CTRPatch::SetRenderMap(const int x, const int y) +{ + mRenderMap = localowner->GetRenderMap(x, y); +} + +void InitRendererPatches( CCMPatch *patch, void *userdata ) +{ + int tx, ty, bx, by; + CTRPatch *localpatch; + CCMLandScape *owner; + CTRLandScape *localowner; + + // Set owning landscape + localowner = (CTRLandScape *)userdata; + owner = (CCMLandScape *)localowner->GetCommon(); + + // Get TRPatch pointer + tx = patch->GetHeightMapX(); + ty = patch->GetHeightMapY(); + bx = tx / owner->GetTerxels(); + by = ty / owner->GetTerxels(); + + localpatch = localowner->GetPatch(bx, by); + localpatch->Clear(); + + localpatch->SetCommon(patch); + localpatch->SetOwner(owner); + localpatch->SetLocalOwner(localowner); + localpatch->SetRenderMap(tx, ty); + localpatch->SetCenter(); +// localpatch->CalcNormal(); +} + +void CTRLandScape::CopyHeightMap(void) +{ + const CCMLandScape *common = GetCommon(); + const byte *heightMap = common->GetHeightMap(); + CTerVert *renderMap = mRenderMap; + int i; + + for(i = 0; i < common->GetRealArea(); i++) + { + renderMap->height = *heightMap; + renderMap++; + heightMap++; + } +} + +CTRLandScape::~CTRLandScape(void) +{ + if(mTRPatches) + { + Z_Free(mTRPatches); + mTRPatches = NULL; + } + if (mSortedPatches) + { + Z_Free(mSortedPatches); + mSortedPatches = 0; + } + if(mRenderMap) + { + Z_Free(mRenderMap); + mRenderMap = NULL; + } +} + +extern CCMLandScape *CM_RegisterTerrain(const char *config, bool server); //cm_load.cpp + +CTRLandScape::CTRLandScape(const char *configstring) +{ +#ifndef PRE_RELEASE_DEMO + int shaderNum; + const CCMLandScape *common; + + memset(this, 0, sizeof(*this)); + + // Sets up the common aspects of the terrain + common = CM_RegisterTerrain(configstring, false); + SetCommon(common); + + tr.landScape.landscape = this; + + mTextureScale = (float)atof(Info_ValueForKey(configstring, "texturescale")) / common->GetTerxels(); + LoadTerrainDef(Info_ValueForKey(configstring, "terrainDef")); + + // To normalise the variance value to a reasonable number + mScalarSize = VectorLengthSquared(common->GetSize()); + + // Calculate and set variance depth + mMaxNode = (Q_log2(common->GetTerxels()) << 1) - 1; + + // Allocate space for the renderer specific data + mRenderMap = (CTerVert *)Z_Malloc(sizeof(CTerVert) * common->GetRealArea(), TAG_R_TERRAIN); + + // Copy byte heightmap to rendermap to speed up calcs + CopyHeightMap(); + + // Calculate the real world location for each heightmap entry + CalculateRealCoords(); + + // Calculate the normal of each terxel + CalculateNormals(); + + // Calculate modulation values for the heightmap + CalculateLighting(); + + // Calculate texture coords (not projected - real) + CalculateTextureCoords(); + + Com_Printf ("R_Terrain: Creating renderer patches.....\n"); + // Initialise all terrain patches + mTRPatches = (CTRPatch *)Z_Malloc(sizeof(CTRPatch) * common->GetBlockCount(), TAG_R_TERRAIN); + + mSortedCount = 2 * common->GetBlockCount(); + mSortedPatches = (TPatchInfo *)Z_Malloc(sizeof(TPatchInfo) * mSortedCount, TAG_R_TERRAIN); + + CM_TerrainPatchIterate(common, InitRendererPatches, this); + + // Calculate shaders dependent on the .terrain file + CalculateShaders(); + + // Get the contents shader + shaderNum = atol(Info_ValueForKey(configstring, "shader"));; + mShader = R_GetShaderByHandle(R_GetShaderByNum(shaderNum, *tr.world)); + + mPatchSize = VectorLength(common->GetPatchSize()); + +#if _DEBUG + mCycleCount = 0; +#endif +#endif // PRE_RELEASE_DEMO +} + +// --------------------------------------------------------------------- + +void RB_SurfaceTerrain( surfaceInfo_t *surf ) +{ + /* + if(backEnd.refdef.rdflags & RDF_PROJECTION2D) + { + return; + } + */ + srfTerrain_t *ls = (srfTerrain_t *)surf; + CTRLandScape *landscape = ls->landscape; + + TerrainFog = tr.world->globalFog; + + landscape->CalculateRegion(); + landscape->Reset(); +// landscape->Tessellate(); + landscape->Render(); +} + +void R_CalcTerrainVisBounds(CTRLandScape *landscape) +{ + const CCMLandScape *common = landscape->GetCommon(); + + // Set up the visbounds using terrain data + if ( common->GetMins()[0] < tr.viewParms.visBounds[0][0] ) + { + tr.viewParms.visBounds[0][0] = common->GetMins()[0]; + } + if ( common->GetMins()[1] < tr.viewParms.visBounds[0][1] ) + { + tr.viewParms.visBounds[0][1] = common->GetMins()[1]; + } + if ( common->GetMins()[2] < tr.viewParms.visBounds[0][2] ) + { + tr.viewParms.visBounds[0][2] = common->GetMins()[2]; + } + + if ( common->GetMaxs()[0] > tr.viewParms.visBounds[1][0] ) + { + tr.viewParms.visBounds[1][0] = common->GetMaxs()[0]; + } + if ( common->GetMaxs()[1] > tr.viewParms.visBounds[1][1] ) + { + tr.viewParms.visBounds[1][1] = common->GetMaxs()[1]; + } + if ( common->GetMaxs()[2] > tr.viewParms.visBounds[1][2] ) + { + tr.viewParms.visBounds[1][2] = common->GetMaxs()[2]; + } +} + +void R_AddTerrainSurfaces(void) +{ + CTRLandScape *landscape; + + if (!r_drawTerrain->integer || (tr.refdef.rdflags & RDF_NOWORLDMODEL)) + { + return; + } + + landscape = tr.landScape.landscape; + if(landscape) + { + R_AddDrawSurf( (surfaceType_t *)(&tr.landScape), landscape->GetShader(), 0, qfalse ); + R_CalcTerrainVisBounds(landscape); + } +} + +void RE_InitRendererTerrain( const char *info ) +{ + CTRLandScape *ls; + + if ( !info || !info[0] ) + { + Com_Printf( "RE_RegisterTerrain: NULL name\n" ); + return; + } + + Com_Printf("R_Terrain: Creating RENDERER data.....\n"); + + // Create and register a new landscape structure + ls = new CTRLandScape(info); +} + +void R_TerrainInit(void) +{ + tr.landScape.surfaceType = SF_TERRAIN; + tr.landScape.landscape = NULL; + + r_terrainTessellate = Cvar_Get("r_terrainTessellate", "3", CVAR_CHEAT); + r_drawTerrain = Cvar_Get("r_drawTerrain", "1", CVAR_CHEAT); + r_showFrameVariance = Cvar_Get("r_showFrameVariance", "0", 0); + r_terrainWaterOffset = Cvar_Get("r_terrainWaterOffset", "0", 0); + + tr.distanceCull = 6000; + tr.distanceCullSquared = tr.distanceCull * tr.distanceCull; +} + +extern void CM_ShutdownTerrain( thandle_t terrainId ); //cm_load.cpp + +void R_TerrainShutdown(void) +{ + CTRLandScape *ls; + +// Com_Printf("R_Terrain: Shutting down RENDERER terrain.....\n"); + ls = tr.landScape.landscape; + if(ls) + { + CM_ShutdownTerrain(0); + delete ls; + tr.landScape.landscape = NULL; + } +} + +// end diff --git a/codemp/renderer/tr_world.cpp b/codemp/renderer/tr_world.cpp new file mode 100644 index 0000000..f8d150c --- /dev/null +++ b/codemp/renderer/tr_world.cpp @@ -0,0 +1,1939 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +#ifdef _XBOX +#include "../qcommon/sparc.h" +static bool lookingForWorstLeaf = false; +#endif + +#ifndef _XBOX +inline void Q_CastShort2Float(float *f, const short *s) +{ + *f = ((float)*s); +} +#endif + + +#ifdef _XBOX +static bool GetCoordsForLeaf(int leafNum, vec3_t coords) +{ + srfSurfaceFace_t *face; + msurface_t *surf; + int i; + + for(i=0; ileafs[leafNum].nummarksurfaces; i++) { + surf = *(tr.world->marksurfaces + + tr.world->leafs[leafNum].firstMarkSurfNum + i); + + if(!surf->data || *surf->data != SF_FACE) { + continue; + } + + face = (srfSurfaceFace_t*)surf->data; + Q_CastShort2Float(&coords[0], (short*)(face->srfPoints + 0)); + Q_CastShort2Float(&coords[1], (short*)(face->srfPoints + 1)); + Q_CastShort2Float(&coords[2], (short*)(face->srfPoints + 2)); + return true; + } + + return false; +} +#endif + + +/* +================= +R_CullTriSurf + +Returns true if the grid is completely culled away. +Also sets the clipped hint bit in tess +================= +*/ +static qboolean R_CullTriSurf( srfTriangles_t *cv ) { + int boxCull; + + boxCull = R_CullLocalBox( cv->bounds ); + + if ( boxCull == CULL_OUT ) { + return qtrue; + } + return qfalse; +} + +/* +================= +R_CullGrid + +Returns true if the grid is completely culled away. +Also sets the clipped hint bit in tess +================= +*/ +static qboolean R_CullGrid( srfGridMesh_t *cv ) { + int boxCull; + int sphereCull; + + if ( r_nocurves->integer ) { + return qtrue; + } + + if ( tr.currentEntityNum != TR_WORLDENT ) { + sphereCull = R_CullLocalPointAndRadius( cv->localOrigin, cv->meshRadius ); + } else { + sphereCull = R_CullPointAndRadius( cv->localOrigin, cv->meshRadius ); + } + boxCull = CULL_OUT; + + // check for trivial reject + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_patch_out++; + return qtrue; + } + // check bounding box if necessary + else if ( sphereCull == CULL_CLIP ) + { + tr.pc.c_sphere_cull_patch_clip++; + + boxCull = R_CullLocalBox( cv->meshBounds ); + + if ( boxCull == CULL_OUT ) + { + tr.pc.c_box_cull_patch_out++; + return qtrue; + } + else if ( boxCull == CULL_IN ) + { + tr.pc.c_box_cull_patch_in++; + } + else + { + tr.pc.c_box_cull_patch_clip++; + } + } + else + { + tr.pc.c_sphere_cull_patch_in++; + } + + return qfalse; +} + + +/* +================ +R_CullSurface + +Tries to back face cull surfaces before they are lighted or +added to the sorting list. + +This will also allow mirrors on both sides of a model without recursion. +================ +*/ +static qboolean R_CullSurface( surfaceType_t *surface, shader_t *shader ) { + srfSurfaceFace_t *sface; + float d; + + if ( r_nocull->integer ) { + return qfalse; + } + + if ( *surface == SF_GRID ) { + return R_CullGrid( (srfGridMesh_t *)surface ); + } + + if ( *surface == SF_TRIANGLES ) { + return R_CullTriSurf( (srfTriangles_t *)surface ); + } + + if ( *surface != SF_FACE ) { + return qfalse; + } + + if ( shader->cullType == CT_TWO_SIDED ) { + return qfalse; + } + + // face culling + if ( !r_facePlaneCull->integer ) { + return qfalse; + } + + sface = ( srfSurfaceFace_t * ) surface; + + if (r_cullRoofFaces->integer) + { //Very slow, but this is only intended for taking shots for automap images. + if (sface->plane.normal[2] > 0.0f && + sface->numPoints > 0) + { //it's facing up I guess + static int i; + static trace_t tr; + static vec3_t basePoint; + static vec3_t endPoint; + static vec3_t nNormal; + static vec3_t v; + + //The fact that this point is in the middle of the array has no relation to the + //orientation in the surface outline. +#ifdef _XBOX + Q_CastShort2Float(&basePoint[0], (short*)(sface->srfPoints + (sface->numPoints / 2) + 0)); + Q_CastShort2Float(&basePoint[1], (short*)(sface->srfPoints + (sface->numPoints / 2) + 1)); + Q_CastShort2Float(&basePoint[2], (short*)(sface->srfPoints + (sface->numPoints / 2) + 2)); +#else + basePoint[0] = sface->points[sface->numPoints/2][0]; + basePoint[1] = sface->points[sface->numPoints/2][1]; + basePoint[2] = sface->points[sface->numPoints/2][2]; +#endif + basePoint[2] += 2.0f; + + //the endpoint will be 8192 units from the chosen point + //in the direction of the surface normal + + //just go straight up I guess, for now (slight hack) + VectorSet(nNormal, 0.0f, 0.0f, 1.0f); + VectorMA(basePoint, 8192.0f, nNormal, endPoint); + + CM_BoxTrace(&tr, basePoint, endPoint, NULL, NULL, 0, (CONTENTS_SOLID|CONTENTS_TERRAIN), qfalse); + + if (!tr.startsolid && + !tr.allsolid && + (tr.fraction == 1.0f || (tr.surfaceFlags & SURF_NOIMPACT))) + { //either hit nothing or sky, so this surface is near the top of the level I guess. Or the floor of a really tall room, but if that's the case we're just screwed. + VectorSubtract(basePoint, tr.endpos, v); + if (tr.fraction == 1.0f || VectorLength(v) < r_roofCullCeilDist->value) + { //ignore it if it's not close to the top, unless it just hit nothing + //Let's try to dig back into the brush based on the negative direction of the plane, + //and if we pop out on the other side we'll see if it's ground or not. + i = 4; + VectorCopy(sface->plane.normal, nNormal); + VectorInverse(nNormal); + + while (i < 4096) + { + VectorMA(basePoint, i, nNormal, endPoint); + CM_BoxTrace(&tr, endPoint, endPoint, NULL, NULL, 0, (CONTENTS_SOLID|CONTENTS_TERRAIN), qfalse); + if (!tr.startsolid && + !tr.allsolid && + tr.fraction == 1.0f) + { //in the clear + break; + } + i++; + } + if (i < 4096) + { //Make sure we got into clearance + VectorCopy(endPoint, basePoint); + basePoint[2] -= 2.0f; + + //just go straight down I guess, for now (slight hack) + VectorSet(nNormal, 0.0f, 0.0f, -1.0f); + VectorMA(basePoint, 4096.0f, nNormal, endPoint); + + //trace a second time from the clear point in the inverse normal direction of the surface. + //If we hit something within a set amount of units, we will assume it's a bridge type object + //and leave it to be drawn. Otherwise we will assume it is a roof or other obstruction and + //cull it out. + CM_BoxTrace(&tr, basePoint, endPoint, NULL, NULL, 0, (CONTENTS_SOLID|CONTENTS_TERRAIN), qfalse); + + if (!tr.startsolid && + !tr.allsolid && + (tr.fraction != 1.0f && !(tr.surfaceFlags & SURF_NOIMPACT))) + { //if we hit nothing or a noimpact going down then this is probably "ground". + VectorSubtract(basePoint, tr.endpos, endPoint); + if (VectorLength(endPoint) > r_roofCullCeilDist->value) + { //128 (by default) is our maximum tolerance, above that will be removed + return qtrue; + } + } + } + } + } + } + } + + d = DotProduct (tr.ori.viewOrigin, sface->plane.normal); + + // don't cull exactly on the plane, because there are levels of rounding + // through the BSP, ICD, and hardware that may cause pixel gaps if an + // epsilon isn't allowed here + if ( shader->cullType == CT_FRONT_SIDED ) { + if ( d < sface->plane.dist - 8 ) { + return qtrue; + } + } else { + if ( d > sface->plane.dist + 8 ) { + return qtrue; + } + } + + return qfalse; +} + + +static int R_DlightFace( srfSurfaceFace_t *face, int dlightBits ) { + float d; + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + d = DotProduct( dl->origin, face->plane.normal ) - face->plane.dist; + if ( !VectorCompare(face->plane.normal, vec3_origin) && (d < -dl->radius || d > dl->radius) ) { + // dlight doesn't reach the plane + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + face->dlightBits = dlightBits; + return dlightBits; +} + +static int R_DlightGrid( srfGridMesh_t *grid, int dlightBits ) { + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits = dlightBits; + return dlightBits; +} + + +static int R_DlightTrisurf( srfTriangles_t *surf, int dlightBits ) { + // FIXME: more dlight culling to trisurfs... + surf->dlightBits = dlightBits; + return dlightBits; +#if 0 + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits = dlightBits; + return dlightBits; +#endif +} + +/* +==================== +R_DlightSurface + +The given surface is going to be drawn, and it touches a leaf +that is touched by one or more dlights, so try to throw out +more dlights if possible. +==================== +*/ +static int R_DlightSurface( msurface_t *surf, int dlightBits ) { + if ( *surf->data == SF_FACE ) { + dlightBits = R_DlightFace( (srfSurfaceFace_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_GRID ) { + dlightBits = R_DlightGrid( (srfGridMesh_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_TRIANGLES ) { + dlightBits = R_DlightTrisurf( (srfTriangles_t *)surf->data, dlightBits ); + } else { + dlightBits = 0; + } + + if ( dlightBits ) { + tr.pc.c_dlightSurfaces++; + } + + return dlightBits; +} + + + +#ifdef _ALT_AUTOMAP_METHOD +static bool tr_drawingAutoMap = false; +#endif +static float g_playerHeight = 0.0f; + +/* +====================== +R_AddWorldSurface +====================== +*/ +static void R_AddWorldSurface( msurface_t *surf, int dlightBits, qboolean noViewCount = qfalse ) +{ + if (!noViewCount) + { + if ( surf->viewCount == tr.viewCount ) + { + // already in this view, but lets make sure all the dlight bits are set + if ( *surf->data == SF_FACE ) + { + ((srfSurfaceFace_t *)surf->data)->dlightBits |= dlightBits; + } + else if ( *surf->data == SF_GRID ) + { + ((srfGridMesh_t *)surf->data)->dlightBits |= dlightBits; + } + else if ( *surf->data == SF_TRIANGLES ) + { + ((srfTriangles_t *)surf->data)->dlightBits |= dlightBits; + } + return; + } + surf->viewCount = tr.viewCount; + // FIXME: bmodel fog? + } + + /* + if (r_shadows->integer == 2) + { + dlightBits = R_DlightSurface( surf, dlightBits ); + //dlightBits = ( dlightBits != 0 ); + R_AddDrawSurf( surf->data, tr.shadowShader, surf->fogIndex, dlightBits ); + } + */ + //world shadows? + + // try to cull before dlighting or adding +#ifdef _ALT_AUTOMAP_METHOD + if (!tr_drawingAutoMap && R_CullSurface( surf->data, surf->shader ) ) +#else + if (R_CullSurface(surf->data, surf->shader)) +#endif + { + return; + } + + // check for dlighting + if ( dlightBits ) { + dlightBits = R_DlightSurface( surf, dlightBits ); + dlightBits = ( dlightBits != 0 ); + } + +#ifdef _ALT_AUTOMAP_METHOD + if (tr_drawingAutoMap) + { + // if (g_playerHeight != g_lastHeight || + // !g_lastHeightValid) + if (*surf->data == SF_FACE) + { //only do this if we need to + bool completelyTransparent = true; + int i = 0; + srfSurfaceFace_t *face = (srfSurfaceFace_t *)surf->data; + byte *indices = (byte *)(face + face->ofsIndices); + float *point; + vec3_t color; + float alpha; + float e; + bool polyStarted = false; + + while (i < face->numIndices) + { + point = &face->points[indices[i]][0]; + + //base the color on the elevation... for now, just check the first point height + if (point[2] < g_playerHeight) + { + e = point[2]-g_playerHeight; + } + else + { + e = g_playerHeight-point[2]; + } + if (e < 0.0f) + { + e = -e; + } + + //set alpha and color based on relative height of point + alpha = e/256.0f; + e /= 512.0f; + + //cap color + if (e > 1.0f) + { + e = 1.0f; + } + else if (e < 0.0f) + { + e = 0.0f; + } + VectorSet(color, e, 1.0f-e, 0.0f); + + //cap alpha + if (alpha > 1.0f) + { + alpha = 1.0f; + } + else if (alpha < 0.0f) + { + alpha = 0.0f; + } + + if (alpha != 1.0f) + { //this point is not entirely alpha'd out, so still draw the surface + completelyTransparent = false; + } + + if (!completelyTransparent) + { + if (!polyStarted) + { + qglBegin(GL_POLYGON); + polyStarted = true; + } + + qglColor4f(color[0], color[1], color[2], 1.0f-alpha); + qglVertex3f(point[i], point[i], point[2]); + } + + i++; + } + + if (polyStarted) + { + qglEnd(); + } + } + } + else +#endif + { + R_AddDrawSurf( surf->data, surf->shader, surf->fogIndex, dlightBits ); + } +} + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +/* +================= +R_AddBrushModelSurfaces +================= +*/ +void R_AddBrushModelSurfaces ( trRefEntity_t *ent ) { + bmodel_t *bmodel; + int clip; + model_t *pModel; + int i; + + pModel = R_GetModelByHandle( ent->e.hModel ); + + bmodel = pModel->bmodel; + + clip = R_CullLocalBox( bmodel->bounds ); + if ( clip == CULL_OUT ) { + return; + } + + if(pModel->bspInstance) + { //rwwRMG - added + R_SetupEntityLighting(&tr.refdef, ent); + } + + //rww - Take this into account later? +// if (!com_RMG || !com_RMG->integer) +// { // don't dlight bmodels on rmg, as multiple copies of the same instance will light up + R_DlightBmodel( bmodel, false ); +// } +// else +// { +// R_DlightBmodel( bmodel, true ); +// } + + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + R_AddWorldSurface( bmodel->firstSurface + i, tr.currentEntity->dlightBits, qtrue ); + } +} + +float GetQuadArea( vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4 ) +{ + vec3_t vec1, vec2, dis1, dis2; + + // Get area of tri1 + VectorSubtract( v1, v2, vec1 ); + VectorSubtract( v1, v4, vec2 ); + CrossProduct( vec1, vec2, dis1 ); + VectorScale( dis1, 0.25f, dis1 ); + + // Get area of tri2 + VectorSubtract( v3, v2, vec1 ); + VectorSubtract( v3, v4, vec2 ); + CrossProduct( vec1, vec2, dis2 ); + VectorScale( dis2, 0.25f, dis2 ); + + // Return addition of disSqr of each tri area + return ( dis1[0] * dis1[0] + dis1[1] * dis1[1] + dis1[2] * dis1[2] + + dis2[0] * dis2[0] + dis2[1] * dis2[1] + dis2[2] * dis2[2] ); +} + +#ifdef _XBOX +float GetQuadArea( unsigned short v1[3], unsigned short v2[3], unsigned short v3[3], unsigned short v4[3]) +{ + vec3_t fv1; + vec3_t fv2; + vec3_t fv3; + vec3_t fv4; + + for(int i=0; i<3; i++) { + Q_CastShort2Float(&fv1[i], (short*)&v1[i]); + Q_CastShort2Float(&fv2[i], (short*)&v2[i]); + Q_CastShort2Float(&fv3[i], (short*)&v3[i]); + Q_CastShort2Float(&fv4[i], (short*)&v4[i]); + } + + return GetQuadArea(fv1, fv2, fv3, fv4); +} +#endif + +void RE_GetBModelVerts( int bmodelIndex, vec3_t *verts, vec3_t normal ) +{ + msurface_t *surfs; + srfSurfaceFace_t *face; + bmodel_t *bmodel; + model_t *pModel; + int i; + // Not sure if we really need to track the best two candidates + int maxDist[2]={0,0}; + int maxIndx[2]={0,0}; + int dist = 0; + float dot1, dot2; + + pModel = R_GetModelByHandle( bmodelIndex ); + bmodel = pModel->bmodel; + + // Loop through all surfaces on the brush and find the best two candidates + for ( i = 0 ; i < bmodel->numSurfaces; i++ ) + { + surfs = bmodel->firstSurface + i; + face = ( srfSurfaceFace_t *)surfs->data; + + // It seems that the safest way to handle this is by finding the area of the faces +#ifdef _XBOX + int nextSurfPoint = NEXT_SURFPOINT(face->flags); + dist = GetQuadArea( face->srfPoints, face->srfPoints + nextSurfPoint, + face->srfPoints + nextSurfPoint * 2, face->srfPoints + + nextSurfPoint * 3 ); +#else + dist = GetQuadArea( face->points[0], face->points[1], face->points[2], face->points[3] ); +#endif + + // Check against the highest max + if ( dist > maxDist[0] ) + { + // Shuffle our current maxes down + maxDist[1] = maxDist[0]; + maxIndx[1] = maxIndx[0]; + + maxDist[0] = dist; + maxIndx[0] = i; + } + // Check against the second highest max + else if ( dist >= maxDist[1] ) + { + // just stomp the old + maxDist[1] = dist; + maxIndx[1] = i; + } + } + + // Hopefully we've found two best case candidates. Now we should see which of these faces the viewer + surfs = bmodel->firstSurface + maxIndx[0]; + face = ( srfSurfaceFace_t *)surfs->data; + dot1 = DotProduct( face->plane.normal, tr.refdef.viewaxis[0] ); + + surfs = bmodel->firstSurface + maxIndx[1]; + face = ( srfSurfaceFace_t *)surfs->data; + dot2 = DotProduct( face->plane.normal, tr.refdef.viewaxis[0] ); + + if ( dot2 < dot1 && dot2 < 0.0f ) + { + i = maxIndx[1]; // use the second face + } + else if ( dot1 < dot2 && dot1 < 0.0f ) + { + i = maxIndx[0]; // use the first face + } + else + { // Possibly only have one face, so may as well use the first face, which also should be the best one + //i = rand() & 1; // ugh, we don't know which to use. I'd hope this would never happen + i = maxIndx[0]; // use the first face + } + + surfs = bmodel->firstSurface + i; + face = ( srfSurfaceFace_t *)surfs->data; + +#ifdef _XBOX + int nextSurfPoint = NEXT_SURFPOINT(face->flags); + for ( int t = 0; t < 4; t++ ) + { + Q_CastShort2Float(&verts[t][0], (short*)(face->srfPoints + nextSurfPoint * t + 0)); + Q_CastShort2Float(&verts[t][1], (short*)(face->srfPoints + nextSurfPoint * t + 1)); + Q_CastShort2Float(&verts[t][2], (short*)(face->srfPoints + nextSurfPoint * t + 2)); + } +#else + for ( int t = 0; t < 4; t++ ) + { + VectorCopy( face->points[t], verts[t] ); + } +#endif +} + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ + +/* +============================================================= +WIREFRAME AUTOMAP GENERATION SYSTEM - BEGIN +============================================================= +*/ +#ifndef _ALT_AUTOMAP_METHOD +typedef struct wireframeSurfPoint_s +{ + vec3_t xyz; + float alpha; + vec3_t color; +} wireframeSurfPoint_t; + +typedef struct wireframeMapSurf_s +{ + bool completelyTransparent; + + int numPoints; + wireframeSurfPoint_t *points; + + wireframeMapSurf_s *next; +} wireframeMapSurf_t; + +typedef struct wireframeMap_s +{ + wireframeMapSurf_t *surfs; +} wireframeMap_t; + +static wireframeMap_t g_autoMapFrame; +static wireframeMapSurf_t **g_autoMapNextFree = NULL; +static bool g_autoMapValid = false; //set to true of g_autoMapFrame is valid. + +//get the next available wireframe automap surface. -rww +static inline wireframeMapSurf_t *R_GetNewWireframeMapSurf(void) +{ + wireframeMapSurf_t **next = &g_autoMapFrame.surfs; + + if (g_autoMapNextFree) + { //save us the time of going through the entire linked list from root + next = g_autoMapNextFree; + } + + while (*next) + { //iterate through until we find the next unused one + next = &(*next)->next; + } + + //allocate memory for it and pass it back + (*next) = (wireframeMapSurf_t *)Z_Malloc(sizeof(wireframeMapSurf_t), TAG_ALL, qtrue); + g_autoMapNextFree = &(*next)->next; + return (*next); +} + +//evaluate a surface, see if it is valid for being part of the +//wireframe map render. -rww +#ifdef _XBOX +static inline void R_EvaluateWireframeSurf(msurface_t *surf) +{ + if (*surf->data == SF_FACE) + { + srfSurfaceFace_t *face = (srfSurfaceFace_t *)surf->data; + int numPoints = face->numPoints; + unsigned char *indices = (unsigned char *)(((char *)face) + face->ofsIndices ); + + if (numPoints > 0) + { //we can add it + int i = 0; + wireframeMapSurf_t *nextSurf = R_GetNewWireframeMapSurf(); + + //now go through the indices and add a point for each + nextSurf->points = (wireframeSurfPoint_t *)Z_Malloc(sizeof(wireframeSurfPoint_t)*face->numIndices, TAG_ALL, qtrue); + nextSurf->numPoints = face->numIndices; + while (i < face->numIndices) + { + vec3_t point; + Q_CastShort2Float(&point[0], (short*)face->srfPoints + indices[i] + 0); + Q_CastShort2Float(&point[1], (short*)face->srfPoints + indices[i] + 1); + Q_CastShort2Float(&point[2], (short*)face->srfPoints + indices[i] + 2); + VectorCopy(point, nextSurf->points[i].xyz); + + i++; + } + } + } + else if (*surf->data == SF_TRIANGLES) + { + //srfTriangles_t *surfTri = (srfTriangles_t *)surf->data; + return; //not handled + } + else if (*surf->data == SF_GRID) + { + //srfGridMesh_t *gridMesh = (srfGridMesh_t *)surf->data; + return; //not handled + } + else + { //...unknown type? + return; + } +} + +#else // _XBOX + +static inline void R_EvaluateWireframeSurf(msurface_t *surf) +{ + if (*surf->data == SF_FACE) + { + srfSurfaceFace_t *face = (srfSurfaceFace_t *)surf->data; + float *points = &face->points[0][0]; + int numPoints = face->numIndices; + int *indices = (int *)((byte *)face + face->ofsIndices); + //byte *indices = (byte *)(face + face->ofsIndices); + + if (points && numPoints > 0) + { //we can add it + int i = 0; + wireframeMapSurf_t *nextSurf = R_GetNewWireframeMapSurf(); + +#if 0 //doing in realtime now + float e; + + //base the color on the elevation... for now, just check the first point height + if (points[2] < 0.0f) + { + e = -points[2]; + } + else + { + e = points[2]; + } + e /= 2048.0f; + if (e > 1.0f) + { + e = 1.0f; + } + else if (e < 0.0f) + { + e = 0.0f; + } + VectorSet(color, e, 1.0f-e, 0.0f); +#endif + + //now go through the indices and add a point for each + nextSurf->points = (wireframeSurfPoint_t *)Z_Malloc(sizeof(wireframeSurfPoint_t)*face->numIndices, TAG_ALL, qtrue); + nextSurf->numPoints = face->numIndices; + while (i < face->numIndices) + { + points = &face->points[indices[i]][0]; + VectorCopy(points, nextSurf->points[i].xyz); + + i++; + } + } + } + else if (*surf->data == SF_TRIANGLES) + { + //srfTriangles_t *surfTri = (srfTriangles_t *)surf->data; + return; //not handled + } + else if (*surf->data == SF_GRID) + { + //srfGridMesh_t *gridMesh = (srfGridMesh_t *)surf->data; + return; //not handled + } + else + { //...unknown type? + return; + } +} + +#endif // _XBOX + +//see if any surfaces on the node are facing opposite directions +//using plane normals. -rww +static inline bool R_NodeHasOppositeFaces(mnode_t *node) +{ + int c, d; + msurface_t *surf, *surf2, **mark, **mark2; + srfSurfaceFace_t *face, *face2; + vec3_t normalDif; + +#ifdef _XBOX + mleaf_s *leaf; + leaf = (mleaf_s*)node; + mark = tr.world->marksurfaces + leaf->firstMarkSurfNum; + c = leaf->nummarksurfaces; +#else + mark = node->firstmarksurface; + c = node->nummarksurfaces; +#endif + + while (c--) + { + surf = *mark; + + if (*surf->data != SF_FACE) + { //if this node is not entirely comprised of faces, I guess we shouldn't check it? + return false; + } + + face = (srfSurfaceFace_t *)surf->data; + + //go through other surfs and compare against this surf +#ifdef _XBOX + leaf = (mleaf_s*)node; + d = leaf->nummarksurfaces; + mark2 = tr.world->marksurfaces + leaf->firstMarkSurfNum; +#else + d = node->nummarksurfaces; + mark2 = node->firstmarksurface; +#endif + while (d--) + { + surf2 = *mark2; + + if (*surf2->data != SF_FACE) + { + return false; + } + face2 = (srfSurfaceFace_t *)surf2->data; + //see if this normal has a drastic angular change + VectorSubtract(face->plane.normal, face2->plane.normal, normalDif); + if (VectorLength(normalDif) >= 1.8f) + { + return true; + } + + mark2++; + } + mark++; + } + + return false; +} + +//recursively called for each node to go through the surfaces on that +//node and generate the wireframe map. -rww +static inline void R_RecursiveWireframeSurf(mnode_t *node) +{ + int c; + msurface_t *surf, **mark; + + if (!node) + { + return; + } + + while (1) + { + if (!node || + node->visframe != tr.visCount) + { //not valid, stop this chain of recursion + return; + } + + if ( node->contents != -1 ) + { + break; + } + + R_RecursiveWireframeSurf(node->children[0]); + + node = node->children[1]; + } + + // add the individual surfaces +#ifdef _XBOX + mleaf_s *leaf; + leaf = (mleaf_s*)node; + mark = tr.world->marksurfaces + leaf->firstMarkSurfNum; + c = leaf->nummarksurfaces; +#else + mark = node->firstmarksurface; + c = node->nummarksurfaces; +#endif + while (c--) + { + // the surface may have already been added if it + // spans multiple leafs + surf = *mark; + R_EvaluateWireframeSurf(surf); + mark++; + } +} + +//generates a wireframe model of the map for the automap view -rww +static void R_GenerateWireframeMap(mnode_t *baseNode) +{ + int i; + + //initialize data to all 0 + memset(&g_autoMapFrame, 0, sizeof(g_autoMapFrame)); + + //take the hit for this frame, mark all of these things as visible + //so we know which are valid for automap generation, but only the + //ones that are facing outside the world! (well, ideally.) + for (i = 0; i < tr.world->numnodes; i++) + { + if (tr.world->nodes[i].contents != CONTENTS_SOLID) + { +#if 0 //doesn't work, I take it surfs on nodes are not related to surfs on brushes + if (!R_NodeHasOppositeFaces(&tr.world->nodes[i])) +#endif + { + tr.world->nodes[i].visframe = tr.visCount; + } + } + } + + //now start the recursive evaluation + R_RecursiveWireframeSurf(baseNode); +} + +//clear out the wireframe map data -rww +void R_DestroyWireframeMap(void) +{ + wireframeMapSurf_t *next; + wireframeMapSurf_t *last; + + if (!g_autoMapValid) + { //not valid to begin with + return; + } + + next = g_autoMapFrame.surfs; + while (next) + { + //free memory allocated for points on this surface + Z_Free(next->points); + + //get the next surface + last = next; + next = next->next; + + //free memory for this surface + Z_Free(last); + } + + //invalidate everything + memset(&g_autoMapFrame, 0, sizeof(g_autoMapFrame)); + g_autoMapValid = false; + g_autoMapNextFree = NULL; +} + +//save 3d automap data to file -rww +qboolean R_WriteWireframeMapToFile(void) +{ + fileHandle_t f; + int requiredSize = 0; + wireframeMapSurf_t *surf = g_autoMapFrame.surfs; + byte *out, *rOut; + + //let's go through and see how much space we're going to need to stuff all this + //data into + while (surf) + { + //memory for each point + requiredSize += sizeof(wireframeSurfPoint_t)*surf->numPoints; + + //memory for numPoints + requiredSize += sizeof(int); + + surf = surf->next; + } + + if (requiredSize <= 0) + { //nothing to do..? + return qfalse; + } + + + f = FS_FOpenFileWrite("blahblah.bla"); + if (!f) + { //can't create? + return qfalse; + } + + //allocate the memory we will need + out = (byte *)Z_Malloc(requiredSize, TAG_ALL, qtrue); + rOut = out; + + //now go through and put the data into the memory + surf = g_autoMapFrame.surfs; + while (surf) + { + memcpy(out, surf, (sizeof(wireframeSurfPoint_t)*surf->numPoints) + sizeof(int)); + + //memory for each point + out += sizeof(wireframeSurfPoint_t)*surf->numPoints; + + //memory for numPoints + out += sizeof(int); + + surf = surf->next; + } + + //now write the buffer, and close + FS_Write(rOut, requiredSize, f); + Z_Free(rOut); + FS_FCloseFile(f); + + return qtrue; +} + +//load 3d automap data from file -rww +qboolean R_GetWireframeMapFromFile(void) +{ + wireframeMapSurf_t *surfs, *rSurfs; + wireframeMapSurf_t *newSurf; + fileHandle_t f; + int i = 0; + int len; + int stepBytes; + + len = FS_FOpenFileRead("blahblah.bla", &f, qfalse); + if (!f || len <= 0) + { //it doesn't exist + return qfalse; + } + + surfs = (wireframeMapSurf_t *)Z_Malloc(len, TAG_ALL, qtrue); + rSurfs = surfs; + FS_Read(surfs, len, f); + + while (i < len) + { + newSurf = R_GetNewWireframeMapSurf(); + newSurf->points = (wireframeSurfPoint_t *)Z_Malloc(sizeof(wireframeSurfPoint_t)*surfs->numPoints, TAG_ALL, qtrue); + + //copy the surf data into the new surf + //note - the surfs->points pointer is NOT pointing to valid memory, a pointer to that + //pointer is actually what we want to use as the location of the point offsets. + memcpy(newSurf->points, &surfs->points, sizeof(wireframeSurfPoint_t)*surfs->numPoints); + newSurf->numPoints = surfs->numPoints; + + //the size of the point data, plus an int (the number of points) + stepBytes = (sizeof(wireframeSurfPoint_t)*surfs->numPoints) + sizeof(int); + i += stepBytes; + + //increment the pointer to the start of the next surface + surfs = (wireframeMapSurf_t *)((byte *)surfs+stepBytes); + } + + //it should end up being equal, if not something was wrong with this file. + assert(i == len); + + FS_FCloseFile(f); + Z_Free(rSurfs); + return qtrue; +} + +//create everything, after destroying any existing data -rww +qboolean R_InitializeWireframeAutomap(void) +{ + if (r_autoMapDisable && r_autoMapDisable->integer) + { + return qfalse; + } + + if (tr.world && + tr.world->nodes) + { + R_DestroyWireframeMap(); +#if 0 //file load-save + if (!R_GetWireframeMapFromFile()) + { //first try loading the data from a file. If there is none, generate it. + R_GenerateWireframeMap(tr.world->nodes); + + //now write it to file, since we have generated it successfully. + R_WriteWireframeMapToFile(); + } +#else //always generate + R_GenerateWireframeMap(tr.world->nodes); +#endif + g_autoMapValid = true; + } + + return (qboolean)g_autoMapValid; +} +#endif //0 +/* +============================================================= +WIREFRAME AUTOMAP GENERATION SYSTEM - END +============================================================= +*/ + +void R_AutomapElevationAdjustment(float newHeight) +{ + g_playerHeight = newHeight; +} + +#ifdef _ALT_AUTOMAP_METHOD +//adjust the player height for gradient elevation colors -rww +qboolean R_InitializeWireframeAutomap(void) +{ //yoink + return qtrue; +} +#endif + +//draw the automap with the given transformation matrix -rww +#define QUADINFINITY 16777216 +static float g_lastHeight = 0.0f; +static bool g_lastHeightValid = false; +static void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ); +const void *R_DrawWireframeAutomap(const void *data) +{ + const drawBufferCommand_t *cmd = (const drawBufferCommand_t *)data; + float e = 0.0f; + float alpha; + wireframeMapSurf_t *s = g_autoMapFrame.surfs; +#ifndef _ALT_AUTOMAP_METHOD + int i; +#endif + + if (!r_autoMap || !r_autoMap->integer) + { + return (const void *)(cmd + 1); + } + +#ifndef _ALT_AUTOMAP_METHOD + if (!g_autoMapValid) + { //data is not valid, don't draw + return (const void *)(cmd + 1); + } +#endif + +#if 0 //instead of this method, just do the automap as a new "scene" + //projection matrix mode + qglMatrixMode(GL_PROJECTION); + + //store the current matrix + qglPushMatrix(); + //translate to our proper pos/angles from identity + qglLoadIdentity(); + qglTranslatef(pos[0], pos[1], pos[2]); + //presumeably this is correct for compensating for quake's + //crazy angle system. + qglRotatef(angles[1], 0.0f, 0.0f, 1.0f); + qglRotatef(-angles[0], 0.0f, 1.0f, 0.0f); + qglRotatef(angles[2], 1.0f, 0.0f, 0.0f); +#endif + + //disable 2d texturing + qglDisable( GL_TEXTURE_2D ); + + //now draw the backdrop +#if 0 //this does no good sadly, because of the issue of having to clear with a second scene + //in order for global fog clearing to work. + if (r_autoMapBackAlpha && r_autoMapBackAlpha->value) + { //specify the automap background alpha + alpha = r_autoMapBackAlpha->value; + + //cap it reasonably + if (alpha < 0.0f) + { + alpha = 0.0f; + } + else if (alpha > 1.0f) + { + alpha = 1.0f; + } + GL_State(GLS_SRCBLEND_SRC_ALPHA|GLS_DSTBLEND_SRC_ALPHA); + } + else +#endif + { + alpha = 1.0f; + GL_State(0); + } + //black + qglColor4f(0.0f, 0.0f, 0.0f, alpha); + + //draw a black backdrop + qglPushMatrix(); + qglLoadIdentity(); //get the ident matrix + + qglBegin( GL_QUADS ); + qglVertex3f( -QUADINFINITY, QUADINFINITY, -(backEnd.viewParms.zFar-1) ); + qglVertex3f( QUADINFINITY, QUADINFINITY, -(backEnd.viewParms.zFar-1) ); + qglVertex3f( QUADINFINITY, -QUADINFINITY, -(backEnd.viewParms.zFar-1) ); + qglVertex3f( -QUADINFINITY, -QUADINFINITY, -(backEnd.viewParms.zFar-1) ); + qglEnd (); + + //pop back the viewmatrix + qglPopMatrix(); + + + //set the mode to line draw + if (r_autoMap->integer == 2) + { //line mode + GL_State(GLS_POLYMODE_LINE|GLS_SRCBLEND_SRC_ALPHA|GLS_DSTBLEND_SRC_COLOR|GLS_DEPTHMASK_TRUE); + } + else + { //fill mode + //GL_State(GLS_SRCBLEND_SRC_ALPHA|GLS_DSTBLEND_SRC_COLOR|GLS_DEPTHMASK_TRUE); + GL_State(GLS_DEPTHMASK_TRUE); + } + + //set culling + GL_Cull(CT_TWO_SIDED); + +#ifndef _ALT_AUTOMAP_METHOD + //Draw the triangles + while (s) + { + //first, loop through and set the alpha on every point for this surf. + //if the alpha ends up being completely transparent for every point, we don't even + //need to draw it + if (g_playerHeight != g_lastHeight || + !g_lastHeightValid) + { //only do this if we need to + i = 0; + s->completelyTransparent = true; + while (i < s->numPoints) + { + //base the color on the elevation... for now, just check the first point height + if (s->points[i].xyz[2] < g_playerHeight) + { + e = s->points[i].xyz[2]-g_playerHeight; + } + else + { + e = g_playerHeight-s->points[i].xyz[2]; + } + if (e < 0.0f) + { + e = -e; + } + + if (r_autoMap->integer != 2) + { //fill mode + if (s->points[i].xyz[2] > (g_playerHeight+64.0f)) + { + s->points[i].alpha = 1.0f; + } + else + { + s->points[i].alpha = e/256.0f; + } + } + else + { + //set alpha and color based on relative height of point + s->points[i].alpha = e/256.0f; + } + e /= 512.0f; + + //cap color + if (e > 1.0f) + { + e = 1.0f; + } + else if (e < 0.0f) + { + e = 0.0f; + } + VectorSet(s->points[i].color, e, 1.0f-e, 0.0f); + + //cap alpha + if (s->points[i].alpha > 1.0f) + { + s->points[i].alpha = 1.0f; + } + else if (s->points[i].alpha < 0.0f) + { + s->points[i].alpha = 0.0f; + } + + if (s->points[i].alpha != 1.0f) + { //this point is not entirely alpha'd out, so still draw the surface + s->completelyTransparent = false; + } + + i++; + } + } + + if (s->completelyTransparent) + { + s = s->next; + continue; + } + + i = 0; + qglBegin(GL_TRIANGLES); + while (i < s->numPoints) + { + if (r_autoMap->integer == 2 || s->numPoints < 3) + { //line mode or not enough verts on surface + qglColor4f(s->points[i].color[0], s->points[i].color[1], s->points[i].color[2], s->points[i].alpha); + } + else + { //fill mode + vec3_t planeNormal; + float fAlpha = s->points[i].alpha; + planeNormal[0] = s->points[0].xyz[1]*(s->points[1].xyz[2]-s->points[2].xyz[2]) + s->points[1].xyz[1]*(s->points[2].xyz[2]-s->points[0].xyz[2]) + s->points[2].xyz[1]*(s->points[0].xyz[2]-s->points[1].xyz[2]); + planeNormal[1] = s->points[0].xyz[2]*(s->points[1].xyz[0]-s->points[2].xyz[0]) + s->points[1].xyz[2]*(s->points[2].xyz[0]-s->points[0].xyz[0]) + s->points[2].xyz[2]*(s->points[0].xyz[0]-s->points[1].xyz[0]); + planeNormal[2] = s->points[0].xyz[0]*(s->points[1].xyz[1]-s->points[2].xyz[1]) + s->points[1].xyz[0]*(s->points[2].xyz[1]-s->points[0].xyz[1]) + s->points[2].xyz[0]*(s->points[0].xyz[1]-s->points[1].xyz[1]); + + if (planeNormal[0] < 0.0f) planeNormal[0] = -planeNormal[0]; + if (planeNormal[1] < 0.0f) planeNormal[1] = -planeNormal[1]; + if (planeNormal[2] < 0.0f) planeNormal[2] = -planeNormal[2]; + + /* + if (s->points[i].xyz[2] > g_playerHeight+64.0f && + planeNormal[2] > 0.7f) + { //surface above player facing up/down directly + fAlpha = 1.0f-planeNormal[2]; + } + */ + + //qglColor4f(planeNormal[0], planeNormal[1], planeNormal[2], fAlpha); + qglColor4f(s->points[i].color[0], s->points[i].color[1], 1.0f-planeNormal[2], fAlpha); + } + qglVertex3f(s->points[i].xyz[0], s->points[i].xyz[1], s->points[i].xyz[2]); + i++; + } + qglEnd(); + s = s->next; + } +#else + tr_drawingAutoMap = true; + R_RecursiveWorldNode( tr.world->nodes, 15, 0 ); + tr_drawingAutoMap = false; +#endif + + g_lastHeight = g_playerHeight; + g_lastHeightValid = true; + +#if 0 //instead of this method, just do the automap as a new "scene" + //pop back the view matrix + qglPopMatrix(); +#endif + + //reenable 2d texturing + qglEnable( GL_TEXTURE_2D ); + + //white color/full alpha + qglColor4f(1.0f, 1.0f, 1.0f, 1.0f); + + return (const void *)(cmd + 1); +} + + +/* +================ +R_RecursiveWorldNode +================ +*/ +static void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ) { + + do + { + int newDlights[2]; + +#ifdef _ALT_AUTOMAP_METHOD + if (tr_drawingAutoMap) + { + node->visframe = tr.visCount; + } +#endif + + // if the node wasn't marked as potentially visible, exit + if (node->visframe != tr.visCount) + { + return; + } + + // if the bounding volume is outside the frustum, nothing + // inside can be visible OPTIMIZE: don't do this all the way to leafs? + +#ifdef _ALT_AUTOMAP_METHOD + if ( r_nocull->integer!=1 && !tr_drawingAutoMap ) +#else + if (r_nocull->integer!=1) +#endif + { + int r; + + if ( planeBits & 1 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~1; // all descendants will also be in front + } + } + + if ( planeBits & 2 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~2; // all descendants will also be in front + } + } + + if ( planeBits & 4 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~4; // all descendants will also be in front + } + } + + if ( planeBits & 8 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~8; // all descendants will also be in front + } + } + + } + + if ( node->contents != -1 ) { + break; + } + + // node is just a decision point, so go down both sides + // since we don't care about sort orders, just go positive to negative + + // determine which dlights are needed + if ( r_nocull->integer!=2 ) + { + newDlights[0] = 0; + newDlights[1] = 0; + if ( dlightBits ) + { + int i; + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) + { + dlight_t *dl; + float dist; + + if ( dlightBits & ( 1 << i ) ) { + dl = &tr.refdef.dlights[i]; + dist = DotProduct( dl->origin, tr.world->planes[node->planeNum].normal ) - + tr.world->planes[node->planeNum].dist; + + if ( dist > -dl->radius ) { + newDlights[0] |= ( 1 << i ); + } + if ( dist < dl->radius ) { + newDlights[1] |= ( 1 << i ); + } + } + } + } + } + else + { + newDlights[0] = dlightBits; + newDlights[1] = dlightBits; + } + + // recurse down the children, front side first + R_RecursiveWorldNode (node->children[0], planeBits, newDlights[0] ); + + // tail recurse + node = node->children[1]; + dlightBits = newDlights[1]; + } while ( 1 ); + + { + // leaf node, so add mark surfaces + int c; + msurface_t *surf, **mark; + mleaf_s *leaf; + + tr.pc.c_leafs++; + + // add to z buffer bounds + if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) { + tr.viewParms.visBounds[0][0] = node->mins[0]; + } + if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) { + tr.viewParms.visBounds[0][1] = node->mins[1]; + } + if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) { + tr.viewParms.visBounds[0][2] = node->mins[2]; + } + + if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) { + tr.viewParms.visBounds[1][0] = node->maxs[0]; + } + if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) { + tr.viewParms.visBounds[1][1] = node->maxs[1]; + } + if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) { + tr.viewParms.visBounds[1][2] = node->maxs[2]; + } + + // add the individual surfaces + leaf = (mleaf_s*)node; + mark = tr.world->marksurfaces + leaf->firstMarkSurfNum; + c = leaf->nummarksurfaces; + while (c--) { + // the surface may have already been added if it + // spans multiple leafs + surf = *mark; + R_AddWorldSurface( surf, dlightBits ); + mark++; + } + } + +} + + +/* +=============== +R_PointInLeaf +=============== +*/ +static mnode_t *R_PointInLeaf( const vec3_t p ) { + mnode_t *node; + float d; + cplane_t *plane; + + if ( !tr.world ) { + Com_Error (ERR_DROP, "R_PointInLeaf: bad model"); + } + + node = tr.world->nodes; + while( 1 ) { + if (node->contents != -1) { + break; + } +#ifdef _XBOX + plane = tr.world->planes + node->planeNum; +#else + plane = node->plane; +#endif + d = DotProduct (p,plane->normal) - plane->dist; + if (d > 0) { + node = node->children[0]; + } else { + node = node->children[1]; + } + } + + return node; +} + +/* +============== +R_ClusterPVS +============== +*/ +static const byte *R_ClusterPVS (int cluster) { + if (!tr.world || !tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters ) { + return tr.world->novis; + } + +#ifdef _XBOX + return tr.world->vis->Decompress(cluster * tr.world->clusterBytes, + tr.world->numClusters); +#else + return tr.world->vis + cluster * tr.world->clusterBytes; +#endif +} + +/* +================= +R_inPVS +================= +*/ +qboolean R_inPVS( const vec3_t p1, const vec3_t p2, byte *mask ) { + int leafnum; + int cluster; + int area1, area2; + + leafnum = CM_PointLeafnum (p1); + cluster = CM_LeafCluster (leafnum); + area1 = CM_LeafArea (leafnum); + + //agh, the damn snapshot mask doesn't work for this + mask = (byte *) CM_ClusterPVS (cluster); + + leafnum = CM_PointLeafnum (p2); + cluster = CM_LeafCluster (leafnum); + area2 = CM_LeafArea (leafnum); + if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) + return qfalse; + //this doesn't freakin work +// if (!CM_AreasConnected (area1, area2)) +// return qfalse; // a door blocks sight + return qtrue; +} + +/* +=============== +R_MarkLeaves + +Mark the leaves and nodes that are in the PVS for the current +cluster +=============== +*/ +#ifdef _XBOX +void R_MarkLeaves (mleaf_s *leafOverride) { + const byte *vis; + mleaf_s *leaf; + mnode_s *parent; + int i; + int cluster; + + // lockpvs lets designers walk around to determine the + // extent of the current pvs + if ( r_lockpvs->integer ) { + return; + } + + // current viewcluster + if(!leafOverride) { + leaf = (mleaf_s*)R_PointInLeaf( tr.viewParms.pvsOrigin ); + } else { + leaf = leafOverride; + } + cluster = leaf->cluster; + + assert(leaf->contents != -1); + + // if the cluster is the same and the area visibility matrix + // hasn't changed, we don't need to mark everything again + + if ( tr.viewCluster == cluster && !tr.refdef.areamaskModified ) { + return; + } + + tr.visCount++; + tr.viewCluster = cluster; + + if ( r_novis->integer || tr.viewCluster == -1 ) { + for (i=0 ; inumnodes ; i++) { + if (tr.world->nodes[i].contents != CONTENTS_SOLID) { + tr.world->nodes[i].visframe = tr.visCount; + } + } + return; + } + + vis = R_ClusterPVS (tr.viewCluster); + + for (i=0,leaf=tr.world->leafs ; inumleafs ; i++, leaf++) { + cluster = leaf->cluster; + if ( cluster < 0 || cluster >= tr.world->numClusters ) { + continue; + } + + // check general pvs + if ( !(vis[cluster>>3] & (1<<(cluster&7))) ) { + continue; + } + + // check for door connection + if (!lookingForWorstLeaf && + (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) { + continue; // not visible + } + + parent = (mnode_t*)leaf; + assert(leaf->contents != -1); + do { + if (parent->visframe == tr.visCount) + break; + parent->visframe = tr.visCount; + parent = parent->parent; + } while (parent); + } +} +#else // _XBOX + +static void R_MarkLeaves (void) { + const byte *vis; + mnode_t *leaf, *parent; + int i; + int cluster; + + // lockpvs lets designers walk around to determine the + // extent of the current pvs + if ( r_lockpvs->integer ) { + return; + } + + // current viewcluster + leaf = R_PointInLeaf( tr.viewParms.pvsOrigin ); + cluster = leaf->cluster; + + // if the cluster is the same and the area visibility matrix + // hasn't changed, we don't need to mark everything again + + // if r_showcluster was just turned on, remark everything + if ( tr.viewCluster == cluster && !tr.refdef.areamaskModified + && !r_showcluster->modified ) { + return; + } + + if ( r_showcluster->modified || r_showcluster->integer ) { + r_showcluster->modified = qfalse; + if ( r_showcluster->integer ) { + Com_Printf ("cluster:%i area:%i\n", cluster, leaf->area ); + } + } + + tr.visCount++; + tr.viewCluster = cluster; + + if ( r_novis->integer || tr.viewCluster == -1 ) { + for (i=0 ; inumnodes ; i++) { + if (tr.world->nodes[i].contents != CONTENTS_SOLID) { + tr.world->nodes[i].visframe = tr.visCount; + } + } + return; + } + + vis = R_ClusterPVS (tr.viewCluster); + + for (i=0,leaf=tr.world->nodes ; inumnodes ; i++, leaf++) { + cluster = leaf->cluster; + if ( cluster < 0 || cluster >= tr.world->numClusters ) { + continue; + } + + // check general pvs + if ( !(vis[cluster>>3] & (1<<(cluster&7))) ) { + continue; + } + + // check for door connection + if ( (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) { + continue; // not visible + } + + parent = leaf; + do { + if (parent->visframe == tr.visCount) + break; + parent->visframe = tr.visCount; + parent = parent->parent; + } while (parent); + } +} +#endif // _XBOX + +/* +============= +R_AddWorldSurfaces +============= +*/ +#ifdef _XBOX +void R_AddWorldSurfaces (void) { + if ( !r_drawworld->integer ) { + return; + } + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } + + tr.currentEntityNum = TR_WORLDENT;//ENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + // clear out the visible min/max + ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] ); + + // perform frustum culling and add all the potentially visible surfaces + if ( tr.refdef.num_dlights > MAX_DLIGHTS ) { + tr.refdef.num_dlights = MAX_DLIGHTS ; + } + + R_RecursiveWorldNode( tr.world->nodes, 15, ( 1 << tr.refdef.num_dlights ) - 1 ); +} + +#else // _XBOX + +void R_AddWorldSurfaces (void) { + if ( !r_drawworld->integer ) { + return; + } + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } + + tr.currentEntityNum = TR_WORLDENT; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + // determine which leaves are in the PVS / areamask + R_MarkLeaves (); + + // clear out the visible min/max + ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] ); + + // perform frustum culling and add all the potentially visible surfaces + if ( tr.refdef.num_dlights > 32 ) { + tr.refdef.num_dlights = 32 ; + } + + R_RecursiveWorldNode( tr.world->nodes, 15, ( 1 << tr.refdef.num_dlights ) - 1 ); +} +#endif // _XBOX diff --git a/codemp/renderer/tr_worldeffects.cpp b/codemp/renderer/tr_worldeffects.cpp new file mode 100644 index 0000000..605edcf --- /dev/null +++ b/codemp/renderer/tr_worldeffects.cpp @@ -0,0 +1,2025 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN SOFTWARE - STAR WARS: JK II +// (c) 2002 Activision +// +// World Effects +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#include "../qcommon/exe_headers.h" +#pragma warning( disable : 4512 ) + +inline float WE_flrand(float min, float max) { + return ((rand() * (max - min)) / 32768.0F) + min; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Externs & Fwd Decl. +//////////////////////////////////////////////////////////////////////////////////////// +extern qboolean ParseVector( const char **text, int count, float *v ); +extern void SetViewportAndScissor( void ); + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#include "tr_local.h" +#include "tr_WorldEffects.h" + +#include "../Ravl/CVec.h" +#include "../Ratl/vector_vs.h" +#include "../Ratl/bits_vs.h" + +#ifdef _XBOX +#include "../win32/glw_win_dx8.h" +#else +#include "glext.h" +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// Defines +//////////////////////////////////////////////////////////////////////////////////////// +#define GLS_ALPHA (GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA) +#define MAX_WIND_ZONES 10 +#define MAX_WEATHER_ZONES 10 +#define MAX_PUFF_SYSTEMS 2 +#define MAX_PARTICLE_CLOUDS 5 + +#ifdef _XBOX +#define POINTCACHE_CELL_SIZE 96.0f + +// Note to Vv: +// you guys may want to look into lowering that number. I've optimized the storage +// space by breaking it up into small boxes (weather zones) around the areas we care about +// in order to speed up load time and reduce memory. A very high number here will mean +// that weather related effects like rain, fog, snow, etc will bleed through to where +// they shouldn't... + +#else +#define POINTCACHE_CELL_SIZE 96.0f +#endif + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Globals +//////////////////////////////////////////////////////////////////////////////////////// +float mMillisecondsElapsed = 0; +float mSecondsElapsed = 0; +bool mFrozen = false; + +CVec3 mGlobalWindVelocity; +CVec3 mGlobalWindDirection; +float mGlobalWindSpeed; +int mParticlesRendered; + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Handy Functions +//////////////////////////////////////////////////////////////////////////////////////// +inline void VectorFloor(vec3_t in) +{ + in[0] = floorf(in[0]); + in[1] = floorf(in[1]); + in[2] = floorf(in[2]); +} + +inline void VectorCeil(vec3_t in) +{ + in[0] = ceilf(in[0]); + in[1] = ceilf(in[1]); + in[2] = ceilf(in[2]); +} + +inline float FloatRand(void) +{ + return ((float)rand() / (float)RAND_MAX); +} + +inline float fast_flrand(float min, float max) +{ + //return min + (max - min) * flrand; + return WE_flrand(min, max); //fixme? +} + +inline void SnapFloatToGrid(float& f, int GridSize) +{ + f = (int)(f); + + bool fNeg = (f<0); + if (fNeg) + { + f *= -1; // Temporarly make it positive + } + + int Offset = ((int)(f) % (int)(GridSize)); + int OffsetAbs = abs(Offset); + if (OffsetAbs>(GridSize/2)) + { + Offset = (GridSize - OffsetAbs) * -1; + } + + f -= Offset; + + if (fNeg) + { + f *= -1; // Put It Back To Negative + } + + f = (int)(f); + + assert(((int)(f)%(int)(GridSize)) == 0); +} + +inline void SnapVectorToGrid(CVec3& Vec, int GridSize) +{ + SnapFloatToGrid(Vec[0], GridSize); + SnapFloatToGrid(Vec[1], GridSize); + SnapFloatToGrid(Vec[2], GridSize); +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Range Structures +//////////////////////////////////////////////////////////////////////////////////////// +struct SVecRange +{ + CVec3 mMins; + CVec3 mMaxs; + + inline void Clear() + { + mMins.Clear(); + mMaxs.Clear(); + } + + inline void Pick(CVec3& V) + { + V[0] = WE_flrand(mMins[0], mMaxs[0]); + V[1] = WE_flrand(mMins[1], mMaxs[1]); + V[2] = WE_flrand(mMins[2], mMaxs[2]); + } + inline void Wrap(CVec3& V, SVecRange &spawnRange) + { + if (V[0]mMaxs[0]) + { + const float d = V[0]-mMaxs[0]; + V[0] = mMins[0]+fmod(d, mMaxs[0]-mMins[0]); + } + + if (V[1]mMaxs[1]) + { + const float d = V[1]-mMaxs[1]; + V[1] = mMins[1]+fmod(d, mMaxs[1]-mMins[1]); + } + + if (V[2]mMaxs[2]) + { + const float d = V[2]-mMaxs[2]; + V[2] = mMins[2]+fmod(d, mMaxs[2]-mMins[2]); + } + } + + inline bool In(const CVec3& V) + { + return (V>mMins && VmMin && VmMin && V TFlags; + + float mAlpha; + TFlags mFlags; + CVec3 mPosition; + CVec3 mVelocity; + float mMass; // A higher number will more greatly resist force and result in greater gravity +}; + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Wind +//////////////////////////////////////////////////////////////////////////////////////// +class CWindZone +{ +public: + bool mGlobal; + SVecRange mRBounds; + SVecRange mRVelocity; + SIntRange mRDuration; + SIntRange mRDeadTime; + float mMaxDeltaVelocityPerUpdate; + float mChanceOfDeadTime; + + CVec3 mCurrentVelocity; + CVec3 mTargetVelocity; + int mTargetVelocityTimeRemaining; + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Initialize - Will setup default values for all data + //////////////////////////////////////////////////////////////////////////////////// + void Initialize() + { + mRBounds.Clear(); + mGlobal = true; + + mRVelocity.mMins = -1500.0f; + mRVelocity.mMins[2] = -10.0f; + mRVelocity.mMaxs = 1500.0f; + mRVelocity.mMaxs[2] = 10.0f; + + mMaxDeltaVelocityPerUpdate = 10.0f; + + mRDuration.mMin = 1000; + mRDuration.mMax = 2000; + + mChanceOfDeadTime = 0.3f; + mRDeadTime.mMin = 1000; + mRDeadTime.mMax = 3000; + + mCurrentVelocity.Clear(); + mTargetVelocity.Clear(); + mTargetVelocityTimeRemaining = 0; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Update - Changes wind when current target velocity expires + //////////////////////////////////////////////////////////////////////////////////// + void Update() + { + if (mTargetVelocityTimeRemaining==0) + { + if (FloatRand() mMaxDeltaVelocityPerUpdate) + { + DeltaVelocityLen = mMaxDeltaVelocityPerUpdate; + } + DeltaVelocity *= (DeltaVelocityLen); + mCurrentVelocity += DeltaVelocity; + } + } +}; +ratl::vector_vs mWindZones; + +bool R_GetWindVector(vec3_t windVector) +{ + VectorCopy(mGlobalWindDirection.v, windVector); + return true; +} + +bool R_GetWindSpeed(float &windSpeed) +{ + windSpeed = mGlobalWindSpeed; + return true; +} + +bool R_GetWindGusting() +{ + return (mGlobalWindSpeed>1000.0f); +} + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Outside Point Cache +//////////////////////////////////////////////////////////////////////////////////////// +class COutside +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + //Global Public Outside Variables + //////////////////////////////////////////////////////////////////////////////////// + bool mOutsideShake; + float mOutsidePain; + +private: + //////////////////////////////////////////////////////////////////////////////////// + // The Outside Cache + //////////////////////////////////////////////////////////////////////////////////// + bool mCacheInit; // Has It Been Cached? + + struct SWeatherZone + { + static bool mMarkedOutside; + ulong* mPointCache; + SVecRange mExtents; + SVecRange mSize; + int mWidth; + int mHeight; + int mDepth; + + //////////////////////////////////////////////////////////////////////////////////// + // Convert To Cell + //////////////////////////////////////////////////////////////////////////////////// + inline void ConvertToCell(const CVec3& pos, int& x, int& y, int& z, int& bit) + { + x = (int)((pos[0] / POINTCACHE_CELL_SIZE) - mSize.mMins[0]); + y = (int)((pos[1] / POINTCACHE_CELL_SIZE) - mSize.mMins[1]); + z = (int)((pos[2] / POINTCACHE_CELL_SIZE) - mSize.mMins[2]); + + bit = (z & 31); + z >>= 5; + } + + //////////////////////////////////////////////////////////////////////////////////// + // CellOutside - Test to see if a given cell is outside + //////////////////////////////////////////////////////////////////////////////////// + inline bool CellOutside(int x, int y, int z, int bit) + { + if ((x < 0 || x >= mWidth) || (y < 0 || y >= mHeight) || (z < 0 || z >= mDepth) || (bit < 0 || bit >= 32)) + { + return !(mMarkedOutside); + } + return (mMarkedOutside==(!!(mPointCache[((z * mWidth * mHeight) + (y * mWidth) + x)]&(1 << bit)))); + } + }; + ratl::vector_vs mWeatherZones; + + +private: + //////////////////////////////////////////////////////////////////////////////////// + // Iteration Variables + //////////////////////////////////////////////////////////////////////////////////// + int mWCells; + int mHCells; + + int mXCell; + int mYCell; + int mZBit; + + int mXMax; + int mYMax; + int mZMax; + + +private: + + + //////////////////////////////////////////////////////////////////////////////////// + // Contents Outside + //////////////////////////////////////////////////////////////////////////////////// + inline bool ContentsOutside(int contents) + { + if (contents&CONTENTS_WATER || contents&CONTENTS_SOLID) + { + return false; + } + if (mCacheInit) + { + if (SWeatherZone::mMarkedOutside) + { + return (!!(contents&CONTENTS_OUTSIDE)); + } + return (!(contents&CONTENTS_INSIDE)); + } + return !!(contents&CONTENTS_OUTSIDE); + } + + + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructor - Will setup default values for all data + //////////////////////////////////////////////////////////////////////////////////// + void Reset() + { + mOutsideShake = false; + mOutsidePain = 0.0; + mCacheInit = false; + SWeatherZone::mMarkedOutside = false; + for (int wz=0; wz> 5; + + int arraySize = (Wz.mWidth * Wz.mHeight * Wz.mDepth); + Wz.mPointCache = (ulong *)Z_Malloc(arraySize*sizeof(ulong), TAG_POINTCACHE, qtrue); + } + } + + + + //////////////////////////////////////////////////////////////////////////////////// + // Cache - Will Scan the World, Creating The Cache + //////////////////////////////////////////////////////////////////////////////////// + void Cache() + { + if (!tr.world || mCacheInit) + { + return; + } + + CVec3 CurPos; + CVec3 Size; + CVec3 Mins; + int x, y, z, q, zbase; + bool curPosOutside; + ulong contents; + ulong bit; + + + // Record The Extents Of The World Incase No Other Weather Zones Exist + //--------------------------------------------------------------------- + if (!mWeatherZones.size()) + { + Com_Printf("WARNING: No Weather Zones Encountered"); + AddWeatherZone(tr.world->bmodels[0].bounds[0], tr.world->bmodels[0].bounds[1]); + } + + // Iterate Over All Weather Zones + //-------------------------------- + for (int zone=0; zoneinDrawBlock); + + // start the draw block + glw_state->inDrawBlock = true; + glw_state->primitiveMode = D3DPT_POINTLIST; + + // update DX with any pending state changes + glw_state->drawStride = 4; + DWORD mask = D3DFVF_XYZ | D3DFVF_DIFFUSE; + glw_state->device->SetVertexShader(mask); + glw_state->shaderMask = mask; + + if(glw_state->matricesDirty[glwstate_t::MatrixMode_Model]) + { + glw_state->device->SetTransform(D3DTS_VIEW, + glw_state->matrixStack[glwstate_t::MatrixMode_Model]->GetTop()); + + glw_state->matricesDirty[glwstate_t::MatrixMode_Model] = false; + } + + // Update the texture and states + // NOTE: Point sprites ALWAYS go on texture stage 3 + glwstate_t::texturexlat_t::iterator it = glw_state->textureXlat.find(glw_state->currentTexture[0]); + glw_state->device->SetTexture( 3, it->second.mipmap ); + glw_state->device->SetTextureStageState(3, D3DTSS_COLOROP, glw_state->textureEnv[0]); + glw_state->device->SetTextureStageState(3, D3DTSS_COLORARG1, D3DTA_TEXTURE); + glw_state->device->SetTextureStageState(3, D3DTSS_COLORARG2, D3DTA_CURRENT); + glw_state->device->SetTextureStageState(3, D3DTSS_ALPHAOP, glw_state->textureEnv[0]); + glw_state->device->SetTextureStageState(3, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + glw_state->device->SetTextureStageState(3, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + glw_state->device->SetTextureStageState(3, D3DTSS_MAXANISOTROPY, it->second.anisotropy); + glw_state->device->SetTextureStageState(3, D3DTSS_MINFILTER, it->second.minFilter); + glw_state->device->SetTextureStageState(3, D3DTSS_MIPFILTER, it->second.mipFilter); + glw_state->device->SetTextureStageState(3, D3DTSS_MAGFILTER, it->second.magFilter); + glw_state->device->SetTextureStageState(3, D3DTSS_ADDRESSU, it->second.wrapU); + glw_state->device->SetTextureStageState(3, D3DTSS_ADDRESSV, it->second.wrapV); + + glw_state->device->SetTexture( 0, NULL ); + glw_state->device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_DISABLE ); + + float attena = 1.0f, attenb = 0.0f, attenc = 0.0004f; + glw_state->device->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_POINTSCALEENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_POINTSIZE, *((DWORD*)&size) ); + glw_state->device->SetRenderState( D3DRS_POINTSIZE_MIN, *((DWORD*)&attenb)); + glw_state->device->SetRenderState( D3DRS_POINTSCALE_A, *((DWORD*)&attena) ); + glw_state->device->SetRenderState( D3DRS_POINTSCALE_B, *((DWORD*)&attenb) ); + glw_state->device->SetRenderState( D3DRS_POINTSCALE_C, *((DWORD*)&attenc) ); + + // set vertex counters + glw_state->numVertices = 0; + glw_state->totalVertices = verts; + int max = glw_state->totalVertices; + if (max > 2040 / glw_state->drawStride) + { + max = 2040 / glw_state->drawStride; + } + glw_state->maxVertices = max; + + // open a draw packet + int num_packets; + if(verts == 0) { + num_packets = 1; + } else { + num_packets = (verts / glw_state->maxVertices) + (!!(verts % glw_state->maxVertices)); + } + int cmd_size = num_packets * 3; + int vert_size = glw_state->drawStride * verts; + + glw_state->device->BeginPush(vert_size + cmd_size + 2, + &glw_state->drawArray); + + glw_state->drawArray[0] = D3DPUSH_ENCODE(D3DPUSH_SET_BEGIN_END, 1); + glw_state->drawArray[1] = glw_state->primitiveMode; + glw_state->drawArray[2] = D3DPUSH_ENCODE( + D3DPUSH_NOINCREMENT_FLAG|D3DPUSH_INLINE_ARRAY, + glw_state->drawStride * glw_state->maxVertices); + glw_state->drawArray += 3; +} + + +static void pointEnd() +{ + glw_state->device->SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_POINTSCALEENABLE, FALSE ); + glw_state->device->SetTexture( 3, NULL ); + glw_state->device->SetTextureStageState( 3, D3DTSS_COLOROP, D3DTOP_DISABLE ); +} +#endif // _XBOX + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Particle Cloud +//////////////////////////////////////////////////////////////////////////////////////// +class CWeatherParticleCloud +{ +private: + //////////////////////////////////////////////////////////////////////////////////// + // DYNAMIC MEMORY + //////////////////////////////////////////////////////////////////////////////////// + image_t* mImage; + CWeatherParticle* mParticles; + +private: + //////////////////////////////////////////////////////////////////////////////////// + // RUN TIME VARIANTS + //////////////////////////////////////////////////////////////////////////////////// + float mSpawnSpeed; + CVec3 mSpawnPlaneNorm; + CVec3 mSpawnPlaneRight; + CVec3 mSpawnPlaneUp; + SVecRange mRange; + + CVec3 mCameraPosition; + CVec3 mCameraForward; + CVec3 mCameraLeft; + CVec3 mCameraDown; + CVec3 mCameraLeftPlusUp; + CVec3 mCameraLeftMinusUp; + + + int mParticleCountRender; + int mGLModeEnum; + + bool mPopulated; + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // CONSTANTS + //////////////////////////////////////////////////////////////////////////////////// + bool mOrientWithVelocity; + float mSpawnPlaneSize; + float mSpawnPlaneDistance; + SVecRange mSpawnRange; + + float mGravity; // How much gravity affects the velocity of a particle + CVec4 mColor; // RGBA color + int mVertexCount; // 3 for triangle, 4 for quad, other numbers not supported + + float mWidth; + float mHeight; + + int mBlendMode; // 0 = ALPHA, 1 = SRC->SRC + int mFilterMode; // 0 = LINEAR, 1 = NEAREST + + float mFade; // How much to fade in and out 1.0 = instant, 0.01 = very slow + + SFloatRange mRotation; + float mRotationDelta; + float mRotationDeltaTarget; + float mRotationCurrent; + SIntRange mRotationChangeTimer; + int mRotationChangeNext; + + SFloatRange mMass; // Determines how slowness to accelerate, higher number = slower + float mFrictionInverse; // How much air friction does this particle have 1.0=none, 0.0=nomove + + int mParticleCount; + + bool mWaterParticles; + + + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Initialize - Create Image, Particles, And Setup All Values + //////////////////////////////////////////////////////////////////////////////////// + void Initialize(int count, const char* texturePath, int VertexCount=4) + { + Reset(); + assert(mParticleCount==0 && mParticles==0); + assert(mImage==0); + + // Create The Image + //------------------ + mImage = R_FindImageFile(texturePath, qfalse, qfalse, qfalse, GL_CLAMP); + if (!mImage) + { + Com_Error(ERR_DROP, "CWeatherParticleCloud: Could not texture %s", texturePath); + } + + GL_Bind(mImage); + + + + // Create The Particles + //---------------------- + mParticleCount = count; + mParticles = new CWeatherParticle[mParticleCount]; + + + + CWeatherParticle* part=0; + for (int particleNum=0; particleNummPosition.Clear(); + part->mVelocity.Clear(); + part->mAlpha = 0.0f; + mMass.Pick(part->mMass); + } + + mVertexCount = VertexCount; + +#ifdef _XBOX + if(mVertexCount == 1) + mGLModeEnum = GL_POINTS; + else +#endif + mGLModeEnum = (mVertexCount==3)?(GL_TRIANGLES):(GL_QUADS); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Reset - Initializes all data to default values + //////////////////////////////////////////////////////////////////////////////////// + void Reset() + { + if (mImage) + { + // TODO: Free Image? + } + mImage = 0; + if (mParticleCount) + { + delete [] mParticles; + } + mParticleCount = 0; + mParticles = 0; + + mPopulated = 0; + + + + // These Are The Default Startup Values For Constant Data + //======================================================== + mOrientWithVelocity = false; + mWaterParticles = false; + + mSpawnPlaneDistance = 500; + mSpawnPlaneSize = 500; + mSpawnRange.mMins = -(mSpawnPlaneDistance*1.25f); + mSpawnRange.mMaxs = (mSpawnPlaneDistance*1.25f); + + mGravity = 300.0f; // Units Per Second + + mColor = 1.0f; + + mVertexCount = 4; + mWidth = 1.0f; + mHeight = 1.0f; + + mBlendMode = 0; + mFilterMode = 0; + + mFade = 10.0f; + + mRotation.Clear(); + mRotationDelta = 0.0f; + mRotationDeltaTarget= 0.0f; + mRotationCurrent = 0.0f; + mRotationChangeNext = -1; + mRotation.mMin = -0.7f; + mRotation.mMax = 0.7f; + mRotationChangeTimer.mMin = 500; + mRotationChangeTimer.mMin = 2000; + + mMass.mMin = 5.0f; + mMass.mMax = 10.0f; + + mFrictionInverse = 0.7f; // No Friction? + } + + //////////////////////////////////////////////////////////////////////////////////// + // Constructor - Will setup default values for all data + //////////////////////////////////////////////////////////////////////////////////// + CWeatherParticleCloud() + { + mImage = 0; + mParticleCount = 0; + Reset(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Initialize - Will setup default values for all data + //////////////////////////////////////////////////////////////////////////////////// + ~CWeatherParticleCloud() + { + Reset(); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // UseSpawnPlane - Check To See If We Should Spawn On A Plane, Or Just Wrap The Box + //////////////////////////////////////////////////////////////////////////////////// + inline bool UseSpawnPlane() + { + return (mGravity!=0.0f); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Update - Applies All Physics Forces To All Contained Particles + //////////////////////////////////////////////////////////////////////////////////// + void Update() + { + CWeatherParticle* part=0; + CVec3 partForce; + CVec3 partMoved; + CVec3 partToCamera; + bool partRendering; + bool partOutside; + bool partInRange; + bool partInView; + int particleNum; + float particleFade = (mFade * mSecondsElapsed); + +/* TODO: Non Global Wind Zones + CWindZone* wind=0; + int windNum; + int windCount = mWindZones.size(); +*/ + + // Compute Camera + //---------------- + { + mCameraPosition = backEnd.viewParms.ori.origin; + mCameraForward = backEnd.viewParms.ori.axis[0]; + mCameraLeft = backEnd.viewParms.ori.axis[1]; + mCameraDown = backEnd.viewParms.ori.axis[2]; + + if (mRotationChangeNext!=-1) + { + if (mRotationChangeNext==0) + { + mRotation.Pick(mRotationDeltaTarget); + mRotationChangeTimer.Pick(mRotationChangeNext); + if (mRotationChangeNext<=0) + { + mRotationChangeNext = 1; + } + } + mRotationChangeNext--; + + float RotationDeltaDifference = (mRotationDeltaTarget - mRotationDelta); + if (fabsf(RotationDeltaDifference)>0.01) + { + mRotationDelta += RotationDeltaDifference; // Blend To New Delta + } + mRotationCurrent += (mRotationDelta * mSecondsElapsed); + float s = sinf(mRotationCurrent); + float c = cosf(mRotationCurrent); + + CVec3 TempCamLeft(mCameraLeft); + + mCameraLeft *= (c * mWidth); + mCameraLeft.ScaleAdd(mCameraDown, (s * mWidth * -1.0f)); + + mCameraDown *= (c * mHeight); + mCameraDown.ScaleAdd(TempCamLeft, (s * mHeight)); + } + else + { + mCameraLeft *= mWidth; + mCameraDown *= mHeight; + } + } + + + // Compute Global Force + //---------------------- + CVec3 force; + { + force.Clear(); + + // Apply Gravity + //--------------- + force[2] = -1.0f * mGravity; + + // Apply Wind Velocity + //--------------------- + force += mGlobalWindVelocity; + } + + + // Update Range + //-------------- + { + mRange.mMins = mCameraPosition + mSpawnRange.mMins; + mRange.mMaxs = mCameraPosition + mSpawnRange.mMaxs; + + // If Using A Spawn Plane, Increase The Range Box A Bit To Account For Rotation On The Spawn Plane + //------------------------------------------------------------------------------------------------- + if (UseSpawnPlane()) + { + for (int dim=0; dim<3; dim++) + { + if (force[dim]>0.01) + { + mRange.mMins[dim] -= (mSpawnPlaneDistance/2.0f); + } + else if (force[dim]<-0.01) + { + mRange.mMaxs[dim] += (mSpawnPlaneDistance/2.0f); + } + } + mSpawnPlaneNorm = force; + mSpawnSpeed = VectorNormalize(mSpawnPlaneNorm.v); + MakeNormalVectors(mSpawnPlaneNorm.v, mSpawnPlaneRight.v, mSpawnPlaneUp.v); + if (mOrientWithVelocity) + { + mCameraDown = mSpawnPlaneNorm; + mCameraDown *= (mHeight * -1); + } + } + + // Optimization For Quad Position Calculation + //-------------------------------------------- + if (mVertexCount==4) + { + mCameraLeftPlusUp = (mCameraLeft - mCameraDown); + mCameraLeftMinusUp = (mCameraLeft + mCameraDown); + } + else + { + mCameraLeftPlusUp = (mCameraDown + mCameraLeft); // should really be called mCamera Left + Down + } + } + + // Stop All Additional Processing + //-------------------------------- + if (mFrozen) + { + return; + } + + + + // Now Update All Particles + //-------------------------- + mParticleCountRender = 0; + for (particleNum=0; particleNummPosition); // First Time Spawn Location + } + + // Grab The Force And Apply Non Global Wind + //------------------------------------------ + partForce = force; + partForce /= part->mMass; + + + // Apply The Force + //----------------- + part->mVelocity += partForce; + part->mVelocity *= mFrictionInverse; + + part->mPosition.ScaleAdd(part->mVelocity, mSecondsElapsed); + + partToCamera = (part->mPosition - mCameraPosition); + partRendering = part->mFlags.get_bit(CWeatherParticle::FLAG_RENDER); + partOutside = mOutside.PointOutside(part->mPosition, mWidth, mHeight); + partInRange = mRange.In(part->mPosition); + partInView = (partOutside && partInRange && (partToCamera.Dot(mCameraForward)>0.0f)); + + // Process Respawn + //----------------- + if (!partInRange && !partRendering) + { + part->mVelocity.Clear(); + + // Reselect A Position On The Spawn Plane + //---------------------------------------- + if (UseSpawnPlane()) + { + part->mPosition = mCameraPosition; + part->mPosition -= (mSpawnPlaneNorm* mSpawnPlaneDistance); + part->mPosition += (mSpawnPlaneRight*WE_flrand(-mSpawnPlaneSize, mSpawnPlaneSize)); + part->mPosition += (mSpawnPlaneUp* WE_flrand(-mSpawnPlaneSize, mSpawnPlaneSize)); + } + + // Otherwise, Just Wrap Around To The Other End Of The Range + //----------------------------------------------------------- + else + { + mRange.Wrap(part->mPosition, mSpawnRange); + } + partInRange = true; + } + + // Process Fade + //-------------- + { + // Start A Fade Out + //------------------ + if (partRendering && !partInView) + { + part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEIN); + part->mFlags.set_bit(CWeatherParticle::FLAG_FADEOUT); + } + + // Switch From Fade Out To Fade In + //--------------------------------- + else if (partRendering && partInView && part->mFlags.get_bit(CWeatherParticle::FLAG_FADEOUT)) + { + part->mFlags.set_bit(CWeatherParticle::FLAG_FADEIN); + part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEOUT); + } + + // Start A Fade In + //----------------- + else if (!partRendering && partInView) + { + partRendering = true; + part->mAlpha = 0.0f; + part->mFlags.set_bit(CWeatherParticle::FLAG_RENDER); + part->mFlags.set_bit(CWeatherParticle::FLAG_FADEIN); + part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEOUT); + } + + // Update Fade + //------------- + if (partRendering) + { + + // Update Fade Out + //----------------- + if (part->mFlags.get_bit(CWeatherParticle::FLAG_FADEOUT)) + { + part->mAlpha -= particleFade; + if (part->mAlpha<=0.0f) + { + part->mAlpha = 0.0f; + part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEOUT); + part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEIN); + part->mFlags.clear_bit(CWeatherParticle::FLAG_RENDER); + partRendering = false; + } + } + + // Update Fade In + //---------------- + else if (part->mFlags.get_bit(CWeatherParticle::FLAG_FADEIN)) + { + part->mAlpha += particleFade; + if (part->mAlpha>=mColor[3]) + { + part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEIN); + part->mAlpha = mColor[3]; + } + } + } + } + + // Keep Track Of The Number Of Particles To Render + //------------------------------------------------- + if (part->mFlags.get_bit(CWeatherParticle::FLAG_RENDER)) + { + mParticleCountRender ++; + } + + + + + + } + mPopulated = true; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Render - + //////////////////////////////////////////////////////////////////////////////////// + void Render() + { + CWeatherParticle* part=0; + int particleNum; + + + // Set The GL State And Image Binding + //------------------------------------ + GL_State((mBlendMode==0)?(GLS_ALPHA):(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE)); + GL_Bind(mImage); + + + // Enable And Disable Things + //--------------------------- + /* + if (mGLModeEnum==GL_POINTS && qglPointParameteriNV) + { + qglEnable(GL_POINT_SPRITE_NV); + + qglPointSize(mWidth); + qglPointParameterfEXT( GL_POINT_SIZE_MIN_EXT, 4.0f ); + qglPointParameterfEXT( GL_POINT_SIZE_MAX_EXT, 2047.0f ); + + qglTexEnvi(GL_POINT_SPRITE_NV, GL_COORD_REPLACE_NV, GL_TRUE); + } + else + */ + //FIXME use this extension? + const float attenuation[3] = + { + 1, 0.0, 0.0004 + }; +#ifdef _XBOX + if (mGLModeEnum==GL_POINTS) + { + pointBegin(mParticleCountRender, mWidth); + } +#else + if (mGLModeEnum == GL_POINTS && qglPointParameterfEXT) + { //fixme use custom parameters but gotta make sure it expects them on same scale first + qglPointSize(10.0); + qglPointParameterfEXT(GL_POINT_SIZE_MIN_EXT, 1.0); + qglPointParameterfEXT(GL_POINT_SIZE_MAX_EXT, 4.0); + qglPointParameterfvEXT(GL_DISTANCE_ATTENUATION_EXT, (float *)attenuation); + } +#endif + else + { + qglEnable(GL_TEXTURE_2D); + //qglDisable(GL_CULL_FACE); + //naughty, you are making the assumption that culling is on when you get here. -rww + GL_Cull(CT_TWO_SIDED); + + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mFilterMode==0)?(GL_LINEAR):(GL_NEAREST)); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (mFilterMode==0)?(GL_LINEAR):(GL_NEAREST)); + + + // Setup Matrix Mode And Translation + //----------------------------------- + qglMatrixMode(GL_MODELVIEW); + qglPushMatrix(); + +#ifdef _XBOX + qglBeginEXT(mGLModeEnum, mParticleCountRender*mVertexCount, mParticleCountRender, 0, mParticleCountRender*mVertexCount, 0); +#endif + } + + // Begin + //------- +#ifndef _XBOX + qglBegin(mGLModeEnum); +#endif + for (particleNum=0; particleNummFlags.get_bit(CWeatherParticle::FLAG_RENDER)) + { + continue; + } + + // Blend Mode Zero -> Apply Alpha Just To Alpha Channel + //------------------------------------------------------ + if (mBlendMode==0) + { + qglColor4f(mColor[0], mColor[1], mColor[2], part->mAlpha); + } + + // Otherwise Apply Alpha To All Channels + //--------------------------------------- + else + { + qglColor4f(mColor[0]*part->mAlpha, mColor[1]*part->mAlpha, mColor[2]*part->mAlpha, mColor[3]*part->mAlpha); + } + + // Render A Point + //---------------- + if (mGLModeEnum==GL_POINTS) + { + qglVertex3fv(part->mPosition.v); + } + + // Render A Triangle + //------------------- + else if (mVertexCount==3) + { + qglTexCoord2f(1.0, 0.0); + qglVertex3f(part->mPosition[0], + part->mPosition[1], + part->mPosition[2]); + + qglTexCoord2f(0.0, 1.0); + qglVertex3f(part->mPosition[0] + mCameraLeft[0], + part->mPosition[1] + mCameraLeft[1], + part->mPosition[2] + mCameraLeft[2]); + + qglTexCoord2f(0.0, 0.0); + qglVertex3f(part->mPosition[0] + mCameraLeftPlusUp[0], + part->mPosition[1] + mCameraLeftPlusUp[1], + part->mPosition[2] + mCameraLeftPlusUp[2]); + } + + // Render A Quad + //--------------- + else + { + // Left bottom. + qglTexCoord2f( 0.0, 0.0 ); + qglVertex3f(part->mPosition[0] - mCameraLeftMinusUp[0], + part->mPosition[1] - mCameraLeftMinusUp[1], + part->mPosition[2] - mCameraLeftMinusUp[2] ); + + // Right bottom. + qglTexCoord2f( 1.0, 0.0 ); + qglVertex3f(part->mPosition[0] - mCameraLeftPlusUp[0], + part->mPosition[1] - mCameraLeftPlusUp[1], + part->mPosition[2] - mCameraLeftPlusUp[2] ); + + // Right top. + qglTexCoord2f( 1.0, 1.0 ); + qglVertex3f(part->mPosition[0] + mCameraLeftMinusUp[0], + part->mPosition[1] + mCameraLeftMinusUp[1], + part->mPosition[2] + mCameraLeftMinusUp[2] ); + + // Left top. + qglTexCoord2f( 0.0, 1.0 ); + qglVertex3f(part->mPosition[0] + mCameraLeftPlusUp[0], + part->mPosition[1] + mCameraLeftPlusUp[1], + part->mPosition[2] + mCameraLeftPlusUp[2] ); + } + } + qglEnd(); + + if (mGLModeEnum==GL_POINTS) + { +#ifdef _XBOX + pointEnd(); +#else + //qglDisable(GL_POINT_SPRITE_NV); + //qglTexEnvi(GL_POINT_SPRITE_NV, GL_COORD_REPLACE_NV, GL_FALSE); +#endif + } + else + { + //qglEnable(GL_CULL_FACE); + //you don't need to do this when you are properly setting cull state. + qglPopMatrix(); + } + + mParticlesRendered += mParticleCountRender; + } +}; +ratl::vector_vs mParticleClouds; + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Init World Effects - Will Iterate Over All Particle Clouds, Clear Them Out, And Erase +//////////////////////////////////////////////////////////////////////////////////////// +void R_InitWorldEffects(void) +{ + srand(Com_Milliseconds()); + + for (int i=0; i1000.0f) + { + mMillisecondsElapsed = 1000.0f; + } + mSecondsElapsed = (mMillisecondsElapsed / 1000.0f); + + + // Make Sure We Are Always Outside Cached + //---------------------------------------- + if (!mOutside.Initialized()) + { + mOutside.Cache(); + } + else + { + // Update All Wind Zones + //----------------------- + if (!mFrozen) + { + mGlobalWindVelocity.Clear(); + for (int wz=0; wz= 1020) +#pragma once +#endif +#if !defined __TR_WORLDEFFECTS_H +#define __TR_WORLDEFFECTS_H + +class CWorldEffectsSystem; + + + +#define PARTICLE_FLAG_RENDER 0x00000001 + +struct SParticle +{ + vec3_t pos; + vec3_t velocity; + unsigned flags; +}; + + + +class CWorldEffect +{ +protected: + CWorldEffect *mNext, *mSlave, *mOwner; + bool mEnabled, mIsSlave; + +public: + enum + { + WORLDEFFECT_ENABLED = 0, + WORLDEFFECT_PARTICLES, + WORLDEFFECT_PARTICLE_COUNT, + + WORLDEFFECT_END + }; + +public: + CWorldEffect(CWorldEffect *owner = 0); + virtual ~CWorldEffect(void); + + void SetNext(CWorldEffect *next) { mNext = next; } + CWorldEffect *GetNext(void) { return mNext; } + void SetSlave(CWorldEffect *slave) { mSlave = slave; } + CWorldEffect *GetSlave(void) { return mSlave; } + void AddSlave(CWorldEffect *slave); + + void SetIsSlave(bool isSlave) { mIsSlave = isSlave; } + void SetOwner(CWorldEffect *owner) { mOwner = owner; } + + virtual bool Command(const char *command); + + virtual void ParmUpdate(CWorldEffectsSystem *system, int which); + virtual void ParmUpdate(CWorldEffect *effect, int which); + virtual void SetVariable(int which, bool newValue, bool doSlave = false); + virtual void SetVariable(int which, float newValue, bool doSlave = false); + virtual void SetVariable(int which, int newValue, bool doSlave = false); + virtual void SetVariable(int which, vec3_t newValue, bool doSlave = false); + + virtual int GetIntVariable(int which) { return 0; } + virtual SParticle *GetParticleVariable(int which) { return 0; } + + virtual void Update(CWorldEffectsSystem *system, float elapseTime); + virtual void Render(CWorldEffectsSystem *system); +}; + + + +class CWorldEffectsSystem +{ +protected: + CWorldEffect *mList, *mLast; + +public: + CWorldEffectsSystem(void); + virtual ~CWorldEffectsSystem(void); + + void AddWorldEffect(CWorldEffect *effect); + + virtual int GetIntVariable(int which) { return 0; } + virtual SParticle *GetParticleVariable(int which) { return 0; } + virtual float GetFloatVariable(int which) { return 0.0; } + virtual float *GetVecVariable(int which) { return 0; } + + virtual bool Command(const char *command); + + virtual void Update(float elapseTime); + virtual void ParmUpdate(int which); + virtual void Render(void); +}; + + +void R_InitWorldEffects(void); +void R_ShutdownWorldEffects(void); +void RB_RenderWorldEffects(void); + +void R_WorldEffectCommand(const char *command); +void R_WorldEffect_f(void); + +bool R_GetWindVector(vec3_t windVector); +bool R_GetWindSpeed(float &windSpeed); + +bool R_IsRaining(); +//bool R_IsSnowing(); +bool R_IsPuffing(); +void R_AddWeatherZone(vec3_t mins, vec3_t maxs); + +#endif // __TR_WORLDEFFECTS_H diff --git a/codemp/rmg/rm_area.cpp b/codemp/rmg/rm_area.cpp new file mode 100644 index 0000000..aad4295 --- /dev/null +++ b/codemp/rmg/rm_area.cpp @@ -0,0 +1,478 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/************************************************************************************************ + * + * Copyright (C) 2001-2002 Raven Software + * + * RM_Area.cpp + * + ************************************************************************************************/ + +#include "RM_Headers.h" + +#ifdef _WIN32 +#pragma optimize("p", on) +#endif + +/************************************************************************************************ + * CRMArea::CRMArea + * constructor + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMArea::CRMArea ( + float spacingRadius, + float paddingSize, + float confineRadius, + vec3_t confineOrigin, + vec3_t lookAtOrigin, + bool flatten, + int symmetric + ) +{ + mMoveCount = 0; + mAngle = 0; + mCollision = true; + mConfineRadius = confineRadius; + mPaddingSize = paddingSize; + mSpacingRadius = spacingRadius; + mFlatten = flatten; + mLookAt = true; + mLockOrigin = false; + mSymmetric = symmetric; + mRadius = spacingRadius; + + VectorCopy ( confineOrigin, mConfineOrigin ); + VectorCopy ( lookAtOrigin, mLookAtOrigin ); +} + +/************************************************************************************************ + * CRMArea::LookAt + * Angle the area towards the given point + * + * inputs: + * lookat - the origin to look at + * + * return: + * the angle in radians that was calculated + * + ************************************************************************************************/ +float CRMArea::LookAt ( vec3_t lookat ) +{ + if (mLookAt) + { // this area orients itself towards a point + vec3_t a; + + VectorCopy ( lookat, mLookAtOrigin ); + VectorSubtract ( lookat, mOrigin, a ); + + mAngle = atan2 ( a[1], a[0] ); + } + + return mAngle; +} + +/************************************************************************************************ + * CRMArea::Mirror + * Mirrors the area to the other side of the map. This includes mirroring the confine origin + * and lookat origin + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +void CRMArea::Mirror ( void ) +{ + mOrigin[0] = -mOrigin[0]; + mOrigin[1] = -mOrigin[1]; + + mConfineOrigin[0] = -mConfineOrigin[0]; + mConfineOrigin[1] = -mConfineOrigin[1]; + + mLookAtOrigin[0] = -mLookAtOrigin[0]; + mLookAtOrigin[1] = -mLookAtOrigin[1]; +} + +/************************************************************************************************ + * CRMAreaManager::CRMAreaManager + * constructor + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMAreaManager::CRMAreaManager ( const vec3_t mins, const vec3_t maxs) +{ + VectorCopy ( mins, mMins ); + VectorCopy ( maxs, mMaxs ); + + mWidth = mMaxs[0] - mMins[0]; + mHeight = mMaxs[1] - mMins[1]; +} + +/************************************************************************************************ + * CRMAreaManager::~CRMAreaManager + * Removes all managed areas + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMAreaManager::~CRMAreaManager ( ) +{ + int i; + + for ( i = mAreas.size() - 1; i >=0; i -- ) + { + delete mAreas[i]; + } + + mAreas.clear(); +} + +/************************************************************************************************ + * CRMAreaManager::MoveArea + * Moves an area within the area manager thus shifting any other areas as needed + * + * inputs: + * area - area to be moved + * origin - new origin to attempt to move to + * + * return: + * none + * + ************************************************************************************************/ +void CRMAreaManager::MoveArea ( CRMArea* movedArea, vec3_t origin) +{ + int index; + int size; + + // Increment the addcount (this is for infinite protection) + movedArea->AddMoveCount (); + + // Infinite recursion prevention + if ( movedArea->GetMoveCount() > 250 ) + { +// assert ( 0 ); + movedArea->EnableCollision ( false ); + return; + } + + // First set the area's origin, This may cause it to be in collision with + // another area but that will get fixed later + movedArea->SetOrigin ( origin ); + + // when symmetric we want to ensure that no instances end up on the "other" side of the imaginary diaganol that cuts the map in two + // mSymmetric tells us which side of the map is legal + if ( movedArea->GetSymmetric ( ) ) + { + const vec3pair_t& bounds = TheRandomMissionManager->GetLandScape()->GetBounds(); + + vec3_t point; + vec3_t dir; + vec3_t tang; + bool push; + float len; + + VectorSubtract( movedArea->GetOrigin(), bounds[0], point ); + VectorSubtract( bounds[1], bounds[0], dir ); + VectorNormalize(dir); + + dir[2] = 0; + point[2] = 0; + VectorMA( bounds[0], DotProduct(point, dir), dir, tang ); + VectorSubtract ( movedArea->GetOrigin(), tang, dir ); + + dir[2] = 0; + push = false; + len = VectorNormalize(dir); + + if ( len < movedArea->GetRadius ( ) ) + { + if ( movedArea->GetLockOrigin ( ) ) + { + movedArea->EnableCollision ( false ); + return; + } + + VectorMA ( point, (movedArea->GetSpacingRadius() - len) + TheRandomMissionManager->GetLandScape()->irand(10,movedArea->GetSpacingRadius()), dir, point ); + origin[0] = point[0] + bounds[0][0]; + origin[1] = point[1] + bounds[0][1]; + movedArea->SetOrigin ( origin ); + } + + switch ( movedArea->GetSymmetric ( ) ) + { + case SYMMETRY_TOPLEFT: + if ( origin[1] > origin[0] ) + { + movedArea->Mirror ( ); + } + break; + + case SYMMETRY_BOTTOMRIGHT: + if ( origin[1] < origin[0] ) + { + movedArea->Mirror ( ); + } + + break; + + default: + // unknown symmetry type + assert ( 0 ); + break; + } + } + + // Confine to area unless we are being pushed back by the same guy who pushed us last time (infinite loop) + if ( movedArea->GetConfineRadius() ) + { + if ( movedArea->GetMoveCount() < 25 ) + { + vec3_t cdiff; + float cdist; + + VectorSubtract ( movedArea->GetOrigin(), movedArea->GetConfineOrigin(), cdiff ); + cdiff[2] = 0; + cdist = VectorLength ( cdiff ); + + if ( cdist + movedArea->GetSpacingRadius() > movedArea->GetConfineRadius() ) + { + cdist = movedArea->GetConfineRadius() - movedArea->GetSpacingRadius(); + VectorNormalize ( cdiff ); + + VectorMA ( movedArea->GetConfineOrigin(), cdist, cdiff, movedArea->GetOrigin()); + } + } + else + { + index = 0; + } + } + + // See if it fell off the world in the x direction + if ( movedArea->GetOrigin()[0] + movedArea->GetSpacingRadius() > mMaxs[0] ) + movedArea->GetOrigin()[0] = mMaxs[0] - movedArea->GetSpacingRadius() - (TheRandomMissionManager->GetLandScape()->irand(10,200)); + else if ( movedArea->GetOrigin()[0] - movedArea->GetSpacingRadius() < mMins[0] ) + movedArea->GetOrigin()[0] = mMins[0] + movedArea->GetSpacingRadius() + (TheRandomMissionManager->GetLandScape()->irand(10,200)); + + // See if it fell off the world in the y direction + if ( movedArea->GetOrigin()[1] + movedArea->GetSpacingRadius() > mMaxs[1] ) + movedArea->GetOrigin()[1] = mMaxs[1] - movedArea->GetSpacingRadius() - (TheRandomMissionManager->GetLandScape()->irand(10,200)); + else if ( movedArea->GetOrigin()[1] - movedArea->GetSpacingRadius() < mMins[1] ) + movedArea->GetOrigin()[1] = mMins[1] + movedArea->GetSpacingRadius() + (TheRandomMissionManager->GetLandScape()->irand(10,200)); + + // Look at what we need to look at + movedArea->LookAt ( movedArea->GetLookAtOrigin() ); + + // Dont collide against things that have no collision +// if ( !movedArea->IsCollisionEnabled ( ) ) +// { +// return; +// } + + // See if its colliding + for(index = 0, size = mAreas.size(); index < size; index ++ ) + { + CRMArea *area = mAreas[index]; + vec3_t diff; + vec3_t newOrigin; + float dist; + float targetdist; + + // Skip the one that was moved in the first place + if ( area == movedArea ) + { + continue; + } + + if ( area->GetLockOrigin ( ) && movedArea->GetLockOrigin( ) ) + { + continue; + } + + // Dont collide against things that have no collision + if ( !area->IsCollisionEnabled ( ) ) + { + continue; + } + + // Grab the distance between the two + // only want the horizontal distance -- dmv + //dist = Distance ( movedArea->GetOrigin ( ), area->GetOrigin ( )); + vec3_t maOrigin; + vec3_t aOrigin; + VectorCopy(movedArea->GetOrigin(), maOrigin); + VectorCopy(area->GetOrigin(), aOrigin); + maOrigin[2] = aOrigin[2] = 0; + dist = Distance ( maOrigin, aOrigin ); + targetdist = movedArea->GetSpacingRadius() + area->GetSpacingRadius() + maximum(movedArea->GetPaddingSize(),area->GetPaddingSize()); + + if ( dist == 0 ) + { + area->GetOrigin()[0] += (50 * (float)(TheRandomMissionManager->GetLandScape()->irand(0,99))/100.0f); + area->GetOrigin()[1] += (50 * (float)(TheRandomMissionManager->GetLandScape()->irand(0,99))/100.0f); + + VectorCopy(area->GetOrigin(), aOrigin); + aOrigin[2] = 0; + + dist = Distance ( maOrigin, aOrigin ); + } + + // Are they are enough apart? + if ( dist >= targetdist ) + { + continue; + } + + // Dont move a step if locked + if ( area->GetLockOrigin ( ) ) + { + MoveArea ( area, area->GetOrigin ( ) ); + continue; + } + + // we got a collision, move the guy we hit + VectorSubtract ( area->GetOrigin(), movedArea->GetOrigin(), diff ); + diff[2] = 0; + VectorNormalize ( diff ); + + // Push by the difference in the distance and no-collide radius + VectorMA ( area->GetOrigin(), targetdist - dist + 1 , diff, newOrigin ); + + // Move the area now + MoveArea ( area, newOrigin ); + } +} + +/************************************************************************************************ + * CRMAreaManager::CreateArea + * Creates an area and adds it to the list of managed areas + * + * inputs: + * none + * + * return: + * a pointer to the newly added area class + * + ************************************************************************************************/ +CRMArea* CRMAreaManager::CreateArea ( + vec3_t origin, + float spacingRadius, + int spacingLine, + float paddingSize, + float confineRadius, + vec3_t confineOrigin, + vec3_t lookAtOrigin, + bool flatten, + bool collide, + bool lockorigin, + int symmetric + ) +{ + CRMArea* area = new CRMArea ( spacingRadius, paddingSize, confineRadius, confineOrigin, lookAtOrigin, flatten, symmetric ); + + if ( lockorigin || spacingLine ) + { + area->LockOrigin ( ); + } + + if (origin[0] != lookAtOrigin[0] || origin[1] != lookAtOrigin[1]) + area->EnableLookAt(true); + + // First add the area to the list + mAreas.push_back ( area ); + + area->EnableCollision(collide); + + // Set the real radius which is used for center line detection + if ( spacingLine ) + { + area->SetRadius ( spacingRadius + (spacingLine - 1) * spacingRadius ); + } + + // Now move the area around + MoveArea ( area, origin ); + + if ( (origin[0] != lookAtOrigin[0] || origin[1] != lookAtOrigin[1]) ) + { + int i; + vec3_t linedir; + vec3_t dir; + vec3_t up = {0,0,1}; + + VectorSubtract ( lookAtOrigin, origin, dir ); + VectorNormalize ( dir ); + dir[2] = 0; + CrossProduct ( dir, up, linedir ); + + for ( i = 0; i < spacingLine - 1; i ++ ) + { + CRMArea* linearea; + vec3_t lineorigin; + + linearea = new CRMArea ( spacingRadius, paddingSize, 0, vec3_origin, vec3_origin, false, symmetric ); + linearea->LockOrigin ( ); + linearea->EnableCollision(collide); + + VectorMA ( origin, spacingRadius + (spacingRadius * 2 * i), linedir, lineorigin ); + mAreas.push_back ( linearea ); + MoveArea ( linearea, lineorigin ); + + linearea = new CRMArea ( spacingRadius, paddingSize, 0, vec3_origin, vec3_origin, false, symmetric ); + linearea->LockOrigin ( ); + linearea->EnableCollision(collide); + + VectorMA ( origin, -spacingRadius - (spacingRadius * 2 * i), linedir, lineorigin ); + mAreas.push_back ( linearea ); + MoveArea ( linearea, lineorigin ); + } + } + + // Return it for convienience + return area; +} + +/************************************************************************************************ + * CRMAreaManager::EnumArea + * Allows for enumeration through the area list. If an invalid index is given then NULL will + * be returned; + * + * inputs: + * index - current enumeration index + * + * return: + * requested area class pointer or NULL if the index was invalid + * + ************************************************************************************************/ +CRMArea* CRMAreaManager::EnumArea ( const int index ) +{ + // This isnt an assertion case because there is no size method for + // the area manager so the areas are enumerated until NULL is returned. + if ( index < 0 || index >= mAreas.size ( ) ) + { + return NULL; + } + + return mAreas[index]; +} + +#ifdef _WIN32 +#pragma optimize("p", off) +#endif diff --git a/codemp/rmg/rm_area.h b/codemp/rmg/rm_area.h new file mode 100644 index 0000000..c7391a4 --- /dev/null +++ b/codemp/rmg/rm_area.h @@ -0,0 +1,99 @@ +/************************************************************************************************ + * + * Copyright (C) 2001-2002 Raven Software + * + * RM_Area.h + * + ************************************************************************************************/ + +#pragma once +#if !defined(RM_AREA_H_INC) +#define RM_AREA_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Area.h") +#endif + +class CRMArea +{ +private: + + float mPaddingSize; + float mSpacingRadius; + float mConfineRadius; + float mRadius; + float mAngle; + int mMoveCount; + vec3_t mOrigin; + vec3_t mConfineOrigin; + vec3_t mLookAtOrigin; + bool mCollision; + bool mFlatten; + bool mLookAt; + bool mLockOrigin; + int mSymmetric; + +public: + + CRMArea ( float spacing, float padding, float confine, vec3_t confineOrigin, vec3_t lookAtOrigin, bool flatten = true, int symmetric = 0 ); + + void Mirror ( void ); + + void SetOrigin(vec3_t origin) { VectorCopy ( origin, mOrigin ); } + void SetAngle(float angle) { mAngle = angle; } + void SetSymmetric(int sym) { mSymmetric = sym; } + + void EnableCollision(bool e) { mCollision = e; } + void EnableLookAt(bool la) {mLookAt = la; } + + float LookAt(vec3_t lookat); + void LockOrigin( void ) { mLockOrigin = true; } + + void AddMoveCount() { mMoveCount++; } + void ClearMoveCount() { mMoveCount=0; } + + float GetPaddingSize() { return mPaddingSize; } + float GetSpacingRadius() { return mSpacingRadius; } + float GetRadius() { return mRadius; } + float GetConfineRadius() { return mConfineRadius; } + float GetAngle() { return mAngle; } + int GetMoveCount() { return mMoveCount; } + vec_t* GetOrigin() { return mOrigin; } + vec_t* GetConfineOrigin() { return mConfineOrigin; } + vec_t* GetLookAtOrigin() { return mLookAtOrigin; } + bool GetLookAt() { return mLookAt;} + bool GetLockOrigin() { return mLockOrigin; } + int GetSymmetric() { return mSymmetric; } + + void SetRadius(float r) { mRadius = r; } + + bool IsCollisionEnabled(){ return mCollision; } + bool IsFlattened (){ return mFlatten; } +}; + +typedef vector rmAreaVector_t; + +class CRMAreaManager +{ +private: + + rmAreaVector_t mAreas; + vec3_t mMins; + vec3_t mMaxs; + float mWidth; + float mHeight; + +public: + + CRMAreaManager ( const vec3_t mins, const vec3_t maxs ); + ~CRMAreaManager ( ); + + CRMArea* CreateArea ( vec3_t origin, float spacing, int spacingline, float padding, float confine, vec3_t confineOrigin, vec3_t lookAtOrigin, bool flatten=true, bool collide=true, bool lockorigin=false, int symmetric=0); + void MoveArea ( CRMArea* area, vec3_t origin); + CRMArea* EnumArea ( const int index ); + +// void CreateMap ( void ); +}; + +#endif + diff --git a/codemp/rmg/rm_headers.h b/codemp/rmg/rm_headers.h new file mode 100644 index 0000000..38a971d --- /dev/null +++ b/codemp/rmg/rm_headers.h @@ -0,0 +1,73 @@ +#pragma once +#if !defined(RM_HEADERS_H_INC) +#define RM_HEADERS_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Headers.h") +#endif + +#pragma warning (push, 3) +#include +#include +#pragma warning (pop) + +using namespace std; + +#if !defined(GENERICPARSER2_H_INC) +#include "../qcommon/GenericParser2.h" +#endif + +#if !defined(CM_LOCAL_H_INC) +#include "../qcommon/cm_local.h" +#endif + +#include "../client/client.h" + +#define MAX_INSTANCE_TRIES 5 + +// on a symmetric map which corner is the first node +typedef enum +{ + SYMMETRY_NONE, + SYMMETRY_TOPLEFT, + SYMMETRY_BOTTOMRIGHT + +} symmetry_t; + +#if !defined(CM_TERRAINMAP_H_INC) + #include "../qcommon/cm_terrainmap.h" +#endif + +#if !defined(RM_AREA_H_INC) + #include "RM_Area.h" +#endif + +#if !defined(RM_PATH_H_INC) + #include "RM_Path.h" +#endif + +#if !defined(RM_OBJECTIVE_H_INC) + #include "RM_Objective.h" +#endif + +#if !defined(RM_INSTANCEFILE_H_INC) + #include "RM_InstanceFile.h" +#endif + +#if !defined(RM_INSTANCE_H_INC) + #include "RM_Instance.h" +#endif + +#if !defined(RM_MISSION_H_INC) + #include "RM_Mission.h" +#endif + +#if !defined(RM_MANAGER_H_INC) + #include "RM_Manager.h" +#endif + +#if !defined(RM_TERRAIN_H_INC) + #include "RM_Terrain.h" +#endif + +#endif diff --git a/codemp/rmg/rm_instance.cpp b/codemp/rmg/rm_instance.cpp new file mode 100644 index 0000000..1ddabc0 --- /dev/null +++ b/codemp/rmg/rm_instance.cpp @@ -0,0 +1,195 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "RM_Headers.h" +#include "../qcommon/cm_terrainmap.h" + +/************************************************************************************************ + * CRMInstance::CRMInstance + * constructs a instnace object using the given parser group + * + * inputs: + * instance: parser group containing information about the instance + * + * return: + * none + * + ************************************************************************************************/ +CRMInstance::CRMInstance ( CGPGroup *instGroup, CRMInstanceFile& instFile ) +{ + mObjective = NULL; + mSpacingRadius = 0; + mFlattenRadius = 0; + mFilter[0] = mTeamFilter[0] = 0; + mArea = NULL; + mAutomapSymbol = 0; + mEntityID = 0; + mSide = 0; + mMirror = 0; + mFlattenHeight = 66; + mSpacingLine = 0; + mSurfaceSprites = true; + mLockOrigin = false; +} + +/************************************************************************************************ + * CRMInstance::PreSpawn + * Prepares the instance for spawning by flattening the ground under it + * + * inputs: + * landscape: landscape the instance will be spawned on + * + * return: + * true: spawn preparation successful + * false: spawn preparation failed + * + ************************************************************************************************/ +bool CRMInstance::PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + vec3_t origin; + CArea area; + + VectorCopy(GetOrigin(), origin); + + if (mMirror) + { + origin[0] = TheRandomMissionManager->GetLandScape()->GetBounds()[0][0] + TheRandomMissionManager->GetLandScape()->GetBounds()[1][0] - origin[0]; + origin[1] = TheRandomMissionManager->GetLandScape()->GetBounds()[0][1] + TheRandomMissionManager->GetLandScape()->GetBounds()[1][1] - origin[1]; + } + + const vec3_t& terxelSize = terrain->GetLandScape()->GetTerxelSize ( ); + const vec3pair_t& bounds = terrain->GetLandScape()->GetBounds(); + + // Align the instance to the center of a terxel + origin[0] = bounds[0][0] + (int)((origin[0] - bounds[0][0] + terxelSize[0] / 2) / terxelSize[0]) * terxelSize[0]; + origin[1] = bounds[0][1] + (int)((origin[1] - bounds[0][1] + terxelSize[1] / 2) / terxelSize[1]) * terxelSize[1]; + + + // This is BAD - By copying the mirrored origin back into the instance, you've now mirrored the original instance + // so when anything from this point on looks at the instance they'll be looking at a mirrored version but will be expecting the original + // so later in the spawn functions the instance will be re-mirrored, because it thinks the mInstances have not been changed +// VectorCopy(origin, GetOrigin()); + + // Flatten the area below the instance + if ( GetFlattenRadius() ) + { + area.Init( origin, GetFlattenRadius(), 0.0f, AT_NONE, 0, 0 ); + terrain->GetLandScape()->FlattenArea( &area, mFlattenHeight | (mSurfaceSprites?0:0x80), false, true, true ); + } + + return true; +} + +/************************************************************************************************ + * CRMInstance::PostSpawn + * Finishes the spawn by linking any objectives into the world that are associated with it + * + * inputs: + * landscape: landscape the instance was spawned on + * + * return: + * true: post spawn successfull + * false: post spawn failed + * + ************************************************************************************************/ +bool CRMInstance::PostSpawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + if ( mObjective ) + { + return mObjective->Link ( ); + } + + return true; +} + +void CRMInstance::DrawAutomapSymbol() +{ + TheRandomMissionManager->AddAutomapSymbol ( GetAutomapSymbol(), GetOrigin(), GetSide ( ) ); +/* + // draw proper symbol on map for instance + switch (GetAutomapSymbol()) + { + default: + case AUTOMAP_NONE: + if (HasObjective()) + CM_TM_AddObjective(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + case AUTOMAP_BLD: + CM_TM_AddBuilding(GetOrigin()[0], GetOrigin()[1], GetSide()); + if (HasObjective()) + CM_TM_AddObjective(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + case AUTOMAP_OBJ: + CM_TM_AddObjective(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + case AUTOMAP_START: + CM_TM_AddStart(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + case AUTOMAP_END: + CM_TM_AddEnd(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + case AUTOMAP_ENEMY: + if (HasObjective()) + CM_TM_AddObjective(GetOrigin()[0], GetOrigin()[1]); + if (1 == Cvar_VariableIntegerValue("rmg_automapshowall")) + CM_TM_AddNPC(GetOrigin()[0], GetOrigin()[1], false); + break; + case AUTOMAP_FRIEND: + if (HasObjective()) + CM_TM_AddObjective(GetOrigin()[0], GetOrigin()[1]); + if (1 == Cvar_VariableIntegerValue("rmg_automapshowall")) + CM_TM_AddNPC(GetOrigin()[0], GetOrigin()[1], true); + break; + case AUTOMAP_WALL: + CM_TM_AddWallRect(GetOrigin()[0], GetOrigin()[1], GetSide()); + break; + } +*/ +} + +/************************************************************************************************ + * CRMInstance::Preview + * Renderings debug information about the instance + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +void CRMInstance::Preview ( const vec3_t from ) +{ +/* CEntity *tent; + + // Add a cylindar for the whole settlement + tent = G_TempEntity( GetOrigin(), EV_DEBUG_CYLINDER ); + VectorCopy( GetOrigin(), tent->s.origin2 ); + tent->s.pos.trBase[2] += 40; + tent->s.origin2[2] += 50; + tent->s.time = 1050 + ((int)(GetSpacingRadius())<<16); + tent->s.time2 = GetPreviewColor ( ); + G_AddTempEntity(tent); + + // Origin line + tent = G_TempEntity( GetOrigin ( ), EV_DEBUG_LINE ); + VectorCopy( GetOrigin(), tent->s.origin2 ); + tent->s.origin2[2] += 400; + tent->s.time = 1050; + tent->s.weapon = 10; + tent->s.time2 = (255<<24) + (255<<16) + (255<<8) + 255; + G_AddTempEntity(tent); + + if ( GetFlattenRadius ( ) ) + { + // Add a cylindar for the whole settlement + tent = G_TempEntity( GetOrigin(), EV_DEBUG_CYLINDER ); + VectorCopy( GetOrigin(), tent->s.origin2 ); + tent->s.pos.trBase[2] += 40; + tent->s.origin2[2] += 50; + tent->s.time = 1050 + ((int)(GetFlattenRadius ( ))<<16); + tent->s.time2 = (255<<24) + (80<<16) +(80<<8) + 80; + G_AddTempEntity(tent); + } +*/ +} diff --git a/codemp/rmg/rm_instance.h b/codemp/rmg/rm_instance.h new file mode 100644 index 0000000..21254e8 --- /dev/null +++ b/codemp/rmg/rm_instance.h @@ -0,0 +1,122 @@ +#pragma once +#if !defined(RM_INSTANCE_H_INC) +#define RM_INSTANCE_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Instance.h") +#endif + +#if !defined(CM_LANDSCAPE_H_INC) +#include "../qcommon/cm_landscape.h" +#endif + +enum +{ + AUTOMAP_NONE = 0, + AUTOMAP_BLD = 1, + AUTOMAP_OBJ = 2, + AUTOMAP_START= 3, + AUTOMAP_END = 4, + AUTOMAP_ENEMY= 5, + AUTOMAP_FRIEND=6, + AUTOMAP_WALL=7 +}; + +class CRMInstance +{ +protected: + char mFilter[MAX_QPATH]; // filter of entities inside of this + char mTeamFilter[MAX_QPATH]; // team specific filter + + vec3pair_t mBounds; // Bounding box for instance itself + + CRMArea* mArea; // Position of the instance + + CRMObjective* mObjective; // Objective associated with this instance + + // optional instance specific strings for objective + string mMessage; // message outputed when objective is completed + string mDescription; // description of objective + string mInfo; // more info for objective + + float mSpacingRadius; // Radius to space instances with + float mFlattenRadius; // Radius to flatten under instances + + int mSpacingLine; // Line of spacing radius's, forces locket + bool mLockOrigin; // Origin cant move + + bool mSurfaceSprites; // allow surface sprites under instance? + + int mAutomapSymbol; // show which symbol on automap 0=none + + int mEntityID; // id of entity spawned + int mSide; // blue or red side + int mMirror; // mirror origin, angle + + int mFlattenHeight; // height to flatten land + +public: + + CRMInstance ( CGPGroup* instance, CRMInstanceFile& instFile); + + virtual ~CRMInstance ( ) { } + + virtual bool IsValid ( ) { return true; } + + virtual bool PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ); + virtual bool Spawn ( CRandomTerrain* terrain, qboolean IsServer ) { return false; } + virtual bool PostSpawn ( CRandomTerrain* terrain, qboolean IsServer ); + + virtual void Preview ( const vec3_t from ); + + virtual void SetArea ( CRMAreaManager* amanager, CRMArea* area ) { mArea = area; } + virtual void SetFilter ( const char *filter ) { strcpy(mFilter, filter); } + virtual void SetTeamFilter ( const char *teamFilter ) { strcpy(mTeamFilter, teamFilter); } + void SetObjective ( CRMObjective* obj ) { mObjective = obj; } + CRMObjective* GetObjective (void) {return mObjective;} + bool HasObjective () {return mObjective != NULL;} + int GetAutomapSymbol () {return mAutomapSymbol;} + void DrawAutomapSymbol (); + const char* GetMessage(void) { return mMessage.c_str(); } + const char* GetDescription(void){ return mDescription.c_str(); } + const char* GetInfo(void) { return mInfo.c_str(); } + void SetMessage(const char* msg) { mMessage = msg; } + void SetDescription(const char* desc) { mDescription = desc; } + void SetInfo(const char* info) { mInfo = info; } + void SetSide(int side) {mSide = side;} + int GetSide ( ) {return mSide;} + + // NOTE: should consider making SetMirror also set all other variables that need flipping + // like the origin and Side, etc... Otherwise an Instance may have had it's origin flipped + // but then later will have mMirror set to false, but the origin is still flipped. So any functions + // that look at the instance later will see mMirror set to false, but not realize the origin has ALREADY been flipped + virtual void SetMirror(int mirror) { mMirror = mirror;} + int GetMirror ( ) { return mMirror;} + + virtual bool GetSurfaceSprites ( ) { return mSurfaceSprites; } + + virtual bool GetLockOrigin ( ) { return mLockOrigin; } + virtual int GetSpacingLine ( ) { return mSpacingLine; } + + virtual int GetPreviewColor ( ) { return 0; } + virtual float GetSpacingRadius ( ) { return mSpacingRadius; } + virtual float GetFlattenRadius ( ) { return mFlattenRadius; } + const char *GetFilter ( ) { return mFilter; } + const char *GetTeamFilter ( ) { return mTeamFilter; } + + CRMArea& GetArea ( ) { return *mArea; } + vec_t* GetOrigin ( ) {return mArea->GetOrigin(); } + float GetAngle ( ) {return mArea->GetAngle();} + void SetAngle(float ang ) { mArea->SetAngle(ang);} + const vec3pair_t& GetBounds(void) const { return(mBounds); } + + void SetFlattenHeight ( int height ) { mFlattenHeight = height; } + int GetFlattenHeight ( void ) { return mFlattenHeight; } + + void SetSpacingRadius (float spacing) { mSpacingRadius = spacing; } +}; + +typedef list::iterator rmInstanceIter_t; +typedef list rmInstanceList_t; + +#endif diff --git a/codemp/rmg/rm_instance_bsp.cpp b/codemp/rmg/rm_instance_bsp.cpp new file mode 100644 index 0000000..05faa40 --- /dev/null +++ b/codemp/rmg/rm_instance_bsp.cpp @@ -0,0 +1,282 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/************************************************************************************************ + * + * RM_Instance_BSP.cpp + * + * Implements the CRMBSPInstance class. This class is reponsible for parsing a + * bsp instance as well as spawning it into a landscape. + * + ************************************************************************************************/ + +#include "../qcommon/cm_local.h" +#include "../server/server.h" +#include "RM_Headers.h" + +#include "RM_Instance_BSP.h" + + +/************************************************************************************************ + * CRMBSPInstance::CRMBSPInstance + * constructs a building instance object using the given parser group + * + * inputs: + * instance: parser group containing information about the building instance + * + * return: + * none + * + ************************************************************************************************/ +CRMBSPInstance::CRMBSPInstance(CGPGroup *instGroup, CRMInstanceFile& instFile) : CRMInstance ( instGroup, instFile ) +{ + strcpy(mBsp, instGroup->FindPairValue("file", "")); + + mAngleVariance = DEG2RAD(atof(instGroup->FindPairValue("anglevariance", "0"))); + mBaseAngle = DEG2RAD(atof(instGroup->FindPairValue("baseangle", "0"))); + mAngleDiff = DEG2RAD(atof(instGroup->FindPairValue("anglediff", "0"))); + mSpacingRadius = atof( instGroup->FindPairValue ( "spacing", "100" ) ); + mSpacingLine = atoi( instGroup->FindPairValue ( "spacingline", "0" ) ); + mSurfaceSprites = (!Q_stricmp ( instGroup->FindPairValue ( "surfacesprites", "no" ), "yes")) ? true : false; + mLockOrigin = (!Q_stricmp ( instGroup->FindPairValue ( "lockorigin", "no" ), "yes")) ? true : false; + mFlattenRadius = atof( instGroup->FindPairValue ( "flatten", "0" ) ); + mHoleRadius = atof( instGroup->FindPairValue ( "hole", "0" ) ); + + const char * automapSymName = instGroup->FindPairValue ( "automap_symbol", "building" ); + if (0 == Q_stricmp(automapSymName, "none")) mAutomapSymbol = AUTOMAP_NONE ; + else if (0 == Q_stricmp(automapSymName, "building")) mAutomapSymbol = AUTOMAP_BLD ; + else if (0 == Q_stricmp(automapSymName, "objective")) mAutomapSymbol = AUTOMAP_OBJ ; + else if (0 == Q_stricmp(automapSymName, "start")) mAutomapSymbol = AUTOMAP_START; + else if (0 == Q_stricmp(automapSymName, "end")) mAutomapSymbol = AUTOMAP_END ; + else if (0 == Q_stricmp(automapSymName, "enemy")) mAutomapSymbol = AUTOMAP_ENEMY; + else if (0 == Q_stricmp(automapSymName, "friend")) mAutomapSymbol = AUTOMAP_FRIEND; + else if (0 == Q_stricmp(automapSymName, "wall")) mAutomapSymbol = AUTOMAP_WALL; + else mAutomapSymbol = atoi( automapSymName ); + + // optional instance objective strings + SetMessage(instGroup->FindPairValue("objective_message","")); + SetDescription(instGroup->FindPairValue("objective_description","")); + SetInfo(instGroup->FindPairValue("objective_info","")); + + mBounds[0][0] = 0; + mBounds[0][1] = 0; + mBounds[1][0] = 0; + mBounds[1][1] = 0; + + mBaseAngle += (TheRandomMissionManager->GetLandScape()->irand(0,mAngleVariance) - mAngleVariance/2); +} + +/************************************************************************************************ + * CRMBSPInstance::Spawn + * spawns a bsp into the world using the previously aquired origin + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +bool CRMBSPInstance::Spawn ( CRandomTerrain* terrain, qboolean IsServer) +{ +#ifndef PRE_RELEASE_DEMO +// TEntity* ent; + float yaw; + char temp[10000]; + char *savePtr; + vec3_t origin; + vec3_t notmirrored; + float water_level = terrain->GetLandScape()->GetWaterHeight(); + + const vec3_t& terxelSize = terrain->GetLandScape()->GetTerxelSize ( ); + const vec3pair_t& bounds = terrain->GetLandScape()->GetBounds(); + + // If this entity somehow lost its collision flag then boot it + if ( !GetArea().IsCollisionEnabled ( ) ) + { + return false; + } + + // copy out the unmirrored version + VectorCopy(GetOrigin(), notmirrored); + + // we want to mirror it before determining the Z value just in case the landscape isn't perfectly mirrored + if (mMirror) + { + GetOrigin()[0] = TheRandomMissionManager->GetLandScape()->GetBounds()[0][0] + TheRandomMissionManager->GetLandScape()->GetBounds()[1][0] - GetOrigin()[0]; + GetOrigin()[1] = TheRandomMissionManager->GetLandScape()->GetBounds()[0][1] + TheRandomMissionManager->GetLandScape()->GetBounds()[1][1] - GetOrigin()[1]; + } + + // Align the instance to the center of a terxel + GetOrigin ( )[0] = bounds[0][0] + (int)((GetOrigin ( )[0] - bounds[0][0] + terxelSize[0] / 2) / terxelSize[0]) * terxelSize[0]; + GetOrigin ( )[1] = bounds[0][1] + (int)((GetOrigin ( )[1] - bounds[0][1] + terxelSize[1] / 2) / terxelSize[1]) * terxelSize[1]; + + // Make sure the bsp is resting on the ground, not below or above it + // NOTE: This check is basically saying "is this instance not a bridge", because when instances are created they are all + // placed above the world's Z boundary, EXCEPT FOR BRIDGES. So this call to GetWorldHeight will move all other instances down to + // ground level except bridges + if ( GetOrigin()[2] > terrain->GetBounds()[1][2] ) + { + if( GetFlattenRadius() ) + { + terrain->GetLandScape()->GetWorldHeight ( GetOrigin(), GetBounds ( ), false ); + GetOrigin()[2] += 5; + } + else if (IsServer) + { // if this instance does not flatten the ground around it, do a trace to more accurately determine its Z value + trace_t tr; + vec3_t end; + vec3_t start; + + VectorCopy(GetOrigin(), end); + VectorCopy(GetOrigin(), start); + // start the trace below the top height of the landscape + start[2] = TheRandomMissionManager->GetLandScape()->GetBounds()[1][2] - 1; + // end the trace at the bottom of the world + end[2] = MIN_WORLD_COORD; + + Com_Memset ( &tr, 0, sizeof ( tr ) ); + SV_Trace( &tr, start, vec3_origin, vec3_origin, end, -1, CONTENTS_TERRAIN|CONTENTS_SOLID, qfalse, 0, 10 ); + + if( !(tr.contents & CONTENTS_TERRAIN) || (tr.fraction == 1.0) ) + { + if ( 0 ) + assert(0); // this should never happen + + // restore the unmirrored origin + VectorCopy( notmirrored, GetOrigin() ); + // don't spawn + return false; + } + // assign the Z-value to wherever it hit the terrain + GetOrigin()[2] = tr.endpos[2]; + // lower it a little, otherwise the bottom of the instance might be exposed if on some weird sloped terrain + GetOrigin()[2] -= 16; // FIXME: would it be better to use a number related to the instance itself like 1/5 it's height or something... + } + + } + else + { + terrain->GetLandScape()->GetWorldHeight ( GetOrigin(), GetBounds ( ), true ); + } + + // save away the origin + VectorCopy(GetOrigin(), origin); + // make sure not to spawn if in water + if (!HasObjective() && GetOrigin()[2] < water_level) + return false; + // restore the origin + VectorCopy(origin, GetOrigin()); + + if (mMirror) + { // change blue things to red for symmetric maps + if (strlen(mFilter) > 0) + { + char * blue = strstr(mFilter,"blue"); + if (blue) + { + blue[0] = (char) 0; + strcat(mFilter, "red"); + SetSide(SIDE_RED); + } + } + if (strlen(mTeamFilter) > 0) + { + char * blue = strstr(mTeamFilter,"blue"); + if (blue) + { + strcpy(mTeamFilter, "red"); + SetSide(SIDE_RED); + } + } + yaw = RAD2DEG(mArea->GetAngle() + mBaseAngle) + 180; + } + else + { + yaw = RAD2DEG(mArea->GetAngle() + mBaseAngle); + } + +/* + if( TheRandomMissionManager->GetMission()->GetSymmetric() ) + { + vec3_t diagonal; + vec3_t lineToPoint; + vec3_t mins; + vec3_t maxs; + vec3_t point; + vec3_t vProj; + vec3_t vec; + float distance; + + VectorCopy( TheRandomMissionManager->GetLandScape()->GetBounds()[1], maxs ); + VectorCopy( TheRandomMissionManager->GetLandScape()->GetBounds()[0], mins ); + VectorCopy( GetOrigin(), point ); + mins[2] = maxs[2] = point[2] = 0; + VectorSubtract( point, mins, lineToPoint ); + VectorSubtract( maxs, mins, diagonal); + + + VectorNormalize(diagonal); + VectorMA( mins, DotProduct(lineToPoint, diagonal), diagonal, vProj); + VectorSubtract(point, vProj, vec ); + distance = VectorLength(vec); + + // if an instance is too close to the imaginary diagonal that cuts the world in half, don't spawn it + // otherwise you can get overlapping instances + if( distance < GetSpacingRadius() ) + { +#ifdef _DEBUG + mAutomapSymbol = AUTOMAP_END; +#endif + if( !HasObjective() ) + { + return false; + } + } + } +*/ + + // Spawn in the bsp model + sprintf(temp, + "{\n" + "\"classname\" \"misc_bsp\"\n" + "\"bspmodel\" \"%s\"\n" + "\"origin\" \"%f %f %f\"\n" + "\"angles\" \"0 %f 0\"\n" + "\"filter\" \"%s\"\n" + "\"teamfilter\" \"%s\"\n" + "\"spacing\" \"%d\"\n" + "\"flatten\" \"%d\"\n" + "}\n", + mBsp, + GetOrigin()[0], GetOrigin()[1], GetOrigin()[2], + AngleNormalize360(yaw), + mFilter, + mTeamFilter, + (int)GetSpacingRadius(), + (int)GetFlattenRadius() + ); + + if (IsServer) + { // only allow for true spawning on the server + savePtr = sv.entityParsePoint; + sv.entityParsePoint = temp; + VM_Call( gvm, GAME_SPAWN_RMG_ENTITY ); + sv.entityParsePoint = savePtr; + } + + DrawAutomapSymbol(); + + Com_DPrintf( "RMG: Building '%s' spawned at (%f %f %f)\n", mBsp, GetOrigin()[0], GetOrigin()[1], GetOrigin()[2] ); + // now restore the instances un-mirrored origin + // NOTE: all this origin flipping, setting the side etc... should be done when mMirror is set + // because right after this function is called, mMirror is set to 0 but all the instance data is STILL MIRRORED -- not good + VectorCopy(notmirrored, GetOrigin()); + +#endif // PRE_RELEASE_DEMO + + return true; +} + + + diff --git a/codemp/rmg/rm_instance_bsp.h b/codemp/rmg/rm_instance_bsp.h new file mode 100644 index 0000000..055c3c1 --- /dev/null +++ b/codemp/rmg/rm_instance_bsp.h @@ -0,0 +1,35 @@ +#pragma once +#if !defined(RM_INSTANCE_BSP_H_INC) +#define RM_INSTANCE_BSP_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Instance_BSP.h") +#endif + +class CRMBSPInstance : public CRMInstance +{ +private: + + char mBsp[MAX_QPATH]; + float mAngleVariance; + float mBaseAngle; + float mAngleDiff; + + float mHoleRadius; + +public: + + CRMBSPInstance ( CGPGroup *instance, CRMInstanceFile& instFile ); + + virtual int GetPreviewColor ( ) { return (255<<24)+255; } + + virtual float GetHoleRadius ( ) { return mHoleRadius; } + + virtual bool Spawn ( CRandomTerrain* terrain, qboolean IsServer ); + + const char* GetModelName (void) const { return(mBsp); } + float GetAngleDiff (void) const { return(mAngleDiff); } + bool GetAngularType (void) const { return(mAngleDiff != 0.0f); } +}; + +#endif \ No newline at end of file diff --git a/codemp/rmg/rm_instance_group.cpp b/codemp/rmg/rm_instance_group.cpp new file mode 100644 index 0000000..c45bdd0 --- /dev/null +++ b/codemp/rmg/rm_instance_group.cpp @@ -0,0 +1,344 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/************************************************************************************************ + * + * RM_Instance_Group.cpp + * + * Implements the CRMGroupInstance class. This class is reponsible for parsing a + * group instance as well as spawning it into a landscape. + * + ************************************************************************************************/ + +#include "RM_Headers.h" + +#include "RM_Instance_Group.h" + +/************************************************************************************************ + * CRMGroupInstance::CRMGroupInstance + * constructur + * + * inputs: + * settlementID: ID of the settlement being created + * + * return: + * none + * + ************************************************************************************************/ +CRMGroupInstance::CRMGroupInstance ( CGPGroup *instGroup, CRMInstanceFile& instFile ) + : CRMInstance ( instGroup, instFile ) +{ + // Grab the padding and confine radius + mPaddingSize = atof ( instGroup->FindPairValue ( "padding", va("%i", TheRandomMissionManager->GetMission()->GetDefaultPadding() ) ) ); + mConfineRadius = atof ( instGroup->FindPairValue ( "confine", "0" ) ); + + const char * automapSymName = instGroup->FindPairValue ( "automap_symbol", "none" ); + if (0 == Q_stricmp(automapSymName, "none")) mAutomapSymbol = AUTOMAP_NONE ; + else if (0 == Q_stricmp(automapSymName, "building")) mAutomapSymbol = AUTOMAP_BLD ; + else if (0 == Q_stricmp(automapSymName, "objective")) mAutomapSymbol = AUTOMAP_OBJ ; + else if (0 == Q_stricmp(automapSymName, "start")) mAutomapSymbol = AUTOMAP_START; + else if (0 == Q_stricmp(automapSymName, "end")) mAutomapSymbol = AUTOMAP_END ; + else if (0 == Q_stricmp(automapSymName, "enemy")) mAutomapSymbol = AUTOMAP_ENEMY; + else if (0 == Q_stricmp(automapSymName, "friend")) mAutomapSymbol = AUTOMAP_FRIEND; + else mAutomapSymbol = atoi( automapSymName ); + + // optional instance objective strings + SetMessage(instGroup->FindPairValue("objective_message","")); + SetDescription(instGroup->FindPairValue("objective_description","")); + SetInfo(instGroup->FindPairValue("objective_info","")); + + // Iterate through the sub groups to determine the instances which make up the group + instGroup = instGroup->GetSubGroups ( ); + + while ( instGroup ) + { + CRMInstance* instance; + const char* name; + int mincount; + int maxcount; + int count; + float minrange; + float maxrange; + + // Make sure only instances are specified as sub groups + assert ( 0 == stricmp ( instGroup->GetName ( ), "instance" ) ); + + // Grab the name + name = instGroup->FindPairValue ( "name", "" ); + + // Grab the range information + minrange = atof(instGroup->FindPairValue ( "minrange", "0" ) ); + maxrange = atof(instGroup->FindPairValue ( "maxrange", "0" ) ); + + // Grab the count information and randomly generate a count value + mincount = atoi(instGroup->FindPairValue ( "mincount", "1" ) ); + maxcount = atoi(instGroup->FindPairValue ( "maxcount", "1" ) ); + count = mincount; + + if ( maxcount > mincount ) + { + count += (TheRandomMissionManager->GetLandScape()->irand(0, maxcount-mincount)); + } + + // For each count create and add the instance + for ( ; count ; count -- ) + { + // Create the instance + instance = instFile.CreateInstance ( name ); + + // Skip this instance if it couldnt be created for some reason. The CreateInstance + // method will report an error so no need to do so here. + if ( NULL == instance ) + { + continue; + } + + // Set the min and max range for the instance + instance->SetFilter(mFilter); + instance->SetTeamFilter(mTeamFilter); + + // Add the instance to the list + mInstances.push_back ( instance ); + } + + // Next sub group + instGroup = instGroup->GetNext ( ); + } +} + +/************************************************************************************************ + * CRMGroupInstance::~CRMGroupInstance + * Removes all buildings and inhabitants + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMGroupInstance::~CRMGroupInstance(void) +{ + // Cleanup + RemoveInstances ( ); +} + +/************************************************************************************************ + * CRMGroupInstance::SetFilter + * Sets a filter used to exclude instances + * + * inputs: + * filter: filter name + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::SetFilter( const char *filter ) +{ + rmInstanceIter_t it; + + CRMInstance::SetFilter(filter); + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + (*it)->SetFilter(filter); + } +} + +/************************************************************************************************ + * CRMGroupInstance::SetTeamFilter + * Sets the filter used to exclude team based instances + * + * inputs: + * teamFilter: filter name + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::SetTeamFilter( const char *teamFilter ) +{ + rmInstanceIter_t it; + + CRMInstance::SetTeamFilter(teamFilter); + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + (*it)->SetTeamFilter(teamFilter); + } +} + +/************************************************************************************************ + * CRMGroupInstance::SetMirror + * Sets the flag to mirror an instance on map + * + * inputs: + * mirror + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::SetMirror(int mirror) +{ + rmInstanceIter_t it; + + CRMInstance::SetMirror(mirror); + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + (*it)->SetMirror(mirror); + } +} + + +/************************************************************************************************ + * CRMGroupInstance::RemoveInstances + * Removes all instances associated with the group + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::RemoveInstances ( ) +{ + rmInstanceIter_t it; + + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + delete *it; + } + + mInstances.clear(); +} + +/************************************************************************************************ + * CRMGroupInstance::PreSpawn + * Prepares the group for spawning by + * + * inputs: + * landscape: landscape to calculate the position within + * instance: instance to calculate the position for + * + * return: + * none + * + ************************************************************************************************/ +bool CRMGroupInstance::PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + rmInstanceIter_t it; + + for(it = mInstances.begin(); it != mInstances.end(); it++ ) + { + CRMInstance* instance = *it; + + instance->SetFlattenHeight ( mFlattenHeight ); + + // Add the instance to the landscape now + instance->PreSpawn ( terrain, IsServer ); + } + + return CRMInstance::PreSpawn ( terrain, IsServer ); +} + +/************************************************************************************************ + * CRMGroupInstance::Spawn + * Adds the group instance to the given landscape using the specified origin. All sub instances + * will be added to the landscape within their min and max range from the origin. + * + * inputs: + * landscape: landscape to add the instance group to + * origin: origin of the instance group + * + * return: + * none + * + ************************************************************************************************/ +bool CRMGroupInstance::Spawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + rmInstanceIter_t it; + + // Spawn all the instances associated with this group + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + CRMInstance* instance = *it; + instance->SetSide(GetSide()); // which side owns it? + + // Add the instance to the landscape now + instance->Spawn ( terrain, IsServer ); + } + + DrawAutomapSymbol(); + + return true; +} + +/************************************************************************************************ + * CRMGroupInstance::Preview + * Renders debug information for the instance + * + * inputs: + * from: point to render the preview from + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::Preview ( const vec3_t from ) +{ + rmInstanceIter_t it; + + CRMInstance::Preview ( from ); + + // Render all the instances + for(it = mInstances.begin(); it != mInstances.end(); it++) + { + CRMInstance* instance = *it; + + instance->Preview ( from ); + } +} + +/************************************************************************************************ + * CRMGroupInstance::SetArea + * Overidden to make sure the groups area doesnt eat up any room. The collision on the + * groups area will be turned off + * + * inputs: + * area: area to set + * + * return: + * none + * + ************************************************************************************************/ +void CRMGroupInstance::SetArea ( CRMAreaManager* amanager, CRMArea* area ) +{ + rmInstanceIter_t it; + + bool collide = area->IsCollisionEnabled ( ); + + // Disable collision + area->EnableCollision ( false ); + + // Do what really needs to get done + CRMInstance::SetArea ( amanager, area ); + + // Prepare for spawn by calculating all the positions of the sub instances + // and flattening the ground below them. + for(it = mInstances.begin(); it != mInstances.end(); it++ ) + { + CRMInstance *instance = *it; + CRMArea *newarea; + vec3_t origin; + + // Drop it in the center of the group for now + origin[0] = GetOrigin()[0]; + origin[1] = GetOrigin()[1]; + origin[2] = 2500; + + // Set the area of position + newarea = amanager->CreateArea ( origin, instance->GetSpacingRadius(), instance->GetSpacingLine(), mPaddingSize, mConfineRadius, GetOrigin(), GetOrigin(), instance->GetFlattenRadius()?true:false, collide, instance->GetLockOrigin(), area->GetSymmetric ( ) ); + instance->SetArea ( amanager, newarea ); + } +} diff --git a/codemp/rmg/rm_instance_group.h b/codemp/rmg/rm_instance_group.h new file mode 100644 index 0000000..0620ae4 --- /dev/null +++ b/codemp/rmg/rm_instance_group.h @@ -0,0 +1,41 @@ +#pragma once +#if !defined(RM_INSTANCE_GROUP_H_INC) +#define RM_INSTANCE_GROUP_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Instance_Group.h") +#endif + +class CRMGroupInstance : public CRMInstance +{ +protected: + + rmInstanceList_t mInstances; + float mConfineRadius; + float mPaddingSize; + +public: + + CRMGroupInstance( CGPGroup* instGroup, CRMInstanceFile& instFile); + ~CRMGroupInstance(); + + virtual bool PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ); + virtual bool Spawn ( CRandomTerrain* terrain, qboolean IsServer ); + + virtual void Preview ( const vec3_t from ); + + virtual void SetFilter ( const char *filter ); + virtual void SetTeamFilter ( const char *teamFilter ); + virtual void SetArea ( CRMAreaManager* amanager, CRMArea* area ); + + virtual int GetPreviewColor ( ) { return (255<<24)+(255<<8); } + virtual float GetSpacingRadius ( ) { return 0; } + virtual float GetFlattenRadius ( ) { return 0; } + virtual void SetMirror(int mirror); + +protected: + + void RemoveInstances ( ); +}; + +#endif \ No newline at end of file diff --git a/codemp/rmg/rm_instance_random.cpp b/codemp/rmg/rm_instance_random.cpp new file mode 100644 index 0000000..0075f76 --- /dev/null +++ b/codemp/rmg/rm_instance_random.cpp @@ -0,0 +1,188 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/************************************************************************************************ + * + * RM_Instance_Random.cpp + * + * Implements the CRMRandomInstance class. This class is reponsible for parsing a + * random instance as well as spawning it into a landscape. + * + ************************************************************************************************/ + +#include "RM_Headers.h" + +#include "RM_Instance_Random.h" + +/************************************************************************************************ + * CRMRandomInstance::CRMRandomInstance + * constructs a random instance by choosing one of the sub instances and creating it + * + * inputs: + * instGroup: parser group containing infromation about this instance + * instFile: reference to an open instance file for creating sub instances + * + * return: + * none + * + ************************************************************************************************/ +CRMRandomInstance::CRMRandomInstance ( CGPGroup *instGroup, CRMInstanceFile& instFile ) + : CRMInstance ( instGroup, instFile ) +{ + CGPGroup* group; + CGPGroup* groups[MAX_RANDOM_INSTANCES]; + int numGroups; + + // Build a list of the groups one can be chosen + for ( numGroups = 0, group = instGroup->GetSubGroups ( ); + group; + group = group->GetNext ( ) ) + { + // If this isnt an instance group then skip it + if ( stricmp ( group->GetName ( ), "instance" ) ) + { + continue; + } + + int multiplier = atoi(group->FindPairValue ( "multiplier", "1" )); + for ( ; multiplier > 0 && numGroups < MAX_RANDOM_INSTANCES; multiplier -- ) + { + groups[numGroups++] = group; + } + } + + // No groups, no instance + if ( !numGroups ) + { + // Initialize this now + mInstance = NULL; + + Com_Printf ( "WARNING: No sub instances specified for random instance '%s'\n", group->FindPairValue ( "name", "unknown" ) ); + return; + } + + // Now choose a group to parse + instGroup = groups[TheRandomMissionManager->GetLandScape()->irand(0,numGroups-1)]; + + // Create the child instance now. If the instance create fails then the + // IsValid routine will return false and this instance wont be added + mInstance = instFile.CreateInstance ( instGroup->FindPairValue ( "name", "" ) ); + mInstance->SetFilter(mFilter); + mInstance->SetTeamFilter(mTeamFilter); + + mAutomapSymbol = mInstance->GetAutomapSymbol(); + + SetMessage(mInstance->GetMessage()); + SetDescription(mInstance->GetDescription()); + SetInfo(mInstance->GetInfo()); +} + +/************************************************************************************************ + * CRMRandomInstance::~CRMRandomInstance + * Deletes the sub instance + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMRandomInstance::~CRMRandomInstance(void) +{ + if ( mInstance ) + { + delete mInstance; + } +} + +void CRMRandomInstance::SetMirror(int mirror) +{ + CRMInstance::SetMirror(mirror); + if (mInstance) + { + mInstance->SetMirror(mirror); + } +} + +void CRMRandomInstance::SetFilter( const char *filter ) +{ + CRMInstance::SetFilter(filter); + if (mInstance) + { + mInstance->SetFilter(filter); + } +} + +void CRMRandomInstance::SetTeamFilter( const char *teamFilter ) +{ + CRMInstance::SetTeamFilter(teamFilter); + if (mInstance) + { + mInstance->SetTeamFilter(teamFilter); + } +} + +/************************************************************************************************ + * CRMRandomInstance::PreSpawn + * Prepares for the spawn of the random instance + * + * inputs: + * landscape: landscape object this instance will be spawned on + * + * return: + * true: preparation successful + * false: preparation failed + * + ************************************************************************************************/ +bool CRMRandomInstance::PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + assert ( mInstance ); + + mInstance->SetFlattenHeight ( GetFlattenHeight( ) ); + + return mInstance->PreSpawn ( terrain, IsServer ); +} + +/************************************************************************************************ + * CRMRandomInstance::Spawn + * Spawns the instance onto the landscape + * + * inputs: + * landscape: landscape object this instance will be spawned on + * + * return: + * true: spawn successful + * false: spawn failed + * + ************************************************************************************************/ +bool CRMRandomInstance::Spawn ( CRandomTerrain* terrain, qboolean IsServer ) +{ + mInstance->SetObjective(GetObjective()); + mInstance->SetSide(GetSide()); + + if ( !mInstance->Spawn ( terrain, IsServer ) ) + { + return false; + } + + return true; +} + +/************************************************************************************************ + * CRMRandomInstance::SetArea + * Forwards the given area off to the internal instance + * + * inputs: + * area: area to be set + * + * return: + * none + * + ************************************************************************************************/ +void CRMRandomInstance::SetArea ( CRMAreaManager* amanager, CRMArea* area ) +{ + CRMInstance::SetArea ( amanager, area ); + + mInstance->SetArea ( amanager, mArea ); +} diff --git a/codemp/rmg/rm_instance_random.h b/codemp/rmg/rm_instance_random.h new file mode 100644 index 0000000..474dfb9 --- /dev/null +++ b/codemp/rmg/rm_instance_random.h @@ -0,0 +1,40 @@ +#pragma once +#if !defined(RM_INSTANCE_RANDOM_H_INC) +#define RM_INSTANCE_RANDOM_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Instance_Random.h") +#endif + +#define MAX_RANDOM_INSTANCES 64 + +class CRMRandomInstance : public CRMInstance +{ +protected: + + CRMInstance* mInstance; + +public: + + CRMRandomInstance ( CGPGroup* instGroup, CRMInstanceFile& instFile ); + ~CRMRandomInstance ( ); + + virtual bool IsValid ( ) { return mInstance==NULL?false:true; } + + virtual int GetPreviewColor ( ) { return mInstance->GetPreviewColor ( ); } + + virtual float GetSpacingRadius ( ) { return mInstance->GetSpacingRadius ( ); } + virtual int GetSpacingLine ( ) { return mInstance->GetSpacingLine ( ); } + virtual float GetFlattenRadius ( ) { return mInstance->GetFlattenRadius ( ); } + virtual bool GetLockOrigin ( ) { return mInstance->GetLockOrigin ( ); } + + virtual void SetFilter ( const char *filter ); + virtual void SetTeamFilter ( const char *teamFilter ); + virtual void SetArea ( CRMAreaManager* amanager, CRMArea* area ); + virtual void SetMirror (int mirror); + + virtual bool PreSpawn ( CRandomTerrain* terrain, qboolean IsServer ); + virtual bool Spawn ( CRandomTerrain* terrain, qboolean IsServer ); +}; + +#endif \ No newline at end of file diff --git a/codemp/rmg/rm_instance_void.cpp b/codemp/rmg/rm_instance_void.cpp new file mode 100644 index 0000000..a92bddf --- /dev/null +++ b/codemp/rmg/rm_instance_void.cpp @@ -0,0 +1,54 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/************************************************************************************************ + * + * RM_Instance_Void.cpp + * + * Implements the CRMVoidInstance class. This class just adds a void into the + * area manager to help space things out. + * + ************************************************************************************************/ + +#include "RM_Headers.h" + +#include "RM_Instance_Void.h" + +/************************************************************************************************ + * CRMVoidInstance::CRMVoidInstance + * constructs a void instance + * + * inputs: + * instGroup: parser group containing infromation about this instance + * instFile: reference to an open instance file for creating sub instances + * + * return: + * none + * + ************************************************************************************************/ +CRMVoidInstance::CRMVoidInstance ( CGPGroup *instGroup, CRMInstanceFile& instFile ) + : CRMInstance ( instGroup, instFile ) +{ + mSpacingRadius = atof( instGroup->FindPairValue ( "spacing", "0" ) ); + mFlattenRadius = atof( instGroup->FindPairValue ( "flatten", "0" ) ); +} + +/************************************************************************************************ + * CRMVoidInstance::SetArea + * Overidden to make sure the void area doesnt continually. + * + * inputs: + * area: area to set + * + * return: + * none + * + ************************************************************************************************/ +void CRMVoidInstance::SetArea ( CRMAreaManager* amanager, CRMArea* area ) +{ + // Disable collision + area->EnableCollision ( false ); + + // Do what really needs to get done + CRMInstance::SetArea ( amanager, area ); +} diff --git a/codemp/rmg/rm_instance_void.h b/codemp/rmg/rm_instance_void.h new file mode 100644 index 0000000..dac0eca --- /dev/null +++ b/codemp/rmg/rm_instance_void.h @@ -0,0 +1,18 @@ +#pragma once +#if !defined(RM_INSTANCE_VOID_H_INC) +#define RM_INSTANCE_VOID_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_Instance_Void.h") +#endif + +class CRMVoidInstance : public CRMInstance +{ +public: + + CRMVoidInstance ( CGPGroup* instGroup, CRMInstanceFile& instFile ); + + virtual void SetArea ( CRMAreaManager* amanager, CRMArea* area ); +}; + +#endif \ No newline at end of file diff --git a/codemp/rmg/rm_instancefile.cpp b/codemp/rmg/rm_instancefile.cpp new file mode 100644 index 0000000..c8b2364 --- /dev/null +++ b/codemp/rmg/rm_instancefile.cpp @@ -0,0 +1,201 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/************************************************************************************************ + * + * RM_InstanceFile.cpp + * + * implements the CRMInstanceFile class. This class provides functionality to load + * and create instances from an instance file. First call Open to open the instance file and + * then use CreateInstance to create new instances. When finished call Close to cleanup. + * + ************************************************************************************************/ + +#include "RM_Headers.h" + +//#include "rm_instance_npc.h" +#include "RM_Instance_BSP.h" +#include "RM_Instance_Random.h" +#include "RM_Instance_Group.h" +#include "RM_Instance_Void.h" + +/************************************************************************************************ + * CRMInstanceFile::CRMInstanceFile + * constructor + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMInstanceFile::CRMInstanceFile ( ) +{ + mInstances = NULL; +} + +/************************************************************************************************ + * CRMInstanceFile::~CRMInstanceFile + * Destroys the instance file by freeing the parser + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMInstanceFile::~CRMInstanceFile ( ) +{ + Close ( ); +} + +/************************************************************************************************ + * CRMInstanceFile::Open + * Opens the given instance file and prepares it for use in instance creation + * + * inputs: + * instance: Name of instance to open. Note that the root path will be automatically + * added and shouldnt be included in the given name + * + * return: + * true: instance file successfully loaded + * false: instance file could not be loaded for some reason + * + ************************************************************************************************/ +bool CRMInstanceFile::Open ( const char* instance ) +{ + char instanceDef[MAX_QPATH]; + CGPGroup *basegroup; + + // Build the filename + Com_sprintf(instanceDef, MAX_QPATH, "ext_data/rmg/%s.instance", instance ); + +#ifndef FINAL_BUILD + // Debug message + Com_Printf("CM_Terrain: Loading and parsing instanceDef %s.....\n", instance); +#endif + + // Parse the text file using the generic parser + if(!Com_ParseTextFile(instanceDef, mParser )) + { + Com_sprintf(instanceDef, MAX_QPATH, "ext_data/arioche/%s.instance", instance ); + if(!Com_ParseTextFile(instanceDef, mParser )) + { + Com_Printf(va("CM_Terrain: Could not open instance file '%s'\n", instanceDef)); + return false; + } + } + + // The whole file.... + basegroup = mParser.GetBaseParseGroup(); + + // The root { } struct + mInstances = basegroup->GetSubGroups(); + + // The "instances" { } structure + mInstances = mInstances->GetSubGroups ( ); + + return true; +} + +/************************************************************************************************ + * CRMInstanceFile::Close + * Closes an open instance file + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +void CRMInstanceFile::Close ( void ) +{ + // If not open then dont close it + if ( NULL == mInstances ) + { + return; + } + mParser.Clean(); + + mInstances = NULL; +} + +/************************************************************************************************ + * CRMInstanceFile::CreateInstance + * Creates an instance (to be freed by caller) using the given instance name. + * + * inputs: + * name: Name of the instance to read from the instance file + * + * return: + * NULL: instance could not be read from the instance file + * NON-NULL: instance created and returned for further use + * + ************************************************************************************************/ +CRMInstance* CRMInstanceFile::CreateInstance ( const char* name ) +{ + static int instanceID = 0; + + CGPGroup* group; + CRMInstance* instance; + + // Make sure we were loaded + assert ( mInstances ); + + // Search through the instances for the one with the given name + for ( group = mInstances; group; group = group->GetNext ( ) ) + { + // Skip it if the name doesnt match + if ( stricmp ( name, group->FindPairValue ( "name", "" ) ) ) + { + continue; + } + + // Handle the various forms of instance types + if ( !stricmp ( group->GetName ( ), "bsp" ) ) + { + instance = new CRMBSPInstance ( group, *this ); + } + else if ( !stricmp ( group->GetName ( ), "npc" ) ) + { +// instance = new CRMNPCInstance ( group, *this ); + continue; + } + else if ( !stricmp ( group->GetName ( ), "group" ) ) + { + instance = new CRMGroupInstance ( group, *this ); + } + else if ( !stricmp ( group->GetName ( ), "random" ) ) + { + instance = new CRMRandomInstance ( group, *this ); + } + else if ( !stricmp ( group->GetName ( ), "void" ) ) + { + instance = new CRMVoidInstance ( group, *this ); + } + else + { + continue; + } + + // If the instance isnt valid after being created then delete it + if ( !instance->IsValid ( ) ) + { + delete instance; + return NULL; + } + + // The instance was successfully created so return it + return instance; + } + +#ifndef FINAL_BUILD + // The instance wasnt found in the file so report it + Com_Printf(va("WARNING: Instance '%s' was not found in the active instance file\n", name )); +#endif + + return NULL; +} diff --git a/codemp/rmg/rm_instancefile.h b/codemp/rmg/rm_instancefile.h new file mode 100644 index 0000000..92f8ebc --- /dev/null +++ b/codemp/rmg/rm_instancefile.h @@ -0,0 +1,28 @@ +#pragma once +#if !defined(RM_INSTANCEFILE_H_INC) +#define RM_INSTANCEFILE_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including RM_InstanceFile.h") +#endif + +class CRMInstance; + +class CRMInstanceFile +{ +public: + + CRMInstanceFile ( ); + ~CRMInstanceFile ( ); + + bool Open ( const char* instance ); + void Close ( void ); + CRMInstance* CreateInstance ( const char* name ); + +protected: + + CGenericParser2 mParser; + CGPGroup* mInstances; +}; + +#endif \ No newline at end of file diff --git a/codemp/rmg/rm_manager.cpp b/codemp/rmg/rm_manager.cpp new file mode 100644 index 0000000..e124c93 --- /dev/null +++ b/codemp/rmg/rm_manager.cpp @@ -0,0 +1,474 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/************************************************************************************************ + * + * RM_Manager.cpp + * + * Implements the CRMManager class. The CRMManager class manages the arioche system. + * + ************************************************************************************************/ + +#include "RM_Headers.h" +#include "../server/server.h" +#include "../qcommon/qcommon.h" + +CRMObjective *CRMManager::mCurObjective=0; + +/************************************************************************************************ + * TheRandomMissionManager + * Pointer to only active CRMManager class + * + ************************************************************************************************/ +CRMManager *TheRandomMissionManager; + +/************************************************************************************************ + * CRMManager::CRMManager + * constructor + * + * inputs: + * + * return: + * + ************************************************************************************************/ +CRMManager::CRMManager(void) +{ + mLandScape = NULL; + mTerrain = NULL; + mMission = NULL; + mCurPriority = 1; + mUseTimeLimit = false; + mAutomapSymbolCount = 0; +} + +/************************************************************************************************ + * CRMManager::~CRMManager + * destructor + * + * inputs: + * + * return: + * + ************************************************************************************************/ +CRMManager::~CRMManager(void) +{ +#ifndef FINAL_BUILD + Com_Printf ("... Shutting down TheRandomMissionManager\n"); +#endif +#ifndef DEDICATED + CM_TM_Free(); +#endif + if (mMission) + { + delete mMission; + mMission = NULL; + } +} + +/************************************************************************************************ + * CRMManager::SetLandscape + * Sets the landscape and terrain object used to load a mission + * + * inputs: + * landscape - landscape object + * + * return: + * none + * + ************************************************************************************************/ +void CRMManager::SetLandScape(CCMLandScape *landscape) +{ + mLandScape = landscape; + mTerrain = landscape->GetRandomTerrain(); +} + +/************************************************************************************************ + * CRMManager::LoadMission + * Loads the mission using the mission name stored in the ar_mission cvar + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +bool CRMManager::LoadMission ( qboolean IsServer ) +{ +#ifndef PRE_RELEASE_DEMO + char instances[MAX_QPATH]; + char mission[MAX_QPATH]; + char course[MAX_QPATH]; + char map[MAX_QPATH]; + char temp[MAX_QPATH]; + +#ifndef FINAL_BUILD + Com_Printf ("--------- Random Mission Manager ---------\n\n"); + Com_Printf ("RMG version : 1.01\n\n"); +#endif + + if (!mTerrain) + { + return false; + } + + // Grab the arioche variables + Cvar_VariableStringBuffer("rmg_usetimelimit", temp, MAX_QPATH); + if (Q_stricmp(temp, "yes") == 0) + { + mUseTimeLimit = true; + } + Cvar_VariableStringBuffer("rmg_instances", instances, MAX_QPATH); + Cvar_VariableStringBuffer("RMG_mission", temp, MAX_QPATH); + Cvar_VariableStringBuffer("rmg_map", map, MAX_QPATH); + sprintf(mission, "%s_%s", temp, map); + Cvar_VariableStringBuffer("rmg_course", course, MAX_QPATH); + + // dump existing mission, if any + if (mMission) + { + delete mMission; + mMission = NULL; + } + + // Create a new mission file + mMission = new CRMMission ( mTerrain ); + + if ( IsServer ) + { + // Load the mission using the arioche variables + if ( !mMission->Load ( mission, instances, course ) ) + { + return false; + } + + // set the names of the teams + CGenericParser2 parser; + CGPGroup* root; + + Cvar_VariableStringBuffer("RMG_terrain", temp, MAX_QPATH); + + // Create the parser for the mission file + if(Com_ParseTextFile(va("ext_data/rmg/%s.teams", temp), parser)) + { + root = parser.GetBaseParseGroup()->GetSubGroups(); + if (0 == stricmp(root->GetName(), "teams")) + { + /* + SV_SetConfigstring( CS_GAMETYPE_REDTEAM, root->FindPairValue ( "red", "marine" )); + SV_SetConfigstring( CS_GAMETYPE_BLUETEAM, root->FindPairValue ( "blue", "thug" )); + */ + //rwwFIXMEFIXME: Do we care about this? + } + parser.Clean(); + } + } + + // Must have a valid landscape before we can spawn the mission + assert ( mLandScape ); + +#ifndef FINAL_BUILD + Com_Printf ("------------------------------------------\n"); +#endif + + return true; +#else + return false; +#endif // PRE_RELEASE_DEMO +} + +/************************************************************************************************ + * CRMManager::IsMissionComplete + * Determines whether or not all the arioche objectives have been met + * + * inputs: + * none + * + * return: + * true: all objectives have been completed + * false: one or more of the objectives has not been met + * + ************************************************************************************************/ +bool CRMManager::IsMissionComplete(void) +{ + if ( NULL == mMission->GetCurrentObjective ( ) ) + { + return true; + } + + return false; +} + +/************************************************************************************************ + * CRMManager::HasTimeExpired + * Determines whether or not the time limit (if one) has expired + * + * inputs: + * none + * + * return: + * true: time limit has expired + * false: time limit has not expired + * + ************************************************************************************************/ +bool CRMManager::HasTimeExpired(void) +{ +/* if (mMission->GetTimeLimit() == 0 || !mUseTimeLimit) + { // no time limit set + return false; + } + + if (mMission->GetTimeLimit() * 1000 * 60 > level.time - level.startTime) + { // we are still under our time limit + return false; + } + + // over our time limit! + return true;*/ + + return false; +} + +/************************************************************************************************ + * CRMManager::UpdateStatisticCvars + * Updates the statistic cvars with data from the game + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +void CRMManager::UpdateStatisticCvars ( void ) +{ +/* // No player set then nothing more to do + if ( mPlayer ) + { + float accuracy; + + // Calculate the accuracy + accuracy = (float)mPlayer->client->ps.persistant[PERS_SHOTS_HIT]; + accuracy /= (float)mPlayer->client->ps.persistant[PERS_SHOTS]; + accuracy *= 100.0f; + + // set the accuracy cvar + gi.Cvar_Set ( "ar_pl_accuracy", va("%d%%",(int)accuracy) ); + + // Set the # of kills cvar + gi.Cvar_Set ( "ar_kills", va("%d", mPlayer->client->ps.persistant[PERS_SCORE] ) ); + + int hours; + int mins; + int seconds; + int tens; + int millisec = (level.time - level.startTime); + + seconds = millisec / 1000; + hours = seconds / (60 * 60); + seconds -= (hours * 60 * 60); + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + gi.Cvar_Set ( "ar_duration", va("%dhr %dmin %dsec", hours, mins, seconds ) ); + + WpnID wpnID = TheWpnSysMgr().GetFavoriteWeapon ( ); + gi.Cvar_Set ( "ar_fav_wp", CWeaponSystem::GetWpnName ( wpnID ) ); + + // show difficulty + char difficulty[MAX_QPATH]; + gi.Cvar_VariableStringBuffer("g_skill", difficulty, MAX_QPATH); + strupr(difficulty); + gi.Cvar_Set ( "ar_diff", va("&GENERIC_%s&",difficulty) ); + + // compute rank + float compositeRank = 1; + int rankMax = 3; // max rank less 1 + float timeRank = mUseTimeLimit ? (1.0f - (mins / (float)mMission->GetTimeLimit())) : 0; + float killRank = mPlayer->client->ps.persistant[PERS_SCORE] / (float)GetCharacterManager().GetAllSize(); + killRank = (killRank > 0) ? killRank : 1.0f; + float accuRank = (accuracy > 0) ? accuracy*0.01f : 1.0f; + float weapRank = 1.0f - CWeaponSystem::GetRank(wpnID); + + compositeRank = ((timeRank + killRank + accuRank + weapRank) / 3.0f) * rankMax + 1; + + if (compositeRank > 4) + compositeRank = 4; + + gi.Cvar_Set ( "ar_rank", va("&RMG_RANK%d&",((int)compositeRank)) ); + }*/ +} + +/************************************************************************************************ + * CRMManager::CompleteMission + * Does end-of-mission stuff (pause game, end screen, return to menu) + * * + * Input * + * : * + * Output / Return * + * : * + ************************************************************************************************/ +void CRMManager::CompleteMission(void) +{ + UpdateStatisticCvars ( ); + + mMission->CompleteMission(); +} + +/************************************************************************************************ + * CRMManager::FailedMission + * Does end-of-mission stuff (pause game, end screen, return to menu) + * * + * Input * + * TimeExpired: indicates if the reason failed was because of time + * Output / Return * + * : * + ************************************************************************************************/ +void CRMManager::FailedMission(bool TimeExpired) +{ + UpdateStatisticCvars ( ); + + mMission->FailedMission(TimeExpired); +} + +/************************************************************************************************ + * CRMManager::CompleteObjective + * Marks the given objective as completed + * + * inputs: + * obj: objective to set as completed + * + * return: + * none + * + ************************************************************************************************/ +void CRMManager::CompleteObjective ( CRMObjective *obj ) +{ + assert ( obj ); + + mMission->CompleteObjective ( obj ); +} + +/************************************************************************************************ + * CRMManager::Preview + * previews the random mission genration information + * + * inputs: + * from: origin being previed from + * + * return: + * none + * + ************************************************************************************************/ +void CRMManager::Preview ( const vec3_t from ) +{ + // Dont bother if we havent reached our timer yet +/* if ( level.time < mPreviewTimer ) + { + return; + } + + // Let the mission do all the previewing + mMission->Preview ( from ); + + // Another second + mPreviewTimer = level.time + 1000;*/ +} + +/************************************************************************************************ + * CRMManager::Preview + * previews the random mission genration information + * + * inputs: + * from: origin being previed from + * + * return: + * none + * + ************************************************************************************************/ +bool CRMManager::SpawnMission ( qboolean IsServer ) +{ + // Spawn the mission + mMission->Spawn ( mTerrain, IsServer ); + + return true; +} + + +void CRMManager::AddAutomapSymbol ( int type, vec3_t origin, int side ) +{ + if ( !type ) + { + return; + } + + mAutomapSymbols[mAutomapSymbolCount].mType = type; + mAutomapSymbols[mAutomapSymbolCount].mSide = side; + VectorCopy ( origin, mAutomapSymbols[mAutomapSymbolCount].mOrigin ); + mAutomapSymbolCount++; +} + +int CRMManager::GetAutomapSymbolCount ( void ) +{ + return mAutomapSymbolCount; +} + +rmAutomapSymbol_t* CRMManager::GetAutomapSymbol ( int index ) +{ + return &mAutomapSymbols[index]; +} + +/* +void CRMManager::WriteAutomapSymbols ( msg_t* msg ) +{ + rmAutomapSymbolIter_t it; + + MSG_WriteShort ( msg, (unsigned long)mAutomapSymbols.size() ); + + for(it = mAutomapSymbols.begin(); it != mAutomapSymbols.end(); it++) + { + CRMAutomapSymbol* symbol = (CRMAutomapSymbol*) *it; + + MSG_WriteByte ( msg, (unsigned char) symbol->mType ); + MSG_WriteByte ( msg, (unsigned char) symbol->mSide ); + MSG_WriteLong ( msg, (long) symbol->mOrigin[0] ); + MSG_WriteLong ( msg, (long) symbol->mOrigin[1] ); + } +} +*/ + +void CRMManager::ProcessAutomapSymbols ( int count, rmAutomapSymbol_t* symbols ) +{ +#ifndef DEDICATED + int i; + + for ( i = 0; i < count; i ++ ) + { + // draw proper symbol on map for instance + switch (symbols[i].mType) + { + case AUTOMAP_BLD: + CM_TM_AddBuilding(symbols[i].mOrigin[0], symbols[i].mOrigin[1], symbols[i].mSide ); + break; + case AUTOMAP_OBJ: + CM_TM_AddObjective(symbols[i].mOrigin[0], symbols[i].mOrigin[1], symbols[i].mSide ); + break; + case AUTOMAP_START: + CM_TM_AddStart(symbols[i].mOrigin[0], symbols[i].mOrigin[1], symbols[i].mSide ); + break; + case AUTOMAP_END: + CM_TM_AddEnd(symbols[i].mOrigin[0], symbols[i].mOrigin[1], symbols[i].mSide ); + break; + case AUTOMAP_ENEMY: + break; + case AUTOMAP_FRIEND: + break; + case AUTOMAP_WALL: + CM_TM_AddWallRect(symbols[i].mOrigin[0], symbols[i].mOrigin[1], symbols[i].mSide ); + break; + } + } +#endif +} diff --git a/codemp/rmg/rm_manager.h b/codemp/rmg/rm_manager.h new file mode 100644 index 0000000..a103707 --- /dev/null +++ b/codemp/rmg/rm_manager.h @@ -0,0 +1,63 @@ +#pragma once +#if !defined(RM_MANAGER_H_INC) +#define RM_MANAGER_H_INC + +#if !defined(CM_LANDSCAPE_H_INC) +#include "../qcommon/cm_landscape.h" +#endif + +class CRMManager +{ +private: + + CRMMission* mMission; + CCMLandScape* mLandScape; + CRandomTerrain* mTerrain; + int mPreviewTimer; + int mCurPriority; + bool mUseTimeLimit; + + rmAutomapSymbol_t mAutomapSymbols[MAX_AUTOMAP_SYMBOLS]; + int mAutomapSymbolCount; + + void UpdateStatisticCvars ( void ); + +public: + + // Constructors + CRMManager (void); + ~CRMManager (void); + + bool LoadMission ( qboolean IsServer ); + bool SpawnMission ( qboolean IsServer ); + + // Accessors + void SetLandScape (CCMLandScape *landscape); + void SetCurPriority (int priority) { mCurPriority = priority; } + + CRandomTerrain* GetTerrain (void) { return mTerrain; } + CCMLandScape* GetLandScape (void) { return mLandScape; } + CRMMission* GetMission (void) { return mMission; } + int GetCurPriority (void) { return mCurPriority; } + + void AddAutomapSymbol ( int type, vec3_t origin, int side ); + int GetAutomapSymbolCount ( void ); + rmAutomapSymbol_t* GetAutomapSymbol ( int index ); + static void ProcessAutomapSymbols ( int count, rmAutomapSymbol_t* symbols ); + + void Preview ( const vec3_t from ); + + bool IsMissionComplete (void); + bool HasTimeExpired (void); + void CompleteObjective ( CRMObjective *obj ); + void CompleteMission (void); + void FailedMission (bool TimeExpired); + + // eek + static CRMObjective *mCurObjective; +}; + +extern CRMManager* TheRandomMissionManager; + + +#endif // RANDOMMISSION_H_INC \ No newline at end of file diff --git a/codemp/rmg/rm_mission.cpp b/codemp/rmg/rm_mission.cpp new file mode 100644 index 0000000..40d5d3b --- /dev/null +++ b/codemp/rmg/rm_mission.cpp @@ -0,0 +1,1940 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/************************************************************************************************ + * + * RM_Mission.cpp + * + * implements the CRMMission class. The CRMMission class loads and manages an arioche mission + * + ************************************************************************************************/ + +#include "RM_Headers.h" + +#define ARIOCHE_CLIPBRUSH_SIZE 300 +#define CVAR_OBJECTIVE 0 + +/************************************************************************************************ + * CRMMission::CRMMission + * constructor + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMMission::CRMMission ( CRandomTerrain* landscape ) +{ + mCurrentObjective = NULL; + mValidPaths = false; + mValidRivers = false; + mValidNodes = false; + mValidWeapons = false; + mValidAmmo = false; + mValidObjectives = false; + mValidInstances = false; + mTimeLimit = 0; + mMaxInstancePosition = 1; + mAccuracyMultiplier = 1.0f; + mHealthMultiplier = 1.0f; + mPickupHealth = 1.0f; + mPickupArmor = 1.0f; + mPickupAmmo = 1.0f; + mPickupWeapon = 1.0f; + mPickupEquipment = 1.0f; + + mDefaultPadding = 0; + mSymmetric = SYMMETRY_NONE; + +// mCheckedEnts.clear(); + + mLandScape = landscape; + + // cut down the possible area that is 'legal' for area manager to use by 20% + vec3_t land_min, land_max; + + land_min[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * 0.1f; + land_min[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * 0.1f; + land_min[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * 0.1f; + + land_max[0] = mLandScape->GetBounds ( )[1][0] - (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * 0.1f; + land_max[1] = mLandScape->GetBounds ( )[1][1] - (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * 0.1f; + land_max[2] = mLandScape->GetBounds ( )[1][2] - (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * 0.1f; + + // Create a new area manager for the landscape + mAreaManager = new CRMAreaManager ( land_min, + land_max ); + + // Create a new path manager + mPathManager = new CRMPathManager ( mLandScape ); +} + +/************************************************************************************************ + * CRMMission::~CRMMission + * destructor + * + * inputs: + * none + * + * return: + * none + * + ************************************************************************************************/ +CRMMission::~CRMMission ( ) +{ + rmObjectiveIter_t oit; + rmInstanceIter_t iit; + +// mCheckedEnts.clear(); + + // Cleanup the objectives + for (oit = mObjectives.begin(); oit != mObjectives.end(); oit++) + { + delete (*oit); + } + + // Cleanup the instances + for (iit = mInstances.begin(); iit != mInstances.end(); iit++) + { + delete (*iit); + } + + if (mPathManager) + { + delete mPathManager; + mPathManager = 0; + } + + if (mAreaManager) + { + delete mAreaManager; + mAreaManager = 0; + } +} + +/************************************************************************************************ + * CRMMission::FindObjective + * searches through the missions objectives for the one with the given name + * + * inputs: + * name: name of objective to find + * + * return: + * objective: objective matching the given name or NULL if it couldnt be found + * + ************************************************************************************************/ +CRMObjective* CRMMission::FindObjective ( const char* name ) +{ + rmObjectiveIter_t it; + + for (it = mObjectives.begin(); it != mObjectives.end(); it++) + { + // Does it match? + if (!stricmp ((*it)->GetName(), name )) + { + return (*it); + } + } + + // Not found + return NULL; +} + +void CRMMission::MirrorPos(vec3_t pos) +{ + pos[0] = 1.0f - pos[0]; + pos[1] = 1.0f - pos[1]; +} + +/************************************************************************************************ + * CRMMission::ParseOrigin + * parses an origin block which includes linking to a node and absolute origins + * + * inputs: + * group: parser group containing the node or origin + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseOrigin ( CGPGroup* originGroup, vec3_t origin, vec3_t lookat, int* flattenHeight ) +{ + const char* szNodeName; + vec3_t mins; + vec3_t maxs; + + if ( flattenHeight ) + { + *flattenHeight = 66; + } + + // If no group was given then use 0,0,0 + if ( NULL == originGroup ) + { + VectorCopy ( vec3_origin, origin ); + return false; + } + + // See if attaching to a named node + szNodeName = originGroup->FindPairValue ( "node", "" ); + if ( *szNodeName ) + { + CRMNode* node; + // Find the node being attached to + node = mPathManager->FindNodeByName ( szNodeName ); + if ( node ) + { + if ( flattenHeight ) + { + if ( node->GetFlattenHeight ( ) == -1 ) + { + node->SetFlattenHeight ( 40 + mLandScape->irand(0,40) ); + } + + *flattenHeight = node->GetFlattenHeight ( ); + } + + VectorCopy(node->GetPos(), origin); + + VectorCopy ( origin, lookat ); + + int dir; + int rnd_offset = mLandScape->irand(0, DIR_MAX-1); + for (dir=0; dirPathExist(d)) + { + vec4_t tmp_pt, tmp_dir; + int pathID = node->GetPath(d); + mLandScape->GetPathInfo(pathID, .1, tmp_pt, tmp_dir ); + lookat[0] = tmp_pt[0]; + lookat[1] = tmp_pt[1]; + lookat[2] = 0; + return true; + } + } + return true; + } + } + + mins[0] = atof( originGroup->FindPairValue ( "left", ".1" ) ); + mins[1] = atof( originGroup->FindPairValue ( "top", ".1" ) ); + maxs[0] = atof( originGroup->FindPairValue ( "right", ".9" ) ); + maxs[1] = atof( originGroup->FindPairValue ( "bottom", ".9" ) ); + + lookat[0] = origin[0] = mLandScape->flrand(mins[0],maxs[0]); + lookat[1] = origin[1] = mLandScape->flrand(mins[1],maxs[1]); + lookat[2] = origin[2] = 0; + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseNodes + * parses all the named nodes in the file + * + * inputs: + * group: parser group containing the named nodes + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseNodes ( CGPGroup* group ) +{ + // If NULL that means this particular difficulty level has no named nodes + if ( NULL == group || mValidNodes) + { + return true; + } + + // how many nodes spaced over map? + int x_cells; + int y_cells; + + x_cells = atoi ( group->FindPairValue ( "x_cells", "3" ) ); + y_cells = atoi ( group->FindPairValue ( "y_cells", "3" ) ); + + mPathManager->CreateArray(x_cells, y_cells); + + // Loop through all the nodes and generate each as specified + for ( group = group->GetSubGroups(); + group; + group=group->GetNext() ) + { + int min_depth = atof( group->FindPairValue ( "min_depth", "0" ) ); + int max_depth = atof( group->FindPairValue ( "max_depth", "5" ) ); + int min_paths = atoi( group->FindPairValue ( "min_paths", "1" ) ); + int max_paths = atoi( group->FindPairValue ( "max_paths", "1" ) ); + + mPathManager->CreateLocation( group->GetName(), min_depth, max_depth, min_paths, max_paths ); + } + + mValidNodes = true; + return true; +} + +/************************************************************************************************ + * CRMMission::ParsePaths + * parses all path styles in the file and then generates paths + * + * inputs: + * group: parser group containing the list of path styles + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParsePaths ( CGPGroup* group ) +{ + // If NULL that means this particular difficulty level has no paths + if ( NULL == group || mValidPaths) + { + return true; + } + + // path style info + float depth; + float deviation; + float breadth; + float minwidth; + float maxwidth; + int points; + + points = atoi ( group->FindPairValue ( "points", "10" ) ); + depth = atof ( group->FindPairValue ( "depth", ".31" ) ); + deviation = atof ( group->FindPairValue ( "deviation", ".025" ) ); + breadth = atof ( group->FindPairValue ( "breadth", "5" ) ); + minwidth = atof ( group->FindPairValue ( "minwidth", ".03" ) ); + maxwidth = atof ( group->FindPairValue ( "maxwidth", ".05" ) ); + + mPathManager->SetPathStyle( points, minwidth, maxwidth, depth, deviation, breadth); + + if (!mValidPaths) + { // we must create paths + mPathManager->GeneratePaths( mSymmetric ); + mValidPaths = true; + } + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseRivers + * parses all river styles in the file and then generates rivers + * + * inputs: + * group: parser group containing the list of path styles + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseRivers ( CGPGroup* group ) +{ + // If NULL that means this particular difficulty level has no rivers + if ( NULL == group || mValidRivers) + { + return true; + } + + // river style info + int maxdepth; + float beddepth; + float deviation; + float breadth; + float minwidth; + float maxwidth; + int points; + string bridge_name; + + maxdepth = atoi ( group->FindPairValue ( "maxpathdepth", "5" ) ); + points = atoi ( group->FindPairValue ( "points", "10" ) ); + beddepth = atof ( group->FindPairValue ( "depth", "1" ) ); + deviation = atof ( group->FindPairValue ( "deviation", ".03" ) ); + breadth = atof ( group->FindPairValue ( "breadth", "7" ) ); + minwidth = atof ( group->FindPairValue ( "minwidth", ".01" ) ); + maxwidth = atof ( group->FindPairValue ( "maxwidth", ".03" ) ); + bridge_name= group->FindPairValue ( "bridge", "" ) ; + + mPathManager->SetRiverStyle( maxdepth, points, minwidth, maxwidth, beddepth, deviation, breadth, bridge_name); + + if (!mValidRivers && + beddepth < 1) // use a depth of 1 if we don't want any rivers + { // we must create rivers + mPathManager->GenerateRivers(); + mValidRivers = true; + } + + return true; +} + +void CRMMission::PlaceBridges() +{ + if (!mValidRivers || strlen(mPathManager->GetBridgeName()) < 1) + return; + + int max_bridges = 0; + int path; + float t; + float river_depth = mLandScape->GetLandScape()->GetWaterHeight(); + vec3_t pos, lastpos; + vec3pair_t bounds; + VectorSet(bounds[0], 0,0,0); + VectorSet(bounds[1], 0,0,0); + + // walk along paths looking for dips + for (path = 0; path < mPathManager->GetPathCount(); path++) + { + vec4_t tmp_pt, tmp_dir; + bool new_water = true; + + mLandScape->GetPathInfo(path, 0, tmp_pt, tmp_dir ); + lastpos[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * tmp_pt[0]; + lastpos[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * tmp_pt[1]; + lastpos[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * tmp_pt[2]; + mLandScape->GetLandScape()->GetWorldHeight ( lastpos, bounds, true ); + + const float delta = 0.05f; + for (t= delta; t < 1.0f; t += delta) + { + mLandScape->GetPathInfo(path, t, tmp_pt, tmp_dir ); + pos[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * tmp_pt[0]; + pos[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * tmp_pt[1]; + pos[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * tmp_pt[2]; + mLandScape->GetLandScape()->GetWorldHeight ( pos, bounds, true ); + + if (new_water && + lastpos[2] < river_depth && + pos[2] < river_depth && + pos[2] > lastpos[2]) + { // add a bridge + if (max_bridges < 3) + { + CRMArea* area; + CRMInstance* instance; + + max_bridges++; + + // create a single bridge + lastpos[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * mPathManager->GetPathDepth(); + instance = mInstanceFile.CreateInstance ( mPathManager->GetBridgeName() ); + + if ( NULL != instance ) + { // Set the area + area = mAreaManager->CreateArea ( lastpos, instance->GetSpacingRadius(), instance->GetSpacingLine(), GetDefaultPadding(), 0, vec3_origin, pos, instance->GetFlattenRadius()?true:false, false, instance->GetLockOrigin() ); + area->EnableLookAt(false); + + instance->SetArea ( mAreaManager, area ); + mInstances.push_back ( instance ); + new_water = false; + } + } + } + else if (pos[2] > river_depth) + { // hit land again + new_water = true; + } + VectorCopy ( pos, lastpos ); + } + } +} + +void CRMMission::PlaceWallInstance(CRMInstance* instance, float xpos, float ypos, float zpos, int x, int y, float angle) +{ + if (NULL == instance) + return; + + float spacing = instance->GetSpacingRadius(); + CRMArea* area; + vec3_t origin; + + origin[0] = xpos + spacing * x; + origin[1] = ypos + spacing * y; + origin[2] = zpos; + + // Set the area of position + area = mAreaManager->CreateArea ( origin, (spacing / 2.1f), 0, GetDefaultPadding(), 0, vec3_origin, origin, instance->GetFlattenRadius()?true:false, false, instance->GetLockOrigin() ); + area->EnableLookAt(false); + area->SetAngle(angle); + instance->SetArea ( mAreaManager, area ); + + mInstances.push_back ( instance ); +} + + +/************************************************************************************************ + * CRMMission::ParseWallRect + * creates instances for walled rectangle at this node (fence) + * + * inputs: + * group: parser group containing the wall rect info + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseWallRect(CGPGroup* group , int side) +{ +#ifndef PRE_RELEASE_DEMO + CGPGroup* wallGroup = group->FindSubGroup ( "wallrect" ) ; + + // If NULL that means this particular instance has no wall rect + if ( NULL == group || NULL == wallGroup) + { + return true; + } + + const char* wallName = wallGroup->FindPairValue ( "wall_instance", "" ); + const char* cornerName = wallGroup->FindPairValue ( "corner_instance", "" ); + const char* towerName = wallGroup->FindPairValue ( "tower_instance", "" ); + const char* gateName = wallGroup->FindPairValue ( "gate_instance", "" ); + const char* ripName = wallGroup->FindPairValue ( "rip_instance", "" ); + + if ( NULL != wallName ) + { + int xcount = atoi( wallGroup->FindPairValue ( "xcount", "0" ) ); + int ycount = atoi( wallGroup->FindPairValue ( "ycount", "0" ) ); + + int gateCount = atoi( wallGroup->FindPairValue ( "gate_count", "1" ) ); + int gateMin = atoi( wallGroup->FindPairValue ( "gate_min", "0" ) ); + int gateMax = atoi( wallGroup->FindPairValue ( "gate_max", "0" ) ); + + int ripCount = atoi( wallGroup->FindPairValue ( "rip_count", "0" ) ); + int ripMin = atoi( wallGroup->FindPairValue ( "rip_min", "0" ) ); + int ripMax = atoi( wallGroup->FindPairValue ( "rip_max", "0" ) ); + + int towerCount = atoi( wallGroup->FindPairValue ( "tower_count", "0" ) ); + int towerMin = atoi( wallGroup->FindPairValue ( "tower_min", "0" ) ); + int towerMax = atoi( wallGroup->FindPairValue ( "tower_max", "0" ) ); + + if (gateMin != gateMax) + gateCount = mLandScape->irand(gateMin,gateMax); + + if (ripMin != ripMax) + ripCount = mLandScape->irand(ripMin,ripMax); + + if (towerMin != towerMax) + towerCount = mLandScape->irand(towerMin,towerMax); + + if (NULL == gateName) + gateCount = 0; + + if (NULL == towerName) + towerCount = 0; + + if (NULL == ripName) + ripCount = 0; + + const char* nodename; + CGPGroup* originGroup = group->FindSubGroup ( "origin" ); + if (originGroup) + { + nodename = originGroup->FindPairValue ( "node", "" ); + if (*nodename) + { + CRMNode* node; + // Find the node being attached to + node = mPathManager->FindNodeByName ( nodename ); + if ( node ) + { + CRMInstance* instance; + int x,y; + int halfx = xcount/2; + int halfy = ycount/2; + float xpos = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * node->GetPos()[0]; + float ypos = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * node->GetPos()[1]; + float zpos = mLandScape->GetBounds ( )[1][2] + 100; + float angle = 0; + int lastGate = 0; + int lastRip = 0; + + // corners + x = -halfx; + y = -halfy; + if (towerCount > 3 || + (towerCount > 0 && mLandScape->irand(1,2) == 1) ) + { + towerCount--; + instance = mInstanceFile.CreateInstance ( towerName ); + } + else + instance = mInstanceFile.CreateInstance ( cornerName ); + angle = DEG2RAD(90); + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, y, angle); + + x = halfx; + y = -halfy; + if (towerCount > 3 || + (towerCount > 0 && mLandScape->irand(1,2) == 1) ) + { + towerCount--; + instance = mInstanceFile.CreateInstance ( towerName ); + } + else + instance = mInstanceFile.CreateInstance ( cornerName ); + angle = DEG2RAD(180); + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, y, angle); + + x = halfx; + y = halfy; + if (towerCount > 3 || + (towerCount > 0 && mLandScape->irand(1,2) == 1) ) + { + towerCount--; + instance = mInstanceFile.CreateInstance ( towerName ); + } + else + instance = mInstanceFile.CreateInstance ( cornerName ); + angle = DEG2RAD(270); + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, y, angle); + + x = -halfx; + y = halfy; + if (towerCount > 3 || + (towerCount > 0 && mLandScape->irand(1,2) == 1) ) + { + towerCount--; + instance = mInstanceFile.CreateInstance ( towerName ); + } + else + instance = mInstanceFile.CreateInstance ( cornerName ); + angle = DEG2RAD(0); + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, y, angle); + + // walls + angle = DEG2RAD(0); + for (x = -halfx+1; x <= halfx-1; x++) + { + if (lastGate<1 && gateCount > 0 && mLandScape->irand(1,(halfx+halfy)/gateCount) == 1) + { // gate + gateCount--; + lastGate = 3; + instance = mInstanceFile.CreateInstance ( gateName ); + } + else if (lastRip<1 && ripCount > 0 && mLandScape->irand(1,(halfx+halfy)/ripCount) == 1) + { // damaged fence + ripCount--; + lastRip = 3; + instance = mInstanceFile.CreateInstance ( ripName ); + } + else + { // just a wall + instance = mInstanceFile.CreateInstance ( wallName ); + lastRip--; + lastGate--; + } + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, -halfy, angle); + } + for (x = -halfx+1; x <= halfx-1; x++) + { + if (lastGate<1 && gateCount > 0 && mLandScape->irand(1,(halfx+halfy)/gateCount) == 1) + { // gate + gateCount--; + lastGate = 3; + instance = mInstanceFile.CreateInstance ( gateName ); + } + else if (lastRip<1 && ripCount > 0 && mLandScape->irand(1,(halfx+halfy)/ripCount) == 1) + { // damaged fence + ripCount--; + lastRip = 3; + instance = mInstanceFile.CreateInstance ( ripName ); + } + else + { // just a wall + instance = mInstanceFile.CreateInstance ( wallName ); + lastRip--; + lastGate--; + } + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, x, halfy, angle); + } + + angle = DEG2RAD(90); + for (y = -halfy+1; y <= halfy-1; y++) + { + if (lastGate<1 && gateCount > 0 && mLandScape->irand(1,(halfx+halfy)/gateCount) == 1) + { // gate + gateCount--; + lastGate = 3; + instance = mInstanceFile.CreateInstance ( gateName ); + } + else if (lastRip<1 && ripCount > 0 && mLandScape->irand(1,(halfx+halfy)/ripCount) == 1) + { // damaged fence + ripCount--; + lastRip = 3; + instance = mInstanceFile.CreateInstance ( ripName ); + } + else + { // just a wall + instance = mInstanceFile.CreateInstance ( wallName ); + lastRip--; + lastGate--; + } + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, -halfx, y, angle); + } + for (y = -halfy+1; y <= halfy-1; y++) + { + if (lastGate<1 && gateCount > 0 && mLandScape->irand(1,(halfx+halfy)/gateCount) == 1) + { // gate + gateCount--; + lastGate = 3; + instance = mInstanceFile.CreateInstance ( gateName ); + } + else if (lastRip<1 && ripCount > 0 && mLandScape->irand(1,(halfx+halfy)/ripCount) == 1) + { // damaged fence + ripCount--; + lastRip = 3; + instance = mInstanceFile.CreateInstance ( ripName ); + } + else + { // just a wall + instance = mInstanceFile.CreateInstance ( wallName ); + lastRip--; + lastGate--; + } + instance->SetSide(side); + PlaceWallInstance(instance, xpos, ypos, zpos, halfx, y, angle); + } + } + } + } + } + else + return false; +#endif // #ifndef PRE_RELEASE_DEMO + + return true; +} + + +/************************************************************************************************ + * CRMMission::ParseInstancesOnPath + * creates instances on path between nodes + * + * inputs: + * group: parser group containing the defenses, other instances on the path between nodes + * + * return: + * true: parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseInstancesOnPath ( CGPGroup* group ) +{ +#ifndef PRE_RELEASE_DEMO + CGPGroup* defenseGroup; + for ( defenseGroup = group->GetSubGroups(); + defenseGroup; + defenseGroup=defenseGroup->GetNext() ) + if (stricmp ( defenseGroup->GetName ( ), "defenses" )==0 || + stricmp ( defenseGroup->GetName(), "instanceonpath")==0) + { + const char* defName = defenseGroup->FindPairValue ( "instance", "" ); + if ( *defName ) + { + float minpos; + float maxpos; + int mincount; + int maxcount; + + // how far along path does this get placed? + minpos = atof( defenseGroup->FindPairValue ( "minposition", "0.5" ) ); + maxpos = atof( defenseGroup->FindPairValue ( "maxposition", "0.5" ) ); + mincount = atoi( defenseGroup->FindPairValue ( "mincount", "1" ) ); + maxcount = atoi( defenseGroup->FindPairValue ( "maxcount", "1" ) ); + + const char* nodename; + CGPGroup* originGroup = group->FindSubGroup ( "origin" ); + if (originGroup) + { + nodename = originGroup->FindPairValue ( "node", "" ); + if (*nodename) + { + CRMNode* node; + // Find the node being attached to + node = mPathManager->FindNodeByName ( nodename ); + if ( node ) + { + int dir; + // look at each connection from this node to others, if there is a path, create a defense + for (dir=0; dirPathExist(dir)) + { // path leads out of this node + CRMArea* area; + CRMInstance* instance; + float spacing; + vec3_t origin; + vec3_t lookat; + vec4_t tmp_pt, tmp_dir; + int n,num_insts = mLandScape->irand(mincount,maxcount); + int pathID = node->GetPath(dir); + + if (0 == num_insts) + continue; + + float posdelta = (maxpos - minpos) / num_insts; + + for (n=0; nFindPairValue ( "spacing", "0" ) ); + if ( spacing ) + { + instance->SetSpacingRadius ( spacing ); + } + + instance->SetFilter(group->FindPairValue("filter", "")); + instance->SetTeamFilter(group->FindPairValue("teamfilter", "")); + + if (strstr(instance->GetTeamFilter(),"red")) + instance->SetSide(SIDE_RED); + else if (strstr(instance->GetTeamFilter(),"blue")) + instance->SetSide(SIDE_BLUE); + + float pos_along_path = mLandScape->flrand(minpos + posdelta*n, minpos + posdelta*(n+1)); + float look_along_path = atof( defenseGroup->FindPairValue ( "pathalign", "1" ) ) ; + mLandScape->GetPathInfo (pathID, pos_along_path, tmp_pt, tmp_dir ); + origin[0] = tmp_pt[0]; + origin[1] = tmp_pt[1]; + + mLandScape->GetPathInfo (pathID, look_along_path, tmp_dir, tmp_pt ); + lookat[0] = tmp_pt[0]; + lookat[1] = tmp_pt[1]; + + origin[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * origin[0]; + origin[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * origin[1]; + origin[2] = mLandScape->GetBounds ( )[0][2] ; + + // look at a point along the path at this location + lookat[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * lookat[0]; + lookat[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * lookat[1]; + lookat[2] = 0; + + // Fixed height? (used for bridges) + if ( !atoi(group->FindPairValue ( "nodrop", "0" )) ) + { + origin[2] = mLandScape->GetBounds ( )[1][2] + 100; + } + + // Set the area of position + area = mAreaManager->CreateArea ( origin, instance->GetSpacingRadius(), instance->GetSpacingLine(), GetDefaultPadding(), 0, origin, lookat, instance->GetFlattenRadius()?true:false, true, instance->GetLockOrigin(), mSymmetric ); + area->EnableLookAt(false); + + if ( node->GetFlattenHeight ( ) == -1 ) + { + node->SetFlattenHeight ( 66 + mLandScape->irand(0,40) ); + } + instance->SetFlattenHeight ( node->GetFlattenHeight ( ) ); + + instance->SetArea ( mAreaManager, area ); + + mInstances.push_back ( instance ); + } + } + } + } + } + } + else + return false; + } + else + return false; + + } +#endif // #ifndef PRE_RELEASE_DEMO + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseInstance + * Parses an individual instance + * + * inputs: + * group: parser group containing the list of instances + * + * return: + * true: instances parsed successfully + * false: instances failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseInstance ( CGPGroup* group ) +{ + CRMArea* area; + CRMInstance* instance; + float spacing; + vec3_t origin; + vec3_t lookat; + int flattenHeight; + + // create fences / walls + + // Create the instance using the instance file helper class + instance = mInstanceFile.CreateInstance ( group->GetName ( ) ); + + // Failed to create, not good + if ( NULL == instance ) + { + return false; + } + + // If a spacing radius was specified then override the one thats + // in the instance + spacing = atof( group->FindPairValue ( "spacing", "0" ) ); + if ( spacing ) + { + instance->SetSpacingRadius ( spacing ); + } + + instance->SetFilter(group->FindPairValue("filter", "")); + instance->SetTeamFilter(group->FindPairValue("teamfilter", "")); + + if (strstr(instance->GetTeamFilter(),"red")) + instance->SetSide( SIDE_RED); + else if (strstr(instance->GetTeamFilter(),"blue")) + instance->SetSide( SIDE_BLUE ); + +// ParseWallRect(group, instance->GetSide()); + + // Get its origin now + ParseOrigin ( group->FindSubGroup ( "origin" ), origin, lookat, &flattenHeight ); + origin[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * origin[0]; + origin[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * origin[1]; + origin[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * origin[2]; + + lookat[0] = mLandScape->GetBounds ( )[0][0] + (mLandScape->GetBounds ( )[1][0]-mLandScape->GetBounds ( )[0][0]) * lookat[0]; + lookat[1] = mLandScape->GetBounds ( )[0][1] + (mLandScape->GetBounds ( )[1][1]-mLandScape->GetBounds ( )[0][1]) * lookat[1]; + lookat[2] = mLandScape->GetBounds ( )[0][2] + (mLandScape->GetBounds ( )[1][2]-mLandScape->GetBounds ( )[0][2]) * lookat[2]; + + // Fixed height? (used for bridges) + if ( !atoi(group->FindPairValue ( "nodrop", "0" )) ) + { + origin[2] = mLandScape->GetBounds ( )[1][2] + 100; + } + + // Set the area of position + area = mAreaManager->CreateArea ( origin, instance->GetSpacingRadius(), instance->GetSpacingLine(), GetDefaultPadding(), 0, vec3_origin, lookat, instance->GetFlattenRadius()?true:false, true, instance->GetLockOrigin(), mSymmetric ); + instance->SetArea ( mAreaManager, area ); + instance->SetFlattenHeight ( flattenHeight ); + + mInstances.push_back ( instance ); + + // create defenses? + ParseInstancesOnPath(group ); + + return true; +} + + +/************************************************************************************************ + * CRMMission::ParseInstances + * parses all instances within the mission and populates the instance list + * + * inputs: + * group: parser group containing the list of instances + * + * return: + * true: instances parsed successfully + * false: instances failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseInstances ( CGPGroup* group ) +{ +#ifndef PRE_RELEASE_DEMO + // If NULL that means this particular difficulty level has no instances + if ( NULL == group ) + { + return true; + } + + // Loop through all the instances in the mission and add each + // to the master list of instances + for ( group = group->GetSubGroups(); + group; + group=group->GetNext() ) + { + ParseInstance ( group ); + } +#endif // #ifndef PRE_RELEASE_DEMO + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseObjectives + * parses all objectives within the mission and populates the objective list + * + * inputs: + * group: parser group containing the list of objectives + * + * return: + * true: objectives parsed successfully + * false: objectives failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseObjectives ( CGPGroup* group ) +{ + // If NULL that means this particular difficulty level has no objectives + if ( NULL == group ) + { + return true; + } + + // Loop through all the objectives in the mission and add each + // to the master list of objectives + for ( group = group->GetSubGroups(); + group; + group=group->GetNext() ) + { + CRMObjective* objective; + + // Create the new objective + objective = new CRMObjective ( group ); + + mObjectives.push_back ( objective ); + } + + mValidObjectives = true; + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseAmmo + * parses the given ammo list and sets the necessary ammo cvars to grant those + * weapons to the players + * + * inputs: + * ammos: parser group containing the ammo list + * + * return: + * true: ammo parsed successfully + * false: ammo failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseAmmo ( CGPGroup* ammos ) +{ +/* CGPValue* ammo; + + // No weapons, no success + if ( NULL == ammos ) + { + return false; + } + + if (0 == gi.Cvar_VariableIntegerValue("ar_wpnselect")) + { + // Make sure the ammo cvars are all reset so ammo from the last map or + // another difficulty level wont carry over + CWeaponSystem::ClearAmmoCvars (TheWpnSysHelper()); + + ammo = ammos->GetPairs ( ); + + // Loop through the weapons listed and grant them to the player + while ( ammo ) + { + // Grab the weapons ID + AmmoID id = CWeaponSystem::GetAmmoID ( ammo->GetName ( ) ); + + // Now set the weapon cvar with the given data + TheWpnSysHelper().CvarSet ( CWeaponSystem::GetAmmoCvar ( id ), ammo->GetTopValue ( ), CVAR_AMMO ); + + // Move on to the next weapon + ammo = (CGPValue*)ammo->GetNext(); + } + } +*/ + mValidAmmo = true; + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseWeapons + * parses the given weapon list and sets the necessary weapon cvars to grant those + * weapons to the players + * + * inputs: + * weapons: parser group containing the weapons list + * + * return: + * true: weapons parsed successfully + * false: weapons failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseWeapons ( CGPGroup* weapons ) +{ +/* CGPValue* weapon; + WpnID id; + + // No weapons, no success + if ( NULL == weapons ) + { + return false; + } + + if (0 == gi.Cvar_VariableIntegerValue("ar_wpnselect")) + { + // Make sure the weapon cvars are all reset so weapons from the last map or + // another difficulty level wont carry over + CWeaponSystem::ClearWpnCvars (TheWpnSysHelper()); + + id = NULL_WpnID; + weapon = weapons->GetPairs ( ); + + // Loop through the weapons listed and grant them to the player + while ( weapon ) + { + // Grab the weapons ID + id = CWeaponSystem::GetWpnID ( weapon->GetName ( ) ); + + // Now set the weapon cvar with the given data + TheWpnSysHelper().CvarSet ( CWeaponSystem::GetWpnCvar ( id ), weapon->GetTopValue ( ) ); + + // Move on to the next weapon + weapon = (CGPValue*)weapon->GetNext(); + } + + // If we found at least one weapon then ready the last one in the list + if ( NULL_WpnID != id ) + { + TheWpnSysHelper().CvarSet("wp_righthand", va("%i/%i/0/0",id,CWeaponSystem::GetClipSize ( id )), CVAR_MISC ); + } + } +*/ + mValidWeapons = true; + + return true; +} + +/************************************************************************************************ + * CRMMission::ParseOutfit + * parses the outfit (weapons and ammo) + * + * inputs: + * outfit: parser group containing the outfit + * + * return: + * true: weapons and ammo parsed successfully + * false: failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseOutfit ( CGPGroup* outfit ) +{ + if ( NULL == outfit ) + { + return false; + } + +/* // Its ok to fail parsing weapons as long as weapons have + // already been parsed at some point + if ( !ParseWeapons ( ParseRandom ( outfit->FindSubGroup ( "weapons" ) ) ) ) + { + if ( !mValidWeapons ) + { + return false; + } + } + + // Its ok to fail parsing ammo as long as ammo have + // already been parsed at some point + if ( !ParseAmmo ( ParseRandom ( outfit->FindSubGroup ( "ammo" ) ) ) ) + { + if ( !mValidAmmo) + { + return false; + } + } +*/ + return true; +} + +/************************************************************************************************ + * CRMMission::ParseRandom + * selects a random sub group with from all within this one + * + * inputs: + * random: parser group containing the various subgroups + * + * return: + * true: parsed successfuly + * false: failed to parse + * + ************************************************************************************************/ +CGPGroup* CRMMission::ParseRandom ( CGPGroup* randomGroup ) +{ + if (NULL == randomGroup) + return NULL; + + CGPGroup* group; + CGPGroup* groups[MAX_RANDOM_CHOICES]; + int numGroups; + + // Build a list of the groups one can be chosen + for ( numGroups = 0, group = randomGroup->GetSubGroups ( ); + group; + group = group->GetNext ( ) ) + { + if ( stricmp ( group->GetName ( ), "random_choice" ) ) + { + continue; + } + + int weight = atoi ( group->FindPairValue ( "random_weight", "1" ) ); + while (weight-- > 0) + groups[numGroups++] = group; + assert (numGroups <= MAX_RANDOM_CHOICES); + } + + // No groups! + if ( !numGroups ) + { + return randomGroup; + } + + // Now choose a group to parse + return groups[mLandScape->irand(0,numGroups-1)]; +} + +/************************************************************************************************ + * CRMMission::ParseDifficulty + * parses the given difficulty and populates the mission with its data + * + * inputs: + * difficulty: parser group containing the difficulties info + * + * return: + * true: difficulty parsed successfully + * false: difficulty failed to parse + * + ************************************************************************************************/ +bool CRMMission::ParseDifficulty ( CGPGroup* difficulty ) +{ + // If a null difficulty then stop the recursion. Make sure to + // return true here so the parsing doesnt fail + if ( NULL == difficulty ) + { + return true; + } + // is map supposed to be symmetric? + mSymmetric = (symmetry_t)atoi(difficulty->GetParent()->FindPairValue ( "symmetric", "0" )); + mBackUpPath = atoi(difficulty->GetParent()->FindPairValue ( "backuppath", "0" )); + if( mSymmetric ) + {// pick between the 2 starting corners -- yes this is a hack + mSymmetric = SYMMETRY_TOPLEFT; + if( TheRandomMissionManager->GetLandScape()->irand(0, 1) ) + { + mSymmetric = SYMMETRY_BOTTOMRIGHT; + } + } + + mDefaultPadding = atoi(difficulty->GetParent()->FindPairValue ( "padding", "0" )); + + // Parse the nodes + if ( !ParseNodes ( ParseRandom ( difficulty->FindSubGroup ( "nodes" ) ) ) ) + { + return false; + } + + // Parse the paths + if ( !ParsePaths ( ParseRandom ( difficulty->FindSubGroup ( "paths" ) ) ) ) + { + return false; + } + + // Parse the rivers + if ( !ParseRivers ( ParseRandom ( difficulty->FindSubGroup ( "rivers" ) ) ) ) + { + return false; + } + + // Handle inherited properties + if ( !ParseDifficulty ( difficulty->GetParent ( )->FindSubGroup ( difficulty->FindPairValue ( "inherit", "" ) ) ) ) + { + return false; + } + +/* + // parse the player's outfit (weapons and ammo) + if ( !ParseOutfit( ParseRandom ( difficulty->FindSubGroup ( "outfit" ) ) ) ) + { + // Its ok to fail parsing weapons as long as weapons have + // already been parsed at some point + if ( !ParseWeapons ( ParseRandom ( difficulty->FindSubGroup ( "weapons" ) ) ) ) + { + if ( !mValidWeapons ) + { + return false; + } + } + + // Its ok to fail parsing ammo as long as ammo have + // already been parsed at some point + if ( !ParseAmmo ( ParseRandom ( difficulty->FindSubGroup ( "ammo" ) ) ) ) + { + if ( !mValidAmmo) + { + return false; + } + } + } + + // Its ok to fail parsing objectives as long as objectives have + // already been parsed at some point + if ( !ParseObjectives ( ParseRandom ( difficulty->FindSubGroup ( "objectives" ) ) ) ) + { + if ( !mValidObjectives ) + { + return false; + } + } +*/ + + // Set the cvars with the available values + Cvar_Set ( "mi_health", difficulty->FindPairValue ( "health", "100" ) ); + Cvar_Set ( "mi_armor", difficulty->FindPairValue ( "armor", "0" ) ); + + // Parse out the timelimit + mTimeLimit = atol(difficulty->FindPairValue("timelimit", "0")); + + // NPC multipliers + mAccuracyMultiplier = atof(difficulty->FindPairValue("npcaccuracy", "1")); + mHealthMultiplier = atof(difficulty->FindPairValue("npchealth", "1")); + + // keep only some of RMG pickups 1 = 100% + mPickupHealth = atof(difficulty->FindPairValue("pickup_health", "1")); + mPickupArmor = atof(difficulty->FindPairValue("pickup_armor", "1")); + mPickupAmmo = atof(difficulty->FindPairValue("pickup_ammo", "1")); + mPickupWeapon = atof(difficulty->FindPairValue("pickup_weapon", "1")); + mPickupEquipment = atof(difficulty->FindPairValue("pickup_equipment", "1")); + + // Its ok to fail parsing instances as long as instances have + // already been parsed at some point + if ( !ParseInstances ( ParseRandom ( difficulty->FindSubGroup ( "instances" ) ) ) ) + { + if ( !mValidInstances ) + { + return false; + } + } + + return true; +} + +/************************************************************************************************ + * CRMMission::Load + * Loads the given mission using the given difficulty level + * + * inputs: + * name: Name of the mission to load (should only be the name rather than the full path) + * difficulty: difficulty level to load + * + * return: + * true: mission successfully loaded + * false: mission failed to load + * + ************************************************************************************************/ +bool CRMMission::Load ( const char* mission, const char* instances, const char* difficulty ) +{ + CGenericParser2 parser; + CGPGroup* root; + + // Create the parser for the mission file + if(!Com_ParseTextFile(va("ext_data/rmg/%s.mission", mission), parser)) + { + if(!Com_ParseTextFile(va("ext_data/arioche/%s.mission", mission), parser)) + { + Com_Printf("ERROR: Failed to open mission file '%s'\n", mission); + return false; + } + } + + // Grab the root parser groop and make sure its mission, otherwise this + // isnt a valid mission file + root = parser.GetBaseParseGroup()->GetSubGroups(); + if(stricmp(root->GetName(), "mission")) + { + Com_Printf("ERROR: '%s' is not a valid mission file\n", mission ); + parser.Clean(); + return false; + } + + // Grab the mission description and set the cvar for it + mDescription = root->FindPairValue ( "description", "" ); +// Cvar_Set("ar_obj_main0",mDescription.c_str(), CVAR_OBJECTIVE); +// Cvar_Set("ar_obj_maincom0", "&OBJECTIVES_INPROGRESS&", CVAR_OBJECTIVE); +// Cvar_SetValue ("ar_cur_objective", 0, CVAR_OBJECTIVE); + + string mInfo = root->FindPairValue ( "info", "" ); +// Cvar_Set("ar_obj_info0",mInfo.c_str(), CVAR_OBJECTIVE); + + mExitScreen = root->FindPairValue ( "exitScreen", "" ); + mTimeExpiredScreen = root->FindPairValue ( "TimeExpiredScreen", "

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�v84zg~zu&kQ3}5oQPSR5L^;$39b(wiWOb}N$(1M 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 z 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 Q);^4@xszbMn4aVQ4_fB7S^xk5 literal 0 HcmV?d00001 diff --git a/code/StarWars.opt b/code/StarWars.opt new file mode 100644 index 0000000000000000000000000000000000000000..2a1659eb01085ab7be7670c57f6b264ab95685e3 GIT binary patch literal 84992 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 hKr$d1kPJu$Bmu&%6 literal 0 HcmV?d00001 diff --git a/code/bspthing/bsp.h b/code/bspthing/bsp.h new file mode 100644 index 0000000..31f3d8d --- /dev/null +++ b/code/bspthing/bsp.h @@ -0,0 +1,231 @@ +/* +============================================================================== + + Common stuff + +============================================================================== +*/ + +#define MAX_QPATH 64 // max length of a quake game pathname + +typedef float vec_t; +typedef vec_t vec2_t[2]; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; +typedef vec_t vec5_t[5]; + +typedef unsigned char byte; + +#define LIGHTMAP_SIZE 128 + +#define LIGHTMAP_BY_VERTEX -3 // pre-lit triangle models + +#define POINTS_ST_SCALE 128.0f +#define POINTS_LIGHT_SCALE 65536.0f + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + + +#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'R') +#define BSP_VERSION 1 + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x800 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_SHADERS 0x400 + +#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define MAX_MAP_FOGS 0x100 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x20000 +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x20000 +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x800000 +#define MAX_MAP_LIGHTGRID 65535 +#define MAX_MAP_LIGHTGRID_ARRAY 0x100000 + +#define MAX_MAP_VISIBILITY 0x400000 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 + + +// key / value pair sizes in the entities lump +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + +//============================================================================= + + +typedef struct { + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define LUMP_LIGHTARRAY 17 +#define HEADER_LUMPS 18 + +typedef struct { + int ident; + int version; + + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (do I still store these?) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int firstLeafSurface; + int numLeafSurfaces; + + int firstLeafBrush; + int numLeafBrushes; +} dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; + int drawSurfNum; +} dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +// Light Style Constants +#define MAXLIGHTMAPS 4 +#define LS_NORMAL 0x00 +#define LS_UNUSED 0xfe +#define LS_NONE 0xff +#define MAX_LIGHT_STYLES 64 + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[MAXLIGHTMAPS][2]; + vec3_t normal; + byte color[MAXLIGHTMAPS][4]; +} mapVert_t; + +#define DRAWVERT_LIGHTMAP_SCALE 32768.0f +#define DRAWVERT_ST_SCALE 16.0f +typedef struct { + vec3_t xyz; + short dvst[2]; + short dvlightmap[MAXLIGHTMAPS][2]; + vec3_t normal; + byte dvcolor[MAXLIGHTMAPS][2]; +} drawVert_t; + +typedef struct +{ + byte ambientLight[MAXLIGHTMAPS][3]; + byte directLight[MAXLIGHTMAPS][3]; + byte styles[MAXLIGHTMAPS]; + byte latLong[2]; +} dgrid_t; + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE +} mapSurfaceType_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + byte lightmapStyles[MAXLIGHTMAPS], vertexStyles[MAXLIGHTMAPS]; + int lightmapNum[MAXLIGHTMAPS]; + int lightmapX[MAXLIGHTMAPS], lightmapY[MAXLIGHTMAPS]; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} dsurface_t; diff --git a/code/bspthing/bspthing.sln b/code/bspthing/bspthing.sln new file mode 100644 index 0000000..7712673 --- /dev/null +++ b/code/bspthing/bspthing.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 7.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bspthing", "bspthing.vcproj", "{613ECD87-EBE1-47D9-A23D-68F52218AEB6}" +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + ConfigName.0 = Debug + ConfigName.1 = Release + EndGlobalSection + GlobalSection(ProjectDependencies) = postSolution + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {613ECD87-EBE1-47D9-A23D-68F52218AEB6}.Debug.ActiveCfg = Debug|Win32 + {613ECD87-EBE1-47D9-A23D-68F52218AEB6}.Debug.Build.0 = Debug|Win32 + {613ECD87-EBE1-47D9-A23D-68F52218AEB6}.Release.ActiveCfg = Release|Win32 + {613ECD87-EBE1-47D9-A23D-68F52218AEB6}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/code/bspthing/bspthing.vcproj b/code/bspthing/bspthing.vcproj new file mode 100644 index 0000000..1c087ee --- /dev/null +++ b/code/bspthing/bspthing.vcproj @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/bspthing/main.cpp b/code/bspthing/main.cpp new file mode 100644 index 0000000..6eea062 --- /dev/null +++ b/code/bspthing/main.cpp @@ -0,0 +1,1111 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "bsp.h" +#include "pbsp.h" + +#include "../qcommon/sparc.h" + +#define SWAP16(x) \ + big_endian ? \ + ((short)( ( ((x) & 0xff00) >> 8 ) + ( ((x) & 0xff) << 8 ) )) \ + : \ + x; + +#define SWAP32(x) \ + big_endian ? \ + ((int)( ( ((x) & 0xff000000) >> 24) \ + + ( ((x) & 0x00ff0000) >> 8 ) \ + + ( ((x) & 0x0000ff00) << 8 ) \ + + ( ((x) & 0x000000ff) << 24 ) )) \ + : \ + x; + +static float SWAPF(float x, bool big_endian) +{ + int temp = SWAP32(*(int*)&x); + return *(float*)&temp; +} + +#define CHECKED_READ(BUFFER, SIZE, OFFSET) \ + if (fseek(in, OFFSET, SEEK_SET) < 0 || \ + !fread(BUFFER, SIZE, 1, in)) \ + { \ + fprintf(stderr, "Error reading BSP.\n"); \ + assert(false); \ + exit(-1); \ + } + +#define CHECKED_WRITE(NAME, BUFFER, SIZE) \ + { \ + char fullname[256]; \ + sprintf(fullname, "%s\\%s.%s", \ + path, NAME, big_endian ? "mbe" : "mle"); \ + FILE* out = fopen(fullname, "wb"); \ + if (!out) \ + { \ + fprintf(stderr, "Error opening %s.\n", fullname); \ + assert(false); \ + exit(-1); \ + } \ + if (!fwrite(BUFFER, SIZE, 1, out)) \ + { \ + fprintf(stderr, "Error writing %s.\n", fullname); \ + assert(false); \ + exit(-1); \ + } \ + fclose(out); \ + } + +static void convert_fogs(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(pdfog_t); + if (num == 0) + { + return; + } + + pdfog_t* fogs = new pdfog_t[num]; + CHECKED_READ(fogs, lump.filelen, lump.fileofs); + + for (int i = 0; i < num; ++i) + { + fogs[i].brushNum = SWAP32(fogs[i].brushNum); + fogs[i].visibleSide = SWAP32(fogs[i].visibleSide); + } + + CHECKED_WRITE("fogs", fogs, lump.filelen); + + delete [] fogs; +} + +static void convert_brushes(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(dbrush_t); + dbrush_t* in_brushes = new dbrush_t[num]; + CHECKED_READ(in_brushes, lump.filelen, lump.fileofs); + + pdbrush_t* out_brushes = new pdbrush_t[num]; + + for (int i = 0; i < num; ++i) + { + assert(in_brushes[i].numSides >= 0 && in_brushes[i].numSides < 256); + assert(in_brushes[i].shaderNum >= 0 && in_brushes[i].shaderNum < 65536); + + out_brushes[i].firstSide = SWAP32(in_brushes[i].firstSide); + out_brushes[i].numSides = in_brushes[i].numSides; + out_brushes[i].shaderNum = SWAP16((unsigned short)in_brushes[i].shaderNum); + } + + int size = num * sizeof(pdbrush_t); + CHECKED_WRITE("brushes", out_brushes, size); + + delete [] out_brushes; + delete [] in_brushes; +} + +static void convert_brushsides(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(dbrushside_t); + dbrushside_t* in_brushesides = new dbrushside_t[num]; + CHECKED_READ(in_brushesides, lump.filelen, lump.fileofs); + + pdbrushside_t* out_brushesides = new pdbrushside_t[num]; + + for (int i = 0; i < num; ++i) + { + assert(in_brushesides[i].shaderNum >= 0 && in_brushesides[i].shaderNum < 256); + + out_brushesides[i].planeNum = SWAP32(in_brushesides[i].planeNum); + out_brushesides[i].shaderNum = in_brushesides[i].shaderNum; + } + + int size = num * sizeof(pdbrushside_t); + CHECKED_WRITE("brushsides", out_brushesides, size); + + delete [] out_brushesides; + delete [] in_brushesides; +} + +static void convert_leafsurfaces(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(int); + int* leafsurfaces = new int[num]; + CHECKED_READ(leafsurfaces, lump.filelen, lump.fileofs); + + for (int i = 0; i < num; ++i) + { + leafsurfaces[i] = SWAP32(leafsurfaces[i]); + } + + CHECKED_WRITE("leafsurfaces", leafsurfaces, lump.filelen); + + delete [] leafsurfaces; +} + +static void convert_nodes(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(dnode_t); + dnode_t* in_nodes = new dnode_t[num]; + CHECKED_READ(in_nodes, lump.filelen, lump.fileofs); + + pdnode_t* out_nodes = new pdnode_t[num]; + + for (int i = 0; i < num; ++i) + { + out_nodes[i].planeNum = SWAP32(in_nodes[i].planeNum); + + for (int j = 0; j < 2; ++j) + { + assert(in_nodes[i].children[j] > -32768 && in_nodes[i].children[j] < 32768); + out_nodes[i].children[j] = SWAP16((short)in_nodes[i].children[j]); + } + + for (int k = 0; k < 3; ++k) + { + assert(in_nodes[i].mins[k] > -32768 && in_nodes[i].mins[k] < 32768); + out_nodes[i].mins[k] = SWAP16((short)in_nodes[i].mins[k]); + + assert(in_nodes[i].maxs[k] > -32768 && in_nodes[i].maxs[k] < 32768); + out_nodes[i].maxs[k] = SWAP16((short)in_nodes[i].maxs[k]); + } + } + + int size = num * sizeof(pdnode_t); + CHECKED_WRITE("nodes", out_nodes, size); + + delete [] out_nodes; + delete [] in_nodes; +} + +static void convert_leafs(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(dleaf_t); + dleaf_t* in_leafs = new dleaf_t[num]; + CHECKED_READ(in_leafs, lump.filelen, lump.fileofs); + + pdleaf_t* out_leafs = new pdleaf_t[num]; + + for (int i = 0; i < num; ++i) + { + assert(in_leafs[i].cluster > -32768 && in_leafs[i].cluster < 32768); + out_leafs[i].cluster = SWAP16((short)in_leafs[i].cluster); + + assert(in_leafs[i].area > -128 && in_leafs[i].area < 128); + out_leafs[i].area = in_leafs[i].area; + + assert(in_leafs[i].firstLeafSurface >= 0 && in_leafs[i].firstLeafSurface < 65536); + out_leafs[i].firstLeafSurface = SWAP16((short)in_leafs[i].firstLeafSurface); + + assert(in_leafs[i].numLeafSurfaces >= 0 && in_leafs[i].numLeafSurfaces < 65536); + out_leafs[i].numLeafSurfaces = SWAP16((short)in_leafs[i].numLeafSurfaces); + + assert(in_leafs[i].firstLeafBrush >= 0 && in_leafs[i].firstLeafBrush < 65536); + out_leafs[i].firstLeafBrush = SWAP16((short)in_leafs[i].firstLeafBrush); + + assert(in_leafs[i].numLeafBrushes >= 0 && in_leafs[i].numLeafBrushes < 65536); + out_leafs[i].numLeafBrushes = SWAP16((short)in_leafs[i].numLeafBrushes); + + for (int k = 0; k < 3; ++k) + { + //assert(in_leafs[i].mins[k] > -32768 && in_leafs[i].mins[k] < 32768); + out_leafs[i].mins[k] = SWAP16((short)in_leafs[i].mins[k]); + + //assert(in_leafs[i].maxs[k] > -32768 && in_leafs[i].maxs[k] < 32768); + out_leafs[i].maxs[k] = SWAP16((short)in_leafs[i].maxs[k]); + } + } + + int size = num * sizeof(pdleaf_t); + CHECKED_WRITE("leafs", out_leafs, size); + + delete [] out_leafs; + delete [] in_leafs; +} + +static void convert_models(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(dmodel_t); + if (num == 0) + { + return; + } + + dmodel_t* in_models = new dmodel_t[num]; + CHECKED_READ(in_models, lump.filelen, lump.fileofs); + + pdmodel_t* out_models = new pdmodel_t[num]; + + for (int i = 0; i < num; ++i) + { + out_models[i].firstSurface = SWAP32(in_models[i].firstSurface); + + assert(in_models[i].numSurfaces >= 0 && in_models[i].numSurfaces < 65536); + out_models[i].numSurfaces = SWAP16((short)in_models[i].numSurfaces); + + out_models[i].firstBrush = SWAP32(in_models[i].firstBrush); + + assert(in_models[i].numBrushes >= 0 && in_models[i].numBrushes < 65536); + out_models[i].numBrushes = SWAP16((short)in_models[i].numBrushes); + + for (int k = 0; k < 3; ++k) + { + out_models[i].mins[k] = SWAPF(in_models[i].mins[k], big_endian); + out_models[i].maxs[k] = SWAPF(in_models[i].maxs[k], big_endian); + } + } + + int size = num * sizeof(pdmodel_t); + CHECKED_WRITE("models", out_models, size); + + delete [] out_models; + delete [] in_models; +} + +static void convert_entities(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + if (lump.filelen == 0) + { + return; + } + + char* entities = new char[lump.filelen]; + + CHECKED_READ(entities, lump.filelen, lump.fileofs); + CHECKED_WRITE("entities", entities, lump.filelen); + + delete [] entities; +} + +static void convert_lightgrid(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(dgrid_t); + if (num == 0) + { + return; + } + + dgrid_t* in_grid = new dgrid_t[num]; + CHECKED_READ(in_grid, lump.filelen, lump.fileofs); + + //figure out how much memory we really need. + int memory = 0; + int i, j; + for(i=0; i= size); + + char* out_grid = new char[size]; + char* data = out_grid + (sizeof(pdgrid_t) * num); + + //Copy data from old array into new array. + memory = 0; + for(i=0; ilatLong[0] = in_grid[i].latLong[0]; + g->latLong[1] = in_grid[i].latLong[1]; + g->flags = 0; + g->data = SWAP32(sizeof(pdgrid_t) * num + memory); + for(j=0; jflags |= 1 << (j + 4); + } + if( + in_grid[i].ambientLight[j][0] != 0 || + in_grid[i].ambientLight[j][1] != 0 || + in_grid[i].ambientLight[j][2] != 0 || + in_grid[i].directLight[j][0] != 0 || + in_grid[i].directLight[j][1] != 0 || + in_grid[i].directLight[j][2] != 0) { + data[memory++] = in_grid[i].ambientLight[j][0]; + data[memory++] = in_grid[i].ambientLight[j][1]; + data[memory++] = in_grid[i].ambientLight[j][2]; + data[memory++] = in_grid[i].directLight[j][0]; + data[memory++] = in_grid[i].directLight[j][1]; + data[memory++] = in_grid[i].directLight[j][2]; + g->flags |= 1 << j; + } + } + } + + CHECKED_WRITE("lightgrid", out_grid, size); + + delete [] out_grid; + delete [] in_grid; +} + +static void convert_lightarray(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(short); + if (num == 0) + { + return; + } + + short* lightarray = new short[num]; + CHECKED_READ(lightarray, lump.filelen, lump.fileofs); + + for (int i = 0; i < num; ++i) + { + lightarray[i] = SWAP16(lightarray[i]); + } + + CHECKED_WRITE("lightarray", lightarray, lump.filelen); + + delete [] lightarray; +} + +static void convert_shaders(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(pdshader_t); + if (num == 0) + { + return; + } + + pdshader_t* shaders = new pdshader_t[num]; + CHECKED_READ(shaders, lump.filelen, lump.fileofs); + + for (int i = 0; i < num; ++i) + { + shaders[i].surfaceFlags = SWAP32(shaders[i].surfaceFlags); + shaders[i].contentFlags = SWAP32(shaders[i].contentFlags); + } + + CHECKED_WRITE("shaders", shaders, lump.filelen); + + delete [] shaders; +} + +static void convert_planes(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(pdplane_t); + pdplane_t* planes = new pdplane_t[num]; + CHECKED_READ(planes, lump.filelen, lump.fileofs); + + for (int i = 0; i < num; ++i) + { + planes[i].normal[0] = SWAPF(planes[i].normal[0], big_endian); + planes[i].normal[1] = SWAPF(planes[i].normal[1], big_endian); + planes[i].normal[2] = SWAPF(planes[i].normal[2], big_endian); + planes[i].dist = SWAPF(planes[i].dist, big_endian); + } + + CHECKED_WRITE("planes", planes, lump.filelen); + + delete [] planes; +} + +static void convert_leafbrushes(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(int); + int* leafbrushes = new int[num]; + CHECKED_READ(leafbrushes, lump.filelen, lump.fileofs); + + for (int i = 0; i < num; ++i) + { + leafbrushes[i] = SWAP32(leafbrushes[i]); + } + + CHECKED_WRITE("leafbrushes", leafbrushes, lump.filelen); + + delete [] leafbrushes; +} + +static void convert_verts(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(mapVert_t); + mapVert_t* in_verts = new mapVert_t[num]; + CHECKED_READ(in_verts, lump.filelen, lump.fileofs); + + pmapVert_t* out_verts = new pmapVert_t[num]; + + for (int i = 0; i < num; ++i) + { + for (int j = 0; j < 3; ++j) + { + assert(in_verts[i].xyz[j] > -32768 && in_verts[i].xyz[j] < 32768); + out_verts[i].xyz[j] = SWAP16((short)in_verts[i].xyz[j]); + + assert(in_verts[i].normal[j] >= -1 && in_verts[i].normal[j] <= 1); + out_verts[i].normal[j] = SWAP16((short)(in_verts[i].normal[j] * 32767.f)); + } + + for (int k = 0; k < 2; ++k) + { + out_verts[i].st[k] = SWAPF(in_verts[i].st[k], big_endian); + } + + for (int m = 0; m < MAXLIGHTMAPS; ++m) + { + for (int n = 0; n < 2; ++n) + { + out_verts[i].lightmap[m][n] = SWAPF( + (in_verts[i].lightmap[m][n] * POINTS_LIGHT_SCALE), + big_endian); + } + + for (int p = 0; p < 4; ++p) + { + out_verts[i].color[m][p] = in_verts[i].color[m][p]; + } + } + } + + int size = num * sizeof(pmapVert_t); + CHECKED_WRITE("verts", out_verts, size); + + delete [] out_verts; + delete [] in_verts; +} + +static void convert_indexes(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(int); + int* in_indexes = new int[num]; + CHECKED_READ(in_indexes, lump.filelen, lump.fileofs); + + short* out_indexes = new short[num]; + + for (int i = 0; i < num; ++i) + { + assert(in_indexes[i] > -32768 && in_indexes[i] < 32768); + out_indexes[i] = SWAP16((short)in_indexes[i]); + } + + int size = num * sizeof(short); + CHECKED_WRITE("indexes", out_indexes, size); + + delete [] out_indexes; + delete [] in_indexes; +} + +static void scale_color(byte* dst, const byte* src, float factor) +{ + byte hichan = 0; + int hiindex = 0; + for (int c = 0; c < 3; ++c) + { + if (hichan < src[c]) + { + hichan = src[c]; + hiindex = c; + } + } + + float test = (float)src[hiindex] * factor; + if (test > 255.f) test = 255.f; + + factor = test / (float)src[hiindex]; + + dst[0] = (byte)((float)src[2] * factor); + dst[1] = (byte)((float)src[1] * factor); + dst[2] = (byte)((float)src[0] * factor); +} + +static void convert_lightmaps(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int in_map_size = LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3; + int bmp_size = in_map_size + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + + int num = lump.filelen / in_map_size; + if (num == 0) + { + return; + } + + unsigned char* in_lightmaps = new unsigned char[lump.filelen]; + CHECKED_READ(in_lightmaps, lump.filelen, lump.fileofs); + + // Setup a BMP file for conversion + char* bmp = new char[bmp_size]; + + BITMAPFILEHEADER* header = (BITMAPFILEHEADER*)bmp; + ((char*)&header->bfType)[0] = 'B'; + ((char*)&header->bfType)[1] = 'M'; + header->bfSize = bmp_size; + header->bfReserved1 = 0; + header->bfReserved2 = 0; + header->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + + BITMAPINFOHEADER* info = (BITMAPINFOHEADER*)(bmp + sizeof(BITMAPFILEHEADER)); + info->biSize = sizeof(BITMAPINFOHEADER); + info->biWidth = LIGHTMAP_SIZE; + info->biHeight = LIGHTMAP_SIZE; + info->biPlanes = 1; + info->biBitCount = 24; + info->biCompression = BI_RGB; + info->biSizeImage = 0; + info->biXPelsPerMeter = 1; + info->biYPelsPerMeter = 1; + info->biClrUsed = 0; + info->biClrImportant = 0; + + // Open the lightmaps output file + char fullname[256]; + sprintf(fullname, "%s\\lightmaps.%s", path, big_endian ? "mbe" : "mle"); + FILE* flightmaps = fopen(fullname, "wb"); + if (!flightmaps) + { + fprintf(stderr, "Error opening %s.\n", fullname); + assert(false); + exit(-1); + } + + // For each lightmap... + for (int i = 0; i < num; ++i) + { + // Update the BMP + unsigned char* in_map = in_lightmaps + i * in_map_size; + for (int y = 0; y < LIGHTMAP_SIZE; ++y) + { + for (int x = 0; x < LIGHTMAP_SIZE; ++x) + { + byte* dst = (byte*)bmp + header->bfOffBits + + ((LIGHTMAP_SIZE - y - 1) * LIGHTMAP_SIZE * 3) + + x * 3; + + byte* src = (byte*)in_map + + y * LIGHTMAP_SIZE * 3 + + x * 3; + + scale_color(dst, src, big_endian ? 1.225f : 1.1f); + } + } + + FILE* fbmp = fopen("~temp.bmp", "wb"); + if (!fbmp) + { + fprintf(stderr, "Error opening ~temp.bmp.\n"); + assert(false); + exit(-1); + } + + fwrite(bmp, bmp_size, 1, fbmp); + fclose(fbmp); + + char* out_map = NULL; + int out_map_size = 0; + + /* if (big_endian) + { + // Use the "texconvpro" GC conversion tool to convert + // ~temp.bmp to s3tc ~temp.out + FILE* ftcs = fopen("~temp.tcs", "wt"); + if (!ftcs) + { + fprintf(stderr, "Error opening ~temp.tcs.\n"); + assert(false); + exit(-1); + } + + fprintf(ftcs, "path = 0\n"); + fprintf(ftcs, "file 0 = ~temp.bmp\n"); + fprintf(ftcs, "image 0 = 0, 0, RGB5A3, 0, 0, 0\n"); + fprintf(ftcs, "texture 0 = 0, x\n"); + + fclose(ftcs); + + const char* cmdline = "texconvpro ~temp.tcs ~temp.tpl"; + system(cmdline); + + rename("~temp.tpl", "~temp.out"); + unlink("~temp.tcs"); + } + else + */ + { + // Use the "nvdxt" conversion tool to convert + // ~temp.bmp to s3tc ~temp.out + const char* cmdline = "nvdxt -file ~temp.bmp -u565 -nomipmap"; + system(cmdline); + + rename("~temp.dds", "~temp.out"); + } + + // Read compressed ~temp.out + FILE* fout = fopen("~temp.out", "rb"); + if (!fout) + { + fprintf(stderr, "Error opening ~temp.out.\n"); + assert(false); + exit(-1); + } + + fseek(fout, 0, SEEK_END); + out_map_size = ftell(fout); + fseek(fout, 0, SEEK_SET); + + out_map = new char[out_map_size]; + fread(out_map, out_map_size, 1, fout); + + fclose(fout); + + unlink("~temp.out"); + + // Append compressed data to lightmaps + if (i == 0) + { + int temp = SWAP32(out_map_size); + fwrite(&temp, sizeof(temp), 1, flightmaps); + } + + fwrite(out_map, out_map_size, 1, flightmaps); + + unlink("~temp.bmp"); + } + + fclose(flightmaps); + + delete [] bmp; + delete [] in_lightmaps; +} + +static void convert_visibility(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + if(lump.filelen == 0) + return; + + unsigned char* in_visibility = new unsigned char[lump.filelen]; + CHECKED_READ(in_visibility, lump.filelen, lump.fileofs); + + char* out_visibility = new char[lump.filelen*2]; + + *((int*)out_visibility + 0) = SWAP32(*((int*)in_visibility + 0)); + *((int*)out_visibility + 1) = SWAP32(*((int*)in_visibility + 1)); + + SPARC vis; + vis.Compress(in_visibility + 8, lump.filelen - 8, 0); + int size = vis.Save(out_visibility + 8, lump.filelen*2 - 8, big_endian) + 8; + + CHECKED_WRITE("visibility", out_visibility, size); + + delete [] out_visibility; + delete [] in_visibility; +} + +static void convert_faces(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + bool warning = false; + + int num = lump.filelen / sizeof(dsurface_t); + if (num == 0) + { + return; + } + + dsurface_t* in_surfaces = new dsurface_t[num]; + CHECKED_READ(in_surfaces, lump.filelen, lump.fileofs); + + pdface_t* out_surfaces = new pdface_t[num]; + + int counter = 0; + + for (int i = 0; i < num; ++i) + { + if (in_surfaces[i].surfaceType == MST_PLANAR) + { + out_surfaces[counter].code = SWAP32(i); + + assert(in_surfaces[i].shaderNum >= 0 && in_surfaces[i].shaderNum < 256); + out_surfaces[counter].shaderNum = in_surfaces[i].shaderNum; + + assert(in_surfaces[i].fogNum > -128 && in_surfaces[i].fogNum < 128); + out_surfaces[counter].fogNum = in_surfaces[i].fogNum; + + assert(in_surfaces[i].firstVert >= 0 && in_surfaces[i].firstVert < 1048576); + assert(in_surfaces[i].numVerts >= 0 && in_surfaces[i].numVerts < 4096); + out_surfaces[counter].verts = SWAP32((in_surfaces[i].firstVert << 12) | + (in_surfaces[i].numVerts & 0xfff)); + + assert(in_surfaces[i].firstIndex >= 0 && in_surfaces[i].firstIndex < 1048576); + assert(in_surfaces[i].numIndexes >= 0 && in_surfaces[i].numIndexes < 4096); + out_surfaces[counter].indexes = SWAP32((in_surfaces[i].firstIndex << 12) | + (in_surfaces[i].numIndexes & 0xfff)); + + for (int j = 0; j < MAXLIGHTMAPS; ++j) + { + if (!warning && + (in_surfaces[i].lightmapNum[j] < -4 || + in_surfaces[i].lightmapNum[j] >= 252)) + { + printf("WARNING: Lightmap index out of range!\n"); + warning = true; + } + + out_surfaces[counter].lightmapNum[j] = in_surfaces[i].lightmapNum[j] + 4; + + if (in_surfaces[i].lightmapNum[0] == LIGHTMAP_BY_VERTEX) + { + out_surfaces[counter].lightmapStyles[j] = in_surfaces[i].vertexStyles[j]; + } + else + { + out_surfaces[counter].lightmapStyles[j] = in_surfaces[i].lightmapStyles[j]; + } + } + + for (int m = 0; m < 3; ++m) + { + assert(in_surfaces[i].lightmapVecs[2][m] >= -1 && + in_surfaces[i].lightmapVecs[2][m] <= 1); + out_surfaces[counter].lightmapVecs[m] = + SWAP16((short)(in_surfaces[i].lightmapVecs[2][m] * 32767.f)); + } + + ++counter; + } + } + + if (counter == 0) return; + + int size = counter * sizeof(pdface_t); + CHECKED_WRITE("faces", out_surfaces, size); + + delete [] out_surfaces; + delete [] in_surfaces; +} + +static void convert_patches(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(dsurface_t); + if (num == 0) + { + return; + } + + dsurface_t* in_surfaces = new dsurface_t[num]; + CHECKED_READ(in_surfaces, lump.filelen, lump.fileofs); + + pdpatch_t* out_surfaces = new pdpatch_t[num]; + + int counter = 0; + + for (int i = 0; i < num; ++i) + { + if (in_surfaces[i].surfaceType == MST_PATCH) + { + out_surfaces[counter].code = SWAP32(i); + + assert(in_surfaces[i].shaderNum >= 0 && in_surfaces[i].shaderNum < 256); + out_surfaces[counter].shaderNum = in_surfaces[i].shaderNum; + + assert(in_surfaces[i].fogNum > -128 && in_surfaces[i].fogNum < 128); + out_surfaces[counter].fogNum = in_surfaces[i].fogNum; + + assert(in_surfaces[i].firstVert >= 0 && in_surfaces[i].firstVert < 1048576); + assert(in_surfaces[i].numVerts >= 0 && in_surfaces[i].numVerts < 4096); + out_surfaces[counter].verts = SWAP32((in_surfaces[i].firstVert << 12) | + (in_surfaces[i].numVerts & 0xfff)); + + assert(in_surfaces[i].patchWidth >= 0 && in_surfaces[i].patchWidth < 256); + out_surfaces[counter].patchWidth = in_surfaces[i].patchWidth; + + assert(in_surfaces[i].patchHeight >= 0 && in_surfaces[i].patchHeight < 256); + out_surfaces[counter].patchHeight = in_surfaces[i].patchHeight; + + for (int j = 0; j < MAXLIGHTMAPS; ++j) + { + assert(in_surfaces[i].lightmapNum[j] >= -4 && in_surfaces[i].lightmapNum[j] < 252); + out_surfaces[counter].lightmapNum[j] = in_surfaces[i].lightmapNum[j] + 4; + + if (in_surfaces[i].lightmapNum[0] == LIGHTMAP_BY_VERTEX) + { + out_surfaces[counter].lightmapStyles[j] = in_surfaces[i].vertexStyles[j]; + } + else + { + out_surfaces[counter].lightmapStyles[j] = in_surfaces[i].lightmapStyles[j]; + } + } + + for (int m = 0; m < 3; ++m) + { + for (int k = 0; k < 2; ++k) + { + assert(in_surfaces[i].lightmapVecs[k][m] > -32768.f && + in_surfaces[i].lightmapVecs[k][m] < 32768.f); + out_surfaces[counter].lightmapVecs[k][m] = + SWAP16((short)in_surfaces[i].lightmapVecs[k][m]); + } + } + + ++counter; + } + } + + if (counter == 0) return; + + int size = counter * sizeof(pdpatch_t); + CHECKED_WRITE("patches", out_surfaces, size); + + delete [] out_surfaces; + delete [] in_surfaces; +} + +static void convert_trisurfs(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(dsurface_t); + if (num == 0) + { + return; + } + + dsurface_t* in_surfaces = new dsurface_t[num]; + CHECKED_READ(in_surfaces, lump.filelen, lump.fileofs); + + pdtrisurf_t* out_surfaces = new pdtrisurf_t[num]; + + int counter = 0; + + for (int i = 0; i < num; ++i) + { + if (in_surfaces[i].surfaceType == MST_TRIANGLE_SOUP) + { + out_surfaces[counter].code = SWAP32(i); + + assert(in_surfaces[i].shaderNum >= 0 && in_surfaces[i].shaderNum < 256); + out_surfaces[counter].shaderNum = in_surfaces[i].shaderNum; + + assert(in_surfaces[i].fogNum > -128 && in_surfaces[i].fogNum < 128); + out_surfaces[counter].fogNum = in_surfaces[i].fogNum; + + assert(in_surfaces[i].firstVert >= 0 && in_surfaces[i].firstVert < 1048576); + assert(in_surfaces[i].numVerts >= 0 && in_surfaces[i].numVerts < 4096); + out_surfaces[counter].verts = SWAP32((in_surfaces[i].firstVert << 12) | + (in_surfaces[i].numVerts & 0xfff)); + + assert(in_surfaces[i].firstIndex >= 0 && in_surfaces[i].firstIndex < 1048576); + assert(in_surfaces[i].numIndexes >= 0 && in_surfaces[i].numIndexes < 4096); + out_surfaces[counter].indexes = SWAP32((in_surfaces[i].firstIndex << 12) | + (in_surfaces[i].numIndexes & 0xfff)); + + for (int j = 0; j < MAXLIGHTMAPS; ++j) + { + if (in_surfaces[i].lightmapNum[0] == LIGHTMAP_BY_VERTEX) + { + out_surfaces[counter].lightmapStyles[j] = in_surfaces[i].vertexStyles[j]; + } + else + { + out_surfaces[counter].lightmapStyles[j] = in_surfaces[i].lightmapStyles[j]; + } + } + + ++counter; + } + } + + if (counter == 0) return; + + int size = counter * sizeof(pdtrisurf_t); + CHECKED_WRITE("trisurfs", out_surfaces, size); + + delete [] out_surfaces; + delete [] in_surfaces; +} + +static void convert_flares(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + int num = lump.filelen / sizeof(dsurface_t); + if (num == 0) + { + return; + } + + dsurface_t* in_surfaces = new dsurface_t[num]; + CHECKED_READ(in_surfaces, lump.filelen, lump.fileofs); + + pdflare_t* out_surfaces = new pdflare_t[num]; + + int counter = 0; + + for (int i = 0; i < num; ++i) + { + if (in_surfaces[i].surfaceType == MST_FLARE) + { + out_surfaces[counter].code = SWAP32(i); + + assert(in_surfaces[i].shaderNum >= 0 && in_surfaces[i].shaderNum < 256); + out_surfaces[counter].shaderNum = in_surfaces[i].shaderNum; + + assert(in_surfaces[i].fogNum > -128 && in_surfaces[i].fogNum < 128); + out_surfaces[counter].fogNum = in_surfaces[i].fogNum; + + for (int j = 0; j < 3; ++j) + { + assert(in_surfaces[i].lightmapOrigin[j] > -32768 && in_surfaces[i].lightmapOrigin[j] < 32768); + out_surfaces[counter].origin[j] = SWAP16((short)in_surfaces[i].lightmapOrigin[j]); + + assert(in_surfaces[i].lightmapVecs[2][j] >= -1 && in_surfaces[i].lightmapVecs[2][j] <= 1); + out_surfaces[counter].normal[j] = SWAP16((short)(in_surfaces[i].lightmapVecs[2][j] * 32767.f)); + + out_surfaces[counter].color[j] = (byte)(in_surfaces[i].lightmapVecs[0][j]); + } + ++counter; + } + } + + if (counter == 0) return; + + int size = counter * sizeof(pdflare_t); + CHECKED_WRITE("flares", out_surfaces, size); + + delete [] out_surfaces; + delete [] in_surfaces; +} + +static void convert_surfaces(const lump_t& lump, FILE* in, bool big_endian, const char* path) +{ + convert_faces(lump, in, big_endian, path); + convert_patches(lump, in, big_endian, path); + convert_trisurfs(lump, in, big_endian, path); + convert_flares(lump, in, big_endian, path); +} + +typedef void (*convertfunc_t)(const lump_t& lump, FILE* in, bool big_endian, const char* path); +static convertfunc_t Converters[HEADER_LUMPS] = +{ + convert_entities, + convert_shaders, + convert_planes, + convert_nodes, + convert_leafs, + convert_leafsurfaces, + convert_leafbrushes, + convert_models, + convert_brushes, + convert_brushsides, + convert_verts, + convert_indexes, + convert_fogs, + convert_surfaces, + convert_lightmaps, + convert_lightgrid, + convert_visibility, + convert_lightarray, +}; + +static void write_misc(const dheader_t& header, FILE* in, bool big_endian, const char* path) +{ + int num = header.lumps[LUMP_SURFACES].filelen / sizeof(dsurface_t); + num = SWAP32(num); + + CHECKED_WRITE("misc", &num, sizeof(int)); +} + +/*static void optimize_lightmaps(const dheader_t* header, FILE* in) +{ + // get the vert data + int num_verts = header->lumps[LUMP_DRAWVERTS].filelen / sizeof(mapVert_t); + mapVert_t* verts = new mapVert_t[num_verts]; + CHECKED_READ(verts, + header->lumps[LUMP_DRAWVERTS].filelen, + header->lumps[LUMP_DRAWVERTS].fileofs); + + // get the surface data + int num_surfs = header->lumps[LUMP_SURFACES].filelen / sizeof(dsurface_t); + dsurface_t* surfs = new dsurface_t[num_surfs]; + CHECKED_READ(surfs, + header->lumps[LUMP_SURFACES].filelen, + header->lumps[LUMP_SURFACES].fileofs); + + // get the lightmap data + const int map_size = LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3; + int num_lightmaps = header->lumps[LUMP_LIGHTMAPS].filelen / map_size; + byte* lightmaps = new byte[header->lumps[LUMP_LIGHTMAPS].filelen]; + CHECKED_READ(lightmaps, + header->lumps[LUMP_LIGHTMAPS].filelen, + header->lumps[LUMP_LIGHTMAPS].fileofs); + + LMOptimizer optimizer; + optimizer.optimize(verts, num_verts, surfs, num_surfs, lightmaps, num_lightmaps); +}*/ + +static void process(const char* name, bool big_endian) +{ + // open the bsp + FILE* in = fopen(name, "rb"); + if (!in) + { + fprintf(stderr, "Unable to open %s\n", name); + exit(-1); + } + + // get the new path name + char* path = new char[strlen(name) + 1]; + strcpy(path, name); + path[strlen(path) - 4] = '\0'; + + mkdir(path); + + dheader_t in_header; + fread(&in_header, sizeof(in_header), 1, in); + + for (int i = 0; i < HEADER_LUMPS; ++i) + { + Converters[i](in_header.lumps[i], in, big_endian, path); + } + + write_misc(in_header, in, big_endian, path); + + delete [] path; + + fclose(in); +} + +int main(int argc, const char** argv) +{ + // check command line + if (argc != 2) + { + fprintf(stderr, "USAGE: %s PATH\n", argv[0]); + return -1; + } + + // find all the BSP files in the path + char spec[256]; + strcpy(spec, argv[1]); + strcat(spec, "\\*.bsp"); + + _finddata_t data; + int h = _findfirst(spec, &data); + while (h != -1) + { + printf("Processing %s...\n", data.name); + + char name[256]; + sprintf(name, "%s\\%s", argv[1], data.name); + + process(name, false); + + if (_findnext(h, &data)) break; + } + _findclose(h); + + return 0; +} \ No newline at end of file diff --git a/code/bspthing/pbsp.h b/code/bspthing/pbsp.h new file mode 100644 index 0000000..a3da97a --- /dev/null +++ b/code/bspthing/pbsp.h @@ -0,0 +1,132 @@ +/* +============================================================================== + + Packed .BSP structures + +============================================================================== +*/ + +#pragma pack(push, 1) +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} pdfog_t; + +typedef struct { + int firstSide; + byte numSides; + unsigned short shaderNum; // the shader that determines the contents flags +} pdbrush_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + byte shaderNum; +} pdbrushside_t; + +typedef struct { + int planeNum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; +} pdnode_t; + +typedef struct { + short cluster; // -1 = opaque cluster (do I still store these?) + signed char area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstLeafSurface; + unsigned short numLeafSurfaces; + + unsigned short firstLeafBrush; + unsigned short numLeafBrushes; +} pdleaf_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface; + unsigned short numSurfaces; + int firstBrush; + unsigned short numBrushes; +} pdmodel_t; + +typedef struct { + byte flags; + byte latLong[2]; + int data; +} pdgrid_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} pdshader_t; + +typedef struct { + float normal[3]; + float dist; +} pdplane_t; + +typedef struct { + float lightmap[MAXLIGHTMAPS][2]; + float st[2]; + short xyz[3]; + short normal[3]; + byte color[MAXLIGHTMAPS][4]; +} pmapVert_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + unsigned int verts; // high 20 bits are first vert, low 12 are num verts + + byte lightmapStyles[MAXLIGHTMAPS]; + byte lightmapNum[MAXLIGHTMAPS]; + + short lightmapVecs[2][3]; // for patches, [0] and [1] are lodbounds + + byte patchWidth; + byte patchHeight; +} pdpatch_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + unsigned int verts; // high 20 bits are first vert, low 12 are num verts + unsigned int indexes; // high 20 bits are first index, low 12 are num indices + + byte lightmapStyles[MAXLIGHTMAPS]; + byte lightmapNum[MAXLIGHTMAPS]; + + short lightmapVecs[3]; +} pdface_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + unsigned int verts; // high 20 bits are first vert, low 12 are num verts + unsigned int indexes; // high 20 bits are first index, low 12 are num indices + + byte lightmapStyles[MAXLIGHTMAPS]; +} pdtrisurf_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + short origin[3]; + short normal[3]; + byte color[3]; +} pdflare_t; + +#pragma pack(pop) diff --git a/code/cgame/FX_ATSTMain.cpp b/code/cgame/FX_ATSTMain.cpp new file mode 100644 index 0000000..c8e0812 --- /dev/null +++ b/code/cgame/FX_ATSTMain.cpp @@ -0,0 +1,105 @@ +// Bowcaster Weapon + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + + +/* +--------------------------- +FX_ATSTMainProjectileThink +--------------------------- +*/ +void FX_ATSTMainProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->gent->s.pos.trDelta, forward ) == 0.0f ) + { + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + } + + // hack the scale of the forward vector if we were just fired or bounced...this will shorten up the tail for a split second so tails don't clip so harshly + int dif = cg.time - cent->gent->s.pos.trTime; + + if ( dif < 30 ) + { + if ( dif < 0 ) + { + dif = 0; + } + + float scale = ( dif / 30.0f ) * 0.95f + 0.05f; + + VectorScale( forward, scale, forward ); + } + + theFxScheduler.PlayEffect( "atst/shot", cent->lerpOrigin, forward ); +} + +/* +--------------------------- +FX_ATSTMainHitWall +--------------------------- +*/ +void FX_ATSTMainHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( "atst/wall_impact", origin, normal ); +} + +/* +--------------------------- +FX_ATSTMainHitPlayer +--------------------------- +*/ +void FX_ATSTMainHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + if ( humanoid ) + { + theFxScheduler.PlayEffect( "atst/flesh_impact", origin, normal ); + } + else + { + theFxScheduler.PlayEffect( "atst/droid_impact", origin, normal ); + } +} + +/* +--------------------------- +FX_ATSTSideAltProjectileThink +--------------------------- +*/ +void FX_ATSTSideAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + theFxScheduler.PlayEffect( "atst/side_alt_shot", cent->lerpOrigin, forward ); +} + +/* +--------------------------- +FX_ATSTSideMainProjectileThink +--------------------------- +*/ +void FX_ATSTSideMainProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + theFxScheduler.PlayEffect( "atst/side_main_shot", cent->lerpOrigin, forward ); +} diff --git a/code/cgame/FX_Blaster.cpp b/code/cgame/FX_Blaster.cpp new file mode 100644 index 0000000..7438e03 --- /dev/null +++ b/code/cgame/FX_Blaster.cpp @@ -0,0 +1,95 @@ +// Blaster Weapon + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + +/* +------------------------- +FX_BlasterProjectileThink +------------------------- +*/ + +void FX_BlasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if (cent->currentState.eFlags & EF_USE_ANGLEDELTA) + { + AngleVectors(cent->currentState.angles, forward, 0, 0); + } + else + { + if ( VectorNormalize2( cent->gent->s.pos.trDelta, forward ) == 0.0f ) + { + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + } + } + + // hack the scale of the forward vector if we were just fired or bounced...this will shorten up the tail for a split second so tails don't clip so harshly + int dif = cg.time - cent->gent->s.pos.trTime; + + if ( dif < 75 ) + { + if ( dif < 0 ) + { + dif = 0; + } + + float scale = ( dif / 75.0f ) * 0.95f + 0.05f; + + VectorScale( forward, scale, forward ); + } + + if ( cent->gent && cent->gent->owner && cent->gent->owner->s.number > 0 ) + { + theFxScheduler.PlayEffect( "blaster/NPCshot", cent->lerpOrigin, forward ); + } + else + { + theFxScheduler.PlayEffect( cgs.effects.blasterShotEffect, cent->lerpOrigin, forward ); + } +} + +/* +------------------------- +FX_BlasterAltFireThink +------------------------- +*/ +void FX_BlasterAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + FX_BlasterProjectileThink( cent, weapon ); +} + +/* +------------------------- +FX_BlasterWeaponHitWall +------------------------- +*/ +void FX_BlasterWeaponHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( cgs.effects.blasterWallImpactEffect, origin, normal ); +} + +/* +------------------------- +FX_BlasterWeaponHitPlayer +------------------------- +*/ +void FX_BlasterWeaponHitPlayer( gentity_t *hit, vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + //temporary? just testing out the damage skin stuff -rww + if ( hit && hit->client && hit->ghoul2.size() ) + { + CG_AddGhoul2Mark(cgs.media.bdecal_burnmark1, flrand(3.5, 4.0), origin, normal, hit->s.number, + hit->client->ps.origin, hit->client->renderInfo.legsYaw, hit->ghoul2, hit->s.modelScale, Q_irand(10000, 13000)); + } + + theFxScheduler.PlayEffect( cgs.effects.blasterFleshImpactEffect, origin, normal ); +} diff --git a/code/cgame/FX_Bowcaster.cpp b/code/cgame/FX_Bowcaster.cpp new file mode 100644 index 0000000..9177232 --- /dev/null +++ b/code/cgame/FX_Bowcaster.cpp @@ -0,0 +1,66 @@ +// Bowcaster Weapon + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + +/* +--------------------------- +FX_BowcasterProjectileThink +--------------------------- +*/ + +void FX_BowcasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->gent->s.pos.trDelta, forward ) == 0.0f ) + { + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + } + + // hack the scale of the forward vector if we were just fired or bounced...this will shorten up the tail for a split second so tails don't clip so harshly + int dif = cg.time - cent->gent->s.pos.trTime; + + if ( dif < 75 ) + { + if ( dif < 0 ) + { + dif = 0; + } + + float scale = ( dif / 75.0f ) * 0.95f + 0.05f; + + VectorScale( forward, scale, forward ); + } + + theFxScheduler.PlayEffect( cgs.effects.bowcasterShotEffect, cent->lerpOrigin, forward ); +} + +/* +--------------------------- +FX_BowcasterHitWall +--------------------------- +*/ + +void FX_BowcasterHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( cgs.effects.bowcasterImpactEffect, origin, normal ); +} + +/* +--------------------------- +FX_BowcasterHitPlayer +--------------------------- +*/ + +void FX_BowcasterHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + theFxScheduler.PlayEffect( cgs.effects.bowcasterImpactEffect, origin, normal ); +} \ No newline at end of file diff --git a/code/cgame/FX_BryarPistol.cpp b/code/cgame/FX_BryarPistol.cpp new file mode 100644 index 0000000..f05ecde --- /dev/null +++ b/code/cgame/FX_BryarPistol.cpp @@ -0,0 +1,156 @@ +// Bryar Pistol Weapon Effects + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + +/* +------------------------- + + MAIN FIRE + +------------------------- +FX_BryarProjectileThink +------------------------- +*/ +void FX_BryarProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->gent->s.pos.trDelta, forward ) == 0.0f ) + { + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + } + + // hack the scale of the forward vector if we were just fired or bounced...this will shorten up the tail for a split second so tails don't clip so harshly + int dif = cg.time - cent->gent->s.pos.trTime; + + if ( dif < 75 ) + { + if ( dif < 0 ) + { + dif = 0; + } + + float scale = ( dif / 75.0f ) * 0.95f + 0.05f; + + VectorScale( forward, scale, forward ); + } + + if ( cent->gent && cent->gent->owner && cent->gent->owner->s.number > 0 ) + { + theFxScheduler.PlayEffect( "bryar/NPCshot", cent->lerpOrigin, forward ); + } + else + { + theFxScheduler.PlayEffect( cgs.effects.bryarShotEffect, cent->lerpOrigin, forward ); + } +} + +/* +------------------------- +FX_BryarHitWall +------------------------- +*/ +void FX_BryarHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( cgs.effects.bryarWallImpactEffect, origin, normal ); +} + +/* +------------------------- +FX_BryarHitPlayer +------------------------- +*/ +void FX_BryarHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + theFxScheduler.PlayEffect( cgs.effects.bryarFleshImpactEffect, origin, normal ); +} + + +/* +------------------------- + + ALT FIRE + +------------------------- +FX_BryarAltProjectileThink +------------------------- +*/ +void FX_BryarAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->gent->s.pos.trDelta, forward ) == 0.0f ) + { + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + } + + // hack the scale of the forward vector if we were just fired or bounced...this will shorten up the tail for a split second so tails don't clip so harshly + int dif = cg.time - cent->gent->s.pos.trTime; + + if ( dif < 75 ) + { + if ( dif < 0 ) + { + dif = 0; + } + + float scale = ( dif / 75.0f ) * 0.95f + 0.05f; + + VectorScale( forward, scale, forward ); + } + + // see if we have some sort of extra charge going on + for ( int t = 1; t < cent->gent->count; t++ ) + { + // just add ourselves over, and over, and over when we are charged + theFxScheduler.PlayEffect( cgs.effects.bryarPowerupShotEffect, cent->lerpOrigin, forward ); + } + + theFxScheduler.PlayEffect( cgs.effects.bryarShotEffect, cent->lerpOrigin, forward ); +} + +/* +------------------------- +FX_BryarAltHitWall +------------------------- +*/ +void FX_BryarAltHitWall( vec3_t origin, vec3_t normal, int power ) +{ + switch( power ) + { + case 4: + case 5: + theFxScheduler.PlayEffect( cgs.effects.bryarWallImpactEffect3, origin, normal ); + break; + + case 2: + case 3: + theFxScheduler.PlayEffect( cgs.effects.bryarWallImpactEffect2, origin, normal ); + break; + + default: + theFxScheduler.PlayEffect( cgs.effects.bryarWallImpactEffect, origin, normal ); + break; + } +} + +/* +------------------------- +FX_BryarAltHitPlayer +------------------------- +*/ +void FX_BryarAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + theFxScheduler.PlayEffect( cgs.effects.bryarFleshImpactEffect, origin, normal ); +} diff --git a/code/cgame/FX_Concussion.cpp b/code/cgame/FX_Concussion.cpp new file mode 100644 index 0000000..98f3f89 --- /dev/null +++ b/code/cgame/FX_Concussion.cpp @@ -0,0 +1,98 @@ +// Concussion Rifle Weapon + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + +/* +--------------------------- +FX_ConcProjectileThink +--------------------------- +*/ + +void FX_ConcProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + theFxScheduler.PlayEffect( "concussion/shot", cent->lerpOrigin, forward ); +} + +/* +--------------------------- +FX_ConcHitWall +--------------------------- +*/ + +void FX_ConcHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( "concussion/explosion", origin, normal ); +} + +/* +--------------------------- +FX_ConcHitPlayer +--------------------------- +*/ + +void FX_ConcHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + theFxScheduler.PlayEffect( "concussion/explosion", origin, normal ); +} + +/* +--------------------------- +FX_ConcAltShot +--------------------------- +*/ +static vec3_t WHITE ={1.0f,1.0f,1.0f}; + +void FX_ConcAltShot( vec3_t start, vec3_t end ) +{ + //"concussion/beam" + FX_AddLine( -1, start, end, 0.1f, 10.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + WHITE, WHITE, 0.0f, + 175, cgi_R_RegisterShader( "gfx/effects/blueLine" ), + 0, FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + + vec3_t BRIGHT={0.75f,0.5f,1.0f}; + + // add some beef + FX_AddLine( -1, start, end, 0.1f, 7.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + BRIGHT, BRIGHT, 0.0f, + 150, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), + 0, FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); +} + + +/* +--------------------------- +FX_ConcAltMiss +--------------------------- +*/ + +void FX_ConcAltMiss( vec3_t origin, vec3_t normal ) +{ + vec3_t pos, c1, c2; + + VectorMA( origin, 4.0f, normal, c1 ); + VectorCopy( c1, c2 ); + c1[2] += 4; + c2[2] += 12; + + VectorAdd( origin, normal, pos ); + pos[2] += 28; + + FX_AddBezier( origin, pos, c1, vec3_origin, c2, vec3_origin, 6.0f, 6.0f, 0.0f, 0.0f, 0.2f, 0.5f, WHITE, WHITE, 0.0f, 4000, cgi_R_RegisterShader( "gfx/effects/smokeTrail" ), FX_ALPHA_WAVE ); + + theFxScheduler.PlayEffect( "concussion/alt_miss", origin, normal ); +} \ No newline at end of file diff --git a/code/cgame/FX_DEMP2.cpp b/code/cgame/FX_DEMP2.cpp new file mode 100644 index 0000000..2a6ff68 --- /dev/null +++ b/code/cgame/FX_DEMP2.cpp @@ -0,0 +1,92 @@ +// DEMP2 Weapon + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" +#include "FxUtil.h" + +/* +--------------------------- +FX_DEMP2_ProjectileThink +--------------------------- +*/ + +void FX_DEMP2_ProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + +// theFxScheduler.PlayEffect( "demp2/shot", cent->lerpOrigin, forward ); +// theFxScheduler.PlayEffect( "demp2/shot2", cent->lerpOrigin, forward ); + theFxScheduler.PlayEffect( "demp2/projectile", cent->lerpOrigin, forward ); +} + +/* +--------------------------- +FX_DEMP2_HitWall +--------------------------- +*/ + +void FX_DEMP2_HitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( "demp2/wall_impact", origin, normal ); +} + +/* +--------------------------- +FX_DEMP2_HitPlayer +--------------------------- +*/ + +void FX_DEMP2_HitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + theFxScheduler.PlayEffect( "demp2/flesh_impact", origin, normal ); +} + +/* +--------------------------- +FX_DEMP2_AltProjectileThink +--------------------------- +*/ + +void FX_DEMP2_AltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + theFxScheduler.PlayEffect( "demp2/projectile", cent->lerpOrigin, forward ); +} + +//--------------------------------------------- +void FX_DEMP2_AltDetonate( vec3_t org, float size ) +{ + localEntity_t *ex; + + ex = CG_AllocLocalEntity(); + ex->leType = LE_FADE_SCALE_MODEL; + memset( &ex->refEntity, 0, sizeof( refEntity_t )); + + ex->refEntity.renderfx |= RF_VOLUMETRIC; + + ex->startTime = cg.time; + ex->endTime = ex->startTime + 1300; + + ex->radius = size; + ex->refEntity.customShader = cgi_R_RegisterShader( "gfx/effects/demp2shell" ); + + ex->refEntity.hModel = cgi_R_RegisterModel( "models/items/sphere.md3" ); + VectorCopy( org, ex->refEntity.origin ); + + ex->color[0] = ex->color[1] = ex->color[2] = 255.0f; +} diff --git a/code/cgame/FX_Disruptor.cpp b/code/cgame/FX_Disruptor.cpp new file mode 100644 index 0000000..6fb7816 --- /dev/null +++ b/code/cgame/FX_Disruptor.cpp @@ -0,0 +1,98 @@ +// Disruptor Weapon + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + + +/* +--------------------------- +FX_DisruptorMainShot +--------------------------- +*/ +static vec3_t WHITE ={1.0f,1.0f,1.0f}; + +void FX_DisruptorMainShot( vec3_t start, vec3_t end ) +{ + FX_AddLine( -1, start, end, 0.1f, 4.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + WHITE, WHITE, 0.0f, + 120, cgi_R_RegisterShader( "gfx/effects/redLine" ), + 0, FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); +} + + +/* +--------------------------- +FX_DisruptorAltShot +--------------------------- +*/ +void FX_DisruptorAltShot( vec3_t start, vec3_t end, qboolean fullCharge ) +{ + FX_AddLine( -1, start, end, 0.1f, 10.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + WHITE, WHITE, 0.0f, + 175, cgi_R_RegisterShader( "gfx/effects/redLine" ), + 0, FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + + if ( fullCharge ) + { + vec3_t YELLER={0.8f,0.7f,0.0f}; + + // add some beef + FX_AddLine( -1, start, end, 0.1f, 7.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + YELLER, YELLER, 0.0f, + 150, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), + 0, FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + } +} + +/* +--------------------------- +FX_DisruptorAltMiss +--------------------------- +*/ + +void FX_DisruptorAltMiss( vec3_t origin, vec3_t normal ) +{ + vec3_t pos, c1, c2; + + VectorMA( origin, 4.0f, normal, c1 ); + VectorCopy( c1, c2 ); + c1[2] += 4; + c2[2] += 12; + + VectorAdd( origin, normal, pos ); + pos[2] += 28; + + FX_AddBezier( origin, pos, c1, vec3_origin, c2, vec3_origin, 6.0f, 6.0f, 0.0f, 0.0f, 0.2f, 0.5f, WHITE, WHITE, 0.0f, 4000, cgi_R_RegisterShader( "gfx/effects/smokeTrail" ), FX_ALPHA_WAVE ); + + theFxScheduler.PlayEffect( "disruptor/alt_miss", origin, normal ); +} + +/* +--------------------------- +FX_KothosBeam +--------------------------- +*/ +void FX_KothosBeam( vec3_t start, vec3_t end ) +{ + FX_AddLine( -1, start, end, 0.1f, 10.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + WHITE, WHITE, 0.0f, + 175, cgi_R_RegisterShader( "gfx/misc/dr1" ), + 0, FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + + vec3_t YELLER={0.8f,0.7f,0.0f}; + + // add some beef + FX_AddLine( -1, start, end, 0.1f, 7.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + YELLER, YELLER, 0.0f, + 150, cgi_R_RegisterShader( "gfx/misc/whiteline2" ), + 0, FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); +} diff --git a/code/cgame/FX_Emplaced.cpp b/code/cgame/FX_Emplaced.cpp new file mode 100644 index 0000000..eb0fa54 --- /dev/null +++ b/code/cgame/FX_Emplaced.cpp @@ -0,0 +1,146 @@ +// Emplaced Weapon + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + +/* +--------------------------- +FX_EmplacedProjectileThink +--------------------------- +*/ + +void FX_EmplacedProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->gent->s.pos.trDelta, forward ) == 0.0f ) + { + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + } + + // hack the scale of the forward vector if we were just fired or bounced...this will shorten up the tail for a split second so tails don't clip so harshly + int dif = cg.time - cent->gent->s.pos.trTime; + + if ( dif < 75 ) + { + if ( dif < 0 ) + { + dif = 0; + } + + float scale = ( dif / 75.0f ) * 0.95f + 0.05f; + + VectorScale( forward, scale, forward ); + } + + // If tie-fighter missle use green shot. + if ( cent->currentState.weapon == WP_TIE_FIGHTER ) + { + theFxScheduler.PlayEffect( "ships/imp_blastershot", cent->lerpOrigin, forward ); + } + else + { + if ( cent->gent && cent->gent->owner && cent->gent->owner->activator && cent->gent->owner->activator->s.number > 0 ) + { + // NPC's do short shot + if ( cent->gent->alt_fire ) + { + theFxScheduler.PlayEffect( "eweb/shotNPC", cent->lerpOrigin, forward ); + } + else + { + theFxScheduler.PlayEffect( "emplaced/shotNPC", cent->lerpOrigin, forward ); + } + } + else + { + // players do long shot + if ( cent->gent && cent->gent->alt_fire ) + { + theFxScheduler.PlayEffect( "eweb/shotNPC", cent->lerpOrigin, forward ); + } + else + { + theFxScheduler.PlayEffect( "emplaced/shot", cent->lerpOrigin, forward ); + } + } + } +} + +/* +--------------------------- +FX_EmplacedHitWall +--------------------------- +*/ + +void FX_EmplacedHitWall( vec3_t origin, vec3_t normal, qboolean eweb ) +{ + if ( eweb ) + { + theFxScheduler.PlayEffect( "eweb/wall_impact", origin, normal ); + } + else + { + theFxScheduler.PlayEffect( "emplaced/wall_impact", origin, normal ); + } +} + +/* +--------------------------- +FX_EmplacedHitPlayer +--------------------------- +*/ + +void FX_EmplacedHitPlayer( vec3_t origin, vec3_t normal, qboolean eweb ) +{ + if ( eweb ) + { + theFxScheduler.PlayEffect( "eweb/flesh_impact", origin, normal ); + } + else + { + theFxScheduler.PlayEffect( "emplaced/wall_impact", origin, normal ); + } +} +/* +--------------------------- +FX_TurretProjectileThink +--------------------------- +*/ + +void FX_TurretProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->gent->s.pos.trDelta, forward ) == 0.0f ) + { + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + } + + // hack the scale of the forward vector if we were just fired or bounced...this will shorten up the tail for a split second so tails don't clip so harshly + int dif = cg.time - cent->gent->s.pos.trTime; + + if ( dif < 75 ) + { + if ( dif < 0 ) + { + dif = 0; + } + + float scale = ( dif / 75.0f ) * 0.95f + 0.05f; + + VectorScale( forward, scale, forward ); + } + + theFxScheduler.PlayEffect( "turret/shot", cent->lerpOrigin, forward ); +} \ No newline at end of file diff --git a/code/cgame/FX_Flechette.cpp b/code/cgame/FX_Flechette.cpp new file mode 100644 index 0000000..c232ff9 --- /dev/null +++ b/code/cgame/FX_Flechette.cpp @@ -0,0 +1,73 @@ +// Golan Arms Flechette Weapon + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + +/* +------------------------- +FX_FlechetteProjectileThink +------------------------- +*/ + +void FX_FlechetteProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + EvaluateTrajectoryDelta( ¢->gent->s.pos, cg.time, forward ); + + if ( VectorNormalize( forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + theFxScheduler.PlayEffect( cgs.effects.flechetteShotEffect, cent->lerpOrigin, forward ); +} + +/* +------------------------- +FX_FlechetteWeaponHitWall +------------------------- +*/ +void FX_FlechetteWeaponHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( cgs.effects.flechetteShotDeathEffect, origin, normal ); +} + +/* +------------------------- +FX_BlasterWeaponHitPlayer +------------------------- +*/ +void FX_FlechetteWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ +// if ( humanoid ) +// { + theFxScheduler.PlayEffect( cgs.effects.flechetteFleshImpactEffect, origin, normal ); +// } +// else +// { +// theFxScheduler.PlayEffect( "blaster/droid_impact", origin, normal ); +// } +} + +/* +------------------------- +FX_FlechetteProjectileThink +------------------------- +*/ + +void FX_FlechetteAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + theFxScheduler.PlayEffect( cgs.effects.flechetteAltShotEffect, cent->lerpOrigin, forward ); +} \ No newline at end of file diff --git a/code/cgame/FX_HeavyRepeater.cpp b/code/cgame/FX_HeavyRepeater.cpp new file mode 100644 index 0000000..4fd8a5b --- /dev/null +++ b/code/cgame/FX_HeavyRepeater.cpp @@ -0,0 +1,92 @@ +// Heavy Repeater Weapon + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + +/* +--------------------------- +FX_RepeaterProjectileThink +--------------------------- +*/ + +void FX_RepeaterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + theFxScheduler.PlayEffect( "repeater/projectile", cent->lerpOrigin, forward ); +} + +/* +------------------------ +FX_RepeaterHitWall +------------------------ +*/ + +void FX_RepeaterHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( "repeater/wall_impact", origin, normal ); +} + +/* +------------------------ +FX_RepeaterHitPlayer +------------------------ +*/ + +void FX_RepeaterHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + theFxScheduler.PlayEffect( "repeater/wall_impact", origin, normal ); +// theFxScheduler.PlayEffect( "repeater/flesh_impact", origin, normal ); +} + +/* +------------------------------ +FX_RepeaterAltProjectileThink +----------------------------- +*/ + +void FX_RepeaterAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + theFxScheduler.PlayEffect( "repeater/alt_projectile", cent->lerpOrigin, forward ); +// theFxScheduler.PlayEffect( "repeater/alt_projectile", cent->lerpOrigin, forward ); +} + +/* +------------------------ +FX_RepeaterAltHitWall +------------------------ +*/ + +void FX_RepeaterAltHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( "repeater/concussion", origin, normal ); +// theFxScheduler.PlayEffect( "repeater/alt_wall_impact2", origin, normal ); +} + +/* +------------------------ +FX_RepeaterAltHitPlayer +------------------------ +*/ + +void FX_RepeaterAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + theFxScheduler.PlayEffect( "repeater/concussion", origin ); +// theFxScheduler.PlayEffect( "repeater/alt_wall_impact2", origin, normal ); +} \ No newline at end of file diff --git a/code/cgame/FX_NoghriShot.cpp b/code/cgame/FX_NoghriShot.cpp new file mode 100644 index 0000000..568938a --- /dev/null +++ b/code/cgame/FX_NoghriShot.cpp @@ -0,0 +1,72 @@ +// Noghri Rifle + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + +/* +------------------------- +FX_NoghriShotProjectileThink +------------------------- +*/ + +void FX_NoghriShotProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->gent->s.pos.trDelta, forward ) == 0.0f ) + { + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + } + + // hack the scale of the forward vector if we were just fired or bounced...this will shorten up the tail for a split second so tails don't clip so harshly + int dif = cg.time - cent->gent->s.pos.trTime; + + if ( dif < 75 ) + { + if ( dif < 0 ) + { + dif = 0; + } + + float scale = ( dif / 75.0f ) * 0.95f + 0.05f; + + VectorScale( forward, scale, forward ); + } + + theFxScheduler.PlayEffect( "noghri_stick/shot", cent->lerpOrigin, forward ); +} + +/* +------------------------- +FX_NoghriShotWeaponHitWall +------------------------- +*/ +void FX_NoghriShotWeaponHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( "noghri_stick/flesh_impact", origin, normal );//no "noghri/wall_impact"? +} +/* +------------------------- +FX_NoghriShotWeaponHitPlayer +------------------------- +*/ +void FX_NoghriShotWeaponHitPlayer( gentity_t *hit, vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + //temporary? just testing out the damage skin stuff -rww + /* + if ( hit && hit->client && hit->ghoul2.size() ) + { + CG_AddGhoul2Mark(cgs.media.bdecal_burnmark1, flrand(3.5, 4.0), origin, normal, hit->s.number, + hit->client->ps.origin, hit->client->renderInfo.legsYaw, hit->ghoul2, hit->s.modelScale, Q_irand(10000, 13000)); + } + */ + + theFxScheduler.PlayEffect( "noghri_stick/flesh_impact", origin, normal ); +} diff --git a/code/cgame/FX_RocketLauncher.cpp b/code/cgame/FX_RocketLauncher.cpp new file mode 100644 index 0000000..7757976 --- /dev/null +++ b/code/cgame/FX_RocketLauncher.cpp @@ -0,0 +1,66 @@ +// Rocket Launcher Weapon + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + +/* +--------------------------- +FX_RocketProjectileThink +--------------------------- +*/ + +void FX_RocketProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + theFxScheduler.PlayEffect( "rocket/shot", cent->lerpOrigin, forward ); +} + +/* +--------------------------- +FX_RocketHitWall +--------------------------- +*/ + +void FX_RocketHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( "rocket/explosion", origin, normal ); +} + +/* +--------------------------- +FX_RocketHitPlayer +--------------------------- +*/ + +void FX_RocketHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + theFxScheduler.PlayEffect( "rocket/explosion", origin, normal ); +} + +/* +--------------------------- +FX_RocketAltProjectileThink +--------------------------- +*/ + +void FX_RocketAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + theFxScheduler.PlayEffect( "rocket/shot", cent->lerpOrigin, forward ); +} diff --git a/code/cgame/FX_TuskenShot.cpp b/code/cgame/FX_TuskenShot.cpp new file mode 100644 index 0000000..9810700 --- /dev/null +++ b/code/cgame/FX_TuskenShot.cpp @@ -0,0 +1,70 @@ +// Tusken Rifle + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +#include "cg_local.h" +#include "cg_media.h" +#include "FxScheduler.h" + +/* +------------------------- +FX_TuskenShotProjectileThink +------------------------- +*/ + +void FX_TuskenShotProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->gent->s.pos.trDelta, forward ) == 0.0f ) + { + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + } + + // hack the scale of the forward vector if we were just fired or bounced...this will shorten up the tail for a split second so tails don't clip so harshly + int dif = cg.time - cent->gent->s.pos.trTime; + + if ( dif < 75 ) + { + if ( dif < 0 ) + { + dif = 0; + } + + float scale = ( dif / 75.0f ) * 0.95f + 0.05f; + + VectorScale( forward, scale, forward ); + } + + theFxScheduler.PlayEffect( "tusken/shot", cent->lerpOrigin, forward ); +} + +/* +------------------------- +FX_TuskenShotWeaponHitWall +------------------------- +*/ +void FX_TuskenShotWeaponHitWall( vec3_t origin, vec3_t normal ) +{ + theFxScheduler.PlayEffect( "tusken/hitwall", origin, normal ); +} +/* +------------------------- +FX_TuskenShotWeaponHitPlayer +------------------------- +*/ +void FX_TuskenShotWeaponHitPlayer( gentity_t *hit, vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + //temporary? just testing out the damage skin stuff -rww + if ( hit && hit->client && hit->ghoul2.size() ) + { + CG_AddGhoul2Mark(cgs.media.bdecal_burnmark1, flrand(3.5, 4.0), origin, normal, hit->s.number, + hit->client->ps.origin, hit->client->renderInfo.legsYaw, hit->ghoul2, hit->s.modelScale, Q_irand(10000, 13000)); + } + + theFxScheduler.PlayEffect( "tusken/hit", origin, normal ); +} diff --git a/code/cgame/FxParsing.cpp b/code/cgame/FxParsing.cpp new file mode 100644 index 0000000..b445287 --- /dev/null +++ b/code/cgame/FxParsing.cpp @@ -0,0 +1,5 @@ +// this include must remain at the top of every FXxxxx.CPP file +#include "common_headers.h" + + + diff --git a/code/cgame/FxParsing.h b/code/cgame/FxParsing.h new file mode 100644 index 0000000..7c35c56 --- /dev/null +++ b/code/cgame/FxParsing.h @@ -0,0 +1,6 @@ +#pragma once +#if !defined(FX_PARSING_H_INC) +#define FX_PARSING_H_INC + + +#endif // FX_PARSING_H diff --git a/code/cgame/FxPrimitives.cpp b/code/cgame/FxPrimitives.cpp new file mode 100644 index 0000000..ad1c188 --- /dev/null +++ b/code/cgame/FxPrimitives.cpp @@ -0,0 +1,2301 @@ +// this include must remain at the top of every CPP file +#include "common_headers.h" + +#if !defined(FX_SCHEDULER_H_INC) + #include "FxScheduler.h" +#endif + +#include "cg_media.h" + +#pragma warning(disable: 4035) +#ifdef _M_IX86 +static long myftol( float f ) +{ + static int tmp; + __asm fld f + __asm fistp tmp + __asm mov eax, tmp +} +#else +static long myftol( float f ) +{ + return f; +} +#endif +#pragma warning(default: 4035) + +extern int drawnFx; +extern int mParticles; +extern int mOParticles; +extern int mLines; +extern int mTails; + +extern vmCvar_t fx_expensivePhysics; + +// Helper function +//------------------------- +void ClampVec( vec3_t dat, byte *res ) +{ + int r; + + // clamp all vec values, then multiply the normalized values by 255 to maximize the result + for ( int i = 0; i < 3; i++ ) + { + r = myftol(dat[i] * 255.0f); + + if ( r < 0 ) + { + r = 0; + } + else if ( r > 255 ) + { + r = 255; + } + + res[i] = (unsigned char)r; + } +} + +void GetOrigin( int clientID, vec3_t org ) +{ + if ( clientID >=0 ) + { + centity_t *cent = &cg_entities[clientID]; + + if ( cent && cent->gent && cent->gent->client ) + { + VectorCopy( cent->gent->client->renderInfo.muzzlePoint, org ); + } + } +} + +void GetDir( int clientID, vec3_t org ) +{ + if ( clientID >=0 ) + { + centity_t *cent = &cg_entities[clientID]; + + if ( cent && cent->gent && cent->gent->client ) + { + VectorCopy( cent->gent->client->renderInfo.muzzleDir, org ); + } + } +} + +//-------------------------- +// +// Base Effect Class +// +//-------------------------- + + +//-------------------------- +// +// Derived Particle Class +// +//-------------------------- + +void CParticle::Die() +{ + if ( mFlags & FX_DEATH_RUNS_FX && !(mFlags & FX_KILL_ON_IMPACT) ) + { + vec3_t norm; + + // Man, this just seems so, like, uncool and stuff... + VectorSet( norm, crandom(), crandom(), crandom()); + VectorNormalize( norm ); + + theFxScheduler.PlayEffect( mDeathFxID, mOrigin1, norm ); + } +} + +//---------------------------- +bool CParticle::Cull() +{ + vec3_t dir; + + // Get the direction to the view + VectorSubtract( mOrigin1, cg.refdef.vieworg, dir ); + + // Check if it's behind the viewer + if ( (DotProduct( cg.refdef.viewaxis[0], dir )) < 0 ) + { + return true; + } + + float len = VectorLengthSquared( dir ); + + // Can't be too close + if ( len < 16 * 16 ) + { + return true; + } + + return false; +} + +//---------------------------- +void CParticle::Draw() +{ + if ( mFlags & FX_DEPTH_HACK ) + { + // Not sure if first person needs to be set, but it can't hurt? + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + // Add our refEntity to the scene + VectorCopy( mOrigin1, mRefEnt.origin ); + theFxHelper.AddFxToScene( &mRefEnt ); + + drawnFx++; + mParticles++; +} + +//---------------------------- +// Update +//---------------------------- +bool CParticle::Update() +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + vec3_t org; + vec3_t ax[3]; + + // Get our current position and direction + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + const centity_t ¢ = cg_entities[mClientID]; + if (cent.currentValid && cent.gent->ghoul2.IsValid()) + { + if (!theFxHelper.GetOriginAxisFromBolt(cent, mModelNum, mBoltNum, org, ax)) + { //could not get bolt + return false; + } + } + else + { + // BTO - Fix for crappy FX system bug + return false; + } + } + else + {//fixme change this to bolt style... + vec3_t dir, ang; + + GetOrigin( mClientID, org ); + GetDir( mClientID, dir ); + + vectoangles( dir, ang ); + AngleVectors( ang, ax[0], ax[1], ax[2] ); + } + vec3_t realVel, realAccel; + + VectorMA( org, mOrgOffset[0], ax[0], org ); + VectorMA( org, mOrgOffset[1], ax[1], org ); + VectorMA( org, mOrgOffset[2], ax[2], org ); + + const float time = (theFxHelper.mTime - mTimeStart) * 0.001f; + // calc the real velocity and accel vectors + VectorScale( ax[0], mVel[0], realVel ); + VectorMA( realVel, mVel[1], ax[1], realVel ); + VectorMA( realVel, mVel[2], ax[2], realVel ); + realVel[2] += 0.5f * mGravity * time; + + VectorScale( ax[0], mAccel[0], realAccel ); + VectorMA( realAccel, mAccel[1], ax[1], realAccel ); + VectorMA( realAccel, mAccel[2], ax[2], realAccel ); + + // Get our real velocity at the current time, taking into account the effects of acceleartion. NOTE: not sure if this is even 100% correct math-wise + VectorMA( realVel, time, realAccel, realVel ); + + // Now move us to where we should be at the given time + VectorMA( org, time, realVel, mOrigin1 ); + + } + else if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) + { + // we are marked for death + return false; + } + + if ( !Cull()) + { + UpdateSize(); + UpdateRGB(); + UpdateAlpha(); + UpdateRotation(); + + Draw(); + } + + return true; +} + +//---------------------------- +// Update Origin +//---------------------------- +bool CParticle::UpdateOrigin() +{ + vec3_t new_origin; +// float ftime, time2; + + UpdateVelocity(); + + // Calc the time differences +// ftime = theFxHelper.mFrameTime * 0.001f; + //time2 = ftime * ftime * 0.5f; +// time2=0; + + // Predict the new position + new_origin[0] = mOrigin1[0] + theFxHelper.mFloatFrameTime * mVel[0];// + time2 * mVel[0]; + new_origin[1] = mOrigin1[1] + theFxHelper.mFloatFrameTime * mVel[1];// + time2 * mVel[1]; + new_origin[2] = mOrigin1[2] + theFxHelper.mFloatFrameTime * mVel[2];// + time2 * mVel[2]; + + // Only perform physics if this object is tagged to do so + if ( (mFlags & FX_APPLY_PHYSICS) ) + { + bool solid; + + if ( (mFlags&FX_EXPENSIVE_PHYSICS) + && fx_expensivePhysics.integer ) + { + solid = true; // by setting this to true, we force a real trace to happen + } + else + { + // if this returns solid, we need to do a trace + solid = !!(CG_PointContents( new_origin, ENTITYNUM_WORLD ) & ( MASK_SHOT | CONTENTS_WATER )); + } + + if ( solid ) + { + trace_t trace; + float dot; + + if ( mFlags & FX_USE_BBOX ) + { + if (mFlags & FX_GHOUL2_TRACE) + { + theFxHelper.G2Trace( &trace, mOrigin1, mMin, mMax, new_origin, ENTITYNUM_NONE, ( MASK_SHOT | CONTENTS_WATER ) ); + } + else + { + theFxHelper.Trace( &trace, mOrigin1, mMin, mMax, new_origin, -1, ( MASK_SHOT | CONTENTS_WATER ) ); + } + } + else + { + if (mFlags & FX_GHOUL2_TRACE) + { + theFxHelper.G2Trace( &trace, mOrigin1, NULL, NULL, new_origin, ENTITYNUM_NONE, ( MASK_SHOT | CONTENTS_WATER ) ); + } + else + { + theFxHelper.Trace( &trace, mOrigin1, NULL, NULL, new_origin, -1, ( MASK_SHOT | CONTENTS_WATER ) ); + } + } + + // Hit something + if ( trace.fraction < 1.0f )//|| trace.startsolid || trace.allsolid ) + { + if ( mFlags & FX_IMPACT_RUNS_FX && !(trace.surfaceFlags & SURF_NOIMPACT )) + { + theFxScheduler.PlayEffect( mImpactFxID, trace.endpos, trace.plane.normal ); + } + + if ( mFlags & FX_KILL_ON_IMPACT ) + { + // time to die + return false; + } + + VectorMA( mVel, theFxHelper.mFloatFrameTime * trace.fraction, mAccel, mVel ); + + dot = DotProduct( mVel, trace.plane.normal ); + + VectorMA( mVel, -2 * dot, trace.plane.normal, mVel ); + + VectorScale( mVel, mElasticity, mVel ); + + // If the velocity is too low, make it stop moving, rotating, and turn off physics to avoid + // doing expensive operations when they aren't needed + if ( trace.plane.normal[2] > 0 && mVel[2] < 4 ) + { + VectorClear( mVel ); + VectorClear( mAccel ); + + mFlags &= ~(FX_APPLY_PHYSICS|FX_IMPACT_RUNS_FX); + } + + // Set the origin to the exact impact point + VectorCopy( trace.endpos, mOrigin1 ); + return true; + } + } + } + + // No physics were done to this object, move it + VectorCopy( new_origin, mOrigin1 ); + + return true; +} + +//---------------------------- +// Update Size +//---------------------------- +void CParticle::UpdateSize() +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + + if ( (mFlags & FX_SIZE_LINEAR) ) + { + // calculate element biasing + perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) + / (float)(mTimeEnd - mTimeStart); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR, FX_WAVE, or FX_CLAMP + if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_NONLINEAR ) + { + if ( theFxHelper.mTime > mSizeParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)(theFxHelper.mTime - mSizeParm) + / (float)(mTimeEnd - mSizeParm); + } + + if ( mFlags & FX_SIZE_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * (float)cos( (theFxHelper.mTime - mTimeStart) * mSizeParm ); + } + else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_CLAMP ) + { + if ( theFxHelper.mTime < mSizeParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mSizeParm - theFxHelper.mTime) + / (float)(mSizeParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( (mFlags & FX_SIZE_LINEAR) ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if (( mFlags & FX_SIZE_RAND )) + { + // Random simply modulates the existing value + perc1 = random() * perc1; + } + + mRefEnt.radius = (mSizeStart * perc1) + (mSizeEnd * (1.0f - perc1)); +} + +//---------------------------- +// Update RGB +//---------------------------- +void CParticle::UpdateRGB() +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + vec3_t res; + + if ( (mFlags & FX_RGB_LINEAR) ) + { + // calculate element biasing + perc1 = 1.0f - (float)( theFxHelper.mTime - mTimeStart ) + / (float)( mTimeEnd - mTimeStart ); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR, FX_WAVE, or FX_CLAMP + if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_NONLINEAR ) + { + if ( theFxHelper.mTime > mRGBParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)( theFxHelper.mTime - mRGBParm ) + / (float)( mTimeEnd - mRGBParm ); + } + + if ( (mFlags & FX_RGB_LINEAR) ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * (float)cos(( theFxHelper.mTime - mTimeStart ) * mRGBParm ); + } + else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_CLAMP ) + { + if ( theFxHelper.mTime < mRGBParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mRGBParm - theFxHelper.mTime) + / (float)(mRGBParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if (( mFlags & FX_RGB_LINEAR )) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if (( mFlags & FX_RGB_RAND )) + { + // Random simply modulates the existing value + perc1 = random() * perc1; + } + + // Now get the correct color + VectorScale( mRGBStart, perc1, res ); + VectorMA( res, (1.0f - perc1), mRGBEnd, mRefEnt.angles ); // angles is a temp storage, will get clamped to a byte in the UpdateAlpha section +} + + +//---------------------------- +// Update Alpha +//---------------------------- +void CParticle::UpdateAlpha() +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + + if ( mFlags & FX_ALPHA_LINEAR ) + { + // calculate element biasing + perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) + / (float)(mTimeEnd - mTimeStart); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR, FX_WAVE, or FX_CLAMP + if (( mFlags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_NONLINEAR ) + { + if ( theFxHelper.mTime > mAlphaParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)(theFxHelper.mTime - mAlphaParm) + / (float)(mTimeEnd - mAlphaParm); + } + + if ( mFlags & FX_ALPHA_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * (float)cos( (theFxHelper.mTime - mTimeStart) * mAlphaParm ); + } + else if (( mFlags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_CLAMP ) + { + if ( theFxHelper.mTime < mAlphaParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mAlphaParm - theFxHelper.mTime) + / (float)(mAlphaParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( mFlags & FX_ALPHA_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + perc1 = (mAlphaStart * perc1) + (mAlphaEnd * (1.0f - perc1)); + + // We should be in the right range, but clamp to ensure + if ( perc1 < 0.0f ) + { + perc1 = 0.0f; + } + else if ( perc1 > 1.0f ) + { + perc1 = 1.0f; + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( (mFlags & FX_ALPHA_RAND) ) + { + // Random simply modulates the existing value + perc1 = random() * perc1; + } + + if ( mFlags & FX_USE_ALPHA ) + { + // should use this when using art that has an alpha channel + ClampVec( mRefEnt.angles, (byte*)(&mRefEnt.shaderRGBA) ); + mRefEnt.shaderRGBA[3] = (byte)(perc1 * 0xff); + } + else + { + // Modulate the rgb fields by the alpha value to do the fade, works fine for additive blending + VectorScale( mRefEnt.angles, perc1, mRefEnt.angles ); + ClampVec( mRefEnt.angles, (byte*)(&mRefEnt.shaderRGBA) ); + } +} + +//-------------------------------- +// +// Derived Oriented Particle Class +// +//-------------------------------- + + +//---------------------------- +bool COrientedParticle::Cull() +{ + vec3_t dir; + + // Get the direction to the view + VectorSubtract( mOrigin1, cg.refdef.vieworg, dir ); + + // Check if it's behind the viewer + if ( (DotProduct( cg.refdef.viewaxis[0], dir )) < 0 ) + { + return true; + } + + float len = VectorLengthSquared( dir ); + + // Can't be too close + if ( len < 24 * 24 ) + { + return true; + } + + return false; +} + +//---------------------------- +void COrientedParticle::Draw() +{ + if ( mFlags & FX_DEPTH_HACK ) + { + // Not sure if first person needs to be set + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + // Add our refEntity to the scene + VectorCopy( mOrigin1, mRefEnt.origin ); + VectorCopy( mNormal, mRefEnt.axis[0] ); + theFxHelper.AddFxToScene( &mRefEnt ); + + drawnFx++; + mOParticles++; +} + +//---------------------------- +// Update +//---------------------------- +bool COrientedParticle::Update() +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + vec3_t org; + vec3_t ax[3]; + + // Get our current position and direction + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + const centity_t ¢ = cg_entities[mClientID]; + if (cent.gent->ghoul2.IsValid()) + { + if (!theFxHelper.GetOriginAxisFromBolt(cent, mModelNum, mBoltNum, org, ax)) + { //could not get bolt + return false; + } + } + } + else + {//fixme change this to bolt style... + vec3_t dir, ang; + + GetOrigin( mClientID, org ); + GetDir( mClientID, dir ); + + vectoangles( dir, ang ); + AngleVectors( ang, ax[0], ax[1], ax[2] ); + } + vec3_t realVel, realAccel; + + VectorMA( org, mOrgOffset[0], ax[0], org ); + VectorMA( org, mOrgOffset[1], ax[1], org ); + VectorMA( org, mOrgOffset[2], ax[2], org ); + + const float time = (theFxHelper.mTime - mTimeStart) * 0.001f; + // calc the real velocity and accel vectors + VectorScale( ax[0], mVel[0], realVel ); + VectorMA( realVel, mVel[1], ax[1], realVel ); + VectorMA( realVel, mVel[2], ax[2], realVel ); + realVel[2] += 0.5f * mGravity * time; + + VectorScale( ax[0], mAccel[0], realAccel ); + VectorMA( realAccel, mAccel[1], ax[1], realAccel ); + VectorMA( realAccel, mAccel[2], ax[2], realAccel ); + + // Get our real velocity at the current time, taking into account the effects of acceleartion. NOTE: not sure if this is even 100% correct math-wise + VectorMA( realVel, time, realAccel, realVel ); + + // Now move us to where we should be at the given time + VectorMA( org, time, realVel, mOrigin1 ); + + //use the normalOffset and add that to the actual normal of the bolt + //NOTE: not tested!!! + vec3_t boltAngles, offsetAngles, transformedAngles; + vectoangles( ax[0], boltAngles ); + vectoangles( mNormalOffset, offsetAngles ); + VectorAdd( boltAngles, offsetAngles, transformedAngles ); + AngleVectors( transformedAngles, mNormal, NULL, NULL ); + } + else if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) + { + // we are marked for death + return false; + } + + if ( !Cull()) + { + UpdateSize(); + UpdateRGB(); + UpdateAlpha(); + UpdateRotation(); + + Draw(); + } + + return true; +} + + +//---------------------------- +// +// Derived Line Class +// +//---------------------------- + +//---------------------------- +void CLine::Draw() +{ + if ( mFlags & FX_DEPTH_HACK ) + { + // Not sure if first person needs to be set, but it can't hurt? + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + VectorCopy( mOrigin1, mRefEnt.origin ); + VectorCopy( mOrigin2, mRefEnt.oldorigin ); + + theFxHelper.AddFxToScene( &mRefEnt ); + + drawnFx++; + mLines++; +} + +//---------------------------- +bool CLine::Update() +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + vec3_t ax[3] = {0}; + // Get our current position and direction + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + const centity_t ¢ = cg_entities[mClientID]; + if (cent.gent->ghoul2.IsValid()) + { + if (!theFxHelper.GetOriginAxisFromBolt(cent, mModelNum, mBoltNum, mOrigin1, ax)) + { //could not get bolt + return false; + } + } + } + else + {//fixme change this to bolt style... + // Get our current position and direction + GetOrigin( mClientID, mOrigin1 ); + GetDir( mClientID, ax[0] ); + } + + VectorAdd(mOrigin1, mOrgOffset, mOrigin1); //add the offset to the bolt point + + vec3_t end; + trace_t trace; + if ( mFlags & FX_APPLY_PHYSICS ) + { + VectorMA( mOrigin1, 2048, ax[0], end ); + + theFxHelper.Trace( &trace, mOrigin1, NULL, NULL, end, mClientID, MASK_SHOT ); + + VectorCopy( trace.endpos, mOrigin2 ); + + if ( mImpactFxID > 0 ) + { + theFxScheduler.PlayEffect( mImpactFxID, trace.endpos, trace.plane.normal ); + } + } + else + { + VectorMA( mOrigin1, mVel[0], ax[0], mOrigin2 ); + VectorMA( mOrigin2, mVel[1], ax[1], mOrigin2 ); + VectorMA( mOrigin2, mVel[2], ax[2], mOrigin2 ); + } + } + + UpdateSize(); + UpdateRGB(); + UpdateAlpha(); + + Draw(); + + return true; +} + + +//---------------------------- +// +// Derived Electricity Class +// +//---------------------------- +void CElectricity::Initialize() +{ + mRefEnt.frame = random() * 1265536; + mRefEnt.endTime = cg.time + (mTimeEnd - mTimeStart); + + if ( mFlags & FX_DEPTH_HACK ) + { + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + if ( mFlags & FX_BRANCH ) + { + mRefEnt.renderfx |= RF_FORKED; + } + + if ( mFlags & FX_TAPER ) + { + mRefEnt.renderfx |= RF_TAPERED; + } + + if ( mFlags & FX_GROW ) + { + mRefEnt.renderfx |= RF_GROW; + } +} + +//---------------------------- +void CElectricity::Draw() +{ + VectorCopy( mOrigin1, mRefEnt.origin ); + VectorCopy( mOrigin2, mRefEnt.oldorigin ); + mRefEnt.angles[0] = mChaos; + mRefEnt.angles[1] = mTimeEnd - mTimeStart; + + theFxHelper.AddFxToScene( &mRefEnt ); + + drawnFx++; + mLines++; // NOT REALLY A LINE! +} + +//---------------------------- +bool CElectricity::Update() +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + //Handle Relative and Bolted Effects + if ( mFlags & FX_RELATIVE ) + {//add mOrgOffset to bolt position and store in mOrigin1 + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + vec3_t ax[3] = {0}; + // Get our current position and direction + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + const centity_t ¢ = cg_entities[mClientID]; + if (cent.gent->ghoul2.IsValid()) + { + if (!theFxHelper.GetOriginAxisFromBolt(cent, mModelNum, mBoltNum, mOrigin1, ax)) + { //could not get bolt + return false; + } + } + } + else + {//fixme change this to bolt style... + // Get our current position and direction + GetOrigin( mClientID, mOrigin1 ); + GetDir( mClientID, ax[0] ); + } + + //add the offset to the bolt point + VectorAdd(mOrigin1, mOrgOffset, mOrigin1); + + //add the endpoint offset to the start to get the final offset + VectorMA( mOrigin1, mVel[0], ax[0], mOrigin2 ); + VectorMA( mOrigin2, mVel[1], ax[1], mOrigin2 ); + VectorMA( mOrigin2, mVel[2], ax[2], mOrigin2 ); + } + //else just uses the static origin1 & origin2 as start and end + UpdateSize(); + UpdateRGB(); + UpdateAlpha(); + + Draw(); + + return true; +} + + +//---------------------------- +// +// Derived Tail Class +// +//---------------------------- +bool CTail::Cull() +{ + vec3_t dir; + + // Get the direction to the view + VectorSubtract( mOrigin1, cg.refdef.vieworg, dir ); + + // Check if it's behind the viewer + if ( (DotProduct( cg.refdef.viewaxis[0], dir )) < 0 ) + { + return true; + } + + return false; +} + +//---------------------------- +void CTail::Draw() +{ + if ( mFlags & FX_DEPTH_HACK ) + { + // Not sure if first person needs to be set + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + VectorCopy( mOrigin1, mRefEnt.origin ); + + theFxHelper.AddFxToScene( &mRefEnt ); + + drawnFx++; + mTails++; +} + +//---------------------------- +bool CTail::Update() +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( !fx_freeze.integer ) + { + VectorCopy( mOrigin1, mOldOrigin ); + } + + if ( mFlags & FX_RELATIVE ) + { + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + vec3_t org; + vec3_t ax[3]; + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + const centity_t ¢ = cg_entities[mClientID]; + if (cent.gent->ghoul2.IsValid()) + { + if (!theFxHelper.GetOriginAxisFromBolt(cent, mModelNum, mBoltNum, org, ax)) + { //could not get bolt + return false; + } + } + } + else + { + vec3_t dir; + // Get our current position and direction + GetOrigin( mClientID, org ); + GetDir( mClientID, dir ); + vec3_t ang; + + vectoangles( dir, ang ); + AngleVectors( ang, ax[0], ax[1], ax[2] ); + } + + vec3_t realVel, realAccel; + + VectorMA( org, mOrgOffset[0], ax[0], org ); + VectorMA( org, mOrgOffset[1], ax[1], org ); + VectorMA( org, mOrgOffset[2], ax[2], org ); + + // calc the real velocity and accel vectors + // FIXME: if you want right and up movement in addition to the forward movement, you'll have to convert dir into a set of perp. axes and do some extra work + VectorScale( ax[0], mVel[0], realVel ); + VectorMA( realVel, mVel[1], ax[1], realVel ); + VectorMA( realVel, mVel[2], ax[2], realVel ); + + VectorScale( ax[0], mAccel[0], realAccel ); + VectorMA( realAccel, mAccel[1], ax[1], realAccel ); + VectorMA( realAccel, mAccel[2], ax[2], realAccel ); + + const float time = (theFxHelper.mTime - mTimeStart) * 0.001f; + + // Get our real velocity at the current time, taking into account the effects of acceleration. NOTE: not sure if this is even 100% correct math-wise + VectorMA( realVel, time, realAccel, realVel ); + + // Now move us to where we should be at the given time + VectorMA( org, time, realVel, mOrigin1 ); + + // Just calc an old point some time in the past, doesn't really matter when + VectorMA( org, (time - 0.003f), realVel, mOldOrigin ); + } + else if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) + { + // we are marked for death + return false; + } + + if ( !Cull() ) + { + UpdateSize(); + UpdateLength(); + UpdateRGB(); + UpdateAlpha(); + + CalcNewEndpoint(); + + Draw(); + } + + return true; +} + +//---------------------------- +void CTail::UpdateLength() +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + + if ( mFlags & FX_LENGTH_LINEAR ) + { + // calculate element biasing + perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) + / (float)(mTimeEnd - mTimeStart); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE + if (( mFlags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_NONLINEAR ) + { + if ( theFxHelper.mTime > mLengthParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)(theFxHelper.mTime - mLengthParm) + / (float)(mTimeEnd - mLengthParm); + } + + if ( mFlags & FX_LENGTH_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * (float)cos( (theFxHelper.mTime - mTimeStart) * mLengthParm ); + } + else if (( mFlags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_CLAMP ) + { + if ( theFxHelper.mTime < mLengthParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mLengthParm - theFxHelper.mTime) + / (float)(mLengthParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( mFlags & FX_LENGTH_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_LENGTH_RAND ) + { + // Random simply modulates the existing value + perc1 = random() * perc1; + } + + mLength = (mLengthStart * perc1) + (mLengthEnd * (1.0f - perc1)); +} + +//---------------------------- +void CTail::CalcNewEndpoint() +{ + vec3_t temp; + + // FIXME: Hmmm, this looks dumb when physics are on and a bounce happens + VectorSubtract( mOldOrigin, mOrigin1, temp ); + + // I wish we didn't have to do a VectorNormalize every frame..... + VectorNormalize( temp ); + + VectorMA( mOrigin1, mLength, temp, mRefEnt.oldorigin ); +} + + +//---------------------------- +// +// Derived Cylinder Class +// +//---------------------------- +void CCylinder::Draw() +{ + if ( mFlags & FX_DEPTH_HACK ) + { + // Not sure if first person needs to be set, but it can't hurt? + mRefEnt.renderfx |= RF_DEPTHHACK; + } + + VectorCopy( mOrigin1, mRefEnt.origin ); + VectorMA( mOrigin1, mLength, mRefEnt.axis[0], mRefEnt.oldorigin ); + + theFxHelper.AddFxToScene( &mRefEnt ); + + drawnFx++; +} + +//---------------------------- +// Update Size2 +//---------------------------- +void CCylinder::UpdateSize2() +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + + if ( mFlags & FX_SIZE2_LINEAR ) + { + // calculate element biasing + perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) + / (float)(mTimeEnd - mTimeStart); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE + if (( mFlags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_NONLINEAR ) + { + if ( theFxHelper.mTime > mSize2Parm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)(theFxHelper.mTime - mSize2Parm) + / (float)(mTimeEnd - mSize2Parm); + } + + if ( (mFlags & FX_SIZE2_LINEAR) ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * (float)cos( (theFxHelper.mTime - mTimeStart) * mSize2Parm ); + } + else if (( mFlags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_CLAMP ) + { + if ( theFxHelper.mTime < mSize2Parm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mSize2Parm - theFxHelper.mTime) + / (float)(mSize2Parm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( mFlags & FX_SIZE2_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_SIZE2_RAND ) + { + // Random simply modulates the existing value + perc1 = random() * perc1; + } + + mRefEnt.backlerp = (mSize2Start * perc1) + (mSize2End * (1.0f - perc1)); +} + +//---------------------------- +bool CCylinder::Update() +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + if ( mFlags & FX_RELATIVE ) + { + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + vec3_t ax[3] = {0}; + // Get our current position and direction + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + const centity_t ¢ = cg_entities[mClientID]; + if (cent.gent->ghoul2.IsValid()) + { + if (!theFxHelper.GetOriginAxisFromBolt(cent, mModelNum, mBoltNum, mOrigin1, ax)) + { //could not get bolt + return false; + } + } + } + else + {//fixme change this to bolt style... + // Get our current position and direction + GetOrigin( mClientID, mOrigin1 ); + GetDir( mClientID, ax[0] ); + } + + VectorAdd(mOrigin1, mOrgOffset, mOrigin1); //add the offset to the bolt point + + VectorCopy( ax[0], mRefEnt.axis[0] ); + //FIXME: should mNormal be a modifier on the forward axis? + /* + VectorMA( mOrigin1, mNormal[0], ax[0], mOrigin2 ); + VectorMA( mOrigin2, mNormal[1], ax[1], mOrigin2 ); + VectorMA( mOrigin2, mNormal[2], ax[2], mOrigin2 ); + */ + } + + UpdateSize(); + UpdateSize2(); + UpdateLength(); + UpdateRGB(); + UpdateAlpha(); + + Draw(); + + return true; +} + + +//---------------------------- +// +// Derived Emitter Class +// +//---------------------------- + +//---------------------------- +// Draw +//---------------------------- +void CEmitter::Draw() +{ + // Emitters don't draw themselves, but they may need to add an attached model + if ( mFlags & FX_ATTACHED_MODEL ) + { + mRefEnt.nonNormalizedAxes = qtrue; + + VectorCopy( mOrigin1, mRefEnt.origin ); + + // ensure that we are sized + for ( int i = 0; i < 3; i++ ) + { + VectorScale( mRefEnt.axis[i], mRefEnt.radius, mRefEnt.axis[i] ); + } + + theFxHelper.AddFxToScene( &mRefEnt ); + } + + // If we are emitting effects, we had better be careful because just calling it every cgame frame could + // either choke up the effects system on a fast machine, or look really nasty on a low end one. + if ( mFlags & FX_EMIT_FX ) + { + vec3_t org, v; + float ftime, time2, + step; + int i, t, dif; + +#define TRAIL_RATE 8 // we "think" at about a 60hz rate + + // Pick a target step distance and square it + step = mDensity + crandom() * mVariance; + step *= step; + + dif = 0; + + for ( t = mOldTime; t <= theFxHelper.mTime; t += TRAIL_RATE ) + { + dif += TRAIL_RATE; + + // ?Not sure if it's better to update this before or after updating the origin + VectorMA( mOldVelocity, dif * 0.001f, mAccel, v ); + + // Calc the time differences + ftime = dif * 0.001f; + time2 = ftime * ftime * 0.5f; + + // Predict the new position + for ( i = 0 ; i < 3 ; i++ ) + { + org[i] = mOldOrigin[i] + ftime * v[i] + time2 * v[i]; + } + + // Only perform physics if this object is tagged to do so + if ( (mFlags & FX_APPLY_PHYSICS) ) + { + bool solid; + + if ( (mFlags&FX_EXPENSIVE_PHYSICS) + && fx_expensivePhysics.integer ) + { + solid = true; // by setting this to true, we force a real trace to happen + } + else + { + // if this returns solid, we need to do a trace + solid = !!(CG_PointContents( org, ENTITYNUM_WORLD ) & MASK_SHOT); + } + + if ( solid ) + { + trace_t trace; + + if ( mFlags & FX_USE_BBOX ) + { + theFxHelper.Trace( &trace, mOldOrigin, mMin, mMax, org, -1, MASK_SHOT ); + } + else + { + theFxHelper.Trace( &trace, mOldOrigin, NULL, NULL, org, -1, MASK_SHOT ); + } + + // Hit something + if ( trace.fraction < 1.0f || trace.startsolid || trace.allsolid ) + { + return; + } + } + } + + // Is it time to draw an effect? + if ( DistanceSquared( org, mOldOrigin ) >= step ) + { + // Pick a new target step distance and square it + step = mDensity + crandom() * mVariance; + step *= step; + + // We met the step criteria so, we should add in the effect + theFxScheduler.PlayEffect( mEmitterFxID, org, mRefEnt.axis ); + + VectorCopy( org, mOldOrigin ); + VectorCopy( v, mOldVelocity ); + dif = 0; + mOldTime = t; + } + } + } + + drawnFx++; +} + +//---------------------------- +bool CEmitter::Update() +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + //FIXME: Handle Relative and Bolted Effects + /* + if ( mFlags & FX_RELATIVE ) + { + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + // Get our current position and direction + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + } + } + */ + // Use this to track if we've stopped moving + VectorCopy( mOrigin1, mOldOrigin ); + VectorCopy( mVel, mOldVelocity ); + + if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) + { // we are marked for death + return false; + } + + // If the thing is no longer moving, kill the angle delta, but don't do it too quickly or it will + // look very artificial. Don't do it too slowly or it will look like there is no friction. + if ( VectorCompare( mOldOrigin, mOrigin1 )) + { + VectorScale( mAngleDelta, 0.6f, mAngleDelta ); + } + + UpdateAngles(); + UpdateSize(); +// UpdateRGB(); // had wanted to do something slick whereby an emitted effect could somehow inherit these +// UpdateAlpha(); // values, but it's not a priority right now. + + Draw(); + + return true; +} + +//---------------------------- +void CEmitter::UpdateAngles() +{ + VectorMA( mAngles, theFxHelper.mFrameTime * 0.01f, mAngleDelta, mAngles ); // was 0.001f, but then you really have to jack up the delta to even notice anything + AnglesToAxis( mAngles, mRefEnt.axis ); +} + + +//-------------------------- +// +// Derived Light Class +// +//-------------------------- +//---------------------------- +// Update +//---------------------------- +bool CLight::Update() +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + //FIXME: Handle Relative and Bolted Effects + /* + if ( mFlags & FX_RELATIVE ) + { + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + // Get our current position and direction + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + } + } + */ + UpdateSize(); + UpdateRGB(); + + Draw(); + + return true; +} + +//---------------------------- +// Update Size +//---------------------------- +void CLight::UpdateSize() +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + + if ( mFlags & FX_SIZE_LINEAR ) + { + // calculate element biasing + perc1 = 1.0f - (float)(theFxHelper.mTime - mTimeStart) + / (float)(mTimeEnd - mTimeStart); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE + if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_NONLINEAR ) + { + if ( theFxHelper.mTime > mSizeParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)(theFxHelper.mTime - mSizeParm) + / (float)(mTimeEnd - mSizeParm); + } + + if ( (mFlags & FX_SIZE_LINEAR) ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * (float)cos( (theFxHelper.mTime - mTimeStart) * mSizeParm ); + } + else if (( mFlags & FX_SIZE_PARM_MASK ) == FX_SIZE_CLAMP ) + { + if ( theFxHelper.mTime < mSizeParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mSizeParm - theFxHelper.mTime) + / (float)(mSizeParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( mFlags & FX_SIZE_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_SIZE_RAND ) + { + // Random simply modulates the existing value + perc1 = random() * perc1; + } + + mRefEnt.radius = (mSizeStart * perc1) + (mSizeEnd * (1.0f - perc1)); +} + +//---------------------------- +// Update RGB +//---------------------------- +void CLight::UpdateRGB() +{ + // completely biased towards start if it doesn't get overridden + float perc1 = 1.0f, perc2 = 1.0f; + vec3_t res; + + if ( mFlags & FX_RGB_LINEAR ) + { + // calculate element biasing + perc1 = 1.0f - (float)( theFxHelper.mTime - mTimeStart ) + / (float)( mTimeEnd - mTimeStart ); + } + + // We can combine FX_LINEAR with _either_ FX_NONLINEAR or FX_WAVE + if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_NONLINEAR ) + { + if ( theFxHelper.mTime > mRGBParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = 1.0f - (float)( theFxHelper.mTime - mRGBParm ) + / (float)( mTimeEnd - mRGBParm ); + } + + if ( mFlags & FX_RGB_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + // wave gen, with parm being the frequency multiplier + perc1 = perc1 * (float)cos(( theFxHelper.mTime - mTimeStart ) * mRGBParm ); + } + else if (( mFlags & FX_RGB_PARM_MASK ) == FX_RGB_CLAMP ) + { + if ( theFxHelper.mTime < mRGBParm ) + { + // get percent done, using parm as the start of the non-linear fade + perc2 = (float)(mRGBParm - theFxHelper.mTime) + / (float)(mRGBParm - mTimeStart); + } + else + { + perc2 = 0.0f; // make it full size?? + } + + if ( mFlags & FX_RGB_LINEAR ) + { + // do an even blend + perc1 = perc1 * 0.5f + perc2 * 0.5f; + } + else + { + // just copy it over...sigh + perc1 = perc2; + } + } + + // If needed, RAND can coexist with linear and either non-linear or wave. + if ( mFlags & FX_RGB_RAND ) + { + // Random simply modulates the existing value + perc1 = random() * perc1; + } + + // Now get the correct color + VectorScale( mRGBStart, perc1, res ); + + mRefEnt.lightingOrigin[0] = res[0] + ( 1.0f - perc1 ) * mRGBEnd[0]; + mRefEnt.lightingOrigin[1] = res[1] + ( 1.0f - perc1 ) * mRGBEnd[1]; + mRefEnt.lightingOrigin[2] = res[2] + ( 1.0f - perc1 ) * mRGBEnd[2]; +} + + +//-------------------------- +// +// Derived Trail Class +// +//-------------------------- +#define NEW_MUZZLE 0 +#define NEW_TIP 1 +#define OLD_TIP 2 +#define OLD_MUZZLE 3 + +//---------------------------- +void CTrail::Draw() +{ + polyVert_t verts[3]; +// vec3_t color; + + // build the first tri out of the new muzzle...new tip...old muzzle + VectorCopy( mVerts[NEW_MUZZLE].origin, verts[0].xyz ); + VectorCopy( mVerts[NEW_TIP].origin, verts[1].xyz ); + VectorCopy( mVerts[OLD_MUZZLE].origin, verts[2].xyz ); + +// VectorScale( mVerts[NEW_MUZZLE].curRGB, mVerts[NEW_MUZZLE].curAlpha, color ); + verts[0].modulate[0] = mVerts[NEW_MUZZLE].rgb[0]; + verts[0].modulate[1] = mVerts[NEW_MUZZLE].rgb[1]; + verts[0].modulate[2] = mVerts[NEW_MUZZLE].rgb[2]; + verts[0].modulate[3] = mVerts[NEW_MUZZLE].alpha; + +// VectorScale( mVerts[NEW_TIP].curRGB, mVerts[NEW_TIP].curAlpha, color ); + verts[1].modulate[0] = mVerts[NEW_TIP].rgb[0]; + verts[1].modulate[1] = mVerts[NEW_TIP].rgb[1]; + verts[1].modulate[2] = mVerts[NEW_TIP].rgb[2]; + verts[1].modulate[3] = mVerts[NEW_TIP].alpha; + +// VectorScale( mVerts[OLD_MUZZLE].curRGB, mVerts[OLD_MUZZLE].curAlpha, color ); + verts[2].modulate[0] = mVerts[OLD_MUZZLE].rgb[0]; + verts[2].modulate[1] = mVerts[OLD_MUZZLE].rgb[1]; + verts[2].modulate[2] = mVerts[OLD_MUZZLE].rgb[2]; + verts[2].modulate[3] = mVerts[OLD_MUZZLE].alpha; + + verts[0].st[0] = mVerts[NEW_MUZZLE].curST[0]; + verts[0].st[1] = mVerts[NEW_MUZZLE].curST[1]; + verts[1].st[0] = mVerts[NEW_TIP].curST[0]; + verts[1].st[1] = mVerts[NEW_TIP].curST[1]; + verts[2].st[0] = mVerts[OLD_MUZZLE].curST[0]; + verts[2].st[1] = mVerts[OLD_MUZZLE].curST[1]; + + // Add this tri + theFxHelper.AddPolyToScene( mShader, 3, verts ); + + // build the second tri out of the old muzzle...old tip...new tip + VectorCopy( mVerts[OLD_MUZZLE].origin, verts[0].xyz ); + VectorCopy( mVerts[OLD_TIP].origin, verts[1].xyz ); + VectorCopy( mVerts[NEW_TIP].origin, verts[2].xyz ); + +// VectorScale( mVerts[OLD_MUZZLE].curRGB, mVerts[OLD_MUZZLE].curAlpha, color ); + verts[0].modulate[0] = mVerts[OLD_MUZZLE].rgb[0]; + verts[0].modulate[1] = mVerts[OLD_MUZZLE].rgb[1]; + verts[0].modulate[2] = mVerts[OLD_MUZZLE].rgb[2]; + verts[0].modulate[3] = mVerts[OLD_MUZZLE].alpha; + +// VectorScale( mVerts[OLD_TIP].curRGB, mVerts[OLD_TIP].curAlpha, color ); + verts[1].modulate[0] = mVerts[OLD_TIP].rgb[0]; + verts[1].modulate[1] = mVerts[OLD_TIP].rgb[1]; + verts[1].modulate[2] = mVerts[OLD_TIP].rgb[2]; + verts[0].modulate[3] = mVerts[OLD_TIP].alpha; + +// VectorScale( mVerts[NEW_TIP].curRGB, mVerts[NEW_TIP].curAlpha, color ); + verts[2].modulate[0] = mVerts[NEW_TIP].rgb[0]; + verts[2].modulate[1] = mVerts[NEW_TIP].rgb[1]; + verts[2].modulate[2] = mVerts[NEW_TIP].rgb[2]; + verts[0].modulate[3] = mVerts[NEW_TIP].alpha; + + verts[0].st[0] = mVerts[OLD_MUZZLE].curST[0]; + verts[0].st[1] = mVerts[OLD_MUZZLE].curST[1]; + verts[1].st[0] = mVerts[OLD_TIP].curST[0]; + verts[1].st[1] = mVerts[OLD_TIP].curST[1]; + verts[2].st[0] = mVerts[NEW_TIP].curST[0]; + verts[2].st[1] = mVerts[NEW_TIP].curST[1]; + + // Add this tri + theFxHelper.AddPolyToScene( mShader, 3, verts ); + + drawnFx++; +} + +//---------------------------- +// Update +//---------------------------- +bool CTrail::Update() +{ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + //FIXME: Handle Relative and Bolted Effects + /* + if ( mFlags & FX_RELATIVE ) + { + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + // Get our current position and direction + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + } + } + */ + float perc = (float)(mTimeEnd - theFxHelper.mTime) / (float)(mTimeEnd - mTimeStart); + + for ( int t = 0; t < 4; t++ ) + { +// mVerts[t].curAlpha = mVerts[t].alpha * perc + mVerts[t].destAlpha * ( 1.0f - perc ); +// if ( mVerts[t].curAlpha < 0.0f ) +// { +// mVerts[t].curAlpha = 0.0f; +// } + +// VectorScale( mVerts[t].rgb, perc, mVerts[t].curRGB ); +// VectorMA( mVerts[t].curRGB, ( 1.0f - perc ), mVerts[t].destrgb, mVerts[t].curRGB ); + mVerts[t].curST[0] = mVerts[t].ST[0] * perc + mVerts[t].destST[0] * ( 1.0f - perc ); + if ( mVerts[t].curST[0] > 1.0f ) + { + mVerts[t].curST[0] = 1.0f; + } + mVerts[t].curST[1] = mVerts[t].ST[1] * perc + mVerts[t].destST[1] * ( 1.0f - perc ); + } + + Draw(); + + return true; +} + + +//-------------------------- +// +// Derived Poly Class +// +//-------------------------- +bool CPoly::Cull() +{ + vec3_t dir; + + // Get the direction to the view + VectorSubtract( mOrigin1, cg.refdef.vieworg, dir ); + + // Check if it's behind the viewer + if ( (DotProduct( cg.refdef.viewaxis[0], dir )) < 0 ) + { + return true; + } + + float len = VectorLengthSquared( dir ); + + // Can't be too close + if ( len < 24 * 24 ) + { + return true; + } + + return false; +} + +//---------------------------- +void CPoly::Draw() +{ + polyVert_t verts[MAX_CPOLY_VERTS]; + + for ( int i = 0; i < mCount; i++ ) + { + // Add our midpoint and vert offset to get the actual vertex + VectorAdd( mOrigin1, mOrg[i], verts[i].xyz ); + + // Assign the same color to each vert + verts[i].modulate[0] = mRefEnt.shaderRGBA[0]; + verts[i].modulate[1] = mRefEnt.shaderRGBA[1]; + verts[i].modulate[2] = mRefEnt.shaderRGBA[2]; + verts[i].modulate[3] = mRefEnt.shaderRGBA[3]; + + // Copy the ST coords + Vector2Copy( mST[i], verts[i].st ); + } + + // Add this poly + theFxHelper.AddPolyToScene( mRefEnt.customShader, mCount, verts ); + + drawnFx++; +} + +//---------------------------- +void CPoly::CalcRotateMatrix() +{ + float cosX, cosZ; + float sinX, sinZ; + float rad; + + // rotate around Z + rad = DEG2RAD( mRotDelta[YAW] * theFxHelper.mFrameTime * 0.01f ); + cosZ = cos( rad ); + sinZ = sin( rad ); + // rotate around X + rad = DEG2RAD( mRotDelta[PITCH] * theFxHelper.mFrameTime * 0.01f ); + cosX = cos( rad ); + sinX = sin( rad ); + +/*Pitch - aroundx Yaw - around z +1 0 0 c -s 0 +0 c -s s c 0 +0 s c 0 0 1 +*/ + mRot[0][0] = cosZ; + mRot[1][0] = -sinZ; + mRot[2][0] = 0; + mRot[0][1] = cosX * sinZ; + mRot[1][1] = cosX * cosZ; + mRot[2][1] = -sinX; + mRot[0][2] = sinX * sinZ; + mRot[1][2] = sinX * cosZ; + mRot[2][2] = cosX; +/* +// ROLL is not supported unless anyone complains, if it needs to be added, use this format +Roll + + c 0 s + 0 1 0 +-s 0 c +*/ + mLastFrameTime = theFxHelper.mFrameTime; +} + +//-------------------------------- +void CPoly::Rotate() +{ + vec3_t temp[MAX_CPOLY_VERTS]; + float dif = abs(mLastFrameTime - theFxHelper.mFrameTime); + + // Very generous check with frameTimes + if ( dif > 0.5f * mLastFrameTime ) + { + CalcRotateMatrix(); + } + + // Multiply our rotation matrix by each of the offset verts to get their new position + for ( int i = 0; i < mCount; i++ ) + { + VectorRotate( mOrg[i], mRot, temp[i] ); + VectorCopy( temp[i], mOrg[i] ); + } +} + +//---------------------------- +// Update +//---------------------------- +bool CPoly::Update() +{ + vec3_t mOldOrigin; + + //FIXME: Handle Relative and Bolted Effects + /* + if ( mFlags & FX_RELATIVE ) + { + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + // Get our current position and direction + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + } + } + */ + // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) + { + return false; + } + + // If our timestamp hasn't exired yet, we won't even consider doing any kind of motion + if ( theFxHelper.mTime > mTimeStamp ) + { + VectorCopy( mOrigin1, mOldOrigin ); + + if (( mTimeStart < theFxHelper.mTime ) && UpdateOrigin() == false ) + { + // we are marked for death + return false; + } + } + + if ( !Cull() ) + { + // only rotate when our start timestamp has expired + if ( theFxHelper.mTime > mTimeStamp ) + { + // Only rotate whilst moving + if ( !VectorCompare( mOldOrigin, mOrigin1 )) + { + Rotate(); + } + } + + UpdateRGB(); + UpdateAlpha(); + + Draw(); + } + + return true; +} + +//---------------------------- +void CPoly::PolyInit() +{ + if ( mCount < 3 ) + { + return; + } + + int i; + vec3_t org={0,0,0}; + + // Find our midpoint + for ( i = 0; i < mCount; i++ ) + { + VectorAdd( org, mOrg[i], org ); + } + + VectorScale( org, (float)(1.0f / mCount), org ); + + // now store our midpoint for physics purposes + VectorCopy( org, mOrigin1 ); + + // Now we process the passed in points and make it so that they aren't actually the point... + // rather, they are the offset from mOrigin1. + for ( i = 0; i < mCount; i++ ) + { + VectorSubtract( mOrg[i], mOrigin1, mOrg[i] ); + } + + CalcRotateMatrix(); +} + +/* +------------------------- +CBezier + +Bezier curve line +------------------------- +*/ +//---------------------------- +bool CBezier::Update( void ) +{ + float ftime, time2; + + //FIXME: Handle Relative and Bolted Effects + /* + if ( mFlags & FX_RELATIVE ) + { + if ( mClientID < 0 || mClientID >= ENTITYNUM_WORLD ) + { // we are somehow not bolted even though the flag is on? + return false; + } + + // Get our current position and direction + if (mModelNum>=0 && mBoltNum>=0) //bolt style + { + } + } + */ + ftime = cg.frametime * 0.001f; + time2 = ftime * ftime * 0.5f; + + for ( int i = 0; i < 3; i++ ) + { + mControl1[i] = mControl1[i] + ftime * mControl1Vel[i] + time2 * mControl1Vel[i]; + mControl2[i] = mControl2[i] + ftime * mControl2Vel[i] + time2 * mControl2Vel[i]; + } + + UpdateSize(); + UpdateRGB(); + UpdateAlpha(); + + Draw(); + + return true; +} + +//---------------------------- +inline void CBezier::DrawSegment( vec3_t start, vec3_t end, float texcoord1, float texcoord2 ) +{ + vec3_t lineDir, cross, viewDir; + static vec3_t lastEnd[2]; + polyVert_t verts[4]; + float scale; + + VectorSubtract( end, start, lineDir ); + VectorSubtract( end, cg.refdef.vieworg, viewDir ); + CrossProduct( lineDir, viewDir, cross ); + VectorNormalize( cross ); + + scale = mRefEnt.radius * 0.5f; + + //Construct the oriented quad + if ( mInit ) + { + VectorCopy( lastEnd[0], verts[0].xyz ); + VectorCopy( lastEnd[1], verts[1].xyz ); + } + else + { + VectorMA( start, -scale, cross, verts[0].xyz ); + VectorMA( start, scale, cross, verts[1].xyz ); + } + + verts[0].st[0] = 0.0f; + verts[0].st[1] = texcoord1; + + verts[0].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord1 ); + verts[0].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord1 ); + verts[0].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord1 ); + verts[0].modulate[3] = mRefEnt.shaderRGBA[3]; + + verts[1].st[0] = 1.0f; + verts[1].st[1] = texcoord1; + verts[1].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord1 ); + verts[1].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord1 ); + verts[1].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord1 ); + verts[1].modulate[3] = mRefEnt.shaderRGBA[3]; + + if ( texcoord1 == 0.0f ) + { + verts[0].modulate[0] = 0; + verts[0].modulate[1] = 0; + verts[0].modulate[2] = 0; + verts[0].modulate[3] = 0; + verts[1].modulate[0] = 0; + verts[1].modulate[1] = 0; + verts[1].modulate[2] = 0; + verts[1].modulate[3] = 0; + } + + VectorMA( end, scale, cross, verts[2].xyz ); + verts[2].st[0] = 1.0f; + verts[2].st[1] = texcoord2; + verts[2].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord2 ); + verts[2].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord2 ); + verts[2].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord2 ); + verts[2].modulate[3] = mRefEnt.shaderRGBA[3]; + + VectorMA( end, -scale, cross, verts[3].xyz ); + verts[3].st[0] = 0.0f; + verts[3].st[1] = texcoord2; + verts[3].modulate[0] = mRefEnt.shaderRGBA[0] * ( 1.0f - texcoord2 ); + verts[3].modulate[1] = mRefEnt.shaderRGBA[1] * ( 1.0f - texcoord2 ); + verts[3].modulate[2] = mRefEnt.shaderRGBA[2] * ( 1.0f - texcoord2 ); + verts[3].modulate[3] = mRefEnt.shaderRGBA[3]; + + cgi_R_AddPolyToScene( mRefEnt.customShader, 4, verts ); + + VectorCopy( verts[2].xyz, lastEnd[1] ); + VectorCopy( verts[3].xyz, lastEnd[0] ); + + mInit = true; +} + +const float BEZIER_RESOLUTION = 16.0f; + +//---------------------------- +void CBezier::Draw( void ) +{ + vec3_t pos, old_pos; + float mu, mum1; + float incr = 1.0f / BEZIER_RESOLUTION, tex = 1.0f, tc1, tc2; + int i; + + VectorCopy( mOrigin1, old_pos ); + + mInit = false; //Signify a new batch for vert gluing + + // Calculate the texture coords so the texture can stretch along the whole bezier +// if ( mFlags & FXF_WRAP ) +// { +// tex = m_stScale / 1.0f; +// } + + float mum13, mu3, group1, group2; + + tc1 = 0.0f; + + for ( mu = incr; mu <= 1.0f; mu += incr ) + { + //Four point curve + mum1 = 1 - mu; + mum13 = mum1 * mum1 * mum1; + mu3 = mu * mu * mu; + group1 = 3 * mu * mum1 * mum1; + group2 = 3 * mu * mu *mum1; + + for ( i = 0; i < 3; i++ ) + { + pos[i] = mum13 * mOrigin1[i] + group1 * mControl1[i] + group2 * mControl2[i] + mu3 * mOrigin2[i]; + } + +// if ( m_flags & FXF_WRAP ) +// { + tc2 = mu * tex; +// } +// else +// { +// // Texture will get mapped onto each segement +// tc1 = 0.0f; +// tc2 = 1.0f; +// } + + //Draw it + DrawSegment( old_pos, pos, tc1, tc2 ); + + VectorCopy( pos, old_pos ); + tc1 = tc2; + } + + drawnFx++; + mLines++; // NOT REALLY A LINE +} + +/* +------------------------- +CFlash + +Full screen flash +------------------------- +*/ + +//---------------------------- +bool CFlash::Update( void ) +{ + UpdateRGB(); + Draw(); + + return true; +} + +//---------------------------- +void CFlash::Init( void ) +{ + vec3_t dif; + float mod = 1.0f, dis; + + VectorSubtract( mOrigin1, cg.refdef.vieworg, dif ); + dis = VectorNormalize( dif ); + + mod = DotProduct( dif, cg.refdef.viewaxis[0] ); + + if ( dis > 600 || ( mod < 0.5f && dis > 100 )) + { + mod = 0.0f; + } + else if ( mod < 0.5f && dis <= 100 ) + { + mod += 1.1f; + } + + mod *= (1.0f - ((dis * dis) / (600.0f * 600.0f))); + + VectorScale( mRGBStart, mod, mRGBStart ); + VectorScale( mRGBEnd, mod, mRGBEnd ); +} + +//---------------------------- +void CFlash::Draw( void ) +{ + mRefEnt.reType = RT_SPRITE; + + for ( int i = 0; i < 3; i++ ) + { + if ( mRefEnt.lightingOrigin[i] > 1.0f ) + { + mRefEnt.lightingOrigin[i] = 1.0f; + } + else if ( mRefEnt.lightingOrigin[i] < 0.0f ) + { + mRefEnt.lightingOrigin[i] = 0.0f; + } + } + mRefEnt.shaderRGBA[0] = mRefEnt.lightingOrigin[0] * 255; + mRefEnt.shaderRGBA[1] = mRefEnt.lightingOrigin[1] * 255; + mRefEnt.shaderRGBA[2] = mRefEnt.lightingOrigin[2] * 255; + mRefEnt.shaderRGBA[3] = 255; + + VectorCopy( cg.refdef.vieworg, mRefEnt.origin ); + VectorMA( mRefEnt.origin, 8, cg.refdef.viewaxis[0], mRefEnt.origin ); + mRefEnt.radius = 12.0f; + + theFxHelper.AddFxToScene( &mRefEnt ); + + drawnFx++; +} diff --git a/code/cgame/FxPrimitives.h b/code/cgame/FxPrimitives.h new file mode 100644 index 0000000..d0c54b4 --- /dev/null +++ b/code/cgame/FxPrimitives.h @@ -0,0 +1,572 @@ + +#if !defined(FX_SYSTEM_H_INC) + #include "FxSystem.h" +#endif + +#ifndef FX_PRIMITIVES_H_INC +#define FX_PRIMITIVES_H_INC + + +#define MAX_EFFECTS 1200 + + +// Generic group flags, used by parser, then get converted to the appropriate specific flags +#define FX_PARM_MASK 0xC // use this to mask off any transition types that use a parm +#define FX_GENERIC_MASK 0xF +#define FX_LINEAR 0x1 +#define FX_RAND 0x2 +#define FX_NONLINEAR 0x4 +#define FX_WAVE 0x8 +#define FX_CLAMP 0xC + +// Group flags +#define FX_ALPHA_SHIFT 0 +#define FX_ALPHA_PARM_MASK 0x0000000C +#define FX_ALPHA_LINEAR 0x00000001 +#define FX_ALPHA_RAND 0x00000002 +#define FX_ALPHA_NONLINEAR 0x00000004 +#define FX_ALPHA_WAVE 0x00000008 +#define FX_ALPHA_CLAMP 0x0000000C + +#define FX_RGB_SHIFT 4 +#define FX_RGB_PARM_MASK 0x000000C0 +#define FX_RGB_LINEAR 0x00000010 +#define FX_RGB_RAND 0x00000020 +#define FX_RGB_NONLINEAR 0x00000040 +#define FX_RGB_WAVE 0x00000080 +#define FX_RGB_CLAMP 0x000000C0 + +#define FX_SIZE_SHIFT 8 +#define FX_SIZE_PARM_MASK 0x00000C00 +#define FX_SIZE_LINEAR 0x00000100 +#define FX_SIZE_RAND 0x00000200 +#define FX_SIZE_NONLINEAR 0x00000400 +#define FX_SIZE_WAVE 0x00000800 +#define FX_SIZE_CLAMP 0x00000C00 + +#define FX_LENGTH_SHIFT 12 +#define FX_LENGTH_PARM_MASK 0x0000C000 +#define FX_LENGTH_LINEAR 0x00001000 +#define FX_LENGTH_RAND 0x00002000 +#define FX_LENGTH_NONLINEAR 0x00004000 +#define FX_LENGTH_WAVE 0x00008000 +#define FX_LENGTH_CLAMP 0x0000C000 + +#define FX_SIZE2_SHIFT 16 +#define FX_SIZE2_PARM_MASK 0x000C0000 +#define FX_SIZE2_LINEAR 0x00010000 +#define FX_SIZE2_RAND 0x00020000 +#define FX_SIZE2_NONLINEAR 0x00040000 +#define FX_SIZE2_WAVE 0x00080000 +#define FX_SIZE2_CLAMP 0x000C0000 + +// Feature flags +#define FX_DEPTH_HACK 0x00100000 +#define FX_RELATIVE 0x00200000 +#define FX_SET_SHADER_TIME 0x00400000 // by having the effects system set the shader time, we can make animating textures start at the correct time +#define FX_EXPENSIVE_PHYSICS 0x00800000 + +//rww - g2-related flags (these can slow things down significantly, use sparingly) +//These should be used only with particles/decals as they steal flags used by cylinders. +#define FX_GHOUL2_TRACE 0x00020000 //use in conjunction with particles - actually do full ghoul2 traces for physics collision against entities with a ghoul2 instance + //shared FX_SIZE2_RAND (used only with cylinders) +#define FX_GHOUL2_DECALS 0x00040000 //use in conjunction with decals - can project decal as a ghoul2 gore skin object onto ghoul2 models + //shared FX_SIZE2_NONLINEAR (used only with cylinders) + +#define FX_ATTACHED_MODEL 0x01000000 + +#define FX_APPLY_PHYSICS 0x02000000 +#define FX_USE_BBOX 0x04000000 // can make physics more accurate at the expense of speed + +#define FX_USE_ALPHA 0x08000000 // the FX system actually uses RGB to do fades, but this will override that + // and cause it to fill in the alpha. + +#define FX_EMIT_FX 0x10000000 // emitters technically don't have to emit stuff, but when they do + // this flag needs to be set +#define FX_DEATH_RUNS_FX 0x20000000 // Normal death triggers effect, but not kill_on_impact +#define FX_KILL_ON_IMPACT 0x40000000 // works just like it says, but only when physics are on. +#define FX_IMPACT_RUNS_FX 0x80000000 // an effect can call another effect when it hits something. + +// Lightning flags, duplicates of existing flags, but lightning doesn't use those flags in that context...and nothing will ever use these in this context..so we are safe. +#define FX_TAPER 0x01000000 // tapers as it moves towards its endpoint +#define FX_BRANCH 0x02000000 // enables lightning branching +#define FX_GROW 0x04000000 // lightning grows from start point to end point over the course of its life + +//------------------------------ +class CEffect +{ +protected: + + vec3_t mOrigin1; + + int mTimeStart; + int mTimeEnd; + + unsigned int mFlags; + + // Size of our object, useful for things that have physics + vec3_t mMin; + vec3_t mMax; + + int mImpactFxID; // if we have an impact event, we may have to call an effect + int mDeathFxID; // if we have a death event, we may have to call an effect + + refEntity_t mRefEnt; + + +public: + + CEffect() { memset( &mRefEnt, 0, sizeof( refEntity_t )); } + virtual ~CEffect() {} + virtual void Die() {} + + virtual bool Update() + { // Game pausing can cause dumb time things to happen, so kill the effect in this instance + if ( mTimeStart > theFxHelper.mTime ) { + return false; + } + return true; + } + + inline void SetSTScale(float s,float t) { mRefEnt.shaderTexCoord[0]=s;mRefEnt.shaderTexCoord[1]=t;} + + inline void SetMin( const vec3_t min ) { if(min){VectorCopy(min,mMin);}else{VectorClear(mMin);} } + inline void SetMax( const vec3_t max ) { if(max){VectorCopy(max,mMax);}else{VectorClear(mMax);} } + inline void SetFlags( int flags ) { mFlags = flags; } + inline void AddFlags( int flags ) { mFlags |= flags; } + inline void ClearFlags( int flags ) { mFlags &= ~flags; } + inline void SetOrigin1( const vec3_t org ) { if(org){VectorCopy(org,mOrigin1);}else{VectorClear(mOrigin1);} } + inline void SetTimeStart( int time ) { mTimeStart = time; if (mFlags&FX_SET_SHADER_TIME) { mRefEnt.shaderTime = cg.time * 0.001f; }} + inline void SetTimeEnd( int time ) { mTimeEnd = time; } + inline void SetImpactFxID( int id ) { mImpactFxID = id; } + inline void SetDeathFxID( int id ) { mDeathFxID = id; } +}; + + +//--------------------------------------------------- +// This class is kind of an exception to the "rule". +// For now it exists only for allowing an easy way +// to get the saber slash trails rendered. +//--------------------------------------------------- +class CTrail : public CEffect +{ +// This is such a specific case thing, just grant public access to the goods. +protected: + + void Draw(); + +public: + + typedef struct + { + vec3_t origin; + + // very specifc case, we can modulate the color and the alpha + vec3_t rgb; + vec3_t destrgb; + vec3_t curRGB; + + float alpha; + float destAlpha; + float curAlpha; + + // this is a very specific case thing...allow interpolating the st coords so we can map the texture + // properly as this segement progresses through it's life + float ST[2]; + float destST[2]; + float curST[2]; + + } TVert; + + TVert mVerts[4]; + qhandle_t mShader; + + + CTrail() {}; + virtual ~CTrail() {}; + + virtual bool Update(); +}; + + +//------------------------------ +class CLight : public CEffect +{ +protected: + + float mSizeStart; + float mSizeEnd; + float mSizeParm; + + vec3_t mRGBStart; + vec3_t mRGBEnd; + float mRGBParm; + + + void UpdateSize(); + void UpdateRGB(); + + void Draw() + { + theFxHelper.AddLightToScene( mOrigin1, mRefEnt.radius, + mRefEnt.lightingOrigin[0], mRefEnt.lightingOrigin[1], mRefEnt.lightingOrigin[2] ); + } + +public: + + CLight() {} + virtual ~CLight() {} + virtual bool Update(); + + inline void SetSizeStart( float sz ) { mSizeStart = sz; } + inline void SetSizeEnd( float sz ) { mSizeEnd = sz; } + inline void SetSizeParm( float parm ) { mSizeParm = parm; } + + inline void SetRGBStart( vec3_t rgb ) { if(rgb){VectorCopy(rgb,mRGBStart);}else{VectorClear(mRGBStart);} } + inline void SetRGBEnd( vec3_t rgb ) { if(rgb){VectorCopy(rgb,mRGBEnd);}else{VectorClear(mRGBEnd);} } + inline void SetRGBParm( float parm ) { mRGBParm = parm; } +}; + +//------------------------------ +class CFlash : public CLight +{ +protected: + + void Draw(); + +public: + + CFlash() {} + virtual ~CFlash() {} + + virtual bool Update(); + + inline void SetShader( qhandle_t sh ) + { assert(sh); + mRefEnt.customShader = sh; + } + void Init( void ); +}; + +//------------------------------ +class CParticle : public CEffect +{ +protected: + + vec3_t mOrgOffset; + + vec3_t mVel; + vec3_t mAccel; + float mGravity; + + float mSizeStart; + float mSizeEnd; + float mSizeParm; + + vec3_t mRGBStart; + vec3_t mRGBEnd; + float mRGBParm; + + float mAlphaStart; + float mAlphaEnd; + float mAlphaParm; + + float mRotationDelta; + float mElasticity; + + short mClientID; + char mModelNum; + char mBoltNum; + + bool UpdateOrigin(); + void UpdateVelocity() {VectorMA( mVel, theFxHelper.mFloatFrameTime, mAccel, mVel ); } + + void UpdateSize(); + void UpdateRGB(); + void UpdateAlpha(); + void UpdateRotation() { mRefEnt.rotation += theFxHelper.mFrameTime * 0.01f * mRotationDelta; } + + bool Cull(); + void Draw(); + +public: + + inline CParticle() { mRefEnt.reType = RT_SPRITE; mClientID = -1; mModelNum = -1; mBoltNum = -1; } + virtual ~CParticle() {} + + virtual void Die(); + virtual bool Update(); + + inline void SetShader( qhandle_t sh ) { mRefEnt.customShader = sh;} + + inline void SetOrgOffset( const vec3_t o ) { if(o){VectorCopy(o,mOrgOffset);}else{VectorClear(mOrgOffset);}} + inline void SetVel( const vec3_t vel ) { if(vel){VectorCopy(vel,mVel);}else{VectorClear(mVel);} } + inline void SetAccel( const vec3_t ac ) { if(ac){VectorCopy(ac,mAccel);}else{VectorClear(mAccel);} } + inline void SetGravity( float grav ) { mGravity = grav; } + + inline void SetSizeStart( float sz ) { mSizeStart = sz; } + inline void SetSizeEnd( float sz ) { mSizeEnd = sz; } + inline void SetSizeParm( float parm ) { mSizeParm = parm; } + + inline void SetRGBStart( const vec3_t rgb ) { if(rgb){VectorCopy(rgb,mRGBStart);}else{VectorClear(mRGBStart);} } + inline void SetRGBEnd( const vec3_t rgb ) { if(rgb){VectorCopy(rgb,mRGBEnd);}else{VectorClear(mRGBEnd);} } + inline void SetRGBParm( float parm ) { mRGBParm = parm; } + + inline void SetAlphaStart( float al ) { mAlphaStart = al; } + inline void SetAlphaEnd( float al ) { mAlphaEnd = al; } + inline void SetAlphaParm( float parm ) { mAlphaParm = parm; } + + inline void SetRotation( float rot ) { mRefEnt.rotation = rot; } + inline void SetRotationDelta( float rot ) { mRotationDelta = rot; } + inline void SetElasticity( float el ) { mElasticity = el; } + + inline void SetClient( int clientID, int modelNum = -1, int boltNum = -1 ) {mClientID = clientID; mModelNum = modelNum; mBoltNum = boltNum; } +}; + + +//------------------------------ +class CLine : public CParticle +{ +protected: + + vec3_t mOrigin2; + + void Draw(); + +public: + + CLine() { mRefEnt.reType = RT_LINE;} + virtual ~CLine() {} + virtual void Die() {} + virtual bool Update(); + + + inline void SetOrigin2( const vec3_t org2 ) { VectorCopy( org2, mOrigin2 ); } +}; + +//------------------------------ +class CBezier : public CLine +{ +protected: + + vec3_t mControl1; + vec3_t mControl1Vel; + + vec3_t mControl2; + vec3_t mControl2Vel; + + bool mInit; + + void Draw(); + +public: + + CBezier(){ mInit = false; } + virtual ~CBezier() {} + virtual void Die() {} + + virtual bool Update(); + + void DrawSegment( vec3_t start, vec3_t end, float texcoord1, float texcoord2 ); + + inline void SetControlPoints( const vec3_t ctrl1, const vec3_t ctrl2 ) { VectorCopy( ctrl1, mControl1 ); VectorCopy( ctrl2, mControl2 ); } + inline void SetControlVel( const vec3_t ctrl1v, const vec3_t ctrl2v ) { VectorCopy( ctrl1v, mControl1Vel ); VectorCopy( ctrl2v, mControl2Vel ); } +}; + + +//------------------------------ +class CElectricity : public CLine +{ +protected: + + float mChaos; + + void Draw(); + +public: + + CElectricity() { mRefEnt.reType = RT_ELECTRICITY; } + virtual ~CElectricity() {} + virtual void Die() {} + + virtual bool Update(); + + void Initialize(); + + inline void SetChaos( float chaos ) { mChaos = chaos; } +}; + + +// Oriented quad +//------------------------------ +class COrientedParticle : public CParticle +{ +protected: + + vec3_t mNormal; + vec3_t mNormalOffset; + + bool Cull(); + void Draw(); + +public: + + COrientedParticle() { mRefEnt.reType = RT_ORIENTED_QUAD; } + virtual ~COrientedParticle() {} + + virtual bool Update(); + + inline void SetNormal( const vec3_t norm ) { VectorCopy( norm, mNormal ); } + inline void SetNormalOffset( const vec3_t norm ) { VectorCopy( norm, mNormalOffset ); } +}; + +//------------------------------ +class CTail : public CParticle +{ +protected: + + vec3_t mOldOrigin; + + float mLengthStart; + float mLengthEnd; + float mLengthParm; + + float mLength; + + void UpdateLength(); + void CalcNewEndpoint(); + + void Draw(); + bool Cull(); + +public: + + CTail() { mRefEnt.reType = RT_LINE; } + virtual ~CTail() {} + + virtual bool Update(); + + inline void SetLengthStart( float len ) { mLengthStart = len; } + inline void SetLengthEnd( float len ) { mLengthEnd = len; } + inline void SetLengthParm( float len ) { mLengthParm = len; } +}; + + +//------------------------------ +class CCylinder : public CTail +{ +protected: + + float mSize2Start; + float mSize2End; + float mSize2Parm; + + void UpdateSize2(); + + void Draw(); + +public: + + CCylinder() { mRefEnt.reType = RT_CYLINDER; } + virtual ~CCylinder() {} + + virtual bool Update(); + + inline void SetSize2Start( float sz ) { mSize2Start = sz; } + inline void SetSize2End( float sz ) { mSize2End = sz; } + inline void SetSize2Parm( float parm ) { mSize2Parm = parm; } + + inline void SetNormal( const vec3_t norm ) { VectorCopy( norm, mRefEnt.axis[0] ); } +}; + + +//------------------------------ +// Emitters are derived from particles because, although they don't draw, any effect called +// from them can borrow an initial or ending value from the emitters current alpha, rgb, etc.. +class CEmitter : public CParticle +{ +protected: + + vec3_t mOldOrigin; // we use these to do some nice + vec3_t mLastOrigin; // tricks... + vec3_t mOldVelocity; // + int mOldTime; + + vec3_t mAngles; // for a rotating thing, using a delta + vec3_t mAngleDelta; // as opposed to an end angle is probably much easier + + int mEmitterFxID; // if we have emitter fx, this is our id + + float mDensity; // controls how often emitter chucks an effect + float mVariance; // density sloppiness + + void UpdateAngles(); + + void Draw(); + +public: + + CEmitter() { + // There may or may not be a model, but if there isn't one, + // we just won't bother adding the refEnt in our Draw func + mRefEnt.reType = RT_MODEL; + } + virtual ~CEmitter() {} + + virtual bool Update(); + + inline void SetModel( qhandle_t model ) { mRefEnt.hModel = model; } + inline void SetAngles( const vec3_t ang ) { if(ang){VectorCopy(ang,mAngles);}else{VectorClear(mAngles);} } + inline void SetAngleDelta( const vec3_t ang){ if(ang){VectorCopy(ang,mAngleDelta);}else{VectorClear(mAngleDelta);} } + inline void SetEmitterFxID( int id ) { mEmitterFxID = id; } + inline void SetDensity( float density ) { mDensity = density; } + inline void SetVariance( float var ) { mVariance = var; } + inline void SetOldTime( int time ) { mOldTime = time; } + inline void SetLastOrg( const vec3_t org ) { if(org){VectorCopy(org,mLastOrigin);}else{VectorClear(mLastOrigin);} } + inline void SetLastVel( const vec3_t vel ) { if(vel){VectorCopy(vel,mOldVelocity);}else{VectorClear(mOldVelocity);}} + +}; + +// We're getting pretty low level here, not the kind of thing to abuse considering how much overhead this +// adds to a SINGLE triangle or quad.... +// The editor doesn't need to see or do anything with this +//------------------------------ +#define MAX_CPOLY_VERTS 5 + +class CPoly : public CParticle +{ +protected: + + int mCount; + vec3_t mRotDelta; + int mTimeStamp; + + bool Cull(); + void Draw(); + +public: + + vec3_t mOrg[MAX_CPOLY_VERTS]; + vec2_t mST[MAX_CPOLY_VERTS]; + + float mRot[3][3]; + int mLastFrameTime; + + + CPoly() {} + virtual ~CPoly() {} + + virtual bool Update(); + + void PolyInit(); + void CalcRotateMatrix(); + void Rotate(); + + inline void SetNumVerts( int c ) { mCount = c; } + inline void SetRot( vec3_t r ) { if(r){VectorCopy(r,mRotDelta);}else{VectorClear(mRotDelta);}} + inline void SetMotionTimeStamp( int t ) { mTimeStamp = theFxHelper.mTime + t; } + inline int GetMotionTimeStamp() { return mTimeStamp; } +}; + + +#endif //FX_PRIMITIVES_H_INC \ No newline at end of file diff --git a/code/cgame/FxScheduler.cpp b/code/cgame/FxScheduler.cpp new file mode 100644 index 0000000..f368f92 --- /dev/null +++ b/code/cgame/FxScheduler.cpp @@ -0,0 +1,2049 @@ +// this include must remain at the top of every CPP file +#include "common_headers.h" + + +#if !defined(FX_SCHEDULER_H_INC) + #include "FxScheduler.h" +#endif + +#if !defined(GHOUL2_SHARED_H_INC) + #include "..\game\ghoul2_shared.h" //for CGhoul2Info_v +#endif + +#if !defined(G2_H_INC) + #include "../ghoul2/G2.h" +#endif + +#if !defined(__Q_SHARED_H) + #include "../game/q_shared.h" +#endif + + +CFxScheduler theFxScheduler; + +// don't even ask,. it's to do with loadsave... +// +vector < sstring_t > g_vstrEffectsNeededPerSlot; +SLoopedEffect gLoopedEffectArray[MAX_LOOPED_FX]; // must be in sync with CFxScheduler::mLoopedEffectArray +void CFxScheduler::FX_CopeWithAnyLoadedSaveGames(void) +{ + if ( !g_vstrEffectsNeededPerSlot.empty() ) + { + memcpy( mLoopedEffectArray, gLoopedEffectArray, sizeof(mLoopedEffectArray) ); + assert( g_vstrEffectsNeededPerSlot.size() == MAX_LOOPED_FX ); + + for (int iFX = 0; iFX < g_vstrEffectsNeededPerSlot.size(); iFX++) + { + const char *psFX_Filename = g_vstrEffectsNeededPerSlot[iFX].c_str(); + if ( psFX_Filename[0] ) + { + // register it... + // + mLoopedEffectArray[ iFX ].mId = RegisterEffect( psFX_Filename ); + // + // cope with any relative stop time... + // + if ( mLoopedEffectArray[ iFX ].mLoopStopTime ) + { + mLoopedEffectArray[ iFX ].mLoopStopTime -= mLoopedEffectArray[ iFX ].mNextTime; + } + // + // and finally reset the time to be the newly-zeroed game time... + // + mLoopedEffectArray[ iFX ].mNextTime = 0; // otherwise it won't process until game time catches up + } + else + { + mLoopedEffectArray[ iFX ].mId = 0; + } + } + + g_vstrEffectsNeededPerSlot.clear(); + } +} + +void FX_CopeWithAnyLoadedSaveGames(void) +{ + theFxScheduler.FX_CopeWithAnyLoadedSaveGames(); +} + +// for loadsave... +// +void FX_Read( void ) +{ + theFxScheduler.LoadSave_Read(); +} + +// for loadsave... +// +void FX_Write( void ) +{ + theFxScheduler.LoadSave_Write(); +} + +void CFxScheduler::LoadSave_Read() +{ + Clean(); // need to get rid of old pre-cache handles, or it thinks it has some older effects when it doesn't + g_vstrEffectsNeededPerSlot.clear(); // jic + gi.ReadFromSaveGame('FXLE', (void *) &gLoopedEffectArray, sizeof(gLoopedEffectArray)); + // + // now read in and re-register the effects we need for those structs... + // + for (int iFX = 0; iFX < MAX_LOOPED_FX; iFX++) + { + char sFX_Filename[MAX_QPATH]; + gi.ReadFromSaveGame('FXFN', sFX_Filename, sizeof(sFX_Filename)); + g_vstrEffectsNeededPerSlot.push_back( sFX_Filename ); + } +} + +void CFxScheduler::LoadSave_Write() +{ + // bsave the data we need... + // + gi.AppendToSaveGame('FXLE', mLoopedEffectArray, sizeof(mLoopedEffectArray)); + // + // then cope with the fact that the mID field in each struct of the array we've just saved will not + // necessarily point at the same thing when reloading, so save out the actual fx filename strings they + // need for re-registration... + // + // since this is only for savegames, and I've got < 2 hours to finish this and test it I'm going to be lazy + // with the ondisk data... (besides, the RLE compression will kill most of this anyway) + // + for (int iFX = 0; iFX < MAX_LOOPED_FX; iFX++) + { + char sFX_Filename[MAX_QPATH]; + memset(sFX_Filename,0,sizeof(sFX_Filename)); // instead of "sFX_Filename[0]=0;" so RLE will squash whole array to nothing, not just stop at '\0' then have old crap after it to compress + + int &iID = mLoopedEffectArray[ iFX ].mId; + if ( iID ) + { + // now we need to look up what string this represents, unfortunately the existing + // lookup table is backwards (keywise) for our needs, so parse the whole thing... + // + for (TEffectID::iterator it = mEffectIDs.begin(); it != mEffectIDs.end(); ++it) + { + if ( (*it).second == iID ) + { + Q_strncpyz( sFX_Filename, (*it).first.c_str(), sizeof(sFX_Filename) ); + break; + } + } + } + + // write out this string... + // + gi.AppendToSaveGame('FXFN', sFX_Filename, sizeof(sFX_Filename)); + } +} + + +//----------------------------------------------------------- +void CMediaHandles::operator=(const CMediaHandles &that ) +{ + mMediaList.clear(); + + for ( int i = 0; i < that.mMediaList.size(); i++ ) + { + mMediaList.push_back( that.mMediaList[i] ); + } +} + +//------------------------------------------------------ +CFxScheduler::CFxScheduler() +{ + memset( &mEffectTemplates, 0, sizeof( mEffectTemplates )); + memset( &mLoopedEffectArray, 0, sizeof( mLoopedEffectArray )); +} + +int CFxScheduler::ScheduleLoopedEffect( int id, int boltInfo, bool isPortal, int iLoopTime, bool isRelative ) +{ + int i; + + assert(id); + assert(boltInfo!=-1); + + for (i=0;i> ENTITY_SHIFT ) & ENTITY_AND; + if ( cg_entities[entNum].gent->inuse ) + {// only play the looped effect when the ent is still inUse.... + PlayEffect( mLoopedEffectArray[i].mId, cg_entities[entNum].lerpOrigin, 0, mLoopedEffectArray[i].mBoltInfo, -1, mLoopedEffectArray[i].mPortalEffect, false, mLoopedEffectArray[i].mIsRelative ); //very important to send FALSE looptime to not recursively add me! + mLoopedEffectArray[i].mNextTime = theFxHelper.mTime + mEffectTemplates[mLoopedEffectArray[i].mId].mRepeatDelay; + } + else + { + theFxHelper.Print( "CFxScheduler::AddLoopedEffects- entity was removed without stopping any looping fx it owned." ); + memset( &mLoopedEffectArray[i], 0, sizeof(mLoopedEffectArray[i]) ); + continue; + } + if ( mLoopedEffectArray[i].mLoopStopTime && mLoopedEffectArray[i].mLoopStopTime < theFxHelper.mTime ) //time's up + {//kill this entry + memset( &mLoopedEffectArray[i], 0, sizeof(mLoopedEffectArray[i]) ); + } + } + } + +} + +//----------------------------------------------------------- +void SEffectTemplate::operator=(const SEffectTemplate &that) +{ + mCopy = true; + + strcpy( mEffectName, that.mEffectName ); + + mPrimitiveCount = that.mPrimitiveCount; + + for( int i = 0; i < mPrimitiveCount; i++ ) + { + mPrimitives[i] = new CPrimitiveTemplate; + *(mPrimitives[i]) = *(that.mPrimitives[i]); + // Mark use as a copy so that we know that we should be chucked when used up + mPrimitives[i]->mCopy = true; + } +} + +//------------------------------------------------------ +// Clean +// Free up any memory we've allocated so we aren't leaking memory +// +// Input: +// Whether to clean everything or just stop the playing (active) effects +// +// Return: +// None +// +//------------------------------------------------------ +void CFxScheduler::Clean(bool bRemoveTemplates /*= true*/, int idToPreserve /*= 0*/) +{ + int i, j; + TScheduledEffect::iterator itr, next; + + // Ditch any scheduled effects + itr = mFxSchedule.begin(); + + while ( itr != mFxSchedule.end() ) + { + next = itr; + next++; + + delete *itr; + mFxSchedule.erase(itr); + + itr = next; + } + + if (bRemoveTemplates) + { + // Ditch any effect templates + for ( i = 1; i < FX_MAX_EFFECTS; i++ ) + { + if ( i == idToPreserve) + { + continue; + } + + if ( mEffectTemplates[i].mInUse ) + { + // Ditch the primitives + for (j = 0; j < mEffectTemplates[i].mPrimitiveCount; j++) + { + delete mEffectTemplates[i].mPrimitives[j]; + } + } + + mEffectTemplates[i].mInUse = false; + } + + if (idToPreserve == 0) + { + mEffectIDs.clear(); + } + else + { + // Clear the effect names, but first get the name of the effect to preserve, + // and restore it after clearing. + fxString_t str; + TEffectID::iterator iter; + + for (iter = mEffectIDs.begin(); iter != mEffectIDs.end(); ++iter) + { + if ((*iter).second == idToPreserve) + { + str = (*iter).first; + break; + } + } + + mEffectIDs.clear(); + + mEffectIDs[str] = idToPreserve; + } + } +} + +//------------------------------------------------------ +// RegisterEffect +// Attempt to open the specified effect file, if +// file read succeeds, parse the file. +// +// Input: +// path or filename to open +// +// Return: +// int handle to the effect +//------------------------------------------------------ +int CFxScheduler::RegisterEffect( const char *file, bool bHasCorrectPath /*= false*/ ) +{ + // Dealing with file names: + // File names can come from two places - the editor, in which case we should use the given + // path as is, and the effect file, in which case we should add the correct path and extension. + // In either case we create a stripped file name to use for naming effects. + // + + char sfile[MAX_QPATH]; + + // Get an extension stripped version of the file + if (bHasCorrectPath) + { + const char *last = file, *p = file; + + while (*p != '\0') + { + if ((*p == '/') || (*p == '\\')) + { + last = p + 1; + } + + p++; + } + + COM_StripExtension( last, sfile ); + } + else + { + COM_StripExtension( file, sfile ); + } + + // see if the specified file is already registered. If it is, just return the id of that file + TEffectID::iterator itr; + + itr = mEffectIDs.find( sfile ); + + if ( itr != mEffectIDs.end() ) + { + return (*itr).second; + } + + CGenericParser2 parser; + int len = 0; + fileHandle_t fh; + char *data; + char temp[MAX_QPATH]; + const char *pfile; + char *bufParse = 0; + + if (bHasCorrectPath) + { + pfile = file; + } + else + { + // Add on our extension and prepend the file with the default path + sprintf( temp, "%s/%s.efx", FX_FILE_PATH, sfile ); + pfile = temp; + } + + len = theFxHelper.OpenFile( pfile, &fh, FS_READ ); + + if ( len < 0 ) + { + theFxHelper.Print( "RegisterEffect: failed to load: %s\n", pfile ); + return 0; + } + + if (len == 0) + { + theFxHelper.Print( "RegisterEffect: INVALID file: %s\n", pfile ); + theFxHelper.CloseFile( fh ); + return 0; + } + + // Allocate enough space to hold the file + // This should be flagged temp, but it seems ok as is. + data = new char[len+1]; + + // Get the goods and ensure Null termination + theFxHelper.ReadFile( data, len, fh ); + data[len] = '\0'; + bufParse = data; + + // Let the generic parser process the whole file + parser.Parse( &bufParse ); + + theFxHelper.CloseFile( fh ); + + // Delete our temp copy of the file + delete [] data; + + // Lets convert the effect file into something that we can work with + return ParseEffect( sfile, parser.GetBaseParseGroup() ); +} + + +//------------------------------------------------------ +// ParseEffect +// Starts at ground zero, using each group header to +// determine which kind of effect we are working with. +// Then we call the appropriate function to parse the +// specified effect group. +// +// Input: +// base group, essentially the whole files contents +// +// Return: +// int handle of the effect +//------------------------------------------------------ +int CFxScheduler::ParseEffect( const char *file, CGPGroup *base ) +{ + CGPGroup *primitiveGroup; + CPrimitiveTemplate *prim; + const char *grpName; + SEffectTemplate *effect = 0; + EPrimType type; + int handle; + CGPValue *pair; + + effect = GetNewEffectTemplate( &handle, file ); + + if ( !handle || !effect ) + { + // failure + return 0; + } + + if ((pair = base->GetPairs())!=0) + { + grpName = pair->GetName(); + if ( !stricmp( grpName, "repeatDelay" )) + { + effect->mRepeatDelay = atoi(pair->GetTopValue()); + } + else + {//unknown + + } + } + + primitiveGroup = base->GetSubGroups(); + + while ( primitiveGroup ) + { + grpName = primitiveGroup->GetName(); + + // Huge stricmp lists suxor + if ( !stricmp( grpName, "particle" )) + { + type = Particle; + } + else if ( !stricmp( grpName, "line" )) + { + type = Line; + } + else if ( !stricmp( grpName, "tail" )) + { + type = Tail; + } + else if ( !stricmp( grpName, "sound" )) + { + type = Sound; + } +#ifdef _IMMERSION + else if ( !stricmp( grpName, "forcefeedback" )) + { + type = Force; + } +#endif // _IMMERSION + else if ( !stricmp( grpName, "cylinder" )) + { + type = Cylinder; + } + else if ( !stricmp( grpName, "electricity" )) + { + type = Electricity; + } + else if ( !stricmp( grpName, "emitter" )) + { + type = Emitter; + } + else if ( !stricmp( grpName, "decal" )) + { + type = Decal; + } + else if ( !stricmp( grpName, "orientedparticle" )) + { + type = OrientedParticle; + } + else if ( !stricmp( grpName, "fxrunner" )) + { + type = FxRunner; + } + else if ( !stricmp( grpName, "light" )) + { + type = Light; + } + else if ( !stricmp( grpName, "cameraShake" )) + { + type = CameraShake; + } + else if ( !stricmp( grpName, "flash" )) + { + type = ScreenFlash; + } + else + { + type = None; + } + + if ( type != None ) + { + prim = new CPrimitiveTemplate; + + prim->mType = type; + prim->ParsePrimitive( primitiveGroup ); + + // Add our primitive template to the effect list + AddPrimitiveToEffect( effect, prim ); + } + + primitiveGroup = (CGPGroup *)primitiveGroup->GetNext(); + } + + return handle; +} + + +//------------------------------------------------------ +// AddPrimitiveToEffect +// Takes a primitive and attaches it to the effect. +// +// Input: +// Effect template that we tack the primitive on to +// Primitive to add to the effect template +// +// Return: +// None +//------------------------------------------------------ +void CFxScheduler::AddPrimitiveToEffect( SEffectTemplate *fx, CPrimitiveTemplate *prim ) +{ + int ct = fx->mPrimitiveCount; + + if ( ct >= FX_MAX_EFFECT_COMPONENTS ) + { + theFxHelper.Print( "FxScheduler: Error--too many primitives in an effect\n" ); + } + else + { + fx->mPrimitives[ct] = prim; + fx->mPrimitiveCount++; + } +} + +//------------------------------------------------------ +// GetNewEffectTemplate +// Finds an unused effect template and returns it to the +// caller. +// +// Input: +// pointer to an id that will be filled in, +// file name-- should be NULL when requesting a copy +// +// Return: +// the id of the added effect template +//------------------------------------------------------ +SEffectTemplate *CFxScheduler::GetNewEffectTemplate( int *id, const char *file ) +{ + SEffectTemplate *effect; + + // wanted zero to be a bogus effect ID, so we just skip it. + for ( int i = 1; i < FX_MAX_EFFECTS; i++ ) + { + effect = &mEffectTemplates[i]; + + if ( !effect->mInUse ) + { + *id = i; + memset( effect, 0, sizeof( SEffectTemplate )); + + // If we are a copy, we really won't have a name that we care about saving for later + if ( file ) + { + mEffectIDs[file] = i; + strcpy( effect->mEffectName, file ); + } + + effect->mInUse = true; + effect->mRepeatDelay = 300; + return effect; + } + } + + theFxHelper.Print( "FxScheduler: Error--reached max effects\n" ); + *id = 0; + return 0; +} + +//------------------------------------------------------ +// GetEffectCopy +// Returns a copy of the desired effect so that it can +// easily be modified run-time. +// +// Input: +// file-- the name of the effect file that you want a copy of +// newHandle-- will actually be the returned handle to the new effect +// you have to hold onto this if you intend to call it again +// +// Return: +// the pointer to the copy +//------------------------------------------------------ +SEffectTemplate *CFxScheduler::GetEffectCopy( const char *file, int *newHandle ) +{ + return ( GetEffectCopy( mEffectIDs[file], newHandle ) ); +} + +//------------------------------------------------------ +// GetEffectCopy +// Returns a copy of the desired effect so that it can +// easily be modified run-time. +// +// Input: +// fxHandle-- the handle to the effect that you want a copy of +// newHandle-- will actually be the returned handle to the new effect +// you have to hold onto this if you intend to call it again +// +// Return: +// the pointer to the copy +//------------------------------------------------------ +SEffectTemplate *CFxScheduler::GetEffectCopy( int fxHandle, int *newHandle ) +{ + if ( fxHandle < 1 || fxHandle >= FX_MAX_EFFECTS || !mEffectTemplates[fxHandle].mInUse ) + { + // Didn't even request a valid effect to copy!!! + theFxHelper.Print( "FxScheduler: Bad effect file copy request\n" ); + + *newHandle = 0; + return 0; + } + + // never get a copy when time is frozen + if ( fx_freeze.integer ) + { + return 0; + } + + // Copies shouldn't have names, otherwise they could trash our stl map used for getting ID from name + SEffectTemplate *copy = GetNewEffectTemplate( newHandle, NULL ); + + if ( copy && *newHandle ) + { + // do the effect copy and mark us as what we are + *copy = mEffectTemplates[fxHandle]; + copy->mCopy = true; + + // the user had better hold onto this handle if they ever hope to call this effect. + return copy; + } + + // No space left to return an effect + *newHandle = 0; + return 0; +} + +//------------------------------------------------------ +// GetPrimitiveCopy +// Helper function that returns a copy of the desired primitive +// +// Input: +// fxHandle - the pointer to the effect copy you want to override +// componentName - name of the component to find +// +// Return: +// the pointer to the desired primitive +//------------------------------------------------------ +CPrimitiveTemplate *CFxScheduler::GetPrimitiveCopy( SEffectTemplate *effectCopy, const char *componentName ) +{ + if ( !effectCopy || !effectCopy->mInUse ) + { + return NULL; + } + + for ( int i = 0; i < effectCopy->mPrimitiveCount; i++ ) + { + if ( !stricmp( effectCopy->mPrimitives[i]->mName, componentName )) + { + // we found a match, so return it + return effectCopy->mPrimitives[i]; + } + } + + // bah, no good. + return NULL; +} + +//------------------------------------------------------ +static void ReportPlayEffectError(int id) +{ +#ifdef _DEBUG + theFxHelper.Print( "CFxScheduler::PlayEffect called with invalid effect ID: %i\n", id ); +#endif +} + + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Applies a default up +// axis. +// +// Input: +// Effect file id and the origin +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( int id, vec3_t origin, bool isPortal ) +{ + vec3_t axis[3]; + + VectorSet( axis[0], 0, 0, 1 ); + VectorSet( axis[1], 1, 0, 0 ); + VectorSet( axis[2], 0, 1, 0 ); + + PlayEffect( id, origin, axis, -1, -1, isPortal ); +} + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Takes a fwd vector +// and builds a right and up vector +// +// Input: +// Effect file id, the origin, and a fwd vector +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( int id, vec3_t origin, vec3_t forward, bool isPortal ) +{ + vec3_t axis[3]; + + // Take the forward vector and create two arbitrary but perpendicular vectors + VectorCopy( forward, axis[0] ); + MakeNormalVectors( forward, axis[1], axis[2] ); + + PlayEffect( id, origin, axis, -1, -1, isPortal ); +} + +#ifdef _IMMERSION +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Takes a fwd vector +// and builds a right and up vector +// +// Input: +// Effect file id, the origin, a fwd vector, and clientNum +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( int id, int clientNum, vec3_t origin, vec3_t forward, bool isPortal ) +{ + vec3_t axis[3]; + + // Take the forward vector and create two arbitrary but perpendicular vectors + VectorCopy( forward, axis[0] ); + MakeNormalVectors( forward, axis[1], axis[2] ); + + PlayEffect( id, origin, axis, -1, clientNum, isPortal ); +} + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Takes a forward vector +// and uses this to complete the axis field. +// +// Input: +// Effect file name, the origin, and a forward vector +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( const char *file, int clientNum, vec3_t origin, vec3_t forward, bool isPortal ) +{ + char sfile[MAX_QPATH]; + + // Get an extenstion stripped version of the file + COM_StripExtension( file, sfile ); + + PlayEffect( mEffectIDs[sfile], clientNum, origin, forward, isPortal ); + +#ifndef FINAL_BUILD + if ( mEffectIDs[sfile] == 0 ) + { + theFxHelper.Print( "CFxScheduler::PlayEffect unregistered/non-existent effect: %s\n", file ); + } +#endif +} + +#endif // _IMMERSION +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Uses the specified axis +// +// Input: +// Effect file name, the origin, and axis. +// Optional boltInfo (defaults to -1) +// Optional entity number to be used by a cheap entity origin bolt (defaults to -1) +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( const char *file, vec3_t origin, vec3_t axis[3], const int boltInfo, const int entNum, bool isPortal, int iLoopTime, bool isRelative ) +{ + char sfile[MAX_QPATH]; + + // Get an extenstion stripped version of the file + COM_StripExtension( file, sfile ); + + // This is a horribly dumb thing to have to do, but QuakeIII might not have calc'd the lerpOrigin + // for the entity we may be trying to bolt onto. We like having the correct origin, so we are + // forced to call this function.... + if ( entNum > -1 ) + { + CG_CalcEntityLerpPositions( &cg_entities[entNum] ); + } + +#ifndef FINAL_BUILD + if ( mEffectIDs[sfile] == 0 ) + { + theFxHelper.Print( "CFxScheduler::PlayEffect unregistered/non-existent effect: %s\n", sfile ); + } +#endif + + PlayEffect( mEffectIDs[sfile], origin, axis, boltInfo, entNum, isPortal, iLoopTime, isRelative ); +} + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Uses the specified axis +// +// Input: +// Effect file name, the origin, and axis. +// Optional boltInfo (defaults to -1) +// Optional entity number to be used by a cheap entity origin bolt (defaults to -1) +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( const char *file, int clientID, bool isPortal ) +{ + char sfile[MAX_QPATH]; + int id; + + // Get an extenstion stripped version of the file + COM_StripExtension( file, sfile ); + id = mEffectIDs[sfile]; + +#ifndef FINAL_BUILD + if ( id == 0 ) + { + theFxHelper.Print( "CFxScheduler::PlayEffect unregistered/non-existent effect: %s\n", file ); + } +#endif + + SEffectTemplate *fx; + CPrimitiveTemplate *prim; + int i = 0; + int count = 0, delay = 0; + SScheduledEffect *sfx; + float factor = 0.0f; + + if ( id < 1 || id >= FX_MAX_EFFECTS || !mEffectTemplates[id].mInUse ) + { + // Now you've done it! + ReportPlayEffectError(id); + return; + } + + // Don't bother scheduling the effect if the system is currently frozen + + // Get the effect. + fx = &mEffectTemplates[id]; + + // Loop through the primitives and schedule each bit + for ( i = 0; i < fx->mPrimitiveCount; i++ ) + { + prim = fx->mPrimitives[i]; + + count = prim->mSpawnCount.GetRoundedVal(); + + if ( prim->mCopy ) + { + // If we are a copy, we need to store a "how many references count" so that we + // can keep the primitive template around for the correct amount of time. + prim->mRefCount = count; + } + + if ( prim->mSpawnFlags & FX_EVEN_DISTRIBUTION ) + { + factor = abs(prim->mSpawnDelay.GetMax() - prim->mSpawnDelay.GetMin()) / (float)count; + } + + // Schedule the random number of bits + for ( int t = 0; t < count; t++ ) + { + if ( prim->mSpawnFlags & FX_EVEN_DISTRIBUTION ) + { + delay = t * factor; + } + else + { + delay = prim->mSpawnDelay.GetVal(); + } + + // if the delay is so small, we may as well just create this bit right now + if ( delay < 1 && !isPortal ) + { + CreateEffect( prim, clientID, -delay ); + } + else + { + // We have to create a new scheduled effect so that we can create it at a later point + // you should avoid this because it's much more expensive + sfx = new SScheduledEffect; + sfx->mStartTime = theFxHelper.mTime + delay; + sfx->mpTemplate = prim; + sfx->mClientID = clientID; + + if (isPortal) + { + sfx->mPortalEffect = true; + } + else + { + sfx->mPortalEffect = false; + } + + mFxSchedule.push_front( sfx ); + } + } + } + + // We track effect templates and primitive templates separately. + if ( fx->mCopy ) + { + // We don't use dynamic memory allocation, so just mark us as dead + fx->mInUse = false; + } +} + +bool gEffectsInPortal = false; //this is just because I don't want to have to add an mPortalEffect field to every actual effect. + +//------------------------------------------------------ +// CreateEffect +// Creates the specified fx taking into account the +// multitude of different ways it could be spawned. +// +// Input: +// template used to build the effect, desired effect origin, +// desired orientation and how late the effect is so that +// it can be moved to the correct spot +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::CreateEffect( CPrimitiveTemplate *fx, int clientID, int delay ) +{ + vec3_t sRGB, eRGB; + vec3_t vel, accel; + vec3_t org,org2; + int flags = 0; + + // Origin calculations -- completely ignores most things + //------------------------------------- + VectorSet( org, fx->mOrigin1X.GetVal(), fx->mOrigin1Y.GetVal(), fx->mOrigin1Z.GetVal() ); + VectorSet( org2, fx->mOrigin2X.GetVal(), fx->mOrigin2Y.GetVal(), fx->mOrigin2Z.GetVal() ); + + // handle RGB color + if ( fx->mSpawnFlags & FX_RGB_COMPONENT_INTERP ) + { + float perc = random(); + + VectorSet( sRGB, fx->mRedStart.GetVal( perc ), fx->mGreenStart.GetVal( perc ), fx->mBlueStart.GetVal( perc ) ); + VectorSet( eRGB, fx->mRedEnd.GetVal( perc ), fx->mGreenEnd.GetVal( perc ), fx->mBlueEnd.GetVal( perc ) ); + } + else + { + VectorSet( sRGB, fx->mRedStart.GetVal(), fx->mGreenStart.GetVal(), fx->mBlueStart.GetVal() ); + VectorSet( eRGB, fx->mRedEnd.GetVal(), fx->mGreenEnd.GetVal(), fx->mBlueEnd.GetVal() ); + } + + // NOTE: This completely disregards a few specialty flags. + VectorSet( vel, fx->mVelX.GetVal( ), fx->mVelY.GetVal( ), fx->mVelZ.GetVal( ) ); + VectorSet( accel, fx->mAccelX.GetVal( ), fx->mAccelY.GetVal( ), fx->mAccelZ.GetVal( ) ); + + // If depth hack ISN'T already on, then turn it on. Otherwise, we treat a pre-existing depth_hack flag as NOT being depth_hack. + // This is done because muzzle flash fx files are shared amongst all shooters, but for the player we need to do depth hack in first person.... + if ( !( fx->mFlags & FX_DEPTH_HACK ) && !cg.renderingThirdPerson ) // hack! + { + flags = fx->mFlags | FX_RELATIVE | FX_DEPTH_HACK; + } + else + { + flags = (fx->mFlags | FX_RELATIVE) & ~FX_DEPTH_HACK; + } + + // We only support particles for now + //------------------------ + switch( fx->mType ) + { + //--------- + case Particle: + //--------- + + FX_AddParticle( clientID, org, vel, accel, fx->mGravity.GetVal(), + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mRotation.GetVal(), fx->mRotationDelta.GetVal(), + fx->mMin, fx->mMax, fx->mElasticity.GetVal(), + fx->mDeathFxHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags ); + break; + + //--------- + case Line: + //--------- + + FX_AddLine( clientID, org, org2, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), flags ); + break; + + //--------- + case Tail: + //--------- + + FX_AddTail( clientID, org, vel, accel, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mLengthStart.GetVal(), fx->mLengthEnd.GetVal(), fx->mLengthParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mMin, fx->mMax, fx->mElasticity.GetVal(), + fx->mDeathFxHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags ); + break; + + + //--------- + case Sound: + //--------- + + if (gEffectsInPortal) + { //could orient this anyway for panning, but eh. It's going to appear to the player in the sky the same place no matter what, so just make it a local sound. + theFxHelper.PlayLocalSound( fx->mMediaHandles.GetHandle(), CHAN_AUTO ); + } + else + { + // bolted sounds actually play on the client.... + theFxHelper.PlaySound( NULL, clientID, CHAN_WEAPON, fx->mMediaHandles.GetHandle() ); + } + break; + +#ifdef _IMMERSION + //--------- + case Force: + //--------- + + // Analogous to Sound (same assumption defined in RegisterForce) + theFxHelper.PlayForce( clientID, fx->mMediaHandles.GetHandle() ); + break; +#endif // _IMMERSION + //--------- + case Light: + //--------- + + // don't much care if the light stays bolted...so just add it. + if ( clientID >= 0 && clientID < ENTITYNUM_WORLD ) + { + // ..um, ok..... + centity_t *cent = &cg_entities[clientID]; + + if ( cent && cent->gent && cent->gent->client ) + { + FX_AddLight( cent->gent->client->renderInfo.muzzlePoint, fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mLife.GetVal(), fx->mFlags ); + } + } + break; + + //--------- + case CameraShake: + //--------- + + if ( clientID >= 0 && clientID < ENTITYNUM_WORLD ) + { + // ..um, ok..... + centity_t *cent = &cg_entities[clientID]; + + if ( cent && cent->gent && cent->gent->client ) + { + theFxHelper.CameraShake( cent->gent->currentOrigin, fx->mElasticity.GetVal(), fx->mRadius.GetVal(), fx->mLife.GetVal() ); + } + } + break; + + default: + break; + } + + // Track when we need to clean ourselves up if we are a copy + if ( fx->mCopy ) + { + fx->mRefCount--; + + if ( fx->mRefCount <= 0 ) + { + delete fx; + } + } +} + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Uses the specified axis +// +// Input: +// Effect id, the origin, and axis. +// Optional boltInfo (defaults to -1) +// Optional entity number to be used by a cheap entity origin bolt (defaults to -1) +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( int id, vec3_t origin, vec3_t axis[3], const int boltInfo, const int entNum, bool isPortal, int iLoopTime, bool isRelative ) +{ + SEffectTemplate *fx; + CPrimitiveTemplate *prim; + int i = 0; + int count = 0, delay = 0; + float factor = 0.0f; + bool forceScheduling = false; + + if ( id < 1 || id >= FX_MAX_EFFECTS || !mEffectTemplates[id].mInUse ) + { + // Now you've done it! + ReportPlayEffectError(id); + return; + } + + // Don't bother scheduling the effect if the system is currently frozen + if ( fx_freeze.integer ) + { + return; + } + + int modelNum = 0, boltNum = -1; + int entityNum = entNum; + +#ifdef _IMMERSION + entityNum = + ( entNum < -1 // HACKHACKHACK (negative if effect plays uncentered on an entity) + ? FF_CLIENT( entNum ) // decode -2 as entNum=0, -3 as entNum=1, ... + : entNum // default + ); +#endif // _IMMERSION + if ( boltInfo > 0 ) + { + // extract the wraith ID from the bolt info + modelNum = ( boltInfo >> MODEL_SHIFT ) & MODEL_AND; + boltNum = ( boltInfo >> BOLT_SHIFT ) & BOLT_AND; + entityNum = ( boltInfo >> ENTITY_SHIFT ) & ENTITY_AND; + + // We always force ghoul bolted objects to be scheduled so that they don't play right away. + forceScheduling = true; + + if (iLoopTime)//0 = not looping, 1 for infinite, else duration + {//store off the id to reschedule every frame + ScheduleLoopedEffect(id, boltInfo, isPortal, iLoopTime, isRelative); + } + } + + + // Get the effect. + fx = &mEffectTemplates[id]; + + // Loop through the primitives and schedule each bit + for ( i = 0; i < fx->mPrimitiveCount; i++ ) + { + prim = fx->mPrimitives[i]; + + if ( prim->mCullRange ) + { + if ( DistanceSquared( origin, cg.refdef.vieworg ) > prim->mCullRange ) // cull range has already been squared + { + // is too far away, so don't add this primitive group + continue; + } + } + + count = prim->mSpawnCount.GetRoundedVal(); + + if ( prim->mCopy ) + { + // If we are a copy, we need to store a "how many references count" so that we + // can keep the primitive template around for the correct amount of time. + prim->mRefCount = count; + } + + if ( prim->mSpawnFlags & FX_EVEN_DISTRIBUTION ) + { + factor = abs(prim->mSpawnDelay.GetMax() - prim->mSpawnDelay.GetMin()) / (float)count; + } + + // Schedule the random number of bits + for ( int t = 0; t < count; t++ ) + { + if ( prim->mSpawnFlags & FX_EVEN_DISTRIBUTION ) + { + delay = t * factor; + } + else + { + delay = prim->mSpawnDelay.GetVal(); + } + + // if the delay is so small, we may as well just create this bit right now + if ( delay < 1 && !forceScheduling && !isPortal ) + { +#ifdef _IMMERSION + if ( boltInfo == -1 && entNum > -1 ) +#else + if ( boltInfo == -1 && entNum != -1 ) +#endif // _IMMERSION + { + // Find out where the entity currently is + CreateEffect( prim, cg_entities[entNum].lerpOrigin, axis, -delay ); + } + else + { + CreateEffect( prim, origin, axis, -delay ); + } + } + else + { + // We have to create a new scheduled effect so that we can create it at a later point + // you should avoid this because it's much more expensive + SScheduledEffect *sfx; + sfx = new SScheduledEffect; + sfx->mStartTime = theFxHelper.mTime + delay; + sfx->mpTemplate = prim; + sfx->mClientID = -1; + sfx->mIsRelative = isRelative; + sfx->mEntNum = entityNum; //ent if bolted, else -1 for none, or -2 for _Immersion client 0 + + sfx->mPortalEffect = isPortal; + + if ( boltInfo == -1 ) + { +#ifdef _IMMERSION + if ( entNum <= -1 ) +#else + if ( entNum == -1 ) +#endif // _IMMERSION + { + // we aren't bolting, so make sure the spawn system knows this by putting -1's in these fields + sfx->mBoltNum = -1; + sfx->mModelNum = 0; + + if ( origin ) + { + VectorCopy( origin, sfx->mOrigin ); + } + else + { + VectorClear( sfx->mOrigin ); + } + + AxisCopy( axis, sfx->mAxis ); + } + else + { + // we are doing bolting onto the origin of the entity, so use a cheaper method + sfx->mBoltNum = -1; + sfx->mModelNum = 0; + + AxisCopy( axis, sfx->mAxis ); + } + } + else + { + // we are bolting, so store the extra info + sfx->mBoltNum = boltNum; + sfx->mModelNum = modelNum; + + // Also, the ghoul bolt may not be around yet, so delay the creation one frame + sfx->mStartTime++; + } + + mFxSchedule.push_front( sfx ); + } + } + } + + // We track effect templates and primitive templates separately. + if ( fx->mCopy ) + { + // We don't use dynamic memory allocation, so just mark us as dead + fx->mInUse = false; + } +} + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Applies a default up +// axis. +// +// Input: +// Effect file name and the origin +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( const char *file, vec3_t origin, bool isPortal ) +{ + char sfile[MAX_QPATH]; + + // Get an extenstion stripped version of the file + COM_StripExtension( file, sfile ); + + PlayEffect( mEffectIDs[sfile], origin, isPortal ); + +#ifndef FINAL_BUILD + if ( mEffectIDs[sfile] == 0 ) + { + theFxHelper.Print( "CFxScheduler::PlayEffect unregistered/non-existent effect: %s\n", file ); + } +#endif +} + +//------------------------------------------------------ +// PlayEffect +// Handles scheduling an effect so all the components +// happen at the specified time. Takes a forward vector +// and uses this to complete the axis field. +// +// Input: +// Effect file name, the origin, and a forward vector +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::PlayEffect( const char *file, vec3_t origin, vec3_t forward, bool isPortal ) +{ + char sfile[MAX_QPATH]; + + // Get an extenstion stripped version of the file + COM_StripExtension( file, sfile ); + + PlayEffect( mEffectIDs[sfile], origin, forward, isPortal ); + +#ifndef FINAL_BUILD + if ( mEffectIDs[sfile] == 0 ) + { + theFxHelper.Print( "CFxScheduler::PlayEffect unregistered/non-existent effect: %s\n", file ); + } +#endif +} + +//------------------------------------------------------ +// AddScheduledEffects +// Handles determining if a scheduled effect should +// be created or not. If it should it handles converting +// the template effect into a real one. +// +// Input: +// boolean portal (true when adding effects to be drawn in the skyportal) +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::AddScheduledEffects( bool portal ) +{ + TScheduledEffect::iterator itr, next; + vec3_t origin; + vec3_t axis[3]; + int oldEntNum = -1, oldBoltIndex = -1, oldModelNum = -1; + qboolean doesBoltExist = qfalse; + + if (portal) + { + gEffectsInPortal = true; + } + else + { + AddLoopedEffects(); + } + + itr = mFxSchedule.begin(); + + while ( itr != mFxSchedule.end() ) + { + next = itr; + next++; + + if (portal == (*itr)->mPortalEffect) + { + if ( *(*itr) <= theFxHelper.mTime ) + { + if ( (*itr)->mClientID >= 0 ) + { + CreateEffect( (*itr)->mpTemplate, (*itr)->mClientID, + theFxHelper.mTime - (*itr)->mStartTime ); + } + else if ((*itr)->mBoltNum == -1) + {// normal effect + #ifdef _IMMERSION + int entNum = (*itr)->mEntNum; + int hitEntNum = ( entNum < -1 ? FF_CLIENT( entNum ) : entNum ); + + CreateEffect + ( (*itr)->mpTemplate + , (entNum >= 0 ? cg_entities[entNum].lerpOrigin : (*itr)->mOrigin) + , (*itr)->mAxis + , theFxHelper.mTime - (*itr)->mStartTime + , hitEntNum + ); + #else + if ( (*itr)->mEntNum != -1 ) + { + // Find out where the entity currently is + CreateEffect( (*itr)->mpTemplate, + cg_entities[(*itr)->mEntNum].lerpOrigin, (*itr)->mAxis, + theFxHelper.mTime - (*itr)->mStartTime ); + } + else + { + CreateEffect( (*itr)->mpTemplate, + (*itr)->mOrigin, (*itr)->mAxis, + theFxHelper.mTime - (*itr)->mStartTime ); + } + #endif // _IMMERSION + } + else + { //bolted on effect + // do we need to go and re-get the bolt matrix again? Since it takes time lets try to do it only once + if (((*itr)->mModelNum != oldModelNum) || ((*itr)->mEntNum != oldEntNum) || ((*itr)->mBoltNum != oldBoltIndex)) + { + const centity_t ¢ = cg_entities[(*itr)->mEntNum]; + if (cent.gent->ghoul2.IsValid()) + { + if ((*itr)->mModelNum>=0&&(*itr)->mModelNumghoul2.size()) + { + if (cent.gent->ghoul2[(*itr)->mModelNum].mModelindex>=0) + { + doesBoltExist = theFxHelper.GetOriginAxisFromBolt(cent, (*itr)->mModelNum, (*itr)->mBoltNum, origin, axis); + } + } + } + + oldModelNum = (*itr)->mModelNum; + oldEntNum = (*itr)->mEntNum; + oldBoltIndex = (*itr)->mBoltNum; + } + + // only do this if we found the bolt + if (doesBoltExist) + { + if ((*itr)->mIsRelative ) + { + CreateEffect( (*itr)->mpTemplate, + vec3_origin, axis, + 0, (*itr)->mEntNum, (*itr)->mModelNum, (*itr)->mBoltNum ); + } + else + { + CreateEffect( (*itr)->mpTemplate, + origin, axis, + theFxHelper.mTime - (*itr)->mStartTime ); + } + } + } + + // Get 'em out of there. + delete *itr; + mFxSchedule.erase(itr); + } + } + + itr = next; + } + + // Add all active effects into the scene + FX_Add(portal); + + gEffectsInPortal = false; +} + +//------------------------------------------------------ +// CreateEffect +// Creates the specified fx taking into account the +// multitude of different ways it could be spawned. +// +// Input: +// template used to build the effect, desired effect origin, +// desired orientation and how late the effect is so that +// it can be moved to the correct spot +// +// Return: +// none +//------------------------------------------------------ +void CFxScheduler::CreateEffect( CPrimitiveTemplate *fx, const vec3_t origin, vec3_t axis[3], int lateTime, int clientID, int modelNum, int boltNum ) +{ + vec3_t org, org2, temp, + vel, accel, + sRGB, eRGB, + ang, angDelta, + ax[3]; + trace_t tr; + int emitterModel; + + // We may modify the axis, so make a work copy + AxisCopy( axis, ax ); + + int flags = fx->mFlags; + if (clientID>=0 && modelNum>=0 && boltNum>=0) + {//since you passed in these values, mark as relative to use them + flags |= FX_RELATIVE; + } + + if( fx->mSpawnFlags & FX_RAND_ROT_AROUND_FWD ) + { + RotatePointAroundVector( ax[1], ax[0], axis[1], random()*360.0f ); + CrossProduct( ax[0], ax[1], ax[2] ); + } + + // Origin calculations + //------------------------------------- + if ( fx->mSpawnFlags & FX_CHEAP_ORG_CALC || flags & FX_RELATIVE ) + { // let's take the easy way out + VectorSet( org, fx->mOrigin1X.GetVal(), fx->mOrigin1Y.GetVal(), fx->mOrigin1Z.GetVal() ); + } + else + { // time for some extra work + VectorScale( ax[0], fx->mOrigin1X.GetVal(), org ); + VectorMA( org, fx->mOrigin1Y.GetVal(), ax[1], org ); + VectorMA( org, fx->mOrigin1Z.GetVal(), ax[2], org ); + } + + // We always add our calculated offset to the passed in origin... + VectorAdd( org, origin, org ); + + // Now, we may need to calc a point on a sphere/ellipsoid/cylinder/disk and add that to it + //---------------------------------------------------------------- + if ( fx->mSpawnFlags & FX_ORG_ON_SPHERE ) + { + float x, y; + float width, height; + + x = DEG2RAD( random() * 360.0f ); + y = DEG2RAD( random() * 180.0f ); + + width = fx->mRadius.GetVal(); + height = fx->mHeight.GetVal(); + + // calculate point on ellipse + VectorSet( temp, sin(x) * width * sin(y), cos(x) * width * sin(y), cos(y) * height ); // sinx * siny, cosx * siny, cosy + VectorAdd( org, temp, org ); + + if ( fx->mSpawnFlags & FX_AXIS_FROM_SPHERE ) + { + // well, we will now override the axis at the users request + VectorNormalize2( temp, ax[0] ); + MakeNormalVectors( ax[0], ax[1], ax[2] ); + } + } + else if ( fx->mSpawnFlags & FX_ORG_ON_CYLINDER ) + { + vec3_t pt; + + // set up our point, then rotate around the current direction to. Make unrotated cylinder centered around 0,0,0 + VectorScale( ax[1], fx->mRadius.GetVal(), pt ); + VectorMA( pt, crandom() * 0.5f * fx->mHeight.GetVal(), ax[0], pt ); + RotatePointAroundVector( temp, ax[0], pt, random() * 360.0f ); + + VectorAdd( org, temp, org ); + + if ( fx->mSpawnFlags & FX_AXIS_FROM_SPHERE ) + { + vec3_t up={0,0,1}; + + // well, we will now override the axis at the users request + VectorNormalize2( temp, ax[0] ); + + if ( ax[0][2] == 1.0f ) + { + // readjust up + VectorSet( up, 0, 1, 0 ); + } + + CrossProduct( up, ax[0], ax[1] ); + CrossProduct( ax[0], ax[1], ax[2] ); + } + } + + + // There are only a few types that really use velocity and acceleration, so do extra work for those types + //-------------------------------------------------------------------------------------------------------- + if ( fx->mType == Particle || fx->mType == OrientedParticle || fx->mType == Tail || fx->mType == Emitter ) + { + // Velocity calculations + //------------------------------------- + if ( fx->mSpawnFlags & FX_VEL_IS_ABSOLUTE || flags & FX_RELATIVE ) + { + VectorSet( vel, fx->mVelX.GetVal(), fx->mVelY.GetVal(), fx->mVelZ.GetVal() ); + } + else + { // bah, do some extra work to coerce it + VectorScale( ax[0], fx->mVelX.GetVal(), vel ); + VectorMA( vel, fx->mVelY.GetVal(), ax[1], vel ); + VectorMA( vel, fx->mVelZ.GetVal(), ax[2], vel ); + } + + // Acceleration calculations + //------------------------------------- + if ( fx->mSpawnFlags & FX_ACCEL_IS_ABSOLUTE || flags & FX_RELATIVE ) + { + VectorSet( accel, fx->mAccelX.GetVal(), fx->mAccelY.GetVal(), fx->mAccelZ.GetVal() ); + } + else + { + VectorScale( ax[0], fx->mAccelX.GetVal(), accel ); + VectorMA( accel, fx->mAccelY.GetVal(), ax[1], accel ); + VectorMA( accel, fx->mAccelZ.GetVal(), ax[2], accel ); + } + + // Gravity is completely decoupled from acceleration since it is __always__ absolute + // NOTE: I only effect Z ( up/down in the Quake world ) + accel[2] += fx->mGravity.GetVal(); + + // There may be a lag between when the effect should be created and when it actually gets created. + // Since we know what the discrepancy is, we can attempt to compensate... + if ( lateTime > 0 ) + { + // Calc the time differences + float ftime = lateTime * 0.001f; + float time2 = ftime * ftime * 0.5f; + + VectorMA( vel, ftime, accel, vel ); + + // Predict the new position + for ( int i = 0 ; i < 3 ; i++ ) + { + org[i] = org[i] + ftime * vel[i] + time2 * vel[i]; + } + } + } // end moving types + + // Line type primitives work with an origin2, so do the extra work for them + //-------------------------------------------------------------------------- + if ( fx->mType == Line || fx->mType == Electricity ) + { + // We may have to do a trace to find our endpoint + if ( fx->mSpawnFlags & FX_ORG2_FROM_TRACE ) + { + VectorMA( org, FX_MAX_TRACE_DIST, ax[0], temp ); + + if ( fx->mSpawnFlags & FX_ORG2_IS_OFFSET ) + { // add a random flair to the endpoint...note: org2 will have to be pretty large to affect this much + // we also do this pre-trace as opposed to post trace since we may have to render an impact effect + // and we will want the normal at the exact endpos... + if ( fx->mSpawnFlags & FX_CHEAP_ORG2_CALC || flags & FX_RELATIVE ) + { + VectorSet( org2, fx->mOrigin2X.GetVal(), fx->mOrigin2Y.GetVal(), fx->mOrigin2Z.GetVal() ); + VectorAdd( org2, temp, temp ); + } + else + { // I can only imagine a few cases where you might want to do this... + VectorMA( temp, fx->mOrigin2X.GetVal(), ax[0], temp ); + VectorMA( temp, fx->mOrigin2Y.GetVal(), ax[1], temp ); + VectorMA( temp, fx->mOrigin2Z.GetVal(), ax[2], temp ); + } + } + + theFxHelper.Trace( &tr, org, NULL, NULL, temp, -1, CONTENTS_SOLID | CONTENTS_SHOTCLIP );//MASK_SHOT ); + + if ( tr.startsolid || tr.allsolid ) + { + VectorCopy( org, org2 ); // this is not a very good solution + } + else + { + VectorCopy( tr.endpos, org2 ); + } + + if ( fx->mSpawnFlags & FX_TRACE_IMPACT_FX ) + { + PlayEffect( fx->mImpactFxHandles.GetHandle(), org2, tr.plane.normal ); + } + } + else + { + if ( fx->mSpawnFlags & FX_CHEAP_ORG2_CALC || flags & FX_RELATIVE ) + { + VectorSet( org2, fx->mOrigin2X.GetVal(), fx->mOrigin2Y.GetVal(), fx->mOrigin2Z.GetVal() ); + } + else + { + VectorScale( ax[0], fx->mOrigin2X.GetVal(), org2 ); + VectorMA( org2, fx->mOrigin2Y.GetVal(), ax[1], org2 ); + VectorMA( org2, fx->mOrigin2Z.GetVal(), ax[2], org2 ); + + VectorAdd( org2, origin, org2 ); + } + + } + } // end special org2 types + + // handle RGB color, but only for types that will use it + //--------------------------------------------------------------------------- +#ifdef _IMMERSION + if ( fx->mType != Sound && fx->mType != FxRunner && fx->mType != CameraShake && fx->mType != Force ) +#else + if ( fx->mType != Sound && fx->mType != FxRunner && fx->mType != CameraShake ) +#endif // _IMMERSION + { + if ( fx->mSpawnFlags & FX_RGB_COMPONENT_INTERP ) + { + float perc = random(); + + VectorSet( sRGB, fx->mRedStart.GetVal( perc ), fx->mGreenStart.GetVal( perc ), fx->mBlueStart.GetVal( perc ) ); + VectorSet( eRGB, fx->mRedEnd.GetVal( perc ), fx->mGreenEnd.GetVal( perc ), fx->mBlueEnd.GetVal( perc ) ); + } + else + { + VectorSet( sRGB, fx->mRedStart.GetVal(), fx->mGreenStart.GetVal(), fx->mBlueStart.GetVal() ); + VectorSet( eRGB, fx->mRedEnd.GetVal(), fx->mGreenEnd.GetVal(), fx->mBlueEnd.GetVal() ); + } + } + + // Now create the appropriate effect entity + //------------------------ + switch( fx->mType ) + { + //--------- + case Particle: + //--------- + + FX_AddParticle( clientID, org, vel, accel, fx->mGravity.GetVal(), + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mRotation.GetVal(), fx->mRotationDelta.GetVal(), + fx->mMin, fx->mMax, fx->mElasticity.GetVal(), + fx->mDeathFxHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, modelNum, boltNum ); + break; + + //--------- + case Line: + //--------- + + FX_AddLine( clientID, org, org2, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), flags, modelNum, boltNum ); + break; + + //--------- + case Tail: + //--------- + + FX_AddTail( clientID, org, vel, accel, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mLengthStart.GetVal(), fx->mLengthEnd.GetVal(), fx->mLengthParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mMin, fx->mMax, fx->mElasticity.GetVal(), + fx->mDeathFxHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, modelNum, boltNum ); + break; + + //---------------- + case Electricity: + //---------------- + + FX_AddElectricity( clientID, org, org2, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mElasticity.GetVal(), fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, modelNum, boltNum ); + break; + + //--------- + case Cylinder: + //--------- + + FX_AddCylinder( clientID, org, ax[0], + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mSize2Start.GetVal(), fx->mSize2End.GetVal(), fx->mSize2Parm.GetVal(), + fx->mLengthStart.GetVal(), fx->mLengthEnd.GetVal(), fx->mLengthParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, modelNum, boltNum ); + break; + + //--------- + case Emitter: + //--------- + + // for chunk angles, you don't really need much control over the end result...you just want variation.. + VectorSet( ang, + fx->mAngle1.GetVal(), + fx->mAngle2.GetVal(), + fx->mAngle3.GetVal() ); + + vectoangles( ax[0], temp ); + VectorAdd( ang, temp, ang ); + + VectorSet( angDelta, + fx->mAngle1Delta.GetVal(), + fx->mAngle2Delta.GetVal(), + fx->mAngle3Delta.GetVal() ); + + emitterModel = fx->mMediaHandles.GetHandle(); + + FX_AddEmitter( org, vel, accel, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + ang, angDelta, + fx->mMin, fx->mMax, fx->mElasticity.GetVal(), + fx->mDeathFxHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), + fx->mEmitterFxHandles.GetHandle(), + fx->mDensity.GetVal(), fx->mVariance.GetVal(), + fx->mLife.GetVal(), emitterModel, flags ); + break; + + //--------- + case Decal: + //--------- + + // I'm calling this function ( at least for now ) because it handles projecting + // the decal mark onto the surfaces properly. This is especially important for large marks. + // The downside is that it's much less flexible.... + CG_ImpactMark( fx->mMediaHandles.GetHandle(), org, ax[0], fx->mRotation.GetVal(), + sRGB[0], sRGB[1], sRGB[2], fx->mAlphaStart.GetVal(), + qtrue, fx->mSizeStart.GetVal(), qfalse ); + + if (fx->mFlags & FX_GHOUL2_DECALS) + { + trace_t tr; + vec3_t end; + + VectorMA(org, 64, ax[0], end); + + theFxHelper.G2Trace(&tr, org, NULL, NULL, end, ENTITYNUM_NONE, MASK_PLAYERSOLID); + + if (tr.entityNum < ENTITYNUM_WORLD && + g_entities[tr.entityNum].ghoul2.size()) + { + gentity_t *ent = &g_entities[tr.entityNum]; + + CG_AddGhoul2Mark(fx->mMediaHandles.GetHandle(), fx->mSizeStart.GetVal(), tr.endpos, tr.plane.normal, + tr.entityNum, ent->client->ps.origin, ent->client->ps.viewangles[YAW], + ent->ghoul2, ent->s.modelScale, Q_irand(40000, 60000)); + } + } + break; + + //------------------- + case OrientedParticle: + //------------------- + + FX_AddOrientedParticle( clientID, org, ax[0], vel, accel, + fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + fx->mAlphaStart.GetVal(), fx->mAlphaEnd.GetVal(), fx->mAlphaParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mRotation.GetVal(), fx->mRotationDelta.GetVal(), + fx->mMin, fx->mMax, fx->mElasticity.GetVal(), + fx->mDeathFxHandles.GetHandle(), fx->mImpactFxHandles.GetHandle(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), flags, modelNum, boltNum ); + break; + + //--------- + case Sound: + //--------- + if (gEffectsInPortal) + { //could orient this anyway for panning, but eh. It's going to appear to the player in the sky the same place no matter what, so just make it a local sound. + theFxHelper.PlayLocalSound( fx->mMediaHandles.GetHandle(), CHAN_AUTO ); + } + else if ( fx->mSpawnFlags & FX_SND_LESS_ATTENUATION ) + { + theFxHelper.PlaySound( org, ENTITYNUM_NONE, CHAN_LESS_ATTEN, fx->mMediaHandles.GetHandle() ); + } + else + { + theFxHelper.PlaySound( org, ENTITYNUM_NONE, CHAN_AUTO, fx->mMediaHandles.GetHandle() ); + } + break; + +#ifdef _IMMERSION + //--------- + case Force: + //--------- + + if ( clientID > -1 ) // Fix me: Allow or abolish FF_LOCAL_CLIENT? + theFxHelper.PlayForce( clientID, fx->mMediaHandles.GetHandle() ); + break; + +#endif // _IMMERSION + //--------- + case FxRunner: + //--------- + + PlayEffect( fx->mPlayFxHandles.GetHandle(), org, ax ); + break; + + //--------- + case Light: + //--------- + + FX_AddLight( org, fx->mSizeStart.GetVal(), fx->mSizeEnd.GetVal(), fx->mSizeParm.GetVal(), + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mLife.GetVal(), fx->mFlags ); + break; + + //--------- + case CameraShake: + //--------- + // It calculates how intense the shake should be based on how close you are to the origin you pass in here + // elasticity is actually the intensity...radius is the distance in which the shake will have some effect + // life is how long the effect lasts. + theFxHelper.CameraShake( org, fx->mElasticity.GetVal(), fx->mRadius.GetVal(), fx->mLife.GetVal() ); + break; + + //-------------- + case ScreenFlash: + //-------------- + + FX_AddFlash( org, + sRGB, eRGB, fx->mRGBParm.GetVal(), + fx->mLife.GetVal(), fx->mMediaHandles.GetHandle(), fx->mFlags ); + break; + + default: + break; + } + + // Track when we need to clean ourselves up if we are a copy + if ( fx->mCopy ) + { + fx->mRefCount--; + + if ( fx->mRefCount <= 0 ) + { + delete fx; + } + } +} \ No newline at end of file diff --git a/code/cgame/FxScheduler.h b/code/cgame/FxScheduler.h new file mode 100644 index 0000000..585abca --- /dev/null +++ b/code/cgame/FxScheduler.h @@ -0,0 +1,497 @@ + +#if !defined(FX_UTIL_H_INC) + #include "FxUtil.h" +#endif + + +#include "../qcommon/sstring.h" +typedef sstring_t fxString_t; + +#if !defined(FX_PARSING_H_INC) + #include "FxParsing.h" +#endif + +#ifndef FX_SCHEDULER_H_INC +#define FX_SCHEDULER_H_INC + +using namespace std; + + +#define FX_FILE_PATH "effects" + +#define FX_MAX_TRACE_DIST WORLD_SIZE +#define FX_MAX_EFFECTS 150 // how many effects the system can store +#define FX_MAX_EFFECT_COMPONENTS 24 // how many primitives an effect can hold, this should be plenty +#define FX_MAX_PRIM_NAME 32 + +//----------------------------------------------- +// These are spawn flags for primitiveTemplates +//----------------------------------------------- + +#define FX_ORG_ON_SPHERE 0x00001 // Pretty dang expensive, calculates a point on a sphere/ellipsoid +#define FX_AXIS_FROM_SPHERE 0x00002 // Can be used in conjunction with org_on_sphere to cause particles to move out + // from the center of the sphere +#define FX_ORG_ON_CYLINDER 0x00004 // calculate point on cylinder/disk + +#define FX_ORG2_FROM_TRACE 0x00010 +#define FX_TRACE_IMPACT_FX 0x00020 // if trace impacts, we should play one of the specified impact fx files +#define FX_ORG2_IS_OFFSET 0x00040 // template specified org2 should be the offset from a trace endpos or + // passed in org2. You might use this to lend a random flair to the endpos. + // Note: this is done pre-trace, so you may have to specify large numbers for this + +#define FX_CHEAP_ORG_CALC 0x00100 // Origin is calculated relative to passed in axis unless this is on. +#define FX_CHEAP_ORG2_CALC 0x00200 // Origin2 is calculated relative to passed in axis unless this is on. +#define FX_VEL_IS_ABSOLUTE 0x00400 // Velocity isn't relative to passed in axis with this flag on. +#define FX_ACCEL_IS_ABSOLUTE 0x00800 // Acceleration isn't relative to passed in axis with this flag on. + +#define FX_RAND_ROT_AROUND_FWD 0x01000 // Randomly rotates up and right around forward vector +#define FX_EVEN_DISTRIBUTION 0x02000 // When you have a delay, it normally picks a random time to play. When + // this flag is on, it generates an even time distribution +#define FX_RGB_COMPONENT_INTERP 0x04000 // Picks a color on the line defined by RGB min & max, default is to pick color in cube defined by min & max + +#define FX_AFFECTED_BY_WIND 0x10000 // we take into account our wind vector when we spawn in + +#define FX_SND_LESS_ATTENUATION 0x20000 // attenuate sounds less + +//----------------------------------------------------------------- +// +// CMediaHandles +// +// Primitive templates might want to use a list of sounds, shaders +// or models to get a bit more variation in their effects. +// +//----------------------------------------------------------------- +class CMediaHandles +{ +private: + + vector mMediaList; + +public: + + void AddHandle( int item ) { mMediaList.push_back( item ); } + int GetHandle() { if (mMediaList.size()==0) {return 0;} + else {return mMediaList[irand(0,mMediaList.size()-1)];} } + + void operator=(const CMediaHandles &that ); +}; + + +//----------------------------------------------------------------- +// +// CFxRange +// +// Primitive templates typically use this class to define each of +// its members. This is done to make it easier to create effects +// with a desired range of characteristics. +// +//----------------------------------------------------------------- +class CFxRange +{ +private: + + float mMin; + float mMax; + +public: + + CFxRange() {mMin=0; mMax=0;} + + inline void SetRange(float min,float max) {mMin=min; mMax=max;} + inline void SetMin(float min) {mMin=min;} + inline void SetMax(float max) {mMax=max;} + + inline float GetMax() const {return mMax;} + inline float GetMin() const {return mMin;} + + inline float GetVal(float percent) const {if(mMin == mMax){return mMin;} + return (mMin + (mMax - mMin) * percent);} + inline float GetVal() const {if(mMin == mMax){return mMin;} + return flrand(mMin, mMax);} + inline int GetRoundedVal() const {if(mMin == mMax){return mMin;} + return (int)(flrand(mMin, mMax) + 0.5f);} + + inline void ForceRange(float min,float max) {if(mMin < min){mMin=min;} if(mMin > max){mMin=max;} + if(mMax < min){mMax=min;} if(mMax > max){mMax=max;}} + inline void Sort() {if(mMin > mMax){float temp = mMin; mMin=mMax;mMax=temp;}} + void operator=(const CFxRange &that) {mMin=that.mMin; mMax=that.mMax;} + + bool operator==(const CFxRange &rhs) const { return ((mMin == rhs.mMin) && + (mMax == rhs.mMax)); } +}; + + +//---------------------------- +// Supported primitive types +//---------------------------- + +enum EPrimType +{ + None = 0, + Particle, // sprite + Line, + Tail, // comet-like tail thing + Cylinder, + Emitter, // emits effects as it moves, can also attach a chunk + Sound, +#ifdef _IMMERSION + Force, +#endif // _IMMERSION + Decal, // projected onto architecture + OrientedParticle, + Electricity, + FxRunner, + Light, + CameraShake, + ScreenFlash +}; + + +//----------------------------------------------------------------- +// +// CPrimitiveTemplate +// +// The primitive template is used to spawn 1 or more fx primitives +// with the range of characteristics defined by the template. +// +// As such, I just made this one huge shared class knowing that +// there won't be many of them in memory at once, and we won't +// be dynamically creating and deleting them mid-game. Also, +// note that not every primitive type will use all of these fields. +// +//----------------------------------------------------------------- +class CPrimitiveTemplate +{ + +public: + + // These kinds of things should not even be allowed to be accessed publicly + bool mCopy; + int mRefCount; // For a copy of a primitive...when we figure out how many items we want to spawn, + // we'll store that here and then decrement us for each we actually spawn. When we + // hit zero, we are no longer used and so we can just free ourselves + + char mName[FX_MAX_PRIM_NAME]; + + EPrimType mType; + + CFxRange mSpawnDelay; + CFxRange mSpawnCount; + CFxRange mLife; + int mCullRange; + + CMediaHandles mMediaHandles; + CMediaHandles mImpactFxHandles; + CMediaHandles mDeathFxHandles; + CMediaHandles mEmitterFxHandles; + CMediaHandles mPlayFxHandles; + + int mFlags; // These need to get passed on to the primitive + int mSpawnFlags; // These are only used to control spawning, but never get passed to prims. + + vec3_t mMin; + vec3_t mMax; + + CFxRange mOrigin1X; + CFxRange mOrigin1Y; + CFxRange mOrigin1Z; + + CFxRange mOrigin2X; + CFxRange mOrigin2Y; + CFxRange mOrigin2Z; + + CFxRange mRadius; // spawn on sphere/ellipse/disk stuff. + CFxRange mHeight; + + CFxRange mRotation; + CFxRange mRotationDelta; + + CFxRange mAngle1; + CFxRange mAngle2; + CFxRange mAngle3; + + CFxRange mAngle1Delta; + CFxRange mAngle2Delta; + CFxRange mAngle3Delta; + + CFxRange mVelX; + CFxRange mVelY; + CFxRange mVelZ; + + CFxRange mAccelX; + CFxRange mAccelY; + CFxRange mAccelZ; + + CFxRange mGravity; + + CFxRange mDensity; + CFxRange mVariance; + + CFxRange mRedStart; + CFxRange mGreenStart; + CFxRange mBlueStart; + + CFxRange mRedEnd; + CFxRange mGreenEnd; + CFxRange mBlueEnd; + + CFxRange mRGBParm; + + CFxRange mAlphaStart; + CFxRange mAlphaEnd; + CFxRange mAlphaParm; + + CFxRange mSizeStart; + CFxRange mSizeEnd; + CFxRange mSizeParm; + + CFxRange mSize2Start; + CFxRange mSize2End; + CFxRange mSize2Parm; + + CFxRange mLengthStart; + CFxRange mLengthEnd; + CFxRange mLengthParm; + + CFxRange mTexCoordS; + CFxRange mTexCoordT; + + CFxRange mElasticity; + + + // Lower level parsing utilities + bool ParseVector( const char *val, vec3_t min, vec3_t max ); + bool ParseFloat( const char *val, float *min, float *max ); + bool ParseGroupFlags( const char *val, int *flags ); + + // Base key processing + // Note that these all have their own parse functions in case it becomes important to do certain kinds + // of validation specific to that type. + bool ParseMin( const char *val ); + bool ParseMax( const char *val ); + bool ParseDelay( const char *val ); + bool ParseCount( const char *val ); + bool ParseLife( const char *val ); + bool ParseElasticity( const char *val ); + bool ParseFlags( const char *val ); + bool ParseSpawnFlags( const char *val ); + + bool ParseOrigin1( const char *val ); + bool ParseOrigin2( const char *val ); + bool ParseRadius( const char *val ); + bool ParseHeight( const char *val ); + bool ParseRotation( const char *val ); + bool ParseRotationDelta( const char *val ); + bool ParseAngle( const char *val ); + bool ParseAngleDelta( const char *val ); + bool ParseVelocity( const char *val ); + bool ParseAcceleration( const char *val ); + bool ParseGravity( const char *val ); + bool ParseDensity( const char *val ); + bool ParseVariance( const char *val ); + + // Group type processing + bool ParseRGB( CGPGroup *grp ); + bool ParseAlpha( CGPGroup *grp ); + bool ParseSize( CGPGroup *grp ); + bool ParseSize2( CGPGroup *grp ); + bool ParseLength( CGPGroup *grp ); + + bool ParseModels( CGPValue *grp ); + bool ParseShaders( CGPValue *grp ); + bool ParseSounds( CGPValue *grp ); +#ifdef _IMMERSION + bool ParseForces( CGPValue *grp ); +#endif // _IMMERSION + + bool ParseImpactFxStrings( CGPValue *grp ); + bool ParseDeathFxStrings( CGPValue *grp ); + bool ParseEmitterFxStrings( CGPValue *grp ); + bool ParsePlayFxStrings( CGPValue *grp ); + + // Group keys + bool ParseRGBStart( const char *val ); + bool ParseRGBEnd( const char *val ); + bool ParseRGBParm( const char *val ); + bool ParseRGBFlags( const char *val ); + + bool ParseAlphaStart( const char *val ); + bool ParseAlphaEnd( const char *val ); + bool ParseAlphaParm( const char *val ); + bool ParseAlphaFlags( const char *val ); + + bool ParseSizeStart( const char *val ); + bool ParseSizeEnd( const char *val ); + bool ParseSizeParm( const char *val ); + bool ParseSizeFlags( const char *val ); + + bool ParseSize2Start( const char *val ); + bool ParseSize2End( const char *val ); + bool ParseSize2Parm( const char *val ); + bool ParseSize2Flags( const char *val ); + + bool ParseLengthStart( const char *val ); + bool ParseLengthEnd( const char *val ); + bool ParseLengthParm( const char *val ); + bool ParseLengthFlags( const char *val ); + + +public: + + CPrimitiveTemplate(); + ~CPrimitiveTemplate() {}; + + bool ParsePrimitive( CGPGroup *grp ); + + void operator=(const CPrimitiveTemplate &that); +}; + +// forward declaration +struct SEffectTemplate; + +// Effects are built of one or more primitives +struct SEffectTemplate +{ + bool mInUse; + bool mCopy; + char mEffectName[MAX_QPATH]; // is this extraneous?? + int mPrimitiveCount; + int mRepeatDelay; + CPrimitiveTemplate *mPrimitives[FX_MAX_EFFECT_COMPONENTS]; + + bool operator == (const char * name) const + { + return !stricmp( mEffectName, name ); + } + void operator=(const SEffectTemplate &that); +}; + + + +//----------------------------------------------------------------- +// +// CFxScheduler +// +// The scheduler not only handles requests to play an effect, it +// tracks the request throughout its life if necessary, creating +// any of the delayed components as needed. +// +//----------------------------------------------------------------- +// needs to be in global space now (loadsave stuff) + +#define MAX_LOOPED_FX 32 +// We hold a looped effect here +struct SLoopedEffect +{ + int mId; // effect id + int mBoltInfo; // used to determine which bolt on the ghoul2 model we should be attaching this effect to + int mNextTime; //time to render again + int mLoopStopTime; //time to die + bool mPortalEffect; // rww - render this before skyportals, and not in the normal world view. + bool mIsRelative; // bolt this puppy on keep it updated +}; + +class CFxScheduler +{ +private: + + // We hold a scheduled effect here + struct SScheduledEffect + { + CPrimitiveTemplate *mpTemplate; // primitive template + int mStartTime; + char mModelNum; // uset to determine which ghoul2 model we want to bolt this effect to + char mBoltNum; // used to determine which bolt on the ghoul2 model we should be attaching this effect to + short mEntNum; // used to determine which entity this ghoul model is attached to. + short mClientID; // FIXME: redundant. this is used for muzzle bolts, merge into normal bolting + bool mPortalEffect; // rww - render this before skyportals, and not in the normal world view. + bool mIsRelative; // bolt this puppy on keep it updated + vec3_t mOrigin; + vec3_t mAxis[3]; + + bool operator <= (const int time) const + { + return mStartTime <= time; + } + }; + +/* Looped Effects get stored and reschedule at mRepeatRate */ + + // must be in sync with gLoopedEffectArray[MAX_LOOPED_FX]! + // + SLoopedEffect mLoopedEffectArray[MAX_LOOPED_FX]; + + int ScheduleLoopedEffect( int id, int boltInfo, bool isPortal, int iLoopTime, bool isRelative ); + void AddLoopedEffects( ); + + + // this makes looking up the index based on the string name much easier + typedef map TEffectID; + + typedef list TScheduledEffect; + + // Effects + SEffectTemplate mEffectTemplates[FX_MAX_EFFECTS]; + TEffectID mEffectIDs; // if you only have the unique effect name, you'll have to use this to get the ID. + + // List of scheduled effects that will need to be created at the correct time. + TScheduledEffect mFxSchedule; + + + // Private function prototypes + SEffectTemplate *GetNewEffectTemplate( int *id, const char *file ); + + void AddPrimitiveToEffect( SEffectTemplate *fx, CPrimitiveTemplate *prim ); + int ParseEffect( const char *file, CGPGroup *base ); + + void CreateEffect( CPrimitiveTemplate *fx, const vec3_t origin, vec3_t axis[3], int lateTime, int clientID = -1, int modelNum = -1, int boltNum = -1 ); + void CreateEffect( CPrimitiveTemplate *fx, int clientID, int lateTime ); + +public: + + CFxScheduler(); + + void LoadSave_Read(); + void LoadSave_Write(); + void FX_CopeWithAnyLoadedSaveGames(); + + int RegisterEffect( const char *file, bool bHasCorrectPath = false ); // handles pre-caching + + + // Nasty overloaded madness + void PlayEffect( int id, vec3_t org, bool isPortal = false ); // uses a default up axis + void PlayEffect( int id, vec3_t org, vec3_t fwd, bool isPortal = false ); // builds arbitrary perp. right vector, does a cross product to define up + void PlayEffect( int id, vec3_t origin, vec3_t axis[3], const int boltInfo=-1, const int entNum=-1, bool isPortal = false, int iLoopTime = false, bool isRelative = false ); + void PlayEffect( const char *file, vec3_t org, bool isPortal = false ); // uses a default up axis + void PlayEffect( const char *file, vec3_t org, vec3_t fwd, bool isPortal = false ); // builds arbitrary perp. right vector, does a cross product to define up + void PlayEffect( const char *file, vec3_t origin, vec3_t axis[3], const int boltInfo, const int entNum, bool isPortal = false, int iLoopTime = false, bool isRelative = false ); + + //for muzzle + void PlayEffect( const char *file, int clientID, bool isPortal = false ); + +#ifdef _IMMERSION // for ff-system + void PlayEffect( int id, int clientNum, vec3_t org, vec3_t fwd, bool isPortal = false ); + void PlayEffect( const char *file, int clientNum, vec3_t origin, vec3_t forward, bool isPortal = false ); +#endif // _IMMERSION + + void StopEffect( const char *file, const int boltInfo, bool isPortal = false ); //find a scheduled Looping effect with these parms and kill it + + void AddScheduledEffects( bool portal ); // call once per CGame frame [rww ammendment - twice now actually, but first only renders portal effects] + + int NumScheduledFx() { return mFxSchedule.size(); } + void Clean(bool bRemoveTemplates = true, int idToPreserve = 0); // clean out the system + + // FX Override functions + SEffectTemplate *GetEffectCopy( int fxHandle, int *newHandle ); + SEffectTemplate *GetEffectCopy( const char *file, int *newHandle ); + + CPrimitiveTemplate *GetPrimitiveCopy( SEffectTemplate *effectCopy, const char *componentName ); +}; + +//------------------- +// The one and only +//------------------- +extern CFxScheduler theFxScheduler; + + +#endif // FX_SCHEDULER_H_INC diff --git a/code/cgame/FxSystem.cpp b/code/cgame/FxSystem.cpp new file mode 100644 index 0000000..ba009ba --- /dev/null +++ b/code/cgame/FxSystem.cpp @@ -0,0 +1,215 @@ +// this include must remain at the top of every FXxxxx.CPP file +#include "common_headers.h" + +#if !defined(FX_SCHEDULER_H_INC) + #include "FxScheduler.h" +#endif + +#include "cg_media.h" //for cgs.model_draw for G2 + +extern vmCvar_t fx_debug; +extern vmCvar_t fx_freeze; + +extern void CG_ExplosionEffects( vec3_t origin, float intensity, int radius, int time ); + +// Stuff for the FxHelper +//------------------------------------------------------ +void SFxHelper::Init() +{ + mTime = 0; +} + +//------------------------------------------------------ +void SFxHelper::Print( const char *msg, ... ) +{ +#ifndef FINAL_BUILD + + va_list argptr; + char text[1024]; + + va_start( argptr, msg ); + vsprintf( text, msg, argptr ); + va_end( argptr ); + + gi.Printf( text ); + +#endif +} + +//------------------------------------------------------ +void SFxHelper::AdjustTime( int frameTime ) +{ + if ( fx_freeze.integer || ( frameTime <= 0 )) + { + // Allow no time progression when we are paused. + mFrameTime = 0; + mFloatFrameTime = 0.0f; + } + else + { + if ( !cg_paused.integer ) + { + if ( frameTime > 300 ) // hack for returning from paused and time bursts + { + frameTime = 300; + } + + mFrameTime = frameTime; + mFloatFrameTime = mFrameTime * 0.001f; + mTime += mFrameTime; + } + } +} + +//------------------------------------------------------ +int SFxHelper::OpenFile( const char *file, fileHandle_t *fh, int mode ) +{ +// char path[256]; + +// sprintf( path, "%s/%s", FX_FILE_PATH, file ); + return cgi_FS_FOpenFile( file, fh, FS_READ ); +} + +//------------------------------------------------------ +int SFxHelper::ReadFile( void *data, int len, fileHandle_t fh ) +{ + return cgi_FS_Read( data, len, fh ); +} + +//------------------------------------------------------ +void SFxHelper::CloseFile( fileHandle_t fh ) +{ + cgi_FS_FCloseFile( fh ); +} + +//------------------------------------------------------ +void SFxHelper::PlaySound( const vec3_t org, int entityNum, int entchannel, int sfxHandle ) +{ + cgi_S_StartSound( org, entityNum, entchannel, sfxHandle ); +} + +//------------------------------------------------------ +void SFxHelper::PlayLocalSound( int sfxHandle, int channelNum ) +{ + cgi_S_StartLocalSound(sfxHandle, channelNum); +} + +//------------------------------------------------------ +void SFxHelper::Trace( trace_t *tr, vec3_t start, vec3_t min, vec3_t max, + vec3_t end, int skipEntNum, int flags ) +{ + CG_Trace( tr, start, min, max, end, skipEntNum, flags ); +} + +void SFxHelper::G2Trace( trace_t *tr, vec3_t start, vec3_t min, vec3_t max, + vec3_t end, int skipEntNum, int flags ) +{ + //CG_Trace( tr, start, min, max, end, skipEntNum, flags, G2_COLLIDE ); + gi.trace(tr, start, NULL, NULL, end, skipEntNum, flags, G2_COLLIDE); +} + +//------------------------------------------------------ +void SFxHelper::AddFxToScene( refEntity_t *ent ) +{ + cgi_R_AddRefEntityToScene( ent ); +} + +//------------------------------------------------------ +int SFxHelper::RegisterShader( const char *shader ) +{ + return cgi_R_RegisterShader( shader ); +} + +//------------------------------------------------------ +int SFxHelper::RegisterSound( const char *sound ) +{ + return cgi_S_RegisterSound( sound ); +} + +//------------------------------------------------------ +int SFxHelper::RegisterModel( const char *model ) +{ + return cgi_R_RegisterModel( model ); +} + +//------------------------------------------------------ +void SFxHelper::AddLightToScene( vec3_t org, float radius, float red, float green, float blue ) +{ + cgi_R_AddLightToScene( org, radius, red, green, blue ); +} + +//------------------------------------------------------ +void SFxHelper::AddPolyToScene( int shader, int count, polyVert_t *verts ) +{ + cgi_R_AddPolyToScene( shader, count, verts ); +} + +//------------------------------------------------------ +void SFxHelper::CameraShake( vec3_t origin, float intensity, int radius, int time ) +{ + CG_ExplosionEffects( origin, intensity, radius, time ); +} + +//------------------------------------------------------ +int SFxHelper::GetOriginAxisFromBolt(const centity_t ¢, int modelNum, int boltNum, vec3_t /*out*/origin, vec3_t /*out*/axis[3]) +{ + if ((cg.time-cent.snapShotTime) > 200) + { //you were added more than 200ms ago, so I say you are no longer valid/in our snapshot. + return 0; + } + + int doesBoltExist; + mdxaBone_t boltMatrix; + vec3_t G2Angles = {cent.lerpAngles[0] , cent.lerpAngles[1], cent.lerpAngles[2]}; + if ( cent.currentState.eType == ET_PLAYER ) + {//players use cent.renderAngles + VectorCopy( cent.renderAngles, G2Angles ); + + if ( cent.gent //has a game entity + && cent.gent->s.m_iVehicleNum != 0 //in a vehicle + && cent.gent->m_pVehicle //have a valid vehicle pointer + && cent.gent->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER //it's not a fighter + && cent.gent->m_pVehicle->m_pVehicleInfo->type != VH_SPEEDER //not a speeder + ) + { + G2Angles[PITCH]=0; + G2Angles[ROLL] =0; + } + } + + // go away and get me the bolt position for this frame please + doesBoltExist = gi.G2API_GetBoltMatrix(cent.gent->ghoul2, modelNum, + boltNum, &boltMatrix, G2Angles, + cent.lerpOrigin, cg.time, cgs.model_draw, + cent.currentState.modelScale); + // set up the axis and origin we need for the actual effect spawning + origin[0] = boltMatrix.matrix[0][3]; + origin[1] = boltMatrix.matrix[1][3]; + origin[2] = boltMatrix.matrix[2][3]; + + axis[1][0] = boltMatrix.matrix[0][0]; + axis[1][1] = boltMatrix.matrix[1][0]; + axis[1][2] = boltMatrix.matrix[2][0]; + + axis[0][0] = boltMatrix.matrix[0][1]; + axis[0][1] = boltMatrix.matrix[1][1]; + axis[0][2] = boltMatrix.matrix[2][1]; + + axis[2][0] = boltMatrix.matrix[0][2]; + axis[2][1] = boltMatrix.matrix[1][2]; + axis[2][2] = boltMatrix.matrix[2][2]; + return doesBoltExist; +} +#ifdef _IMMERSION +//------------------------------------------------------ +ffHandle_t SFxHelper::RegisterForce( const char *force, int channel ) +{ + return cgi_FF_Register( force, channel ); +} + +//------------------------------------------------------ +void SFxHelper::PlayForce( int entityNum, ffHandle_t ff ) +{ + cgi_FF_Start( ff, entityNum ); +} +#endif // _IMMERSION \ No newline at end of file diff --git a/code/cgame/FxSystem.h b/code/cgame/FxSystem.h new file mode 100644 index 0000000..affae0e --- /dev/null +++ b/code/cgame/FxSystem.h @@ -0,0 +1,84 @@ + +#if !defined(CG_LOCAL_H_INC) + #include "cg_local.h" +#endif + +#ifndef FX_SYSTEM_H_INC +#define FX_SYSTEM_H_INC + + +#define irand Q_irand +#define flrand Q_flrand + +extern vmCvar_t fx_debug; +extern vmCvar_t fx_freeze; + +inline void Vector2Clear(vec2_t a) +{ + a[0] = 0.0f; + a[1] = 0.0f; +} + +inline void Vector2Set(vec2_t a,float b,float c) +{ + a[0] = b; + a[1] = c; +} + +inline void Vector2Copy(vec2_t src,vec2_t dst) +{ + dst[0] = src[0]; + dst[1] = src[1]; +} + + +extern void CG_CalcEntityLerpPositions( centity_t * ); + + +struct SFxHelper +{ + int mTime; + int mFrameTime; + float mFloatFrameTime; + + void Init(); + void AdjustTime( int time ); + + // These functions are wrapped and used by the fx system in case it makes things a bit more portable + void Print( const char *msg, ... ); + + // File handling + int OpenFile( const char *path, fileHandle_t *fh, int mode ); + int ReadFile( void *data, int len, fileHandle_t fh ); + void CloseFile( fileHandle_t fh ); + + // Sound + void PlaySound( const vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); + void PlayLocalSound( sfxHandle_t sfx, int channelNum ); + int RegisterSound( const char *sound ); + +#ifdef _IMMERSION + void PlayForce( int entityNum, ffHandle_t ff ); + ffHandle_t RegisterForce( const char *force, int channel ); +#endif // _IMMERSION + //G2 + int GetOriginAxisFromBolt(const centity_t ¢, int modelNum, int boltNum, vec3_t /*out*/origin, vec3_t /*out*/*axis); + + // Physics/collision + void Trace( trace_t *tr, vec3_t start, vec3_t min, vec3_t max, vec3_t end, int skipEntNum, int flags ); + void G2Trace( trace_t *tr, vec3_t start, vec3_t min, vec3_t max, vec3_t end, int skipEntNum, int flags ); + + void AddFxToScene( refEntity_t *ent ); + void AddLightToScene( vec3_t org, float radius, float red, float green, float blue ); + + int RegisterShader( const char *shader ); + int RegisterModel( const char *model ); + + void AddPolyToScene( int shader, int count, polyVert_t *verts ); + + void CameraShake( vec3_t origin, float intensity, int radius, int time ); +}; + +extern SFxHelper theFxHelper; + +#endif // FX_SYSTEM_H_INC diff --git a/code/cgame/FxTemplate.cpp b/code/cgame/FxTemplate.cpp new file mode 100644 index 0000000..59dbabc --- /dev/null +++ b/code/cgame/FxTemplate.cpp @@ -0,0 +1,2370 @@ + +// this include must remain at the top of every CPP file +#include "common_headers.h" + +#if !defined(FX_SCHEDULER_H_INC) + #include "FxScheduler.h" +#endif + +//------------------------------------------------------ +// CPrimitiveTemplate +// Set up our minimal default values +// +// Input: +// none +// +// Return: +// none +//------------------------------------------------------ +CPrimitiveTemplate::CPrimitiveTemplate() +{ + // We never start out as a copy or with a name + mCopy = false; + mName[0] = 0; + mCullRange = 0; + + mFlags = mSpawnFlags = 0; + + mLife.SetRange( 50.0f, 50.0f ); + mSpawnCount.SetRange( 1.0f, 1.0f ); + mRadius.SetRange( 10.0f, 10.0f ); + mHeight.SetRange( 10.0f, 10.0f ); + + VectorSet( mMin, 0.0f, 0.0f, 0.0f ); + VectorSet( mMax, 0.0f, 0.0f, 0.0f ); + + mRedStart.SetRange( 1.0f, 1.0f ); + mGreenStart.SetRange( 1.0f, 1.0f ); + mBlueStart.SetRange( 1.0f, 1.0f ); + + mRedEnd.SetRange( 1.0f, 1.0f ); + mGreenEnd.SetRange( 1.0f, 1.0f ); + mBlueEnd.SetRange( 1.0f, 1.0f ); + + mAlphaStart.SetRange( 1.0f, 1.0f ); + mAlphaEnd.SetRange( 1.0f, 1.0f ); + + mSizeStart.SetRange( 1.0f, 1.0f ); + mSizeEnd.SetRange( 1.0f, 1.0f ); + + mSize2Start.SetRange( 1.0f, 1.0f ); + mSize2End.SetRange( 1.0f, 1.0f ); + + mLengthStart.SetRange( 1.0f, 1.0f ); + mLengthEnd.SetRange( 1.0f, 1.0f ); + + mTexCoordS.SetRange( 1.0f, 1.0f ); + mTexCoordT.SetRange( 1.0f, 1.0f ); + + mVariance.SetRange( 1.0f, 1.0f ); + mDensity.SetRange( 10.0f, 10.0f );// default this high so it doesn't do bad things +} + +//----------------------------------------------------------- +void CPrimitiveTemplate::operator=(const CPrimitiveTemplate &that) +{ + // I'm assuming that doing a memcpy wouldn't work here + // If you are looking at this and know a better way to do this, please tell me. + strcpy( mName, that.mName ); + + mType = that.mType; + + mSpawnDelay = that.mSpawnDelay; + mSpawnCount = that.mSpawnCount; + mLife = that.mLife; + mCullRange = that.mCullRange; + + mMediaHandles = that.mMediaHandles; + mImpactFxHandles = that.mImpactFxHandles; + mDeathFxHandles = that.mDeathFxHandles; + mEmitterFxHandles = that.mEmitterFxHandles; + mPlayFxHandles = that.mPlayFxHandles; + + mFlags = that.mFlags; + mSpawnFlags = that.mSpawnFlags; + + VectorCopy( that.mMin, mMin ); + VectorCopy( that.mMax, mMax ); + + mOrigin1X = that.mOrigin1X; + mOrigin1Y = that.mOrigin1Y; + mOrigin1Z = that.mOrigin1Z; + + mOrigin2X = that.mOrigin2X; + mOrigin2Y = that.mOrigin2Y; + mOrigin2Z = that.mOrigin2Z; + + mRadius = that.mRadius; + mHeight = that.mHeight; + + mRotation = that.mRotation; + mRotationDelta = that.mRotationDelta; + + mAngle1 = that.mAngle1; + mAngle2 = that.mAngle2; + mAngle3 = that.mAngle3; + + mAngle1Delta = that.mAngle1Delta; + mAngle2Delta = that.mAngle2Delta; + mAngle3Delta = that.mAngle3Delta; + + mVelX = that.mVelX; + mVelY = that.mVelY; + mVelZ = that.mVelZ; + + mAccelX = that.mAccelX; + mAccelY = that.mAccelY; + mAccelZ = that.mAccelZ; + + mGravity = that.mGravity; + + mDensity = that.mDensity; + mVariance = that.mVariance; + + mRedStart = that.mRedStart; + mGreenStart = that.mGreenStart; + mBlueStart = that.mBlueStart; + + mRedEnd = that.mRedEnd; + mGreenEnd = that.mGreenEnd; + mBlueEnd = that.mBlueEnd; + + mRGBParm = that.mRGBParm; + + mAlphaStart = that.mAlphaStart; + mAlphaEnd = that.mAlphaEnd; + mAlphaParm = that.mAlphaParm; + + mSizeStart = that.mSizeStart; + mSizeEnd = that.mSizeEnd; + mSizeParm = that.mSizeParm; + + mSize2Start = that.mSize2Start; + mSize2End = that.mSize2End; + mSize2Parm = that.mSize2Parm; + + mLengthStart = that.mLengthStart; + mLengthEnd = that.mLengthEnd; + mLengthParm = that.mLengthParm; + + mTexCoordS = that.mTexCoordS; + mTexCoordT = that.mTexCoordT; + + mElasticity = that.mElasticity; +} + +//------------------------------------------------------ +// ParseFloat +// Removes up to two values from a passed in string and +// sets these values into the passed in min and max +// fields. if no max is present, min is copied into it. +// +// input: +// string that contains up to two float values +// min & max are used to return the parse values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseFloat( const char *val, float *min, float *max ) +{ + // We don't allow passing in a null for either of the fields + if ( min == 0 || max == 0 ) + { // failue + return false; + } + + // attempt to read out the values + int v = sscanf( val, "%f %f", min, max ); + + if ( v == 0 ) + { // nothing was there, failure + return false; + } + else if ( v == 1 ) + { // only one field entered, this is ok, but we should copy min into max + *max = *min; + } + + return true; +} + + +//------------------------------------------------------ +// ParseVector +// Removes up to six values from a passed in string and +// sets these values into the passed in min and max vector +// fields. if no max is present, min is copied into it. +// +// input: +// string that contains up to six float values +// min & max are used to return the parse values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseVector( const char *val, vec3_t min, vec3_t max ) +{ + // we don't allow passing in a null + if ( min == 0 || max == 0 ) + { + return false; + } + + // attempt to read out our values + int v = sscanf( val, "%f %f %f %f %f %f", &min[0], &min[1], &min[2], &max[0], &max[1], &max[2] ); + + // Check for completeness + if ( v < 3 || v == 4 || v == 5 ) + { // not a complete value + return false; + } + else if ( v == 3 ) + { // only a min was entered, so copy the result into max + VectorCopy( min, max ); + } + + return true; +} + +//------------------------------------------------------ +// ParseGroupFlags +// Group flags are generic in nature, so we can easily +// use a generic function to parse them in, then the +// caller can shift them into the appropriate range. +// +// input: +// string that contains the flag strings +// *flags returns the set bit flags +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseGroupFlags( const char *val, int *flags ) +{ + // Must pass in a non-null pointer + if ( flags == 0 ) + { + return false; + } + + char flag[][32] = {"\0","\0","\0","0"}; + bool ok = true; + + // For a sub group, really you probably only have one or two flags set + int v = sscanf( val, "%s %s %s %s", flag[0], flag[1], flag[2], flag[3] ); + + // Clear out the flags field, then convert the flag string to an actual value ( use generic flags ) + *flags = 0; + + for ( int i = 0; i < 4; i++ ) + { + if ( i + 1 > v ) + { + return true; + } + + if ( !stricmp( flag[i], "linear" )) + { + *flags |= FX_LINEAR; + } + else if ( !stricmp( flag[i], "nonlinear" )) + { + *flags |= FX_NONLINEAR; + } + else if ( !stricmp( flag[i], "wave" )) + { + *flags |= FX_WAVE; + } + else if ( !stricmp( flag[i], "random" )) + { + *flags |= FX_RAND; + } + else if ( !stricmp( flag[i], "clamp" )) + { + *flags |= FX_CLAMP; + } + else + { // we have badness going on, but continue on in case there are any valid fields in here + ok = false; + } + } + + return ok; +} + +//------------------------------------------------------ +// ParseMin +// Reads in a min bounding box field in vector format +// +// input: +// string that contains three float values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseMin( const char *val ) +{ + vec3_t min; + + if ( ParseVector( val, min, min ) == true ) + { + VectorCopy( min, mMin ); + + // We assume that if a min is being set that we are using physics and a bounding box + mFlags |= (FX_USE_BBOX | FX_APPLY_PHYSICS); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseMax +// Reads in a max bounding box field in vector format +// +// input: +// string that contains three float values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseMax( const char *val ) +{ + vec3_t max; + + if ( ParseVector( val, max, max ) == true ) + { + VectorCopy( max, mMax ); + + // We assume that if a max is being set that we are using physics and a bounding box + mFlags |= (FX_USE_BBOX | FX_APPLY_PHYSICS); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseLife +// Reads in a ranged life value +// +// input: +// string that contains a float range ( two vals ) +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLife( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mLife.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseDelay +// Reads in a ranged delay value +// +// input: +// string that contains a float range ( two vals ) +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseDelay( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSpawnDelay.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseCount +// Reads in a ranged count value +// +// input: +// string that contains a float range ( two vals ) +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseCount( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSpawnCount.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseElasticity +// Reads in a ranged elasticity value +// +// input: +// string that contains a float range ( two vals ) +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseElasticity( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mElasticity.SetRange( min, max ); + + // We assume that if elasticity is set that we are using physics, but don't assume we are + // using a bounding box unless a min/max are explicitly set +// mFlags |= FX_APPLY_PHYSICS; + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseOrigin1 +// Reads in an origin field in vector format +// +// input: +// string that contains three float values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseOrigin1( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mOrigin1X.SetRange( min[0], max[0] ); + mOrigin1Y.SetRange( min[1], max[1] ); + mOrigin1Z.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseOrigin2 +// Reads in an origin field in vector format +// +// input: +// string that contains three float values +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseOrigin2( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mOrigin2X.SetRange( min[0], max[0] ); + mOrigin2Y.SetRange( min[1], max[1] ); + mOrigin2Z.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRadius +// Reads in a ranged radius value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRadius( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mRadius.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseHeight +// Reads in a ranged height value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseHeight( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mHeight.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRotation +// Reads in a ranged rotation value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRotation( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == qtrue ) + { + mRotation.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRotationDelta +// Reads in a ranged rotationDelta value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRotationDelta( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == qtrue ) + { + mRotationDelta.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAngle +// Reads in a ranged angle field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAngle( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mAngle1.SetRange( min[0], max[0] ); + mAngle2.SetRange( min[1], max[1] ); + mAngle3.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAngleDelta +// Reads in a ranged angleDelta field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAngleDelta( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mAngle1Delta.SetRange( min[0], max[0] ); + mAngle2Delta.SetRange( min[1], max[1] ); + mAngle3Delta.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseVelocity +// Reads in a ranged velocity field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseVelocity( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mVelX.SetRange( min[0], max[0] ); + mVelY.SetRange( min[1], max[1] ); + mVelZ.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseFlags +// These are flags that are not specific to a group, +// rather, they are specific to the whole primitive. +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseFlags( const char *val ) +{ + char flag[][32] = {"\0","\0","\0","\0","\0","\0","\0"}; + bool ok = true; + + // For a primitive, really you probably only have two or less flags set + int v = sscanf( val, "%s %s %s %s %s %s %s", flag[0], flag[1], flag[2], flag[3], flag[4], flag[5], flag[6] ); + + for ( int i = 0; i < 7; i++ ) + { + if ( i + 1 > v ) + { + return true; + } + + if ( !stricmp( flag[i], "useModel" )) + { + mFlags |= FX_ATTACHED_MODEL; + } + else if ( !stricmp( flag[i], "useBBox" )) + { + mFlags |= FX_USE_BBOX; + } + else if ( !stricmp( flag[i], "usePhysics" )) + { + mFlags |= FX_APPLY_PHYSICS; + } + else if ( !stricmp( flag[i], "expensivePhysics" )) + { + mFlags |= FX_EXPENSIVE_PHYSICS; + } + //rww - begin g2 stuff + else if ( !stricmp( flag[i], "ghoul2Collision" )) + { + mFlags |= (FX_GHOUL2_TRACE|FX_APPLY_PHYSICS|FX_EXPENSIVE_PHYSICS); + } + else if ( !stricmp( flag[i], "ghoul2Decals" )) + { + mFlags |= FX_GHOUL2_DECALS; + } + //rww - end + else if ( !stricmp( flag[i], "impactKills" )) + { + mFlags |= FX_KILL_ON_IMPACT; + } + else if ( !stricmp( flag[i], "impactFx" )) + { + mFlags |= FX_IMPACT_RUNS_FX; + } + else if ( !stricmp( flag[i], "deathFx" )) + { + mFlags |= FX_DEATH_RUNS_FX; + } + else if ( !stricmp( flag[i], "useAlpha" )) + { + mFlags |= FX_USE_ALPHA; + } + else if ( !stricmp( flag[i], "emitFx" )) + { + mFlags |= FX_EMIT_FX; + } + else if ( !stricmp( flag[i], "depthHack" )) + { + mFlags |= FX_DEPTH_HACK; + } + else if ( !stricmp( flag[i], "setShaderTime" )) + { + mFlags |= FX_SET_SHADER_TIME; + } + else + { // we have badness going on, but continue on in case there are any valid fields in here + ok = false; + } + } + + return ok; +} + +//------------------------------------------------------ +// ParseSpawnFlags +// These kinds of flags control how things spawn. They +// never get passed on to a primitive. +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSpawnFlags( const char *val ) +{ + char flag[][32] = {"\0","\0","\0","\0","\0","\0","\0"}; + bool ok = true; + + // For a primitive, really you probably only have two or less flags set + int v = sscanf( val, "%s %s %s %s %s %s %s", flag[0], flag[1], flag[2], flag[3], flag[4], flag[5], flag[6] ); + + for ( int i = 0; i < 7; i++ ) + { + if ( i + 1 > v ) + { + return true; + } + + if ( !stricmp( flag[i], "org2fromTrace" )) + { + mSpawnFlags |= FX_ORG2_FROM_TRACE; + } + else if ( !stricmp( flag[i], "traceImpactFx" )) + { + mSpawnFlags |= FX_TRACE_IMPACT_FX; + } + else if ( !stricmp( flag[i], "org2isOffset" )) + { + mSpawnFlags |= FX_ORG2_IS_OFFSET; + } + else if ( !stricmp( flag[i], "cheapOrgCalc" )) + { + mSpawnFlags |= FX_CHEAP_ORG_CALC; + } + else if ( !stricmp( flag[i], "cheapOrg2Calc" )) + { + mSpawnFlags |= FX_CHEAP_ORG2_CALC; + } + else if ( !stricmp( flag[i], "absoluteVel" )) + { + mSpawnFlags |= FX_VEL_IS_ABSOLUTE; + } + else if ( !stricmp( flag[i], "absoluteAccel" )) + { + mSpawnFlags |= FX_ACCEL_IS_ABSOLUTE; + } + else if ( !stricmp( flag[i], "orgOnSphere" )) // sphere/ellipsoid + { + mSpawnFlags |= FX_ORG_ON_SPHERE; + } + else if ( !stricmp( flag[i], "orgOnCylinder" )) // cylinder/disk + { + mSpawnFlags |= FX_ORG_ON_CYLINDER; + } + else if ( !stricmp( flag[i], "axisFromSphere" )) + { + mSpawnFlags |= FX_AXIS_FROM_SPHERE; + } + else if ( !stricmp( flag[i], "randrotaroundfwd" )) + { + mSpawnFlags |= FX_RAND_ROT_AROUND_FWD; + } + else if ( !stricmp( flag[i], "evenDistribution" )) + { + mSpawnFlags |= FX_EVEN_DISTRIBUTION; + } + else if ( !stricmp( flag[i], "rgbComponentInterpolation" )) + { + mSpawnFlags |= FX_RGB_COMPONENT_INTERP; + } + else if ( !stricmp( flag[i], "lessAttenuation" )) + { + mSpawnFlags |= FX_SND_LESS_ATTENUATION; + } + else + { // we have badness going on, but continue on in case there are any valid fields in here + ok = false; + } + } + + return ok; +} + +//------------------------------------------------------ +// ParseAcceleration +// Reads in a ranged acceleration field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAcceleration( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mAccelX.SetRange( min[0], max[0] ); + mAccelY.SetRange( min[1], max[1] ); + mAccelZ.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseGravity +// Reads in a ranged gravity value +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseGravity( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mGravity.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseDensity +// Reads in a ranged density value. Density is only +// for emitters that are calling effects...it basically +// specifies how often the emitter should emit fx. +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseDensity( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mDensity.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseVariance +// Reads in a ranged variance value. Variance is only +// valid for emitters that are calling effects... +// it basically determines the amount of slop in the +// density calculations +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseVariance( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mVariance.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRGBStart +// Reads in a ranged rgbStart field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRGBStart( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mRedStart.SetRange( min[0], max[0] ); + mGreenStart.SetRange( min[1], max[1] ); + mBlueStart.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRGBEnd +// Reads in a ranged rgbEnd field in vector format +// +// input: +// string that contains one or two vectors +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRGBEnd( const char *val ) +{ + vec3_t min, max; + + if ( ParseVector( val, min, max ) == true ) + { + mRedEnd.SetRange( min[0], max[0] ); + mGreenEnd.SetRange( min[1], max[1] ); + mBlueEnd.SetRange( min[2], max[2] ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRGBParm +// Reads in a ranged rgbParm field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRGBParm( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mRGBParm.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseRGBFlags +// Reads in a set of rgbFlags in string format +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRGBFlags( const char *val ) +{ + int flags; + + if ( ParseGroupFlags( val, &flags ) == true ) + { + // Convert our generic flag values into type specific ones + mFlags |= ( flags << FX_RGB_SHIFT ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAlphaStart +// Reads in a ranged alphaStart field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAlphaStart( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mAlphaStart.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAlphaEnd +// Reads in a ranged alphaEnd field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAlphaEnd( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mAlphaEnd.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAlphaParm +// Reads in a ranged alphaParm field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAlphaParm( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mAlphaParm.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseAlphaFlags +// Reads in a set of alphaFlags in string format +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAlphaFlags( const char *val ) +{ + int flags; + + if ( ParseGroupFlags( val, &flags ) == true ) + { + // Convert our generic flag values into type specific ones + mFlags |= ( flags << FX_ALPHA_SHIFT ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSizeStart +// Reads in a ranged sizeStart field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSizeStart( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSizeStart.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSizeEnd +// Reads in a ranged sizeEnd field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSizeEnd( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSizeEnd.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSizeParm +// Reads in a ranged sizeParm field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSizeParm( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSizeParm.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSizeFlags +// Reads in a set of sizeFlags in string format +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSizeFlags( const char *val ) +{ + int flags; + + if ( ParseGroupFlags( val, &flags ) == true ) + { + // Convert our generic flag values into type specific ones + mFlags |= ( flags << FX_SIZE_SHIFT ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSize2Start +// Reads in a ranged Size2Start field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize2Start( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSize2Start.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSize2End +// Reads in a ranged Size2End field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize2End( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSize2End.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSize2Parm +// Reads in a ranged Size2Parm field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize2Parm( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mSize2Parm.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseSize2Flags +// Reads in a set of Size2Flags in string format +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize2Flags( const char *val ) +{ + int flags; + + if ( ParseGroupFlags( val, &flags ) == true ) + { + // Convert our generic flag values into type specific ones + mFlags |= ( flags << FX_SIZE2_SHIFT ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseLengthStart +// Reads in a ranged lengthStart field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLengthStart( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mLengthStart.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseLengthEnd +// Reads in a ranged lengthEnd field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLengthEnd( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mLengthEnd.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseLengthParm +// Reads in a ranged lengthParm field in float format +// +// input: +// string that contains one or two floats +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLengthParm( const char *val ) +{ + float min, max; + + if ( ParseFloat( val, &min, &max ) == true ) + { + mLengthParm.SetRange( min, max ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseLengthFlags +// Reads in a set of lengthFlags in string format +// +// input: +// string that contains the flag strings +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLengthFlags( const char *val ) +{ + int flags; + + if ( ParseGroupFlags( val, &flags ) == true ) + { + // Convert our generic flag values into type specific ones + mFlags |= ( flags << FX_LENGTH_SHIFT ); + return true; + } + + return false; +} + +//------------------------------------------------------ +// ParseShaders +// Reads in a group of shaders and registers them +// +// input: +// Parse group that contains the list of shaders to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseShaders( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + + handle = theFxHelper.RegisterShader( val ); + mMediaHandles.AddHandle( handle ); + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxHelper.RegisterShader( val ); + mMediaHandles.AddHandle( handle ); + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseShaders called with an empty list!\n" ); + return false; + } + } + + return true; +} + +//------------------------------------------------------ +// ParseSounds +// Reads in a group of sounds and registers them +// +// input: +// Parse group that contains the list of sounds to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSounds( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + + handle = theFxHelper.RegisterSound( val ); + mMediaHandles.AddHandle( handle ); + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxHelper.RegisterSound( val ); + mMediaHandles.AddHandle( handle ); + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseSounds called with an empty list!\n" ); + return false; + } + } + + return true; +} + +#ifdef _IMMERSION +//------------------------------------------------------ +// ParseForces +// Reads in a group of forces and registers them +// +// input: +// Parse group that contains the list of forces to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseForces( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + + // Assumes FF_CHANNEL_WEAPON because sound mechanism assumes this + handle = theFxHelper.RegisterForce( val, FF_CHANNEL_WEAPON ); + mMediaHandles.AddHandle( handle ); + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + // Assumes FF_CHANNEL_WEAPON because sound mechanism assumes this + handle = theFxHelper.RegisterForce( val, FF_CHANNEL_WEAPON ); + mMediaHandles.AddHandle( handle ); + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseForces called with an empty list!\n" ); + return false; + } + } + + return true; +} +#endif // _IMMERSION +//------------------------------------------------------ +// ParseModels +// Reads in a group of models and registers them +// +// input: +// Parse group that contains the list of models to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseModels( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + + handle = theFxHelper.RegisterModel( val ); + mMediaHandles.AddHandle( handle ); + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxHelper.RegisterModel( val ); + mMediaHandles.AddHandle( handle ); + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseModels called with an empty list!\n" ); + return false; + } + } + + mFlags |= FX_ATTACHED_MODEL; + + return true; +} + +//------------------------------------------------------ +// ParseImpactFxStrings +// Reads in a group of fx file names and registers them +// +// input: +// Parse group that contains the list of fx to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseImpactFxStrings( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mImpactFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Impact effect file not found.\n" ); + return false; + } + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mImpactFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Impact effect file not found.\n" ); + return false; + } + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseImpactFxStrings called with an empty list!\n" ); + return false; + } + } + + mFlags |= FX_IMPACT_RUNS_FX | FX_APPLY_PHYSICS; + + return true; +} + +//------------------------------------------------------ +// ParseDeathFxStrings +// Reads in a group of fx file names and registers them +// +// input: +// Parse group that contains the list of fx to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseDeathFxStrings( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mDeathFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Death effect file not found.\n" ); + return false; + } + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mDeathFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Death effect file not found.\n" ); + return false; + } + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseDeathFxStrings called with an empty list!\n" ); + return false; + } + } + + mFlags |= FX_DEATH_RUNS_FX; + + return true; +} + +//------------------------------------------------------ +// ParseEmitterFxStrings +// Reads in a group of fx file names and registers them +// +// input: +// Parse group that contains the list of fx to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseEmitterFxStrings( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mEmitterFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Emitter effect file not found.\n" ); + return false; + } + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mEmitterFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Emitter effect file not found.\n" ); + return false; + } + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParseEmitterFxStrings called with an empty list!\n" ); + return false; + } + } + + mFlags |= FX_EMIT_FX; + + return true; +} + +//------------------------------------------------------ +// ParsePlayFxStrings +// Reads in a group of fx file names and registers them +// +// input: +// Parse group that contains the list of fx to parse +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParsePlayFxStrings( CGPValue *grp ) +{ + const char *val; + int handle; + + if ( grp->IsList() ) + { + // If we are a list we have to do separate processing + CGPObject *list = grp->GetList(); + + while ( list ) + { + // name is actually the value contained in the list + val = list->GetName(); + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mPlayFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Effect file not found.\n" ); + return false; + } + + list = (CGPValue *)list->GetNext(); + } + } + else + { + // Let's get a value + val = grp->GetTopValue(); + + if ( val ) + { + handle = theFxScheduler.RegisterEffect( val ); + + if ( handle ) + { + mPlayFxHandles.AddHandle( handle ); + } + else + { + theFxHelper.Print( "FxTemplate: Effect file not found.\n" ); + return false; + } + } + else + { + // empty "list" + theFxHelper.Print( "CPrimitiveTemplate::ParsePlayFxStrings called with an empty list!\n" ); + return false; + } + } + + return true; +} + +//------------------------------------------------------ +// ParseRGB +// Takes an RGB group and chomps out any pairs contained +// in it. +// +// input: +// the parse group to process +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseRGB( CGPGroup *grp ) +{ + CGPValue *pairs; + const char *key; + const char *val; + + // Inside of the group, we should have a series of pairs + pairs = grp->GetPairs(); + + while( pairs ) + { + // Let's get the key field + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "start" )) + { + ParseRGBStart( val ); + } + else if ( !stricmp( key, "end" )) + { + ParseRGBEnd( val ); + } + else if ( !stricmp( key, "parm" ) || !stricmp( key, "parms" )) + { + ParseRGBParm( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { + ParseRGBFlags( val ); + } + else + { + theFxHelper.Print( "Unknown key parsing an RGB group: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + return true; +} + +//------------------------------------------------------ +// ParseAlpha +// Takes an alpha group and chomps out any pairs contained +// in it. +// +// input: +// the parse group to process +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseAlpha( CGPGroup *grp ) +{ + CGPValue *pairs; + const char *key; + const char *val; + + // Inside of the group, we should have a series of pairs + pairs = grp->GetPairs(); + + while( pairs ) + { + // Let's get the key field + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "start" )) + { + ParseAlphaStart( val ); + } + else if ( !stricmp( key, "end" )) + { + ParseAlphaEnd( val ); + } + else if ( !stricmp( key, "parm" ) || !stricmp( key, "parms" )) + { + ParseAlphaParm( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { + ParseAlphaFlags( val ); + } + else + { + theFxHelper.Print( "Unknown key parsing an Alpha group: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + return true; +} + +//------------------------------------------------------ +// ParseSize +// Takes a size group and chomps out any pairs contained +// in it. +// +// input: +// the parse group to process +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize( CGPGroup *grp ) +{ + CGPValue *pairs; + const char *key; + const char *val; + + // Inside of the group, we should have a series of pairs + pairs = grp->GetPairs(); + + while( pairs ) + { + // Let's get the key field + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "start" )) + { + ParseSizeStart( val ); + } + else if ( !stricmp( key, "end" )) + { + ParseSizeEnd( val ); + } + else if ( !stricmp( key, "parm" ) || !stricmp( key, "parms" )) + { + ParseSizeParm( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { + ParseSizeFlags( val ); + } + else + { + theFxHelper.Print( "Unknown key parsing a Size group: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + return true; +} + +//------------------------------------------------------ +// ParseSize2 +// Takes a Size2 group and chomps out any pairs contained +// in it. +// +// input: +// the parse group to process +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseSize2( CGPGroup *grp ) +{ + CGPValue *pairs; + const char *key; + const char *val; + + // Inside of the group, we should have a series of pairs + pairs = grp->GetPairs(); + + while( pairs ) + { + // Let's get the key field + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "start" )) + { + ParseSize2Start( val ); + } + else if ( !stricmp( key, "end" )) + { + ParseSize2End( val ); + } + else if ( !stricmp( key, "parm" ) || !stricmp( key, "parms" )) + { + ParseSize2Parm( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { + ParseSize2Flags( val ); + } + else + { + theFxHelper.Print( "Unknown key parsing a Size2 group: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + return true; +} + +//------------------------------------------------------ +// ParseLength +// Takes a length group and chomps out any pairs contained +// in it. +// +// input: +// the parse group to process +// +// return: +// success of parse operation. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParseLength( CGPGroup *grp ) +{ + CGPValue *pairs; + const char *key; + const char *val; + + // Inside of the group, we should have a series of pairs + pairs = grp->GetPairs(); + + while( pairs ) + { + // Let's get the key field + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "start" )) + { + ParseLengthStart( val ); + } + else if ( !stricmp( key, "end" )) + { + ParseLengthEnd( val ); + } + else if ( !stricmp( key, "parm" ) || !stricmp( key, "parms" )) + { + ParseLengthParm( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { + ParseLengthFlags( val ); + } + else + { + theFxHelper.Print( "Unknown key parsing a Length group: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + return true; +} + + +// Parse a primitive, apply defaults first, grab any base level +// key pairs, then process any sub groups we may contain. +//------------------------------------------------------ +bool CPrimitiveTemplate::ParsePrimitive( CGPGroup *grp ) +{ + CGPGroup *subGrp; + CGPValue *pairs; + const char *key; + const char *val; + + // Lets work with the pairs first + pairs = grp->GetPairs(); + + while( pairs ) + { + // the fields + key = pairs->GetName(); + val = pairs->GetTopValue(); + + // Huge stricmp lists suxor + if ( !stricmp( key, "count" )) + { + ParseCount( val ); + } + else if ( !stricmp( key, "shaders" ) || !stricmp( key, "shader" )) + { + ParseShaders( pairs ); + } + else if ( !stricmp( key, "models" ) || !stricmp( key, "model" )) + { + ParseModels( pairs ); + } + else if ( !stricmp( key, "sounds" ) || !stricmp( key, "sound" )) + { + ParseSounds( pairs ); + } +#ifdef _IMMERSION + else if ( !stricmp( key, "forces" ) || !stricmp( key, "force" )) + { + ParseForces( pairs ); + } +#endif // _IMMERSION + else if ( !stricmp( key, "impactfx" )) + { + ParseImpactFxStrings( pairs ); + } + else if ( !stricmp( key, "deathfx" )) + { + ParseDeathFxStrings( pairs ); + } + else if ( !stricmp( key, "emitfx" )) + { + ParseEmitterFxStrings( pairs ); + } + else if ( !stricmp( key, "playfx" )) + { + ParsePlayFxStrings( pairs ); + } + else if ( !stricmp( key, "life" )) + { + ParseLife( val ); + } + else if ( !stricmp( key, "cullrange" )) + { + mCullRange = atoi( val ); + mCullRange *= mCullRange; // Square + } + else if ( !stricmp( key, "delay" )) + { + ParseDelay( val ); + } + else if ( !stricmp( key, "bounce" ) || !stricmp( key, "intensity" )) // me==bad for reusing this...but it shouldn't hurt anything) + { + ParseElasticity( val ); + } + else if ( !stricmp( key, "min" )) + { + ParseMin( val ); + } + else if ( !stricmp( key, "max" )) + { + ParseMax( val ); + } + else if ( !stricmp( key, "angle" ) || !stricmp( key, "angles" )) + { + ParseAngle( val ); + } + else if ( !stricmp( key, "angleDelta" )) + { + ParseAngleDelta( val ); + } + else if ( !stricmp( key, "velocity" ) || !stricmp( key, "vel" )) + { + ParseVelocity( val ); + } + else if ( !stricmp( key, "acceleration" ) || !stricmp( key, "accel" )) + { + ParseAcceleration( val ); + } + else if ( !stricmp( key, "gravity" )) + { + ParseGravity( val ); + } + else if ( !stricmp( key, "density" )) + { + ParseDensity( val ); + } + else if ( !stricmp( key, "variance" )) + { + ParseVariance( val ); + } + else if ( !stricmp( key, "origin" )) + { + ParseOrigin1( val ); + } + else if ( !stricmp( key, "origin2" )) + { + ParseOrigin2( val ); + } + else if ( !stricmp( key, "radius" )) // part of ellipse/cylinder calcs. + { + ParseRadius( val ); + } + else if ( !stricmp( key, "height" )) // part of ellipse/cylinder calcs. + { + ParseHeight( val ); + } + else if ( !stricmp( key, "rotation" )) + { + ParseRotation( val ); + } + else if ( !Q_stricmp( key, "rotationDelta" )) + { + ParseRotationDelta( val ); + } + else if ( !stricmp( key, "flags" ) || !stricmp( key, "flag" )) + { // these need to get passed on to the primitive + ParseFlags( val ); + } + else if ( !stricmp( key, "spawnFlags" ) || !stricmp( key, "spawnFlag" )) + { // these are used to spawn things in cool ways, but don't ever get passed on to prims. + ParseSpawnFlags( val ); + } + else if ( !stricmp( key, "name" )) + { + if ( val ) + { + // just stash the descriptive name of the primitive + strcpy( mName, val ); + } + } + else + { + theFxHelper.Print( "Unknown key parsing an effect primitive: %s\n", key ); + } + + pairs = (CGPValue *)pairs->GetNext(); + } + + subGrp = grp->GetSubGroups(); + + // Lets chomp on the groups now + while ( subGrp ) + { + key = subGrp->GetName(); + + if ( !stricmp( key, "rgb" )) + { + ParseRGB( subGrp ); + } + else if ( !stricmp( key, "alpha" )) + { + ParseAlpha( subGrp ); + } + else if ( !stricmp( key, "size" ) || !stricmp( key, "width" )) + { + ParseSize( subGrp ); + } + else if ( !stricmp( key, "size2" ) || !stricmp( key, "width2" )) + { + ParseSize2( subGrp ); + } + else if ( !stricmp( key, "length" ) || !stricmp( key, "height" )) + { + ParseLength( subGrp ); + } + else + { + theFxHelper.Print( "Unknown group key parsing a particle: %s\n", key ); + } + + subGrp = (CGPGroup *)subGrp->GetNext(); + } + + return true; +} \ No newline at end of file diff --git a/code/cgame/FxUtil.cpp b/code/cgame/FxUtil.cpp new file mode 100644 index 0000000..374707e --- /dev/null +++ b/code/cgame/FxUtil.cpp @@ -0,0 +1,1400 @@ + +// this include must remain at the top of every CPP file +#include "common_headers.h" + +#if !defined(FX_SCHEDULER_H_INC) + #include "FxScheduler.h" +#endif + +vec3_t WHITE = {1.0f, 1.0f, 1.0f}; + +struct SEffectList +{ + CEffect *mEffect; + int mKillTime; + bool mPortal; +}; + +#define PI 3.14159f + +SEffectList effectList[MAX_EFFECTS]; +SEffectList *nextValidEffect; +SFxHelper theFxHelper; + +int activeFx = 0; +int mMax = 0; +int mMaxTime = 0; +int drawnFx; +int mParticles; +int mOParticles; +int mLines; +int mTails; +qboolean fxInitialized = qfalse; + +//------------------------- +// FX_Free +// +// Frees all FX +//------------------------- +bool FX_Free( void ) +{ + for ( int i = 0; i < MAX_EFFECTS; i++ ) + { + if ( effectList[i].mEffect ) + { + delete effectList[i].mEffect; + } + + effectList[i].mEffect = 0; + } + + activeFx = 0; + + theFxScheduler.Clean(); + return true; +} + +//------------------------- +// FX_Stop +// +// Frees all active FX but leaves the templates +//------------------------- +void FX_Stop( void ) +{ + for ( int i = 0; i < MAX_EFFECTS; i++ ) + { + if ( effectList[i].mEffect ) + { + delete effectList[i].mEffect; + } + + effectList[i].mEffect = 0; + } + + activeFx = 0; + + theFxScheduler.Clean(false); +} + +//------------------------- +// FX_Init +// +// Preps system for use +//------------------------- +int FX_Init( void ) +{ + if ( fxInitialized == qfalse ) + { + fxInitialized = qtrue; + + for ( int i = 0; i < MAX_EFFECTS; i++ ) + { + effectList[i].mEffect = 0; + } + } + + FX_Free(); + + mMax = 0; + mMaxTime = 0; + + nextValidEffect = &effectList[0]; + theFxHelper.Init(); + + // ( nothing to see here, go away ) + // + extern void FX_CopeWithAnyLoadedSaveGames(); + FX_CopeWithAnyLoadedSaveGames(); + + return true; +} + + +//------------------------- +// FX_FreeMember +//------------------------- +static void FX_FreeMember( SEffectList *obj ) +{ + obj->mEffect->Die(); + delete obj->mEffect; + obj->mEffect = 0; + + // May as well mark this to be used next + nextValidEffect = obj; + + activeFx--; +} + + +//------------------------- +// FX_GetValidEffect +// +// Finds an unused effect slot +// +// Note - in the editor, this function may return NULL, indicating that all +// effects are being stopped. +//------------------------- +static SEffectList *FX_GetValidEffect() +{ + if ( nextValidEffect->mEffect == 0 ) + { + return nextValidEffect; + } + + int i; + SEffectList *ef; + + // Blah..plow through the list till we find something that is currently untainted + for ( i = 0, ef = effectList; i < MAX_EFFECTS; i++, ef++ ) + { + if ( ef->mEffect == 0 ) + { + return ef; + } + } + + // report the error. +#ifndef FINAL_BUILD + theFxHelper.Print( "FX system out of effects\n" ); +#endif + + // Hmmm.. just trashing the first effect in the list is a poor approach + FX_FreeMember( &effectList[0] ); + + // Recursive call + return nextValidEffect; +} + + +//------------------------- +// FX_ActiveFx +// +// Returns whether these are any active or scheduled effects +//------------------------- +bool FX_ActiveFx(void) +{ + return ((activeFx > 0) || (theFxScheduler.NumScheduledFx() > 0)); +} + + +//------------------------- +// FX_Add +// +// Adds all fx to the view +//------------------------- +void FX_Add( bool portal ) +{ + int i; + SEffectList *ef; + + drawnFx = 0; + mParticles = 0; + mOParticles = 0; + mLines = 0; + mTails = 0; + + int numFx = activeFx; //but stop when there can't be any more left! + for ( i = 0, ef = effectList; i < MAX_EFFECTS && numFx; i++, ef++ ) + { + if ( ef->mEffect != 0) + { + --numFx; + if (portal != ef->mPortal) + { + continue; //this one does not render in this scene + } + // Effect is active + if ( theFxHelper.mTime > ef->mKillTime ) + { + // Clean up old effects, calling any death effects as needed + // this flag just has to be cleared otherwise death effects might not happen correctly + ef->mEffect->ClearFlags( FX_KILL_ON_IMPACT ); + FX_FreeMember( ef ); + } + else + { + if ( ef->mEffect->Update() == false ) + { + // We've been marked for death + FX_FreeMember( ef ); + continue; + } + } + } + } + if ( fx_debug.integer == 2 && !portal ) + { + if (theFxHelper.mFrameTime > 100 || theFxHelper.mFrameTime < 5) + theFxHelper.Print( "theFxHelper.mFrameTime = %i\n", theFxHelper.mFrameTime ); + } + if ( fx_debug.integer == 1 && !portal ) + { + if ( theFxHelper.mTime > mMaxTime ) + { + // decay pretty harshly when we do it + mMax *= 0.9f; + mMaxTime = theFxHelper.mTime + 200; // decay 5 times a second if we haven't set a new max + } + if ( activeFx > mMax ) + { + // but we can never be less that the current activeFx count + mMax = activeFx; + mMaxTime = theFxHelper.mTime + 4000; // since we just increased the max, hold it for at least 4 seconds + } + + // Particles + if ( mParticles > 500 ) + { + theFxHelper.Print( ">Particles ^1%4i ", mParticles ); + } + else if ( mParticles > 250 ) + { + theFxHelper.Print( ">Particles ^3%4i ", mParticles ); + } + else + { + theFxHelper.Print( ">Particles %4i ", mParticles ); + } + + // Lines + if ( mLines > 500 ) + { + theFxHelper.Print( ">Lines ^1%4i\n", mLines ); + } + else if ( mLines > 250 ) + { + theFxHelper.Print( ">Lines ^3%4i\n", mLines ); + } + else + { + theFxHelper.Print( ">Lines %4i\n", mLines ); + } + + // OParticles + if ( mOParticles > 500 ) + { + theFxHelper.Print( ">OParticles ^1%4i ", mOParticles ); + } + else if ( mOParticles > 250 ) + { + theFxHelper.Print( ">OParticles ^3%4i ", mOParticles ); + } + else + { + theFxHelper.Print( ">OParticles %4i ", mOParticles ); + } + + // Tails + if ( mTails > 400 ) + { + theFxHelper.Print( ">Tails ^1%4i\n", mTails ); + } + else if ( mTails > 200 ) + { + theFxHelper.Print( ">Tails ^3%4i\n", mTails ); + } + else + { + theFxHelper.Print( ">Tails %4i\n", mTails ); + } + + // Active + if ( activeFx > 600 ) + { + theFxHelper.Print( ">Active ^1%4i ", activeFx ); + } + else if ( activeFx > 400 ) + { + theFxHelper.Print( ">Active ^3%4i ", activeFx ); + } + else + { + theFxHelper.Print( ">Active %4i ", activeFx ); + } + + // Drawn + if ( drawnFx > 600 ) + { + theFxHelper.Print( ">Drawn ^1%4i ", drawnFx ); + } + else if ( drawnFx > 400 ) + { + theFxHelper.Print( ">Drawn ^3%4i ", drawnFx ); + } + else + { + theFxHelper.Print( ">Drawn %4i ", drawnFx ); + } + + // Max + if ( mMax > 600 ) + { + theFxHelper.Print( ">Max ^1%4i ", mMax ); + } + else if ( mMax > 400 ) + { + theFxHelper.Print( ">Max ^3%4i ", mMax ); + } + else + { + theFxHelper.Print( ">Max %4i ", mMax ); + } + + // Scheduled + if ( theFxScheduler.NumScheduledFx() > 100 ) + { + theFxHelper.Print( ">Scheduled ^1%4i\n", theFxScheduler.NumScheduledFx() ); + } + else if ( theFxScheduler.NumScheduledFx() > 50 ) + { + theFxHelper.Print( ">Scheduled ^3%4i\n", theFxScheduler.NumScheduledFx() ); + } + else + { + theFxHelper.Print( ">Scheduled %4i\n", theFxScheduler.NumScheduledFx() ); + } + } +} + + +//------------------------- +// FX_AddPrimitive +// +// Note - in the editor, this function may change *pEffect to NULL, indicating that +// all effects are being stopped. +//------------------------- +extern bool gEffectsInPortal; //from FXScheduler.cpp so i don't have to pass it in on EVERY FX_ADD* +void FX_AddPrimitive( CEffect **pEffect, int killTime ) +{ + SEffectList *item = FX_GetValidEffect(); + + item->mEffect = *pEffect; + item->mKillTime = theFxHelper.mTime + killTime; + item->mPortal = gEffectsInPortal; //global set in AddScheduledEffects + + activeFx++; + + // Stash these in the primitive so it has easy access to the vals + (*pEffect)->SetTimeStart( theFxHelper.mTime ); + (*pEffect)->SetTimeEnd( theFxHelper.mTime + killTime ); +} + + +//------------------------- +// FX_AddParticle +//------------------------- +CParticle *FX_AddParticle( int clientID, const vec3_t org, const vec3_t vel, const vec3_t accel, float gravity, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + const vec3_t sRGB, const vec3_t eRGB, float rgbParm, + float rotation, float rotationDelta, + const vec3_t min, const vec3_t max, float elasticity, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags, int modelNum, int boltNum ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding effects when the system is paused + return 0; + } + + CParticle *fx = new CParticle; + + if ( fx ) + { + if (flags&FX_RELATIVE && clientID>=0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( org ); + fx->SetClient( clientID, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( org ); + } + fx->SetVel( vel ); + fx->SetAccel( accel ); + fx->SetGravity( gravity ); + + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetFlags( flags ); + fx->SetShader( shader ); + fx->SetRotation( rotation ); + fx->SetRotationDelta( rotationDelta ); + fx->SetElasticity( elasticity ); + fx->SetMin( min ); + fx->SetMax( max ); + fx->SetDeathFxID( deathID ); + fx->SetImpactFxID( impactID ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + // in the editor, fx may now be NULL + } + + return fx; +} + + +//------------------------- +// FX_AddLine +//------------------------- +CLine *FX_AddLine( int clientID, vec3_t start, vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int impactFX_id, int flags, int modelNum, int boltNum ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding new effects when the system is paused + return 0; + } + + CLine *fx = new CLine; + + if ( fx ) + { + if (flags&FX_RELATIVE && clientID>=0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( start ); //offset from bolt pos + fx->SetVel( end ); //vel is the vector offset from bolt+orgOffset + fx->SetClient( clientID, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( start ); + fx->SetOrigin2( end ); + } + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetShader( shader ); + fx->SetFlags( flags ); + + fx->SetSTScale( 1.0f, 1.0f ); + fx->SetImpactFxID( impactFX_id ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + // in the editor, fx may now be NULL + } + + return fx; +} + +//------------------------- +// FX_AddElectricity +//------------------------- +CElectricity *FX_AddElectricity( int clientID, vec3_t start, vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + float chaos, int killTime, qhandle_t shader, int flags, int modelNum, int boltNum ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding new effects when the system is paused + return 0; + } + + CElectricity *fx = new CElectricity; + + if ( fx ) + { + if (flags&FX_RELATIVE && clientID>=0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( start );//offset + fx->SetVel( end ); //vel is the vector offset from bolt+orgOffset + fx->SetClient( clientID, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( start ); + fx->SetOrigin2( end ); + } + + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetShader( shader ); + fx->SetFlags( flags ); + fx->SetChaos( chaos ); + + fx->SetSTScale( 1.0f, 1.0f ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + // in the editor, fx may now be NULL? + if ( fx ) + { + fx->Initialize(); + } + } + + return fx; +} + + +//------------------------- +// FX_AddTail +//------------------------- +CTail *FX_AddTail( int clientID, vec3_t org, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float length1, float length2, float lengthParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + vec3_t min, vec3_t max, float elasticity, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags, int modelNum, int boltNum ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding effects when the system is paused + return 0; + } + + CTail *fx = new CTail; + + if ( fx ) + { + if (flags&FX_RELATIVE && clientID>=0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( org ); + fx->SetClient( clientID, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( org ); + } + fx->SetVel( vel ); + fx->SetAccel( accel ); + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Length---------------- + fx->SetLengthStart( length1 ); + fx->SetLengthEnd( length2 ); + + if (( flags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_WAVE ) + { + fx->SetLengthParm( lengthParm * PI * 0.001f ); + } + else if ( flags & FX_LENGTH_PARM_MASK ) + { + fx->SetLengthParm( lengthParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetFlags( flags ); + fx->SetShader( shader ); + fx->SetElasticity( elasticity ); + fx->SetMin( min ); + fx->SetMax( max ); + fx->SetSTScale( 1.0f, 1.0f ); + fx->SetDeathFxID( deathID ); + fx->SetImpactFxID( impactID ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + // in the editor, fx may now be NULL + } + + return fx; +} + +//------------------------- +// FX_AddCylinder +//------------------------- +CCylinder *FX_AddCylinder( int clientID, vec3_t start, vec3_t normal, + float size1s, float size1e, float sizeParm, + float size2s, float size2e, float size2Parm, + float length1, float length2, float lengthParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + int killTime, qhandle_t shader, int flags, int modelNum, int boltNum ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding new effects when the system is paused + return 0; + } + + CCylinder *fx = new CCylinder; + + if ( fx ) + { + if (flags&FX_RELATIVE && clientID>=0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( start );//offset + //NOTE: relative version doesn't ever use normal! + //fx->SetNormal( normal ); + fx->SetClient( clientID, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( start ); + fx->SetNormal( normal ); + } + + // RGB---------------- + fx->SetRGBStart( rgb1 ); + fx->SetRGBEnd( rgb2 ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size1---------------- + fx->SetSizeStart( size1s ); + fx->SetSizeEnd( size1e ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size2---------------- + fx->SetSize2Start( size2s ); + fx->SetSize2End( size2e ); + + if (( flags & FX_SIZE2_PARM_MASK ) == FX_SIZE2_WAVE ) + { + fx->SetSize2Parm( size2Parm * PI * 0.001f ); + } + else if ( flags & FX_SIZE2_PARM_MASK ) + { + fx->SetSize2Parm( size2Parm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Length1--------------- + fx->SetLengthStart( length1 ); + fx->SetLengthEnd( length2 ); + + if (( flags & FX_LENGTH_PARM_MASK ) == FX_LENGTH_WAVE ) + { + fx->SetLengthParm( lengthParm * PI * 0.001f ); + } + else if ( flags & FX_LENGTH_PARM_MASK ) + { + fx->SetLengthParm( lengthParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetShader( shader ); + fx->SetFlags( flags ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + +//------------------------- +// FX_AddEmitter +//------------------------- +CEmitter *FX_AddEmitter( vec3_t org, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + vec3_t angs, vec3_t deltaAngs, + vec3_t min, vec3_t max, float elasticity, + int deathID, int impactID, int emitterID, + float density, float variance, + int killTime, qhandle_t model, int flags ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding effects when the system is paused + return 0; + } + + CEmitter *fx = new CEmitter; + + if ( fx ) + { + fx->SetOrigin1( org ); + fx->SetVel( vel ); + fx->SetAccel( accel ); + + // RGB---------------- + fx->SetRGBStart( rgb1 ); + fx->SetRGBEnd( rgb2 ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetAngles( angs ); + fx->SetAngleDelta( deltaAngs ); + fx->SetFlags( flags ); + fx->SetModel( model ); + fx->SetElasticity( elasticity ); + fx->SetMin( min ); + fx->SetMax( max ); + fx->SetDeathFxID( deathID ); + fx->SetImpactFxID( impactID ); + fx->SetEmitterFxID( emitterID ); + fx->SetDensity( density ); + fx->SetVariance( variance ); + fx->SetOldTime( theFxHelper.mTime ); + + fx->SetLastOrg( org ); + fx->SetLastVel( vel ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + // in the editor, fx may now be NULL + } + + return fx; +} + +//------------------------- +// FX_AddLight +//------------------------- +CLight *FX_AddLight( vec3_t org, float size1, float size2, float sizeParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + int killTime, int flags ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding effects when the system is paused + return 0; + } + + CLight *fx = new CLight; + + if ( fx ) + { + fx->SetOrigin1( org ); + + // RGB---------------- + fx->SetRGBStart( rgb1 ); + fx->SetRGBEnd( rgb2 ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetFlags( flags ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + // in the editor, fx may now be NULL + } + + return fx; + +} + + +//------------------------- +// FX_AddOrientedParticle +//------------------------- +COrientedParticle *FX_AddOrientedParticle( int clientID, vec3_t org, vec3_t norm, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + float rotation, float rotationDelta, + vec3_t min, vec3_t max, float bounce, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags, int modelNum, int boltNum ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding effects when the system is paused + return 0; + } + + COrientedParticle *fx = new COrientedParticle; + + if ( fx ) + { + if (flags&FX_RELATIVE && clientID>=0) + { + fx->SetOrigin1( NULL ); + fx->SetOrgOffset( org );//offset + fx->SetNormalOffset( norm ); + fx->SetClient( clientID, modelNum, boltNum ); + } + else + { + fx->SetOrigin1( org ); + fx->SetNormal( norm ); + } + fx->SetVel( vel ); + fx->SetAccel( accel ); + + // RGB---------------- + fx->SetRGBStart( rgb1 ); + fx->SetRGBEnd( rgb2 ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetFlags( flags ); + fx->SetShader( shader ); + fx->SetRotation( rotation ); + fx->SetRotationDelta( rotationDelta ); + fx->SetElasticity( bounce ); + fx->SetMin( min ); + fx->SetMax( max ); + fx->SetDeathFxID( deathID ); + fx->SetImpactFxID( impactID ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + // in the editor, fx may now be NULL + } + + return fx; +} + + +//------------------------- +// FX_AddPoly +//------------------------- +CPoly *FX_AddPoly( vec3_t *verts, vec2_t *st, int numVerts, + vec3_t vel, vec3_t accel, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + vec3_t rotationDelta, float bounce, int motionDelay, + int killTime, qhandle_t shader, int flags ) +{ + if ( theFxHelper.mFrameTime < 1 || !verts ) + { // disallow adding effects when the system is paused or the user doesn't pass in a vert array + return 0; + } + + CPoly *fx = new CPoly; + + if ( fx ) + { + // Do a cheesy copy of the verts and texture coords into our own structure + for ( int i = 0; i < numVerts; i++ ) + { + VectorCopy( verts[i], fx->mOrg[i] ); + Vector2Copy( st[i], fx->mST[i] ); + } + + fx->SetVel( vel ); + fx->SetAccel( accel ); + + // RGB---------------- + fx->SetRGBStart( rgb1 ); + fx->SetRGBEnd( rgb2 ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetFlags( flags ); + fx->SetShader( shader ); + fx->SetRot( rotationDelta ); + fx->SetElasticity( bounce ); + fx->SetMotionTimeStamp( motionDelay ); + fx->SetNumVerts( numVerts ); + + // Now that we've set our data up, let's process it into a useful format + fx->PolyInit(); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + // in the editor, fx may now be NULL + } + + return fx; +} + + +//------------------------- +// FX_AddBezier +//------------------------- +CBezier *FX_AddBezier( const vec3_t start, const vec3_t end, + const vec3_t control1, const vec3_t control1Vel, + const vec3_t control2, const vec3_t control2Vel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + const vec3_t sRGB, const vec3_t eRGB, const float rgbParm, + int killTime, qhandle_t shader, int flags ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding new effects when the system is paused + return 0; + } + + CBezier *fx = new CBezier; + + if ( fx ) + { + fx->SetOrigin1( start ); + fx->SetOrigin2( end ); + + fx->SetControlPoints( control1, control2 ); + fx->SetControlVel( control1Vel, control2Vel ); + + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } + + fx->SetShader( shader ); + fx->SetFlags( flags ); + + fx->SetSTScale( 1.0f, 1.0f ); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + +//------------------------- +// FX_AddFlash +//------------------------- +CFlash *FX_AddFlash( vec3_t origin, vec3_t sRGB, vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags = 0 ) +{ + if ( theFxHelper.mFrameTime < 1 ) + { // disallow adding new effects when the system is paused + return 0; + } + + CFlash *fx = new CFlash; + + if ( fx ) + { + fx->SetOrigin1( origin ); + + // RGB---------------- + fx->SetRGBStart( sRGB ); + fx->SetRGBEnd( eRGB ); + + if (( flags & FX_RGB_PARM_MASK ) == FX_RGB_WAVE ) + { + fx->SetRGBParm( rgbParm * PI * 0.001f ); + } + else if ( flags & FX_RGB_PARM_MASK ) + { + // rgbParm should be a value from 0-100.. + fx->SetRGBParm( rgbParm * 0.01f * killTime + theFxHelper.mTime ); + } + +/* // Alpha---------------- + fx->SetAlphaStart( alpha1 ); + fx->SetAlphaEnd( alpha2 ); + + if (( flags & FX_ALPHA_PARM_MASK ) == FX_ALPHA_WAVE ) + { + fx->SetAlphaParm( alphaParm * PI * 0.001f ); + } + else if ( flags & FX_ALPHA_PARM_MASK ) + { + fx->SetAlphaParm( alphaParm * 0.01f * killTime + theFxHelper.mTime ); + } + + // Size---------------- + fx->SetSizeStart( size1 ); + fx->SetSizeEnd( size2 ); + + if (( flags & FX_SIZE_PARM_MASK ) == FX_SIZE_WAVE ) + { + fx->SetSizeParm( sizeParm * PI * 0.001f ); + } + else if ( flags & FX_SIZE_PARM_MASK ) + { + fx->SetSizeParm( sizeParm * 0.01f * killTime + theFxHelper.mTime ); + } +*/ + fx->SetShader( shader ); + fx->SetFlags( flags ); + +// fx->SetSTScale( 1.0f, 1.0f ); + + fx->Init(); + + FX_AddPrimitive( (CEffect**)&fx, killTime ); + } + + return fx; +} + +//------------------------------------------------------- +// Functions for limited backward compatibility with EF. +// These calls can be used for simple programmatic +// effects, temp effects or debug graphics. +// Note that this is not an all-inclusive list of +// fx add functions from EF, nor are the calls guaranteed +// to produce the exact same result. +//------------------------------------------------------- + +//--------------------------------------------------- +void FX_AddSprite( vec3_t origin, vec3_t vel, vec3_t accel, + float scale, float dscale, + float sAlpha, float eAlpha, + float rotation, float bounce, + int life, qhandle_t shader, int flags ) +{ + FX_AddParticle( -1, origin, vel, accel, 0, scale, scale, 0, + sAlpha, eAlpha, FX_ALPHA_LINEAR, + WHITE, WHITE, 0, + rotation, 0, + vec3_origin, vec3_origin, bounce, + 0, 0, + life, shader, flags ); +} + +//--------------------------------------------------- +void FX_AddSprite( vec3_t origin, vec3_t vel, vec3_t accel, + float scale, float dscale, + float sAlpha, float eAlpha, + vec3_t sRGB, vec3_t eRGB, + float rotation, float bounce, + int life, qhandle_t shader, int flags ) +{ + FX_AddParticle( -1, origin, vel, accel, 0, scale, scale, 0, + sAlpha, eAlpha, FX_ALPHA_LINEAR, + sRGB, eRGB, 0, + rotation, 0, + vec3_origin, vec3_origin, bounce, + 0, 0, + life, shader, flags ); +} + +//--------------------------------------------------- +void FX_AddLine( vec3_t start, vec3_t end, float stScale, + float width, float dwidth, + float sAlpha, float eAlpha, + int life, qhandle_t shader, int flags ) +{ + FX_AddLine( -1, start, end, width, width, 0, + sAlpha, eAlpha, FX_ALPHA_LINEAR, + WHITE, WHITE, 0, + life, shader, 0, 0 ); +} + +//--------------------------------------------------- +void FX_AddLine( vec3_t start, vec3_t end, float stScale, + float width, float dwidth, + float sAlpha, float eAlpha, + vec3_t sRGB, vec3_t eRGB, + int life, qhandle_t shader, int flags ) +{ + FX_AddLine( -1, start, end, width, width, 0, + sAlpha, eAlpha, FX_ALPHA_LINEAR, + sRGB, eRGB, 0, + life, shader, 0, flags ); +} + +//--------------------------------------------------- +void FX_AddQuad( vec3_t origin, vec3_t normal, + vec3_t vel, vec3_t accel, + float sradius, float eradius, + float salpha, float ealpha, + vec3_t sRGB, vec3_t eRGB, + float rotation, int life, qhandle_t shader, int flags ) +{ + FX_AddOrientedParticle( -1, origin, normal, vel, accel, + sradius, eradius, 0.0f, + salpha, ealpha, 0.0f, + sRGB, eRGB, 0.0f, + rotation, 0.0f, + NULL, NULL, 0.0f, 0, 0, life, + shader, 0 ); +} diff --git a/code/cgame/FxUtil.h b/code/cgame/FxUtil.h new file mode 100644 index 0000000..445a394 --- /dev/null +++ b/code/cgame/FxUtil.h @@ -0,0 +1,130 @@ + +#if !defined(FX_PRIMITIVES_H_INC) + #include "FxPrimitives.h" +#endif + +#ifndef FX_UTIL_H_INC +#define FX_UTIL_H_INC + + +bool FX_Free( void ); // ditches all active effects; +int FX_Init( void ); // called in CG_Init to purge the fx system. +void FX_Add( bool portal ); // called every cgame frame to add all fx into the scene. +void FX_Stop( void ); // ditches all active effects without touching the templates. + +bool FX_ActiveFx(void); // returns whether there are any active or scheduled effects + + +CParticle *FX_AddParticle( int clientID, const vec3_t org, const vec3_t vel, const vec3_t accel, float gravity, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + const vec3_t rgb1, const vec3_t rgb2, float rgbParm, + float rotation, float rotationDelta, + const vec3_t min, const vec3_t max, float elasticity, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags, int modelNum = -1, int boltNum = -1 ); + +CLine *FX_AddLine( int clientID, vec3_t start, vec3_t end, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + int killTime, qhandle_t shader, int impactFX_id, int flags, int modelNum = -1, int boltNum = -1 ); + +CElectricity *FX_AddElectricity( int clientID, vec3_t start, vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t sRGB, vec3_t eRGB, float rgbParm, + float chaos, int killTime, qhandle_t shader, int flags, int modelNum = -1, int boltNum = -1 ); + +CTail *FX_AddTail( int clientID, vec3_t org, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float length1, float length2, float lengthParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + vec3_t min, vec3_t max, float elasticity, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags, int modelNum = -1, int boltNum = -1 ); + +CCylinder *FX_AddCylinder( int clientID, vec3_t start, vec3_t normal, + float size1s, float size1e, float size1Parm, + float size2s, float size2e, float size2Parm, + float length1, float length2, float lengthParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + int killTime, qhandle_t shader, int flags, int modelNum = -1, int boltNum = -1 ); + +CEmitter *FX_AddEmitter( vec3_t org, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + vec3_t angs, vec3_t deltaAngs, + vec3_t min, vec3_t max, float elasticity, + int deathID, int impactID, int emitterID, + float density, float variance, + int killTime, qhandle_t model, int flags ); + +CLight *FX_AddLight( vec3_t org, float size1, float size2, float sizeParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + int killTime, int flags ); + +COrientedParticle *FX_AddOrientedParticle( int clientID, vec3_t org, vec3_t norm, vec3_t vel, vec3_t accel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + float rotation, float rotationDelta, + vec3_t min, vec3_t max, float bounce, + int deathID, int impactID, + int killTime, qhandle_t shader, int flags, int modelNum = -1, int boltNum = -1 ); + +CPoly *FX_AddPoly( vec3_t *verts, vec2_t *st, int numVerts, + vec3_t vel, vec3_t accel, + float alpha1, float alpha2, float alphaParm, + vec3_t rgb1, vec3_t rgb2, float rgbParm, + vec3_t rotationDelta, float bounce, int motionDelay, + int killTime, qhandle_t shader, int flags ); + +CFlash *FX_AddFlash( vec3_t origin, vec3_t sRGB, vec3_t eRGB, float rgbParm, + int life, qhandle_t shader, int flags ); + + +// Included for backwards compatibility with CHC and for doing quick programmatic effects. +void FX_AddSprite( vec3_t origin, vec3_t vel, vec3_t accel, + float scale, float dscale, + float sAlpha, float eAlpha, + float rotation, float bounce, + int life, qhandle_t shader, int flags = 0 ); + +void FX_AddSprite( vec3_t origin, vec3_t vel, vec3_t accel, + float scale, float dscale, + float sAlpha, float eAlpha, + vec3_t sRGB, vec3_t eRGB, + float rotation, float bounce, + int life, qhandle_t shader, int flags = 0 ); + +void FX_AddLine( vec3_t start, vec3_t end, float stScale, + float width, float dwidth, + float sAlpha, float eAlpha, + int life, qhandle_t shader, int flags = 0 ); + +void FX_AddLine( vec3_t start, vec3_t end, float stScale, + float width, float dwidth, + float sAlpha, float eAlpha, + vec3_t sRGB, vec3_t eRGB, + int life, qhandle_t shader, int flags = 0 ); + +void FX_AddQuad( vec3_t origin, vec3_t normal, + vec3_t vel, vec3_t accel, + float sradius, float eradius, + float salpha, float ealpha, + vec3_t sRGB, vec3_t eRGB, + float rotation, int life, qhandle_t shader, int flags = 0 ); + +CBezier *FX_AddBezier( const vec3_t start, const vec3_t end, + const vec3_t control1, const vec3_t control1Vel, + const vec3_t control2, const vec3_t control2Vel, + float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + const vec3_t sRGB, const vec3_t eRGB, const float rgbParm, + int killTime, qhandle_t shader, int flags = 0 ); + + +#endif //FX_UTIL_H_INC \ No newline at end of file diff --git a/code/cgame/animtable.h b/code/cgame/animtable.h new file mode 100644 index 0000000..4772bf2 --- /dev/null +++ b/code/cgame/animtable.h @@ -0,0 +1,1792 @@ +// special file included only by cg_players.cpp & ui_players.cpp +// +// moved it from the original header file for PCH reasons... +// + +#if defined(_XBOX) && !defined(_JK2EXE) && !defined(_UI) // Linker only wants one copy +extern stringID_table_t animTable[MAX_ANIMATIONS+1]; +#else +stringID_table_t animTable [MAX_ANIMATIONS+1] = +{ + //================================================= + //HEAD ANIMS + //================================================= + //# #sep Head-only anims + ENUM2STRING(FACE_TALK0), //# silent + ENUM2STRING(FACE_TALK1), //# quiet + ENUM2STRING(FACE_TALK2), //# semi-quiet + ENUM2STRING(FACE_TALK3), //# semi-loud + ENUM2STRING(FACE_TALK4), //# loud + ENUM2STRING(FACE_ALERT), //# + ENUM2STRING(FACE_SMILE), //# + ENUM2STRING(FACE_FROWN), //# + ENUM2STRING(FACE_DEAD), //# + + //================================================= + //ANIMS IN WHICH UPPER AND LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep ENUM2STRING(BOTH_ DEATHS + ENUM2STRING(BOTH_DEATH1), //# First Death anim + ENUM2STRING(BOTH_DEATH2), //# Second Death anim + ENUM2STRING(BOTH_DEATH3), //# Third Death anim + ENUM2STRING(BOTH_DEATH4), //# Fourth Death anim + ENUM2STRING(BOTH_DEATH5), //# Fifth Death anim + ENUM2STRING(BOTH_DEATH6), //# Sixth Death anim + ENUM2STRING(BOTH_DEATH7), //# Seventh Death anim + ENUM2STRING(BOTH_DEATH8), //# + ENUM2STRING(BOTH_DEATH9), //# + ENUM2STRING(BOTH_DEATH10), //# + ENUM2STRING(BOTH_DEATH11), //# + ENUM2STRING(BOTH_DEATH12), //# + ENUM2STRING(BOTH_DEATH13), //# + ENUM2STRING(BOTH_DEATH14), //# + ENUM2STRING(BOTH_DEATH15), //# + ENUM2STRING(BOTH_DEATH16), //# + ENUM2STRING(BOTH_DEATH17), //# + ENUM2STRING(BOTH_DEATH18), //# + ENUM2STRING(BOTH_DEATH19), //# + ENUM2STRING(BOTH_DEATH20), //# + ENUM2STRING(BOTH_DEATH21), //# + ENUM2STRING(BOTH_DEATH22), //# + ENUM2STRING(BOTH_DEATH23), //# + ENUM2STRING(BOTH_DEATH24), //# + ENUM2STRING(BOTH_DEATH25), //# + + ENUM2STRING(BOTH_DEATHFORWARD1), //# First Death in which they get thrown forward + ENUM2STRING(BOTH_DEATHFORWARD2), //# Second Death in which they get thrown forward + ENUM2STRING(BOTH_DEATHFORWARD3), //# Tavion's falling in cin# 23 + ENUM2STRING(BOTH_DEATHBACKWARD1), //# First Death in which they get thrown backward + ENUM2STRING(BOTH_DEATHBACKWARD2), //# Second Death in which they get thrown backward + + ENUM2STRING(BOTH_DEATH1IDLE), //# Idle while close to death + ENUM2STRING(BOTH_LYINGDEATH1), //# Death to play when killed lying down + ENUM2STRING(BOTH_STUMBLEDEATH1), //# Stumble forward and fall face first death + ENUM2STRING(BOTH_FALLDEATH1), //# Fall forward off a high cliff and splat death - start + ENUM2STRING(BOTH_FALLDEATH1INAIR), //# Fall forward off a high cliff and splat death - loop + ENUM2STRING(BOTH_FALLDEATH1LAND), //# Fall forward off a high cliff and splat death - hit bottom + ENUM2STRING(BOTH_DEATH_ROLL), //# Death anim from a roll + ENUM2STRING(BOTH_DEATH_FLIP), //# Death anim from a flip + ENUM2STRING(BOTH_DEATH_SPIN_90_R), //# Death anim when facing 90 degrees right + ENUM2STRING(BOTH_DEATH_SPIN_90_L), //# Death anim when facing 90 degrees left + ENUM2STRING(BOTH_DEATH_SPIN_180), //# Death anim when facing backwards + ENUM2STRING(BOTH_DEATH_LYING_UP), //# Death anim when lying on back + ENUM2STRING(BOTH_DEATH_LYING_DN), //# Death anim when lying on front + ENUM2STRING(BOTH_DEATH_FALLING_DN), //# Death anim when falling on face + ENUM2STRING(BOTH_DEATH_FALLING_UP), //# Death anim when falling on back + ENUM2STRING(BOTH_DEATH_CROUCHED), //# Death anim when crouched + //# #sep ENUM2STRING(BOTH_ DEAD POSES # Should be last frame of corresponding previous anims + ENUM2STRING(BOTH_DEAD1), //# First Death finished pose + ENUM2STRING(BOTH_DEAD2), //# Second Death finished pose + ENUM2STRING(BOTH_DEAD3), //# Third Death finished pose + ENUM2STRING(BOTH_DEAD4), //# Fourth Death finished pose + ENUM2STRING(BOTH_DEAD5), //# Fifth Death finished pose + ENUM2STRING(BOTH_DEAD6), //# Sixth Death finished pose + ENUM2STRING(BOTH_DEAD7), //# Seventh Death finished pose + ENUM2STRING(BOTH_DEAD8), //# + ENUM2STRING(BOTH_DEAD9), //# + ENUM2STRING(BOTH_DEAD10), //# + ENUM2STRING(BOTH_DEAD11), //# + ENUM2STRING(BOTH_DEAD12), //# + ENUM2STRING(BOTH_DEAD13), //# + ENUM2STRING(BOTH_DEAD14), //# + ENUM2STRING(BOTH_DEAD15), //# + ENUM2STRING(BOTH_DEAD16), //# + ENUM2STRING(BOTH_DEAD17), //# + ENUM2STRING(BOTH_DEAD18), //# + ENUM2STRING(BOTH_DEAD19), //# + ENUM2STRING(BOTH_DEAD20), //# + ENUM2STRING(BOTH_DEAD21), //# + ENUM2STRING(BOTH_DEAD22), //# + ENUM2STRING(BOTH_DEAD23), //# + ENUM2STRING(BOTH_DEAD24), //# + ENUM2STRING(BOTH_DEAD25), //# + ENUM2STRING(BOTH_DEADFORWARD1), //# First thrown forward death finished pose + ENUM2STRING(BOTH_DEADFORWARD2), //# Second thrown forward death finished pose + ENUM2STRING(BOTH_DEADBACKWARD1), //# First thrown backward death finished pose + ENUM2STRING(BOTH_DEADBACKWARD2), //# Second thrown backward death finished pose + ENUM2STRING(BOTH_LYINGDEAD1), //# Killed lying down death finished pose + ENUM2STRING(BOTH_STUMBLEDEAD1), //# Stumble forward death finished pose + ENUM2STRING(BOTH_FALLDEAD1LAND), //# Fall forward and splat death finished pose + //# #sep ENUM2STRING(BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + ENUM2STRING(BOTH_DEADFLOP1), //# React to being shot from First Death finished pose + ENUM2STRING(BOTH_DEADFLOP2), //# React to being shot from Second Death finished pose + ENUM2STRING(BOTH_DISMEMBER_HEAD1), //# + ENUM2STRING(BOTH_DISMEMBER_TORSO1), //# + ENUM2STRING(BOTH_DISMEMBER_LLEG), //# + ENUM2STRING(BOTH_DISMEMBER_RLEG), //# + ENUM2STRING(BOTH_DISMEMBER_RARM), //# + ENUM2STRING(BOTH_DISMEMBER_LARM), //# + //# #sep ENUM2STRING(BOTH_ PAINS + ENUM2STRING(BOTH_PAIN1), //# First take pain anim + ENUM2STRING(BOTH_PAIN2), //# Second take pain anim + ENUM2STRING(BOTH_PAIN3), //# Third take pain anim + ENUM2STRING(BOTH_PAIN4), //# Fourth take pain anim + ENUM2STRING(BOTH_PAIN5), //# Fifth take pain anim - from behind + ENUM2STRING(BOTH_PAIN6), //# Sixth take pain anim - from behind + ENUM2STRING(BOTH_PAIN7), //# Seventh take pain anim - from behind + ENUM2STRING(BOTH_PAIN8), //# Eigth take pain anim - from behind + ENUM2STRING(BOTH_PAIN9), //# + ENUM2STRING(BOTH_PAIN10), //# + ENUM2STRING(BOTH_PAIN11), //# + ENUM2STRING(BOTH_PAIN12), //# + ENUM2STRING(BOTH_PAIN13), //# + ENUM2STRING(BOTH_PAIN14), //# + ENUM2STRING(BOTH_PAIN15), //# + ENUM2STRING(BOTH_PAIN16), //# + ENUM2STRING(BOTH_PAIN17), //# + ENUM2STRING(BOTH_PAIN18), //# + + //# #sep ENUM2STRING(BOTH_ ATTACKS + ENUM2STRING(BOTH_ATTACK1), //# Attack with stun baton + ENUM2STRING(BOTH_ATTACK2), //# Attack with one-handed pistol + ENUM2STRING(BOTH_ATTACK3), //# Attack with blaster rifle + ENUM2STRING(BOTH_ATTACK4), //# Attack with disruptor + ENUM2STRING(BOTH_ATTACK5), //# Another Rancor Attack + ENUM2STRING(BOTH_ATTACK6), //# Yet Another Rancor Attack + ENUM2STRING(BOTH_ATTACK7), //# Yet Another Rancor Attack + ENUM2STRING(BOTH_ATTACK10), //# Attack with thermal det + ENUM2STRING(BOTH_ATTACK11), //# "Attack" with tripmine and detpack + ENUM2STRING(BOTH_MELEE1), //# First melee attack + ENUM2STRING(BOTH_MELEE2), //# Second melee attack + ENUM2STRING(BOTH_THERMAL_READY), //# pull back with thermal + ENUM2STRING(BOTH_THERMAL_THROW), //# throw thermal + //* #sep ENUM2STRING(BOTH_ SABER ANIMS + //Saber attack anims - power level 1 + ENUM2STRING(BOTH_A1_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A1__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A1__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A1_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A1_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A1_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A1_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T1_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T1_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T1_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T1_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T1__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T1__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T1__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T1__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T1_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T1_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T1_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T1_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T1_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T1_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T1_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T1_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T1_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T1_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T1_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T1_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T1__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T1__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T1__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T1_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T1_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T1_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T1_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T1_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T1_TR_BR) + ENUM2STRING(BOTH_T1_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T1_T__BR) + ENUM2STRING(BOTH_T1__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T1_BR__R) + ENUM2STRING(BOTH_T1__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T1_T___R) + ENUM2STRING(BOTH_T1_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T1__R_TR) + ENUM2STRING(BOTH_T1_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T1_T__TR) + ENUM2STRING(BOTH_T1_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T1__R_TL) + ENUM2STRING(BOTH_T1_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T1_TR_TL) + ENUM2STRING(BOTH_T1_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T1_T__TL) + ENUM2STRING(BOTH_T1_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T1__L_TL) + ENUM2STRING(BOTH_T1__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T1_TR__L) + ENUM2STRING(BOTH_T1__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T1_T___L) + ENUM2STRING(BOTH_T1__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T1_BL__L) + ENUM2STRING(BOTH_T1_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T1_T__BL) + ENUM2STRING(BOTH_T1_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T1_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S1_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S1_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S1_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S1_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S1_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S1_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S1_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R1_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R1__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R1__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B1_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B1__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B1_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B1_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B1_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B1__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B1_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D1_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D1__R___), //# Deflection toward R + ENUM2STRING(BOTH_D1_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D1_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D1__L___), //# Deflection toward L + ENUM2STRING(BOTH_D1_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D1_B____), //# Deflection toward B + //Saber attack anims - power level 2 + ENUM2STRING(BOTH_A2_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A2__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A2__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A2_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A2_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A2_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A2_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T2_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T2_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T2_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T2_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T2__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T2__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T2__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T2__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T2_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T2_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T2_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T2_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T2_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T2_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T2_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T2_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T2_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T2_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T2_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T2_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T2__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T2__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T2__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T2_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T2_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T2_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T2_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T2_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T2_TR_BR) + ENUM2STRING(BOTH_T2_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T2_T__BR) + ENUM2STRING(BOTH_T2__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T2_BR__R) + ENUM2STRING(BOTH_T2__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T2_T___R) + ENUM2STRING(BOTH_T2_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T2__R_TR) + ENUM2STRING(BOTH_T2_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T2_T__TR) + ENUM2STRING(BOTH_T2_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T2__R_TL) + ENUM2STRING(BOTH_T2_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T2_TR_TL) + ENUM2STRING(BOTH_T2_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T2_T__TL) + ENUM2STRING(BOTH_T2_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T2__L_TL) + ENUM2STRING(BOTH_T2__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T2_TR__L) + ENUM2STRING(BOTH_T2__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T2_T___L) + ENUM2STRING(BOTH_T2__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T2_BL__L) + ENUM2STRING(BOTH_T2_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T2_T__BL) + ENUM2STRING(BOTH_T2_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T2_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S2_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S2_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S2_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S2_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S2_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S2_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S2_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R2_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R2__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R2__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B2_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B2__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B2_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B2_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B2_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B2__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B2_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D2_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D2__R___), //# Deflection toward R + ENUM2STRING(BOTH_D2_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D2_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D2__L___), //# Deflection toward L + ENUM2STRING(BOTH_D2_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D2_B____), //# Deflection toward B + //Saber attack anims - power level 3 + ENUM2STRING(BOTH_A3_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A3__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A3__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A3_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A3_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A3_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A3_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T3_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T3_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T3_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T3_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T3__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T3__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T3__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T3__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T3_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T3_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T3_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T3_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T3_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T3_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T3_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T3_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T3_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T3_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T3_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T3_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T3__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T3__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T3__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T3_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T3_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T3_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T3_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T3_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T3_TR_BR) + ENUM2STRING(BOTH_T3_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T3_T__BR) + ENUM2STRING(BOTH_T3__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T3_BR__R) + ENUM2STRING(BOTH_T3__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T3_T___R) + ENUM2STRING(BOTH_T3_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T3__R_TR) + ENUM2STRING(BOTH_T3_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T3_T__TR) + ENUM2STRING(BOTH_T3_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T3__R_TL) + ENUM2STRING(BOTH_T3_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T3_TR_TL) + ENUM2STRING(BOTH_T3_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T3_T__TL) + ENUM2STRING(BOTH_T3_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T3__L_TL) + ENUM2STRING(BOTH_T3__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T3_TR__L) + ENUM2STRING(BOTH_T3__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T3_T___L) + ENUM2STRING(BOTH_T3__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T3_BL__L) + ENUM2STRING(BOTH_T3_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T3_T__BL) + ENUM2STRING(BOTH_T3_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T3_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S3_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S3_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S3_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S3_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S3_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S3_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S3_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R3_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R3__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R3__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B3_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B3__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B3_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B3_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B3_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B3__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B3_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D3_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D3__R___), //# Deflection toward R + ENUM2STRING(BOTH_D3_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D3_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D3__L___), //# Deflection toward L + ENUM2STRING(BOTH_D3_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D3_B____), //# Deflection toward B + //Saber attack anims - power level 4 - Desann's + ENUM2STRING(BOTH_A4_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A4__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A4__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A4_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A4_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A4_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A4_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T4_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T4_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T4_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T4_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T4__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T4__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T4__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T4__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T4_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T4_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T4_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T4_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T4_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T4_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T4_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T4_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T4_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T4_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T4_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T4_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T4__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T4__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T4__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T4_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T4_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T4_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T4_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T4_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T4_TR_BR) + ENUM2STRING(BOTH_T4_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T4_T__BR) + ENUM2STRING(BOTH_T4__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T4_BR__R) + ENUM2STRING(BOTH_T4__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T4_T___R) + ENUM2STRING(BOTH_T4_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T4__R_TR) + ENUM2STRING(BOTH_T4_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T4_T__TR) + ENUM2STRING(BOTH_T4_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T4__R_TL) + ENUM2STRING(BOTH_T4_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T4_TR_TL) + ENUM2STRING(BOTH_T4_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T4_T__TL) + ENUM2STRING(BOTH_T4_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T4__L_TL) + ENUM2STRING(BOTH_T4__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T4_TR__L) + ENUM2STRING(BOTH_T4__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T4_T___L) + ENUM2STRING(BOTH_T4__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T4_BL__L) + ENUM2STRING(BOTH_T4_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T4_T__BL) + ENUM2STRING(BOTH_T4_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T4_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S4_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S4_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S4_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S4_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S4_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S4_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S4_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R4_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R4__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R4__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B4_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B4__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B4_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B4_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B4_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B4__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B4_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D4_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D4__R___), //# Deflection toward R + ENUM2STRING(BOTH_D4_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D4_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D4__L___), //# Deflection toward L + ENUM2STRING(BOTH_D4_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D4_B____), //# Deflection toward B + //Saber attack anims - power level 5 - Tavion's + ENUM2STRING(BOTH_A5_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A5__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A5__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A5_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A5_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A5_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A5_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T5_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T5_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T5_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T5_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T5__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T5__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T5__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T5__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T5_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T5_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T5_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T5_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T5_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T5_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T5_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T5_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T5_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T5_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T5_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T5_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T5__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T5__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T5__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T5_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T5_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T5_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T5_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T5_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T5_TR_BR) + ENUM2STRING(BOTH_T5_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T5_T__BR) + ENUM2STRING(BOTH_T5__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T5_BR__R) + ENUM2STRING(BOTH_T5__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T5_T___R) + ENUM2STRING(BOTH_T5_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T5__R_TR) + ENUM2STRING(BOTH_T5_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T5_T__TR) + ENUM2STRING(BOTH_T5_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T5__R_TL) + ENUM2STRING(BOTH_T5_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T5_TR_TL) + ENUM2STRING(BOTH_T5_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T5_T__TL) + ENUM2STRING(BOTH_T5_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T5__L_TL) + ENUM2STRING(BOTH_T5__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T5_TR__L) + ENUM2STRING(BOTH_T5__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T5_T___L) + ENUM2STRING(BOTH_T5__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T5_BL__L) + ENUM2STRING(BOTH_T5_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T5_T__BL) + ENUM2STRING(BOTH_T5_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T5_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S5_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S5_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S5_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S5_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S5_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S5_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S5_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R5_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R5__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R5__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B5_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B5__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B5_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B5_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B5_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B5__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B5_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D5_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D5__R___), //# Deflection toward R + ENUM2STRING(BOTH_D5_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D5_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D5__L___), //# Deflection toward L + ENUM2STRING(BOTH_D5_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D5_B____), //# Deflection toward B + //Saber attack anims - power level 6 + ENUM2STRING(BOTH_A6_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A6__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A6__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A6_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A6_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A6_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A6_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T6_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T6_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T6_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T6_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T6__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T6__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T6__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T6__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T6_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T6_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T6_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T6_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T6_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T6_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T6_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T6_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T6_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T6_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T6_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T6_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T6__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T6__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T6__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T6_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T6_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T6_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T6_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T6_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T6_TR_BR) + ENUM2STRING(BOTH_T6_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T6_T__BR) + ENUM2STRING(BOTH_T6__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T6_BR__R) + ENUM2STRING(BOTH_T6__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T6_T___R) + ENUM2STRING(BOTH_T6_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T6__R_TR) + ENUM2STRING(BOTH_T6_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T6_T__TR) + ENUM2STRING(BOTH_T6_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T6__R_TL) + ENUM2STRING(BOTH_T6_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T6_TR_TL) + ENUM2STRING(BOTH_T6_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T6_T__TL) + ENUM2STRING(BOTH_T6_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T6__L_TL) + ENUM2STRING(BOTH_T6__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T6_TR__L) + ENUM2STRING(BOTH_T6__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T6_T___L) + ENUM2STRING(BOTH_T6__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T6_BL__L) + ENUM2STRING(BOTH_T6_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T6_T__BL) + ENUM2STRING(BOTH_T6_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T6_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S6_S6_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S6_S6__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S6_S6__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S6_S6_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S6_S6_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S6_S6_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S6_S6_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R6_B__S6), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R6__L_S6), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R6__R_S6), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_TL_S6), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_BR_S6), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_BL_S6), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_TR_S6), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B6_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B6__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B6_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B6_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B6_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B6__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B6_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D6_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D6__R___), //# Deflection toward R + ENUM2STRING(BOTH_D6_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D6_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D6__L___), //# Deflection toward L + ENUM2STRING(BOTH_D6_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D6_B____), //# Deflection toward B + //Saber attack anims - power level 7 + ENUM2STRING(BOTH_A7_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A7__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A7__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A7_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A7_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A7_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A7_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T7_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T7_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T7_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T7_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T7__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T7__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T7__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T7__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T7_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T7_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T7_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T7_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T7_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T7_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T7_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T7_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T7_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T7_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T7_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T7_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T7__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T7__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T7__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T7_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T7_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T7_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T7_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T7_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T7_TR_BR) + ENUM2STRING(BOTH_T7_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T7_T__BR) + ENUM2STRING(BOTH_T7__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T7_BR__R) + ENUM2STRING(BOTH_T7__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T7_T___R) + ENUM2STRING(BOTH_T7_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T7__R_TR) + ENUM2STRING(BOTH_T7_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T7_T__TR) + ENUM2STRING(BOTH_T7_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T7__R_TL) + ENUM2STRING(BOTH_T7_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T7_TR_TL) + ENUM2STRING(BOTH_T7_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T7_T__TL) + ENUM2STRING(BOTH_T7_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T7__L_TL) + ENUM2STRING(BOTH_T7__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T7_TR__L) + ENUM2STRING(BOTH_T7__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T7_T___L) + ENUM2STRING(BOTH_T7__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T7_BL__L) + ENUM2STRING(BOTH_T7_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T7_T__BL) + ENUM2STRING(BOTH_T7_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T7_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S7_S7_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S7_S7__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S7_S7__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S7_S7_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S7_S7_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S7_S7_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S7_S7_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R7_B__S7), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R7__L_S7), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R7__R_S7), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_TL_S7), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_BR_S7), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_BL_S7), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_TR_S7), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B7_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B7__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B7_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B7_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B7_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B7__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B7_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D7_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D7__R___), //# Deflection toward R + ENUM2STRING(BOTH_D7_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D7_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D7__L___), //# Deflection toward L + ENUM2STRING(BOTH_D7_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D7_B____), //# Deflection toward B + //Saber parry anims + ENUM2STRING(BOTH_P1_S1_T_), //# Block shot/saber top + ENUM2STRING(BOTH_P1_S1_TR), //# Block shot/saber top right + ENUM2STRING(BOTH_P1_S1_TL), //# Block shot/saber top left + ENUM2STRING(BOTH_P1_S1_BL), //# Block shot/saber bottom left + ENUM2STRING(BOTH_P1_S1_BR), //# Block shot/saber bottom right + //Saber knockaway + ENUM2STRING(BOTH_K1_S1_T_), //# knockaway saber top + ENUM2STRING(BOTH_K1_S1_TR), //# knockaway saber top right + ENUM2STRING(BOTH_K1_S1_TL), //# knockaway saber top left + ENUM2STRING(BOTH_K1_S1_BL), //# knockaway saber bottom left + ENUM2STRING(BOTH_K1_S1_B_), //# knockaway saber bottom + ENUM2STRING(BOTH_K1_S1_BR), //# knockaway saber bottom right + //Saber attack knocked away + ENUM2STRING(BOTH_V1_BR_S1), //# BR attack knocked away + ENUM2STRING(BOTH_V1__R_S1), //# R attack knocked away + ENUM2STRING(BOTH_V1_TR_S1), //# TR attack knocked away + ENUM2STRING(BOTH_V1_T__S1), //# T attack knocked away + ENUM2STRING(BOTH_V1_TL_S1), //# TL attack knocked away + ENUM2STRING(BOTH_V1__L_S1), //# L attack knocked away + ENUM2STRING(BOTH_V1_BL_S1), //# BL attack knocked away + ENUM2STRING(BOTH_V1_B__S1), //# B attack knocked away + //Saber parry broken + ENUM2STRING(BOTH_H1_S1_T_), //# saber knocked down from top parry + ENUM2STRING(BOTH_H1_S1_TR), //# saber knocked down-left from TR parry + ENUM2STRING(BOTH_H1_S1_TL), //# saber knocked down-right from TL parry + ENUM2STRING(BOTH_H1_S1_BL), //# saber knocked up-right from BL parry + ENUM2STRING(BOTH_H1_S1_B_), //# saber knocked up over head from ready? + ENUM2STRING(BOTH_H1_S1_BR), //# saber knocked up-left from BR parry + //Dual Sabers parry anims + ENUM2STRING(BOTH_P6_S6_T_), //# Block shot/saber top + ENUM2STRING(BOTH_P6_S6_TR), //# Block shot/saber top right + ENUM2STRING(BOTH_P6_S6_TL), //# Block shot/saber top left + ENUM2STRING(BOTH_P6_S6_BL), //# Block shot/saber bottom left + ENUM2STRING(BOTH_P6_S6_BR), //# Block shot/saber bottom right + //Dual Sabers knockaway + ENUM2STRING(BOTH_K6_S6_T_), //# knockaway saber top + ENUM2STRING(BOTH_K6_S6_TR), //# knockaway saber top right + ENUM2STRING(BOTH_K6_S6_TL), //# knockaway saber top left + ENUM2STRING(BOTH_K6_S6_BL), //# knockaway saber bottom left + ENUM2STRING(BOTH_K6_S6_B_), //# knockaway saber bottom + ENUM2STRING(BOTH_K6_S6_BR), //# knockaway saber bottom right + //Dual Sabers attack knocked away + ENUM2STRING(BOTH_V6_BR_S6), //# BR attack knocked away + ENUM2STRING(BOTH_V6__R_S6), //# R attack knocked away + ENUM2STRING(BOTH_V6_TR_S6), //# TR attack knocked away + ENUM2STRING(BOTH_V6_T__S6), //# T attack knocked away + ENUM2STRING(BOTH_V6_TL_S6), //# TL attack knocked away + ENUM2STRING(BOTH_V6__L_S6), //# L attack knocked away + ENUM2STRING(BOTH_V6_BL_S6), //# BL attack knocked away + ENUM2STRING(BOTH_V6_B__S6), //# B attack knocked away + //Dual Sabers parry broken + ENUM2STRING(BOTH_H6_S6_T_), //# saber knocked down from top parry + ENUM2STRING(BOTH_H6_S6_TR), //# saber knocked down-left from TR parry + ENUM2STRING(BOTH_H6_S6_TL), //# saber knocked down-right from TL parry + ENUM2STRING(BOTH_H6_S6_BL), //# saber knocked up-right from BL parry + ENUM2STRING(BOTH_H6_S6_B_), //# saber knocked up over head from ready? + ENUM2STRING(BOTH_H6_S6_BR), //# saber knocked up-left from BR parry + //SaberStaff parry anims + ENUM2STRING(BOTH_P7_S7_T_), //# Block shot/saber top + ENUM2STRING(BOTH_P7_S7_TR), //# Block shot/saber top right + ENUM2STRING(BOTH_P7_S7_TL), //# Block shot/saber top left + ENUM2STRING(BOTH_P7_S7_BL), //# Block shot/saber bottom left + ENUM2STRING(BOTH_P7_S7_BR), //# Block shot/saber bottom right + //SaberStaff knockaway + ENUM2STRING(BOTH_K7_S7_T_), //# knockaway saber top + ENUM2STRING(BOTH_K7_S7_TR), //# knockaway saber top right + ENUM2STRING(BOTH_K7_S7_TL), //# knockaway saber top left + ENUM2STRING(BOTH_K7_S7_BL), //# knockaway saber bottom left + ENUM2STRING(BOTH_K7_S7_B_), //# knockaway saber bottom + ENUM2STRING(BOTH_K7_S7_BR), //# knockaway saber bottom right + //SaberStaff attack knocked away + ENUM2STRING(BOTH_V7_BR_S7), //# BR attack knocked away + ENUM2STRING(BOTH_V7__R_S7), //# R attack knocked away + ENUM2STRING(BOTH_V7_TR_S7), //# TR attack knocked away + ENUM2STRING(BOTH_V7_T__S7), //# T attack knocked away + ENUM2STRING(BOTH_V7_TL_S7), //# TL attack knocked away + ENUM2STRING(BOTH_V7__L_S7), //# L attack knocked away + ENUM2STRING(BOTH_V7_BL_S7), //# BL attack knocked away + ENUM2STRING(BOTH_V7_B__S7), //# B attack knocked away + //SaberStaff parry broken + ENUM2STRING(BOTH_H7_S7_T_), //# saber knocked down from top parry + ENUM2STRING(BOTH_H7_S7_TR), //# saber knocked down-left from TR parry + ENUM2STRING(BOTH_H7_S7_TL), //# saber knocked down-right from TL parry + ENUM2STRING(BOTH_H7_S7_BL), //# saber knocked up-right from BL parry + ENUM2STRING(BOTH_H7_S7_B_), //# saber knocked up over head from ready? + ENUM2STRING(BOTH_H7_S7_BR), //# saber knocked up-left from BR parry + //Sabers locked anims + //* #sep BOTH_ SABER LOCKED ANIMS + //BOTH_(DL, S, ST)_(DL, S, ST)_(T, S)_(L, B, SB)_1(_W, _L) +//===Single locks================================================================== +//SINGLE vs. DUAL + //side locks - I'm using a single and they're using dual + ENUM2STRING(BOTH_LK_S_DL_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_DL_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_DL_S_L_1), //lock if I'm using single vs. a dual + ENUM2STRING(BOTH_LK_S_DL_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_DL_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_S_DL_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_DL_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_DL_T_L_1), //lock if I'm using single vs. a dual + ENUM2STRING(BOTH_LK_S_DL_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_DL_T_SB_1_W), //super break I won +//SINGLE vs. STAFF + //side locks + ENUM2STRING(BOTH_LK_S_ST_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_ST_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_ST_S_L_1), //lock if I'm using single vs. a staff + ENUM2STRING(BOTH_LK_S_ST_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_ST_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_S_ST_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_ST_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_ST_T_L_1), //lock if I'm using single vs. a staff + ENUM2STRING(BOTH_LK_S_ST_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_ST_T_SB_1_W), //super break I won +//SINGLE vs. SINGLE + //side locks + ENUM2STRING(BOTH_LK_S_S_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_S_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_S_S_L_1), //lock if I'm using single vs. a single and I initiated + ENUM2STRING(BOTH_LK_S_S_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_S_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_S_S_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_S_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_S_T_L_1), //lock if I'm using single vs. a single and I initiated + ENUM2STRING(BOTH_LK_S_S_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_S_T_SB_1_W), //super break I won +//===Dual Saber locks================================================================== +//DUAL vs. DUAL + //side locks + ENUM2STRING(BOTH_LK_DL_DL_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_DL_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_DL_S_L_1), //lock if I'm using dual vs. dual and I initiated + ENUM2STRING(BOTH_LK_DL_DL_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_DL_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_DL_DL_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_DL_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_DL_T_L_1), //lock if I'm using dual vs. dual and I initiated + ENUM2STRING(BOTH_LK_DL_DL_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_DL_T_SB_1_W), //super break I won +//DUAL vs. STAFF + //side locks + ENUM2STRING(BOTH_LK_DL_ST_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_ST_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_ST_S_L_1), //lock if I'm using dual vs. a staff + ENUM2STRING(BOTH_LK_DL_ST_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_ST_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_DL_ST_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_ST_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_ST_T_L_1), //lock if I'm using dual vs. a staff + ENUM2STRING(BOTH_LK_DL_ST_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_ST_T_SB_1_W), //super break I won +//DUAL vs. SINGLE + //side locks + ENUM2STRING(BOTH_LK_DL_S_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_S_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_S_S_L_1), //lock if I'm using dual vs. a single + ENUM2STRING(BOTH_LK_DL_S_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_S_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_DL_S_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_S_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_S_T_L_1), //lock if I'm using dual vs. a single + ENUM2STRING(BOTH_LK_DL_S_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_S_T_SB_1_W), //super break I won +//===Saber Staff locks================================================================== +//STAFF vs. DUAL + //side locks + ENUM2STRING(BOTH_LK_ST_DL_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_DL_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_DL_S_L_1), //lock if I'm using staff vs. dual + ENUM2STRING(BOTH_LK_ST_DL_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_DL_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_ST_DL_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_DL_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_DL_T_L_1), //lock if I'm using staff vs. dual + ENUM2STRING(BOTH_LK_ST_DL_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_DL_T_SB_1_W), //super break I won +//STAFF vs. STAFF + //side locks + ENUM2STRING(BOTH_LK_ST_ST_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_ST_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_ST_S_L_1), //lock if I'm using staff vs. a staff and I initiated + ENUM2STRING(BOTH_LK_ST_ST_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_ST_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_ST_ST_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_ST_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_ST_T_L_1), //lock if I'm using staff vs. a staff and I initiated + ENUM2STRING(BOTH_LK_ST_ST_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_ST_T_SB_1_W), //super break I won +//STAFF vs. SINGLE + //side locks + ENUM2STRING(BOTH_LK_ST_S_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_S_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_S_S_L_1), //lock if I'm using staff vs. a single + ENUM2STRING(BOTH_LK_ST_S_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_S_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_ST_S_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_S_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_S_T_L_1), //lock if I'm using staff vs. a single + ENUM2STRING(BOTH_LK_ST_S_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_S_T_SB_1_W), //super break I won +//Special cases for same saber style vs. each other (won't fit in nice 5-anim size lists above) + ENUM2STRING(BOTH_LK_S_S_S_L_2), //lock if I'm using single vs. a single and other intitiated + ENUM2STRING(BOTH_LK_S_S_T_L_2), //lock if I'm using single vs. a single and other initiated + ENUM2STRING(BOTH_LK_DL_DL_S_L_2), //lock if I'm using dual vs. dual and other initiated + ENUM2STRING(BOTH_LK_DL_DL_T_L_2), //lock if I'm using dual vs. dual and other initiated + ENUM2STRING(BOTH_LK_ST_ST_S_L_2), //lock if I'm using staff vs. a staff and other initiated + ENUM2STRING(BOTH_LK_ST_ST_T_L_2), //lock if I'm using staff vs. a staff and other initiated +//===End Saber locks================================================================== + ENUM2STRING(BOTH_BF2RETURN), //# + ENUM2STRING(BOTH_BF2BREAK), //# + ENUM2STRING(BOTH_BF2LOCK), //# + ENUM2STRING(BOTH_BF1RETURN), //# + ENUM2STRING(BOTH_BF1BREAK), //# + ENUM2STRING(BOTH_BF1LOCK), //# + ENUM2STRING(BOTH_CWCIRCLE_R2__R_S1), //# + ENUM2STRING(BOTH_CCWCIRCLE_R2__L_S1), //# + ENUM2STRING(BOTH_CWCIRCLE_A2__L__R), //# + ENUM2STRING(BOTH_CCWCIRCLE_A2__R__L), //# + ENUM2STRING(BOTH_CWCIRCLEBREAK), //# + ENUM2STRING(BOTH_CCWCIRCLEBREAK), //# + ENUM2STRING(BOTH_CWCIRCLELOCK), //# + ENUM2STRING(BOTH_CCWCIRCLELOCK), //# + //other saber anims/attacks + ENUM2STRING(BOTH_SABERFAST_STANCE), + ENUM2STRING(BOTH_SABERSLOW_STANCE), + ENUM2STRING(BOTH_SABERDUAL_STANCE), + ENUM2STRING(BOTH_SABERSTAFF_STANCE), + ENUM2STRING(BOTH_A2_STABBACK1), //# Stab saber backward + ENUM2STRING(BOTH_ATTACK_BACK), //# Swing around backwards and attack + ENUM2STRING(BOTH_JUMPFLIPSLASHDOWN1),//# + ENUM2STRING(BOTH_JUMPFLIPSTABDOWN),//# + ENUM2STRING(BOTH_FORCELEAP2_T__B_),//# + ENUM2STRING(BOTH_LUNGE2_B__T_),//# + ENUM2STRING(BOTH_CROUCHATTACKBACK1),//# + //New specials for JKA: + ENUM2STRING(BOTH_JUMPATTACK6),//# + ENUM2STRING(BOTH_JUMPATTACK7),//# + ENUM2STRING(BOTH_SPINATTACK6),//# + ENUM2STRING(BOTH_SPINATTACK7),//# + ENUM2STRING(BOTH_S1_S6),//# From stand1 to saberdual stance - turning on your dual sabers + ENUM2STRING(BOTH_S6_S1),//# From dualstaff stance to stand1 - turning off your dual sabers + ENUM2STRING(BOTH_S1_S7),//# From stand1 to saberstaff stance - turning on your saberstaff + ENUM2STRING(BOTH_S7_S1),//# From saberstaff stance to stand1 - turning off your saberstaff + ENUM2STRING(BOTH_FORCELONGLEAP_START), + ENUM2STRING(BOTH_FORCELONGLEAP_ATTACK), + ENUM2STRING(BOTH_FORCELONGLEAP_LAND), + ENUM2STRING(BOTH_FORCEWALLRUNFLIP_START), + ENUM2STRING(BOTH_FORCEWALLRUNFLIP_END), + ENUM2STRING(BOTH_FORCEWALLRUNFLIP_ALT), + ENUM2STRING(BOTH_FORCEWALLREBOUND_FORWARD), + ENUM2STRING(BOTH_FORCEWALLREBOUND_LEFT), + ENUM2STRING(BOTH_FORCEWALLREBOUND_BACK), + ENUM2STRING(BOTH_FORCEWALLREBOUND_RIGHT), + ENUM2STRING(BOTH_FORCEWALLHOLD_FORWARD), + ENUM2STRING(BOTH_FORCEWALLHOLD_LEFT), + ENUM2STRING(BOTH_FORCEWALLHOLD_BACK), + ENUM2STRING(BOTH_FORCEWALLHOLD_RIGHT), + ENUM2STRING(BOTH_FORCEWALLRELEASE_FORWARD), + ENUM2STRING(BOTH_FORCEWALLRELEASE_LEFT), + ENUM2STRING(BOTH_FORCEWALLRELEASE_BACK), + ENUM2STRING(BOTH_FORCEWALLRELEASE_RIGHT), + ENUM2STRING(BOTH_A7_KICK_F), + ENUM2STRING(BOTH_A7_KICK_B), + ENUM2STRING(BOTH_A7_KICK_R), + ENUM2STRING(BOTH_A7_KICK_L), + ENUM2STRING(BOTH_A7_KICK_S), + ENUM2STRING(BOTH_A7_KICK_BF), + ENUM2STRING(BOTH_A7_KICK_BF_STOP), + ENUM2STRING(BOTH_A7_KICK_RL), + ENUM2STRING(BOTH_A7_KICK_F_AIR), + ENUM2STRING(BOTH_A7_KICK_B_AIR), + ENUM2STRING(BOTH_A7_KICK_R_AIR), + ENUM2STRING(BOTH_A7_KICK_L_AIR), + ENUM2STRING(BOTH_FLIP_ATTACK7), + ENUM2STRING(BOTH_FLIP_HOLD7), + ENUM2STRING(BOTH_FLIP_LAND), + ENUM2STRING(BOTH_PULL_IMPALE_STAB), + ENUM2STRING(BOTH_PULL_IMPALE_SWING), + ENUM2STRING(BOTH_PULLED_INAIR_B), + ENUM2STRING(BOTH_PULLED_INAIR_F), + ENUM2STRING(BOTH_STABDOWN), + ENUM2STRING(BOTH_STABDOWN_STAFF), + ENUM2STRING(BOTH_STABDOWN_DUAL), + ENUM2STRING(BOTH_A6_SABERPROTECT), + ENUM2STRING(BOTH_A7_SOULCAL), + ENUM2STRING(BOTH_A1_SPECIAL), + ENUM2STRING(BOTH_A2_SPECIAL), + ENUM2STRING(BOTH_A3_SPECIAL), + ENUM2STRING(BOTH_ROLL_STAB), + + //# #sep ENUM2STRING(BOTH_ STANDING + ENUM2STRING(BOTH_STAND1), //# Standing idle, no weapon, hands down + ENUM2STRING(BOTH_STAND1IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND2), //# Standing idle with a saber + ENUM2STRING(BOTH_STAND2IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND2IDLE2), + ENUM2STRING(BOTH_STAND3), //# Standing idle with 2-handed weapon + ENUM2STRING(BOTH_STAND3IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND4), //# hands clasp behind back + ENUM2STRING(BOTH_STAND5), //# standing idle, no weapon, hand down, back straight + ENUM2STRING(BOTH_STAND5IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND6), //# one handed), gun at side), relaxed stand + ENUM2STRING(BOTH_STAND8), //# both hands on hips (male) + ENUM2STRING(BOTH_STAND1TO2), //# Transition from stand1 to stand2 + ENUM2STRING(BOTH_STAND2TO1), //# Transition from stand2 to stand1 + ENUM2STRING(BOTH_STAND2TO4), //# Transition from stand2 to stand4 + ENUM2STRING(BOTH_STAND4TO2), //# Transition from stand4 to stand2 + ENUM2STRING(BOTH_STAND4TOATTACK2), //# relaxed stand to 1-handed pistol ready + ENUM2STRING(BOTH_STANDUP2), //# Luke standing up from his meditation platform (cin # 37) + ENUM2STRING(BOTH_STAND5TOSIT3), //# transition from stand 5 to sit 3 + ENUM2STRING(BOTH_STAND1TOSTAND5), //# Transition from stand1 to stand5 + ENUM2STRING(BOTH_STAND5TOSTAND1), //# Transition from stand5 to stand1 + ENUM2STRING(BOTH_STAND5TOAIM), //# Transition of Kye aiming his gun at Desann (cin #9) + ENUM2STRING(BOTH_STAND5STARTLEDLOOKLEFT), //# Kyle turning to watch the bridge drop (cin #9) + ENUM2STRING(BOTH_STARTLEDLOOKLEFTTOSTAND5), //# Kyle returning to stand 5 from watching the bridge drop (cin #9) + ENUM2STRING(BOTH_STAND5TOSTAND8), //# Transition from stand5 to stand8 + ENUM2STRING(BOTH_STAND7TOSTAND8), //# Tavion putting hands on back of chair (cin #11) + ENUM2STRING(BOTH_STAND8TOSTAND5), //# Transition from stand8 to stand5 + ENUM2STRING(BOTH_STAND9), //# Kyle's standing idle, no weapon, hands down + ENUM2STRING(BOTH_STAND9IDLE1), //# Kyle's random standing idle + ENUM2STRING(BOTH_STAND5SHIFTWEIGHT), //# Weightshift from stand5 to side and back to stand5 + ENUM2STRING(BOTH_STAND5SHIFTWEIGHTSTART), //# From stand5 to side + ENUM2STRING(BOTH_STAND5SHIFTWEIGHTSTOP), //# From side to stand5 + ENUM2STRING(BOTH_STAND5TURNLEFTSTART), //# Start turning left from stand5 + ENUM2STRING(BOTH_STAND5TURNLEFTSTOP), //# Stop turning left from stand5 + ENUM2STRING(BOTH_STAND5TURNRIGHTSTART), //# Start turning right from stand5 + ENUM2STRING(BOTH_STAND5TURNRIGHTSTOP), //# Stop turning right from stand5 + ENUM2STRING(BOTH_STAND5LOOK180LEFTSTART), //# Start looking over left shoulder (cin #17) + ENUM2STRING(BOTH_STAND5LOOK180LEFTSTOP), //# Stop looking over left shoulder (cin #17) + + ENUM2STRING(BOTH_CONSOLE1START), //# typing at a console + ENUM2STRING(BOTH_CONSOLE1), //# typing at a console + ENUM2STRING(BOTH_CONSOLE1STOP), //# typing at a console + ENUM2STRING(BOTH_CONSOLE2START), //# typing at a console with comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2), //# typing at a console with comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2STOP), //# typing at a console with comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2HOLDCOMSTART), //# lean in to type at console while holding comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2HOLDCOMSTOP), //# lean away after typing at console while holding comm link in hand (cin #5) + + ENUM2STRING(BOTH_GUARD_LOOKAROUND1), //# Cradling weapon and looking around + ENUM2STRING(BOTH_GUARD_IDLE1), //# Cradling weapon and standing + ENUM2STRING(BOTH_GESTURE1), //# Generic gesture), non-specific + ENUM2STRING(BOTH_GESTURE2), //# Generic gesture), non-specific + ENUM2STRING(BOTH_WALK1TALKCOMM1), //# Talking into coom link while walking + ENUM2STRING(BOTH_TALK1), //# Generic talk anim + ENUM2STRING(BOTH_TALK2), //# Generic talk anim + ENUM2STRING(BOTH_TALKCOMM1START), //# Start talking into a comm link + ENUM2STRING(BOTH_TALKCOMM1), //# Talking into a comm link + ENUM2STRING(BOTH_TALKCOMM1STOP), //# Stop talking into a comm link + ENUM2STRING(BOTH_TALKGESTURE1), //# Generic talk anim + + ENUM2STRING(BOTH_HEADTILTLSTART), //# Head tilt to left + ENUM2STRING(BOTH_HEADTILTLSTOP), //# Head tilt to left + ENUM2STRING(BOTH_HEADTILTRSTART), //# Head tilt to right + ENUM2STRING(BOTH_HEADTILTRSTOP), //# Head tilt to right + ENUM2STRING(BOTH_HEADNOD), //# Head shake YES + ENUM2STRING(BOTH_HEADSHAKE), //# Head shake NO + ENUM2STRING(BOTH_SIT2HEADTILTLSTART), //# Head tilt to left from seated position 2 + ENUM2STRING(BOTH_SIT2HEADTILTLSTOP), //# Head tilt to left from seated position 2 + + ENUM2STRING(BOTH_REACH1START), //# Monmothma reaching for crystal + ENUM2STRING(BOTH_REACH1STOP), //# Monmothma reaching for crystal + + ENUM2STRING(BOTH_COME_ON1), //# Jan gesturing to Kyle (cin #32a) + ENUM2STRING(BOTH_STEADYSELF1), //# Jan trying to keep footing (cin #32a) Kyle (cin#5) + ENUM2STRING(BOTH_STEADYSELF1END), //# Return hands to side from STEADSELF1 Kyle (cin#5) + ENUM2STRING(BOTH_SILENCEGESTURE1), //# Luke silencing Kyle with a raised hand (cin #37) + ENUM2STRING(BOTH_REACHFORSABER1), //# Luke holding hand out for Kyle's saber (cin #37) + ENUM2STRING(BOTH_SABERKILLER1), //# Tavion about to strike Jan with saber (cin #9) + ENUM2STRING(BOTH_SABERKILLEE1), //# Jan about to be struck by Tavion with saber (cin #9) + ENUM2STRING(BOTH_HUGGER1), //# Kyle hugging Jan (cin #29) + ENUM2STRING(BOTH_HUGGERSTOP1), //# Kyle stop hugging Jan but don't let her go (cin #29) + ENUM2STRING(BOTH_HUGGEE1), //# Jan being hugged (cin #29) + ENUM2STRING(BOTH_HUGGEESTOP1), //# Jan stop being hugged but don't let go (cin #29) + + ENUM2STRING(BOTH_SABERTHROW1START), //# Desann throwing his light saber (cin #26) + ENUM2STRING(BOTH_SABERTHROW1STOP), //# Desann throwing his light saber (cin #26) + ENUM2STRING(BOTH_SABERTHROW2START), //# Kyle throwing his light saber (cin #32) + ENUM2STRING(BOTH_SABERTHROW2STOP), //# Kyle throwing his light saber (cin #32) + + //# #sep ENUM2STRING(BOTH_ SITTING/CROUCHING + ENUM2STRING(BOTH_SIT1), //# Normal chair sit. + ENUM2STRING(BOTH_SIT2), //# Lotus position. + ENUM2STRING(BOTH_SIT3), //# Sitting in tired position), elbows on knees + + ENUM2STRING(BOTH_SIT2TOSTAND5), //# Transition from sit 2 to stand 5 + ENUM2STRING(BOTH_STAND5TOSIT2), //# Transition from stand 5 to sit 2 + ENUM2STRING(BOTH_SIT2TOSIT4), //# Trans from sit2 to sit4 (cin #12) Luke leaning back from lotus position. + ENUM2STRING(BOTH_SIT3TOSTAND5), //# transition from sit 3 to stand 5 + + ENUM2STRING(BOTH_CROUCH1), //# Transition from standing to crouch + ENUM2STRING(BOTH_CROUCH1IDLE), //# Crouching idle + ENUM2STRING(BOTH_CROUCH1WALK), //# Walking while crouched + ENUM2STRING(BOTH_CROUCH1WALKBACK), //# Walking while crouched + ENUM2STRING(BOTH_UNCROUCH1), //# Transition from crouch to standing + ENUM2STRING(BOTH_CROUCH2TOSTAND1), //# going from crouch2 to stand1 + ENUM2STRING(BOTH_CROUCH3), //# Desann crouching down to Kyle (cin 9) + ENUM2STRING(BOTH_UNCROUCH3), //# Desann uncrouching down to Kyle (cin 9) + ENUM2STRING(BOTH_CROUCH4), //# Slower version of crouch1 for cinematics + ENUM2STRING(BOTH_UNCROUCH4), //# Slower version of uncrouch1 for cinematics + + ENUM2STRING(BOTH_GUNSIT1), //# sitting on an emplaced gun. + + // Swoop Vehicle animations. + //* #sep BOTH_ SWOOP ANIMS + ENUM2STRING(BOTH_VS_MOUNT_L), //# Mount from left + ENUM2STRING(BOTH_VS_DISMOUNT_L), //# Dismount to left + ENUM2STRING(BOTH_VS_MOUNT_R), //# Mount from right (symmetry) + ENUM2STRING(BOTH_VS_DISMOUNT_R), //# Dismount to right (symmetry) + + ENUM2STRING(BOTH_VS_MOUNTJUMP_L), //# + ENUM2STRING(BOTH_VS_MOUNTTHROW), //# Land on an occupied vehicle & throw off current pilot + ENUM2STRING(BOTH_VS_MOUNTTHROW_L), //# Land on an occupied vehicle & throw off current pilot + ENUM2STRING(BOTH_VS_MOUNTTHROW_R), //# Land on an occupied vehicle & throw off current pilot + ENUM2STRING(BOTH_VS_MOUNTTHROWEE), //# Current pilot getting thrown off by another guy + + ENUM2STRING(BOTH_VS_LOOKLEFT), //# Turn & Look behind and to the left (no weapon) + ENUM2STRING(BOTH_VS_LOOKRIGHT), //# Turn & Look behind and to the right (no weapon) + + ENUM2STRING(BOTH_VS_TURBO), //# Hit The Turbo Button + + ENUM2STRING(BOTH_VS_REV), //# Player looks back as swoop reverses + + ENUM2STRING(BOTH_VS_AIR), //# Player stands up when swoop is airborn + ENUM2STRING(BOTH_VS_AIR_G), //# "" with Gun + ENUM2STRING(BOTH_VS_AIR_SL), //# "" with Saber Left + ENUM2STRING(BOTH_VS_AIR_SR), //# "" with Saber Right + + ENUM2STRING(BOTH_VS_LAND), //# Player bounces down when swoop lands + ENUM2STRING(BOTH_VS_LAND_G), //# "" with Gun + ENUM2STRING(BOTH_VS_LAND_SL), //# "" with Saber Left + ENUM2STRING(BOTH_VS_LAND_SR), //# "" with Saber Right + + ENUM2STRING(BOTH_VS_IDLE), //# Sit + ENUM2STRING(BOTH_VS_IDLE_G), //# Sit (gun) + ENUM2STRING(BOTH_VS_IDLE_SL), //# Sit (saber left) + ENUM2STRING(BOTH_VS_IDLE_SR), //# Sit (saber right) + + ENUM2STRING(BOTH_VS_LEANL), //# Lean left + ENUM2STRING(BOTH_VS_LEANL_G), //# Lean left (gun) + ENUM2STRING(BOTH_VS_LEANL_SL), //# Lean left (saber left) + ENUM2STRING(BOTH_VS_LEANL_SR), //# Lean left (saber right) + + ENUM2STRING(BOTH_VS_LEANR), //# Lean right + ENUM2STRING(BOTH_VS_LEANR_G), //# Lean right (gun) + ENUM2STRING(BOTH_VS_LEANR_SL), //# Lean right (saber left) + ENUM2STRING(BOTH_VS_LEANR_SR), //# Lean right (saber right) + + ENUM2STRING(BOTH_VS_ATL_S), //# Attack left with saber + ENUM2STRING(BOTH_VS_ATR_S), //# Attack right with saber + ENUM2STRING(BOTH_VS_ATR_TO_L_S), //# Attack toss saber from right to left hand + ENUM2STRING(BOTH_VS_ATL_TO_R_S), //# Attack toss saber from left to right hand + ENUM2STRING(BOTH_VS_ATR_G), //# Attack right with gun (90) + ENUM2STRING(BOTH_VS_ATL_G), //# Attack left with gun (90) + ENUM2STRING(BOTH_VS_ATF_G), //# Attack forward with gun + + ENUM2STRING(BOTH_VS_PAIN1), //# Pain + + // Added 12/04/02 by Aurelio. + //* #sep BOTH_ TAUNTAUN ANIMS + ENUM2STRING(BOTH_VT_MOUNT_L), //# Mount from left + ENUM2STRING(BOTH_VT_MOUNT_R), //# Mount from right + ENUM2STRING(BOTH_VT_MOUNT_B), //# Mount from air, behind + ENUM2STRING(BOTH_VT_DISMOUNT), //# Dismount for tauntaun + ENUM2STRING(BOTH_VT_DISMOUNT_L), //# Dismount to tauntauns left + ENUM2STRING(BOTH_VT_DISMOUNT_R), //# Dismount to tauntauns right (symmetry) + + ENUM2STRING(BOTH_VT_WALK_FWD), //# Walk forward + ENUM2STRING(BOTH_VT_WALK_REV), //# Walk backward + ENUM2STRING(BOTH_VT_WALK_FWD_L), //# walk lean left + ENUM2STRING(BOTH_VT_WALK_FWD_R), //# walk lean right + ENUM2STRING(BOTH_VT_RUN_FWD), //# Run forward + ENUM2STRING(BOTH_VT_RUN_REV), //# Look backwards while running (not weapon specific) + ENUM2STRING(BOTH_VT_RUN_FWD_L), //# run lean left + ENUM2STRING(BOTH_VT_RUN_FWD_R), //# run lean right + + ENUM2STRING(BOTH_VT_SLIDEF), //# Tauntaun slides forward with abrupt stop + ENUM2STRING(BOTH_VT_AIR), //# Tauntaun jump + ENUM2STRING(BOTH_VT_ATB), //# Tauntaun tail swipe + ENUM2STRING(BOTH_VT_PAIN1), //# Pain + ENUM2STRING(BOTH_VT_DEATH1), //# Die + ENUM2STRING(BOTH_VT_STAND), //# Stand still and breath + ENUM2STRING(BOTH_VT_BUCK), //# Tauntaun bucking loop animation + + ENUM2STRING(BOTH_VT_LAND), //# Player bounces down when tauntaun lands + ENUM2STRING(BOTH_VT_TURBO), //# Hit The Turbo Button + ENUM2STRING(BOTH_VT_IDLE_SL), //# Sit (saber left) + ENUM2STRING(BOTH_VT_IDLE_SR), //# Sit (saber right) + ENUM2STRING(BOTH_VT_IDLE), //# Sit with no weapon selected + ENUM2STRING(BOTH_VT_IDLE1), //# Sit with no weapon selected + ENUM2STRING(BOTH_VT_IDLE_S), //# Sit with saber selected + ENUM2STRING(BOTH_VT_IDLE_G), //# Sit with gun selected + ENUM2STRING(BOTH_VT_IDLE_T), //# Sit with thermal grenade selected + + ENUM2STRING(BOTH_VT_ATL_S), //# Attack left with saber + ENUM2STRING(BOTH_VT_ATR_S), //# Attack right with saber + ENUM2STRING(BOTH_VT_ATR_TO_L_S), //# Attack toss saber from right to left hand + ENUM2STRING(BOTH_VT_ATL_TO_R_S), //# Attack toss saber from left to right hand + ENUM2STRING(BOTH_VT_ATR_G), //# Attack right with gun (90) + ENUM2STRING(BOTH_VT_ATL_G), //# Attack left with gun (90) + ENUM2STRING(BOTH_VT_ATF_G), //# Attack forward with gun + + + // Added 2/26/02 by Aurelio. + //* #sep BOTH_ FIGHTER ANIMS + ENUM2STRING( BOTH_GEARS_OPEN ), + ENUM2STRING( BOTH_GEARS_CLOSE ), + ENUM2STRING( BOTH_WINGS_OPEN ), + ENUM2STRING( BOTH_WINGS_CLOSE ), + + /////////////////////////////////// + + ENUM2STRING(BOTH_DEATH14_UNGRIP), //# Desann's end death (cin #35) + ENUM2STRING(BOTH_DEATH14_SITUP), //# Tavion sitting up after having been thrown (cin #23) + ENUM2STRING(BOTH_KNEES1), //# Tavion on her knees + ENUM2STRING(BOTH_KNEES2), //# Tavion on her knees looking down + ENUM2STRING(BOTH_KNEES2TO1), //# Transition of KNEES2 to KNEES1 + + //# #sep ENUM2STRING(BOTH_ MOVING + ENUM2STRING(BOTH_WALK1), //# Normal walk + ENUM2STRING(BOTH_WALK2), //# Normal walk + ENUM2STRING(BOTH_WALK_STAFF), //# Walk with saberstaff turned on + ENUM2STRING(BOTH_WALKBACK_STAFF), //# Walk backwards with saberstaff turned on + ENUM2STRING(BOTH_WALK_DUAL), //# Walk with dual turned on + ENUM2STRING(BOTH_WALKBACK_DUAL), //# Walk backwards with dual turned on + ENUM2STRING(BOTH_WALK5), //# Tavion taunting Kyle (cin 22) + ENUM2STRING(BOTH_WALK6), //# Slow walk for Luke (cin 12) + ENUM2STRING(BOTH_WALK7), //# Fast walk + ENUM2STRING(BOTH_RUN1), //# Full run + ENUM2STRING(BOTH_RUN1START), //# Start into full run1 + ENUM2STRING(BOTH_RUN1STOP), //# Stop from full run1 + ENUM2STRING(BOTH_RUN2), //# Full run + ENUM2STRING(BOTH_RUN1TORUN2), //# Wampa run anim transition + ENUM2STRING(BOTH_RUN2TORUN1), //# Wampa run anim transition + ENUM2STRING(BOTH_RUN4), //# Jawa run + ENUM2STRING(BOTH_RUN_STAFF), //# Run with saberstaff turned on + ENUM2STRING(BOTH_RUNBACK_STAFF), //# Run backwards with saberstaff turned on + ENUM2STRING(BOTH_RUN_DUAL), //# Run with dual turned on + ENUM2STRING(BOTH_RUNBACK_DUAL), //# Run backwards with dual turned on + ENUM2STRING(BOTH_STRAFE_LEFT1), //# Sidestep left), should loop + ENUM2STRING(BOTH_STRAFE_RIGHT1), //# Sidestep right), should loop + ENUM2STRING(BOTH_RUNSTRAFE_LEFT1), //# Sidestep left), should loop + ENUM2STRING(BOTH_RUNSTRAFE_RIGHT1), //# Sidestep right), should loop + ENUM2STRING(BOTH_TURN_LEFT1), //# Turn left), should loop + ENUM2STRING(BOTH_TURN_RIGHT1), //# Turn right), should loop + ENUM2STRING(BOTH_TURNSTAND1), //# Turn from STAND1 position + ENUM2STRING(BOTH_TURNSTAND2), //# Turn from STAND2 position + ENUM2STRING(BOTH_TURNSTAND3), //# Turn from STAND3 position + ENUM2STRING(BOTH_TURNSTAND4), //# Turn from STAND4 position + ENUM2STRING(BOTH_TURNSTAND5), //# Turn from STAND5 position + ENUM2STRING(BOTH_TURNCROUCH1), //# Turn from CROUCH1 position + + ENUM2STRING(BOTH_WALKBACK1), //# Walk1 backwards + ENUM2STRING(BOTH_WALKBACK2), //# Walk2 backwards + ENUM2STRING(BOTH_RUNBACK1), //# Run1 backwards + ENUM2STRING(BOTH_RUNBACK2), //# Run1 backwards + + //# #sep BOTH_ JUMPING + ENUM2STRING(BOTH_JUMP1), //# Jump - wind-up and leave ground + ENUM2STRING(BOTH_INAIR1), //# In air loop (from jump) + ENUM2STRING(BOTH_LAND1), //# Landing (from in air loop) + ENUM2STRING(BOTH_LAND2), //# Landing Hard (from a great height) + + ENUM2STRING(BOTH_JUMPBACK1), //# Jump backwards - wind-up and leave ground + ENUM2STRING(BOTH_INAIRBACK1), //# In air loop (from jump back) + ENUM2STRING(BOTH_LANDBACK1), //# Landing backwards(from in air loop) + + ENUM2STRING(BOTH_JUMPLEFT1), //# Jump left - wind-up and leave ground + ENUM2STRING(BOTH_INAIRLEFT1), //# In air loop (from jump left) + ENUM2STRING(BOTH_LANDLEFT1), //# Landing left(from in air loop) + + ENUM2STRING(BOTH_JUMPRIGHT1), //# Jump right - wind-up and leave ground + ENUM2STRING(BOTH_INAIRRIGHT1), //# In air loop (from jump right) + ENUM2STRING(BOTH_LANDRIGHT1), //# Landing right(from in air loop) + + ENUM2STRING(BOTH_FORCEJUMP1), //# Jump - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIR1), //# In air loop (from jump) + ENUM2STRING(BOTH_FORCELAND1), //# Landing (from in air loop) + + ENUM2STRING(BOTH_FORCEJUMPBACK1), //# Jump backwards - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIRBACK1), //# In air loop (from jump back) + ENUM2STRING(BOTH_FORCELANDBACK1), //# Landing backwards(from in air loop) + + ENUM2STRING(BOTH_FORCEJUMPLEFT1), //# Jump left - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIRLEFT1), //# In air loop (from jump left) + ENUM2STRING(BOTH_FORCELANDLEFT1), //# Landing left(from in air loop) + + ENUM2STRING(BOTH_FORCEJUMPRIGHT1), //# Jump right - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIRRIGHT1), //# In air loop (from jump right) + ENUM2STRING(BOTH_FORCELANDRIGHT1), //# Landing right(from in air loop) + //# #sep BOTH_ ACROBATICS + ENUM2STRING(BOTH_FLIP_F), //# Flip forward + ENUM2STRING(BOTH_FLIP_B), //# Flip backwards + ENUM2STRING(BOTH_FLIP_L), //# Flip left + ENUM2STRING(BOTH_FLIP_R), //# Flip right + + ENUM2STRING(BOTH_ROLL_F), //# Roll forward + ENUM2STRING(BOTH_ROLL_B), //# Roll backward + ENUM2STRING(BOTH_ROLL_L), //# Roll left + ENUM2STRING(BOTH_ROLL_R), //# Roll right + + ENUM2STRING(BOTH_HOP_F), //# quickstep forward + ENUM2STRING(BOTH_HOP_B), //# quickstep backwards + ENUM2STRING(BOTH_HOP_L), //# quickstep left + ENUM2STRING(BOTH_HOP_R), //# quickstep right + + ENUM2STRING(BOTH_DODGE_FL), //# lean-dodge forward left + ENUM2STRING(BOTH_DODGE_FR), //# lean-dodge forward right + ENUM2STRING(BOTH_DODGE_BL), //# lean-dodge backwards left + ENUM2STRING(BOTH_DODGE_BR), //# lean-dodge backwards right + ENUM2STRING(BOTH_DODGE_L), //# lean-dodge left + ENUM2STRING(BOTH_DODGE_R), //# lean-dodge right + ENUM2STRING(BOTH_DODGE_HOLD_FL), //# lean-dodge pose forward left + ENUM2STRING(BOTH_DODGE_HOLD_FR), //# lean-dodge pose forward right + ENUM2STRING(BOTH_DODGE_HOLD_BL), //# lean-dodge pose backwards left + ENUM2STRING(BOTH_DODGE_HOLD_BR), //# lean-dodge pose backwards right + ENUM2STRING(BOTH_DODGE_HOLD_L), //# lean-dodge pose left + ENUM2STRING(BOTH_DODGE_HOLD_R), //# lean-dodge pose right + + //MP taunt anims + ENUM2STRING(BOTH_ENGAGETAUNT), + ENUM2STRING(BOTH_BOW), + ENUM2STRING(BOTH_MEDITATE), + ENUM2STRING(BOTH_MEDITATE_END), + ENUM2STRING(BOTH_SHOWOFF_FAST), + ENUM2STRING(BOTH_SHOWOFF_MEDIUM), + ENUM2STRING(BOTH_SHOWOFF_STRONG), + ENUM2STRING(BOTH_SHOWOFF_DUAL), + ENUM2STRING(BOTH_SHOWOFF_STAFF), + ENUM2STRING(BOTH_VICTORY_FAST), + ENUM2STRING(BOTH_VICTORY_MEDIUM), + ENUM2STRING(BOTH_VICTORY_STRONG), + ENUM2STRING(BOTH_VICTORY_DUAL), + ENUM2STRING(BOTH_VICTORY_STAFF), + //other saber/acro anims + ENUM2STRING(BOTH_ARIAL_LEFT), //# + ENUM2STRING(BOTH_ARIAL_RIGHT), //# + ENUM2STRING(BOTH_CARTWHEEL_LEFT), //# + ENUM2STRING(BOTH_CARTWHEEL_RIGHT), //# + ENUM2STRING(BOTH_FLIP_LEFT), //# + ENUM2STRING(BOTH_FLIP_BACK1), //# + ENUM2STRING(BOTH_FLIP_BACK2), //# + ENUM2STRING(BOTH_FLIP_BACK3), //# + ENUM2STRING(BOTH_BUTTERFLY_LEFT), //# + ENUM2STRING(BOTH_BUTTERFLY_RIGHT), //# + ENUM2STRING(BOTH_WALL_RUN_RIGHT), //# + ENUM2STRING(BOTH_WALL_RUN_RIGHT_FLIP),//# + ENUM2STRING(BOTH_WALL_RUN_RIGHT_STOP),//# + ENUM2STRING(BOTH_WALL_RUN_LEFT), //# + ENUM2STRING(BOTH_WALL_RUN_LEFT_FLIP),//# + ENUM2STRING(BOTH_WALL_RUN_LEFT_STOP),//# + ENUM2STRING(BOTH_WALL_FLIP_RIGHT), //# + ENUM2STRING(BOTH_WALL_FLIP_LEFT), //# + ENUM2STRING(BOTH_KNOCKDOWN1), //# knocked backwards + ENUM2STRING(BOTH_KNOCKDOWN2), //# knocked backwards hard + ENUM2STRING(BOTH_KNOCKDOWN3), //# knocked forwards + ENUM2STRING(BOTH_KNOCKDOWN4), //# knocked backwards from crouch + ENUM2STRING(BOTH_KNOCKDOWN5), //# dupe of 3 - will be removed + ENUM2STRING(BOTH_GETUP1), //# + ENUM2STRING(BOTH_GETUP2), //# + ENUM2STRING(BOTH_GETUP3), //# + ENUM2STRING(BOTH_GETUP4), //# + ENUM2STRING(BOTH_GETUP5), //# + ENUM2STRING(BOTH_GETUP_CROUCH_F1), //# + ENUM2STRING(BOTH_GETUP_CROUCH_B1), //# + ENUM2STRING(BOTH_FORCE_GETUP_F1), //# + ENUM2STRING(BOTH_FORCE_GETUP_F2), //# + ENUM2STRING(BOTH_FORCE_GETUP_B1), //# + ENUM2STRING(BOTH_FORCE_GETUP_B2), //# + ENUM2STRING(BOTH_FORCE_GETUP_B3), //# + ENUM2STRING(BOTH_FORCE_GETUP_B4), //# + ENUM2STRING(BOTH_FORCE_GETUP_B5), //# + ENUM2STRING(BOTH_FORCE_GETUP_B6), //# + ENUM2STRING(BOTH_GETUP_BROLL_B), //# + ENUM2STRING(BOTH_GETUP_BROLL_F), //# + ENUM2STRING(BOTH_GETUP_BROLL_L), //# + ENUM2STRING(BOTH_GETUP_BROLL_R), //# + ENUM2STRING(BOTH_GETUP_FROLL_B), //# + ENUM2STRING(BOTH_GETUP_FROLL_F), //# + ENUM2STRING(BOTH_GETUP_FROLL_L), //# + ENUM2STRING(BOTH_GETUP_FROLL_R), //# + ENUM2STRING(BOTH_WALL_FLIP_BACK1), //# + ENUM2STRING(BOTH_WALL_FLIP_BACK2), //# + ENUM2STRING(BOTH_SPIN1), //# + ENUM2STRING(BOTH_CEILING_CLING), //# clinging to ceiling + ENUM2STRING(BOTH_CEILING_DROP), //# dropping from ceiling cling + + //TESTING + ENUM2STRING(BOTH_FJSS_TR_BL), //# jump spin slash tr to bl + ENUM2STRING(BOTH_FJSS_TL_BR), //# jump spin slash bl to tr + ENUM2STRING(BOTH_RIGHTHANDCHOPPEDOFF),//# + ENUM2STRING(BOTH_DEFLECTSLASH__R__L_FIN),//# + ENUM2STRING(BOTH_BASHED1),//# + ENUM2STRING(BOTH_ARIAL_F1),//# + ENUM2STRING(BOTH_BUTTERFLY_FR1),//# + ENUM2STRING(BOTH_BUTTERFLY_FL1),//# + + //NEW SABER/JEDI/FORCE ANIMS + ENUM2STRING(BOTH_BACK_FLIP_UP), //# back flip up Bonus Animation!!!! + ENUM2STRING(BOTH_LOSE_SABER), //# player losing saber (pulled from hand by force pull 4 - Kyle?) + ENUM2STRING(BOTH_STAFF_TAUNT), //# taunt saberstaff + ENUM2STRING(BOTH_DUAL_TAUNT), //# taunt dual + ENUM2STRING(BOTH_A6_FB), //# dual attack front/back + ENUM2STRING(BOTH_A6_LR), //# dual attack left/right + ENUM2STRING(BOTH_A7_HILT), //# saber knock (alt + stand still) + //Alora + ENUM2STRING(BOTH_ALORA_SPIN), //#jump spin attack death ballet + ENUM2STRING(BOTH_ALORA_FLIP_1), //# gymnast move 1 + ENUM2STRING(BOTH_ALORA_FLIP_2), //# gymnast move 2 + ENUM2STRING(BOTH_ALORA_FLIP_3), //# gymnast move3 + ENUM2STRING(BOTH_ALORA_FLIP_B), //# gymnast move back + ENUM2STRING(BOTH_ALORA_SPIN_THROW), //# dual saber throw + ENUM2STRING(BOTH_ALORA_SPIN_SLASH), //# spin slash special bonus animation!! :) + ENUM2STRING(BOTH_ALORA_TAUNT), //# special taunt + //Rosh (Kothos battle) + ENUM2STRING(BOTH_ROSH_PAIN), //# hurt animation (exhausted) + ENUM2STRING(BOTH_ROSH_HEAL), //# healed/rejuvenated + //Tavion + ENUM2STRING(BOTH_TAVION_SCEPTERGROUND), //# stabbing ground with sith sword shoots electricity everywhere + ENUM2STRING(BOTH_TAVION_SWORDPOWER),//# Tavion doing the He-Man(tm) thing + ENUM2STRING(BOTH_SCEPTER_START), //#Point scepter and attack start + ENUM2STRING(BOTH_SCEPTER_HOLD), //#Point scepter and attack hold + ENUM2STRING(BOTH_SCEPTER_STOP), //#Point scepter and attack stop + //Kyle Boss + ENUM2STRING(BOTH_KYLE_GRAB), //# grab + ENUM2STRING(BOTH_KYLE_MISS), //# miss + ENUM2STRING(BOTH_KYLE_PA_1), //# hold 1 + ENUM2STRING(BOTH_PLAYER_PA_1), //# player getting held 1 + ENUM2STRING(BOTH_KYLE_PA_2), //# hold 2 + ENUM2STRING(BOTH_PLAYER_PA_2), //# player getting held 2 + ENUM2STRING(BOTH_PLAYER_PA_FLY), //# player getting knocked back from punch at end of hold 1 + ENUM2STRING(BOTH_KYLE_PA_3), //# hold 3 + ENUM2STRING(BOTH_PLAYER_PA_3), //# player getting held 3 + ENUM2STRING(BOTH_PLAYER_PA_3_FLY),//# player getting thrown at end of hold 3 + //Rancor + ENUM2STRING(BOTH_BUCK_RIDER), //# Rancor bucks when someone is on him + //WAMPA Grabbing enemy + ENUM2STRING(BOTH_HOLD_START), //# + ENUM2STRING(BOTH_HOLD_MISS), //# + ENUM2STRING(BOTH_HOLD_IDLE), //# + ENUM2STRING(BOTH_HOLD_END), //# + ENUM2STRING(BOTH_HOLD_ATTACK), //# + ENUM2STRING(BOTH_HOLD_SNIFF), //# Sniff the guy you're holding + ENUM2STRING(BOTH_HOLD_DROP), //# just drop 'em + //BEING GRABBED BY WAMPA + ENUM2STRING(BOTH_GRABBED), //# + ENUM2STRING(BOTH_RELEASED), //# + ENUM2STRING(BOTH_HANG_IDLE), //# + ENUM2STRING(BOTH_HANG_ATTACK), //# + ENUM2STRING(BOTH_HANG_PAIN), //# + + //# #sep BOTH_ MISC MOVEMENT + ENUM2STRING(BOTH_HIT1), //# Kyle hit by crate in cin #9 + ENUM2STRING(BOTH_LADDER_UP1), //# Climbing up a ladder with rungs at 16 unit intervals + ENUM2STRING(BOTH_LADDER_DWN1), //# Climbing down a ladder with rungs at 16 unit intervals + ENUM2STRING(BOTH_LADDER_IDLE), //# Just sitting on the ladder + + //# #sep ENUM2STRING(BOTH_ FLYING IDLE + ENUM2STRING(BOTH_FLY_SHIELDED), //# For sentry droid, shields in + + //# #sep BOTH_ SWIMMING + ENUM2STRING(BOTH_SWIM_IDLE1), //# Swimming Idle 1 + ENUM2STRING(BOTH_SWIMFORWARD), //# Swim forward loop + ENUM2STRING(BOTH_SWIMBACKWARD), //# Swim backward loop + + //# #sep ENUM2STRING(BOTH_ LYING + ENUM2STRING(BOTH_SLEEP1), //# laying on back-rknee up-rhand on torso + ENUM2STRING(BOTH_SLEEP6START), //# Kyle leaning back to sleep (cin 20) + ENUM2STRING(BOTH_SLEEP6STOP), //# Kyle waking up and shaking his head (cin 21) + ENUM2STRING(BOTH_SLEEP1GETUP), //# alarmed and getting up out of sleep1 pose to stand + ENUM2STRING(BOTH_SLEEP1GETUP2), //# + + ENUM2STRING(BOTH_CHOKE1START), //# tavion in force grip choke + ENUM2STRING(BOTH_CHOKE1STARTHOLD), //# loop of tavion in force grip choke + ENUM2STRING(BOTH_CHOKE1), //# tavion in force grip choke + + ENUM2STRING(BOTH_CHOKE2), //# tavion recovering from force grip choke + ENUM2STRING(BOTH_CHOKE3), //# left-handed choke (for people still holding a weapon) + + //# #sep ENUM2STRING(BOTH_ HUNTER-SEEKER BOT-SPECIFIC + ENUM2STRING(BOTH_POWERUP1), //# Wakes up + + ENUM2STRING(BOTH_TURNON), //# Protocol Droid wakes up + ENUM2STRING(BOTH_TURNOFF), //# Protocol Droid shuts off + ENUM2STRING(BOTH_BUTTON1), //# Single button push with right hand + ENUM2STRING(BOTH_BUTTON2), //# Single button push with left finger + ENUM2STRING(BOTH_BUTTON_HOLD), //# Single button hold with left hand + ENUM2STRING(BOTH_BUTTON_RELEASE), //# Single button release with left hand + + //# JEDI-SPECIFIC + //# #sep BOTH_ FORCE ANIMS + ENUM2STRING(BOTH_RESISTPUSH), //# plant yourself to resist force push/pulls. + ENUM2STRING(BOTH_FORCEPUSH), //# Use off-hand to do force power. + ENUM2STRING(BOTH_FORCEPULL), //# Use off-hand to do force power. + ENUM2STRING(BOTH_MINDTRICK1), //# Use off-hand to do mind trick + ENUM2STRING(BOTH_MINDTRICK2), //# Use off-hand to do distraction + ENUM2STRING(BOTH_FORCELIGHTNING), //# Use off-hand to do lightning + ENUM2STRING(BOTH_FORCELIGHTNING_START), //# Use off-hand to do lightning - start + ENUM2STRING(BOTH_FORCELIGHTNING_HOLD), //# Use off-hand to do lightning - hold + ENUM2STRING(BOTH_FORCELIGHTNING_RELEASE),//# Use off-hand to do lightning - release + ENUM2STRING(BOTH_FORCEHEAL_START), //# Healing meditation pose start + ENUM2STRING(BOTH_FORCEHEAL_STOP), //# Healing meditation pose end + ENUM2STRING(BOTH_FORCEHEAL_QUICK), //# Healing meditation gesture + ENUM2STRING(BOTH_SABERPULL), //# Use off-hand to do force power. + ENUM2STRING(BOTH_FORCEGRIP1), //# force-gripping (no anim?) + ENUM2STRING(BOTH_FORCEGRIP3), //# force-gripping (right-hand) + ENUM2STRING(BOTH_FORCEGRIP3THROW), //# throwing while force-gripping (right hand) + ENUM2STRING(BOTH_FORCEGRIP_HOLD), //# Use off-hand to do grip - hold + ENUM2STRING(BOTH_FORCEGRIP_RELEASE),//# Use off-hand to do grip - release + ENUM2STRING(BOTH_TOSS1), //# throwing to left after force gripping + ENUM2STRING(BOTH_TOSS2), //# throwing to right after force gripping + //NEW force anims for JKA: + ENUM2STRING(BOTH_FORCE_RAGE), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING_START), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING_HOLD), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING_RELEASE), + ENUM2STRING(BOTH_FORCE_DRAIN), + ENUM2STRING(BOTH_FORCE_DRAIN_START), + ENUM2STRING(BOTH_FORCE_DRAIN_HOLD), + ENUM2STRING(BOTH_FORCE_DRAIN_RELEASE), + ENUM2STRING(BOTH_FORCE_DRAIN_GRAB_START), + ENUM2STRING(BOTH_FORCE_DRAIN_GRAB_HOLD), + ENUM2STRING(BOTH_FORCE_DRAIN_GRAB_END), + ENUM2STRING(BOTH_FORCE_DRAIN_GRABBED), + ENUM2STRING(BOTH_FORCE_ABSORB), + ENUM2STRING(BOTH_FORCE_ABSORB_START), + ENUM2STRING(BOTH_FORCE_ABSORB_END), + ENUM2STRING(BOTH_FORCE_PROTECT), + ENUM2STRING(BOTH_FORCE_PROTECT_FAST), + + ENUM2STRING(BOTH_WIND), + + ENUM2STRING(BOTH_STAND_TO_KNEEL), + ENUM2STRING(BOTH_KNEEL_TO_STAND), + + ENUM2STRING(BOTH_TUSKENATTACK1), + ENUM2STRING(BOTH_TUSKENATTACK2), + ENUM2STRING(BOTH_TUSKENATTACK3), + ENUM2STRING(BOTH_TUSKENLUNGE1), + ENUM2STRING(BOTH_TUSKENTAUNT1), + + ENUM2STRING(BOTH_COWER1_START), //# cower start + ENUM2STRING(BOTH_COWER1), //# cower loop + ENUM2STRING(BOTH_COWER1_STOP), //# cower stop + ENUM2STRING(BOTH_SONICPAIN_START), + ENUM2STRING(BOTH_SONICPAIN_HOLD), + ENUM2STRING(BOTH_SONICPAIN_END), + + //new anim slots per Jarrod's request + ENUM2STRING(BOTH_STAND10), + ENUM2STRING(BOTH_STAND10_TALK1), + ENUM2STRING(BOTH_STAND10_TALK2), + ENUM2STRING(BOTH_STAND10TOSTAND1), + + ENUM2STRING(BOTH_STAND1_TALK1), + ENUM2STRING(BOTH_STAND1_TALK2), + ENUM2STRING(BOTH_STAND1_TALK3), + + ENUM2STRING(BOTH_SIT4), + ENUM2STRING(BOTH_SIT5), + ENUM2STRING(BOTH_SIT5_TALK1), + ENUM2STRING(BOTH_SIT5_TALK2), + ENUM2STRING(BOTH_SIT5_TALK3), + + ENUM2STRING(BOTH_SIT6), + ENUM2STRING(BOTH_SIT7), + //================================================= + //ANIMS IN WHICH ONLY THE UPPER OBJECTS ARE IN MD3 + //================================================= + //# #sep ENUM2STRING(TORSO_ WEAPON-RELATED + ENUM2STRING(TORSO_DROPWEAP1), //# Put weapon away + ENUM2STRING(TORSO_DROPWEAP4), //# Put weapon away + ENUM2STRING(TORSO_RAISEWEAP1), //# Draw Weapon + ENUM2STRING(TORSO_RAISEWEAP4), //# Draw Weapon + ENUM2STRING(TORSO_WEAPONREADY1), //# Ready to fire stun baton + ENUM2STRING(TORSO_WEAPONREADY2), //# Ready to fire one-handed blaster pistol + ENUM2STRING(TORSO_WEAPONREADY3), //# Ready to fire blaster rifle + ENUM2STRING(TORSO_WEAPONREADY4), //# Ready to fire sniper rifle + ENUM2STRING(TORSO_WEAPONREADY10), //# Ready to fire thermal det + ENUM2STRING(TORSO_WEAPONIDLE2), //# Holding one-handed blaster + ENUM2STRING(TORSO_WEAPONIDLE3), //# Holding blaster rifle + ENUM2STRING(TORSO_WEAPONIDLE4), //# Holding sniper rifle + ENUM2STRING(TORSO_WEAPONIDLE10), //# Holding thermal det + + //# #sep ENUM2STRING(TORSO_ USING NON-WEAPON OBJECTS + + //# #sep ENUM2STRING(TORSO_ MISC + ENUM2STRING(TORSO_SURRENDER_START), //# arms up + ENUM2STRING(TORSO_SURRENDER_STOP), //# arms back down + ENUM2STRING(TORSO_CHOKING1), //# TEMP + + ENUM2STRING(TORSO_HANDSIGNAL1), + ENUM2STRING(TORSO_HANDSIGNAL2), + ENUM2STRING(TORSO_HANDSIGNAL3), + ENUM2STRING(TORSO_HANDSIGNAL4), + ENUM2STRING(TORSO_HANDSIGNAL5), + + //================================================= + //ANIMS IN WHICH ONLY THE LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep Legs-only anims + ENUM2STRING(LEGS_TURN1), //# What legs do when you turn your lower body to match your upper body facing + ENUM2STRING(LEGS_TURN2), //# Leg turning from stand2 + ENUM2STRING(LEGS_LEAN_LEFT1), //# Lean left + ENUM2STRING(LEGS_LEAN_RIGHT1), //# Lean Right + ENUM2STRING(LEGS_CHOKING1), //# TEMP + ENUM2STRING(LEGS_LEFTUP1), //# On a slope with left foot 4 higher than right + ENUM2STRING(LEGS_LEFTUP2), //# On a slope with left foot 8 higher than right + ENUM2STRING(LEGS_LEFTUP3), //# On a slope with left foot 12 higher than right + ENUM2STRING(LEGS_LEFTUP4), //# On a slope with left foot 16 higher than right + ENUM2STRING(LEGS_LEFTUP5), //# On a slope with left foot 20 higher than right + ENUM2STRING(LEGS_RIGHTUP1), //# On a slope with RIGHT foot 4 higher than left + ENUM2STRING(LEGS_RIGHTUP2), //# On a slope with RIGHT foot 8 higher than left + ENUM2STRING(LEGS_RIGHTUP3), //# On a slope with RIGHT foot 12 higher than left + ENUM2STRING(LEGS_RIGHTUP4), //# On a slope with RIGHT foot 16 higher than left + ENUM2STRING(LEGS_RIGHTUP5), //# On a slope with RIGHT foot 20 higher than left + ENUM2STRING(LEGS_S1_LUP1), + ENUM2STRING(LEGS_S1_LUP2), + ENUM2STRING(LEGS_S1_LUP3), + ENUM2STRING(LEGS_S1_LUP4), + ENUM2STRING(LEGS_S1_LUP5), + ENUM2STRING(LEGS_S1_RUP1), + ENUM2STRING(LEGS_S1_RUP2), + ENUM2STRING(LEGS_S1_RUP3), + ENUM2STRING(LEGS_S1_RUP4), + ENUM2STRING(LEGS_S1_RUP5), + ENUM2STRING(LEGS_S3_LUP1), + ENUM2STRING(LEGS_S3_LUP2), + ENUM2STRING(LEGS_S3_LUP3), + ENUM2STRING(LEGS_S3_LUP4), + ENUM2STRING(LEGS_S3_LUP5), + ENUM2STRING(LEGS_S3_RUP1), + ENUM2STRING(LEGS_S3_RUP2), + ENUM2STRING(LEGS_S3_RUP3), + ENUM2STRING(LEGS_S3_RUP4), + ENUM2STRING(LEGS_S3_RUP5), + ENUM2STRING(LEGS_S4_LUP1), + ENUM2STRING(LEGS_S4_LUP2), + ENUM2STRING(LEGS_S4_LUP3), + ENUM2STRING(LEGS_S4_LUP4), + ENUM2STRING(LEGS_S4_LUP5), + ENUM2STRING(LEGS_S4_RUP1), + ENUM2STRING(LEGS_S4_RUP2), + ENUM2STRING(LEGS_S4_RUP3), + ENUM2STRING(LEGS_S4_RUP4), + ENUM2STRING(LEGS_S4_RUP5), + ENUM2STRING(LEGS_S5_LUP1), + ENUM2STRING(LEGS_S5_LUP2), + ENUM2STRING(LEGS_S5_LUP3), + ENUM2STRING(LEGS_S5_LUP4), + ENUM2STRING(LEGS_S5_LUP5), + ENUM2STRING(LEGS_S5_RUP1), + ENUM2STRING(LEGS_S5_RUP2), + ENUM2STRING(LEGS_S5_RUP3), + ENUM2STRING(LEGS_S5_RUP4), + ENUM2STRING(LEGS_S5_RUP5), + ENUM2STRING(LEGS_S6_LUP1), + ENUM2STRING(LEGS_S6_LUP2), + ENUM2STRING(LEGS_S6_LUP3), + ENUM2STRING(LEGS_S6_LUP4), + ENUM2STRING(LEGS_S6_LUP5), + ENUM2STRING(LEGS_S6_RUP1), + ENUM2STRING(LEGS_S6_RUP2), + ENUM2STRING(LEGS_S6_RUP3), + ENUM2STRING(LEGS_S6_RUP4), + ENUM2STRING(LEGS_S6_RUP5), + ENUM2STRING(LEGS_S7_LUP1), + ENUM2STRING(LEGS_S7_LUP2), + ENUM2STRING(LEGS_S7_LUP3), + ENUM2STRING(LEGS_S7_LUP4), + ENUM2STRING(LEGS_S7_LUP5), + ENUM2STRING(LEGS_S7_RUP1), + ENUM2STRING(LEGS_S7_RUP2), + ENUM2STRING(LEGS_S7_RUP3), + ENUM2STRING(LEGS_S7_RUP4), + ENUM2STRING(LEGS_S7_RUP5), + + //New anim as per Jarrod's request + ENUM2STRING(LEGS_TURN180), + + //====================================================== + //cinematic anims + //====================================================== + //# #sep BOTH_ CINEMATIC-ONLY + ENUM2STRING(BOTH_CIN_1), //# Level specific cinematic 1 + ENUM2STRING(BOTH_CIN_2), //# Level specific cinematic 2 + ENUM2STRING(BOTH_CIN_3), //# Level specific cinematic 3 + ENUM2STRING(BOTH_CIN_4), //# Level specific cinematic 4 + ENUM2STRING(BOTH_CIN_5), //# Level specific cinematic 5 + ENUM2STRING(BOTH_CIN_6), //# Level specific cinematic 6 + ENUM2STRING(BOTH_CIN_7), //# Level specific cinematic 7 + ENUM2STRING(BOTH_CIN_8), //# Level specific cinematic 8 + ENUM2STRING(BOTH_CIN_9), //# Level specific cinematic 9 + ENUM2STRING(BOTH_CIN_10), //# Level specific cinematic 10 + ENUM2STRING(BOTH_CIN_11), //# Level specific cinematic 11 + ENUM2STRING(BOTH_CIN_12), //# Level specific cinematic 12 + ENUM2STRING(BOTH_CIN_13), //# Level specific cinematic 13 + ENUM2STRING(BOTH_CIN_14), //# Level specific cinematic 14 + ENUM2STRING(BOTH_CIN_15), //# Level specific cinematic 15 + ENUM2STRING(BOTH_CIN_16), //# Level specific cinematic 16 + ENUM2STRING(BOTH_CIN_17), //# Level specific cinematic 17 + ENUM2STRING(BOTH_CIN_18), //# Level specific cinematic 18 + ENUM2STRING(BOTH_CIN_19), //# Level specific cinematic 19 + ENUM2STRING(BOTH_CIN_20), //# Level specific cinematic 20 + ENUM2STRING(BOTH_CIN_21), //# Level specific cinematic 21 + ENUM2STRING(BOTH_CIN_22), //# Level specific cinematic 22 + ENUM2STRING(BOTH_CIN_23), //# Level specific cinematic 23 + ENUM2STRING(BOTH_CIN_24), //# Level specific cinematic 24 + ENUM2STRING(BOTH_CIN_25), //# Level specific cinematic 25 + + ENUM2STRING(BOTH_CIN_26), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_27), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_28), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_29), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_30), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_31), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_32), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_33), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_34), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_35), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_36), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_37), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_38), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_39), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_40), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_41), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_42), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_43), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_44), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_45), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_46), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_47), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_48), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_49), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_50), //# Level specific cinematic + + //must be terminated + NULL,-1 +}; +#endif // _XBOX / _UI diff --git a/code/cgame/cg_camera.cpp b/code/cgame/cg_camera.cpp new file mode 100644 index 0000000..52baf38 --- /dev/null +++ b/code/cgame/cg_camera.cpp @@ -0,0 +1,2003 @@ +//Client camera controls for cinematics + +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +#include "cg_media.h" + +#include "..\game\g_roff.h" + +bool in_camera = false; +camera_t client_camera={0}; +extern qboolean player_locked; + +extern gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match); +extern void G_UseTargets (gentity_t *ent, gentity_t *activator); +void CGCam_FollowDisable( void ); +void CGCam_TrackDisable( void ); +void CGCam_Distance( float distance, qboolean initLerp ); +void CGCam_DistanceDisable( void ); +extern int CG_CalcFOVFromX( float fov_x ); +extern void WP_SaberCatch( gentity_t *self, gentity_t *saber, qboolean switchToSaber ); + +#define TOP_LETTERBOX_FRACTION 0.7 +#define LETTERBOX_BAR_HEIGHT_DIVISOR 6 + + +/* +TODO: +CloseUp, FullShot & Longshot commands: + + camera( CLOSEUP, , angles(pitch yaw roll) ) + Will find the ent, apply angle offset to their head forward(minus pitch), + get a preset distance away and set the FOV. Trace to point, if less than + 1.0, put it there and open up FOV accordingly. + Be sure to frame it so that eyespot and tag_head are positioned at proper + places in the frame - ie: eyespot not in center, but not closer than 1/4 + screen width to the top...? +*/ +/* +------------------------- +CGCam_Init +------------------------- +*/ + +void CGCam_Init( void ) +{ + extern qboolean qbVidRestartOccured; + if (!qbVidRestartOccured) + { + memset( &client_camera, 0, sizeof ( camera_t ) ); + } +} + +#ifdef _XBOX +void CGCam_SetWidescreen( qboolean widescreen ) +{ + client_camera.widescreen = widescreen; + cg.widescreen = widescreen; +} +#endif + +/* +------------------------- +CGCam_Enable +------------------------- +*/ +extern void CG_CalcVrect(void); +void CGCam_Enable( void ) +{ + client_camera.bar_alpha = 0.0f; + client_camera.bar_time = cg.time; + + client_camera.bar_alpha_source = 0.0f; + client_camera.bar_alpha_dest = 1.0f; + + client_camera.bar_height_source = 0.0f; +// client_camera.bar_height_dest = 480/10; + client_camera.bar_height_dest = 480/LETTERBOX_BAR_HEIGHT_DIVISOR; + client_camera.bar_height = 0.0f; + + client_camera.info_state |= CAMERA_BAR_FADING; + + client_camera.FOV = CAMERA_DEFAULT_FOV; + client_camera.FOV2 = CAMERA_DEFAULT_FOV; + + in_camera = true; + + client_camera.next_roff_time = 0; + + if ( &g_entities[0] && g_entities[0].client ) + { + //Player zero not allowed to do anything + VectorClear( g_entities[0].client->ps.velocity ); + g_entities[0].contents = 0; + + if ( cg.zoomMode ) + { + // need to shut off some form of zooming + cg.zoomMode = 0; + } + + if ( g_entities[0].client->ps.saberInFlight && g_entities[0].client->ps.saber[0].Active() ) + {//saber is out + gentity_t *saberent = &g_entities[g_entities[0].client->ps.saberEntityNum]; + if ( saberent ) + { + WP_SaberCatch( &g_entities[0], saberent, qfalse ); + } + } + + for ( int i = 0; i < NUM_FORCE_POWERS; i++ ) + {//deactivate any active force powers + g_entities[0].client->ps.forcePowerDuration[i] = 0; +extern void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower ); + if ( g_entities[0].client->ps.forcePowerDuration[i] || (g_entities[0].client->ps.forcePowersActive&( 1 << i )) ) + { + WP_ForcePowerStop( &g_entities[0], (forcePowers_t)i ); + } + } + } + +#ifdef _XBOX + extern char entityVisList[2024 + 256]; + memset(entityVisList, -1, sizeof(entityVisList)); +#endif +} +/* +------------------------- +CGCam_Disable +------------------------- +*/ + +void CGCam_Disable( void ) +{ + in_camera = false; + + client_camera.bar_alpha = 1.0f; + client_camera.bar_time = cg.time; + + client_camera.bar_alpha_source = 1.0f; + client_camera.bar_alpha_dest = 0.0f; + + client_camera.bar_height_source = 480/10; + client_camera.bar_height_dest = 0.0f; + + client_camera.info_state |= CAMERA_BAR_FADING; + + if ( &g_entities[0] && g_entities[0].client ) + { + g_entities[0].contents = CONTENTS_BODY;//MASK_PLAYERSOLID; + } + + gi.SendServerCommand( NULL, "cts"); + + //if ( cg_skippingcin.integer ) + {//We're skipping the cinematic and it's over now + gi.cvar_set("timescale", "1"); + gi.cvar_set("skippingCinematic", "0"); + } + + //we just came out of camera, so update cg.refdef.vieworg out of the camera's origin so the snapshot will know our new ori + VectorCopy( g_entities[0].currentOrigin, cg.refdef.vieworg); + VectorCopy( g_entities[0].client->ps.viewangles, cg.refdefViewAngles ); + +#ifdef _XBOX + extern char entityVisList[2024 + 256]; + memset(entityVisList, -1, sizeof(entityVisList)); +#endif +} + +/* +------------------------- +CGCam_SetPosition +------------------------- +*/ + +void CGCam_SetPosition( vec3_t org ) +{ + VectorCopy( org, client_camera.origin ); + VectorCopy( client_camera.origin, cg.refdef.vieworg ); +} + +/* +------------------------- +CGCam_Move +------------------------- +*/ + +void CGCam_Move( vec3_t dest, float duration ) +{ + if ( client_camera.info_state & CAMERA_ROFFING ) + { + client_camera.info_state &= ~CAMERA_ROFFING; + } + + CGCam_TrackDisable(); + CGCam_DistanceDisable(); + + if ( !duration ) + { + client_camera.info_state &= ~CAMERA_MOVING; + CGCam_SetPosition( dest ); + return; + } + + client_camera.info_state |= CAMERA_MOVING; + + VectorCopy( dest, client_camera.origin2 ); + + client_camera.move_duration = duration; + client_camera.move_time = cg.time; +} + +/* +------------------------- +CGCam_SetAngles +------------------------- +*/ + +void CGCam_SetAngles( vec3_t ang ) +{ + VectorCopy( ang, client_camera.angles ); + VectorCopy(client_camera.angles, cg.refdefViewAngles ); +} + +/* +------------------------- +CGCam_Pan +------------------------- +*/ + +void CGCam_Pan( vec3_t dest, vec3_t panDirection, float duration ) +{ + //vec3_t panDirection = {0, 0, 0}; + int i; + float delta1 , delta2; + + CGCam_FollowDisable(); + CGCam_DistanceDisable(); + + if ( !duration ) + { + CGCam_SetAngles( dest ); + client_camera.info_state &= ~CAMERA_PANNING; + return; + } + + //FIXME: make the dest an absolute value, and pass in a + //panDirection as well. If a panDirection's axis value is + //zero, find the shortest difference for that axis. + //Store the delta in client_camera.angles2. + for( i = 0; i < 3; i++ ) + { + dest[i] = AngleNormalize360( dest[i] ); + delta1 = dest[i] - AngleNormalize360( client_camera.angles[i] ); + if ( delta1 < 0 ) + { + delta2 = delta1 + 360; + } + else + { + delta2 = delta1 - 360; + } + if ( !panDirection[i] ) + {//Didn't specify a direction, pick shortest + if( Q_fabs(delta1) < Q_fabs(delta2) ) + { + client_camera.angles2[i] = delta1; + } + else + { + client_camera.angles2[i] = delta2; + } + } + else if ( panDirection[i] < 0 ) + { + if( delta1 < 0 ) + { + client_camera.angles2[i] = delta1; + } + else if( delta1 > 0 ) + { + client_camera.angles2[i] = delta2; + } + else + {//exact + client_camera.angles2[i] = 0; + } + } + else if ( panDirection[i] > 0 ) + { + if( delta1 > 0 ) + { + client_camera.angles2[i] = delta1; + } + else if( delta1 < 0 ) + { + client_camera.angles2[i] = delta2; + } + else + {//exact + client_camera.angles2[i] = 0; + } + } + } + //VectorCopy( dest, client_camera.angles2 ); + + client_camera.info_state |= CAMERA_PANNING; + + client_camera.pan_duration = duration; + client_camera.pan_time = cg.time; +} + +/* +------------------------- +CGCam_SetRoll +------------------------- +*/ + +void CGCam_SetRoll( float roll ) +{ + client_camera.angles[2] = roll; +} + +/* +------------------------- +CGCam_Roll +------------------------- +*/ + +void CGCam_Roll( float dest, float duration ) +{ + if ( !duration ) + { + CGCam_SetRoll( dest ); + return; + } + + //FIXME/NOTE: this will override current panning!!! + client_camera.info_state |= CAMERA_PANNING; + + VectorCopy( client_camera.angles, client_camera.angles2 ); + client_camera.angles2[2] = AngleDelta( dest, client_camera.angles[2] ); + + client_camera.pan_duration = duration; + client_camera.pan_time = cg.time; +} + +/* +------------------------- +CGCam_SetFOV +------------------------- +*/ + +void CGCam_SetFOV( float FOV ) +{ + client_camera.FOV = FOV; +} + +/* +------------------------- +CGCam_Zoom +------------------------- +*/ + +void CGCam_Zoom( float FOV, float duration ) +{ + if ( !duration ) + { + CGCam_SetFOV( FOV ); + return; + } + client_camera.info_state |= CAMERA_ZOOMING; + + client_camera.FOV_time = cg.time; + client_camera.FOV2 = FOV; + + client_camera.FOV_duration = duration; +} + +void CGCam_Zoom2( float FOV, float FOV2, float duration ) +{ + if ( !duration ) + { + CGCam_SetFOV( FOV2 ); + return; + } + client_camera.info_state |= CAMERA_ZOOMING; + + client_camera.FOV_time = cg.time; + client_camera.FOV = FOV; + client_camera.FOV2 = FOV2; + + client_camera.FOV_duration = duration; +} + +void CGCam_ZoomAccel( float initialFOV, float fovVelocity, float fovAccel, float duration) +{ + if ( !duration ) + { + return; + } + client_camera.info_state |= CAMERA_ACCEL; + + client_camera.FOV_time = cg.time; + client_camera.FOV2 = initialFOV; + client_camera.FOV_vel = fovVelocity; + client_camera.FOV_acc = fovAccel; + + client_camera.FOV_duration = duration; +} + +/* +------------------------- +CGCam_Fade +------------------------- +*/ + +void CGCam_SetFade( vec4_t dest ) +{//Instant completion + client_camera.info_state &= ~CAMERA_FADING; + client_camera.fade_duration = 0; + Vector4Copy( dest, client_camera.fade_source ); + Vector4Copy( dest, client_camera.fade_color ); +} + +/* +------------------------- +CGCam_Fade +------------------------- +*/ + +void CGCam_Fade( vec4_t source, vec4_t dest, float duration ) +{ + if ( !duration ) + { + CGCam_SetFade( dest ); + return; + } + + Vector4Copy( source, client_camera.fade_source ); + Vector4Copy( dest, client_camera.fade_dest ); + + client_camera.fade_duration = duration; + client_camera.fade_time = cg.time; + + client_camera.info_state |= CAMERA_FADING; +} + +void CGCam_FollowDisable( void ) +{ + client_camera.info_state &= ~CAMERA_FOLLOWING; + client_camera.cameraGroup[0] = 0; + client_camera.cameraGroupZOfs = 0; + client_camera.cameraGroupTag[0] = 0; +} + +void CGCam_TrackDisable( void ) +{ + client_camera.info_state &= ~CAMERA_TRACKING; + client_camera.trackEntNum = ENTITYNUM_WORLD; +} + +void CGCam_DistanceDisable( void ) +{ + client_camera.distance = 0; +} +/* +------------------------- +CGCam_Follow +------------------------- +*/ + +void CGCam_Follow( const char *cameraGroup, float speed, float initLerp ) +{ + int len; + + //Clear any previous + CGCam_FollowDisable(); + + if(!cameraGroup || !cameraGroup[0]) + { + return; + } + + if ( Q_stricmp("none", (char *)cameraGroup) == 0 ) + {//Turn off all aiming + return; + } + + if ( Q_stricmp("NULL", (char *)cameraGroup) == 0 ) + {//Turn off all aiming + return; + } + + //NOTE: if this interrupts a pan before it's done, need to copy the cg.refdef.viewAngles to the camera.angles! + client_camera.info_state |= CAMERA_FOLLOWING; + client_camera.info_state &= ~CAMERA_PANNING; + + len = strlen(cameraGroup); + strncpy( client_camera.cameraGroup, cameraGroup, sizeof(client_camera.cameraGroup) ); + //NULL terminate last char in case they type a name too long + client_camera.cameraGroup[len] = 0; + + if ( speed ) + { + client_camera.followSpeed = speed; + } + else + { + client_camera.followSpeed = 100.0f; + } + + if ( initLerp ) + { + client_camera.followInitLerp = qtrue; + } + else + { + client_camera.followInitLerp = qfalse; + } +} + +/* +------------------------- +Q3_CameraAutoAim + + Keeps camera pointed at an entity, usually will be a misc_camera_focus + misc_camera_focus can be on a track that stays closest to it's subjects on that + path (like Q3_CameraAutoTrack) or is can simply always put itself between it's subjects. + misc_camera_focus can also set FOV/camera dist needed to keep the subjects in frame +------------------------- +*/ + +void CG_CameraAutoAim( const char *name ) +{ + /* + gentity_t *aimEnt = NULL; + + //Clear any previous + CGCam_FollowDisable(); + + if(Q_stricmp("none", (char *)name) == 0) + {//Turn off all aiming + return; + } + + aimEnt = G_Find(NULL, FOFS(targetname), (char *)name); + + if(!aimEnt) + { + gi.Printf(S_COLOR_RED"ERROR: %s camera aim target not found\n", name); + return; + } + + //Lerp time... + //aimEnt->aimDebounceTime = level.time;//FIXME: over time + client_camera.aimEntNum = aimEnt->s.number; + CGCam_Follow( aimEnt->cameraGroup, aimEnt->speed, aimEnt->spawnflags&1 ); + */ +} + +/* +------------------------- +CGCam_Track +------------------------- +*/ +//void CGCam_Track( char *trackName, float speed, float duration ) +void CGCam_Track( const char *trackName, float speed, float initLerp ) +{ + gentity_t *trackEnt = NULL; + + CGCam_TrackDisable(); + + if(Q_stricmp("none", (char *)trackName) == 0) + {//turn off tracking + return; + } + + //NOTE: if this interrupts a move before it's done, need to copy the cg.refdef.vieworg to the camera.origin! + //This will find a path_corner now, not a misc_camera_track + trackEnt = G_Find(NULL, FOFS(targetname), (char *)trackName); + + if ( !trackEnt ) + { + gi.Printf(S_COLOR_RED"ERROR: %s camera track target not found\n", trackName); + return; + } + + client_camera.info_state |= CAMERA_TRACKING; + client_camera.info_state &= ~CAMERA_MOVING; + + client_camera.trackEntNum = trackEnt->s.number; + client_camera.initSpeed = speed/10.0f; + client_camera.speed = speed; + client_camera.nextTrackEntUpdateTime = cg.time; + + if ( initLerp ) + { + client_camera.trackInitLerp = qtrue; + } + else + { + client_camera.trackInitLerp = qfalse; + } + /* + if ( client_camera.info_state & CAMERA_FOLLOWING ) + {//Used to snap angles? Do what...? + } + */ + + //Set a moveDir + VectorSubtract( trackEnt->currentOrigin, client_camera.origin, client_camera.moveDir ); + + if ( !client_camera.trackInitLerp ) + {//want to snap to first position + //Snap to trackEnt's origin + VectorCopy( trackEnt->currentOrigin, client_camera.origin ); + + //Set new moveDir if trackEnt has a next path_corner + //Possible that track has no next point, in which case we won't be moving anyway + if ( trackEnt->target && trackEnt->target[0] ) + { + gentity_t *newTrackEnt = G_Find( NULL, FOFS(targetname), trackEnt->target ); + if ( newTrackEnt ) + { + VectorSubtract( newTrackEnt->currentOrigin, client_camera.origin, client_camera.moveDir ); + } + } + } + + VectorNormalize( client_camera.moveDir ); +} + +/* +------------------------- +Q3_CameraAutoTrack + + Keeps camera a certain distance from target entity but on the specified CameraPath + The distance can be set in a script or derived from a misc_camera_focus. + Dist will interpolate when changed, can also set acceleration/deceleration values. + FOV will also interpolate. + + CameraPath might be a MAX path or perhaps a series of path_corners on the map itself +------------------------- +*/ + +void CG_CameraAutoTrack( const char *name ) +{ + /* + gentity_t *trackEnt = NULL; + + CGCam_TrackDisable(); + + if(Q_stricmp("none", (char *)name) == 0) + {//turn off tracking + return; + } + + //This will find a path_corner now, not a misc_camera_track + trackEnt = G_Find(NULL, FOFS(targetname), (char *)name); + + if(!trackEnt) + { + gi.Printf(S_COLOR_RED"ERROR: %s camera track target not found\n", name); + return; + } + + //FIXME: last arg will be passed in + CGCam_Track( trackEnt->s.number, trackEnt->speed, qfalse ); + //FIXME: this will be a seperate call + CGCam_Distance( trackEnt->radius, qtrue); + */ +} + +/* +------------------------- +CGCam_Distance +------------------------- +*/ + +void CGCam_Distance( float distance, float initLerp ) +{ + client_camera.distance = distance; + + if ( initLerp ) + { + client_camera.distanceInitLerp = qtrue; + } + else + { + client_camera.distanceInitLerp = qfalse; + } +} + +//======================================================================================== + + +void CGCam_FollowUpdate ( void ) +{ + vec3_t center, dir, cameraAngles, vec, focus[MAX_CAMERA_GROUP_SUBJECTS];//No more than 16 subjects in a cameraGroup + gentity_t *from = NULL; + centity_t *fromCent = NULL; + int num_subjects = 0, i; + qboolean focused = qfalse; + + if ( client_camera.cameraGroup && client_camera.cameraGroup[0] ) + { + //Stay centered in my cameraGroup, if I have one + while( NULL != (from = G_Find(from, FOFS(cameraGroup), client_camera.cameraGroup))) + { + /* + if ( from->s.number == client_camera.aimEntNum ) + {//This is the misc_camera_focus, we'll be removing this ent altogether eventually + continue; + } + */ + + if ( num_subjects >= MAX_CAMERA_GROUP_SUBJECTS ) + { + gi.Printf(S_COLOR_RED"ERROR: Too many subjects in shot composition %s", client_camera.cameraGroup); + break; + } + + fromCent = &cg_entities[from->s.number]; + if ( !fromCent ) + { + continue; + } + + focused = qfalse; + if ( from->client && client_camera.cameraGroupTag && client_camera.cameraGroupTag[0] && fromCent->gent->ghoul2.size() ) + { + int newBolt = gi.G2API_AddBolt( &fromCent->gent->ghoul2[from->playerModel], client_camera.cameraGroupTag ); + if ( newBolt != -1 ) + { + mdxaBone_t boltMatrix; + vec3_t fromAngles = {0,from->client->ps.legsYaw,0}; + + gi.G2API_GetBoltMatrix( fromCent->gent->ghoul2, from->playerModel, newBolt, &boltMatrix, fromAngles, fromCent->lerpOrigin, cg.time, cgs.model_draw, fromCent->currentState.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, focus[num_subjects] ); + + focused = qtrue; + } + } + if ( !focused ) + { + if ( from->s.pos.trType != TR_STATIONARY ) +// if ( from->s.pos.trType == TR_INTERPOLATE ) + {//use interpolated origin? + if ( !VectorCompare( vec3_origin, fromCent->lerpOrigin ) ) + {//hunh? Somehow we've never seen this gentity on the client, so there is no lerpOrigin, so cheat over to the game and use the currentOrigin + VectorCopy( from->currentOrigin, focus[num_subjects] ); + } + else + { + VectorCopy( fromCent->lerpOrigin, focus[num_subjects] ); + } + } + else + { + VectorCopy(from->currentOrigin, focus[num_subjects]); + } + //FIXME: make a list here of their s.numbers instead so we can do other stuff with the list below + if ( from->client ) + {//Track to their eyes - FIXME: maybe go off a tag? + //FIXME: + //Based on FOV and distance to subject from camera, pick the point that + //keeps eyes 3/4 up from bottom of screen... what about bars? + focus[num_subjects][2] += from->client->ps.viewheight; + } + } + if ( client_camera.cameraGroupZOfs ) + { + focus[num_subjects][2] += client_camera.cameraGroupZOfs; + } + num_subjects++; + } + + if ( !num_subjects ) // Bad cameragroup + { +#ifndef FINAL_BUILD + gi.Printf(S_COLOR_RED"ERROR: Camera Focus unable to locate cameragroup: %s\n", client_camera.cameraGroup); +#endif + return; + } + + //Now average all points + VectorCopy( focus[0], center ); + for( i = 1; i < num_subjects; i++ ) + { + VectorAdd( focus[i], center, center ); + } + VectorScale( center, 1.0f/((float)num_subjects), center ); + } + else + { + return; + } + + //Need to set a speed to keep a distance from + //the subject- fixme: only do this if have a distance + //set + VectorSubtract( client_camera.subjectPos, center, vec ); + client_camera.subjectSpeed = VectorLengthSquared( vec ) * 100.0f / cg.frametime; + + /* + if ( !cg_skippingcin.integer ) + { + Com_Printf( S_COLOR_RED"org: %s\n", vtos(center) ); + } + */ + VectorCopy( center, client_camera.subjectPos ); + + VectorSubtract( center, cg.refdef.vieworg, dir );//can't use client_camera.origin because it's not updated until the end of the move. + + //Get desired angle + vectoangles(dir, cameraAngles); + + if ( client_camera.followInitLerp ) + {//Lerping + float frac = cg.frametime/100.0f * client_camera.followSpeed/100.f; + for( i = 0; i < 3; i++ ) + { + cameraAngles[i] = AngleNormalize180( cameraAngles[i] ); + cameraAngles[i] = AngleNormalize180( client_camera.angles[i] + frac * AngleNormalize180(cameraAngles[i] - client_camera.angles[i]) ); + cameraAngles[i] = AngleNormalize180( cameraAngles[i] ); + } +#if 0 + Com_Printf( "%s\n", vtos(cameraAngles) ); +#endif + } + else + {//Snapping, should do this first time if follow_lerp_to_start_duration is zero + //will lerp from this point on + client_camera.followInitLerp = qtrue; + for( i = 0; i < 3; i++ ) + {//normalize so that when we start lerping, it doesn't freak out + cameraAngles[i] = AngleNormalize180( cameraAngles[i] ); + } + //So tracker doesn't move right away thinking the first angle change + //is the subject moving... FIXME: shouldn't set this until lerp done OR snapped? + client_camera.subjectSpeed = 0; + } + + //Point camera to lerp angles + /* + if ( !cg_skippingcin.integer ) + { + Com_Printf( "ang: %s\n", vtos(cameraAngles) ); + } + */ + VectorCopy( cameraAngles, client_camera.angles ); +} + +void CGCam_TrackEntUpdate ( void ) +{//FIXME: only do every 100 ms + gentity_t *trackEnt = NULL; + gentity_t *newTrackEnt = NULL; + qboolean reached = qfalse; + vec3_t vec; + float dist; + + if ( client_camera.trackEntNum >= 0 && client_camera.trackEntNum < ENTITYNUM_WORLD ) + {//We're already heading to a path_corner + trackEnt = &g_entities[client_camera.trackEntNum]; + VectorSubtract( trackEnt->currentOrigin, client_camera.origin, vec ); + dist = VectorLengthSquared( vec ); + if ( dist < 256 )//16 squared + {//FIXME: who should be doing the using here? + G_UseTargets( trackEnt, trackEnt ); + reached = qtrue; + } + } + + if ( trackEnt && reached ) + { + + if ( trackEnt->target && trackEnt->target[0] ) + {//Find our next path_corner + newTrackEnt = G_Find( NULL, FOFS(targetname), trackEnt->target ); + if ( newTrackEnt ) + { + if ( newTrackEnt->radius < 0 ) + {//Don't bother trying to maintain a radius + client_camera.distance = 0; + client_camera.speed = client_camera.initSpeed; + } + else if ( newTrackEnt->radius > 0 ) + { + client_camera.distance = newTrackEnt->radius; + } + + if ( newTrackEnt->speed < 0 ) + {//go back to our default speed + client_camera.speed = client_camera.initSpeed; + } + else if ( newTrackEnt->speed > 0 ) + { + client_camera.speed = newTrackEnt->speed/10.0f; + } + } + } + else + {//stop thinking if this is the last one + CGCam_TrackDisable(); + } + } + + if ( newTrackEnt ) + {//Update will lerp this + client_camera.info_state |= CAMERA_TRACKING; + client_camera.trackEntNum = newTrackEnt->s.number; + VectorCopy( newTrackEnt->currentOrigin, client_camera.trackToOrg ); + } + + client_camera.nextTrackEntUpdateTime = cg.time + 100; +} + +void CGCam_TrackUpdate ( void ) +{ + vec3_t goalVec, curVec, trackPos, vec; + float goalDist, dist; + qboolean slowDown = qfalse; + + if ( client_camera.nextTrackEntUpdateTime <= cg.time ) + { + CGCam_TrackEntUpdate(); + } + + VectorSubtract( client_camera.trackToOrg, client_camera.origin, goalVec ); + goalDist = VectorNormalize( goalVec ); + if ( goalDist > 100 ) + { + goalDist = 100; + } + else if ( goalDist < 10 ) + { + goalDist = 10; + } + + if ( client_camera.distance && client_camera.info_state & CAMERA_FOLLOWING ) + { + float adjust = 0.0f, desiredSpeed = 0.0f; + float dot; + + if ( !client_camera.distanceInitLerp ) + { + VectorSubtract( client_camera.origin, client_camera.subjectPos, vec ); + VectorNormalize( vec ); + //FIXME: use client_camera.moveDir here? + VectorMA( client_camera.subjectPos, client_camera.distance, vec, client_camera.origin ); + //Snap to first time only + client_camera.distanceInitLerp = qtrue; + return; + } + else if ( client_camera.subjectSpeed > 0.05f ) + {//Don't start moving until subject moves + VectorSubtract( client_camera.subjectPos, client_camera.origin, vec ); + dist = VectorNormalize(vec); + dot = DotProduct(goalVec, vec); + + if ( dist > client_camera.distance ) + {//too far away + if ( dot > 0 ) + {//Camera is moving toward the subject + adjust = (dist - client_camera.distance);//Speed up + } + else if ( dot < 0 ) + {//Camera is moving away from the subject + adjust = (dist - client_camera.distance) * -1.0f;//Slow down + } + } + else if ( dist < client_camera.distance ) + {//too close + if(dot > 0) + {//Camera is moving toward the subject + adjust = (client_camera.distance - dist) * -1.0f;//Slow down + } + else if(dot < 0) + {//Camera is moving away from the subject + adjust = (client_camera.distance - dist);//Speed up + } + } + + //Speed of the focus + our error + //desiredSpeed = aimCent->gent->speed + (adjust * cg.frametime/100.0f);//cg.frameInterpolation); + desiredSpeed = (adjust);// * cg.frametime/100.0f);//cg.frameInterpolation); + + //self->moveInfo.speed = desiredSpeed; + + //Don't change speeds faster than 10 every 10th of a second + float max_allowed_accel = MAX_ACCEL_PER_FRAME * (cg.frametime/100.0f); + + if ( !client_camera.subjectSpeed ) + {//full stop + client_camera.speed = desiredSpeed; + } + else if ( client_camera.speed - desiredSpeed > max_allowed_accel ) + {//new speed much slower, slow down at max accel + client_camera.speed -= max_allowed_accel; + } + else if ( desiredSpeed - client_camera.speed > max_allowed_accel ) + {//new speed much faster, speed up at max accel + client_camera.speed += max_allowed_accel; + } + else + {//remember this speed + client_camera.speed = desiredSpeed; + } + + //Com_Printf("Speed: %4.2f (%4.2f)\n", self->moveInfo.speed, aimCent->gent->speed); + } + } + else + { + //slowDown = qtrue; + } + + + //FIXME: this probably isn't right, round it out more + VectorScale( goalVec, cg.frametime/100.0f, goalVec ); + VectorScale( client_camera.moveDir, (100.0f - cg.frametime)/100.0f, curVec ); + VectorAdd( goalVec, curVec, client_camera.moveDir ); + VectorNormalize( client_camera.moveDir ); + if(slowDown) + { + VectorMA( client_camera.origin, client_camera.speed * goalDist/100.0f * cg.frametime/100.0f, client_camera.moveDir, trackPos ); + } + else + { + VectorMA( client_camera.origin, client_camera.speed * cg.frametime/100.0f , client_camera.moveDir, trackPos ); + } + + //FIXME: Implement + //Need to find point on camera's path that is closest to the desired distance from subject + //OR: Need to intelligently pick this desired distance based on framing... + VectorCopy( trackPos, client_camera.origin ); +} + +//========================================================================================= + +/* +------------------------- +CGCam_UpdateBarFade +------------------------- +*/ + +void CGCam_UpdateBarFade( void ) +{ + if ( client_camera.bar_time + BAR_DURATION < cg.time ) + { + client_camera.bar_alpha = client_camera.bar_alpha_dest; + client_camera.info_state &= ~CAMERA_BAR_FADING; + client_camera.bar_height = client_camera.bar_height_dest; + } + else + { + client_camera.bar_alpha = client_camera.bar_alpha_source + ( ( client_camera.bar_alpha_dest - client_camera.bar_alpha_source ) / BAR_DURATION ) * ( cg.time - client_camera.bar_time );; + client_camera.bar_height = client_camera.bar_height_source + ( ( client_camera.bar_height_dest - client_camera.bar_height_source ) / BAR_DURATION ) * ( cg.time - client_camera.bar_time );; + } +} + +/* +------------------------- +CGCam_UpdateFade +------------------------- +*/ + +void CGCam_UpdateFade( void ) +{ + if ( client_camera.info_state & CAMERA_FADING ) + { + if ( client_camera.fade_time + client_camera.fade_duration < cg.time ) + { + Vector4Copy( client_camera.fade_dest, client_camera.fade_color ); + client_camera.info_state &= ~CAMERA_FADING; + } + else + { + for ( int i = 0; i < 4; i++ ) + { + client_camera.fade_color[i] = client_camera.fade_source[i] + (( ( client_camera.fade_dest[i] - client_camera.fade_source[i] ) ) / client_camera.fade_duration ) * ( cg.time - client_camera.fade_time ); + } + } + } +} +/* +------------------------- +CGCam_Update +------------------------- +*/ +static void CGCam_Roff( void ); + +void CGCam_Update( void ) +{ + int i; + qboolean checkFollow = qfalse; + qboolean checkTrack = qfalse; + + // Apply new roff data to the camera as needed + if ( client_camera.info_state & CAMERA_ROFFING ) + { + CGCam_Roff(); + } + + //Check for a zoom + if (client_camera.info_state & CAMERA_ACCEL) + { + // x = x0 + vt + 0.5*a*t*t + float actualFOV_X = client_camera.FOV; + float sanityMin = 1, sanityMax = 180; + float t = (cg.time - client_camera.FOV_time)*0.001; // mult by 0.001 cuz otherwise t is too darned big + float fovDuration = client_camera.FOV_duration; + +#ifndef FINAL_BUILD + if (cg_roffval4.integer) + { + fovDuration = cg_roffval4.integer; + } +#endif + if ( client_camera.FOV_time + fovDuration < cg.time ) + { + client_camera.info_state &= ~CAMERA_ACCEL; + } + else + { + float initialPosVal = client_camera.FOV2; + float velVal = client_camera.FOV_vel; + float accVal = client_camera.FOV_acc; + +#ifndef FINAL_BUILD + if (cg_roffdebug.integer) + { + if (fabs(cg_roffval1.value) > 0.001f) + { + initialPosVal = cg_roffval1.value; + } + if (fabs(cg_roffval2.value) > 0.001f) + { + velVal = cg_roffval2.value; + } + if (fabs(cg_roffval3.value) > 0.001f) + { + accVal = cg_roffval3.value; + } + } +#endif + float initialPos = initialPosVal; + float vel = velVal*t; + float acc = 0.5*accVal*t*t; + + actualFOV_X = initialPos + vel + acc; + if (cg_roffdebug.integer) + { + Com_Printf("%d: fovaccel from %2.1f using vel = %2.4f, acc = %2.4f (current fov calc = %5.6f)\n", + cg.time, initialPosVal, velVal, accVal, actualFOV_X); + } + + if (actualFOV_X < sanityMin) + { + actualFOV_X = sanityMin; + } + else if (actualFOV_X > sanityMax) + { + actualFOV_X = sanityMax; + } + client_camera.FOV = actualFOV_X; + } + CG_CalcFOVFromX( actualFOV_X ); + } + else if ( client_camera.info_state & CAMERA_ZOOMING ) + { + float actualFOV_X; + + if ( client_camera.FOV_time + client_camera.FOV_duration < cg.time ) + { + actualFOV_X = client_camera.FOV = client_camera.FOV2; + client_camera.info_state &= ~CAMERA_ZOOMING; + } + else + { + actualFOV_X = client_camera.FOV + (( ( client_camera.FOV2 - client_camera.FOV ) ) / client_camera.FOV_duration ) * ( cg.time - client_camera.FOV_time ); + } + CG_CalcFOVFromX( actualFOV_X ); + } + else + { + CG_CalcFOVFromX( client_camera.FOV ); + } + + //Check for roffing angles + if ( (client_camera.info_state & CAMERA_ROFFING) && !(client_camera.info_state & CAMERA_FOLLOWING) ) + { + if (client_camera.info_state & CAMERA_CUT) + { + // we're doing a cut, so just go to the new angles. none of this hifalutin lerping business. + for ( i = 0; i < 3; i++ ) + { + cg.refdefViewAngles[i] = AngleNormalize360( ( client_camera.angles[i] + client_camera.angles2[i] ) ); + } + } + else + { + for ( i = 0; i < 3; i++ ) + { + cg.refdefViewAngles[i] = client_camera.angles[i] + ( client_camera.angles2[i] / client_camera.pan_duration ) * ( cg.time - client_camera.pan_time ); + } + } + } + else if ( client_camera.info_state & CAMERA_PANNING ) + { + if (client_camera.info_state & CAMERA_CUT) + { + // we're doing a cut, so just go to the new angles. none of this hifalutin lerping business. + for ( i = 0; i < 3; i++ ) + { + cg.refdefViewAngles[i] = AngleNormalize360( ( client_camera.angles[i] + client_camera.angles2[i] ) ); + } + } + else + { + //Note: does not actually change the camera's angles until the pan time is done! + if ( client_camera.pan_time + client_camera.pan_duration < cg.time ) + {//finished panning + for ( i = 0; i < 3; i++ ) + { + client_camera.angles[i] = AngleNormalize360( ( client_camera.angles[i] + client_camera.angles2[i] ) ); + } + + client_camera.info_state &= ~CAMERA_PANNING; + VectorCopy(client_camera.angles, cg.refdefViewAngles ); + } + else + {//still panning + for ( i = 0; i < 3; i++ ) + { + //NOTE: does not store the resultant angle in client_camera.angles until pan is done + cg.refdefViewAngles[i] = client_camera.angles[i] + ( client_camera.angles2[i] / client_camera.pan_duration ) * ( cg.time - client_camera.pan_time ); + } + } + } + } + else + { + checkFollow = qtrue; + } + + //Check for movement + if ( client_camera.info_state & CAMERA_MOVING ) + { + //NOTE: does not actually move the camera until the movement time is done! + if ( client_camera.move_time + client_camera.move_duration < cg.time ) + { + VectorCopy( client_camera.origin2, client_camera.origin ); + client_camera.info_state &= ~CAMERA_MOVING; + VectorCopy( client_camera.origin, cg.refdef.vieworg ); + } + else + { + if (client_camera.info_state & CAMERA_CUT) + { + // we're doing a cut, so just go to the new origin. none of this fancypants lerping stuff. + for ( i = 0; i < 3; i++ ) + { + cg.refdef.vieworg[i] = client_camera.origin2[i]; + } + } + else + { + for ( i = 0; i < 3; i++ ) + { + cg.refdef.vieworg[i] = client_camera.origin[i] + (( ( client_camera.origin2[i] - client_camera.origin[i] ) ) / client_camera.move_duration ) * ( cg.time - client_camera.move_time ); + } + } + } + } + else + { + checkTrack = qtrue; + } + + if ( checkFollow ) + { + if ( client_camera.info_state & CAMERA_FOLLOWING ) + {//This needs to be done after camera movement + CGCam_FollowUpdate(); + } + VectorCopy(client_camera.angles, cg.refdefViewAngles ); + } + + if ( checkTrack ) + { + if ( client_camera.info_state & CAMERA_TRACKING ) + {//This has to run AFTER Follow if the camera is following a cameraGroup + CGCam_TrackUpdate(); + } + + VectorCopy( client_camera.origin, cg.refdef.vieworg ); + } + + //Bar fading + if ( client_camera.info_state & CAMERA_BAR_FADING ) + { + CGCam_UpdateBarFade(); + } + + //Normal fading - separate call because can finish after camera is disabled + CGCam_UpdateFade(); + + //Update shaking if there's any + //CGCam_UpdateSmooth( cg.refdef.vieworg, cg.refdefViewAngles ); + CGCam_UpdateShake( cg.refdef.vieworg, cg.refdefViewAngles ); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); +} + +/* +------------------------- +CGCam_DrawWideScreen +------------------------- +*/ + +void CGCam_DrawWideScreen( void ) +{ + vec4_t modulate; + + //Only draw if visible + if ( client_camera.bar_alpha ) + { + CGCam_UpdateBarFade(); + + modulate[0] = modulate[1] = modulate[2] = 0.0f; + modulate[3] = client_camera.bar_alpha; + +#ifdef _XBOX + if(cg.widescreen) { + CG_FillRect( cg.refdef.x, cg.refdef.y, 720, client_camera.bar_height*TOP_LETTERBOX_FRACTION, modulate ); + CG_FillRect( cg.refdef.x, cg.refdef.y + 480 - client_camera.bar_height, 720, client_camera.bar_height, modulate ); + } + else { +#endif + CG_FillRect( cg.refdef.x, cg.refdef.y, 640, client_camera.bar_height*TOP_LETTERBOX_FRACTION, modulate ); + CG_FillRect( cg.refdef.x, cg.refdef.y + 480 - client_camera.bar_height, 640, client_camera.bar_height, modulate ); +#ifdef _XBOX + } +#endif + } + + //NOTENOTE: Camera always draws the fades unless the alpha is 0 + if ( client_camera.fade_color[3] == 0.0f ) + return; + +#ifdef _XBOX + if(cg.widescreen) + CG_FillRect( cg.refdef.x, cg.refdef.y, 720, 480, client_camera.fade_color ); + else +#endif + CG_FillRect( cg.refdef.x, cg.refdef.y, 640, 480, client_camera.fade_color ); +} + +/* +------------------------- +CGCam_RenderScene +------------------------- +*/ +void CGCam_RenderScene( void ) +{ + CGCam_Update(); + CG_CalcVrect(); +} + +/* +------------------------- +CGCam_Shake +------------------------- +*/ + +void CGCam_Shake( float intensity, int duration ) +{ + if ( intensity > MAX_SHAKE_INTENSITY ) + intensity = MAX_SHAKE_INTENSITY; + + client_camera.shake_intensity = intensity; + client_camera.shake_duration = duration; + client_camera.shake_start = cg.time; +#ifdef _IMMERSION + // FIX ME: This is far too weak... but I don't want it to interfere with other effects. + cgi_FF_Shake( int(intensity * 625), duration ); // 625 = (10000 / MAX_SHAKE_INTENSITY) +#endif // _IMMERSION +#ifdef _XBOX + cgi_FF_Xbox_Shake(intensity,duration); +#endif +} + +void CGCam_Shake( float intensity, int duration, bool rumble ) +{ + if ( intensity > MAX_SHAKE_INTENSITY ) + intensity = MAX_SHAKE_INTENSITY; + + client_camera.shake_intensity = intensity; + client_camera.shake_duration = duration; + client_camera.shake_start = cg.time; +#ifdef _IMMERSION + // FIX ME: This is far too weak... but I don't want it to interfere with other effects. + cgi_FF_Shake( int(intensity * 625), duration ); // 625 = (10000 / MAX_SHAKE_INTENSITY) +#endif // _IMMERSION +#ifdef _XBOX + if(rumble) + cgi_FF_Xbox_Shake(intensity,duration); +#endif +} + +/* +------------------------- +CGCam_UpdateShake + +This doesn't actually affect the camera's info, but passed information instead +------------------------- +*/ + +void CGCam_UpdateShake( vec3_t origin, vec3_t angles ) +{ + vec3_t moveDir; + float intensity_scale, intensity; + + if ( client_camera.shake_duration <= 0 ) + return; + + if ( cg.time > ( client_camera.shake_start + client_camera.shake_duration ) ) + { + client_camera.shake_intensity = 0; + client_camera.shake_duration = 0; + client_camera.shake_start = 0; + return; + } + + //intensity_scale now also takes into account FOV with 90.0 as normal + intensity_scale = 1.0f - ( (float) ( cg.time - client_camera.shake_start ) / (float) client_camera.shake_duration ) * (((client_camera.FOV+client_camera.FOV2)/2.0f)/90.0f); + + intensity = client_camera.shake_intensity * intensity_scale; + + for ( int i = 0; i < 3; i++ ) + { + moveDir[i] = ( crandom() * intensity ); + } + + //FIXME: Lerp + + //Move the camera + VectorAdd( origin, moveDir, origin ); + + for ( i=0; i < 2; i++ ) // Don't do ROLL + moveDir[i] = ( crandom() * intensity ); + + //FIXME: Lerp + + //Move the angles + VectorAdd( angles, moveDir, angles ); +} + +void CGCam_Smooth( float intensity, int duration ) +{ + client_camera.smooth_active=false; // means smooth_origin and angles are valid + if ( intensity>1.0f||intensity==0.0f||duration<1) + { + client_camera.info_state &= ~CAMERA_SMOOTHING; + return; + } + client_camera.info_state |= CAMERA_SMOOTHING; + client_camera.smooth_intensity = intensity; + client_camera.smooth_duration = duration; + client_camera.smooth_start = cg.time; +} + +void CGCam_UpdateSmooth( vec3_t origin, vec3_t angles ) +{ + if (!(client_camera.info_state&CAMERA_SMOOTHING)||cg.time > ( client_camera.smooth_start + client_camera.smooth_duration )) + { + client_camera.info_state &= ~CAMERA_SMOOTHING; + return; + } + if (!client_camera.smooth_active) + { + client_camera.smooth_active=true; + VectorCopy(origin,client_camera.smooth_origin); + return; + } + float factor=client_camera.smooth_intensity; + if (client_camera.smooth_duration>200&&cg.time > ( client_camera.smooth_start + client_camera.smooth_duration-100 )) + { + factor+=(1.0f-client_camera.smooth_intensity)* + (100.0f-(client_camera.smooth_start + client_camera.smooth_duration-cg.time))/100.0f; + } + int i; + for (i=0;i<3;i++) + { + client_camera.smooth_origin[i]*=(1.0f-factor); + client_camera.smooth_origin[i]+=factor*origin[i]; + origin[i]=client_camera.smooth_origin[i]; + } +} + +void CGCam_NotetrackProcessFov(const char *addlArg) +{ + int a = 0; + char t[64]; + + if (!addlArg || !addlArg[0]) + { + Com_Printf("camera roff 'fov' notetrack missing fov argument\n", addlArg); + return; + } + if (isdigit(addlArg[a])) + { + // "fov " + int d = 0, tsize = 64; + + memset(t, 0, tsize*sizeof(char)); + while (addlArg[a] && d < tsize) + { + t[d++] = addlArg[a++]; + } + // now the contents of t represent our desired fov + float newFov = atof(t); +#ifndef FINAL_BUILD + if (cg_roffdebug.integer) + { + if (fabs(cg_roffval1.value) > 0.001f) + { + newFov = cg_roffval1.value; + } + } +#endif + if (cg_roffdebug.integer) + { + Com_Printf("notetrack: 'fov %2.2f' on frame %d\n", newFov, client_camera.roff_frame); + } + CGCam_Zoom(newFov, 0); + } +} + +void CGCam_NotetrackProcessFovZoom(const char *addlArg) +{ + int a = 0; + float beginFOV = 0, endFOV = 0, fovTime = 0; + + if (!addlArg || !addlArg[0]) + { + Com_Printf("camera roff 'fovzoom' notetrack missing arguments\n", addlArg); + return; + } + // + // "fovzoom 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 oXyrgF2UD1ah&w{kG~Ep%*+Y`Mw2zuz$PdeZm2 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 literal 0 HcmV?d00001 diff --git a/code/ff/IFC/IFCErrors.h b/code/ff/IFC/IFCErrors.h new file mode 100644 index 0000000..3b32ca0 --- /dev/null +++ b/code/ff/IFC/IFCErrors.h @@ -0,0 +1,181 @@ +/********************************************************************** + Copyright (c) 1999 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: IFCErrors.h + + PURPOSE: Error codes returned in IFC; Error handling in IFC + + STARTED: 2/28/99 by Jeff Mallett + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/6/99 jrm: Added user error handling control + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(IFCERRORS_H__INCLUDED_) +#define IFCERRORS_H__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include +#include "ImmBaseTypes.h" + + +/**************************************************************************** + * + * Error Codes + * + ****************************************************************************/ + +typedef enum { + IFC_ERR_OK = 0, + + IFC_ERR_UNKNOWN_ERROR = 1, + + IFC_ERR_ALLOCATION_FAILED = 2, + IFC_ERR_INVALID_PARAMETER = 3, + IFC_ERR_NULL_PARAMETER = 4, + IFC_ERR_WRONG_FORM = 5, + + IFC_ERR_DEVICE_IS_NULL = 6, + IFC_ERR_INVALID_GUID = 7, + IFC_ERR_EFFECT_NOT_INITIALIZED = 8, + + IFC_ERR_CANT_INITIALIZE_DEVICE = 9, + + IFC_ERR_CANT_CREATE_EFFECT = 10, + IFC_ERR_CANT_CREATE_EFFECT_FROM_IFR = 11, + IFC_ERR_NO_EFFECTS_FOUND = 12, + IFC_ERR_EFFECT_IS_COMPOUND = 13, + + IFC_ERR_PROJECT_ALREADY_OPEN = 14, + IFC_ERR_PROJECT_NOT_OPEN = 15, + + IFC_ERR_NO_DX7_DEVICE = 16, + IFC_ERR_CANT_WRITE_IFR = 17, + + IFC_ERR_DINPUT_NOT_FOUND = 18, + IFC_ERR_IMMAPI_NOT_FOUND = 19, + + IFC_ERR_FILE_NOT_FOUND = 20, + IFC_ERR_NO_VERSION_INFO = 21 + +} IFC_ERROR_CODE; + +typedef enum { + IFC_OUTPUT_ERR_TO_DEBUG = 0x0001, + IFC_OUTPUT_ERR_TO_DIALOG = 0x0002 +} IFC_ERROR_HANDLING_FLAGS; + + +/**************************************************************************** + * + * Macros + * + ****************************************************************************/ + +// +// ------ PUBLIC MACROS ------ +// +#define IFC_GET_LAST_ERROR CIFCErrors::GetLastErrorCode() +#define IFC_SET_ERROR_HANDLING CIFCErrors::SetErrorHandling + + +// +// ------ PRIVATE MACROS ------ +// +#if (IFC_VERSION >= 0x0110) + #define IFC_SET_ERROR(err) CIFCErrors::SetErrorCode(err, __FILE__, __LINE__) +#else + #define IFC_SET_ERROR(err) CIFCErrors::SetErrorCode(err) +#endif +#define IFC_CLEAR_ERROR IFC_SET_ERROR(IFC_ERR_OK) + + + +/**************************************************************************** + * + * CIFCErrors + * + ****************************************************************************/ +// All members are static. Don't bother instantiating an object of this class. + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CIFCErrors +{ + // + // ATTRIBUTES + // + + public: + + static HRESULT + GetLastErrorCode() + { return m_Err; } + + static void + SetErrorHandling(unsigned long dwFlags) + { m_dwErrHandlingFlags = dwFlags; } + + +// +// ------ PRIVATE INTERFACE ------ +// + + // Internally used by IFC classes + static void + SetErrorCode( + HRESULT err +#if (IFC_VERSION >= 0x0110) + , const char *sFile, int nLine +#endif + ); + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + private: + + static HRESULT m_Err; + static unsigned long m_dwErrHandlingFlags; +}; + + +#endif // IFCERRORS_H__INCLUDED_ diff --git a/code/ff/IFC/ImmBaseTypes.h b/code/ff/IFC/ImmBaseTypes.h new file mode 100644 index 0000000..433d01f --- /dev/null +++ b/code/ff/IFC/ImmBaseTypes.h @@ -0,0 +1,359 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmBaseTypes.h + + PURPOSE: Base Types for Feelit API Foundation Classes + + STARTED: 10/29/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + +**********************************************************************/ + + +#if !defined(AFX_IMMBASETYPES_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMBASETYPES_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +//#include +#include "FeelitApi.h" + +// Version 0x0100 -- IFC10 +// Version 0x0101 -- IFC10p Special release: contains CImmProject::DestroyEffect +// Version 0x0110 -- IFC21 (and IFC20?) +#ifndef IFC_VERSION + #define IFC_VERSION 0x0110 +#endif + +//#if (IFC_VERSION >= 0x0101) + // This means that a user can call delete on a CImmCompoundEffect + // object that was allocated through a CImmProject::CreateEffect() + // and all will be well. +// #define PROTECT_AGAINST_DELETION +//#endif + +#if (IFC_VERSION >= 0x0110) + #define IFC_START_DELAY + #define IFC_EFFECT_CACHING +#endif + +// Add define for DirectInput Device emulating FEELit Device +#define FEELIT_DEVICETYPE_DIRECTINPUT 3 + + +//================================================================ +// TYPE WRAPPERS +//================================================================ + +// +// IMM --> FEELIT Wrappers +// +#define IMM_VERSION FEELIT_VERSION + +#define IMM_DEVICETYPE_DEVICE FEELIT_DEVICETYPE_DEVICE +#define IMM_DEVICETYPE_MOUSE FEELIT_DEVICETYPE_MOUSE +#define IMM_DEVICETYPE_HID FEELIT_DEVICETYPE_HID +#define IMM_DEVICETYPE_DIRECTINPUT FEELIT_DEVICETYPE_DIRECTINPUT + +#define IMM_EFFECT FEELIT_EFFECT +#define LPIMM_EFFECT LPFEELIT_EFFECT +#define LPCIMM_EFFECT LPCFEELIT_EFFECT + +#define IMM_CONDITION FEELIT_CONDITION +#define LPIMM_CONDITION LPFEELIT_CONDITION +#define LPCIMM_CONDITION LPCFEELIT_CONDITION + +#define IMM_CONSTANTFORCE FEELIT_CONSTANTFORCE +#define LPIMM_CONSTANTFORCE LPFEELIT_CONSTANTFORCE +#define LPCIMM_CONSTANTFORCE LPCFEELIT_CONSTANTFORCE + +#define IMM_ELLIPSE FEELIT_ELLIPSE +#define LPIMM_ELLIPSE LPFEELIT_ELLIPSE +#define LPCIMM_ELLIPSE LPCFEELIT_ELLIPSE + +#define IMM_ENCLOSURE FEELIT_ENCLOSURE +#define LPIMM_ENCLOSURE LPFEELIT_ENCLOSURE +#define LPCIMM_ENCLOSURE LPCFEELIT_ENCLOSURE + +#define IMM_PERIODIC FEELIT_PERIODIC +#define LPIMM_PERIODIC LPFEELIT_PERIODIC +#define LPCIMM_PERIODIC LPCFEELIT_PERIODIC + +#define IMM_RAMPFORCE FEELIT_RAMPFORCE +#define LPIMM_RAMPFORCE LPFEELIT_RAMPFORCE +#define LPCIMM_RAMPFORCE LPCFEELIT_RAMPFORCE + +#define IMM_TEXTURE FEELIT_TEXTURE +#define LPIMM_TEXTURE LPFEELIT_TEXTURE +#define LPCIMM_TEXTURE LPCFEELIT_TEXTURE + +#define IMM_ENVELOPE FEELIT_ENVELOPE +#define LPIMM_ENVELOPE LPFEELIT_ENVELOPE +#define LPCIMM_ENVELOPE LPCFEELIT_ENVELOPE + +#define LPIIMM_API LPIFEELIT +#define LPIIMM_EFFECT LPIFEELIT_EFFECT +#define LPIIMM_DEVICE LPIFEELIT_DEVICE + +#define IMM_CUSTOMFORCE FEELIT_CUSTOMFORCE +#define LPIMM_CUSTOMFORCE LPFEELIT_CUSTOMFORCE +#define LPCIMM_CUSTOMFORCE LPCFEELIT_CUSTOMFORCE + +#define LPIMM_ENUMDEVICESCALLBACK LPFEELIT_ENUMDEVICESCALLBACK + +/* Effect Types */ +#define IMM_EFFECTTYPE_ALL FEELIT_FEFFECTTYPE_ALL + +#define IMM_EFFECTTYPE_CONSTANTFORCE FEELIT_FEFFECTTYPE_CONSTANTFORCE +#define IMM_EFFECTTYPE_RAMPFORCE FEELIT_FEFFECTTYPE_RAMPFORCE +#define IMM_EFFECTTYPE_PERIODIC FEELIT_FEFFECTTYPE_PERIODIC +#define IMM_EFFECTTYPE_CONDITION FEELIT_FEFFECTTYPE_CONDITION +#define IMM_EFFECTTYPE_ENCLOSURE FEELIT_FEFFECTTYPE_ENCLOSURE +#define IMM_EFFECTTYPE_ELLIPSE FEELIT_FEFFECTTYPE_ELLIPSE +#define IMM_EFFECTTYPE_TEXTURE FEELIT_FEFFECTTYPE_TEXTURE +#define IMM_EFFECTTYPE_COMPOUND 0x00000008 +#define IMM_EFFECTTYPE_CUSTOMFORCE FEELIT_FEFFECTTYPE_CUSTOMFORCE +#define IMM_EFFECTTYPE_HARDWARE FEELIT_FEFFECTTYPE_HARDWARE + +#define IMM_EFFECTTYPE_FFATTACK FEELIT_FEFFECTTYPE_FFATTACK +#define IMM_EFFECTTYPE_FFFADE FEELIT_FEFFECTTYPE_FFFADE +#define IMM_EFFECTTYPE_SATURATION FEELIT_FEFFECTTYPE_SATURATION +#define IMM_EFFECTTYPE_POSNEGCOEFFICIENTS FEELIT_FEFFECTTYPE_POSNEGCOEFFICIENTS +#define IMM_EFFECTTYPE_POSNEGSATURATION FEELIT_FEFFECTTYPE_POSNEGSATURATION +#define IMM_EFFECTTYPE_DEADBAND FEELIT_FEFFECTTYPE_DEADBAND + +/* Units */ +#define IMM_DEGREES FEELIT_DEGREES +#define IMM_FFNOMINALMAX FEELIT_FFNOMINALMAX +#define IMM_SECONDS FEELIT_SECONDS + +/* Start Flags */ +#define IMM_START_SOLO FEELIT_FSTART_SOLO +#define IMM_START_NODOWNLOAD FEELIT_FSTART_NODOWNLOAD + +/* Status Flags */ +#define IMM_STATUS_PLAYING FEELIT_FSTATUS_PLAYING +#define IMM_STATUS_EMULATED FEELIT_FSTATUS_EMULATED + +/* Stiffness Mask Flags */ +#define IMM_STIFF_NONE FEELIT_FSTIFF_NONE +#define IMM_STIFF_OUTERLEFTWALL FEELIT_FSTIFF_OUTERLEFTWALL +#define IMM_STIFF_INNERLEFTWALL FEELIT_FSTIFF_INNERLEFTWALL +#define IMM_STIFF_INNERRIGHTWALL FEELIT_FSTIFF_INNERRIGHTWALL +#define IMM_STIFF_OUTERRIGHTWALL FEELIT_FSTIFF_OUTERRIGHTWALL +#define IMM_STIFF_OUTERTOPWALL FEELIT_FSTIFF_OUTERTOPWALL +#define IMM_STIFF_INNERTOPWALL FEELIT_FSTIFF_INNERTOPWALL +#define IMM_STIFF_INNERBOTTOMWALL FEELIT_FSTIFF_INNERBOTTOMWALL +#define IMM_STIFF_OUTERBOTTOMWALL FEELIT_FSTIFF_OUTERBOTTOMWALL +#define IMM_STIFF_OUTERANYWALL FEELIT_FSTIFF_OUTERANYWALL +#define IMM_STIFF_INBOUNDANYWALL FEELIT_FSTIFF_INBOUNDANYWALL +#define IMM_STIFF_INNERANYWALL FEELIT_FSTIFF_INNERANYWALL +#define IMM_STIFF_OUTBOUNDANYWALL FEELIT_FSTIFF_OUTBOUNDANYWALL +#define IMM_STIFF_ANYWALL FEELIT_FSTIFF_ANYWALL + +/* Clipping Mask Flags */ +#define IMM_CLIP_NONE FEELIT_FCLIP_NONE +#define IMM_CLIP_OUTERLEFTWALL FEELIT_FCLIP_OUTERLEFTWALL +#define IMM_CLIP_INNERLEFTWALL FEELIT_FCLIP_INNERLEFTWALL +#define IMM_CLIP_INNERRIGHTWALL FEELIT_FCLIP_INNERRIGHTWALL +#define IMM_CLIP_OUTERRIGHTWALL FEELIT_FCLIP_OUTERRIGHTWALL +#define IMM_CLIP_OUTERTOPWALL FEELIT_FCLIP_OUTERTOPWALL +#define IMM_CLIP_INNERTOPWALL FEELIT_FCLIP_INNERTOPWALL +#define IMM_CLIP_INNERBOTTOMWALL FEELIT_FCLIP_INNERBOTTOMWALL +#define IMM_CLIP_OUTERBOTTOMWALL FEELIT_FCLIP_OUTERBOTTOMWALL +#define IMM_CLIP_OUTERANYWALL FEELIT_FCLIP_OUTERANYWALL +#define IMM_CLIP_INNERANYWALL FEELIT_FCLIP_INNERANYWALL +#define IMM_CLIP_ANYWALL FEELIT_FCLIP_ANYWALL + +/* Device capabilities Stuct */ +#define IMM_DEVCAPS FEELIT_DEVCAPS +#define LPIMM_DEVCAPS LPFEELIT_DEVCAPS +#define LPCIMM_DEVCAPS LPCFEELIT_DEVCAPS + +/* Device capabilities flags */ +#define IMM_DEVCAPS_ATTACHED FEELIT_FDEVCAPS_ATTACHED +#define IMM_DEVCAPS_POLLEDDEVICE FEELIT_FDEVCAPS_POLLEDDEVICE +#define IMM_DEVCAPS_EMULATED FEELIT_FDEVCAPS_EMULATED +#define IMM_DEVCAPS_POLLEDDATAFORMAT FEELIT_FDEVCAPS_POLLEDDATAFORMAT +#define IMM_DEVCAPS_FORCEFEEDBACK FEELIT_FDEVCAPS_FORCEFEEDBACK +#define IMM_DEVCAPS_FFATTACK FEELIT_FDEVCAPS_FFATTACK +#define IMM_DEVCAPS_FFFADE FEELIT_FDEVCAPS_FFFADE +#define IMM_DEVCAPS_SATURATION FEELIT_FDEVCAPS_SATURATION +#define IMM_DEVCAPS_POSNEGCOEFFICIENTS FEELIT_FDEVCAPS_POSNEGCOEFFICIENTS +#define IMM_DEVCAPS_POSNEGSATURATION FEELIT_FDEVCAPS_POSNEGSATURATION +#define IMM_DEVCAPS_DEADBAND FEELIT_FDEVCAPS_DEADBAND + +#define LPIMM_DEVICEINSTANCE LPFEELIT_DEVICEINSTANCE +#define LPCIMM_DEVICEOBJECTINSTANCE LPCFEELIT_DEVICEOBJECTINSTANCE +#define LPCIMM_EFFECTINFO LPCFEELIT_EFFECTINFO + +#define IMM_PARAM_DURATION FEELIT_FPARAM_DURATION +#define IMM_PARAM_SAMPLEPERIOD FEELIT_FPARAM_SAMPLEPERIOD +#define IMM_PARAM_GAIN FEELIT_FPARAM_GAIN +#define IMM_PARAM_TRIGGERBUTTON FEELIT_FPARAM_TRIGGERBUTTON +#define IMM_PARAM_TRIGGERREPEATINTERVAL FEELIT_FPARAM_TRIGGERREPEATINTERVAL +#define IMM_PARAM_AXES FEELIT_FPARAM_AXES +#define IMM_PARAM_DIRECTION FEELIT_FPARAM_DIRECTION +#define IMM_PARAM_ENVELOPE FEELIT_FPARAM_ENVELOPE +#define IMM_PARAM_TYPESPECIFICPARAMS FEELIT_FPARAM_TYPESPECIFICPARAMS +#define IMM_PARAM_STARTDELAY FEELIT_FPARAM_STARTDELAY +#define IMM_PARAM_ALLPARAMS FEELIT_FPARAM_ALLPARAMS +#define IMM_PARAM_START FEELIT_FPARAM_START +#define IMM_PARAM_NORESTART FEELIT_FPARAM_NORESTART +#define IMM_PARAM_NODOWNLOAD FEELIT_FPARAM_NODOWNLOAD + +#define IMM_EFFECT_OBJECTIDS FEELIT_FEFFECT_OBJECTIDS +#define IMM_EFFECT_OBJECTOFFSETS FEELIT_FEFFECT_OBJECTOFFSETS +#define IMM_EFFECT_CARTESIAN FEELIT_FEFFECT_CARTESIAN +#define IMM_EFFECT_POLAR FEELIT_FEFFECT_POLAR +#define IMM_EFFECT_SPHERICAL FEELIT_FEFFECT_SPHERICAL + +#define IMM_PARAM_NOTRIGGER FEELIT_PARAM_NOTRIGGER + +/* Offsets */ +#define IMM_MOUSEOFFSET_XAXIS FEELIT_MOUSEOFFSET_XAXIS +#define IMM_MOUSEOFFSET_YAXIS FEELIT_MOUSEOFFSET_YAXIS +#define IMM_MOUSEOFFSET_ZAXIS FEELIT_MOUSEOFFSET_ZAXIS +#define IMM_MOUSEOFFSET_XFORCE FEELIT_MOUSEOFFSET_XFORCE +#define IMM_MOUSEOFFSET_YFORCE FEELIT_MOUSEOFFSET_YFORCE +#define IMM_MOUSEOFFSET_ZFORCE FEELIT_MOUSEOFFSET_ZFORCE +#define IMM_MOUSEOFFSET_BUTTON0 FEELIT_MOUSEOFFSET_BUTTON0 +#define IMM_MOUSEOFFSET_BUTTON1 FEELIT_MOUSEOFFSET_BUTTON1 +#define IMM_MOUSEOFFSET_BUTTON2 FEELIT_MOUSEOFFSET_BUTTON2 +#define IMM_MOUSEOFFSET_BUTTON3 FEELIT_MOUSEOFFSET_BUTTON3 + +/* Cooperative Level Flags */ +#define IMM_COOPLEVEL_FOREGROUND FEELIT_FCOOPLEVEL_FOREGROUND +#define IMM_COOPLEVEL_BACKGROUND FEELIT_FCOOPLEVEL_BACKGROUND + +/* Enumeration codes */ +#define IMM_ENUM_STOP FEELIT_ENUM_STOP +#define IMM_ENUM_CONTINUE FEELIT_ENUM_CONTINUE + +#define IMM_ENUMDEV_ALLDEVICES FEELIT_FENUMDEV_ALLDEVICES +#define IMM_ENUMDEV_ATTACHEDONLY FEELIT_FENUMDEV_ATTACHEDONLY +#define IMM_ENUMDEV_FORCEFEEDBACK FEELIT_FENUMDEV_FORCEFEEDBACK + +/* Return values */ +#define IMM_RESULT_OK FEELIT_RESULT_OK +#define IMM_RESULT_NOTATTACHED FEELIT_RESULT_NOTATTACHED +#define IMM_RESULT_BUFFEROVERFLOW FEELIT_RESULT_BUFFEROVERFLOW +#define IMM_RESULT_PROPNOEFFECT FEELIT_RESULT_PROPNOEFFECT +#define IMM_RESULT_NOEFFECT FEELIT_RESULT_NOEFFECT +#define IMM_RESULT_POLLEDDEVICE FEELIT_RESULT_POLLEDDEVICE +#define IMM_RESULT_DOWNLOADSKIPPED FEELIT_RESULT_DOWNLOADSKIPPED +#define IMM_RESULT_EFFECTRESTARTED FEELIT_RESULT_EFFECTRESTARTED +#define IMM_RESULT_TRUNCATED FEELIT_RESULT_TRUNCATED +#define IMM_RESULT_TRUNCATEDANDRESTARTED FEELIT_RESULT_TRUNCATEDANDRESTARTED +#define IMM_ERROR_OLDFEELITVERSION FEELIT_ERROR_OLDFEELITVERSION +#define IMM_ERROR_BETAFEELITVERSION FEELIT_ERROR_BETAFEELITVERSION +#define IMM_ERROR_BADDRIVERVER FEELIT_ERROR_BADDRIVERVER +#define IMM_ERROR_DEVICENOTREG FEELIT_ERROR_DEVICENOTREG +#define IMM_ERROR_NOTFOUND FEELIT_ERROR_NOTFOUND +#define IMM_ERROR_OBJECTNOTFOUND FEELIT_ERROR_OBJECTNOTFOUND +#define IMM_ERROR_INVALIDPARAM FEELIT_ERROR_INVALIDPARAM +#define IMM_ERROR_NOINTERFACE FEELIT_ERROR_NOINTERFACE +#define IMM_ERROR_GENERIC FEELIT_ERROR_GENERIC +#define IMM_ERROR_OUTOFMEMORY FEELIT_ERROR_OUTOFMEMORY +#define IMM_ERROR_UNSUPPORTED FEELIT_ERROR_UNSUPPORTED +#define IMM_ERROR_NOTINITIALIZED FEELIT_ERROR_NOTINITIALIZED +#define IMM_ERROR_ALREADYINITIALIZED FEELIT_ERROR_ALREADYINITIALIZED +#define IMM_ERROR_NOAGGREGATION FEELIT_ERROR_NOAGGREGATION +#define IMM_ERROR_OTHERAPPHASPRIO FEELIT_ERROR_OTHERAPPHASPRIO +#define IMM_ERROR_INPUTLOST FEELIT_ERROR_INPUTLOST +#define IMM_ERROR_ACQUIRED FEELIT_ERROR_ACQUIRED +#define IMM_ERROR_NOTACQUIRED FEELIT_ERROR_NOTACQUIRED +#define IMM_ERROR_READONLY FEELIT_ERROR_READONLY +#define IMM_ERROR_HANDLEEXISTS FEELIT_ERROR_HANDLEEXISTS +#define IMM_ERROR_INSUFFICIENTPRIVS FEELIT_ERROR_INSUFFICIENTPRIVS +#define IMM_ERROR_DEVICEFULL FEELIT_ERROR_DEVICEFULL +#define IMM_ERROR_MOREDATA FEELIT_ERROR_MOREDATA +#define IMM_ERROR_NOTDOWNLOADED FEELIT_ERROR_NOTDOWNLOADED +#define IMM_ERROR_HASEFFECTS FEELIT_ERROR_HASEFFECTS +#define IMM_ERROR_NOTEXCLUSIVEACQUIRED FEELIT_ERROR_NOTEXCLUSIVEACQUIRED +#define IMM_ERROR_INCOMPLETEEFFECT FEELIT_ERROR_INCOMPLETEEFFECT +#define IMM_ERROR_NOTBUFFERED FEELIT_ERROR_NOTBUFFERED +#define IMM_ERROR_EFFECTPLAYING FEELIT_ERROR_EFFECTPLAYING +#define IMM_ERROR_INTERNAL FEELIT_ERROR_INTERNAL +#define IMM_ERROR_INACTIVE FEELIT_ERROR_INACTIVE + +//================================================================ +// GUID WRAPPERS +//================================================================ + +// +// Immersion --> Feelit Wrappers +// +#define GUID_Imm_ConstantForce GUID_Feelit_ConstantForce +#define GUID_Imm_RampForce GUID_Feelit_RampForce +#define GUID_Imm_Square GUID_Feelit_Square +#define GUID_Imm_Sine GUID_Feelit_Sine +#define GUID_Imm_Triangle GUID_Feelit_Triangle +#define GUID_Imm_SawtoothUp GUID_Feelit_SawtoothUp +#define GUID_Imm_SawtoothDown GUID_Feelit_SawtoothDown +#define GUID_Imm_Spring GUID_Feelit_Spring +#define GUID_Imm_DeviceSpring GUID_Feelit_DeviceSpring +#define GUID_Imm_Damper GUID_Feelit_Damper +#define GUID_Imm_Inertia GUID_Feelit_Inertia +#define GUID_Imm_Friction GUID_Feelit_Friction +#define GUID_Imm_Texture GUID_Feelit_Texture +#define GUID_Imm_Grid GUID_Feelit_Grid +#define GUID_Imm_Enclosure GUID_Feelit_Enclosure +#define GUID_Imm_Ellipse GUID_Feelit_Ellipse +#define GUID_Imm_CustomForce GUID_Feelit_CustomForce + +#define CLSID_Imm CLSID_Feelit +#define CLSID_ImmDevice CLSID_FeelitDevice +#define GUID_Imm_XAxis GUID_Feelit_XAxis +#define GUID_Imm_YAxis GUID_Feelit_YAxis +#define GUID_Imm_ZAxis GUID_Feelit_ZAxis +#define GUID_Imm_RxAxis GUID_Feelit_RxAxis +#define GUID_Imm_RyAxis GUID_Feelit_RyAxis +#define GUID_Imm_RzAxis GUID_Feelit_RzAxis +#define GUID_Imm_Slider GUID_Feelit_Slider +#define GUID_Imm_Button GUID_Feelit_Button +#define GUID_Imm_Key GUID_Feelit_Key +#define GUID_Imm_POV GUID_Feelit_POV +#define GUID_Imm_Unknown GUID_Feelit_Unknown +#define GUID_Imm_Mouse GUID_Feelit_Mouse + + +#endif // !defined(AFX_IMMBASETYPES_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) + + + + + + + + + + + + + + + + + diff --git a/code/ff/IFC/ImmBox.h b/code/ff/IFC/ImmBox.h new file mode 100644 index 0000000..6d45539 --- /dev/null +++ b/code/ff/IFC/ImmBox.h @@ -0,0 +1,170 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmBox.h + + PURPOSE: Box Class for Immersion Foundation Classes + + STARTED: 11/04/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + 11/15/99 sdr (Steve Rank): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMBOX_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMBOX_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmBaseTypes.h" +#include "ImmEffect.h" +#include "ImmEnclosure.h" + + + +//================================================================ +// Constants +//================================================================ + +const POINT IMM_BOX_MOUSE_POS_AT_START = { MAXLONG, MAXLONG }; + +#define IMM_BOX_DEFAULT_STIFFNESS 5000 +#define IMM_BOX_DEFAULT_WIDTH 10 +#define IMM_BOX_DEFAULT_HEIGHT IMM_ENCLOSURE_HEIGHT_AUTO +#define IMM_BOX_DEFAULT_WALL_WIDTH IMM_ENCLOSURE_WALL_WIDTH_AUTO + +#define IMM_BOX_DEFAULT_CENTER_POINT IMM_BOX_MOUSE_POS_AT_START + + + + +//================================================================ +// CImmBox +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmBox : public CImmEnclosure +{ + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CImmBox(); + + // Destructor + virtual + ~CImmBox(); + + + // + // ATTRIBUTES + // + + public: + + + BOOL + ChangeParameters( + POINT pntCenter, + LONG lStiffness = IMM_EFFECT_DONT_CHANGE, + DWORD dwWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwHeight = IMM_EFFECT_DONT_CHANGE, + DWORD dwWallWidth = IMM_EFFECT_DONT_CHANGE, + CImmEffect* pInsideEffect = (CImmEffect*) IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParameters( + LPCRECT pRectOutside, + LONG lStiffness = IMM_EFFECT_DONT_CHANGE, + DWORD dwWallWidth = IMM_EFFECT_DONT_CHANGE, + CImmEffect* pInsideEffect = (CImmEffect*) IMM_EFFECT_DONT_CHANGE + ); + + + // + // OPERATIONS + // + + public: + + + BOOL + Initialize( + CImmDevice* pDevice, + DWORD dwWidth = IMM_ENCLOSURE_DEFAULT_WIDTH, + DWORD dwHeight = IMM_ENCLOSURE_DEFAULT_HEIGHT, + LONG lStiffness = IMM_BOX_DEFAULT_STIFFNESS, + DWORD dwWallWidth = IMM_BOX_DEFAULT_WALL_WIDTH, + POINT pntCenter = IMM_BOX_DEFAULT_CENTER_POINT, + CImmEffect* pInsideEffect = NULL, + DWORD dwNoDownload = 0 + ); + + + BOOL + Initialize( + CImmDevice* pDevice, + LPCRECT pRectOutside, + LONG lStiffness = IMM_BOX_DEFAULT_STIFFNESS, + DWORD dwWallWidth = IMM_BOX_DEFAULT_WALL_WIDTH, + CImmEffect* pInsideEffect = NULL, + DWORD dwNoDownload = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + + // + // INTERNAL DATA + // + + protected: + +}; + + +#endif // !defined(AFX_IMMBOX_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmCompoundEffect.h b/code/ff/IFC/ImmCompoundEffect.h new file mode 100644 index 0000000..694f34d --- /dev/null +++ b/code/ff/IFC/ImmCompoundEffect.h @@ -0,0 +1,228 @@ +/********************************************************************** + Copyright (c) 1999 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmCompoundEffect.h + + PURPOSE: Manages Compound Effects for Force Foundation Classes + + STARTED: 2/24/99 by Jeff Mallett + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#include "ImmBaseTypes.h" +#include "ImmEffect.h" + +#include "ImmIFR.h" + +#if !defined(__FEELCOMPOUNDEFFECT_H) +#define __FEELCOMPOUNDEFFECT_H + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +/* +** IMM_FFE_FILEEFFECT - struct used by DX7 to read and write to FFE +** files. This struct is different from DIFILEEFFECT due to the use +** of the non const LPDIEFFECT. An LPDIEFFECT is needed to be able to +** collect information from IFC class objects. This should be defined +** elsewhere, but no more appropriate header currently exists. +*/ +typedef struct IMM_FFE_FILEEFFECT{ + DWORD dwSize; + GUID GuidEffect; + LPDIEFFECT lpDiEffect; + CHAR szFriendlyName[MAX_PATH]; +}IMM_FFE_FILEEFFECT, *LPIMM_FFE_FILEEFFECT; + +//================================================================ +// CImmCompoundEffect +//================================================================ +// Represents a compound effect, such as might be created in +// Immersion Studio. Contains an array of effect objects. +// Methods iterate over component effects, passing the message +// to each one. +// Also, has stuff for being used by CImmProject: +// * next pointer so can be put on a linked list +// * force name + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmCompoundEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + +public://### protected: + // Constructs a CImmCompoundEffect + // Don't try to construct a CImmCompoundEffect yourself. + // Instead let CImmProject construct it for you. + CImmCompoundEffect( + IFREffect **hEffects, + long nEffects, + LPCSTR pEffectName + ); + + public: + + ~CImmCompoundEffect(); + + + // + // ATTRIBUTES + // + + public: + + long + GetNumberOfContainedEffects() const + { return m_nEffects; } + + const char * + GetName() const + { return m_lpszName; } + + GENERIC_EFFECT_PTR + GetContainedEffect( + long index + ); + + GENERIC_EFFECT_PTR + GetContainedEffect( + LPCSTR lpszEffectName + ); + + DWORD + GetEffectType(); + + // + // OPERATIONS + // + + public: + + // Start all the contained effects + BOOL Start( + DWORD dwIterations = IMM_EFFECT_DONT_CHANGE, + DWORD dwFlags = 0 + ); + + // Stop all the contained effects + BOOL Stop(); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL initialize( + CImmDevice* pDevice, + IFREffect **hEffects, + DWORD dwNoDownload + ); + + BOOL + set_contained_effect( + GENERIC_EFFECT_PTR pObject, + int index = 0 + ); + + BOOL + set_name( + const char *lpszName + ); + + void + set_next( + CImmCompoundEffect *pNext + ) + { m_pNext = pNext; } + + CImmCompoundEffect * + get_next() const + { return m_pNext; } + + void + set_objID( + GUID* pobjID + ) + { m_objID = *pobjID; } + + BOOL + set_contained_obj_IDs( + GUID *guidList + ); + + int + buffer_ifr_object(TCHAR* pData); + + BOOL + get_ffe_object(LPIMM_FFE_FILEEFFECT pffeObject); + + + // + // FRIENDS + // + + public: + + friend class CImmProject; + + + // + // INTERNAL DATA + // + + protected: + + GENERIC_EFFECT_PTR *m_paEffects; // Array of force class object pointers + long m_nEffects; // Number of effects in m_paEffects + + private: + + char *m_lpszName; // Name of the compound effect + GUID m_objID; + GUID *m_pContainedObjIDs; + CImmCompoundEffect *m_pNext; // Next compound effect in the project +#ifdef PROTECT_AGAINST_DELETION + CImmProject *m_pOwningProject; +#endif +}; + +#endif diff --git a/code/ff/IFC/ImmCondition.h b/code/ff/IFC/ImmCondition.h new file mode 100644 index 0000000..abd0378 --- /dev/null +++ b/code/ff/IFC/ImmCondition.h @@ -0,0 +1,451 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmCondition.h + + PURPOSE: Immersion Foundation Classes Base Condition Effect + + STARTED: Oct.10.97 + + NOTES/REVISIONS: + Mar.02.99 jrm (Jeff Mallett): Force-->Feel renaming + Mar.02.99 jrm: Added GetIsCompatibleGUID + Mar.15.99 jrm: __declspec(dllimport/dllexport) the whole class + Nov.15.99 efw (Evan Wies): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMCondition_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMCondition_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmBaseTypes.h" +#include "ImmEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +const POINT IMM_CONDITION_PT_NULL = { 0, 0 }; + +#define IMM_CONDITION_DEFAULT_COEFFICIENT 2500 +#define IMM_CONDITION_DEFAULT_SATURATION 10000 +#define IMM_CONDITION_DEFAULT_DEADBAND 100 +#define IMM_CONDITION_DEFAULT_CENTER_POINT IMM_EFFECT_MOUSE_POS_AT_START +#define IMM_CONDITION_DEFAULT_DURATION INFINITE + +typedef enum { + IC_NULL = 0, + IC_POSITIVE_COEFFICIENT, + IC_NEGATIVE_COEFFICIENT, + IC_POSITIVE_SATURATION, + IC_NEGATIVE_SATURATION, + IC_DEAD_BAND, + IC_AXIS, + IC_CENTER, + IC_DIRECTION_X, + IC_DIRECTION_Y, + IC_ANGLE, + IC_CONDITION_X, + IC_CONDITION_Y +} IC_ArgumentType; + +#define IC_CONDITION IC_CONDITION_X + + + +//================================================================ +// CImmCondition +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmCondition : public CImmEffect +{ + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CImmCondition( + const GUID& rguidEffect + ); + + // Destructor + virtual + ~CImmCondition(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + virtual DWORD GetEffectType() + { return IMM_EFFECTTYPE_CONDITION; } + + // Use this form for single-axis and dual-axis effects + BOOL + ChangeConditionParams( + LPCIMM_CONDITION pConditionX, + LPCIMM_CONDITION pConditionY + ); + + // Use this form for directional effects + BOOL + ChangeConditionParams( + LPCIMM_CONDITION pCondition, + LONG lDirectionX, + LONG lDirectionY + ); + + // Use this form for directional effects + BOOL + ChangeConditionParamsPolar( + LPCIMM_CONDITION pCondition, + LONG lAngle + ); + + // Use this form for single-axis, dual-axis, or directional effects + BOOL + ChangeConditionParamsX( + LONG lPositiveCoefficient, + LONG lNegativeCoefficient = IMM_EFFECT_DONT_CHANGE, + DWORD dwPositiveSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegativeSaturation = IMM_EFFECT_DONT_CHANGE, + LONG lDeadBand = IMM_EFFECT_DONT_CHANGE, + POINT pntCenter = IMM_EFFECT_DONT_CHANGE_POINT, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeConditionParamsPolarX( + LONG lPositiveCoefficient, + LONG lNegativeCoefficient, + DWORD dwPositiveSaturation, + DWORD dwNegativeSaturation, + LONG lDeadBand, + POINT pntCenter, + LONG lAngle + ); + + BOOL + ChangeConditionParamsY( + LONG lPositiveCoefficient, + LONG lNegativeCoefficient = IMM_EFFECT_DONT_CHANGE, + DWORD dwPositiveSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegativeSaturation = IMM_EFFECT_DONT_CHANGE, + LONG lDeadBand = IMM_EFFECT_DONT_CHANGE, + POINT pntCenter = IMM_EFFECT_DONT_CHANGE_POINT, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeConditionParamsPolarY( + LONG lPositiveCoefficient, + LONG lNegativeCoefficient, + DWORD dwPositiveSaturation, + DWORD dwNegativeSaturation, + LONG lDeadBand, + POINT pntCenter, + LONG lAngle + ); + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + ChangeConditionParams( + LONG lPositiveCoefficient, + LONG lNegativeCoefficient = IMM_EFFECT_DONT_CHANGE, + DWORD dwPositiveSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegativeSaturation = IMM_EFFECT_DONT_CHANGE, + LONG lDeadBand = IMM_EFFECT_DONT_CHANGE, + POINT pntCenter = IMM_EFFECT_DONT_CHANGE_POINT, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeConditionParamsPolar( + LONG lPositiveCoefficient, + LONG lNegativeCoefficient, + DWORD dwPositiveSaturation, + DWORD dwNegativeSaturation, + LONG lDeadBand, + POINT pntCenter, + LONG lAngle + ); + + BOOL ChangePositiveCoefficientX( LONG lPositiveCoefficient ); + BOOL ChangeNegativeCoefficientX( LONG lNegativeCoefficient ); + BOOL ChangePositiveSaturationX( DWORD dwPositiveSaturation ); + BOOL ChangeNegativeSaturationX( DWORD dwNegativeSaturation ); + BOOL ChangeDeadBandX( LONG lDeadBand ); + + BOOL ChangePositiveCoefficientY( LONG lPositiveCoefficient ); + BOOL ChangeNegativeCoefficientY( LONG lNegativeCoefficient ); + BOOL ChangePositiveSaturationY( DWORD dwPositiveSaturation ); + BOOL ChangeNegativeSaturationY( DWORD dwNegativeSaturation ); + BOOL ChangeDeadBandY( LONG lDeadBand ); + + BOOL ChangePositiveCoefficient( LONG lPositiveCoefficient ); + BOOL ChangeNegativeCoefficient( LONG lNegativeCoefficient ); + BOOL ChangePositiveSaturation( DWORD dwPositiveSaturation ); + BOOL ChangeNegativeSaturation( DWORD dwNegativeSaturation ); + BOOL ChangeDeadBand( LONG lDeadBand ); + + BOOL + SetCenter( + POINT pntCenter + ); + + BOOL + ChangeConditionParams2( + IC_ArgumentType type, + ... + ); + + BOOL GetPositiveCoefficientX( LONG &lPositiveCoefficient ); + BOOL GetNegativeCoefficientX( LONG &lNegativeCoefficient ); + BOOL GetPositiveSaturationX( DWORD &dwPositiveSaturation ); + BOOL GetNegativeSaturationX( DWORD &dwNegativeSaturation ); + BOOL GetDeadBandX( LONG &lDeadBand ); + + BOOL GetPositiveCoefficientY( LONG &lPositiveCoefficient ); + BOOL GetNegativeCoefficientY( LONG &lNegativeCoefficient ); + BOOL GetPositiveSaturationY( DWORD &dwPositiveSaturation ); + BOOL GetNegativeSaturationY( DWORD &dwNegativeSaturation ); + BOOL GetDeadBandY( LONG &lDeadBand ); + + BOOL GetAxis( DWORD &dwfAxis ); + BOOL GetCenter( POINT &pntCenter ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + const IMM_EFFECT &effect, + DWORD dwNoDownload = 0 + ); + + // Use this form for single-axis and dual-axis effects + BOOL + InitCondition( + CImmDevice* pDevice, + LPCIMM_CONDITION pConditionX, + LPCIMM_CONDITION pConditionY, + BOOL bUseDeviceCoordinates = FALSE, + DWORD dwNoDownload = 0 + ); + + + // Use this form for directional effects + BOOL + InitCondition( + CImmDevice* pDevice, + LPCIMM_CONDITION pCondition, + LONG lDirectionX, + LONG lDirectionY, + BOOL bUseDeviceCoordinates = FALSE, + DWORD dwNoDownload = 0 + ); + + + // Use this form for directional effects + BOOL + InitConditionPolar( + CImmDevice* pDevice, + LPCIMM_CONDITION pCondition, + LONG lAngle, + BOOL bUseDeviceCoordinates = FALSE, + DWORD dwNoDownload = 0 + ); + + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + InitCondition( + CImmDevice* pDevice, + LONG lPositiveCoefficient = IMM_CONDITION_DEFAULT_COEFFICIENT, + LONG lNegativeCoefficient = IMM_CONDITION_DEFAULT_COEFFICIENT, + DWORD dwPositiveSaturation = IMM_CONDITION_DEFAULT_SATURATION, + DWORD dwNegativeSaturation = IMM_CONDITION_DEFAULT_SATURATION, + LONG lDeadBand = IMM_CONDITION_DEFAULT_DEADBAND, + DWORD dwfAxis = IMM_EFFECT_AXIS_BOTH, + POINT pntCenter = IMM_CONDITION_DEFAULT_CENTER_POINT, + LONG lDirectionX = IMM_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = IMM_EFFECT_DEFAULT_DIRECTION_Y, + BOOL bUseDeviceCoordinates = FALSE, + DWORD dwNoDownload = 0 + ); + + // Use this form for directional effects + BOOL + InitConditionPolar( + CImmDevice* pDevice, + LONG lPositiveCoefficient = IMM_CONDITION_DEFAULT_COEFFICIENT, + LONG lNegativeCoefficient = IMM_CONDITION_DEFAULT_COEFFICIENT, + DWORD dwPositiveSaturation = IMM_CONDITION_DEFAULT_SATURATION, + DWORD dwNegativeSaturation = IMM_CONDITION_DEFAULT_SATURATION, + LONG lDeadBand = IMM_CONDITION_DEFAULT_DEADBAND, + POINT pntCenter = IMM_CONDITION_DEFAULT_CENTER_POINT, + LONG lAngle = IMM_EFFECT_DEFAULT_ANGLE, + BOOL bUseDeviceCoordinates = FALSE, + DWORD dwNoDownload = 0 + ); + + + virtual BOOL + Start( + DWORD dwIterations = 1, + DWORD dwFlags = 0, + BOOL bAllowStartDelayEmulation = true + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + void + convert_line_point_to_offset( + POINT pntOnLine + ); + + BOOL + set_parameters( + DWORD dwfAxis, + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + LPCIMM_CONDITION pConditionX, + LPCIMM_CONDITION pConditionY + ); + + BOOL + set_parameters( + DWORD dwfAxis, + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + LONG lPositiveCoefficient, + LONG lNegativeCoefficient, + DWORD dwPositiveSaturation, + DWORD dwNegativeSaturation, + LONG lDeadBand, + POINT pntCenter + ); + + DWORD + change_parameters( + LONG lDirection0, + LONG lDirection1, + LONG lPositiveCoefficient, + LONG lNegativeCoefficient, + DWORD dwPositiveSaturation, + DWORD dwNegativeSaturation, + LONG lDeadBand, + POINT pntCenter, + int fAxis + ); + + DWORD + change_parameters( + LONG lDirection0, + LONG lDirection1, + LPCIMM_CONDITION pConditionX, + LPCIMM_CONDITION pConditionY + ); + + int + buffer_ifr_data( + TCHAR* pData + ); + + BOOL + get_ffe_data( + LPDIEFFECT pdiEffect + ); + + // + // INTERNAL DATA + // + + IMM_CONDITION m_aCondition[2]; + DWORD m_dwfAxis; + BOOL m_bUseMousePosAtStart; + + protected: + BOOL m_bUseDeviceCoordinates; + +}; + + +// +// INLINES +// + +inline BOOL +CImmCondition::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_Spring) || + IsEqualGUID(guid, GUID_Imm_DeviceSpring) || + IsEqualGUID(guid, GUID_Imm_Damper) || + IsEqualGUID(guid, GUID_Imm_Inertia) || + IsEqualGUID(guid, GUID_Imm_Friction) || + IsEqualGUID(guid, GUID_Imm_Texture) || + IsEqualGUID(guid, GUID_Imm_Grid); +} + +#endif // !defined(AFX_IMMCondition_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) + diff --git a/code/ff/IFC/ImmConstant.h b/code/ff/IFC/ImmConstant.h new file mode 100644 index 0000000..8a70ad7 --- /dev/null +++ b/code/ff/IFC/ImmConstant.h @@ -0,0 +1,219 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmConstant.h + + PURPOSE: Base Constant Class for Immersion Foundation Classes + + STARTED: 11/03/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + 11/15/99 sdr (Steve Rank): Converted to IFC +**********************************************************************/ + + +#if !defined(AFX_IMMCONSTANT_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMCONSTANT_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmBaseTypes.h" +#include "ImmEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +const POINT IMM_CONSTANT_DEFAULT_DIRECTION = { 1, 0 }; +#define IMM_CONSTANT_DEFAULT_DURATION 1000 // Milliseconds +#define IMM_CONSTANT_DEFAULT_MAGNITUDE 5000 + + + +//================================================================ +// CImmConstant +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmConstant : public CImmEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CImmConstant(); + + // Destructor + virtual + ~CImmConstant(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + virtual DWORD GetEffectType() + { return IMM_EFFECTTYPE_CONSTANTFORCE; } + + BOOL + ChangeParameters( + LONG lDirectionX, + LONG lDirectionY, + DWORD dwDuration = IMM_EFFECT_DONT_CHANGE, + LONG lMagnitude = IMM_EFFECT_DONT_CHANGE, + LPIMM_ENVELOPE pEnvelope = (LPIMM_ENVELOPE) IMM_EFFECT_DONT_CHANGE_PTR + ); + + BOOL + ChangeParametersPolar( + LONG lAngle, + DWORD dwDuration = IMM_EFFECT_DONT_CHANGE, + LONG lMagnitude = IMM_EFFECT_DONT_CHANGE, + LPIMM_ENVELOPE pEnvelope = (LPIMM_ENVELOPE) IMM_EFFECT_DONT_CHANGE_PTR + ); + + BOOL ChangeMagnitude( LONG lMagnitude ); + BOOL GetMagnitude( LONG &lMagnitude ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + const IMM_EFFECT &effect, + DWORD dwNoDownload = 0 + ); + + virtual + BOOL + Initialize( + CImmDevice* pDevice, + LONG lDirectionX = IMM_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = IMM_EFFECT_DEFAULT_DIRECTION_Y, + DWORD dwDuration = IMM_CONSTANT_DEFAULT_DURATION, + LONG lMagnitude = IMM_CONSTANT_DEFAULT_MAGNITUDE, + LPIMM_ENVELOPE pEnvelope = NULL, + DWORD dwNoDownload = 0 + ); + + virtual + BOOL + InitializePolar( + CImmDevice* pDevice, + LONG lAngle = IMM_EFFECT_DEFAULT_ANGLE, + DWORD dwDuration = IMM_CONSTANT_DEFAULT_DURATION, + LONG lMagnitude = IMM_CONSTANT_DEFAULT_MAGNITUDE, + LPIMM_ENVELOPE pEnvelope = NULL, + DWORD dwNoDownload = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + DWORD dwDuration, + LONG lMagnitude, + LPIMM_ENVELOPE pEnvelope + ); + + DWORD + change_parameters( + LONG lDirection0, + LONG lDirection1, + DWORD dwDuration, + LONG lMagnitude, + LPIMM_ENVELOPE pEnvelope + ); + + int + buffer_ifr_data( + TCHAR* pData + ); + + BOOL + get_ffe_data( + LPDIEFFECT pdiEffect + ); + + // + // INTERNAL DATA + // + + IMM_CONSTANTFORCE m_ConstantForce; + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CImmConstant::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_ConstantForce); +} + + +#endif // !defined(AFX_IMMCONSTANT_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmDXDevice.h b/code/ff/IFC/ImmDXDevice.h new file mode 100644 index 0000000..f1f2695 --- /dev/null +++ b/code/ff/IFC/ImmDXDevice.h @@ -0,0 +1,148 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmDXDevice.h + + PURPOSE: Abstraction of DirectX Force Feedback device + + STARTED: 10/10/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + +#ifndef ImmDXDevice_h +#define ImmDXDevice_h + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmDevice.h" + + +//================================================================ +// CImmDXDevice +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmDXDevice : public CImmDevice +{ + + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CImmDXDevice(); + + // Destructor + virtual + ~CImmDXDevice(); + + + // + // ATTRIBUTES + // + + public: + + virtual LPIIMM_API + GetAPI() + { return (LPIIMM_API) m_piApi; } // actually LPDIRECTINPUT + + virtual LPIIMM_DEVICE + GetDevice() + { return (LPIIMM_DEVICE) m_piDevice; } // actually LPDIRECTINPUTDEVICE2 + + virtual DWORD GetProductType(); + + virtual BOOL GetDriverVersion( + DWORD &dwFFDriverVersion, + DWORD &dwFirmwareRevision, + DWORD &dwHardwareRevision); + + virtual int GetProductName(LPTSTR lpszProductName, int nMaxCount); + virtual int GetProductGUIDString(LPTSTR lpszGUID, int nMaxCount); + virtual GUID GetProductGUID(); + + // + // OPERATIONS + // + + public: + + BOOL + Initialize( + HANDLE hinstApp, + HANDLE hwndApp, + LPDIRECTINPUT pDI = NULL, + LPDIRECTINPUTDEVICE2 piDevice = NULL, + BOOL bEnumerate = TRUE + ); + + virtual BOOL + GetCurrentPosition( long &lXPos, long &lYPos ); + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + virtual void + reset(); + + friend class CImmDevices; + static BOOL CALLBACK + devices_enum_proc( + LPDIDEVICEINSTANCE pImmDevInst, + LPVOID pv + ); + + + // + // INTERNAL DATA + // + + protected: + + // TODO: these are unused... delete them in future rev + BOOL m_bpDIPreExist; + BOOL m_bpDIDevicePreExist; + // end of useless variables + + LPDIRECTINPUT m_piApi; + LPDIRECTINPUTDEVICE2 m_piDevice; +}; + +#endif // ForceDXDevice_h diff --git a/code/ff/IFC/ImmDamper.h b/code/ff/IFC/ImmDamper.h new file mode 100644 index 0000000..7edef39 --- /dev/null +++ b/code/ff/IFC/ImmDamper.h @@ -0,0 +1,185 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmDamper.h + + PURPOSE: Immersion Foundation Classes Damper Effect + + STARTED: Oct.14.97 + + NOTES/REVISIONS: + Mar.02.99 jrm (Jeff Mallett): Force-->Feel renaming + Mar.02.99 jrm: Added GetIsCompatibleGUID + Mar.15.99 jrm: __declspec(dllimport/dllexport) the whole class + Nov.15.99 efw (Evan Wies): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMDamper_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMDamper_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include +#include "ImmCondition.h" + + +//================================================================ +// Constants +//================================================================ + +#define IMM_DAMPER_DEFAULT_VISCOSITY 2500 +#define IMM_DAMPER_DEFAULT_SATURATION 10000 +#define IMM_DAMPER_DEFAULT_MIN_VELOCITY 0 + + + +//================================================================ +// CImmDamper +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmDamper : public CImmCondition +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CImmDamper(); + + // Destructor + virtual ~CImmDamper(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + LONG lViscosity, + DWORD dwSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwMinVelocity = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParametersPolar( + LONG lViscosity, + DWORD dwSaturation, + DWORD dwMinVelocity, + LONG lAngle + ); + + BOOL ChangeViscosity( LONG lViscosity ); + + BOOL ChangeMinVelocityX( DWORD dwMinVelocity ); + BOOL ChangeMinVelocityY( DWORD dwMinVelocity ); + //For setting both axes to the same value + BOOL ChangeMinVelocity( DWORD dwMinVelocity ); + + BOOL GetViscosity( LONG &lViscosity ); + + BOOL GetMinVelocityX( DWORD &dwMinVelocity ); + BOOL GetMinVelocityY( DWORD &dwMinVelocity ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + DWORD dwViscosity = IMM_DAMPER_DEFAULT_VISCOSITY, + DWORD dwSaturation = IMM_DAMPER_DEFAULT_SATURATION, + DWORD dwMinVelocity = IMM_DAMPER_DEFAULT_MIN_VELOCITY, + DWORD dwfAxis = IMM_EFFECT_AXIS_BOTH, + LONG lDirectionX = IMM_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = IMM_EFFECT_DEFAULT_DIRECTION_Y, + DWORD dwNoDownload = 0 + ); + + virtual BOOL + InitializePolar( + CImmDevice* pDevice, + DWORD dwViscosity, + DWORD dwSaturation, + DWORD dwMinVelocity, + LONG lAngle, + DWORD dwNoDownload = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CImmDamper::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_Damper); +} + + +#endif // !defined(AFX_IMMDamper_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmDevice.h b/code/ff/IFC/ImmDevice.h new file mode 100644 index 0000000..9a0384a --- /dev/null +++ b/code/ff/IFC/ImmDevice.h @@ -0,0 +1,281 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmDevice.h + + PURPOSE: Abstract Base Device Class for Force Foundation Classes + + STARTED: 10/10/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + 3/16/99 jrm: Made abstract. Moved functionality to CImmMouse/CImmDXDevice + +**********************************************************************/ + +#if !defined(AFX_FORCEDEVICE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FORCEDEVICE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#define DIRECTINPUT_VERSION 0x0800 //[ 0x0300 | 0x0500 | 0x0700 | 0x0800 ] +#include +#include "ImmBaseTypes.h" + +#ifdef IFC_EFFECT_CACHING + #include "ImmEffectSuite.h" +#endif + + +//================================================================ +// Device and Technology types +//================================================================ + +//Company IDs +const DWORD IMM_OTHERPARTNER =0x01000000; +const DWORD IMM_IMMERSION =0x02000000; +const DWORD IMM_ACTLABS =0x03000000; +const DWORD IMM_ANKO =0x04000000; +const DWORD IMM_AVB =0x05000000; +const DWORD IMM_BOEDER =0x06000000; +const DWORD IMM_CHPRODUCTS =0x07000000; +const DWORD IMM_CHIC =0x08000000; +const DWORD IMM_GUILLEMOT =0x09000000; +const DWORD IMM_GENIUS =0x0a000000; +const DWORD IMM_HAPP =0x0b000000; +const DWORD IMM_INTERACT =0x0c000000; +const DWORD IMM_INTERACTIVEIO =0x0d000000; +const DWORD IMM_KYE =0x0e000000; +const DWORD IMM_LMP =0x0f000000; +const DWORD IMM_LOGITECH =0x10000000; +const DWORD IMM_MADCATZ =0x11000000; +const DWORD IMM_MICROSOFT =0x12000000; +const DWORD IMM_PADIX =0x13000000; +const DWORD IMM_PRIMAX =0x14000000; +const DWORD IMM_QUANTUM3D =0x15000000; +const DWORD IMM_ROCKFIRE =0x16000000; +const DWORD IMM_SCT =0x17000000; +const DWORD IMM_SMELECTRONIC =0x18000000; +const DWORD IMM_SYSGRATION =0x19000000; +const DWORD IMM_THRUSTMASTER =0x1a000000; +const DWORD IMM_TRUST =0x1b000000; +const DWORD IMM_VIKINGS =0x1c000000; + +// Device IDs +const DWORD IMM_OTHERDEVICE =0x00000001; +const DWORD IMM_JOYSTICK =0x00000002; +const DWORD IMM_WHEEL =0x00000003; +const DWORD IMM_GAMEPAD =0x00000004; +const DWORD IMM_ABSMOUSE =0x00000005; +const DWORD IMM_RELMOUSE =0x00000006; + +//Technology IDs +//Note that these are bit masks +const DWORD IMM_OTHERTECH =0x00000001; +const DWORD IMM_FULLFF =0x00000002; +const DWORD IMM_IHDFF =0x00000004; +const DWORD IMM_VIBROFF =0x00000008; + +//Product Types (not to be confused with product GUIDs) +const DWORD IMM_JOYSTICK_FULLFF = MAKELONG(IMM_FULLFF, IMM_JOYSTICK); +const DWORD IMM_WHEEL_FULLFF = MAKELONG(IMM_FULLFF, IMM_WHEEL); +const DWORD IMM_GAMEPAD_FULLFF = MAKELONG(IMM_FULLFF, IMM_GAMEPAD); +const DWORD IMM_ABSMOUSE_FULLFF = MAKELONG(IMM_FULLFF, IMM_ABSMOUSE); + +const DWORD IMM_JOYSTICK_IHDFF = MAKELONG(IMM_IHDFF, IMM_JOYSTICK); +const DWORD IMM_WHEEL_IHDFF = MAKELONG(IMM_IHDFF, IMM_WHEEL); +const DWORD IMM_GAMEPAD_IHDFF = MAKELONG(IMM_IHDFF, IMM_GAMEPAD); +const DWORD IMM_RELMOUSE_IHDFF = MAKELONG(IMM_IHDFF, IMM_RELMOUSE); + +const DWORD IMM_JOYSTICK_VIBROFF= MAKELONG(IMM_VIBROFF, IMM_JOYSTICK); +const DWORD IMM_WHEEL_VIBROFF = MAKELONG(IMM_VIBROFF, IMM_WHEEL); +const DWORD IMM_GAMEPAD_VIBROFF = MAKELONG(IMM_VIBROFF, IMM_GAMEPAD); +const DWORD IMM_RELMOUSE_VIBROFF= MAKELONG(IMM_VIBROFF, IMM_RELMOUSE); + +//================================================================ +// CImmDevice +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmDevice +{ + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CImmDevice(); + + // Destructor + virtual + ~CImmDevice(); + + + // + // ATTRIBUTES + // + + public: + + virtual LPIIMM_API + GetAPI() + = 0; // pure virtual function + + virtual LPIIMM_DEVICE // Will actually return LPDIRECTINPUTDEVICE2 for DX supported device + GetDevice() + = 0; // pure virtual function + + DWORD + GetDeviceType() const + { return m_dwDeviceType; } + + virtual DWORD GetProductType() = 0; + virtual BOOL GetDriverVersion( + DWORD &dwFFDriverVersion, + DWORD &dwFirmwareRevision, + DWORD &dwHardwareRevision) + = 0; + + virtual int GetProductName(LPTSTR lpszProductName, int nMaxCount) = 0; + virtual int GetProductGUIDString(LPTSTR lpszGUID, int nMaxCount) = 0; + virtual GUID GetProductGUID() = 0; + + static BOOL GetIFCVersion(DWORD &dwMajor, DWORD &dwMinor, DWORD &dwBuild, DWORD &dwBuildMinor); + static BOOL GetImmAPIVersion(DWORD &dwMajor, DWORD &dwMinor, DWORD &dwBuild, DWORD &dwBuildMinor); + static BOOL GetDXVersion(DWORD &dwMajor, DWORD &dwMinor, DWORD &dwBuild, DWORD &dwBuildMinor); + + // + // OPERATIONS + // + + public: + + static CImmDevice * + CreateDevice(HINSTANCE hinstApp, HWND hwndApp); + + virtual BOOL + GetCurrentPosition( long &lXPos, long &lYPos ) + = 0; // pure virtual function + + virtual BOOL + ChangeScreenResolution( + BOOL bAutoSet, + DWORD dwXScreenSize = 0, + DWORD dwYScreenSize = 0 + ); + + // The default state is using standard Win32 Mouse messages (e.g., WM_MOUSEMOVE) + // and functions (e.g, GetCursorPos). Call only to switch to relative mode + // if not using standard Win32 Mouse services (e.g., DirectInput) for mouse + // input. + BOOL + UsesWin32MouseServices( + BOOL bWin32MouseServ + ); + + // Another syntax for SwitchToAbsoluteMode. + // The default is Absolute mode. Call only to switch to Relative mode or + // to switch back to Absolute mode. + virtual BOOL + SwitchToAbsoluteMode( + BOOL bAbsMode + ); + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // CACHING + // + +#ifdef IFC_EFFECT_CACHING + public: + + void Cache_AddEffect(CImmEffect *pImmEffect); + void Cache_RemoveEffect(const CImmEffect *pImmEffect); + void Cache_SwapOutEffect(); + + protected: + + void Cache_LoadEffectSuite(CImmEffectSuite *pSuite, BOOL bCreateOnDevice); + void Cache_UnloadEffectSuite(CImmEffectSuite *pSuite, BOOL bUnloadFromDevice); + + CEffectList m_Cache; // List of all effects created on device +#endif + + // + // HELPERS + // + + protected: + + // Performs device preparation by setting the device's parameters + virtual BOOL + prepare_device(); + + virtual void + reset() + = 0; // pure virtual function + + static BOOL CALLBACK + enum_didevices_proc( + LPDIDEVICEINSTANCE pImmDevInst, + LPVOID pv + ); + + static BOOL CALLBACK + enum_devices_proc( + LPIMM_DEVICEINSTANCE pImmDevInst, + LPVOID pv + ); + + void + detach_effects(); + + // + // INTERNAL DATA + // + + protected: + + BOOL m_bInitialized; + DWORD m_dwDeviceType; + GUID m_guidDevice; + BOOL m_bGuidValid; + DWORD m_dwProductType; + +}; + + +#endif // !defined(AFX_FORCEDEVICE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmDevices.h b/code/ff/IFC/ImmDevices.h new file mode 100644 index 0000000..f133f7a --- /dev/null +++ b/code/ff/IFC/ImmDevices.h @@ -0,0 +1,156 @@ +/********************************************************************** + Copyright (c) 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmDevices.h + + PURPOSE: Abstract Base Device Class for Immersion Foundation Classes + + STARTED: 3/29/00 + + NOTES/REVISIONS: + 3/29/00 jrm (Jeff Mallett): Started + +**********************************************************************/ + +#if !defined(AFX_FORCEDEVICES_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FORCEDEVICES_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmBaseTypes.h" + + + +typedef enum { + IMM_ENUMERATE_IMM_DEVICES = 0x00000001, + IMM_ENUMERATE_DX_DEVICES = 0x00000002, + IMM_ENUMERATE_ALL = 0xFFFFFFFF +} IMM_ENUMERATE; + +typedef enum { + IMM_NO_PREFERENCE = 0x00000000, + IMM_PREFER_IMM_DEVICES = 0x00000001, + IMM_PREFER_DX_DEVICES = 0x00000002 +} IMM_ENUMERATE_PREFERENCE; + +class CImmDevices; +class CInitializeEnum { +public: + HANDLE m_hinstApp; + HANDLE m_hwndApp; + DWORD m_dwCooperativeFlag; + CImmDevices *m_pDevices; + long m_lMaximumDevices; +}; + +//================================================================ +// CImmDevices +//================================================================ + +typedef class CImmDevice * IMM_DEVICE_PTR; + + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmDevices { + + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + CImmDevices(); + ~CImmDevices(); + + + // + // ATTRIBUTES + // + + public: + + long + GetNumDevices() + { return m_lNumDevices; } + + IMM_DEVICE_PTR + GetDevice(long lIndex); + + + // + // OPERATIONS + // + + public: + + void + AddDevice(IMM_DEVICE_PTR pDevice); + + long + CreateDevices( + HINSTANCE hinstApp, + HWND hwndApp, + long lMaximumDevices = -1, // means "all" + IMM_ENUMERATE type = IMM_ENUMERATE_ALL, + IMM_ENUMERATE_PREFERENCE preference = IMM_NO_PREFERENCE + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + +protected: + + BOOL + enumerate_dx_devices(CInitializeEnum *pIE); + + BOOL + enumerate_imm_devices(CInitializeEnum *pIE); + + void + clean_up(); + + // + // INTERNAL DATA + // + + protected: + + long m_lNumDevices; + IMM_DEVICE_PTR *m_DeviceArray; +}; + +#endif // !defined(AFX_FORCEDEVICE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) + diff --git a/code/ff/IFC/ImmEffect.h b/code/ff/IFC/ImmEffect.h new file mode 100644 index 0000000..3bfc9a8 --- /dev/null +++ b/code/ff/IFC/ImmEffect.h @@ -0,0 +1,440 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmEffect.h + + PURPOSE: Immersion Foundation Classes Base Effect + + STARTED: Oct.10.97 + + NOTES/REVISIONS: + Mar.02.99 jrm (Jeff Mallett): Force-->Feel renaming + Mar.02.99 jrm: Added GetIsCompatibleGUID and feel_to_DI_GUID + Mar.15.99 jrm: __declspec(dllimport/dllexport) the whole class + Nov.15.99 efw (Evan Wies): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMEffect_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMEffect_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmBaseTypes.h" +#include "ImmDevice.h" +class CImmProject; + +//================================================================ +// Constants +//================================================================ + +#define IMM_EFFECT_AXIS_X 1 +#define IMM_EFFECT_AXIS_Y 2 +#define IMM_EFFECT_AXIS_BOTH 3 +#define IMM_EFFECT_AXIS_DIRECTIONAL 4 +#define IMM_EFFECT_DONT_CHANGE MINLONG +#define IMM_EFFECT_DONT_CHANGE_PTR MAXDWORD +const POINT IMM_EFFECT_DONT_CHANGE_POINT = { 0xFFFFFFFF, 0xFFFFFFFF }; +const POINT IMM_EFFECT_MOUSE_POS_AT_START = { MAXLONG, MAXLONG }; + +#define IMM_EFFECT_DEFAULT_ENVELOPE NULL +#define IMM_EFFECT_DEFAULT_DIRECTION_X 1 +#define IMM_EFFECT_DEFAULT_DIRECTION_Y 1 +#define IMM_EFFECT_DEFAULT_ANGLE 0 + +// GENERIC_EFFECT_PTR +// This is really a pointer to a child of CImmEffect. +typedef class CImmEffect * GENERIC_EFFECT_PTR; + + +//================================================================ +// CImmEffect +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CImmEffect( + const GUID& rguidEffect + ); + + // Destructor + virtual + ~CImmEffect(); + + // + // ATTRIBUTES + // + + public: + + LPIIMM_EFFECT + GetEffect() + { return m_piImmEffect; } + + CImmDevice* + GetDevice() + { return m_pImmDevice; } + + BOOL + GetStatus( + DWORD* pdwStatus + ); + + void + GetParameters(IMM_EFFECT &Effect); + BOOL GetEnvelope( LPIMM_ENVELOPE pEnvelope ); + + BOOL GetDuration( DWORD &dwDuration ); + BOOL GetGain( DWORD &dwGain ); + BOOL GetStartDelay( DWORD &dwStartDelay ); + BOOL GetTriggerButton( DWORD &dwTriggerButton ); + BOOL GetTriggerRepeatInterval( DWORD &dwTriggerRepeatInterval ); + BOOL GetDirection( LONG &lDirectionX, LONG &lDirectionY ); + BOOL GetDirection( LONG &lAngle ); + + BOOL GetIterations( DWORD &dwIterations ); + + GUID + GetGUID() + { return m_guidEffect; } + + virtual BOOL + GetIsCompatibleGUID( + GUID & /* guid */ + ) + { return true; } + + virtual DWORD GetEffectType() + { return 0; } + + LPCSTR + GetName() + { return m_lpszName; } + + // Allocates an object of the correct IFC class from the given GUID + static GENERIC_EFFECT_PTR + NewObjectFromGUID( + GUID &guid + ); + + BOOL + ChangeBaseParams( + LONG lDirectionX, + LONG lDirectionY, + DWORD dwDuration = IMM_EFFECT_DONT_CHANGE, + LPIMM_ENVELOPE pEnvelope = (LPIMM_ENVELOPE) IMM_EFFECT_DONT_CHANGE_PTR, + DWORD dwSamplePeriod = IMM_EFFECT_DONT_CHANGE, + DWORD dwGain = IMM_EFFECT_DONT_CHANGE, + DWORD dwTriggerButton = IMM_EFFECT_DONT_CHANGE, + DWORD dwTriggerRepeatInterval = IMM_EFFECT_DONT_CHANGE +#ifdef IFC_START_DELAY + ,DWORD dwStartDelay = IMM_EFFECT_DONT_CHANGE // milliseconds +#endif + ); + + BOOL + ChangeBaseParamsPolar( + LONG lAngle, + DWORD dwDuration = IMM_EFFECT_DONT_CHANGE, // milliseconds + LPIMM_ENVELOPE pEnvelope = (LPIMM_ENVELOPE) IMM_EFFECT_DONT_CHANGE_PTR, + DWORD dwSamplePeriod = IMM_EFFECT_DONT_CHANGE, + DWORD dwGain = IMM_EFFECT_DONT_CHANGE, + DWORD dwTriggerButton = IMM_EFFECT_DONT_CHANGE, + DWORD dwTriggerRepeatInterval = IMM_EFFECT_DONT_CHANGE +#ifdef IFC_START_DELAY + ,DWORD dwStartDelay = IMM_EFFECT_DONT_CHANGE // milliseconds +#endif + ); + + BOOL + ChangeDirection( + LONG lDirectionX, + LONG lDirectionY + ); + + BOOL + ChangeDirection( + LONG lAngle + ); + + BOOL + ChangeDuration( + DWORD dwDuration + ); + + BOOL + ChangeGain( + DWORD dwGain + ); + + BOOL + ChangeStartDelay( + DWORD dwStartDelay + ); + + BOOL + ChangeTriggerButton( + DWORD dwTriggerButton + ); + + BOOL + ChangeTriggerRepeatInterval( + DWORD dwTriggerRepeatInterval + ); + + BOOL + ChangeIterations( + DWORD dwIterations + ); + + BOOL + ChangeEnvelope( + DWORD dwAttackLevel, + DWORD dwAttackTime, // microseconds + DWORD dwFadeLevel, + DWORD dwFadeTime // microseconds + ); + + BOOL + ChangeEnvelope( + LPIMM_ENVELOPE pEnvelope + ); + + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + const IMM_EFFECT &effect, + DWORD dwNoDownload = 0 + ); + + virtual BOOL + InitializeFromProject( + CImmProject &project, + LPCSTR lpszEffectName, + CImmDevice* pDevice = NULL, + DWORD dwNoDownload = 0 + ); + + virtual BOOL + Start( + DWORD dwIterations = IMM_EFFECT_DONT_CHANGE, + DWORD dwFlags = 0 +#ifdef IFC_START_DELAY + , BOOL bAllowStartDelayEmulation = true +#endif + ); + + virtual BOOL + Stop(); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // CACHING + // + +#ifdef IFC_EFFECT_CACHING + + public: + + friend class CEffectList; + friend class CImmCompoundEffect; + + BOOL GetIsPlaying(); + BOOL GetIsTriggered() const; + short GetPriority() const { return m_Priority; } + void SetPriority(short priority) { m_Priority = priority; } + virtual HRESULT Unload(); + virtual void Reload(); + + //Althought public, this should only be used internally. + BOOL + set_outside_effect( + CImmEffect* pImmOutsideEffect + ); + + BOOL + get_is_inside_effect() + { return m_bIsInsideEffect; } + + public: + + ECacheState m_CacheState; // effect's status in the cache + BOOL m_bInCurrentSuite; // is the effect in the currently loaded suite? + short m_Priority; // Priority within suite: higher number is higher priority + DWORD m_dwLastStarted; // when last started (0 = never) or when param change made on device + DWORD m_dwLastStopped; // when last stopped (0 = not since last start) + DWORD m_dwLastLoaded; // when last loaded with CImmEffectSuite::Load or Create + + protected: + + CImmDevice *m_pImmDevice; // ### Use instead of m_piImmDevice +#endif + + // + // HELPERS + // + protected: + +#ifdef IFC_START_DELAY + void EmulateStartDelay( + DWORD dwIterations, + DWORD dwNoDownload + ); +#endif + +#ifdef IFC_EFFECT_CACHING + public: // initalize needs to be called by CImmDevice +#endif + BOOL + initialize( + CImmDevice* pDevice, + DWORD dwNoDownload + ); +#ifdef IFC_EFFECT_CACHING + protected: +#endif + + HRESULT + set_parameters_on_device( + DWORD dwFlags + ); + + BOOL + set_name( + const char *lpszName + ); + + void + imm_to_DI_GUID( + GUID &guid + ); + + void + DI_to_imm_GUID( + GUID &guid + ); + + void + reset(); + + void + reset_effect_struct(); + + void + reset_device(); + + void + buffer_direction( + TCHAR** pData + ); + + void + buffer_long_param( + TCHAR** pData, + LPCSTR lpszKey, + long lDefault, + long lValue + ); + + void + buffer_dword_param( + TCHAR** pData, + LPCSTR lpszKey, + DWORD dwDefault, + DWORD dwValue + ); + + virtual int + buffer_ifr_data( + TCHAR* pData + ); + + virtual BOOL + get_ffe_data( + LPDIEFFECT pdiEffect + ); + + + // + // INTERNAL DATA + // + + protected: + + IMM_EFFECT m_Effect; + DWORD m_dwaAxes[2]; + LONG m_laDirections[2]; + IMM_ENVELOPE m_Envelope; + + GUID m_guidEffect; + BOOL m_bIsPlaying; + DWORD m_dwDeviceType; + LPIIMM_DEVICE m_piImmDevice; // Might also be holding LPDIRECTINPUTDEVICE2 + LPIIMM_EFFECT m_piImmEffect; + DWORD m_cAxes; // Number of axes + DWORD m_dwNoDownload; + DWORD m_dwIterations; + char *m_lpszName; // Name of this effect primative + + // Needed for co-ordinating events for Enclosures/Ellipes and the inside effects. + BOOL m_bIsInsideEffect; + CImmEffect* m_pOutsideEffect; + +#ifdef IFC_START_DELAY + public: + // Prevents access to dangling pointer when this is deleted + // All relevent code may be removed when all hardware and drivers support start delay + CImmEffect **m_ppTimerRef; // pointer to pointer to this. +#endif +}; + + +#endif // !defined(AFX_ImmEffect_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) + diff --git a/code/ff/IFC/ImmEffectSuite.h b/code/ff/IFC/ImmEffectSuite.h new file mode 100644 index 0000000..1086253 --- /dev/null +++ b/code/ff/IFC/ImmEffectSuite.h @@ -0,0 +1,103 @@ + +/********************************************************************** + Copyright (c) 1999 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmEffectSuite.h + + PURPOSE: Caching of effects + + STARTED: 6/16/99 Jeff Mallett + + NOTES/REVISIONS: + +**********************************************************************/ + +#if !defined(AFX_FEELEFFECTSUITE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_FEELEFFECTSUITE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmBaseTypes.h" + +#ifdef IFC_EFFECT_CACHING + +class CImmDevice; //#include "ImmDevice.h" +class CImmEffect; //#include "ImmEffect.h" + +typedef enum { + IMMCACHE_NOT_ON_DEVICE, + IMMCACHE_ON_DEVICE, + IMMCACHE_SWAPPED_OUT +} ECacheState; + + +//================================================================ +// CEffectList, CEffectListElement +//================================================================ + +class DLLIFC CEffectListElement +{ +public: + CEffectListElement() : m_pImmEffect(NULL), m_pNext(NULL) { } + + CImmEffect *m_pImmEffect; + CEffectListElement *m_pNext; +}; + +class DLLIFC CEffectList +{ +public: + CEffectList() : m_pFirstEffect(NULL) { } + ~CEffectList(); + BOOL AddEffect(CImmEffect *pImmEffect); + BOOL RemoveEffect(const CImmEffect *pImmEffect); + void ClearDevice(CImmDevice *pImmDevice); + + CEffectListElement *m_pFirstEffect; +}; + + +//================================================================ +// CImmEffectSuite +//================================================================ + +class CImmEffectSuite +{ +public: + CImmEffectSuite() : m_bCurrentSuite(false) { } + CEffectListElement *GetFirstEffect(); + void AddEffect(CImmEffect *pImmEffect); + void RemoveEffect(CImmEffect *pImmEffect); + void SetPriorities(short priority); + + BOOL m_bCurrentSuite; // Is the suite the "current suite"? +private: + CEffectList m_EffectList; // List of effects in suite +}; + +#endif // IFC_EFFECT_CACHING +#endif // !defined(AFX_FEELEFFECTSUITE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmEllipse.h b/code/ff/IFC/ImmEllipse.h new file mode 100644 index 0000000..75bfbf7 --- /dev/null +++ b/code/ff/IFC/ImmEllipse.h @@ -0,0 +1,295 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmEllipse.h + + PURPOSE: Base Ellipse Class for Immersion Foundation Classes + + STARTED: 10/29/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + 11/15/99 sdr (Steve Rank): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMELLIPSE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMELLIPSE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmBaseTypes.h" +#include "ImmEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +#define IMM_ELLIPSE_DEFAULT_STIFFNESS 5000 +#define IMM_ELLIPSE_DEFAULT_SATURATION 10000 +#define IMM_ELLIPSE_DEFAULT_WIDTH 10 +#define IMM_ELLIPSE_HEIGHT_AUTO MAXDWORD +#define IMM_ELLIPSE_DEFAULT_HEIGHT IMM_ELLIPSE_HEIGHT_AUTO +#define IMM_ELLIPSE_WALL_WIDTH_AUTO MAXDWORD +#define IMM_ELLIPSE_DEFAULT_WALL_WIDTH IMM_ELLIPSE_WALL_WIDTH_AUTO +#define IMM_ELLIPSE_DEFAULT_STIFFNESS_MASK IMM_STIFF_ANYWALL +#define IMM_ELLIPSE_DEFAULT_CLIPPING_MASK IMM_CLIP_NONE + +#define IMM_ELLIPSE_DEFAULT_CENTER_POINT IMM_EFFECT_MOUSE_POS_AT_START + + + + + +//================================================================ +// CImmEllipse +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmEllipse : public CImmEffect +{ + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CImmEllipse(); + + // Destructor + virtual + ~CImmEllipse(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + virtual DWORD GetEffectType() + { return IMM_EFFECTTYPE_ELLIPSE; } + + BOOL + ChangeParameters( + POINT pntCenter, + DWORD dwWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwHeight = IMM_EFFECT_DONT_CHANGE, + LONG lStiffness = IMM_EFFECT_DONT_CHANGE, + DWORD dwWallThickness = IMM_EFFECT_DONT_CHANGE, + DWORD dwSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwStiffnessMask = IMM_EFFECT_DONT_CHANGE, + DWORD dwClippingMask = IMM_EFFECT_DONT_CHANGE, + CImmEffect* pInsideEffect = (CImmEffect*) IMM_EFFECT_DONT_CHANGE, + LONG lAngle = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParameters( + LPCRECT pRectOutside, + LONG lStiffness = IMM_EFFECT_DONT_CHANGE, + DWORD dwWallThickness = IMM_EFFECT_DONT_CHANGE, + DWORD dwSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwStiffnessMask = IMM_EFFECT_DONT_CHANGE, + DWORD dwClippingMask = IMM_EFFECT_DONT_CHANGE, + CImmEffect* pInsideEffect = (CImmEffect*) IMM_EFFECT_DONT_CHANGE, + LONG lAngle = IMM_EFFECT_DONT_CHANGE + ); + + + BOOL ChangeStiffness ( LONG lStiffness ); + BOOL ChangeWallThickness( DWORD dwThickness ); + BOOL ChangeSaturation ( DWORD dwSaturation ); + BOOL ChangeStiffnessMask( DWORD dwStiffnessMask ); + BOOL ChangeClippingMask ( DWORD dwClippingMask ); + BOOL ChangeInsideEffect ( CImmEffect* pInsideEffect ); + + BOOL + ChangeRect( + LPCRECT pRect + ); + + + BOOL + ChangeCenter( + POINT pntCenter + ); + + + BOOL + ChangeCenter( + LONG x, + LONG y + ); + + BOOL GetStiffness ( LONG &lStiffness ); + BOOL GetWallThickness( DWORD &dwThickness ); + BOOL GetSaturation ( DWORD &dwSaturation ); + BOOL GetStiffnessMask( DWORD &dwStiffnessMask ); + BOOL GetClippingMask ( DWORD &dwClippingMask ); + + BOOL GetRect( RECT* pRect ); + BOOL GetCenter( POINT &pntCenter ); + BOOL GetCenter( LONG &x, LONG &y); + + CImmEffect* GetInsideEffect(); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + const IMM_EFFECT &effect, + DWORD dwNoDownload = 0 + ); + + BOOL + Initialize( + CImmDevice* pDevice, + DWORD dwWidth = IMM_ELLIPSE_DEFAULT_WIDTH, + DWORD dwHeight = IMM_ELLIPSE_DEFAULT_HEIGHT, + LONG lStiffness = IMM_ELLIPSE_DEFAULT_STIFFNESS, + DWORD dwWallWidth = IMM_ELLIPSE_DEFAULT_WALL_WIDTH, + DWORD dwSaturation = IMM_ELLIPSE_DEFAULT_SATURATION, + DWORD dwStiffnessMask = IMM_ELLIPSE_DEFAULT_STIFFNESS_MASK, + DWORD dwClippingMask = IMM_ELLIPSE_DEFAULT_CLIPPING_MASK, + POINT pntCenter = IMM_ELLIPSE_DEFAULT_CENTER_POINT, + CImmEffect* pInsideEffect = NULL, + LONG lAngle = IMM_EFFECT_DEFAULT_ANGLE, + DWORD dwNoDownload = 0 + ); + + + BOOL + Initialize( + CImmDevice* pDevice, + LPCRECT pRectOutside, + LONG lStiffness = IMM_ELLIPSE_DEFAULT_STIFFNESS, + DWORD dwWallWidth = IMM_ELLIPSE_DEFAULT_WALL_WIDTH, + DWORD dwSaturation = IMM_ELLIPSE_DEFAULT_SATURATION, + DWORD dwStiffnessMask = IMM_ELLIPSE_DEFAULT_STIFFNESS_MASK, + DWORD dwClippingMask = IMM_ELLIPSE_DEFAULT_CLIPPING_MASK, + CImmEffect* pInsideEffect = NULL, + LONG lAngle = IMM_EFFECT_DEFAULT_ANGLE, + DWORD dwNoDownload = 0 + ); + + + virtual BOOL + Start( + DWORD dwIterations = 1, + DWORD dwFlags = 0, + BOOL bAllowStartDelayEmulation = true + ); + + HRESULT Unload(); + void Reload(); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + LPCRECT pRectOutside, + LONG lStiffness, + DWORD dwWallWidth, + DWORD dwSaturation, + DWORD dwStiffnessMask, + DWORD dwClippingMask, + CImmEffect* pInsideEffect, + LONG lAngle + ); + + DWORD + change_parameters( + LPCRECT prectBoundary, + LONG lStiffness, + DWORD dwWallThickness, + DWORD dwSaturation, + DWORD dwStiffnessMask, + DWORD dwClippingMask, + CImmEffect* pInsideEffect, + LONG lAngle + ); + + int + buffer_ifr_data( + TCHAR* pData + ); + + // + // INTERNAL DATA + // + + protected: + + IMM_ELLIPSE m_ellipse; + BOOL m_bUseMousePosAtStart; + + // Needed for co-ordinating events for Enclosures/Ellipes and the inside effects. + CImmEffect* m_pInsideEffect; +}; + + + +// +// INLINES +// + +inline BOOL +CImmEllipse::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_Ellipse); +} + +#endif // !defined(AFX_IMMELLIPSE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmEnclosure.h b/code/ff/IFC/ImmEnclosure.h new file mode 100644 index 0000000..e5675c5 --- /dev/null +++ b/code/ff/IFC/ImmEnclosure.h @@ -0,0 +1,325 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmEnclosure.h + + PURPOSE: Base Enclosure Class for Immersion Foundation Classes + + STARTED: 10/29/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + 11/15/99 sdr (Steve Rank): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMENCLOSURE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMENCLOSURE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmBaseTypes.h" +#include "ImmEffect.h" + + +//================================================================ +// Constants +//================================================================ + + +#define IMM_ENCLOSURE_DEFAULT_STIFFNESS 5000 +#define IMM_ENCLOSURE_DEFAULT_SATURATION 10000 +#define IMM_ENCLOSURE_DEFAULT_WIDTH 10 +#define IMM_ENCLOSURE_HEIGHT_AUTO MAXDWORD +#define IMM_ENCLOSURE_DEFAULT_HEIGHT IMM_ENCLOSURE_HEIGHT_AUTO +#define IMM_ENCLOSURE_WALL_WIDTH_AUTO MAXDWORD +#define IMM_ENCLOSURE_DEFAULT_WALL_WIDTH IMM_ENCLOSURE_WALL_WIDTH_AUTO +#define IMM_ENCLOSURE_DEFAULT_STIFFNESS_MASK IMM_STIFF_ANYWALL +#define IMM_ENCLOSURE_DEFAULT_CLIPPING_MASK IMM_CLIP_NONE + +#define IMM_ENCLOSURE_DEFAULT_CENTER_POINT IMM_EFFECT_MOUSE_POS_AT_START + + + + + +//================================================================ +// CImmEnclosure +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmEnclosure : public CImmEffect +{ + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CImmEnclosure(); + + // Destructor + virtual + ~CImmEnclosure(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + virtual DWORD GetEffectType() + { return IMM_EFFECTTYPE_ENCLOSURE; } + + BOOL + ChangeParameters( + POINT pntCenter, + DWORD dwWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwHeight = IMM_EFFECT_DONT_CHANGE, + LONG lTopAndBottomWallStiffness = IMM_EFFECT_DONT_CHANGE, + LONG lLeftAndRightWallStiffness = IMM_EFFECT_DONT_CHANGE, + DWORD dwTopAndBottomWallWallWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwLeftAndRightWallWallWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwTopAndBottomWallSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwLeftAndRightWallSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwStiffnessMask = IMM_EFFECT_DONT_CHANGE, + DWORD dwClippingMask = IMM_EFFECT_DONT_CHANGE, + CImmEffect* pInsideEffect = (CImmEffect*) IMM_EFFECT_DONT_CHANGE, + LONG lAngle = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParameters( + LPCRECT pRectOutside, + LONG lTopAndBottomWallStiffness = IMM_EFFECT_DONT_CHANGE, + LONG lLeftAndRightWallStiffness = IMM_EFFECT_DONT_CHANGE, + DWORD dwTopAndBottomWallWallThickness = IMM_EFFECT_DONT_CHANGE, + DWORD dwLeftAndRightWallWallThickness = IMM_EFFECT_DONT_CHANGE, + DWORD dwTopAndBottomWallSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwLeftAndRightWallSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwStiffnessMask = IMM_EFFECT_DONT_CHANGE, + DWORD dwClippingMask = IMM_EFFECT_DONT_CHANGE, + CImmEffect* pInsideEffect = (CImmEffect*) IMM_EFFECT_DONT_CHANGE, + LONG lAngle = IMM_EFFECT_DONT_CHANGE + ); + + BOOL ChangeTopAndBottomWallStiffness ( LONG lTopAndBottomWallStiffness ); + BOOL ChangeLeftAndRightWallStiffness ( LONG lLeftAndRightWallStiffness ); + BOOL ChangeTopAndBottomWallThickness ( DWORD dwTopAndBottomWallThickness ); + BOOL ChangeLeftAndRightWallThickness ( DWORD dwLeftAndRightWallThickness ); + BOOL ChangeTopAndBottomWallSaturation( DWORD dwTopAndBottomWallSaturation ); + BOOL ChangeLeftAndRightWallSaturation( DWORD dwLeftAndRightWallSaturation ); + BOOL ChangeStiffnessMask ( DWORD dwStiffnessMask ); + BOOL ChangeClippingMask ( DWORD dwClippingMask ); + BOOL ChangeInsideEffect ( CImmEffect* pInsideEffect ); + + BOOL + ChangeRect( + LPCRECT pRect + ); + + + BOOL + ChangeCenter( + POINT pntCenter + ); + + + BOOL + ChangeCenter( + LONG x, + LONG y + ); + + BOOL + ShowRect( + BOOL bRectOn + ); + + BOOL GetTopAndBottomWallStiffness ( LONG &lTopAndBottomWallStiffness ); + BOOL GetLeftAndRightWallStiffness ( LONG &lLeftAndRightWallStiffness ); + BOOL GetTopAndBottomWallThickness ( DWORD &dwTopAndBottomWallThickness ); + BOOL GetLeftAndRightWallThickness ( DWORD &dwLeftAndRightWallThickness ); + BOOL GetTopAndBottomWallSaturation ( DWORD &dwTopAndBottomWallSaturation ); + BOOL GetLeftAndRightWallSaturation ( DWORD &dwLeftAndRightWallSaturation ); + BOOL GetStiffnessMask ( DWORD &dwStiffnessMask ); + BOOL GetClippingMask ( DWORD &dwClippingMask ); + + BOOL GetRect( RECT* pRect ); + BOOL GetCenter( POINT &pntCenter ); + BOOL GetCenter( LONG &x, LONG &y); + + CImmEffect* GetInsideEffect (); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + const IMM_EFFECT &effect, + DWORD dwNoDownload = 0 + ); + + BOOL + Initialize( + CImmDevice* pDevice, + DWORD dwWidth = IMM_ENCLOSURE_DEFAULT_WIDTH, + DWORD dwHeight = IMM_ENCLOSURE_DEFAULT_HEIGHT, + LONG lTopAndBottomWallStiffness = IMM_ENCLOSURE_DEFAULT_STIFFNESS, + LONG lLeftAndRightWallStiffness = IMM_ENCLOSURE_DEFAULT_STIFFNESS, + DWORD dwTopAndBottomWallWallWidth = IMM_ENCLOSURE_DEFAULT_WALL_WIDTH, + DWORD dwLeftAndRightWallWallWidth = IMM_ENCLOSURE_DEFAULT_WALL_WIDTH, + DWORD dwTopAndBottomWallSaturation = IMM_ENCLOSURE_DEFAULT_SATURATION, + DWORD dwLeftAndRightWallSaturation = IMM_ENCLOSURE_DEFAULT_SATURATION, + DWORD dwStiffnessMask = IMM_ENCLOSURE_DEFAULT_STIFFNESS_MASK, + DWORD dwClippingMask = IMM_ENCLOSURE_DEFAULT_CLIPPING_MASK, + POINT pntCenter = IMM_ENCLOSURE_DEFAULT_CENTER_POINT, + CImmEffect* pInsideEffect = NULL, + LONG lAngle = IMM_EFFECT_DEFAULT_ANGLE, + DWORD dwNoDownload = 0 + ); + + + BOOL + Initialize( + CImmDevice* pDevice, + LPCRECT pRectOutside, + LONG lTopAndBottomWallStiffness = IMM_ENCLOSURE_DEFAULT_STIFFNESS, + LONG lLeftAndRightWallStiffness = IMM_ENCLOSURE_DEFAULT_STIFFNESS, + DWORD dwTopAndBottomWallWallWidth = IMM_ENCLOSURE_DEFAULT_WALL_WIDTH, + DWORD dwLeftAndRightWallWallWidth = IMM_ENCLOSURE_DEFAULT_WALL_WIDTH, + DWORD dwTopAndBottomWallSaturation = IMM_ENCLOSURE_DEFAULT_SATURATION, + DWORD dwLeftAndRightWallSaturation = IMM_ENCLOSURE_DEFAULT_SATURATION, + DWORD dwStiffnessMask = IMM_ENCLOSURE_DEFAULT_STIFFNESS_MASK, + DWORD dwClippingMask = IMM_ENCLOSURE_DEFAULT_CLIPPING_MASK, + CImmEffect* pInsideEffect = NULL, + LONG lAngle = IMM_EFFECT_DEFAULT_ANGLE, + DWORD dwNoDownload = 0 + ); + + + virtual BOOL + Start( + DWORD dwIterations = 1, + DWORD dwFlags = 0, + BOOL bAllowStartDelayEmulation = true + ); + + virtual BOOL + Stop(); + + HRESULT Unload(); + void Reload(); + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + LPCRECT pRectOutside, + LONG lTopAndBottomWallStiffness, + LONG lLeftAndRightWallStiffness, + DWORD dwTopAndBottomWallWallWidth, + DWORD dwLeftAndRightWallWallWidth, + DWORD dwTopAndBottomWallSaturation, + DWORD dwLeftAndRightWallSaturation, + DWORD dwStiffnessMask, + DWORD dwClippingMask, + CImmEffect* pInsideEffect, + LONG lAngle + ); + + DWORD + change_parameters( + LPCRECT prectBoundary, + LONG lTopAndBottomWallStiffness, + LONG lLeftAndRightWallStiffness, + DWORD dwTopAndBottomWallThickness, + DWORD dwLeftAndRightWallThickness, + DWORD dwTopAndBottomWallSaturation, + DWORD dwLeftAndRightWallSaturation, + DWORD dwStiffnessMask, + DWORD dwClippingMask, + CImmEffect* pInsideEffect, + LONG lAngle + ); + + int + buffer_ifr_data( + TCHAR* pData + ); + + // + // INTERNAL DATA + // + + protected: + + IMM_ENCLOSURE m_enclosure; + BOOL m_bUseMousePosAtStart; + + // Needed for co-ordinating events for Enclosures/Ellipes and the inside effects. + CImmEffect* m_pInsideEffect; + +}; + + +// +// INLINES +// + +inline BOOL +CImmEnclosure::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_Enclosure); +} + +#endif // !defined(AFX_IMMENCLOSURE_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmFriction.h b/code/ff/IFC/ImmFriction.h new file mode 100644 index 0000000..d393d03 --- /dev/null +++ b/code/ff/IFC/ImmFriction.h @@ -0,0 +1,176 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmFriction.h + + PURPOSE: Immersion Foundation Classes Friction Effect + + STARTED: Dec.29.97 + + NOTES/REVISIONS: + Mar.02.99 jrm (Jeff Mallett): Force-->Feel renaming + Mar.02.99 jrm: Added GetIsCompatibleGUID + Mar.15.99 jrm: __declspec(dllimport/dllexport) the whole class + Nov.15.99 efw (Evan Wies): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_ImmFriction_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_ImmFriction_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include +#include "ImmCondition.h" + + +//================================================================ +// Constants +//================================================================ + +#define IMM_FRICTION_DEFAULT_COEFFICIENT 2500 +#define IMM_FRICTION_DEFAULT_MIN_VELOCITY 0 + + +//================================================================ +// CImmFriction +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmFriction : public CImmCondition +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CImmFriction(); + + // Destructor + virtual + ~CImmFriction(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + DWORD dwCoefficient, + DWORD dwMinVelocity = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParametersPolar( + DWORD dwCoefficient, + DWORD dwMinVelocity, + LONG lAngle + ); + + BOOL ChangeMinVelocityX( DWORD dwMinVelocity ); + BOOL ChangeMinVelocityY( DWORD dwMinVelocity ); + //For setting both axes to the same value + BOOL ChangeMinVelocity( DWORD dwMinVelocity ); + + BOOL GetMinVelocityX( DWORD &dwMinVelocity ); + BOOL GetMinVelocityY( DWORD &dwMinVelocity ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + DWORD dwCoefficient = IMM_FRICTION_DEFAULT_COEFFICIENT, + DWORD dwMinVelocity = IMM_FRICTION_DEFAULT_MIN_VELOCITY, + DWORD dwfAxis = IMM_EFFECT_AXIS_BOTH, + LONG lDirectionX = IMM_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = IMM_EFFECT_DEFAULT_DIRECTION_Y, + DWORD dwNoDownload = 0 + ); + + virtual BOOL + InitializePolar( + CImmDevice* pDevice, + DWORD dwCoefficient, + DWORD dwMinVelocity, + LONG lAngle, + DWORD dwNoDownload = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CImmFriction::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_Friction); +} + + +#endif // !defined(AFX_ImmFriction_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmGrid.h b/code/ff/IFC/ImmGrid.h new file mode 100644 index 0000000..a96e0d2 --- /dev/null +++ b/code/ff/IFC/ImmGrid.h @@ -0,0 +1,179 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmGrid.h + + PURPOSE: Immersion Foundation Classes Grid Effect + + STARTED: Dec.11.97 + + NOTES/REVISIONS: + Mar.02.99 jrm (Jeff Mallett): Force-->Feel renaming + Mar.02.99 jrm: Added GetIsCompatibleGUID + Mar.02.99 jrm: __declspec(dllimport/dllexport) the whole class + Nov.15.99 efw (Evan Wies): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMGrid_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMGrid_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include +#include "ImmCondition.h" + + +//================================================================ +// Constants +//================================================================ + +#define IMM_GRID_DEFAULT_HORIZ_OFFSET 0 +#define IMM_GRID_DEFAULT_VERT_OFFSET 0 +#define IMM_GRID_DEFAULT_HORIZ_SPACING 100 +#define IMM_GRID_DEFAULT_VERT_SPACING 100 +#define IMM_GRID_DEFAULT_NODE_STRENGTH 5000 +#define IMM_GRID_DEFAULT_NODE_SATURATION 10000 + + + +//================================================================ +// CImmGrid +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmGrid : public CImmCondition +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CImmGrid(); + + // Destructor + virtual + ~CImmGrid(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + DWORD dwHorizSpacing, + DWORD dwVertSpacing = IMM_EFFECT_DONT_CHANGE, + LONG lHorizNodeStrength = IMM_EFFECT_DONT_CHANGE, + LONG lVertNodeStrength = IMM_EFFECT_DONT_CHANGE, + LONG lHorizOffset = IMM_EFFECT_DONT_CHANGE, + LONG lVertOffset = IMM_EFFECT_DONT_CHANGE, + DWORD dwHorizNodeSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwVertNodeSaturation = IMM_EFFECT_DONT_CHANGE + ); + + BOOL ChangeHSpacing( DWORD dwHorizSpacing ); + BOOL ChangeVSpacing( DWORD dwVertSpacing ); + BOOL ChangeHNodeStrength( LONG lHorizNodeStrength ); + BOOL ChangeVNodeStrength( LONG lVertNodeStrength ); + BOOL ChangeOffset( POINT pntOffset ); + BOOL ChangeHNodeSaturation( DWORD dwHorizNodeSaturation ); + BOOL ChangeVNodeSaturation( DWORD dwVertNodeSaturation ); + + BOOL GetHSpacing( DWORD &dwHorizSpacing ); + BOOL GetVSpacing( DWORD &dwVertSpacing ); + BOOL GetHNodeStrength( LONG &lHorizNodeStrength ); + BOOL GetVNodeStrength( LONG &lVertNodeStrength ); + BOOL GetOffset( POINT &pntOffset ); + BOOL GetHNodeSaturation( DWORD &dwHorizNodeSaturation ); + BOOL GetVNodeSaturation( DWORD &dwVertNodeSaturation ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + DWORD dwHorizSpacing = IMM_GRID_DEFAULT_HORIZ_SPACING, + DWORD dwVertSpacing = IMM_GRID_DEFAULT_VERT_SPACING, + LONG lHorizNodeStrength = IMM_GRID_DEFAULT_NODE_STRENGTH, + LONG lVertNodeStrength = IMM_GRID_DEFAULT_NODE_STRENGTH, + DWORD dwHorizOffset = IMM_GRID_DEFAULT_HORIZ_OFFSET, + DWORD dwVertOffset = IMM_GRID_DEFAULT_VERT_OFFSET, + DWORD dwHorizNodeSaturation = IMM_GRID_DEFAULT_NODE_SATURATION, + DWORD dwVertNodeSaturation = IMM_GRID_DEFAULT_NODE_SATURATION, + DWORD dwNoDownload = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CImmGrid::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_Grid); +} + +#endif // !defined(AFX_IMMGrid_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmIFR.h b/code/ff/IFC/ImmIFR.h new file mode 100644 index 0000000..9b3f828 --- /dev/null +++ b/code/ff/IFC/ImmIFR.h @@ -0,0 +1,308 @@ +/********************************************************************** + Copyright (c) 1999 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: FEELitIFR.h + + PURPOSE: Input/Output for IFR Files, FEELit version + + STARTED: + + NOTES/REVISIONS: + +**********************************************************************/ + +#if !defined( _IMMIFR_H_) +#define _IMMIFR_H_ + +#ifndef __FEELITAPI_INCLUDED__ + #error include 'dinput.h' before including this file for structures. +#endif /* !__DINPUT_INCLUDED__ */ + +#define IFRAPI __stdcall + +#if !defined(_IFCDLL_) +#define DLLAPI __declspec(dllimport) +#else +#define DLLAPI __declspec(dllexport) +#endif + +#if defined __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* +** CONSTANTS +*/ + +/* +** RT_IMMERSION - Resource type for IFR projects stored as resources. +** This is the resource type looked for by IFLoadProjectResource(). +*/ +#define RT_IMMERSION ((LPCSTR)"IMMERSION") + + +/* +** TYPES/STRUCTURES +*/ + +/* +** HIFRPROJECT - used to identify a loaded project as a whole. +** individual objects within a project are uniquely referenced by name. +** Created by the IFLoadProject*() functions and released by IFReleaseProject(). +*/ +typedef LPVOID HIFRPROJECT; + +/* +** IFREffect - contains the information needed to create a DI effect +** using IDirectInputEffect::CreateEffect(). An array of pointers to these +** structures is allocated and returned by IFCreateEffectStructs(). +*/ +typedef struct { + GUID guid; + DWORD dwIterations; + char *effectName; + LPIMM_EFFECT lpDIEffect; +} IFREffect; + + +/* +** FUNCTION DECLARATIONS +*/ + +/* +** IFLoadProjectResource() - Load a project from a resource. +** hRsrcModule - handle of the module containing the project definition resource. +** pRsrcName - name or MAKEINTRESOURCE(id) identifier of resource to load. +** pDevice - device for which the project is being loaded. If NULL, +** effects will be created generically, and IFCreateEffects() will fail. +** Returns an identifier for the loaded project, or NULL if unsuccessful. +*/ +DLLAPI +HIFRPROJECT +IFRAPI +IFRLoadProjectResource( + HMODULE hRsrcModule, + LPCSTR pRsrcName, + LPIIMM_DEVICE pDevice ); + +/* +** IFLoadProjectPointer() - Load a project from a pointer. +** pProject - points to a project definition. +** pDevice - device for which the project is being loaded. If NULL, +** effects will be created generically, and IFCreateEffects() will fail. +** Returns an identifier for the loaded project, or NULL if unsuccessful. +*/ +DLLAPI +HIFRPROJECT +IFRAPI +IFRLoadProjectPointer( + LPVOID pProject, + LPIIMM_DEVICE pDevice ); + +/* +** IFLoadProjectFile() - Load a project from a file. +** pProjectFileName - points to a project file name. +** pDevice - device for which the project is being loaded. If NULL, +** effects will be created generically, and IFCreateEffects() will fail. +** Returns an identifier for the loaded project, or NULL if unsuccessful. +*/ +DLLAPI +HIFRPROJECT +IFRAPI +IFRLoadProjectFile( + LPCSTR pProjectFileName, + LPIIMM_DEVICE pDevice ); + +/* +** IFRLoadProjectFromMemory() - Load a project from memory. +** +** In cases where a file or resource is readily accessible, it may +** be necessary to pass IFR formated information through memory. +** +** pProjectDef - memory addres that contains information from an IFR file. +** pDevice - device for which the project is being loaded. If NULL, +** effects will be created generically, and IFRCreateEffects() will fail. +** Returns an identifier for the loaded project, or NULL if unsuccessful. +*/ +DLLAPI +HIFRPROJECT +IFRAPI +IFRLoadProjectFromMemory( + LPVOID pProjectDef, + LPIIMM_DEVICE pDevice ); + +/* +** IFLoadProjectObjectPointer() - Load a project from a pointer to a single +** object definition (usually used only by the editor). +** pObject - points to an object definition. +** pDevice - device for which the project is being loaded. If NULL, +** effects will be created generically, and IFCreateEffects() will fail. +** Returns an identifier for the loaded project, or NULL if unsuccessful. +*/ +DLLAPI +HIFRPROJECT +IFRAPI +IFRLoadProjectObjectPointer( + LPVOID pObject, + LPIIMM_DEVICE pDevice ); + +/* +** IFReleaseProject() - Release a loaded project. +** hProject - identifies the project to be released. +** Returns TRUE if the project is released, FALSE if it is an invalid project. +*/ +DLLAPI +BOOL +IFRAPI +IFRReleaseProject( + HIFRPROJECT hProject ); + +/* +** IFCreateEffectStructs() - Create IFREffects for a named effect. +** hProject - identifies the project containing the object. +** pObjectName - name of the object for which to create structures. +** pNumEffects - if not NULL will be set to a count of the IFREffect +** structures in the array (not including the terminating NULL pointer.) +** Returns a pointer to the allocated array of pointers to IFREffect +** structures. The array is terminated with a NULL pointer. If the +** function fails, a NULL pointer is returned. +*/ +DLLAPI +IFREffect ** +IFRAPI +IFRCreateEffectStructs( + HIFRPROJECT hProject, + LPCSTR pObjectName, + int *pNumEffects ); + +DLLAPI +IFREffect ** +IFRAPI +IFRCreateEffectStructsByIndex( + HIFRPROJECT hProject, + int nObjectIndex, + int *pNumEffects ); + +DLLAPI +int +IFRAPI +IFRGetNumEffects( + HIFRPROJECT hProject + ); + +DLLAPI +LPCSTR +IFRAPI +IFRGetObjectNameByIndex( + HIFRPROJECT hProject, + int nObjectIndex ); + +DLLAPI +LPCSTR +IFRAPI +IFRGetObjectSoundPath( + HIFRPROJECT hProject, + LPCSTR pObjectName ); + +DLLAPI +DWORD +IFRAPI +IFRGetObjectType( + HIFRPROJECT hProject, + LPCSTR pObjectName ); + +DLLAPI +DWORD +IFRAPI +IFRGetObjectTypeByIndex( + HIFRPROJECT hProject, + int nObjectIndex ); + +DLLAPI +LPCSTR +IFRAPI +IFRGetObjectNameByGUID( + HIFRPROJECT hProject, + GUID *pGUID ); + +DLLAPI +GUID +IFRAPI +IFRGetObjectID( + HIFRPROJECT hProject, + LPCSTR pObjectName); + +DLLAPI +GUID* +IFRAPI +IFRGetContainedObjIDs( + HIFRPROJECT hProject, + LPCSTR pCompoundObjName); + + +/* +** IFReleaseEffectStructs() - Release an array of IFREffects. +** hProject - identifies the project for which the effects were created. +** pEffects - points to the array of IFREffect pointers to be released. +** Returns TRUE if the array is released, FALSE if it is an invalid array. +*/ +DLLAPI +BOOL +IFRAPI +IFRReleaseEffectStructs( + HIFRPROJECT hProject, + IFREffect **pEffects ); + +/* +** IFCreateEffects() - Creates the DirectInput effects using +** IDirectInput::CreateEffect(). +** hProject - identifies the project containing the object. +** pObjectName - name of the object for which to create effects. +** pNumEffects - if not NULL will be set to a count of the IDirectInputEffect +** pointers in the array (not including the terminating NULL pointer.) +** Returns a pointer to the allocated array of pointers to IDirectInputEffects. +** The array is terminated with a NULL pointer. If the function fails, +** a NULL pointer is returned. +*/ +DLLAPI +LPIIMM_EFFECT * +IFRAPI +IFRCreateEffects( + HIFRPROJECT hProject, + LPCSTR pObjectName, + int *pNumEffects ); + +/* +** IFReleaseEffects() - Releases an array of IDirectInputEffect structures. +** hProject - identifies the project for which the effects were created. +** pEffects - points to the array if IDirectInputEffect pointers to be released. +** Returns TRUE if the array is released, FALSE if it is an invalid array. +*/ +DLLAPI +BOOL +IFRAPI +IFRReleaseEffects( + HIFRPROJECT hProject, + LPIIMM_EFFECT *pEffects ); + +#if defined __cplusplus +} +#endif /* __cplusplus */ + +#endif /* !IMMIFR_h */ diff --git a/code/ff/IFC/ImmInertia.h b/code/ff/IFC/ImmInertia.h new file mode 100644 index 0000000..301d54c --- /dev/null +++ b/code/ff/IFC/ImmInertia.h @@ -0,0 +1,183 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmInertia.h + + PURPOSE: Immersion Foundation Classes Inertia Effect + + STARTED: Dec.29.97 + + NOTES/REVISIONS: + Mar.02.99 jrm (Jeff Mallett): Force-->Feel renaming + Mar.02.99 jrm: Added GetIsCompatibleGUID + Mar.15.99 jrm: __declspec(dllimport/dllexport) the whole class + Nov.15.99 efw (Evan Wies): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMInertia_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMInertia_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include +#include "ImmCondition.h" + + +//================================================================ +// Constants +//================================================================ + +#define IMM_INERTIA_DEFAULT_COEFFICIENT 2500 +#define IMM_INERTIA_DEFAULT_SATURATION 10000 +#define IMM_INERTIA_DEFAULT_MIN_ACCELERATION 0 + + + +//================================================================ +// CImmInertia +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmInertia : public CImmCondition +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CImmInertia(); + + // Destructor + virtual + ~CImmInertia(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + DWORD dwCoefficient, + DWORD dwSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwMinAcceleration = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParametersPolar( + DWORD dwCoefficient, + DWORD dwSaturation, + DWORD dwMinAcceleration, + LONG lAngle + ); + + + BOOL ChangeMinAccelerationX( DWORD dwMinAcceleration ); + BOOL ChangeMinAccelerationY( DWORD dwMinAcceleration ); + BOOL ChangeMinAcceleration( DWORD dwMinAcceleration ); + + BOOL GetMinAccelerationX( DWORD &dwMinAcceleration ); + BOOL GetMinAccelerationY( DWORD &dwMinAcceleration ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + DWORD dwCoefficient = IMM_INERTIA_DEFAULT_COEFFICIENT, + DWORD dwSaturation = IMM_INERTIA_DEFAULT_SATURATION, + DWORD dwMinAcceleration = IMM_INERTIA_DEFAULT_MIN_ACCELERATION, + DWORD dwfAxis = IMM_EFFECT_AXIS_BOTH, + LONG lDirectionX = IMM_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = IMM_EFFECT_DEFAULT_DIRECTION_Y, + DWORD dwNoDownload = 0 + ); + + virtual BOOL + InitializePolar( + CImmDevice* pDevice, + DWORD dwCoefficient, + DWORD dwSaturation, + DWORD dwMinAcceleration, + LONG lAngle, + DWORD dwNoDownload = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CImmInertia::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_Inertia); +} + + +#endif // !defined(AFX_IMMInertia_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) + diff --git a/code/ff/IFC/ImmMouse.h b/code/ff/IFC/ImmMouse.h new file mode 100644 index 0000000..57fec48 --- /dev/null +++ b/code/ff/IFC/ImmMouse.h @@ -0,0 +1,164 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmMouse.h + + PURPOSE: Abstraction of Feelit mouse device + + STARTED: 10/10/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + +#ifndef ImmMouse_h +#define ImmMouse_h + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmDevice.h" + + +//================================================================ +// CImmMouse +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmMouse : public CImmDevice +{ + + // + // CONSTRUCTOR/DESCTRUCTOR + // + + public: + + // Constructor + CImmMouse(); + + // Destructor + virtual + ~CImmMouse(); + + + // + // ATTRIBUTES + // + + public: + + virtual LPIIMM_API + GetAPI() + { return m_piApi; } + + virtual LPIIMM_DEVICE + GetDevice() + { return m_piDevice; } + + virtual DWORD GetProductType(); + + virtual BOOL GetDriverVersion( + DWORD &dwFFDriverVersion, + DWORD &dwFirmwareRevision, + DWORD &dwHardwareRevision); + + virtual int GetProductName(LPTSTR lpszProductName, int nMaxCount); + virtual int GetProductGUIDString(LPTSTR lpszGUID, int nMaxCount); + virtual GUID GetProductGUID(); + + BOOL + HaveImmMouse() + { return m_piDevice != NULL; } + + + // + // OPERATIONS + // + + public: + + BOOL + Initialize( + HANDLE hinstApp, + HANDLE hwndApp, + DWORD dwCooperativeFlag = IMM_COOPLEVEL_FOREGROUND, + BOOL bEnumerate = TRUE + ); + + virtual BOOL + ChangeScreenResolution( + BOOL bAutoSet, + DWORD dwXScreenSize = 0, + DWORD dwYScreenSize = 0 + ); + + // Another syntax for SwitchToAbsoluteMode. + // The default is Absolute mode. Call only to switch to Relative mode or + // to switch back to Absolute mode. + virtual BOOL + SwitchToAbsoluteMode( + BOOL bAbsMode + ); + + virtual BOOL + GetCurrentPosition( long &lXPos, long &lYPos ); + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + virtual void + reset(); + + virtual BOOL + prepare_device(); + + friend class CImmDevices; + static BOOL CALLBACK + devices_enum_proc( + LPIMM_DEVICEINSTANCE pImmDevInst, + LPVOID pv + ); + + // + // INTERNAL DATA + // + + protected: + + LPIIMM_API m_piApi; + LPIIMM_DEVICE m_piDevice; +}; + +#endif // ImmMouse_h \ No newline at end of file diff --git a/code/ff/IFC/ImmPeriodic.h b/code/ff/IFC/ImmPeriodic.h new file mode 100644 index 0000000..f4912c4 --- /dev/null +++ b/code/ff/IFC/ImmPeriodic.h @@ -0,0 +1,259 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmPeriodic.h + + PURPOSE: Base Periodic Class for Immersion Foundation Classes + + STARTED: 11/03/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + 11/15/99 sdr (Steve Rank): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMPERIODIC_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMPERIODIC_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmBaseTypes.h" +#include "ImmEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +#define IMM_PERIODIC_DEFAULT_DURATION 1000 // Milliseconds +#define IMM_PERIODIC_DEFAULT_MAGNITUDE 5000 +#define IMM_PERIODIC_DEFAULT_PERIOD 100 // Milliseconds +#define IMM_PERIODIC_DEFAULT_OFFSET 0 +#define IMM_PERIODIC_DEFAULT_PHASE 0 // Degrees +#define IMM_PERIODIC_DEFAULT_DIRECTION_X 1 // Pixels +#define IMM_PERIODIC_DEFAULT_DIRECTION_Y 0 // Pixels +#define IMM_PERIODIC_DEFAULT_ANGLE 9000 // 100ths of degrees + + + + +//================================================================ +// CImmPeriodic +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmPeriodic : public CImmEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructors + + // You may use this form if you will immediately initialize it + // from an IFR file... + CImmPeriodic(); + + // Otherwise use this form... + CImmPeriodic( + const GUID& rguidEffect + ); + + // Destructor + virtual + ~CImmPeriodic(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + virtual DWORD GetEffectType() + { return IMM_EFFECTTYPE_PERIODIC; } + + BOOL + ChangeParameters( + DWORD dwMagnitude, + DWORD dwPeriod = IMM_EFFECT_DONT_CHANGE, + DWORD dwDuration = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE, + LONG lOffset = IMM_EFFECT_DONT_CHANGE, + DWORD dwPhase = IMM_EFFECT_DONT_CHANGE, + LPIMM_ENVELOPE pEnvelope = (LPIMM_ENVELOPE) IMM_EFFECT_DONT_CHANGE_PTR + ); + + BOOL + ChangeParametersPolar( + DWORD dwMagnitude, + DWORD dwPeriod = IMM_EFFECT_DONT_CHANGE, + DWORD dwDuration = IMM_EFFECT_DONT_CHANGE, + LONG lAngle = IMM_EFFECT_DONT_CHANGE, + LONG lOffset = IMM_EFFECT_DONT_CHANGE, + DWORD dwPhase = IMM_EFFECT_DONT_CHANGE, + LPIMM_ENVELOPE pEnvelope = (LPIMM_ENVELOPE) IMM_EFFECT_DONT_CHANGE_PTR + ); + + BOOL ChangeMagnitude( DWORD dwMagnitude ); + BOOL ChangePeriod( DWORD dwPeriod ); + BOOL ChangeOffset( LONG lOffset ); + BOOL ChangePhase( DWORD dwPhase ); + + BOOL GetMagnitude( DWORD &dwMagnitude ); + BOOL GetPeriod( DWORD &dwPeriod ); + BOOL GetOffset( LONG &lOffset ); + BOOL GetPhase( DWORD &dwPhase ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + const IMM_EFFECT &effect, + DWORD dwNoDownload = 0 + ); + + virtual BOOL + Initialize( + CImmDevice* pDevice, + DWORD dwMagnitude = IMM_PERIODIC_DEFAULT_MAGNITUDE, + DWORD dwPeriod = IMM_PERIODIC_DEFAULT_PERIOD, + DWORD dwDuration = IMM_PERIODIC_DEFAULT_DURATION, + LONG lDirectionX = IMM_PERIODIC_DEFAULT_DIRECTION_X, + LONG lDirectionY = IMM_PERIODIC_DEFAULT_DIRECTION_Y, + LONG lOffset = IMM_PERIODIC_DEFAULT_OFFSET, + DWORD dwPhase = IMM_PERIODIC_DEFAULT_PHASE, + LPIMM_ENVELOPE pEnvelope = NULL, + DWORD dwNoDownload = 0 + ); + + virtual BOOL + InitializePolar( + CImmDevice* pDevice, + DWORD dwMagnitude = IMM_PERIODIC_DEFAULT_MAGNITUDE, + DWORD dwPeriod = IMM_PERIODIC_DEFAULT_PERIOD, + DWORD dwDuration = IMM_PERIODIC_DEFAULT_DURATION, + LONG lAngle = IMM_PERIODIC_DEFAULT_ANGLE, + LONG lOffset = IMM_PERIODIC_DEFAULT_OFFSET, + DWORD dwPhase = IMM_PERIODIC_DEFAULT_PHASE, + LPIMM_ENVELOPE pEnvelope = NULL, + DWORD dwNoDownload = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + DWORD dwDuration, + DWORD dwMagnitude, + DWORD dwPeriod, + LONG lOffset, + DWORD dwPhase, + LPIMM_ENVELOPE pEnvelope + ); + + DWORD + change_parameters( + LONG lDirection0, + LONG lDirection1, + DWORD dwDuration, + DWORD dwMagnitude, + DWORD dwPeriod, + LONG lOffset, + DWORD dwPhase, + LPIMM_ENVELOPE pEnvelope + ); + + int + buffer_ifr_data( + TCHAR* pData + ); + + BOOL + get_ffe_data( + LPDIEFFECT pdiEffect + ); + + // + // INTERNAL DATA + // + + protected: + + IMM_PERIODIC m_Periodic; + +}; + + +// +// INLINES +// + +inline BOOL +CImmPeriodic::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_Sine) || + IsEqualGUID(guid, GUID_Imm_Square) || + IsEqualGUID(guid, GUID_Imm_Triangle) || + IsEqualGUID(guid, GUID_Imm_SawtoothUp) || + IsEqualGUID(guid, GUID_Imm_SawtoothDown); +} + +#endif // !defined(AFX_IMMPERIODIC_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmProjects.h b/code/ff/IFC/ImmProjects.h new file mode 100644 index 0000000..13f21b9 --- /dev/null +++ b/code/ff/IFC/ImmProjects.h @@ -0,0 +1,392 @@ +/********************************************************************** + Copyright (c) 1999 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmProjects.h + + PURPOSE: CImmProject + Manages a set of forces in a project. + There will be a project for each opened IFR file. + CImmProjects + Manages a set of projects + + STARTED: 2/22/99 by Jeff Mallett + + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + +#ifndef __IMM_PROJECTS_H +#define __IMM_PROJECTS_H + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + + +#include "IFCErrors.h" +#include "ImmBaseTypes.h" +#include "ImmDevice.h" +#include "ImmCompoundEffect.h" + +class CImmProjects; + + +//================================================================ +// CImmProject +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmProject +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + CImmProject(); + + ~CImmProject(); + + void + Close(); + + + // + // ATTRIBUTES + // + + public: + + CImmDevice* + GetDevice() const + { return m_pDevice; } + + BOOL + GetIsOpen() const + { return m_hProj != NULL; } + + CImmCompoundEffect * + GetCreatedEffect( + LPCSTR lpszEffectName + ); + + CImmCompoundEffect * + GetCreatedEffect( + int nIndex + ); + + int + GetNumEffectsFromIFR(); + + LPCSTR + GetEffectNameFromIFRbyIndex( + int nEffectIndex + ); + + LPCSTR + GetEffectSoundPathFromIFR( + LPCSTR lpszEffectName + ); + + DWORD + GetEffectType( + LPCSTR lpszEffectName + ); + + DWORD + GetEffectTypeFromIFR( + LPCSTR lpszEffectName + ); + + DWORD + GetEffectTypeFromIFR( + int nEffectIndex + ); + + int + GetNumCreatedEffects() + { return m_nCreatedEffects;} + + // + // OPERATIONS + // + + public: + + BOOL + Start( + LPCSTR lpszEffectName = NULL, + DWORD dwIterations = IMM_EFFECT_DONT_CHANGE, + DWORD dwFlags = 0, + CImmDevice* pDevice = NULL + ); + + BOOL + Stop( + LPCSTR lpszEffectName = NULL + ); + + BOOL + OpenFile( + LPCSTR lpszFilePath, + CImmDevice *pDevice + ); + + BOOL + LoadProjectFromResource( + HMODULE hRsrcModule, + LPCSTR pRsrcName, + CImmDevice *pDevice + ); + + BOOL + LoadProjectFromMemory( + LPVOID pProjectDef, + CImmDevice *pDevice + ); + + BOOL + LoadProjectObjectPointer( + BYTE *pMem, + CImmDevice *pDevice + ); + + BOOL + WriteToFile( + LPCSTR lpszFilename + ); + + CImmCompoundEffect * + CreateEffect( + LPCSTR lpszEffectName, + CImmDevice* pDevice = NULL, + DWORD dwNoDownload = 0 + ); + + CImmCompoundEffect * + CreateEffectByIndex( + int nEffectIndex, + CImmDevice* pDevice = NULL, + DWORD dwNoDownload = 0 + ); + + CImmCompoundEffect * + AddEffect( + LPCSTR lpszEffectName, + GENERIC_EFFECT_PTR pObject + ); + +#if (IFC_VERSION >= 0x0101) + void + DestroyEffect( + CImmCompoundEffect *pCompoundEffect + ); +#endif + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + void + set_next( + CImmProject *pNext + ) + { m_pNext = pNext; } + + CImmProject * + get_next() const + { return m_pNext; } + + void + append_effect_to_list( + CImmCompoundEffect* pEffect + ); +#if (IFC_VERSION >= 0x0101) + BOOL + remove_effect_from_list( + CImmCompoundEffect* pEffect + ); +#endif + + IFREffect ** + create_effect_structs( + LPCSTR lpszEffectName, + int &nEff + ); + + IFREffect ** + create_effect_structs_by_index( + int nEffectIndex, + int &nEff + ); + + BOOL + release_effect_structs( + IFREffect **hEffects + ); + + // + // FRIENDS + // + + public: + + friend BOOL + CImmEffect::InitializeFromProject( + CImmProject &project, + LPCSTR lpszEffectName, + CImmDevice* pDevice, /* = NULL */ + DWORD dwNoDownload // = 0 + ); + +#ifdef PROTECT_AGAINST_DELETION + friend CImmCompoundEffect::~CImmCompoundEffect(); +#endif + + friend class CImmProjects; + + // + // INTERNAL DATA + // + + protected: + + HIFRPROJECT m_hProj; + DWORD m_dwProjectFileType; + CImmCompoundEffect* m_pCreatedEffects; + CImmDevice* m_pDevice; + LPDIRECTINPUT m_piDI7; + LPDIRECTINPUTDEVICE2 m_piDIDevice7; + TCHAR m_szProjectFileName[MAX_PATH]; + + int m_nCreatedEffects; + + private: + + CImmProject* m_pNext; +}; + + + +//================================================================ +// CImmProjects +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmProjects +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + CImmProjects() : m_pProjects(NULL) { } + + ~CImmProjects(); + + void + Close(); + + + // + // ATTRIBUTES + // + + public: + + CImmProject * + GetProject( + int index = 0 + ); + + + // + // OPERATIONS + // + + public: + + BOOL + Stop(); + + long + OpenFile( + LPCSTR lpszFilePath, + CImmDevice *pDevice + ); + + long + LoadProjectFromResource( + HMODULE hRsrcModule, + LPCSTR pRsrcName, + CImmDevice *pDevice + ); + + long + LoadProjectFromMemory( + LPVOID pProjectDef, + CImmDevice *pDevice + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + + // + // INTERNAL DATA + // + protected: + + CImmProject *m_pProjects; +}; + + + +#endif // __IMM_PROJECTS_H diff --git a/code/ff/IFC/ImmRamp.h b/code/ff/IFC/ImmRamp.h new file mode 100644 index 0000000..1acca49 --- /dev/null +++ b/code/ff/IFC/ImmRamp.h @@ -0,0 +1,225 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmRamp.h + + PURPOSE: Base Ramp Force Class for Immersion Foundation Classes + + STARTED: 12/11/97 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + 11/15/99 sdr (Steve Rank): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMRAMP_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMRAMP_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include "ImmBaseTypes.h" +#include "ImmEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +#define IMM_RAMP_DEFAULT_DURATION 1000 // Milliseconds +#define IMM_RAMP_DEFAULT_MAGNITUDE_START 0 +#define IMM_RAMP_DEFAULT_MAGNITUDE_END 10000 + + + +//================================================================ +// CImmRamp +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmRamp : public CImmEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CImmRamp(); + + // Destructor + virtual + ~CImmRamp(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + virtual DWORD GetEffectType() + { return IMM_EFFECTTYPE_RAMPFORCE; } + + BOOL + ChangeParameters( + LONG lDirectionX, + LONG lDirectionY, + DWORD dwDuration = IMM_EFFECT_DONT_CHANGE, + LONG lMagStart = IMM_EFFECT_DONT_CHANGE, + LONG lMagEnd = IMM_EFFECT_DONT_CHANGE, + LPIMM_ENVELOPE pEnvelope = (LPIMM_ENVELOPE) IMM_EFFECT_DONT_CHANGE_PTR + ); + + BOOL + ChangeParametersPolar( + LONG lAngle, + DWORD dwDuration = IMM_EFFECT_DONT_CHANGE, + LONG lMagStart = IMM_EFFECT_DONT_CHANGE, + LONG lMagEnd = IMM_EFFECT_DONT_CHANGE, + LPIMM_ENVELOPE pEnvelope = (LPIMM_ENVELOPE) IMM_EFFECT_DONT_CHANGE_PTR + ); + + BOOL ChangeStartMagnitude( LONG lMagStart ); + BOOL ChangeEndMagnitude( LONG lMagEnd ); + + BOOL GetStartMagnitude( LONG &lMagStart ); + BOOL GetEndMagnitude( LONG &lMagEnd ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + const IMM_EFFECT &effect, + DWORD dwFlags = 0 + ); + + virtual BOOL + Initialize( + CImmDevice* pDevice, + LONG lDirectionX = IMM_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = IMM_EFFECT_DEFAULT_DIRECTION_Y, + DWORD dwDuration = IMM_RAMP_DEFAULT_DURATION, + LONG lMagStart = IMM_RAMP_DEFAULT_MAGNITUDE_START, + LONG lMagEnd = IMM_RAMP_DEFAULT_MAGNITUDE_END, + LPIMM_ENVELOPE pEnvelope = NULL, + DWORD dwFlags = 0 + ); + + virtual BOOL + InitializePolar( + CImmDevice* pDevice, + LONG lAngle = IMM_EFFECT_DEFAULT_ANGLE, + DWORD dwDuration = IMM_RAMP_DEFAULT_DURATION, + LONG lMagStart = IMM_RAMP_DEFAULT_MAGNITUDE_START, + LONG lMagEnd = IMM_RAMP_DEFAULT_MAGNITUDE_END, + LPIMM_ENVELOPE pEnvelope = NULL, + DWORD dwFlags = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + DWORD dwDuration, + LONG lMagStart, + LONG lMagEnd, + LPIMM_ENVELOPE pEnvelope + ); + + DWORD + change_parameters( + LONG lDirection0, + LONG lDirection1, + DWORD dwDuration, + LONG lMagStart, + LONG lMagEnd, + LPIMM_ENVELOPE pEnvelope + ); + + int + buffer_ifr_data( + TCHAR* pData + ); + + BOOL + get_ffe_data( + LPDIEFFECT pdiEffect + ); + + // + // INTERNAL DATA + // + + protected: + + IMM_RAMPFORCE m_RampForce; + +}; + + +// +// INLINES +// + +inline BOOL +CImmRamp::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_RampForce); +} + +#endif // !defined(AFX_IMMRAMP_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/IFC/ImmSpring.h b/code/ff/IFC/ImmSpring.h new file mode 100644 index 0000000..d7a2509 --- /dev/null +++ b/code/ff/IFC/ImmSpring.h @@ -0,0 +1,183 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmSpring.h + + PURPOSE: Immersion Foundation Classes Spring Effect + + STARTED: Oct.10.97 + + NOTES/REVISIONS: + Mar.02.99 jrm (Jeff Mallett): Force-->Feel renaming + Mar.02.99 jrm: Added GetIsCompatibleGUID + Mar.15.99 jrm: __declspec(dllimport/dllexport) the whole class + Nov.15.99 efw (Evan Wies): Converted to IFC + +**********************************************************************/ + + +#if !defined(AFX_IMMSpring_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_IMMSpring_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include +#include "ImmCondition.h" + + +//================================================================ +// Constants +//================================================================ + +#define IMM_SPRING_DEFAULT_STIFFNESS 2500 +#define IMM_SPRING_DEFAULT_SATURATION 10000 +#define IMM_SPRING_DEFAULT_DEADBAND 100 +#define IMM_SPRING_DEFAULT_CENTER_POINT IMM_EFFECT_MOUSE_POS_AT_START +#define IMM_SPRING_DEFAULT_DIRECTION_X 1 +#define IMM_SPRING_DEFAULT_DIRECTION_Y 0 +#define IMM_SPRING_DEFAULT_ANGLE 0 + + +//================================================================ +// CImmSpring +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmSpring : public CImmCondition +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CImmSpring(); + + // Destructor + virtual + ~CImmSpring(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + BOOL + ChangeParameters( + POINT pntCenter, + LONG lStiffness = IMM_EFFECT_DONT_CHANGE, + DWORD dwSaturation = IMM_EFFECT_DONT_CHANGE, + DWORD dwDeadband = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeParametersPolar( + POINT pntCenter, + LONG lStiffness, + DWORD dwSaturation, + DWORD dwDeadband, + LONG lAngle + ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + LONG lStiffness = IMM_SPRING_DEFAULT_STIFFNESS, + DWORD dwSaturation = IMM_SPRING_DEFAULT_SATURATION, + DWORD dwDeadband = IMM_SPRING_DEFAULT_DEADBAND, + DWORD dwfAxis = IMM_EFFECT_AXIS_BOTH, + POINT pntCenter = IMM_SPRING_DEFAULT_CENTER_POINT, + LONG lDirectionX = IMM_SPRING_DEFAULT_DIRECTION_X, + LONG lDirectionY = IMM_SPRING_DEFAULT_DIRECTION_Y, + BOOL bUseDeviceCoordinates = FALSE, + DWORD dwNoDownload = 0 + ); + + virtual BOOL + InitializePolar( + CImmDevice* pDevice, + LONG lStiffness, + DWORD dwSaturation, + DWORD dwDeadband, + POINT pntCenter, + LONG lAngle, + BOOL bUseDeviceCoordinates = FALSE, + DWORD dwNoDownload = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + // + // INTERNAL DATA + // + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CImmSpring::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_Spring); +} + +#endif // !defined(AFX_IMMSpring_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) + diff --git a/code/ff/IFC/ImmTexture.h b/code/ff/IFC/ImmTexture.h new file mode 100644 index 0000000..0fcf018 --- /dev/null +++ b/code/ff/IFC/ImmTexture.h @@ -0,0 +1,407 @@ +/********************************************************************** + Copyright (c) 1997 - 2000 Immersion Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation may be granted without fee; + interested parties are encouraged to request permission from + Immersion Corporation + 801 Fox Lane + San Jose, CA 95131 + 408-467-1900 + + IMMERSION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + IN NO EVENT SHALL IMMERSION BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + FILE: ImmTexture.h + + PURPOSE: Texture Class for Feelit API Foundation Classes + + STARTED: 2/27/98 + + NOTES/REVISIONS: + 3/2/99 jrm (Jeff Mallett): Force-->Feel renaming + 3/2/99 jrm: Added GetIsCompatibleGUID + 3/15/99 jrm: __declspec(dllimport/dllexport) the whole class + +**********************************************************************/ + + +#if !defined(AFX_ImmTexture_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) +#define AFX_ImmTexture_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#ifndef _IFCDLL_ +#define DLLIFC __declspec(dllimport) +#else +#define DLLIFC __declspec(dllexport) +#endif + +#include +#include "ImmBaseTypes.h" +#include "ImmEffect.h" + + + +//================================================================ +// Constants +//================================================================ + +const POINT IMM_TEXTURE_PT_NULL = { 0, 0 }; +const POINT IMM_TEXTURE_DEFAULT_OFFSET_POINT = { 0, 0}; + +#define IMM_TEXTURE_DEFAULT_MAGNITUDE 5000 +#define IMM_TEXTURE_DEFAULT_WIDTH 10 +#define IMM_TEXTURE_DEFAULT_SPACING 20 + + +//================================================================ +// CImmTexture +//================================================================ + +// +// ------ PUBLIC INTERFACE ------ +// + +class DLLIFC CImmTexture : public CImmEffect +{ + // + // CONSTRUCTOR/DESTRUCTOR + // + + public: + + // Constructor + CImmTexture(); + + // Destructor + virtual + ~CImmTexture(); + + + // + // ATTRIBUTES + // + + public: + + virtual BOOL + GetIsCompatibleGUID( + GUID &guid + ); + + virtual DWORD GetEffectType() + { return IMM_EFFECTTYPE_TEXTURE; } + + // Use this form for single-axis and dual-axis effects + BOOL + ChangeTextureParams( + LPCIMM_TEXTURE pTextureX, + LPCIMM_TEXTURE pTextureY + ); + + // Use this form for directional effects + BOOL + ChangeTextureParams( + LPCIMM_TEXTURE pTexture, + LONG lDirectionX, + LONG lDirectionY + ); + + // Use this form for directional effects + BOOL + ChangeTextureParamsPolar( + LPCIMM_TEXTURE pTexture, + LONG lAngle + ); + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + ChangeTextureParams( + LONG lPosBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpSpacing = IMM_EFFECT_DONT_CHANGE, + LONG lNegBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpSpacing = IMM_EFFECT_DONT_CHANGE, + POINT pntOffset = IMM_EFFECT_DONT_CHANGE_POINT, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE + ); + + // Use this form for single-axis, dual-axis, or directional effects + BOOL + ChangeTextureParamsX( + LONG lPosBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpSpacing = IMM_EFFECT_DONT_CHANGE, + LONG lNegBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpSpacing = IMM_EFFECT_DONT_CHANGE, + POINT pntOffset = IMM_EFFECT_DONT_CHANGE_POINT, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeTextureParamsY( + LONG lPosBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpSpacing = IMM_EFFECT_DONT_CHANGE, + LONG lNegBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpSpacing = IMM_EFFECT_DONT_CHANGE, + POINT pntOffset = IMM_EFFECT_DONT_CHANGE_POINT, + LONG lDirectionX = IMM_EFFECT_DONT_CHANGE, + LONG lDirectionY = IMM_EFFECT_DONT_CHANGE + ); + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + ChangeTextureParamsPolar( + LONG lPosBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpSpacing = IMM_EFFECT_DONT_CHANGE, + LONG lNegBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpSpacing = IMM_EFFECT_DONT_CHANGE, + POINT pntOffset = IMM_EFFECT_DONT_CHANGE_POINT, + LONG lAngle = IMM_EFFECT_DONT_CHANGE + ); + + // Use this form for single-axis, dual-axis, or directional effects + BOOL + ChangeTextureParamsPolarX( + LONG lPosBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpSpacing = IMM_EFFECT_DONT_CHANGE, + LONG lNegBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpSpacing = IMM_EFFECT_DONT_CHANGE, + POINT pntOffset = IMM_EFFECT_DONT_CHANGE_POINT, + LONG lAngle = IMM_EFFECT_DONT_CHANGE + ); + + BOOL + ChangeTextureParamsPolarY( + LONG lPosBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwPosBumpSpacing = IMM_EFFECT_DONT_CHANGE, + LONG lNegBumpMag = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpWidth = IMM_EFFECT_DONT_CHANGE, + DWORD dwNegBumpSpacing = IMM_EFFECT_DONT_CHANGE, + POINT pntOffset = IMM_EFFECT_DONT_CHANGE_POINT, + LONG lAngle = IMM_EFFECT_DONT_CHANGE + ); + + // Use these to change the the X Axis parameters for a dual-axis effect + BOOL ChangePositiveBumpMagX( LONG lPosBumpMag ); + BOOL ChangeNegativeBumpMagX( LONG lNegBumpMag ); + BOOL ChangePositiveBumpSpacingX( DWORD dwPosBumpSpacing ); + BOOL ChangeNegativeBumpSpacingX( DWORD dwNegBumpSpacing ); + BOOL ChangePositiveBumpWidthX( DWORD dwPosBumpWidth ); + BOOL ChangeNegativeBumpWidthX( DWORD dwNegBumpWidth ); + + // Use these to change the the Y Axis parameters for a dual-axis effect + BOOL ChangePositiveBumpMagY( LONG lPosBumpMag ); + BOOL ChangeNegativeBumpMagY( LONG lNegBumpMag ); + BOOL ChangePositiveBumpSpacingY( DWORD dwPosBumpSpacing ); + BOOL ChangeNegativeBumpSpacingY( DWORD dwNegBumpSpacing ); + BOOL ChangePositiveBumpWidthY( DWORD dwPosBumpWidth ); + BOOL ChangeNegativeBumpWidthY( DWORD dwNegBumpWidth ); + + // Use these to change the the parameters for a single-axis or + // dual-axis symetrical effect + BOOL ChangePositiveBumpMag( LONG lPosBumpMag ); + BOOL ChangeNegativeBumpMag( LONG lNegBumpMag ); + BOOL ChangePositiveBumpSpacing( DWORD dwPosBumpSpacing ); + BOOL ChangeNegativeBumpSpacing( DWORD dwNegBumpSpacing ); + BOOL ChangePositiveBumpWidth( DWORD dwPosBumpWidth ); + BOOL ChangeNegativeBumpWidth( DWORD dwNegBumpWidth ); + + BOOL ChangeOffset( POINT pntOffset ); + + BOOL GetPositiveBumpMagX( LONG &lPosBumpMag ); + BOOL GetNegativeBumpMagX( LONG &lNegBumpMag ); + BOOL GetPositiveBumpSpacingX( DWORD &dwPosBumpSpacing ); + BOOL GetNegativeBumpSpacingX( DWORD &dwNegBumpSpacing ); + BOOL GetPositiveBumpWidthX( DWORD &dwPosBumpWidth ); + BOOL GetNegativeBumpWidthX( DWORD &dwNegBumpWidth ); + BOOL GetPositiveBumpMagY( LONG &lPosBumpMag ); + BOOL GetNegativeBumpMagY( LONG &lNegBumpMag ); + BOOL GetPositiveBumpSpacingY( DWORD &dwPosBumpSpacing ); + BOOL GetNegativeBumpSpacingY( DWORD &dwNegBumpSpacing ); + BOOL GetPositiveBumpWidthY( DWORD &dwPosBumpWidth ); + BOOL GetNegativeBumpWidthY( DWORD &dwNegBumpWidth ); + BOOL GetOffset( POINT &pntOffset ); + + // + // OPERATIONS + // + + public: + + virtual BOOL + Initialize( + CImmDevice* pDevice, + const IMM_EFFECT &effect, + DWORD dwNoDownload = 0 + ); + + // Use this form for single-axis and dual-axis effects + BOOL + InitTexture( + CImmDevice* pDevice, + LPCIMM_TEXTURE pTextureX, + LPCIMM_TEXTURE pTextureY, + DWORD dwNoDownload = 0 + ); + + + // Use this form for directional effects + BOOL + InitTexture( + CImmDevice* pDevice, + LPCIMM_TEXTURE pTexture, + LONG lDirectionX, + LONG lDirectionY, + DWORD dwNoDownload = 0 + ); + + + // Use this form for directional effects + BOOL + InitTexturePolar( + CImmDevice* pDevice, + LPCIMM_TEXTURE pTexture, + LONG lAngle, + DWORD dwNoDownload = 0 + ); + + + // Use this form for single-axis, dual-axis symetrical, or directional effects + BOOL + InitTexture( + CImmDevice* pDevice, + LONG lPosBumpMag = IMM_TEXTURE_DEFAULT_MAGNITUDE, + DWORD dwPosBumpWidth = IMM_TEXTURE_DEFAULT_WIDTH, + DWORD dwPosBumpSpacing = IMM_TEXTURE_DEFAULT_SPACING, + LONG lNegBumpMag = IMM_TEXTURE_DEFAULT_MAGNITUDE, + DWORD dwNegBumpWidth = IMM_TEXTURE_DEFAULT_WIDTH, + DWORD dwNegBumpSpacing = IMM_TEXTURE_DEFAULT_SPACING, + DWORD dwfAxis = IMM_EFFECT_AXIS_BOTH, + POINT pntOffset = IMM_TEXTURE_DEFAULT_OFFSET_POINT, + LONG lDirectionX = IMM_EFFECT_DEFAULT_DIRECTION_X, + LONG lDirectionY = IMM_EFFECT_DEFAULT_DIRECTION_Y, + DWORD dwNoDownload = 0 + ); + + // Use this form for directional effects + BOOL + InitTexturePolar( + CImmDevice* pDevice, + LONG lPosBumpMag = IMM_TEXTURE_DEFAULT_MAGNITUDE, + DWORD dwPosBumpWidth = IMM_TEXTURE_DEFAULT_WIDTH, + DWORD dwPosBumpSpacing = IMM_TEXTURE_DEFAULT_SPACING, + LONG lNegBumpMag = IMM_TEXTURE_DEFAULT_MAGNITUDE, + DWORD dwNegBumpWidth = IMM_TEXTURE_DEFAULT_WIDTH, + DWORD dwNegBumpSpacing = IMM_TEXTURE_DEFAULT_SPACING, + POINT pntOffset = IMM_TEXTURE_DEFAULT_OFFSET_POINT, + LONG lAngle = IMM_EFFECT_DEFAULT_ANGLE, + DWORD dwNoDownload = 0 + ); + + +// +// ------ PRIVATE INTERFACE ------ +// + + // + // HELPERS + // + + protected: + + BOOL + set_parameters( + DWORD dwfAxis, + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + LPCIMM_TEXTURE pTextureX, + LPCIMM_TEXTURE pTextureY + ); + + BOOL + set_parameters( + DWORD dwfAxis, + DWORD dwfCoordinates, + LONG lDirection0, + LONG lDirection1, + LONG lPosBumpMag, + DWORD dwPosBumpWidth, + DWORD dwPosBumpSpacing, + LONG lNegBumpMag, + DWORD dwNegBumpWidth, + DWORD dwNegBumpSpacing, + POINT pntOffset + ); + + DWORD + change_parameters( + LONG lDirection0, + LONG lDirection1, + LPCIMM_TEXTURE pTextureX, + LPCIMM_TEXTURE pTextureY + ); + + DWORD + change_parameters( + LONG lDirection0, + LONG lDirection1, + LONG lPosBumpMag, + DWORD dwPosBumpWidth, + DWORD dwPosBumpSpacing, + LONG lNegBumpMag, + DWORD dwNegBumpWidth, + DWORD dwNegBumpSpacing, + POINT pntOffset, + int fAxis + ); + + int + buffer_ifr_data( + TCHAR* pData + ); + + // + // INTERNAL DATA + // + + IMM_TEXTURE m_aTexture[2]; + DWORD m_dwfAxis; + + protected: + +}; + + + +// +// INLINES +// + +inline BOOL +CImmTexture::GetIsCompatibleGUID(GUID &guid) +{ + return IsEqualGUID(guid, GUID_Imm_Texture); +} + +#endif // !defined(AFX_ImmTexture_H__135B88C4_4175_11D1_B049_0020AF30269A__INCLUDED_) diff --git a/code/ff/cl_ff.cpp b/code/ff/cl_ff.cpp new file mode 100644 index 0000000..931496e --- /dev/null +++ b/code/ff/cl_ff.cpp @@ -0,0 +1,72 @@ +//#include "../server/exe_headers.h" +#include "../client/client.h" + +#ifdef _IMMERSION + +#include "ff_public.h" +#include "ff.h" +#include "ff_snd.h" + +extern clientActive_t cl; + +void CL_InitFF( void ) +{ + cvar_t *use_ff = Cvar_Get( "use_ff", "1", CVAR_ARCHIVE ); + + if (!use_ff + || !use_ff->integer + || !FF_Init() + ){ + FF_Shutdown(); + } +} + +void CL_ShutdownFF( void ) +{ + FF_Shutdown(); +} + +qboolean IsLocalClient( int clientNum ) +{ + return qboolean + ( clientNum == 0 //clientNum == cl.snap.ps.clientNum + || clientNum == FF_CLIENT_LOCAL // assumed local + ); +} + +void CL_FF_Start( ffHandle_t ff, int clientNum ) +{ + if ( IsLocalClient( clientNum ) ) + { + //FF_Play( ff ); // plays instantly + FF_AddForce( ff ); // plays at end of frame + } +} + +void CL_FF_Stop( ffHandle_t ff, int clientNum ) +{ + if ( IsLocalClient( clientNum ) ) + { + FF_Stop( ff ); + } +} + +/* +void CL_FF_EnsurePlaying( ffHandle_t ff, int clientNum ) +{ + if ( IsLocalClient( clientNum ) ) + { + FF_EnsurePlaying( ff ); + } +} +*/ + +void CL_FF_AddLoopingForce( ffHandle_t ff, int clientNum ) +{ + if ( IsLocalClient( clientNum ) ) + { + FF_AddLoopingForce( ff ); + } +} + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/cl_ff.h b/code/ff/cl_ff.h new file mode 100644 index 0000000..9fc7fc1 --- /dev/null +++ b/code/ff/cl_ff.h @@ -0,0 +1,13 @@ +#ifndef __CL_FF_H +#define __CL_FF_H + +#include "ff_public.h" + +void CL_InitFF ( void ); +void CL_ShutdownFF ( void ); + +void CL_FF_Start ( ffHandle_t ff, int clientNum = FF_CLIENT_LOCAL ); +void CL_FF_Stop ( ffHandle_t ff, int clientNum = FF_CLIENT_LOCAL ); +void CL_FF_AddLoopingForce ( ffHandle_t ff, int clientNum = FF_CLIENT_LOCAL ); + +#endif // __CL_FF_H diff --git a/code/ff/common_headers.h b/code/ff/common_headers.h new file mode 100644 index 0000000..20b20eb --- /dev/null +++ b/code/ff/common_headers.h @@ -0,0 +1,50 @@ +#ifndef FF_COMMON_HEADERS_H +#define FF_COMMON_HEADERS_H + +#ifdef _IMMERSION + +#include "ff_local.h" + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4032) +#pragma warning(disable : 4051) +#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4115) +#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4136) +#pragma warning(disable : 4152) // nonstandard extension, function/data pointer conversion in expression +#pragma warning(disable : 4201) +#pragma warning(disable : 4214) // bitfields on type other than int +#pragma warning(disable : 4244) // conversion from double to float +#pragma warning(disable : 4284) // return type not UDT +#pragma warning(disable : 4305) // truncation from const double to float +#pragma warning(disable : 4310) // cast truncates constant value +#pragma warning(disable : 4503) // decorated name length truncated +#pragma warning(disable : 4505) // unreferenced local function has been removed +#pragma warning(disable : 4511) // copy ctor could not be genned +#pragma warning(disable : 4512) // assignment op could not be genned +#pragma warning(disable : 4514) // unreffed inline removed +#pragma warning(disable : 4663) // c++ lang change +#pragma warning(disable : 4710) // not inlined +#pragma warning(disable : 4711) // selected for automatic inline expansion +#pragma warning(disable : 4220) // varargs matches remaining parameters +#pragma warning(disable : 4786) // identifier was truncated +#pragma warning(disable : 4800) // forcing value to bool 'true' or 'false' (performance warning) +#pragma warning(disable : 4702) +#pragma warning( push, 3 ) +#include +#include +#include +#include +#pragma warning( pop ) +using namespace std; + +#include "ifc.h" +#include "ff_utils.h" +#include "ff_system.h" + +#endif // _IMMERSION + +#endif // FF_COMMON_HEADERS_H diff --git a/code/ff/ff.cpp b/code/ff/ff.cpp new file mode 100644 index 0000000..f3da949 --- /dev/null +++ b/code/ff/ff.cpp @@ -0,0 +1,383 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +#define INITGUID // this will need removing if already defined in someone else's module. Only one must exist in whole game + +//#include "ff.h" +//#include "ff_ffset.h" +//#include "ff_compound.h" +//#include "ff_system.h" + +FFSystem gFFSystem; + +cvar_t *use_ff; +cvar_t *ensureShake; +cvar_t *ff_developer; +#ifdef FF_DELAY +cvar_t *ff_delay; +#endif +cvar_t *ff_channels; + +static const char *_pass = "SUCCEEDED"; +static const char *_fail = "FAILED"; + +const char *gChannelName[ FF_CHANNEL_MAX ] = +{ "FF_CHANNEL_WEAPON" +, "FF_CHANNEL_MENU" +, "FF_CHANNEL_TOUCH" +, "FF_CHANNEL_DAMAGE" +, "FF_CHANNEL_BODY" +, "FF_CHANNEL_FORCE" +, "FF_CHANNEL_FOOT" +}; + +// Enable/Disable Com_Printf in FF_* functions +#if( 1 ) +#ifdef FF_PRINT +#define FF_PROLOGUE( name, string ) qboolean result = qfalse; if ( FF_IsAvailable() ) { if ( ff_developer && ff_developer->integer ) Com_Printf( "%s: \"%s\" ", #name, string ); +#define FF_PROLOGUE_NOQUOTE( name, string ) qboolean result = qfalse; if ( FF_IsAvailable() ) { if ( ff_developer && ff_developer->integer ) Com_Printf( "%s: %s ", #name, string ); +#define FF_EPILOGUE FF_EPILOGUE_NORETURN; return result; +#define FF_EPILOGUE_NORETURN } if ( ff_developer && ff_developer->integer ) Com_Printf( "[%s]\n", ( result ? _pass : _fail ) ); +#define FF_RESULT( function ) result = qboolean( function ); +#else +#define FF_PROLOGUE( name, string ) qboolean result = qfalse; if ( FF_IsAvailable() ) { +#define FF_PROLOGUE_NOQUOTE( name, string ) qboolean result = qfalse; if ( FF_IsAvailable() ) { +#define FF_EPILOGUE FF_EPILOGUE_NORETURN; return result; +#define FF_EPILOGUE_NORETURN } +#define FF_RESULT( function ) result = qboolean( function ); +#endif +#else +#define FF_PROLOGUE( name, string ) qboolean result = qfalse; +#define FF_PROLOGUE_NOQUOTE( name, string ) qboolean result = qfalse; +#define FF_EPILOGUE return result; +#define FF_EPILOGUE_NORETURN +#define FF_RESULT( function ) result = qboolean( function ); +#endif + +////-------------- +/// FF_IsAvailable +//------------------ +// Test to see if force feedback is currently operating. This is almost useless. +// The only good it does currently is temporarily toggle effects on/off for users +// amusement... feedback on, feedback off, feedback on, feedback off. Results are +// instantaneous. FF_* calls basically skip themselves harmlessly. +// +// Assumptions: +// * External system unloads this module if no device is present. +// * External system unloads this module if feedback is disabled when system (re)starts +// +// Parameters: +// None +// +// Returns: +// - true: feedback currently enabled +// - false: feedback currently disabled +// +qboolean FF_IsAvailable(void) +{ + return (use_ff && use_ff->integer && gFFSystem.IsInitialized()) ? qtrue : qfalse; +} + +qboolean FF_IsInitialized(void) +{ + return gFFSystem.IsInitialized(); +} + +////------- +/// FF_Init +//----------- +// Initializes the force feedback system. +// +// This function may be called multiple times to apply changes in cvars. +// +// Assumptions: +// * If FF_Init returns qfalse, caller calls FF_Shutdown +// +// Parameters: +// None +// +// Returns: +// - qtrue: module initialized properly. +// - qfalse: module experienced an error. Caller MUST call FF_Shutdown. +// +qboolean FF_Init( void ) +{ + if ( !gFFSystem.IsInitialized() ) + { + // + // Console variable setup + // + +#ifdef FF_CONSOLECOMMAND + Cmd_AddCommand( "ff_stopall", CMD_FF_StopAll ); + Cmd_AddCommand( "ff_info", CMD_FF_Info ); +#endif + use_ff = Cvar_Get( "use_ff", "1", CVAR_ARCHIVE /*| CVAR_LATCH*/); + ensureShake = Cvar_Get( "ff_ensureShake", "1", CVAR_TEMP); + ff_developer = Cvar_Get( "ff_developer", "0", CVAR_TEMP); + ff_channels = Cvar_Get( "ff_channels", FF_CHANNEL, CVAR_ARCHIVE); +#ifdef FF_DELAY + ff_delay = Cvar_Get( "ff_delay", FF_DELAY, CVAR_ARCHIVE); +#endif + } + + return qboolean // assumes external system will call FF_Shutdown in case of failure + ( ff_channels != NULL + && gFFSystem.Init( ff_channels->string ) + ); +} + +////----------- +/// FF_Shutdown +//--------------- +// Shut force feedback system down and free resources. +// +// Assumptions: +// * Always called if FF_Init returns qfalse. ALWAYS. (Or memory leaks occur) +// * Never called twice in succession. (always in response to previous assumption) +// +// Parameters: +// None +// +// Returns: +// None +// +void FF_Shutdown(void) +{ +#ifdef FF_CONSOLECOMMAND + Cmd_RemoveCommand( "ff_stopall" ); + Cmd_RemoveCommand( "ff_info" ); +#endif + + gFFSystem.Shutdown(); +} + +////----------- +/// FF_Register +//--------------- +// Loads a named effect from an .ifr file through the game's file system. The handle +// returned is not tied to any particular device. The feedback system may even change +// which device receives the effect without the need to restart the external system. +// The is ONE EXCEPTION. If this module is not loaded when the registration phase +// passes, the external system will need to be restarted to register effects properly. +// +// Parameters: +// * name: effect name from .ifr (case-sensitive) +// * channel: channel to output effect. A channel may play on 0+ devices. +// * notfound: return a valid handle even if effect is not found +// - Allows temporary disabling of a channel in-game without losing effects +// - Only use with trusted effect names, not user input. See CMD_FF_Play. +// +// Returns: +// Handle to loaded effect +// +ffHandle_t FF_Register( const char *name, int channel, qboolean notfound ) +{ + ffHandle_t ff = FF_HANDLE_NULL; + + // Removed console print... too much spam with AddLoopingForce. +/* + FF_PROLOGUE( FF_Register, ( name ? name : "" ) ); + ff = gFFSystem.Register( name, channel, notfound ); + FF_RESULT( ff != FF_HANDLE_NULL ); + FF_EPILOGUE_NORETURN; +*/ + if ( FF_IsAvailable() ) + ff = gFFSystem.Register( name, channel, notfound ); + + return ff; +} + +////---------------- +/// FF_EnsurePlaying +//-------------------- +// Starts an effect if the effect is not currently playing. +// Does not restart currently playing effects. +// +// Parameters: +// * ff: handle of an effect +// +// Returns: +// - qtrue: effect started +// - qfalse: effect was not started +// +qboolean FF_EnsurePlaying(ffHandle_t ff) +{ + FF_PROLOGUE( FF_EnsurePlaying, gFFSystem.GetName( ff ) ); + FF_RESULT( gFFSystem.EnsurePlaying( ff ) ); + FF_EPILOGUE; +} + +////------- +/// FF_Play +//----------- +// Start an effect on its registered channel. +// +// Parameters +// * ff: handle to an effect +// +// Returns: +// - qtrue: effect started +// - qfalse: effect was not started +// +qboolean FF_Play(ffHandle_t ff) +{ + FF_PROLOGUE( FF_Play, gFFSystem.GetName( ff ) ); + FF_RESULT( gFFSystem.Play( ff ) ); + FF_EPILOGUE; +} + +////---------- +/// FF_StopAll +//-------------- +// Stop all currently playing effects. +// +// Parameters: +// None +// +// Returns: +// - qtrue: no errors occurred +// - qfalse: an error occurred +// +qboolean FF_StopAll(void) +{ + FF_PROLOGUE( FF_StopAll, "" ); + FF_RESULT( gFFSystem.StopAll() ); + FF_EPILOGUE; +} + +////------- +/// FF_Stop +//----------- +// Stop an effect. Only returns qfalse if there's an error +// +// Parameters: +// * ff: handle to a playing effect +// +// Returns: +// - qtrue: no errors occurred +// - qfalse: an error occurred +// +qboolean FF_Stop(ffHandle_t ff) +{ + FF_PROLOGUE( FF_Stop, gFFSystem.GetName( ff ) ); + FF_RESULT( gFFSystem.Stop( ff ) ); + FF_EPILOGUE; +} + + +////-------- +/// FF_Shake +//------------ +// Shake the mouse (play the special "shake" effect) at a given strength +// for a given duration. The shake effect can be a compound containing +// multiple component effects, but each component effect's magnitude and +// duration will be set to the parameters passed in this function. +// +// Parameters: +// * intensity [0..10000] - Magnitude of effect +// * duration [0..MAXINT] - Length of shake in milliseconds +// +// Returns: +// - qtrue: shake started +// - qfalse: shake did not start +// +qboolean FF_Shake(int intensity, int duration) +{ + char message[64]; + message[0] = 0; + sprintf( message, "intensity=%d, duration=%d", intensity, duration ); + FF_PROLOGUE_NOQUOTE( FF_Shake, message ); + FF_RESULT( gFFSystem.Shake( intensity, duration, qboolean( ensureShake->integer != qfalse ) ) ); + FF_EPILOGUE; +} + +#ifdef FF_CONSOLECOMMAND + +////-------------- +/// CMD_FF_StopAll +//------------------ +// Console function which stops all currently playing effects +// +// Parameters: +// None +// +// Returns: +// None +// +void CMD_FF_StopAll(void) +{ + // Display messages + if ( FF_StopAll() ) + { + Com_Printf( "stopping all effects\n" ); + } + else + { + Com_Printf( "failed to stop all effects\n" ); + } +} + +////----------- +/// CMD_FF_Info +//--------------- +// Console function which displays various ff-system information. +// +// Parameters: +// * 'devices' display list of ff devices currently connected +// * 'channels' display list of support ff channels +// * 'order' display search order used by ff name-resolution system (ff_ffset.cpp) +// * 'files' display currently loaded .ifr files sorted by device +// * 'effects' display currently registered effects sorted by device +// +// Returns: +// None +// +void CMD_FF_Info(void) +{ + TNameTable Unprocessed, Processed; + int i, max; + + for + ( i = 1, max = Cmd_Argc() + ; i < max + ; i++ + ){ + Unprocessed.push_back( Cmd_Argv( i ) ); + } + + if ( Unprocessed.size() == 0 ) + { + + if ( ff_developer->integer ) + Com_Printf( "Usage: ff_info [devices] [channels] [order] [files] [effects]\n" ); + else + Com_Printf( "Usage: ff_info [devices] [channels]\n" ); + + return; + } + + gFFSystem.Display( Unprocessed, Processed ); + + if ( Unprocessed.size() > 0 ) + { + Com_Printf( "invalid parameters:" ); + for + ( i = 0 + ; i < Unprocessed.size() + ; i++ + ){ + Com_Printf( " %s", Unprocessed[ i ].c_str() ); + } + + if ( ff_developer->integer ) + Com_Printf( "Usage: ff_info [devices] [channels] [order] [files] [effects]\n" ); + else + Com_Printf( "Usage: ff_info [devices] [channels]\n" ); + } +} + +#endif // FF_CONSOLECOMMAND + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/ff.h b/code/ff/ff.h new file mode 100644 index 0000000..20f8f5f --- /dev/null +++ b/code/ff/ff.h @@ -0,0 +1,33 @@ +#ifndef __FF_H +#define __FF_H + +#include "../ff/ff_public.h" + +#ifdef _FF + +// +// Externally visible functions +// + +qboolean FF_Init (void); +void FF_Shutdown (void); +qboolean FF_IsAvailable (void); +qboolean FF_IsInitialized (void); +ffHandle_t FF_Register (const char* ff, int channel, qboolean notfound = qtrue); +qboolean FF_Play (ffHandle_t ff); +qboolean FF_EnsurePlaying (ffHandle_t ff); +qboolean FF_Stop (ffHandle_t ff); +qboolean FF_StopAll (void); +qboolean FF_Shake (int intensity, int duration); + +#ifdef FF_CONSOLECOMMAND +typedef void (*xcommand_t) (void); +void CMD_FF_StopAll (void); +void CMD_FF_Info (void); +#endif + +//ffExport_t* GetFFAPI ( int apiVersion, ffImport_t *ffimp ); + +#endif // _FF + +#endif // __FF_H diff --git a/code/ff/ff_ChannelCompound.h b/code/ff/ff_ChannelCompound.h new file mode 100644 index 0000000..e26001f --- /dev/null +++ b/code/ff/ff_ChannelCompound.h @@ -0,0 +1,64 @@ +#ifndef FF_CHANNELCOMPOUND_H +#define FF_CHANNELCOMPOUND_H + +#include "ff_MultiCompound.h" + +////--------------- +/// ChannelCompound +//------------------- +// Stored in THandleTable. This class associates MultiCompound with some arbitrary 'channel.' +// Further, this class assumes that its MultiEffects have the same name and are probably +// initialized on different devices. None of this is enforced at this time. +// +class ChannelCompound : public MultiCompound +{ +protected: + int mChannel; +public: + ChannelCompound( int channel = FF_CHANNEL_MAX ) + : MultiCompound() + { + mChannel = + ( (channel >= 0 && channel < FF_CHANNEL_MAX) + ? channel + : FF_CHANNEL_MAX + ); + } + + ChannelCompound( Set &compound, int channel = FF_CHANNEL_MAX ) + : MultiCompound( compound ) + { + mChannel = + ( (channel >= 0 && channel < FF_CHANNEL_MAX) + ? channel + : FF_CHANNEL_MAX + ); + } + + int GetChannel() + { + return mChannel; + } + const char *GetName() + { + return mSet.size() + ? (*mSet.begin())->GetName() + : NULL + ; + } + qboolean operator == ( ChannelCompound &channelcompound ) + { + return qboolean + ( mChannel == channelcompound.mChannel + && (*(MultiCompound*)this) == *(MultiCompound*)&channelcompound + ); + } + qboolean operator != ( ChannelCompound &channelcompound ) + { + return qboolean( !( (*this) == channelcompound ) ); + } +}; + +typedef vector THandleTable; + +#endif // FF_CHANNELCOMPOUND_H diff --git a/code/ff/ff_ChannelSet.cpp b/code/ff/ff_ChannelSet.cpp new file mode 100644 index 0000000..91ecea6 --- /dev/null +++ b/code/ff/ff_ChannelSet.cpp @@ -0,0 +1,162 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +extern const char *gChannelName[]; + +////--------------------------- +/// FFChannelSet::ParseChannels +//------------------------------- +// This is the worst hack of a parser ever devised. +// +qboolean FFChannelSet::ParseChannels( const char *channels ) +{ + if ( !channels ) + return qfalse; + + int channel; + const char *pos; + + for + ( pos = channels + ; pos && sscanf( pos, "%d", &channel ) == 1 + ; + ){ + int device; + char *endpos; + endpos = strchr( pos, ';' ); + + if ( channel >= 0 && channel < FF_CHANNEL_MAX ) + { + for + ( pos = strchr( pos, ',' ) + ; pos && ( !endpos || pos < endpos ) && sscanf( pos, " ,%d", &device ) == 1 + ; pos = strchr( pos + 1, ',' ) + ){ + if ( device >= 0 && device < mSet.size() ) + { + for + ( ChannelIterator itChannel( mChannel, channel ) + ; itChannel != mChannel.end() + && (**itChannel).second != device // found duplicate + ; ++itChannel + ); + + // Don't allow duplicates + if ( itChannel == mChannel.end() ) + { + FFChannelSet::Channel::value_type Value( channel, device ); + Value.second = device; + mChannel.insert( Value ); + } + } + } + } + + pos = ( endpos ? endpos + 1 : NULL); + + } + + // FIX ME -- return qfalse if there is a parse error + return qtrue; +} + +////---------------------- +/// FFChannelSet::Register +//-------------------------- +// +// Assumptions: +// * 'compound' is empty of effects and contains the desired channel prior to entry. +// +// Parameters: +// * compound: its channel parameter is an input. its effect set is filled with registered +// - effects. 'compound' should not contain any effects prior to this function call. +// * name: effect name to register in each FFSet on the channel +// * create: qtrue if FFSet should create the effect, qfalse if it should just look it up. +// +qboolean FFChannelSet::Register( ChannelCompound &compound, const char *name, qboolean create ) +{ + for + ( ChannelIterator itChannel( mChannel, compound.GetChannel() ) + ; itChannel != mChannel.end() + ; ++itChannel + ){ + MultiEffect *Effect; + Effect = mSet[ (**itChannel).second ]->Register( name, create ); + if ( Effect ) + compound.GetSet().insert( Effect ); + } + + return qboolean( compound.GetSet().size() != 0 ); +} + +#ifdef FF_CONSOLECOMMAND + +void FFChannelSet::GetDisplayTokens( TNameTable &Tokens ) +{ + FFMultiSet::GetDisplayTokens( Tokens ); + Tokens.push_back( "channels" ); + Tokens.push_back( "devices" ); +} + + +void FFChannelSet::Display( TNameTable &Unprocessed, TNameTable &Processed ) +{ + FFMultiSet::Display( Unprocessed, Processed ); + + for + ( TNameTable::iterator itName = Unprocessed.begin() + ; itName != Unprocessed.end() + ; + ){ + if ( stricmp( "channels", (*itName).c_str() ) == 0 ) + { + Com_Printf( "[available channels]\n" ); + + for + ( int i = 0 + ; i < FF_CHANNEL_MAX + ; i++ + ){ + Com_Printf( "%d) %s devices:", i, gChannelName[ i ] ); + for + ( ChannelIterator itChannel( mChannel, i ) + ; itChannel != mChannel.end() + ; ++itChannel + ){ + Com_Printf( " %d", (**itChannel).second ); + } + Com_Printf( "\n" ); + } + + Processed.push_back( *itName ); + itName = Unprocessed.erase( itName ); + } + else if ( stricmp( "devices", (*itName).c_str() ) == 0 ) + { + Com_Printf( "[initialized devices]\n" ); + + for + ( int i = 0 + ; i < mDevices->GetNumDevices() + ; i++ + ){ + char ProductName[ FF_MAX_PATH ]; + ProductName[ 0 ] = 0; + mDevices->GetDevice( i )->GetProductName( ProductName, FF_MAX_PATH - 1 ); + Com_Printf( "%d) %s\n", i, ProductName ); + } + + Processed.push_back( *itName ); + itName = Unprocessed.erase( itName ); + } + else + { + itName++; + } + } +} + +#endif // FF_CONSOLECOMMAND + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/ff_ChannelSet.h b/code/ff/ff_ChannelSet.h new file mode 100644 index 0000000..645f44b --- /dev/null +++ b/code/ff/ff_ChannelSet.h @@ -0,0 +1,59 @@ +#ifndef FF_CHANNELSET_H +#define FF_CHANNELSET_H + +#include "ff_utils.h" +#include "ff_MultiSet.h" +#include "ff_ChannelCompound.h" + +//===[FFChannelSet]===================================================///////////// +// +// An extension to FFMultiSet that operates on a subset of its +// elements specified by a channel. This channel may be inherent +// to a ChannelCompound passed as a parameter. +// +//====================================================================///////////// + +class FFChannelSet : public FFMultiSet +{ +public: + typedef multimap Channel; +protected: + Channel mChannel; + qboolean ParseChannels( const char *channels ); +public: + qboolean Init( FFConfigParser &config, const char *channels ) + { + return qboolean + ( FFMultiSet::Init( config ) // Initialize devices + && ParseChannels( channels ) // Assign channels to devices + ); + } + void clear() + { + mChannel.clear(); + FFMultiSet::clear(); + } + qboolean Register( ChannelCompound &compound, const char *name, qboolean create ); + + // + // Optional + // +#ifdef FF_ACCESSOR +// Channel& GetAll() { return mChannel; } +#endif + +#ifdef FF_CONSOLECOMMAND + void Display( TNameTable &Unprocessed, TNameTable &Processed ); + static void GetDisplayTokens( TNameTable &Tokens ); +#endif +}; + +class ChannelIterator : public multimapIterator +{ +public: + ChannelIterator( FFChannelSet::Channel &map, int channel ) + : multimapIterator( map, channel ) + {} +}; + +#endif // FF_CHANNELSET_H \ No newline at end of file diff --git a/code/ff/ff_ConfigParser.cpp b/code/ff/ff_ConfigParser.cpp new file mode 100644 index 0000000..e8b1cd1 --- /dev/null +++ b/code/ff/ff_ConfigParser.cpp @@ -0,0 +1,483 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +//#include "ff_ConfigParser.h" +//#include "ifc.h" +//#include "ff_utils.h" + +////-------------------- +/// FFConfigParser::Init +//------------------------ +// Reads the force feedback configuration file. Call this once after the device +// is initialized. +// +// Parameters: +// * filename +// +// Returns: +// * qtrue - the effects set directory has been set according to the initialized +// device. (See base/fffx/fffx.cfg) +// * qfalse - no effects set could be determined for this device. +// +qboolean FFConfigParser::Init( const char *filename ) +{ + Clear(); // Always cleanup + + return qboolean( filename && Parse( LoadFile( filename ) ) ); +} + +////--------------------- +/// FFConfigParser::Clear +//------------------------- +// +// +// Parameters: +// +// Returns: +// +void FFConfigParser::Clear( void ) +{ + mMap.clear(); + mDefaultSet.clear(); +} + +////--------------------- +/// FFConfigParser::Parse +//------------------------- +// +// +// Parameters: +// +// Returns: +// +qboolean FFConfigParser::Parse( void *file ) +{ + qboolean result = qboolean( file != NULL ); + + if ( file ) + { + const char *token = 0, *pos = (const char*)file; + for + ( token = COM_ParseExt( &pos, qtrue ) + ; token[ 0 ] + && result // fail if any problem + ; token = COM_ParseExt( &pos, qtrue ) + ){ + if ( !stricmp( token, "ffdefaults" ) ) + { + result &= ParseDefaults( &pos ); + } + else + if ( !stricmp( token, "ffsets" ) ) + { + result &= ParseSets( &pos ); + } + else + { + // unexpected field + result = qfalse; + } + } + + FS_FreeFile( file ); + } + + return result; +} + +////--------------------------------- +/// FFConfigParser::ParseDefaultBlock +//------------------------------------- +// +// +// Parameters: +// +// Returns: +// +qboolean FFConfigParser::ParseDefault( const char **pos, TDeviceType &defaultSet ) +{ + qboolean result = qboolean( pos != NULL ); + + if ( pos ) + { + char *token = COM_ParseExt( pos, qtrue ); + if ( token[ 0 ] == '{' ) + { + for + ( token = COM_ParseExt( pos, qtrue ) + ; token[ 0 ] + && token[ 0 ] != '}' + && result // fail if any problem + ; token = COM_ParseExt( pos, qtrue ) + ){ + int device = 0; + + if ( sscanf( token, "%d", &device ) ) + { + string &str = defaultSet[ device ]; + if ( !str.size() ) + { + str = COM_ParseExt( pos, qfalse ); + result &= qboolean( str.size() > 0 ); + } + else + { + result = qfalse; +#ifdef FF_PRINT + ConsoleParseError + ( "Redefinition of DeviceType index" + , token + ); +#endif + } + } + else + { + result = qfalse; +#ifdef FF_PRINT + ConsoleParseError + ( "DeviceType field should begin with an integer" + , token + ); +#endif + } + } + } + } + + return result; +} + + + +////---------------------------- +/// FFConfigParser::ParseDefault +//-------------------------------- +// +// +// Parameters: +// +// Returns: +// +qboolean FFConfigParser::ParseDefaults( const char **pos ) +{ + qboolean result = qboolean( pos != NULL ); + + if ( pos ) + { + char *token = COM_ParseExt( pos, qtrue ); + if ( token[ 0 ] == '{' ) + { + for + ( token = COM_ParseExt( pos, qtrue ) + ; token[ 0 ] + && token[ 0 ] != '}' + && result // fail if any problem + ; token = COM_ParseExt( pos, qtrue ) + ){ + int techType = 0; + + if ( sscanf( token, "%d", &techType ) ) + { + TDeviceType &deviceType = mDefaultSet[ techType ]; + if ( !deviceType.size() ) + { + result &= ParseDefault( pos, deviceType ); + mDefaultPriority.push_back( techType ); + } + else + { + result = qfalse; +#ifdef FF_PRINT + ConsoleParseError + ( "Redefinition of TechType index" + , token + ); +#endif + } + } + else + { + result = qfalse; +#ifdef FF_PRINT + ConsoleParseError + ( "TechType fields should begin with integers" + , token + ); +#endif + } + } + } + else + { + // expected '{' + result = qfalse; + } + } + + return result; +} + +////-------------------------- +/// FFConfigParser::RightOfSet +//------------------------------ +// +// +// Parameters: +// +// Returns: +// +const char* FFConfigParser::RightOfSet( const char *effectname ) +{ + const char *s = effectname; + + // Check through all set names and test effectname against it + for + ( TMap::iterator itMap = mMap.begin() + ; itMap != mMap.end() && s == effectname + ; itMap++ + ){ + s = RightOf( effectname, (*itMap).first.c_str() ); + } + + return s ? s : effectname; +} + +qboolean FFConfigParser::ParseSetDevices( const char **pos, TDevice &device ) +{ + qboolean result = qboolean( pos != NULL ); + + if ( pos ) + { + char *token = COM_ParseExt( pos, qtrue ); + if ( token[ 0 ] == '{' ) + { + for + ( token = COM_ParseExt( pos, qtrue ) + ; token[ 0 ] + && token[ 0 ] != '}' + && result // fail if any problem + ; token = COM_ParseExt( pos, qtrue ) + ){ + device.insert( token ); + } + + result = qboolean( token[ 0 ] != 0 ); + } + else + { + // expected '{' + result = qfalse; + } + } + + return result; +} + +qboolean FFConfigParser::ParseSetIncludes( const char **pos, TInclude &include ) +{ + qboolean result = qboolean( pos != NULL ); + + if ( pos ) + { + char *token = COM_ParseExt( pos, qtrue ); + if ( token[ 0 ] == '{' ) + { + for + ( token = COM_ParseExt( pos, qtrue ) + ; token[ 0 ] + && token[ 0 ] != '}' + && result // fail if any problem + ; token = COM_ParseExt( pos, qtrue ) + ){ + include.push_back( token ); + } + + result = qboolean( token[ 0 ] != 0 ); + } + else + { + // expected '{' + result = qfalse; + } + } + + return result; +} + +qboolean FFConfigParser::ParseSet( const char **pos, TData &data ) +{ + qboolean result = qboolean( pos != NULL ); + + if ( pos ) + { + const char *oldpos = *pos; // allows set declarations with no attributes to have no "{}" + char *token = COM_ParseExt( pos, qtrue ); + if ( token[ 0 ] == '{' ) + { + for + ( token = COM_ParseExt( pos, qtrue ) + ; token[ 0 ] + && token[ 0 ] != '}' + && result // fail if any problem + ; token = COM_ParseExt( pos, qtrue ) + ){ + if ( !stricmp( token, "includes" ) ) + { + result &= ParseSetIncludes( pos, data.include ); + } + else + if ( !stricmp( token, "devices" ) ) + { + result &= ParseSetDevices( pos, data.device ); + } + else + { + result = qfalse; +#ifdef FF_PRINT + ConsoleParseError + ( "Invalid set parameter. Should be 'includes' or 'devices'" + , token + ); +#endif + } + } + } + else + { + // expected '{' (no longer expected!) + //result = qfalse; (no longer an error!) + *pos = oldpos; + } + } + + return result; +} + +////------------------------- +/// FFConfigParser::ParseSets +//----------------------------- +// +// +// Parameters: +// +// Returns: +// +qboolean FFConfigParser::ParseSets( const char **pos ) +{ + qboolean result = qboolean( pos != NULL ); + string groupName; + + if ( pos ) + { + char *token = COM_ParseExt( pos, qtrue ); + if ( token[ 0 ] == '{' ) + { + for + ( token = COM_ParseExt( pos, qtrue ) + ; token[ 0 ] + && token[ 0 ] != '}' + && result // fail if any problem + ; token = COM_ParseExt( pos, qtrue ) + ){ + TData &data = mMap[ token ]; + result &= ParseSet( pos, data ); + } + } + else + { + // expected '{' + result = qfalse; + } + } + + return result; +} + +////--------------------------- +/// FFConfigParser::GetIncludes +//------------------------------- +// +// +// Parameters: +// +// Returns: +// +FFConfigParser::TInclude& FFConfigParser::GetIncludes( const char *name ) +{ + TMap::iterator itMap = mMap.find( name ); + if ( itMap != mMap.end() ) + return (*itMap).second.include; + + // No includes present + static TInclude emptyInclude; + return emptyInclude; +} + +const char * FFConfigParser::GetFFSet( CImmDevice *Device ) +{ + char devName[ FF_MAX_PATH ]; + const char *ffset = NULL; + + // + // Check explicit name + // + + devName[0] = 0; + Device->GetProductName( devName, FF_MAX_PATH - 1 ); + for + ( TMap::iterator itmap = mMap.begin() + ; itmap != mMap.end() + ; itmap++ + ){ + TDevice::iterator itdev; + + itdev = (*itmap).second.device.find( devName ); + if ( itdev != (*itmap).second.device.end() ) + ffset = (*itmap).first.c_str(); + } + + + // + // Check device defaults + // + + for + ( int i = 0 + ; !ffset && i < mDefaultPriority.size() + ; i++ + ){ + int defaultTechType; + DWORD productType = Device->GetProductType(); + WORD deviceType = HIWORD( productType ); + WORD techType = LOWORD( productType ); + + defaultTechType = mDefaultPriority[ i ]; + + // + // Check for minimum required features + // + + if ( (techType & defaultTechType) >= defaultTechType ) + { + // + // Check that device exists in this technology section + // + + TDeviceType::iterator itDeviceType = mDefaultSet[ defaultTechType ].find( deviceType ); + if ( itDeviceType != mDefaultSet[ defaultTechType ].end() ) + { + ffset = (*itDeviceType).second.c_str(); + } + } + + // + // If not, try next technology section + // + } + + return ffset; +} + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/ff_ConfigParser.h b/code/ff/ff_ConfigParser.h new file mode 100644 index 0000000..3945685 --- /dev/null +++ b/code/ff/ff_ConfigParser.h @@ -0,0 +1,51 @@ +#ifndef __FF_CONFIGPARSER_H +#define __FF_CONFIGPARSER_H + +//#include "ff_public.h" // in precompiled header + +//class CImmDevice; + +class FFConfigParser +{ +public: + typedef vector TInclude; + typedef set TDevice; + typedef map TDeviceType; + typedef map TTechType; + typedef vector TDefaultPriority; + + typedef struct + { + TInclude include; + TDevice device; + } TData; + + typedef map TMap; + +protected: + TTechType mDefaultSet; + TDefaultPriority mDefaultPriority; + TMap mMap; // Contains all effect sets by name + + qboolean Parse( void *file ); + qboolean ParseSets( const char **pos ); + qboolean ParseSet( const char **pos, TData &data ); + qboolean ParseSetIncludes( const char **pos, TInclude &include ); + qboolean ParseSetDevices( const char **pos, TDevice &device ); + qboolean ParseDefaults( const char **pos ); + qboolean ParseDefault( const char **pos, TDeviceType &defaultSet ); + +public: + + qboolean Init( const char *filename/*, CImmDevice *Device = NULL*/ ); + void Clear( void ); + +// const char* RightOfBase( const char *effectname ); +// const char* RightOfGame( const char *effectname ); + const char* RightOfSet( const char *effectname ); + + const char* GetFFSet( CImmDevice *Device ); + TInclude& GetIncludes( const char *name = NULL ); +}; + +#endif // __FF_CONFIGPARSER_H diff --git a/code/ff/ff_HandleTable.cpp b/code/ff/ff_HandleTable.cpp new file mode 100644 index 0000000..6f20b70 --- /dev/null +++ b/code/ff/ff_HandleTable.cpp @@ -0,0 +1,133 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +////---------------------- +/// FFHandleTable::Convert +//-------------------------- +// +// +ffHandle_t FFHandleTable::Convert( ChannelCompound &compound, const char *name, qboolean create ) +{ + ffHandle_t ff = FF_HANDLE_NULL; + + // Reserve a handle for effects that failed to create. + // Rerouting channels to other devices may cause an effect to become lost. + // This assumes that FF_Register is always called with legitimate effect names. + // See CMD_FF_Play on how to handle possibly-bogus user input. + // (It does not call this function) + if ( compound.GetSet().size() ) + ff = Convert( compound ); + else + { + for + ( FFHandleTable::RegFail::iterator itRegFail = mRegFail.begin() + ; itRegFail != mRegFail.end() + && (*itRegFail).second != name + ; itRegFail++ + ); + + ff = + ( itRegFail != mRegFail.end() + ? (*itRegFail).first + : FF_HANDLE_NULL + ); + } + + if ( ff == FF_HANDLE_NULL ) + { + mVector.push_back( compound ); + ff = mVector.size() - 1; + + // Remember effect name for future 'ff_restart' calls. + if ( create && !compound.GetSet().size() ) + mRegFail[ ff ] = name; + } + + return ff; +} + +////---------------------- +/// FFHandleTable::Convert +//-------------------------- +// Looks for 'compound' in the table. +// +// Assumes: +// * 'compound' is non-empty +// +// Returns: +// ffHandle_t +// +ffHandle_t FFHandleTable::Convert( ChannelCompound &compound ) +{ + for + ( int i = 1 + ; i < mVector.size() + && mVector[ i ] != compound + ; i++ + ); + + return + ( i < mVector.size() + ? i + : FF_HANDLE_NULL + ); +} + +////----------------------------- +/// FFHandleTable::GetFailedNames +//--------------------------------- +// +// +qboolean FFHandleTable::GetFailedNames( TNameTable &NameTable ) +{ + for + ( RegFail::iterator itRegFail = mRegFail.begin() + ; itRegFail != mRegFail.end() + ; itRegFail++ + ){ + NameTable[ (*itRegFail).first ] = (*itRegFail).second; + } + + return qboolean( mRegFail.size() != 0 ); +} + +////-------------------------- +/// FFHandleTable::GetChannels +//------------------------------ +// +// +qboolean FFHandleTable::GetChannels( vector &channel ) +{ + //ASSERT( channel.size() >= mVector.size() ); + + for + ( int i = 1 + ; i < mVector.size() + ; i++ + ){ + channel[ i ] = mVector[ i ].GetChannel(); + } + + return qtrue; +} + +const char *FFHandleTable::GetName( ffHandle_t ff ) +{ + const char *result = NULL; + + if ( !mVector[ ff ].IsEmpty() ) + { + result = mVector[ ff ].GetName(); + } + else + { + RegFail::iterator itRegFail = mRegFail.find( ff ); + if ( itRegFail != mRegFail.end() ) + result = (*itRegFail).second.c_str(); + } + + return result; +} + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/ff_HandleTable.h b/code/ff/ff_HandleTable.h new file mode 100644 index 0000000..d07167c --- /dev/null +++ b/code/ff/ff_HandleTable.h @@ -0,0 +1,61 @@ +#ifndef FF_HANDLETABLE_H +#define FF_HANDLETABLE_H + +//===[FFHandleTable]==================================================///////////// +// +// This table houses the master list of initialized effects. Indices +// into this table are handles used by external modules. This way +// effects may be reinitialized on other devices, removed entirely, +// and perused informatively at any time without invalidating pointers. +// +//====================================================================///////////// + +class FFHandleTable +{ +public: + typedef vector Vector; + typedef map RegFail; +protected: + Vector mVector; + RegFail mRegFail; +public: + FFHandleTable() + : mVector() + , mRegFail() + { + //Init(); // guarantees operator [] always works + } + void Init() + { + ChannelCompound handle_null; + mVector.push_back( handle_null ); + } + // Empties handle table except for FF_HANDLE_NULL + void clear() + { + mVector.clear(); + mRegFail.clear(); + //Init(); // guarantees operator [] always works + } + int size() + { + return mVector.size(); + } + ChannelCompound& operator [] ( ffHandle_t ff ) + { + return mVector[ InRange( ff, 0, mVector.size() - 1, FF_HANDLE_NULL ) ]; + } + qboolean GetFailedNames( TNameTable &NameTable ); + qboolean GetChannels( vector &channels ); + ffHandle_t Convert( ChannelCompound &compound, const char *name, qboolean create ); + ffHandle_t Convert( ChannelCompound &compound ); + const char *GetName( ffHandle_t ff ); + + // + // Optional + // +// qboolean Lookup( set &result, MultiEffect *effect, const char *name ); +// qboolean Lookup( set &result, set &effect, const char *name ); +}; + +#endif // FF_HANDLETABLE_H \ No newline at end of file diff --git a/code/ff/ff_MultiCompound.cpp b/code/ff/ff_MultiCompound.cpp new file mode 100644 index 0000000..e2319eb --- /dev/null +++ b/code/ff/ff_MultiCompound.cpp @@ -0,0 +1,201 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +////------------------ +/// MultiCompound::Add +//---------------------- +// Insert a single compound effect if it does not already exist. +// Only fails when parameter is NULL. +// +qboolean MultiCompound::Add( MultiEffect *effect ) +{ + return effect ? ( mSet.insert( effect ), qtrue ) : qfalse; +} + +////------------------ +/// MultiCompound::Add +//---------------------- +// Merge set of compound effects with current set. NULL pointers are excluded. +// Returns false if set contains any NULL pointers. +// +qboolean MultiCompound::Add( Set &effect ) +{ + qboolean result = qtrue; + + for + ( Set::iterator itSet = effect.begin() + ; itSet != effect.end() + ; itSet++ + ){ + result &= Add( *itSet ); + } + + return result; +} + +////-------------------- +/// MultiCompound::Start +//------------------------ +// Analogous to CImmCompoundEffect::Start. Starts all contained compound effects. +// Returns false if any effect returns false. +// +qboolean MultiCompound::Start() +{ + qboolean result = qtrue; + + for + ( Set::iterator itSet = mSet.begin() + ; itSet != mSet.end() + ; itSet++ + ){ + result &= (*itSet)->Start(); + } + + return qboolean + ( result + && mSet.size() != 0 + ); +} + +qboolean MultiCompound::IsPlaying() +{ + for + ( Set::iterator itSet = mSet.begin() + ; itSet != mSet.end() + ; itSet++ + ){ + if ( !(*itSet)->IsPlaying() ) + return qfalse; + } + + return qtrue; +} + +////---------------------------- +/// MultiCompound::EnsurePlaying +//-------------------------------- +// Starts any contained compound effects if they are not currently playing. +// Returns false if any effect returns false or any are playing. +// +qboolean MultiCompound::EnsurePlaying() +{ + qboolean result = qtrue; + + if ( !IsPlaying() ) + { + for + ( Set::iterator itSet = mSet.begin() + ; itSet != mSet.end() + ; itSet++ + ){ + result &= (*itSet)->Start(); + } + } + + return qboolean + ( result + && mSet.size() != 0 + ); +} + +////------------------- +/// MultiCompound::Stop +//----------------------- +// Analogous to CImmCompoundEffect::Stop. Stops all contained compound effects. +// Returns false if any effect returns false. +// +qboolean MultiCompound::Stop() +{ + qboolean result = qtrue; + + for + ( Set::iterator itSet = mSet.begin() + ; itSet != mSet.end() + ; itSet++ + ){ + result &= qboolean( (*itSet)->Stop() ); + } + + return qboolean + ( result + && mSet.size() != 0 + ); +} + +////----------------------------- +/// MultiCompound::ChangeDuration +//--------------------------------- +// Changes duration of all compounds. +// Returns false if any effect returns false. +// +qboolean MultiCompound::ChangeDuration( DWORD Duration ) +{ + qboolean result = qtrue; + + for + ( Set::iterator itSet = mSet.begin() + ; itSet != mSet.end() + ; itSet++ + ){ + result &= (*itSet)->ChangeDuration( Duration ); + } + + return qboolean + ( result + && mSet.size() != 0 + ); +} + +////------------------------- +/// MultiCompound::ChangeGain +//----------------------------- +// Changes gain of all compounds. +// Returns false if any effect returns false. +// +qboolean MultiCompound::ChangeGain( DWORD Gain ) +{ + qboolean result = qtrue; + + for + ( Set::iterator itSet = mSet.begin() + ; itSet != mSet.end() + ; itSet++ + ){ + result &= (*itSet)->ChangeGain( Gain ); + } + + return qboolean + ( result + && mSet.size() != 0 + ); +} + +////-------------------------- +/// MultiCompound::operator == +//------------------------------ +// Returns qtrue if the sets are EXACTLY equal, including order. This is not good +// in general. (Fix me) +// +qboolean MultiCompound::operator == ( MultiCompound &compound ) +{ + Set &other = compound.mSet; + qboolean result = qfalse; + + if ( mSet.size() == other.size() ) + { + for + ( Set::iterator itSet = mSet.begin(), itOther = other.begin() + ; itSet != mSet.end() + //&& itOther != other.end() // assumed since mSet.size() == other.size() + && (*itSet) == (*itOther) + ; itSet++, itOther++ + ); + + result = qboolean( itSet == mSet.end() ); + } + + return result; +} + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/ff_MultiCompound.h b/code/ff/ff_MultiCompound.h new file mode 100644 index 0000000..a5bbc6e --- /dev/null +++ b/code/ff/ff_MultiCompound.h @@ -0,0 +1,54 @@ +#ifndef FF_MULTICOMPOUND_H +#define FF_MULTICOMPOUND_H + +//#include "common_headers.h" +#include "ff_MultiEffect.h" + +////------------- +/// MultiCompound +//----------------- +// MultiCompound is a container for MultiEffect pointers. +// It is not a single, complex effect and should not be treated as such. +// +class MultiCompound +{ +public: + typedef set Set; +protected: + Set mSet; +public: + MultiCompound() + : mSet() + {} + + MultiCompound( Set &compound ) + : mSet() + { + Add( compound ); + } + + Set& GetSet() { return mSet; } + qboolean Add( MultiEffect *Compound ); + qboolean Add( Set &compound ); + + // CImmEffect iterations + qboolean Start(); + qboolean Stop(); + qboolean ChangeDuration( DWORD Duration ); + qboolean ChangeGain( DWORD Gain ); + + // Utilities + qboolean IsEmpty() { return qboolean( mSet.size() == 0 ); } + qboolean operator == ( MultiCompound &compound ); + qboolean operator != ( MultiCompound &compound ) + { + return qboolean( !( (*this) == compound ) ); + } + + // Other iterations + qboolean IsPlaying(); + qboolean EnsurePlaying(); + +}; + +#endif // FF_MULTICOMPOUND_H \ No newline at end of file diff --git a/code/ff/ff_MultiEffect.cpp b/code/ff/ff_MultiEffect.cpp new file mode 100644 index 0000000..70b5cdd --- /dev/null +++ b/code/ff/ff_MultiEffect.cpp @@ -0,0 +1,281 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +////-------------------------- +/// MultiEffect::GetStartDelay +//------------------------------ +// Determines the shortest start delay. +// +qboolean MultiEffect::GetStartDelay( DWORD &StartDelay ) +{ + StartDelay = MAXDWORD; + qboolean result = qtrue; + + int i,max; + for + ( i = 0, max = GetNumberOfContainedEffects() + ; i < max + ; i++ + ){ + DWORD CurrentStartDelay; + CImmEffect* pIE = GetContainedEffect( i ); + if ( pIE + && pIE->GetStartDelay( CurrentStartDelay ) + ){ + StartDelay = Min( StartDelay, CurrentStartDelay ); + } + else + { + result = qfalse; + } + } + + return qboolean + ( result + && max > 0 + ); +} + +////------------------------ +/// MultiEffect::GetDelayEnd +//---------------------------- +// Computes end of earliest start delay. Compare this value with ::GetTickCount() +// to determine if any component waveform started playing on the device. +// +qboolean MultiEffect::GetDelayEnd( DWORD &DelayEnd ) +{ + DelayEnd = MAXDWORD; + qboolean result = qtrue; + + int i,max; + for + ( i = 0, max = GetNumberOfContainedEffects() + ; i < max + ; i++ + ){ + DWORD StartDelay; + CImmEffect* pIE = GetContainedEffect( i ); + if ( pIE + && pIE->GetStartDelay( StartDelay ) + ){ + DelayEnd = Min( DelayEnd, StartDelay + pIE->m_dwLastStarted ); + } + else + { + result = qfalse; + } + } + + return qboolean + ( result + && max > 0 + ); +} + +////--------------------------- +/// MultiEffect::ChangeDuration +//------------------------------- +// Analogous to CImmEffect::ChangeDuration. Changes duration of all component effects. +// Returns false if any effect returns false. Attempts to change duration of all effects +// regardless of individual return values. +// +qboolean MultiEffect::ChangeDuration( DWORD Duration ) +{ + DWORD CurrentDuration; + qboolean result = GetDuration( CurrentDuration ); + + if ( result ) + { + DWORD RelativeDuration = Duration - CurrentDuration; + + int i,max; + for + ( i = 0, max = GetNumberOfContainedEffects() + ; i < max + ; i++ + ){ + IMM_ENVELOPE Envelope = {0}; + CImmEffect* pIE = GetContainedEffect( i ); + result &= qboolean + ( pIE + && pIE->GetDuration( CurrentDuration ) + && pIE->ChangeDuration( CurrentDuration + RelativeDuration ) + && ( !pIE->GetEnvelope( &Envelope ) + || ( Envelope.dwAttackTime = ( CurrentDuration ? (DWORD)((float)Envelope.dwAttackTime * (float)Duration / (float)CurrentDuration) : 0 ) + , Envelope.dwFadeTime = ( CurrentDuration ? (DWORD)((float)Envelope.dwFadeTime * (float)Duration / (float)CurrentDuration) : 0 ) + , pIE->ChangeEnvelope( &Envelope ) + ) + ) + ); + } + + result &= qboolean( max > 0 ); + } + + return result; +} + +////----------------------- +/// MultiEffect::ChangeGain +//--------------------------- +// Analogous to CImmEffect::ChangeGain. Changes gain of all component effects. +// Returns false if any effect returns false. Attempts to change gain of all effects +// regardless of individual return values. +// +qboolean MultiEffect::ChangeGain( DWORD Gain ) +{ + DWORD CurrentGain; + qboolean result = GetGain( CurrentGain ); + + if ( result ) + { + DWORD RelativeGain = Gain - CurrentGain; + + int i,max; + for + ( i = 0, max = GetNumberOfContainedEffects() + ; i < max + ; i++ + ){ + CImmEffect* pIE = GetContainedEffect( i ); + result &= qboolean + ( pIE + && pIE->GetGain( CurrentGain ) + && pIE->ChangeGain( CurrentGain + RelativeGain ) + ); + } + + result &= qboolean( max > 0 ); + } + + return result; +} + +////---------------------- +/// MultiEffect::GetStatus +//-------------------------- +// Analogous to CImmEffect::GetStatus. ORs all status flags from all component effects. +// Returns false if any effect returns false. Attempts to get status of all effects +// regardless of individual return values. +// +qboolean MultiEffect::GetStatus( DWORD &Status ) +{ + Status = 0; + qboolean result = qtrue; + + int i,max; + for + ( i = 0, max = GetNumberOfContainedEffects() + ; i < max + ; i++ + ){ + DWORD CurrentStatus; + CImmEffect* pIE = GetContainedEffect( i ); + if ( pIE + && pIE->GetStatus( &CurrentStatus ) + ){ + Status |= CurrentStatus; + } + else + { + result = qfalse; + } + } + + return qboolean + ( result + && max > 0 + ); +} + +qboolean MultiEffect::ChangeStartDelay( DWORD StartDelay ) +{ + DWORD CurrentStartDelay; + qboolean result = GetStartDelay( CurrentStartDelay ); + + if ( result ) + { + DWORD RelativeStartDelay = StartDelay - CurrentStartDelay; + + int i,max; + for + ( i = 0, max = GetNumberOfContainedEffects() + ; i < max + ; i++ + ){ + CImmEffect* pIE = GetContainedEffect( i ); + result &= qboolean + ( pIE + && pIE->GetStartDelay( CurrentStartDelay ) + && pIE->ChangeStartDelay( CurrentStartDelay + RelativeStartDelay ) + ); + } + + result &= qboolean( max > 0 ); + } + + return result; +} + +qboolean MultiEffect::GetDuration( DWORD &Duration ) +{ + Duration = 0; + qboolean result = qtrue; + + int i,max; + for + ( i = 0, max = GetNumberOfContainedEffects() + ; i < max + ; i++ + ){ + DWORD CurrentDuration; + CImmEffect* pIE = GetContainedEffect( i ); + if ( pIE + && pIE->GetDuration( CurrentDuration ) + ){ + Duration = Max( Duration, CurrentDuration ); + } + else + { + result = qfalse; + } + } + + return qboolean + ( result + && max > 0 + ); +} + +qboolean MultiEffect::GetGain( DWORD &Gain ) +{ + Gain = 0; + qboolean result = qtrue; + + int i,max; + for + ( i = 0, max = GetNumberOfContainedEffects() + ; i < max + ; i++ + ){ + DWORD CurrentGain; + CImmEffect* pIE = GetContainedEffect( i ); + if ( pIE + && pIE->GetGain( CurrentGain ) + ){ + Gain = Max( Gain, CurrentGain ); + } + else + { + result = qfalse; + } + } + + return qboolean + ( result + && max > 0 + ); +} + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/ff_MultiEffect.h b/code/ff/ff_MultiEffect.h new file mode 100644 index 0000000..5f542cc --- /dev/null +++ b/code/ff/ff_MultiEffect.h @@ -0,0 +1,59 @@ +#ifndef MULTIEFFECT_H +#define MULTIEFFECT_H + +//#include "common_headers.h" +//#include "ifc.h" + +////----------- +/// MultiEffect +//--------------- +// CImmCompoundEffect makes no assumption that its contained effects form a more +// complex, single effect. MultiEffect makes this assumption and provides member +// functions available in CImmEffect to operate on this "complex" effect. +// +// Do not instantiate. (Do not call constructor) +// Instead, cast existing CImmCompoundEffect* to MultiEffect* +// Utility functions are specific to the needs of this system. +// +class MultiEffect : public CImmCompoundEffect +{ +public: + // dummy constructor + MultiEffect() : CImmCompoundEffect( NULL, 0, NULL ) {} // Never call (cast instead) + + // CImmEffect extensions + qboolean GetStatus( DWORD &Status ); + qboolean GetStartDelay( DWORD &StartDelay ); + qboolean GetDuration( DWORD &Duration ); + qboolean GetGain( DWORD &Gain ); + qboolean ChangeDuration( DWORD Duration ); + qboolean ChangeGain( DWORD Gain ); + qboolean ChangeStartDelay( DWORD StartDelay ); + + // utility functions + qboolean GetDelayEnd( DWORD &DelayEnd ); + qboolean IsBeyondStartDelay() + { + DWORD DelayEnd; + return qboolean + ( GetDelayEnd( DelayEnd ) + && DelayEnd < ::GetTickCount() // Does not account for counter overflow. + ); + } + qboolean IsPlaying() + { + DWORD Status; + return qboolean( GetStatus( Status ) && (Status & IMM_STATUS_PLAYING) ); + } + + // CImmCompoundEffect overrides + qboolean Start( DWORD dwIterations = IMM_EFFECT_DONT_CHANGE, DWORD dwFlags = 0 ) + { + return qboolean + ( IsBeyondStartDelay() + && CImmCompoundEffect::Start( dwIterations, dwFlags ) + ); + } +}; + +#endif // MULTIEFFECT_H \ No newline at end of file diff --git a/code/ff/ff_MultiSet.cpp b/code/ff/ff_MultiSet.cpp new file mode 100644 index 0000000..711e5e6 --- /dev/null +++ b/code/ff/ff_MultiSet.cpp @@ -0,0 +1,140 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +#include "..\win32\win_local.h" + +////---------------- +/// FFMultiSet::Init +//-------------------- +// Initializes all attached force feedback devices. An empty FFSet is created +// for each device. Each device will have its own copy of whatever .ifr file +// set 'config' specifies. +// +// Always pair with clear() +// +qboolean FFMultiSet::Init( FFSystem::Config &config ) +{ + mConfig = &config; + +#ifdef FF_PRINT + //Com_Printf( "Feedback devices:\n" ); +#endif + + HINSTANCE hInstance = (HINSTANCE)g_wv.hInstance; + HWND hWnd = (HWND)g_wv.hWnd; + + mDevices = new CImmDevices; + if ( mDevices && mDevices->CreateDevices( hInstance, hWnd ) ) + { + for + ( int i = 0 + ; i < mDevices->GetNumDevices() + ; i++ + ){ + FFSet *ffSet = NULL; + ffSet = new FFSet( config, mDevices->GetDevice( i ) ); + if ( ffSet ) + { +#ifdef FF_PRINT + char ProductName[ FF_MAX_PATH ]; + *ProductName = 0; + mDevices->GetDevice( i )->GetProductName( ProductName, FF_MAX_PATH - 1 ); + Com_Printf( "%d) %s\n", i, ProductName ); +#endif + mSet.push_back( ffSet ); + } + } + } + + return qboolean( mSet.size() ); +} + +////------------------------------ +/// FFMultiSet::GetRegisteredNames +//---------------------------------- +// +// +qboolean FFMultiSet::GetRegisteredNames( TNameTable &NameTable ) +{ + for + ( int i = 0 + ; i < mSet.size() + ; i++ + ){ + mSet[ i ]->GetRegisteredNames( NameTable ); + } + + return qtrue; +} + +////------------------- +/// FFMultiSet::StopAll +//----------------------- +// Stops all effects in every FFSet. +// Returns qfalse if any return false. +// +qboolean FFMultiSet::StopAll() +{ + qboolean result = qtrue; + + for + ( int i = 0 + ; i < mSet.size() + ; i++ + ){ + result &= mSet[ i ]->StopAll(); + } + + return result; +} + +////----------------- +/// FFMultiSet::clear +//--------------------- +// Cleans up. +// +void FFMultiSet::clear() +{ + mConfig = NULL; + for + ( int i = 0 + ; i < mSet.size() + ; i++ + ){ + DeletePointer( mSet[ i ] ); + } + mSet.clear(); + DeletePointer( mDevices ); +} + +#ifdef FF_CONSOLECOMMAND + +void FFMultiSet::GetDisplayTokens( TNameTable &Tokens ) +{ + FFSet::GetDisplayTokens( Tokens ); +} + +////------------------- +/// FFMultiSet::Display +//----------------------- +// +// +void FFMultiSet::Display( TNameTable &Unprocessed, TNameTable &Processed ) +{ + for + ( int i = 0 + ; i < mSet.size() + ; i++ + ){ + TNameTable Temp1, Temp2; + Temp1.clear(); + Temp2.clear(); + Temp1.insert( Temp1.begin(), Processed.begin(), Processed.end() ); + mSet[ i ]->Display( Temp1, Temp2 ); + } +} + +#endif // FF_CONSOLECOMMAND + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/ff_MultiSet.h b/code/ff/ff_MultiSet.h new file mode 100644 index 0000000..f242980 --- /dev/null +++ b/code/ff/ff_MultiSet.h @@ -0,0 +1,43 @@ +#ifndef FF_MULTISET_H +#define FF_MULTISET_H + +#include "ff_ffset.h" + +//===[FFMultiSet]=====================================================///////////// +// +// A collection class of FFSet objects. These functions generally +// iterate over the entire set of FFSets, performing the same operation +// on all contained FFSets. +// +//====================================================================///////////// + +class FFMultiSet +{ +public: + typedef vector Set; +protected: + FFConfigParser *mConfig; + Set mSet; + CImmDevices *mDevices; +public: + qboolean Init( FFConfigParser &config ); +// qboolean Lookup( set &effect, const char *name ); + qboolean GetRegisteredNames( TNameTable &NameTable ); + qboolean StopAll(); + void clear(); + + // + // Optional + // +#ifdef FF_ACCESSOR + Set& GetSets() { return mSet; } + CImmDevices* GetDevices() { return mDevices; } +#endif + +#ifdef FF_CONSOLECOMMAND + void Display( TNameTable &Unprocessed, TNameTable &Processed ); + static void GetDisplayTokens( TNameTable &Tokens ); +#endif +}; + +#endif // FF_MULTISET_H \ No newline at end of file diff --git a/code/ff/ff_console.cpp b/code/ff/ff_console.cpp new file mode 100644 index 0000000..2183a13 --- /dev/null +++ b/code/ff/ff_console.cpp @@ -0,0 +1,287 @@ +/* + * Stubs to allow linking with FF_ fnuctions declared. + * Brian Osman + */ + +//JLFRUMBLE includes modified to avoid typename collision field_t MPSKIPPED +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../client/keycodes.h" +//#include "../client/client.h" +#include "../client/fffx.h" +#include "../win32/win_input.h" +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + + + +void FF_StopAll(void) +{ + Com_Printf("FF_StopAll: Please implement.\n"); + // Do nothing +} + +void FF_Stop(ffFX_e effect) +{ + Com_Printf("FF_Stop: Please implement fffx_id = %i\n",effect); + // Do nothing +} + +void FF_EnsurePlaying(ffFX_e effect) +{ + Com_Printf("FF_EnsurePlaying: Please implement fffx_id = %i\n",effect); + // Do nothing +} + +void FF_Play(ffFX_e effect) +{ + int s; // script id + static int const_rumble[2] = {-1, -1}; // script id for constant rumble + int client; + + // super huge switch for rumble effects + switch(effect) + { + case fffx_AircraftCarrierTakeOff: + case fffx_BasketballDribble: + case fffx_CarEngineIdle: + case fffx_ChainsawIdle: + case fffx_ChainsawInAction: + case fffx_DieselEngineIdle: + case fffx_Jump: + s = IN_CreateRumbleScript(IN_GetMainController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 50000, 10000, 200); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Land: + s = IN_CreateRumbleScript(IN_GetMainController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 50000, 10000, 200); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_MachineGun: + s = IN_CreateRumbleScript(IN_GetMainController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 56000, 20000, 230); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Punched: + case fffx_RocketLaunch: + + case fffx_SecretDoor: + case fffx_SwitchClick: // used by saber + s = IN_CreateRumbleScript(IN_GetMainController(), 1, true); + if (s != -1) + { + IN_AddRumbleState(s, 30000, 10000, 120); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_WindGust: + case fffx_WindShear: + case fffx_Pistol: + s = IN_CreateRumbleScript(IN_GetMainController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 50000, 10000, 200); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Shotgun: + case fffx_Laser1: + s = IN_CreateRumbleScript(IN_GetMainController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 25000, 25000, 75); + IN_AddRumbleState(s, 0, 0, 15); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Laser2: + s = IN_CreateRumbleScript(IN_GetMainController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 20000, 20000, 75); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Laser3: + s = IN_CreateRumbleScript(IN_GetMainController(), 1, true); + if (s != -1) + { + IN_AddRumbleState(s, 35000, 35000, 100); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_Laser4: + case fffx_Laser5: + case fffx_Laser6: + case fffx_OutOfAmmo: + case fffx_LightningGun: + case fffx_Missile: + case fffx_GatlingGun: + s = IN_CreateRumbleScript(IN_GetMainController(), 2, true); + if (s != -1) + { + IN_AddRumbleState(s, 39000, 0, 220); + IN_AddRumbleState(s, 0, 0, 10); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_ShortPlasma: + case fffx_PlasmaCannon1: + case fffx_PlasmaCannon2: + case fffx_Cannon: + case fffx_FallingShort: + case fffx_FallingMedium: + s = IN_CreateRumbleScript(IN_GetMainController(), 1, true); + if (s != -1) + { + IN_AddRumbleState(s, 25000,10000, 230); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_FallingFar: + s = IN_CreateRumbleScript(IN_GetMainController(), 1, true); + if (s != -1) + { + IN_AddRumbleState(s, 32000,10000, 230); + IN_ExecuteRumbleScript(s); + } + break; + case fffx_StartConst: + client = 0; + if(const_rumble[client] == -1) + { + const_rumble[client] = IN_CreateRumbleScript(IN_GetMainController(), 4, true); + if (const_rumble[client] != -1) + { + IN_AddEffectFade4(const_rumble[client], 0,0, 50000, 50000, 1000); + //IN_AddRumbleState(const_rumble[client], 40000, 40000, 300); + //IN_AddEffectFade4(const_rumble[client], 50000,50000, 0, 0, 1000); + IN_ExecuteRumbleScript(const_rumble[client]); + } + } + break; + case fffx_StopConst: + client = 0; + if (const_rumble[client] == -1) + return; + IN_KillRumbleScript(const_rumble[client]); + const_rumble[client] = -1; + break; + default: + Com_Printf("No rumble script is defined for fffx_id = %i\n",effect); + break; + } +} + +/********* +FF_XboxShake + +intensity - speed of rumble +duration - length of rumble +*********/ +#define FF_SH_MIN_MOTOR_SPEED 25000 +#define FF_SH_MOTOR_SPEED_MODIFIER (65535 - FF_SH_MIN_MOTOR_SPEED) +void FF_XboxShake(float intensity, int duration) +{ + + int s; + s = IN_CreateRumbleScript(IN_GetMainController(), 1, true); + if (s != -1) + { + int speed; + // figure out the speed + speed = (FF_SH_MIN_MOTOR_SPEED) + (FF_SH_MOTOR_SPEED_MODIFIER * intensity); + + // Add the state and execute + IN_AddRumbleState(s, speed, speed, duration); + IN_ExecuteRumbleScript(s); + } +} + +/********* +FF_XboxDamage + +damage - Amount of damage +xpos - x position for the damage ( -1.0 - 1.0 ) + +The following function various the rumble based upon +the amount of damage and the position of the damage. +*********/ +#define FF_DA_MIN_MOTOR_SPEED 20000 // use this to vary the minimum intensity +#define FF_DA_MOTOR_SPEED_MODIFIER (65535 - FF_DA_MIN_MOTOR_SPEED) +void FF_XboxDamage(int damage, float xpos) +{ + int s; + s = IN_CreateRumbleScript(IN_GetMainController(), 1, true); + if (s != -1) + { + int leftMotorSpeed; + int rightMotorSpeed; + int duration; + float per; + + duration = 175; + + // how much damage? + if(damage > 100) + { + per = 1.0; + } + else + { + per = damage/100; + } + + if(xpos >= -0.2 && xpos <= 0.2) // damge to center + { + leftMotorSpeed = rightMotorSpeed = (FF_DA_MIN_MOTOR_SPEED)+(FF_DA_MOTOR_SPEED_MODIFIER * per); + } + else if(xpos > 0.2) // damage to right + { + rightMotorSpeed = (FF_DA_MIN_MOTOR_SPEED)+(FF_DA_MOTOR_SPEED_MODIFIER * per); + leftMotorSpeed = 0; + } + else // damage to left + { + leftMotorSpeed = (FF_DA_MIN_MOTOR_SPEED)+(FF_DA_MOTOR_SPEED_MODIFIER * per);; + rightMotorSpeed = 0; + } + + // Add the state and execute + IN_AddRumbleState(s, leftMotorSpeed, rightMotorSpeed, duration); + IN_ExecuteRumbleScript(s); + } +} + +void FF_XboxSaberRumble( void ) +{ + + int s; + s = IN_CreateRumbleScript(IN_GetMainController(), 1, true); + if (s != -1) + { + // Add the state and execute + IN_AddRumbleState(s, 55000, 55000, 100); + IN_ExecuteRumbleScript(s); + } +} + diff --git a/code/ff/ff_ffset.cpp b/code/ff/ff_ffset.cpp new file mode 100644 index 0000000..167a1b7 --- /dev/null +++ b/code/ff/ff_ffset.cpp @@ -0,0 +1,356 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +//#include "ff.h" +//#include "ff_ffset.h" +//#include "ff_compound.h" +//#include "ff_system.h" + +extern cvar_t *ff_developer; +#ifdef FF_DELAY +extern cvar_t *ff_delay; +#endif + +extern FFSystem gFFSystem; + +FFSet::FFSet( FFConfigParser &ConfigParser, CImmDevice *Device ) +: mParser( ConfigParser ) +, mDevice( Device ) +, mInclude() +, mIncludePath() +{ + const char *setname = mParser.GetFFSet( mDevice ); + if ( setname ) + { + TProject temp; + mInclude.push_back( temp ); + mIncludePath.push_back( setname ); + InitIncludes( setname ); + } +} + +FFSet::~FFSet() +{ + for + ( TInclude::iterator itInclude = mInclude.begin() + ; itInclude != mInclude.end() + ; itInclude++ + ){ + for + ( TProject::iterator itProject = (*itInclude).begin() + ; itProject != (*itInclude).end() + ; itProject++ + ){ + DeletePointer( (*itProject).second ); + } + } +} + +void FFSet::InitIncludes( const char *setname ) +{ + FFConfigParser::TInclude &include = mParser.GetIncludes( setname ); + + for // each include listed in config file + ( int i = 0 + ; i < include.size() + ; i++ + ){ + for // each include entered into current list + ( unsigned int j = 0 + ; j < mIncludePath.size() + ; j++ + ){ + if ( include[ i ] == mIncludePath[ j ] ) // exists in current list + break; + } + + if ( j == mIncludePath.size() ) // does not exist in current list + { + TProject temp; + mInclude.push_back( temp ); + mIncludePath.push_back( include[ i ] ); + InitIncludes( include[ i ].c_str() ); // recurse + } + } +} + +MultiEffect* FFSet::Register( const char *path, qboolean create ) +{ + char outpath[ FF_MAX_PATH ]; + MultiEffect* effect = NULL; + + if ( FS_VerifyName( "FFSet::Register", path, outpath ) ) + { + for // each included set + ( int i = 0 + ; i < mInclude.size() && !effect + ; i++ + ){ + char setpath[ FF_MAX_PATH ], *afterincludepath; + // need to use explicit path if provided. + sprintf( setpath, "%s/%s", mIncludePath[ i ].c_str(), UncommonDirectory( path, mIncludePath[ i ].c_str() ) ); + afterincludepath = setpath + mIncludePath[ i ].length() + 1; + + for // each possible file/effectname combination + ( int separator = _rcpos( afterincludepath, '/' ) + ; separator >= 0 && !effect + ; separator = _rcpos( afterincludepath, '/', separator ) + ){ + CImmProject *immProject; + char temp[4]; + + temp[0] = 0; + afterincludepath[separator] = 0; + if ( stricmp( afterincludepath + separator - 4, ".ifr" ) ) + { + memcpy( temp, afterincludepath + separator + 1, 4 ); + sprintf( afterincludepath + separator, ".ifr" ); + } + + immProject = NULL; + + TProject::iterator itProject = mInclude[ i ].find( afterincludepath ); + if ( itProject != mInclude[ i ].end() ) + { + immProject = (*itProject).second; + } + else if ( create ) + { + void *buffer = LoadFile( setpath ); + if ( buffer ) + { + immProject = new CImmProject; + + if ( immProject ) + { + if ( !immProject->LoadProjectFromMemory( buffer, mDevice ) ) + { + DeletePointer( immProject ); +#ifdef FF_PRINT + if ( ff_developer->integer ) + Com_Printf( "...Corrupt or invalid file: %s\n", setpath ); + } + else + { + if ( ff_developer->integer ) + Com_Printf( "...Adding file \"%s\"\n", setpath ); +#endif FF_PRINT + } + } + + FS_FreeFile( buffer ); + } + + mInclude[ i ][ afterincludepath ] = immProject; + } + + if ( temp[ 0 ] ) + { + afterincludepath[ separator ] = '/'; + memcpy( afterincludepath + separator + 1, temp, 4 ); + } + + if ( immProject ) + { + effect = (MultiEffect*)immProject->GetCreatedEffect( afterincludepath + separator + 1 ); + if ( !effect && create ) + { + effect = (MultiEffect*)immProject->CreateEffect( afterincludepath + separator + 1, mDevice, IMM_PARAM_NODOWNLOAD ); +#ifdef FF_DELAY + // Delay the effect (better sound synchronization) + if ( effect + && ff_delay + //&& *ff_delay + ){ + effect->ChangeStartDelay( ff_delay->integer ); + } +#endif // FF_DELAY + } + } + } + } + } + + return effect; +} + +qboolean FFSet::StopAll( void ) +{ + for + ( TInclude::iterator itInclude = mInclude.begin() + ; itInclude != mInclude.end() + ; itInclude++ + ){ + for + ( TProject::iterator itProject = (*itInclude).begin() + ; itProject != (*itInclude).end() + ; itProject++ + ){ + if ( (*itProject).second ) + (*itProject).second->Stop(); + } + } + + return qtrue; +} + +void FFSet::GetRegisteredNames( TNameTable &NameTable ) +{ + FFSystem::Handle ffHandle = gFFSystem.GetHandles(); + + for + ( int IncludeIndex = 0 + ; IncludeIndex < mInclude.size() + ; IncludeIndex++ + ){ + for + ( TProject::iterator itProject = mInclude[ IncludeIndex ].begin() + ; itProject != mInclude[ IncludeIndex ].end() + ; itProject++ + ){ + char effectname[ FF_MAX_PATH ]; + int i; + + if ( !(*itProject).second ) + continue; + + i = 0; + + for + ( MultiEffect *Effect = (MultiEffect*)(*itProject).second->GetCreatedEffect( i ) + ; Effect + ; Effect = (MultiEffect*)(*itProject).second->GetCreatedEffect( ++i ) + ){ + sprintf( effectname, "%s/%s/%s", mIncludePath[ IncludeIndex ].c_str(), (*itProject).first.c_str(), Effect->GetName() ); + + for + ( int i = 0 + ; i < ffHandle.size() + ; i++ + ){ + ChannelCompound::Set &compound = ffHandle[ i ].GetSet(); + if + ( NameTable[ i ].length() == 0 + && compound.find( Effect ) != compound.end() + ){ + NameTable[ i ] = effectname; + } + } + } + } + } +} + +//////////////////////////////////////////////// + +#ifdef FF_CONSOLECOMMAND + +void FFSet::GetDisplayTokens( TNameTable &Tokens ) +{ + if ( ff_developer->integer ) + { + Tokens.push_back( "order" ); + Tokens.push_back( "files" ); + } +} + + +void FFSet::Display( TNameTable &Unprocessed, TNameTable &Processed ) +{ + for + ( TNameTable::iterator itName = Unprocessed.begin() + ; itName != Unprocessed.end() + ; + ){ + if ( stricmp( "order", (*itName).c_str() ) == 0 ) + { + if ( ff_developer->integer ) + DisplaySearchOrder(); + //else + // Com_Printf( "\"order\" only available when ff_developer is set\n" ); + + Processed.push_back( *itName ); + itName = Unprocessed.erase( itName ); + } + else + if ( stricmp( "files", (*itName).c_str() ) == 0 ) + { + if ( ff_developer->integer ) + DisplayLoadedFiles(); + //else + // Com_Printf( "\"files\" only available when ff_developer is set\n" ); + + Processed.push_back( *itName ); + itName = Unprocessed.erase( itName ); + } + else + { + itName++; + } + } + +} + +void FFSet::DisplaySearchOrder( void ) +{ + char ProductName[ FF_MAX_PATH ]; + *ProductName = 0; + mDevice->GetProductName( ProductName, FF_MAX_PATH - 1 ); + Com_Printf( "[search order] -\"%s\"\n", ProductName ); + + for + ( int i = 0 + ; i < mInclude.size() + ; i++ + ){ + Com_Printf( "%d) %s\n", i, mIncludePath[ i ].c_str() ); + } +} + +void FFSet::DisplayLoadedFiles( void ) +{ + int total = 0; +#ifdef _DEBUG + int nulltotal = 0; // Variable to indicate how bad my algorithm is +#endif + + char ProductName[ FF_MAX_PATH ]; + *ProductName = 0; + mDevice->GetProductName( ProductName, FF_MAX_PATH - 1 ); + Com_Printf( "[loaded files] -\"%s\"\n", ProductName ); + + for + ( int i = 0 + ; i < mInclude.size() + ; i++ + ){ + for + ( TProject::iterator itProject = mInclude[ i ].begin() + ; itProject != mInclude[ i ].end() + ; itProject++ + ){ + if ( (*itProject).second ) + { + ++total; + Com_Printf( "%s/%s\n", mIncludePath[ i ].c_str(), (*itProject).first.c_str() ); + } +#ifdef _DEBUG + else + { + ++nulltotal; + Com_Printf( "%s/%s [null]\n", mIncludePath[ i ].c_str(), (*itProject).first.c_str() ); + } +#endif + } + } + + Com_Printf( "Total: %d files\n", total ); +#ifdef _DEBUG + Com_Printf( "Total: %d null files\n", nulltotal ); +#endif +} + +#endif // FF_CONSOLECOMMAND + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/ff_ffset.h b/code/ff/ff_ffset.h new file mode 100644 index 0000000..10ac22a --- /dev/null +++ b/code/ff/ff_ffset.h @@ -0,0 +1,64 @@ +#ifndef FFSET_H +#define FFSET_H + +//#include "ff_ConfigParser.h" +//#include "ff_utils.h" +//#include "ff_compound.h" +//class MultiEffect; +//#include "ifc.h" +//class CImmDevice; +//class CImmProject; + +#include "ff_MultiEffect.h" + +class FFSet +{ + // + // Types + // +public: + typedef map TProject; + typedef vector TInclude; + typedef vector TIncludePath; + + // + // Variables + // +protected: + TInclude mInclude; + TIncludePath mIncludePath; + CImmDevice *mDevice; + FFConfigParser &mParser; + + // + // Functions + // +public: + FFSet( FFConfigParser &ConfigParser, CImmDevice *Device ); + ~FFSet(); + MultiEffect* Register( const char *path, qboolean create = qtrue ); + void GetRegisteredNames( TNameTable &NameTable ); + qboolean StopAll( void ); + +protected: + void InitIncludes( const char *setname = NULL ); + + // + // Optional + // +#ifdef FF_ACCESSOR +public: + CImmDevice* GetDevice( void ) { return mDevice; } +#endif + +#ifdef FF_CONSOLECOMMAND +public: + void Display( TNameTable &Unprocessed, TNameTable &Processed ); + void DisplaySearchOrder( void ); + void DisplayLoadedFiles( void ); + static void GetDisplayTokens( TNameTable &Tokens ); +#endif + +}; + +#endif // FFSET_H \ No newline at end of file diff --git a/code/ff/ff_local.h b/code/ff/ff_local.h new file mode 100644 index 0000000..8522628 --- /dev/null +++ b/code/ff/ff_local.h @@ -0,0 +1,24 @@ +#ifndef FF_LOCAL_H +#define FF_LOCAL_H + +#define FF_ACCESSOR +#define FF_API_VERSION 1 + +// Better sound synchronization +// This is default value for cvar ff_delay. User may tweak this. +#define FF_DELAY "40" +// Default: all channels output to primary device +#define FF_CHANNEL "0,0;1,0;2,0;3,0;4,0;5,0" +// Optional system features +#define FF_PRINT +#ifdef FF_PRINT +#define FF_CONSOLECOMMAND +#endif +// (end) Optional system features + +#include "..\game\q_shared.h" // includes ff_public.h +#include "..\qcommon\qcommon.h" + +#define FF_MAX_PATH MAX_QPATH + +#endif // FF_LOCAL_H \ No newline at end of file diff --git a/code/ff/ff_public.h b/code/ff/ff_public.h new file mode 100644 index 0000000..84ed4d0 --- /dev/null +++ b/code/ff/ff_public.h @@ -0,0 +1,43 @@ +#ifndef __FF_PUBLIC_H +#define __FF_PUBLIC_H + +#define FF_HANDLE_NULL 0 +#define FF_CLIENT_LOCAL (-2) +#define FF_CLIENT( client ) (FF_CLIENT_LOCAL - client) + +typedef int ffHandle_t; + +/* +enum FFChannel_e +{ FF_CHANNEL_WEAPON +, FF_CHANNEL_MENU +, FF_CHANNEL_TOUCH +, FF_CHANNEL_DAMAGE +, FF_CHANNEL_VEHICLE +, FF_CHANNEL_MAX +}; +*/ +#define FF_CHANNEL_WEAPON 0 +#define FF_CHANNEL_MENU 1 +#define FF_CHANNEL_TOUCH 2 +#define FF_CHANNEL_DAMAGE 3 +#define FF_CHANNEL_BODY 4 +#define FF_CHANNEL_FORCE 5 +#define FF_CHANNEL_FOOT 6 +#define FF_CHANNEL_MAX 7 + +#ifdef _FF + +/* +inline qboolean operator &= ( qboolean &lvalue, qboolean rvalue ) +{ + lvalue = qboolean( (int)lvalue && (int)rvalue ); + return lvalue; +} +*/ + +#include "../ff/ff.h" // basic system functions +#include "../ff/ff_snd.h" // sound system similarities +#endif // _FF + +#endif // __FF_PUBLIC_H \ No newline at end of file diff --git a/code/ff/ff_snd.cpp b/code/ff/ff_snd.cpp new file mode 100644 index 0000000..3ab96ed --- /dev/null +++ b/code/ff/ff_snd.cpp @@ -0,0 +1,503 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +#include "ff_snd.h" +#include "ff.h" + +extern FFSystem gFFSystem; + +#define FF_GAIN_STEP 500 + +// +// Internal data structures +// +// This whole system should mirror snd_dma.cpp to some degree. +// Right now, not much works. + +/* +template +static T RelativeDistance( T Volume, T Min, T Max ) +{ + if ( Min == Max ) + if ( Volume < Min ) + return 0.f; + else + return 1.f; + + return (Volume - Min) / (Max - Min); +}; + +template +int Round( T value, int mod ) +{ + int intval = (int)value; + int intmod = intval % mod; + int roundup = intmod >= mod / 2; + + return + ( intval + ? roundup + ? intval + mod - intmod + : intval - intmod + : roundup + ? mod + : 0 + ); +} +*/ + +class SndForce +{ +public: + ffHandle_t mHandle; + int mRefs; + qboolean mPlaying; +// int mEntNum; +// vec3_t mOrigin; +// struct SDistanceLimits +// { int min; +// int max; +// } mDistance; + +public: + void zero() + { + mHandle = FF_HANDLE_NULL; + mRefs = 0; + mPlaying = qfalse; +// mEntNum = 0; +// mDistance.min = 0; +// mDistance.max = 0; +// mOrigin[0] = 1.f; +// mOrigin[1] = 0.f; +// mOrigin[2] = 0.f; + } + SndForce() + { + zero(); + } + SndForce( const SndForce &other ) + { + memcpy( this, &other, sizeof(SndForce) ); + } + SndForce( ffHandle_t handle/*, int entNum, const vec3_t origin, float maxDistance, float minDistance*/ ) + : mHandle( handle ) + , mRefs( 0 ) + , mPlaying( qfalse ) +// , mEntNum( entNum ) + { +// mDistance.min = minDistance; +// mDistance.max = maxDistance; +// memcpy( mOrigin, origin, sizeof(mOrigin) ); + } + void AddRef() + { + ++mRefs; + } + void SubRef() + { + --mRefs; + } + qboolean Update( void ) const + { + return qboolean + ( mRefs != 0 + && (ChannelCompound*)mHandle + ); + } +/* int GetGain() + { + float distance = 1.f - GetRelativeDistance(); + + return distance == 0.f + ? 10000 + : Clamp + ( Round + ( distance * 10000 + , FF_GAIN_STEP + ) + , 0 + , 10000 + ) + ; + } + float GetRelativeDistance() + { + return !mRefs + ? 1.f + : IsOrigin() + ? 0.f + : RelativeDistance + ( sqrt + ( mOrigin[0] * mOrigin[0] + + mOrigin[1] * mOrigin[1] + + mOrigin[2] * mOrigin[2] + ) + , mDistance.min + , mDistance.max + ) / mRefs + ; + } + qboolean IsOrigin() + { + return qboolean + ( !mOrigin[0] + && !mOrigin[1] + && !mOrigin[2] + ); + } + void Respatialize( int entNum, const vec3_t origin ) + { + extern vec3_t s_entityPosition[]; + + if ( mEntNum != entNum ) + { + // Assumes all forces follow its entity and is centered on entity + mOrigin[0] = s_entityPosition[ entNum ][0] - origin[0]; + mOrigin[1] = s_entityPosition[ entNum ][1] - origin[1]; + mOrigin[2] = s_entityPosition[ entNum ][2] - origin[2]; + } + else + { + memset( mOrigin, 0, sizeof(mOrigin) ); + } + }*/ + void operator += ( SndForce &other ); +}; + +// Fancy comparator +struct SndForceLess : public less +{ + bool operator() ( const SndForce &x, const SndForce &y ) + { + return bool + (/* x.mEntNum < y.mEntNum + ||*/x.mHandle < y.mHandle +// || x.mOrigin < y.mOrigin // uhhh... compare components + || x.mPlaying < y.mPlaying + ); + } +}; + + +class LoopForce : public SndForce +{ +public: + LoopForce(){} + LoopForce( const LoopForce &other ) + { + memcpy( this, &other, sizeof(LoopForce) ); + } + LoopForce( ffHandle_t handle/*int entNum, , const vec3_t origin, float maxDistance, float minDistance*/ ) + : SndForce( handle/*, entNum, origin, maxDistance, minDistance*/ ) + {} + + void Add( ffHandle_t ff/*, int entNum, const vec3_t origin*/ ); +// void Respatialize( int entNum, const vec3_t origin ); + qboolean Update( void ) + { + qboolean result = SndForce::Update(); + mRefs = 0; + return result; + } +}; + +class SndForceSet +{ +public: + typedef set PendingSet; + typedef map ActiveSet; + ActiveSet mActive; + PendingSet mPending; +public: + void Add( ffHandle_t handle/*, int entNum, const vec3_t origin, float maxDistance, float minDistance*/ ) + { + const_cast (*mPending.insert( SndForce( handle/*, entNum, origin, maxDistance, minDistance*/ ) ).first).AddRef(); + } + qboolean Update( void ); +/* void Respatialize( int entNum, const vec3_t origin ) + { + for + ( PendingSet::iterator itPending = mPending.begin() + ; itPending != mPending.end() + ; itPending++ + ){ + (*itPending).Respatialize( entNum, origin ); + } + }*/ +}; + +class LoopForceSet +{ +public: + typedef set PendingSet; + typedef map ActiveSet; + ActiveSet mActive; + PendingSet mPending; +public: + void Add( ffHandle_t handle/*, int entNum, const vec3_t origin, float maxDistance, float minDistance*/ ) + { + const_cast (*mPending.insert( LoopForce( handle/*, entNum, origin, maxDistance, minDistance*/ ) ).first).AddRef(); + } + qboolean Update( void ); +/* void Respatialize( int entNum, const vec3_t origin ) + { + for + ( PendingSet::iterator itPending = mPending.begin() + ; itPending != mPending.end() + ; itPending++ + ){ + (*itPending).Respatialize( entNum, origin ); + } + }*/ +}; + +class MasterForceSet +{ +protected: + int mEntityNum; +// vec3_t mOrigin; + SndForceSet mSnd; + LoopForceSet mLoop; + +public: + void Add( ffHandle_t handle/*, int entNum, const vec3_t origin, float maxDistance, float minDistance*/ ) + { + mSnd.Add( handle/*, entNum, origin, maxDistance, minDistance*/ ); + } + void AddLoop( ffHandle_t handle/*, int entNum, const vec3_t origin, float maxDistance, float minDistance*/ ) + { + mLoop.Add( handle/*, entNum, origin, maxDistance, minDistance*/ ); + } +/* void Respatialize( int entNum, const vec3_t origin ) + { + memcpy( mOrigin, origin, sizeof(mOrigin) ); + mEntityNum = entNum; + mSnd.Respatialize( entNum, origin ); + mLoop.Respatialize( entNum, origin ); + } +*/ void Update( void ); +}; + +// +// =================================================================================== +// + +static MasterForceSet _MasterForceSet; + +void FF_AddForce( ffHandle_t ff/*, int entNum, const vec3_t origin, float maxDistance, float minDistance*/ ) +{ + _MasterForceSet.Add( ff/*, entNum, origin, maxDistance, minDistance*/ ); +} + +void FF_AddLoopingForce( ffHandle_t ff/*, int entNum, const vec3_t origin, float maxDistance, float minDistance*/ ) +{ + _MasterForceSet.AddLoop( ff/*, entNum, origin, maxDistance, minDistance*/ ); +} +/* +void FF_Respatialize( int entNum, const vec3_t origin ) +{ + _MasterForceSet.Respatialize( entNum, origin ); +} +*/ +void FF_Update( void ) +{ + _MasterForceSet.Update(); +} + +// +// =================================================================================== +// + +void MasterForceSet::Update() +{ + mSnd.Update(); + mLoop.Update(); +} + +////----------------- +/// LoopForce::Update +//--------------------- +// Starts/Stops/Updates looping forces. +// Call once per frame after all looping forces have been added and respatialized. +// +qboolean LoopForceSet::Update() +{ + ActiveSet::iterator itActive; + PendingSet::iterator itPending; + + // Sum effects + ActiveSet active; + for + ( itPending = mPending.begin() + ; itPending != mPending.end() + ; itPending++ + ){ + if ( (const_cast (*itPending)).Update() ) + { + active[ (*itPending).mHandle ] += const_cast (*itPending) ; + } + } + + // Stop and remove unreferenced effects + for + ( itActive = mActive.begin() + ; itActive != mActive.end() + ; //itActive++ + ){ + if ( active.find( (*itActive).first ) != active.end() ) + { + itActive++; + } + else + { + SndForce &sndForce = (*itActive).second; + FF_Stop( sndForce.mHandle ); + itActive = mActive.erase( itActive ); + } + } + + // Decide whether to start or update + for + ( itActive = active.begin() + ; itActive != active.end() + ; itActive++ + ){ + SndForce &sndForce = mActive[ (*itActive).first ]; + sndForce.mHandle = (*itActive).first; + if ( sndForce.mPlaying ) + { + // Just update it + +// if ( (*itActive).second.GetGain() != sndForce.GetGain() ) +// { +// gFFSystem.ChangeGain( sndForce.mHandle, sndForce.GetGain() ); +// } + } + else + { + // Update and start it + +// gFFSystem.ChangeGain( sndForce.mHandle, sndForce.GetGain() ); + FF_Play( sndForce.mHandle ); + sndForce.mPlaying = qtrue; + } + } + + mPending.clear(); + + return qtrue; +} + +////------------------- +/// SndForceSet::Update +//----------------------- +// +// +qboolean SndForceSet::Update() +{ + ActiveSet::iterator itActive; + PendingSet::iterator itPending; +/* + // Remove finished effects from active //and pending sets + for + ( itActive = mActive.begin() + ; itActive != mActive.end() + ; //itActive++ + ){ + if ( gFFSystem.IsPlaying( (*itActive).first ) ) + { + ++itActive; + } + else + { +#if( 0 ) + for + ( itPending = mPending.begin() + ; itPending != mPending.end() + ; itPending++ + ){ + if + ( (*itPending).mHandle == (*itActive).first + && (*itPending).mPlaying + ){ + itPending = mPending.erase( itPending ); + } + } +#endif + itActive = mActive.erase( itActive ); + } + } +*/ + // Sum effects + ActiveSet start; + for + ( itPending = mPending.begin() + ; itPending != mPending.end() + ; itPending++ + ){ + if ( (*itPending).Update() ) + { + start[ (*itPending).mHandle ] += const_cast (*itPending); + } + } + + // Decide whether to start ( no updating one-shots ) + for + ( itActive = start.begin() + ; itActive != start.end() + ; itActive++ + ){ +/* SndForce &sndForce = mActive[ (*itActive).first ]; + sndForce.mHandle = (*itActive).first; + if ( (*itActive).second.GetGain() >= sndForce.GetGain() ) + { + //gFFSystem.ChangeGain( sndForce.mHandle, sndForce.GetGain() ); + FF_Start( sndForce.mHandle ); + sndForce.mPlaying = qtrue; + } +*/ FF_Play( (*itActive).first ); + } + + mPending.clear(); + + return qfalse; +} + +void SndForce::operator += ( SndForce &other ) +{ + /* + float dist = other.GetRelativeDistance(); + + if ( dist < 1.f ) + { + float thisdist = GetRelativeDistance(); + + if ( thisdist < 1.f ) + { + if ( dist == 0.f || thisdist == 0.f ) + { + mOrigin[0] = 0.f; + mOrigin[1] = 0.f; + mOrigin[2] = 0.f; + } + else + { + // This is so shitty + mOrigin[0] *= dist; + mOrigin[1] *= dist; + mOrigin[2] *= dist; + } + } + else + { + memcpy( mOrigin, other.mOrigin, sizeof(mOrigin) ); + } + */ + + mRefs += other.mRefs; +// } +} + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/ff_snd.h b/code/ff/ff_snd.h new file mode 100644 index 0000000..a972572 --- /dev/null +++ b/code/ff/ff_snd.h @@ -0,0 +1,11 @@ +#ifndef FF_SND_H +#define FF_SND_H + +#include "../ff/ff_public.h" + +void FF_AddForce( ffHandle_t ff/*, int entNum, const vec3_t origin, float maxDistance, float minDistance*/ ); +void FF_AddLoopingForce( ffHandle_t ff/*, int entNum, const vec3_t origin, float maxDistance, float minDistance*/ ); +//void FF_Respatialize( int entNum, const vec3_t origin ); +void FF_Update( void ); + +#endif // FF_SND_H \ No newline at end of file diff --git a/code/ff/ff_system.cpp b/code/ff/ff_system.cpp new file mode 100644 index 0000000..09b2a50 --- /dev/null +++ b/code/ff/ff_system.cpp @@ -0,0 +1,151 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +//#include "ff.h" +//#include "ff_ffset.h" +//#include "ff_compound.h" +//#include "ff_system.h" + +extern cvar_t *ff_developer; + +////-------------- +/// FFSystem::Init +//------------------ +// +// +qboolean FFSystem::Init( const char *channels ) +{ + // kludgy restart mechanism + typedef struct + { TNameTable name; + vector channel; + } TRestartInfo; + TRestartInfo restart; + + if ( mInitialized ) + { + restart.name.resize( mHandle.size(), "" ); + restart.channel.resize( mHandle.size(), FF_CHANNEL_MAX ); + + mChannel.GetRegisteredNames( restart.name ); + mHandle.GetFailedNames( restart.name ); + mHandle.GetChannels( restart.channel ); + + Shutdown(); + } + + mHandle.Init(); + + if ( mConfig.Init( "fffx/fffx.cfg" ) // Process config file + && mChannel.Init( mConfig, channels ) ) // Init devices + { + if ( restart.name.size() > 1 ) + { + for + ( int i = 1 + ; i < restart.name.size() + ; i++ + ){ // ignore leading device-specific set name -- (may be switching devices) + Register( mConfig.RightOfSet( restart.name[ i ].c_str() ), restart.channel[ i ] ); + } + } + else + ffShake = Register( "fffx/player/shake", FF_CHANNEL_BODY ); + + mInitialized = qtrue; + } + + return mInitialized; +} + +#ifdef FF_CONSOLECOMMAND + +void FFSystem::GetDisplayTokens( TNameTable &Tokens ) +{ + FFChannelSet::GetDisplayTokens( Tokens ); + if ( ff_developer->integer ) + { + Tokens.push_back( "effects" ); + } +} + +void FFSystem::Display( TNameTable &Unprocessed, TNameTable &Processed ) +{ + for + ( TNameTable::iterator itName = Unprocessed.begin() + ; itName != Unprocessed.end() + ; + ){ + if ( stricmp( "effects", (*itName).c_str() ) == 0 ) + { + if ( ff_developer->integer ) + { + Com_Printf( "[registered effects]\n" ); + + TNameTable EffectNames; + int total = 0; + Channel::Set &ffSet = mChannel.GetSets(); + + for + ( int i = 0 + ; i < ffSet.size() + ; i++ + ){ + char ProductName[ FF_MAX_PATH ]; + *ProductName = 0; + ffSet[ i ]->GetDevice()->GetProductName( ProductName, FF_MAX_PATH - 1 ); + Com_Printf( "%s...\n", ProductName ); + + EffectNames.clear(); + EffectNames.resize( mHandle.size(), "" ); + ffSet[ i ]->GetRegisteredNames( EffectNames ); + + for + ( int j = 1 + ; j < EffectNames.size() + ; j++ + ){ + if ( EffectNames[ j ].length() ) + Com_Printf( "%3d) \"%s\" channel=%d\n", total++, EffectNames[ j ].c_str(), mHandle[ j ].GetChannel() ); + } + } + + EffectNames.clear(); + EffectNames.resize( mHandle.size(), "" ); + + if ( mHandle.GetFailedNames( EffectNames ) ) + { + Com_Printf( "Failed Registrants...\n" ); + for + ( int j = 1 + ; j < EffectNames.size() + ; j++ + ){ + if ( EffectNames[ j ].length() ) + Com_Printf( "%3d) \"%s\" channel=%d\n", total++, EffectNames[ j ].c_str(), mHandle[ j ].GetChannel() ); + } + } + } + //else + //{ + // Com_Printf( "\"effects\" only available when ff_developer is set\n" ); + //} + + Processed.push_back( *itName ); + itName = Unprocessed.erase( itName ); + } + else + { + itName++; + } + } + + mChannel.Display( Unprocessed, Processed ); +} + +#endif // FF_CONSOLECOMMAND + +#endif // _IMMERSION + + diff --git a/code/ff/ff_system.h b/code/ff/ff_system.h new file mode 100644 index 0000000..9e3c5c9 --- /dev/null +++ b/code/ff/ff_system.h @@ -0,0 +1,117 @@ +#ifndef FF_SYSTEM_H +#define FF_SYSTEM_H + +//#include "ff_utils.h" +//#include "ff_compound.h" +//#include "ff_ffset.h" +//#include "ff_configparser.h" + +#include "ff_ConfigParser.h" +#include "ff_ChannelSet.h" +#include "ff_HandleTable.h" + +//===[FFSystem]=======================================================///////////// +// +// The main system for a single user with multiple channels for +// multiple simultaneous devices. All this is factored and 'classy' +// with the intent to make it more readable and easy to track bugs. +// +// That's the intent, at least. +// +//====================================================================///////////// + +class FFSystem +{ +public: + typedef FFConfigParser Config; + typedef FFChannelSet Channel; + typedef FFHandleTable Handle; +protected: + Config mConfig; + Channel mChannel; + Handle mHandle; + qboolean mInitialized; + ffHandle_t ffShake; +public: + qboolean Init( const char *channels ); + void Shutdown() + { + mInitialized = qfalse; + mHandle.clear(); + mChannel.clear(); + mConfig.Clear(); + } + ffHandle_t Register( const char *name, int channel, qboolean notfound = qtrue, qboolean create = qtrue ) + { + ffHandle_t result = FF_HANDLE_NULL; + if ( name && name[ 0 ] ) + { + ChannelCompound compound( channel ); + mChannel.Register( compound, name, create ); + result = mHandle.Convert( compound, name, notfound ); + } + return result; + } + qboolean StopAll() + { + return mChannel.StopAll(); + } + const char* GetName( ffHandle_t ff ) + { + return mHandle.GetName( ff ); + } + qboolean Stop( ffHandle_t ff ) + { + return mHandle[ ff ].Stop(); + } + qboolean Play( ffHandle_t ff ) + { + return mHandle[ ff ].Start(); + } + qboolean EnsurePlaying( ffHandle_t ff ) + { + return mHandle[ ff ].EnsurePlaying(); + } + qboolean Shake( int intensity, int duration, qboolean ensure = qtrue ) + { + ChannelCompound &Compound = mHandle[ ffShake ]; + Compound.ChangeDuration( duration ); + Compound.ChangeGain( intensity ); + return ensure + ? EnsurePlaying( ffShake ) + : Compound.Start() + ; + } + qboolean IsInitialized() { return mInitialized; } + qboolean IsPlaying( ffHandle_t ff ) + { + return mHandle[ ff ].IsPlaying(); + } + qboolean ChangeGain( ffHandle_t ff, DWORD gain ) + { + return mHandle[ ff ].ChangeGain( gain ); + } + + // + // Optional + // +// qboolean Lookup( set &result, const char *name ) +// { +// set effect; +// return +// mChannel.Lookup( effect, name ) +// && mHandle.Lookup( result, effect, name ); +// } + +#ifdef FF_ACCESSOR +// Channel& GetChannels() { return mChannel; } // for CMD_FF_Info + Handle& GetHandles() { return mHandle; } // for CMD_FF_Info +#endif + +#ifdef FF_CONSOLECOMMAND + void Display( TNameTable &Unprocessed, TNameTable &Processed ); + void GetDisplayTokens( TNameTable &Tokens ); +#endif +}; + +#endif // FF_SYSTEM_H \ No newline at end of file diff --git a/code/ff/ff_utils.cpp b/code/ff/ff_utils.cpp new file mode 100644 index 0000000..f9ecdba --- /dev/null +++ b/code/ff/ff_utils.cpp @@ -0,0 +1,105 @@ +#include "common_headers.h" + +#ifdef _IMMERSION + +//#include "ff_utils.h" + +extern cvar_t *ff_developer; + +// +// Didn't know about strrchr. This is slightly different anyway. +// +int _rcpos( const char* string, char c, int pos ) +{ + int length = strlen( string ); + length = ( pos >= 0 && pos < length ? pos : length ); + for ( int i = length - 1; i >= 0; i-- ) + if ( string[i] == c ) + return i; + return -1; +} + +void* LoadFile( const char *filename ) +{ + void *buffer; + + int length = FS_ReadFile( filename, &buffer ); + + return length != -1 ? buffer : NULL; +} + +const char *UncommonDirectory( const char *target, const char *comp ) +{ + const char *pos = target; + + for + ( int i = 0 + ; target[ i ] && comp[ i ] && target[ i ] == comp[ i ] + ; i++ + ){ + if ( target[ i ] == '/' ) + pos = target + i + 1; + } + + if ( !comp[ i ] && target[ i ] == '/' ) + pos = target + i + 1; + else + if ( !target[ i ] && comp[ i ] == '/' ) + pos = target + i; + + return pos; +} + +////------- +/// RightOf +//----------- +// +// +// Parameters: +// +// Returns: +// +const char* RightOf( const char *str, const char *str2 ) +{ + if ( !str || !str2 ) + return NULL; + + const char *s = str; + int len1 = strlen( str ); + int len2 = strlen( str2 ); + + if ( (len2) + && (len1 >= len2) + ){ + s = strstr( str, str2 ); + if ( s ) + { + if ( ((s == str) && (*(s + len2) == '/')) + || ((*(s - 1) == '/') && (*(s + len2) == '/')) + ){ + s += len2 + 1; + } + } + } + + return s ? s : str; +} + +#ifdef FF_PRINT + +void ConsoleParseError( const char *message, const char *line, int pos /*=0*/) +{ + if ( ff_developer && ff_developer->integer ) + { + Com_Printf( "Parse error: %s\n%s\n%*c\n", message, line, pos + 1, '^' ); + } +} + +qboolean FS_VerifyName( const char *src, const char *name, char *out, int maxlen ) +{ + return qtrue; +} + +#endif // FF_PRINT + +#endif // _IMMERSION \ No newline at end of file diff --git a/code/ff/ff_utils.h b/code/ff/ff_utils.h new file mode 100644 index 0000000..65b0951 --- /dev/null +++ b/code/ff/ff_utils.h @@ -0,0 +1,154 @@ +#ifndef FF_UTILS_H +#define FF_UTILS_H + +//#include "ff_public.h" + +template +inline Type Clamp( Type arg, Type min, Type max ) +{; + if ( arg <= min ) + return min; + else + if ( arg > max ) + return max; + return arg; +} + +template +inline Type Max( Type arg, Type arg2 ) +{ + if ( arg < arg2 ) + return arg2; + return arg; +} + +template +inline Type Min( Type arg, Type arg2 ) +{ + if ( arg > arg2 ) + return arg2; + return arg; +} + +template +inline Type InRange( Type arg, Type min, Type max, Type invalid ) +{ + if ( arg < min || arg > max ) + return invalid; + return arg; +} + +typedef vector TNameTable; + +int _rcpos( const char* string, char c, int pos = -1 ); +void* LoadFile( const char *filename ); +const char *UncommonDirectory( const char *target, const char *comp ); +const char* RightOf( const char *str, const char *str2 ); + +template< class Type > +void DeletePointer( Type &Pointer, const char *String = 0 ) +{ + if ( Pointer ) + { +#ifdef FF_PRINT + if ( String ) + Com_Printf( "%s\n", String ); +#endif + delete Pointer; + Pointer = NULL; + } +} + +#ifdef FF_PRINT +void ConsoleParseError( const char *message, const char *line, int pos = 0 ); +#endif + +qboolean FS_VerifyName( const char *src, const char *name, char *out, int maxlen = FF_MAX_PATH ); + +//===[multimapIterator]================================================///////////// +// +// Convenience class for iterating through a multimap. It's not actually +// all that convenient :( It's slightly more intuitive than the +// actual multimap iteration logic. +// +//====================================================================///////////// + +template< class T > +class multimapIterator +{ +protected: + T::iterator mIt; + T &mMap; + T::key_type mKey; +public: + multimapIterator( T &map, T::key_type key ) + : mMap( map ) + , mKey( key ) + { + mIt = mMap.find( mKey ); + } + multimapIterator& operator ++ () + { + if ( mIt != mMap.end() ) + { + mIt++; + if ( (*mIt).first != mKey ) + mIt = mMap.end(); + } + return *this; + } + qboolean operator != ( T::iterator it ) + { + return qboolean( mIt != it ); + } + qboolean operator == ( T::iterator it ) + { + return qboolean( mIt == it ); + } + T::iterator operator * () + { + return mIt; // must dereference twice to access first and second + } + T::iterator operator = ( T::iterator it ) + { + mIt = it; + return mIt; + } + operator qboolean () + { + return qboolean( mIt != mMap.end() ); + } +}; + +/* +template< class T > +class multimapIteratorIterator +{ +protected: + multimapIterator mIt; + +public: + multimapIteratorIterator( T &map ) + : mIt( map, map.begin() ) + { + } + multimapIteratorIterator& operator ++ () + { + for + ( T::iterator last = *mIt + ; mIt + ; last = mIt, ++mIt + ); + + mIt = ++last; + + return *this; + } + multimapIterator& operator * () + { + return mIt; + } +}; +*/ + +#endif // FF_UTILS_H \ No newline at end of file diff --git a/code/game/AI_Animal.cpp b/code/game/AI_Animal.cpp new file mode 100644 index 0000000..435dc6c --- /dev/null +++ b/code/game/AI_Animal.cpp @@ -0,0 +1,390 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" + +#include "..\Ratl\vector_vs.h" + +#define MAX_PACKS 10 + +#define LEAVE_PACK_DISTANCE 1000 +#define JOIN_PACK_DISTANCE 800 +#define WANDER_RANGE 1000 +#define FRIGHTEN_DISTANCE 300 + +extern qboolean G_PlayerSpawned( void ); + +ratl::vector_vs mPacks; + + +//////////////////////////////////////////////////////////////////////////////////////// +// Update The Packs, Delete Dead Leaders, Join / Split Packs, Find MY Leader +//////////////////////////////////////////////////////////////////////////////////////// +gentity_t* NPC_AnimalUpdateLeader(void) +{ + // Find The Closest Pack Leader, Not Counting Myself + //--------------------------------------------------- + gentity_t* closestLeader = 0; + float closestDist = 0; + int myLeaderNum = 0; + + for (int i=0; ihealth<=0) + { + if (mPacks[i]==NPC->client->leader) + { + NPC->client->leader = 0; + } + + mPacks.erase_swap(i); + + if (i>=mPacks.size()) + { + closestLeader = 0; + break; + } + } + + // Don't Count Self + //------------------ + if (mPacks[i]==NPC) + { + myLeaderNum = i; + continue; + } + + float Dist = Distance(mPacks[i]->currentOrigin, NPC->currentOrigin); + if (!closestLeader || Distclient->leader==NPC) + { + mPacks.erase_swap(myLeaderNum); // Erase Myself From The Leader List + } + + // Join The Pack! + //---------------- + NPC->client->leader = closestLeader; + } + + + // Do I Have A Leader? + //--------------------- + if (NPC->client->leader) + { + // AM I A Leader? + //---------------- + if (NPC->client->leader!=NPC) + { + // If Our Leader Is Dead, Clear Him Out + + if ( NPC->client->leader->health<=0 || NPC->client->leader->inuse == 0) + { + NPC->client->leader = 0; + } + + // If My Leader Isn't His Own Leader, Then, Use His Leader + //--------------------------------------------------------- + else if (NPC->client->leader->client->leader!=NPC->client->leader) + { + // Eh. Can this get more confusing? + NPC->client->leader = NPC->client->leader->client->leader; + } + + // If Our Leader Is Too Far Away, Clear Him Out + //------------------------------------------------------ + else if ( Distance(NPC->client->leader->currentOrigin, NPC->currentOrigin)>LEAVE_PACK_DISTANCE) + { + NPC->client->leader = 0; + } + } + + } + + // If We Couldn't Find A Leader, Then Become One + //----------------------------------------------- + else if (!mPacks.full()) + { + NPC->client->leader = NPC; + mPacks.push_back(NPC); + } + return NPC->client->leader; +} + + + + +/* +------------------------- +NPC_BSAnimal_Default +------------------------- +*/ +void NPC_BSAnimal_Default( void ) +{ + if (!NPC || !NPC->client) + { + return; + } + + // Update Some Positions + //----------------------- + CVec3 CurrentLocation(NPC->currentOrigin); + + + // Update The Leader + //------------------- + gentity_t* leader = NPC_AnimalUpdateLeader(); + + + // Select Closest Threat Location + //-------------------------------- + CVec3 ThreatLocation(0,0,0); + qboolean PlayerSpawned = G_PlayerSpawned(); + if ( PlayerSpawned ) + {//player is actually in the level now + ThreatLocation = player->currentOrigin; + } + int alertEvent = NPC_CheckAlertEvents(qtrue, qtrue, -1, qfalse, AEL_MINOR, qfalse); + if ( alertEvent >= 0 ) + { + alertEvent_t *event = &level.alertEvents[alertEvent]; + if (event->owner!=NPC && Distance(event->position, CurrentLocation.v)radius) + { + ThreatLocation = event->position; + } + } + + + +// float DistToThreat = CurrentLocation.Dist(ThreatLocation); +// float DistFromHome = CurrentLocation.Dist(mHome); + + + + bool EvadeThreat = (level.timeinvestigateSoundDebounceTime); + bool CharmedDocile = (level.timeconfusionTime); + bool CharmedApproach = (level.timecharmedTime); + + + + // If Not Already Evading, Test To See If We Should "Know" About The Threat + //-------------------------------------------------------------------------- +/* if (false && !EvadeThreat && PlayerSpawned && (DistToThreatcurrentAngles); + LookAim.AngToVec(); + CVec3 MyPos(CurrentLocation); + MyPos -= ThreatLocation; + MyPos.SafeNorm(); + + float DirectionSimilarity = MyPos.Dot(LookAim); + + if (fabsf(DirectionSimilarity)<0.8f) + { + EvadeThreat = true; + NPCInfo->investigateSoundDebounceTime = level.time + Q_irand(0, 1000); + VectorCopy(ThreatLocation.v, NPCInfo->investigateGoal); + } + }*/ + + + + + + STEER::Activate(NPC); + { + // Charmed Approach - Walk TOWARD The Threat Location + //---------------------------------------------------- + if (CharmedApproach) + { + NAV::GoTo(NPC, NPCInfo->investigateGoal); + } + + // Charmed Docile - Stay Put + //--------------------------- + else if (CharmedDocile) + { + NAV::ClearPath(NPC); + STEER::Stop(NPC); + } + + // Run Away From This Threat + //--------------------------- + else if (EvadeThreat) + { + NAV::ClearPath(NPC); + STEER::Flee(NPC, NPCInfo->investigateGoal); + } + + // Normal Behavior + //----------------- + else + { + // Follow Our Pack Leader! + //------------------------- + if (leader && leader!=NPC) + { + float followDist = 100.0f; + float curDist = Distance(NPC->currentOrigin, leader->followPos); + + + // Update The Leader's Follow Position + //------------------------------------- + STEER::FollowLeader(NPC, leader, followDist); + + bool inSeekRange = (curDistfollowPosWaypoint)); + bool leaderStop = ((level.time - leader->lastMoveTime)>500); + + // If Close Enough, Dump Any Existing Path + //----------------------------------------- + if (inSeekRange || onNbrPoints) + { + NAV::ClearPath(NPC); + + // If The Leader Isn't Moving, Stop + //---------------------------------- + if (leaderStop) + { + STEER::Stop(NPC); + } + + // Otherwise, Try To Get To The Follow Position + //---------------------------------------------- + else + { + STEER::Seek(NPC, leader->followPos, fabsf(followDist)/2.0f/*slowing distance*/, 1.0f/*wight*/, leader->resultspeed); + } + } + + // Otherwise, Get A Path To The Follow Position + //---------------------------------------------- + else + { + NAV::GoTo(NPC, leader->followPosWaypoint); + } + STEER::Separation(NPC, 4.0f); + STEER::AvoidCollisions(NPC, leader); + } + + // Leader AI - Basically Wander + //------------------------------ + else + { + // Are We Doing A Path? + //---------------------- + bool HasPath = NAV::HasPath(NPC); + if (HasPath) + { + HasPath = NAV::UpdatePath(NPC); + if (HasPath) + { + STEER::Path(NPC); // Follow The Path + STEER::AvoidCollisions(NPC); + } + } + + if (!HasPath) + { + // If Debounce Time Has Expired, Choose A New Sub State + //------------------------------------------------------ + if (NPCInfo->investigateDebounceTimeaiFlags &= ~NPCAI_OFF_PATH; + NPCInfo->aiFlags &= ~NPCAI_WALKING; + + + // Pick Another Spot + //------------------- + int NEXTSUBSTATE = Q_irand(0, 10); + + bool RandomPathNode = (NEXTSUBSTATE<8); //(NEXTSUBSTATE<9); + bool PathlessWander = (NEXTSUBSTATE<9); //false; + + + + // Random Path Node + //------------------ + if (RandomPathNode) + { + // Sometimes, Walk + //----------------- + if (Q_irand(0, 1)==0) + { + NPCInfo->aiFlags |= NPCAI_WALKING; + } + + NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); + NAV::FindPath(NPC, NAV::ChooseRandomNeighbor(NAV::GetNearestNode(NPC)));//, mHome.v, WANDER_RANGE)); + } + + // Pathless Wandering + //-------------------- + else if (PathlessWander) + { + // Sometimes, Walk + //----------------- + if (Q_irand(0, 1)==0) + { + NPCInfo->aiFlags |= NPCAI_WALKING; + } + + NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); + NPCInfo->aiFlags |= NPCAI_OFF_PATH; + } + + // Just Stand Here + //----------------- + else + { + NPCInfo->investigateDebounceTime = level.time + Q_irand(2000, 6000); + //NPC_SetAnim(NPC, SETANIM_BOTH, ((Q_irand(0, 1)==0)?(BOTH_GUARD_LOOKAROUND1):(BOTH_GUARD_IDLE1)), SETANIM_FLAG_NORMAL); + } + } + + // Ok, So We Don't Have A Path, And Debounce Time Is Still Active, So We Are Either Wandering Or Looking Around + //-------------------------------------------------------------------------------------------------------------- + else + { + // if (DistFromHome>(WANDER_RANGE)) + // { + // STEER::Seek(NPC, mHome); + // } + // else + { + if (NPCInfo->aiFlags & NPCAI_OFF_PATH) + { + STEER::Wander(NPC); + STEER::AvoidCollisions(NPC); + } + else + { + STEER::Stop(NPC); + } + } + } + } + } + } + } + STEER::DeActivate(NPC, &ucmd); + + NPC_UpdateAngles( qtrue, qtrue ); +} + diff --git a/code/game/AI_AssassinDroid.cpp b/code/game/AI_AssassinDroid.cpp new file mode 100644 index 0000000..73da27c --- /dev/null +++ b/code/game/AI_AssassinDroid.cpp @@ -0,0 +1,197 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + +//custom anims: + //both_attack1 - running attack + //both_attack2 - crouched attack + //both_attack3 - standing attack + //both_stand1idle1 - idle + //both_crouch2stand1 - uncrouch + //both_death4 - running death + +#define ASSASSIN_SHIELD_SIZE 75 +#define TURN_ON 0x00000000 +#define TURN_OFF 0x00000100 + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool BubbleShield_IsOn() +{ + return (NPC->flags&FL_SHIELDED); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void BubbleShield_TurnOn() +{ + if (!BubbleShield_IsOn()) + { + NPC->flags |= FL_SHIELDED; + NPC->client->ps.powerups[PW_GALAK_SHIELD] = Q3_INFINITE; + gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "force_shield", TURN_ON ); + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void BubbleShield_TurnOff() +{ + if ( BubbleShield_IsOn()) + { + NPC->flags &= ~FL_SHIELDED; + NPC->client->ps.powerups[PW_GALAK_SHIELD] = 0; + gi.G2API_SetSurfaceOnOff( &NPC->ghoul2[NPC->playerModel], "force_shield", TURN_OFF ); + } +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Push A Particular Ent +//////////////////////////////////////////////////////////////////////////////////////// +void BubbleShield_PushEnt(gentity_t* pushed, vec3_t smackDir) +{ + G_Damage(pushed, NPC, NPC, smackDir, NPC->currentOrigin, (g_spskill->integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_ELECTROCUTE); + G_Throw(pushed, smackDir, 10); + + // Make Em Electric + //------------------ + pushed->s.powerups |= (1 << PW_SHOCKED); + if (pushed->client) + { + pushed->client->ps.powerups[PW_SHOCKED] = level.time + 1000; + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Go Through All The Ents Within The Radius Of The Shield And Push Them +//////////////////////////////////////////////////////////////////////////////////////// +void BubbleShield_PushRadiusEnts() +{ + int numEnts; + gentity_t* radiusEnts[128]; + const float radius = ASSASSIN_SHIELD_SIZE; + vec3_t mins, maxs; + vec3_t smackDir; + float smackDist; + + for (int i = 0; i < 3; i++ ) + { + mins[i] = NPC->currentOrigin[i] - radius; + maxs[i] = NPC->currentOrigin[i] + radius; + } + + numEnts = gi.EntitiesInBox(mins, maxs, radiusEnts, 128); + for (int entIndex=0; entIndexclient) + { + continue; + } + + // Don't Push Away Other Assassin Droids + //--------------------------------------- + if (radiusEnts[entIndex]->client->NPC_class==NPC->client->NPC_class) + { + continue; + } + + // Should Have Already Pushed The Enemy If He Touched Us + //------------------------------------------------------- + if (NPC->enemy && NPCInfo->touchedByPlayer==NPC->enemy && radiusEnts[entIndex]==NPC->enemy) + { + continue; + } + + // Do The Vector Distance Test + //----------------------------- + VectorSubtract(radiusEnts[entIndex]->currentOrigin, NPC->currentOrigin, smackDir); + smackDist = VectorNormalize(smackDir); + if (smackDisthealth<=0) + { + if (BubbleShield_IsOn()) + { + BubbleShield_TurnOff(); + } + return; + } + + + // Recharge Shields + //------------------ + NPC->client->ps.stats[STAT_ARMOR] += 1; + if (NPC->client->ps.stats[STAT_ARMOR]>250) + { + NPC->client->ps.stats[STAT_ARMOR] = 250; + } + + + + + // If We Have Enough Armor And Are Not Shooting Right Now, Kick The Shield On + //---------------------------------------------------------------------------- + if (NPC->client->ps.stats[STAT_ARMOR]>100 && TIMER_Done(NPC, "ShieldsDown")) + { + // Check On Timers To Raise And Lower Shields + //-------------------------------------------- + if ((level.time - NPCInfo->enemyLastSeenTime)<1000 && TIMER_Done(NPC, "ShieldsUp")) + { + TIMER_Set(NPC, "ShieldsDown", 2000); // Drop Shields + TIMER_Set(NPC, "ShieldsUp", Q_irand(4000, 5000)); // Then Bring Them Back Up For At Least 3 sec + } + + BubbleShield_TurnOn(); + if (BubbleShield_IsOn()) + { + // Update Our Shader Value + //------------------------- + NPC->client->renderInfo.customRGBA[0] = + NPC->client->renderInfo.customRGBA[1] = + NPC->client->renderInfo.customRGBA[2] = + NPC->client->renderInfo.customRGBA[3] = (NPC->client->ps.stats[STAT_ARMOR] - 100); + + + // If Touched By An Enemy, ALWAYS Shove Them + //------------------------------------------- + if (NPC->enemy && NPCInfo->touchedByPlayer==NPC->enemy) + { + vec3_t dir; + VectorSubtract(NPC->enemy->currentOrigin, NPC->currentOrigin, dir); + VectorNormalize(dir); + BubbleShield_PushEnt(NPC->enemy, dir); + } + + // Push Anybody Else Near + //------------------------ + BubbleShield_PushRadiusEnts(); + } + } + + + // Shields Gone + //-------------- + else + { + BubbleShield_TurnOff(); + } +} \ No newline at end of file diff --git a/code/game/AI_Atst.cpp b/code/game/AI_Atst.cpp new file mode 100644 index 0000000..38d2246 --- /dev/null +++ b/code/game/AI_Atst.cpp @@ -0,0 +1,315 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" + + +#define MIN_MELEE_RANGE 640 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define TURN_OFF 0x00000100//G2SURFACEFLAG_NODESCENDANTS + +#define LEFT_ARM_HEALTH 40 +#define RIGHT_ARM_HEALTH 40 + +/* +------------------------- +NPC_ATST_Precache +------------------------- +*/ +void NPC_ATST_Precache(void) +{ + G_SoundIndex( "sound/chars/atst/atst_damaged1" ); + G_SoundIndex( "sound/chars/atst/atst_damaged2" ); + + RegisterItem( FindItemForWeapon( WP_ATST_MAIN )); //precache the weapon + RegisterItem( FindItemForWeapon( WP_BOWCASTER )); //precache the weapon + RegisterItem( FindItemForWeapon( WP_ROCKET_LAUNCHER )); //precache the weapon + + G_EffectIndex( "env/med_explode2" ); +// G_EffectIndex( "smaller_chunks" ); + G_EffectIndex( "blaster/smoke_bolton" ); + G_EffectIndex( "explosions/droidexplosion1" ); +} + +//----------------------------------------------------------------- +static void ATST_PlayEffect( gentity_t *self, const int boltID, const char *fx ) +{ + if ( boltID >=0 && fx && fx[0] ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir; + + gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, + boltID, + &boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time), + NULL, self->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir ); + + G_PlayEffect( fx, org, dir ); + } +} + +/* +------------------------- +G_ATSTCheckPain + +Called by NPC's and player in an ATST +------------------------- +*/ + +void G_ATSTCheckPain( gentity_t *self, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + int newBolt; + + if ( rand() & 1 ) + { + G_SoundOnEnt( self, CHAN_LESS_ATTEN, "sound/chars/atst/atst_damaged1" ); + } + else + { + G_SoundOnEnt( self, CHAN_LESS_ATTEN, "sound/chars/atst/atst_damaged2" ); + } + + if ((hitLoc==HL_ARM_LT) && (self->locationDamage[HL_ARM_LT] > LEFT_ARM_HEALTH)) + { + if (self->locationDamage[hitLoc] >= LEFT_ARM_HEALTH) // Blow it up? + { + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash3" ); + if ( newBolt != -1 ) + { +// G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt1, self->s.number); + ATST_PlayEffect( self, self->genericBolt1, "env/med_explode2" ); + G_PlayEffect( G_EffectIndex("blaster/smoke_bolton"), self->playerModel, newBolt, self->s.number, point); + } + + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head_light_blaster_cann", TURN_OFF ); + } + } + else if ((hitLoc==HL_ARM_RT) && (self->locationDamage[HL_ARM_RT] > RIGHT_ARM_HEALTH)) // Blow it up? + { + if (self->locationDamage[hitLoc] >= RIGHT_ARM_HEALTH) + { + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash4" ); + if ( newBolt != -1 ) + { +// G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt2, self->s.number); + ATST_PlayEffect( self, self->genericBolt2, "env/med_explode2" ); + G_PlayEffect( G_EffectIndex("blaster/smoke_bolton"), self->playerModel, newBolt, self->s.number, point); + } + + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head_concussion_charger", TURN_OFF ); + } + } +} +/* +------------------------- +NPC_ATST_Pain +------------------------- +*/ +void NPC_ATST_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + G_ATSTCheckPain( self, other, point, damage, mod, hitLoc ); + NPC_Pain( self, inflictor, other, point, damage, mod ); +} + +/* +------------------------- +ATST_Hunt +-------------------------` +*/ +void ATST_Hunt( qboolean visible, qboolean advance ) +{ + + if ( NPCInfo->goalEntity == NULL ) + {//hunt + NPCInfo->goalEntity = NPC->enemy; + } + + NPCInfo->combatMove = qtrue; + + NPC_MoveToGoal( qtrue ); + +} + +/* +------------------------- +ATST_Ranged +------------------------- +*/ +void ATST_Ranged( qboolean visible, qboolean advance, qboolean altAttack ) +{ + + if ( TIMER_Done( NPC, "atkDelay" ) && visible ) // Attack? + { + TIMER_Set( NPC, "atkDelay", Q_irand( 500, 3000 ) ); + + if (altAttack) + { + ucmd.buttons |= BUTTON_ATTACK|BUTTON_ALT_ATTACK; + } + else + { + ucmd.buttons |= BUTTON_ATTACK; + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ATST_Hunt( visible, advance ); + } +} + +/* +------------------------- +ATST_Attack +------------------------- +*/ +void ATST_Attack( void ) +{ + qboolean altAttack=qfalse; + int blasterTest,chargerTest,weapon; + + if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )// + { + NPC->enemy = NULL; + return; + } + + NPC_FaceEnemy( qtrue ); + + // Rate our distance to the target, and our visibilty + float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + distance_e distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; + qboolean visible = NPC_ClearLOS( NPC->enemy ); + qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ATST_Hunt( visible, advance ); + return; + } + } + + // Decide what type of attack to do + switch ( distRate ) + { + case DIST_MELEE: + NPC_ChangeWeapon( WP_ATST_MAIN ); + break; + + case DIST_LONG: + + NPC_ChangeWeapon( WP_ATST_SIDE ); + + // See if the side weapons are there + blasterTest = gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "head_light_blaster_cann" ); + chargerTest = gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "head_concussion_charger" ); + + // It has both side weapons + if (!(blasterTest & TURN_OFF) && !(chargerTest & TURN_OFF)) + { + weapon = Q_irand( 0, 1); // 0 is blaster, 1 is charger (ALT SIDE) + + if (weapon) // Fire charger + { + altAttack = qtrue; + } + else + { + altAttack = qfalse; + } + + } + else if (!(blasterTest & TURN_OFF)) // Blaster is on + { + altAttack = qfalse; + } + else if (!(chargerTest & TURN_OFF)) // Blaster is on + { + altAttack = qtrue; + } + else + { + NPC_ChangeWeapon( WP_NONE ); + } + break; + } + + NPC_FaceEnemy( qtrue ); + + ATST_Ranged( visible, advance,altAttack ); +} + +/* +------------------------- +ATST_Patrol +------------------------- +*/ +void ATST_Patrol( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + } + } + +} + +/* +------------------------- +ATST_Idle +------------------------- +*/ +void ATST_Idle( void ) +{ + + NPC_BSIdle(); + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_NORMAL ); +} + +/* +------------------------- +NPC_BSDroid_Default +------------------------- +*/ +void NPC_BSATST_Default( void ) +{ + if ( NPC->enemy ) + { + if( (NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) ) + { + NPCInfo->goalEntity = NPC->enemy; + } + ATST_Attack(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + ATST_Patrol(); + } + else + { + ATST_Idle(); + } +} diff --git a/code/game/AI_BobaFett.cpp b/code/game/AI_BobaFett.cpp new file mode 100644 index 0000000..6b62b42 --- /dev/null +++ b/code/game/AI_BobaFett.cpp @@ -0,0 +1,1244 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN SOFTWARE - STAR WARS: JK II +// (c) 2002 Activision +// +// Boba Fett +// --------- +// Ah yes, this file is pretty messy. I've tried to move everything in here, but in fact +// a lot of his AI occurs in the seeker and jedi AI files. Some of these functions +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#include "g_headers.h" +#include "b_local.h" + + +//////////////////////////////////////////////////////////////////////////////////////// +// Forward References Of Functions +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_Precache( void ); +void Boba_DustFallNear(const vec3_t origin, int dustcount); +void Boba_ChangeWeapon(int wp); +qboolean Boba_StopKnockdown(gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown = qfalse); + +// Flight Related Functions (also used by Rocket Trooper) +//-------------------------------------------------------- +qboolean Boba_Flying( gentity_t *self ); +void Boba_FlyStart( gentity_t *self ); +void Boba_FlyStop( gentity_t *self ); + +// Called From NPC_Pain() +//----------------------------- +void Boba_Pain( gentity_t *self, gentity_t *inflictor, int damage, int mod); + + +// Local: Flame Thrower Weapon +//----------------------------- +void Boba_FireFlameThrower( gentity_t *self ); +void Boba_StopFlameThrower( gentity_t *self ); +void Boba_StartFlameThrower( gentity_t *self ); +void Boba_DoFlameThrower( gentity_t *self ); + +// Local: Other Tactics +//---------------------- +void Boba_DoAmbushWait( gentity_t *self); +void Boba_DoSniper( gentity_t *self); + +// Local: Respawning +//------------------- +bool Boba_Respawn(); + +// Called From Within AI_Jedi && AI_Seeker +//----------------------------------------- +void Boba_Fire(); +void Boba_FireDecide(); + +// Local: Called From Tactics() +//---------------------------- +void Boba_TacticsSelect(); +bool Boba_CanSeeEnemy( gentity_t *self ); + + +// Called From NPC_RunBehavior() +//------------------------------- +void Boba_Update(); // Always Called First, Before Any Other Thinking +bool Boba_Tactics(); // If returns true, Jedi and Seeker AI not used +bool Boba_Flee(); // If returns true, Jedi and Seeker AI not used + + + +//////////////////////////////////////////////////////////////////////////////////////// +// External Functions +//////////////////////////////////////////////////////////////////////////////////////// +extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast ); +extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty ); +extern void ForceJump( gentity_t *self, usercmd_t *ucmd ); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); + +//////////////////////////////////////////////////////////////////////////////////////// +// External Data +//////////////////////////////////////////////////////////////////////////////////////// +extern cvar_t* g_bobaDebug; + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Boba Debug Output +//////////////////////////////////////////////////////////////////////////////////////// +#ifndef FINAL_BUILD +#if !defined(CTYPE_H_INC) + #include + #define CTYPE_H_INC +#endif + +#if !defined(STDARG_H_INC) + #include + #define STDARG_H_INC +#endif + +#if !defined(STDIO_H_INC) + #include + #define STDIO_H_INC +#endif +void Boba_Printf(const char * format, ...) +{ + if (g_bobaDebug->integer==0) + { + return; + } + + static char string[2][1024]; // in case this is called by nested functions + static int index = 0; + static char nFormat[300]; + char* buf; + + // Tack On The Standard Format Around The Given Format + //----------------------------------------------------- + sprintf(nFormat, "[BOBA %8d] %s\n", level.time, format); + + + // Resolve Remaining Elipsis Parameters Into Newly Formated String + //----------------------------------------------------------------- + buf = string[index & 1]; + index++; + + va_list argptr; + va_start (argptr, format); + vsprintf (buf, nFormat, argptr); + va_end (argptr); + + // Print It To Debug Output Console + //---------------------------------- + gi.Printf(buf); +} +#else +void Boba_Printf(const char * format, ...) +{ +} +#endif + + +//////////////////////////////////////////////////////////////////////////////////////// +// Defines +//////////////////////////////////////////////////////////////////////////////////////// +#define BOBA_FLAMEDURATION 3000 +#define BOBA_FLAMETHROWRANGE 128 +#define BOBA_FLAMETHROWSIZE 40 +#define BOBA_FLAMETHROWDAMAGEMIN 1//10 +#define BOBA_FLAMETHROWDAMAGEMAX 5//40 +#define BOBA_ROCKETRANGEMIN 300 +#define BOBA_ROCKETRANGEMAX 2000 + + +//////////////////////////////////////////////////////////////////////////////////////// +// Global Data +//////////////////////////////////////////////////////////////////////////////////////// +bool BobaHadDeathScript = false; +bool BobaActive = false; +vec3_t BobaFootStepLoc; +int BobaFootStepCount = 0; + +vec3_t AverageEnemyDirection; +int AverageEnemyDirectionSamples; + + +//////////////////////////////////////////////////////////////////////////////////////// +// Enums +//////////////////////////////////////////////////////////////////////////////////////// +enum EBobaTacticsState +{ + BTS_NONE, + + // Attack + //-------- + BTS_RIFLE, // Uses Jedi / Seeker Movement + BTS_MISSILE, // Uses Jedi / Seeker Movement + BTS_SNIPER, // Uses Special Movement Internal To This File + BTS_FLAMETHROW, // Locked In Place + + // Waiting + //--------- + BTS_AMBUSHWAIT, // Goto CP & Wait + + BTS_MAX +}; + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_Precache( void ) +{ + G_SoundIndex( "sound/chars/boba/bf_blast-off.wav" ); + G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" ); + G_SoundIndex( "sound/chars/boba/bf_land.wav" ); + G_SoundIndex( "sound/weapons/boba/bf_flame.mp3" ); + G_SoundIndex( "sound/player/footsteps/boot1" ); + G_SoundIndex( "sound/player/footsteps/boot2" ); + G_SoundIndex( "sound/player/footsteps/boot3" ); + G_SoundIndex( "sound/player/footsteps/boot4" ); + G_EffectIndex( "boba/jetSP" ); + G_EffectIndex( "boba/fthrw" ); + G_EffectIndex( "volumetric/black_smoke" ); + G_EffectIndex( "chunks/dustFall" ); + + AverageEnemyDirectionSamples = 0; + VectorClear(AverageEnemyDirection); + BobaHadDeathScript = false; + BobaActive = true; + BobaFootStepCount = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_DustFallNear(const vec3_t origin, int dustcount) +{ + if (!BobaActive) + { + return; + } + + trace_t testTrace; + vec3_t testDirection; + vec3_t testStartPos; + vec3_t testEndPos; + + VectorCopy(origin, testStartPos); + for (int i=0; iinuse)?(0):(ENTITYNUM_NONE), MASK_SHOT ); + + if (!testTrace.startsolid && + !testTrace.allsolid && + testTrace.fraction>0.1f && + testTrace.fraction<0.9f) + { + G_PlayEffect( "chunks/dustFall", testTrace.endpos, testTrace.plane.normal ); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// This is just a super silly wrapper around NPC_Change Weapon +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_ChangeWeapon( int wp ) +{ + if ( NPC->s.weapon == wp ) + { + return; + } + NPC_ChangeWeapon( wp ); + G_AddEvent( NPC, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" )); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Choose an "anti-knockdown" response +//////////////////////////////////////////////////////////////////////////////////////// +qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown ) +{ + if ( self->client->NPC_class != CLASS_BOBAFETT ) + { + return qfalse; + } + + if ( self->client->moveType == MT_FLYSWIM ) + {//can't knock me down when I'm flying + return qtrue; + } + + vec3_t pDir, fwd, right, ang = {0, self->currentAngles[YAW], 0}; + float fDot, rDot; + int strafeTime = Q_irand( 1000, 2000 ); + + AngleVectors( ang, fwd, right, NULL ); + VectorNormalize2( pushDir, pDir ); + fDot = DotProduct( pDir, fwd ); + rDot = DotProduct( pDir, right ); + + if ( Q_irand( 0, 2 ) ) + {//flip or roll with it + usercmd_t tempCmd; + if ( fDot >= 0.4f ) + { + tempCmd.forwardmove = 127; + TIMER_Set( self, "moveforward", strafeTime ); + } + else if ( fDot <= -0.4f ) + { + tempCmd.forwardmove = -127; + TIMER_Set( self, "moveback", strafeTime ); + } + else if ( rDot > 0 ) + { + tempCmd.rightmove = 127; + TIMER_Set( self, "strafeRight", strafeTime ); + TIMER_Set( self, "strafeLeft", -1 ); + } + else + { + tempCmd.rightmove = -127; + TIMER_Set( self, "strafeLeft", strafeTime ); + TIMER_Set( self, "strafeRight", -1 ); + } + G_AddEvent( self, EV_JUMP, 0 ); + if ( !Q_irand( 0, 1 ) ) + {//flip + self->client->ps.forceJumpCharge = 280;//FIXME: calc this intelligently? + ForceJump( self, &tempCmd ); + } + else + {//roll + TIMER_Set( self, "duck", strafeTime ); + } + self->painDebounceTime = 0;//so we do something + } + else if ( !Q_irand( 0, 1 ) && forceKnockdown ) + {//resist + WP_ResistForcePush( self, pusher, qtrue ); + } + else + {//fall down + return qfalse; + } + + return qtrue; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Is this entity flying +//////////////////////////////////////////////////////////////////////////////////////// +qboolean Boba_Flying( gentity_t *self ) +{ + assert(self && self->NPC && self->client && self->client->NPC_class==CLASS_BOBAFETT); + return ((qboolean)(self->client->moveType==MT_FLYSWIM)); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool Boba_CanSeeEnemy( gentity_t *self ) +{ + assert(self && self->NPC && self->client && self->client->NPC_class==CLASS_BOBAFETT); + return ((level.time - self->NPC->enemyLastSeenTime)<1000); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_Pain( gentity_t *self, gentity_t *inflictor, int damage, int mod) +{ + if (mod==MOD_SABER && !(NPCInfo->aiFlags&NPCAI_FLAMETHROW)) + { + TIMER_Set( self, "Boba_TacticsSelect", 0); // Hurt By The Saber, Time To Try Something New + } + if (self->NPC->aiFlags&NPCAI_FLAMETHROW) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.torsoAnimTimer = level.time - TIMER_Get(self, "falmeTime"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_FlyStart( gentity_t *self ) +{//switch to seeker AI for a while + if ( TIMER_Done( self, "jetRecharge" ) + && !Boba_Flying( self ) ) + { + self->client->ps.gravity = 0; + self->svFlags |= SVF_CUSTOM_GRAVITY; + self->client->moveType = MT_FLYSWIM; + //start jet effect + self->client->jetPackTime = level.time + Q_irand( 3000, 10000 ); + if ( self->genericBolt1 != -1 ) + { + G_PlayEffect( G_EffectIndex( "boba/jetSP" ), self->playerModel, self->genericBolt1, self->s.number, self->currentOrigin, qtrue, qtrue ); + } + if ( self->genericBolt2 != -1 ) + { + G_PlayEffect( G_EffectIndex( "boba/jetSP" ), self->playerModel, self->genericBolt2, self->s.number, self->currentOrigin, qtrue, qtrue ); + } + + //take-off sound + G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_blast-off.wav" ); + //jet loop sound + self->s.loopSound = G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" ); + if ( self->NPC ) + { + self->count = Q3_INFINITE; // SEEKER shot ammo count + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_FlyStop( gentity_t *self ) +{ + self->client->ps.gravity = g_gravity->value; + self->svFlags &= ~SVF_CUSTOM_GRAVITY; + self->client->moveType = MT_RUNJUMP; + //Stop effect + self->client->jetPackTime = 0; + if ( self->genericBolt1 != -1 ) + { + G_StopEffect( "boba/jetSP", self->playerModel, self->genericBolt1, self->s.number ); + } + if ( self->genericBolt2 != -1 ) + { + G_StopEffect( "boba/jetSP", self->playerModel, self->genericBolt2, self->s.number ); + } + + //stop jet loop sound + G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_land.wav" ); + + self->s.loopSound = 0; + if ( self->NPC ) + { + self->count = 0; // SEEKER shot ammo count + TIMER_Set( self, "jetRecharge", Q_irand( 1000, 5000 ) ); + TIMER_Set( self, "jumpChaseDebounce", Q_irand( 500, 2000 ) ); + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// This func actually does the damage inflicting traces +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_FireFlameThrower( gentity_t *self ) +{ + trace_t tr; + vec3_t start, end, dir; + CVec3 traceMins(self->mins); + CVec3 traceMaxs(self->maxs); + gentity_t* traceEnt = NULL; + int damage = Q_irand( BOBA_FLAMETHROWDAMAGEMIN, BOBA_FLAMETHROWDAMAGEMAX ); + + AngleVectors(self->currentAngles, dir, 0, 0); + dir[2] = 0.0f; + VectorCopy(self->currentOrigin, start); + traceMins *= 0.5f; + traceMaxs *= 0.5f; + start[2] += 40.0f; + + VectorMA( start, 150.0f, dir, end ); + + if (g_bobaDebug->integer) + { + CG_DrawEdge(start, end, EDGE_IMPACT_POSSIBLE); + } + gi.trace( &tr, start, self->mins, self->maxs, end, self->s.number, MASK_SHOT); + + traceEnt = &g_entities[tr.entityNum]; + if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage ) + { + G_Damage( traceEnt, self, self, dir, tr.endpos, damage, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC|DAMAGE_IGNORE_TEAM, MOD_LAVA, HL_NONE ); + if (traceEnt->health>0) + { +// G_Knockdown( traceEnt, self, dir, Q_irand(200, 330), qfalse); + G_Throw(traceEnt, dir, 30); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_StopFlameThrower( gentity_t *self ) +{ + if ((NPCInfo->aiFlags&NPCAI_FLAMETHROW)) + { + self->NPC->aiFlags &= ~NPCAI_FLAMETHROW; + self->client->ps.torsoAnimTimer = 0; + + TIMER_Set( self, "flameTime", 0); + TIMER_Set( self, "nextAttackDelay", 0); + TIMER_Set( self, "Boba_TacticsSelect", 0); + + // G_SoundOnEnt( self, CHAN_WEAPON, "sound/effects/flameoff.mp3" ); + G_StopEffect( G_EffectIndex("boba/fthrw"), self->playerModel, self->genericBolt3, self->s.number); + + Boba_Printf("FlameThrower OFF"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_StartFlameThrower( gentity_t *self ) +{ + if (!(NPCInfo->aiFlags&NPCAI_FLAMETHROW)) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + self->NPC->aiFlags |= NPCAI_FLAMETHROW; + self->client->ps.torsoAnimTimer = BOBA_FLAMEDURATION; + + TIMER_Set( self, "flameTime", BOBA_FLAMEDURATION); + TIMER_Set( self, "nextAttackDelay", BOBA_FLAMEDURATION); + TIMER_Set( self, "nextFlameDelay", BOBA_FLAMEDURATION*2); + TIMER_Set( self, "Boba_TacticsSelect", BOBA_FLAMEDURATION); + + G_SoundOnEnt( self, CHAN_WEAPON, "sound/weapons/boba/bf_flame.mp3" ); + G_PlayEffect( G_EffectIndex("boba/fthrw"), self->playerModel, self->genericBolt3, self->s.number, self->s.origin, 1 ); + + Boba_Printf("FlameThrower ON"); + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_DoFlameThrower( gentity_t *self ) +{ + if (!(NPCInfo->aiFlags&NPCAI_FLAMETHROW) && TIMER_Done(self, "nextAttackDelay")) + { + Boba_StartFlameThrower( self ); + } + + if ( (NPCInfo->aiFlags&NPCAI_FLAMETHROW)) + { + Boba_FireFlameThrower( self ); + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_DoAmbushWait( gentity_t *self) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_DoSniper( gentity_t *self) +{ + if (TIMER_Done(NPC, "PickNewSniperPoint")) + { + TIMER_Set(NPC, "PickNewSniperPoint", Q_irand(15000, 25000)); + int SniperPoint = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_SNIPE|CP_CLEAR|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1); + if (SniperPoint!=-1) + { + NPC_SetCombatPoint(SniperPoint); + NPC_SetMoveGoal( NPC, level.combatPoints[SniperPoint].origin, 20, qtrue, SniperPoint ); + } + } + + if (Distance(NPC->currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin)<50.0f) + { + Boba_FireDecide(); + } + + + bool IsOnAPath = !!NPC_MoveToGoal(qtrue); + + // Resolve Blocked Problems + //-------------------------- + if (NPCInfo->aiFlags&NPCAI_BLOCKED && + NPC->client->moveType!=MT_FLYSWIM && + ((level.time - NPCInfo->blockedDebounceTime)>3000) + ) + { + Boba_Printf("BLOCKED: Attempting Jump"); + if (IsOnAPath) + { + if (!NPC_TryJump(NPCInfo->blockedTargetPosition)) + { + Boba_Printf(" Failed"); + } + } + } + + NPC_FaceEnemy(qtrue); + NPC_UpdateAngles( qtrue, qtrue ); +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Call This function to make Boba actually shoot his current weapon +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_Fire() +{ + WeaponThink(qtrue); + + // If Actually Fired, Decide To Apply Alt Fire And Calc Next Attack Delay + //------------------------------------------------------------------------ + if (ucmd.buttons&BUTTON_ATTACK) + { + switch (NPC->s.weapon) + { + case WP_ROCKET_LAUNCHER: + TIMER_Set( NPC, "nextAttackDelay", Q_irand(1000, 2000)); + + // Occasionally Shoot A Homing Missile + //------------------------------------- + if (!Q_irand(0,3)) + { + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + NPC->client->fireDelay = Q_irand( 1000, 3000 ); + } + break; + + case WP_DISRUPTOR: + TIMER_Set(NPC, "nextAttackDelay", Q_irand(1000, 4000)); + + // Occasionally Alt-Fire + //----------------------- + if (!Q_irand(0,3)) + { + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + NPC->client->fireDelay = Q_irand( 1000, 3000 ); + } + break; + + case WP_BLASTER: + + if (TIMER_Done(NPC, "nextBlasterAltFireDecide")) + { + if (Q_irand(0, (NPC->count*2)+3)>2) + { + TIMER_Set(NPC, "nextBlasterAltFireDecide", Q_irand(3000, 8000)); + if (!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) + { + Boba_Printf("ALT FIRE On"); + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + NPC_ChangeWeapon(WP_BLASTER); // Update Delay Timers + } + } + else + { + TIMER_Set(NPC, "nextBlasterAltFireDecide", Q_irand(2000, 5000)); + if ( (NPCInfo->scriptFlags&SCF_ALT_FIRE)) + { + Boba_Printf("ALT FIRE Off"); + NPCInfo->scriptFlags &=~SCF_ALT_FIRE; + NPC_ChangeWeapon(WP_BLASTER); // Update Delay Timers + } + } + } + + // Occasionally Alt Fire + //----------------------- + if (NPCInfo->scriptFlags&SCF_ALT_FIRE) + { + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + break; + } + } +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Call this function to see if Fett should fire his current weapon +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_FireDecide( void ) +{ + // Any Reason Not To Shoot? + //-------------------------- + if (!NPC || // Only NPCs + !NPC->client || // Only Clients + NPC->client->NPC_class!=CLASS_BOBAFETT || // Only Boba + !NPC->enemy || // Only If There Is An Enemy + NPC->s.weapon==WP_NONE || // Only If Using A Valid Weapon + !TIMER_Done(NPC, "nextAttackDelay") || // Only If Ready To Shoot Again + !Boba_CanSeeEnemy(NPC) // Only If Enemy Recently Seen + ) + { + return; + } + + // Now Check Weapon Specific Parameters To See If We Should Shoot Or Not + //----------------------------------------------------------------------- + switch (NPC->s.weapon) + { + case WP_ROCKET_LAUNCHER: + if (Distance(NPC->currentOrigin, NPC->enemy->currentOrigin)>400.0f) + { + Boba_Fire(); + } + break; + + case WP_DISRUPTOR: + // TODO: Add Conditions Here + Boba_Fire(); + break; + + case WP_BLASTER: + // TODO: Add Conditions Here + Boba_Fire(); + break; + } +} + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Tactics avaliable to Boba Fett: +// -------------------------------- +// BTS_RIFLE, // Uses Jedi / Seeker Movement +// BTS_MISSILE, // Uses Jedi / Seeker Movement +// BTS_SNIPER, // Uses Special Movement Internal To This File +// BTS_FLAMETHROW, // Locked In Place +// BTS_AMBUSHWAIT, // Goto CP & Wait +// +// +// Weapons available to Boba Fett: +// -------------------------------- +// WP_NONE (Flame Thrower) +// WP_ROCKET_LAUNCHER +// WP_BLASTER +// WP_DISRUPTOR +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_TacticsSelect() +{ + // Don't Change Tactics For A Little While + //------------------------------------------ + TIMER_Set(NPC, "Boba_TacticsSelect", Q_irand(8000, 15000)); + int nextState = NPCInfo->localState; + + + // Get Some Data That Will Help With The Selection Of The Next Tactic + //-------------------------------------------------------------------- + bool enemyAlive = (NPC->enemy->health>0); + float enemyDistance = Distance(NPC->currentOrigin, NPC->enemy->currentOrigin); + bool enemyInFlameRange = (enemyDistanceBOBA_ROCKETRANGEMIN && enemyDistancecount), he will be less likely to + // choose the blaster, and more likely to go for the missile launcher + nextState = (!enemyInRocketRange || Q_irand(0, NPC->count)<1)?(BTS_RIFLE):(BTS_MISSILE); + } + + // Hmmm... Havn't Seen The Player In A While, We Might Want To Try Something Sneaky + //----------------------------------------------------------------------------------- + else + { + bool SnipePointsNear = false; // TODO + bool AmbushPointNear = false; // TODO + + if (Q_irand(0, NPC->count)>0) + { + int SniperPoint = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_SNIPE|CP_CLEAR|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1); + if (SniperPoint!=-1) + { + NPC_SetCombatPoint(SniperPoint); + NPC_SetMoveGoal( NPC, level.combatPoints[SniperPoint].origin, 20, qtrue, SniperPoint ); + TIMER_Set(NPC, "PickNewSniperPoint", Q_irand(15000, 25000)); + SnipePointsNear = true; + } + } + + + if (SnipePointsNear && TIMER_Done(NPC, "Boba_NoSniperTime")) + { + TIMER_Set(NPC, "Boba_NoSniperTime", 120000); // Don't snipe again for a while + TIMER_Set(NPC, "Boba_TacticsSelect", Q_irand(35000, 45000));// More patience here + nextState = BTS_SNIPER; + } + else if (AmbushPointNear) + { + TIMER_Set(NPC, "Boba_TacticsSelect", Q_irand(15000, 25000));// More patience here + nextState = BTS_AMBUSHWAIT; + } + else + { + nextState = (!enemyInRocketRange || Q_irand(0, NPC->count)<1)?(BTS_RIFLE):(BTS_MISSILE); + } + } + + + + // The Next State Has Been Selected, Now Change Weapon If Necessary + //------------------------------------------------------------------ + if (nextState!=NPCInfo->localState) + { + NPCInfo->localState = nextState; + switch (NPCInfo->localState) + { + case BTS_FLAMETHROW: + Boba_Printf("NEW TACTIC: Flame Thrower"); + Boba_ChangeWeapon(WP_NONE); + Boba_DoFlameThrower(NPC); + break; + + case BTS_RIFLE: + Boba_Printf("NEW TACTIC: Rifle"); + Boba_ChangeWeapon(WP_BLASTER); + break; + + case BTS_MISSILE: + Boba_Printf("NEW TACTIC: Rocket Launcher"); + Boba_ChangeWeapon(WP_ROCKET_LAUNCHER); + break; + + case BTS_SNIPER: + Boba_Printf("NEW TACTIC: Sniper"); + Boba_ChangeWeapon(WP_DISRUPTOR); + break; + + case BTS_AMBUSHWAIT: + Boba_Printf("NEW TACTIC: Ambush"); + Boba_ChangeWeapon(WP_NONE); + break; + } + } +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Tactics +// +// This function is called right after Update() +// If returns true, Jedi and Seeker AI not used for movement +// +//////////////////////////////////////////////////////////////////////////////////////// +bool Boba_Tactics() +{ + if (!NPC->enemy) + { + return false; + } + + // Think About Changing Tactics + //------------------------------ + if (TIMER_Done(NPC, "Boba_TacticsSelect")) + { + Boba_TacticsSelect(); + } + + // These Tactics Require Seeker & Jedi Movement + //---------------------------------------------- + if (!NPCInfo->localState || + NPCInfo->localState==BTS_RIFLE || + NPCInfo->localState==BTS_MISSILE) + { + return false; + } + + // Flame Thrower - Locked In Place + //--------------------------------- + if (NPCInfo->localState==BTS_FLAMETHROW) + { + Boba_DoFlameThrower( NPC ); + } + + // Sniper - Move Around, And Take Shots + //-------------------------------------- + else if (NPCInfo->localState==BTS_SNIPER) + { + Boba_DoSniper( NPC ); + } + + // Ambush Wait + //------------ + else if (NPCInfo->localState==BTS_AMBUSHWAIT) + { + Boba_DoAmbushWait( NPC ); + } + + + NPC_FacePosition( NPC->enemy->currentOrigin, qtrue); + NPC_UpdateAngles(qtrue, qtrue); + + return true; // Do Not Use Normal Jedi Or Seeker Movement +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool Boba_Respawn() +{ + int cp = -1; + + // Try To Predict Where The Enemy Is Going + //----------------------------------------- + if (AverageEnemyDirectionSamples && NPC->behaviorSet[BSET_DEATH]==0) + { + vec3_t endPos; + VectorMA(NPC->enemy->currentOrigin, 1000.0f / (float)AverageEnemyDirectionSamples, AverageEnemyDirection, endPos); + cp = NPC_FindCombatPoint(endPos, 0, endPos, CP_FLEE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1); + Boba_Printf("Attempting Predictive Spawn Point"); + } + + // If That Failed, Try To Go Directly To The Enemy + //------------------------------------------------- + if (cp==-1) + { + cp = NPC_FindCombatPoint(NPC->enemy->currentOrigin, 0, NPC->enemy->currentOrigin, CP_FLEE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1); + Boba_Printf("Attempting Closest Current Spawn Point"); + } + + // If We've Found One, Go There + //------------------------------ + if (cp!=-1) + { + NPC_SetCombatPoint( cp ); + NPCInfo->surrenderTime = 0; + NPC->health = NPC->max_health; + NPC->svFlags &=~SVF_NOCLIENT; + NPC->count ++; // This is the number of times spawned + G_SetOrigin(NPC, level.combatPoints[cp].origin); + + AverageEnemyDirectionSamples = 0; + VectorClear(AverageEnemyDirection); + + Boba_Printf("Found Spawn Point (%d)", cp); + return true; + } + + assert(0); // Yea, that's bad... + Boba_Printf("FAILED TO FIND SPAWN POINT"); + return false; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Boba_Update() +{ + // Never Forget The Player... Never. + //----------------------------------- + if (player && player->inuse && !NPC->enemy) + { + G_SetEnemy(NPC, player); + NPC->svFlags |= SVF_LOCKEDENEMY; // Don't forget about the enemy once you've found him + } + + // Hey, This Is Boba, He Tests The Trace All The Time + //---------------------------------------------------- + if (NPC->enemy) + { + if (!(NPC->svFlags&SVF_NOCLIENT)) + { + trace_t testTrace; + vec3_t eyes; + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); + gi.trace (&testTrace, eyes, NULL, NULL, NPC->enemy->currentOrigin, NPC->s.number, MASK_SHOT); + + bool wasSeen = Boba_CanSeeEnemy(NPC); + + if (!testTrace.startsolid && + !testTrace.allsolid && + testTrace.entityNum == NPC->enemy->s.number) + { + NPCInfo->enemyLastSeenTime = level.time; + NPCInfo->enemyLastHeardTime = level.time; + VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation); + VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastHeardLocation); + } + else if (gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin)) + { + NPCInfo->enemyLastHeardTime = level.time; + VectorCopy(NPC->enemy->currentOrigin, NPCInfo->enemyLastHeardLocation); + } + + if (g_bobaDebug->integer) + { + bool nowSeen = Boba_CanSeeEnemy(NPC); + if (!wasSeen && nowSeen) + { + Boba_Printf("Enemy Seen"); + } + if (wasSeen && !nowSeen) + { + Boba_Printf("Enemy Lost"); + } + CG_DrawEdge(NPC->currentOrigin, NPC->enemy->currentOrigin, (nowSeen)?(EDGE_IMPACT_SAFE):(EDGE_IMPACT_POSSIBLE)); + } + } + + if (!NPCInfo->surrenderTime) + { + if ((level.time - NPCInfo->enemyLastSeenTime)>20000 && TIMER_Done(NPC, "TooLongGoneRespawn")) + { + TIMER_Set(NPC, "TooLongGoneRespawn", 30000); // Give him some time to get to you before trying again + Boba_Printf("Gone Too Long, Attempting Respawn Even Though Not Hiding"); + Boba_Respawn(); + } + } + } + + + // Make Sure He Always Appears In The Last Area With Full Health When His Death Script Is Turned On + //-------------------------------------------------------------------------------------------------- + if (!BobaHadDeathScript && NPC->behaviorSet[BSET_DEATH]!=0) + { + if (!gi.inPVS(NPC->enemy->currentOrigin, NPC->currentOrigin)) + { + Boba_Printf("Attempting Final Battle Spawn..."); + if (Boba_Respawn()) + { + BobaHadDeathScript = true; + } + else + { + Boba_Printf("Failed"); + } + } + } + + + + // Don't Forget To Turn Off That Flame Thrower, Mr. Fett - You're Waisting Precious Natural Gases + //------------------------------------------------------------------------------------------------ + if ((NPCInfo->aiFlags&NPCAI_FLAMETHROW) && (TIMER_Done(NPC, "flameTime"))) + { + Boba_StopFlameThrower(NPC); + } + + + // Occasionally A Jump Turns Into A Rocket Fly + //--------------------------------------------- + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE + && NPC->client->ps.forceJumpZStart + && !Q_irand( 0, 10 ) ) + {//take off + Boba_FlyStart( NPC ); + } + + + // If Hurting, Try To Run Away + //----------------------------- + if (!NPCInfo->surrenderTime && (NPC->healthmax_health/10)) + { + Boba_Printf("Time To Surrender, Searching For Flee Point"); + + + // Find The Closest Flee Point That I Can Get To + //----------------------------------------------- + int cp = NPC_FindCombatPoint(NPC->currentOrigin, 0, NPC->currentOrigin, CP_FLEE|CP_HAS_ROUTE|CP_TRYFAR|CP_HORZ_DIST_COLL, 0, -1); + if (cp!=-1) + { + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp ); + if (NPC->count<6) + { + NPCInfo->surrenderTime = level.time + Q_irand(5000, 10000) + 1000*(6-NPC->count); + } + else + { + NPCInfo->surrenderTime = level.time + Q_irand(5000, 10000); + } + } + else + { + Boba_Printf(" Failure"); + } + } +} + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool Boba_Flee() +{ + bool EnemyRecentlySeen = ((level.time - NPCInfo->enemyLastSeenTime)<10000); + bool ReachedEscapePoint = (Distance(level.combatPoints[NPCInfo->combatPoint].origin, NPC->currentOrigin)<50.0f); + bool HasBeenGoneEnough = (level.time>NPCInfo->surrenderTime || (level.time - NPCInfo->enemyLastSeenTime)>400000); + + + // Is It Time To Come Back For Some More? + //---------------------------------------- + if (!EnemyRecentlySeen || ReachedEscapePoint) + { + NPC->svFlags |= SVF_NOCLIENT; + if (HasBeenGoneEnough) + { + if ((level.time - NPCInfo->enemyLastSeenTime)>400000) + { + Boba_Printf(" Gone Too Long, Attempting Respawn"); + } + + if (Boba_Respawn()) + { + return true; + } + } + else if (ReachedEscapePoint && (NPCInfo->surrenderTime - level.time)>3000) + { + if (TIMER_Done(NPC, "SpookPlayerTimer")) + { + vec3_t testDirection; + TIMER_Set(NPC, "SpookPlayerTimer", Q_irand(2000, 10000)); + switch(Q_irand(0, 1)) + { + case 0: + Boba_Printf("SPOOK: Dust"); + Boba_DustFallNear(NPC->enemy->currentOrigin, Q_irand(1,2)); + break; + + case 1: + Boba_Printf("SPOOK: Footsteps"); + testDirection[0] = (random() * 0.5f) - 1.0f; + testDirection[0] += (testDirection[0]>0.0f)?(0.5f):(-0.5f); + testDirection[1] = (random() * 0.5f) - 1.0f; + testDirection[1] += (testDirection[1]>0.0f)?(0.5f):(-0.5f); + testDirection[2] = 1.0f; + VectorMA(NPC->enemy->currentOrigin, 400.0f, testDirection, BobaFootStepLoc); + + BobaFootStepCount = Q_irand(3,8); + break; + } + } + + if (BobaFootStepCount && TIMER_Done(NPC, "BobaFootStepFakeTimer")) + { + TIMER_Set(NPC, "BobaFootStepFakeTimer", Q_irand(300, 800)); + BobaFootStepCount --; + G_SoundAtSpot(BobaFootStepLoc, G_SoundIndex(va("sound/player/footsteps/boot%d", Q_irand(1,4))), qtrue); + } + + if (TIMER_Done(NPC, "ResampleEnemyDirection") && NPC->enemy->resultspeed>10.0f) + { + TIMER_Set(NPC, "ResampleEnemyDirection", Q_irand(500, 1000)); + AverageEnemyDirectionSamples ++; + + vec3_t moveDir; + VectorCopy(NPC->enemy->client->ps.velocity, moveDir); + VectorNormalize(moveDir); + + VectorAdd(AverageEnemyDirection, moveDir, AverageEnemyDirection); + } + + if (g_bobaDebug->integer && AverageEnemyDirectionSamples) + { + vec3_t endPos; + VectorMA(NPC->enemy->currentOrigin, 500.0f / (float)AverageEnemyDirectionSamples, AverageEnemyDirection, endPos); + CG_DrawEdge(NPC->enemy->currentOrigin, endPos, EDGE_IMPACT_POSSIBLE); + } + } + } + else + { + NPCInfo->surrenderTime += 100; + } + + // Finish The Flame Thrower First... + //----------------------------------- + if (NPCInfo->aiFlags&NPCAI_FLAMETHROW) + { + Boba_DoFlameThrower( NPC ); + NPC_FacePosition( NPC->enemy->currentOrigin, qtrue); + NPC_UpdateAngles(qtrue, qtrue); + return true; + } + + bool IsOnAPath = !!NPC_MoveToGoal(qtrue); + if (!ReachedEscapePoint && + NPCInfo->aiFlags&NPCAI_BLOCKED && + NPC->client->moveType!=MT_FLYSWIM && + ((level.time - NPCInfo->blockedDebounceTime)>1000) + ) + { + if (!Boba_CanSeeEnemy(NPC) && Distance(NPC->currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin)<200) + { + Boba_Printf("BLOCKED: Just Teleporting There"); + G_SetOrigin(NPC, level.combatPoints[NPCInfo->combatPoint].origin); + } + else + { + Boba_Printf("BLOCKED: Attempting Jump"); + + if (IsOnAPath) + { + if (NPC_TryJump(NPCInfo->blockedTargetPosition)) + { + } + else + { + Boba_Printf(" Failed"); + } + } + else if (EnemyRecentlySeen) + { + if (NPC_TryJump(NPCInfo->enemyLastSeenLocation)) + { + } + else + { + Boba_Printf(" Failed"); + } + } + } + } + + + NPC_UpdateAngles( qtrue, qtrue ); + return true; +} \ No newline at end of file diff --git a/code/game/AI_Civilian.cpp b/code/game/AI_Civilian.cpp new file mode 100644 index 0000000..5b5a9cb --- /dev/null +++ b/code/game/AI_Civilian.cpp @@ -0,0 +1,43 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" +#include "Q3_Interface.h" + +extern qboolean NPC_CheckSurrender( void ); +extern void NPC_BehaviorSet_Default( int bState ); + +void NPC_BSCivilian_Default( int bState ) +{ + if ( NPC->enemy + && NPC->s.weapon == WP_NONE + && NPC_CheckSurrender() ) + {//surrendering, do nothing + } + else if ( NPC->enemy + && NPC->s.weapon == WP_NONE + && bState != BS_HUNT_AND_KILL + && !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//if in battle and have no weapon, run away, fixme: when in BS_HUNT_AND_KILL, they just stand there + if ( !NPCInfo->goalEntity + || bState != BS_FLEE //not fleeing + || ( NPC_BSFlee()//have reached our flee goal + && NPC->enemy//still have enemy (NPC_BSFlee checks enemy and can clear it) + && DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ) < 16384 )//enemy within 128 + ) + {//run away! + NPC_StartFlee( NPC->enemy, NPC->enemy->currentOrigin, AEL_DANGER_GREAT, 5000, 10000 ); + } + } + else + {//not surrendering + //FIXME: if unarmed and a jawa/ugnuaght, constantly look for enemies/players to run away from? + //FIXME: if we have a weapon and an enemy, set out playerTeam to the opposite of our enemy..??? + NPC_BehaviorSet_Default(bState); + } + if ( !VectorCompare( NPC->client->ps.moveDir, vec3_origin ) ) + {//moving + if ( NPC->client->ps.legsAnim == BOTH_COWER1 ) + {//stop cowering anim on legs + NPC->client->ps.legsAnimTimer = 0; + } + } +} \ No newline at end of file diff --git a/code/game/AI_Default.cpp b/code/game/AI_Default.cpp new file mode 100644 index 0000000..d3dbc95 --- /dev/null +++ b/code/game/AI_Default.cpp @@ -0,0 +1,958 @@ +#include "g_headers.h" + +#include "Q3_Interface.h" + +//#include "anims.h" +//extern int PM_AnimLength( int index, animNumber_t anim ); +//extern qboolean PM_HasAnimation( gentity_t *ent, int animation ); +//extern int PM_AnimLength( int index, animNumber_t anim ); +//#define MAX_IDLE_ANIMS 8 +extern int g_crosshairEntNum; + +/* +void NPC_LostEnemyDecideChase(void) + + We lost our enemy and want to drop him but see if we should chase him if we are in the proper bState +*/ + +void NPC_LostEnemyDecideChase(void) +{ + switch( NPCInfo->behaviorState ) + { + case BS_HUNT_AND_KILL: + //We were chasing him and lost him, so try to find him + if ( NPC->enemy == NPCInfo->goalEntity && NPC->enemy->lastWaypoint != WAYPOINT_NONE ) + {//Remember his last valid Wp, then check it out + //FIXME: Should we only do this if there's no other enemies or we've got LOCKED_ENEMY on? + NPC_BSSearchStart( NPC->enemy->lastWaypoint, BS_SEARCH ); + } + //If he's not our goalEntity, we're running somewhere else, so lose him + break; + default: + break; + } + G_ClearEnemy( NPC ); +} +/* +------------------------- +NPC_StandIdle +------------------------- +*/ + +void NPC_StandIdle( void ) +{ +/* + //Must be done with any other animations + if ( NPC->client->ps.legsAnimTimer != 0 ) + return; + + //Not ready to do another one + if ( TIMER_Done( NPC, "idleAnim" ) == false ) + return; + + int anim = NPC->client->ps.legsAnim; + + if ( anim != BOTH_STAND1 && anim != BOTH_STAND2 ) + return; + + //FIXME: Account for STAND1 or STAND2 here and set the base anim accordingly + int baseSeq = ( anim == BOTH_STAND1 ) ? BOTH_STAND1_RANDOM1 : BOTH_STAND2_RANDOM1; + + //Must have at least one random idle animation + //NOTENOTE: This relies on proper ordering of animations, which SHOULD be okay + if ( PM_HasAnimation( NPC, baseSeq ) == false ) + return; + + int newIdle = Q_irand( 0, MAX_IDLE_ANIMS-1 ); + + //FIXME: Technically this could never complete.. but that's not really too likely + while( 1 ) + { + if ( PM_HasAnimation( NPC, baseSeq + newIdle ) ) + break; + + newIdle = Q_irand( 0, MAX_IDLE_ANIMS ); + } + + //Start that animation going + NPC_SetAnim( NPC, SETANIM_BOTH, baseSeq + newIdle, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + int newTime = PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t) (baseSeq + newIdle) ); + + //Don't do this again for a random amount of time + TIMER_Set( NPC, "idleAnim", newTime + Q_irand( 2000, 10000 ) ); +*/ +} + +qboolean NPC_StandTrackAndShoot (gentity_t *NPC, qboolean canDuck) +{ + qboolean attack_ok = qfalse; + qboolean duck_ok = qfalse; + qboolean faced = qfalse; + float attack_scale = 1.0; + + //First see if we're hurt bad- if so, duck + //FIXME: if even when ducked, we can shoot someone, we should. + //Maybe is can be shot even when ducked, we should run away to the nearest cover? + if ( canDuck ) + { + if ( NPC->health < 20 ) + { + // if( NPC->svFlags&SVF_HEALING || random() ) + if( random() ) + { + duck_ok = qtrue; + } + } + else if ( NPC->health < 40 ) + { +// if ( NPC->svFlags&SVF_HEALING ) +// {//Medic is on the way, get down! +// duck_ok = qtrue; +// } + // no more borg +/// if ( NPC->client->playerTeam!= TEAM_BORG ) +// {//Borg don't care if they're about to die + //attack_scale will be a max of .66 +// attack_scale = NPC->health/60; +// } + } + } + + //NPC_CheckEnemy( qtrue, qfalse ); + + if ( !duck_ok ) + {//made this whole part a function call + attack_ok = NPC_CheckCanAttack( attack_scale, qtrue ); + faced = qtrue; + } + + if ( canDuck && (duck_ok || (!attack_ok && client->fireDelay == 0)) && ucmd.upmove != -127 ) + {//if we didn't attack check to duck if we're not already + if( !duck_ok ) + { + if ( NPC->enemy->client ) + { + if ( NPC->enemy->enemy == NPC ) + { + if ( NPC->enemy->client->buttons & BUTTON_ATTACK ) + {//FIXME: determine if enemy fire angles would hit me or get close + if ( NPC_CheckDefend( 1.0 ) )//FIXME: Check self-preservation? Health? + { + duck_ok = qtrue; + } + } + } + } + } + + if ( duck_ok ) + {//duck and don't shoot + attack_ok = qfalse; + ucmd.upmove = -127; + NPCInfo->duckDebounceTime = level.time + 1000;//duck for a full second + } + } + + return faced; +} + + +void NPC_BSIdle( void ) +{ + //FIXME if there is no nav data, we need to do something else + // if we're stuck, try to move around it + if ( UpdateGoal() ) + { + NPC_MoveToGoal( qtrue ); + } + + if ( ( ucmd.forwardmove == 0 ) && ( ucmd.rightmove == 0 ) && ( ucmd.upmove == 0 ) ) + { +// NPC_StandIdle(); + } + + NPC_UpdateAngles( qtrue, qtrue ); + ucmd.buttons |= BUTTON_WALKING; +} + +void NPC_BSRun (void) +{ + //FIXME if there is no nav data, we need to do something else + // if we're stuck, try to move around it + if ( UpdateGoal() ) + { + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +void NPC_BSStandGuard (void) +{ + //FIXME: Use Snapshot info + if ( NPC->enemy == NULL ) + {//Possible to pick one up by being shot + if( random() < 0.5 ) + { + if(NPC->client->enemyTeam) + { + gentity_t *newenemy = NPC_PickEnemy(NPC, NPC->client->enemyTeam, (NPC->cantHitEnemyCounter < 10), (NPC->client->enemyTeam == TEAM_PLAYER), qtrue); + //only checks for vis if couldn't hit last enemy + if(newenemy) + { + G_SetEnemy( NPC, newenemy ); + } + } + } + } + + if ( NPC->enemy != NULL ) + { + if( NPCInfo->tempBehavior == BS_STAND_GUARD ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + + if( NPCInfo->behaviorState == BS_STAND_GUARD ) + { + NPCInfo->behaviorState = BS_STAND_AND_SHOOT; + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSHuntAndKill +------------------------- +*/ + +void NPC_BSHuntAndKill( void ) +{ + qboolean turned = qfalse; + vec3_t vec; + float enemyDist; + visibility_t oEVis; + int curAnim; + + NPC_CheckEnemy( NPCInfo->tempBehavior != BS_HUNT_AND_KILL, qfalse );//don't find new enemy if this is tempbehav + + if ( NPC->enemy ) + { + oEVis = enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT );//CHECK_360|//CHECK_PVS| + if(enemyVisibility > VIS_PVS) + { + if ( !NPC_EnemyTooFar( NPC->enemy, 0, qtrue ) ) + {//Enemy is close enough to shoot - FIXME: this next func does this also, but need to know here for info on whether ot not to turn later + NPC_CheckCanAttack( 1.0, qfalse ); + turned = qtrue; + } + } + + curAnim = NPC->client->ps.legsAnim; + if(curAnim != BOTH_ATTACK1 && curAnim != BOTH_ATTACK2 && curAnim != BOTH_ATTACK3 && curAnim != BOTH_MELEE1 && curAnim != BOTH_MELEE2 ) + {//Don't move toward enemy if we're in a full-body attack anim + //FIXME, use IdealDistance to determin if we need to close distance + VectorSubtract(NPC->enemy->currentOrigin, NPC->currentOrigin, vec); + enemyDist = VectorLength(vec); + if( enemyDist > 48 && ((enemyDist*1.5)*(enemyDist*1.5) >= NPC_MaxDistSquaredForWeapon() || + oEVis != VIS_SHOOT || + //!(ucmd.buttons & BUTTON_ATTACK) || + enemyDist > IdealDistance(NPC)*3 ) ) + {//We should close in? + NPCInfo->goalEntity = NPC->enemy; + + NPC_MoveToGoal( qtrue ); + } + else if(enemyDist < IdealDistance(NPC)) + {//We should back off? + //if(ucmd.buttons & BUTTON_ATTACK) + { + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + NPC_MoveToGoal( qtrue ); + + ucmd.forwardmove *= -1; + ucmd.rightmove *= -1; + VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + + ucmd.buttons |= BUTTON_WALKING; + } + }//otherwise, stay where we are + } + } + else + {//ok, stand guard until we find an enemy + if( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + { + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + } + return; + } + + if(!turned) + { + NPC_UpdateAngles(qtrue, qtrue); + } +} + +void NPC_BSStandAndShoot (void) +{ + //FIXME: + //When our numbers outnumber enemies 3 to 1, or only one of them, + //go into hunt and kill mode + + //FIXME: + //When they're all dead, go to some script or wander off to sickbay? + + if(NPC->client->playerTeam && NPC->client->enemyTeam) + { + //FIXME: don't realize this right away- or else enemies show up and we're standing around + /* + if( teamNumbers[NPC->enemyTeam] == 0 ) + {//ok, stand guard until we find another enemy + //reset our rush counter + teamCounter[NPC->playerTeam] = 0; + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + return; + }*/ + /* + //FIXME: whether to do this or not should be settable + else if( NPC->playerTeam != TEAM_BORG )//Borg don't rush + { + //FIXME: In case reinforcements show up, we should wait a few seconds + //and keep checking before rushing! + //Also: what if not everyone on our team is going after playerTeam? + //Also: our team count includes medics! + if(NPC->health > 25) + {//Can we rush the enemy? + if(teamNumbers[NPC->enemyTeam] == 1 || + teamNumbers[NPC->playerTeam] >= teamNumbers[NPC->enemyTeam]*3) + {//Only one of them or we outnumber 3 to 1 + if(teamStrength[NPC->playerTeam] >= 75 || + (teamStrength[NPC->playerTeam] >= 50 && teamStrength[NPC->playerTeam] > teamStrength[NPC->enemyTeam])) + {//Our team is strong enough to rush + teamCounter[NPC->playerTeam]++; + if(teamNumbers[NPC->playerTeam] * 17 <= teamCounter[NPC->playerTeam]) + {//ok, we waited 1.7 think cycles on average and everyone is go, let's do it! + //FIXME: Should we do this to everyone on our team? + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + //FIXME: if the tide changes, we should retreat! + //FIXME: when do we reset the counter? + NPC_BSHuntAndKill (); + return; + } + } + else//Oops! Something's wrong, reset the counter to rush + teamCounter[NPC->playerTeam] = 0; + } + else//Oops! Something's wrong, reset the counter to rush + teamCounter[NPC->playerTeam] = 0; + } + } + */ + } + + NPC_CheckEnemy(qtrue, qfalse); + + if(NPCInfo->duckDebounceTime > level.time && NPC->client->ps.weapon != WP_SABER ) + { + ucmd.upmove = -127; + if(NPC->enemy) + { + NPC_CheckCanAttack(1.0, qtrue); + } + return; + } + + if(NPC->enemy) + { + if(!NPC_StandTrackAndShoot( NPC, qtrue )) + {//That func didn't update our angles + NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH]; + NPC_UpdateAngles(qtrue, qtrue); + } + } + else + { + NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH]; + NPC_UpdateAngles(qtrue, qtrue); +// NPC_BSIdle();//only moves if we have a goal + } +} + +void NPC_BSRunAndShoot (void) +{ + /*if(NPC->playerTeam && NPC->enemyTeam) + { + //FIXME: don't realize this right away- or else enemies show up and we're standing around + if( teamNumbers[NPC->enemyTeam] == 0 ) + {//ok, stand guard until we find another enemy + //reset our rush counter + teamCounter[NPC->playerTeam] = 0; + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + return; + } + }*/ + + //NOTE: are we sure we want ALL run and shoot people to move this way? + //Shouldn't it check to see if we have an enemy and our enemy is our goal?! + //Moved that check into NPC_MoveToGoal + //NPCInfo->combatMove = qtrue; + + NPC_CheckEnemy( qtrue, qfalse ); + + if ( NPCInfo->duckDebounceTime > level.time ) // && NPCInfo->hidingGoal ) + { + ucmd.upmove = -127; + if ( NPC->enemy ) + { + NPC_CheckCanAttack( 1.0, qfalse ); + } + return; + } + + if ( NPC->enemy ) + { + int monitor = NPC->cantHitEnemyCounter; + NPC_StandTrackAndShoot( NPC, qfalse );//(NPCInfo->hidingGoal != NULL) ); + + if ( !(ucmd.buttons & BUTTON_ATTACK) && ucmd.upmove >= 0 && NPC->cantHitEnemyCounter > monitor ) + {//not crouching and not firing + vec3_t vec; + + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, vec ); + vec[2] = 0; + if ( VectorLength( vec ) > 128 || NPC->cantHitEnemyCounter >= 10 ) + {//run at enemy if too far away + //The cantHitEnemyCounter getting high has other repercussions + //100 (10 seconds) will make you try to pick a new enemy... + //But we're chasing, so we clamp it at 50 here + if ( NPC->cantHitEnemyCounter > 60 ) + { + NPC->cantHitEnemyCounter = 60; + } + + if ( NPC->cantHitEnemyCounter >= (NPCInfo->stats.aggression+1) * 10 ) + { + NPC_LostEnemyDecideChase(); + } + + //chase and face + ucmd.angles[YAW] = 0; + ucmd.angles[PITCH] = 0; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + NPC_MoveToGoal( qtrue ); + NPC_UpdateAngles(qtrue, qtrue); + } + else + { + //FIXME: this could happen if they're just on the other side + //of a thin wall or something else blocking out shot. That + //would make us just stand there and not go around it... + //but maybe it's okay- might look like we're waiting for + //him to come out...? + //Current solution: runs around if cantHitEnemyCounter gets + //to 10 (1 second). + } + } + else + {//Clear the can't hit enemy counter here + NPC->cantHitEnemyCounter = 0; + } + } + else + { + if ( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + {//lost him, go back to what we were doing before + NPCInfo->tempBehavior = BS_DEFAULT; + return; + } + +// NPC_BSRun();//only moves if we have a goal + } +} + +//Simply turn until facing desired angles +void NPC_BSFace (void) +{ + //FIXME: once you stop sending turning info, they reset to whatever their delta_angles was last???? + //Once this is over, it snaps back to what it was facing before- WHY??? + if( NPC_UpdateAngles ( qtrue, qtrue ) ) + { + Q3_TaskIDComplete( NPC, TID_BSTATE ); + + NPCInfo->desiredYaw = client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = client->ps.viewangles[PITCH]; + + NPCInfo->aimTime = 0;//ok to turn normally now + } +} + +void NPC_BSPointShoot (qboolean shoot) +{//FIXME: doesn't check for clear shot... + vec3_t muzzle, dir, angles, org; + + if ( !NPC->enemy || !NPC->enemy->inuse || (NPC->enemy->NPC && NPC->enemy->health <= 0) ) + {//FIXME: should still keep shooting for a second or two after they actually die... + Q3_TaskIDComplete( NPC, TID_BSTATE ); + goto finished; + return; + } + + CalcEntitySpot(NPC, SPOT_WEAPON, muzzle); + CalcEntitySpot(NPC->enemy, SPOT_HEAD, org);//Was spot_org + //Head is a little high, so let's aim for the chest: + if ( NPC->enemy->client ) + { + org[2] -= 12;//NOTE: is this enough? + } + + VectorSubtract(org, muzzle, dir); + vectoangles(dir, angles); + + switch( NPC->client->ps.weapon ) + { + case WP_NONE: +// case WP_TRICORDER: + case WP_MELEE: + case WP_TUSKEN_STAFF: + case WP_SABER: + //don't do any pitch change if not holding a firing weapon + break; + default: + NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]); + break; + } + + NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]); + + if ( NPC_UpdateAngles ( qtrue, qtrue ) ) + {//FIXME: if angles clamped, this may never work! + //NPCInfo->shotTime = NPC->attackDebounceTime = 0; + + if ( shoot ) + {//FIXME: needs to hold this down if using a weapon that requires it, like phaser... + ucmd.buttons |= BUTTON_ATTACK; + } + + if ( !shoot || !(NPC->svFlags & SVF_LOCKEDENEMY) ) + {//If locked_enemy is on, dont complete until it is destroyed... + Q3_TaskIDComplete( NPC, TID_BSTATE ); + goto finished; + } + } + else if ( shoot && (NPC->svFlags & SVF_LOCKEDENEMY) ) + {//shooting them till their dead, not aiming right at them yet... + /* + qboolean movingTarget = qfalse; + + if ( NPC->enemy->client ) + { + if ( VectorLengthSquared( NPC->enemy->client->ps.velocity ) ) + { + movingTarget = qtrue; + } + } + else if ( VectorLengthSquared( NPC->enemy->s.pos.trDelta ) ) + { + movingTarget = qtrue; + } + + if (movingTarget ) + */ + { + float dist = VectorLength( dir ); + float yawMiss, yawMissAllow = NPC->enemy->maxs[0]; + float pitchMiss, pitchMissAllow = (NPC->enemy->maxs[2] - NPC->enemy->mins[2])/2; + + if ( yawMissAllow < 8.0f ) + { + yawMissAllow = 8.0f; + } + + if ( pitchMissAllow < 8.0f ) + { + pitchMissAllow = 8.0f; + } + + yawMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw ))) * dist; + pitchMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[PITCH], NPCInfo->desiredPitch))) * dist; + + if ( yawMissAllow >= yawMiss && pitchMissAllow > pitchMiss ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + } + } + + return; + +finished: + NPCInfo->desiredYaw = client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = client->ps.viewangles[PITCH]; + + NPCInfo->aimTime = 0;//ok to turn normally now +} + +/* +void NPC_BSMove(void) +Move in a direction, face another +*/ +void NPC_BSMove(void) +{ + gentity_t *goal = NULL; + + NPC_CheckEnemy(qtrue, qfalse); + if(NPC->enemy) + { + NPC_CheckCanAttack(1.0, qfalse); + } + else + { + NPC_UpdateAngles(qtrue, qtrue); + } + + goal = UpdateGoal(); + if(goal) + { +// NPCInfo->moveToGoalMod = 1.0; + + NPC_SlideMoveToGoal(); + } +} + +/* +void NPC_BSShoot(void) +Move in a direction, face another +*/ + +void NPC_BSShoot(void) +{ +// NPC_BSMove(); + + enemyVisibility = VIS_SHOOT; + + if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING ) + { + client->ps.weaponstate = WEAPON_READY; + } + + WeaponThink(qtrue); +} + +/* +void NPC_BSPatrol( void ) + + Same as idle, but you look for enemies every "vigilance" + using your angles, HFOV, VFOV and visrange, and listen for sounds within earshot... +*/ +void NPC_BSPatrol( void ) +{ + //int alertEventNum; + + if(level.time > NPCInfo->enemyCheckDebounceTime) + { + NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 1000); + NPC_CheckEnemy(qtrue, qfalse); + if(NPC->enemy) + {//FIXME: do anger script + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + //NPC_AngerSound(); + return; + } + } + + //FIXME: Implement generic sound alerts + /* + alertEventNum = NPC_CheckAlertEvents( qtrue, qtrue ); + if( alertEventNum != -1 ) + {//If we heard something, see if we should check it out + if ( NPC_CheckInvestigate( alertEventNum ) ) + { + return; + } + } + */ + + NPCInfo->investigateSoundDebounceTime = 0; + //FIXME if there is no nav data, we need to do something else + // if we're stuck, try to move around it + if ( UpdateGoal() ) + { + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); + + ucmd.buttons |= BUTTON_WALKING; +} + +/* +void NPC_BSDefault(void) + uses various scriptflags to determine how an npc should behave +*/ +extern void NPC_CheckGetNewWeapon( void ); +extern void NPC_BSST_Attack( void ); + +void NPC_BSDefault( void ) +{ +// vec3_t enemyDir; +// float enemyDist; +// float shootDist; +// qboolean enemyFOV = qfalse; +// qboolean enemyShotFOV = qfalse; +// qboolean enemyPVS = qfalse; +// vec3_t enemyHead; +// vec3_t muzzle; +// qboolean enemyLOS = qfalse; +// qboolean enemyCS = qfalse; + qboolean move = qtrue; +// qboolean shoot = qfalse; + + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) + {//being forced to walk + if( NPC->client->ps.torsoAnim != TORSO_SURRENDER_START ) + { + NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD ); + } + } + //look for a new enemy if don't have one and are allowed to look, validate current enemy if have one + NPC_CheckEnemy( (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES), qfalse ); + if ( !NPC->enemy ) + {//still don't have an enemy + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + {//check for alert events + //FIXME: Check Alert events, see if we should investigate or just look at it + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED ); + + //There is an event to look at + if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + {//heard/saw something + if ( level.alertEvents[alertEvent].level >= AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + {//was a big event + if ( level.alertEvents[alertEvent].owner + && level.alertEvents[alertEvent].owner != NPC + && level.alertEvents[alertEvent].owner->client + && level.alertEvents[alertEvent].owner->health >= 0 + && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + } + } + else + {//FIXME: investigate lesser events + } + } + //FIXME: also check our allies' condition? + } + } + + if ( NPC->enemy && !(NPCInfo->scriptFlags&SCF_FORCED_MARCH) ) + { + // just use the stormtrooper attack AI... + NPC_CheckGetNewWeapon(); + if ( NPC->client->leader + && NPCInfo->goalEntity == NPC->client->leader + && !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + { + NPC_ClearGoal(); + } + NPC_BSST_Attack(); + return; +/* + //have an enemy + //FIXME: if one of these fails, meaning we can't shoot, do we really need to do the rest? + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, enemyDir ); + enemyDist = VectorNormalize( enemyDir ); + enemyDist *= enemyDist; + shootDist = NPC_MaxDistSquaredForWeapon(); + + enemyFOV = InFOV( NPC->enemy, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ); + enemyShotFOV = InFOV( NPC->enemy, NPC, 20, 20 ); + enemyPVS = gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ); + + if ( enemyPVS ) + {//in the pvs + trace_t tr; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemyHead ); + enemyHead[2] -= Q_flrand( 0.0f, NPC->enemy->maxs[2]*0.5f ); + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + enemyLOS = NPC_ClearLOS( muzzle, enemyHead ); + + gi.trace ( &tr, muzzle, vec3_origin, vec3_origin, enemyHead, NPC->s.number, MASK_SHOT ); + enemyCS = NPC_EvaluateShot( tr.entityNum, qtrue ); + } + else + {//skip thr 2 traces since they would have to fail + enemyLOS = qfalse; + enemyCS = qfalse; + } + + if ( enemyCS && enemyShotFOV ) + {//can hit enemy if we want + NPC->cantHitEnemyCounter = 0; + } + else + {//can't hit + NPC->cantHitEnemyCounter++; + } + + if ( enemyCS && enemyShotFOV && enemyDist < shootDist ) + {//can shoot + shoot = qtrue; + if ( NPCInfo->goalEntity == NPC->enemy ) + {//my goal is my enemy and I have a clear shot, no need to chase right now + move = qfalse; + } + } + else + {//don't shoot yet, keep chasing + shoot = qfalse; + move = qtrue; + } + + //shoot decision + if ( !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//try to shoot + if ( NPC->enemy ) + { + if ( shoot ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + } + } + } + + //chase decision + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + {//go after him + NPCInfo->goalEntity = NPC->enemy; + //FIXME: don't need to chase when have a clear shot and in range? + if ( !enemyCS && NPC->cantHitEnemyCounter > 60 ) + {//haven't been able to shoot enemy for about 6 seconds, need to do something + //FIXME: combat points? Just chase? + if ( enemyPVS ) + {//in my PVS, just pick a combat point + //FIXME: implement + } + else + {//just chase him + } + } + //FIXME: in normal behavior, should we use combat Points? Do we care? Is anyone actually going to ever use this AI? + } + else if ( NPC->cantHitEnemyCounter > 60 ) + {//pick a new one + NPC_CheckEnemy( qtrue, qfalse ); + } + + if ( enemyPVS && enemyLOS )//&& !enemyShotFOV ) + {//have a clear LOS to him//, but not looking at him + //Find the desired angles + vec3_t angles; + + GetAnglesForDirection( muzzle, enemyHead, angles ); + + NPCInfo->desiredYaw = AngleNormalize180( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize180( angles[PITCH] ); + } + */ + } + + if ( UpdateGoal() ) + {//have a goal + if ( !NPC->enemy + && NPC->client->leader + && NPCInfo->goalEntity == NPC->client->leader + && !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + { + NPC_BSFollowLeader(); + } + else + { + //set angles + if ( (NPCInfo->scriptFlags & SCF_FACE_MOVE_DIR) || NPCInfo->goalEntity != NPC->enemy ) + {//face direction of movement, NOTE: default behavior when not chasing enemy + NPCInfo->combatMove = qfalse; + } + else + {//face goal.. FIXME: what if have a navgoal but want to face enemy while moving? Will this do that? + vec3_t dir, angles; + + NPCInfo->combatMove = qfalse; + + VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir ); + vectoangles( dir, angles ); + NPCInfo->desiredYaw = angles[YAW]; + if ( NPCInfo->goalEntity == NPC->enemy ) + { + NPCInfo->desiredPitch = angles[PITCH]; + } + } + + //set movement + //override default walk/run behavior + //NOTE: redundant, done in NPC_ApplyScriptFlags + if ( NPCInfo->scriptFlags & SCF_RUNNING ) + { + ucmd.buttons &= ~BUTTON_WALKING; + } + else if ( NPCInfo->scriptFlags & SCF_WALKING ) + { + ucmd.buttons |= BUTTON_WALKING; + } + else if ( NPCInfo->goalEntity == NPC->enemy ) + { + ucmd.buttons &= ~BUTTON_WALKING; + } + else + { + ucmd.buttons |= BUTTON_WALKING; + } + + if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) + {//being forced to walk + if ( g_crosshairEntNum != NPC->s.number ) + {//don't walk if player isn't aiming at me + move = qfalse; + } + } + + if ( move ) + { + //move toward goal + NPC_MoveToGoal( qtrue ); + } + } + } + else if ( !NPC->enemy && NPC->client->leader ) + { + NPC_BSFollowLeader(); + } + + //update angles + NPC_UpdateAngles( qtrue, qtrue ); +} \ No newline at end of file diff --git a/code/game/AI_Droid.cpp b/code/game/AI_Droid.cpp new file mode 100644 index 0000000..3290abf --- /dev/null +++ b/code/game/AI_Droid.cpp @@ -0,0 +1,556 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" + +//static void R5D2_LookAround( void ); +float NPC_GetPainChance( gentity_t *self, int damage ); + +#define TURN_OFF 0x00000100 + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_BACKINGUP, + LSTATE_SPINNING, + LSTATE_PAIN, + LSTATE_DROP +}; + +/* +------------------------- +R2D2_PartsMove +------------------------- +*/ +void R2D2_PartsMove(void) +{ + // Front 'eye' lense + if ( TIMER_Done(NPC,"eyeDelay") ) + { + NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); + + NPC->pos1[0]+=Q_irand( -20, 20 ); // Roll + NPC->pos1[1]=Q_irand( -20, 20 ); + NPC->pos1[2]=Q_irand( -20, 20 ); + + if (NPC->genericBone1) + { + gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + TIMER_Set( NPC, "eyeDelay", Q_irand( 100, 1000 ) ); + } +} + +/* +------------------------- +NPC_BSDroid_Idle +------------------------- +*/ +void Droid_Idle( void ) +{ +// VectorCopy( NPCInfo->investigateGoal, lookPos ); + +// NPC_FacePosition( lookPos ); +} + +/* +------------------------- +R2D2_TurnAnims +------------------------- +*/ +void R2D2_TurnAnims ( void ) +{ + float turndelta; + int anim; + + turndelta = AngleDelta(NPC->currentAngles[YAW], NPCInfo->desiredYaw); + + if ((fabs(turndelta) > 20) && ((NPC->client->NPC_class == CLASS_R2D2) || (NPC->client->NPC_class == CLASS_R5D2))) + { + anim = NPC->client->ps.legsAnim; + if (turndelta<0) + { + if (anim != BOTH_TURN_LEFT1) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_LEFT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + else + { + if (anim != BOTH_TURN_RIGHT1) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_RIGHT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + +} + +/* +------------------------- +Droid_Patrol +------------------------- +*/ +void Droid_Patrol( void ) +{ + + NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); + + if ( NPC->client && NPC->client->NPC_class != CLASS_GONK ) + { + R2D2_PartsMove(); // Get his eye moving. + R2D2_TurnAnims(); + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + + if( NPC->client && NPC->client->NPC_class == CLASS_MOUSE ) + { + NPCInfo->desiredYaw += sin(level.time*.5) * 25; // Weaves side to side a little + + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, va("sound/chars/mouse/misc/mousego%d.wav", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + else if( NPC->client && NPC->client->NPC_class == CLASS_R2D2 ) + { + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, va("sound/chars/r2d2/misc/r2d2talk0%d.wav", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + else if( NPC->client && NPC->client->NPC_class == CLASS_R5D2 ) + { + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, va("sound/chars/r5d2/misc/r5talk%d.wav", Q_irand(1, 4)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + if( NPC->client && NPC->client->NPC_class == CLASS_GONK ) + { + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, va("sound/chars/gonk/misc/gonktalk%d.wav", Q_irand(1, 2)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } +// else +// { +// R5D2_LookAround(); +// } + } + + NPC_UpdateAngles( qtrue, qtrue ); + +} + +/* +------------------------- +Droid_Run +------------------------- +*/ +void Droid_Run( void ) +{ + R2D2_PartsMove(); + + if ( NPCInfo->localState == LSTATE_BACKINGUP ) + { + ucmd.forwardmove = -127; + NPCInfo->desiredYaw += 5; + + NPCInfo->localState = LSTATE_NONE; // So he doesn't constantly backup. + } + else + { + ucmd.forwardmove = 64; + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + if (NPC_MoveToGoal( qfalse )) + { + NPCInfo->desiredYaw += sin(level.time*.5) * 5; // Weaves side to side a little + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +void Droid_Spin( void ) +------------------------- +*/ +void Droid_Spin( void ) +{ + vec3_t dir = {0,0,1}; + + R2D2_TurnAnims(); + + + // Head is gone, spin and spark + if ( NPC->client->NPC_class == CLASS_R5D2 ) + { + // No head? + if (gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "head" )) + { + if (TIMER_Done(NPC,"smoke") && !TIMER_Done(NPC,"droidsmoketotal")) + { + TIMER_Set( NPC, "smoke", 100); + G_PlayEffect( "volumetric/droid_smoke" , NPC->currentOrigin,dir); + } + + if (TIMER_Done(NPC,"droidspark")) + { + TIMER_Set( NPC, "droidspark", Q_irand(100,500)); + G_PlayEffect( "sparks/spark", NPC->currentOrigin,dir); + } + + ucmd.forwardmove = Q_irand( -64, 64); + + if (TIMER_Done(NPC,"roam")) + { + TIMER_Set( NPC, "roam", Q_irand( 250, 1000 ) ); + NPCInfo->desiredYaw = Q_irand( 0, 360 ); // Go in random directions + } + } + else + { + if (TIMER_Done(NPC,"roam")) + { + NPCInfo->localState = LSTATE_NONE; + } + else + { + NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around + } + } + } + else + { + if (TIMER_Done(NPC,"roam")) + { + NPCInfo->localState = LSTATE_NONE; + } + else + { + NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSDroid_Pain +------------------------- +*/ +void NPC_Droid_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + int anim; + float pain_chance; + + if ( self->NPC && self->NPC->ignorePain ) + { + return; + } + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( self->client->NPC_class == CLASS_R5D2 ) + { + pain_chance = NPC_GetPainChance( self, damage ); + + // Put it in pain + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this + { + // Health is between 0-30 or was hit by a DEMP2 so pop his head + if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) + { + if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE + { + if ((self->NPC->localState != LSTATE_SPINNING) && + (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "head" ))) + { + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head", TURN_OFF ); + +// G_PlayEffect( "small_chunks" , self->currentOrigin ); + G_PlayEffect( "chunks/r5d2head", self->currentOrigin ); + + self->s.powerups |= ( 1 << PW_SHOCKED ); + self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + + TIMER_Set( self, "droidsmoketotal", 5000); + TIMER_Set( self, "droidspark", 100); + self->NPC->localState = LSTATE_SPINNING; + } + } + } + // Just give him normal pain for a little while + else + { + anim = self->client->ps.legsAnim; + + if ( anim == BOTH_STAND2 ) // On two legs? + { + anim = BOTH_PAIN1; + } + else // On three legs + { + anim = BOTH_PAIN2; + } + + NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + // Spin around in pain + self->NPC->localState = LSTATE_SPINNING; + TIMER_Set( self, "roam", Q_irand(1000,2000)); + } + } + } + else if (self->client->NPC_class == CLASS_MOUSE) + { + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) + { + self->NPC->localState = LSTATE_SPINNING; + self->s.powerups |= ( 1 << PW_SHOCKED ); + self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + } + else + { + self->NPC->localState = LSTATE_BACKINGUP; + } + + self->NPC->scriptFlags &= ~SCF_LOOK_FOR_ENEMIES; + } + else if ((self->client->NPC_class == CLASS_R2D2)) + { + + pain_chance = NPC_GetPainChance( self, damage ); + + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this + { + anim = self->client->ps.legsAnim; + + if ( anim == BOTH_STAND2 ) // On two legs? + { + anim = BOTH_PAIN1; + } + else // On three legs + { + anim = BOTH_PAIN2; + } + + NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + // Spin around in pain + self->NPC->localState = LSTATE_SPINNING; + TIMER_Set( self, "roam", Q_irand(1000,2000)); + } + } + else if ( self->client->NPC_class == CLASS_INTERROGATOR && ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) && other ) + { + vec3_t dir; + + VectorSubtract( self->currentOrigin, other->currentOrigin, dir ); + VectorNormalize( dir ); + + VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); + self->client->ps.velocity[2] -= 127; + } + + NPC_Pain( self, inflictor, other, point, damage, mod); +} + + +/* +------------------------- +Droid_Pain +------------------------- +*/ +void Droid_Pain(void) +{ + if (TIMER_Done(NPC,"droidpain")) //He's done jumping around + { + NPCInfo->localState = LSTATE_NONE; + } +} + +/* +------------------------- +NPC_Mouse_Precache +------------------------- +*/ +void NPC_Mouse_Precache( void ) +{ + int i; + + for (i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/mouse/misc/mousego%d.wav", i ) ); + } + + G_EffectIndex( "env/small_explode" ); + G_SoundIndex( "sound/chars/mouse/misc/death1" ); + G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" ); +} + +/* +------------------------- +NPC_R5D2_Precache +------------------------- +*/ +void NPC_R5D2_Precache(void) +{ + for ( int i = 1; i < 5; i++) + { + G_SoundIndex( va( "sound/chars/r5d2/misc/r5talk%d.wav", i ) ); + } + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); // ?? + G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" ); + G_EffectIndex( "env/med_explode"); + G_EffectIndex( "volumetric/droid_smoke" ); + G_EffectIndex( "chunks/r5d2head"); +} + +/* +------------------------- +NPC_R2D2_Precache +------------------------- +*/ +void NPC_R2D2_Precache(void) +{ + for ( int i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/r2d2/misc/r2d2talk0%d.wav", i ) ); + } + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); // ?? + G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" ); + G_EffectIndex( "env/med_explode"); +} + +/* +------------------------- +NPC_Gonk_Precache +------------------------- +*/ +void NPC_Gonk_Precache( void ) +{ + G_SoundIndex("sound/chars/gonk/misc/gonktalk1.wav"); + G_SoundIndex("sound/chars/gonk/misc/gonktalk2.wav"); + + G_SoundIndex("sound/chars/gonk/misc/death1.wav"); + G_SoundIndex("sound/chars/gonk/misc/death2.wav"); + G_SoundIndex("sound/chars/gonk/misc/death3.wav"); + + G_EffectIndex( "env/med_explode"); +} + +/* +------------------------- +NPC_Protocol_Precache +------------------------- +*/ +void NPC_Protocol_Precache( void ) +{ + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); + G_EffectIndex( "env/med_explode"); +} + +/* +static void R5D2_OffsetLook( float offset, vec3_t out ) +{ + vec3_t angles, forward, temp; + + GetAnglesForDirection( NPC->currentOrigin, NPCInfo->investigateGoal, angles ); + angles[YAW] += offset; + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( NPC->currentOrigin, 64, forward, out ); + + CalcEntitySpot( NPC, SPOT_HEAD, temp ); + out[2] = temp[2]; +} +*/ + +/* +------------------------- +R5D2_LookAround +------------------------- +*/ +/* +static void R5D2_LookAround( void ) +{ + vec3_t lookPos; + float perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime; + + //Keep looking at the spot + if ( perc < 0.25 ) + { + VectorCopy( NPCInfo->investigateGoal, lookPos ); + } + else if ( perc < 0.5f ) //Look up but straight ahead + { + R5D2_OffsetLook( 0.0f, lookPos ); + } + else if ( perc < 0.75f ) //Look right + { + R5D2_OffsetLook( 45.0f, lookPos ); + } + else //Look left + { + R5D2_OffsetLook( -45.0f, lookPos ); + } + + NPC_FacePosition( lookPos ); +} + +*/ + +/* +------------------------- +NPC_BSDroid_Default +------------------------- +*/ +void NPC_BSDroid_Default( void ) +{ + + if ( NPCInfo->localState == LSTATE_SPINNING ) + { + Droid_Spin(); + } + else if ( NPCInfo->localState == LSTATE_PAIN ) + { + Droid_Pain(); + } + else if ( NPCInfo->localState == LSTATE_DROP ) + { + NPC_UpdateAngles( qtrue, qtrue ); + ucmd.upmove = crandom() * 64; + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Droid_Patrol(); + } + else + { + Droid_Run(); + } +} diff --git a/code/game/AI_GalakMech.cpp b/code/game/AI_GalakMech.cpp new file mode 100644 index 0000000..dd7d2a4 --- /dev/null +++ b/code/game/AI_GalakMech.cpp @@ -0,0 +1,743 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN SOFTWARE - STAR WARS: JK III +// (c) 2002 Activision +// +// April 3, 2003 - This file has been commandeered for use by AI vehicle pilots. +// +//////////////////////////////////////////////////////////////////////////////////////// +#include "g_headers.h" + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#include "b_local.h" +#include "anims.h" +#include "g_navigator.h" +#include "g_Vehicles.h" +#if !defined(RATL_VECTOR_VS_INC) + #include "..\Ratl\vector_vs.h" +#endif + + +//////////////////////////////////////////////////////////////////////////////////////// +// Defines +//////////////////////////////////////////////////////////////////////////////////////// +#define MAX_VEHICLES_REGISTERED 100 + +#define ATTACK_FWD 0.95f +#define ATTACK_SIDE 0.20f +#define AIM_SIDE 0.60f +#define FUTURE_PRED_DIST 20.0f +#define FUTURE_SIDE_DIST 60.0f +#define ATTACK_FLANK_SLOWING 1000.0f +#define RAM_DIST 150.0f +#define MIN_STAY_VIEWABLE_TIME 20000 + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Externs +//////////////////////////////////////////////////////////////////////////////////////// +extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent ); +extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast ); + + + +trace_t mPilotViewTrace; +int mPilotViewTraceCount; +int mActivePilotCount; +ratl::vector_vs mRegistered; + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Pilot_Reset(void) +{ + mPilotViewTraceCount = 0; + mActivePilotCount = 0; + mRegistered.clear(); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +int Pilot_ActivePilotCount() +{ + return mActivePilotCount; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Pilot_Update(void) +{ + mActivePilotCount = 0; + mRegistered.clear(); + for (int i=0; igreetEnt && + g_entities[i].NPC->greetEnt->owner==(&g_entities[i]) + ) + { + mActivePilotCount++; + } + if ( g_entities[i].inuse && + g_entities[i].client && + g_entities[i].m_pVehicle && + !g_entities[i].owner && + g_entities[i].health>0 && + g_entities[i].m_pVehicle->m_pVehicleInfo->type==VH_SPEEDER && + !mRegistered.full()) + { + mRegistered.push_back(&g_entities[i]); + } + + } + + + if (player && + player->inuse && + TIMER_Done(player, "FlybySoundArchitectureDebounce")) + { + TIMER_Set(player, "FlybySoundArchitectureDebounce", 300); + + Vehicle_t* pVeh = G_IsRidingVehicle(player); + + if (pVeh && + (pVeh->m_pVehicleInfo->soundFlyBy || pVeh->m_pVehicleInfo->soundFlyBy2) && + //fabsf(pVeh->m_pParentEntity->currentAngles[2])<15.0f && + VectorLength(pVeh->m_pParentEntity->client->ps.velocity)>500.0f) + { + vec3_t projectedPosition; + vec3_t projectedDirection; + vec3_t projectedRight; + vec3_t anglesNoRoll; + + VectorCopy(pVeh->m_pParentEntity->currentAngles, anglesNoRoll); + anglesNoRoll[2] = 0; + AngleVectors(anglesNoRoll, projectedDirection, projectedRight, 0); + + VectorMA(player->currentOrigin, 1.2f, pVeh->m_pParentEntity->client->ps.velocity, projectedPosition); + VectorMA(projectedPosition, Q_flrand(-200.0f, 200.0f), projectedRight, projectedPosition); + + gi.trace(&mPilotViewTrace, + player->currentOrigin, + 0, + 0, + projectedPosition, + player->s.number, + MASK_SHOT); + + if ((mPilotViewTrace.allsolid==qfalse) && + (mPilotViewTrace.startsolid==qfalse) && + (mPilotViewTrace.fraction<0.99f) && + (mPilotViewTrace.plane.normal[2]<0.5f) && + (DotProduct(projectedDirection, mPilotViewTrace.plane.normal)<-0.5f) + ) + { + // CG_DrawEdge(player->currentOrigin, mPilotViewTrace.endpos, EDGE_IMPACT_POSSIBLE); + TIMER_Set(player, "FlybySoundArchitectureDebounce", Q_irand(1000, 2000)); + + int soundFlyBy = pVeh->m_pVehicleInfo->soundFlyBy; + if (pVeh->m_pVehicleInfo->soundFlyBy2 && (!soundFlyBy || !Q_irand(0,1))) + { + soundFlyBy = pVeh->m_pVehicleInfo->soundFlyBy2; + } + G_SoundAtSpot(mPilotViewTrace.endpos, soundFlyBy, qtrue); + } + else + { + // CG_DrawEdge(player->currentOrigin, mPilotViewTrace.endpos, EDGE_IMPACT_SAFE); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool Pilot_AnyVehiclesRegistered() +{ + return (!mRegistered.empty()); +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Vehicle Registration +// +// Any vehicles that can be ridden by NPCs should be registered here +// +//////////////////////////////////////////////////////////////////////////////////////// +void Vehicle_Register(gentity_t *ent) +{ +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Vehicle Remove From The List Of Valid +//////////////////////////////////////////////////////////////////////////////////////// +void Vehicle_Remove(gentity_t *ent) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Vehicle_Find +// +// Will look through all registered vehicles and choose the closest one that the given +// entity can get to. +// +//////////////////////////////////////////////////////////////////////////////////////// +gentity_t* Vehicle_Find(gentity_t *ent) +{ + gentity_t* closest = 0; + float closestDist = 0; + float curDist = 0; + + + for (int i=0; iowner) + { + curDist = Distance(mRegistered[i]->currentOrigin, ent->currentOrigin); + if (curDist<1000 && (!closest || curDistenemy) + { + // If Still On A Vehicle, Jump Off + //--------------------------------- + if (NPCInfo->greetEnt) + { + ucmd.upmove = 128.0f; + + if (NPCInfo->greetEnt && NPCInfo->greetEnt->m_pVehicle && level.timeconfusionTime) + { + Vehicle_t* pVeh = NPCInfo->greetEnt->m_pVehicle; + if (!(pVeh->m_ulFlags&VEH_OUTOFCONTROL)) + { + gentity_t* parent = pVeh->m_pParentEntity; + float CurSpeed = VectorLength(parent->client->ps.velocity); + pVeh->m_pVehicleInfo->StartDeathDelay(pVeh, 10000); + pVeh->m_ulFlags |= (VEH_OUTOFCONTROL); + VectorScale(parent->client->ps.velocity, 1.25f, parent->pos3); + if (CurSpeedm_pVehicleInfo->speedMax) + { + VectorNormalize(parent->pos3); + if (fabsf(parent->pos3[2])<0.25f) + { + VectorScale(parent->pos3, (pVeh->m_pVehicleInfo->speedMax * 1.25f), parent->pos3); + } + else + { + VectorScale(parent->client->ps.velocity, 1.25f, parent->pos3); + } + } + } + } + + if (NPCInfo->greetEnt->owner==NPC) + { + return true; + } + NPCInfo->greetEnt = 0; + } + + // Otherwise Nothing To See Here + //------------------------------- + return false; + } + + + // If We Already Have A Target Vehicle, Make Sure It Is Still Valid + //------------------------------------------------------------------ + if (NPCInfo->greetEnt) + { + if (!NPCInfo->greetEnt->inuse || + !NPCInfo->greetEnt->m_pVehicle || + !NPCInfo->greetEnt->m_pVehicle->m_pVehicleInfo) + { + NPCInfo->greetEnt = Vehicle_Find(NPC); + } + else + { + if (NPCInfo->greetEnt->owner && NPCInfo->greetEnt->owner!=NPC) + { + NPCInfo->greetEnt = Vehicle_Find(NPC); + } + } + } + + // If We Have An Enemy, Try To Find A Vehicle Nearby + //--------------------------------------------------- + else + { + NPCInfo->greetEnt = Vehicle_Find(NPC); + } + + // If No Vehicle Available, Continue As Usual + //-------------------------------------------- + if (!NPCInfo->greetEnt) + { + return false; + } + + + + if (NPCInfo->greetEnt->owner==NPC) + { + Pilot_Steer_Vehicle(); + } + else + { + Pilot_Goto_Vehicle(); + } + + Pilot_Update_Enemy(); + return true; +} + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Pilot_Update_Enemy() +{ + if (!TIMER_Exists(NPC, "PilotRemoveTime")) + { + TIMER_Set(NPC, "PilotRemoveTime", MIN_STAY_VIEWABLE_TIME); + } + + if (TIMER_Done(NPC, "NextPilotCheckEnemyTime")) + { + TIMER_Set(NPC, "NextPilotCheckEnemyTime", Q_irand(1000,2000)); + if (NPC->enemy && Distance(NPC->currentOrigin, NPC->enemy->currentOrigin)>1000.0f) + { + mPilotViewTraceCount ++; + gi.trace(&mPilotViewTrace, + NPC->currentOrigin, + 0, + 0, + NPC->enemy->currentOrigin, + NPC->s.number, + MASK_SHOT); + + if ((mPilotViewTrace.allsolid==qfalse) && + (mPilotViewTrace.startsolid==qfalse ) && + ((mPilotViewTrace.entityNum==NPC->enemy->s.number)||(mPilotViewTrace.entityNum==NPC->enemy->s.m_iVehicleNum))) + { + TIMER_Set(NPC, "PilotRemoveTime", MIN_STAY_VIEWABLE_TIME); + } + } + else + { + TIMER_Set(NPC, "PilotRemoveTime", MIN_STAY_VIEWABLE_TIME); + } + } + + if (TIMER_Done(NPC, "PilotRemoveTime")) + { + if (NPCInfo->greetEnt->owner==NPC) + { + NPCInfo->greetEnt->e_ThinkFunc = thinkF_G_FreeEntity; + NPCInfo->greetEnt->nextthink = level.time; + } + NPC->e_ThinkFunc = thinkF_G_FreeEntity; + NPC->nextthink = level.time; + } +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Pilot_Goto_Vehicle() +{ + STEER::Activate(NPC); + { + if (STEER::Reached(NPC, NPCInfo->greetEnt, 80.0f)) + { + NPC_Use(NPCInfo->greetEnt, NPC, NPC); + } + else if (NAV::OnNeighboringPoints(NPC, NPCInfo->greetEnt)) + { + STEER::Persue(NPC, NPCInfo->greetEnt, 50.0f, 0.0f, 30.0f, 0.0f, true); + } + else + { + if (!NAV::GoTo(NPC, NPCInfo->greetEnt)) + { + STEER::Stop(NPC); + } + } + } + STEER::AvoidCollisions(NPC); + STEER::DeActivate(NPC, &ucmd); + NPC_UpdateAngles(qtrue, qtrue); +} + +extern bool VEH_StartStrafeRam(Vehicle_t *pVeh, bool Right); + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Pilot_Steer_Vehicle() +{ + if (!NPC->enemy || !NPC->enemy->client) + { + return; + } + + + + + + +// SETUP +//======= + // Setup Actor Data + //------------------ + CVec3 ActorPos(NPC->currentOrigin); + CVec3 ActorAngles(NPC->currentAngles); + ActorAngles[2] = 0; + Vehicle_t* ActorVeh = NPCInfo->greetEnt->m_pVehicle; + bool ActorInTurbo = (ActorVeh->m_iTurboTime>level.time); + float ActorSpeed = (ActorVeh)?(VectorLength(ActorVeh->m_pParentEntity->client->ps.velocity)):(NPC->client->ps.speed); + + + // If my vehicle is spinning out of control, just hold on, we're going to die!!!!! + //--------------------------------------------------------------------------------- + if (ActorVeh && (ActorVeh->m_ulFlags & VEH_OUTOFCONTROL)) + { + if (NPC->client->ps.weapon!=WP_NONE) + { + NPC_ChangeWeapon(WP_NONE); + } + ucmd.buttons &=~BUTTON_ATTACK; + ucmd.buttons &=~BUTTON_ALT_ATTACK; + return; + } + + CVec3 ActorDirection; + AngleVectors(ActorAngles.v, ActorDirection.v, 0, 0); + + CVec3 ActorFuturePos(ActorPos); + ActorFuturePos.ScaleAdd(ActorDirection, FUTURE_PRED_DIST); + + bool ActorDoTurbo = false; + bool ActorAccelerate = false; + bool ActorAimAtTarget= true; + float ActorYawOffset = 0.0f; + + + // Setup Enemy Data + //------------------ + CVec3 EnemyPos(NPC->enemy->currentOrigin); + CVec3 EnemyAngles(NPC->enemy->currentAngles); + EnemyAngles[2] = 0; + Vehicle_t* EnemyVeh = (NPC->enemy->s.m_iVehicleNum)?(g_entities[NPC->enemy->s.m_iVehicleNum].m_pVehicle):(0); + bool EnemyInTurbo = (EnemyVeh && EnemyVeh->m_iTurboTime>level.time); + float EnemySpeed = (EnemyVeh)?(EnemyVeh->m_pParentEntity->client->ps.speed):(NPC->enemy->resultspeed); + bool EnemySlideBreak = (EnemyVeh && (EnemyVeh->m_ulFlags&VEH_SLIDEBREAKING || EnemyVeh->m_ulFlags&VEH_STRAFERAM)); + bool EnemyDead = (NPC->enemy->health<=0); + + bool ActorFlank = (NPCInfo->lastAvoidSteerSideDebouncer>level.time && EnemyVeh && EnemySpeed>10.0f); + + CVec3 EnemyDirection; + CVec3 EnemyRight; + AngleVectors(EnemyAngles.v, EnemyDirection.v, EnemyRight.v, 0); + + CVec3 EnemyFuturePos(EnemyPos); + EnemyFuturePos.ScaleAdd(EnemyDirection, FUTURE_PRED_DIST); + + ESide EnemySide = ActorPos.LRTest(EnemyPos, EnemyFuturePos); + CVec3 EnemyFlankPos(EnemyFuturePos); + EnemyFlankPos.ScaleAdd(EnemyRight, (EnemySide==Side_Right)?(FUTURE_SIDE_DIST):(-FUTURE_SIDE_DIST)); + + // Debug Draw Enemy Data + //----------------------- + if (false) + { + CG_DrawEdge(EnemyPos.v, EnemyFuturePos.v, EDGE_IMPACT_SAFE); + CG_DrawEdge(EnemyFuturePos.v, EnemyFlankPos.v, EDGE_IMPACT_SAFE); + } + + + // Setup Move And Aim Directions + //------------------------------- + CVec3 MoveDirection((ActorFlank)?(EnemyFlankPos):(EnemyFuturePos)); + MoveDirection -= ActorPos; + float MoveDistance = MoveDirection.SafeNorm(); + float MoveAccuracy = MoveDirection.Dot(ActorDirection); + + CVec3 AimDirection(EnemyPos); + AimDirection -= ActorPos; + float AimDistance = AimDirection.SafeNorm(); + float AimAccuracy = AimDirection.Dot(ActorDirection); + + + + if (!ActorFlank && TIMER_Done(NPC, "FlankAttackCheck")) + { + TIMER_Set(NPC, "FlankAttackCheck", Q_irand(1000, 3000)); + if (MoveDistance<4000 && Q_irand(0, 1)==0) + { + NPCInfo->lastAvoidSteerSideDebouncer = level.time + Q_irand(8000, 14000); + } + } + + + + // Fly By Sounds + //--------------- + if ((ActorVeh->m_pVehicleInfo->soundFlyBy || ActorVeh->m_pVehicleInfo->soundFlyBy2) && + EnemyVeh && + MoveDistance<800 && + ActorSpeed>500.0f && + TIMER_Done(NPC, "FlybySoundDebouncer") + ) + { + if (EnemySpeed<100.0f || (ActorDirection.Dot(EnemyDirection)*(MoveDistance/800.0f))<-0.5f) + { + TIMER_Set(NPC, "FlybySoundDebouncer", 2000); + int soundFlyBy = ActorVeh->m_pVehicleInfo->soundFlyBy; + if (ActorVeh->m_pVehicleInfo->soundFlyBy2 && (!soundFlyBy || !Q_irand(0,1))) + { + soundFlyBy = ActorVeh->m_pVehicleInfo->soundFlyBy2; + } + G_Sound(ActorVeh->m_pParentEntity, soundFlyBy); + } + } + + + +// FLY PAST BEHAVIOR +//=================== + if (EnemySlideBreak || !TIMER_Done(NPC, "MinHoldDirectionTime")) + { + if (TIMER_Done(NPC, "MinHoldDirectionTime")) + { + TIMER_Set(NPC, "MinHoldDirectionTime", 500); // Hold For At Least 500 ms + } + ActorAccelerate = true; // Go + ActorAimAtTarget = false; // Don't Alter Our Aim Direction + ucmd.buttons &=~BUTTON_VEH_SPEED; // Let Normal Vehicle Controls Go + } + + +// FLANKING BEHAVIOR +//=================== + else if (ActorFlank) + { + ActorAccelerate = true; + ActorDoTurbo = (MoveDistance>2500 || EnemyInTurbo); + ucmd.buttons |= BUTTON_VEH_SPEED; // Tells PMove to use the ps.speed we calculate here, not the one from g_vehicles.c + + + // For Flanking, We Calculate The Speed By Hand, Rather Than Using Pure Accelerate / No Accelerate Functionality + //--------------------------------------------------------------------------------------------------------------- + NPC->client->ps.speed = ActorVeh->m_pVehicleInfo->speedMax * ((ActorInTurbo)?(1.35f):(1.15f)); + + + // If In Slowing Distance, Scale Down The Speed As We Approach Our Move Target + //----------------------------------------------------------------------------- + if (MoveDistanceclient->ps.speed *= (MoveDistance/ATTACK_FLANK_SLOWING); + NPC->client->ps.speed += EnemySpeed; + + // Match Enemy Speed + //------------------- + if (NPC->client->ps.speed<5.0f && EnemySpeed<5.0f) + { + NPC->client->ps.speed = EnemySpeed; + } + + // Extra Slow Down When Out In Front + //----------------------------------- + if (MoveAccuracy<0.0f) + { + NPC->client->ps.speed *= (MoveAccuracy + 1.0f); + } + + + MoveDirection *= (MoveDistance/ATTACK_FLANK_SLOWING); + EnemyDirection *= 1.0f - (MoveDistance/ATTACK_FLANK_SLOWING); + MoveDirection += EnemyDirection; + + if (TIMER_Done(NPC, "RamCheck")) + { + TIMER_Set(NPC, "RamCheck", Q_irand(1000, 3000)); + if (MoveDistance0.99f && MoveDistance<500 && !EnemyDead) + { + ActorAccelerate = true; + ActorDoTurbo = false; + } + else + { + ActorAccelerate = ((MoveDistance>500 && EnemySpeed>20.0f) || MoveDistance>1000); + ActorDoTurbo = (MoveDistance>3000 && EnemySpeed>20.0f); + } + ucmd.buttons &=~BUTTON_VEH_SPEED; + } + + + + +// APPLY RESULTS +//======================= + // Decide Turbo + //-------------- + if (ActorDoTurbo || ActorInTurbo) + { + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + else + { + ucmd.buttons &=~BUTTON_ALT_ATTACK; + } + + // Decide Acceleration + //--------------------- + ucmd.forwardmove = (ActorAccelerate)?(127):(0); + + + + // Decide To Shoot + //----------------- + ucmd.buttons &=~BUTTON_ATTACK; + ucmd.rightmove = 0; + if (AimDistance<2000 && !EnemyDead) + { + // If Doing A Ram Attack + //----------------------- + if (ActorYawOffset!=0) + { + if (NPC->client->ps.weapon!=WP_NONE) + { + NPC_ChangeWeapon(WP_NONE); + } + ucmd.buttons &=~BUTTON_ATTACK; + } + else if (AimAccuracy>ATTACK_FWD) + { + if (NPC->client->ps.weapon!=WP_NONE) + { + NPC_ChangeWeapon(WP_NONE); + } + ucmd.buttons |= BUTTON_ATTACK; + } + else if (AimAccuracy-AIM_SIDE) + { + if (NPC->client->ps.weapon!=WP_BLASTER) + { + NPC_ChangeWeapon(WP_BLASTER); + } + + if (AimAccuracy-ATTACK_SIDE) + { + //if (!TIMER_Done(NPC, "RiderAltAttack")) + //{ + // ucmd.buttons |= BUTTON_ALT_ATTACK; + //} + //else + //{ + ucmd.buttons |= BUTTON_ATTACK; + + /* if (TIMER_Done(NPC, "RiderAltAttackCheck")) + { + TIMER_Set(NPC, "RiderAltAttackCheck", Q_irand(1000, 3000)); + if (Q_irand(0, 2)==0) + { + TIMER_Set(NPC, "RiderAltAttack", 300); + } + }*/ + //} + WeaponThink(true); + } + ucmd.rightmove = (EnemySide==Side_Left)?( 127):(-127); + } + else + { + if (NPC->client->ps.weapon!=WP_NONE) + { + NPC_ChangeWeapon(WP_NONE); + } + } + } + else + { + if (NPC->client->ps.weapon!=WP_NONE) + { + NPC_ChangeWeapon(WP_NONE); + } + } + + + // Aim At Target + //--------------- + if (ActorAimAtTarget) + { + MoveDirection.VecToAng(); + NPCInfo->desiredPitch = AngleNormalize360(MoveDirection[PITCH]); + NPCInfo->desiredYaw = AngleNormalize360(MoveDirection[YAW] + ActorYawOffset); + } + NPC_UpdateAngles(qtrue, qtrue); +} + diff --git a/code/game/AI_Glider.cpp b/code/game/AI_Glider.cpp new file mode 100644 index 0000000..d769cd5 --- /dev/null +++ b/code/game/AI_Glider.cpp @@ -0,0 +1,3 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + diff --git a/code/game/AI_Grenadier.cpp b/code/game/AI_Grenadier.cpp new file mode 100644 index 0000000..9f5f930 --- /dev/null +++ b/code/game/AI_Grenadier.cpp @@ -0,0 +1,669 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +#include "g_navigator.h" + +extern void CG_DrawAlert( vec3_t origin, float rating ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern void NPC_AimAdjust( int change ); +extern qboolean FlyingCreature( gentity_t *ent ); + + +#define MAX_VIEW_DIST 1024 +#define MAX_VIEW_SPEED 250 +#define MAX_LIGHT_INTENSITY 255 +#define MIN_LIGHT_THRESHOLD 0.1 + +#define DISTANCE_SCALE 0.25f +#define DISTANCE_THRESHOLD 0.075f +#define SPEED_SCALE 0.25f +#define FOV_SCALE 0.5f +#define LIGHT_SCALE 0.25f + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 ) + +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS; +static qboolean enemyCS; +static qboolean faceEnemy; +static qboolean move; +static qboolean shoot; +static float enemyDist; + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void Grenadier_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "duck", 0 ); + TIMER_Set( ent, "stand", 0 ); + TIMER_Set( ent, "shuffleTime", 0 ); + TIMER_Set( ent, "sleepTime", 0 ); + TIMER_Set( ent, "enemyLastVisible", 0 ); + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "hideTime", 0 ); + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "stick", 0 ); + TIMER_Set( ent, "scoutTime", 0 ); + TIMER_Set( ent, "flee", 0 ); +} + +void NPC_Grenadier_PlayConfusionSound( gentity_t *self ) +{//FIXME: make this a custom sound in sound set + if ( self->health > 0 ) + { + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + TIMER_Set( self, "flee", 0 ); + self->NPC->squadState = SQUAD_IDLE; + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} + + +/* +------------------------- +NPC_ST_Pain +------------------------- +*/ + +void NPC_Grenadier_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod ) +{ + self->NPC->localState = LSTATE_UNDERFIRE; + + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stand", 2000 ); + + NPC_Pain( self, inflictor, other, point, damage, mod ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } +} + +/* +------------------------- +ST_HoldPosition +------------------------- +*/ + +static void Grenadier_HoldPosition( void ) +{ + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + NPCInfo->goalEntity = NULL; + + /*if ( TIMER_Done( NPC, "stand" ) ) + {//FIXME: what if can't shoot from this pos? + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +/* +------------------------- +ST_Move +------------------------- +*/ + +static qboolean Grenadier_Move( void ) +{ + NPCInfo->combatMove = qtrue;//always move straight toward our goal + + qboolean moved = NPC_MoveToGoal( qtrue ); +// navInfo_t info; + + //Get the move info +// NAV_GetLastMove( info ); + + //FIXME: if we bump into another one of our guys and can't get around him, just stop! + //If we hit our target, then stop and fire! +// if ( info.flags & NIF_COLLISION ) +// { +// if ( info.blocker == NPC->enemy ) +// { +// Grenadier_HoldPosition(); +// } +// } + + //If our move failed, then reset + if ( moved == qfalse ) + {//couldn't get to enemy + if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPC->client->ps.weapon == WP_THERMAL && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) + {//we were running after enemy + //Try to find a combat point that can hit the enemy + int cpFlags = (CP_CLEAR|CP_HAS_ROUTE); + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + int cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, cpFlags, 32 ); + if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + {//okay, try one by the enemy + cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32 ); + } + //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him... + if ( cp != -1 ) + {//found a combat point that has a clear shot to enemy + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp ); + return moved; + } + } + //just hang here + Grenadier_HoldPosition(); + } + + return moved; +} + +/* +------------------------- +NPC_BSGrenadier_Patrol +------------------------- +*/ + +void NPC_BSGrenadier_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be automatic now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + //Is there danger nearby + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); + if ( NPC_CheckForDanger( alertEvent ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + {//check for other alert events + //There is an event to look at + if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; + if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED ) + { + if ( level.alertEvents[alertEvent].owner && + level.alertEvents[alertEvent].owner->client && + level.alertEvents[alertEvent].owner->health >= 0 && + level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + //NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + } + } + else + {//FIXME: get more suspicious over time? + //Save the position for movement (if necessary) + VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); + NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); + if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) + {//suspicious looks longer + NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); + } + } + } + } + + if ( NPCInfo->investigateDebounceTime > level.time ) + {//FIXME: walk over to it, maybe? Not if not chase enemies + //NOTE: stops walking or doing anything else below + vec3_t dir, angles; + float o_yaw, o_pitch; + + VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); + vectoangles( dir, angles ); + + o_yaw = NPCInfo->desiredYaw; + o_pitch = NPCInfo->desiredPitch; + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + NPC_UpdateAngles( qtrue, qtrue ); + + NPCInfo->desiredYaw = o_yaw; + NPCInfo->desiredPitch = o_pitch; + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSGrenadier_Idle +------------------------- +*/ +/* +void NPC_BSGrenadier_Idle( void ) +{ + //FIXME: check for other alert events? + + //Is there danger nearby? + if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) ); + + NPC_UpdateAngles( qtrue, qtrue ); +} +*/ +/* +------------------------- +ST_CheckMoveState +------------------------- +*/ + +static void Grenadier_CheckMoveState( void ) +{ + //See if we're a scout + if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//behaviorState == BS_STAND_AND_SHOOT ) + { + if ( NPCInfo->goalEntity == NPC->enemy ) + { + move = qfalse; + return; + } + } + //See if we're running away + else if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + if ( TIMER_Done( NPC, "flee" ) ) + { + NPCInfo->squadState = SQUAD_IDLE; + } + else + { + faceEnemy = qfalse; + } + } + /* + else if ( NPCInfo->squadState == SQUAD_IDLE ) + { + if ( !NPCInfo->goalEntity ) + { + move = qfalse; + return; + } + //Should keep moving toward player when we're out of range... right? + } + */ + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( STEER::Reached(NPC, NPCInfo->goalEntity, 16, !!FlyingCreature(NPC)) || + ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) ) + { + int newSquadState = SQUAD_STAND_AND_SHOOT; + //we got where we wanted to go, set timers based on why we were running + switch ( NPCInfo->squadState ) + { + case SQUAD_RETREAT://was running away + TIMER_Set( NPC, "duck", (NPC->max_health - NPC->health) * 100 ); + TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); + newSquadState = SQUAD_COVER; + break; + case SQUAD_TRANSITION://was heading for a combat point + TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); + break; + case SQUAD_SCOUT://was running after player + break; + default: + break; + } + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels + //don't do something else just yet + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); + //stop fleeing + if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + TIMER_Set( NPC, "flee", -level.time ); + NPCInfo->squadState = SQUAD_IDLE; + } + return; + } + + //keep going, hold of roamTimer until we get there + TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); + } + + if ( !NPCInfo->goalEntity ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = (NPC->maxs[0]*1.5f); + } + } +} + +/* +------------------------- +ST_CheckFireState +------------------------- +*/ + +static void Grenadier_CheckFireState( void ) +{ + if ( enemyCS ) + {//if have a clear shot, always try + return; + } + + if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT ) + {//runners never try to fire at the last pos + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + //continue to fire on their last position + /* + if ( !Q_irand( 0, 1 ) && NPCInfo->enemyLastSeenTime && level.time - NPCInfo->enemyLastSeenTime < 4000 ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + + VectorNormalize( dir ); + + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + //FIXME: they always throw toward enemy, so this will be very odd... + shoot = qtrue; + faceEnemy = qfalse; + + return; + } + */ +} + +qboolean Grenadier_EvaluateShot( int hit ) +{ + if ( !NPC->enemy ) + { + return qfalse; + } + + if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].svFlags&SVF_GLASS_BRUSH)) ) + {//can hit enemy or will hit glass, so shoot anyway + return qtrue; + } + return qfalse; +} + +/* +------------------------- +NPC_BSGrenadier_Attack +------------------------- +*/ + +void NPC_BSGrenadier_Attack( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //NPC_CheckEnemy( qtrue, qfalse ); + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )// + { + NPC_BSGrenadier_Patrol();//FIXME: or patrol? + return; + } + + if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//going to run + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( !NPC->enemy ) + {//WTF? somehow we lost our enemy? + NPC_BSGrenadier_Patrol();//FIXME: or patrol? + return; + } + + enemyLOS = enemyCS = qfalse; + move = qtrue; + faceEnemy = qfalse; + shoot = qfalse; + enemyDist = DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ); + + //See if we should switch to melee attack + if ( enemyDist < 16384 && (!NPC->enemy->client||NPC->enemy->client->ps.weapon != WP_SABER||(!NPC->enemy->client->ps.SaberActive())) )//128 + {//enemy is close and not using saber + if ( NPC->client->ps.weapon == WP_THERMAL ) + {//grenadier + trace_t trace; + gi.trace ( &trace, NPC->currentOrigin, NPC->enemy->mins, NPC->enemy->maxs, NPC->enemy->currentOrigin, NPC->s.number, NPC->enemy->clipmask ); + if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->enemy->s.number ) ) + {//I can get right to him + //reset fire-timing variables + NPC_ChangeWeapon( WP_MELEE ); + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT ) + {//FIXME: should we be overriding scriptFlags? + NPCInfo->scriptFlags |= SCF_CHASE_ENEMIES;//NPCInfo->behaviorState = BS_HUNT_AND_KILL; + } + } + } + } + else if ( enemyDist > 65536 || (NPC->enemy->client && NPC->enemy->client->ps.weapon == WP_SABER && NPC->enemy->client->ps.SaberActive()) )//256 + {//enemy is far or using saber + if ( NPC->client->ps.weapon == WP_MELEE && (NPC->client->ps.stats[STAT_WEAPONS]&(1<enemy ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + enemyLOS = qtrue; + + if ( NPC->client->ps.weapon == WP_MELEE ) + { + if ( enemyDist <= 4096 && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 90, 45 ) )//within 64 & infront + { + VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); + enemyCS = qtrue; + } + } + else if ( InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 45, 90 ) ) + {//in front of me + //can we shoot our target? + //FIXME: how accurate/necessary is this check? + int hit = NPC_ShotEntity( NPC->enemy ); + gentity_t *hitEnt = &g_entities[hit]; + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) ) + { + VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); + float enemyHorzDist = DistanceHorizontalSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ); + if ( enemyHorzDist < 1048576 ) + {//within 1024 + enemyCS = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + } + else + { + NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + } + } + } + } + else + { + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + /* + else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + } + */ + + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + + if ( enemyCS ) + { + shoot = qtrue; + if ( NPC->client->ps.weapon == WP_THERMAL ) + {//don't chase and throw + move = qfalse; + } + else if ( NPC->client->ps.weapon == WP_MELEE && enemyDist < (NPC->maxs[0]+NPC->enemy->maxs[0]+16)*(NPC->maxs[0]+NPC->enemy->maxs[0]+16) ) + {//close enough + move = qfalse; + } + }//this should make him chase enemy when out of range...? + + //Check for movement to take care of + Grenadier_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + Grenadier_CheckFireState(); + + if ( move ) + {//move toward goal + if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared + { + move = Grenadier_Move(); + } + else + { + move = qfalse; + } + } + + if ( !move ) + { + if ( !TIMER_Done( NPC, "duck" ) ) + { + ucmd.upmove = -127; + } + //FIXME: what about leaning? + } + else + {//stop ducking! + TIMER_Set( NPC, "duck", -1 ); + } + + if ( !faceEnemy ) + {//we want to face in the dir we're running + if ( move ) + {//don't run away and shoot + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + shoot = qfalse; + } + NPC_UpdateAngles( qtrue, qtrue ); + } + else// if ( faceEnemy ) + {//face the enemy + NPC_FaceEnemy(); + } + + if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) + { + shoot = qfalse; + } + + //FIXME: don't shoot right away! + if ( shoot ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time ); + } + + } + } +} + +void NPC_BSGrenadier_Default( void ) +{ + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSGrenadier_Patrol(); + } + else//if ( NPC->enemy ) + {//have an enemy + NPC_BSGrenadier_Attack(); + } +} diff --git a/code/game/AI_HazardTrooper.cpp b/code/game/AI_HazardTrooper.cpp new file mode 100644 index 0000000..d57f2b5 --- /dev/null +++ b/code/game/AI_HazardTrooper.cpp @@ -0,0 +1,1571 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN SOFTWARE - STAR WARS: JK II +// (c) 2002 Activision +// +// Troopers +// +// TODO +// ---- +// +// +// +// +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +//////////////////////////////////////////////////////////////////////////////////////// +#include "g_headers.h" + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#include "b_local.h" +#if !defined(RAVL_VEC_INC) + #include "..\Ravl\CVec.h" +#endif +#if !defined(RATL_ARRAY_VS_INC) + #include "..\Ratl\array_vs.h" +#endif +#if !defined(RATL_VECTOR_VS_INC) + #include "..\Ratl\vector_vs.h" +#endif +#if !defined(RATL_HANDLE_POOL_VS_INC) + #include "..\Ratl\handle_pool_vs.h" +#endif +#if !defined(RUFL_HSTRING_INC) + #include "..\Rufl\hstring.h" +#endif + + +//////////////////////////////////////////////////////////////////////////////////////// +// Defines +//////////////////////////////////////////////////////////////////////////////////////// +#define MAX_TROOPS 100 +#define MAX_ENTS_PER_TROOP 7 +#define MAX_TROOP_JOIN_DIST2 1000000 //1000 units +#define MAX_TROOP_MERGE_DIST2 250000 //500 units +#define TARGET_POS_VISITED 10000 //100 units + + +bool NPC_IsTrooper(gentity_t* actor); + +enum +{ + SPEECH_CHASE, + SPEECH_CONFUSED, + SPEECH_COVER, + SPEECH_DETECTED, + SPEECH_GIVEUP, + SPEECH_LOOK, + SPEECH_LOST, + SPEECH_OUTFLANK, + SPEECH_ESCAPING, + SPEECH_SIGHT, + SPEECH_SOUND, + SPEECH_SUSPICIOUS, + SPEECH_YELL, + SPEECH_PUSHED +}; +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +static void HT_Speech( gentity_t *self, int speechType, float failChance ) +{ + if ( random() < failChance ) + { + return; + } + + if ( failChance >= 0 ) + {//a negative failChance makes it always talk + if ( self->NPC->group ) + {//group AI speech debounce timer + if ( self->NPC->group->speechDebounceTime > level.time ) + { + return; + } + /* + else if ( !self->NPC->group->enemy ) + { + if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time ) + { + return; + } + } + */ + } + else if ( !TIMER_Done( self, "chatter" ) ) + {//personal timer + return; + } + } + + TIMER_Set( self, "chatter", Q_irand( 2000, 4000 ) ); + + if ( self->NPC->blockedSpeechDebounceTime > level.time ) + { + return; + } + + switch( speechType ) + { + case SPEECH_CHASE: + G_AddVoiceEvent( self, Q_irand(EV_CHASE1, EV_CHASE3), 2000 ); + break; + case SPEECH_CONFUSED: + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + break; + case SPEECH_COVER: + G_AddVoiceEvent( self, Q_irand(EV_COVER1, EV_COVER5), 2000 ); + break; + case SPEECH_DETECTED: + G_AddVoiceEvent( self, Q_irand(EV_DETECTED1, EV_DETECTED5), 2000 ); + break; + case SPEECH_GIVEUP: + G_AddVoiceEvent( self, Q_irand(EV_GIVEUP1, EV_GIVEUP4), 2000 ); + break; + case SPEECH_LOOK: + G_AddVoiceEvent( self, Q_irand(EV_LOOK1, EV_LOOK2), 2000 ); + break; + case SPEECH_LOST: + G_AddVoiceEvent( self, EV_LOST1, 2000 ); + break; + case SPEECH_OUTFLANK: + G_AddVoiceEvent( self, Q_irand(EV_OUTFLANK1, EV_OUTFLANK2), 2000 ); + break; + case SPEECH_ESCAPING: + G_AddVoiceEvent( self, Q_irand(EV_ESCAPING1, EV_ESCAPING3), 2000 ); + break; + case SPEECH_SIGHT: + G_AddVoiceEvent( self, Q_irand(EV_SIGHT1, EV_SIGHT3), 2000 ); + break; + case SPEECH_SOUND: + G_AddVoiceEvent( self, Q_irand(EV_SOUND1, EV_SOUND3), 2000 ); + break; + case SPEECH_SUSPICIOUS: + G_AddVoiceEvent( self, Q_irand(EV_SUSPICIOUS1, EV_SUSPICIOUS5), 2000 ); + break; + case SPEECH_YELL: + G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 2000 ); + break; + case SPEECH_PUSHED: + G_AddVoiceEvent( self, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 2000 ); + break; + default: + break; + } + + self->NPC->blockedSpeechDebounceTime = level.time + 2000; +} + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Troop +// +// Troopers primarly derive their behavior from cooperation as a collective group of +// individuals. They join Troops, each of which has a leader responsible for direcing +// the movement of the rest of the group. +// +//////////////////////////////////////////////////////////////////////////////////////// +class CTroop +{ + //////////////////////////////////////////////////////////////////////////////////// + // Various Troop Wide Data + //////////////////////////////////////////////////////////////////////////////////// + int mTroopHandle; + int mTroopTeam; + bool mTroopReform; + + float mFormSpacingFwd; + float mFormSpacingRight; + float mSurroundFanAngle; + +public: + bool Empty() {return mActors.empty();} + int Team() {return mTroopTeam;} + int Handle() {return mTroopHandle;} + + //////////////////////////////////////////////////////////////////////////////////// + // Initialize - Clear out all data, all actors, reset all variables + //////////////////////////////////////////////////////////////////////////////////// + void Initialize(int TroopHandle=0) + { + mActors.clear(); + mTarget = 0; + mState = TS_NONE; + mTroopHandle = TroopHandle; + mTroopTeam = 0; + mTroopReform = false; + } + //////////////////////////////////////////////////////////////////////////////////// + // DistanceSq - Quick Operation to see how far an ent is from the rest of the troop + //////////////////////////////////////////////////////////////////////////////////// + float DistanceSq(gentity_t* ent) + { + if (mActors.size()) + { + return DistanceSquared(ent->currentOrigin, mActors[0]->currentOrigin); + } + return 0.0f; + } + + + + + + + + + + +private: + //////////////////////////////////////////////////////////////////////////////////// + // The Actors + // + // Actors are all the troopers who belong to the group, their positions in this + // vector affect their positions in the troop, whith the first actor as the leader + //////////////////////////////////////////////////////////////////////////////////// + ratl::vector_vs mActors; + + //////////////////////////////////////////////////////////////////////////////////// + // MakeActorLeader - Move A Given Index To A Leader Position + //////////////////////////////////////////////////////////////////////////////////// + void MakeActorLeader(int index) + { + if (index!=0) + { + mActors[0]->client->leader = 0; + mActors.swap(index, 0); + } + mActors[0]->client->leader = mActors[0]; + if (mActors[0]) + { + if (mActors[0]->client->NPC_class==CLASS_HAZARD_TROOPER) + { + mFormSpacingFwd = 75.0f; + mFormSpacingRight = 50.0f; + } + else + { + mFormSpacingFwd = 75.0f; + mFormSpacingRight = 20.0f; + } + } + } + +public: + //////////////////////////////////////////////////////////////////////////////////// + // AddActor - Adds a new actor to the troop & automatically promote to leader + //////////////////////////////////////////////////////////////////////////////////// + void AddActor(gentity_t* actor) + { + assert(actor->NPC->troop==0 && !mActors.full()); + actor->NPC->troop = mTroopHandle; + mActors.push_back(actor); + mTroopReform = true; + if ((mActors.size()==1) || (actor->NPC->rank > mActors[0]->NPC->rank)) + { + MakeActorLeader(mActors.size()-1); + } + if (!mTroopTeam) + { + mTroopTeam = actor->client->playerTeam; + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // RemoveActor - Removes an actor from the troop & automatically promote leader + //////////////////////////////////////////////////////////////////////////////////// + void RemoveActor(gentity_t* actor) + { + assert(actor->NPC->troop==mTroopHandle); + int bestNewLeader=-1; + int numEnts = mActors.size(); + bool found = false; + mTroopReform = true; + + // Find The Actor + //---------------- + for (int i=0; i=0 && (mActors[i]->NPC->rank > mActors[bestNewLeader]->NPC->rank)) + { + bestNewLeader = i; + } + } + if (!mActors.empty() && bestNewLeader>=0) + { + MakeActorLeader(bestNewLeader); + } + + assert(found); + actor->NPC->troop = 0; + } + + + + + + + + + + + +private: + //////////////////////////////////////////////////////////////////////////////////// + // Enemy + // + // The troop has a collective enemy that it knows about, which is updated by all + // the members of the group; + //////////////////////////////////////////////////////////////////////////////////// + gentity_t* mTarget; + bool mTargetVisable; + int mTargetVisableStartTime; + int mTargetVisableStopTime; + CVec3 mTargetVisablePosition; + int mTargetIndex; + int mTargetLastKnownTime; + CVec3 mTargetLastKnownPosition; + bool mTargetLastKnownPositionVisited; + + //////////////////////////////////////////////////////////////////////////////////// + // RegisterTarget - Records That the target is seen, when and where + //////////////////////////////////////////////////////////////////////////////////// + void RegisterTarget(gentity_t* target, int index, bool visable) + { + if (!mTarget) + { + HT_Speech(mActors[0], SPEECH_DETECTED, 0); + } + else if ((level.time - mTargetLastKnownTime)>8000) + { + HT_Speech(mActors[0], SPEECH_SIGHT, 0); + } + + if (visable) + { + mTargetVisableStopTime = level.time; + if (!mTargetVisable) + { + mTargetVisableStartTime = level.time; + } + + CalcEntitySpot(target, SPOT_HEAD, mTargetVisablePosition.v); + mTargetVisablePosition[2] -= 10.0f; + } + + mTarget = target; + mTargetVisable = visable; + mTargetIndex = index; + mTargetLastKnownTime = level.time; + mTargetLastKnownPosition = target->currentOrigin; + mTargetLastKnownPositionVisited = false; + } + + //////////////////////////////////////////////////////////////////////////////////// + // RegisterTarget - Records That the target is seen, when and where + //////////////////////////////////////////////////////////////////////////////////// + bool TargetLastKnownPositionVisited() + { + if (!mTargetLastKnownPositionVisited) + { + float dist = DistanceSquared(mTargetLastKnownPosition.v, mActors[0]->currentOrigin); + mTargetLastKnownPositionVisited = (dist1.0f) + { + val = 1.0f; + } + if (val<0.0f) + { + val = 0.0f; + } + return val; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Target Visibility + // + // Compute all factors that can add visibility to a target + //////////////////////////////////////////////////////////////////////////////////// + float TargetVisibility(gentity_t* target) + { + float Scale = 0.8f; + if (target->client && target->client->ps.weapon==WP_SABER && target->client->ps.SaberActive()) + { + Scale += 0.1f; + } + return ClampScale(Scale); + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + float TargetNoiseLevel(gentity_t* target) + { + float Scale = 0.1f; + Scale += target->resultspeed / (float)g_speed->integer; + if (target->client && target->client->ps.weapon==WP_SABER && target->client->ps.SaberActive()) + { + Scale += 0.2f; + } + return ClampScale(Scale); + } + + + + //////////////////////////////////////////////////////////////////////////////////// + // Scan For Enemies + //////////////////////////////////////////////////////////////////////////////////// + void ScanForTarget(int scannerIndex) + { + gentity_t* target; + int targetIndex=0; + int targetStop=ENTITYNUM_WORLD; + CVec3 targetPos; + CVec3 targetDirection; + float targetDistance; + float targetVisibility; + float targetNoiseLevel; + + gentity_t* scanner = mActors[scannerIndex]; + gNPCstats_t* scannerStats = &(scanner->NPC->stats); + float scannerMaxViewDist = scannerStats->visrange; + float scannerMinVisability = 0.1f;//1.0f - scannerStats->vigilance; + float scannerMaxHearDist = scannerStats->earshot; + float scannerMinNoiseLevel = 0.3f;//1.0f - scannerStats->vigilance; + CVec3 scannerPos(scanner->currentOrigin); + CVec3 scannerFwd(scanner->currentAngles); + scannerFwd.AngToVec(); + + // If Existing Target, Only Check It + //----------------------------------- + if (mTarget) + { + targetIndex = mTargetIndex; + targetStop = mTargetIndex+1; + } + + SaveNPCGlobals(); + SetNPCGlobals(scanner); + + + for (; targetIndexcurrentOrigin; + if (target->client && target->client->ps.leanofs) + { + targetPos = target->client->renderInfo.eyePoint; + } + + targetDirection = (targetPos - scannerPos); + targetDistance = targetDirection.SafeNorm(); + + // Can The Scanner SEE The Target? + //--------------------------------- + if (targetDistancescannerMinVisability) + { + if (NPC_ClearLOS(targetPos.v)) + { + RegisterTarget(target, targetIndex, true); + RestoreNPCGlobals(); + return; + } + } + } + + // Can The Scanner HEAR The Target? + //---------------------------------- + if (targetDistancescannerMinNoiseLevel) + { + RegisterTarget(target, targetIndex, false); + RestoreNPCGlobals(); + return; + } + } + } + RestoreNPCGlobals(); + } + + + + + + + + +private: + //////////////////////////////////////////////////////////////////////////////////// + // Troop State + // + // The troop as a whole can be acting under a number of different "behavior states" + //////////////////////////////////////////////////////////////////////////////////// + enum ETroopState + { + TS_NONE = 0, // No troop wide activity active + + TS_ADVANCE, // CHOOSE A NEW ADVANCE TACTIC + TS_ADVANCE_REGROUP, // All ents move into squad position + TS_ADVANCE_SEARCH, // Slow advance, looking left to right, in formation + TS_ADVANCE_COVER, // One at a time moves forward, goes off path, provides cover + TS_ADVANCE_FORMATION, // In formation jog to goal location + + TS_ATTACK, // CHOOSE A NEW ATTACK TACTIC + TS_ATTACK_LINE, // Form 2 lines, front kneel, back stand + TS_ATTACK_FLANK, // Same As Line, except scouting group attemts to get around other side of target + TS_ATTACK_SURROUND, // Get on all sides of target + TS_ATTACK_COVER, // + + TS_MAX + }; + ETroopState mState; + + CVec3 mFormHead; + CVec3 mFormFwd; + CVec3 mFormRight; + + + //////////////////////////////////////////////////////////////////////////////////// + // TroopInFormation - A quick check to see if the troop is currently in formation + //////////////////////////////////////////////////////////////////////////////////// + bool TroopInFormation() + { + float maxActorRangeSq = ((mActors.size()/2) + 2) * mFormSpacingFwd; + maxActorRangeSq *= maxActorRangeSq; + for (int actorIndex=1; actorIndexmaxActorRangeSq) + { + return false; + } + } + return true; + } + + //////////////////////////////////////////////////////////////////////////////////// + // SActorOrder + //////////////////////////////////////////////////////////////////////////////////// + struct SActorOrder + { + CVec3 mPosition; + int mCombatPoint; + bool mKneelAndShoot; + }; + ratl::array_vs mOrders; + + + //////////////////////////////////////////////////////////////////////////////////// + // LeaderIssueAndUpdateOrders - Tell Everyone Where To Go + //////////////////////////////////////////////////////////////////////////////////// + void LeaderIssueAndUpdateOrders(ETroopState NextState) + { + int actorIndex; + int actorCount = mActors.size(); + + // Always Put Guys Closest To The Order Locations In Those Locations + //------------------------------------------------------------------- + for (int orderIndex=1; orderIndexcurrentOrigin); + float currentDistance = closestActorDistance; + for (actorIndex=orderIndex+1; actorIndexcurrentOrigin); + if (currentDistancepos1); + } + +// PHASE I - VOICE COMMANDS & ANIMATIONS +//======================================= + gentity_t* leader = mActors[0]; + + if (NextState!=mState) + { + if (mActors.size()>0) + { + switch (NextState) + { + case (TS_ADVANCE_REGROUP) : + { + break; + } + case (TS_ADVANCE_SEARCH) : + { + HT_Speech(leader, SPEECH_LOOK, 0); + break; + } + case (TS_ADVANCE_COVER) : + { + HT_Speech(leader, SPEECH_COVER, 0); + NPC_SetAnim(leader, SETANIM_TORSO, TORSO_HANDSIGNAL4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLDLESS); + break; + } + case (TS_ADVANCE_FORMATION) : + { + HT_Speech(leader, SPEECH_ESCAPING, 0); + break; + } + + + case (TS_ATTACK_LINE) : + { + HT_Speech(leader, SPEECH_CHASE, 0); + NPC_SetAnim(leader, SETANIM_TORSO, TORSO_HANDSIGNAL1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLDLESS); + break; + } + case (TS_ATTACK_FLANK) : + { + HT_Speech(leader, SPEECH_OUTFLANK, 0); + NPC_SetAnim(leader, SETANIM_TORSO, TORSO_HANDSIGNAL3, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLDLESS); + break; + } + case (TS_ATTACK_SURROUND) : + { + HT_Speech(leader, SPEECH_GIVEUP, 0); + NPC_SetAnim(leader, SETANIM_TORSO, TORSO_HANDSIGNAL2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLDLESS); + break; + } + case (TS_ATTACK_COVER) : + { + HT_Speech(leader, SPEECH_COVER, 0); + break; + } + default: + { + } + } + } + } + + // If Attacking, And Not Forced To Reform, Don't Recalculate Orders + //------------------------------------------------------------------ + else if (NextState>TS_ATTACK && !mTroopReform) + { + return; + } + + +// PHASE II - COMPUTE THE NEW FORMATION HEAD, FORWARD, AND RIGHT VECTORS +//======================================================================= + CVec3 PreviousFwd = mFormFwd; + + mFormHead = leader->currentOrigin; + mFormFwd = (NAV::HasPath(leader))?(NAV::NextPosition(leader)):(mTargetLastKnownPosition); + mFormFwd -= mFormHead; + mFormFwd[2] = 0; + mFormFwd *= -1.0f; // Form Forward Goes Behind The Leader + mFormFwd.Norm(); + + mFormRight = mFormFwd; + mFormRight.Cross(CVec3::mZ); + + + // Scale Vectors By Spacing Distances + //------------------------------------ + mFormFwd *= mFormSpacingFwd; + mFormRight *= mFormSpacingRight; + + // If Attacking, Move Head Forward Some To Center On Target + //---------------------------------------------------------- + if (NextState>TS_ATTACK) + { + if (!mTroopReform) + { + int FwdNum = ((actorCount/2)+1); + for (int i=0; icurrentOrigin, + mActors[0]->mins, + mActors[0]->maxs, + mOrders[0].mPosition.v, + mActors[0]->s.number, + mActors[0]->clipmask + ); + + if (trace.fraction<1.0f) + { + mOrders[0].mPosition = trace.endpos; + } + } + else + { + mOrders[0].mPosition = mTargetLastKnownPosition; + } + + VectorCopy(mOrders[0].mPosition.v, mActors[0]->pos1); + + CVec3 FormTgtToHead(mFormHead); + FormTgtToHead -= mTargetLastKnownPosition; + /*float FormTgtToHeadDist = */FormTgtToHead.SafeNorm(); + + CVec3 BaseAngleToHead(FormTgtToHead); + BaseAngleToHead.VecToAng(); + +// int NumPerSide = mActors.size()/2; +// float WidestAngle = FORMATION_SURROUND_FAN * (NumPerSide+1); + + + +// PHASE III - USE FORMATION VECTORS TO COMPUTE ORDERS FOR ALL ACTORS +//==================================================================== + for (actorIndex=1; actorIndexNPC->combatPoint!=-1) + { + NPC_FreeCombatPoint(mActors[actorIndex]->NPC->combatPoint, false); + mActors[actorIndex]->NPC->combatPoint = -1; + } + + + Order.mPosition = mFormHead; + Order.mCombatPoint = -1; + Order.mKneelAndShoot= false; + + + // Advance Orders + //---------------- + if (NextState=4) + { + int cpFlags = (CP_HAS_ROUTE|CP_AVOID_ENEMY|CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY); + float avoidDist = 128.0f; + + Order.mCombatPoint = NPC_FindCombatPointRetry( + mActors[actorIndex]->currentOrigin, + mActors[actorIndex]->currentOrigin, + mActors[actorIndex]->currentOrigin, + &cpFlags, + avoidDist, + 0); + + if (Order.mCombatPoint!=-1 && (cpFlags&CP_CLEAR)) + { + Order.mPosition = level.combatPoints[Order.mCombatPoint].origin; + NPC_SetCombatPoint(Order.mCombatPoint); + } + else + { + Order.mPosition.ScaleAdd(mFormFwd, FwdScale); + Order.mPosition.ScaleAdd(mFormRight, SideScale); + } + } + else if (NextState==TS_ATTACK_SURROUND) + { + Order.mPosition.ScaleAdd(mFormFwd, FwdScale); + Order.mPosition.ScaleAdd(mFormRight, SideScale); + +/* CVec3 FanAngles = BaseAngleToHead; + FanAngles[YAW] += (SideScale * (WidestAngle-(FwdScale*FORMATION_SURROUND_FAN))); + FanAngles.AngToVec(); + + Order.mPosition = mTargetLastKnownPosition; + Order.mPosition.ScaleAdd(FanAngles, FormTgtToHeadDist); +*/ + } + else if (NextState==TS_ATTACK_COVER) + { + Order.mPosition.ScaleAdd(mFormFwd, FwdScale); + Order.mPosition.ScaleAdd(mFormRight, SideScale); + } + } + + if (NextState>=TS_ATTACK) + { + trace_t trace; + CVec3 OrderUp(Order.mPosition); + OrderUp[2] += 10.0f; + + gi.trace(&trace, + Order.mPosition.v, + mActors[actorIndex]->mins, + mActors[actorIndex]->maxs, + OrderUp.v, + mActors[actorIndex]->s.number, + CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP); + + if (trace.startsolid || trace.allsolid) + { + int cpFlags = (CP_HAS_ROUTE|CP_AVOID_ENEMY|CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY); + float avoidDist = 128.0f; + + Order.mCombatPoint = NPC_FindCombatPointRetry( + mActors[actorIndex]->currentOrigin, + mActors[actorIndex]->currentOrigin, + mActors[actorIndex]->currentOrigin, + &cpFlags, + avoidDist, + 0); + + if (Order.mCombatPoint!=-1) + { + Order.mPosition = level.combatPoints[Order.mCombatPoint].origin; + NPC_SetCombatPoint(Order.mCombatPoint); + } + else + { + Order.mPosition = mOrders[0].mPosition; + } + } + } + RestoreNPCGlobals(); + } + + mTroopReform = false; + mState = NextState; + } + + //////////////////////////////////////////////////////////////////////////////////// + // SufficientCoverNearby - Look at nearby combat points, see if there is enough + //////////////////////////////////////////////////////////////////////////////////// + bool SufficientCoverNearby() + { + // TODO: Evaluate Available Combat Points + return false; + } + + + + + + + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Update - This is the primary "think" function from the troop + //////////////////////////////////////////////////////////////////////////////////// + void Update() + { + if (mActors.empty()) + { + return; + } + ScanForTarget(0 /*Q_irand(0, (mActors.size()-1))*/); + if (mTarget) + { + ETroopState NextState = mState; + int TimeSinceLastSeen = (level.time - mTargetVisableStopTime); + // int TimeVisable = (mTargetVisableStopTime - mTargetVisableStartTime); + bool Attack = (TimeSinceLastSeen<2000); + + if (Attack) + { + // If Not Currently Attacking, Or We Want To Pick A New Attack Tactic + //-------------------------------------------------------------------- + if (mState4)?(TS_ATTACK_FLANK):(TS_ATTACK_LINE); + } + else + { + NextState = (SufficientCoverNearby())?(TS_ATTACK_COVER):(TS_ATTACK_SURROUND); + } + } + } + else + { + if (!TroopInFormation()) + { + NextState = TS_ADVANCE_REGROUP; + } + else + { + if (TargetLastKnownPositionVisited()) + { + NextState = TS_ADVANCE_SEARCH; + } + else + { + NextState = (TimeSinceLastSeen<10000)?(TS_ADVANCE_COVER):(TS_ADVANCE_FORMATION); + } + } + } + LeaderIssueAndUpdateOrders(NextState); + + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // MergeInto - Merges all actors into anther troop + //////////////////////////////////////////////////////////////////////////////////// + void MergeInto(CTroop& Other) + { + int numEnts = mActors.size(); + for (int i=0; iclient->leader = 0; + mActors[i]->NPC->troop = 0; + Other.AddActor(mActors[i]); + } + mActors.clear(); + + if (!Other.mTarget && mTarget) + { + Other.mTarget = mTarget; + Other.mTargetIndex = mTargetIndex; + Other.mTargetLastKnownPosition = mTargetLastKnownPosition; + Other.mTargetLastKnownPositionVisited = mTargetLastKnownPositionVisited; + Other.mTargetLastKnownTime = mTargetLastKnownTime; + Other.mTargetVisableStartTime = mTargetVisableStartTime; + Other.mTargetVisableStopTime = mTargetVisableStopTime; + Other.mTargetVisable = mTargetVisable; + Other.mTargetVisablePosition = mTargetVisablePosition; + Other.LeaderIssueAndUpdateOrders(mState); + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + gentity_t* TrackingTarget() + { + return mTarget; + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + gentity_t* TroopLeader() + { + return mActors[0]; + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + int TimeSinceSeenTarget() + { + return (level.time - mTargetVisableStopTime); + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + CVec3& TargetVisablePosition() + { + return mTargetVisablePosition; + } + + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + float FormSpacingFwd() + { + return mFormSpacingFwd; + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + gentity_t* TooCloseToTroopMember(gentity_t* actor) + { + for (int i=0; iresultspeed<10.0f) + // { + // continue; + // } + + if (i==0) + { + if (Distance(actor->currentOrigin, mActors[i]->currentOrigin)<(mFormSpacingFwd*0.5f)) + { + return mActors[i]; + } + } + else + { + if (Distance(actor->currentOrigin, mActors[i]->currentOrigin)<(mFormSpacingFwd*0.5f)) + { + return mActors[i]; + } + } + } + assert("Somehow this actor is not actually in the troop..."==0); + return 0; + } +}; +typedef ratl::handle_pool_vs TTroopPool; +TTroopPool mTroops; + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Erase All Data, Set To Default Vals Before Entities Spawn +//////////////////////////////////////////////////////////////////////////////////////// +void Troop_Reset() +{ + mTroops.clear(); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Entities Have Just Spawned, Initialize +//////////////////////////////////////////////////////////////////////////////////////// +void Troop_Initialize() +{ +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Global Update Of All Troops +//////////////////////////////////////////////////////////////////////////////////////// +void Troop_Update() +{ + for (TTroopPool::iterator i=mTroops.begin(); i!=mTroops.end(); i++) + { + i->Update(); + } +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Erase All Data, Set To Default Vals Before Entities Spawn +//////////////////////////////////////////////////////////////////////////////////////// +void Trooper_UpdateTroop(gentity_t* actor) +{ + // Try To Join A Troop + //--------------------- + if (!actor->NPC->troop) + { + float curDist = 0; + float closestDist = 0; + TTroopPool::iterator closestTroop = mTroops.end(); + trace_t trace; + + for (TTroopPool::iterator iTroop=mTroops.begin(); iTroop!=mTroops.end(); iTroop++) + { + if (iTroop->Team()==actor->client->playerTeam) + { + curDist = iTroop->DistanceSq(actor); + if (curDistcurrentOrigin, + actor->mins, + actor->maxs, + iTroop->TroopLeader()->currentOrigin, + actor->s.number, + CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP); + + if (!trace.allsolid && + !trace.startsolid && + (trace.fraction>=1.0f || trace.entityNum==iTroop->TroopLeader()->s.number)) + { + closestDist = curDist; + closestTroop = iTroop; + } + } + } + } + + // If Found, Add The Actor To It + //-------------------------------- + if (closestTroop!=mTroops.end()) + { + closestTroop->AddActor(actor); + } + + // If We Couldn't Find One, Create A New Troop + //--------------------------------------------- + else if (!mTroops.full()) + { + int nTroopHandle = mTroops.alloc(); + mTroops[nTroopHandle].Initialize(nTroopHandle); + mTroops[nTroopHandle].AddActor(actor); + } + } + + // If This Is A Leader, Then He Is Responsible For Merging Troops + //---------------------------------------------------------------- + else if (actor->client->leader==actor) + { + float curDist = 0; + float closestDist = 0; + TTroopPool::iterator closestTroop = mTroops.end(); + + for (TTroopPool::iterator iTroop=mTroops.begin(); iTroop!=mTroops.end(); iTroop++) + { + curDist = iTroop->DistanceSq(actor); + if ((curDistNPC->troop)) + { + closestDist = curDist; + closestTroop = iTroop; + } + } + + if (closestTroop!=mTroops.end()) + { + int oldTroopNum = actor->NPC->troop; + mTroops[oldTroopNum].MergeInto(*closestTroop); + mTroops.free(oldTroopNum); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool Trooper_UpdateSmackAway(gentity_t* actor, gentity_t* target) +{ + if (actor->client->ps.legsAnim==BOTH_MELEE1) + { + if (TIMER_Done(actor, "Trooper_SmackAway")) + { + CVec3 ActorPos(actor->currentOrigin); + CVec3 ActorToTgt(target->currentOrigin); + ActorToTgt -= ActorPos; + float ActorToTgtDist = ActorToTgt.SafeNorm(); + + if (ActorToTgtDist<100.0f) + { + G_Throw(target, ActorToTgt.v, 200.0f); + } + } + return true; + } + return false; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Trooper_SmackAway(gentity_t* actor, gentity_t* target) +{ + assert(actor && actor->NPC); + if (actor->client->ps.legsAnim!=BOTH_MELEE1) + { + NPC_SetAnim(actor, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + TIMER_Set(actor, "Trooper_SmackAway", actor->client->ps.torsoAnimTimer/4.0f); + } +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool Trooper_Kneeling(gentity_t* actor) +{ + return (actor->NPC->aiFlags&NPCAI_KNEEL || actor->client->ps.legsAnim==BOTH_STAND_TO_KNEEL); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Trooper_KneelDown(gentity_t* actor) +{ + assert(actor && actor->NPC); + if (!Trooper_Kneeling(actor) && level.time>actor->NPC->kneelTime) + { + NPC_SetAnim(actor, SETANIM_BOTH, BOTH_STAND_TO_KNEEL, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + actor->NPC->aiFlags |= NPCAI_KNEEL; + actor->NPC->kneelTime = level.time + Q_irand(3000, 6000); + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Trooper_StandUp(gentity_t* actor, bool always=false) +{ + assert(actor && actor->NPC); + if (Trooper_Kneeling(actor) && (always || level.time>actor->NPC->kneelTime)) + { + actor->NPC->aiFlags &= ~NPCAI_KNEEL; + NPC_SetAnim(actor, SETANIM_BOTH, BOTH_KNEEL_TO_STAND, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + actor->NPC->kneelTime = level.time + Q_irand(3000, 6000); + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +int Trooper_CanHitTarget(gentity_t* actor, gentity_t* target, CTroop& troop, float& MuzzleToTargetDistance, CVec3& MuzzleToTarget) +{ + trace_t tr; + CVec3 MuzzlePoint(actor->currentOrigin); + CalcEntitySpot(actor, SPOT_WEAPON, MuzzlePoint.v); + + MuzzleToTarget = troop.TargetVisablePosition(); + MuzzleToTarget -= MuzzlePoint; + MuzzleToTargetDistance = MuzzleToTarget.SafeNorm(); + + + CVec3 MuzzleDirection(actor->currentAngles); + MuzzleDirection.AngToVec(); + + // Aiming In The Right Direction? + //-------------------------------- + if (MuzzleDirection.Dot(MuzzleToTarget)>0.95) + { + // Clear Line Of Sight To Target? + //-------------------------------- + gi.trace(&tr, MuzzlePoint.v, NULL, NULL, troop.TargetVisablePosition().v, actor->s.number, MASK_SHOT); + if (tr.startsolid || tr.allsolid) + { + return ENTITYNUM_NONE; + } + if (tr.entityNum==target->s.number || tr.fraction>0.9f) + { + return target->s.number; + } + return tr.entityNum; + } + return ENTITYNUM_NONE; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Run The Per Trooper Update +//////////////////////////////////////////////////////////////////////////////////////// +void Trooper_Think(gentity_t* actor) +{ + gentity_t* target = (actor->NPC->troop)?(mTroops[actor->NPC->troop].TrackingTarget()):(0); + if (target) + { + G_SetEnemy(actor, target); + + CTroop& troop = mTroops[actor->NPC->troop]; + bool AtPos = STEER::Reached(actor, actor->pos1, 10.0f); + int traceTgt = ENTITYNUM_NONE; + bool traced = false; + bool inSmackAway = false; + + float MuzzleToTargetDistance = 0.0f; + CVec3 MuzzleToTarget; + + if (actor->NPC->combatPoint!=-1) + { + traceTgt = Trooper_CanHitTarget(actor, target, troop, MuzzleToTargetDistance, MuzzleToTarget); + traced = true; + if (traceTgt==target->s.number) + { + AtPos = true; + } + } + + + // Smack! + //------- + if (Trooper_UpdateSmackAway(actor, target)) + { + traced = true; + AtPos = true; + inSmackAway = true; + } + + + if (false) + { + CG_DrawEdge(actor->currentOrigin, actor->pos1, EDGE_IMPACT_SAFE); + } + + // If There, Stop Moving + //----------------------- + STEER::Activate(actor); + { + gentity_t* fleeFrom = troop.TooCloseToTroopMember(actor); + + // If Too Close To The Leader, Get Out Of His Way + //------------------------------------------------ + if (fleeFrom) + { + STEER::Flee(actor, fleeFrom->currentOrigin, 1.0f); + AtPos = false; + } + + + // If In Position, Stop Moving + //----------------------------- + if (AtPos) + { + NAV::ClearPath(actor); + STEER::Stop(actor); + } + + // Otherwise, Try To Get To Position + //----------------------------------- + else + { + Trooper_StandUp(actor, true); + + // If Close Enough, Persue Our Target Directly + //--------------------------------------------- + bool moveSuccess = STEER::GoTo(NPC, actor->pos1, 10.0f, false); + + // Otherwise + //----------- + if (!moveSuccess) + { + moveSuccess = NAV::GoTo(NPC, actor->pos1); + } + + // If No Way To Get To Position, Stay Here + //----------------------------------------- + if (!moveSuccess || (level.time - actor->lastMoveTime)>4000) + { + AtPos = true; + } + } + } + STEER::DeActivate(actor, &ucmd); + + + + + // If There And Target Was Recently Visable + //------------------------------------------ + if (AtPos && (troop.TimeSinceSeenTarget()<1500)) + { + if (!traced) + { + traceTgt = Trooper_CanHitTarget(actor, target, troop, MuzzleToTargetDistance, MuzzleToTarget); + } + + // Shoot! + //-------- + if (traceTgt==target->s.number) + { + if (actor->s.weapon==WP_BLASTER) + { + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + WeaponThink(qtrue); + } + else if (!inSmackAway) + { + // Otherwise, If Kneeling, Get Up! + //--------------------------------- + if (Trooper_Kneeling(actor)) + { + Trooper_StandUp(actor); + } + + // If The Enemy Is Close Enough, Smack Him Away + //---------------------------------------------- + else if (MuzzleToTargetDistance<40.0f) + { + Trooper_SmackAway(actor, target); + } + + // If We Would Have It A Friend, Ask Him To Kneel + //------------------------------------------------ + else if (traceTgt!=ENTITYNUM_NONE && + traceTgt!=ENTITYNUM_WORLD && + g_entities[traceTgt].client && + g_entities[traceTgt].NPC && + g_entities[traceTgt].client->playerTeam==actor->client->playerTeam && + NPC_IsTrooper(&g_entities[traceTgt]) && + g_entities[traceTgt].resultspeed<1.0f && + !(g_entities[traceTgt].NPC->aiFlags & NPCAI_KNEEL)) + { + Trooper_KneelDown(&g_entities[traceTgt]); + } + } + + + // Convert To Angles And Set That As Our Desired Look Direction + //-------------------------------------------------------------- + if (MuzzleToTargetDistance>100) + { + MuzzleToTarget.VecToAng(); + + NPCInfo->desiredYaw = MuzzleToTarget[YAW]; + NPCInfo->desiredPitch = MuzzleToTarget[PITCH]; + } + else + { + MuzzleToTarget = troop.TargetVisablePosition(); + MuzzleToTarget.v[2] -= 20.0f; // Aim Lower + MuzzleToTarget -= actor->currentOrigin; + MuzzleToTarget.SafeNorm(); + MuzzleToTarget.VecToAng(); + + NPCInfo->desiredYaw = MuzzleToTarget[YAW]; + NPCInfo->desiredPitch = MuzzleToTarget[PITCH]; + } + } + + NPC_UpdateFiringAngles( qtrue, qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + + if (Trooper_Kneeling(actor)) + { + ucmd.upmove = -127; // Set Crouch Height + } + } + + + + + else + { + NPC_BSST_Default(); + } +} + + + +//////////////////////////////////////////////////////////////////////////////////////// +/* +------------------------- +NPC_BehaviorSet_Trooper +------------------------- +*/ +//////////////////////////////////////////////////////////////////////////////////////// +void NPC_BehaviorSet_Trooper( int bState ) +{ + Trooper_UpdateTroop(NPC); + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + Trooper_Think(NPC); + break; + + case BS_INVESTIGATE: + NPC_BSST_Investigate(); + break; + + case BS_SLEEP: + NPC_BSST_Sleep(); + break; + + default: + Trooper_Think(NPC); + break; + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// IsTrooper - return true if you want a given actor to use trooper AI +//////////////////////////////////////////////////////////////////////////////////////// +bool NPC_IsTrooper(gentity_t* actor) +{ + return ( + actor && + actor->NPC && + actor->s.weapon && + !!(actor->NPC->scriptFlags&SCF_NO_GROUPS)// && +// !(actor->NPC->scriptFlags&SCF_CHASE_ENEMIES) + ); +} + +void NPC_LeaveTroop(gentity_t* actor) +{ + assert(actor->NPC->troop); + int wasInTroop = actor->NPC->troop; + mTroops[actor->NPC->troop].RemoveActor(actor); + if (mTroops[wasInTroop].Empty()) + { + mTroops.free(wasInTroop); + } +} + + diff --git a/code/game/AI_Howler.cpp b/code/game/AI_Howler.cpp new file mode 100644 index 0000000..cff15c0 --- /dev/null +++ b/code/game/AI_Howler.cpp @@ -0,0 +1,852 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" + +// These define the working combat range for these suckers +#define MIN_DISTANCE 54 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 128 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 +#define LSTATE_FLEE 2 +#define LSTATE_BERZERK 3 + +#define HOWLER_RETREAT_DIST 300.0f +#define HOWLER_PANIC_HEALTH 10 + +extern void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir ); +extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex = 0 ); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern qboolean NAV_DirSafe( gentity_t *self, vec3_t dir, float dist ); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex ); +extern int NPC_GetEntsNearBolt( gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean PM_HasAnimation( gentity_t *ent, int animation ); + +static void Howler_Attack( float enemyDist, qboolean howl = qfalse ); +/* +------------------------- +NPC_Howler_Precache +------------------------- +*/ +void NPC_Howler_Precache( void ) +{ + int i; + //G_SoundIndex( "sound/chars/howler/howl.mp3" ); + G_EffectIndex( "howler/sonic" ); + G_SoundIndex( "sound/chars/howler/howl.mp3" ); + for ( i = 1; i < 3; i++ ) + { + G_SoundIndex( va( "sound/chars/howler/idle_hiss%d.mp3", i ) ); + } + for ( i = 1; i < 6; i++ ) + { + G_SoundIndex( va( "sound/chars/howler/howl_talk%d.mp3", i ) ); + G_SoundIndex( va( "sound/chars/howler/howl_yell%d.mp3", i ) ); + } +} + +void Howler_ClearTimers( gentity_t *self ) +{ + //clear all my timers + TIMER_Set( self, "flee", -level.time ); + TIMER_Set( self, "retreating", -level.time ); + TIMER_Set( self, "standing", -level.time ); + TIMER_Set( self, "walking", -level.time ); + TIMER_Set( self, "running", -level.time ); + TIMER_Set( self, "aggressionDecay", -level.time ); + TIMER_Set( self, "speaking", -level.time ); +} + +static qboolean NPC_Howler_Move( int randomJumpChance = 0 ) +{ + if ( !TIMER_Done( NPC, "standing" ) ) + {//standing around + return qfalse; + } + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//in air, don't do anything + return qfalse; + } + if ( (!NPC->enemy&&TIMER_Done( NPC, "running" )) || !TIMER_Done( NPC, "walking" ) ) + { + ucmd.buttons |= BUTTON_WALKING; + } + if ( (!randomJumpChance||Q_irand( 0, randomJumpChance )) + && NPC_MoveToGoal( qtrue ) ) + { + if ( VectorCompare( NPC->client->ps.moveDir, vec3_origin ) + || !NPC->client->ps.speed ) + {//uh.... wtf? Got there? + if ( NPCInfo->goalEntity ) + { + NPC_FaceEntity( NPCInfo->goalEntity, qfalse ); + } + else + { + NPC_UpdateAngles( qfalse, qtrue ); + } + return qtrue; + } + //TEMP: don't want to strafe + VectorClear( NPC->client->ps.moveDir ); + ucmd.rightmove = 0.0f; +// Com_Printf( "Howler moving %d\n",ucmd.forwardmove ); + //if backing up, go slow... + if ( ucmd.forwardmove < 0.0f ) + { + ucmd.buttons |= BUTTON_WALKING; + //if ( NPC->client->ps.speed > NPCInfo->stats.walkSpeed ) + {//don't walk faster than I'm allowed to + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + } + } + else + { + if ( (ucmd.buttons&BUTTON_WALKING) ) + { + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + } + else + { + NPC->client->ps.speed = NPCInfo->stats.runSpeed; + } + } + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPC_UpdateAngles( qfalse, qtrue ); + } + else if ( NPCInfo->goalEntity ) + {//couldn't get where we wanted to go, try to jump there + NPC_FaceEntity( NPCInfo->goalEntity, qfalse ); + NPC_TryJump( NPCInfo->goalEntity, 400.0f, -256.0f ); + } + return qtrue; +} +/* +------------------------- +Howler_Idle +------------------------- +*/ +static void Howler_Idle( void ) +{ +} + + +/* +------------------------- +Howler_Patrol +------------------------- +*/ +static void Howler_Patrol( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + NPC_Howler_Move( 100 ); + } + + vec3_t dif; + VectorSubtract( g_entities[0].currentOrigin, NPC->currentOrigin, dif ); + + if ( VectorLengthSquared( dif ) < 256 * 256 ) + { + G_SetEnemy( NPC, &g_entities[0] ); + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Howler_Idle(); + return; + } + + Howler_Attack( 0.0f, qtrue ); +} + +/* +------------------------- +Howler_Move +------------------------- +*/ +static qboolean Howler_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + return NPC_Howler_Move( 30 ); + } + return qfalse; +} + +//--------------------------------------------------------- +static void Howler_TryDamage( int damage, qboolean tongue, qboolean knockdown ) +{ + vec3_t start, end, dir; + trace_t tr; + + if ( tongue ) + { + G_GetBoltPosition( NPC, NPC->genericBolt1, start ); + G_GetBoltPosition( NPC, NPC->genericBolt2, end ); + VectorSubtract( end, start, dir ); + float dist = VectorNormalize( dir ); + VectorMA( start, dist+16, dir, end ); + } + else + { + VectorCopy( NPC->currentOrigin, start ); + AngleVectors( NPC->currentAngles, dir, NULL, NULL ); + VectorMA( start, MIN_DISTANCE*2, dir, end ); + } + +#ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + G_DebugLine(start, end, 1000, 0x000000ff, qtrue); + } +#endif + // Should probably trace from the mouth, but, ah well. + gi.trace( &tr, start, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + + if ( tr.entityNum < ENTITYNUM_WORLD ) + {//hit *something* + gentity_t *victim = &g_entities[tr.entityNum]; + if ( !victim->client + || victim->client->NPC_class != CLASS_HOWLER ) + {//not another howler + + if ( knockdown && victim->client ) + {//only do damage if victim isn't knocked down. If he isn't, knock him down + if ( PM_InKnockDown( &victim->client->ps ) ) + { + return; + } + } + //FIXME: some sort of damage effect (claws and tongue are cutting you... blood?) + G_Damage( victim, NPC, NPC, dir, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + if ( knockdown && victim->health > 0 ) + {//victim still alive + G_Knockdown( victim, NPC, NPC->client->ps.velocity, 500, qfalse ); + } + } + } +} + +static void Howler_Howl( void ) +{ + gentity_t *radiusEnts[ 128 ]; + int numEnts; + const float radius = (NPC->spawnflags&1)?256:128; + const float halfRadSquared = ((radius/2)*(radius/2)); + const float radiusSquared = (radius*radius); + float distSq; + int i; + vec3_t boltOrg; + + AddSoundEvent( NPC, NPC->currentOrigin, 512, AEL_DANGER, qfalse, qtrue ); + + numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->handLBolt, boltOrg ); + + for ( i = 0; i < numEnts; i++ ) + { + if ( !radiusEnts[i]->inuse ) + { + continue; + } + + if ( radiusEnts[i] == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnts[i]->client == NULL ) + {//must be a client + continue; + } + + if ( radiusEnts[i]->client->NPC_class == CLASS_HOWLER ) + {//other howlers immune + continue; + } + + distSq = DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ); + if ( distSq <= radiusSquared ) + { + if ( distSq < halfRadSquared ) + {//close enough to do damage, too + if ( Q_irand( 0, g_spskill->integer ) ) + {//does no damage on easy, does 1 point every other frame on medium, more often on hard + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, NPC->currentOrigin, 1, DAMAGE_NO_KNOCKBACK, MOD_IMPACT ); + } + } + if ( radiusEnts[i]->health > 0 + && radiusEnts[i]->client + && radiusEnts[i]->client->NPC_class != CLASS_RANCOR + && radiusEnts[i]->client->NPC_class != CLASS_ATST + && !PM_InKnockDown( &radiusEnts[i]->client->ps ) ) + { + if ( PM_HasAnimation( radiusEnts[i], BOTH_SONICPAIN_START ) ) + { + if ( radiusEnts[i]->client->ps.torsoAnim != BOTH_SONICPAIN_START + && radiusEnts[i]->client->ps.torsoAnim != BOTH_SONICPAIN_HOLD ) + { + NPC_SetAnim( radiusEnts[i], SETANIM_LEGS, BOTH_SONICPAIN_START, SETANIM_FLAG_NORMAL ); + NPC_SetAnim( radiusEnts[i], SETANIM_TORSO, BOTH_SONICPAIN_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + radiusEnts[i]->client->ps.torsoAnimTimer += 100; + radiusEnts[i]->client->ps.weaponTime = radiusEnts[i]->client->ps.torsoAnimTimer; + } + else if ( radiusEnts[i]->client->ps.torsoAnimTimer <= 100 ) + {//at the end of the sonic pain start or hold anim + NPC_SetAnim( radiusEnts[i], SETANIM_LEGS, BOTH_SONICPAIN_HOLD, SETANIM_FLAG_NORMAL ); + NPC_SetAnim( radiusEnts[i], SETANIM_TORSO, BOTH_SONICPAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + radiusEnts[i]->client->ps.torsoAnimTimer += 100; + radiusEnts[i]->client->ps.weaponTime = radiusEnts[i]->client->ps.torsoAnimTimer; + } + } + /* + else if ( distSq < halfRadSquared + && radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE + && !Q_irand( 0, 10 ) )//FIXME: base on skill + {//within range + G_Knockdown( radiusEnts[i], NPC, vec3_origin, 500, qfalse ); + } + */ + } + } + } + + float playerDist = NPC_EntRangeFromBolt( player, NPC->genericBolt1 ); + if ( playerDist < 256.0f ) + { + CGCam_Shake( 1.0f*playerDist/128.0f, 200 ); + } +} + +//------------------------------ +static void Howler_Attack( float enemyDist, qboolean howl ) +{ + int dmg = (NPCInfo->localState==LSTATE_BERZERK)?5:2; + + if ( !TIMER_Exists( NPC, "attacking" )) + { + int attackAnim = BOTH_GESTURE1; + // Going to do an attack + if ( NPC->enemy && NPC->enemy->client && PM_InKnockDown( &NPC->enemy->client->ps ) + && enemyDist <= MIN_DISTANCE ) + { + attackAnim = BOTH_ATTACK2; + } + else if ( !Q_irand( 0, 4 ) || howl ) + {//howl attack + //G_SoundOnEnt( NPC, CHAN_VOICE, "sound/chars/howler/howl.mp3" ); + } + else if ( enemyDist > MIN_DISTANCE && Q_irand( 0, 1 ) ) + {//lunge attack + //jump foward + vec3_t fwd, yawAng = {0, NPC->client->ps.viewangles[YAW], 0}; + AngleVectors( yawAng, fwd, NULL, NULL ); + VectorScale( fwd, (enemyDist*3.0f), NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 200; + NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; + + attackAnim = BOTH_ATTACK1; + } + else + {//tongue attack + attackAnim = BOTH_ATTACK2; + } + + NPC_SetAnim( NPC, SETANIM_BOTH, attackAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD | SETANIM_FLAG_RESTART ); + if ( NPCInfo->localState == LSTATE_BERZERK ) + {//attack again right away + TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer ); + } + else + { + TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_irand( 0, 1500 ) );//FIXME: base on skill + TIMER_Set( NPC, "standing", -level.time ); + TIMER_Set( NPC, "walking", -level.time ); + TIMER_Set( NPC, "running", NPC->client->ps.legsAnimTimer + 5000 ); + } + + TIMER_Set( NPC, "attack_dmg", 200 ); // level two damage + } + + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_ATTACK1: + case BOTH_MELEE1: + if ( NPC->client->ps.legsAnimTimer > 650//more than 13 frames left + && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 800 )//at least 16 frames into anim + { + Howler_TryDamage( dmg, qfalse, qfalse ); + } + break; + case BOTH_ATTACK2: + case BOTH_MELEE2: + if ( NPC->client->ps.legsAnimTimer > 350//more than 7 frames left + && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 550 )//at least 11 frames into anim + { + Howler_TryDamage( dmg, qtrue, qfalse ); + } + break; + case BOTH_GESTURE1: + { + if ( NPC->client->ps.legsAnimTimer > 1800//more than 36 frames left + && PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim ) - NPC->client->ps.legsAnimTimer >= 950 )//at least 19 frames into anim + { + Howler_Howl(); + if ( !NPC->count ) + { + G_PlayEffect( G_EffectIndex( "howler/sonic" ), NPC->playerModel, NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, 4750, qtrue ); + G_SoundOnEnt( NPC, CHAN_VOICE, "sound/chars/howler/howl.mp3" ); + NPC->count = 1; + } + } + } + break; + default: + //anims seem to get reset after a load, so just stop attacking and it will restart as needed. + TIMER_Remove( NPC, "attacking" ); + break; + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); +} + +//---------------------------------- +static void Howler_Combat( void ) +{ + qboolean faced = qfalse; + float distance; + qboolean advance = qfalse; + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//not on the ground + if ( NPC->client->ps.legsAnim == BOTH_JUMP1 + || NPC->client->ps.legsAnim == BOTH_INAIR1 ) + {//flying through the air with the greatest of ease, etc + Howler_TryDamage( 10, qfalse, qfalse ); + } + } + else + {//not in air, see if we should attack or advance + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS( NPC->enemy ) )//|| UpdateGoal( )) + { + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + + if ( NPCInfo->localState == LSTATE_BERZERK ) + { + NPC_Howler_Move( 3 ); + } + else + { + NPC_Howler_Move( 10 ); + } + NPC_UpdateAngles( qfalse, qtrue ); + return; + } + + distance = DistanceHorizontal( NPC->currentOrigin, NPC->enemy->currentOrigin ); + + if ( NPC->enemy && NPC->enemy->client && PM_InKnockDown( &NPC->enemy->client->ps ) ) + {//get really close to knocked down enemies + advance = (qboolean)( distance > MIN_DISTANCE ? qtrue : qfalse ); + } + else + { + advance = (qboolean)( distance > MAX_DISTANCE ? qtrue : qfalse );//MIN_DISTANCE + } + + if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else if ( TIMER_Done( NPC, "standing" ) ) + { + faced = Howler_Move( 1 ); + } + } + else + { + Howler_Attack( distance ); + } + } + + if ( !faced ) + { + if ( //TIMER_Done( NPC, "standing" ) //not just standing there + //!advance //not moving + TIMER_Done( NPC, "attacking" ) )// not attacking + {//not standing around + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + } + else + { + NPC_UpdateAngles( qfalse, qtrue ); + } + } +} + +/* +------------------------- +NPC_Howler_Pain +------------------------- +*/ +void NPC_Howler_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + if ( !self || !self->NPC ) + { + return; + } + + if ( self->NPC->localState != LSTATE_BERZERK )//damage >= 10 ) + { + self->NPC->stats.aggression += damage; + self->NPC->localState = LSTATE_WAITING; + + TIMER_Remove( self, "attacking" ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + //if ( self->client->ps.legsAnim == BOTH_GESTURE1 ) + { + G_StopEffect( G_EffectIndex( "howler/sonic" ), self->playerModel, self->genericBolt1, self->s.number ); + } + + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( self, "takingPain", self->client->ps.legsAnimTimer );//2900 ); + + if ( self->health > HOWLER_PANIC_HEALTH ) + {//still have some health left + if ( Q_irand( 0, self->max_health ) > self->health )//FIXME: or check damage? + {//back off! + TIMER_Set( self, "standing", -level.time ); + TIMER_Set( self, "running", -level.time ); + TIMER_Set( self, "walking", -level.time ); + TIMER_Set( self, "retreating", Q_irand( 1000, 5000 ) ); + } + else + {//go after him! + TIMER_Set( self, "standing", -level.time ); + TIMER_Set( self, "running", self->client->ps.legsAnimTimer+Q_irand(3000,6000) ); + TIMER_Set( self, "walking", -level.time ); + TIMER_Set( self, "retreating", -level.time ); + } + } + else if ( self->NPC ) + {//panic! + if ( Q_irand( 0, 1 ) ) + {//berzerk + self->NPC->localState = LSTATE_BERZERK; + } + else + {//flee + self->NPC->localState = LSTATE_FLEE; + TIMER_Set( self, "flee", Q_irand( 10000, 30000 ) ); + } + } + } +} + + +/* +------------------------- +NPC_BSHowler_Default +------------------------- +*/ +void NPC_BSHowler_Default( void ) +{ + if ( NPC->client->ps.legsAnim != BOTH_GESTURE1 ) + { + NPC->count = 0; + } + //FIXME: if in jump, do damage in front and maybe knock them down? + if ( !TIMER_Done( NPC, "attacking" ) ) + { + if ( NPC->enemy ) + { + //NPC_FaceEnemy( qfalse ); + Howler_Attack( Distance( NPC->enemy->currentOrigin, NPC->currentOrigin ) ); + } + else + { + //NPC_UpdateAngles( qfalse, qtrue ); + Howler_Attack( 0.0f ); + } + NPC_UpdateAngles( qfalse, qtrue ); + return; + } + + if ( NPC->enemy ) + { + if ( NPCInfo->stats.aggression > 0 ) + { + if ( TIMER_Done( NPC, "aggressionDecay" ) ) + { + NPCInfo->stats.aggression--; + TIMER_Set( NPC, "aggressionDecay", 500 ); + } + } + if ( !TIMER_Done( NPC, "flee" ) + && NPC_BSFlee() ) //this can clear ENEMY + {//successfully trying to run away + return; + } + if ( NPC->enemy == NULL) + { + NPC_UpdateAngles( qfalse, qtrue ); + return; + } + if ( NPCInfo->localState == LSTATE_FLEE ) + {//we were fleeing, now done (either timer ran out or we cannot flee anymore + if ( NPC_ClearLOS( NPC->enemy ) ) + {//if enemy is still around, go berzerk + NPCInfo->localState = LSTATE_BERZERK; + } + else + {//otherwise, lick our wounds? + NPCInfo->localState = LSTATE_CLEAR; + TIMER_Set( NPC, "standing", Q_irand( 3000, 10000 ) ); + } + } + else if ( NPCInfo->localState == LSTATE_BERZERK ) + {//go nuts! + } + else if ( NPCInfo->stats.aggression >= Q_irand( 75, 125 ) ) + {//that's it, go nuts! + NPCInfo->localState = LSTATE_BERZERK; + } + else if ( !TIMER_Done( NPC, "retreating" ) ) + {//trying to back off + NPC_FaceEnemy( qtrue ); + if ( NPC->client->ps.speed > NPCInfo->stats.walkSpeed ) + { + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + } + ucmd.buttons |= BUTTON_WALKING; + if ( Distance( NPC->enemy->currentOrigin, NPC->currentOrigin ) < HOWLER_RETREAT_DIST ) + {//enemy is close + vec3_t moveDir; + AngleVectors( NPC->currentAngles, moveDir, NULL, NULL ); + VectorScale( moveDir, -1, moveDir ); + if ( !NAV_DirSafe( NPC, moveDir, 8 ) ) + {//enemy is backing me up against a wall or ledge! Start to get really mad! + NPCInfo->stats.aggression += 2; + } + else + {//back off + ucmd.forwardmove = -127; + } + //enemy won't leave me alone, get mad... + NPCInfo->stats.aggression++; + } + return; + } + else if ( TIMER_Done( NPC, "standing" ) ) + {//not standing around + if ( !(NPCInfo->last_ucmd.forwardmove) + && !(NPCInfo->last_ucmd.rightmove) ) + {//stood last frame + if ( TIMER_Done( NPC, "walking" ) + && TIMER_Done( NPC, "running" ) ) + {//not walking or running + if ( Q_irand( 0, 2 ) ) + {//run for a while + TIMER_Set( NPC, "walking", Q_irand( 4000, 8000 ) ); + } + else + {//walk for a bit + TIMER_Set( NPC, "running", Q_irand( 2500, 5000 ) ); + } + } + } + else if ( (NPCInfo->last_ucmd.buttons&BUTTON_WALKING) ) + {//walked last frame + if ( TIMER_Done( NPC, "walking" ) ) + {//just finished walking + if ( Q_irand( 0, 5 ) || DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ) < MAX_DISTANCE_SQR ) + {//run for a while + TIMER_Set( NPC, "running", Q_irand( 4000, 20000 ) ); + } + else + {//stand for a bit + TIMER_Set( NPC, "standing", Q_irand( 2000, 6000 ) ); + } + } + } + else + {//ran last frame + if ( TIMER_Done( NPC, "running" ) ) + {//just finished running + if ( Q_irand( 0, 8 ) || DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ) < MAX_DISTANCE_SQR ) + {//walk for a while + TIMER_Set( NPC, "walking", Q_irand( 3000, 10000 ) ); + } + else + {//stand for a bit + TIMER_Set( NPC, "standing", Q_irand( 2000, 6000 ) ); + } + } + } + } + if ( NPC_ValidEnemy( NPC->enemy ) == qfalse ) + { + TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now + if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) + {//it's been a while since the enemy died, or enemy is completely gone, get bored with him + NPC->enemy = NULL; + Howler_Patrol(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) + { + gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? + NPC->enemy = NULL; + gentity_t *newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); + NPC->enemy = sav_enemy; + if ( newEnemy && newEnemy != sav_enemy ) + {//picked up a new enemy! + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, newEnemy ); + if ( NPC->enemy != NPC->lastEnemy ) + {//clear this so that we only sniff the player the first time we pick them up + NPC->useDebounceTime = 0; + } + //hold this one for at least 5-15 seconds + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + } + else + {//look again in 2-5 secs + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); + } + } + Howler_Combat(); + if ( TIMER_Done( NPC, "speaking" ) ) + { + if ( !TIMER_Done( NPC, "standing" ) + || !TIMER_Done( NPC, "retreating" )) + { + G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/howler/idle_hiss%d.mp3", Q_irand( 1, 2 ) ) ); + } + else if ( !TIMER_Done( NPC, "walking" ) + || NPCInfo->localState == LSTATE_FLEE ) + { + G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/howler/howl_talk%d.mp3", Q_irand( 1, 5 ) ) ); + } + else + { + G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/howler/howl_yell%d.mp3", Q_irand( 1, 5 ) ) ); + } + if ( NPCInfo->localState == LSTATE_BERZERK + || NPCInfo->localState == LSTATE_FLEE ) + { + TIMER_Set( NPC, "speaking", Q_irand( 1000, 4000 ) ); + } + else + { + TIMER_Set( NPC, "speaking", Q_irand( 3000, 8000 ) ); + } + } + return; + } + else + { + if ( TIMER_Done( NPC, "speaking" ) ) + { + if ( !Q_irand( 0, 3 ) ) + { + G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/howler/idle_hiss%d.mp3", Q_irand( 1, 2 ) ) ); + } + else + { + G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/howler/howl_talk%d.mp3", Q_irand( 1, 5 ) ) ); + } + TIMER_Set( NPC, "speaking", Q_irand( 4000, 12000 ) ); + } + if ( NPCInfo->stats.aggression > 0 ) + { + if ( TIMER_Done( NPC, "aggressionDecay" ) ) + { + NPCInfo->stats.aggression--; + TIMER_Set( NPC, "aggressionDecay", 200 ); + } + } + if ( TIMER_Done( NPC, "standing" ) ) + {//not standing around + if ( !(NPCInfo->last_ucmd.forwardmove) + && !(NPCInfo->last_ucmd.rightmove) ) + {//stood last frame + if ( TIMER_Done( NPC, "walking" ) + && TIMER_Done( NPC, "running" ) ) + {//not walking or running + if ( NPCInfo->goalEntity ) + {//have somewhere to go + if ( Q_irand( 0, 2 ) ) + {//walk for a while + TIMER_Set( NPC, "walking", Q_irand( 3000, 10000 ) ); + } + else + {//run for a bit + TIMER_Set( NPC, "running", Q_irand( 2500, 5000 ) ); + } + } + } + } + else if ( (NPCInfo->last_ucmd.buttons&BUTTON_WALKING) ) + {//walked last frame + if ( TIMER_Done( NPC, "walking" ) ) + {//just finished walking + if ( Q_irand( 0, 3 ) ) + {//run for a while + TIMER_Set( NPC, "running", Q_irand( 3000, 6000 ) ); + } + else + {//stand for a bit + TIMER_Set( NPC, "standing", Q_irand( 2500, 5000 ) ); + } + } + } + else + {//ran last frame + if ( TIMER_Done( NPC, "running" ) ) + {//just finished running + if ( Q_irand( 0, 2 ) ) + {//walk for a while + TIMER_Set( NPC, "walking", Q_irand( 6000, 15000 ) ); + } + else + {//stand for a bit + TIMER_Set( NPC, "standing", Q_irand( 4000, 6000 ) ); + } + } + } + } + if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Howler_Patrol(); + } + else + { + Howler_Idle(); + } + } + + NPC_UpdateAngles( qfalse, qtrue ); +} diff --git a/code/game/AI_ImperialProbe.cpp b/code/game/AI_ImperialProbe.cpp new file mode 100644 index 0000000..206bcf8 --- /dev/null +++ b/code/game/AI_ImperialProbe.cpp @@ -0,0 +1,597 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" + +gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse ); +extern gitem_t *FindItemForAmmo( ammo_t ammo ); + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_BACKINGUP, + LSTATE_SPINNING, + LSTATE_PAIN, + LSTATE_DROP +}; + +void ImperialProbe_Idle( void ); + +void NPC_Probe_Precache(void) +{ + for ( int i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/probe/misc/probetalk%d", i ) ); + } + G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); + G_SoundIndex("sound/chars/probe/misc/anger1"); + G_SoundIndex("sound/chars/probe/misc/fire"); + + G_EffectIndex( "chunks/probehead" ); + G_EffectIndex( "env/med_explode2" ); + G_EffectIndex( "explosions/probeexplosion1"); + G_EffectIndex( "bryar/muzzle_flash" ); + + RegisterItem( FindItemForAmmo( AMMO_BLASTER )); + RegisterItem( FindItemForWeapon( WP_BRYAR_PISTOL ) ); +} +/* +------------------------- +Hunter_MaintainHeight +------------------------- +*/ + +#define VELOCITY_DECAY 0.85f + +void ImperialProbe_MaintainHeight( void ) +{ + float dif; +// vec3_t endPos; +// trace_t trace; + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at about enemy eye level + if ( NPC->enemy ) + { + // Find the height difference + dif = NPC->enemy->currentOrigin[2] - NPC->currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 8 ) + { + if ( fabs( dif ) > 16 ) + { + dif = ( dif < 0 ? -16 : 16 ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + // Apply friction + else if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + + // Stay at a given height until we take on an enemy +/* VectorSet( endPos, NPC->currentOrigin[0], NPC->currentOrigin[1], NPC->currentOrigin[2] - 512 ); + gi.trace( &trace, NPC->currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID ); + + if ( trace.fraction != 1.0f ) + { + float length = ( trace.fraction * 512 ); + + if ( length < 80 ) + { + ucmd.upmove = 32; + } + else if ( length > 120 ) + { + ucmd.upmove = -32; + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } */ + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +/* +------------------------- +ImperialProbe_Strafe +------------------------- +*/ + +#define HUNTER_STRAFE_VEL 256 +#define HUNTER_STRAFE_DIS 200 +#define HUNTER_UPWARD_PUSH 32 + +void ImperialProbe_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->currentOrigin, HUNTER_STRAFE_DIS * dir, right, end ); + + gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, HUNTER_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + // Add a slight upward push + NPC->client->ps.velocity[2] += HUNTER_UPWARD_PUSH; + + // Set the strafe start time so we can do a controlled roll + NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +/* +------------------------- +ImperialProbe_Hunt +-------------------------` +*/ + +#define HUNTER_FORWARD_BASE_SPEED 10 +#define HUNTER_FORWARD_MULTIPLIER 5 + +void ImperialProbe_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + ImperialProbe_Strafe(); + return; + } + } + + //If we don't want to advance, stop here + if ( advance == qfalse ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + NPC_MoveToGoal(qtrue); + return; + } + else + { + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = HUNTER_FORWARD_BASE_SPEED + HUNTER_FORWARD_MULTIPLIER * g_spskill->integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +/* +------------------------- +ImperialProbe_FireBlaster +------------------------- +*/ +void ImperialProbe_FireBlaster(void) +{ + vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1; + static vec3_t forward, vright, up; + static vec3_t muzzle; + gentity_t *missile; + mdxaBone_t boltMatrix; + + //FIXME: use {0, NPC->client->ps.legsYaw, 0} + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, + NPC->genericBolt1, + &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 ); + + G_PlayEffect( "bryar/muzzle_flash", muzzle1 ); + + G_Sound( NPC, G_SoundIndex( "sound/chars/probe/misc/fire" )); + + if (NPC->health) + { + CalcEntitySpot( NPC->enemy, SPOT_CHEST, enemy_org1 ); + enemy_org1[0]+= Q_irand(0,10); + enemy_org1[1]+= Q_irand(0,10); + VectorSubtract (enemy_org1, muzzle1, delta1); + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + } + else + { + AngleVectors (NPC->currentAngles, forward, vright, up); + } + + missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + if ( g_spskill->integer <= 1 ) + { + missile->damage = 5; + } + else + { + missile->damage = 10; + } + + + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +ImperialProbe_Ranged +------------------------- +*/ +void ImperialProbe_Ranged( qboolean visible, qboolean advance ) +{ + int delay_min,delay_max; + + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + + if ( g_spskill->integer == 0 ) + { + delay_min = 500; + delay_max = 3000; + } + else if ( g_spskill->integer > 1 ) + { + delay_min = 500; + delay_max = 2000; + } + else + { + delay_min = 300; + delay_max = 1500; + } + + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) ); + ImperialProbe_FireBlaster(); +// ucmd.buttons |= BUTTON_ATTACK; + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ImperialProbe_Hunt( visible, advance ); + } +} + +/* +------------------------- +ImperialProbe_AttackDecision +------------------------- +*/ + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +void ImperialProbe_AttackDecision( void ) +{ + // Always keep a good height off the ground + ImperialProbe_MaintainHeight(); + + //randomly talk + if ( TIMER_Done(NPC,"patrolNoise") ) + { + if (TIMER_Done(NPC,"angerNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); + } + } + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt() == qfalse ) + { + ImperialProbe_Idle(); + return; + } + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL); + + // Rate our distance to the target, and our visibilty + float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); +// distance_e distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; + qboolean visible = NPC_ClearLOS( NPC->enemy ); + qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ImperialProbe_Hunt( visible, advance ); + return; + } + } + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + // Decide what type of attack to do + ImperialProbe_Ranged( visible, advance ); +} + +/* +------------------------- +NPC_BSDroid_Pain +------------------------- +*/ +void NPC_Probe_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + float pain_chance; + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) // demp2 always messes them up real good + { + vec3_t endPos; + trace_t trace; + + VectorSet( endPos, self->currentOrigin[0], self->currentOrigin[1], self->currentOrigin[2] - 128 ); + gi.trace( &trace, self->currentOrigin, NULL, NULL, endPos, self->s.number, MASK_SOLID ); + + if ( trace.fraction == 1.0f || mod == MOD_DEMP2 ) // demp2 always does this + { + if (self->client->clientInfo.headModel != 0) + { + vec3_t origin; + + VectorCopy(self->currentOrigin,origin); + origin[2] +=50; +// G_PlayEffect( "small_chunks", origin ); + G_PlayEffect( "chunks/probehead", origin ); + G_PlayEffect( "env/med_explode2", origin ); + self->client->clientInfo.headModel = 0; + self->client->moveType = MT_RUNJUMP; + self->client->ps.gravity = g_gravity->value*.1; + } + + if ( (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) && other ) + { + vec3_t dir; + + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + + VectorSubtract( self->currentOrigin, other->currentOrigin, dir ); + VectorNormalize( dir ); + + VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); + self->client->ps.velocity[2] -= 127; + } + + self->s.powerups |= ( 1 << PW_SHOCKED ); + self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + + self->NPC->localState = LSTATE_DROP; + } + } + else + { + pain_chance = NPC_GetPainChance( self, damage ); + + if ( random() < pain_chance ) // Spin around in pain? + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE); + } + } + + NPC_Pain( self, inflictor, other, point, damage, mod); +} + +/* +------------------------- +ImperialProbe_Idle +------------------------- +*/ + +void ImperialProbe_Idle( void ) +{ + ImperialProbe_MaintainHeight(); + + NPC_BSIdle(); +} + +/* +------------------------- +NPC_BSImperialProbe_Patrol +------------------------- +*/ +void ImperialProbe_Patrol( void ) +{ + ImperialProbe_MaintainHeight(); + + if ( NPC_CheckPlayerTeamStealth() ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL ); + + if ( UpdateGoal() ) + { + //start loop sound once we move + NPC->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + //randomly talk + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + else // He's got an enemy. Make him angry. + { + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/probe/misc/anger1" ); + TIMER_Set( NPC, "angerNoise", Q_irand( 2000, 4000 ) ); + //NPCInfo->behaviorState = BS_HUNT_AND_KILL; + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +ImperialProbe_Wait +------------------------- +*/ +void ImperialProbe_Wait(void) +{ + if ( NPCInfo->localState == LSTATE_DROP ) + { + vec3_t endPos; + trace_t trace; + + NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->desiredYaw + 25 ); + + VectorSet( endPos, NPC->currentOrigin[0], NPC->currentOrigin[1], NPC->currentOrigin[2] - 32 ); + gi.trace( &trace, NPC->currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID ); + + if ( trace.fraction != 1.0f ) + { + G_Damage(NPC, NPC->enemy, NPC->enemy, NULL, NULL, 2000, 0,MOD_UNKNOWN); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSImperialProbe_Default +------------------------- +*/ +void NPC_BSImperialProbe_Default( void ) +{ + + if ( NPC->enemy ) + { + NPCInfo->goalEntity = NPC->enemy; + ImperialProbe_AttackDecision(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + ImperialProbe_Patrol(); + } + else if ( NPCInfo->localState == LSTATE_DROP ) + { + ImperialProbe_Wait(); + } + else + { + ImperialProbe_Idle(); + } +} diff --git a/code/game/AI_Interrogator.cpp b/code/game/AI_Interrogator.cpp new file mode 100644 index 0000000..4e7495f --- /dev/null +++ b/code/game/AI_Interrogator.cpp @@ -0,0 +1,456 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" + +void Interrogator_Idle( void ); +void DeathFX( gentity_t *ent ); + +enum +{ +LSTATE_BLADESTOP=0, +LSTATE_BLADEUP, +LSTATE_BLADEDOWN, +}; + +/* +------------------------- +NPC_Interrogator_Precache +------------------------- +*/ +void NPC_Interrogator_Precache(gentity_t *self) +{ + G_SoundIndex( "sound/chars/interrogator/misc/torture_droid_lp" ); + G_SoundIndex("sound/chars/mark1/misc/anger.wav"); + G_SoundIndex( "sound/chars/probe/misc/talk"); + G_SoundIndex( "sound/chars/interrogator/misc/torture_droid_inject" ); + G_SoundIndex( "sound/chars/interrogator/misc/int_droid_explo" ); + G_EffectIndex( "explosions/droidexplosion1" ); +} +/* +------------------------- +Interrogator_die +------------------------- +*/ +void Interrogator_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) +{ + self->client->ps.velocity[2] = -100; + /* + self->locationDamage[HL_NONE] += damage; + if (self->locationDamage[HL_NONE] > 40) + { + DeathFX(self); + self->client->ps.eFlags |= EF_NODRAW; + self->contents = CONTENTS_CORPSE; + } + else + */ + { + self->client->moveType = MT_WALK; + self->client->ps.velocity[0] = Q_irand( -10, -20 ); + self->client->ps.velocity[1] = Q_irand( -10, -20 ); + self->client->ps.velocity[2] = -100; + } + //self->takedamage = qfalse; + //self->client->ps.eFlags |= EF_NODRAW; + //self->contents = 0; + return; +} + +/* +------------------------- +Interrogator_PartsMove +------------------------- +*/ +void Interrogator_PartsMove(void) +{ + // Syringe + if ( TIMER_Done(NPC,"syringeDelay") ) + { + NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); + + if ((NPC->pos1[1] < 60) || (NPC->pos1[1] > 300)) + { + NPC->pos1[1]+=Q_irand( -20, 20 ); // Pitch + } + else if (NPC->pos1[1] > 180) + { + NPC->pos1[1]=Q_irand( 300, 360 ); // Pitch + } + else + { + NPC->pos1[1]=Q_irand( 0, 60 ); // Pitch + } + + gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + TIMER_Set( NPC, "syringeDelay", Q_irand( 100, 1000 ) ); + } + + // Scalpel + if ( TIMER_Done(NPC,"scalpelDelay") ) + { + // Change pitch + if ( NPCInfo->localState == LSTATE_BLADEDOWN ) // Blade is moving down + { + NPC->pos2[0]-= 30; + if (NPC->pos2[0] < 180) + { + NPC->pos2[0] = 180; + NPCInfo->localState = LSTATE_BLADEUP; // Make it move up + } + } + else // Blade is coming back up + { + NPC->pos2[0]+= 30; + if (NPC->pos2[0] >= 360) + { + NPC->pos2[0] = 360; + NPCInfo->localState = LSTATE_BLADEDOWN; // Make it move down + TIMER_Set( NPC, "scalpelDelay", Q_irand( 100, 1000 ) ); + } + } + + NPC->pos2[0] = AngleNormalize360( NPC->pos2[0]); + gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone2, NPC->pos2, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + + // Claw + NPC->pos3[1] += Q_irand( 10, 30 ); + NPC->pos3[1] = AngleNormalize360( NPC->pos3[1]); + gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone3, NPC->pos3, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + +} + +#define VELOCITY_DECAY 0.85f +#define HUNTER_UPWARD_PUSH 2 + +/* +------------------------- +Interrogator_MaintainHeight +------------------------- +*/ +void Interrogator_MaintainHeight( void ) +{ + float dif; +// vec3_t endPos; +// trace_t trace; + + NPC->s.loopSound = G_SoundIndex( "sound/chars/interrogator/misc/torture_droid_lp" ); + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at about enemy eye level + if ( NPC->enemy ) + { + // Find the height difference + dif = (NPC->enemy->currentOrigin[2] + NPC->enemy->maxs[2]) - NPC->currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 2 ) + { + if ( fabs( dif ) > 16 ) + { + dif = ( dif < 0 ? -16 : 16 ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + // Apply friction + else if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +#define HUNTER_STRAFE_VEL 32 +#define HUNTER_STRAFE_DIS 200 +/* +------------------------- +Interrogator_Strafe +------------------------- +*/ +void Interrogator_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + float dif; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->currentOrigin, HUNTER_STRAFE_DIS * dir, right, end ); + + gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, HUNTER_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + // Add a slight upward push + if ( NPC->enemy ) + { + // Find the height difference + dif = (NPC->enemy->currentOrigin[2] + 32) - NPC->currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 8 ) + { + dif = ( dif < 0 ? -HUNTER_UPWARD_PUSH : HUNTER_UPWARD_PUSH ); + } + + NPC->client->ps.velocity[2] += dif; + + } + + // Set the strafe start time + NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +/* +------------------------- +Interrogator_Hunt +-------------------------` +*/ + +#define HUNTER_FORWARD_BASE_SPEED 10 +#define HUNTER_FORWARD_MULTIPLIER 2 + +void Interrogator_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + Interrogator_PartsMove(); + + NPC_FaceEnemy(qfalse); + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Interrogator_Strafe(); + if ( NPCInfo->standTime > level.time ) + {//successfully strafed + return; + } + } + } + + //If we don't want to advance, stop here + if ( advance == qfalse ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + NPC_MoveToGoal(qtrue); + return; + + } + else + { + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = HUNTER_FORWARD_BASE_SPEED + HUNTER_FORWARD_MULTIPLIER * g_spskill->integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +#define MIN_DISTANCE 64 + +/* +------------------------- +Interrogator_Melee +------------------------- +*/ +void Interrogator_Melee( qboolean visible, qboolean advance ) +{ + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + // Make sure that we are within the height range before we allow any damage to happen + if ( NPC->currentOrigin[2] >= NPC->enemy->currentOrigin[2]+NPC->enemy->mins[2] && NPC->currentOrigin[2]+NPC->mins[2]+8 < NPC->enemy->currentOrigin[2]+NPC->enemy->maxs[2] ) + { + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) ); + G_Damage( NPC->enemy, NPC, NPC, 0, 0, 2, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + + NPC->enemy->client->poisonDamage = 18; + NPC->enemy->client->poisonTime = level.time + 1000; + + // Drug our enemy up and do the wonky vision thing + gentity_t *tent = G_TempEntity( NPC->enemy->currentOrigin, EV_DRUGGED ); + tent->owner = NPC->enemy; + + G_Sound( NPC, G_SoundIndex( "sound/chars/interrogator/misc/torture_droid_inject.mp3" )); + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Interrogator_Hunt( visible, advance ); + } +} + +/* +------------------------- +Interrogator_Attack +------------------------- +*/ +void Interrogator_Attack( void ) +{ + // Always keep a good height off the ground + Interrogator_MaintainHeight(); + + //randomly talk + if ( TIMER_Done(NPC,"patrolNoise") ) + { + if (TIMER_Done(NPC,"angerNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/talk.wav", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); + } + } + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt() == qfalse ) + { + Interrogator_Idle(); + return; + } + + // Rate our distance to the target, and our visibilty + float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + qboolean visible = NPC_ClearLOS( NPC->enemy ); + qboolean advance = (qboolean)(distance > MIN_DISTANCE*MIN_DISTANCE ); + + if ( !visible ) + { + advance = qtrue; + } + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Interrogator_Hunt( visible, advance ); + } + + NPC_FaceEnemy( qtrue ); + + if (!advance) + { + Interrogator_Melee( visible, advance ); + } +} + +/* +------------------------- +Interrogator_Idle +------------------------- +*/ +void Interrogator_Idle( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/mark1/misc/anger.wav" ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + Interrogator_MaintainHeight(); + + NPC_BSIdle(); +} + +/* +------------------------- +NPC_BSInterrogator_Default +------------------------- +*/ +void NPC_BSInterrogator_Default( void ) +{ + //NPC->e_DieFunc = dieF_Interrogator_die; + + if ( NPC->enemy ) + { + Interrogator_Attack(); + } + else + { + Interrogator_Idle(); + } + +} \ No newline at end of file diff --git a/code/game/AI_Jedi.cpp b/code/game/AI_Jedi.cpp new file mode 100644 index 0000000..c00cba1 --- /dev/null +++ b/code/game/AI_Jedi.cpp @@ -0,0 +1,7610 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +#include "wp_saber.h" + +//Externs +extern qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy ); +extern void CG_DrawAlert( vec3_t origin, float rating ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f ); +extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 ); +extern void ForceJump( gentity_t *self, usercmd_t *ucmd ); +extern void NPC_ClearLookTarget( gentity_t *self ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern qboolean NPC_CheckEnemyStealth( void ); +extern gitem_t *FindItemForAmmo( ammo_t ammo ); +extern void ForceLightning( gentity_t *self ); +extern void ForceHeal( gentity_t *self ); +extern void ForceRage( gentity_t *self ); +extern void ForceProtect( gentity_t *self ); +extern void ForceAbsorb( gentity_t *self ); +extern qboolean ForceDrain2( gentity_t *self ); +extern int WP_MissileBlockForBlock( int saberBlock ); +extern qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +extern qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +extern void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower ); +extern void WP_KnockdownTurret( gentity_t *self, gentity_t *pas ); +extern void WP_DeactivateSaber( gentity_t *self, qboolean clearLength = qfalse ); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern qboolean PM_SaberInStart( int move ); +extern qboolean PM_SaberInSpecialAttack( int anim ); +extern qboolean PM_SaberInAttack( int move ); +extern qboolean PM_SaberInBounce( int move ); +extern qboolean PM_SaberInParry( int move ); +extern qboolean PM_SaberInKnockaway( int move ); +extern qboolean PM_SaberInBrokenParry( int move ); +extern qboolean PM_SaberInDeflect( int move ); +extern qboolean PM_SpinningSaberAnim( int anim ); +extern qboolean PM_FlippingAnim( int anim ); +extern qboolean PM_RollingAnim( int anim ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean PM_InRoll( playerState_t *ps ); +extern qboolean PM_InGetUp( playerState_t *ps ); +extern qboolean PM_InSpecialJump( int anim ); +extern qboolean PM_SuperBreakWinAnim( int anim ); +extern qboolean PM_InOnGroundAnim ( playerState_t *ps ); +extern qboolean PM_DodgeAnim( int anim ); +extern qboolean PM_DodgeHoldAnim( int anim ); +extern qboolean PM_InAirKickingAnim( int anim ); +extern qboolean PM_KickingAnim( int anim ); +extern qboolean PM_StabDownAnim( int anim ); +extern qboolean PM_SuperBreakLoseAnim( int anim ); +extern qboolean PM_SaberInKata( saberMoveName_t saberMove ); +extern qboolean PM_InRollIgnoreTimer( playerState_t *ps ); +extern qboolean PM_PainAnim( int anim ); +extern qboolean G_CanKickEntity( gentity_t *self, gentity_t *target ); +extern saberMoveName_t G_PickAutoKick( gentity_t *self, gentity_t *enemy, qboolean storeMove ); +extern saberMoveName_t G_PickAutoMultiKick( gentity_t *self, qboolean allowSingles, qboolean storeMove ); +extern qboolean NAV_DirSafe( gentity_t *self, vec3_t dir, float dist ); +extern qboolean NAV_MoveDirSafe( gentity_t *self, usercmd_t *cmd, float distScale = 1.0f ); +extern float NPC_EnemyRangeFromBolt( int boltIndex ); +extern qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode ); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker ); +extern qboolean PM_LockedAnim( int anim ); +extern qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask); + +extern cvar_t *g_saberRealisticCombat; +extern cvar_t *d_slowmodeath; +extern cvar_t *g_saberNewControlScheme; +extern int parryDebounce[]; + +//Locals +static void Jedi_Aggression( gentity_t *self, int change ); +qboolean Jedi_WaitingAmbush( gentity_t *self ); +void Tavion_SithSwordRecharge( void ); +qboolean Rosh_BeingHealed( gentity_t *self ); + +static qboolean enemy_in_striking_range = qfalse; +static int jediSpeechDebounceTime[TEAM_NUM_TEAMS];//used to stop several jedi from speaking all at once + +void NPC_CultistDestroyer_Precache( void ) +{ + G_SoundIndex( "sound/movers/objects/green_beam_lp2.wav" ); + G_EffectIndex( "force/destruction_exp" ); +} + +void NPC_ShadowTrooper_Precache( void ) +{ + RegisterItem( FindItemForAmmo( AMMO_FORCE ) ); + G_SoundIndex( "sound/chars/shadowtrooper/cloak.wav" ); + G_SoundIndex( "sound/chars/shadowtrooper/decloak.wav" ); +} + +void NPC_Rosh_Dark_Precache( void ) +{ + G_EffectIndex( "force/kothos_recharge.efx" ); + G_EffectIndex( "force/kothos_beam.efx" ); +} + +void Jedi_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "strafeLeft", 0 ); + TIMER_Set( ent, "strafeRight", 0 ); + TIMER_Set( ent, "noStrafe", 0 ); + TIMER_Set( ent, "walking", 0 ); + TIMER_Set( ent, "taunting", 0 ); + TIMER_Set( ent, "parryTime", 0 ); + TIMER_Set( ent, "parryReCalcTime", 0 ); + TIMER_Set( ent, "forceJumpChasing", 0 ); + TIMER_Set( ent, "jumpChaseDebounce", 0 ); + TIMER_Set( ent, "moveforward", 0 ); + TIMER_Set( ent, "moveback", 0 ); + TIMER_Set( ent, "movenone", 0 ); + TIMER_Set( ent, "moveright", 0 ); + TIMER_Set( ent, "moveleft", 0 ); + TIMER_Set( ent, "movecenter", 0 ); + TIMER_Set( ent, "saberLevelDebounce", 0 ); + TIMER_Set( ent, "noRetreat", 0 ); + TIMER_Set( ent, "holdLightning", 0 ); + TIMER_Set( ent, "gripping", 0 ); + TIMER_Set( ent, "draining", 0 ); + TIMER_Set( ent, "noturn", 0 ); + TIMER_Set( ent, "specialEvasion", 0 ); +} + +qboolean Jedi_CultistDestroyer( gentity_t *self ) +{ + if ( !self || !self->client ) + { + return qfalse; + } + //FIXME: just make a flag, dude! + if ( self->client->NPC_class == CLASS_REBORN + && self->s.weapon == WP_MELEE + && Q_stricmp( "cultist_destroyer", self->NPC_type ) == 0 ) + { + return qtrue; + } + return qfalse; +} + +void Jedi_PlayBlockedPushSound( gentity_t *self ) +{ + if ( !self->s.number ) + { + G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 ); + } + else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 ); + self->NPC->blockedSpeechDebounceTime = level.time + 3000; + } +} + +void Jedi_PlayDeflectSound( gentity_t *self ) +{ + if ( !self->s.number ) + { + G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 ); + } + else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 ); + self->NPC->blockedSpeechDebounceTime = level.time + 3000; + } +} + +void NPC_Jedi_PlayConfusionSound( gentity_t *self ) +{ + if ( self->health > 0 ) + { + if ( self->client + && ( self->client->NPC_class == CLASS_ALORA + || self->client->NPC_class == CLASS_TAVION + || self->client->NPC_class == CLASS_DESANN ) ) + { + G_AddVoiceEvent( self, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), 2000 ); + } + else if ( Q_irand( 0, 1 ) ) + { + G_AddVoiceEvent( self, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 2000 ); + } + else + { + G_AddVoiceEvent( self, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 2000 ); + } + } +} + +qboolean Jedi_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir ) +{ + if ( self->s.number < MAX_CLIENTS || !self->NPC ) + {//only NPCs + return qfalse; + } + + if ( self->client->ps.forcePowerLevel[FP_LEVITATION] < FORCE_LEVEL_1 ) + {//only force-users + return qfalse; + } + + if ( self->client->moveType == MT_FLYSWIM ) + {//can't knock me down when I'm flying + return qtrue; + } + + if ( self->NPC && (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) + {//bosses always get out of a knockdown + } + else if ( Q_irand( 0, RANK_CAPTAIN+5 ) > self->NPC->rank ) + {//lower their rank, the more likely they are fall down + return qfalse; + } + + vec3_t pDir, fwd, right, ang = {0, self->currentAngles[YAW], 0}; + float fDot, rDot; + int strafeTime = Q_irand( 1000, 2000 ); + + AngleVectors( ang, fwd, right, NULL ); + VectorNormalize2( pushDir, pDir ); + fDot = DotProduct( pDir, fwd ); + rDot = DotProduct( pDir, right ); + + //flip or roll with it + usercmd_t tempCmd; + if ( fDot >= 0.4f ) + { + tempCmd.forwardmove = 127; + TIMER_Set( self, "moveforward", strafeTime ); + } + else if ( fDot <= -0.4f ) + { + tempCmd.forwardmove = -127; + TIMER_Set( self, "moveback", strafeTime ); + } + else if ( rDot > 0 ) + { + tempCmd.rightmove = 127; + TIMER_Set( self, "strafeRight", strafeTime ); + TIMER_Set( self, "strafeLeft", -1 ); + } + else + { + tempCmd.rightmove = -127; + TIMER_Set( self, "strafeLeft", strafeTime ); + TIMER_Set( self, "strafeRight", -1 ); + } + G_AddEvent( self, EV_JUMP, 0 ); + if ( !Q_irand( 0, 1 ) ) + {//flip + self->client->ps.forceJumpCharge = 280;//FIXME: calc this intelligently? + ForceJump( self, &tempCmd ); + } + else + {//roll + TIMER_Set( self, "duck", strafeTime ); + } + self->painDebounceTime = 0;//so we do something + + return qtrue; +} +extern void Boba_FireDecide( void ); +extern void RT_FireDecide( void ); +extern void Boba_FlyStart( gentity_t *self ); + + + + + +//=============================================================================================== +//TAVION BOSS +//=============================================================================================== +void NPC_TavionScepter_Precache( void ) +{ + G_EffectIndex( "scepter/beam_warmup.efx" ); + G_EffectIndex( "scepter/beam.efx" ); + G_EffectIndex( "scepter/slam_warmup.efx" ); + G_EffectIndex( "scepter/slam.efx" ); + G_EffectIndex( "scepter/impact.efx" ); + G_SoundIndex( "sound/weapons/scepter/loop.wav" ); + G_SoundIndex( "sound/weapons/scepter/slam_warmup.wav" ); + G_SoundIndex( "sound/weapons/scepter/beam_warmup.wav" ); +} + +void NPC_TavionSithSword_Precache( void ) +{ + G_EffectIndex( "scepter/recharge.efx" ); + G_EffectIndex( "scepter/invincibility.efx" ); + G_EffectIndex( "scepter/sword.efx" ); + G_SoundIndex( "sound/weapons/scepter/recharge.wav" ); +} + +void Tavion_ScepterDamage( void ) +{ + if ( !NPC->ghoul2.size() + || NPC->weaponModel[1] <= 0 ) + { + return; + } + + if ( NPC->genericBolt1 != -1 ) + { + int curTime = (cg.time?cg.time:level.time); + qboolean hit = qfalse; + int lastHit = ENTITYNUM_NONE; + for ( int time = curTime-25; time <= curTime+25&&!hit; time += 25 ) + { + mdxaBone_t boltMatrix; + vec3_t tip, dir, base, angles={0,NPC->currentAngles[YAW],0}; + trace_t trace; + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->weaponModel[1], + NPC->genericBolt1, + &boltMatrix, angles, NPC->currentOrigin, time, + NULL, NPC->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, base ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_X, dir ); + VectorMA( base, 512, dir, tip ); + #ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + G_DebugLine(base, tip, 1000, 0x000000ff, qtrue); + } + #endif + gi.trace( &trace, base, vec3_origin, vec3_origin, tip, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 10 ); + if ( trace.fraction < 1.0f ) + {//hit something + gentity_t *traceEnt = &g_entities[trace.entityNum]; + + //FIXME: too expensive! + //if ( time == curTime ) + {//UGH + G_PlayEffect( G_EffectIndex( "scepter/impact.efx" ), trace.endpos, trace.plane.normal ); + } + + if ( traceEnt->takedamage + && trace.entityNum != lastHit + && (!traceEnt->client || traceEnt == NPC->enemy || traceEnt->client->NPC_class != NPC->client->NPC_class) ) + {//smack + int dmg = Q_irand( 10, 20 )*(g_spskill->integer+1);//NOTE: was 6-12 + //FIXME: debounce? + //FIXME: do dismemberment + G_Damage( traceEnt, NPC, NPC, vec3_origin, trace.endpos, dmg, DAMAGE_NO_KNOCKBACK, MOD_SABER );//MOD_MELEE ); + if ( traceEnt->client ) + { + if ( !Q_irand( 0, 2 ) ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_CONFUSE1, EV_CONFUSE2 ), 10000 ); + } + else + { + G_AddVoiceEvent( NPC, EV_JDETECTED3, 10000 ); + } + G_Throw( traceEnt, dir, Q_flrand( 50, 80 ) ); + if ( traceEnt->health > 0 && !Q_irand( 0, 2 ) )//FIXME: base on skill! + {//do pain on enemy + G_Knockdown( traceEnt, NPC, dir, 300, qtrue ); + } + } + hit = qtrue; + lastHit = trace.entityNum; + } + } + } + } +} + +void Tavion_ScepterSlam( void ) +{ + if ( !NPC->ghoul2.size() + || NPC->weaponModel[1] <= 0 ) + { + return; + } + + int boltIndex = gi.G2API_AddBolt(&NPC->ghoul2[NPC->weaponModel[1]], "*weapon"); + if ( boltIndex != -1 ) + { + mdxaBone_t boltMatrix; + vec3_t handle, bottom, angles={0,NPC->currentAngles[YAW],0}; + trace_t trace; + gentity_t *radiusEnts[ 128 ]; + int numEnts; + const float radius = 300.0f; + const float halfRad = (radius/2); + float dist; + int i; + vec3_t mins, maxs, entDir; + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->weaponModel[1], + boltIndex, + &boltMatrix, angles, NPC->currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, handle ); + VectorCopy( handle, bottom ); + bottom[2] -= 128.0f; + + gi.trace( &trace, handle, vec3_origin, vec3_origin, bottom, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 10 ); + G_PlayEffect( G_EffectIndex( "scepter/slam.efx" ), trace.endpos, trace.plane.normal ); + + //Setup the bbox to search in + for ( i = 0; i < 3; i++ ) + { + mins[i] = trace.endpos[i] - radius; + maxs[i] = trace.endpos[i] + radius; + } + + //Get the number of entities in a given space + numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 ); + + for ( i = 0; i < numEnts; i++ ) + { + if ( !radiusEnts[i]->inuse ) + { + continue; + } + + if ( (radiusEnts[i]->flags&FL_NO_KNOCKBACK) ) + {//don't throw them back + continue; + } + + if ( radiusEnts[i] == NPC ) + {//Skip myself + continue; + } + + if ( radiusEnts[i]->client == NULL ) + {//must be a client + if ( G_EntIsBreakable( radiusEnts[i]->s.number, NPC ) ) + {//damage breakables within range, but not as much + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, 100, 0, MOD_EXPLOSIVE_SPLASH ); + } + continue; + } + + if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) + || (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) ) + {//can't be one being held + continue; + } + + VectorSubtract( radiusEnts[i]->currentOrigin, trace.endpos, entDir ); + dist = VectorNormalize( entDir ); + if ( dist <= radius ) + { + if ( dist < halfRad ) + {//close enough to do damage, too + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 20, 30 ), DAMAGE_NO_KNOCKBACK, MOD_EXPLOSIVE_SPLASH ); + } + if ( radiusEnts[i]->client + && radiusEnts[i]->client->NPC_class != CLASS_RANCOR + && radiusEnts[i]->client->NPC_class != CLASS_ATST ) + { + float throwStr = 0.0f; + if ( g_spskill->integer > 1 ) + { + throwStr = 10.0f+((radius-dist)/2.0f); + if ( throwStr > 150.0f ) + { + throwStr = 150.0f; + } + } + else + { + throwStr = 10.0f+((radius-dist)/4.0f); + if ( throwStr > 85.0f ) + { + throwStr = 85.0f; + } + } + entDir[2] += 0.1f; + VectorNormalize( entDir ); + G_Throw( radiusEnts[i], entDir, throwStr ); + if ( radiusEnts[i]->health > 0 ) + { + if ( dist < halfRad + || radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//within range of my fist or within ground-shaking range and not in the air + G_Knockdown( radiusEnts[i], NPC, vec3_origin, 500, qtrue ); + } + } + } + } + } + } +} + +void Tavion_StartScepterBeam( void ) +{ + G_PlayEffect( G_EffectIndex( "scepter/beam_warmup.efx" ), NPC->weaponModel[1], NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, 0, qtrue ); + G_SoundOnEnt( NPC, CHAN_ITEM, "sound/weapons/scepter/beam_warmup.wav" ); + NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SCEPTER_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoAnimTimer += 200; + NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer; + NPC->client->ps.pm_time = NPC->client->ps.torsoAnimTimer; + NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorClear( NPC->client->ps.velocity ); + VectorClear( NPC->client->ps.moveDir ); +} + +void Tavion_StartScepterSlam( void ) +{ + G_PlayEffect( G_EffectIndex( "scepter/slam_warmup.efx" ), NPC->weaponModel[1], NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, 0, qtrue ); + G_SoundOnEnt( NPC, CHAN_ITEM, "sound/weapons/scepter/slam_warmup.wav" ); + NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TAVION_SCEPTERGROUND, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer; + NPC->client->ps.pm_time = NPC->client->ps.torsoAnimTimer; + NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorClear( NPC->client->ps.velocity ); + VectorClear( NPC->client->ps.moveDir ); + NPC->count = 0; +} + +void Tavion_SithSwordRecharge( void ) +{ + if ( NPC->client->ps.torsoAnim != BOTH_TAVION_SWORDPOWER + && NPC->count + && TIMER_Done( NPC, "rechargeDebounce" ) + && NPC->weaponModel[0] != -1 ) + { + NPC->s.loopSound = G_SoundIndex( "sound/weapons/scepter/recharge.wav" ); + int boltIndex = gi.G2API_AddBolt(&NPC->ghoul2[NPC->weaponModel[0]], "*weapon"); + NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TAVION_SWORDPOWER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + G_PlayEffect( G_EffectIndex( "scepter/recharge.efx" ), NPC->weaponModel[0], boltIndex, NPC->s.number, NPC->currentOrigin, NPC->client->ps.torsoAnimTimer, qtrue ); + NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer; + NPC->client->ps.pm_time = NPC->client->ps.torsoAnimTimer; + NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorClear( NPC->client->ps.velocity ); + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.powerups[PW_INVINCIBLE] = level.time + NPC->client->ps.torsoAnimTimer + 10000; + G_PlayEffect( G_EffectIndex( "scepter/invincibility.efx" ), NPC->playerModel, 0, NPC->s.number, NPC->currentOrigin, NPC->client->ps.torsoAnimTimer + 10000, qfalse ); + TIMER_Set( NPC, "rechargeDebounce", NPC->client->ps.torsoAnimTimer + 10000 + Q_irand(10000,20000) ); + NPC->count--; + //now you have a chance of killing her + NPC->flags &= ~FL_UNDYING; + } +} + +//====================================================================================== +//END TAVION BOSS +//====================================================================================== + +void Jedi_Cloak( gentity_t *self ) +{ + if ( self && self->client ) + { + if ( !self->client->ps.powerups[PW_CLOAKED] ) + {//cloak + self->client->ps.powerups[PW_CLOAKED] = Q3_INFINITE; + self->client->ps.powerups[PW_UNCLOAKING] = level.time + 2000; + //FIXME: debounce attacks? + //FIXME: temp sound + G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/shadowtrooper/cloak.wav" ); + } + } +} + +void Jedi_Decloak( gentity_t *self ) +{ + if ( self && self->client ) + { + if ( self->client->ps.powerups[PW_CLOAKED] ) + {//Uncloak + self->client->ps.powerups[PW_CLOAKED] = 0; + self->client->ps.powerups[PW_UNCLOAKING] = level.time + 2000; + //FIXME: temp sound + G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/shadowtrooper/decloak.wav" ); + } + } +} + +void Jedi_CheckCloak( void ) +{ + if ( NPC + && NPC->client + && NPC->client->NPC_class == CLASS_SHADOWTROOPER + && Q_stricmpn("shadowtrooper", NPC->NPC_type, 13 ) == 0 ) + { + if ( NPC->client->ps.SaberActive() || + NPC->health <= 0 || + NPC->client->ps.saberInFlight || + (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) || + (NPC->client->ps.eFlags&EF_FORCE_DRAINED) || + NPC->painDebounceTime > level.time ) + {//can't be cloaked if saber is on, or dead or saber in flight or taking pain or being gripped + Jedi_Decloak( NPC ); + } + else if ( NPC->health > 0 + && !NPC->client->ps.saberInFlight + && !(NPC->client->ps.eFlags&EF_FORCE_GRIPPED) + && !(NPC->client->ps.eFlags&EF_FORCE_DRAINED) + && NPC->painDebounceTime < level.time ) + {//still alive, have saber in hand, not taking pain and not being gripped + Jedi_Cloak( NPC ); + } + } +} +/* +========================================================================================== +AGGRESSION +========================================================================================== +*/ +static void Jedi_Aggression( gentity_t *self, int change ) +{ + int upper_threshold, lower_threshold; + + self->NPC->stats.aggression += change; + + //FIXME: base this on initial NPC stats + if ( self->client->playerTeam == TEAM_PLAYER ) + {//good guys are less aggressive + upper_threshold = 7; + lower_threshold = 1; + } + else + {//bad guys are more aggressive + if ( self->client->NPC_class == CLASS_DESANN ) + { + upper_threshold = 20; + lower_threshold = 5; + } + else + { + upper_threshold = 10; + lower_threshold = 3; + } + } + + if ( self->NPC->stats.aggression > upper_threshold ) + { + self->NPC->stats.aggression = upper_threshold; + } + else if ( self->NPC->stats.aggression < lower_threshold ) + { + self->NPC->stats.aggression = lower_threshold; + } + //Com_Printf( "(%d) %s agg %d change: %d\n", level.time, self->NPC_type, self->NPC->stats.aggression, change ); +} + +static void Jedi_AggressionErosion( int amt ) +{ + if ( TIMER_Done( NPC, "roamTime" ) ) + {//the longer we're not alerted and have no enemy, the more our aggression goes down + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) ); + Jedi_Aggression( NPC, amt ); + } + + if ( NPCInfo->stats.aggression < 4 || (NPCInfo->stats.aggression < 6&&NPC->client->NPC_class == CLASS_DESANN)) + {//turn off the saber + WP_DeactivateSaber( NPC ); + } +} + +void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy ) +{ + float healthAggression; + float weaponAggression; + + switch( enemy->s.weapon ) + { + case WP_SABER: + healthAggression = (float)self->health/200.0f*6.0f; + weaponAggression = 7;//go after him + break; + case WP_BLASTER: + if ( DistanceSquared( self->currentOrigin, enemy->currentOrigin ) < 65536 )//256 squared + { + healthAggression = (float)self->health/200.0f*8.0f; + weaponAggression = 8;//go after him + } + else + { + healthAggression = 8.0f - ((float)self->health/200.0f*8.0f); + weaponAggression = 2;//hang back for a second + } + break; + default: + healthAggression = (float)self->health/200.0f*8.0f; + weaponAggression = 6;//approach + break; + } + //Average these with current aggression + int newAggression = ceil( (healthAggression + weaponAggression + (float)self->NPC->stats.aggression )/3.0f); + //Com_Printf( "(%d) new agg %d - new enemy\n", level.time, newAggression ); + Jedi_Aggression( self, newAggression - self->NPC->stats.aggression ); + + //don't taunt right away + TIMER_Set( self, "chatter", Q_irand( 4000, 7000 ) ); +} + +static void Jedi_Rage( void ) +{ + Jedi_Aggression( NPC, 10 - NPCInfo->stats.aggression + Q_irand( -2, 2 ) ); + TIMER_Set( NPC, "roamTime", 0 ); + TIMER_Set( NPC, "chatter", 0 ); + TIMER_Set( NPC, "walking", 0 ); + TIMER_Set( NPC, "taunting", 0 ); + TIMER_Set( NPC, "jumpChaseDebounce", 0 ); + TIMER_Set( NPC, "movenone", 0 ); + TIMER_Set( NPC, "movecenter", 0 ); + TIMER_Set( NPC, "noturn", 0 ); + ForceRage( NPC ); +} + +void Jedi_RageStop( gentity_t *self ) +{ + if ( self->NPC ) + {//calm down and back off + TIMER_Set( self, "roamTime", 0 ); + Jedi_Aggression( self, Q_irand( -5, 0 ) ); + } +} +/* +========================================================================================== +SPEAKING +========================================================================================== +*/ + +static qboolean Jedi_BattleTaunt( void ) +{ + if ( TIMER_Done( NPC, "chatter" ) + && !Q_irand( 0, 3 ) + && NPCInfo->blockedSpeechDebounceTime < level.time + && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time ) + { + int event = -1; + if ( NPC->enemy + && NPC->enemy->client + && (NPC->enemy->client->NPC_class == CLASS_RANCOR + || NPC->enemy->client->NPC_class == CLASS_WAMPA + || NPC->enemy->client->NPC_class == CLASS_SAND_CREATURE) ) + {//never taunt these mindless creatures + //NOTE: howlers? tusken? etc? Only reborn? + } + else + { + if ( NPC->client->playerTeam == TEAM_PLAYER + && NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_JEDI ) + {//a jedi fighting a jedi - training + if ( NPC->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) + {//only trainer taunts + event = EV_TAUNT1; + } + } + else + {//reborn or a jedi fighting an enemy + event = Q_irand( EV_TAUNT1, EV_TAUNT3 ); + } + if ( event != -1 ) + { + G_AddVoiceEvent( NPC, event, 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 6000; + if ( (NPCInfo->aiFlags&NPCAI_ROSH) ) + { + TIMER_Set( NPC, "chatter", Q_irand( 8000, 20000 ) ); + } + else + { + TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) ); + } + + if ( NPC->enemy && NPC->enemy->NPC && NPC->enemy->s.weapon == WP_SABER && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_JEDI ) + {//Have the enemy jedi say something in response when I'm done? + } + return qtrue; + } + } + } + return qfalse; +} + +/* +========================================================================================== +MOVEMENT +========================================================================================== +*/ +static qboolean Jedi_ClearPathToSpot( vec3_t dest, int impactEntNum ) +{ + trace_t trace; + vec3_t mins, start, end, dir; + float dist, drop; + + //Offset the step height + VectorSet( mins, NPC->mins[0], NPC->mins[1], NPC->mins[2] + STEPSIZE ); + + gi.trace( &trace, NPC->currentOrigin, mins, NPC->maxs, dest, NPC->s.number, NPC->clipmask ); + + //Do a simple check + if ( trace.allsolid || trace.startsolid ) + {//inside solid + return qfalse; + } + + if ( trace.fraction < 1.0f ) + {//hit something + if ( impactEntNum != ENTITYNUM_NONE && trace.entityNum == impactEntNum ) + {//hit what we're going after + return qtrue; + } + else + { + return qfalse; + } + } + + //otherwise, clear path in a straight line. + //Now at intervals of my size, go along the trace and trace down STEPSIZE to make sure there is a solid floor. + VectorSubtract( dest, NPC->currentOrigin, dir ); + dist = VectorNormalize( dir ); + if ( dest[2] > NPC->currentOrigin[2] ) + {//going up, check for steps + drop = STEPSIZE; + } + else + {//going down or level, check for moderate drops + drop = 64; + } + for ( float i = NPC->maxs[0]*2; i < dist; i += NPC->maxs[0]*2 ) + {//FIXME: does this check the last spot, too? We're assuming that should be okay since the enemy is there? + VectorMA( NPC->currentOrigin, i, dir, start ); + VectorCopy( start, end ); + end[2] -= drop; + gi.trace( &trace, start, mins, NPC->maxs, end, NPC->s.number, NPC->clipmask );//NPC->mins? + if ( trace.fraction < 1.0f || trace.allsolid || trace.startsolid ) + {//good to go + continue; + } + //no floor here! (or a long drop?) + return qfalse; + } + //we made it! + return qtrue; +} + +qboolean NPC_MoveDirClear( int forwardmove, int rightmove, qboolean reset ) +{ + vec3_t forward, right, testPos, angles, mins; + trace_t trace; + float fwdDist, rtDist; + float bottom_max = -STEPSIZE*4 - 1; + + if ( !forwardmove && !rightmove ) + {//not even moving + //gi.Printf( "%d skipping walk-cliff check (not moving)\n", level.time ); + return qtrue; + } + + if ( ucmd.upmove > 0 || NPC->client->ps.forceJumpCharge ) + {//Going to jump + //gi.Printf( "%d skipping walk-cliff check (going to jump)\n", level.time ); + return qtrue; + } + + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//in the air + //gi.Printf( "%d skipping walk-cliff check (in air)\n", level.time ); + return qtrue; + } + /* + if ( fabs( AngleDelta( NPC->currentAngles[YAW], NPCInfo->desiredYaw ) ) < 5.0 )//!ucmd.angles[YAW] ) + {//Not turning much, don't do this + //NOTE: Should this not happen only if you're not turning AT ALL? + // You could be turning slowly but moving fast, so that would + // still let you walk right off a cliff... + //NOTE: Or maybe it is a good idea to ALWAYS do this, regardless + // of whether ot not we're turning? But why would we be walking + // straight into a wall or off a cliff unless we really wanted to? + return; + } + */ + + //FIXME: to really do this right, we'd have to actually do a pmove to predict where we're + //going to be... maybe this should be a flag and pmove handles it and sets a flag so AI knows + //NEXT frame? Or just incorporate current velocity, runspeed and possibly friction? + VectorCopy( NPC->mins, mins ); + mins[2] += STEPSIZE; + angles[PITCH] = angles[ROLL] = 0; + angles[YAW] = NPC->client->ps.viewangles[YAW];//Add ucmd.angles[YAW]? + AngleVectors( angles, forward, right, NULL ); + fwdDist = ((float)forwardmove)/2.0f; + rtDist = ((float)rightmove)/2.0f; + VectorMA( NPC->currentOrigin, fwdDist, forward, testPos ); + VectorMA( testPos, rtDist, right, testPos ); + gi.trace( &trace, NPC->currentOrigin, mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + if ( trace.allsolid || trace.startsolid ) + {//hmm, trace started inside this brush... how do we decide if we should continue? + //FIXME: what do we do if we start INSIDE a CONTENTS_BOTCLIP? Try the trace again without that in the clipmask? + if ( reset ) + { + trace.fraction = 1.0f; + } + VectorCopy( testPos, trace.endpos ); + //return qtrue; + } + if ( trace.fraction < 0.6 ) + {//Going to bump into something very close, don't move, just turn + if ( (NPC->enemy && trace.entityNum == NPC->enemy->s.number) || (NPCInfo->goalEntity && trace.entityNum == NPCInfo->goalEntity->s.number) ) + {//okay to bump into enemy or goal + //gi.Printf( "%d bump into enemy/goal okay\n", level.time ); + return qtrue; + } + else if ( reset ) + {//actually want to screw with the ucmd + //gi.Printf( "%d avoiding walk into wall (entnum %d)\n", level.time, trace.entityNum ); + ucmd.forwardmove = 0; + ucmd.rightmove = 0; + VectorClear( NPC->client->ps.moveDir ); + } + return qfalse; + } + + if ( NPCInfo->goalEntity ) + { + if ( NPCInfo->goalEntity->currentOrigin[2] < NPC->currentOrigin[2] ) + {//goal is below me, okay to step off at least that far plus stepheight + bottom_max += NPCInfo->goalEntity->currentOrigin[2] - NPC->currentOrigin[2]; + } + } + VectorCopy( trace.endpos, testPos ); + testPos[2] += bottom_max; + + gi.trace( &trace, trace.endpos, mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask ); + + //FIXME:Should we try to see if we can still get to our goal using the waypoint network from this trace.endpos? + //OR: just put NPC clip brushes on these edges (still fall through when die) + + if ( trace.allsolid || trace.startsolid ) + {//Not going off a cliff + //gi.Printf( "%d walk off cliff okay (droptrace in solid)\n", level.time ); + return qtrue; + } + + if ( trace.fraction < 1.0 ) + {//Not going off a cliff + //FIXME: what if plane.normal is sloped? We'll slide off, not land... plus this doesn't account for slide-movement... + //gi.Printf( "%d walk off cliff okay will hit entnum %d at dropdist of %4.2f\n", level.time, trace.entityNum, (trace.fraction*bottom_max) ); + return qtrue; + } + + //going to fall at least bottom_max, don't move, just turn... is this bad, though? What if we want them to drop off? + if ( reset ) + {//actually want to screw with the ucmd + //gi.Printf( "%d avoiding walk off cliff\n", level.time ); + ucmd.forwardmove *= -1.0;//= 0; + ucmd.rightmove *= -1.0;//= 0; + VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + } + return qfalse; +} +/* +------------------------- +Jedi_HoldPosition +------------------------- +*/ + +static void Jedi_HoldPosition( void ) +{ + //NPCInfo->squadState = SQUAD_STAND_AND_SHOOT; + NPCInfo->goalEntity = NULL; + + /* + if ( TIMER_Done( NPC, "stand" ) ) + { + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +/* +------------------------- +Jedi_Move +------------------------- +*/ + +static qboolean Jedi_Move( gentity_t *goal, qboolean retreat ) +{ + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = goal; + + qboolean moved = NPC_MoveToGoal( qtrue ); + if (!moved) + { + Jedi_HoldPosition(); + } + + // NAV_TODO: Put Retreate Behavior Here + //FIXME: temp retreat behavior- should really make this toward a safe spot or maybe to outflank enemy + if ( retreat ) + {//FIXME: should we trace and make sure we can go this way? Or somehow let NPC_MoveToGoal know we want to retreat and have it handle it? + ucmd.forwardmove *= -1; + ucmd.rightmove *= -1; + //we clear moveDir here so the Jedi's ucmd-driven movement does do not enter checks + VectorClear( NPC->client->ps.moveDir ); + //VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + } + return moved; +} + +static qboolean Jedi_Hunt( void ) +{ + //gi.Printf( "Hunting\n" ); + //if we're at all interested in fighting, go after him + if ( NPCInfo->stats.aggression > 1 ) + {//approach enemy + NPCInfo->combatMove = qtrue; + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + else + { + /* if ( NPCInfo->goalEntity == NULL ) + {//hunt + NPCInfo->goalEntity = NPC->enemy; + } + if (NPC->client && NPC->client->NPC_class==CLASS_BOBAFETT) + { + NPCInfo->goalEntity = NPC->enemy; + }*/ +// NPC_SetMoveGoal(NPC, NPC->enemy->currentOrigin, 40.0f, false, 0, NPC->enemy); + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 40.0f; + + //Jedi_Move( NPC->enemy, qfalse ); + if ( NPC_MoveToGoal( qfalse ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + } + return qfalse; +} + +/* +static qboolean Jedi_Track( void ) +{ + //if we're at all interested in fighting, go after him + if ( NPCInfo->stats.aggression > 1 ) + {//approach enemy + NPCInfo->combatMove = qtrue; + NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 16, qtrue ); + if ( NPC_MoveToGoal( qfalse ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + return qfalse; +} +*/ + +static void Jedi_StartBackOff( void ) +{ + TIMER_Set( NPC, "roamTime", -level.time ); + TIMER_Set( NPC, "strafeLeft", -level.time ); + TIMER_Set( NPC, "strafeRight", -level.time ); + TIMER_Set( NPC, "walking", -level.time ); + TIMER_Set( NPC, "moveforward", -level.time ); + TIMER_Set( NPC, "movenone", -level.time ); + TIMER_Set( NPC, "moveright", -level.time ); + TIMER_Set( NPC, "moveleft", -level.time ); + TIMER_Set( NPC, "movecenter", -level.time ); + TIMER_Set( NPC, "moveback", 1000 ); + ucmd.forwardmove = -127; + ucmd.rightmove = 0; + ucmd.upmove = 0; + if ( d_JediAI->integer ) + { + Com_Printf( "%s backing off from spin attack!\n", NPC->NPC_type ); + } + TIMER_Set( NPC, "specialEvasion", 1000 ); + TIMER_Set( NPC, "noRetreat", -level.time ); + if ( PM_PainAnim(NPC->client->ps.legsAnim) ) + { + NPC->client->ps.legsAnimTimer = 0; + } + VectorClear( NPC->client->ps.moveDir ); +} + +static qboolean Jedi_Retreat( void ) +{ + if ( !TIMER_Done( NPC, "noRetreat" ) ) + {//don't actually move + return qfalse; + } + //FIXME: when retreating, we should probably see if we can retreat + //in the direction we want. If not...? Evade? + //gi.Printf( "Retreating\n" ); + return Jedi_Move( NPC->enemy, qtrue ); +} + +static qboolean Jedi_Advance( void ) +{ + if ( (NPCInfo->aiFlags&NPCAI_HEAL_ROSH) ) + { + return qfalse; + } + if ( !NPC->client->ps.saberInFlight ) + { + NPC->client->ps.SaberActivate(); + } + //gi.Printf( "Advancing\n" ); + return Jedi_Move( NPC->enemy, qfalse ); + + //TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) ); + //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); + //TIMER_Set( NPC, "duck", 0 ); +} + +static void Jedi_AdjustSaberAnimLevel( gentity_t *self, int newLevel ) +{ + if ( !self || !self->client ) + { + return; + } + //FIXME: each NPC shold have a unique pattern of behavior for the order in which they + if ( self->client->playerTeam == TEAM_ENEMY ) + { + //FIXME: CLASS_CULTIST + self->NPC->rank instead of these Q_stricmps? + if ( !Q_stricmp( "cultist_saber_all", self->NPC_type ) + || !Q_stricmp( "cultist_saber_all_throw", self->NPC_type ) ) + {//use any, regardless of rank, etc. + } + else if ( !Q_stricmp( "cultist_saber", self->NPC_type ) + || !Q_stricmp( "cultist_saber_throw", self->NPC_type ) ) + {//fast only + self->client->ps.saberAnimLevel = SS_FAST; + } + else if ( !Q_stricmp( "cultist_saber_med", self->NPC_type ) + || !Q_stricmp( "cultist_saber_med_throw", self->NPC_type ) ) + {//med only + self->client->ps.saberAnimLevel = SS_MEDIUM; + } + else if ( !Q_stricmp( "cultist_saber_strong", self->NPC_type ) + || !Q_stricmp( "cultist_saber_strong_throw", self->NPC_type ) ) + {//strong only + self->client->ps.saberAnimLevel = SS_STRONG; + } + else + {//regular reborn + if ( self->NPC->rank == RANK_CIVILIAN || self->NPC->rank == RANK_LT_JG ) + {//grunt and fencer always uses quick attacks + self->client->ps.saberAnimLevel = SS_FAST; + return; + } + if ( self->NPC->rank == RANK_CREWMAN + || self->NPC->rank == RANK_ENSIGN ) + {//acrobat & force-users always use medium attacks + self->client->ps.saberAnimLevel = SS_MEDIUM; + return; + } + /* + if ( self->NPC->rank == RANK_LT ) + {//boss always uses strong attacks + self->client->ps.saberAnimLevel = SS_STRONG; + return; + } + */ + } + } + if ( newLevel < SS_FAST ) + { + newLevel = SS_FAST; + } + else if ( newLevel > SS_STAFF ) + { + newLevel = SS_STAFF; + } + //use the different attacks, how often they switch and under what circumstances + if ( !(self->client->ps.saberStylesKnown&(1<client->ps.saberAnimLevel = newLevel; + } + + if ( d_JediAI->integer ) + { + switch ( self->client->ps.saberAnimLevel ) + { + case SS_FAST: + gi.Printf( S_COLOR_GREEN"%s Saber Attack Set: fast\n", self->NPC_type ); + break; + case SS_MEDIUM: + gi.Printf( S_COLOR_YELLOW"%s Saber Attack Set: medium\n", self->NPC_type ); + break; + case SS_STRONG: + gi.Printf( S_COLOR_RED"%s Saber Attack Set: strong\n", self->NPC_type ); + break; + } + } +} + +static void Jedi_CheckDecreaseSaberAnimLevel( void ) +{ + if ( !NPC->client->ps.weaponTime && !(ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS)) ) + {//not attacking + if ( TIMER_Done( NPC, "saberLevelDebounce" ) && !Q_irand( 0, 10 ) ) + { + //Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel-1) );//drop + Jedi_AdjustSaberAnimLevel( NPC, Q_irand( SS_FAST, SS_STRONG ));//random + TIMER_Set( NPC, "saberLevelDebounce", Q_irand( 3000, 10000 ) ); + } + } + else + { + TIMER_Set( NPC, "saberLevelDebounce", Q_irand( 1000, 5000 ) ); + } +} + +static qboolean Jedi_DecideKick( void ) +{ + if ( PM_InKnockDown( &NPC->client->ps ) ) + { + return qfalse; + } + if ( PM_InRoll( &NPC->client->ps ) ) + { + return qfalse; + } + if ( PM_InGetUp( &NPC->client->ps ) ) + { + return qfalse; + } + if ( !NPC->enemy || (NPC->enemy->s.number < MAX_CLIENTS&&NPC->enemy->health<=0) ) + {//have no enemy or enemy is a dead player + return qfalse; + } + //FIXME: check FP_SABER_OFFENSE? + //FIXME: check for saber staff style only? + //FIXME: g_spskill? + if ( Q_irand( 0, RANK_CAPTAIN+5 ) > NPCInfo->rank ) + {//low chance, based on rank + return qfalse; + } + if ( Q_irand( 0, 10 ) > NPCInfo->stats.aggression ) + {//the madder the better + return qfalse; + } + if ( !TIMER_Done( NPC, "kickDebounce" ) ) + {//just did one + return qfalse; + } + //go for it! + return qtrue; +} + +void Kyle_GrabEnemy( void ) +{ + WP_SabersCheckLock2( NPC, NPC->enemy, (sabersLockMode_t)Q_irand(LOCK_KYLE_GRAB1,LOCK_KYLE_GRAB2) );//LOCK_KYLE_GRAB3 + TIMER_Set( NPC, "grabEnemyDebounce", NPC->client->ps.torsoAnimTimer + Q_irand( 4000, 20000 ) ); +} + +void Kyle_TryGrab( void ) +{ + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_KYLE_GRAB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoAnimTimer += 200; + NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer; + NPC->client->ps.saberMove = NPC->client->ps.saberMoveNext = LS_READY; + VectorClear( NPC->client->ps.velocity ); + VectorClear( NPC->client->ps.moveDir ); + ucmd.rightmove = ucmd.forwardmove = ucmd.upmove = 0; + NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer; + //WTF? + NPC->client->ps.SaberDeactivate(); +} + +qboolean Kyle_CanDoGrab( void ) +{ + if ( NPC->client->NPC_class == CLASS_KYLE && (NPC->spawnflags&1) ) + {//Boss Kyle + if ( NPC->enemy && NPC->enemy->client ) + {//have a valid enemy + if ( TIMER_Done( NPC, "grabEnemyDebounce" ) ) + {//okay to grab again + if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE + && NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//me and enemy are on ground + if ( !PM_InOnGroundAnim( &NPC->enemy->client->ps ) ) + { + if ( (NPC->client->ps.weaponTime <= 200||NPC->client->ps.torsoAnim==BOTH_KYLE_GRAB) + && !NPC->client->ps.saberInFlight ) + { + if ( fabs(NPC->enemy->currentOrigin[2]-NPC->currentOrigin[2])<=8.0f ) + {//close to same level of ground + if ( DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ) <= 10000.0f ) + { + return qtrue; + } + } + } + } + } + } + } + } + return qfalse; +} + +static void Jedi_CombatDistance( int enemy_dist ) +{//FIXME: for many of these checks, what we really want is horizontal distance to enemy + if ( Jedi_CultistDestroyer( NPC ) ) + {//destroyer + Jedi_Advance(); + //always run, regardless of what navigation tells us to do! + NPC->client->ps.speed = NPCInfo->stats.runSpeed; + ucmd.buttons &= ~BUTTON_WALKING; + return; + } + if ( enemy_dist < 128 + && NPC->enemy + && NPC->enemy->client + && (NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK6 + || NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK7 ) ) + {//whoa, back off!!! + if ( Q_irand( -3, NPCInfo->rank ) > RANK_CREWMAN ) + { + Jedi_StartBackOff(); + return; + } + } + if ( NPC->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//when gripping, don't move + return; + } + else if ( !TIMER_Done( NPC, "gripping" ) ) + {//stopped gripping, clear timers just in case + TIMER_Set( NPC, "gripping", -level.time ); + TIMER_Set( NPC, "attackDelay", Q_irand( 0, 1000 ) ); + } + + if ( NPC->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 ) + {//when draining, don't move + return; + } + else if ( !TIMER_Done( NPC, "draining" ) ) + {//stopped draining, clear timers just in case + TIMER_Set( NPC, "draining", -level.time ); + TIMER_Set( NPC, "attackDelay", Q_irand( 0, 1000 ) ); + } + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( !TIMER_Done( NPC, "flameTime" ) ) + { + if ( enemy_dist > 50 ) + { + Jedi_Advance(); + } + else if ( enemy_dist <= 0 ) + { + Jedi_Retreat(); + } + } + else if ( enemy_dist < 200 ) + { + Jedi_Retreat(); + } + else if ( enemy_dist > 1024 ) + { + Jedi_Advance(); + } + } + else if ( NPC->client->ps.legsAnim == BOTH_ALORA_SPIN_THROW ) + {//don't move at all + //FIXME: sabers need trails + } + else if ( NPC->client->ps.torsoAnim == BOTH_KYLE_GRAB ) + {//see if we grabbed enemy + if ( NPC->client->ps.torsoAnimTimer <= 200 ) + { + if ( Kyle_CanDoGrab() + && NPC_EnemyRangeFromBolt( NPC->handRBolt ) <= 72.0f ) + {//grab him! + Kyle_GrabEnemy(); + return; + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer; + return; + } + } + //else just sit here? + return; + } + else if ( NPC->client->ps.saberInFlight && + !PM_SaberInBrokenParry( NPC->client->ps.saberMove ) + && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//maintain distance + if ( enemy_dist < NPC->client->ps.saberEntityDist ) + { + Jedi_Retreat(); + } + else if ( enemy_dist > NPC->client->ps.saberEntityDist && enemy_dist > 100 ) + { + Jedi_Advance(); + } + if ( NPC->client->ps.weapon == WP_SABER //using saber + && NPC->client->ps.saberEntityState == SES_LEAVING //not returning yet + && NPC->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1 //2nd or 3rd level lightsaber + && !(NPC->client->ps.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//hold it out there + ucmd.buttons |= BUTTON_ALT_ATTACK; + //FIXME: time limit? + } + } + else if ( !TIMER_Done( NPC, "taunting" ) ) + { + if ( enemy_dist <= 64 ) + {//he's getting too close + ucmd.buttons |= BUTTON_ATTACK; + if ( !NPC->client->ps.saberInFlight ) + { + NPC->client->ps.SaberActivate(); + } + TIMER_Set( NPC, "taunting", -level.time ); + } + else if ( NPC->client->ps.torsoAnim == BOTH_GESTURE1 && NPC->client->ps.torsoAnimTimer < 2000 ) + {//we're almost done with our special taunt + //FIXME: this doesn't always work, for some reason + if ( !NPC->client->ps.saberInFlight ) + { + NPC->client->ps.SaberActivate(); + } + } + } + else if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON ) + {//we won a saber lock, press the advantage + if ( enemy_dist > 0 ) + {//get closer so we can hit! + Jedi_Advance(); + } + if ( enemy_dist > 128 ) + {//lost 'em + NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON; + } + if ( NPC->enemy->painDebounceTime + 2000 < level.time ) + {//the window of opportunity is gone + NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON; + } + //don't strafe? + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + } + else if ( NPC->enemy->client + && NPC->enemy->s.weapon == WP_SABER + && NPC->enemy->client->ps.saberLockTime > level.time + && NPC->client->ps.saberLockTime < level.time ) + {//enemy is in a saberLock and we are not + if ( enemy_dist < 64 ) + {//FIXME: maybe just pick another enemy? + Jedi_Retreat(); + } + } + else if ( NPC->enemy->s.weapon == WP_TURRET + && !Q_stricmp( "PAS", NPC->enemy->classname ) + && NPC->enemy->s.apos.trType == TR_STATIONARY ) + { + if ( enemy_dist > forcePushPullRadius[FORCE_LEVEL_1] - 16 ) + { + Jedi_Advance(); + } + int testlevel; + if ( NPC->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1 ) + {// + testlevel = FORCE_LEVEL_1; + } + else + { + testlevel = NPC->client->ps.forcePowerLevel[FP_PUSH]; + } + if ( enemy_dist < forcePushPullRadius[testlevel] - 16 ) + {//close enough to push + if ( InFront( NPC->enemy->currentOrigin, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, 0.6f ) ) + {//knock it down + WP_KnockdownTurret( NPC, NPC->enemy ); + //do the forcethrow call just for effect + ForceThrow( NPC, qfalse ); + } + } + } + else if ( enemy_dist <= 64 + && ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||(!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,10))) ) + {//can't use saber and they're in striking range + if ( !Q_irand( 0, 5 ) && InFront( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.2f ) ) + { + if ( ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||NPC->max_health - NPC->health > NPC->max_health*0.25f)//lost over 1/4 of our health or not firing + && WP_ForcePowerUsable( NPC, FP_DRAIN, 20 )//know how to drain and have enough power + && !Q_irand( 0, 2 ) ) + {//drain + TIMER_Set( NPC, "draining", 3000 ); + TIMER_Set( NPC, "attackDelay", 3000 ); + Jedi_Advance(); + return; + } + else + { + if ( Jedi_DecideKick() ) + {//let's try a kick + if ( G_PickAutoMultiKick( NPC, qfalse, qtrue ) != LS_NONE + || (G_CanKickEntity(NPC, NPC->enemy ) && G_PickAutoKick( NPC, NPC->enemy, qtrue ) != LS_NONE ) ) + {//kicked! + TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) ); + return; + } + } + ForceThrow( NPC, qfalse ); + } + } + Jedi_Retreat(); + } + else if ( enemy_dist <= 64 + && NPC->max_health - NPC->health > NPC->max_health*0.25f//lost over 1/4 of our health + && NPC->client->ps.forcePowersKnown&(1<enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 0.2f ) ) + { + TIMER_Set( NPC, "draining", 3000 ); + TIMER_Set( NPC, "attackDelay", 3000 ); + Jedi_Advance(); + return; + } + else if ( enemy_dist <= -16 ) + {//we're too damn close! + if ( !Q_irand( 0, 30 ) + && Kyle_CanDoGrab() ) + { + Kyle_TryGrab(); + return; + } + else if ( NPC->client->ps.stats[STAT_WEAPONS]&(1<enemy ) && G_PickAutoKick( NPC, NPC->enemy, qtrue ) != LS_NONE ) ) + {//kicked! + TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) ); + return; + } + } + Jedi_Retreat(); + } + else if ( enemy_dist <= 0 ) + {//we're within striking range + //if we are attacking, see if we should stop + if ( NPCInfo->stats.aggression < 4 ) + {//back off and defend + if ( !Q_irand( 0, 30 ) + && Kyle_CanDoGrab() ) + { + Kyle_TryGrab(); + return; + } + else if ( NPC->client->ps.stats[STAT_WEAPONS]&(1<enemy ) && G_PickAutoKick( NPC, NPC->enemy, qtrue ) != LS_NONE ) ) + {//kicked! + TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) ); + return; + } + } + Jedi_Retreat(); + } + } + else if ( enemy_dist > 256 ) + {//we're way out of range + qboolean usedForce = qfalse; + if ( NPCInfo->stats.aggression < Q_irand( 0, 20 ) + && NPC->health < NPC->max_health*0.75f + && !Q_irand( 0, 2 ) ) + { + if ( NPC->enemy + && NPC->enemy->s.number < MAX_CLIENTS + && NPC->client->NPC_class!=CLASS_KYLE + && ((NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) + || NPC->client->NPC_class==CLASS_SHADOWTROOPER) + && Q_irand(0, 3-g_spskill->integer) ) + {//hmm, bosses should do this less against the player + } + else if ( NPC->client->ps.saber[0].type == SABER_SITH_SWORD + && NPC->weaponModel[0] != -1 ) + { + Tavion_SithSwordRecharge(); + usedForce = qtrue; + } + else if ( (NPC->client->ps.forcePowersKnown&(1<client->ps.forcePowersActive&(1<client->ps.forcePowersKnown&(1<client->ps.forcePowersActive&(1<client->ps.forcePowersKnown&(1<client->ps.forcePowersActive&(1<client->ps.forcePowersKnown&(1<client->ps.forcePowersActive&(1< 384 ) + {//FIXME: check for enemy facing away and/or moving away + if ( !Q_irand( 0, 10 ) && NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time ) + { + if ( NPC_ClearLOS( NPC->enemy ) ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_JCHASE1, EV_JCHASE3 ), 3000 ); + } + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + } + //Unless we're totally hiding, go after him + if ( NPCInfo->stats.aggression > 0 ) + {//approach enemy + if ( !usedForce ) + { + if ( NPC->enemy + && NPC->enemy->client + && (NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK6 + || NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK7 ) ) + {//stay put! + } + else + { + Jedi_Advance(); + } + } + } + } + /* + else if ( enemy_dist < 96 && NPC->enemy && NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//too close and in air, so retreat + Jedi_Retreat(); + } + */ + //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade.... + else if ( enemy_dist > 50 )//FIXME: not hardcoded- base on our reach (modelScale?) and saberLengthMax + {//we're out of striking range and we are allowed to attack + //first, check some tactical force power decisions + if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.eFlags&EF_FORCE_GRIPPED) ) + {//They're being gripped, rush them! + if ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//far away or allowed to use saber + Jedi_Advance(); + } + } + } + if ( (NPCInfo->rank >= RANK_LT_JG||WP_ForcePowerUsable( NPC, FP_SABERTHROW, 0 )) + && !Q_irand( 0, 5 ) + && !(NPC->client->ps.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//throw saber + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + } + else if ( NPC->enemy && NPC->enemy->client && //valid enemy + NPC->enemy->client->ps.saberInFlight && NPC->enemy->client->ps.saber[0].Active() && //enemy throwing saber + !NPC->client->ps.weaponTime && //I'm not busy + WP_ForcePowerAvailable( NPC, FP_GRIP, 0 ) && //I can use the power + !Q_irand( 0, 10 ) && //don't do it all the time, averages to 1 check a second + Q_irand( 0, 6 ) < g_spskill->integer && //more likely on harder diff + Q_irand( RANK_CIVILIAN, RANK_CAPTAIN ) < NPCInfo->rank //more likely against harder enemies + && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 20, 30 ) ) + {//They're throwing their saber, grip them! + //taunt + if ( TIMER_Done( NPC, "chatter" ) && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && NPCInfo->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + if ( (NPCInfo->aiFlags&NPCAI_ROSH) ) + { + TIMER_Set( NPC, "chatter", 6000 ); + } + else + { + TIMER_Set( NPC, "chatter", 3000 ); + } + } + + //grip + TIMER_Set( NPC, "gripping", 3000 ); + TIMER_Set( NPC, "attackDelay", 3000 ); + } + else + { + if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.forcePowersActive&(1<enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//far away or allowed to use saber + Jedi_Advance(); + } + } + } + } + if ( NPC->client->NPC_class == CLASS_KYLE + && (NPC->spawnflags&1) + && (NPC->enemy&&NPC->enemy->client&&!NPC->enemy->client->ps.saberInFlight) + && TIMER_Done( NPC, "kyleTakesSaber" ) + && !Q_irand( 0, 20 ) ) + { + ForceThrow( NPC, qtrue ); + } + else if ( NPC->client->ps.stats[STAT_WEAPONS]&(1<client->NPC_class == CLASS_KYLE && (NPC->spawnflags&1) ) + { + chanceScale = 4; + } + else if ( NPC->enemy + && NPC->enemy->s.number < MAX_CLIENTS + && ((NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) + || NPC->client->NPC_class==CLASS_SHADOWTROOPER) ) + {//hmm, bosses do this less against player + chanceScale = 8 - g_spskill->integer*2; + } + else if ( NPC->client->NPC_class == CLASS_DESANN + || !Q_stricmp("Yoda",NPC->NPC_type) ) + //|| (NPC->client->NPC_class == CLASS_CULTIST && NPC->client->ps.weapon == WP_NONE) )//force-only cultists use force a lot + { + chanceScale = 1; + } + else if ( NPCInfo->rank == RANK_ENSIGN ) + { + chanceScale = 2; + } + else if ( NPCInfo->rank >= RANK_LT_JG ) + { + chanceScale = 5; + } + if ( chanceScale + && (enemy_dist > Q_irand( 100, 200 ) || (NPCInfo->scriptFlags&SCF_DONT_FIRE) || (!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,3)) ) + && enemy_dist < 500 + && (Q_irand( 0, chanceScale*10 )<5 || (NPC->enemy->client && NPC->enemy->client->ps.weapon != WP_SABER && !Q_irand( 0, chanceScale ) ) ) ) + {//else, randomly try some kind of attack every now and then + //FIXME: Cultist fencers don't have any of these fancy powers + // the only thing they might be able to do is throw their saber + if ( (NPCInfo->rank == RANK_ENSIGN //old reborn crap + || NPCInfo->rank > RANK_LT_JG //old reborn crap + /* + || WP_ForcePowerUsable( NPC, FP_PULL, 0 ) + || WP_ForcePowerUsable( NPC, FP_LIGHTNING, 0 ) + || WP_ForcePowerUsable( NPC, FP_DRAIN, 0 ) + || WP_ForcePowerUsable( NPC, FP_GRIP, 0 ) + || WP_ForcePowerUsable( NPC, FP_SABERTHROW, 0 ) + */ + ) + && (!Q_irand( 0, 1 ) || NPC->s.weapon != WP_SABER) ) + { + if ( WP_ForcePowerUsable( NPC, FP_PULL, 0 ) && !Q_irand( 0, 2 ) ) + { + //force pull the guy to me! + //FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]] + ForceThrow( NPC, qtrue ); + //maybe strafe too? + TIMER_Set( NPC, "duck", enemy_dist*3 ); + if ( Q_irand( 0, 1 ) ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + } + else if ( WP_ForcePowerUsable( NPC, FP_LIGHTNING, 0 ) + && ((NPCInfo->scriptFlags&SCF_DONT_FIRE)&&Q_stricmp("cultist_lightning",NPC->NPC_type) || Q_irand( 0, 1 )) ) + { + ForceLightning( NPC ); + if ( NPC->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 ) + { + NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_spskill->integer*500) ); + TIMER_Set( NPC, "holdLightning", NPC->client->ps.weaponTime ); + } + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + } + else if ( NPC->health < NPC->max_health * 0.75f + && Q_irand( FORCE_LEVEL_0, NPC->client->ps.forcePowerLevel[FP_DRAIN] ) > FORCE_LEVEL_1 + && WP_ForcePowerUsable( NPC, FP_DRAIN, 0 ) + && ((NPCInfo->scriptFlags&SCF_DONT_FIRE)&&Q_stricmp("cultist_drain",NPC->NPC_type) || Q_irand( 0, 1 )) ) + { + ForceDrain2( NPC ); + NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_spskill->integer*500) ); + TIMER_Set( NPC, "draining", NPC->client->ps.weaponTime ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + } + else if ( WP_ForcePowerUsable( NPC, FP_GRIP, 0 ) + && NPC->enemy && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 20, 30 ) ) + { + //taunt + if ( TIMER_Done( NPC, "chatter" ) && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && NPCInfo->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + if ( (NPCInfo->aiFlags&NPCAI_ROSH) ) + { + TIMER_Set( NPC, "chatter", 6000 ); + } + else + { + TIMER_Set( NPC, "chatter", 3000 ); + } + } + + //grip + TIMER_Set( NPC, "gripping", 3000 ); + TIMER_Set( NPC, "attackDelay", 3000 ); + } + else + { + if ( WP_ForcePowerUsable( NPC, FP_SABERTHROW, 0 ) + && !(NPC->client->ps.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//throw saber + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + } + } + else + { + if ( (NPCInfo->rank >= RANK_LT_JG||WP_ForcePowerUsable( NPC, FP_SABERTHROW, 0 )) + && !(NPC->client->ps.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//throw saber + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + } + } + //see if we should advance now + else if ( NPCInfo->stats.aggression > 5 ) + {//approach enemy + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//far away or allowed to use saber + Jedi_Advance(); + } + } + } + } + else + {//maintain this distance? + //walk? + } + } + } + } + else + {//we're not close enough to attack, but not far enough away to be safe + if ( !Q_irand( 0, 30 ) + && Kyle_CanDoGrab() ) + { + Kyle_TryGrab(); + return; + } + if ( NPCInfo->stats.aggression < 4 ) + {//back off and defend + if ( Jedi_DecideKick() ) + {//let's try a kick + if ( G_PickAutoMultiKick( NPC, qfalse, qtrue ) != LS_NONE + || (G_CanKickEntity(NPC, NPC->enemy ) && G_PickAutoKick( NPC, NPC->enemy, qtrue ) != LS_NONE ) ) + {//kicked! + TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) ); + return; + } + } + Jedi_Retreat(); + } + else if ( NPCInfo->stats.aggression > 5 ) + {//try to get closer + if ( enemy_dist > 0 && !(NPCInfo->scriptFlags&SCF_DONT_FIRE)) + {//we're allowed to use our lightsaber, get closer + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + Jedi_Advance(); + } + } + } + } + else + {//agression is 4 or 5... somewhere in the middle + //what do we do here? Nothing? + //Move forward and back? + } + } + //if really really mad, rage! + if ( NPCInfo->stats.aggression > Q_irand( 5, 15 ) + && NPC->health < NPC->max_health*0.75f + && !Q_irand( 0, 2 ) ) + { + if ( (NPC->client->ps.forcePowersKnown&(1<client->ps.forcePowersActive&(1<client->ps.saberEventFlags&SEF_LOCK_WON && NPC->enemy && NPC->enemy->painDebounceTime > level.time ) + {//don't strafe if pressing the advantage of winning a saberLock + return qfalse; + } + if ( TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) ) + { + qboolean strafed = qfalse; + //TODO: make left/right choice a tactical decision rather than random: + // try to keep own back away from walls and ledges, + // try to keep enemy's back to a ledge or wall + // Maybe try to strafe toward designer-placed "safe spots" or "goals"? + int strafeTime = Q_irand( strafeTimeMin, strafeTimeMax ); + + if ( Q_irand( 0, 1 ) ) + { + if ( NPC_MoveDirClear( ucmd.forwardmove, -127, qfalse ) ) + { + TIMER_Set( NPC, "strafeLeft", strafeTime ); + strafed = qtrue; + } + else if ( NPC_MoveDirClear( ucmd.forwardmove, 127, qfalse ) ) + { + TIMER_Set( NPC, "strafeRight", strafeTime ); + strafed = qtrue; + } + } + else + { + if ( NPC_MoveDirClear( ucmd.forwardmove, 127, qfalse ) ) + { + TIMER_Set( NPC, "strafeRight", strafeTime ); + strafed = qtrue; + } + else if ( NPC_MoveDirClear( ucmd.forwardmove, -127, qfalse ) ) + { + TIMER_Set( NPC, "strafeLeft", strafeTime ); + strafed = qtrue; + } + } + + if ( strafed ) + { + TIMER_Set( NPC, "noStrafe", strafeTime + Q_irand( nextStrafeTimeMin, nextStrafeTimeMax ) ); + if ( walking ) + {//should be a slow strafe + TIMER_Set( NPC, "walking", strafeTime ); + } + return qtrue; + } + } + return qfalse; +} + +/* +static void Jedi_FaceEntity( gentity_t *self, gentity_t *other, qboolean doPitch ) +{ + vec3_t entPos; + vec3_t muzzle; + + //Get the positions + CalcEntitySpot( other, SPOT_ORIGIN, entPos ); + + //Get the positions + CalcEntitySpot( self, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD + + //Find the desired angles + vec3_t angles; + + GetAnglesForDirection( muzzle, entPos, angles ); + + self->NPC->desiredYaw = AngleNormalize360( angles[YAW] ); + if ( doPitch ) + { + self->NPC->desiredPitch = AngleNormalize360( angles[PITCH] ); + } +} +*/ + +/* +qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc ) + +Jedi will play a dodge anim, blur, and make the force speed noise. + +Right now used to dodge instant-hit weapons. + +FIXME: possibly call this for saber melee evasion and/or missile evasion? +FIXME: possibly let player do this too? +*/ +qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc ) +{ + int dodgeAnim = -1; + + if ( !self || !self->client || self->health <= 0 ) + { + return qfalse; + } + + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//can't dodge in mid-air + return qfalse; + } + + if ( self->client->ps.pm_time && (self->client->ps.pm_flags&PMF_TIME_KNOCKBACK) ) + {//in some effect that stops me from moving on my own + return qfalse; + } + + if ( self->enemy == shooter ) + {//FIXME: make it so that we are better able to dodge shots from my current enemy + } + if ( self->s.number ) + {//if an NPC, check game skill setting + /* + if ( self->NPC && (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) + {//those NPCs are "bosses" and always succeed + if ( Q_irand( 0, 2 ) > g_spskill->integer ) + {//more of a chance of failing the dodge on lower difficulty + return qfalse; + } + //FIXME: check my overall skill (rank) to determine if I should be able to dodge it? + //check force speed power level to determine if I should be able to dodge it + if ( Q_irand( 0, 3 ) > self->client->ps.forcePowerLevel[FP_SPEED] ) + {//more likely to fail on lower force speed level, but NPCs are generally better at it than the player + return qfalse; + } + } + */ + } + else + {//the player + if ( !(self->client->ps.forcePowersActive&(1< self->client->ps.forcePowerLevel[FP_SPEED] ) + {//more likely to fail on lower force speed level + return qfalse; + } + } + + if ( hitLoc == HL_NONE ) + { + if ( tr ) + { + for ( int z = 0; z < MAX_G2_COLLISIONS; z++ ) + { + if ( tr->G2CollisionMap[z].mEntityNum == -1 ) + {//actually, completely break out of this for loop since nothing after this in the aray should ever be valid either + continue;//break;// + } + + CCollisionRecord &coll = tr->G2CollisionMap[z]; + G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &hitLoc, coll.mCollisionPosition, NULL, NULL, MOD_UNKNOWN ); + //only want the first + break; + } + } + } + + switch( hitLoc ) + { + case HL_NONE: + return qfalse; + break; + + case HL_FOOT_RT: + case HL_FOOT_LT: + case HL_LEG_RT: + case HL_LEG_LT: + case HL_WAIST: + if ( !self->s.number ) + {//don't force the player to jump + return qfalse; + } + else + { + if ( !self->enemy && G_ValidEnemy(self,shooter)) + { + G_SetEnemy( self, shooter ); + } + if ( self->NPC + && ((self->NPC->scriptFlags&SCF_NO_ACROBATICS) || PM_InKnockDown( &self->client->ps ) ) ) + { + return qfalse; + } + if ( self->client + && (self->client->ps.forceRageRecoveryTime > level.time || (self->client->ps.forcePowersActive&(1<client->NPC_class == CLASS_BOBAFETT && !Q_irand(0,1)) + { + return qfalse; // half the time he dodges + } + + + if ( self->client->NPC_class == CLASS_BOBAFETT + || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) + || self->client->NPC_class == CLASS_ROCKETTROOPER ) + { + self->client->ps.forceJumpCharge = 280;//FIXME: calc this intelligently? + } + else + { + self->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently? + WP_ForcePowerStop( self, FP_GRIP ); + } + return qtrue; + } + break; + + case HL_BACK_RT: + dodgeAnim = BOTH_DODGE_FL; + break; + case HL_CHEST_RT: + dodgeAnim = BOTH_DODGE_BL; + break; + case HL_BACK_LT: + dodgeAnim = BOTH_DODGE_FR; + break; + case HL_CHEST_LT: + dodgeAnim = BOTH_DODGE_BR; + break; + case HL_BACK: + case HL_CHEST: + dodgeAnim = Q_irand( BOTH_DODGE_FL, BOTH_DODGE_R ); + break; + case HL_ARM_RT: + case HL_HAND_RT: + dodgeAnim = BOTH_DODGE_L; + break; + case HL_ARM_LT: + case HL_HAND_LT: + dodgeAnim = BOTH_DODGE_R; + break; + case HL_HEAD: + dodgeAnim = Q_irand( BOTH_DODGE_FL, BOTH_DODGE_BR ); + break; + } + + if ( dodgeAnim != -1 ) + { + int extraHoldTime = 0;//Q_irand( 5, 40 ) * 50; + /* + int type = SETANIM_TORSO; + if ( VectorCompare( self->client->ps.velocity, vec3_origin ) ) + {//not moving + type = SETANIM_BOTH; + } + */ + if ( self->s.number < MAX_CLIENTS ) + {//player + if ( (self->client->ps.forcePowersActive&(1<client->ps.torsoAnim ) + && !PM_DodgeHoldAnim( self->client->ps.torsoAnim ) ) + {//already in a dodge + //use the hold pose, don't start it all over again + dodgeAnim = BOTH_DODGE_HOLD_FL+(dodgeAnim-BOTH_DODGE_FL); + extraHoldTime = 200; + } + } + } + + //set the dodge anim we chose + NPC_SetAnim( self, SETANIM_BOTH, dodgeAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );//type + if ( extraHoldTime && self->client->ps.torsoAnimTimer < extraHoldTime ) + { + self->client->ps.torsoAnimTimer += extraHoldTime; + } + //if ( type == SETANIM_BOTH ) + { + self->client->ps.legsAnimTimer = self->client->ps.torsoAnimTimer; + } + + if ( self->s.number ) + {//NPC + //maybe force them to stop moving in this case? + self->client->ps.pm_time = self->client->ps.torsoAnimTimer + Q_irand( 100, 1000 ); + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + //do force speed effect + self->client->ps.forcePowersActive |= (1 << FP_SPEED); + self->client->ps.forcePowerDuration[FP_SPEED] = level.time + self->client->ps.torsoAnimTimer; + //sound + G_Sound( self, G_SoundIndex( "sound/weapons/force/speed.wav" ) ); + } + else + {//player + ForceSpeed( self, 500 ); + } + + WP_ForcePowerStop( self, FP_GRIP ); + if ( !self->enemy && G_ValidEnemy( self, shooter) ) + { + G_SetEnemy( self, shooter ); + if ( self->s.number ) + { + Jedi_Aggression( self, 10 ); + } + } + return qtrue; + } + return qfalse; +} + +evasionType_t Jedi_CheckFlipEvasions( gentity_t *self, float rightdot, float zdiff ) +{ + if ( self->NPC && (self->NPC->scriptFlags&SCF_NO_ACROBATICS) ) + { + return EVASION_NONE; + } + if ( self->client ) + { + if ( self->client->NPC_class == CLASS_BOBAFETT ) + {//boba can't flip + return EVASION_NONE; + } + if ( self->client->ps.forceRageRecoveryTime > level.time + || (self->client->ps.forcePowersActive&(1<client->ps.legsAnim == BOTH_WALL_RUN_LEFT || self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT ) + {//already running on a wall + vec3_t right, fwdAngles = {0, self->client->ps.viewangles[YAW], 0}; + int anim = -1; + + AngleVectors( fwdAngles, NULL, right, NULL ); + + float animLength = PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.legsAnim ); + if ( self->client->ps.legsAnim == BOTH_WALL_RUN_LEFT && rightdot < 0 ) + {//I'm running on a wall to my left and the attack is on the left + if ( animLength - self->client->ps.legsAnimTimer > 400 + && self->client->ps.legsAnimTimer > 400 ) + {//not at the beginning or end of the anim + anim = BOTH_WALL_RUN_LEFT_FLIP; + } + } + else if ( self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT && rightdot > 0 ) + {//I'm running on a wall to my right and the attack is on the right + if ( animLength - self->client->ps.legsAnimTimer > 400 + && self->client->ps.legsAnimTimer > 400 ) + {//not at the beginning or end of the anim + anim = BOTH_WALL_RUN_RIGHT_FLIP; + } + } + if ( anim != -1 ) + {//flip off the wall! + //FIXME: check the direction we will flip towards for do-not-enter/walls/drops? + //NOTE: we presume there is still a wall there! + if ( anim == BOTH_WALL_RUN_LEFT_FLIP ) + { + self->client->ps.velocity[0] *= 0.5f; + self->client->ps.velocity[1] *= 0.5f; + VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity ); + } + else if ( anim == BOTH_WALL_RUN_RIGHT_FLIP ) + { + self->client->ps.velocity[0] *= 0.5f; + self->client->ps.velocity[1] *= 0.5f; + VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity ); + } + int parts = SETANIM_LEGS; + if ( !self->client->ps.weaponTime ) + { + parts = SETANIM_BOTH; + } + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL); + G_AddEvent( self, EV_JUMP, 0 ); + return EVASION_OTHER; + } + } + else if ( self->client->NPC_class != CLASS_DESANN //desann doesn't do these kind of frilly acrobatics + && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT) + && Q_irand( 0, 1 ) + && !PM_InRoll( &self->client->ps ) + && !PM_InKnockDown( &self->client->ps ) + && !PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) ) + { + vec3_t fwd, right, traceto, mins = {self->mins[0],self->mins[1],self->mins[2]+STEPSIZE}, maxs = {self->maxs[0],self->maxs[1],24}, fwdAngles = {0, self->client->ps.viewangles[YAW], 0}; + trace_t trace; + + AngleVectors( fwdAngles, fwd, right, NULL ); + + int parts = SETANIM_BOTH, anim; + float speed, checkDist; + + if ( PM_SaberInAttack( self->client->ps.saberMove ) + || PM_SaberInStart( self->client->ps.saberMove ) ) + { + parts = SETANIM_LEGS; + } + if ( rightdot >= 0 ) + { + if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_ARIAL_LEFT; + } + else + { + anim = BOTH_CARTWHEEL_LEFT; + } + checkDist = -128; + speed = -200; + } + else + { + if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_ARIAL_RIGHT; + } + else + { + anim = BOTH_CARTWHEEL_RIGHT; + } + checkDist = 128; + speed = 200; + } + //trace in the dir that we want to go + VectorMA( self->currentOrigin, checkDist, right, traceto ); + gi.trace( &trace, self->currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP ); + if ( trace.fraction >= 1.0f ) + {//it's clear, let's do it + //FIXME: check for drops? + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.weaponTime = self->client->ps.legsAnimTimer;//don't attack again until this anim is done + vec3_t fwdAngles, jumpRt; + VectorCopy( self->client->ps.viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + //do the flip + AngleVectors( fwdAngles, NULL, jumpRt, NULL ); + VectorScale( jumpRt, speed, self->client->ps.velocity ); + self->client->ps.forceJumpCharge = 0;//so we don't play the force flip anim + self->client->ps.velocity[2] = 200; + self->client->ps.forceJumpZStart = self->currentOrigin[2];//so we don't take damage if we land at same height + self->client->ps.pm_flags |= PMF_JUMPING; + if ( self->client->NPC_class == CLASS_BOBAFETT + || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + //ucmd.upmove = 0; + return EVASION_CARTWHEEL; + } + else if ( !(trace.contents&CONTENTS_BOTCLIP) ) + {//hit a wall, not a do-not-enter brush + //FIXME: before we check any of these jump-type evasions, we should check for headroom, right? + //Okay, see if we can flip *off* the wall and go the other way + vec3_t idealNormal; + VectorSubtract( self->currentOrigin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( (trace.entityNums.solid!=SOLID_BMODEL) || DotProduct( trace.plane.normal, idealNormal ) > 0.7f ) + {//it's a ent of some sort or it's a wall roughly facing us + float bestCheckDist = 0; + //hmm, see if we're moving forward + if ( DotProduct( self->client->ps.velocity, fwd ) < 200 ) + {//not running forward very fast + //check to see if it's okay to move the other way + if ( (trace.fraction*checkDist) <= 32 ) + {//wall on that side is close enough to wall-flip off of or wall-run on + bestCheckDist = checkDist; + checkDist *= -1.0f; + VectorMA( self->currentOrigin, checkDist, right, traceto ); + //trace in the dir that we want to go + gi.trace( &trace, self->currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP ); + if ( trace.fraction >= 1.0f ) + {//it's clear, let's do it + //FIXME: check for drops? + //turn the cartwheel into a wallflip in the other dir + if ( rightdot > 0 ) + { + anim = BOTH_WALL_FLIP_LEFT; + self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0; + VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity ); + } + else + { + anim = BOTH_WALL_FLIP_RIGHT; + self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0; + VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity ); + } + self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + //animate me + int parts = SETANIM_LEGS; + if ( !self->client->ps.weaponTime ) + { + parts = SETANIM_BOTH; + } + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.forceJumpZStart = self->currentOrigin[2];//so we don't take damage if we land at same height + self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL); + if ( self->client->NPC_class == CLASS_BOBAFETT + || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER)) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + return EVASION_OTHER; + } + else + {//boxed in on both sides + if ( DotProduct( self->client->ps.velocity, fwd ) < 0 ) + {//moving backwards + return EVASION_NONE; + } + if ( (trace.fraction*checkDist) <= 32 && (trace.fraction*checkDist) < bestCheckDist ) + { + bestCheckDist = checkDist; + } + } + } + else + {//too far from that wall to flip or run off it, check other side + checkDist *= -1.0f; + VectorMA( self->currentOrigin, checkDist, right, traceto ); + //trace in the dir that we want to go + gi.trace( &trace, self->currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP ); + if ( (trace.fraction*checkDist) <= 32 ) + {//wall on this side is close enough + bestCheckDist = checkDist; + } + else + {//neither side has a wall within 32 + return EVASION_NONE; + } + } + } + //Try wall run? + if ( bestCheckDist ) + {//one of the walls was close enough to wall-run on + //FIXME: check for long enough wall and a drop at the end? + if ( bestCheckDist > 0 ) + {//it was to the right + anim = BOTH_WALL_RUN_RIGHT; + } + else + {//it was to the left + anim = BOTH_WALL_RUN_LEFT; + } + self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + //animate me + int parts = SETANIM_LEGS; + if ( !self->client->ps.weaponTime ) + { + parts = SETANIM_BOTH; + } + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.forceJumpZStart = self->currentOrigin[2];//so we don't take damage if we land at same height + self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL); + if ( self->client->NPC_class == CLASS_BOBAFETT + || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER)) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + return EVASION_OTHER; + } + //else check for wall in front, do backflip off wall + } + } + } + return EVASION_NONE; +} + +int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType ) +{ + if ( !self->client ) + { + return 0; + } + if ( !self->s.number ) + {//player + return parryDebounce[self->client->ps.forcePowerLevel[FP_SABER_DEFENSE]]; + } + else if ( self->NPC ) + { + /* + if ( !g_saberRealisticCombat->integer + && ( g_spskill->integer == 2 || (g_spskill->integer == 1 && (self->client->NPC_class == CLASS_TAVION||self->client->NPC_class == CLASS_ALORA) ) ) ) + { + if ( (self->client->NPC_class == CLASS_TAVION||self->client->NPC_class == CLASS_ALORA) ) + { + return 0; + } + else + { + return Q_irand( 0, 150 ); + } + } + else + */ + { + int baseTime; + if ( evasionType == EVASION_DODGE ) + { + baseTime = self->client->ps.torsoAnimTimer; + } + else if ( evasionType == EVASION_CARTWHEEL ) + { + baseTime = self->client->ps.torsoAnimTimer; + } + else if ( self->client->ps.saberInFlight ) + { + baseTime = Q_irand( 1, 3 ) * 50; + } + else + { + /* + baseTime = 1000; + + switch ( g_spskill->integer ) + { + case 0: + baseTime = 1500; + break; + case 1: + baseTime = 1000; + break; + case 2: + default: + baseTime = 500; + break; + } + */ + if ( 1 )//g_saberRealisticCombat->integer ) + { + baseTime = 500; + + switch ( g_spskill->integer ) + { + case 0: + baseTime = 400;//was 500 + break; + case 1: + baseTime = 200;//was 300 + break; + case 2: + default: + baseTime = 100; + break; + } + } + else + { + baseTime = 150;//500; + + switch ( g_spskill->integer ) + { + case 0: + baseTime = 200;//500; + break; + case 1: + baseTime = 100;//300; + break; + case 2: + default: + baseTime = 50;//100; + break; + } + } + + if ( self->client->NPC_class == CLASS_ALORA + || self->client->NPC_class == CLASS_SHADOWTROOPER + || self->client->NPC_class == CLASS_TAVION ) + {//Tavion & Alora are faster + baseTime = ceil(baseTime/2.0f); + } + else if ( self->NPC->rank >= RANK_LT_JG ) + {//fencers, bosses, shadowtroopers, luke, desann, et al use the norm + if ( Q_irand( 0, 2 ) ) + {//medium speed parry + baseTime = baseTime; + } + else + {//with the occasional fast parry + baseTime = ceil(baseTime/2.0f); + } + } + else if ( self->NPC->rank == RANK_CIVILIAN ) + {//grunts are slowest + baseTime = baseTime*Q_irand(1,3); + } + else if ( self->NPC->rank == RANK_CREWMAN ) + {//acrobats aren't so bad + if ( evasionType == EVASION_PARRY + || evasionType == EVASION_DUCK_PARRY + || evasionType == EVASION_JUMP_PARRY ) + {//slower with parries + baseTime = baseTime*Q_irand(1,2); + } + else + {//faster with acrobatics + //baseTime = baseTime; + } + } + else + {//force users are kinda slow + baseTime = baseTime*Q_irand(1,2); + } + if ( evasionType == EVASION_DUCK || evasionType == EVASION_DUCK_PARRY ) + { + baseTime += 250;//300;//100; + } + else if ( evasionType == EVASION_JUMP || evasionType == EVASION_JUMP_PARRY ) + { + baseTime += 400;//500;//50; + } + else if ( evasionType == EVASION_OTHER ) + { + baseTime += 50;//100; + } + else if ( evasionType == EVASION_FJUMP ) + { + baseTime += 300;//400;//100; + } + } + + return baseTime; + } + } + return 0; +} + +qboolean Jedi_QuickReactions( gentity_t *self ) +{ + if ( ( self->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) || + self->client->NPC_class == CLASS_SHADOWTROOPER || self->client->NPC_class == CLASS_ALORA || self->client->NPC_class == CLASS_TAVION || + (self->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_1&&g_spskill->integer>1) || + (self->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&g_spskill->integer>0) ) + { + return qtrue; + } + return qfalse; +} + +qboolean Jedi_SaberBusy( gentity_t *self ) +{ + if ( self->client->ps.torsoAnimTimer > 300 + && ( (PM_SaberInAttack( self->client->ps.saberMove )&&self->client->ps.saberAnimLevel==SS_STRONG) + || PM_SpinningSaberAnim( self->client->ps.torsoAnim ) + || PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) + //|| PM_SaberInBounce( self->client->ps.saberMove ) + || PM_SaberInBrokenParry( self->client->ps.saberMove ) + //|| PM_SaberInDeflect( self->client->ps.saberMove ) + || PM_FlippingAnim( self->client->ps.torsoAnim ) + || PM_RollingAnim( self->client->ps.torsoAnim ) ) ) + {//my saber is not in a parrying position + return qtrue; + } + return qfalse; +} + +qboolean Jedi_InNoAIAnim( gentity_t *self ) +{ + if ( !self || !self->client ) + {//wtf??? + return qtrue; + } + + if ( NPCInfo->rank >= RANK_COMMANDER ) + {//boss-level guys can multitask, the rest need to chill out during special moves + return qfalse; + } + + if ( PM_KickingAnim( NPC->client->ps.legsAnim ) + ||PM_StabDownAnim( NPC->client->ps.legsAnim ) + ||PM_InAirKickingAnim( NPC->client->ps.legsAnim ) + ||PM_InRollIgnoreTimer( &NPC->client->ps ) + ||PM_SaberInKata((saberMoveName_t)NPC->client->ps.saberMove) + ||PM_SuperBreakWinAnim( NPC->client->ps.torsoAnim ) + ||PM_SuperBreakLoseAnim( NPC->client->ps.torsoAnim ) ) + { + return qtrue; + } + + switch ( self->client->ps.legsAnim ) + { + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + case BOTH_FLIP_F: + case BOTH_FLIP_B: + case BOTH_FLIP_L: + case BOTH_FLIP_R: + case BOTH_DODGE_FL: + case BOTH_DODGE_FR: + case BOTH_DODGE_BL: + case BOTH_DODGE_BR: + case BOTH_DODGE_L: + case BOTH_DODGE_R: + case BOTH_DODGE_HOLD_FL: + case BOTH_DODGE_HOLD_FR: + case BOTH_DODGE_HOLD_BL: + case BOTH_DODGE_HOLD_BR: + case BOTH_DODGE_HOLD_L: + case BOTH_DODGE_HOLD_R: + case BOTH_FORCEWALLRUNFLIP_START: + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + case BOTH_JUMPFLIPSLASHDOWN1: + case BOTH_JUMPFLIPSTABDOWN: + case BOTH_FORCELEAP2_T__B_: + case BOTH_ROLL_STAB: + case BOTH_SPINATTACK6: + case BOTH_SPINATTACK7: + case BOTH_PULL_IMPALE_STAB: + case BOTH_PULL_IMPALE_SWING: + case BOTH_A6_FB: + case BOTH_A6_LR: + case BOTH_A7_HILT: + return qtrue; + break; + } + return qfalse; +} + +void Jedi_CheckJumpEvasionSafety( gentity_t *self, usercmd_t *cmd, evasionType_t evasionType ) +{ + if ( evasionType != EVASION_OTHER//not a FlipEvasion, which does it's own safety checks + && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//on terra firma right now + if ( NPC->client->ps.velocity[2] > 0 + || NPC->client->ps.forceJumpCharge + || cmd->upmove > 0 ) + {//going to jump + if ( !NAV_MoveDirSafe( NPC, cmd, NPC->client->ps.speed*10.0f ) ) + {//we can't jump in the dir we're pushing in + //cancel the evasion + NPC->client->ps.velocity[2] = NPC->client->ps.forceJumpCharge = 0; + cmd->upmove = 0; + if ( d_JediAI->integer ) + { + Com_Printf( S_COLOR_RED"jump not safe, cancelling!" ); + } + } + else if ( NPC->client->ps.velocity[0] || NPC->client->ps.velocity[1] ) + {//sliding + vec3_t jumpDir; + float jumpDist = VectorNormalize2( NPC->client->ps.velocity, jumpDir ); + if ( !NAV_DirSafe( NPC, jumpDir, jumpDist ) ) + {//this jump combined with our momentum would send us into a do not enter brush, so cancel it + //cancel the evasion + NPC->client->ps.velocity[2] = NPC->client->ps.forceJumpCharge = 0; + cmd->upmove = 0; + if ( d_JediAI->integer ) + { + Com_Printf( S_COLOR_RED"jump not safe, cancelling!\n" ); + } + } + } + if ( d_JediAI->integer ) + { + Com_Printf( S_COLOR_GREEN"jump checked, is safe\n" ); + } + } + } +} +/* +------------------------- +Jedi_SaberBlock + +Pick proper block anim + +FIXME: Based on difficulty level/enemy saber combat skill, make this decision-making more/less effective + +NOTE: always blocking projectiles in this func! + +------------------------- +*/ +extern qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result ); +evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist = 0.0f ) +{ + vec3_t hitloc, hitdir, diff, fwdangles={0,0,0}, right; + float rightdot; + float zdiff; + int duckChance = 0; + int dodgeAnim = -1; + qboolean saberBusy = qfalse, evaded = qfalse; + evasionType_t evasionType = EVASION_NONE; + + if ( !self || !self->client ) + { + return EVASION_NONE; + } + + if ( PM_LockedAnim( self->client->ps.torsoAnim ) + && self->client->ps.torsoAnimTimer ) + {//Never interrupt these... + return EVASION_NONE; + } + if ( PM_InSpecialJump( self->client->ps.legsAnim ) + && PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) ) + { + return EVASION_NONE; + } + + if ( Jedi_InNoAIAnim( self ) ) + { + return EVASION_NONE; + } + + + //FIXME: if we don't have our saber in hand, pick the force throw option or a jump or strafe! + //FIXME: reborn don't block enough anymore + if ( !incoming ) + { + VectorCopy( pHitloc, hitloc ); + VectorCopy( phitDir, hitdir ); + //FIXME: maybe base this on rank some? And/or g_spskill? + if ( self->client->ps.saberInFlight ) + {//DOH! do non-saber evasion! + saberBusy = qtrue; + } + /* + else if ( Jedi_QuickReactions( self ) ) + {//jedi trainer and tavion are must faster at parrying and can do it whenever they like + //Also, on medium, all level 3 people can parry any time and on hard, all level 2 or 3 people can parry any time + } + */ + else + { + saberBusy = Jedi_SaberBusy( self ); + } + } + else + { + if ( incoming->s.weapon == WP_SABER ) + {//flying lightsaber, face it! + //FIXME: for this to actually work, we'd need to call update angles too? + //Jedi_FaceEntity( self, incoming, qtrue ); + } + VectorCopy( incoming->currentOrigin, hitloc ); + VectorNormalize2( incoming->s.pos.trDelta, hitdir ); + } + + VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff ); + diff[2] = 0; + //VectorNormalize( diff ); + fwdangles[1] = self->client->ps.viewangles[1]; + // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine. + AngleVectors( fwdangles, NULL, right, NULL ); + + rightdot = DotProduct(right, diff);// + Q_flrand(-0.10f,0.10f); + //totalHeight = self->client->renderInfo.eyePoint[2] - self->absmin[2]; + zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];// + Q_irand(-6,6); + + qboolean doDodge = qfalse; + qboolean alwaysDodgeOrRoll = qfalse; + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + saberBusy = qtrue; + doDodge = qtrue; + alwaysDodgeOrRoll = qtrue; + } + else + { + if ( self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER ) + { + saberBusy = qtrue; + alwaysDodgeOrRoll = qtrue; + } + //see if we can dodge if need-be + if ( (dist>16&&(Q_irand( 0, 2 )||saberBusy)) + || self->client->ps.saberInFlight + || !self->client->ps.SaberActive() + || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) ) + {//either it will miss by a bit (and 25% chance) OR our saber is not in-hand OR saber is off + if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT_JG) ) + {//acrobat or fencer or above + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE &&//on the ground + !(self->client->ps.pm_flags&PMF_DUCKED)&&cmd->upmove>=0&&TIMER_Done( self, "duck" )//not ducking + && !PM_InRoll( &self->client->ps )//not rolling + && !PM_InKnockDown( &self->client->ps )//not knocked down + && ( self->client->ps.saberInFlight || + (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) || + (!PM_SaberInAttack( self->client->ps.saberMove )//not attacking + && !PM_SaberInStart( self->client->ps.saberMove )//not starting an attack + && !PM_SpinningSaberAnim( self->client->ps.torsoAnim )//not in a saber spin + && !PM_SaberInSpecialAttack( self->client->ps.torsoAnim ))//not in a special attack + ) + ) + {//need to check all these because it overrides both torso and legs with the dodge + doDodge = qtrue; + } + } + } + } + + qboolean doRoll = qfalse; + if ( ( self->client->NPC_class == CLASS_BOBAFETT //boba fett + || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) //non-saber reborn (cultist) + ) + && !Q_irand( 0, 2 ) + ) + { + doRoll = qtrue; + } + + // Figure out what quadrant the block was in. + if ( d_JediAI->integer ) + { + gi.Printf( "(%d) evading attack from height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, hitloc[2]-self->absmin[2],zdiff,rightdot); + } + + //UL = > -1//-6 + //UR = > -6//-9 + //TOP = > +6//+4 + //FIXME: take FP_SABER_DEFENSE into account here somehow? + if ( zdiff >= -5 )//was 0 + { + if ( incoming || !saberBusy || alwaysDodgeOrRoll ) + { + if ( rightdot > 12 + || (rightdot > 3 && zdiff < 5) + || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, 0.3 + {//coming from right + if ( doDodge ) + { + if ( doRoll ) + {//roll! + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + evasionType = EVASION_DUCK; + evaded = qtrue; + } + else if ( Q_irand( 0, 1 ) ) + { + dodgeAnim = BOTH_DODGE_FL; + } + else + { + dodgeAnim = BOTH_DODGE_BL; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT; + evasionType = EVASION_PARRY; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + if ( zdiff > 5 ) + { + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK_PARRY; + evaded = qtrue; + if ( d_JediAI->integer ) + { + gi.Printf( "duck " ); + } + } + else + { + duckChance = 6; + } + } + } + if ( d_JediAI->integer ) + { + gi.Printf( "UR block\n" ); + } + } + else if ( rightdot < -12 + || (rightdot < -3 && zdiff < 5) + || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, -0.3 + {//coming from left + if ( doDodge ) + { + if ( doRoll ) + { + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeLeft", 0 ); + evasionType = EVASION_DUCK; + evaded = qtrue; + } + else if ( Q_irand( 0, 1 ) ) + { + dodgeAnim = BOTH_DODGE_FR; + } + else + { + dodgeAnim = BOTH_DODGE_BR; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + evasionType = EVASION_PARRY; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + if ( zdiff > 5 ) + { + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK_PARRY; + evaded = qtrue; + if ( d_JediAI->integer ) + { + gi.Printf( "duck " ); + } + } + else + { + duckChance = 6; + } + } + } + if ( d_JediAI->integer ) + { + gi.Printf( "UL block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_TOP; + evasionType = EVASION_PARRY; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + duckChance = 4; + } + if ( d_JediAI->integer ) + { + gi.Printf( "TOP block\n" ); + } + } + evaded = qtrue; + } + else + { + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + //duckChance = 2; + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK; + evaded = qtrue; + if ( d_JediAI->integer ) + { + gi.Printf( "duck " ); + } + } + } + } + //LL = -22//= -18 to -39 + //LR = -23//= -20 to -41 + else if ( zdiff > -22 )//was-15 ) + { + if ( 1 )//zdiff < -10 ) + {//hmm, pretty low, but not low enough to use the low block, so we need to duck + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + //duckChance = 2; + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK; + evaded = qtrue; + if ( d_JediAI->integer ) + { + gi.Printf( "duck " ); + } + } + else + {//in air! Ducking does no good + } + } + if ( incoming || !saberBusy || alwaysDodgeOrRoll ) + { + if ( rightdot > 8 || (rightdot > 3 && zdiff < -11) )//was normalized, 0.2 + { + if ( doDodge ) + { + if ( doRoll ) + {//roll! + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + } + else + { + dodgeAnim = BOTH_DODGE_L; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT; + if ( evasionType == EVASION_DUCK ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_PARRY; + } + } + if ( d_JediAI->integer ) + { + gi.Printf( "mid-UR block\n" ); + } + } + else if ( rightdot < -8 || (rightdot < -3 && zdiff < -11) )//was normalized, -0.2 + { + if ( doDodge ) + { + if ( doRoll ) + {//roll! + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + } + else + { + dodgeAnim = BOTH_DODGE_R; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + if ( evasionType == EVASION_DUCK ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_PARRY; + } + } + if ( d_JediAI->integer ) + { + gi.Printf( "mid-UL block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_TOP; + if ( evasionType == EVASION_DUCK ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_PARRY; + } + if ( d_JediAI->integer ) + { + gi.Printf( "mid-TOP block\n" ); + } + } + evaded = qtrue; + } + } + else + { + if ( saberBusy || (zdiff < -36 && ( zdiff < -44 || !Q_irand( 0, 2 ) ) ) )//was -30 and -40//2nd one was -46 + {//jump! + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//already in air, duck to pull up legs + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK; + evaded = qtrue; + if ( d_JediAI->integer ) + { + gi.Printf( "legs up\n" ); + } + if ( incoming || !saberBusy ) + { + //since the jump may be cleared if not safe, set a lower block too + if ( rightdot >= 0 ) + { + self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + evasionType = EVASION_DUCK_PARRY; + if ( d_JediAI->integer ) + { + gi.Printf( "LR block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + evasionType = EVASION_DUCK_PARRY; + if ( d_JediAI->integer ) + { + gi.Printf( "LL block\n" ); + } + } + evaded = qtrue; + } + } + else + {//gotta jump! + if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) && + (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) ) + {//superjump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.forceRageRecoveryTime < level.time + && !(self->client->ps.forcePowersActive&(1<client->ps ) ) + { + self->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently + evasionType = EVASION_FJUMP; + evaded = qtrue; + if ( d_JediAI->integer ) + { + gi.Printf( "force jump + " ); + } + } + } + else + {//normal jump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.forceRageRecoveryTime < level.time + && !(self->client->ps.forcePowersActive&(1<client->NPC_class == CLASS_BOBAFETT + || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) + ) + && !Q_irand( 0, 1 ) ) + {//flip! + if ( rightdot > 0 ) + { + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + TIMER_Set( self, "walking", 0 ); + } + else + { + TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeLeft", 0 ); + TIMER_Set( self, "walking", 0 ); + } + } + else + { + if ( self == NPC ) + { + cmd->upmove = 127; + } + else + { + self->client->ps.velocity[2] = JUMP_VELOCITY; + } + } + evasionType = EVASION_JUMP; + evaded = qtrue; + if ( d_JediAI->integer ) + { + gi.Printf( "jump + " ); + } + } + if ( self->client->NPC_class == CLASS_ALORA + || self->client->NPC_class == CLASS_SHADOWTROOPER + || self->client->NPC_class == CLASS_TAVION ) + { + if ( !incoming + && self->client->ps.groundEntityNum < ENTITYNUM_NONE + && !Q_irand( 0, 2 ) ) + { + if ( !PM_SaberInAttack( self->client->ps.saberMove ) + && !PM_SaberInStart( self->client->ps.saberMove ) + && !PM_InRoll( &self->client->ps ) + && !PM_InKnockDown( &self->client->ps ) + && !PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) ) + {//do the butterfly! + int butterflyAnim; + if ( self->client->NPC_class == CLASS_ALORA + && !Q_irand( 0, 2 ) ) + { + butterflyAnim = BOTH_ALORA_SPIN; + } + else if ( Q_irand( 0, 1 ) ) + { + butterflyAnim = BOTH_BUTTERFLY_LEFT; + } + else + { + butterflyAnim = BOTH_BUTTERFLY_RIGHT; + } + evasionType = EVASION_CARTWHEEL; + NPC_SetAnim( self, SETANIM_BOTH, butterflyAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.velocity[2] = 225; + self->client->ps.forceJumpZStart = self->currentOrigin[2];//so we don't take damage if we land at same height + self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + self->client->ps.SaberActivateTrail( 300 );//FIXME: reset this when done! + /* + if ( self->client->NPC_class == CLASS_BOBAFETT + || (self->client->NPC_class == CLASS_REBORN && self->s.weapon != WP_SABER) ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + */ + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + cmd->upmove = 0; + saberBusy = qtrue; + evaded = qtrue; + } + } + } + } + if ( ((evasionType = Jedi_CheckFlipEvasions( self, rightdot, zdiff ))!=EVASION_NONE) ) + { + if ( d_slowmodeath->integer > 5 && self->enemy && !self->enemy->s.number ) + { + G_StartMatrixEffect( self ); + } + saberBusy = qtrue; + evaded = qtrue; + } + else if ( incoming || !saberBusy ) + { + //since the jump may be cleared if not safe, set a lower block too + if ( rightdot >= 0 ) + { + self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + if ( evasionType == EVASION_JUMP ) + { + evasionType = EVASION_JUMP_PARRY; + } + else if ( evasionType == EVASION_NONE ) + { + evasionType = EVASION_PARRY; + } + if ( d_JediAI->integer ) + { + gi.Printf( "LR block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + if ( evasionType == EVASION_JUMP ) + { + evasionType = EVASION_JUMP_PARRY; + } + else if ( evasionType == EVASION_NONE ) + { + evasionType = EVASION_PARRY; + } + if ( d_JediAI->integer ) + { + gi.Printf( "LL block\n" ); + } + } + evaded = qtrue; + } + } + } + else + { + if ( incoming || !saberBusy ) + { + if ( rightdot >= 0 ) + { + self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + evasionType = EVASION_PARRY; + if ( d_JediAI->integer ) + { + gi.Printf( "LR block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + evasionType = EVASION_PARRY; + if ( d_JediAI->integer ) + { + gi.Printf( "LL block\n" ); + } + } + if ( incoming && incoming->s.weapon == WP_SABER ) + {//thrown saber! + if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) && + (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) ) + {//superjump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.forceRageRecoveryTime < level.time + && !(self->client->ps.forcePowersActive&(1<client->ps ) ) + { + self->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently + evasionType = EVASION_FJUMP; + if ( d_JediAI->integer ) + { + gi.Printf( "force jump + " ); + } + } + } + else + {//normal jump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.forceRageRecoveryTime < level.time + && !(self->client->ps.forcePowersActive&(1<upmove = 127; + } + else + { + self->client->ps.velocity[2] = JUMP_VELOCITY; + } + evasionType = EVASION_JUMP_PARRY; + if ( d_JediAI->integer ) + { + gi.Printf( "jump + " ); + } + } + } + } + evaded = qtrue; + } + } + } + if ( evasionType == EVASION_NONE ) + { + return EVASION_NONE; + } +//======================================================================================= + //see if it's okay to jump + Jedi_CheckJumpEvasionSafety( self, cmd, evasionType ); +//======================================================================================= + //stop taunting + TIMER_Set( self, "taunting", 0 ); + //stop gripping + TIMER_Set( self, "gripping", -level.time ); + WP_ForcePowerStop( self, FP_GRIP ); + //stop draining + TIMER_Set( self, "draining", -level.time ); + WP_ForcePowerStop( self, FP_DRAIN ); + + if ( dodgeAnim != -1 ) + {//dodged + evasionType = EVASION_DODGE; + NPC_SetAnim( self, SETANIM_BOTH, dodgeAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.weaponTime = self->client->ps.torsoAnimTimer; + //force them to stop moving in this case + self->client->ps.pm_time = self->client->ps.torsoAnimTimer; + //FIXME: maybe make a sound? Like a grunt? EV_JUMP? + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + //dodged, not block + if ( d_slowmodeath->integer > 5 && self->enemy && !self->enemy->s.number ) + { + G_StartMatrixEffect( self ); + } + } + else + { + if ( duckChance ) + { + if ( !Q_irand( 0, duckChance ) ) + { + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + if ( evasionType == EVASION_PARRY ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_DUCK; + } + /* + if ( d_JediAI->integer ) + { + gi.Printf( "duck " ); + } + */ + } + } + + if ( incoming ) + { + self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked ); + } + + } + //if ( self->client->ps.saberBlocked != BLOCKED_NONE ) + { + int parryReCalcTime = Jedi_ReCalcParryTime( self, evasionType ); + if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime ) + { + self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime; + } + } + return evasionType; +} + +static evasionType_t Jedi_CheckEvadeSpecialAttacks( void ) +{ + if ( !NPC + || !NPC->client ) + { + return EVASION_NONE; + } + + if ( !NPC->enemy + || NPC->enemy->health <= 0 + || !NPC->enemy->client ) + {//don't keep blocking him once he's dead (or if not a client) + return EVASION_NONE; + } + + if ( NPC->enemy->s.number >= MAX_CLIENTS ) + {//only do these against player + return EVASION_NONE; + } + + if ( !TIMER_Done( NPC, "specialEvasion" ) ) + {//still evading from last time + return EVASION_NONE; + } + + if ( NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK6 + || NPC->enemy->client->ps.torsoAnim == BOTH_SPINATTACK7 ) + {//back away from these + if ( (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) + || NPC->client->NPC_class == CLASS_SHADOWTROOPER + || NPC->client->NPC_class == CLASS_ALORA + || Q_irand( 0, NPCInfo->rank ) > RANK_LT_JG ) + {//see if we should back off + if ( InFront( NPC->currentOrigin, NPC->enemy->currentOrigin, NPC->enemy->currentAngles ) ) + {//facing me + float minSafeDistSq = (NPC->maxs[0]*1.5f+NPC->enemy->maxs[0]*1.5f+NPC->enemy->client->ps.SaberLength()+24.0f); + minSafeDistSq *= minSafeDistSq; + if ( DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ) < minSafeDistSq ) + {//back off! + Jedi_StartBackOff(); + return EVASION_OTHER; + } + } + } + } + else + {//check some other attacks? + //check roll-stab + if ( NPC->enemy->client->ps.torsoAnim == BOTH_ROLL_STAB + || (NPC->enemy->client->ps.torsoAnim == BOTH_ROLL_F && ((NPC->enemy->client->pers.lastCommand.buttons&BUTTON_ATTACK)||(NPC->enemy->client->ps.pm_flags&PMF_ATTACK_HELD)) ) ) + {//either already in a roll-stab or may go into one + if ( (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) + || NPC->client->NPC_class == CLASS_SHADOWTROOPER + || NPC->client->NPC_class == CLASS_ALORA + || Q_irand( -3, NPCInfo->rank ) > RANK_LT_JG ) + {//see if we should evade + vec3_t yawOnlyAngles = {0, NPC->enemy->currentAngles[YAW], 0 }; + if ( InFront( NPC->currentOrigin, NPC->enemy->currentOrigin, yawOnlyAngles, 0.25f ) ) + {//facing me + float minSafeDistSq = (NPC->maxs[0]*1.5f+NPC->enemy->maxs[0]*1.5f+NPC->enemy->client->ps.SaberLength()+24.0f); + minSafeDistSq *= minSafeDistSq; + float distSq = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + if ( distSq < minSafeDistSq ) + {//evade! + qboolean doJump = ( NPC->enemy->client->ps.torsoAnim == BOTH_ROLL_STAB || distSq < 3000.0f );//not much time left, just jump! + if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) + || !doJump ) + {//roll? + vec3_t enemyRight, dir2Me; + + AngleVectors( yawOnlyAngles, NULL, enemyRight, NULL ); + VectorSubtract( NPC->currentOrigin, NPC->enemy->currentOrigin, dir2Me ); + VectorNormalize( dir2Me ); + float dot = DotProduct( enemyRight, dir2Me ); + + ucmd.forwardmove = 0; + TIMER_Start( NPC, "duck", Q_irand( 500, 1500 ) ); + ucmd.upmove = -127; + //NOTE: this *assumes* I'm facing him! + if ( dot > 0 ) + {//I'm to his right + if ( !NPC_MoveDirClear( 0, -127, qfalse ) ) + {//fuck, jump instead + doJump = qtrue; + } + else + { + TIMER_Start( NPC, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( NPC, "strafeRight", 0 ); + ucmd.rightmove = -127; + if ( d_JediAI->integer ) + { + Com_Printf( "%s rolling left from roll-stab!\n", NPC->NPC_type ); + } + if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//fuck it, just force it + NPC_SetAnim(NPC,SETANIM_BOTH,BOTH_ROLL_L,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + G_AddEvent( NPC, EV_ROLL, 0 ); + NPC->client->ps.saberMove = LS_NONE; + } + } + } + else + {//I'm to his left + if ( !NPC_MoveDirClear( 0, 127, qfalse ) ) + {//fuck, jump instead + doJump = qtrue; + } + else + { + TIMER_Start( NPC, "strafeRight", Q_irand( 500, 1500 ) ); + TIMER_Set( NPC, "strafeLeft", 0 ); + ucmd.rightmove = 127; + if ( d_JediAI->integer ) + { + Com_Printf( "%s rolling right from roll-stab!\n", NPC->NPC_type ); + } + if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//fuck it, just force it + NPC_SetAnim(NPC,SETANIM_BOTH,BOTH_ROLL_R,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + G_AddEvent( NPC, EV_ROLL, 0 ); + NPC->client->ps.saberMove = LS_NONE; + } + } + } + if ( !doJump ) + { + TIMER_Set( NPC, "specialEvasion", 3000 ); + return EVASION_DUCK; + } + } + //didn't roll, do jump + if ( NPC->s.weapon != WP_SABER + || (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) + || NPC->client->NPC_class == CLASS_SHADOWTROOPER + || NPC->client->NPC_class == CLASS_ALORA + || Q_irand( -3, NPCInfo->rank ) > RANK_CREWMAN ) + {//superjump + NPC->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently + if ( Q_irand( 0, 2 ) ) + {//make it a backflip + ucmd.forwardmove = -127; + TIMER_Set( NPC, "roamTime", -level.time ); + TIMER_Set( NPC, "strafeLeft", -level.time ); + TIMER_Set( NPC, "strafeRight", -level.time ); + TIMER_Set( NPC, "walking", -level.time ); + TIMER_Set( NPC, "moveforward", -level.time ); + TIMER_Set( NPC, "movenone", -level.time ); + TIMER_Set( NPC, "moveright", -level.time ); + TIMER_Set( NPC, "moveleft", -level.time ); + TIMER_Set( NPC, "movecenter", -level.time ); + TIMER_Set( NPC, "moveback", Q_irand( 500, 1000 ) ); + if ( d_JediAI->integer ) + { + Com_Printf( "%s backflipping from roll-stab!\n", NPC->NPC_type ); + } + } + else + { + if ( d_JediAI->integer ) + { + Com_Printf( "%s force-jumping over roll-stab!\n", NPC->NPC_type ); + } + } + TIMER_Set( NPC, "specialEvasion", 3000 ); + return EVASION_FJUMP; + } + else + {//normal jump + ucmd.upmove = 127; + if ( d_JediAI->integer ) + { + Com_Printf( "%s jumping over roll-stab!\n", NPC->NPC_type ); + } + TIMER_Set( NPC, "specialEvasion", 2000 ); + return EVASION_JUMP; + } + } + } + } + } + } + return EVASION_NONE; +} + +extern float ShortestLineSegBewteen2LineSegs( vec3_t start1, vec3_t end1, vec3_t start2, vec3_t end2, vec3_t close_pnt1, vec3_t close_pnt2 ); +extern int WPDEBUG_SaberColor( saber_colors_t saberColor ); +static qboolean Jedi_SaberBlock( void ) +{ + vec3_t hitloc, saberTipOld, saberTip, top, bottom, axisPoint, saberPoint, dir;//saberBase, + vec3_t pointDir, baseDir, tipDir, saberHitPoint, saberMins={-4,-4,-4}, saberMaxs={4,4,4}; + float pointDist, baseDirPerc; + float dist, bestDist = Q3_INFINITE; + int saberNum = 0, bladeNum = 0; + int closestSaberNum = 0, closestBladeNum = 0; + + //FIXME: reborn don't block enough anymore + /* + //maybe do this on easy only... or only on grunt-level reborn + if ( NPC->client->ps.weaponTime ) + {//i'm attacking right now + return qfalse; + } + */ + + if ( !TIMER_Done( NPC, "parryReCalcTime" ) ) + {//can't do our own re-think of which parry to use yet + return qfalse; + } + + if ( NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] > level.time ) + {//can't move the saber to another position yet + return qfalse; + } + + /* + if ( NPCInfo->rank < RANK_LT_JG && Q_irand( 0, (2 - g_spskill->integer) ) ) + {//lower rank reborn have a random chance of not doing it at all + NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 300; + return qfalse; + } + */ + + if ( NPC->enemy->health <= 0 || !NPC->enemy->client ) + {//don't keep blocking him once he's dead (or if not a client) + return qfalse; + } + /* + //VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip ); + //VectorMA( NPC->enemy->client->renderInfo.muzzlePointNext, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirNext, saberTipNext ); + VectorMA( NPC->enemy->client->renderInfo.muzzlePointOld, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirOld, saberTipOld ); + VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip ); + + VectorSubtract( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->renderInfo.muzzlePointOld, dir );//get the dir + VectorAdd( dir, NPC->enemy->client->renderInfo.muzzlePoint, saberBase );//extrapolate + + VectorSubtract( saberTip, saberTipOld, dir );//get the dir + VectorAdd( dir, saberTip, saberTipOld );//extrapolate + + VectorCopy( NPC->currentOrigin, top ); + top[2] = NPC->absmax[2]; + VectorCopy( NPC->currentOrigin, bottom ); + bottom[2] = NPC->absmin[2]; + + float dist = ShortestLineSegBewteen2LineSegs( saberBase, saberTipOld, bottom, top, saberPoint, axisPoint ); + if ( 0 )//dist > NPC->maxs[0]*4 )//was *3 + {//FIXME: sometimes he reacts when you're too far away to actually hit him + if ( d_JediAI->integer ) + { + gi.Printf( "enemy saber dist: %4.2f\n", dist ); + } + TIMER_Set( NPC, "parryTime", -1 ); + return qfalse; + } + + //get the actual point of impact + trace_t tr; + gi.trace( &tr, saberPoint, vec3_origin, vec3_origin, axisPoint, NPC->enemy->s.number, MASK_SHOT, G2_RETURNONHIT, 10 ); + if ( tr.allsolid || tr.startsolid ) + {//estimate + VectorSubtract( saberPoint, axisPoint, dir ); + VectorNormalize( dir ); + VectorMA( axisPoint, NPC->maxs[0]*1.22, dir, hitloc ); + } + else + { + VectorCopy( tr.endpos, hitloc ); + } + */ + + //FIXME: need to check against both sabers/blades now!...? + + for ( saberNum = 0; saberNum < MAX_SABERS; saberNum++ ) + { + for ( bladeNum = 0; bladeNum < NPC->enemy->client->ps.saber[saberNum].numBlades; bladeNum++ ) + { + if ( NPC->enemy->client->ps.saber[saberNum].type != SABER_NONE + && NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].length > 0 ) + {//valid saber and this blade is on + VectorMA( NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld, NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].length, NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld, saberTipOld ); + VectorMA( NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].length, NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, saberTip ); + + VectorCopy( NPC->currentOrigin, top ); + top[2] = NPC->absmax[2]; + VectorCopy( NPC->currentOrigin, bottom ); + bottom[2] = NPC->absmin[2]; + + dist = ShortestLineSegBewteen2LineSegs( NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, saberTip, bottom, top, saberPoint, axisPoint ); + if ( dist < bestDist ) + { + bestDist = dist; + closestSaberNum = saberNum; + closestBladeNum = bladeNum; + } + } + } + } + + if ( bestDist > NPC->maxs[0]*5 )//was *3 + {//FIXME: sometimes he reacts when you're too far away to actually hit him + if ( d_JediAI->integer ) + { + Com_Printf( S_COLOR_RED"enemy saber dist: %4.2f\n", bestDist ); + } + /* + if ( bestDist < 300 //close + && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves + && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || BG_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me + {//he's swinging at me and close enough to be a threat, don't start an attack right now + TIMER_Set( NPC, "parryTime", 100 ); + } + else + */ + { + TIMER_Set( NPC, "parryTime", -1 ); + } + return qfalse; + } + + dist = bestDist; + + if ( d_JediAI->integer ) + { + Com_Printf( S_COLOR_GREEN"enemy saber dist: %4.2f\n", dist ); + } + + //now use the closest blade for my evasion check + VectorMA( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePointOld, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].length, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzleDirOld, saberTipOld ); + VectorMA( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePoint, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].length, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzleDir, saberTip ); + + VectorCopy( NPC->currentOrigin, top ); + top[2] = NPC->absmax[2]; + VectorCopy( NPC->currentOrigin, bottom ); + bottom[2] = NPC->absmin[2]; + + dist = ShortestLineSegBewteen2LineSegs( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePoint, saberTip, bottom, top, saberPoint, axisPoint ); + VectorSubtract( saberPoint, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePoint, pointDir ); + pointDist = VectorLength( pointDir ); + + if ( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].length <= 0 ) + { + baseDirPerc = 0.5f; + } + else + { + baseDirPerc = pointDist/NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].length; + } + VectorSubtract( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePoint, NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].muzzlePointOld, baseDir ); + VectorSubtract( saberTip, saberTipOld, tipDir ); + VectorScale( baseDir, baseDirPerc, baseDir ); + VectorMA( baseDir, 1.0f-baseDirPerc, tipDir, dir ); + VectorMA( saberPoint, 200, dir, hitloc ); + + //get the actual point of impact + trace_t tr; + gi.trace( &tr, saberPoint, saberMins, saberMaxs, hitloc, NPC->enemy->s.number, CONTENTS_BODY );//, G2_RETURNONHIT, 10 ); + if ( tr.allsolid || tr.startsolid || tr.fraction >= 1.0f ) + {//estimate + vec3_t dir2Me; + VectorSubtract( axisPoint, saberPoint, dir2Me ); + dist = VectorNormalize( dir2Me ); + if ( DotProduct( dir, dir2Me ) < 0.2f ) + {//saber is not swinging in my direction + /* + if ( dist < 300 //close + && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves + && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || PM_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me + {//he's swinging at me and close enough to be a threat, don't start an attack right now + TIMER_Set( NPC, "parryTime", 100 ); + } + else + */ + { + TIMER_Set( NPC, "parryTime", -1 ); + } + return qfalse; + } + ShortestLineSegBewteen2LineSegs( saberPoint, hitloc, bottom, top, saberHitPoint, hitloc ); + /* + VectorSubtract( saberPoint, axisPoint, dir ); + VectorNormalize( dir ); + VectorMA( axisPoint, NPC->maxs[0]*1.22, dir, hitloc ); + */ + } + else + { + VectorCopy( tr.endpos, hitloc ); + } + + if ( d_JediAI->integer ) + { + G_DebugLine( saberPoint, hitloc, FRAMETIME, WPDEBUG_SaberColor( NPC->enemy->client->ps.saber[closestSaberNum].blade[closestBladeNum].color ), qtrue ); + } + + //FIXME: if saber is off and/or we have force speed and want to be really cocky, + // and the swing misses by some amount, we can use the dodges here... :) + evasionType_t evasionType; + if ( (evasionType=Jedi_SaberBlockGo( NPC, &ucmd, hitloc, dir, NULL, dist )) != EVASION_NONE ) + {//did some sort of evasion + if ( evasionType != EVASION_DODGE ) + {//(not dodge) + if ( !NPC->client->ps.saberInFlight ) + {//make sure saber is on + NPC->client->ps.SaberActivate(); + } + + //debounce our parry recalc time + int parryReCalcTime = Jedi_ReCalcParryTime( NPC, evasionType ); + TIMER_Set( NPC, "parryReCalcTime", Q_irand( 0, parryReCalcTime ) ); + if ( d_JediAI->integer ) + { + gi.Printf( "Keep parry choice until: %d\n", level.time + parryReCalcTime ); + } + + //determine how long to hold this anim + if ( TIMER_Done( NPC, "parryTime" ) ) + { + if ( NPC->client->NPC_class == CLASS_TAVION + || NPC->client->NPC_class == CLASS_SHADOWTROOPER + || NPC->client->NPC_class == CLASS_ALORA ) + { + TIMER_Set( NPC, "parryTime", Q_irand( parryReCalcTime/2, parryReCalcTime*1.5 ) ); + } + else if ( NPCInfo->rank >= RANK_LT_JG ) + {//fencers and higher hold a parry less + TIMER_Set( NPC, "parryTime", parryReCalcTime ); + } + else + {//others hold it longer + TIMER_Set( NPC, "parryTime", Q_irand( 1, 2 )*parryReCalcTime ); + } + } + } + else + {//dodged + int dodgeTime = NPC->client->ps.torsoAnimTimer; + if ( NPCInfo->rank > RANK_LT_COMM && NPC->client->NPC_class != CLASS_DESANN ) + {//higher-level guys can dodge faster + dodgeTime -= 200; + } + TIMER_Set( NPC, "parryReCalcTime", dodgeTime ); + TIMER_Set( NPC, "parryTime", dodgeTime ); + } + } + if ( evasionType != EVASION_DUCK_PARRY + && evasionType != EVASION_JUMP_PARRY + && evasionType != EVASION_JUMP + && evasionType != EVASION_DUCK + && evasionType != EVASION_FJUMP ) + { + if ( Jedi_CheckEvadeSpecialAttacks() != EVASION_NONE ) + {//got a new evasion! + //see if it's okay to jump + Jedi_CheckJumpEvasionSafety( NPC, &ucmd, evasionType ); + } + } + return qtrue; +} +/* +------------------------- +Jedi_EvasionSaber + +defend if other is using saber and attacking me! +------------------------- +*/ +static void Jedi_EvasionSaber( vec3_t enemy_movedir, float enemy_dist, vec3_t enemy_dir ) +{ + vec3_t dirEnemy2Me; + int evasionChance = 30;//only step aside 30% if he's moving at me but not attacking + qboolean enemy_attacking = qfalse; + qboolean throwing_saber = qfalse; + qboolean shooting_lightning = qfalse; + + if ( !NPC->enemy->client ) + { + return; + } + else if ( NPC->enemy->client + && NPC->enemy->s.weapon == WP_SABER + && NPC->enemy->client->ps.saberLockTime > level.time ) + {//don't try to block/evade an enemy who is in a saberLock + return; + } + else if ( (NPC->client->ps.saberEventFlags&SEF_LOCK_WON) + && NPC->enemy->painDebounceTime > level.time ) + {//pressing the advantage of winning a saber lock + return; + } + + if ( NPC->enemy->client->ps.saberInFlight && !TIMER_Done( NPC, "taunting" ) ) + {//if he's throwing his saber, stop taunting + TIMER_Set( NPC, "taunting", -level.time ); + if ( !NPC->client->ps.saberInFlight ) + { + NPC->client->ps.SaberActivate(); + } + } + + if ( TIMER_Done( NPC, "parryTime" ) ) + { + if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE && + NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//wasn't blocked myself + NPC->client->ps.saberBlocked = BLOCKED_NONE; + } + } + + if ( NPC->enemy->client->ps.weaponTime && NPC->enemy->client->ps.weaponstate == WEAPON_FIRING ) + { + if ( (!NPC->client->ps.saberInFlight || (NPC->client->ps.dualSabers&&NPC->client->ps.saber[1].Active()) ) + && Jedi_SaberBlock() ) + { + return; + } + } + else if ( Jedi_CheckEvadeSpecialAttacks() != EVASION_NONE ) + { + return; + } + + + VectorSubtract( NPC->currentOrigin, NPC->enemy->currentOrigin, dirEnemy2Me ); + VectorNormalize( dirEnemy2Me ); + + if ( NPC->enemy->client->ps.weaponTime && NPC->enemy->client->ps.weaponstate == WEAPON_FIRING ) + {//enemy is attacking + enemy_attacking = qtrue; + evasionChance = 90; + } + + if ( (NPC->enemy->client->ps.forcePowersActive&(1<enemy->client->ps.saberInFlight + && NPC->enemy->client->ps.saberEntityNum != ENTITYNUM_NONE + && NPC->enemy->client->ps.saberEntityState != SES_RETURNING ) + {//enemy is shooting lightning + enemy_attacking = qtrue; + throwing_saber = qtrue; + } + + //FIXME: this needs to take skill and rank(reborn type) into account much more + if ( Q_irand( 0, 100 ) < evasionChance ) + {//check to see if he's coming at me + float facingAmt; + if ( VectorCompare( enemy_movedir, vec3_origin ) || shooting_lightning || throwing_saber ) + {//he's not moving (or he's using a ranged attack), see if he's facing me + vec3_t enemy_fwd; + AngleVectors( NPC->enemy->client->ps.viewangles, enemy_fwd, NULL, NULL ); + facingAmt = DotProduct( enemy_fwd, dirEnemy2Me ); + } + else + {//he's moving + facingAmt = DotProduct( enemy_movedir, dirEnemy2Me ); + } + + if ( Q_flrand( 0.25, 1 ) < facingAmt ) + {//coming at/facing me! + int whichDefense = 0; + /*if ( NPC->client->NPC_class == CLASS_SABOTEUR ) + { + int sabDef = Q_irand( 0, 3 ); + if ( sabDef ) + {//25% chance of trying normal jedi defense logic + whichDefense = 100; + } + else + { + if ( sabDef == 1 ) + {//25% chance of strafing + Jedi_Strafe( 300, 1000, 0, 1000, qfalse ); + } + else + {//50% chance of trying to dodge/roll/jump using jedi missile evasion logic + Jedi_SaberBlock(); + } + return; + } + } + else */if ( NPC->client->ps.weaponTime + || NPC->client->ps.saberInFlight + || NPC->client->NPC_class == CLASS_BOBAFETT + || (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER) + || NPC->client->NPC_class == CLASS_ROCKETTROOPER ) + {//I'm attacking or recovering from a parry, can only try to strafe/jump right now + if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression ) + { + return; + } + whichDefense = 100; + } + else + { + if ( shooting_lightning ) + {//check for lightning attack + //only valid defense is strafe and/or jump + whichDefense = 100; + } + else if ( throwing_saber ) + {//he's thrown his saber! See if it's coming at me + float saberDist; + vec3_t saberDir2Me; + vec3_t saberMoveDir; + gentity_t *saber = &g_entities[NPC->enemy->client->ps.saberEntityNum]; + VectorSubtract( NPC->currentOrigin, saber->currentOrigin, saberDir2Me ); + saberDist = VectorNormalize( saberDir2Me ); + VectorCopy( saber->s.pos.trDelta, saberMoveDir ); + VectorNormalize( saberMoveDir ); + if ( !Q_irand( 0, 3 ) ) + { + //Com_Printf( "(%d) raise agg - enemy threw saber\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 ) + {//it's heading towards me + if ( saberDist < 100 ) + {//it's close + whichDefense = Q_irand( 3, 6 ); + } + else if ( saberDist < 200 ) + {//got some time, yet, try pushing + whichDefense = Q_irand( 0, 8 ); + } + } + } + if ( whichDefense ) + {//already chose one + } + else if ( enemy_dist > 80 || !enemy_attacking ) + {//he's pretty far, or not swinging, just strafe + if ( VectorCompare( enemy_movedir, vec3_origin ) ) + {//if he's not moving, not swinging and far enough away, no evasion necc. + return; + } + if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression ) + { + return; + } + whichDefense = 100; + } + else + {//he's getting close and swinging at me + vec3_t fwd; + //see if I'm facing him + AngleVectors( NPC->client->ps.viewangles, fwd, NULL, NULL ); + if ( DotProduct( enemy_dir, fwd ) < 0.5 ) + {//I'm not really facing him, best option is to strafe + whichDefense = Q_irand( 5, 16 ); + } + else if ( enemy_dist < 56 ) + {//he's very close, maybe we should be more inclined to block or throw + whichDefense = Q_irand( NPCInfo->stats.aggression, 12 ); + } + else + { + whichDefense = Q_irand( 2, 16 ); + } + } + } + + if ( whichDefense >= 4 && whichDefense <= 12 ) + {//would try to block + if ( NPC->client->ps.saberInFlight ) + {//can't, saber in not in hand, so fall back to strafe/jump + whichDefense = 100; + } + } + + switch( whichDefense ) + { + case 0: + case 1: + case 2: + case 3: + //use jedi force push? or kick? + //FIXME: try to do this if health low or enemy back to a cliff? + if ( Jedi_DecideKick()//let's try a kick + && ( G_PickAutoMultiKick( NPC, qfalse, qtrue ) != LS_NONE + || (G_CanKickEntity(NPC, NPC->enemy )&&G_PickAutoKick( NPC, NPC->enemy, qtrue )!=LS_NONE) + ) + ) + {//kicked + TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) ); + } + else if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && TIMER_Done( NPC, "parryTime" ) ) + {//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]] + ForceThrow( NPC, qfalse ); + } + break; + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + //try to parry the blow + //gi.Printf( "blocking\n" ); + Jedi_SaberBlock(); + break; + default: + //Evade! + //start a strafe left/right if not already + if ( !Q_irand( 0, 5 ) || !Jedi_Strafe( 300, 1000, 0, 1000, qfalse ) ) + {//certain chance they will pick an alternative evasion + //if couldn't strafe, try a different kind of evasion... + if ( Jedi_DecideKick() && G_CanKickEntity(NPC, NPC->enemy ) && G_PickAutoKick( NPC, NPC->enemy, qtrue ) != LS_NONE ) + {//kicked! + TIMER_Set( NPC, "kickDebounce", Q_irand( 3000, 10000 ) ); + } + else if ( shooting_lightning || throwing_saber || enemy_dist < 80 ) + { + //FIXME: force-jump+forward - jump over the guy! + if ( shooting_lightning || (!Q_irand( 0, 2 ) && NPCInfo->stats.aggression < 4 && TIMER_Done( NPC, "parryTime" ) ) ) + { + if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && !shooting_lightning && Q_irand( 0, 2 ) ) + {//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]] + ForceThrow( NPC, qfalse ); + } + else if ( (NPCInfo->rank==RANK_CREWMAN||NPCInfo->rank>RANK_LT_JG) + && !(NPCInfo->scriptFlags&SCF_NO_ACROBATICS) + && NPC->client->ps.forceRageRecoveryTime < level.time + && !(NPC->client->ps.forcePowersActive&(1<client->ps ) ) + {//FIXME: make this a function call? + //FIXME: check for clearance, safety of landing spot? + NPC->client->ps.forceJumpCharge = 480; + //Don't jump again for another 2 to 5 seconds + TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) ); + if ( Q_irand( 0, 2 ) ) + { + ucmd.forwardmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + else + { + ucmd.forwardmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + //FIXME: if this jump is cleared, we can't block... so pick a random lower block? + if ( Q_irand( 0, 1 ) )//FIXME: make intelligent + { + NPC->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + } + else + { + NPC->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + } + } + } + else if ( enemy_attacking ) + { + Jedi_SaberBlock(); + } + } + } + else + {//strafed + if ( d_JediAI->integer ) + { + gi.Printf( "def strafe\n" ); + } + if ( !(NPCInfo->scriptFlags&SCF_NO_ACROBATICS) + && NPC->client->ps.forceRageRecoveryTime < level.time + && !(NPC->client->ps.forcePowersActive&(1<rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG ) + && !PM_InKnockDown( &NPC->client->ps ) + && !Q_irand( 0, 5 ) ) + {//FIXME: make this a function call? + //FIXME: check for clearance, safety of landing spot? + if ( NPC->client->NPC_class == CLASS_BOBAFETT + || (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER) + || NPC->client->NPC_class == CLASS_ROCKETTROOPER ) + { + NPC->client->ps.forceJumpCharge = 280;//FIXME: calc this intelligently? + } + else + { + NPC->client->ps.forceJumpCharge = 320; + } + //Don't jump again for another 2 to 5 seconds + TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) ); + } + } + break; + } + + //turn off slow walking no matter what + TIMER_Set( NPC, "walking", -level.time ); + TIMER_Set( NPC, "taunting", -level.time ); + } + } +} +/* +------------------------- +Jedi_Flee +------------------------- +*/ +/* + +static qboolean Jedi_Flee( void ) +{ + return qfalse; +} +*/ + + +/* +========================================================================================== +INTERNAL AI ROUTINES +========================================================================================== +*/ +gentity_t *Jedi_FindEnemyInCone( gentity_t *self, gentity_t *fallback, float minDot ) +{ + vec3_t forward, mins, maxs, dir; + float dist, bestDist = Q3_INFINITE; + gentity_t *enemy = fallback; + gentity_t *check = NULL; + gentity_t *entityList[MAX_GENTITIES]; + int e, numListedEntities; + trace_t tr; + + if ( !self->client ) + { + return enemy; + } + + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + + for ( e = 0 ; e < 3 ; e++ ) + { + mins[e] = self->currentOrigin[e] - 1024; + maxs[e] = self->currentOrigin[e] + 1024; + } + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + check = entityList[e]; + if ( check == self ) + {//me + continue; + } + if ( !(check->inuse) ) + {//freed + continue; + } + if ( !check->client ) + {//not a client - FIXME: what about turrets? + continue; + } + if ( check->client->playerTeam != self->client->enemyTeam ) + {//not an enemy - FIXME: what about turrets? + continue; + } + if ( check->health <= 0 ) + {//dead + continue; + } + + if ( !gi.inPVS( check->currentOrigin, self->currentOrigin ) ) + {//can't potentially see them + continue; + } + + VectorSubtract( check->currentOrigin, self->currentOrigin, dir ); + dist = VectorNormalize( dir ); + + if ( DotProduct( dir, forward ) < minDot ) + {//not in front + continue; + } + + //really should have a clear LOS to this thing... + gi.trace( &tr, self->currentOrigin, vec3_origin, vec3_origin, check->currentOrigin, self->s.number, MASK_SHOT ); + if ( tr.fraction < 1.0f && tr.entityNum != check->s.number ) + {//must have clear shot + continue; + } + + if ( dist < bestDist ) + {//closer than our last best one + dist = bestDist; + enemy = check; + } + } + return enemy; +} + +static void Jedi_SetEnemyInfo( vec3_t enemy_dest, vec3_t enemy_dir, float *enemy_dist, vec3_t enemy_movedir, float *enemy_movespeed, int prediction ) +{ + if ( !NPC || !NPC->enemy ) + {//no valid enemy + return; + } + if ( !NPC->enemy->client ) + { + VectorClear( enemy_movedir ); + *enemy_movespeed = 0; + VectorCopy( NPC->enemy->currentOrigin, enemy_dest ); + enemy_dest[2] += NPC->enemy->mins[2] + 24;//get it's origin to a height I can work with + VectorSubtract( enemy_dest, NPC->currentOrigin, enemy_dir ); + //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade.... + *enemy_dist = VectorNormalize( enemy_dir );// - (NPC->client->ps.saberLengthMax + NPC->maxs[0]*1.5 + 16); + } + else + {//see where enemy is headed + VectorCopy( NPC->enemy->client->ps.velocity, enemy_movedir ); + *enemy_movespeed = VectorNormalize( enemy_movedir ); + //figure out where he'll be, say, 3 frames from now + VectorMA( NPC->enemy->currentOrigin, *enemy_movespeed * 0.001 * prediction, enemy_movedir, enemy_dest ); + //figure out what dir the enemy's estimated position is from me and how far from the tip of my saber he is + VectorSubtract( enemy_dest, NPC->currentOrigin, enemy_dir );//NPC->client->renderInfo.muzzlePoint + //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade.... + *enemy_dist = VectorNormalize( enemy_dir ) - (NPC->client->ps.SaberLengthMax() + NPC->maxs[0]*1.5 + 16); + //FIXME: keep a group of enemies around me and use that info to make decisions... + // For instance, if there are multiple enemies, evade more, push them away + // and use medium attacks. If enemies are using blasters, switch to fast. + // If one jedi enemy, use strong attacks. Use grip when fighting one or + // two enemies, use lightning spread when fighting multiple enemies, etc. + // Also, when kill one, check rest of group instead of walking up to victim. + } + //init this to false + enemy_in_striking_range = qfalse; + if ( *enemy_dist <= 0.0f ) + { + enemy_in_striking_range = qtrue; + } + else + {//if he's too far away, see if he's at least facing us or coming towards us + if ( *enemy_dist <= 32.0f ) + {//has to be facing us + vec3_t eAngles = {0,NPC->currentAngles[YAW],0}; + if ( InFOV( NPC->currentOrigin, NPC->enemy->currentOrigin, eAngles, 30, 90 ) ) + {//in striking range + enemy_in_striking_range = qtrue; + } + } + if ( *enemy_dist >= 64.0f ) + {//we have to be approaching each other + float vDot = 1.0f; + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//I am moving, see if I'm moving toward the enemy + vec3_t eDir; + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, eDir ); + VectorNormalize( eDir ); + vDot = DotProduct( eDir, NPC->client->ps.velocity ); + } + else if ( NPC->enemy->client && !VectorCompare( NPC->enemy->client->ps.velocity, vec3_origin ) ) + {//I'm not moving, but the enemy is, see if he's moving towards me + vec3_t meDir; + VectorSubtract( NPC->currentOrigin, NPC->enemy->currentOrigin, meDir ); + VectorNormalize( meDir ); + vDot = DotProduct( meDir, NPC->enemy->client->ps.velocity ); + } + else + {//neither of us is moving, below check will fail, so just return + return; + } + if ( vDot >= *enemy_dist ) + {//moving towards each other + enemy_in_striking_range = qtrue; + } + } + } +} + +void NPC_EvasionSaber( void ) +{ + if ( ucmd.upmove <= 0//not jumping + && (!ucmd.upmove || !ucmd.rightmove) )//either just ducking or just strafing (i.e.: not rolling + {//see if we need to avoid their saber + vec3_t enemy_dir, enemy_movedir, enemy_dest; + float enemy_dist, enemy_movespeed; + //set enemy + Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 ); + Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir ); + } +} + +extern float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire ); +static void Jedi_FaceEnemy( qboolean doPitch ) +{ + vec3_t enemy_eyes, eyes; + vec3_t angles = { 0 }; + + if ( NPC == NULL ) + return; + + if ( NPC->enemy == NULL ) + return; + + if ( NPC->client->ps.forcePowersActive & (1<client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//don't update? + NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH]; + NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW]; + return; + } + CalcEntitySpot( NPC, SPOT_HEAD, eyes ); + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_eyes ); + + if ( NPC->client->NPC_class == CLASS_BOBAFETT + && TIMER_Done( NPC, "flameTime" ) + && NPC->s.weapon != WP_NONE + && NPC->s.weapon != WP_DISRUPTOR + && (NPC->s.weapon != WP_ROCKET_LAUNCHER||!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) + && NPC->s.weapon != WP_THERMAL + && NPC->s.weapon != WP_TRIP_MINE + && NPC->s.weapon != WP_DET_PACK + && NPC->s.weapon != WP_STUN_BATON + && NPC->s.weapon != WP_MELEE ) + {//boba leads his enemy + if ( NPC->health < NPC->max_health*0.5f ) + {//lead + float missileSpeed = WP_SpeedOfMissileForWeapon( NPC->s.weapon, ((qboolean)(NPCInfo->scriptFlags&SCF_ALT_FIRE)) ); + if ( missileSpeed ) + { + float eDist = Distance( eyes, enemy_eyes ); + eDist /= missileSpeed;//How many seconds it will take to get to the enemy + VectorMA( enemy_eyes, eDist*Q_flrand(0.95f,1.25f), NPC->enemy->client->ps.velocity, enemy_eyes ); + } + } + } + + //Find the desired angles + if ( !NPC->client->ps.saberInFlight + && (NPC->client->ps.legsAnim == BOTH_A2_STABBACK1 + || NPC->client->ps.legsAnim == BOTH_CROUCHATTACKBACK1 + || NPC->client->ps.legsAnim == BOTH_ATTACK_BACK + || NPC->client->ps.legsAnim == BOTH_A7_KICK_B ) + ) + {//point *away* + GetAnglesForDirection( enemy_eyes, eyes, angles ); + } + else if ( NPC->client->ps.legsAnim == BOTH_A7_KICK_R ) + {//keep enemy to right + } + else if ( NPC->client->ps.legsAnim == BOTH_A7_KICK_L ) + {//keep enemy to left + } + else if ( NPC->client->ps.legsAnim == BOTH_A7_KICK_RL + || NPC->client->ps.legsAnim == BOTH_A7_KICK_BF + || NPC->client->ps.legsAnim == BOTH_A7_KICK_S ) + {//??? + } + else + {//point towards him + GetAnglesForDirection( eyes, enemy_eyes, angles ); + } + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + /* + if ( NPC->client->ps.saberBlocked == BLOCKED_UPPER_LEFT ) + {//temp hack- to make up for poor coverage on left side + NPCInfo->desiredYaw += 30; + } + */ + + if ( doPitch ) + { + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + if ( NPC->client->ps.saberInFlight ) + {//tilt down a little + NPCInfo->desiredPitch += 10; + } + } + //FIXME: else desiredPitch = 0? Or keep previous? +} + +static void Jedi_DebounceDirectionChanges( void ) +{ + //FIXME: check these before making fwd/back & right/left decisions? + //Time-debounce changes in forward/back dir + if ( ucmd.forwardmove > 0 ) + { + if ( !TIMER_Done( NPC, "moveback" ) || !TIMER_Done( NPC, "movenone" ) ) + { + ucmd.forwardmove = 0; + //now we have to normalize the total movement again + if ( ucmd.rightmove > 0 ) + { + ucmd.rightmove = 127; + } + else if ( ucmd.rightmove < 0 ) + { + ucmd.rightmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveback", -level.time ); + if ( TIMER_Done( NPC, "movenone" ) ) + { + TIMER_Set( NPC, "movenone", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveforward" ) ) + {//FIXME: should be if it's zero? + if ( TIMER_Done( NPC, "lastmoveforward" ) ) + { + int holdDirTime = Q_irand( 500, 2000 ); + TIMER_Set( NPC, "moveforward", holdDirTime ); + //so we don't keep doing this over and over again - new nav stuff makes them coast to a stop, so they could be just slowing down from the last "moveback" timer's ending... + TIMER_Set( NPC, "lastmoveforward", holdDirTime + Q_irand(1000,2000) ); + } + } + else + {//NOTE: edge checking should stop me if this is bad... but what if it sends us colliding into the enemy? + //if being forced to move forward, do a full-speed moveforward + ucmd.forwardmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + } + else if ( ucmd.forwardmove < 0 ) + { + if ( !TIMER_Done( NPC, "moveforward" ) || !TIMER_Done( NPC, "movenone" ) ) + { + ucmd.forwardmove = 0; + //now we have to normalize the total movement again + if ( ucmd.rightmove > 0 ) + { + ucmd.rightmove = 127; + } + else if ( ucmd.rightmove < 0 ) + { + ucmd.rightmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveforward", -level.time ); + if ( TIMER_Done( NPC, "movenone" ) ) + { + TIMER_Set( NPC, "movenone", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveback" ) ) + {//FIXME: should be if it's zero? + if ( TIMER_Done( NPC, "lastmoveback" ) ) + { + int holdDirTime = Q_irand( 500, 2000 ); + TIMER_Set( NPC, "moveback", holdDirTime ); + //so we don't keep doing this over and over again - new nav stuff makes them coast to a stop, so they could be just slowing down from the last "moveback" timer's ending... + TIMER_Set( NPC, "lastmoveback", holdDirTime + Q_irand(1000,2000) ); + } + } + else + {//NOTE: edge checking should stop me if this is bad... + //if being forced to move back, do a full-speed moveback + ucmd.forwardmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + } + else if ( !TIMER_Done( NPC, "moveforward" ) ) + {//NOTE: edge checking should stop me if this is bad... but what if it sends us colliding into the enemy? + ucmd.forwardmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + else if ( !TIMER_Done( NPC, "moveback" ) ) + {//NOTE: edge checking should stop me if this is bad... + ucmd.forwardmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + //Time-debounce changes in right/left dir + if ( ucmd.rightmove > 0 ) + { + if ( !TIMER_Done( NPC, "moveleft" ) || !TIMER_Done( NPC, "movecenter" ) ) + { + ucmd.rightmove = 0; + //now we have to normalize the total movement again + if ( ucmd.forwardmove > 0 ) + { + ucmd.forwardmove = 127; + } + else if ( ucmd.forwardmove < 0 ) + { + ucmd.forwardmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveleft", -level.time ); + if ( TIMER_Done( NPC, "movecenter" ) ) + { + TIMER_Set( NPC, "movecenter", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveright" ) ) + {//FIXME: should be if it's zero? + if ( TIMER_Done( NPC, "lastmoveright" ) ) + { + int holdDirTime = Q_irand( 250, 1500 ); + TIMER_Set( NPC, "moveright", holdDirTime ); + //so we don't keep doing this over and over again - new nav stuff makes them coast to a stop, so they could be just slowing down from the last "moveback" timer's ending... + TIMER_Set( NPC, "lastmoveright", holdDirTime + Q_irand(1000,2000) ); + } + } + else + {//NOTE: edge checking should stop me if this is bad... + //if being forced to move back, do a full-speed moveright + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + } + else if ( ucmd.rightmove < 0 ) + { + if ( !TIMER_Done( NPC, "moveright" ) || !TIMER_Done( NPC, "movecenter" ) ) + { + ucmd.rightmove = 0; + //now we have to normalize the total movement again + if ( ucmd.forwardmove > 0 ) + { + ucmd.forwardmove = 127; + } + else if ( ucmd.forwardmove < 0 ) + { + ucmd.forwardmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveright", -level.time ); + if ( TIMER_Done( NPC, "movecenter" ) ) + { + TIMER_Set( NPC, "movecenter", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveleft" ) ) + {//FIXME: should be if it's zero? + if ( TIMER_Done( NPC, "lastmoveleft" ) ) + { + int holdDirTime = Q_irand( 250, 1500 ); + TIMER_Set( NPC, "moveleft", holdDirTime ); + //so we don't keep doing this over and over again - new nav stuff makes them coast to a stop, so they could be just slowing down from the last "moveback" timer's ending... + TIMER_Set( NPC, "lastmoveleft", holdDirTime + Q_irand(1000,2000) ); + } + } + else + {//NOTE: edge checking should stop me if this is bad... + //if being forced to move back, do a full-speed moveleft + ucmd.rightmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + } + else if ( !TIMER_Done( NPC, "moveright" ) ) + {//NOTE: edge checking should stop me if this is bad... + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + else if ( !TIMER_Done( NPC, "moveleft" ) ) + {//NOTE: edge checking should stop me if this is bad... + ucmd.rightmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } +} + +static void Jedi_TimersApply( void ) +{ + //use careful anim/slower movement if not already moving + if ( !ucmd.forwardmove && !TIMER_Done( NPC, "walking" ) ) + { + ucmd.buttons |= (BUTTON_WALKING); + } + + if ( !TIMER_Done( NPC, "taunting" ) ) + { + ucmd.buttons |= (BUTTON_WALKING); + } + + if ( !ucmd.rightmove ) + {//only if not already strafing + //FIXME: if enemy behind me and turning to face enemy, don't strafe in that direction, too + if ( !TIMER_Done( NPC, "strafeLeft" ) ) + { + if ( NPCInfo->desiredYaw > NPC->client->ps.viewangles[YAW] + 60 ) + {//we want to turn left, don't apply the strafing + } + else + {//go ahead and strafe left + ucmd.rightmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + } + else if ( !TIMER_Done( NPC, "strafeRight" ) ) + { + if ( NPCInfo->desiredYaw < NPC->client->ps.viewangles[YAW] - 60 ) + {//we want to turn right, don't apply the strafing + } + else + {//go ahead and strafe left + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + } + } + + Jedi_DebounceDirectionChanges(); + + if ( !TIMER_Done( NPC, "gripping" ) ) + {//FIXME: what do we do if we ran out of power? NPC's can't? + //FIXME: don't keep turning to face enemy or we'll end up spinning around + ucmd.buttons |= BUTTON_FORCEGRIP; + } + + if ( !TIMER_Done( NPC, "draining" ) ) + {//FIXME: what do we do if we ran out of power? NPC's can't? + //FIXME: don't keep turning to face enemy or we'll end up spinning around + ucmd.buttons |= BUTTON_FORCE_DRAIN; + } + + if ( !TIMER_Done( NPC, "holdLightning" ) ) + {//hold down the lightning key + ucmd.buttons |= BUTTON_FORCE_LIGHTNING; + } +} + +static void Jedi_CombatTimersUpdate( int enemy_dist ) +{ + if ( Jedi_CultistDestroyer( NPC ) ) + { + Jedi_Aggression( NPC, 5 ); + return; + } +//===START MISSING CODE================================================================= + if ( TIMER_Done( NPC, "roamTime" ) ) + { + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) ); + //okay, now mess with agression + if ( NPC->enemy && NPC->enemy->client ) + { + switch( NPC->enemy->client->ps.weapon ) + { + //FIXME: add new weapons + case WP_SABER: + //If enemy has a lightsaber, always close in + if ( !NPC->enemy->client->ps.SaberActive() ) + {//fool! Standing around unarmed, charge! + //Com_Printf( "(%d) raise agg - enemy saber off\n", level.time ); + Jedi_Aggression( NPC, 2 ); + } + else + { + //Com_Printf( "(%d) raise agg - enemy saber\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + break; + case WP_BLASTER: + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_REPEATER: + case WP_DEMP2: + case WP_FLECHETTE: + case WP_ROCKET_LAUNCHER: + case WP_CONCUSSION: + //if he has a blaster, move in when: + //They're not shooting at me + if ( NPC->enemy->attackDebounceTime < level.time ) + {//does this apply to players? + //Com_Printf( "(%d) raise agg - enemy not shooting ranged weap\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + //He's closer than a dist that gives us time to deflect + if ( enemy_dist < 256 ) + { + //Com_Printf( "(%d) raise agg - enemy ranged weap- too close\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + break; + default: + break; + } + } + } + + if ( TIMER_Done( NPC, "noStrafe" ) && TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) ) + { + //FIXME: Maybe more likely to do this if aggression higher? Or some other stat? + if ( !Q_irand( 0, 4 ) ) + {//start a strafe + if ( Jedi_Strafe( 1000, 3000, 0, 4000, qtrue ) ) + { + if ( d_JediAI->integer ) + { + gi.Printf( "off strafe\n" ); + } + } + } + else + {//postpone any strafing for a while + TIMER_Set( NPC, "noStrafe", Q_irand( 1000, 3000 ) ); + } + } +//===END MISSING CODE================================================================= + if ( NPC->client->ps.saberEventFlags ) + {//some kind of saber combat event is still pending + int newFlags = NPC->client->ps.saberEventFlags; + if ( NPC->client->ps.saberEventFlags&SEF_PARRIED ) + {//parried + TIMER_Set( NPC, "parryTime", -1 ); + /* + if ( NPCInfo->rank >= RANK_LT_JG ) + { + NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 100; + } + else + { + NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + } + */ + if ( NPC->enemy && (!NPC->enemy->client||PM_SaberInKnockaway( NPC->enemy->client->ps.saberMove )) ) + {//advance! + Jedi_Aggression( NPC, 1 );//get closer + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel-1) );//use a faster attack + } + else + { + if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff? + { + //Com_Printf( "(%d) drop agg - we parried\n", level.time ); + Jedi_Aggression( NPC, -1 ); + } + if ( !Q_irand( 0, 1 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel-1) ); + } + } + if ( d_JediAI->integer ) + { + gi.Printf( "(%d) PARRY: agg %d, no parry until %d\n", level.time, NPCInfo->stats.aggression, level.time + 100 ); + } + newFlags &= ~SEF_PARRIED; + } + if ( !NPC->client->ps.weaponTime && (NPC->client->ps.saberEventFlags&SEF_HITENEMY) )//hit enemy + {//we hit our enemy last time we swung, drop our aggression + if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff? + { + //Com_Printf( "(%d) drop agg - we hit enemy\n", level.time ); + Jedi_Aggression( NPC, -1 ); + if ( d_JediAI->integer ) + { + gi.Printf( "(%d) HIT: agg %d\n", level.time, NPCInfo->stats.aggression ); + } + if ( !Q_irand( 0, 3 ) + && NPCInfo->blockedSpeechDebounceTime < level.time + && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time + && NPC->painDebounceTime < level.time - 1000 ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + } + if ( !Q_irand( 0, 2 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel+1) ); + } + newFlags &= ~SEF_HITENEMY; + } + if ( (NPC->client->ps.saberEventFlags&SEF_BLOCKED) ) + {//was blocked whilst attacking + if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove ) + || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN ) + { + //Com_Printf( "(%d) drop agg - we were knock-blocked\n", level.time ); + if ( NPC->client->ps.saberInFlight ) + {//lost our saber, too!!! + Jedi_Aggression( NPC, -5 );//really really really should back off!!! + } + else + { + Jedi_Aggression( NPC, -2 );//really should back off! + } + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel+1) );//use a stronger attack + if ( d_JediAI->integer ) + { + gi.Printf( "(%d) KNOCK-BLOCKED: agg %d\n", level.time, NPCInfo->stats.aggression ); + } + } + else + { + if ( !Q_irand( 0, 2 ) )//FIXME: dependant on rank/diff? + { + //Com_Printf( "(%d) drop agg - we were blocked\n", level.time ); + Jedi_Aggression( NPC, -1 ); + if ( d_JediAI->integer ) + { + gi.Printf( "(%d) BLOCKED: agg %d\n", level.time, NPCInfo->stats.aggression ); + } + } + if ( !Q_irand( 0, 1 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel+1) ); + } + } + newFlags &= ~SEF_BLOCKED; + //FIXME: based on the type of parry the enemy is doing and my skill, + // choose an attack that is likely to get around the parry? + // right now that's generic in the saber animation code, auto-picks + // a next anim for me, but really should be AI-controlled. + } + if ( NPC->client->ps.saberEventFlags&SEF_DEFLECTED ) + {//deflected a shot + newFlags &= ~SEF_DEFLECTED; + if ( !Q_irand( 0, 3 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel-1) ); + } + } + if ( NPC->client->ps.saberEventFlags&SEF_HITWALL ) + {//hit a wall + newFlags &= ~SEF_HITWALL; + } + if ( NPC->client->ps.saberEventFlags&SEF_HITOBJECT ) + {//hit some other damagable object + if ( !Q_irand( 0, 3 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.saberAnimLevel-1) ); + } + newFlags &= ~SEF_HITOBJECT; + } + NPC->client->ps.saberEventFlags = newFlags; + } +} + +static void Jedi_CombatIdle( int enemy_dist ) +{ + if ( !TIMER_Done( NPC, "parryTime" ) ) + { + return; + } + if ( NPC->client->ps.saberInFlight ) + {//don't do this idle stuff if throwing saber + return; + } + if ( NPC->client->ps.forcePowersActive&(1<client->ps.forceRageRecoveryTime > level.time ) + {//never taunt while raging or recovering from rage + return; + } + if ( NPC->client->ps.stats[STAT_WEAPONS]&(1<client->ps.saber[0].type == SABER_SITH_SWORD ) + {//never taunt when holding sith sword + return; + } + //FIXME: make these distance numbers defines? + if ( enemy_dist >= 64 ) + {//FIXME: only do this if standing still? + //based on aggression, flaunt/taunt + int chance = 20; + if ( NPC->client->NPC_class == CLASS_SHADOWTROOPER ) + { + chance = 10; + } + //FIXME: possibly throw local objects at enemy? + if ( Q_irand( 2, chance ) < NPCInfo->stats.aggression ) + { + if ( TIMER_Done( NPC, "chatter" ) ) + {//FIXME: add more taunt behaviors + //FIXME: sometimes he turns it off, then turns it right back on again??? + if ( enemy_dist > 200 + && NPC->client->NPC_class != CLASS_BOBAFETT + && (NPC->client->NPC_class != CLASS_REBORN || NPC->s.weapon == WP_SABER) + && NPC->client->NPC_class != CLASS_ROCKETTROOPER + && NPC->client->ps.SaberActive() + && !Q_irand( 0, 5 ) ) + {//taunt even more, turn off the saber + //FIXME: don't do this if health low? + if ( NPC->client->ps.saberAnimLevel != SS_STAFF + && NPC->client->ps.saberAnimLevel != SS_DUAL ) + {//those taunts leave saber on + WP_DeactivateSaber( NPC ); + } + //Don't attack for a bit + NPCInfo->stats.aggression = 3; + //FIXME: maybe start strafing? + //debounce this + if ( NPC->client->playerTeam != TEAM_PLAYER && !Q_irand( 0, 1 )) + { + NPC->client->ps.taunting = level.time + 100; + TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) ); + TIMER_Set( NPC, "taunting", 5500 ); + } + else + { + Jedi_BattleTaunt(); + TIMER_Set( NPC, "taunting", Q_irand( 5000, 10000 ) ); + } + } + else if ( Jedi_BattleTaunt() ) + {//FIXME: pick some anims + } + } + } + } +} + +extern qboolean PM_SaberInParry( int move ); +static qboolean Jedi_AttackDecide( int enemy_dist ) +{ + if ( !TIMER_Done( NPC, "allyJediDelay" ) ) + { + return qfalse; + } + + if ( Jedi_CultistDestroyer( NPC ) ) + {//destroyer + if ( enemy_dist <= 32 ) + {//go boom! + //float? + //VectorClear( NPC->client->ps.velocity ); + //NPC->client->ps.gravity = 0; + //NPC->svFlags |= SVF_CUSTOM_GRAVITY; + //NPC->client->moveType = MT_FLYSWIM; + //NPC->flags |= FL_NO_KNOCKBACK; + NPC->flags |= FL_GODMODE; + NPC->takedamage = qfalse; + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_FORCE_RAGE, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + NPC->client->ps.forcePowersActive |= ( 1 << FP_RAGE ); + NPC->painDebounceTime = NPC->useDebounceTime = level.time + NPC->client->ps.torsoAnimTimer; + return qtrue; + } + return qfalse; + } + if ( NPC->enemy->client + && NPC->enemy->s.weapon == WP_SABER + && NPC->enemy->client->ps.saberLockTime > level.time + && NPC->client->ps.saberLockTime < level.time ) + {//enemy is in a saberLock and we are not + return qfalse; + } + + if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON ) + {//we won a saber lock, press the advantage with an attack! + int chance = 0; + if ( (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) ) + {//desann and luke + chance = 20; + } + else if ( NPC->client->NPC_class == CLASS_TAVION + || NPC->client->NPC_class == CLASS_ALORA ) + {//tavion + chance = 10; + } + else if ( NPC->client->NPC_class == CLASS_SHADOWTROOPER ) + {//shadowtrooper + chance = 5; + } + else if ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG ) + {//fencer + chance = 5; + } + else + { + chance = NPCInfo->rank; + } + if ( Q_irand( 0, 30 ) < chance ) + {//based on skill with some randomness + NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;//clear this now that we are using the opportunity + TIMER_Set( NPC, "noRetreat", Q_irand( 500, 2000 ) ); + //FIXME: check enemy_dist? + NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0; + //NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + NPC->client->ps.saberBlocked = BLOCKED_NONE; + WeaponThink( qtrue ); + return qtrue; + } + } + + if ( NPC->client->NPC_class == CLASS_TAVION || + NPC->client->NPC_class == CLASS_ALORA || + NPC->client->NPC_class == CLASS_SHADOWTROOPER || + ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG ) || + ( NPC->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) ) + {//tavion, fencers, jedi trainer are all good at following up a parry with an attack + if ( ( PM_SaberInParry( NPC->client->ps.saberMove ) || PM_SaberInKnockaway( NPC->client->ps.saberMove ) ) + && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//try to attack straight from a parry + NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0; + //NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + NPC->client->ps.saberBlocked = BLOCKED_NONE; + Jedi_AdjustSaberAnimLevel( NPC, SS_FAST );//try to follow-up with a quick attack + WeaponThink( qtrue ); + return qtrue; + } + } + + //try to hit them if we can + if ( !enemy_in_striking_range ) + { + return qfalse; + } + + if ( !TIMER_Done( NPC, "parryTime" ) ) + { + return qfalse; + } + + if ( (NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//not allowed to attack + return qfalse; + } + + if ( !(ucmd.buttons&BUTTON_ATTACK) + && !(ucmd.buttons&BUTTON_ALT_ATTACK) + && !(ucmd.buttons&BUTTON_FORCE_FOCUS) ) + {//not already attacking + //Try to attack + WeaponThink( qtrue ); + } + + //FIXME: Maybe try to push enemy off a ledge? + + //close enough to step forward + + //FIXME: an attack debounce timer other than the phaser debounce time? + // or base it on aggression? + + if ( ucmd.buttons&BUTTON_ATTACK && !NPC_Jumping()) + {//attacking + /* + if ( enemy_dist > 32 && NPCInfo->stats.aggression >= 4 ) + {//move forward if we're too far away and we're chasing him + ucmd.forwardmove = 127; + } + else if ( enemy_dist < 0 ) + {//move back if we're too close + ucmd.forwardmove = -127; + } + */ + //FIXME: based on the type of parry/attack the enemy is doing and my skill, + // choose an attack that is likely to get around the parry? + // right now that's generic in the saber animation code, auto-picks + // a next anim for me, but really should be AI-controlled. + //FIXME: have this interact with/override above strafing code? + if ( !ucmd.rightmove ) + {//not already strafing + if ( !Q_irand( 0, 3 ) ) + {//25% chance of doing this + vec3_t right, dir2enemy; + + AngleVectors( NPC->currentAngles, NULL, right, NULL ); + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentAngles, dir2enemy ); + if ( DotProduct( right, dir2enemy ) > 0 ) + {//he's to my right, strafe left + ucmd.rightmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + else + {//he's to my left, strafe right + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + } + } + return qtrue; + } + + return qfalse; +} + + +extern void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir ); +extern qboolean PM_KickingAnim( int anim ); +static void Jedi_CheckEnemyMovement( float enemy_dist ) +{ + if ( !NPC->enemy || !NPC->enemy->client ) + { + return; + } + + if ( !(NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) ) + { + if ( PM_KickingAnim( NPC->enemy->client->ps.legsAnim ) + && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE + //FIXME: I'm relatively close to him + && (NPC->enemy->client->ps.legsAnim == BOTH_A7_KICK_RL + || NPC->enemy->client->ps.legsAnim == BOTH_A7_KICK_BF + || NPC->enemy->client->ps.legsAnim == BOTH_A7_KICK_S + || (NPC->enemy->enemy && NPC->enemy->enemy == NPC)) + ) + {//run into the kick! + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + Jedi_Advance(); + } + else if ( NPC->enemy->client->ps.torsoAnim == BOTH_A7_HILT + && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//run into the hilt bash + //FIXME : only if in front! + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + Jedi_Advance(); + } + else if ( (NPC->enemy->client->ps.torsoAnim == BOTH_A6_FB + || NPC->enemy->client->ps.torsoAnim == BOTH_A6_LR) + && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//run into the attack + //FIXME : only if on R/L or F/B? + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + Jedi_Advance(); + } + else if ( NPC->enemy->enemy && NPC->enemy->enemy == NPC ) + {//enemy is mad at *me* + if ( NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSLASHDOWN1 + || NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSTABDOWN + || NPC->enemy->client->ps.legsAnim == BOTH_FLIP_ATTACK7 ) + {//enemy is flipping over me + if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT ) + {//be nice and stand still for him... + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + } + } + else if ( NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_BACK1 + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_RIGHT + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_LEFT + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_LEFT_FLIP + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT_FLIP ) + {//he's flipping off a wall + if ( NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//still in air + if ( enemy_dist < 256 ) + {//close + if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT ) + {//be nice and stand still for him... + /* + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "noturn", Q_irand( 200, 500 ) ); + */ + //stop current movement + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "noturn", Q_irand( 250, 500 )*(3-g_spskill->integer) ); + + vec3_t enemyFwd, dest, dir; + + VectorCopy( NPC->enemy->client->ps.velocity, enemyFwd ); + VectorNormalize( enemyFwd ); + VectorMA( NPC->enemy->currentOrigin, -64, enemyFwd, dest ); + VectorSubtract( dest, NPC->currentOrigin, dir ); + if ( VectorNormalize( dir ) > 32 ) + { + G_UcmdMoveForDir( NPC, &ucmd, dir ); + } + else + { + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + } + } + } + } + } + else if ( NPC->enemy->client->ps.legsAnim == BOTH_A2_STABBACK1 ) + {//he's stabbing backwards + if ( enemy_dist < 256 && enemy_dist > 64 ) + {//close + if ( !InFront( NPC->currentOrigin, NPC->enemy->currentOrigin, NPC->enemy->currentAngles, 0.0f ) ) + {//behind him + if ( !Q_irand( 0, NPCInfo->rank ) ) + {//be nice and stand still for him... + //stop current movement + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + + vec3_t enemyFwd, dest, dir; + + AngleVectors( NPC->enemy->currentAngles, enemyFwd, NULL, NULL ); + VectorMA( NPC->enemy->currentOrigin, -32, enemyFwd, dest ); + VectorSubtract( dest, NPC->currentOrigin, dir ); + if ( VectorNormalize( dir ) > 64 ) + { + G_UcmdMoveForDir( NPC, &ucmd, dir ); + } + else + { + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + } + } + } + } + } + } + } + //FIXME: also: + // If enemy doing wall flip, keep running forward + // If enemy doing back-attack and we're behind him keep running forward toward his back, don't strafe +} + +static void Jedi_CheckJumps( void ) +{ + if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) ) + { + NPC->client->ps.forceJumpCharge = 0; + ucmd.upmove = 0; + return; + } + //FIXME: should probably check this before AI decides that best move is to jump? Otherwise, they may end up just standing there and looking dumb + //FIXME: all this work and he still jumps off ledges... *sigh*... need CONTENTS_BOTCLIP do-not-enter brushes...? + vec3_t jumpVel = {0,0,0}; + + if ( NPC->client->ps.forceJumpCharge ) + { + //gi.Printf( "(%d) force jump\n", level.time ); + WP_GetVelocityForForceJump( NPC, jumpVel, &ucmd ); + } + else if ( ucmd.upmove > 0 ) + { + //gi.Printf( "(%d) regular jump\n", level.time ); + VectorCopy( NPC->client->ps.velocity, jumpVel ); + jumpVel[2] = JUMP_VELOCITY; + } + else + { + return; + } + + //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry... + if ( !jumpVel[0] && !jumpVel[1] )//FIXME: && !ucmd.forwardmove && !ucmd.rightmove? + {//we assume a jump straight up is safe + //gi.Printf( "(%d) jump straight up is safe\n", level.time ); + return; + } + //Now predict where this is going + //in steps, keep evaluating the trajectory until the new z pos is <= than current z pos, trace down from there + trace_t trace; + trajectory_t tr; + vec3_t lastPos, testPos, bottom; + int elapsedTime; + + VectorCopy( NPC->currentOrigin, tr.trBase ); + VectorCopy( jumpVel, tr.trDelta ); + tr.trType = TR_GRAVITY; + tr.trTime = level.time; + VectorCopy( NPC->currentOrigin, lastPos ); + + //This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down? + for ( elapsedTime = 500; elapsedTime <= 4000; elapsedTime += 500 ) + { + EvaluateTrajectory( &tr, level.time + elapsedTime, testPos ); + //FIXME: account for PM_AirMove if ucmd.forwardmove and/or ucmd.rightmove is non-zero... + if ( testPos[2] < lastPos[2] ) + {//going down, don't check for BOTCLIP + gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask );//FIXME: include CONTENTS_BOTCLIP? + } + else + {//going up, check for BOTCLIP + gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + } + if ( trace.allsolid || trace.startsolid ) + {//WTF? + //FIXME: what do we do when we start INSIDE the CONTENTS_BOTCLIP? Do the trace again without that clipmask? + goto jump_unsafe; + return; + } + if ( trace.fraction < 1.0f ) + {//hit something + if ( trace.contents & CONTENTS_BOTCLIP ) + {//hit a do-not-enter brush + goto jump_unsafe; + return; + } + //FIXME: trace through func_glass? + break; + } + VectorCopy( testPos, lastPos ); + } + //okay, reached end of jump, now trace down from here for a floor + VectorCopy( trace.endpos, bottom ); + if ( bottom[2] > NPC->currentOrigin[2] ) + {//only care about dist down from current height or lower + bottom[2] = NPC->currentOrigin[2]; + } + else if ( NPC->currentOrigin[2] - bottom[2] > 400 ) + {//whoa, long drop, don't do it! + //probably no floor at end of jump, so don't jump + goto jump_unsafe; + return; + } + bottom[2] -= 128; + gi.trace( &trace, trace.endpos, NPC->mins, NPC->maxs, bottom, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid || trace.startsolid || trace.fraction < 1.0f ) + {//hit ground! + if ( trace.entityNum < ENTITYNUM_WORLD ) + {//landed on an ent + gentity_t *groundEnt = &g_entities[trace.entityNum]; + if ( groundEnt->svFlags&SVF_GLASS_BRUSH ) + {//don't land on breakable glass! + goto jump_unsafe; + return; + } + } + //gi.Printf( "(%d) jump is safe\n", level.time ); + return; + } +jump_unsafe: + //probably no floor at end of jump, so don't jump + //gi.Printf( "(%d) unsafe jump cleared\n", level.time ); + NPC->client->ps.forceJumpCharge = 0; + ucmd.upmove = 0; +} + +extern void RT_JetPackEffect( int duration ); +void RT_CheckJump( void ) +{ + int jumpEntNum = ENTITYNUM_NONE; + vec3_t jumpPos = {0,0,0}; + + if ( !NPCInfo->goalEntity ) + { + if ( NPC->enemy ) + { + //FIXME: debounce this? + if ( TIMER_Done( NPC, "roamTime" ) + && Q_irand( 0, 9 ) ) + {//okay to try to find another spot to be + int cpFlags = (CP_CLEAR|CP_HAS_ROUTE);//must have a clear shot at enemy + float enemyDistSq = DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + //FIXME: base these ranges on weapon + if ( enemyDistSq > (2048*2048) ) + {//hmm, close in? + cpFlags |= CP_APPROACH_ENEMY; + } + else if ( enemyDistSq < (256*256) ) + {//back off! + cpFlags |= CP_RETREAT; + } + int sendFlags = cpFlags; + int cp = NPC_FindCombatPointRetry( NPC->currentOrigin, + NPC->currentOrigin, + NPC->currentOrigin, + &sendFlags, + 256, + NPCInfo->lastFailedCombatPoint ); + if ( cp == -1 ) + {//try again, no route needed since we can rocket-jump to it! + cpFlags &= ~CP_HAS_ROUTE; + cp = NPC_FindCombatPointRetry( NPC->currentOrigin, + NPC->currentOrigin, + NPC->currentOrigin, + &cpFlags, + 256, + NPCInfo->lastFailedCombatPoint ); + } + if ( cp != -1 ) + { + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp ); + } + else + {//FIXME: okay to do this if have good close-range weapon... + //FIXME: should we really try to go right for him?! + //NPCInfo->goalEntity = NPC->enemy; + jumpEntNum = NPC->enemy->s.number; + VectorCopy( NPC->enemy->currentOrigin, jumpPos ); + //return; + } + TIMER_Set( NPC, "roamTime", Q_irand( 3000, 12000 ) ); + } + else + {//FIXME: okay to do this if have good close-range weapon... + //FIXME: should we really try to go right for him?! + //NPCInfo->goalEntity = NPC->enemy; + jumpEntNum = NPC->enemy->s.number; + VectorCopy( NPC->enemy->currentOrigin, jumpPos ); + //return; + } + } + else + { + return; + } + } + else + { + jumpEntNum = NPCInfo->goalEntity->s.number; + VectorCopy( NPCInfo->goalEntity->currentOrigin, jumpPos ); + } + vec3_t vec2Goal; + VectorSubtract( jumpPos, NPC->currentOrigin, vec2Goal ); + if ( fabs( vec2Goal[2] ) < 32 ) + {//not a big height diff, see how far it is + vec2Goal[2] = 0; + if ( VectorLengthSquared( vec2Goal ) < (256*256) ) + {//too close! Don't rocket-jump to it... + return; + } + } + //If we can't get straight at him + if ( !Jedi_ClearPathToSpot( jumpPos, jumpEntNum ) ) + {//hunt him down + if ( (NPC_ClearLOS( NPC->enemy )||NPCInfo->enemyLastSeenTime>level.time-500) + && InFOV( jumpPos, NPC->currentOrigin, NPC->client->ps.viewangles, 20, 60 ) ) + { + if ( NPC_TryJump( jumpPos ) ) // Rocket Trooper + {//just do the jetpack effect for a litte bit + RT_JetPackEffect( Q_irand( 800, 1500) ); + return; + } + } + + if ( Jedi_Hunt() && !(NPCInfo->aiFlags&NPCAI_BLOCKED) )//FIXME: have to do this because they can ping-pong forever + {//can macro-navigate to him + return; + } + else + {//FIXME: try to find a waypoint that can see enemy, jump from there + if ( STEER::HasBeenBlockedFor(NPC, 2000) ) + {//try to jump to the blockedTargetPosition + if ( NPC_TryJump(NPCInfo->blockedTargetPosition) ) // Rocket Trooper + {//just do the jetpack effect for a litte bit + RT_JetPackEffect( Q_irand( 800, 1500) ); + } + } + } + } +} +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +static void Jedi_Combat( void ) +{ + vec3_t enemy_dir, enemy_movedir, enemy_dest; + float enemy_dist, enemy_movespeed; + qboolean enemy_lost = qfalse; + trace_t trace; + + //See where enemy will be 300 ms from now + Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 ); + + if ( NPC_Jumping( ) ) + {//I'm in the middle of a jump, so just see if I should attack + Jedi_AttackDecide( enemy_dist ); + return; + } + + if ( TIMER_Done( NPC, "allyJediDelay" ) ) + { + if ( !(NPC->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 ) + {//not gripping + //If we can't get straight at him + if ( !Jedi_ClearPathToSpot( enemy_dest, NPC->enemy->s.number ) ) + {//hunt him down + //gi.Printf( "No Clear Path\n" ); + if ( (NPC_ClearLOS( NPC->enemy )||NPCInfo->enemyLastSeenTime>level.time-500) && NPC_FaceEnemy( qtrue ) )//( NPCInfo->rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG ) && + { + //try to jump to him? + /* + vec3_t end; + VectorCopy( NPC->currentOrigin, end ); + end[2] += 36; + gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0 ) + { + vec3_t angles, forward; + VectorCopy( NPC->client->ps.viewangles, angles ); + angles[0] = 0; + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( end, 64, forward, end ); + gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + if ( !trace.allsolid && !trace.startsolid ) + { + if ( trace.fraction >= 1.0 || trace.plane.normal[2] > 0 ) + { + ucmd.upmove = 127; + ucmd.forwardmove = 127; + return; + } + } + } + */ + //FIXME: about every 1 second calc a velocity, + //run a loop of traces with evaluate trajectory + //for gravity with my size, see if it makes it... + //this will also catch misacalculations that send you off ledges! + //gi.Printf( "Considering Jump\n" ); + if (NPC->client && NPC->client->NPC_class==CLASS_BOBAFETT) + { + Boba_FireDecide(); + } + /* else if ( NPC_TryJump( NPC->enemy ) ) // Jedi, can see enemy, but can't get to him + {//FIXME: what about jumping to his enemyLastSeenLocation? + return; + }*/ + } + + //Check for evasion + if ( TIMER_Done( NPC, "parryTime" ) ) + {//finished parrying + if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE && + NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//wasn't blocked myself + NPC->client->ps.saberBlocked = BLOCKED_NONE; + } + } + if ( Jedi_Hunt() && !(NPCInfo->aiFlags&NPCAI_BLOCKED) )//FIXME: have to do this because they can ping-pong forever + {//can macro-navigate to him + if ( enemy_dist < 384 && !Q_irand( 0, 10 ) && NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && !NPC_ClearLOS( NPC->enemy ) ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_JLOST1, EV_JLOST3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + if (NPC->client && NPC->client->NPC_class==CLASS_BOBAFETT) + { + Boba_FireDecide(); + } + + return; + } + //well, try to head for his last seen location + /* + else if ( Jedi_Track() ) + { + return; + } + */ else + {//FIXME: try to find a waypoint that can see enemy, jump from there + /* if ( STEER::HasBeenBlockedFor(NPC, 3000) ) + {//try to jump to the blockedDest + if (NPCInfo->blockedTargetEntity) + { + NPC_TryJump(NPCInfo->blockedTargetEntity); // commented Out + } + else + { + NPC_TryJump(NPCInfo->blockedTargetPosition);// commented Out + } + }*/ + + enemy_lost = qtrue; + } + } + } + //else, we can see him or we can't track him at all + + //every few seconds, decide if we should we advance or retreat? + Jedi_CombatTimersUpdate( enemy_dist ); + + //We call this even if lost enemy to keep him moving and to update the taunting behavior + //maintain a distance from enemy appropriate for our aggression level + Jedi_CombatDistance( enemy_dist ); + } + + //if ( !enemy_lost ) + if (NPC->client->NPC_class != CLASS_BOBAFETT) + { + //Update our seen enemy position + if ( !NPC->enemy->client || ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) ) + { + VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + NPCInfo->enemyLastSeenTime = level.time; + } + + //Turn to face the enemy + if ( TIMER_Done( NPC, "noturn" ) && !NPC_Jumping() ) + { + Jedi_FaceEnemy( qtrue ); + } + NPC_UpdateAngles( qtrue, qtrue ); + + //Check for evasion + if ( TIMER_Done( NPC, "parryTime" ) ) + {//finished parrying + if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE && + NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//wasn't blocked myself + NPC->client->ps.saberBlocked = BLOCKED_NONE; + } + } + if ( NPC->enemy->s.weapon == WP_SABER ) + { + Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir ); + } + else + {//do we need to do any evasion for other kinds of enemies? + } + + //apply strafing/walking timers, etc. + Jedi_TimersApply(); + + if ( TIMER_Done( NPC, "allyJediDelay" ) ) + { + if ( ( !NPC->client->ps.saberInFlight || (NPC->client->ps.saberAnimLevel == SS_DUAL && NPC->client->ps.saber[1].Active()) ) + && (!(NPC->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2) ) + {//not throwing saber or using force grip + //see if we can attack + if ( !Jedi_AttackDecide( enemy_dist ) ) + {//we're not attacking, decide what else to do + Jedi_CombatIdle( enemy_dist ); + //FIXME: lower aggression when actually strike offensively? Or just when do damage? + } + else + {//we are attacking + //stop taunting + TIMER_Set( NPC, "taunting", -level.time ); + } + } + else + { + } + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + Boba_FireDecide(); + } + else if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER ) + { + RT_FireDecide(); + } + } + + //Check for certain enemy special moves + Jedi_CheckEnemyMovement( enemy_dist ); + //Make sure that we don't jump off ledges over long drops + Jedi_CheckJumps(); + //Just make sure we don't strafe into walls or off cliffs + + if ( VectorCompare( NPC->client->ps.moveDir, vec3_origin )//stomped the NAV system's moveDir + && !NPC_MoveDirClear( ucmd.forwardmove, ucmd.rightmove, qtrue ) )//check ucmd-driven movement + {//uh-oh, we are going to fall or hit something + /* + navInfo_t info; + //Get the move info + NAV_GetLastMove( info ); + if ( !(info.flags & NIF_MACRO_NAV) ) + {//micro-navigation told us to step off a ledge, try using macronav for now + NPC_MoveToGoal( qfalse ); + } + */ + //reset the timers. + TIMER_Set( NPC, "strafeLeft", 0 ); + TIMER_Set( NPC, "strafeRight", 0 ); + } +} + + +/* +========================================================================================== +EXTERNALLY CALLED BEHAVIOR STATES +========================================================================================== +*/ + +/* +------------------------- +NPC_Jedi_Pain +------------------------- +*/ + +void NPC_Jedi_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + //FIXME: base the actual aggression add/subtract on health? + //FIXME: don't do this more than once per frame? + //FIXME: when take pain, stop force gripping....? + if ( other->s.weapon == WP_SABER ) + {//back off + TIMER_Set( self, "parryTime", -1 ); + if ( self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) ) + {//less for Desann + self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill->integer)*50; + } + else if ( self->NPC->rank >= RANK_LT_JG ) + { + self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill->integer)*100;//300 + } + else + { + self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill->integer)*200;//500 + } + if ( !Q_irand( 0, 3 ) ) + {//ouch... maybe switch up which saber power level we're using + Jedi_AdjustSaberAnimLevel( self, Q_irand( SS_FAST, SS_STRONG ) ); + } + if ( !Q_irand( 0, 1 ) )//damage > 20 || self->health < 40 || + { + //Com_Printf( "(%d) drop agg - hit by saber\n", level.time ); + Jedi_Aggression( self, -1 ); + } + if ( d_JediAI->integer ) + { + gi.Printf( "(%d) PAIN: agg %d, no parry until %d\n", level.time, self->NPC->stats.aggression, level.time+500 ); + } + //for testing only + // Figure out what quadrant the hit was in. + if ( d_JediAI->integer ) + { + vec3_t diff, fwdangles, right; + + VectorSubtract( point, self->client->renderInfo.eyePoint, diff ); + diff[2] = 0; + fwdangles[1] = self->client->ps.viewangles[1]; + AngleVectors( fwdangles, NULL, right, NULL ); + float rightdot = DotProduct(right, diff); + float zdiff = point[2] - self->client->renderInfo.eyePoint[2]; + + gi.Printf( "(%d) saber hit at height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, point[2]-self->absmin[2],zdiff,rightdot); + } + } + else + {//attack + //Com_Printf( "(%d) raise agg - hit by ranged\n", level.time ); + Jedi_Aggression( self, 1 ); + } + + self->NPC->enemyCheckDebounceTime = 0; + + WP_ForcePowerStop( self, FP_GRIP ); + + NPC_Pain( self, inflictor, other, point, damage, mod ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } + + //drop me from the ceiling if I'm on it + if ( Jedi_WaitingAmbush( self ) ) + { + self->client->noclip = false; + } + if ( self->client->ps.legsAnim == BOTH_CEILING_CLING ) + { + NPC_SetAnim( self, SETANIM_LEGS, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( self->client->ps.torsoAnim == BOTH_CEILING_CLING ) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + + //check special defenses + if ( other + && other->client + && !OnSameTeam( self, other )) + {//hit by a client + //FIXME: delay this until *after* the pain anim? + if ( mod == MOD_FORCE_GRIP + || mod == MOD_FORCE_LIGHTNING + || mod == MOD_FORCE_DRAIN ) + {//see if we should turn on absorb + if ( (self->client->ps.forcePowersKnown&(1<client->ps.forcePowersActive&(1<s.number >= MAX_CLIENTS //enemy is an NPC + || Q_irand( 0, g_spskill->integer+1 ) )//enemy is player + { + if ( Q_irand( 0, self->NPC->rank ) > RANK_ENSIGN ) + { + if ( !Q_irand( 0, 5 ) ) + { + ForceAbsorb( self ); + } + } + } + } + } + else if ( damage > Q_irand( 5, 20 ) ) + {//respectable amount of normal damage + if ( (self->client->ps.forcePowersKnown&(1<client->ps.forcePowersActive&(1<s.number >= MAX_CLIENTS //enemy is an NPC + || Q_irand( 0, g_spskill->integer+1 ) )//enemy is player + { + if ( Q_irand( 0, self->NPC->rank ) > RANK_ENSIGN ) + { + if ( !Q_irand( 0, 1 ) ) + { + if ( other->s.number < MAX_CLIENTS + && ((self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) + || self->client->NPC_class==CLASS_SHADOWTROOPER) + && Q_irand(0, 6-g_spskill->integer) ) + { + } + else + { + ForceProtect( self ); + } + } + } + } + } + } + } +} + +qboolean Jedi_CheckDanger( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue ); + if ( level.alertEvents[alertEvent].level >= AEL_DANGER ) + {//run away! + if ( !level.alertEvents[alertEvent].owner + || !level.alertEvents[alertEvent].owner->client + || (level.alertEvents[alertEvent].owner!=NPC&&level.alertEvents[alertEvent].owner->client->playerTeam!=NPC->client->playerTeam) ) + {//no owner + return qfalse; + } + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + return qfalse; +} + +extern int g_crosshairEntNum; +qboolean Jedi_CheckAmbushPlayer( void ) +{ + if ( !player || !player->client ) + { + return qfalse; + } + + if ( !NPC_ValidEnemy( player ) ) + { + return qfalse; + } + + if ( NPC->client->ps.powerups[PW_CLOAKED] || g_crosshairEntNum != NPC->s.number ) + {//if I'm not cloaked and the player's crosshair is on me, I will wake up, otherwise do this stuff down here... + if ( !gi.inPVS( player->currentOrigin, NPC->currentOrigin ) ) + {//must be in same room + return qfalse; + } + else + { + if ( !NPC->client->ps.powerups[PW_CLOAKED] ) + { + NPC_SetLookTarget( NPC, 0, 0 ); + } + } + float target_dist, zDiff = NPC->currentOrigin[2]-player->currentOrigin[2]; + if ( zDiff <= 0 || zDiff > 512 ) + {//never ambush if they're above me or way way below me + return qfalse; + } + + //If the target is this close, then wake up regardless + if ( (target_dist = DistanceHorizontalSquared( player->currentOrigin, NPC->currentOrigin )) > 4096 ) + {//closer than 64 - always ambush + if ( target_dist > 147456 ) + {//> 384, not close enough to ambush + return qfalse; + } + //Check FOV first + if ( NPC->client->ps.powerups[PW_CLOAKED] ) + { + if ( InFOV( player, NPC, 30, 90 ) == qfalse ) + { + return qfalse; + } + } + else + { + if ( InFOV( player, NPC, 45, 90 ) == qfalse ) + { + return qfalse; + } + } + } + + if ( !NPC_ClearLOS( player ) ) + { + return qfalse; + } + } + + G_SetEnemy( NPC, player ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; +} + +void Jedi_Ambush( gentity_t *self ) +{ + self->client->noclip = false; + self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer; + if ( self->client->NPC_class != CLASS_BOBAFETT + && self->client->NPC_class != CLASS_ROCKETTROOPER ) + { + self->client->ps.SaberActivate(); + } + Jedi_Decloak( self ); + G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 1000 ); +} + +qboolean Jedi_WaitingAmbush( gentity_t *self ) +{ + if ( (self->spawnflags&JSF_AMBUSH) && self->client->noclip ) + { + return qtrue; + } + return qfalse; +} +/* +------------------------- +Jedi_Patrol +------------------------- +*/ + +static void Jedi_Patrol( void ) +{ + NPC->client->ps.saberBlocked = BLOCKED_NONE; + + if ( Jedi_WaitingAmbush( NPC ) ) + {//hiding on the ceiling + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_CEILING_CLING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + {//look for enemies + if ( Jedi_CheckAmbushPlayer() || Jedi_CheckDanger() ) + {//found him! + Jedi_Ambush( NPC ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + else if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )//NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + {//look for enemies + gentity_t *best_enemy = NULL; + float best_enemy_dist = Q3_INFINITE; + for ( int i = 0; i < ENTITYNUM_WORLD; i++ ) + { + gentity_t *enemy = &g_entities[i]; + float enemy_dist; + if ( enemy && enemy->client && NPC_ValidEnemy( enemy )) + { + if ( gi.inPVS( NPC->currentOrigin, enemy->currentOrigin ) ) + {//we could potentially see him + enemy_dist = DistanceSquared( NPC->currentOrigin, enemy->currentOrigin ); + if ( enemy->s.number == 0 || enemy_dist < best_enemy_dist ) + { + //if the enemy is close enough, or threw his saber, take him as the enemy + //FIXME: what if he throws a thermal detonator? + //FIXME: use jediSpeechDebounceTime[NPC->client->playerTeam] < level.time ) check for anger sound + if ( enemy_dist < (220*220) || ( NPCInfo->investigateCount>= 3 && NPC->client->ps.SaberActive() ) ) + { + G_SetEnemy( NPC, enemy ); + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + NPCInfo->stats.aggression = 3; + break; + } + else if ( enemy->client->ps.saberInFlight && enemy->client->ps.SaberActive() ) + {//threw his saber, see if it's heading toward me and close enough to consider a threat + float saberDist; + vec3_t saberDir2Me; + vec3_t saberMoveDir; + gentity_t *saber = &g_entities[enemy->client->ps.saberEntityNum]; + VectorSubtract( NPC->currentOrigin, saber->currentOrigin, saberDir2Me ); + saberDist = VectorNormalize( saberDir2Me ); + VectorCopy( saber->s.pos.trDelta, saberMoveDir ); + VectorNormalize( saberMoveDir ); + if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 ) + {//it's heading towards me + if ( saberDist < 200 ) + {//incoming! + G_SetEnemy( NPC, enemy ); + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + NPCInfo->stats.aggression = 3; + break; + } + } + } + best_enemy_dist = enemy_dist; + best_enemy = enemy; + } + } + } + } + if ( !NPC->enemy ) + {//still not mad + if ( !best_enemy ) + { + //Com_Printf( "(%d) drop agg - no enemy (patrol)\n", level.time ); + Jedi_AggressionErosion(-1); + //FIXME: what about alerts? But not if ignore alerts + } + else + {//have one to consider + if ( NPC_ClearLOS( best_enemy ) ) + {//we have a clear (of architecture) LOS to him + if ( (NPCInfo->aiFlags&NPCAI_NO_JEDI_DELAY) ) + {//just get mad right away + if ( DistanceHorizontalSquared( NPC->currentOrigin, best_enemy->currentOrigin ) < (1024*1024) ) + { + G_SetEnemy( NPC, best_enemy ); + NPCInfo->stats.aggression = 20; + } + } + else if ( best_enemy->s.number ) + {//just attack + G_SetEnemy( NPC, best_enemy ); + NPCInfo->stats.aggression = 3; + } + else if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + {//the player, toy with him + //get progressively more interested over time + if ( TIMER_Done( NPC, "watchTime" ) ) + {//we want to pick him up in stages + if ( TIMER_Get( NPC, "watchTime" ) == -1 ) + {//this is the first time, we'll ignore him for a couple seconds + TIMER_Set( NPC, "watchTime", Q_irand( 3000, 5000 ) ); + goto finish; + } + else + {//okay, we've ignored him, now start to notice him + if ( !NPCInfo->investigateCount ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_JDETECTED1, EV_JDETECTED3 ), 3000 ); + } + NPCInfo->investigateCount++; + TIMER_Set( NPC, "watchTime", Q_irand( 4000, 10000 ) ); + } + } + //while we're waiting, do what we need to do + if ( best_enemy_dist < (440*440) || NPCInfo->investigateCount >= 2 ) + {//stage three: keep facing him + NPC_FaceEntity( best_enemy, qtrue ); + if ( best_enemy_dist < (330*330) ) + {//stage four: turn on the saber + if ( !NPC->client->ps.saberInFlight ) + { + NPC->client->ps.SaberActivate(); + } + } + } + else if ( best_enemy_dist < (550*550) || NPCInfo->investigateCount == 1 ) + {//stage two: stop and face him every now and then + if ( TIMER_Done( NPC, "watchTime" ) ) + { + NPC_FaceEntity( best_enemy, qtrue ); + } + } + else + {//stage one: look at him. + NPC_SetLookTarget( NPC, best_enemy->s.number, 0 ); + } + } + } + else if ( TIMER_Done( NPC, "watchTime" ) ) + {//haven't seen him in a bit, clear the lookTarget + NPC_ClearLookTarget( NPC ); + } + } + } + } +finish: + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + //Jedi_Move( NPCInfo->goalEntity ); + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); + + if ( NPC->enemy ) + {//just picked one up + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + } +} + +qboolean Jedi_CanPullBackSaber( gentity_t *self ) +{ + if ( self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN && !TIMER_Done( self, "parryTime" ) ) + { + return qfalse; + } + + if ( self->client->NPC_class == CLASS_SHADOWTROOPER + || self->client->NPC_class == CLASS_ALORA + || ( self->NPC && (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) ) + { + return qtrue; + } + + if ( self->painDebounceTime > level.time )//|| self->client->ps.weaponTime > 0 ) + { + return qfalse; + } + + return qtrue; +} +/* +------------------------- +NPC_BSJedi_FollowLeader +------------------------- +*/ +extern qboolean NAV_CheckAhead( gentity_t *self, vec3_t end, trace_t &trace, int clipmask ); + +void NPC_BSJedi_FollowLeader( void ) +{ + NPC->client->ps.saberBlocked = BLOCKED_NONE; + if ( !NPC->enemy ) + { + //Com_Printf( "(%d) drop agg - no enemy (follow)\n", level.time ); + Jedi_AggressionErosion(-1); + } + + //did we drop our saber? If so, go after it! + if ( NPC->client->ps.saberInFlight ) + {//saber is not in hand + if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0 + {// + if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY ) + {//fell to the ground, try to pick it up... + if ( Jedi_CanPullBackSaber( NPC ) ) + { + //FIXME: if it's on the ground and we just pulled it back to us, should we + // stand still for a bit to make sure it gets to us...? + // otherwise we could end up running away from it while it's on its + // way back to us and we could lose it again. + NPC->client->ps.saberBlocked = BLOCKED_NONE; + NPCInfo->goalEntity = &g_entities[NPC->client->ps.saberEntityNum]; + ucmd.buttons |= BUTTON_ATTACK; + if ( NPC->enemy && NPC->enemy->health > 0 ) + {//get our saber back NOW! + if ( !NPC_MoveToGoal( qtrue ) )//Jedi_Move( NPCInfo->goalEntity, qfalse ); + {//can't nav to it, try jumping to it + NPC_FaceEntity( NPCInfo->goalEntity, qtrue ); + NPC_TryJump( NPCInfo->goalEntity ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + } + } + + //try normal movement + NPC_BSFollowLeader(); + + + if (!NPC->enemy && + NPC->health < NPC->max_health && + (NPC->client->ps.forcePowersKnown&(1<client->ps.forcePowersActive&(1<rank >= RANK_LT_COMM ) + {//only top-level guys and bosses do this + if ( (ucmd.buttons&BUTTON_ATTACK) ) + {//attacking + if ( (g_saberNewControlScheme->integer + && !(ucmd.buttons&BUTTON_FORCE_FOCUS) ) + ||(!g_saberNewControlScheme->integer + && !(ucmd.buttons&BUTTON_ALT_ATTACK) ) ) + {//not already going to do a kata move somehow + if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//on the ground + if ( ucmd.upmove <= 0 && NPC->client->ps.forceJumpCharge <= 0 ) + {//not going to try to jump + /* + if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) ) + {//uh-oh, no jumping moves! + if ( NPC->client->ps.saberAnimLevel == SS_STAFF ) + {//this kata move has a jump in it... + return qfalse; + } + } + */ + + if ( Q_irand( 0, g_spskill->integer+1 ) //50% chance on easy, 66% on medium, 75% on hard + && !Q_irand( 0, 9 ) )//10% chance overall + {//base on skill level + ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + if ( g_saberNewControlScheme->integer ) + { + ucmd.buttons |= BUTTON_FORCE_FOCUS; + } + else + { + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + return qtrue; + } + } + } + } + } + } + return qfalse; +} + + +/* +------------------------- +Jedi_Attack +------------------------- +*/ + +static void Jedi_Attack( void ) +{ + //Don't do anything if we're in a pain anim + if ( NPC->painDebounceTime > level.time ) + { + if ( Q_irand( 0, 1 ) ) + { + Jedi_FaceEnemy( qtrue ); + } + NPC_UpdateAngles( qtrue, qtrue ); + if ( NPC->client->ps.torsoAnim == BOTH_KYLE_GRAB ) + {//see if we grabbed enemy + if ( NPC->client->ps.torsoAnimTimer <= 200 ) + { + if ( Kyle_CanDoGrab() + && NPC_EnemyRangeFromBolt( NPC->handRBolt ) < 88.0f ) + {//grab him! + Kyle_GrabEnemy(); + return; + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer; + return; + } + } + //else just sit here? + } + return; + } + + if ( NPC->client->ps.saberLockTime > level.time ) + { + //FIXME: maybe kick out of saberlock? + //maybe if I'm losing I should try to force-push out of it? Very rarely, though... + if ( NPC->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 + && NPC->client->ps.saberLockTime < level.time + 5000 + && !Q_irand( 0, 10 )) + { + ForceThrow( NPC, qfalse ); + } + //based on my skill, hit attack button every other to every several frames in order to push enemy back + else + { + float chance; + + if ( NPC->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",NPC->NPC_type) ) + { + if ( g_spskill->integer ) + { + chance = 4.0f;//he pushes *hard* + } + else + { + chance = 3.0f;//he pushes *hard* + } + } + else if ( NPC->client->NPC_class == CLASS_TAVION + || NPC->client->NPC_class == CLASS_SHADOWTROOPER + || NPC->client->NPC_class == CLASS_ALORA + || (NPC->client->NPC_class == CLASS_KYLE&&(NPC->spawnflags&1)) ) + { + chance = 2.0f+g_spskill->value;//from 2 to 4 + } + else + {//the escalation in difficulty is nice, here, but cap it so it doesn't get *impossible* on hard + float maxChance = (float)(RANK_LT)/2.0f+3.0f;//5? + if ( !g_spskill->value ) + { + chance = (float)(NPCInfo->rank)/2.0f; + } + else + { + chance = (float)(NPCInfo->rank)/2.0f+1.0f; + } + if ( chance > maxChance ) + { + chance = maxChance; + } + } + if ( (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) ) + { + chance += Q_irand(0,2); + } + else if ( (NPCInfo->aiFlags&NPCAI_SUBBOSS_CHARACTER) ) + { + chance += Q_irand(-1,1); + } + if ( Q_flrand( -4.0f, chance ) >= 0.0f && !(NPC->client->ps.pm_flags&PMF_ATTACK_HELD) ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + //did we drop our saber? If so, go after it! + if ( NPC->client->ps.saberInFlight ) + {//saber is not in hand + if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0 + {// + if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY ) + {//fell to the ground, try to pick it up + if ( Jedi_CanPullBackSaber( NPC ) ) + { + NPC->client->ps.saberBlocked = BLOCKED_NONE; + NPCInfo->goalEntity = &g_entities[NPC->client->ps.saberEntityNum]; + ucmd.buttons |= BUTTON_ATTACK; + if ( NPC->enemy && NPC->enemy->health > 0 ) + {//get our saber back NOW! + Jedi_Move( NPCInfo->goalEntity, qfalse ); + NPC_UpdateAngles( qtrue, qtrue ); + if ( NPC->enemy->s.weapon == WP_SABER ) + {//be sure to continue evasion + vec3_t enemy_dir, enemy_movedir, enemy_dest; + float enemy_dist, enemy_movespeed; + Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 ); + Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir ); + } + return; + } + } + } + } + } + //see if our enemy was killed by us, gloat and turn off saber after cool down. + //FIXME: don't do this if we have other enemies to fight...? + if ( NPC->enemy ) + { + if ( NPC->enemy->health <= 0 + && NPC->enemy->enemy == NPC + && (NPC->client->playerTeam != TEAM_PLAYER||(NPC->client->NPC_class==CLASS_KYLE&&(NPC->spawnflags&1)&&NPC->enemy==player)) )//good guys don't gloat (unless it's Kyle having just killed his student + {//my enemy is dead and I killed him + NPCInfo->enemyCheckDebounceTime = 0;//keep looking for others + + if ( NPC->client->NPC_class == CLASS_BOBAFETT + || (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER) + || NPC->client->NPC_class == CLASS_ROCKETTROOPER ) + { + if ( NPCInfo->walkDebounceTime < level.time && NPCInfo->walkDebounceTime >= 0 ) + { + TIMER_Set( NPC, "gloatTime", 10000 ); + NPCInfo->walkDebounceTime = -1; + } + if ( !TIMER_Done( NPC, "gloatTime" ) ) + { + if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared + { + NPCInfo->goalEntity = NPC->enemy; + Jedi_Move( NPC->enemy, qfalse ); + ucmd.buttons |= BUTTON_WALKING; + } + else + { + TIMER_Set( NPC, "gloatTime", 0 ); + } + } + else if ( NPCInfo->walkDebounceTime == -1 ) + { + NPCInfo->walkDebounceTime = -2; + G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = level.time + 3000; + NPCInfo->desiredPitch = 0; + NPCInfo->goalEntity = NULL; + } + Jedi_FaceEnemy( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + { + if ( !TIMER_Done( NPC, "parryTime" ) ) + { + TIMER_Set( NPC, "parryTime", -1 ); + NPC->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + } + NPC->client->ps.saberBlocked = BLOCKED_NONE; + if ( NPC->client->ps.SaberActive() || NPC->client->ps.saberInFlight ) + {//saber is still on (or we're trying to pull it back), count down erosion and keep facing the enemy + //FIXME: need to stop this from happening over and over again when they're blocking their victim's saber + //FIXME: turn off saber sooner so we get cool walk anim? + //Com_Printf( "(%d) drop agg - enemy dead\n", level.time ); + Jedi_AggressionErosion(-3); + if ( !NPC->client->ps.SaberActive() && !NPC->client->ps.saberInFlight ) + {//turned off saber (in hand), gloat + G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = level.time + 3000; + NPCInfo->desiredPitch = 0; + NPCInfo->goalEntity = NULL; + } + TIMER_Set( NPC, "gloatTime", 10000 ); + } + if ( NPC->client->ps.SaberActive() || NPC->client->ps.saberInFlight || !TIMER_Done( NPC, "gloatTime" ) ) + {//keep walking + if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared + { + NPCInfo->goalEntity = NPC->enemy; + Jedi_Move( NPC->enemy, qfalse ); + ucmd.buttons |= BUTTON_WALKING; + } + else + {//got there + if ( NPC->health < NPC->max_health ) + { + if ( NPC->client->ps.saber[0].type == SABER_SITH_SWORD + && NPC->weaponModel[0] != -1 ) + { + Tavion_SithSwordRecharge(); + } + else if ( (NPC->client->ps.forcePowersKnown&(1<client->ps.forcePowersActive&(1<enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) + { + if ( NPC->enemy->count <= 0 ) + {//it's out of ammo + if ( NPC->enemy->activator && NPC_ValidEnemy( NPC->enemy->activator ) ) + { + gentity_t *turretOwner = NPC->enemy->activator; + G_ClearEnemy( NPC ); + G_SetEnemy( NPC, turretOwner ); + } + else + { + G_ClearEnemy( NPC ); + } + } + } + if ( NPC->enemy->NPC + && NPC->enemy->NPC->charmedTime > level.time ) + {//my enemy was charmed + if ( OnSameTeam( NPC, NPC->enemy ) ) + {//has been charmed to be on my team + G_ClearEnemy( NPC ); + } + } + if ( NPC->client->playerTeam == TEAM_ENEMY + && NPC->client->enemyTeam == TEAM_PLAYER + && NPC->enemy + && NPC->enemy->client + && NPC->enemy->client->playerTeam != NPC->client->enemyTeam + && OnSameTeam( NPC, NPC->enemy ) + && !(NPC->svFlags&SVF_LOCKEDENEMY) ) + {//an evil jedi somehow got another evil NPC as an enemy, they were probably charmed and it's run out now + if ( !NPC_ValidEnemy( NPC->enemy ) ) + { + G_ClearEnemy( NPC ); + } + } + NPC_CheckEnemy( qtrue, qtrue ); + + if ( !NPC->enemy ) + { + NPC->client->ps.saberBlocked = BLOCKED_NONE; + if ( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + {//lost him, go back to what we were doing before + NPCInfo->tempBehavior = BS_DEFAULT; + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + Jedi_Patrol();//was calling Idle... why? + return; + } + + //always face enemy if have one + NPCInfo->combatMove = qtrue; + + //Track the player and kill them if possible + Jedi_Combat(); + + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) + || ((NPC->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_HEAL] 0 ) + { + ucmd.upmove = 0; + } + NPC->client->ps.forceJumpCharge = 0; + VectorClear( NPC->client->ps.moveDir ); + } + + //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry... + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//don't push while in air, throws off jumps! + //FIXME: if we are in the air over a drop near a ledge, should we try to push back towards the ledge? + ucmd.forwardmove = 0; + ucmd.rightmove = 0; + VectorClear( NPC->client->ps.moveDir ); + } + + if ( !TIMER_Done( NPC, "duck" ) ) + { + ucmd.upmove = -127; + } + + if ( NPC->client->NPC_class != CLASS_BOBAFETT + && (NPC->client->NPC_class != CLASS_REBORN || NPC->s.weapon == WP_SABER) + && NPC->client->NPC_class != CLASS_ROCKETTROOPER ) + { + if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove ) || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN ) + {//just make sure they don't pull their saber to them if they're being blocked + ucmd.buttons &= ~BUTTON_ATTACK; + } + } + + if( (NPCInfo->scriptFlags&SCF_DONT_FIRE) //not allowed to attack + || ((NPC->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_HEAL]client->ps.saberEventFlags&SEF_INWATER)&&!NPC->client->ps.saberInFlight) )//saber in water + { + ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS); + } + + if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) ) + { + ucmd.upmove = 0; + NPC->client->ps.forceJumpCharge = 0; + } + + if ( NPC->client->NPC_class != CLASS_BOBAFETT + && (NPC->client->NPC_class != CLASS_REBORN || NPC->s.weapon == WP_SABER) + && NPC->client->NPC_class != CLASS_ROCKETTROOPER ) + { + Jedi_CheckDecreaseSaberAnimLevel(); + } + + if ( ucmd.buttons & BUTTON_ATTACK && NPC->client->playerTeam == TEAM_ENEMY ) + { + if ( Q_irand( 0, NPC->client->ps.saberAnimLevel ) > 0 + && Q_irand( 0, NPC->max_health+10 ) > NPC->health + && !Q_irand( 0, 3 )) + {//the more we're hurt and the stronger the attack we're using, the more likely we are to make a anger noise when we swing + G_AddVoiceEvent( NPC, Q_irand( EV_COMBAT1, EV_COMBAT3 ), 1000 ); + } + } + + //Check for trying a kata move + //FIXME: what about force-pull attacks? + if ( Jedi_CheckKataAttack() ) + {//doing a kata attack + } + else + {//check other special combat behavior + if ( NPC->client->NPC_class != CLASS_BOBAFETT + && (NPC->client->NPC_class != CLASS_REBORN || NPC->s.weapon == WP_SABER) + && NPC->client->NPC_class != CLASS_ROCKETTROOPER ) + { + if ( NPC->client->NPC_class == CLASS_TAVION + || NPC->client->NPC_class == CLASS_SHADOWTROOPER + || NPC->client->NPC_class == CLASS_ALORA + || (g_spskill->integer && ( NPC->client->NPC_class == CLASS_DESANN || NPCInfo->rank >= Q_irand( RANK_CREWMAN, RANK_CAPTAIN )))) + {//Tavion will kick in force speed if the player does... + if ( NPC->enemy + && !NPC->enemy->s.number + && NPC->enemy->client + && (NPC->enemy->client->ps.forcePowersActive & (1<client->ps.forcePowersActive & (1<integer ) + { + case 0: + chance = 9; + case 1: + chance = 3; + case 2: + chance = 1; + break; + } + if ( !Q_irand( 0, chance ) ) + { + ForceSpeed( NPC ); + } + } + } + } + //Sometimes Alora flips towards you instead of runs + if ( NPC->client->NPC_class == CLASS_ALORA ) + { + if ( (ucmd.buttons&BUTTON_ALT_ATTACK) ) + {//chance of doing a special dual saber throw + if ( NPC->client->ps.saberAnimLevel == SS_DUAL + && !NPC->client->ps.saberInFlight ) + { + if ( Distance( NPC->enemy->currentOrigin, NPC->currentOrigin ) >= 120 ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ALORA_SPIN_THROW, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer; + //FIXME: don't move + //FIXME: sabers need trails and sounds + } + } + } + else if ( NPC->enemy + && ucmd.forwardmove > 0 + && fabs((float)ucmd.rightmove) < 32 + && !(ucmd.buttons&BUTTON_WALKING) + && !(ucmd.buttons&BUTTON_ATTACK) + && NPC->client->ps.saberMove == LS_READY + && NPC->client->ps.legsAnim == BOTH_RUN_DUAL ) + {//running at us, not attacking + if ( Distance( NPC->enemy->currentOrigin, NPC->currentOrigin ) > 80 ) + { + if ( NPC->client->ps.legsAnim == BOTH_FLIP_F + || NPC->client->ps.legsAnim == BOTH_ALORA_FLIP_1 + || NPC->client->ps.legsAnim == BOTH_ALORA_FLIP_2 + || NPC->client->ps.legsAnim == BOTH_ALORA_FLIP_3 ) + { + if ( NPC->client->ps.legsAnimTimer <= 200 && Q_irand( 0, 2 ) ) + {//go ahead and start anotther + NPC_SetAnim( NPC, SETANIM_BOTH, Q_irand(BOTH_ALORA_FLIP_1,BOTH_ALORA_FLIP_3), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + } + } + else if ( !Q_irand( 0, 6 ) ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, Q_irand(BOTH_ALORA_FLIP_1,BOTH_ALORA_FLIP_3), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + } + } + } + } + } + + if ( VectorCompare( NPC->client->ps.moveDir, vec3_origin ) + && (ucmd.forwardmove||ucmd.rightmove) ) + {//using ucmds to move this turn, not NAV + if ( (ucmd.buttons&BUTTON_WALKING) ) + {//FIXME: NAV system screws with speed directly, so now I have to re-set it myself! + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + } + else + { + NPC->client->ps.speed = NPCInfo->stats.runSpeed; + } + } +} + +qboolean Rosh_BeingHealed( gentity_t *self ) +{ + if ( self + && self->NPC + && self->client + && (self->NPC->aiFlags&NPCAI_ROSH) + && (self->flags&FL_UNDYING) + && ( self->health == 1 //need healing + || self->client->ps.powerups[PW_INVINCIBLE] > level.time ) )//being healed + { + return qtrue; + } + return qfalse; +} + +qboolean Rosh_TwinPresent( gentity_t *self ) +{ + gentity_t *foundTwin = G_Find( NULL, FOFS(NPC_type), "DKothos" ); + if ( !foundTwin + || foundTwin->health < 0 ) + { + foundTwin = G_Find( NULL, FOFS(NPC_type), "VKothos" ); + } + if ( !foundTwin + || foundTwin->health < 0 ) + {//oh well, both twins are dead... + return qfalse; + } + return qtrue; +} + +qboolean Rosh_TwinNearBy( gentity_t *self ) +{ + gentity_t *foundTwin = G_Find( NULL, FOFS(NPC_type), "DKothos" ); + if ( !foundTwin + || foundTwin->health < 0 ) + { + foundTwin = G_Find( NULL, FOFS(NPC_type), "VKothos" ); + } + if ( !foundTwin + || foundTwin->health < 0 ) + {//oh well, both twins are dead... + return qfalse; + } + if ( self->client + && foundTwin->client ) + { + if ( Distance( self->currentOrigin, foundTwin->currentOrigin ) <= 512.0f + && G_ClearLineOfSight( self->client->renderInfo.eyePoint, foundTwin->client->renderInfo.eyePoint, foundTwin->s.number, MASK_OPAQUE ) ) + { + //make them look charge me for a bit while I do this + TIMER_Set( self, "chargeMeUp", Q_irand( 2000, 4000 ) ); + return qtrue; + } + } + return qfalse; +} + +qboolean Kothos_HealRosh( void ) +{ + if ( NPC->client + && NPC->client->leader + && NPC->client->leader->client ) + { + if ( DistanceSquared( NPC->client->leader->currentOrigin, NPC->currentOrigin ) <= (256*256) + && G_ClearLineOfSight( NPC->client->leader->client->renderInfo.eyePoint, NPC->client->renderInfo.eyePoint, NPC->s.number, MASK_OPAQUE ) ) + { + //NPC_FaceEntity( NPC->client->leader, qtrue ); + NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_FORCE_2HANDEDLIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoAnimTimer = 1000; + + //FIXME: unique effect and sound + //NPC->client->ps.eFlags |= EF_POWERING_ROSH; + if ( NPC->ghoul2.size() ) + { + mdxaBone_t boltMatrix; + vec3_t fxOrg, fxDir, angles={0,NPC->currentAngles[YAW],0}; + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, + (Q_irand(0,1)?NPC->handLBolt:NPC->handRBolt), + &boltMatrix, angles, NPC->currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, fxOrg ); + VectorSubtract( NPC->client->leader->currentOrigin, fxOrg, fxDir ); + VectorNormalize( fxDir ); + G_PlayEffect( G_EffectIndex( "force/kothos_beam.efx" ), fxOrg, fxDir ); + } + //BEG HACK LINE + gentity_t *tent = G_TempEntity( NPC->currentOrigin, EV_KOTHOS_BEAM ); + tent->svFlags |= SVF_BROADCAST; + tent->s.otherEntityNum = NPC->s.number; + tent->s.otherEntityNum2 = NPC->client->leader->s.number; + //END HACK LINE + + NPC->client->leader->health += Q_irand( 1+g_spskill->integer*2, 4+g_spskill->integer*3 );//from 1-5 to 4-10 + if ( NPC->client->leader->client ) + { + if ( NPC->client->leader->client->ps.legsAnim == BOTH_FORCEHEAL_START + && NPC->client->leader->health >= NPC->client->leader->max_health ) + {//let him get up now + NPC_SetAnim( NPC->client->leader, SETANIM_BOTH, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + //FIXME: temp effect + G_PlayEffect( G_EffectIndex( "force/kothos_recharge.efx" ), NPC->client->leader->playerModel, 0, NPC->client->leader->s.number, NPC->client->leader->currentOrigin, NPC->client->leader->client->ps.torsoAnimTimer, qfalse ); + //make him invincible while we recharge him + NPC->client->leader->client->ps.powerups[PW_INVINCIBLE] = level.time + NPC->client->leader->client->ps.torsoAnimTimer; + NPC->client->leader->NPC->ignorePain = qfalse; + NPC->client->leader->health = NPC->client->leader->max_health; + } + else + { + G_PlayEffect( G_EffectIndex( "force/kothos_recharge.efx" ), NPC->client->leader->playerModel, 0, NPC->client->leader->s.number, NPC->client->leader->currentOrigin, 500, qfalse ); + NPC->client->leader->client->ps.powerups[PW_INVINCIBLE] = level.time + 500; + } + } + //decrement + NPC->count--; + if ( !NPC->count ) + { + TIMER_Set( NPC, "healRoshDebounce", Q_irand( 5000, 10000 ) ); + NPC->count = 100; + } + //now protect me, too + if ( g_spskill->integer ) + {//not on easy + G_PlayEffect( G_EffectIndex( "force/kothos_recharge.efx" ), NPC->playerModel, 0, NPC->s.number, NPC->currentOrigin, 500, qfalse ); + NPC->client->ps.powerups[PW_INVINCIBLE] = level.time + 500; + } + return qtrue; + } + } + return qfalse; +} + +void Kothos_PowerRosh( void ) +{ + if ( NPC->client + && NPC->client->leader ) + { + if ( Distance( NPC->client->leader->currentOrigin, NPC->currentOrigin ) <= 512.0f + && G_ClearLineOfSight( NPC->client->leader->client->renderInfo.eyePoint, NPC->client->renderInfo.eyePoint, NPC->s.number, MASK_OPAQUE ) ) + { + NPC_FaceEntity( NPC->client->leader, qtrue ); + NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoAnimTimer = 500; + //FIXME: unique effect and sound + //NPC->client->ps.eFlags |= EF_POWERING_ROSH; + G_PlayEffect( G_EffectIndex( "force/kothos_beam.efx" ), NPC->playerModel, NPC->handLBolt, NPC->s.number, NPC->currentOrigin, 500, qfalse ); + if ( NPC->client->leader->client ) + {//hmm, give him some force? + NPC->client->leader->client->ps.forcePower++; + } + } + } +} + +qboolean Kothos_Retreat( void ) +{ + STEER::Activate( NPC ); + STEER::Evade( NPC, NPC->enemy ); + STEER::AvoidCollisions( NPC, NPC->client->leader ); + STEER::DeActivate( NPC, &ucmd ); + if ( (NPCInfo->aiFlags&NPCAI_BLOCKED) ) + { + if ( level.time - NPCInfo->blockedDebounceTime > 1000 ) + { + return qfalse; + } + } + return qtrue; +} + +#define TWINS_DANGER_DIST_EASY (128.0f*128.0f) +#define TWINS_DANGER_DIST_MEDIUM (192.0f*192.0f) +#define TWINS_DANGER_DIST_HARD (256.0f*256.0f) +float Twins_DangerDist( void ) +{ + switch ( g_spskill->integer ) + { + case 0: + return TWINS_DANGER_DIST_EASY; + break; + case 1: + return TWINS_DANGER_DIST_MEDIUM; + break; + case 2: + default: + return TWINS_DANGER_DIST_HARD; + break; + } +} + +qboolean Jedi_InSpecialMove( void ) +{ + if ( NPC->client->ps.torsoAnim == BOTH_KYLE_PA_1 + || NPC->client->ps.torsoAnim == BOTH_KYLE_PA_2 + || NPC->client->ps.torsoAnim == BOTH_KYLE_PA_3 + || NPC->client->ps.torsoAnim == BOTH_PLAYER_PA_1 + || NPC->client->ps.torsoAnim == BOTH_PLAYER_PA_2 + || NPC->client->ps.torsoAnim == BOTH_PLAYER_PA_3 + || NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_END + || NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRABBED ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + + if ( Jedi_InNoAIAnim( NPC ) ) + {//in special anims, don't do force powers or attacks, just face the enemy + if ( NPC->enemy ) + { + NPC_FaceEnemy( qtrue ); + } + else + { + NPC_UpdateAngles( qtrue, qtrue ); + } + return qtrue; + } + + /* + if ( NPC->client->ps.forceGripEntityNum < ENTITYNUM_WORLD + && (NPC->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 ) + {//stop facing the enemy, just use your current angles + NPC_UpdateAngles( qtrue, qtrue ); + } + */ + + if ( NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START + || NPC->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_HOLD ) + { + if ( !TIMER_Done( NPC, "draining" ) ) + {//FIXME: what do we do if we ran out of power? NPC's can't? + //FIXME: don't keep turning to face enemy or we'll end up spinning around + ucmd.buttons |= BUTTON_FORCE_DRAIN; + } + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + + if ( NPC->client->ps.torsoAnim == BOTH_TAVION_SWORDPOWER ) + { + NPC->health += Q_irand( 1, 2 ); + if ( NPC->health > NPC->max_health ) + { + NPC->health = NPC->max_health; + } + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + + if ( NPC->client->ps.torsoAnim == BOTH_SCEPTER_START ) + { + if ( NPC->client->ps.torsoAnimTimer <= 100 ) + {//go into the hold + NPC->s.loopSound = G_SoundIndex( "sound/weapons/scepter/loop.wav" ); + G_PlayEffect( G_EffectIndex( "scepter/beam.efx" ), NPC->weaponModel[1], NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, 10000, qtrue ); + NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SCEPTER_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoAnimTimer += 200; + NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer; + NPC->client->ps.pm_time = NPC->client->ps.torsoAnimTimer; + NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorClear( NPC->client->ps.velocity ); + VectorClear( NPC->client->ps.moveDir ); + } + if ( NPC->enemy ) + { + NPC_FaceEnemy( qtrue ); + } + else + { + NPC_UpdateAngles( qtrue, qtrue ); + } + return qtrue; + } + else if ( NPC->client->ps.torsoAnim == BOTH_SCEPTER_HOLD ) + { + if ( NPC->client->ps.torsoAnimTimer <= 100 ) + { + NPC->s.loopSound = 0; + G_StopEffect( G_EffectIndex( "scepter/beam.efx" ), NPC->weaponModel[1], NPC->genericBolt1, NPC->s.number ); + NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SCEPTER_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->painDebounceTime = level.time + NPC->client->ps.torsoAnimTimer; + NPC->client->ps.pm_time = NPC->client->ps.torsoAnimTimer; + NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorClear( NPC->client->ps.velocity ); + VectorClear( NPC->client->ps.moveDir ); + } + else + { + Tavion_ScepterDamage(); + } + if ( NPC->enemy ) + { + NPC_FaceEnemy( qtrue ); + } + else + { + NPC_UpdateAngles( qtrue, qtrue ); + } + return qtrue; + } + else if ( NPC->client->ps.torsoAnim == BOTH_SCEPTER_STOP ) + { + if ( NPC->enemy ) + { + NPC_FaceEnemy( qtrue ); + } + else + { + NPC_UpdateAngles( qtrue, qtrue ); + } + return qtrue; + } + else if ( NPC->client->ps.torsoAnim == BOTH_TAVION_SCEPTERGROUND ) + { + if ( NPC->client->ps.torsoAnimTimer <= 1200 + && !NPC->count ) + { + Tavion_ScepterSlam(); + NPC->count = 1; + } + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + + if ( Jedi_CultistDestroyer( NPC ) ) + { + if ( !NPC->takedamage ) + {//ready to explode + if ( NPC->useDebounceTime <= level.time ) + { + //this should damage everyone - FIXME: except other destroyers? + NPC->client->playerTeam = TEAM_FREE;//FIXME: will this destroy wampas, tusken & rancors? + WP_Explode( NPC ); + return qtrue; + } + if ( NPC->enemy ) + { + NPC_FaceEnemy( qfalse ); + } + return qtrue; + } + } + + if ( NPC->client->NPC_class == CLASS_REBORN ) + { + if ( (NPCInfo->aiFlags&NPCAI_HEAL_ROSH) ) + { + if ( !NPC->client->leader ) + {//find Rosh + NPC->client->leader = G_Find( NULL, FOFS(NPC_type), "rosh_dark" ); + } + //NPC->client->ps.eFlags &= ~EF_POWERING_ROSH; + if ( NPC->client->leader ) + { + qboolean helpingRosh = qfalse; + NPC->flags |= FL_LOCK_PLAYER_WEAPONS; + NPC->client->leader->flags |= FL_UNDYING; + if ( NPC->client->leader->client ) + { + NPC->client->leader->client->ps.forcePowersKnown |= FORCE_POWERS_ROSH_FROM_TWINS; + } + if ( NPC->client->leader->client->ps.legsAnim == BOTH_FORCEHEAL_START + && TIMER_Done( NPC, "healRoshDebounce" ) ) + { + if ( Kothos_HealRosh() ) + { + helpingRosh = qtrue; + } + else + {//can't get to him! + NPC_BSJedi_FollowLeader(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + + /* + if ( !helpingRosh + && !TIMER_Done( NPC->client->leader, "chargeMeUp" ) + && NPC->client->leader->health > 0) + { + Kothos_PowerRosh(); + helpingRosh = qtrue; + } + */ + + if ( helpingRosh ) + { + WP_ForcePowerStop( NPC, FP_LIGHTNING ); + WP_ForcePowerStop( NPC, FP_DRAIN ); + WP_ForcePowerStop( NPC, FP_GRIP ); + NPC_FaceEntity( NPC->client->leader, qtrue ); + return qtrue; + } + else if ( NPC->enemy && DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ) < Twins_DangerDist() ) + { + if ( NPC->enemy && Kothos_Retreat() ) + { + NPC_FaceEnemy( qtrue ); + //NPC_UpdateAngles( qtrue, qtrue ); + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + if ( NPC->painDebounceTime > level.time + || (NPC->health < 100 && Q_irand(-20, (g_spskill->integer+1)*10) > 0 ) + || !Q_irand( 0, 80-(g_spskill->integer*20) ) ) + { + NPC->flags &= ~FL_LOCK_PLAYER_WEAPONS; + switch ( Q_irand( 0, 7+g_spskill->integer ) )//on easy: no lightning + { + case 0: + case 1: + case 2: + case 3: + ForceThrow( NPC, qfalse, qfalse ); + NPC->client->ps.weaponTime = Q_irand( 1000, 3000 )+(2-g_spskill->integer)*1000; + if ( NPC->painDebounceTime <= level.time + && NPC->health >= 100 ) + { + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + } + break; + case 4: + case 5: + ForceDrain2( NPC ); + NPC->client->ps.weaponTime = Q_irand( 3000, 6000 )+(2-g_spskill->integer)*2000; + TIMER_Set( NPC, "draining", NPC->client->ps.weaponTime ); + if ( NPC->painDebounceTime <= level.time + && NPC->health >= 100 ) + { + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + } + break; + case 6: + case 7: + if ( NPC->enemy && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 20, 30 ) ) + { + NPC->client->ps.weaponTime = Q_irand( 3000, 6000 )+(2-g_spskill->integer)*2000; + TIMER_Set( NPC, "gripping", 3000 ); + if ( NPC->painDebounceTime <= level.time + && NPC->health >= 100 ) + { + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + } + } + break; + case 8: + case 9: + default: + ForceLightning( NPC ); + if ( NPC->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 ) + { + NPC->client->ps.weaponTime = Q_irand( 3000, 6000 )+(2-g_spskill->integer)*2000; + TIMER_Set( NPC, "holdLightning", NPC->client->ps.weaponTime ); + } + if ( NPC->painDebounceTime <= level.time + && NPC->health >= 100 ) + { + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + } + break; + } + } + } + else + { + NPC->flags &= ~FL_LOCK_PLAYER_WEAPONS; + } + Jedi_TimersApply(); + return qtrue; + } + else + { + NPC->flags &= ~FL_LOCK_PLAYER_WEAPONS; + } + } + else if ( !G_ClearLOS( NPC, NPC->client->leader ) + || DistanceSquared( NPC->currentOrigin, NPC->client->leader->currentOrigin ) > (512*512) ) + {//can't see Rosh or too far away, catch up with him + if ( !TIMER_Done( NPC, "attackDelay" ) ) + { + NPC->flags &= ~FL_LOCK_PLAYER_WEAPONS; + } + NPC_BSJedi_FollowLeader(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + else + { + if ( !TIMER_Done( NPC, "attackDelay" ) ) + { + NPC->flags &= ~FL_LOCK_PLAYER_WEAPONS; + } + STEER::Activate( NPC ); + STEER::Stop( NPC ); + STEER::DeActivate( NPC, &ucmd ); + NPC_FaceEnemy( qtrue ); + //NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + //NPC_BSJedi_FollowLeader(); + } + } + NPC_UpdateAngles( qtrue, qtrue ); + //NPC->client->ps.eFlags &= ~EF_POWERING_ROSH; + //G_StopEffect( G_EffectIndex( "force/kothos_beam.efx" ), NPC->playerModel, NPC->handLBolt, NPC->s.number ); + } + else if ( (NPCInfo->aiFlags&NPCAI_ROSH) ) + { + if ( (NPC->flags&FL_UNDYING) ) + {//Vil and/or Dasariah still around to heal me + if ( NPC->health == 1 //need healing + || NPC->client->ps.powerups[PW_INVINCIBLE] > level.time )//being healed + {//FIXME: custom anims + if ( Rosh_TwinPresent( NPC ) ) + { + if ( !NPC->client->ps.weaponTime ) + {//not attacking + if ( NPC->client->ps.legsAnim != BOTH_FORCEHEAL_START + && NPC->client->ps.legsAnim != BOTH_FORCEHEAL_STOP ) + {//get down and wait for Vil or Dasariah to help us + //FIXME: sound? + NPC->client->ps.legsAnimTimer = NPC->client->ps.torsoAnimTimer = 0; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoAnimTimer = NPC->client->ps.legsAnimTimer = -1; + NPC->client->ps.SaberDeactivate(); + NPCInfo->ignorePain = qtrue; + } + } + NPC->client->ps.saberBlocked = BLOCKED_NONE; + NPC->client->ps.saberMove = NPC->client->ps.saberMoveNext = LS_NONE; + NPC->painDebounceTime = level.time + 500; + NPC->client->ps.pm_time = 500; + NPC->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + VectorClear( NPC->client->ps.velocity ); + VectorClear( NPC->client->ps.moveDir ); + return qtrue; + } + } + } + } + } + + if ( PM_SuperBreakWinAnim( NPC->client->ps.torsoAnim ) ) + { + NPC_FaceEnemy( qtrue ); + if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + VectorClear( NPC->client->ps.velocity ); + } + VectorClear( NPC->client->ps.moveDir ); + ucmd.rightmove = ucmd.forwardmove = ucmd.upmove = 0; + return qtrue; + } + + return qfalse; +} + +extern void NPC_BSST_Patrol( void ); +extern void NPC_BSSniper_Default( void ); +extern void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir ); +void NPC_BSJedi_Default( void ) +{ + if ( Jedi_InSpecialMove() ) + { + return; + } + + Jedi_CheckCloak(); + + if( !NPC->enemy ) + {//don't have an enemy, look for one + if ( NPC->client->NPC_class == CLASS_BOBAFETT + || (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER) + || NPC->client->NPC_class == CLASS_ROCKETTROOPER ) + { + NPC_BSST_Patrol(); + } + else + { + Jedi_Patrol(); + } + } + else//if ( NPC->enemy ) + {//have an enemy + if ( Jedi_WaitingAmbush( NPC ) ) + {//we were still waiting to drop down - must have had enemy set on me outside my AI + Jedi_Ambush( NPC ); + } + + if ( Jedi_CultistDestroyer( NPC ) + && !NPCInfo->charmedTime ) + {//destroyer + //permanent effect + NPCInfo->charmedTime = Q3_INFINITE; + NPC->client->ps.forcePowersActive |= ( 1 << FP_RAGE ); + NPC->client->ps.forcePowerDuration[FP_RAGE] = Q3_INFINITE; + //NPC->client->ps.eFlags |= EF_FORCE_DRAINED; + //FIXME: precache me! + NPC->s.loopSound = G_SoundIndex( "sound/movers/objects/green_beam_lp2.wav" );//test/charm.wav" ); + } + + Jedi_Attack(); + //if we have multiple-jedi combat, probably need to keep checking (at certain debounce intervals) for a better (closer, more active) enemy and switch if needbe... + if ( ((!ucmd.buttons&&!NPC->client->ps.forcePowersActive)||(NPC->enemy&&NPC->enemy->health<=0)) && NPCInfo->enemyCheckDebounceTime < level.time ) + {//not doing anything (or walking toward a vanquished enemy - fixme: always taunt the player?), not using force powers and it's time to look again + //FIXME: build a list of all local enemies (since we have to find best anyway) for other AI factors- like when to use group attacks, determine when to change tactics, when surrounded, when blocked by another in the enemy group, etc. Should we build this group list or let the enemies maintain their own list and we just access it? + gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? + NPC->enemy = NULL; + gentity_t *newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); + NPC->enemy = sav_enemy; + if ( newEnemy && newEnemy != sav_enemy ) + {//picked up a new enemy! + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, newEnemy ); + } + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 3000 ); + } + } + if ( NPC->client->ps.saber[0].type == SABER_SITH_SWORD + && NPC->weaponModel[0] != -1 ) + { + if ( NPC->health < 100 + && !Q_irand( 0, 20 ) ) + { + Tavion_SithSwordRecharge(); + } + } +} \ No newline at end of file diff --git a/code/game/AI_Mark1.cpp b/code/game/AI_Mark1.cpp new file mode 100644 index 0000000..24b65cf --- /dev/null +++ b/code/game/AI_Mark1.cpp @@ -0,0 +1,746 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" +#include "b_local.h" +#include "g_nav.h" + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define TURN_OFF 0x00000100 + +#define LEFT_ARM_HEALTH 40 +#define RIGHT_ARM_HEALTH 40 +#define AMMO_POD_HEALTH 40 + +#define BOWCASTER_VELOCITY 1300 +#define BOWCASTER_NPC_DAMAGE_EASY 12 +#define BOWCASTER_NPC_DAMAGE_NORMAL 24 +#define BOWCASTER_NPC_DAMAGE_HARD 36 +#define BOWCASTER_SIZE 2 +#define BOWCASTER_SPLASH_DAMAGE 0 +#define BOWCASTER_SPLASH_RADIUS 0 + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_ASLEEP, + LSTATE_WAKEUP, + LSTATE_FIRED0, + LSTATE_FIRED1, + LSTATE_FIRED2, + LSTATE_FIRED3, + LSTATE_FIRED4, +}; + +qboolean NPC_CheckPlayerTeamStealth( void ); +gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse ); +void Mark1_BlasterAttack(qboolean advance); +void DeathFX( gentity_t *ent ); +extern gitem_t *FindItemForAmmo( ammo_t ammo ); + +/* +------------------------- +NPC_Mark1_Precache +------------------------- +*/ +void NPC_Mark1_Precache(void) +{ + G_SoundIndex( "sound/chars/mark1/misc/mark1_wakeup"); + G_SoundIndex( "sound/chars/mark1/misc/shutdown"); + G_SoundIndex( "sound/chars/mark1/misc/walk"); + G_SoundIndex( "sound/chars/mark1/misc/run"); + G_SoundIndex( "sound/chars/mark1/misc/death1"); + G_SoundIndex( "sound/chars/mark1/misc/death2"); + G_SoundIndex( "sound/chars/mark1/misc/anger"); + G_SoundIndex( "sound/chars/mark1/misc/mark1_fire"); + G_SoundIndex( "sound/chars/mark1/misc/mark1_pain"); + G_SoundIndex( "sound/chars/mark1/misc/mark1_explo"); + +// G_EffectIndex( "small_chunks"); + G_EffectIndex( "env/med_explode2"); + G_EffectIndex( "explosions/probeexplosion1"); + G_EffectIndex( "blaster/smoke_bolton"); + G_EffectIndex( "bryar/muzzle_flash"); + G_EffectIndex( "explosions/droidexplosion1" ); + + RegisterItem( FindItemForAmmo( AMMO_METAL_BOLTS)); + RegisterItem( FindItemForAmmo( AMMO_BLASTER )); + RegisterItem( FindItemForWeapon( WP_BOWCASTER )); + RegisterItem( FindItemForWeapon( WP_BRYAR_PISTOL )); +} + +/* +------------------------- +NPC_Mark1_Part_Explode +------------------------- +*/ +void NPC_Mark1_Part_Explode( gentity_t *self, int bolt ) +{ + if ( bolt >=0 ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir; + + gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, + bolt, + &boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time), + NULL, self->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir ); + + G_PlayEffect( "env/med_explode2", org, dir ); + G_PlayEffect( G_EffectIndex("blaster/smoke_bolton"), self->playerModel, bolt, self->s.number, org ); + } +} + +/* +------------------------- +Mark1_Idle +------------------------- +*/ +void Mark1_Idle( void ) +{ + + NPC_BSIdle(); + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SLEEP1, SETANIM_FLAG_NORMAL ); +} + +/* +------------------------- +Mark1Dead_FireRocket +- Shoot the left weapon, the multi-blaster +------------------------- +*/ +void Mark1Dead_FireRocket (void) +{ + mdxaBone_t boltMatrix; + vec3_t muzzle1,muzzle_dir; + + int damage = 50; + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, + NPC->genericBolt5, + &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, muzzle_dir ); + + G_PlayEffect( "bryar/muzzle_flash", muzzle1, muzzle_dir ); + + G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_fire")); + + gentity_t *missile = CreateMissile( muzzle1, muzzle_dir, BOWCASTER_VELOCITY, 10000, NPC ); + + missile->classname = "bowcaster_proj"; + missile->s.weapon = WP_BOWCASTER; + + VectorSet( missile->maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + missile->splashDamage = BOWCASTER_SPLASH_DAMAGE; + missile->splashRadius = BOWCASTER_SPLASH_RADIUS; + + // we don't want it to bounce + missile->bounceCount = 0; + +} + +/* +------------------------- +Mark1Dead_FireBlaster +- Shoot the left weapon, the multi-blaster +------------------------- +*/ +void Mark1Dead_FireBlaster (void) +{ + vec3_t muzzle1,muzzle_dir; + gentity_t *missile; + mdxaBone_t boltMatrix; + int bolt; + + bolt = NPC->genericBolt1; + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, + bolt, + &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, muzzle_dir ); + + G_PlayEffect( "bryar/muzzle_flash", muzzle1, muzzle_dir ); + + missile = CreateMissile( muzzle1, muzzle_dir, 1600, 10000, NPC ); + + G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_fire")); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->damage = 1; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +Mark1_die +------------------------- +*/ +void Mark1_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) +{ + /* + int anim; + + // Is he dead already? + anim = self->client->ps.legsAnim; + if (((anim==BOTH_DEATH1) || (anim==BOTH_DEATH2)) && (self->client->ps.torsoAnimTimer==0)) + { // This is because self->health keeps getting zeroed out. HL_NONE acts as health in this case. + self->locationDamage[HL_NONE] += damage; + if (self->locationDamage[HL_NONE] > 50) + { + DeathFX(self); + self->client->ps.eFlags |= EF_NODRAW; + self->contents = CONTENTS_CORPSE; + // G_FreeEntity( self ); // Is this safe? I can't see why we'd mark it nodraw and then just leave it around?? + self->e_ThinkFunc = thinkF_G_FreeEntity; + self->nextthink = level.time + FRAMETIME; + } + return; + } + */ + + G_Sound( self, G_SoundIndex(va("sound/chars/mark1/misc/death%d.wav",Q_irand( 1, 2)))); + + // Choose a death anim + if (Q_irand( 1, 10) > 5) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_DEATH2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_DEATH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } +} + +/* +------------------------- +Mark1_dying +------------------------- +*/ +void Mark1_dying( gentity_t *self ) +{ + int num,newBolt; + + if (self->client->ps.torsoAnimTimer>0) + { + if (TIMER_Done(self,"dyingExplosion")) + { + num = Q_irand( 1, 3); + + // Find place to generate explosion + if (num == 1) + { + num = Q_irand( 8, 10); + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], va("*flash%d",num) ); + NPC_Mark1_Part_Explode(self,newBolt); + } + else + { + num = Q_irand( 1, 6); + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], va("*torso_tube%d",num) ); + NPC_Mark1_Part_Explode(self,newBolt); + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], va("torso_tube%d",num), TURN_OFF ); + } + + TIMER_Set( self, "dyingExplosion", Q_irand( 300, 1000 ) ); + } + + +// int dir; +// vec3_t right; + + // Shove to the side +// AngleVectors( self->client->renderInfo.eyeAngles, NULL, right, NULL ); +// VectorMA( self->client->ps.velocity, -80, right, self->client->ps.velocity ); + + // See which weapons are there + // Randomly fire blaster + if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm" )) // Is the blaster still on the model? + { + if (Q_irand( 1, 5) == 1) + { + SaveNPCGlobals(); + SetNPCGlobals( self ); + Mark1Dead_FireBlaster(); + RestoreNPCGlobals(); + } + } + + // Randomly fire rocket + if (!gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "r_arm" )) // Is the rocket still on the model? + { + if (Q_irand( 1, 10) == 1) + { + SaveNPCGlobals(); + SetNPCGlobals( self ); + Mark1Dead_FireRocket(); + RestoreNPCGlobals(); + } + } + } + +} + +/* +------------------------- +NPC_Mark1_Pain +- look at what was hit and see if it should be removed from the model. +------------------------- +*/ +void NPC_Mark1_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + int newBolt,i,chance; + + NPC_Pain( self, inflictor, other, point, damage, mod ); + + G_Sound( self, G_SoundIndex("sound/chars/mark1/misc/mark1_pain")); + + // Hit in the CHEST??? + if (hitLoc==HL_CHEST) + { + chance = Q_irand( 1, 4); + + if ((chance == 1) && (damage > 5)) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + // Hit in the left arm? + else if ((hitLoc==HL_ARM_LT) && (self->locationDamage[HL_ARM_LT] > LEFT_ARM_HEALTH)) + { + if (self->locationDamage[hitLoc] >= LEFT_ARM_HEALTH) // Blow it up? + { + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash3" ); + if ( newBolt != -1 ) + { + NPC_Mark1_Part_Explode(self,newBolt); + } + + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm", TURN_OFF ); + } + } + // Hit in the right arm? + else if ((hitLoc==HL_ARM_RT) && (self->locationDamage[HL_ARM_RT] > RIGHT_ARM_HEALTH)) // Blow it up? + { + if (self->locationDamage[hitLoc] >= RIGHT_ARM_HEALTH) + { + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash4" ); + if ( newBolt != -1 ) + { +// G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt2, self->s.number); + NPC_Mark1_Part_Explode( self, newBolt ); + } + + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "r_arm", TURN_OFF ); + } + } + // Check ammo pods + else + { + for (i=0;i<6;i++) + { + if ((hitLoc==HL_GENERIC1+i) && (self->locationDamage[HL_GENERIC1+i] > AMMO_POD_HEALTH)) // Blow it up? + { + if (self->locationDamage[hitLoc] >= AMMO_POD_HEALTH) + { + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], va("*torso_tube%d",(i+1)) ); + if ( newBolt != -1 ) + { + NPC_Mark1_Part_Explode(self,newBolt); + } + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], va("torso_tube%d",(i+1)), TURN_OFF ); + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + break; + } + } + } + } + + // Are both guns shot off? + if ((gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "l_arm" )) && + (gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "r_arm" ))) + { + G_Damage(self,NULL,NULL,NULL,NULL,self->health,0,MOD_UNKNOWN); + } + +} + +/* +------------------------- +Mark1_Hunt +- look for enemy. +-------------------------` +*/ +void Mark1_Hunt(void) +{ + + if ( NPCInfo->goalEntity == NULL ) + { + NPCInfo->goalEntity = NPC->enemy; + } + + NPC_FaceEnemy( qtrue ); + + NPCInfo->combatMove = qtrue; + NPC_MoveToGoal( qtrue ); +} + +/* +------------------------- +Mark1_FireBlaster +- Shoot the left weapon, the multi-blaster +------------------------- +*/ +void Mark1_FireBlaster(void) +{ + vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1; + static vec3_t forward, vright, up; + static vec3_t muzzle; + gentity_t *missile; + mdxaBone_t boltMatrix; + int bolt; + + // Which muzzle to fire from? + if ((NPCInfo->localState <= LSTATE_FIRED0) || (NPCInfo->localState == LSTATE_FIRED4)) + { + NPCInfo->localState = LSTATE_FIRED1; + bolt = NPC->genericBolt1; + } + else if (NPCInfo->localState == LSTATE_FIRED1) + { + NPCInfo->localState = LSTATE_FIRED2; + bolt = NPC->genericBolt2; + } + else if (NPCInfo->localState == LSTATE_FIRED2) + { + NPCInfo->localState = LSTATE_FIRED3; + bolt = NPC->genericBolt3; + } + else + { + NPCInfo->localState = LSTATE_FIRED4; + bolt = NPC->genericBolt4; + } + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, + bolt, + &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 ); + + if (NPC->health) + { + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 ); + VectorSubtract (enemy_org1, muzzle1, delta1); + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + } + else + { + AngleVectors (NPC->currentAngles, forward, vright, up); + } + + G_PlayEffect( "bryar/muzzle_flash", muzzle1, forward ); + + G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_fire")); + + missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->damage = 1; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +Mark1_BlasterAttack +------------------------- +*/ +void Mark1_BlasterAttack(qboolean advance ) +{ + int chance; + + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + chance = Q_irand( 1, 5); + + NPCInfo->burstCount++; + + if (NPCInfo->burstCount<3) // Too few shots this burst? + { + chance = 2; // Force it to keep firing. + } + else if (NPCInfo->burstCount>12) // Too many shots fired this burst? + { + NPCInfo->burstCount = 0; + chance = 1; // Force it to stop firing. + } + + // Stop firing. + if (chance == 1) + { + NPCInfo->burstCount = 0; + TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000) ); + NPC->client->ps.torsoAnimTimer=0; // Just in case the firing anim is running. + } + else + { + if (TIMER_Done( NPC, "attackDelay2" )) // Can't be shooting every frame. + { + TIMER_Set( NPC, "attackDelay2", Q_irand( 50, 50) ); + Mark1_FireBlaster(); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + return; + } + } + else if (advance) + { + if ( NPC->client->ps.torsoAnim == BOTH_ATTACK1 ) + { + NPC->client->ps.torsoAnimTimer=0; // Just in case the firing anim is running. + } + Mark1_Hunt(); + } + else // Make sure he's not firing. + { + if ( NPC->client->ps.torsoAnim == BOTH_ATTACK1 ) + { + NPC->client->ps.torsoAnimTimer=0; // Just in case the firing anim is running. + } + } +} + +/* +------------------------- +Mark1_FireRocket +------------------------- +*/ +void Mark1_FireRocket(void) +{ + mdxaBone_t boltMatrix; + vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1; + static vec3_t forward, vright, up; + + int damage = 50; + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, + NPC->genericBolt5, + &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 ); + +// G_PlayEffect( "blaster/muzzle_flash", muzzle1 ); + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 ); + VectorSubtract (enemy_org1, muzzle1, delta1); + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + + G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_fire" )); + + gentity_t *missile = CreateMissile( muzzle1, forward, BOWCASTER_VELOCITY, 10000, NPC ); + + missile->classname = "bowcaster_proj"; + missile->s.weapon = WP_BOWCASTER; + + VectorSet( missile->maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + missile->splashDamage = BOWCASTER_SPLASH_DAMAGE; + missile->splashRadius = BOWCASTER_SPLASH_RADIUS; + + // we don't want it to bounce + missile->bounceCount = 0; + +} + +/* +------------------------- +Mark1_RocketAttack +------------------------- +*/ +void Mark1_RocketAttack( qboolean advance ) +{ + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000) ); + NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + Mark1_FireRocket(); + } + else if (advance) + { + Mark1_Hunt(); + } +} + +/* +------------------------- +Mark1_AttackDecision +------------------------- +*/ +void Mark1_AttackDecision( void ) +{ + int blasterTest,rocketTest; + + //randomly talk + if ( TIMER_Done(NPC,"patrolNoise") ) + { + if (TIMER_Done(NPC,"angerNoise")) + { +// G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4)))); + TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); + } + } + + // Enemy is dead or he has no enemy. + if ((NPC->enemy->health<1) || ( NPC_CheckEnemyExt() == qfalse )) + { + NPC->enemy = NULL; + return; + } + + // Rate our distance to the target and visibility + float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + distance_e distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; + qboolean visible = NPC_ClearLOS( NPC->enemy ); + qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ((!visible) || (!NPC_FaceEnemy(qtrue))) + { + Mark1_Hunt(); + return; + } + + // See if the side weapons are there + blasterTest = gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "l_arm" ); + rocketTest = gi.G2API_GetSurfaceRenderStatus( &NPC->ghoul2[NPC->playerModel], "r_arm" ); + + // It has both side weapons + if (!blasterTest && !rocketTest) + { + ; // So do nothing. + } + else if (blasterTest) + { + distRate = DIST_LONG; + } + else if (rocketTest) + { + distRate = DIST_MELEE; + } + else // It should never get here, but just in case + { + NPC->health = 0; + NPC->client->ps.stats[STAT_HEALTH] = 0; + GEntity_DieFunc(NPC, NPC, NPC, 100, MOD_UNKNOWN); + } + + // We can see enemy so shoot him if timers let you. + NPC_FaceEnemy( qtrue ); + + if (distRate == DIST_MELEE) + { + Mark1_BlasterAttack(advance); + } + else if (distRate == DIST_LONG) + { + Mark1_RocketAttack(advance); + } +} + +/* +------------------------- +Mark1_Patrol +------------------------- +*/ +void Mark1_Patrol( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { + G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/mark1_wakeup")); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + } + + //randomly talk +// if (TIMER_Done(NPC,"patrolNoise")) +// { +// G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4)))); +// +// TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); +// } + } + +} + + +/* +------------------------- +NPC_BSMark1_Default +------------------------- +*/ +void NPC_BSMark1_Default( void ) +{ + //NPC->e_DieFunc = dieF_Mark1_die; + + if ( NPC->enemy ) + { + NPCInfo->goalEntity = NPC->enemy; + Mark1_AttackDecision(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Mark1_Patrol(); + } + else + { + Mark1_Idle(); + } +} \ No newline at end of file diff --git a/code/game/AI_Mark2.cpp b/code/game/AI_Mark2.cpp new file mode 100644 index 0000000..6d74bff --- /dev/null +++ b/code/game/AI_Mark2.cpp @@ -0,0 +1,358 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" + +//#define AMMO_POD_HEALTH 40 +#define AMMO_POD_HEALTH 1 +#define TURN_OFF 0x00000100 + +#define VELOCITY_DECAY 0.25 +#define MAX_DISTANCE 256 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) +#define MIN_DISTANCE 24 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +extern gitem_t *FindItemForAmmo( ammo_t ammo ); + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_DROPPINGDOWN, + LSTATE_DOWN, + LSTATE_RISINGUP, +}; + +gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse ); + +void NPC_Mark2_Precache( void ) +{ + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" );// blows up on death + G_SoundIndex( "sound/chars/mark2/misc/mark2_pain" ); + G_SoundIndex( "sound/chars/mark2/misc/mark2_fire" ); + G_SoundIndex( "sound/chars/mark2/misc/mark2_move_lp" ); + + G_EffectIndex( "explosions/droidexplosion1" ); + G_EffectIndex( "env/med_explode2" ); + G_EffectIndex( "blaster/smoke_bolton" ); + G_EffectIndex( "bryar/muzzle_flash" ); + + RegisterItem( FindItemForWeapon( WP_BRYAR_PISTOL )); + RegisterItem( FindItemForAmmo( AMMO_METAL_BOLTS)); + RegisterItem( FindItemForAmmo( AMMO_POWERCELL )); + RegisterItem( FindItemForAmmo( AMMO_BLASTER )); +} + +/* +------------------------- +NPC_Mark2_Part_Explode +------------------------- +*/ +void NPC_Mark2_Part_Explode( gentity_t *self, int bolt ) +{ + if ( bolt >=0 ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir; + + gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, + bolt, + &boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time), + NULL, self->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir ); + + G_PlayEffect( "env/med_explode2", org, dir ); + G_PlayEffect( G_EffectIndex("blaster/smoke_bolton"), self->playerModel, bolt, self->s.number, org); + } + + self->count++; // Count of pods blown off +} + +/* +------------------------- +NPC_Mark2_Pain +- look at what was hit and see if it should be removed from the model. +------------------------- +*/ +void NPC_Mark2_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + int newBolt,i; + + NPC_Pain( self, inflictor, other, point, damage, mod ); + + for (i=0;i<3;i++) + { + if ((hitLoc==HL_GENERIC1+i) && (self->locationDamage[HL_GENERIC1+i] > AMMO_POD_HEALTH)) // Blow it up? + { + if (self->locationDamage[hitLoc] >= AMMO_POD_HEALTH) + { + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], va("torso_canister%d",(i+1)) ); + if ( newBolt != -1 ) + { + NPC_Mark2_Part_Explode(self,newBolt); + } + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], va("torso_canister%d",(i+1)), TURN_OFF ); + break; + } + } + } + + G_Sound( self, G_SoundIndex( "sound/chars/mark2/misc/mark2_pain" )); + + // If any pods were blown off, kill him + if (self->count > 0) + { + G_Damage( self, NULL, NULL, NULL, NULL, self->health, DAMAGE_NO_PROTECTION, MOD_UNKNOWN ); + } +} + +/* +------------------------- +Mark2_Hunt +------------------------- +*/ +void Mark2_Hunt(void) +{ + if ( NPCInfo->goalEntity == NULL ) + { + NPCInfo->goalEntity = NPC->enemy; + } + + // Turn toward him before moving towards him. + NPC_FaceEnemy( qtrue ); + + NPCInfo->combatMove = qtrue; + NPC_MoveToGoal( qtrue ); +} + +/* +------------------------- +Mark2_FireBlaster +------------------------- +*/ +void Mark2_FireBlaster(qboolean advance) +{ + vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1; + static vec3_t forward, vright, up; + static vec3_t muzzle; + gentity_t *missile; + mdxaBone_t boltMatrix; + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, + NPC->genericBolt1, + &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle1 ); + + if (NPC->health) + { + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 ); + VectorSubtract (enemy_org1, muzzle1, delta1); + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + } + else + { + AngleVectors (NPC->currentAngles, forward, vright, up); + } + + G_PlayEffect( "bryar/muzzle_flash", muzzle1, forward ); + + G_Sound( NPC, G_SoundIndex("sound/chars/mark2/misc/mark2_fire")); + + missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->damage = 1; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +Mark2_BlasterAttack +------------------------- +*/ +void Mark2_BlasterAttack(qboolean advance) +{ + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + if (NPCInfo->localState == LSTATE_NONE) // He's up so shoot less often. + { + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2000) ); + } + else + { + TIMER_Set( NPC, "attackDelay", Q_irand( 100, 500) ); + } + Mark2_FireBlaster(advance); + return; + } + else if (advance) + { + Mark2_Hunt(); + } +} + +/* +------------------------- +Mark2_AttackDecision +------------------------- +*/ +void Mark2_AttackDecision( void ) +{ + NPC_FaceEnemy( qtrue ); + + float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + qboolean visible = NPC_ClearLOS( NPC->enemy ); + qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // He's been ordered to get up + if (NPCInfo->localState == LSTATE_RISINGUP) + { + NPC->flags &= ~FL_SHIELDED; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + if ((NPC->client->ps.legsAnimTimer==0) && + NPC->client->ps.torsoAnim == BOTH_RUN1START ) + { + NPCInfo->localState = LSTATE_NONE; // He's up again. + } + return; + } + + // If we cannot see our target, move to see it + if ((!visible) || (!NPC_FaceEnemy(qtrue))) + { + // If he's going down or is down, make him get up + if ((NPCInfo->localState == LSTATE_DOWN) || (NPCInfo->localState == LSTATE_DROPPINGDOWN)) + { + if ( TIMER_Done( NPC, "downTime" ) ) // Down being down?? (The delay is so he doesn't pop up and down when the player goes in and out of range) + { + NPCInfo->localState = LSTATE_RISINGUP; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + TIMER_Set( NPC, "runTime", Q_irand( 3000, 8000) ); // So he runs for a while before testing to see if he should drop down. + } + } + else + { + Mark2_Hunt(); + } + return; + } + + // He's down but he could advance if he wants to. + if ((advance) && (TIMER_Done( NPC, "downTime" )) && (NPCInfo->localState == LSTATE_DOWN)) + { + NPCInfo->localState = LSTATE_RISINGUP; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + TIMER_Set( NPC, "runTime", Q_irand( 3000, 8000) ); // So he runs for a while before testing to see if he should drop down. + } + + NPC_FaceEnemy( qtrue ); + + // Dropping down to shoot + if (NPCInfo->localState == LSTATE_DROPPINGDOWN) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + TIMER_Set( NPC, "downTime", Q_irand( 3000, 9000) ); + + if ((NPC->client->ps.legsAnimTimer==0) && NPC->client->ps.torsoAnim == BOTH_RUN1STOP ) + { + NPC->flags |= FL_SHIELDED; + NPCInfo->localState = LSTATE_DOWN; + } + } + // He's down and shooting + else if (NPCInfo->localState == LSTATE_DOWN) + { +// NPC->flags |= FL_SHIELDED;//only damagable by lightsabers and missiles + + Mark2_BlasterAttack(qfalse); + } + else if (TIMER_Done( NPC, "runTime" )) // Lowering down to attack. But only if he's done running at you. + { + NPCInfo->localState = LSTATE_DROPPINGDOWN; + } + else if (advance) + { + // We can see enemy so shoot him if timer lets you. + Mark2_BlasterAttack(advance); + } +} + + +/* +------------------------- +Mark2_Patrol +------------------------- +*/ +void Mark2_Patrol( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { +// G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/anger.wav")); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + } + + //randomly talk + if (TIMER_Done(NPC,"patrolNoise")) + { +// G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4)))); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } +} + +/* +------------------------- +Mark2_Idle +------------------------- +*/ +void Mark2_Idle( void ) +{ + NPC_BSIdle(); +} + +/* +------------------------- +NPC_BSMark2_Default +------------------------- +*/ +void NPC_BSMark2_Default( void ) +{ + if ( NPC->enemy ) + { + NPCInfo->goalEntity = NPC->enemy; + Mark2_AttackDecision(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Mark2_Patrol(); + } + else + { + Mark2_Idle(); + } +} diff --git a/code/game/AI_MineMonster.cpp b/code/game/AI_MineMonster.cpp new file mode 100644 index 0000000..9436507 --- /dev/null +++ b/code/game/AI_MineMonster.cpp @@ -0,0 +1,269 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" + + +// These define the working combat range for these suckers +#define MIN_DISTANCE 54 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 128 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +/* +------------------------- +NPC_MineMonster_Precache +------------------------- +*/ +void NPC_MineMonster_Precache( void ) +{ + for ( int i = 0; i < 4; i++ ) + { + G_SoundIndex( va("sound/chars/mine/misc/bite%i.wav", i+1 )); + G_SoundIndex( va("sound/chars/mine/misc/miss%i.wav", i+1 )); + } +} + + +/* +------------------------- +MineMonster_Idle +------------------------- +*/ +void MineMonster_Idle( void ) +{ + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } +} + + +/* +------------------------- +MineMonster_Patrol +------------------------- +*/ +void MineMonster_Patrol( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + vec3_t dif; + VectorSubtract( g_entities[0].currentOrigin, NPC->currentOrigin, dif ); + + if ( VectorLengthSquared( dif ) < 256 * 256 ) + { + G_SetEnemy( NPC, &g_entities[0] ); + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + MineMonster_Idle(); + return; + } +} + +/* +------------------------- +MineMonster_Move +------------------------- +*/ +void MineMonster_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + NPC_MoveToGoal( qtrue ); + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + } +} + +//--------------------------------------------------------- +void MineMonster_TryDamage( gentity_t *enemy, int damage ) +{ + vec3_t end, dir; + trace_t tr; + + if ( !enemy ) + { + return; + } + + AngleVectors( NPC->client->ps.viewangles, dir, NULL, NULL ); + VectorMA( NPC->currentOrigin, MIN_DISTANCE, dir, end ); + + // Should probably trace from the mouth, but, ah well. + gi.trace( &tr, NPC->currentOrigin, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + + if ( tr.entityNum >= 0 && tr.entityNum < ENTITYNUM_NONE ) + { + G_Damage( &g_entities[tr.entityNum], NPC, NPC, dir, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, va("sound/chars/mine/misc/bite%i.wav", Q_irand(1,4))); + } + else + { + G_SoundOnEnt( NPC, CHAN_VOICE_ATTEN, va("sound/chars/mine/misc/miss%i.wav", Q_irand(1,4))); + } +} + +//------------------------------ +void MineMonster_Attack( void ) +{ + if ( !TIMER_Exists( NPC, "attacking" )) + { + // usually try and play a jump attack if the player somehow got above them....or just really rarely + if ( NPC->enemy && ((NPC->enemy->currentOrigin[2] - NPC->currentOrigin[2] > 10 && random() > 0.1f ) + || random() > 0.8f )) + { + // Going to do ATTACK4 + TIMER_Set( NPC, "attacking", 1750 + random() * 200 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK4, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack2_dmg", 950 ); // level two damage + } + else if ( random() > 0.5f ) + { + if ( random() > 0.8f ) + { + // Going to do ATTACK3, (rare) + TIMER_Set( NPC, "attacking", 850 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack2_dmg", 400 ); // level two damage + } + else + { + // Going to do ATTACK1 + TIMER_Set( NPC, "attacking", 850 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack1_dmg", 450 ); // level one damage + } + } + else + { + // Going to do ATTACK2 + TIMER_Set( NPC, "attacking", 1250 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack1_dmg", 700 ); // level one damage + } + } + else + { + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + if ( TIMER_Done2( NPC, "attack1_dmg", qtrue )) + { + MineMonster_TryDamage( NPC->enemy, 5 ); + } + else if ( TIMER_Done2( NPC, "attack2_dmg", qtrue )) + { + MineMonster_TryDamage( NPC->enemy, 10 ); + } + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); +} + +//---------------------------------- +void MineMonster_Combat( void ) +{ + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS( NPC->enemy ) || UpdateGoal( )) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + + NPC_MoveToGoal( qtrue ); + return; + } + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + float distance = DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + + qboolean advance = (qboolean)( distance > MIN_DISTANCE_SQR ? qtrue : qfalse ); + + if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + MineMonster_Move( 1 ); + } + } + else + { + MineMonster_Attack(); + } +} + +/* +------------------------- +NPC_MineMonster_Pain +------------------------- +*/ +void NPC_MineMonster_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + G_AddEvent( self, EV_PAIN, floor((float)self->health/self->max_health*100.0f) ); + + if ( damage >= 10 ) + { + TIMER_Remove( self, "attacking" ); + TIMER_Remove( self, "attacking1_dmg" ); + TIMER_Remove( self, "attacking2_dmg" ); + TIMER_Set( self, "takingPain", 1350 ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } +} + + +/* +------------------------- +NPC_BSMineMonster_Default +------------------------- +*/ +void NPC_BSMineMonster_Default( void ) +{ + if ( NPC->enemy ) + { + MineMonster_Combat(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + MineMonster_Patrol(); + } + else + { + MineMonster_Idle(); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/code/game/AI_Rancor.cpp b/code/game/AI_Rancor.cpp new file mode 100644 index 0000000..23e190e --- /dev/null +++ b/code/game/AI_Rancor.cpp @@ -0,0 +1,1691 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" + +// These define the working combat range for these suckers +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 1024 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +#define SPF_RANCOR_MUTANT 1 +#define SPF_RANCOR_FASTKILL 2 + +extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker ); +extern cvar_t *g_dismemberment; +extern cvar_t *g_bobaDebug; + +void Rancor_Attack( float distance, qboolean doCharge, qboolean aimAtBlockedEntity ); +/* +------------------------- +NPC_Rancor_Precache +------------------------- +*/ +void NPC_Rancor_Precache( void ) +{ + int i; + for ( i = 1; i < 5; i ++ ) + { + G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", i) ); + } + G_SoundIndex( "sound/chars/rancor/swipehit.wav" ); + G_SoundIndex( "sound/chars/rancor/chomp.wav" ); +} + +void NPC_MutantRancor_Precache( void ) +{ + G_SoundIndex( "sound/chars/rancor/breath_start.wav" ); + G_SoundIndex( "sound/chars/rancor/breath_loop.wav" ); + G_EffectIndex( "mrancor/breath" ); +} +//FIXME: initialize all my timers + +qboolean Rancor_CheckAhead( vec3_t end ) +{ + trace_t trace; + int clipmask = NPC->clipmask|CONTENTS_BOTCLIP; + + //make sure our goal isn't underground (else the trace will fail) + vec3_t bottom = {end[0],end[1],end[2]+NPC->mins[2]}; + gi.trace( &trace, end, vec3_origin, vec3_origin, bottom, NPC->s.number, NPC->clipmask ); + if ( trace.fraction < 1.0f ) + {//in the ground, raise it up + end[2] -= NPC->mins[2]*(1.0f-trace.fraction)-0.125f; + } + + gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask ); + + if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) ) + {//started inside do not enter, so ignore them + clipmask &= ~CONTENTS_BOTCLIP; + gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask ); + } + //Do a simple check + if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) ) + return qtrue; + + if ( trace.entityNum < ENTITYNUM_WORLD + && G_EntIsBreakable( trace.entityNum, NPC ) ) + {//breakable brush in our way, break it + // NPCInfo->blockedEntity = &g_entities[trace.entityNum]; + return qtrue; + } + + //Aw screw it, always try to go straight at him if we can at all + if ( trace.fraction >= 0.25f ) + return qtrue; + + //FIXME: if something in the way that's not the world, set blocked ent + return qfalse; +} + +/* +------------------------- +Rancor_Idle +------------------------- +*/ +void Rancor_Idle( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } +} + + +qboolean Rancor_CheckRoar( gentity_t *self ) +{ + if ( !self->wait ) + {//haven't ever gotten mad yet + self->wait = 1;//do this only once + NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( self, "rageTime", self->client->ps.legsAnimTimer ); + return qtrue; + } + return qfalse; +} +/* +------------------------- +Rancor_Patrol +------------------------- +*/ +void Rancor_Patrol( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Rancor_Idle(); + return; + } + Rancor_CheckRoar( NPC ); + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); +} + +/* +------------------------- +Rancor_Move +------------------------- +*/ +void Rancor_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0]); // just get us within combat range + //FIXME: for some reason, if NPC_MoveToGoal fails, it sets my angles to my lastPathAngles, which I don't want + float savYaw = NPCInfo->desiredYaw; + bool savWalking = !!(ucmd.buttons&BUTTON_WALKING); + if ( !NPC_MoveToGoal( qtrue ) ) + {//can't macro-nav, just head right for him + //FIXME: if something in the way that's not the world, set blocked ent + vec3_t dest; + VectorCopy( NPCInfo->goalEntity->currentOrigin, dest ); + if ( Rancor_CheckAhead( dest ) ) + {//use our temp move straight to goal check + if (!savWalking) + { + ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal() + } + STEER::Activate(NPC); + STEER::Seek(NPC, dest); + STEER::AvoidCollisions(NPC); + STEER::DeActivate(NPC, &ucmd); + /* + VectorSubtract( dest, NPC->currentOrigin, NPC->client->ps.moveDir ); + NPC->client->ps.speed = VectorNormalize( NPC->client->ps.moveDir ); + NPCInfo->desiredYaw = vectoyaw( NPC->client->ps.moveDir ); + if ( (ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed > NPCInfo->stats.walkSpeed ) + { + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + } + else + { + if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed ) + { + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + } + if ( !(ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed < NPCInfo->stats.runSpeed ) + { + NPC->client->ps.speed = NPCInfo->stats.runSpeed; + } + else if ( NPC->client->ps.speed > NPCInfo->stats.runSpeed ) + { + NPC->client->ps.speed = NPCInfo->stats.runSpeed; + } + } + */ + } + else + {//all else fails, look at him + // gi.Printf("Fail\n"); + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw = savYaw; + /* if ( !NPCInfo->blockedEntity ) + {//not already trying to break a breakable somewhere + if ( NPC->enemy && NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum < ENTITYNUM_WORLD ) + {//hmm, maybe he's on a breakable brush? + if ( G_EntIsBreakable( NPC->enemy->client->ps.groundEntityNum, NPC ) ) + {//break it! + gentity_t *breakable = &g_entities[NPC->enemy->client->ps.groundEntityNum]; + int sanityCheck = 0; + //FiXME: and if he's on a stack of 3 breakables? + //FIXME: See if the breakable has a targetname, if so see if the thing targeting it is a breakable, if so, etc... + while ( sanityCheck < 20 && breakable && breakable->targetname ) + { + gentity_t *breakableNext = NULL; + while ( sanityCheck < 20 && (breakableNext = G_Find( breakableNext, FOFS(target), breakable->targetname )) != NULL ) + { + if ( breakableNext && G_EntIsBreakable( breakableNext->s.number, NPC ) ) + { + breakable = breakableNext; + break; + } + else + { + sanityCheck++; + } + } + if ( !breakableNext ) + {//not targetted by another breakable that we can break + break; + } + else + { + sanityCheck++; + } + } + NPCInfo->blockedEntity = breakable; + } + } + }*/ + if ( !NPCInfo->blockedEntity && NPC->enemy && gi.inPVS(NPC->currentOrigin, NPC->enemy->currentOrigin)) + {//nothing to destroy? just go straight at goal dest + qboolean horzClose = qfalse; + if (!savWalking) + { + ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal() + } + + if ( DistanceHorizontal( NPC->enemy->currentOrigin, NPC->currentOrigin ) < (NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])) ) + {//close, just look at him + horzClose = qtrue; + NPC_FaceEnemy( qtrue ); + } + else + {//try to move towards him + STEER::Activate(NPC); + STEER::Seek(NPC, dest); + STEER::AvoidCollisions(NPC); + STEER::DeActivate(NPC, &ucmd); + } + //let him know he should attack at random out of frustration? + if ( NPCInfo->goalEntity == NPC->enemy ) + { + if ( TIMER_Done( NPC, "attacking" ) + && TIMER_Done( NPC, "frustrationAttack" ) ) + { + float enemyDist = Distance( dest, NPC->currentOrigin ); + if ( (!horzClose||!Q_irand(0,5)) + && Q_irand( 0, 1 ) ) + { + Rancor_Attack( enemyDist, qtrue, qfalse ); + } + else + { + Rancor_Attack( enemyDist, qfalse, qfalse ); + } + if ( horzClose ) + { + TIMER_Set( NPC, "frustrationAttack", Q_irand( 2000, 5000 ) ); + } + else + { + TIMER_Set( NPC, "frustrationAttack", Q_irand( 5000, 15000 ) ); + } + } + } + } + } + } + } +} + +//--------------------------------------------------------- +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force = qfalse ); +extern float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex ); +extern int NPC_GetEntsNearBolt( gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg ); + +void Rancor_DropVictim( gentity_t *self ) +{ + //FIXME: if Rancor dies, it should drop its victim. + //FIXME: if Rancor is removed, it must remove its victim. + //FIXME: if in BOTH_HOLD_DROP, throw them a little, too? + if ( self->activator ) + { + if ( self->activator->client ) + { + self->activator->client->ps.eFlags &= ~EF_HELD_BY_RANCOR; + } + self->activator->activator = NULL; + if ( self->activator->health <= 0 ) + { + if ( self->activator->s.number ) + {//never free player + if ( self->count == 1 ) + {//in my hand, just drop them + if ( self->activator->client ) + { + self->activator->client->ps.legsAnimTimer = self->activator->client->ps.torsoAnimTimer = 0; + //FIXME: ragdoll? + } + } + else + { + G_FreeEntity( self->activator ); + } + } + else + { + self->activator->s.eFlags |= EF_NODRAW; + if ( self->activator->client ) + { + self->activator->client->ps.eFlags |= EF_NODRAW; + } + self->activator->clipmask &= ~CONTENTS_BODY; + } + } + else + { + if ( self->activator->NPC ) + {//start thinking again + self->activator->NPC->nextBStateThink = level.time; + } + //clear their anim and let them fall + self->activator->client->ps.legsAnimTimer = self->activator->client->ps.torsoAnimTimer = 0; + } + if ( self->enemy == self->activator ) + { + self->enemy = NULL; + } + if ( self->activator->s.number == 0 ) + {//don't attack the player again for a bit + TIMER_Set( self, "attackDebounce", Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) ); + } + self->activator = NULL; + } + self->count = 0;//drop him +} + +void Rancor_Swing( int boltIndex, qboolean tryGrab ) +{ + gentity_t *radiusEnts[ 128 ]; + int numEnts; + const float radius = (NPC->spawnflags&SPF_RANCOR_MUTANT)?200:88; + const float radiusSquared = (radius*radius); + int i; + vec3_t boltOrg; + vec3_t originUp; + + VectorCopy(NPC->currentOrigin, originUp); + originUp[2] += (NPC->maxs[2]*0.75f); + + numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, boltIndex, boltOrg ); + + //if ( NPCInfo->blockedEntity && G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) ) + {//attacking a breakable brush + //HMM... maybe always do this? + //if boltOrg inside a breakable brush, damage it + trace_t trace; + gi.trace( &trace, NPC->pos3, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY ); +#ifndef FINAL_BUILD + if ( g_bobaDebug->integer > 0 ) + { + G_DebugLine(NPC->pos3, boltOrg, 1000, 0x000000ff, qtrue); + } +#endif + //remember pos3 for the trace from last hand pos to current hand pos next time + VectorCopy( boltOrg, NPC->pos3 ); + //FIXME: also do a trace TO the bolt from where we are...? + if ( G_EntIsBreakable( trace.entityNum, NPC ) ) + { + G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 100, 0, MOD_MELEE ); + } + else + {//fuck, do an actual line trace, I guess... + gi.trace( &trace, originUp, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY ); +#ifndef FINAL_BUILD + if ( g_bobaDebug->integer > 0 ) + { + G_DebugLine(originUp, boltOrg, 1000, 0x000000ff, qtrue); + } +#endif + if ( G_EntIsBreakable( trace.entityNum, NPC ) ) + { + G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE ); + } + } + } + + for ( i = 0; i < numEnts; i++ ) + { + if ( !radiusEnts[i]->inuse ) + { + continue; + } + + if ( radiusEnts[i] == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnts[i]->client == NULL ) + {//must be a client + continue; + } + + if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) + ||(radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) ) + {//can't be one already being held + continue; + } + + if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) ) + {//not if invisible + continue; + } + /* + if ( !radiusEnts[i]->contents ) + {//not if non-solid + continue; + } + */ + + if ( DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ) <= radiusSquared ) + { + if ( !gi.inPVS( radiusEnts[i]->currentOrigin, NPC->currentOrigin ) ) + {//don't grab anything that's in another PVS + continue; + } + /* + qboolean skipGrab = qfalse; + if ( tryGrab//want to grab + && (NPC->spawnflags&SPF_RANCOR_FASTKILL)//mutant rancor + && radiusEnts[i]->s.number >= MAX_CLIENTS //not the player + && Q_irand( 0, 1 ) )//50% chance + {//don't grab them, just smack them away + skipGrab = qtrue; + } + */ + if ( tryGrab + //&& !skipGrab + && NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth! + && radiusEnts[i]->client->NPC_class != CLASS_RANCOR + && radiusEnts[i]->client->NPC_class != CLASS_GALAKMECH + && radiusEnts[i]->client->NPC_class != CLASS_ATST + && radiusEnts[i]->client->NPC_class != CLASS_GONK + && radiusEnts[i]->client->NPC_class != CLASS_R2D2 + && radiusEnts[i]->client->NPC_class != CLASS_R5D2 + && radiusEnts[i]->client->NPC_class != CLASS_MARK1 + && radiusEnts[i]->client->NPC_class != CLASS_MARK2 + && radiusEnts[i]->client->NPC_class != CLASS_MOUSE + && radiusEnts[i]->client->NPC_class != CLASS_PROBE + && radiusEnts[i]->client->NPC_class != CLASS_SEEKER + && radiusEnts[i]->client->NPC_class != CLASS_REMOTE + && radiusEnts[i]->client->NPC_class != CLASS_SENTRY + && radiusEnts[i]->client->NPC_class != CLASS_INTERROGATOR + && radiusEnts[i]->client->NPC_class != CLASS_VEHICLE ) + {//grab + if ( NPC->count == 2 ) + {//have one in my mouth, remove him + TIMER_Remove( NPC, "clearGrabbed" ); + Rancor_DropVictim( NPC ); + } + NPC->enemy = radiusEnts[i];//make him my new best friend + radiusEnts[i]->client->ps.eFlags |= EF_HELD_BY_RANCOR; + //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something + radiusEnts[i]->activator = NPC; // kind of dumb, but when we are locked to the Rancor, we are owned by it. + NPC->activator = radiusEnts[i];//remember him + NPC->count = 1;//in my hand + //wait to attack + TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_irand(500, 2500) ); + if ( radiusEnts[i]->health > 0 ) + {//do pain on enemy + GEntity_PainFunc( radiusEnts[i], NPC, NPC, radiusEnts[i]->currentOrigin, 0, MOD_CRUSH ); + } + else if ( radiusEnts[i]->client ) + { + NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + } + else + {//smack + G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + //actually push the enemy + vec3_t pushDir; + /* + //VectorSubtract( radiusEnts[i]->currentOrigin, boltOrg, pushDir ); + VectorSubtract( radiusEnts[i]->currentOrigin, NPC->currentOrigin, pushDir ); + pushDir[2] = Q_flrand( 100, 200 ); + VectorNormalize( pushDir ); + */ + if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL) + && radiusEnts[i]->s.number >= MAX_CLIENTS ) + { + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, boltOrg, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE ); + } + vec3_t angs; + VectorCopy( NPC->client->ps.viewangles, angs ); + angs[YAW] += Q_flrand( 25, 50 ); + angs[PITCH] = Q_flrand( -25, -15 ); + AngleVectors( angs, pushDir, NULL, NULL ); + if ( radiusEnts[i]->client->NPC_class != CLASS_RANCOR + && radiusEnts[i]->client->NPC_class != CLASS_ATST + && !(radiusEnts[i]->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( radiusEnts[i], pushDir, 250 ); + if ( radiusEnts[i]->health > 0 ) + {//do pain on enemy + G_Knockdown( radiusEnts[i], NPC, pushDir, 100, qtrue ); + } + } + } + } + } +} + +void Rancor_Smash( void ) +{ + gentity_t *radiusEnts[ 128 ]; + int numEnts; + const float radius = (NPC->spawnflags&SPF_RANCOR_MUTANT)?256:128; + const float halfRadSquared = ((radius/2)*(radius/2)); + const float radiusSquared = (radius*radius); + float distSq; + int i; + vec3_t boltOrg; + + AddSoundEvent( NPC, NPC->currentOrigin, 512, AEL_DANGER, qfalse, qtrue ); + + numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->handLBolt, boltOrg ); + + //if ( NPCInfo->blockedEntity && G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) ) + {//attacking a breakable brush + //HMM... maybe always do this? + //if boltOrg inside a breakable brush, damage it + trace_t trace; + gi.trace( &trace, boltOrg, vec3_origin, vec3_origin, NPC->pos3, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY ); +#ifndef FINAL_BUILD + if ( g_bobaDebug->integer > 0 ) + { + G_DebugLine(NPC->pos3, boltOrg, 1000, 0x000000ff, qtrue); + } +#endif + //remember pos3 for the trace from last hand pos to current hand pos next time + VectorCopy( boltOrg, NPC->pos3 ); + //FIXME: also do a trace TO the bolt from where we are...? + if ( G_EntIsBreakable( trace.entityNum, NPC ) ) + { + G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE ); + } + else + {//fuck, do an actual line trace, I guess... + gi.trace( &trace, NPC->currentOrigin, vec3_origin, vec3_origin, boltOrg, NPC->s.number, CONTENTS_SOLID|CONTENTS_BODY ); +#ifndef FINAL_BUILD + if ( g_bobaDebug->integer > 0 ) + { + G_DebugLine(NPC->currentOrigin, boltOrg, 1000, 0x000000ff, qtrue); + } +#endif + if ( G_EntIsBreakable( trace.entityNum, NPC ) ) + { + G_Damage( &g_entities[trace.entityNum], NPC, NPC, vec3_origin, boltOrg, 200, 0, MOD_MELEE ); + } + } + } + + for ( i = 0; i < numEnts; i++ ) + { + if ( !radiusEnts[i]->inuse ) + { + continue; + } + + if ( radiusEnts[i] == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnts[i]->client == NULL ) + {//must be a client + if ( G_EntIsBreakable( radiusEnts[i]->s.number, NPC ) ) + {//damage breakables within range, but not as much + if ( !Q_irand( 0, 1 ) ) + { + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, 100, 0, MOD_MELEE ); + } + } + continue; + } + + if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) ) + {//can't be one being held + continue; + } + + if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) ) + {//not if invisible + continue; + } + + distSq = DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ); + if ( distSq <= radiusSquared ) + { + if ( distSq < halfRadSquared ) + {//close enough to do damage, too + G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL) + && radiusEnts[i]->s.number >= MAX_CLIENTS ) + { + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, boltOrg, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE ); + } + else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )//FIXME: a flag or something would be better + {//more damage + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 40, 55 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + else + { + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 10, 25 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + } + if ( radiusEnts[i]->health > 0 + && radiusEnts[i]->client + && radiusEnts[i]->client->NPC_class != CLASS_RANCOR + && radiusEnts[i]->client->NPC_class != CLASS_ATST ) + { + if ( distSq < halfRadSquared + || radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//within range of my fist or withing ground-shaking range and not in the air + if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) ) + { + G_Knockdown( radiusEnts[i], NPC, vec3_origin, 500, qtrue ); + } + else + { + G_Knockdown( radiusEnts[i], NPC, vec3_origin, Q_irand( 200, 350), qtrue ); + } + } + } + } + } +} + +void Rancor_Bite( void ) +{ + gentity_t *radiusEnts[ 128 ]; + int numEnts; + const float radius = 100; + const float radiusSquared = (radius*radius); + int i; + vec3_t boltOrg; + + numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, NPC->gutBolt, boltOrg ); + + for ( i = 0; i < numEnts; i++ ) + { + if ( !radiusEnts[i]->inuse ) + { + continue; + } + + if ( radiusEnts[i] == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnts[i]->client == NULL ) + {//must be a client + continue; + } + + if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) ) + {//can't be one already being held + continue; + } + + if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) ) + {//not if invisible + continue; + } + + if ( DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ) <= radiusSquared ) + { + if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL) + && radiusEnts[i]->s.number >= MAX_CLIENTS ) + { + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, radiusEnts[i]->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE ); + } + else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) ) + {//more damage + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 35, 50 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + else + { + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, Q_irand( 15, 30 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + if ( radiusEnts[i]->health <= 0 && radiusEnts[i]->client ) + {//killed them, chance of dismembering + if ( !Q_irand( 0, 1 ) ) + {//bite something off + int hitLoc = HL_WAIST; + if ( g_dismemberment->integer < 11381138 ) + { + hitLoc = Q_irand( HL_WAIST, HL_HAND_LT ); + } + else + { + hitLoc = Q_irand( HL_WAIST, HL_HEAD ); + } + if ( hitLoc == HL_HEAD ) + { + NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else if ( hitLoc == HL_WAIST ) + { + NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + radiusEnts[i]->client->dismembered = false; + //FIXME: the limb should just disappear, cuz I ate it + G_DoDismemberment( radiusEnts[i], radiusEnts[i]->currentOrigin, MOD_SABER, 1000, hitLoc, qtrue ); + } + } + G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); + } + } +} +//------------------------------ +extern gentity_t *TossClientItems( gentity_t *self ); +void Rancor_Attack( float distance, qboolean doCharge, qboolean aimAtBlockedEntity ) +{ + if ( !TIMER_Exists( NPC, "attacking" ) + && TIMER_Done( NPC, "attackDebounce" ) ) + { + if ( NPC->count == 2 && NPC->activator ) + { + } + else if ( NPC->count == 1 && NPC->activator ) + {//holding enemy + if ( (!(NPC->spawnflags&SPF_RANCOR_FASTKILL) ||NPC->activator->s.numberactivator->health > 0 + && Q_irand( 0, 1 ) ) + {//quick bite + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 450 ); + } + else + {//full eat + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 900 ); + //Make victim scream in fright + if ( NPC->activator->health > 0 && NPC->activator->client ) + { + G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 ); + NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + if ( NPC->activator->NPC ) + {//no more thinking for you + TossClientItems( NPC ); + NPC->activator->NPC->nextBStateThink = Q3_INFINITE; + } + } + } + } + else if ( NPC->enemy->health > 0 && doCharge ) + {//charge + if ( !Q_irand( 0, 3 ) ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK5, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 1250 ); + if ( NPC->enemy && NPC->enemy->s.number == 0 ) + {//don't attack the player again for a bit + TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) ); + } + } + else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) ) + {//breath attack + int breathAnim = BOTH_ATTACK4; + gentity_t *checkEnt = NULL; + vec3_t center; + if ( NPC->enemy && NPC->enemy->inuse ) + { + checkEnt = NPC->enemy; + VectorCopy( NPC->enemy->currentOrigin, center ); + } + else if ( NPCInfo->blockedEntity && NPCInfo->blockedEntity->inuse ) + { + checkEnt = NPCInfo->blockedEntity; + //if it has an origin brush, use it... + if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) ) + {//no origin brush, calc center + VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center ); + VectorScale( center, 0.5f, center ); + } + else + {//use origin brush as center + VectorCopy( NPCInfo->blockedEntity->s.origin, center ); + } + } + if ( checkEnt ) + { + float zHeightRelative = center[2]-NPC->currentOrigin[2]; + if ( zHeightRelative >= (128.0f*NPC->s.modelScale[2]) ) + { + breathAnim = BOTH_ATTACK7; + } + else if ( zHeightRelative >= (64.0f*NPC->s.modelScale[2]) ) + { + breathAnim = BOTH_ATTACK6; + } + } + NPC_SetAnim( NPC, SETANIM_BOTH, breathAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + //start effect here + G_PlayEffect( G_EffectIndex( "mrancor/breath" ), NPC->playerModel, NPC->gutBolt, NPC->s.number, NPC->currentOrigin, (NPC->client->ps.legsAnimTimer-500), qfalse ); + TIMER_Set( NPC, "breathAttack", NPC->client->ps.legsAnimTimer-500 ); + G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/chars/rancor/breath_start.wav" ); + NPC->s.loopSound = G_SoundIndex( "sound/chars/rancor/breath_loop.wav" ); + if ( NPC->enemy && NPC->enemy->s.number == 0 ) + {//don't attack the player again for a bit + TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) ); + } + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 1250 ); + vec3_t fwd, yawAng ={0, NPC->client->ps.viewangles[YAW], 0}; + AngleVectors( yawAng, fwd, NULL, NULL ); + VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 150; + NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; + if ( NPC->enemy && NPC->enemy->s.number == 0 ) + {//don't attack the player again for a bit + TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) ); + } + } + } + else if ( !Q_irand(0, 1) + /*&& (NPC->spawnflags&SPF_RANCOR_MUTANT)*/ ) + {//mutant rancor can smash + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 900 ); + //init pos3 for the trace from last hand pos to current hand pos + VectorCopy( NPC->currentOrigin, NPC->pos3 ); + } + else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) + || distance >= NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])-64.0f ) + {//try to grab + int grabAnim = BOTH_ATTACK2; + gentity_t *checkEnt = NULL; + vec3_t center; + if ( (!aimAtBlockedEntity||!NPCInfo->blockedEntity) && NPC->enemy && NPC->enemy->inuse ) + { + checkEnt = NPC->enemy; + VectorCopy( NPC->enemy->currentOrigin, center ); + } + else if ( NPCInfo->blockedEntity && NPCInfo->blockedEntity->inuse ) + { + checkEnt = NPCInfo->blockedEntity; + //if it has an origin brush, use it... + if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) ) + {//no origin brush, calc center + VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center ); + VectorScale( center, 0.5f, center ); + } + else + {//use origin brush as center + VectorCopy( NPCInfo->blockedEntity->s.origin, center ); + } + } + if ( checkEnt ) + { + float zHeightRelative = center[2]-NPC->currentOrigin[2]; + if ( zHeightRelative >= (128.0f*NPC->s.modelScale[2]) ) + { + grabAnim = BOTH_ATTACK11; + } + else if ( zHeightRelative >= (64.0f*NPC->s.modelScale[2]) ) + { + grabAnim = BOTH_ATTACK10; + } + } + NPC_SetAnim( NPC, SETANIM_BOTH, grabAnim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 800 ); + if ( NPC->enemy && NPC->enemy->s.number == 0 ) + {//don't attack the player again for a bit + TIMER_Set( NPC, "attackDebounce", NPC->client->ps.legsAnimTimer + Q_irand( 2000, 4000+((2-g_spskill->integer)*2000) ) ); + } + //init pos3 for the trace from last hand pos to current hand pos + VectorCopy( NPC->currentOrigin, NPC->pos3 ); + } + else + { + //FIXME: back up? + ucmd.forwardmove = -64; + //FIXME: check for walls/ledges? + return; + } + + TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + random() * 200 ); + } + + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + float playerDist; + + if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) + { + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_MELEE1: + Rancor_Smash(); + playerDist = NPC_EntRangeFromBolt( player, NPC->handLBolt ); + if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) ) + { + if ( playerDist < 512 ) + { + CGCam_Shake( 1.0f*playerDist/256, 1000 ); + } + } + else + { + if ( playerDist < 256 ) + { + CGCam_Shake( 1.0f*playerDist/128.0f, 1000 ); + } + } + break; + case BOTH_MELEE2: + Rancor_Bite(); + TIMER_Set( NPC, "attack_dmg2", 450 ); + break; + case BOTH_ATTACK1: + if ( NPC->count == 1 && NPC->activator ) + { + if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL) + && NPC->activator->s.number >= MAX_CLIENTS ) + { + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, NPC->activator->health+1000, (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_PROTECTION), MOD_MELEE ); + } + else if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) )//FIXME: a flag or something would be better + {//more damage + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, Q_irand( 55, 70 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + else + { + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + if ( NPC->activator->health <= 0 ) + {//killed him + if ( g_dismemberment->integer >= 11381138 ) + {//make it look like we bit his head off + NPC->activator->client->dismembered = false; + G_DoDismemberment( NPC->activator, NPC->activator->currentOrigin, MOD_SABER, 1000, HL_HEAD, qtrue ); + } + NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); + } + break; + case BOTH_ATTACK2: + case BOTH_ATTACK10: + case BOTH_ATTACK11: + //try to grab + Rancor_Swing( NPC->handRBolt, qtrue ); + break; + case BOTH_ATTACK3: + if ( NPC->count == 1 && NPC->activator ) + { + //cut in half + if ( NPC->activator->client ) + { + NPC->activator->client->dismembered = false; + G_DoDismemberment( NPC->activator, NPC->enemy->currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); + } + //KILL + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, NPC->enemy->health+1000, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE, HL_NONE ); + if ( NPC->activator->client ) + { + NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + TIMER_Set( NPC, "attack_dmg2", 1350 ); + G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); + } + break; + } + } + else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) ) + { + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_MELEE1: + break; + case BOTH_MELEE2: + Rancor_Bite(); + break; + case BOTH_ATTACK1: + break; + case BOTH_ATTACK2: + break; + case BOTH_ATTACK3: + if ( NPC->count == 1 && NPC->activator ) + {//swallow victim + G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); + //FIXME: sometimes end up with a live one in our mouths? + //just make sure they're dead + if ( NPC->activator->health > 0 ) + { + //cut in half + NPC->activator->client->dismembered = false; + G_DoDismemberment( NPC->activator, NPC->enemy->currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); + //KILL + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, NPC->enemy->health+1000, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE, HL_NONE ); + NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); + } + NPC->count = 2; + TIMER_Set( NPC, "clearGrabbed", 2600 ); + } + break; + } + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); +} + +//---------------------------------- +void Rancor_Combat( void ) +{ + if ( NPC->count ) + {//holding my enemy + NPCInfo->enemyLastSeenTime = level.time; + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL) + && NPC->activator + && NPC->activator->s.number >= MAX_CLIENTS ) + { + Rancor_Attack( 0, qfalse, qfalse ); + } + else if ( NPC->useDebounceTime >= level.time + && NPC->activator ) + {//just sniffing the guy + if ( NPC->useDebounceTime <= level.time + 100 + && NPC->client->ps.legsAnim != BOTH_HOLD_DROP) + {//just about done, drop him + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer+(Q_irand(500,1000)*(3-g_spskill->integer)) ); + } + } + else + { + if ( !NPC->useDebounceTime + && NPC->activator + && NPC->activator->s.number < MAX_CLIENTS ) + {//first time I pick the player, just sniff them + if ( TIMER_Done(NPC,"attacking") ) + {//ready to attack + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_SNIFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->useDebounceTime = level.time + NPC->client->ps.legsAnimTimer + Q_irand( 500, 2000 ); + } + } + else + { + Rancor_Attack( 0, qfalse, qfalse ); + } + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + NPCInfo->goalRadius = NPC->maxs[0]+(MAX_DISTANCE*NPC->s.modelScale[0]); // just get us within combat range + + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS( NPC->enemy ) || UpdateGoal( )) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + + Rancor_Move( qfalse ); + return; + } + + NPCInfo->enemyLastSeenTime = level.time; + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + float distance = Distance( NPC->currentOrigin, NPC->enemy->currentOrigin ); + + qboolean advance = (qboolean)( distance > (NPC->maxs[0]+(MIN_DISTANCE*NPC->s.modelScale[0])) ? qtrue : qfalse ); + qboolean doCharge = qfalse; + + if ( advance ) + {//have to get closer + if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) + && (!NPC->enemy||!NPC->enemy->client) ) + {//don't do breath attack vs. bbrushes + } + else + { + vec3_t yawOnlyAngles = {0, NPC->currentAngles[YAW], 0}; + if ( NPC->enemy->health > 0 + && fabs(distance-(250.0f*NPC->s.modelScale[0])) <= (80.0f*NPC->s.modelScale[0]) + && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, yawOnlyAngles, 30, 30 ) ) + { + int chance = 9; + if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) ) + {//higher chance of doing breath attack + chance = 5-g_spskill->integer; + } + if ( !Q_irand( 0, chance ) ) + {//go for the charge + doCharge = qtrue; + advance = qfalse; + } + } + } + } + + if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + Rancor_Move( 1 ); + } + } + else + { + Rancor_Attack( distance, doCharge, qfalse ); + } +} + +/* +------------------------- +NPC_Rancor_Pain +------------------------- +*/ +void NPC_Rancor_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + qboolean hitByRancor = qfalse; + + if ( self->NPC && self->NPC->ignorePain ) + { + return; + } + if ( !TIMER_Done( self, "breathAttack" ) ) + {//nothing interrupts breath attack + return; + } + + TIMER_Remove( self, "confusionTime" ); + + if ( other&&other->client&&other->client->NPC_class==CLASS_RANCOR ) + { + hitByRancor = qtrue; + } + if ( other + && other->inuse + && other != self->enemy + && !(other->flags&FL_NOTARGET) ) + { + if ( !self->count ) + { + if ( (!other->s.number&&!Q_irand(0,3)) + || !self->enemy + || self->enemy->health == 0 + || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR) + || (!Q_irand(0, 4 ) && DistanceSquared( other->currentOrigin, self->currentOrigin ) < DistanceSquared( self->enemy->currentOrigin, self->currentOrigin )) ) + {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker + //FIXME: if can't nav to my enemy, take this guy if I can nav to him + self->lastEnemy = self->enemy; + G_SetEnemy( self, other ); + if ( self->enemy != self->lastEnemy ) + {//clear this so that we only sniff the player the first time we pick them up + self->useDebounceTime = 0; + } + TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + if ( hitByRancor ) + {//stay mad at this Rancor for 2-5 secs before looking for other enemies + TIMER_Set( self, "rancorInfight", Q_irand( 2000, 5000 ) ); + } + + } + } + } + if ( (hitByRancor|| (self->count==1&&self->activator&&!Q_irand(0,4)) || Q_irand( 0, 200 ) < damage )//hit by rancor, hit while holding live victim, or took a lot of damage + && self->client->ps.legsAnim != BOTH_STAND1TO2 + && TIMER_Done( self, "takingPain" ) ) + { + if ( !Rancor_CheckRoar( self ) ) + { + if ( self->client->ps.legsAnim != BOTH_MELEE1 + && self->client->ps.legsAnim != BOTH_MELEE2 + && self->client->ps.legsAnim != BOTH_ATTACK2 + && self->client->ps.legsAnim != BOTH_ATTACK10 + && self->client->ps.legsAnim != BOTH_ATTACK11 ) + {//cant interrupt one of the big attack anims + /* + if ( self->count != 1 + || other == self->activator + || (self->client->ps.legsAnim != BOTH_ATTACK1&&self->client->ps.legsAnim != BOTH_ATTACK3) ) + */ + {//if going to bite our victim, only victim can interrupt that anim + if ( self->health > 100 || hitByRancor ) + { + TIMER_Remove( self, "attacking" ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( self->count == 1 ) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + TIMER_Set( self, "takingPain", self->client->ps.legsAnimTimer+Q_irand(0, 500*(2-g_spskill->integer)) ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } + } + } + } + //let go + /* + if ( !Q_irand( 0, 3 ) && self->count == 1 ) + { + Rancor_DropVictim( self ); + } + */ + } +} + +void Rancor_CheckDropVictim( void ) +{ + if ( (NPC->spawnflags&SPF_RANCOR_FASTKILL) + && NPC->activator->s.number >= MAX_CLIENTS ) + { + return; + } + vec3_t mins={NPC->activator->mins[0]-1,NPC->activator->mins[1]-1,0}; + vec3_t maxs={NPC->activator->maxs[0]+1,NPC->activator->maxs[1]+1,1}; + vec3_t start={NPC->activator->currentOrigin[0],NPC->activator->currentOrigin[1],NPC->activator->absmin[2]}; + vec3_t end={NPC->activator->currentOrigin[0],NPC->activator->currentOrigin[1],NPC->activator->absmax[2]-1}; + trace_t trace; + gi.trace( &trace, start, mins, maxs, end, NPC->activator->s.number, NPC->activator->clipmask ); + // First, if the held person is outside the world, eat them instead! + extern int CM_LeafCluster( int ); + extern int CM_PointLeafnum( const vec3_t p ); + if( CM_LeafCluster( CM_PointLeafnum( NPC->activator->currentOrigin ) ) == -1 ) + { + Rancor_Attack( 0, qfalse, qfalse ); + } + else if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0f ) + { + Rancor_DropVictim( NPC ); + } +} + +qboolean Rancor_AttackBBrush( void ) +{ + trace_t trace; + vec3_t center; + vec3_t dir2Brush, end; + float checkDist = 64.0f;//32.0f; + + if ( VectorCompare( NPCInfo->blockedEntity->s.origin, vec3_origin ) ) + {//no origin brush, calc center + VectorAdd( NPCInfo->blockedEntity->mins, NPCInfo->blockedEntity->maxs, center ); + VectorScale( center, 0.5f, center ); + } + else + { + VectorCopy( NPCInfo->blockedEntity->s.origin, center ); + } + if ( NAVDEBUG_showCollision ) + { + CG_DrawEdge( NPC->currentOrigin, center, EDGE_IMPACT_POSSIBLE ); + } + center[2] = NPC->currentOrigin[2];//we can't fly, so let's ignore z diff + NPC_FacePosition( center, qfalse ); + //see if we're close to it + VectorSubtract( center, NPC->currentOrigin, dir2Brush ); + float brushSize = ((NPCInfo->blockedEntity->maxs[0] - NPCInfo->blockedEntity->mins[0])*0.5f+(NPCInfo->blockedEntity->maxs[1] - NPCInfo->blockedEntity->mins[1])*0.5f) * 0.5f; + float dist2Brush = VectorNormalize( dir2Brush )-(NPC->maxs[0])-brushSize; + if ( dist2Brush < (MIN_DISTANCE*NPC->s.modelScale[0]) ) + {//close enough to just hit it + trace.fraction = 0.0f; + trace.entityNum = NPCInfo->blockedEntity->s.number; + } + else + { + VectorMA( NPC->currentOrigin, checkDist, dir2Brush, end ); + gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid || trace.startsolid ) + {//wtf? + NPCInfo->blockedEntity = NULL; + return qfalse; + } + } + if ( trace.fraction >= 1.0f //too far away + || trace.entityNum != NPCInfo->blockedEntity->s.number )//OR blocked by something else + {//keep moving towards it + ucmd.buttons &= ~BUTTON_WALKING; // Unset from MoveToGoal() + STEER::Activate(NPC); + STEER::Seek(NPC, center); + STEER::AvoidCollisions(NPC); + STEER::DeActivate(NPC, &ucmd); + /* + VectorCopy( dir2Brush, NPC->client->ps.moveDir ); + if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed ) + { + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + ucmd.buttons |= BUTTON_WALKING; + } + */ + //NPCInfo->enemyLastSeenTime = level.time; + //let the function that called us know that we called NAV ourselves + } + else if ( trace.entityNum == NPCInfo->blockedEntity->s.number ) + {//close enough, smash it! + Rancor_Attack( (trace.fraction*checkDist), qfalse, qtrue );//FIXME: maybe charge at it, smash through? + TIMER_Remove( NPC, "attackDebounce" );//don't wait on these + NPCInfo->enemyLastSeenTime = level.time; + } + else + { + //Com_Printf( S_COLOR_RED"RANCOR cannot reach intended breakable %s, blocked by %s\n", NPC->blockedEntity->targetname, g_entities[trace.entityNum].classname ); + if ( G_EntIsBreakable( trace.entityNum, NPC ) ) + {//oh, well, smash that, then + //G_SetEnemy( NPC, &g_entities[trace.entityNum] ); + gentity_t* prevblockedEnt = NPCInfo->blockedEntity; + NPCInfo->blockedEntity = &g_entities[trace.entityNum]; + Rancor_Attack( (trace.fraction*checkDist), qfalse, qtrue );//FIXME: maybe charge at it, smash through? + TIMER_Remove( NPC, "attackDebounce" );//don't wait on these + NPCInfo->enemyLastSeenTime = level.time; + NPCInfo->blockedEntity = prevblockedEnt; + } + else + { + NPCInfo->blockedEntity = NULL; + return qfalse; + } + } + return qtrue; +} + +void Rancor_FireBreathAttack( void ) +{ + int damage = Q_irand( 10, 15 ); + trace_t tr; + gentity_t *traceEnt = NULL; + mdxaBone_t boltMatrix; + vec3_t start, end, dir, traceMins = {-4, -4, -4}, traceMaxs = {4, 4, 4}; + vec3_t rancAngles = {0,NPC->client->ps.viewangles[YAW],0}; + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, NPC->gutBolt, + &boltMatrix, rancAngles, NPC->currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, start ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Z, dir ); + VectorMA( start, 512, dir, end ); + + gi.trace( &tr, start, traceMins, traceMaxs, end, NPC->s.number, MASK_SHOT ); + + traceEnt = &g_entities[tr.entityNum]; + if ( tr.entityNum < ENTITYNUM_WORLD + && traceEnt->takedamage + && traceEnt->client ) + {//breath attack only does damage to living things + G_Damage( traceEnt, NPC, NPC, dir, tr.endpos, damage*2, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC|DAMAGE_IGNORE_TEAM, MOD_LAVA, HL_NONE ); + } + if ( tr.fraction < 1.0f ) + {//hit something, do radius damage + G_RadiusDamage( tr.endpos, NPC, damage, 250, NPC, MOD_LAVA ); + } +} + +void Rancor_CheckAnimDamage( void ) +{ + if ( NPC->client->ps.legsAnim == BOTH_ATTACK2 + || NPC->client->ps.legsAnim == BOTH_ATTACK10 + || NPC->client->ps.legsAnim == BOTH_ATTACK11 ) + { + if ( NPC->client->ps.legsAnimTimer >= 1200 && NPC->client->ps.legsAnimTimer <= 1350 ) + { + if ( Q_irand( 0, 2 ) ) + { + Rancor_Swing( NPC->handRBolt, qfalse ); + } + else + { + Rancor_Swing( NPC->handRBolt, qtrue ); + } + } + else if ( NPC->client->ps.legsAnimTimer >= 1100 && NPC->client->ps.legsAnimTimer <= 1550 ) + { + Rancor_Swing( NPC->handRBolt, qtrue ); + } + } + else if ( NPC->client->ps.legsAnim == BOTH_ATTACK5 ) + { + if ( NPC->client->ps.legsAnimTimer >= 750 && NPC->client->ps.legsAnimTimer <= 1300 ) + { + Rancor_Swing( NPC->handLBolt, qfalse ); + } + else if ( NPC->client->ps.legsAnimTimer >= 1700 && NPC->client->ps.legsAnimTimer <= 2300 ) + { + Rancor_Swing( NPC->handRBolt, qfalse ); + } + } +} +/* +------------------------- +NPC_BSRancor_Default +------------------------- +*/ +void NPC_BSRancor_Default( void ) +{ + AddSightEvent( NPC, NPC->currentOrigin, 1024, AEL_DANGER_GREAT, 50 ); + + if (NPCInfo->blockedEntity && TIMER_Done(NPC, "blockedEntityIgnore")) + { + if (!TIMER_Exists(NPC, "blockedEntityTimeOut")) + { + TIMER_Set(NPC, "blockedEntityTimeOut", 5000); + } + else if (TIMER_Done(NPC, "blockedEntityTimeOut")) + { + TIMER_Remove(NPC, "blockedEntityTimeOut"); + TIMER_Set(NPC, "blockedEntityIgnore", 25000); + NPCInfo->blockedEntity = NULL; + } + } + else + { + TIMER_Remove(NPC, "blockedEntityTimeOut"); + TIMER_Remove(NPC, "blockedEntityIgnore"); + } + + Rancor_CheckAnimDamage(); + + if ( !TIMER_Done( NPC, "breathAttack" ) ) + {//doing breath attack, just do damage + Rancor_FireBreathAttack(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else if ( NPC->client->ps.legsAnim == BOTH_ATTACK4 + || NPC->client->ps.legsAnim == BOTH_ATTACK6 + || NPC->client->ps.legsAnim == BOTH_ATTACK7 ) + { + G_StopEffect( G_EffectIndex( "mrancor/breath" ), NPC->playerModel, NPC->gutBolt, NPC->s.number ); + NPC->s.loopSound = 0; + } + + if ( TIMER_Done2( NPC, "clearGrabbed", qtrue ) ) + { + Rancor_DropVictim( NPC ); + } + else if ( (NPC->client->ps.legsAnim == BOTH_PAIN2 || NPC->client->ps.legsAnim == BOTH_HOLD_DROP ) + && NPC->count == 1 + && NPC->activator ) + { + Rancor_CheckDropVictim(); + } + if ( !TIMER_Done( NPC, "rageTime" ) ) + {//do nothing but roar first time we see an enemy + AddSoundEvent( NPC, NPC->currentOrigin, 1024, AEL_DANGER_GREAT, qfalse, qfalse ); + NPC_FaceEnemy( qtrue ); + return; + } + + if ( NPCInfo->localState == LSTATE_WAITING + && TIMER_Done2( NPC, "takingPain", qtrue ) ) + {//was not doing anything because we were taking pain, but pain is done now, so clear it... + NPCInfo->localState = LSTATE_CLEAR; + } + + if ( !TIMER_Done( NPC, "confusionTime" ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( NPC->enemy ) + { + if ( NPC->enemy->client //enemy is a client + && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught + && NPC->enemy->enemy != NPC//enemy's enemy is not me + && (!NPC->enemy->enemy || !NPC->enemy->enemy->client || NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR) )//enemy's enemy is not a client or is not a rancor (which is as scary as me anyway) + {//they should be scared of ME and no-one else + G_SetEnemy( NPC->enemy, NPC ); + } + if ( TIMER_Done(NPC,"angrynoise") ) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/rancor/anger%d.wav", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); + } + else + { + AddSoundEvent( NPC, NPC->currentOrigin, 512, AEL_DANGER_GREAT, qfalse, qfalse ); + } + if ( NPC->count == 2 && NPC->client->ps.legsAnim == BOTH_ATTACK3 ) + {//we're still chewing our enemy up + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while + if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_RANCOR ) + {//got mad at another Rancor, look for a valid enemy + if ( TIMER_Done( NPC, "rancorInfight" ) ) + { + NPC_CheckEnemyExt( qtrue ); + } + } + else if ( !NPC->count ) + { + if ( NPCInfo->blockedEntity ) + {//something in our way + if ( !NPCInfo->blockedEntity->inuse ) + {//was destroyed + NPCInfo->blockedEntity = NULL; + } + else + { + //a breakable? + if ( G_EntIsBreakable( NPCInfo->blockedEntity->s.number, NPC ) ) + {//breakable brush + if ( !Rancor_AttackBBrush() ) + {//didn't move inside that func, so call move here...? + Rancor_Move( 1 ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + {//if it's a client and in our way, get mad at it! + if ( NPCInfo->blockedEntity != NPC->enemy + && NPCInfo->blockedEntity->client + && NPC_ValidEnemy( NPCInfo->blockedEntity ) + && !Q_irand( 0, 9 ) ) + { + G_SetEnemy( NPC, NPCInfo->blockedEntity ); + //look again in 2-5 secs + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); + NPCInfo->blockedEntity = NULL; + } + } + } + } + if ( NPC_ValidEnemy( NPC->enemy ) == qfalse ) + { + TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now + if ( !NPC->enemy->inuse + || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) + || (NPC->spawnflags&SPF_RANCOR_FASTKILL) )//don't linger on dead bodies + {//it's been a while since the enemy died, or enemy is completely gone, get bored with him + if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) + && player && player->health >= 0 ) + {//all else failing, always go after the player + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, player ); + if ( NPC->enemy != NPC->lastEnemy ) + {//clear this so that we only sniff the player the first time we pick them up + NPC->useDebounceTime = 0; + } + } + else + { + NPC->enemy = NULL; + Rancor_Patrol(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) + { + gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? + NPC->enemy = NULL; + gentity_t *newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); + NPC->enemy = sav_enemy; + if ( newEnemy && newEnemy != sav_enemy ) + {//picked up a new enemy! + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, newEnemy ); + if ( NPC->enemy != NPC->lastEnemy ) + {//clear this so that we only sniff the player the first time we pick them up + NPC->useDebounceTime = 0; + } + //hold this one for at least 5-15 seconds + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + } + else + {//look again in 2-5 secs + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); + } + } + } + Rancor_Combat(); + if ( TIMER_Done( NPC, "attacking" ) + && TIMER_Done( NPC, "takingpain" ) + && TIMER_Done( NPC, "confusionDebounce" ) + && NPCInfo->localState == LSTATE_CLEAR + && !NPC->count ) + {//not busy + if ( !ucmd.forwardmove + && !ucmd.rightmove + && VectorCompare( NPC->client->ps.moveDir, vec3_origin ) ) + {//not moving + if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ) + {//haven't seen an enemy in a while + if ( !Q_irand( 0, 20 ) ) + { + if ( Q_irand( 0, 1 ) ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + TIMER_Set( NPC, "confusionTime", NPC->client->ps.legsAnimTimer ); + TIMER_Set( NPC, "confusionDebounce", NPC->client->ps.legsAnimTimer+Q_irand( 4000, 8000 ) ); + } + } + } + } + } + else + { + if ( TIMER_Done(NPC,"idlenoise") ) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/rancor/snort_%d.wav", Q_irand(1, 4)) ); + + TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); + AddSoundEvent( NPC, NPC->currentOrigin, 384, AEL_DANGER, qfalse, qfalse ); + } + if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Rancor_Patrol(); + if ( !NPC->enemy && NPC->wait ) + {//we've been mad before and can't find an enemy + if ( (NPC->spawnflags&SPF_RANCOR_MUTANT) + && player && player->health >= 0 ) + {//all else failing, always go after the player + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, player ); + if ( NPC->enemy != NPC->lastEnemy ) + {//clear this so that we only sniff the player the first time we pick them up + NPC->useDebounceTime = 0; + } + } + } + } + else + { + Rancor_Idle(); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/code/game/AI_Remote.cpp b/code/game/AI_Remote.cpp new file mode 100644 index 0000000..99ad29a --- /dev/null +++ b/code/game/AI_Remote.cpp @@ -0,0 +1,389 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" + +gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse ); +void Remote_Strafe( void ); + +#define VELOCITY_DECAY 0.85f + + +//Local state enums +enum +{ + LSTATE_NONE = 0, +}; + +void Remote_Idle( void ); + +void NPC_Remote_Precache(void) +{ + G_SoundIndex("sound/chars/remote/misc/fire.wav"); + G_SoundIndex( "sound/chars/remote/misc/hiss.wav"); + G_EffectIndex( "env/small_explode"); +} + +/* +------------------------- +NPC_Remote_Pain +------------------------- +*/ +void NPC_Remote_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + SaveNPCGlobals(); + SetNPCGlobals( self ); + Remote_Strafe(); + RestoreNPCGlobals(); + + NPC_Pain( self, inflictor, other, point, damage, mod ); +} + +/* +------------------------- +Remote_MaintainHeight +------------------------- +*/ +void Remote_MaintainHeight( void ) +{ + float dif; + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + // If we have an enemy, we should try to hover at or a little below enemy eye level + if ( NPC->enemy ) + { + if (TIMER_Done( NPC, "heightChange")) + { + TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); + + // Find the height difference + dif = (NPC->enemy->currentOrigin[2] + Q_irand( 0, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 2 ) + { + if ( fabs( dif ) > 24 ) + { + dif = ( dif < 0 ? -24 : 24 ); + } + dif *= 10; + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + NPC->fx_time = level.time; + G_Sound( NPC, G_SoundIndex("sound/chars/remote/misc/hiss.wav")); + } + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + dif = ( dif < 0 ? -24 : 24 ); + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +#define REMOTE_STRAFE_VEL 256 +#define REMOTE_STRAFE_DIS 200 +#define REMOTE_UPWARD_PUSH 32 + +/* +------------------------- +Remote_Strafe +------------------------- +*/ +void Remote_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->currentOrigin, REMOTE_STRAFE_DIS * dir, right, end ); + + gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, REMOTE_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + G_Sound( NPC, G_SoundIndex("sound/chars/remote/misc/hiss.wav")); + + // Add a slight upward push + NPC->client->ps.velocity[2] += REMOTE_UPWARD_PUSH; + + // Set the strafe start time so we can do a controlled roll + NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +#define REMOTE_FORWARD_BASE_SPEED 10 +#define REMOTE_FORWARD_MULTIPLIER 5 + +/* +------------------------- +Remote_Hunt +------------------------- +*/ +void Remote_Hunt( qboolean visible, qboolean advance, qboolean retreat ) +{ + float distance, speed; + vec3_t forward; + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Remote_Strafe(); + return; + } + } + + //If we don't want to advance, stop here + if ( advance == qfalse && visible == qtrue ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + NPC_MoveToGoal(qtrue); + return; + + } + else + { + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = REMOTE_FORWARD_BASE_SPEED + REMOTE_FORWARD_MULTIPLIER * g_spskill->integer; + if ( retreat == qtrue ) + { + speed *= -1; + } + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + + +/* +------------------------- +Remote_Fire +------------------------- +*/ +void Remote_Fire (void) +{ + vec3_t delta1, enemy_org1, muzzle1; + vec3_t angleToEnemy1; + static vec3_t forward, vright, up; + static vec3_t muzzle; + gentity_t *missile; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 ); + VectorCopy( NPC->currentOrigin, muzzle1 ); + + VectorSubtract (enemy_org1, muzzle1, delta1); + + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + + missile = CreateMissile( NPC->currentOrigin, forward, 1000, 10000, NPC ); + + G_PlayEffect( "bryar/muzzle_flash", NPC->currentOrigin, forward ); + + missile->classname = "briar"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->damage = 10; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +Remote_Ranged +------------------------- +*/ +void Remote_Ranged( qboolean visible, qboolean advance, qboolean retreat ) +{ + + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) ); + Remote_Fire(); + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Remote_Hunt( visible, advance, retreat ); + } +} + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 80 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +/* +------------------------- +Remote_Attack +------------------------- +*/ +void Remote_Attack( void ) +{ + if ( TIMER_Done(NPC,"spin") ) + { + TIMER_Set( NPC, "spin", Q_irand( 250, 1500 ) ); + NPCInfo->desiredYaw += Q_irand( -200, 200 ); + } + // Always keep a good height off the ground + Remote_MaintainHeight(); + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt() == qfalse ) + { + Remote_Idle(); + return; + } + + // Rate our distance to the target, and our visibilty + float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); +// distance_e distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; + qboolean visible = NPC_ClearLOS( NPC->enemy ); + float idealDist = MIN_DISTANCE_SQR+(MIN_DISTANCE_SQR*Q_flrand( 0, 1 )); + qboolean advance = (qboolean)(distance > idealDist*1.25); + qboolean retreat = (qboolean)(distance < idealDist*0.75); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Remote_Hunt( visible, advance, retreat ); + return; + } + } + + Remote_Ranged( visible, advance, retreat ); + +} + +/* +------------------------- +Remote_Idle +------------------------- +*/ +void Remote_Idle( void ) +{ + Remote_MaintainHeight(); + + NPC_BSIdle(); +} + +/* +------------------------- +Remote_Patrol +------------------------- +*/ +void Remote_Patrol( void ) +{ + Remote_MaintainHeight(); + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( UpdateGoal() ) + { + //start loop sound once we move + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + + +/* +------------------------- +NPC_BSRemote_Default +------------------------- +*/ +void NPC_BSRemote_Default( void ) +{ + if ( NPC->enemy ) + { + Remote_Attack(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Remote_Patrol(); + } + else + { + Remote_Idle(); + } +} \ No newline at end of file diff --git a/code/game/AI_RocketTrooper.cpp b/code/game/AI_RocketTrooper.cpp new file mode 100644 index 0000000..d407363 --- /dev/null +++ b/code/game/AI_RocketTrooper.cpp @@ -0,0 +1,912 @@ +#include "g_headers.h" +#include "b_local.h" +//#include "g_nav.h" +//#include "anims.h" +//#include "wp_saber.h" +extern qboolean PM_FlippingAnim( int anim ); +extern void NPC_BSST_Patrol( void ); + +extern void RT_FlyStart( gentity_t *self ); +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); + +#define VELOCITY_DECAY 0.7f + +#define RT_FLYING_STRAFE_VEL 60 +#define RT_FLYING_STRAFE_DIS 200 +#define RT_FLYING_UPWARD_PUSH 150 + +#define RT_FLYING_FORWARD_BASE_SPEED 50 +#define RT_FLYING_FORWARD_MULTIPLIER 10 + +void RT_Precache( void ) +{ + G_SoundIndex( "sound/chars/boba/bf_blast-off.wav" ); + G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" ); + G_SoundIndex( "sound/chars/boba/bf_land.wav" ); + G_EffectIndex( "rockettrooper/flameNEW" ); + G_EffectIndex( "rockettrooper/light_cone" );//extern this? At least use a different one +} + +extern void NPC_BehaviorSet_Stormtrooper( int bState ); +void RT_RunStormtrooperAI( void ) +{ + int bState; + //Execute our bState + if(NPCInfo->tempBehavior) + {//Overrides normal behavior until cleared + bState = NPCInfo->tempBehavior; + } + else + { + if(!NPCInfo->behaviorState) + NPCInfo->behaviorState = NPCInfo->defaultBehavior; + + bState = NPCInfo->behaviorState; + } + NPC_BehaviorSet_Stormtrooper( bState ); +} + +void RT_FireDecide( void ) +{ + qboolean enemyLOS = qfalse; + qboolean enemyCS = qfalse; + qboolean enemyInFOV = qfalse; + //qboolean move = qtrue; + qboolean faceEnemy = qfalse; + qboolean shoot = qfalse; + qboolean hitAlly = qfalse; + vec3_t impactPos; + float enemyDist; + + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE + && NPC->client->ps.forceJumpZStart + && !PM_FlippingAnim( NPC->client->ps.legsAnim ) + && !Q_irand( 0, 10 ) ) + {//take off + RT_FlyStart( NPC ); + } + + if ( !NPC->enemy ) + { + return; + } + + VectorClear( impactPos ); + enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + + vec3_t enemyDir, shootDir; + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, enemyDir ); + VectorNormalize( enemyDir ); + AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL ); + float dot = DotProduct( enemyDir, shootDir ); + if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 ) + {//enemy is in front of me or they're very close and not behind me + enemyInFOV = qtrue; + } + + if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128 + {//enemy within 128 + if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) && + (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//shooting an explosive, but enemy too close, switch to primary fire + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not... + } + } + + //can we see our target? + if ( TIMER_Done( NPC, "nextAttackDelay" ) && TIMER_Done( NPC, "flameTime" ) ) + { + if ( NPC_ClearLOS( NPC->enemy ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + enemyLOS = qtrue; + + if ( NPC->client->ps.weapon == WP_NONE ) + { + enemyCS = qfalse;//not true, but should stop us from firing + } + else + {//can we shoot our target? + if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER + || (NPC->client->ps.weapon == WP_CONCUSSION && !(NPCInfo->scriptFlags&SCF_ALT_FIRE)) + || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128 + { + enemyCS = qfalse;//not true, but should stop us from firing + hitAlly = qtrue;//us! + //FIXME: if too close, run away! + } + else if ( enemyInFOV ) + {//if enemy is FOV, go ahead and check for shooting + int hit = NPC_ShotEntity( NPC->enemy, impactPos ); + gentity_t *hitEnt = &g_entities[hit]; + + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage && ((hitEnt->svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) ) + {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway + enemyCS = qtrue; + //NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + else + {//Hmm, have to get around this bastard + //NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) + {//would hit an ally, don't fire!!! + hitAlly = qtrue; + } + else + {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire + } + } + } + else + { + enemyCS = qfalse;//not true, but should stop us from firing + } + } + } + else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + //NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + + if ( NPC->client->ps.weapon == WP_NONE ) + { + faceEnemy = qfalse; + shoot = qfalse; + } + else + { + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + if ( enemyCS ) + { + shoot = qtrue; + } + } + + if ( !enemyCS ) + {//if have a clear shot, always try + //See if we should continue to fire on their last position + //!TIMER_Done( NPC, "stick" ) || + if ( !hitAlly //we're not going to hit an ally + && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS? + && NPCInfo->enemyLastSeenTime > 0 )//we've seen the enemy + { + if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )//we have seem the enemy in the last 10 seconds + { + if ( !Q_irand( 0, 10 ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + qboolean tooClose = qfalse; + qboolean tooFar = qfalse; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + if ( VectorCompare( impactPos, vec3_origin ) ) + {//never checked ShotEntity this frame, so must do a trace... + trace_t tr; + //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; + vec3_t forward, end; + AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 8192, forward, end ); + gi.trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + VectorCopy( tr.endpos, impactPos ); + } + + //see if impact would be too close to me + float distThreshold = 16384/*128*128*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 65536/*256*256*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 65536/*256*256*/; + } + break; + case WP_CONCUSSION: + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + { + distThreshold = 65536/*256*256*/; + } + break; + default: + break; + } + + float dist = DistanceSquared( impactPos, muzzle ); + + if ( dist < distThreshold ) + {//impact would be too close to me + tooClose = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 || + (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 )) + {//we've haven't seen them in the last 5 seconds + //see if it's too far from where he is + distThreshold = 65536/*256*256*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 262144/*512*512*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 262144/*512*512*/; + } + break; + case WP_CONCUSSION: + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + { + distThreshold = 262144/*512*512*/; + } + break; + default: + break; + } + dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation ); + if ( dist > distThreshold ) + {//impact would be too far from enemy + tooFar = qtrue; + } + } + + if ( !tooClose && !tooFar ) + {//okay too shoot at last pos + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + VectorNormalize( dir ); + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot = qtrue; + faceEnemy = qfalse; + } + } + } + } + } + + //FIXME: don't shoot right away! + if ( NPC->client->fireDelay ) + { + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER + || (NPC->s.weapon == WP_CONCUSSION&&!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) ) + { + if ( !enemyLOS || !enemyCS ) + {//cancel it + NPC->client->fireDelay = 0; + } + else + {//delay our next attempt + TIMER_Set( NPC, "nextAttackDelay", Q_irand( 1000, 3000 ) );//FIXME: base on g_spskill + } + } + } + else if ( shoot ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "nextAttackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + //NASTY + int altChance = 6;//FIXME: base on g_spskill + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER ) + { + if ( (ucmd.buttons&BUTTON_ATTACK) + && !Q_irand( 0, altChance ) ) + {//every now and then, shoot a homing rocket + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + NPC->client->fireDelay = Q_irand( 1000, 3000 );//FIXME: base on g_spskill + } + } + else if ( NPC->s.weapon == WP_CONCUSSION ) + { + if ( (ucmd.buttons&BUTTON_ATTACK) + && Q_irand( 0, altChance*5 ) ) + {//fire the beam shot + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + TIMER_Set( NPC, "nextAttackDelay", Q_irand( 1500, 2500 ) );//FIXME: base on g_spskill + } + else + {//fire the rocket-like shot + TIMER_Set( NPC, "nextAttackDelay", Q_irand( 3000, 5000 ) );//FIXME: base on g_spskill + } + } + } + } + } +} + +//===================================================================================== +//FLYING behavior +//===================================================================================== +qboolean RT_Flying( gentity_t *self ) +{ + return ((qboolean)(self->client->moveType==MT_FLYSWIM)); +} + +void RT_FlyStart( gentity_t *self ) +{//switch to seeker AI for a while + if ( TIMER_Done( self, "jetRecharge" ) + && !RT_Flying( self ) ) + { + self->client->ps.gravity = 0; + self->svFlags |= SVF_CUSTOM_GRAVITY; + self->client->moveType = MT_FLYSWIM; + //Inform NPC_HandleAIFlags we want to fly + self->NPC->aiFlags |= NPCAI_FLY; + self->lastInAirTime = level.time; + + //start jet effect + self->client->jetPackTime = Q3_INFINITE; + if ( self->genericBolt1 != -1 ) + { + G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), self->playerModel, self->genericBolt1, self->s.number, self->currentOrigin, qtrue, qtrue ); + } + if ( self->genericBolt2 != -1 ) + { + G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), self->playerModel, self->genericBolt2, self->s.number, self->currentOrigin, qtrue, qtrue ); + } + + //take-off sound + G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_blast-off.wav" ); + //jet loop sound + self->s.loopSound = G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" ); + if ( self->NPC ) + { + self->count = Q3_INFINITE; // SEEKER shot ammo count + } + } +} + +void RT_FlyStop( gentity_t *self ) +{ + self->client->ps.gravity = g_gravity->value; + self->svFlags &= ~SVF_CUSTOM_GRAVITY; + self->client->moveType = MT_RUNJUMP; + //Stop the effect + self->client->jetPackTime = 0; + if ( self->genericBolt1 != -1 ) + { + G_StopEffect("rockettrooper/flameNEW", self->playerModel, self->genericBolt1, self->s.number ); + } + if ( self->genericBolt2 != -1 ) + { + G_StopEffect("rockettrooper/flameNEW", self->playerModel, self->genericBolt2, self->s.number ); + } + //stop jet loop sound + self->s.loopSound = 0; + G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/boba/bf_land.wav" ); + + if ( self->NPC ) + { + self->count = 0; // SEEKER shot ammo count + TIMER_Set( self, "jetRecharge", Q_irand( 1000, 5000 ) ); + TIMER_Set( self, "jumpChaseDebounce", Q_irand( 500, 2000 ) ); + } +} + +void RT_JetPackEffect( int duration ) +{ + if ( NPC->genericBolt1 != -1 ) + { + G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), NPC->playerModel, NPC->genericBolt1, NPC->s.number, NPC->currentOrigin, duration, qtrue ); + } + if ( NPC->genericBolt2 != -1 ) + { + G_PlayEffect( G_EffectIndex( "rockettrooper/flameNEW" ), NPC->playerModel, NPC->genericBolt2, NPC->s.number, NPC->currentOrigin, duration, qtrue ); + } + + //take-off sound + G_SoundOnEnt( NPC, CHAN_ITEM, "sound/chars/boba/bf_blast-off.wav" ); +} + +void RT_Flying_ApplyFriction( float frictionScale ) +{ + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY;///frictionScale; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY;///frictionScale; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +void RT_Flying_MaintainHeight( void ) +{ + float dif = 0; + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + if ( NPC->forcePushTime > level.time ) + {//if being pushed, we don't have control over our movement + return; + } + + if ( (NPC->client->ps.pm_flags&PMF_TIME_KNOCKBACK) ) + {//don't slow down for a bit + if ( NPC->client->ps.pm_time > 0 ) + { + VectorScale( NPC->client->ps.velocity, 0.9f, NPC->client->ps.velocity ); + return; + } + } + + /* + if ( (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) ) + { + RT_Flying_ApplyFriction( 3.0f ); + return; + } + */ + // If we have an enemy, we should try to hover at or a little below enemy eye level + if ( NPC->enemy + && (!Q3_TaskIDPending( NPC, TID_MOVE_NAV ) || !NPCInfo->goalEntity ) ) + { + if (TIMER_Done( NPC, "heightChange" )) + { + TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); + + float enemyZHeight = NPC->enemy->currentOrigin[2]; + if ( NPC->enemy->client + && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE + && (NPC->enemy->client->ps.forcePowersActive&(1<enemy->client->ps.forceJumpZStart; + } + + // Find the height difference + dif = (enemyZHeight + Q_flrand( NPC->enemy->maxs[2]/2, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2]; + + float difFactor = 10.0f; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 2*difFactor ) + { + if ( fabs( dif ) > 20*difFactor ) + { + dif = ( dif < 0 ? -20*difFactor : 20*difFactor ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + NPC->client->ps.velocity[2] *= Q_flrand( 0.85f, 1.25f ); + } + else + {//don't get too far away from height of enemy... + float enemyZHeight = NPC->enemy->currentOrigin[2]; + if ( NPC->enemy->client + && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE + && (NPC->enemy->client->ps.forcePowersActive&(1<enemy->client->ps.forceJumpZStart; + } + dif = NPC->currentOrigin[2] - (enemyZHeight+64); + float maxHeight = 200; + float hDist = DistanceHorizontal( NPC->enemy->currentOrigin, NPC->currentOrigin ); + if ( hDist < 512 ) + { + maxHeight *= hDist/512; + } + if ( dif > maxHeight ) + { + if ( NPC->client->ps.velocity[2] > 0 )//FIXME: or: we can't see him anymore + {//slow down + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + else + {//start coming back down + NPC->client->ps.velocity[2] -= 4; + } + } + else if ( dif < -200 && NPC->client->ps.velocity[2] < 0 )//we're way below him + { + if ( NPC->client->ps.velocity[2] < 0 )//FIXME: or: we can't see him anymore + {//slow down + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) > -2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + else + {//start going back up + NPC->client->ps.velocity[2] += 4; + } + } + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; + } + else if ( VectorCompare( NPC->pos1, vec3_origin ) ) + {//have a starting position as a reference point + dif = NPC->pos1[2] - NPC->currentOrigin[2]; + } + + if ( fabs( dif ) > 24 ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + + // Apply friction + RT_Flying_ApplyFriction( 1.0f ); +} + +void RT_Flying_Strafe( void ) +{ + int side; + vec3_t end, right, dir; + trace_t tr; + + if ( random() > 0.7f + || !NPC->enemy + || !NPC->enemy->client ) + { + // Do a regular style strafe + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonably valid + side = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->currentOrigin, RT_FLYING_STRAFE_DIS * side, right, end ); + + gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + float vel = RT_FLYING_STRAFE_VEL+Q_flrand(-20,20); + VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity ); + if ( !Q_irand( 0, 3 ) ) + { + // Add a slight upward push + float upPush = RT_FLYING_UPWARD_PUSH; + if ( NPC->client->ps.velocity[2] < 300 ) + { + if ( NPC->client->ps.velocity[2] < 300+upPush ) + { + NPC->client->ps.velocity[2] += upPush; + } + else + { + NPC->client->ps.velocity[2] = 300; + } + } + } + +// NPCInfo->standTime = level.time + 1000 + random() * 500; // Original +// NPCInfo->standTime = level.time + 2000 + random() * 500; // Revision 1 + NPCInfo->standTime = level.time + 1500 + random() * 500; // Revision 2 + } + } + else + { + // Do a strafe to try and keep on the side of their enemy + AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL ); + + // Pick a random side + side = ( rand() & 1 ) ? -1 : 1; + float stDis = RT_FLYING_STRAFE_DIS*2.0f; + VectorMA( NPC->enemy->currentOrigin, stDis * side, right, end ); + + // then add a very small bit of random in front of/behind the player action + VectorMA( end, crandom() * 25, dir, end ); + + gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + float vel = (RT_FLYING_STRAFE_VEL*4)+Q_flrand(-20,20); + VectorSubtract( tr.endpos, NPC->currentOrigin, dir ); + dir[2] *= 0.25; // do less upward change + float dis = VectorNormalize( dir ); + if ( dis > vel ) + { + dis = vel; + } + // Try to move the desired enemy side + VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity ); + + if ( !Q_irand( 0, 3 ) ) + { + float upPush = RT_FLYING_UPWARD_PUSH; + // Add a slight upward push + if ( NPC->client->ps.velocity[2] < 300 ) + { + if ( NPC->client->ps.velocity[2] < 300+upPush ) + { + NPC->client->ps.velocity[2] += upPush; + } + else + { + NPC->client->ps.velocity[2] = 300; + } + } + else if ( NPC->client->ps.velocity[2] > 300 ) + { + NPC->client->ps.velocity[2] = 300; + } + } + +// NPCInfo->standTime = level.time + 2500 + random() * 500; // Original +// NPCInfo->standTime = level.time + 5000 + random() * 500; // Revision 1 + NPCInfo->standTime = level.time + 3500 + random() * 500; // Revision 2 + } + } +} + +void RT_Flying_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + if ( NPC->forcePushTime >= level.time ) + //|| (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) ) + {//if being pushed, we don't have control over our movement + NPC->delay = 0; + return; + } + NPC_FaceEnemy( qtrue ); + + // If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + NPC->delay = 0; + RT_Flying_Strafe(); + return; + } + } + + // If we don't want to advance, stop here + if ( advance ) + { + // Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 24; + + NPC->delay = 0; + NPC_MoveToGoal(qtrue); + return; + + } + } + //else move straight at/away from him + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward ); + forward[2] *= 0.1f; + distance = VectorNormalize( forward ); + + speed = RT_FLYING_FORWARD_BASE_SPEED + RT_FLYING_FORWARD_MULTIPLIER * g_spskill->integer; + if ( advance && distance < Q_flrand( 256, 3096 ) ) + { + NPC->delay = 0; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); + } + else if ( distance < Q_flrand( 0, 128 ) ) + { + if ( NPC->health <= 50 ) + {//always back off + NPC->delay = 0; + } + else if ( !TIMER_Done( NPC, "backoffTime" ) ) + {//still backing off from end of last delay + NPC->delay = 0; + } + else if ( !NPC->delay ) + {//start a new delay + NPC->delay = Q_irand( 0, 10+(20*(2-g_spskill->integer)) ); + } + else + {//continue the current delay + NPC->delay--; + } + if ( !NPC->delay ) + {//delay done, now back off for a few seconds! + TIMER_Set( NPC, "backoffTime", Q_irand( 2000, 5000 ) ); + VectorMA( NPC->client->ps.velocity, speed*-2, forward, NPC->client->ps.velocity ); + } + } + else + { + NPC->delay = 0; + } +} + +void RT_Flying_Ranged( qboolean visible, qboolean advance ) +{ + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + RT_Flying_Hunt( visible, advance ); + } +} + +void RT_Flying_Attack( void ) +{ + // Always keep a good height off the ground + RT_Flying_MaintainHeight(); + + // Rate our distance to the target, and our visibilty + float distance = DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + qboolean visible = NPC_ClearLOS( NPC->enemy ); + qboolean advance = (qboolean)(distance>(256.0f*256.0f)); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + RT_Flying_Hunt( visible, advance ); + return; + } + } + + RT_Flying_Ranged( visible, advance ); +} + +void RT_Flying_Think( void ) +{ + if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV ) + && UpdateGoal() ) + {//being scripted to go to a certain spot, don't maintain height + if ( NPC_MoveToGoal( qtrue ) ) + {//we could macro-nav to our goal + if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse ) + { + NPC_FaceEnemy( qtrue ); + RT_FireDecide(); + } + } + else + {//frick, no where to nav to, keep us in the air! + RT_Flying_MaintainHeight(); + } + return; + } + + if ( NPC->random == 0.0f ) + { + // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method. + NPC->random = random() * 6.3f; // roughly 2pi + } + + if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse ) + { + RT_Flying_Attack(); + RT_FireDecide(); + return; + } + else + { + RT_Flying_MaintainHeight(); + RT_RunStormtrooperAI(); + return; + } +} + + +//===================================================================================== +//ON GROUND WITH ENEMY behavior +//===================================================================================== + + +//===================================================================================== +//DEFAULT behavior +//===================================================================================== +extern void RT_CheckJump( void ); +void NPC_BSRT_Default( void ) +{ + //FIXME: custom pain and death funcs: + //pain3 is in air + //die in air is both_falldeath1 + //attack1 is on ground, attack2 is in air + + //FIXME: this doesn't belong here + if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + if ( NPCInfo->rank >= RANK_LT )//&& !Q_irand( 0, 50 ) ) + {//officers always stay in the air + NPC->client->ps.velocity[2] = Q_irand( 50, 125 ); + NPC->NPC->aiFlags |= NPCAI_FLY; //fixme also, Inform NPC_HandleAIFlags we want to fly + } + } + + if ( RT_Flying( NPC ) ) + {//FIXME: only officers need do this, right? + RT_Flying_Think(); + } + else if ( NPC->enemy != NULL ) + {//rocketrooper on ground with enemy + UpdateGoal(); + RT_RunStormtrooperAI(); + RT_CheckJump(); + //NPC_BSST_Default();//FIXME: add missile avoidance + //RT_Hunt();//NPC_BehaviorSet_Jedi( bState ); + } + else + {//shouldn't have gotten in here + RT_RunStormtrooperAI(); + //NPC_BSST_Patrol(); + } +} \ No newline at end of file diff --git a/code/game/AI_SaberDroid.cpp b/code/game/AI_SaberDroid.cpp new file mode 100644 index 0000000..af3514d --- /dev/null +++ b/code/game/AI_SaberDroid.cpp @@ -0,0 +1,443 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +#include "g_navigator.h" +#include "wp_saber.h" + +//extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void WP_DeactivateSaber( gentity_t *self, qboolean clearLength = qfalse ); +extern int PM_AnimLength( int index, animNumber_t anim ); + +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS; +static qboolean enemyCS; +static qboolean faceEnemy; +static qboolean move; +static qboolean shoot; +static float enemyDist; + +/* +void NPC_SaberDroid_PlayConfusionSound( gentity_t *self ) +{//FIXME: make this a custom sound in sound set + if ( self->health > 0 ) + { + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + TIMER_Set( self, "flee", 0 ); + self->NPC->squadState = SQUAD_IDLE; + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} +*/ + +/* +------------------------- +ST_Move +------------------------- +*/ + +static qboolean SaberDroid_Move( void ) +{ + NPCInfo->combatMove = qtrue;//always move straight toward our goal + UpdateGoal(); + if ( !NPCInfo->goalEntity ) + { + NPCInfo->goalEntity = NPC->enemy; + } + NPCInfo->goalRadius = 30.0f; + + qboolean moved = NPC_MoveToGoal( qtrue ); +// navInfo_t info; + + //Get the move info +// NAV_GetLastMove( info ); + + //FIXME: if we bump into another one of our guys and can't get around him, just stop! + //If we hit our target, then stop and fire! +// if ( info.flags & NIF_COLLISION ) +// { +// if ( info.blocker == NPC->enemy ) +// { +// SaberDroid_HoldPosition(); +// } +// } + + //If our move failed, then reset + /* + if ( moved == qfalse ) + {//couldn't get to enemy + //just hang here + SaberDroid_HoldPosition(); + } + */ + + return moved; +} + +/* +------------------------- +NPC_BSSaberDroid_Patrol +------------------------- +*/ + +void NPC_BSSaberDroid_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be automatic now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + //Is there danger nearby + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); + //There is an event to look at + if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; + if ( level.alertEvents[alertEvent].level >= AEL_DISCOVERED ) + { + if ( level.alertEvents[alertEvent].owner && + level.alertEvents[alertEvent].owner->client && + level.alertEvents[alertEvent].owner->health >= 0 && + level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + //NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + } + } + else + {//FIXME: get more suspicious over time? + //Save the position for movement (if necessary) + VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); + NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); + if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) + {//suspicious looks longer + NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); + } + } + } + + if ( NPCInfo->investigateDebounceTime > level.time ) + {//FIXME: walk over to it, maybe? Not if not chase enemies + //NOTE: stops walking or doing anything else below + vec3_t dir, angles; + float o_yaw, o_pitch; + + VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); + vectoangles( dir, angles ); + + o_yaw = NPCInfo->desiredYaw; + o_pitch = NPCInfo->desiredPitch; + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + NPC_UpdateAngles( qtrue, qtrue ); + + NPCInfo->desiredYaw = o_yaw; + NPCInfo->desiredPitch = o_pitch; + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + else if ( !NPC->client->ps.weaponTime + && TIMER_Done( NPC, "attackDelay" ) + && TIMER_Done( NPC, "inactiveDelay" ) ) + { + if ( NPC->client->ps.SaberActive() ) + { + WP_DeactivateSaber( NPC, qfalse ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURNOFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +int SaberDroid_PowerLevelForSaberAnim( gentity_t *self ) +{ + switch ( self->client->ps.legsAnim ) + { + case BOTH_A2_TR_BL: + if ( self->client->ps.torsoAnimTimer <= 200 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.legsAnim ) - self->client->ps.torsoAnimTimer < 200 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_2; + break; + case BOTH_A1_BL_TR: + if ( self->client->ps.torsoAnimTimer <= 300 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.legsAnim ) - self->client->ps.torsoAnimTimer < 200 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_1; + break; + case BOTH_A1__L__R: + if ( self->client->ps.torsoAnimTimer <= 250 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.legsAnim ) - self->client->ps.torsoAnimTimer < 150 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_1; + break; + case BOTH_A3__L__R: + if ( self->client->ps.torsoAnimTimer <= 200 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.legsAnim ) - self->client->ps.torsoAnimTimer < 300 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + } + return FORCE_LEVEL_0; +} + +/* +------------------------- +NPC_BSSaberDroid_Attack +------------------------- +*/ + +void NPC_SaberDroid_PickAttack( void ) +{ + int attackAnim = Q_irand( 0, 3 ); + switch ( attackAnim ) + { + case 0: + default: + attackAnim = BOTH_A2_TR_BL; + NPC->client->ps.saberMove = LS_A_TR2BL; + NPC->client->ps.saberAnimLevel = SS_MEDIUM; + break; + case 1: + attackAnim = BOTH_A1_BL_TR; + NPC->client->ps.saberMove = LS_A_BL2TR; + NPC->client->ps.saberAnimLevel = SS_FAST; + break; + case 2: + attackAnim = BOTH_A1__L__R; + NPC->client->ps.saberMove = LS_A_L2R; + NPC->client->ps.saberAnimLevel = SS_FAST; + break; + case 3: + attackAnim = BOTH_A3__L__R; + NPC->client->ps.saberMove = LS_A_L2R; + NPC->client->ps.saberAnimLevel = SS_STRONG; + break; + } + NPC->client->ps.saberBlocking = saberMoveData[NPC->client->ps.saberMove].blocking; + if ( saberMoveData[NPC->client->ps.saberMove].trailLength > 0 ) + { + NPC->client->ps.SaberActivateTrail( saberMoveData[NPC->client->ps.saberMove].trailLength ); // saber trail lasts for 75ms...feel free to change this if you want it longer or shorter + } + else + { + NPC->client->ps.SaberDeactivateTrail( 0 ); + } + + NPC_SetAnim( NPC, SETANIM_BOTH, attackAnim, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + NPC->client->ps.torsoAnim = NPC->client->ps.legsAnim;//need to do this because we have no anim split but saber code checks torsoAnim + NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer = NPC->client->ps.legsAnimTimer; + NPC->client->ps.weaponstate = WEAPON_FIRING; +} + +void NPC_BSSaberDroid_Attack( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //NPC_CheckEnemy( qtrue, qfalse ); + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )// + { + NPC->enemy = NULL; + NPC_BSSaberDroid_Patrol();//FIXME: or patrol? + return; + } + + if ( !NPC->enemy ) + {//WTF? somehow we lost our enemy? + NPC_BSSaberDroid_Patrol();//FIXME: or patrol? + return; + } + + enemyLOS = enemyCS = qfalse; + move = qtrue; + faceEnemy = qfalse; + shoot = qfalse; + enemyDist = DistanceSquared( NPC->enemy->currentOrigin, NPC->currentOrigin ); + + //can we see our target? + if ( NPC_ClearLOS( NPC->enemy ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + enemyLOS = qtrue; + + if ( enemyDist <= 4096 && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, 90, 45 ) )//within 64 & infront + { + VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); + enemyCS = qtrue; + } + } + /* + else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + } + */ + + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + + if ( !TIMER_Done( NPC, "taunting" ) ) + { + move = qfalse; + } + else if ( enemyCS ) + { + shoot = qtrue; + if ( enemyDist < (NPC->maxs[0]+NPC->enemy->maxs[0]+32)*(NPC->maxs[0]+NPC->enemy->maxs[0]+32) ) + {//close enough + move = qfalse; + } + }//this should make him chase enemy when out of range...? + + if ( NPC->client->ps.legsAnimTimer + && NPC->client->ps.legsAnim != BOTH_A3__L__R )//this one is a running attack + {//in the middle of a held, stationary anim, can't move + move = qfalse; + } + + if ( move ) + {//move toward goal + move = SaberDroid_Move(); + if ( move ) + {//if we had to chase him, be sure to attack as soon as possible + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + } + } + + if ( !faceEnemy ) + {//we want to face in the dir we're running + if ( move ) + {//don't run away and shoot + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + shoot = qfalse; + } + NPC_UpdateAngles( qtrue, qtrue ); + } + else// if ( faceEnemy ) + {//face the enemy + NPC_FaceEnemy(); + } + + if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) + { + shoot = qfalse; + } + + //FIXME: need predicted blocking? + //FIXME: don't shoot right away! + if ( shoot ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + NPC_SaberDroid_PickAttack(); + if ( NPCInfo->rank > RANK_CREWMAN ) + { + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime+Q_irand(0, 1000) ); + } + else + { + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime+Q_irand( 0, 1000 )+(Q_irand( 0, (3-g_spskill->integer)*2 )*500) ); + } + } + } + } +} + +void NPC_BSSD_Default( void ) +{ + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSSaberDroid_Patrol(); + } + else//if ( NPC->enemy ) + {//have an enemy + if ( !NPC->client->ps.SaberActive() ) + { + NPC->client->ps.SaberActivate(); + if ( NPC->client->ps.legsAnim == BOTH_TURNOFF + || NPC->client->ps.legsAnim == BOTH_STAND1 ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURNON, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + + NPC_BSSaberDroid_Attack(); + TIMER_Set( NPC, "inactiveDelay", Q_irand( 2000, 4000 ) ); + } + if ( !NPC->client->ps.weaponTime ) + { + NPC->client->ps.saberMove = LS_READY; + NPC->client->ps.saberBlocking = saberMoveData[LS_READY].blocking; + NPC->client->ps.SaberDeactivateTrail( 0 ); + NPC->client->ps.saberAnimLevel = SS_MEDIUM; + NPC->client->ps.weaponstate = WEAPON_READY; + } +} \ No newline at end of file diff --git a/code/game/AI_SandCreature.cpp b/code/game/AI_SandCreature.cpp new file mode 100644 index 0000000..4e9d660 --- /dev/null +++ b/code/game/AI_SandCreature.cpp @@ -0,0 +1,818 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + +#include "b_local.h" + +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); + +#define MIN_ATTACK_DIST_SQ 128 +#define MIN_MISS_DIST 100 +#define MIN_MISS_DIST_SQ (MIN_MISS_DIST*MIN_MISS_DIST) +#define MAX_MISS_DIST 500 +#define MAX_MISS_DIST_SQ (MAX_MISS_DIST*MAX_MISS_DIST) +#define MIN_SCORE -37500 //speed of (50*50) - dist of (200*200) + +void SandCreature_Precache( void ) +{ + int i; + G_EffectIndex( "env/sand_dive" ); + G_EffectIndex( "env/sand_spray" ); + G_EffectIndex( "env/sand_move" ); + G_EffectIndex( "env/sand_move_breach" ); + //G_EffectIndex( "env/sand_attack_breach" ); + for ( i = 1; i < 4; i++ ) + { + G_SoundIndex( va( "sound/chars/sand_creature/voice%d.mp3", i ) ); + } + G_SoundIndex( "sound/chars/sand_creature/slither.wav" ); +} + +void SandCreature_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( NPC, "speaking", -level.time ); + TIMER_Set( NPC, "breaching", -level.time ); + TIMER_Set( NPC, "breachDebounce", -level.time ); + TIMER_Set( NPC, "pain", -level.time ); + TIMER_Set( NPC, "attacking", -level.time ); + TIMER_Set( NPC, "missDebounce", -level.time ); +} + +void NPC_SandCreature_Die( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + //FIXME: somehow make him solid when he dies? +} + +void NPC_SandCreature_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + if ( TIMER_Done( self, "pain" ) ) + { + //FIXME: effect and sound + //FIXME: shootable during this anim? + NPC_SetAnim( self, SETANIM_LEGS, Q_irand(BOTH_ATTACK1,BOTH_ATTACK2), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + G_AddEvent( self, EV_PAIN, Q_irand( 0, 100 ) ); + TIMER_Set( self, "pain", self->client->ps.legsAnimTimer + Q_irand( 500, 2000 ) ); + float playerDist = Distance( player->currentOrigin, self->currentOrigin ); + if ( playerDist < 256 ) + { + CGCam_Shake( 1.0f*playerDist/128.0f, self->client->ps.legsAnimTimer ); + } + } + self->enemy = self->NPC->goalEntity = NULL; +} + +void SandCreature_MoveEffect( void ) +{ + vec3_t up = {0,0,1}; + vec3_t org = {NPC->currentOrigin[0], NPC->currentOrigin[1], NPC->absmin[2]+2}; + + float playerDist = Distance( player->currentOrigin, NPC->currentOrigin ); + if ( playerDist < 256 ) + { + CGCam_Shake( 0.75f*playerDist/256.0f, 250 ); + } + + if ( level.time-NPC->client->ps.lastStationary > 2000 ) + {//first time moving for at least 2 seconds + //clear speakingtime + TIMER_Set( NPC, "speaking", -level.time ); + } + + if ( TIMER_Done( NPC, "breaching" ) + && TIMER_Done( NPC, "breachDebounce" ) + && TIMER_Done( NPC, "pain" ) + && TIMER_Done( NPC, "attacking" ) + && !Q_irand( 0, 10 ) ) + {//Breach! + //FIXME: only do this while moving forward? + trace_t trace; + //make him solid here so he can be hit/gets blocked on stuff. Check clear first. + gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, MASK_NPCSOLID ); + if ( !trace.allsolid && !trace.startsolid ) + { + NPC->clipmask = MASK_NPCSOLID;//turn solid for a little bit + NPC->contents = CONTENTS_BODY; + //NPC->takedamage = qtrue;//can be shot? + + //FIXME: Breach sound? + //FIXME: Breach effect? + NPC_SetAnim( NPC, SETANIM_LEGS, BOTH_WALK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + TIMER_Set( NPC, "breaching", NPC->client->ps.legsAnimTimer ); + TIMER_Set( NPC, "breachDebounce", NPC->client->ps.legsAnimTimer+Q_irand( 0, 10000 ) ); + } + } + if ( !TIMER_Done( NPC, "breaching" ) ) + {//different effect when breaching + //FIXME: make effect + G_PlayEffect( G_EffectIndex( "env/sand_move_breach" ), org, up ); + } + else + { + G_PlayEffect( G_EffectIndex( "env/sand_move" ), org, up ); + } + NPC->s.loopSound = G_SoundIndex( "sound/chars/sand_creature/slither.wav" ); +} + +qboolean SandCreature_CheckAhead( vec3_t end ) +{ + trace_t trace; + int clipmask = NPC->clipmask|CONTENTS_BOTCLIP; + + //make sure our goal isn't underground (else the trace will fail) + vec3_t bottom = {end[0],end[1],end[2]+NPC->mins[2]}; + gi.trace( &trace, end, vec3_origin, vec3_origin, bottom, NPC->s.number, NPC->clipmask ); + if ( trace.fraction < 1.0f ) + {//in the ground, raise it up + end[2] -= NPC->mins[2]*(1.0f-trace.fraction)-0.125f; + } + + gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask ); + + if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) ) + {//started inside do not enter, so ignore them + clipmask &= ~CONTENTS_BOTCLIP; + gi.trace( &trace, NPC->currentOrigin, NPC->mins, NPC->maxs, end, NPC->s.number, clipmask ); + } + //Do a simple check + if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) ) + return qtrue; + + if ( trace.plane.normal[2] >= MIN_WALK_NORMAL ) + { + return qtrue; + } + + //This is a work around + float radius = ( NPC->maxs[0] > NPC->maxs[1] ) ? NPC->maxs[0] : NPC->maxs[1]; + float dist = Distance( NPC->currentOrigin, end ); + float tFrac = 1.0f - ( radius / dist ); + + if ( trace.fraction >= tFrac ) + return qtrue; + + return qfalse; +} + +qboolean SandCreature_Move( void ) +{ + qboolean moved = qfalse; + //FIXME should ignore doors..? + vec3_t dest; + VectorCopy( NPCInfo->goalEntity->currentOrigin, dest ); + //Sand Creatures look silly using waypoints when they can go straight to the goal + if ( SandCreature_CheckAhead( dest ) ) + {//use our temp move straight to goal check + VectorSubtract( dest, NPC->currentOrigin, NPC->client->ps.moveDir ); + NPC->client->ps.speed = VectorNormalize( NPC->client->ps.moveDir ); + if ( (ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed > NPCInfo->stats.walkSpeed ) + { + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + } + else + { + if ( NPC->client->ps.speed < NPCInfo->stats.walkSpeed ) + { + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + } + if ( !(ucmd.buttons&BUTTON_WALKING) && NPC->client->ps.speed < NPCInfo->stats.runSpeed ) + { + NPC->client->ps.speed = NPCInfo->stats.runSpeed; + } + else if ( NPC->client->ps.speed > NPCInfo->stats.runSpeed ) + { + NPC->client->ps.speed = NPCInfo->stats.runSpeed; + } + } + moved = qtrue; + } + else + { + moved = NPC_MoveToGoal( qtrue ); + } + if ( moved && NPC->radius ) + { + vec3_t newPos; + float curTurfRange, newTurfRange; + curTurfRange = DistanceHorizontal( NPC->currentOrigin, NPC->s.origin ); + VectorMA( NPC->currentOrigin, NPC->client->ps.speed/100.0f, NPC->client->ps.moveDir, newPos ); + newTurfRange = DistanceHorizontal( newPos, NPC->s.origin ); + if ( newTurfRange > NPC->radius && newTurfRange > curTurfRange ) + {//would leave our range + //stop + NPC->client->ps.speed = 0.0f; + VectorClear( NPC->client->ps.moveDir ); + ucmd.forwardmove = ucmd.rightmove = 0; + moved = qfalse; + } + } + return (moved); + //often erroneously returns false ??? something wrong with NAV...? +} + +void SandCreature_Attack( qboolean miss ) +{ + //FIXME: make it able to grab a thermal detonator, take it down, + // then have it explode inside them, killing them + // (or, do damage, making them stick half out of the ground and + // screech for a bit, giving you a chance to run for it!) + + //FIXME: effect and sound + //FIXME: shootable during this anim? + if ( !NPC->enemy->client ) + { + NPC_SetAnim( NPC, SETANIM_LEGS, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + } + else + { + NPC_SetAnim( NPC, SETANIM_LEGS, Q_irand( BOTH_ATTACK1, BOTH_ATTACK2 ), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + } + //don't do anything else while in this anim + TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer ); + float playerDist = Distance( player->currentOrigin, NPC->currentOrigin ); + if ( playerDist < 256 ) + { + //FIXME: tone this down + CGCam_Shake( 0.75f*playerDist/128.0f, NPC->client->ps.legsAnimTimer ); + } + + if ( miss ) + {//purposely missed him, chance of knocking him down + //FIXME: if, during the attack anim, I do end up catching him close to my mouth, then snatch him anyway... + if ( NPC->enemy && NPC->enemy->client ) + { + vec3_t dir2Enemy; + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, dir2Enemy ); + if ( dir2Enemy[2] < 30 ) + { + dir2Enemy[2] = 30; + } + if ( g_spskill->integer > 0 ) + { + float enemyDist = VectorNormalize( dir2Enemy ); + //FIXME: tone this down, smaller radius + if ( enemyDist < 200 && NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + float throwStr = ((200-enemyDist)*0.4f)+20; + if ( throwStr > 45 ) + { + throwStr = 45; + } + G_Throw( NPC->enemy, dir2Enemy, throwStr ); + if ( g_spskill->integer > 1 ) + {//knock them down, too + if ( NPC->enemy->health > 0 + && Q_flrand( 50, 150 ) > enemyDist ) + {//knock them down + G_Knockdown( NPC->enemy, NPC, dir2Enemy, 300, qtrue ); + if ( NPC->enemy->s.number < MAX_CLIENTS ) + {//make the player look up at me + vec3_t vAng; + vectoangles( dir2Enemy, vAng ); + VectorSet( vAng, AngleNormalize180(vAng[PITCH])*-1, NPC->enemy->client->ps.viewangles[YAW], 0 ); + SetClientViewAngle( NPC->enemy, vAng ); + } + } + } + } + } + } + } + else + { + NPC->enemy->activator = NPC; // kind of dumb, but when we are locked to the Rancor, we are owned by it. + NPC->activator = NPC->enemy;//remember him + //this guy isn't going anywhere anymore + NPC->enemy->contents = 0; + NPC->enemy->clipmask = 0; + + if ( NPC->activator->client ) + { + NPC->activator->client->ps.SaberDeactivate(); + NPC->activator->client->ps.eFlags |= EF_HELD_BY_SAND_CREATURE; + if ( NPC->activator->health > 0 && NPC->activator->client ) + { + G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 ); + NPC_SetAnim( NPC->activator, SETANIM_LEGS, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TossClientItems( NPC ); + if ( NPC->activator->NPC ) + {//no more thinking for you + NPC->activator->NPC->nextBStateThink = Q3_INFINITE; + } + } + /* + if ( !NPC->activator->s.number ) + { + cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_CDP|CG_OVERRIDE_3RD_PERSON_RNG); + cg.overrides.thirdPersonCameraDamp = 0; + cg.overrides.thirdPersonRange = 120; + } + */ + } + else + { + NPC->activator->s.eFlags |= EF_HELD_BY_SAND_CREATURE; + } + } +} + +float SandCreature_EntScore( gentity_t *ent ) +{ + float moveSpeed, dist; + + if ( ent->client ) + { + moveSpeed = VectorLengthSquared( ent->client->ps.velocity ); + } + else + { + moveSpeed = VectorLengthSquared( ent->s.pos.trDelta ); + } + dist = DistanceSquared( NPC->currentOrigin, ent->currentOrigin ); + return (moveSpeed-dist); +} + +void SandCreature_SeekEnt( gentity_t *bestEnt, float score ) +{ + NPCInfo->enemyLastSeenTime = level.time; + VectorCopy( bestEnt->currentOrigin, NPCInfo->enemyLastSeenLocation ); + NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 0, qfalse ); + if ( score > MIN_SCORE ) + { + NPC->enemy = bestEnt; + } +} + +void SandCreature_CheckMovingEnts( void ) +{ + gentity_t *radiusEnts[ 128 ]; + int numEnts; + const float radius = NPCInfo->stats.earshot; + int i; + vec3_t mins, maxs; + + for ( i = 0; i < 3; i++ ) + { + mins[i] = NPC->currentOrigin[i] - radius; + maxs[i] = NPC->currentOrigin[i] + radius; + } + + numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 ); + int bestEnt = -1; + float bestScore = 0; + float checkScore; + + for ( i = 0; i < numEnts; i++ ) + { + if ( !radiusEnts[i]->inuse ) + { + continue; + } + + if ( radiusEnts[i] == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnts[i]->client == NULL ) + {//must be a client + if ( radiusEnts[i]->s.eType != ET_MISSILE + || radiusEnts[i]->s.weapon != WP_THERMAL ) + {//not a thermal detonator + continue; + } + } + else + { + if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) ) + {//can't be one being held + continue; + } + + if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) ) + {//can't be one being held + continue; + } + + if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) ) + {//can't be one being held + continue; + } + + if ( (radiusEnts[i]->s.eFlags&EF_NODRAW) ) + {//not if invisible + continue; + } + + if ( radiusEnts[i]->client->ps.groundEntityNum != ENTITYNUM_WORLD ) + {//not on the ground + continue; + } + + if ( radiusEnts[i]->client->NPC_class == CLASS_SAND_CREATURE ) + { + continue; + } + } + + if ( (radiusEnts[i]->flags&FL_NOTARGET) ) + { + continue; + } + /* + if ( radiusEnts[i]->client && (radiusEnts[i]->client->NPC_class == CLASS_RANCOR || radiusEnts[i]->client->NPC_class == CLASS_ATST ) ) + {//can't grab rancors or atst's + continue; + } + */ + checkScore = SandCreature_EntScore( radiusEnts[i] ); + //FIXME: take mass into account too? What else? + if ( checkScore > bestScore ) + { + bestScore = checkScore; + bestEnt = i; + } + } + if ( bestEnt != -1 ) + { + SandCreature_SeekEnt( radiusEnts[bestEnt], bestScore ); + } +} + +void SandCreature_SeekAlert( int alertEvent ) +{ + alertEvent_t *alert = &level.alertEvents[alertEvent]; + + //FIXME: check for higher alert status or closer than last location? + NPCInfo->enemyLastSeenTime = level.time; + VectorCopy( alert->position, NPCInfo->enemyLastSeenLocation ); + NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 0, qfalse ); +} + +void SandCreature_CheckAlerts( void ) +{ + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + int alertEvent = NPC_CheckAlertEvents( qfalse, qtrue, NPCInfo->lastAlertID, qfalse, AEL_MINOR, qtrue ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + //if ( level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + SandCreature_SeekAlert( alertEvent ); + } + } + } +} + +float SandCreature_DistSqToGoal( qboolean goalIsEnemy ) +{ + float goalDistSq; + if ( !NPCInfo->goalEntity || goalIsEnemy ) + { + if ( !NPC->enemy ) + { + return Q3_INFINITE; + } + NPCInfo->goalEntity = NPC->enemy; + } + + if ( NPCInfo->goalEntity->client ) + { + goalDistSq = DistanceSquared( NPC->currentOrigin, NPCInfo->goalEntity->currentOrigin ); + } + else + { + vec3_t gOrg; + VectorCopy( NPCInfo->goalEntity->currentOrigin, gOrg ); + gOrg[2] -= (NPC->mins[2]-NPCInfo->goalEntity->mins[2]);//moves the gOrg up/down to make it's origin seem at the proper height as if it had my mins + goalDistSq = DistanceSquared( NPC->currentOrigin, gOrg ); + } + return goalDistSq; +} + +void SandCreature_Chase( void ) +{ + if ( !NPC->enemy->inuse ) + {//freed + NPC->enemy = NULL; + return; + } + + if ( (NPC->svFlags&SVF_LOCKEDENEMY) ) + {//always know where he is + NPCInfo->enemyLastSeenTime = level.time; + } + + if ( !(NPC->svFlags&SVF_LOCKEDENEMY) ) + { + if ( level.time-NPCInfo->enemyLastSeenTime > 10000 ) + { + NPC->enemy = NULL; + return; + } + } + + if ( NPC->enemy->client ) + { + if ( (NPC->enemy->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) + || (NPC->enemy->client->ps.eFlags&EF_HELD_BY_RANCOR) + || (NPC->enemy->client->ps.eFlags&EF_HELD_BY_WAMPA) ) + {//was picked up by another monster, forget about him + NPC->enemy = NULL; + NPC->svFlags &= ~SVF_LOCKEDENEMY; + return; + } + } + //chase the enemy + if ( NPC->enemy->client + && NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_WORLD + && !(NPC->svFlags&SVF_LOCKEDENEMY) ) + {//off the ground! + //FIXME: keep moving in the dir we were moving for a little bit... + } + else + { + float enemyScore = SandCreature_EntScore( NPC->enemy ); + if ( enemyScore < MIN_SCORE + && !(NPC->svFlags&SVF_LOCKEDENEMY) ) + {//too slow or too far away + } + else + { + float moveSpeed; + if ( NPC->enemy->client ) + { + moveSpeed = VectorLengthSquared( NPC->enemy->client->ps.velocity ); + } + else + { + moveSpeed = VectorLengthSquared( NPC->enemy->s.pos.trDelta ); + } + if ( moveSpeed ) + {//he's still moving, update my goalEntity's origin + SandCreature_SeekEnt( NPC->enemy, 0 ); + NPCInfo->enemyLastSeenTime = level.time; + } + } + } + + if ( (level.time-NPCInfo->enemyLastSeenTime) > 5000 + && !(NPC->svFlags&SVF_LOCKEDENEMY) ) + {//enemy hasn't moved in about 5 seconds, see if there's anything else of interest + SandCreature_CheckAlerts(); + SandCreature_CheckMovingEnts(); + } + + float enemyDistSq = SandCreature_DistSqToGoal( qtrue ); + + //FIXME: keeps chasing goalEntity even when it's already reached it...? + if ( enemyDistSq >= MIN_ATTACK_DIST_SQ//NPCInfo->goalEntity && + && (level.time-NPCInfo->enemyLastSeenTime) <= 3000 ) + {//sensed enemy (or something) less than 3 seconds ago + ucmd.buttons &= ~BUTTON_WALKING; + if ( SandCreature_Move() ) + { + SandCreature_MoveEffect(); + } + } + else if ( (level.time-NPCInfo->enemyLastSeenTime) <= 5000 + && !(NPC->svFlags&SVF_LOCKEDENEMY) ) + {//NOTE: this leaves a 2-second dead zone in which they'll just sit there unless their enemy moves + //If there is an event we might be interested in if we weren't still interested in our enemy + if ( NPC_CheckAlertEvents( qfalse, qtrue, NPCInfo->lastAlertID, qfalse, AEL_MINOR, qtrue ) >= 0 ) + {//just stir + SandCreature_MoveEffect(); + } + } + + if ( enemyDistSq < MIN_ATTACK_DIST_SQ ) + { + if ( NPC->enemy->client ) + { + NPC->client->ps.viewangles[YAW] = NPC->enemy->client->ps.viewangles[YAW]; + } + if ( TIMER_Done( NPC, "breaching" ) ) + { + //okay to attack + SandCreature_Attack( qfalse ); + } + } + else if ( enemyDistSq < MAX_MISS_DIST_SQ + && enemyDistSq > MIN_MISS_DIST_SQ + && NPC->enemy->client + && TIMER_Done( NPC, "breaching" ) + && TIMER_Done( NPC, "missDebounce" ) + && !VectorCompare( NPC->pos1, NPC->currentOrigin ) //so we don't come up again in the same spot + && !Q_irand( 0, 10 ) ) + { + if ( !(NPC->svFlags&SVF_LOCKEDENEMY) ) + { + //miss them + SandCreature_Attack( qtrue ); + VectorCopy( NPC->currentOrigin, NPC->pos1 ); + TIMER_Set( NPC, "missDebounce", Q_irand( 3000, 10000 ) ); + } + } +} + +void SandCreature_Hunt( void ) +{ + SandCreature_CheckAlerts(); + SandCreature_CheckMovingEnts(); + //If we have somewhere to go, then do that + //FIXME: keeps chasing goalEntity even when it's already reached it...? + if ( NPCInfo->goalEntity + && SandCreature_DistSqToGoal( qfalse ) >= MIN_ATTACK_DIST_SQ ) + { + ucmd.buttons |= BUTTON_WALKING; + if ( SandCreature_Move() ) + { + SandCreature_MoveEffect(); + } + } + else + { + NPC_ReachedGoal(); + } +} + +void SandCreature_Sleep( void ) +{ + SandCreature_CheckAlerts(); + SandCreature_CheckMovingEnts(); + //FIXME: keeps chasing goalEntity even when it's already reached it! + if ( NPCInfo->goalEntity + && SandCreature_DistSqToGoal( qfalse ) >= MIN_ATTACK_DIST_SQ ) + { + ucmd.buttons |= BUTTON_WALKING; + if ( SandCreature_Move() ) + { + SandCreature_MoveEffect(); + } + } + else + { + NPC_ReachedGoal(); + } + /* + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + //FIXME: Sand Creatures look silly using waypoints when they can go straight to the goal + if ( SandCreature_Move() ) + { + SandCreature_MoveEffect(); + } + } + */ +} + +void SandCreature_PushEnts() +{ + int numEnts; + gentity_t* radiusEnts[128]; + const float radius = 70; + vec3_t mins, maxs; + vec3_t smackDir; + float smackDist; + + for (int i = 0; i < 3; i++ ) + { + mins[i] = NPC->currentOrigin[i] - radius; + maxs[i] = NPC->currentOrigin[i] + radius; + } + + numEnts = gi.EntitiesInBox(mins, maxs, radiusEnts, 128); + for (int entIndex=0; entIndexclient || radiusEnts[entIndex]==NPC) + { + continue; + } + + // Do The Vector Distance Test + //----------------------------- + VectorSubtract(radiusEnts[entIndex]->currentOrigin, NPC->currentOrigin, smackDir); + smackDist = VectorNormalize(smackDir); + if (smackDists.loopSound = 0; + + if ( NPC->health > 0 && TIMER_Done( NPC, "breaching" ) ) + {//go back to non-solid mode + if ( NPC->contents ) + { + NPC->contents = 0; + } + if ( NPC->clipmask == MASK_NPCSOLID ) + { + NPC->clipmask = CONTENTS_SOLID|CONTENTS_MONSTERCLIP; + } + if ( TIMER_Done( NPC, "speaking" ) ) + { + G_SoundOnEnt( NPC, CHAN_VOICE, va( "sound/chars/sand_creature/voice%d.mp3", Q_irand( 1, 3 ) ) ); + TIMER_Set( NPC, "speaking", Q_irand( 3000, 10000 ) ); + } + } + else + {//still in breaching anim + visible = qtrue; + //FIXME: maybe push things up/away and maybe knock people down when doing this? + //FIXME: don't turn while breaching? + //FIXME: move faster while breaching? + //NOTE: shaking now done whenever he moves + } + + //FIXME: when in start and end of attack/pain anims, need ground disturbance effect around him + // NOTENOTE: someone stubbed this code in, so I figured I'd use it. The timers are all weird, ie, magic numbers that sort of work, + // but maybe I'll try and figure out real values later if I have time. + if ( NPC->client->ps.legsAnim == BOTH_ATTACK1 + || NPC->client->ps.legsAnim == BOTH_ATTACK2 ) + {//FIXME: get start and end frame numbers for this effect for each of these anims + vec3_t up={0,0,1}; + vec3_t org; + VectorCopy( NPC->currentOrigin, org ); + org[2] -= 40; + if ( NPC->client->ps.legsAnimTimer > 3700 ) + { +// G_PlayEffect( G_EffectIndex( "env/sand_dive" ), NPC->currentOrigin, up ); + G_PlayEffect( G_EffectIndex( "env/sand_spray" ), org, up ); + } + else if ( NPC->client->ps.legsAnimTimer > 1600 && NPC->client->ps.legsAnimTimer < 1900 ) + { + G_PlayEffect( G_EffectIndex( "env/sand_spray" ), org, up ); + } + //G_PlayEffect( G_EffectIndex( "env/sand_attack_breach" ), org, up ); + } + + + if ( !TIMER_Done( NPC, "pain" ) ) + { + visible = qtrue; + } + else if ( !TIMER_Done( NPC, "attacking" ) ) + { + visible = qtrue; + } + else + { + if ( NPC->activator ) + {//kill and remove the guy we ate + //FIXME: want to play ...? What was I going to say? + NPC->activator->health = 0; + GEntity_DieFunc( NPC->activator, NPC, NPC, 1000, MOD_MELEE, 0, HL_NONE ); + if ( NPC->activator->s.number ) + { + G_FreeEntity( NPC->activator ); + } + else + {//can't remove the player, just make him invisible + NPC->client->ps.eFlags |= EF_NODRAW; + } + NPC->activator = NPC->enemy = NPCInfo->goalEntity = NULL; + } + + if ( NPC->enemy ) + { + SandCreature_Chase(); + } + else if ( (level.time - NPCInfo->enemyLastSeenTime) < 5000 )//FIXME: should make this able to be variable + {//we were alerted recently, move towards there and look for footsteps, etc. + SandCreature_Hunt(); + } + else + {//no alerts, sleep and wake up only by alerts + //FIXME: keeps chasing goalEntity even when it's already reached it! + SandCreature_Sleep(); + } + } + NPC_UpdateAngles( qtrue, qtrue ); + if ( !visible ) + { + NPC->client->ps.eFlags |= EF_NODRAW; + NPC->s.eFlags |= EF_NODRAW; + } + else + { + NPC->client->ps.eFlags &= ~EF_NODRAW; + NPC->s.eFlags &= ~EF_NODRAW; + + SandCreature_PushEnts(); + } +} + +//FIXME: need pain behavior of sticking up through ground, writhing and screaming +//FIXME: need death anim like pain, but flopping aside and staying above ground... \ No newline at end of file diff --git a/code/game/AI_Seeker.cpp b/code/game/AI_Seeker.cpp new file mode 100644 index 0000000..5318602 --- /dev/null +++ b/code/game/AI_Seeker.cpp @@ -0,0 +1,539 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" + +extern void NPC_BSST_Patrol( void ); +extern void Boba_FireDecide( void ); +extern gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse ); + +void Seeker_Strafe( void ); + +#define VELOCITY_DECAY 0.7f + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 80 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define SEEKER_STRAFE_VEL 100 +#define SEEKER_STRAFE_DIS 200 +#define SEEKER_UPWARD_PUSH 32 + +#define SEEKER_FORWARD_BASE_SPEED 10 +#define SEEKER_FORWARD_MULTIPLIER 2 + +#define SEEKER_SEEK_RADIUS 1024 + +//------------------------------------ +void NPC_Seeker_Precache(void) +{ + G_SoundIndex("sound/chars/seeker/misc/fire.wav"); + G_SoundIndex( "sound/chars/seeker/misc/hiss.wav"); + G_EffectIndex( "env/small_explode"); +} + +//------------------------------------ +void NPC_Seeker_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + if ( !(self->svFlags & SVF_CUSTOM_GRAVITY )) + {//void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod, int hitLoc=HL_NONE ); + G_Damage( self, NULL, NULL, vec3_origin, (float*)vec3_origin, 999, 0, MOD_FALLING ); + } + + SaveNPCGlobals(); + SetNPCGlobals( self ); + Seeker_Strafe(); + RestoreNPCGlobals(); + NPC_Pain( self, inflictor, other, point, damage, mod ); +} + +//------------------------------------ +void Seeker_MaintainHeight( void ) +{ + float dif; + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at or a little below enemy eye level + if ( NPC->enemy ) + { + if (TIMER_Done( NPC, "heightChange" )) + { + TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); + + // Find the height difference + dif = (NPC->enemy->currentOrigin[2] + Q_flrand( NPC->enemy->maxs[2]/2, NPC->enemy->maxs[2]+8 )) - NPC->currentOrigin[2]; + + float difFactor = 1.0f; + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( TIMER_Done( NPC, "flameTime" ) ) + { + difFactor = 10.0f; + } + } + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 2*difFactor ) + { + if ( fabs( dif ) > 24*difFactor ) + { + dif = ( dif < 0 ? -24*difFactor : 24*difFactor ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + NPC->client->ps.velocity[2] *= Q_flrand( 0.85f, 3.0f ); + } + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +//------------------------------------ +void Seeker_Strafe( void ) +{ + int side; + vec3_t end, right, dir; + trace_t tr; + + if ( random() > 0.7f || !NPC->enemy || !NPC->enemy->client ) + { + // Do a regular style strafe + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonably valid + side = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->currentOrigin, SEEKER_STRAFE_DIS * side, right, end ); + + gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + float vel = SEEKER_STRAFE_VEL; + float upPush = SEEKER_UPWARD_PUSH; + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + G_Sound( NPC, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); + } + else + { + vel *= 3.0f; + upPush *= 4.0f; + } + VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity ); + // Add a slight upward push + NPC->client->ps.velocity[2] += upPush; + + NPCInfo->standTime = level.time + 1000 + random() * 500; + } + } + else + { + // Do a strafe to try and keep on the side of their enemy + AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL ); + + // Pick a random side + side = ( rand() & 1 ) ? -1 : 1; + float stDis = SEEKER_STRAFE_DIS; + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + stDis *= 2.0f; + } + VectorMA( NPC->enemy->currentOrigin, stDis * side, right, end ); + + // then add a very small bit of random in front of/behind the player action + VectorMA( end, crandom() * 25, dir, end ); + + gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorSubtract( tr.endpos, NPC->currentOrigin, dir ); + dir[2] *= 0.25; // do less upward change + float dis = VectorNormalize( dir ); + + // Try to move the desired enemy side + VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity ); + + float upPush = SEEKER_UPWARD_PUSH; + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + G_Sound( NPC, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); + } + else + { + upPush *= 4.0f; + } + + // Add a slight upward push + NPC->client->ps.velocity[2] += upPush; + + NPCInfo->standTime = level.time + 2500 + random() * 500; + } + } +} + +//------------------------------------ +void Seeker_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + NPC_FaceEnemy( qtrue ); + + // If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Seeker_Strafe(); + return; + } + } + + // If we don't want to advance, stop here + if ( advance == qfalse ) + { + return; + } + + // Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 24; + + NPC_MoveToGoal(qtrue); + return; + + } + else + { + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = SEEKER_FORWARD_BASE_SPEED + SEEKER_FORWARD_MULTIPLIER * g_spskill->integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +//------------------------------------ +void Seeker_Fire( void ) +{ + vec3_t dir, enemy_org, muzzle; + gentity_t *missile; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org ); + VectorSubtract( enemy_org, NPC->currentOrigin, dir ); + VectorNormalize( dir ); + + // move a bit forward in the direction we shall shoot in so that the bolt doesn't poke out the other side of the seeker + VectorMA( NPC->currentOrigin, 15, dir, muzzle ); + + missile = CreateMissile( muzzle, dir, 1000, 10000, NPC ); + + G_PlayEffect( "blaster/muzzle_flash", NPC->currentOrigin, dir ); + + missile->classname = "blaster"; + missile->s.weapon = WP_BLASTER; + + missile->damage = 5; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; +} + +//------------------------------------ +void Seeker_Ranged( qboolean visible, qboolean advance ) +{ + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + if ( NPC->count > 0 ) + { + if ( TIMER_Done( NPC, "attackDelay" )) // Attack? + { + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 2500 )); + Seeker_Fire(); + NPC->count--; + } + } + else + { + // out of ammo, so let it die...give it a push up so it can fall more and blow up on impact + // NPC->client->ps.gravity = 900; + // NPC->svFlags &= ~SVF_CUSTOM_GRAVITY; + // NPC->client->ps.velocity[2] += 16; + G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN ); + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Seeker_Hunt( visible, advance ); + } +} + +//------------------------------------ +void Seeker_Attack( void ) +{ + // Always keep a good height off the ground + Seeker_MaintainHeight(); + + // Rate our distance to the target, and our visibilty + float distance = DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + qboolean visible = NPC_ClearLOS( NPC->enemy ); + qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + advance = (qboolean)(distance>(200.0f*200.0f)); + } + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Seeker_Hunt( visible, advance ); + return; + } + } + + Seeker_Ranged( visible, advance ); +} + +//------------------------------------ +void Seeker_FindEnemy( void ) +{ + int numFound; + float dis, bestDis = SEEKER_SEEK_RADIUS * SEEKER_SEEK_RADIUS + 1; + vec3_t mins, maxs; + gentity_t *entityList[MAX_GENTITIES], *ent, *best = NULL; + + VectorSet( maxs, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS ); + VectorScale( maxs, -1, mins ); + + numFound = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( int i = 0 ; i < numFound ; i++ ) + { + ent = entityList[i]; + + if ( ent->s.number == NPC->s.number || !ent->client || !ent->NPC || ent->health <= 0 || !ent->inuse ) + { + continue; + } + + if ( ent->client->playerTeam == NPC->client->playerTeam || ent->client->playerTeam == TEAM_NEUTRAL ) // don't attack same team or bots + { + continue; + } + + // try to find the closest visible one + if ( !NPC_ClearLOS( ent )) + { + continue; + } + + dis = DistanceHorizontalSquared( NPC->currentOrigin, ent->currentOrigin ); + + if ( dis <= bestDis ) + { + bestDis = dis; + best = ent; + } + } + + if ( best ) + { + // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method. + NPC->random = random() * 6.3f; // roughly 2pi + + NPC->enemy = best; + } +} + +//------------------------------------ +void Seeker_FollowPlayer( void ) +{ + Seeker_MaintainHeight(); + + float dis = DistanceHorizontalSquared( NPC->currentOrigin, g_entities[0].currentOrigin ); + vec3_t pt, dir; + + float minDistSqr = MIN_DISTANCE_SQR; + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( TIMER_Done( NPC, "flameTime" ) ) + { + minDistSqr = 200*200; + } + } + + if ( dis < minDistSqr ) + { + // generally circle the player closely till we take an enemy..this is our target point + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + pt[0] = g_entities[0].currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 250; + pt[1] = g_entities[0].currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 250; + if ( NPC->client->jetPackTime < level.time ) + { + pt[2] = NPC->currentOrigin[2] - 64; + } + else + { + pt[2] = g_entities[0].currentOrigin[2] + 200; + } + } + else + { + pt[0] = g_entities[0].currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 56; + pt[1] = g_entities[0].currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 56; + pt[2] = g_entities[0].currentOrigin[2] + 40; + } + + VectorSubtract( pt, NPC->currentOrigin, dir ); + VectorMA( NPC->client->ps.velocity, 0.8f, dir, NPC->client->ps.velocity ); + } + else + { + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + if ( TIMER_Done( NPC, "seekerhiss" )) + { + TIMER_Set( NPC, "seekerhiss", 1000 + random() * 1000 ); + G_Sound( NPC, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); + } + } + + // Hey come back! + NPCInfo->goalEntity = &g_entities[0]; + NPCInfo->goalRadius = 32; + NPC_MoveToGoal( qtrue ); + NPC->owner = &g_entities[0]; + } + + if ( NPCInfo->enemyCheckDebounceTime < level.time ) + { + // check twice a second to find a new enemy + Seeker_FindEnemy(); + NPCInfo->enemyCheckDebounceTime = level.time + 500; + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +//------------------------------------ +void NPC_BSSeeker_Default( void ) +{ + if ( in_camera ) + { + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + // cameras make me commit suicide.... + G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN ); + } + } + + if ( NPC->random == 0.0f ) + { + // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method. + NPC->random = random() * 6.3f; // roughly 2pi + } + + if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse ) + { + if ( NPC->client->NPC_class != CLASS_BOBAFETT + && ( NPC->enemy->s.number == 0 || ( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_SEEKER )) ) + { + //hacked to never take the player as an enemy, even if the player shoots at it + NPC->enemy = NULL; + } + else + { + Seeker_Attack(); + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + Boba_FireDecide(); + } + return; + } + } + else if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + NPC_BSST_Patrol(); + return; + } + + // In all other cases, follow the player and look for enemies to take on + Seeker_FollowPlayer(); +} \ No newline at end of file diff --git a/code/game/AI_Sentry.cpp b/code/game/AI_Sentry.cpp new file mode 100644 index 0000000..0b1a839 --- /dev/null +++ b/code/game/AI_Sentry.cpp @@ -0,0 +1,569 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" + +gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse ); +extern gitem_t *FindItemForAmmo( ammo_t ammo ); + +#define MIN_DISTANCE 256 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define SENTRY_FORWARD_BASE_SPEED 10 +#define SENTRY_FORWARD_MULTIPLIER 5 + +#define SENTRY_VELOCITY_DECAY 0.85f +#define SENTRY_STRAFE_VEL 256 +#define SENTRY_STRAFE_DIS 200 +#define SENTRY_UPWARD_PUSH 32 +#define SENTRY_HOVER_HEIGHT 24 + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_ASLEEP, + LSTATE_WAKEUP, + LSTATE_ACTIVE, + LSTATE_POWERING_UP, + LSTATE_ATTACKING, +}; + +/* +------------------------- +NPC_Sentry_Precache +------------------------- +*/ +void NPC_Sentry_Precache(void) +{ + G_SoundIndex( "sound/chars/sentry/misc/sentry_explo" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_pain" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_open" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_close" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" ); + + for ( int i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/sentry/misc/talk%d", i ) ); + } + + G_EffectIndex( "bryar/muzzle_flash"); + G_EffectIndex( "env/med_explode"); + + RegisterItem( FindItemForAmmo( AMMO_BLASTER )); +} + +/* +================ +sentry_use +================ +*/ +void sentry_use( gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + self->flags &= ~FL_SHIELDED; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +// self->NPC->localState = LSTATE_WAKEUP; + self->NPC->localState = LSTATE_ACTIVE; +} + +/* +------------------------- +NPC_Sentry_Pain +------------------------- +*/ +void NPC_Sentry_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + NPC_Pain( self, inflictor, other, point, damage, mod ); + + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) + { + self->NPC->burstCount = 0; + TIMER_Set( self, "attackDelay", Q_irand( 9000, 12000) ); + self->flags |= FL_SHIELDED; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + G_SoundOnEnt( self, CHAN_AUTO, "sound/chars/sentry/misc/sentry_pain" ); + + self->NPC->localState = LSTATE_ACTIVE; + } + + // You got hit, go after the enemy +// if (self->NPC->localState == LSTATE_ASLEEP) +// { +// G_Sound( self, G_SoundIndex("sound/chars/sentry/misc/shieldsopen.wav")); +// +// self->flags &= ~FL_SHIELDED; +// NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +// self->NPC->localState = LSTATE_WAKEUP; +// } +} + +/* +------------------------- +Sentry_Fire +------------------------- +*/ +void Sentry_Fire (void) +{ + vec3_t muzzle; + static vec3_t forward, vright, up; + gentity_t *missile; + mdxaBone_t boltMatrix; + int bolt; + + NPC->flags &= ~FL_SHIELDED; + + if ( NPCInfo->localState == LSTATE_POWERING_UP ) + { + if ( TIMER_Done( NPC, "powerup" )) + { + NPCInfo->localState = LSTATE_ATTACKING; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + { + // can't do anything right now + return; + } + } + else if ( NPCInfo->localState == LSTATE_ACTIVE ) + { + NPCInfo->localState = LSTATE_POWERING_UP; + + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/sentry/misc/sentry_shield_open" ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "powerup", 250 ); + return; + } + else if ( NPCInfo->localState != LSTATE_ATTACKING ) + { + // bad because we are uninitialized + NPCInfo->localState = LSTATE_ACTIVE; + return; + } + + // Which muzzle to fire from? + int which = NPCInfo->burstCount % 3; + switch( which ) + { + case 0: + bolt = NPC->genericBolt1; + break; + case 1: + bolt = NPC->genericBolt2; + break; + case 2: + default: + bolt = NPC->genericBolt3; + } + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, + bolt, + &boltMatrix, NPC->currentAngles, NPC->currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, muzzle ); + + AngleVectors( NPC->currentAngles, forward, vright, up ); +// G_Sound( NPC, G_SoundIndex("sound/chars/sentry/misc/shoot.wav")); + + G_PlayEffect( "bryar/muzzle_flash", muzzle, forward ); + + missile = CreateMissile( muzzle, forward, 1600, 10000, NPC ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + NPCInfo->burstCount++; + NPC->attackDebounceTime = level.time + 50; + missile->damage = 5; + + // now scale for difficulty + if ( g_spskill->integer == 0 ) + { + NPC->attackDebounceTime += 200; + missile->damage = 1; + } + else if ( g_spskill->integer == 1 ) + { + NPC->attackDebounceTime += 100; + missile->damage = 3; + } +} + +/* +------------------------- +Sentry_MaintainHeight +------------------------- +*/ +void Sentry_MaintainHeight( void ) +{ + float dif; + + NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" ); + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at about enemy eye level + if ( NPC->enemy ) + { + // Find the height difference + dif = (NPC->enemy->currentOrigin[2]+NPC->enemy->maxs[2]) - NPC->currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 8 ) + { + if ( fabs( dif ) > SENTRY_HOVER_HEIGHT ) + { + dif = ( dif < 0 ? -24 : 24 ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + + if (goal) + { + dif = goal->currentOrigin[2] - NPC->currentOrigin[2]; + + if ( fabs( dif ) > SENTRY_HOVER_HEIGHT ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + // Apply friction to Z + else if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } + + NPC_FaceEnemy( qtrue ); +} + +/* +------------------------- +Sentry_Idle +------------------------- +*/ +void Sentry_Idle( void ) +{ + Sentry_MaintainHeight(); + + // Is he waking up? + if (NPCInfo->localState == LSTATE_WAKEUP) + { + if (NPC->client->ps.torsoAnimTimer<=0) + { + NPCInfo->scriptFlags |= SCF_LOOK_FOR_ENEMIES; + NPCInfo->burstCount = 0; + } + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SLEEP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->flags |= FL_SHIELDED; + + NPC_BSIdle(); + } +} + +/* +------------------------- +Sentry_Strafe +------------------------- +*/ +void Sentry_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->currentOrigin, SENTRY_STRAFE_DIS * dir, right, end ); + + gi.trace( &tr, NPC->currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, SENTRY_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + // Add a slight upward push + NPC->client->ps.velocity[2] += SENTRY_UPWARD_PUSH; + + // Set the strafe start time so we can do a controlled roll + NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +/* +------------------------- +Sentry_Hunt +------------------------- +*/ +void Sentry_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Sentry_Strafe(); + return; + } + } + + //If we don't want to advance, stop here + if ( !advance && visible ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + NPC_MoveToGoal(qtrue); + return; + } + else + { + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = SENTRY_FORWARD_BASE_SPEED + SENTRY_FORWARD_MULTIPLIER * g_spskill->integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +/* +------------------------- +Sentry_RangedAttack +------------------------- +*/ +void Sentry_RangedAttack( qboolean visible, qboolean advance ) +{ + if ( TIMER_Done( NPC, "attackDelay" ) && NPC->attackDebounceTime < level.time && visible ) // Attack? + { + if ( NPCInfo->burstCount > 6 ) + { + if ( !NPC->fly_sound_debounce_time ) + {//delay closing down to give the player an opening + NPC->fly_sound_debounce_time = level.time + Q_irand( 500, 2000 ); + } + else if ( NPC->fly_sound_debounce_time < level.time ) + { + NPCInfo->localState = LSTATE_ACTIVE; + NPC->fly_sound_debounce_time = NPCInfo->burstCount = 0; + TIMER_Set( NPC, "attackDelay", Q_irand( 2000, 3500) ); + NPC->flags |= FL_SHIELDED; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/sentry/misc/sentry_shield_close" ); + } + } + else + { + Sentry_Fire(); + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Sentry_Hunt( visible, advance ); + } +} + +/* +------------------------- +Sentry_AttackDecision +------------------------- +*/ +void Sentry_AttackDecision( void ) +{ + // Always keep a good height off the ground + Sentry_MaintainHeight(); + + NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" ); + + //randomly talk + if ( TIMER_Done(NPC,"patrolNoise") ) + { + if (TIMER_Done(NPC,"angerNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); + } + } + + // He's dead. + if (NPC->enemy->health<1) + { + NPC->enemy = NULL; + Sentry_Idle(); + return; + } + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt() == qfalse ) + { + Sentry_Idle(); + return; + } + + // Rate our distance to the target and visibilty + float distance = (int) DistanceHorizontalSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + qboolean visible = NPC_ClearLOS( NPC->enemy ); + qboolean advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Sentry_Hunt( visible, advance ); + return; + } + } + + NPC_FaceEnemy( qtrue ); + + Sentry_RangedAttack( visible, advance ); +} + +qboolean NPC_CheckPlayerTeamStealth( void ); + +/* +------------------------- +NPC_Sentry_Patrol +------------------------- +*/ +void NPC_Sentry_Patrol( void ) +{ + Sentry_MaintainHeight(); + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( UpdateGoal() ) + { + //start loop sound once we move + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + //randomly talk + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSSentry_Default +------------------------- +*/ +void NPC_BSSentry_Default( void ) +{ + if ( NPC->targetname ) + { + NPC->e_UseFunc = useF_sentry_use; + } + + if (( NPC->enemy ) && (NPCInfo->localState != LSTATE_WAKEUP)) + { + // Don't attack if waking up or if no enemy + Sentry_AttackDecision(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + NPC_Sentry_Patrol(); + } + else + { + Sentry_Idle(); + } +} diff --git a/code/game/AI_Sniper.cpp b/code/game/AI_Sniper.cpp new file mode 100644 index 0000000..b34d556 --- /dev/null +++ b/code/game/AI_Sniper.cpp @@ -0,0 +1,911 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +#include "g_navigator.h" + +extern void CG_DrawAlert( vec3_t origin, float rating ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern qboolean FlyingCreature( gentity_t *ent ); +extern void Saboteur_Cloak( gentity_t *self ); + +//extern CNavigator navigator; + +#define SPF_NO_HIDE 2 + +#define MAX_VIEW_DIST 1024 +#define MAX_VIEW_SPEED 250 +#define MAX_LIGHT_INTENSITY 255 +#define MIN_LIGHT_THRESHOLD 0.1 + +#define DISTANCE_SCALE 0.25f +#define DISTANCE_THRESHOLD 0.075f +#define SPEED_SCALE 0.25f +#define FOV_SCALE 0.5f +#define LIGHT_SCALE 0.25f + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 ) + +extern void NPC_Tusken_Taunt( void ); +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS; +static qboolean enemyCS; +static qboolean faceEnemy; +static qboolean move; +static qboolean shoot; +static float enemyDist; + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void Sniper_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "duck", 0 ); + TIMER_Set( ent, "stand", 0 ); + TIMER_Set( ent, "shuffleTime", 0 ); + TIMER_Set( ent, "sleepTime", 0 ); + TIMER_Set( ent, "enemyLastVisible", 0 ); + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "hideTime", 0 ); + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "stick", 0 ); + TIMER_Set( ent, "scoutTime", 0 ); + TIMER_Set( ent, "flee", 0 ); + TIMER_Set( ent, "taunting", 0 ); +} + +void NPC_Sniper_PlayConfusionSound( gentity_t *self ) +{//FIXME: make this a custom sound in sound set + if ( self->health > 0 ) + { + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + TIMER_Set( self, "flee", 0 ); + self->NPC->squadState = SQUAD_IDLE; + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} + + +/* +------------------------- +NPC_ST_Pain +------------------------- +*/ + +void NPC_Sniper_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod ) +{ + self->NPC->localState = LSTATE_UNDERFIRE; + + if ( self->client->NPC_class == CLASS_SABOTEUR ) + { + Saboteur_Decloak( self ); + } + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stand", 2000 ); + + NPC_Pain( self, inflictor, other, point, damage, mod ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } +} + +/* +------------------------- +ST_HoldPosition +------------------------- +*/ + +static void Sniper_HoldPosition( void ) +{ + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + NPCInfo->goalEntity = NULL; + + /*if ( TIMER_Done( NPC, "stand" ) ) + {//FIXME: what if can't shoot from this pos? + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +/* +------------------------- +ST_Move +------------------------- +*/ + +static qboolean Sniper_Move( void ) +{ + NPCInfo->combatMove = qtrue;//always move straight toward our goal + + qboolean moved = NPC_MoveToGoal( qtrue ); +// navInfo_t info; + + //Get the move info +// NAV_GetLastMove( info ); + + //FIXME: if we bump into another one of our guys and can't get around him, just stop! + //If we hit our target, then stop and fire! +// if ( info.flags & NIF_COLLISION ) +// { +// if ( info.blocker == NPC->enemy ) +// { +// Sniper_HoldPosition(); +// } +// } + + //If our move failed, then reset + if ( moved == qfalse ) + {//couldn't get to enemy + if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) + {//we were running after enemy + //Try to find a combat point that can hit the enemy + int cpFlags = (CP_CLEAR|CP_HAS_ROUTE); + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + int cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, cpFlags, 32 ); + if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + {//okay, try one by the enemy + cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32 ); + } + //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him... + if ( cp != -1 ) + {//found a combat point that has a clear shot to enemy + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp ); + return moved; + } + } + //just hang here + Sniper_HoldPosition(); + } + + return moved; +} + +/* +------------------------- +NPC_BSSniper_Patrol +------------------------- +*/ + +void NPC_BSSniper_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + NPC->count = 0; + + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//Should be auto now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + //Is there danger nearby + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); + if ( NPC_CheckForDanger( alertEvent ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + {//check for other alert events + //There is an event to look at + if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; + if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED ) + { + if ( level.alertEvents[alertEvent].owner && + level.alertEvents[alertEvent].owner->client && + level.alertEvents[alertEvent].owner->health >= 0 && + level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + //NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*100, (6-NPCInfo->stats.aim)*500 ) ); + } + } + else + {//FIXME: get more suspicious over time? + //Save the position for movement (if necessary) + //FIXME: sound? + VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); + NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); + if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) + {//suspicious looks longer + NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); + } + } + } + } + + if ( NPCInfo->investigateDebounceTime > level.time ) + {//FIXME: walk over to it, maybe? Not if not chase enemies flag + //NOTE: stops walking or doing anything else below + vec3_t dir, angles; + float o_yaw, o_pitch; + + VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); + vectoangles( dir, angles ); + + o_yaw = NPCInfo->desiredYaw; + o_pitch = NPCInfo->desiredPitch; + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + NPC_UpdateAngles( qtrue, qtrue ); + + NPCInfo->desiredYaw = o_yaw; + NPCInfo->desiredPitch = o_pitch; + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSSniper_Idle +------------------------- +*/ +/* +void NPC_BSSniper_Idle( void ) +{ + //reset our shotcount + NPC->count = 0; + + //FIXME: check for other alert events? + + //Is there danger nearby? + if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue ) ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) ); + + NPC_UpdateAngles( qtrue, qtrue ); +} +*/ +/* +------------------------- +ST_CheckMoveState +------------------------- +*/ + +static void Sniper_CheckMoveState( void ) +{ + //See if we're a scout + if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT ) + { + if ( NPCInfo->goalEntity == NPC->enemy ) + { + move = qfalse; + return; + } + } + //See if we're running away + else if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + if ( TIMER_Done( NPC, "flee" ) ) + { + NPCInfo->squadState = SQUAD_IDLE; + } + else + { + faceEnemy = qfalse; + } + } + else if ( NPCInfo->squadState == SQUAD_IDLE ) + { + if ( !NPCInfo->goalEntity ) + { + move = qfalse; + return; + } + } + + if ( !TIMER_Done( NPC, "taunting" ) ) + {//no move while taunting + move = qfalse; + return; + } + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( STEER::Reached(NPC, NPCInfo->goalEntity, 16, !!FlyingCreature(NPC)) || + ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) ) + { + int newSquadState = SQUAD_STAND_AND_SHOOT; + //we got where we wanted to go, set timers based on why we were running + switch ( NPCInfo->squadState ) + { + case SQUAD_RETREAT://was running away + if ( NPC->client->NPC_class == CLASS_SABOTEUR ) + { + Saboteur_Cloak( NPC ); + } + TIMER_Set( NPC, "duck", (NPC->max_health - NPC->health) * 100 ); + TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); + newSquadState = SQUAD_COVER; + break; + case SQUAD_TRANSITION://was heading for a combat point + TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); + break; + case SQUAD_SCOUT://was running after player + break; + default: + break; + } + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*50, (6-NPCInfo->stats.aim)*100 ) ); //FIXME: Slant for difficulty levels, too? + //don't do something else just yet + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); + //stop fleeing + if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + TIMER_Set( NPC, "flee", -level.time ); + NPCInfo->squadState = SQUAD_IDLE; + } + return; + } + + //keep going, hold of roamTimer until we get there + TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); + } +} + +static void Sniper_ResolveBlockedShot( void ) +{ + if ( TIMER_Done( NPC, "duck" ) ) + {//we're not ducking + if ( TIMER_Done( NPC, "roamTime" ) ) + {//not roaming + //FIXME: try to find another spot from which to hit the enemy + if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && (!NPCInfo->goalEntity || NPCInfo->goalEntity == NPC->enemy) ) + {//we were running after enemy + //Try to find a combat point that can hit the enemy + int cpFlags = (CP_CLEAR|CP_HAS_ROUTE); + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + int cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, cpFlags, 32 ); + if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + {//okay, try one by the enemy + cp = NPC_FindCombatPoint( NPC->currentOrigin, NPC->currentOrigin, NPC->enemy->currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32 ); + } + //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him... + if ( cp != -1 ) + {//found a combat point that has a clear shot to enemy + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp ); + TIMER_Set( NPC, "duck", -1 ); + if ( NPC->client->NPC_class == CLASS_SABOTEUR ) + { + Saboteur_Decloak( NPC ); + } + TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) ); + return; + } + } + } + } + /* + else + {//maybe we should stand + if ( TIMER_Done( NPC, "stand" ) ) + {//stand for as long as we'll be here + TIMER_Set( NPC, "stand", Q_irand( 500, 2000 ) ); + return; + } + } + //Hmm, can't resolve this by telling them to duck or telling me to stand + //We need to move! + TIMER_Set( NPC, "roamTime", -1 ); + TIMER_Set( NPC, "stick", -1 ); + TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) ); + */ +} + +/* +------------------------- +ST_CheckFireState +------------------------- +*/ + +static void Sniper_CheckFireState( void ) +{ + if ( enemyCS ) + {//if have a clear shot, always try + return; + } + + if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT ) + {//runners never try to fire at the last pos + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + if ( !TIMER_Done( NPC, "taunting" ) ) + {//no shoot while taunting + return; + } + + //continue to fire on their last position + if ( !Q_irand( 0, 1 ) + && NPCInfo->enemyLastSeenTime + && level.time - NPCInfo->enemyLastSeenTime < ((5-NPCInfo->stats.aim)*1000) )//FIXME: incorporate skill too? + { + if ( !VectorCompare( vec3_origin, NPCInfo->enemyLastSeenLocation ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + + VectorNormalize( dir ); + + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot = qtrue; + //faceEnemy = qfalse; + } + return; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 10000 ) + {//next time we see him, we'll miss few times first + NPC->count = 0; + } +} + +qboolean Sniper_EvaluateShot( int hit ) +{ + if ( !NPC->enemy ) + { + return qfalse; + } + + gentity_t *hitEnt = &g_entities[hit]; + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage && ((hitEnt->svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) + || ( hitEnt && (hitEnt->svFlags&SVF_GLASS_BRUSH)) ) + {//can hit enemy or will hit glass, so shoot anyway + return qtrue; + } + return qfalse; +} + +void Sniper_FaceEnemy( void ) +{ + //FIXME: the ones behind kill holes are facing some arbitrary direction and not firing + //FIXME: If actually trying to hit enemy, don't fire unless enemy is at least in front of me? + //FIXME: need to give designers option to make them not miss first few shots + if ( NPC->enemy ) + { + vec3_t muzzle, target, angles, forward, right, up; + //Get the positions + AngleVectors( NPC->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( NPC, forward, right, up, muzzle, 0 ); + //CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + CalcEntitySpot( NPC->enemy, SPOT_ORIGIN, target ); + + if ( enemyDist > 65536 && NPCInfo->stats.aim < 5 )//is 256 squared, was 16384 (128*128) + { + if ( NPC->count < (5-NPCInfo->stats.aim) ) + {//miss a few times first + if ( shoot && TIMER_Done( NPC, "attackDelay" ) && level.time >= NPCInfo->shotTime ) + {//ready to fire again + qboolean aimError = qfalse; + qboolean hit = qtrue; + int tryMissCount = 0; + trace_t trace; + + GetAnglesForDirection( muzzle, target, angles ); + AngleVectors( angles, forward, right, up ); + + while ( hit && tryMissCount < 10 ) + { + tryMissCount++; + if ( !Q_irand( 0, 1 ) ) + { + aimError = qtrue; + if ( !Q_irand( 0, 1 ) ) + { + VectorMA( target, NPC->enemy->maxs[2]*Q_flrand(1.5, 4), right, target ); + } + else + { + VectorMA( target, NPC->enemy->mins[2]*Q_flrand(1.5, 4), right, target ); + } + } + if ( !aimError || !Q_irand( 0, 1 ) ) + { + if ( !Q_irand( 0, 1 ) ) + { + VectorMA( target, NPC->enemy->maxs[2]*Q_flrand(1.5, 4), up, target ); + } + else + { + VectorMA( target, NPC->enemy->mins[2]*Q_flrand(1.5, 4), up, target ); + } + } + gi.trace( &trace, muzzle, vec3_origin, vec3_origin, target, NPC->s.number, MASK_SHOT ); + hit = Sniper_EvaluateShot( trace.entityNum ); + } + NPC->count++; + } + else + { + if ( !enemyLOS ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + else + {//based on distance, aim value, difficulty and enemy movement, miss + //FIXME: incorporate distance as a factor? + int missFactor = 8-(NPCInfo->stats.aim+g_spskill->integer) * 3; + if ( missFactor > ENEMY_POS_LAG_STEPS ) + { + missFactor = ENEMY_POS_LAG_STEPS; + } + else if ( missFactor < 0 ) + {//??? + missFactor = 0 ; + } + VectorCopy( NPCInfo->enemyLaggedPos[missFactor], target ); + } + GetAnglesForDirection( muzzle, target, angles ); + } + else + { + target[2] += Q_flrand( 0, NPC->enemy->maxs[2] ); + //CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, target ); + GetAnglesForDirection( muzzle, target, angles ); + } + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + } + NPC_UpdateAngles( qtrue, qtrue ); +} + +void Sniper_UpdateEnemyPos( void ) +{ + int index; + for ( int i = MAX_ENEMY_POS_LAG-ENEMY_POS_LAG_INTERVAL; i >= 0; i -= ENEMY_POS_LAG_INTERVAL ) + { + index = i/ENEMY_POS_LAG_INTERVAL; + if ( !index ) + { + CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, NPCInfo->enemyLaggedPos[index] ); + NPCInfo->enemyLaggedPos[index][2] -= Q_flrand( 2, 16 ); + } + else + { + VectorCopy( NPCInfo->enemyLaggedPos[index-1], NPCInfo->enemyLaggedPos[index] ); + } + } +} + +/* +------------------------- +NPC_BSSniper_Attack +------------------------- +*/ + +void Sniper_StartHide( void ) +{ + int duckTime = Q_irand( 2000, 5000 ); + + TIMER_Set( NPC, "duck", duckTime ); + if ( NPC->client->NPC_class == CLASS_SABOTEUR ) + { + Saboteur_Cloak( NPC ); + } + TIMER_Set( NPC, "watch", 500 ); + TIMER_Set( NPC, "attackDelay", duckTime + Q_irand( 500, 2000 ) ); +} + +void NPC_BSSniper_Attack( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //NPC_CheckEnemy( qtrue, qfalse ); + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )// + { + NPC_BSSniper_Patrol();//FIXME: or patrol? + return; + } + + if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//going to run + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( !NPC->enemy ) + {//WTF? somehow we lost our enemy? + NPC_BSSniper_Patrol();//FIXME: or patrol? + return; + } + + enemyLOS = enemyCS = qfalse; + move = qtrue; + faceEnemy = qfalse; + shoot = qfalse; + enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + if ( enemyDist < 16384 )//128 squared + {//too close, so switch to primary fire + if ( NPC->client->ps.weapon == WP_DISRUPTOR + || NPC->client->ps.weapon == WP_TUSKEN_RIFLE ) + {//sniping... should be assumed + if ( NPCInfo->scriptFlags & SCF_ALT_FIRE ) + {//use primary fire + trace_t trace; + gi.trace ( &trace, NPC->enemy->currentOrigin, NPC->enemy->mins, NPC->enemy->maxs, NPC->currentOrigin, NPC->enemy->s.number, NPC->enemy->clipmask ); + if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->s.number ) ) + {//he can get right to me + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( NPC->client->ps.weapon ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + //FIXME: switch back if he gets far away again? + } + } + else if ( enemyDist > 65536 )//256 squared + { + if ( NPC->client->ps.weapon == WP_DISRUPTOR + || NPC->client->ps.weapon == WP_TUSKEN_RIFLE ) + {//sniping... should be assumed + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + {//use primary fire + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( NPC->client->ps.weapon ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + Sniper_UpdateEnemyPos(); + //can we see our target? + if ( NPC_ClearLOS( NPC->enemy ) )//|| (NPCInfo->stats.aim >= 5 && gi.inPVS( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin )) ) + { + NPCInfo->enemyLastSeenTime = level.time; + VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); + enemyLOS = qtrue; + float maxShootDist = NPC_MaxDistSquaredForWeapon(); + if ( enemyDist < maxShootDist ) + { + vec3_t fwd, right, up, muzzle, end; + trace_t tr; + AngleVectors( NPC->client->ps.viewangles, fwd, right, up ); + CalcMuzzlePoint( NPC, fwd, right, up, muzzle, 0 ); + VectorMA( muzzle, 8192, fwd, end ); + gi.trace ( &tr, muzzle, NULL, NULL, end, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 0 ); + + int hit = tr.entityNum; + //can we shoot our target? + if ( Sniper_EvaluateShot( hit ) ) + { + enemyCS = qtrue; + } + } + } + /* + else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + } + */ + + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + + if ( !TIMER_Done( NPC, "taunting" ) ) + { + move = qfalse; + shoot = qfalse; + } + else if ( enemyCS ) + { + shoot = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 3000 ) + {//Hmm, have to get around this bastard... FIXME: this NPCInfo->enemyLastSeenTime builds up when ducked seems to make them want to run when they uncrouch + Sniper_ResolveBlockedShot(); + } + else if ( NPC->client->ps.weapon == WP_TUSKEN_RIFLE && !Q_irand( 0, 100 ) ) + {//start a taunt + NPC_Tusken_Taunt(); + TIMER_Set( NPC, "duck", -1 ); + move = qfalse; + } + + //Check for movement to take care of + Sniper_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + Sniper_CheckFireState(); + + if ( move ) + {//move toward goal + if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared + { + move = Sniper_Move(); + } + else + { + move = qfalse; + } + } + + if ( !move ) + { + if ( !TIMER_Done( NPC, "duck" ) ) + { + if ( TIMER_Done( NPC, "watch" ) ) + {//not while watching + ucmd.upmove = -127; + if ( NPC->client->NPC_class == CLASS_SABOTEUR ) + { + Saboteur_Cloak( NPC ); + } + } + } + //FIXME: what about leaning? + //FIXME: also, when stop ducking, start looking, if enemy can see me, chance of ducking back down again + } + else + {//stop ducking! + TIMER_Set( NPC, "duck", -1 ); + if ( NPC->client->NPC_class == CLASS_SABOTEUR ) + { + Saboteur_Decloak( NPC ); + } + } + + if ( TIMER_Done( NPC, "duck" ) + && TIMER_Done( NPC, "watch" ) + && (TIMER_Get( NPC, "attackDelay" )-level.time) > 1000 + && NPC->attackDebounceTime < level.time ) + { + if ( enemyLOS && (NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + { + if ( NPC->fly_sound_debounce_time < level.time ) + { + NPC->fly_sound_debounce_time = level.time + 2000; + } + } + } + + if ( !faceEnemy ) + {//we want to face in the dir we're running + if ( move ) + {//don't run away and shoot + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + shoot = qfalse; + } + NPC_UpdateAngles( qtrue, qtrue ); + } + else// if ( faceEnemy ) + {//face the enemy + Sniper_FaceEnemy(); + } + + if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) + { + shoot = qfalse; + } + + //FIXME: don't shoot right away! + if ( shoot ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + WeaponThink( qtrue ); + if ( ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK) ) + { + G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/null.wav" ); + } + + //took a shot, now hide + if ( !(NPC->spawnflags&SPF_NO_HIDE) && !Q_irand( 0, 1 ) ) + { + //FIXME: do this if in combat point and combat point has duck-type cover... also handle lean-type cover + Sniper_StartHide(); + } + else + { + TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time ); + } + } + } +} + +void NPC_BSSniper_Default( void ) +{ + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSSniper_Patrol(); + } + else//if ( NPC->enemy ) + {//have an enemy + NPC_BSSniper_Attack(); + } +} diff --git a/code/game/AI_Stormtrooper.cpp b/code/game/AI_Stormtrooper.cpp new file mode 100644 index 0000000..fbb3868 --- /dev/null +++ b/code/game/AI_Stormtrooper.cpp @@ -0,0 +1,2722 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +#include "g_navigator.h" + +extern void CG_DrawAlert( vec3_t origin, float rating ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState ); +extern qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum ); +extern void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot ); +extern void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void NPC_CheckGetNewWeapon( void ); +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); +extern int GetTime ( int lastTime ); +extern void NPC_AimAdjust( int change ); +extern qboolean FlyingCreature( gentity_t *ent ); +extern void NPC_EvasionSaber( void ); +extern qboolean RT_Flying( gentity_t *self ); + +//extern CNavigator navigator; +extern cvar_t *d_asynchronousGroupAI; + +#define MAX_VIEW_DIST 1024 +#define MAX_VIEW_SPEED 250 +#define MAX_LIGHT_INTENSITY 255 +#define MIN_LIGHT_THRESHOLD 0.1 +#define ST_MIN_LIGHT_THRESHOLD 30 +#define ST_MAX_LIGHT_THRESHOLD 180 +#define DISTANCE_THRESHOLD 0.075f +#define MIN_TURN_AROUND_DIST_SQ (10000) //(100 squared) don't stop running backwards if your goal is less than 100 away +#define SABER_AVOID_DIST 128.0f//256.0f +#define SABER_AVOID_DIST_SQ (SABER_AVOID_DIST*SABER_AVOID_DIST) + +#define DISTANCE_SCALE 0.35f //These first three get your base detection rating, ideally add up to 1 +#define FOV_SCALE 0.40f // +#define LIGHT_SCALE 0.25f // + +#define SPEED_SCALE 0.25f //These next two are bonuses +#define TURNING_SCALE 0.25f // + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 ) + +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS; +static qboolean enemyCS; +static qboolean enemyInFOV; +static qboolean hitAlly; +static qboolean faceEnemy; +static qboolean move; +static qboolean shoot; +static float enemyDist; +static vec3_t impactPos; + +int groupSpeechDebounceTime[TEAM_NUM_TEAMS];//used to stop several group AI from speaking all at once + +void NPC_Saboteur_Precache( void ) +{ + G_SoundIndex( "sound/chars/shadowtrooper/cloak.wav" ); + G_SoundIndex( "sound/chars/shadowtrooper/decloak.wav" ); +} + +void Saboteur_Decloak( gentity_t *self, int uncloakTime ) +{ + if ( self && self->client ) + { + if ( self->client->ps.powerups[PW_CLOAKED] && TIMER_Done(self, "decloakwait")) + {//Uncloak + self->client->ps.powerups[PW_CLOAKED] = 0; + self->client->ps.powerups[PW_UNCLOAKING] = level.time + 2000; + //FIXME: temp sound + G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/shadowtrooper/decloak.wav" ); + TIMER_Set( self, "nocloak", uncloakTime ); + + // Can't Recloak + //self->NPC->aiFlags &= ~NPCAI_SHIELDS; + } + } +} + +void Saboteur_Cloak( gentity_t *self ) +{ + if ( self && self->client && self->NPC ) + {//FIXME: need to have this timer set once first? + if ( TIMER_Done( self, "nocloak" ) ) + {//not sitting around waiting to cloak again + if ( !(self->NPC->aiFlags&NPCAI_SHIELDS) ) + {//not allowed to cloak, actually + Saboteur_Decloak( self ); + } + else if ( !self->client->ps.powerups[PW_CLOAKED] ) + {//cloak + self->client->ps.powerups[PW_CLOAKED] = Q3_INFINITE; + self->client->ps.powerups[PW_UNCLOAKING] = level.time + 2000; + //FIXME: debounce attacks? + //FIXME: temp sound + G_SoundOnEnt( self, CHAN_ITEM, "sound/chars/shadowtrooper/cloak.wav" ); + } + } + } +} + + + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void ST_AggressionAdjust( gentity_t *self, int change ) +{ + int upper_threshold, lower_threshold; + + self->NPC->stats.aggression += change; + + //FIXME: base this on initial NPC stats + if ( self->client->playerTeam == TEAM_PLAYER ) + {//good guys are less aggressive + upper_threshold = 7; + lower_threshold = 1; + } + else + {//bad guys are more aggressive + upper_threshold = 10; + lower_threshold = 3; + } + + if ( self->NPC->stats.aggression > upper_threshold ) + { + self->NPC->stats.aggression = upper_threshold; + } + else if ( self->NPC->stats.aggression < lower_threshold ) + { + self->NPC->stats.aggression = lower_threshold; + } +} + +void ST_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "duck", 0 ); + TIMER_Set( ent, "stand", 0 ); + TIMER_Set( ent, "shuffleTime", 0 ); + TIMER_Set( ent, "sleepTime", 0 ); + TIMER_Set( ent, "enemyLastVisible", 0 ); + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "hideTime", 0 ); + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "stick", 0 ); + TIMER_Set( ent, "scoutTime", 0 ); + TIMER_Set( ent, "flee", 0 ); + TIMER_Set( ent, "interrogating", 0 ); + TIMER_Set( ent, "verifyCP", 0 ); + TIMER_Set( ent, "strafeRight", 0 ); + TIMER_Set( ent, "strafeLeft", 0 ); +} + +enum +{ + SPEECH_CHASE, + SPEECH_CONFUSED, + SPEECH_COVER, + SPEECH_DETECTED, + SPEECH_GIVEUP, + SPEECH_LOOK, + SPEECH_LOST, + SPEECH_OUTFLANK, + SPEECH_ESCAPING, + SPEECH_SIGHT, + SPEECH_SOUND, + SPEECH_SUSPICIOUS, + SPEECH_YELL, + SPEECH_PUSHED +}; + +static void ST_Speech( gentity_t *self, int speechType, float failChance ) +{ + if ( random() < failChance ) + { + return; + } + + if ( failChance >= 0 ) + {//a negative failChance makes it always talk + if ( self->NPC->group ) + {//group AI speech debounce timer + if ( self->NPC->group->speechDebounceTime > level.time ) + { + return; + } + /* + else if ( !self->NPC->group->enemy ) + { + if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time ) + { + return; + } + } + */ + } + else if ( !TIMER_Done( self, "chatter" ) ) + {//personal timer + return; + } + else if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time ) + {//for those not in group AI + //FIXME: let certain speech types interrupt others? Let closer NPCs interrupt farther away ones? + return; + } + } + + if ( self->NPC->group ) + {//So they don't all speak at once... + //FIXME: if they're not yet mad, they have no group, so distracting a group of them makes them all speak! + self->NPC->group->speechDebounceTime = level.time + Q_irand( 2000, 4000 ); + } + else + { + TIMER_Set( self, "chatter", Q_irand( 2000, 4000 ) ); + } + groupSpeechDebounceTime[self->client->playerTeam] = level.time + Q_irand( 2000, 4000 ); + + if ( self->NPC->blockedSpeechDebounceTime > level.time ) + { + return; + } + + switch( speechType ) + { + case SPEECH_CHASE: + G_AddVoiceEvent( self, Q_irand(EV_CHASE1, EV_CHASE3), 2000 ); + break; + case SPEECH_CONFUSED: + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + break; + case SPEECH_COVER: + G_AddVoiceEvent( self, Q_irand(EV_COVER1, EV_COVER5), 2000 ); + break; + case SPEECH_DETECTED: + G_AddVoiceEvent( self, Q_irand(EV_DETECTED1, EV_DETECTED5), 2000 ); + break; + case SPEECH_GIVEUP: + G_AddVoiceEvent( self, Q_irand(EV_GIVEUP1, EV_GIVEUP4), 2000 ); + break; + case SPEECH_LOOK: + G_AddVoiceEvent( self, Q_irand(EV_LOOK1, EV_LOOK2), 2000 ); + break; + case SPEECH_LOST: + G_AddVoiceEvent( self, EV_LOST1, 2000 ); + break; + case SPEECH_OUTFLANK: + G_AddVoiceEvent( self, Q_irand(EV_OUTFLANK1, EV_OUTFLANK2), 2000 ); + break; + case SPEECH_ESCAPING: + G_AddVoiceEvent( self, Q_irand(EV_ESCAPING1, EV_ESCAPING3), 2000 ); + break; + case SPEECH_SIGHT: + G_AddVoiceEvent( self, Q_irand(EV_SIGHT1, EV_SIGHT3), 2000 ); + break; + case SPEECH_SOUND: + G_AddVoiceEvent( self, Q_irand(EV_SOUND1, EV_SOUND3), 2000 ); + break; + case SPEECH_SUSPICIOUS: + G_AddVoiceEvent( self, Q_irand(EV_SUSPICIOUS1, EV_SUSPICIOUS5), 2000 ); + break; + case SPEECH_YELL: + G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 2000 ); + break; + case SPEECH_PUSHED: + G_AddVoiceEvent( self, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 2000 ); + break; + default: + break; + } + + self->NPC->blockedSpeechDebounceTime = level.time + 2000; +} + +void ST_MarkToCover( gentity_t *self ) +{ + if ( !self || !self->NPC ) + { + return; + } + self->NPC->localState = LSTATE_UNDERFIRE; + TIMER_Set( self, "attackDelay", Q_irand( 500, 2500 ) ); + ST_AggressionAdjust( self, -3 ); + if ( self->NPC->group && self->NPC->group->numGroup > 1 ) + { + ST_Speech( self, SPEECH_COVER, 0 );//FIXME: flee sound? + } +} + +void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime ) +{ + if ( !self || !self->NPC ) + { + return; + } + G_StartFlee( self, enemy, dangerPoint, dangerLevel, minTime, maxTime ); + if ( self->NPC->group && self->NPC->group->numGroup > 1 ) + { + ST_Speech( self, SPEECH_COVER, 0 );//FIXME: flee sound? + } +} +/* +------------------------- +NPC_ST_Pain +------------------------- +*/ + +void NPC_ST_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + self->NPC->localState = LSTATE_UNDERFIRE; + + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "hideTime", -1 ); + TIMER_Set( self, "stand", 2000 ); + + NPC_Pain( self, inflictor, other, point, damage, mod, hitLoc ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } +} + +/* +------------------------- +ST_HoldPosition +------------------------- +*/ + +static void ST_HoldPosition( void ) +{ + if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + TIMER_Set( NPC, "flee", -level.time ); + } + TIMER_Set( NPC, "verifyCP", Q_irand( 1000, 3000 ) );//don't look for another one for a few seconds + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + //NPCInfo->combatPoint = -1;//??? + if ( !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//don't have a script waiting for me to get to my point, okay to stop trying and stand + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + NPCInfo->goalEntity = NULL; + } + +} + +void NPC_ST_SayMovementSpeech( void ) +{ + if ( !NPCInfo->movementSpeech ) + { + return; + } + if ( NPCInfo->group && + NPCInfo->group->commander && + NPCInfo->group->commander->client && + NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL && + !Q_irand( 0, 3 ) ) + {//imperial (commander) gives the order + ST_Speech( NPCInfo->group->commander, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance ); + } + else + {//really don't want to say this unless we can actually get there... + ST_Speech( NPC, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance ); + } + + NPCInfo->movementSpeech = 0; + NPCInfo->movementSpeechChance = 0.0f; +} + +void NPC_ST_StoreMovementSpeech( int speech, float chance ) +{ + NPCInfo->movementSpeech = speech; + NPCInfo->movementSpeechChance = chance; +} +/* +------------------------- +ST_Move +------------------------- +*/ +void ST_TransferMoveGoal( gentity_t *self, gentity_t *other ); +static qboolean ST_Move( void ) +{ + NPCInfo->combatMove = qtrue;//always move straight toward our goal + + qboolean moved = NPC_MoveToGoal( qtrue ); + if (moved==qfalse) + { + ST_HoldPosition(); + } + + NPC_ST_SayMovementSpeech(); + + return moved; +} + + +/* +------------------------- +NPC_ST_SleepShuffle +------------------------- +*/ + +static void NPC_ST_SleepShuffle( void ) +{ + //Play an awake script if we have one + if ( G_ActivateBehavior( NPC, BSET_AWAKE) ) + { + return; + } + + //Automate some movement and noise + if ( TIMER_Done( NPC, "shuffleTime" ) ) + { + + //TODO: Play sleeping shuffle animation + + //int soundIndex = Q_irand( 0, 1 ); + + /* + switch ( soundIndex ) + { + case 0: + G_Sound( NPC, G_SoundIndex("sound/chars/imperialsleeper1/scav4/hunh.mp3") ); + break; + + case 1: + G_Sound( NPC, G_SoundIndex("sound/chars/imperialsleeper3/scav4/tryingtosleep.wav") ); + break; + } + */ + + TIMER_Set( NPC, "shuffleTime", 4000 ); + TIMER_Set( NPC, "sleepTime", 2000 ); + return; + } + + //They made another noise while we were stirring, see if we can see them + if ( TIMER_Done( NPC, "sleepTime" ) ) + { + NPC_CheckPlayerTeamStealth(); + TIMER_Set( NPC, "sleepTime", 2000 ); + } +} + +/* +------------------------- +NPC_ST_Sleep +------------------------- +*/ + +void NPC_BSST_Sleep( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qfalse, qtrue );//only check sounds since we're alseep! + + //There is an event we heard + if ( alertEvent >= 0 ) + { + //See if it was enough to wake us up + if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { + if ( &g_entities[0] && g_entities[0].health > 0 ) + { + G_SetEnemy( NPC, &g_entities[0] ); + return; + } + } + + //Otherwise just stir a bit + NPC_ST_SleepShuffle(); + return; + } +} + +/* +------------------------- +NPC_CheckEnemyStealth +------------------------- +*/ + +qboolean NPC_CheckEnemyStealth( gentity_t *target ) +{ + float target_dist, minDist = 40;//any closer than 40 and we definitely notice + + //In case we aquired one some other way + if ( NPC->enemy != NULL ) + return qtrue; + + //Ignore notarget + if ( target->flags & FL_NOTARGET ) + return qfalse; + + if ( target->health <= 0 ) + { + return qfalse; + } + + if ( target->client->ps.weapon == WP_SABER && target->client->ps.SaberActive() && !target->client->ps.saberInFlight ) + {//if target has saber in hand and activated, we wake up even sooner even if not facing him + minDist = 100; + } + + target_dist = DistanceSquared( target->currentOrigin, NPC->currentOrigin ); + //If the target is this close, then wake up regardless + if ( !(target->client->ps.pm_flags&PMF_DUCKED)//not ducking + && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES)//looking for enemies + && target_dist < (minDist*minDist) )//closer than minDist + { + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + float maxViewDist = MAX_VIEW_DIST; + +// if ( NPCInfo->stats.visrange > maxViewDist ) + {//FIXME: should we always just set maxViewDist to this? + maxViewDist = NPCInfo->stats.visrange; + } + + if ( target_dist > (maxViewDist*maxViewDist) ) + {//out of possible visRange + return qfalse; + } + + //Check FOV first + if ( InFOV( target, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) + return qfalse; + + qboolean clearLOS = ( target->client->ps.leanofs ) ? NPC_ClearLOS( target->client->renderInfo.eyePoint ) : NPC_ClearLOS( target ); + + //Now check for clear line of vision + if ( clearLOS ) + { + if ( target->client->NPC_class == CLASS_ATST ) + {//can't miss 'em! + G_SetEnemy( NPC, target ); + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + vec3_t targ_org = {target->currentOrigin[0],target->currentOrigin[1],target->currentOrigin[2]+target->maxs[2]-4}; + float hAngle_perc = NPC_GetHFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.hfov ); + float vAngle_perc = NPC_GetVFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.vfov ); + + //Scale them vertically some, and horizontally pretty harshly + vAngle_perc *= vAngle_perc;//( vAngle_perc * vAngle_perc ); + hAngle_perc *= ( hAngle_perc * hAngle_perc ); + + //Cap our vertical vision severely + //if ( vAngle_perc <= 0.3f ) // was 0.5f + // return qfalse; + + //Assess the player's current status + target_dist = Distance( target->currentOrigin, NPC->currentOrigin ); + + float target_speed = VectorLength( target->client->ps.velocity ); + int target_crouching = ( target->client->usercmd.upmove < 0 ); + float dist_rating = ( target_dist / maxViewDist ); + float speed_rating = ( target_speed / MAX_VIEW_SPEED ); + float turning_rating = AngleDelta( target->client->ps.viewangles[PITCH], target->lastAngles[PITCH] )/180.0f + AngleDelta( target->client->ps.viewangles[YAW], target->lastAngles[YAW] )/180.0f; + float light_level = ( target->lightLevel / MAX_LIGHT_INTENSITY ); + float FOV_perc = 1.0f - ( hAngle_perc + vAngle_perc ) * 0.5f; //FIXME: Dunno about the average... + float vis_rating = 0.0f; + + //Too dark + if ( light_level < MIN_LIGHT_THRESHOLD ) + return qfalse; + + //Too close? + if ( dist_rating < DISTANCE_THRESHOLD ) + { + G_SetEnemy( NPC, target ); + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + //Out of range + if ( dist_rating > 1.0f ) + return qfalse; + + //Cap our speed checks + if ( speed_rating > 1.0f ) + speed_rating = 1.0f; + + + //Calculate the distance, fov and light influences + //...Visibilty linearly wanes over distance + float dist_influence = DISTANCE_SCALE * ( ( 1.0f - dist_rating ) ); + //...As the percentage out of the FOV increases, straight perception suffers on an exponential scale + float fov_influence = FOV_SCALE * ( 1.0f - FOV_perc ); + //...Lack of light hides, abundance of light exposes + float light_influence = ( light_level - 0.5f ) * LIGHT_SCALE; + + //Calculate our base rating + float target_rating = dist_influence + fov_influence + light_influence; + + //Now award any final bonuses to this number + int contents = gi.pointcontents( targ_org, target->s.number ); + if ( contents&CONTENTS_WATER ) + { + int myContents = gi.pointcontents( NPC->client->renderInfo.eyePoint, NPC->s.number ); + if ( !(myContents&CONTENTS_WATER) ) + {//I'm not in water + if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER ) + {//these guys can see in in/through water pretty well + vis_rating = 0.10f;//10% bonus + } + else + { + vis_rating = 0.35f;//35% bonus + } + } + else + {//else, if we're both in water + if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER ) + {//I can see him just fine + } + else + { + vis_rating = 0.15f;//15% bonus + } + } + } + else + {//not in water + if ( contents&CONTENTS_FOG ) + { + vis_rating = 0.15f;//15% bonus + } + } + + target_rating *= (1.0f - vis_rating); + + //...Motion draws the eye quickly + target_rating += speed_rating * SPEED_SCALE; + target_rating += turning_rating * TURNING_SCALE; + //FIXME: check to see if they're animating, too? But can we do something as simple as frame != oldframe? + + //...Smaller targets are harder to indentify + if ( target_crouching ) + { + target_rating *= 0.9f; //10% bonus + } + + //If he's violated the threshold, then realize him + //float difficulty_scale = 1.0f + (2.0f-g_spskill->value);//if playing on easy, 20% harder to be seen...? + float realize, cautious; + if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER ) + {//swamptroopers can see much better + realize = (float)CAUTIOUS_THRESHOLD/**difficulty_scale*/; + cautious = (float)CAUTIOUS_THRESHOLD * 0.75f/**difficulty_scale*/; + } + else + { + realize = (float)REALIZE_THRESHOLD/**difficulty_scale*/; + cautious = (float)CAUTIOUS_THRESHOLD * 0.75f/**difficulty_scale*/; + } + + if ( target_rating > realize && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + //If he's above the caution threshold, then realize him in a few seconds unless he moves to cover + if ( target_rating > cautious && !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + {//FIXME: ambushing guys should never talk + if ( TIMER_Done( NPC, "enemyLastVisible" ) ) + {//If we haven't already, start the counter + int lookTime = Q_irand( 4500, 8500 ); + //NPCInfo->timeEnemyLastVisible = level.time + 2000; + TIMER_Set( NPC, "enemyLastVisible", lookTime ); + //TODO: Play a sound along the lines of, "Huh? What was that?" + ST_Speech( NPC, SPEECH_SIGHT, 0 ); + NPC_TempLookTarget( NPC, target->s.number, lookTime, lookTime ); + //FIXME: set desired yaw and pitch towards this guy? + } + else if ( TIMER_Get( NPC, "enemyLastVisible" ) <= level.time + 500 && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) //FIXME: Is this reliable? + { + if ( NPCInfo->rank < RANK_LT && !Q_irand( 0, 2 ) ) + { + int interrogateTime = Q_irand( 2000, 4000 ); + ST_Speech( NPC, SPEECH_SUSPICIOUS, 0 ); + TIMER_Set( NPC, "interrogating", interrogateTime ); + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", interrogateTime ); + TIMER_Set( NPC, "stand", interrogateTime ); + } + else + { + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + //FIXME: ambush guys (like those popping out of water) shouldn't delay... + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + TIMER_Set( NPC, "stand", Q_irand( 500, 2500 ) ); + } + return qtrue; + } + + return qfalse; + } + } + + return qfalse; +} + +qboolean NPC_CheckPlayerTeamStealth( void ) +{ + /* + //NOTENOTE: For now, all stealh checks go against the player, since + // he is the main focus. Squad members and rivals do not + // fall into this category and will be ignored. + + NPC_CheckEnemyStealth( &g_entities[0] ); //Change this pointer to assess other entities + */ + gentity_t *enemy; + for ( int i = 0; i < ENTITYNUM_WORLD; i++ ) + { + if(!PInUse(i)) + continue; + enemy = &g_entities[i]; + if ( enemy + && enemy->client + && NPC_ValidEnemy( enemy ) ) + { + if ( NPC_CheckEnemyStealth( enemy ) ) //Change this pointer to assess other entities + { + return qtrue; + } + } + } + return qfalse; +} + +qboolean NPC_CheckEnemiesInSpotlight( void ) +{ + gentity_t *entityList[MAX_GENTITIES]; + gentity_t *enemy, *suspect = NULL; + int i, numListedEntities; + vec3_t mins, maxs; + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = NPC->client->renderInfo.eyePoint[i] - NPC->speed; + maxs[i] = NPC->client->renderInfo.eyePoint[i] + NPC->speed; + } + + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( i = 0; i < numListedEntities; i++ ) + { + if(!PInUse(i)) + continue; + + enemy = entityList[i]; + + if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam ) + {//valid ent & client, valid enemy, on the target team + //check to see if they're in my FOV + if ( InFOV( enemy->currentOrigin, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) ) + {//in my cone + //check to see that they're close enough + if ( DistanceSquared( NPC->client->renderInfo.eyePoint, enemy->currentOrigin )-256/*fudge factor: 16 squared*/ <= NPC->speed*NPC->speed ) + {//within range + //check to see if we have a clear trace to them + if ( G_ClearLOS( NPC, enemy ) ) + {//clear LOS + //make sure their light level is at least my beam's brightness + //FIXME: HOW? + //enemy->lightLevel / MAX_LIGHT_INTENSITY + + //good enough, take him! + //FIXME: pick closest one? + //FIXME: have the graduated noticing like other NPCs? (based on distance, FOV dot, etc...) + G_SetEnemy( NPC, enemy ); + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + } + } + if ( InFOV( enemy->currentOrigin, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, 90, NPCInfo->stats.vfov*3 ) ) + {//one to look at if we don't get an enemy + if ( G_ClearLOS( NPC, enemy ) ) + {//clear LOS + if ( suspect == NULL || DistanceSquared( NPC->client->renderInfo.eyePoint, enemy->currentOrigin ) < DistanceSquared( NPC->client->renderInfo.eyePoint, suspect->currentOrigin ) ) + {//remember him + suspect = enemy; + } + } + } + } + } + if ( suspect && Q_flrand( 0, NPCInfo->stats.visrange*NPCInfo->stats.visrange ) > DistanceSquared( NPC->client->renderInfo.eyePoint, suspect->currentOrigin ) ) + {//hey! who's that? + if ( TIMER_Done( NPC, "enemyLastVisible" ) ) + {//If we haven't already, start the counter + int lookTime = Q_irand( 4500, 8500 ); + //NPCInfo->timeEnemyLastVisible = level.time + 2000; + TIMER_Set( NPC, "enemyLastVisible", lookTime ); + //TODO: Play a sound along the lines of, "Huh? What was that?" + ST_Speech( NPC, SPEECH_SIGHT, 0 ); + //set desired yaw and pitch towards this guy? + //FIXME: this is permanent, they will never look away... *sigh* + NPC_FacePosition( suspect->currentOrigin, qtrue ); + //FIXME: they still need some sort of eye/head tag/bone that can turn? + //NPC_TempLookTarget( NPC, suspect->s.number, lookTime, lookTime ); + } + else if ( TIMER_Get( NPC, "enemyLastVisible" ) <= level.time + 500 + && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) //FIXME: Is this reliable? + { + if ( !Q_irand( 0, 2 ) ) + { + int interrogateTime = Q_irand( 2000, 4000 ); + ST_Speech( NPC, SPEECH_SUSPICIOUS, 0 ); + TIMER_Set( NPC, "interrogating", interrogateTime ); + //G_SetEnemy( NPC, target ); + //NPCInfo->enemyLastSeenTime = level.time; + //TIMER_Set( NPC, "attackDelay", interrogateTime ); + //TIMER_Set( NPC, "stand", interrogateTime ); + //set desired yaw and pitch towards this guy? + //FIXME: this is permanent, they will never look away... *sigh* + NPC_FacePosition( suspect->currentOrigin, qtrue ); + //FIXME: they still need some sort of eye/head tag/bone that can turn? + //NPC_TempLookTarget( NPC, suspect->s.number, interrogateTime, interrogateTime ); + } + } + } + return qfalse; +} +/* +------------------------- +NPC_ST_InvestigateEvent +------------------------- +*/ + +#define MAX_CHECK_THRESHOLD 1 + +static qboolean NPC_ST_InvestigateEvent( int eventID, bool extraSuspicious ) +{ + //If they've given themselves away, just take them as an enemy + if ( NPCInfo->confusionTime < level.time ) + { + if ( level.alertEvents[eventID].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { + //NPCInfo->lastAlertID = level.alertEvents[eventID].ID; + if ( !level.alertEvents[eventID].owner || + !level.alertEvents[eventID].owner->client || + level.alertEvents[eventID].owner->health <= 0 || + level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam ) + {//not an enemy + return qfalse; + } + //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him? Or just let combat AI handle this... (act as if you lost him) + //ST_Speech( NPC, SPEECH_CHARGE, 0 ); + G_SetEnemy( NPC, level.alertEvents[eventID].owner ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + if ( level.alertEvents[eventID].type == AET_SOUND ) + {//heard him, didn't see him, stick for a bit + TIMER_Set( NPC, "roamTime", Q_irand( 500, 2500 ) ); + } + return qtrue; + } + } + + //don't look at the same alert twice + /* + if ( level.alertEvents[eventID].ID == NPCInfo->lastAlertID ) + { + return qfalse; + } + NPCInfo->lastAlertID = level.alertEvents[eventID].ID; + */ + + //Must be ready to take another sound event + /* + if ( NPCInfo->investigateSoundDebounceTime > level.time ) + { + return qfalse; + } + */ + + if ( level.alertEvents[eventID].type == AET_SIGHT ) + {//sight alert, check the light level + if ( level.alertEvents[eventID].light < Q_irand( ST_MIN_LIGHT_THRESHOLD, ST_MAX_LIGHT_THRESHOLD ) ) + {//below my threshhold of potentially seeing + return qfalse; + } + } + + //Save the position for movement (if necessary) + VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal ); + + //First awareness of it + NPCInfo->investigateCount += ( extraSuspicious ) ? 2 : 1; + + //Clamp the value + if ( NPCInfo->investigateCount > 4 ) + NPCInfo->investigateCount = 4; + + //See if we should walk over and investigate + if ( level.alertEvents[eventID].level > AEL_MINOR && NPCInfo->investigateCount > 1 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + { + //make it so they can walk right to this point and look at it rather than having to use combatPoints + if ( G_ExpandPointToBBox( NPCInfo->investigateGoal, NPC->mins, NPC->maxs, NPC->s.number, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) ) ) + {//we were able to move the investigateGoal to a point in which our bbox would fit + //drop the goal to the ground so we can get at it + vec3_t end; + trace_t trace; + VectorCopy( NPCInfo->investigateGoal, end ); + end[2] -= 512;//FIXME: not always right? What if it's even higher, somehow? + gi.trace( &trace, NPCInfo->investigateGoal, NPC->mins, NPC->maxs, end, ENTITYNUM_NONE, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) ); + if ( trace.fraction >= 1.0f ) + {//too high to even bother + //FIXME: look at them??? + } + else + { + VectorCopy( trace.endpos, NPCInfo->investigateGoal ); + NPC_SetMoveGoal( NPC, NPCInfo->investigateGoal, 16, qtrue ); + NPCInfo->localState = LSTATE_INVESTIGATE; + } + } + else + { + int id = NPC_FindCombatPoint( NPCInfo->investigateGoal, NPCInfo->investigateGoal, NPCInfo->investigateGoal, CP_INVESTIGATE|CP_HAS_ROUTE, 0 ); + + if ( id != -1 ) + { + NPC_SetMoveGoal( NPC, level.combatPoints[id].origin, 16, qtrue, id ); + NPCInfo->localState = LSTATE_INVESTIGATE; + } + } + //Say something + //FIXME: only if have others in group... these should be responses? + if ( NPCInfo->investigateDebounceTime+NPCInfo->pauseTime > level.time ) + {//was already investigating + if ( NPCInfo->group && + NPCInfo->group->commander && + NPCInfo->group->commander->client && + NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL && + !Q_irand( 0, 3 ) ) + { + ST_Speech( NPCInfo->group->commander, SPEECH_LOOK, 0 );//FIXME: "I'll go check it out" type sounds + } + else + { + ST_Speech( NPC, SPEECH_LOOK, 0 );//FIXME: "I'll go check it out" type sounds + } + } + else + { + if ( level.alertEvents[eventID].type == AET_SIGHT ) + { + ST_Speech( NPC, SPEECH_SIGHT, 0 ); + } + else if ( level.alertEvents[eventID].type == AET_SOUND ) + { + ST_Speech( NPC, SPEECH_SOUND, 0 ); + } + } + //Setup the debounce info + NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000; + NPCInfo->investigateSoundDebounceTime = level.time + 2000; + NPCInfo->pauseTime = level.time; + } + else + {//just look? + //Say something + if ( level.alertEvents[eventID].type == AET_SIGHT ) + { + ST_Speech( NPC, SPEECH_SIGHT, 0 ); + } + else if ( level.alertEvents[eventID].type == AET_SOUND ) + { + ST_Speech( NPC, SPEECH_SOUND, 0 ); + } + //Setup the debounce info + NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 1000; + NPCInfo->investigateSoundDebounceTime = level.time + 1000; + NPCInfo->pauseTime = level.time; + VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal ); + if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER + && !RT_Flying( NPC ) ) + { + //if ( !Q_irand( 0, 2 ) ) + {//look around + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + + if ( level.alertEvents[eventID].level >= AEL_DANGER ) + { + NPCInfo->investigateDebounceTime = Q_irand( 500, 2500 ); + } + + //Start investigating + NPCInfo->tempBehavior = BS_INVESTIGATE; + return qtrue; +} + +/* +------------------------- +ST_OffsetLook +------------------------- +*/ + +static void ST_OffsetLook( float offset, vec3_t out ) +{ + vec3_t angles, forward, temp; + + GetAnglesForDirection( NPC->currentOrigin, NPCInfo->investigateGoal, angles ); + angles[YAW] += offset; + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( NPC->currentOrigin, 64, forward, out ); + + CalcEntitySpot( NPC, SPOT_HEAD, temp ); + out[2] = temp[2]; +} + +/* +------------------------- +ST_LookAround +------------------------- +*/ + +static void ST_LookAround( void ) +{ + vec3_t lookPos; + float perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime; + + //Keep looking at the spot + if ( perc < 0.25 ) + { + VectorCopy( NPCInfo->investigateGoal, lookPos ); + } + else if ( perc < 0.5f ) //Look up but straight ahead + { + ST_OffsetLook( 0.0f, lookPos ); + } + else if ( perc < 0.75f ) //Look right + { + ST_OffsetLook( 45.0f, lookPos ); + } + else //Look left + { + ST_OffsetLook( -45.0f, lookPos ); + } + + NPC_FacePosition( lookPos ); +} + +/* +------------------------- +NPC_BSST_Investigate +------------------------- +*/ + +void NPC_BSST_Investigate( void ) +{ + //get group- mainly for group speech debouncing, but may use for group scouting/investigating AI, too + AI_GetGroup( NPC ); + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( NPCInfo->confusionTime < level.time ) + { + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + //Look for an enemy + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + ST_Speech( NPC, SPEECH_DETECTED, 0 ); + NPCInfo->tempBehavior = BS_DEFAULT; + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, NPCInfo->lastAlertID ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + if ( NPCInfo->confusionTime < level.time ) + { + if ( NPC_CheckForDanger( alertEvent ) ) + {//running like hell + ST_Speech( NPC, SPEECH_COVER, 0 );//FIXME: flee sound? + return; + } + } + + //if ( level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + NPC_ST_InvestigateEvent( alertEvent, qtrue ); + } + } + } + + //If we're done looking, then just return to what we were doing + if ( ( NPCInfo->investigateDebounceTime + NPCInfo->pauseTime ) < level.time ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + NPCInfo->goalEntity = UpdateGoal(); + + NPC_UpdateAngles( qtrue, qtrue ); + //Say something + ST_Speech( NPC, SPEECH_GIVEUP, 0 ); + return; + } + + //FIXME: else, look for new alerts + + //See if we're searching for the noise's origin + if ( NPCInfo->localState == LSTATE_INVESTIGATE && (NPCInfo->goalEntity!=NULL) ) + { + //See if we're there + if ( !STEER::Reached(NPC, NPCInfo->goalEntity, 32, !!FlyingCreature(NPC)) ) + { + ucmd.buttons |= BUTTON_WALKING; + + //Try and move there + if ( NPC_MoveToGoal( qtrue ) ) + { + //Bump our times + NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000; + NPCInfo->pauseTime = level.time; + + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + //Otherwise we're done or have given up + //Say something + //ST_Speech( NPC, SPEECH_LOOK, 0.33f ); + NPCInfo->localState = LSTATE_NONE; + } + + //Look around + ST_LookAround(); +} + +/* +------------------------- +NPC_BSST_Patrol +------------------------- +*/ + +void NPC_BSST_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + + //Not a scriptflag, but... + if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER && (NPC->client->ps.eFlags&EF_SPOTLIGHT) ) + {//using spotlight search mode + vec3_t eyeFwd, end, mins={-2,-2,-2}, maxs={2,2,2}; + trace_t trace; + AngleVectors( NPC->client->renderInfo.eyeAngles, eyeFwd, NULL, NULL ); + VectorMA( NPC->client->renderInfo.eyePoint, NPCInfo->stats.visrange, eyeFwd, end ); + //get server-side trace impact point + gi.trace( &trace, NPC->client->renderInfo.eyePoint, mins, maxs, end, NPC->s.number, MASK_OPAQUE|CONTENTS_BODY|CONTENTS_CORPSE ); + NPC->speed = (trace.fraction*NPCInfo->stats.visrange); + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + //FIXME: do a FOV cone check, then a trace + if ( trace.entityNum < ENTITYNUM_WORLD ) + {//hit something + //try cheap check first + gentity_t *enemy = &g_entities[trace.entityNum]; + if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam ) + { + G_SetEnemy( NPC, enemy ); + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + //FIXME: maybe do a quick check of ents within the spotlight's radius? + //hmmm, look around + if ( NPC_CheckEnemiesInSpotlight() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + else + { + //get group- mainly for group speech debouncing, but may use for group scouting/investigating AI, too + AI_GetGroup( NPC ); + + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + if ( NPC_CheckForDanger( alertEvent ) ) + {//going to run? + ST_Speech( NPC, SPEECH_COVER, 0 ); + return; + } + else if (NPC->client->NPC_class==CLASS_BOBAFETT) + { + //NPCInfo->lastAlertID = level.alertEvents[eventID].ID; + if ( !level.alertEvents[alertEvent].owner || + !level.alertEvents[alertEvent].owner->client || + level.alertEvents[alertEvent].owner->health <= 0 || + level.alertEvents[alertEvent].owner->client->playerTeam != NPC->client->enemyTeam ) + {//not an enemy + return; + } + //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him? Or just let combat AI handle this... (act as if you lost him) + //ST_Speech( NPC, SPEECH_CHARGE, 0 ); + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return; + } + else if ( NPC_ST_InvestigateEvent( alertEvent, qfalse ) ) + {//actually going to investigate it + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + //ST_Move( NPCInfo->goalEntity ); + NPC_MoveToGoal( qtrue ); + } + else// if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + if ( NPC->client->NPC_class != CLASS_IMPERIAL && NPC->client->NPC_class != CLASS_IMPWORKER ) + {//imperials do not look around + if ( TIMER_Done( NPC, "enemyLastVisible" ) ) + {//nothing suspicious, look around + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredYaw = NPC->s.angles[1] + Q_irand( -90, 90 ); + } + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredPitch = Q_irand( -20, 20 ); + } + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); + //TEMP hack for Imperial stand anim + if ( NPC->client->NPC_class == CLASS_IMPERIAL + || NPC->client->NPC_class == CLASS_IMPWORKER ) + {//hack + if ( NPC->client->ps.weapon != WP_CONCUSSION ) + {//not Rax + if ( ucmd.forwardmove || ucmd.rightmove || ucmd.upmove ) + {//moving + + if( (!NPC->client->ps.torsoAnimTimer) || (NPC->client->ps.torsoAnim == BOTH_STAND4) ) + { + if ( (ucmd.buttons&BUTTON_WALKING) && !(NPCInfo->scriptFlags&SCF_RUNNING) ) + {//not running, only set upper anim + // No longer overrides scripted anims + NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoAnimTimer = 200; + } + } + } + else + {//standing still, set both torso and legs anim + // No longer overrides scripted anims + if( ( !NPC->client->ps.torsoAnimTimer || (NPC->client->ps.torsoAnim == BOTH_STAND4) ) && + ( !NPC->client->ps.legsAnimTimer || (NPC->client->ps.legsAnim == BOTH_STAND4) ) ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoAnimTimer = NPC->client->ps.legsAnimTimer = 200; + } + } + //FIXME: this is a disgusting hack that is supposed to make the Imperials start with their weapon holstered- need a better way + if ( NPC->client->ps.weapon != WP_NONE ) + { + ChangeWeapon( NPC, WP_NONE ); + NPC->client->ps.weapon = WP_NONE; + NPC->client->ps.weaponstate = WEAPON_READY; + G_RemoveWeaponModels( NPC ); + } + } + } +} + +/* +------------------------- +NPC_BSST_Idle +------------------------- +*/ +/* +void NPC_BSST_Idle( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + NPC_ST_InvestigateEvent( alertEvent, qfalse ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) ); + + NPC_UpdateAngles( qtrue, qtrue ); +} +*/ +/* +------------------------- +ST_CheckMoveState +------------------------- +*/ + +static void ST_CheckMoveState( void ) +{ + if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//moving toward a goal that a script is waiting on, so don't stop for anything! + move = qtrue; + } + else if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER + && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//no squad stuff + return; + } +// else if ( NPC->NPC->scriptFlags&SCF_NO_GROUPS ) + { + move = qtrue; + } + //See if we're a scout + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( STEER::Reached(NPC, NPCInfo->goalEntity, 16, !!FlyingCreature(NPC)) || + (enemyLOS && (NPCInfo->aiFlags&NPCAI_STOP_AT_LOS) && !Q3_TaskIDPending(NPC, TID_MOVE_NAV)) + ) + {//either hit our navgoal or our navgoal was not a crucial (scripted) one (maybe a combat point) and we're scouting and found our enemy + int newSquadState = SQUAD_STAND_AND_SHOOT; + //we got where we wanted to go, set timers based on why we were running + switch ( NPCInfo->squadState ) + { + case SQUAD_RETREAT://was running away + //done fleeing, obviously + TIMER_Set( NPC, "duck", (NPC->max_health - NPC->health) * 100 ); + TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); + TIMER_Set( NPC, "flee", -level.time ); + newSquadState = SQUAD_COVER; + break; + case SQUAD_TRANSITION://was heading for a combat point + TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); + break; + case SQUAD_SCOUT://was running after player + break; + default: + break; + } + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, newSquadState ); + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels + //don't do something else just yet + + // THIS IS THE ONE TRUE PLACE WHERE ROAM TIME IS SET + TIMER_Set( NPC, "roamTime", Q_irand( 8000, 15000 ) );//Q_irand( 1000, 4000 ) ); + if (Q_irand(0, 3)==0) + { + TIMER_Set( NPC, "duck", Q_irand(5000, 10000) ); // just reached our goal, chance of ducking now + } + return; + } + + //keep going, hold of roamTimer until we get there + TIMER_Set( NPC, "roamTime", Q_irand( 8000, 9000 ) ); + } +} + +void ST_ResolveBlockedShot( int hit ) +{ + int stuckTime; + //figure out how long we intend to stand here, max + if ( TIMER_Get( NPC, "roamTime" ) > TIMER_Get( NPC, "stick" ) ) + { + stuckTime = TIMER_Get( NPC, "roamTime" )-level.time; + } + else + { + stuckTime = TIMER_Get( NPC, "stick" )-level.time; + } + + if ( TIMER_Done( NPC, "duck" ) ) + {//we're not ducking + if ( AI_GroupContainsEntNum( NPCInfo->group, hit ) ) + { + gentity_t *member = &g_entities[hit]; + if ( TIMER_Done( member, "duck" ) ) + {//they aren't ducking + if ( TIMER_Done( member, "stand" ) ) + {//they're not being forced to stand + //tell them to duck at least as long as I'm not moving + TIMER_Set( member, "duck", stuckTime ); // tell my friend to duck so I can shoot over his head + return; + } + } + } + } + else + {//maybe we should stand + if ( TIMER_Done( NPC, "stand" ) ) + {//stand for as long as we'll be here + TIMER_Set( NPC, "stand", stuckTime ); + return; + } + } + //Hmm, can't resolve this by telling them to duck or telling me to stand + //We need to move! + TIMER_Set( NPC, "roamTime", -1 ); + TIMER_Set( NPC, "stick", -1 ); + TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "attakDelay", Q_irand( 1000, 3000 ) ); +} + +/* +------------------------- +ST_CheckFireState +------------------------- +*/ + +static void ST_CheckFireState( void ) +{ + if ( enemyCS ) + {//if have a clear shot, always try + return; + } + + if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT ) + {//runners never try to fire at the last pos + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + //See if we should continue to fire on their last position + //!TIMER_Done( NPC, "stick" ) || + if ( !hitAlly //we're not going to hit an ally + && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS? + && NPCInfo->enemyLastSeenTime > 0 //we've seen the enemy + && NPCInfo->group //have a group + && (NPCInfo->group->numState[SQUAD_RETREAT]>0||NPCInfo->group->numState[SQUAD_TRANSITION]>0||NPCInfo->group->numState[SQUAD_SCOUT]>0) )//laying down covering fire + { + if ( level.time - NPCInfo->enemyLastSeenTime < 10000 &&//we have seem the enemy in the last 10 seconds + (!NPCInfo->group || level.time - NPCInfo->group->lastSeenEnemyTime < 10000 ))//we are not in a group or the group has seen the enemy in the last 10 seconds + { + if ( !Q_irand( 0, 10 ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + qboolean tooClose = qfalse; + qboolean tooFar = qfalse; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + if ( VectorCompare( impactPos, vec3_origin ) ) + {//never checked ShotEntity this frame, so must do a trace... + trace_t tr; + //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; + vec3_t forward, end; + AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 8192, forward, end ); + gi.trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + VectorCopy( tr.endpos, impactPos ); + } + + //see if impact would be too close to me + float distThreshold = 16384/*128*128*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 65536/*256*256*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 65536/*256*256*/; + } + break; + case WP_CONCUSSION: + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + { + distThreshold = 65536/*256*256*/; + } + break; + default: + break; + } + + float dist = DistanceSquared( impactPos, muzzle ); + + if ( dist < distThreshold ) + {//impact would be too close to me + tooClose = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 || + (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 )) + {//we've haven't seen them in the last 5 seconds + //see if it's too far from where he is + distThreshold = 65536/*256*256*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 262144/*512*512*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 262144/*512*512*/; + } + break; + case WP_CONCUSSION: + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + { + distThreshold = 262144/*512*512*/; + } + break; + default: + break; + } + dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation ); + if ( dist > distThreshold ) + {//impact would be too far from enemy + tooFar = qtrue; + } + } + + if ( !tooClose && !tooFar ) + {//okay too shoot at last pos + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + VectorNormalize( dir ); + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot = qtrue; + faceEnemy = qfalse; + //AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + return; + } + } + } + } +} + +void ST_TrackEnemy( gentity_t *self, vec3_t enemyPos ) +{ + //clear timers + TIMER_Set( self, "attackDelay", Q_irand( 1000, 2000 ) ); + //TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stick", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "stand", -1 ); + TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) ); + //leave my combat point + NPC_FreeCombatPoint( self->NPC->combatPoint ); + //go after his last seen pos + NPC_SetMoveGoal( self, enemyPos, 100.0f, qfalse ); + if (Q_irand(0,3)==0) + { + NPCInfo->aiFlags |= NPCAI_STOP_AT_LOS; + } +} + +int ST_ApproachEnemy( gentity_t *self ) +{ + TIMER_Set( self, "attackDelay", Q_irand( 250, 500 ) ); + //TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stick", Q_irand( 1000, 2000 ) ); + TIMER_Set( self, "stand", -1 ); + TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) ); + //leave my combat point + NPC_FreeCombatPoint( self->NPC->combatPoint ); + //return the relevant combat point flags + return (CP_CLEAR|CP_CLOSEST); +} + +void ST_HuntEnemy( gentity_t *self ) +{ + //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );//Disabled this for now, guys who couldn't hunt would never attack + //TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "stick", Q_irand( 250, 1000 ) ); + TIMER_Set( NPC, "stand", -1 ); + TIMER_Set( NPC, "scoutTime", TIMER_Get( NPC, "stick" )-level.time+Q_irand(5000, 10000) ); + //leave my combat point + NPC_FreeCombatPoint( NPCInfo->combatPoint ); + //go directly after the enemy + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + self->NPC->goalEntity = NPC->enemy; + } +} + +void ST_TransferTimers( gentity_t *self, gentity_t *other ) +{ + TIMER_Set( other, "attackDelay", TIMER_Get( self, "attackDelay" )-level.time ); + TIMER_Set( other, "duck", TIMER_Get( self, "duck" )-level.time ); + TIMER_Set( other, "stick", TIMER_Get( self, "stick" )-level.time ); + TIMER_Set( other, "scoutTime", TIMER_Get( self, "scoutTime" )-level.time ); + TIMER_Set( other, "roamTime", TIMER_Get( self, "roamTime" )-level.time ); + TIMER_Set( other, "stand", TIMER_Get( self, "stand" )-level.time ); + TIMER_Set( self, "attackDelay", -1 ); + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stick", -1 ); + TIMER_Set( self, "scoutTime", -1 ); + TIMER_Set( self, "roamTime", -1 ); + TIMER_Set( self, "stand", -1 ); +} + +void ST_TransferMoveGoal( gentity_t *self, gentity_t *other ) +{ + if ( Q3_TaskIDPending( self, TID_MOVE_NAV ) ) + {//can't transfer movegoal when a script we're running is waiting to complete + return; + } + if ( self->NPC->combatPoint != -1 ) + {//I've got a combatPoint I'm going to, give it to him + self->NPC->lastFailedCombatPoint = other->NPC->combatPoint = self->NPC->combatPoint; + self->NPC->combatPoint = -1; + } + else + {//I must be going for a goal, give that to him instead + if ( self->NPC->goalEntity == self->NPC->tempGoal ) + { + NPC_SetMoveGoal( other, self->NPC->tempGoal->currentOrigin, self->NPC->goalRadius, ((self->NPC->tempGoal->svFlags&SVF_NAVGOAL)?true:false) ); + } + else + { + other->NPC->goalEntity = self->NPC->goalEntity; + } + } + //give him my squadstate + AI_GroupUpdateSquadstates( self->NPC->group, other, NPCInfo->squadState ); + + //give him my timers and clear mine + ST_TransferTimers( self, other ); + + //now make me stand around for a second or two at least + AI_GroupUpdateSquadstates( self->NPC->group, self, SQUAD_STAND_AND_SHOOT ); + TIMER_Set( self, "stand", Q_irand( 1000, 3000 ) ); +} + +int ST_GetCPFlags( void ) +{ + int cpFlags = 0; + if ( NPC && NPCInfo->group ) + { + if ( NPC == NPCInfo->group->commander && NPC->client->NPC_class == CLASS_IMPERIAL ) + {//imperials hang back and give orders + if ( NPCInfo->group->numGroup > 1 && Q_irand( -3, NPCInfo->group->numGroup ) > 1 ) + {//FIXME: make sure he;s giving orders with these lines + if ( Q_irand( 0, 1 ) ) + { + ST_Speech( NPC, SPEECH_CHASE, 0.5 ); + } + else + { + ST_Speech( NPC, SPEECH_YELL, 0.5 ); + } + } + cpFlags = (CP_CLEAR|CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT); + } + else if ( NPCInfo->group->morale < 0 ) + {//hide + cpFlags = (CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT); + /* + if ( NPC->client->NPC_class == CLASS_SABOTEUR && !Q_irand( 0, 3 ) ) + { + Saboteur_Cloak( NPC ); + } + */ + } +/* else if ( NPCInfo->group->morale < NPCInfo->group->numGroup ) + {//morale is low for our size + int moraleDrop = NPCInfo->group->numGroup - NPCInfo->group->morale; + if ( moraleDrop < -6 ) + {//flee (no clear shot needed) + cpFlags = (CP_FLEE|CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE); + } + else if ( moraleDrop < -3 ) + {//retreat (no clear shot needed) + cpFlags = (CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE); + } + else if ( moraleDrop < 0 ) + {//cover (no clear shot needed) + cpFlags = (CP_COVER|CP_AVOID|CP_SAFE); + } + }*/ + else + { + int moraleBoost = NPCInfo->group->morale - NPCInfo->group->numGroup; + if ( moraleBoost > 20 ) + {//charge to any one and outflank (no cover needed) + cpFlags = (CP_CLEAR|CP_FLANK|CP_APPROACH_ENEMY); + //Saboteur_Decloak( NPC ); + } + else if ( moraleBoost > 15 ) + {//charge to closest one (no cover needed) + cpFlags = (CP_CLEAR|CP_CLOSEST|CP_APPROACH_ENEMY); + /* + if ( NPC->client->NPC_class == CLASS_SABOTEUR && !Q_irand( 0, 3 ) ) + { + Saboteur_Decloak( NPC ); + } + */ + } + else if ( moraleBoost > 10 ) + {//charge closer (no cover needed) + cpFlags = (CP_CLEAR|CP_APPROACH_ENEMY); + /* + if ( NPC->client->NPC_class == CLASS_SABOTEUR && !Q_irand( 0, 6 ) ) + { + Saboteur_Decloak( NPC ); + } + */ + } + } + } + if ( !cpFlags ) + { + //at some medium level of morale + switch( Q_irand( 0, 3 ) ) + { + case 0://just take the nearest one + cpFlags = (CP_CLEAR|CP_COVER|CP_NEAREST); + break; + case 1://take one closer to the enemy + cpFlags = (CP_CLEAR|CP_COVER|CP_APPROACH_ENEMY); + break; + case 2://take the one closest to the enemy + cpFlags = (CP_CLEAR|CP_COVER|CP_CLOSEST|CP_APPROACH_ENEMY); + break; + case 3://take the one on the other side of the enemy + cpFlags = (CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY); + break; + } + } + if ( NPC && (NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + return cpFlags; +} + +/* +------------------------- +ST_Commander + + Make decisions about who should go where, etc. + +FIXME: leader (group-decision-making) AI? +FIXME: need alternate routes! +FIXME: more group voice interaction +FIXME: work in pairs? + +------------------------- +*/ +void ST_Commander( void ) +{ + int i;//, j; + int cp, cpFlags_org, cpFlags; + AIGroupInfo_t *group = NPCInfo->group; + gentity_t *member;//, *buddy; + qboolean runner = qfalse; + qboolean enemyLost = qfalse; + qboolean scouting = qfalse; + int squadState; + float avoidDist; + + group->processed = qtrue; + + if ( group->enemy == NULL || group->enemy->client == NULL ) + {//hmm, no enemy...?! + return; + } + + //FIXME: have this group commander check the enemy group (if any) and see if they have + // superior numbers. If they do, fall back rather than advance. If you have + // superior numbers, advance on them. + //FIXME: find the group commander and have him occasionally give orders when there is speech + //FIXME: start fleeing when only a couple of you vs. a lightsaber, possibly give up if the only one left + + SaveNPCGlobals(); + + if ( group->lastSeenEnemyTime < level.time - 180000 ) + {//dissolve the group + ST_Speech( NPC, SPEECH_LOST, 0.0f ); + group->enemy->waypoint = NAV::GetNearestNode(group->enemy); + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + SetNPCGlobals( member ); + if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//running somewhere that a script requires us to go, don't break from that + continue; + } + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not allowed to move on my own + continue; + } + //Lost enemy for three minutes? go into search mode? + G_ClearEnemy( NPC ); + NPC->waypoint = NAV::GetNearestNode(group->enemy); + if ( NPC->waypoint == WAYPOINT_NONE ) + { + NPCInfo->behaviorState = BS_DEFAULT;//BS_PATROL; + } + else if ( group->enemy->waypoint == WAYPOINT_NONE || (NAV::EstimateCostToGoal( NPC->waypoint, group->enemy->waypoint ) >= Q3_INFINITE) ) + { + NPC_BSSearchStart( NPC->waypoint, BS_SEARCH ); + } + else + { + NPC_BSSearchStart( group->enemy->waypoint, BS_SEARCH ); + } + } + group->enemy = NULL; + RestoreNPCGlobals(); + return; + } + + + + + //see if anyone is running + if ( group->numState[SQUAD_SCOUT] > 0 || + group->numState[SQUAD_TRANSITION] > 0 || + group->numState[SQUAD_RETREAT] > 0 ) + {//someone is running + runner = qtrue; + } + + if ( /*!runner &&*/ group->lastSeenEnemyTime > level.time - 32000 && group->lastSeenEnemyTime < level.time - 30000 ) + {//no-one has seen the enemy for 30 seconds// and no-one is running after him + if ( group->commander && !Q_irand( 0, 1 ) ) + { + ST_Speech( group->commander, SPEECH_ESCAPING, 0.0f ); + } + else + { + ST_Speech( NPC, SPEECH_ESCAPING, 0.0f ); + } + //don't say this again + NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + + if ( group->lastSeenEnemyTime < level.time - 7000 ) + {//no-one has seen the enemy for at least 10 seconds! Should send a scout + enemyLost = qtrue; + } + + //Go through the list: + + //Everyone should try to get to a combat point if possible + int curMemberNum, lastMemberNum; + if ( d_asynchronousGroupAI->integer ) + {//do one member a turn + group->activeMemberNum++; + if ( group->activeMemberNum >= group->numGroup ) + { + group->activeMemberNum = 0; + } + curMemberNum = group->activeMemberNum; + lastMemberNum = curMemberNum + 1; + } + else + { + curMemberNum = 0; + lastMemberNum = group->numGroup; + } + for ( i = curMemberNum; i < lastMemberNum; i++ ) + { + //reset combat point flags + cp = -1; + cpFlags = 0; + squadState = SQUAD_IDLE; + avoidDist = 0; + scouting = qfalse; + + //get the next guy + member = &g_entities[group->member[i].number]; + if ( !member->enemy ) + {//don't include guys that aren't angry + continue; + } + SetNPCGlobals( member ); + + if ( !TIMER_Done( NPC, "flee" ) ) + {//running away + continue; + } + + if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//running somewhere that a script requires us to go + continue; + } + + if ( NPC->s.weapon == WP_NONE + && NPCInfo->goalEntity + && NPCInfo->goalEntity == NPCInfo->tempGoal + && NPCInfo->goalEntity->s.eType == ET_ITEM ) + {//running to pick up a gun, don't do other logic + continue; + } + + + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not allowed to do combat-movement + continue; + } + + + if ( NPC->client->ps.weapon == WP_NONE ) + {//weaponless, should be hiding + if ( NPCInfo->goalEntity == NULL || NPCInfo->goalEntity->enemy == NULL || NPCInfo->goalEntity->enemy->s.eType != ET_ITEM ) + {//not running after a pickup + if ( TIMER_Done( NPC, "hideTime" ) || (DistanceSquared( group->enemy->currentOrigin, NPC->currentOrigin ) < 65536 && NPC_ClearLOS( NPC->enemy )) ) + {//done hiding or enemy near and can see us + //er, start another flee I guess? + NPC_StartFlee( NPC->enemy, NPC->enemy->currentOrigin, AEL_DANGER_GREAT, 5000, 10000 ); + }//else, just hang here + } + continue; + } + + if (enemyLost && NAV::InSameRegion(NPC, NPC->enemy->currentOrigin)) + { + ST_TrackEnemy( NPC, NPC->enemy->currentOrigin ); + continue; + } + + if (!NPC->enemy) + { + continue; + } + + + // Check To See We Have A Clear Shot To The Enemy Every Couple Seconds + //--------------------------------------------------------------------- + if (TIMER_Done( NPC, "checkGrenadeTooCloseDebouncer" )) + { + TIMER_Set (NPC, "checkGrenadeTooCloseDebouncer", Q_irand(300, 600)); + + vec3_t mins; + vec3_t maxs; + bool fled = false; + gentity_t* ent; + + gentity_t *entityList[MAX_GENTITIES]; + + for (int i = 0 ; i < 3 ; i++ ) + { + mins[i] = NPC->currentOrigin[i] - 200; + maxs[i] = NPC->currentOrigin[i] + 200; + } + + int numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for (int e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + + if (ent == NPC) + continue; + if (ent->owner == NPC) + continue; + if ( !(ent->inuse) ) + continue; + if ( ent->s.eType == ET_MISSILE ) + { + if ( ent->s.weapon == WP_THERMAL ) + {//a thermal + if ( ent->has_bounced && (!ent->owner || !OnSameTeam(ent->owner, NPC))) + {//bounced and an enemy thermal + ST_Speech( NPC, SPEECH_COVER, 0 );//FIXME: flee sound? + NPC_StartFlee(NPC->enemy, ent->currentOrigin, AEL_DANGER_GREAT, 1000, 2000); + fled = true; +// cpFlags |= (CP_CLEAR|CP_COVER); // NOPE, Can't See The Enemy, So Find A New Combat Point + TIMER_Set (NPC, "checkGrenadeTooCloseDebouncer", Q_irand(2000, 4000)); + break; + } + } + } + } + if (fled) + { + continue; + } + } + + + // Check To See We Have A Clear Shot To The Enemy Every Couple Seconds + //--------------------------------------------------------------------- + if (TIMER_Done( NPC, "checkEnemyVisDebouncer" )) + { + TIMER_Set (NPC, "checkEnemyVisDebouncer", Q_irand(3000, 7000)); + if (!NPC_ClearLOS(NPC->enemy)) + { + cpFlags |= (CP_CLEAR|CP_COVER); // NOPE, Can't See The Enemy, So Find A New Combat Point + } + } + + // Check To See If The Enemy Is Too Close For Comfort + //---------------------------------------------------- + if (NPC->client->NPC_class!=CLASS_ASSASSIN_DROID) + { + if (TIMER_Done(NPC, "checkEnemyTooCloseDebouncer")) + { + TIMER_Set (NPC, "checkEnemyTooCloseDebouncer", Q_irand(1000, 6000)); + + float distThreshold = 16384/*128*128*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 65536/*256*256*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 65536/*256*256*/; + } + break; + case WP_CONCUSSION: + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + { + distThreshold = 65536/*256*256*/; + } + break; + default: + break; + } + + if ( DistanceSquared( group->enemy->currentOrigin, NPC->currentOrigin ) < distThreshold ) + { + cpFlags |= (CP_CLEAR|CP_COVER); + } + } + } + + + //clear the local state + NPCInfo->localState = LSTATE_NONE; + + cpFlags &= ~CP_NEAREST; + //Assign combat points + if ( cpFlags ) + {//we want to run to a combat point + //always avoid enemy when picking combat points, and we always want to be able to get there + cpFlags |= CP_AVOID_ENEMY|CP_HAS_ROUTE|CP_TRYFAR; + avoidDist = 200; + cpFlags_org = cpFlags; //remember what we *wanted* to do... + + //now get a combat point + if ( cp == -1 ) + {//may have had sone set above + cp = NPC_FindCombatPointRetry( NPC->currentOrigin, NPC->currentOrigin, NPC->currentOrigin, &cpFlags, avoidDist, NPCInfo->lastFailedCombatPoint ); + } + + //see if we got a valid one + if ( cp != -1 ) + {//found a combat point + //let others know that someone is now running + runner = qtrue; + //don't change course again until we get to where we're going + TIMER_Set( NPC, "roamTime", Q3_INFINITE ); + + + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp ); + + // If Successfully + if ((cpFlags&CP_FLANK) || ((cpFlags&CP_COVER) && (cpFlags&CP_CLEAR))) + { + } + else if (Q_irand(0,3)==0) + { + NPCInfo->aiFlags |= NPCAI_STOP_AT_LOS; + } + + //okay, try a move right now to see if we can even get there + if ( (cpFlags&CP_FLANK) ) + { + if ( group->numGroup > 1 ) + { + NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 ); + } + } + else if ( (cpFlags&CP_COVER) && !(cpFlags&CP_CLEAR) ) + {//going into hiding + NPC_ST_StoreMovementSpeech( SPEECH_COVER, -1 ); + } + else + { + if ( !Q_irand( 0, 20 ) ) + {//hell, we're loading the sounds, use them every now and then! + if ( Q_irand( 0, 1 ) ) + { + NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 ); + } + else + { + NPC_ST_StoreMovementSpeech( SPEECH_ESCAPING, -1 ); + } + } + } + } + } + } + + RestoreNPCGlobals(); + return; +} + +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +void Noghri_StickTrace( void ) +{ + if ( !NPC->ghoul2.size() + || NPC->weaponModel[0] <= 0 ) + { + return; + } + + int boltIndex = gi.G2API_AddBolt(&NPC->ghoul2[NPC->weaponModel[0]], "*weapon"); + if ( boltIndex != -1 ) + { + int curTime = (cg.time?cg.time:level.time); + qboolean hit = qfalse; + int lastHit = ENTITYNUM_NONE; + for ( int time = curTime-25; time <= curTime+25&&!hit; time += 25 ) + { + mdxaBone_t boltMatrix; + vec3_t tip, dir, base, angles={0,NPC->currentAngles[YAW],0}; + vec3_t mins={-2,-2,-2},maxs={2,2,2}; + trace_t trace; + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->weaponModel[0], + boltIndex, + &boltMatrix, angles, NPC->currentOrigin, time, + NULL, NPC->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, base ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, dir ); + VectorMA( base, 48, dir, tip ); + #ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + G_DebugLine(base, tip, FRAMETIME, 0x000000ff, qtrue); + } + #endif + gi.trace( &trace, base, mins, maxs, tip, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 10 ); + if ( trace.fraction < 1.0f && trace.entityNum != lastHit ) + {//hit something + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( traceEnt->takedamage + && (!traceEnt->client || traceEnt == NPC->enemy || traceEnt->client->NPC_class != NPC->client->NPC_class) ) + {//smack + int dmg = Q_irand( 12, 20 );//FIXME: base on skill! + //FIXME: debounce? + G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/tusken_staff/stickhit%d.wav", Q_irand( 1, 4 ) ) ) ); + G_Damage( traceEnt, NPC, NPC, vec3_origin, trace.endpos, dmg, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + if ( traceEnt->health > 0 && dmg > 17 ) + {//do pain on enemy + G_Knockdown( traceEnt, NPC, dir, 300, qtrue ); + } + lastHit = trace.entityNum; + hit = qtrue; + } + } + } + } +} +/* +------------------------- +NPC_BSST_Attack +------------------------- +*/ + +void NPC_BSST_Attack( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //NPC_CheckEnemy( qtrue, qfalse ); + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt() == qfalse )//!NPC->enemy )// + { + if( NPC->client->playerTeam == TEAM_PLAYER ) + { + NPC_BSPatrol(); + } + else + { + NPC_BSST_Patrol();//FIXME: or patrol? + } + return; + } + + //FIXME: put some sort of delay into the guys depending on how they saw you...? + + //Get our group info + if ( TIMER_Done( NPC, "interrogating" ) ) + { + AI_GetGroup( NPC );//, 45, 512, NPC->enemy ); + } + else + { + //FIXME: when done interrogating, I should send out a team alert! + } + + if ( NPCInfo->group ) + {//I belong to a squad of guys - we should *always* have a group + if ( !NPCInfo->group->processed ) + {//I'm the first ent in my group, I'll make the command decisions +#if AI_TIMERS + int startTime = GetTime(0); +#endif// AI_TIMERS + ST_Commander(); +#if AI_TIMERS + int commTime = GetTime ( startTime ); + if ( commTime > 20 ) + { + gi.Printf( S_COLOR_RED"ERROR: Commander time: %d\n", commTime ); + } + else if ( commTime > 10 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: Commander time: %d\n", commTime ); + } + else if ( commTime > 2 ) + { + gi.Printf( S_COLOR_GREEN"Commander time: %d\n", commTime ); + } +#endif// AI_TIMERS + } + } + else if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//not already fleeing, and going to run + ST_Speech( NPC, SPEECH_COVER, 0 ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( !NPC->enemy ) + {//WTF? somehow we lost our enemy? + NPC_BSST_Patrol();//FIXME: or patrol? + return; + } + + if (NPCInfo->goalEntity && NPCInfo->goalEntity!=NPC->enemy) + { + NPCInfo->goalEntity = UpdateGoal(); + } + + + enemyLOS = enemyCS = enemyInFOV = qfalse; + move = qtrue; + faceEnemy = qfalse; + shoot = qfalse; + hitAlly = qfalse; + VectorClear( impactPos ); + enemyDist = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + + vec3_t enemyDir, shootDir; + VectorSubtract( NPC->enemy->currentOrigin, NPC->currentOrigin, enemyDir ); + VectorNormalize( enemyDir ); + AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL ); + float dot = DotProduct( enemyDir, shootDir ); + if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 ) + {//enemy is in front of me or they're very close and not behind me + enemyInFOV = qtrue; + } + + if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128 + {//enemy within 128 + if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) && + (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//shooting an explosive, but enemy too close, switch to primary fire + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not... + } + } + else if ( enemyDist > 65536 )//256 squared + { + if ( NPC->client->ps.weapon == WP_DISRUPTOR ) + {//sniping... + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + {//use primary fire + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( NPC->client->ps.weapon ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + //can we see our target? + if ( NPC_ClearLOS( NPC->enemy ) ) + { + AI_GroupUpdateEnemyLastSeen( NPCInfo->group, NPC->enemy->currentOrigin ); + NPCInfo->enemyLastSeenTime = level.time; + enemyLOS = qtrue; + + if ( NPC->client->ps.weapon == WP_NONE ) + { + enemyCS = qfalse;//not true, but should stop us from firing + NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon + } + else + {//can we shoot our target? + if ((enemyDist < MIN_ROCKET_DIST_SQUARED) && + ((level.time - NPC->lastMoveTime)<5000) && + ( + (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER + || (NPC->client->ps.weapon == WP_CONCUSSION && !(NPCInfo->scriptFlags&SCF_ALT_FIRE)) + || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))))) + { + enemyCS = qfalse;//not true, but should stop us from firing + hitAlly = qtrue;//us! + //FIXME: if too close, run away! + } + else if ( enemyInFOV ) + {//if enemy is FOV, go ahead and check for shooting + int hit = NPC_ShotEntity( NPC->enemy, impactPos ); + gentity_t *hitEnt = &g_entities[hit]; + + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage && ((hitEnt->svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) ) + {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway + AI_GroupUpdateClearShotTime( NPCInfo->group ); + enemyCS = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + else + {//Hmm, have to get around this bastard + NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + ST_ResolveBlockedShot( hit ); + if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) + {//would hit an ally, don't fire!!! + hitAlly = qtrue; + } + else + {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire + } + } + } + else + { + enemyCS = qfalse;//not true, but should stop us from firing + } + } + } + else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + + if ( NPC->client->ps.weapon == WP_NONE ) + { + faceEnemy = qfalse; + shoot = qfalse; + } + else + { + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + if ( enemyCS ) + { + shoot = qtrue; + } + } + + //Check for movement to take care of + ST_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + ST_CheckFireState(); + + if ( faceEnemy ) + {//face the enemy + NPC_FaceEnemy( qtrue ); + } + + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not supposed to chase my enemies + if ( NPCInfo->goalEntity == NPC->enemy ) + {//goal is my entity, so don't move + move = qfalse; + } + } + else if (NPC->NPC->scriptFlags&SCF_NO_GROUPS) + { + // NPCInfo->goalEntity = UpdateGoal(); + + NPCInfo->goalEntity = (enemyLOS)?(0):(NPC->enemy); + } + + if ( NPC->client->fireDelay && NPC->s.weapon == WP_ROCKET_LAUNCHER ) + { + move = qfalse; + } + + if ( !ucmd.rightmove ) + {//only if not already strafing for some strange reason...? + //NOTE: these are never set here, but can be set in AI_Jedi.cpp for those NPCs who are sort of Stormtrooper/Jedi hybrids + //NOTE: this stomps navigation movement entirely! + //FIXME: if enemy behind me and turning to face enemy, don't strafe in that direction, too + if ( !TIMER_Done( NPC, "strafeLeft" ) ) + { + /* + if ( NPCInfo->desiredYaw > NPC->client->ps.viewangles[YAW] + 60 ) + {//we want to turn left, don't apply the strafing + } + else + */ + {//go ahead and strafe left + ucmd.rightmove = -127; + //re-check the duck as we might want to be rolling + VectorClear( NPC->client->ps.moveDir ); + move = qfalse; + } + } + else if ( !TIMER_Done( NPC, "strafeRight" ) ) + { + /*if ( NPCInfo->desiredYaw < NPC->client->ps.viewangles[YAW] - 60 ) + {//we want to turn right, don't apply the strafing + } + else + */ + {//go ahead and strafe left + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + move = qfalse; + } + } + } + + if ( NPC->client->ps.legsAnim == BOTH_GUARD_LOOKAROUND1 ) + {//don't move when doing silly look around thing + move = qfalse; + } + if ( move ) + {//move toward goal + if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared + { + move = ST_Move(); + if ( (NPC->client->NPC_class != CLASS_ROCKETTROOPER||NPC->s.weapon!=WP_ROCKET_LAUNCHER||enemyDistgoalEntity + && DistanceSquared( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin ) > MIN_TURN_AROUND_DIST_SQ ) + {//don't stop running backwards if your goal is less than 100 away + if ( TIMER_Done( NPC, "runBackwardsDebounce" ) ) + {//not already waiting for next run backwards + if ( !TIMER_Exists( NPC, "runningBackwards" ) ) + {//start running backwards + TIMER_Set( NPC, "runningBackwards", Q_irand( 500, 1000 ) );//Q_irand( 2000, 3500 ) ); + } + else if ( TIMER_Done2( NPC, "runningBackwards", qtrue ) ) + {//done running backwards + TIMER_Set( NPC, "runBackwardsDebounce", Q_irand( 3000, 5000 ) ); + } + } + } + } + else + {//not running backwards + //TIMER_Remove( NPC, "runningBackwards" ); + } + } + else + { + move = qfalse; + } + } + + if ( !move ) + { + if (NPC->client->NPC_class != CLASS_ASSASSIN_DROID) + { + if ( !TIMER_Done( NPC, "duck" ) ) + { + ucmd.upmove = -127; + } + } + //FIXME: what about leaning? + } + else + {//stop ducking! + TIMER_Set( NPC, "duck", -1 ); + } + + if ( NPC->client->NPC_class == CLASS_REBORN//cultist using a gun + && NPCInfo->rank >= RANK_LT_COMM //commando or better + && NPC->enemy->s.weapon == WP_SABER )//fighting a saber-user + {//commando saboteur vs. jedi/reborn + //see if we need to avoid their saber + NPC_EvasionSaber(); + } + + if ( //!TIMER_Done( NPC, "flee" ) || + (move&&!TIMER_Done( NPC, "runBackwardsDebounce" )) ) + {//running away + faceEnemy = qfalse; + } + + //FIXME: check scf_face_move_dir here? + + if ( !faceEnemy ) + {//we want to face in the dir we're running + if ( !move ) + {//if we haven't moved, we should look in the direction we last looked? + VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); + } + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + NPC_UpdateAngles( qtrue, qtrue ); + if ( move ) + {//don't run away and shoot + shoot = qfalse; + } + } + + if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + { + shoot = qfalse; + } + + if ( NPC->enemy && NPC->enemy->enemy ) + { + if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) + {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) + shoot = qfalse; + } + } + //FIXME: don't shoot right away! + if ( NPC->client->fireDelay ) + { + if ( NPC->client->NPC_class == CLASS_SABOTEUR ) + { + Saboteur_Decloak( NPC ); + } + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER + || (NPC->s.weapon==WP_CONCUSSION&&!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) ) + { + if ( !enemyLOS || !enemyCS ) + {//cancel it + NPC->client->fireDelay = 0; + } + else + {//delay our next attempt + TIMER_Set( NPC, "attackDelay", Q_irand( 3000, 5000 ) ); + } + } + } + else if ( shoot ) + {//try to shoot if it's time + if ( NPC->client->NPC_class == CLASS_SABOTEUR ) + { + Saboteur_Decloak( NPC ); + } + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + //NASTY + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER ) + { + if ( (ucmd.buttons&BUTTON_ATTACK) + && !move + && g_spskill->integer > 1 + && !Q_irand( 0, 3 ) ) + {//every now and then, shoot a homing rocket + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + NPC->client->fireDelay = Q_irand( 1000, 2500 ); + } + } + else if ( NPC->s.weapon == WP_NOGHRI_STICK + && enemyDist < (48*48) )//? + { + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + NPC->client->fireDelay = Q_irand( 1500, 2000 ); + } + } + } + else + { + if ( NPC->attackDebounceTime < level.time ) + { + if ( NPC->client->NPC_class == CLASS_SABOTEUR ) + { + Saboteur_Cloak( NPC ); + } + } + } +} + +extern qboolean G_TuskenAttackAnimDamage( gentity_t *self ); +void NPC_BSST_Default( void ) +{ + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( NPC->s.weapon == WP_NOGHRI_STICK ) + { + if ( G_TuskenAttackAnimDamage( NPC ) ) + { + Noghri_StickTrace(); + } + } + + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSST_Patrol(); + } + else //if ( NPC->enemy ) + {//have an enemy + if ( NPC->enemy->client //enemy is a client + && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught + && NPC->enemy->enemy != NPC//enemy's enemy is not me + && (!NPC->enemy->enemy || !NPC->enemy->enemy->client || (NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR&&NPC->enemy->enemy->client->NPC_class!=CLASS_WAMPA)) )//enemy's enemy is not a client or is not a wampa or rancor (which is scarier than me) + {//they should be scared of ME and no-one else + G_SetEnemy( NPC->enemy, NPC ); + } + NPC_CheckGetNewWeapon(); + NPC_BSST_Attack(); + } +} diff --git a/code/game/AI_Tusken.cpp b/code/game/AI_Tusken.cpp new file mode 100644 index 0000000..c503a65 --- /dev/null +++ b/code/game/AI_Tusken.cpp @@ -0,0 +1,512 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +#include "g_navigator.h" + +extern void CG_DrawAlert( vec3_t origin, float rating ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern void NPC_AimAdjust( int change ); +extern qboolean FlyingCreature( gentity_t *ent ); +extern int PM_AnimLength( int index, animNumber_t anim ); + + +#define MAX_VIEW_DIST 1024 +#define MAX_VIEW_SPEED 250 +#define MAX_LIGHT_INTENSITY 255 +#define MIN_LIGHT_THRESHOLD 0.1 + +#define DISTANCE_SCALE 0.25f +#define DISTANCE_THRESHOLD 0.075f +#define SPEED_SCALE 0.25f +#define FOV_SCALE 0.5f +#define LIGHT_SCALE 0.25f + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 ) + +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS; +static qboolean enemyCS; +static qboolean faceEnemy; +static qboolean move; +static qboolean shoot; +static float enemyDist; + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +/* +------------------------- +NPC_Tusken_Precache +------------------------- +*/ +void NPC_Tusken_Precache( void ) +{ + int i; + for ( i = 1; i < 5; i ++ ) + { + G_SoundIndex( va( "sound/weapons/tusken_staff/stickhit%d.wav", i ) ); + } +} + +void Tusken_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "duck", 0 ); + TIMER_Set( ent, "stand", 0 ); + TIMER_Set( ent, "shuffleTime", 0 ); + TIMER_Set( ent, "sleepTime", 0 ); + TIMER_Set( ent, "enemyLastVisible", 0 ); + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "hideTime", 0 ); + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "stick", 0 ); + TIMER_Set( ent, "scoutTime", 0 ); + TIMER_Set( ent, "flee", 0 ); + TIMER_Set( ent, "taunting", 0 ); +} + +void NPC_Tusken_PlayConfusionSound( gentity_t *self ) +{//FIXME: make this a custom sound in sound set + if ( self->health > 0 ) + { + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + TIMER_Set( self, "flee", 0 ); + self->NPC->squadState = SQUAD_IDLE; + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} + + +/* +------------------------- +NPC_ST_Pain +------------------------- +*/ + +void NPC_Tusken_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, vec3_t point, int damage, int mod ) +{ + self->NPC->localState = LSTATE_UNDERFIRE; + + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stand", 2000 ); + + NPC_Pain( self, inflictor, other, point, damage, mod ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } +} + +/* +------------------------- +ST_HoldPosition +------------------------- +*/ + +static void Tusken_HoldPosition( void ) +{ + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + NPCInfo->goalEntity = NULL; +} + +/* +------------------------- +ST_Move +------------------------- +*/ + +static qboolean Tusken_Move( void ) +{ + NPCInfo->combatMove = qtrue;//always move straight toward our goal + + qboolean moved = NPC_MoveToGoal( qtrue ); + + //If our move failed, then reset + if ( moved == qfalse ) + {//couldn't get to enemy + //just hang here + Tusken_HoldPosition(); + } + + return moved; +} + +/* +------------------------- +NPC_BSTusken_Patrol +------------------------- +*/ + +void NPC_BSTusken_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + //Is there danger nearby + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); + if ( NPC_CheckForDanger( alertEvent ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + {//check for other alert events + //There is an event to look at + if ( alertEvent >= 0 )//&& level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + //NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; + if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED ) + { + if ( level.alertEvents[alertEvent].owner && + level.alertEvents[alertEvent].owner->client && + level.alertEvents[alertEvent].owner->health >= 0 && + level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + //NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + } + } + else + {//FIXME: get more suspicious over time? + //Save the position for movement (if necessary) + VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); + NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); + if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) + {//suspicious looks longer + NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); + } + } + } + } + + if ( NPCInfo->investigateDebounceTime > level.time ) + {//FIXME: walk over to it, maybe? Not if not chase enemies + //NOTE: stops walking or doing anything else below + vec3_t dir, angles; + float o_yaw, o_pitch; + + VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); + vectoangles( dir, angles ); + + o_yaw = NPCInfo->desiredYaw; + o_pitch = NPCInfo->desiredPitch; + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + NPC_UpdateAngles( qtrue, qtrue ); + + NPCInfo->desiredYaw = o_yaw; + NPCInfo->desiredPitch = o_pitch; + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + + +void NPC_Tusken_Taunt( void ) +{ + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TUSKENTAUNT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "taunting", NPC->client->ps.torsoAnimTimer ); + TIMER_Set( NPC, "duck", -1 ); +} + +/* +------------------------- +NPC_BSTusken_Attack +------------------------- +*/ + +void NPC_BSTusken_Attack( void ) +{ +// IN PAIN +//--------- + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + +// IN FLEE +//--------- + if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + + +// UPDATE OUR ENEMY +//------------------ + if (NPC_CheckEnemyExt()==qfalse || !NPC->enemy) + { + NPC_BSTusken_Patrol(); + return; + } + enemyDist = Distance(NPC->enemy->currentOrigin, NPC->currentOrigin); + + // Is The Current Enemy A Jawa? + //------------------------------ + if (NPC->enemy->client && NPC->enemy->client->NPC_class==CLASS_JAWA) + { + // Make Sure His Enemy Is Me + //--------------------------- + if (NPC->enemy->enemy!=NPC) + { + G_SetEnemy(NPC->enemy, NPC); + } + + // Should We Forget About Our Current Enemy And Go After The Player? + //------------------------------------------------------------------- + if ((player) && // If There Is A Player Pointer + (player!=NPC->enemy) && // The Player Is Not Currently My Enemy + (Distance(player->currentOrigin, NPC->currentOrigin)<130.0f) && // The Player Is Close Enough + (NAV::InSameRegion(NPC, player)) // And In The Same Region + ) + { + G_SetEnemy(NPC, player); + } + } + + // Update Our Last Seen Time + //--------------------------- + if (NPC_ClearLOS(NPC->enemy)) + { + NPCInfo->enemyLastSeenTime = level.time; + } + + + + // Check To See If We Are In Attack Range + //---------------------------------------- + float boundsMin = (NPC->maxs[0]+NPC->enemy->maxs[0]); + float lungeRange = (boundsMin + 65.0f); + float strikeRange = (boundsMin + 40.0f); + bool meleeRange = (enemyDistclient->ps.weapon!=WP_TUSKEN_RIFLE); + bool canSeeEnemy = ((level.time - NPCInfo->enemyLastSeenTime)<3000); + + // Check To Start Taunting + //------------------------- + if (canSeeEnemy && !meleeRange && TIMER_Done(NPC, "tuskenTauntCheck")) + { + TIMER_Set(NPC, "tuskenTauntCheck", Q_irand(2000, 6000)); + if (!Q_irand(0,3)) + { + NPC_Tusken_Taunt(); + } + } + + + if (TIMER_Done(NPC, "taunting")) + { + // Should I Attack? + //------------------ + if (meleeRange || (!meleeWeapon && canSeeEnemy)) + { + if (!(NPCInfo->scriptFlags&SCF_FIRE_WEAPON) && // If This Flag Is On, It Calls Attack From Elsewhere + !(NPCInfo->scriptFlags&SCF_DONT_FIRE) && // If This Flag Is On, Don't Fire At All + (TIMER_Done(NPC, "attackDelay")) + ) + { + ucmd.buttons &= ~BUTTON_ALT_ATTACK; + + // If Not In Strike Range, Do Lunge, Or If We Don't Have The Staff, Just Shoot Normally + //-------------------------------------------------------------------------------------- + if (enemyDist > strikeRange) + { + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + + WeaponThink( qtrue ); + TIMER_Set(NPC, "attackDelay", NPCInfo->shotTime-level.time); + } + + if ( !TIMER_Done( NPC, "duck" ) ) + { + ucmd.upmove = -127; + } + } + + // Or Should I Move? + //------------------- + else if (NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) + { + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = lungeRange; + Tusken_Move(); + } + } + + +// UPDATE ANGLES +//--------------- + if (canSeeEnemy) + { + NPC_FaceEnemy(qtrue); + } + NPC_UpdateAngles(qtrue, qtrue); +} + +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +void Tusken_StaffTrace( void ) +{ + if ( !NPC->ghoul2.size() + || NPC->weaponModel[0] <= 0 ) + { + return; + } + + int boltIndex = gi.G2API_AddBolt(&NPC->ghoul2[NPC->weaponModel[0]], "*weapon"); + if ( boltIndex != -1 ) + { + int curTime = (cg.time?cg.time:level.time); + qboolean hit = qfalse; + int lastHit = ENTITYNUM_NONE; + for ( int time = curTime-25; time <= curTime+25&&!hit; time += 25 ) + { + mdxaBone_t boltMatrix; + vec3_t tip, dir, base, angles={0,NPC->currentAngles[YAW],0}; + vec3_t mins={-2,-2,-2},maxs={2,2,2}; + trace_t trace; + + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->weaponModel[0], + boltIndex, + &boltMatrix, angles, NPC->currentOrigin, time, + NULL, NPC->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, base ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir ); + VectorMA( base, -20, dir, base ); + VectorMA( base, 78, dir, tip ); + #ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + G_DebugLine(base, tip, 1000, 0x000000ff, qtrue); + } + #endif + gi.trace( &trace, base, mins, maxs, tip, NPC->s.number, MASK_SHOT, G2_RETURNONHIT, 10 ); + if ( trace.fraction < 1.0f && trace.entityNum != lastHit ) + {//hit something + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( traceEnt->takedamage + && (!traceEnt->client || traceEnt == NPC->enemy || traceEnt->client->NPC_class != NPC->client->NPC_class) ) + {//smack + int dmg = Q_irand( 5, 10 ) * (g_spskill->integer+1); + + //FIXME: debounce? + G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/tusken_staff/stickhit%d.wav", Q_irand( 1, 4 ) ) ) ); + G_Damage( traceEnt, NPC, NPC, vec3_origin, trace.endpos, dmg, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + if ( traceEnt->health > 0 + && ( (traceEnt->client&&traceEnt->client->NPC_class==CLASS_JAWA&&!Q_irand(0,1)) + || dmg > 19 ) )//FIXME: base on skill! + {//do pain on enemy + G_Knockdown( traceEnt, NPC, dir, 300, qtrue ); + } + lastHit = trace.entityNum; + hit = qtrue; + } + } + } + } +} + +qboolean G_TuskenAttackAnimDamage( gentity_t *self ) +{ + if (self->client->ps.torsoAnim==BOTH_TUSKENATTACK1 || + self->client->ps.torsoAnim==BOTH_TUSKENATTACK2 || + self->client->ps.torsoAnim==BOTH_TUSKENATTACK3 || + self->client->ps.torsoAnim==BOTH_TUSKENLUNGE1) + { + float current = 0.0f; + int end = 0; + int start = 0; + if (!!gi.G2API_GetBoneAnimIndex(& + self->ghoul2[self->playerModel], + self->lowerLumbarBone, + level.time, + ¤t, + &start, + &end, + NULL, + NULL, + NULL)) + { + float percentComplete = (current-start)/(end-start); + //gi.Printf("%f\n", percentComplete); + switch (self->client->ps.torsoAnim) + { + case BOTH_TUSKENATTACK1: return (percentComplete>0.3 && percentComplete<0.7); + case BOTH_TUSKENATTACK2: return (percentComplete>0.3 && percentComplete<0.7); + case BOTH_TUSKENATTACK3: return (percentComplete>0.1 && percentComplete<0.5); + case BOTH_TUSKENLUNGE1: return (percentComplete>0.3 && percentComplete<0.5); + } + } + } + return qfalse; +} + +void NPC_BSTusken_Default( void ) +{ + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( G_TuskenAttackAnimDamage( NPC ) ) + { + Tusken_StaffTrace(); + } + + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSTusken_Patrol(); + } + else//if ( NPC->enemy ) + {//have an enemy + NPC_BSTusken_Attack(); + } +} diff --git a/code/game/AI_Utils.cpp b/code/game/AI_Utils.cpp new file mode 100644 index 0000000..16e6160 --- /dev/null +++ b/code/game/AI_Utils.cpp @@ -0,0 +1,1055 @@ +// These utilities are meant for strictly non-player, non-team NPCs. +// These functions are in their own file because they are only intended +// for use with NPCs who's logic has been overriden from the original +// AI code, and who's code resides in files with the AI_ prefix. + +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + + +#include "b_local.h" +#include "g_nav.h" +#include "g_navigator.h" + +#define MAX_RADIUS_ENTS 128 +#define DEFAULT_RADIUS 45 + +//extern CNavigator navigator; +extern cvar_t *d_noGroupAI; +qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member ); + +/* +------------------------- +AI_GetGroupSize +------------------------- +*/ + +int AI_GetGroupSize( vec3_t origin, int radius, team_t playerTeam, gentity_t *avoid ) +{ + gentity_t *radiusEnts[ MAX_RADIUS_ENTS ]; + vec3_t mins, maxs; + int numEnts, realCount = 0; + + //Setup the bbox to search in + for ( int i = 0; i < 3; i++ ) + { + mins[i] = origin[i] - radius; + maxs[i] = origin[i] + radius; + } + + //Get the number of entities in a given space + numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS ); + + //Cull this list + for ( int j = 0; j < numEnts; j++ ) + { + //Validate clients + if ( radiusEnts[ j ]->client == NULL ) + continue; + + //Skip the requested avoid ent if present + if ( ( avoid != NULL ) && ( radiusEnts[ j ] == avoid ) ) + continue; + + //Must be on the same team + if ( radiusEnts[ j ]->client->playerTeam != playerTeam ) + continue; + + //Must be alive + if ( radiusEnts[ j ]->health <= 0 ) + continue; + + realCount++; + } + + return realCount; +} + +//Overload + +int AI_GetGroupSize( gentity_t *ent, int radius ) +{ + if ( ( ent == NULL ) || ( ent->client == NULL ) ) + return -1; + + return AI_GetGroupSize( ent->currentOrigin, radius, ent->client->playerTeam, ent ); +} + +void AI_SetClosestBuddy( AIGroupInfo_t *group ) +{ + int i, j; + int dist, bestDist; + + for ( i = 0; i < group->numGroup; i++ ) + { + group->member[i].closestBuddy = ENTITYNUM_NONE; + + bestDist = Q3_INFINITE; + for ( j = 0; j < group->numGroup; j++ ) + { + dist = DistanceSquared( g_entities[group->member[i].number].currentOrigin, g_entities[group->member[j].number].currentOrigin ); + if ( dist < bestDist ) + { + bestDist = dist; + group->member[i].closestBuddy = group->member[j].number; + } + } + } +} + +void AI_SortGroupByPathCostToEnemy( AIGroupInfo_t *group ) +{ + AIGroupMember_t bestMembers[MAX_GROUP_MEMBERS]; + int i, j, k; + qboolean sort = qfalse; + + if ( group->enemy != NULL ) + {//FIXME: just use enemy->waypoint? + group->enemyWP = NAV::GetNearestNode(group->enemy); + } + else + { + group->enemyWP = WAYPOINT_NONE; + } + + for ( i = 0; i < group->numGroup; i++ ) + { + if ( group->enemyWP == WAYPOINT_NONE ) + {//FIXME: just use member->waypoint? + group->member[i].waypoint = WAYPOINT_NONE; + group->member[i].pathCostToEnemy = Q3_INFINITE; + } + else + {//FIXME: just use member->waypoint? + group->member[i].waypoint = NAV::GetNearestNode(group->enemy); + if ( group->member[i].waypoint != WAYPOINT_NONE ) + { + group->member[i].pathCostToEnemy = NAV::EstimateCostToGoal( group->member[i].waypoint, group->enemyWP ); + //at least one of us has a path, so do sorting + sort = qtrue; + } + else + { + group->member[i].pathCostToEnemy = Q3_INFINITE; + } + } + } + //Now sort + if ( sort ) + { + //initialize bestMembers data + for ( j = 0; j < group->numGroup; j++ ) + { + bestMembers[j].number = ENTITYNUM_NONE; + } + + for ( i = 0; i < group->numGroup; i++ ) + { + for ( j = 0; j < group->numGroup; j++ ) + { + if ( bestMembers[j].number != ENTITYNUM_NONE ) + {//slot occupied + if ( group->member[i].pathCostToEnemy < bestMembers[j].pathCostToEnemy ) + {//this guy has a shorter path than the one currenly in this spot, bump him and put myself in here + for ( k = group->numGroup; k > j; k++ ) + { + memcpy( &bestMembers[k], &bestMembers[k-1], sizeof( bestMembers[k] ) ); + } + memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) ); + break; + } + } + else + {//slot unoccupied, reached end of list, throw self in here + memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) ); + break; + } + } + } + + //Okay, now bestMembers is a sorted list, just copy it into group->members + for ( i = 0; i < group->numGroup; i++ ) + { + memcpy( &group->member[i], &bestMembers[i], sizeof( group->member[i] ) ); + } + } +} + +qboolean AI_FindSelfInPreviousGroup( gentity_t *self ) +{//go through other groups made this frame and see if any of those contain me already + int i, j; + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( level.groups[i].numGroup )//&& level.groups[i].enemy != NULL ) + {//check this one + for ( j = 0; j < level.groups[i].numGroup; j++ ) + { + if ( level.groups[i].member[j].number == self->s.number ) + { + self->NPC->group = &level.groups[i]; + return qtrue; + } + } + } + } + return qfalse; +} + +void AI_InsertGroupMember( AIGroupInfo_t *group, gentity_t *member ) +{ + //okay, you know what? Check this damn group and make sure we're not already in here! + for ( int i = 0; i < group->numGroup; i++ ) + { + if ( group->member[i].number == member->s.number ) + {//already in here + break; + } + } + if ( i < group->numGroup ) + {//found him in group already + } + else + {//add him in + group->member[group->numGroup++].number = member->s.number; + group->numState[member->NPC->squadState]++; + } + if ( !group->commander || (member->NPC->rank > group->commander->NPC->rank) ) + {//keep track of highest rank + group->commander = member; + } + member->NPC->group = group; +} + +qboolean AI_TryJoinPreviousGroup( gentity_t *self ) +{//go through other groups made this frame and see if any of those have the same enemy as me... if so, add me in! + int i; + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( level.groups[i].numGroup + && level.groups[i].numGroup < (MAX_GROUP_MEMBERS - 1) + //&& level.groups[i].enemy != NULL + && level.groups[i].enemy == self->enemy ) + {//has members, not full and has my enemy + if ( AI_ValidateGroupMember( &level.groups[i], self ) ) + {//I am a valid member for this group + AI_InsertGroupMember( &level.groups[i], self ); + return qtrue; + } + } + } + return qfalse; +} + +qboolean AI_GetNextEmptyGroup( gentity_t *self ) +{ + if ( AI_FindSelfInPreviousGroup( self ) ) + {//already in one, no need to make a new one + return qfalse; + } + + if ( AI_TryJoinPreviousGroup( self ) ) + {//try to just put us in one that already exists + return qfalse; + } + + //okay, make a whole new one, then + for ( int i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( !level.groups[i].numGroup ) + {//make a new one + self->NPC->group = &level.groups[i]; + return qtrue; + } + } + + //if ( i >= MAX_FRAME_GROUPS ) + {//WTF? Out of groups! + self->NPC->group = NULL; + return qfalse; + } +} + +qboolean AI_ValidateNoEnemyGroupMember( AIGroupInfo_t *group, gentity_t *member ) +{ + if ( !group ) + { + return qfalse; + } + vec3_t center; + if ( group->commander ) + { + VectorCopy( group->commander->currentOrigin, center ); + } + else + {//hmm, just pick the first member + if ( group->member[0].number < 0 || group->member[0].number >= ENTITYNUM_WORLD ) + { + return qfalse; + } + VectorCopy( g_entities[group->member[0].number].currentOrigin, center ); + } + //FIXME: maybe it should be based on the center of the mass of the group, not the commander? + if ( DistanceSquared( center, member->currentOrigin ) > 147456/*384*384*/ ) + { + return qfalse; + } + if ( !gi.inPVS( member->currentOrigin, center ) ) + {//not within PVS of the group enemy + return qfalse; + } + return qtrue; +} + +qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member ) +{ + //Validate ents + if ( member == NULL ) + return qfalse; + + //Validate clients + if ( member->client == NULL ) + return qfalse; + + //Validate NPCs + if ( member->NPC == NULL ) + return qfalse; + + //must be aware + if ( member->NPC->confusionTime > level.time ) + return qfalse; + + //must be allowed to join groups + if ( member->NPC->scriptFlags&SCF_NO_GROUPS ) + return qfalse; + + //Must not be in another group + if ( member->NPC->group != NULL && member->NPC->group != group ) + {//FIXME: if that group's enemy is mine, why not absorb that group into mine? + return qfalse; + } + + //Must be alive + if ( member->health <= 0 ) + return qfalse; + + //can't be in an emplaced gun + if( member->s.eFlags & EF_LOCKED_TO_WEAPON ) + return qfalse; + + if( member->s.eFlags & EF_HELD_BY_RANCOR ) + return qfalse; + + if( member->s.eFlags & EF_HELD_BY_SAND_CREATURE ) + return qfalse; + + if( member->s.eFlags & EF_HELD_BY_WAMPA ) + return qfalse; + + //Must be on the same team + if ( member->client->playerTeam != group->team ) + return qfalse; + + if ( member->client->ps.weapon == WP_SABER ||//!= self->s.weapon ) + member->client->ps.weapon == WP_THERMAL || + member->client->ps.weapon == WP_DISRUPTOR || + member->client->ps.weapon == WP_EMPLACED_GUN || + member->client->ps.weapon == WP_BOT_LASER || // Probe droid - Laser blast + member->client->ps.weapon == WP_MELEE || + member->client->ps.weapon == WP_TURRET || // turret guns + member->client->ps.weapon == WP_ATST_MAIN || + member->client->ps.weapon == WP_ATST_SIDE || + member->client->ps.weapon == WP_TIE_FIGHTER ) + {//not really a squad-type guy + return qfalse; + } + + if ( member->client->NPC_class == CLASS_ATST || + member->client->NPC_class == CLASS_PROBE || + member->client->NPC_class == CLASS_SEEKER || + member->client->NPC_class == CLASS_REMOTE || + member->client->NPC_class == CLASS_SENTRY || + member->client->NPC_class == CLASS_INTERROGATOR || + member->client->NPC_class == CLASS_MINEMONSTER || + member->client->NPC_class == CLASS_HOWLER || + member->client->NPC_class == CLASS_RANCOR || + member->client->NPC_class == CLASS_MARK1 || + member->client->NPC_class == CLASS_MARK2 ) + {//these kinds of enemies don't actually use this group AI + return qfalse; + } + + //should have same enemy + if ( member->enemy != group->enemy ) + { + if ( member->enemy != NULL ) + {//he's fighting someone else, leave him out + return qfalse; + } + if ( !gi.inPVS( member->currentOrigin, group->enemy->currentOrigin ) ) + {//not within PVS of the group enemy + return qfalse; + } + } + else if ( group->enemy == NULL ) + {//if the group is a patrol group, only take those within the room and radius + if ( !AI_ValidateNoEnemyGroupMember( group, member ) ) + { + return qfalse; + } + } + //must be actually in combat mode + if ( !TIMER_Done( member, "interrogating" ) ) + return qfalse; + //FIXME: need to have a route to enemy and/or clear shot? + return qtrue; +} + +/* +------------------------- +AI_GetGroup +------------------------- +*/ +//#define MAX_WAITERS 128 +void AI_GetGroup( gentity_t *self ) +{ + int i; + gentity_t *member;//, *waiter; + //int waiters[MAX_WAITERS]; + + if ( !self || !self->NPC ) + { + return; + } + + if ( d_noGroupAI->integer ) + { + self->NPC->group = NULL; + return; + } + + if ( !self->client ) + { + self->NPC->group = NULL; + return; + } + + if ( self->NPC->scriptFlags&SCF_NO_GROUPS ) + { + self->NPC->group = NULL; + return; + } + + if ( self->enemy && (!self->enemy->client || (level.time - self->NPC->enemyLastSeenTime > 7000 ))) + { + self->NPC->group = NULL; + return; + } + + if ( !AI_GetNextEmptyGroup( self ) ) + {//either no more groups left or we're already in a group built earlier + return; + } + + //create a new one + memset( self->NPC->group, 0, sizeof( AIGroupInfo_t ) ); + + self->NPC->group->enemy = self->enemy; + self->NPC->group->team = self->client->playerTeam; + self->NPC->group->processed = qfalse; + self->NPC->group->commander = self; + self->NPC->group->memberValidateTime = level.time + 2000; + self->NPC->group->activeMemberNum = 0; + + if ( self->NPC->group->enemy ) + { + self->NPC->group->lastSeenEnemyTime = level.time; + self->NPC->group->lastClearShotTime = level.time; + VectorCopy( self->NPC->group->enemy->currentOrigin, self->NPC->group->enemyLastSeenPos ); + } + +// for ( i = 0, member = &g_entities[0]; i < globals.num_entities ; i++, member++) + for ( i = 0; i < globals.num_entities ; i++) + { + if(!PInUse(i)) + continue; + member = &g_entities[i]; + + if ( !AI_ValidateGroupMember( self->NPC->group, member ) ) + {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group + continue; + } + + //store it + AI_InsertGroupMember( self->NPC->group, member ); + + if ( self->NPC->group->numGroup >= (MAX_GROUP_MEMBERS - 1) ) + {//full + break; + } + } + + /* + //now go through waiters and see if any should join the group + //NOTE: Some should hang back and probably not attack, so we can ambush + //NOTE: only do this if calling for reinforcements? + for ( i = 0; i < numWaiters; i++ ) + { + waiter = &g_entities[waiters[i]]; + + for ( j = 0; j < self->NPC->group->numGroup; j++ ) + { + member = &g_entities[self->NPC->group->member[j]; + + if ( gi.inPVS( waiter->currentOrigin, member->currentOrigin ) ) + {//this waiter is within PVS of a current member + } + } + } + */ + + if ( self->NPC->group->numGroup <= 0 ) + {//none in group + self->NPC->group = NULL; + return; + } + + AI_SortGroupByPathCostToEnemy( self->NPC->group ); + AI_SetClosestBuddy( self->NPC->group ); +} + +void AI_SetNewGroupCommander( AIGroupInfo_t *group ) +{ + gentity_t *member = NULL; + group->commander = NULL; + for ( int i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + + if ( !group->commander || (member && member->NPC && group->commander->NPC && member->NPC->rank > group->commander->NPC->rank) ) + {//keep track of highest rank + group->commander = member; + } + } +} + +void AI_DeleteGroupMember( AIGroupInfo_t *group, int memberNum ) +{ + if ( group->commander && group->commander->s.number == group->member[memberNum].number ) + { + group->commander = NULL; + } + if ( g_entities[group->member[memberNum].number].NPC ) + { + g_entities[group->member[memberNum].number].NPC->group = NULL; + } + for ( int i = memberNum; i < (group->numGroup-1); i++ ) + { + memcpy( &group->member[i], &group->member[i+1], sizeof( group->member[i] ) ); + } + if ( memberNum < group->activeMemberNum ) + { + group->activeMemberNum--; + if ( group->activeMemberNum < 0 ) + { + group->activeMemberNum = 0; + } + } + group->numGroup--; + if ( group->numGroup < 0 ) + { + group->numGroup = 0; + } + AI_SetNewGroupCommander( group ); +} + +void AI_DeleteSelfFromGroup( gentity_t *self ) +{ + //FIXME: if killed, keep track of how many in group killed? To affect morale? + for ( int i = 0; i < self->NPC->group->numGroup; i++ ) + { + if ( self->NPC->group->member[i].number == self->s.number ) + { + AI_DeleteGroupMember( self->NPC->group, i ); + return; + } + } +} + +extern void ST_AggressionAdjust( gentity_t *self, int change ); +extern void ST_MarkToCover( gentity_t *self ); +extern void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime ); +void AI_GroupMemberKilled( gentity_t *self ) +{ +/* AIGroupInfo_t *group = self->NPC->group; + gentity_t *member; + qboolean noflee = qfalse; + + if ( !group ) + {//what group? + return; + } + if ( !self || !self->NPC || self->NPC->rank < RANK_ENSIGN ) + {//I'm not an officer, let's not really care for now + return; + } + //temporarily drop group morale for a few seconds + group->moraleAdjust -= self->NPC->rank; + //go through and drop aggression on my teammates (more cover, worse aim) + for ( int i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( member == self ) + { + continue; + } + if ( member->NPC->rank > RANK_ENSIGN ) + {//officers do not panic + noflee = qtrue; + } + else + { + ST_AggressionAdjust( member, -1 ); + member->NPC->currentAim -= Q_irand( 0, 10 );//Q_irand( 0, 2);//drop their aim accuracy + } + } + //okay, if I'm the group commander, make everyone else flee + if ( group->commander != self ) + {//I'm not the commander... hmm, should maybe a couple flee... maybe those near me? + return; + } + //now see if there is another of sufficient rank to keep them from fleeing + if ( !noflee ) + { + self->NPC->group->speechDebounceTime = 0; + for ( int i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( member == self ) + { + continue; + } + if ( member->NPC->rank < RANK_ENSIGN ) + {//grunt + if ( group->enemy && DistanceSquared( member->currentOrigin, group->enemy->currentOrigin ) < 65536 )//256*256 + {//those close to enemy run away! + ST_StartFlee( member, group->enemy, member->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); + } + else if ( DistanceSquared( member->currentOrigin, self->currentOrigin ) < 65536 ) + {//those close to me run away! + ST_StartFlee( member, group->enemy, member->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); + } + else + {//else, maybe just a random chance + if ( Q_irand( 0, self->NPC->rank ) > member->NPC->rank ) + {//lower rank they are, higher rank I am, more likely they are to flee + ST_StartFlee( member, group->enemy, member->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); + } + else + { + ST_MarkToCover( member ); + } + } + member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more + } + member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more + } + }*/ +} + +void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot ) +{ + if ( !group ) + { + return; + } + group->lastSeenEnemyTime = level.time; + VectorCopy( spot, group->enemyLastSeenPos ); +} + +void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group ) +{ + if ( !group ) + { + return; + } + group->lastClearShotTime = level.time; +} + +void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState ) +{ + if ( !group ) + { + member->NPC->squadState = newSquadState; + return; + } + + for ( int i = 0; i < group->numGroup; i++ ) + { + if ( group->member[i].number == member->s.number ) + { + group->numState[member->NPC->squadState]--; + member->NPC->squadState = newSquadState; + group->numState[member->NPC->squadState]++; + return; + } + } +} + +qboolean AI_RefreshGroup( AIGroupInfo_t *group ) +{ + gentity_t *member; + int i;//, j; + + //see if we should merge with another group + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( &level.groups[i] == group ) + { + break; + } + else + { + if ( level.groups[i].enemy == group->enemy ) + {//2 groups with same enemy + if ( level.groups[i].numGroup+group->numGroup < (MAX_GROUP_MEMBERS - 1) ) + {//combining the members would fit in one group + qboolean deleteWhenDone = qtrue; + //combine the members of mine into theirs + for ( int j = 0; j < group->numGroup; j++ ) + { + member = &g_entities[group->member[j].number]; + if ( level.groups[i].enemy == NULL ) + {//special case for groups without enemies, must be in range + if ( !AI_ValidateNoEnemyGroupMember( &level.groups[i], member ) ) + { + deleteWhenDone = qfalse; + continue; + } + } + //remove this member from this group + AI_DeleteGroupMember( group, j ); + //keep marker at same place since we deleted this guy and shifted everyone up one + j--; + //add them to the earlier group + AI_InsertGroupMember( &level.groups[i], member ); + } + //return and delete this group + if ( deleteWhenDone ) + { + return qfalse; + } + } + } + } + } + //clear numStates + for ( i = 0; i < NUM_SQUAD_STATES; i++ ) + { + group->numState[i] = 0; + } + + //go through group and validate each membership + group->commander = NULL; + for ( i = 0; i < group->numGroup; i++ ) + { + /* + //this checks for duplicate copies of one member in a group + for ( j = 0; j < group->numGroup; j++ ) + { + if ( i != j ) + { + if ( group->member[i].number == group->member[j].number ) + { + break; + } + } + } + if ( j < group->numGroup ) + {//found a dupe! + gi.Printf( S_COLOR_RED"ERROR: member %s(%d) a duplicate group member!!!\n", g_entities[group->member[i].number].targetname, group->member[i].number ); + AI_DeleteGroupMember( group, i ); + i--; + continue; + } + */ + member = &g_entities[group->member[i].number]; + + //Must be alive + if ( member->health <= 0 ) + { + AI_DeleteGroupMember( group, i ); + //keep marker at same place since we deleted this guy and shifted everyone up one + i--; + } + else if ( group->memberValidateTime < level.time && !AI_ValidateGroupMember( group, member ) ) + { + //remove this one from the group + AI_DeleteGroupMember( group, i ); + //keep marker at same place since we deleted this guy and shifted everyone up one + i--; + } + else + {//membership is valid + //keep track of squadStates + group->numState[member->NPC->squadState]++; + if ( !group->commander || member->NPC->rank > group->commander->NPC->rank ) + {//keep track of highest rank + group->commander = member; + } + } + } + if ( group->memberValidateTime < level.time ) + { + group->memberValidateTime = level.time + Q_irand( 500, 2500 ); + } + //Now add any new guys as long as we're not full + /* + for ( i = 0, member = &g_entities[0]; i < globals.num_entities && group->numGroup < (MAX_GROUP_MEMBERS - 1); i++, member++) + { + if ( !AI_ValidateGroupMember( group, member ) ) + {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group + continue; + } + if ( member->NPC->group == group ) + {//DOH, already in our group + continue; + } + + //store it + AI_InsertGroupMember( group, member ); + } + */ + + //calc the morale of this group + group->morale = group->moraleAdjust; + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( member->NPC->rank < RANK_ENSIGN ) + {//grunts + group->morale++; + } + else + { + group->morale += member->NPC->rank; + } + if ( group->commander && debugNPCAI->integer ) + { + G_DebugLine( group->commander->currentOrigin, member->currentOrigin, FRAMETIME, 0x00ff00ff, qtrue ); + } + } + if ( group->enemy ) + {//modify morale based on enemy health and weapon + if ( group->enemy->health < 10 ) + { + group->morale += 10; + } + else if ( group->enemy->health < 25 ) + { + group->morale += 5; + } + else if ( group->enemy->health < 50 ) + { + group->morale += 2; + } + switch( group->enemy->s.weapon ) + { + case WP_SABER: + group->morale -= 5; + break; + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: + group->morale += 3; + break; + case WP_DISRUPTOR: + group->morale += 2; + break; + case WP_REPEATER: + group->morale -= 1; + break; + case WP_FLECHETTE: + group->morale -= 2; + break; + case WP_ROCKET_LAUNCHER: + group->morale -= 10; + break; + case WP_CONCUSSION: + group->morale -= 12; + break; + case WP_THERMAL: + group->morale -= 5; + break; + case WP_TRIP_MINE: + group->morale -= 3; + break; + case WP_DET_PACK: + group->morale -= 10; + break; + case WP_MELEE: // Any ol' melee attack + group->morale += 20; + break; + case WP_STUN_BATON: + group->morale += 10; + break; + case WP_EMPLACED_GUN: + group->morale -= 8; + break; + case WP_ATST_MAIN: + group->morale -= 8; + break; + case WP_ATST_SIDE: + group->morale -= 20; + break; + } + } + if ( group->moraleDebounce < level.time ) + {//slowly degrade whatever moraleAdjusters we may have + if ( group->moraleAdjust > 0 ) + { + group->moraleAdjust--; + } + else if ( group->moraleAdjust < 0 ) + { + group->moraleAdjust++; + } + group->moraleDebounce = level.time + 1000;//FIXME: define? + } + //mark this group as not having been run this frame + group->processed = qfalse; + + return (group->numGroup>0); +} + +void AI_UpdateGroups( void ) +{ + if ( d_noGroupAI->integer ) + { + return; + } + //Clear all Groups + for ( int i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( !level.groups[i].numGroup || AI_RefreshGroup( &level.groups[i] ) == qfalse )//level.groups[i].enemy == NULL || + { + memset( &level.groups[i], 0, sizeof( level.groups[i] ) ); + } + } +} + +qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum ) +{ + if ( !group ) + { + return qfalse; + } + for ( int i = 0; i < group->numGroup; i++ ) + { + if ( group->member[i].number == entNum ) + { + return qtrue; + } + } + return qfalse; +} +//Overload + +/* +void AI_GetGroup( AIGroupInfo_t &group, gentity_t *ent, int radius ) +{ + if ( ent->client == NULL ) + return; + + vec3_t temp, angles; + + //FIXME: This is specialized code.. move? + if ( ent->enemy ) + { + VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, temp ); + VectorNormalize( temp ); //FIXME: Needed? + vectoangles( temp, angles ); + } + else + { + VectorCopy( ent->currentAngles, angles ); + } + + AI_GetGroup( group, ent->currentOrigin, ent->currentAngles, DEFAULT_RADIUS, radius, ent->client->playerTeam, ent, ent->enemy ); +} +*/ + +/* +------------------------- +AI_DistributeAttack +------------------------- +*/ + +#define MAX_RADIUS_ENTS 128 + +gentity_t *AI_DistributeAttack( gentity_t *attacker, gentity_t *enemy, team_t team, int threshold ) +{ + //Don't take new targets + if ( NPC->svFlags & SVF_LOCKEDENEMY ) + return enemy; + + int numSurrounding = AI_GetGroupSize( enemy->currentOrigin, 48, team, attacker ); + + //First, see if we should look for the player + if ( enemy != &g_entities[0] ) + { + int aroundPlayer = AI_GetGroupSize( g_entities[0].currentOrigin, 48, team, attacker ); + + //See if we're above our threshold + if ( aroundPlayer < threshold ) + { + return &g_entities[0]; + } + } + + //See if our current enemy is still ok + if ( numSurrounding < threshold ) + return enemy; + + //Otherwise we need to take a new enemy if possible + vec3_t mins, maxs; + + //Setup the bbox to search in + for ( int i = 0; i < 3; i++ ) + { + mins[i] = enemy->currentOrigin[i] - 512; + maxs[i] = enemy->currentOrigin[i] + 512; + } + + //Get the number of entities in a given space + gentity_t *radiusEnts[ MAX_RADIUS_ENTS ]; + + int numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS ); + + //Cull this list + for ( int j = 0; j < numEnts; j++ ) + { + //Validate clients + if ( radiusEnts[ j ]->client == NULL ) + continue; + + //Skip the requested avoid ent if present + if ( ( radiusEnts[ j ] == enemy ) ) + continue; + + //Must be on the same team + if ( radiusEnts[ j ]->client->playerTeam != enemy->client->playerTeam ) + continue; + + //Must be alive + if ( radiusEnts[ j ]->health <= 0 ) + continue; + + //Must not be overwhelmed + if ( AI_GetGroupSize( radiusEnts[j]->currentOrigin, 48, team, attacker ) > threshold ) + continue; + + return radiusEnts[j]; + } + + return NULL; +} diff --git a/code/game/AI_Wampa.cpp b/code/game/AI_Wampa.cpp new file mode 100644 index 0000000..dd013ad --- /dev/null +++ b/code/game/AI_Wampa.cpp @@ -0,0 +1,906 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" + +// These define the working combat range for these suckers +#define MIN_DISTANCE 48 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 1024 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +float enemyDist = 0; +extern qboolean NAV_CheckAhead( gentity_t *self, vec3_t end, trace_t &trace, int clipmask ); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern cvar_t *g_dismemberment; +/* +------------------------- +NPC_Wampa_Precache +------------------------- +*/ +void NPC_Wampa_Precache( void ) +{ + /* + int i; + for ( i = 1; i < 4; i ++ ) + { + G_SoundIndex( va("sound/chars/wampa/growl%d.wav", i) ); + } + for ( i = 1; i < 3; i ++ ) + { + G_SoundIndex( va("sound/chars/wampa/snort%d.wav", i) ); + } + */ + G_SoundIndex( "sound/chars/rancor/swipehit.wav" ); + //G_SoundIndex( "sound/chars/wampa/chomp.wav" ); +} + + +/* +------------------------- +Wampa_Idle +------------------------- +*/ +void Wampa_Idle( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } +} + +qboolean Wampa_CheckRoar( gentity_t *self ) +{ + if ( self->wait < level.time ) + { + self->wait = level.time + Q_irand( 5000, 20000 ); + NPC_SetAnim( self, SETANIM_BOTH, Q_irand(BOTH_GESTURE1,BOTH_GESTURE2), (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD) ); + TIMER_Set( self, "rageTime", self->client->ps.legsAnimTimer ); + return qtrue; + } + return qfalse; +} +/* +------------------------- +Wampa_Patrol +------------------------- +*/ +void Wampa_Patrol( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Wampa_Idle(); + return; + } + Wampa_CheckRoar( NPC ); + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); +} + +/* +------------------------- +Wampa_Move +------------------------- +*/ +void Wampa_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + + trace_t trace; + if ( !NAV_CheckAhead( NPC, NPCInfo->goalEntity->currentOrigin, trace, (NPC->clipmask|CONTENTS_BOTCLIP) ) ) + { + if ( !NPC_MoveToGoal( qfalse ) ) + { + STEER::Activate(NPC); + STEER::Seek(NPC, NPCInfo->goalEntity->currentOrigin); + STEER::AvoidCollisions(NPC); + STEER::DeActivate(NPC, &ucmd); + } + } + NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE; // just get us within combat range + + if ( NPC->enemy ) + {//pick correct movement speed and anim + //run by default + ucmd.buttons &= ~BUTTON_WALKING; + if ( !TIMER_Done( NPC, "runfar" ) + || !TIMER_Done( NPC, "runclose" ) ) + {//keep running with this anim & speed for a bit + } + else if ( !TIMER_Done( NPC, "walk" ) ) + {//keep walking for a bit + ucmd.buttons |= BUTTON_WALKING; + } + else if ( visible && enemyDist > 350 && NPCInfo->stats.runSpeed == 200 )//180 ) + {//fast run, all fours + //BOTH_RUN1 + NPCInfo->stats.runSpeed = 300; + TIMER_Set( NPC, "runfar", Q_irand( 4000, 8000 ) ); + if ( NPC->client->ps.legsAnim == BOTH_RUN2 ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN2TORUN1, SETANIM_FLAG_HOLD ); + } + } + else if ( enemyDist > 200 && NPCInfo->stats.runSpeed == 300 ) + {//slow run, upright + //BOTH_RUN2 + NPCInfo->stats.runSpeed = 200;//180; + TIMER_Set( NPC, "runclose", Q_irand( 5000, 10000 ) ); + if ( NPC->client->ps.legsAnim == BOTH_RUN1 ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1TORUN2, SETANIM_FLAG_HOLD ); + } + } + else if ( enemyDist < 100 ) + {//walk + NPCInfo->stats.runSpeed = 200;//180; + ucmd.buttons |= BUTTON_WALKING; + TIMER_Set( NPC, "walk", Q_irand( 6000, 12000 ) ); + } + } + } +} + +//--------------------------------------------------------- +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force = qfalse ); +extern int NPC_GetEntsNearBolt( gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg ); + +void Wampa_Slash( int boltIndex, qboolean backhand ) +{ + gentity_t *radiusEnts[ 128 ]; + int numEnts; + const float radius = 88; + const float radiusSquared = (radius*radius); + int i; + vec3_t boltOrg; + int damage = (backhand)?Q_irand(10,15):Q_irand(20,30); + + numEnts = NPC_GetEntsNearBolt( radiusEnts, radius, boltIndex, boltOrg ); + + for ( i = 0; i < numEnts; i++ ) + { + if ( !radiusEnts[i]->inuse ) + { + continue; + } + + if ( radiusEnts[i] == NPC ) + {//Skip the wampa ent + continue; + } + + if ( radiusEnts[i]->client == NULL ) + {//must be a client + continue; + } + + if ( DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ) <= radiusSquared ) + { + //smack + G_Damage( radiusEnts[i], NPC, NPC, vec3_origin, radiusEnts[i]->currentOrigin, damage, ((backhand)?0:DAMAGE_NO_KNOCKBACK), MOD_MELEE ); + if ( backhand ) + { + //actually push the enemy + vec3_t pushDir; + vec3_t angs; + VectorCopy( NPC->client->ps.viewangles, angs ); + angs[YAW] += Q_flrand( 25, 50 ); + angs[PITCH] = Q_flrand( -25, -15 ); + AngleVectors( angs, pushDir, NULL, NULL ); + if ( radiusEnts[i]->client->NPC_class != CLASS_WAMPA + && radiusEnts[i]->client->NPC_class != CLASS_RANCOR + && radiusEnts[i]->client->NPC_class != CLASS_ATST + && !(radiusEnts[i]->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( radiusEnts[i], pushDir, 65 ); + if ( radiusEnts[i]->health > 0 && Q_irand( 0, 1 ) ) + {//do pain on enemy + G_Knockdown( radiusEnts[i], NPC, pushDir, 300, qtrue ); + } + } + } + else if ( radiusEnts[i]->health <= 0 && radiusEnts[i]->client ) + {//killed them, chance of dismembering + if ( !Q_irand( 0, 1 ) ) + {//bite something off + int hitLoc = HL_HAND_LT; + /* + if ( g_dismemberment->integer < 11381138 ) + { + hitLoc = Q_irand( HL_WAIST, HL_HAND_LT ); + } + else + { + hitLoc = Q_irand( HL_WAIST, HL_HEAD ); + } + if ( hitLoc == HL_HEAD ) + { + NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else if ( hitLoc == HL_WAIST ) + { + NPC_SetAnim( radiusEnts[i], SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + */ + radiusEnts[i]->client->dismembered = false; + //FIXME: the limb should just disappear, cuz I ate it + G_DoDismemberment( radiusEnts[i], radiusEnts[i]->currentOrigin, MOD_SABER, 1000, hitLoc, qtrue ); + } + } + else if ( !Q_irand( 0, 3 ) && radiusEnts[i]->health > 0 ) + {//one out of every 4 normal hits does a knockdown, too + vec3_t pushDir; + vec3_t angs; + VectorCopy( NPC->client->ps.viewangles, angs ); + angs[YAW] += Q_flrand( 25, 50 ); + angs[PITCH] = Q_flrand( -25, -15 ); + AngleVectors( angs, pushDir, NULL, NULL ); + G_Knockdown( radiusEnts[i], NPC, pushDir, 35, qtrue ); + } + G_Sound( radiusEnts[i], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + } + } +} + +//------------------------------ +void Wampa_Attack( float distance, qboolean doCharge ) +{ + if ( !TIMER_Exists( NPC, "attacking" ) ) + { + if ( !Q_irand(0, 3) && !doCharge ) + {//double slash + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 750 ); + } + else if ( doCharge || (distance > 270 && distance < 430 && !Q_irand(0, 1)) ) + {//leap + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 500 ); + vec3_t fwd, yawAng ={0, NPC->client->ps.viewangles[YAW], 0}; + AngleVectors( yawAng, fwd, NULL, NULL ); + VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 150; + NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; + } + else if ( distance < 100 )//&& !Q_irand( 0, 4 ) ) + {//grab + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_START, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + NPC->client->ps.legsAnimTimer += 200; + TIMER_Set( NPC, "attack_dmg", 250 ); + } + else + {//backhand + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 250 ); + } + + TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + random() * 200 ); + //allow us to re-evaluate our running speed/anim + TIMER_Set( NPC, "runfar", -1 ); + TIMER_Set( NPC, "runclose", -1 ); + TIMER_Set( NPC, "walk", -1 ); + } + + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + + if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) + { + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_ATTACK1: + Wampa_Slash( NPC->handRBolt, qfalse ); + //do second hit + TIMER_Set( NPC, "attack_dmg2", 100 ); + break; + case BOTH_ATTACK2: + Wampa_Slash( NPC->handRBolt, qfalse ); + TIMER_Set( NPC, "attack_dmg2", 100 ); + break; + case BOTH_ATTACK3: + Wampa_Slash( NPC->handLBolt, qtrue ); + break; + } + } + else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) ) + { + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_ATTACK1: + Wampa_Slash( NPC->handLBolt, qfalse ); + break; + case BOTH_ATTACK2: + Wampa_Slash( NPC->handLBolt, qfalse ); + break; + } + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); + + if ( NPC->client->ps.legsAnim == BOTH_ATTACK1 && distance > (NPC->maxs[0]+MIN_DISTANCE) ) + {//okay to keep moving + ucmd.buttons |= BUTTON_WALKING; + Wampa_Move( 1 ); + } +} + +//---------------------------------- +void Wampa_Combat( void ) +{ + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS( NPC->enemy ) ) + { + if ( !Q_irand( 0, 10 ) ) + { + if ( Wampa_CheckRoar( NPC ) ) + { + return; + } + } + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE; // just get us within combat range + + Wampa_Move( 0 ); + return; + } + /* + else if ( UpdateGoal() ) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE; // just get us within combat range + + Wampa_Move( 1 ); + return; + }*/ + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + //FIXME: always seems to face off to the left or right?!!!! + NPC_FaceEnemy( qtrue ); + + float distance = enemyDist = Distance( NPC->currentOrigin, NPC->enemy->currentOrigin ); + + qboolean advance = (qboolean)( distance > (NPC->maxs[0]+MIN_DISTANCE) ? qtrue : qfalse ); + qboolean doCharge = qfalse; + + if ( advance ) + {//have to get closer + vec3_t yawOnlyAngles = {0, NPC->currentAngles[YAW], 0}; + if ( NPC->enemy->health > 0//enemy still alive + && fabs(distance-350) <= 80 //enemy anywhere from 270 to 430 away + && InFOV( NPC->enemy->currentOrigin, NPC->currentOrigin, yawOnlyAngles, 20, 20 ) )//enemy generally in front + {//10% chance of doing charge anim + if ( !Q_irand( 0, 6 ) ) + {//go for the charge + doCharge = qtrue; + advance = qfalse; + } + } + } + + if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + Wampa_Move( 1 ); + } + } + else + { + if ( !Q_irand( 0, 15 ) ) + {//FIXME: only do this if we just damaged them or vice-versa? + if ( Wampa_CheckRoar( NPC ) ) + { + return; + } + } + Wampa_Attack( distance, doCharge ); + } +} + +/* +------------------------- +NPC_Wampa_Pain +------------------------- +*/ +void NPC_Wampa_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + qboolean hitByWampa = qfalse; + if ( self->count ) + {//FIXME: need pain anim + NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND2TO1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( self, "takingPain", self->client->ps.legsAnimTimer ); + TIMER_Set(self,"attacking",-level.time); + return; + } + if ( other&&other->client&&other->client->NPC_class==CLASS_WAMPA ) + { + hitByWampa = qtrue; + } + if ( other + && other->inuse + && other != self->enemy + && !(other->flags&FL_NOTARGET) ) + { + if ( (!other->s.number&&!Q_irand(0,3)) + || !self->enemy + || self->enemy->health == 0 + || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_WAMPA) + || (!Q_irand(0, 4 ) && DistanceSquared( other->currentOrigin, self->currentOrigin ) < DistanceSquared( self->enemy->currentOrigin, self->currentOrigin )) ) + {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker + //FIXME: if can't nav to my enemy, take this guy if I can nav to him + self->lastEnemy = other; + G_SetEnemy( self, other ); + if ( self->enemy != self->lastEnemy ) + {//clear this so that we only sniff the player the first time we pick them up + self->useDebounceTime = 0; + } + TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + if ( hitByWampa ) + {//stay mad at this Wampa for 2-5 secs before looking for other enemies + TIMER_Set( self, "wampaInfight", Q_irand( 2000, 5000 ) ); + } + } + } + if ( (hitByWampa|| Q_irand( 0, 100 ) < damage )//hit by wampa, hit while holding live victim, or took a lot of damage + && self->client->ps.legsAnim != BOTH_GESTURE1 + && self->client->ps.legsAnim != BOTH_GESTURE2 + && TIMER_Done( self, "takingPain" ) ) + { + if ( !Wampa_CheckRoar( self ) ) + { + if ( self->client->ps.legsAnim != BOTH_ATTACK1 + && self->client->ps.legsAnim != BOTH_ATTACK2 + && self->client->ps.legsAnim != BOTH_ATTACK3 ) + {//cant interrupt one of the big attack anims + if ( self->health > 100 || hitByWampa ) + { + TIMER_Remove( self, "attacking" ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( !Q_irand( 0, 1 ) ) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + TIMER_Set( self, "takingPain", self->client->ps.legsAnimTimer+Q_irand(0, 500*(2-g_spskill->integer)) ); + TIMER_Set(self,"attacking",-level.time); + //allow us to re-evaluate our running speed/anim + TIMER_Set( self, "runfar", -1 ); + TIMER_Set( self, "runclose", -1 ); + TIMER_Set( self, "walk", -1 ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } + } + } + } +} + +void Wampa_DropVictim( gentity_t *self ) +{ + //FIXME: if Wampa dies, it should drop its victim. + //FIXME: if Wampa is removed, it must remove its victim. + //FIXME: if in BOTH_HOLD_DROP, throw them a little, too? + if ( self->health > 0 ) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND2TO1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + TIMER_Set(self,"attacking",-level.time); + if ( self->activator ) + { + if ( self->activator->client ) + { + self->activator->client->ps.eFlags &= ~EF_HELD_BY_WAMPA; + } + self->activator->activator = NULL; + NPC_SetAnim( self->activator, SETANIM_BOTH, BOTH_RELEASED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->activator->client->ps.legsAnimTimer += 500; + self->activator->client->ps.weaponTime = self->activator->client->ps.torsoAnimTimer = self->activator->client->ps.legsAnimTimer; + if ( self->activator->health > 0 ) + { + if ( self->activator->NPC ) + {//start thinking again + self->activator->NPC->nextBStateThink = level.time; + } + if ( self->activator->client && self->activator->s.number < MAX_CLIENTS ) + { + vec3_t vicAngles = {30,AngleNormalize180(self->client->ps.viewangles[YAW]+180),0}; + SetClientViewAngle( self->activator, vicAngles ); + } + } + else + { + if ( self->enemy == self->activator ) + { + self->enemy = NULL; + } + self->activator->clipmask &= ~CONTENTS_BODY; + } + self->activator = NULL; + } + self->count = 0;//drop him +} + +qboolean Wampa_CheckDropVictim( gentity_t *self, qboolean excludeMe ) +{ + if ( !self + || !self->activator ) + { + return qtrue; + } + vec3_t mins={self->activator->mins[0]-1,self->activator->mins[1]-1,0}; + vec3_t maxs={self->activator->maxs[0]+1,self->activator->maxs[1]+1,1}; + vec3_t start={self->activator->currentOrigin[0],self->activator->currentOrigin[1],self->activator->absmin[2]}; + vec3_t end={self->activator->currentOrigin[0],self->activator->currentOrigin[1],self->activator->absmax[2]-1}; + trace_t trace; + if ( excludeMe ) + { + gi.unlinkentity( self ); + } + gi.trace( &trace, start, mins, maxs, end, self->activator->s.number, self->activator->clipmask ); + if ( excludeMe ) + { + gi.linkentity( self ); + } + if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0f ) + { + Wampa_DropVictim( self ); + return qtrue; + } + if ( excludeMe ) + {//victim stuck in wall + if ( self->NPC ) + {//turn + self->NPC->desiredYaw += Q_irand( -30, 30 ); + self->NPC->lockedDesiredYaw = self->NPC->desiredYaw; + } + } + return qfalse; +} + +extern float NPC_EnemyRangeFromBolt( int boltIndex ); +qboolean Wampa_TryGrab( void ) +{ + const float radius = 64.0f; + + if ( !NPC->enemy + || !NPC->enemy->client + || NPC->enemy->health <= 0 ) + { + return qfalse; + } + + float enemyDist = NPC_EnemyRangeFromBolt( NPC->handRBolt ); + if ( enemyDist <= radius + && !NPC->count //don't have one in hand already + && NPC->enemy->client->NPC_class != CLASS_RANCOR + && NPC->enemy->client->NPC_class != CLASS_GALAKMECH + && NPC->enemy->client->NPC_class != CLASS_ATST + && NPC->enemy->client->NPC_class != CLASS_GONK + && NPC->enemy->client->NPC_class != CLASS_R2D2 + && NPC->enemy->client->NPC_class != CLASS_R5D2 + && NPC->enemy->client->NPC_class != CLASS_MARK1 + && NPC->enemy->client->NPC_class != CLASS_MARK2 + && NPC->enemy->client->NPC_class != CLASS_MOUSE + && NPC->enemy->client->NPC_class != CLASS_PROBE + && NPC->enemy->client->NPC_class != CLASS_SEEKER + && NPC->enemy->client->NPC_class != CLASS_REMOTE + && NPC->enemy->client->NPC_class != CLASS_SENTRY + && NPC->enemy->client->NPC_class != CLASS_INTERROGATOR + && NPC->enemy->client->NPC_class != CLASS_VEHICLE ) + {//grab + NPC->enemy = NPC->enemy;//make him my new best friend + NPC->enemy->client->ps.eFlags |= EF_HELD_BY_WAMPA; + //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something + NPC->enemy->activator = NPC; // kind of dumb, but when we are locked to the Rancor, we are owned by it. + NPC->activator = NPC->enemy;//remember him + NPC->count = 1;//in my hand + //wait to attack + TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer + Q_irand(500, 2500) ); + NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_GRABBED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "takingPain", -level.time ); + return qtrue; + } + else if ( enemyDist < radius*2.0f ) + {//smack + G_Sound( NPC->enemy, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + //actually push the enemy + vec3_t pushDir; + vec3_t angs; + VectorCopy( NPC->client->ps.viewangles, angs ); + angs[YAW] += Q_flrand( 25, 50 ); + angs[PITCH] = Q_flrand( -25, -15 ); + AngleVectors( angs, pushDir, NULL, NULL ); + if ( NPC->enemy->client->NPC_class != CLASS_RANCOR + && NPC->enemy->client->NPC_class != CLASS_ATST + && !(NPC->enemy->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( NPC->enemy, pushDir, Q_irand( 30, 70 ) ); + if ( NPC->enemy->health > 0 ) + {//do pain on enemy + G_Knockdown( NPC->enemy, NPC, pushDir, 300, qtrue ); + } + } + } + return qfalse; +} + +/* +------------------------- +NPC_BSWampa_Default +------------------------- +*/ +void NPC_BSWampa_Default( void ) +{ + //NORMAL ANIMS + // stand1 = normal stand + // walk1 = normal, non-angry walk + // walk2 = injured + // run1 = far away run + // run2 = close run + //VICTIM ANIMS + // grabswipe = melee1 - sweep out and grab + // stand2 attack = attack4 - while holding victim, swipe at him + // walk3_drag = walk5 - walk with drag + // stand2 = hold victim + // stand2to1 = drop victim + if ( NPC->client->ps.legsAnim == BOTH_HOLD_START ) + { + NPC_FaceEnemy( qtrue ); + if ( NPC->client->ps.legsAnimTimer < 200 ) + {//see if he's there to grab + if ( !Wampa_TryGrab() ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + return; + } + + + if ( NPC->count ) + { + if ( !NPC->activator + || !NPC->activator->client ) + {//wtf? + NPC->count = 0; + NPC->activator = NULL; + } + else + { + if ( NPC->client->ps.legsAnim == BOTH_HOLD_DROP ) + { + if ( NPC->client->ps.legsAnimTimer < PM_AnimLength(NPC->client->clientInfo.animFileIndex, (animNumber_t)NPC->client->ps.legsAnim)-500 ) + {//at least half a second into the anim + if ( Wampa_CheckDropVictim( NPC, qfalse ) ) + { + TIMER_Set( NPC, "attacking", 1000+(Q_irand(500,1000)*(3-g_spskill->integer)) ); + } + } + } + else if ( !TIMER_Done( NPC, "takingPain" ) ) + { + Wampa_CheckDropVictim( NPC, qfalse ); + } + else if ( NPC->activator->health <= 0 ) + { + if ( TIMER_Done(NPC,"sniffCorpse") ) + { + Wampa_CheckDropVictim( NPC, qfalse ); + } + } + else if ( NPC->useDebounceTime >= level.time + && NPC->activator ) + {//just sniffing the guy + if ( NPC->useDebounceTime <= level.time + 100 + && NPC->client->ps.legsAnim != BOTH_HOLD_DROP) + {//just about done, drop him + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attacking", NPC->client->ps.legsAnimTimer+500 ); + } + } + else + { + if ( !NPC->useDebounceTime + && NPC->activator + && NPC->activator->s.number < MAX_CLIENTS ) + {//first time I pick the player, just sniff them + if ( TIMER_Done(NPC,"attacking") ) + {//ready to attack + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_SNIFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->useDebounceTime = level.time + NPC->client->ps.legsAnimTimer + Q_irand( 500, 2000 ); + } + } + else + { + if ( TIMER_Done(NPC,"attacking") ) + {//ready to attack + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_ATTACK/*BOTH_ATTACK4*/, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set(NPC,"grabAttackDamage",1400); + TIMER_Set(NPC,"attacking",NPC->client->ps.legsAnimTimer+Q_irand(3000,10000)); + } + + if ( NPC->client->ps.legsAnim == BOTH_HOLD_ATTACK ) + { + if ( NPC->client->ps.legsAnimTimer ) + { + if ( TIMER_Done2(NPC,"grabAttackDamage",qtrue) ) + { + G_Sound( NPC->activator, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->currentOrigin, Q_irand( 25, 40 ), (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_ARMOR), MOD_MELEE ); + if ( NPC->activator->health <= 0 ) + {//killed them, chance of dismembering + int hitLoc = HL_HAND_LT; + // if ( g_dismemberment->integer < 11381138 ) + // { + // hitLoc = Q_irand( HL_WAIST, HL_HAND_LT ); + // } + // else + // { + // hitLoc = Q_irand( HL_WAIST, HL_HEAD ); + // } + NPC->activator->client->dismembered = false; + //FIXME: the limb should just disappear, cuz I ate it + G_DoDismemberment( NPC->activator, NPC->activator->currentOrigin, MOD_SABER, 1000, hitLoc, qtrue ); + TIMER_Set( NPC, "sniffCorpse", Q_irand( 2000, 5000 ) ); + } + NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_HANG_PAIN, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_IDLE/*BOTH_ATTACK4*/, SETANIM_FLAG_NORMAL ); + } + } + else if ( NPC->client->ps.legsAnim == BOTH_STAND2TO1 + && !NPC->client->ps.legsAnimTimer ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_HOLD_IDLE, SETANIM_FLAG_NORMAL ); + } + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( NPCInfo->localState == LSTATE_WAITING + && TIMER_Done2( NPC, "takingPain", qtrue ) ) + {//was not doing anything because we were taking pain, but pain is done now, so clear it... + NPCInfo->localState = LSTATE_CLEAR; + } + + if ( !TIMER_Done( NPC, "rageTime" ) ) + {//do nothing but roar first time we see an enemy + NPC_FaceEnemy( qtrue ); + return; + } + if ( NPC->enemy ) + { + if ( NPC->enemy->client //enemy is a client + && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught + && NPC->enemy->enemy != NPC//enemy's enemy is not me + && (!NPC->enemy->enemy || !NPC->enemy->enemy->client || NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR) )//enemy's enemy is not a client or is not a rancor (which is scarier than me) + {//they should be scared of ME and no-one else + G_SetEnemy( NPC->enemy, NPC ); + } + if ( !TIMER_Done(NPC,"attacking") ) + {//in middle of attack + //face enemy + NPC_FaceEnemy( qtrue ); + //continue attack logic + enemyDist = Distance( NPC->currentOrigin, NPC->enemy->currentOrigin ); + Wampa_Attack( enemyDist, qfalse ); + return; + } + else + { + if ( TIMER_Done(NPC,"angrynoise") ) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/wampa/misc/anger%d.wav", Q_irand(1, 2)) ); + + TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); + } + //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while + if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_WAMPA ) + {//got mad at another Wampa, look for a valid enemy + if ( TIMER_Done( NPC, "wampaInfight" ) ) + { + NPC_CheckEnemyExt( qtrue ); + } + } + else + { + if ( NPC_ValidEnemy( NPC->enemy ) == qfalse ) + { + TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now + if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) + {//it's been a while since the enemy died, or enemy is completely gone, get bored with him + NPC->enemy = NULL; + Wampa_Patrol(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) + { + gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? + NPC->enemy = NULL; + gentity_t *newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); + NPC->enemy = sav_enemy; + if ( newEnemy && newEnemy != sav_enemy ) + {//picked up a new enemy! + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, newEnemy ); + if ( NPC->enemy != NPC->lastEnemy ) + {//clear this so that we only sniff the player the first time we pick them up + NPC->useDebounceTime = 0; + } + //hold this one for at least 5-15 seconds + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + } + else + {//look again in 2-5 secs + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); + } + } + } + Wampa_Combat(); + return; + } + } + else + { + if ( TIMER_Done(NPC,"idlenoise") ) + { + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/wampa/misc/anger3.wav" ); + + TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); + } + if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Wampa_Patrol(); + } + else + { + Wampa_Idle(); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/code/game/AnimalNPC.c b/code/game/AnimalNPC.c new file mode 100644 index 0000000..c091203 --- /dev/null +++ b/code/game/AnimalNPC.c @@ -0,0 +1,1065 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#include "..\game\wp_saber.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +#endif + +#ifdef QAGAME //we only want a few of these functions for BG + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifndef _JK2MP +extern void CG_ChangeWeapon( int num ); +#endif + +extern void Vehicle_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_VehicleTrace( trace_t *results, const vec3_t start, const vec3_t tMins, const vec3_t tMaxs, const vec3_t end, int passEntityNum, int contentmask ); + +// Update death sequence. +static void DeathUpdate( Vehicle_t *pVeh ) +{ + if ( level.time >= pVeh->m_iDieTime ) + { + // If the vehicle is not empty. + if ( pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) + { + pVeh->m_pVehicleInfo->EjectAll( pVeh ); + } + else + { + // Waste this sucker. + } + + // Die now... +/* else + { + vec3_t mins, maxs, bottom; + trace_t trace; + + if ( pVeh->m_pVehicleInfo->explodeFX ) + { + G_PlayEffect( pVeh->m_pVehicleInfo->explodeFX, parent->currentOrigin ); + //trace down and place mark + VectorCopy( parent->currentOrigin, bottom ); + bottom[2] -= 80; + gi.trace( &trace, parent->currentOrigin, vec3_origin, vec3_origin, bottom, parent->s.number, CONTENTS_SOLID ); + if ( trace.fraction < 1.0f ) + { + VectorCopy( trace.endpos, bottom ); + bottom[2] += 2; + G_PlayEffect( "ships/ship_explosion_mark", trace.endpos ); + } + } + + parent->takedamage = qfalse;//so we don't recursively damage ourselves + if ( pVeh->m_pVehicleInfo->explosionRadius > 0 && pVeh->m_pVehicleInfo->explosionDamage > 0 ) + { + VectorCopy( parent->mins, mins ); + mins[2] = -4;//to keep it off the ground a *little* + VectorCopy( parent->maxs, maxs ); + VectorCopy( parent->currentOrigin, bottom ); + bottom[2] += parent->mins[2] - 32; + gi.trace( &trace, parent->currentOrigin, mins, maxs, bottom, parent->s.number, CONTENTS_SOLID ); + G_RadiusDamage( trace.endpos, NULL, pVeh->m_pVehicleInfo->explosionDamage, pVeh->m_pVehicleInfo->explosionRadius, NULL, MOD_EXPLOSIVE );//FIXME: extern damage and radius or base on fuel + } + + parent->e_ThinkFunc = thinkF_G_FreeEntity; + parent->nextthink = level.time + FRAMETIME; + }*/ + } +} + +// Like a think or move command, this updates various vehicle properties. +static bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + return g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ); +} +#endif //QAGAME + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + float fWalkSpeedMax; + int curTime; + bgEntity_t *parent = pVeh->m_pParentEntity; +#ifdef _JK2MP + playerState_t *parentPS = parent->playerState; +#else + playerState_t *parentPS = &parent->client->ps; +#endif + +#ifndef _JK2MP//SP + curTime = level.time; +#elif QAGAME//MP GAME + curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + curTime = pm->cmd.serverTime; +#endif + + +#ifndef _JK2MP //bad for prediction - fixme + // Bucking so we can't do anything. + if ( pVeh->m_ulFlags & VEH_BUCKING || pVeh->m_ulFlags & VEH_FLYING || pVeh->m_ulFlags & VEH_CRASHING ) + { +//#ifdef QAGAME //this was in Update above +// ((gentity_t *)parent)->client->ps.speed = 0; +//#endif + parentPS->speed = 0; + return; + } +#endif + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + speedMax = pVeh->m_pVehicleInfo->speedMax; + + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + + + + if ( pVeh->m_pPilot /*&& (pilotPS->weapon == WP_NONE || pilotPS->weapon == WP_MELEE )*/ && + (pVeh->m_ucmd.buttons & BUTTON_ALT_ATTACK) && pVeh->m_pVehicleInfo->turboSpeed ) + { + if ((curTime - pVeh->m_iTurboTime)>pVeh->m_pVehicleInfo->turboRecharge) + { + pVeh->m_iTurboTime = (curTime + pVeh->m_pVehicleInfo->turboDuration); +#ifndef _JK2MP //kill me now + if (pVeh->m_pVehicleInfo->soundTurbo) + { + G_SoundIndexOnEnt(pVeh->m_pParentEntity, CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo); + } +#endif + parentPS->speed = pVeh->m_pVehicleInfo->turboSpeed; // Instantly Jump To Turbo Speed + } + } + + if ( curTime < pVeh->m_iTurboTime ) + { + speedMax = pVeh->m_pVehicleInfo->turboSpeed; + } + else + { + speedMax = pVeh->m_pVehicleInfo->speedMax; + } + +#ifdef _JK2MP + if ( !parentPS->m_iVehicleNum ) +#else + if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//drifts to a stop + speedInc = speedIdle * pVeh->m_fTimeModifier; + VectorClear( parentPS->moveDir ); + //m_ucmd.forwardmove = 127; + parentPS->speed = 0; + } + else + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + + if ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + parentPS->speed -= speedIdleDec; + } + } + // No input, so coast to stop. + else if ( parentPS->speed > 0.0f ) + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < 0.0f ) + { + parentPS->speed = 0.0f; + } + } + else if ( parentPS->speed < 0.0f ) + { + parentPS->speed += speedIdleDec; + if ( parentPS->speed > 0.0f ) + { + parentPS->speed = 0.0f; + } + } + } + else + { + if ( pVeh->m_ucmd.forwardmove < 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + if ( pVeh->m_ucmd.upmove < 0 ) + { + pVeh->m_ucmd.upmove = 0; + } + + //pVeh->m_ucmd.rightmove = 0; + + /*if ( !pVeh->m_pVehicleInfo->strafePerc + || (!g_speederControlScheme->value && !parent->s.number) ) + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + pVeh->m_ucmd.rightmove = 0; + }*/ + } + + fWalkSpeedMax = speedMax * 0.275f; + if ( curTime>pVeh->m_iTurboTime && (pVeh->m_ucmd.buttons & BUTTON_WALKING) && parentPS->speed > fWalkSpeedMax ) + { + parentPS->speed = fWalkSpeedMax; + } + else if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessOrientCommands the Vehicle. +static void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + bgEntity_t *parent = pVeh->m_pParentEntity; + playerState_t *parentPS, *riderPS; + +#ifdef _JK2MP + bgEntity_t *rider = NULL; + if (parent->s.owner != ENTITYNUM_NONE) + { + rider = PM_BGEntForNum(parent->s.owner); //&g_entities[parent->r.ownerNum]; + } +#else + gentity_t *rider = parent->owner; +#endif + + // Bucking so we can't do anything. +#ifndef _JK2MP //bad for prediction - fixme + if ( pVeh->m_ulFlags & VEH_BUCKING || pVeh->m_ulFlags & VEH_FLYING || pVeh->m_ulFlags & VEH_CRASHING ) + { + return; + } +#endif + +#ifdef _JK2MP + if ( !rider ) +#else + if ( !rider || !rider->client ) +#endif + { + rider = parent; + } + + + +#ifdef _JK2MP + parentPS = parent->playerState; + riderPS = rider->playerState; +#else + parentPS = &parent->client->ps; + riderPS = &rider->client->ps; +#endif + + if (rider) + { +#ifdef _JK2MP + float angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*4.0f; //magic number hackery + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; +#endif + } + + +/* speed = VectorLength( parentPS->velocity ); + + // If the player is the rider... + if ( rider->s.number < MAX_CLIENTS ) + {//FIXME: use the vehicle's turning stat in this calc + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + } + else + { + float turnSpeed = pVeh->m_pVehicleInfo->turningSpeed; + if ( !pVeh->m_pVehicleInfo->turnWhenStopped + && !parentPS->speed )//FIXME: or !pVeh->m_ucmd.forwardmove? + {//can't turn when not moving + //FIXME: or ramp up to max turnSpeed? + turnSpeed = 0.0f; + } +#ifdef _JK2MP + if (rider->s.eType == ET_NPC) +#else + if ( !rider || rider->NPC ) +#endif + {//help NPCs out some + turnSpeed *= 2.0f; +#ifdef _JK2MP + if (parentPS->speed > 200.0f) +#else + if ( parent->client->ps.speed > 200.0f ) +#endif + { + turnSpeed += turnSpeed * parentPS->speed/200.0f*0.05f; + } + } + turnSpeed *= pVeh->m_fTimeModifier; + + //default control scheme: strafing turns, mouselook aims + if ( pVeh->m_ucmd.rightmove < 0 ) + { + pVeh->m_vOrientation[YAW] += turnSpeed; + } + else if ( pVeh->m_ucmd.rightmove > 0 ) + { + pVeh->m_vOrientation[YAW] -= turnSpeed; + } + + if ( pVeh->m_pVehicleInfo->malfunctionArmorLevel && pVeh->m_iArmor <= pVeh->m_pVehicleInfo->malfunctionArmorLevel ) + {//damaged badly + } + }*/ + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef _JK2MP //temp hack til mp speeder controls are sorted -rww +void AnimalProcessOri(Vehicle_t *pVeh) +{ + ProcessOrientCommands(pVeh); +} +#endif + +#ifdef QAGAME //back to our game-only functions +// This function makes sure that the vehicle is properly animated. +/* +static void AnimalTailSwipe(Vehicle_t* pVeh, gentity_t *parent, gentity_t *pilot) +{ + trace_t trace; + vec3_t angles; + vec3_t vRoot, vTail; + vec3_t lMins, lMaxs; + mdxaBone_t boltMatrix; + int iRootBone; + int iRootTail; + + VectorSet(angles, 0, parent->currentAngles[YAW], 0); + VectorSet(lMins, parent->mins[0]-1, parent->mins[1]-1, 0); + VectorSet(lMaxs, parent->maxs[0]+1, parent->maxs[1]+1, 1); +#ifdef _JK2MP + iRootBone = trap_G2API_AddBolt( parent->ghoul2, 0, "tail_01" ); + iRootTail = trap_G2API_AddBolt( parent->ghoul2, 0, "tail_04" ); + + // Get the positions of the root of the tail and the tail end of it. + trap_G2API_GetBoltMatrix( parent->ghoul2, 0, iRootBone, + &boltMatrix, angles, parent->currentOrigin, level.time, + NULL, parent->modelScale ); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, vRoot ); + + trap_G2API_GetBoltMatrix( parent->ghoul2, 0, iRootTail, + &boltMatrix, angles, parent->currentOrigin, level.time, + NULL, parent->modelScale ); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, vTail ); +#else + iRootBone = gi.G2API_GetBoneIndex( &parent->ghoul2[parent->playerModel], "tail_01", qtrue ); + iRootTail = gi.G2API_GetBoneIndex( &parent->ghoul2[parent->playerModel], "tail_04", qtrue ); + + // Get the positions of the root of the tail and the tail end of it. + gi.G2API_GetBoltMatrix( parent->ghoul2, parent->playerModel, iRootBone, + &boltMatrix, angles, parent->currentOrigin, (cg.time?cg.time:level.time), + NULL, parent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, vRoot ); + + gi.G2API_GetBoltMatrix( parent->ghoul2, parent->playerModel, iRootTail, + &boltMatrix, angles, parent->currentOrigin, (cg.time?cg.time:level.time), + NULL, parent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, vTail ); +#endif + + // Trace from the root of the tail to the very end. + G_VehicleTrace( &trace, vRoot, lMins, lMaxs, vTail, parent->s.number, MASK_NPCSOLID ); + if ( trace.fraction < 1.0f ) + { + if ( ENTITYNUM_NONE != trace.entityNum && g_entities[trace.entityNum].client && +#ifndef _JK2MP //no rancor in jk2mp (at least not currently) + g_entities[trace.entityNum].client->NPC_class != CLASS_RANCOR && +#else //and in mp want to check inuse + g_entities[trace.entityNum].inuse && +#endif + g_entities[trace.entityNum].client->NPC_class != CLASS_VEHICLE ) + { + vec3_t pushDir; + vec3_t angs; + int iDamage = 10; + // Get the direction we're facing. + VectorCopy( parent->client->ps.viewangles, angs ); + // Add some fudge. + angs[YAW] += Q_flrand( 5, 15 ); + angs[PITCH] = Q_flrand( -20, -10 ); + AngleVectors( angs, pushDir, NULL, NULL ); + // Reverse direction. + pushDir[YAW] = -pushDir[YAW]; + + // Smack this ho down. +#ifdef _JK2MP + G_Sound( &g_entities[trace.entityNum], CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); +#else + G_Sound( &g_entities[trace.entityNum], G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); +#endif + G_Throw( &g_entities[trace.entityNum], pushDir, 50 ); + + if ( g_entities[trace.entityNum].health > 0 ) + { + // Knock down and dish out some hurt. + gentity_t *hit = &g_entities[trace.entityNum]; +#ifdef _JK2MP + if (BG_KnockDownable(&hit->client->ps)) + { + hit->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + hit->client->ps.forceHandExtendTime = pm->cmd.serverTime + 1100; + hit->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim + + hit->client->ps.otherKiller = pilot->s.number; + hit->client->ps.otherKillerTime = level.time + 5000; + hit->client->ps.otherKillerDebounceTime = level.time + 100; + + hit->client->ps.velocity[0] = pushDir[0]*80; + hit->client->ps.velocity[1] = pushDir[1]*80; + hit->client->ps.velocity[2] = 100; + } +#else + G_Knockdown( hit, parent, pushDir, 300, qtrue ); +#endif + G_Damage( hit, parent, parent, NULL, NULL, iDamage, DAMAGE_NO_KNOCKBACK | DAMAGE_IGNORE_TEAM, MOD_MELEE ); + //G_PlayEffect( pVeh->m_pVehicleInfo->explodeFX, parent->currentOrigin ); + }// Not Dead + }// Not Rancor & In USe + }// Trace Hit Anything? +} +*/ +static void AnimateVehicle( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_VT_IDLE; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + gentity_t * pilot = (gentity_t *)pVeh->m_pPilot; + gentity_t * parent = (gentity_t *)pVeh->m_pParentEntity; + playerState_t * pilotPS; + playerState_t * parentPS; + float fSpeedPercToMax; + +#ifdef _JK2MP + pilotPS = (pilot)?(pilot->playerState):(0); + parentPS = parent->playerState; +#else + pilotPS = (pilot)?(&pilot->client->ps):(0); + parentPS = &parent->client->ps; +#endif + + // We're dead (boarding is reused here so I don't have to make another variable :-). + if ( parent->health <= 0 ) + { + if ( pVeh->m_iBoarding != -999 ) // Animate the death just once! + { + pVeh->m_iBoarding = -999; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + // FIXME! Why do you keep repeating over and over!!?!?!? Bastard! + //Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_DEATH1, iFlags, iBlend ); + } + return; + } + + // If they're bucking, play the animation and leave... + if ( parent->client->ps.legsAnim == BOTH_VT_BUCK ) + { + // Done with animation? Erase the flag. + if ( parent->client->ps.legsAnimTimer <= 0 ) + { + pVeh->m_ulFlags &= ~VEH_BUCKING; + } + else + { + return; + } + } + else if ( pVeh->m_ulFlags & VEH_BUCKING ) + { + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + Anim = BOTH_VT_BUCK; + iBlend = 500; + Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_BUCK, iFlags, iBlend ); + return; + } + + // Boarding animation. + if ( pVeh->m_iBoarding != 0 ) + { + // We've just started boarding, set the amount of time it will take to finish boarding. + if ( pVeh->m_iBoarding < 0 ) + { + int iAnimLen; + + // Boarding from left... + if ( pVeh->m_iBoarding == -1 ) + { + Anim = BOTH_VT_MOUNT_L; + } + else if ( pVeh->m_iBoarding == -2 ) + { + Anim = BOTH_VT_MOUNT_R; + } + else if ( pVeh->m_iBoarding == -3 ) + { + Anim = BOTH_VT_MOUNT_B; + } + + // Set the delay time (which happens to be the time it takes for the animation to complete). + // NOTE: Here I made it so the delay is actually 70% (0.7f) of the animation time. +#ifdef _JK2MP + iAnimLen = BG_AnimLength( parent->localAnimIndex, Anim ) * 0.7f; +#else + iAnimLen = PM_AnimLength( parent->client->clientInfo.animFileIndex, Anim ) * 0.7f; +#endif + pVeh->m_iBoarding = level.time + iAnimLen; + + // Set the animation, which won't be interrupted until it's completed. + // TODO: But what if he's killed? Should the animation remain persistant??? + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); + if (pilot) + { + Vehicle_SetAnim(pilot, SETANIM_BOTH, Anim, iFlags, iBlend ); + } + return; + } + // Otherwise we're done. + else if ( pVeh->m_iBoarding <= level.time ) + { + pVeh->m_iBoarding = 0; + } + } + + // Percentage of maximum speed relative to current speed. + //float fSpeed = VectorLength( client->ps.velocity ); + fSpeedPercToMax = parent->client->ps.speed / pVeh->m_pVehicleInfo->speedMax; + + + // Going in reverse... + if ( fSpeedPercToMax < -0.01f ) + { + Anim = BOTH_VT_WALK_REV; + iBlend = 600; + } + else + { + bool Turbo = (fSpeedPercToMax>0.0f && level.timem_iTurboTime); + bool Walking = (fSpeedPercToMax>0.0f && ((pVeh->m_ucmd.buttons&BUTTON_WALKING) || fSpeedPercToMax<=0.275f)); + bool Running = (fSpeedPercToMax>0.275f); + + + // Remove Crashing Flag + //---------------------- + pVeh->m_ulFlags &= ~VEH_CRASHING; + + if (Turbo) + {// Kicked In Turbo + iBlend = 50; + iFlags = SETANIM_FLAG_OVERRIDE; + Anim = BOTH_VT_TURBO; + } + else + {// No Special Moves + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + Anim = (Walking)?(BOTH_VT_WALK_FWD ):((Running)?(BOTH_VT_RUN_FWD ):(BOTH_VT_IDLE1)); + } + } + Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); +} + +//rwwFIXMEFIXME: This is all going to have to be predicted I think, or it will feel awful +//and lagged +// This function makes sure that the rider's in this vehicle are properly animated. +static void AnimateRiders( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_VT_IDLE; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 500; + gentity_t *pilot = (gentity_t *)pVeh->m_pPilot; + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + playerState_t *pilotPS; + playerState_t *parentPS; + float fSpeedPercToMax; + +#ifdef _JK2MP + pilotPS = pVeh->m_pPilot->playerState; + parentPS = pVeh->m_pPilot->playerState; +#else + pilotPS = &pVeh->m_pPilot->client->ps; + parentPS = &pVeh->m_pParentEntity->client->ps; +#endif + + // Boarding animation. + if ( pVeh->m_iBoarding != 0 ) + { + return; + } + + // Percentage of maximum speed relative to current speed. + fSpeedPercToMax = parent->client->ps.speed / pVeh->m_pVehicleInfo->speedMax; + +/* // Going in reverse... +#ifdef _JK2MP //handled in pmove in mp + if (0) +#else + if ( fSpeedPercToMax < -0.01f ) +#endif + { + Anim = BOTH_VT_WALK_REV; + iBlend = 600; + bool HasWeapon = ((pilotPS->weapon != WP_NONE) && (pilotPS->weapon != WP_MELEE)); + if (HasWeapon) + { + if (pVeh->m_pPilot->s.numberm_pPilot->client->ps.weapon = WP_NONE; + G_RemoveWeaponModels(pVeh->m_pPilot); + } + } + else +*/ + { + bool HasWeapon = ((pilotPS->weapon != WP_NONE) && (pilotPS->weapon != WP_MELEE)); + bool Attacking = (HasWeapon && !!(pVeh->m_ucmd.buttons&BUTTON_ATTACK)); + bool Right = (pVeh->m_ucmd.rightmove>0); + bool Left = (pVeh->m_ucmd.rightmove<0); + bool Turbo = (fSpeedPercToMax>0.0f && level.timem_iTurboTime); + bool Walking = (fSpeedPercToMax>0.0f && ((pVeh->m_ucmd.buttons&BUTTON_WALKING) || fSpeedPercToMax<=0.275f)); + bool Running = (fSpeedPercToMax>0.275f); + EWeaponPose WeaponPose = WPOSE_NONE; + + + // Remove Crashing Flag + //---------------------- + pVeh->m_ulFlags &= ~VEH_CRASHING; + + + // Put Away Saber When It Is Not Active + //-------------------------------------- +#ifndef _JK2MP + if (HasWeapon && + (pVeh->m_pPilot->s.number>=MAX_CLIENTS || (cg.weaponSelectTime+500)weapon==WP_SABER && (Turbo || !pilotPS->SaberActive()))) + { + if (pVeh->m_pPilot->s.numberm_pPilot->client->ps.weapon = WP_NONE; + G_RemoveWeaponModels(pVeh->m_pPilot); + } +#endif + + // Don't Interrupt Attack Anims + //------------------------------ +#ifdef _JK2MP + if (pilotPS->weaponTime>0) + { + return; + } +#else + if (pilotPS->torsoAnim>=BOTH_VT_ATL_S && pilotPS->torsoAnim<=BOTH_VT_ATF_G) + { + float bodyCurrent = 0.0f; + int bodyEnd = 0; + if (!!gi.G2API_GetBoneAnimIndex(&pVeh->m_pPilot->ghoul2[pVeh->m_pPilot->playerModel], pVeh->m_pPilot->rootBone, level.time, &bodyCurrent, NULL, &bodyEnd, NULL, NULL, NULL)) + { + if (bodyCurrent<=((float)(bodyEnd)-1.5f)) + { + return; + } + } + } +#endif + + // Compute The Weapon Pose + //-------------------------- + if (pilotPS->weapon==WP_BLASTER) + { + WeaponPose = WPOSE_BLASTER; + } + else if (pilotPS->weapon==WP_SABER) + { + if ( (pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VT_ATL_TO_R_S) + { + pVeh->m_ulFlags &= ~VEH_SABERINLEFTHAND; + } + if (!(pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VT_ATR_TO_L_S) + { + pVeh->m_ulFlags |= VEH_SABERINLEFTHAND; + } + WeaponPose = (pVeh->m_ulFlags&VEH_SABERINLEFTHAND)?(WPOSE_SABERLEFT):(WPOSE_SABERRIGHT); + } + + + if (Attacking && WeaponPose) + {// Attack! + iBlend = 100; + iFlags = SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART; + + if (Turbo) + { + Right = true; + Left = false; + } + + // Auto Aiming + //=============================================== + if (!Left && !Right) // Allow player strafe keys to override + { +#ifndef _JK2MP + if (pVeh->m_pPilot->enemy) + { + vec3_t toEnemy; + float toEnemyDistance; + vec3_t actorRight; + float actorRightDot; + + VectorSubtract(pVeh->m_pPilot->currentOrigin, pVeh->m_pPilot->enemy->currentOrigin, toEnemy); + toEnemyDistance = VectorNormalize(toEnemy); + + AngleVectors(pVeh->m_pParentEntity->currentAngles, 0, actorRight, 0); + actorRightDot = DotProduct(toEnemy, actorRight); + + if (fabsf(actorRightDot)>0.5f || pilotPS->weapon==WP_SABER) + { + Left = (actorRightDot>0.0f); + Right = !Left; + } + else + { + Right = Left = false; + } + } + else +#endif + if (pilotPS->weapon==WP_SABER && !Left && !Right) + { + Left = (WeaponPose==WPOSE_SABERLEFT); + Right = !Left; + } + } + + + if (Left) + {// Attack Left + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_ATL_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VT_ATL_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VT_ATR_TO_L_S; break; + default: assert(0); + } + } + else if (Right) + {// Attack Right + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_ATR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VT_ATL_TO_R_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VT_ATR_S; break; + default: assert(0); + } + } + else + {// Attack Ahead + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_ATF_G; break; + default: assert(0); + } + } + } + else if (Turbo) + {// Kicked In Turbo + iBlend = 50; + iFlags = SETANIM_FLAG_OVERRIDE; + Anim = BOTH_VT_TURBO; + } + else + {// No Special Moves + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + + if (WeaponPose==WPOSE_NONE) + { + if (Walking) + { + Anim = BOTH_VT_WALK_FWD; + } + else if (Running) + { + Anim = BOTH_VT_RUN_FWD; + } + else + { + Anim = BOTH_VT_IDLE1;//(Q_irand(0,1)==0)?(BOTH_VT_IDLE):(BOTH_VT_IDLE1); + } + } + else + { + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_IDLE_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VT_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VT_IDLE_SR; break; + default: assert(0); + } + } + }// No Special Moves + } + + Vehicle_SetAnim( pilot, SETANIM_BOTH, Anim, iFlags, iBlend ); +} +#endif //QAGAME + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +//on the client this function will only set up the process command funcs +void G_SetAnimalVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + pVehInfo->AnimateVehicle = AnimateVehicle; + pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; +// pVehInfo->Board = Board; +// pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; + pVehInfo->DeathUpdate = DeathUpdate; +// pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; + pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif //QAGAME + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +//this is a BG function too in MP so don't un-bg-compatibilify it -rww +void G_CreateAnimalNPC( Vehicle_t **pVeh, const char *strAnimalType ) +{ + // Allocate the Vehicle. +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#else + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#endif +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/code/game/AnimalNPC.cpp b/code/game/AnimalNPC.cpp new file mode 100644 index 0000000..7d5e30b --- /dev/null +++ b/code/game/AnimalNPC.cpp @@ -0,0 +1,680 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +#include "g_local.h" +#include "g_functions.h" +#include "g_vehicles.h" + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; + + +// Update death sequence. +void CAnimalNPC::DeathUpdate() +{ + if ( level.time >= m_iDieTime ) + { + // If the vehicle is not empty. + if ( !(m_pParentEntity->client->ps.eFlags & EF_EMPTY_VEHICLE) ) + { + EjectAll(); + } + else + { + // Waste this sucker. + } + + // Die now... +/* else + { + vec3_t mins, maxs, bottom; + trace_t trace; + + if ( m_pVehicleInfo->explodeFX ) + { + G_PlayEffect( m_pVehicleInfo->explodeFX, m_pParentEntity->currentOrigin ); + //trace down and place mark + VectorCopy( m_pParentEntity->currentOrigin, bottom ); + bottom[2] -= 80; + gi.trace( &trace, m_pParentEntity->currentOrigin, vec3_origin, vec3_origin, bottom, m_pParentEntity->s.number, CONTENTS_SOLID ); + if ( trace.fraction < 1.0f ) + { + VectorCopy( trace.endpos, bottom ); + bottom[2] += 2; + G_PlayEffect( "ships/ship_explosion_mark", trace.endpos ); + } + } + + m_pParentEntity->takedamage = qfalse;//so we don't recursively damage ourselves + if ( m_pVehicleInfo->explosionRadius > 0 && m_pVehicleInfo->explosionDamage > 0 ) + { + VectorCopy( m_pParentEntity->mins, mins ); + mins[2] = -4;//to keep it off the ground a *little* + VectorCopy( m_pParentEntity->maxs, maxs ); + VectorCopy( m_pParentEntity->currentOrigin, bottom ); + bottom[2] += m_pParentEntity->mins[2] - 32; + gi.trace( &trace, m_pParentEntity->currentOrigin, mins, maxs, bottom, m_pParentEntity->s.number, CONTENTS_SOLID ); + G_RadiusDamage( trace.endpos, NULL, m_pVehicleInfo->explosionDamage, m_pVehicleInfo->explosionRadius, NULL, MOD_EXPLOSIVE );//FIXME: extern damage and radius or base on fuel + } + + m_pParentEntity->e_ThinkFunc = thinkF_G_FreeEntity; + m_pParentEntity->nextthink = level.time + FRAMETIME; + }*/ + } +} + +// Like a think or move command, this updates various vehicle properties. +bool CAnimalNPC::Update( const usercmd_t *pUmcd ) +{ + // Bucking so we can't do anything. + if ( m_ulFlags & VEH_BUCKING || m_ulFlags & VEH_FLYING || m_ulFlags & VEH_CRASHING ) + { + m_pParentEntity->client->ps.speed = 0; + return false; + } + + return CVehicleNPC::Update( pUmcd ); +} + +// ProcessMoveCommands the Vehicle. +void CAnimalNPC::ProcessMoveCommands() +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + + speedIdleDec = m_pVehicleInfo->decelIdle * m_fTimeModifier; + speedMax = m_pVehicleInfo->speedMax; + + speedIdle = m_pVehicleInfo->speedIdle; + speedIdleAccel = m_pVehicleInfo->accelIdle * m_fTimeModifier; + speedMin = m_pVehicleInfo->speedMin; + + if ( m_pParentEntity->client->ps.eFlags & EF_EMPTY_VEHICLE ) + {//drifts to a stop + speedInc = speedIdle * m_fTimeModifier; + VectorClear( client->ps.moveDir ); + //m_ucmd.forwardmove = 127; + m_pParentEntity->client->ps.speed = 0; + } + else + { + speedInc = m_pVehicleInfo->acceleration * m_fTimeModifier; + } + + if ( m_pParentEntity->client->ps.speed || client->ps.groundEntityNum == ENTITYNUM_NONE || + m_ucmd.forwardmove || m_ucmd.upmove > 0 ) + { + if ( m_ucmd.forwardmove > 0 && speedInc ) + { + m_pParentEntity->client->ps.speed += speedInc; + } + else if ( m_ucmd.forwardmove < 0 ) + { + if ( m_pParentEntity->client->ps.speed > speedIdle ) + { + m_pParentEntity->client->ps.speed -= speedInc; + } + else if ( client->ps.speed > speedMin ) + { + m_pParentEntity->client->ps.speed -= speedIdleDec; + } + } + // No input, so coast to stop. + else if ( m_pParentEntity->client->ps.speed > 0.0f ) + { + m_pParentEntity->client->ps.speed -= speedIdleDec; + if ( m_pParentEntity->client->ps.speed < 0.0f ) + { + m_pParentEntity->client->ps.speed = 0.0f; + } + } + else if ( m_pParentEntity->client->ps.speed < 0.0f ) + { + m_pParentEntity->client->ps.speed += speedIdleDec; + if ( m_pParentEntity->client->ps.speed > 0.0f ) + { + m_pParentEntity->client->ps.speed = 0.0f; + } + } + } + else + { + if ( m_ucmd.forwardmove < 0 ) + { + m_ucmd.forwardmove = 0; + } + if ( m_ucmd.upmove < 0 ) + { + m_ucmd.upmove = 0; + } + + m_ucmd.rightmove = 0; + + /*if ( !m_pVehicleInfo->strafePerc + || (!g_speederControlScheme->value && !m_pParentEntity->s.number) ) + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + m_ucmd.rightmove = 0; + }*/ + } + + float fWalkSpeedMax = speedMax * 0.275f; + if ( m_ucmd.buttons & BUTTON_WALKING && m_pParentEntity->client->ps.speed > fWalkSpeedMax ) + { + m_pParentEntity->client->ps.speed = fWalkSpeedMax; + } + else if ( m_pParentEntity->client->ps.speed > speedMax ) + { + m_pParentEntity->client->ps.speed = speedMax; + } + else if ( m_pParentEntity->client->ps.speed < speedMin ) + { + m_pParentEntity->client->ps.speed = speedMin; + } + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +// ProcessOrientCommands the Vehicle. +void CAnimalNPC::ProcessOrientCommands() +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + + gentity_t *rider = m_pParentEntity->owner; + if ( !rider || !rider->client ) + { + rider = m_pParentEntity; + } + + float speed; + speed = VectorLength( m_pParentEntity->client->ps.velocity ); + + // If the player is the rider... + if ( !rider->s.number ) + {//FIXME: use the vehicle's turning stat in this calc + m_vOrientation[YAW] = rider->client->ps.viewangles[YAW]; + } + else + { + float turnSpeed = m_pVehicleInfo->turningSpeed; + if ( !m_pVehicleInfo->turnWhenStopped + && !m_pParentEntity->client->ps.speed )//FIXME: or !m_ucmd.forwardmove? + {//can't turn when not moving + //FIXME: or ramp up to max turnSpeed? + turnSpeed = 0.0f; + } + if ( !rider || rider->NPC ) + {//help NPCs out some + turnSpeed *= 2.0f; + if ( m_pParentEntity->client->ps.speed > 200.0f ) + { + turnSpeed += turnSpeed * m_pParentEntity->client->ps.speed/200.0f*0.05f; + } + } + turnSpeed *= m_fTimeModifier; + + //default control scheme: strafing turns, mouselook aims + if ( m_ucmd.rightmove < 0 ) + { + m_vOrientation[YAW] += turnSpeed; + } + else if ( m_ucmd.rightmove > 0 ) + { + m_vOrientation[YAW] -= turnSpeed; + } + + if ( m_iArmor <= 25 ) + {//damaged badly + } + } + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); + +// This function makes sure that the vehicle is properly animated. +void CAnimalNPC::AnimateVehicle() +{ + animNumber_t Anim = BOTH_VT_IDLE; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + + // We're dead (boarding is reused here so I don't have to make another variable :-). + if ( m_pParentEntity->health <= 0 ) + { + if ( m_iBoarding != -999 ) // Animate the death just once! + { + m_iBoarding = -999; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + // FIXME! Why do you keep repeating over and over!!?!?!? Bastard! + NPC_SetAnim( m_pParentEntity, SETANIM_LEGS, BOTH_VT_DEATH1, iFlags, iBlend ); + } + return; + } + + // If they're bucking, play the animation and leave... + if ( m_pParentEntity->client->ps.legsAnim == BOTH_VT_BUCK ) + { + // Done with animation? Erase the flag. + if ( m_pParentEntity->client->ps.legsAnimTimer <= 0 ) + { + m_ulFlags &= ~VEH_BUCKING; + } + else + { + return; + } + } + else if ( m_ulFlags & VEH_BUCKING ) + { + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + Anim = BOTH_VT_BUCK; + iBlend = 500; + NPC_SetAnim( m_pParentEntity, SETANIM_LEGS, BOTH_VT_BUCK, iFlags, iBlend ); + return; + } + + // Boarding animation. + if ( m_iBoarding != 0 ) + { + // We've just started boarding, set the amount of time it will take to finish boarding. + if ( m_iBoarding < 0 ) + { + // Boarding from left... + if ( m_iBoarding == -1 ) + { + Anim = BOTH_VT_MOUNT_L; + } + else if ( m_iBoarding == -2 ) + { + Anim = BOTH_VT_MOUNT_R; + } + else if ( m_iBoarding == -3 ) + { + Anim = BOTH_VT_MOUNT_B; + } + + // Set the delay time (which happens to be the time it takes for the animation to complete). + // NOTE: Here I made it so the delay is actually 70% (0.7f) of the animation time. + int iAnimLen = PM_AnimLength( m_pParentEntity->client->clientInfo.animFileIndex, Anim ) * 0.7f; + m_iBoarding = level.time + iAnimLen; + + // Set the animation, which won't be interrupted until it's completed. + // TODO: But what if he's killed? Should the animation remain persistant??? + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + NPC_SetAnim( m_pParentEntity, SETANIM_LEGS, Anim, iFlags, iBlend ); + return; + } + // Otherwise we're done. + else if ( m_iBoarding <= level.time ) + { + m_iBoarding = 0; + } + } + + // Percentage of maximum speed relative to current speed. + //float fSpeed = VectorLength( client->ps.velocity ); + float fSpeedPercToMax = m_pParentEntity->client->ps.speed / m_pVehicleInfo->speedMax; + + // If we're moving... + if ( fSpeedPercToMax > 0.0f ) //fSpeedPercToMax >= 0.85f ) + { + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE; + float fYawDelta = m_vPrevOrientation[YAW] - m_vOrientation[YAW]; + + // NOTE: Mikes suggestion for fixing the stuttering walk (left/right) is to maintain the + // current frame between animations. I have no clue how to do this and have to work on other + // stuff so good luck to him :-p AReis + + // If we're walking (or our speed is less than .275%)... + if ( ( m_ucmd.buttons & BUTTON_WALKING ) || fSpeedPercToMax < 0.275f ) + { + // Make them lean if we're turning. + /*if ( fYawDelta < -0.0001f ) + { + Anim = BOTH_VT_WALK_FWD_L; + } + else if ( fYawDelta > 0.0001 ) + { + Anim = BOTH_VT_WALK_FWD_R; + } + else*/ + { + Anim = BOTH_VT_WALK_FWD; + } + } + // otherwise we're running. + else + { + // Make them lean if we're turning. + /*if ( fYawDelta < -0.0001f ) + { + Anim = BOTH_VT_RUN_FWD_L; + } + else if ( fYawDelta > 0.0001 ) + { + Anim = BOTH_VT_RUN_FWD_R; + } + else*/ + { + Anim = BOTH_VT_RUN_FWD; + } + } + } + else + { + // Going in reverse... + if ( fSpeedPercToMax < -0.018f ) + { + iFlags = SETANIM_FLAG_OVERRIDE; + Anim = BOTH_VT_WALK_REV; + iBlend = 500; + } + else + { + int iChance = Q_irand( 0, 20000 ); + + // Every once in a while buck or do a different idle... + iFlags = SETANIM_FLAG_NORMAL | SETANIM_FLAG_RESTART | SETANIM_FLAG_HOLD; + iBlend = 600; + Anim = BOTH_VT_IDLE; + if ( iChance <= 15000 ) + { + Anim = BOTH_VT_IDLE; + } + else if ( iChance > 15000 && iChance <= 19990 ) + { + Anim = BOTH_VT_IDLE1; + } + else if ( iChance > 19990 && iChance <= 20000 ) + { + //m_ulFlags |= VEH_BUCKING; + } + } + } + + // Crashing. + if ( m_ulFlags & VEH_CRASHING ) + { + m_ulFlags &= ~VEH_CRASHING; // Remove the flag, we are doing the animation. + iBlend = 0; + Anim = BOTH_VT_LAND; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + } + + // In the Air. + if ( m_ulFlags & VEH_FLYING ) + { + m_ulFlags &= ~VEH_FLYING; + iBlend = 10; + Anim = BOTH_VT_AIR; + iFlags = SETANIM_FLAG_OVERRIDE; + } + + NPC_SetAnim( m_pParentEntity, SETANIM_LEGS, Anim, iFlags, iBlend ); +} + +// This function makes sure that the rider's in this vehicle are properly animated. +void CAnimalNPC::AnimateRiders() +{ + animNumber_t Anim = BOTH_VT_IDLE; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 500; + + // Boarding animation. + if ( m_iBoarding != 0 ) + { + // We've just started moarding, set the amount of time it will take to finish moarding. + if ( m_iBoarding < 0 ) + { + iBlend = 0; + // Boarding from left... + if ( m_iBoarding == -1 ) + { + Anim = BOTH_VT_MOUNT_L; + } + else if ( m_iBoarding == -2 ) + { + Anim = BOTH_VT_MOUNT_R; + } + else if ( m_iBoarding == -3 ) + { + Anim = BOTH_VT_MOUNT_B; + } + + // Set the animation, which won't be interrupted until it's completed. + // TODO: But what if he's killed? Should the animation remain persistant??? + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + NPC_SetAnim( m_pPilot, SETANIM_BOTH, Anim, iFlags, iBlend ); + return; + } + // Otherwise we're done. + else if ( m_iBoarding <= level.time ) + { + m_iBoarding = 0; + } + } + + // Percentage of maximum speed relative to current speed. + float fSpeedPercToMax = m_pParentEntity->client->ps.speed / m_pVehicleInfo->speedMax; + + // Going in reverse... + if ( fSpeedPercToMax < -0.01f ) + { + Anim = BOTH_VT_WALK_REV; + iBlend = 600; + } + else + { + // If they have a weapon... + if ( m_pPilot->client->ps.weapon != WP_NONE && m_pPilot->client->ps.weapon != WP_MELEE ) + { + // If they're firing, play the right fire animation. + if ( m_ucmd.buttons & ( BUTTON_ATTACK | BUTTON_ALT_ATTACK ) ) + { + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + iBlend = 0; + switch ( m_pPilot->client->ps.weapon ) + { + case WP_SABER: + // If we're already in an attack animation, leave (let it continue). + if ( m_pPilot->client->ps.torsoAnimTimer > 0 && (m_pPilot->client->ps.torsoAnim == BOTH_VT_ATR_S || + m_pPilot->client->ps.torsoAnim == BOTH_VT_ATL_S) ) + { + //FIXME: no need to even call the PM_SetAnim at all in this case + Anim = (animNumber_t)m_pPilot->client->ps.torsoAnim; + iFlags = SETANIM_FLAG_NORMAL; + break; + } + + // Start the attack. + if ( m_ucmd.rightmove > 0 ) //right side attack + { + Anim = BOTH_VT_ATR_S; + } + else if ( m_ucmd.rightmove < 0 ) //left-side attack + { + Anim = BOTH_VT_ATL_S; + } + else //random + { + //FIXME: alternate back and forth or auto-aim? + if ( !Q_irand( 0, 1 ) ) + { + Anim = BOTH_VT_ATR_S; + } + else + { + Anim = BOTH_VT_ATL_S; + } + } + break; + + case WP_BLASTER: + // Override the shoot anim. + if ( m_pPilot->client->ps.torsoAnim == BOTH_ATTACK3 ) + { + if ( m_ucmd.rightmove > 0 ) //right side attack + { + Anim = BOTH_VT_ATR_G; + } + else if ( m_ucmd.rightmove < 0 ) //left side + { + Anim = BOTH_VT_ATL_G; + } + else //frontal + { + Anim = BOTH_VT_ATF_G; + } + } + else if ( m_pPilot->client->ps.torsoAnim == BOTH_VT_ATR_G || + m_pPilot->client->ps.torsoAnim == BOTH_VT_ATL_G || + m_pPilot->client->ps.torsoAnim == BOTH_VT_ATF_G ) + { + Anim = (animNumber_t)m_pPilot->client->ps.torsoAnim; + iFlags = SETANIM_FLAG_RESTART; + } + else + { + Anim = (animNumber_t)m_pPilot->client->ps.torsoAnim; + iFlags = 0; + } + break; + + case WP_THERMAL: + // Override throw animation. + if ( m_pPilot->client->ps.torsoAnim == BOTH_THERMAL_THROW + || m_pPilot->client->ps.torsoAnim == BOTH_ATTACK10 ) + { + if ( m_ucmd.rightmove > 0 ) //right side attack + { + Anim = BOTH_VT_ATR_T; + } + else if ( m_ucmd.rightmove < 0 ) //left side + { + Anim = BOTH_VT_ATL_T; + } + else //frontal + { + Anim = BOTH_VT_ATF_T; + } + break; + } + else + { + Anim = (animNumber_t)m_pPilot->client->ps.torsoAnim; + iFlags = 0; + } + } + } + // They're not firing so play the Idle for the weapon. + else + { + iFlags = SETANIM_FLAG_NORMAL; + iBlend = 1500; + + switch ( m_pPilot->client->ps.weapon ) + { + case WP_SABER: + Anim = BOTH_VT_IDLE_S; + break; + + case WP_BLASTER: + Anim = BOTH_VT_IDLE_G; + break; + + case WP_THERMAL: + Anim = BOTH_VT_IDLE_T; + break; + } + } + } + // Do normal Idle's and other things (crash, air move, etc...) + else + { + // In the Air. + if ( m_ulFlags & VEH_FLYING ) + { + iBlend = 1500; + Anim = BOTH_VT_AIR; + iFlags = SETANIM_FLAG_OVERRIDE; + } + else + { + // If we're moving... + if ( fSpeedPercToMax > 0.0f ) //fSpeedPercToMax >= 0.85f ) + { + // If we're walking (or our speed is less than 0.275f%)... + if ( ( m_ucmd.buttons & BUTTON_WALKING ) || fSpeedPercToMax < 0.275f ) + { + iBlend = 800; + // Make them lean if we're turning. + if ( m_ucmd.rightmove < 0 ) + { + Anim = BOTH_VT_WALK_FWD_L; + } + else if ( m_ucmd.rightmove > 0 ) + { + Anim = BOTH_VT_WALK_FWD_R; + } + else + { + Anim = BOTH_VT_WALK_FWD; + } + } + // otherwise we're running. + else + { + iBlend = 1000; + // Make them lean if we're turning. + if ( m_ucmd.rightmove < 0 ) + { + Anim = BOTH_VT_RUN_FWD_L; + } + else if ( m_ucmd.rightmove > 0 ) + { + Anim = BOTH_VT_RUN_FWD_R; + } + else + { + Anim = BOTH_VT_RUN_FWD; + } + } + } + else + { + int iChance = Q_irand( 0, 20000 ); + + // Every once in a while buck or do a different idle... + iFlags = SETANIM_FLAG_NORMAL | SETANIM_FLAG_RESTART; + iBlend = 600; + Anim = BOTH_VT_IDLE; + if ( iChance > 19990 && iChance <= 20000 ) + { + m_ulFlags |= VEH_BUCKING; + Anim = BOTH_VT_BUCK; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + } + } + } + } + } + + NPC_SetAnim( m_pPilot, SETANIM_BOTH, Anim, iFlags, iBlend ); +} \ No newline at end of file diff --git a/code/game/Copy of game.vcproj b/code/game/Copy of game.vcproj new file mode 100644 index 0000000..525df7b --- /dev/null +++ b/code/game/Copy of game.vcproj @@ -0,0 +1,2916 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/game/FighterNPC.c b/code/game/FighterNPC.c new file mode 100644 index 0000000..fe0c0c6 --- /dev/null +++ b/code/game/FighterNPC.c @@ -0,0 +1,1751 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +#endif + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +#ifdef QAGAME //SP or gameside MP +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern void G_VehicleTrace( trace_t *results, const vec3_t start, const vec3_t tMins, const vec3_t tMaxs, const vec3_t end, int passEntityNum, int contentmask ); +#endif + +extern qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ); + +#ifdef _JK2MP + +#include "../namespace_begin.h" + +extern void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int BG_GetTime(void); +#endif + +extern void BG_ExternThisSoICanRecompileInDebug( Vehicle_t *pVeh, playerState_t *riderPS ); + +//this stuff has got to be predicted, so.. +bool BG_FighterUpdate(Vehicle_t *pVeh, const usercmd_t *pUcmd, vec3_t trMins, vec3_t trMaxs, float gravity, + void (*traceFunc)( trace_t *results, const vec3_t start, const vec3_t lmins, const vec3_t lmaxs, const vec3_t end, int passEntityNum, int contentMask )) +{ + vec3_t bottom; + playerState_t *parentPS; + qboolean isDead = qfalse; +#ifdef QAGAME //don't do this on client + // Make sure the riders are not visible or collidable. + pVeh->m_pVehicleInfo->Ghost( pVeh, pVeh->m_pPilot ); +#endif + + +#ifdef _JK2MP + parentPS = pVeh->m_pParentEntity->playerState; +#else + parentPS = &pVeh->m_pParentEntity->client->ps; +#endif + + if (!parentPS) + { + Com_Error(ERR_DROP, "NULL PS in BG_FighterUpdate (%s)", pVeh->m_pVehicleInfo->name); + return false; + } + + // If we have a pilot, take out gravity (it's a flying craft...). + if ( pVeh->m_pPilot ) + { + parentPS->gravity = 0; +#ifndef _JK2MP //don't need this flag in mp, I.. guess + pVeh->m_pParentEntity->svFlags |= SVF_CUSTOM_GRAVITY; +#endif + } + else + { +#ifndef _JK2MP //don't need this flag in mp, I.. guess + pVeh->m_pParentEntity->svFlags &= ~SVF_CUSTOM_GRAVITY; +#else //in MP set grav back to normal gravity + if (pVeh->m_pVehicleInfo->gravity) + { + parentPS->gravity = pVeh->m_pVehicleInfo->gravity; + } + else + { //it doesn't have gravity specified apparently + parentPS->gravity = gravity; + } +#endif + } + +#ifdef _JK2MP + isDead = (qboolean)((parentPS->eFlags&EF_DEAD)!=0); +#else + isDead = (parentPS->stats[STAT_HEALTH] <= 0 ); +#endif + + /* + if ( isDead || + (pVeh->m_pVehicleInfo->surfDestruction && + pVeh->m_iRemovedSurfaces ) ) + {//can't land if dead or spiralling out of control + pVeh->m_LandTrace.fraction = 1.0f; + pVeh->m_LandTrace.contents = pVeh->m_LandTrace.surfaceFlags = 0; + VectorClear( pVeh->m_LandTrace.plane.normal ); + pVeh->m_LandTrace.allsolid = qfalse; + pVeh->m_LandTrace.startsolid = qfalse; + } + else + { + */ + //argh, no, I need to have a way to see when they impact the ground while damaged. -rww + + // Check to see if the fighter has taken off yet (if it's a certain height above ground). + VectorCopy( parentPS->origin, bottom ); + bottom[2] -= pVeh->m_pVehicleInfo->landingHeight; + + traceFunc( &pVeh->m_LandTrace, parentPS->origin, trMins, trMaxs, bottom, pVeh->m_pParentEntity->s.number, (MASK_NPCSOLID&~CONTENTS_BODY) ); + //} + + return true; +} + +#ifdef QAGAME //ONLY in SP or on server, not cgame + +// Like a think or move command, this updates various vehicle properties. +static bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + assert(pVeh->m_pParentEntity); + if (!BG_FighterUpdate(pVeh, pUcmd, ((gentity_t *)pVeh->m_pParentEntity)->mins, + ((gentity_t *)pVeh->m_pParentEntity)->maxs, +#ifdef _JK2MP + g_gravity.value, +#else + g_gravity->value, +#endif + G_VehicleTrace)) + { + return false; + } + + if ( !g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ) ) + { + return false; + } + + return true; +} + +// Board this Vehicle (get on). The first entity to board an empty vehicle becomes the Pilot. +static bool Board( Vehicle_t *pVeh, bgEntity_t *pEnt ) +{ + if ( !g_vehicleInfo[VEHICLE_BASE].Board( pVeh, pEnt ) ) + return false; + + // Set the board wait time (they won't be able to do anything, including getting off, for this amount of time). + pVeh->m_iBoarding = level.time + 1500; + + return true; +} + +// Eject an entity from the vehicle. +static bool Eject( Vehicle_t *pVeh, bgEntity_t *pEnt, qboolean forceEject ) +{ + if ( g_vehicleInfo[VEHICLE_BASE].Eject( pVeh, pEnt, forceEject ) ) + { + return true; + } + + return false; +} + +#endif //end game-side only + +//method of decrementing the given angle based on the given taking variable frame times into account +static float PredictedAngularDecrement(float scale, float timeMod, float originalAngle) +{ + float fixedBaseDec = originalAngle*0.05f; + float r = 0.0f; + + if (fixedBaseDec < 0.0f) + { + fixedBaseDec = -fixedBaseDec; + } + + fixedBaseDec *= (1.0f+(1.0f-scale)); + + if (fixedBaseDec < 0.1f) + { //don't increment in incredibly small fractions, it would eat up unnecessary bandwidth. + fixedBaseDec = 0.1f; + } + + fixedBaseDec *= (timeMod*0.1f); + if (originalAngle > 0.0f) + { //subtract + r = (originalAngle-fixedBaseDec); + if (r < 0.0f) + { + r = 0.0f; + } + } + else if (originalAngle < 0.0f) + { //add + r = (originalAngle+fixedBaseDec); + if (r > 0.0f) + { + r = 0.0f; + } + } + + return r; +} + +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver +qboolean FighterIsInSpace( gentity_t *gParent ) +{ + if ( gParent + && gParent->client + && gParent->client->inSpaceIndex + && gParent->client->inSpaceIndex < ENTITYNUM_WORLD ) + { + return qtrue; + } + return qfalse; +} +#endif + +qboolean FighterOverValidLandingSurface( Vehicle_t *pVeh ) +{ + if ( pVeh->m_LandTrace.fraction < 1.0f //ground present + && pVeh->m_LandTrace.plane.normal[2] >= MIN_LANDING_SLOPE )//flat enough + //FIXME: also check for a certain surface flag ... "landing zones"? + { + return qtrue; + } + return qfalse; +} + +qboolean FighterIsLanded( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + if ( FighterOverValidLandingSurface( pVeh ) + && !parentPS->speed )//stopped + { + return qtrue; + } + return qfalse; +} + +qboolean FighterIsLanding( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + + if ( FighterOverValidLandingSurface( pVeh ) +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + && pVeh->m_pVehicleInfo->Inhabited( pVeh )//has to have a driver in order to be capable of landing +#endif + && (pVeh->m_ucmd.forwardmove < 0||pVeh->m_ucmd.upmove<0) //decelerating or holding crouch button + && parentPS->speed <= MIN_LANDING_SPEED )//going slow enough to start landing - was using pVeh->m_pVehicleInfo->speedIdle, but that's still too fast + { + return qtrue; + } + return qfalse; +} + +qboolean FighterIsLaunching( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + + if ( FighterOverValidLandingSurface( pVeh ) +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + && pVeh->m_pVehicleInfo->Inhabited( pVeh )//has to have a driver in order to be capable of landing +#endif + && pVeh->m_ucmd.upmove > 0 //trying to take off + && parentPS->speed <= 200.0f )//going slow enough to start landing - was using pVeh->m_pVehicleInfo->speedIdle, but that's still too fast + { + return qtrue; + } + return qfalse; +} + +qboolean FighterSuspended( Vehicle_t *pVeh, playerState_t *parentPS ) +{ +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + if (!pVeh->m_pPilot//empty + && !parentPS->speed//not moving + && pVeh->m_ucmd.forwardmove <= 0//not trying to go forward for whatever reason + && pVeh->m_pParentEntity != NULL + && (((gentity_t *)pVeh->m_pParentEntity)->spawnflags&2) )//SUSPENDED spawnflag is on + { + return qtrue; + } + return qfalse; +#elif CGAME + return qfalse; +#endif +} + +#ifdef CGAME +extern void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); //cg_syscalls.c +extern sfxHandle_t trap_S_RegisterSound( const char *sample); //cg_syscalls.c +#endif + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +#define FIGHTER_MIN_TAKEOFF_FRACTION 0.7f +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + bgEntity_t *parent = pVeh->m_pParentEntity; + qboolean isLandingOrLaunching = qfalse; +#ifndef _JK2MP//SP + int curTime = level.time; +#elif QAGAME//MP GAME + int curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + int curTime = pm->cmd.serverTime; +#endif + +#ifdef _JK2MP + playerState_t *parentPS = parent->playerState; +#else + playerState_t *parentPS = &parent->client->ps; +#endif + +#ifdef _JK2MP + if ( parentPS->hyperSpaceTime + && curTime - parentPS->hyperSpaceTime < HYPERSPACE_TIME ) + {//Going to Hyperspace + //totally override movement + float timeFrac = ((float)(curTime-parentPS->hyperSpaceTime))/HYPERSPACE_TIME; + if ( timeFrac < HYPERSPACE_TELEPORT_FRAC ) + {//for first half, instantly jump to top speed! + if ( !(parentPS->eFlags2&EF2_HYPERSPACE) ) + {//waiting to face the right direction, do nothing + parentPS->speed = 0.0f; + } + else + { + if ( parentPS->speed < HYPERSPACE_SPEED ) + {//just started hyperspace +//MIKE: This is going to play the sound twice for the predicting client, I suggest using +//a predicted event or only doing it game-side. -rich +#ifdef QAGAME//MP GAME-side + //G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_LOCAL, pVeh->m_pVehicleInfo->soundHyper ); +#elif CGAME//MP CGAME-side + trap_S_StartSound( NULL, pm->ps->clientNum, CHAN_LOCAL, pVeh->m_pVehicleInfo->soundHyper ); +#endif + } + + parentPS->speed = HYPERSPACE_SPEED; + } + } + else + {//slow from top speed to 200... + parentPS->speed = 200.0f + ((1.0f-timeFrac)*(1.0f/HYPERSPACE_TELEPORT_FRAC)*(HYPERSPACE_SPEED-200.0f)); + //don't mess with acceleration, just pop to the high velocity + if ( VectorLength( parentPS->velocity ) < parentPS->speed ) + { + VectorScale( parentPS->moveDir, parentPS->speed, parentPS->velocity ); + } + } + return; + } +#endif + + if ( pVeh->m_iDropTime >= curTime ) + {//no speed, just drop + parentPS->speed = 0.0f; + parentPS->gravity = 800; + return; + } + + isLandingOrLaunching = (FighterIsLanding( pVeh, parentPS )||FighterIsLaunching( pVeh, parentPS )); + + // If we are hitting the ground, just allow the fighter to go up and down. + if ( isLandingOrLaunching//going slow enough to start landing + && (pVeh->m_ucmd.forwardmove<=0||pVeh->m_LandTrace.fraction<=FIGHTER_MIN_TAKEOFF_FRACTION) )//not trying to accelerate away already (or: you are trying to, but not high enough off the ground yet) + {//FIXME: if start to move forward and fly over something low while still going relatively slow, you may try to land even though you don't mean to... + //float fInvFrac = 1.0f - pVeh->m_LandTrace.fraction; + + if ( pVeh->m_ucmd.upmove > 0 ) + { +#ifdef _JK2MP + if ( parentPS->velocity[2] <= 0 + && pVeh->m_pVehicleInfo->soundTakeOff ) + {//taking off for the first time +#ifdef QAGAME//MP GAME-side + G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_AUTO, pVeh->m_pVehicleInfo->soundTakeOff ); +#endif + } +#endif + parentPS->velocity[2] += pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier;// * ( /*fInvFrac **/ 1.5f ); + } + else if ( pVeh->m_ucmd.upmove < 0 ) + { + parentPS->velocity[2] -= pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier;// * ( /*fInvFrac **/ 1.8f ); + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( pVeh->m_LandTrace.fraction != 0.0f ) + { + parentPS->velocity[2] -= pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + + if ( pVeh->m_LandTrace.fraction <= FIGHTER_MIN_TAKEOFF_FRACTION ) + { + //pVeh->m_pParentEntity->client->ps.velocity[0] *= pVeh->m_LandTrace.fraction; + //pVeh->m_pParentEntity->client->ps.velocity[1] *= pVeh->m_LandTrace.fraction; + + //remember to always base this stuff on the time modifier! otherwise, you create + //framerate-dependancy issues and break prediction in MP -rww + //parentPS->velocity[2] *= pVeh->m_LandTrace.fraction; + //it's not an angle, but hey + parentPS->velocity[2] = PredictedAngularDecrement(pVeh->m_LandTrace.fraction, pVeh->m_fTimeModifier*5.0f, parentPS->velocity[2]); + + parentPS->speed = 0; + } + } + + // Make sure they don't pitch as they near the ground. + //pVeh->m_vOrientation[PITCH] *= 0.7f; + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.7f, pVeh->m_fTimeModifier*10.0f, pVeh->m_vOrientation[PITCH]); + + return; + } + + if ( (pVeh->m_ucmd.upmove > 0) && pVeh->m_pVehicleInfo->turboSpeed ) + { + if ((curTime - pVeh->m_iTurboTime)>pVeh->m_pVehicleInfo->turboRecharge) + { + pVeh->m_iTurboTime = (curTime + pVeh->m_pVehicleInfo->turboDuration); + if (pVeh->m_pVehicleInfo->iTurboStartFX) + { + int i; + for (i=0; im_iExhaustTag[i]==-1) + { + break; + } + #ifndef _JK2MP//SP + G_PlayEffect(pVeh->m_pVehicleInfo->iTurboStartFX, pVeh->m_pParentEntity->playerModel, pVeh->m_iExhaustTag[i], pVeh->m_pParentEntity->s.number, pVeh->m_pParentEntity->currentOrigin ); + #else + //TODO: MP Play Effect? + #endif + } + } + //NOTE: turbo sound can't be part of effect if effect is played on every muzzle! + if ( pVeh->m_pVehicleInfo->soundTurbo ) + { +#ifndef _JK2MP//SP + G_SoundIndexOnEnt( pVeh->m_pParentEntity, CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo ); +#elif QAGAME//MP GAME-side + G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo ); +#elif CGAME//MP CGAME-side + //trap_S_StartSound( NULL, pVeh->m_pParentEntity->s.number, CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo ); +#endif + } + } + } + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + if ( curTime < pVeh->m_iTurboTime ) + {//going turbo speed + speedMax = pVeh->m_pVehicleInfo->turboSpeed; + //double our acceleration + speedInc *= 2.0f; + //force us to move forward + pVeh->m_ucmd.forwardmove = 127; +#ifdef _JK2MP//SP can cheat and just check m_iTurboTime directly... :) + //add flag to let cgame know to draw the iTurboFX effect + parentPS->eFlags |= EF_JETPACK_ACTIVE; +#endif + } + /* + //FIXME: if turbotime is up and we're waiting for it to recharge, should our max speed drop while we recharge? + else if ( (curTime - pVeh->m_iTurboTime)<3000 ) + {//still waiting for the recharge + speedMax = pVeh->m_pVehicleInfo->speedMax*0.75; + } + */ + else + {//normal max speed + speedMax = pVeh->m_pVehicleInfo->speedMax; +#ifdef _JK2MP//SP can cheat and just check m_iTurboTime directly... :) + if ( (parentPS->eFlags&EF_JETPACK_ACTIVE) ) + {//stop cgame from playing the turbo exhaust effect + parentPS->eFlags &= ~EF_JETPACK_ACTIVE; + } +#endif + } + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + + if ( (parentPS->brokenLimbs&(1<brokenLimbs&(1<m_iRemovedSurfaces + || parentPS->electrifyTime>=curTime) + { //go out of control + parentPS->speed += speedInc; + //Why set forwardmove? PMove code doesn't use it... does it? + pVeh->m_ucmd.forwardmove = 127; + } +#ifdef QAGAME //well, the thing is always going to be inhabited if it's being predicted! + else if ( FighterSuspended( pVeh, parentPS ) ) + { + parentPS->speed = 0; + pVeh->m_ucmd.forwardmove = 0; + } + else if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) + && parentPS->speed > 0 ) + {//pilot jumped out while we were moving forward (not landing or landed) so just keep the throttle locked + //Why set forwardmove? PMove code doesn't use it... does it? + pVeh->m_ucmd.forwardmove = 127; + } +#endif + else if ( ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) && pVeh->m_LandTrace.fraction >= 0.05f ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + pVeh->m_ucmd.forwardmove = 127; + } + else if ( pVeh->m_ucmd.forwardmove < 0 + || pVeh->m_ucmd.upmove < 0 ) + {//decelerating or braking + if ( pVeh->m_ucmd.upmove < 0 ) + {//braking (trying to land?), slow down faster + if ( pVeh->m_ucmd.forwardmove ) + {//decelerator + brakes + speedInc += pVeh->m_pVehicleInfo->braking; + speedIdleDec += pVeh->m_pVehicleInfo->braking; + } + else + {//just brakes + speedInc = speedIdleDec = pVeh->m_pVehicleInfo->braking; + } + } + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + if ( FighterOverValidLandingSurface( pVeh ) ) + {//there's ground below us and we're trying to slow down, slow down faster + parentPS->speed -= speedInc; + } + else + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < MIN_LANDING_SPEED ) + {//unless you can land, don't drop below the landing speed!!! This way you can't come to a dead stop in mid-air + parentPS->speed = MIN_LANDING_SPEED; + } + } + } + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + pVeh->m_ucmd.forwardmove = 127; + } + else if ( speedMin >= 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + } + //else not accel, decel or braking + else if ( pVeh->m_pVehicleInfo->throttleSticks ) + {//we're using a throttle that sticks at current speed + if ( parentPS->speed <= MIN_LANDING_SPEED ) + {//going less than landing speed + if ( FighterOverValidLandingSurface( pVeh ) ) + {//close to ground and not going very fast + //slow to a stop if within landing height and not accel/decel/braking + if ( parentPS->speed > 0 ) + {//slow down + parentPS->speed -= speedIdleDec; + } + else if ( parentPS->speed < 0 ) + {//going backwards, slow down + parentPS->speed += speedIdleDec; + } + } + else + {//not over a valid landing surf, but going too slow + //speed up to idle speed if not over a valid landing surf and not accel/decel/braking + if ( parentPS->speed < speedIdle ) + { + parentPS->speed += speedIdleAccel; + if ( parentPS->speed > speedIdle ) + { + parentPS->speed = speedIdle; + } + } + } + } + } + else + {//then speed up or slow down to idle speed + //accelerate to cruising speed only, otherwise, just coast + // If they've launched, apply some constant motion. + if ( (pVeh->m_LandTrace.fraction >= 1.0f //no ground + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE )//or can't land on ground below us + && speedIdle > 0 ) + {//not above ground and have an idle speed + //float fSpeed = pVeh->m_pParentEntity->client->ps.speed; + if ( parentPS->speed < speedIdle ) + { + parentPS->speed += speedIdleAccel; + if ( parentPS->speed > speedIdle ) + { + parentPS->speed = speedIdle; + } + } + else if ( parentPS->speed > 0 ) + {//slow down + parentPS->speed -= speedIdleDec; + + if ( parentPS->speed < speedIdle ) + { + parentPS->speed = speedIdle; + } + } + } + else//either close to ground or no idle speed + {//slow to a stop if no idle speed or within landing height and not accel/decel/braking + if ( parentPS->speed > 0 ) + {//slow down + parentPS->speed -= speedIdleDec; + } + else if ( parentPS->speed < 0 ) + {//going backwards, slow down + parentPS->speed += speedIdleDec; + } + } + } + } + else + { + if ( pVeh->m_ucmd.forwardmove < 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + if ( pVeh->m_ucmd.upmove < 0 ) + { + pVeh->m_ucmd.upmove = 0; + } + +#ifndef _JK2MP + if ( !pVeh->m_pVehicleInfo->strafePerc || (!g_speederControlScheme->value && !pVeh->m_pParentEntity->s.number) ) + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + pVeh->m_ucmd.rightmove = 0; + } +#endif + } + +#if 1//This is working now, but there are some transitional jitters... Rich? +//STRAFING============================================================================== + if ( pVeh->m_pVehicleInfo->strafePerc +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + && pVeh->m_pVehicleInfo->Inhabited( pVeh )//has to have a driver in order to be capable of landing +#endif + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTimem_LandTrace.fraction >= 1.0f//no grounf + ||pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE//can't land here + ||parentPS->speed>MIN_LANDING_SPEED)//going too fast to land + && pVeh->m_ucmd.rightmove ) + {//strafe + vec3_t vAngles, vRight; + float strafeSpeed = (pVeh->m_pVehicleInfo->strafePerc*speedMax)*5.0f; + VectorCopy( pVeh->m_vOrientation, vAngles ); + vAngles[PITCH] = vAngles[ROLL] = 0; + AngleVectors( vAngles, NULL, vRight, NULL ); + + if ( pVeh->m_ucmd.rightmove > 0 ) + {//strafe right + //FIXME: this will probably make it possible to cheat and + // go faster than max speed if you keep turning and strafing... + if ( pVeh->m_fStrafeTime > -MAX_STRAFE_TIME ) + {//can strafe right for 2 seconds + float curStrafeSpeed = DotProduct( parentPS->velocity, vRight ); + if ( curStrafeSpeed > 0.0f ) + {//if > 0, already strafing right + strafeSpeed -= curStrafeSpeed;//so it doesn't add up + } + if ( strafeSpeed > 0 ) + { + VectorMA( parentPS->velocity, strafeSpeed*pVeh->m_fTimeModifier, vRight, parentPS->velocity ); + } + pVeh->m_fStrafeTime -= 50*pVeh->m_fTimeModifier; + } + } + else + {//strafe left + if ( pVeh->m_fStrafeTime < MAX_STRAFE_TIME ) + {//can strafe left for 2 seconds + float curStrafeSpeed = DotProduct( parentPS->velocity, vRight ); + if ( curStrafeSpeed < 0.0f ) + {//if < 0, already strafing left + strafeSpeed += curStrafeSpeed;//so it doesn't add up + } + if ( strafeSpeed > 0 ) + { + VectorMA( parentPS->velocity, -strafeSpeed*pVeh->m_fTimeModifier, vRight, parentPS->velocity ); + } + pVeh->m_fStrafeTime += 50*pVeh->m_fTimeModifier; + } + } + //strafing takes away from forward speed? If so, strafePerc above should use speedMax + //parentPS->speed *= (1.0f-pVeh->m_pVehicleInfo->strafePerc); + } + else//if ( pVeh->m_fStrafeTime ) + { + if ( pVeh->m_fStrafeTime > 0 ) + { + pVeh->m_fStrafeTime -= 50*pVeh->m_fTimeModifier; + if ( pVeh->m_fStrafeTime < 0 ) + { + pVeh->m_fStrafeTime = 0.0f; + } + } + else if ( pVeh->m_fStrafeTime < 0 ) + { + pVeh->m_fStrafeTime += 50*pVeh->m_fTimeModifier; + if ( pVeh->m_fStrafeTime > 0 ) + { + pVeh->m_fStrafeTime = 0.0f; + } + } + } +//STRAFING============================================================================== +#endif + + if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + +#ifdef QAGAME//FIXME: get working in GAME and CGAME + if ((pVeh->m_vOrientation[PITCH]*0.1f) > 10.0f) + { //pitched downward, increase speed more and more based on our tilt + if ( FighterIsInSpace( (gentity_t *)parent ) ) + {//in space, do nothing with speed base on pitch... + } + else + { + //really should only do this when on a planet + float mult = pVeh->m_vOrientation[PITCH]*0.1f; + if (mult < 1.0f) + { + mult = 1.0f; + } + parentPS->speed = PredictedAngularDecrement(mult, pVeh->m_fTimeModifier*10.0f, parentPS->speed); + } + } + + if (pVeh->m_iRemovedSurfaces + || parentPS->electrifyTime>=curTime) + { //going down + if ( FighterIsInSpace( (gentity_t *)parent ) ) + {//we're in a valid trigger_space brush + //simulate randomness + if ( !(parent->s.number&3) ) + {//even multiple of 3, don't do anything + parentPS->gravity = 0; + } + else if ( !(parent->s.number&2) ) + {//even multiple of 2, go up + parentPS->gravity = -500.0f; + parentPS->velocity[2] = 80.0f; + } + else + {//odd number, go down + parentPS->gravity = 500.0f; + parentPS->velocity[2] = -80.0f; + } + } + else + {//over a planet + parentPS->gravity = 500.0f; + parentPS->velocity[2] = -80.0f; + } + } + else if ( FighterSuspended( pVeh, parentPS ) ) + { + parentPS->gravity = 0; + } + else if ( (!parentPS->speed||parentPS->speed < speedIdle) && pVeh->m_ucmd.upmove <= 0 ) + {//slowing down or stopped and not trying to take off + if ( FighterIsInSpace( (gentity_t *)parent ) ) + {//we're in space, stopping doesn't make us drift downward + if ( FighterOverValidLandingSurface( pVeh ) ) + {//well, there's something below us to land on, so go ahead and lower us down to it + parentPS->gravity = (speedIdle - parentPS->speed)/4; + } + } + else + {//over a planet + parentPS->gravity = (speedIdle - parentPS->speed)/4; + } + } + else + { + parentPS->gravity = 0; + } +#else//FIXME: get above checks working in GAME and CGAME + parentPS->gravity = 0; +#endif + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +extern void BG_VehicleTurnRateForSpeed( Vehicle_t *pVeh, float speed, float *mPitchOverride, float *mYawOverride ); +static void FighterWingMalfunctionCheck( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + float mPitchOverride = 1.0f; + float mYawOverride = 1.0f; + BG_VehicleTurnRateForSpeed( pVeh, parentPS->speed, &mPitchOverride, &mYawOverride ); + //check right wing damage + if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] += (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*50.0f; + } + else if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] += (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*12.5f; + } + + //check left wing damage + if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] -= (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*50.0f; + } + else if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] -= (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*12.5f; + } + +} + +static void FighterNoseMalfunctionCheck( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + float mPitchOverride = 1.0f; + float mYawOverride = 1.0f; + BG_VehicleTurnRateForSpeed( pVeh, parentPS->speed, &mPitchOverride, &mYawOverride ); + //check nose damage + if ( (parentPS->brokenLimbs&(1<m_vOrientation[PITCH] += sin( pVeh->m_ucmd.serverTime*0.001 )*pVeh->m_fTimeModifier*mPitchOverride*50.0f; + } + else if ( (parentPS->brokenLimbs&(1<m_vOrientation[PITCH] += sin( pVeh->m_ucmd.serverTime*0.001 )*pVeh->m_fTimeModifier*mPitchOverride*20.0f; + } +} + +static void FighterDamageRoutine( Vehicle_t *pVeh, bgEntity_t *parent, playerState_t *parentPS, playerState_t *riderPS, qboolean isDead ) +{ + if ( !pVeh->m_iRemovedSurfaces ) + {//still in one piece + if ( pVeh->m_pParentEntity && isDead ) + {//death spiral + pVeh->m_ucmd.upmove = 0; + //FIXME: don't bias toward pitching down when not in space + /* + if ( FighterIsInSpace( pVeh->m_pParentEntity ) ) + { + } + else + */ + if ( !(pVeh->m_pParentEntity->s.number%3) ) + {//NOT everyone should do this + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + } + } + else if ( !(pVeh->m_pParentEntity->s.number%2) ) + { + pVeh->m_vOrientation[PITCH] -= pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > -60.0f ) + { + pVeh->m_vOrientation[PITCH] = -60.0f; + } + } + } + if ( (pVeh->m_pParentEntity->s.number%2) ) + { + pVeh->m_vOrientation[YAW] += pVeh->m_fTimeModifier; + pVeh->m_vOrientation[ROLL] += pVeh->m_fTimeModifier*4.0f; + } + else + { + pVeh->m_vOrientation[YAW] -= pVeh->m_fTimeModifier; + pVeh->m_vOrientation[ROLL] -= pVeh->m_fTimeModifier*4.0f; + } + } + return; + } + + //if we get into here we have at least one broken piece + pVeh->m_ucmd.upmove = 0; + + //if you're off the ground and not suspended, pitch down + //FIXME: not in space! + if ( pVeh->m_LandTrace.fraction >= 0.1f ) + { + if ( !FighterSuspended( pVeh, parentPS ) ) + { + //pVeh->m_ucmd.forwardmove = 0; + //FIXME: don't bias towards pitching down when in space... + if ( !(pVeh->m_pParentEntity->s.number%2) ) + {//NOT everyone should do this + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + } + } + else if ( !(pVeh->m_pParentEntity->s.number%3) ) + { + pVeh->m_vOrientation[PITCH] -= pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > -60.0f ) + { + pVeh->m_vOrientation[PITCH] = -60.0f; + } + } + } + //else: just keep going forward + } + } +#ifdef QAGAME + if ( pVeh->m_LandTrace.fraction < 1.0f ) + { //if you land at all when pieces of your ship are missing, then die + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + gentity_t *killer = parent; +#ifdef _JK2MP//only have this info in MP... + if (parent->client->ps.otherKiller < ENTITYNUM_WORLD && + parent->client->ps.otherKillerTime > level.time) + { + gentity_t *potentialKiller = &g_entities[parent->client->ps.otherKiller]; + + if (potentialKiller->inuse && potentialKiller->client) + { //he's valid I guess + killer = potentialKiller; + } + } +#endif + G_Damage(parent, killer, killer, vec3_origin, parent->client->ps.origin, 99999, DAMAGE_NO_ARMOR, MOD_SUICIDE); + } +#endif + + if ( ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) && + ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) ) + { //wings on both side broken + float factor = 2.0f; + if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) + { //all wings broken + factor *= 2.0f; + } + + if ( !(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5) ) + {//won't yaw, so increase roll factor + factor *= 4.0f; + } + + pVeh->m_vOrientation[ROLL] += (pVeh->m_fTimeModifier*factor); //do some spiralling + } + else if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) + { //left wing broken + float factor = 2.0f; + if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) + { //if both are broken.. + factor *= 2.0f; + } + + if ( !(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5) ) + {//won't yaw, so increase roll factor + factor *= 4.0f; + } + + pVeh->m_vOrientation[ROLL] += factor*pVeh->m_fTimeModifier; + } + else if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) + { //right wing broken + float factor = 2.0f; + if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) + { //if both are broken.. + factor *= 2.0f; + } + + if ( !(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5) ) + {//won't yaw, so increase roll factor + factor *= 4.0f; + } + + pVeh->m_vOrientation[ROLL] -= factor*pVeh->m_fTimeModifier; + } +} + +#ifdef _JK2MP +void FighterYawAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ + float angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); + + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*0.8f; //magic number hackery + + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +} + +void FighterPitchAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ + float angDif = AngleSubtract(pVeh->m_vOrientation[PITCH], riderPS->viewangles[PITCH]); + + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*0.8f; //magic number hackery + + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[PITCH] = AngleNormalize360(pVeh->m_vOrientation[PITCH] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +} +#endif + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessOrientCommands the Vehicle. +static void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + + bgEntity_t *parent = pVeh->m_pParentEntity; + playerState_t *parentPS, *riderPS; + float angleTimeMod; +#ifdef QAGAME + const float groundFraction = 0.1f; +#endif + float curRoll = 0.0f; + qboolean isDead = qfalse; + qboolean isLandingOrLanded = qfalse; +#ifndef _JK2MP//SP + int curTime = level.time; +#elif QAGAME//MP GAME + int curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + int curTime = pm->cmd.serverTime; +#endif + +#ifdef _JK2MP + bgEntity_t *rider = NULL; + if (parent->s.owner != ENTITYNUM_NONE) + { + rider = PM_BGEntForNum(parent->s.owner); //&g_entities[parent->r.ownerNum]; + } +#else + gentity_t *rider = parent->owner; +#endif + +#ifdef _JK2MP + if ( !rider ) +#else + if ( !rider || !rider->client ) +#endif + { + rider = parent; + } + +#ifdef _JK2MP + parentPS = parent->playerState; + riderPS = rider->playerState; + isDead = (qboolean)((parentPS->eFlags&EF_DEAD)!=0); +#else + parentPS = &parent->client->ps; + riderPS = &rider->client->ps; + isDead = (parentPS->stats[STAT_HEALTH] <= 0 ); +#endif + +#ifdef _JK2MP + if ( parentPS->hyperSpaceTime + && (curTime - parentPS->hyperSpaceTime) < HYPERSPACE_TIME ) + {//Going to Hyperspace + VectorCopy( riderPS->viewangles, pVeh->m_vOrientation ); + VectorCopy( riderPS->viewangles, parentPS->viewangles ); + return; + } +#endif + + if ( pVeh->m_iDropTime >= curTime ) + {//you can only YAW during this + parentPS->viewangles[YAW] = pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + return; + } + + angleTimeMod = pVeh->m_fTimeModifier; + + if ( isDead || parentPS->electrifyTime>=curTime || + (pVeh->m_pVehicleInfo->surfDestruction && + pVeh->m_iRemovedSurfaces && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) ) + { //do some special stuff for when all the wings are torn off + FighterDamageRoutine(pVeh, parent, parentPS, riderPS, isDead); + pVeh->m_vOrientation[ROLL] = AngleNormalize180( pVeh->m_vOrientation[ROLL] ); + return; + } + + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + pVeh->m_vOrientation[ROLL] = PredictedAngularDecrement(0.95f, angleTimeMod*2.0f, pVeh->m_vOrientation[ROLL]); + } + + isLandingOrLanded = (FighterIsLanding( pVeh, parentPS )||FighterIsLanded( pVeh, parentPS )); + + if (!isLandingOrLanded) + { //don't do this stuff while landed.. I guess. I don't want ships spinning in place, looks silly. + int m = 0; + float aVelDif; + float dForVel; + + FighterWingMalfunctionCheck( pVeh, parentPS ); + + while (m < 3) + { + aVelDif = pVeh->m_vFullAngleVelocity[m]; + + if (aVelDif != 0.0f) + { + dForVel = (aVelDif*0.1f)*pVeh->m_fTimeModifier; + if (dForVel > 1.0f || dForVel < -1.0f) + { + pVeh->m_vOrientation[m] += dForVel; + pVeh->m_vOrientation[m] = AngleNormalize180(pVeh->m_vOrientation[m]); + if (m == PITCH) + { //don't pitch downward into ground even more. + if (pVeh->m_vOrientation[m] > 90.0f && (pVeh->m_vOrientation[m]-dForVel) < 90.0f) + { + pVeh->m_vOrientation[m] = 90.0f; + pVeh->m_vFullAngleVelocity[m] = -pVeh->m_vFullAngleVelocity[m]; + } + } + if (riderPS) + { + riderPS->viewangles[m] = pVeh->m_vOrientation[m]; + } + pVeh->m_vFullAngleVelocity[m] -= dForVel; + } + else + { + pVeh->m_vFullAngleVelocity[m] = 0.0f; + } + } + + m++; + } + } + else + { //clear decr/incr angles once landed. + VectorClear(pVeh->m_vFullAngleVelocity); + } + + curRoll = pVeh->m_vOrientation[ROLL]; + + // If we're landed, we shouldn't be able to do anything but take off. + if ( isLandingOrLanded //going slow enough to start landing + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTimespeed > 0.0f ) + {//Uh... what? Why? + if ( pVeh->m_LandTrace.fraction < 0.3f ) + { + pVeh->m_vOrientation[PITCH] = 0.0f; + } + else + { + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.83f, angleTimeMod*10.0f, pVeh->m_vOrientation[PITCH]); + } + } + if ( pVeh->m_LandTrace.fraction > 0.1f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + {//off the ground, at least (or not on a valid landing surf) + // Dampen the turn rate based on the current height. +#ifdef _JK2MP + FighterYawAdjust(pVeh, riderPS, parentPS); +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW];//*pVeh->m_LandTrace.fraction; +#endif + } + } + else if ( (pVeh->m_iRemovedSurfaces||parentPS->electrifyTime>=curTime)//spiralling out of control + && (!(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5)) ) + {//no yaw control + } + else if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS && parentPS->speed > 0.0f )//&& !( pVeh->m_ucmd.forwardmove > 0 && pVeh->m_LandTrace.fraction != 1.0f ) ) + { + if ( BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + VectorCopy( riderPS->viewangles, pVeh->m_vOrientation ); + VectorCopy( riderPS->viewangles, parentPS->viewangles ); +#ifdef _JK2MP + //BG_ExternThisSoICanRecompileInDebug( pVeh, riderPS ); +#endif + + curRoll = pVeh->m_vOrientation[ROLL]; + + FighterNoseMalfunctionCheck( pVeh, parentPS ); + + //VectorCopy( pVeh->m_vOrientation, parentPS->viewangles ); + } + else + { + /* + float fTurnAmt[3]; + //PITCH + fTurnAmt[PITCH] = riderPS->viewangles[PITCH] * 0.08f; + //YAW + fTurnAmt[YAW] = riderPS->viewangles[YAW] * 0.065f; + fTurnAmt[YAW] *= fTurnAmt[YAW]; + // Dampen the turn rate based on the current height. + if ( riderPS->viewangles[YAW] < 0 ) + {//must keep it negative because squaring a negative makes it positive + fTurnAmt[YAW] = -fTurnAmt[YAW]; + } + fTurnAmt[YAW] *= pVeh->m_LandTrace.fraction; + //ROLL + fTurnAmt[2] = 0.0f; + */ + + //Actal YAW +#ifdef _JK2MP + FighterYawAdjust(pVeh, riderPS, parentPS); +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; +#endif + + // If we are not hitting the ground, allow the fighter to pitch up and down. + if ( !FighterOverValidLandingSurface( pVeh ) + || parentPS->speed > MIN_LANDING_SPEED ) + //if ( ( pVeh->m_LandTrace.fraction >= 1.0f || pVeh->m_ucmd.forwardmove != 0 ) && pVeh->m_LandTrace.fraction >= 0.0f ) + { + float fYawDelta; + +#ifdef _JK2MP + FighterPitchAdjust(pVeh, riderPS, parentPS); +#else + pVeh->m_vOrientation[PITCH] = riderPS->viewangles[PITCH]; +#endif + + FighterNoseMalfunctionCheck( pVeh, parentPS ); + + // Adjust the roll based on the turn amount and dampen it a little. + fYawDelta = AngleSubtract(pVeh->m_vOrientation[YAW], pVeh->m_vPrevOrientation[YAW]); //pVeh->m_vOrientation[YAW] - pVeh->m_vPrevOrientation[YAW]; + if ( fYawDelta > 8.0f ) + { + fYawDelta = 8.0f; + } + else if ( fYawDelta < -8.0f ) + { + fYawDelta = -8.0f; + } + curRoll -= fYawDelta; + + curRoll = PredictedAngularDecrement(0.93f, angleTimeMod*2.0f, curRoll); + //cap it reasonably + //NOTE: was hardcoded to 40.0f, now using extern data + if ( pVeh->m_pVehicleInfo->rollLimit != -1 ) + { + if (curRoll > pVeh->m_pVehicleInfo->rollLimit ) + { + curRoll = pVeh->m_pVehicleInfo->rollLimit; + } + else if (curRoll < -pVeh->m_pVehicleInfo->rollLimit) + { + curRoll = -pVeh->m_pVehicleInfo->rollLimit; + } + } + } + } + } + + // If you are directly impacting the ground, even out your pitch. + if ( isLandingOrLanded ) + {//only if capable of landing + if ( !isDead + && parentPS->electrifyTimem_pVehicleInfo->surfDestruction || !pVeh->m_iRemovedSurfaces ) ) + {//not crashing or spiralling out of control... + if ( pVeh->m_vOrientation[PITCH] > 0 ) + { + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.2f, angleTimeMod*10.0f, pVeh->m_vOrientation[PITCH]); + } + else + { + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.75f, angleTimeMod*10.0f, pVeh->m_vOrientation[PITCH]); + } + } + } + + +/* +//NOTE: all this is redundant now since we have the FighterDamageRoutine func... +#ifdef _JK2MP //...yeah. Need to send armor across net for prediction to work. + if ( isDead ) +#else + if ( pVeh->m_iArmor <= 0 ) +#endif + {//going to explode + //FIXME: maybe make it erratically jerk or spin or start and stop? +#ifndef _JK2MP + if ( g_speederControlScheme->value > 0 || !rider || rider->s.number ) +#else + if (1) +#endif + { + pVeh->m_ucmd.rightmove = Q_irand( -64, 64 ); + } + else + { + pVeh->m_ucmd.rightmove = 0; + } + pVeh->m_ucmd.forwardmove = Q_irand( -32, 127 ); + pVeh->m_ucmd.upmove = Q_irand( -127, 127 ); + pVeh->m_vOrientation[YAW] += Q_flrand( -10, 10 ); + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + if ( pVeh->m_LandTrace.fraction != 0.0f ) + { + parentPS->velocity[2] -= pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + } +*/ + // If no one is in this vehicle and it's up in the sky, pitch it forward as it comes tumbling down. +#ifdef QAGAME //never gonna happen on client anyway, we can't be getting predicted unless the predicting client is boarded + if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) + && pVeh->m_LandTrace.fraction >= groundFraction + && !FighterIsInSpace( (gentity_t *)parent ) + && !FighterSuspended( pVeh, parentPS ) ) + { + pVeh->m_ucmd.upmove = 0; + //pVeh->m_ucmd.forwardmove = 0; + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + } + } +#endif + + if ( !pVeh->m_fStrafeTime ) + {//use that roll + pVeh->m_vOrientation[ROLL] = curRoll; + //NOTE: this seems really backwards... + if ( pVeh->m_vOrientation[ROLL] ) + { //continually adjust the yaw based on the roll.. + if ( (pVeh->m_iRemovedSurfaces||parentPS->electrifyTime>=curTime)//spiralling out of control + && (!(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5)) ) + {//leave YAW alone + } + else + { + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + pVeh->m_vOrientation[YAW] -= ((pVeh->m_vOrientation[ROLL])*0.05f)*pVeh->m_fTimeModifier; + } + } + } + } + else + {//add in strafing roll + float strafeRoll = (pVeh->m_fStrafeTime/MAX_STRAFE_TIME)*pVeh->m_pVehicleInfo->rollLimit;//pVeh->m_pVehicleInfo->bankingSpeed* + float strafeDif = AngleSubtract(strafeRoll, pVeh->m_vOrientation[ROLL]); + pVeh->m_vOrientation[ROLL] += (strafeDif*0.1f)*pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + {//cap it reasonably + if ( pVeh->m_pVehicleInfo->rollLimit != -1 + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTimem_vOrientation[ROLL] > pVeh->m_pVehicleInfo->rollLimit ) + { + pVeh->m_vOrientation[ROLL] = pVeh->m_pVehicleInfo->rollLimit; + } + else if (pVeh->m_vOrientation[ROLL] < -pVeh->m_pVehicleInfo->rollLimit) + { + pVeh->m_vOrientation[ROLL] = -pVeh->m_pVehicleInfo->rollLimit; + } + } + } + } + + if (pVeh->m_pVehicleInfo->surfDestruction) + { + FighterDamageRoutine(pVeh, parent, parentPS, riderPS, isDead); + } + pVeh->m_vOrientation[ROLL] = AngleNormalize180( pVeh->m_vOrientation[ROLL] ); + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef QAGAME //ONLY in SP or on server, not cgame + +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); + +// This function makes sure that the vehicle is properly animated. +static void AnimateVehicle( Vehicle_t *pVeh ) +{ + int Anim = -1; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + qboolean isLanding = qfalse, isLanded = qfalse; +#ifdef _JK2MP + playerState_t *parentPS = pVeh->m_pParentEntity->playerState; +#else + playerState_t *parentPS = &pVeh->m_pParentEntity->client->ps; +#endif +#ifndef _JK2MP//SP + //nothing +#elif QAGAME//MP GAME + int curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + int curTime = pm->cmd.serverTime; +#endif + +#ifdef _JK2MP + if ( parentPS->hyperSpaceTime + && curTime - parentPS->hyperSpaceTime < HYPERSPACE_TIME ) + {//Going to Hyperspace + //close the wings (FIXME: makes sense on X-Wing, not Shuttle?) + if ( pVeh->m_ulFlags & VEH_WINGSOPEN ) + { + pVeh->m_ulFlags &= ~VEH_WINGSOPEN; + Anim = BOTH_WINGS_CLOSE; + } + } + else +#endif + { + isLanding = FighterIsLanding( pVeh, parentPS ); + isLanded = FighterIsLanded( pVeh, parentPS ); + + // if we're above launch height (way up in the air)... + if ( !isLanding && !isLanded ) + { + if ( !( pVeh->m_ulFlags & VEH_WINGSOPEN ) ) + { + pVeh->m_ulFlags |= VEH_WINGSOPEN; + pVeh->m_ulFlags &= ~VEH_GEARSOPEN; + Anim = BOTH_WINGS_OPEN; + } + } + // otherwise we're below launch height and still taking off. + else + { + if ( (pVeh->m_ucmd.forwardmove < 0 || pVeh->m_ucmd.upmove < 0||isLanded) + && pVeh->m_LandTrace.fraction <= 0.4f + && pVeh->m_LandTrace.plane.normal[2] >= MIN_LANDING_SLOPE ) + {//already landed or trying to land and close to ground + // Open gears. + if ( !( pVeh->m_ulFlags & VEH_GEARSOPEN ) ) + { +#ifdef _JK2MP + if ( pVeh->m_pVehicleInfo->soundLand ) + {//just landed? +#ifdef QAGAME//MP GAME-side + G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_AUTO, pVeh->m_pVehicleInfo->soundLand ); +#elif CGAME//MP CGAME-side + //trap_S_StartSound( NULL, pVeh->m_pParentEntity->s.number, CHAN_AUTO, pVeh->m_pVehicleInfo->soundLand ); +#endif + } +#endif + pVeh->m_ulFlags |= VEH_GEARSOPEN; + Anim = BOTH_GEARS_OPEN; + } + } + else + {//trying to take off and almost halfway off the ground + // Close gears (if they're open). + if ( pVeh->m_ulFlags & VEH_GEARSOPEN ) + { + pVeh->m_ulFlags &= ~VEH_GEARSOPEN; + Anim = BOTH_GEARS_CLOSE; + //iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + } + // If gears are closed, and we are below launch height, close the wings. + else + { + if ( pVeh->m_ulFlags & VEH_WINGSOPEN ) + { + pVeh->m_ulFlags &= ~VEH_WINGSOPEN; + Anim = BOTH_WINGS_CLOSE; + } + } + } + } + } + + if ( Anim != -1 ) + { + #ifdef _JK2MP + BG_SetAnim(pVeh->m_pParentEntity->playerState, bgAllAnims[pVeh->m_pParentEntity->localAnimIndex].anims, + SETANIM_BOTH, Anim, iFlags, iBlend); + #else + NPC_SetAnim( pVeh->m_pParentEntity, SETANIM_BOTH, Anim, iFlags, iBlend ); + #endif + } +} + +// This function makes sure that the rider's in this vehicle are properly animated. +static void AnimateRiders( Vehicle_t *pVeh ) +{ +} + +#endif //game-only + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +void G_SetFighterVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME //ONLY in SP or on server, not cgame + pVehInfo->AnimateVehicle = AnimateVehicle; + pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; + pVehInfo->Board = Board; + pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; +// pVehInfo->DeathUpdate = DeathUpdate; +// pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; + pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif //game-only + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +void G_CreateFighterNPC( Vehicle_t **pVeh, const char *strType ) +{ + // Allocate the Vehicle. +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); +#else + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); +#endif + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strType )]; +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/code/game/G_Timer.cpp b/code/game/G_Timer.cpp new file mode 100644 index 0000000..8d2c205 --- /dev/null +++ b/code/game/G_Timer.cpp @@ -0,0 +1,397 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +#include "G_Local.h" +#include "../rufl/hstring.h" + +#define MAX_GTIMERS 16384 + +typedef struct gtimer_s +{ + hstring id; // Use handle strings, so that things work after loading + int time; + struct gtimer_s *next; // In either free list or current list +} gtimer_t; + +gtimer_t g_timerPool[ MAX_GTIMERS ]; +gtimer_t *g_timers[ MAX_GENTITIES ]; +gtimer_t *g_timerFreeList; + + +static int TIMER_GetCount(int num) +{ + gtimer_t *p = g_timers[num]; + int count = 0; + + while (p) + { + count++; + p = p->next; + } + + return count; +} + + +/* +------------------------- +TIMER_RemoveHelper + +Scans an entities timer list to remove a given +timer from the list and put it on the free list + +Doesn't do much error checking, only called below +------------------------- +*/ +static void TIMER_RemoveHelper( int num, gtimer_t *timer ) +{ + gtimer_t *p = g_timers[num]; + + // Special case: first timer in list + if (p == timer) + { + g_timers[num] = g_timers[num]->next; + p->next = g_timerFreeList; + g_timerFreeList = p; + return; + } + + // Find the predecessor + while (p->next != timer) + { + p = p->next; + } + + // Rewire + p->next = p->next->next; + timer->next = g_timerFreeList; + g_timerFreeList = timer; + return; +} + + + + +/* +------------------------- +TIMER_Clear +------------------------- +*/ + +void TIMER_Clear( void ) +{ + int i; + for (i = 0; i < MAX_GENTITIES; i++) + { + g_timers[i] = NULL; + } + + for (i = 0; i < MAX_GTIMERS - 1; i++) + { + g_timerPool[i].next = &g_timerPool[i+1]; + } + g_timerPool[MAX_GTIMERS-1].next = NULL; + g_timerFreeList = &g_timerPool[0]; +} + +/* +------------------------- +TIMER_Clear +------------------------- +*/ + +void TIMER_Clear( int idx ) +{ + // rudimentary safety checks, might be other things to check? + if ( idx >= 0 && idx < MAX_GENTITIES ) + { + gtimer_t *p = g_timers[idx]; + + // No timers at all -> do nothing + if (!p) + { + return; + } + + // Find the end of this ents timer list + while (p->next) + { + p = p->next; + } + + // Splice the lists + p->next = g_timerFreeList; + g_timerFreeList = g_timers[idx]; + g_timers[idx] = NULL; + return; + } +} + + +/* +------------------------- +TIMER_Save +------------------------- +*/ + +void TIMER_Save( void ) +{ + int j; + gentity_t *ent; + + for ( j = 0, ent = &g_entities[0]; j < MAX_GENTITIES; j++, ent++ ) + { + unsigned char numTimers = TIMER_GetCount(j); + + if ( !ent->inuse && numTimers) + { +// Com_Printf( "WARNING: ent with timers not inuse\n" ); + assert(numTimers); + TIMER_Clear( j ); + numTimers = 0; + } + + //Write out the timer information + gi.AppendToSaveGame('TIME', (void *)&numTimers, sizeof(numTimers)); + + gtimer_t *p = g_timers[j]; + assert ((numTimers && p) || (!numTimers && !p)); + + while(p) + { + const char *timerID = p->id.c_str(); + const int length = strlen(timerID) + 1; + const int time = p->time - level.time; //convert this back to delta so we can use SET after loading + + assert( length < 1024 );//This will cause problems when loading the timer if longer + + //Write out the id string + gi.AppendToSaveGame('TMID', (void *) timerID, length); + + //Write out the timer data + gi.AppendToSaveGame('TDTA', (void *) &time, sizeof( time ) ); + p = p->next; + } + } +} + +/* +------------------------- +TIMER_Load +------------------------- +*/ + +void TIMER_Load( void ) +{ + int j; + gentity_t *ent; + + for ( j = 0, ent = &g_entities[0]; j < MAX_GENTITIES; j++, ent++ ) + { + unsigned char numTimers; + + gi.ReadFromSaveGame( 'TIME', (void *)&numTimers, sizeof(numTimers) ); + + //Read back all entries + for ( int i = 0; i < numTimers; i++ ) + { + int time; + char tempBuffer[1024]; // Still ugly. Setting ourselves up for 007 AUF all over again. =) + + assert (sizeof(g_timers[0]->time) == sizeof(time) );//make sure we're reading the same size as we wrote + + //Read the id string and time + gi.ReadFromSaveGame( 'TMID', (char *) tempBuffer, 0 ); + gi.ReadFromSaveGame( 'TDTA', (void *) &time, sizeof( time ) ); + + //this is odd, we saved all the timers in the autosave, but not all the ents are spawned yet from an auto load, so skip it + if (ent->inuse) + { //Restore it + TIMER_Set(ent, tempBuffer, time); + } + } + } +} + + +static gtimer_t *TIMER_GetNew(int num, const char *identifier) +{ + assert(num < ENTITYNUM_MAX_NORMAL);//don't want timers on NONE or the WORLD + gtimer_t *p = g_timers[num]; + + // Search for an existing timer with this name + while (p) + { + if (p->id == identifier) + { // Found it + return p; + } + + p = p->next; + } + + // No existing timer with this name was found, so grab one from the free list + if (!g_timerFreeList) + {//oh no, none free! + assert(g_timerFreeList); + return NULL; + } + + p = g_timerFreeList; + g_timerFreeList = g_timerFreeList->next; + p->next = g_timers[num]; + g_timers[num] = p; + return p; +} + + +gtimer_t *TIMER_GetExisting(int num, const char *identifier) +{ + gtimer_t *p = g_timers[num]; + + while (p) + { + if (p->id == identifier) + { // Found it + return p; + } + + p = p->next; + } + + return NULL; +} + + + +/* +------------------------- +TIMER_Set +------------------------- +*/ + +void TIMER_Set( gentity_t *ent, const char *identifier, int duration ) +{ + assert(ent->inuse); + gtimer_t *timer = TIMER_GetNew(ent->s.number, identifier); + + if (timer) + { + timer->id = identifier; + timer->time = level.time + duration; + } +} + +/* +------------------------- +TIMER_Get +------------------------- +*/ + +int TIMER_Get( gentity_t *ent, const char *identifier ) +{ + gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier); + + if (!timer) + { + return -1; + } + + return timer->time; +} + +/* +------------------------- +TIMER_Done +------------------------- +*/ + +qboolean TIMER_Done( gentity_t *ent, const char *identifier ) +{ + gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier); + + if (!timer) + { + return qtrue; + } + + return (timer->time < level.time); +} + +/* +------------------------- +TIMER_Done2 + +Returns false if timer has been +started but is not done...or if +timer was never started +------------------------- +*/ + +qboolean TIMER_Done2( gentity_t *ent, const char *identifier, qboolean remove ) +{ + gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier); + qboolean res; + + if (!timer) + { + return qfalse; + } + + res = (timer->time < level.time); + + if (res && remove) + { + // Put it back on the free list + TIMER_RemoveHelper(ent->s.number, timer); + } + + return res; +} + +/* +------------------------- +TIMER_Exists +------------------------- +*/ +qboolean TIMER_Exists( gentity_t *ent, const char *identifier ) +{ + return (qboolean)TIMER_GetExisting(ent->s.number, identifier); +} + + + +/* +------------------------- +TIMER_Remove +Utility to get rid of any timer +------------------------- +*/ +void TIMER_Remove( gentity_t *ent, const char *identifier ) +{ + gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier); + + if (!timer) + { + return; + } + + // Put it back on the free list + TIMER_RemoveHelper(ent->s.number, timer); +} + +/* +------------------------- +TIMER_Start +------------------------- +*/ + +qboolean TIMER_Start( gentity_t *self, const char *identifier, int duration ) +{ + if ( TIMER_Done( self, identifier ) ) + { + TIMER_Set( self, identifier, duration ); + return qtrue; + } + return qfalse; +} diff --git a/code/game/NPC.cpp b/code/game/NPC.cpp new file mode 100644 index 0000000..3807130 --- /dev/null +++ b/code/game/NPC.cpp @@ -0,0 +1,2724 @@ +// +// NPC.cpp - generic functions +// + +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + + + +#include "b_local.h" +#include "anims.h" +#include "g_functions.h" +#include "say.h" +#include "Q3_Interface.h" +#include "g_vehicles.h" + +extern vec3_t playerMins; +extern vec3_t playerMaxs; +//extern void PM_SetAnimFinal(int *torsoAnim,int *legsAnim,int type,int anim,int priority,int *torsoAnimTimer,int *legsAnimTimer,gentity_t *gent); +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); +extern void NPC_BSNoClip ( void ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void NPC_ApplyRoff (void); +extern void NPC_TempLookTarget ( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern void NPC_CheckPlayerAim ( void ); +extern void NPC_CheckAllClear ( void ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern qboolean NPC_CheckLookTarget( gentity_t *self ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +extern void Mark1_dying( gentity_t *self ); +extern void NPC_BSCinematic( void ); +extern int GetTime ( int lastTime ); +extern void G_CheckCharmed( gentity_t *self ); +extern qboolean Boba_Flying( gentity_t *self ); +extern qboolean RT_Flying( gentity_t *self ); +extern qboolean Jedi_CultistDestroyer( gentity_t *self ); +extern void Boba_Update(); +extern bool Boba_Flee(); +extern bool Boba_Tactics(); +extern void BubbleShield_Update(); +extern qboolean PM_LockedAnim( int anim ); + +extern cvar_t *g_dismemberment; +extern cvar_t *g_saberRealisticCombat; +extern cvar_t *g_corpseRemovalTime; + +//Local Variables +// ai debug cvars +cvar_t *debugNPCAI; // used to print out debug info about the bot AI +cvar_t *debugNPCFreeze; // set to disable bot ai and temporarily freeze them in place +cvar_t *debugNPCName; +cvar_t *d_saberCombat; +cvar_t *d_JediAI; +cvar_t *d_noGroupAI; +cvar_t *d_asynchronousGroupAI; +cvar_t *d_slowmodeath; + +extern qboolean stop_icarus; + +gentity_t *NPC; +gNPC_t *NPCInfo; +gclient_t *client; +usercmd_t ucmd; +visibility_t enemyVisibility; + +void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +static bState_t G_CurrentBState( gNPC_t *gNPC ); + +extern int eventClearTime; + +void CorpsePhysics( gentity_t *self ) +{ + // run the bot through the server like it was a real client + memset( &ucmd, 0, sizeof( ucmd ) ); + ClientThink( self->s.number, &ucmd ); + VectorCopy( self->s.origin, self->s.origin2 ); + + //FIXME: match my pitch and roll for the slope of my groundPlane + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !(self->flags&FL_DISINTEGRATED) ) + {//on the ground + //FIXME: check 4 corners + pitch_roll_for_slope( self ); + } + + if ( eventClearTime == level.time + ALERT_CLEAR_TIME ) + {//events were just cleared out so add me again + if ( !(self->client->ps.eFlags&EF_NODRAW) ) + { + AddSightEvent( self->enemy, self->currentOrigin, 384, AEL_DISCOVERED ); + } + } + + if ( level.time - self->s.time > 3000 ) + {//been dead for 3 seconds + if ( g_dismemberment->integer < 11381138 && !g_saberRealisticCombat->integer ) + {//can't be dismembered once dead + if ( self->client->NPC_class != CLASS_PROTOCOL ) + { + self->client->dismembered = true; + } + } + } + + if ( level.time - self->s.time > 500 ) + {//don't turn "nonsolid" until about 1 second after actual death + + if ((self->client->NPC_class != CLASS_MARK1) && (self->client->NPC_class != CLASS_INTERROGATOR)) // The Mark1 & Interrogator stays solid. + { + self->contents = CONTENTS_CORPSE; + } + + if ( self->message ) + { + self->contents |= CONTENTS_TRIGGER; + } + } +} + +/* +---------------------------------------- +NPC_RemoveBody + +Determines when it's ok to ditch the corpse +---------------------------------------- +*/ +#define REMOVE_DISTANCE 128 +#define REMOVE_DISTANCE_SQR (REMOVE_DISTANCE * REMOVE_DISTANCE) +extern qboolean InFOVFromPlayerView ( gentity_t *ent, int hFOV, int vFOV ); +qboolean G_OkayToRemoveCorpse( gentity_t *self ) +{ + //if we're still on a vehicle, we won't remove ourselves until we get ejected + if ( self->client && self->client->NPC_class != CLASS_VEHICLE && self->s.m_iVehicleNum != 0 ) + { + Vehicle_t *pVeh = g_entities[self->s.m_iVehicleNum].m_pVehicle; + if ( pVeh ) + { + if ( !pVeh->m_pVehicleInfo->Eject( pVeh, self, qtrue ) ) + {//dammit, still can't get off the vehicle... + return qfalse; + } + } + else + { + assert(0); +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_RED"ERROR: Dead pilot's vehicle removed while corpse was riding it (pilot: %s)???\n",self->targetname); +#endif + } + } + + if ( self->message ) + {//I still have a key + return qfalse; + } + + if ( IIcarusInterface::GetIcarus()->IsRunning( self->m_iIcarusID ) ) + {//still running a script + return qfalse; + } + + if ( self->activator + && self->activator->client + && ((self->activator->client->ps.eFlags&EF_HELD_BY_RANCOR) + || (self->activator->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) + || (self->activator->client->ps.eFlags&EF_HELD_BY_WAMPA)) ) + {//still holding a victim? + return qfalse; + } + + if ( self->client + && ((self->client->ps.eFlags&EF_HELD_BY_RANCOR) + || (self->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) + || (self->client->ps.eFlags&EF_HELD_BY_WAMPA) ) ) + {//being held by a creature + return qfalse; + } + + if ( self->client->ps.heldByClient < ENTITYNUM_WORLD ) + {//being dragged + return qfalse; + } + + //okay, well okay to remove us...? + return qtrue; +} + +void NPC_RemoveBody( gentity_t *self ) +{ + self->nextthink = level.time + FRAMETIME/2; + + //run physics at 20fps + CorpsePhysics( self ); + + if ( self->NPC->nextBStateThink <= level.time ) + {//run logic at 10 fps + if( self->m_iIcarusID != IIcarusInterface::ICARUS_INVALID && !stop_icarus ) + { + IIcarusInterface::GetIcarus()->Update( self->m_iIcarusID ); + } + self->NPC->nextBStateThink = level.time + FRAMETIME; + + if ( !G_OkayToRemoveCorpse( self ) ) + { + return; + } + + // I don't consider this a hack, it's creative coding . . . + // I agree, very creative... need something like this for ATST and GALAKMECH too! + if (self->client->NPC_class == CLASS_MARK1) + { + Mark1_dying( self ); + } + + // Since these blow up, remove the bounding box. + if ( self->client->NPC_class == CLASS_REMOTE + || self->client->NPC_class == CLASS_SENTRY + || self->client->NPC_class == CLASS_PROBE + || self->client->NPC_class == CLASS_INTERROGATOR + || self->client->NPC_class == CLASS_PROBE + || self->client->NPC_class == CLASS_MARK2 ) + { + G_FreeEntity( self ); + return; + } + + //FIXME: don't ever inflate back up? + self->maxs[2] = self->client->renderInfo.eyePoint[2] - self->currentOrigin[2] + 4; + if ( self->maxs[2] < -8 ) + { + self->maxs[2] = -8; + } + + if ( (self->NPC->aiFlags&NPCAI_HEAL_ROSH) ) + {//kothos twins' bodies are never removed + return; + } + + if ( self->client->NPC_class == CLASS_GALAKMECH ) + {//never disappears + return; + } + + if ( self->NPC && self->NPC->timeOfDeath <= level.time ) + { + self->NPC->timeOfDeath = level.time + 1000; + // Only do all of this nonsense for Scav boys ( and girls ) + /// if ( self->client->playerTeam == TEAM_SCAVENGERS || self->client->playerTeam == TEAM_KLINGON + // || self->client->playerTeam == TEAM_HIROGEN || self->client->playerTeam == TEAM_MALON ) + // should I check NPC_class here instead of TEAM ? - dmv + if( self->client->playerTeam == TEAM_ENEMY || self->client->NPC_class == CLASS_PROTOCOL ) + { + self->nextthink = level.time + FRAMETIME; // try back in a second + + if ( DistanceSquared( g_entities[0].currentOrigin, self->currentOrigin ) <= REMOVE_DISTANCE_SQR ) + { + return; + } + + if ( (InFOVFromPlayerView( self, 110, 90 )) ) // generous FOV check + { + if ( (NPC_ClearLOS( &g_entities[0], self->currentOrigin )) ) + { + return; + } + } + } + + //FIXME: there are some conditions - such as heavy combat - in which we want + // to remove the bodies... but in other cases it's just weird, like + // when they're right behind you in a closed room and when they've been + // placed as dead NPCs by a designer... + // For now we just assume that a corpse with no enemy was + // placed in the map as a corpse + if ( self->enemy ) + { + if ( self->client && self->client->ps.saberEntityNum > 0 && self->client->ps.saberEntityNum < ENTITYNUM_WORLD ) + { + gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum]; + if ( saberent ) + { + G_FreeEntity( saberent ); + } + } + G_FreeEntity( self ); + } + } + } +} + +/* +---------------------------------------- +NPC_RemoveBody + +Determines when it's ok to ditch the corpse +---------------------------------------- +*/ + +int BodyRemovalPadTime( gentity_t *ent ) +{ + int time; + + if ( !ent || !ent->client ) + return 0; +/* + switch ( ent->client->playerTeam ) + { + case TEAM_KLINGON: // no effect, we just remove them when the player isn't looking + case TEAM_SCAVENGERS: + case TEAM_HIROGEN: + case TEAM_MALON: + case TEAM_IMPERIAL: + case TEAM_STARFLEET: + time = 10000; // 15 secs. + break; + + case TEAM_BORG: + time = 2000; + break; + + case TEAM_STASIS: + return qtrue; + break; + + case TEAM_FORGE: + time = 1000; + break; + + case TEAM_BOTS: +// if (!Q_stricmp( ent->NPC_type, "mouse" )) +// { + time = 0; +// } +// else +// { +// time = 10000; +// } + break; + + case TEAM_8472: + time = 2000; + break; + + default: + // never go away + time = Q3_INFINITE; + break; + } +*/ + // team no longer indicates species/race, so in this case we'd use NPC_class, but + switch( ent->client->NPC_class ) + { + case CLASS_MOUSE: + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + //case CLASS_PROTOCOL: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_PROBE: + case CLASS_SEEKER: + case CLASS_REMOTE: + case CLASS_SENTRY: + case CLASS_INTERROGATOR: + time = 0; + break; + default: + // never go away + if ( g_corpseRemovalTime->integer <= 0 ) + { + time = Q3_INFINITE; + } + else + { + time = g_corpseRemovalTime->integer*1000; + } + break; + + } + + + return time; +} + + +/* +---------------------------------------- +NPC_RemoveBodyEffect + +Effect to be applied when ditching the corpse +---------------------------------------- +*/ + +static void NPC_RemoveBodyEffect(void) +{ +// vec3_t org; +// gentity_t *tent; + + if ( !NPC || !NPC->client || (NPC->s.eFlags & EF_NODRAW) ) + return; +/* + switch(NPC->client->playerTeam) + { + case TEAM_STARFLEET: + //FIXME: Starfleet beam out + break; + + case TEAM_BOTS: +// VectorCopy( NPC->currentOrigin, org ); +// org[2] -= 16; +// tent = G_TempEntity( org, EV_BOT_EXPLODE ); +// tent->owner = NPC; + + break; + + default: + break; + } +*/ + + + // team no longer indicates species/race, so in this case we'd use NPC_class, but + + // stub code + switch(NPC->client->NPC_class) + { + case CLASS_PROBE: + case CLASS_SEEKER: + case CLASS_REMOTE: + case CLASS_SENTRY: + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + //case CLASS_PROTOCOL: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_INTERROGATOR: + case CLASS_ATST: // yeah, this is a little weird, but for now I'm listing all droids + // VectorCopy( NPC->currentOrigin, org ); + // org[2] -= 16; + // tent = G_TempEntity( org, EV_BOT_EXPLODE ); + // tent->owner = NPC; + break; + default: + break; + } + + +} + + +/* +==================================================================== +void pitch_roll_for_slope (edict_t *forwhom, vec3_t *slope, vec3_t storeAngles ) + +MG + +This will adjust the pitch and roll of a monster to match +a given slope - if a non-'0 0 0' slope is passed, it will +use that value, otherwise it will use the ground underneath +the monster. If it doesn't find a surface, it does nothinh\g +and returns. +==================================================================== +*/ + +void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope, vec3_t storeAngles ) +{ + vec3_t slope; + vec3_t nvf, ovf, ovr, startspot, endspot, new_angles = { 0, 0, 0 }; + float pitch, mod, dot; + + //if we don't have a slope, get one + if( !pass_slope || VectorCompare( vec3_origin, pass_slope ) ) + { + trace_t trace; + + VectorCopy( forwhom->currentOrigin, startspot ); + startspot[2] += forwhom->mins[2] + 4; + VectorCopy( startspot, endspot ); + endspot[2] -= 300; + gi.trace( &trace, forwhom->currentOrigin, vec3_origin, vec3_origin, endspot, forwhom->s.number, MASK_SOLID ); +// if(trace_fraction>0.05&&forwhom.movetype==MOVETYPE_STEP) +// forwhom.flags(-)FL_ONGROUND; + + if ( trace.fraction >= 1.0 ) + return; + + if( !( &trace.plane ) ) + return; + + if ( VectorCompare( vec3_origin, trace.plane.normal ) ) + return; + + VectorCopy( trace.plane.normal, slope ); + } + else + { + VectorCopy( pass_slope, slope ); + } + + if ( forwhom->client && forwhom->client->NPC_class == CLASS_VEHICLE ) + {//special code for vehicles + Vehicle_t *pVeh = forwhom->m_pVehicle; + + vec3_t tempAngles; + tempAngles[PITCH] = tempAngles[ROLL] = 0; + tempAngles[YAW] = pVeh->m_vOrientation[YAW]; + AngleVectors( tempAngles, ovf, ovr, NULL ); + } + else + { + AngleVectors( forwhom->currentAngles, ovf, ovr, NULL ); + } + + vectoangles( slope, new_angles ); + pitch = new_angles[PITCH] + 90; + new_angles[ROLL] = new_angles[PITCH] = 0; + + AngleVectors( new_angles, nvf, NULL, NULL ); + + mod = DotProduct( nvf, ovr ); + + if ( mod<0 ) + mod = -1; + else + mod = 1; + + dot = DotProduct( nvf, ovf ); + + if ( storeAngles ) + { + storeAngles[PITCH] = dot * pitch; + storeAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + } + else if ( forwhom->client ) + { + forwhom->client->ps.viewangles[PITCH] = dot * pitch; + forwhom->client->ps.viewangles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + float oldmins2 = forwhom->mins[2]; + forwhom->mins[2] = -24 + 12 * fabs(forwhom->client->ps.viewangles[PITCH])/180.0f; + //FIXME: if it gets bigger, move up + if ( oldmins2 > forwhom->mins[2] ) + {//our mins is now lower, need to move up + //FIXME: trace? + forwhom->client->ps.origin[2] += (oldmins2 - forwhom->mins[2]); + forwhom->currentOrigin[2] = forwhom->client->ps.origin[2]; + gi.linkentity( forwhom ); + } + } + else + { + forwhom->currentAngles[PITCH] = dot * pitch; + forwhom->currentAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + } +} + +/* +void NPC_PostDeathThink( void ) +{ + float mostdist; + trace_t trace1, trace2, trace3, trace4, movetrace; + vec3_t org, endpos, startpos, forward, right; + int whichtrace = 0; + float cornerdist[4]; + qboolean frontbackbothclear = false; + qboolean rightleftbothclear = false; + + if( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE || !VectorCompare( vec3_origin, NPC->client->ps.velocity ) ) + { + if ( NPC->client->ps.groundEntityNum != ENTITYNUM_NONE && NPC->client->ps.friction == 1.0 )//check avelocity? + { + pitch_roll_for_slope( NPC ); + } + + return; + } + + cornerdist[FRONT] = cornerdist[BACK] = cornerdist[RIGHT] = cornerdist[LEFT] = 0.0f; + + mostdist = MIN_DROP_DIST; + + AngleVectors( NPC->currentAngles, forward, right, NULL ); + VectorCopy( NPC->currentOrigin, org ); + org[2] += NPC->mins[2]; + + VectorMA( org, NPC->dead_size, forward, startpos ); + VectorCopy( startpos, endpos ); + endpos[2] -= 128; + gi.trace( &trace1, startpos, vec3_origin, vec3_origin, endpos, NPC->s.number, MASK_SOLID, ); + if( !trace1.allsolid && !trace1.startsolid ) + { + cornerdist[FRONT] = trace1.fraction; + if ( trace1.fraction > mostdist ) + { + mostdist = trace1.fraction; + whichtrace = 1; + } + } + + VectorMA( org, -NPC->dead_size, forward, startpos ); + VectorCopy( startpos, endpos ); + endpos[2] -= 128; + gi.trace( &trace2, startpos, vec3_origin, vec3_origin, endpos, NPC->s.number, MASK_SOLID ); + if( !trace2.allsolid && !trace2.startsolid ) + { + cornerdist[BACK] = trace2.fraction; + if ( trace2.fraction > mostdist ) + { + mostdist = trace2.fraction; + whichtrace = 2; + } + } + + VectorMA( org, NPC->dead_size/2, right, startpos ); + VectorCopy( startpos, endpos ); + endpos[2] -= 128; + gi.trace( &trace3, startpos, vec3_origin, vec3_origin, endpos, NPC->s.number, MASK_SOLID ); + if ( !trace3.allsolid && !trace3.startsolid ) + { + cornerdist[RIGHT] = trace3.fraction; + if ( trace3.fraction>mostdist ) + { + mostdist = trace3.fraction; + whichtrace = 3; + } + } + + VectorMA( org, -NPC->dead_size/2, right, startpos ); + VectorCopy( startpos, endpos ); + endpos[2] -= 128; + gi.trace( &trace4, startpos, vec3_origin, vec3_origin, endpos, NPC->s.number, MASK_SOLID ); + if ( !trace4.allsolid && !trace4.startsolid ) + { + cornerdist[LEFT] = trace4.fraction; + if ( trace4.fraction > mostdist ) + { + mostdist = trace4.fraction; + whichtrace = 4; + } + } + + //OK! Now if two opposite sides are hanging, use a third if any, else, do nothing + if ( cornerdist[FRONT] > MIN_DROP_DIST && cornerdist[BACK] > MIN_DROP_DIST ) + frontbackbothclear = true; + + if ( cornerdist[RIGHT] > MIN_DROP_DIST && cornerdist[LEFT] > MIN_DROP_DIST ) + rightleftbothclear = true; + + if ( frontbackbothclear && rightleftbothclear ) + return; + + if ( frontbackbothclear ) + { + if ( cornerdist[RIGHT] > MIN_DROP_DIST ) + whichtrace = 3; + else if ( cornerdist[LEFT] > MIN_DROP_DIST ) + whichtrace = 4; + else + return; + } + + if ( rightleftbothclear ) + { + if ( cornerdist[FRONT] > MIN_DROP_DIST ) + whichtrace = 1; + else if ( cornerdist[BACK] > MIN_DROP_DIST ) + whichtrace = 2; + else + return; + } + + switch ( whichtrace ) + {//check for stuck + case 1: + VectorMA( NPC->currentOrigin, NPC->maxs[0], forward, endpos ); + gi.trace( &movetrace, NPC->currentOrigin, NPC->mins, NPC->maxs, endpos, NPC->s.number, MASK_MONSTERSOLID ); + if ( movetrace.allsolid || movetrace.startsolid || movetrace.fraction < 1.0 ) + if ( canmove( movetrace.ent ) ) + whichtrace = -1; + else + whichtrace = 0; + break; + case 2: + VectorMA( NPC->currentOrigin, -NPC->maxs[0], forward, endpos ); + gi.trace( &movetrace, NPC->currentOrigin, NPC->mins, NPC->maxs, endpos, NPC->s.number, MASK_MONSTERSOLID ); + if ( movetrace.allsolid || movetrace.startsolid || movetrace.fraction < 1.0 ) + if ( canmove( movetrace.ent ) ) + whichtrace = -1; + else + whichtrace = 0; + break; + case 3: + VectorMA( NPC->currentOrigin, NPC->maxs[0], right, endpos ); + gi.trace( &movetrace, NPC->currentOrigin, NPC->mins, NPC->maxs, endpos, NPC->s.number, MASK_MONSTERSOLID ); + if ( movetrace.allsolid || movetrace.startsolid || movetrace.fraction < 1.0 ) + if ( canmove( movetrace.ent ) ) + whichtrace = -1; + else + whichtrace = 0; + break; + case 4: + VectorMA( NPC->currentOrigin, -NPC->maxs[0], right, endpos ); + gi.trace( &movetrace, NPC->currentOrigin, NPC->mins, NPC->maxs, endpos, NPC->s.number, MASK_MONSTERSOLID ); + if (movetrace.allsolid || movetrace.startsolid || movetrace.fraction < 1.0 ) + if ( canmove( movetrace.ent ) ) + whichtrace = -1; + else + whichtrace = 0; + break; + } + + switch ( whichtrace ) + { + case 1: + VectorMA( NPC->client->ps.velocity, 200, forward, NPC->client->ps.velocity ); + if ( trace1.fraction >= 0.9 ) + { +//can't anymore, origin not in center of deathframe! +// NPC->avelocity[PITCH] = -300; + NPC->client->ps.friction = 1.0; + } + else + { + pitch_roll_for_slope( NPC, &trace1.plane.normal ); + NPC->client->ps.friction = trace1.plane.normal[2] * 0.1; + } + return; + break; + + case 2: + VectorMA( NPC->client->ps.velocity, -200, forward, NPC->client->ps.velocity ); + if(trace2.fraction >= 0.9) + { +//can't anymore, origin not in center of deathframe! +// NPC->avelocity[PITCH] = 300; + NPC->client->ps.friction = 1.0; + } + else + { + pitch_roll_for_slope( NPC, &trace2.plane.normal ); + NPC->client->ps.friction = trace2.plane.normal[2] * 0.1; + } + return; + break; + + case 3: + VectorMA( NPC->client->ps.velocity, 200, right, NPC->client->ps.velocity ); + if ( trace3.fraction >= 0.9 ) + { +//can't anymore, origin not in center of deathframe! +// NPC->avelocity[ROLL] = -300; + NPC->client->ps.friction = 1.0; + } + else + { + pitch_roll_for_slope( NPC, &trace3.plane.normal ); + NPC->client->ps.friction = trace3.plane.normal[2] * 0.1; + } + return; + break; + + case 4: + VectorMA( NPC->client->ps.velocity, -200, right, NPC->client->ps.velocity ); + if ( trace4.fraction >= 0.9 ) + { +//can't anymore, origin not in center of deathframe! +// NPC->avelocity[ROLL] = 300; + NPC->client->ps.friction = 1.0; + } + else + { + pitch_roll_for_slope( NPC, &trace4.plane.normal ); + NPC->client->ps.friction = trace4.plane.normal[2] * 0.1; + } + return; + break; + } + + //on solid ground + if ( whichtrace == -1 ) + { + return; + } + NPC->client->ps.friction = 1.0; + + //VectorClear( NPC->avelocity ); + pitch_roll_for_slope( NPC ); + + //gi.linkentity (NPC); +} +*/ + +/* +---------------------------------------- +DeadThink +---------------------------------------- +*/ +static void DeadThink ( void ) +{ + trace_t trace; + //HACKHACKHACKHACKHACK + //We should really have a seperate G2 bounding box (seperate from the physics bbox) for G2 collisions only + //FIXME: don't ever inflate back up? + //GAH! With Ragdoll, they get stuck in the ceiling + float oldMaxs2 = NPC->maxs[2]; + NPC->maxs[2] = NPC->client->renderInfo.eyePoint[2] - NPC->currentOrigin[2] + 4; + if ( NPC->maxs[2] < -8 ) + { + NPC->maxs[2] = -8; + } + if ( NPC->maxs[2] > oldMaxs2 ) + {//inflating maxs, make sure we're not inflating into solid + gi.trace (&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + {//must be inflating + NPC->maxs[2] = oldMaxs2; + } + } + /* + { + if ( VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//not flying through the air + if ( NPC->mins[0] > -32 ) + { + NPC->mins[0] -= 1; + gi.trace (&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->mins[0] += 1; + } + } + if ( NPC->maxs[0] < 32 ) + { + NPC->maxs[0] += 1; + gi.trace (&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->maxs[0] -= 1; + } + } + if ( NPC->mins[1] > -32 ) + { + NPC->mins[1] -= 1; + gi.trace (&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->mins[1] += 1; + } + } + if ( NPC->maxs[1] < 32 ) + { + NPC->maxs[1] += 1; + gi.trace (&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, NPC->currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->maxs[1] -= 1; + } + } + } + } + //HACKHACKHACKHACKHACK + */ + + + //FIXME: tilt and fall off of ledges? + //NPC_PostDeathThink(); + + /* + if ( !NPCInfo->timeOfDeath && NPC->client != NULL && NPCInfo != NULL ) + { + //haven't finished death anim yet and were NOT given a specific amount of time to wait before removal + int legsAnim = NPC->client->ps.legsAnim; + animation_t *animations = knownAnimFileSets[NPC->client->clientInfo.animFileIndex].animations; + + NPC->bounceCount = -1; // This is a cheap hack for optimizing the pointcontents check below + + //ghoul doesn't tell us this anymore + //if ( NPC->client->renderInfo.legsFrame == animations[legsAnim].firstFrame + (animations[legsAnim].numFrames - 1) ) + { + //reached the end of the death anim + NPCInfo->timeOfDeath = level.time + BodyRemovalPadTime( NPC ); + } + } + else + */ + { + //death anim done (or were given a specific amount of time to wait before removal), wait the requisite amount of time them remove + if ( level.time >= NPCInfo->timeOfDeath + BodyRemovalPadTime( NPC ) ) + { + if ( NPC->client->ps.eFlags & EF_NODRAW ) + { + if ( !IIcarusInterface::GetIcarus()->IsRunning( NPC->m_iIcarusID ) ) + { + NPC->e_ThinkFunc = thinkF_G_FreeEntity; + NPC->nextthink = level.time + FRAMETIME; + } + } + else + { + // Start the body effect first, then delay 400ms before ditching the corpse + NPC_RemoveBodyEffect(); + + //FIXME: keep it running through physics somehow? + NPC->e_ThinkFunc = thinkF_NPC_RemoveBody; + NPC->nextthink = level.time + FRAMETIME/2; + // if ( NPC->client->playerTeam == TEAM_FORGE ) + // NPCInfo->timeOfDeath = level.time + FRAMETIME * 8; + // else if ( NPC->client->playerTeam == TEAM_BOTS ) + class_t npc_class = NPC->client->NPC_class; + // check for droids + if ( npc_class == CLASS_SEEKER || npc_class == CLASS_REMOTE || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || + npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || + npc_class == CLASS_MARK2 || npc_class == CLASS_SENTRY )//npc_class == CLASS_PROTOCOL || + { + NPC->client->ps.eFlags |= EF_NODRAW; + NPCInfo->timeOfDeath = level.time + FRAMETIME * 8; + } + else + { + NPCInfo->timeOfDeath = level.time + FRAMETIME * 4; + } + } + return; + } + } + + // If the player is on the ground and the resting position contents haven't been set yet...(BounceCount tracks the contents) + if ( NPC->bounceCount < 0 && NPC->s.groundEntityNum >= 0 ) + { + // if client is in a nodrop area, make him/her nodraw + int contents = NPC->bounceCount = gi.pointcontents( NPC->currentOrigin, -1 ); + + if ( ( contents & CONTENTS_NODROP ) ) + { + NPC->client->ps.eFlags |= EF_NODRAW; + } + } + + CorpsePhysics( NPC ); +} + +/* +=============== +SetNPCGlobals + +local function to set globals used throughout the AI code +=============== +*/ +void SetNPCGlobals( gentity_t *ent ) +{ + NPC = ent; + NPCInfo = ent->NPC; + client = ent->client; + memset( &ucmd, 0, sizeof( usercmd_t ) ); +} + +gentity_t *_saved_NPC; +gNPC_t *_saved_NPCInfo; +gclient_t *_saved_client; +usercmd_t _saved_ucmd; + +void SaveNPCGlobals() +{ + _saved_NPC = NPC; + _saved_NPCInfo = NPCInfo; + _saved_client = client; + memcpy( &_saved_ucmd, &ucmd, sizeof( usercmd_t ) ); +} + +void RestoreNPCGlobals() +{ + NPC = _saved_NPC; + NPCInfo = _saved_NPCInfo; + client = _saved_client; + memcpy( &ucmd, &_saved_ucmd, sizeof( usercmd_t ) ); +} + +//We MUST do this, other funcs were using NPC illegally when "self" wasn't the global NPC +void ClearNPCGlobals( void ) +{ + NPC = NULL; + NPCInfo = NULL; + client = NULL; +} +//=============== + +extern qboolean showBBoxes; +vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0}; +vec3_t NPCDEBUG_GREEN = {0.0, 1.0, 0.0}; +vec3_t NPCDEBUG_BLUE = {0.0, 0.0, 1.0}; +vec3_t NPCDEBUG_LIGHT_BLUE = {0.3f, 0.7f, 1.0}; +extern void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ); +extern void CG_Line( vec3_t start, vec3_t end, vec3_t color, float alpha ); +extern void CG_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color ); + +void NPC_ShowDebugInfo (void) +{ + if ( showBBoxes ) + { + gentity_t *found = NULL; + vec3_t mins, maxs; + + //do player, too + VectorAdd( player->currentOrigin, player->mins, mins ); + VectorAdd( player->currentOrigin, player->maxs, maxs ); + CG_Cube( mins, maxs, NPCDEBUG_RED, 0.25 ); + //do NPCs + while( (found = G_Find( found, FOFS(classname), "NPC" ) ) != NULL ) + { + if ( gi.inPVS( found->currentOrigin, g_entities[0].currentOrigin ) ) + { + VectorAdd( found->currentOrigin, found->mins, mins ); + VectorAdd( found->currentOrigin, found->maxs, maxs ); + CG_Cube( mins, maxs, NPCDEBUG_RED, 0.25 ); + } + } + } +} + +void NPC_ApplyScriptFlags (void) +{ + if ( NPCInfo->scriptFlags & SCF_CROUCHED ) + { + if ( NPCInfo->charmedTime > level.time && (ucmd.forwardmove || ucmd.rightmove) ) + {//ugh, if charmed and moving, ignore the crouched command + } + else + { + ucmd.upmove = -127; + } + } + + if(NPCInfo->scriptFlags & SCF_RUNNING) + { + ucmd.buttons &= ~BUTTON_WALKING; + } + else if(NPCInfo->scriptFlags & SCF_WALKING) + { + if ( NPCInfo->charmedTime > level.time && (ucmd.forwardmove || ucmd.rightmove) ) + {//ugh, if charmed and moving, ignore the walking command + } + else + { + ucmd.buttons |= BUTTON_WALKING; + } + } +/* + if(NPCInfo->scriptFlags & SCF_CAREFUL) + { + ucmd.buttons |= BUTTON_CAREFUL; + } +*/ + if(NPCInfo->scriptFlags & SCF_LEAN_RIGHT) + { + ucmd.buttons |= BUTTON_USE; + ucmd.rightmove = 127; + ucmd.forwardmove = 0; + ucmd.upmove = 0; + } + else if(NPCInfo->scriptFlags & SCF_LEAN_LEFT) + { + ucmd.buttons |= BUTTON_USE; + ucmd.rightmove = -127; + ucmd.forwardmove = 0; + ucmd.upmove = 0; + } + + if ( (NPCInfo->scriptFlags & SCF_ALT_FIRE) && (ucmd.buttons & BUTTON_ATTACK) ) + {//Use altfire instead + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + + // only removes NPC when it's safe too (Player is out of PVS) + if( NPCInfo->scriptFlags & SCF_SAFE_REMOVE ) + { + // take from BSRemove + if( !gi.inPVS( NPC->currentOrigin, g_entities[0].currentOrigin ) )//FIXME: use cg.vieworg? + { + G_UseTargets2( NPC, NPC, NPC->target3 ); + NPC->s.eFlags |= EF_NODRAW; + NPC->svFlags &= ~SVF_NPC; + NPC->s.eType = ET_INVISIBLE; + NPC->contents = 0; + NPC->health = 0; + NPC->targetname = NULL; + + //Disappear in half a second + NPC->e_ThinkFunc = thinkF_G_FreeEntity; + NPC->nextthink = level.time + FRAMETIME; + }//FIXME: else allow for out of FOV??? + + } +} + + +extern qboolean JET_Flying( gentity_t *self ); +extern void JET_FlyStart( gentity_t *self ); +extern void JET_FlyStop( gentity_t *self ); + +void NPC_HandleAIFlags (void) +{ + // Update Guys With Jet Packs + //---------------------------- + if (NPCInfo->scriptFlags & SCF_FLY_WITH_JET) + { + bool ShouldFly = !!(NPCInfo->aiFlags & NPCAI_FLY); + bool IsFlying = !!(JET_Flying(NPC)); + bool IsInTheAir = (NPC->client->ps.groundEntityNum==ENTITYNUM_NONE); + + if (IsFlying) + { + // Don't Stop Flying Until Near The Ground + //----------------------------------------- + if (IsInTheAir) + { + vec3_t ground; + trace_t trace; + VectorCopy(NPC->currentOrigin, ground); + ground[2] -= 60.0f; + gi.trace(&trace, NPC->currentOrigin, 0, 0, ground, NPC->s.number, NPC->clipmask); + + IsInTheAir = (!trace.allsolid && !trace.startsolid && trace.fraction>0.9f); + } + + // If Flying, Remember The Last Time + //----------------------------------- + if (IsInTheAir) + { + NPC->lastInAirTime = level.time; + ShouldFly = true; + } + + + // Auto Turn Off Jet Pack After 1 Second On The Ground + //----------------------------------------------------- + else if (!ShouldFly && (level.time - NPC->lastInAirTime)>3000) + { + NPCInfo->aiFlags &= ~NPCAI_FLY; + } + } + + + // If We Should Be Flying And Are Not, Start Er Up + //------------------------------------------------- + if (ShouldFly && !IsFlying) + { + JET_FlyStart(NPC); // EVENTUALLY, Remove All Other Calls + } + + // Otherwise, If Needed, Shut It Off + //----------------------------------- + else if (!ShouldFly && IsFlying) + { + JET_FlyStop(NPC); // EVENTUALLY, Remove All Other Calls + } + } + + //FIXME: make these flags checks a function call like NPC_CheckAIFlagsAndTimers + if ( NPCInfo->aiFlags & NPCAI_LOST ) + {//Print that you need help! + //FIXME: shouldn't remove this just yet if cg_draw needs it + NPCInfo->aiFlags &= ~NPCAI_LOST; + + /* + if ( showWaypoints ) + { + Quake3Game()->DebugPrint(WL_WARNING, "%s can't navigate to target %s (my wp: %d, goal wp: %d)\n", NPC->targetname, NPCInfo->goalEntity->targetname, NPC->waypoint, NPCInfo->goalEntity->waypoint ); + } + */ + + if ( NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) + {//We can't nav to our enemy + //Drop enemy and see if we should search for him + NPC_LostEnemyDecideChase(); + } + } + + //been told to play a victory sound after a delay + if ( NPCInfo->greetingDebounceTime && NPCInfo->greetingDebounceTime < level.time ) + { + G_AddVoiceEvent( NPC, Q_irand(EV_VICTORY1, EV_VICTORY3), Q_irand( 2000, 4000 ) ); + NPCInfo->greetingDebounceTime = 0; + } + + if ( NPCInfo->ffireCount > 0 ) + { + if ( NPCInfo->ffireFadeDebounce < level.time ) + { + NPCInfo->ffireCount--; + //Com_Printf( "drop: %d < %d\n", NPCInfo->ffireCount, 3+((2-g_spskill->integer)*2) ); + NPCInfo->ffireFadeDebounce = level.time + 3000; + } + } +} + +void NPC_AvoidWallsAndCliffs (void) +{ +/* + vec3_t forward, right, testPos, angles, mins; + trace_t trace; + float fwdDist, rtDist; + //FIXME: set things like this forward dir once at the beginning + //of a frame instead of over and over again + if ( NPCInfo->aiFlags & NPCAI_NO_COLL_AVOID ) + { + return; + } + + if ( ucmd.upmove > 0 || NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//Going to jump or in the air + return; + } + + if ( !ucmd.forwardmove && !ucmd.rightmove ) + { + return; + } + + if ( fabs( AngleDelta( NPC->currentAngles[YAW], NPCInfo->desiredYaw ) ) < 5.0 )//!ucmd.angles[YAW] ) + {//Not turning much, don't do this + //NOTE: Should this not happen only if you're not turning AT ALL? + // You could be turning slowly but moving fast, so that would + // still let you walk right off a cliff... + //NOTE: Or maybe it is a good idea to ALWAYS do this, regardless + // of whether ot not we're turning? But why would we be walking + // straight into a wall or off a cliff unless we really wanted to? + return; + } + + VectorCopy( NPC->mins, mins ); + mins[2] += STEPSIZE; + angles[YAW] = NPC->client->ps.viewangles[YAW];//Add ucmd.angles[YAW]? + AngleVectors( angles, forward, right, NULL ); + fwdDist = ((float)ucmd.forwardmove)/16.0f; + rtDist = ((float)ucmd.rightmove)/16.0f; + VectorMA( NPC->currentOrigin, fwdDist, forward, testPos ); + VectorMA( testPos, rtDist, right, testPos ); + gi.trace( &trace, NPC->currentOrigin, mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid || trace.startsolid || trace.fraction < 1.0 ) + {//Going to bump into something, don't move, just turn + ucmd.forwardmove = 0; + ucmd.rightmove = 0; + return; + } + + VectorCopy(trace.endpos, testPos); + testPos[2] -= 128; + + gi.trace( &trace, trace.endpos, mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid || trace.startsolid || trace.fraction < 1.0 ) + {//Not going off a cliff + return; + } + + //going to fall at least 128, don't move, just turn... is this bad, though? What if we want them to drop off? + ucmd.forwardmove = 0; + ucmd.rightmove = 0; + return; +*/ +} + +void NPC_CheckAttackScript(void) +{ + if(!(ucmd.buttons & BUTTON_ATTACK)) + { + return; + } + + G_ActivateBehavior(NPC, BSET_ATTACK); +} + +float NPC_MaxDistSquaredForWeapon (void); +void NPC_CheckAttackHold(void) +{ + vec3_t vec; + + // If they don't have an enemy they shouldn't hold their attack anim. + if ( !NPC->enemy ) + { + NPCInfo->attackHoldTime = 0; + return; + } + + //FIXME: need to tie this into AI somehow? + VectorSubtract(NPC->enemy->currentOrigin, NPC->currentOrigin, vec); + if( VectorLengthSquared(vec) > NPC_MaxDistSquaredForWeapon() ) + { + NPCInfo->attackHoldTime = 0; + } + else if( NPCInfo->attackHoldTime && NPCInfo->attackHoldTime > level.time ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + else if ( ( NPCInfo->attackHold ) && ( ucmd.buttons & BUTTON_ATTACK ) ) + { + NPCInfo->attackHoldTime = level.time + NPCInfo->attackHold; + } + else + { + NPCInfo->attackHoldTime = 0; + } +} + +/* +void NPC_KeepCurrentFacing(void) + +Fills in a default ucmd to keep current angles facing +*/ +void NPC_KeepCurrentFacing(void) +{ + if(!ucmd.angles[YAW]) + { + ucmd.angles[YAW] = ANGLE2SHORT( client->ps.viewangles[YAW] ) - client->ps.delta_angles[YAW]; + } + + if(!ucmd.angles[PITCH]) + { + ucmd.angles[PITCH] = ANGLE2SHORT( client->ps.viewangles[PITCH] ) - client->ps.delta_angles[PITCH]; + } +} + +/* +------------------------- +NPC_BehaviorSet_Charmed +------------------------- +*/ + +void NPC_BehaviorSet_Charmed( int bState ) +{ + switch( bState ) + { + case BS_FOLLOW_LEADER://# 40: Follow your leader and shoot any enemies you come across + NPC_BSFollowLeader(); + break; + case BS_REMOVE: + NPC_BSRemove(); + break; + case BS_SEARCH: //# 43: Using current waypoint as a base, search the immediate branches of waypoints for enemies + NPC_BSSearch(); + break; + case BS_WANDER: //# 46: Wander down random waypoint paths + NPC_BSWander(); + break; + case BS_FLEE: + NPC_BSFlee(); + break; + default: + case BS_DEFAULT://whatever + NPC_BSDefault(); + break; + } +} +/* +------------------------- +NPC_BehaviorSet_Default +------------------------- +*/ + +void NPC_BehaviorSet_Default( int bState ) +{ + switch( bState ) + { + case BS_ADVANCE_FIGHT://head toward captureGoal, shoot anything that gets in the way + NPC_BSAdvanceFight (); + break; + case BS_SLEEP://Follow a path, looking for enemies + NPC_BSSleep (); + break; + case BS_FOLLOW_LEADER://# 40: Follow your leader and shoot any enemies you come across + NPC_BSFollowLeader(); + break; + case BS_JUMP: //41: Face navgoal and jump to it. + NPC_BSJump(); + break; + case BS_REMOVE: + NPC_BSRemove(); + break; + case BS_SEARCH: //# 43: Using current waypoint as a base, search the immediate branches of waypoints for enemies + NPC_BSSearch(); + break; + case BS_NOCLIP: + NPC_BSNoClip(); + break; + case BS_WANDER: //# 46: Wander down random waypoint paths + NPC_BSWander(); + break; + case BS_FLEE: + NPC_BSFlee(); + break; + case BS_WAIT: + NPC_BSWait(); + break; + case BS_CINEMATIC: + NPC_BSCinematic(); + break; + default: + case BS_DEFAULT://whatever + NPC_BSDefault(); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Interrogator +------------------------- +*/ +void NPC_BehaviorSet_Interrogator( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSInterrogator_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +void NPC_BSImperialProbe_Attack( void ); +void NPC_BSImperialProbe_Patrol( void ); +void NPC_BSImperialProbe_Wait(void); + +/* +------------------------- +NPC_BehaviorSet_ImperialProbe +------------------------- +*/ +void NPC_BehaviorSet_ImperialProbe( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSImperialProbe_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + + +void NPC_BSSeeker_Default( void ); + +/* +------------------------- +NPC_BehaviorSet_Seeker +------------------------- +*/ +void NPC_BehaviorSet_Seeker( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + default: + NPC_BSSeeker_Default(); + break; + } +} + +void NPC_BSRemote_Default( void ); + +/* +------------------------- +NPC_BehaviorSet_Remote +------------------------- +*/ +void NPC_BehaviorSet_Remote( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSRemote_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +void NPC_BSSentry_Default( void ); + +/* +------------------------- +NPC_BehaviorSet_Sentry +------------------------- +*/ +void NPC_BehaviorSet_Sentry( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSSentry_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Grenadier +------------------------- +*/ +void NPC_BehaviorSet_Grenadier( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSGrenadier_Default(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Tusken +------------------------- +*/ +void NPC_BehaviorSet_Tusken( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSTusken_Default(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Sniper +------------------------- +*/ +void NPC_BehaviorSet_Sniper( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSSniper_Default(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} +/* +------------------------- +NPC_BehaviorSet_Stormtrooper +------------------------- +*/ + +void NPC_BehaviorSet_Stormtrooper( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSST_Default(); + break; + + case BS_INVESTIGATE: + NPC_BSST_Investigate(); + break; + + case BS_SLEEP: + NPC_BSST_Sleep(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Jedi +------------------------- +*/ + +void NPC_BehaviorSet_Jedi( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_INVESTIGATE://WTF???!! + case BS_DEFAULT: + NPC_BSJedi_Default(); + break; + + case BS_FOLLOW_LEADER: + NPC_BSJedi_FollowLeader(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +qboolean G_JediInNormalAI( gentity_t *ent ) +{//NOTE: should match above func's switch! + //check our bState + bState_t bState = G_CurrentBState( ent->NPC ); + switch ( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_INVESTIGATE: + case BS_DEFAULT: + case BS_FOLLOW_LEADER: + return qtrue; + break; + } + return qfalse; +} + +/* +------------------------- +NPC_BehaviorSet_Droid +------------------------- +*/ +void NPC_BehaviorSet_Droid( int bState ) +{ + switch( bState ) + { + case BS_DEFAULT: + case BS_STAND_GUARD: + case BS_PATROL: + NPC_BSDroid_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Mark1 +------------------------- +*/ +void NPC_BehaviorSet_Mark1( int bState ) +{ + switch( bState ) + { + case BS_DEFAULT: + case BS_STAND_GUARD: + case BS_PATROL: + NPC_BSMark1_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Mark2 +------------------------- +*/ +void NPC_BehaviorSet_Mark2( int bState ) +{ + switch( bState ) + { + case BS_DEFAULT: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + NPC_BSMark2_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_ATST +------------------------- +*/ +void NPC_BehaviorSet_ATST( int bState ) +{ + switch( bState ) + { + case BS_DEFAULT: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + NPC_BSATST_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_MineMonster +------------------------- +*/ +void NPC_BehaviorSet_MineMonster( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSMineMonster_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Howler +------------------------- +*/ +void NPC_BehaviorSet_Howler( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSHowler_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Rancor +------------------------- +*/ +void NPC_BehaviorSet_Rancor( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSRancor_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Wampa +------------------------- +*/ +void NPC_BehaviorSet_Wampa( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSWampa_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_SandCreature +------------------------- +*/ +void NPC_BehaviorSet_SandCreature( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSSandCreature_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Droid +------------------------- +*/ +// Added 01/21/03 by AReis. +void NPC_BehaviorSet_Animal( int bState ) +{ + switch( bState ) + { + case BS_DEFAULT: + case BS_STAND_GUARD: + case BS_PATROL: + NPC_BSAnimal_Default(); + + //NPC_BSDroid_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_RunBehavior +------------------------- +*/ +extern void NPC_BSEmplaced( void ); +extern qboolean NPC_CheckSurrender( void ); +extern void NPC_BSRT_Default( void ); +extern void NPC_BSCivilian_Default( int bState ); +extern void NPC_BSSD_Default( void ); +extern void NPC_BehaviorSet_Trooper( int bState ); +extern bool NPC_IsTrooper( gentity_t *ent ); +extern bool Pilot_MasterUpdate(); + +void NPC_RunBehavior( int team, int bState ) +{ + qboolean dontSetAim = qfalse; + + // + if ( bState == BS_CINEMATIC ) + { + NPC_BSCinematic(); + } + else if ( (NPCInfo->scriptFlags&SCF_PILOT) && Pilot_MasterUpdate()) + { + return; + } + else if ( NPC_JumpBackingUp() ) + { + return; + } + else if ( !TIMER_Done(NPC, "DEMP2_StunTime")) + { + NPC_UpdateAngles(qtrue, qtrue); + return; + } + else if ( NPC->client->ps.weapon == WP_EMPLACED_GUN ) + { + NPC_BSEmplaced(); + G_CheckCharmed( NPC ); + return; + } + else if ( NPC->client->NPC_class == CLASS_HOWLER ) + { + NPC_BehaviorSet_Howler( bState ); + return; + } + else if ( Jedi_CultistDestroyer( NPC ) ) + { + NPC_BSJedi_Default(); + dontSetAim = qtrue; + } + else if ( NPC->client->NPC_class == CLASS_SABER_DROID ) + {//saber droid + NPC_BSSD_Default(); + } + else if ( NPC->client->ps.weapon == WP_SABER ) + {//jedi + NPC_BehaviorSet_Jedi( bState ); + dontSetAim = qtrue; + } + else if ( NPC->client->NPC_class == CLASS_REBORN && NPC->client->ps.weapon == WP_MELEE ) + {//force-only reborn + NPC_BehaviorSet_Jedi( bState ); + dontSetAim = qtrue; + } + else if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + Boba_Update(); + if (NPCInfo->surrenderTime) + { + Boba_Flee(); + } + else + { + if (!Boba_Tactics()) + { + if ( Boba_Flying( NPC ) ) + { + NPC_BehaviorSet_Seeker(bState); + } + else + { + NPC_BehaviorSet_Jedi( bState ); + } + } + } + dontSetAim = qtrue; + } + else if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER ) + {//bounty hunter + if ( RT_Flying( NPC ) || NPC->enemy != NULL ) + { + NPC_BSRT_Default(); + } + else + { + NPC_BehaviorSet_Stormtrooper( bState ); + } + G_CheckCharmed( NPC ); + dontSetAim = qtrue; + } + else if ( NPC->client->NPC_class == CLASS_RANCOR ) + { + NPC_BehaviorSet_Rancor( bState ); + } + else if ( NPC->client->NPC_class == CLASS_SAND_CREATURE ) + { + NPC_BehaviorSet_SandCreature( bState ); + } + else if ( NPC->client->NPC_class == CLASS_WAMPA ) + { + NPC_BehaviorSet_Wampa( bState ); + G_CheckCharmed( NPC ); + } + else if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) + {//being forced to march + NPC_BSDefault(); + } + else if ( NPC->client->ps.weapon == WP_TUSKEN_RIFLE ) + { + if ( (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + { + NPC_BehaviorSet_Sniper( bState ); + G_CheckCharmed( NPC ); + return; + } + else + { + NPC_BehaviorSet_Tusken( bState ); + G_CheckCharmed( NPC ); + return; + } + } + else if ( NPC->client->ps.weapon == WP_TUSKEN_STAFF ) + { + NPC_BehaviorSet_Tusken( bState ); + G_CheckCharmed( NPC ); + return; + } + else if ( NPC->client->ps.weapon == WP_NOGHRI_STICK ) + { + NPC_BehaviorSet_Stormtrooper( bState ); + G_CheckCharmed( NPC ); + } + else + { + switch( team ) + { + + // case TEAM_SCAVENGERS: + // case TEAM_IMPERIAL: + // case TEAM_KLINGON: + // case TEAM_HIROGEN: + // case TEAM_MALON: + // not sure if TEAM_ENEMY is appropriate here, I think I should be using NPC_class to check for behavior - dmv + case TEAM_ENEMY: + // special cases for enemy droids + switch( NPC->client->NPC_class) + { + case CLASS_ATST: + NPC_BehaviorSet_ATST( bState ); + return; + case CLASS_PROBE: + NPC_BehaviorSet_ImperialProbe(bState); + return; + case CLASS_REMOTE: + NPC_BehaviorSet_Remote( bState ); + return; + case CLASS_SENTRY: + NPC_BehaviorSet_Sentry(bState); + return; + case CLASS_INTERROGATOR: + NPC_BehaviorSet_Interrogator( bState ); + return; + case CLASS_MINEMONSTER: + NPC_BehaviorSet_MineMonster( bState ); + return; + case CLASS_HOWLER: + NPC_BehaviorSet_Howler( bState ); + return; + case CLASS_RANCOR: + NPC_BehaviorSet_Rancor( bState ); + return; + case CLASS_SAND_CREATURE: + NPC_BehaviorSet_SandCreature( bState ); + return; + case CLASS_MARK1: + NPC_BehaviorSet_Mark1( bState ); + return; + case CLASS_MARK2: + NPC_BehaviorSet_Mark2( bState ); + return; + } + + + if (NPC->client->NPC_class==CLASS_ASSASSIN_DROID) + { + BubbleShield_Update(); + } + + if (NPC_IsTrooper(NPC)) + { + NPC_BehaviorSet_Trooper( bState); + return; + } + + if ( NPC->enemy && NPC->client->ps.weapon == WP_NONE && bState != BS_HUNT_AND_KILL && !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//if in battle and have no weapon, run away, fixme: when in BS_HUNT_AND_KILL, they just stand there + if ( bState != BS_FLEE ) + { + NPC_StartFlee( NPC->enemy, NPC->enemy->currentOrigin, AEL_DANGER_GREAT, 5000, 10000 ); + } + else + { + NPC_BSFlee(); + } + return; + } + if ( NPC->client->ps.weapon == WP_SABER ) + {//special melee exception + NPC_BehaviorSet_Default( bState ); + return; + } + if ( NPC->client->ps.weapon == WP_DISRUPTOR && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//a sniper + NPC_BehaviorSet_Sniper( bState ); + return; + } + if ( NPC->client->ps.weapon == WP_THERMAL + || NPC->client->ps.weapon == WP_MELEE )//FIXME: separate AI for melee fighters + {//a grenadier + NPC_BehaviorSet_Grenadier( bState ); + return; + } + if ( NPC_CheckSurrender() ) + { + return; + } + NPC_BehaviorSet_Stormtrooper( bState ); + break; + + case TEAM_NEUTRAL: + + // special cases for enemy droids + if ( NPC->client->NPC_class == CLASS_PROTOCOL ) + { + NPC_BehaviorSet_Default(bState); + } + else if ( NPC->client->NPC_class == CLASS_UGNAUGHT + || NPC->client->NPC_class == CLASS_JAWA ) + {//others, too? + NPC_BSCivilian_Default( bState ); + return; + } + // Add special vehicle behavior here. + else if ( NPC->client->NPC_class == CLASS_VEHICLE ) + { + Vehicle_t *pVehicle = NPC->m_pVehicle; + if ( !pVehicle->m_pPilot && pVehicle->m_iBoarding==0 ) + { + if (pVehicle->m_pVehicleInfo->type == VH_ANIMAL) + { + NPC_BehaviorSet_Animal( bState ); + } + + // TODO: The only other case were we want a vehicle to do something specifically is + // perhaps in multiplayer where we want the shuttle to be able to lift off when not + // occupied and in a landing zone. + } + } + else + { + // Just one of the average droids + NPC_BehaviorSet_Droid( bState ); + } + break; + + default: + if ( NPC->client->NPC_class == CLASS_SEEKER ) + { + NPC_BehaviorSet_Seeker(bState); + } + else + { + if ( NPCInfo->charmedTime > level.time ) + { + NPC_BehaviorSet_Charmed( bState ); + } + else + { + NPC_BehaviorSet_Default( bState ); + } + G_CheckCharmed( NPC ); + dontSetAim = qtrue; + } + break; + } + } +} + +static bState_t G_CurrentBState( gNPC_t *gNPC ) +{ + if ( gNPC->tempBehavior != BS_DEFAULT ) + {//Overrides normal behavior until cleared + return (gNPC->tempBehavior); + } + + if( gNPC->behaviorState == BS_DEFAULT ) + { + gNPC->behaviorState = gNPC->defaultBehavior; + } + + return (gNPC->behaviorState); +} +/* +=============== +NPC_ExecuteBState + + MCG + +NPC Behavior state thinking + +=============== +*/ +void NPC_ExecuteBState ( gentity_t *self)//, int msec ) +{ + bState_t bState; + + NPC_HandleAIFlags(); + + //FIXME: these next three bits could be a function call, some sort of setup/cleanup func + //Lookmode must be reset every think cycle + if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time) + { + G_ActivateBehavior( NPC, BSET_DELAYED); + NPC->delayScriptTime = 0; + } + + //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func + NPCInfo->combatMove = qfalse; + + //Execute our bState + bState = G_CurrentBState( NPCInfo ); + + //Pick the proper bstate for us and run it + NPC_RunBehavior( self->client->playerTeam, bState ); + + +// if(bState != BS_POINT_COMBAT && NPCInfo->combatPoint != -1) +// { + //level.combatPoints[NPCInfo->combatPoint].occupied = qfalse; + //NPCInfo->combatPoint = -1; +// } + + //Here we need to see what the scripted stuff told us to do +//Only process snapshot if independant and in combat mode- this would pick enemies and go after needed items +// ProcessSnapshot(); + +//Ignore my needs if I'm under script control- this would set needs for items +// CheckSelf(); + + //Back to normal? All decisions made? + + //FIXME: don't walk off ledges unless we can get to our goal faster that way, or that's our goal's surface + //NPCPredict(); + + if ( NPC->enemy ) + { + if ( !NPC->enemy->inuse ) + {//just in case bState doesn't catch this + G_ClearEnemy( NPC ); + } + } + + if ( NPC->client->ps.saberLockTime && NPC->client->ps.saberLockEnemy != ENTITYNUM_NONE ) + { + NPC_SetLookTarget( NPC, NPC->client->ps.saberLockEnemy, level.time+1000 ); + } + else if ( !NPC_CheckLookTarget( NPC ) ) + { + if ( NPC->enemy ) + { + NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 ); + } + } + + if ( NPC->enemy ) + { + if(NPC->enemy->flags & FL_DONT_SHOOT) + { + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons &= ~BUTTON_ALT_ATTACK; + } + else if ( NPC->client->playerTeam != TEAM_ENEMY //not an enemy + && (NPC->client->playerTeam != TEAM_FREE || (NPC->client->NPC_class == CLASS_TUSKEN && Q_irand( 0, 4 )))//not a rampaging creature or I'm a tusken and I feel generous (temporarily) + && NPC->enemy->NPC + && (NPC->enemy->NPC->surrenderTime > level.time || (NPC->enemy->NPC->scriptFlags&SCF_FORCED_MARCH)) ) + {//don't shoot someone who's surrendering if you're a good guy + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons &= ~BUTTON_ALT_ATTACK; + } + + if(client->ps.weaponstate == WEAPON_IDLE) + { + client->ps.weaponstate = WEAPON_READY; + } + } + else + { + if(client->ps.weaponstate == WEAPON_READY) + { + client->ps.weaponstate = WEAPON_IDLE; + } + } + + if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time) + {//We just shot but aren't still shooting, so hold the gun up for a while + if(client->ps.weapon == WP_SABER ) + {//One-handed + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); + } + else if(client->ps.weapon == WP_BRYAR_PISTOL) + {//Sniper pose + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + /*//FIXME: What's the proper solution here? + else + {//heavy weapon + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + */ + } + + NPC_CheckAttackHold(); + NPC_ApplyScriptFlags(); + + //cliff and wall avoidance + NPC_AvoidWallsAndCliffs(); + + // run the bot through the server like it was a real client +//=== Save the ucmd for the second no-think Pmove ============================ + ucmd.serverTime = level.time - 50; + memcpy( &NPCInfo->last_ucmd, &ucmd, sizeof( usercmd_t ) ); + if ( !NPCInfo->attackHoldTime ) + { + NPCInfo->last_ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS);//so we don't fire twice in one think + } +//============================================================================ + NPC_CheckAttackScript(); + NPC_KeepCurrentFacing(); + + if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) + {//If we were following a roff, we don't do normal pmoves. + ClientThink( NPC->s.number, &ucmd ); + } + else + { + NPC_ApplyRoff(); + } + + // end of thinking cleanup + NPCInfo->touchedByPlayer = NULL; + + NPC_CheckPlayerAim(); + NPC_CheckAllClear(); + + /*if( ucmd.forwardmove || ucmd.rightmove ) + { + int i, la = -1, ta = -1; + + for(i = 0; i < MAX_ANIMATIONS; i++) + { + if( NPC->client->ps.legsAnim == i ) + { + la = i; + } + + if( NPC->client->ps.torsoAnim == i ) + { + ta = i; + } + + if(la != -1 && ta != -1) + { + break; + } + } + + if(la != -1 && ta != -1) + {//FIXME: should never play same frame twice or restart an anim before finishing it + gi.Printf("LegsAnim: %s(%d) TorsoAnim: %s(%d)\n", animTable[la].name, NPC->renderInfo.legsFrame, animTable[ta].name, NPC->client->renderInfo.torsoFrame); + } + }*/ +} + +void NPC_CheckInSolid(void) +{ + trace_t trace; + vec3_t point; + VectorCopy(NPC->currentOrigin, point); + point[2] -= 0.25; + + gi.trace(&trace, NPC->currentOrigin, NPC->mins, NPC->maxs, point, NPC->s.number, NPC->clipmask); + if(!trace.startsolid && !trace.allsolid) + { + VectorCopy(NPC->currentOrigin, NPCInfo->lastClearOrigin); + } + else + { + if(VectorLengthSquared(NPCInfo->lastClearOrigin)) + { +// gi.Printf("%s stuck in solid at %s: fixing...\n", NPC->script_targetname, vtos(NPC->currentOrigin)); + G_SetOrigin(NPC, NPCInfo->lastClearOrigin); + gi.linkentity(NPC); + } + } +} + +/* +=============== +NPC_Think + +Main NPC AI - called once per frame +=============== +*/ +#if AI_TIMERS +extern int AITime; +#endif// AI_TIMERS +void NPC_Think ( gentity_t *self)//, int msec ) +{ + vec3_t oldMoveDir; + + self->nextthink = level.time + FRAMETIME/2; + + SetNPCGlobals( self ); + + memset( &ucmd, 0, sizeof( ucmd ) ); + + VectorCopy( self->client->ps.moveDir, oldMoveDir ); + VectorClear( self->client->ps.moveDir ); + // see if NPC ai is frozen + if ( debugNPCFreeze->integer || (NPC->svFlags&SVF_ICARUS_FREEZE) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + ClientThink(self->s.number, &ucmd); + VectorCopy(self->s.origin, self->s.origin2 ); + return; + } + + if(!self || !self->NPC || !self->client) + { + return; + } + + // dead NPCs have a special think, don't run scripts (for now) + //FIXME: this breaks deathscripts + if ( self->health <= 0 ) + { + DeadThink(); + if ( NPCInfo->nextBStateThink <= level.time ) + { + if( self->m_iIcarusID != IIcarusInterface::ICARUS_INVALID && !stop_icarus ) + { + IIcarusInterface::GetIcarus()->Update( self->m_iIcarusID ); + } + } + return; + } + + // TODO! Tauntaun's (and other creature vehicles?) think, we'll need to make an exception here to allow that. + + if ( self->client + && self->client->NPC_class == CLASS_VEHICLE + && self->NPC_type + && ( !self->m_pVehicle->m_pVehicleInfo->Inhabited( self->m_pVehicle ) ) ) + {//empty swoop logic + if ( self->owner ) + {//still have attached owner, check and see if can forget him (so he can use me later) + vec3_t dir2owner; + VectorSubtract( self->owner->currentOrigin, self->currentOrigin, dir2owner ); + + gentity_t *oldOwner = self->owner; + self->owner = NULL;//clear here for that SpotWouldTelefrag check...? + + if ( VectorLengthSquared( dir2owner ) > 128*128 + || !(self->clipmask&oldOwner->clipmask) + || (DotProduct( self->client->ps.velocity, oldOwner->client->ps.velocity ) < -200.0f &&!G_BoundsOverlap( self->absmin, self->absmin, oldOwner->absmin, oldOwner->absmax )) ) + {//all clear, become solid to our owner now + gi.linkentity( self ); + } + else + {//blocked, retain owner + self->owner = oldOwner; + } + } + } + if ( player->client->ps.viewEntity == self->s.number ) + {//being controlled by player + if ( self->client ) + {//make the noises + if ( TIMER_Done( self, "patrolNoise" ) && !Q_irand( 0, 20 ) ) + { + switch( self->client->NPC_class ) + { + case CLASS_R2D2: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3)) ); + break; + case CLASS_R5D2: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4)) ); + break; + case CLASS_PROBE: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d.wav",Q_irand(1, 3)) ); + break; + case CLASS_MOUSE: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3)) ); + break; + case CLASS_GONK: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2)) ); + break; + } + TIMER_Set( self, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + //FIXME: might want to at least make sounds or something? + //NPC_UpdateAngles(qtrue, qtrue); + //Which ucmd should we send? Does it matter, since it gets overridden anyway? + NPCInfo->last_ucmd.serverTime = level.time - 50; + ClientThink( NPC->s.number, &ucmd ); + VectorCopy(self->s.origin, self->s.origin2 ); + return; + } + + if ( NPCInfo->nextBStateThink <= level.time ) + { +#if AI_TIMERS + int startTime = GetTime(0); +#endif// AI_TIMERS + if ( NPC->s.eType != ET_PLAYER ) + {//Something drastic happened in our script + return; + } + + if ( NPC->s.weapon == WP_SABER && g_spskill->integer >= 2 && NPCInfo->rank > RANK_LT_JG ) + {//Jedi think faster on hard difficulty, except low-rank (reborn) + NPCInfo->nextBStateThink = level.time + FRAMETIME/2; + } + else + {//Maybe even 200 ms? + NPCInfo->nextBStateThink = level.time + FRAMETIME; + } + + //nextthink is set before this so something in here can override it + NPC_ExecuteBState( self ); + +#if AI_TIMERS + int addTime = GetTime( startTime ); + if ( addTime > 50 ) + { + gi.Printf( S_COLOR_RED"ERROR: NPC number %d, %s %s at %s, weaponnum: %d, using %d of AI time!!!\n", NPC->s.number, NPC->NPC_type, NPC->targetname, vtos(NPC->currentOrigin), NPC->s.weapon, addTime ); + } + AITime += addTime; +#endif// AI_TIMERS + } + else + { + if ( NPC->client + && NPC->client->NPC_class == CLASS_ROCKETTROOPER + && (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) + && NPC->client->moveType == MT_FLYSWIM + && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//reduce velocity + VectorScale( NPC->client->ps.velocity, 0.75f, NPC->client->ps.velocity ); + } + VectorCopy( oldMoveDir, self->client->ps.moveDir ); + //or use client->pers.lastCommand? + NPCInfo->last_ucmd.serverTime = level.time - 50; + if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) + {//If we were following a roff, we don't do normal pmoves. + //FIXME: firing angles (no aim offset) or regular angles? + NPC_UpdateAngles(qtrue, qtrue); + memcpy( &ucmd, &NPCInfo->last_ucmd, sizeof( usercmd_t ) ); + ClientThink(NPC->s.number, &ucmd); + } + else + { + NPC_ApplyRoff(); + } + VectorCopy(self->s.origin, self->s.origin2 ); + } + //must update icarus *every* frame because of certain animation completions in the pmove stuff that can leave a 50ms gap between ICARUS animation commands + if( self->m_iIcarusID != IIcarusInterface::ICARUS_INVALID && !stop_icarus ) + { + IIcarusInterface::GetIcarus()->Update( self->m_iIcarusID ); + } +} + +void NPC_InitAI ( void ) +{ + debugNPCAI = gi.cvar ( "d_npcai", "0", CVAR_CHEAT ); + debugNPCFreeze = gi.cvar ( "d_npcfreeze", "0", CVAR_CHEAT); + d_JediAI = gi.cvar ( "d_JediAI", "0", CVAR_CHEAT ); + d_noGroupAI = gi.cvar ( "d_noGroupAI", "0", CVAR_CHEAT ); + d_asynchronousGroupAI = gi.cvar ( "d_asynchronousGroupAI", "1", CVAR_CHEAT ); + + //0 = never (BORING) + //1 = kyle only + //2 = kyle and last enemy jedi + //3 = kyle and any enemy jedi + //4 = kyle and last enemy in a group, special kicks + //5 = kyle and any enemy + //6 = also when kyle takes pain or enemy jedi dodges player saber swing or does an acrobatic evasion + // NOTE : I also create this in UI_Init() + d_slowmodeath = gi.cvar ( "d_slowmodeath", "3", CVAR_ARCHIVE );//save this setting + + d_saberCombat = gi.cvar ( "d_saberCombat", "0", CVAR_CHEAT ); +} + +/* +================================== +void NPC_InitAnimTable( void ) + + Need to initialize this table. + If someone tried to play an anim + before table is filled in with + values, causes tasks that wait for + anim completion to never finish. + (frameLerp of 0 * numFrames of 0 = 0) +================================== +*/ +void NPC_InitAnimTable( void ) +{ + for ( int i = 0; i < MAX_ANIM_FILES; i++ ) + { + for ( int j = 0; j < MAX_ANIMATIONS; j++ ) + { + level.knownAnimFileSets[i].animations[j].firstFrame = 0; + level.knownAnimFileSets[i].animations[j].frameLerp = 100; +// level.knownAnimFileSets[i].animations[j].initialLerp = 100; + level.knownAnimFileSets[i].animations[j].numFrames = 0; + } + } +} + +extern int G_ParseAnimFileSet( const char *skeletonName, const char *modelName=0); +void NPC_InitGame( void ) +{ +// globals.NPCs = (gNPC_t *) gi.TagMalloc(game.maxclients * sizeof(game.bots[0]), TAG_GAME); + debugNPCName = gi.cvar ( "d_npc", "", 0 ); + NPC_LoadParms(); + NPC_InitAI(); + NPC_InitAnimTable(); + G_ParseAnimFileSet("_humanoid"); //GET THIS CACHED NOW BEFORE CGAME STARTS + /* + ResetTeamCounters(); + for ( int team = TEAM_FREE; team < TEAM_NUM_TEAMS; team++ ) + { + teamLastEnemyTime[team] = -10000; + } + */ +} + +void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend) +{ // FIXME : once torsoAnim and legsAnim are in the same structure for NCP and Players + // rename PM_SETAnimFinal to PM_SetAnim and have both NCP and Players call PM_SetAnim + + if ( !ent ) + { + return; + } + + if ( ent->health > 0 ) + {//don't lock anims if the guy is dead + if ( ent->client->ps.torsoAnimTimer + && PM_LockedAnim( ent->client->ps.torsoAnim ) + && !PM_LockedAnim( anim ) ) + {//nothing can override these special anims + setAnimParts &= ~SETANIM_TORSO; + } + + if ( ent->client->ps.legsAnimTimer + && PM_LockedAnim( ent->client->ps.legsAnim ) + && !PM_LockedAnim( anim ) ) + {//nothing can override these special anims + setAnimParts &= ~SETANIM_LEGS; + } + } + + if ( !setAnimParts ) + { + return; + } + + if(ent->client) + {//Players, NPCs + if (setAnimFlags&SETANIM_FLAG_OVERRIDE) + { + if (setAnimParts & SETANIM_TORSO) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->client->ps.torsoAnim != anim ) + { + PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoAnimTimer, 0 ); + } + } + if (setAnimParts & SETANIM_LEGS) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->client->ps.legsAnim != anim ) + { + PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, 0 ); + } + } + } + + PM_SetAnimFinal(&ent->client->ps.torsoAnim,&ent->client->ps.legsAnim,setAnimParts,anim,setAnimFlags, + &ent->client->ps.torsoAnimTimer,&ent->client->ps.legsAnimTimer,ent, iBlend ); + } + else + {//bodies, etc. + if (setAnimFlags&SETANIM_FLAG_OVERRIDE) + { + if (setAnimParts & SETANIM_TORSO) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->s.torsoAnim != anim ) + { + PM_SetTorsoAnimTimer( ent, &ent->s.torsoAnimTimer, 0 ); + } + } + if (setAnimParts & SETANIM_LEGS) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->s.legsAnim != anim ) + { + PM_SetLegsAnimTimer( ent, &ent->s.legsAnimTimer, 0 ); + } + } + } + + PM_SetAnimFinal(&ent->s.torsoAnim,&ent->s.legsAnim,setAnimParts,anim,setAnimFlags, + &ent->s.torsoAnimTimer,&ent->s.legsAnimTimer,ent); + } +} diff --git a/code/game/NPC_behavior.cpp b/code/game/NPC_behavior.cpp new file mode 100644 index 0000000..9f07940 --- /dev/null +++ b/code/game/NPC_behavior.cpp @@ -0,0 +1,2067 @@ +//NPC_behavior.cpp +/* +FIXME - MCG: +These all need to make use of the snapshots. Write something that can look for only specific +things in a snapshot or just go through the snapshot every frame and save the info in case +we need it... +*/ + +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" +#include "g_navigator.h" +#include "Q3_Interface.h" + +extern cvar_t *g_AIsurrender; +extern qboolean showBBoxes; +static vec3_t NPCDEBUG_BLUE = {0.0, 0.0, 1.0}; +extern void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ); +extern void NPC_CheckGetNewWeapon( void ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern void NPC_AimAdjust( int change ); +extern qboolean G_StandardHumanoid( gentity_t *self ); +/* + void NPC_BSAdvanceFight (void) + +Advance towards your captureGoal and shoot anyone you can along the way. +*/ +void NPC_BSAdvanceFight (void) +{//FIXME: IMPLEMENT +//Head to Goal if I can + + //Make sure we're still headed where we want to capture + if ( NPCInfo->captureGoal ) + {//FIXME: if no captureGoal, what do we do? + //VectorCopy( NPCInfo->captureGoal->currentOrigin, NPCInfo->tempGoal->currentOrigin ); + //NPCInfo->goalEntity = NPCInfo->tempGoal; + + NPC_SetMoveGoal( NPC, NPCInfo->captureGoal->currentOrigin, 16, qtrue ); + + NPCInfo->goalTime = level.time + 100000; + } + +// NPC_BSRun(); + + NPC_CheckEnemy(qtrue, qfalse); + + //FIXME: Need melee code + if( NPC->enemy ) + {//See if we can shoot him + vec3_t delta, forward; + vec3_t angleToEnemy; + vec3_t hitspot, muzzle, diff, enemy_org, enemy_head; + float distanceToEnemy; + qboolean attack_ok = qfalse; + qboolean dead_on = qfalse; + float attack_scale = 1.0; + float aim_off; + float max_aim_off = 64; + + //Yaw to enemy + VectorMA(NPC->enemy->absmin, 0.5, NPC->enemy->maxs, enemy_org); + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorSubtract (enemy_org, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + distanceToEnemy = VectorNormalize(delta); + + if(!NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue)) + { + attack_ok = qtrue; + } + + if(attack_ok) + { + NPC_UpdateShootAngles(angleToEnemy, qfalse, qtrue); + + NPCInfo->enemyLastVisibility = enemyVisibility; + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV);//CHECK_360|//CHECK_PVS| + + if(enemyVisibility == VIS_FOV) + {//He's in our FOV + + attack_ok = qtrue; + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_head); + + if(attack_ok) + { + trace_t tr; + gentity_t *traceEnt; + //are we gonna hit him if we shoot at his center? + gi.trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT ); + traceEnt = &g_entities[tr.entityNum]; + if( traceEnt != NPC->enemy && + (!traceEnt || !traceEnt->client || !NPC->client->enemyTeam || NPC->client->enemyTeam != traceEnt->client->playerTeam) ) + {//no, so shoot for the head + attack_scale *= 0.75; + gi.trace ( &tr, muzzle, NULL, NULL, enemy_head, NPC->s.number, MASK_SHOT ); + traceEnt = &g_entities[tr.entityNum]; + } + + VectorCopy( tr.endpos, hitspot ); + + if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) ) + { + dead_on = qtrue; + } + else + { + attack_scale *= 0.5; + if(NPC->client->playerTeam) + { + if(traceEnt && traceEnt->client && traceEnt->client->playerTeam) + { + if(NPC->client->playerTeam == traceEnt->client->playerTeam) + {//Don't shoot our own team + attack_ok = qfalse; + } + } + } + } + } + + if( attack_ok ) + { + //ok, now adjust pitch aim + VectorSubtract (hitspot, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateShootAngles(angleToEnemy, qtrue, qfalse); + + if( !dead_on ) + {//We're not going to hit him directly, try a suppressing fire + //see if where we're going to shoot is too far from his origin + AngleVectors (NPCInfo->shootAngles, forward, NULL, NULL); + VectorMA ( muzzle, distanceToEnemy, forward, hitspot); + VectorSubtract(hitspot, enemy_org, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off)//FIXME: use aim value to allow poor aim? + { + attack_scale *= 0.75; + //see if where we're going to shoot is too far from his head + VectorSubtract(hitspot, enemy_head, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off) + { + attack_ok = qfalse; + } + } + attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off; + } + } + } + } + + if( attack_ok ) + { + if( NPC_CheckAttack( attack_scale )) + {//check aggression to decide if we should shoot + enemyVisibility = VIS_SHOOT; + WeaponThink(qtrue); + } + else + attack_ok = qfalse; + } +//Don't do this- only for when stationary and trying to shoot an enemy +// else +// NPC->cantHitEnemyCounter++; + } + else + {//FIXME: + NPC_UpdateShootAngles(NPC->client->ps.viewangles, qtrue, qtrue); + } + + if(!ucmd.forwardmove && !ucmd.rightmove) + {//We reached our captureGoal + if( NPC->m_iIcarusID != IIcarusInterface::ICARUS_INVALID /*NPC->taskManager*/ ) + { + Q3_TaskIDComplete( NPC, TID_BSTATE ); + } + } +} + +void Disappear(gentity_t *self) +{ +// ClientDisconnect(self); + self->s.eFlags |= EF_NODRAW; + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = -1; +} + +void MakeOwnerInvis (gentity_t *self); +void BeamOut (gentity_t *self) +{ +// gentity_t *tent = G_Spawn(); + +/* + tent->owner = self; + tent->think = MakeOwnerInvis; + tent->nextthink = level.time + 1800; + //G_AddEvent( ent, EV_PLAYER_TELEPORT, 0 ); + tent = G_TempEntity( self->client->pcurrentOrigin, EV_PLAYER_TELEPORT ); +*/ + //fixme: doesn't actually go away! + self->nextthink = level.time + 1500; + self->e_ThinkFunc = thinkF_Disappear; + self->client->playerTeam = TEAM_FREE; + self->svFlags |= SVF_BEAMING; +} + +void NPC_BSCinematic( void ) +{ + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + if (NPCInfo->scriptFlags&SCF_FIRE_WEAPON_NO_ANIM) + { + if (TIMER_Done(NPC, "NoAnimFireDelay")) + { + TIMER_Set(NPC, "NoAnimFireDelay", NPC_AttackDebounceForWeapon()); + FireWeapon(NPC, (NPCInfo->scriptFlags&SCF_ALT_FIRE)) ; + } + } + + if ( UpdateGoal() ) + {//have a goalEntity + //move toward goal, should also face that goal + NPC_MoveToGoal( qtrue ); + } + + if ( NPCInfo->watchTarget ) + {//have an entity which we want to keep facing + //NOTE: this will override any angles set by NPC_MoveToGoal + vec3_t eyes, viewSpot, viewvec, viewangles; + + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); + CalcEntitySpot( NPCInfo->watchTarget, SPOT_HEAD_LEAN, viewSpot ); + + VectorSubtract( viewSpot, eyes, viewvec ); + + vectoangles( viewvec, viewangles ); + + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw = viewangles[YAW]; + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch = viewangles[PITCH]; + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +void NPC_BSWait( void ) +{ + NPC_UpdateAngles( qtrue, qtrue ); +} + + +void NPC_BSInvestigate (void) +{ +/* + //FIXME: maybe allow this to be set as a tempBState in a script? Just specify the + //investigateGoal, investigateDebounceTime and investigateCount? (Needs a macro) + vec3_t invDir, invAngles, spot; + gentity_t *saveGoal; + //BS_INVESTIGATE would turn toward goal, maybe take a couple steps towards it, + //look for enemies, then turn away after your investigate counter was down- + //investigate counter goes up every time you set it... + + if(level.time > NPCInfo->enemyCheckDebounceTime) + { + NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 1000); + NPC_CheckEnemy(qtrue, qfalse); + if(NPC->enemy) + {//FIXME: do anger script + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->behaviorState = BS_RUN_AND_SHOOT; + NPCInfo->tempBehavior = BS_DEFAULT; + NPC_AngerSound(); + return; + } + } + + NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_WEAPONREADY3, SETANIM_FLAG_NORMAL ); + + if(NPCInfo->stats.vigilance <= 1.0 && NPCInfo->eventOwner) + { + VectorCopy(NPCInfo->eventOwner->currentOrigin, NPCInfo->investigateGoal); + } + + saveGoal = NPCInfo->goalEntity; + if( level.time > NPCInfo->walkDebounceTime ) + { + vec3_t vec; + + VectorSubtract(NPCInfo->investigateGoal, NPC->currentOrigin, vec); + vec[2] = 0; + if(VectorLength(vec) > 64) + { + if(Q_irand(0, 100) < NPCInfo->investigateCount) + {//take a full step + //NPCInfo->walkDebounceTime = level.time + 1400; + //actually finds length of my BOTH_WALK anim + NPCInfo->walkDebounceTime = PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_WALK1 ); + } + } + } + + if( level.time < NPCInfo->walkDebounceTime ) + {//walk toward investigateGoal + + /* + NPCInfo->goalEntity = NPCInfo->tempGoal; + VectorCopy(NPCInfo->investigateGoal, NPCInfo->tempGoal->currentOrigin); + */ + +/* NPC_SetMoveGoal( NPC, NPCInfo->investigateGoal, 16, qtrue ); + + NPC_MoveToGoal( qtrue ); + + //FIXME: walk2? + NPC_SetAnim(NPC,SETANIM_LEGS,BOTH_WALK1,SETANIM_FLAG_NORMAL); + + ucmd.buttons |= BUTTON_WALKING; + } + else + { + + NPC_SetAnim(NPC,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + + if(NPCInfo->hlookCount > 30) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->hlookCount = 0; + } + } + else if(NPCInfo->hlookCount < -30) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->hlookCount = 0; + } + } + else if(NPCInfo->hlookCount == 0) + { + NPCInfo->hlookCount = Q_irand(-1, 1); + } + else if(Q_irand(0, 10) > 7) + { + if(NPCInfo->hlookCount > 0) + { + NPCInfo->hlookCount++; + } + else//lookCount < 0 + { + NPCInfo->hlookCount--; + } + } + + if(NPCInfo->vlookCount >= 15) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->vlookCount = 0; + } + } + else if(NPCInfo->vlookCount <= -15) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->vlookCount = 0; + } + } + else if(NPCInfo->vlookCount == 0) + { + NPCInfo->vlookCount = Q_irand(-1, 1); + } + else if(Q_irand(0, 10) > 8) + { + if(NPCInfo->vlookCount > 0) + { + NPCInfo->vlookCount++; + } + else//lookCount < 0 + { + NPCInfo->vlookCount--; + } + } + + //turn toward investigateGoal + CalcEntitySpot( NPC, SPOT_HEAD, spot ); + VectorSubtract(NPCInfo->investigateGoal, spot, invDir); + VectorNormalize(invDir); + vectoangles(invDir, invAngles); + NPCInfo->desiredYaw = AngleNormalize360(invAngles[YAW] + NPCInfo->hlookCount); + NPCInfo->desiredPitch = AngleNormalize360(invAngles[PITCH] + NPCInfo->hlookCount); + } + + NPC_UpdateAngles(qtrue, qtrue); + + NPCInfo->goalEntity = saveGoal; + + if(level.time > NPCInfo->investigateDebounceTime) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + + NPC_CheckSoundEvents(); + */ +} + +qboolean NPC_CheckInvestigate( int alertEventNum ) +{ + gentity_t *owner = level.alertEvents[alertEventNum].owner; + int invAdd = level.alertEvents[alertEventNum].level; + vec3_t soundPos; + float soundRad = level.alertEvents[alertEventNum].radius; + float earshot = NPCInfo->stats.earshot; + + VectorCopy( level.alertEvents[alertEventNum].position, soundPos ); + + //NOTE: Trying to preserve previous investigation behavior + if ( !owner ) + { + return qfalse; + } + + if ( owner->s.eType != ET_PLAYER && owner == NPCInfo->goalEntity ) + { + return qfalse; + } + + if ( owner->s.eFlags & EF_NODRAW ) + { + return qfalse; + } + + if ( owner->flags & FL_NOTARGET ) + { + return qfalse; + } + + if ( soundRad < earshot ) + { + return qfalse; + } + + //if(!gi.inPVSIgnorePortals(ent->currentOrigin, NPC->currentOrigin))//should we be able to hear through areaportals? + if ( !gi.inPVS( soundPos, NPC->currentOrigin ) ) + {//can hear through doors? + return qfalse; + } + + if ( owner->client && owner->client->playerTeam && NPC->client->playerTeam && owner->client->playerTeam != NPC->client->playerTeam ) + { + if( (float)NPCInfo->investigateCount >= (NPCInfo->stats.vigilance*200) && owner ) + {//If investigateCount == 10, just take it as enemy and go + if ( NPC_ValidEnemy( owner ) ) + {//FIXME: run angerscript + G_SetEnemy( NPC, owner ); + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + return qtrue; + } + } + else + { + NPCInfo->investigateCount += invAdd; + } + //run awakescript + G_ActivateBehavior(NPC, BSET_AWAKE); + + /* + if ( Q_irand(0, 10) > 7 ) + { + NPC_AngerSound(); + } + */ + + //NPCInfo->hlookCount = NPCInfo->vlookCount = 0; + NPCInfo->eventOwner = owner; + VectorCopy( soundPos, NPCInfo->investigateGoal ); + if ( NPCInfo->investigateCount > 20 ) + { + NPCInfo->investigateDebounceTime = level.time + 10000; + } + else + { + NPCInfo->investigateDebounceTime = level.time + (NPCInfo->investigateCount*500); + } + NPCInfo->tempBehavior = BS_INVESTIGATE; + return qtrue; + } + + return qfalse; +} + + +/* +void NPC_BSSleep( void ) +*/ +void NPC_BSSleep( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qtrue, qfalse ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + G_ActivateBehavior(NPC, BSET_AWAKE); + return; + } + + /* + if ( level.time > NPCInfo->enemyCheckDebounceTime ) + { + if ( NPC_CheckSoundEvents() != -1 ) + {//only 1 alert per second per 0.1 of vigilance + NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 10000); + G_ActivateBehavior(NPC, BSET_AWAKE); + } + } + */ +} + + +extern qboolean NPC_MoveDirClear( int forwardmove, int rightmove, qboolean reset ); + +bool NPC_BSFollowLeader_UpdateLeader(void) +{ + if ( NPC->client->leader//have a leader + && NPC->client->leader->s.number < MAX_CLIENTS //player + && NPC->client->leader->client//player is a client + && !NPC->client->leader->client->pers.enterTime )//player has not finished spawning in yet + {//don't do anything just yet, but don't clear the leader either + return false; + } + + if (NPC->client->leader && NPC->client->leader->health<=0) + { + NPC->client->leader = NULL; + } + + if ( !NPC->client->leader ) + {//ok, stand guard until we find an enemy + if( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + { + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + } + if ( NPCInfo->behaviorState == BS_FOLLOW_LEADER ) + { + NPCInfo->behaviorState = BS_DEFAULT; + } + if ( NPCInfo->defaultBehavior == BS_FOLLOW_LEADER ) + { + NPCInfo->defaultBehavior = BS_DEFAULT; + } + return false; + } + return true; +} + + +void NPC_BSFollowLeader_UpdateEnemy(void) +{ + if ( !NPC->enemy ) + {//no enemy, find one + NPC_CheckEnemy( NPCInfo->confusionTimeenemy ) + {//just found one + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + } + else + { + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + int eventID = NPC_CheckAlertEvents( qtrue, qtrue ); + if ( eventID > -1 && level.alertEvents[eventID].level >= AEL_SUSPICIOUS && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { + //NPCInfo->lastAlertID = level.alertEvents[eventID].ID; + if ( !level.alertEvents[eventID].owner || + !level.alertEvents[eventID].owner->client || + level.alertEvents[eventID].owner->health <= 0 || + level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam ) + {//not an enemy + } + else + { + //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him? Or just let combat AI handle this... (act as if you lost him) + G_SetEnemy( NPC, level.alertEvents[eventID].owner ); + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 1000 ) ); + } + } + + } + } + if ( !NPC->enemy ) + { + if ( NPC->client->leader + && NPC->client->leader->enemy + && NPC->client->leader->enemy != NPC + && ( (NPC->client->leader->enemy->client&&NPC->client->leader->enemy->client->playerTeam==NPC->client->enemyTeam) + ||(NPC->client->leader->enemy->svFlags&SVF_NONNPC_ENEMY&&NPC->client->leader->enemy->noDamageTeam==NPC->client->enemyTeam) ) + && NPC->client->leader->enemy->health > 0 ) + { + G_SetEnemy( NPC, NPC->client->leader->enemy ); + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + NPCInfo->enemyLastSeenTime = level.time; + } + } + } + else + { + if ( NPC->enemy->health <= 0 || (NPC->enemy->flags&FL_NOTARGET) ) + { + G_ClearEnemy( NPC ); + if ( NPCInfo->enemyCheckDebounceTime > level.time + 1000 ) + { + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 2000 ); + } + } + else if ( NPC->client->ps.weapon && NPCInfo->enemyCheckDebounceTime < level.time ) + { + NPC_CheckEnemy( (NPCInfo->confusionTimetempBehavior!=BS_FOLLOW_LEADER), qfalse );//don't find new enemy if this is tempbehav + } + } +} + + +bool NPC_BSFollowLeader_AttackEnemy(void) +{ + if ( NPC->client->ps.weapon == WP_SABER )//|| NPCInfo->confusionTime>level.time ) + {//lightsaber user or charmed enemy + if ( NPCInfo->tempBehavior != BS_FOLLOW_LEADER ) + {//not already in a temp bState + //go after the guy + NPCInfo->tempBehavior = BS_HUNT_AND_KILL; + NPC_UpdateAngles(qtrue, qtrue); + return true; + } + } + + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT );//CHECK_360|CHECK_PVS| + if ( enemyVisibility > VIS_PVS ) + {//face + vec3_t enemy_org, muzzle, delta, angleToEnemy; + float distanceToEnemy; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org ); + NPC_AimWiggle( enemy_org ); + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorSubtract( enemy_org, muzzle, delta); + vectoangles( delta, angleToEnemy ); + distanceToEnemy = VectorNormalize( delta ); + + NPCInfo->desiredYaw = angleToEnemy[YAW]; + NPCInfo->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles( qtrue, qtrue ); + + if ( enemyVisibility >= VIS_SHOOT ) + {//shoot + NPC_AimAdjust( 2 ); + if ( NPC_GetHFOVPercentage( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, NPCInfo->stats.hfov ) > 0.6f + && NPC_GetHFOVPercentage( NPC->enemy->currentOrigin, NPC->currentOrigin, NPC->client->ps.viewangles, NPCInfo->stats.vfov ) > 0.5f ) + {//actually withing our front cone + WeaponThink( qtrue ); + } + } + else + { + NPC_AimAdjust( 1 ); + } + + //NPC_CheckCanAttack(1.0, qfalse); + } + else + { + NPC_AimAdjust( -1 ); + } + return false; +} + +bool NPC_BSFollowLeader_CanAttack(void) +{ + return (NPC->enemy + && NPC->client->ps.weapon + && !(NPCInfo->aiFlags&NPCAI_HEAL_ROSH) //Kothos twins never go after their enemy + ); +} + +bool NPC_BSFollowLeader_InFullBodyAttack(void) +{ + return ( + NPC->client->ps.legsAnim==BOTH_ATTACK1 || + NPC->client->ps.legsAnim==BOTH_ATTACK2 || + NPC->client->ps.legsAnim==BOTH_ATTACK3 || + NPC->client->ps.legsAnim==BOTH_MELEE1 || + NPC->client->ps.legsAnim==BOTH_MELEE2 + ); +} + +void NPC_BSFollowLeader_LookAtLeader(void) +{ + vec3_t head, leaderHead, delta, angleToLeader; + + CalcEntitySpot( NPC->client->leader, SPOT_HEAD, leaderHead ); + CalcEntitySpot( NPC, SPOT_HEAD, head ); + VectorSubtract (leaderHead, head, delta); + vectoangles ( delta, angleToLeader ); + VectorNormalize(delta); + NPC->NPC->desiredYaw = angleToLeader[YAW]; + NPC->NPC->desiredPitch = angleToLeader[PITCH]; + + NPC_UpdateAngles(qtrue, qtrue); +} + +void NPC_BSFollowLeader (void) +{ + // If In A Jump, Return + //---------------------- + if (NPC_Jumping()) + { + return; + } + + // If There Is No Leader, Return + //------------------------------- + if (!NPC_BSFollowLeader_UpdateLeader()) + { + return; + } + + // Don't Do Anything Else If In A Full Body Attack + //------------------------------------------------- + if (NPC_BSFollowLeader_InFullBodyAttack()) + { + return; + } + + // Update The Enemy + //------------------ + NPC_BSFollowLeader_UpdateEnemy(); + + + // Do Any Attacking + //------------------ + if (NPC_BSFollowLeader_CanAttack()) + { + if (NPC_BSFollowLeader_AttackEnemy()) + { + return; + } + } + else + { + NPC_BSFollowLeader_LookAtLeader(); + } + + + + + + + float followDist = (NPCInfo->followDist)?(NPCInfo->followDist):(110.0f); + bool moveSuccess; + + STEER::Activate(NPC); + { + if (NPC->client->leader->client && NPC->client->leader->client->ps.groundEntityNum!=ENTITYNUM_NONE) + { + // If Too Close, Back Away Some + //------------------------------ + if (STEER::Reached(NPC, NPC->client->leader, 65.0f)) + { + STEER::Evade(NPC, NPC->client->leader); + } + else + { + // Attempt To Steer Directly To Our Goal + //--------------------------------------- + moveSuccess = STEER::GoTo(NPC, NPC->client->leader, followDist); + + // Perhaps Not Close Enough? Try To Use The Navigation Grid + //----------------------------------------------------------- + if (!moveSuccess) + { + moveSuccess = NAV::GoTo(NPC, NPC->client->leader); + if (!moveSuccess) + { + STEER::Stop(NPC); + } + } + } + } + else + { + STEER::Stop(NPC); + } + } + STEER::DeActivate(NPC, &ucmd); +} + + + +#define APEX_HEIGHT 200.0f +#define PARA_WIDTH (sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT)) +#define JUMP_SPEED 200.0f +void NPC_BSJump (void) +{ + vec3_t dir, angles, p1, p2, apex; + float time, height, forward, z, xy, dist, yawError, apexHeight; + + if( !NPCInfo->goalEntity ) + {//Should have task completed the navgoal + return; + } + + if ( NPCInfo->jumpState != JS_JUMPING && NPCInfo->jumpState != JS_LANDING ) + { + //Face navgoal + VectorSubtract(NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir); + vectoangles(dir, angles); + NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]); + NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]); + } + + NPC_UpdateAngles ( qtrue, qtrue ); + yawError = AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw ); + //We don't really care about pitch here + + switch ( NPCInfo->jumpState ) + { + case JS_FACING: + if ( yawError < MIN_ANGLE_ERROR ) + {//Facing it, Start crouching + NPC_SetAnim(NPC, SETANIM_LEGS, BOTH_CROUCH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + NPCInfo->jumpState = JS_CROUCHING; + } + break; + case JS_CROUCHING: + if ( NPC->client->ps.legsAnimTimer > 0 ) + {//Still playing crouching anim + return; + } + + //Create a parabola + + if ( NPC->currentOrigin[2] > NPCInfo->goalEntity->currentOrigin[2] ) + { + VectorCopy( NPC->currentOrigin, p1 ); + VectorCopy( NPCInfo->goalEntity->currentOrigin, p2 ); + } + else if ( NPC->currentOrigin[2] < NPCInfo->goalEntity->currentOrigin[2] ) + { + VectorCopy( NPCInfo->goalEntity->currentOrigin, p1 ); + VectorCopy( NPC->currentOrigin, p2 ); + } + else + { + VectorCopy( NPC->currentOrigin, p1 ); + VectorCopy( NPCInfo->goalEntity->currentOrigin, p2 ); + } + + //z = xy*xy + VectorSubtract( p2, p1, dir ); + dir[2] = 0; + + //Get xy and z diffs + xy = VectorNormalize( dir ); + z = p1[2] - p2[2]; + + apexHeight = APEX_HEIGHT/2; + /* + //Determine most desirable apex height + apexHeight = (APEX_HEIGHT * PARA_WIDTH/xy) + (APEX_HEIGHT * z/128); + if ( apexHeight < APEX_HEIGHT * 0.5 ) + { + apexHeight = APEX_HEIGHT*0.5; + } + else if ( apexHeight > APEX_HEIGHT * 2 ) + { + apexHeight = APEX_HEIGHT*2; + } + */ + + //FIXME: length of xy will change curve of parabola, need to account for this + //somewhere... PARA_WIDTH + + z = (sqrt(apexHeight + z) - sqrt(apexHeight)); + + assert(z >= 0); + +// gi.Printf("apex is %4.2f percent from p1: ", (xy-z)*0.5/xy*100.0f); + + xy -= z; + xy *= 0.5; + + assert(xy > 0); + + VectorMA( p1, xy, dir, apex ); + apex[2] += apexHeight; + + VectorCopy(apex, NPC->pos1); + + //Now we have the apex, aim for it + height = apex[2] - NPC->currentOrigin[2]; + time = sqrt( height / ( .5 * NPC->client->ps.gravity ) ); + if ( !time ) + { +// gi.Printf("ERROR no time in jump\n"); + return; + } + + // set s.origin2 to the push velocity + VectorSubtract ( apex, NPC->currentOrigin, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 0; + dist = VectorNormalize( NPC->client->ps.velocity ); + + forward = dist / time; + VectorScale( NPC->client->ps.velocity, forward, NPC->client->ps.velocity ); + + NPC->client->ps.velocity[2] = time * NPC->client->ps.gravity; + +// gi.Printf( "%s jumping %s, gravity at %4.0f percent\n", NPC->targetname, vtos(NPC->client->ps.velocity), NPC->client->ps.gravity/8.0f ); + + NPCInfo->jumpState = JS_JUMPING; + //FIXME: jumpsound? + break; + case JS_JUMPING: + + if ( showBBoxes ) + { + VectorAdd(NPC->mins, NPC->pos1, p1); + VectorAdd(NPC->maxs, NPC->pos1, p2); + CG_Cube( p1, p2, NPCDEBUG_BLUE, 0.5 ); + } + + if ( NPC->s.groundEntityNum != ENTITYNUM_NONE) + {//Landed, start landing anim + //FIXME: if the + VectorClear(NPC->client->ps.velocity); + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_LAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + NPCInfo->jumpState = JS_LANDING; + //FIXME: landsound? + } + else if ( NPC->client->ps.legsAnimTimer > 0 ) + {//Still playing jumping anim + //FIXME: apply jump velocity here, a couple frames after start, not right away + return; + } + else + {//still in air, but done with jump anim, play inair anim + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_INAIR1, SETANIM_FLAG_OVERRIDE); + } + break; + case JS_LANDING: + if ( NPC->client->ps.legsAnimTimer > 0 ) + {//Still playing landing anim + return; + } + else + { + NPCInfo->jumpState = JS_WAITING; + + NPCInfo->goalEntity = UpdateGoal(); + // If he made it to his goal or his task is no longer pending. + if ( !NPCInfo->goalEntity || !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + { + NPC_ClearGoal(); + NPCInfo->goalTime = level.time; + NPCInfo->aiFlags &= ~NPCAI_MOVING; + ucmd.forwardmove = 0; + NPC->flags &= ~FL_NO_KNOCKBACK; + //Return that the goal was reached + Q3_TaskIDComplete( NPC, TID_MOVE_NAV ); + } + } + break; + case JS_WAITING: + default: + NPCInfo->jumpState = JS_FACING; + break; + } +} + +void NPC_BSRemove (void) +{ + NPC_UpdateAngles ( qtrue, qtrue ); + if( !gi.inPVS( NPC->currentOrigin, g_entities[0].currentOrigin ) )//FIXME: use cg.vieworg? + { + G_UseTargets2( NPC, NPC, NPC->target3 ); + NPC->s.eFlags |= EF_NODRAW; + NPC->svFlags &= ~SVF_NPC; + NPC->s.eType = ET_INVISIBLE; + NPC->contents = 0; + NPC->health = 0; + NPC->targetname = NULL; + + //Disappear in half a second + NPC->e_ThinkFunc = thinkF_G_FreeEntity; + NPC->nextthink = level.time + FRAMETIME; + }//FIXME: else allow for out of FOV??? +} + +void NPC_BSSearch (void) +{ + NPC_CheckAlertEvents(qtrue, qtrue, -1, qfalse, AEL_DANGER, qfalse); + //FIXME: do something with these alerts...? + //FIXME: do the Stormtrooper alert reaction? (investigation) + if ( (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) + && NPC->client->enemyTeam != TEAM_NEUTRAL ) + {//look for enemies + NPC_CheckEnemy(qtrue, qfalse); + if ( NPC->enemy ) + {//found one + if( NPCInfo->tempBehavior == BS_SEARCH ) + {//if tempbehavior, set tempbehavior to default + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + {//if bState, change to run and shoot + NPCInfo->behaviorState = BS_DEFAULT;//BS_HUNT_AND_KILL; + //NPC_BSRunAndShoot(); + } + return; + } + } + + //FIXME: what if our goalEntity is not NULL and NOT our tempGoal - they must + //want us to do something else? If tempBehavior, just default, else set + //to run and shoot...? + + //FIXME: Reimplement + + if ( !NPCInfo->investigateDebounceTime ) + {//On our way to a tempGoal + float minGoalReachedDistSquared = 32*32; + vec3_t vec; + + //Keep moving toward our tempGoal + NPCInfo->goalEntity = NPCInfo->tempGoal; + + VectorSubtract ( NPCInfo->tempGoal->currentOrigin, NPC->currentOrigin, vec); + if ( vec[2] < 24 ) + { + vec[2] = 0; + } + + if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) + { + /* + //FIXME: can't get the radius... + float wpRadSq = waypoints[NPCInfo->tempGoal->waypoint].radius * waypoints[NPCInfo->tempGoal->waypoint].radius; + if ( minGoalReachedDistSquared > wpRadSq ) + { + minGoalReachedDistSquared = wpRadSq; + } + */ + + minGoalReachedDistSquared = 32*32;//12*12; + } + + if ( VectorLengthSquared( vec ) < minGoalReachedDistSquared ) + { + //Close enough, just got there + NPC->waypoint = NAV::GetNearestNode(NPC); + + if ( ( NPCInfo->homeWp == WAYPOINT_NONE ) || ( NPC->waypoint == WAYPOINT_NONE ) ) + { + //Heading for or at an invalid waypoint, get out of this bState + if( NPCInfo->tempBehavior == BS_SEARCH ) + {//if tempbehavior, set tempbehavior to default + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + {//if bState, change to stand guard + NPCInfo->behaviorState = BS_STAND_GUARD; + NPC_BSRunAndShoot(); + } + return; + } + + if ( NPC->waypoint == NPCInfo->homeWp ) + { + //Just Reached our homeWp, if this is the first time, run your lostenemyscript + if ( NPCInfo->aiFlags & NPCAI_ENROUTE_TO_HOMEWP ) + { + NPCInfo->aiFlags &= ~NPCAI_ENROUTE_TO_HOMEWP; + G_ActivateBehavior( NPC, BSET_LOSTENEMY ); + } + + } + + //gi.Printf("Got there.\n"); + //gi.Printf("Looking..."); + if( !Q_irand(0, 1) ) + { + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_NORMAL); + } + else + { + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL); + } + NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); + } + else + { + NPC_MoveToGoal( qtrue ); + } + } + else + { + //We're there + if ( NPCInfo->investigateDebounceTime > level.time ) + { + //Still waiting around for a bit + //Turn angles every now and then to look around + if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) + { + if ( !Q_irand( 0, 30 ) ) + { + // NAV_TODO: What if there are no neighbors? + vec3_t branchPos, lookDir; + + NAV::GetNodePosition(NAV::ChooseRandomNeighbor(NPCInfo->tempGoal->waypoint), branchPos); + + VectorSubtract( branchPos, NPCInfo->tempGoal->currentOrigin, lookDir ); + NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + Q_flrand( -45, 45 ) ); + } + } + //gi.Printf("."); + } + else + {//Just finished waiting + NPC->waypoint = NAV::GetNearestNode(NPC); + + if ( NPC->waypoint == NPCInfo->homeWp ) + { + // NAV_TODO: What if there are no neighbors? + + int nextWp = NAV::ChooseRandomNeighbor(NPCInfo->tempGoal->waypoint); + NAV::GetNodePosition(nextWp, NPCInfo->tempGoal->currentOrigin); + NPCInfo->tempGoal->waypoint = nextWp; + + } + else + {//At a branch, so return home + NAV::GetNodePosition(NPCInfo->homeWp, NPCInfo->tempGoal->currentOrigin); + NPCInfo->tempGoal->waypoint = NPCInfo->homeWp; + } + + NPCInfo->investigateDebounceTime = 0; + //Start moving toward our tempGoal + NPCInfo->goalEntity = NPCInfo->tempGoal; + NPC_MoveToGoal( qtrue ); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSSearchStart +------------------------- +*/ + +void NPC_BSSearchStart( int homeWp, bState_t bState ) +{ + //FIXME: Reimplement + NPCInfo->homeWp = homeWp; + NPCInfo->tempBehavior = bState; + NPCInfo->aiFlags |= NPCAI_ENROUTE_TO_HOMEWP; + NPCInfo->investigateDebounceTime = 0; + NAV::GetNodePosition(homeWp, NPCInfo->tempGoal->currentOrigin); + NPCInfo->tempGoal->waypoint = homeWp; + //gi.Printf("\nHeading for wp %d...\n", NPCInfo->homeWp); +} + +/* +------------------------- +NPC_BSNoClip + + Use in extreme circumstances only +------------------------- +*/ + +void NPC_BSNoClip ( void ) +{ + if ( UpdateGoal() ) + { + vec3_t dir, forward, right, angles, up = {0, 0, 1}; + float fDot, rDot, uDot; + + VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir ); + + vectoangles( dir, angles ); + NPCInfo->desiredYaw = angles[YAW]; + + AngleVectors( NPC->currentAngles, forward, right, NULL ); + + VectorNormalize( dir ); + + fDot = DotProduct(forward, dir) * 127; + rDot = DotProduct(right, dir) * 127; + uDot = DotProduct(up, dir) * 127; + + ucmd.forwardmove = floor(fDot); + ucmd.rightmove = floor(rDot); + ucmd.upmove = floor(uDot); + } + else + { + //Cut velocity? + VectorClear( NPC->client->ps.velocity ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +void NPC_BSWander (void) +{//FIXME: don't actually go all the way to the next waypoint, just move in fits and jerks...? + + NPC_CheckAlertEvents(qtrue, qtrue, -1, qfalse, AEL_DANGER, qfalse); + //FIXME: do something with these alerts...? + //FIXME: do the Stormtrooper alert reaction? (investigation) + if ( (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) + && NPC->client->enemyTeam != TEAM_NEUTRAL ) + {//look for enemies + NPC_CheckEnemy(qtrue, qfalse); + if ( NPC->enemy ) + {//found one + if( NPCInfo->tempBehavior == BS_WANDER ) + {//if tempbehavior, set tempbehavior to default + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + {//if bState, change to run and shoot + NPCInfo->behaviorState = BS_DEFAULT;//BS_HUNT_AND_KILL; + //NPC_BSRunAndShoot(); + } + return; + } + } + + STEER::Activate(NPC); + + + // Are We Doing A Path? + //---------------------- + bool HasPath = NAV::HasPath(NPC); + if (HasPath) + { + HasPath = NAV::UpdatePath(NPC); + if (HasPath) + { + STEER::Path(NPC); // Follow The Path + STEER::AvoidCollisions(NPC); + + if ((NPCInfo->aiFlags&NPCAI_BLOCKED) && (level.time-NPCInfo->blockedDebounceTime)>1000) + { + HasPath = false;// find a new one + } + } + } + + if (!HasPath) + { + // If Debounce Time Has Expired, Choose A New Sub State + //------------------------------------------------------ + if (NPCInfo->investigateDebounceTimeaiFlags&NPCAI_BLOCKED) && (level.time-NPCInfo->blockedDebounceTime)>1000)) + { + // Clear Out Flags From The Previous Substate + //-------------------------------------------- + NPCInfo->aiFlags &= ~NPCAI_OFF_PATH; + NPCInfo->aiFlags &= ~NPCAI_WALKING; + + + // Pick Another Spot + //------------------- + int NEXTSUBSTATE = Q_irand(0, 10); + + bool RandomPathNode = (NEXTSUBSTATE<9); //(NEXTSUBSTATE<4); + bool PathlessWander = false; //(NEXTSUBSTATE<9) + + + + // Random Path Node + //------------------ + if (RandomPathNode) + { + // Sometimes, Walk + //----------------- + if (Q_irand(0, 1)==0) + { + NPCInfo->aiFlags |= NPCAI_WALKING; + } + + NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); + NAV::FindPath(NPC, NAV::ChooseRandomNeighbor(NAV::GetNearestNode(NPC))); + } + + // Pathless Wandering + //-------------------- + else if (PathlessWander) + { + // Sometimes, Walk + //----------------- + if (Q_irand(0, 1)==0) + { + NPCInfo->aiFlags |= NPCAI_WALKING; + } + + NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); + NPCInfo->aiFlags |= NPCAI_OFF_PATH; + } + + // Just Stand Here + //----------------- + else + { + NPCInfo->investigateDebounceTime = level.time + Q_irand(2000, 10000); + NPC_SetAnim(NPC, SETANIM_BOTH, ((Q_irand(0, 1)==0)?(BOTH_GUARD_LOOKAROUND1):(BOTH_GUARD_IDLE1)), SETANIM_FLAG_NORMAL); + } + } + + // Ok, So We Don't Have A Path, And Debounce Time Is Still Active, So We Are Either Wandering Or Looking Around + //-------------------------------------------------------------------------------------------------------------- + else + { + if (NPCInfo->aiFlags & NPCAI_OFF_PATH) + { + STEER::Wander(NPC); + STEER::AvoidCollisions(NPC); + } + else + { + STEER::Stop(NPC); + } + } + } + STEER::DeActivate(NPC, &ucmd); + + NPC_UpdateAngles( qtrue, qtrue ); + return; +} + +/* +void NPC_BSFaceLeader (void) +{ + vec3_t head, leaderHead, delta, angleToLeader; + + if ( !NPC->client->leader ) + {//uh.... okay. + return; + } + + CalcEntitySpot( NPC->client->leader, SPOT_HEAD, leaderHead ); + CalcEntitySpot( NPC, SPOT_HEAD, head ); + VectorSubtract( leaderHead, head, delta ); + vectoangles( delta, angleToLeader ); + VectorNormalize( delta ); + NPC->NPC->desiredYaw = angleToLeader[YAW]; + NPC->NPC->desiredPitch = angleToLeader[PITCH]; + + NPC_UpdateAngles(qtrue, qtrue); +} +*/ +/* +------------------------- +NPC_BSFlee +------------------------- +*/ +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void WP_DropWeapon( gentity_t *dropper, vec3_t velocity ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern int g_crosshairEntNum; +qboolean NPC_CanSurrender( void ) +{ + if ( NPC->client ) + { + switch ( NPC->client->NPC_class ) + { + case CLASS_ATST: + case CLASS_CLAW: + case CLASS_DESANN: + case CLASS_FISH: + case CLASS_FLIER2: + case CLASS_GALAK: + case CLASS_GLIDER: + case CLASS_GONK: // droid + case CLASS_HOWLER: + case CLASS_RANCOR: + case CLASS_SAND_CREATURE: + case CLASS_WAMPA: + case CLASS_INTERROGATOR: // droid + case CLASS_JAN: + case CLASS_JEDI: + case CLASS_KYLE: + case CLASS_LANDO: + case CLASS_LIZARD: + case CLASS_LUKE: + case CLASS_MARK1: // droid + case CLASS_MARK2: // droid + case CLASS_GALAKMECH: // droid + case CLASS_MINEMONSTER: + case CLASS_MONMOTHA: + case CLASS_MORGANKATARN: + case CLASS_MOUSE: // droid + case CLASS_MURJJ: + case CLASS_PROBE: // droid + case CLASS_PROTOCOL: // droid + case CLASS_R2D2: // droid + case CLASS_R5D2: // droid + case CLASS_REBORN: + case CLASS_REELO: + case CLASS_REMOTE: + case CLASS_SEEKER: // droid + case CLASS_SENTRY: + case CLASS_SHADOWTROOPER: + case CLASS_SWAMP: + case CLASS_TAVION: + case CLASS_ALORA: + case CLASS_TUSKEN: + case CLASS_BOBAFETT: + case CLASS_ROCKETTROOPER: + case CLASS_SABER_DROID: + case CLASS_ASSASSIN_DROID: + case CLASS_HAZARD_TROOPER: + case CLASS_PLAYER: + case CLASS_VEHICLE: + return qfalse; + break; + } + if ( !G_StandardHumanoid( NPC ) ) + { + return qfalse; + } + if ( NPC->client->ps.weapon == WP_SABER ) + { + return qfalse; + } + } + if ( NPCInfo ) + { + if ( (NPCInfo->aiFlags&NPCAI_BOSS_CHARACTER) ) + { + return qfalse; + } + if ( (NPCInfo->aiFlags&NPCAI_SUBBOSS_CHARACTER) ) + { + return qfalse; + } + if ( (NPCInfo->aiFlags&NPCAI_ROSH) ) + { + return qfalse; + } + if ( (NPCInfo->aiFlags&NPCAI_HEAL_ROSH) ) + { + return qfalse; + } + } + return qtrue; +} + +void NPC_Surrender( void ) +{//FIXME: say "don't shoot!" if we weren't already surrendering + if ( NPC->client->ps.weaponTime || PM_InKnockDown( &NPC->client->ps ) ) + { + return; + } + if ( !NPC_CanSurrender() ) + { + return; + } + if ( NPC->s.weapon != WP_NONE && + NPC->s.weapon != WP_MELEE && + NPC->s.weapon != WP_SABER ) + { + WP_DropWeapon( NPC, NULL ); + } + if ( NPCInfo->surrenderTime < level.time - 5000 ) + {//haven't surrendered for at least 6 seconds, tell them what you're doing + //FIXME: need real dialogue EV_SURRENDER + NPCInfo->blockedSpeechDebounceTime = 0;//make sure we say this + G_AddVoiceEvent( NPC, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 3000 ); + } + + // Already Surrendering? If So, Just Update Animations + //------------------------------------------------------ + if (NPCInfo->surrenderTime>level.time) + { + if (NPC->client->ps.torsoAnim==BOTH_COWER1_START && NPC->client->ps.torsoAnimTimer<=100) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_COWER1, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + NPCInfo->surrenderTime = level.time + NPC->client->ps.torsoAnimTimer; + } + if (NPC->client->ps.torsoAnim==BOTH_COWER1 && NPC->client->ps.torsoAnimTimer<=100) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_COWER1_STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + NPCInfo->surrenderTime = level.time + NPC->client->ps.torsoAnimTimer; + } + } + + // New To The Surrender, So Start The Animation + //---------------------------------------------- + else + { + if ( NPC->client->NPC_class == CLASS_JAWA && NPC->client->ps.weapon == WP_NONE ) + {//an unarmed Jawa is very scared + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_COWER1, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + //FIXME: stop doing this if decide to take off and run + } + else + { + // A Big Monster? OR: Being Tracked By A Homing Rocket? So Do The Cower Sequence + //------------------------------------------ + if ( (NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class==CLASS_RANCOR) || !TIMER_Done( NPC, "rocketChasing" ) ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_COWER1_START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + } + + // Otherwise, Use The Old Surrender "Arms In Air" Animation + //---------------------------------------------------------- + else + { + NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + NPC->client->ps.torsoAnimTimer = Q_irand(3000, 8000); // Pretend the anim lasts longer + } + } + NPCInfo->surrenderTime = level.time + NPC->client->ps.torsoAnimTimer + 1000; + } +} + +qboolean NPC_CheckSurrender( void ) +{ + if ( !g_AIsurrender->integer + && NPC->client->NPC_class != CLASS_UGNAUGHT + && NPC->client->NPC_class != CLASS_JAWA ) + {//not enabled + return qfalse; + } + if ( !Q3_TaskIDPending( NPC, TID_MOVE_NAV ) //not scripted to go somewhere + && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE //not in the air + && !NPC->client->ps.weaponTime && !PM_InKnockDown( &NPC->client->ps )//not firing and not on the ground + && NPC->enemy && NPC->enemy->client && NPC->enemy->enemy == NPC && NPC->enemy->s.weapon != WP_NONE && (NPC->enemy->s.weapon != WP_MELEE || (NPC->enemy->client->NPC_class == CLASS_RANCOR||NPC->enemy->client->NPC_class == CLASS_WAMPA) )//enemy is using a weapon or is a Rancor or Wampa + && NPC->enemy->health > 20 && NPC->enemy->painDebounceTime < level.time - 3000 && NPC->enemy->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time - 1000 ) + {//don't surrender if scripted to run somewhere or if we're in the air or if we're busy or if we don't have an enemy or if the enemy is not mad at me or is hurt or not a threat or busy being attacked + //FIXME: even if not in a group, don't surrender if there are other enemies in the PVS and within a certain range? + if ( NPC->s.weapon != WP_ROCKET_LAUNCHER + && NPC->s.weapon != WP_CONCUSSION + && NPC->s.weapon != WP_REPEATER + && NPC->s.weapon != WP_FLECHETTE + && NPC->s.weapon != WP_SABER ) + {//jedi and heavy weapons guys never surrender + //FIXME: rework all this logic into some orderly fashion!!! + if ( NPC->s.weapon != WP_NONE ) + {//they have a weapon so they'd have to drop it to surrender + //don't give up unless low on health + if ( NPC->health > 25 || NPC->health >= NPC->max_health ) + { + return qfalse; + } + if ( g_crosshairEntNum == NPC->s.number && NPC->painDebounceTime > level.time ) + {//if he just shot me, always give up + //fall through + } + else + {//don't give up unless facing enemy and he's very close + if ( !InFOV( player, NPC, 60, 30 ) ) + {//I'm not looking at them + return qfalse; + } + else if ( DistanceSquared( NPC->currentOrigin, player->currentOrigin ) < 65536/*256*256*/ ) + {//they're not close + return qfalse; + } + else if ( !gi.inPVS( NPC->currentOrigin, player->currentOrigin ) ) + {//they're not in the same room + return qfalse; + } + } + } + if ( !NPCInfo->group || (NPCInfo->group && NPCInfo->group->numGroup <= 1) ) + {//I'm alone but I was in a group//FIXME: surrender anyway if just melee or no weap? + if ( NPC->s.weapon == WP_NONE + //NPC has a weapon + || NPC->enemy == player + || (NPC->enemy->s.weapon == WP_SABER&&NPC->enemy->client&&NPC->enemy->client->ps.SaberActive()) + || (NPC->enemy->NPC && NPC->enemy->NPC->group && NPC->enemy->NPC->group->numGroup > 2) ) + {//surrender only if have no weapon or fighting a player or jedi or if we are outnumbered at least 3 to 1 + if ( NPC->enemy == player ) + {//player is the guy I'm running from + if ( g_crosshairEntNum == NPC->s.number ) + {//give up if player is aiming at me + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + else if ( player->s.weapon == WP_SABER ) + {//player is using saber + if ( InFOV( NPC, player, 60, 30 ) ) + {//they're looking at me + if ( DistanceSquared( NPC->currentOrigin, player->currentOrigin ) < 16384/*128*128*/ ) + {//they're close + if ( gi.inPVS( NPC->currentOrigin, player->currentOrigin ) ) + {//they're in the same room + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + } + } + } + else if ( NPC->enemy ) + {//??? + //should NPC's surrender to others? + if ( InFOV( NPC, NPC->enemy, 30, 30 ) ) + {//they're looking at me + float maxDist = (64+(NPC->maxs[0]*1.5)+(NPC->enemy->maxs[0]*1.5)); + maxDist *= maxDist; + if ( DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ) < maxDist ) + {//they're close + if ( gi.inPVS( NPC->currentOrigin, NPC->enemy->currentOrigin ) ) + {//they're in the same room + //FIXME: should player-team NPCs not fire on surrendered NPCs? + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + } + } + } + } + } + } + return qfalse; +} + +void NPC_JawaFleeSound( void ) +{ + if ( NPC + && NPC->client + && NPC->client->NPC_class == CLASS_JAWA + && !Q_irand( 0, 3 ) + && NPCInfo->blockedSpeechDebounceTime < level.time + && !Q3_TaskIDPending(NPC, TID_CHAN_VOICE ) ) + {//ooteenee!!!! + //Com_Printf( "ooteenee!!!!\n" ); + G_SoundOnEnt(NPC, CHAN_VOICE, "sound/chars/jawa/misc/ooh-tee-nee.wav" ); + NPCInfo->blockedSpeechDebounceTime = level.time + 2000; + } +} + +extern gentity_t *NPC_SearchForWeapons( void ); +extern qboolean G_CanPickUpWeapons( gentity_t *other ); +qboolean NPC_BSFlee( void ) +{ + bool enemyRecentlySeen = false; + float enemyTooCloseDist = 50.0f; + bool reachedEscapePoint = false; + bool hasEscapePoint = false; + bool moveSuccess = false; + bool inSurrender = (level.timesurrenderTime); + + + + // Check For Enemies And Alert Events + //------------------------------------ + NPC_CheckEnemy(qtrue, qfalse); + NPC_CheckAlertEvents(qtrue, qtrue, -1, qfalse, AEL_DANGER, qfalse); + if (NPC->enemy && G_ClearLOS(NPC, NPC->enemy)) + { + NPCInfo->enemyLastSeenTime = level.time; + } + enemyRecentlySeen = (NPC->enemy && (level.time - NPCInfo->enemyLastSeenTime)<3000); + if (enemyRecentlySeen) + { + if (NPC->enemy->client && NPC->enemy->client->NPC_class==CLASS_RANCOR) + { + enemyTooCloseDist = 400.0f; + } + enemyTooCloseDist += NPC->maxs[0] + NPC->enemy->maxs[0]; + } + + + // Look For Weapons To Pick Up + //----------------------------- + if (enemyRecentlySeen && // Is There An Enemy Near? + NPC->client->NPC_class!=CLASS_PRISONER && // Prisoners can't pickup weapons + NPCInfo->rank>RANK_CIVILIAN && // Neither can civilians + TIMER_Done(NPC, "panic") && // Panic causes him to run for a bit, don't pickup weapons + TIMER_Done(NPC, "CheckForWeaponToPickup") && + G_CanPickUpWeapons( NPC ) //Allowed To Pick Up Dropped Weapons + ) + { + gentity_t *foundWeap = NPC_SearchForWeapons(); + + // Ok, There Is A Weapon! Try Going To It! + //------------------------------------------ + if (foundWeap && NAV::SafePathExists(NPC->currentOrigin, foundWeap->currentOrigin, NPC->enemy->currentOrigin, 150.0f)) + { + NAV::ClearPath(NPC); // Remove Any Old Path + + NPCInfo->goalEntity = foundWeap; // Change Our Target Goal + NPCInfo->goalRadius = 30.0f; // 30 good enough? + + TIMER_Set(NPC, "CheckForWeaponToPickup", Q_irand(10000, 50000)); + } + + // Look Again Soon + //----------------- + else + { + TIMER_Set(NPC, "CheckForWeaponToPickup", Q_irand(1000, 5000)); + } + } + + // If Attempting To Get To An Entity That Is Gone, Clear The Pointer + //------------------------------------------------------------------- + if ( NPCInfo->goalEntity + && !Q3_TaskIDPending(NPC, TID_MOVE_NAV) + && NPC->enemy + && Distance( NPCInfo->goalEntity->currentOrigin, NPC->enemy->currentOrigin ) < enemyTooCloseDist ) + { + //our goal is too close to our enemy, dump it... + NPCInfo->goalEntity = NULL; + } + if (NPCInfo->goalEntity && !NPCInfo->goalEntity->inuse) + { + NPCInfo->goalEntity = 0; + } + hasEscapePoint = (NPCInfo->goalEntity && NPCInfo->goalRadius!=0.0f); + + + + + STEER::Activate(NPC); + { + // Have We Reached The Escape Point? + //----------------------------------- + if (hasEscapePoint && STEER::Reached(NPC, NPCInfo->goalEntity, NPCInfo->goalRadius, false)) + { + if (Q3_TaskIDPending(NPC, TID_MOVE_NAV)) + { + Q3_TaskIDComplete(NPC, TID_MOVE_NAV); + } + reachedEscapePoint = true; + } + + + // If Super Close To The Enemy, Run In The Other Direction + //--------------------------------------------------------- + if (enemyRecentlySeen && + Distance(NPC->enemy->currentOrigin, NPC->currentOrigin)enemy); + STEER::AvoidCollisions(NPC); + } + + // If Already At The Escape Point, Or Surrendering, Don't Move + //------------------------------------------------------------- + else if (reachedEscapePoint || inSurrender) + { + STEER::Stop(NPC); + } + else + { + // Try To Get To The Escape Point + //-------------------------------- + if (hasEscapePoint) + { + moveSuccess = STEER::GoTo(NPC, NPCInfo->goalEntity, true); + if (!moveSuccess) + { + moveSuccess = NAV::GoTo(NPC, NPCInfo->goalEntity, 0.3f); + } + } + + // Cant Get To The Escape Point, So If There Is An Enemy + //------------------------------------------------------- + if (!moveSuccess && enemyRecentlySeen) + { + // Try To Get To The Farthest Combat Point From Him + //-------------------------------------------------- + NAV::TNodeHandle Nbr = NAV::ChooseFarthestNeighbor(NPC, NPC->enemy->currentOrigin, 0.25f); + if (Nbr>0) + { + moveSuccess = STEER::GoTo(NPC, NAV::GetNodePosition(Nbr), true); + if (!moveSuccess) + { + moveSuccess = NAV::GoTo(NPC, Nbr, 0.3f); + } + } + } + + // If We Still Can't (Or Don't Need To) Move, Just Stop + //------------------------------------------------------ + if (!moveSuccess) + { + STEER::Stop(NPC); + } + } + } + STEER::DeActivate(NPC, &ucmd); + + + // Is There An Enemy Around? + //--------------------------- + if (enemyRecentlySeen) + { + // Time To Surrender? + //-------------------- + if ( TIMER_Done( NPC, "panic" ) ) + { + //done panicking, time to realize we're dogmeat, if we haven't been able to flee for a few seconds + if ((level.time-NPC->lastMoveTime)>3000 + && (level.time-NPCInfo->surrenderTime) > 3000 )//and haven't just finished surrendering + { + NPC_FaceEnemy(); + NPC_Surrender(); + } + } + + // Time To Choose A New Escape Point? + //------------------------------------ + if ((!hasEscapePoint || reachedEscapePoint) && TIMER_Done(NPC, "FindNewEscapePointDebounce")) + { + TIMER_Set(NPC, "FindNewEscapePointDebounce", 2500); + + int escapePoint = NPC_FindCombatPoint( + NPC->currentOrigin, + NPC->enemy->currentOrigin, + NPC->currentOrigin, + CP_COVER|CP_AVOID_ENEMY|CP_HAS_ROUTE, + 128 ); + if (escapePoint!=-1) + { + NPC_JawaFleeSound(); + NPC_SetCombatPoint(escapePoint); + NPC_SetMoveGoal(NPC, level.combatPoints[escapePoint].origin, 8, qtrue, escapePoint); + } + } + } + + + // If Only Temporarly In Flee, Think About Perhaps Returning To Combat + //--------------------------------------------------------------------- + if (NPCInfo->tempBehavior==BS_FLEE && + TIMER_Done(NPC, "flee") && + NPC->s.weapon != WP_NONE && + NPC->s.weapon != WP_MELEE + ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + + // Always Update Angles + //---------------------- + NPC_UpdateAngles( qtrue, qtrue ); + if (reachedEscapePoint) + { + return qtrue; + } + return qfalse; +} + +void NPC_StartFlee( gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ) +{ + if ( Q3_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//running somewhere that a script requires us to go, don't interrupt that! + return; + } + + if (NPCInfo->scriptFlags & SCF_DONT_FLEE ) // no flee for you + { + return; + } + + + //if have a fleescript, run that instead + if ( G_ActivateBehavior( NPC, BSET_FLEE ) ) + { + return; + } + + //FIXME: play a flee sound? Appropriate to situation? + if ( enemy ) + { + NPC_JawaFleeSound(); + G_SetEnemy( NPC, enemy ); + } + + + //FIXME: if don't have a weapon, find nearest one we have a route to and run for it? + int cp = -1; + if ( dangerLevel > AEL_DANGER || NPC->s.weapon == WP_NONE || ((!NPCInfo->group || NPCInfo->group->numGroup <= 1) && NPC->health <= 10 ) ) + {//IF either great danger OR I have no weapon OR I'm alone and low on health, THEN try to find a combat point out of PVS + cp = NPC_FindCombatPoint( NPC->currentOrigin, dangerPoint, NPC->currentOrigin, CP_COVER|CP_AVOID|CP_HAS_ROUTE|CP_NO_PVS, 128 ); + } + //FIXME: still happens too often... + if ( cp == -1 ) + {//okay give up on the no PVS thing + cp = NPC_FindCombatPoint( NPC->currentOrigin, dangerPoint, NPC->currentOrigin, CP_COVER|CP_AVOID|CP_HAS_ROUTE, 128 ); + if ( cp == -1 ) + {//okay give up on the avoid + cp = NPC_FindCombatPoint( NPC->currentOrigin, dangerPoint, NPC->currentOrigin, CP_COVER|CP_HAS_ROUTE, 128 ); + if ( cp == -1 ) + {//okay give up on the cover + cp = NPC_FindCombatPoint( NPC->currentOrigin, dangerPoint, NPC->currentOrigin, CP_HAS_ROUTE, 128 ); + } + } + } + + //see if we got a valid one + if ( cp != -1 ) + {//found a combat point + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp ); + } + else + {//couldn't find a place to hide + //FIXME: re-implement the old BS_FLEE behavior of following any the waypoint edge + // that leads away from the danger point. + NPC_SetMoveGoal( NPC, NPC->currentOrigin, 0/*goalRadius*/, qtrue, cp ); + } + + if ( dangerLevel > AEL_DANGER//geat danger always makes people turn and run + || NPC->s.weapon == WP_NONE //melee/unarmed guys turn and run, others keep facing you and shooting + || NPC->s.weapon == WP_MELEE + || NPC->s.weapon == WP_TUSKEN_STAFF ) + { + NPCInfo->tempBehavior = BS_FLEE;//we don't want to do this forever! + //FIXME: only make it temp if you have a weapon? Otherwise, permanent? + // NPCInfo->behaviorState = BS_FLEE; + // NPCInfo->tempBehavior = BS_DEFAULT; + } + + //FIXME: localize this Timer? + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + //FIXME: is this always applicable? + NPCInfo->squadState = SQUAD_RETREAT; + TIMER_Set( NPC, "flee", Q_irand( fleeTimeMin, fleeTimeMax ) ); + TIMER_Set( NPC, "panic", Q_irand( 1000, 4000 ) );//how long to wait before trying to nav to a dropped weapon + TIMER_Set( NPC, "duck", 0 ); +} + +void G_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ) +{ + if ( !self->NPC ) + {//player + return; + } + SaveNPCGlobals(); + SetNPCGlobals( self ); + + NPC_StartFlee( enemy, dangerPoint, dangerLevel, fleeTimeMin, fleeTimeMax ); + + RestoreNPCGlobals(); +} + +void NPC_BSEmplaced( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt() == qfalse ) + { + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredYaw = NPC->s.angles[1] + Q_irand( -90, 90 ); + } + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredPitch = Q_irand( -20, 20 ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + qboolean enemyLOS = qfalse; + qboolean enemyCS = qfalse; + qboolean faceEnemy = qfalse; + qboolean shoot = qfalse; + vec3_t impactPos; + + if ( NPC_ClearLOS( NPC->enemy ) ) + { + enemyLOS = qtrue; + + int hit = NPC_ShotEntity( NPC->enemy, impactPos ); + gentity_t *hitEnt = &g_entities[hit]; + + if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->takedamage ) ) + {//can hit enemy or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway + enemyCS = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + } +/* + else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } +*/ + + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + if ( enemyCS ) + { + shoot = qtrue; + } + + if ( faceEnemy ) + {//face the enemy + NPC_FaceEnemy( qtrue ); + } + else + {//we want to face in the dir we're running + NPC_UpdateAngles( qtrue, qtrue ); + } + + if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + { + shoot = qfalse; + } + + if ( NPC->enemy && NPC->enemy->enemy ) + { + if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) + {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) + shoot = qfalse; + } + } + if ( shoot ) + {//try to shoot if it's time + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + } +} \ No newline at end of file diff --git a/code/game/NPC_combat.cpp b/code/game/NPC_combat.cpp new file mode 100644 index 0000000..a767c18 --- /dev/null +++ b/code/game/NPC_combat.cpp @@ -0,0 +1,3317 @@ +//NPC_combat.cpp + +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + + + +#include "b_local.h" +#include "g_nav.h" +#include "g_navigator.h" +#include "wp_saber.h" + +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void G_SetEnemy( gentity_t *self, gentity_t *enemy ); +extern qboolean NPC_CheckLookTarget( gentity_t *self ); +extern void NPC_ClearLookTarget( gentity_t *self ); +extern void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy ); +extern qboolean PM_DroidMelee( int npc_class ); +extern int delayedShutDown; +extern qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy ); + +void ChangeWeapon( gentity_t *ent, int newWeapon ); + +void G_ClearEnemy (gentity_t *self) +{ + NPC_CheckLookTarget( self ); + + if ( self->enemy ) + { + if ( ( G_ValidEnemy(self, self->enemy ) ) && ( self->svFlags & SVF_LOCKEDENEMY ) ) + { + return; + } + + + if( self->client && self->client->renderInfo.lookTarget == self->enemy->s.number ) + { + NPC_ClearLookTarget( self ); + } + + if ( self->NPC && self->enemy == self->NPC->goalEntity ) + { + self->NPC->goalEntity = NULL; + } + //FIXME: set last enemy? + } + + self->enemy = NULL; +} + +/* +------------------------- +NPC_AngerAlert +------------------------- +*/ + +#define ANGER_ALERT_RADIUS 512 +#define ANGER_ALERT_SOUND_RADIUS 256 + +void G_AngerAlert( gentity_t *self ) +{ + if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) ) + {//I'm not a team playa... + return; + } + if ( !TIMER_Done( self, "interrogating" ) ) + {//I'm interrogating, don't wake everyone else up yet... FIXME: this may never wake everyone else up, though! + return; + } + //FIXME: hmm.... with all the other new alerts now, is this still neccesary or even a good idea...? + G_AlertTeam( self, self->enemy, ANGER_ALERT_RADIUS, ANGER_ALERT_SOUND_RADIUS ); +} + +/* +------------------------- +G_TeamEnemy +------------------------- +*/ + +qboolean G_TeamEnemy( gentity_t *self ) +{//FIXME: Probably a better way to do this, is a linked list of your teammates already available? + int i; + gentity_t *ent; + + if ( !self->client || self->client->playerTeam == TEAM_FREE ) + { + return qfalse; + } + if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) ) + {//I'm not a team playa... + return qfalse; + } + + for( i = 1; i < MAX_GENTITIES; i++ ) + { + ent = &g_entities[i]; + + if ( ent == self ) + { + continue; + } + + if ( ent->health <= 0 ) + { + continue; + } + + if ( !ent->client ) + { + continue; + } + + if ( ent->client->playerTeam != self->client->playerTeam ) + {//ent is not on my team + continue; + } + + if ( ent->enemy ) + {//they have an enemy + if ( !ent->enemy->client || ent->enemy->client->playerTeam != self->client->playerTeam ) + {//the ent's enemy is either a normal ent or is a player/NPC that is not on my team + return qtrue; + } + } + } + + return qfalse; +} + +qboolean G_CheckSaberAllyAttackDelay( gentity_t *self, gentity_t *enemy ) +{ + if ( !self || !self->enemy ) + { + return qfalse; + } + if ( self->NPC + && self->client->leader == player + && self->enemy + && self->enemy->s.weapon != WP_SABER + && self->s.weapon == WP_SABER ) + {//assisting the player and I'm using a saber and my enemy is not + TIMER_Set( self, "allyJediDelay", -level.time ); + //use the distance to the enemy to determine how long to delay + float distance = Distance( enemy->currentOrigin, self->currentOrigin ); + if ( distance < 256 ) + { + return qtrue; + } + int delay = 500; + if ( distance > 2048 ) + {//the farther they are, the shorter the delay + delay = 5000-floor(distance);//(6-g_spskill->integer)); + if ( delay < 500 ) + { + delay = 500; + } + } + else + {//the close they are, the shorter the delay + delay = floor(distance*4);//(6-g_spskill->integer)); + if ( delay > 5000 ) + { + delay = 5000; + } + } + TIMER_Set( self, "allyJediDelay", delay ); + + return qtrue; + } + return qfalse; +} + +void G_AttackDelay( gentity_t *self, gentity_t *enemy ) +{ + if ( enemy && self->client && self->NPC ) + {//delay their attack based on how far away they're facing from enemy + vec3_t fwd, dir; + int attDelay; + + VectorSubtract( self->client->renderInfo.eyePoint, enemy->currentOrigin, dir );//purposely backwards + VectorNormalize( dir ); + AngleVectors( self->client->renderInfo.eyeAngles, fwd, NULL, NULL ); + //dir[2] = fwd[2] = 0;//ignore z diff? + + attDelay = (4-g_spskill->integer)*500;//initial: from 1000ms delay on hard to 2000ms delay on easy + if ( self->client->playerTeam == TEAM_PLAYER ) + {//invert + attDelay = 2000-attDelay; + } + attDelay += floor( (DotProduct( fwd, dir )+1.0f) * 2000.0f );//add up to 4000ms delay if they're facing away + + //FIXME: should distance matter, too? + + //Now modify the delay based on NPC_class, weapon, and team + //NOTE: attDelay should be somewhere between 1000 to 6000 milliseconds + switch ( self->client->NPC_class ) + { + case CLASS_IMPERIAL://they give orders and hang back + attDelay += Q_irand( 500, 1500 ); + break; + case CLASS_STORMTROOPER://stormtroopers shoot sooner + if ( self->NPC->rank >= RANK_LT ) + {//officers shoot even sooner + attDelay -= Q_irand( 500, 1500 ); + } + else + {//normal stormtroopers don't have as fast reflexes as officers + attDelay -= Q_irand( 0, 1000 ); + } + break; + case CLASS_SWAMPTROOPER://shoot very quickly? What about guys in water? + attDelay -= Q_irand( 1000, 2000 ); + break; + case CLASS_IMPWORKER://they panic, don't fire right away + attDelay += Q_irand( 1000, 2500 ); + break; + case CLASS_TRANDOSHAN: + attDelay -= Q_irand( 500, 1500 ); + break; + case CLASS_JAN: + case CLASS_LANDO: + case CLASS_PRISONER: + case CLASS_REBEL: + attDelay -= Q_irand( 500, 1500 ); + break; + case CLASS_GALAKMECH: + case CLASS_ATST: + attDelay -= Q_irand( 1000, 2000 ); + break; + case CLASS_REELO: + case CLASS_UGNAUGHT: + case CLASS_JAWA: + return; + break; + case CLASS_MINEMONSTER: + case CLASS_MURJJ: + return; + break; + case CLASS_INTERROGATOR: + case CLASS_PROBE: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_SENTRY: + return; + break; + case CLASS_REMOTE: + case CLASS_SEEKER: + return; + break; + /* + case CLASS_GRAN: + case CLASS_RODIAN: + case CLASS_WEEQUAY: + case CLASS_TUSKEN: + break; + case CLASS_JEDI: + case CLASS_SHADOWTROOPER: + case CLASS_TAVION: + case CLASS_REBORN: + case CLASS_LUKE: + case CLASS_KYLE: + case CLASS_DESANN: + break; + */ + } + + switch ( self->s.weapon ) + { + case WP_NONE: + case WP_SABER: + return; + break; + case WP_BRYAR_PISTOL: + break; + case WP_BLASTER: + if ( self->NPC->scriptFlags & SCF_ALT_FIRE ) + {//rapid-fire blasters + attDelay += Q_irand( 0, 500 ); + } + else + {//regular blaster + attDelay -= Q_irand( 0, 500 ); + } + break; + case WP_BOWCASTER: + attDelay += Q_irand( 0, 500 ); + break; + case WP_REPEATER: + if ( !(self->NPC->scriptFlags&SCF_ALT_FIRE) ) + {//rapid-fire blasters + attDelay += Q_irand( 0, 500 ); + } + break; + case WP_FLECHETTE: + attDelay += Q_irand( 500, 1500 ); + break; + case WP_ROCKET_LAUNCHER: + attDelay += Q_irand( 500, 1500 ); + break; + case WP_CONCUSSION: + attDelay += Q_irand( 500, 1500 ); + break; + case WP_BLASTER_PISTOL: // apparently some enemy only version of the blaster + attDelay -= Q_irand( 500, 1500 ); + break; + case WP_DISRUPTOR://sniper's don't delay? + return; + break; + case WP_THERMAL://grenade-throwing has a built-in delay + return; + break; + case WP_MELEE: // Any ol' melee attack + return; + break; + case WP_EMPLACED_GUN: + return; + break; + case WP_TURRET: // turret guns + return; + break; + case WP_BOT_LASER: // Probe droid - Laser blast + return; + break; + case WP_NOGHRI_STICK: + attDelay += Q_irand( 0, 500 ); + break; + /* + case WP_DEMP2: + break; + case WP_TRIP_MINE: + break; + case WP_DET_PACK: + break; + case WP_STUN_BATON: + break; + case WP_ATST_MAIN: + break; + case WP_ATST_SIDE: + break; + case WP_TIE_FIGHTER: + break; + case WP_RAPID_FIRE_CONC: + break; + */ + } + + if ( self->client->playerTeam == TEAM_PLAYER ) + {//clamp it + if ( attDelay > 2000 ) + { + attDelay = 2000; + } + } + + //don't shoot right away + if ( attDelay > 4000+((2-g_spskill->integer)*3000) ) + { + attDelay = 4000+((2-g_spskill->integer)*3000); + } + TIMER_Set( self, "attackDelay", attDelay );//Q_irand( 1500, 4500 ) ); + //don't move right away either + if ( attDelay > 4000 ) + { + attDelay = 4000 - Q_irand(500, 1500); + } + else + { + attDelay -= Q_irand(500, 1500); + } + + TIMER_Set( self, "roamTime", attDelay );//was Q_irand( 1000, 3500 ); + } +} +/* +------------------------- +G_SetEnemy +------------------------- +*/ +extern gentity_t *G_CheckControlledTurretEnemy(gentity_t *self, gentity_t *enemy, qboolean validate ); + +void Saboteur_Cloak( gentity_t *self ); +void G_AimSet( gentity_t *self, int aim ); +void G_SetEnemy( gentity_t *self, gentity_t *enemy ) +{ + int event = 0; + + //Must be valid + if ( enemy == NULL ) + return; + + //Must be valid + if ( enemy->inuse == 0 ) + { + return; + } + + enemy = G_CheckControlledTurretEnemy(self, enemy, qtrue); + if (!enemy) + { + return; + } + + //Don't take the enemy if in notarget + if ( enemy->flags & FL_NOTARGET ) + return; + + if ( !self->NPC ) + { + self->enemy = enemy; + return; + } + + if ( self->NPC->confusionTime > level.time ) + {//can't pick up enemies if confused + return; + } + +#ifdef _DEBUG + if ( self->s.number ) + { + assert( enemy != self ); + } +#endif// _DEBUG + +// if ( enemy->client && enemy->client->playerTeam == TEAM_DISGUISE ) +// {//unmask the player +// enemy->client->playerTeam = TEAM_PLAYER; +// } + + if ( self->client && self->NPC && enemy->client && enemy->client->playerTeam == self->client->playerTeam ) + {//Probably a damn script! + if ( self->NPC->charmedTime > level.time ) + {//Probably a damn script! + return; + } + } + + if ( self->NPC && self->client && self->client->ps.weapon == WP_SABER ) + { + //when get new enemy, set a base aggression based on what that enemy is using, how far they are, etc. + NPC_Jedi_RateNewEnemy( self, enemy ); + } + + //NOTE: this is not necessarily true! + //self->NPC->enemyLastSeenTime = level.time; + + if ( self->enemy == NULL ) + { + //TEMP HACK: turn on our saber + if ( self->health > 0 ) + { + self->client->ps.SaberActivate(); + } + + //FIXME: Have to do this to prevent alert cascading + G_ClearEnemy( self ); + self->enemy = enemy; + if (self->client && self->client->NPC_class == CLASS_SABOTEUR) + { + Saboteur_Cloak(NPC); // Cloak + TIMER_Set(self, "decloakwait", 3000); // Wait 3 sec before decloak and attack + } + + + //Special case- if player is being hunted by his own people, set the player's team to team_free + if ( self->client->playerTeam == TEAM_PLAYER + && enemy->s.number == 0 + && enemy->client + && enemy->client->playerTeam == TEAM_PLAYER ) + {//make the player "evil" so that everyone goes after him + enemy->client->enemyTeam = TEAM_FREE; + enemy->client->playerTeam = TEAM_FREE; + } + + //If have an anger script, run that instead of yelling + if( G_ActivateBehavior( self, BSET_ANGER ) ) + { + } + else if ( self->client + && self->client->NPC_class == CLASS_KYLE + && self->client->leader == player + && !TIMER_Done( self, "kyleAngerSoundDebounce" ) ) + {//don't yell that you have an enemy more than once every five seconds + } + else if ( self->client && enemy->client && self->client->playerTeam != enemy->client->playerTeam ) + { + //FIXME: Use anger when entire team has no enemy. + // Basically, you're first one to notice enemies + if ( self->forcePushTime < level.time ) // not currently being pushed + { + if ( !G_TeamEnemy( self ) && self->client->NPC_class != CLASS_BOBAFETT) + {//team did not have an enemy previously + if ( self->NPC + && self->client->playerTeam == TEAM_PLAYER + && enemy->s.number < MAX_CLIENTS + && self->client->clientInfo.customBasicSoundDir + && self->client->clientInfo.customBasicSoundDir[0] + && Q_stricmp( "jedi2", self->client->clientInfo.customBasicSoundDir ) == 0 ) + { + switch ( Q_irand( 0, 2 ) ) + { + case 0: + G_SoundOnEnt( self, CHAN_VOICE, "sound/chars/jedi2/28je2008.wav" ); + break; + case 1: + G_SoundOnEnt( self, CHAN_VOICE, "sound/chars/jedi2/28je2009.wav" ); + break; + case 2: + G_SoundOnEnt( self, CHAN_VOICE, "sound/chars/jedi2/28je2012.wav" ); + break; + } + self->NPC->blockedSpeechDebounceTime = level.time + 2000; + } + else + { + if ( Q_irand( 0, 1 )) + {//hell, we're loading them, might as well use them! + event = Q_irand(EV_CHASE1, EV_CHASE3); + } + else + { + event = Q_irand(EV_ANGER1, EV_ANGER3); + } + } + } + } + + if ( event ) + {//yell + if ( self->client + && self->client->NPC_class == CLASS_KYLE + && self->client->leader == player ) + {//don't yell that you have an enemy more than once every 4-8 seconds + TIMER_Set( self, "kyleAngerSoundDebounce", Q_irand( 4000, 8000 ) ); + } + G_AddVoiceEvent( self, event, 2000 ); + } + } + + if ( self->s.weapon == WP_BLASTER || self->s.weapon == WP_REPEATER || + self->s.weapon == WP_THERMAL || self->s.weapon == WP_BLASTER_PISTOL + || self->s.weapon == WP_BOWCASTER ) + {//Hmm, how about sniper and bowcaster? + //When first get mad, aim is bad + //Hmm, base on game difficulty, too? Rank? + if ( self->client->playerTeam == TEAM_PLAYER ) + { + G_AimSet( self, Q_irand( self->NPC->stats.aim - (5*(g_spskill->integer)), self->NPC->stats.aim - g_spskill->integer ) ); + } + else + { + int minErr = 3; + int maxErr = 12; + if ( self->client->NPC_class == CLASS_IMPWORKER ) + { + minErr = 15; + maxErr = 30; + } + else if ( self->client->NPC_class == CLASS_STORMTROOPER && self->NPC && self->NPC->rank <= RANK_CREWMAN ) + { + minErr = 5; + maxErr = 15; + } + + G_AimSet( self, Q_irand( self->NPC->stats.aim - (maxErr*(3-g_spskill->integer)), self->NPC->stats.aim - (minErr*(3-g_spskill->integer)) ) ); + } + } + + //Alert anyone else in the area + if ( Q_stricmp( "desperado", self->NPC_type ) != 0 && Q_stricmp( "paladin", self->NPC_type ) != 0 ) + {//special holodeck enemies exception + if ( !(self->client->ps.eFlags&EF_FORCE_GRIPPED) ) + {//gripped people can't call for help + G_AngerAlert( self ); + } + } + + if ( !G_CheckSaberAllyAttackDelay( self, enemy ) ) + {//not a saber ally holding back + //Stormtroopers don't fire right away! + G_AttackDelay( self, enemy ); + } + + //FIXME: this is a disgusting hack that is supposed to make the Imperials start with their weapon holstered- need a better way + if ( self->client->ps.weapon == WP_NONE && !Q_stricmpn( self->NPC_type, "imp", 3 ) && !(self->NPC->scriptFlags & SCF_FORCED_MARCH) ) + { + if ( self->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER ) ) + { + ChangeWeapon( self, WP_BLASTER ); + self->client->ps.weapon = WP_BLASTER; + self->client->ps.weaponstate = WEAPON_READY; + G_CreateG2AttachedWeaponModel( self, weaponData[WP_BLASTER].weaponMdl, self->handRBolt, 0 ); + } + else if ( self->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER_PISTOL ) ) + { + ChangeWeapon( self, WP_BLASTER_PISTOL ); + self->client->ps.weapon = WP_BLASTER_PISTOL; + self->client->ps.weaponstate = WEAPON_READY; + G_CreateG2AttachedWeaponModel( self, weaponData[WP_BLASTER_PISTOL].weaponMdl, self->handRBolt, 0 ); + } + } + return; + } + + //Otherwise, just picking up another enemy + + if ( event ) + { + G_AddVoiceEvent( self, event, 2000 ); + } + + //Take the enemy + G_ClearEnemy(self); + self->enemy = enemy; +} + + +/* +int ChooseBestWeapon( void ) +{ + int n; + int weapon; + + // check weapons in the NPC's weapon preference order + for ( n = 0; n < MAX_WEAPONS; n++ ) + { + weapon = NPCInfo->weaponOrder[n]; + + if ( weapon == WP_NONE ) + { + break; + } + + if ( !HaveWeapon( weapon ) ) + { + continue; + } + + if ( client->ps.ammo[weaponData[weapon].ammoIndex] ) + { + return weapon; + } + } + + // check weapons serially (mainly in case a weapon is not on the NPC's list) + for ( weapon = 1; weapon < WP_NUM_WEAPONS; weapon++ ) + { + if ( !HaveWeapon( weapon ) ) + { + continue; + } + + if ( client->ps.ammo[weaponData[weapon].ammoIndex] ) + { + return weapon; + } + } + + return client->ps.weapon; +} +*/ + +void ChangeWeapon( gentity_t *ent, int newWeapon ) +{ + if ( !ent || !ent->client || !ent->NPC ) + { + return; + } + + ent->client->ps.weapon = newWeapon; + ent->NPC->shotTime = 0; + ent->NPC->burstCount = 0; + ent->NPC->attackHold = 0; + ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[newWeapon].ammoIndex]; + + switch ( newWeapon ) + { + case WP_BRYAR_PISTOL://prifle + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attackdebounce + break; + + case WP_BLASTER_PISTOL: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( ent->weaponModel[1] > 0 ) + {//commando + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 4; + ent->NPC->burstMean = 8; + ent->NPC->burstMax = 12; + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 600;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 400;//attack debounce + else + ent->NPC->burstSpacing = 250;//attack debounce + } + else if ( ent->client->NPC_class == CLASS_SABOTEUR ) + { + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 900;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 600;//attack debounce + else + ent->NPC->burstSpacing = 400;//attack debounce + } + else + { + // ent->NPC->burstSpacing = 1000;//attackdebounce + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + } + break; + + case WP_BOT_LASER://probe attack + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 600;//attackdebounce + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 600;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 400;//attack debounce + else + ent->NPC->burstSpacing = 200;//attack debounce + break; + + case WP_SABER: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 0;//attackdebounce + break; + + case WP_DISRUPTOR: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + switch( g_spskill->integer ) + { + case 0: + ent->NPC->burstSpacing = 2500;//attackdebounce + break; + case 1: + ent->NPC->burstSpacing = 2000;//attackdebounce + break; + case 2: + ent->NPC->burstSpacing = 1500;//attackdebounce + break; + } + } + else + { + ent->NPC->burstSpacing = 1000;//attackdebounce + } + break; + + case WP_TUSKEN_RIFLE: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + switch( g_spskill->integer ) + { + case 0: + ent->NPC->burstSpacing = 2500;//attackdebounce + break; + case 1: + ent->NPC->burstSpacing = 2000;//attackdebounce + break; + case 2: + ent->NPC->burstSpacing = 1500;//attackdebounce + break; + } + } + else + { + ent->NPC->burstSpacing = 1000;//attackdebounce + } + break; + + case WP_BOWCASTER: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 1000;//attackdebounce + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + break; + + case WP_REPEATER: + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 2000;//attackdebounce + } + else + { + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 3; + ent->NPC->burstMean = 6; + ent->NPC->burstMax = 10; + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 1500;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + } + break; + + case WP_DEMP2: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attackdebounce + break; + + case WP_FLECHETTE: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + ent->NPC->burstSpacing = 2000;//attackdebounce + } + else + { + ent->NPC->burstSpacing = 1000;//attackdebounce + } + break; + + case WP_ROCKET_LAUNCHER: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 2500;//attackdebounce + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 2500;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 2000;//attack debounce + else + ent->NPC->burstSpacing = 1500;//attack debounce + break; + + case WP_CONCUSSION: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + {//beam + ent->NPC->burstSpacing = 1200;//attackdebounce + } + else + {//rocket + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 2300;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 1800;//attack debounce + else + ent->NPC->burstSpacing = 1200;//attack debounce + } + break; + + case WP_THERMAL: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 3000;//attackdebounce + if ( g_spskill->integer == 0 ) +// ent->NPC->burstSpacing = 3000;//attack debounce + ent->NPC->burstSpacing = 4500;//attack debounce + else if ( g_spskill->integer == 1 ) +// ent->NPC->burstSpacing = 2500;//attack debounce + ent->NPC->burstSpacing = 3000;//attack debounce + else + ent->NPC->burstSpacing = 2000;//attack debounce + break; + + /* + case WP_SABER: + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 5;//0.5 sec + ent->NPC->burstMean = 10;//1 second + ent->NPC->burstMax = 20;//3 seconds + ent->NPC->burstSpacing = 2000;//2 seconds + ent->NPC->attackHold = 1000;//Hold attack button for a 1-second burst + break; + + case WP_TRICORDER: + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 5; + ent->NPC->burstMean = 10; + ent->NPC->burstMax = 30; + ent->NPC->burstSpacing = 1000; + break; + */ + + case WP_BLASTER: + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 3; + ent->NPC->burstMean = 3; + ent->NPC->burstMax = 3; + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 1500;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + } + else + { + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + // ent->NPC->burstSpacing = 1000;//attackdebounce + } + break; + + case WP_MELEE: + case WP_TUSKEN_STAFF: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attackdebounce + break; + + case WP_ATST_MAIN: + case WP_ATST_SIDE: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 1000;//attackdebounce + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + break; + + case WP_EMPLACED_GUN: + //FIXME: give some designer-control over this? + if ( ent->client && ent->client->NPC_class == CLASS_REELO ) + { + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attack debounce + // if ( g_spskill->integer == 0 ) + // ent->NPC->burstSpacing = 300;//attack debounce + // else if ( g_spskill->integer == 1 ) + // ent->NPC->burstSpacing = 200;//attack debounce + // else + // ent->NPC->burstSpacing = 100;//attack debounce + } + else + { + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 2; // 3 shots, really + ent->NPC->burstMean = 2; + ent->NPC->burstMax = 2; + + if ( ent->owner ) // if we have an owner, it should be the chair at this point...so query the chair for its shot debounce times, etc. + { + if ( g_spskill->integer == 0 ) + { + ent->NPC->burstSpacing = ent->owner->wait + 400;//attack debounce + ent->NPC->burstMin = ent->NPC->burstMax = 1; // two shots + } + else if ( g_spskill->integer == 1 ) + { + ent->NPC->burstSpacing = ent->owner->wait + 200;//attack debounce + } + else + { + ent->NPC->burstSpacing = ent->owner->wait;//attack debounce + } + } + else + { + if ( g_spskill->integer == 0 ) + { + ent->NPC->burstSpacing = 1200;//attack debounce + ent->NPC->burstMin = ent->NPC->burstMax = 1; // two shots + } + else if ( g_spskill->integer == 1 ) + { + ent->NPC->burstSpacing = 1000;//attack debounce + } + else + { + ent->NPC->burstSpacing = 800;//attack debounce + } + } + } + break; + + case WP_NOGHRI_STICK: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( g_spskill->integer == 0 ) + ent->NPC->burstSpacing = 2250;//attack debounce + else if ( g_spskill->integer == 1 ) + ent->NPC->burstSpacing = 1500;//attack debounce + else + ent->NPC->burstSpacing = 750;//attack debounce + break; + + default: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + break; + } +} + +void NPC_ChangeWeapon( int newWeapon ) +{ + qboolean changing = qfalse; + if ( newWeapon != NPC->client->ps.weapon ) + { + changing = qtrue; + } + if ( changing ) + { + G_RemoveWeaponModels( NPC ); + } + ChangeWeapon( NPC, newWeapon ); + if ( changing && NPC->client->ps.weapon != WP_NONE ) + { + if ( NPC->client->ps.weapon == WP_SABER ) + { + WP_SaberAddG2SaberModels( NPC ); + } + else + { + G_CreateG2AttachedWeaponModel( NPC, weaponData[NPC->client->ps.weapon].weaponMdl, NPC->handRBolt, 0 ); + } + } +} +/* +void NPC_ApplyWeaponFireDelay(void) +How long, if at all, in msec the actual fire should delay from the time the attack was started +*/ +void NPC_ApplyWeaponFireDelay(void) +{ + if ( NPC->attackDebounceTime > level.time ) + {//Just fired, if attacking again, must be a burst fire, so don't add delay + //NOTE: Borg AI uses attackDebounceTime "incorrectly", so this will always return for them! + return; + } + + switch(client->ps.weapon) + { + case WP_BOT_LASER: + NPCInfo->burstCount = 0; + client->fireDelay = 500; + break; + + case WP_THERMAL: + if ( client->ps.clientNum ) + {//NPCs delay... + //FIXME: player should, too, but would feel weird in 1st person, even though it + // would look right in 3rd person. Really should have a wind-up anim + // for player as he holds down the fire button to throw, then play + // the actual throw when he lets go... + client->fireDelay = 700; + } + break; + + case WP_MELEE: + case WP_TUSKEN_STAFF: + if ( !PM_DroidMelee( client->NPC_class ) ) + {//FIXME: should be unique per melee anim + client->fireDelay = 300; + } + break; + + case WP_TUSKEN_RIFLE: + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + {//FIXME: should be unique per melee anim + client->fireDelay = 300; + } + break; + + default: + client->fireDelay = 0; + break; + } +}; + +/* +------------------------- +ShootThink +------------------------- +*/ +void ShootThink( void ) +{ + int delay; + + ucmd.buttons &= ~BUTTON_ATTACK; +/* + if ( enemyVisibility != VIS_SHOOT) + return; +*/ + + if ( client->ps.weapon == WP_NONE ) + return; + + if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING && client->ps.weaponstate != WEAPON_IDLE) + return; + + if ( level.time < NPCInfo->shotTime ) + { + return; + } + + ucmd.buttons |= BUTTON_ATTACK; + + NPCInfo->currentAmmo = client->ps.ammo[weaponData[client->ps.weapon].ammoIndex]; // checkme + + NPC_ApplyWeaponFireDelay(); + + if ( NPCInfo->aiFlags & NPCAI_BURST_WEAPON ) + { + if ( !NPCInfo->burstCount ) + { + NPCInfo->burstCount = Q_irand( NPCInfo->burstMin, NPCInfo->burstMax ); + /* + NPCInfo->burstCount = erandom( NPCInfo->burstMean ); + if ( NPCInfo->burstCount < NPCInfo->burstMin ) + { + NPCInfo->burstCount = NPCInfo->burstMin; + } + else if ( NPCInfo->burstCount > NPCInfo->burstMax ) + { + NPCInfo->burstCount = NPCInfo->burstMax; + } + */ + delay = 0; + } + else + { + NPCInfo->burstCount--; + if ( NPCInfo->burstCount == 0 ) + { + delay = NPCInfo->burstSpacing + Q_irand(-150, 150); + } + else + { + delay = 0; + } + } + + if ( !delay ) + { + // HACK: dirty little emplaced bits, but is done because it would otherwise require some sort of new variable... + if ( client->ps.weapon == WP_EMPLACED_GUN ) + { + if ( NPC->owner ) // try and get the debounce values from the chair if we can + { + if ( g_spskill->integer == 0 ) + { + delay = NPC->owner->random + 150; + } + else if ( g_spskill->integer == 1 ) + { + delay = NPC->owner->random + 100; + } + else + { + delay = NPC->owner->random; + } + } + else + { + if ( g_spskill->integer == 0 ) + { + delay = 350; + } + else if ( g_spskill->integer == 1 ) + { + delay = 300; + } + else + { + delay = 200; + } + } + } + } + } + else + { + delay = NPCInfo->burstSpacing + Q_irand(-150, 150); + } + + NPCInfo->shotTime = level.time + delay; + NPC->attackDebounceTime = level.time + NPC_AttackDebounceForWeapon(); +} + +/* +static void WeaponThink( qboolean inCombat ) +FIXME makes this so there's a delay from event that caused us to check to actually doing it + +Added: hacks for Borg +*/ +void WeaponThink( qboolean inCombat ) +{ + if ( client->ps.weaponstate == WEAPON_RAISING || client->ps.weaponstate == WEAPON_DROPPING ) + { + ucmd.weapon = client->ps.weapon; + ucmd.buttons &= ~BUTTON_ATTACK; + return; + } + + // can't shoot while shield is up + if (NPC->flags&FL_SHIELDED && NPC->client->NPC_class==CLASS_ASSASSIN_DROID) + { + return; + } + + // Can't Fire While Cloaked + if (NPC->client && + (NPC->client->ps.powerups[PW_CLOAKED] || (level.timeclient->ps.powerups[PW_UNCLOAKING]))) + { + return; + } + + +//MCG - Begin + //For now, no-one runs out of ammo + if ( NPC->client->ps.ammo[ weaponData[client->ps.weapon].ammoIndex ] < weaponData[client->ps.weapon].energyPerShot ) + { + Add_Ammo( NPC, client->ps.weapon, weaponData[client->ps.weapon].energyPerShot*10 ); + } + else if ( NPC->client->ps.ammo[ weaponData[client->ps.weapon].ammoIndex ] < weaponData[client->ps.weapon].altEnergyPerShot ) + { + Add_Ammo( NPC, client->ps.weapon, weaponData[client->ps.weapon].altEnergyPerShot*5 ); + } + + /*if ( NPC->playerTeam == TEAM_BORG ) + {//HACK!!! + if(!(NPC->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BORG_WEAPON ))) + NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BORG_WEAPON ); + + if ( client->ps.weapon != WP_BORG_WEAPON ) + { + NPC_ChangeWeapon( WP_BORG_WEAPON ); + Add_Ammo (NPC, client->ps.weapon, 10); + NPCInfo->currentAmmo = client->ps.ammo[client->ps.weapon]; + } + } + else */ + + /*if ( NPC->client->playerTeam == TEAM_SCAVENGERS ) + {//HACK!!! + if(!(NPC->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER ))) + NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BLASTER ); + + if ( client->ps.weapon != WP_BLASTER ) + + { + NPC_ChangeWeapon( WP_BLASTER ); + Add_Ammo (NPC, client->ps.weapon, 10); +// NPCInfo->currentAmmo = client->ps.ammo[client->ps.weapon]; + NPCInfo->currentAmmo = client->ps.ammo[weaponData[client->ps.weapon].ammoIndex]; // checkme + } + } + else*/ +//MCG - End + { + // if the gun in our hands is out of ammo, we need to change + /*if ( client->ps.ammo[client->ps.weapon] == 0 ) + { + NPCInfo->aiFlags |= NPCAI_CHECK_WEAPON; + } + + if ( NPCInfo->aiFlags & NPCAI_CHECK_WEAPON ) + { + NPCInfo->aiFlags &= ~NPCAI_CHECK_WEAPON; + bestWeapon = ChooseBestWeapon(); + if ( bestWeapon != client->ps.weapon ) + { + NPC_ChangeWeapon( bestWeapon ); + } + }*/ + } + + ucmd.weapon = client->ps.weapon; + ShootThink(); +} + +/* +HaveWeapon +*/ + +qboolean HaveWeapon( int weapon ) +{ + return ( client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) ); +} + +qboolean EntIsGlass (gentity_t *check) +{ + if(check->classname && + !Q_stricmp("func_breakable", check->classname) && + check->count == 1 && check->health <= 100) + { + return qtrue; + } + + return qfalse; +} + +qboolean ShotThroughGlass (trace_t *tr, gentity_t *target, vec3_t spot, int mask) +{ + gentity_t *hit = &g_entities[ tr->entityNum ]; + if(hit != target && EntIsGlass(hit)) + {//ok to shoot through breakable glass + int skip = hit->s.number; + vec3_t muzzle; + + VectorCopy(tr->endpos, muzzle); + gi.trace (tr, muzzle, NULL, NULL, spot, skip, mask ); + return qtrue; + } + + return qfalse; +} + +/* +CanShoot +determine if NPC can directly target enemy + +this function does not check teams, invulnerability, notarget, etc.... + +Added: If can't shoot center, try head, if not, see if it's close enough to try anyway. +*/ +qboolean CanShoot ( gentity_t *ent, gentity_t *shooter ) +{ + trace_t tr; + vec3_t muzzle; + vec3_t spot, diff; + gentity_t *traceEnt; + + CalcEntitySpot( shooter, SPOT_WEAPON, muzzle ); + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); //FIXME preferred target locations for some weapons (feet for R/L) + + gi.trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT ); + traceEnt = &g_entities[ tr.entityNum ]; + + // point blank, baby! + if (tr.startsolid && (shooter->NPC) && (shooter->NPC->touchedByPlayer) ) + { + traceEnt = shooter->NPC->touchedByPlayer; + } + + if ( ShotThroughGlass( &tr, ent, spot, MASK_SHOT ) ) + { + traceEnt = &g_entities[ tr.entityNum ]; + } + + // shot is dead on + if ( traceEnt == ent ) + { + return qtrue; + } +//MCG - Begin + else + {//ok, can't hit them in center, try their head + CalcEntitySpot( ent, SPOT_HEAD, spot ); + gi.trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT ); + traceEnt = &g_entities[ tr.entityNum ]; + if ( traceEnt == ent) + { + return qtrue; + } + } + + //Actually, we should just check to fire in dir we're facing and if it's close enough, + //and we didn't hit someone on our own team, shoot + VectorSubtract(spot, tr.endpos, diff); + if(VectorLength(diff) < random() * 32) + { + return qtrue; + } +//MCG - End + // shot would hit a non-client + if ( !traceEnt->client ) + { + return qfalse; + } + + // shot is blocked by another player + + // he's already dead, so go ahead + if ( traceEnt->health <= 0 ) + { + return qtrue; + } + + // don't deliberately shoot a teammate + if ( traceEnt->client && ( traceEnt->client->playerTeam == shooter->client->playerTeam ) ) + { + return qfalse; + } + + // he's just in the wrong place, go ahead + return qtrue; +} + + +/* +void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ) + +Added: hacks for scripted NPCs +*/ +void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ) +{ + // is he is already our enemy? + if ( other == NPC->enemy ) + return; + + if ( other->flags & FL_NOTARGET ) + return; + + // we already have an enemy and this guy is in our FOV, see if this guy would be better + if ( NPC->enemy && vis == VIS_FOV ) + { + if ( NPCInfo->enemyLastSeenTime - level.time < 2000 ) + { + return; + } + if ( enemyVisibility == VIS_UNKNOWN ) + { + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV ); + } + if ( enemyVisibility == VIS_FOV ) + { + return; + } + } + + if ( !NPC->enemy ) + {//only take an enemy if you don't have one yet + G_SetEnemy( NPC, other ); + } + + if ( vis == VIS_FOV ) + { + NPCInfo->enemyLastSeenTime = level.time; + VectorCopy( other->currentOrigin, NPCInfo->enemyLastSeenLocation ); + NPCInfo->enemyLastHeardTime = 0; + VectorClear( NPCInfo->enemyLastHeardLocation ); + } + else + { + NPCInfo->enemyLastSeenTime = 0; + VectorClear( NPCInfo->enemyLastSeenLocation ); + NPCInfo->enemyLastHeardTime = level.time; + VectorCopy( other->currentOrigin, NPCInfo->enemyLastHeardLocation ); + } +} + + +//========================================== +//MCG Added functions: +//========================================== + +/* +int NPC_AttackDebounceForWeapon (void) + +DOES NOT control how fast you can fire +Only makes you keep your weapon up after you fire + +*/ +int NPC_AttackDebounceForWeapon (void) +{ + switch ( NPC->client->ps.weapon ) + { +/* + case WP_BLASTER://scav rifle + return 1000; + break; + + case WP_BRYAR_PISTOL://prifle + return 3000; + break; + + case WP_SABER: + return 100; + break; + + + case WP_TRICORDER: + return 0;//tricorder + break; +*/ + case WP_SABER: + if ( NPC->client->NPC_class == CLASS_KYLE + && (NPC->spawnflags&1) ) + { + return Q_irand( 1500, 5000 ); + } + else + { + return 0; + } + break; + + case WP_BOT_LASER: + + if ( g_spskill->integer == 0 ) + return 2000; + + if ( g_spskill->integer == 1 ) + return 1500; + + return 1000; + break; + + default: + return NPCInfo->burstSpacing + Q_irand(-100, 100);//was 100 by default + break; + } +} + +//FIXME: need a mindist for explosive weapons +float NPC_MaxDistSquaredForWeapon (void) +{ + if(NPCInfo->stats.shootDistance > 0) + {//overrides default weapon dist + return NPCInfo->stats.shootDistance * NPCInfo->stats.shootDistance; + } + + switch ( NPC->s.weapon ) + { + case WP_BLASTER://scav rifle + return 1024 * 1024;//should be shorter? + break; + + case WP_BRYAR_PISTOL://prifle + return 1024 * 1024; + break; + + case WP_BLASTER_PISTOL://prifle + return 1024 * 1024; + break; + + case WP_DISRUPTOR://disruptor + case WP_TUSKEN_RIFLE: + if ( NPCInfo->scriptFlags & SCF_ALT_FIRE ) + { + return ( 4096 * 4096 ); + } + else + { + return 1024 * 1024; + } + break; +/* + case WP_SABER: + return 1024 * 1024; + break; + + + case WP_TRICORDER: + return 0;//tricorder + break; +*/ + case WP_SABER: + if ( NPC->client && NPC->client->ps.SaberLength() ) + {//FIXME: account for whether enemy and I are heading towards each other! + return (NPC->client->ps.SaberLength() + NPC->maxs[0]*1.5)*(NPC->client->ps.SaberLength() + NPC->maxs[0]*1.5); + } + else + { + return 48*48; + } + break; + + default: + return 1024 * 1024;//was 0 + break; + } +} + + + +qboolean NPC_EnemyTooFar(gentity_t *enemy, float dist, qboolean toShoot) +{ + vec3_t vec; + + + if ( !toShoot ) + {//Not trying to actually press fire button with this check + if ( NPC->client->ps.weapon == WP_SABER ) + {//Just have to get to him + return qfalse; + } + } + + + if(!dist) + { + VectorSubtract(NPC->currentOrigin, enemy->currentOrigin, vec); + dist = VectorLengthSquared(vec); + } + + if(dist > NPC_MaxDistSquaredForWeapon()) + return qtrue; + + return qfalse; +} + +/* +NPC_PickEnemy + +Randomly picks a living enemy from the specified team and returns it + +FIXME: For now, you MUST specify an enemy team + +If you specify choose closest, it will find only the closest enemy + +If you specify checkVis, it will return and enemy that is visible + +If you specify findPlayersFirst, it will try to find players first + +You can mix and match any of those options (example: find closest visible players first) + +FIXME: this should go through the snapshot and find the closest enemy +*/ +gentity_t *NPC_PickEnemy( gentity_t *closestTo, int enemyTeam, qboolean checkVis, qboolean findPlayersFirst, qboolean findClosest ) +{ + int num_choices = 0; + int choice[128];//FIXME: need a different way to determine how many choices? + gentity_t *newenemy = NULL; + gentity_t *closestEnemy = NULL; + int entNum; + vec3_t diff; + float relDist; + float bestDist = Q3_INFINITE; + qboolean failed = qfalse; + int visChecks = (CHECK_360|CHECK_FOV|CHECK_VISRANGE); + int minVis = VIS_FOV; + + if ( enemyTeam == TEAM_NEUTRAL ) + { + return NULL; + } + + if ( NPCInfo->behaviorState == BS_STAND_AND_SHOOT || + NPCInfo->behaviorState == BS_HUNT_AND_KILL ) + {//Formations guys don't require inFov to pick up a target + //These other behavior states are active battle states and should not + //use FOV. FOV checks are for enemies who are patrolling, guarding, etc. + visChecks &= ~CHECK_FOV; + minVis = VIS_360; + } + + if( findPlayersFirst ) + {//try to find a player first + newenemy = &g_entities[0]; + if( newenemy->client && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW)) + { + if( newenemy->health > 0 ) + { + if( NPC_ValidEnemy( newenemy) )//enemyTeam == TEAM_PLAYER || newenemy->client->playerTeam == enemyTeam || ( enemyTeam == TEAM_PLAYER ) ) + {//FIXME: check for range and FOV or vis? + if( newenemy != NPC->lastEnemy ) + {//Make sure we're not just going back and forth here + if ( gi.inPVS(newenemy->currentOrigin, NPC->currentOrigin) ) + { + if(NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL) + { + if(!NPC->enemy) + { + if(!InVisrange(newenemy)) + { + failed = qtrue; + } + else if(NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV) + { + failed = qtrue; + } + } + } + + if ( !failed ) + { + VectorSubtract( closestTo->currentOrigin, newenemy->currentOrigin, diff ); + relDist = VectorLengthSquared(diff); + if ( newenemy->client->hiddenDist > 0 ) + { + if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist ) + { + //out of hidden range + if ( VectorLengthSquared( newenemy->client->hiddenDir ) ) + {//They're only hidden from a certain direction, check + float dot; + VectorNormalize( diff ); + dot = DotProduct( newenemy->client->hiddenDir, diff ); + if ( dot > 0.5 ) + {//I'm not looking in the right dir toward them to see them + failed = qtrue; + } + else + { + Debug_Printf(debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot ); + } + } + else + { + failed = qtrue; + } + } + else + { + Debug_Printf(debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist ); + } + } + + if(!failed) + { + if(findClosest) + { + if(relDist < bestDist) + { + if(!NPC_EnemyTooFar(newenemy, relDist, qfalse)) + { + if(checkVis) + { + if( NPC_CheckVisibility ( newenemy, visChecks ) == minVis ) + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + else + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + } + } + else if(!NPC_EnemyTooFar(newenemy, 0, qfalse)) + { + if(checkVis) + { + if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV ) + { + choice[num_choices++] = newenemy->s.number; + } + } + else + { + choice[num_choices++] = newenemy->s.number; + } + } + } + } + } + } + } + } + } + } + + if (findClosest && closestEnemy) + { + return closestEnemy; + } + + if (num_choices) + { + return &g_entities[ choice[rand() % num_choices] ]; + } + + /* + //FIXME: used to have an option to look *only* for the player... now...? Still need it? + if ( enemyTeam == TEAM_PLAYER ) + {//couldn't find the player + return NULL; + } + */ + + num_choices = 0; + bestDist = Q3_INFINITE; + closestEnemy = NULL; + + for ( entNum = 0; entNum < globals.num_entities; entNum++ ) + { + newenemy = &g_entities[entNum]; + + if ( newenemy != NPC && (newenemy->client || newenemy->svFlags & SVF_NONNPC_ENEMY) && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW)) + { + if ( newenemy->health > 0 ) + { + if ( (newenemy->client && NPC_ValidEnemy( newenemy)) + || (!newenemy->client && newenemy->noDamageTeam == enemyTeam) ) + {//FIXME: check for range and FOV or vis? + if ( NPC->client->playerTeam == TEAM_PLAYER && enemyTeam == TEAM_PLAYER ) + {//player allies turning on ourselves? How? + if ( newenemy->s.number ) + {//only turn on the player, not other player allies + continue; + } + } + + if ( newenemy != NPC->lastEnemy ) + {//Make sure we're not just going back and forth here + if(!gi.inPVS(newenemy->currentOrigin, NPC->currentOrigin)) + { + continue; + } + + if ( NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL ) + { + if ( !NPC->enemy ) + { + if ( !InVisrange( newenemy ) ) + { + continue; + } + else if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV ) + { + continue; + } + } + } + + VectorSubtract( closestTo->currentOrigin, newenemy->currentOrigin, diff ); + relDist = VectorLengthSquared(diff); + if ( newenemy->client && newenemy->client->hiddenDist > 0 ) + { + if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist ) + { + //out of hidden range + if ( VectorLengthSquared( newenemy->client->hiddenDir ) ) + {//They're only hidden from a certain direction, check + float dot; + + VectorNormalize( diff ); + dot = DotProduct( newenemy->client->hiddenDir, diff ); + if ( dot > 0.5 ) + {//I'm not looking in the right dir toward them to see them + continue; + } + else + { + Debug_Printf(debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot ); + } + } + else + { + continue; + } + } + else + { + Debug_Printf(debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist ); + } + } + + if ( findClosest ) + { + if ( relDist < bestDist ) + { + if ( !NPC_EnemyTooFar( newenemy, relDist, qfalse ) ) + { + if ( checkVis ) + { + //FIXME: NPCs need to be able to pick up other NPCs behind them, + //but for now, commented out because it was picking up enemies it shouldn't + //if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 ) + if ( NPC_CheckVisibility ( newenemy, visChecks ) == minVis ) + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + else + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + } + } + else if ( !NPC_EnemyTooFar( newenemy, 0, qfalse ) ) + { + if ( checkVis ) + { + //if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV ) + if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 ) + { + choice[num_choices++] = newenemy->s.number; + } + } + else + { + choice[num_choices++] = newenemy->s.number; + } + } + } + } + } + } + } + + + if (findClosest) + {//FIXME: you can pick up an enemy around a corner this way. + return closestEnemy; + } + + if (!num_choices) + { + return NULL; + } + + return &g_entities[ choice[rand() % num_choices] ]; +} + +/* +gentity_t *NPC_PickAlly ( void ) + + Simply returns closest visible ally +*/ + +gentity_t *NPC_PickAlly ( qboolean facingEachOther, float range, qboolean ignoreGroup, qboolean movingOnly ) +{ + gentity_t *ally = NULL; + gentity_t *closestAlly = NULL; + int entNum; + vec3_t diff; + float relDist; + float bestDist = range; + + for ( entNum = 0; entNum < globals.num_entities; entNum++ ) + { + ally = &g_entities[entNum]; + + if ( ally->client ) + { + if ( ally->health > 0 ) + { + if ( ally->client && ( ally->client->playerTeam == NPC->client->playerTeam || + NPC->client->playerTeam == TEAM_ENEMY ) )// && ally->client->playerTeam == TEAM_DISGUISE ) ) ) + {//if on same team or if player is disguised as your team + if ( ignoreGroup ) + { + if ( ally == NPC->client->leader ) + { + //reject + continue; + } + if ( ally->client && ally->client->leader && ally->client->leader == NPC ) + { + //reject + continue; + } + } + + if(!gi.inPVS(ally->currentOrigin, NPC->currentOrigin)) + { + continue; + } + + if ( movingOnly && ally->client && NPC->client ) + {//They have to be moving relative to each other + if ( !DistanceSquared( ally->client->ps.velocity, NPC->client->ps.velocity ) ) + { + continue; + } + } + + VectorSubtract( NPC->currentOrigin, ally->currentOrigin, diff ); + relDist = VectorNormalize( diff ); + if ( relDist < bestDist ) + { + if ( facingEachOther ) + { + vec3_t vf; + float dot; + + AngleVectors( ally->client->ps.viewangles, vf, NULL, NULL ); + VectorNormalize(vf); + dot = DotProduct(diff, vf); + + if ( dot < 0.5 ) + {//Not facing in dir to me + continue; + } + //He's facing me, am I facing him? + AngleVectors( NPC->client->ps.viewangles, vf, NULL, NULL ); + VectorNormalize(vf); + dot = DotProduct(diff, vf); + + if ( dot > -0.5 ) + {//I'm not facing opposite of dir to me + continue; + } + //I am facing him + } + + if ( NPC_CheckVisibility ( ally, CHECK_360|CHECK_VISRANGE ) >= VIS_360 ) + { + bestDist = relDist; + closestAlly = ally; + } + } + } + } + } + } + + + return closestAlly; +} + +gentity_t *NPC_CheckEnemy( qboolean findNew, qboolean tooFarOk, qboolean setEnemy ) +{ + qboolean forcefindNew = qfalse; + gentity_t *closestTo; + gentity_t *newEnemy = NULL; + //FIXME: have a "NPCInfo->persistance" we can set to determine how long to try to shoot + //someone we can't hit? Rather than hard-coded 10? + + //FIXME they shouldn't recognize enemy's death instantly + + //TEMP FIX: + //if(NPC->enemy->client) + //{ + // NPC->enemy->health = NPC->enemy->client->ps.stats[STAT_HEALTH]; + //} + + if ( NPC->enemy ) + { + if ( !NPC->enemy->inuse )//|| NPC->enemy == NPC )//wtf? NPCs should never get mad at themselves! + { + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + } + } + + if ( NPC->svFlags & SVF_IGNORE_ENEMIES ) + {//We're ignoring all enemies for now + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + return NULL; + } + + // Kyle does not get new enemies if not close to his leader + if (NPC->client->NPC_class==CLASS_KYLE && + NPC->client->leader && + Distance(NPC->client->leader->currentOrigin, NPC->currentOrigin)>3000 + ) + { + if (NPC->enemy) + { + G_ClearEnemy( NPC ); + } + return NULL; + } + + + if ( NPC->svFlags & SVF_LOCKEDENEMY ) + {//keep this enemy until dead + if ( NPC->enemy ) + { + if ( (!NPC->NPC && !(NPC->svFlags & SVF_NONNPC_ENEMY) ) || NPC->enemy->health > 0 ) + {//Enemy never had health (a train or info_not_null, etc) or did and is now dead (NPCs, turrets, etc) + return NULL; + } + } + NPC->svFlags &= ~SVF_LOCKEDENEMY; + } + + if ( NPC->enemy ) + { + if ( NPC_EnemyTooFar(NPC->enemy, 0, qfalse) ) + { + if(findNew) + {//See if there is a close one and take it if so, else keep this one + forcefindNew = qtrue; + } + else if(!tooFarOk)//FIXME: don't need this extra bool any more + { + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + } + } + else if ( !gi.inPVS(NPC->currentOrigin, NPC->enemy->currentOrigin ) ) + {//FIXME: should this be a line-of site check? + //FIXME: a lot of things check PVS AGAIN when deciding whether + //or not to shoot, redundant! + //Should we lose the enemy? + //FIXME: if lose enemy, run lostenemyscript + if ( NPC->enemy->client && NPC->enemy->client->hiddenDist ) + {//He ducked into shadow while we weren't looking + //Drop enemy and see if we should search for him + NPC_LostEnemyDecideChase(); + } + else + {//If we're not chasing him, we need to lose him + //NOTE: since we no longer have bStates, really, this logic doesn't work, so never give him up + + /* + switch( NPCInfo->behaviorState ) + { + case BS_HUNT_AND_KILL: + //Okay to lose PVS, we're chasing them + break; + case BS_RUN_AND_SHOOT: + //FIXME: only do this if !(NPCInfo->scriptFlags&SCF_CHASE_ENEMY) + //If he's not our goalEntity, we're running somewhere else, so lose him + if ( NPC->enemy != NPCInfo->goalEntity ) + { + G_ClearEnemy( NPC ); + } + break; + default: + //We're not chasing him, so lose him as an enemy + G_ClearEnemy( NPC ); + break; + } + */ + } + } + } + + if ( NPC->enemy ) + { + if ( NPC->enemy->health <= 0 || NPC->enemy->flags & FL_NOTARGET ) + { + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + } + } + + closestTo = NPC; + //FIXME: check your defendEnt, if you have one, see if their enemy is different + //than yours, or, if they don't have one, pick the closest enemy to THEM? + if ( NPCInfo->defendEnt ) + {//Trying to protect someone + if ( NPCInfo->defendEnt->health > 0 ) + {//Still alive, We presume we're close to them, navigation should handle this? + if ( NPCInfo->defendEnt->enemy ) + {//They were shot or acquired an enemy + if ( NPC->enemy != NPCInfo->defendEnt->enemy ) + {//They have a different enemy, take it! + newEnemy = NPCInfo->defendEnt->enemy; + if ( setEnemy ) + { + G_SetEnemy( NPC, NPCInfo->defendEnt->enemy ); + } + } + } + else if ( NPC->enemy == NULL ) + {//We don't have an enemy, so find closest to defendEnt + closestTo = NPCInfo->defendEnt; + } + } + } + + if (!NPC->enemy || ( NPC->enemy && NPC->enemy->health <= 0 ) || forcefindNew ) + {//FIXME: NPCs that are moving after an enemy should ignore the can't hit enemy counter- that should only be for NPCs that are standing still + //NOTE: cantHitEnemyCounter >= 100 means we couldn't hit enemy for a full + // 10 seconds, so give up. This means even if we're chasing him, we would + // try to find another enemy after 10 seconds (assuming the cantHitEnemyCounter + // is allowed to increment in a chasing bState) + qboolean foundenemy = qfalse; + + if(!findNew) + { + if ( setEnemy ) + { + NPC->lastEnemy = NPC->enemy; + G_ClearEnemy(NPC); + } + return NULL; + } + + //If enemy dead or unshootable, look for others on out enemy's team + if ( NPC->client->enemyTeam != TEAM_NEUTRAL) + { + //NOTE: this only checks vis if can't hit enemy for 10 tries, which I suppose + // means they need to find one that in more than just PVS + //newenemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, (NPC->cantHitEnemyCounter > 10), qfalse, qtrue );//3rd parm was (NPC->enemyTeam == TEAM_STARFLEET) + //For now, made it so you ALWAYS have to check VIS + newEnemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, qtrue, qfalse, qtrue );//3rd parm was (NPC->enemyTeam == TEAM_STARFLEET) + if ( newEnemy ) + { + foundenemy = qtrue; + if ( setEnemy ) + { + G_SetEnemy( NPC, newEnemy ); + } + } + } + + //if ( !forcefindNew ) + { + if ( !foundenemy ) + { + if ( setEnemy ) + { + NPC->lastEnemy = NPC->enemy; + G_ClearEnemy(NPC); + } + } + + NPC->cantHitEnemyCounter = 0; + } + //FIXME: if we can't find any at all, go into INdependant NPC AI, pursue and kill + } + + if ( NPC->enemy && NPC->enemy->client ) + { + if(NPC->enemy->client->playerTeam + && NPC->enemy->client->playerTeam != TEAM_FREE) + { +// assert( NPC->client->playerTeam != NPC->enemy->client->playerTeam); + if( NPC->client->playerTeam != NPC->enemy->client->playerTeam + && NPC->client->enemyTeam != TEAM_FREE + && NPC->client->enemyTeam != NPC->enemy->client->playerTeam ) + { + NPC->client->enemyTeam = NPC->enemy->client->playerTeam; + } + } + } + return newEnemy; +} + +/* +------------------------- +NPC_ClearShot +------------------------- +*/ + +qboolean NPC_ClearShot( gentity_t *ent ) +{ + if ( ( NPC == NULL ) || ( ent == NULL ) ) + return qfalse; + + vec3_t muzzle; + trace_t tr; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + // add aim error + // use weapon instead of specific npc types, although you could add certain npc classes if you wanted +// if ( NPC->client->playerTeam == TEAM_SCAVENGERS ) + if( NPC->s.weapon == WP_BLASTER || NPC->s.weapon == WP_BLASTER_PISTOL ) // any other guns to check for? + { + vec3_t mins = { -2, -2, -2 }; + vec3_t maxs = { 2, 2, 2 }; + + gi.trace ( &tr, muzzle, mins, maxs, ent->currentOrigin, NPC->s.number, MASK_SHOT ); + } + else + { + gi.trace ( &tr, muzzle, NULL, NULL, ent->currentOrigin, NPC->s.number, MASK_SHOT ); + } + + if ( tr.startsolid || tr.allsolid ) + { + return qfalse; + } + + if ( tr.entityNum == ent->s.number ) + return qtrue; + + return qfalse; +} + +/* +------------------------- +NPC_ShotEntity +------------------------- +*/ + +int NPC_ShotEntity( gentity_t *ent, vec3_t impactPos ) +{ + if ( ( NPC == NULL ) || ( ent == NULL ) ) + return qfalse; + + vec3_t muzzle; + vec3_t targ; + trace_t tr; + + if ( NPC->s.weapon == WP_THERMAL ) + {//thermal aims from slightly above head + //FIXME: what about low-angle shots, rolling the thermal under something? + vec3_t angles, forward, end; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + VectorSet( angles, 0, NPC->client->ps.viewangles[1], 0 ); + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( muzzle, 8, forward, end ); + end[2] += 24; + gi.trace ( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + VectorCopy( tr.endpos, muzzle ); + } + else + { + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + } + CalcEntitySpot( ent, SPOT_CHEST, targ ); + + // add aim error + // use weapon instead of specific npc types, although you could add certain npc classes if you wanted +// if ( NPC->client->playerTeam == TEAM_SCAVENGERS ) + if( NPC->s.weapon == WP_BLASTER || NPC->s.weapon == WP_BLASTER_PISTOL ) // any other guns to check for? + { + vec3_t mins = { -2, -2, -2 }; + vec3_t maxs = { 2, 2, 2 }; + + gi.trace ( &tr, muzzle, mins, maxs, targ, NPC->s.number, MASK_SHOT ); + } + else + { + gi.trace ( &tr, muzzle, NULL, NULL, targ, NPC->s.number, MASK_SHOT ); + } + //FIXME: if using a bouncing weapon like the bowcaster, should we check the reflection of the wall, too? + if ( impactPos ) + {//they want to know *where* the hit would be, too + VectorCopy( tr.endpos, impactPos ); + } +/* // NPCs should be able to shoot even if the muzzle would be inside their target + if ( tr.startsolid || tr.allsolid ) + { + return ENTITYNUM_NONE; + } +*/ + return tr.entityNum; +} + +qboolean NPC_EvaluateShot( int hit, qboolean glassOK ) +{ + if ( !NPC->enemy ) + { + return qfalse; + } + + if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].svFlags&SVF_GLASS_BRUSH)) ) + {//can hit enemy or will hit glass, so shoot anyway + return qtrue; + } + return qfalse; +} + +/* +NPC_CheckAttack + +Simply checks aggression and returns true or false +*/ + +qboolean NPC_CheckAttack (float scale) +{ + if(!scale) + scale = 1.0; + + if(((float)NPCInfo->stats.aggression) * scale < Q_flrand(0, 4)) + { + return qfalse; + } + + if(NPCInfo->shotTime > level.time) + return qfalse; + + return qtrue; +} + +/* +NPC_CheckDefend + +Simply checks evasion and returns true or false +*/ + +qboolean NPC_CheckDefend (float scale) +{ + if(!scale) + scale = 1.0; + + if((float)(NPCInfo->stats.evasion) > random() * 4 * scale) + return qtrue; + + return qfalse; +} + + +//NOTE: BE SURE TO CHECK PVS BEFORE THIS! +qboolean NPC_CheckCanAttack (float attack_scale, qboolean stationary) +{ + vec3_t delta, forward; + vec3_t angleToEnemy; + vec3_t hitspot, muzzle, diff, enemy_org;//, enemy_head; + float distanceToEnemy; + qboolean attack_ok = qfalse; +// qboolean duck_ok = qfalse; + qboolean dead_on = qfalse; + float aim_off; + float max_aim_off = 128 - (16 * (float)NPCInfo->stats.aim); + trace_t tr; + gentity_t *traceEnt = NULL; + + if(NPC->enemy->flags & FL_NOTARGET) + { + return qfalse; + } + + //FIXME: only check to see if should duck if that provides cover from the + //enemy!!! + if(!attack_scale) + { + attack_scale = 1.0; + } + //Yaw to enemy + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org ); + NPC_AimWiggle( enemy_org ); + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorSubtract (enemy_org, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + distanceToEnemy = VectorNormalize(delta); + + NPC->NPC->desiredYaw = angleToEnemy[YAW]; + NPC_UpdateFiringAngles(qfalse, qtrue); + + if( NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue) ) + {//Too far away? Do not attack + return qfalse; + } + + if(client->fireDelay > 0) + {//already waiting for a shot to fire + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles(qtrue, qfalse); + return qfalse; + } + + if(NPCInfo->scriptFlags & SCF_DONT_FIRE) + { + return qfalse; + } + + NPCInfo->enemyLastVisibility = enemyVisibility; + //See if they're in our FOV and we have a clear shot to them + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV);////CHECK_PVS| + + if(enemyVisibility >= VIS_FOV) + {//He's in our FOV + + attack_ok = qtrue; + //CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_head); + + //Check to duck + if ( NPC->enemy->client ) + { + if ( NPC->enemy->enemy == NPC ) + { + if ( NPC->enemy->client->buttons & BUTTON_ATTACK ) + {//FIXME: determine if enemy fire angles would hit me or get close + if ( NPC_CheckDefend( 1.0 ) )//FIXME: Check self-preservation? Health? + {//duck and don't shoot + attack_ok = qfalse; + ucmd.upmove = -127; + } + } + } + } + + if(attack_ok) + { + //are we gonna hit him + //NEW: use actual forward facing + AngleVectors( client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, distanceToEnemy, forward, hitspot ); + gi.trace( &tr, muzzle, NULL, NULL, hitspot, NPC->s.number, MASK_SHOT ); + ShotThroughGlass( &tr, NPC->enemy, hitspot, MASK_SHOT ); + /* + //OLD: trace regardless of facing + gi.trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT ); + ShotThroughGlass(&tr, NPC->enemy, enemy_org, MASK_SHOT); + */ + + traceEnt = &g_entities[tr.entityNum]; + + /* + if( traceEnt != NPC->enemy &&//FIXME: if someone on our team is in the way, suggest that they duck if possible + (!traceEnt || !traceEnt->client || !NPC->client->enemyTeam || NPC->client->enemyTeam != traceEnt->client->playerTeam) ) + {//no, so shoot for somewhere between the head and torso + //NOTE: yes, I know this looks weird, but it works + enemy_org[0] += 0.3*Q_flrand(NPC->enemy->mins[0], NPC->enemy->maxs[0]); + enemy_org[1] += 0.3*Q_flrand(NPC->enemy->mins[1], NPC->enemy->maxs[1]); + enemy_org[2] -= NPC->enemy->maxs[2]*Q_flrand(0.0f, 1.0f); + + attack_scale *= 0.75; + gi.trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT ); + ShotThroughGlass(&tr, NPC->enemy, enemy_org, MASK_SHOT); + traceEnt = &g_entities[tr.entityNum]; + } + */ + + VectorCopy( tr.endpos, hitspot ); + + if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) ) + { + dead_on = qtrue; + } + else + { + attack_scale *= 0.5; + if(NPC->client->playerTeam) + { + if(traceEnt && traceEnt->client && traceEnt->client->playerTeam) + { + if(NPC->client->playerTeam == traceEnt->client->playerTeam) + {//Don't shoot our own team + attack_ok = qfalse; + } + } + } + } + } + + if( attack_ok ) + { + //ok, now adjust pitch aim + VectorSubtract (hitspot, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles(qtrue, qfalse); + + if( !dead_on ) + {//We're not going to hit him directly, try a suppressing fire + //see if where we're going to shoot is too far from his origin + if(traceEnt && (traceEnt->health <= 30 || EntIsGlass(traceEnt))) + {//easy to kill - go for it + if(traceEnt->e_DieFunc == dieF_ExplodeDeath_Wait && traceEnt->splashDamage) + {//going to explode, don't shoot if close to self + VectorSubtract(NPC->currentOrigin, traceEnt->currentOrigin, diff); + if(VectorLengthSquared(diff) < traceEnt->splashRadius*traceEnt->splashRadius) + {//Too close to shoot! + attack_ok = qfalse; + } + else + {//Hey, it might kill him, do it! + attack_scale *= 2;// + } + } + } + else + { + AngleVectors (client->ps.viewangles, forward, NULL, NULL); + VectorMA ( muzzle, distanceToEnemy, forward, hitspot); + VectorSubtract(hitspot, enemy_org, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off)//FIXME: use aim value to allow poor aim? + { + attack_scale *= 0.75; + //see if where we're going to shoot is too far from his head + VectorSubtract(hitspot, enemy_org, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off) + { + attack_ok = qfalse; + } + } + attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off; + } + } + } + } + else + {//Update pitch anyway + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles(qtrue, qfalse); + } + + if( attack_ok ) + { + if( NPC_CheckAttack( attack_scale )) + {//check aggression to decide if we should shoot + enemyVisibility = VIS_SHOOT; + WeaponThink(qtrue); + } + else + attack_ok = qfalse; + } + + return attack_ok; +} +//======================================================================================== +//OLD id-style hunt and kill +//======================================================================================== +/* +IdealDistance + +determines what the NPC's ideal distance from it's enemy should +be in the current situation +*/ +float IdealDistance ( gentity_t *self ) +{ + float ideal; + + ideal = 225 - 20 * NPCInfo->stats.aggression; + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + ideal += 200; + break; + + case WP_CONCUSSION: + ideal += 200; + break; + + case WP_THERMAL: + ideal += 50; + break; + +/* case WP_TRICORDER: + ideal = 0; + break; +*/ + case WP_SABER: + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: + case WP_BLASTER: + default: + break; + } + + return ideal; +} + +/*QUAKED point_combat (0.7 0 0.7) (-20 -20 -24) (20 20 45) DUCK FLEE INVESTIGATE SQUAD LEAN SNIPE +NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point + +DUCK - NPC will duck and fire from this point, NOT IMPLEMENTED? +FLEE - Will choose this point when running +INVESTIGATE - Will look here if a sound is heard near it +SQUAD - NOT IMPLEMENTED +LEAN - Lean-type cover, NOT IMPLEMENTED +SNIPE - Snipers look for these first, NOT IMPLEMENTED +*/ + +void SP_point_combat( gentity_t *self ) +{ + if(level.numCombatPoints >= MAX_COMBAT_POINTS) + { +#ifndef FINAL_BUILD + gi.Printf(S_COLOR_RED"ERROR: Too many combat points, limit is %d\n", MAX_COMBAT_POINTS); +#endif + G_FreeEntity(self); + return; + } + + self->s.origin[2] += 0.125; + G_SetOrigin(self, self->s.origin); + gi.linkentity(self); + + if ( G_CheckInSolid( self, qtrue ) ) + { +#ifndef FINAL_BUILD + gi.Printf( S_COLOR_RED"ERROR: combat point at %s in solid!\n", vtos(self->currentOrigin) ); +#endif + } + + VectorCopy( self->currentOrigin, level.combatPoints[level.numCombatPoints].origin ); + + level.combatPoints[level.numCombatPoints].flags = self->spawnflags; + level.combatPoints[level.numCombatPoints].occupied = qfalse; + + level.numCombatPoints++; + + NAV::SpawnedPoint(self, NAV::PT_COMBATNODE); + + G_FreeEntity(self); +}; + +void CP_FindCombatPointWaypoints( void ) +{ + for ( int i = 0; i < level.numCombatPoints; i++ ) + { + level.combatPoints[i].waypoint = NAV::GetNearestNode(level.combatPoints[i].origin); + if ( level.combatPoints[i].waypoint == WAYPOINT_NONE ) + { + assert(0); + level.combatPoints[i].waypoint = NAV::GetNearestNode(level.combatPoints[i].origin); + gi.Printf( S_COLOR_RED"ERROR: Combat Point at %s has no waypoint!\n", vtos(level.combatPoints[i].origin) ); + delayedShutDown = level.time + 100; + } + } +} + + +/* +------------------------- +NPC_CollectCombatPoints +------------------------- +*/ + +typedef map< float, int > combatPoint_m; + +static int NPC_CollectCombatPoints( const vec3_t origin, const float radius, combatPoint_m &points, const int flags ) +{ + float radiusSqr = (radius*radius); + float distance; + + //Collect all nearest + for ( int i = 0; i < level.numCombatPoints; i++ ) + { + //Must be vacant + if ( level.combatPoints[i].occupied == (int) qtrue ) + continue; + + //If we want a duck space, make sure this is one + if ( ( flags & CP_DUCK ) && !( level.combatPoints[i].flags & CPF_DUCK ) ) + continue; + + //If we want a flee point, make sure this is one + if ( ( flags & CP_FLEE ) && !( level.combatPoints[i].flags & CPF_FLEE ) ) + continue; + + //If we want a snipe point, make sure this is one + if ( ( flags & CP_SNIPE ) && !( level.combatPoints[i].flags & CPF_SNIPE ) ) + continue; + + ///Make sure this is an investigate combat point + if ( ( flags & CP_INVESTIGATE ) && ( level.combatPoints[i].flags & CPF_INVESTIGATE ) ) + continue; + + //Squad points are only valid if we're looking for them + if ( ( level.combatPoints[i].flags & CPF_SQUAD ) && ( ( flags & CP_SQUAD ) == qfalse ) ) + continue; + + if ( flags&CP_NO_PVS ) + {//must not be within PVS of mu current origin + if ( gi.inPVS( origin, level.combatPoints[i].origin ) ) + { + continue; + } + } + + if ( flags&CP_HORZ_DIST_COLL ) + { + distance = DistanceHorizontalSquared( origin, level.combatPoints[i].origin ); + } + else + { + distance = DistanceSquared( origin, level.combatPoints[i].origin ); + } + + if ( distance < radiusSqr ) + { + //Using a map will sort nearest automatically + points[ distance ] = i; + } + } + + return points.size(); +} + +/* +------------------------- +NPC_FindCombatPoint +------------------------- +*/ + +#define MIN_AVOID_DOT 0.7f +#define MIN_AVOID_DISTANCE 128 +#define MIN_AVOID_DISTANCE_SQUARED ( MIN_AVOID_DISTANCE * MIN_AVOID_DISTANCE ) +#define CP_COLLECT_RADIUS 512.0f + +int NPC_FindCombatPoint( const vec3_t position, const vec3_t avoidPosition, vec3_t destPosition, const int flags, float avoidDist, const int ignorePoint ) +{ + combatPoint_m points; + combatPoint_m::iterator cpi; + + int best = -1;//, cost, bestCost = Q3_INFINITE, waypoint = WAYPOINT_NONE, destWaypoint = WAYPOINT_NONE; + trace_t tr; + float collRad = CP_COLLECT_RADIUS; + vec3_t eDir2Me, eDir2CP, weaponOffset; + vec3_t enemyPosition; + float dotToCp; + float distSqPointToNPC; + float distSqPointToEnemy; + float distSqPointToEnemyHoriz; + float distSqPointToEnemyCheck; + float distSqNPCToEnemy; + float distSqNPCToEnemyHoriz; + float distSqNPCToEnemyCheck; + float visRangeSq = (NPCInfo->stats.visrange*NPCInfo->stats.visrange); + bool useHorizDist = (NPC->s.weapon==WP_THERMAL) || (flags & CP_HORZ_DIST_COLL); + + if (NPC->enemy) + { + VectorCopy(NPC->enemy->currentOrigin, enemyPosition); + } + else if (avoidPosition) + { + VectorCopy(avoidPosition, enemyPosition); + } + else if (destPosition) + { + VectorCopy(destPosition, enemyPosition); + } + else + { + VectorCopy(NPC->currentOrigin, enemyPosition); + } + + if ( avoidDist <= 0 ) + { + avoidDist = MIN_AVOID_DISTANCE_SQUARED; + } + else + { + avoidDist *= avoidDist; + } + + + //Collect our nearest points + if ( (flags & CP_NO_PVS) || (flags & CP_TRYFAR)) + {//much larger radius since most will be dropped? + collRad = CP_COLLECT_RADIUS*4; + } + NPC_CollectCombatPoints( destPosition, collRad, points, flags );//position + + for ( cpi = points.begin(); cpi != points.end(); cpi++ ) + { + const int i = (*cpi).second; + + //Must not be one we want to ignore + if ( i == ignorePoint ) + { + continue; + } + + //Get some distances for reasoning + distSqPointToNPC = (*cpi).first; + + distSqPointToEnemy = DistanceSquared (level.combatPoints[i].origin, enemyPosition); + distSqPointToEnemyHoriz = DistanceHorizontalSquared(level.combatPoints[i].origin, enemyPosition); + distSqPointToEnemyCheck = (useHorizDist)?(distSqPointToEnemyHoriz):(distSqPointToEnemy); + + distSqNPCToEnemy = DistanceSquared (NPC->currentOrigin, enemyPosition); + distSqNPCToEnemyHoriz = DistanceHorizontalSquared(NPC->currentOrigin, enemyPosition); + distSqNPCToEnemyCheck = (useHorizDist)?(distSqNPCToEnemyHoriz ):(distSqNPCToEnemy); + + + + //Ignore points that are farther than currently located + if ( (flags & CP_APPROACH_ENEMY) && (distSqPointToEnemyCheck > distSqNPCToEnemyCheck)) + { + continue; + } + + //Ignore points that are closer than currently located + if ( (flags & CP_RETREAT) && (distSqPointToEnemyCheck < distSqNPCToEnemyCheck)) + { + continue; + } + + //Ignore points that are out of vis range + if ( (flags & CP_CLEAR) && (distSqPointToEnemyCheck > visRangeSq)) + { + continue; + } + + //Avoid this position? + if ( avoidPosition && !(flags & CP_AVOID_ENEMY) && (flags & CP_AVOID) && (DistanceSquared(level.combatPoints[i].origin, avoidPosition)= 0.4 ) + { + continue; + } + } + + //we must have a route to the combat point + if ( (flags & CP_HAS_ROUTE) && !NAV::InSameRegion(NPC, level.combatPoints[i].origin)) + { + continue; + } + + + //See if we're trying to avoid our enemy + if (flags & CP_AVOID_ENEMY) + { + //Can't be too close to the enemy + if (distSqPointToEnemy(avoidDist) && + !NAV::SafePathExists(position, level.combatPoints[i].origin, enemyPosition, avoidDist)) + { + continue; + } + } + + //Okay, now make sure it's not blocked + gi.trace( &tr, level.combatPoints[i].origin, NPC->mins, NPC->maxs, level.combatPoints[i].origin, NPC->s.number, NPC->clipmask ); + if ( tr.allsolid || tr.startsolid ) + { + continue; + } + + if (NPC->enemy) + { + // Ignore Points That Do Not Have A Clear LOS To The Player + if ( (flags & CP_CLEAR) ) + { + CalcEntitySpot(NPC, SPOT_WEAPON, weaponOffset); + VectorSubtract(weaponOffset, NPC->currentOrigin, weaponOffset); + VectorAdd(weaponOffset, level.combatPoints[i].origin, weaponOffset); + + if (NPC_ClearLOS(weaponOffset, NPC->enemy)==qfalse) + { + continue; + } + } + + // Ignore points that are not behind cover + if ( (flags & CP_COVER) && NPC_ClearLOS(level.combatPoints[i].origin, NPC->enemy)==qtrue) + { + continue; + } + } + + //they are sorted by this distance, so the first one to get this far is the closest + return i; + } + + return best; +} + +int NPC_FindCombatPointRetry( const vec3_t position, + const vec3_t avoidPosition, + vec3_t destPosition, + int *cpFlags, + float avoidDist, + const int ignorePoint ) +{ + int cp = -1; + cp = NPC_FindCombatPoint( position, + avoidPosition, + destPosition, + *cpFlags, + avoidDist, + ignorePoint ); + while ( cp == -1 && (*cpFlags&~CP_HAS_ROUTE) != CP_ANY ) + {//start "OR"ing out certain flags to see if we can find *any* point + if ( *cpFlags & CP_INVESTIGATE ) + {//don't need to investigate + *cpFlags &= ~CP_INVESTIGATE; + } + else if ( *cpFlags & CP_SQUAD ) + {//don't need to stick to squads + *cpFlags &= ~CP_SQUAD; + } + else if ( *cpFlags & CP_DUCK ) + {//don't need to duck + *cpFlags &= ~CP_DUCK; + } + else if ( *cpFlags & CP_NEAREST ) + {//don't need closest one to me + *cpFlags &= ~CP_NEAREST; + } + else if ( *cpFlags & CP_FLANK ) + {//don't need to flank enemy + *cpFlags &= ~CP_FLANK; + } + else if ( *cpFlags & CP_SAFE ) + {//don't need one that hasn't been shot at recently + *cpFlags &= ~CP_SAFE; + } + else if ( *cpFlags & CP_CLOSEST ) + {//don't need to get closest to enemy + *cpFlags &= ~CP_CLOSEST; + //but let's try to approach at least + *cpFlags |= CP_APPROACH_ENEMY; + } + else if ( *cpFlags & CP_APPROACH_ENEMY ) + {//don't need to approach enemy + *cpFlags &= ~CP_APPROACH_ENEMY; + } + else if ( *cpFlags & CP_COVER ) + {//don't need cover + *cpFlags &= ~CP_COVER; + //but let's pick one that makes us duck + //*cpFlags |= CP_DUCK; + } + // else if ( *cpFlags & CP_CLEAR ) + // {//don't need a clear shot to enemy + // *cpFlags &= ~CP_CLEAR; + // } + // Never Give Up On Avoiding The Enemy + // else if ( *cpFlags & CP_AVOID_ENEMY ) + // {//don't need to avoid enemy + // *cpFlags &= ~CP_AVOID_ENEMY; + // } + else if ( *cpFlags & CP_RETREAT ) + {//don't need to retreat + *cpFlags &= ~CP_RETREAT; + } + else if ( *cpFlags &CP_FLEE ) + {//don't need to flee + *cpFlags &= ~CP_FLEE; + //but at least avoid enemy and pick one that gives cover + *cpFlags |= (CP_COVER|CP_AVOID_ENEMY); + } + else if ( *cpFlags & CP_AVOID ) + {//okay, even pick one right by me + *cpFlags &= ~CP_AVOID; + } + else if ( *cpFlags & CP_SHORTEST_PATH ) + {//okay, don't need the one with the shortest path + *cpFlags &= ~CP_SHORTEST_PATH; + } + else + {//screw it, we give up! + return -1; + /* + if ( *cpFlags & CP_HAS_ROUTE ) + {//NOTE: this is really an absolute worst case scenario - will go to the first cp on the map! + *cpFlags &= ~CP_HAS_ROUTE; + } + else + {//NOTE: this is really an absolute worst case scenario - will go to the first cp on the map! + *cpFlags = CP_ANY; + } + */ + } + //now try again + cp = NPC_FindCombatPoint( position, + avoidPosition, + destPosition, + *cpFlags, + avoidDist, + ignorePoint ); + } + return cp; +} +/* +------------------------- +NPC_FindSquadPoint +------------------------- +*/ + +int NPC_FindSquadPoint( vec3_t position ) +{ + float dist, nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE; + int nearestPoint = -1; + + //float playerDist = DistanceSquared( g_entities[0].currentOrigin, NPC->currentOrigin ); + + for ( int i = 0; i < level.numCombatPoints; i++ ) + { + //Squad points are only valid if we're looking for them + if ( ( level.combatPoints[i].flags & CPF_SQUAD ) == qfalse ) + continue; + + //Must be vacant + if ( level.combatPoints[i].occupied == qtrue ) + continue; + + dist = DistanceSquared( position, level.combatPoints[i].origin ); + + //The point cannot take us past the player + //if ( dist > ( playerDist * DotProduct( dirToPlayer, playerDir ) ) ) //FIXME: Retain this + // continue; + + //See if this is closer than the others + if ( dist < nearestDist ) + { + nearestPoint = i; + nearestDist = dist; + } + } + + return nearestPoint; +} + +/* +------------------------- +NPC_ReserveCombatPoint +------------------------- +*/ + +qboolean NPC_ReserveCombatPoint( int combatPointID ) +{ + //Make sure it's valid + if ( combatPointID > level.numCombatPoints ) + return qfalse; + + //Make sure it's not already occupied + if ( level.combatPoints[combatPointID].occupied ) + return qfalse; + + //Reserve it + level.combatPoints[combatPointID].occupied = qtrue; + + return qtrue; +} + +/* +------------------------- +NPC_FreeCombatPoint +------------------------- +*/ + +qboolean NPC_FreeCombatPoint( int combatPointID, qboolean failed ) +{ + if ( failed ) + {//remember that this one failed for us + NPCInfo->lastFailedCombatPoint = combatPointID; + } + //Make sure it's valid + if ( combatPointID > level.numCombatPoints ) + return qfalse; + + //Make sure it's currently occupied + if ( level.combatPoints[combatPointID].occupied == qfalse ) + return qfalse; + + //Free it + level.combatPoints[combatPointID].occupied = qfalse; + + return qtrue; +} + +/* +------------------------- +NPC_SetCombatPoint +------------------------- +*/ + +qboolean NPC_SetCombatPoint( int combatPointID ) +{ + if (combatPointID==NPCInfo->combatPoint) + { + return qtrue; + } + + //Free a combat point if we already have one + if ( NPCInfo->combatPoint != -1 ) + { + NPC_FreeCombatPoint( NPCInfo->combatPoint ); + } + + if ( NPC_ReserveCombatPoint( combatPointID ) == qfalse ) + return qfalse; + + NPCInfo->combatPoint = combatPointID; + + return qtrue; +} + +extern qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper ); +gentity_t *NPC_SearchForWeapons( void ) +{ + gentity_t *found = g_entities, *bestFound = NULL; + float dist, bestDist = Q3_INFINITE; + int i; +// for ( found = g_entities; found < &g_entities[globals.num_entities] ; found++) + for ( i = 0; iinuse ) +// { +// continue; +// } + if(!PInUse(i)) + continue; + + found=&g_entities[i]; + + //FIXME: Also look for ammo_racks that have weapons on them? + if ( found->s.eType != ET_ITEM ) + { + continue; + } + if ( found->item->giType != IT_WEAPON ) + { + continue; + } + if ( found->s.eFlags & EF_NODRAW ) + { + continue; + } + if ( CheckItemCanBePickedUpByNPC( found, NPC ) ) + { + if ( gi.inPVS( found->currentOrigin, NPC->currentOrigin ) ) + { + dist = DistanceSquared( found->currentOrigin, NPC->currentOrigin ); + if ( dist < bestDist ) + { + if (NAV::InSameRegion(NPC, found)) + {//can nav to it + bestDist = dist; + bestFound = found; + } + } + } + } + } + + return bestFound; +} + +void NPC_SetPickUpGoal( gentity_t *foundWeap ) +{ + vec3_t org; + + //NPCInfo->goalEntity = foundWeap; + VectorCopy( foundWeap->currentOrigin, org ); + org[2] += 24 - (foundWeap->mins[2]*-1);//adjust the origin so that I am on the ground + NPC_SetMoveGoal( NPC, org, foundWeap->maxs[0]*0.75, qfalse, -1, foundWeap ); + NPCInfo->tempGoal->waypoint = foundWeap->waypoint; + NPCInfo->tempBehavior = BS_DEFAULT; + NPCInfo->squadState = SQUAD_TRANSITION; +} + +extern void Q3_TaskIDComplete( gentity_t *ent, taskID_t taskType ); +extern qboolean G_CanPickUpWeapons( gentity_t *other ); +void NPC_CheckGetNewWeapon( void ) +{ + if ( NPC->client + && !G_CanPickUpWeapons( NPC ) ) + {//this NPC can't pick up weapons... + return; + } + if ( NPC->s.weapon == WP_NONE && NPC->enemy ) + {//if running away because dropped weapon... + if ( NPCInfo->goalEntity + && NPCInfo->goalEntity == NPCInfo->tempGoal + && NPCInfo->goalEntity->enemy + && !NPCInfo->goalEntity->enemy->inuse ) + {//maybe was running at a weapon that was picked up + NPC_ClearGoal(); + Q3_TaskIDComplete( NPC, TID_MOVE_NAV ); + //NPCInfo->goalEntity = NULL; + } + if ( TIMER_Done( NPC, "panic" ) && NPCInfo->goalEntity == NULL ) + {//need a weapon, any lying around? + gentity_t *foundWeap = NPC_SearchForWeapons(); + if ( foundWeap ) + { + NPC_SetPickUpGoal( foundWeap ); + } + } + } +} + +void NPC_AimAdjust( int change ) +{ + if ( !TIMER_Exists( NPC, "aimDebounce" ) ) + { + int debounce = 500+(3-g_spskill->integer)*100; + TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) ); + //int debounce = 1000+(3-g_spskill->integer)*500; + //TIMER_Set( NPC, "aimDebounce", Q_irand( debounce, debounce+2000 ) ); + return; + } + if ( TIMER_Done( NPC, "aimDebounce" ) ) + { + NPCInfo->currentAim += change; + if ( NPCInfo->currentAim > NPCInfo->stats.aim ) + {//can never be better than max aim + NPCInfo->currentAim = NPCInfo->stats.aim; + } + else if ( NPCInfo->currentAim < -30 ) + {//can never be worse than this + NPCInfo->currentAim = -30; + } + + //Com_Printf( "%s new aim = %d\n", NPC->NPC_type, NPCInfo->currentAim ); + + int debounce = 500+(3-g_spskill->integer)*100; + TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) ); + //int debounce = 1000+(3-g_spskill->integer)*500; + //TIMER_Set( NPC, "aimDebounce", Q_irand( debounce, debounce+2000 ) ); + } +} + +void G_AimSet( gentity_t *self, int aim ) +{ + if ( self->NPC ) + { + self->NPC->currentAim = aim; + //Com_Printf( "%s new aim = %d\n", self->NPC_type, self->NPC->currentAim ); + + int debounce = 500+(3-g_spskill->integer)*100; + TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+1000 ) ); + // int debounce = 1000+(3-g_spskill->integer)*500; + // TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+2000 ) ); + } +} diff --git a/code/game/NPC_goal.cpp b/code/game/NPC_goal.cpp new file mode 100644 index 0000000..798e145 --- /dev/null +++ b/code/game/NPC_goal.cpp @@ -0,0 +1,188 @@ +//b_goal.cpp +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + + + + +#include "b_local.h" +#include "Q3_Interface.h" + +extern qboolean FlyingCreature( gentity_t *ent ); +/* +SetGoal +*/ + +void SetGoal( gentity_t *goal, float rating ) +{ + NPCInfo->goalEntity = goal; +// NPCInfo->goalEntityNeed = rating; + NPCInfo->goalTime = level.time; + if ( goal ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_SetGoal: %s @ %s (%f)\n", goal->classname, vtos( goal->currentOrigin), rating ); + } + else + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_SetGoal: NONE\n" ); + } +} + + +/* +NPC_SetGoal +*/ + +void NPC_SetGoal( gentity_t *goal, float rating ) +{ + if ( goal == NPCInfo->goalEntity ) + { + return; + } + + if ( !goal ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_ERROR, "NPC_SetGoal: NULL goal\n" ); + return; + } + + if ( goal->client ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_ERROR, "NPC_SetGoal: goal is a client\n" ); + return; + } + + if ( NPCInfo->goalEntity ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_SetGoal: push %s\n", NPCInfo->goalEntity->classname ); + NPCInfo->lastGoalEntity = NPCInfo->goalEntity; +// NPCInfo->lastGoalEntityNeed = NPCInfo->goalEntityNeed; + } + + SetGoal( goal, rating ); +} + + +/* +NPC_ClearGoal +*/ + +void NPC_ClearGoal( void ) +{ + gentity_t *goal; + + if ( !NPCInfo->lastGoalEntity ) + { + SetGoal( NULL, 0.0 ); + return; + } + + goal = NPCInfo->lastGoalEntity; + NPCInfo->lastGoalEntity = NULL; + if ( goal->inuse && !(goal->s.eFlags & EF_NODRAW) ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_ClearGoal: pop %s\n", goal->classname ); + SetGoal( goal, 0 );//, NPCInfo->lastGoalEntityNeed + return; + } + + SetGoal( NULL, 0.0 ); +} + +/* +------------------------- +G_BoundsOverlap +------------------------- +*/ + +qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2) +{//NOTE: flush up against counts as overlapping + if(mins1[0]>maxs2[0]) + return qfalse; + + if(mins1[1]>maxs2[1]) + return qfalse; + + if(mins1[2]>maxs2[2]) + return qfalse; + + if(maxs1[0]goalTime = level.time; + +//MCG - Begin + NPCInfo->aiFlags &= ~NPCAI_MOVING; + ucmd.forwardmove = 0; + //Return that the goal was reached + Q3_TaskIDComplete( NPC, TID_MOVE_NAV ); +//MCG - End +} +/* +ReachedGoal + +id removed checks against waypoints and is now checking surfaces +*/ +qboolean ReachedGoal( gentity_t *goal ) +{ + + if ( NPCInfo->aiFlags & NPCAI_TOUCHED_GOAL ) + { + NPCInfo->aiFlags &= ~NPCAI_TOUCHED_GOAL; + return qtrue; + } + return STEER::Reached(NPC, goal, NPCInfo->goalRadius, !!FlyingCreature(NPC)); +} + +/* +static gentity_t *UpdateGoal( void ) + +Id removed a lot of shit here... doesn't seem to handle waypoints independantly of goalentity + +In fact, doesn't seem to be any waypoint info on entities at all any more? + +MCG - Since goal is ALWAYS goalEntity, took out a lot of sending goal entity pointers around for no reason +*/ + +gentity_t *UpdateGoal( void ) +{ + //FIXME: CREED should look at this + // this func doesn't seem to be working correctly for the sand creature + + gentity_t *goal; + + if ( !NPCInfo->goalEntity ) + { + return NULL; + } + + if ( !NPCInfo->goalEntity->inuse ) + {//Somehow freed it, but didn't clear it + NPC_ClearGoal(); + return NULL; + } + + goal = NPCInfo->goalEntity; + + if ( ReachedGoal( goal ) ) + { + NPC_ReachedGoal(); + goal = NULL;//so they don't keep trying to move to it + }//else if fail, need to tell script so? + + return goal; +} + diff --git a/code/game/NPC_misc.cpp b/code/game/NPC_misc.cpp new file mode 100644 index 0000000..ea1cac3 --- /dev/null +++ b/code/game/NPC_misc.cpp @@ -0,0 +1,79 @@ +// +// NPC_misc.cpp +// + +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + + + + +#include "b_local.h" +#include "q_shared.h" +/* +Debug_Printf +*/ +void Debug_Printf (cvar_t *cv, int debugLevel, char *fmt, ...) +{ + char *color; + va_list argptr; + char msg[1024]; + + if (cv->value < debugLevel) + return; + + if (debugLevel == DEBUG_LEVEL_DETAIL) + color = S_COLOR_WHITE; + else if (debugLevel == DEBUG_LEVEL_INFO) + color = S_COLOR_GREEN; + else if (debugLevel == DEBUG_LEVEL_WARNING) + color = S_COLOR_YELLOW; + else if (debugLevel == DEBUG_LEVEL_ERROR) + color = S_COLOR_RED; + else + color = S_COLOR_RED; + + va_start (argptr,fmt); + vsprintf (msg, fmt, argptr); + va_end (argptr); + + gi.Printf("%s%5i:%s", color, level.time, msg); +} + + +/* +Debug_NPCPrintf +*/ +void Debug_NPCPrintf (gentity_t *printNPC, cvar_t *cv, int debugLevel, char *fmt, ...) +{ + int color; + va_list argptr; + char msg[1024]; + + if (cv->value < debugLevel) + { + return; + } + + if ( debugNPCName->string[0] && Q_stricmp( debugNPCName->string, printNPC->targetname) != 0 ) + { + return; + } + + if (debugLevel == DEBUG_LEVEL_DETAIL) + color = COLOR_WHITE; + else if (debugLevel == DEBUG_LEVEL_INFO) + color = COLOR_GREEN; + else if (debugLevel == DEBUG_LEVEL_WARNING) + color = COLOR_YELLOW; + else if (debugLevel == DEBUG_LEVEL_ERROR) + color = COLOR_RED; + else + color = COLOR_RED; + + va_start (argptr,fmt); + vsprintf (msg, fmt, argptr); + va_end (argptr); + + gi.Printf ("%c%c%5i (%s) %s", Q_COLOR_ESCAPE, color, level.time, printNPC->targetname, msg); +} diff --git a/code/game/NPC_move.cpp b/code/game/NPC_move.cpp new file mode 100644 index 0000000..db14f3c --- /dev/null +++ b/code/game/NPC_move.cpp @@ -0,0 +1,844 @@ +// +// NPC_move.cpp +// + +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" + +extern qboolean NPC_ClearPathToGoal( vec3_t dir, gentity_t *goal ); +extern qboolean NAV_MoveDirSafe( gentity_t *self, usercmd_t *cmd, float distScale = 1.0f ); + +void CG_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color ); + +qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2); +extern int GetTime ( int lastTime ); + +navInfo_t frameNavInfo; +extern qboolean FlyingCreature( gentity_t *ent ); +extern qboolean PM_InKnockDown( playerState_t *ps ); + +extern cvar_t *g_navSafetyChecks; + +extern qboolean Boba_Flying( gentity_t *self ); +extern qboolean PM_InRoll( playerState_t *ps ); + +#define APEX_HEIGHT 200.0f +#define PARA_WIDTH (sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT)) +#define JUMP_SPEED 200.0f + + + + +static qboolean NPC_TryJump(); + + + + +static qboolean NPC_Jump( vec3_t dest, int goalEntNum ) +{//FIXME: if land on enemy, knock him down & jump off again + float targetDist, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed, + float originalShotSpeed, shotSpeed, speedStep = 50.0f, minShotSpeed = 30.0f, maxShotSpeed = 500.0f; + qboolean belowBlocked = qfalse, aboveBlocked = qfalse; + vec3_t targetDir, shotVel, failCase; + trace_t trace; + trajectory_t tr; + qboolean blocked; + int elapsedTime, timeStep = 250, hitCount = 0, aboveTries = 0, belowTries = 0, maxHits = 10; + vec3_t lastPos, testPos, bottom; + + VectorSubtract( dest, NPC->currentOrigin, targetDir ); + targetDist = VectorNormalize( targetDir ); + //make our shotSpeed reliant on the distance + originalShotSpeed = targetDist;//DistanceHorizontal( dest, NPC->currentOrigin )/2.0f; + if ( originalShotSpeed > maxShotSpeed ) + { + originalShotSpeed = maxShotSpeed; + } + else if ( originalShotSpeed < minShotSpeed ) + { + originalShotSpeed = minShotSpeed; + } + shotSpeed = originalShotSpeed; + + while ( hitCount < maxHits ) + { + VectorScale( targetDir, shotSpeed, shotVel ); + travelTime = targetDist/shotSpeed; + shotVel[2] += travelTime * 0.5 * NPC->client->ps.gravity; + + if ( !hitCount ) + {//save the first one as the worst case scenario + VectorCopy( shotVel, failCase ); + } + + if ( 1 )//tracePath ) + {//do a rough trace of the path + blocked = qfalse; + + VectorCopy( NPC->currentOrigin, tr.trBase ); + VectorCopy( shotVel, tr.trDelta ); + tr.trType = TR_GRAVITY; + tr.trTime = level.time; + travelTime *= 1000.0f; + VectorCopy( NPC->currentOrigin, lastPos ); + + //This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down? + for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep ) + { + if ( (float)elapsedTime > travelTime ) + {//cap it + elapsedTime = floor( travelTime ); + } + EvaluateTrajectory( &tr, level.time + elapsedTime, testPos ); + //FUCK IT, always check for do not enter... + gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + /* + if ( testPos[2] < lastPos[2] + && elapsedTime < floor( travelTime ) ) + {//going down, haven't reached end, ignore botclip + gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask ); + } + else + {//going up, check for botclip + gi.trace( &trace, lastPos, NPC->mins, NPC->maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + } + */ + + if ( trace.allsolid || trace.startsolid ) + {//started in solid + if ( NAVDEBUG_showCollision ) + { + CG_DrawEdge( lastPos, trace.endpos, EDGE_RED_TWOSECOND ); + } + return qfalse;//you're hosed, dude + } + if ( trace.fraction < 1.0f ) + {//hit something + if ( NAVDEBUG_showCollision ) + { + CG_DrawEdge( lastPos, trace.endpos, EDGE_RED_TWOSECOND ); // TryJump + } + if ( trace.entityNum == goalEntNum ) + {//hit the enemy, that's bad! + blocked = qtrue; + /* + if ( g_entities[goalEntNum].client && g_entities[goalEntNum].client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//bah, would collide in mid-air, no good + blocked = qtrue; + } + else + {//he's on the ground, good enough, I guess + //Hmm, don't want to land on him, though...? + } + */ + break; + } + else + { + if ( trace.contents & CONTENTS_BOTCLIP ) + {//hit a do-not-enter brush + blocked = qtrue; + break; + } + if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, dest ) < 4096 )//hit within 64 of desired location, should be okay + {//close enough! + break; + } + else + {//FIXME: maybe find the extents of this brush and go above or below it on next try somehow? + impactDist = DistanceSquared( trace.endpos, dest ); + if ( impactDist < bestImpactDist ) + { + bestImpactDist = impactDist; + VectorCopy( shotVel, failCase ); + } + blocked = qtrue; + break; + } + } + } + else + { + if ( NAVDEBUG_showCollision ) + { + CG_DrawEdge( lastPos, testPos, EDGE_WHITE_TWOSECOND ); // TryJump + } + } + if ( elapsedTime == floor( travelTime ) ) + {//reached end, all clear + if ( trace.fraction >= 1.0f ) + {//hmm, make sure we'll land on the ground... + //FIXME: do we care how far below ourselves or our dest we'll land? + VectorCopy( trace.endpos, bottom ); + bottom[2] -= 128; + gi.trace( &trace, trace.endpos, NPC->mins, NPC->maxs, bottom, NPC->s.number, NPC->clipmask ); + if ( trace.fraction >= 1.0f ) + {//would fall too far + blocked = qtrue; + } + } + break; + } + else + { + //all clear, try next slice + VectorCopy( testPos, lastPos ); + } + } + if ( blocked ) + {//hit something, adjust speed (which will change arc) + hitCount++; + //alternate back and forth between trying an arc slightly above or below the ideal + if ( (hitCount%2) && !belowBlocked ) + {//odd + belowTries++; + shotSpeed = originalShotSpeed - (belowTries*speedStep); + } + else if ( !aboveBlocked ) + {//even + aboveTries++; + shotSpeed = originalShotSpeed + (aboveTries*speedStep); + } + else + {//can't go any higher or lower + hitCount = maxHits; + break; + } + if ( shotSpeed > maxShotSpeed ) + { + shotSpeed = maxShotSpeed; + aboveBlocked = qtrue; + } + else if ( shotSpeed < minShotSpeed ) + { + shotSpeed = minShotSpeed; + belowBlocked = qtrue; + } + } + else + {//made it! + break; + } + } + else + {//no need to check the path, go with first calc + break; + } + } + + if ( hitCount >= maxHits ) + {//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?) + return qfalse; + //NOTE: or try failcase? + //VectorCopy( failCase, NPC->client->ps.velocity ); + //return qtrue; + } + VectorCopy( shotVel, NPC->client->ps.velocity ); + return qtrue; +} + + + + + + +#define NPC_JUMP_PREP_BACKUP_DIST 34.0f + +trace_t mJumpTrace; + + + +qboolean NPC_CanTryJump() +{ + if (!(NPCInfo->scriptFlags&SCF_NAV_CAN_JUMP) || // Can't Jump + (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) || // If Can't Jump At All + (level.timejumpBackupTime) || // If Backing Up, Don't Try The Jump Again + (level.timejumpNextCheckTime) || // Don't Even Try To Jump Again For This Amount Of Time + (NPCInfo->jumpTime) || // Don't Jump If Already Going + (PM_InKnockDown(&NPC->client->ps)) || // Don't Jump If In Knockdown + (PM_InRoll(&NPC->client->ps)) || // ... Or Roll + (NPC->client->ps.groundEntityNum==ENTITYNUM_NONE) // ... Or In The Air + ) + { + return qfalse; + } + return qtrue; +} + +qboolean NPC_TryJump(const vec3_t& pos, float max_xy_dist, float max_z_diff) +{ + if (NPC_CanTryJump()) + { + NPCInfo->jumpNextCheckTime = level.time + Q_irand(1000, 2000); + + VectorCopy(pos, NPCInfo->jumpDest); + + // Can't Try To Jump At A Point In The Air + //----------------------------------------- + { + vec3_t groundTest; + VectorCopy(pos, groundTest); + groundTest[2] += (NPC->mins[2]*3); + gi.trace(&mJumpTrace, NPCInfo->jumpDest, vec3_origin, vec3_origin, groundTest, NPC->s.number, NPC->clipmask ); + if (mJumpTrace.fraction >= 1.0f) + { + return qfalse; //no ground = no jump + } + } + NPCInfo->jumpTarget = 0; + NPCInfo->jumpMaxXYDist = (max_xy_dist)?(max_xy_dist):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?1200:750); + NPCInfo->jumpMazZDist = (max_z_diff)?(max_z_diff):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?-1000:-450); + NPCInfo->jumpTime = 0; + NPCInfo->jumpBackupTime = 0; + return NPC_TryJump(); + } + return qfalse; +} + +qboolean NPC_TryJump(gentity_t *goal, float max_xy_dist, float max_z_diff) +{ + if (NPC_CanTryJump()) + { + NPCInfo->jumpNextCheckTime = level.time + Q_irand(1000, 3000); + + // Can't Jump At Targets In The Air + //--------------------------------- + if (goal->client && goal->client->ps.groundEntityNum==ENTITYNUM_NONE) + { + return qfalse; + } + VectorCopy(goal->currentOrigin, NPCInfo->jumpDest); + NPCInfo->jumpTarget = goal; + NPCInfo->jumpMaxXYDist = (max_xy_dist)?(max_xy_dist):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?1200:750); + NPCInfo->jumpMazZDist = (max_z_diff)?(max_z_diff):((NPC->client->NPC_class==CLASS_ROCKETTROOPER)?-1000:-400); + NPCInfo->jumpTime = 0; + NPCInfo->jumpBackupTime = 0; + return NPC_TryJump(); + } + return qfalse; +} + +void NPC_JumpAnimation() +{ + int jumpAnim = BOTH_JUMP1; + + if ( NPC->client->NPC_class == CLASS_BOBAFETT + || (NPC->client->NPC_class == CLASS_REBORN && NPC->s.weapon != WP_SABER) + || NPC->client->NPC_class == CLASS_ROCKETTROOPER + ||( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG ) ) + {//can't do acrobatics + jumpAnim = BOTH_FORCEJUMP1; + } + else if (NPC->client->NPC_class != CLASS_HOWLER) + { + if ( NPC->client->NPC_class == CLASS_ALORA && Q_irand( 0, 3 ) ) + { + jumpAnim = Q_irand( BOTH_ALORA_FLIP_1, BOTH_ALORA_FLIP_3 ); + } + else + { + jumpAnim = BOTH_FLIP_F; + } + } + NPC_SetAnim( NPC, SETANIM_BOTH, jumpAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +} + +extern void JET_FlyStart(gentity_t* actor); + +void NPC_JumpSound() +{ + if ( NPC->client->NPC_class == CLASS_HOWLER ) + { + //FIXME: can I delay the actual jump so that it matches the anim...? + } + else if ( NPC->client->NPC_class == CLASS_BOBAFETT + || NPC->client->NPC_class == CLASS_ROCKETTROOPER ) + { + // does this really need to be here? + JET_FlyStart(NPC); + } + else + { + G_SoundOnEnt( NPC, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } +} + +qboolean NPC_TryJump() +{ + vec3_t targetDirection; + float targetDistanceXY; + float targetDistanceZ; + + // Get The Direction And Distances To The Target + //----------------------------------------------- + VectorSubtract(NPCInfo->jumpDest, NPC->currentOrigin, targetDirection); + targetDirection[2] = 0.0f; + targetDistanceXY = VectorNormalize(targetDirection); + targetDistanceZ = NPCInfo->jumpDest[2] - NPC->currentOrigin[2]; + + if ((targetDistanceXY>NPCInfo->jumpMaxXYDist) || + (targetDistanceZjumpMazZDist)) + { + return qfalse; + } + + + // Test To See If There Is A Wall Directly In Front Of Actor, If So, Backup Some + //------------------------------------------------------------------------------- + if (TIMER_Done(NPC, "jumpBackupDebounce")) + { + vec3_t actorProjectedTowardTarget; + VectorMA(NPC->currentOrigin, NPC_JUMP_PREP_BACKUP_DIST, targetDirection, actorProjectedTowardTarget); + gi.trace(&mJumpTrace, NPC->currentOrigin, vec3_origin, vec3_origin, actorProjectedTowardTarget, NPC->s.number, NPC->clipmask); + if ((mJumpTrace.fraction < 1.0f) || + (mJumpTrace.allsolid) || + (mJumpTrace.startsolid)) + { + if (NAVDEBUG_showCollision) + { + CG_DrawEdge(NPC->currentOrigin, actorProjectedTowardTarget, EDGE_RED_TWOSECOND); // TryJump + } + + // TODO: We may want to test to see if it is safe to back up here? + NPCInfo->jumpBackupTime = level.time + 1000; + TIMER_Set(NPC, "jumpBackupDebounce", 5000); + return qtrue; + } + } + + +// bool Wounded = (NPC->health < 150); +// bool OnLowerLedge = ((targetDistanceZ<-80.0f) && (targetDistanceZ>-200.0f)); +// bool WithinNormalJumpRange = ((targetDistanceZ<32.0f) && (targetDistanceXY<200.0f)); + bool WithinForceJumpRange = ((fabsf(targetDistanceZ)>0) || (targetDistanceXY>128)); + +/* if (Wounded && OnLowerLedge) + { + ucmd.forwardmove = 127; + VectorClear(NPC->client->ps.moveDir); + TIMER_Set(NPC, "duck", -level.time); + return qtrue; + } + + if (WithinNormalJumpRange) + { + ucmd.upmove = 127; + ucmd.forwardmove = 127; + VectorClear(NPC->client->ps.moveDir); + TIMER_Set(NPC, "duck", -level.time); + return qtrue; + } +*/ + + if (!WithinForceJumpRange) + { + return qfalse; + } + + + + // If There Is Any Chance That This Jump Will Land On An Enemy, Try 8 Different Traces Around The Target + //------------------------------------------------------------------------------------------------------- + if (NPCInfo->jumpTarget) + { + float minSafeRadius = (NPC->maxs[0]*1.5f) + (NPCInfo->jumpTarget->maxs[0]*1.5f); + float minSafeRadiusSq = (minSafeRadius * minSafeRadius); + + if (DistanceSquared(NPCInfo->jumpDest, NPCInfo->jumpTarget->currentOrigin)jumpDest, startPos); + + floorPos[2] = NPCInfo->jumpDest[2] + (NPC->mins[2]-32); + + for (int sideTryCount=0; sideTryCount<8; sideTryCount++) + { + NPCInfo->jumpSide++; + if ( NPCInfo->jumpSide > 7 ) + { + NPCInfo->jumpSide = 0; + } + + switch ( NPCInfo->jumpSide ) + { + case 0: + NPCInfo->jumpDest[0] = startPos[0] + minSafeRadius; + NPCInfo->jumpDest[1] = startPos[1]; + break; + case 1: + NPCInfo->jumpDest[0] = startPos[0] + minSafeRadius; + NPCInfo->jumpDest[1] = startPos[1] + minSafeRadius; + break; + case 2: + NPCInfo->jumpDest[0] = startPos[0]; + NPCInfo->jumpDest[1] = startPos[1] + minSafeRadius; + break; + case 3: + NPCInfo->jumpDest[0] = startPos[0] - minSafeRadius; + NPCInfo->jumpDest[1] = startPos[1] + minSafeRadius; + break; + case 4: + NPCInfo->jumpDest[0] = startPos[0] - minSafeRadius; + NPCInfo->jumpDest[1] = startPos[1]; + break; + case 5: + NPCInfo->jumpDest[0] = startPos[0] - minSafeRadius; + NPCInfo->jumpDest[1] = startPos[1] - minSafeRadius; + break; + case 6: + NPCInfo->jumpDest[0] = startPos[0]; + NPCInfo->jumpDest[1] = startPos[1] - minSafeRadius; + break; + case 7: + NPCInfo->jumpDest[0] = startPos[0] + minSafeRadius; + NPCInfo->jumpDest[1] = startPos[1] -=minSafeRadius; + break; + } + + floorPos[0] = NPCInfo->jumpDest[0]; + floorPos[1] = NPCInfo->jumpDest[1]; + + gi.trace(&mJumpTrace, NPCInfo->jumpDest, NPC->mins, NPC->maxs, floorPos, (NPCInfo->jumpTarget)?(NPCInfo->jumpTarget->s.number):(NPC->s.number), (NPC->clipmask|CONTENTS_BOTCLIP)); + if ((mJumpTrace.fraction<1.0f) && + (!mJumpTrace.allsolid) && + (!mJumpTrace.startsolid)) + { + break; + } + + if ( NAVDEBUG_showCollision ) + { + CG_DrawEdge( NPCInfo->jumpDest, floorPos, EDGE_RED_TWOSECOND ); + } + } + + // If All Traces Failed, Just Try Going Right Back At The Target Location + //------------------------------------------------------------------------ + if ((mJumpTrace.fraction>=1.0f) || + (mJumpTrace.allsolid) || + (mJumpTrace.startsolid)) + { + VectorCopy(startPos, NPCInfo->jumpDest); + } + } + } + + // Now, Actually Try The Jump To The Dest Target + //----------------------------------------------- + if (NPC_Jump(NPCInfo->jumpDest, (NPCInfo->jumpTarget)?(NPCInfo->jumpTarget->s.number):(NPC->s.number))) + { + // We Made IT! + //------------- + NPC_JumpAnimation(); + NPC_JumpSound(); + + NPC->client->ps.forceJumpZStart = NPC->currentOrigin[2]; + NPC->client->ps.pm_flags |= PMF_JUMPING; + NPC->client->ps.weaponTime = NPC->client->ps.torsoAnimTimer; + NPC->client->ps.forcePowersActive |= ( 1 << FP_LEVITATION ); + ucmd.forwardmove = 0; + NPCInfo->jumpTime = 1; + + VectorClear(NPC->client->ps.moveDir); + TIMER_Set(NPC, "duck", -level.time); + + return qtrue; + } + return qfalse; +} + +qboolean NPC_Jumping() +{ + if ( NPCInfo->jumpTime ) + { + if ( !(NPC->client->ps.pm_flags & PMF_JUMPING )//forceJumpZStart ) + && !(NPC->client->ps.pm_flags&PMF_TRIGGER_PUSHED)) + {//landed + NPCInfo->jumpTime = 0; + } + else + { + // if (NPCInfo->jumpTarget) + // { + // NPC_FaceEntity(NPCInfo->jumpTarget, qtrue); + // } + // else + { + NPC_FacePosition(NPCInfo->jumpDest, qtrue); + } + return qtrue; + } + } + return qfalse; +} + +qboolean NPC_JumpBackingUp() +{ + if (NPCInfo->jumpBackupTime) + { + if (level.timejumpBackupTime) + { + STEER::Activate(NPC); + STEER::Flee(NPC, NPCInfo->jumpDest); + STEER::DeActivate(NPC, &ucmd); + NPC_FacePosition(NPCInfo->jumpDest, qtrue); + NPC_UpdateAngles( qfalse, qtrue ); + return qtrue; + } + + NPCInfo->jumpBackupTime = 0; + return NPC_TryJump(); + } + return false; +} + + + + +/* +------------------------- +NPC_CheckCombatMove +------------------------- +*/ + +inline qboolean NPC_CheckCombatMove( void ) +{ + //return NPCInfo->combatMove; + if ( ( NPCInfo->goalEntity && NPC->enemy && NPCInfo->goalEntity == NPC->enemy ) || ( NPCInfo->combatMove ) ) + { + return qtrue; + } + + if ( NPCInfo->goalEntity && NPCInfo->watchTarget ) + { + if ( NPCInfo->goalEntity != NPCInfo->watchTarget ) + { + return qtrue; + } + } + + return qfalse; +} + +/* +------------------------- +NPC_LadderMove +------------------------- +*/ + +static void NPC_LadderMove( vec3_t dir ) +{ + //FIXME: this doesn't guarantee we're facing ladder + //ALSO: Need to be able to get off at top + //ALSO: Need to play an anim + //ALSO: Need transitionary anims? + + if ( ( dir[2] > 0 ) || ( dir[2] < 0 && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) ) + { + //Set our movement direction + ucmd.upmove = (dir[2] > 0) ? 127 : -127; + + //Don't move around on XY + ucmd.forwardmove = ucmd.rightmove = 0; + } +} + +/* +------------------------- +NPC_GetMoveInformation +------------------------- +*/ + +inline qboolean NPC_GetMoveInformation( vec3_t dir, float *distance ) +{ + //NOTENOTE: Use path stacks! + + //Make sure we have somewhere to go + if ( NPCInfo->goalEntity == NULL ) + return qfalse; + + //Get our move info + VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir ); + *distance = VectorNormalize( dir ); + + VectorCopy( NPCInfo->goalEntity->currentOrigin, NPCInfo->blockedTargetPosition ); + + return qtrue; +} + +/* +------------------------- +NAV_GetLastMove +------------------------- +*/ + +void NAV_GetLastMove( navInfo_t &info ) +{ + info = frameNavInfo; +} + + +void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir ) +{ + vec3_t forward, right; + + AngleVectors( self->currentAngles, forward, right, NULL ); + + dir[2] = 0; + VectorNormalize( dir ); + //NPCs cheat and store this directly because converting movement into a ucmd loses precision + VectorCopy( dir, self->client->ps.moveDir ); + + float fDot = DotProduct( forward, dir ) * 127.0f; + float rDot = DotProduct( right, dir ) * 127.0f; + //Must clamp this because DotProduct is not guaranteed to return a number within -1 to 1, and that would be bad when we're shoving this into a signed byte + if ( fDot > 127.0f ) + { + fDot = 127.0f; + } + if ( fDot < -127.0f ) + { + fDot = -127.0f; + } + if ( rDot > 127.0f ) + { + rDot = 127.0f; + } + if ( rDot < -127.0f ) + { + rDot = -127.0f; + } + cmd->forwardmove = floor(fDot); + cmd->rightmove = floor(rDot); + + /* + vec3_t wishvel; + for ( int i = 0 ; i < 3 ; i++ ) + { + wishvel[i] = forward[i]*cmd->forwardmove + right[i]*cmd->rightmove; + } + VectorNormalize( wishvel ); + if ( !VectorCompare( wishvel, dir ) ) + { + Com_Printf( "PRECISION LOSS: %s != %s\n", vtos(wishvel), vtos(dir) ); + } + */ +} + +/* +------------------------- +NPC_MoveToGoal + + Now assumes goal is goalEntity, was no reason for it to be otherwise +------------------------- +*/ + +#if AI_TIMERS +extern int navTime; +#endif// AI_TIMERS +qboolean NPC_MoveToGoal( qboolean tryStraight ) //FIXME: tryStraight not even used! Stop passing it +{ +#if AI_TIMERS + int startTime = GetTime(0); +#endif// AI_TIMERS + + if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->client->ps.legsAnim >= BOTH_PAIN1 ) && ( NPC->client->ps.legsAnim <= BOTH_PAIN18 ) && NPC->client->ps.legsAnimTimer > 0 ) ) + {//If taking full body pain, don't move + return qtrue; + } + + if( NPC->s.eFlags & EF_LOCKED_TO_WEAPON ) + {//If in an emplaced gun, never try to navigate! + return qtrue; + } + + if( NPC->s.eFlags & EF_HELD_BY_RANCOR ) + {//If in a rancor's hand, never try to navigate! + return qtrue; + } + if( NPC->s.eFlags & EF_HELD_BY_WAMPA ) + {//If in a wampa's hand, never try to navigate! + return qtrue; + } + if( NPC->s.eFlags & EF_HELD_BY_SAND_CREATURE ) + {//If in a worm's mouth, never try to navigate! + return qtrue; + } + + if ( NPC->watertype & CONTENTS_LADDER ) + {//Do we still want to do this? + vec3_t dir; + VectorSubtract( NPCInfo->goalEntity->currentOrigin, NPC->currentOrigin, dir ); + VectorNormalize( dir ); + NPC_LadderMove( dir ); + } + + + bool moveSuccess = true; + STEER::Activate(NPC); + { + // Attempt To Steer Directly To Our Goal + //--------------------------------------- + moveSuccess = STEER::GoTo(NPC, NPCInfo->goalEntity, NPCInfo->goalRadius); + + // Perhaps Not Close Enough? Try To Use The Navigation Grid + //----------------------------------------------------------- + if (!moveSuccess) + { + moveSuccess = NAV::GoTo(NPC, NPCInfo->goalEntity); + if (!moveSuccess) + { + STEER::Stop(NPC); + } + } + } + STEER::DeActivate(NPC, &ucmd); + + + #if AI_TIMERS + navTime += GetTime( startTime ); + #endif// AI_TIMERS + return moveSuccess; +} + +/* +------------------------- +void NPC_SlideMoveToGoal( void ) + + Now assumes goal is goalEntity, if want to use tempGoal, you set that before calling the func +------------------------- +*/ +qboolean NPC_SlideMoveToGoal( void ) +{ + float saveYaw = NPC->client->ps.viewangles[YAW]; + + NPCInfo->combatMove = qtrue; + + qboolean ret = NPC_MoveToGoal( qtrue ); + + NPCInfo->desiredYaw = saveYaw; + + return ret; +} + + +/* +------------------------- +NPC_ApplyRoff +------------------------- +*/ + +void NPC_ApplyRoff(void) +{ + PlayerStateToEntityState( &NPC->client->ps, &NPC->s ); + VectorCopy ( NPC->currentOrigin, NPC->lastOrigin ); + + // use the precise origin for linking + gi.linkentity(NPC); +} + diff --git a/code/game/NPC_reactions.cpp b/code/game/NPC_reactions.cpp new file mode 100644 index 0000000..66e31ad --- /dev/null +++ b/code/game/NPC_reactions.cpp @@ -0,0 +1,1164 @@ +//NPC_reactions.cpp + +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + + + + +#include "b_local.h" +#include "anims.h" +#include "g_functions.h" +#include "wp_saber.h" +#include "g_Vehicles.h" + +extern qboolean G_CheckForStrongAttackMomentum( gentity_t *self ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern void cgi_S_StartSound( const vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); +extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim ); +extern qboolean NPC_CheckLookTarget( gentity_t *self ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +extern qboolean Jedi_WaitingAmbush( gentity_t *self ); +extern void Jedi_Ambush( gentity_t *self ); +extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker ); + +extern qboolean PM_SaberInSpecialAttack( int anim ); +extern qboolean PM_SpinningSaberAnim( int anim ); +extern qboolean PM_SpinningAnim( int anim ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean PM_CrouchAnim( int anim ); +extern qboolean PM_FlippingAnim( int anim ); +extern qboolean PM_RollingAnim( int anim ); +extern qboolean PM_InCartwheel( int anim ); + +extern cvar_t *g_spskill; +extern int teamLastEnemyTime[]; +extern qboolean stop_icarus; +extern int killPlayerTimer; + +float g_crosshairEntDist = Q3_INFINITE; +int g_crosshairSameEntTime = 0; +int g_crosshairEntNum = ENTITYNUM_NONE; +int g_crosshairEntTime = 0; +/* +------------------------- +NPC_CheckAttacker +------------------------- +*/ + +static void NPC_CheckAttacker( gentity_t *other, int mod ) +{ + //FIXME: I don't see anything in here that would stop teammates from taking a teammate + // as an enemy. Ideally, there would be code before this to prevent that from + // happening, but that is presumptuous. + + //valid ent - FIXME: a VALIDENT macro would be nice here + if ( !other ) + return; + + if ( other == NPC ) + return; + + if ( !other->inuse ) + return; + + //Don't take a target that doesn't want to be + if ( other->flags & FL_NOTARGET ) + return; + + if ( NPC->svFlags & SVF_LOCKEDENEMY ) + {//IF LOCKED, CANNOT CHANGE ENEMY!!!!! + return; + } + + //If we haven't taken a target, just get mad + if ( NPC->enemy == NULL )//was using "other", fixed to NPC + { + G_SetEnemy( NPC, other ); + return; + } + + //we have an enemy, see if he's dead + if ( NPC->enemy->health <= 0 ) + { + G_ClearEnemy( NPC ); + G_SetEnemy( NPC, other ); + return; + } + + //Don't take the same enemy again + if ( other == NPC->enemy ) + return; + + if ( NPC->client->ps.weapon == WP_SABER ) + {//I'm a jedi + if ( mod == MOD_SABER ) + {//I was hit by a saber FIXME: what if this was a thrown saber? + //always switch to this enemy if I'm a jedi and hit by another saber + G_ClearEnemy( NPC ); + G_SetEnemy( NPC, other ); + return; + } + } + //Special case player interactions + if ( other == &g_entities[0] ) + { + //Account for the skill level to skew the results + float luckThreshold; + + switch ( g_spskill->integer ) + { + //Easiest difficulty, mild chance of picking up the player + case 0: + luckThreshold = 0.9f; + break; + + //Medium difficulty, half-half chance of picking up the player + case 1: + luckThreshold = 0.5f; + break; + + //Hardest difficulty, always turn on attacking player + case 2: + default: + luckThreshold = 0.0f; + break; + } + + //Randomly pick up the target + if ( random() > luckThreshold ) + { + G_ClearEnemy( other ); + other->enemy = NPC; + } + + return; + } +} + +void NPC_SetPainEvent( gentity_t *self ) +{ + if ( !self->NPC || !(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + { + // no more borg + // if( self->client->playerTeam != TEAM_BORG ) + // { + if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) ) + { + G_AddEvent( self, EV_PAIN, floor((float)self->health/self->max_health*100.0f) ); + } + // } + } +} + +/* +------------------------- +NPC_GetPainChance +------------------------- +*/ + +float NPC_GetPainChance( gentity_t *self, int damage ) +{ + if ( !self->enemy ) + {//surprised, always take pain + return 1.0f; + } + + if ( damage > self->max_health/2.0f ) + { + return 1.0f; + } + + float pain_chance = (float)(self->max_health-self->health)/(self->max_health*2.0f) + (float)damage/(self->max_health/2.0f); + switch ( g_spskill->integer ) + { + case 0: //easy + //return 0.75f; + break; + + case 1://med + pain_chance *= 0.5f; + //return 0.35f; + break; + + case 2://hard + default: + pain_chance *= 0.1f; + //return 0.05f; + break; + } + //Com_Printf( "%s: %4.2f\n", self->NPC_type, pain_chance ); + return pain_chance; +} + +/* +------------------------- +NPC_ChoosePainAnimation +------------------------- +*/ + +#define MIN_PAIN_TIME 200 + +extern int G_PickPainAnim( gentity_t *self, const vec3_t point, int damage, int hitLoc ); +void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc, int voiceEvent = -1 ) +{ + //If we've already taken pain, then don't take it again + if ( level.time < self->painDebounceTime && mod != MOD_ELECTROCUTE && mod != MOD_MELEE ) + {//FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim? + return; + } + + int pain_anim = -1; + float pain_chance; + + if ( self->s.weapon == WP_THERMAL && self->client->fireDelay > 0 ) + {//don't interrupt thermal throwing anim + return; + } + else if (self->client->ps.powerups[PW_GALAK_SHIELD]) + { + return; + } + else if ( self->client->NPC_class == CLASS_GALAKMECH ) + { + if ( hitLoc == HL_GENERIC1 ) + {//hit the antenna! + pain_chance = 1.0f; + self->s.powerups |= ( 1 << PW_SHOCKED ); + self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); + } + else if ( self->client->ps.powerups[PW_GALAK_SHIELD] ) + {//shield up + return; + } + else if ( self->health > 200 && damage < 100 ) + {//have a *lot* of health + pain_chance = 0.05f; + } + else + {//the lower my health and greater the damage, the more likely I am to play a pain anim + pain_chance = (200.0f-self->health)/100.0f + damage/50.0f; + } + } + else if ( self->client && self->client->playerTeam == TEAM_PLAYER && other && !other->s.number ) + {//ally shot by player always complains + pain_chance = 1.1f; + } + else + { + if ( other && other->s.weapon == WP_SABER || mod == MOD_ELECTROCUTE || mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/ ) + { + if ( self->client->ps.weapon == WP_SABER + && other->s.number < MAX_CLIENTS ) + {//hmm, shouldn't *always* react to damage from player if I have a saber + pain_chance = 1.05f - ((self->NPC->rank)/(float)RANK_CAPTAIN); + } + else + { + pain_chance = 1.0f;//always take pain from saber + } + } + else if ( mod == MOD_GAS ) + { + pain_chance = 1.0f; + } + else if ( mod == MOD_MELEE ) + {//higher in rank (skill) we are, less likely we are to be fazed by a punch + pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN); + } + else if ( self->client->NPC_class == CLASS_PROTOCOL ) + { + pain_chance = 1.0f; + } + else + { + pain_chance = NPC_GetPainChance( self, damage ); + } + if ( self->client->NPC_class == CLASS_DESANN ) + { + pain_chance *= 0.5f; + } + } + + //See if we're going to flinch + if ( random() < pain_chance ) + { + //Pick and play our animation + if ( (self->client->ps.eFlags&EF_FORCE_GRIPPED) ) + { + G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); + } + else if ( mod == MOD_GAS ) + { + //SIGH... because our choke sounds are inappropriately long, I have to debounce them in code! + if ( TIMER_Done( self, "gasChokeSound" ) ) + { + TIMER_Set( self, "gasChokeSound", Q_irand( 1000, 2000 ) ); + G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); + } + } + else if ( (self->client->ps.eFlags&EF_FORCE_DRAINED) ) + { + NPC_SetPainEvent( self ); + } + else + {//not being force-gripped or force-drained + if ( G_CheckForStrongAttackMomentum( self ) + || PM_SpinningAnim( self->client->ps.legsAnim ) + || PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) + || PM_InKnockDown( &self->client->ps ) + || PM_RollingAnim( self->client->ps.legsAnim ) + || (PM_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) ) + {//strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain + } + else + {//play an anim + if ( self->client->NPC_class == CLASS_GALAKMECH ) + {//only has 1 for now + //FIXME: never plays this, it seems... + pain_anim = BOTH_PAIN1; + } + else if ( mod == MOD_MELEE ) + { + pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); + } + else if ( self->s.weapon == WP_SABER ) + {//temp HACK: these are the only 2 pain anims that look good when holding a saber + pain_anim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); + } + else if ( mod != MOD_ELECTROCUTE ) + { + pain_anim = G_PickPainAnim( self, point, damage, hitLoc ); + } + + if ( pain_anim == -1 ) + { + pain_anim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN18 ); + } + self->client->ps.saberAnimLevel = SS_FAST;//next attack must be a quick attack + self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in + int parts = SETANIM_BOTH; + if ( PM_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) ) + { + parts = SETANIM_LEGS; + } + self->NPC->aiFlags &= ~NPCAI_KNEEL; + NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( voiceEvent != -1 ) + { + G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) ); + } + else + { + NPC_SetPainEvent( self ); + } + } + + //Setup the timing for it + if ( mod == MOD_ELECTROCUTE ) + { + self->painDebounceTime = level.time + 4000; + } + self->painDebounceTime = level.time + PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t) pain_anim ); + self->client->fireDelay = 0; + } +} +extern qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy ); + +gentity_t *G_CheckControlledTurretEnemy(gentity_t *self, gentity_t *enemy, qboolean validate ) +{ + if ( enemy->e_UseFunc == useF_emplaced_gun_use + || enemy->e_UseFunc == useF_eweb_use ) + { + if ( enemy->activator + && enemy->activator->client ) + {//return the controller of the eweb/emplaced gun + if (validate==qfalse || !self->client || G_ValidEnemy(self, enemy)) + { + return enemy->activator; + } + } + return NULL; + } + return enemy; +} + +extern void Boba_Pain( gentity_t *self, gentity_t *inflictor, int damage, int mod); +/* +=============== +NPC_Pain +=============== +*/ +void NPC_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc ) +{ + team_t otherTeam = TEAM_FREE; + int voiceEvent = -1; + + if ( self->NPC == NULL ) + return; + + if ( other == NULL ) + return; + + //or just remove ->pain in player_die? + if ( self->client->ps.pm_type == PM_DEAD ) + return; + + if ( other == self ) + return; + + other = G_CheckControlledTurretEnemy(self, other, qfalse); + if (!other) + { + return; + } + + //MCG: Ignore damage from your own team for now + if ( other->client ) + { + otherTeam = other->client->playerTeam; + // if ( otherTeam == TEAM_DISGUISE ) + // { + // otherTeam = TEAM_PLAYER; + // } + } + + if ( self->client->playerTeam + && other->client + && otherTeam == self->client->playerTeam + && (!player->client->ps.viewEntity || other->s.number != player->client->ps.viewEntity)) + {//hit by a teammate + if ( other != self->enemy && self != other->enemy ) + {//we weren't already enemies + if ( self->enemy || other->enemy + || (other->s.number&&other->s.number!=player->client->ps.viewEntity) + /*|| (!other->s.number&&Q_irand( 0, 3 ))*/ ) + {//if one of us actually has an enemy already, it's okay, just an accident OR wasn't hit by player or someone controlled by player OR player hit ally and didn't get 25% chance of getting mad (FIXME:accumulate anger+base on diff?) + //FIXME: player should have to do a certain amount of damage to ally or hit them several times to make them mad + //Still run pain and flee scripts + if ( self->client && self->NPC ) + {//Run any pain instructions + if ( self->health <= (self->max_health/3) && G_ActivateBehavior(self, BSET_FLEE) ) + { + + } + else// if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) ) + { + G_ActivateBehavior(self, BSET_PAIN); + } + } + if ( damage != -1 ) + {//-1 == don't play pain anim + //Set our proper pain animation + if ( Q_irand( 0, 1 ) ) + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN ); + } + else + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc ); + } + } + return; + } + else if ( self->NPC && !other->s.number )//should be assumed, but... + {//dammit, stop that! + if ( self->NPC->charmedTime > level.time ) + {//mindtricked + return; + } + else if ( self->NPC->ffireCount < 3+((2-g_spskill->integer)*2) ) + {//not mad enough yet + //Com_Printf( "chck: %d < %d\n", self->NPC->ffireCount, 3+((2-g_spskill->integer)*2) ); + if ( damage != -1 ) + {//-1 == don't play pain anim + //Set our proper pain animation + if ( Q_irand( 0, 1 ) ) + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN ); + } + else + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc ); + } + } + return; + } + else if ( G_ActivateBehavior( self, BSET_FFIRE ) ) + {//we have a specific script to run, so do that instead + return; + } + else + {//okay, we're going to turn on our ally, we need to set and lock our enemy and put ourselves in a bstate that lets us attack him (and clear any flags that would stop us) + self->NPC->blockedSpeechDebounceTime = 0; + voiceEvent = EV_FFTURN; + self->NPC->behaviorState = self->NPC->tempBehavior = self->NPC->defaultBehavior = BS_DEFAULT; + other->flags &= ~FL_NOTARGET; + self->svFlags &= ~(SVF_IGNORE_ENEMIES|SVF_ICARUS_FREEZE|SVF_NO_COMBAT_SOUNDS); + G_SetEnemy( self, other ); + self->svFlags |= SVF_LOCKEDENEMY; + self->NPC->scriptFlags &= ~(SCF_DONT_FIRE|SCF_CROUCHED|SCF_WALKING|SCF_NO_COMBAT_TALK|SCF_FORCED_MARCH); + self->NPC->scriptFlags |= (SCF_CHASE_ENEMIES|SCF_NO_MIND_TRICK); + //NOTE: we also stop ICARUS altogether + stop_icarus = qtrue; + if ( !killPlayerTimer ) + { + killPlayerTimer = level.time + 10000; + } + } + } + } + } + + SaveNPCGlobals(); + SetNPCGlobals( self ); + + //Do extra bits + if ( NPCInfo->ignorePain == qfalse ) + { + NPCInfo->confusionTime = 0;//clear any charm or confusion, regardless + if ( NPC->ghoul2.size() && NPC->headBolt != -1 ) + { + G_StopEffect("force/confusion", NPC->playerModel, NPC->headBolt, NPC->s.number ); + } + if ( damage != -1 ) + {//-1 == don't play pain anim + //Set our proper pain animation + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, voiceEvent ); + } + //Check to take a new enemy + if ( NPC->enemy != other && NPC != other ) + {//not already mad at them + //if it's an eweb or emplaced gun, get mad at the owner, not the gun + NPC_CheckAttacker( other, mod ); + } + } + + //Attempt to run any pain instructions + if ( self->client && self->NPC ) + { + //FIXME: This needs better heuristics perhaps + if(self->health <= (self->max_health/3) && G_ActivateBehavior(self, BSET_FLEE) ) + { + } + else //if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) ) + { + G_ActivateBehavior(self, BSET_PAIN); + } + } + + //Attempt to fire any paintargets we might have + if( self->paintarget && self->paintarget[0] ) + { + G_UseTargets2(self, other, self->paintarget); + } + + if (self->client && self->client->NPC_class==CLASS_BOBAFETT) + { + Boba_Pain( self, inflictor, damage, mod); + } + + + RestoreNPCGlobals(); +} + +/* +------------------------- +NPC_Touch +------------------------- +*/ +extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname ); +void NPC_Touch(gentity_t *self, gentity_t *other, trace_t *trace) +{ + if(!self->NPC) + return; + + SaveNPCGlobals(); + SetNPCGlobals( self ); + + if ( self->message && self->health <= 0 ) + {//I am dead and carrying a key + if ( other && player && player->health > 0 && other == player ) + {//player touched me + char *text; + qboolean keyTaken; + //give him my key + if ( Q_stricmp( "goodie", self->message ) == 0 ) + {//a goodie key + if ( (keyTaken = INV_GoodieKeyGive( other )) == qtrue ) + { + text = "cp @SP_INGAME_TOOK_IMPERIAL_GOODIE_KEY"; + G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_GOODIE_KEY )-bg_itemlist) ); + } + else + { + text = "cp @SP_INGAME_CANT_CARRY_GOODIE_KEY"; + } + } + else + {//a named security key + if ( (keyTaken = INV_SecurityKeyGive( player, self->message )) == qtrue ) + { + text = "cp @SP_INGAME_TOOK_IMPERIAL_SECURITY_KEY"; + G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_SECURITY_KEY )-bg_itemlist) ); + } + else + { + text = "cp @SP_INGAME_CANT_CARRY_SECURITY_KEY"; + } + } + if ( keyTaken ) + {//remove my key + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "l_arm_key", 0x00000002 ); + self->message = NULL; + self->client->ps.eFlags &= ~EF_FORCE_VISIBLE; //remove sight flag + G_Sound( player, G_SoundIndex( "sound/weapons/key_pkup.wav" ) ); + } + gi.SendServerCommand( NULL, text ); + } + } + + if ( other->client ) + {//FIXME: if pushing against another bot, both ucmd.rightmove = 127??? + //Except if not facing one another... + if ( other->health > 0 ) + { + NPCInfo->touchedByPlayer = other; + } + + if ( other == NPCInfo->goalEntity ) + { + NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL; + } + + if( !(self->svFlags&SVF_LOCKEDENEMY) && !(self->svFlags&SVF_IGNORE_ENEMIES) && !(other->flags & FL_NOTARGET) ) + { + if ( self->client->enemyTeam ) + {//See if we bumped into an enemy + if ( other->client->playerTeam == self->client->enemyTeam ) + {//bumped into an enemy + if( NPCInfo->behaviorState != BS_HUNT_AND_KILL && !NPCInfo->tempBehavior ) + {//MCG - Begin: checking specific BS mode here, this is bad, a HACK + //FIXME: not medics? + if ( NPC->enemy != other ) + {//not already mad at them + G_SetEnemy( NPC, other ); + } + // NPCInfo->tempBehavior = BS_HUNT_AND_KILL; + } + } + } + } + + //FIXME: do this if player is moving toward me and with a certain dist? + /* + if ( other->s.number == 0 && self->client->playerTeam == other->client->playerTeam ) + { + VectorAdd( self->client->pushVec, other->client->ps.velocity, self->client->pushVec ); + } + */ + } + else + {//FIXME: check for SVF_NONNPC_ENEMY flag here? + if ( other->health > 0 ) + { + if ( NPC->enemy == other && (other->svFlags&SVF_NONNPC_ENEMY) ) + { + NPCInfo->touchedByPlayer = other; + } + } + + if ( other == NPCInfo->goalEntity ) + { + NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL; + } + } + + if ( NPC->client->NPC_class == CLASS_RANCOR ) + {//rancor + if ( NPCInfo->blockedEntity != other && TIMER_Done(NPC, "blockedEntityIgnore")) + {//blocked + //if ( G_EntIsBreakable( other->s.number, NPC ) ) + {//bumped into another breakable, so take that one instead? + NPCInfo->blockedEntity = other;//??? + } + } + } + + RestoreNPCGlobals(); +} + +/* +------------------------- +NPC_TempLookTarget +------------------------- +*/ + +void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ) +{ + if ( !self->client ) + { + return; + } + + if ( !minLookTime ) + { + minLookTime = 1000; + } + + if ( !maxLookTime ) + { + maxLookTime = 1000; + } + + if ( !NPC_CheckLookTarget( self ) ) + {//Not already looking at something else + //Look at him for 1 to 3 seconds + NPC_SetLookTarget( self, lookEntNum, level.time + Q_irand( minLookTime, maxLookTime ) ); + } +} + +void NPC_Respond( gentity_t *self, int userNum ) +{ + int event = -1; + /* + + if ( Q_irand( 0, 1 ) ) + { + event = Q_irand(EV_RESPOND1, EV_RESPOND3); + } + else + { + event = Q_irand(EV_BUSY1, EV_BUSY3); + } + */ + + if ( !Q_irand( 0, 1 ) ) + {//set looktarget to them for a second or two + NPC_TempLookTarget( self, userNum, 1000, 3000 ); + } + + //some last-minute hacked in responses + switch ( self->client->NPC_class ) + { + case CLASS_JAN: + if ( self->enemy ) + { + if ( !Q_irand( 0, 2 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 2 ) ) + { + event = EV_SUSPICIOUS4; + } + else if ( !Q_irand( 0, 1 ) ) + { + event = EV_SOUND1; + } + else + { + event = EV_CONFUSE1; + } + break; + case CLASS_LANDO: + if ( self->enemy ) + { + if ( !Q_irand( 0, 2 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 6 ) ) + { + event = EV_SIGHT2; + } + else if ( !Q_irand( 0, 5 ) ) + { + event = EV_GIVEUP4; + } + else if ( Q_irand( 0, 4 ) > 1 ) + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + else + { + event = Q_irand( EV_JDETECTED1, EV_JDETECTED2 ); + } + break; + case CLASS_LUKE: + if ( self->enemy ) + { + event = EV_COVER1; + } + else + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + break; + case CLASS_JEDI: + case CLASS_KYLE: + if ( !self->enemy ) + { + /* + if ( !(self->svFlags&SVF_IGNORE_ENEMIES) + && (self->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES) + && self->client->enemyTeam == TEAM_ENEMY ) + { + event = Q_irand( EV_ANGER1, EV_ANGER3 ); + } + else + */ + { + event = Q_irand( EV_CONFUSE1, EV_CONFUSE3 ); + } + } + break; + case CLASS_PRISONER: + if ( self->enemy ) + { + if ( Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + } + else + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + break; + case CLASS_REBEL: + if ( self->enemy ) + { + if ( !Q_irand( 0, 2 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else + { + event = Q_irand( EV_DETECTED1, EV_DETECTED5 ); + } + } + else + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + break; + case CLASS_BESPIN_COP: + if ( !Q_stricmp( "bespincop", self->NPC_type ) ) + {//variant 1 + if ( self->enemy ) + { + if ( Q_irand( 0, 9 ) > 6 ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 6 ) > 4 ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 3 ) ) + { + event = Q_irand( EV_SIGHT2, EV_SIGHT3 ); + } + else if ( !Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + else if ( !Q_irand( 0, 2 ) ) + { + event = EV_LOST1; + } + else if ( !Q_irand( 0, 1 ) ) + { + event = EV_ESCAPING2; + } + else + { + event = EV_GIVEUP4; + } + } + else + {//variant2 + if ( self->enemy ) + { + if ( Q_irand( 0, 9 ) > 6 ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 6 ) > 4 ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 3 ) ) + { + event = Q_irand( EV_SIGHT1, EV_SIGHT2 ); + } + else if ( !Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + else if ( !Q_irand( 0, 2 ) ) + { + event = EV_LOST1; + } + else if ( !Q_irand( 0, 1 ) ) + { + event = EV_GIVEUP3; + } + else + { + event = EV_CONFUSE1; + } + } + break; + case CLASS_R2D2: // droid + G_Sound(self, G_SoundIndex(va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3)))); + break; + case CLASS_R5D2: // droid + G_Sound(self, G_SoundIndex(va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4)))); + break; + case CLASS_MOUSE: // droid + G_Sound(self, G_SoundIndex(va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3)))); + break; + case CLASS_GONK: // droid + G_Sound(self, G_SoundIndex(va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2)))); + break; + case CLASS_JAWA: + G_SoundOnEnt(self, CHAN_VOICE, va("sound/chars/jawa/misc/chatter%d.wav",Q_irand(1, 6)) ); + if ( self->NPC ) + { + self->NPC->blockedSpeechDebounceTime = level.time + 2000; + } + break; + } + + if ( event != -1 ) + { + //hack here because we reuse some "combat" and "extra" sounds + qboolean addFlag = (self->NPC->scriptFlags&SCF_NO_COMBAT_TALK); + self->NPC->scriptFlags &= ~SCF_NO_COMBAT_TALK; + + G_AddVoiceEvent( self, event, 3000 ); + + if ( addFlag ) + { + self->NPC->scriptFlags |= SCF_NO_COMBAT_TALK; + } + } +} + +/* +------------------------- +NPC_UseResponse +------------------------- +*/ + +void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone ) +{ + if ( !self->NPC || !self->client ) + { + return; + } + + if ( user->s.number != 0 ) + {//not used by the player + if ( useWhenDone ) + { + G_ActivateBehavior( self, BSET_USE ); + } + return; + } + + if ( user->client && self->client->playerTeam != user->client->playerTeam && self->client->playerTeam != TEAM_NEUTRAL ) + {//only those on the same team react + if ( useWhenDone ) + { + G_ActivateBehavior( self, BSET_USE ); + } + return; + } + + if ( self->NPC->blockedSpeechDebounceTime > level.time ) + {//I'm not responding right now + return; + } + + if ( gi.VoiceVolume[self->s.number] ) + {//I'm talking already + if ( !useWhenDone ) + {//you're not trying to use me + return; + } + } + + if ( useWhenDone ) + { + G_ActivateBehavior( self, BSET_USE ); + } + else + { + NPC_Respond( self, user->s.number ); + } +} + +/* +------------------------- +NPC_Use +------------------------- +*/ +extern void Add_Batteries( gentity_t *ent, int *count ); + +void NPC_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if (self->client->ps.pm_type == PM_DEAD) + {//or just remove ->pain in player_die? + return; + } + + SaveNPCGlobals(); + SetNPCGlobals( self ); + + if(self->client && self->NPC) + { + // If this is a vehicle, let the other guy board it. Added 12/14/02 by AReis. + if ( self->client->NPC_class == CLASS_VEHICLE ) + { + Vehicle_t *pVeh = self->m_pVehicle; + + if ( pVeh && pVeh->m_pVehicleInfo && other && other->client ) + {//safety + //if I used myself, eject everyone on me + if ( other == self ) + { + pVeh->m_pVehicleInfo->EjectAll( pVeh ); + } + // If other is already riding this vehicle (self), eject him. + else if ( other->owner == self ) + { + pVeh->m_pVehicleInfo->Eject( pVeh, other, qfalse ); + } + // Otherwise board this vehicle. + else + { + pVeh->m_pVehicleInfo->Board( pVeh, other ); + } + } + } + else if ( Jedi_WaitingAmbush( NPC ) ) + { + Jedi_Ambush( NPC ); + } + //Run any use instructions + if ( activator && activator->s.number == 0 && self->client->NPC_class == CLASS_GONK ) + { + // must be using the gonk, so attempt to give battery power. + // NOTE: this will steal up to MAX_BATTERIES for the activator, leaving the residual on the gonk for potential later use. + Add_Batteries( activator, &self->client->ps.batteryCharge ); + } + // Not using MEDICs anymore +/* + if ( self->NPC->behaviorState == BS_MEDIC_HIDE && activator->client ) + {//Heal me NOW, dammit! + if ( activator->health < activator->max_health ) + {//person needs help + if ( self->NPC->eventualGoal != activator ) + {//not my current patient already + NPC_TakePatient( activator ); + G_ActivateBehavior( self, BSET_USE ); + } + } + else if ( !self->enemy && activator->s.number == 0 && !gi.VoiceVolume[self->s.number] && !(self->NPC->scriptFlags&SCF_NO_RESPONSE) ) + {//I don't have an enemy and I'm not talking and I was used by the player + NPC_UseResponse( self, other, qfalse ); + } + } +*/ +// else if ( self->behaviorSet[BSET_USE] ) + + if ( self->behaviorSet[BSET_USE] ) + { + NPC_UseResponse( self, other, qtrue ); + } +// else if ( isMedic( self ) ) +// {//Heal me NOW, dammit! +// NPC_TakePatient( activator ); +// } + else if ( !self->enemy + //&& self->client->NPC_class == CLASS_VEHICLE + && activator->s.number == 0 + && !gi.VoiceVolume[self->s.number] + && !(self->NPC->scriptFlags&SCF_NO_RESPONSE) ) + {//I don't have an enemy and I'm not talking and I was used by the player + NPC_UseResponse( self, other, qfalse ); + } + } + + RestoreNPCGlobals(); +} + +void NPC_CheckPlayerAim( void ) +{ + //FIXME: need appropriate dialogue + /* + gentity_t *player = &g_entities[0]; + + if ( player && player->client && player->client->ps.weapon > (int)(WP_NONE) && player->client->ps.weapon < (int)(WP_TRICORDER) ) + {//player has a weapon ready + if ( g_crosshairEntNum == NPC->s.number && level.time - g_crosshairEntTime < 200 + && g_crosshairSameEntTime >= 3000 && g_crosshairEntDist < 256 ) + {//if the player holds the crosshair on you for a few seconds + //ask them what the fuck they're doing + G_AddVoiceEvent( NPC, Q_irand( EV_FF_1A, EV_FF_1C ), 0 ); + } + } + */ +} + +void NPC_CheckAllClear( void ) +{ + //FIXME: need to make this happen only once after losing enemies, not over and over again + /* + if ( NPC->client && !NPC->enemy && level.time - teamLastEnemyTime[NPC->client->playerTeam] > 10000 ) + {//Team hasn't seen an enemy in 10 seconds + if ( !Q_irand( 0, 2 ) ) + { + G_AddVoiceEvent( NPC, Q_irand(EV_SETTLE1, EV_SETTLE3), 3000 ); + } + } + */ +} \ No newline at end of file diff --git a/code/game/NPC_senses.cpp b/code/game/NPC_senses.cpp new file mode 100644 index 0000000..67afb17 --- /dev/null +++ b/code/game/NPC_senses.cpp @@ -0,0 +1,1106 @@ +//NPC_senses.cpp + +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + + + + +#include "b_local.h" +#ifdef _DEBUG + #include +#endif + +extern int eventClearTime; +/* +qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask) + +returns true if can see from point 1 to 2, even through glass (1 pane)- doesn't work with portals +*/ +qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask) +{ + trace_t tr; + + gi.trace ( &tr, point1, NULL, NULL, point2, ignore, clipmask ); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + gentity_t *hit = &g_entities[ tr.entityNum ]; + if(EntIsGlass(hit)) + { + vec3_t newpoint1; + VectorCopy(tr.endpos, newpoint1); + gi.trace (&tr, newpoint1, NULL, NULL, point2, hit->s.number, clipmask ); + + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + } + + return qfalse; +} + +/* +CanSee +determine if NPC can see an entity + +This is a straight line trace check. This function does not look at PVS or FOV, +or take any AI related factors (for example, the NPC's reaction time) into account + +FIXME do we need fat and thin version of this? +*/ +qboolean CanSee ( gentity_t *ent ) +{ + trace_t tr; + vec3_t eyes; + vec3_t spot; + + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); + + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); + gi.trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE ); + ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_HEAD, spot ); + gi.trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE ); + ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_LEGS, spot ); + gi.trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE ); + ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + return qfalse; +} + +qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f ) +{ + vec3_t dir, forward, angles; + float dot; + + VectorSubtract( spot, from, dir ); + dir[2] = 0; + VectorNormalize( dir ); + + VectorCopy( fromAngles, angles ); + angles[0] = 0; + AngleVectors( angles, forward, NULL, NULL ); + + dot = DotProduct( dir, forward ); + + return (dot > threshHold); +} + +float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ) +{ + vec3_t dir, forward, angles; + float dot; + + VectorSubtract( spot, from, dir ); + dir[2] = 0; + VectorNormalize( dir ); + + VectorCopy( fromAngles, angles ); + angles[0] = 0; + AngleVectors( angles, forward, NULL, NULL ); + + dot = DotProduct( dir, forward ); + + return dot; +} +/* +InFOV + +IDEA: further off to side of FOV range, higher chance of failing even if technically in FOV, + keep core of 50% to sides as always succeeding +*/ + +//Position compares + +qboolean InFOV( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV ) +{ + vec3_t deltaVector, angles, deltaAngles; + + VectorSubtract ( spot, from, deltaVector ); + vectoangles ( deltaVector, angles ); + + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + return qfalse; +} + +//NPC to position + +qboolean InFOV( vec3_t origin, gentity_t *from, int hFOV, int vFOV ) +{ + vec3_t fromAngles, eyes; + + if( from->client ) + { + VectorCopy(from->client->ps.viewangles, fromAngles); + } + else + { + VectorCopy(from->s.angles, fromAngles); + } + + CalcEntitySpot( from, SPOT_HEAD, eyes ); + + return InFOV( origin, eyes, fromAngles, hFOV, vFOV ); +} + +//Entity to entity +qboolean InFOVFromPlayerView ( gentity_t *ent, int hFOV, int vFOV ) +{ + vec3_t eyes; + vec3_t spot; + vec3_t deltaVector; + vec3_t angles, fromAngles; + vec3_t deltaAngles; + + if ( !player || !player->client ) + { + return qfalse; + } + if ( cg.time ) + { + VectorCopy( cg.refdefViewAngles, fromAngles ); + } + else + { + VectorCopy( player->client->ps.viewangles, fromAngles ); + } + + if( cg.time ) + { + VectorCopy( cg.refdef.vieworg, eyes ); + } + else + { + CalcEntitySpot( player, SPOT_HEAD_LEAN, eyes ); + } + + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); + VectorSubtract ( spot, eyes, deltaVector); + + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_HEAD, spot ); + VectorSubtract ( spot, eyes, deltaVector); + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_LEGS, spot ); + VectorSubtract ( spot, eyes, deltaVector); + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + return qfalse; +} + +qboolean InFOV ( gentity_t *ent, gentity_t *from, int hFOV, int vFOV ) +{ + vec3_t eyes; + vec3_t spot; + vec3_t deltaVector; + vec3_t angles, fromAngles; + vec3_t deltaAngles; + + if( from->client ) + { + if( from->client->NPC_class != CLASS_RANCOR + && from->client->NPC_class != CLASS_WAMPA + && !VectorCompare( from->client->renderInfo.eyeAngles, vec3_origin ) ) + {//Actual facing of tag_head! + //NOTE: Stasis aliens may have a problem with this? + VectorCopy( from->client->renderInfo.eyeAngles, fromAngles ); + } + else + { + VectorCopy( from->client->ps.viewangles, fromAngles ); + } + } + else + { + VectorCopy(from->s.angles, fromAngles); + } + + CalcEntitySpot( from, SPOT_HEAD_LEAN, eyes ); + + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); + VectorSubtract ( spot, eyes, deltaVector); + + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_HEAD, spot ); + VectorSubtract ( spot, eyes, deltaVector); + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_LEGS, spot ); + VectorSubtract ( spot, eyes, deltaVector); + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + return qfalse; +} + +qboolean InVisrange ( gentity_t *ent ) +{//FIXME: make a calculate visibility for ents that takes into account + //lighting, movement, turning, crouch/stand up, other anims, hide brushes, etc. + vec3_t eyes; + vec3_t spot; + vec3_t deltaVector; + float visrange = (NPCInfo->stats.visrange*NPCInfo->stats.visrange); + + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); + + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); + VectorSubtract ( spot, eyes, deltaVector); + + /*if(ent->client) + { + float vel, avel; + if(ent->client->ps.velocity[0] || ent->client->ps.velocity[1] || ent->client->ps.velocity[2]) + { + vel = VectorLength(ent->client->ps.velocity); + if(vel > 128) + { + visrange += visrange * (vel/256); + } + } + + if(ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + {//FIXME: shouldn't they need to have line of sight to you to detect this? + avel = VectorLength(ent->avelocity); + if(avel > 15) + { + visrange += visrange * (avel/60); + } + } + }*/ + + if(VectorLengthSquared(deltaVector) > visrange) + { + return qfalse; + } + + return qtrue; +} + +/* +NPC_CheckVisibility +*/ + +visibility_t NPC_CheckVisibility ( gentity_t *ent, int flags ) +{ + // flags should never be 0 + if ( !flags ) + { + return VIS_NOT; + } + + // check PVS + if ( flags & CHECK_PVS ) + { + if ( !gi.inPVS ( ent->currentOrigin, NPC->currentOrigin ) ) + { + return VIS_NOT; + } + } + if ( !(flags & (CHECK_360|CHECK_FOV|CHECK_SHOOT)) ) + { + return VIS_PVS; + } + + // check within visrange + if (flags & CHECK_VISRANGE) + { + if( !InVisrange ( ent ) ) + { + return VIS_PVS; + } + } + + // check 360 degree visibility + //Meaning has to be a direct line of site + if ( flags & CHECK_360 ) + { + if ( !CanSee ( ent ) ) + { + return VIS_PVS; + } + } + if ( !(flags & (CHECK_FOV|CHECK_SHOOT)) ) + { + return VIS_360; + } + + // check FOV + if ( flags & CHECK_FOV ) + { + if ( !InFOV ( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov) ) + { + return VIS_360; + } + } + + if ( !(flags & CHECK_SHOOT) ) + { + return VIS_FOV; + } + + // check shootability + if ( flags & CHECK_SHOOT ) + { + if ( !CanShoot ( ent, NPC ) ) + { + return VIS_FOV; + } + } + + return VIS_SHOOT; +} + +/* +------------------------- +NPC_CheckSoundEvents +------------------------- +*/ +static int G_CheckSoundEvents( gentity_t *self, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel, qboolean onGroundOnly ) +{ + int bestEvent = -1; + int bestAlert = -1; + int bestTime = -1; + float dist, radius; + + maxHearDist *= maxHearDist; + + for ( int i = 0; i < level.numAlertEvents; i++ ) + { + //are we purposely ignoring this alert? + if ( level.alertEvents[i].ID == ignoreAlert ) + continue; + //We're only concerned about sounds + if ( level.alertEvents[i].type != AET_SOUND ) + continue; + //must be at least this noticable + if ( level.alertEvents[i].level < minAlertLevel ) + continue; + //must have an owner? + if ( mustHaveOwner && !level.alertEvents[i].owner ) + continue; + //must be on the ground? + if ( onGroundOnly && !level.alertEvents[i].onGround ) + continue; + + //Must be within range + dist = DistanceSquared( level.alertEvents[i].position, self->currentOrigin ); + + //can't hear it + if ( dist > maxHearDist ) + continue; + + if ( self->client && self->client->NPC_class != CLASS_SAND_CREATURE ) + {//sand creatures hear all in within their earshot, regardless of quietness and alert sound radius! + radius = level.alertEvents[i].radius * level.alertEvents[i].radius; + if ( dist > radius ) + continue; + + if ( level.alertEvents[i].addLight ) + {//a quiet sound, must have LOS to hear it + if ( G_ClearLOS( self, level.alertEvents[i].position ) == qfalse ) + {//no LOS, didn't hear it + continue; + } + } + } + + //See if this one takes precedence over the previous one + if ( level.alertEvents[i].level >= bestAlert //higher alert level + || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer + {//NOTE: equal is better because it's later in the array + bestEvent = i; + bestAlert = level.alertEvents[i].level; + bestTime = level.alertEvents[i].timestamp; + } + } + + return bestEvent; +} + +float G_GetLightLevel( vec3_t pos, vec3_t fromDir ) +{ + vec3_t ambient={0}, directed, lightDir; + float lightLevel; + + cgi_R_GetLighting( pos, ambient, directed, lightDir ); + + lightLevel = VectorLength( ambient ) + (VectorLength( directed )*DotProduct( lightDir, fromDir )); + + return lightLevel; +} +/* +------------------------- +NPC_CheckSightEvents +------------------------- +*/ +static int G_CheckSightEvents( gentity_t *self, int hFOV, int vFOV, float maxSeeDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) +{ + int bestEvent = -1; + int bestAlert = -1; + int bestTime = -1; + float dist, radius; + + maxSeeDist *= maxSeeDist; + for ( int i = 0; i < level.numAlertEvents; i++ ) + { + //are we purposely ignoring this alert? + if ( level.alertEvents[i].ID == ignoreAlert ) + continue; + //We're only concerned about sounds + if ( level.alertEvents[i].type != AET_SIGHT ) + continue; + //must be at least this noticable + if ( level.alertEvents[i].level < minAlertLevel ) + continue; + //must have an owner? + if ( mustHaveOwner && !level.alertEvents[i].owner ) + continue; + + //Must be within range + dist = DistanceSquared( level.alertEvents[i].position, self->currentOrigin ); + + //can't see it + if ( dist > maxSeeDist ) + continue; + + radius = level.alertEvents[i].radius * level.alertEvents[i].radius; + if ( dist > radius ) + continue; + + //Must be visible + if ( InFOV( level.alertEvents[i].position, self, hFOV, vFOV ) == qfalse ) + continue; + + if ( G_ClearLOS( self, level.alertEvents[i].position ) == qfalse ) + continue; + + //FIXME: possibly have the light level at this point affect the + // visibility/alert level of this event? Would also + // need to take into account how bright the event + // itself is. A lightsaber would stand out more + // in the dark... maybe pass in a light level that + // is added to the actual light level at this position? + + //See if this one takes precedence over the previous one + if ( level.alertEvents[i].level >= bestAlert //higher alert level + || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer + {//NOTE: equal is better because it's later in the array + bestEvent = i; + bestAlert = level.alertEvents[i].level; + bestTime = level.alertEvents[i].timestamp; + } + } + + return bestEvent; +} + +qboolean G_RememberAlertEvent( gentity_t *self, int alertIndex ) +{ + if ( !self || !self->NPC ) + {//not a valid ent + return qfalse; + } + + if ( alertIndex == -1 ) + {//not a valid event + return qfalse; + } + + // Get The Event Struct + //---------------------- + alertEvent_t& at = level.alertEvents[alertIndex]; + + if ( at.ID == self->NPC->lastAlertID ) + {//already know this one + return qfalse; + } + + if (at.owner==self) + {//don't care about events that I made + return false; + } + + self->NPC->lastAlertID = at.ID; + + // Now, If It Is Dangerous Enough, We Want To Register This With The Pathfinding System + //-------------------------------------------------------------------------------------- + bool IsDangerous = (at.level >= AEL_DANGER); + bool IsFromNPC = (at.owner && at.owner->client); + bool IsFromEnemy = (IsFromNPC && at.owner->client->playerTeam!=self->client->playerTeam); + + if (IsDangerous && (!IsFromNPC || IsFromEnemy)) + { + NAV::RegisterDangerSense(self, alertIndex); + } + + return qtrue; +} +/* +------------------------- +NPC_CheckAlertEvents + + NOTE: Should all NPCs create alertEvents too so they can detect each other? +------------------------- +*/ + +int G_CheckAlertEvents( gentity_t *self, qboolean checkSight, qboolean checkSound, float maxSeeDist, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel, qboolean onGroundOnly ) +{ + if ( &g_entities[0] == NULL || g_entities[0].health <= 0 ) + { + //player is dead + return -1; + } + + int bestSoundEvent = -1; + int bestSightEvent = -1; + int bestSoundAlert = -1; + int bestSightAlert = -1; + + if ( checkSound ) + { + //get sound event + bestSoundEvent = G_CheckSoundEvents( self, maxHearDist, ignoreAlert, mustHaveOwner, minAlertLevel, onGroundOnly ); + //get sound event alert level + if ( bestSoundEvent >= 0 ) + { + bestSoundAlert = level.alertEvents[bestSoundEvent].level; + } + } + + if ( checkSight ) + { + //get sight event + if ( self->NPC ) + { + bestSightEvent = G_CheckSightEvents( self, self->NPC->stats.hfov, self->NPC->stats.vfov, maxSeeDist, ignoreAlert, mustHaveOwner, minAlertLevel ); + } + else + { + bestSightEvent = G_CheckSightEvents( self, 80, 80, maxSeeDist, ignoreAlert, mustHaveOwner, minAlertLevel );//FIXME: look at cg_view to get more accurate numbers? + } + //get sight event alert level + if ( bestSightEvent >= 0 ) + { + bestSightAlert = level.alertEvents[bestSightEvent].level; + } + + //return the one that has a higher alert (or sound if equal) + //FIXME: This doesn't take the distance of the event into account + + if ( bestSightEvent >= 0 && bestSightAlert > bestSoundAlert ) + {//valid best sight event, more important than the sound event + //get the light level of the alert event for this checker + vec3_t eyePoint, sightDir; + //get eye point + CalcEntitySpot( self, SPOT_HEAD_LEAN, eyePoint ); + VectorSubtract( level.alertEvents[bestSightEvent].position, eyePoint, sightDir ); + level.alertEvents[bestSightEvent].light = level.alertEvents[bestSightEvent].addLight + G_GetLightLevel( level.alertEvents[bestSightEvent].position, sightDir ); + //return the sight event + if ( G_RememberAlertEvent( self, bestSightEvent ) ) + { + return bestSightEvent; + } + } + } + //return the sound event + if ( G_RememberAlertEvent( self, bestSoundEvent ) ) + { + return bestSoundEvent; + } + //no event or no new event + return -1; +} + +int NPC_CheckAlertEvents( qboolean checkSight, qboolean checkSound, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel, qboolean onGroundOnly ) +{ + return G_CheckAlertEvents( NPC, checkSight, checkSound, NPCInfo->stats.visrange, NPCInfo->stats.earshot, ignoreAlert, mustHaveOwner, minAlertLevel, onGroundOnly ); +} + +extern void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower ); + +qboolean G_CheckForDanger( gentity_t *self, int alertEvent ) +{//FIXME: more bStates need to call this? + if ( alertEvent == -1 ) + { + return qfalse; + } + + if ( level.alertEvents[alertEvent].level >= AEL_DANGER ) + {//run away! + if ( !level.alertEvents[alertEvent].owner || !level.alertEvents[alertEvent].owner->client || (level.alertEvents[alertEvent].owner!=self&&level.alertEvents[alertEvent].owner->client->playerTeam!=self->client->playerTeam) ) + { + if ( self->NPC ) + { + if ( self->NPC->scriptFlags & SCF_DONT_FLEE ) + {//can't flee + return qfalse; + } + else + { + if ( level.alertEvents[alertEvent].level >= AEL_DANGER_GREAT || self->s.weapon == WP_NONE || self->s.weapon == WP_MELEE ) + {//flee for a longer period of time + NPC_StartFlee( level.alertEvents[alertEvent].owner, level.alertEvents[alertEvent].position, level.alertEvents[alertEvent].level, 3000, 6000 ); + } + else if ( !Q_irand( 0, 10 ) )//FIXME: base on rank? aggression? + {//just normal danger and I have a weapon, so just a 25% chance of fleeing only for a few seconds + //FIXME: used to just find a better combat point, need that functionality back + NPC_StartFlee( level.alertEvents[alertEvent].owner, level.alertEvents[alertEvent].position, level.alertEvents[alertEvent].level, 1000, 3000 ); + } + else + {//didn't flee + TIMER_Set( NPC, "duck", 2000); // something dangerous going on... + return qfalse; + } + return qtrue; + } + } + else + { + return qtrue; + } + } + } + return qfalse; +} +qboolean NPC_CheckForDanger( int alertEvent ) +{//FIXME: more bStates need to call this? + return G_CheckForDanger( NPC, alertEvent ); +} + +/* +------------------------- +AddSoundEvent +------------------------- +*/ +qboolean RemoveOldestAlert( void ); +void AddSoundEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, qboolean needLOS, qboolean onGround ) +{ + //FIXME: Handle this in another manner? + if ( level.numAlertEvents >= MAX_ALERT_EVENTS ) + { + if ( !RemoveOldestAlert() ) + {//how could that fail? + return; + } + } + + if ( owner == NULL && alertLevel < AEL_DANGER ) //allows un-owned danger alerts + return; + + //FIXME: why are Sand creatures suddenly crashlanding? + if ( owner && owner->client && owner->client->NPC_class == CLASS_SAND_CREATURE ) + { + return; + } + + //FIXME: if owner is not a player or player ally, and there are no player allies present, + // perhaps we don't need to store the alert... unless we want the player to + // react to enemy alert events in some way? + +#ifdef _DEBUG + assert( !_isnan(position[0]) && !_isnan(position[1]) && !_isnan(position[2]) ); +#endif + VectorCopy( position, level.alertEvents[ level.numAlertEvents ].position ); + + level.alertEvents[ level.numAlertEvents ].radius = radius; + level.alertEvents[ level.numAlertEvents ].level = alertLevel; + level.alertEvents[ level.numAlertEvents ].type = AET_SOUND; + level.alertEvents[ level.numAlertEvents ].owner = owner; + if ( needLOS ) + {//a very low-level sound, when check this sound event, check for LOS + level.alertEvents[ level.numAlertEvents ].addLight = 1; //will force an LOS trace on this sound + } + else + { + level.alertEvents[ level.numAlertEvents ].addLight = 0; //will force an LOS trace on this sound + } + level.alertEvents[ level.numAlertEvents ].onGround = onGround; + + level.alertEvents[ level.numAlertEvents ].ID = ++level.curAlertID; + level.alertEvents[ level.numAlertEvents ].timestamp = level.time; + level.numAlertEvents++; +} + +/* +------------------------- +AddSightEvent +------------------------- +*/ + +void AddSightEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, float addLight ) +{ + //FIXME: Handle this in another manner? + if ( level.numAlertEvents >= MAX_ALERT_EVENTS ) + { + if ( !RemoveOldestAlert() ) + {//how could that fail? + return; + } + } + + if ( owner == NULL && alertLevel < AEL_DANGER ) //allows un-owned danger alerts + return; + + //FIXME: if owner is not a player or player ally, and there are no player allies present, + // perhaps we don't need to store the alert... unless we want the player to + // react to enemy alert events in some way? + +#ifdef _DEBUG + assert( !_isnan(position[0]) && !_isnan(position[1]) && !_isnan(position[2]) ); +#endif + VectorCopy( position, level.alertEvents[ level.numAlertEvents ].position ); + + level.alertEvents[ level.numAlertEvents ].radius = radius; + level.alertEvents[ level.numAlertEvents ].level = alertLevel; + level.alertEvents[ level.numAlertEvents ].type = AET_SIGHT; + level.alertEvents[ level.numAlertEvents ].owner = owner; + level.alertEvents[ level.numAlertEvents ].addLight = addLight; //will get added to actual light at that point when it's checked + level.alertEvents[ level.numAlertEvents ].ID = level.curAlertID++; + level.alertEvents[ level.numAlertEvents ].timestamp = level.time; + + level.numAlertEvents++; +} + +/* +------------------------- +ClearPlayerAlertEvents +------------------------- +*/ + +void ClearPlayerAlertEvents( void ) +{ + int curNumAlerts = level.numAlertEvents; + //loop through them all (max 32) + for ( int i = 0; i < curNumAlerts; i++ ) + { + //see if the event is old enough to delete + if ( level.alertEvents[i].timestamp && level.alertEvents[i].timestamp + ALERT_CLEAR_TIME < level.time ) + {//this event has timed out + //drop the count + level.numAlertEvents--; + //shift the rest down + if ( level.numAlertEvents > 0 ) + {//still have more in the array + if ( (i+1) < MAX_ALERT_EVENTS ) + { + memmove( &level.alertEvents[i], &level.alertEvents[i+1], sizeof(alertEvent_t)*(MAX_ALERT_EVENTS-(i+1) ) ); + } + } + else + {//just clear this one... or should we clear the whole array? + memset( &level.alertEvents[i], 0, sizeof( alertEvent_t ) ); + } + } + } + //make sure this never drops below zero... if it does, something very very bad happened + assert( level.numAlertEvents >= 0 ); + + if ( eventClearTime < level.time ) + {//this is just a 200ms debouncer so things that generate constant alerts (like corpses and missiles) add an alert every 200 ms + eventClearTime = level.time + ALERT_CLEAR_TIME; + } +} + +qboolean RemoveOldestAlert( void ) +{ + int oldestEvent = -1, oldestTime = Q3_INFINITE; + //loop through them all (max 32) + for ( int i = 0; i < level.numAlertEvents; i++ ) + { + //see if the event is old enough to delete + if ( level.alertEvents[i].timestamp < oldestTime ) + { + oldestEvent = i; + oldestTime = level.alertEvents[i].timestamp; + } + } + if ( oldestEvent != -1 ) + { + //drop the count + level.numAlertEvents--; + //shift the rest down + if ( level.numAlertEvents > 0 ) + {//still have more in the array + if ( (oldestEvent+1) < MAX_ALERT_EVENTS ) + { + memmove( &level.alertEvents[oldestEvent], &level.alertEvents[oldestEvent+1], sizeof(alertEvent_t)*(MAX_ALERT_EVENTS-(oldestEvent+1) ) ); + } + } + else + {//just clear this one... or should we clear the whole array? + memset( &level.alertEvents[oldestEvent], 0, sizeof( alertEvent_t ) ); + } + } + //make sure this never drops below zero... if it does, something very very bad happened + assert( level.numAlertEvents >= 0 ); + //return true is have room for one now + return (level.numAlertEvents hFOV ) + return 0.0f; + + return ( ( hFOV - delta ) / hFOV ); +} + +/* +------------------------- +NPC_GetVFOVPercentage +------------------------- +*/ + +float NPC_GetVFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float vFOV ) +{ + vec3_t deltaVector, angles; + float delta; + + VectorSubtract ( spot, from, deltaVector ); + + vectoangles ( deltaVector, angles ); + + delta = fabs( AngleDelta ( facing[PITCH], angles[PITCH] ) ); + + if ( delta > vFOV ) + return 0.0f; + + return ( ( vFOV - delta ) / vFOV ); +} + +#define MAX_INTEREST_DIST ( 256 * 256 ) +/* +------------------------- +NPC_FindLocalInterestPoint +------------------------- +*/ + +int G_FindLocalInterestPoint( gentity_t *self ) +{ + int i, bestPoint = ENTITYNUM_NONE; + float dist, bestDist = Q3_INFINITE; + vec3_t diffVec, eyes; + + CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes ); + for ( i = 0; i < level.numInterestPoints; i++ ) + { + //Don't ignore portals? If through a portal, need to look at portal! + if ( gi.inPVS( level.interestPoints[i].origin, eyes ) ) + { + VectorSubtract( level.interestPoints[i].origin, eyes, diffVec ); + if ( (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 < 48 && + fabs(diffVec[2]) > (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 ) + {//Too close to look so far up or down + continue; + } + dist = VectorLengthSquared( diffVec ); + //Some priority to more interesting points + //dist -= ((int)level.interestPoints[i].lookMode * 5) * ((int)level.interestPoints[i].lookMode * 5); + if ( dist < MAX_INTEREST_DIST && dist < bestDist ) + { + if ( G_ClearLineOfSight( eyes, level.interestPoints[i].origin, self->s.number, MASK_OPAQUE ) ) + { + bestDist = dist; + bestPoint = i; + } + } + } + } + if ( bestPoint != ENTITYNUM_NONE && level.interestPoints[bestPoint].target ) + { + G_UseTargets2( self, self, level.interestPoints[bestPoint].target ); + } + return bestPoint; +} + +/*QUAKED target_interest (1 0.8 0.5) (-4 -4 -4) (4 4 4) +A point that a squadmate will look at if standing still + +target - thing to fire when someone looks at this thing +*/ + +void SP_target_interest( gentity_t *self ) +{//FIXME: rename point_interest + if(level.numInterestPoints >= MAX_INTEREST_POINTS) + { + gi.Printf("ERROR: Too many interest points, limit is %d\n", MAX_INTEREST_POINTS); + G_FreeEntity(self); + return; + } + + VectorCopy(self->currentOrigin, level.interestPoints[level.numInterestPoints].origin); + + if(self->target && self->target[0]) + { + level.interestPoints[level.numInterestPoints].target = G_NewString( self->target ); + } + + level.numInterestPoints++; + + G_FreeEntity(self); +} diff --git a/code/game/NPC_sounds.cpp b/code/game/NPC_sounds.cpp new file mode 100644 index 0000000..62afeed --- /dev/null +++ b/code/game/NPC_sounds.cpp @@ -0,0 +1,118 @@ +//NPC_sounds.cpp + +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + + + + +#include "b_local.h" +#include "Q3_Interface.h" + +/* +void NPC_AngerSound (void) +{ + if(NPCInfo->investigateSoundDebounceTime) + return; + + NPCInfo->investigateSoundDebounceTime = 1; + +// switch((int)NPC->client->race) +// { +// case RACE_KLINGON: + //G_Sound(NPC, G_SoundIndex(va("sound/mgtest/klingon/talk%d.wav", Q_irand(1, 4)))); +// break; +// } +} +*/ + +extern void G_SpeechEvent( gentity_t *self, int event ); +void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ) +{ + if ( !self->NPC ) + { + return; + } + + if ( !self->client || self->client->ps.pm_type >= PM_DEAD ) + { + return; + } + + if ( self->NPC->blockedSpeechDebounceTime > level.time ) + { + return; + } + + if ( Q3_TaskIDPending( self, TID_CHAN_VOICE ) ) + { + return; + } + + if ( self->client && self->client->NPC_class == CLASS_SABOTEUR ) + { + if ( self->client->ps.powerups[PW_CLOAKED] + || self->client->ps.powerups[PW_UNCLOAKING] > level.time ) + {//I'm cloaked (or still decloaking), so don't talk and give away my position... + //don't make any combat voice noises, but still make pain and death sounds + if ( (event >= EV_ANGER1 && event <= EV_VICTORY3) + || (event >= EV_CHASE1 && event <= EV_SUSPICIOUS5) ) + { + return; + } + + if ( event >= EV_GIVEUP1 && event <= EV_SUSPICIOUS5 ) + { + return; + } + } + } + + if ( (self->NPC->scriptFlags&SCF_NO_COMBAT_TALK) && ( (event >= EV_ANGER1 && event <= EV_VICTORY3) || (event >= EV_CHASE1 && event <= EV_SUSPICIOUS5) ) )//(event < EV_FF_1A || event > EV_FF_3C) && (event < EV_RESPOND1 || event > EV_MISSION3) ) + { + return; + } + + if ( (self->NPC->scriptFlags&SCF_NO_ALERT_TALK) && (event >= EV_GIVEUP1 && event <= EV_SUSPICIOUS5) ) + { + return; + } + //FIXME: Also needs to check for teammates. Don't want + // everyone babbling at once + + //NOTE: was losing too many speech events, so we do it directly now, screw networking! + //G_AddEvent( self, event, 0 ); + G_SpeechEvent( self, event ); + + //won't speak again for 5 seconds (unless otherwise specified) + self->NPC->blockedSpeechDebounceTime = level.time + ((speakDebounceTime==0) ? 5000 : speakDebounceTime); +} + +void NPC_PlayConfusionSound( gentity_t *self ) +{ + if ( self->health > 0 ) + { + if ( self->enemy ||//was mad + !TIMER_Done( self, "enemyLastVisible" ) ||//saw something suspicious + self->client->renderInfo.lookTarget == 0//was looking at player + ) + { + self->NPC->blockedSpeechDebounceTime = 0;//make sure we say this + G_AddVoiceEvent( self, Q_irand( EV_CONFUSE2, EV_CONFUSE3 ), 2000 ); + } + else if ( self->NPC && self->NPC->investigateDebounceTime+self->NPC->pauseTime > level.time )//was checking something out + { + self->NPC->blockedSpeechDebounceTime = 0;//make sure we say this + G_AddVoiceEvent( self, EV_CONFUSE1, 2000 ); + } + //G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} diff --git a/code/game/NPC_spawn.cpp b/code/game/NPC_spawn.cpp new file mode 100644 index 0000000..d77db2d --- /dev/null +++ b/code/game/NPC_spawn.cpp @@ -0,0 +1,4351 @@ +//b_spawn.cpp +//added by MCG + +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + + + +#include "Q3_Interface.h" +#include "b_local.h" +#include "anims.h" +#include "g_functions.h" +#include "wp_saber.h" +#include "g_vehicles.h" + +extern qboolean G_CheckInSolid (gentity_t *self, qboolean fix); +extern void ClientUserinfoChanged( int clientNum ); +extern qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ); +extern void Jedi_Cloak( gentity_t *self ); +extern void Saboteur_Cloak( gentity_t *self ); + +//extern void FX_BorgTeleport( vec3_t org ); + +extern void G_MatchPlayerWeapon( gentity_t *ent ); +extern void Q3_SetParm (int entID, int parmNum, const char *parmValue); + +//extern void CG_ShimmeryThing_Spawner( vec3_t start, vec3_t end, float radius, qboolean taper, int duration ); + +//extern void NPC_StasisSpawnEffect( gentity_t *ent ); + +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); + +extern int WP_SaberInitBladeData( gentity_t *ent ); +extern void ST_ClearTimers( gentity_t *ent ); +extern void Jedi_ClearTimers( gentity_t *ent ); +extern void Howler_ClearTimers( gentity_t *self ); +#define NSF_DROP_TO_FLOOR 16 + + +//void HirogenAlpha_Precache( void ); + +/* +------------------------- +NPC_PainFunc +------------------------- +*/ + +painFunc_t NPC_PainFunc( gentity_t *ent ) +{ + painFunc_t func; + + if ( ent->client->ps.weapon == WP_SABER ) + { + func = painF_NPC_Jedi_Pain; + } + else + { + // team no longer indicates race/species, use NPC_class to determine different npc types + /* + switch ( ent->client->playerTeam ) + { + default: + func = painF_NPC_Pain; + break; + } + */ + switch( ent->client->NPC_class ) + { + // troopers get special pain + case CLASS_SABOTEUR: + case CLASS_STORMTROOPER: + case CLASS_SWAMPTROOPER: + case CLASS_NOGHRI: + func = painF_NPC_ST_Pain; + break; + + case CLASS_SEEKER: + func = painF_NPC_Seeker_Pain; + break; + + case CLASS_REMOTE: + func = painF_NPC_Remote_Pain; + break; + + case CLASS_MINEMONSTER: + func = painF_NPC_MineMonster_Pain; + break; + + case CLASS_HOWLER: + func = painF_NPC_Howler_Pain; + break; + + case CLASS_RANCOR: + func = painF_NPC_Rancor_Pain; + break; + + case CLASS_WAMPA: + func = painF_NPC_Wampa_Pain; + break; + + case CLASS_SAND_CREATURE: + func = painF_NPC_SandCreature_Pain; + break; + + // all other droids, did I miss any? + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + case CLASS_MOUSE: + case CLASS_PROTOCOL: + case CLASS_INTERROGATOR: + func = painF_NPC_Droid_Pain; + break; + case CLASS_PROBE: + func = painF_NPC_Probe_Pain; + break; + + case CLASS_SENTRY: + func = painF_NPC_Sentry_Pain; + break; + case CLASS_MARK1: + func = painF_NPC_Mark1_Pain; + break; + case CLASS_MARK2: + func = painF_NPC_Mark2_Pain; + break; + case CLASS_ATST: + func = painF_NPC_ATST_Pain; + break; + case CLASS_GALAKMECH: + func = painF_NPC_GM_Pain; + break; + // everyone else gets the normal pain func + default: + func = painF_NPC_Pain; + break; + } + + } + + return func; +} + + +/* +------------------------- +NPC_TouchFunc +------------------------- +*/ + +touchFunc_t NPC_TouchFunc( gentity_t *ent ) +{ + return touchF_NPC_Touch; +} + +/* +------------------------- +NPC_SetMiscDefaultData +------------------------- +*/ +void G_ClassSetDontFlee( gentity_t *self ) +{ + if ( !self || !self->client || !self->NPC ) + { + return; + } + switch ( self->client->NPC_class ) + { + case CLASS_ATST: + case CLASS_CLAW: + case CLASS_DESANN: + case CLASS_FISH: + case CLASS_FLIER2: + case CLASS_GALAK: + case CLASS_GLIDER: + case CLASS_RANCOR: + case CLASS_SAND_CREATURE: + case CLASS_INTERROGATOR: // droid + case CLASS_JAN: + case CLASS_JEDI: + case CLASS_KYLE: + case CLASS_LANDO: + case CLASS_LIZARD: + case CLASS_LUKE: + case CLASS_MARK1: // droid + case CLASS_MARK2: // droid + case CLASS_GALAKMECH: // droid + case CLASS_MONMOTHA: + case CLASS_MORGANKATARN: + case CLASS_MURJJ: + case CLASS_PROBE: // droid + case CLASS_REBORN: + case CLASS_REELO: + case CLASS_REMOTE: + case CLASS_SEEKER: // droid + case CLASS_SENTRY: + case CLASS_SHADOWTROOPER: + case CLASS_SWAMP: + case CLASS_TAVION: + case CLASS_ALORA: + case CLASS_BOBAFETT: + case CLASS_SABER_DROID: + case CLASS_ASSASSIN_DROID: + case CLASS_PLAYER: + case CLASS_VEHICLE: + self->NPC->scriptFlags |= SCF_DONT_FLEE; + break; + } + if ( (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) + { + self->NPC->scriptFlags |= SCF_DONT_FLEE; + } + if ( (self->NPC->aiFlags&NPCAI_SUBBOSS_CHARACTER) ) + { + self->NPC->scriptFlags |= SCF_DONT_FLEE; + } + if ( (self->NPC->aiFlags&NPCAI_ROSH) ) + { + self->NPC->scriptFlags |= SCF_DONT_FLEE; + } + if ( (self->NPC->aiFlags&NPCAI_HEAL_ROSH) ) + { + self->NPC->scriptFlags |= SCF_DONT_FLEE; + } +} + +extern void Vehicle_Register(gentity_t *ent); +extern void RT_FlyStart( gentity_t *self ); +extern void SandCreature_ClearTimers( gentity_t *ent ); +void NPC_SetMiscDefaultData( gentity_t *ent ) +{ + if ( ent->spawnflags & SFB_CINEMATIC ) + {//if a cinematic guy, default us to wait bState + ent->NPC->behaviorState = BS_CINEMATIC; + } + if ( ent->client->NPC_class == CLASS_RANCOR ) + { + if ( Q_stricmp( "mutant_rancor", ent->NPC_type ) == 0 ) + { + ent->spawnflags |= 1;//just so I know it's a mutant rancor as opposed to a normal one + ent->NPC->aiFlags |= NPCAI_NAV_THROUGH_BREAKABLES; + ent->mass = 2000; + } + else + { + ent->NPC->aiFlags |= NPCAI_NAV_THROUGH_BREAKABLES; + ent->mass = 1000; + } + ent->flags |= FL_NO_KNOCKBACK; + } + else if ( ent->client->NPC_class == CLASS_SAND_CREATURE ) + {//??? + ent->clipmask = CONTENTS_SOLID|CONTENTS_MONSTERCLIP;//it can go through others + ent->contents = 0;//can't be hit? + ent->takedamage = qfalse;//can't be killed + ent->flags |= FL_NO_KNOCKBACK; + SandCreature_ClearTimers( ent ); + } + else if ( ent->client->NPC_class == CLASS_BOBAFETT ) + {//set some stuff, precache + ent->client->ps.forcePowersKnown |= ( 1 << FP_LEVITATION ); + ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3; + ent->client->ps.forcePower = 100; + ent->NPC->scriptFlags |= (SCF_NAV_CAN_FLY|SCF_FLY_WITH_JET|SCF_NAV_CAN_JUMP); + NPC->flags |= FL_UNDYING; // Can't Kill Boba + } + else if ( ent->client->NPC_class == CLASS_ROCKETTROOPER ) + {//set some stuff, precache + ent->client->ps.forcePowersKnown |= ( 1 << FP_LEVITATION ); + ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3; + ent->client->ps.forcePower = 100; + ent->NPC->scriptFlags |= (SCF_NAV_CAN_FLY|SCF_FLY_WITH_JET|SCF_NAV_CAN_JUMP);//no groups, no combat points! + if ( Q_stricmp( "rockettrooper2Officer", ent->NPC_type ) == 0 ) + {//start in the air, use spotlight + //ent->NPC->scriptFlags |= SCF_NO_GROUPS; + ent->NPC->scriptFlags &= ~SCF_FLY_WITH_JET; + RT_FlyStart( ent ); + NPC_SetMoveGoal( ent, ent->currentOrigin, 16, qfalse, -1, NULL ); + VectorCopy( ent->currentOrigin, ent->pos1 ); + } + if ( (ent->spawnflags&2) ) + {//spotlight + ent->client->ps.eFlags |= EF_SPOTLIGHT; + } + } + else if (ent->client->NPC_class == CLASS_SABER_DROID) + { + ent->flags |= FL_NO_KNOCKBACK; + } + else if ( ent->client->NPC_class == CLASS_SABOTEUR ) + {//can cloak + ent->NPC->aiFlags |= NPCAI_SHIELDS;//give them the ability to cloak + if ( (ent->spawnflags&16) ) + {//start cloaked + Saboteur_Cloak( ent ); + } + } + else if ( ent->client->NPC_class == CLASS_ASSASSIN_DROID ) + { + ent->client->ps.stats[STAT_ARMOR] = 250; // start with full armor + if (ent->s.weapon==WP_BLASTER) + { + ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + ent->flags |= (FL_NO_KNOCKBACK); + } + + if (ent->spawnflags&4096) + { + ent->NPC->scriptFlags |= SCF_NO_GROUPS;//don't use combat points or group AI + } + + if ( Q_stricmp( "DKothos", ent->NPC_type ) == 0 + || Q_stricmp( "VKothos", ent->NPC_type ) == 0 ) + { + ent->NPC->scriptFlags |= SCF_DONT_FIRE; + ent->NPC->aiFlags |= NPCAI_HEAL_ROSH; + ent->count = 100; + } + else if ( Q_stricmp( "rosh_dark", ent->NPC_type ) == 0 ) + { + ent->NPC->aiFlags |= NPCAI_ROSH; + } + + if ( Q_stricmpn( ent->NPC_type, "hazardtrooper", 13 ) == 0 ) + {//hazard trooper + ent->NPC->scriptFlags |= SCF_NO_GROUPS;//don't use combat points or group AI + ent->flags |= (FL_SHIELDED|FL_NO_KNOCKBACK);//low-level shots bounce off, no knockback + } + if ( !Q_stricmp( "Yoda", ent->NPC_type ) ) + {//FIXME: extern this into NPC.cfg? + ent->NPC->scriptFlags |= SCF_NO_FORCE;//force powers don't work on him + ent->NPC->aiFlags |= NPCAI_BOSS_CHARACTER; + } + if ( !Q_stricmp( "emperor", ent->NPC_type ) + || !Q_stricmp( "cultist_grip", ent->NPC_type ) + || !Q_stricmp( "cultist_drain", ent->NPC_type ) + || !Q_stricmp( "cultist_lightning", ent->NPC_type )) + {//FIXME: extern this into NPC.cfg? + ent->NPC->scriptFlags |= SCF_DONT_FIRE;//so he uses only force powers + } + if (!Q_stricmp( "Rax", ent->NPC_type ) ) + { + ent->NPC->scriptFlags |= SCF_DONT_FLEE; + } + if ( !Q_stricmp( "cultist_destroyer", ent->NPC_type ) ) + { + ent->splashDamage = 1000; + ent->splashRadius = 384; + //FIXME: precache these! + ent->fxID = G_EffectIndex( "force/destruction_exp" ); + ent->NPC->scriptFlags |= (SCF_DONT_FLEE|SCF_IGNORE_ALERTS); + ent->NPC->ignorePain = qtrue; + } + if ( Q_stricmp( "chewie", ent->NPC_type ) ) + { + //in case chewie ever loses his gun... + ent->NPC->aiFlags |= NPCAI_HEAVY_MELEE; + } + //================== + if ( ent->client->ps.saber[0].type != SABER_NONE + && (!(ent->NPC->aiFlags&NPCAI_MATCHPLAYERWEAPON)||!ent->weaponModel[0]) ) + {//if I'm equipped with a saber, initialize it (them) + ent->client->ps.SaberDeactivate(); + ent->client->ps.SetSaberLength( 0 ); + WP_SaberInitBladeData( ent ); + if ( ent->client->ps.weapon == WP_SABER ) + {//this is our current weapon, add the models now + WP_SaberAddG2SaberModels( ent ); + } + Jedi_ClearTimers( ent ); + } + if ( ent->client->ps.forcePowersKnown != 0 ) + { + WP_InitForcePowers( ent ); + if (ent->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) + { + ent->NPC->scriptFlags |= SCF_NAV_CAN_JUMP; // anyone who has any force jump can jump + } + } + if ( ent->client->NPC_class == CLASS_HOWLER ) + { + Howler_ClearTimers( ent ); + ent->NPC->scriptFlags |= SCF_NO_FALLTODEATH; + ent->flags |= FL_NO_IMPACT_DMG; + ent->NPC->scriptFlags |= SCF_NAV_CAN_JUMP; // These jokers can jump + } + if ( ent->client->NPC_class == CLASS_DESANN + || ent->client->NPC_class == CLASS_TAVION + || ent->client->NPC_class == CLASS_LUKE + || ent->client->NPC_class == CLASS_KYLE + || Q_stricmp("tavion_scepter", ent->NPC_type ) == 0 + || Q_stricmp("alora_dual", ent->NPC_type ) == 0 ) + { + ent->NPC->aiFlags |= NPCAI_BOSS_CHARACTER; + } + else if ( Q_stricmp( "alora", ent->NPC_type ) == 0 + || Q_stricmp( "rosh_dark", ent->NPC_type ) == 0 ) + { + ent->NPC->aiFlags |= NPCAI_SUBBOSS_CHARACTER; + } + if ( ent->client->NPC_class == CLASS_TUSKEN ) + { + if ( g_spskill->integer > 1 ) + {//on hard, tusken raiders are faster than you + ent->NPC->stats.runSpeed = 280; + ent->NPC->stats.walkSpeed = 65; + } + } + //***I'm not sure whether I should leave this as a TEAM_ switch, I think NPC_class may be more appropriate - dmv + switch(ent->client->playerTeam) + { + case TEAM_PLAYER: + //ent->flags |= FL_NO_KNOCKBACK; + if ( ent->client->NPC_class == CLASS_SEEKER ) + { + ent->NPC->defaultBehavior = BS_DEFAULT; + ent->client->ps.gravity = 0; + ent->svFlags |= SVF_CUSTOM_GRAVITY; + ent->client->moveType = MT_FLYSWIM; + ent->count = 30; // SEEKER shot ammo count + return; + } + else if ( ent->client->NPC_class == CLASS_JEDI + || ent->client->NPC_class == CLASS_KYLE + || ent->client->NPC_class == CLASS_LUKE ) + {//good jedi + ent->client->enemyTeam = TEAM_ENEMY; + if ( ent->spawnflags & JSF_AMBUSH ) + {//ambusher + ent->NPC->scriptFlags |= SCF_IGNORE_ALERTS; + ent->client->noclip = true;//hang + } + } + else + { + if (ent->client->ps.weapon != WP_NONE + && ent->client->ps.weapon != WP_SABER //sabers done above + && (!(ent->NPC->aiFlags&NPCAI_MATCHPLAYERWEAPON)||!ent->weaponModel[0]) )//they do this themselves + { + G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 ); + } + switch ( ent->client->ps.weapon ) + { + case WP_BRYAR_PISTOL://FIXME: new weapon: imp blaster pistol + case WP_BLASTER_PISTOL: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_REPEATER: + case WP_DEMP2: + case WP_FLECHETTE: + case WP_ROCKET_LAUNCHER: + case WP_CONCUSSION: + default: + break; + case WP_THERMAL: + case WP_BLASTER: + //FIXME: health in NPCs.cfg, and not all blaster users are stormtroopers + //ent->health = 25; + //FIXME: not necc. a ST + ST_ClearTimers( ent ); + if ( ent->NPC->rank >= RANK_LT || ent->client->ps.weapon == WP_THERMAL ) + {//officers, grenade-throwers use alt-fire + //ent->health = 50; + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + break; + } + } + if ( ent->client->NPC_class == CLASS_PLAYER + || ent->client->NPC_class == CLASS_VEHICLE + || (ent->spawnflags & SFB_CINEMATIC) ) + { + ent->NPC->defaultBehavior = BS_CINEMATIC; + } + else + { + ent->NPC->defaultBehavior = BS_FOLLOW_LEADER; + ent->client->leader = &g_entities[0];//player + } + break; + + case TEAM_NEUTRAL: + + if ( Q_stricmp( ent->NPC_type, "gonk" ) == 0 ) + { + // I guess we generically make them player usable + ent->svFlags |= SVF_PLAYER_USABLE; + + // Not even sure if we want to give different levels of batteries? ...Or even that these are the values we'd want to use. + switch ( g_spskill->integer ) + { + case 0: // EASY + ent->client->ps.batteryCharge = MAX_BATTERIES * 0.8f; + break; + case 1: // MEDIUM + ent->client->ps.batteryCharge = MAX_BATTERIES * 0.75f; + break; + default : + case 2: // HARD + ent->client->ps.batteryCharge = MAX_BATTERIES * 0.5f; + break; + } + } + break; + + case TEAM_ENEMY: + { + ent->NPC->defaultBehavior = BS_DEFAULT; + if ( ent->client->NPC_class == CLASS_SHADOWTROOPER + && Q_stricmpn("shadowtrooper", ent->NPC_type, 13 ) == 0 ) + {//FIXME: a spawnflag? + Jedi_Cloak( ent ); + } + if( ent->client->NPC_class == CLASS_TAVION || + ent->client->NPC_class == CLASS_ALORA || + (ent->client->NPC_class == CLASS_REBORN && ent->client->ps.weapon == WP_SABER) || + ent->client->NPC_class == CLASS_DESANN || + ent->client->NPC_class == CLASS_SHADOWTROOPER ) + { + ent->client->enemyTeam = TEAM_PLAYER; + if ( ent->spawnflags & JSF_AMBUSH ) + {//ambusher + ent->NPC->scriptFlags |= SCF_IGNORE_ALERTS; + ent->client->noclip = true;//hang + } + } + else if( ent->client->NPC_class == CLASS_PROBE || ent->client->NPC_class == CLASS_REMOTE || + ent->client->NPC_class == CLASS_INTERROGATOR || ent->client->NPC_class == CLASS_SENTRY) + { + ent->NPC->defaultBehavior = BS_DEFAULT; + ent->client->ps.gravity = 0; + ent->svFlags |= SVF_CUSTOM_GRAVITY; + ent->client->moveType = MT_FLYSWIM; + } + else + { + if ( ent->client->ps.weapon != WP_NONE + && ent->client->ps.weapon != WP_SABER//sabers done above + && (!(ent->NPC->aiFlags&NPCAI_MATCHPLAYERWEAPON)||!ent->weaponModel[0]) )//they do this themselves + { + G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 ); + } + switch ( ent->client->ps.weapon ) + { + case WP_BRYAR_PISTOL: + break; + case WP_BLASTER_PISTOL: + NPCInfo->scriptFlags |= SCF_PILOT; + if ( ent->client->NPC_class == CLASS_REBORN + && ent->NPC->rank >= RANK_LT_COMM + && (!(ent->NPC->aiFlags&NPCAI_MATCHPLAYERWEAPON)||!ent->weaponModel[0]) )//they do this themselves + {//dual blaster pistols, so add the left-hand one, too + G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handLBolt, 1 ); + } + break; + case WP_DISRUPTOR: + //Sniper + //ent->NPC->scriptFlags |= SCF_ALT_FIRE;//FIXME: use primary fire sometimes? Up close? Different class of NPC? + break; + case WP_BOWCASTER: + NPCInfo->scriptFlags |= SCF_PILOT; + break; + case WP_REPEATER: + NPCInfo->scriptFlags |= SCF_PILOT; + //machine-gunner + break; + case WP_DEMP2: + break; + case WP_FLECHETTE: + NPCInfo->scriptFlags |= SCF_PILOT; + //shotgunner + if ( !Q_stricmp( "stofficeralt", ent->NPC_type ) ) + { + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + break; + case WP_ROCKET_LAUNCHER: + break; + case WP_CONCUSSION: + break; + case WP_THERMAL: + //Gran, use main, bouncy fire +// ent->NPC->scriptFlags |= SCF_ALT_FIRE; + break; + case WP_MELEE: + break; + case WP_NOGHRI_STICK: + break; + default: + case WP_BLASTER: + //FIXME: health in NPCs.cfg, and not all blaster users are stormtroopers + //FIXME: not necc. a ST + NPCInfo->scriptFlags |= SCF_PILOT; + + ST_ClearTimers( ent ); + if ( ent->NPC->rank >= RANK_COMMANDER ) + {//commanders use alt-fire + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + if ( !Q_stricmp( "rodian2", ent->NPC_type ) ) + { + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + break; + } + } + } + break; + + default: + ent->NPC->defaultBehavior = BS_DEFAULT; + if ( ent->client->ps.weapon != WP_NONE + && ent->client->ps.weapon != WP_MELEE + && ent->client->ps.weapon != WP_SABER//sabers done above + && (!(ent->NPC->aiFlags&NPCAI_MATCHPLAYERWEAPON)||!ent->weaponModel[0]) )//they do this themselves + { + G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 ); + } + break; + } + + + if ( ent->client->NPC_class == CLASS_ATST || ent->client->NPC_class == CLASS_MARK1 ) // chris/steve/kevin requested that the mark1 be shielded also + { + ent->flags |= (FL_SHIELDED|FL_NO_KNOCKBACK); + } + + // Set CAN FLY Flag for Navigation On The Following Classes + //---------------------------------------------------------- + if (ent->client->NPC_class==CLASS_PROBE || + ent->client->NPC_class==CLASS_REMOTE || + ent->client->NPC_class==CLASS_SEEKER || + ent->client->NPC_class==CLASS_SENTRY || + ent->client->NPC_class==CLASS_GLIDER || + ent->client->NPC_class==CLASS_IMPWORKER || + ent->client->NPC_class==CLASS_BOBAFETT || + ent->client->NPC_class==CLASS_ROCKETTROOPER + ) + { + ent->NPC->scriptFlags |= SCF_NAV_CAN_FLY; + } + + if (ent->client->NPC_class==CLASS_VEHICLE) + { + Vehicle_Register(ent); + } + + if ( ent->client->ps.stats[STAT_WEAPONS]&(1<weaponModel[1] ) + {//we have the scepter, so put it in our left hand if we don't already have a second weapon + G_CreateG2AttachedWeaponModel( ent, weaponData[WP_SCEPTER].weaponMdl, ent->handLBolt, 1 ); + } + ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->weaponModel[1]], "*flash"); + } + + if ( ent->client->ps.saber[0].type == SABER_SITH_SWORD ) + { + ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->weaponModel[0]], "*flash"); + G_PlayEffect( G_EffectIndex( "scepter/sword.efx" ), ent->weaponModel[0], ent->genericBolt1, ent->s.number, ent->currentOrigin, qtrue, qtrue ); + //how many times can she recharge? + ent->count = g_spskill->integer*2; + //To make sure she can do it at least once + ent->flags |= FL_UNDYING; + } + + if ( ent->client->ps.weapon == WP_NOGHRI_STICK + && ent->weaponModel[0] ) + { + ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->weaponModel[0]], "*flash"); + } + + G_ClassSetDontFlee( ent ); +} + +/* +------------------------- +NPC_WeaponsForTeam +------------------------- +*/ + +int NPC_WeaponsForTeam( team_t team, int spawnflags, const char *NPC_type ) +{ + //*** not sure how to handle this, should I pass in class instead of team and go from there? - dmv + switch(team) + { + // no longer exists +// case TEAM_BORG: +// break; + +// case TEAM_HIROGEN: +// if( Q_stricmp( "hirogenalpha", NPC_type ) == 0 ) +// return ( 1 << WP_BLASTER); + //Falls through + +// case TEAM_KLINGON: + + //NOTENOTE: Falls through + +// case TEAM_IMPERIAL: + case TEAM_ENEMY: + if ( Q_stricmp( "tavion", NPC_type ) == 0 || + Q_stricmpn( "reborn", NPC_type, 6 ) == 0 || + Q_stricmp( "desann", NPC_type ) == 0 || + Q_stricmpn( "shadowtrooper", NPC_type, 13 ) == 0 ) + return ( 1 << WP_SABER); +// return ( 1 << WP_IMPERIAL_BLADE); + //NOTENOTE: Falls through if not a knife user + +// case TEAM_SCAVENGERS: +// case TEAM_MALON: + //FIXME: default weapon in npc config? + if ( Q_stricmpn( "stofficer", NPC_type, 9 ) == 0 ) + { + return ( 1 << WP_FLECHETTE); + } + if ( Q_stricmp( "stcommander", NPC_type ) == 0 ) + { + return ( 1 << WP_REPEATER); + } + if ( Q_stricmp( "swamptrooper", NPC_type ) == 0 ) + { + return ( 1 << WP_FLECHETTE); + } + if ( Q_stricmp( "swamptrooper2", NPC_type ) == 0 ) + { + return ( 1 << WP_REPEATER); + } + if ( Q_stricmp( "rockettrooper", NPC_type ) == 0 ) + { + return ( 1 << WP_ROCKET_LAUNCHER); + } + if ( Q_stricmpn( "shadowtrooper", NPC_type, 13 ) == 0 ) + { + return ( 1 << WP_SABER);//|( 1 << WP_RAPID_CONCUSSION)? + } + if ( Q_stricmp( "imperial", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER_PISTOL); + } + if ( Q_stricmpn( "impworker", NPC_type, 9 ) == 0 ) + { + return ( 1 << WP_BLASTER_PISTOL); + } + if ( Q_stricmp( "stormpilot", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER_PISTOL); + } + if ( Q_stricmp( "galak", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "galak_mech", NPC_type ) == 0 ) + { + return ( 1 << WP_REPEATER); + } + if ( Q_stricmpn( "ugnaught", NPC_type, 8 ) == 0 ) + { + return WP_NONE; + } + if ( Q_stricmp( "granshooter", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "granboxer", NPC_type ) == 0 ) + { + return ( 1 << WP_MELEE); + } + if ( Q_stricmpn( "gran", NPC_type, 4 ) == 0 ) + { + return (( 1 << WP_THERMAL)|( 1 << WP_MELEE)); + } + if ( Q_stricmp( "rodian", NPC_type ) == 0 ) + { + return ( 1 << WP_DISRUPTOR); + } + if ( Q_stricmp( "rodian2", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + + if (( Q_stricmp( "interrogator",NPC_type) == 0) || ( Q_stricmp( "sentry",NPC_type) == 0) || (Q_stricmpn( "protocol",NPC_type,8) == 0) ) + { + return WP_NONE; + } + + if ( Q_stricmpn( "weequay", NPC_type, 7 ) == 0 ) + { + return ( 1 << WP_BOWCASTER);//|( 1 << WP_STAFF )(FIXME: new weap?) + } + if ( Q_stricmp( "impofficer", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "impcommander", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if (( Q_stricmp( "probe", NPC_type ) == 0 ) || ( Q_stricmp( "seeker", NPC_type ) == 0 )) + { + return ( 1 << WP_BOT_LASER); + } + if ( Q_stricmpn( "remote", NPC_type, 6 ) == 0 ) + { + return ( 1 << WP_BOT_LASER ); + } + if ( Q_stricmp( "trandoshan", NPC_type ) == 0 ) + { + return (1<client->playerTeam, ent->spawnflags, ent->NPC_type ); + + ent->client->ps.stats[STAT_WEAPONS] = 0; + for ( int curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ ) + { + if ( (weapons & ( 1 << curWeap )) ) + { + ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << curWeap ); + RegisterItem( FindItemForWeapon( (weapon_t)(curWeap) ) ); //precache the weapon + ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[curWeap].ammoIndex] = 100;//FIXME: max ammo + + if ( bestWeap == WP_SABER ) + { + // still want to register other weapons -- force saber to be best weap + continue; + } + + if ( curWeap == WP_MELEE ) + { + if ( bestWeap == WP_NONE ) + {// We'll only consider giving Melee since we haven't found anything better yet. + bestWeap = curWeap; + } + } + else if ( curWeap > bestWeap || bestWeap == WP_MELEE ) + { + // This will never override saber as best weap. Also will override WP_MELEE if something better comes later in the list + bestWeap = curWeap; + } + } + } + + ent->client->ps.weapon = bestWeap; +} + +/* +------------------------- +NPC_SpawnEffect + + NOTE: Make sure any effects called here have their models, tga's and sounds precached in + CG_RegisterNPCEffects in cg_player.cpp +------------------------- +*/ + +static void NPC_SpawnEffect (gentity_t *ent) +{ +} + +//-------------------------------------------------------------- +// NPC_SetFX_SpawnStates +// +// Set up any special parms for spawn effects +//-------------------------------------------------------------- +void NPC_SetFX_SpawnStates( gentity_t *ent ) +{ + ent->client->ps.gravity = g_gravity->value; +} + +//-------------------------------------------------------------- +extern qboolean stop_icarus; +void NPC_Begin (gentity_t *ent) +{ + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + usercmd_t ucmd; + gentity_t *spawnPoint = NULL; + + memset( &ucmd, 0, sizeof( ucmd ) ); + + if ( !(ent->spawnflags & SFB_NOTSOLID) ) + {//No NPCs should telefrag + if ( Q_stricmp( ent->NPC_type, "nullDriver") == 0 ) + {//FIXME: should be a better way to check this + } + else if( SpotWouldTelefrag( ent, TEAM_FREE ) )//(team_t)(ent->client->playerTeam) + { + if ( ent->wait < 0 ) + {//remove yourself + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "NPC %s could not spawn, firing target3 (%s) and removing self\n", ent->targetname, ent->target3 ); + //Fire off our target3 + G_UseTargets2( ent, ent, ent->target3 ); + + //Kill us + ent->e_ThinkFunc = thinkF_G_FreeEntity; + ent->nextthink = level.time + 100; + } + else + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "NPC %s at (%5.0f %5.0f %5.0f) couldn't spawn, waiting %4.2f secs to try again\n", + ent->targetname, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], ent->wait/1000.0f ); + ent->e_ThinkFunc = thinkF_NPC_Begin; + ent->nextthink = level.time + ent->wait;//try again in half a second + } + return; + } + } + //Spawn effect + NPC_SpawnEffect( ent ); + + VectorCopy( ent->client->ps.origin, spawn_origin); + VectorCopy( ent->s.angles, spawn_angles); + spawn_angles[YAW] = ent->NPC->desiredYaw; + + client = ent->client; + + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + + client->airOutTime = level.time + 12000; + + client->ps.clientNum = ent->s.number; + // clear entity values + + if ( ent->health ) // Was health supplied in map + { + ent->max_health = client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = ent->health; + } + else if ( ent->NPC->stats.health ) // Was health supplied in NPC.cfg? + { + + if ( ent->client->NPC_class != CLASS_REBORN + && ent->client->NPC_class != CLASS_SHADOWTROOPER + //&& ent->client->NPC_class != CLASS_TAVION + //&& ent->client->NPC_class != CLASS_DESANN + && ent->client->NPC_class != CLASS_JEDI ) + {// up everyone except jedi + if ( !Q_stricmp("tavion_sith_sword", ent->NPC_type ) + || !Q_stricmp("tavion_scepter", ent->NPC_type ) + || !Q_stricmp("kyle_boss", ent->NPC_type ) + || !Q_stricmp("alora_dual", ent->NPC_type ) + || !Q_stricmp("alora_boss", ent->NPC_type ) ) + {//bosses are a bit different + ent->NPC->stats.health = ceil((float)ent->NPC->stats.health*0.75f + ((float)ent->NPC->stats.health/4.0f*g_spskill->value)); // 75% on easy, 100% on medium, 125% on hard + } + else + { + ent->NPC->stats.health += ent->NPC->stats.health/4 * g_spskill->integer; // 100% on easy, 125% on medium, 150% on hard + } + } + + ent->max_health = client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = ent->NPC->stats.health; + } + else + { + ent->max_health = client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = 100; + } + + if ( !Q_stricmp( "rodian", ent->NPC_type ) ) + {//sniper + //NOTE: this will get overridden by any aim settings in their spawnscripts + switch ( g_spskill->integer ) + { + case 0: + ent->NPC->stats.aim = 1; + break; + case 1: + ent->NPC->stats.aim = Q_irand( 2, 3 ); + break; + case 2: + ent->NPC->stats.aim = Q_irand( 3, 4 ); + break; + } + } + else if ( ent->client->NPC_class == CLASS_STORMTROOPER + || ent->client->NPC_class == CLASS_SWAMPTROOPER + || ent->client->NPC_class == CLASS_IMPWORKER + || !Q_stricmp( "rodian2", ent->NPC_type ) ) + {//tweak yawspeed for these NPCs based on difficulty + switch ( g_spskill->integer ) + { + case 0: + ent->NPC->stats.yawSpeed *= 0.75f; + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + { + ent->NPC->stats.aim -= Q_irand( 3, 6 ); + } + break; + case 1: + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + { + ent->NPC->stats.aim -= Q_irand( 2, 4 ); + } + break; + case 2: + ent->NPC->stats.yawSpeed *= 1.5f; + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + { + ent->NPC->stats.aim -= Q_irand( 0, 2 ); + } + break; + } + } + else if ( ent->client->NPC_class == CLASS_REBORN + || ent->client->NPC_class == CLASS_SHADOWTROOPER ) + { + switch ( g_spskill->integer ) + { + case 1: + ent->NPC->stats.yawSpeed *= 1.25f; + break; + case 2: + ent->NPC->stats.yawSpeed *= 1.5f; + break; + } + } + + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->mass = 10; + ent->takedamage = qtrue; + + /*ent->inuse = qtrue; + SetInUse(ent); + ent->m_iIcarusID = -1; // ICARUS_INVALID + */ + assert(ent->inuse); + assert(ent->m_iIcarusID==IIcarusInterface::ICARUS_INVALID); + + // CRITICAL NOTE! This was already done somewhere else and it was overwriting the previous value!!! + if ( !ent->classname || Q_stricmp( ent->classname, "noclass" ) == 0 ) + ent->classname = "NPC"; + +// if ( ent->client->race == RACE_HOLOGRAM ) +// {//can shoot through holograms, but not walk through them +// ent->contents = CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_ITEM;//contents_corspe to make them show up in ID and use traces +// ent->clipmask = MASK_NPCSOLID; +// } else + if(!(ent->spawnflags & SFB_NOTSOLID)) + { + ent->contents = CONTENTS_BODY; + ent->clipmask = MASK_NPCSOLID; + } + else + { + ent->contents = 0; + ent->clipmask = MASK_NPCSOLID&~CONTENTS_BODY; + } + if(!ent->client->moveType)//Static? + { + ent->client->moveType = MT_RUNJUMP; + } + ent->e_DieFunc = dieF_player_die; + ent->waterlevel = 0; + ent->watertype = 0; + + //visible to player and NPCs + if ( ent->client->NPC_class != CLASS_R2D2 && + ent->client->NPC_class != CLASS_R5D2 && + ent->client->NPC_class != CLASS_MOUSE && + ent->client->NPC_class != CLASS_GONK && + ent->client->NPC_class != CLASS_PROTOCOL ) + { + ent->flags &= ~FL_NOTARGET; + } + ent->s.eFlags &= ~EF_NODRAW; + + NPC_SetFX_SpawnStates( ent ); + + client->ps.friction = 6; + + if ( ent->client->ps.weapon == WP_NONE ) + {//not set by the NPCs.cfg + NPC_SetWeapons(ent); + } + //select the weapon + ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[ent->client->ps.weapon].ammoIndex]; + ent->client->ps.weaponstate = WEAPON_IDLE; + ChangeWeapon( ent, ent->client->ps.weapon ); + + VectorCopy( spawn_origin, client->ps.origin ); + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + // clear entity state values + ent->s.eType = ET_PLAYER; +// ent->s.skinNum = ent - g_entities - 1; // used as index to get custom models + + VectorCopy (spawn_origin, ent->s.origin); +// ent->s.origin[2] += 1; // make sure off ground + + SetClientViewAngle( ent, spawn_angles ); + client->renderInfo.lookTarget = ENTITYNUM_NONE; + //clear IK grabbing stuff + client->ps.heldClient = client->ps.heldByClient = ENTITYNUM_NONE; + + if(!(ent->spawnflags & 64)) + { + if ( Q_stricmp( ent->NPC_type, "nullDriver") == 0 ) + {//FIXME: should be a better way to check this + } + else + { + G_KillBox( ent ); + } + gi.linkentity (ent); + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + client->latched_buttons = 0; + + if ( ent->client->NPC_class != CLASS_VEHICLE ) + { + // set default animations + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_NORMAL ); + } + + if( spawnPoint ) + { + // fire the targets of the spawn point + G_UseTargets( spawnPoint, ent ); + } + + //ICARUS include + // NOTE! Does this need to be called here? It seems to be getting called twice... + // Once before the level starts, then once again when the entity is spawned! + Quake3Game()->InitEntity( ent ); + +//==NPC initialization + SetNPCGlobals( ent ); + + ent->enemy = NPCInfo->eventualGoal; + NPCInfo->timeOfDeath = 0; + NPCInfo->shotTime = 0; + NPC_ClearGoal(); + NPC_ChangeWeapon( ent->client->ps.weapon ); + +//==Final NPC initialization + ent->e_PainFunc = NPC_PainFunc( ent ); //painF_NPC_Pain; + ent->e_TouchFunc = NPC_TouchFunc( ent ); //touchF_NPC_Touch; +// ent->NPC->side = 1; + + ent->client->ps.ping = ent->NPC->stats.reactions * 50; + + //MCG - Begin: NPC hacks + //FIXME: Set the team correctly + ent->client->ps.persistant[PERS_TEAM] = ent->client->playerTeam; + + ent->e_UseFunc = useF_NPC_Use; + ent->e_ThinkFunc = thinkF_NPC_Think; + ent->nextthink = level.time + FRAMETIME + Q_irand(0, 100); + + NPC_SetMiscDefaultData( ent ); + if ( ent->health <= 0 ) + { + //ORIGINAL ID: health will count down towards max_health + ent->health = client->ps.stats[STAT_HEALTH] = ent->max_health; + } + else + { + client->ps.stats[STAT_HEALTH] = ent->max_health = ent->health; + } + ChangeWeapon( ent, ent->client->ps.weapon );//yes, again... sigh + + if ( !(ent->spawnflags & SFB_STARTINSOLID) ) + {//Not okay to start in solid + G_CheckInSolid( ent, qtrue ); + } + VectorClear( ent->NPC->lastClearOrigin ); + + //Run a script if you have one assigned to you + if ( G_ActivateBehavior( ent, BSET_SPAWN ) ) + { + if( ent->m_iIcarusID != IIcarusInterface::ICARUS_INVALID/*ent->taskManager*/ && !stop_icarus ) + { + IIcarusInterface::GetIcarus()->Update( ent->m_iIcarusID ); + } + } + + VectorCopy( ent->currentOrigin, ent->client->renderInfo.eyePoint ); + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + memset( &ucmd, 0, sizeof( ucmd ) ); + _VectorCopy( client->pers.cmd_angles, ucmd.angles ); + + ent->client->ps.groundEntityNum = ENTITYNUM_NONE; + + if ( ent->NPC->aiFlags & NPCAI_MATCHPLAYERWEAPON ) + { + G_MatchPlayerWeapon( ent ); + } + + ClientThink( ent->s.number, &ucmd ); + + gi.linkentity( ent ); + + if ( ent->client->playerTeam == TEAM_ENEMY || ent->client->playerTeam == TEAM_FREE ) + {//valid enemy spawned + if ( !(ent->spawnflags&SFB_CINEMATIC) && ent->NPC->behaviorState != BS_CINEMATIC ) + {//not a cinematic enemy + if ( g_entities[0].client ) + { + g_entities[0].client->sess.missionStats.enemiesSpawned++; + } + } + } +} + +/* +------------------------- +NPC_StasisSpawn_Go +------------------------- +*/ +/* +qboolean NPC_StasisSpawn_Go( gentity_t *ent ) +{ + //Setup an owner pointer if we need it + if VALIDSTRING( ent->ownername ) + { + ent->owner = G_Find( NULL, FOFS( targetname ), ent->ownername ); + + if ( ( ent->owner ) && ( ent->owner->health <= 0 ) ) + {//our spawner thing is broken + if ( ent->target2 && ent->target2[0] ) + { + //Fire off our target2 + G_UseTargets2( ent, ent, ent->target2 ); + + //Kill us + ent->e_ThinkFunc = thinkF_G_FreeEntity; + ent->nextthink = level.time + 100; + } + else + { + //Try to spawn again in one second + ent->e_ThinkFunc = thinkF_NPC_Spawn_Go; + ent->nextthink = level.time + 1000; + } + return qfalse; + } + } + + //Test for an entity blocking the spawn + trace_t tr; + gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, ent->currentOrigin, ent->s.number, MASK_NPCSOLID ); + + //Can't have anything in the way + if ( tr.allsolid || tr.startsolid ) + { + ent->nextthink = level.time + 1000; + return qfalse; + } + + return qtrue; +} +*/ +void NPC_DefaultScriptFlags( gentity_t *ent ) +{ + if ( !ent || !ent->NPC ) + { + return; + } + //Set up default script flags + ent->NPC->scriptFlags = (SCF_CHASE_ENEMIES|SCF_LOOK_FOR_ENEMIES); +} + +#define MAX_SAFESPAWN_ENTS 4 + +bool NPC_SafeSpawn( gentity_t *ent, float safeRadius ) +{ + gentity_t *radiusEnts[ MAX_SAFESPAWN_ENTS ]; + vec3_t safeMins, safeMaxs; + float distance = 999999; + int numEnts = 0; + float safeRadiusSquared = safeRadius*safeRadius; + + if (!ent) + { + return false; + } + + //Setup the bbox to search in + for ( int i = 0; i < 3; i++ ) + { + safeMins[i] = ent->currentOrigin[i] - safeRadius; + safeMaxs[i] = ent->currentOrigin[i] + safeRadius; + } + + //Get a number of entities in a given space + numEnts = gi.EntitiesInBox( safeMins, safeMaxs, radiusEnts, MAX_SAFESPAWN_ENTS ); + + for ( i = 0; i < numEnts; i++ ) + { + //Don't consider self + if ( radiusEnts[i] == ent ) + continue; + + if (radiusEnts[i]->NPC && (radiusEnts[i]->health == 0)) + { + // ignore dead guys + continue; + } + + distance = DistanceSquared( ent->currentOrigin, radiusEnts[i]->currentOrigin ); + + //Found one too close to us + if ( distance < safeRadiusSquared ) + { + return false; + } + } + return true; +} + + +/* +------------------------- +NPC_Spawn_Go +------------------------- +*/ + +gentity_t *NPC_Spawn_Do( gentity_t *ent, qboolean fullSpawnNow ) +{ + gentity_t *newent; + int index; + vec3_t saveOrg; + +/* //Do extra code for stasis spawners + if ( Q_stricmp( ent->classname, "NPC_Stasis" ) == 0 ) + { + if ( NPC_StasisSpawn_Go( ent ) == qfalse ) + return; + } +*/ + + // 4/18/03 kef -- don't let guys spawn into other guys + if (ent->spawnflags & 4096) + { + if (!NPC_SafeSpawn(ent, 64)) + { + return NULL; + } + } + + //Test for drop to floor + if ( ent->spawnflags & NSF_DROP_TO_FLOOR ) + { + trace_t tr; + vec3_t bottom; + + VectorCopy( ent->currentOrigin, saveOrg ); + VectorCopy( ent->currentOrigin, bottom ); + bottom[2] = MIN_WORLD_COORD; + gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, bottom, ent->s.number, MASK_NPCSOLID ); + if ( !tr.allsolid && !tr.startsolid && tr.fraction < 1.0 ) + { + G_SetOrigin( ent, tr.endpos ); + } + } + + //Check the spawner's count + if( ent->count != -1 ) + { + ent->count--; + + if( ent->count <= 0 ) + { + ent->e_UseFunc = useF_NULL;//never again + //will be removed below + } + } + + newent = G_Spawn(); + + if ( newent == NULL ) + { + gi.Printf ( S_COLOR_RED"ERROR: NPC G_Spawn failed\n" ); + + if ( ent->spawnflags & NSF_DROP_TO_FLOOR ) + { + G_SetOrigin( ent, saveOrg ); + } + return NULL; + } + + newent->client = (gclient_s *)gi.Malloc(sizeof(gclient_s), TAG_G_ALLOC, qtrue); + + newent->svFlags |= SVF_NPC; + + if ( ent->NPC_type == NULL ) + { + ent->NPC_type = "random"; + newent->NPC_type = "random"; + } + else + { + newent->NPC_type = strlwr( G_NewString( ent->NPC_type ) ); //get my own copy so i can free it when i die + } + + newent->NPC = (gNPC_t*) gi.Malloc(sizeof(gNPC_t), TAG_G_ALLOC, qtrue); + + newent->NPC->tempGoal = G_Spawn(); + + newent->NPC->tempGoal->classname = "NPC_goal"; + newent->NPC->tempGoal->owner = newent; + newent->NPC->tempGoal->svFlags |= SVF_NOCLIENT; + +//==NPC_Connect( newent, net_name );=================================== + + if ( ent->svFlags & SVF_NO_BASIC_SOUNDS ) + { + newent->svFlags |= SVF_NO_BASIC_SOUNDS; + } + if ( ent->svFlags & SVF_NO_COMBAT_SOUNDS ) + { + newent->svFlags |= SVF_NO_COMBAT_SOUNDS; + } + if ( ent->svFlags & SVF_NO_EXTRA_SOUNDS ) + { + newent->svFlags |= SVF_NO_EXTRA_SOUNDS; + } + + if ( ent->message ) + {//has a key + newent->message = G_NewString(ent->message);//copy the key name + newent->flags |= FL_NO_KNOCKBACK;//don't fall off ledges + } + + // If this is a vehicle we need to see what kind it is so we properlly allocate it. + if ( Q_stricmp( ent->classname, "NPC_Vehicle" ) == 0 ) + { + // Get the vehicle entry index. + int iVehIndex = BG_VehicleGetIndex( newent->NPC_type ); + + if ( iVehIndex == -1 ) + { + Com_Printf( S_COLOR_RED"ERROR: Attempting to Spawn an unrecognized Vehicle! - %s\n", newent->NPC_type ); + G_FreeEntity( newent ); + if ( ent->spawnflags & NSF_DROP_TO_FLOOR ) + { + G_SetOrigin( ent, saveOrg ); + } + return NULL; + } + newent->soundSet = G_NewString(ent->soundSet);//get my own copy so i can free it when i die + + // NOTE: If you change/add any of these, update NPC_Spawn_f for the new vehicle you + // want to be able to spawn in manually. + + // See what kind of vehicle this is and allocate it properly. + switch( g_vehicleInfo[iVehIndex].type ) + { + case VH_ANIMAL: + // Create the animal (making sure all it's data is initialized). + G_CreateAnimalNPC( &newent->m_pVehicle, newent->NPC_type ); + break; + + case VH_SPEEDER: + // Create the speeder (making sure all it's data is initialized). + G_CreateSpeederNPC( &newent->m_pVehicle, newent->NPC_type ); + break; + + case VH_FIGHTER: + // Create the fighter (making sure all it's data is initialized). + G_CreateFighterNPC( &newent->m_pVehicle, newent->NPC_type ); + break; + case VH_WALKER: + // Create the animal (making sure all it's data is initialized). + G_CreateWalkerNPC( &newent->m_pVehicle, newent->NPC_type ); + break; + + default: + Com_Printf( S_COLOR_RED"ERROR: Attempting to Spawn an unrecognized Vehicle Type! - %s\n", newent->NPC_type ); + G_FreeEntity( newent ); + if ( ent->spawnflags & NSF_DROP_TO_FLOOR ) + { + G_SetOrigin( ent, saveOrg ); + } + return NULL; + } + + //grab this from the spawner + if ( (ent->spawnflags&1) ) + {//wants to explode when not in vis of player + newent->endFrame = ent->endFrame; + } + + // Setup the vehicle. + newent->m_pVehicle->m_pParentEntity = newent; + newent->m_pVehicle->m_pVehicleInfo->Initialize( newent->m_pVehicle ); + newent->client->NPC_class = CLASS_VEHICLE; + + } + else + { + newent->client->ps.weapon = WP_NONE;//init for later check in NPC_Begin + } + + newent->classname = "NPC"; + VectorCopy(ent->s.origin, newent->s.origin); + VectorCopy(ent->s.origin, newent->client->ps.origin); + VectorCopy(ent->s.origin, newent->currentOrigin); + G_SetOrigin(newent, ent->s.origin);//just to be sure! + //NOTE: on vehicles, anything in the .npc file will STOMP data on the NPC that's set by the vehicle + if ( !NPC_ParseParms( ent->NPC_type, newent ) ) + { + gi.Printf ( S_COLOR_RED "ERROR: Couldn't spawn NPC %s\n", ent->NPC_type ); + G_FreeEntity( newent ); + if ( ent->spawnflags & NSF_DROP_TO_FLOOR ) + { + G_SetOrigin( ent, saveOrg ); + } + return NULL; + } + + if ( ent->NPC_type ) + { + if ( !Q_stricmp( ent->NPC_type, "player" ) ) + {// Or check NPC_type against player's NPC_type? + newent->NPC->aiFlags |= NPCAI_MATCHPLAYERWEAPON; + } + else if ( !Q_stricmp( ent->NPC_type, "test" ) ) + { + int n; + for ( n = 0; n < 1 ; n++) + { + if ( !(g_entities[n].svFlags & SVF_NPC) && g_entities[n].client) + { + VectorCopy(g_entities[n].s.origin, newent->s.origin); + newent->client->playerTeam = g_entities[n].client->playerTeam; + break; + } + } + newent->NPC->defaultBehavior = newent->NPC->behaviorState = BS_WAIT; + // newent->svFlags |= SVF_NOPUSH; + } + } +//===================================================================== + //set the info we want + newent->health = ent->health; + newent->wait = ent->wait; + + //copy strings so we can safely free them + newent->script_targetname = G_NewString(ent->NPC_targetname); + newent->targetname = G_NewString(ent->NPC_targetname); + newent->target = G_NewString(ent->NPC_target);//death + newent->target2 = G_NewString(ent->target2);//knocked out death + newent->target3 = G_NewString(ent->target3);//??? + newent->target4 = G_NewString(ent->target4);//ffire death + newent->paintarget = G_NewString(ent->paintarget); + newent->opentarget = G_NewString(ent->opentarget); + newent->radius = ent->radius; + + newent->NPC->eventualGoal = ent->enemy; + + for( index = BSET_FIRST; index < NUM_BSETS; index++) + { + if ( ent->behaviorSet[index] ) + { + newent->behaviorSet[index] = ent->behaviorSet[index]; + } + } + + VectorCopy(ent->s.angles, newent->s.angles); + VectorCopy(ent->s.angles, newent->currentAngles); + VectorCopy(ent->s.angles, newent->client->ps.viewangles); + newent->NPC->desiredYaw =ent->s.angles[YAW]; + + //gi.linkentity(newent); //check: don't need this here? + newent->spawnflags = ent->spawnflags; + +//==New stuff===================================================================== + newent->s.eType = ET_PLAYER; + + //FIXME: Call CopyParms + if ( ent->parms ) + { + for ( int parmNum = 0; parmNum < MAX_PARMS; parmNum++ ) + { + if ( ent->parms->parm[parmNum] && ent->parms->parm[parmNum][0] ) + { + Q3_SetParm( newent->s.number, parmNum, ent->parms->parm[parmNum] ); + } + } + } + //FIXME: copy cameraGroup, store mine in message or other string field + + //set origin + newent->s.pos.trType = TR_INTERPOLATE; + newent->s.pos.trTime = level.time; + VectorCopy( newent->currentOrigin, newent->s.pos.trBase ); + VectorClear( newent->s.pos.trDelta ); + newent->s.pos.trDuration = 0; + //set angles + newent->s.apos.trType = TR_INTERPOLATE; + newent->s.apos.trTime = level.time; + VectorCopy( newent->currentOrigin, newent->s.apos.trBase ); + VectorClear( newent->s.apos.trDelta ); + newent->s.apos.trDuration = 0; + + newent->NPC->combatPoint = -1; + newent->NPC->aiFlags |= ent->bounceCount;//ai flags + + + newent->flags |= FL_NOTARGET;//So he's ignored until he's fully spawned + newent->s.eFlags |= EF_NODRAW;//So he's ignored until he's fully spawned + + if ( fullSpawnNow ) + { + newent->owner = ent->owner; + } + else + { + newent->e_ThinkFunc = thinkF_NPC_Begin; + newent->nextthink = level.time + FRAMETIME; + } + NPC_DefaultScriptFlags( newent ); + + gi.linkentity (newent); + + if(ent->e_UseFunc == useF_NULL) + { + if( ent->target ) + {//use any target we're pointed at + G_UseTargets ( ent, ent ); + } + if(ent->closetarget) + {//last guy should fire this target when he dies + if (newent->target) + {//zap + gi.Free(newent->target); + } + newent->target = G_NewString(ent->closetarget); + } + G_FreeEntity( ent );//bye! + } + else if ( ent->spawnflags & NSF_DROP_TO_FLOOR ) + { + G_SetOrigin( ent, saveOrg ); + } + if ( fullSpawnNow ) + { + NPC_Begin( newent ); + } + return newent; +} + +void NPC_Spawn_Go( gentity_t *ent ) +{ + NPC_Spawn_Do( ent, qfalse ); +} + +/* +------------------------- +NPC_StasisSpawnEffect +------------------------- +*/ +/* +void NPC_StasisSpawnEffect( gentity_t *ent ) +{ + vec3_t start, end, forward; + qboolean taper; + + //Floor or wall? + if ( ent->spawnflags & 1 ) + { + AngleVectors( ent->s.angles, forward, NULL, NULL ); + VectorMA( ent->currentOrigin, 24, forward, end ); + VectorMA( ent->currentOrigin, -20, forward, start ); + + start[2] += 64; + + taper = qtrue; + } + else + { + VectorCopy( ent->currentOrigin, start ); + VectorCopy( start, end ); + end[2] += 48; + taper = qfalse; + } + + //Add the effect +// CG_ShimmeryThing_Spawner( start, end, 32, qtrue, 1000 ); +} +*/ +/* +------------------------- +NPC_ShySpawn +------------------------- +*/ + +#define SHY_THINK_TIME 1000 +#define SHY_SPAWN_DISTANCE 128 +#define SHY_SPAWN_DISTANCE_SQR ( SHY_SPAWN_DISTANCE * SHY_SPAWN_DISTANCE ) + +void NPC_ShySpawn( gentity_t *ent ) +{ + ent->nextthink = level.time + SHY_THINK_TIME; + ent->e_ThinkFunc = thinkF_NPC_ShySpawn; + + if ( DistanceSquared( g_entities[0].currentOrigin, ent->currentOrigin ) <= SHY_SPAWN_DISTANCE_SQR ) + return; + + if ( (InFOV( ent, &g_entities[0], 80, 64 )) ) // FIXME: hardcoded fov + if ( (NPC_ClearLOS( &g_entities[0], ent->currentOrigin )) ) + return; + + // 4/18/03 kef -- don't let guys spawn into other guys + if (ent->spawnflags & 4096) + { + if (!NPC_SafeSpawn(ent, 64)) + { + return; + } + } + + ent->e_ThinkFunc = thinkF_NULL; + ent->nextthink = 0; + + NPC_Spawn_Go( ent ); +} + +/* +------------------------- +NPC_Spawn +------------------------- +*/ + +void NPC_Spawn ( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + //delay before spawning NPC + if (other->spawnflags&32) + { + ent->enemy = activator; + } + if( ent->delay ) + { +/* //Stasis does an extra step + if ( Q_stricmp( ent->classname, "NPC_Stasis" ) == 0 ) + { + if ( NPC_StasisSpawn_Go( ent ) == qfalse ) + return; + } +*/ + if ( ent->spawnflags & 2048 ) // SHY + ent->e_ThinkFunc = thinkF_NPC_ShySpawn; + else + ent->e_ThinkFunc = thinkF_NPC_Spawn_Go; + + ent->nextthink = level.time + ent->delay; + } + else + { + if ( ent->spawnflags & 2048 ) // SHY + NPC_ShySpawn( ent ); + else + NPC_Spawn_Go( ent ); + } +} + +/*QUAKED NPC_spawner (1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY SAFE + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +SAFE - Won't spawn if an entity is within 64 units + +NPC_type - name of NPC (in npcs.cfg) to spawn in + +targetname - name this NPC goes by for targetting +target - NPC will fire this when it spawns it's last NPC (should this be when the last NPC it spawned dies?) +target2 - Fired by stasis spawners when they try to spawn while their spawner model is broken +target3 - Fired by spawner if they try to spawn and are blocked and have a wait < 0 (removes them) + +If targeted, will only spawn a NPC when triggered +count - how many NPCs to spawn (only if targetted) default = 1 +delay - how long to wait to spawn after used +wait - if trying to spawn and blocked, how many seconds to wait before trying again (default = 0.5, < 0 = never try again and fire target2) + +NPC_targetname - NPC's targetname AND script_targetname +NPC_target - NPC's target to fire when killed +NPC_target2 - NPC's target to fire when knocked out +NPC_target4 - NPC's target to fire when killed by friendly fire +NPC_type - type of NPC ("Borg" (default), "Xian", etc) +health - starting health (default = 100) + +"noBasicSounds" - set to 1 to prevent loading and usage of basic sounds (pain, death, etc) +"noCombatSounds" - set to 1 to prevent loading and usage of combat sounds (anger, victory, etc.) +"noExtraSounds" - set to 1 to prevent loading and usage of "extra" sounds (chasing the enemy - detecting them, flanking them... also jedi combat sounds) + +spawnscript - default script to run once spawned (none by default) +usescript - default script to run when used (none by default) +awakescript - default script to run once awoken (none by default) +angerscript - default script to run once angered (none by default) +painscript - default script to run when hit (none by default) +fleescript - default script to run when hit and below 50% health (none by default) +deathscript - default script to run when killed (none by default) +These strings can be used to activate behaviors instead of scripts - these are checked +first and so no scripts should be names with these names: + default - 0: whatever + idle - 1: Stand around, do abolutely nothing + roam - 2: Roam around, collect stuff + walk - 3: Crouch-Walk toward their goals + run - 4: Run toward their goals + standshoot - 5: Stay in one spot and shoot- duck when neccesary + standguard - 6: Wait around for an enemy + patrol - 7: Follow a path, looking for enemies + huntkill - 8: Track down enemies and kill them + evade - 9: Run from enemies + evadeshoot - 10: Run from enemies, shoot them if they hit you + runshoot - 11: Run to your goal and shoot enemy when possible + defend - 12: Defend an entity or spot? + snipe - 13: Stay hidden, shoot enemy only when have perfect shot and back turned + combat - 14: Attack, evade, use cover, move about, etc. Full combat AI - id NPC code + medic - 15: Go for lowest health buddy, hide and heal him. + takecover - 16: Find nearest cover from enemies + getammo - 17: Go get some ammo + advancefight - 18: Go somewhere and fight along the way + face - 19: turn until facing desired angles + wait - 20: do nothing + formation - 21: Maintain a formation + crouch - 22: Crouch-walk toward their goals + +delay - after spawned or triggered, how many seconds to wait to spawn the NPC +*/ +//void NPC_PrecacheModels ( char *NPCName ); +extern qboolean spawning; // the G_Spawn*() functions are valid (only turned on during one function) +extern void NPC_PrecacheByClassName(const char*); + +void SP_NPC_spawner( gentity_t *self) +{ + extern void NPC_PrecacheAnimationCFG( const char *NPC_type ); + float fDelay; + + //register/precache the models needed for this NPC, not anymore + //self->classname = "NPC_spawner"; + + if(!self->count) + { + self->count = 1; + } + + //NOTE: bounceCount is transferred to the spawned NPC's NPC->aiFlags + self->bounceCount = 0; + + { + static int garbage; + //Stop loading of certain extra sounds + if ( G_SpawnInt( "noBasicSounds", "0", &garbage ) ) + { + self->svFlags |= SVF_NO_BASIC_SOUNDS; + } + if ( G_SpawnInt( "noCombatSounds", "0", &garbage ) ) + { + self->svFlags |= SVF_NO_COMBAT_SOUNDS; + } + if ( G_SpawnInt( "noExtraSounds", "0", &garbage ) ) + { + self->svFlags |= SVF_NO_EXTRA_SOUNDS; + } + if ( G_SpawnInt( "nodelay", "0", &garbage ) ) + { + self->bounceCount |= NPCAI_NO_JEDI_DELAY; + } + } + + + if ( !self->wait ) + { + self->wait = 500; + } + else + { + self->wait *= 1000;//1 = 1 msec, 1000 = 1 sec + } + + G_SpawnFloat( "delay", "0", &fDelay ); + if ( fDelay ) + { + self->delay = ceil(1000.0f*fDelay);//1 = 1 msec, 1000 = 1 sec + } + + if ( self->delay > 0 ) + { + self->svFlags |= SVF_NPC_PRECACHE; + } + + //We have to load the animation.cfg now because spawnscripts are going to want to set anims and we need to know their length and if they're valid + NPC_PrecacheAnimationCFG( self->NPC_type ); + + if ( self->targetname ) + {//Wait for triggering + self->e_UseFunc = useF_NPC_Spawn; + self->svFlags |= SVF_NPC_PRECACHE;//FIXME: precache my weapons somehow? + //NPC_PrecacheModels( self->NPC_type ); + } + else + { + //NOTE: auto-spawners never check for shy spawning + if ( spawning ) + {//in entity spawn stage - map starting up + self->e_ThinkFunc = thinkF_NPC_Spawn_Go; + self->nextthink = level.time + START_TIME_REMOVE_ENTS + 50; + } + else + {//else spawn right now + NPC_Spawn( self, self, self ); + } + } + + if (!(self->svFlags&SVF_NPC_PRECACHE)) + { + NPC_PrecacheByClassName(self->NPC_type); + } + + //FIXME: store cameraGroup somewhere else and apply to spawned NPCs' cameraGroup + //Or just don't include NPC_spawners in cameraGroupings + + if ( self->message ) + {//may drop a key, precache the key model and pickup sound + G_SoundIndex( "sound/weapons/key_pkup.wav" ); + if ( !Q_stricmp( "goodie", self->message ) ) + { + RegisterItem( FindItemForInventory( INV_GOODIE_KEY ) ); + } + else + { + RegisterItem( FindItemForInventory( INV_SECURITY_KEY ) ); + } + } +} + + +//============================================================================================= +//VEHICLES +//============================================================================================= +/*QUAKED NPC_Vehicle (1 0 0) (-16 -16 -24) (16 16 32) NO_VIS_DIE x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY SAFE +set NPC_type to vehicle name in vehicles.dat + +NO_VIS_DIE - die after certain amount of time of not having a LOS to the Player (default time is 10 seconds, change by setting "noVisTime") +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +SAFE - Won't spawn if an entity is within 64 units + +"noVisTime" - how long to wait after spawning/last being seen by the player beforw blowing ourselves up (default is 10 seconds) +"skin" - which skin to set "red" for example - If no skin it is random +*/ +void NPC_VehicleSpawnUse( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + G_VehicleSpawn( self ); +} + +void SP_NPC_Vehicle( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "swoop"; + } + + if ( !self->classname ) + { + self->classname = "NPC_Vehicle"; + } + + G_SetOrigin( self, self->s.origin ); + G_SetAngles( self, self->s.angles ); + G_SpawnString("skin", "", &self->soundSet); + + //grab this from the spawner + if ( (self->spawnflags&1) ) + {//wants to explode when not in vis of player + if ( !self->endFrame ) + { + self->endFrame = NO_PILOT_DIE_TIME; + } + } + + if ( self->targetname ) + { + self->svFlags |= SVF_NPC_PRECACHE; // Precache The Bike when all other npcs are precached + self->e_UseFunc = useF_NPC_VehicleSpawnUse; + //we're not spawning until later, so precache us now + BG_VehicleGetIndex( self->NPC_type ); + } + else + { + G_VehicleSpawn( self ); + } +} + +//============================================================================================= +//CHARACTERS +//============================================================================================= + +/*QUAKED NPC_Player (1 0 0) (-16 -16 -24) (16 16 32) x RIFLEMAN PHASER TRICORDER DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Player( gentity_t *self) +{ + self->NPC_type = "Player"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Kyle (1 0 0) (-16 -16 -24) (16 16 32) BOSS x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +BOSS - Uses Boss AI +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Kyle( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "Kyle_boss"; + } + else + { + self->NPC_type = "Kyle"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Lando(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Lando( gentity_t *self) +{ + self->NPC_type = "Lando"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Jan(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Jan( gentity_t *self) +{ + self->NPC_type = "Jan"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Luke(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Luke( gentity_t *self) +{ + self->NPC_type = "Luke"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_MonMothma(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_MonMothma( gentity_t *self) +{ + self->NPC_type = "MonMothma"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Rosh_Penin (1 0 0) (-16 -16 -24) (16 16 32) DARKSIDE NOFORCE x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +Good Rosh +DARKSIDE - Evil Rosh +NOFORCE - Can't jump, starts with no saber +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Rosh_Penin( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "rosh_dark"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "rosh_penin_noforce"; + } + else + { + self->NPC_type = "rosh_penin"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Tavion (1 0 0) (-16 -16 -24) (16 16 32) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tavion( gentity_t *self) +{ + self->NPC_type = "Tavion"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Tavion_New (1 0 0) (-16 -16 -24) (16 16 32) SCEPTER SITH_SWORD x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Has a red lightsaber and force powers, uses her saber style from JK2 + +SCEPTER - Has a red lightsaber and force powers, Ragnos' Scepter in left hand, uses dual saber style and occasionally attacks with Scepter +SITH_SWORD - Has Ragnos' Sith Sword in right hand and force powers, uses strong style +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tavion_New( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "tavion_scepter"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "tavion_sith_sword"; + } + else + { + self->NPC_type = "tavion_new"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Alora (1 0 0) (-16 -16 -24) (16 16 32) DUAL x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Lightsaber and level 2 force powers, 300 health + +DUAL - Dual sabers and level 3 force powers, 500 health +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Alora( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "alora_dual"; + } + else + { + self->NPC_type = "alora"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Reelo(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Reelo( gentity_t *self) +{ + self->NPC_type = "Reelo"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Galak(1 0 0) (-16 -16 -24) (16 16 40) MECH x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +MECH - will be the armored Galak + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Galak( gentity_t *self) +{ +} + +/*QUAKED NPC_Desann(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Desann( gentity_t *self) +{ + self->NPC_type = "Desann"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Rax(1 0 0) (-16 -16 -24) (16 16 40) FUN x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +FUN - Makes him magically the funnest thing ever in any game ever made. (actually does nothing, it'll just be fun by the power of suggestion). +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Rax( gentity_t *self ) +{ + self->NPC_type = "Rax"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_BobaFett(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_BobaFett( gentity_t *self ) +{ + self->NPC_type = "Boba_Fett"; + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Ragnos(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Ragnos( gentity_t *self ) +{ + self->NPC_type = "Ragnos"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Lannik_Racto(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Lannik_Racto( gentity_t *self ) +{ + self->NPC_type = "lannik_racto"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Kothos(1 0 0) (-16 -16 -24) (16 16 40) VIL x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +VIL - spawns Vil Kothos instead of Dasariah (can anyone tell them apart anyway...?) + +Force only... will (eventually) be set up to re-inforce their leader (use SET_LEADER) by healing them, recharging them, keeping the player away, etc. + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Kothos( gentity_t *self ) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "VKothos"; + } + else + { + self->NPC_type = "DKothos"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Chewbacca(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Chewbacca( gentity_t *self ) +{ + self->NPC_type = "Chewie"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Bartender(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Bartender( gentity_t *self) +{ + self->NPC_type = "Bartender"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_MorganKatarn(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_MorganKatarn( gentity_t *self) +{ + self->NPC_type = "MorganKatarn"; + + SP_NPC_spawner( self ); +} + +//============================================================================================= +//ALLIES +//============================================================================================= + +/*QUAKED NPC_Jedi(1 0 0) (-16 -16 -24) (16 16 40) TRAINER MASTER RANDOM x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +TRAINER - Special Jedi- instructor +MASTER - Special Jedi- master +RANDOM - creates a random Jedi student using the available player models/skins (excludes the current model of the player) +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Ally Jedi NPC Buddy - tags along with player +*/ +static char *randomJedis[] = { + "jedi_hf1", + "jedi_rm1", + "jedi_zf1", + "jedi_hm1", + "jedi_tf1", + "jedi_kdm1", +}; + +extern cvar_t *g_char_model; +void SP_NPC_Jedi( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( self->spawnflags & 4 ) // Random jedi student + { + static unsigned long jediSequence = 0; + + int myIdx; + for (myIdx = 0; myIdx < 6; ++myIdx) + if (strstr(randomJedis[myIdx], g_char_model->string)) + break; + // Sanity check, if player doesn't have a jedi_xx model right now: + if (myIdx == 6) + myIdx = 0; + + // Get one of the three after ours: + int idx = (myIdx + (jediSequence % 3) + 1) % 6; + jediSequence++; + + self->NPC_type = randomJedis[idx]; + } + else if ( self->spawnflags & 2 ) + { + self->NPC_type = "jedimaster"; + } + else if ( self->spawnflags & 1 ) + { + self->NPC_type = "jeditrainer"; + } + else + { + /* + if ( !Q_irand( 0, 2 ) ) + { + self->NPC_type = "JediF"; + } + else + */if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Jedi"; + } + else + { + self->NPC_type = "Jedi2"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Prisoner(1 0 0) (-16 -16 -24) (16 16 40) ELDER x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Prisoner( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( (self->spawnflags&1) ) + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "elder"; + } + else + { + self->NPC_type = "elder2"; + } + } + else + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Prisoner"; + } + else + { + self->NPC_type = "Prisoner2"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Merchant(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Merchant( gentity_t *self) +{ + self->NPC_type = "merchant"; + + SP_NPC_spawner( self ); +} +/*QUAKED NPC_Rebel(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Rebel( gentity_t *self) +{ + if(!self->NPC_type) + { + self->NPC_type = "Rebel"; + } + + SP_NPC_spawner( self ); +} + +//============================================================================================= +//ENEMIES +//============================================================================================= + +/*QUAKED NPC_Human_Merc(1 0 0) (-16 -16 -24) (16 16 40) BOWCASTER REPEATER FLECHETTE CONCUSSION DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +100 health, blaster rifle + +BOWCASTER - Starts with a Bowcaster +REPEATER - Starts with a Repeater +FLECHETTE - Starts with a Flechette gun +CONCUSSION - Starts with a Concussion Rifle + +If you want them to start with any other kind of weapon, make a spawnscript for them that sets their weapon. + +"message" - turns on his key surface. This is the name of the key you get when you walk over his body. This must match the "message" field of the func_security_panel you want this key to open. Set to "goodie" to have him carrying a goodie key that player can use to operate doors with "GOODIE" spawnflag. NOTE: this overrides all the weapon spawnflags + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Human_Merc( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->message ) + { + self->NPC_type = "human_merc_key"; + } + else if ( (self->spawnflags&1) ) + { + self->NPC_type = "human_merc_bow"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "human_merc_rep"; + } + else if ( (self->spawnflags&4) ) + { + self->NPC_type = "human_merc_flc"; + } + else if ( (self->spawnflags&8) ) + { + self->NPC_type = "human_merc_cnc"; + } + else + { + self->NPC_type = "human_merc"; + } + } + SP_NPC_spawner( self ); +} + +//TROOPERS============================================================================= + +/*QUAKED NPC_Stormtrooper(1 0 0) (-16 -16 -24) (16 16 40) OFFICER COMMANDER ALTOFFICER ROCKET DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY COMMANDO +30 health, blaster + +OFFICER - 60 health, flechette +COMMANDER - 60 health, heavy repeater +ALTOFFICER - 60 health, alt-fire flechette (grenades) +ROCKET - 60 health, rocket launcher + +COMMANDO - Causes character to use Hazard Trooper (move in formation AI) + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Stormtrooper( gentity_t *self) +{ + if ( self->spawnflags & 8 ) + {//rocketer + self->NPC_type = "rockettrooper"; + } + else if ( self->spawnflags & 4 ) + {//alt-officer + self->NPC_type = "stofficeralt"; + } + else if ( self->spawnflags & 2 ) + {//commander + self->NPC_type = "stcommander"; + } + else if ( self->spawnflags & 1 ) + {//officer + self->NPC_type = "stofficer"; + } + else + {//regular trooper + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "StormTrooper"; + } + else + { + self->NPC_type = "StormTrooper2"; + } + } + + SP_NPC_spawner( self ); +} +void SP_NPC_StormtrooperOfficer( gentity_t *self) +{ + self->spawnflags |= 1; + SP_NPC_Stormtrooper( self ); +} +/*QUAKED NPC_Snowtrooper(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +30 health, blaster + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Snowtrooper( gentity_t *self) +{ + self->NPC_type = "snowtrooper"; + + SP_NPC_spawner( self ); +} +/*QUAKED NPC_Tie_Pilot(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +30 health, blaster + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tie_Pilot( gentity_t *self) +{ + self->NPC_type = "stormpilot"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_RocketTrooper(1 0 0) (-16 -16 -24) (16 16 40) OFFICER SPOTLIGHT x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +200 health, flies, rockets + +OFFICER - starts flying, uses concussion rifle instead of rockets +SPOTLIGHT - uses a shoulder-mounted spotlight + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_RocketTrooper( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "rockettrooper2Officer"; + } + else + { + self->NPC_type = "rockettrooper2"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_HazardTrooper(1 0 0) (-16 -16 -24) (16 16 40) OFFICER CONCUSSION x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +250 health, repeater + +OFFICER - 400 health, flechette +CONCUSSION - 400 health, concussion rifle +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_HazardTrooper( gentity_t *self) +{ + if ( !self->NPC_type ) + { + + + if ( (self->spawnflags&1) ) + { + self->NPC_type = "hazardtrooperofficer"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "hazardtrooperconcussion"; + } + else + { + self->NPC_type = "hazardtrooper"; + } + } + + SP_NPC_spawner( self ); +} + +//OTHERS============================================================================= + +/*QUAKED NPC_Ugnaught(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Ugnaught( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Ugnaught"; + } + else + { + self->NPC_type = "Ugnaught2"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Jawa(1 0 0) (-16 -16 -24) (16 16 40) ARMED x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +ARMED - starts with the Jawa gun in-hand + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Jawa( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "jawa_armed"; + } + else + { + self->NPC_type = "jawa"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Gran(1 0 0) (-16 -16 -24) (16 16 40) SHOOTER BOXER x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +Uses grenade + +SHOOTER - uses blaster instead of +BOXER - uses fists only +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Gran( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "granshooter"; + } + else if ( self->spawnflags & 2 ) + { + self->NPC_type = "granboxer"; + } + else + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "gran"; + } + else + { + self->NPC_type = "gran2"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Rodian(1 0 0) (-16 -16 -24) (16 16 40) BLASTER NO_HIDE x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +BLASTER uses a blaster instead of sniper rifle, different skin +NO_HIDE (only applicable with snipers) does not duck and hide between shots +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Rodian( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags&1 ) + { + self->NPC_type = "rodian2"; + } + else + { + self->NPC_type = "rodian"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Weequay(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Weequay( gentity_t *self) +{ + if ( !self->NPC_type ) + { + switch ( Q_irand( 0, 3 ) ) + { + case 0: + self->NPC_type = "Weequay"; + break; + case 1: + self->NPC_type = "Weequay2"; + break; + case 2: + self->NPC_type = "Weequay3"; + break; + case 3: + self->NPC_type = "Weequay4"; + break; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Trandoshan(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Trandoshan( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "Trandoshan"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Tusken(1 0 0) (-16 -16 -24) (16 16 40) SNIPER x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tusken( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "tuskensniper"; + } + else + { + self->NPC_type = "tusken"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Noghri(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Noghri( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "noghri"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_SwampTrooper(1 0 0) (-16 -16 -24) (16 16 40) REPEATER x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +REPEATER - Swaptrooper who uses the repeater +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_SwampTrooper( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "SwampTrooper2"; + } + else + { + self->NPC_type = "SwampTrooper"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Imperial(1 0 0) (-16 -16 -24) (16 16 40) OFFICER COMMANDER x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY + +Greyshirt grunt, uses blaster pistol, 20 health. + +OFFICER - Brownshirt Officer, uses blaster rifle, 40 health +COMMANDER - Blackshirt Commander, uses rapid-fire blaster rifle, 80 healt + +"message" - if a COMMANDER, turns on his key surface. This is the name of the key you get when you walk over his body. This must match the "message" field of the func_security_panel you want this key to open. Set to "goodie" to have him carrying a goodie key that player can use to operate doors with "GOODIE" spawnflag. + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Imperial( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "ImpOfficer"; + } + else if ( self->spawnflags & 2 ) + { + self->NPC_type = "ImpCommander"; + } + else + { + self->NPC_type = "Imperial"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_ImpWorker(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_ImpWorker( gentity_t *self) +{ + self->NPC_type = "ImpWorker"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_BespinCop(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_BespinCop( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( !Q_irand( 0, 1 ) ) + { + self->NPC_type = "BespinCop"; + } + else + { + self->NPC_type = "BespinCop2"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Reborn(1 0 0) (-16 -16 -24) (16 16 40) FORCE FENCER ACROBAT BOSS CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Default Reborn is A poor lightsaber fighter, acrobatic and uses no force powers. 40 health. + +FORCE - Uses force powers but is not the best lightsaber fighter and not acrobatic. 75 health. +FENCER - A good lightsaber fighter, but not acrobatic and uses no force powers. 100 health. +ACROBAT - quite acrobatic, but not the best lightsaber fighter and uses no force powers. 100 health. +BOSS - quite acrobatic, good lightsaber fighter and uses force powers. 150 health. + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +nodelay - set to "1" to make the Reborn not stand around and taunt the player before attacking +*/ +void SP_NPC_Reborn( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "rebornforceuser"; + } + else if ( self->spawnflags & 2 ) + { + self->NPC_type = "rebornfencer"; + } + else if ( self->spawnflags & 4 ) + { + self->NPC_type = "rebornacrobat"; + } + else if ( self->spawnflags & 8 ) + { + self->NPC_type = "rebornboss"; + } + else + { + self->NPC_type = "reborn"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Reborn_New(1 0 0) (-16 -16 -24) (16 16 40) DUAL STAFF WEAK MASTER CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Reborn is an excellent lightsaber fighter, acrobatic and uses force powers. Full-length red saber, 200 health. + +DUAL - Use 2 shorter sabers +STAFF - Uses a saber staff +WEAK - Is a bit less tough +MASTER - Is SUPER tough +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +nodelay - set to "1" to make the Reborn not stand around and taunt the player before attacking +*/ +void SP_NPC_Reborn_New( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&8) ) + {//tougher guys + if ( (self->spawnflags&1) ) + { + self->NPC_type = "RebornMasterDual"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "RebornMasterStaff"; + } + else + { + self->NPC_type = "RebornMaster"; + } + } + else if ( (self->spawnflags&4) ) + {//weaker guys + if ( (self->spawnflags&1) ) + { + self->NPC_type = "reborn_dual2"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "reborn_staff2"; + } + else + { + self->NPC_type = "reborn_new2"; + } + } + else + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "reborn_dual"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "reborn_staff"; + } + else + { + self->NPC_type = "reborn_new"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist_Saber(1 0 0) (-16 -16 -24) (16 16 40) MED STRONG ALL THROW CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Uses a saber and no force powers. 100 health. + +default fencer uses fast style - weak, but can attack rapidly. Good defense. + +MED - Uses medium style - average speed and attack strength, average defense. +STRONG - Uses strong style, slower than others, but can do a lot of damage with one blow. Weak defense. +ALL - Knows all 3 styles, switches between them, good defense. +THROW - can throw their saber (level 2) - reduces their defense some (can use this spawnflag alone or in combination with any *one* of the previous 3 spawnflags) + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +nodelay - set to "1" to make the Cultist not stand around and taunt the player before attacking +*/ +void SP_NPC_Cultist_Saber( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_med_throw"; + } + else + { + self->NPC_type = "cultist_saber_med"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_strong_throw"; + } + else + { + self->NPC_type = "cultist_saber_strong"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_all_throw"; + } + else + { + self->NPC_type = "cultist_saber_all"; + } + } + else + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_throw"; + } + else + { + self->NPC_type = "cultist_saber"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist_Saber_Powers(1 0 0) (-16 -16 -24) (16 16 40) MED STRONG ALL THROW CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Uses a saber and has a couple low-level powers. 150 health. + +default fencer uses fast style - weak, but can attack rapidly. Good defense. + +MED - Uses medium style - average speed and attack strength, average defense. +STRONG - Uses strong style, slower than others, but can do a lot of damage with one blow. Weak defense. +ALL - Knows all 3 styles, switches between them, good defense. +THROW - can throw their saber (level 2) - reduces their defense some (can use this spawnflag alone or in combination with any *one* of the previous 3 spawnflags) + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +nodelay - set to "1" to make the Cultist not stand around and taunt the player before attacking +*/ +void SP_NPC_Cultist_Saber_Powers( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_med_throw2"; + } + else + { + self->NPC_type = "cultist_saber_med2"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_strong_throw2"; + } + else + { + self->NPC_type = "cultist_saber_strong2"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_all_throw2"; + } + else + { + self->NPC_type = "cultist_saber_all2"; + } + } + else + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_throw"; + } + else + { + self->NPC_type = "cultist_saber2"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist(1 0 0) (-16 -16 -24) (16 16 40) SABER GRIP LIGHTNING DRAIN CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Cultist uses a blaster and force powers. 40 health. + +SABER - Uses a saber and no force powers +GRIP - Uses no weapon and grip, push and pull +LIGHTNING - Uses no weapon and lightning and push +DRAIN - Uses no weapons and drain and push + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +nodelay - set to "1" to make the Cultist not stand around and taunt the player before attacking +*/ +void SP_NPC_Cultist( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = NULL; + self->spawnflags = 0;//fast, no throw + switch ( Q_irand( 0, 2 ) ) + { + case 0://medium + self->spawnflags |= 1; + break; + case 1://strong + self->spawnflags |= 2; + break; + case 2://all + self->spawnflags |= 4; + break; + } + if ( Q_irand( 0, 1 ) ) + {//throw + self->spawnflags |= 8; + } + SP_NPC_Cultist_Saber( self ); + return; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "cultist_grip"; + } + else if ( (self->spawnflags&4) ) + { + self->NPC_type = "cultist_lightning"; + } + else if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_drain"; + } + else + { + self->NPC_type = "cultist"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist_Commando(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Cultist uses dual blaster pistols and force powers. 40 health. + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +nodelay - set to "1" to make the Cultist not stand around and taunt the player before attacking +*/ +void SP_NPC_Cultist_Commando( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "cultistcommando"; + } + SP_NPC_spawner( self ); +} +/*QUAKED NPC_Cultist_Destroyer(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Cultist has no weapons, runs up to you chanting & building up a Force Destruction blast - when gets to you, screams & explodes + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist_Destroyer( gentity_t *self) +{ + self->NPC_type = "cultist_destroyer"; + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_ShadowTrooper(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +nodelay - set to "1" to make the Cultist not stand around and taunt the player before attacking +*/ +void SP_NPC_ShadowTrooper( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( !Q_irand( 0, 1 ) ) + { + self->NPC_type = "ShadowTrooper"; + } + else + { + self->NPC_type = "ShadowTrooper2"; + } + } + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Saboteur(1 0 0) (-16 -16 -24) (16 16 40) SNIPER PISTOL x x CLOAKED CINEMATIC NOTSOLID STARTINSOLID SHY +Has a blaster rifle, can cloak and roll + +SNIPER - Has a sniper rifle, no acrobatics, but can dodge +PISTOL - Just has a pistol, can roll +COMMANDO - Has 2 pistols and can roll & dodge + +CLOAKED - Starts cloaked +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Saboteur( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "saboteursniper"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "saboteurpistol"; + } + else if ( (self->spawnflags&4) ) + { + self->NPC_type = "saboteurcommando"; + } + else + { + self->NPC_type = "saboteur"; + } + } + SP_NPC_spawner( self ); +} + +//============================================================================================= +//MONSTERS +//============================================================================================= + +/*QUAKED NPC_Monster_Murjj (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Murjj( gentity_t *self) +{ + self->NPC_type = "Murjj"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Swamp (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Swamp( gentity_t *self) +{ + self->NPC_type = "Swamp"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Howler (1 0 0) (-16 -16 -24) (16 16 8) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Howler( gentity_t *self) +{ + self->NPC_type = "howler"; + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Rancor (1 0 0) (-30 -30 -24) (30 30 136) MUTANT FASTKILL x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +2000 health, picks people up and eats them + +MUTANT - Bigger, meaner, nastier, Frencher. Breath attack, pound attack. +FASTKILL - Kills NPCs faster +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Rancor( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "mutant_rancor"; + } + else + { + self->NPC_type = "rancor"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Mutant_Rancor (1 0 0) (-60 -60 -24) (60 60 360) x FASTKILL x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +Bigger, meaner, nastier, Frencher. Breath attack, pound attack. + +FASTKILL - Kills NPCs faster +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Mutant_Rancor( gentity_t *self) +{ + self->NPC_type = "mutant_rancor"; + + SP_NPC_spawner( self ); +} +/*QUAKED NPC_Monster_Wampa (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Wampa( gentity_t *self) +{ + self->NPC_type = "wampa"; + + SP_NPC_spawner( self ); +} +/*QUAKED NPC_MineMonster (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_MineMonster( gentity_t *self) +{ + self->NPC_type = "minemonster"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Claw (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Claw( gentity_t *self) +{ + self->NPC_type = "Claw"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Glider (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Glider( gentity_t *self) +{ + self->NPC_type = "Glider"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Flier2 (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Flier2( gentity_t *self) +{ + self->NPC_type = "Flier2"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Lizard (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Lizard( gentity_t *self) +{ + self->NPC_type = "Lizard"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Fish (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Fish( gentity_t *self) +{ + self->NPC_type = "Fish"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Sand_Creature (1 0 0) (-24 -24 -24) (24 24 0) FAST x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +turfrange - if set, they will not go beyond this dist from their spawn position +*/ +void SP_NPC_Monster_Sand_Creature( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "sand_creature_fast"; + } + else + { + self->NPC_type = "sand_creature"; + } + + SP_NPC_spawner( self ); +} + +//============================================================================================= +//DROIDS +//============================================================================================= + +/*QUAKED NPC_Droid_Interrogator (1 0 0) (-12 -12 -24) (12 12 0) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Droid_Interrogator( gentity_t *self) +{ + self->NPC_type = "interrogator"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_Probe (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Imperial Probe Droid - the multilegged floating droid that Han and Chewie shot on the ice planet Hoth +*/ +void SP_NPC_Droid_Probe( gentity_t *self) +{ + self->NPC_type = "probe"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_Mark1 (1 0 0) (-36 -36 -24) (36 36 80) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Big walking droid + +*/ +void SP_NPC_Droid_Mark1( gentity_t *self) +{ + self->NPC_type = "mark1"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_Mark2 (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Small rolling droid with one gun. + +*/ +void SP_NPC_Droid_Mark2( gentity_t *self) +{ + self->NPC_type = "mark2"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_ATST (1 0 0) (-40 -40 -24) (40 40 248) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Droid_ATST( gentity_t *self) +{ + self->NPC_type = "atst"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_Remote (1 0 0) (-4 -4 -24) (4 4 8) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Remote Droid - the floating round droid used by Obi Wan to train Luke about the force while on the Millenium Falcon. +*/ +void SP_NPC_Droid_Remote( gentity_t *self) +{ + self->NPC_type = "remote_sp"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_Seeker (1 0 0) (-4 -4 -24) (4 4 8) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Seeker Droid - floating round droids that shadow troopers spawn +*/ +void SP_NPC_Droid_Seeker( gentity_t *self) +{ + self->NPC_type = "seeker"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_Sentry (1 0 0) (-24 -24 -24) (24 24 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Sentry Droid - Large, armored floating Imperial droids with 3 forward-facing gun turrets +*/ +void SP_NPC_Droid_Sentry( gentity_t *self) +{ + self->NPC_type = "sentry"; + + SP_NPC_spawner( self ); + +} + +/*QUAKED NPC_Droid_Gonk (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Gonk Droid - the droid that looks like a walking ice machine. Was in the Jawa land crawler, walking around talking to itself. + +NOTARGET by default +*/ +void SP_NPC_Droid_Gonk( gentity_t *self) +{ + self->NPC_type = "gonk"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_Mouse (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Mouse Droid - small, box shaped droid, first seen on the Death Star. Chewie yelled at it and it backed up and ran away. + +NOTARGET by default +*/ +void SP_NPC_Droid_Mouse( gentity_t *self) +{ + self->NPC_type = "mouse"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_R2D2 (1 0 0) (-12 -12 -24) (12 12 40) IMPERIAL x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +R2D2 Droid - you probably know this one already. + +NOTARGET by default +*/ +void SP_NPC_Droid_R2D2( gentity_t *self) +{ + if ( self->spawnflags&1 ) + {//imperial skin + self->NPC_type = "r2d2_imp"; + } + else + { + self->NPC_type = "r2d2"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_R5D2 (1 0 0) (-12 -12 -24) (12 12 40) IMPERIAL ALWAYSDIE x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +ALWAYSDIE - won't go into spinning zombie AI when at low health. +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +R5D2 Droid - the droid originally chosen by Uncle Owen until it blew a bad motivator, and they took R2D2 instead. + +NOTARGET by default +*/ +void SP_NPC_Droid_R5D2( gentity_t *self) +{ + if ( self->spawnflags&1 ) + {//imperial skin + self->NPC_type = "r5d2_imp"; + } + else + { + self->NPC_type = "r5d2"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_Protocol (1 0 0) (-12 -12 -24) (12 12 40) IMPERIAL x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +NOTARGET by default +*/ +void SP_NPC_Droid_Protocol( gentity_t *self) +{ + if ( self->spawnflags&1 ) + {//imperial skin + self->NPC_type = "protocol_imp"; + } + else + { + self->NPC_type = "protocol"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_Assassin (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Droid_Assassin( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "assassin_droid"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Droid_Saber (1 0 0) (-12 -12 -24) (12 12 40) TRAINING x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Droid_Saber( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "saber_droid_training"; + } + else + { + self->NPC_type = "saber_droid"; + } + } + + SP_NPC_spawner( self ); +} + +//NPC console commands +/* +NPC_Spawn_f +*/ + +static void NPC_Spawn_f(void) +{ + gentity_t *NPCspawner = G_Spawn(); + vec3_t forward, end; + trace_t trace; + qboolean isVehicle = qfalse; + + if(!NPCspawner) + { + gi.Printf( S_COLOR_RED"NPC_Spawn Error: Out of entities!\n" ); + return; + } + + NPCspawner->e_ThinkFunc = thinkF_G_FreeEntity; + NPCspawner->nextthink = level.time + FRAMETIME; + + + char *npc_type = gi.argv( 2 ); + if (!npc_type ) + { + gi.Printf( S_COLOR_RED"Error, expected:\n NPC spawn [NPC type (from NCPCs.cfg)]\n" ); + return; + } + + if ( !Q_stricmp( "vehicle", npc_type ) ) + {//spawning a vehicle + isVehicle = qtrue; + npc_type = gi.argv( 3 ); + if (!npc_type ) + { + gi.Printf( S_COLOR_RED"Error, expected:\n NPC spawn vehicle [NPC type (from NCPCs.cfg)]\n" ); + return; + } + } + + //Spawn it at spot of first player + //FIXME: will gib them! + AngleVectors(g_entities[0].client->ps.viewangles, forward, NULL, NULL); + VectorNormalize(forward); + VectorMA(g_entities[0].currentOrigin, 64, forward, end); + gi.trace(&trace, g_entities[0].currentOrigin, NULL, NULL, end, 0, MASK_SOLID); + VectorCopy(trace.endpos, end); + end[2] -= 24; + gi.trace(&trace, trace.endpos, NULL, NULL, end, 0, MASK_SOLID); + VectorCopy(trace.endpos, end); + end[2] += 24; + G_SetOrigin(NPCspawner, end); + VectorCopy(NPCspawner->currentOrigin, NPCspawner->s.origin); + //set the yaw so that they face away from player + NPCspawner->s.angles[1] = g_entities[0].client->ps.viewangles[1]; + + gi.linkentity(NPCspawner); + + NPCspawner->NPC_type = strlwr( G_NewString( npc_type ) ); + NPCspawner->NPC_targetname = G_NewString(gi.argv( 3 )); + + NPCspawner->count = 1; + + NPCspawner->delay = 0; + + NPCspawner->wait = 500; + + //NPCspawner->spawnflags |= SFB_NOTSOLID; + + //NPCspawner->playerTeam = TEAM_FREE; + //NPCspawner->behaviorSet[BSET_SPAWN] = "common/guard"; + + if ( isVehicle ) + {//must let NPC spawn func know this is a vehicle we're trying to spawn + NPCspawner->classname = "NPC_Vehicle"; + } + + NPC_PrecacheByClassName(NPCspawner->NPC_type); + if ( !Q_stricmp( "kyle_boss", NPCspawner->NPC_type )) + {//bah + NPCspawner->spawnflags |= 1; + } + + if ( !Q_stricmp( "key", NPCspawner->NPC_type )) + {//bah + NPCspawner->message = "key"; + NPCspawner->NPC_type = "imperial"; + } + if ( !Q_stricmp( "jedi_random", NPCspawner->NPC_type ) ) + {//special case, for testing + NPCspawner->NPC_type = NULL; + NPCspawner->spawnflags |= 4; + SP_NPC_Jedi( NPCspawner ); + } + else + { + NPC_Spawn( NPCspawner, NPCspawner, NPCspawner ); + } +} + +/* +NPC_Kill_f +*/ + +void NPC_Kill_f( void ) +{ + int n; + gentity_t *player; + char *name; + team_t killTeam = TEAM_FREE; + qboolean killNonSF = qfalse; + + name = gi.argv( 2 ); + + if ( !*name || !name[0] ) + { + gi.Printf( S_COLOR_RED"Error, Expected:\n"); + gi.Printf( S_COLOR_RED"NPC kill '[NPC targetname]' - kills NPCs with certain targetname\n" ); + gi.Printf( S_COLOR_RED"or\n" ); + gi.Printf( S_COLOR_RED"NPC kill 'all' - kills all NPCs\n" ); + gi.Printf( S_COLOR_RED"or\n" ); + gi.Printf( S_COLOR_RED"NPC team '[teamname]' - kills all NPCs of a certain team ('nonally' is all but your allies)\n" ); + return; + } + + if ( Q_stricmp( "team", name ) == 0 ) + { + name = gi.argv( 3 ); + + if ( !*name || !name[0] ) + { + gi.Printf( S_COLOR_RED"NPC_Kill Error: 'npc kill team' requires a team name!\n" ); + gi.Printf( S_COLOR_RED"Valid team names are:\n"); + for ( n = (TEAM_FREE + 1); n < TEAM_NUM_TEAMS; n++ ) + { + gi.Printf( S_COLOR_RED"%s\n", GetStringForID( TeamTable, n ) ); + } + gi.Printf( S_COLOR_RED"nonally - kills all but your teammates\n" ); + return; + } + + if ( Q_stricmp( "nonally", name ) == 0 ) + { + killNonSF = qtrue; + } + else + { + killTeam = (team_t)GetIDForString( TeamTable, name ); + + if ( killTeam == -1 ) + { + gi.Printf( S_COLOR_RED"NPC_Kill Error: team '%s' not recognized\n", name ); + gi.Printf( S_COLOR_RED"Valid team names are:\n"); + for ( n = (TEAM_FREE + 1); n < TEAM_NUM_TEAMS; n++ ) + { + gi.Printf( S_COLOR_RED"%s\n", GetStringForID( TeamTable, n ) ); + } + gi.Printf( S_COLOR_RED"nonally - kills all but your teammates\n" ); + return; + } + } + } + + for ( n = 1; n < ENTITYNUM_MAX_NORMAL; n++) + { + player = &g_entities[n]; + if (!player->inuse) { + continue; + } + if ( killNonSF ) + { + if ( player ) + { + if ( player->client ) + { + if ( player->client->playerTeam != TEAM_PLAYER ) + { + gi.Printf( S_COLOR_GREEN"Killing NPC %s named %s\n", player->NPC_type, player->targetname ); + /* + if ( (player->flags&FL_UNDYING) ) + { + G_Damage( player, NULL, NULL, NULL, NULL, player->health+10000, 0, MOD_UNKNOWN ); + } + else + */ + { + player->health = 0; + GEntity_DieFunc(player, player, player, player->max_health, MOD_UNKNOWN); + } + } + } + else if ( player->NPC_type && player->classname && player->classname[0] && Q_stricmp( "NPC_starfleet", player->classname ) != 0 ) + {//A spawner, remove it + gi.Printf( S_COLOR_GREEN"Removing NPC spawner %s with NPC named %s\n", player->NPC_type, player->NPC_targetname ); + G_FreeEntity( player ); + //FIXME: G_UseTargets2(player, player, player->NPC_target & player->target);? + } + } + } + else if ( player && player->NPC && player->client ) + { + if ( killTeam != TEAM_FREE ) + { + if ( player->client->playerTeam == killTeam ) + { + gi.Printf( S_COLOR_GREEN"Killing NPC %s named %s\n", player->NPC_type, player->targetname ); + /* + if ( (player->flags&FL_UNDYING) ) + { + G_Damage( player, NULL, NULL, NULL, NULL, player->health+10000, 0, MOD_UNKNOWN ); + } + else + */ + { + player->health = 0; + GEntity_DieFunc(player, player, player, player->max_health, MOD_UNKNOWN); + } + } + } + else if( (player->targetname && Q_stricmp( name, player->targetname ) == 0) + || Q_stricmp( name, "all" ) == 0 ) + { + gi.Printf( S_COLOR_GREEN"Killing NPC %s named %s\n", player->NPC_type, player->targetname ); + player->client->ps.stats[STAT_HEALTH] = 0; + /* + if ( (player->flags&FL_UNDYING) ) + { + G_Damage( player, NULL, NULL, NULL, NULL, player->health+10000, 0, MOD_UNKNOWN ); + } + else + */ + { + player->health = 0; + GEntity_DieFunc(player, player, player, 100, MOD_UNKNOWN); + } + } + } + else if ( player && (player->svFlags&SVF_NPC_PRECACHE) ) + {//a spawner + if( (player->targetname && Q_stricmp( name, player->targetname ) == 0) + || Q_stricmp( name, "all" ) == 0 ) + { + gi.Printf( S_COLOR_GREEN"Removing NPC spawner %s named %s\n", player->NPC_type, player->targetname ); + G_FreeEntity( player ); + } + } + } +} + +void NPC_PrintScore( gentity_t *ent ) +{ + gi.Printf( "%s: %d\n", ent->targetname, ent->client->ps.persistant[PERS_SCORE] ); +} + +/* +Svcmd_NPC_f + +parse and dispatch bot commands +*/ +qboolean showBBoxes = qfalse; +void Svcmd_NPC_f( void ) +{ + char *cmd; + + cmd = gi.argv( 1 ); + + if ( !*cmd ) + { + gi.Printf( "Valid NPC commands are:\n" ); + gi.Printf( " spawn [NPC type (from *.npc files)]\n" ); + gi.Printf( " spawn vehicle [NPC type (from *.npc files, only for NPCs that are CLASS_VEHICLE and have a .veh file)]\n" ); + gi.Printf( " kill [NPC targetname] or [all(kills all NPCs)] or 'team [teamname]'\n" ); + gi.Printf( " showbounds (draws exact bounding boxes of NPCs)\n" ); + gi.Printf( " score [NPC targetname] (prints number of kills per NPC)\n" ); + } + else if ( Q_stricmp( cmd, "spawn" ) == 0 ) + { + NPC_Spawn_f(); + } + else if ( Q_stricmp( cmd, "kill" ) == 0 ) + { + NPC_Kill_f(); + } + else if ( Q_stricmp( cmd, "showbounds" ) == 0 ) + {//Toggle on and off + showBBoxes = showBBoxes ? qfalse : qtrue; + } + else if ( Q_stricmp ( cmd, "score" ) == 0 ) + { + char *cmd2 = gi.argv(2); + gentity_t *ent = NULL; + + if ( !cmd2 || !cmd2[0] ) + {//Show the score for all NPCs + gi.Printf( "SCORE LIST:\n" ); + for ( int i = 0; i < ENTITYNUM_WORLD; i++ ) + { + ent = &g_entities[i]; + if ( !ent || !ent->client ) + { + continue; + } + NPC_PrintScore( ent ); + } + } + else + { + if ( (ent = G_Find( NULL, FOFS(targetname), cmd2 )) != NULL && ent->client ) + { + NPC_PrintScore( ent ); + } + else + { + gi.Printf( "ERROR: NPC score - no such NPC %s\n", cmd2 ); + } + } + } +} diff --git a/code/game/NPC_stats.cpp b/code/game/NPC_stats.cpp new file mode 100644 index 0000000..ca619c5 --- /dev/null +++ b/code/game/NPC_stats.cpp @@ -0,0 +1,4018 @@ +//NPC_stats.cpp +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + +#include "b_local.h" +#include "b_public.h" +#include "anims.h" +#include "wp_saber.h" +#include "g_Vehicles.h" +#if !defined(RUFL_HSTRING_INC) + #include "..\Rufl\hstring.h" +#endif + #include "..\Ratl\string_vs.h" + #include "..\Rufl\hstring.h" + #include "..\Ratl\vector_vs.h" + +extern void WP_RemoveSaber( gentity_t *ent, int saberNum ); +extern qboolean NPCsPrecached; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern stringID_table_t WPTable[]; + +#define MAX_MODELS_PER_LEVEL 40 + +#ifdef _XBOX +using dllNamespace::hstring; +#endif +hstring modelsAlreadyDone[MAX_MODELS_PER_LEVEL]; + + +stringID_table_t animEventTypeTable[] = +{ + ENUM2STRING(AEV_SOUND), //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay + ENUM2STRING(AEV_FOOTSTEP), //# animID AEV_FOOTSTEP framenum footstepType + ENUM2STRING(AEV_EFFECT), //# animID AEV_EFFECT framenum effectpath boltName + ENUM2STRING(AEV_FIRE), //# animID AEV_FIRE framenum altfire chancetofire + ENUM2STRING(AEV_MOVE), //# animID AEV_MOVE framenum forwardpush rightpush uppush + ENUM2STRING(AEV_SOUNDCHAN), //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay + //must be terminated + NULL,-1 +}; + +stringID_table_t footstepTypeTable[] = +{ + ENUM2STRING(FOOTSTEP_R), + ENUM2STRING(FOOTSTEP_L), + ENUM2STRING(FOOTSTEP_HEAVY_R), + ENUM2STRING(FOOTSTEP_HEAVY_L), + //must be terminated + NULL,-1 +}; + +stringID_table_t FPTable[] = +{ + ENUM2STRING(FP_HEAL), + ENUM2STRING(FP_LEVITATION), + ENUM2STRING(FP_SPEED), + ENUM2STRING(FP_PUSH), + ENUM2STRING(FP_PULL), + ENUM2STRING(FP_TELEPATHY), + ENUM2STRING(FP_GRIP), + ENUM2STRING(FP_LIGHTNING), + ENUM2STRING(FP_SABERTHROW), + ENUM2STRING(FP_SABER_DEFENSE), + ENUM2STRING(FP_SABER_OFFENSE), + //new Jedi Academy powers + ENUM2STRING(FP_RAGE), + ENUM2STRING(FP_PROTECT), + ENUM2STRING(FP_ABSORB), + ENUM2STRING(FP_DRAIN), + ENUM2STRING(FP_SEE), + "", -1 +}; + + +stringID_table_t TeamTable[] = +{ + "free", TEAM_FREE, // caution, some code checks a team_t via "if (!team_t_varname)" so I guess this should stay as entry 0, great or what? -slc + ENUM2STRING(TEAM_FREE), // caution, some code checks a team_t via "if (!team_t_varname)" so I guess this should stay as entry 0, great or what? -slc + "player", TEAM_PLAYER, + ENUM2STRING(TEAM_PLAYER), + "enemy", TEAM_ENEMY, + ENUM2STRING(TEAM_ENEMY), + "neutral", TEAM_NEUTRAL, // most droids are team_neutral, there are some exceptions like Probe,Seeker,Interrogator + ENUM2STRING(TEAM_NEUTRAL), // most droids are team_neutral, there are some exceptions like Probe,Seeker,Interrogator + "", -1 +}; + + +// this list was made using the model directories, this MUST be in the same order as the CLASS_ enum in teams.h +stringID_table_t ClassTable[] = +{ + ENUM2STRING(CLASS_NONE), // hopefully this will never be used by an npc), just covering all bases + ENUM2STRING(CLASS_ATST), // technically droid... + ENUM2STRING(CLASS_BARTENDER), + ENUM2STRING(CLASS_BESPIN_COP), + ENUM2STRING(CLASS_CLAW), + ENUM2STRING(CLASS_COMMANDO), + ENUM2STRING(CLASS_DESANN), + ENUM2STRING(CLASS_FISH), + ENUM2STRING(CLASS_FLIER2), + ENUM2STRING(CLASS_GALAK), + ENUM2STRING(CLASS_GLIDER), + ENUM2STRING(CLASS_GONK), // droid + ENUM2STRING(CLASS_GRAN), + ENUM2STRING(CLASS_HOWLER), + ENUM2STRING(CLASS_RANCOR), + ENUM2STRING(CLASS_SAND_CREATURE), + ENUM2STRING(CLASS_WAMPA), + ENUM2STRING(CLASS_IMPERIAL), + ENUM2STRING(CLASS_IMPWORKER), + ENUM2STRING(CLASS_INTERROGATOR), // droid + ENUM2STRING(CLASS_JAN), + ENUM2STRING(CLASS_JEDI), + ENUM2STRING(CLASS_KYLE), + ENUM2STRING(CLASS_LANDO), + ENUM2STRING(CLASS_LIZARD), + ENUM2STRING(CLASS_LUKE), + ENUM2STRING(CLASS_MARK1), // droid + ENUM2STRING(CLASS_MARK2), // droid + ENUM2STRING(CLASS_GALAKMECH), // droid + ENUM2STRING(CLASS_MINEMONSTER), + ENUM2STRING(CLASS_MONMOTHA), + ENUM2STRING(CLASS_MORGANKATARN), + ENUM2STRING(CLASS_MOUSE), // droid + ENUM2STRING(CLASS_MURJJ), + ENUM2STRING(CLASS_PRISONER), + ENUM2STRING(CLASS_PROBE), // droid + ENUM2STRING(CLASS_PROTOCOL), // droid + ENUM2STRING(CLASS_R2D2), // droid + ENUM2STRING(CLASS_R5D2), // droid + ENUM2STRING(CLASS_REBEL), + ENUM2STRING(CLASS_REBORN), + ENUM2STRING(CLASS_REELO), + ENUM2STRING(CLASS_REMOTE), + ENUM2STRING(CLASS_RODIAN), + ENUM2STRING(CLASS_SEEKER), // droid + ENUM2STRING(CLASS_SENTRY), + ENUM2STRING(CLASS_SHADOWTROOPER), + ENUM2STRING(CLASS_SABOTEUR), + ENUM2STRING(CLASS_STORMTROOPER), + ENUM2STRING(CLASS_SWAMP), + ENUM2STRING(CLASS_SWAMPTROOPER), + ENUM2STRING(CLASS_NOGHRI), + ENUM2STRING(CLASS_TAVION), + ENUM2STRING(CLASS_ALORA), + ENUM2STRING(CLASS_TRANDOSHAN), + ENUM2STRING(CLASS_UGNAUGHT), + ENUM2STRING(CLASS_JAWA), + ENUM2STRING(CLASS_WEEQUAY), + ENUM2STRING(CLASS_TUSKEN), + ENUM2STRING(CLASS_BOBAFETT), + ENUM2STRING(CLASS_ROCKETTROOPER), + ENUM2STRING(CLASS_SABER_DROID), + ENUM2STRING(CLASS_PLAYER), + ENUM2STRING(CLASS_ASSASSIN_DROID), + ENUM2STRING(CLASS_HAZARD_TROOPER), + ENUM2STRING(CLASS_VEHICLE), + "", -1 +}; + +/* +NPC_ReactionTime +*/ +//FIXME use grandom in here +int NPC_ReactionTime ( void ) +{ + return 200 * ( 6 - NPCInfo->stats.reactions ); +} + +// +// parse support routines +// + +qboolean G_ParseLiteral( const char **data, const char *string ) +{ + const char *token; + + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) + { + gi.Printf( "unexpected EOF\n" ); + return qtrue; + } + + if ( Q_stricmp( token, string ) ) + { + gi.Printf( "required string '%s' missing\n", string ); + return qtrue; + } + + return qfalse; +} + +// +// NPC parameters file : ext_data/NPCs/*.npc* +// +#define MAX_NPC_DATA_SIZE 0x20000 +char NPCParms[MAX_NPC_DATA_SIZE]; + +/* +static rank_t TranslateRankName( const char *name ) + + Should be used to determine pip bolt-ons +*/ +static rank_t TranslateRankName( const char *name ) +{ + if ( !Q_stricmp( name, "civilian" ) ) + { + return RANK_CIVILIAN; + } + + if ( !Q_stricmp( name, "crewman" ) ) + { + return RANK_CREWMAN; + } + + if ( !Q_stricmp( name, "ensign" ) ) + { + return RANK_ENSIGN; + } + + if ( !Q_stricmp( name, "ltjg" ) ) + { + return RANK_LT_JG; + } + + if ( !Q_stricmp( name, "lt" ) ) + { + return RANK_LT; + } + + if ( !Q_stricmp( name, "ltcomm" ) ) + { + return RANK_LT_COMM; + } + + if ( !Q_stricmp( name, "commander" ) ) + { + return RANK_COMMANDER; + } + + if ( !Q_stricmp( name, "captain" ) ) + { + return RANK_CAPTAIN; + } + + return RANK_CIVILIAN; +} + +#ifdef _XBOX +extern saber_colors_t TranslateSaberColor( const char *name ); +#else +saber_colors_t TranslateSaberColor( const char *name ) +{ + if ( !Q_stricmp( name, "red" ) ) + { + return SABER_RED; + } + if ( !Q_stricmp( name, "orange" ) ) + { + return SABER_ORANGE; + } + if ( !Q_stricmp( name, "yellow" ) ) + { + return SABER_YELLOW; + } + if ( !Q_stricmp( name, "green" ) ) + { + return SABER_GREEN; + } + if ( !Q_stricmp( name, "blue" ) ) + { + return SABER_BLUE; + } + if ( !Q_stricmp( name, "purple" ) ) + { + return SABER_PURPLE; + } + if ( !Q_stricmp( name, "random" ) ) + { + return ((saber_colors_t)(Q_irand( SABER_ORANGE, SABER_PURPLE ))); + } + return SABER_BLUE; +} +#endif + +/* static int MethodNameToNumber( const char *name ) { + if ( !Q_stricmp( name, "EXPONENTIAL" ) ) { + return METHOD_EXPONENTIAL; + } + if ( !Q_stricmp( name, "LINEAR" ) ) { + return METHOD_LINEAR; + } + if ( !Q_stricmp( name, "LOGRITHMIC" ) ) { + return METHOD_LOGRITHMIC; + } + if ( !Q_stricmp( name, "ALWAYS" ) ) { + return METHOD_ALWAYS; + } + if ( !Q_stricmp( name, "NEVER" ) ) { + return METHOD_NEVER; + } + return -1; +} + +static int ItemNameToNumber( const char *name, int itemType ) { +// int n; + + for ( n = 0; n < bg_numItems; n++ ) { + if ( bg_itemlist[n].type != itemType ) { + continue; + } + if ( Q_stricmp( bg_itemlist[n].classname, name ) == 0 ) { + return bg_itemlist[n].tag; + } + } + return -1; +} +*/ + +static int MoveTypeNameToEnum( const char *name ) +{ + if(!Q_stricmp("runjump", name)) + { + return MT_RUNJUMP; + } + else if(!Q_stricmp("walk", name)) + { + return MT_WALK; + } + else if(!Q_stricmp("flyswim", name)) + { + return MT_FLYSWIM; + } + else if(!Q_stricmp("static", name)) + { + return MT_STATIC; + } + + return MT_STATIC; +} + +extern void CG_RegisterClientRenderInfo(clientInfo_t *ci, renderInfo_t *ri); +extern void CG_RegisterClientModels (int entityNum); +extern void CG_RegisterNPCCustomSounds( clientInfo_t *ci ); +//extern void CG_RegisterNPCEffects( team_t team ); + +//#define CONVENIENT_ANIMATION_FILE_DEBUG_THING + +#ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING +void SpewDebugStuffToFile(animation_t *bgGlobalAnimations) +{ + char BGPAFtext[40000]; + fileHandle_t f; + int i = 0; + + gi.FS_FOpenFile("file_of_debug_stuff_SP.txt", &f, FS_WRITE); + + if (!f) + { + return; + } + + BGPAFtext[0] = 0; + + while (i < MAX_ANIMATIONS) + { + strcat(BGPAFtext, va("%i %i\n", i, bgGlobalAnimations[i].frameLerp)); + i++; + } + + gi.FS_Write(BGPAFtext, strlen(BGPAFtext), f); + gi.FS_FCloseFile(f); +} +#endif + +int CG_CheckAnimFrameForEventType( animevent_t *animEvents, int keyFrame, animEventType_t eventType, unsigned short modelIndex ) +{ + for ( int i = 0; i < MAX_ANIM_EVENTS; i++ ) + { + if ( animEvents[i].keyFrame == keyFrame ) + {//there is an animevent on this frame already + if ( animEvents[i].eventType == eventType ) + {//and it is of the same type + if ( animEvents[i].modelOnly == modelIndex ) + {//and it is for the same model + return i; + } + } + } + } + //nope + return -1; +} + +/* +====================== +ParseAnimationEvtBlock +====================== +*/ +static void ParseAnimationEvtBlock(int glaIndex, unsigned short modelIndex, const char* aeb_filename, animevent_t *animEvents, animation_t *animations, unsigned char &lastAnimEvent, const char **text_p, bool bIsFrameSkipped) +{ + const char *token; + int num, n, animNum, keyFrame, lowestVal, highestVal, curAnimEvent = 0; + animEventType_t eventType; + char stringData[MAX_QPATH]; + + // get past starting bracket + while(1) + { + token = COM_Parse( text_p ); + if ( !Q_stricmp( token, "{" ) ) + { + break; + } + } + + //NOTE: instead of a blind increment, increase the index + // this way if we have an event on an anim that already + // has an event of that type, it stomps it + + // read information for each frame + while ( 1 ) + { + if ( lastAnimEvent >= MAX_ANIM_EVENTS ) + { + CG_Error( "ParseAnimationEvtBlock: number events in file %s > MAX_ANIM_EVENTS(%i)", aeb_filename, MAX_ANIM_EVENTS ); + } + // Get base frame of sequence + token = COM_Parse( text_p ); + if ( !token || !token[0]) + { + break; + } + + if ( !Q_stricmp( token, "}" ) ) // At end of block + { + break; + } + + //Compare to same table as animations used + // so we don't have to use actual numbers for animation first frames, + // just need offsets. + //This way when animation numbers change, this table won't have to be updated, + // at least not much. + animNum = GetIDForString(animTable, token); + if(animNum == -1) + {//Unrecognized ANIM ENUM name, + Com_Printf(S_COLOR_YELLOW"WARNING: Unknown ANIM %s in file %s\n", token, aeb_filename ); + //skip this entry + SkipRestOfLine( text_p ); + continue; + } + + if ( animations[animNum].numFrames == 0 ) + {//we don't use this anim +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_YELLOW"WARNING: %s: anim %s not used by this model\n", aeb_filename, token); +#endif + //skip this entry + SkipRestOfLine( text_p ); + continue; + } + + token = COM_Parse( text_p ); + eventType = (animEventType_t)GetIDForString(animEventTypeTable, token); + if ( eventType == AEV_NONE || eventType == -1 ) + {//Unrecognized ANIM EVENT TYPE + Com_Printf(S_COLOR_RED"ERROR: Unknown EVENT %s in animEvent file %s\n", token, aeb_filename ); + continue; + } + + // Get offset to frame within sequence + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + + keyFrame = atoi( token ); + if ( bIsFrameSkipped && + (animations[animNum].numFrames>1) // important, else frame 1 gets divided down and becomes frame 0. Carcass & Assimilate also work this way + ) + { + keyFrame /= 2; // if we ever use any other value in frame-skipping we'll have to figure out some way of reading it, since it's not stored anywhere + } + if(keyFrame >= animations[animNum].numFrames) + { + Com_Printf(S_COLOR_YELLOW"WARNING: Event out of range on %s in %s\n", GetStringForID(animTable,animNum), aeb_filename ); + assert(keyFrame < animations[animNum].numFrames); + keyFrame = animations[animNum].numFrames-1; //clamp it + } + + //set our start frame + keyFrame += animations[animNum].firstFrame; + + //see if this frame already has an event of this type on it, if so, overwrite it + curAnimEvent = CG_CheckAnimFrameForEventType( animEvents, keyFrame, eventType, modelIndex ); + if ( curAnimEvent == -1 ) + {//this anim frame doesn't already have an event of this type on it + curAnimEvent = lastAnimEvent; + } + + //now that we know which event index we're going to plug the data into, start doing it + animEvents[curAnimEvent].eventType = eventType; + assert(keyFrame >= 0 && keyFrame < 65535); // + animEvents[curAnimEvent].keyFrame = keyFrame; + animEvents[curAnimEvent].glaIndex = glaIndex; + animEvents[curAnimEvent].modelOnly = modelIndex; + int tempVal; + + //now read out the proper data based on the type + switch ( animEvents[curAnimEvent].eventType ) + { + case AEV_SOUNDCHAN: //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + if ( stricmp( token, "CHAN_VOICE_ATTEN" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE_ATTEN; + } + else if ( stricmp( token, "CHAN_VOICE_GLOBAL" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE_GLOBAL; + } + else if ( stricmp( token, "CHAN_ANNOUNCER" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_ANNOUNCER; + } + else if ( stricmp( token, "CHAN_BODY" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_BODY; + } + else if ( stricmp( token, "CHAN_WEAPON" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_WEAPON; + } + else if ( stricmp( token, "CHAN_VOICE" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE; + } + else + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_AUTO; + } + //fall through to normal sound + case AEV_SOUND: //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay + //get soundstring + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + strcpy(stringData, token); + //get lowest value + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + lowestVal = atoi( token ); + //get highest value + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + highestVal = atoi( token ); + //Now precache all the sounds + //NOTE: If we can be assured sequential handles, we can store the first sound index and count + // unfortunately, if these sounds were previously registered, we cannot be guaranteed sequential indices. Thus an array + if(lowestVal && highestVal) + { + assert(highestVal - lowestVal < MAX_RANDOM_ANIM_SOUNDS); + for ( n = lowestVal, num = AED_SOUNDINDEX_START; n <= highestVal && num <= AED_SOUNDINDEX_END; n++, num++ ) + { + animEvents[curAnimEvent].eventData[num] = G_SoundIndex( va( stringData, n ) );//cgi_S_RegisterSound + } + animEvents[curAnimEvent].eventData[AED_SOUND_NUMRANDOMSNDS] = num - 1; + } + else + { + animEvents[curAnimEvent].eventData[AED_SOUNDINDEX_START] = G_SoundIndex( stringData );//cgi_S_RegisterSound +#if 0 //#ifndef FINAL_BUILD (only meaningfull if using S_RegisterSound + if ( !animEvents[curAnimEvent].eventData[AED_SOUNDINDEX_START] ) + {//couldn't register it - file not found + Com_Printf( S_COLOR_RED "ParseAnimationSndBlock: sound %s does not exist (%s)!\n", stringData, *aeb_filename ); + } +#endif + animEvents[curAnimEvent].eventData[AED_SOUND_NUMRANDOMSNDS] = 0; + } + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_SOUND_PROBABILITY] = atoi( token ); + break; + case AEV_FOOTSTEP: //# animID AEV_FOOTSTEP framenum footstepType + //get footstep type + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + animEvents[curAnimEvent].eventData[AED_FOOTSTEP_TYPE] = GetIDForString(footstepTypeTable, token); + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_FOOTSTEP_PROBABILITY] = atoi( token ); + break; + case AEV_EFFECT: //# animID AEV_EFFECT framenum effectpath boltName + //get effect index + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + if ( token[0] && Q_stricmp( "special", token ) == 0 ) + {//special hard-coded effects + //let cgame know it's not a real effect + animEvents[curAnimEvent].eventData[AED_EFFECTINDEX] = -1; + //get the name of it + token = COM_Parse( text_p ); + animEvents[curAnimEvent].stringData = G_NewString( token ); + } + else + {//regular effect + tempVal = G_EffectIndex(token); + assert(tempVal > -32767 && tempVal < 32767); + animEvents[curAnimEvent].eventData[AED_EFFECTINDEX] = tempVal; + //get bolt index + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + if ( Q_stricmp( "none", token ) != 0 && Q_stricmp( "NULL", token ) != 0 ) + {//actually are specifying a bolt to use + animEvents[curAnimEvent].stringData = G_NewString( token ); + } + } + //NOTE: this string will later be used to add a bolt and store the index, as below: + //animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], animEvent->stringData ); + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_EFFECT_PROBABILITY] = atoi( token ); + break; + case AEV_FIRE: //# animID AEV_FIRE framenum altfire chancetofire + //get altfire + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_FIRE_ALT] = atoi( token ); + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_FIRE_PROBABILITY] = atoi( token ); + break; + case AEV_MOVE: //# animID AEV_MOVE framenum forwardpush rightpush uppush + //get forward push + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + tempVal = atoi(token); + assert(tempVal > -32767 && tempVal < 32767); + animEvents[curAnimEvent].eventData[AED_MOVE_FWD] = tempVal; + //get right push + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + tempVal = atoi(token); + assert(tempVal > -32767 && tempVal < 32767); + animEvents[curAnimEvent].eventData[AED_MOVE_RT] = tempVal; + //get upwards push + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + tempVal = atoi(token); + assert(tempVal > -32767 && tempVal < 32767); + animEvents[curAnimEvent].eventData[AED_MOVE_UP] = tempVal; + break; + default: //unknown? + SkipRestOfLine( text_p ); + continue; + break; + } + + if ( curAnimEvent == lastAnimEvent ) + { + lastAnimEvent++; + } + } +} + + + +/* +====================== +G_ParseAnimationEvtFile + +Read a configuration file containing animation events +models/players/kyle/animevents.cfg, etc + +This file's presence is not required + +====================== +*/ +static +void G_ParseAnimationEvtFile(int glaIndex, const char* eventsDirectory, int fileIndex, int iRealGLAIndex = -1, bool modelSpecific = false) +{ + int len; + const char* token; + char text[80000]; + const char* text_p = text; + fileHandle_t f; + char eventsPath[MAX_QPATH]; + int modelIndex = 0; + + assert(fileIndex>=0 && fileIndex5 && !stricmp(&psAnimFileInternalName[strlen(psAnimFileInternalName)-5],"_skip")); + bool bIsFrameSkipped = false; + if( (psAnimFileInternalName && strstr(psAnimFileInternalName, "_humanoid")) || + strstr(eventsDirectory, "rancor") ) + bIsFrameSkipped = true; + + // Open The File, Make Sure It Is Safe + //------------------------------------- + Com_sprintf(eventsPath, MAX_QPATH, "models/players/%s/animevents.cfg", eventsDirectory); + len = cgi_FS_FOpenFile(eventsPath, &f, FS_READ); + if ( len <= 0 ) + {//no file + return; + } + if ( len >= sizeof( text ) - 1 ) + { + CG_Printf( "File %s too long\n", eventsPath ); + return; + } + + // Read It To The Buffer, Close The File + //--------------------------------------- + cgi_FS_Read( text, len, f ); + text[len] = 0; + cgi_FS_FCloseFile( f ); + + + // Get The Pointers To The Anim Event Arrays + //------------------------------------------- + animFileSet_t& afileset = level.knownAnimFileSets[fileIndex]; + animevent_t *legsAnimEvents = afileset.legsAnimEvents; + animevent_t *torsoAnimEvents = afileset.torsoAnimEvents; + animation_t *animations = afileset.animations; + + + if (modelSpecific) + { + hstring modelName(eventsDirectory); + modelIndex = modelName.handle(); + } + + + // read information for batches of sounds (UPPER or LOWER) + while ( 1 ) + { + // Get base frame of sequence + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) + { + break; + } + + //these stomp anything set in the include file (if it's an event of the same type on the same frame)! + if ( !Q_stricmp(token,"UPPEREVENTS") ) // A batch of upper events + { + ParseAnimationEvtBlock(glaIndex, modelIndex, eventsPath, torsoAnimEvents, animations, afileset.torsoAnimEventCount, &text_p, bIsFrameSkipped); + } + + else if ( !Q_stricmp(token,"LOWEREVENTS") ) // A batch of lower events + { + ParseAnimationEvtBlock(glaIndex, modelIndex, eventsPath, legsAnimEvents, animations, afileset.legsAnimEventCount, &text_p, bIsFrameSkipped); + } + } +} + +/* +====================== +G_ParseAnimationFile + +Read a configuration file containing animation coutns and rates +models/players/visor/animation.cfg, etc + +====================== +*/ +qboolean G_ParseAnimationFile(int glaIndex, const char *skeletonName, int fileIndex) +{ + char text[80000]; + int len = 0; + const char *token = 0; + float fps = 0; + const char* text_p = text; + int animNum = 0; + animation_t* animations = level.knownAnimFileSets[fileIndex].animations; + char skeletonPath[MAX_QPATH]; + + + // Read In The File To The Text Buffer, Make Sure Everything Is Safe To Continue + //------------------------------------------------------------------------------- + Com_sprintf(skeletonPath, MAX_QPATH, "models/players/%s/%s.cfg", skeletonName, skeletonName); + len = gi.RE_GetAnimationCFG(skeletonPath, text, sizeof(text)); + if ( len <= 0 ) + { + Com_sprintf(skeletonPath, MAX_QPATH, "models/players/%s/animation.cfg", skeletonName); + len = gi.RE_GetAnimationCFG(skeletonPath, text, sizeof(text)); + if ( len <= 0 ) + { + return qfalse; + } + } + if ( len >= sizeof( text ) - 1 ) + { + G_Error( "G_ParseAnimationFile: File %s too long\n (%d > %d)", skeletonName, len, sizeof( text ) - 1); + return qfalse; + } + + + + // Read In Each Token + //-------------------- + while(1) + { + token = COM_Parse( &text_p ); + + // If No Token, We've Reached The End Of The File + //------------------------------------------------ + if ( !token || !token[0]) + { + break; + } + + // Get The Anim Number Converted From The First Token + //---------------------------------------------------- + animNum = GetIDForString(animTable, token); + if(animNum == -1) + { +#ifndef FINAL_BUILD + if (strcmp(token,"ROOT")) + { + Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, skeletonPath); + } +#endif + //unrecognized animation so skip to end of line, + while (token[0]) + { + token = COM_ParseExt( &text_p, qfalse ); //returns empty string when next token is EOL + } + continue; + } + + // GLAIndex + //---------- + animations[animNum].glaIndex = glaIndex; // Passed Into This Func + + // First Frame + //------------- + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + assert(atoi(token) >= 0 && atoi(token) < 65536); + animations[animNum].firstFrame = atoi( token ); + + // Num Frames + //------------ + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + assert(atoi(token) >= 0 && atoi(token) < 65536); + animations[animNum].numFrames = atoi( token ); + + // Loop Frames + //------------- + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + assert(atoi(token) >= -1 && atoi(token) < 128); + animations[animNum].loopFrames = atoi( token ); + + // FPS + //----- + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + fps = atof( token ); + if ( fps == 0 ) + { + fps = 1;//Don't allow divide by zero error + } + + // Calculate Frame Lerp + //---------------------- + int lerp; + if ( fps < 0 ) + {//backwards + lerp = floor(1000.0f / fps); + assert(lerp > -32767 && lerp < 32767); + animations[animNum].frameLerp = lerp; + assert(animations[animNum].frameLerp <= 1); + } + else + { + lerp = ceil(1000.0f / fps); + assert(lerp > -32767 && lerp < 32767); + animations[animNum].frameLerp = lerp; + assert(animations[animNum].frameLerp >= 1); + } + +/* lerp = ceil(1000.0f / Q_fabs(fps)); + assert(lerp > -32767 && lerp < 32767); + animations[animNum].initialLerp = lerp; +*/ + } + + + + +#ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING + if (strstr(af_filename, "humanoid")) + { + SpewDebugStuffToFile(animations); + } +#endif + + return qtrue; +} + + + + +//////////////////////////////////////////////////////////////////////// +// G_ParseAnimFileSet +// +// This function is responsible for building the animation file and +// the animation event file. +// +//////////////////////////////////////////////////////////////////////// +int G_ParseAnimFileSet(const char *skeletonName, const char *modelName=0) +{ + int fileIndex=0; + + + // Try To Find An Existing Skeleton File For This Animation Set + //-------------------------------------------------------------- + for (fileIndex=0; fileIndex=level.numKnownAnimFileSets) + { + if (level.numKnownAnimFileSets==MAX_ANIM_FILES) + { + G_Error( "G_ParseAnimFileSet: MAX_ANIM_FILES" ); + return -1; + } + + // Allocate A new File Set, And Get Some Shortcut Pointers + //--------------------------------------------------------- + fileIndex = level.numKnownAnimFileSets; + level.numKnownAnimFileSets++; + strcpy(level.knownAnimFileSets[fileIndex].filename, skeletonName); + + level.knownAnimFileSets[fileIndex].torsoAnimEventCount = 0; + level.knownAnimFileSets[fileIndex].legsAnimEventCount = 0; + + animation_t* animations = level.knownAnimFileSets[fileIndex].animations; + animevent_t* legsAnimEvents = level.knownAnimFileSets[fileIndex].legsAnimEvents; + animevent_t* torsoAnimEvents = level.knownAnimFileSets[fileIndex].torsoAnimEvents; + + int i, j; + + // Initialize The Frames Information + //----------------------------------- + for(i=0; iplayerModel == -1 ) + { + return; + } + if ( Q_stricmp( "player", pModelName ) == 0 ) + {//model is actually stored on console + modelName = g_char_model->string; + } + else + { + modelName = (char *)pModelName; + } + //get the location of the animation.cfg + GLAName = gi.G2API_GetGLAName( &ent->ghoul2[ent->playerModel] ); + //now load and parse the animation.cfg, animevents.cfg and set the animFileIndex + if ( !GLAName) + { + Com_Printf( S_COLOR_RED"Failed find animation file name models/players/%s\n", modelName ); + strippedName="_humanoid"; //take a guess, maybe it's right? + } + else + { + Q_strncpyz( animName, GLAName, sizeof( animName ), qtrue ); + slash = strrchr( animName, '/' ); + if ( slash ) + { + *slash = 0; + } + strippedName = COM_SkipPath( animName ); + } + + //now load and parse the animation.cfg, animevents.cfg and set the animFileIndex + ent->client->clientInfo.animFileIndex = G_ParseAnimFileSet(strippedName, modelName); + + if (ent->client->clientInfo.animFileIndex<0) + { + Com_Printf( S_COLOR_RED"Failed to load animation file set models/players/%s/animation.cfg\n", modelName ); +#ifndef FINAL_BUILD + Com_Error(ERR_FATAL, "Failed to load animation file set models/players/%s/animation.cfg\n", modelName); +#endif + } +} + + +void NPC_PrecacheAnimationCFG( const char *NPC_type ) +{ + char filename[MAX_QPATH]; + const char *token; + const char *value; + const char *p; + + if ( !Q_stricmp( "random", NPC_type ) ) + {//sorry, can't precache a random just yet + return; + } + + p = NPCParms; + COM_BeginParseSession(); + + // look for the right NPC + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + return; + + if ( !Q_stricmp( token, NPC_type ) ) + { + break; + } + + SkipBracedSection( &p ); + } + + if ( !p ) + { + return; + } + + if ( G_ParseLiteral( &p, "{" ) ) + { + return; + } + + // parse the NPC info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPC_type ); + return; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + // legsmodel + if ( !Q_stricmp( token, "legsmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //must copy data out of this pointer into a different part of memory because the funcs we're about to call will call COM_ParseExt + Q_strncpyz( filename, value, sizeof( filename ), qtrue ); + G_ParseAnimFileSet( filename ); + return; + } + + // playerModel + if ( !Q_stricmp( token, "playerModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + char animName[MAX_QPATH]; + char *GLAName; + char *slash = NULL; + char *strippedName; + + int handle = gi.G2API_PrecacheGhoul2Model( va( "models/players/%s/model.glm", value ) ); + if ( handle > 0 )//FIXME: isn't 0 a valid handle? + { + GLAName = gi.G2API_GetAnimFileNameIndex( handle ); + if ( GLAName ) + { + Q_strncpyz( animName, GLAName, sizeof( animName ), qtrue ); + slash = strrchr( animName, '/' ); + if ( slash ) + { + *slash = 0; + } + strippedName = COM_SkipPath( animName ); + + //must copy data out of this pointer into a different part of memory because the funcs we're about to call will call COM_ParseExt + Q_strncpyz( filename, value, sizeof( filename ), qtrue ); + + G_ParseAnimFileSet(strippedName, filename); + return; + } + } + } + } +} + +extern int NPC_WeaponsForTeam( team_t team, int spawnflags, const char *NPC_type ); +void NPC_PrecacheWeapons( team_t playerTeam, int spawnflags, char *NPCtype ) +{ + int weapons = NPC_WeaponsForTeam( playerTeam, spawnflags, NPCtype ); + gitem_t *item; + for ( int curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ ) + { + if ( (weapons & ( 1 << curWeap )) ) + { + item = FindItemForWeapon( ((weapon_t)(curWeap)) ); //precache the weapon + CG_RegisterItemSounds( (item-bg_itemlist) ); + CG_RegisterItemVisuals( (item-bg_itemlist) ); + //precache the in-hand/in-world ghoul2 weapon model + + char weaponModel[64]; + + strcpy (weaponModel, weaponData[curWeap].weaponMdl); + if (char *spot = strstr(weaponModel, ".md3") ) { + *spot = 0; + spot = strstr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on + if (!spot) { + strcat (weaponModel, "_w"); + } + strcat (weaponModel, ".glm"); //and change to ghoul2 + } + gi.G2API_PrecacheGhoul2Model( weaponModel ); // correct way is item->world_model + } + } +} + +/* +void NPC_PrecacheByClassName ( char *NPCName ) + +This runs all the class specific precache functions + +*/ + +extern void NPC_ShadowTrooper_Precache( void ); +extern void NPC_Gonk_Precache( void ); +extern void NPC_Mouse_Precache( void ); +extern void NPC_Seeker_Precache( void ); +extern void NPC_Remote_Precache( void ); +extern void NPC_R2D2_Precache(void); +extern void NPC_R5D2_Precache(void); +extern void NPC_Probe_Precache(void); +extern void NPC_Interrogator_Precache(gentity_t *self); +extern void NPC_MineMonster_Precache( void ); +extern void NPC_Howler_Precache( void ); +extern void NPC_Rancor_Precache( void ); +extern void NPC_MutantRancor_Precache( void ); +extern void NPC_Wampa_Precache( void ); +extern void NPC_ATST_Precache(void); +extern void NPC_Sentry_Precache(void); +extern void NPC_Mark1_Precache(void); +extern void NPC_Mark2_Precache(void); +extern void NPC_Protocol_Precache( void ); +extern void Boba_Precache( void ); +extern void RT_Precache( void ); +extern void SandCreature_Precache( void ); +extern void NPC_TavionScepter_Precache( void ); +extern void NPC_TavionSithSword_Precache( void ); +extern void NPC_Rosh_Dark_Precache( void ); +extern void NPC_Tusken_Precache( void ); +extern void NPC_Saboteur_Precache( void ); +extern void NPC_CultistDestroyer_Precache( void ); +void NPC_Jawa_Precache( void ) +{ + for ( int i = 1; i < 7; i++ ) + { + G_SoundIndex( va( "sound/chars/jawa/misc/chatter%d.wav", i ) ); + } + G_SoundIndex( "sound/chars/jawa/misc/ooh-tee-nee.wav" ); +} + +void NPC_PrecacheByClassName( const char* type ) +{ + if (!type || !type[0]) + { + return; + } + + if ( !Q_stricmp( "gonk", type)) + { + NPC_Gonk_Precache(); + } + else if ( !Q_stricmp( "mouse", type)) + { + NPC_Mouse_Precache(); + } + else if ( !Q_stricmpn( "r2d2", type, 4)) + { + NPC_R2D2_Precache(); + } + else if ( !Q_stricmp( "atst", type)) + { + NPC_ATST_Precache(); + } + else if ( !Q_stricmpn( "r5d2", type, 4)) + { + NPC_R5D2_Precache(); + } + else if ( !Q_stricmp( "mark1", type)) + { + NPC_Mark1_Precache(); + } + else if ( !Q_stricmp( "mark2", type)) + { + NPC_Mark2_Precache(); + } + else if ( !Q_stricmp( "interrogator", type)) + { + NPC_Interrogator_Precache(NULL); + } + else if ( !Q_stricmp( "probe", type)) + { + NPC_Probe_Precache(); + } + else if ( !Q_stricmp( "seeker", type)) + { + NPC_Seeker_Precache(); + } + else if ( !Q_stricmpn( "remote", type, 6)) + { + NPC_Remote_Precache(); + } + else if ( !Q_stricmpn( "shadowtrooper", type, 13 ) ) + { + NPC_ShadowTrooper_Precache(); + } + else if ( !Q_stricmp( "minemonster", type )) + { + NPC_MineMonster_Precache(); + } + else if ( !Q_stricmp( "howler", type )) + { + NPC_Howler_Precache(); + } + else if ( !Q_stricmp( "rancor", type )) + { + NPC_Rancor_Precache(); + } + else if ( !Q_stricmp( "mutant_rancor", type )) + { + NPC_Rancor_Precache(); + NPC_MutantRancor_Precache(); + } + else if ( !Q_stricmp( "wampa", type )) + { + NPC_Wampa_Precache(); + } + else if ( !Q_stricmp( "sand_creature", type )) + { + SandCreature_Precache(); + } + else if ( !Q_stricmp( "sentry", type )) + { + NPC_Sentry_Precache(); + } + else if ( !Q_stricmp( "protocol", type )) + { + NPC_Protocol_Precache(); + } + else if ( !Q_stricmp( "boba_fett", type )) + { + Boba_Precache(); + } + else if ( !Q_stricmp( "rockettrooper2", type )) + { + RT_Precache(); + } + else if ( !Q_stricmp( "rockettrooper2Officer", type )) + { + RT_Precache(); + } + else if ( !Q_stricmp( "tavion_scepter", type )) + { + NPC_TavionScepter_Precache(); + } + else if ( !Q_stricmp( "tavion_sith_sword", type )) + { + NPC_TavionSithSword_Precache(); + } + else if ( !Q_stricmp( "rosh_dark", type ) ) + { + NPC_Rosh_Dark_Precache(); + } + else if ( !Q_stricmpn( "tusken", type, 6 ) ) + { + NPC_Tusken_Precache(); + } + else if ( !Q_stricmpn( "saboteur", type, 8 ) ) + { + NPC_Saboteur_Precache(); + } + else if ( !Q_stricmp( "cultist_destroyer", type ) ) + { + NPC_CultistDestroyer_Precache(); + } + else if ( !Q_stricmpn( "jawa", type, 4 ) ) + { + NPC_Jawa_Precache(); + } +} + + + +/* +void NPC_Precache ( char *NPCName ) + +Precaches NPC skins, tgas and md3s. + +*/ +void NPC_Precache ( gentity_t *spawner ) +{ + clientInfo_t ci={0}; + renderInfo_t ri={0}; + team_t playerTeam = TEAM_FREE; + const char *token; + const char *value; + const char *p; + char *patch; + char sound[MAX_QPATH]; + qboolean md3Model = qfalse; + char playerModel[MAX_QPATH] = { 0 }; + char customSkin[MAX_QPATH]; + + if ( !Q_stricmp( "random", spawner->NPC_type ) ) + {//sorry, can't precache a random just yet + return; + } + + strcpy(customSkin,"default"); + + p = NPCParms; + COM_BeginParseSession(); + + // look for the right NPC + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + return; + + if ( !Q_stricmp( token, spawner->NPC_type ) ) + { + break; + } + + SkipBracedSection( &p ); + } + + if ( !p ) + { + return; + } + + if ( G_ParseLiteral( &p, "{" ) ) + { + return; + } + + // parse the NPC info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", spawner->NPC_type ); + return; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + // headmodel + if ( !Q_stricmp( token, "headmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + } + else + { + Q_strncpyz( ri.headModelName, value, sizeof(ri.headModelName), qtrue); + } + md3Model = qtrue; + continue; + } + + // torsomodel + if ( !Q_stricmp( token, "torsomodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + } + else + { + Q_strncpyz( ri.torsoModelName, value, sizeof(ri.torsoModelName), qtrue); + } + md3Model = qtrue; + continue; + } + + // legsmodel + if ( !Q_stricmp( token, "legsmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( ri.legsModelName, value, sizeof(ri.legsModelName), qtrue); + md3Model = qtrue; + continue; + } + + // playerModel + if ( !Q_stricmp( token, "playerModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( playerModel, value, sizeof(playerModel), qtrue); + md3Model = qfalse; + continue; + } + + // customSkin + if ( !Q_stricmp( token, "customSkin" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( customSkin, value, sizeof(customSkin), qtrue); + continue; + } + + // playerTeam + if ( !Q_stricmp( token, "playerTeam" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + playerTeam = (team_t)GetIDForString( TeamTable, token ); + continue; + } + + + // snd + if ( !Q_stricmp( token, "snd" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + if ( !(spawner->svFlags&SVF_NO_BASIC_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + ci.customBasicSoundDir = G_NewString( sound ); + } + continue; + } + + // sndcombat + if ( !Q_stricmp( token, "sndcombat" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + if ( !(spawner->svFlags&SVF_NO_COMBAT_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + ci.customCombatSoundDir = G_NewString( sound ); + } + continue; + } + + // sndextra + if ( !Q_stricmp( token, "sndextra" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + if ( !(spawner->svFlags&SVF_NO_EXTRA_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + ci.customExtraSoundDir = G_NewString( sound ); + } + continue; + } + + // sndjedi + if ( !Q_stricmp( token, "sndjedi" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + if ( !(spawner->svFlags&SVF_NO_EXTRA_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + ci.customJediSoundDir = G_NewString( sound ); + } + continue; + } + + //cache weapons + if ( !Q_stricmp( token, "weapon" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + int weap = GetIDForString( WPTable, value ); + if ( weap >= WP_NONE && weap < WP_NUM_WEAPONS ) + { + if ( weap > WP_NONE ) + { + RegisterItem( FindItemForWeapon( (weapon_t)(weap) ) ); //precache the weapon + } + } + continue; + } + //cache sabers + //saber name + if ( !Q_stricmp( token, "saber" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + char *saberName = G_NewString( value ); + saberInfo_t saber; + WP_SaberParseParms( saberName, &saber ); + if ( saber.model && saber.model[0] ) + { + G_ModelIndex( saber.model ); + } + if ( saber.skin && saber.skin[0] ) + { + gi.RE_RegisterSkin( saber.skin ); + G_SkinIndex( saber.skin ); + } + continue; + } + + //second saber name + if ( !Q_stricmp( token, "saber2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + char *saberName = G_NewString( value ); + saberInfo_t saber; + WP_SaberParseParms( saberName, &saber ); + if ( saber.model && saber.model[0] ) + { + G_ModelIndex( saber.model ); + } + if ( saber.skin && saber.skin[0] ) + { + gi.RE_RegisterSkin( saber.skin ); + G_SkinIndex( saber.skin ); + } + continue; + } + } + + if ( md3Model ) + { + CG_RegisterClientRenderInfo( &ci, &ri ); + } + else + { + char skinName[MAX_QPATH]; + //precache ghoul2 model + gi.G2API_PrecacheGhoul2Model( va( "models/players/%s/model.glm", playerModel ) ); + //precache skin + if (strchr(customSkin, '|')) + {//three part skin + Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/|%s", playerModel, customSkin ); + } + else + {//standard skin + Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_%s.skin", playerModel, customSkin ); + } + // lets see if it's out there + gi.RE_RegisterSkin( skinName ); + } + + //precache this NPC's possible weapons + NPC_PrecacheWeapons( playerTeam, spawner->spawnflags, spawner->NPC_type ); + + // Anything else special about them + NPC_PrecacheByClassName( spawner->NPC_type ); + + CG_RegisterNPCCustomSounds( &ci ); + //CG_RegisterNPCEffects( playerTeam ); + //FIXME: Look for a "sounds" directory and precache death, pain, alert sounds +} + +void NPC_BuildRandom( gentity_t *NPC ) +{ +} + +extern void G_MatchPlayerWeapon( gentity_t *ent ); +extern void G_InitPlayerFromCvars( gentity_t *ent ); +extern void G_SetG2PlayerModel( gentity_t * const ent, const char *modelName, const char *customSkin, const char *surfOff, const char *surfOn ); +qboolean NPC_ParseParms( const char *NPCName, gentity_t *NPC ) +{ + const char *token; + const char *value; + const char *p; + int n; + float f; + char *patch; + char sound[MAX_QPATH]; + char playerModel[MAX_QPATH]; + char customSkin[MAX_QPATH]; + clientInfo_t *ci = &NPC->client->clientInfo; + renderInfo_t *ri = &NPC->client->renderInfo; + gNPCstats_t *stats = NULL; + qboolean md3Model = qtrue; + char surfOff[1024]={0}; + char surfOn[1024]={0}; + qboolean parsingPlayer = qfalse; + + strcpy(customSkin,"default"); + if ( !NPCName || !NPCName[0]) + { + NPCName = "Player"; + } + + if ( !NPC->s.number && NPC->client != NULL ) + {//player, only want certain data + parsingPlayer = qtrue; + } + + if ( NPC->NPC ) + { + stats = &NPC->NPC->stats; +/* + NPC->NPC->allWeaponOrder[0] = WP_BRYAR_PISTOL; + NPC->NPC->allWeaponOrder[1] = WP_SABER; + NPC->NPC->allWeaponOrder[2] = WP_IMOD; + NPC->NPC->allWeaponOrder[3] = WP_SCAVENGER_RIFLE; + NPC->NPC->allWeaponOrder[4] = WP_TRICORDER; + NPC->NPC->allWeaponOrder[6] = WP_NONE; + NPC->NPC->allWeaponOrder[6] = WP_NONE; + NPC->NPC->allWeaponOrder[7] = WP_NONE; +*/ + // fill in defaults + stats->sex = SEX_MALE; + stats->aggression = 3; + stats->aim = 3; + stats->earshot = 1024; + stats->evasion = 3; + stats->hfov = 90; + stats->intelligence = 3; + stats->move = 3; + stats->reactions = 3; + stats->vfov = 60; + stats->vigilance = 0.1f; + stats->visrange = 1024; + if (g_entities[ENTITYNUM_WORLD].max_health && stats->visrange>g_entities[ENTITYNUM_WORLD].max_health) + { + stats->visrange = g_entities[ENTITYNUM_WORLD].max_health; + } + stats->health = 0; + + stats->yawSpeed = 90; + stats->walkSpeed = 90; + stats->runSpeed = 300; + stats->acceleration = 15;//Increase/descrease speed this much per frame (20fps) + } + else + { + stats = NULL; + } + + Q_strncpyz( ci->name, NPCName, sizeof( ci->name ) ); + + NPC->playerModel = -1; + + //Set defaults + //FIXME: should probably put default torso and head models, but what about enemies + //that don't have any- like Stasis? + //Q_strncpyz( ri->headModelName, DEFAULT_HEADMODEL, sizeof(ri->headModelName), qtrue); + //Q_strncpyz( ri->torsoModelName, DEFAULT_TORSOMODEL, sizeof(ri->torsoModelName), qtrue); + //Q_strncpyz( ri->legsModelName, DEFAULT_LEGSMODEL, sizeof(ri->legsModelName), qtrue); + ri->headModelName[0] = 0; + ri->torsoModelName[0]= 0; + ri->legsModelName[0] =0; + + ri->headYawRangeLeft = 80; + ri->headYawRangeRight = 80; + ri->headPitchRangeUp = 45; + ri->headPitchRangeDown = 45; + ri->torsoYawRangeLeft = 60; + ri->torsoYawRangeRight = 60; + ri->torsoPitchRangeUp = 30; + ri->torsoPitchRangeDown = 50; + + VectorCopy(playerMins, NPC->mins); + VectorCopy(playerMaxs, NPC->maxs); + NPC->client->crouchheight = CROUCH_MAXS_2; + NPC->client->standheight = DEFAULT_MAXS_2; + + NPC->client->moveType = MT_RUNJUMP; + + NPC->client->dismemberProbHead = 100; + NPC->client->dismemberProbArms = 100; + NPC->client->dismemberProbHands = 100; + NPC->client->dismemberProbWaist = 100; + NPC->client->dismemberProbLegs = 100; + + NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = 1.0f; + + *(int*)ri->customRGBA=-1; + + if ( !Q_stricmp( "random", NPCName ) ) + {//Randomly assemble an NPC + NPC_BuildRandom( NPC ); + } + else + { + p = NPCParms; + COM_BeginParseSession(); + + // look for the right NPC + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return qfalse; + } + + if ( !Q_stricmp( token, NPCName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { + return qfalse; + } + + if ( G_ParseLiteral( &p, "{" ) ) + { + return qfalse; + } + + // parse the NPC info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPCName ); + return qfalse; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + //===MODEL PROPERTIES=========================================================== + // custom color + if ( !Q_stricmp( token, "customRGBA" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if ( !Q_stricmp( value, "random") ) + { + ri->customRGBA[0]=Q_irand(0,255); + ri->customRGBA[1]=Q_irand(0,255); + ri->customRGBA[2]=Q_irand(0,255); + ri->customRGBA[3]=255; + } + else if ( !Q_stricmp( value, "random1") ) + { + ri->customRGBA[3]=255; + switch (Q_irand(0,5)) + { + default: + case 0: + ri->customRGBA[0]=127; + ri->customRGBA[1]=153; + ri->customRGBA[2]=255; + break; + case 1: + ri->customRGBA[0]=177; + ri->customRGBA[1]=29; + ri->customRGBA[2]=13; + break; + case 2: + ri->customRGBA[0]=47; + ri->customRGBA[1]=90; + ri->customRGBA[2]=40; + break; + case 3: + ri->customRGBA[0]=181; + ri->customRGBA[1]=207; + ri->customRGBA[2]=255; + break; + case 4: + ri->customRGBA[0]=138; + ri->customRGBA[1]=83; + ri->customRGBA[2]=0; + break; + case 5: + ri->customRGBA[0]=254; + ri->customRGBA[1]=199; + ri->customRGBA[2]=14; + break; + } + } + else if ( !Q_stricmp( value, "jedi_hf" ) ) + { + ri->customRGBA[3]=255; + switch (Q_irand(0,7)) + { + default: + case 0://red1 + ri->customRGBA[0]=165; + ri->customRGBA[1]=48; + ri->customRGBA[2]=21; + break; + case 1://yellow1 + ri->customRGBA[0]=254; + ri->customRGBA[1]=230; + ri->customRGBA[2]=132; + break; + case 2://bluegray + ri->customRGBA[0]=181; + ri->customRGBA[1]=207; + ri->customRGBA[2]=255; + break; + case 3://pink + ri->customRGBA[0]=233; + ri->customRGBA[1]=183; + ri->customRGBA[2]=208; + break; + case 4://lt blue + ri->customRGBA[0]=161; + ri->customRGBA[1]=226; + ri->customRGBA[2]=240; + break; + case 5://blue + ri->customRGBA[0]=101; + ri->customRGBA[1]=159; + ri->customRGBA[2]=255; + break; + case 6://orange + ri->customRGBA[0]=255; + ri->customRGBA[1]=157; + ri->customRGBA[2]=114; + break; + case 7://violet + ri->customRGBA[0]=216; + ri->customRGBA[1]=160; + ri->customRGBA[2]=255; + break; + } + } + else if ( !Q_stricmp( value, "jedi_hm" ) ) + { + ri->customRGBA[3]=255; + switch (Q_irand(0,7)) + { + default: + case 0://yellow + ri->customRGBA[0]=252; + ri->customRGBA[1]=243; + ri->customRGBA[2]=180; + break; + case 1://blue + ri->customRGBA[0]=69; + ri->customRGBA[1]=109; + ri->customRGBA[2]=255; + break; + case 2://gold + ri->customRGBA[0]=254; + ri->customRGBA[1]=197; + ri->customRGBA[2]=73; + break; + case 3://orange + ri->customRGBA[0]=178; + ri->customRGBA[1]=78; + ri->customRGBA[2]=18; + break; + case 4://bluegreen + ri->customRGBA[0]=112; + ri->customRGBA[1]=153; + ri->customRGBA[2]=161; + break; + case 5://blue2 + ri->customRGBA[0]=123; + ri->customRGBA[1]=182; + ri->customRGBA[2]=255; + break; + case 6://green2 + ri->customRGBA[0]=0; + ri->customRGBA[1]=88; + ri->customRGBA[2]=105; + break; + case 7://violet + ri->customRGBA[0]=138; + ri->customRGBA[1]=0; + ri->customRGBA[2]=0; + break; + } + } + else if ( !Q_stricmp( value, "jedi_kdm" ) ) + { + ri->customRGBA[3]=255; + switch (Q_irand(0,8)) + { + default: + case 0://blue + ri->customRGBA[0]=85; + ri->customRGBA[1]=120; + ri->customRGBA[2]=255; + break; + case 1://violet + ri->customRGBA[0]=173; + ri->customRGBA[1]=142; + ri->customRGBA[2]=219; + break; + case 2://brown1 + ri->customRGBA[0]=254; + ri->customRGBA[1]=197; + ri->customRGBA[2]=73; + break; + case 3://orange + ri->customRGBA[0]=138; + ri->customRGBA[1]=83; + ri->customRGBA[2]=0; + break; + case 4://gold + ri->customRGBA[0]=254; + ri->customRGBA[1]=199; + ri->customRGBA[2]=14; + break; + case 5://blue2 + ri->customRGBA[0]=68; + ri->customRGBA[1]=194; + ri->customRGBA[2]=217; + break; + case 6://red1 + ri->customRGBA[0]=170; + ri->customRGBA[1]=3; + ri->customRGBA[2]=30; + break; + case 7://yellow1 + ri->customRGBA[0]=225; + ri->customRGBA[1]=226; + ri->customRGBA[2]=144; + break; + case 8://violet2 + ri->customRGBA[0]=167; + ri->customRGBA[1]=202; + ri->customRGBA[2]=255; + break; + } + } + else if ( !Q_stricmp( value, "jedi_rm" ) ) + { + ri->customRGBA[3]=255; + switch (Q_irand(0,8)) + { + default: + case 0://blue + ri->customRGBA[0]=127; + ri->customRGBA[1]=153; + ri->customRGBA[2]=255; + break; + case 1://green1 + ri->customRGBA[0]=208; + ri->customRGBA[1]=249; + ri->customRGBA[2]=85; + break; + case 2://blue2 + ri->customRGBA[0]=181; + ri->customRGBA[1]=207; + ri->customRGBA[2]=255; + break; + case 3://gold + ri->customRGBA[0]=138; + ri->customRGBA[1]=83; + ri->customRGBA[2]=0; + break; + case 4://gold + ri->customRGBA[0]=224; + ri->customRGBA[1]=171; + ri->customRGBA[2]=44; + break; + case 5://green2 + ri->customRGBA[0]=49; + ri->customRGBA[1]=155; + ri->customRGBA[2]=131; + break; + case 6://red1 + ri->customRGBA[0]=163; + ri->customRGBA[1]=79; + ri->customRGBA[2]=17; + break; + case 7://violet2 + ri->customRGBA[0]=148; + ri->customRGBA[1]=104; + ri->customRGBA[2]=228; + break; + case 8://green3 + ri->customRGBA[0]=138; + ri->customRGBA[1]=136; + ri->customRGBA[2]=0; + break; + } + } + else if ( !Q_stricmp( value, "jedi_tf" ) ) + { + ri->customRGBA[3]=255; + switch (Q_irand(0,5)) + { + default: + case 0://green1 + ri->customRGBA[0]=255; + ri->customRGBA[1]=235; + ri->customRGBA[2]=100; + break; + case 1://blue1 + ri->customRGBA[0]=62; + ri->customRGBA[1]=155; + ri->customRGBA[2]=255; + break; + case 2://red1 + ri->customRGBA[0]=255; + ri->customRGBA[1]=110; + ri->customRGBA[2]=120; + break; + case 3://purple + ri->customRGBA[0]=180; + ri->customRGBA[1]=150; + ri->customRGBA[2]=255; + break; + case 4://flesh + ri->customRGBA[0]=255; + ri->customRGBA[1]=200; + ri->customRGBA[2]=212; + break; + case 5://base + ri->customRGBA[0]=255; + ri->customRGBA[1]=255; + ri->customRGBA[2]=255; + break; + } + } + else if ( !Q_stricmp( value, "jedi_zf" ) ) + { + ri->customRGBA[3]=255; + switch (Q_irand(0,7)) + { + default: + case 0://red1 + ri->customRGBA[0]=204; + ri->customRGBA[1]=19; + ri->customRGBA[2]=21; + break; + case 1://orange1 + ri->customRGBA[0]=255; + ri->customRGBA[1]=107; + ri->customRGBA[2]=40; + break; + case 2://pink1 + ri->customRGBA[0]=255; + ri->customRGBA[1]=148; + ri->customRGBA[2]=155; + break; + case 3://gold + ri->customRGBA[0]=255; + ri->customRGBA[1]=164; + ri->customRGBA[2]=59; + break; + case 4://violet1 + ri->customRGBA[0]=216; + ri->customRGBA[1]=160; + ri->customRGBA[2]=255; + break; + case 5://blue1 + ri->customRGBA[0]=101; + ri->customRGBA[1]=159; + ri->customRGBA[2]=255; + break; + case 6://blue2 + ri->customRGBA[0]=161; + ri->customRGBA[1]=226; + ri->customRGBA[2]=240; + break; + case 7://blue3 + ri->customRGBA[0]=37; + ri->customRGBA[1]=155; + ri->customRGBA[2]=181; + break; + } + } + else + { + ri->customRGBA[0]=atoi(value); + + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + ri->customRGBA[1]=n; + + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + ri->customRGBA[2]=n; + + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + ri->customRGBA[3]=n; + } + continue; + } + + // headmodel + if ( !Q_stricmp( token, "headmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + ri->headModelName[0] = NULL; + //Zero the head clamp range so the torso & legs don't lag behind + ri->headYawRangeLeft = + ri->headYawRangeRight = + ri->headPitchRangeUp = + ri->headPitchRangeDown = 0; + } + else + { + Q_strncpyz( ri->headModelName, value, sizeof(ri->headModelName), qtrue); + } + continue; + } + + // torsomodel + if ( !Q_stricmp( token, "torsomodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + ri->torsoModelName[0] = NULL; + //Zero the torso clamp range so the legs don't lag behind + ri->torsoYawRangeLeft = + ri->torsoYawRangeRight = + ri->torsoPitchRangeUp = + ri->torsoPitchRangeDown = 0; + } + else + { + Q_strncpyz( ri->torsoModelName, value, sizeof(ri->torsoModelName), qtrue); + } + continue; + } + + // legsmodel + if ( !Q_stricmp( token, "legsmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( ri->legsModelName, value, sizeof(ri->legsModelName), qtrue); + //Need to do this here to get the right index + ci->animFileIndex = G_ParseAnimFileSet(ri->legsModelName); + continue; + } + + // playerModel + if ( !Q_stricmp( token, "playerModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( playerModel, value, sizeof(playerModel), qtrue); + md3Model = qfalse; + continue; + } + + // customSkin + if ( !Q_stricmp( token, "customSkin" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( customSkin, value, sizeof(customSkin), qtrue); + continue; + } + + // surfOff + if ( !Q_stricmp( token, "surfOff" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( surfOff[0] ) + { + strncat( (char *)surfOff, ",", sizeof(surfOff) ); + strncat( (char *)surfOff, value, sizeof(surfOff) ); + } + else + { + Q_strncpyz( surfOff, value, sizeof(surfOff), qtrue); + } + continue; + } + + // surfOn + if ( !Q_stricmp( token, "surfOn" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( surfOn[0] ) + { + strncat( (char *)surfOn, ",", sizeof(surfOn) ); + strncat( (char *)surfOn, value, sizeof(surfOn) ); + } + else + { + Q_strncpyz( surfOn, value, sizeof(surfOn), qtrue); + } + continue; + } + + //headYawRangeLeft + if ( !Q_stricmp( token, "headYawRangeLeft" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->headYawRangeLeft = n; + continue; + } + + //headYawRangeRight + if ( !Q_stricmp( token, "headYawRangeRight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->headYawRangeRight = n; + continue; + } + + //headPitchRangeUp + if ( !Q_stricmp( token, "headPitchRangeUp" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->headPitchRangeUp = n; + continue; + } + + //headPitchRangeDown + if ( !Q_stricmp( token, "headPitchRangeDown" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->headPitchRangeDown = n; + continue; + } + + //torsoYawRangeLeft + if ( !Q_stricmp( token, "torsoYawRangeLeft" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->torsoYawRangeLeft = n; + continue; + } + + //torsoYawRangeRight + if ( !Q_stricmp( token, "torsoYawRangeRight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->torsoYawRangeRight = n; + continue; + } + + //torsoPitchRangeUp + if ( !Q_stricmp( token, "torsoPitchRangeUp" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->torsoPitchRangeUp = n; + continue; + } + + //torsoPitchRangeDown + if ( !Q_stricmp( token, "torsoPitchRangeDown" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->torsoPitchRangeDown = n; + continue; + } + + // Uniform XYZ scale + if ( !Q_stricmp( token, "scale" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = n/100.0f; + } + continue; + } + + //X scale + if ( !Q_stricmp( token, "scaleX" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + NPC->s.modelScale[0] = n/100.0f; + } + continue; + } + + //Y scale + if ( !Q_stricmp( token, "scaleY" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + NPC->s.modelScale[1] = n/100.0f; + } + continue; + } + + //Z scale + if ( !Q_stricmp( token, "scaleZ" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + NPC->s.modelScale[2] = n/100.0f; + } + continue; + } + + //===AI STATS===================================================================== + if ( !parsingPlayer ) + { + // aggression + if ( !Q_stricmp( token, "aggression" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->aggression = n; + } + continue; + } + + // aim + if ( !Q_stricmp( token, "aim" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->aim = n; + } + continue; + } + + // earshot + if ( !Q_stricmp( token, "earshot" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->earshot = f; + } + continue; + } + + // evasion + if ( !Q_stricmp( token, "evasion" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->evasion = n; + } + continue; + } + + // hfov + if ( !Q_stricmp( token, "hfov" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 180 ) { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->hfov = n;// / 2; //FIXME: Why was this being done?! + } + continue; + } + + // intelligence + if ( !Q_stricmp( token, "intelligence" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->intelligence = n; + } + continue; + } + + // move + if ( !Q_stricmp( token, "move" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->move = n; + } + continue; + } + + // reactions + if ( !Q_stricmp( token, "reactions" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->reactions = n; + } + continue; + } + + // shootDistance + if ( !Q_stricmp( token, "shootDistance" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->shootDistance = f; + } + continue; + } + + // vfov + if ( !Q_stricmp( token, "vfov" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 2 || n > 360 ) { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->vfov = n / 2; + } + continue; + } + + // vigilance + if ( !Q_stricmp( token, "vigilance" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->vigilance = f; + } + continue; + } + + // visrange + if ( !Q_stricmp( token, "visrange" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->visrange = f; + if (g_entities[ENTITYNUM_WORLD].max_health && stats->visrange>g_entities[ENTITYNUM_WORLD].max_health) + { + stats->visrange = g_entities[ENTITYNUM_WORLD].max_health; + } + } + continue; + } + + // race + // if ( !Q_stricmp( token, "race" ) ) + // { + // if ( COM_ParseString( &p, &value ) ) + // { + // continue; + // } + // NPC->client->race = TranslateRaceName(value); + // continue; + // } + + // rank + if ( !Q_stricmp( token, "rank" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->NPC ) + { + NPC->NPC->rank = TranslateRankName(value); + } + continue; + } + } + + // health + if ( !Q_stricmp( token, "health" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->health = n; + } + else if ( parsingPlayer ) + { + NPC->client->ps.stats[STAT_MAX_HEALTH] = NPC->client->pers.maxHealth = NPC->max_health = n; + } + continue; + } + + // fullName + if ( !Q_stricmp( token, "fullName" ) ) + { +#ifndef FINAL_BUILD + gi.Printf( S_COLOR_YELLOW"WARNING: fullname ignored in NPC '%s'\n", NPCName ); +#endif + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + continue; + } + + // playerTeam + if ( !Q_stricmp( token, "playerTeam" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + NPC->client->playerTeam = (team_t)GetIDForString( TeamTable, value ); + continue; + } + + // enemyTeam + if ( !Q_stricmp( token, "enemyTeam" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + NPC->client->enemyTeam = (team_t)GetIDForString( TeamTable, value ); + continue; + } + + // class + if ( !Q_stricmp( token, "class" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + NPC->client->NPC_class = (class_t)GetIDForString( ClassTable, value ); + + // No md3's for vehicles. + if ( NPC->client->NPC_class == CLASS_VEHICLE ) + { + if ( !NPC->m_pVehicle ) + {//you didn't spawn this guy right! + Com_Printf ( S_COLOR_RED "ERROR: Tried to spawn a vehicle NPC (%s) without using NPC_Vehicle or 'NPC spawn vehicle '!!! Bad, bad, bad! Shame on you!\n", NPCName ); + return qfalse; + } + md3Model = qfalse; + } + + continue; + } + + // dismemberment probability for head + if ( !Q_stricmp( token, "dismemberProbHead" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + NPC->client->dismemberProbHead = n; + } + continue; + } + + // dismemberment probability for arms + if ( !Q_stricmp( token, "dismemberProbArms" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + NPC->client->dismemberProbArms = n; + } + continue; + } + + // dismemberment probability for hands + if ( !Q_stricmp( token, "dismemberProbHands" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + NPC->client->dismemberProbHands = n; + } + continue; + } + + // dismemberment probability for waist + if ( !Q_stricmp( token, "dismemberProbWaist" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + NPC->client->dismemberProbWaist = n; + } + continue; + } + + // dismemberment probability for legs + if ( !Q_stricmp( token, "dismemberProbLegs" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + NPC->client->dismemberProbLegs = n; + } + continue; + } + + //===MOVEMENT STATS============================================================ + + if ( !Q_stricmp( token, "width" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + + NPC->mins[0] = NPC->mins[1] = -n; + NPC->maxs[0] = NPC->maxs[1] = n; + continue; + } + + if ( !Q_stricmp( token, "height" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + + if ( NPC->client->NPC_class == CLASS_VEHICLE + && NPC->m_pVehicle + && NPC->m_pVehicle->m_pVehicleInfo + && NPC->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + {//a flying vehicle's origin must be centered in bbox + NPC->maxs[2] = NPC->client->standheight = (n/2.0f); + NPC->mins[2] = -NPC->maxs[2]; + NPC->s.origin[2] += (DEFAULT_MINS_2-NPC->mins[2])+0.125f; + VectorCopy(NPC->s.origin, NPC->client->ps.origin); + VectorCopy(NPC->s.origin, NPC->currentOrigin); + G_SetOrigin( NPC, NPC->s.origin ); + gi.linkentity(NPC); + } + else + { + NPC->mins[2] = DEFAULT_MINS_2;//Cannot change + NPC->maxs[2] = NPC->client->standheight = n + DEFAULT_MINS_2; + } + NPC->s.radius = n; + continue; + } + + if ( !Q_stricmp( token, "crouchheight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + + NPC->client->crouchheight = n + DEFAULT_MINS_2; + continue; + } + + if ( !parsingPlayer ) + { + if ( !Q_stricmp( token, "movetype" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + NPC->client->moveType = (movetype_t)MoveTypeNameToEnum(value); + continue; + } + + // yawSpeed + if ( !Q_stricmp( token, "yawSpeed" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n <= 0) { + gi.Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->yawSpeed = ((float)(n)); + } + continue; + } + + // walkSpeed + if ( !Q_stricmp( token, "walkSpeed" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->walkSpeed = n; + } + continue; + } + + //runSpeed + if ( !Q_stricmp( token, "runSpeed" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->runSpeed = n; + } + continue; + } + + //acceleration + if ( !Q_stricmp( token, "acceleration" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->acceleration = n; + } + continue; + } + + //sex + if ( !Q_stricmp( token, "sex" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( value[0] == 'm' ) + {//male + stats->sex = SEX_MALE; + } + else if ( value[0] == 'n' ) + {//neutral + stats->sex = SEX_NEUTRAL; + } + else if ( value[0] == 'a' ) + {//asexual? + stats->sex = SEX_NEUTRAL; + } + else if ( value[0] == 'f' ) + {//female + stats->sex = SEX_FEMALE; + } + else if ( value[0] == 's' ) + {//shemale? + stats->sex = SEX_SHEMALE; + } + else if ( value[0] == 'h' ) + {//hermaphrodite? + stats->sex = SEX_SHEMALE; + } + else if ( value[0] == 't' ) + {//transsexual/transvestite? + stats->sex = SEX_SHEMALE; + } + continue; + } +//===MISC=============================================================================== + // default behavior + if ( !Q_stricmp( token, "behavior" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < BS_DEFAULT || n >= NUM_BSTATES ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + NPC->NPC->defaultBehavior = (bState_t)(n); + } + continue; + } + } + + // snd + if ( !Q_stricmp( token, "snd" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( !(NPC->svFlags&SVF_NO_BASIC_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + ci->customBasicSoundDir = G_NewString( sound ); + } + continue; + } + + // sndcombat + if ( !Q_stricmp( token, "sndcombat" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( !(NPC->svFlags&SVF_NO_COMBAT_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + ci->customCombatSoundDir = G_NewString( sound ); + } + continue; + } + + // sndextra + if ( !Q_stricmp( token, "sndextra" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( !(NPC->svFlags&SVF_NO_EXTRA_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + ci->customExtraSoundDir = G_NewString( sound ); + } + continue; + } + + // sndjedi + if ( !Q_stricmp( token, "sndjedi" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( !(NPC->svFlags&SVF_NO_EXTRA_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + ci->customJediSoundDir = G_NewString( sound ); + } + continue; + } + + //New NPC/jedi stats: + //starting weapon + if ( !Q_stricmp( token, "weapon" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //FIXME: need to precache the weapon, too? (in above func) + int weap = GetIDForString( WPTable, value ); + if ( weap >= WP_NONE && weap < WP_NUM_WEAPONS ) + { + NPC->client->ps.weapon = weap; + NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << weap ); + if ( weap > WP_NONE ) + { + RegisterItem( FindItemForWeapon( (weapon_t)(weap) ) ); //precache the weapon + NPC->client->ps.ammo[weaponData[weap].ammoIndex] = ammoData[weaponData[weap].ammoIndex].max; + } + } + continue; + } + + if ( !parsingPlayer ) + { + //altFire + if ( !Q_stricmp( token, "altFire" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( NPC->NPC ) + { + if ( n != 0 ) + { + NPC->NPC->scriptFlags |= SCF_ALT_FIRE; + } + } + continue; + } + //Other unique behaviors/numbers that are currently hardcoded? + } + + //force powers + int fp = GetIDForString( FPTable, token ); + if ( fp >= FP_FIRST && fp < NUM_FORCE_POWERS ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + //FIXME: need to precache the fx, too? (in above func) + //cap + if ( n > 5 ) + { + n = 5; + } + else if ( n < 0 ) + { + n = 0; + } + if ( n ) + {//set + NPC->client->ps.forcePowersKnown |= ( 1 << fp ); + } + else + {//clear + NPC->client->ps.forcePowersKnown &= ~( 1 << fp ); + } + NPC->client->ps.forcePowerLevel[fp] = n; + continue; + } + + //max force power + if ( !Q_stricmp( token, "forcePowerMax" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + NPC->client->ps.forcePowerMax = n; + continue; + } + + //force regen rate - default is 100ms + if ( !Q_stricmp( token, "forceRegenRate" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + NPC->client->ps.forcePowerRegenRate = n; + continue; + } + + //force regen amount - default is 1 (points per second) + if ( !Q_stricmp( token, "forceRegenAmount" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + NPC->client->ps.forcePowerRegenAmount = n; + continue; + } + + //have a sabers.cfg and just name your saber in your NPCs.cfg/ICARUS script + //saber name + if ( !Q_stricmp( token, "saber" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + char *saberName = G_NewString( value ); + WP_SaberParseParms( saberName, &NPC->client->ps.saber[0] ); + //if it requires a specific style, make sure we know how to use it + if ( NPC->client->ps.saber[0].style ) + { + NPC->client->ps.saberStylesKnown |= (1<client->ps.saber[0].style); + } + continue; + } + + //second saber name + if ( !Q_stricmp( token, "saber2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if ( !NPC->client->ps.saber[0].twoHanded ) + {//can't use a second saber if first one is a two-handed saber...? + char *saberName = G_NewString( value ); + WP_SaberParseParms( saberName, &NPC->client->ps.saber[1] ); + if ( NPC->client->ps.saber[1].style ) + { + NPC->client->ps.saberStylesKnown |= (1<client->ps.saber[1].style); + } + if ( NPC->client->ps.saber[1].twoHanded ) + {//tsk tsk, can't use a twoHanded saber as second saber + WP_RemoveSaber( NPC, 1 ); + } + else + { + NPC->client->ps.dualSabers = qtrue; + } + } + continue; + } + + // saberColor + if ( !Q_stricmpn( token, "saberColor", 10) ) + { + if ( !NPC->client ) + { + continue; + } + + if (strlen(token)==10) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber_colors_t color = TranslateSaberColor( value ); + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->ps.saber[0].blade[n].color = color; + } + } + else if (strlen(token)==11) + { + int index = atoi(&token[10])-1; + if (index > 7 || index < 1 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberColor '%s' in %s\n", token, NPCName ); + continue; + } + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + NPC->client->ps.saber[0].blade[index].color = TranslateSaberColor( value ); + } + else + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberColor '%s' in %s\n", token, NPCName ); + } + continue; + } + + + if ( !Q_stricmpn( token, "saber2Color", 11 ) ) + { + if ( !NPC->client ) + { + continue; + } + + if (strlen(token)==11) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber_colors_t color = TranslateSaberColor( value ); + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->ps.saber[1].blade[n].color = color; + } + } + else if (strlen(token)==12) + { + n = atoi(&token[11])-1; + if (n > 7 || n < 1 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Color '%s' in %s\n", token, NPCName ); + continue; + } + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + NPC->client->ps.saber[1].blade[n].color = TranslateSaberColor( value ); + } + else + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Color '%s' in %s\n", token, NPCName ); + } + continue; + } + + + //saber length + if ( !Q_stricmpn( token, "saberLength", 11) ) + { + if (strlen(token)==11) + { + n = -1; + } + else if (strlen(token)==12) + { + n = atoi(&token[11])-1; + if (n > 7 || n < 1 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberLength '%s' in %s\n", token, NPCName ); + continue; + } + } + else + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberLength '%s' in %s\n", token, NPCName ); + continue; + } + + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + + if (n == -1)//do them all + { + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->ps.saber[0].blade[n].lengthMax = f; + } + } + else //just one + { + NPC->client->ps.saber[0].blade[n].lengthMax = f; + } + continue; + } + + if ( !Q_stricmpn( token, "saber2Length", 12) ) + { + if (strlen(token)==12) + { + n = -1; + } + else if (strlen(token)==13) + { + n = atoi(&token[12])-1; + if (n > 7 || n < 1 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Length '%s' in %s\n", token, NPCName ); + continue; + } + } + else + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Length '%s' in %s\n", token, NPCName ); + continue; + } + + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + + if (n == -1)//do them all + { + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->ps.saber[1].blade[n].lengthMax = f; + } + } + else //just one + { + NPC->client->ps.saber[1].blade[n].lengthMax = f; + } + continue; + } + + + //saber radius + if ( !Q_stricmpn( token, "saberRadius", 11 ) ) + { + if (strlen(token)==11) + { + n = -1; + } + else if (strlen(token)==12) + { + n = atoi(&token[11])-1; + if (n > 7 || n < 1 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberRadius '%s' in %s\n", token, NPCName ); + continue; + } + } + else + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberRadius '%s' in %s\n", token, NPCName ); + continue; + } + + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same length by default + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->ps.saber[0].blade[n].radius = f; + } + } + else + { + NPC->client->ps.saber[0].blade[n].radius = f; + } + continue; + } + + + if ( !Q_stricmpn( token, "saber2Radius", 12 ) ) + { + if (strlen(token)==12) + { + n = -1; + } + else if (strlen(token)==13) + { + n = atoi(&token[12])-1; + if (n > 7 || n < 1 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Radius '%s' in %s\n", token, NPCName ); + continue; + } + } + else + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Radius '%s' in %s\n", token, NPCName ); + continue; + } + + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same length by default + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->ps.saber[1].blade[n].radius = f; + } + } + else + { + NPC->client->ps.saber[1].blade[n].radius = f; + } + continue; + } + + //ADD: + //saber sounds (on, off, loop) + //loop sound (like Vader's breathing or droid bleeps, etc.) + + //starting saber style + if ( !Q_stricmp( token, "saberStyle" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( n > SS_STAFF ) + { + n = SS_STAFF; + } + else if ( n < SS_FAST ) + { + n = SS_FAST; + } + if ( n ) + {//set + NPC->client->ps.saberStylesKnown |= ( 1 << n ); + } + else + {//clear + NPC->client->ps.saberStylesKnown &= ~( 1 << n ); + } + NPC->client->ps.saberAnimLevel = n; + if ( parsingPlayer ) + { + cg.saberAnimLevelPending = n; + } + continue; + } + + if ( !parsingPlayer ) + { + gi.Printf( "WARNING: unknown keyword '%s' while parsing '%s'\n", token, NPCName ); + } + SkipRestOfLine( &p ); + } + } + + ci->infoValid = qfalse; + +/* +Ghoul2 Insert Start +*/ + if ( !md3Model ) + { + NPC->weaponModel[0] = -1; + if ( Q_stricmp( "player", playerModel ) == 0 ) + {//set the model from the console cvars + G_InitPlayerFromCvars( NPC ); + //now set the weapon, etc. + G_MatchPlayerWeapon( NPC ); + //NPC->NPC->aiFlags |= NPCAI_MATCHPLAYERWEAPON;//FIXME: may not always want this + } + else + {//do a normal model load + + // If this is a vehicle, set the model name from the vehicle type array. + if ( NPC->client->NPC_class == CLASS_VEHICLE ) + { + int iVehIndex = BG_VehicleGetIndex( NPC->NPC_type ); + strcpy(customSkin, "default"); // Ignore any custom skin that may have come from the NPC File + Q_strncpyz( playerModel, g_vehicleInfo[iVehIndex].model, sizeof(playerModel), qtrue); + if ( g_vehicleInfo[iVehIndex].skin && g_vehicleInfo[iVehIndex].skin[0] ) + { + bool forceSkin = false; + + // Iterate Over All Possible Skins + //--------------------------------- + ratl::vector_vs skinarray; + ratl::string_vs<256> skins(g_vehicleInfo[iVehIndex].skin); + for (ratl::string_vs<256>::tokenizer i = skins.begin("|"); i!=skins.end(); i++) + { + if (NPC->soundSet && NPC->soundSet[0] && Q_stricmp(*i, NPC->soundSet)==0) + { + forceSkin = true; + } + skinarray.push_back(*i); + } + + // Soundset Is The Designer Set Way To Supply A Skin + //--------------------------------------------------- + if (forceSkin) + { + Q_strncpyz( customSkin, NPC->soundSet, sizeof(customSkin), qtrue); + } + + // Otherwise Choose A Random Skin + //-------------------------------- + else + { + if (NPC->soundSet && NPC->soundSet[0]) + { + gi.Printf(S_COLOR_RED"WARNING: Unable to use skin (%s)", NPC->soundSet); + } + Q_strncpyz( customSkin, *skinarray[Q_irand(0, skinarray.size()-1)], sizeof(customSkin), qtrue); + } + if (NPC->soundSet && gi.bIsFromZone(NPC->soundSet, TAG_G_ALLOC)) { + gi.Free(NPC->soundSet); + } + NPC->soundSet = 0; // clear the pointer + } + } + + G_SetG2PlayerModel( NPC, playerModel, customSkin, surfOff, surfOn ); + } + } +/* +Ghoul2 Insert End +*/ + if( NPCsPrecached ) + {//Spawning in after initial precache, our models are precached, we just need to set our clientInfo + CG_RegisterClientModels( NPC->s.number ); + CG_RegisterNPCCustomSounds( ci ); + //CG_RegisterNPCEffects( NPC->client->playerTeam ); + } + + return qtrue; +} + +void NPC_LoadParms( void ) +{ + int len, totallen, npcExtFNLen, fileCnt, i; + char *buffer, *holdChar, *marker; + char npcExtensionListBuf[2048]; // The list of file names read in + + //gi.Printf( "Parsing ext_data/npcs/*.npc definitions\n" ); + + //set where to store the first one + totallen = 0; + marker = NPCParms; + marker[0] = '\0'; + + //now load in the .npc definitions + fileCnt = gi.FS_GetFileList("ext_data/npcs", ".npc", npcExtensionListBuf, sizeof(npcExtensionListBuf) ); + + holdChar = npcExtensionListBuf; + for ( i = 0; i < fileCnt; i++, holdChar += npcExtFNLen + 1 ) + { + npcExtFNLen = strlen( holdChar ); + + //gi.Printf( "Parsing %s\n", holdChar ); + + len = gi.FS_ReadFile( va( "ext_data/npcs/%s", holdChar), (void **) &buffer ); + + if ( len == -1 ) + { + gi.Printf( "NPC_LoadParms: error reading file %s\n", holdChar ); + } + else + { + if ( totallen && *(marker-1) == '}' ) + {//don't let previous file end on a } because that must be a stand-alone token + strcat( marker, " " ); + totallen++; + marker++; + } + len = COM_Compress( buffer ); + + if ( totallen + len >= MAX_NPC_DATA_SIZE ) { + G_Error( "NPC_LoadParms: ran out of space before reading %s\n(you must make the .npc files smaller)", holdChar ); + } + strcat( marker, buffer ); + gi.FS_FreeFile( buffer ); + + totallen += len; + marker += len; + } + } +} diff --git a/code/game/NPC_utils.cpp b/code/game/NPC_utils.cpp new file mode 100644 index 0000000..18dc3ee --- /dev/null +++ b/code/game/NPC_utils.cpp @@ -0,0 +1,1668 @@ +//NPC_utils.cpp + +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" + + + + +#include "b_local.h" +#include "Q3_Interface.h" + +extern Vehicle_t *G_IsRidingVehicle( gentity_t *pEnt ); + +int teamNumbers[TEAM_NUM_TEAMS]; +int teamStrength[TEAM_NUM_TEAMS]; +int teamCounter[TEAM_NUM_TEAMS]; + +#define VALID_ATTACK_CONE 2.0f //Degrees +void GetAnglesForDirection( const vec3_t p1, const vec3_t p2, vec3_t out ); + +/* +void CalcEntitySpot ( gentity_t *ent, spot_t spot, vec3_t point ) + +Added: Uses shootAngles if a NPC has them + +*/ +extern void ViewHeightFix(const gentity_t *const ent); +extern void AddLeanOfs(const gentity_t *const ent, vec3_t point); +extern void SubtractLeanOfs(const gentity_t *const ent, vec3_t point); +void CalcEntitySpot ( const gentity_t *ent, const spot_t spot, vec3_t point ) +{ + vec3_t forward, up, right; + vec3_t start, end; + trace_t tr; + + if ( !ent ) + { + return; + } + ViewHeightFix(ent); + switch ( spot ) + { + case SPOT_ORIGIN: + if(VectorCompare(ent->currentOrigin, vec3_origin)) + {//brush + VectorSubtract(ent->absmax, ent->absmin, point);//size + VectorMA(ent->absmin, 0.5, point, point); + } + else + { + VectorCopy ( ent->currentOrigin, point ); + } + break; + + case SPOT_CHEST: + case SPOT_HEAD: + if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) && (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD) ) + {//Actual tag_head eyespot! + //FIXME: Stasis aliens may have a problem here... + VectorCopy( ent->client->renderInfo.eyePoint, point ); + if ( ent->client->NPC_class == CLASS_ATST ) + {//adjust up some + point[2] += 28;//magic number :) + } + if ( ent->NPC ) + {//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards + point[0] = ent->currentOrigin[0]; + point[1] = ent->currentOrigin[1]; + } + else if ( !ent->s.number ) + { + SubtractLeanOfs( ent, point ); + } + } + else + { + VectorCopy ( ent->currentOrigin, point ); + if ( ent->client ) + { + point[2] += ent->client->ps.viewheight; + } + } + if ( spot == SPOT_CHEST && ent->client ) + { + if ( ent->client->NPC_class != CLASS_ATST ) + {//adjust up some + point[2] -= ent->maxs[2]*0.2f; + } + } + break; + + case SPOT_HEAD_LEAN: + if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) && (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD) ) + {//Actual tag_head eyespot! + //FIXME: Stasis aliens may have a problem here... + VectorCopy( ent->client->renderInfo.eyePoint, point ); + if ( ent->client->NPC_class == CLASS_ATST ) + {//adjust up some + point[2] += 28;//magic number :) + } + if ( ent->NPC ) + {//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards + point[0] = ent->currentOrigin[0]; + point[1] = ent->currentOrigin[1]; + } + else if ( !ent->s.number ) + { + SubtractLeanOfs( ent, point ); + } + //NOTE: automatically takes leaning into account! + } + else + { + VectorCopy ( ent->currentOrigin, point ); + if ( ent->client ) + { + point[2] += ent->client->ps.viewheight; + } + //AddLeanOfs ( ent, point ); + } + break; + + //FIXME: implement... + //case SPOT_CHEST: + //Returns point 3/4 from tag_torso to tag_head? + //break; + + case SPOT_LEGS: + VectorCopy ( ent->currentOrigin, point ); + point[2] += (ent->mins[2] * 0.5); + break; + + case SPOT_WEAPON: + if( ent->NPC && !VectorCompare( ent->NPC->shootAngles, vec3_origin ) && !VectorCompare( ent->NPC->shootAngles, ent->client->ps.viewangles )) + { + AngleVectors( ent->NPC->shootAngles, forward, right, up ); + } + else + { + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + } + CalcMuzzlePoint( (gentity_t*)ent, forward, right, up, point, 0 ); + //NOTE: automatically takes leaning into account! + break; + + case SPOT_GROUND: + // if entity is on the ground, just use it's absmin + if ( ent->s.groundEntityNum != -1 ) + { + VectorCopy( ent->currentOrigin, point ); + point[2] = ent->absmin[2]; + break; + } + + // if it is reasonably close to the ground, give the point underneath of it + VectorCopy( ent->currentOrigin, start ); + start[2] = ent->absmin[2]; + VectorCopy( start, end ); + end[2] -= 64; + gi.trace( &tr, start, ent->mins, ent->maxs, end, ent->s.number, MASK_PLAYERSOLID ); + if ( tr.fraction < 1.0 ) + { + VectorCopy( tr.endpos, point); + break; + } + + // otherwise just use the origin + VectorCopy( ent->currentOrigin, point ); + break; + + default: + VectorCopy ( ent->currentOrigin, point ); + break; + } +} + + +//=================================================================================== + +/* +qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) + +Added: option to do just pitch or just yaw + +Does not include "aim" in it's calculations + +FIXME: stop compressing angles into shorts!!!! +*/ +extern cvar_t *g_timescale; +extern bool NPC_IsTrooper( gentity_t *ent ); +qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) +{ +#if 1 + + float error; + float decay; + float targetPitch = 0; + float targetYaw = 0; + float yawSpeed; + qboolean exact = qtrue; + + // if angle changes are locked; just keep the current angles + // aimTime isn't even set anymore... so this code was never reached, but I need a way to lock NPC's yaw, so instead of making a new SCF_ flag, just use the existing render flag... - dmv + if ( !NPC->enemy && ( (level.time < NPCInfo->aimTime) || NPC->client->renderInfo.renderFlags & RF_LOCKEDANGLE) ) + { + if(doPitch) + targetPitch = NPCInfo->lockedDesiredPitch; + + if(doYaw) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + // we're changing the lockedDesired Pitch/Yaw below so it's lost it's original meaning, get rid of the lock flag + NPC->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE; + + if(doPitch) + { + targetPitch = NPCInfo->desiredPitch; + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + } + + if(doYaw) + { + targetYaw = NPCInfo->desiredYaw; + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + } + + if ( NPC->s.weapon == WP_EMPLACED_GUN ) + { + // FIXME: this seems to do nothing, actually... + yawSpeed = 20; + } + else + { + if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER + && !NPC->enemy ) + {//just slowly lookin' around + yawSpeed = 1; + } + else + { + yawSpeed = NPCInfo->stats.yawSpeed; + } + } + + if ( NPC->s.weapon == WP_SABER && NPC->client->ps.forcePowersActive&(1<value; + } + + if (!NPC_IsTrooper(NPC) + && NPC->enemy + && !G_IsRidingVehicle( NPC ) + && NPC->client->NPC_class != CLASS_VEHICLE ) + { + if (NPC->s.weapon==WP_BLASTER_PISTOL || + NPC->s.weapon==WP_BLASTER || + NPC->s.weapon==WP_BOWCASTER || + NPC->s.weapon==WP_REPEATER || + NPC->s.weapon==WP_FLECHETTE || + NPC->s.weapon==WP_BRYAR_PISTOL || + NPC->s.weapon==WP_NOGHRI_STICK) + { + yawSpeed *= 10.0f; + } + } + + if( doYaw ) + { + // decay yaw error + error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); + if( fabs(error) > MIN_ANGLE_ERROR ) + { + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0f / 1000.0f;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; + } + + //FIXME: have a pitchSpeed? + if( doPitch ) + { + // decay pitch error + error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + if ( fabs(error) > MIN_ANGLE_ERROR ) + { + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0f / 1000.0f;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + if ( exact && Q3_TaskIDPending( NPC, TID_ANGLE_FACE ) ) + { + Q3_TaskIDComplete( NPC, TID_ANGLE_FACE ); + } + return exact; + +#else + + float error; + float decay; + float targetPitch = 0; + float targetYaw = 0; + float yawSpeed; + //float runningMod = NPCInfo->currentSpeed/100.0f; + qboolean exact = qtrue; + qboolean doSound = qfalse; + + // if angle changes are locked; just keep the current angles + if ( level.time < NPCInfo->aimTime ) + { + if(doPitch) + targetPitch = NPCInfo->lockedDesiredPitch; + if(doYaw) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + if(doPitch) + targetPitch = NPCInfo->desiredPitch; + if(doYaw) + targetYaw = NPCInfo->desiredYaw; + +// NPCInfo->aimTime = level.time + 250; + if(doPitch) + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + if(doYaw) + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + + yawSpeed = NPCInfo->stats.yawSpeed; + + if(doYaw) + { + // decay yaw error + error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); + if( fabs(error) > MIN_ANGLE_ERROR ) + { + /* + if(NPC->client->playerTeam == TEAM_BORG&& + NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) + {//HACK - borg turn more jittery + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + //Snap to + if(fabs(error) > 10) + { + if(random() > 0.6) + { + doSound = qtrue; + } + } + + if ( error < 0.0)//-10.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else if ( error > 0.0)//10.0 ) + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + else*/ + + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; + } + + //FIXME: have a pitchSpeed? + if(doPitch) + { + // decay pitch error + error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + if ( fabs(error) > MIN_ANGLE_ERROR ) + { + /* + if(NPC->client->playerTeam == TEAM_BORG&& + NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) + {//HACK - borg turn more jittery + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + //Snap to + if(fabs(error) > 10) + { + if(random() > 0.6) + { + doSound = qtrue; + } + } + + if ( error < 0.0)//-10.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else if ( error > 0.0)//10.0 ) + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + else*/ + + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + /* + if(doSound) + { + G_Sound(NPC, G_SoundIndex(va("sound/enemies/borg/borgservo%d.wav", Q_irand(1, 8)))); + } + */ + + return exact; + +#endif + +} + +void NPC_AimWiggle( vec3_t enemy_org ) +{ + //shoot for somewhere between the head and torso + //NOTE: yes, I know this looks weird, but it works + if ( NPCInfo->aimErrorDebounceTime < level.time ) + { + NPCInfo->aimOfs[0] = 0.3*Q_flrand(NPC->enemy->mins[0], NPC->enemy->maxs[0]); + NPCInfo->aimOfs[1] = 0.3*Q_flrand(NPC->enemy->mins[1], NPC->enemy->maxs[1]); + if ( NPC->enemy->maxs[2] > 0 ) + { + NPCInfo->aimOfs[2] = NPC->enemy->maxs[2]*Q_flrand(0.0f, -1.0f); + } + } + VectorAdd( enemy_org, NPCInfo->aimOfs, enemy_org ); +} + +/* +qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) + + Includes aim when determining angles - so they don't always hit... + */ +qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) +{ + +#if 0 + + float diff; + float error; + float targetPitch = 0; + float targetYaw = 0; + qboolean exact = qtrue; + + if ( level.time < NPCInfo->aimTime ) + { + if( doPitch ) + targetPitch = NPCInfo->lockedDesiredPitch; + + if( doYaw ) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + if( doPitch ) + { + targetPitch = NPCInfo->desiredPitch; + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + } + + if( doYaw ) + { + targetYaw = NPCInfo->desiredYaw; + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + } + + if( doYaw ) + { + // add yaw error based on NPCInfo->aim value + error = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1); + + if(Q_irand(0, 1)) + error *= -1; + + diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); + + if ( diff ) + exact = qfalse; + + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; + } + + if( doPitch ) + { + // add pitch error based on NPCInfo->aim value + error = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1); + + diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + + if ( diff ) + exact = qfalse; + + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + return exact; + +#else + + float error, diff; + float decay; + float targetPitch = 0; + float targetYaw = 0; + qboolean exact = qtrue; + + // if angle changes are locked; just keep the current angles + if ( level.time < NPCInfo->aimTime ) + { + if(doPitch) + targetPitch = NPCInfo->lockedDesiredPitch; + if(doYaw) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + if(doPitch) + targetPitch = NPCInfo->desiredPitch; + if(doYaw) + targetYaw = NPCInfo->desiredYaw; + +// NPCInfo->aimTime = level.time + 250; + if(doPitch) + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + if(doYaw) + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + + if ( NPCInfo->aimErrorDebounceTime < level.time ) + { + if ( Q_irand(0, 1 ) ) + { + NPCInfo->lastAimErrorYaw = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1); + } + if ( Q_irand(0, 1 ) ) + { + NPCInfo->lastAimErrorPitch = ((float)(6 - NPCInfo->stats.aim)) * Q_flrand(-1, 1); + } + NPCInfo->aimErrorDebounceTime = level.time + Q_irand(250, 2000); + } + + if(doYaw) + { + // decay yaw diff + diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); + + if ( diff) + { + exact = qfalse; + + decay = 60.0 + 80.0; + decay *= 50.0f / 1000.0f;//msec + if ( diff < 0.0 ) + { + diff += decay; + if ( diff > 0.0 ) + { + diff = 0.0; + } + } + else + { + diff -= decay; + if ( diff < 0.0 ) + { + diff = 0.0; + } + } + } + + // add yaw error based on NPCInfo->aim value + error = NPCInfo->lastAimErrorYaw; + + /* + if(Q_irand(0, 1)) + { + error *= -1; + } + */ + + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; + } + + if(doPitch) + { + // decay pitch diff + diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + if ( diff) + { + exact = qfalse; + + decay = 60.0 + 80.0; + decay *= 50.0f / 1000.0f;//msec + if ( diff < 0.0 ) + { + diff += decay; + if ( diff > 0.0 ) + { + diff = 0.0; + } + } + else + { + diff -= decay; + if ( diff < 0.0 ) + { + diff = 0.0; + } + } + } + + error = NPCInfo->lastAimErrorPitch; + + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + return exact; + +#endif + +} +//=================================================================================== + +/* +static void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) + +Does update angles on shootAngles +*/ + +void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) +{//FIXME: shoot angles either not set right or not used! + float error; + float decay; + float targetPitch = 0; + float targetYaw = 0; + + if(doPitch) + targetPitch = angles[PITCH]; + if(doYaw) + targetYaw = angles[YAW]; + + + if(doYaw) + { + // decay yaw error + error = AngleDelta ( NPCInfo->shootAngles[YAW], targetYaw ); + if ( error ) + { + decay = 60.0 + 80.0 * NPCInfo->stats.aim; + decay *= 100.0f / 1000.0f;//msec + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + NPCInfo->shootAngles[YAW] = targetYaw + error; + } + + if(doPitch) + { + // decay pitch error + error = AngleDelta ( NPCInfo->shootAngles[PITCH], targetPitch ); + if ( error ) + { + decay = 60.0 + 80.0 * NPCInfo->stats.aim; + decay *= 100.0f / 1000.0f;//msec + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + NPCInfo->shootAngles[PITCH] = targetPitch + error; + } +} + +/* +void SetTeamNumbers (void) + +Sets the number of living clients on each team + +FIXME: Does not account for non-respawned players! +FIXME: Don't include medics? +*/ +void SetTeamNumbers (void) +{ + gentity_t *found; + int i; + + for( i = 0; i < TEAM_NUM_TEAMS; i++ ) + { + teamNumbers[i] = 0; + teamStrength[i] = 0; + } + + for( i = 0; i < 1 ; i++ ) + { + found = &g_entities[i]; + + if( found->client ) + { + if( found->health > 0 )//FIXME: or if a player! + { + teamNumbers[found->client->playerTeam]++; + teamStrength[found->client->playerTeam] += found->health; + } + } + } + + for( i = 0; i < TEAM_NUM_TEAMS; i++ ) + {//Get the average health + teamStrength[i] = floor( ((float)(teamStrength[i])) / ((float)(teamNumbers[i])) ); + } +} + +extern stringID_table_t BSTable[]; +extern stringID_table_t BSETTable[]; +qboolean G_ActivateBehavior (gentity_t *self, int bset ) +{ + bState_t bSID = (bState_t)-1; + char *bs_name = NULL; + + if ( !self ) + { + return qfalse; + } + + bs_name = self->behaviorSet[bset]; + + if( !(VALIDSTRING( bs_name )) ) + { + return qfalse; + } + + if ( self->NPC ) + { + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + } + + if(bSID > -1) + { + self->NPC->tempBehavior = BS_DEFAULT; + self->NPC->behaviorState = bSID; + if ( bSID == BS_SEARCH || bSID == BS_WANDER ) + { + //FIXME: Reimplement? + if( self->waypoint != WAYPOINT_NONE ) + { + NPC_BSSearchStart( self->waypoint, bSID ); + } + else + { + self->waypoint = NAV::GetNearestNode(self); + if( self->waypoint != WAYPOINT_NONE ) + { + NPC_BSSearchStart( self->waypoint, bSID ); + } + } + } + } + else + { + Quake3Game()->DebugPrint( IGameInterface::WL_VERBOSE, "%s attempting to run bSet %s (%s)\n", self->targetname, GetStringForID( BSETTable, bset ), bs_name ); + Quake3Game()->RunScript( self, bs_name ); + } + return qtrue; +} + + +/* +============================================================================= + + Extended Functions + +============================================================================= +*/ + +/* +------------------------- +NPC_ValidEnemy +------------------------- +*/ + +qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy ) +{ + //Must be a valid pointer + if ( enemy == NULL ) + return qfalse; + + //Must not be me + if ( enemy == self ) + return qfalse; + + //Must not be deleted + if ( enemy->inuse == qfalse ) + return qfalse; + + //Must be alive + if ( enemy->health <= 0 ) + return qfalse; + + //In case they're in notarget mode + if ( enemy->flags & FL_NOTARGET ) + return qfalse; + + //Must be an NPC + if ( enemy->client == NULL ) + { + if ( enemy->svFlags&SVF_NONNPC_ENEMY ) + {//still potentially valid + if (self->client) + { + if ( enemy->noDamageTeam == self->client->playerTeam ) + { + return qfalse; + } + else + { + return qtrue; + } + } + else + { + if ( enemy->noDamageTeam == self->noDamageTeam ) + { + return qfalse; + } + else + { + return qtrue; + } + } + } + else + { + return qfalse; + } + } + + if ( enemy->client->playerTeam == TEAM_FREE && enemy->s.number < MAX_CLIENTS ) + {//An evil player, everyone attacks him + return qtrue; + } + + //Can't be on the same team + if ( enemy->client->playerTeam == self->client->playerTeam ) + { + return qfalse; + } + + //if haven't seen him in a while, give up + //if ( NPCInfo->enemyLastSeenTime != 0 && level.time - NPCInfo->enemyLastSeenTime > 7000 )//FIXME: make a stat? + // return qfalse; + + if ( enemy->client->playerTeam == self->client->enemyTeam //simplest case: they're on my enemy team + || (self->client->enemyTeam == TEAM_FREE && enemy->client->NPC_class != self->client->NPC_class )//I get mad at anyone and this guy isn't the same class as me + || (enemy->client->NPC_class == CLASS_WAMPA && enemy->enemy )//a rampaging wampa + || (enemy->client->NPC_class == CLASS_RANCOR && enemy->enemy )//a rampaging rancor + || (enemy->client->playerTeam == TEAM_FREE && enemy->client->enemyTeam == TEAM_FREE && enemy->enemy && enemy->enemy->client && (enemy->enemy->client->playerTeam == self->client->playerTeam||(enemy->enemy->client->playerTeam != TEAM_ENEMY&&self->client->playerTeam==TEAM_PLAYER))) //enemy is a rampaging non-aligned creature who is attacking someone on our team or a non-enemy (this last condition is used only if we're a good guy - in effect, we protect the innocent) + ) + { + return qtrue; + } + //all other cases = false? + return qfalse; +} + +qboolean NPC_ValidEnemy( gentity_t *ent ) +{ + return G_ValidEnemy( NPC, ent ); +} + +/* +------------------------- +NPC_TargetVisible +------------------------- +*/ + +qboolean NPC_TargetVisible( gentity_t *ent ) +{ + //Make sure we're in a valid range + if ( DistanceSquared( ent->currentOrigin, NPC->currentOrigin ) > ( NPCInfo->stats.visrange * NPCInfo->stats.visrange ) ) + return qfalse; + + //Check our FOV + if ( InFOV( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) + return qfalse; + + //Check for sight + if ( NPC_ClearLOS( ent ) == qfalse ) + return qfalse; + + return qtrue; +} + +/* +------------------------- +NPC_GetCheckDelta +------------------------- +*/ + +/* +#define CHECK_TIME_BASE 250 +#define CHECK_TIME_BASE_SQUARED ( CHECK_TIME_BASE * CHECK_TIME_BASE ) + +static int NPC_GetCheckDelta( void ) +{ + if ( NPC_ValidEnemy( NPC->enemy ) == qfalse ) + { + int distance = DistanceSquared( NPC->currentOrigin, g_entities[0].currentOrigin ); + + distance /= CHECK_TIME_BASE_SQUARED; + + return ( CHECK_TIME_BASE * distance ); + } + + return 0; +} +*/ + +/* +------------------------- +NPC_FindNearestEnemy +------------------------- +*/ + +#define MAX_RADIUS_ENTS 256 //NOTE: This can cause entities to be lost +#define NEAR_DEFAULT_RADIUS 256 +extern gentity_t *G_CheckControlledTurretEnemy(gentity_t *self, gentity_t *enemy, qboolean validate ); + +int NPC_FindNearestEnemy( gentity_t *ent ) +{ + gentity_t *radiusEnts[ MAX_RADIUS_ENTS ]; + gentity_t *nearest; + vec3_t mins, maxs; + int nearestEntID = -1; + float nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE; + float distance; + int numEnts, numChecks = 0; + + //Setup the bbox to search in + for ( int i = 0; i < 3; i++ ) + { + mins[i] = ent->currentOrigin[i] - NPCInfo->stats.visrange; + maxs[i] = ent->currentOrigin[i] + NPCInfo->stats.visrange; + } + + //Get a number of entities in a given space + numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS ); + + for ( i = 0; i < numEnts; i++ ) + { + nearest = G_CheckControlledTurretEnemy(ent, radiusEnts[i], qtrue); + + //Don't consider self + if ( nearest == ent ) + continue; + + //Must be valid + if ( NPC_ValidEnemy( nearest ) == qfalse ) + continue; + + numChecks++; + //Must be visible + if ( NPC_TargetVisible( nearest ) == qfalse ) + continue; + + distance = DistanceSquared( ent->currentOrigin, nearest->currentOrigin ); + + //Found one closer to us + if ( distance < nearestDist ) + { + nearestEntID = nearest->s.number; + nearestDist = distance; + } + } + + return nearestEntID; +} + +/* +------------------------- +NPC_PickEnemyExt +------------------------- +*/ + +gentity_t *NPC_PickEnemyExt( qboolean checkAlerts = qfalse ) +{ + //Check for Hazard Team status and remove this check + /* + if ( NPC->client->playerTeam != TEAM_STARFLEET ) + { + //If we've found the player, return it + if ( NPC_FindPlayer() ) + return &g_entities[0]; + } + */ + + //If we've asked for the closest enemy + int entID = NPC_FindNearestEnemy( NPC ); + + //If we have a valid enemy, use it + if ( entID >= 0 ) + return &g_entities[entID]; + + if ( checkAlerts ) + { + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + alertEvent_t *event = &level.alertEvents[alertEvent]; + + //Don't pay attention to our own alerts + if ( event->owner == NPC ) + return NULL; + + if ( event->level >= AEL_DISCOVERED ) + { + //If it's the player, attack him + if ( event->owner == &g_entities[0] ) + return event->owner; + + //If it's on our team, then take its enemy as well + if ( ( event->owner->client ) && ( event->owner->client->playerTeam == NPC->client->playerTeam ) ) + return event->owner->enemy; + } + } + } + + return NULL; +} + +/* +------------------------- +NPC_FindPlayer +------------------------- +*/ + +qboolean NPC_FindPlayer( void ) +{ + return NPC_TargetVisible( &g_entities[0] ); +} + +/* +------------------------- +NPC_CheckPlayerDistance +------------------------- +*/ + +static qboolean NPC_CheckPlayerDistance( void ) +{ + //Make sure we have an enemy + if ( NPC->enemy == NULL ) + return qfalse; + + //Only do this for non-players + if ( NPC->enemy->s.number == 0 ) + return qfalse; + + //must be set up to get mad at player + if ( !NPC->client || NPC->client->enemyTeam != TEAM_PLAYER ) + return qfalse; + + //Must be within our FOV + if ( InFOV( &g_entities[0], NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) + return qfalse; + + float distance = DistanceSquared( NPC->currentOrigin, NPC->enemy->currentOrigin ); + + if ( distance > DistanceSquared( NPC->currentOrigin, g_entities[0].currentOrigin ) ) + { + G_SetEnemy( NPC, &g_entities[0] ); + return qtrue; + } + + return qfalse; +} + +/* +------------------------- +NPC_FindEnemy +------------------------- +*/ + +qboolean NPC_FindEnemy( qboolean checkAlerts = qfalse ) +{ + //We're ignoring all enemies for now + if( NPC->svFlags & SVF_IGNORE_ENEMIES ) + { + G_ClearEnemy( NPC ); + return qfalse; + } + + //we can't pick up any enemies for now + if( NPCInfo->confusionTime > level.time ) + { + G_ClearEnemy( NPC ); + return qfalse; + } + + //Don't want a new enemy + if ( ( NPC_ValidEnemy( NPC->enemy ) ) && ( NPC->svFlags & SVF_LOCKEDENEMY ) ) + return qtrue; + + //See if the player is closer than our current enemy + if ( NPC->client->NPC_class != CLASS_RANCOR + && NPC->client->NPC_class != CLASS_WAMPA + && NPC->client->NPC_class != CLASS_SAND_CREATURE + && NPC_CheckPlayerDistance() ) + {//rancors, wampas & sand creatures don't care if player is closer, they always go with closest + return qtrue; + } + + //Otherwise, turn off the flag + NPC->svFlags &= ~SVF_LOCKEDENEMY; + + //If we've gotten here alright, then our target it still valid + if ( NPC_ValidEnemy( NPC->enemy ) ) + return qtrue; + + gentity_t *newenemy = NPC_PickEnemyExt( checkAlerts ); + + //if we found one, take it as the enemy + if( NPC_ValidEnemy( newenemy ) ) + { + G_SetEnemy( NPC, newenemy ); + return qtrue; + } + + G_ClearEnemy( NPC ); + return qfalse; +} + +/* +------------------------- +NPC_CheckEnemyExt +------------------------- +*/ + +qboolean NPC_CheckEnemyExt( qboolean checkAlerts ) +{ + //Make sure we're ready to think again +/* + if ( NPCInfo->enemyCheckDebounceTime > level.time ) + return qfalse; + + //Get our next think time + NPCInfo->enemyCheckDebounceTime = level.time + NPC_GetCheckDelta(); + + //Attempt to find an enemy + return NPC_FindEnemy(); +*/ + return NPC_FindEnemy( checkAlerts ); +} + +/* +------------------------- +NPC_FacePosition +------------------------- +*/ + +qboolean NPC_FacePosition( vec3_t position, qboolean doPitch ) +{ + vec3_t muzzle; + qboolean facing = qtrue; + + //Get the positions + if ( NPC->client && (NPC->client->NPC_class == CLASS_RANCOR || NPC->client->NPC_class == CLASS_WAMPA || NPC->client->NPC_class == CLASS_SAND_CREATURE) ) + { + CalcEntitySpot( NPC, SPOT_ORIGIN, muzzle ); + muzzle[2] += NPC->maxs[2] * 0.75f; + } + else if ( NPC->client && NPC->client->NPC_class == CLASS_GALAKMECH ) + { + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + } + else + { + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD + if ( NPC->client->NPC_class == CLASS_ROCKETTROOPER ) + {//*sigh*, look down more + position[2] -= 32; + } + } + + //Find the desired angles + vec3_t angles; + + GetAnglesForDirection( muzzle, position, angles ); + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + + if ( NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_ATST ) + { + // FIXME: this is kind of dumb, but it was the easiest way to get it to look sort of ok + NPCInfo->desiredYaw += Q_flrand( -5, 5 ) + sin( level.time * 0.004f ) * 7; + NPCInfo->desiredPitch += Q_flrand( -2, 2 ); + } + //Face that yaw + NPC_UpdateAngles( qtrue, qtrue ); + + //Find the delta between our goal and our current facing + float yawDelta = AngleNormalize360( NPCInfo->desiredYaw - ( SHORT2ANGLE( ucmd.angles[YAW] + client->ps.delta_angles[YAW] ) ) ); + + //See if we are facing properly + if ( fabs( yawDelta ) > VALID_ATTACK_CONE ) + facing = qfalse; + + if ( doPitch ) + { + //Find the delta between our goal and our current facing + float currentAngles = ( SHORT2ANGLE( ucmd.angles[PITCH] + client->ps.delta_angles[PITCH] ) ); + float pitchDelta = NPCInfo->desiredPitch - currentAngles; + + //See if we are facing properly + if ( fabs( pitchDelta ) > VALID_ATTACK_CONE ) + facing = qfalse; + } + + return facing; +} + +/* +------------------------- +NPC_FaceEntity +------------------------- +*/ + +qboolean NPC_FaceEntity( gentity_t *ent, qboolean doPitch ) +{ + vec3_t entPos; + + //Get the positions + CalcEntitySpot( ent, SPOT_HEAD_LEAN, entPos ); + + return NPC_FacePosition( entPos, doPitch ); +} + +/* +------------------------- +NPC_FaceEnemy +------------------------- +*/ + +qboolean NPC_FaceEnemy( qboolean doPitch ) +{ + if ( NPC == NULL ) + return qfalse; + + if ( NPC->enemy == NULL ) + return qfalse; + + return NPC_FaceEntity( NPC->enemy, doPitch ); +} + +/* +------------------------- +NPC_CheckCanAttackExt +------------------------- +*/ + +qboolean NPC_CheckCanAttackExt( void ) +{ + //We don't want them to shoot + if( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + return qfalse; + + //Turn to face + if ( NPC_FaceEnemy( qtrue ) == qfalse ) + return qfalse; + + //Must have a clear line of sight to the target + if ( NPC_ClearShot( NPC->enemy ) == qfalse ) + return qfalse; + + return qtrue; +} + +/* +------------------------- +NPC_ClearLookTarget +------------------------- +*/ + +void NPC_ClearLookTarget( gentity_t *self ) +{ + if ( !self->client ) + { + return; + } + + self->client->renderInfo.lookTarget = ENTITYNUM_NONE;//ENTITYNUM_WORLD; + self->client->renderInfo.lookTargetClearTime = 0; +} + +/* +------------------------- +NPC_SetLookTarget +------------------------- +*/ +void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ) +{ + if ( !self->client ) + { + return; + } + + self->client->renderInfo.lookTarget = entNum; + self->client->renderInfo.lookTargetClearTime = clearTime; +} + +/* +------------------------- +NPC_CheckLookTarget +------------------------- +*/ +qboolean NPC_CheckLookTarget( gentity_t *self ) +{ + if ( self->client ) + { + if ( self->client->renderInfo.lookTarget >= 0 && self->client->renderInfo.lookTarget < ENTITYNUM_WORLD ) + {//within valid range + if ( (&g_entities[self->client->renderInfo.lookTarget] == NULL) || !g_entities[self->client->renderInfo.lookTarget].inuse ) + {//lookTarget not inuse or not valid anymore + NPC_ClearLookTarget( self ); + } + else if ( self->client->renderInfo.lookTargetClearTime && self->client->renderInfo.lookTargetClearTime < level.time ) + {//Time to clear lookTarget + NPC_ClearLookTarget( self ); + } + else if ( g_entities[self->client->renderInfo.lookTarget].client && self->enemy && (&g_entities[self->client->renderInfo.lookTarget] != self->enemy) ) + {//should always look at current enemy if engaged in battle... FIXME: this could override certain scripted lookTargets...??? + NPC_ClearLookTarget( self ); + } + else + { + return qtrue; + } + } + } + + return qfalse; +} + +/* +------------------------- +NPC_CheckCharmed +------------------------- +*/ +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +void G_CheckCharmed( gentity_t *self ) +{ + if ( self + && self->client + && self->client->playerTeam == TEAM_PLAYER + && self->NPC + && self->NPC->charmedTime + && (self->NPC->charmedTime < level.time ||self->health <= 0) ) + {//we were charmed, set us back! + //NOTE: presumptions here... + team_t savTeam = self->client->enemyTeam; + self->client->enemyTeam = self->client->playerTeam; + self->client->playerTeam = savTeam; + self->client->leader = NULL; + self->NPC->charmedTime = 0; + if ( self->health > 0 ) + { + if ( self->NPC->tempBehavior == BS_FOLLOW_LEADER ) + { + self->NPC->tempBehavior = BS_DEFAULT; + } + G_ClearEnemy( self ); + //say something to let player know you've snapped out of it + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + } + +} + +void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex = 0 ) +{ + if ( !self || !self->ghoul2.size() ) + { + return; + } + mdxaBone_t boltMatrix; + vec3_t result, angles={0,self->currentAngles[YAW],0}; + + gi.G2API_GetBoltMatrix( self->ghoul2, modelIndex, + boltIndex, + &boltMatrix, angles, self->currentOrigin, (cg.time?cg.time:level.time), + NULL, self->s.modelScale ); + if ( pos ) + { + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, result ); + VectorCopy( result, pos ); + } +} + +float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex ) +{ + vec3_t org; + + if ( !targEnt ) + { + return Q3_INFINITE; + } + + G_GetBoltPosition( NPC, boltIndex, org ); + + return (Distance( targEnt->currentOrigin, org )); +} + +float NPC_EnemyRangeFromBolt( int boltIndex ) +{ + return (NPC_EntRangeFromBolt( NPC->enemy, boltIndex )); +} + +int G_GetEntsNearBolt( gentity_t *self, gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg ) +{ + vec3_t mins, maxs; + int i; + + //get my handRBolt's position + vec3_t org; + + G_GetBoltPosition( self, boltIndex, org ); + + VectorCopy( org, boltOrg ); + + //Setup the bbox to search in + for ( i = 0; i < 3; i++ ) + { + mins[i] = boltOrg[i] - radius; + maxs[i] = boltOrg[i] + radius; + } + + //Get the number of entities in a given space + return (gi.EntitiesInBox( mins, maxs, radiusEnts, 128 )); +} + +int NPC_GetEntsNearBolt( gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg ) +{ + return (G_GetEntsNearBolt( NPC, radiusEnts, radius, boltIndex, boltOrg )); +} + +extern qboolean RT_Flying( gentity_t *self ); +extern void RT_FlyStart( gentity_t *self ); +extern void RT_FlyStop( gentity_t *self ); +extern qboolean Boba_Flying( gentity_t *self ); +extern void Boba_FlyStart( gentity_t *self ); +extern void Boba_FlyStop( gentity_t *self ); + +qboolean JET_Flying( gentity_t *self ) +{ + if ( !self || !self->client ) + { + return qfalse; + } + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + return (Boba_Flying(self)); + } + else if ( self->client->NPC_class == CLASS_ROCKETTROOPER ) + { + return (RT_Flying(self)); + } + else + { + return qfalse; + } +} + +void JET_FlyStart( gentity_t *self ) +{ + if ( !self || !self->client ) + { + return; + } + self->lastInAirTime = level.time; + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + Boba_FlyStart( self ); + } + else if ( self->client->NPC_class == CLASS_ROCKETTROOPER ) + { + RT_FlyStart( self ); + } +} + +void JET_FlyStop( gentity_t *self ) +{ + if ( !self || !self->client ) + { + return; + } + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + Boba_FlyStop( self ); + } + else if ( self->client->NPC_class == CLASS_ROCKETTROOPER ) + { + RT_FlyStop( self ); + } +} diff --git a/code/game/Q3_Interface.cpp b/code/game/Q3_Interface.cpp new file mode 100644 index 0000000..db89e59 --- /dev/null +++ b/code/game/Q3_Interface.cpp @@ -0,0 +1,11286 @@ +// ICARUS Engine Interface File +// +// This file is the only section of the ICARUS systems that +// is not directly portable from engine to engine. +// +// -- jweier + +// leave this line at the top for PCH reasons... +#include "g_headers.h" + +#include "g_local.h" +#include "g_functions.h" +#include "Q3_Interface.h" +#include "anims.h" +#include "b_local.h" +#include "events.h" +#include "g_nav.h" +#include "..\cgame\cg_camera.h" +#include "..\game\objectives.h" +#include "g_roff.h" +#include "..\cgame\cg_local.h" +#include "wp_saber.h" +#include "g_vehicles.h" + +extern cvar_t *com_buildScript; + +extern void InitMover( gentity_t *ent ); +extern void MatchTeam( gentity_t *teamLeader, int moverState, int time ); +//extern void SetMoverState( gentity_t *ent, moverState_t moverState, int time ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern char *G_GetLocationForEnt( gentity_t *ent ); +extern void NPC_BSSearchStart( int homeWp, bState_t bState ); +extern void InitMoverTrData( gentity_t *ent ); +extern qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ); +extern cvar_t *g_sex; +extern cvar_t *g_timescale; +extern void G_SetEnemy( gentity_t *self, gentity_t *enemy ); +//extern void FX_BorgTeleport( vec3_t org ); +static void Q3_SetWeapon (int entID, const char *wp_name); +static void Q3_SetItem (int entID, const char *item_name); +extern void CG_ChangeWeapon( int num ); +extern int TAG_GetOrigin2( const char *owner, const char *name, vec3_t origin ); +extern void G_TeamRetaliation ( gentity_t *targ, gentity_t *attacker, qboolean stopIcarus ); +extern void G_PlayDoorLoopSound( gentity_t *ent ); +extern void G_PlayDoorSound( gentity_t *ent, int type ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +extern void NPC_ClearLookTarget( gentity_t *self ); +extern void WP_SaberSetColor( gentity_t *ent, int saberNum, int bladeNum, char *colorName ); +extern void WP_SetSaber( gentity_t *ent, int saberNum, char *saberName ); +extern qboolean PM_HasAnimation( gentity_t *ent, int animation ); +extern void G_ChangePlayerModel( gentity_t *ent, const char *newModel ); +extern vehicleType_t TranslateVehicleName( char *name ); +extern void WP_SetSaberOrigin( gentity_t *self, vec3_t newOrg ); +extern void JET_FlyStart( gentity_t *self ); +extern void JET_FlyStop( gentity_t *self ); +extern void Rail_LockCenterOfTrack(const char* trackName); +extern void Rail_UnLockCenterOfTrack(const char* trackName); +extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex = 0 ); +extern qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force = qfalse ); + +extern int BMS_START; +extern int BMS_MID; +extern int BMS_END; +extern cvar_t *g_skippingcin; + +extern qboolean stop_icarus; + +static void PrisonerObjCheck(const char *name,const char *data); + +#define stringIDExpand(str, strEnum) str, strEnum, ENUM2STRING(strEnum) +//#define stringIDExpand(str, strEnum) str,strEnum + +/* +stringID_table_t tagsTable [] = +{ +} +*/ + +stringID_table_t BSTable[] = +{ + ENUM2STRING(BS_DEFAULT),//# default behavior for that NPC + ENUM2STRING(BS_ADVANCE_FIGHT),//# Advance to captureGoal and shoot enemies if you can + ENUM2STRING(BS_SLEEP),//# Play awake script when startled by sound + ENUM2STRING(BS_FOLLOW_LEADER),//# Follow your leader and shoot any enemies you come across + ENUM2STRING(BS_JUMP),//# Face navgoal and jump to it. + ENUM2STRING(BS_SEARCH),//# Using current waypoint as a base), search the immediate branches of waypoints for enemies + ENUM2STRING(BS_WANDER),//# Wander down random waypoint paths + ENUM2STRING(BS_NOCLIP),//# Moves through walls), etc. + ENUM2STRING(BS_REMOVE),//# Waits for player to leave PVS then removes itself + ENUM2STRING(BS_CINEMATIC),//# Does nothing but face it's angles and move to a goal if it has one + ENUM2STRING(BS_FLEE),//# Run toward the nav goal, avoiding danger + //the rest are internal only + "", -1, +}; + + + +stringID_table_t BSETTable[] = +{ + ENUM2STRING(BSET_SPAWN),//# script to use when first spawned + ENUM2STRING(BSET_USE),//# script to use when used + ENUM2STRING(BSET_AWAKE),//# script to use when awoken/startled + ENUM2STRING(BSET_ANGER),//# script to use when aquire an enemy + ENUM2STRING(BSET_ATTACK),//# script to run when you attack + ENUM2STRING(BSET_VICTORY),//# script to run when you kill someone + ENUM2STRING(BSET_LOSTENEMY),//# script to run when you can't find your enemy + ENUM2STRING(BSET_PAIN),//# script to use when take pain + ENUM2STRING(BSET_FLEE),//# script to use when take pain below 50% of health + ENUM2STRING(BSET_DEATH),//# script to use when killed + ENUM2STRING(BSET_DELAYED),//# script to run when self->delayScriptTime is reached + ENUM2STRING(BSET_BLOCKED),//# script to run when blocked by a friendly NPC or player + ENUM2STRING(BSET_BUMPED),//# script to run when bumped into a friendly NPC or player (can set bumpRadius) + ENUM2STRING(BSET_STUCK),//# script to run when blocked by a wall + ENUM2STRING(BSET_FFIRE),//# script to run when player shoots their own teammates + ENUM2STRING(BSET_FFDEATH),//# script to run when player kills a teammate + stringIDExpand("", BSET_INVALID), + "", -1, +}; + +stringID_table_t WPTable[] = +{ + "NULL",WP_NONE, + ENUM2STRING(WP_NONE), + // Player weapons + ENUM2STRING(WP_SABER), // NOTE: lots of code assumes this is the first weapon (... which is crap) so be careful -Ste. + ENUM2STRING(WP_BLASTER_PISTOL), // apparently some enemy only version of the blaster + ENUM2STRING(WP_BLASTER), + ENUM2STRING(WP_DISRUPTOR), + ENUM2STRING(WP_BOWCASTER), + ENUM2STRING(WP_REPEATER), + ENUM2STRING(WP_DEMP2), + ENUM2STRING(WP_FLECHETTE), + ENUM2STRING(WP_ROCKET_LAUNCHER), + ENUM2STRING(WP_THERMAL), + ENUM2STRING(WP_TRIP_MINE), + ENUM2STRING(WP_DET_PACK), + ENUM2STRING(WP_CONCUSSION), + ENUM2STRING(WP_MELEE), // Any ol' melee attack + //NOTE: player can only have up to 16 weapons), anything after that is enemy only + ENUM2STRING(WP_STUN_BATON), + // NPC enemy weapons + ENUM2STRING(WP_BRYAR_PISTOL), + ENUM2STRING(WP_EMPLACED_GUN), + ENUM2STRING(WP_BOT_LASER), // Probe droid - Laser blast + ENUM2STRING(WP_TURRET), // turret guns + ENUM2STRING(WP_ATST_MAIN), + ENUM2STRING(WP_ATST_SIDE), + ENUM2STRING(WP_TIE_FIGHTER), + ENUM2STRING(WP_RAPID_FIRE_CONC), + ENUM2STRING(WP_JAWA), + ENUM2STRING(WP_TUSKEN_RIFLE), + ENUM2STRING(WP_TUSKEN_STAFF), + ENUM2STRING(WP_SCEPTER), + ENUM2STRING(WP_NOGHRI_STICK), + "", NULL +}; + +stringID_table_t INVTable[] = +{ + ENUM2STRING(INV_ELECTROBINOCULARS), + ENUM2STRING(INV_BACTA_CANISTER), + ENUM2STRING(INV_SEEKER), + ENUM2STRING(INV_LIGHTAMP_GOGGLES), + ENUM2STRING(INV_SENTRY), + "", NULL +}; + +stringID_table_t eventTable[] = +{ + //BOTH_h + //END + "", EV_BAD, +}; + +stringID_table_t DMSTable[] = +{ + "NULL",-1, + ENUM2STRING(DM_AUTO), //# let the game determine the dynamic music as normal + ENUM2STRING(DM_SILENCE), //# stop the music + ENUM2STRING(DM_EXPLORE), //# force the exploration music to play + ENUM2STRING(DM_ACTION), //# force the action music to play + ENUM2STRING(DM_BOSS), //# force the boss battle music to play (if there is any) + ENUM2STRING(DM_DEATH), //# force the "player dead" music to play + "", -1 +}; + +stringID_table_t HLTable[] = +{ + "NULL",-1, + ENUM2STRING(HL_FOOT_RT), + ENUM2STRING(HL_FOOT_LT), + ENUM2STRING(HL_LEG_RT), + ENUM2STRING(HL_LEG_LT), + ENUM2STRING(HL_WAIST), + ENUM2STRING(HL_BACK_RT), + ENUM2STRING(HL_BACK_LT), + ENUM2STRING(HL_BACK), + ENUM2STRING(HL_CHEST_RT), + ENUM2STRING(HL_CHEST_LT), + ENUM2STRING(HL_CHEST), + ENUM2STRING(HL_ARM_RT), + ENUM2STRING(HL_ARM_LT), + ENUM2STRING(HL_HAND_RT), + ENUM2STRING(HL_HAND_LT), + ENUM2STRING(HL_HEAD), + ENUM2STRING(HL_GENERIC1), + ENUM2STRING(HL_GENERIC2), + ENUM2STRING(HL_GENERIC3), + ENUM2STRING(HL_GENERIC4), + ENUM2STRING(HL_GENERIC5), + ENUM2STRING(HL_GENERIC6), + "", -1 +}; + +stringID_table_t setTable[] = +{ + ENUM2STRING(SET_SPAWNSCRIPT),//0 + ENUM2STRING(SET_USESCRIPT), + ENUM2STRING(SET_AWAKESCRIPT), + ENUM2STRING(SET_ANGERSCRIPT), + ENUM2STRING(SET_ATTACKSCRIPT), + ENUM2STRING(SET_VICTORYSCRIPT), + ENUM2STRING(SET_PAINSCRIPT), + ENUM2STRING(SET_FLEESCRIPT), + ENUM2STRING(SET_DEATHSCRIPT), + ENUM2STRING(SET_DELAYEDSCRIPT), + ENUM2STRING(SET_BLOCKEDSCRIPT), + ENUM2STRING(SET_FFIRESCRIPT), + ENUM2STRING(SET_FFDEATHSCRIPT), + ENUM2STRING(SET_MINDTRICKSCRIPT), + ENUM2STRING(SET_NO_MINDTRICK), + ENUM2STRING(SET_ORIGIN), + ENUM2STRING(SET_TELEPORT_DEST), + ENUM2STRING(SET_ANGLES), + ENUM2STRING(SET_XVELOCITY), + ENUM2STRING(SET_YVELOCITY), + ENUM2STRING(SET_ZVELOCITY), + ENUM2STRING(SET_Z_OFFSET), + ENUM2STRING(SET_ENEMY), + ENUM2STRING(SET_LEADER), + ENUM2STRING(SET_NAVGOAL), + ENUM2STRING(SET_ANIM_UPPER), + ENUM2STRING(SET_ANIM_LOWER), + ENUM2STRING(SET_ANIM_BOTH), + ENUM2STRING(SET_ANIM_HOLDTIME_LOWER), + ENUM2STRING(SET_ANIM_HOLDTIME_UPPER), + ENUM2STRING(SET_ANIM_HOLDTIME_BOTH), + ENUM2STRING(SET_PLAYER_TEAM), + ENUM2STRING(SET_ENEMY_TEAM), + ENUM2STRING(SET_BEHAVIOR_STATE), + ENUM2STRING(SET_BEHAVIOR_STATE), + ENUM2STRING(SET_HEALTH), + ENUM2STRING(SET_ARMOR), + ENUM2STRING(SET_DEFAULT_BSTATE), + ENUM2STRING(SET_CAPTURE), + ENUM2STRING(SET_DPITCH), + ENUM2STRING(SET_DYAW), + ENUM2STRING(SET_EVENT), + ENUM2STRING(SET_TEMP_BSTATE), + ENUM2STRING(SET_COPY_ORIGIN), + ENUM2STRING(SET_VIEWTARGET), + ENUM2STRING(SET_WEAPON), + ENUM2STRING(SET_ITEM), + ENUM2STRING(SET_WALKSPEED), + ENUM2STRING(SET_RUNSPEED), + ENUM2STRING(SET_YAWSPEED), + ENUM2STRING(SET_AGGRESSION), + ENUM2STRING(SET_AIM), + ENUM2STRING(SET_FRICTION), + ENUM2STRING(SET_GRAVITY), + ENUM2STRING(SET_IGNOREPAIN), + ENUM2STRING(SET_IGNOREENEMIES), + ENUM2STRING(SET_IGNOREALERTS), + ENUM2STRING(SET_DONTSHOOT), + ENUM2STRING(SET_DONTFIRE), + ENUM2STRING(SET_LOCKED_ENEMY), + ENUM2STRING(SET_NOTARGET), + ENUM2STRING(SET_LEAN), + ENUM2STRING(SET_CROUCHED), + ENUM2STRING(SET_WALKING), + ENUM2STRING(SET_RUNNING), + ENUM2STRING(SET_CHASE_ENEMIES), + ENUM2STRING(SET_LOOK_FOR_ENEMIES), + ENUM2STRING(SET_FACE_MOVE_DIR), + ENUM2STRING(SET_ALT_FIRE), + ENUM2STRING(SET_DONT_FLEE), + ENUM2STRING(SET_FORCED_MARCH), + ENUM2STRING(SET_NO_RESPONSE), + ENUM2STRING(SET_NO_COMBAT_TALK), + ENUM2STRING(SET_NO_ALERT_TALK), + ENUM2STRING(SET_UNDYING), + ENUM2STRING(SET_TREASONED), + ENUM2STRING(SET_DISABLE_SHADER_ANIM), + ENUM2STRING(SET_SHADER_ANIM), + ENUM2STRING(SET_INVINCIBLE), + ENUM2STRING(SET_NOAVOID), + ENUM2STRING(SET_SHOOTDIST), + ENUM2STRING(SET_TARGETNAME), + ENUM2STRING(SET_TARGET), + ENUM2STRING(SET_TARGET2), + ENUM2STRING(SET_LOCATION), + ENUM2STRING(SET_PAINTARGET), + ENUM2STRING(SET_TIMESCALE), + ENUM2STRING(SET_VISRANGE), + ENUM2STRING(SET_EARSHOT), + ENUM2STRING(SET_VIGILANCE), + ENUM2STRING(SET_HFOV), + ENUM2STRING(SET_VFOV), + ENUM2STRING(SET_DELAYSCRIPTTIME), + ENUM2STRING(SET_FORWARDMOVE), + ENUM2STRING(SET_RIGHTMOVE), + ENUM2STRING(SET_LOCKYAW), + ENUM2STRING(SET_SOLID), + ENUM2STRING(SET_CAMERA_GROUP), + ENUM2STRING(SET_CAMERA_GROUP_Z_OFS), + ENUM2STRING(SET_CAMERA_GROUP_TAG), + ENUM2STRING(SET_LOOK_TARGET), + ENUM2STRING(SET_ADDRHANDBOLT_MODEL), + ENUM2STRING(SET_REMOVERHANDBOLT_MODEL), + ENUM2STRING(SET_ADDLHANDBOLT_MODEL), + ENUM2STRING(SET_REMOVELHANDBOLT_MODEL), + ENUM2STRING(SET_FACEAUX), + ENUM2STRING(SET_FACEBLINK), + ENUM2STRING(SET_FACEBLINKFROWN), + ENUM2STRING(SET_FACEFROWN), + ENUM2STRING(SET_FACENORMAL), + ENUM2STRING(SET_FACEEYESCLOSED), + ENUM2STRING(SET_FACEEYESOPENED), + ENUM2STRING(SET_SCROLLTEXT), + ENUM2STRING(SET_LCARSTEXT), + ENUM2STRING(SET_CENTERTEXT), + ENUM2STRING(SET_SCROLLTEXTCOLOR), + ENUM2STRING(SET_CAPTIONTEXTCOLOR), + ENUM2STRING(SET_CENTERTEXTCOLOR), + ENUM2STRING(SET_PLAYER_USABLE), + ENUM2STRING(SET_STARTFRAME), + ENUM2STRING(SET_ENDFRAME), + ENUM2STRING(SET_ANIMFRAME), + ENUM2STRING(SET_LOOP_ANIM), + ENUM2STRING(SET_INTERFACE), + ENUM2STRING(SET_SHIELDS), + ENUM2STRING(SET_NO_KNOCKBACK), + ENUM2STRING(SET_INVISIBLE), + ENUM2STRING(SET_VAMPIRE), + ENUM2STRING(SET_FORCE_INVINCIBLE), + ENUM2STRING(SET_GREET_ALLIES), + ENUM2STRING(SET_PLAYER_LOCKED), + ENUM2STRING(SET_LOCK_PLAYER_WEAPONS), + ENUM2STRING(SET_NO_IMPACT_DAMAGE), + ENUM2STRING(SET_PARM1), + ENUM2STRING(SET_PARM2), + ENUM2STRING(SET_PARM3), + ENUM2STRING(SET_PARM4), + ENUM2STRING(SET_PARM5), + ENUM2STRING(SET_PARM6), + ENUM2STRING(SET_PARM7), + ENUM2STRING(SET_PARM8), + ENUM2STRING(SET_PARM9), + ENUM2STRING(SET_PARM10), + ENUM2STRING(SET_PARM11), + ENUM2STRING(SET_PARM12), + ENUM2STRING(SET_PARM13), + ENUM2STRING(SET_PARM14), + ENUM2STRING(SET_PARM15), + ENUM2STRING(SET_PARM16), + ENUM2STRING(SET_DEFEND_TARGET), + ENUM2STRING(SET_WAIT), + ENUM2STRING(SET_COUNT), + ENUM2STRING(SET_SHOT_SPACING), + ENUM2STRING(SET_VIDEO_PLAY), + ENUM2STRING(SET_VIDEO_FADE_IN), + ENUM2STRING(SET_VIDEO_FADE_OUT), + ENUM2STRING(SET_REMOVE_TARGET), + ENUM2STRING(SET_LOADGAME), + ENUM2STRING(SET_MENU_SCREEN), + ENUM2STRING(SET_OBJECTIVE_SHOW), + ENUM2STRING(SET_OBJECTIVE_HIDE), + ENUM2STRING(SET_OBJECTIVE_SUCCEEDED), + ENUM2STRING(SET_OBJECTIVE_SUCCEEDED_NO_UPDATE), + ENUM2STRING(SET_OBJECTIVE_FAILED), + ENUM2STRING(SET_MISSIONFAILED), + ENUM2STRING(SET_TACTICAL_SHOW), + ENUM2STRING(SET_TACTICAL_HIDE), + ENUM2STRING(SET_FOLLOWDIST), + ENUM2STRING(SET_SCALE), + ENUM2STRING(SET_OBJECTIVE_CLEARALL), + ENUM2STRING(SET_OBJECTIVE_LIGHTSIDE), + ENUM2STRING(SET_MISSIONSTATUSTEXT), + ENUM2STRING(SET_WIDTH), + ENUM2STRING(SET_CLOSINGCREDITS), + ENUM2STRING(SET_SKILL), + ENUM2STRING(SET_MISSIONSTATUSTIME), + ENUM2STRING(SET_FORCE_HEAL_LEVEL), + ENUM2STRING(SET_FORCE_JUMP_LEVEL), + ENUM2STRING(SET_FORCE_SPEED_LEVEL), + ENUM2STRING(SET_FORCE_PUSH_LEVEL), + ENUM2STRING(SET_FORCE_PULL_LEVEL), + ENUM2STRING(SET_FORCE_MINDTRICK_LEVEL), + ENUM2STRING(SET_FORCE_GRIP_LEVEL), + ENUM2STRING(SET_FORCE_LIGHTNING_LEVEL), + ENUM2STRING(SET_SABER_THROW), + ENUM2STRING(SET_SABER_DEFENSE), + ENUM2STRING(SET_SABER_OFFENSE), + ENUM2STRING(SET_FORCE_RAGE_LEVEL), + ENUM2STRING(SET_FORCE_PROTECT_LEVEL), + ENUM2STRING(SET_FORCE_ABSORB_LEVEL), + ENUM2STRING(SET_FORCE_DRAIN_LEVEL), + ENUM2STRING(SET_FORCE_SIGHT_LEVEL), + ENUM2STRING(SET_SABER1_COLOR1), + ENUM2STRING(SET_SABER1_COLOR2), + ENUM2STRING(SET_SABER2_COLOR1), + ENUM2STRING(SET_SABER2_COLOR2), + ENUM2STRING(SET_DISMEMBER_LIMB), + + ENUM2STRING(SET_VIEWENTITY), + ENUM2STRING(SET_WATCHTARGET), + ENUM2STRING(SET_SABERACTIVE), + + ENUM2STRING(SET_SABER1BLADEON), + ENUM2STRING(SET_SABER1BLADEOFF), + ENUM2STRING(SET_SABER2BLADEON), + ENUM2STRING(SET_SABER2BLADEOFF), + + ENUM2STRING(SET_ADJUST_AREA_PORTALS), + ENUM2STRING(SET_DMG_BY_HEAVY_WEAP_ONLY), + ENUM2STRING(SET_SHIELDED), + ENUM2STRING(SET_NO_GROUPS), + ENUM2STRING(SET_FIRE_WEAPON), + ENUM2STRING(SET_FIRE_WEAPON_NO_ANIM), + ENUM2STRING(SET_SAFE_REMOVE), + ENUM2STRING(SET_BOBA_JET_PACK), + ENUM2STRING(SET_INACTIVE), + ENUM2STRING(SET_FUNC_USABLE_VISIBLE), + ENUM2STRING(SET_END_SCREENDISSOLVE), + ENUM2STRING(SET_LOOPSOUND), + ENUM2STRING(SET_ICARUS_FREEZE), + ENUM2STRING(SET_ICARUS_UNFREEZE), + ENUM2STRING(SET_SABER1), + ENUM2STRING(SET_SABER2), + ENUM2STRING(SET_PLAYERMODEL), + ENUM2STRING(SET_VEHICLE), + ENUM2STRING(SET_SECURITY_KEY), + ENUM2STRING(SET_DAMAGEENTITY), + ENUM2STRING(SET_USE_CP_NEAREST), + ENUM2STRING(SET_MORELIGHT), + ENUM2STRING(SET_CINEMATIC_SKIPSCRIPT), + ENUM2STRING(SET_RAILCENTERTRACKLOCKED), + ENUM2STRING(SET_RAILCENTERTRACKUNLOCKED), + ENUM2STRING(SET_NO_FORCE), + ENUM2STRING(SET_NO_FALLTODEATH), + ENUM2STRING(SET_DISMEMBERABLE), + ENUM2STRING(SET_NO_ACROBATICS), + ENUM2STRING(SET_MUSIC_STATE), + ENUM2STRING(SET_USE_SUBTITLES), + ENUM2STRING(SET_CLEAN_DAMAGING_ENTS), + ENUM2STRING(SET_HUD), + //JKA + ENUM2STRING(SET_NO_PVS_CULL), + ENUM2STRING(SET_CLOAK), + ENUM2STRING(SET_RENDER_CULL_RADIUS), + ENUM2STRING(SET_DISTSQRD_TO_PLAYER), + ENUM2STRING(SET_FORCE_HEAL), + ENUM2STRING(SET_FORCE_SPEED), + ENUM2STRING(SET_FORCE_PUSH), + ENUM2STRING(SET_FORCE_PUSH_FAKE), + ENUM2STRING(SET_FORCE_PULL), + ENUM2STRING(SET_FORCE_MIND_TRICK), + ENUM2STRING(SET_FORCE_GRIP), + ENUM2STRING(SET_FORCE_LIGHTNING), + ENUM2STRING(SET_FORCE_SABERTHROW), + ENUM2STRING(SET_FORCE_RAGE), + ENUM2STRING(SET_FORCE_PROTECT), + ENUM2STRING(SET_FORCE_ABSORB), + ENUM2STRING(SET_FORCE_DRAIN), + ENUM2STRING(SET_WINTER_GEAR), + ENUM2STRING(SET_NO_ANGLES), + ENUM2STRING(SET_SABER_ORIGIN), + ENUM2STRING(SET_SKIN), + + "", SET_, +}; + +qboolean COM_ParseString( char **data, char **s ); + +//======================================================================= + +vec4_t textcolor_caption; +vec4_t textcolor_center; +vec4_t textcolor_scroll; + +/* +------------------------- +void Q3_SetTaskID( gentity_t *ent, taskID_t taskType, int taskID ) +------------------------- +*/ + +static void Q3_TaskIDSet( gentity_t *ent, taskID_t taskType, int taskID ) +{ + if ( taskType < TID_CHAN_VOICE || taskType >= NUM_TIDS ) + { + return; + } + + //Might be stomping an old task, so complete and clear previous task if there was one + Q3_TaskIDComplete( ent, taskType ); + + ent->taskID[taskType] = taskID; +} + +/* +------------------------- +SetTextColor +------------------------- +*/ + +static void SetTextColor ( vec4_t textcolor,const char *color) +{ + + if (Q_stricmp(color,"BLACK") == 0) + { + Vector4Copy( colorTable[CT_BLACK], textcolor ); + } + else if (Q_stricmp(color,"RED") == 0) + { + Vector4Copy( colorTable[CT_RED], textcolor ); + } + else if (Q_stricmp(color,"GREEN") == 0) + { + Vector4Copy( colorTable[CT_GREEN], textcolor ); + } + else if (Q_stricmp(color,"YELLOW") == 0) + { + Vector4Copy( colorTable[CT_YELLOW], textcolor ); + } + else if (Q_stricmp(color,"BLUE") == 0) + { + Vector4Copy( colorTable[CT_BLUE], textcolor ); + } + else if (Q_stricmp(color,"CYAN") == 0) + { + Vector4Copy( colorTable[CT_CYAN], textcolor ); + } + else if (Q_stricmp(color,"MAGENTA") == 0) + { + Vector4Copy( colorTable[CT_MAGENTA], textcolor ); + } + else if (Q_stricmp(color,"WHITE") == 0) + { + Vector4Copy( colorTable[CT_WHITE], textcolor ); + } + else + { + Vector4Copy( colorTable[CT_WHITE], textcolor ); + } + + return; +} + +/* +------------------------- +void Q3_ClearTaskID( int *taskID ) + +WARNING: Clearing a taskID will make that task never finish unless you intend to + return the same taskID from somewhere else. +------------------------- +*/ +void Q3_TaskIDClear( int *taskID ) +{ + *taskID = -1; +} + +/* +------------------------- +qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ) +------------------------- +*/ +qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ) +{ + if ( ent->m_iIcarusID == IIcarusInterface::ICARUS_INVALID /*!ent->sequencer || !ent->taskManager*/ ) + { + return qfalse; + } + + if ( taskType < TID_CHAN_VOICE || taskType >= NUM_TIDS ) + { + return qfalse; + } + + if ( ent->taskID[taskType] >= 0 )//-1 is none + { + return qtrue; + } + + return qfalse; +} + +/* +------------------------- +void Q3_TaskIDComplete( gentity_t *ent, taskID_t taskType ) +------------------------- +*/ +void Q3_TaskIDComplete( gentity_t *ent, taskID_t taskType ) +{ + if ( taskType < TID_CHAN_VOICE || taskType >= NUM_TIDS ) + { + return; + } + + if ( ent->m_iIcarusID != IIcarusInterface::ICARUS_INVALID /*ent->taskManager*/ && Q3_TaskIDPending( ent, taskType ) ) + {//Complete it + IIcarusInterface::GetIcarus()->Completed( ent->m_iIcarusID, ent->taskID[taskType] ); + + //See if any other tasks have the name number and clear them so we don't complete more than once + int clearTask = ent->taskID[taskType]; + for ( int tid = 0; tid < NUM_TIDS; tid++ ) + { + if ( ent->taskID[tid] == clearTask ) + { + Q3_TaskIDClear( &ent->taskID[tid] ); + } + } + + //clear it - should be cleared in for loop above + //Q3_TaskIDClear( &ent->taskID[taskType] ); + } + //otherwise, wasn't waiting for a task to complete anyway +} + +/* +============ +Q3_CheckStringCounterIncrement + Description : + Return type : static float + Argument : const char *string +============ +*/ +static float Q3_CheckStringCounterIncrement( const char *string ) +{ + char *numString; + float val = 0.0f; + + if ( string[0] == '+' ) + {//We want to increment whatever the value is by whatever follows the + + if ( string[1] ) + { + numString = (char *)&string[1]; + val = atof( numString ); + } + } + else if ( string[0] == '-' ) + {//we want to decrement + if ( string[1] ) + { + numString = (char *)&string[1]; + val = atof( numString ) * -1; + } + } + + return val; +} + +/* +------------------------- +Q3_GetAnimLower +------------------------- +*/ +static char *Q3_GetAnimLower( gentity_t *ent ) +{ + if ( ent->client == NULL ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_GetAnimLower: attempted to read animation state off non-client!\n" ); + return NULL; + } + + int anim = ent->client->ps.legsAnim; + + return (char *) GetStringForID( animTable, anim ); +} + +/* +------------------------- +Q3_GetAnimUpper +------------------------- +*/ +static char *Q3_GetAnimUpper( gentity_t *ent ) +{ + if ( ent->client == NULL ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_GetAnimUpper: attempted to read animation state off non-client!\n" ); + return NULL; + } + + int anim = ent->client->ps.torsoAnim; + + return (char *) GetStringForID( animTable, anim ); +} + +/* +------------------------- +Q3_GetAnimBoth +------------------------- +*/ +static char *Q3_GetAnimBoth( gentity_t *ent ) +{ + char *lowerName, *upperName; + + lowerName = Q3_GetAnimLower( ent ); + upperName = Q3_GetAnimUpper( ent ); + + if ( VALIDSTRING( lowerName ) == false ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_GetAnimBoth: NULL legs animation string found!\n" ); + return NULL; + } + + if ( VALIDSTRING( upperName ) == false ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_GetAnimBoth: NULL torso animation string found!\n" ); + return NULL; + } + + if ( stricmp( lowerName, upperName ) ) + { +#ifdef _DEBUG // sigh, cut down on tester reports that aren't important + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_GetAnimBoth: legs and torso animations did not match : returning legs\n" ); +#endif + } + + return lowerName; +} + +/* +------------------------- +Q3_SetObjective +------------------------- +*/ +extern qboolean G_CheckPlayerDarkSide( void ); +static void Q3_SetObjective(const char *ObjEnum, int status) +{ + int objectiveID; + gclient_t *client; + objectives_t *objective; + int *objectivesShown; + + client = &level.clients[0]; + + objectiveID = GetIDForString( objectiveTable, ObjEnum ); + objective = &client->sess.mission_objectives[objectiveID]; + objectivesShown = &client->sess.missionObjectivesShown; + + + switch (status) + { + case SET_OBJ_HIDE : + objective->display = OBJECTIVE_HIDE; + break; + case SET_OBJ_SHOW : + objective->display = OBJECTIVE_SHOW; + objectivesShown++; + missionInfo_Updated = qtrue; // Activate flashing text + break; + case SET_OBJ_PENDING : + objective->status = OBJECTIVE_STAT_PENDING; + if (objective->display != OBJECTIVE_HIDE) + { + objectivesShown++; + missionInfo_Updated = qtrue; // Activate flashing text + } + break; + case SET_OBJ_SUCCEEDED : + objective->status = OBJECTIVE_STAT_SUCCEEDED; + if (objective->display != OBJECTIVE_HIDE) + { + objectivesShown++; + missionInfo_Updated = qtrue; // Activate flashing text + } + break; + case SET_OBJ_FAILED : + objective->status = OBJECTIVE_STAT_FAILED; + if (objective->display != OBJECTIVE_HIDE) + { + objectivesShown++; + missionInfo_Updated = qtrue; // Activate flashing text + } + break; + } + + if ( objectiveID == LIGHTSIDE_OBJ + && status == SET_OBJ_FAILED ) + {//don't do this unless we just set that specific objective (just turned to the dark side) with this SET_OBJECTIVE command + G_CheckPlayerDarkSide(); + } +} + + +/* +------------------------- +Q3_SetMissionFailed +------------------------- +*/ +extern void G_PlayerGuiltDeath( void ); +static void Q3_SetMissionFailed(const char *TextEnum) +{ + gentity_t *ent = &g_entities[0]; + + if ( ent->health >= 0 ) + { + G_PlayerGuiltDeath(); + } + ent->health = 0; + //FIXME: what about other NPCs? Scripts? + + // statusTextIndex is looked at on the client side. + statusTextIndex = GetIDForString( missionFailedTable, TextEnum ); + cg.missionStatusShow = qtrue; + if ( ent->client ) + {//hold this screen up for at least 2 seconds + ent->client->respawnTime = level.time + 2000; + } +} + +/* +------------------------- +Q3_SetStatusText +------------------------- +*/ +static void Q3_SetStatusText(const char *StatusTextEnum) +{ + int statusTextID; + + statusTextID = GetIDForString( statusTextTable, StatusTextEnum ); + + switch (statusTextID) + { + case STAT_INSUBORDINATION: + case STAT_YOUCAUSEDDEATHOFTEAMMATE: + case STAT_DIDNTPROTECTTECH: + case STAT_DIDNTPROTECT7OF9: + case STAT_NOTSTEALTHYENOUGH: + case STAT_STEALTHTACTICSNECESSARY: + case STAT_WATCHYOURSTEP: + case STAT_JUDGEMENTMUCHDESIRED: + statusTextIndex = statusTextID; + break; + default: + assert(0); + break; + } +} + + +/* +------------------------- +Q3_ObjectiveClearAll +------------------------- +*/ +static void Q3_ObjectiveClearAll(void) +{ + client = &level.clients[0]; + memset(client->sess.mission_objectives,0,sizeof(client->sess.mission_objectives)); +} + +/* +============= +Q3_SetCaptionTextColor + +Change color text prints in +============= +*/ +static void Q3_SetCaptionTextColor ( const char *color) +{ + SetTextColor(textcolor_caption,color); +} + +/* +============= +Q3_SetCenterTextColor + +Change color text prints in +============= +*/ +static void Q3_SetCenterTextColor ( const char *color) +{ + SetTextColor(textcolor_center,color); +} + +/* +============= +Q3_SetScrollTextColor + +Change color text prints in +============= +*/ +static void Q3_SetScrollTextColor ( const char *color) +{ + SetTextColor(textcolor_scroll,color); +} + +/* +============= +Q3_ScrollText + +Prints a message in the center of the screen +============= +*/ +static void Q3_ScrollText ( const char *id) +{ + gi.SendServerCommand( NULL, "st \"%s\"", id); + + return; +} + +/* +============= +Q3_LCARSText + +Prints a message in the center of the screen giving it an LCARS frame around it +============= +*/ +static void Q3_LCARSText ( const char *id) +{ + gi.SendServerCommand( NULL, "lt \"%s\"", id); + + return; +} + +/* +============= +` + +Returns the sequencer of the entity by the given name +============= +*/ +/*static gentity_t *Q3_GetEntityByName( const char *name ) +{ + gentity_t *ent; + entitylist_t::iterator ei; + char temp[1024]; + + if ( name == NULL || name[0] == NULL ) + return NULL; + + strncpy( (char *) temp, name, sizeof(temp) ); + temp[sizeof(temp)-1] = 0; + + ei = ICARUS_EntList.find( strupr( (char *) temp ) ); + + if ( ei == ICARUS_EntList.end() ) + return NULL; + + ent = &g_entities[(*ei).second]; + + return ent; + // this now returns the ent instead of the sequencer -- dmv 06/27/01 +// if (ent == NULL) +// return NULL; +// return ent->sequencer; +}*/ + +/* +============= +Q3_GetTime + +Get the current game time +============= +*/ +/*static DWORD Q3_GetTime( void ) +{ + return level.time; +}*/ + +/* +============= +G_AddSexToPlayerString + +Take any string, look for "jaden_male/" replace with "jaden_fmle/" based on "sex" +And: Take any string, look for "/mr_" replace with "/ms_" based on "sex" +returns qtrue if changed to ms +============= +*/ +static qboolean G_AddSexToPlayerString ( char *string, qboolean qDoBoth ) +{ + char *start; + + if VALIDSTRING( string ) { + if ( g_sex->string[0] == 'f' ) { + start = strstr( string, "jaden_male/" ); + if ( start != NULL ) { + strncpy( start, "jaden_fmle", 10 ); + return qtrue; + } else { + start = strrchr( string, '/' ); //get the last slash before the wav + if (start != NULL) { + if (!strncmp( start, "/mr_", 4) ) { + if (qDoBoth) { //we want to change mr to ms + start[2] = 's'; //change mr to ms + return qtrue; + } else { //IF qDoBoth + return qfalse; //don't want this one + } + } + } //IF found slash + } + } //IF Female + else { //i'm male + start = strrchr( string, '/' ); //get the last slash before the wav + if (start != NULL) { + if (!strncmp( start, "/ms_", 4) ) { + return qfalse; //don't want this one + } + } //IF found slash + } + } //if VALIDSTRING + return qtrue; +} + + +/* + +/* +============= +Q3_SetAngles + +Sets the angles of an entity directly +============= +*/ +static void Q3_SetDYaw( int entID, float data ); +static void Q3_SetAngles( int entID, vec3_t angles ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAngles: bad ent %d\n", entID); + return; + } + + if (ent->client) + { + SetClientViewAngle( ent, angles ); + if ( ent->NPC ) + { + Q3_SetDYaw( entID, angles[YAW] ); + } + } + else + { + VectorCopy( angles, ent->s.angles ); + VectorCopy( angles, ent->s.apos.trBase ); + VectorCopy( angles, ent->currentAngles ); + } + gi.linkentity( ent ); +} + +/* +============= +Q3_SetOrigin + +Sets the origin of an entity directly +============= +*/ +static void Q3_SetOrigin( int entID, vec3_t origin ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetOrigin: bad ent %d\n", entID); + return; + } + + gi.unlinkentity (ent); + + if(ent->client) + { + VectorCopy(origin, ent->client->ps.origin); + VectorCopy(origin, ent->currentOrigin); + ent->client->ps.origin[2] += 1; + + VectorClear (ent->client->ps.velocity); + ent->client->ps.pm_time = 160; // hold time + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + +// G_KillBox (ent); + } + else + { + G_SetOrigin( ent, origin ); + } + + gi.linkentity( ent ); +} + + +/* +============ +MoveOwner + Description : + Return type : void + Argument : gentity_t *self +============ +*/ +void MoveOwner( gentity_t *self ) +{ + self->nextthink = level.time + FRAMETIME; + self->e_ThinkFunc = thinkF_G_FreeEntity; + + if ( !self->owner || !self->owner->inuse ) + { + return; + } + + if ( SpotWouldTelefrag2( self->owner, self->currentOrigin ) ) + { + self->e_ThinkFunc = thinkF_MoveOwner; + } + else + { + G_SetOrigin( self->owner, self->currentOrigin ); + gi.linkentity( self->owner ); + Q3_TaskIDComplete( self->owner, TID_MOVE_NAV ); + } +} + + +/* +============= +Q3_SetTeleportDest + +Copies passed origin to ent running script once there is nothing there blocking the spot +============= +*/ +static qboolean Q3_SetTeleportDest( int entID, vec3_t org ) +{ + gentity_t *teleEnt = &g_entities[entID]; + + if ( teleEnt ) + { + if ( SpotWouldTelefrag2( teleEnt, org ) ) + { + gentity_t *teleporter = G_Spawn(); + + G_SetOrigin( teleporter, org ); + gi.linkentity( teleporter ); + teleporter->owner = teleEnt; + + teleporter->e_ThinkFunc = thinkF_MoveOwner; + teleporter->nextthink = level.time + FRAMETIME; + + return qfalse; + } + else + { + G_SetOrigin( teleEnt, org ); + gi.linkentity( teleEnt ); + } + } + + return qtrue; +} + +/* +============= +Q3_SetCopyOrigin + +Copies origin of found ent into ent running script +=============` +*/ +static void Q3_SetCopyOrigin( int entID, const char *name ) +{ + gentity_t *found = G_Find( NULL, FOFS(targetname), (char *) name); + + if(found) + { + Q3_SetOrigin( entID, found->currentOrigin ); + SetClientViewAngle( &g_entities[entID], found->s.angles ); + } + else + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetCopyOrigin: ent %s not found!\n", name); + } +} + +/* +============= +Q3_SetVelocity + +Set the velocity of an entity directly +============= +*/ +static void Q3_SetVelocity( int entID, int axis, float speed ) +{ + gentity_t *found = &g_entities[entID]; + //FIXME: Not supported + if(!found) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetVelocity invalid entID %d\n", entID); + return; + } + + if(!found->client) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetVelocity: not a client %d\n", entID); + return; + } + + //FIXME: add or set? + found->client->ps.velocity[axis] += speed; + + found->client->ps.pm_time = 500; + found->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; +} + +/* +============ +Q3_SetAdjustAreaPortals + Description : + Return type : void + Argument : int entID + Argument : qboolean shields +============ +*/ +static void Q3_SetAdjustAreaPortals( int entID, qboolean adjust ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAdjustAreaPortals: invalid entID %d\n", entID); + return; + } + + ent->svFlags = (adjust) ? (ent->svFlags|SVF_MOVER_ADJ_AREA_PORTALS) : (ent->svFlags&~SVF_MOVER_ADJ_AREA_PORTALS); +} + +/* +============ +Q3_SetDmgByHeavyWeapOnly + Description : + Return type : void + Argument : int entID + Argument : qboolean dmg +============ +*/ +static void Q3_SetDmgByHeavyWeapOnly( int entID, qboolean dmg ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetDmgByHeavyWeapOnly: invalid entID %d\n", entID); + return; + } + + ent->flags = (dmg) ? (ent->flags|FL_DMG_BY_HEAVY_WEAP_ONLY) : (ent->flags&~FL_DMG_BY_HEAVY_WEAP_ONLY); +} + +/* +============ +Q3_SetShielded + Description : + Return type : void + Argument : int entID + Argument : qboolean dmg +============ +*/ +static void Q3_SetShielded( int entID, qboolean dmg ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetShielded: invalid entID %d\n", entID); + return; + } + + ent->flags = (dmg) ? (ent->flags|FL_SHIELDED) : (ent->flags&~FL_SHIELDED); +} + +/* +============ +Q3_SetNoGroups + Description : + Return type : void + Argument : int entID + Argument : qboolean dmg +============ +*/ +static void Q3_SetNoGroups( int entID, qboolean noGroups ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoGroups: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoGroups: ent %s is not an NPC!\n", ent->targetname ); + return; + } + + ent->NPC->scriptFlags = noGroups ? (ent->NPC->scriptFlags|SCF_NO_GROUPS) : (ent->NPC->scriptFlags&~SCF_NO_GROUPS); +} + +/* +============= +moverCallback + +Utility function +============= +*/ +extern void misc_model_breakable_gravity_init( gentity_t *ent, qboolean dropToFloor ); +void moverCallback( gentity_t *ent ) +{ + //complete the task + Q3_TaskIDComplete( ent, TID_MOVE_NAV ); + + // play sound + ent->s.loopSound = 0;//stop looping sound + G_PlayDoorSound( ent, BMS_END );//play end sound + + if ( ent->moverState == MOVER_1TO2 ) + {//reached open + // reached pos2 + MatchTeam( ent, MOVER_POS2, level.time ); + //SetMoverState( ent, MOVER_POS2, level.time ); + } + else if ( ent->moverState == MOVER_2TO1 ) + {//reached closed + MatchTeam( ent, MOVER_POS1, level.time ); + //SetMoverState( ent, MOVER_POS1, level.time ); + //close the portal + if ( ent->svFlags & SVF_MOVER_ADJ_AREA_PORTALS ) + { + gi.AdjustAreaPortalState( ent, qfalse ); + } + } + + if ( ent->e_BlockedFunc == blockedF_Blocked_Mover ) + { + ent->e_BlockedFunc = blockedF_NULL; + } + + if ( !Q_stricmp( "misc_model_breakable", ent->classname ) && ent->physicsBounce ) + {//a gravity-affected model + misc_model_breakable_gravity_init( ent, qfalse ); + } +} + +/* +============= +anglerCallback + +Utility function +============= +*/ +void anglerCallback( gentity_t *ent ) +{ + //Complete the task + Q3_TaskIDComplete( ent, TID_ANGLE_FACE ); + + // play sound + ent->s.loopSound = 0;//stop looping sound + G_PlayDoorSound( ent, BMS_END );//play end sound + + //Set the currentAngles, clear all movement + VectorMA( ent->s.apos.trBase, (ent->s.apos.trDuration*0.001f), ent->s.apos.trDelta, ent->currentAngles ); + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + VectorClear( ent->s.apos.trDelta ); + ent->s.apos.trDuration = 1; + ent->s.apos.trType = TR_STATIONARY; + ent->s.apos.trTime = level.time; + + //Stop thinking + ent->e_ReachedFunc = reachedF_NULL; + if ( ent->e_ThinkFunc == thinkF_anglerCallback ) + { + ent->e_ThinkFunc = thinkF_NULL; + } + + //link + gi.linkentity( ent ); +} + +/* +============= +moveAndRotateCallback + +Utility function +============= +*/ +void moveAndRotateCallback( gentity_t *ent ) +{ + //stop turning + anglerCallback( ent ); + //stop moving + moverCallback( ent ); +} + +void Blocked_Mover( gentity_t *ent, gentity_t *other ) { + // remove anything other than a client -- no longer the case + + // don't remove security keys or goodie keys + if ( (other->s.eType == ET_ITEM) && (other->item->giTag >= INV_GOODIE_KEY && other->item->giTag <= INV_SECURITY_KEY) ) + { + // should we be doing anything special if a key blocks it... move it somehow..? + } + // if your not a client, or your a dead client remove yourself... + else if ( other->s.number && (!other->client || (other->client && other->health <= 0 && other->contents == CONTENTS_CORPSE && !other->message)) ) + { + if ( !IIcarusInterface::GetIcarus()->IsRunning( other->m_iIcarusID ) /*!other->taskManager || !other->taskManager->IsRunning()*/ ) + { + // if an item or weapon can we do a little explosion..? + G_FreeEntity( other ); + return; + } + } + + if ( ent->damage ) { + G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH ); + } +} + +/* +============= +Q3_Lerp2Start + +Lerps the origin of an entity to its starting position +============= +// NEEDED??? +*/ +/*static void Q3_Lerp2Start( int entID, int taskID, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + + if(!ent) + { + Quake3Game()->DebugPrint( WL_WARNING, "Q3_Lerp2Start: invalid entID %d\n", entID); + return; + } + + if ( ent->client || ent->NPC || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + Quake3Game()->DebugPrint( WL_ERROR, "Q3_Lerp2Start: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + //FIXME: set up correctly!!! + ent->moverState = MOVER_2TO1; + ent->s.eType = ET_MOVER; + ent->e_ReachedFunc = reachedF_moverCallback; //Callsback the the completion of the move + if ( ent->damage ) + { + ent->e_BlockedFunc = blockedF_Blocked_Mover; + } + + ent->s.pos.trDuration = duration * 10; //In seconds + ent->s.pos.trTime = level.time; + + Q3_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + gi.linkentity( ent ); +}*/ + +/* +============= +Q3_Lerp2End + +Lerps the origin of an entity to its ending position +============= +// NEEDED??? +*/ +/*static void Q3_Lerp2End( int entID, int taskID, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + + if(!ent) + { + Quake3Game()->DebugPrint( WL_WARNING, "Q3_Lerp2End: invalid entID %d\n", entID); + return; + } + + if ( ent->client || ent->NPC || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + Quake3Game()->DebugPrint( WL_ERROR, "Q3_Lerp2End: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + if ( ent->moverState == MOVER_POS1 ) + {//open the portal + if ( ent->svFlags & SVF_MOVER_ADJ_AREA_PORTALS ) + { + gi.AdjustAreaPortalState( ent, qtrue ); + } + } + + //FIXME: set up correctly!!! + ent->moverState = MOVER_1TO2; + ent->s.eType = ET_MOVER; + ent->e_ReachedFunc = reachedF_moverCallback; //Callsback the the completion of the move + if ( ent->damage ) + { + ent->e_BlockedFunc = blockedF_Blocked_Mover; + } + + ent->s.pos.trDuration = duration * 10; //In seconds + ent->s.time = level.time; + + Q3_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + gi.linkentity( ent ); +}*/ + +/* +============= +Q3_Lerp2Pos + +Lerps the origin and angles of an entity to the destination values + +============= +*/ +/*static void Q3_Lerp2Pos( int taskID, int entID, vec3_t origin, vec3_t angles, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + vec3_t ang; + int i; + + if(!ent) + { + Quake3Game()->DebugPrint( WL_WARNING, "Q3_Lerp2Pos: invalid entID %d\n", entID); + return; + } + + if ( ent->client || ent->NPC || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + Quake3Game()->DebugPrint( WL_ERROR, "Q3_Lerp2Pos: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + //Don't allow a zero duration + if ( duration == 0 ) + duration = 1; + + // + // Movement + + moverState_t moverState = ent->moverState; + + if ( moverState == MOVER_POS1 || moverState == MOVER_2TO1 ) + { + VectorCopy( ent->currentOrigin, ent->pos1 ); + VectorCopy( origin, ent->pos2 ); + + if ( moverState == MOVER_POS1 ) + {//open the portal + if ( ent->svFlags & SVF_MOVER_ADJ_AREA_PORTALS ) + { + gi.AdjustAreaPortalState( ent, qtrue ); + } + } + + moverState = MOVER_1TO2; + } + else /*if ( moverState == MOVER_POS2 || moverState == MOVER_1TO2 )*/ +/* { + VectorCopy( ent->currentOrigin, ent->pos2 ); + VectorCopy( origin, ent->pos1 ); + + moverState = MOVER_2TO1; + } + + InitMoverTrData( ent ); + + ent->s.pos.trDuration = duration; + + // start it going + MatchTeam( ent, moverState, level.time ); + //SetMoverState( ent, moverState, level.time ); + + //Only do the angles if specified + if ( angles != NULL ) + { + // + // Rotation + + for ( i = 0; i < 3; i++ ) + { + ang[i] = AngleDelta( angles[i], ent->currentAngles[i] ); + ent->s.apos.trDelta[i] = ( ang[i] / ( duration * 0.001f ) ); + } + + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + + if ( ent->alt_fire ) + { + ent->s.apos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.apos.trType = TR_NONLINEAR_STOP; + } + ent->s.apos.trDuration = duration; + + ent->s.apos.trTime = level.time; + + ent->e_ReachedFunc = reachedF_moveAndRotateCallback; + Q3_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + } + else + { + //Setup the last bits of information + ent->e_ReachedFunc = reachedF_moverCallback; + } + + if ( ent->damage ) + { + ent->e_BlockedFunc = blockedF_Blocked_Mover; + } + + Q3_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + gi.linkentity( ent ); + +}*/ + +/* +============= +Q3_Lerp2Origin + +Lerps the origin to the destination value +============= +*/ +static void Q3_Lerp2Origin( int taskID, int entID, vec3_t origin, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + + if(!ent) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_Lerp2Origin: invalid entID %d\n", entID); + return; + } + + if ( ent->client || ent->NPC || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_Lerp2Origin: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + moverState_t moverState = ent->moverState; + + if ( moverState == MOVER_POS1 || moverState == MOVER_2TO1 ) + { + VectorCopy( ent->currentOrigin, ent->pos1 ); + VectorCopy( origin, ent->pos2 ); + + if ( moverState == MOVER_POS1 ) + {//open the portal + if ( ent->svFlags & SVF_MOVER_ADJ_AREA_PORTALS ) + { + gi.AdjustAreaPortalState( ent, qtrue ); + } + } + + moverState = MOVER_1TO2; + } + else if ( moverState == MOVER_POS2 || moverState == MOVER_1TO2 ) + { + VectorCopy( ent->currentOrigin, ent->pos2 ); + VectorCopy( origin, ent->pos1 ); + + moverState = MOVER_2TO1; + } + + InitMoverTrData( ent ); //FIXME: This will probably break normal things that are being moved... + + ent->s.pos.trDuration = duration; + + // start it going + MatchTeam( ent, moverState, level.time ); + //SetMoverState( ent, moverState, level.time ); + + ent->e_ReachedFunc = reachedF_moverCallback; + if ( ent->damage ) + { + ent->e_BlockedFunc = blockedF_Blocked_Mover; + } + if ( taskID != -1 ) + { + Q3_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + } + // starting sound + G_PlayDoorLoopSound( ent );//start looping sound + G_PlayDoorSound( ent, BMS_START ); //play start sound + + gi.linkentity( ent ); +} + +static void Q3_SetOriginOffset( int entID, int axis, float offset ) +{ + gentity_t *ent = &g_entities[entID]; + + if(!ent) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetOriginOffset: invalid entID %d\n", entID); + return; + } + + if ( ent->client || ent->NPC || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetOriginOffset: ent %d is NOT a mover!\n", entID); + return; + } + + vec3_t origin; + VectorCopy( ent->s.origin, origin ); + origin[axis] += offset; + float duration = 0; + if ( ent->speed ) + { + duration = fabs(offset)/fabs(ent->speed)*1000.0f; + } + Q3_Lerp2Origin( -1, entID, origin, duration ); +} +/* +============= +Q3_LerpAngles + +Lerps the angles to the destination value +============= +*/ +/*static void Q3_Lerp2Angles( int taskID, int entID, vec3_t angles, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + vec3_t ang; + int i; + + if(!ent) + { + Quake3Game()->DebugPrint( WL_WARNING, "Q3_Lerp2Angles: invalid entID %d\n", entID); + return; + } + + if ( ent->client || ent->NPC || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + Quake3Game()->DebugPrint( WL_ERROR, "Q3_Lerp2Angles: ent %d is NOT a mover!\n", entID); + return; + } + + //If we want an instant move, don't send 0... + ent->s.apos.trDuration = (duration>0) ? duration : 1; + + for ( i = 0; i < 3; i++ ) + { + ang [i] = AngleSubtract( angles[i], ent->currentAngles[i]); + ent->s.apos.trDelta[i] = ( ang[i] / ( ent->s.apos.trDuration * 0.001f ) ); + } + + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + + if ( ent->alt_fire ) + { + ent->s.apos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.apos.trType = TR_NONLINEAR_STOP; + } + + ent->s.apos.trTime = level.time; + + Q3_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + + //ent->e_ReachedFunc = reachedF_NULL; + ent->e_ThinkFunc = thinkF_anglerCallback; + ent->nextthink = level.time + duration; + + gi.linkentity( ent ); +}*/ + +/* +============= +Q3_GetTag + +Gets the value of a tag by the give name +============= +*/ +/*static int Q3_GetTag( int entID, const char *name, int lookup, vec3_t info ) +{ + gentity_t *ent = &g_entities[ entID ]; + + VALIDATEB( ent ); + + switch ( lookup ) + { + case TYPE_ORIGIN: + //return TAG_GetOrigin( ent->targetname, name, info ); + return TAG_GetOrigin( ent->ownername, name, info ); + break; + + case TYPE_ANGLES: + //return TAG_GetAngles( ent->targetname, name, info ); + return TAG_GetAngles( ent->ownername, name, info ); + break; + } + + return false; +}*/ + +/* +============= +Q3_SetNavGoal + +Sets the navigational goal of an entity +============= +*/ +static qboolean Q3_SetNavGoal( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[ entID ]; + vec3_t goalPos; + + if ( !ent->health ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNavGoal: tried to set a navgoal (\"%s\") on a corpse! \"%s\"\n", name, ent->script_targetname ); + return qfalse; + } + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNavGoal: tried to set a navgoal (\"%s\") on a non-NPC: \"%s\"\n", name, ent->script_targetname ); + return qfalse; + } + if ( !ent->NPC->tempGoal ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNavGoal: tried to set a navgoal (\"%s\") on a dead NPC: \"%s\"\n", name, ent->script_targetname ); + return qfalse; + } + if ( !ent->NPC->tempGoal->inuse ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNavGoal: NPC's (\"%s\") navgoal is freed: \"%s\"\n", name, ent->script_targetname ); + return qfalse; + } + if( Q_stricmp( "null", name) == 0 + || Q_stricmp( "NULL", name) == 0 ) + { + ent->NPC->goalEntity = NULL; + Q3_TaskIDComplete( ent, TID_MOVE_NAV ); + return qfalse; + } + else + { + //Get the position of the goal + if ( TAG_GetOrigin2( NULL, name, goalPos ) == false ) + { + gentity_t *targ = G_Find(NULL, FOFS(targetname), (char*)name); + if ( !targ ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNavGoal: can't find NAVGOAL \"%s\"\n", name ); + return qfalse; + } + else + { + ent->NPC->goalEntity = targ; + ent->NPC->goalRadius = sqrt(ent->maxs[0]+ent->maxs[0]) + sqrt(targ->maxs[0]+targ->maxs[0]); + ent->NPC->aiFlags &= ~NPCAI_TOUCHED_GOAL; + } + } + else + { + int goalRadius = TAG_GetRadius( NULL, name ); + NPC_SetMoveGoal( ent, goalPos, goalRadius, qtrue ); + //We know we want to clear the lastWaypoint here + ent->NPC->goalEntity->lastWaypoint = WAYPOINT_NONE; + ent->NPC->aiFlags &= ~NPCAI_TOUCHED_GOAL; + #ifdef _DEBUG + //this is *only* for debugging navigation + ent->NPC->tempGoal->target = G_NewString( name ); + #endif// _DEBUG + return qtrue; + } + } + return qfalse; +} + +//----------------------------------------------- + +/* +============ +SetLowerAnim + Description : + Return type : static void + Argument : int entID + Argument : int animID +============ +*/ +static void SetLowerAnim( int entID, int animID) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "SetLowerAnim: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "SetLowerAnim: ent %d is NOT a player or NPC!\n", entID); + return; + } + + NPC_SetAnim(ent,SETANIM_LEGS,animID,SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE); +} + + +/* +============ +SetUpperAnim + Description : + Return type : static void + Argument : int entID + Argument : int animID +============ +*/ +static void SetUpperAnim ( int entID, int animID) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "SetUpperAnim: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "SetLowerAnim: ent %d is NOT a player or NPC!\n", entID); + return; + } + + NPC_SetAnim(ent,SETANIM_TORSO,animID,SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE); +} + +//----------------------------------------------- + +/* +============= +Q3_SetAnimUpper + +Sets the upper animation of an entity +============= +*/ +static qboolean Q3_SetAnimUpper( int entID, const char *anim_name ) +{ + int animID = 0; + + animID = GetIDForString( animTable, anim_name ); + + if( animID == -1 ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAnimUpper: unknown animation sequence '%s'\n", anim_name ); + return qfalse; + } + + if ( !PM_HasAnimation( &g_entities[entID], animID ) ) + { + return qfalse; + } + + SetUpperAnim( entID, animID ); + return qtrue; +} + + +/* +============= +Q3_SetAnimLower + +Sets the lower animation of an entity +============= +*/ +static qboolean Q3_SetAnimLower( int entID, const char *anim_name ) +{ + int animID = 0; + + //FIXME: Setting duck anim does not actually duck! + + animID = GetIDForString( animTable, anim_name ); + + if( animID == -1 ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAnimLower: unknown animation sequence '%s'\n", anim_name ); + return qfalse; + } + + if ( !PM_HasAnimation( &g_entities[entID], animID ) ) + { + return qfalse; + } + + SetLowerAnim( entID, animID ); + return qtrue; +} + +/* +============ +Q3_SetAnimHoldTime + Description : + Return type : static void + Argument : int entID + Argument : int int_data + Argument : qboolean lower +============ +*/ +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); +static void Q3_SetAnimHoldTime( int entID, int int_data, qboolean lower ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAnimHoldTime: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetAnimHoldTime: ent %d is NOT a player or NPC!\n", entID); + return; + } + + if(lower) + { + PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, int_data ); + } + else + { + PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoAnimTimer, int_data ); + } +} + +/* +============= +Q3_SetEnemy + +Sets the enemy of an entity +============= +*/ +static void Q3_SetEnemy( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetEnemy: invalid entID %d\n", entID); + return; + } + + if( !Q_stricmp("NONE", name) || !Q_stricmp("NULL", name)) + { + if(ent->NPC) + { + G_ClearEnemy(ent); + } + else + { + ent->enemy = NULL; + } + } + else + { + gentity_t *enemy = G_Find( NULL, FOFS(targetname), (char *) name); + + if(enemy == NULL) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetEnemy: no such enemy: '%s'\n", name ); + return; + } + /*else if(enemy->health <= 0) + { + //Quake3Game()->DebugPrint( WL_ERROR, "Q3_SetEnemy: ERROR - desired enemy has health %d\n", enemy->health ); + return; + }*/ + else + { + if(ent->NPC) + { + G_SetEnemy( ent, enemy ); + ent->cantHitEnemyCounter = 0; + } + else + { + G_SetEnemy(ent, enemy); + } + } + } +} + + +/* +============= +Q3_SetLeader + +Sets the leader of an NPC +============= +*/ +static void Q3_SetLeader( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetLeader: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetLeader: ent %d is NOT a player or NPC!\n", entID); + return; + } + + if( !Q_stricmp("NONE", name) || !Q_stricmp("NULL", name)) + { + ent->client->leader = NULL; + } + else + { + gentity_t *leader = G_Find( NULL, FOFS(targetname), (char *) name); + + if(leader == NULL) + { + //Quake3Game()->DebugPrint( WL_ERROR,"Q3_SetEnemy: unable to locate enemy: '%s'\n", name ); + return; + } + else if(leader->health <= 0) + { + //Quake3Game()->DebugPrint( WL_ERROR,"Q3_SetEnemy: ERROR - desired enemy has health %d\n", enemy->health ); + return; + } + else + { + ent->client->leader = leader; + } + } +} + +stringID_table_t teamTable [] = +{ + ENUM2STRING(TEAM_FREE), +// ENUM2STRING(TEAM_STARFLEET), +// ENUM2STRING(TEAM_BORG), +// ENUM2STRING(TEAM_PARASITE), +// ENUM2STRING(TEAM_SCAVENGERS), +// ENUM2STRING(TEAM_KLINGON), +// ENUM2STRING(TEAM_MALON), +// ENUM2STRING(TEAM_HIROGEN), +// ENUM2STRING(TEAM_IMPERIAL), +// ENUM2STRING(TEAM_STASIS), +// ENUM2STRING(TEAM_8472), +// ENUM2STRING(TEAM_BOTS), +// ENUM2STRING(TEAM_FORGE), +// ENUM2STRING(TEAM_DISGUISE), + ENUM2STRING(TEAM_PLAYER), + ENUM2STRING(TEAM_ENEMY), + ENUM2STRING(TEAM_NEUTRAL), + "", TEAM_FREE, +}; + + +/* +============ +Q3_SetPlayerTeam + Description : + Return type : static void + Argument : int entID + Argument : const char *teamName +============ +*/ +static void Q3_SetPlayerTeam( int entID, const char *teamName ) +{ + gentity_t *ent = &g_entities[entID]; + team_t newTeam; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetPlayerTeam: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetPlayerTeam: ent %d is NOT a player or NPC!\n", entID); + return; + } + + newTeam = (team_t)GetIDForString( teamTable, teamName ); + ent->client->playerTeam = newTeam; +} + + +/* +============ +Q3_SetEnemyTeam + Description : + Return type : static void + Argument : int entID + Argument : const char *teamName +============ +*/ +static void Q3_SetEnemyTeam( int entID, const char *teamName ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetEnemyTeam: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetEnemyTeam: ent %d is NOT a player or NPC!\n", entID); + return; + } + + ent->client->enemyTeam = (team_t)GetIDForString( teamTable, teamName ); +} + + +/* +============ +Q3_SetHealth + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetHealth( int entID, int data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetHealth: invalid entID %d\n", entID); + return; + } + + // FIXME : should we really let you set health on a dead guy? + // this close to gold I won't change it, but warn you about it + if( ent->health <= 0 ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetHealth: trying to set health on a dead guy! %d\n", entID); + } + + if ( data < 0 ) + { + data = 0; + } + + ent->health = data; + + // should adjust max if new health is higher than max + if ( ent->health > ent->max_health ) + { + ent->max_health = ent->health; + } + + if(!ent->client) + { + return; + } + + ent->client->ps.stats[STAT_HEALTH] = data; + if ( ent->s.number == 0 ) + {//clamp health to max + if ( ent->client->ps.stats[STAT_HEALTH] > ent->client->ps.stats[STAT_MAX_HEALTH] ) + { + ent->health = ent->client->ps.stats[STAT_HEALTH] = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + if ( data == 0 ) + {//artificially "killing" the player", don't let him respawn right away + ent->client->ps.pm_type = PM_DEAD; + //delay respawn for 2 seconds + ent->client->respawnTime = level.time + 2000; + //stop all scripts + if (Q_stricmpn(level.mapname,"_holo",5)) { + stop_icarus = qtrue; + } + //make the team killable + //G_MakeTeamVulnerable(); + } + } +} + +/* +============ +Q3_SetArmor + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetArmor( int entID, int data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetArmor: invalid entID %d\n", entID); + return; + } + + if(!ent->client) + { + return; + } + + ent->client->ps.stats[STAT_ARMOR] = data; + if ( ent->s.number == 0 ) + {//clamp armor to max_health + if ( ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_HEALTH] ) + { + ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + } +} + +/* +============ +Q3_SetBState + Description : + Return type : static qboolean + Argument : int entID + Argument : const char *bs_name +FIXME: this should be a general NPC wrapper function + that is called ANY time a bState is changed... +============ +*/ +static qboolean Q3_SetBState( int entID, const char *bs_name ) +{ + gentity_t *ent = &g_entities[entID]; + bState_t bSID; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetBState: invalid entID %d\n", entID); + return qtrue; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetBState: '%s' is not an NPC\n", ent->targetname ); + return qtrue;//ok to complete + } + + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + if ( bSID > -1 ) + { + if ( bSID == BS_SEARCH || bSID == BS_WANDER ) + { + //FIXME: Reimplement + + if( ent->waypoint != WAYPOINT_NONE ) + { + NPC_BSSearchStart( ent->waypoint, bSID ); + } + else + { + ent->waypoint = NAV::GetNearestNode(ent); + + if( ent->waypoint != WAYPOINT_NONE ) + { + NPC_BSSearchStart( ent->waypoint, bSID ); + } + else + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetBState: '%s' is not in a valid waypoint to search from!\n", ent->targetname ); + return qtrue; + } + } + } + + + ent->NPC->tempBehavior = BS_DEFAULT;//need to clear any temp behaviour + if ( ent->NPC->behaviorState == BS_NOCLIP && bSID != BS_NOCLIP ) + {//need to rise up out of the floor after noclipping + ent->currentOrigin[2] += 0.125; + G_SetOrigin( ent, ent->currentOrigin ); + gi.linkentity( ent ); + } + ent->NPC->behaviorState = bSID; + if ( bSID == BS_DEFAULT ) + { + ent->NPC->defaultBehavior = bSID; + } + } + + ent->NPC->aiFlags &= ~NPCAI_TOUCHED_GOAL; + +// if ( bSID == BS_FLY ) +// {//FIXME: need a set bState wrapper +// ent->client->moveType = MT_FLYSWIM; +// } +// else + { + //FIXME: these are presumptions! + //Q3_SetGravity( entID, g_gravity->value ); + //ent->client->moveType = MT_RUNJUMP; + } + + if ( bSID == BS_NOCLIP ) + { + ent->client->noclip = true; + } + else + { + ent->client->noclip = false; + } + +/* + if ( bSID == BS_FACE || bSID == BS_POINT_AND_SHOOT || bSID == BS_FACE_ENEMY ) + { + ent->NPC->aimTime = level.time + 5 * 1000;//try for 5 seconds + return qfalse;//need to wait for task complete message + } +*/ + +// if ( bSID == BS_SNIPER || bSID == BS_ADVANCE_FIGHT ) + if ( bSID == BS_ADVANCE_FIGHT ) + { + return qfalse;//need to wait for task complete message + } + +/* + if ( bSID == BS_SHOOT || bSID == BS_POINT_AND_SHOOT ) + {//Let them shoot right NOW + ent->NPC->shotTime = ent->attackDebounceTime = level.time; + } +*/ + if ( bSID == BS_JUMP ) + { + ent->NPC->jumpState = JS_FACING; + } + + return qtrue;//ok to complete +} + + +/* +============ +Q3_SetTempBState + Description : + Return type : static qboolean + Argument : int entID + Argument : const char *bs_name +============ +*/ +static qboolean Q3_SetTempBState( int entID, const char *bs_name ) +{ + gentity_t *ent = &g_entities[entID]; + bState_t bSID; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetTempBState: invalid entID %d\n", entID); + return qtrue; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetTempBState: '%s' is not an NPC\n", ent->targetname ); + return qtrue;//ok to complete + } + + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + if ( bSID > -1 ) + { + ent->NPC->tempBehavior = bSID; + } + +/* + if ( bSID == BS_FACE || bSID == BS_POINT_AND_SHOOT || bSID == BS_FACE_ENEMY ) + { + ent->NPC->aimTime = level.time + 5 * 1000;//try for 5 seconds + return qfalse;//need to wait for task complete message + } +*/ + +/* + if ( bSID == BS_SHOOT || bSID == BS_POINT_AND_SHOOT ) + {//Let them shoot right NOW + ent->NPC->shotTime = ent->attackDebounceTime = level.time; + } +*/ + return qtrue;//ok to complete +} + + +/* +============ +Q3_SetDefaultBState + Description : + Return type : static void + Argument : int entID + Argument : const char *bs_name +============ +*/ +static void Q3_SetDefaultBState( int entID, const char *bs_name ) +{ + gentity_t *ent = &g_entities[entID]; + bState_t bSID; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetDefaultBState: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetDefaultBState: '%s' is not an NPC\n", ent->targetname ); + return; + } + + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + if ( bSID > -1 ) + { + ent->NPC->defaultBehavior = bSID; + } +} + + +/* +============ +Q3_SetDPitch + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetDPitch( int entID, float data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetDPitch: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC || !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetDPitch: '%s' is not an NPC\n", ent->targetname ); + return; + } + + int pitchMin = -ent->client->renderInfo.headPitchRangeUp + 1; + int pitchMax = ent->client->renderInfo.headPitchRangeDown - 1; + + //clamp angle to -180 -> 180 + data = AngleNormalize180( data ); + + //Clamp it to my valid range + if ( data < -1 ) + { + if ( data < pitchMin ) + { + data = pitchMin; + } + } + else if ( data > 1 ) + { + if ( data > pitchMax ) + { + data = pitchMax; + } + } + + ent->NPC->lockedDesiredPitch = ent->NPC->desiredPitch = data; +} + + +/* +============ +Q3_SetDYaw + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetDYaw( int entID, float data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetDYaw: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetDYaw: '%s' is not an NPC\n", ent->targetname ); + return; + } + + if(!ent->enemy) + {//don't mess with this if they're aiming at someone + ent->NPC->lockedDesiredYaw = ent->NPC->desiredYaw = ent->s.angles[1] = data; + } + else + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Could not set DYAW: '%s' has an enemy (%s)!\n", ent->targetname, ent->enemy->targetname ); + } +} + + +/* +============ +Q3_SetShootDist + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetShootDist( int entID, float data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetShootDist: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetShootDist: '%s' is not an NPC\n", ent->targetname ); + return; + } + + ent->NPC->stats.shootDistance = data; +} + + +/* +============ +Q3_SetVisrange + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetVisrange( int entID, float data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetVisrange: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetVisrange: '%s' is not an NPC\n", ent->targetname ); + return; + } + + ent->NPC->stats.visrange = data; +} + + +/* +============ +Q3_SetEarshot + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetEarshot( int entID, float data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetEarshot: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetEarshot: '%s' is not an NPC\n", ent->targetname ); + return; + } + + ent->NPC->stats.earshot = data; +} + + +/* +============ +Q3_SetVigilance + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetVigilance( int entID, float data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetVigilance: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetVigilance: '%s' is not an NPC\n", ent->targetname ); + return; + } + + ent->NPC->stats.vigilance = data; +} + + +/* +============ +Q3_SetVFOV + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetVFOV( int entID, int data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetVFOV: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetVFOV: '%s' is not an NPC\n", ent->targetname ); + return; + } + + ent->NPC->stats.vfov = data; +} + + +/* +============ +Q3_SetHFOV + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetHFOV( int entID, int data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetHFOV: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetHFOV: '%s' is not an NPC\n", ent->targetname ); + return; + } + + ent->NPC->stats.hfov = data; +} + + +/* +============ +Q3_SetWidth + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetWidth( int entID, int data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetWidth: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetWidth: '%s' is not an NPC\n", ent->targetname ); + return; + } + + ent->maxs[0] = ent->maxs[1] = data; + ent->mins[0] = ent->mins[1] = -data; +} + +/* +============ +Q3_GetTimeScale + Description : + Return type : static DWORD + Argument : void +============ +*/ +// NEEDED??? +/*static DWORD Q3_GetTimeScale( void ) +{ + //return Q3_TIME_SCALE; + return g_timescale->value; +}*/ + + +/* +============ +Q3_SetTimeScale + Description : + Return type : static void + Argument : int entID + Argument : const char *data +============ +*/ +static void Q3_SetTimeScale( int entID, const char *data ) +{ + // if we're skipping the script DO NOT allow timescale to be set (skipping needs it at 100) + if ( g_skippingcin->integer ) + { + return; + } + + gi.cvar_set("timescale", data); +} + + +/* +============ +Q3_SetInvisible + Description : + Return type : static void + Argument : int entID + Argument : qboolean invisible +============ +*/ +static void Q3_SetInvisible( int entID, qboolean invisible ) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetInvisible: invalid entID %d\n", entID); + return; + } + + if ( invisible ) + { + self->s.eFlags |= EF_NODRAW; + if ( self->client ) + { + self->client->ps.eFlags |= EF_NODRAW; + } + self->contents = 0; + } + else + { + self->s.eFlags &= ~EF_NODRAW; + if ( self->client ) + { + self->client->ps.eFlags &= ~EF_NODRAW; + } + } +} + +/* +============ +Q3_SetVampire + Description : + Return type : static void + Argument : int entID + Argument : qboolean vampire +============ +*/ +static void Q3_SetVampire( int entID, qboolean vampire ) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self || !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetVampire: entID %d not a client\n", entID); + return; + } + + if ( vampire ) + { + self->client->ps.powerups[PW_DISINT_2] = Q3_INFINITE; + } + else + { + self->client->ps.powerups[PW_DISINT_2] = 0; + } +} +/* +============ +Q3_SetGreetAllies + Description : + Return type : static void + Argument : int entID + Argument : qboolean greet +============ +*/ +static void Q3_SetGreetAllies( int entID, qboolean greet ) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetGreetAllies: invalid entID %d\n", entID); + return; + } + + if ( !self->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetGreetAllies: ent %s is not an NPC!\n", self->targetname ); + return; + } + + if ( greet ) + { + self->NPC->aiFlags |= NPCAI_GREET_ALLIES; + } + else + { + self->NPC->aiFlags &= ~NPCAI_GREET_ALLIES; + } +} + + +/* +============ +Q3_SetViewTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *name +============ +*/ +static void Q3_SetViewTarget (int entID, const char *name) +{ + gentity_t *self = &g_entities[entID]; + gentity_t *viewtarget = G_Find( NULL, FOFS(targetname), (char *) name); + vec3_t viewspot, selfspot, viewvec, viewangles; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetViewTarget: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetViewTarget: '%s' is not a player/NPC!\n", self->targetname ); + return; + } + + //FIXME: Exception handle here + if (viewtarget == NULL) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetViewTarget: can't find ViewTarget: '%s'\n", name ); + return; + } + + //FIXME: should we set behavior to BS_FACE and keep facing this ent as it moves + //around for a script-specified length of time...? + VectorCopy ( self->currentOrigin, selfspot ); + selfspot[2] += self->client->ps.viewheight; + + if ( viewtarget->client && (!g_skippingcin || !g_skippingcin->integer ) ) + { + VectorCopy ( viewtarget->client->renderInfo.eyePoint, viewspot ); + } + else + { + VectorCopy ( viewtarget->currentOrigin, viewspot ); + } + + VectorSubtract( viewspot, selfspot, viewvec ); + + vectoangles( viewvec, viewangles ); + + Q3_SetDYaw( entID, viewangles[YAW] ); + if ( !g_skippingcin || !g_skippingcin->integer ) + { + Q3_SetDPitch( entID, viewangles[PITCH] ); + } +} + + +/* +============ +Q3_SetWatchTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *name +============ +*/ +static void Q3_SetWatchTarget (int entID, const char *name) +{ + gentity_t *self = &g_entities[entID]; + gentity_t *watchTarget = NULL; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetWatchTarget: invalid entID %d\n", entID); + return; + } + + if ( !self->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetWatchTarget: '%s' is not an NPC!\n", self->targetname ); + return; + } + + if ( Q_stricmp( "NULL", name ) == 0 || Q_stricmp( "NONE", name ) == 0 || ( self->targetname && (Q_stricmp( self->targetname, name ) == 0) ) ) + {//clearing watchTarget + self->NPC->watchTarget = NULL; + } + + watchTarget = G_Find( NULL, FOFS(targetname), (char *) name); + if ( watchTarget == NULL ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetWatchTarget: can't find WatchTarget: '%s'\n", name ); + return; + } + + self->NPC->watchTarget = watchTarget; +} + +void Q3_SetLoopSound(int entID, const char *name) +{ + sfxHandle_t index; + gentity_t *self = &g_entities[entID]; + + if ( Q_stricmp( "NULL", name ) == 0 || Q_stricmp( "NONE", name )==0) + { + self->s.loopSound = 0; + return; + } + + if ( self->s.eType == ET_MOVER ) + { + index = cgi_S_RegisterSound( name ); + } + else + { + index = G_SoundIndex( name ); + } + + if (index) + { + self->s.loopSound = index; + } + else + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetLoopSound: can't find sound file: '%s'\n", name ); + } +} + +void Q3_SetICARUSFreeze( int entID, const char *name, qboolean freeze ) +{ + gentity_t *self = G_Find( NULL, FOFS(targetname), name ); + if ( !self ) + {//hmm, targetname failed, try script_targetname? + self = G_Find( NULL, FOFS(script_targetname), name ); + } + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetICARUSFreeze: invalid ent %s\n", name); + return; + } + + if ( freeze ) + { + self->svFlags |= SVF_ICARUS_FREEZE; + } + else + { + self->svFlags &= ~SVF_ICARUS_FREEZE; + } +} + +/* +============ +Q3_SetViewEntity + Description : + Return type : static void + Argument : int entID + Argument : const char *name +============ +*/ +extern qboolean G_ClearViewEntity( gentity_t *ent ); +extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity ); +void Q3_SetViewEntity(int entID, const char *name) +{ + gentity_t *self = &g_entities[entID]; + gentity_t *viewtarget = G_Find( NULL, FOFS(targetname), (char *) name); + + if ( entID != 0 ) + {//only valid on player + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetViewEntity: only valid on player\n", entID); + return; + } + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetViewEntity: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetViewEntity: '%s' is not a player!\n", self->targetname ); + return; + } + + if ( !name ) + { + G_ClearViewEntity( self ); + return; + } + + if ( viewtarget == NULL ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetViewEntity: can't find ViewEntity: '%s'\n", name ); + return; + } + + G_SetViewEntity( self, viewtarget ); +} + +/* +============ +Q3_SetWeapon + Description : + Return type : static void + Argument : int entID + Argument : const char *wp_name +============ +*/ +extern gentity_t *TossClientItems( gentity_t *self ); +void G_SetWeapon( gentity_t *self, int wp ) +{ + qboolean hadWeapon = qfalse; + + if ( !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetWeapon: '%s' is not a player/NPC!\n", self->targetname ); + return; + } + + if ( self->NPC ) + {//since a script sets a weapon, we presume we don't want to auto-match the player's weapon anymore + self->NPC->aiFlags &= ~NPCAI_MATCHPLAYERWEAPON; + } + + if(wp == WP_NONE) + {//no weapon + self->client->ps.weapon = WP_NONE; + G_RemoveWeaponModels( self ); + if ( self->s.number < MAX_CLIENTS ) + {//make sure the cgame-side knows this + CG_ChangeWeapon( wp ); + } + return; + } + + gitem_t *item = FindItemForWeapon( (weapon_t) wp); + RegisterItem( item ); //make sure the weapon is cached in case this runs at startup + + if ( self->client->ps.stats[STAT_WEAPONS]&( 1 << wp ) ) + { + hadWeapon = qtrue; + } + if ( self->NPC ) + {//Should NPCs have only 1 weapon at a time? + self->client->ps.stats[STAT_WEAPONS] = ( 1 << wp ); + self->client->ps.ammo[weaponData[wp].ammoIndex] = 999; + + ChangeWeapon( self, wp ); + self->client->ps.weapon = wp; + self->client->ps.weaponstate = WEAPON_READY;//WEAPON_RAISING; + G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" )); + } + else + { + self->client->ps.stats[STAT_WEAPONS] |= ( 1 << wp ); + self->client->ps.ammo[weaponData[wp].ammoIndex] = ammoData[weaponData[wp].ammoIndex].max; + + G_AddEvent( self, EV_ITEM_PICKUP, (item - bg_itemlist) ); + //force it to change + CG_ChangeWeapon( wp ); + G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" )); + } + G_RemoveWeaponModels( self ); + + if ( wp == WP_SABER ) + { + if ( !hadWeapon ) + { + WP_SaberInitBladeData( self ); + } + WP_SaberAddG2SaberModels( self ); + } + else + { + G_CreateG2AttachedWeaponModel( self, weaponData[wp].weaponMdl, self->handRBolt, 0 ); + } +} + +static void Q3_SetWeapon (int entID, const char *wp_name) +{ + gentity_t *self = &g_entities[entID]; + int wp = GetIDForString( WPTable, wp_name ); + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetWeapon: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetWeapon: '%s' is not a player/NPC!\n", self->targetname ); + return; + } + + if ( self->NPC ) + {//since a script sets a weapon, we presume we don't want to auto-match the player's weapon anymore + self->NPC->aiFlags &= ~NPCAI_MATCHPLAYERWEAPON; + } + + if(!Q_stricmp("drop", wp_name)) + {//no weapon, drop it + TossClientItems( self ); + self->client->ps.weapon = WP_NONE; + G_RemoveWeaponModels( self ); + return; + } + + G_SetWeapon( self, wp ); +} + +/* +============ +Q3_SetItem + Description : + Return type : static void + Argument : int entID + Argument : const char *wp_name +============ +*/ +static void Q3_SetItem (int entID, const char *item_name) +{ + gentity_t *self = &g_entities[entID]; + int inv; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetWeapon: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetWeapon: '%s' is not a player/NPC!\n", self->targetname ); + return; + } + + inv = GetIDForString( INVTable, item_name ); + + + gitem_t *item = FindItemForInventory(inv); + RegisterItem( item ); //make sure the item is cached in case this runs at startup + +// G_AddEvent( self, EV_ITEM_PICKUP, (item - bg_itemlist) ); +// G_AddEvent( self, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" )); + + self->client->ps.stats[STAT_ITEMS] |= (1<giTag); + + if( (inv == INV_ELECTROBINOCULARS) || (inv == INV_LIGHTAMP_GOGGLES) ) + { + self->client->ps.inventory[inv] = 1; + return; + } + // else Bacta, seeker, sentry + if( self->client->ps.inventory[inv] < 5 ) + { + self->client->ps.inventory[inv]++; + } +} + + + +/* +============ +Q3_SetWalkSpeed + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetWalkSpeed (int entID, int int_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetWalkSpeed: invalid entID %d\n", entID); + return; + } + + if ( !self->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetWalkSpeed: '%s' is not an NPC!\n", self->targetname ); + return; + } + + if(int_data == 0) + { + self->NPC->stats.walkSpeed = self->client->ps.speed = 1; + } + + self->NPC->stats.walkSpeed = self->client->ps.speed = int_data; +} + + +/* +============ +Q3_SetRunSpeed + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetRunSpeed (int entID, int int_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetRunSpeed: invalid entID %d\n", entID); + return; + } + + if ( !self->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetRunSpeed: '%s' is not an NPC!\n", self->targetname ); + return; + } + + if(int_data == 0) + { + self->NPC->stats.runSpeed = self->client->ps.speed = 1; + } + + self->NPC->stats.runSpeed = self->client->ps.speed = int_data; +} + + +/* +============ +Q3_SetYawSpeed + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetYawSpeed (int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetYawSpeed: invalid entID %d\n", entID); + return; + } + + if ( !self->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetYawSpeed: '%s' is not an NPC!\n", self->targetname ); + return; + } + + self->NPC->stats.yawSpeed = float_data; +} + + +/* +============ +Q3_SetAggression + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetAggression(int entID, int int_data) +{ + gentity_t *self = &g_entities[entID]; + + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAggression: invalid entID %d\n", entID); + return; + } + + if ( !self->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetAggression: '%s' is not an NPC!\n", self->targetname ); + return; + } + + if(int_data < 1 || int_data > 5) + return; + + self->NPC->stats.aggression = int_data; +} + + +/* +============ +Q3_SetAim + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetAim(int entID, int int_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAim: invalid entID %d\n", entID); + return; + } + + if ( !self->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetAim: '%s' is not an NPC!\n", self->targetname ); + return; + } + + if(int_data < 1 || int_data > 5) + return; + + self->NPC->stats.aim = int_data; +} + + +/* +============ +Q3_SetFriction + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetFriction(int entID, int int_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetFriction: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetFriction: '%s' is not an NPC/player!\n", self->targetname ); + return; + } + + self->client->ps.friction = int_data; +} + + +/* +============ +Q3_SetGravity + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetGravity(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetGravity: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetGravity: '%s' is not an NPC/player!\n", self->targetname ); + return; + } + + //FIXME: what if we want to return them to normal global gravity? + self->svFlags |= SVF_CUSTOM_GRAVITY; + self->client->ps.gravity = float_data; +} + + +/* +============ +Q3_SetWait + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetWait(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetWait: invalid entID %d\n", entID); + return; + } + + self->wait = float_data; +} + + +static void Q3_SetShotSpacing(int entID, int int_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetShotSpacing: invalid entID %d\n", entID); + return; + } + + if ( !self->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetShotSpacing: '%s' is not an NPC!\n", self->targetname ); + return; + } + + self->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + self->NPC->burstSpacing = int_data; +} + +/* +============ +Q3_SetFollowDist + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetFollowDist(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetFollowDist: invalid entID %d\n", entID); + return; + } + + if ( !self->client || !self->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetFollowDist: '%s' is not an NPC!\n", self->targetname ); + return; + } + + self->NPC->followDist = float_data; +} + + +/* +============ +Q3_SetScale + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetScale(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetScale: invalid entID %d\n", entID); + return; + } + + self->s.scale = float_data; +} + + +/* +============ +Q3_SetRenderCullRadius + Description : allows NPCs to be drawn even when their origin is very far away from their model + Return type : static void + Argument : int entID + Argument : float float_data (the new radius for render culling) +============ +*/ +static void Q3_SetRenderCullRadius(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetRenderCullRadius: invalid entID %d\n", entID); + return; + } + + self->s.radius = float_data; +} + + +/* +============ +Q3_SetCount + Description : + Return type : static void + Argument : int entID + Argument : const char *data +============ +*/ +static void Q3_SetCount(int entID, const char *data) +{ + gentity_t *self = &g_entities[entID]; + float val = 0.0f; + + //FIXME: use FOFS() stuff here to make a generic entity field setting? + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetCount: invalid entID %d\n", entID); + return; + } + + if ( (val = Q3_CheckStringCounterIncrement( data )) ) + { + self->count += (int)(val); + } + else + { + self->count = atoi((char *) data); + } +} + + +/* +============ +Q3_SetSquadName + Description : + Return type : static void + Argument : int entID + Argument : const char *squadname +============ +*/ +/* +static void Q3_SetSquadName (int entID, const char *squadname) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetSquadName: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetSquadName: '%s' is not an NPC/player!\n", self->targetname ); + return; + } + + if(!Q_stricmp("NULL", ((char *)squadname))) + { + self->client->squadname = NULL; + } + else + { + self->client->squadname = G_NewString(squadname); + } +} +*/ + +/* +============ +Q3_SetTargetName + Description : + Return type : static void + Argument : int entID + Argument : const char *targetname +============ +*/ +static void Q3_SetTargetName (int entID, const char *targetname) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetTargetName: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)targetname))) + { + self->targetname = NULL; + } + else + { + self->targetname = G_NewString( targetname ); + } +} + + +/* +============ +Q3_SetTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *target +============ +*/ +static void Q3_SetTarget (int entID, const char *target) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetTarget: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)target))) + { + self->target = NULL; + } + else + { + self->target = G_NewString( target ); + } +} + +/* +============ +Q3_SetTarget2 + Description : + Return type : static void + Argument : int entID + Argument : const char *target +============ +*/ +static void Q3_SetTarget2 (int entID, const char *target2) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetTarget2: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)target2))) + { + self->target2 = NULL; + } + else + { + self->target2 = G_NewString( target2 ); + } +} +/* +============ +Q3_SetRemoveTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *target +============ +*/ +static void Q3_SetRemoveTarget (int entID, const char *target) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetRemoveTarget: invalid entID %d\n", entID); + return; + } + + if ( !self->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetRemoveTarget: '%s' is not an NPC!\n", self->targetname ); + return; + } + + if( !Q_stricmp("NULL", ((char *)target)) ) + { + self->target3 = NULL; + } + else + { + self->target3 = G_NewString( target ); + } +} + + +/* +============ +Q3_SetPainTarget + Description : + Return type : void + Argument : int entID + Argument : const char *targetname +============ +*/ +static void Q3_SetPainTarget (int entID, const char *targetname) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetPainTarget: invalid entID %d\n", entID); + return; + } + + if(Q_stricmp("NULL", ((char *)targetname)) == 0) + { + self->paintarget = NULL; + } + else + { + self->paintarget = G_NewString((char *)targetname); + } +} + +static void Q3_SetMusicState( const char *dms ) +{ + int newDMS = GetIDForString( DMSTable, dms ); + if ( newDMS != -1 ) + { + level.dmState = newDMS; + } +} + +static void Q3_SetForcePowerLevel ( int entID, int forcePower, int forceLevel ) +{ + if ( forcePower < FP_FIRST || forceLevel >= NUM_FORCE_POWERS ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetForcePowerLevel: Force Power index %d out of range (%d-%d)\n", forcePower, FP_FIRST, (NUM_FORCE_POWERS-1) ); + return; + } + + if ( forceLevel < 0 || forceLevel >= NUM_FORCE_POWER_LEVELS ) + { + if ( forcePower != FP_SABER_OFFENSE || forceLevel >= SS_NUM_SABER_STYLES ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetForcePowerLevel: Force power setting %d out of range (0-3)\n", forceLevel ); + return; + } + } + + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetForcePowerLevel: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetForcePowerLevel: ent %s is not a player or NPC\n", self->targetname ); + return; + } + + if ((forceLevel > self->client->ps.forcePowerLevel[forcePower]) && (entID==0) && (forceLevel > 0)) + { + if (0) + { + if (!cg_updatedDataPadForcePower1.integer) + { + missionInfo_Updated = qtrue; // Activate flashing text + gi.cvar_set("cg_updatedDataPadForcePower1", va("%d",forcePower+1)); // The +1 is offset in the print routine. It ain't pretty, I know. + cg_updatedDataPadForcePower1.integer = forcePower+1; + } + else if (!cg_updatedDataPadForcePower2.integer) + { + missionInfo_Updated = qtrue; // Activate flashing text + gi.cvar_set("cg_updatedDataPadForcePower2", va("%d",forcePower+1)); // The +1 is offset in the print routine. It ain't pretty, I know. + cg_updatedDataPadForcePower2.integer = forcePower+1; + } + else if (!cg_updatedDataPadForcePower3.integer) + { + missionInfo_Updated = qtrue; // Activate flashing text + gi.cvar_set("cg_updatedDataPadForcePower3", va("%d",forcePower+1)); // The +1 is offset in the print routine. It ain't pretty, I know. + cg_updatedDataPadForcePower3.integer = forcePower+1; + } + } + } + + self->client->ps.forcePowerLevel[forcePower] = forceLevel; + if ( forceLevel ) + { + self->client->ps.forcePowersKnown |= ( 1 << forcePower ); + } + else + { + self->client->ps.forcePowersKnown &= ~( 1 << forcePower ); + } +} + +extern qboolean G_InventorySelectable( int index,gentity_t *other); +static void Q3_GiveSecurityKey( int entID, char *keyname ) +{ + gentity_t *other = &g_entities[entID]; + int i, original; + + if ( !other ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_GiveSecurityKey: invalid entID %d\n", entID); + return; + } + + if ( !other->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_GiveSecurityKey: ent %s is not a player or NPC\n", other->targetname ); + return; + } + + if ( !keyname || !keyname[0] || !Q_stricmp( "none", keyname ) || !Q_stricmp( "null", keyname ) ) + {//remove the key + if ( other->message ) + {//remove it + INV_SecurityKeyTake( other, other->message ); + } + return; + } + + other->client->ps.stats[STAT_ITEMS] |= (1<= INV_MAX)) + { + cg.inventorySelect = (INV_MAX - 1); + } + + if ( G_InventorySelectable( cg.inventorySelect,other ) ) + { + return; + } + cg.inventorySelect++; + } + + cg.inventorySelect = original; +} + +/* +============ +Q3_SetParm + Description : + Return type : void + Argument : int entID + Argument : int parmNum + Argument : const char *parmValue +============ +*/ +void Q3_SetParm (int entID, int parmNum, const char *parmValue) +{ + gentity_t *ent = &g_entities[entID]; + float val; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetParm: invalid entID %d\n", entID); + return; + } + + if ( parmNum < 0 || parmNum >= MAX_PARMS ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "SET_PARM: parmNum %d out of range!\n", parmNum ); + return; + } + + if( !ent->parms ) + { + ent->parms = (parms_t *)G_Alloc( sizeof(parms_t) ); + memset( ent->parms, 0, sizeof(parms_t) ); + } + + if ( (val = Q3_CheckStringCounterIncrement( parmValue )) ) + { + val += atof( ent->parms->parm[parmNum] ); + sprintf( ent->parms->parm[parmNum], "%f", val ); + } + else + {//Just copy the string + //copy only 16 characters + strncpy( ent->parms->parm[parmNum], parmValue, sizeof(ent->parms->parm[0]) ); + //set the last charcter to null in case we had to truncate their passed string + if ( ent->parms->parm[parmNum][sizeof(ent->parms->parm[0]) - 1] != 0 ) + {//Tried to set a string that is too long + ent->parms->parm[parmNum][sizeof(ent->parms->parm[0]) - 1] = 0; + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "SET_PARM: parm%d string too long, truncated to '%s'!\n", parmNum, ent->parms->parm[parmNum] ); + } + } +} + + + +/* +============= +Q3_SetCaptureGoal + +Sets the capture spot goal of an entity +============= +*/ +static void Q3_SetCaptureGoal( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + gentity_t *goal = G_Find( NULL, FOFS(targetname), (char *) name); + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetCaptureGoal: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetCaptureGoal: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + //FIXME: Exception handle here + if (goal == NULL) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetCaptureGoal: can't find CaptureGoal target: '%s'\n", name ); + return; + } + + if(ent->NPC) + { + ent->NPC->captureGoal = goal; + ent->NPC->goalEntity = goal; + ent->NPC->goalTime = level.time + 100000; + } +} + +/* +============= +Q3_SetEvent + +? +============= +*/ +static void Q3_SetEvent( int entID, const char *event_name ) +{ + gentity_t *ent = &g_entities[entID]; +// gentity_t *tent = NULL; + int event; +// vec3_t spot; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetEvent: invalid entID %d\n", entID); + return; + } + + event = GetIDForString( eventTable, event_name ); + switch( event ) + { +/* + case EV_DISINTEGRATE: + if( VectorCompare( ent->currentOrigin, vec3_origin ) ) + {//Brush with no origin + VectorSubtract( ent->absmax, ent->absmin, spot ); + VectorMA( ent->absmin, 0.5, spot, spot ); + } + else + { + VectorCopy( ent->currentOrigin, spot ); + spot[2] += ent->maxs[2]/2; + } + tent = G_TempEntity( spot, EV_DISINTEGRATION ); + tent->s.eventParm = PW_REGEN; + tent->owner = ent; + break; + +*/ + case EV_BAD: + default: + //Quake3Game()->DebugPrint( IGameInterface::WL_ERROR,"Q3_SetEvent: Invalid Event %d\n", event ); + return; + break; + } +} + +/* +============ +Q3_Use + +Uses an entity +============ +*/ +/*static void Q3_Use( int entID, const char *target ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_Use: invalid entID %d\n", entID); + return; + } + + if( !target || !target[0] ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_Use: string is NULL!\n" ); + return; + } + + G_UseTargets2(ent, ent, target); +}*/ + + +/* +============ +Q3_SetBehaviorSet + +? +============ +*/ +static qboolean Q3_SetBehaviorSet( int entID, int toSet, const char *scriptname) +{ + gentity_t *ent = &g_entities[entID]; + bSet_t bSet = BSET_INVALID; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetBehaviorSet: invalid entID %d\n", entID); + return qfalse; + } + + switch(toSet) + { + case SET_SPAWNSCRIPT: + bSet = BSET_SPAWN; + break; + case SET_USESCRIPT: + bSet = BSET_USE; + break; + case SET_AWAKESCRIPT: + bSet = BSET_AWAKE; + break; + case SET_ANGERSCRIPT: + bSet = BSET_ANGER; + break; + case SET_ATTACKSCRIPT: + bSet = BSET_ATTACK; + break; + case SET_VICTORYSCRIPT: + bSet = BSET_VICTORY; + break; + case SET_LOSTENEMYSCRIPT: + bSet = BSET_LOSTENEMY; + break; + case SET_PAINSCRIPT: + bSet = BSET_PAIN; + break; + case SET_FLEESCRIPT: + bSet = BSET_FLEE; + break; + case SET_DEATHSCRIPT: + bSet = BSET_DEATH; + break; + case SET_DELAYEDSCRIPT: + bSet = BSET_DELAYED; + break; + case SET_BLOCKEDSCRIPT: + bSet = BSET_BLOCKED; + break; + case SET_FFIRESCRIPT: + bSet = BSET_FFIRE; + break; + case SET_FFDEATHSCRIPT: + bSet = BSET_FFDEATH; + break; + case SET_MINDTRICKSCRIPT: + bSet = BSET_MINDTRICK; + break; + } + + if(bSet < BSET_SPAWN || bSet >= NUM_BSETS) + { + return qfalse; + } + + if(!Q_stricmp("NULL", scriptname)) + { + if ( ent->behaviorSet[bSet] != NULL ) + { +// gi.TagFree( ent->behaviorSet[bSet] ); + } + + ent->behaviorSet[bSet] = NULL; + //memset( &ent->behaviorSet[bSet], 0, sizeof(ent->behaviorSet[bSet]) ); + } + else + { + if ( scriptname ) + { + if ( ent->behaviorSet[bSet] != NULL ) + { +// gi.TagFree( ent->behaviorSet[bSet] ); + } + + ent->behaviorSet[bSet] = G_NewString( (char *) scriptname ); //FIXME: This really isn't good... + } + + //ent->behaviorSet[bSet] = scriptname; + //strncpy( (char *) &ent->behaviorSet[bSet], scriptname, MAX_BSET_LENGTH ); + } + return qtrue; +} + +/* +============ +Q3_SetDelayScriptTime + +? +============ +*/ +static void Q3_SetDelayScriptTime(int entID, int delayTime) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetDelayScriptTime: invalid entID %d\n", entID); + return; + } + + ent->delayScriptTime = level.time + delayTime; +} + + +/* +============ +Q3_SetIgnorePain + +? +============ +*/ +static void Q3_SetIgnorePain( int entID, qboolean data) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetIgnorePain: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetIgnorePain: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + ent->NPC->ignorePain = data; +} + +/* +============ +Q3_SetIgnoreEnemies + +? +============ +*/ +static void Q3_SetIgnoreEnemies( int entID, qboolean data) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetIgnoreEnemies: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetIgnoreEnemies: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(data) + { + ent->svFlags |= SVF_IGNORE_ENEMIES; + } + else + { + ent->svFlags &= ~SVF_IGNORE_ENEMIES; + } +} + +/* +============ +Q3_SetIgnoreAlerts + +? +============ +*/ +static void Q3_SetIgnoreAlerts( int entID, qboolean data) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetIgnoreAlerts: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetIgnoreAlerts: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(data) + { + ent->NPC->scriptFlags |= SCF_IGNORE_ALERTS; + } + else + { + ent->NPC->scriptFlags &= ~SCF_IGNORE_ALERTS; + } +} + + +/* +============ +Q3_SetNoTarget + +? +============ +*/ +static void Q3_SetNoTarget( int entID, qboolean data) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoTarget: invalid entID %d\n", entID); + return; + } + + if(data) + ent->flags |= FL_NOTARGET; + else + ent->flags &= ~FL_NOTARGET; +} + +/* +============ +Q3_SetDontShoot + +? +============ +*/ +static void Q3_SetDontShoot( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetDontShoot: invalid entID %d\n", entID); + return; + } + + if(add) + { + ent->flags |= FL_DONT_SHOOT; + } + else + { + ent->flags &= ~FL_DONT_SHOOT; + } +} + +/* +============ +Q3_SetDontFire + +? +============ +*/ +static void Q3_SetDontFire( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetDontFire: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetDontFire: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_DONT_FIRE; + } + else + { + ent->NPC->scriptFlags &= ~SCF_DONT_FIRE; + } +} + +/* +============ +Q3_SetFireWeapon + +? +============ +*/ +static void Q3_SetFireWeapon(int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_FireWeapon: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetFireWeapon: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_FIRE_WEAPON; + } + else + { + ent->NPC->scriptFlags &= ~SCF_FIRE_WEAPON; + } +} + +/* +============ +Q3_SetFireWeaponNoAnim + +? +============ +*/ +static void Q3_SetFireWeaponNoAnim(int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_FireWeaponNoAnim: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetFireWeaponNoAnim: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_FIRE_WEAPON_NO_ANIM; + } + else + { + ent->NPC->scriptFlags &= ~SCF_FIRE_WEAPON_NO_ANIM; + } +} + +/* +============ +Q3_SetSafeRemove + +If true, NPC will remove itself once player is not in PVS +============ +*/ +static void Q3_SetSafeRemove(int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetSafeRemove: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetSafeRemove: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_SAFE_REMOVE; + } + else + { + ent->NPC->scriptFlags &= ~SCF_SAFE_REMOVE; + } +} + +/* +============ +Q3_SetBobaJetPack + +Turn on/off Boba's jet pack +============ +*/ +static void Q3_SetBobaJetPack(int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetBobaJetPack: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetBobaJetPack: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + // make sure we this is Boba Fett + if ( ent->client && ent->client->NPC_class != CLASS_BOBAFETT ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetBobaJetPack: '%s' is not Boba Fett!\n", ent->targetname ); + return; + } + + if(add) + { + if ( ent->genericBolt1 != -1 ) + { + G_PlayEffect( G_EffectIndex( "boba/jetSP" ), ent->playerModel, ent->genericBolt1, ent->s.number, ent->currentOrigin, qtrue, qtrue ); + } + if ( ent->genericBolt2 != -1 ) + { + G_PlayEffect( G_EffectIndex( "boba/jetSP" ), ent->playerModel, ent->genericBolt2, ent->s.number, ent->currentOrigin, qtrue, qtrue ); + } + //take-off sound + G_SoundOnEnt( ent, CHAN_ITEM, "sound/chars/boba/bf_blast-off.wav" ); + //jet loop sound + ent->s.loopSound = G_SoundIndex( "sound/chars/boba/bf_jetpack_lp.wav" ); + } + else + { + if ( ent->genericBolt1 != -1 ) + { + G_StopEffect( "boba/jetSP", ent->playerModel, ent->genericBolt1, ent->s.number ); + } + if ( ent->genericBolt2 != -1 ) + { + G_StopEffect( "boba/jetSP", ent->playerModel, ent->genericBolt2, ent->s.number ); + } + //stop jet loop sound + ent->s.loopSound = 0; + G_SoundOnEnt( ent, CHAN_ITEM, "sound/chars/boba/bf_land.wav" ); + + } +} +/* +============ +Q3_SetInactive + +? +============ +*/ +static void Q3_SetInactive(int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetInactive: invalid entID %d\n", entID); + return; + } + + if(add) + { + ent->svFlags |= SVF_INACTIVE; + } + else + { + ent->svFlags &= ~SVF_INACTIVE; + } +} + +/* +============ +Q3_SetFuncUsableVisible + +? +============ +*/ +static void Q3_SetFuncUsableVisible(int entID, qboolean visible ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetFuncUsableVisible: invalid entID %d\n", entID); + return; + } + + // Yeah, I know that this doesn't even do half of what the func_usable use code does, but if I've got two things on top of each other...and only + // one is visible at a time....and neither can ever be used......and finally, the shader on it has the shader_anim stuff going on....It doesn't seem + // like I can easily use the other version without nasty side effects. + if( visible ) + { + ent->svFlags &= ~SVF_NOCLIENT; + ent->s.eFlags &= ~EF_NODRAW; + } + else + { + ent->svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + } +} + +/* +============ +Q3_SetLockedEnemy + +? +============ +*/ +static void Q3_SetLockedEnemy ( int entID, qboolean locked) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetLockedEnemy: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetLockedEnemy: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + //FIXME: make an NPCAI_FLAG + if(locked) + { + ent->svFlags |= SVF_LOCKEDENEMY; + } + else + { + ent->svFlags &= ~SVF_LOCKEDENEMY; + } +} + +char cinematicSkipScript[64]; + +/* +============ +Q3_SetCinematicSkipScript + +============ +*/ +static void Q3_SetCinematicSkipScript( char *scriptname ) +{ + + if(Q_stricmp("none", scriptname) == 0 || Q_stricmp("NULL", scriptname) == 0) + { + cinematicSkipScript[0] = 0; + } + else + { + Q_strncpyz(cinematicSkipScript,scriptname,sizeof(cinematicSkipScript)); + } + +} + +/* +============ +Q3_SetNoMindTrick + +? +============ +*/ +static void Q3_SetNoMindTrick( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoMindTrick: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNoMindTrick: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_NO_MIND_TRICK; + ent->NPC->confusionTime = 0; + if ( ent->ghoul2.size() && ent->headBolt != -1 ) + { + G_StopEffect("force/confusion", ent->playerModel, ent->headBolt, ent->s.number ); + } + } + else + { + ent->NPC->scriptFlags &= ~SCF_NO_MIND_TRICK; + } +} + +/* +============ +Q3_SetCrouched + +? +============ +*/ +static void Q3_SetCrouched( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetCrouched: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetCrouched: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_CROUCHED; + } + else + { + ent->NPC->scriptFlags &= ~SCF_CROUCHED; + } +} + +/* +============ +Q3_SetWalking + +? +============ +*/ +static void Q3_SetWalking( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetWalking: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetWalking: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_WALKING; + } + else + { + ent->NPC->scriptFlags &= ~SCF_WALKING; + } +} + +/* +============ +Q3_SetRunning + +? +============ +*/ +static void Q3_SetRunning( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetRunning: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetRunning: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_RUNNING; + } + else + { + ent->NPC->scriptFlags &= ~SCF_RUNNING; + } +} + +/* +============ +Q3_SetForcedMarch + +? +============ +*/ +static void Q3_SetForcedMarch( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetForcedMarch: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetForcedMarch: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_FORCED_MARCH; + } + else + { + ent->NPC->scriptFlags &= ~SCF_FORCED_MARCH; + } +} +/* +============ +Q3_SetChaseEnemies + +indicates whether the npc should chase after an enemy +============ +*/ +static void Q3_SetChaseEnemies( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetChaseEnemies: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetChaseEnemies: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_CHASE_ENEMIES; + } + else + { + ent->NPC->scriptFlags &= ~SCF_CHASE_ENEMIES; + } +} + +/* +============ +Q3_SetLookForEnemies + +if set npc will be on the look out for potential enemies +if not set, npc will ignore enemies +============ +*/ +static void Q3_SetLookForEnemies( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetLookForEnemies: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetLookForEnemies: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_LOOK_FOR_ENEMIES; + } + else + { + ent->NPC->scriptFlags &= ~SCF_LOOK_FOR_ENEMIES; + } +} + +/* +============ +Q3_SetFaceMoveDir + +============ +*/ +static void Q3_SetFaceMoveDir( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetFaceMoveDir: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetFaceMoveDir: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_FACE_MOVE_DIR; + } + else + { + ent->NPC->scriptFlags &= ~SCF_FACE_MOVE_DIR; + } +} + +/* +============ +Q3_SetAltFire + +? +============ +*/ +static void Q3_SetAltFire( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAltFire: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetAltFire: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + else + { + ent->NPC->scriptFlags &= ~SCF_ALT_FIRE; + } + + ChangeWeapon( ent, ent->client->ps.weapon ); + +} + +/* +============ +Q3_SetDontFlee + +? +============ +*/ +static void Q3_SetDontFlee( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetDontFlee: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetDontFlee: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_DONT_FLEE; + } + else + { + ent->NPC->scriptFlags &= ~SCF_DONT_FLEE; + } +} + +/* +============ +Q3_SetNoResponse + +? +============ +*/ +static void Q3_SetNoResponse( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoResponse: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNoResponse: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_NO_RESPONSE; + } + else + { + ent->NPC->scriptFlags &= ~SCF_NO_RESPONSE; + } +} + +/* +============ +Q3_SetCombatTalk + +? +============ +*/ +static void Q3_SetCombatTalk( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetCombatTalk: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetCombatTalk: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if ( add ) + { + ent->NPC->scriptFlags |= SCF_NO_COMBAT_TALK; + } + else + { + ent->NPC->scriptFlags &= ~SCF_NO_COMBAT_TALK; + } +} + +/* +============ +Q3_SetAlertTalk + +? +============ +*/ +static void Q3_SetAlertTalk( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAlertTalk: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetAlertTalk: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if ( add ) + { + ent->NPC->scriptFlags |= SCF_NO_ALERT_TALK; + } + else + { + ent->NPC->scriptFlags &= ~SCF_NO_ALERT_TALK; + } +} + +/* +============ +Q3_SetUseCpNearest + +? +============ +*/ +static void Q3_SetUseCpNearest( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetUseCpNearest: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetUseCpNearest: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if ( add ) + { + ent->NPC->scriptFlags |= SCF_USE_CP_NEAREST; + } + else + { + ent->NPC->scriptFlags &= ~SCF_USE_CP_NEAREST; + } +} + +/* +============ +Q3_SetNoForce + +? +============ +*/ +static void Q3_SetNoForce( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoForce: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNoForce: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if ( add ) + { + ent->NPC->scriptFlags |= SCF_NO_FORCE; + } + else + { + ent->NPC->scriptFlags &= ~SCF_NO_FORCE; + } +} + +/* +============ +Q3_SetNoAcrobatics + +? +============ +*/ +static void Q3_SetNoAcrobatics( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoAcrobatics: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNoAcrobatics: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if ( add ) + { + ent->NPC->scriptFlags |= SCF_NO_ACROBATICS; + } + else + { + ent->NPC->scriptFlags &= ~SCF_NO_ACROBATICS; + } +} + +/* +============ +Q3_SetUseSubtitles + +? +============ +*/ +static void Q3_SetUseSubtitles( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetUseSubtitles: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetUseSubtitles: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if ( add ) + { + ent->NPC->scriptFlags |= SCF_USE_SUBTITLES; + } + else + { + ent->NPC->scriptFlags &= ~SCF_USE_SUBTITLES; + } +} + +/* +============ +Q3_SetNoFallToDeath + +? +============ +*/ +static void Q3_SetNoFallToDeath( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoFallToDeath: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNoFallToDeath: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if ( add ) + { + ent->NPC->scriptFlags |= SCF_NO_FALLTODEATH; + } + else + { + ent->NPC->scriptFlags &= ~SCF_NO_FALLTODEATH; + } +} + +/* +============ +Q3_SetDismemberable + +? +============ +*/ +static void Q3_SetDismemberable( int entID, qboolean dismemberable) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetDismemberable: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetDismemberable: '%s' is not an client!\n", ent->targetname ); + return; + } + + ent->client->dismembered = !dismemberable; +} + + +/* +============ +Q3_SetMoreLight + +? +============ +*/ +static void Q3_SetMoreLight( int entID, qboolean add ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetMoreLight: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetMoreLight: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if ( add ) + { + ent->NPC->scriptFlags |= SCF_MORELIGHT; + } + else + { + ent->NPC->scriptFlags &= ~SCF_MORELIGHT; + } +} + +/* +============ +Q3_SetUndying + +? +============ +*/ +static void Q3_SetUndying( int entID, qboolean undying) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetUndying: invalid entID %d\n", entID); + return; + } + + if(undying) + { + ent->flags |= FL_UNDYING; + } + else + { + ent->flags &= ~FL_UNDYING; + } +} + +/* +============ +Q3_SetInvincible + +? +============ +*/ +static void Q3_SetInvincible( int entID, qboolean invincible) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetInvincible: invalid entID %d\n", entID); + return; + } + + if ( !Q_stricmp( "func_breakable", ent->classname ) ) + { + if ( invincible ) + { + ent->spawnflags |= 1; + } + else + { + ent->spawnflags &= ~1; + } + return; + } + + if ( invincible ) + { + ent->flags |= FL_GODMODE; + } + else + { + ent->flags &= ~FL_GODMODE; + } +} +/* +============ +Q3_SetForceInvincible + Description : + Return type : static void + Argument : int entID + Argument : qboolean forceInv +============ +*/ +static void Q3_SetForceInvincible( int entID, qboolean forceInv ) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self || !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetForceInvincible: entID %d not a client\n", entID); + return; + } + + Q3_SetInvincible( entID, forceInv ); + if ( forceInv ) + { + self->client->ps.powerups[PW_INVINCIBLE] = Q3_INFINITE; + } + else + { + self->client->ps.powerups[PW_INVINCIBLE] = 0; + } +} + +/* +============ +Q3_SetNoAvoid + +? +============ +*/ +static void Q3_SetNoAvoid( int entID, qboolean noAvoid) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoAvoid: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetNoAvoid: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(noAvoid) + { + ent->NPC->aiFlags |= NPCAI_NO_COLL_AVOID; + } + else + { + ent->NPC->aiFlags &= ~NPCAI_NO_COLL_AVOID; + } +} + +/* +============ +SolidifyOwner + Description : + Return type : void + Argument : gentity_t *self +============ +*/ +void SolidifyOwner( gentity_t *self ) +{ + self->nextthink = level.time + FRAMETIME; + self->e_ThinkFunc = thinkF_G_FreeEntity; + + if ( !self->owner || !self->owner->inuse ) + { + return; + } + + int oldContents = self->owner->contents; + self->owner->contents = CONTENTS_BODY; + if ( SpotWouldTelefrag2( self->owner, self->owner->currentOrigin ) ) + { + self->owner->contents = oldContents; + self->e_ThinkFunc = thinkF_SolidifyOwner; + } + else + { + if ( self->owner->NPC && !(self->owner->spawnflags & SFB_NOTSOLID) ) + { + self->owner->clipmask |= CONTENTS_BODY; + } + Q3_TaskIDComplete( self->owner, TID_RESIZE ); + } +} + + +/* +============ +Q3_SetSolid + +? +============ +*/ +static qboolean Q3_SetSolid( int entID, qboolean solid) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetSolid: invalid entID %d\n", entID); + return qtrue; + } + + if ( solid ) + {//FIXME: Presumption + int oldContents = ent->contents; + ent->contents = CONTENTS_BODY; + if ( SpotWouldTelefrag2( ent, ent->currentOrigin ) ) + { + gentity_t *solidifier = G_Spawn(); + + solidifier->owner = ent; + + solidifier->e_ThinkFunc = thinkF_SolidifyOwner; + solidifier->nextthink = level.time + FRAMETIME; + + ent->contents = oldContents; + return qfalse; + } + ent->clipmask |= CONTENTS_BODY; + } + else + {//FIXME: Presumption + if ( ent->s.eFlags & EF_NODRAW ) + {//We're invisible too, so set contents to none + ent->contents = 0; + } + else + { + ent->contents = CONTENTS_CORPSE; + } + if ( ent->NPC ) + { + if(!(ent->spawnflags & SFB_NOTSOLID)) + { + ent->clipmask &= ~CONTENTS_BODY; + } + } + } + return qtrue; +} +/* +============ +Q3_SetLean + +? +============ +*/ +#define LEAN_NONE 0 +#define LEAN_RIGHT 1 +#define LEAN_LEFT 2 +static void Q3_SetLean( int entID, int lean) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetLean: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetLean: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(lean == LEAN_RIGHT) + { + ent->NPC->scriptFlags |= SCF_LEAN_RIGHT; + ent->NPC->scriptFlags &= ~SCF_LEAN_LEFT; + } + else if(lean == LEAN_LEFT) + { + ent->NPC->scriptFlags |= SCF_LEAN_LEFT; + ent->NPC->scriptFlags &= ~SCF_LEAN_RIGHT; + } + else + { + ent->NPC->scriptFlags &= ~SCF_LEAN_LEFT; + ent->NPC->scriptFlags &= ~SCF_LEAN_RIGHT; + } +} + +/* +============ +Q3_SetForwardMove + +? +============ +*/ +static void Q3_SetForwardMove( int entID, int fmoveVal) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetForwardMove: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetForwardMove: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + ent->client->forced_forwardmove = fmoveVal; +} + +/* +============ +Q3_SetRightMove + +? +============ +*/ +static void Q3_SetRightMove( int entID, int rmoveVal) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetRightMove: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetRightMove: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + ent->client->forced_rightmove = rmoveVal; +} + +/* +============ +Q3_SetLockAngle + +? +============ +*/ +static void Q3_SetLockAngle( int entID, const char *lockAngle) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetLockAngle: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetLockAngle: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + if(Q_stricmp("off", lockAngle) == 0) + {//free it + ent->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE; + } + else + { + ent->client->renderInfo.renderFlags |= RF_LOCKEDANGLE; + + + if(Q_stricmp("auto", lockAngle) == 0) + {//use current yaw + if( ent->NPC ) // I need this to work on NPCs, so their locked value + { + ent->NPC->lockedDesiredYaw = NPC->client->ps.viewangles[YAW]; // could also set s.angles[1] and desiredYaw to this value... + } + else + { + ent->client->renderInfo.lockYaw = ent->client->ps.viewangles[YAW]; + } + } + else + {//specified yaw + if( ent->NPC ) // I need this to work on NPCs, so their locked value + { + ent->NPC->lockedDesiredYaw = atof((char *)lockAngle); // could also set s.angles[1] and desiredYaw to this value... + } + else + { + ent->client->renderInfo.lockYaw = atof((char *)lockAngle); + } + } + } +} + + +/* +============ +Q3_CameraGroup + +? +============ +*/ +static void Q3_CameraGroup( int entID, char *camG) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_CameraGroup: invalid entID %d\n", entID); + return; + } + + ent->cameraGroup = G_NewString(camG); +} + +extern camera_t client_camera; +/* +============ +Q3_CameraGroupZOfs + +? +============ +*/ +static void Q3_CameraGroupZOfs( float camGZOfs ) +{ + client_camera.cameraGroupZOfs = camGZOfs; +} +/* +============ +Q3_CameraGroup + +? +============ +*/ +static void Q3_CameraGroupTag( char *camGTag ) +{ + Q_strncpyz( client_camera.cameraGroupTag, camGTag, sizeof(client_camera.cameraGroupTag) ); +} + +/* +============ +Q3_RemoveRHandModel +============ +*/ +static void Q3_RemoveRHandModel( int entID, char *addModel) +{ + gentity_t *ent = &g_entities[entID]; + + if ( ent->cinematicModel >= 0 ) + { + gi.G2API_RemoveGhoul2Model(ent->ghoul2,ent->cinematicModel); + } +} + +/* +============ +Q3_AddRHandModel +============ +*/ +static void Q3_AddRHandModel( int entID, char *addModel) +{ + gentity_t *ent = &g_entities[entID]; + + ent->cinematicModel = gi.G2API_InitGhoul2Model(ent->ghoul2, addModel, G_ModelIndex( addModel )); + if ( ent->cinematicModel != -1 ) + { + // attach it to the hand + gi.G2API_AttachG2Model(&ent->ghoul2[ent->cinematicModel], &ent->ghoul2[ent->playerModel], + ent->handRBolt, ent->playerModel); + } +} + +/* +============ +Q3_AddLHandModel +============ +*/ +static void Q3_AddLHandModel( int entID, char *addModel) +{ + gentity_t *ent = &g_entities[entID]; + + ent->cinematicModel = gi.G2API_InitGhoul2Model(ent->ghoul2, addModel, G_ModelIndex( addModel )); + if ( ent->cinematicModel != -1 ) + { + // attach it to the hand + gi.G2API_AttachG2Model(&ent->ghoul2[ent->cinematicModel], &ent->ghoul2[ent->playerModel], + ent->handLBolt, ent->playerModel); + } +} + +/* +============ +Q3_RemoveLHandModel +============ +*/ +static void Q3_RemoveLHandModel( int entID, char *addModel) +{ + gentity_t *ent = &g_entities[entID]; + + if ( ent->cinematicModel >= 0 ) + { + gi.G2API_RemoveGhoul2Model(ent->ghoul2, ent->cinematicModel); + } +} + +/* +============ +Q3_LookTarget + +? +============ +*/ +static void Q3_LookTarget( int entID, char *targetName) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_LookTarget: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_LookTarget: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + if(Q_stricmp("none", targetName) == 0 || Q_stricmp("NULL", targetName) == 0) + {//clearing look target + NPC_ClearLookTarget( ent ); + return; + } + + gentity_t *targ = G_Find(NULL, FOFS(targetname), targetName); + if(!targ) + { + targ = G_Find(NULL, FOFS(script_targetname), targetName); + if (!targ) + { + targ = G_Find(NULL, FOFS(NPC_targetname), targetName); + if (!targ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_LookTarget: Can't find ent %s\n", targetName ); + return; + } + } + } + + NPC_SetLookTarget( ent, targ->s.number, 0 ); +} + +/* +============ +Q3_Face + +? +============ +*/ +static void Q3_Face( int entID,int expression, float holdtime) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_Face: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_Face: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + //FIXME: change to milliseconds to be consistant! + holdtime *= 1000; + + switch(expression) + { + case SET_FACEEYESCLOSED: + ent->client->facial_blink = 1; + break; + case SET_FACEEYESOPENED: + ent->client->facial_blink = -1; + break; + case SET_FACEBLINK: + ent->client->facial_timer = -(level.time + holdtime); + break; + case SET_FACEAUX: + ent->client->facial_timer = -(level.time + holdtime); + ent->client->facial_anim = FACE_ALERT; + break; + case SET_FACEBLINKFROWN: + ent->client->facial_blink = -(level.time + holdtime); +//fall through + case SET_FACEFROWN: + ent->client->facial_timer = -(level.time + holdtime); + ent->client->facial_anim = FACE_FROWN; + break; + + case SET_FACENORMAL: + ent->client->facial_timer = level.time + Q_flrand(6000.0, 10000.0); + ent->client->facial_blink = level.time + Q_flrand(3000.0, 5000.0); + break; + } + +} + + +/* +============ +Q3_SetPlayerUsable + Description : + Return type : void + Argument : int entID + Argument : qboolean usable +============ +*/ +static void Q3_SetPlayerUsable( int entID, qboolean usable ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetPlayerUsable: invalid entID %d\n", entID); + return; + } + + if(usable) + { + ent->svFlags |= SVF_PLAYER_USABLE; + } + else + { + ent->svFlags &= ~SVF_PLAYER_USABLE; + } +} + +/* +============ +Q3_SetDisableShaderAnims + Description : + Return type : static void + Argument : int entID + Argument : int disabled +============ +*/ +static void Q3_SetDisableShaderAnims( int entID, int disabled ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetDisableShaderAnims: invalid entID %d\n", entID); + return; + } + + if ( disabled ) + { + ent->s.eFlags |= EF_DISABLE_SHADER_ANIM; + } + else + { + ent->s.eFlags &= ~EF_DISABLE_SHADER_ANIM; + } +} + +/* +============ +Q3_SetShaderAnim + Description : + Return type : static void + Argument : int entID + Argument : int disabled +============ +*/ +static void Q3_SetShaderAnim( int entID, int disabled ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetShaderAnim: invalid entID %d\n", entID); + return; + } + + if ( disabled ) + { + ent->s.eFlags |= EF_SHADER_ANIM; + } + else + { + ent->s.eFlags &= ~EF_SHADER_ANIM; + } +} + +void Q3_SetBroadcast( int entID, qboolean broadcast ) +{ + gentity_t *ent = &g_entities[entID]; + if ( broadcast ) + { + ent->svFlags |= SVF_BROADCAST; + } + else + { + ent->svFlags &= ~SVF_BROADCAST; + } +} + +void Q3_SetForcePower( int entID, int forcePower, qboolean powerOn ) +{ + gentity_t *ent = &g_entities[entID]; + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetForcePower: invalid entID %d\n", entID); + return; + } + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetForcePower: ent # %d not a client!\n", entID ); + return; + } + if ( powerOn ) + { + ent->client->ps.forcePowersForced |= (1<client->ps.forcePowersForced &= ~(1<DebugPrint( IGameInterface::WL_WARNING, "Q3_SetStartFrame: invalid entID %d\n", entID); + return; + } + + if ( ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetLoopAnim: command not valid on players/NPCs!\n" ); + return; + } + + if ( startFrame >= 0 ) + { + ent->s.frame = startFrame; + ent->startFrame = startFrame; + } +} + + +/* +============ +Q3_SetEndFrame + Description : + Return type : static void + Argument : int entID + Argument : int endFrame +============ +*/ +static void Q3_SetEndFrame( int entID, int endFrame ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetEndFrame: invalid entID %d\n", entID); + return; + } + + if ( ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetEndFrame: command not valid on players/NPCs!\n" ); + return; + } + + if ( endFrame >= 0 ) + { + ent->endFrame = endFrame; + } +} + +/* +============ +Q3_SetAnimFrame + Description : + Return type : static void + Argument : int entID + Argument : int startFrame +============ +*/ +static void Q3_SetAnimFrame( int entID, int animFrame ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAnimFrame: invalid entID %d\n", entID); + return; + } + + if ( ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAnimFrame: command not valid on players/NPCs!\n" ); + return; + } + + if ( animFrame >= ent->endFrame ) + { + ent->s.frame = ent->endFrame; + } + else if ( animFrame >= ent->startFrame ) + { + ent->s.frame = animFrame; + } + else + { + // FIXME/NOTE: Set s.frame anyway?? + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetAnimFrame: value must be valid number between StartFrame and EndFrame.\n" ); + return; + } +} + +void InflateOwner( gentity_t *self ) +{ + self->nextthink = level.time + FRAMETIME; + self->e_ThinkFunc = thinkF_G_FreeEntity; + + if ( !self->owner || !self->owner->inuse ) + { + return; + } + + trace_t trace; + + gi.trace( &trace, self->currentOrigin, self->mins, self->maxs, self->currentOrigin, self->owner->s.number, self->owner->clipmask&~(CONTENTS_SOLID|CONTENTS_MONSTERCLIP) ); + if ( trace.allsolid || trace.startsolid ) + { + self->e_ThinkFunc = thinkF_InflateOwner; + return; + } + + if ( Q3_TaskIDPending( self->owner, TID_RESIZE ) ) + { + Q3_TaskIDComplete( self->owner, TID_RESIZE ); + + VectorCopy( self->mins, self->owner->mins ); + VectorCopy( self->maxs, self->owner->maxs ); + gi.linkentity( self->owner ); + } +} + + +/* +============ +Q3_SetLoopAnim + Description : + Return type : void + Argument : int entID + Argument : qboolean loopAnim +============ +*/ +static void Q3_SetLoopAnim( int entID, qboolean loopAnim ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetLoopAnim: invalid entID %d\n", entID); + return; + } + + if ( ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetLoopAnim: command not valid on players/NPCs!\n" ); + return; + } + + ent->loopAnim = loopAnim; +} + + +/* +============ +Q3_SetShields + Description : + Return type : void + Argument : int entID + Argument : qboolean shields +============ +*/ +static void Q3_SetShields( int entID, qboolean shields ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetShields: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetShields: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if ( shields ) + { + ent->NPC->aiFlags |= NPCAI_SHIELDS; + } + else + { + ent->NPC->aiFlags &= ~NPCAI_SHIELDS; + } +} + +/* +============ +Q3_SetSaberActive + Description : + Return type : void + Argument : int entID + Argument : qboolean shields +============ +*/ +static void Q3_SetSaberActive( int entID, qboolean active ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetSaberActive: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetSaberActive: '%s' is not an player/NPC!\n", ent->targetname ); + return; + } + + if ( ent->client->ps.weapon != WP_SABER ) + { + if ( (ent->client->ps.stats[STAT_WEAPONS]&(1<NPC ) + { + ChangeWeapon( ent, WP_SABER ); + } + else + { + gitem_t *item = FindItemForWeapon( WP_SABER ); + RegisterItem( item ); //make sure the weapon is cached in case this runs at startup + G_AddEvent( ent, EV_ITEM_PICKUP, (item - bg_itemlist) ); + CG_ChangeWeapon( WP_SABER ); + } + ent->client->ps.weapon = WP_SABER; + ent->client->ps.weaponstate = WEAPON_READY; + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" )); + } + else + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetSaberActive: '%s' is not using a saber!\n", ent->targetname ); + return; + } + } + + if ( active ) + { + ent->client->ps.SaberActivate(); + } + else + { + ent->client->ps.SaberDeactivate(); + } +} + +/* +============ + Name: Q3_SetSaberBladeActive + Description: Make a specific blade of a specific Saber active or inactive. + Created: 10/02/02 by Aurelio Reis, Modified: 10/02/02 by Aurelio Reis + [in] int entID The ID of the Entity to modify. + [in] int iSaber Which Saber to modify. + [in] int iBlade Which blade to modify (0 - (NUM_BLADES - 1)). + [in] bool bActive Whether to make it active (default true) or inactive (false). + [return] void +============ +*/ +static void Q3_SetSaberBladeActive( int entID, int iSaber, int iBlade, qboolean bActive = qtrue ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetSaberBladeActive: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetSaberBladeActive: '%s' is not an player/NPC!\n", ent->targetname ); + return; + } + + if ( ent->client->ps.weapon != WP_SABER ) + { + if ( (ent->client->ps.stats[STAT_WEAPONS]&(1<NPC ) + { + ChangeWeapon( ent, WP_SABER ); + } + else + { + gitem_t *item = FindItemForWeapon( WP_SABER ); + RegisterItem( item ); //make sure the weapon is cached in case this runs at startup + G_AddEvent( ent, EV_ITEM_PICKUP, (item - bg_itemlist) ); + CG_ChangeWeapon( WP_SABER ); + } + ent->client->ps.weapon = WP_SABER; + ent->client->ps.weaponstate = WEAPON_READY; + G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" )); + } + else + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_SetSaberBladeActive: '%s' is not using a saber!\n", ent->targetname ); + return; + } + } + + // Activate the Blade. + ent->client->ps.SaberBladeActivate( iSaber, iBlade, bActive ); +} + +/* +============ +Q3_SetNoKnockback + Description : + Return type : void + Argument : int entID + Argument : qboolean noKnockback +============ +*/ +static void Q3_SetNoKnockback( int entID, qboolean noKnockback ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoKnockback: invalid entID %d\n", entID); + return; + } + + if ( noKnockback ) + { + ent->flags |= FL_NO_KNOCKBACK; + } + else + { + ent->flags &= ~FL_NO_KNOCKBACK; + } +} + +/* +============ +Q3_SetCleanDamagingEnts + Description : + Return type : void +============ +*/ +static void Q3_SetCleanDamagingEnts( void ) +{ + gentity_t *ent = NULL; + + for ( int i = 0; i < ENTITYNUM_WORLD; i++ ) + { + if ( !PInUse( i )) + { + continue; + } + + ent = &g_entities[i]; + + if ( ent ) + { + if ( !ent->client && ( ent->s.weapon == WP_DET_PACK || ent->s.weapon == WP_TRIP_MINE || ent->s.weapon == WP_THERMAL )) + { + // check for a client, otherwise we could remove someone holding this weapon + G_FreeEntity( ent ); + } + else if ( ent->s.weapon == WP_TURRET && ent->activator && ent->activator->s.number == 0 && !Q_stricmp( "PAS", ent->classname )) + { + // is a player owner personal assault sentry gun. + G_FreeEntity( ent ); + } + else if ( ent->client && ent->client->NPC_class == CLASS_SEEKER ) + { + // they blow up when they run out of ammo, so this may as well just do the same. + G_Damage( ent, ent, ent, NULL, NULL, 999, 0, MOD_UNKNOWN ); + } + } + } +} + + +/* +============ +Q3_SetInterface + Description : + Return type : void + Argument : int entID + Argument : const char *data +============ +*/ +static void Q3_SetInterface( int entID, const char *data ) +{ + gi.cvar_set("cg_drawStatus", data); +} + +/* +============ +Q3_SetLocation + Description : + Return type : qboolean + Argument : int entID + Argument : const char *location +============ +*/ +static qboolean Q3_SetLocation( int entID, const char *location ) +{ + gentity_t *ent = &g_entities[entID]; + char *currentLoc; + + if ( !ent ) + { + return qtrue; + } + + currentLoc = G_GetLocationForEnt( ent ); + if ( currentLoc && currentLoc[0] && Q_stricmp( location, currentLoc ) == 0 ) + { + return qtrue; + } + + ent->message = G_NewString( location ); + return qfalse; +} + +/* +============ +Q3_SetPlayerLocked + Description : + Return type : void + Argument : int entID + Argument : qboolean locked +============ +*/ +qboolean player_locked = qfalse; +static void Q3_SetPlayerLocked( int entID, qboolean locked ) +{ + gentity_t *ent = &g_entities[0]; + + player_locked = locked; + if ( ent && ent->client ) + {//stop him too + VectorClear(ent->client->ps.velocity); + } +} + +/* +============ +Q3_SetLockPlayerWeapons + Description : + Return type : void + Argument : int entID + Argument : qboolean locked +============ +*/ +static void Q3_SetLockPlayerWeapons( int entID, qboolean locked ) +{ + gentity_t *ent = &g_entities[0]; + + ent->flags &= ~FL_LOCK_PLAYER_WEAPONS; + + if( locked ) + { + ent->flags |= FL_LOCK_PLAYER_WEAPONS; + } + +} + + +/* +============ +Q3_SetNoImpactDamage + Description : + Return type : void + Argument : int entID + Argument : qboolean locked +============ +*/ +static void Q3_SetNoImpactDamage( int entID, qboolean noImp ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_SetNoImpactDamage: invalid entID %d\n", entID); + return; + } + + ent->flags &= ~FL_NO_IMPACT_DMG; + + if( noImp ) + { + ent->flags |= FL_NO_IMPACT_DMG; + } + +} + +extern void CG_CameraAutoAim( const char *name ); +extern void CG_CameraAutoTrack( const char *name ); + +/* +============ +Q3_SetVar + Description : + Return type : static void + Argument : int taskID + Argument : int entID + Argument : const char *type_name + Argument : const char *data +============ +*/ +/*void SetVar( int taskID, int entID, const char *type_name, const char *data ) +{ + int vret = Q3_VariableDeclared( type_name ) ; + float float_data; + float val = 0.0f; + + + if ( vret != VTYPE_NONE ) + { + switch ( vret ) + { + case VTYPE_FLOAT: + //Check to see if increment command + if ( (val = Q3_CheckStringCounterIncrement( data )) ) + { + Q3_GetFloatVariable( type_name, &float_data ); + float_data += val; + } + else + { + float_data = atof((char *) data); + } + Q3_SetFloatVariable( type_name, float_data ); + break; + + case VTYPE_STRING: + Q3_SetStringVariable( type_name, data ); + break; + + case VTYPE_VECTOR: + Q3_SetVectorVariable( type_name, (char *) data ); + break; + } + + return; + } + + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "%s variable or field not found!\n", type_name ); +}*/ + +/* +============ +Q3_RemoveEnt + Description : + Return type : void + Argument : gentity_t *victim +============ +*/ +static void Q3_RemoveEnt( gentity_t *victim ) +{ + if (!victim || !victim->inuse) + { + return; + } + + if( victim->client ) + { + if ( victim->client->NPC_class == CLASS_VEHICLE ) + {//eject everyone out of a vehicle that's about to remove itself + Vehicle_t *pVeh = victim->m_pVehicle; + if ( pVeh && pVeh->m_pVehicleInfo ) + { + pVeh->m_pVehicleInfo->EjectAll( pVeh ); + } + } + //ClientDisconnect(ent); + victim->s.eFlags |= EF_NODRAW; + victim->svFlags &= ~SVF_NPC; + victim->s.eType = ET_INVISIBLE; + victim->contents = 0; + victim->health = 0; + victim->targetname = NULL; + + if ( victim->NPC && victim->NPC->tempGoal != NULL ) + { + G_FreeEntity( victim->NPC->tempGoal ); + victim->NPC->tempGoal = NULL; + } + if ( victim->client->ps.saberEntityNum != ENTITYNUM_NONE && victim->client->ps.saberEntityNum > 0 ) + { + if ( g_entities[victim->client->ps.saberEntityNum].inuse ) + { + G_FreeEntity( &g_entities[victim->client->ps.saberEntityNum] ); + } + victim->client->ps.saberEntityNum = ENTITYNUM_NONE; + } + //Disappear in half a second + victim->e_ThinkFunc = thinkF_G_FreeEntity; + victim->nextthink = level.time + 500; + return; + } + else + { + victim->e_ThinkFunc = thinkF_G_FreeEntity; + victim->nextthink = level.time + 100; + } +} + +/* +============ +MakeOwnerInvis + Description : + Return type : void + Argument : gentity_t *self +============ +*/ +void MakeOwnerInvis(gentity_t *self) +{ + if(self->owner && self->owner->client) + { + self->owner->client->ps.powerups[PW_CLOAKED] = level.time + 500; + } + + //HACKHGACLHACK!! - MCG + self->e_ThinkFunc = thinkF_RemoveOwner; + self->nextthink = level.time + 400; +} + + +/* +============ +MakeOwnerEnergy + Description : + Return type : void + Argument : gentity_t *self +============ +*/ +void MakeOwnerEnergy(gentity_t *self) +{ + if(self->owner && self->owner->client) + { +// self->owner->client->ps.powerups[PW_QUAD] = level.time + 1000; + } + + G_FreeEntity(self); +} + +// NOTE! RemoveOwner() is a function used within the entity (a pointer to the function is +// contained). This leads to a "funny" predicament: why is RemoveOwner() here??? +// NOTE NOTE! This is also true of Q3_Remove, which should be eliminated as soon as possible. +/* +============ +Q3_Remove + Description : + Return type : void + Argument : int entID + Argument : const char *name +============ +*/ +static void Q3_Remove( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + gentity_t *victim = NULL; + + if( !Q_stricmp( "self", name ) ) + { + victim = ent; + if ( !victim ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_Remove: can't find %s\n", name ); + return; + } + Q3_RemoveEnt( victim ); + } + else if( !Q_stricmp( "enemy", name ) ) + { + victim = ent->enemy; + if ( !victim ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_Remove: can't find %s\n", name ); + return; + } + Q3_RemoveEnt( victim ); + } + else + { + victim = G_Find( NULL, FOFS(targetname), (char *) name ); + if ( !victim ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_Remove: can't find %s\n", name ); + return; + } + + while ( victim ) + { + Q3_RemoveEnt( victim ); + victim = G_Find( victim, FOFS(targetname), (char *) name ); + } + } +} + +/* +============ +RemoveOwner + Description : + Return type : void + Argument : gentity_t *self +============ +*/ +void RemoveOwner (gentity_t *self) +{ + if ( self->owner && self->owner->inuse ) + {//I have an owner and they heavn't been freed yet + Q3_Remove( self->owner->s.number, "self" ); + } + + G_FreeEntity( self ); +} + +void Q3_DismemberLimb( int entID, char *hitLocName ) +{ + gentity_t *self = &g_entities[entID]; + int hitLoc = GetIDForString( HLTable, hitLocName ); + vec3_t point; + + if ( !self ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_WARNING, "Q3_DismemberLimb: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_DismemberLimb: '%s' is not a player/NPC!\n", self->targetname ); + return; + } + + if ( !self->ghoul2.size() ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_DismemberLimb: '%s' is not a ghoul model!\n", self->targetname ); + return; + } + + if ( hitLoc <= HL_NONE || hitLoc >= HL_MAX ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "Q3_DismemberLimb: '%s' is not a valid hit location!\n", hitLocName ); + return; + } + + switch ( hitLoc ) + { + case HL_FOOT_RT: + VectorCopy( self->client->renderInfo.footRPoint, point ); + break; + case HL_FOOT_LT: + VectorCopy( self->client->renderInfo.footLPoint, point ); + break; + case HL_LEG_RT: + G_GetBoltPosition( self, self->kneeRBolt, point ); + break; + case HL_LEG_LT: + G_GetBoltPosition( self, self->kneeLBolt, point ); + break; + case HL_ARM_RT: + case HL_CHEST_RT: + case HL_BACK_LT: + G_GetBoltPosition( self, self->elbowRBolt, point ); + break; + case HL_ARM_LT: + case HL_CHEST_LT: + case HL_BACK_RT: + G_GetBoltPosition( self, self->elbowLBolt, point ); + break; + case HL_WAIST: + case HL_BACK: + case HL_CHEST: + VectorCopy( self->client->renderInfo.torsoPoint, point ); + break; + case HL_HAND_RT: + VectorCopy( self->client->renderInfo.handRPoint, point ); + break; + case HL_HAND_LT: + VectorCopy( self->client->renderInfo.handLPoint, point ); + break; + case HL_HEAD: + VectorCopy( self->client->renderInfo.headPoint, point ); + break; + case HL_GENERIC1: + case HL_GENERIC2: + case HL_GENERIC3: + case HL_GENERIC4: + case HL_GENERIC5: + case HL_GENERIC6: + VectorCopy( self->currentOrigin, point ); + break; + } + G_DoDismemberment( self, point, MOD_SABER, 1000, hitLoc, qtrue ); +} +/* +------------------------- +VariableDeclared +------------------------- +*/ +int CQuake3GameInterface::VariableDeclared( const char *name ) +{ + //Check the strings + varString_m::iterator vsi = m_varStrings.find( name ); + + if ( vsi != m_varStrings.end() ) + return VTYPE_STRING; + + //Check the floats + varFloat_m::iterator vfi = m_varFloats.find( name ); + + if ( vfi != m_varFloats.end() ) + return VTYPE_FLOAT; + + //Check the vectors + varString_m::iterator vvi = m_varVectors.find( name ); + + if ( vvi != m_varVectors.end() ) + return VTYPE_VECTOR; + + return VTYPE_NONE; +} + +/* +------------------------- +SetVar +------------------------- +*/ + +void CQuake3GameInterface::SetVar( int taskID, int entID, const char *type_name, const char *data ) +{ + int vret = VariableDeclared( type_name ) ; + float float_data; + float val = 0.0f; + + int favre=0; + if( Q_stricmp( type_name, "path_enemies_dead" )==0 ) + { + favre=4; + } + + if( Q_stricmp( type_name, "path_door_open" )==0 ) + { + favre = 4; + } + + if ( vret != VTYPE_NONE ) + { + switch ( vret ) + { + case VTYPE_FLOAT: + //Check to see if increment command + if ( (val = Q3_CheckStringCounterIncrement( data )) ) + { + GetFloatVariable( type_name, &float_data ); + float_data += val; + } + else + { + float_data = atof((char *) data); + } + SetFloatVariable( type_name, float_data ); + break; + + case VTYPE_STRING: + SetStringVariable( type_name, data ); + break; + + case VTYPE_VECTOR: + SetVectorVariable( type_name, (char *) data ); + break; + } + + return; + } + + DebugPrint( WL_ERROR, "%s variable or field not found!\n", type_name ); +} + +/* +------------------------- +GetFloatVariable +------------------------- +*/ + +int CQuake3GameInterface::GetFloatVariable( const char *name, float *value ) +{ + //Check the floats + varFloat_m::iterator vfi = m_varFloats.find( name ); + + if ( vfi != m_varFloats.end() ) + { + *value = (*vfi).second; + return true; + } + + return false; +} + +/* +------------------------- +GetStringVariable +------------------------- +*/ + +int CQuake3GameInterface::GetStringVariable( const char *name, const char **value ) +{ + //Check the strings + varString_m::iterator vsi = m_varStrings.find( name ); + + if ( vsi != m_varStrings.end() ) + { + *value = (const char *) ((*vsi).second).c_str(); + return true; + } + + return false; +} + +/* +------------------------- +GetVectorVariable +------------------------- +*/ + +int CQuake3GameInterface::GetVectorVariable( const char *name, vec3_t value ) +{ + //Check the strings + varString_m::iterator vvi = m_varVectors.find( name ); + + if ( vvi != m_varVectors.end() ) + { + const char *str = ((*vvi).second).c_str(); + + sscanf( str, "%f %f %f", &value[0], &value[1], &value[2] ); + return true; + } + + return false; +} + +/* +------------------------- +InitVariables +------------------------- +*/ + +void CQuake3GameInterface::InitVariables( void ) +{ + m_varStrings.clear(); + m_varFloats.clear(); + m_varVectors.clear(); + + if ( m_numVariables > 0 ) + DebugPrint( WL_WARNING, "%d residual variables found!\n", m_numVariables ); + + m_numVariables = 0; +} + +/* +------------------------- +SetVariable_Float +------------------------- +*/ + +int CQuake3GameInterface::SetFloatVariable( const char *name, float value ) +{ + //Check the floats + varFloat_m::iterator vfi = m_varFloats.find( name ); + + if ( vfi == m_varFloats.end() ) + return VTYPE_FLOAT; + + (*vfi).second = value; + + return true; +} + +/* +------------------------- +SetVariable_String +------------------------- +*/ + +int CQuake3GameInterface::SetStringVariable( const char *name, const char *value ) +{ + //Check the strings + varString_m::iterator vsi = m_varStrings.find( name ); + + if ( vsi == m_varStrings.end() ) + return false; + + (*vsi).second = value; + + return true; +} + +/* +------------------------- +SetVariable_Vector +------------------------- +*/ + +int CQuake3GameInterface::SetVectorVariable( const char *name, const char *value ) +{ + //Check the strings + varString_m::iterator vvi = m_varVectors.find( name ); + + if ( vvi == m_varVectors.end() ) + return false; + + (*vvi).second = value; + + return true; +} + +/* +------------------------- +VariableSaveFloats +------------------------- +*/ + +void CQuake3GameInterface::VariableSaveFloats( varFloat_m &fmap ) +{ + int numFloats = fmap.size(); + gi.AppendToSaveGame( 'FVAR', &numFloats, sizeof( numFloats ) ); + + varFloat_m::iterator vfi; + STL_ITERATE( vfi, fmap ) + { + //Save out the map id + int idSize = strlen( ((*vfi).first).c_str() ); + + //Save out the real data + gi.AppendToSaveGame( 'FIDL', &idSize, sizeof( idSize ) ); + gi.AppendToSaveGame( 'FIDS', (void *) ((*vfi).first).c_str(), idSize ); + + //Save out the float value + gi.AppendToSaveGame( 'FVAL', &((*vfi).second), sizeof( float ) ); + } +} + +/* +------------------------- +VariableSaveStrings +------------------------- +*/ + +void CQuake3GameInterface::VariableSaveStrings( varString_m &smap ) +{ + int numStrings = smap.size(); + gi.AppendToSaveGame( 'SVAR', &numStrings, sizeof( numStrings ) ); + + varString_m::iterator vsi; + STL_ITERATE( vsi, smap ) + { + //Save out the map id + int idSize = strlen( ((*vsi).first).c_str() ); + + //Save out the real data + gi.AppendToSaveGame( 'SIDL', &idSize, sizeof( idSize ) ); + gi.AppendToSaveGame( 'SIDS', (void *) ((*vsi).first).c_str(), idSize ); + + //Save out the string value + idSize = strlen( ((*vsi).second).c_str() ); + + gi.AppendToSaveGame( 'SVSZ', &idSize, sizeof( idSize ) ); + gi.AppendToSaveGame( 'SVAL', (void *) ((*vsi).second).c_str(), idSize ); + } +} + +/* +------------------------- +VariableSave +------------------------- +*/ + +int CQuake3GameInterface::VariableSave( void ) +{ + VariableSaveFloats( m_varFloats ); + VariableSaveStrings( m_varStrings ); + VariableSaveStrings( m_varVectors); + + return qtrue; +} + +/* +------------------------- +VariableLoadFloats +------------------------- +*/ + +void CQuake3GameInterface::VariableLoadFloats( varFloat_m &fmap ) +{ + int numFloats; + char tempBuffer[1024]; + + gi.ReadFromSaveGame( 'FVAR', &numFloats, sizeof( numFloats ) ); + + for ( int i = 0; i < numFloats; i++ ) + { + int idSize; + + gi.ReadFromSaveGame( 'FIDL', &idSize, sizeof( idSize ) ); + gi.ReadFromSaveGame( 'FIDS', &tempBuffer, idSize ); + tempBuffer[ idSize ] = 0; + + float val; + + gi.ReadFromSaveGame( 'FVAL', &val, sizeof( float ) ); + + DeclareVariable( TK_FLOAT, (const char *) &tempBuffer ); + SetFloatVariable( (const char *) &tempBuffer, val ); + } +} + +/* +------------------------- +VariableLoadStrings +------------------------- +*/ + +void CQuake3GameInterface::VariableLoadStrings( int type, varString_m &fmap ) +{ + int numFloats; + char tempBuffer[1024]; + char tempBuffer2[1024]; + + gi.ReadFromSaveGame( 'SVAR', &numFloats, sizeof( numFloats ) ); + + for ( int i = 0; i < numFloats; i++ ) + { + int idSize; + + gi.ReadFromSaveGame( 'SIDL', &idSize, sizeof( idSize ) ); + gi.ReadFromSaveGame( 'SIDS', &tempBuffer, idSize ); + tempBuffer[ idSize ] = 0; + + gi.ReadFromSaveGame( 'SVSZ', &idSize, sizeof( idSize ) ); + gi.ReadFromSaveGame( 'SVAL', &tempBuffer2, idSize ); + tempBuffer2[ idSize ] = 0; + + switch ( type ) + { + case TK_STRING: + DeclareVariable( TK_STRING, (const char *) &tempBuffer ); + SetStringVariable( (const char *) &tempBuffer, (const char *) &tempBuffer2 ); + break; + + case TK_VECTOR: + DeclareVariable( TK_VECTOR, (const char *) &tempBuffer ); + SetVectorVariable( (const char *) &tempBuffer, (const char *) &tempBuffer2 ); + break; + } + } +} + +/* +------------------------- +VariableLoad +------------------------- +*/ + +int CQuake3GameInterface::VariableLoad( void ) +{ + InitVariables(); + + VariableLoadFloats( m_varFloats ); + + VariableLoadStrings( TK_STRING, m_varStrings ); + + VariableLoadStrings( TK_VECTOR, m_varVectors); + + return qfalse; +} + +// Static Singleton Instance. +CQuake3GameInterface *CQuake3GameInterface::m_pInstance = NULL; + +// Destructor. +IGameInterface::~IGameInterface() {} + +int IGameInterface::s_IcarusFlavorsNeeded = 1; + +// Get this Interface Instance. +IGameInterface *IGameInterface::GetGame( int flavor ) +{ + // If no instance exists, create one... + if ( !CQuake3GameInterface::m_pInstance ) + { + CQuake3GameInterface::m_pInstance = new CQuake3GameInterface(); + } + + return CQuake3GameInterface::m_pInstance; +} + +// Destroy the game Instance. +void IGameInterface::Destroy() +{ + if ( CQuake3GameInterface::m_pInstance ) + { + delete CQuake3GameInterface::m_pInstance; + CQuake3GameInterface::m_pInstance = NULL; + } +} + +// Constructor. +CQuake3GameInterface::CQuake3GameInterface() : IGameInterface() +{ + m_ScriptList.clear(); + m_EntityList.clear(); + + m_numVariables = 0; + + m_entFilter = -1; + + gclient_t* client = &level.clients[0]; + memset(&client->sess, 0, sizeof(client->sess)); +} + +// Destructor. +CQuake3GameInterface::~CQuake3GameInterface() +{ + scriptlist_t::iterator iterScript; + entitylist_t::iterator iterEntity; + gentity_t *ent = &g_entities[0]; + + // Release all entities Icarus resources. + for ( int i = 0; i < globals.num_entities; i++, ent++ ) + { + if ( !ent->inuse ) + continue; + + FreeEntity( ent ); + } + + // Clear out all precached script's. + for ( iterScript = m_ScriptList.begin(); iterScript != m_ScriptList.end(); iterScript++ ) + { + Free( (*iterScript).second->buffer ); + delete (*iterScript).second; + } + + m_ScriptList.clear(); + m_EntityList.clear(); +} + +// Initialize an Entity by ID. +bool CQuake3GameInterface::InitEntity( gentity_t *pEntity ) +{ + //Make sure this is a fresh entity. + assert( pEntity->m_iIcarusID == IIcarusInterface::ICARUS_INVALID ); + + if ( pEntity->m_iIcarusID != IIcarusInterface::ICARUS_INVALID ) + return false; + + // Get an Icarus ID. + pEntity->m_iIcarusID = IIcarusInterface::GetIcarus()->GetIcarusID( pEntity->s.number ); + + // Initialize all taskIDs to -1 + memset( &pEntity->taskID, -1, sizeof( pEntity->taskID ) ); + + // Add this entity to a map of valid associated entity's for quick retrieval later. + AssociateEntity( pEntity ); + + //Precache all the entity's scripts. + PrecacheEntity( pEntity ); + + return false; +} + +// Free an Entity by ID. +void CQuake3GameInterface::FreeEntity( gentity_t *pEntity ) +{ + //Make sure the entity is valid + if ( pEntity->m_iIcarusID == IIcarusInterface::ICARUS_INVALID ) + return; + + // Remove the Entity from the Entity map so that when their g_entity index is reused, + // ICARUS doesn't try to affect the new (incorrect) pEntity. + if VALIDSTRING( pEntity->script_targetname ) + { + char temp[1024]; + + strncpy( (char *) temp, pEntity->script_targetname, 1023 ); + temp[ 1023 ] = 0; + + entitylist_t::iterator it = m_EntityList.find( strupr(temp) ); + + if (it != m_EntityList.end()) + { + m_EntityList.erase(it); + } + } + + // Delete the Icarus ID, but lets not construct icarus to do it + if (IIcarusInterface::GetIcarus(0,false)) + { + IIcarusInterface::GetIcarus()->DeleteIcarusID( pEntity->m_iIcarusID ); + } +} + +// Determines whether or not a Game Element needs ICARUS information. +bool CQuake3GameInterface::ValidEntity( gentity_t *pEntity ) +{ + int i; + + // Targeted by a script. + if ( pEntity->script_targetname && pEntity->script_targetname[0] ) + return true; + + // Potentially able to call a script. + for ( i = 0; i < NUM_BSETS; i++ ) + { + if VALIDSTRING( pEntity->behaviorSet[i] ) + { + //Com_Printf( "WARNING: Entity %d (%s) has behaviorSet but no script_targetname -- using targetname\n", pEntity->s.number, pEntity->targetname ); + pEntity->script_targetname = G_NewString(pEntity->targetname); + return true; + } + } + + return false; +} + +// Associate the entity's id and name so that it can be referenced later. +void CQuake3GameInterface::AssociateEntity( gentity_t *pEntity ) +{ + char temp[1024]; + + if ( VALIDSTRING( pEntity->script_targetname ) == false ) + return; + + strncpy( (char *) temp, pEntity->script_targetname, 1023 ); + temp[ 1023 ] = 0; + + m_EntityList[ strupr( (char *) temp ) ] = pEntity->s.number; +} + +// Make a valid script name. +int CQuake3GameInterface::MakeValidScriptName( char **strScriptName ) +{ + if ( !Q_stricmp( *strScriptName, "NULL" ) || !Q_stricmp( *strScriptName, "default" ) ) + return 0; + + // ensure "scripts" (Q3_SCRIPT_DIR), which will be missing if this was called recursively... + // MAX_FILENAME_LENGTH should really be MAX_QPATH (and 64 bytes instead of 1024), but this fits the rest of the code + char sFilename[MAX_FILENAME_LENGTH]; + + if ( !Q_stricmpn( *strScriptName, Q3_SCRIPT_DIR, strlen( Q3_SCRIPT_DIR ) ) ) + { + Q_strncpyz( sFilename, *strScriptName, sizeof( sFilename ) ); + } + else + { + Q_strncpyz( sFilename, va( "%s/%s", Q3_SCRIPT_DIR, *strScriptName ), sizeof( sFilename ) ); + } + + return 1; +} + +// First looks to see if a script has already been loaded, if so, return SCRIPT_ALREADYREGISTERED. If a script has +// NOT been already cached, that script is loaded and the return is SCRIPT_REGISTERED. If a script could not +// be found cached and could not be loaded we return SCRIPT_COULDNOTREGISTER. +int CQuake3GameInterface::RegisterScript( const char *strFileName, void **ppBuf, int &iLength ) +{ + // If the file name is invalid, leave. + if ( !strFileName || strFileName[0] == '\0' || !Q_stricmp( strFileName, "NULL" ) || !Q_stricmp( strFileName, "default" ) ) + return SCRIPT_COULDNOTREGISTER; + + // Ensure "scripts" (Q3_SCRIPT_DIR), which will be missing if this was called recursively... + // MAX_FILENAME_LENGTH should really be MAX_QPATH (and 64 bytes instead of 1024), but this fits the rest of the code + char sFilename[MAX_FILENAME_LENGTH]; + + if ( !Q_stricmpn( strFileName, Q3_SCRIPT_DIR, strlen( Q3_SCRIPT_DIR ) ) ) + { + Q_strncpyz( sFilename, strFileName, sizeof( sFilename ) ); + } + else + { + Q_strncpyz( sFilename, va( "%s/%s", Q3_SCRIPT_DIR, strFileName ), sizeof( sFilename ) ); + } + + scriptlist_t::iterator ei; + pscript_t *pscript; + + // Make sure this isn't already cached + ei = m_ScriptList.find( strFileName ); + + // If we found the script (didn't get to the end of the list), return true. + if ( ei != m_ScriptList.end() ) + { + // Send back the script buffer info. + (*ppBuf) = (*ei).second->buffer; + iLength = (*ei).second->length; + + return SCRIPT_ALREADYREGISTERED; + } + + // Prepare the name with the extension. + char newname[MAX_FILENAME_LENGTH]; + sprintf((char *) newname, "%s%s", sFilename, IBI_EXT ); + + qboolean qbIgnoreFileRead = qfalse; + + // NOTENOTE: For the moment I've taken this back out, to avoid doubling the number of fopen()'s per file. +/*#if 0//#ifndef FINAL_BUILD + // small update here, if called during interrogate, don't let gi.FS_ReadFile() complain because it can't + // find stuff like BS_RUN_AND_SHOOT as scriptname... During FINALBUILD the message won't appear anyway, hence + // the ifndef, this just cuts down on internal error reports while testing release mode... + // + + if (bCalledDuringInterrogate) + { + fileHandle_t file; + + gi.FS_FOpenFile( newname, &file, FS_READ ); + + if ( file == NULL ) + { + qbIgnoreFileRead = qtrue; // warn disk code further down not to try FS_ReadFile() + } + else + { + gi.FS_FCloseFile( file ); + } + } +#endif*/ + + // Read the Script File into the Buffer. + char *pBuf = NULL; + iLength = qbIgnoreFileRead ? -1 : gi.FS_ReadFile( newname, (void **) &pBuf ); + + if ( iLength <= 0 ) + { + // File not found, but keep quiet during interrogate stage, because of stuff like BS_RUN_AND_SHOOT as scriptname + // +/* if (!bCalledDuringInterrogate) + { + Com_Printf(S_COLOR_RED"Could not open file '%s'\n", newname ); + }*/ + return SCRIPT_COULDNOTREGISTER; + } + + // Allocate a new pscript (Script Buffer). + pscript = new pscript_t; + + // Allocate a buffer of the size we need then mem copy over the buffer. + pscript->buffer = (char *)Malloc( iLength ); + memcpy( pscript->buffer, pBuf, iLength ); + pscript->length = iLength; + + // We (mem)copied the data over so release the original buffer. + gi.FS_FreeFile( pBuf ); + + // Keep track of the buffer still. + (*ppBuf) = pscript->buffer; + + // Add the Script Buffer to the STL map. + m_ScriptList[strFileName] = pscript; + + return SCRIPT_REGISTERED; +} + +// Precache all the resources needed by a Script and it's Entity (or vice-versa). +int CQuake3GameInterface::PrecacheEntity( gentity_t *pEntity ) +{ + extern stringID_table_t BSTable[]; + int i; + + for ( i = 0; i < NUM_BSETS; i++ ) + { + if ( pEntity->behaviorSet[i] == NULL ) + continue; + + if ( GetIDForString( BSTable, pEntity->behaviorSet[i] ) == -1 ) + {//not a behavior set + char *pBuf = NULL; + int iLength = 0; + + // Try to register this script. + if ( RegisterScript( pEntity->behaviorSet[i], (void **) &pBuf, iLength ) != SCRIPT_COULDNOTREGISTER ) + { + assert( pBuf ); + assert( iLength ); + + if ( pBuf != NULL && iLength > 0 ) + // Tell Icarus to precache the script data. + IIcarusInterface::GetIcarus()->Precache( pBuf, iLength ); + } + else + // otherwise try the next guy, maybe It will work. + continue; + } + } + + return 0; +} + +// Run the script. +void CQuake3GameInterface::RunScript( const gentity_t *pEntity, const char *strScriptName ) +{ + char *pBuf = NULL; + int iLength = 0; + + switch( RegisterScript( strScriptName, (void **) &pBuf, iLength ) ) + { + // If could not be loaded, leave! + case SCRIPT_COULDNOTREGISTER: + DebugPrint( WL_WARNING, "RunScript: Script was not found and could not be loaded!!! %s\n", strScriptName); + return; + + // We loaded the script and registered it, so run it! + case SCRIPT_REGISTERED: + case SCRIPT_ALREADYREGISTERED: + assert( pBuf ); + assert( iLength ); + if ( IIcarusInterface::GetIcarus()->Run( pEntity->m_iIcarusID, pBuf, iLength ) != IIcarusInterface::ICARUS_INVALID ) + DebugPrint( WL_VERBOSE, "%d Script %s executed by %s %s\n", level.time, (char *) strScriptName, pEntity->classname, pEntity->targetname ); + return; + } +} + +void CQuake3GameInterface::Svcmd( void ) +{ + char *cmd = gi.argv( 1 ); + + if ( Q_stricmp( cmd, "log" ) == 0 ) + { + g_ICARUSDebug->integer = WL_DEBUG; + if ( VALIDSTRING( gi.argv( 2 ) ) ) + { + gentity_t *ent = G_Find( NULL, FOFS( script_targetname ), gi.argv(2) ); + + if ( ent == NULL ) + { + Com_Printf( "Entity \"%s\" not found!\n", gi.argv(2) ); + return; + } + + //Start logging + Com_Printf("Logging ICARUS info for entity %s\n", gi.argv(2) ); + + m_entFilter = ( ent->s.number == m_entFilter ) ? -1 : ent->s.number; + } + + Com_Printf("Logging ICARUS info for all entities\n"); + } +} + +// Get the current Game flavor. +int CQuake3GameInterface::GetFlavor() +{ + return 0; +} + +// Reads in a file and attaches the script directory properly. +int CQuake3GameInterface::LoadFile( const char *name, void **buf ) +{ + int iLength = 0; + + // Register the Script (or retrieve it). + RegisterScript( name, buf, iLength ); + + return iLength; +} + +// Prints a message in the center of the screen. +void CQuake3GameInterface::CenterPrint( const char *format, ... ) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, format); + vsprintf (text, format, argptr); + va_end (argptr); + + // FIXME: added '!' so you can print something that's hasn't been precached, '@' searches only for precache text + // this is just a TEMPORARY placeholder until objectives are in!!! -- dmv 11/26/01 + + if ((text[0] == '@') || text[0] == '!') // It's a key + { + if( text[0] == '!') + { + gi.SendServerCommand( NULL, "cp \"%s\"", (text+1) ); + return; + } + + gi.SendServerCommand( NULL, "cp \"%s\"", text ); + } + + DebugPrint( WL_VERBOSE, "%s\n", text); // Just a developers note +} + +// Print a debug message. +void CQuake3GameInterface::DebugPrint( e_DebugPrintLevel level, const char *format, ... ) +{ + //Don't print messages they don't want to see + if ( g_ICARUSDebug->integer < level ) + return; + + va_list argptr; + char text[1024]; + + va_start (argptr, format); + vsprintf (text, format, argptr); + va_end (argptr); + + //Add the color formatting + switch ( level ) + { + case WL_ERROR: + Com_Printf ( S_COLOR_RED"ERROR: %s", text ); + break; + + case WL_WARNING: + Com_Printf ( S_COLOR_YELLOW"WARNING: %s", text ); + break; + + case WL_DEBUG: + { + int entNum; + char *buffer; + + sscanf( text, "%d", &entNum ); + + if ( ( m_entFilter >= 0 ) && ( m_entFilter != entNum ) ) + return; + + buffer = (char *) text; + buffer += 5; + + if ( ( entNum < 0 ) || ( entNum > MAX_GENTITIES ) ) + entNum = 0; + + Com_Printf ( S_COLOR_BLUE"DEBUG: %s(%d): %s\n", g_entities[entNum].script_targetname, entNum, buffer ); + break; + } + default: + case WL_VERBOSE: + Com_Printf ( S_COLOR_GREEN"INFO: %s", text ); + break; + } +} + +//Gets the current time +unsigned int CQuake3GameInterface::GetTime( void ) +{ + return level.time; +} + +// DWORD CQuake3GameInterface::GetTimeScale(void ) {} + +// NOTE: This extern does not really fit here, fix later please... +extern void G_SoundBroadcast( gentity_t *ent, int soundIndex ); +// Plays a sound from an entity. +int CQuake3GameInterface::PlaySound( int taskID, int entID, const char *name, const char *channel ) +{ + gentity_t *ent = &g_entities[entID]; + char finalName[MAX_QPATH]; + soundChannel_t voice_chan = CHAN_VOICE; // set a default so the compiler doesn't bitch + qboolean type_voice = qfalse; + + Q_strncpyz( finalName, name, MAX_QPATH, 0 ); + strlwr(finalName); + G_AddSexToPlayerString( finalName, qtrue ); + + COM_StripExtension( (const char *)finalName, finalName ); + + int soundHandle = G_SoundIndex( (char *) finalName ); + bool bBroadcast = false; + + if ( ( stricmp( channel, "CHAN_ANNOUNCER" ) == 0 ) || (ent->classname && Q_stricmp("target_scriptrunner", ent->classname ) == 0) ) { + bBroadcast = true; + } + + + // moved here from further down so I can easily check channel-type without code dup... + // + if ( stricmp( channel, "CHAN_VOICE" ) == 0 ) + { + voice_chan = CHAN_VOICE; + type_voice = qtrue; + } + else if ( stricmp( channel, "CHAN_VOICE_ATTEN" ) == 0 ) + { + voice_chan = CHAN_VOICE_ATTEN; + type_voice = qtrue; + } + else if ( stricmp( channel, "CHAN_VOICE_GLOBAL" ) == 0 ) // this should broadcast to everyone, put only casue animation on G_SoundOnEnt... + { + voice_chan = CHAN_VOICE_GLOBAL; + type_voice = qtrue; + bBroadcast = true; + } + + // if we're in-camera, check for skipping cinematic and ifso, no subtitle print (since screen is not being + // updated anyway during skipping). This stops leftover subtitles being left onscreen after unskipping. + // + if (!in_camera || + (!g_skippingcin || !g_skippingcin->integer) + ) // paranoia towards project end + { + // Text on + // certain NPC's we always want to use subtitles regardless of subtitle setting + if (g_subtitles->integer == 1 || (ent->NPC && (ent->NPC->scriptFlags & SCF_USE_SUBTITLES) ) ) // Show all text + { + if ( in_camera) // Cinematic + { + gi.SendServerCommand( NULL, "ct \"%s\" %i", finalName, soundHandle ); + } + else //if (precacheWav[i].speaker==SP_NONE) // lower screen text + { + gentity_t *ent2 = &g_entities[0]; + // the numbers in here were either the original ones Bob entered (350), or one arrived at from checking the distance Chell stands at in stasis2 by the computer core that was submitted as a bug report... + // + if (bBroadcast || (DistanceSquared(ent->currentOrigin, ent2->currentOrigin) < ((voice_chan == CHAN_VOICE_ATTEN)?(350 * 350):(1200 * 1200)) ) ) + { + gi.SendServerCommand( NULL, "ct \"%s\" %i", finalName, soundHandle ); + } + } + } + // Cinematic only + else if (g_subtitles->integer == 2) // Show only talking head text and CINEMATIC + { + if ( in_camera) // Cinematic text + { + gi.SendServerCommand( NULL, "ct \"%s\" %i", finalName, soundHandle); + } + } + } + + if ( type_voice ) + { + if ( g_timescale->value > 1.0f ) + {//Skip the damn sound! + return qtrue; + } + else + { + //This the voice channel + G_SoundOnEnt( ent, voice_chan, ((char *) finalName) ); + } + //Remember we're waiting for this + Q3_TaskIDSet( ent, TID_CHAN_VOICE, taskID ); + //do not task_return complete + // if( voice_chan == CHAN_VOICE_GLOBAL ) + // { + // G_SoundBroadcast( ent, soundHandle ); + // } + return qfalse; + } + + if ( bBroadcast ) + {//Broadcast the sound + G_SoundBroadcast( ent, soundHandle ); + } + else + { + G_Sound( ent, soundHandle ); + } + + return qtrue; +} + +// Lerps the origin and angles of an entity to the destination values. +void CQuake3GameInterface::Lerp2Pos( int taskID, int entID, vec3_t origin, vec3_t angles, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + vec3_t ang; + int i; + + if(!ent) + { + DebugPrint( WL_WARNING, "Lerp2Pos: invalid entID %d\n", entID); + return; + } + + if ( ent->client || ent->NPC || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + DebugPrint( WL_ERROR, "Lerp2Pos: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + //Don't allow a zero duration + if ( duration == 0 ) + duration = 1; + + // + // Movement + + moverState_t moverState = ent->moverState; + + if ( moverState == MOVER_POS1 || moverState == MOVER_2TO1 ) + { + VectorCopy( ent->currentOrigin, ent->pos1 ); + VectorCopy( origin, ent->pos2 ); + + if ( moverState == MOVER_POS1 ) + {//open the portal + if ( ent->svFlags & SVF_MOVER_ADJ_AREA_PORTALS ) + { + gi.AdjustAreaPortalState( ent, qtrue ); + } + } + + moverState = MOVER_1TO2; + } + else /*if ( moverState == MOVER_POS2 || moverState == MOVER_1TO2 )*/ + { + VectorCopy( ent->currentOrigin, ent->pos2 ); + VectorCopy( origin, ent->pos1 ); + + moverState = MOVER_2TO1; + } + + InitMoverTrData( ent ); + + ent->s.pos.trDuration = duration; + + // start it going + MatchTeam( ent, moverState, level.time ); + //SetMoverState( ent, moverState, level.time ); + + //Only do the angles if specified + if ( angles != NULL ) + { + // + // Rotation + + for ( i = 0; i < 3; i++ ) + { + ang[i] = AngleDelta( angles[i], ent->currentAngles[i] ); + ent->s.apos.trDelta[i] = ( ang[i] / ( duration * 0.001f ) ); + } + + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + + if ( ent->alt_fire ) + { + ent->s.apos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.apos.trType = TR_NONLINEAR_STOP; + } + ent->s.apos.trDuration = duration; + + ent->s.apos.trTime = level.time; + + ent->e_ReachedFunc = reachedF_moveAndRotateCallback; + Q3_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + } + else + { + //Setup the last bits of information + ent->e_ReachedFunc = reachedF_moverCallback; + } + + if ( ent->damage ) + { + ent->e_BlockedFunc = blockedF_Blocked_Mover; + } + + Q3_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + gi.linkentity( ent ); +} + +// void CQuake3GameInterface::Lerp2Origin( int taskID, int entID, vec3_t origin, float duration ) {} + +// Lerps the angles to the destination value. +void CQuake3GameInterface::Lerp2Angles( int taskID, int entID, vec3_t angles, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + vec3_t ang; + int i; + + if(!ent) + { + DebugPrint( WL_WARNING, "Lerp2Angles: invalid entID %d\n", entID); + return; + } + + if ( ent->client || ent->NPC || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + DebugPrint( WL_ERROR, "Lerp2Angles: ent %d is NOT a mover!\n", entID); + return; + } + + //If we want an instant move, don't send 0... + ent->s.apos.trDuration = (duration>0) ? duration : 1; + + for ( i = 0; i < 3; i++ ) + { + ang [i] = AngleSubtract( angles[i], ent->currentAngles[i]); + ent->s.apos.trDelta[i] = ( ang[i] / ( ent->s.apos.trDuration * 0.001f ) ); + } + + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + + if ( ent->alt_fire ) + { + ent->s.apos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.apos.trType = TR_NONLINEAR_STOP; + } + + ent->s.apos.trTime = level.time; + + Q3_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + + //ent->e_ReachedFunc = reachedF_NULL; + ent->e_ThinkFunc = thinkF_anglerCallback; + ent->nextthink = level.time + duration; + + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + gi.linkentity( ent ); +} + +// Gets the value of a tag by the give name. +int CQuake3GameInterface::GetTag( int entID, const char *name, int lookup, vec3_t info ) +{ + gentity_t *ent = &g_entities[ entID ]; + + VALIDATEB( ent ); + + switch ( lookup ) + { + case TYPE_ORIGIN: + //return TAG_GetOrigin( ent->targetname, name, info ); + return TAG_GetOrigin( ent->ownername, name, info ); + break; + + case TYPE_ANGLES: + //return TAG_GetAngles( ent->targetname, name, info ); + return TAG_GetAngles( ent->ownername, name, info ); + break; + } + + return false; +} + +// void CQuake3GameInterface::Lerp2Start( int taskID, int entID, float duration ) {} +// void CQuake3GameInterface::Lerp2End( int taskID, int entID, float duration ) {} + +void CQuake3GameInterface::Set( int taskID, int entID, const char *type_name, const char *data ) +{ + gentity_t *ent = &g_entities[entID]; + float float_data; + int int_data, toSet; + vec3_t vector_data; + + //Set this for callbacks + toSet = GetIDForString( setTable, type_name ); + + //TODO: Throw in a showscript command that will list each command and what they're doing... + // maybe as simple as printing that line of the script to the console preceeded by the person's name? + // showscript can take any number of targetnames or "all"? Groupname? + switch ( toSet ) + { + case SET_ORIGIN: + sscanf( data, "%f %f %f", &vector_data[0], &vector_data[1], &vector_data[2] ); + G_SetOrigin( ent, vector_data ); + if ( Q_strncmp( "NPC_", ent->classname, 4 ) == 0 ) + {//hack for moving spawners + VectorCopy( vector_data, ent->s.origin); + } + if ( ent->client ) + {//clear jump start positions so we don't take damage when we land... + ent->client->ps.jumpZStart = ent->client->ps.forceJumpZStart = vector_data[2]; + } + gi.linkentity( ent ); + break; + + case SET_TELEPORT_DEST: + sscanf( data, "%f %f %f", &vector_data[0], &vector_data[1], &vector_data[2] ); + if ( !Q3_SetTeleportDest( entID, vector_data ) ) + { + Q3_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + return; + } + break; + + case SET_COPY_ORIGIN: + Q3_SetCopyOrigin( entID, (char *) data ); + break; + + case SET_ANGLES: + //Q3_SetAngles( entID, *(vec3_t *) data); + sscanf( data, "%f %f %f", &vector_data[0], &vector_data[1], &vector_data[2] ); + Q3_SetAngles( entID, vector_data); + break; + + case SET_XVELOCITY: + float_data = atof((char *) data); + Q3_SetVelocity( entID, 0, float_data); + break; + + case SET_YVELOCITY: + float_data = atof((char *) data); + Q3_SetVelocity( entID, 1, float_data); + break; + + case SET_ZVELOCITY: + float_data = atof((char *) data); + Q3_SetVelocity( entID, 2, float_data); + break; + + case SET_Z_OFFSET: + float_data = atof((char *) data); + Q3_SetOriginOffset( entID, 2, float_data); + break; + + case SET_ENEMY: + Q3_SetEnemy( entID, (char *) data ); + break; + + case SET_LEADER: + Q3_SetLeader( entID, (char *) data ); + break; + + case SET_NAVGOAL: + if ( Q3_SetNavGoal( entID, (char *) data ) ) + { + Q3_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + return; //Don't call it back + } + break; + + case SET_ANIM_UPPER: + if ( Q3_SetAnimUpper( entID, (char *) data ) ) + { + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the top + Q3_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + return; //Don't call it back + } + break; + + case SET_ANIM_LOWER: + if ( Q3_SetAnimLower( entID, (char *) data ) ) + { + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the bottom + Q3_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + return; //Don't call it back + } + break; + + case SET_ANIM_BOTH: + { + int both = 0; + if ( Q3_SetAnimUpper( entID, (char *) data ) ) + { + Q3_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + both++; + } + else + { + DebugPrint( WL_ERROR, "SetAnimUpper: %s does not have anim %s!\n", ent->targetname, (char *)data ); + } + if ( Q3_SetAnimLower( entID, (char *) data ) ) + { + Q3_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + both++; + } + else + { + DebugPrint( WL_ERROR, "SetAnimLower: %s does not have anim %s!\n", ent->targetname, (char *)data ); + } + if ( both >= 2 ) + { + Q3_TaskIDSet( ent, TID_ANIM_BOTH, taskID ); + } + if ( both ) + { + return; //Don't call it back + } + } + break; + + case SET_ANIM_HOLDTIME_LOWER: + int_data = atoi((char *) data); + Q3_SetAnimHoldTime( entID, int_data, qtrue ); + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the bottom + Q3_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + return; //Don't call it back + break; + + case SET_ANIM_HOLDTIME_UPPER: + int_data = atoi((char *) data); + Q3_SetAnimHoldTime( entID, int_data, qfalse ); + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the top + Q3_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + return; //Don't call it back + break; + + case SET_ANIM_HOLDTIME_BOTH: + int_data = atoi((char *) data); + Q3_SetAnimHoldTime( entID, int_data, qfalse ); + Q3_SetAnimHoldTime( entID, int_data, qtrue ); + Q3_TaskIDSet( ent, TID_ANIM_BOTH, taskID ); + Q3_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + Q3_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + return; //Don't call it back + break; + + case SET_PLAYER_TEAM: + Q3_SetPlayerTeam( entID, (char *) data ); + break; + + case SET_ENEMY_TEAM: + Q3_SetEnemyTeam( entID, (char *) data ); + break; + + case SET_HEALTH: + int_data = atoi((char *) data); + Q3_SetHealth( entID, int_data ); + break; + + case SET_ARMOR: + int_data = atoi((char *) data); + Q3_SetArmor( entID, int_data ); + break; + + case SET_BEHAVIOR_STATE: + if( !Q3_SetBState( entID, (char *) data ) ) + { + Q3_TaskIDSet( ent, TID_BSTATE, taskID ); + return;//don't complete + } + break; + + case SET_DEFAULT_BSTATE: + Q3_SetDefaultBState( entID, (char *) data ); + break; + + case SET_TEMP_BSTATE: + if( !Q3_SetTempBState( entID, (char *) data ) ) + { + Q3_TaskIDSet( ent, TID_BSTATE, taskID ); + return;//don't complete + } + break; + + case SET_CAPTURE: + Q3_SetCaptureGoal( entID, (char *) data ); + break; + + case SET_DPITCH://FIXME: make these set tempBehavior to BS_FACE and await completion? Or set lockedDesiredPitch/Yaw and aimTime? + float_data = atof((char *) data); + Q3_SetDPitch( entID, float_data ); + Q3_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + return; + break; + + case SET_DYAW: + float_data = atof((char *) data); + Q3_SetDYaw( entID, float_data ); + Q3_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + return; + break; + + case SET_EVENT: + Q3_SetEvent( entID, (char *) data ); + break; + + case SET_VIEWTARGET: + Q3_SetViewTarget( entID, (char *) data ); + Q3_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + return; + break; + + case SET_WATCHTARGET: + Q3_SetWatchTarget( entID, (char *) data ); + break; + + case SET_VIEWENTITY: + Q3_SetViewEntity( entID, (char *) data ); + break; + + case SET_LOOPSOUND: + Q3_SetLoopSound( entID, (char *) data ); + break; + + case SET_ICARUS_FREEZE: + case SET_ICARUS_UNFREEZE: + Q3_SetICARUSFreeze( entID, (char *) data, (toSet==SET_ICARUS_FREEZE) ); + break; + + case SET_WEAPON: + Q3_SetWeapon ( entID, (char *) data); + break; + + case SET_ITEM: + Q3_SetItem ( entID, (char *) data); + break; + + case SET_WALKSPEED: + int_data = atoi((char *) data); + Q3_SetWalkSpeed ( entID, int_data); + break; + + case SET_RUNSPEED: + int_data = atoi((char *) data); + Q3_SetRunSpeed ( entID, int_data); + break; + + case SET_WIDTH: + int_data = atoi((char *) data); + Q3_SetWidth( entID, int_data ); + return; + break; + + case SET_YAWSPEED: + float_data = atof((char *) data); + Q3_SetYawSpeed ( entID, float_data); + break; + + case SET_AGGRESSION: + int_data = atoi((char *) data); + Q3_SetAggression ( entID, int_data); + break; + + case SET_AIM: + int_data = atoi((char *) data); + Q3_SetAim ( entID, int_data); + break; + + case SET_FRICTION: + int_data = atoi((char *) data); + Q3_SetFriction ( entID, int_data); + break; + + case SET_GRAVITY: + float_data = atof((char *) data); + Q3_SetGravity ( entID, float_data); + break; + + case SET_WAIT: + float_data = atof((char *) data); + Q3_SetWait( entID, float_data); + break; + + case SET_FOLLOWDIST: + float_data = atof((char *) data); + Q3_SetFollowDist( entID, float_data); + break; + + case SET_SCALE: + float_data = atof((char *) data); + Q3_SetScale( entID, float_data); + break; + + case SET_RENDER_CULL_RADIUS: + float_data = atof((char *) data); + Q3_SetRenderCullRadius(entID, float_data); + break; + + case SET_COUNT: + Q3_SetCount( entID, (char *) data); + break; + + case SET_SHOT_SPACING: + int_data = atoi((char *) data); + Q3_SetShotSpacing( entID, int_data ); + break; + + case SET_IGNOREPAIN: + if(!stricmp("true", ((char *)data))) + Q3_SetIgnorePain( entID, qtrue); + else if(!stricmp("false", ((char *)data))) + Q3_SetIgnorePain( entID, qfalse); + break; + + case SET_IGNOREENEMIES: + if(!stricmp("true", ((char *)data))) + Q3_SetIgnoreEnemies( entID, qtrue); + else if(!stricmp("false", ((char *)data))) + Q3_SetIgnoreEnemies( entID, qfalse); + break; + + case SET_IGNOREALERTS: + if(!stricmp("true", ((char *)data))) + Q3_SetIgnoreAlerts( entID, qtrue); + else if(!stricmp("false", ((char *)data))) + Q3_SetIgnoreAlerts( entID, qfalse); + break; + + case SET_DONTSHOOT: + if(!stricmp("true", ((char *)data))) + Q3_SetDontShoot( entID, qtrue); + else if(!stricmp("false", ((char *)data))) + Q3_SetDontShoot( entID, qfalse); + break; + + case SET_DONTFIRE: + if(!stricmp("true", ((char *)data))) + Q3_SetDontFire( entID, qtrue); + else if(!stricmp("false", ((char *)data))) + Q3_SetDontFire( entID, qfalse); + break; + + case SET_LOCKED_ENEMY: + if(!stricmp("true", ((char *)data))) + Q3_SetLockedEnemy( entID, qtrue); + else if(!stricmp("false", ((char *)data))) + Q3_SetLockedEnemy( entID, qfalse); + break; + + case SET_NOTARGET: + if(!stricmp("true", ((char *)data))) + Q3_SetNoTarget( entID, qtrue); + else if(!stricmp("false", ((char *)data))) + Q3_SetNoTarget( entID, qfalse); + break; + + case SET_LEAN: + if(!stricmp("right", ((char *)data))) + Q3_SetLean( entID, LEAN_RIGHT); + else if(!stricmp("left", ((char *)data))) + Q3_SetLean( entID, LEAN_LEFT); + else + Q3_SetLean( entID, LEAN_NONE); + break; + + case SET_SHOOTDIST: + float_data = atof((char *) data); + Q3_SetShootDist( entID, float_data ); + break; + + case SET_TIMESCALE: + Q3_SetTimeScale( entID, (char *) data ); + break; + + case SET_VISRANGE: + float_data = atof((char *) data); + Q3_SetVisrange( entID, float_data ); + break; + + case SET_EARSHOT: + float_data = atof((char *) data); + Q3_SetEarshot( entID, float_data ); + break; + + case SET_VIGILANCE: + float_data = atof((char *) data); + Q3_SetVigilance( entID, float_data ); + break; + + case SET_VFOV: + int_data = atoi((char *) data); + Q3_SetVFOV( entID, int_data ); + break; + + case SET_HFOV: + int_data = atoi((char *) data); + Q3_SetHFOV( entID, int_data ); + break; + + case SET_TARGETNAME: + Q3_SetTargetName( entID, (char *) data ); + break; + + case SET_TARGET: + Q3_SetTarget( entID, (char *) data ); + break; + + case SET_TARGET2: + Q3_SetTarget2( entID, (char *) data ); + break; + + case SET_LOCATION: + if ( !Q3_SetLocation( entID, (char *) data ) ) + { + Q3_TaskIDSet( ent, TID_LOCATION, taskID ); + return; + } + break; + + case SET_PAINTARGET: + Q3_SetPainTarget( entID, (char *) data ); + break; + + case SET_DEFEND_TARGET: + DebugPrint( WL_WARNING, "SetDefendTarget unimplemented\n", entID ); + //Q3_SetEnemy( entID, (char *) data); + break; + + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + Q3_SetParm( entID, (toSet-SET_PARM1), (char *) data ); + break; + + case SET_SPAWNSCRIPT: + case SET_USESCRIPT: + case SET_AWAKESCRIPT: + case SET_ANGERSCRIPT: + case SET_ATTACKSCRIPT: + case SET_VICTORYSCRIPT: + case SET_PAINSCRIPT: + case SET_FLEESCRIPT: + case SET_DEATHSCRIPT: + case SET_DELAYEDSCRIPT: + case SET_BLOCKEDSCRIPT: + case SET_FFIRESCRIPT: + case SET_FFDEATHSCRIPT: + case SET_MINDTRICKSCRIPT: + if( !Q3_SetBehaviorSet(entID, toSet, (char *) data) ) + DebugPrint( WL_ERROR, "SetBehaviorSet: Invalid bSet %s\n", type_name ); + break; + + case SET_NO_MINDTRICK: + if(!stricmp("true", ((char *)data))) + Q3_SetNoMindTrick( entID, qtrue); + else + Q3_SetNoMindTrick( entID, qfalse); + break; + + case SET_CINEMATIC_SKIPSCRIPT : + Q3_SetCinematicSkipScript((char *) data); + break; + + case SET_RAILCENTERTRACKLOCKED : + Rail_LockCenterOfTrack((char *) data); + break; + + case SET_RAILCENTERTRACKUNLOCKED : + Rail_UnLockCenterOfTrack((char *) data); + break; + + case SET_DELAYSCRIPTTIME: + int_data = atoi((char *) data); + Q3_SetDelayScriptTime( entID, int_data ); + break; + + case SET_CROUCHED: + if(!stricmp("true", ((char *)data))) + Q3_SetCrouched( entID, qtrue); + else + Q3_SetCrouched( entID, qfalse); + break; + + case SET_WALKING: + if(!stricmp("true", ((char *)data))) + Q3_SetWalking( entID, qtrue); + else + Q3_SetWalking( entID, qfalse); + break; + + case SET_RUNNING: + if(!stricmp("true", ((char *)data))) + Q3_SetRunning( entID, qtrue); + else + Q3_SetRunning( entID, qfalse); + break; + + case SET_CHASE_ENEMIES: + if(!stricmp("true", ((char *)data))) + Q3_SetChaseEnemies( entID, qtrue); + else + Q3_SetChaseEnemies( entID, qfalse); + break; + + case SET_LOOK_FOR_ENEMIES: + if(!stricmp("true", ((char *)data))) + Q3_SetLookForEnemies( entID, qtrue); + else + Q3_SetLookForEnemies( entID, qfalse); + break; + + case SET_FACE_MOVE_DIR: + if(!stricmp("true", ((char *)data))) + Q3_SetFaceMoveDir( entID, qtrue); + else + Q3_SetFaceMoveDir( entID, qfalse); + break; + + case SET_ALT_FIRE: + if(!stricmp("true", ((char *)data))) + Q3_SetAltFire( entID, qtrue); + else + Q3_SetAltFire( entID, qfalse); + break; + + case SET_DONT_FLEE: + if(!stricmp("true", ((char *)data))) + Q3_SetDontFlee( entID, qtrue); + else + Q3_SetDontFlee( entID, qfalse); + break; + + case SET_FORCED_MARCH: + if(!stricmp("true", ((char *)data))) + Q3_SetForcedMarch( entID, qtrue); + else + Q3_SetForcedMarch( entID, qfalse); + break; + + case SET_NO_RESPONSE: + if(!stricmp("true", ((char *)data))) + Q3_SetNoResponse( entID, qtrue); + else + Q3_SetNoResponse( entID, qfalse); + break; + + case SET_NO_COMBAT_TALK: + if(!stricmp("true", ((char *)data))) + Q3_SetCombatTalk( entID, qtrue); + else + Q3_SetCombatTalk( entID, qfalse); + break; + + case SET_NO_ALERT_TALK: + if(!stricmp("true", ((char *)data))) + Q3_SetAlertTalk( entID, qtrue); + else + Q3_SetAlertTalk( entID, qfalse); + break; + + case SET_USE_CP_NEAREST: + if(!stricmp("true", ((char *)data))) + Q3_SetUseCpNearest( entID, qtrue); + else + Q3_SetUseCpNearest( entID, qfalse); + break; + + case SET_NO_FORCE: + if(!stricmp("true", ((char *)data))) + Q3_SetNoForce( entID, qtrue); + else + Q3_SetNoForce( entID, qfalse); + break; + + case SET_NO_ACROBATICS: + if(!stricmp("true", ((char *)data))) + Q3_SetNoAcrobatics( entID, qtrue); + else + Q3_SetNoAcrobatics( entID, qfalse); + break; + + case SET_USE_SUBTITLES: + if(!stricmp("true", ((char *)data))) + Q3_SetUseSubtitles( entID, qtrue); + else + Q3_SetUseSubtitles( entID, qfalse); + break; + + case SET_NO_FALLTODEATH: + if(!stricmp("true", ((char *)data))) + Q3_SetNoFallToDeath( entID, qtrue); + else + Q3_SetNoFallToDeath( entID, qfalse); + break; + + case SET_DISMEMBERABLE: + if(!stricmp("true", ((char *)data))) + Q3_SetDismemberable( entID, qtrue); + else + Q3_SetDismemberable( entID, qfalse); + break; + + case SET_MORELIGHT: + if(!stricmp("true", ((char *)data))) + Q3_SetMoreLight( entID, qtrue); + else + Q3_SetMoreLight( entID, qfalse); + break; + + + case SET_TREASONED: + DebugPrint( WL_VERBOSE, "SET_TREASONED is disabled, do not use\n" ); + /* + G_TeamRetaliation( NULL, &g_entities[0], qfalse ); + ffireLevel = FFIRE_LEVEL_RETALIATION; + */ + break; + + case SET_UNDYING: + if(!stricmp("true", ((char *)data))) + Q3_SetUndying( entID, qtrue); + else + Q3_SetUndying( entID, qfalse); + break; + + case SET_INVINCIBLE: + if(!stricmp("true", ((char *)data))) + Q3_SetInvincible( entID, qtrue); + else + Q3_SetInvincible( entID, qfalse); + break; + + case SET_NOAVOID: + if(!stricmp("true", ((char *)data))) + Q3_SetNoAvoid( entID, qtrue); + else + Q3_SetNoAvoid( entID, qfalse); + break; + + case SET_SOLID: + if(!stricmp("true", ((char *)data))) + { + if ( !Q3_SetSolid( entID, qtrue) ) + { + Q3_TaskIDSet( ent, TID_RESIZE, taskID ); + return; + } + } + else + { + Q3_SetSolid( entID, qfalse); + } + break; + + case SET_INVISIBLE: + if( !stricmp("true", ((char *)data)) ) + Q3_SetInvisible( entID, qtrue ); + else + Q3_SetInvisible( entID, qfalse ); + break; + + case SET_VAMPIRE: + if( !stricmp("true", ((char *)data)) ) + Q3_SetVampire( entID, qtrue ); + else + Q3_SetVampire( entID, qfalse ); + break; + + case SET_FORCE_INVINCIBLE: + if( !stricmp("true", ((char *)data)) ) + Q3_SetForceInvincible( entID, qtrue ); + else + Q3_SetForceInvincible( entID, qfalse ); + break; + + case SET_GREET_ALLIES: + if( !stricmp("true", ((char *)data)) ) + Q3_SetGreetAllies( entID, qtrue ); + else + Q3_SetGreetAllies( entID, qfalse ); + break; + + case SET_PLAYER_LOCKED: + if( !stricmp("true", ((char *)data)) ) + Q3_SetPlayerLocked( entID, qtrue ); + else + Q3_SetPlayerLocked( entID, qfalse ); + break; + + case SET_LOCK_PLAYER_WEAPONS: + if( !stricmp("true", ((char *)data)) ) + Q3_SetLockPlayerWeapons( entID, qtrue ); + else + Q3_SetLockPlayerWeapons( entID, qfalse ); + break; + + case SET_NO_IMPACT_DAMAGE: + if( !stricmp("true", ((char *)data)) ) + Q3_SetNoImpactDamage( entID, qtrue ); + else + Q3_SetNoImpactDamage( entID, qfalse ); + break; + + case SET_FORWARDMOVE: + int_data = atoi((char *) data); + Q3_SetForwardMove( entID, int_data); + break; + + case SET_RIGHTMOVE: + int_data = atoi((char *) data); + Q3_SetRightMove( entID, int_data); + break; + + case SET_LOCKYAW: + Q3_SetLockAngle( entID, data); + break; + + case SET_CAMERA_GROUP: + Q3_CameraGroup(entID, (char *)data); + break; + case SET_CAMERA_GROUP_Z_OFS: + float_data = atof((char *) data); + Q3_CameraGroupZOfs( float_data ); + break; + case SET_CAMERA_GROUP_TAG: + Q3_CameraGroupTag( (char *)data ); + break; + + //FIXME: put these into camera commands + case SET_LOOK_TARGET: + Q3_LookTarget(entID, (char *)data); + break; + + case SET_ADDRHANDBOLT_MODEL: + Q3_AddRHandModel(entID, (char *)data); + break; + + case SET_REMOVERHANDBOLT_MODEL: + Q3_RemoveRHandModel(entID, (char *)data); + break; + + case SET_ADDLHANDBOLT_MODEL: + Q3_AddLHandModel(entID, (char *)data); + break; + + case SET_REMOVELHANDBOLT_MODEL: + Q3_RemoveLHandModel(entID, (char *)data); + break; + + case SET_FACEEYESCLOSED: + case SET_FACEEYESOPENED: + case SET_FACEAUX: + case SET_FACEBLINK: + case SET_FACEBLINKFROWN: + case SET_FACEFROWN: + case SET_FACENORMAL: + float_data = atof((char *) data); + Q3_Face(entID, toSet, float_data); + break; + + case SET_SCROLLTEXT: + Q3_ScrollText( (char *)data ); + break; + + case SET_LCARSTEXT: + Q3_LCARSText( (char *)data ); + break; + + case SET_CENTERTEXT: + CenterPrint( (char *)data ); + break; + + case SET_CAPTIONTEXTCOLOR: + Q3_SetCaptionTextColor ( (char *)data ); + break; + case SET_CENTERTEXTCOLOR: + Q3_SetCenterTextColor ( (char *)data ); + break; + case SET_SCROLLTEXTCOLOR: + Q3_SetScrollTextColor ( (char *)data ); + break; + + case SET_PLAYER_USABLE: + if(!stricmp("true", ((char *)data))) + { + Q3_SetPlayerUsable(entID, qtrue); + } + else + { + Q3_SetPlayerUsable(entID, qfalse); + } + break; + + case SET_STARTFRAME: + int_data = atoi((char *) data); + Q3_SetStartFrame(entID, int_data); + break; + + case SET_ENDFRAME: + int_data = atoi((char *) data); + Q3_SetEndFrame(entID, int_data); + + Q3_TaskIDSet( ent, TID_ANIM_BOTH, taskID ); + return; + break; + + case SET_ANIMFRAME: + int_data = atoi((char *) data); + Q3_SetAnimFrame(entID, int_data); + return; + break; + + case SET_LOOP_ANIM: + if(!stricmp("true", ((char *)data))) + { + Q3_SetLoopAnim(entID, qtrue); + } + else + { + Q3_SetLoopAnim(entID, qfalse); + } + break; + + case SET_INTERFACE: + if(!stricmp("true", ((char *)data))) + { + Q3_SetInterface(entID, "1"); + } + else + { + Q3_SetInterface(entID, "0"); + } + + break; + + case SET_SHIELDS: + if(!stricmp("true", ((char *)data))) + { + Q3_SetShields(entID, qtrue); + } + else + { + Q3_SetShields(entID, qfalse); + } + break; + + case SET_SABERACTIVE: + if(!stricmp("true", ((char *)data))) + { +#ifdef _XBOX + // Big fat hack to ensure the saber blades get bolted on correctly + // Amended - to only run on the PC, or it screws up tavion in kor2 + if( !entID ) + { + Q3_SetWeapon ( entID, "wp_blaster"); + Q3_SetWeapon ( entID, "wp_saber"); + } +#endif + Q3_SetSaberActive( entID, qtrue ); + } + else + { + Q3_SetSaberActive( entID, qfalse ); + } + break; + + // Created: 10/02/02 by Aurelio Reis, Modified: 10/02/02 + // Make a specific Saber 1 Blade active. + case SET_SABER1BLADEON: + // Get which Blade to activate. + int_data = atoi((char *) data); + Q3_SetSaberBladeActive( entID, 0, int_data, qtrue ); + break; + + // Make a specific Saber 1 Blade inactive. + case SET_SABER1BLADEOFF: + // Get which Blade to deactivate. + int_data = atoi((char *) data); + Q3_SetSaberBladeActive( entID, 0, int_data, qfalse ); + break; + + // Make a specific Saber 2 Blade active. + case SET_SABER2BLADEON: + // Get which Blade to activate. + int_data = atoi((char *) data); + Q3_SetSaberBladeActive( entID, 1, int_data, qtrue ); + break; + + // Make a specific Saber 2 Blade inactive. + case SET_SABER2BLADEOFF: + // Get which Blade to deactivate. + int_data = atoi((char *) data); + Q3_SetSaberBladeActive( entID, 1, int_data, qfalse ); + break; + + case SET_DAMAGEENTITY: + int_data = atoi((char *) data); + G_Damage( ent, ent, ent, NULL, NULL, int_data, 0, MOD_UNKNOWN ); + break; + + case SET_SABER_ORIGIN: + sscanf( data, "%f %f %f", &vector_data[0], &vector_data[1], &vector_data[2] ); + WP_SetSaberOrigin( ent, vector_data ); + break; + + case SET_ADJUST_AREA_PORTALS: + if(!stricmp("true", ((char *)data))) + { + Q3_SetAdjustAreaPortals( entID, qtrue ); + } + else + { + Q3_SetAdjustAreaPortals( entID, qfalse ); + } + break; + + case SET_DMG_BY_HEAVY_WEAP_ONLY: + if(!stricmp("true", ((char *)data))) + { + Q3_SetDmgByHeavyWeapOnly( entID, qtrue ); + } + else + { + Q3_SetDmgByHeavyWeapOnly( entID, qfalse ); + } + break; + + case SET_SHIELDED: + if(!stricmp("true", ((char *)data))) + { + Q3_SetShielded( entID, qtrue ); + } + else + { + Q3_SetShielded( entID, qfalse ); + } + break; + + case SET_NO_GROUPS: + if(!stricmp("true", ((char *)data))) + { + Q3_SetNoGroups( entID, qtrue ); + } + else + { + Q3_SetNoGroups( entID, qfalse ); + } + break; + + case SET_FIRE_WEAPON: + if(!stricmp("true", ((char *)data))) + { + Q3_SetFireWeapon( entID, qtrue); + } + else if(!stricmp("false", ((char *)data))) + { + Q3_SetFireWeapon( entID, qfalse); + } + break; + + case SET_FIRE_WEAPON_NO_ANIM: + if(!stricmp("true", ((char *)data))) + { + Q3_SetFireWeaponNoAnim( entID, qtrue); + } + else if(!stricmp("false", ((char *)data))) + { + Q3_SetFireWeaponNoAnim( entID, qfalse); + } + break; + case SET_SAFE_REMOVE: + if(!stricmp("true", ((char *)data))) + { + Q3_SetSafeRemove( entID, qtrue); + } + else if(!stricmp("false", ((char *)data))) + { + Q3_SetSafeRemove( entID, qfalse); + } + break; + + case SET_BOBA_JET_PACK: + if(!stricmp("true", ((char *)data))) + { + Q3_SetBobaJetPack( entID, qtrue); + } + else if(!stricmp("false", ((char *)data))) + { + Q3_SetBobaJetPack( entID, qfalse); + } + break; + + case SET_INACTIVE: + if(!stricmp("true", ((char *)data))) + { + Q3_SetInactive( entID, qtrue); + } + else if(!stricmp("false", ((char *)data))) + { + Q3_SetInactive( entID, qfalse); + } + else if(!stricmp("unlocked", ((char *)data))) + { +extern void UnLockDoors(gentity_t *const ent); + UnLockDoors(&g_entities[entID]); + } + else if(!stricmp("locked", ((char *)data))) + { +extern void LockDoors(gentity_t *const ent); + LockDoors(&g_entities[entID]); + } + break; + + case SET_END_SCREENDISSOLVE: + gi.SendConsoleCommand( "endscreendissolve\n"); + break; + + case SET_FUNC_USABLE_VISIBLE: + if(!stricmp("true", ((char *)data))) + { + Q3_SetFuncUsableVisible( entID, qtrue); + } + else if(!stricmp("false", ((char *)data))) + { + Q3_SetFuncUsableVisible( entID, qfalse); + } + break; + + case SET_NO_KNOCKBACK: + if(!stricmp("true", ((char *)data))) + { + Q3_SetNoKnockback(entID, qtrue); + } + else + { + Q3_SetNoKnockback(entID, qfalse); + } + break; + + case SET_VIDEO_PLAY: + // don't do this check now, James doesn't want a scripted cinematic to also skip any Video cinematics as well, + // the "timescale" and "skippingCinematic" cvars will be set back to normal in the Video code, so doing a + // skip will now only skip one section of a multiple-part story (eg VOY1 bridge sequence) + // +// if ( g_timescale->value <= 1.0f ) + { + gi.SendConsoleCommand( va("inGameCinematic %s\n", (char *)data) ); + } + break; + + case SET_VIDEO_FADE_IN: + if(!stricmp("true", ((char *)data))) + { + gi.cvar_set("cl_VidFadeUp", "1"); + } + else + { + gi.cvar_set("cl_VidFadeUp", "0"); + } + break; + + case SET_VIDEO_FADE_OUT: + if(!stricmp("true", ((char *)data))) + { + gi.cvar_set("cl_VidFadeDown", "1"); + } + else + { + gi.cvar_set("cl_VidFadeDown", "0"); + } + break; + case SET_REMOVE_TARGET: + Q3_SetRemoveTarget( entID, (const char *) data ); + break; + + case SET_LOADGAME: + gi.SendConsoleCommand( va("load %s\n", (const char *) data ) ); + break; + + case SET_MENU_SCREEN: + //UI_SetActiveMenu( (const char *) data ); + gi.SendConsoleCommand( va("uimenu %s\n", (char *)data) ); + break; + + case SET_OBJECTIVE_SHOW: + missionInfo_Updated = qtrue; // Activate flashing text + gi.cvar_set("cg_updatedDataPadObjective", "1"); + + Q3_SetObjective((const char *) data ,SET_OBJ_SHOW); + Q3_SetObjective((const char *) data ,SET_OBJ_PENDING); + break; + case SET_OBJECTIVE_HIDE: + Q3_SetObjective((const char *) data ,SET_OBJ_HIDE); + break; + case SET_OBJECTIVE_SUCCEEDED: + missionInfo_Updated = qtrue; // Activate flashing text + gi.cvar_set("cg_updatedDataPadObjective", "1"); + Q3_SetObjective((const char *) data ,SET_OBJ_SUCCEEDED); + break; + case SET_OBJECTIVE_SUCCEEDED_NO_UPDATE: + Q3_SetObjective((const char *) data ,SET_OBJ_SUCCEEDED); + break; + case SET_OBJECTIVE_FAILED: + Q3_SetObjective((const char *) data ,SET_OBJ_FAILED); + break; + + case SET_OBJECTIVE_CLEARALL: + Q3_ObjectiveClearAll(); + break; + + case SET_MISSIONFAILED: + Q3_SetMissionFailed((const char *) data); + break; + + case SET_MISSIONSTATUSTEXT: + Q3_SetStatusText((const char *) data); + break; + + case SET_MISSIONSTATUSTIME: + int_data = atoi((char *) data); + cg.missionStatusDeadTime = level.time + int_data; + break; + + case SET_CLOSINGCREDITS: + gi.cvar_set("cg_endcredits", "1"); + break; + + case SET_SKILL: +// //can never be set + break; + + case SET_DISABLE_SHADER_ANIM: + if(!stricmp("true", ((char *)data))) + { + Q3_SetDisableShaderAnims( entID, qtrue); + } + else + { + Q3_SetDisableShaderAnims( entID, qfalse); + } + break; + + case SET_SHADER_ANIM: + if(!stricmp("true", ((char *)data))) + { + Q3_SetShaderAnim( entID, qtrue); + } + else + { + Q3_SetShaderAnim( entID, qfalse); + } + break; + + case SET_MUSIC_STATE: + Q3_SetMusicState( (char *) data ); + break; + + case SET_CLEAN_DAMAGING_ENTS: + Q3_SetCleanDamagingEnts(); + break; + + case SET_HUD: + if(!stricmp("true", ((char *)data))) + { + gi.cvar_set("cg_drawHUD", "1"); + } + else + { + gi.cvar_set("cg_drawHUD", "0"); + } + break; + + case SET_FORCE_HEAL_LEVEL: + case SET_FORCE_JUMP_LEVEL: + case SET_FORCE_SPEED_LEVEL: + case SET_FORCE_PUSH_LEVEL: + case SET_FORCE_PULL_LEVEL: + case SET_FORCE_MINDTRICK_LEVEL: + case SET_FORCE_GRIP_LEVEL: + case SET_FORCE_LIGHTNING_LEVEL: + case SET_SABER_THROW: + case SET_SABER_DEFENSE: + case SET_SABER_OFFENSE: + case SET_FORCE_RAGE_LEVEL: + case SET_FORCE_PROTECT_LEVEL: + case SET_FORCE_ABSORB_LEVEL: + case SET_FORCE_DRAIN_LEVEL: + case SET_FORCE_SIGHT_LEVEL: + int_data = atoi((char *) data); + Q3_SetForcePowerLevel( entID, (toSet-SET_FORCE_HEAL_LEVEL), int_data ); + break; + + case SET_SABER1: + case SET_SABER2: + WP_SetSaber( &g_entities[entID], toSet-SET_SABER1, (char *)data ); + break; + + case SET_PLAYERMODEL: + G_ChangePlayerModel( &g_entities[entID], (const char *)data ); + break; + + // NOTE: Preconditions for entering a vehicle still exist. This is not assured to work. -Areis + case SET_VEHICLE: + Use( entID, (char *)data ); +// G_DriveVehicle( &g_entities[entID], NULL, (char *)data ); + break; + + case SET_SECURITY_KEY: + Q3_GiveSecurityKey( entID, (char *)data ); + break; + + case SET_SABER1_COLOR1: + case SET_SABER1_COLOR2: + WP_SaberSetColor( &g_entities[entID], 0, toSet-SET_SABER1_COLOR1, (char *)data ); + break; + case SET_SABER2_COLOR1: + case SET_SABER2_COLOR2: + WP_SaberSetColor( &g_entities[entID], 1, toSet-SET_SABER2_COLOR1, (char *)data ); + break; + case SET_DISMEMBER_LIMB: + Q3_DismemberLimb( entID, (char *)data ); + break; + case SET_NO_PVS_CULL: + Q3_SetBroadcast( entID, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + // Set a Saboteur to cloak (true) or un-cloak (false). + case SET_CLOAK: // Created: 01/08/03 by AReis. +//extern void Jedi_Cloak( gentity_t *self ); +extern void Saboteur_Cloak( gentity_t *self ); + if( stricmp("true", ((char *)data)) == 0 ) + { + Saboteur_Cloak( &g_entities[entID] ); + } + else + { + Saboteur_Decloak( &g_entities[entID] ); + } + break; + case SET_FORCE_HEAL: + Q3_SetForcePower( entID, FP_HEAL, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_SPEED: + Q3_SetForcePower( entID, FP_SPEED, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_PUSH: + Q3_SetForcePower( entID, FP_PUSH, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_PUSH_FAKE: + ForceThrow( &g_entities[entID], qfalse, qtrue ); + break; + case SET_FORCE_PULL: + Q3_SetForcePower( entID, FP_PULL, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_MIND_TRICK: + Q3_SetForcePower( entID, FP_TELEPATHY, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_GRIP: + Q3_SetForcePower( entID, FP_GRIP, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_LIGHTNING: + Q3_SetForcePower( entID, FP_LIGHTNING, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_SABERTHROW: + Q3_SetForcePower( entID, FP_SABERTHROW, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_RAGE: + Q3_SetForcePower( entID, FP_RAGE, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_PROTECT: + Q3_SetForcePower( entID, FP_PROTECT, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_ABSORB: + Q3_SetForcePower( entID, FP_ABSORB, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + case SET_FORCE_DRAIN: + Q3_SetForcePower( entID, FP_DRAIN, (qboolean)(Q_stricmp("true",(char*)data)==0) ); + break; + +extern cvar_t *g_char_model; +extern cvar_t *g_char_skin_head; +extern cvar_t *g_char_skin_torso; +extern cvar_t *g_char_skin_legs; + case SET_WINTER_GEAR: // Created: 03/26/03 by AReis. + { + // If this is a (fake) Player NPC or this IS the Player... + if ( entID == 0 || ( ent->NPC_type && stricmp( ent->NPC_type, "player" ) == 0 ) ) + { + char strSkin[MAX_QPATH]; + // Set the Winter Gear Skin if true, otherwise set back to normal configuration. + if( stricmp( "true", ((char *)data) ) == 0 ) + { + Com_sprintf( strSkin, sizeof( strSkin ), "models/players/%s/|%s|%s|%s", g_char_model->string, g_char_skin_head->string, "torso_g1", "lower_e1" ); + } + else + { + Com_sprintf( strSkin, sizeof( strSkin ), "models/players/%s/|%s|%s|%s", g_char_model->string, g_char_skin_head->string, g_char_skin_torso->string, g_char_skin_legs->string ); + } + int iSkinID = gi.RE_RegisterSkin( strSkin ); + if ( iSkinID ) + { + gi.G2API_SetSkin( &ent->ghoul2[ent->playerModel], G_SkinIndex( strSkin ), iSkinID ); + } + } + break; + } + case SET_NO_ANGLES: + if ( entID >= 0 && entID < ENTITYNUM_WORLD ) + { + if ( (Q_stricmp("true",(char*)data)==0) ) + { + g_entities[entID].flags |= FL_NO_ANGLES; + } + else + { + g_entities[entID].flags &= ~FL_NO_ANGLES; + } + } + break; + case SET_SKIN: + // If this is a (fake) Player NPC or this IS the Player... + {//just blindly sets whatever skin you set! include full path after "base/"... eg: "models/players/tavion_new/model_possessed.skin" + gentity_t *ent = &g_entities[entID]; + if ( ent && ent->inuse && ent->ghoul2.size() ) + { + int iSkinID = gi.RE_RegisterSkin( (char *)data ); + if ( iSkinID ) + { + gi.G2API_SetSkin( &ent->ghoul2[ent->playerModel], G_SkinIndex( (char *)data ), iSkinID ); + } + } + } + break; + + default: + //DebugPrint( WL_ERROR, "Set: '%s' is not a valid set field\n", type_name ); + SetVar( taskID, entID, type_name, data ); + PrisonerObjCheck(type_name,data); + break; + } + + IIcarusInterface::GetIcarus()->Completed( ent->m_iIcarusID, taskID ); +} + +void CQuake3GameInterface::PrisonerObjCheck(const char *name,const char *data) +{ + float float_data; + int holdData; + + if (!Q_stricmp("ui_prisonerobj_currtotal",name)) + { + GetFloatVariable( name, &float_data ); + holdData = (int) float_data; + gi.cvar_set("ui_prisonerobj_currtotal", va("%d",holdData)); + } + else if (!Q_stricmp("ui_prisonerobj_maxtotal",name)) + { + gi.cvar_set("ui_prisonerobj_maxtotal", data); + } + +} +// Uses an entity. +void CQuake3GameInterface::Use( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + DebugPrint( WL_WARNING, "Use: invalid entID %d\n", entID ); + return; + } + + if( !name || !name[0] ) + { + DebugPrint( WL_WARNING, "Use: string is NULL!\n" ); + return; + } + + if ( ent->s.number == 0 && ent->client->NPC_class == CLASS_ATST ) + {//a player trying to get out of his ATST + GEntity_UseFunc( ent->activator, ent, ent ); + return; + } + /* + if ( ent->client ) + {//put the button on him, too, since some other things (like getting out of a vehicle?) check for the use button directly... + ent->client->usercmd.buttons |= BUTTON_USE; + } + */ + G_UseTargets2( ent, ent, name ); +} + +void CQuake3GameInterface::Activate( int entID, const char *name ) +{ + Q3_SetInactive( entID, qtrue ); +} + +void CQuake3GameInterface::Deactivate( int entID, const char *name ) +{ + Q3_SetInactive( entID, qfalse ); +} + +// Kill an entity. +void CQuake3GameInterface::Kill( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + gentity_t *victim = NULL; + int o_health; + + if( !stricmp( name, "self") ) + { + victim = ent; + } + else if( !stricmp( name, "enemy" ) ) + { + victim = ent->enemy; + } + else + { + victim = G_Find (NULL, FOFS(targetname), (char *) name ); + } + + if ( !victim ) + { + DebugPrint( WL_WARNING, "Kill: can't find %s\n", name); + return; + } + + /* + if ( victim == ent ) + { + DebugPrint( WL_ERROR, "Kill: entity %s trying to kill self - not allowed!\n", name); + return; + } + */ + if ( victim == ent ) + {//don't ICARUS_FreeEnt me, I'm in the middle of a script! (FIXME: shouldn't ICARUS handle this internally?) + victim->svFlags |= SVF_KILLED_SELF; + } + + o_health = victim->health; + victim->health = 0; + if ( victim->client ) + { + victim->flags |= FL_NO_KNOCKBACK; + } + //G_SetEnemy(victim, ent); + if( victim->e_DieFunc != dieF_NULL ) // check can be omitted + { + GEntity_DieFunc( victim, NULL, NULL, o_health, MOD_UNKNOWN ); + } +} + +// Remove an entity. +void CQuake3GameInterface::Remove( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + gentity_t *victim = NULL; + + if( !Q_stricmp( "self", name ) ) + { + victim = ent; + if ( !victim ) + { + DebugPrint( WL_WARNING, "Remove: can't find %s\n", name ); + return; + } + Q3_RemoveEnt( victim ); + } + else if( !Q_stricmp( "enemy", name ) ) + { + victim = ent->enemy; + if ( !victim ) + { + DebugPrint( WL_WARNING, "Remove: can't find %s\n", name ); + return; + } + Q3_RemoveEnt( victim ); + } + else + { + victim = G_Find( NULL, FOFS(targetname), (char *) name ); + if ( !victim ) + { + DebugPrint( WL_WARNING, "Remove: can't find %s\n", name ); + return; + } + + while ( victim ) + { + Q3_RemoveEnt( victim ); + victim = G_Find( victim, FOFS(targetname), (char *) name ); + } + } +} + +// Get a random (float) number. +float CQuake3GameInterface::Random( float min, float max ) +{ + return ((rand() * (max - min)) / 32768.0F) + min; +} + +void CQuake3GameInterface::Play( int taskID, int entID, const char *type, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !stricmp( type, "PLAY_ROFF" ) ) + { + // Try to load the requested ROFF + if ( G_LoadRoff( name ) ) + { + ent->roff = G_NewString( name ); + + // Start the roff from the beginning + ent->roff_ctr = 0; + + //Save this off for later + Q3_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + + // Let the ROFF playing start. + ent->next_roff_time = level.time; + + // These need to be initialised up front... + VectorCopy( ent->currentOrigin, ent->pos1 ); + VectorCopy( ent->currentAngles, ent->pos2 ); + gi.linkentity( ent ); + } + } +} + +//Camera functions +void CQuake3GameInterface::CameraPan( vec3_t angles, vec3_t dir, float duration ) +{ + CGCam_Pan( angles, dir, duration ); +} + +void CQuake3GameInterface::CameraMove( vec3_t origin, float duration ) +{ + CGCam_Move( origin, duration ); +} + +void CQuake3GameInterface::CameraZoom( float fov, float duration ) +{ + CGCam_Zoom( fov, duration ); +} + +void CQuake3GameInterface::CameraRoll( float angle, float duration ) +{ + CGCam_Roll( angle, duration ); +} + +void CQuake3GameInterface::CameraFollow( const char *name, float speed, float initLerp ) +{ + CGCam_Follow( name, speed, initLerp ); +} + +void CQuake3GameInterface::CameraTrack( const char *name, float speed, float initLerp ) +{ + CGCam_Track( name, speed, initLerp ); +} + +void CQuake3GameInterface::CameraDistance( float dist, float initLerp ) +{ + CGCam_Distance( dist, initLerp ); +} + +void CQuake3GameInterface::CameraFade( float sr, float sg, float sb, float sa, float dr, float dg, float db, float da, float duration ) +{ + vec4_t src, dst; + + src[0] = sr; + src[1] = sg; + src[2] = sb; + src[3] = sa; + + dst[0] = dr; + dst[1] = dg; + dst[2] = db; + dst[3] = da; + + CGCam_Fade( src, dst, duration ); +} + +void CQuake3GameInterface::CameraPath( const char *name ) +{ + CGCam_StartRoff( G_NewString( name ) ); +} + +void CQuake3GameInterface::CameraEnable( void ) +{ + CGCam_Enable(); +} + +void CQuake3GameInterface::CameraDisable( void ) +{ + CGCam_Disable(); +} + +void CQuake3GameInterface::CameraShake( float intensity, int duration ) +{ + CGCam_Shake( intensity, duration ); +} + +int CQuake3GameInterface::GetFloat( int entID, const char *name, float *value ) +{ + gentity_t *ent = &g_entities[entID]; +// gclient_t *client; + + if ( !ent ) + { + return false; + } + + int toGet = GetIDForString( setTable, name ); //FIXME: May want to make a "getTable" as well + //FIXME: I'm getting really sick of these huge switch statements! + + //NOTENOTE: return true if the value was correctly obtained + switch ( toGet ) + { + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + if (ent->parms == NULL) + { + DebugPrint( WL_ERROR, "GET_PARM: %s %s did not have any parms set!\n", ent->classname, ent->targetname ); + return false; // would prefer qfalse, but I'm fitting in with what's here + } + *value = atof( ent->parms->parm[toGet - SET_PARM1] ); + break; + + case SET_COUNT: + *value = ent->count; + break; + + case SET_HEALTH: + *value = ent->health; + break; + + case SET_SKILL: + *value = g_spskill->integer; + break; + + case SET_XVELOCITY://## %f="0.0" # Velocity along X axis + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_XVELOCITY, %s not a client\n", ent->targetname ); + return false; + } + *value = ent->client->ps.velocity[0]; + break; + + case SET_YVELOCITY://## %f="0.0" # Velocity along Y axis + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_YVELOCITY, %s not a client\n", ent->targetname ); + return false; + } + *value = ent->client->ps.velocity[1]; + break; + + case SET_ZVELOCITY://## %f="0.0" # Velocity along Z axis + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_ZVELOCITY, %s not a client\n", ent->targetname ); + return false; + } + *value = ent->client->ps.velocity[2]; + break; + + case SET_Z_OFFSET: + *value = ent->currentOrigin[2] - ent->s.origin[2]; + break; + + case SET_DPITCH://## %f="0.0" # Pitch for NPC to turn to + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_DPITCH, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->desiredPitch; + break; + + case SET_DYAW://## %f="0.0" # Yaw for NPC to turn to + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_DYAW, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->desiredPitch; + break; + + case SET_WIDTH://## %f="0.0" # Width of NPC bounding box + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_WIDTH, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->mins[0]; + break; + case SET_TIMESCALE://## %f="0.0" # Speed-up slow down game (0 - 1.0) + *value = g_timescale->value; + break; + case SET_CAMERA_GROUP_Z_OFS://## %s="NULL" # all ents with this cameraGroup will be focused on + return false; + break; + + case SET_VISRANGE://## %f="0.0" # How far away NPC can see + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_VISRANGE, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.visrange; + break; + + case SET_EARSHOT://## %f="0.0" # How far an NPC can hear + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_EARSHOT, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.earshot; + break; + + case SET_VIGILANCE://## %f="0.0" # How often to look for enemies (0 - 1.0) + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_VIGILANCE, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.vigilance; + break; + + case SET_GRAVITY://## %f="0.0" # Change this ent's gravity - 800 default + if ( (ent->svFlags&SVF_CUSTOM_GRAVITY) && ent->client ) + { + *value = ent->client->ps.gravity; + } + else + { + *value = g_gravity->value; + } + break; + + case SET_FACEEYESCLOSED: + case SET_FACEEYESOPENED: + case SET_FACEAUX: //## %f="0.0" # Set face to Aux expression for number of seconds + case SET_FACEBLINK: //## %f="0.0" # Set face to Blink expression for number of seconds + case SET_FACEBLINKFROWN: //## %f="0.0" # Set face to Blinkfrown expression for number of seconds + case SET_FACEFROWN: //## %f="0.0" # Set face to Frown expression for number of seconds + case SET_FACENORMAL: //## %f="0.0" # Set face to Normal expression for number of seconds + DebugPrint( WL_WARNING, "GetFloat: SET_FACE___ not implemented\n" ); + return false; + break; + case SET_WAIT: //## %f="0.0" # Change an entity's wait field + *value = ent->wait; + break; + case SET_FOLLOWDIST: //## %f="0.0" # How far away to stay from leader in BS_FOLLOW_LEADER + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_FOLLOWDIST, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->followDist; + break; + //# #sep ints + case SET_ANIM_HOLDTIME_LOWER://## %d="0" # Hold lower anim for number of milliseconds + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_ANIM_HOLDTIME_LOWER, %s not a client\n", ent->targetname ); + return false; + } + *value = ent->client->ps.legsAnimTimer; + break; + case SET_ANIM_HOLDTIME_UPPER://## %d="0" # Hold upper anim for number of milliseconds + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_ANIM_HOLDTIME_UPPER, %s not a client\n", ent->targetname ); + return false; + } + *value = ent->client->ps.torsoAnimTimer; + break; + case SET_ANIM_HOLDTIME_BOTH://## %d="0" # Hold lower and upper anims for number of milliseconds + DebugPrint( WL_WARNING, "GetFloat: SET_ANIM_HOLDTIME_BOTH not implemented\n" ); + return false; + break; + case SET_ARMOR://## %d="0" # Change armor + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_ARMOR, %s not a client\n", ent->targetname ); + return false; + } + *value = ent->client->ps.stats[STAT_ARMOR]; + break; + case SET_WALKSPEED://## %d="0" # Change walkSpeed + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_WALKSPEED, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.walkSpeed; + break; + case SET_RUNSPEED://## %d="0" # Change runSpeed + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_RUNSPEED, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.runSpeed; + break; + case SET_YAWSPEED://## %d="0" # Change yawSpeed + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_YAWSPEED, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.yawSpeed; + break; + case SET_AGGRESSION://## %d="0" # Change aggression 1-5 + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_AGGRESSION, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.aggression; + break; + case SET_AIM://## %d="0" # Change aim 1-5 + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_AIM, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.aim; + break; + case SET_FRICTION://## %d="0" # Change ent's friction - 6 default + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_FRICTION, %s not a client\n", ent->targetname ); + return false; + } + *value = ent->client->ps.friction; + break; + case SET_SHOOTDIST://## %d="0" # How far the ent can shoot - 0 uses weapon + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_SHOOTDIST, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.shootDistance; + break; + case SET_HFOV://## %d="0" # Horizontal field of view + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_HFOV, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.hfov; + break; + case SET_VFOV://## %d="0" # Vertical field of view + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_VFOV, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->stats.vfov; + break; + case SET_DELAYSCRIPTTIME://## %d="0" # How many seconds to wait before running delayscript + *value = ent->delayScriptTime - level.time; + break; + case SET_FORWARDMOVE://## %d="0" # NPC move forward -127(back) to 127 + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_FORWARDMOVE, %s not a client\n", ent->targetname ); + return false; + } + *value = ent->client->forced_forwardmove; + break; + case SET_RIGHTMOVE://## %d="0" # NPC move right -127(left) to 127 + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_RIGHTMOVE, %s not a client\n", ent->targetname ); + return false; + } + *value = ent->client->forced_rightmove; + break; + case SET_STARTFRAME: //## %d="0" # frame to start animation sequence on + *value = ent->startFrame; + break; + case SET_ENDFRAME: //## %d="0" # frame to end animation sequence on + *value = ent->endFrame; + break; + case SET_ANIMFRAME: //## %d="0" # of current frame + *value = ent->s.frame; + break; + + case SET_SHOT_SPACING://## %d="1000" # Time between shots for an NPC - reset to defaults when changes weapon + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_SHOT_SPACING, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->burstSpacing; + break; + case SET_MISSIONSTATUSTIME://## %d="0" # Amount of time until Mission Status should be shown after death + *value = cg.missionStatusDeadTime - level.time; + break; + //# #sep booleans + case SET_IGNOREPAIN://## %t="BOOL_TYPES" # Do not react to pain + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_IGNOREPAIN, %s not an NPC\n", ent->targetname ); + return false; + } + *value = ent->NPC->ignorePain; + break; + case SET_IGNOREENEMIES://## %t="BOOL_TYPES" # Do not acquire enemies + *value = (ent->svFlags&SVF_IGNORE_ENEMIES); + break; + case SET_IGNOREALERTS://## Do not get enemy set by allies in area(ambush) + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_IGNOREALERTS, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_IGNORE_ALERTS); + + case SET_DONTSHOOT://## %t="BOOL_TYPES" # Others won't shoot you + *value = (ent->flags&FL_DONT_SHOOT); + break; + case SET_NOTARGET://## %t="BOOL_TYPES" # Others won't pick you as enemy + *value = (ent->flags&FL_NOTARGET); + break; + case SET_DONTFIRE://## %t="BOOL_TYPES" # Don't fire your weapon + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_DONTFIRE, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_DONT_FIRE); + break; + + case SET_LOCKED_ENEMY://## %t="BOOL_TYPES" # Keep current enemy until dead + *value = (ent->svFlags&SVF_LOCKEDENEMY); + break; + case SET_CROUCHED://## %t="BOOL_TYPES" # Force NPC to crouch + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_CROUCHED, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_CROUCHED); + break; + case SET_WALKING://## %t="BOOL_TYPES" # Force NPC to move at walkSpeed + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_WALKING, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_WALKING); + break; + case SET_RUNNING://## %t="BOOL_TYPES" # Force NPC to move at runSpeed + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_RUNNING, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_RUNNING); + break; + case SET_CHASE_ENEMIES://## %t="BOOL_TYPES" # NPC will chase after enemies + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_CHASE_ENEMIES, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_CHASE_ENEMIES); + break; + case SET_LOOK_FOR_ENEMIES://## %t="BOOL_TYPES" # NPC will be on the lookout for enemies + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_LOOK_FOR_ENEMIES, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES); + break; + case SET_FACE_MOVE_DIR://## %t="BOOL_TYPES" # NPC will face in the direction it's moving + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_FACE_MOVE_DIR, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_FACE_MOVE_DIR); + break; + case SET_FORCED_MARCH://## %t="BOOL_TYPES" # Force NPC to move at runSpeed + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_FORCED_MARCH, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SET_FORCED_MARCH); + break; + case SET_UNDYING://## %t="BOOL_TYPES" # Can take damage down to 1 but not die + *value = (ent->flags&FL_UNDYING); + break; + case SET_NOAVOID://## %t="BOOL_TYPES" # Will not avoid other NPCs or architecture + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_NOAVOID, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->aiFlags&NPCAI_NO_COLL_AVOID); + break; + + case SET_SOLID://## %t="BOOL_TYPES" # Make yourself notsolid or solid + *value = ent->contents; + break; + case SET_PLAYER_USABLE://## %t="BOOL_TYPES" # Can be activateby the player's "use" button + *value = (ent->svFlags&SVF_PLAYER_USABLE); + break; + case SET_LOOP_ANIM://## %t="BOOL_TYPES" # For non-NPCs: loop your animation sequence + *value = ent->loopAnim; + break; + case SET_INTERFACE://## %t="BOOL_TYPES" # Player interface on/off + DebugPrint( WL_WARNING, "GetFloat: SET_INTERFACE not implemented\n" ); + return false; + break; + case SET_SHIELDS://## %t="BOOL_TYPES" # NPC has no shields (Borg do not adapt) + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_SHIELDS, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->aiFlags&NPCAI_SHIELDS); + case SET_SABERACTIVE: + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_SABERACTIVE, %s not a client\n", ent->targetname ); + return false; + } + *value = (ent->client->ps.SaberActive()); + break; + case SET_INVISIBLE://## %t="BOOL_TYPES" # Makes an NPC not solid and not visible + *value = (ent->s.eFlags&EF_NODRAW); + break; + case SET_VAMPIRE://## %t="BOOL_TYPES" # Makes an NPC not solid and not visible + if ( !ent->client ) + { + return false; + } + else + { + *value = (ent->client->ps.powerups[PW_DISINT_2]>level.time); + } + break; + case SET_FORCE_INVINCIBLE://## %t="BOOL_TYPES" # Makes an NPC not solid and not visible + if ( !ent->client ) + { + return false; + } + else + { + *value = (ent->client->ps.powerups[PW_INVINCIBLE]>level.time); + } + break; + case SET_GREET_ALLIES://## %t="BOOL_TYPES" # Makes an NPC greet teammates + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_GREET_ALLIES, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->aiFlags&NPCAI_GREET_ALLIES); + break; + case SET_VIDEO_FADE_IN://## %t="BOOL_TYPES" # Makes video playback fade in + DebugPrint( WL_WARNING, "GetFloat: SET_VIDEO_FADE_IN not implemented\n" ); + return false; + break; + case SET_VIDEO_FADE_OUT://## %t="BOOL_TYPES" # Makes video playback fade out + DebugPrint( WL_WARNING, "GetFloat: SET_VIDEO_FADE_OUT not implemented\n" ); + return false; + break; + case SET_PLAYER_LOCKED://## %t="BOOL_TYPES" # Makes it so player cannot move + *value = player_locked; + break; + case SET_LOCK_PLAYER_WEAPONS://## %t="BOOL_TYPES" # Makes it so player cannot switch weapons + *value = (ent->flags&FL_LOCK_PLAYER_WEAPONS); + break; + case SET_NO_IMPACT_DAMAGE://## %t="BOOL_TYPES" # Makes it so player cannot switch weapons + *value = (ent->flags&FL_NO_IMPACT_DMG); + break; + case SET_NO_KNOCKBACK://## %t="BOOL_TYPES" # Stops this ent from taking knockback from weapons + *value = (ent->flags&FL_NO_KNOCKBACK); + break; + case SET_ALT_FIRE://## %t="BOOL_TYPES" # Force NPC to use altfire when shooting + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_ALT_FIRE, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_ALT_FIRE); + break; + case SET_NO_RESPONSE://## %t="BOOL_TYPES" # NPCs will do generic responses when this is on (usescripts override generic responses as well) + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_NO_RESPONSE, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_NO_RESPONSE); + break; + case SET_INVINCIBLE://## %t="BOOL_TYPES" # Completely unkillable + *value = (ent->flags&FL_GODMODE); + break; + case SET_MISSIONSTATUSACTIVE: //# Turns on Mission Status Screen + *value = cg.missionStatusShow; + break; + case SET_NO_COMBAT_TALK://## %t="BOOL_TYPES" # NPCs will not do their combat talking noises when this is on + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_NO_COMBAT_TALK, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_NO_COMBAT_TALK); + break; + case SET_NO_ALERT_TALK://## %t="BOOL_TYPES" # NPCs will not do their combat talking noises when this is on + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_NO_ALERT_TALK, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_NO_ALERT_TALK); + break; + case SET_USE_CP_NEAREST://## %t="BOOL_TYPES" # NPCs will use their closest combat points, not try and find ones next to the player, or flank player + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_USE_CP_NEAREST, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_USE_CP_NEAREST); + break; + case SET_DISMEMBERABLE://## %t="BOOL_TYPES" # NPC will not be affected by force powers + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_DISMEMBERABLE, %s not a client\n", ent->targetname ); + return false; + } + *value = !(ent->client->dismembered); + break; + case SET_NO_FORCE: + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_NO_FORCE, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_NO_FORCE); + break; + case SET_NO_ACROBATICS: + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_NO_ACROBATICS, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_NO_ACROBATICS); + break; + case SET_USE_SUBTITLES: + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_USE_SUBTITLES, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_USE_SUBTITLES); + break; + case SET_NO_FALLTODEATH://## %t="BOOL_TYPES" # NPC will not be affected by force powers + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_NO_FALLTODEATH, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_NO_FALLTODEATH); + break; + case SET_MORELIGHT://## %t="BOOL_TYPES" # NPCs will use their closest combat points, not try and find ones next to the player, or flank player + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetFloat: SET_MORELIGHT, %s not an NPC\n", ent->targetname ); + return false; + } + *value = (ent->NPC->scriptFlags&SCF_MORELIGHT); + break; + case SET_TREASONED://## %t="BOOL_TYPES" # Player has turned on his own- scripts will stop: NPCs will turn on him and level changes load the brig + DebugPrint( WL_VERBOSE, "SET_TREASONED is disabled, do not use\n" ); + *value = 0;//(ffireLevel>=FFIRE_LEVEL_RETALIATION); + break; + case SET_DISABLE_SHADER_ANIM: //## %t="BOOL_TYPES" # Shaders won't animate + *value = (ent->s.eFlags & EF_DISABLE_SHADER_ANIM); + break; + case SET_SHADER_ANIM: //## %t="BOOL_TYPES" # Shader will be under frame control + *value = (ent->s.eFlags & EF_SHADER_ANIM); + break; + + case SET_OBJECTIVE_LIGHTSIDE: + { + *value = level.clients[0].sess.mission_objectives[LIGHTSIDE_OBJ].status; + break; + } + + // kef 4/16/03 -- just trying to put together some scripted meta-AI for swoop riders + case SET_DISTSQRD_TO_PLAYER: + { + vec3_t distSquared; + + VectorSubtract(player->currentOrigin, ent->s.origin, distSquared); + + *value = VectorLengthSquared(distSquared); + break; + } + + default: + if ( VariableDeclared( name ) != VTYPE_FLOAT ) + return false; + + return GetFloatVariable( name, value ); + } + + return true; +} + +int CQuake3GameInterface::GetVector( int entID, const char *name, vec3_t value ) +{ + gentity_t *ent = &g_entities[entID]; + if ( !ent ) + { + return false; + } + + int toGet = GetIDForString( setTable, name ); //FIXME: May want to make a "getTable" as well + //FIXME: I'm getting really sick of these huge switch statements! + + //NOTENOTE: return true if the value was correctly obtained + switch ( toGet ) + { + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + sscanf( ent->parms->parm[toGet - SET_PARM1], "%f %f %f", &value[0], &value[1], &value[2] ); + break; + + case SET_ORIGIN: + VectorCopy(ent->currentOrigin, value); + break; + + case SET_ANGLES: + VectorCopy(ent->currentAngles, value); + break; + + case SET_TELEPORT_DEST://## %v="0.0 0.0 0.0" # Set origin here as soon as the area is clear + DebugPrint( WL_WARNING, "GetVector: SET_TELEPORT_DEST not implemented\n" ); + return false; + break; + + default: + + if ( VariableDeclared( name ) != VTYPE_VECTOR ) + return false; + + return GetVectorVariable( name, value ); + } + + return true; +} + +int CQuake3GameInterface::GetString( int entID, const char *name, char **value ) +{ + gentity_t *ent = &g_entities[entID]; + if ( !ent ) + { + return false; + } + + int toGet = GetIDForString( setTable, name ); //FIXME: May want to make a "getTable" as well + + switch ( toGet ) + { + case SET_ANIM_BOTH: + *value = (char *) Q3_GetAnimBoth( ent ); + + if ( VALIDSTRING( value ) == false ) + return false; + + break; + + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + if ( ent->parms ) + { + *value = (char *) ent->parms->parm[toGet - SET_PARM1]; + } + else + { + DebugPrint( WL_WARNING, "GetString: invalid ent %s has no parms!\n", ent->targetname ); + return false; + } + break; + + case SET_TARGET: + *value = (char *) ent->target; + break; + + case SET_LOCATION: + *value = G_GetLocationForEnt( ent ); + if ( !value || !value[0] ) + { + return false; + } + break; + + //# #sep Scripts and other file paths + case SET_SPAWNSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when spawned //0 - do not change these, these are equal to BSET_SPAWN, etc + *value = ent->behaviorSet[BSET_SPAWN]; + break; + case SET_USESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when used + *value = ent->behaviorSet[BSET_USE]; + break; + case SET_AWAKESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when startled + *value = ent->behaviorSet[BSET_AWAKE]; + break; + case SET_ANGERSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script run when find an enemy for the first time + *value = ent->behaviorSet[BSET_ANGER]; + break; + case SET_ATTACKSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when you shoot + *value = ent->behaviorSet[BSET_ATTACK]; + break; + case SET_VICTORYSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when killed someone + *value = ent->behaviorSet[BSET_VICTORY]; + break; + case SET_LOSTENEMYSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when you can't find your enemy + *value = ent->behaviorSet[BSET_LOSTENEMY]; + break; + case SET_PAINSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when hit + *value = ent->behaviorSet[BSET_PAIN]; + break; + case SET_FLEESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when hit and low health + *value = ent->behaviorSet[BSET_FLEE]; + break; + case SET_DEATHSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when killed + *value = ent->behaviorSet[BSET_DEATH]; + break; + case SET_DELAYEDSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run after a delay + *value = ent->behaviorSet[BSET_DELAYED]; + break; + case SET_BLOCKEDSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when blocked by teammate + *value = ent->behaviorSet[BSET_BLOCKED]; + break; + case SET_FFIRESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when player has shot own team repeatedly + *value = ent->behaviorSet[BSET_FFIRE]; + break; + case SET_FFDEATHSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when player kills a teammate + *value = ent->behaviorSet[BSET_FFDEATH]; + break; + + //# #sep Standard strings + case SET_ENEMY://## %s="NULL" # Set enemy by targetname + if ( ent->enemy != NULL ) + { + *value = ent->enemy->targetname; + } + else return false; + break; + case SET_LEADER://## %s="NULL" # Set for BS_FOLLOW_LEADER + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetString: SET_LEADER, %s not a client\n", ent->targetname ); + return false; + } + else if ( ent->client->leader ) + { + *value = ent->client->leader->targetname; + } + else return false; + break; + case SET_CAPTURE://## %s="NULL" # Set captureGoal by targetname + if ( ent->NPC == NULL ) + { + DebugPrint( WL_WARNING, "GetString: SET_CAPTURE, %s not an NPC\n", ent->targetname ); + return false; + } + else if ( ent->NPC->captureGoal != NULL ) + { + *value = ent->NPC->captureGoal->targetname; + } + else return false; + break; + + case SET_TARGETNAME://## %s="NULL" # Set/change your targetname + *value = ent->targetname; + break; + case SET_PAINTARGET://## %s="NULL" # Set/change what to use when hit + *value = ent->paintarget; + break; + case SET_PLAYERMODEL: + *value = ent->NPC_type; + break; + case SET_CAMERA_GROUP://## %s="NULL" # all ents with this cameraGroup will be focused on + *value = ent->cameraGroup; + break; + case SET_CAMERA_GROUP_TAG://## %s="NULL" # all ents with this cameraGroup will be focused on + return false; + break; + case SET_LOOK_TARGET://## %s="NULL" # object for NPC to look at + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetString: SET_LOOK_TARGET, %s not a client\n", ent->targetname ); + return false; + } + else + { + gentity_t *lookTarg = &g_entities[ent->client->renderInfo.lookTarget]; + if ( lookTarg != NULL ) + { + *value = lookTarg->targetname; + } + else return false; + } + break; + case SET_TARGET2://## %s="NULL" # Set/change your target2: on NPC's: this fires when they're knocked out by the red hypo + *value = ent->target2; + break; + + case SET_REMOVE_TARGET://## %s="NULL" # Target that is fired when someone completes the BS_REMOVE behaviorState + *value = ent->target3; + break; + case SET_WEAPON: + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetString: SET_WEAPON, %s not a client\n", ent->targetname ); + return false; + } + else + { + *value = (char *)GetStringForID( WPTable, ent->client->ps.weapon ); + } + break; + + case SET_ITEM: + if ( ent->client == NULL ) + { + DebugPrint( WL_WARNING, "GetString: SET_ITEM, %s not a client\n", ent->targetname ); + return false; + } + else + { + // *value = (char *)GetStringForID( WPTable, ent->client->ps.weapon ); + } + break; + case SET_MUSIC_STATE: + *value = (char *)GetStringForID( DMSTable, level.dmState ); + break; + //The below cannot be gotten + case SET_NAVGOAL://## %s="NULL" # *Move to this navgoal then continue script + DebugPrint( WL_WARNING, "GetString: SET_NAVGOAL not implemented\n" ); + return false; + break; + case SET_VIEWTARGET://## %s="NULL" # Set angles toward ent by targetname + DebugPrint( WL_WARNING, "GetString: SET_VIEWTARGET not implemented\n" ); + return false; + break; + case SET_WATCHTARGET://## %s="NULL" # Set angles toward ent by targetname + if ( ent && ent->NPC && ent->NPC->watchTarget ) + { + *value = ent->NPC->watchTarget->targetname; + } + else + { + DebugPrint( WL_WARNING, "GetString: SET_WATCHTARGET no watchTarget!\n" ); + return false; + } + break; + case SET_VIEWENTITY: + DebugPrint( WL_WARNING, "GetString: SET_VIEWENTITY not implemented\n" ); + return false; + break; + case SET_CAPTIONTEXTCOLOR: //## %s="" # Color of text RED:WHITE:BLUE: YELLOW + DebugPrint( WL_WARNING, "GetString: SET_CAPTIONTEXTCOLOR not implemented\n" ); + return false; + break; + case SET_CENTERTEXTCOLOR: //## %s="" # Color of text RED:WHITE:BLUE: YELLOW + DebugPrint( WL_WARNING, "GetString: SET_CENTERTEXTCOLOR not implemented\n" ); + return false; + break; + case SET_SCROLLTEXTCOLOR: //## %s="" # Color of text RED:WHITE:BLUE: YELLOW + DebugPrint( WL_WARNING, "GetString: SET_SCROLLTEXTCOLOR not implemented\n" ); + return false; + break; + case SET_COPY_ORIGIN://## %s="targetname" # Copy the origin of the ent with targetname to your origin + DebugPrint( WL_WARNING, "GetString: SET_COPY_ORIGIN not implemented\n" ); + return false; + break; + case SET_DEFEND_TARGET://## %s="targetname" # This NPC will attack the target NPC's enemies + DebugPrint( WL_WARNING, "GetString: SET_COPY_ORIGIN not implemented\n" ); + return false; + break; + case SET_VIDEO_PLAY://## %s="filename" !!"W:\game\base\video\!!#*.roq" # Play a Video (inGame) + DebugPrint( WL_WARNING, "GetString: SET_VIDEO_PLAY not implemented\n" ); + return false; + break; + case SET_LOADGAME://## %s="exitholodeck" # Load the savegame that was auto-saved when you started the holodeck + DebugPrint( WL_WARNING, "GetString: SET_LOADGAME not implemented\n" ); + return false; + break; + case SET_LOCKYAW://## %s="off" # Lock legs to a certain yaw angle (or "off" or "auto" uses current) + DebugPrint( WL_WARNING, "GetString: SET_LOCKYAW not implemented\n" ); + return false; + break; + case SET_SCROLLTEXT: //## %s="" # key of text string to print + DebugPrint( WL_WARNING, "GetString: SET_SCROLLTEXT not implemented\n" ); + return false; + break; + case SET_LCARSTEXT: //## %s="" # key of text string to print in LCARS frame + DebugPrint( WL_WARNING, "GetString: SET_LCARSTEXT not implemented\n" ); + return false; + break; + + case SET_CENTERTEXT: + DebugPrint( WL_WARNING, "GetString: SET_CENTERTEXT not implemented\n" ); + return false; + break; + + default: + if ( VariableDeclared( name ) != VTYPE_STRING ) + return false; + + return GetStringVariable( name, (const char **) value ); + } + + return true; +} + +int CQuake3GameInterface::Evaluate( int p1Type, const char *p1, int p2Type, const char *p2, int operatorType ) +{ + float f1=0, f2=0; + vec3_t v1, v2; + char *c1=0, *c2=0; + int i1=0, i2=0; + + //Always demote to int on float to integer comparisons + if ( ( ( p1Type == TK_FLOAT ) && ( p2Type == TK_INT ) ) || ( ( p1Type == TK_INT ) && ( p2Type == TK_FLOAT ) ) ) + { + p1Type = TK_INT; + p2Type = TK_INT; + } + + //Cannot compare two disimilar types + if ( p1Type != p2Type ) + { + DebugPrint( WL_ERROR, "Evaluate comparing two disimilar types!\n"); + return false; + } + + //Format the parameters + switch ( p1Type ) + { + case TK_FLOAT: + sscanf( p1, "%f", &f1 ); + sscanf( p2, "%f", &f2 ); + break; + + case TK_INT: + sscanf( p1, "%d", &i1 ); + sscanf( p2, "%d", &i2 ); + break; + + case TK_VECTOR: + sscanf( p1, "%f %f %f", &v1[0], &v1[1], &v1[2] ); + sscanf( p2, "%f %f %f", &v2[0], &v2[1], &v2[2] ); + break; + + case TK_STRING: + case TK_IDENTIFIER: + c1 = (char *) p1; + c2 = (char *) p2; + break; + + default: + DebugPrint( WL_WARNING, "Evaluate unknown type used!\n"); + return false; + } + + //Compare them and return the result + + //FIXME: YUCK!!! Better way to do this? + + switch ( operatorType ) + { + + // + // EQUAL TO + // + case TK_EQUALS: + + switch ( p1Type ) + { + case TK_FLOAT: + return (int) ( f1 == f2 ); + break; + + case TK_INT: + return (int) ( i1 == i2 ); + break; + + case TK_VECTOR: + return (int) VectorCompare( v1, v2 ); + break; + + case TK_STRING: + case TK_IDENTIFIER: + return (int) !stricmp( c1, c2 ); //NOTENOTE: The script uses proper string comparison logic (ex. ( a == a ) == true ) + break; + + default: + DebugPrint( WL_ERROR, "Evaluate unknown type used!\n"); + return false; + } + + break; + + // + // GREATER THAN + // + + case TK_GREATER_THAN: + + switch ( p1Type ) + { + case TK_FLOAT: + return (int) ( f1 > f2 ); + break; + + case TK_INT: + return (int) ( i1 > i2 ); + break; + + case TK_VECTOR: + DebugPrint( WL_ERROR, "Evaluate vector comparisons of type GREATER THAN cannot be performed!"); + return false; + break; + + case TK_STRING: + case TK_IDENTIFIER: + DebugPrint( WL_ERROR, "Evaluate string comparisons of type GREATER THAN cannot be performed!"); + return false; + break; + + default: + DebugPrint( WL_ERROR, "Evaluate unknown type used!\n"); + return false; + } + + break; + + // + // LESS THAN + // + + case TK_LESS_THAN: + + switch ( p1Type ) + { + case TK_FLOAT: + return (int) ( f1 < f2 ); + break; + + case TK_INT: + return (int) ( i1 < i2 ); + break; + + case TK_VECTOR: + DebugPrint( WL_ERROR, "Evaluate vector comparisons of type LESS THAN cannot be performed!"); + return false; + break; + + case TK_STRING: + case TK_IDENTIFIER: + DebugPrint( WL_ERROR, "Evaluate string comparisons of type LESS THAN cannot be performed!"); + return false; + break; + + default: + DebugPrint( WL_ERROR, "Evaluate unknown type used!\n"); + return false; + } + + break; + + // + // NOT + // + + case TK_NOT: //NOTENOTE: Implied "NOT EQUAL TO" + + switch ( p1Type ) + { + case TK_FLOAT: + return (int) ( f1 != f2 ); + break; + + case TK_INT: + return (int) ( i1 != i2 ); + break; + + case TK_VECTOR: + return (int) !VectorCompare( v1, v2 ); + break; + + case TK_STRING: + case TK_IDENTIFIER: + return (int) stricmp( c1, c2 ); + break; + + default: + DebugPrint( WL_ERROR, "Evaluate unknown type used!\n"); + return false; + } + + break; + + // + // GREATER THAN OR EQUAL TO + // + + case TK_GE: + + switch ( p1Type ) + { + case TK_FLOAT: + return (int) ( f1 >= f2 ); + break; + + case TK_INT: + return (int) ( i1 >= i2 ); + break; + + case TK_VECTOR: + DebugPrint( WL_ERROR, "Evaluate vector comparisons of type GREATER THAN OR EQUAL TO cannot be performed!"); + return false; + break; + + case TK_STRING: + case TK_IDENTIFIER: + DebugPrint( WL_ERROR, "Evaluate string comparisons of type GREATER THAN OR EQUAL TO cannot be performed!"); + return false; + break; + + default: + DebugPrint( WL_ERROR, "Evaluate unknown type used!\n"); + return false; + } + + break; + + // + // LESS THAN OR EQUAL TO + // + + case TK_LE: + + switch ( p1Type ) + { + case TK_FLOAT: + return (int) ( f1 <= f2 ); + break; + + case TK_INT: + return (int) ( i1 <= i2 ); + break; + + case TK_VECTOR: + DebugPrint( WL_ERROR, "Evaluate vector comparisons of type LESS THAN OR EQUAL TO cannot be performed!"); + return false; + break; + + case TK_STRING: + case TK_IDENTIFIER: + DebugPrint( WL_ERROR, "Evaluate string comparisons of type LESS THAN OR EQUAL TO cannot be performed!"); + return false; + break; + + default: + DebugPrint( WL_ERROR, "Evaluate unknown type used!\n"); + return false; + } + + break; + + default: + DebugPrint( WL_ERROR, "Evaluate unknown operator used!\n"); + break; + } + + return false; +} + +void CQuake3GameInterface::DeclareVariable( int type, const char *name ) +{ + //Cannot declare the same variable twice + if ( VariableDeclared( name ) != VTYPE_NONE ) + return; + + if ( m_numVariables > MAX_VARIABLES ) + { + DebugPrint( WL_ERROR, "too many variables already declared, maximum is %d\n", MAX_VARIABLES ); + return; + } + + switch( type ) + { + case TK_FLOAT: + m_varFloats[ name ] = 0.0f; + break; + + case TK_STRING: + m_varStrings[ name ] = "NULL"; + break; + + + case TK_VECTOR: + m_varVectors[ name ] = "0.0 0.0 0.0"; + break; + + default: + DebugPrint( WL_ERROR, "unknown 'type' for declare() function!\n" ); + return; + break; + } + + m_numVariables++; +} + +void CQuake3GameInterface::FreeVariable( const char *name ) +{ + //Check the strings + varString_m::iterator vsi = m_varStrings.find( name ); + + if ( vsi != m_varStrings.end() ) + { + m_varStrings.erase( vsi ); + m_numVariables--; + return; + } + + //Check the floats + varFloat_m::iterator vfi = m_varFloats.find( name ); + + if ( vfi != m_varFloats.end() ) + { + m_varFloats.erase( vfi ); + m_numVariables--; + return; + } + + //Check the strings + varString_m::iterator vvi = m_varVectors.find( name ); + + if ( vvi != m_varVectors.end() ) + { + m_varVectors.erase( vvi ); + m_numVariables--; + return; + } +} + +//Save / Load functions +int CQuake3GameInterface::WriteSaveData( unsigned long chid, void *data, int length ) +{ + return gi.AppendToSaveGame( chid, data, length ); +} + +int CQuake3GameInterface::ReadSaveData( unsigned long chid, void *address, int length, void **addressptr ) +{ + return gi.ReadFromSaveGame( chid, address, length, addressptr ); +} + +int CQuake3GameInterface::LinkGame( int entID, int icarusID ) +{ + gentity_t *pEntity = &g_entities[entID]; + + if ( pEntity == NULL ) + return false; + + // Set the icarus ID. + pEntity->m_iIcarusID = icarusID; + + // Associate this Entity with the entity list. + AssociateEntity( pEntity ); + + return true; +} + +// Access functions +int CQuake3GameInterface::CreateIcarus( int entID ) +{ + gentity_t *pEntity = &g_entities[entID]; + + // If the entity doesn't have an Icarus ID, obtain and assign a new one. + if ( pEntity->m_iIcarusID == IIcarusInterface::ICARUS_INVALID ) + pEntity->m_iIcarusID = IIcarusInterface::GetIcarus()->GetIcarusID( entID ); + + return pEntity->m_iIcarusID; +} + +//Polls the engine for the sequencer of the entity matching the name passed +int CQuake3GameInterface::GetByName( const char *name ) +{ + gentity_t *ent; + entitylist_t::iterator ei; + char temp[1024]; + + if ( name == NULL || name[0] == NULL ) + return -1; + + strncpy( (char *) temp, name, sizeof(temp) ); + temp[sizeof(temp)-1] = 0; + + ei = m_EntityList.find( strupr( (char *) temp ) ); + + if ( ei == m_EntityList.end() ) + return -1; + + ent = &g_entities[(*ei).second]; + + return ent->s.number; + + // this now returns the ent instead of the sequencer -- dmv 06/27/01 +// if (ent == NULL) +// return NULL; +// return ent->sequencer; +} + +// (g_entities[m_ownerID].svFlags&SVF_ICARUS_FREEZE) +// return -1 indicates invalid +int CQuake3GameInterface::IsFrozen( int entID ) +{ + return (g_entities[entID].svFlags & SVF_ICARUS_FREEZE); +} + +void CQuake3GameInterface::Free(void* data) +{ + gi.Free( data ); +} + +void* CQuake3GameInterface::Malloc( int size ) +{ + return gi.Malloc( size, TAG_ICARUS, qtrue ); +} + +float CQuake3GameInterface::MaxFloat(void) +{ + // CHANGE! + return 34000000; +} + +// Script Precache functions. +void CQuake3GameInterface::PrecacheRoff( const char *name ) +{ + G_LoadRoff( name ); +} + +void CQuake3GameInterface::PrecacheScript( const char *name ) +{ + char newname[1024]; //static char newname[1024]; + // Strip the extension since we want the real name of the script. + COM_StripExtension( name, (char *) newname ); + + char *pBuf = NULL; + int iLength = 0; + + // Try to Register the Script. + switch( RegisterScript( newname, (void **) &pBuf, iLength ) ) + { + // If the script has already been registered (or could not be loaded), leave! + case SCRIPT_COULDNOTREGISTER: + if ( !Q_stricmp( newname, "NULL" ) || !Q_stricmp( newname, "default" ) ) + {//these are not real errors, suppress warning + return; + } + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "PrecacheScript: Failed to load %s!\n", newname ); + assert(SCRIPT_COULDNOTREGISTER); + return; + case SCRIPT_ALREADYREGISTERED: + return; + + // We loaded the script and registered it, so precache it through Icarus now. + case SCRIPT_REGISTERED: + IIcarusInterface::GetIcarus()->Precache( pBuf, iLength ); + return; + } +} + +void CQuake3GameInterface::PrecacheSound( const char *name ) +{ + char finalName[MAX_QPATH]; + + Q_strncpyz( finalName, name, MAX_QPATH, 0 ); + strlwr(finalName); + if (com_buildScript->integer) + { //get the male sound first + G_SoundIndex( finalName ); + } + G_AddSexToPlayerString( finalName, qtrue ); //now get female + + G_SoundIndex( finalName ); +} + +void CQuake3GameInterface::PrecacheFromSet( const char *setname, const char *filename ) +{ + //JW NOTENOTE: This will not catch special case get() inlines! (There's not really a good way to do that) + + // Get the id for this set identifier then check against valid types. + switch ( GetIDForString( setTable, setname ) ) + { + case SET_SPAWNSCRIPT: + case SET_USESCRIPT: + case SET_AWAKESCRIPT: + case SET_ANGERSCRIPT: + case SET_ATTACKSCRIPT: + case SET_VICTORYSCRIPT: + case SET_LOSTENEMYSCRIPT: + case SET_PAINSCRIPT: + case SET_FLEESCRIPT: + case SET_DEATHSCRIPT: + case SET_DELAYEDSCRIPT: + case SET_BLOCKEDSCRIPT: + case SET_FFIRESCRIPT: + case SET_FFDEATHSCRIPT: + case SET_MINDTRICKSCRIPT: + case SET_CINEMATIC_SKIPSCRIPT: + PrecacheScript(filename); + break; + + case SET_LOOPSOUND: //like ID_SOUND, but set's looping + G_SoundIndex( filename ); + break; + + case SET_VIDEO_PLAY: //in game cinematic + if (com_buildScript->integer) + { + fileHandle_t file; + char name[MAX_OSPATH]; + + if (strstr( filename, "/") == NULL && strstr( filename, "\\") == NULL) { + Com_sprintf ( name, sizeof(name), "video/%s", filename ); + } else { + Com_sprintf ( name, sizeof(name), "%s", filename ); + } + COM_StripExtension( name, name ); + COM_DefaultExtension( name, sizeof( name ), ".roq" ); + + gi.FS_FOpenFile( name, &file, FS_READ ); // trigger the file copy + if (file) + { + gi.FS_FCloseFile( file ); + } + } + break; + + case SET_ADDRHANDBOLT_MODEL: + case SET_ADDLHANDBOLT_MODEL: + { + gi.G2API_PrecacheGhoul2Model( filename ); + } + break; + case SET_WEAPON: + { + const int wp = GetIDForString( WPTable, filename ); + if (wp > 0) + { + gitem_t *item = FindItemForWeapon( (weapon_t) wp); + RegisterItem( item ); //make sure the weapon is cached in case this runs at startup + } + } + break; + + default: + break; + } +} +////////////////////////////////////////////////////////////////////////// +/* END Quake 3 Interface Declarations END */ +////////////////////////////////////////////////////////////////////////// diff --git a/code/game/Q3_Interface.h b/code/game/Q3_Interface.h new file mode 100644 index 0000000..f9e2dd2 --- /dev/null +++ b/code/game/Q3_Interface.h @@ -0,0 +1,704 @@ +#ifndef __Q3_INTERFACE__ +#define __Q3_INTERFACE__ + +#include "../Icarus/IcarusInterface.h" + +//NOTENOTE: The enums and tables in this file will obviously bitch if they are included multiple times, don't do that + +typedef enum //# setType_e +{ + //# #sep Parm strings + SET_PARM1 = 0,//## %s="" # Set entity parm1 + SET_PARM2,//## %s="" # Set entity parm2 + SET_PARM3,//## %s="" # Set entity parm3 + SET_PARM4,//## %s="" # Set entity parm4 + SET_PARM5,//## %s="" # Set entity parm5 + SET_PARM6,//## %s="" # Set entity parm6 + SET_PARM7,//## %s="" # Set entity parm7 + SET_PARM8,//## %s="" # Set entity parm8 + SET_PARM9,//## %s="" # Set entity parm9 + SET_PARM10,//## %s="" # Set entity parm10 + SET_PARM11,//## %s="" # Set entity parm11 + SET_PARM12,//## %s="" # Set entity parm12 + SET_PARM13,//## %s="" # Set entity parm13 + SET_PARM14,//## %s="" # Set entity parm14 + SET_PARM15,//## %s="" # Set entity parm15 + SET_PARM16,//## %s="" # Set entity parm16 + + // NOTE!!! If you add any other SET_xxxxxxSCRIPT types, make sure you update the 'case' statements in + // ICARUS_InterrogateScript() (game/g_ICARUS.cpp), or the script-precacher won't find them. + + //# #sep Scripts and other file paths + SET_SPAWNSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when spawned //0 - do not change these, these are equal to BSET_SPAWN, etc + SET_USESCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when used + SET_AWAKESCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when startled + SET_ANGERSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script run when find an enemy for the first time + SET_ATTACKSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when you shoot + SET_VICTORYSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when killed someone + SET_LOSTENEMYSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when you can't find your enemy + SET_PAINSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when hit + SET_FLEESCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when hit and low health + SET_DEATHSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when killed + SET_DELAYEDSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run after a delay + SET_BLOCKEDSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when blocked by teammate + SET_FFIRESCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when player has shot own team repeatedly + SET_FFDEATHSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when player kills a teammate + SET_MINDTRICKSCRIPT,//## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when player kills a teammate + SET_VIDEO_PLAY,//## %s="filename" !!"W:\game\base\video\!!#*.roq" # Play a video (inGame) + SET_CINEMATIC_SKIPSCRIPT, //## %s="filename" !!"W:\game\base\scripts\!!#*.txt" # Script to run when skipping the running cinematic + SET_RAILCENTERTRACKLOCKED, //## %s="targetname" # Turn off the centered movers on the given track + SET_RAILCENTERTRACKUNLOCKED, //## %s="targetname" # Turn on the centered movers on the given track + SET_SKIN,//## %s="models/players/???/model_default.skin" # just blindly sets whatever skin you set! include full path after "base/"... eg: "models/players/tavion_new/model_possessed.skin" + + //# #sep Standard strings + SET_ENEMY,//## %s="NULL" # Set enemy by targetname + SET_LEADER,//## %s="NULL" # Set for BS_FOLLOW_LEADER + SET_NAVGOAL,//## %s="NULL" # *Move to this navgoal then continue script + SET_CAPTURE,//## %s="NULL" # Set captureGoal by targetname + SET_VIEWTARGET,//## %s="NULL" # Set angles toward ent by targetname + SET_WATCHTARGET,//## %s="NULL" # Set angles toward ent by targetname, will *continue* to face them... only in BS_CINEMATIC + SET_TARGETNAME,//## %s="NULL" # Set/change your targetname + SET_PAINTARGET,//## %s="NULL" # Set/change what to use when hit + SET_CAMERA_GROUP,//## %s="NULL" # all ents with this cameraGroup will be focused on + SET_CAMERA_GROUP_TAG,//## %s="NULL" # What tag on all clients to try to track + SET_LOOK_TARGET,//## %s="NULL" # object for NPC to look at + SET_ADDRHANDBOLT_MODEL, //## %s="NULL" # object to place on NPC right hand bolt + SET_REMOVERHANDBOLT_MODEL, //## %s="NULL" # object to remove from NPC right hand bolt + SET_ADDLHANDBOLT_MODEL, //## %s="NULL" # object to place on NPC left hand bolt + SET_REMOVELHANDBOLT_MODEL, //## %s="NULL" # object to remove from NPC left hand bolt + SET_CAPTIONTEXTCOLOR, //## %s="" # Color of text RED,WHITE,BLUE, YELLOW + SET_CENTERTEXTCOLOR, //## %s="" # Color of text RED,WHITE,BLUE, YELLOW + SET_SCROLLTEXTCOLOR, //## %s="" # Color of text RED,WHITE,BLUE, YELLOW + SET_COPY_ORIGIN,//## %s="targetname" # Copy the origin of the ent with targetname to your origin + SET_DEFEND_TARGET,//## %s="targetname" # This NPC will attack the target NPC's enemies + SET_TARGET,//## %s="NULL" # Set/change your target + SET_TARGET2,//## %s="NULL" # Set/change your target2, on NPC's, this fires when they're knocked out by the red hypo + SET_LOCATION,//## %s="INVALID" # What trigger_location you're in - Can only be gotten, not set! + SET_REMOVE_TARGET,//## %s="NULL" # Target that is fired when someone completes the BS_REMOVE behaviorState + SET_LOADGAME,//## %s="exitholodeck" # Load the savegame that was auto-saved when you started the holodeck + SET_LOCKYAW,//## %s="off" # Lock legs to a certain yaw angle (or "off" or "auto" uses current) + SET_VIEWENTITY,//## %s="NULL" # Make the player look through this ent's eyes - also shunts player movement control to this ent + SET_LOOPSOUND,//## %s="FILENAME" !!"W:\game\base\!!#sound\*.*" # Looping sound to play on entity + SET_ICARUS_FREEZE,//## %s="NULL" # Specify name of entity to freeze - !!!NOTE!!! since the ent is frozen, you must have some other entity unfreeze it!!! + SET_ICARUS_UNFREEZE,//## %s="NULL" # Specify name of entity to unfreeze + SET_SABER1,//## %s="none" # Name of a saber in sabers.cfg to use in first hand. "none" removes current saber + SET_SABER2,//## %s="none" # Name of a saber in sabers.cfg to use in second hand. "none" removes current saber + SET_PLAYERMODEL,//## %s="Kyle" # Name of an NPC config in NPC2.cfg to use for this ent + SET_VEHICLE,//## %s="speeder" # Name of an vehicle config in vehicles.cfg to make this ent drive + SET_SECURITY_KEY,// %s="keyname" # name of a security key to give to the player - don't place one in map, just give the name here and it handles the rest (use "null" to remove their current key) + + SET_SCROLLTEXT, //## %s="" # key of text string to print + SET_LCARSTEXT, //## %s="" # key of text string to print in LCARS frame + SET_CENTERTEXT, //## %s="" # key of text string to print in center of screen. + + //# #sep vectors + SET_ORIGIN,//## %v="0.0 0.0 0.0" # Set origin explicitly or with TAG + SET_ANGLES,//## %v="0.0 0.0 0.0" # Set angles explicitly or with TAG + SET_TELEPORT_DEST,//## %v="0.0 0.0 0.0" # Set origin here as soon as the area is clear + SET_SABER_ORIGIN,//## %v="0.0 0.0 0.0" # Removes this ent's saber from their hand, turns it off, and places it at the specified location + + //# #sep floats + SET_XVELOCITY,//## %f="0.0" # Velocity along X axis + SET_YVELOCITY,//## %f="0.0" # Velocity along Y axis + SET_ZVELOCITY,//## %f="0.0" # Velocity along Z axis + SET_Z_OFFSET,//## %f="0.0" # Vertical offset from original origin... offset/ent's speed * 1000ms is duration + SET_DPITCH,//## %f="0.0" # Pitch for NPC to turn to + SET_DYAW,//## %f="0.0" # Yaw for NPC to turn to + SET_TIMESCALE,//## %f="0.0" # Speed-up slow down game (0 - 1.0) + SET_CAMERA_GROUP_Z_OFS,//## %s="NULL" # when following an ent with the camera, apply this z ofs + SET_VISRANGE,//## %f="0.0" # How far away NPC can see + SET_EARSHOT,//## %f="0.0" # How far an NPC can hear + SET_VIGILANCE,//## %f="0.0" # How often to look for enemies (0 - 1.0) + SET_GRAVITY,//## %f="0.0" # Change this ent's gravity - 800 default + SET_FACEAUX, //## %f="0.0" # Set face to Aux expression for number of seconds + SET_FACEBLINK, //## %f="0.0" # Set face to Blink expression for number of seconds + SET_FACEBLINKFROWN, //## %f="0.0" # Set face to Blinkfrown expression for number of seconds + SET_FACEFROWN, //## %f="0.0" # Set face to Frown expression for number of seconds + SET_FACENORMAL, //## %f="0.0" # Set face to Normal expression for number of seconds + SET_FACEEYESCLOSED, //## %f="0.0" # Set face to Eyes closed + SET_FACEEYESOPENED, //## %f="0.0" # Set face to Eyes open + SET_WAIT, //## %f="0.0" # Change an entity's wait field + SET_FOLLOWDIST, //## %f="0.0" # How far away to stay from leader in BS_FOLLOW_LEADER + SET_SCALE, //## %f="0.0" # Scale the entity model + SET_RENDER_CULL_RADIUS, //## %f="40.0" # Used to ensure rendering for entities with geographically sprawling animations (world units) + SET_DISTSQRD_TO_PLAYER, //## %f="0.0" # Only to be used in a 'get'. (Distance to player)*(Distance to player) + + //# #sep ints + SET_ANIM_HOLDTIME_LOWER,//## %d="0" # Hold lower anim for number of milliseconds + SET_ANIM_HOLDTIME_UPPER,//## %d="0" # Hold upper anim for number of milliseconds + SET_ANIM_HOLDTIME_BOTH,//## %d="0" # Hold lower and upper anims for number of milliseconds + SET_HEALTH,//## %d="0" # Change health + SET_ARMOR,//## %d="0" # Change armor + SET_WALKSPEED,//## %d="0" # Change walkSpeed + SET_RUNSPEED,//## %d="0" # Change runSpeed + SET_YAWSPEED,//## %d="0" # Change yawSpeed + SET_AGGRESSION,//## %d="0" # Change aggression 1-5 + SET_AIM,//## %d="0" # Change aim 1-5 + SET_FRICTION,//## %d="0" # Change ent's friction - 6 default + SET_SHOOTDIST,//## %d="0" # How far the ent can shoot - 0 uses weapon + SET_HFOV,//## %d="0" # Horizontal field of view + SET_VFOV,//## %d="0" # Vertical field of view + SET_DELAYSCRIPTTIME,//## %d="0" # How many milliseconds to wait before running delayscript + SET_FORWARDMOVE,//## %d="0" # NPC move forward -127(back) to 127 + SET_RIGHTMOVE,//## %d="0" # NPC move right -127(left) to 127 + SET_STARTFRAME, //## %d="0" # frame to start animation sequence on + SET_ENDFRAME, //## %d="0" # frame to end animation sequence on + SET_ANIMFRAME, //## %d="0" # frame to set animation sequence to + SET_COUNT, //## %d="0" # Change an entity's count field + SET_SHOT_SPACING,//## %d="1000" # Time between shots for an NPC - reset to defaults when changes weapon + SET_MISSIONSTATUSTIME,//## %d="0" # Amount of time until Mission Status should be shown after death + SET_WIDTH,//## %d="0.0" # Width of NPC bounding box. + SET_SABER1BLADEON,//## %d="0.0" # Activate a specific blade of Saber 1 (0 - (MAX_BLADES - 1)). + SET_SABER1BLADEOFF,//## %d="0.0" # Deactivate a specific blade of Saber 1 (0 - (MAX_BLADES - 1)). + SET_SABER2BLADEON,//## %d="0.0" # Activate a specific blade of Saber 2 (0 - (MAX_BLADES - 1)). + SET_SABER2BLADEOFF,//## %d="0.0" # Deactivate a specific blade of Saber 2 (0 - (MAX_BLADES - 1)). + SET_DAMAGEENTITY, //## %d="5" # Damage this entity with set amount. + + //# #sep booleans + SET_IGNOREPAIN,//## %t="BOOL_TYPES" # Do not react to pain + SET_IGNOREENEMIES,//## %t="BOOL_TYPES" # Do not acquire enemies + SET_IGNOREALERTS,//## %t="BOOL_TYPES" # Do not get enemy set by allies in area(ambush) + SET_DONTSHOOT,//## %t="BOOL_TYPES" # Others won't shoot you + SET_NOTARGET,//## %t="BOOL_TYPES" # Others won't pick you as enemy + SET_DONTFIRE,//## %t="BOOL_TYPES" # Don't fire your weapon + SET_LOCKED_ENEMY,//## %t="BOOL_TYPES" # Keep current enemy until dead + SET_CROUCHED,//## %t="BOOL_TYPES" # Force NPC to crouch + SET_WALKING,//## %t="BOOL_TYPES" # Force NPC to move at walkSpeed + SET_RUNNING,//## %t="BOOL_TYPES" # Force NPC to move at runSpeed + SET_CHASE_ENEMIES,//## %t="BOOL_TYPES" # NPC will chase after enemies + SET_LOOK_FOR_ENEMIES,//## %t="BOOL_TYPES" # NPC will be on the lookout for enemies + SET_FACE_MOVE_DIR,//## %t="BOOL_TYPES" # NPC will face in the direction it's moving + SET_DONT_FLEE,//## %t="BOOL_TYPES" # NPC will not run from danger + SET_FORCED_MARCH,//## %t="BOOL_TYPES" # NPC will not move unless you aim at him + SET_UNDYING,//## %t="BOOL_TYPES" # Can take damage down to 1 but not die + SET_NOAVOID,//## %t="BOOL_TYPES" # Will not avoid other NPCs or architecture + SET_SOLID,//## %t="BOOL_TYPES" # Make yourself notsolid or solid + SET_PLAYER_USABLE,//## %t="BOOL_TYPES" # Can be activateby the player's "use" button + SET_LOOP_ANIM,//## %t="BOOL_TYPES" # For non-NPCs, loop your animation sequence + SET_INTERFACE,//## %t="BOOL_TYPES" # Player interface on/off + SET_SHIELDS,//## %t="BOOL_TYPES" # NPC has no shields (Borg do not adapt) + SET_INVISIBLE,//## %t="BOOL_TYPES" # Makes an NPC not solid and not visible + SET_VAMPIRE,//## %t="BOOL_TYPES" # Draws only in mirrors/portals + SET_FORCE_INVINCIBLE,//## %t="BOOL_TYPES" # Force Invincibility effect, also godmode + SET_GREET_ALLIES,//## %t="BOOL_TYPES" # Makes an NPC greet teammates + SET_VIDEO_FADE_IN,//## %t="BOOL_TYPES" # Makes video playback fade in + SET_VIDEO_FADE_OUT,//## %t="BOOL_TYPES" # Makes video playback fade out + SET_PLAYER_LOCKED,//## %t="BOOL_TYPES" # Makes it so player cannot move + SET_LOCK_PLAYER_WEAPONS,//## %t="BOOL_TYPES" # Makes it so player cannot switch weapons + SET_NO_IMPACT_DAMAGE,//## %t="BOOL_TYPES" # Stops this ent from taking impact damage + SET_NO_KNOCKBACK,//## %t="BOOL_TYPES" # Stops this ent from taking knockback from weapons + SET_ALT_FIRE,//## %t="BOOL_TYPES" # Force NPC to use altfire when shooting + SET_NO_RESPONSE,//## %t="BOOL_TYPES" # NPCs will do generic responses when this is on (usescripts override generic responses as well) + SET_INVINCIBLE,//## %t="BOOL_TYPES" # Completely unkillable + SET_MISSIONSTATUSACTIVE, //# Turns on Mission Status Screen + SET_NO_COMBAT_TALK,//## %t="BOOL_TYPES" # NPCs will not do their combat talking noises when this is on + SET_NO_ALERT_TALK,//## %t="BOOL_TYPES" # NPCs will not do their combat talking noises when this is on + SET_TREASONED,//## %t="BOOL_TYPES" # Player has turned on his own- scripts will stop, NPCs will turn on him and level changes load the brig + SET_DISABLE_SHADER_ANIM,//## %t="BOOL_TYPES" # Allows turning off an animating shader in a script + SET_SHADER_ANIM,//## %t="BOOL_TYPES" # Sets a shader with an image map to be under frame control + SET_SABERACTIVE,//## %t="BOOL_TYPES" # Turns saber on/off + SET_ADJUST_AREA_PORTALS,//## %t="BOOL_TYPES" # Only set this on things you move with script commands that you *want* to open/close area portals. Default is off. + SET_DMG_BY_HEAVY_WEAP_ONLY,//## %t="BOOL_TYPES" # When true, only a heavy weapon class missile/laser can damage this ent. + SET_SHIELDED,//## %t="BOOL_TYPES" # When true, ion_cannon is shielded from any kind of damage. + SET_NO_GROUPS,//## %t="BOOL_TYPES" # This NPC cannot alert groups or be part of a group + SET_FIRE_WEAPON,//## %t="BOOL_TYPES" # Makes NPC will hold down the fire button, until this is set to false + SET_FIRE_WEAPON_NO_ANIM,//## %t="BOOL_TYPES" # NPC will hold down the fire button, but they won't play firing anim + SET_SAFE_REMOVE,//## %t="BOOL_TYPES" # NPC will remove only when it's safe (Player is not in PVS) + SET_BOBA_JET_PACK,//## %t="BOOL_TYPES" # Turn on/off Boba Fett's Jet Pack + SET_NO_MINDTRICK,//## %t="BOOL_TYPES" # Makes NPC immune to jedi mind-trick + SET_INACTIVE,//## %t="BOOL_TYPES" # in lieu of using a target_activate or target_deactivate + SET_FUNC_USABLE_VISIBLE,//## %t="BOOL_TYPES" # provides an alternate way of changing func_usable to be visible or not, DOES NOT AFFECT SOLID + SET_SECRET_AREA_FOUND,//## %t="BOOL_TYPES" # Increment secret areas found counter + SET_END_SCREENDISSOLVE,//## %t="BOOL_TYPES" # End of game dissolve into star background and credits + SET_USE_CP_NEAREST,//## %t="BOOL_TYPES" # NPCs will use their closest combat points, not try and find ones next to the player, or flank player + SET_MORELIGHT,//## %t="BOOL_TYPES" # NPC will have a minlight of 96 + SET_NO_FORCE,//## %t="BOOL_TYPES" # NPC will not be affected by force powers + SET_NO_FALLTODEATH,//## %t="BOOL_TYPES" # NPC will not scream and tumble and fall to hit death over large drops + SET_DISMEMBERABLE,//## %t="BOOL_TYPES" # NPC will not be dismemberable if you set this to false (default is true) + SET_NO_ACROBATICS,//## %t="BOOL_TYPES" # Jedi won't jump, roll or cartwheel + SET_USE_SUBTITLES,//## %t="BOOL_TYPES" # When true NPC will always display subtitle regardless of subtitle setting + SET_CLEAN_DAMAGING_ENTS,//## %t="BOOL_TYPES" # Removes entities that could muck up cinematics, explosives, turrets, seekers. + SET_HUD,//## %t="BOOL_TYPES" # Turns on/off HUD + //JKA + SET_NO_PVS_CULL,//## %t="BOOL_TYPES" # This entity will *always* be drawn - use only for special case cinematic NPCs that have anims that cover multiple rooms!!! + SET_CLOAK, //## %t="BOOL_TYPES" # Set a Saboteur to cloak (true) or un-cloak (false). + SET_FORCE_HEAL,//## %t="BOOL_TYPES" # Causes this ent to start force healing at whatever level of force heal they have + SET_FORCE_SPEED,//## %t="BOOL_TYPES" # Causes this ent to start force speeding at whatever level of force speed they have (may not do anything for NPCs?) + SET_FORCE_PUSH,//## %t="BOOL_TYPES" # Causes this ent to do a force push at whatever level of force push they have - will not fail + SET_FORCE_PUSH_FAKE,//## %t="BOOL_TYPES" # Causes this ent to do a force push anim, sound and effect, will not push anything + SET_FORCE_PULL,//## %t="BOOL_TYPES" # Causes this ent to do a force push at whatever level of force push they have - will not fail + SET_FORCE_MIND_TRICK,//## %t="BOOL_TYPES" # Causes this ent to do a jedi mind trick at whatever level of mind trick they have (may not do anything for NPCs?) + SET_FORCE_GRIP,//## %t="BOOL_TYPES" # Causes this ent to grip their enemy at whatever level of grip they have (will grip until scripted to stop) + SET_FORCE_LIGHTNING,//## %t="BOOL_TYPES" # Causes this ent to lightning at whatever level of lightning they have (will lightning until scripted to stop) + SET_FORCE_SABERTHROW,//## %t="BOOL_TYPES" # Causes this ent to throw their saber at whatever level of saber throw they have (will throw saber until scripted to stop) + SET_FORCE_RAGE,//## %t="BOOL_TYPES" # Causes this ent to go into force rage at whatever level of force rage they have + SET_FORCE_PROTECT,//## %t="BOOL_TYPES" # Causes this ent to start a force protect at whatever level of force protect they have + SET_FORCE_ABSORB,//## %t="BOOL_TYPES" # Causes this ent to do start a force absorb at whatever level of force absorb they have + SET_FORCE_DRAIN,//## %t="BOOL_TYPES" # Causes this ent to start force draining their enemy at whatever level of force drain they have (will drain until scripted to stop) + SET_WINTER_GEAR, //## %t="BOOL_TYPES" # Set the player to wear his/her winter gear (skins torso_g1 and lower_e1), or restore the default skins. + SET_NO_ANGLES, //## %t="BOOL_TYPES" # This NPC/player will not have any bone angle overrides or pitch or roll (should only be used in cinematics) + + //# #sep calls + SET_SKILL,//## %r%d="0" # Cannot set this, only get it - valid values are 0 through 3 + + //# #sep Special tables + SET_ANIM_UPPER,//## %t="ANIM_NAMES" # Torso and head anim + SET_ANIM_LOWER,//## %t="ANIM_NAMES" # Legs anim + SET_ANIM_BOTH,//## %t="ANIM_NAMES" # Set same anim on torso and legs + SET_PLAYER_TEAM,//## %t="TEAM_NAMES" # Your team + SET_ENEMY_TEAM,//## %t="TEAM_NAMES" # Team in which to look for enemies + SET_BEHAVIOR_STATE,//## %t="BSTATE_STRINGS" # Change current bState + SET_DEFAULT_BSTATE,//## %t="BSTATE_STRINGS" # Change fallback bState + SET_TEMP_BSTATE,//## %t="BSTATE_STRINGS" # Set/Chang a temp bState + SET_EVENT,//## %t="EVENT_NAMES" # Events you can initiate + SET_WEAPON,//## %t="WEAPON_NAMES" # Change/Stow/Drop weapon + SET_ITEM,//## %t="ITEM_NAMES" # Give items + SET_MUSIC_STATE,//## %t="MUSIC_STATES" # Set the state of the dynamic music + + SET_FORCE_HEAL_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_JUMP_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_SPEED_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_PUSH_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_PULL_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_MINDTRICK_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_GRIP_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_LIGHTNING_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_SABER_THROW,//## %t="FORCE_LEVELS" # Change force power level + SET_SABER_DEFENSE,//## %t="FORCE_LEVELS" # Change force power level + SET_SABER_OFFENSE,//## %t="SABER_STYLES" # Change force power level + SET_FORCE_RAGE_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_PROTECT_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_ABSORB_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_DRAIN_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_FORCE_SIGHT_LEVEL,//## %t="FORCE_LEVELS" # Change force power level + SET_SABER1_COLOR1, //## %t="SABER_COLORS" # Set color of first blade of first saber + SET_SABER1_COLOR2, //## %t="SABER_COLORS" # Set color of second blade of first saber + SET_SABER2_COLOR1, //## %t="SABER_COLORS" # Set color of first blade of first saber + SET_SABER2_COLOR2, //## %t="SABER_COLORS" # Set color of second blade of first saber + SET_DISMEMBER_LIMB, //## %t="HIT_LOCATIONS" # Cut off a part of a body and send the limb flying + + SET_OBJECTIVE_SHOW, //## %t="OBJECTIVES" # Show objective on mission screen + SET_OBJECTIVE_HIDE, //## %t="OBJECTIVES" # Hide objective from mission screen + SET_OBJECTIVE_SUCCEEDED,//## %t="OBJECTIVES" # Mark objective as completed + SET_OBJECTIVE_SUCCEEDED_NO_UPDATE,//## %t="OBJECTIVES" # Mark objective as completed, no update sent to screen + SET_OBJECTIVE_FAILED, //## %t="OBJECTIVES" # Mark objective as failed + + SET_MISSIONFAILED, //## %t="MISSIONFAILED" # Mission failed screen activates + + SET_TACTICAL_SHOW, //## %t="TACTICAL" # Show tactical info on mission objectives screen + SET_TACTICAL_HIDE, //## %t="TACTICAL" # Hide tactical info on mission objectives screen + SET_OBJECTIVE_CLEARALL, //## # Force all objectives to be hidden +/* + SET_OBJECTIVEFOSTER, +*/ + SET_OBJECTIVE_LIGHTSIDE, //## # Used to get whether the player has chosen the light (succeeded) or dark (failed) side. + + SET_MISSIONSTATUSTEXT, //## %t="STATUSTEXT" # Text to appear in mission status screen + SET_MENU_SCREEN,//## %t="MENUSCREENS" # Brings up specified menu screen + + SET_CLOSINGCREDITS, //## # Show closing credits + + //in-bhc tables + SET_LEAN,//## %t="LEAN_TYPES" # Lean left, right or stop leaning + + //# #eol + SET_ +} setType_t; + + +// this enum isn't used directly by the game, it's mainly for BehavEd to scan for... +// +typedef enum //# playType_e +{ + //# #sep Types of file to play + PLAY_ROFF = 0,//## %s="filename" !!"W:\game\base\scripts\!!#*.rof" # Play a ROFF file + + //# #eol + PLAY_NUMBEROF + +} playType_t; + + +const int Q3_TIME_SCALE = 1; //MILLISECONDS + +extern char cinematicSkipScript[64]; + +//General +extern void Q3_TaskIDClear( int *taskID ); +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); +extern void Q3_TaskIDComplete( gentity_t *ent, taskID_t taskType ); +extern void Q3_DPrintf( const char *, ... ); + +//Not referenced directly as script function - all are called through Q3_Set +extern void Q3_SetAnimBoth( int entID, const char *anim_name ); +extern void Q3_SetVelocity( int entID, vec3_t angles ); + +////////////////////////////////////////////////////////////////////////// +/* BEGIN Almost useless tokenizer and interpreter constants BEGIN */ +////////////////////////////////////////////////////////////////////////// +#define MAX_STRING_LENGTH 256 +#define MAX_IDENTIFIER_LENGTH 128 + +#define TKF_IGNOREDIRECTIVES 0x00000001 // skip over lines starting with # +#define TKF_USES_EOL 0x00000002 // generate end of line tokens +#define TKF_NODIRECTIVES 0x00000004 // don't treat # in any special way +#define TKF_WANTUNDEFINED 0x00000008 // if token not found in symbols create undefined token +#define TKF_WIDEUNDEFINEDSYMBOLS 0x00000010 // when undefined token encountered, accumulate until space +#define TKF_RAWSYMBOLSONLY 0x00000020 +#define TKF_NUMERICIDENTIFIERSTART 0x00000040 +#define TKF_IGNOREKEYWORDS 0x00000080 +#define TKF_NOCASEKEYWORDS 0x00000100 +#define TKF_NOUNDERSCOREINIDENTIFIER 0x00000200 +#define TKF_NODASHINIDENTIFIER 0x00000400 +#define TKF_COMMENTTOKENS 0x00000800 + +enum +{ + TKERR_NONE, + TKERR_UNKNOWN, + TKERR_BUFFERCREATE, + TKERR_UNRECOGNIZEDSYMBOL, + TKERR_DUPLICATESYMBOL, + TKERR_STRINGLENGTHEXCEEDED, + TKERR_IDENTIFIERLENGTHEXCEEDED, + TKERR_EXPECTED_INTEGER, + TKERR_EXPECTED_IDENTIFIER, + TKERR_EXPECTED_STRING, + TKERR_EXPECTED_CHAR, + TKERR_EXPECTED_FLOAT, + TKERR_UNEXPECTED_TOKEN, + TKERR_INVALID_DIRECTIVE, + TKERR_INCLUDE_FILE_NOTFOUND, + TKERR_UNMATCHED_DIRECTIVE, + TKERR_USERERROR, +}; + +enum +{ + TK_EOF = -1, + TK_UNDEFINED, + TK_COMMENT, + TK_EOL, + TK_CHAR, + TK_STRING, + TK_INT, + TK_INTEGER = TK_INT, + TK_FLOAT, + TK_IDENTIFIER, + TK_USERDEF, +}; + +//Token defines +enum +{ + TK_BLOCK_START = TK_USERDEF, + TK_BLOCK_END, + TK_VECTOR_START, + TK_VECTOR_END, + TK_OPEN_PARENTHESIS, + TK_CLOSED_PARENTHESIS, + TK_VECTOR, + TK_GREATER_THAN, + TK_LESS_THAN, + TK_EQUALS, + TK_NOT, + + NUM_USER_TOKENS +}; + +//ID defines +enum +{ + ID_AFFECT = NUM_USER_TOKENS, + ID_SOUND, + ID_MOVE, + ID_ROTATE, + ID_WAIT, + ID_BLOCK_START, + ID_BLOCK_END, + ID_SET, + ID_LOOP, + ID_LOOPEND, + ID_PRINT, + ID_USE, + ID_FLUSH, + ID_RUN, + ID_KILL, + ID_REMOVE, + ID_CAMERA, + ID_GET, + ID_RANDOM, + ID_IF, + ID_ELSE, + ID_REM, + ID_TASK, + ID_DO, + ID_DECLARE, + ID_FREE, + ID_DOWAIT, + ID_SIGNAL, + ID_WAITSIGNAL, + ID_PLAY, + + ID_TAG, + ID_EOF, + NUM_IDS +}; + +//Type defines +enum +{ + //Wait types + TYPE_WAIT_COMPLETE = NUM_IDS, + TYPE_WAIT_TRIGGERED, + + //Set types + TYPE_ANGLES, + TYPE_ORIGIN, + + //Affect types + TYPE_INSERT, + TYPE_FLUSH, + + //Camera types + TYPE_PAN, + TYPE_ZOOM, + TYPE_MOVE, + TYPE_FADE, + TYPE_PATH, + TYPE_ENABLE, + TYPE_DISABLE, + TYPE_SHAKE, + TYPE_ROLL, + TYPE_TRACK, + TYPE_DISTANCE, + TYPE_FOLLOW, + + //Variable type + TYPE_VARIABLE, + + TYPE_EOF, + + ID_CONTINUE, + ID_BREAK, + ID_ACTIVATE, + ID_DEACTIVATE, + ID_WHILE, + ID_WAITUNTIL, + ID_WAITAFFECT, + ID_TASKCOMPLETED, + ID_SIGNALCOUNT, + TK_GE, + TK_LE, + + NUM_TYPES +}; + +enum +{ + MSG_COMPLETED, + MSG_EOF, + NUM_MESSAGES, +}; +////////////////////////////////////////////////////////////////////////// +/* END Constants END */ +////////////////////////////////////////////////////////////////////////// + +//NOTENOTE: Only change this to re-point ICARUS to a new script directory +// NOTE! A '/' (forward slash) should be checked for along with the script dir. +#define Q3_SCRIPT_DIR "scripts" + +#define MAX_FILENAME_LENGTH 256 + +#define IBI_EXT ".IBI" //(I)nterpreted (B)lock (I)nstructions +#define IBI_HEADER_ID "IBI" + +////////////////////////////////////////////////////////////////////////// +/* BEGIN Quake 3 Game Interface BEGIN */ +////////////////////////////////////////////////////////////////////////// + +// The script data buffer. +typedef struct pscript_s +{ + char *buffer; + long length; +} pscript_t; + +// STL map type definitions for the Entity List and Script Buffer List. +typedef map < string, int, less, allocator > entitylist_t; +typedef map < string, pscript_t*, less, allocator > scriptlist_t; + +// STL map type definitions for the variable containers. +typedef map < string, string > varString_m; +typedef map < string, float > varFloat_m; + + +// The Quake 3 Game Interface Class for Quake3 and Icarus to use. +// Created: 10/08/02 by Aurelio Reis. +class CQuake3GameInterface : public IGameInterface +{ +private: + // A list of script buffers (to ensure we cache a script only once). + scriptlist_t m_ScriptList; + + // A list of Game Elements/Objects. + entitylist_t m_EntityList; + + // Variable stuff. + varString_m m_varStrings; + varFloat_m m_varFloats; + varString_m m_varVectors; + int m_numVariables; + + int m_entFilter; + + // Register variables functions. + void SetVar( int taskID, int entID, const char *type_name, const char *data ); + void VariableSaveFloats( varFloat_m &fmap ); + void VariableSaveStrings( varString_m &smap ); + void VariableLoadFloats( varFloat_m &fmap ); + void VariableLoadStrings( int type, varString_m &fmap ); + void InitVariables( void ); + int GetStringVariable( const char *name, const char **value ); + int GetFloatVariable( const char *name, float *value ); + int GetVectorVariable( const char *name, vec3_t value ); + int VariableDeclared( const char *name ); + int SetFloatVariable( const char *name, float value ); + int SetStringVariable( const char *name, const char *value ); + int SetVectorVariable( const char *name, const char *value ); + void PrisonerObjCheck(const char *name,const char *data); + +public: + // Static Singleton Instance. + static CQuake3GameInterface *m_pInstance; + + // Variable enums + enum { VTYPE_NONE = 0, VTYPE_FLOAT, VTYPE_STRING, VTYPE_VECTOR, MAX_VARIABLES = 32 }; + + // Register enums. + enum { SCRIPT_COULDNOTREGISTER = 0, SCRIPT_REGISTERED, SCRIPT_ALREADYREGISTERED }; + + // Constructor. + CQuake3GameInterface(); + + // Destructor (NOTE: Destroy the Game Interface BEFORE the Icarus Interface). + ~CQuake3GameInterface(); + + // Initialize an Entity by ID. + bool InitEntity( gentity_t *pEntity ); + + // Free an Entity by ID (NOTE, if this is called while a script is running the game will crash!). + void FreeEntity( gentity_t *pEntity ); + + // Determines whether or not an Entity needs ICARUS information. + bool ValidEntity( gentity_t *pEntity ); + + // Associate the entity's id and name so that it can be referenced later. + void AssociateEntity( gentity_t *pEntity ); + + // Make a valid script name. + int MakeValidScriptName( char **strScriptName ); + + // First looks to see if a script has already been loaded, if so, return SCRIPT_ALREADYREGISTERED. If a script has + // NOT been already cached, that script is loaded and the return is SCRIPT_REGISTERED. If a script could not + // be found cached and could not be loaded we return SCRIPT_COULDNOTREGISTER. + int RegisterScript( const char *strFileName, void **ppBuf, int &iLength ); + + // Precache all the resources needed by a Script and it's Entity (or vice-versa). + int PrecacheEntity( gentity_t *pEntity ); + + // Run the script. + void RunScript( const gentity_t *pEntity, const char *strScriptName ); + + // Log Icarus Entity's? + void Svcmd( void ); + + // Clear the list of entitys. + void ClearEntityList() { m_EntityList.clear(); } + + // Save all Variables. + int VariableSave( void ); + + // Load all Variables. + int VariableLoad( void ); + + // Overiddables. + + // Get the current Game flavor. + int GetFlavor(); + + //General + int LoadFile( const char *name, void **buf ); + void CenterPrint( const char *format, ... ); + void DebugPrint( e_DebugPrintLevel, const char *, ... ); + unsigned int GetTime( void ); //Gets the current time + //DWORD GetTimeScale(void ); + int PlaySound( int taskID, int entID, const char *name, const char *channel ); + void Lerp2Pos( int taskID, int entID, vec3_t origin, vec3_t angles, float duration ); + //void Lerp2Origin( int taskID, int entID, vec3_t origin, float duration ); + void Lerp2Angles( int taskID, int entID, vec3_t angles, float duration ); + int GetTag( int entID, const char *name, int lookup, vec3_t info ); + //void Lerp2Start( int taskID, int entID, float duration ); + //void Lerp2End( int taskID, int entID, float duration ); + void Set( int taskID, int entID, const char *type_name, const char *data ); + void Use( int entID, const char *name ); + void Activate( int entID, const char *name ); + void Deactivate( int entID, const char *name ); + void Kill( int entID, const char *name ); + void Remove( int entID, const char *name ); + float Random( float min, float max ); + void Play( int taskID, int entID, const char *type, const char *name ); + + //Camera functions + void CameraPan( vec3_t angles, vec3_t dir, float duration ); + void CameraMove( vec3_t origin, float duration ); + void CameraZoom( float fov, float duration ); + void CameraRoll( float angle, float duration ); + void CameraFollow( const char *name, float speed, float initLerp ); + void CameraTrack( const char *name, float speed, float initLerp ); + void CameraDistance( float dist, float initLerp ); + void CameraFade( float sr, float sg, float sb, float sa, float dr, float dg, float db, float da, float duration ); + void CameraPath( const char *name ); + void CameraEnable( void ); + void CameraDisable( void ); + void CameraShake( float intensity, int duration ); + + int GetFloat( int entID, const char *name, float *value ); + int GetVector( int entID, const char *name, vec3_t value ); + int GetString( int entID, const char *name, char **value ); + + int Evaluate( int p1Type, const char *p1, int p2Type, const char *p2, int operatorType ); + + void DeclareVariable( int type, const char *name ); + void FreeVariable( const char *name ); + + //Save / Load functions + int WriteSaveData( unsigned long chid, void *data, int length ); + int ReadSaveData( unsigned long chid, void *address, int length, void **addressptr = NULL ); + int LinkGame( int entID, int icarusID ); + + // Access functions + int CreateIcarus( int entID); + //Polls the engine for the sequencer of the entity matching the name passed + int GetByName( const char *name ); + // (g_entities[m_ownerID].svFlags&SVF_ICARUS_FREEZE) // return -1 indicates invalid + int IsFrozen(int entID); + void Free(void* data); + void *Malloc( int size ); + float MaxFloat(void); + + // Script precache functions. + void PrecacheRoff( const char *name ); + void PrecacheScript( const char *name ); + void PrecacheSound( const char *name ); + void PrecacheFromSet( const char *setname, const char *filename ); +}; + +// A Quick accessor function for accessing Quake 3 Interface specific functions. +inline CQuake3GameInterface *Quake3Game() { return (CQuake3GameInterface *)IGameInterface::GetGame(); } + +////////////////////////////////////////////////////////////////////////// +/* END Quake 3 Game Interface END */ +////////////////////////////////////////////////////////////////////////// + +#endif //__Q3_INTERFACE__ \ No newline at end of file diff --git a/code/game/SpeederNPC.c b/code/game/SpeederNPC.c new file mode 100644 index 0000000..50e4e47 --- /dev/null +++ b/code/game/SpeederNPC.c @@ -0,0 +1,1190 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#include "..\game\wp_saber.h" +#include "../cgame/cg_local.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +extern void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +#endif + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +#ifdef QAGAME //SP or gameside MP +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +#endif + +#ifdef _JK2MP + +#include "../namespace_begin.h" + +extern void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int BG_GetTime(void); +#endif + +//Alright, actually, most of this file is shared between game and cgame for MP. +//I would like to keep it this way, so when modifying for SP please keep in +//mind the bgEntity restrictions imposed. -rww + +#define STRAFERAM_DURATION 8 +#define STRAFERAM_ANGLE 8 + + +#ifndef _JK2MP +bool VEH_StartStrafeRam(Vehicle_t *pVeh, bool Right) +{ + if (!(pVeh->m_ulFlags&VEH_STRAFERAM)) + { + float speed = VectorLength(pVeh->m_pParentEntity->client->ps.velocity); + if (speed>400.0f) + { + // Compute Pos3 + //-------------- + vec3_t right; + AngleVectors(pVeh->m_vOrientation, 0, right, 0); + VectorMA(pVeh->m_pParentEntity->client->ps.velocity, (Right)?( speed):(-speed), right, pVeh->m_pParentEntity->pos3); + + pVeh->m_ulFlags |= VEH_STRAFERAM; + pVeh->m_fStrafeTime = (Right)?(STRAFERAM_DURATION):(-STRAFERAM_DURATION); + + if (pVeh->m_iSoundDebounceTimerm_pVehicleInfo->soundShift1; break; + case 2: shiftSound=pVeh->m_pVehicleInfo->soundShift2; break; + case 3: shiftSound=pVeh->m_pVehicleInfo->soundShift3; break; + case 4: shiftSound=pVeh->m_pVehicleInfo->soundShift4; break; + } + if (shiftSound) + { + pVeh->m_iSoundDebounceTimer = level.time + Q_irand(1000, 4000); + G_SoundIndexOnEnt( pVeh->m_pParentEntity, CHAN_AUTO, shiftSound); + } + } + return true; + } + } + return false; +} +#else +bool VEH_StartStrafeRam(Vehicle_t *pVeh, bool Right, int Duration) +{ + return false; +} +#endif + + +#ifdef QAGAME //game-only.. for now +// Like a think or move command, this updates various vehicle properties. +bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + if ( !g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ) ) + { + return false; + } + + // See whether this vehicle should be exploding. + if ( pVeh->m_iDieTime != 0 ) + { + pVeh->m_pVehicleInfo->DeathUpdate( pVeh ); + } + + // Update move direction. +#ifndef _JK2MP //this makes prediction unhappy, and rightfully so. + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + + if ( pVeh->m_ulFlags & VEH_FLYING ) + { + vec3_t vVehAngles; + VectorSet(vVehAngles, 0, pVeh->m_vOrientation[YAW], 0 ); + AngleVectors( vVehAngles, parent->client->ps.moveDir, NULL, NULL ); + } + else + { + vec3_t vVehAngles; + VectorSet(vVehAngles, pVeh->m_vOrientation[PITCH], pVeh->m_vOrientation[YAW], 0 ); + AngleVectors( vVehAngles, parent->client->ps.moveDir, NULL, NULL ); + } + + // Check For A Strafe Ram + //------------------------ + if (!(pVeh->m_ulFlags&VEH_STRAFERAM) && !(pVeh->m_ulFlags&VEH_FLYING)) + { + // Started A Strafe + //------------------ + if (pVeh->m_ucmd.rightmove && !pVeh->m_fStrafeTime) + { + pVeh->m_fStrafeTime = (pVeh->m_ucmd.rightmove>0)?(level.time):(-1*level.time); + } + + // Ended A Strafe + //---------------- + else if (!pVeh->m_ucmd.rightmove && pVeh->m_fStrafeTime) + { + // If It Was A Short Burst, Start The Strafe Ram + //----------------------------------------------- + if ((level.time - abs(pVeh->m_fStrafeTime))<300) + { + if (!VEH_StartStrafeRam(pVeh, (pVeh->m_fStrafeTime>0))) + { + pVeh->m_fStrafeTime = 0; + } + } + + // Otherwise, Clear The Timer + //---------------------------- + else + { + pVeh->m_fStrafeTime = 0; + } + } + } + + // If Currently In A StrafeRam, Check To See If It Is Done (Timed Out) + //--------------------------------------------------------------------- + else if (!pVeh->m_fStrafeTime) + { + pVeh->m_ulFlags &=~VEH_STRAFERAM; + } + + + // Exhaust Effects Start And Stop When The Accelerator Is Pressed + //---------------------------------------------------------------- + if (pVeh->m_pVehicleInfo->iExhaustFX) + { + // Start It On Each Exhaust Bolt + //------------------------------- + if (pVeh->m_ucmd.forwardmove && !(pVeh->m_ulFlags&VEH_ACCELERATORON)) + { + pVeh->m_ulFlags |= VEH_ACCELERATORON; + for (int i=0; (im_iExhaustTag[i]!=-1); i++) + { + G_PlayEffect(pVeh->m_pVehicleInfo->iExhaustFX, parent->playerModel, pVeh->m_iExhaustTag[i], parent->s.number, parent->currentOrigin, 1, qtrue); + } + } + + // Stop It On Each Exhaust Bolt + //------------------------------ + else if (!pVeh->m_ucmd.forwardmove && (pVeh->m_ulFlags&VEH_ACCELERATORON)) + { + pVeh->m_ulFlags &=~VEH_ACCELERATORON; + for (int i=0; (im_iExhaustTag[i]!=-1); i++) + { + G_StopEffect(pVeh->m_pVehicleInfo->iExhaustFX, parent->playerModel, pVeh->m_iExhaustTag[i], parent->s.number); + } + } + } + + if (!(pVeh->m_ulFlags&VEH_ARMORLOW) && (pVeh->m_iArmor <= pVeh->m_pVehicleInfo->armor/3)) + { + pVeh->m_ulFlags |= VEH_ARMORLOW; + + } + + // Armor Gone Effects (Fire) + //--------------------------- + if (pVeh->m_pVehicleInfo->iArmorGoneFX) + { + if (!(pVeh->m_ulFlags&VEH_ARMORGONE) && (pVeh->m_iArmor <= 0)) + { + pVeh->m_ulFlags |= VEH_ARMORGONE; + G_PlayEffect(pVeh->m_pVehicleInfo->iArmorGoneFX, parent->playerModel, parent->crotchBolt, parent->s.number, parent->currentOrigin, 1, qtrue); + parent->s.loopSound = G_SoundIndex( "sound/vehicles/common/fire_lp.wav" ); + } + } +#endif + + return true; +} +#endif //QAGAME + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + playerState_t *parentPS; + playerState_t *pilotPS = NULL; + int curTime; + +#ifdef _JK2MP + parentPS = pVeh->m_pParentEntity->playerState; + if (pVeh->m_pPilot) + { + pilotPS = pVeh->m_pPilot->playerState; + } +#else + parentPS = &pVeh->m_pParentEntity->client->ps; + if (pVeh->m_pPilot) + { + pilotPS = &pVeh->m_pPilot->client->ps; + } +#endif + + + // If we're flying, make us accelerate at 40% (about half) acceleration rate, and restore the pitch + // to origin (straight) position (at 5% increments). + if ( pVeh->m_ulFlags & VEH_FLYING ) + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier * 0.4f; + } +#ifdef _JK2MP + else if ( !parentPS->m_iVehicleNum ) +#else + else if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//drifts to a stop + speedInc = 0; + //pVeh->m_ucmd.forwardmove = 127; + } + else + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + +#ifndef _JK2MP//SP + curTime = level.time; +#elif QAGAME//MP GAME + curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + curTime = pm->cmd.serverTime; +#endif + + + + if ( (pVeh->m_pPilot /*&& (pilotPS->weapon == WP_NONE || pilotPS->weapon == WP_MELEE )*/ && + (pVeh->m_ucmd.buttons & BUTTON_ALT_ATTACK) && pVeh->m_pVehicleInfo->turboSpeed) +#ifdef _JK2MP + || + (parentPS && parentPS->electrifyTime > curTime && pVeh->m_pVehicleInfo->turboSpeed) //make them go! +#endif + ) + { +#ifdef _JK2MP + if ( (parentPS && parentPS->electrifyTime > curTime) || + (pVeh->m_pPilot->playerState && + (pVeh->m_pPilot->playerState->weapon == WP_MELEE || + (pVeh->m_pPilot->playerState->weapon == WP_SABER && pVeh->m_pPilot->playerState->saberHolstered))) ) + { +#endif + if ((curTime - pVeh->m_iTurboTime)>pVeh->m_pVehicleInfo->turboRecharge) + { + pVeh->m_iTurboTime = (curTime + pVeh->m_pVehicleInfo->turboDuration); + if (pVeh->m_pVehicleInfo->iTurboStartFX) + { + int i; + for (i=0; (im_iExhaustTag[i]!=-1); i++) + { + #ifndef _JK2MP//SP + // Start The Turbo Fx Start + //-------------------------- + G_PlayEffect(pVeh->m_pVehicleInfo->iTurboStartFX, pVeh->m_pParentEntity->playerModel, pVeh->m_iExhaustTag[i], pVeh->m_pParentEntity->s.number, pVeh->m_pParentEntity->currentOrigin ); + + // Start The Looping Effect + //-------------------------- + if (pVeh->m_pVehicleInfo->iTurboFX) + { + G_PlayEffect(pVeh->m_pVehicleInfo->iTurboFX, pVeh->m_pParentEntity->playerModel, pVeh->m_iExhaustTag[i], pVeh->m_pParentEntity->s.number, pVeh->m_pParentEntity->currentOrigin, pVeh->m_pVehicleInfo->turboDuration, qtrue); + } + + #else + #ifdef QAGAME + if (pVeh->m_pParentEntity && + pVeh->m_pParentEntity->ghoul2 && + pVeh->m_pParentEntity->playerState) + { //fine, I'll use a tempent for this, but only because it's played only once at the start of a turbo. + vec3_t boltOrg, boltDir; + mdxaBone_t boltMatrix; + + VectorSet(boltDir, 0.0f, pVeh->m_pParentEntity->playerState->viewangles[YAW], 0.0f); + + trap_G2API_GetBoltMatrix(pVeh->m_pParentEntity->ghoul2, 0, pVeh->m_iExhaustTag[i], &boltMatrix, boltDir, pVeh->m_pParentEntity->playerState->origin, level.time, NULL, pVeh->m_pParentEntity->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltDir); + G_PlayEffectID(pVeh->m_pVehicleInfo->iTurboStartFX, boltOrg, boltDir); + } + #endif + #endif + } + } + #ifndef _JK2MP //kill me now + if (pVeh->m_pVehicleInfo->soundTurbo) + { + G_SoundIndexOnEnt(pVeh->m_pParentEntity, CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo); + } + #endif + parentPS->speed = pVeh->m_pVehicleInfo->turboSpeed; // Instantly Jump To Turbo Speed + } +#ifdef _JK2MP + } +#endif + } + + // Slide Breaking + if (pVeh->m_ulFlags&VEH_SLIDEBREAKING) + { + if (pVeh->m_ucmd.forwardmove>=0 +#ifndef _JK2MP + || ((level.time - pVeh->m_pParentEntity->lastMoveTime)>500) +#endif + ) + { + pVeh->m_ulFlags &= ~VEH_SLIDEBREAKING; + } + parentPS->speed = 0; + } + else if ( + (curTime > pVeh->m_iTurboTime) && + !(pVeh->m_ulFlags&VEH_FLYING) && + pVeh->m_ucmd.forwardmove<0 && + fabs(pVeh->m_vOrientation[ROLL])>25.0f) + { + pVeh->m_ulFlags |= VEH_SLIDEBREAKING; + } + + + if ( curTime < pVeh->m_iTurboTime ) + { + speedMax = pVeh->m_pVehicleInfo->turboSpeed; + if (parentPS) + { + parentPS->eFlags |= EF_JETPACK_ACTIVE; + } + } + else + { + speedMax = pVeh->m_pVehicleInfo->speedMax; + if (parentPS) + { + parentPS->eFlags &= ~EF_JETPACK_ACTIVE; + } + } + + + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + + // Xbox - yeah, see we have these input devices -- and they're ANALOG! Wow! + if( pVeh->m_ucmd.forwardmove > 0 ) + speedMax *= pVeh->m_ucmd.forwardmove / 127.0f; + + if ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + parentPS->speed -= speedIdleDec; + } + } + // No input, so coast to stop. + else if ( parentPS->speed > 0.0f ) + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < 0.0f ) + { + parentPS->speed = 0.0f; + } + } + else if ( parentPS->speed < 0.0f ) + { + parentPS->speed += speedIdleDec; + if ( parentPS->speed > 0.0f ) + { + parentPS->speed = 0.0f; + } + } + } + else + { + if ( !pVeh->m_pVehicleInfo->strafePerc +#ifdef _JK2MP + || (0 && pVeh->m_pParentEntity->s.number < MAX_CLIENTS) ) +#else + || (!g_speederControlScheme->value && !pVeh->m_pParentEntity->s.number) ) +#endif + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + //pVeh->m_ucmd.rightmove = 0; + } + } + + if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + +#ifndef _JK2MP + // In SP, The AI Pilots Can Directly Control The Speed Of Their Bike In Order To + // Match The Speed Of The Person They Are Trying To Chase + //------------------------------------------------------------------------------- + if (pVeh->m_pPilot && (pVeh->m_ucmd.buttons&BUTTON_VEH_SPEED)) + { + parentPS->speed = pVeh->m_pPilot->client->ps.speed; + } +#endif + + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +//Oh, and please, use "< MAX_CLIENTS" to check for "player" and not +//"!s.number", this is a universal check that will work for both SP +//and MP. -rww +// ProcessOrientCommands the Vehicle. +#ifdef _JK2MP //temp hack til mp speeder controls are sorted -rww +extern void AnimalProcessOri(Vehicle_t *pVeh); +#endif +void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + playerState_t *riderPS; + playerState_t *parentPS; + +#ifdef _JK2MP + float angDif; + + if (pVeh->m_pPilot) + { + riderPS = pVeh->m_pPilot->playerState; + } + else + { + riderPS = pVeh->m_pParentEntity->playerState; + } + parentPS = pVeh->m_pParentEntity->playerState; + + //pVeh->m_vOrientation[YAW] = 0.0f;//riderPS->viewangles[YAW]; + angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*4.0f; //magic number hackery + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - angDif*(pVeh->m_fTimeModifier*0.2f)); + + if (parentPS->electrifyTime > pm->cmd.serverTime) + { //do some crazy stuff + pVeh->m_vOrientation[YAW] += (sin(pm->cmd.serverTime/1000.0f)*3.0f)*pVeh->m_fTimeModifier; + } + } + +#else + gentity_t *rider = pVeh->m_pParentEntity->owner; + if ( !rider || !rider->client ) + { + riderPS = &pVeh->m_pParentEntity->client->ps; + } + else + { + riderPS = &rider->client->ps; + } + parentPS = &pVeh->m_pParentEntity->client->ps; + + if (pVeh->m_ulFlags & VEH_FLYING) + { + pVeh->m_vOrientation[YAW] += pVeh->m_vAngularVelocity; + } + else if ( + (pVeh->m_ulFlags & VEH_SLIDEBREAKING) || // No Angles Control While Out Of Control + (pVeh->m_ulFlags & VEH_OUTOFCONTROL) // No Angles Control While Out Of Control + ) + { + // Any ability to change orientation? + } + else if ( + (pVeh->m_ulFlags & VEH_STRAFERAM) // No Angles Control While Strafe Ramming + ) + { + if (pVeh->m_fStrafeTime>0) + { + pVeh->m_fStrafeTime--; + pVeh->m_vOrientation[ROLL] += (pVeh->m_fStrafeTime<( STRAFERAM_DURATION/2))?(-STRAFERAM_ANGLE):( STRAFERAM_ANGLE); + } + else if (pVeh->m_fStrafeTime<0) + { + pVeh->m_fStrafeTime++; + pVeh->m_vOrientation[ROLL] += (pVeh->m_fStrafeTime>(-STRAFERAM_DURATION/2))?( STRAFERAM_ANGLE):(-STRAFERAM_ANGLE); + } + } + else + { + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + } +#endif + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef QAGAME + +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); + +// This function makes sure that the vehicle is properly animated. +void AnimateVehicle( Vehicle_t *pVeh ) +{ +} + +#endif //QAGAME + +//rest of file is shared + +#ifndef _JK2MP +extern void CG_ChangeWeapon( int num ); +#endif + + +#ifndef _JK2MP +extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 ); +#endif + + +//NOTE NOTE NOTE NOTE NOTE NOTE +//I want to keep this function BG too, because it's fairly generic already, and it +//would be nice to have proper prediction of animations. -rww +// This function makes sure that the rider's in this vehicle are properly animated. +void AnimateRiders( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_VS_IDLE; + float fSpeedPercToMax; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + playerState_t *pilotPS; + playerState_t *parentPS; + int curTime; + + + // Boarding animation. + if ( pVeh->m_iBoarding != 0 ) + { + // We've just started moarding, set the amount of time it will take to finish moarding. + if ( pVeh->m_iBoarding < 0 ) + { + int iAnimLen; + + // Boarding from left... + if ( pVeh->m_iBoarding == -1 ) + { + Anim = BOTH_VS_MOUNT_L; + } + else if ( pVeh->m_iBoarding == -2 ) + { + Anim = BOTH_VS_MOUNT_R; + } + else if ( pVeh->m_iBoarding == -3 ) + { + Anim = BOTH_VS_MOUNTJUMP_L; + } + else if ( pVeh->m_iBoarding == VEH_MOUNT_THROW_LEFT) + { + iBlend = 0; + Anim = BOTH_VS_MOUNTTHROW_R; + } + else if ( pVeh->m_iBoarding == VEH_MOUNT_THROW_RIGHT) + { + iBlend = 0; + Anim = BOTH_VS_MOUNTTHROW_L; + } + + // Set the delay time (which happens to be the time it takes for the animation to complete). + // NOTE: Here I made it so the delay is actually 40% (0.4f) of the animation time. +#ifdef _JK2MP + iAnimLen = BG_AnimLength( pVeh->m_pPilot->localAnimIndex, Anim ) * 0.4f; + pVeh->m_iBoarding = BG_GetTime() + iAnimLen; +#else + iAnimLen = PM_AnimLength( pVeh->m_pPilot->client->clientInfo.animFileIndex, Anim );// * 0.4f; + if (pVeh->m_iBoarding!=VEH_MOUNT_THROW_LEFT && pVeh->m_iBoarding!=VEH_MOUNT_THROW_RIGHT) + { + pVeh->m_iBoarding = level.time + (iAnimLen*0.4f); + } + else + { + pVeh->m_iBoarding = level.time + iAnimLen; + } +#endif + // Set the animation, which won't be interrupted until it's completed. + // TODO: But what if he's killed? Should the animation remain persistant??? + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + +#ifdef _JK2MP + BG_SetAnim(pVeh->m_pPilot->playerState, bgAllAnims[pVeh->m_pPilot->localAnimIndex].anims, + SETANIM_BOTH, Anim, iFlags, iBlend); +#else + NPC_SetAnim( pVeh->m_pPilot, SETANIM_BOTH, Anim, iFlags, iBlend ); + if (pVeh->m_pOldPilot) + { + iAnimLen = PM_AnimLength( pVeh->m_pPilot->client->clientInfo.animFileIndex, BOTH_VS_MOUNTTHROWEE); + NPC_SetAnim( pVeh->m_pOldPilot, SETANIM_BOTH, BOTH_VS_MOUNTTHROWEE, iFlags, iBlend ); + } +#endif + } + +#ifndef _JK2MP + if (pVeh->m_pOldPilot && pVeh->m_pOldPilot->client->ps.torsoAnimTimer<=0) + { + if (Q_irand(0, player->count)==0) + { + player->count++; + player->lastEnemy = pVeh->m_pOldPilot; + G_StartMatrixEffect(player, MEF_LOOK_AT_ENEMY|MEF_NO_RANGEVAR|MEF_NO_VERTBOB|MEF_NO_SPIN, 1000); + } + + gentity_t* oldPilot = pVeh->m_pOldPilot; + pVeh->m_pVehicleInfo->Eject(pVeh, pVeh->m_pOldPilot, qtrue); // will set pointer to zero + + // Kill Him + //---------- + oldPilot->client->noRagTime = -1; // no ragdoll for you + G_Damage(oldPilot, pVeh->m_pPilot, pVeh->m_pPilot, pVeh->m_pPilot->currentAngles, pVeh->m_pPilot->currentOrigin, 1000, 0, MOD_CRUSH); + + // Compute THe Throw Direction As Backwards From The Vehicle's Velocity + //---------------------------------------------------------------------- + vec3_t throwDir; + VectorScale(pVeh->m_pParentEntity->client->ps.velocity, -1.0f, throwDir); + VectorNormalize(throwDir); + throwDir[2] += 0.3f; // up a little + + // Now Throw Him Out + //------------------- + G_Throw(oldPilot, throwDir, VectorLength(pVeh->m_pParentEntity->client->ps.velocity)/10.0f); + NPC_SetAnim(oldPilot, SETANIM_BOTH, BOTH_DEATHBACKWARD1, SETANIM_FLAG_OVERRIDE, iBlend ); + } +#endif + + return; + } + +#ifdef _JK2MP //fixme + if (1) return; +#endif + +#ifdef _JK2MP + pilotPS = pVeh->m_pPilot->playerState; + parentPS = pVeh->m_pPilot->playerState; +#else + pilotPS = &pVeh->m_pPilot->client->ps; + parentPS = &pVeh->m_pParentEntity->client->ps; +#endif + +#ifndef _JK2MP//SP + curTime = level.time; +#elif QAGAME//MP GAME + curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + curTime = pm->cmd.serverTime; +#endif + + // Percentage of maximum speed relative to current speed. + fSpeedPercToMax = parentPS->speed / pVeh->m_pVehicleInfo->speedMax; + +/* // Going in reverse... +#ifdef _JK2MP + if ( pVeh->m_ucmd.forwardmove < 0 && !(pVeh->m_ulFlags & VEH_SLIDEBREAKING)) +#else + if ( fSpeedPercToMax < -0.018f && !(pVeh->m_ulFlags & VEH_SLIDEBREAKING)) +#endif + { + Anim = BOTH_VS_REV; + iBlend = 500; + bool HasWeapon = ((pilotPS->weapon != WP_NONE) && (pilotPS->weapon != WP_MELEE)); + if (HasWeapon) + { + if (pVeh->m_pPilot->s.numberm_pPilot->client->ps.weapon = WP_NONE; + G_RemoveWeaponModels(pVeh->m_pPilot); + } + } + else +*/ + { + bool HasWeapon = ((pilotPS->weapon != WP_NONE) && (pilotPS->weapon != WP_MELEE)); + bool Attacking = (HasWeapon && !!(pVeh->m_ucmd.buttons&BUTTON_ATTACK)); +#ifdef _JK2MP //fixme: flying tends to spaz out a lot + bool Flying = false; + bool Crashing = false; +#else + bool Flying = !!(pVeh->m_ulFlags & VEH_FLYING); + bool Crashing = !!(pVeh->m_ulFlags & VEH_CRASHING); +#endif + bool Right = (pVeh->m_ucmd.rightmove>0); + bool Left = (pVeh->m_ucmd.rightmove<0); + bool Turbo = (curTimem_iTurboTime); + EWeaponPose WeaponPose = WPOSE_NONE; + + + // Remove Crashing Flag + //---------------------- + pVeh->m_ulFlags &= ~VEH_CRASHING; + + + // Put Away Saber When It Is Not Active + //-------------------------------------- +#ifndef _JK2MP + if (HasWeapon && + (pVeh->m_pPilot->s.number>=MAX_CLIENTS || (cg.weaponSelectTime+500)weapon==WP_SABER && !pilotPS->SaberActive()))) + { + if (pVeh->m_pPilot->s.numberm_pPilot->client->ps.stats[ STAT_WEAPONS ] |= 1; // Riding means you get WP_NONE + CG_ChangeWeapon(WP_NONE); + } + + pVeh->m_pPilot->client->ps.weapon = WP_NONE; + G_RemoveWeaponModels(pVeh->m_pPilot); + } +#endif + + // Don't Interrupt Attack Anims + //------------------------------ +#ifdef _JK2MP + if (pilotPS->weaponTime>0) + { + return; + } +#else + if (pilotPS->torsoAnim>=BOTH_VS_ATL_S && pilotPS->torsoAnim<=BOTH_VS_ATF_G) + { + float bodyCurrent = 0.0f; + int bodyEnd = 0; + if (!!gi.G2API_GetBoneAnimIndex(&pVeh->m_pPilot->ghoul2[pVeh->m_pPilot->playerModel], pVeh->m_pPilot->rootBone, level.time, &bodyCurrent, NULL, &bodyEnd, NULL, NULL, NULL)) + { + if (bodyCurrent<=((float)(bodyEnd)-1.5f)) + { + return; + } + } + } +#endif + + // Compute The Weapon Pose + //-------------------------- + if (pilotPS->weapon==WP_BLASTER) + { + WeaponPose = WPOSE_BLASTER; + } + else if (pilotPS->weapon==WP_SABER) + { + if ( (pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VS_ATL_TO_R_S) + { + pVeh->m_ulFlags &= ~VEH_SABERINLEFTHAND; + } + if (!(pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VS_ATR_TO_L_S) + { + pVeh->m_ulFlags |= VEH_SABERINLEFTHAND; + } + WeaponPose = (pVeh->m_ulFlags&VEH_SABERINLEFTHAND)?(WPOSE_SABERLEFT):(WPOSE_SABERRIGHT); + } + + + if (Attacking && WeaponPose) + {// Attack! + iBlend = 100; + iFlags = SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART; + + // Auto Aiming + //=============================================== + if (!Left && !Right) // Allow player strafe keys to override + { +#ifndef _JK2MP + if (pVeh->m_pPilot->enemy) + { + vec3_t toEnemy; + float toEnemyDistance; + vec3_t actorRight; + float actorRightDot; + + VectorSubtract(pVeh->m_pPilot->currentOrigin, pVeh->m_pPilot->enemy->currentOrigin, toEnemy); + toEnemyDistance = VectorNormalize(toEnemy); + + AngleVectors(pVeh->m_pParentEntity->currentAngles, 0, actorRight, 0); + actorRightDot = DotProduct(toEnemy, actorRight); + + if (fabsf(actorRightDot)>0.5f || pilotPS->weapon==WP_SABER) + { + Left = (actorRightDot>0.0f); + Right = !Left; + } + else + { + Right = Left = false; + } + } + else +#endif + if (pilotPS->weapon==WP_SABER && !Left && !Right) + { + Left = (WeaponPose==WPOSE_SABERLEFT); + Right = !Left; + } + } + + + if (Left) + {// Attack Left + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VS_ATL_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_ATL_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_ATR_TO_L_S; break; + default: assert(0); + } + } + else if (Right) + {// Attack Right + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VS_ATR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_ATL_TO_R_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_ATR_S; break; + default: assert(0); + } + } + else + {// Attack Ahead + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VS_ATF_G; break; + default: assert(0); + } + } + + } + else if (Left && pVeh->m_ucmd.buttons&BUTTON_USE) + {// Look To The Left Behind + iBlend = 400; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + switch(WeaponPose) + { + case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; + default: Anim = BOTH_VS_LOOKLEFT; + } + } + else if (Right && pVeh->m_ucmd.buttons&BUTTON_USE) + {// Look To The Right Behind + iBlend = 400; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + switch(WeaponPose) + { + case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; + default: Anim = BOTH_VS_LOOKRIGHT; + } + } + else if (Turbo) + {// Kicked In Turbo + iBlend = 50; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + Anim = BOTH_VS_TURBO; + } + else if (Flying) + {// Off the ground in a jump + iBlend = 800; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_AIR; break; + case WPOSE_BLASTER: Anim = BOTH_VS_AIR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_AIR_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_AIR_SR; break; + default: assert(0); + } + } + else if (Crashing) + {// Hit the ground! + iBlend = 100; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_LAND; break; + case WPOSE_BLASTER: Anim = BOTH_VS_LAND_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_LAND_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_LAND_SR; break; + default: assert(0); + } + } + else + {// No Special Moves + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + + if (pVeh->m_vOrientation[ROLL] <= -20) + {// Lean Left + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_LEANL; break; + case WPOSE_BLASTER: Anim = BOTH_VS_LEANL_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_LEANL_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_LEANL_SR; break; + default: assert(0); + } + } + else if (pVeh->m_vOrientation[ROLL] >= 20) + {// Lean Right + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_LEANR; break; + case WPOSE_BLASTER: Anim = BOTH_VS_LEANR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_LEANR_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_LEANR_SR; break; + default: assert(0); + } + } + else + {// No Lean + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_IDLE; break; + case WPOSE_BLASTER: Anim = BOTH_VS_IDLE_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; + default: assert(0); + } + } + }// No Special Moves + }// Going backwards? + +#ifdef _JK2MP + iFlags &= ~SETANIM_FLAG_OVERRIDE; + if (pVeh->m_pPilot->playerState->torsoAnim == Anim) + { + pVeh->m_pPilot->playerState->torsoTimer = BG_AnimLength(pVeh->m_pPilot->localAnimIndex, Anim); + } + if (pVeh->m_pPilot->playerState->legsAnim == Anim) + { + pVeh->m_pPilot->playerState->legsTimer = BG_AnimLength(pVeh->m_pPilot->localAnimIndex, Anim); + } + BG_SetAnim(pVeh->m_pPilot->playerState, bgAllAnims[pVeh->m_pPilot->localAnimIndex].anims, + SETANIM_BOTH, Anim, iFlags|SETANIM_FLAG_HOLD, iBlend); +#else + NPC_SetAnim( pVeh->m_pPilot, SETANIM_BOTH, Anim, iFlags, iBlend ); +#endif +} + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +void G_SetSpeederVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + pVehInfo->AnimateVehicle = AnimateVehicle; + pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; +// pVehInfo->Board = Board; +// pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; +// pVehInfo->DeathUpdate = DeathUpdate; +// pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; + pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif + + //shared + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +extern char current_speeders; +// Create/Allocate a new Animal Vehicle (initializing it as well). +void G_CreateSpeederNPC( Vehicle_t **pVeh, const char *strType ) +{ + + current_speeders++; + +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strType )]; +#else + // Allocate the Vehicle. + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strType )]; +#endif + + (*pVeh)->alreadyCleaned = false; +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/code/game/WalkerNPC.c b/code/game/WalkerNPC.c new file mode 100644 index 0000000..554d173 --- /dev/null +++ b/code/game/WalkerNPC.c @@ -0,0 +1,573 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +#endif + +#ifdef QAGAME //we only want a few of these functions for BG + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern void Vehicle_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_VehicleTrace( trace_t *results, const vec3_t start, const vec3_t tMins, const vec3_t tMaxs, const vec3_t end, int passEntityNum, int contentmask ); + +static void RegisterAssets( Vehicle_t *pVeh ) +{ + //atst uses turret weapon +#ifdef _JK2MP + RegisterItem(BG_FindItemForWeapon(WP_TURRET)); +#else + // PUT SOMETHING HERE... +#endif + + //call the standard RegisterAssets now + g_vehicleInfo[VEHICLE_BASE].RegisterAssets( pVeh ); +} + +// Like a think or move command, this updates various vehicle properties. +/* +static bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + return g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ); +} +*/ + +// Board this Vehicle (get on). The first entity to board an empty vehicle becomes the Pilot. +static bool Board( Vehicle_t *pVeh, bgEntity_t *pEnt ) +{ + if ( !g_vehicleInfo[VEHICLE_BASE].Board( pVeh, pEnt ) ) + return false; + + // Set the board wait time (they won't be able to do anything, including getting off, for this amount of time). + pVeh->m_iBoarding = level.time + 1500; + + return true; +} +#endif //QAGAME + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + float fWalkSpeedMax; + bgEntity_t *parent = pVeh->m_pParentEntity; +#ifdef _JK2MP + playerState_t *parentPS = parent->playerState; +#else + playerState_t *parentPS = &parent->client->ps; +#endif + + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + speedMax = pVeh->m_pVehicleInfo->speedMax; + + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + +#ifdef _JK2MP + if ( !parentPS->m_iVehicleNum ) +#else + if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//drifts to a stop + speedInc = speedIdle * pVeh->m_fTimeModifier; + VectorClear( parentPS->moveDir ); + //m_ucmd.forwardmove = 127; + parentPS->speed = 0; + } + else + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + + if ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + parentPS->speed -= speedIdleDec; + } + } + // No input, so coast to stop. + else if ( parentPS->speed > 0.0f ) + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < 0.0f ) + { + parentPS->speed = 0.0f; + } + } + else if ( parentPS->speed < 0.0f ) + { + parentPS->speed += speedIdleDec; + if ( parentPS->speed > 0.0f ) + { + parentPS->speed = 0.0f; + } + } + } + else + { + if ( pVeh->m_ucmd.forwardmove < 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + if ( pVeh->m_ucmd.upmove < 0 ) + { + pVeh->m_ucmd.upmove = 0; + } + + pVeh->m_ucmd.rightmove = 0; + + /*if ( !pVeh->m_pVehicleInfo->strafePerc + || (!g_speederControlScheme->value && !parent->s.number) ) + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + pVeh->m_ucmd.rightmove = 0; + }*/ + } + + fWalkSpeedMax = speedMax * 0.275f; + if ( pVeh->m_ucmd.buttons & BUTTON_WALKING && parentPS->speed > fWalkSpeedMax ) + { + parentPS->speed = fWalkSpeedMax; + } + else if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +#ifdef _JK2MP +extern void FighterYawAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS); //FighterNPC.c +extern void FighterPitchAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS); //FighterNPC.c +#endif + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessOrientCommands the Vehicle. +static void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + float speed; + bgEntity_t *parent = pVeh->m_pParentEntity; + playerState_t *parentPS, *riderPS; + +#ifdef _JK2MP + bgEntity_t *rider = NULL; + if (parent->s.owner != ENTITYNUM_NONE) + { + rider = PM_BGEntForNum(parent->s.owner); //&g_entities[parent->r.ownerNum]; + } +#else + gentity_t *rider = parent->owner; +#endif + +#ifdef _JK2MP + if ( !rider ) +#else + if ( !rider || !rider->client ) +#endif + { + rider = parent; + } + +#ifdef _JK2MP + parentPS = parent->playerState; + riderPS = rider->playerState; +#else + parentPS = &parent->client->ps; + riderPS = &rider->client->ps; +#endif + + speed = VectorLength( parentPS->velocity ); + + // If the player is the rider... + if ( rider->s.number < MAX_CLIENTS ) + {//FIXME: use the vehicle's turning stat in this calc +#ifdef _JK2MP + FighterYawAdjust(pVeh, riderPS, parentPS); + //FighterPitchAdjust(pVeh, riderPS, parentPS); + pVeh->m_vOrientation[PITCH] = riderPS->viewangles[PITCH]; +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + pVeh->m_vOrientation[PITCH] = riderPS->viewangles[PITCH]; +#endif + } + else + { + float turnSpeed = pVeh->m_pVehicleInfo->turningSpeed; + if ( !pVeh->m_pVehicleInfo->turnWhenStopped + && !parentPS->speed )//FIXME: or !pVeh->m_ucmd.forwardmove? + {//can't turn when not moving + //FIXME: or ramp up to max turnSpeed? + turnSpeed = 0.0f; + } +#ifdef _JK2MP + if (rider->s.eType == ET_NPC) +#else + if ( !rider || rider->NPC ) +#endif + {//help NPCs out some + turnSpeed *= 2.0f; +#ifdef _JK2MP + if (parentPS->speed > 200.0f) +#else + if ( parent->client->ps.speed > 200.0f ) +#endif + { + turnSpeed += turnSpeed * parentPS->speed/200.0f*0.05f; + } + } + turnSpeed *= pVeh->m_fTimeModifier; + + //default control scheme: strafing turns, mouselook aims + if ( pVeh->m_ucmd.rightmove < 0 ) + { + pVeh->m_vOrientation[YAW] += turnSpeed; + } + else if ( pVeh->m_ucmd.rightmove > 0 ) + { + pVeh->m_vOrientation[YAW] -= turnSpeed; + } + + if ( pVeh->m_pVehicleInfo->malfunctionArmorLevel && pVeh->m_iArmor <= pVeh->m_pVehicleInfo->malfunctionArmorLevel ) + {//damaged badly + } + } + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef QAGAME //back to our game-only functions +// This function makes sure that the vehicle is properly animated. +static void AnimateVehicle( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_STAND1; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + float fSpeedPercToMax; + + // We're dead (boarding is reused here so I don't have to make another variable :-). + if ( parent->health <= 0 ) + { + if ( pVeh->m_iBoarding != -999 ) // Animate the death just once! + { + pVeh->m_iBoarding = -999; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + // FIXME! Why do you keep repeating over and over!!?!?!? Bastard! + //Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_DEATH1, iFlags, iBlend ); + } + return; + } + +// Following is redundant to g_vehicles.c +// if ( pVeh->m_iBoarding ) +// { +// //we have no boarding anim +// if (pVeh->m_iBoarding < level.time) +// { //we are on now +// pVeh->m_iBoarding = 0; +// } +// else +// { +// return; +// } +// } + + // Percentage of maximum speed relative to current speed. + //float fSpeed = VectorLength( client->ps.velocity ); + fSpeedPercToMax = parent->client->ps.speed / pVeh->m_pVehicleInfo->speedMax; + + // If we're moving... + if ( fSpeedPercToMax > 0.0f ) //fSpeedPercToMax >= 0.85f ) + { + float fYawDelta; + + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE; + fYawDelta = pVeh->m_vPrevOrientation[YAW] - pVeh->m_vOrientation[YAW]; + + // NOTE: Mikes suggestion for fixing the stuttering walk (left/right) is to maintain the + // current frame between animations. I have no clue how to do this and have to work on other + // stuff so good luck to him :-p AReis + + // If we're walking (or our speed is less than .275%)... + if ( ( pVeh->m_ucmd.buttons & BUTTON_WALKING ) || fSpeedPercToMax < 0.275f ) + { + // Make them lean if we're turning. + /*if ( fYawDelta < -0.0001f ) + { + Anim = BOTH_VT_WALK_FWD_L; + } + else if ( fYawDelta > 0.0001 ) + { + Anim = BOTH_VT_WALK_FWD_R; + } + else*/ + { + Anim = BOTH_WALK1; + } + } + // otherwise we're running. + else + { + // Make them lean if we're turning. + /*if ( fYawDelta < -0.0001f ) + { + Anim = BOTH_VT_RUN_FWD_L; + } + else if ( fYawDelta > 0.0001 ) + { + Anim = BOTH_VT_RUN_FWD_R; + } + else*/ + { + Anim = BOTH_RUN1; + } + } + } + else + { + // Going in reverse... + if ( fSpeedPercToMax < -0.018f ) + { + iFlags = SETANIM_FLAG_NORMAL; + Anim = BOTH_WALKBACK1; + iBlend = 500; + } + else + { + //int iChance = Q_irand( 0, 20000 ); + + // Every once in a while buck or do a different idle... + iFlags = SETANIM_FLAG_NORMAL | SETANIM_FLAG_RESTART | SETANIM_FLAG_HOLD; + iBlend = 600; +#ifdef _JK2MP + if (parent->client->ps.m_iVehicleNum) +#else + if ( pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//occupado + Anim = BOTH_STAND1; + } + else + {//wide open for you, baby + Anim = BOTH_STAND2; + } + } + } + + Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); +} + +//rwwFIXMEFIXME: This is all going to have to be predicted I think, or it will feel awful +//and lagged +#endif //QAGAME + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +//on the client this function will only set up the process command funcs +void G_SetWalkerVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + pVehInfo->AnimateVehicle = AnimateVehicle; +// pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; + pVehInfo->Board = Board; +// pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; +// pVehInfo->DeathUpdate = DeathUpdate; + pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; +// pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif //QAGAME + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +//this is a BG function too in MP so don't un-bg-compatibilify it -rww +void G_CreateWalkerNPC( Vehicle_t **pVeh, const char *strAnimalType ) +{ + // Allocate the Vehicle. +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#else + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#endif +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/code/game/ai.h b/code/game/ai.h new file mode 100644 index 0000000..0fc31f9 --- /dev/null +++ b/code/game/ai.h @@ -0,0 +1,134 @@ +#ifndef __AI__ +#define __AI__ + +//Distance ratings +enum distance_e +{ + DIST_MELEE, + DIST_LONG, +}; + +//Attack types +enum attack_e +{ + ATTACK_MELEE, + ATTACK_RANGE, +}; + +enum +{ + SQUAD_IDLE, //No target found, waiting + SQUAD_STAND_AND_SHOOT, //Standing in position and shoot (no cover) + SQUAD_RETREAT, //Running away from combat + SQUAD_COVER, //Under protective cover + SQUAD_TRANSITION, //Moving between points, not firing + SQUAD_POINT, //On point, laying down suppressive fire + SQUAD_SCOUT, //Poking out to draw enemy + NUM_SQUAD_STATES, +}; + +//sigh... had to move in here for groupInfo +typedef enum //# rank_e +{ + RANK_CIVILIAN, + RANK_CREWMAN, + RANK_ENSIGN, + RANK_LT_JG, + RANK_LT, + RANK_LT_COMM, + RANK_COMMANDER, + RANK_CAPTAIN +} rank_t; + +qboolean NPC_CheckPlayerTeamStealth( void ); + +//AI_GRENADIER +void NPC_BSGrenadier_Default( void ); + +//AI_TUSKEN +void NPC_BSTusken_Default( void ); + +//AI_SNIPER +void NPC_BSSniper_Default( void ); + +//AI_STORMTROOPER +void Saboteur_Decloak( gentity_t *self, int uncloakTime = 2000 ); +void NPC_BSST_Investigate( void ); +void NPC_BSST_Default( void ); +void NPC_BSST_Sleep( void ); + +//AI_JEDI +void NPC_BSJedi_Investigate( void ); +void NPC_BSJedi_Default( void ); +void NPC_BSJedi_FollowLeader( void ); + +// AI_DROID +void NPC_BSDroid_Default( void ); + +// AI_ImperialProbe +void NPC_BSImperialProbe_Default( void ); + +// AI_atst +void NPC_BSATST_Default( void ); + +void NPC_BSInterrogator_Default( void ); + +// AI Mark 1 +void NPC_BSMark1_Default( void ); + +// AI Mark 2 +void NPC_BSMark2_Default( void ); + +//monsters +void NPC_BSMineMonster_Default( void ); +void NPC_BSHowler_Default( void ); +void NPC_BSRancor_Default( void ); +void NPC_BSWampa_Default( void ); +void NPC_BSSandCreature_Default( void ); + +// animals +void NPC_BSAnimal_Default( void ); + +//Utilities +//Group AI +#define MAX_FRAME_GROUPS 32 +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct AIGroupMember_s +{ + int number; + int waypoint; + int pathCostToEnemy; + int closestBuddy; +} AIGroupMember_t; + +#define MAX_GROUP_MEMBERS 32 +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct AIGroupInfo_s +{ + int numGroup; + qboolean processed; + team_t team; + gentity_t *enemy; + int enemyWP; + int speechDebounceTime; + int lastClearShotTime; + int lastSeenEnemyTime; + int morale; + int moraleAdjust; + int moraleDebounce; + int memberValidateTime; + int activeMemberNum; + gentity_t *commander; + vec3_t enemyLastSeenPos; + int numState[ NUM_SQUAD_STATES ]; + AIGroupMember_t member[ MAX_GROUP_MEMBERS ]; +} AIGroupInfo_t; + +int AI_GetGroupSize( vec3_t origin, int radius, team_t playerTeam, gentity_t *avoid = NULL ); +int AI_GetGroupSize( gentity_t *ent, int radius ); + +void AI_GetGroup( gentity_t *self ); + +gentity_t *AI_DistributeAttack( gentity_t *attacker, gentity_t *enemy, team_t team, int threshold ); + +#endif //__AI__ \ No newline at end of file diff --git a/code/game/anims.h b/code/game/anims.h new file mode 100644 index 0000000..8ad06f5 --- /dev/null +++ b/code/game/anims.h @@ -0,0 +1,1797 @@ +#ifndef __ANIMS_H__ +#define __ANIMS_H__ +// playerAnimations + + +typedef enum //# animNumber_e +{ + //================================================= + //HEAD ANIMS + //================================================= + //# #sep Head-only anims + FACE_TALK0, //# silent + FACE_TALK1, //# quiet + FACE_TALK2, //# semi-quiet + FACE_TALK3, //# semi-loud + FACE_TALK4, //# loud + FACE_ALERT, //# + FACE_SMILE, //# + FACE_FROWN, //# + FACE_DEAD, //# + + //================================================= + //ANIMS IN WHICH UPPER AND LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep BOTH_ DEATHS + BOTH_DEATH1, //# First Death anim + BOTH_DEATH2, //# Second Death anim + BOTH_DEATH3, //# Third Death anim + BOTH_DEATH4, //# Fourth Death anim + BOTH_DEATH5, //# Fifth Death anim + BOTH_DEATH6, //# Sixth Death anim + BOTH_DEATH7, //# Seventh Death anim + BOTH_DEATH8, //# + BOTH_DEATH9, //# + BOTH_DEATH10, //# + BOTH_DEATH11, //# + BOTH_DEATH12, //# + BOTH_DEATH13, //# + BOTH_DEATH14, //# + BOTH_DEATH15, //# + BOTH_DEATH16, //# + BOTH_DEATH17, //# + BOTH_DEATH18, //# + BOTH_DEATH19, //# + BOTH_DEATH20, //# + BOTH_DEATH21, //# + BOTH_DEATH22, //# + BOTH_DEATH23, //# + BOTH_DEATH24, //# + BOTH_DEATH25, //# + + BOTH_DEATHFORWARD1, //# First Death in which they get thrown forward + BOTH_DEATHFORWARD2, //# Second Death in which they get thrown forward + BOTH_DEATHFORWARD3, //# Tavion's falling in cin# 23 + BOTH_DEATHBACKWARD1, //# First Death in which they get thrown backward + BOTH_DEATHBACKWARD2, //# Second Death in which they get thrown backward + + BOTH_DEATH1IDLE, //# Idle while close to death + BOTH_LYINGDEATH1, //# Death to play when killed lying down + BOTH_STUMBLEDEATH1, //# Stumble forward and fall face first death + BOTH_FALLDEATH1, //# Fall forward off a high cliff and splat death - start + BOTH_FALLDEATH1INAIR, //# Fall forward off a high cliff and splat death - loop + BOTH_FALLDEATH1LAND, //# Fall forward off a high cliff and splat death - hit bottom + BOTH_DEATH_ROLL, //# Death anim from a roll + BOTH_DEATH_FLIP, //# Death anim from a flip + BOTH_DEATH_SPIN_90_R, //# Death anim when facing 90 degrees right + BOTH_DEATH_SPIN_90_L, //# Death anim when facing 90 degrees left + BOTH_DEATH_SPIN_180, //# Death anim when facing backwards + BOTH_DEATH_LYING_UP, //# Death anim when lying on back + BOTH_DEATH_LYING_DN, //# Death anim when lying on front + BOTH_DEATH_FALLING_DN, //# Death anim when falling on face + BOTH_DEATH_FALLING_UP, //# Death anim when falling on back + BOTH_DEATH_CROUCHED, //# Death anim when crouched + //# #sep BOTH_ DEAD POSES # Should be last frame of corresponding previous anims + BOTH_DEAD1, //# First Death finished pose + BOTH_DEAD2, //# Second Death finished pose + BOTH_DEAD3, //# Third Death finished pose + BOTH_DEAD4, //# Fourth Death finished pose + BOTH_DEAD5, //# Fifth Death finished pose + BOTH_DEAD6, //# Sixth Death finished pose + BOTH_DEAD7, //# Seventh Death finished pose + BOTH_DEAD8, //# + BOTH_DEAD9, //# + BOTH_DEAD10, //# + BOTH_DEAD11, //# + BOTH_DEAD12, //# + BOTH_DEAD13, //# + BOTH_DEAD14, //# + BOTH_DEAD15, //# + BOTH_DEAD16, //# + BOTH_DEAD17, //# + BOTH_DEAD18, //# + BOTH_DEAD19, //# + BOTH_DEAD20, //# + BOTH_DEAD21, //# + BOTH_DEAD22, //# + BOTH_DEAD23, //# + BOTH_DEAD24, //# + BOTH_DEAD25, //# + BOTH_DEADFORWARD1, //# First thrown forward death finished pose + BOTH_DEADFORWARD2, //# Second thrown forward death finished pose + BOTH_DEADBACKWARD1, //# First thrown backward death finished pose + BOTH_DEADBACKWARD2, //# Second thrown backward death finished pose + BOTH_LYINGDEAD1, //# Killed lying down death finished pose + BOTH_STUMBLEDEAD1, //# Stumble forward death finished pose + BOTH_FALLDEAD1LAND, //# Fall forward and splat death finished pose + //# #sep BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + BOTH_DEADFLOP1, //# React to being shot from First Death finished pose + BOTH_DEADFLOP2, //# React to being shot from Second Death finished pose + BOTH_DISMEMBER_HEAD1, //# + BOTH_DISMEMBER_TORSO1, //# + BOTH_DISMEMBER_LLEG, //# + BOTH_DISMEMBER_RLEG, //# + BOTH_DISMEMBER_RARM, //# + BOTH_DISMEMBER_LARM, //# + //# #sep BOTH_ PAINS + BOTH_PAIN1, //# First take pain anim + BOTH_PAIN2, //# Second take pain anim + BOTH_PAIN3, //# Third take pain anim + BOTH_PAIN4, //# Fourth take pain anim + BOTH_PAIN5, //# Fifth take pain anim - from behind + BOTH_PAIN6, //# Sixth take pain anim - from behind + BOTH_PAIN7, //# Seventh take pain anim - from behind + BOTH_PAIN8, //# Eigth take pain anim - from behind + BOTH_PAIN9, //# + BOTH_PAIN10, //# + BOTH_PAIN11, //# + BOTH_PAIN12, //# + BOTH_PAIN13, //# + BOTH_PAIN14, //# + BOTH_PAIN15, //# + BOTH_PAIN16, //# + BOTH_PAIN17, //# + BOTH_PAIN18, //# + + //# #sep BOTH_ ATTACKS + BOTH_ATTACK1, //# Attack with stun baton + BOTH_ATTACK2, //# Attack with one-handed pistol + BOTH_ATTACK3, //# Attack with blaster rifle + BOTH_ATTACK4, //# Attack with disruptor + BOTH_ATTACK5, //# Another Rancor Attack + BOTH_ATTACK6, //# Yet Another Rancor Attack + BOTH_ATTACK7, //# Yet Another Rancor Attack + BOTH_ATTACK10, //# Attack with thermal det + BOTH_ATTACK11, //# "Attack" with tripmine and detpack + BOTH_MELEE1, //# First melee attack + BOTH_MELEE2, //# Second melee attack + BOTH_THERMAL_READY, //# pull back with thermal + BOTH_THERMAL_THROW, //# throw thermal + //* #sep BOTH_ SABER ANIMS + //Saber attack anims - power level 1 + BOTH_A1_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A1__L__R, //# Fast weak horizontal attack left to right + BOTH_A1__R__L, //# Fast weak horizontal attack right to left + BOTH_A1_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A1_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A1_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A1_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T1_BR__R, //# Fast arc bottom right to right + BOTH_T1_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T1_BR__L, //# Fast weak spin bottom right to left + BOTH_T1_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T1__R_TR, //# Fast arc right to top right + BOTH_T1__R_TL, //# Fast arc right to top left + BOTH_T1__R__L, //# Fast weak spin right to left + BOTH_T1__R_BL, //# Fast weak spin right to bottom left + BOTH_T1_TR_BR, //# Fast arc top right to bottom right + BOTH_T1_TR_TL, //# Fast arc top right to top left + BOTH_T1_TR__L, //# Fast arc top right to left + BOTH_T1_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T1_T__BR, //# Fast arc top to bottom right + BOTH_T1_T___R, //# Fast arc top to right + BOTH_T1_T__TR, //# Fast arc top to top right + BOTH_T1_T__TL, //# Fast arc top to top left + BOTH_T1_T___L, //# Fast arc top to left + BOTH_T1_T__BL, //# Fast arc top to bottom left + BOTH_T1_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T1_TL_BL, //# Fast arc top left to bottom left + BOTH_T1__L_BR, //# Fast weak spin left to bottom right + BOTH_T1__L__R, //# Fast weak spin left to right + BOTH_T1__L_TL, //# Fast arc left to top left + BOTH_T1_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T1_BL__R, //# Fast weak spin bottom left to right + BOTH_T1_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T1_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T1_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T1_TR_BR) + BOTH_T1_BR_T_, //# Fast arc bottom right to top (use: BOTH_T1_T__BR) + BOTH_T1__R_BR, //# Fast arc right to bottom right (use: BOTH_T1_BR__R) + BOTH_T1__R_T_, //# Fast ar right to top (use: BOTH_T1_T___R) + BOTH_T1_TR__R, //# Fast arc top right to right (use: BOTH_T1__R_TR) + BOTH_T1_TR_T_, //# Fast arc top right to top (use: BOTH_T1_T__TR) + BOTH_T1_TL__R, //# Fast arc top left to right (use: BOTH_T1__R_TL) + BOTH_T1_TL_TR, //# Fast arc top left to top right (use: BOTH_T1_TR_TL) + BOTH_T1_TL_T_, //# Fast arc top left to top (use: BOTH_T1_T__TL) + BOTH_T1_TL__L, //# Fast arc top left to left (use: BOTH_T1__L_TL) + BOTH_T1__L_TR, //# Fast arc left to top right (use: BOTH_T1_TR__L) + BOTH_T1__L_T_, //# Fast arc left to top (use: BOTH_T1_T___L) + BOTH_T1__L_BL, //# Fast arc left to bottom left (use: BOTH_T1_BL__L) + BOTH_T1_BL_T_, //# Fast arc bottom left to top (use: BOTH_T1_T__BL) + BOTH_T1_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T1_TL_BL) + //Saber Attack Start Transitions + BOTH_S1_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S1_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S1_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S1_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S1_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S1_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S1_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R1_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R1__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R1__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R1_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R1_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R1_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R1_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B1_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B1__R___, //# Bounce-back if attack from R is blocked + BOTH_B1_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B1_T____, //# Bounce-back if attack from T is blocked + BOTH_B1_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B1__L___, //# Bounce-back if attack from L is blocked + BOTH_B1_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D1_BR___, //# Deflection toward BR + BOTH_D1__R___, //# Deflection toward R + BOTH_D1_TR___, //# Deflection toward TR + BOTH_D1_TL___, //# Deflection toward TL + BOTH_D1__L___, //# Deflection toward L + BOTH_D1_BL___, //# Deflection toward BL + BOTH_D1_B____, //# Deflection toward B + //Saber attack anims - power level 2 + BOTH_A2_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A2__L__R, //# Fast weak horizontal attack left to right + BOTH_A2__R__L, //# Fast weak horizontal attack right to left + BOTH_A2_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A2_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A2_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A2_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T2_BR__R, //# Fast arc bottom right to right + BOTH_T2_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T2_BR__L, //# Fast weak spin bottom right to left + BOTH_T2_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T2__R_TR, //# Fast arc right to top right + BOTH_T2__R_TL, //# Fast arc right to top left + BOTH_T2__R__L, //# Fast weak spin right to left + BOTH_T2__R_BL, //# Fast weak spin right to bottom left + BOTH_T2_TR_BR, //# Fast arc top right to bottom right + BOTH_T2_TR_TL, //# Fast arc top right to top left + BOTH_T2_TR__L, //# Fast arc top right to left + BOTH_T2_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T2_T__BR, //# Fast arc top to bottom right + BOTH_T2_T___R, //# Fast arc top to right + BOTH_T2_T__TR, //# Fast arc top to top right + BOTH_T2_T__TL, //# Fast arc top to top left + BOTH_T2_T___L, //# Fast arc top to left + BOTH_T2_T__BL, //# Fast arc top to bottom left + BOTH_T2_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T2_TL_BL, //# Fast arc top left to bottom left + BOTH_T2__L_BR, //# Fast weak spin left to bottom right + BOTH_T2__L__R, //# Fast weak spin left to right + BOTH_T2__L_TL, //# Fast arc left to top left + BOTH_T2_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T2_BL__R, //# Fast weak spin bottom left to right + BOTH_T2_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T2_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T2_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T2_TR_BR) + BOTH_T2_BR_T_, //# Fast arc bottom right to top (use: BOTH_T2_T__BR) + BOTH_T2__R_BR, //# Fast arc right to bottom right (use: BOTH_T2_BR__R) + BOTH_T2__R_T_, //# Fast ar right to top (use: BOTH_T2_T___R) + BOTH_T2_TR__R, //# Fast arc top right to right (use: BOTH_T2__R_TR) + BOTH_T2_TR_T_, //# Fast arc top right to top (use: BOTH_T2_T__TR) + BOTH_T2_TL__R, //# Fast arc top left to right (use: BOTH_T2__R_TL) + BOTH_T2_TL_TR, //# Fast arc top left to top right (use: BOTH_T2_TR_TL) + BOTH_T2_TL_T_, //# Fast arc top left to top (use: BOTH_T2_T__TL) + BOTH_T2_TL__L, //# Fast arc top left to left (use: BOTH_T2__L_TL) + BOTH_T2__L_TR, //# Fast arc left to top right (use: BOTH_T2_TR__L) + BOTH_T2__L_T_, //# Fast arc left to top (use: BOTH_T2_T___L) + BOTH_T2__L_BL, //# Fast arc left to bottom left (use: BOTH_T2_BL__L) + BOTH_T2_BL_T_, //# Fast arc bottom left to top (use: BOTH_T2_T__BL) + BOTH_T2_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T2_TL_BL) + //Saber Attack Start Transitions + BOTH_S2_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S2_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S2_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S2_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S2_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S2_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S2_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R2_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R2__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R2__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R2_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R2_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R2_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R2_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B2_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B2__R___, //# Bounce-back if attack from R is blocked + BOTH_B2_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B2_T____, //# Bounce-back if attack from T is blocked + BOTH_B2_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B2__L___, //# Bounce-back if attack from L is blocked + BOTH_B2_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D2_BR___, //# Deflection toward BR + BOTH_D2__R___, //# Deflection toward R + BOTH_D2_TR___, //# Deflection toward TR + BOTH_D2_TL___, //# Deflection toward TL + BOTH_D2__L___, //# Deflection toward L + BOTH_D2_BL___, //# Deflection toward BL + BOTH_D2_B____, //# Deflection toward B + //Saber attack anims - power level 3 + BOTH_A3_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A3__L__R, //# Fast weak horizontal attack left to right + BOTH_A3__R__L, //# Fast weak horizontal attack right to left + BOTH_A3_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A3_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A3_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A3_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T3_BR__R, //# Fast arc bottom right to right + BOTH_T3_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T3_BR__L, //# Fast weak spin bottom right to left + BOTH_T3_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T3__R_TR, //# Fast arc right to top right + BOTH_T3__R_TL, //# Fast arc right to top left + BOTH_T3__R__L, //# Fast weak spin right to left + BOTH_T3__R_BL, //# Fast weak spin right to bottom left + BOTH_T3_TR_BR, //# Fast arc top right to bottom right + BOTH_T3_TR_TL, //# Fast arc top right to top left + BOTH_T3_TR__L, //# Fast arc top right to left + BOTH_T3_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T3_T__BR, //# Fast arc top to bottom right + BOTH_T3_T___R, //# Fast arc top to right + BOTH_T3_T__TR, //# Fast arc top to top right + BOTH_T3_T__TL, //# Fast arc top to top left + BOTH_T3_T___L, //# Fast arc top to left + BOTH_T3_T__BL, //# Fast arc top to bottom left + BOTH_T3_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T3_TL_BL, //# Fast arc top left to bottom left + BOTH_T3__L_BR, //# Fast weak spin left to bottom right + BOTH_T3__L__R, //# Fast weak spin left to right + BOTH_T3__L_TL, //# Fast arc left to top left + BOTH_T3_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T3_BL__R, //# Fast weak spin bottom left to right + BOTH_T3_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T3_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T3_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T3_TR_BR) + BOTH_T3_BR_T_, //# Fast arc bottom right to top (use: BOTH_T3_T__BR) + BOTH_T3__R_BR, //# Fast arc right to bottom right (use: BOTH_T3_BR__R) + BOTH_T3__R_T_, //# Fast ar right to top (use: BOTH_T3_T___R) + BOTH_T3_TR__R, //# Fast arc top right to right (use: BOTH_T3__R_TR) + BOTH_T3_TR_T_, //# Fast arc top right to top (use: BOTH_T3_T__TR) + BOTH_T3_TL__R, //# Fast arc top left to right (use: BOTH_T3__R_TL) + BOTH_T3_TL_TR, //# Fast arc top left to top right (use: BOTH_T3_TR_TL) + BOTH_T3_TL_T_, //# Fast arc top left to top (use: BOTH_T3_T__TL) + BOTH_T3_TL__L, //# Fast arc top left to left (use: BOTH_T3__L_TL) + BOTH_T3__L_TR, //# Fast arc left to top right (use: BOTH_T3_TR__L) + BOTH_T3__L_T_, //# Fast arc left to top (use: BOTH_T3_T___L) + BOTH_T3__L_BL, //# Fast arc left to bottom left (use: BOTH_T3_BL__L) + BOTH_T3_BL_T_, //# Fast arc bottom left to top (use: BOTH_T3_T__BL) + BOTH_T3_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T3_TL_BL) + //Saber Attack Start Transitions + BOTH_S3_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S3_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S3_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S3_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S3_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S3_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S3_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R3_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R3__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R3__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R3_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R3_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R3_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R3_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B3_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B3__R___, //# Bounce-back if attack from R is blocked + BOTH_B3_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B3_T____, //# Bounce-back if attack from T is blocked + BOTH_B3_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B3__L___, //# Bounce-back if attack from L is blocked + BOTH_B3_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D3_BR___, //# Deflection toward BR + BOTH_D3__R___, //# Deflection toward R + BOTH_D3_TR___, //# Deflection toward TR + BOTH_D3_TL___, //# Deflection toward TL + BOTH_D3__L___, //# Deflection toward L + BOTH_D3_BL___, //# Deflection toward BL + BOTH_D3_B____, //# Deflection toward B + //Saber attack anims - power level 4 - Desann's + BOTH_A4_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A4__L__R, //# Fast weak horizontal attack left to right + BOTH_A4__R__L, //# Fast weak horizontal attack right to left + BOTH_A4_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A4_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A4_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A4_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T4_BR__R, //# Fast arc bottom right to right + BOTH_T4_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T4_BR__L, //# Fast weak spin bottom right to left + BOTH_T4_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T4__R_TR, //# Fast arc right to top right + BOTH_T4__R_TL, //# Fast arc right to top left + BOTH_T4__R__L, //# Fast weak spin right to left + BOTH_T4__R_BL, //# Fast weak spin right to bottom left + BOTH_T4_TR_BR, //# Fast arc top right to bottom right + BOTH_T4_TR_TL, //# Fast arc top right to top left + BOTH_T4_TR__L, //# Fast arc top right to left + BOTH_T4_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T4_T__BR, //# Fast arc top to bottom right + BOTH_T4_T___R, //# Fast arc top to right + BOTH_T4_T__TR, //# Fast arc top to top right + BOTH_T4_T__TL, //# Fast arc top to top left + BOTH_T4_T___L, //# Fast arc top to left + BOTH_T4_T__BL, //# Fast arc top to bottom left + BOTH_T4_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T4_TL_BL, //# Fast arc top left to bottom left + BOTH_T4__L_BR, //# Fast weak spin left to bottom right + BOTH_T4__L__R, //# Fast weak spin left to right + BOTH_T4__L_TL, //# Fast arc left to top left + BOTH_T4_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T4_BL__R, //# Fast weak spin bottom left to right + BOTH_T4_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T4_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T4_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T4_TR_BR) + BOTH_T4_BR_T_, //# Fast arc bottom right to top (use: BOTH_T4_T__BR) + BOTH_T4__R_BR, //# Fast arc right to bottom right (use: BOTH_T4_BR__R) + BOTH_T4__R_T_, //# Fast ar right to top (use: BOTH_T4_T___R) + BOTH_T4_TR__R, //# Fast arc top right to right (use: BOTH_T4__R_TR) + BOTH_T4_TR_T_, //# Fast arc top right to top (use: BOTH_T4_T__TR) + BOTH_T4_TL__R, //# Fast arc top left to right (use: BOTH_T4__R_TL) + BOTH_T4_TL_TR, //# Fast arc top left to top right (use: BOTH_T4_TR_TL) + BOTH_T4_TL_T_, //# Fast arc top left to top (use: BOTH_T4_T__TL) + BOTH_T4_TL__L, //# Fast arc top left to left (use: BOTH_T4__L_TL) + BOTH_T4__L_TR, //# Fast arc left to top right (use: BOTH_T4_TR__L) + BOTH_T4__L_T_, //# Fast arc left to top (use: BOTH_T4_T___L) + BOTH_T4__L_BL, //# Fast arc left to bottom left (use: BOTH_T4_BL__L) + BOTH_T4_BL_T_, //# Fast arc bottom left to top (use: BOTH_T4_T__BL) + BOTH_T4_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T4_TL_BL) + //Saber Attack Start Transitions + BOTH_S4_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S4_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S4_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S4_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S4_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S4_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S4_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R4_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R4__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R4__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R4_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R4_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R4_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R4_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B4_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B4__R___, //# Bounce-back if attack from R is blocked + BOTH_B4_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B4_T____, //# Bounce-back if attack from T is blocked + BOTH_B4_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B4__L___, //# Bounce-back if attack from L is blocked + BOTH_B4_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D4_BR___, //# Deflection toward BR + BOTH_D4__R___, //# Deflection toward R + BOTH_D4_TR___, //# Deflection toward TR + BOTH_D4_TL___, //# Deflection toward TL + BOTH_D4__L___, //# Deflection toward L + BOTH_D4_BL___, //# Deflection toward BL + BOTH_D4_B____, //# Deflection toward B + //Saber attack anims - power level 5 - Tavion's + BOTH_A5_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A5__L__R, //# Fast weak horizontal attack left to right + BOTH_A5__R__L, //# Fast weak horizontal attack right to left + BOTH_A5_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A5_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A5_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A5_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T5_BR__R, //# Fast arc bottom right to right + BOTH_T5_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T5_BR__L, //# Fast weak spin bottom right to left + BOTH_T5_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T5__R_TR, //# Fast arc right to top right + BOTH_T5__R_TL, //# Fast arc right to top left + BOTH_T5__R__L, //# Fast weak spin right to left + BOTH_T5__R_BL, //# Fast weak spin right to bottom left + BOTH_T5_TR_BR, //# Fast arc top right to bottom right + BOTH_T5_TR_TL, //# Fast arc top right to top left + BOTH_T5_TR__L, //# Fast arc top right to left + BOTH_T5_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T5_T__BR, //# Fast arc top to bottom right + BOTH_T5_T___R, //# Fast arc top to right + BOTH_T5_T__TR, //# Fast arc top to top right + BOTH_T5_T__TL, //# Fast arc top to top left + BOTH_T5_T___L, //# Fast arc top to left + BOTH_T5_T__BL, //# Fast arc top to bottom left + BOTH_T5_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T5_TL_BL, //# Fast arc top left to bottom left + BOTH_T5__L_BR, //# Fast weak spin left to bottom right + BOTH_T5__L__R, //# Fast weak spin left to right + BOTH_T5__L_TL, //# Fast arc left to top left + BOTH_T5_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T5_BL__R, //# Fast weak spin bottom left to right + BOTH_T5_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T5_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T5_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T5_TR_BR) + BOTH_T5_BR_T_, //# Fast arc bottom right to top (use: BOTH_T5_T__BR) + BOTH_T5__R_BR, //# Fast arc right to bottom right (use: BOTH_T5_BR__R) + BOTH_T5__R_T_, //# Fast ar right to top (use: BOTH_T5_T___R) + BOTH_T5_TR__R, //# Fast arc top right to right (use: BOTH_T5__R_TR) + BOTH_T5_TR_T_, //# Fast arc top right to top (use: BOTH_T5_T__TR) + BOTH_T5_TL__R, //# Fast arc top left to right (use: BOTH_T5__R_TL) + BOTH_T5_TL_TR, //# Fast arc top left to top right (use: BOTH_T5_TR_TL) + BOTH_T5_TL_T_, //# Fast arc top left to top (use: BOTH_T5_T__TL) + BOTH_T5_TL__L, //# Fast arc top left to left (use: BOTH_T5__L_TL) + BOTH_T5__L_TR, //# Fast arc left to top right (use: BOTH_T5_TR__L) + BOTH_T5__L_T_, //# Fast arc left to top (use: BOTH_T5_T___L) + BOTH_T5__L_BL, //# Fast arc left to bottom left (use: BOTH_T5_BL__L) + BOTH_T5_BL_T_, //# Fast arc bottom left to top (use: BOTH_T5_T__BL) + BOTH_T5_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T5_TL_BL) + //Saber Attack Start Transitions + BOTH_S5_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S5_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S5_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S5_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S5_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S5_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S5_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R5_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R5__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R5__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R5_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R5_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R5_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R5_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B5_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B5__R___, //# Bounce-back if attack from R is blocked + BOTH_B5_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B5_T____, //# Bounce-back if attack from T is blocked + BOTH_B5_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B5__L___, //# Bounce-back if attack from L is blocked + BOTH_B5_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D5_BR___, //# Deflection toward BR + BOTH_D5__R___, //# Deflection toward R + BOTH_D5_TR___, //# Deflection toward TR + BOTH_D5_TL___, //# Deflection toward TL + BOTH_D5__L___, //# Deflection toward L + BOTH_D5_BL___, //# Deflection toward BL + BOTH_D5_B____, //# Deflection toward B + //Saber attack anims - power level 6 + BOTH_A6_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A6__L__R, //# Fast weak horizontal attack left to right + BOTH_A6__R__L, //# Fast weak horizontal attack right to left + BOTH_A6_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A6_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A6_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A6_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T6_BR__R, //# Fast arc bottom right to right + BOTH_T6_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T6_BR__L, //# Fast weak spin bottom right to left + BOTH_T6_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T6__R_TR, //# Fast arc right to top right + BOTH_T6__R_TL, //# Fast arc right to top left + BOTH_T6__R__L, //# Fast weak spin right to left + BOTH_T6__R_BL, //# Fast weak spin right to bottom left + BOTH_T6_TR_BR, //# Fast arc top right to bottom right + BOTH_T6_TR_TL, //# Fast arc top right to top left + BOTH_T6_TR__L, //# Fast arc top right to left + BOTH_T6_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T6_T__BR, //# Fast arc top to bottom right + BOTH_T6_T___R, //# Fast arc top to right + BOTH_T6_T__TR, //# Fast arc top to top right + BOTH_T6_T__TL, //# Fast arc top to top left + BOTH_T6_T___L, //# Fast arc top to left + BOTH_T6_T__BL, //# Fast arc top to bottom left + BOTH_T6_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T6_TL_BL, //# Fast arc top left to bottom left + BOTH_T6__L_BR, //# Fast weak spin left to bottom right + BOTH_T6__L__R, //# Fast weak spin left to right + BOTH_T6__L_TL, //# Fast arc left to top left + BOTH_T6_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T6_BL__R, //# Fast weak spin bottom left to right + BOTH_T6_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T6_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T6_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T6_TR_BR) + BOTH_T6_BR_T_, //# Fast arc bottom right to top (use: BOTH_T6_T__BR) + BOTH_T6__R_BR, //# Fast arc right to bottom right (use: BOTH_T6_BR__R) + BOTH_T6__R_T_, //# Fast ar right to top (use: BOTH_T6_T___R) + BOTH_T6_TR__R, //# Fast arc top right to right (use: BOTH_T6__R_TR) + BOTH_T6_TR_T_, //# Fast arc top right to top (use: BOTH_T6_T__TR) + BOTH_T6_TL__R, //# Fast arc top left to right (use: BOTH_T6__R_TL) + BOTH_T6_TL_TR, //# Fast arc top left to top right (use: BOTH_T6_TR_TL) + BOTH_T6_TL_T_, //# Fast arc top left to top (use: BOTH_T6_T__TL) + BOTH_T6_TL__L, //# Fast arc top left to left (use: BOTH_T6__L_TL) + BOTH_T6__L_TR, //# Fast arc left to top right (use: BOTH_T6_TR__L) + BOTH_T6__L_T_, //# Fast arc left to top (use: BOTH_T6_T___L) + BOTH_T6__L_BL, //# Fast arc left to bottom left (use: BOTH_T6_BL__L) + BOTH_T6_BL_T_, //# Fast arc bottom left to top (use: BOTH_T6_T__BL) + BOTH_T6_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T6_TL_BL) + //Saber Attack Start Transitions + BOTH_S6_S6_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S6_S6__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S6_S6__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S6_S6_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S6_S6_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S6_S6_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S6_S6_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R6_B__S6, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R6__L_S6, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R6__R_S6, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R6_TL_S6, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R6_BR_S6, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R6_BL_S6, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R6_TR_S6, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B6_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B6__R___, //# Bounce-back if attack from R is blocked + BOTH_B6_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B6_T____, //# Bounce-back if attack from T is blocked + BOTH_B6_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B6__L___, //# Bounce-back if attack from L is blocked + BOTH_B6_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D6_BR___, //# Deflection toward BR + BOTH_D6__R___, //# Deflection toward R + BOTH_D6_TR___, //# Deflection toward TR + BOTH_D6_TL___, //# Deflection toward TL + BOTH_D6__L___, //# Deflection toward L + BOTH_D6_BL___, //# Deflection toward BL + BOTH_D6_B____, //# Deflection toward B + //Saber attack anims - power level 7 + BOTH_A7_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A7__L__R, //# Fast weak horizontal attack left to right + BOTH_A7__R__L, //# Fast weak horizontal attack right to left + BOTH_A7_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A7_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A7_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A7_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T7_BR__R, //# Fast arc bottom right to right + BOTH_T7_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T7_BR__L, //# Fast weak spin bottom right to left + BOTH_T7_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T7__R_TR, //# Fast arc right to top right + BOTH_T7__R_TL, //# Fast arc right to top left + BOTH_T7__R__L, //# Fast weak spin right to left + BOTH_T7__R_BL, //# Fast weak spin right to bottom left + BOTH_T7_TR_BR, //# Fast arc top right to bottom right + BOTH_T7_TR_TL, //# Fast arc top right to top left + BOTH_T7_TR__L, //# Fast arc top right to left + BOTH_T7_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T7_T__BR, //# Fast arc top to bottom right + BOTH_T7_T___R, //# Fast arc top to right + BOTH_T7_T__TR, //# Fast arc top to top right + BOTH_T7_T__TL, //# Fast arc top to top left + BOTH_T7_T___L, //# Fast arc top to left + BOTH_T7_T__BL, //# Fast arc top to bottom left + BOTH_T7_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T7_TL_BL, //# Fast arc top left to bottom left + BOTH_T7__L_BR, //# Fast weak spin left to bottom right + BOTH_T7__L__R, //# Fast weak spin left to right + BOTH_T7__L_TL, //# Fast arc left to top left + BOTH_T7_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T7_BL__R, //# Fast weak spin bottom left to right + BOTH_T7_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T7_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T7_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T7_TR_BR) + BOTH_T7_BR_T_, //# Fast arc bottom right to top (use: BOTH_T7_T__BR) + BOTH_T7__R_BR, //# Fast arc right to bottom right (use: BOTH_T7_BR__R) + BOTH_T7__R_T_, //# Fast ar right to top (use: BOTH_T7_T___R) + BOTH_T7_TR__R, //# Fast arc top right to right (use: BOTH_T7__R_TR) + BOTH_T7_TR_T_, //# Fast arc top right to top (use: BOTH_T7_T__TR) + BOTH_T7_TL__R, //# Fast arc top left to right (use: BOTH_T7__R_TL) + BOTH_T7_TL_TR, //# Fast arc top left to top right (use: BOTH_T7_TR_TL) + BOTH_T7_TL_T_, //# Fast arc top left to top (use: BOTH_T7_T__TL) + BOTH_T7_TL__L, //# Fast arc top left to left (use: BOTH_T7__L_TL) + BOTH_T7__L_TR, //# Fast arc left to top right (use: BOTH_T7_TR__L) + BOTH_T7__L_T_, //# Fast arc left to top (use: BOTH_T7_T___L) + BOTH_T7__L_BL, //# Fast arc left to bottom left (use: BOTH_T7_BL__L) + BOTH_T7_BL_T_, //# Fast arc bottom left to top (use: BOTH_T7_T__BL) + BOTH_T7_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T7_TL_BL) + //Saber Attack Start Transitions + BOTH_S7_S7_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S7_S7__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S7_S7__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S7_S7_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S7_S7_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S7_S7_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S7_S7_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R7_B__S7, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R7__L_S7, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R7__R_S7, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R7_TL_S7, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R7_BR_S7, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R7_BL_S7, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R7_TR_S7, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B7_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B7__R___, //# Bounce-back if attack from R is blocked + BOTH_B7_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B7_T____, //# Bounce-back if attack from T is blocked + BOTH_B7_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B7__L___, //# Bounce-back if attack from L is blocked + BOTH_B7_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D7_BR___, //# Deflection toward BR + BOTH_D7__R___, //# Deflection toward R + BOTH_D7_TR___, //# Deflection toward TR + BOTH_D7_TL___, //# Deflection toward TL + BOTH_D7__L___, //# Deflection toward L + BOTH_D7_BL___, //# Deflection toward BL + BOTH_D7_B____, //# Deflection toward B + //Saber parry anims + BOTH_P1_S1_T_, //# Block shot/saber top + BOTH_P1_S1_TR, //# Block shot/saber top right + BOTH_P1_S1_TL, //# Block shot/saber top left + BOTH_P1_S1_BL, //# Block shot/saber bottom left + BOTH_P1_S1_BR, //# Block shot/saber bottom right + //Saber knockaway + BOTH_K1_S1_T_, //# knockaway saber top + BOTH_K1_S1_TR, //# knockaway saber top right + BOTH_K1_S1_TL, //# knockaway saber top left + BOTH_K1_S1_BL, //# knockaway saber bottom left + BOTH_K1_S1_B_, //# knockaway saber bottom + BOTH_K1_S1_BR, //# knockaway saber bottom right + //Saber attack knocked away + BOTH_V1_BR_S1, //# BR attack knocked away + BOTH_V1__R_S1, //# R attack knocked away + BOTH_V1_TR_S1, //# TR attack knocked away + BOTH_V1_T__S1, //# T attack knocked away + BOTH_V1_TL_S1, //# TL attack knocked away + BOTH_V1__L_S1, //# L attack knocked away + BOTH_V1_BL_S1, //# BL attack knocked away + BOTH_V1_B__S1, //# B attack knocked away + //Saber parry broken + BOTH_H1_S1_T_, //# saber knocked down from top parry + BOTH_H1_S1_TR, //# saber knocked down-left from TR parry + BOTH_H1_S1_TL, //# saber knocked down-right from TL parry + BOTH_H1_S1_BL, //# saber knocked up-right from BL parry + BOTH_H1_S1_B_, //# saber knocked up over head from ready? + BOTH_H1_S1_BR, //# saber knocked up-left from BR parry + //Dual Saber parry anims + BOTH_P6_S6_T_, //# Block shot/saber top + BOTH_P6_S6_TR, //# Block shot/saber top right + BOTH_P6_S6_TL, //# Block shot/saber top left + BOTH_P6_S6_BL, //# Block shot/saber bottom left + BOTH_P6_S6_BR, //# Block shot/saber bottom right + //Dual Saber knockaway + BOTH_K6_S6_T_, //# knockaway saber top + BOTH_K6_S6_TR, //# knockaway saber top right + BOTH_K6_S6_TL, //# knockaway saber top left + BOTH_K6_S6_BL, //# knockaway saber bottom left + BOTH_K6_S6_B_, //# knockaway saber bottom + BOTH_K6_S6_BR, //# knockaway saber bottom right + //Dual Saber attack knocked away + BOTH_V6_BR_S6, //# BR attack knocked away + BOTH_V6__R_S6, //# R attack knocked away + BOTH_V6_TR_S6, //# TR attack knocked away + BOTH_V6_T__S6, //# T attack knocked away + BOTH_V6_TL_S6, //# TL attack knocked away + BOTH_V6__L_S6, //# L attack knocked away + BOTH_V6_BL_S6, //# BL attack knocked away + BOTH_V6_B__S6, //# B attack knocked away + //Dual Saber parry broken + BOTH_H6_S6_T_, //# saber knocked down from top parry + BOTH_H6_S6_TR, //# saber knocked down-left from TR parry + BOTH_H6_S6_TL, //# saber knocked down-right from TL parry + BOTH_H6_S6_BL, //# saber knocked up-right from BL parry + BOTH_H6_S6_B_, //# saber knocked up over head from ready? + BOTH_H6_S6_BR, //# saber knocked up-left from BR parry + //SaberStaff parry anims + BOTH_P7_S7_T_, //# Block shot/saber top + BOTH_P7_S7_TR, //# Block shot/saber top right + BOTH_P7_S7_TL, //# Block shot/saber top left + BOTH_P7_S7_BL, //# Block shot/saber bottom left + BOTH_P7_S7_BR, //# Block shot/saber bottom right + //SaberStaff knockaway + BOTH_K7_S7_T_, //# knockaway saber top + BOTH_K7_S7_TR, //# knockaway saber top right + BOTH_K7_S7_TL, //# knockaway saber top left + BOTH_K7_S7_BL, //# knockaway saber bottom left + BOTH_K7_S7_B_, //# knockaway saber bottom + BOTH_K7_S7_BR, //# knockaway saber bottom right + //SaberStaff attack knocked away + BOTH_V7_BR_S7, //# BR attack knocked away + BOTH_V7__R_S7, //# R attack knocked away + BOTH_V7_TR_S7, //# TR attack knocked away + BOTH_V7_T__S7, //# T attack knocked away + BOTH_V7_TL_S7, //# TL attack knocked away + BOTH_V7__L_S7, //# L attack knocked away + BOTH_V7_BL_S7, //# BL attack knocked away + BOTH_V7_B__S7, //# B attack knocked away + //SaberStaff parry broken + BOTH_H7_S7_T_, //# saber knocked down from top parry + BOTH_H7_S7_TR, //# saber knocked down-left from TR parry + BOTH_H7_S7_TL, //# saber knocked down-right from TL parry + BOTH_H7_S7_BL, //# saber knocked up-right from BL parry + BOTH_H7_S7_B_, //# saber knocked up over head from ready? + BOTH_H7_S7_BR, //# saber knocked up-left from BR parry + //Sabers locked anims + //* #sep BOTH_ SABER LOCKED ANIMS + //BOTH_(DL, S, ST)_(DL, S, ST)_(T, S)_(L, B, SB)_1(_W, _L) +//===Single locks================================================================== +//SINGLE vs. DUAL + //side locks - I'm using a single and they're using dual + BOTH_LK_S_DL_S_B_1_L, //normal break I lost + BOTH_LK_S_DL_S_B_1_W, //normal break I won + BOTH_LK_S_DL_S_L_1, //lock if I'm using single vs. a dual + BOTH_LK_S_DL_S_SB_1_L, //super break I lost + BOTH_LK_S_DL_S_SB_1_W, //super break I won + //top locks + BOTH_LK_S_DL_T_B_1_L, //normal break I lost + BOTH_LK_S_DL_T_B_1_W, //normal break I won + BOTH_LK_S_DL_T_L_1, //lock if I'm using single vs. a dual + BOTH_LK_S_DL_T_SB_1_L, //super break I lost + BOTH_LK_S_DL_T_SB_1_W, //super break I won +//SINGLE vs. STAFF + //side locks + BOTH_LK_S_ST_S_B_1_L, //normal break I lost + BOTH_LK_S_ST_S_B_1_W, //normal break I won + BOTH_LK_S_ST_S_L_1, //lock if I'm using single vs. a staff + BOTH_LK_S_ST_S_SB_1_L, //super break I lost + BOTH_LK_S_ST_S_SB_1_W, //super break I won + //top locks + BOTH_LK_S_ST_T_B_1_L, //normal break I lost + BOTH_LK_S_ST_T_B_1_W, //normal break I won + BOTH_LK_S_ST_T_L_1, //lock if I'm using single vs. a staff + BOTH_LK_S_ST_T_SB_1_L, //super break I lost + BOTH_LK_S_ST_T_SB_1_W, //super break I won +//SINGLE vs. SINGLE + //side locks + BOTH_LK_S_S_S_B_1_L, //normal break I lost + BOTH_LK_S_S_S_B_1_W, //normal break I won + BOTH_LK_S_S_S_L_1, //lock if I'm using single vs. a single and I initiated + BOTH_LK_S_S_S_SB_1_L, //super break I lost + BOTH_LK_S_S_S_SB_1_W, //super break I won + //top locks + BOTH_LK_S_S_T_B_1_L, //normal break I lost + BOTH_LK_S_S_T_B_1_W, //normal break I won + BOTH_LK_S_S_T_L_1, //lock if I'm using single vs. a single and I initiated + BOTH_LK_S_S_T_SB_1_L, //super break I lost + BOTH_LK_S_S_T_SB_1_W, //super break I won +//===Dual Saber locks================================================================== +//DUAL vs. DUAL + //side locks + BOTH_LK_DL_DL_S_B_1_L, //normal break I lost + BOTH_LK_DL_DL_S_B_1_W, //normal break I won + BOTH_LK_DL_DL_S_L_1, //lock if I'm using dual vs. dual and I initiated + BOTH_LK_DL_DL_S_SB_1_L, //super break I lost + BOTH_LK_DL_DL_S_SB_1_W, //super break I won + //top locks + BOTH_LK_DL_DL_T_B_1_L, //normal break I lost + BOTH_LK_DL_DL_T_B_1_W, //normal break I won + BOTH_LK_DL_DL_T_L_1, //lock if I'm using dual vs. dual and I initiated + BOTH_LK_DL_DL_T_SB_1_L, //super break I lost + BOTH_LK_DL_DL_T_SB_1_W, //super break I won +//DUAL vs. STAFF + //side locks + BOTH_LK_DL_ST_S_B_1_L, //normal break I lost + BOTH_LK_DL_ST_S_B_1_W, //normal break I won + BOTH_LK_DL_ST_S_L_1, //lock if I'm using dual vs. a staff + BOTH_LK_DL_ST_S_SB_1_L, //super break I lost + BOTH_LK_DL_ST_S_SB_1_W, //super break I won + //top locks + BOTH_LK_DL_ST_T_B_1_L, //normal break I lost + BOTH_LK_DL_ST_T_B_1_W, //normal break I won + BOTH_LK_DL_ST_T_L_1, //lock if I'm using dual vs. a staff + BOTH_LK_DL_ST_T_SB_1_L, //super break I lost + BOTH_LK_DL_ST_T_SB_1_W, //super break I won +//DUAL vs. SINGLE + //side locks + BOTH_LK_DL_S_S_B_1_L, //normal break I lost + BOTH_LK_DL_S_S_B_1_W, //normal break I won + BOTH_LK_DL_S_S_L_1, //lock if I'm using dual vs. a single + BOTH_LK_DL_S_S_SB_1_L, //super break I lost + BOTH_LK_DL_S_S_SB_1_W, //super break I won + //top locks + BOTH_LK_DL_S_T_B_1_L, //normal break I lost + BOTH_LK_DL_S_T_B_1_W, //normal break I won + BOTH_LK_DL_S_T_L_1, //lock if I'm using dual vs. a single + BOTH_LK_DL_S_T_SB_1_L, //super break I lost + BOTH_LK_DL_S_T_SB_1_W, //super break I won +//===Saber Staff locks================================================================== +//STAFF vs. DUAL + //side locks + BOTH_LK_ST_DL_S_B_1_L, //normal break I lost + BOTH_LK_ST_DL_S_B_1_W, //normal break I won + BOTH_LK_ST_DL_S_L_1, //lock if I'm using staff vs. dual + BOTH_LK_ST_DL_S_SB_1_L, //super break I lost + BOTH_LK_ST_DL_S_SB_1_W, //super break I won + //top locks + BOTH_LK_ST_DL_T_B_1_L, //normal break I lost + BOTH_LK_ST_DL_T_B_1_W, //normal break I won + BOTH_LK_ST_DL_T_L_1, //lock if I'm using staff vs. dual + BOTH_LK_ST_DL_T_SB_1_L, //super break I lost + BOTH_LK_ST_DL_T_SB_1_W, //super break I won +//STAFF vs. STAFF + //side locks + BOTH_LK_ST_ST_S_B_1_L, //normal break I lost + BOTH_LK_ST_ST_S_B_1_W, //normal break I won + BOTH_LK_ST_ST_S_L_1, //lock if I'm using staff vs. a staff and I initiated + BOTH_LK_ST_ST_S_SB_1_L, //super break I lost + BOTH_LK_ST_ST_S_SB_1_W, //super break I won + //top locks + BOTH_LK_ST_ST_T_B_1_L, //normal break I lost + BOTH_LK_ST_ST_T_B_1_W, //normal break I won + BOTH_LK_ST_ST_T_L_1, //lock if I'm using staff vs. a staff and I initiated + BOTH_LK_ST_ST_T_SB_1_L, //super break I lost + BOTH_LK_ST_ST_T_SB_1_W, //super break I won +//STAFF vs. SINGLE + //side locks + BOTH_LK_ST_S_S_B_1_L, //normal break I lost + BOTH_LK_ST_S_S_B_1_W, //normal break I won + BOTH_LK_ST_S_S_L_1, //lock if I'm using staff vs. a single + BOTH_LK_ST_S_S_SB_1_L, //super break I lost + BOTH_LK_ST_S_S_SB_1_W, //super break I won + //top locks + BOTH_LK_ST_S_T_B_1_L, //normal break I lost + BOTH_LK_ST_S_T_B_1_W, //normal break I won + BOTH_LK_ST_S_T_L_1, //lock if I'm using staff vs. a single + BOTH_LK_ST_S_T_SB_1_L, //super break I lost + BOTH_LK_ST_S_T_SB_1_W, //super break I won +//Special cases for same saber style vs. each other (won't fit in nice 5-anim size lists above) + BOTH_LK_S_S_S_L_2, //lock if I'm using single vs. a single and other intitiated + BOTH_LK_S_S_T_L_2, //lock if I'm using single vs. a single and other initiated + BOTH_LK_DL_DL_S_L_2, //lock if I'm using dual vs. dual and other initiated + BOTH_LK_DL_DL_T_L_2, //lock if I'm using dual vs. dual and other initiated + BOTH_LK_ST_ST_S_L_2, //lock if I'm using staff vs. a staff and other initiated + BOTH_LK_ST_ST_T_L_2, //lock if I'm using staff vs. a staff and other initiated +//===End Saber locks================================================================== + //old locks + BOTH_BF2RETURN, //# + BOTH_BF2BREAK, //# + BOTH_BF2LOCK, //# + BOTH_BF1RETURN, //# + BOTH_BF1BREAK, //# + BOTH_BF1LOCK, //# + BOTH_CWCIRCLE_R2__R_S1, //# + BOTH_CCWCIRCLE_R2__L_S1, //# + BOTH_CWCIRCLE_A2__L__R, //# + BOTH_CCWCIRCLE_A2__R__L, //# + BOTH_CWCIRCLEBREAK, //# + BOTH_CCWCIRCLEBREAK, //# + BOTH_CWCIRCLELOCK, //# + BOTH_CCWCIRCLELOCK, //# + //other saber anims + //* #sep BOTH_ SABER MISC ANIMS + BOTH_SABERFAST_STANCE, + BOTH_SABERSLOW_STANCE, + BOTH_SABERDUAL_STANCE, + BOTH_SABERSTAFF_STANCE, + BOTH_A2_STABBACK1, //# Stab saber backward + BOTH_ATTACK_BACK, //# Swing around backwards and attack + BOTH_JUMPFLIPSLASHDOWN1,//# + BOTH_JUMPFLIPSTABDOWN,//# + BOTH_FORCELEAP2_T__B_,//# + BOTH_LUNGE2_B__T_,//# + BOTH_CROUCHATTACKBACK1,//# + //New specials for JKA: + BOTH_JUMPATTACK6,//# + BOTH_JUMPATTACK7,//# + BOTH_SPINATTACK6,//# + BOTH_SPINATTACK7,//# + BOTH_S1_S6,//# From stand1 to saberdual stance - turning on your dual sabers + BOTH_S6_S1,//# From dualstaff stance to stand1 - turning off your dual sabers + BOTH_S1_S7,//# From stand1 to saberstaff stance - turning on your saberstaff + BOTH_S7_S1,//# From saberstaff stance to stand1 - turning off your saberstaff + BOTH_FORCELONGLEAP_START, + BOTH_FORCELONGLEAP_ATTACK, + BOTH_FORCELONGLEAP_LAND, + BOTH_FORCEWALLRUNFLIP_START, + BOTH_FORCEWALLRUNFLIP_END, + BOTH_FORCEWALLRUNFLIP_ALT, + BOTH_FORCEWALLREBOUND_FORWARD, + BOTH_FORCEWALLREBOUND_LEFT, + BOTH_FORCEWALLREBOUND_BACK, + BOTH_FORCEWALLREBOUND_RIGHT, + BOTH_FORCEWALLHOLD_FORWARD, + BOTH_FORCEWALLHOLD_LEFT, + BOTH_FORCEWALLHOLD_BACK, + BOTH_FORCEWALLHOLD_RIGHT, + BOTH_FORCEWALLRELEASE_FORWARD, + BOTH_FORCEWALLRELEASE_LEFT, + BOTH_FORCEWALLRELEASE_BACK, + BOTH_FORCEWALLRELEASE_RIGHT, + BOTH_A7_KICK_F, + BOTH_A7_KICK_B, + BOTH_A7_KICK_R, + BOTH_A7_KICK_L, + BOTH_A7_KICK_S, + BOTH_A7_KICK_BF, + BOTH_A7_KICK_BF_STOP, + BOTH_A7_KICK_RL, + BOTH_A7_KICK_F_AIR, + BOTH_A7_KICK_B_AIR, + BOTH_A7_KICK_R_AIR, + BOTH_A7_KICK_L_AIR, + BOTH_FLIP_ATTACK7, + BOTH_FLIP_HOLD7, + BOTH_FLIP_LAND, + BOTH_PULL_IMPALE_STAB, + BOTH_PULL_IMPALE_SWING, + BOTH_PULLED_INAIR_B, + BOTH_PULLED_INAIR_F, + BOTH_STABDOWN, + BOTH_STABDOWN_STAFF, + BOTH_STABDOWN_DUAL, + BOTH_A6_SABERPROTECT, + BOTH_A7_SOULCAL, + BOTH_A1_SPECIAL, + BOTH_A2_SPECIAL, + BOTH_A3_SPECIAL, + BOTH_ROLL_STAB, + + //# #sep BOTH_ STANDING + BOTH_STAND1, //# Standing idle, no weapon, hands down + BOTH_STAND1IDLE1, //# Random standing idle + BOTH_STAND2, //# Standing idle with a saber + BOTH_STAND2IDLE1, //# Random standing idle + BOTH_STAND2IDLE2, //# Random standing idle + BOTH_STAND3, //# Standing idle with 2-handed weapon + BOTH_STAND3IDLE1, //# Random standing idle + BOTH_STAND4, //# hands clasp behind back + BOTH_STAND5, //# standing idle, no weapon, hand down, back straight + BOTH_STAND5IDLE1, //# Random standing idle + BOTH_STAND6, //# one handed, gun at side, relaxed stand + BOTH_STAND8, //# both hands on hips (male) + BOTH_STAND1TO2, //# Transition from stand1 to stand2 + BOTH_STAND2TO1, //# Transition from stand2 to stand1 + BOTH_STAND2TO4, //# Transition from stand2 to stand4 + BOTH_STAND4TO2, //# Transition from stand4 to stand2 + BOTH_STAND4TOATTACK2, //# relaxed stand to 1-handed pistol ready + BOTH_STANDUP2, //# Luke standing up from his meditation platform (cin # 37) + BOTH_STAND5TOSIT3, //# transition from stand 5 to sit 3 + BOTH_STAND1TOSTAND5, //# Transition from stand1 to stand5 + BOTH_STAND5TOSTAND1, //# Transition from stand5 to stand1 + BOTH_STAND5TOAIM, //# Transition of Kye aiming his gun at Desann (cin #9) + BOTH_STAND5STARTLEDLOOKLEFT, //# Kyle turning to watch the bridge drop (cin #9) + BOTH_STARTLEDLOOKLEFTTOSTAND5, //# Kyle returning to stand 5 from watching the bridge drop (cin #9) + BOTH_STAND5TOSTAND8, //# Transition from stand5 to stand8 + BOTH_STAND7TOSTAND8, //# Tavion putting hands on back of chair (cin #11) + BOTH_STAND8TOSTAND5, //# Transition from stand8 to stand5 + BOTH_STAND9, //# Kyle's standing idle, no weapon, hands down + BOTH_STAND9IDLE1, //# Kyle's random standing idle + BOTH_STAND5SHIFTWEIGHT, //# Weightshift from stand5 to side and back to stand5 + BOTH_STAND5SHIFTWEIGHTSTART, //# From stand5 to side + BOTH_STAND5SHIFTWEIGHTSTOP, //# From side to stand5 + BOTH_STAND5TURNLEFTSTART, //# Start turning left from stand5 + BOTH_STAND5TURNLEFTSTOP, //# Stop turning left from stand5 + BOTH_STAND5TURNRIGHTSTART, //# Start turning right from stand5 + BOTH_STAND5TURNRIGHTSTOP, //# Stop turning right from stand5 + BOTH_STAND5LOOK180LEFTSTART, //# Start looking over left shoulder (cin #17) + BOTH_STAND5LOOK180LEFTSTOP, //# Stop looking over left shoulder (cin #17) + + BOTH_CONSOLE1START, //# typing at a console + BOTH_CONSOLE1, //# typing at a console + BOTH_CONSOLE1STOP, //# typing at a console + BOTH_CONSOLE2START, //# typing at a console with comm link in hand (cin #5) + BOTH_CONSOLE2, //# typing at a console with comm link in hand (cin #5) + BOTH_CONSOLE2STOP, //# typing at a console with comm link in hand (cin #5) + BOTH_CONSOLE2HOLDCOMSTART, //# lean in to type at console while holding comm link in hand (cin #5) + BOTH_CONSOLE2HOLDCOMSTOP, //# lean away after typing at console while holding comm link in hand (cin #5) + + BOTH_GUARD_LOOKAROUND1, //# Cradling weapon and looking around + BOTH_GUARD_IDLE1, //# Cradling weapon and standing + BOTH_GESTURE1, //# Generic gesture, non-specific + BOTH_GESTURE2, //# Generic gesture, non-specific + BOTH_WALK1TALKCOMM1, //# Talking into coom link while walking + BOTH_TALK1, //# Generic talk anim + BOTH_TALK2, //# Generic talk anim + BOTH_TALKCOMM1START, //# Start talking into a comm link + BOTH_TALKCOMM1, //# Talking into a comm link + BOTH_TALKCOMM1STOP, //# Stop talking into a comm link + BOTH_TALKGESTURE1, //# Generic talk anim + + BOTH_HEADTILTLSTART, //# Head tilt to left + BOTH_HEADTILTLSTOP, //# Head tilt to left + BOTH_HEADTILTRSTART, //# Head tilt to right + BOTH_HEADTILTRSTOP, //# Head tilt to right + BOTH_HEADNOD, //# Head shake YES + BOTH_HEADSHAKE, //# Head shake NO + BOTH_SIT2HEADTILTLSTART, //# Head tilt to left from seated position 2 + BOTH_SIT2HEADTILTLSTOP, //# Head tilt to left from seated position 2 + + BOTH_REACH1START, //# Monmothma reaching for crystal + BOTH_REACH1STOP, //# Monmothma reaching for crystal + + BOTH_COME_ON1, //# Jan gesturing to Kyle (cin #32a) + BOTH_STEADYSELF1, //# Jan trying to keep footing (cin #32a) + BOTH_STEADYSELF1END, //# Return hands to side from STEADSELF1 Kyle (cin#5) + BOTH_SILENCEGESTURE1, //# Luke silencing Kyle with a raised hand (cin #37) + BOTH_REACHFORSABER1, //# Luke holding hand out for Kyle's saber (cin #37) + BOTH_SABERKILLER1, //# Tavion about to strike Jan with saber (cin #9) + BOTH_SABERKILLEE1, //# Jan about to be struck by Tavion with saber (cin #9) + BOTH_HUGGER1, //# Kyle hugging Jan (cin #29) + BOTH_HUGGERSTOP1, //# Kyle stop hugging Jan but don't let her go (cin #29) + BOTH_HUGGEE1, //# Jan being hugged (cin #29) + BOTH_HUGGEESTOP1, //# Jan stop being hugged but don't let go (cin #29) + + BOTH_SABERTHROW1START, //# Desann throwing his light saber (cin #26) + BOTH_SABERTHROW1STOP, //# Desann throwing his light saber (cin #26) + BOTH_SABERTHROW2START, //# Kyle throwing his light saber (cin #32) + BOTH_SABERTHROW2STOP, //# Kyle throwing his light saber (cin #32) + + //# #sep BOTH_ SITTING/CROUCHING + BOTH_SIT1, //# Normal chair sit. + BOTH_SIT2, //# Lotus position. + BOTH_SIT3, //# Sitting in tired position, elbows on knees + + BOTH_SIT2TOSTAND5, //# Transition from sit 2 to stand 5 + BOTH_STAND5TOSIT2, //# Transition from stand 5 to sit 2 + BOTH_SIT2TOSIT4, //# Trans from sit2 to sit4 (cin #12) Luke leaning back from lotus position. + BOTH_SIT3TOSTAND5, //# transition from sit 3 to stand 5 + + BOTH_CROUCH1, //# Transition from standing to crouch + BOTH_CROUCH1IDLE, //# Crouching idle + BOTH_CROUCH1WALK, //# Walking while crouched + BOTH_CROUCH1WALKBACK, //# Walking while crouched + BOTH_UNCROUCH1, //# Transition from crouch to standing + BOTH_CROUCH2TOSTAND1, //# going from crouch2 to stand1 + BOTH_CROUCH3, //# Desann crouching down to Kyle (cin 9) + BOTH_UNCROUCH3, //# Desann uncrouching down to Kyle (cin 9) + BOTH_CROUCH4, //# Slower version of crouch1 for cinematics + BOTH_UNCROUCH4, //# Slower version of uncrouch1 for cinematics + + BOTH_GUNSIT1, //# sitting on an emplaced gun. + + // Swoop Vehicle animations. + //* #sep BOTH_ SWOOP ANIMS + BOTH_VS_MOUNT_L, //# Mount from left + BOTH_VS_DISMOUNT_L, //# Dismount to left + BOTH_VS_MOUNT_R, //# Mount from right (symmetry) + BOTH_VS_DISMOUNT_R, //# DISMOUNT TO RIGHT (SYMMETRY) + + BOTH_VS_MOUNTJUMP_L, //# + BOTH_VS_MOUNTTHROW, //# Land on an occupied vehicle & throw off current pilot + BOTH_VS_MOUNTTHROW_L, //# Land on an occupied vehicle & throw off current pilot + BOTH_VS_MOUNTTHROW_R, //# Land on an occupied vehicle & throw off current pilot + BOTH_VS_MOUNTTHROWEE, //# Current pilot getting thrown off by another guy + + BOTH_VS_LOOKLEFT, //# Turn & Look behind and to the left (no weapon) + BOTH_VS_LOOKRIGHT, //# Turn & Look behind and to the right (no weapon) + + BOTH_VS_TURBO, //# Hit The Turbo Button + + BOTH_VS_REV, //# Player looks back as swoop reverses + + BOTH_VS_AIR, //# Player stands up when swoop is airborn + BOTH_VS_AIR_G, //# "" with Gun + BOTH_VS_AIR_SL, //# "" with Saber Left + BOTH_VS_AIR_SR, //# "" with Saber Right + + BOTH_VS_LAND, //# Player bounces down when swoop lands + BOTH_VS_LAND_G, //# "" with Gun + BOTH_VS_LAND_SL, //# "" with Saber Left + BOTH_VS_LAND_SR, //# "" with Saber Right + + BOTH_VS_IDLE, //# Sit + BOTH_VS_IDLE_G, //# Sit (gun) + BOTH_VS_IDLE_SL, //# Sit (saber left) + BOTH_VS_IDLE_SR, //# Sit (saber right) + + BOTH_VS_LEANL, //# Lean left + BOTH_VS_LEANL_G, //# Lean left (gun) + BOTH_VS_LEANL_SL, //# Lean left (saber left) + BOTH_VS_LEANL_SR, //# Lean left (saber right) + + BOTH_VS_LEANR, //# Lean right + BOTH_VS_LEANR_G, //# Lean right (gun) + BOTH_VS_LEANR_SL, //# Lean right (saber left) + BOTH_VS_LEANR_SR, //# Lean right (saber right) + + BOTH_VS_ATL_S, //# Attack left with saber + BOTH_VS_ATR_S, //# Attack right with saber + BOTH_VS_ATR_TO_L_S, //# Attack toss saber from right to left hand + BOTH_VS_ATL_TO_R_S, //# Attack toss saber from left to right hand + BOTH_VS_ATR_G, //# Attack right with gun (90) + BOTH_VS_ATL_G, //# Attack left with gun (90) + BOTH_VS_ATF_G, //# Attack forward with gun + + BOTH_VS_PAIN1, //# Pain + + // Added 12/04/02 by Aurelio. + //* #sep BOTH_ TAUNTAUN ANIMS + BOTH_VT_MOUNT_L, //# Mount from left + BOTH_VT_MOUNT_R, //# Mount from right + BOTH_VT_MOUNT_B, //# Mount from air, behind + BOTH_VT_DISMOUNT, //# Dismount for tauntaun + BOTH_VT_DISMOUNT_L, //# Dismount to tauntauns left + BOTH_VT_DISMOUNT_R, //# Dismount to tauntauns right (symmetry) + + BOTH_VT_WALK_FWD, //# Walk forward + BOTH_VT_WALK_REV, //# Walk backward + BOTH_VT_WALK_FWD_L, //# walk lean left + BOTH_VT_WALK_FWD_R, //# Walk lean right + BOTH_VT_RUN_FWD, //# Run forward + BOTH_VT_RUN_REV, //# Look backwards while running (not weapon specific) + BOTH_VT_RUN_FWD_L, //# Run lean left + BOTH_VT_RUN_FWD_R, //# Run lean right + + BOTH_VT_SLIDEF, //# Tauntaun slides forward with abrupt stop + BOTH_VT_AIR, //# Tauntaun jump + BOTH_VT_ATB, //# Tauntaun tail swipe + BOTH_VT_PAIN1, //# Pain + BOTH_VT_DEATH1, //# Die + BOTH_VT_STAND, //# Stand still and breath + BOTH_VT_BUCK, //# Tauntaun bucking loop animation + + BOTH_VT_LAND, //# Player bounces down when tauntaun lands + BOTH_VT_TURBO, //# Hit The Turbo Button + BOTH_VT_IDLE_SL, //# Sit (saber left) + BOTH_VT_IDLE_SR, //# Sit (saber right) + + BOTH_VT_IDLE, //# Sit with no weapon selected + BOTH_VT_IDLE1, //# Sit with no weapon selected + BOTH_VT_IDLE_S, //# Sit with saber selected + BOTH_VT_IDLE_G, //# Sit with gun selected + BOTH_VT_IDLE_T, //# Sit with thermal grenade selected + + BOTH_VT_ATL_S, //# Attack left with saber + BOTH_VT_ATR_S, //# Attack right with saber + BOTH_VT_ATR_TO_L_S, //# Attack toss saber from right to left hand + BOTH_VT_ATL_TO_R_S, //# Attack toss saber from left to right hand + BOTH_VT_ATR_G, //# Attack right with gun (90) + BOTH_VT_ATL_G, //# Attack left with gun (90) + BOTH_VT_ATF_G, //# Attack forward with gun + + + // Added 2/26/02 by Aurelio. + //* #sep BOTH_ FIGHTER ANIMS + BOTH_GEARS_OPEN, + BOTH_GEARS_CLOSE, + BOTH_WINGS_OPEN, + BOTH_WINGS_CLOSE, + + BOTH_DEATH14_UNGRIP, //# Desann's end death (cin #35) + BOTH_DEATH14_SITUP, //# Tavion sitting up after having been thrown (cin #23) + BOTH_KNEES1, //# Tavion on her knees + BOTH_KNEES2, //# Tavion on her knees looking down + BOTH_KNEES2TO1, //# Transition of KNEES2 to KNEES1 + + //# #sep BOTH_ MOVING + BOTH_WALK1, //# Normal walk + BOTH_WALK2, //# Normal walk + BOTH_WALK_STAFF, //# Walk with saberstaff turned on + BOTH_WALKBACK_STAFF, //# Walk backwards with saberstaff turned on + BOTH_WALK_DUAL, //# Walk with dual turned on + BOTH_WALKBACK_DUAL, //# Walk backwards with dual turned on + BOTH_WALK5, //# Tavion taunting Kyle (cin 22) + BOTH_WALK6, //# Slow walk for Luke (cin 12) + BOTH_WALK7, //# Fast walk + BOTH_RUN1, //# Full run + BOTH_RUN1START, //# Start into full run1 + BOTH_RUN1STOP, //# Stop from full run1 + BOTH_RUN2, //# Full run + BOTH_RUN1TORUN2, //# Wampa run anim transition + BOTH_RUN2TORUN1, //# Wampa run anim transition + BOTH_RUN4, //# Jawa Run + BOTH_RUN_STAFF, //# Run with saberstaff turned on + BOTH_RUNBACK_STAFF, //# Run backwards with saberstaff turned on + BOTH_RUN_DUAL, //# Run with dual turned on + BOTH_RUNBACK_DUAL, //# Run backwards with dual turned on + BOTH_STRAFE_LEFT1, //# Sidestep left, should loop + BOTH_STRAFE_RIGHT1, //# Sidestep right, should loop + BOTH_RUNSTRAFE_LEFT1, //# Sidestep left, should loop + BOTH_RUNSTRAFE_RIGHT1, //# Sidestep right, should loop + BOTH_TURN_LEFT1, //# Turn left, should loop + BOTH_TURN_RIGHT1, //# Turn right, should loop + BOTH_TURNSTAND1, //# Turn from STAND1 position + BOTH_TURNSTAND2, //# Turn from STAND2 position + BOTH_TURNSTAND3, //# Turn from STAND3 position + BOTH_TURNSTAND4, //# Turn from STAND4 position + BOTH_TURNSTAND5, //# Turn from STAND5 position + BOTH_TURNCROUCH1, //# Turn from CROUCH1 position + + BOTH_WALKBACK1, //# Walk1 backwards + BOTH_WALKBACK2, //# Walk2 backwards + BOTH_RUNBACK1, //# Run1 backwards + BOTH_RUNBACK2, //# Run1 backwards + + //# #sep BOTH_ JUMPING + BOTH_JUMP1, //# Jump - wind-up and leave ground + BOTH_INAIR1, //# In air loop (from jump) + BOTH_LAND1, //# Landing (from in air loop) + BOTH_LAND2, //# Landing Hard (from a great height) + + BOTH_JUMPBACK1, //# Jump backwards - wind-up and leave ground + BOTH_INAIRBACK1, //# In air loop (from jump back) + BOTH_LANDBACK1, //# Landing backwards(from in air loop) + + BOTH_JUMPLEFT1, //# Jump left - wind-up and leave ground + BOTH_INAIRLEFT1, //# In air loop (from jump left) + BOTH_LANDLEFT1, //# Landing left(from in air loop) + + BOTH_JUMPRIGHT1, //# Jump right - wind-up and leave ground + BOTH_INAIRRIGHT1, //# In air loop (from jump right) + BOTH_LANDRIGHT1, //# Landing right(from in air loop) + + BOTH_FORCEJUMP1, //# Jump - wind-up and leave ground + BOTH_FORCEINAIR1, //# In air loop (from jump) + BOTH_FORCELAND1, //# Landing (from in air loop) + + BOTH_FORCEJUMPBACK1, //# Jump backwards - wind-up and leave ground + BOTH_FORCEINAIRBACK1, //# In air loop (from jump back) + BOTH_FORCELANDBACK1, //# Landing backwards(from in air loop) + + BOTH_FORCEJUMPLEFT1, //# Jump left - wind-up and leave ground + BOTH_FORCEINAIRLEFT1, //# In air loop (from jump left) + BOTH_FORCELANDLEFT1, //# Landing left(from in air loop) + + BOTH_FORCEJUMPRIGHT1, //# Jump right - wind-up and leave ground + BOTH_FORCEINAIRRIGHT1, //# In air loop (from jump right) + BOTH_FORCELANDRIGHT1, //# Landing right(from in air loop) + //# #sep BOTH_ ACROBATICS + BOTH_FLIP_F, //# Flip forward + BOTH_FLIP_B, //# Flip backwards + BOTH_FLIP_L, //# Flip left + BOTH_FLIP_R, //# Flip right + + BOTH_ROLL_F, //# Roll forward + BOTH_ROLL_B, //# Roll backward + BOTH_ROLL_L, //# Roll left + BOTH_ROLL_R, //# Roll right + + BOTH_HOP_F, //# quickstep forward + BOTH_HOP_B, //# quickstep backwards + BOTH_HOP_L, //# quickstep left + BOTH_HOP_R, //# quickstep right + + BOTH_DODGE_FL, //# lean-dodge forward left + BOTH_DODGE_FR, //# lean-dodge forward right + BOTH_DODGE_BL, //# lean-dodge backwards left + BOTH_DODGE_BR, //# lean-dodge backwards right + BOTH_DODGE_L, //# lean-dodge left + BOTH_DODGE_R, //# lean-dodge right + BOTH_DODGE_HOLD_FL, //# lean-dodge pose forward left + BOTH_DODGE_HOLD_FR, //# lean-dodge pose forward right + BOTH_DODGE_HOLD_BL, //# lean-dodge pose backwards left + BOTH_DODGE_HOLD_BR, //# lean-dodge pose backwards right + BOTH_DODGE_HOLD_L, //# lean-dodge pose left + BOTH_DODGE_HOLD_R, //# lean-dodge pose right + + //MP taunt anims + BOTH_ENGAGETAUNT, + BOTH_BOW, + BOTH_MEDITATE, + BOTH_MEDITATE_END, + BOTH_SHOWOFF_FAST, + BOTH_SHOWOFF_MEDIUM, + BOTH_SHOWOFF_STRONG, + BOTH_SHOWOFF_DUAL, + BOTH_SHOWOFF_STAFF, + BOTH_VICTORY_FAST, + BOTH_VICTORY_MEDIUM, + BOTH_VICTORY_STRONG, + BOTH_VICTORY_DUAL, + BOTH_VICTORY_STAFF, + //other saber/acro anims + BOTH_ARIAL_LEFT, //# + BOTH_ARIAL_RIGHT, //# + BOTH_CARTWHEEL_LEFT, //# + BOTH_CARTWHEEL_RIGHT, //# + BOTH_FLIP_LEFT, //# + BOTH_FLIP_BACK1, //# + BOTH_FLIP_BACK2, //# + BOTH_FLIP_BACK3, //# + BOTH_BUTTERFLY_LEFT, //# + BOTH_BUTTERFLY_RIGHT, //# + BOTH_WALL_RUN_RIGHT, //# + BOTH_WALL_RUN_RIGHT_FLIP,//# + BOTH_WALL_RUN_RIGHT_STOP,//# + BOTH_WALL_RUN_LEFT, //# + BOTH_WALL_RUN_LEFT_FLIP,//# + BOTH_WALL_RUN_LEFT_STOP,//# + BOTH_WALL_FLIP_RIGHT, //# + BOTH_WALL_FLIP_LEFT, //# + BOTH_KNOCKDOWN1, //# knocked backwards + BOTH_KNOCKDOWN2, //# knocked backwards hard + BOTH_KNOCKDOWN3, //# knocked forwards + BOTH_KNOCKDOWN4, //# knocked backwards from crouch + BOTH_KNOCKDOWN5, //# dupe of 3 - will be removed + BOTH_GETUP1, //# + BOTH_GETUP2, //# + BOTH_GETUP3, //# + BOTH_GETUP4, //# + BOTH_GETUP5, //# + BOTH_GETUP_CROUCH_F1, //# + BOTH_GETUP_CROUCH_B1, //# + BOTH_FORCE_GETUP_F1, //# + BOTH_FORCE_GETUP_F2, //# + BOTH_FORCE_GETUP_B1, //# + BOTH_FORCE_GETUP_B2, //# + BOTH_FORCE_GETUP_B3, //# + BOTH_FORCE_GETUP_B4, //# + BOTH_FORCE_GETUP_B5, //# + BOTH_FORCE_GETUP_B6, //# + BOTH_GETUP_BROLL_B, //# + BOTH_GETUP_BROLL_F, //# + BOTH_GETUP_BROLL_L, //# + BOTH_GETUP_BROLL_R, //# + BOTH_GETUP_FROLL_B, //# + BOTH_GETUP_FROLL_F, //# + BOTH_GETUP_FROLL_L, //# + BOTH_GETUP_FROLL_R, //# + BOTH_WALL_FLIP_BACK1, //# + BOTH_WALL_FLIP_BACK2, //# + BOTH_SPIN1, //# + BOTH_CEILING_CLING, //# clinging to ceiling + BOTH_CEILING_DROP, //# dropping from ceiling cling + + //TESTING + BOTH_FJSS_TR_BL, //# jump spin slash tr to bl + BOTH_FJSS_TL_BR, //# jump spin slash bl to tr + BOTH_RIGHTHANDCHOPPEDOFF,//# + BOTH_DEFLECTSLASH__R__L_FIN,//# + BOTH_BASHED1,//# + BOTH_ARIAL_F1,//# + BOTH_BUTTERFLY_FR1,//# + BOTH_BUTTERFLY_FL1,//# + + //NEW SABER/JEDI/FORCE ANIMS + BOTH_BACK_FLIP_UP, //# back flip up Bonus Animation!!!! + BOTH_LOSE_SABER, //# player losing saber (pulled from hand by force pull 4 - Kyle?) + BOTH_STAFF_TAUNT, //# taunt saberstaff + BOTH_DUAL_TAUNT, //# taunt dual + BOTH_A6_FB, //# dual attack front/back + BOTH_A6_LR, //# dual attack left/right + BOTH_A7_HILT, //# saber knock (alt + stand still) + //Alora + BOTH_ALORA_SPIN, //#jump spin attack death ballet + BOTH_ALORA_FLIP_1, //# gymnast move 1 + BOTH_ALORA_FLIP_2, //# gymnast move 2 + BOTH_ALORA_FLIP_3, //# gymnast move3 + BOTH_ALORA_FLIP_B, //# gymnast move back + BOTH_ALORA_SPIN_THROW, //# dual saber throw + BOTH_ALORA_SPIN_SLASH, //# spin slash special bonus animation!! :) + BOTH_ALORA_TAUNT, //# special taunt + //Rosh (Kothos battle) + BOTH_ROSH_PAIN, //# hurt animation (exhausted) + BOTH_ROSH_HEAL, //# healed/rejuvenated + //Tavion + BOTH_TAVION_SCEPTERGROUND, //# stabbing ground with sith sword shoots electricity everywhere + BOTH_TAVION_SWORDPOWER,//# Tavion doing the He-Man(tm) thing + BOTH_SCEPTER_START, //#Point scepter and attack start + BOTH_SCEPTER_HOLD, //#Point scepter and attack hold + BOTH_SCEPTER_STOP, //#Point scepter and attack stop + //Kyle Boss + BOTH_KYLE_GRAB, //# grab + BOTH_KYLE_MISS, //# miss + BOTH_KYLE_PA_1, //# hold 1 + BOTH_PLAYER_PA_1, //# player getting held 1 + BOTH_KYLE_PA_2, //# hold 2 + BOTH_PLAYER_PA_2, //# player getting held 2 + BOTH_PLAYER_PA_FLY, //# player getting knocked back from punch at end of hold 1 + BOTH_KYLE_PA_3, //# hold 3 + BOTH_PLAYER_PA_3, //# player getting held 3 + BOTH_PLAYER_PA_3_FLY,//# player getting thrown at end of hold 3 + //Rancor + BOTH_BUCK_RIDER, //# Rancor bucks when someone is on him + //WAMPA Grabbing enemy + BOTH_HOLD_START, //# + BOTH_HOLD_MISS, //# + BOTH_HOLD_IDLE, //# + BOTH_HOLD_END, //# + BOTH_HOLD_ATTACK, //# + BOTH_HOLD_SNIFF, //# Sniff the guy you're holding + BOTH_HOLD_DROP, //# just drop 'em + //BEING GRABBED BY WAMPA + BOTH_GRABBED, //# + BOTH_RELEASED, //# + BOTH_HANG_IDLE, //# + BOTH_HANG_ATTACK, //# + BOTH_HANG_PAIN, //# + + //# #sep BOTH_ MISC MOVEMENT + BOTH_HIT1, //# Kyle hit by crate in cin #9 + BOTH_LADDER_UP1, //# Climbing up a ladder with rungs at 16 unit intervals + BOTH_LADDER_DWN1, //# Climbing down a ladder with rungs at 16 unit intervals + BOTH_LADDER_IDLE, //# Just sitting on the ladder + + //# #sep BOTH_ FLYING IDLE + BOTH_FLY_SHIELDED, //# For sentry droid, shields in + + //# #sep BOTH_ SWIMMING + BOTH_SWIM_IDLE1, //# Swimming Idle 1 + BOTH_SWIMFORWARD, //# Swim forward loop + BOTH_SWIMBACKWARD, //# Swim backward loop + + //# #sep BOTH_ LYING + BOTH_SLEEP1, //# laying on back-rknee up-rhand on torso + BOTH_SLEEP6START, //# Kyle leaning back to sleep (cin 20) + BOTH_SLEEP6STOP, //# Kyle waking up and shaking his head (cin 21) + BOTH_SLEEP1GETUP, //# alarmed and getting up out of sleep1 pose to stand + BOTH_SLEEP1GETUP2, //# + + BOTH_CHOKE1START, //# tavion in force grip choke + BOTH_CHOKE1STARTHOLD, //# loop of tavion in force grip choke + BOTH_CHOKE1, //# tavion in force grip choke + + BOTH_CHOKE2, //# tavion recovering from force grip choke + BOTH_CHOKE3, //# left-handed choke (for people still holding a weapon) + + //# #sep BOTH_ HUNTER-SEEKER BOT-SPECIFIC + BOTH_POWERUP1, //# Wakes up + + BOTH_TURNON, //# Protocol Droid wakes up + BOTH_TURNOFF, //# Protocol Droid shuts off + + BOTH_BUTTON1, //# Single button push with right hand + BOTH_BUTTON2, //# Single button push with left finger + BOTH_BUTTON_HOLD, //# Single button hold with left hand + BOTH_BUTTON_RELEASE, //# Single button release with left hand + + //# JEDI-SPECIFIC + //# #sep BOTH_ FORCE ANIMS + BOTH_RESISTPUSH, //# plant yourself to resist force push/pulls. + BOTH_FORCEPUSH, //# Use off-hand to do force power. + BOTH_FORCEPULL, //# Use off-hand to do force power. + BOTH_MINDTRICK1, //# Use off-hand to do mind trick + BOTH_MINDTRICK2, //# Use off-hand to do distraction + BOTH_FORCELIGHTNING, //# Use off-hand to do lightning + BOTH_FORCELIGHTNING_START, //# Use off-hand to do lightning - start + BOTH_FORCELIGHTNING_HOLD, //# Use off-hand to do lightning - hold + BOTH_FORCELIGHTNING_RELEASE,//# Use off-hand to do lightning - release + BOTH_FORCEHEAL_START, //# Healing meditation pose start + BOTH_FORCEHEAL_STOP, //# Healing meditation pose end + BOTH_FORCEHEAL_QUICK, //# Healing meditation gesture + BOTH_SABERPULL, //# Use off-hand to do force power. + BOTH_FORCEGRIP1, //# force-gripping (no anim?) + BOTH_FORCEGRIP3, //# force-gripping (right hand) + BOTH_FORCEGRIP3THROW, //# throwing while force-gripping (right hand) + BOTH_FORCEGRIP_HOLD, //# Use off-hand to do grip - hold + BOTH_FORCEGRIP_RELEASE,//# Use off-hand to do grip - release + BOTH_TOSS1, //# throwing to left after force gripping + BOTH_TOSS2, //# throwing to right after force gripping + //NEW force anims for JKA: + BOTH_FORCE_RAGE, + BOTH_FORCE_2HANDEDLIGHTNING, + BOTH_FORCE_2HANDEDLIGHTNING_START, + BOTH_FORCE_2HANDEDLIGHTNING_HOLD, + BOTH_FORCE_2HANDEDLIGHTNING_RELEASE, + BOTH_FORCE_DRAIN, + BOTH_FORCE_DRAIN_START, + BOTH_FORCE_DRAIN_HOLD, + BOTH_FORCE_DRAIN_RELEASE, + BOTH_FORCE_DRAIN_GRAB_START, + BOTH_FORCE_DRAIN_GRAB_HOLD, + BOTH_FORCE_DRAIN_GRAB_END, + BOTH_FORCE_DRAIN_GRABBED, + BOTH_FORCE_ABSORB, + BOTH_FORCE_ABSORB_START, + BOTH_FORCE_ABSORB_END, + BOTH_FORCE_PROTECT, + BOTH_FORCE_PROTECT_FAST, + + BOTH_WIND, + + BOTH_STAND_TO_KNEEL, + BOTH_KNEEL_TO_STAND, + + BOTH_TUSKENATTACK1, + BOTH_TUSKENATTACK2, + BOTH_TUSKENATTACK3, + BOTH_TUSKENLUNGE1, + BOTH_TUSKENTAUNT1, + + BOTH_COWER1_START, //# cower start + BOTH_COWER1, //# cower loop + BOTH_COWER1_STOP, //# cower stop + BOTH_SONICPAIN_START, + BOTH_SONICPAIN_HOLD, + BOTH_SONICPAIN_END, + + //new anim slots per Jarrod's request + BOTH_STAND10, + BOTH_STAND10_TALK1, + BOTH_STAND10_TALK2, + BOTH_STAND10TOSTAND1, + + BOTH_STAND1_TALK1, + BOTH_STAND1_TALK2, + BOTH_STAND1_TALK3, + + BOTH_SIT4, + BOTH_SIT5, + BOTH_SIT5_TALK1, + BOTH_SIT5_TALK2, + BOTH_SIT5_TALK3, + + BOTH_SIT6, + BOTH_SIT7, + + //================================================= + //ANIMS IN WHICH ONLY THE UPPER OBJECTS ARE IN MD3 + //================================================= + //# #sep TORSO_ WEAPON-RELATED + TORSO_DROPWEAP1, //# Put weapon away + TORSO_DROPWEAP4, //# Put weapon away + TORSO_RAISEWEAP1, //# Draw Weapon + TORSO_RAISEWEAP4, //# Draw Weapon + TORSO_WEAPONREADY1, //# Ready to fire stun baton + TORSO_WEAPONREADY2, //# Ready to fire one-handed blaster pistol + TORSO_WEAPONREADY3, //# Ready to fire blaster rifle + TORSO_WEAPONREADY4, //# Ready to fire sniper rifle + TORSO_WEAPONREADY10, //# Ready to fire thermal det + TORSO_WEAPONIDLE2, //# Holding one-handed blaster + TORSO_WEAPONIDLE3, //# Holding blaster rifle + TORSO_WEAPONIDLE4, //# Holding sniper rifle + TORSO_WEAPONIDLE10, //# Holding thermal det + + //# #sep TORSO_ MISC + TORSO_SURRENDER_START, //# arms up + TORSO_SURRENDER_STOP, //# arms back down + + TORSO_CHOKING1, //# TEMP + + TORSO_HANDSIGNAL1, + TORSO_HANDSIGNAL2, + TORSO_HANDSIGNAL3, + TORSO_HANDSIGNAL4, + TORSO_HANDSIGNAL5, + + + //================================================= + //ANIMS IN WHICH ONLY THE LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep Legs-only anims + LEGS_TURN1, //# What legs do when you turn your lower body to match your upper body facing + LEGS_TURN2, //# Leg turning from stand2 + LEGS_LEAN_LEFT1, //# Lean left + LEGS_LEAN_RIGHT1, //# Lean Right + LEGS_CHOKING1, //# TEMP + LEGS_LEFTUP1, //# On a slope with left foot 4 higher than right + LEGS_LEFTUP2, //# On a slope with left foot 8 higher than right + LEGS_LEFTUP3, //# On a slope with left foot 12 higher than right + LEGS_LEFTUP4, //# On a slope with left foot 16 higher than right + LEGS_LEFTUP5, //# On a slope with left foot 20 higher than right + LEGS_RIGHTUP1, //# On a slope with RIGHT foot 4 higher than left + LEGS_RIGHTUP2, //# On a slope with RIGHT foot 8 higher than left + LEGS_RIGHTUP3, //# On a slope with RIGHT foot 12 higher than left + LEGS_RIGHTUP4, //# On a slope with RIGHT foot 16 higher than left + LEGS_RIGHTUP5, //# On a slope with RIGHT foot 20 higher than left + LEGS_S1_LUP1, + LEGS_S1_LUP2, + LEGS_S1_LUP3, + LEGS_S1_LUP4, + LEGS_S1_LUP5, + LEGS_S1_RUP1, + LEGS_S1_RUP2, + LEGS_S1_RUP3, + LEGS_S1_RUP4, + LEGS_S1_RUP5, + LEGS_S3_LUP1, + LEGS_S3_LUP2, + LEGS_S3_LUP3, + LEGS_S3_LUP4, + LEGS_S3_LUP5, + LEGS_S3_RUP1, + LEGS_S3_RUP2, + LEGS_S3_RUP3, + LEGS_S3_RUP4, + LEGS_S3_RUP5, + LEGS_S4_LUP1, + LEGS_S4_LUP2, + LEGS_S4_LUP3, + LEGS_S4_LUP4, + LEGS_S4_LUP5, + LEGS_S4_RUP1, + LEGS_S4_RUP2, + LEGS_S4_RUP3, + LEGS_S4_RUP4, + LEGS_S4_RUP5, + LEGS_S5_LUP1, + LEGS_S5_LUP2, + LEGS_S5_LUP3, + LEGS_S5_LUP4, + LEGS_S5_LUP5, + LEGS_S5_RUP1, + LEGS_S5_RUP2, + LEGS_S5_RUP3, + LEGS_S5_RUP4, + LEGS_S5_RUP5, + LEGS_S6_LUP1, + LEGS_S6_LUP2, + LEGS_S6_LUP3, + LEGS_S6_LUP4, + LEGS_S6_LUP5, + LEGS_S6_RUP1, + LEGS_S6_RUP2, + LEGS_S6_RUP3, + LEGS_S6_RUP4, + LEGS_S6_RUP5, + LEGS_S7_LUP1, + LEGS_S7_LUP2, + LEGS_S7_LUP3, + LEGS_S7_LUP4, + LEGS_S7_LUP5, + LEGS_S7_RUP1, + LEGS_S7_RUP2, + LEGS_S7_RUP3, + LEGS_S7_RUP4, + LEGS_S7_RUP5, + + //New anim as per Jarrod's request + LEGS_TURN180, + + //====================================================== + //cinematic anims + //====================================================== + //# #sep BOTH_ CINEMATIC-ONLY + BOTH_CIN_1, //# Level specific cinematic 1 + BOTH_CIN_2, //# Level specific cinematic 2 + BOTH_CIN_3, //# Level specific cinematic 3 + BOTH_CIN_4, //# Level specific cinematic 4 + BOTH_CIN_5, //# Level specific cinematic 5 + BOTH_CIN_6, //# Level specific cinematic 6 + BOTH_CIN_7, //# Level specific cinematic 7 + BOTH_CIN_8, //# Level specific cinematic 8 + BOTH_CIN_9, //# Level specific cinematic 9 + BOTH_CIN_10, //# Level specific cinematic 10 + BOTH_CIN_11, //# Level specific cinematic 11 + BOTH_CIN_12, //# Level specific cinematic 12 + BOTH_CIN_13, //# Level specific cinematic 13 + BOTH_CIN_14, //# Level specific cinematic 14 + BOTH_CIN_15, //# Level specific cinematic 15 + BOTH_CIN_16, //# Level specific cinematic 16 + BOTH_CIN_17, //# Level specific cinematic 17 + BOTH_CIN_18, //# Level specific cinematic 18 + BOTH_CIN_19, //# Level specific cinematic 19 + BOTH_CIN_20, //# Level specific cinematic 20 + BOTH_CIN_21, //# Level specific cinematic 21 + BOTH_CIN_22, //# Level specific cinematic 22 + BOTH_CIN_23, //# Level specific cinematic 23 + BOTH_CIN_24, //# Level specific cinematic 24 + BOTH_CIN_25, //# Level specific cinematic 25 + BOTH_CIN_26, //# Level specific cinematic + BOTH_CIN_27, //# Level specific cinematic + BOTH_CIN_28, //# Level specific cinematic + BOTH_CIN_29, //# Level specific cinematic + BOTH_CIN_30, //# Level specific cinematic + BOTH_CIN_31, //# Level specific cinematic + BOTH_CIN_32, //# Level specific cinematic + BOTH_CIN_33, //# Level specific cinematic + BOTH_CIN_34, //# Level specific cinematic + BOTH_CIN_35, //# Level specific cinematic + BOTH_CIN_36, //# Level specific cinematic + BOTH_CIN_37, //# Level specific cinematic + BOTH_CIN_38, //# Level specific cinematic + BOTH_CIN_39, //# Level specific cinematic + BOTH_CIN_40, //# Level specific cinematic + BOTH_CIN_41, //# Level specific cinematic + BOTH_CIN_42, //# Level specific cinematic + BOTH_CIN_43, //# Level specific cinematic + BOTH_CIN_44, //# Level specific cinematic + BOTH_CIN_45, //# Level specific cinematic + BOTH_CIN_46, //# Level specific cinematic + BOTH_CIN_47, //# Level specific cinematic + BOTH_CIN_48, //# Level specific cinematic + BOTH_CIN_49, //# Level specific cinematic + BOTH_CIN_50, //# Level specific cinematic + + //# #eol + MAX_ANIMATIONS, + MAX_TOTALANIMATIONS, +} animNumber_t; + +#define SABER_ANIM_GROUP_SIZE (BOTH_A2_T__B_ - BOTH_A1_T__B_) + + +#endif// #ifndef __ANIMS_H__ + diff --git a/code/game/b_local.h b/code/game/b_local.h new file mode 100644 index 0000000..aa71f25 --- /dev/null +++ b/code/game/b_local.h @@ -0,0 +1,353 @@ +//B_local.h +//re-added by MCG +#ifndef __B_LOCAL_H__ +#define __B_LOCAL_H__ + +#include "g_local.h" +#include "say.h" + +#include "AI.h" + +#define AI_TIMERS 0//turn on to see print-outs of AI/nav timing +// +// Navigation susbsystem +// + +#define NAVF_DUCK 0x00000001 +#define NAVF_JUMP 0x00000002 +#define NAVF_HOLD 0x00000004 +#define NAVF_SLOW 0x00000008 + +#define DEBUG_LEVEL_DETAIL 4 +#define DEBUG_LEVEL_INFO 3 +#define DEBUG_LEVEL_WARNING 2 +#define DEBUG_LEVEL_ERROR 1 +#define DEBUG_LEVEL_NONE 0 + +#define MAX_GOAL_REACHED_DIST_SQUARED 256//16 squared +#define MIN_ANGLE_ERROR 0.01f + +#define MIN_ROCKET_DIST_SQUARED 16384//128*128 +// +// NPC.cpp +// +// ai debug cvars +void SetNPCGlobals( gentity_t *ent ); +void SaveNPCGlobals(); +void RestoreNPCGlobals(); +extern cvar_t *debugNPCAI; // used to print out debug info about the NPC AI +extern cvar_t *debugNPCFreeze; // set to disable NPC ai and temporarily freeze them in place +extern cvar_t *debugNPCName; +extern cvar_t *d_JediAI; +extern cvar_t *d_saberCombat; +extern void NPC_Think ( gentity_t *self); +extern void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope = NULL, vec3_t storeAngles = NULL ); + +//NPC_reactions.cpp +extern void NPC_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, vec3_t point, int damage, int mod ,int hitLoc); +extern void NPC_Touch( gentity_t *self, gentity_t *other, trace_t *trace ); +extern void NPC_Use( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern float NPC_GetPainChance( gentity_t *self, int damage ); + +// +// NPC_misc.cpp +// +extern void Debug_Printf( cvar_t *cv, int level, char *fmt, ... ); +extern void Debug_NPCPrintf( gentity_t *printNPC, cvar_t *cv, int debugLevel, char *fmt, ... ); + +//MCG - Begin============================================================ +//NPC_ai variables - shared by NPC.cpp andf the following modules +extern gentity_t *NPC; +extern gNPC_t *NPCInfo; +extern gclient_t *client; +extern usercmd_t ucmd; +extern visibility_t enemyVisibility; + +//AI_Default +extern qboolean NPC_CheckInvestigate( int alertEventNum ); +extern qboolean NPC_StandTrackAndShoot (gentity_t *NPC); +extern void NPC_BSIdle( void ); +extern void NPC_BSPointShoot(qboolean shoot); +extern void NPC_BSStandGuard (void); +extern void NPC_BSPatrol (void); +extern void NPC_BSHuntAndKill (void); +extern void NPC_BSStandAndShoot (void); +extern void NPC_BSRunAndShoot (void); +extern void NPC_BSWait( void ); +extern void NPC_BSDefault( void ); + +//NPC_behavior +extern void NPC_BSAdvanceFight (void); +extern void NPC_BSInvestigate (void); +extern void NPC_BSSleep( void ); +extern void NPC_BSFollowLeader (void); +extern void NPC_BSJump (void); +extern void NPC_BSRemove (void); +extern void NPC_BSSearch (void); +extern void NPC_BSSearchStart (int homeWp, bState_t bState); +extern void NPC_BSWander (void); +extern qboolean NPC_BSFlee( void ); +extern void NPC_StartFlee( gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ); +extern void G_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ); + +//NPC_combat +extern int ChooseBestWeapon( void ); +extern void NPC_ChangeWeapon( int newWeapon ); +extern void ShootThink( void ); +extern void WeaponThink( qboolean inCombat ); +extern qboolean HaveWeapon( int weapon ); +extern qboolean CanShoot ( gentity_t *ent, gentity_t *shooter ); +extern void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ); +extern gentity_t *NPC_PickEnemy (gentity_t *closestTo, int enemyTeam, qboolean checkVis, qboolean findPlayersFirst, qboolean findClosest); +extern gentity_t *NPC_CheckEnemy (qboolean findNew, qboolean tooFarOk, qboolean setEnemy = qtrue ); +extern qboolean NPC_CheckAttack (float scale); +extern qboolean NPC_CheckDefend (float scale); +extern qboolean NPC_CheckCanAttack (float attack_scale, qboolean stationary); +extern int NPC_AttackDebounceForWeapon (void); +extern qboolean EntIsGlass (gentity_t *check); +extern qboolean ShotThroughGlass (trace_t *tr, gentity_t *target, vec3_t spot, int mask); +extern void G_ClearEnemy (gentity_t *self); +extern void G_SetEnemy (gentity_t *self, gentity_t *enemy); +extern gentity_t *NPC_PickAlly ( qboolean facingEachOther, float range, qboolean ignoreGroup, qboolean movingOnly ); +extern void NPC_LostEnemyDecideChase(void); +extern float NPC_MaxDistSquaredForWeapon( void ); +extern qboolean NPC_EvaluateShot( int hit, qboolean glassOK ); +extern int NPC_ShotEntity( gentity_t *ent, vec3_t impactPos = NULL ); + +//NPC_formation +extern qboolean NPC_SlideMoveToGoal (void); +extern float NPC_FindClosestTeammate (gentity_t *self); +extern void NPC_CalcClosestFormationSpot(gentity_t *self); +extern void G_MaintainFormations (gentity_t *self); +extern void NPC_BSFormation (void); +extern void NPC_CreateFormation (gentity_t *self); +extern void NPC_DropFormation (gentity_t *self); +extern void NPC_ReorderFormation (gentity_t *self); +extern void NPC_InsertIntoFormation (gentity_t *self); +extern void NPC_DeleteFromFormation (gentity_t *self); + +#define COLLISION_RADIUS 32 +#define NUM_POSITIONS 30 + +//NPC spawnflags +#define SFB_SMALLHULL 1 + +#define SFB_RIFLEMAN 2 +#define SFB_OLDBORG 2//Borg +#define SFB_PHASER 4 +#define SFB_GUN 4//Borg +#define SFB_TRICORDER 8 +#define SFB_TASER 8//Borg +#define SFB_DRILL 16//Borg + +#define SFB_CINEMATIC 32 +#define SFB_NOTSOLID 64 +#define SFB_STARTINSOLID 128 + +#define SFB_TROOPERAI (1<<9) + +//NPC_goal +extern void SetGoal( gentity_t *goal, float rating ); +extern void NPC_SetGoal( gentity_t *goal, float rating ); +extern void NPC_ClearGoal( void ); +extern void NPC_ReachedGoal( void ); +extern qboolean ReachedGoal( gentity_t *goal ); +extern gentity_t *UpdateGoal( void ); +extern qboolean NPC_MoveToGoal( qboolean tryStraight ); + +//NPC_move +qboolean NPC_Jumping( void ); +qboolean NPC_JumpBackingUp( void ); + +qboolean NPC_TryJump( gentity_t *goal, float max_xy_dist = 0.0f, float max_z_diff = 0.0f ); +qboolean NPC_TryJump( const vec3_t& pos,float max_xy_dist = 0.0f, float max_z_diff = 0.0f ); + +//NPC_reactions + +//NPC_senses +#define ALERT_CLEAR_TIME 200 +#define CHECK_PVS 1 +#define CHECK_360 2 +#define CHECK_FOV 4 +#define CHECK_SHOOT 8 +#define CHECK_VISRANGE 16 +extern qboolean CanSee ( gentity_t *ent ); +extern qboolean InFOV ( gentity_t *ent, gentity_t *from, int hFOV, int vFOV ); +extern qboolean InFOV( vec3_t origin, gentity_t *from, int hFOV, int vFOV ); +extern qboolean InFOV( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV ); +extern visibility_t NPC_CheckVisibility ( gentity_t *ent, int flags ); +extern qboolean InVisrange ( gentity_t *ent ); + +//NPC_sounds +//extern void NPC_AngerSound(void); + +//NPC_spawn +extern void NPC_Spawn( gentity_t *self ); + +//NPC_stats +extern int NPC_ReactionTime ( void ); +extern qboolean NPC_ParseParms( const char *NPCName, gentity_t *NPC ); +extern void NPC_LoadParms( void ); + +//NPC_utils +extern int teamNumbers[TEAM_NUM_TEAMS]; +extern int teamStrength[TEAM_NUM_TEAMS]; +extern int teamCounter[TEAM_NUM_TEAMS]; +extern void CalcEntitySpot ( const gentity_t *ent, const spot_t spot, vec3_t point ); +extern qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ); +extern void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ); +extern qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ); +extern void SetTeamNumbers (void); +extern qboolean G_ActivateBehavior (gentity_t *self, int bset ); +extern void NPC_AimWiggle( vec3_t enemy_org ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); + + +//other modules +extern void CalcMuzzlePoint ( gentity_t *const ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint, float lead_in ); + +//g_combat +extern void ExplodeDeath( gentity_t *self ); +extern void ExplodeDeath_Wait( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc ); +extern void GoExplodeDeath( gentity_t *self, gentity_t *other, gentity_t *activator); +extern float IdealDistance ( gentity_t *self ); + +//g_client +extern qboolean SpotWouldTelefrag( gentity_t *spot, team_t checkteam ); + +//g_squad +extern void NPC_SetSayState (gentity_t *self, gentity_t *to, int saying); + +//g_utils +extern qboolean G_CheckInSolid (gentity_t *self, qboolean fix); +extern qboolean infront(gentity_t *from, gentity_t *to); + +//MCG - End============================================================ + +// NPC.cpp +extern void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend = SETANIM_BLEND_DEFAULT); +extern qboolean NPC_EnemyTooFar(gentity_t *enemy, float dist, qboolean toShoot); + +// ================================================================== + +inline qboolean NPC_ClearLOS( const vec3_t start, const vec3_t end ) +{ + return G_ClearLOS( NPC, start, end ); +} +inline qboolean NPC_ClearLOS( const vec3_t end ) +{ + return G_ClearLOS( NPC, end ); +} +inline qboolean NPC_ClearLOS( gentity_t *ent ) +{ + return G_ClearLOS( NPC, ent ); +} +inline qboolean NPC_ClearLOS( const vec3_t start, gentity_t *ent ) +{ + return G_ClearLOS( NPC, start, ent ); +} +inline qboolean NPC_ClearLOS( gentity_t *ent, const vec3_t end ) +{ + return G_ClearLOS( NPC, ent, end ); +} + +extern qboolean NPC_ClearShot( gentity_t *ent ); + +extern int NPC_FindCombatPoint( const vec3_t position, const vec3_t avoidPosition, vec3_t enemyPosition, const int flags, const float avoidDist, const int ignorePoint = -1 ); +extern int NPC_FindCombatPointRetry( const vec3_t position, + const vec3_t avoidPosition, + vec3_t enemyPosition, + int *cpFlags, + float avoidDist, + const int ignorePoint ); + + +extern qboolean NPC_ReserveCombatPoint( int combatPointID ); +extern qboolean NPC_FreeCombatPoint( int combatPointID, qboolean failed = qfalse ); +extern qboolean NPC_SetCombatPoint( int combatPointID ); + +#define CP_ANY 0 //No flags +#define CP_COVER 0x00000001 //The enemy cannot currently shoot this position +#define CP_CLEAR 0x00000002 //This cover point has a clear shot to the enemy +#define CP_FLEE 0x00000004 //This cover point is marked as a flee point +#define CP_DUCK 0x00000008 //This cover point is marked as a duck point +#define CP_NEAREST 0x00000010 //Find the nearest combat point +#define CP_AVOID_ENEMY 0x00000020 //Avoid our enemy +#define CP_INVESTIGATE 0x00000040 //A special point worth enemy investigation if searching +#define CP_SQUAD 0x00000080 //Squad path +#define CP_AVOID 0x00000100 //Avoid supplied position +#define CP_APPROACH_ENEMY 0x00000200 //Try to get closer to enemy +#define CP_CLOSEST 0x00000400 //Take the closest combatPoint to the enemy that's available +#define CP_FLANK 0x00000800 //Pick a combatPoint behind the enemy +#define CP_HAS_ROUTE 0x00001000 //Pick a combatPoint that we have a route to +#define CP_SNIPE 0x00002000 //Pick a combatPoint that is marked as a sniper spot +#define CP_SAFE 0x00004000 //Pick a combatPoint that is not have dangerTime +#define CP_HORZ_DIST_COLL 0x00008000 //Collect combat points within *horizontal* dist +#define CP_NO_PVS 0x00010000 //A combat point out of the PVS of enemy pos +#define CP_RETREAT 0x00020000 //Try to get farther from enemy +#define CP_SHORTEST_PATH 0x0004000 //Shortest path from me to combat point to enemy +#define CP_TRYFAR 0x00080000 + +#define CPF_NONE 0 +#define CPF_DUCK 0x00000001 +#define CPF_FLEE 0x00000002 +#define CPF_INVESTIGATE 0x00000004 +#define CPF_SQUAD 0x00000008 +#define CPF_LEAN 0x00000010 +#define CPF_SNIPE 0x00000020 + +#define MAX_COMBAT_POINT_CHECK 32 + +extern int NPC_ValidEnemy( gentity_t *ent ); +extern int NPC_CheckEnemyExt( qboolean checkAlerts = qfalse ); +extern qboolean NPC_FindPlayer( void ); +extern qboolean NPC_CheckCanAttackExt( void ); + +extern int NPC_CheckAlertEvents( qboolean checkSight, qboolean checkSound, int ignoreAlert = -1, qboolean mustHaveOwner = qfalse, int minAlertLevel = AEL_MINOR, qboolean onGroundOnly = qfalse ); +extern qboolean NPC_CheckForDanger( int alertEvent ); +extern void G_AlertTeam( gentity_t *victim, gentity_t *attacker, float radius, float soundDist ); + +extern int NPC_FindSquadPoint( vec3_t position ); + +extern void ClearPlayerAlertEvents( void ); + +extern qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2); + +extern void NPC_SetMoveGoal( gentity_t *ent, vec3_t point, int radius, qboolean isNavGoal = qfalse, int combatPoint = -1, gentity_t *targetEnt = NULL ); + +extern void NPC_ApplyWeaponFireDelay(void); + +//NPC_FaceXXX suite +extern qboolean NPC_FacePosition( vec3_t position, qboolean doPitch = qtrue ); +extern qboolean NPC_FaceEntity( gentity_t *ent, qboolean doPitch = qtrue ); +extern qboolean NPC_FaceEnemy( qboolean doPitch = qtrue ); + +//Skill level cvar +extern cvar_t *g_spskill; + +#define NIF_NONE 0x00000000 +#define NIF_FAILED 0x00000001 //failed to find a way to the goal +#define NIF_MACRO_NAV 0x00000002 //using macro navigation +#define NIF_COLLISION 0x00000004 //resolving collision with an entity +#define NIF_BLOCKED 0x00000008 //blocked from moving + +/* +------------------------- +struct navInfo_s +------------------------- +*/ + +typedef struct navInfo_s +{ + gentity_t *blocker; + vec3_t direction; + vec3_t pathDirection; + float distance; + trace_t trace; + int flags; +} navInfo_t; + + + + +#endif diff --git a/code/game/b_public.h b/code/game/b_public.h new file mode 100644 index 0000000..97f7d5b --- /dev/null +++ b/code/game/b_public.h @@ -0,0 +1,401 @@ +#ifndef __B_PUBLIC_H__ +#define __B_PUBLIC_H__ + +#include "bstate.h" +#include "AI.h" + +#define NPCAI_CHECK_WEAPON 0x00000001 +#define NPCAI_BURST_WEAPON 0x00000002 +#define NPCAI_MOVING 0x00000004 +#define NPCAI_TOUCHED_GOAL 0x00000008 +#define NPCAI_PUSHED 0x00000010 +#define NPCAI_NO_COLL_AVOID 0x00000020 +#define NPCAI_BLOCKED 0x00000040 +#define NPCAI_SUBBOSS_CHARACTER 0x00000080 //Alora, tough reborn +#define NPCAI_OFF_PATH 0x00000100 +#define NPCAI_IN_SQUADPOINT 0x00000200 +#define NPCAI_STRAIGHT_TO_DESTPOS 0x00000400 +#define NPCAI_HEAVY_MELEE 0x00000800 //4x melee damage, dismemberment +#define NPCAI_NO_SLOWDOWN 0x00001000 +#define NPCAI_LOST 0x00002000 //Can't nav to his goal +#define NPCAI_SHIELDS 0x00004000 //Has shields, borg can adapt +#define NPCAI_GREET_ALLIES 0x00008000 //Say hi to nearby allies +#define NPCAI_FORM_TELE_NAV 0x00010000 //Tells formation people to use nav info to get to +#define NPCAI_ENROUTE_TO_HOMEWP 0x00020000 //Lets us know to run our lostenemyscript when we get to homeWp +#define NPCAI_MATCHPLAYERWEAPON 0x00040000 //Match the player's weapon except when it changes during cinematics +#define NPCAI_DIE_ON_IMPACT 0x00100000 //Next time you crashland, die! +#define NPCAI_WALKING 0x00200000 +#define NPCAI_STOP_AT_LOS 0x00400000 //Stop Running When We Hit LOS +#define NPCAI_NAV_THROUGH_BREAKABLES 0x00800000 //Navigation allows connections through breakable (func_glass, func_breakable or misc_model_breakable) +#define NPCAI_KNEEL 0x01000000 //Kneel befor Zod +#define NPCAI_FLY 0x02000000 //Fly, My Pretty! +#define NPCAI_FLAMETHROW 0x04000000 +#define NPCAI_ROSH 0x08000000 //I am Rosh, when I'm hurt, drop to one knee and wait for Vil or Dasariah to heal me +#define NPCAI_HEAL_ROSH 0x10000000 //Constantly look for NPC with NPC_type of rosh_dark, follow him, heal him if needbe +#define NPCAI_JUMP 0x20000000 //Jump Now +#define NPCAI_BOSS_CHARACTER 0x40000000 //Boss NPC flag for certain immunities/defenses +#define NPCAI_NO_JEDI_DELAY 0x80000000 //Reborn/Jedi don't taunt enemy before attacking + +//Script flags +#define SCF_CROUCHED 0x00000001 //Force ucmd.upmove to be -127 +#define SCF_WALKING 0x00000002 //Force BUTTON_WALKING to be pressed +#define SCF_MORELIGHT 0x00000004 //NPC will have a minlight of 96 +#define SCF_LEAN_RIGHT 0x00000008 //Force rightmove+BUTTON_USE +#define SCF_LEAN_LEFT 0x00000010 //Force leftmove+BUTTON_USE +#define SCF_RUNNING 0x00000020 //Takes off walking button, overrides SCF_WALKING +#define SCF_ALT_FIRE 0x00000040 //Force to use alt-fire when firing +#define SCF_NO_RESPONSE 0x00000080 //NPC will not do generic responses to being used +#define SCF_FFDEATH 0x00000100 //Just tells player_die to run the friendly fire deathscript +#define SCF_NO_COMBAT_TALK 0x00000200 //NPC will not use their generic combat chatter stuff +#define SCF_CHASE_ENEMIES 0x00000400 //NPC chase enemies - FIXME: right now this is synonymous with using combat points... should it be? +#define SCF_LOOK_FOR_ENEMIES 0x00000800 //NPC be on the lookout for enemies +#define SCF_FACE_MOVE_DIR 0x00001000 //NPC face direction it's moving - FIXME: not really implemented right now +#define SCF_IGNORE_ALERTS 0x00002000 //NPC ignore alert events +#define SCF_DONT_FIRE 0x00004000 //NPC won't shoot +#define SCF_DONT_FLEE 0x00008000 //NPC never flees +#define SCF_FORCED_MARCH 0x00010000 //NPC that the player must aim at to make him walk +#define SCF_NO_GROUPS 0x00020000 //NPC cannot alert groups or be part of a group +#define SCF_FIRE_WEAPON 0x00040000 //NPC will fire his (her) weapon +#define SCF_NO_MIND_TRICK 0x00080000 //Not succeptible to mind tricks +#define SCF_USE_CP_NEAREST 0x00100000 //Will use combat point close to it, not next to player or try and flank player +#define SCF_NO_FORCE 0x00200000 //Not succeptible to force powers +#define SCF_NO_FALLTODEATH 0x00400000 //NPC will not scream and tumble and fall to hit death over large drops +#define SCF_NO_ACROBATICS 0x00800000 //Jedi won't jump, roll or cartwheel +#define SCF_USE_SUBTITLES 0x01000000 //Regardless of subtitle setting, this NPC will display subtitles when it speaks lines +#define SCF_NO_ALERT_TALK 0x02000000 //Will not say alert sounds, but still can be woken up by alerts +#define SCF_NAV_CAN_FLY 0x04000000 //Navigation allows connections through air +#define SCF_FLY_WITH_JET 0x08000000 //Must Fly With A Jet +#define SCF_PILOT 0x10000000 //Can pilot a vehicle +#define SCF_NAV_CAN_JUMP 0x20000000 //Can attempt to jump when blocked +#define SCF_FIRE_WEAPON_NO_ANIM 0x40000000 //Fire weapon but don't play weapon firing anim +#define SCF_SAFE_REMOVE 0x80000000 //Remove NPC when it's safe (when player isn't looking) + + +//#ifdef __DEBUG + +//Debug flag definitions + +#define AID_IDLE 0x00000000 //Nothing is happening +#define AID_ACQUIRED 0x00000001 //A target has been found +#define AID_LOST 0x00000002 //Alert, but no target is in sight +#define AID_CONFUSED 0x00000004 //Is unable to come up with a course of action +#define AID_LOSTPATH 0x00000008 //Cannot make a valid movement due to lack of connections + +//#endif //__DEBUG + +//extern qboolean showWaypoints; + +typedef enum {VIS_UNKNOWN, VIS_NOT, VIS_PVS, VIS_360, VIS_FOV, VIS_SHOOT} visibility_t; +typedef enum {SPOT_ORIGIN, SPOT_CHEST, SPOT_HEAD, SPOT_HEAD_LEAN, SPOT_WEAPON, SPOT_LEGS, SPOT_GROUND} spot_t; + +typedef enum //# lookMode_e +{ + LM_ENT = 0, + LM_INTEREST +} lookMode_t; + +typedef enum //# jumpState_e +{ + JS_WAITING = 0, + JS_FACING, + JS_CROUCHING, + JS_JUMPING, + JS_LANDING +} jumpState_t; + +typedef enum +{ + SEX_NEUTRAL = 0, + SEX_MALE, + SEX_FEMALE, + SEX_SHEMALE//what the Hell, ya never know... +} sexType_t; + +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct gNPCstats_e +{//Stats, loaded in, and can be set by scripts + //AI + int aggression; // " + int aim; // " + float earshot; // " + int evasion; // " + int hfov; // horizontal field of view + int intelligence; // " + int move; // " + int reactions; // 1-5, higher is better + float shootDistance; //Maximum range- overrides range set for weapon if nonzero + int vfov; // vertical field of view + float vigilance; // " + float visrange; // " + //Movement + int runSpeed; + int walkSpeed; + float yawSpeed; // 1 - whatever, default is 50 + int health; + int acceleration; + //sex + sexType_t sex; //male, female, etc. +} gNPCstats_t; + + +#define MAX_ENEMY_POS_LAG 2400 +#define ENEMY_POS_LAG_INTERVAL 100 +#define ENEMY_POS_LAG_STEPS (MAX_ENEMY_POS_LAG/ENEMY_POS_LAG_INTERVAL) + +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct +{ + //FIXME: Put in playerInfo or something + int timeOfDeath; //FIXME do we really need both of these + gentity_t *touchedByPlayer; + + visibility_t enemyLastVisibility; + + int aimTime; + float desiredYaw; + float desiredPitch; + float lockedDesiredYaw; + float lockedDesiredPitch; + gentity_t *aimingBeam; // debugging aid + + vec3_t enemyLastSeenLocation; + int enemyLastSeenTime; + vec3_t enemyLastHeardLocation; + int enemyLastHeardTime; + int lastAlertID; //unique ID + + int eFlags; + int aiFlags; + + int currentAmmo; // this sucks, need to find a better way + int shotTime; + int burstCount; + int burstMin; + int burstMean; + int burstMax; + int burstSpacing; + int attackHold; + int attackHoldTime; + vec3_t shootAngles; //Angles to where bot is shooting - fixme: make he torso turn to reflect these + + //extra character info + rank_t rank; //for pips + + //Behavior state info + bState_t behaviorState; //determines what actions he should be doing + bState_t defaultBehavior;//State bot will default to if none other set + bState_t tempBehavior;//While valid, overrides other behavior + + qboolean ignorePain; //only play pain scripts when take pain + + int duckDebounceTime;//Keeps them ducked for a certain time + int walkDebounceTime; + int enemyCheckDebounceTime; + int investigateDebounceTime; + int investigateCount; + vec3_t investigateGoal; + int investigateSoundDebounceTime; + int greetingDebounceTime;//when we can greet someone next + gentity_t *eventOwner; + + //bState-specific fields + gentity_t *coverTarg; + jumpState_t jumpState; + float followDist; + + // goal, navigation & pathfinding + gentity_t *tempGoal; // used for locational goals (player's last seen/heard position) + gentity_t *goalEntity; + gentity_t *lastGoalEntity; + gentity_t *eventualGoal; + gentity_t *captureGoal; //Where we should try to capture + gentity_t *defendEnt; //Who we're trying to protect + gentity_t *greetEnt; //Who we're greeting + int goalTime; //FIXME: This is never actually used + qboolean straightToGoal; //move straight at navgoals + float distToGoal; + int navTime; + int blockingEntNum; + int blockedSpeechDebounceTime; + + int homeWp; + int avoidSide; + int leaderAvoidSide; + int lastAvoidSteerSide; + int lastAvoidSteerSideDebouncer; + AIGroupInfo_t *group; + int troop; + + vec3_t lastPathAngles; //So we know which way to face generally when we stop + + //stats + gNPCstats_t stats; + int aimErrorDebounceTime; + float lastAimErrorYaw; + float lastAimErrorPitch; + vec3_t aimOfs; + int currentAim; + int currentAggression; + + //scriptflags + int scriptFlags;//in b_local.h + + //moveInfo + int desiredSpeed; + int currentSpeed; + char last_forwardmove; + char last_rightmove; + vec3_t lastClearOrigin; + int shoveCount; + + int blockedDebounceTime; + gentity_t* blockedEntity; // The entity That Causes The Current Blockage + + vec3_t blockedTargetPosition; // Where the actor was trying to get TO before blocked + gentity_t* blockedTargetEntity; // Where the actor was trying to get TO before blocked + + + //jump info + vec3_t jumpDest; // Where The Actor Is Trying To Jump TO + gentity_t* jumpTarget; // What Entity The Actor Is Trying To Jump TO + float jumpMaxXYDist; // The Minimal Delta On The XY Plane Allowed To Jump To The Dest + float jumpMazZDist; + int jumpSide; // Which Side The Last Jump Occured On + int jumpTime; // When The Last Jump Started + int jumpBackupTime; // If Active, Then The Guy Should Backup Before Jumping + int jumpNextCheckTime; // The Minimal Next Time To Check For A Jump + + // + int combatPoint;//NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point + int lastFailedCombatPoint;//NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point + int movementSpeech; //what to say when you first successfully move + float movementSpeechChance;//how likely you are to say it + + //Testing physics at 20fps + int nextBStateThink; + usercmd_t last_ucmd; + + // + //JWEIER ADDITIONS START + + qboolean combatMove; + int goalRadius; + + //FIXME: These may be redundant + + /* + int weaponTime; //Time until refire is valid + int jumpTime; + */ + int pauseTime; //Time to stand still + int standTime; + + int localState; //Tracking information local to entity + int squadState; //Tracking information for team level interaction + + //JWEIER ADDITIONS END + // + + int confusionTime; //Doesn't respond to alerts or pick up enemies (unless shot) until this time is up + int charmedTime; //charmed to enemy team + int controlledTime; //controlled by player + int surrenderTime; //Hands up + int kneelTime; //kneeling (for troopers) + + //Lagging enemy position - FIXME: seems awful wasteful... + vec3_t enemyLaggedPos[ENEMY_POS_LAG_STEPS]; + + gentity_t *watchTarget; //for BS_CINEMATIC, keeps facing this ent + + int ffireCount; //sigh... you'd think I'd be able to find a way to do this without having to use 3 int fields, but... + int ffireDebounce; + int ffireFadeDebounce; +} gNPC_t; + +void G_SquadPathsInit(void); +void NPC_InitGame( void ); +void G_LoadBoltOns( void ); +void Svcmd_NPC_f( void ); +/* +void Bot_InitGame( void ); +void Bot_InitPreSpawn( void ); +void Bot_InitPostSpawn( void ); +void Bot_Shutdown( void ); +void Bot_Think( gentity_t *ent, int msec ); +void Bot_Connect( gentity_t *bot, char *botName ); +void Bot_Begin( gentity_t *bot ); +void Bot_Disconnect( gentity_t *bot ); +void Svcmd_Bot_f( void ); +void Nav_ItemSpawn( gentity_t *ent, int remaining ); +*/ + +// +// This section should be moved to QFILES.H +// +/* +#define NAVFILE_ID (('I')+('N'<<8)+('A'<<16)+('V'<<24)) +#define NAVFILE_VERSION 6 + +typedef struct { + unsigned id; + unsigned version; + unsigned checksum; + unsigned surfaceCount; + unsigned edgeCount; +} navheader_t; + + +#define MAX_SURFACES 4096 + +#define NSF_PUSH 0x00000001 +#define NSF_WATERLEVEL1 0x00000002 +#define NSF_WATERLEVEL2 0x00000004 +#define NSF_WATER_NOAIR 0x00000008 +#define NSF_DUCK 0x00000010 +#define NSF_PAIN 0x00000020 +#define NSF_TELEPORTER 0x00000040 +#define NSF_PLATHIGH 0x00000080 +#define NSF_PLATLOW 0x00000100 +#define NSF_DOOR_FLOOR 0x00000200 +#define NSF_DOOR_SHOOT 0x00000400 +#define NSF_DOOR_BUTTON 0x00000800 +#define NSF_BUTTON 0x00001000 + +typedef struct { + vec3_t origin; + vec2_t absmin; + vec2_t absmax; + int parm; + unsigned flags; + unsigned edgeCount; + unsigned edgeIndex; +} nsurface_t; + + +#define NEF_DUCK 0x00000001 +#define NEF_JUMP 0x00000002 +#define NEF_HOLD 0x00000004 +#define NEF_WALK 0x00000008 +#define NEF_RUN 0x00000010 +#define NEF_NOAIRMOVE 0x00000020 +#define NEF_LEFTGROUND 0x00000040 +#define NEF_PLAT 0x00000080 +#define NEF_FALL1 0x00000100 +#define NEF_FALL2 0x00000200 +#define NEF_DOOR_SHOOT 0x00000400 +#define NEF_DOOR_BUTTON 0x00000800 +#define NEF_BUTTON 0x00001000 + +typedef struct { + vec3_t origin; + vec2_t absmin; // region within this surface that is the portal to the other surface + vec2_t absmax; + int surfaceNum; + unsigned flags; // jump, prerequisite button, will take falling damage, etc... + float cost; + int dirIndex; + vec3_t endSpot; + int parm; +} nedge_t; +*/ +#endif diff --git a/code/game/bg_lib.cpp b/code/game/bg_lib.cpp new file mode 100644 index 0000000..fe09a22 --- /dev/null +++ b/code/game/bg_lib.cpp @@ -0,0 +1,656 @@ +// this include must remain at the top of every bg_xxxx CPP file +#include "common_headers.h" + +//#include "q_shared.h" + +// this file is excluded from release builds because of intrinsics + +size_t strlen( const char *string ) { + const char *s; + + s = string; + while ( *s ) { + s++; + } + return s - string; +} + + +char *strcat( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *s ) { + s++; + } + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + +char *strcpy( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + + +int strcmp( const char *string1, const char *string2 ) { + while ( *string1 == *string2 && *string1 && *string2 ) { + string1++; + string2++; + } + return *string1 - *string2; +} + + +char *strchr( const char *string, int c ) { + do { + if ( *string == c ) { + return ( char * )string; + } + string++; + } while ( *(string-1) ); + return (char *)0; +} + +char *strstr( const char *string, const char *strCharSet ) { + while ( *string ) { + int i; + + for ( i = 0 ; strCharSet[i] ; i++ ) { + if ( string[i] != strCharSet[i] ) { + break; + } + } + if ( !strCharSet[i] ) { + return (char *)string; + } + string++; + } + return (char *)0; +} + +#ifndef _MSC_VER + +int tolower( int c ) { + if ( c >= 'A' && c <= 'Z' ) { + c += 'a' - 'A'; + } + return c; +} + +#endif + +int toupper( int c ) { + if ( c >= 'a' && c <= 'z' ) { + c += 'A' - 'a'; + } + return c; +} + +//#ifndef _MSC_VER + + + +#if 0 + +double floor( double x ) { + return (int)(x + 0x40000000) - 0x40000000; +} + +void *memset( void *dest, int c, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = c; + } + return dest; +} + +void *memcpy( void *dest, const void *src, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = ((char *)src)[count]; + } + return dest; +} + +char *strncpy( char *strDest, const char *strSource, size_t count ) { + char *s; + + s = strDest; + while ( *strSource && count ) { + *s++ = *strSource++; + count--; + } + while ( count-- ) { + *s++ = 0; + } + return strDest; +} + +double sqrt( double x ) { + float y; + float delta; + float maxError; + + if ( x <= 0 ) { + return 0; + } + + // initial guess + y = x / 2; + + // refine + maxError = x * 0.001; + + do { + delta = ( y * y ) - x; + y -= delta / ( 2 * y ); + } while ( delta > maxError || delta < -maxError ); + + return y; +} + + +float sintable[1024] = { +0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, +0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, +0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, +0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, +0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, +0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, +0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, +0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, +0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, +0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, +0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, +0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, +0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, +0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, +0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, +0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, +0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, +0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, +0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, +0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, +0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, +0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, +0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, +0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, +0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, +0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, +0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, +0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, +0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, +0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, +0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, +0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, +0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, +0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, +0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, +0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, +0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, +0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, +0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, +0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, +0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, +0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, +0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, +0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, +0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, +0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, +0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, +0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, +0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, +0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, +0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, +0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, +0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, +0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, +0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, +0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, +0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, +0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, +0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, +0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, +0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, +0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, +0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, +0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, +0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, +0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, +0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, +0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, +0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, +0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, +0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, +0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, +0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, +0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, +0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, +0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, +0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, +0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, +0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, +0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, +0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, +0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, +0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, +0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, +0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, +0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, +0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, +0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, +0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, +0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, +0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, +0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, +0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, +0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, +0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, +0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, +0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, +0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, +0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, +0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, +0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, +0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, +0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, +0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, +0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, +0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, +0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, +0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, +0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, +0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, +0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, +0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, +0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, +0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, +0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, +0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, +0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, +0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, +0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, +0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, +0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, +0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, +0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, +0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, +0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, +0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, +0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, +0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 +}; + +double sin( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 0: + return sintable[index]; + case 1: + return sintable[1023-index]; + case 2: + return -sintable[index]; + case 3: + return -sintable[1023-index]; + } + return 0; +} + + +double cos( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 3: + return sintable[index]; + case 0: + return sintable[1023-index]; + case 1: + return -sintable[index]; + case 2: + return -sintable[1023-index]; + } + return 0; +} + + +double atan2( double y, double x ) { + float base; + float temp; + float dir; + float test; + int i; + + if ( x < 0 ) { + if ( y >= 0 ) { + // quad 1 + base = M_PI / 2; + temp = x; + x = y; + y = -temp; + } else { + // quad 2 + base = M_PI; + x = -x; + y = -y; + } + } else { + if ( y < 0 ) { + // quad 3 + base = 3 * M_PI / 2; + temp = x; + x = -y; + y = temp; + } + } + + if ( y > x ) { + base += M_PI/2; + temp = x; + x = y; + y = temp; + dir = -1; + } else { + dir = 1; + } + + // calcualte angle in octant 0 + if ( x == 0 ) { + return base; + } + y /= x; + + for ( i = 0 ; i < 512 ; i++ ) { + test = sintable[i] / sintable[1023-i]; + if ( test > y ) { + break; + } + } + + return base + dir * i * ( M_PI/2048); +} + + +#endif + +double tan( double x ) { + return sin(x) / cos(x); +} + + +static int randSeed = 0; + +int rand( void ) { + randSeed = (69069 * randSeed + 1); + return randSeed & 0x7fff; +} + +double atof( const char *string ) { + double sign; + double value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // check for decimal point + if ( c == '.' ) { + double fraction; + + string++; + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + + return value * sign; +} + +#ifndef _MSC_VER + +int atoi( const char *string ) { + int sign; + int value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + return value * sign; +} + +#endif + +int abs( int n ) { + return n < 0 ? -n : n; +} + +double fabs( double x ) { + return x < 0 ? -x : x; +} + + + +//========================================================= + + +void AddInt( char **buf_p, int val, int width ) { + char text[32]; + int digits; + int signedVal; + char *buf; + + digits = 0; + signedVal = val; + if ( val < 0 ) { + val = -val; + } + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + while ( digits < width ) { + *buf++ = ' '; + width--; + } + + while ( digits-- ) { + *buf++ = text[digits]; + } + + *buf_p = buf; +} + +void AddFloat( char **buf_p, float fval, int width ) { + char text[32]; + int digits; + float signedVal; + char *buf; + int val; + + // FIXME!!!! handle fractions + + digits = 0; + signedVal = fval; + if ( fval < 0 ) { + fval = -fval; + } + + val = (int)fval; + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + while ( digits < width ) { + *buf++ = ' '; + width--; + } + + while ( digits-- ) { + *buf++ = text[digits]; + } + + *buf_p = buf; +} + +int vsprintf( char *buffer, const char *fmt, va_list argptr ) { + char *buf_p; + int cmd; + int *arg; + char *s; + int width; + + buf_p = buffer; + arg = (int *)argptr; + + while ( *fmt ) { + if ( fmt[0] != '%' ) { + *buf_p++ = *fmt++; + continue; + } + + cmd = fmt[1]; + if ( cmd >= '0' && cmd <= '9' ) { + width = cmd - '0'; + cmd = fmt[2]; + fmt++; + } else { + width = 0; + } + fmt += 2; + + switch ( cmd ) { + case 'i': + case 'd': + case 'u': + AddInt( &buf_p, *arg, width ); + break; + + case 'f': + AddFloat( &buf_p, *(float *)arg, width ); + break; + + case 's': + s = (char *)*arg; + if ( !s ) { + s = ""; + } + while ( *s ) { + *buf_p++ = *s++; + } + break; + } + arg++; + } + + *buf_p = 0; + return buf_p - buffer; +} + diff --git a/code/game/bg_local.h b/code/game/bg_local.h new file mode 100644 index 0000000..d11d598 --- /dev/null +++ b/code/game/bg_local.h @@ -0,0 +1,56 @@ +// bg_local.h -- local definitions for the bg (both games) files + +#define TIMER_LAND 130 +#define TIMER_GESTURE (34*66+50) + +#define OVERCLIP 1.001F + +// 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 +typedef struct { + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +extern pmove_t *pm; +extern pml_t pml; + +// movement parameters +extern const float pm_stopspeed; +extern const float pm_duckScale; +extern const float pm_swimScale; +extern const float pm_wadeScale; + +extern const float pm_accelerate; +extern const float pm_airaccelerate; +extern const float pm_wateraccelerate; +extern const float pm_flyaccelerate; + +extern const float pm_friction; +extern const float pm_waterfriction; +extern const float pm_flightfriction; + +extern int c_pmove; + +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); + +qboolean PM_SlideMove( float gravity ); +void PM_StepSlideMove( float gravity ); + + + diff --git a/code/game/bg_misc.cpp b/code/game/bg_misc.cpp new file mode 100644 index 0000000..1f1ab43 --- /dev/null +++ b/code/game/bg_misc.cpp @@ -0,0 +1,741 @@ +// this include must remain at the top of every bg_xxxx CPP file +#include "common_headers.h" + +// included in both game dll and client + +//#include "q_shared.h" +#include "g_local.h" +#include "bg_public.h" +#include "g_items.h" +#include "g_vehicles.h" + + +extern weaponData_t weaponData[WP_NUM_WEAPONS]; +extern ammoData_t ammoData[AMMO_MAX]; + + +#define PICKUPSOUND "sound/weapons/w_pkup.wav" + +/*QUAKED weapon_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended +DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION for weapons, ammo, and item pickups. +The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface. +The NOPLAYER flag makes it so player cannot pick it up. +The ALLOWNPC flag allows only NPCs to pick it up, too + +If an item is the target of another entity, it will spawn as normal, use INVISIBLE to hide it. + +An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. + +"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. +"random" random number of plus or minus seconds varied from the respawn time +"count" override quantity or duration on most items. +"team" only this team can pick it up + "player" + "neutral" + "enemy" +*/ + +/*QUAKED weapon_stun_baton (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/stun_baton/baton.md3" +*/ +/*QUAKED weapon_saber (.3 .3 1) (-16 -16 -8) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE NOGLOW +model="/models/weapons2/saber/saber_w.md3" +When picked up, will be used as a second saber unless: + 1) It's a two-handed saber + 2) The old saber was two-handed + OR + 3) You set "saberSolo" to "1" + +saberType - entry name from sabers.cfg - which kind of saber this is - use "player" to make it so that the saber will be whatever saber the player is configured to use +saberColor - red, orange, yellow, green, blue, and purple +saberSolo - set to "1" and this will be the only saber the person who picks this up will be holding +*/ +/*QUAKED weapon_bryar_pistol (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/briar_pistol/briar_pistol.md3" +*/ +/*QUAKED weapon_blaster_pistol (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/blaster_pistol/blaster_pistol.md3" +*/ +/*QUAKED weapon_blaster (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/blaster_r/blaster.md3" +*/ +/*QUAKED weapon_disruptor (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/disruptor/disruptor.md3" +*/ +/*QUAKED weapon_bowcaster (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/bowcaster/bowcaster.md3" +*/ +/*QUAKED weapon_repeater (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/heavy_repeater/heavy_repeater.md3" +*/ +/*QUAKED weapon_demp2 (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/demp2/demp2.md3" +*/ +/*QUAKED weapon_flechette (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/golan_arms/golan_arms.md3" +*/ +/*QUAKED weapon_concussion_rifle (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/c_rifle/c_rifle.md3" +*/ +/*QUAKED weapon_rocket_launcher (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/merr_sonn/merr_sonn.md3" +*/ +/*QUAKED weapon_thermal (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/thermal/thermal.md3" +*/ +/*QUAKED weapon_trip_mine (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/laser_trap/laser_trap.md3" +*/ +/*QUAKED weapon_det_pack (.3 .3 1) (-16 -16 -2) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +model="/models/weapons2/detpack/det_pack.md3" +*/ + +/*QUAKED item_seeker (.3 .3 1) (-8 -8 -4) (8 8 16) suspended +30 seconds of seeker drone +*/ +/*QUAKED item_bacta (.3 .3 1) (-8 -8 0) (8 8 16) suspended +model="/models/items/bacta.md3" +*/ +/*QUAKED item_datapad (.3 .3 1) (-8 -8 0) (8 8 16) suspended +model="/models/items/datapad.md3" +*/ +/*QUAKED item_binoculars (.3 .3 1) (-8 -8 0) (8 8 16) suspended +model="/models/items/binoculars.md3" +*/ +/*QUAKED item_sentry_gun (.3 .3 1) (-8 -8 0) (8 8 16) suspended +*/ +/*QUAKED item_la_goggles (.3 .3 1) (-8 -8 0) (8 8 16) suspended +*/ +/*QUAKED ammo_force (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +Ammo for the force. +*/ +/*QUAKED ammo_blaster (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +Ammo for the Bryar and Blaster pistols. +*/ +/*QUAKED ammo_powercell (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +Ammo for Tenloss Disruptor, Wookie Bowcaster, and the Destructive Electro Magnetic Pulse (demp2 ) guns +*/ +/*QUAKED ammo_metallic_bolts (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +Ammo for Imperial Heavy Repeater and the Golan Arms Flechette +*/ +/*QUAKED ammo_rockets (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +Ammo for Merr-Sonn portable missile launcher +*/ +/*QUAKED ammo_thermal (.3 .5 1) (-16 -16 -0) (16 16 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +Belt of thermal detonators +*/ +/*QUAKED ammo_tripmine (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +3 pack of tripmines +*/ +/*QUAKED ammo_detpack (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +3 pack of detpacks +*/ + +/*QUAKED item_medpak_instant (.3 .3 1) (-8 -8 -4) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +*/ + +/*QUAKED item_shield_sm_instant (.3 .3 1) (-8 -8 -4) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +*/ + +/*QUAKED item_shield_lrg_instant (.3 .3 1) (-8 -8 -4) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID VERTICAL INVISIBLE +*/ +/*QUAKED item_goodie_key (.3 .3 1) (-8 -8 0) (8 8 16) suspended +*/ +/*QUAKED item_security_key (.3 .3 1) (-8 -8 0) (8 8 16) suspended +message - used to differentiate one key from another. +*/ +/*QUAKED item_battery (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +model="/models/items/battery.md3" +battery pickup item +*/ + +/*QUAKED holocron_force_heal (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +force heal pickup item + +"count" level of force power this holocron gives activator ( range: 0-3, default 1) +*/ + +/*QUAKED holocron_force_levitation (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +force levitation pickup item + +"count" level of force power this holocron gives activator ( range: 0-3, default 1) +*/ + +/*QUAKED holocron_force_speed (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +force speed pickup item + +"count" level of force power this holocron gives activator ( range: 0-3, default 1) +*/ + +/*QUAKED holocron_force_push (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +force push pickup item + +"count" level of force power this holocron gives activator ( range: 0-3, default 1) +*/ + +/*QUAKED holocron_force_pull (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +force pull pickup item + +"count" level of force power this holocron gives activator ( range: 0-3, default 1) +*/ + +/*QUAKED holocron_force_telepathy (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +force telepathy pickup item + +"count" level of force power this holocron gives activator ( range: 0-3, default 1) +*/ + +/*QUAKED holocron_force_grip (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +force grip pickup item + +"count" level of force power this holocron gives activator ( range: 0-3, default 1) +*/ + +/*QUAKED holocron_force_lightining (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +force lighting pickup item + +"count" level of force power this holocron gives activator ( range: 0-3, default 1) +*/ + +/*QUAKED holocron_force_saberthrow (.3 .5 1) (-8 -8 -0) (8 8 16) SUSPEND NOPLAYER ALLOWNPC NOTSOLID +force saberthrow pickup item + +"count" level of force power this holocron gives activator ( range: 0-3, default 1) +*/ + +gitem_t bg_itemlist[ITM_NUM_ITEMS+1];//need a null on the end + +//int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) ; +const int bg_numItems = ITM_NUM_ITEMS; + + + +/* +=============== +FindItemForWeapon + +=============== +*/ +gitem_t *FindItemForWeapon( weapon_t weapon ) { + int i; + + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( bg_itemlist[i].giType == IT_WEAPON && bg_itemlist[i].giTag == weapon ) { + return &bg_itemlist[i]; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon); + return NULL; +} + +//---------------------------------------------- +gitem_t *FindItemForInventory( int inv ) +{ + int i; + gitem_t *it; + + // Now just check for any other kind of item. + for ( i = 1 ; i < bg_numItems ; i++ ) + { + it = &bg_itemlist[i]; + + if ( it->giType == IT_HOLDABLE ) + { + if ( it->giTag == inv ) + { + return it; + } + } + } + + Com_Error( ERR_DROP, "Couldn't find item for inventory %i", inv ); + return NULL; +} + +/* +=============== +FindItemForWeapon + +=============== +*/ +gitem_t *FindItemForAmmo( ammo_t ammo ) +{ + int i; + + for ( i = 1 ; i < bg_numItems ; i++ ) + { + if ( bg_itemlist[i].giType == IT_AMMO && bg_itemlist[i].giTag == ammo ) + { + return &bg_itemlist[i]; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for ammo %i", ammo ); + return NULL; +} + +/* +=============== +FindItem + +=============== +*/ +gitem_t *FindItem( const char *className ) { + int i; + + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( !Q_stricmp( bg_itemlist[i].classname, className ) ) + return &bg_itemlist[i]; + } + + return NULL; +} + + +/* +================ +BG_CanItemBeGrabbed + +Returns false if the item should not be picked up. +This needs to be the same for client side prediction and server use. +================ +*/ +qboolean BG_CanItemBeGrabbed( const entityState_t *ent, const playerState_t *ps ) { + gitem_t *item; + + if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) { + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" ); + } + + item = &bg_itemlist[ent->modelindex]; + + switch( item->giType ) { + + case IT_WEAPON: + // See if we already have this weapon. + if ( !(ps->stats[ STAT_WEAPONS ] & ( 1 << item->giTag ))) + { + // Don't have this weapon yet, so pick it up. + return qtrue; + } + else if ( item->giTag == WP_SABER ) + {//always pick up a saber, might be a new one? + return qtrue; + } + + // Make sure that we aren't already full on ammo for this weapon + if ( ps->ammo[weaponData[item->giTag].ammoIndex] >= ammoData[weaponData[item->giTag].ammoIndex].max ) + { + // full, so don't grab the item + return qfalse; + } + + return qtrue; // could use more of this type of ammo, so grab the item + + case IT_AMMO: + + if (item->giTag != AMMO_FORCE) + { + // since the ammo is the weapon in this case, picking up ammo should actually give you the weapon + switch( item->giTag ) + { + case AMMO_THERMAL: + if( !(ps->stats[STAT_WEAPONS] & ( 1 << WP_THERMAL ) ) ) + { + return qtrue; + } + break; + case AMMO_DETPACK: + if( !(ps->stats[STAT_WEAPONS] & ( 1 << WP_DET_PACK ) ) ) + { + return qtrue; + } + break; + case AMMO_TRIPMINE: + if( !(ps->stats[STAT_WEAPONS] & ( 1 << WP_TRIP_MINE ) ) ) + { + return qtrue; + } + break; + } + + if ( ps->ammo[ item->giTag ] >= ammoData[item->giTag].max ) // checkme + { + return qfalse; // can't hold any more + } + } + else + { + if (ps->forcePower >= ammoData[item->giTag].max*2) + { + return qfalse; // can't hold any more + } + + } + + return qtrue; + + case IT_ARMOR: + // we also clamp armor to the maxhealth for handicapping + if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH] ) { + return qfalse; + } + return qtrue; + + case IT_HEALTH: + if ((ps->forcePowersActive & (1 << FP_RAGE))) + {//ragers can't use health + return qfalse; + } + // don't pick up if already at max + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { + return qfalse; + } + return qtrue; + + case IT_BATTERY: + // don't pick up if already at max + if ( ps->batteryCharge >= MAX_BATTERIES ) + { + return qfalse; + } + return qtrue; + + case IT_HOLOCRON: + // pretty lame but for now you can always pick these up + return qtrue; + + + case IT_HOLDABLE: + if ( item->giTag >= INV_ELECTROBINOCULARS && item->giTag <= INV_SENTRY ) + { + // hardcoded--can only pick up five of any holdable + if ( ps->inventory[item->giTag] >= 5 ) + { + return qfalse; + } + } + return qtrue; + } + + return qfalse; +} + +//====================================================================== + +/* +================ +EvaluateTrajectory + +================ +*/ +void EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorCopy( tr->trBase, result ); + break; + case TR_LINEAR: + deltaTime = ( atTime - tr->trTime ) * 0.001F; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = (float)sin( deltaTime * M_PI * 2 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) + { + atTime = tr->trTime + tr->trDuration; + } + //old totally linear + deltaTime = ( atTime - tr->trTime ) * 0.001F; // milliseconds to seconds + if ( deltaTime < 0 ) + {//going past the total duration + deltaTime = 0; + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_NONLINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) + { + atTime = tr->trTime + tr->trDuration; + } + //new slow-down at end + if ( atTime - tr->trTime > tr->trDuration || atTime - tr->trTime <= 0 ) + { + deltaTime = 0; + } + else + {//FIXME: maybe scale this somehow? So that it starts out faster and stops faster? + deltaTime = tr->trDuration*0.001f*((float)cos( DEG2RAD(90.0f - (90.0f*((float)atTime-tr->trTime)/(float)tr->trDuration)) )); + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001F; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5F * g_gravity->value * deltaTime * deltaTime;//DEFAULT_GRAVITY + break; + default: + Com_Error( ERR_DROP, "EvaluateTrajectory: unknown trType: %i", tr->trTime ); + break; + } +} + +/* +================ +EvaluateTrajectoryDelta + +Returns current speed at given time +================ +*/ +void EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = (float)cos( deltaTime * M_PI * 2 ); // derivative of sin = cos + phase *= 0.5; + VectorScale( tr->trDelta, phase, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) + { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + case TR_NONLINEAR_STOP: + if ( atTime - tr->trTime > tr->trDuration || atTime - tr->trTime <= 0 ) + { + VectorClear( result ); + return; + } + deltaTime = tr->trDuration*0.001f*((float)cos( DEG2RAD(90.0f - (90.0f*((float)atTime-tr->trTime)/(float)tr->trDuration)) )); + VectorScale( tr->trDelta, deltaTime, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001F; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= g_gravity->value * deltaTime; // DEFAULT_GRAVITY + break; + default: + Com_Error( ERR_DROP, "EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +/* +=============== +AddEventToPlayerstate + +Handles the sequence numbers +=============== +*/ +void AddEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { + ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; + ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; + ps->eventSequence++; +} + + +/* +=============== +CurrentPlayerstateEvent + +=============== +*/ +int CurrentPlayerstateEvent( playerState_t *ps ) { + return ps->events[ (ps->eventSequence-1) & (MAX_PS_EVENTS-1) ]; +} + +/* +======================== +PlayerStateToEntityState + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void PlayerStateToEntityState( playerState_t *ps, entityState_t *s ) +{ + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) + { + s->eType = ET_INVISIBLE; + } + /*else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) + { + s->eType = ET_INVISIBLE; + } */ + else + { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_INTERPOLATE; + VectorCopy( ps->origin, s->pos.trBase ); + //SnapVector( s->pos.trBase ); + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + //SnapVector( s->apos.trBase ); + + s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + + // new sabre stuff + s->saberActive = ps->SaberActive();//WHY is this on the entityState_t, too??? + s->saberInFlight = ps->saberInFlight; + + // NOTE: Although we store this stuff locally on a vehicle, who's to say we + // can't bring back these variables and fill them at the appropriate time? -Aurelio + // We need to bring these in from the vehicle NPC. + if ( g_entities[ps->clientNum].client && g_entities[ps->clientNum].client->NPC_class == CLASS_VEHICLE && g_entities[ps->clientNum].NPC ) + { + Vehicle_t *pVeh = g_entities[ps->clientNum].m_pVehicle; + s->vehicleArmor = pVeh->m_iArmor; + VectorCopy( pVeh->m_vOrientation, s->vehicleAngles ); + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } +#if 0 + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else { + int seq; + + seq = (ps->eventSequence-1) & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->eventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + } + + // show some roll in the body based on velocity and angle + if ( ps->stats[STAT_HEALTH] > 0 ) { + vec3_t right; + float sign; + float side; + float value; + + AngleVectors( ps->viewangles, NULL, right, NULL ); + + side = DotProduct (ps->velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs(side); + + value = 2; // g_rollangle->value; + + if (side < 200 /* g_rollspeed->value */ ) + side = side * value / 200; // g_rollspeed->value; + else + side = value; + + s->angles[ROLL] = (int)(side*sign * 4); + } +#endif +} + + +/* +============ +BG_PlayerTouchesItem + +Items can be picked up without actually touching their physical bounds +============ +*/ +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) { + vec3_t origin; + + EvaluateTrajectory( &item->pos, atTime, origin ); + + // we are ignoring ducked differences here + if ( ps->origin[0] - origin[0] > 44 + || ps->origin[0] - origin[0] < -50 + || ps->origin[1] - origin[1] > 36 + || ps->origin[1] - origin[1] < -36 + || ps->origin[2] - origin[2] > 36 + || ps->origin[2] - origin[2] < -36 ) { + return qfalse; + } + + return qtrue; +} + + +/* +================= +BG_EmplacedView + +Shared code for emplaced angle gun constriction +================= +*/ +int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint) +{ + float dif = AngleSubtract(baseAngles[YAW], angles[YAW]); + + if (dif > constraint || + dif < -constraint) + { + float amt; + + if (dif > constraint) + { + amt = (dif-constraint); + dif = constraint; + } + else if (dif < -constraint) + { + amt = (dif+constraint); + dif = -constraint; + } + else + { + amt = 0.0f; + } + + *newYaw = AngleSubtract(angles[YAW], -dif); + + if (amt > 1.0f || amt < -1.0f) + { //significant, force the view + return 2; + } + else + { //just a little out of range + return 1; + } + } + + return 0; +} diff --git a/code/game/bg_pangles.cpp b/code/game/bg_pangles.cpp new file mode 100644 index 0000000..da6b681 --- /dev/null +++ b/code/game/bg_pangles.cpp @@ -0,0 +1,1851 @@ +// this include must remain at the top of every bg_xxxx CPP file +#include "common_headers.h" + +// define GAME_INCLUDE so that g_public.h does not define the +// short, server-visible gclient_t and gentity_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "q_shared.h" +#include "g_shared.h" +#include "bg_local.h" +#include "anims.h" +#include "wp_saber.h" +#include "g_vehicles.h" +#include "../ghoul2/ghoul2_gore.h" + +extern void CG_SetClientViewAngles( vec3_t angles, qboolean overrideViewEnt ); +extern qboolean PM_InAnimForSaberMove( int anim, int saberMove ); +extern qboolean PM_InForceGetUp( playerState_t *ps ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean PM_InReboundJump( int anim ); +extern qboolean PM_StabDownAnim( int anim ); +extern qboolean PM_DodgeAnim( int anim ); +extern qboolean PM_DodgeHoldAnim( int anim ); +extern qboolean PM_InReboundHold( int anim ); +extern qboolean PM_InKnockDownNoGetup( playerState_t *ps ); +extern qboolean PM_InGetUpNoRoll( playerState_t *ps ); +extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent ); +extern void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +extern qboolean G_ControlledByPlayer( gentity_t *self ); + +extern qboolean cg_usingInFrontOf; +extern qboolean player_locked; +extern pmove_t *pm; +extern pml_t pml; + +extern cvar_t *g_debugMelee; + + +void BG_IK_MoveLimb( CGhoul2Info_v &ghoul2, int boltIndex, char *animBone, char *firstBone, char *secondBone, + int time, entityState_t *ent, int animFileIndex, int basePose, + vec3_t desiredPos, qboolean *ikInProgress, vec3_t origin, + vec3_t angles, vec3_t scale, int blendTime, qboolean forceHalt ) +{ + mdxaBone_t holdPointMatrix; + vec3_t holdPoint; + vec3_t torg; + float distToDest; + animation_t *anim = &level.knownAnimFileSets[animFileIndex].animations[basePose]; + + assert( ghoul2.size() ); + + assert( anim->firstFrame > 0 );//FIXME: umm...? + + if ( !*ikInProgress && !forceHalt ) + { + sharedSetBoneIKStateParams_t ikP; + + //restrict the shoulder joint + //VectorSet(ikP.pcjMins,-50.0f,-80.0f,-15.0f); + //VectorSet(ikP.pcjMaxs,15.0f,40.0f,15.0f); + + //for now, leaving it unrestricted, but restricting elbow joint. + //This lets us break the arm however we want in order to fling people + //in throws, and doesn't look bad. + VectorSet( ikP.pcjMins, 0, 0, 0 ); + VectorSet( ikP.pcjMaxs, 0, 0, 0 ); + + //give the info on our entity. + ikP.blendTime = blendTime; + VectorCopy( origin, ikP.origin ); + VectorCopy( angles, ikP.angles ); + ikP.angles[PITCH] = 0; + ikP.pcjOverrides = 0; + ikP.radius = 10.0f; + VectorCopy( scale, ikP.scale ); + + //base pose frames for the limb + ikP.startFrame = anim->firstFrame + anim->numFrames; + ikP.endFrame = anim->firstFrame + anim->numFrames; + + //ikP.forceAnimOnBone = qfalse; //let it use existing anim if it's the same as this one. + + //we want to call with a null bone name first. This will init all of the + //ik system stuff on the g2 instance, because we need ragdoll effectors + //in order for our pcj's to know how to angle properly. + if ( !gi.G2API_SetBoneIKState( ghoul2, time, NULL, IKS_DYNAMIC, &ikP ) ) + { + assert( !"Failed to init IK system for g2 instance!" ); + } + + //Now, create our IK bone state. + if ( gi.G2API_SetBoneIKState( ghoul2, time, "lower_lumbar", IKS_DYNAMIC, &ikP ) ) + { + //restrict the elbow joint + VectorSet( ikP.pcjMins, -90.0f, -20.0f, -20.0f ); + VectorSet( ikP.pcjMaxs, 30.0f, 20.0f, -20.0f ); + if ( gi.G2API_SetBoneIKState( ghoul2, time, "upper_lumbar", IKS_DYNAMIC, &ikP ) ) + { + //restrict the elbow joint + VectorSet( ikP.pcjMins, -90.0f, -20.0f, -20.0f ); + VectorSet( ikP.pcjMaxs, 30.0f, 20.0f, -20.0f ); + if ( gi.G2API_SetBoneIKState( ghoul2, time, "thoracic", IKS_DYNAMIC, &ikP ) ) + { + //restrict the elbow joint + VectorSet( ikP.pcjMins, -90.0f, -20.0f, -20.0f ); + VectorSet( ikP.pcjMaxs, 30.0f, 20.0f, -20.0f ); + if ( gi.G2API_SetBoneIKState( ghoul2, time, secondBone, IKS_DYNAMIC, &ikP ) ) + { + //restrict the elbow joint + VectorSet( ikP.pcjMins, -90.0f, -20.0f, -20.0f ); + VectorSet( ikP.pcjMaxs, 30.0f, 20.0f, -20.0f ); + + if ( gi.G2API_SetBoneIKState( ghoul2, time, firstBone, IKS_DYNAMIC, &ikP ) ) + { //everything went alright. + *ikInProgress = qtrue; + } + } + } + } + } + } + + if ( *ikInProgress && !forceHalt ) + { //actively update our ik state. + sharedIKMoveParams_t ikM; + CRagDollUpdateParams tuParms; + vec3_t tAngles; + + //set the argument struct up + VectorCopy( desiredPos, ikM.desiredOrigin ); //we want the bone to move here.. if possible + + VectorCopy( angles, tAngles ); + tAngles[PITCH] = tAngles[ROLL] = 0; + + gi.G2API_GetBoltMatrix( ghoul2, 0, boltIndex, &holdPointMatrix, tAngles, origin, time, 0, scale ); + //Get the point position from the matrix. + holdPoint[0] = holdPointMatrix.matrix[0][3]; + holdPoint[1] = holdPointMatrix.matrix[1][3]; + holdPoint[2] = holdPointMatrix.matrix[2][3]; + + VectorSubtract( holdPoint, desiredPos, torg ); + distToDest = VectorLength( torg ); + + //closer we are, more we want to keep updated. + //if we're far away we don't want to be too fast or we'll start twitching all over. + if ( distToDest < 2 ) + { //however if we're this close we want very precise movement + ikM.movementSpeed = 0.4f; + } + else if ( distToDest < 16 ) + { + ikM.movementSpeed = 0.9f;//8.0f; + } + else if ( distToDest < 32 ) + { + ikM.movementSpeed = 0.8f;//4.0f; + } + else if ( distToDest < 64 ) + { + ikM.movementSpeed = 0.7f;//2.0f; + } + else + { + ikM.movementSpeed = 0.6f; + } + VectorCopy( origin, ikM.origin ); //our position in the world. + + ikM.boneName[0] = 0; + if ( gi.G2API_IKMove( ghoul2, time, &ikM ) ) + { + //now do the standard model animate stuff with ragdoll update params. + VectorCopy( angles, tuParms.angles ); + tuParms.angles[PITCH] = 0; + + VectorCopy( origin, tuParms.position ); + VectorCopy( scale, tuParms.scale ); + + tuParms.me = ent->number; + VectorClear( tuParms.velocity ); + + gi.G2API_AnimateG2Models( ghoul2, time, &tuParms ); + } + else + { + *ikInProgress = qfalse; + } + } + else if ( *ikInProgress ) + { //kill it + float cFrame, animSpeed; + int sFrame, eFrame, flags; + + gi.G2API_SetBoneIKState( ghoul2, time, "lower_lumbar", IKS_NONE, NULL ); + gi.G2API_SetBoneIKState( ghoul2, time, "upper_lumbar", IKS_NONE, NULL ); + gi.G2API_SetBoneIKState( ghoul2, time, "thoracic", IKS_NONE, NULL ); + gi.G2API_SetBoneIKState( ghoul2, time, secondBone, IKS_NONE, NULL ); + gi.G2API_SetBoneIKState( ghoul2, time, firstBone, IKS_NONE, NULL ); + + //then reset the angles/anims on these PCJs + gi.G2API_SetBoneAngles( &ghoul2[0], "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, time ); + gi.G2API_SetBoneAngles( &ghoul2[0], "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, time ); + gi.G2API_SetBoneAngles( &ghoul2[0], "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, time ); + gi.G2API_SetBoneAngles( &ghoul2[0], secondBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, time ); + gi.G2API_SetBoneAngles( &ghoul2[0], firstBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, time ); + + //Get the anim/frames that the pelvis is on exactly, and match the left arm back up with them again. + gi.G2API_GetBoneAnim( &ghoul2[0], animBone, (const int)time, &cFrame, &sFrame, &eFrame, &flags, &animSpeed, 0 ); + gi.G2API_SetBoneAnim( &ghoul2[0], "lower_lumbar", sFrame, eFrame, flags, animSpeed, time, sFrame, 300 ); + gi.G2API_SetBoneAnim( &ghoul2[0], "upper_lumbar", sFrame, eFrame, flags, animSpeed, time, sFrame, 300 ); + gi.G2API_SetBoneAnim( &ghoul2[0], "thoracic", sFrame, eFrame, flags, animSpeed, time, sFrame, 300 ); + gi.G2API_SetBoneAnim( &ghoul2[0], secondBone, sFrame, eFrame, flags, animSpeed, time, sFrame, 300 ); + gi.G2API_SetBoneAnim( &ghoul2[0], firstBone, sFrame, eFrame, flags, animSpeed, time, sFrame, 300 ); + + //And finally, get rid of all the ik state effector data by calling with null bone name (similar to how we init it). + gi.G2API_SetBoneIKState( ghoul2, time, NULL, IKS_NONE, NULL ); + + *ikInProgress = qfalse; + } +} + +void PM_IKUpdate( gentity_t *ent ) +{ + //The bone we're holding them by and the next bone after that + char *animBone = "lower_lumbar"; + char *firstBone = "lradius"; + char *secondBone = "lhumerus"; + char *defaultBoltName = "*r_hand"; + + if ( !ent->client ) + { + return; + } + if ( ent->client->ps.heldByClient <= ENTITYNUM_WORLD ) + { //then put our arm in this client's hand + gentity_t *holder = &g_entities[ent->client->ps.heldByClient]; + + if ( holder && holder->inuse && holder->client && holder->ghoul2.size()) + { + if ( !ent->client->ps.heldByBolt ) + {//bolt wan't set + ent->client->ps.heldByBolt = gi.G2API_AddBolt( &holder->ghoul2[0], defaultBoltName ); + } + } + else + { //they're gone, stop holding me + ent->client->ps.heldByClient = 0; + return; + } + + if ( ent->client->ps.heldByBolt ) + { + mdxaBone_t boltMatrix; + vec3_t boltOrg; + vec3_t tAngles; + + VectorCopy( holder->client->ps.viewangles, tAngles ); + tAngles[PITCH] = tAngles[ROLL] = 0; + + gi.G2API_GetBoltMatrix( holder->ghoul2, 0, ent->client->ps.heldByBolt, &boltMatrix, tAngles, holder->client->ps.origin, level.time, 0, holder->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, boltOrg ); + + int grabbedByBolt = gi.G2API_AddBolt( &ent->ghoul2[0], firstBone ); + if ( grabbedByBolt ) + { + //point the limb + BG_IK_MoveLimb( ent->ghoul2, grabbedByBolt, animBone, firstBone, secondBone, + level.time, &ent->s, ent->client->clientInfo.animFileIndex, + ent->client->ps.torsoAnim/*BOTH_DEAD1*/, boltOrg, &ent->client->ps.ikStatus, + ent->client->ps.origin, ent->client->ps.viewangles, ent->s.modelScale, + 500, qfalse ); + + //now see if we need to be turned and/or pulled + vec3_t grabDiff, grabbedByOrg; + + VectorCopy( ent->client->ps.viewangles, tAngles ); + tAngles[PITCH] = tAngles[ROLL] = 0; + + gi.G2API_GetBoltMatrix( ent->ghoul2, 0, grabbedByBolt, &boltMatrix, tAngles, ent->client->ps.origin, level.time, 0, ent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, grabbedByOrg ); + + //check for turn + vec3_t org2Targ, org2Bolt; + VectorSubtract( boltOrg, ent->currentOrigin, org2Targ ); + float org2TargYaw = vectoyaw( org2Targ ); + VectorSubtract( grabbedByOrg, ent->currentOrigin, org2Bolt ); + float org2BoltYaw = vectoyaw( org2Bolt ); + if ( org2TargYaw-1.0f > org2BoltYaw ) + { + ent->currentAngles[YAW]++; + G_SetAngles( ent, ent->currentAngles ); + } + else if ( org2TargYaw+1.0f < org2BoltYaw ) + { + ent->currentAngles[YAW]--; + G_SetAngles( ent, ent->currentAngles ); + } + + //check for pull + VectorSubtract( boltOrg, grabbedByOrg, grabDiff ); + if ( VectorLength( grabDiff ) > 128.0f ) + {//too far, release me + ent->client->ps.heldByClient = holder->client->ps.heldClient = ENTITYNUM_NONE; + } + else if ( 1 ) + {//pull me along + trace_t trace; + vec3_t destOrg; + VectorAdd( ent->currentOrigin, grabDiff, destOrg ); + gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, destOrg, ent->s.number, (ent->clipmask&~holder->contents) ); + G_SetOrigin( ent, trace.endpos ); + //FIXME: better yet: do an actual slidemove to the new pos? + //FIXME: if I'm alive, just tell me to walk some? + } + //FIXME: if I need to turn to keep my bone facing him, do so... + } + //don't let us fall? + VectorClear( ent->client->ps.velocity ); + //FIXME: also make the holder point his holding limb at you? + } + } + else if ( ent->client->ps.ikStatus ) + { //make sure we aren't IKing if we don't have anyone to hold onto us. + if ( ent && ent->inuse && ent->client && ent->ghoul2.size() ) + { + if ( !ent->client->ps.heldByBolt ) + { + ent->client->ps.heldByBolt = gi.G2API_AddBolt( &ent->ghoul2[0], defaultBoltName ); + } + } + else + { //This shouldn't happen, but just in case it does, we'll have a failsafe. + ent->client->ps.heldByBolt = 0; + ent->client->ps.ikStatus = qfalse; + } + + if ( ent->client->ps.heldByBolt ) + { + BG_IK_MoveLimb( ent->ghoul2, ent->client->ps.heldByBolt, animBone, firstBone, secondBone, + level.time, &ent->s, ent->client->clientInfo.animFileIndex, + ent->client->ps.torsoAnim/*BOTH_DEAD1*/, (float *)vec3_origin, + &ent->client->ps.ikStatus, ent->client->ps.origin, + ent->client->ps.viewangles, ent->s.modelScale, 500, qtrue ); + } + } +} + + +void BG_G2SetBoneAngles( centity_t *cent, gentity_t *gent, int boneIndex, const vec3_t angles, const int flags, + const Eorientations up, const Eorientations right, const Eorientations forward, qhandle_t *modelList ) +{ + if (boneIndex!=-1) + { + gi.G2API_SetBoneAnglesIndex( ¢->gent->ghoul2[0], boneIndex, angles, flags, up, right, forward, modelList ); + } +} + +#define MAX_YAWSPEED_X_WING 1 +#define MAX_PITCHSPEED_X_WING 1 +void PM_ScaleUcmd( playerState_t *ps, usercmd_t *cmd, gentity_t *gent ) +{ + if ( G_IsRidingVehicle( gent ) ) + {//driving a vehicle + //clamp the turn rate + int maxPitchSpeed = MAX_PITCHSPEED_X_WING;//switch, eventually? Or read from file? + int diff = AngleNormalize180(SHORT2ANGLE((cmd->angles[PITCH]+ps->delta_angles[PITCH]))) - floor(ps->viewangles[PITCH]); + + if ( diff > maxPitchSpeed ) + { + cmd->angles[PITCH] = ANGLE2SHORT( ps->viewangles[PITCH] + maxPitchSpeed ) - ps->delta_angles[PITCH]; + } + else if ( diff < -maxPitchSpeed ) + { + cmd->angles[PITCH] = ANGLE2SHORT( ps->viewangles[PITCH] - maxPitchSpeed ) - ps->delta_angles[PITCH]; + } + + //Um, WTF? When I turn in a certain direction, I start going backwards? Or strafing? + int maxYawSpeed = MAX_YAWSPEED_X_WING;//switch, eventually? Or read from file? + diff = AngleNormalize180(SHORT2ANGLE(cmd->angles[YAW]+ps->delta_angles[YAW]) - floor(ps->viewangles[YAW])); + + //clamp the turn rate + if ( diff > maxYawSpeed ) + { + cmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] + maxYawSpeed ) - ps->delta_angles[YAW]; + } + else if ( diff < -maxYawSpeed ) + { + cmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] - maxYawSpeed ) - ps->delta_angles[YAW]; + } + } +} + +extern void SetClientViewAngle( gentity_t *ent, vec3_t angle ); +qboolean PM_LockAngles( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + return qtrue; +} + +qboolean PM_AdjustAnglesToGripper( gentity_t *ent, usercmd_t *ucmd ) +{//FIXME: make this more generic and have it actually *tell* the client what cmd angles it should be locked at? + if ( ((ent->client->ps.eFlags&EF_FORCE_GRIPPED)||(ent->client->ps.eFlags&EF_FORCE_DRAINED)) && ent->enemy ) + { + vec3_t dir, angles; + + VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, dir ); + vectoangles( dir, angles ); + angles[PITCH] = AngleNormalize180( angles[PITCH] ); + angles[YAW] = AngleNormalize180( angles[YAW] ); + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, angles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT(angles[PITCH]) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT(angles[YAW]) - ent->client->ps.delta_angles[YAW]; + return qtrue; + } + return qfalse; +} + +qboolean PM_AdjustAnglesToPuller( gentity_t *ent, gentity_t *puller, usercmd_t *ucmd, qboolean faceAway ) +{//FIXME: make this more generic and have it actually *tell* the client what cmd angles it should be locked at? + vec3_t dir, angles; + + VectorSubtract( puller->currentOrigin, ent->currentOrigin, dir ); + vectoangles( dir, angles ); + angles[PITCH] = AngleNormalize180( angles[PITCH] ); + if ( faceAway ) + { + angles[YAW] += 180; + } + angles[YAW] = AngleNormalize180( angles[YAW] ); + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, angles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT(angles[PITCH]) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT(angles[YAW]) - ent->client->ps.delta_angles[YAW]; + return qtrue; +} + +qboolean PM_AdjustAngleForWallRun( gentity_t *ent, usercmd_t *ucmd, qboolean doMove ) +{ + if (( ent->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT || ent->client->ps.legsAnim == BOTH_WALL_RUN_LEFT ) && ent->client->ps.legsAnimTimer > 500 ) + {//wall-running and not at end of anim + //stick to wall, if there is one + vec3_t fwd, rt, traceTo, mins = {ent->mins[0],ent->mins[1],0}, maxs = {ent->maxs[0],ent->maxs[1],24}, fwdAngles = {0, ent->client->ps.viewangles[YAW], 0}; + trace_t trace; + float dist, yawAdjust=0.0f; + + AngleVectors( fwdAngles, fwd, rt, NULL ); + + if ( ent->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT ) + { + dist = 128; + yawAdjust = -90; + } + else + { + dist = -128; + yawAdjust = 90; + } + VectorMA( ent->currentOrigin, dist, rt, traceTo ); + gi.trace( &trace, ent->currentOrigin, mins, maxs, traceTo, ent->s.number, ent->clipmask ); + if ( trace.fraction < 1.0f + && (trace.plane.normal[2] >= 0.0f && trace.plane.normal[2] <= 0.4f) )//&& ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) + { + trace_t trace2; + vec3_t traceTo2; + vec3_t wallRunFwd, wallRunAngles = {0}; + + wallRunAngles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust; + AngleVectors( wallRunAngles, wallRunFwd, NULL, NULL ); + + VectorMA( ent->currentOrigin, 32, wallRunFwd, traceTo2 ); + gi.trace( &trace2, ent->currentOrigin, mins, maxs, traceTo2, ent->s.number, ent->clipmask ); + if ( trace2.fraction < 1.0f && DotProduct( trace2.plane.normal, wallRunFwd ) <= -0.999f ) + {//wall we can't run on in front of us + trace.fraction = 1.0f;//just a way to get it to kick us off the wall below + } + } + if ( trace.fraction < 1.0f + && (trace.plane.normal[2] >= 0.0f && trace.plane.normal[2] <= 0.4f) )//&& ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//still a vertical wall there + //FIXME: don't pull around 90 turns + //FIXME: simulate stepping up steps here, somehow? + if ( (ent->s.number>=MAX_CLIENTS&&!G_ControlledByPlayer(ent)) || !player_locked ) + { + if ( ent->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT ) + { + ucmd->rightmove = 127; + } + else + { + ucmd->rightmove = -127; + } + } + if ( ucmd->upmove < 0 ) + { + ucmd->upmove = 0; + } + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + //make me face perpendicular to the wall + ent->client->ps.viewangles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust; + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + if ( (ent->s.number&&!G_ControlledByPlayer(ent)) || !player_locked ) + { + if ( doMove ) + { + //push me forward + float zVel = ent->client->ps.velocity[2]; + if ( zVel > forceJumpStrength[FORCE_LEVEL_2]/2.0f ) + { + zVel = forceJumpStrength[FORCE_LEVEL_2]/2.0f; + } + //pull me toward the wall + VectorScale( trace.plane.normal, -128, ent->client->ps.velocity ); + if ( ent->client->ps.legsAnimTimer > 500 ) + {//not at end of anim yet, pushing forward + //FIXME: or MA? + float speed = 175; + if ( ucmd->forwardmove < 0 ) + {//slower + speed = 100; + } + else if ( ucmd->forwardmove > 0 ) + { + speed = 250;//running speed + } + VectorMA( ent->client->ps.velocity, speed, fwd, ent->client->ps.velocity ); + } + ent->client->ps.velocity[2] = zVel;//preserve z velocity + //VectorMA( ent->client->ps.velocity, -128, trace.plane.normal, ent->client->ps.velocity ); + //pull me toward the wall, too + //VectorMA( ent->client->ps.velocity, dist, rt, ent->client->ps.velocity ); + } + } + ucmd->forwardmove = 0; + return qtrue; + } + else if ( doMove ) + {//stop it + if ( ent->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT ) + { + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_WALL_RUN_RIGHT_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else if ( ent->client->ps.legsAnim == BOTH_WALL_RUN_LEFT ) + { + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_WALL_RUN_LEFT_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + return qfalse; +} + +extern int PM_AnimLength( int index, animNumber_t anim ); +qboolean PM_AdjustAnglesForSpinningFlip( gentity_t *ent, usercmd_t *ucmd, qboolean anglesOnly ) +{ + vec3_t newAngles; + float animLength, spinStart, spinEnd, spinAmt, spinLength; + animNumber_t spinAnim; + + if ( ent->client->ps.legsAnim == BOTH_JUMPFLIPSTABDOWN ) + { + spinAnim = BOTH_JUMPFLIPSTABDOWN; + spinStart = 300.0f;//700.0f; + spinEnd = 1400.0f; + spinAmt = 180.0f; + } + else if ( ent->client->ps.legsAnim == BOTH_JUMPFLIPSLASHDOWN1 ) + { + spinAnim = BOTH_JUMPFLIPSLASHDOWN1; + spinStart = 300.0f;//700.0f;//1500.0f; + spinEnd = 1400.0f;//2300.0f; + spinAmt = 180.0f; + } + else + { + if ( !anglesOnly ) + { + if ( (ent->s.numberclient->clientInfo.animFileIndex, spinAnim ); + float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer); + //face me + if ( elapsedTime >= spinStart && elapsedTime <= spinEnd ) + { + spinLength = spinEnd - spinStart; + VectorCopy( ent->client->ps.viewangles, newAngles ); + newAngles[YAW] = ent->angle + (spinAmt * (elapsedTime-spinStart) / spinLength); + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, newAngles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + if ( anglesOnly ) + { + return qtrue; + } + } + else if ( anglesOnly ) + { + return qfalse; + } + //push me + if ( ent->client->ps.legsAnimTimer > 300 )//&& ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//haven't landed or reached end of anim yet + if ( (ent->s.number>=MAX_CLIENTS&&!G_ControlledByPlayer(ent)) || !player_locked ) + { + vec3_t pushDir, pushAngles = {0,ent->angle,0}; + AngleVectors( pushAngles, pushDir, NULL, NULL ); + if ( DotProduct( ent->client->ps.velocity, pushDir ) < 100 ) + { + VectorMA( ent->client->ps.velocity, 10, pushDir, ent->client->ps.velocity ); + } + } + } + //do a dip in the view + if ( (ent->s.numberps->viewheight = standheight + viewDip; + } + return qtrue; +} + +qboolean PM_AdjustAnglesForBackAttack( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( (ent->s.number>=MAX_CLIENTS&&!G_ControlledByPlayer(ent)) ) + { + return qfalse; + } + if ( ( ent->client->ps.saberMove == LS_A_BACK || ent->client->ps.saberMove == LS_A_BACK_CR || ent->client->ps.saberMove == LS_A_BACKSTAB ) + && PM_InAnimForSaberMove( ent->client->ps.torsoAnim, ent->client->ps.saberMove ) ) + { + if ( ent->client->ps.saberMove != LS_A_BACKSTAB || !ent->enemy || (ent->s.number>=MAX_CLIENTS&&!G_ControlledByPlayer(ent)) ) + { + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + } + else + {//keep player facing away from their enemy + vec3_t enemyBehindDir; + VectorSubtract( ent->currentOrigin, ent->enemy->currentOrigin, enemyBehindDir ); + float enemyBehindYaw = AngleNormalize180( vectoyaw( enemyBehindDir ) ); + float yawError = AngleNormalize180( enemyBehindYaw - AngleNormalize180( ent->client->ps.viewangles[YAW] ) ); + if ( yawError > 1 ) + { + yawError = 1; + } + else if ( yawError < -1 ) + { + yawError = -1; + } + ucmd->angles[YAW] = ANGLE2SHORT( AngleNormalize180( ent->client->ps.viewangles[YAW] + yawError ) ) - ent->client->ps.delta_angles[YAW]; + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + } + return qtrue; + } + return qfalse; +} + +qboolean PM_AdjustAnglesForSaberLock( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( ent->client->ps.saberLockTime > level.time ) + { + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + return qtrue; + } + return qfalse; +} + +int G_MinGetUpTime( gentity_t *ent ) +{ + if ( ent + && ent->client + && ( ent->client->ps.legsAnim == BOTH_PLAYER_PA_3_FLY + || ent->client->ps.legsAnim == BOTH_LK_DL_ST_T_SB_1_L + || ent->client->ps.legsAnim == BOTH_RELEASED ) ) + {//special cases + return 200; + } + else if ( ent && ent->client && ent->client->NPC_class == CLASS_ALORA ) + {//alora springs up very quickly from knockdowns! + return 1000; + } + else if ( (ent->s.clientNum < MAX_CLIENTS||G_ControlledByPlayer(ent)) ) + {//player can get up faster based on his/her force jump skill + int getUpTime = PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + if ( ent->client->ps.forcePowerLevel[FP_LEVITATION] >= FORCE_LEVEL_3 ) + { + return (getUpTime+400);//750 + } + else if ( ent->client->ps.forcePowerLevel[FP_LEVITATION] == FORCE_LEVEL_2 ) + { + return (getUpTime+200);//500 + } + else if ( ent->client->ps.forcePowerLevel[FP_LEVITATION] == FORCE_LEVEL_1 ) + { + return (getUpTime+100);//250 + } + else + { + return getUpTime; + } + } + return 200; +} + +qboolean PM_AdjustAnglesForKnockdown( gentity_t *ent, usercmd_t *ucmd, qboolean angleClampOnly ) +{ + if ( PM_InKnockDown( &ent->client->ps ) ) + {//being knocked down or getting up, can't do anything! + if ( !angleClampOnly ) + { + if ( ent->client->ps.legsAnimTimer > G_MinGetUpTime( ent ) + || (ent->s.number >= MAX_CLIENTS&&!G_ControlledByPlayer(ent)) ) + {//can't get up yet + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + } + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + //you can jump up out of a knockdown and you get get up into a crouch from a knockdown + //ucmd->upmove = 0; + //if ( !PM_InForceGetUp( &ent->client->ps ) || ent->client->ps.torsoAnimTimer > 800 || ent->s.weapon != WP_SABER ) + if ( ent->health > 0 ) + {//can only attack if you've started a force-getup and are using the saber + ucmd->buttons = 0; + } + } + if ( !PM_InForceGetUp( &ent->client->ps ) ) + {//can't turn unless in a force getup + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + return qtrue; + } + } + return qfalse; +} + +qboolean PM_AdjustAnglesForDualJumpAttack( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + return qtrue; +} + +qboolean PM_AdjustAnglesForLongJump( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + return qtrue; +} + +qboolean PM_AdjustAnglesForGrapple( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + return qtrue; +} + +qboolean PM_AdjustAngleForWallRunUp( gentity_t *ent, usercmd_t *ucmd, qboolean doMove ) +{ + if ( ent->client->ps.legsAnim == BOTH_FORCEWALLRUNFLIP_START ) + {//wall-running up + //stick to wall, if there is one + vec3_t fwd, traceTo, mins = {ent->mins[0],ent->mins[1],0}, maxs = {ent->maxs[0],ent->maxs[1],24}, fwdAngles = {0, ent->client->ps.viewangles[YAW], 0}; + trace_t trace; + float dist = 128; + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + VectorMA( ent->currentOrigin, dist, fwd, traceTo ); + gi.trace( &trace, ent->currentOrigin, mins, maxs, traceTo, ent->s.number, ent->clipmask ); + if ( trace.fraction > 0.5f ) + {//hmm, some room, see if there's a floor right here + trace_t trace2; + vec3_t top, bottom; + VectorCopy( trace.endpos, top ); + top[2] += (ent->mins[2]*-1) + 4.0f; + VectorCopy( top, bottom ); + bottom[2] -= 64.0f;//was 32.0f + gi.trace( &trace2, top, ent->mins, ent->maxs, bottom, ent->s.number, ent->clipmask ); + if ( !trace2.allsolid + && !trace2.startsolid + && trace2.fraction < 1.0f + && trace2.plane.normal[2] > 0.7f )//slope we can stand on + {//cool, do the alt-flip and land on whetever it is we just scaled up + VectorScale( fwd, 100, ent->client->ps.velocity ); + ent->client->ps.velocity[2] += 200; + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_FORCEWALLRUNFLIP_ALT, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + ent->client->ps.pm_flags |= PMF_JUMP_HELD; + ent->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + ent->client->ps.forcePowersActive |= (1<upmove = 0; + return qfalse; + } + } + if ( //ucmd->upmove <= 0 && + ent->client->ps.legsAnimTimer > 0 + && ucmd->forwardmove > 0 + && trace.fraction < 1.0f + && (trace.plane.normal[2] >= 0.0f && trace.plane.normal[2] <= MAX_WALL_RUN_Z_NORMAL) ) + {//still a vertical wall there + //make sure there's not a ceiling above us! + trace_t trace2; + VectorCopy( ent->currentOrigin, traceTo ); + traceTo[2] += 64; + gi.trace( &trace2, ent->currentOrigin, mins, maxs, traceTo, ent->s.number, ent->clipmask ); + if ( trace2.fraction < 1.0f ) + {//will hit a ceiling, so force jump-off right now + //NOTE: hits any entity or clip brush in the way, too, not just architecture! + } + else + {//all clear, keep going + //FIXME: don't pull around 90 turns + //FIXME: simulate stepping up steps here, somehow? + if ( (ent->s.number>=MAX_CLIENTS&&!G_ControlledByPlayer(ent)) || !player_locked ) + { + ucmd->forwardmove = 127; + } + if ( ucmd->upmove < 0 ) + { + ucmd->upmove = 0; + } + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + //make me face the wall + ent->client->ps.viewangles[YAW] = vectoyaw( trace.plane.normal )+180; + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + if ( (ent->s.number>=MAX_CLIENTS&&!G_ControlledByPlayer(ent)) || !player_locked ) + { + if ( doMove ) + { + //pull me toward the wall + VectorScale( trace.plane.normal, -128, ent->client->ps.velocity ); + //push me up + if ( ent->client->ps.legsAnimTimer > 200 ) + {//not at end of anim yet + float speed = 300; + /* + if ( ucmd->forwardmove < 0 ) + {//slower + speed = 100; + } + else if ( ucmd->forwardmove > 0 ) + { + speed = 250;//running speed + } + */ + ent->client->ps.velocity[2] = speed;//preserve z velocity + } + //pull me toward the wall + //VectorMA( ent->client->ps.velocity, -128, trace.plane.normal, ent->client->ps.velocity ); + } + } + ucmd->forwardmove = 0; + return qtrue; + } + } + //failed! + if ( doMove ) + {//stop it + VectorScale( fwd, WALL_RUN_UP_BACKFLIP_SPEED, ent->client->ps.velocity ); + ent->client->ps.velocity[2] += 200; + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_FORCEWALLRUNFLIP_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + ent->client->ps.pm_flags |= PMF_JUMP_HELD; + ent->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + ent->client->ps.forcePowersActive |= (1<upmove = 0; + //return qtrue; + } + } + return qfalse; +} + +float G_ForceWallJumpStrength( void ) +{ + return (forceJumpStrength[FORCE_LEVEL_3]/2.5f); +} + +qboolean PM_AdjustAngleForWallJump( gentity_t *ent, usercmd_t *ucmd, qboolean doMove ) +{ + if ( PM_InReboundJump( ent->client->ps.legsAnim ) + || PM_InReboundHold( ent->client->ps.legsAnim ) ) + {//hugging wall, getting ready to jump off + //stick to wall, if there is one + vec3_t checkDir, traceTo, mins = {ent->mins[0],ent->mins[1],0}, maxs = {ent->maxs[0],ent->maxs[1],24}, fwdAngles = {0, ent->client->ps.viewangles[YAW], 0}; + trace_t trace; + float dist = 128, yawAdjust; + switch ( ent->client->ps.legsAnim ) + { + case BOTH_FORCEWALLREBOUND_RIGHT: + case BOTH_FORCEWALLHOLD_RIGHT: + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + yawAdjust = -90; + break; + case BOTH_FORCEWALLREBOUND_LEFT: + case BOTH_FORCEWALLHOLD_LEFT: + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + VectorScale( checkDir, -1, checkDir ); + yawAdjust = 90; + break; + case BOTH_FORCEWALLREBOUND_FORWARD: + case BOTH_FORCEWALLHOLD_FORWARD: + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + yawAdjust = 180; + break; + case BOTH_FORCEWALLREBOUND_BACK: + case BOTH_FORCEWALLHOLD_BACK: + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + VectorScale( checkDir, -1, checkDir ); + yawAdjust = 0; + break; + default: + //WTF??? + return qfalse; + break; + } + if ( g_debugMelee->integer ) + {//uber-skillz + if ( ucmd->upmove > 0 ) + {//hold on until you let go manually + if ( PM_InReboundHold( ent->client->ps.legsAnim ) ) + {//keep holding + if ( ent->client->ps.legsAnimTimer < 150 ) + { + ent->client->ps.legsAnimTimer = 150; + } + } + else + {//if got to hold part of anim, play hold anim + if ( ent->client->ps.legsAnimTimer <= 300 ) + { + ent->client->ps.SaberDeactivate(); + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_FORCEWALLRELEASE_FORWARD+(ent->client->ps.legsAnim-BOTH_FORCEWALLHOLD_FORWARD), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 150; + } + } + } + } + VectorMA( ent->currentOrigin, dist, checkDir, traceTo ); + gi.trace( &trace, ent->currentOrigin, mins, maxs, traceTo, ent->s.number, ent->clipmask ); + if ( //ucmd->upmove <= 0 && + ent->client->ps.legsAnimTimer > 100 && + trace.fraction < 1.0f && fabs(trace.plane.normal[2]) <= MAX_WALL_GRAB_SLOPE ) + {//still a vertical wall there + //FIXME: don't pull around 90 turns + /* + if ( (ent->s.number>=MAX_CLIENTS&&!G_ControlledByPlayer(ent)) || !player_locked ) + { + ucmd->forwardmove = 127; + } + */ + if ( ucmd->upmove < 0 ) + { + ucmd->upmove = 0; + } + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + //align me to the wall + ent->client->ps.viewangles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust; + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + if ( (ent->s.number>=MAX_CLIENTS&&!G_ControlledByPlayer(ent)) || !player_locked ) + { + if ( doMove ) + { + //pull me toward the wall + VectorScale( trace.plane.normal, -128, ent->client->ps.velocity ); + } + } + ucmd->upmove = 0; + ent->client->ps.pm_flags |= PMF_STUCK_TO_WALL; + return qtrue; + } + else if ( doMove + && (ent->client->ps.pm_flags&PMF_STUCK_TO_WALL)) + {//jump off + //push off of it! + ent->client->ps.pm_flags &= ~PMF_STUCK_TO_WALL; + ent->client->ps.velocity[0] = ent->client->ps.velocity[1] = 0; + VectorScale( checkDir, -JUMP_OFF_WALL_SPEED, ent->client->ps.velocity ); + ent->client->ps.velocity[2] = G_ForceWallJumpStrength(); + ent->client->ps.pm_flags |= PMF_JUMPING|PMF_JUMP_HELD; + G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + ent->client->ps.forcePowersActive |= (1<client->ps.legsAnim ) ) + {//if was in hold pose, release now + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_FORCEWALLRELEASE_FORWARD+(ent->client->ps.legsAnim-BOTH_FORCEWALLHOLD_FORWARD), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + //no control for half a second + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + ent->client->ps.pm_time = 500; + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + ucmd->upmove = 0; + //return qtrue; + } + } + ent->client->ps.pm_flags &= ~PMF_STUCK_TO_WALL; + return qfalse; +} + +qboolean PM_AdjustAnglesForBFKick( gentity_t *self, usercmd_t *ucmd, vec3_t fwdAngs, qboolean aimFront ) +{ + //Auto-aim the player at the ent in front/back of them + //FIXME: camera angle should always be in front/behind me for the 2 kicks + // (to hide how far away the two entities really are) + //FIXME: don't let the people we're auto-aiming at move? + gentity_t *ent; + gentity_t *entityList[MAX_GENTITIES]; + vec3_t mins, maxs; + int i, e; + int radius = ((self->maxs[0]*1.5f)+(self->maxs[0]*1.5f)+STAFF_KICK_RANGE+24.0f);//a little wide on purpose + vec3_t center, vec2Ent, v_fwd; + float distToEnt, bestDist = Q3_INFINITE; + float dot, bestDot = -1.1f; + float bestYaw = Q3_INFINITE; + + AngleVectors( fwdAngs, v_fwd, NULL, NULL ); + + VectorCopy( self->currentOrigin, center ); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + + int numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + + if (ent == self) + continue; + if (ent->owner == self) + continue; + if ( !(ent->inuse) ) + continue; + //not a client? + if ( !ent->client ) + continue; + //ally? + if ( ent->client->playerTeam == self->client->playerTeam ) + continue; + //on the ground + if ( PM_InKnockDown( &ent->client->ps ) ) + continue; + //dead? + if ( ent->health <= 0 ) + { + if ( level.time - ent->s.time > 2000 ) + {//died more than 2 seconds ago, forget him + continue; + } + } + //too far? + VectorSubtract( ent->currentOrigin, center, vec2Ent ); + distToEnt = VectorNormalize( vec2Ent ); + if ( distToEnt > radius ) + continue; + + if ( !aimFront ) + {//aim away from them + VectorScale( vec2Ent, -1, vec2Ent ); + } + dot = DotProduct( vec2Ent, v_fwd ); + if ( dot < 0.0f ) + {//never turn all the way around + continue; + } + if ( dot > bestDot || ((bestDot-dot<0.25f)&&distToEnt-bestDist>8.0f) ) + {//more in front... OR: still relatively close to in front and significantly closer + bestDot = dot; + bestDist = distToEnt; + bestYaw = vectoyaw( vec2Ent ); + } + } + if ( bestYaw != Q3_INFINITE && bestYaw != fwdAngs[YAW] ) + {//aim us at them + AngleNormalize180( bestYaw ); + AngleNormalize180( fwdAngs[YAW] ); + float angDiff = AngleSubtract( bestYaw, fwdAngs[YAW] ); + AngleNormalize180( angDiff ); + if ( fabs(angDiff) <= 3.0f ) + { + self->client->ps.viewangles[YAW] = bestYaw; + } + else if ( angDiff > 0.0f ) + {//more than 3 degrees higher + self->client->ps.viewangles[YAW] += 3.0f; + } + else + {//must be more than 3 less than + self->client->ps.viewangles[YAW] -= 3.0f; + } + if ( self->client->ps.viewEntity <= 0 || self->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( self, self->client->ps.viewangles ); + } + ucmd->angles[YAW] = ANGLE2SHORT( self->client->ps.viewangles[YAW] ) - self->client->ps.delta_angles[YAW]; + return qtrue; + } + else + {//lock these angles + if ( self->client->ps.viewEntity <= 0 || self->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( self, self->client->ps.viewangles ); + } + ucmd->angles[YAW] = ANGLE2SHORT( self->client->ps.viewangles[YAW] ) - self->client->ps.delta_angles[YAW]; + } + return qtrue; +} + +qboolean PM_AdjustAnglesForStabDown( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( PM_StabDownAnim( ent->client->ps.torsoAnim ) + && ent->client->ps.torsoAnimTimer ) + {//lock our angles + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + float elapsedTime = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ) - ent->client->ps.torsoAnimTimer; + //FIXME: scale forwardmove by dist from enemy - none if right next to him (so we don't slide off!) + if ( ent->enemy ) + { + float dist2Enemy = DistanceHorizontal( ent->enemy->currentOrigin, ent->currentOrigin ); + if ( dist2Enemy > (ent->enemy->maxs[0]*1.5f)+(ent->maxs[0]*1.5f) ) + { + ent->client->ps.speed = dist2Enemy*2.0f; + } + else + { + ent->client->ps.speed = 0; + } + } + else + { + ent->client->ps.speed = 150; + } + switch ( ent->client->ps.legsAnim ) + { + case BOTH_STABDOWN: + if ( elapsedTime >= 300 && elapsedTime < 900 ) + {//push forward? + //FIXME: speed! + ucmd->forwardmove = 127; + } + break; + case BOTH_STABDOWN_STAFF: + if ( elapsedTime > 400 && elapsedTime < 950 ) + {//push forward? + //FIXME: speed! + ucmd->forwardmove = 127; + } + break; + case BOTH_STABDOWN_DUAL: + if ( elapsedTime >= 300 && elapsedTime < 900 ) + {//push forward? + //FIXME: speed! + ucmd->forwardmove = 127; + } + break; + } + VectorClear( ent->client->ps.moveDir ); + + if ( ent->enemy//still have a valid enemy + && ent->enemy->client//who is a client + && (PM_InKnockDownNoGetup( &ent->enemy->client->ps ) //enemy still on ground + || PM_InGetUpNoRoll( &ent->enemy->client->ps ) ) )//or getting straight up + {//aim at the enemy + vec3_t enemyDir; + VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, enemyDir ); + float enemyYaw = AngleNormalize180( vectoyaw( enemyDir ) ); + float yawError = AngleNormalize180( enemyYaw - AngleNormalize180( ent->client->ps.viewangles[YAW] ) ); + if ( yawError > 1 ) + { + yawError = 1; + } + else if ( yawError < -1 ) + { + yawError = -1; + } + ucmd->angles[YAW] = ANGLE2SHORT( AngleNormalize180( ent->client->ps.viewangles[YAW] + yawError ) ) - ent->client->ps.delta_angles[YAW]; + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + } + else + {//can't turn + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + } + return qtrue; + } + return qfalse; +} + +qboolean PM_AdjustAnglesForSpinProtect( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( ent->client->ps.torsoAnim == BOTH_A6_SABERPROTECT ) + {//in the dual spin thing + if ( ent->client->ps.torsoAnimTimer ) + {//flatten and lock our angles, pull back camera + //FIXME: lerp this + ent->client->ps.viewangles[PITCH] = 0; + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + return qtrue; + } + } + return qfalse; +} + +qboolean PM_AdjustAnglesForWallRunUpFlipAlt( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + return qtrue; +} + +qboolean PM_AdjustAnglesForHeldByMonster( gentity_t *ent, gentity_t *monster, usercmd_t *ucmd ) +{ + vec3_t newViewAngles; + if ( !monster || !monster->client ) + { + return qfalse; + } + VectorScale( monster->client->ps.viewangles, -1, newViewAngles ); + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, newViewAngles ); + } + ucmd->angles[PITCH] = ANGLE2SHORT( newViewAngles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( newViewAngles[YAW] ) - ent->client->ps.delta_angles[YAW]; + return qtrue; +} + +qboolean G_OkayToLean( playerState_t *ps, usercmd_t *cmd, qboolean interruptOkay ) +{ + return false; + /* + if ( (ps->clientNum < MAX_CLIENTS||G_ControlledByPlayer(&g_entities[ps->clientNum]))//player + && ps->groundEntityNum != ENTITYNUM_NONE//on ground + && ( (interruptOkay//okay to interrupt a lean + && PM_DodgeAnim( ps->torsoAnim ) )//already leaning + || + (!ps->weaponTime//not attacking or being prevented from attacking + && !ps->legsAnimTimer//not in any held legs anim + && !ps->torsoAnimTimer) //not in any held torso anim + ) + && !(cmd->buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_LIGHTNING|BUTTON_USE_FORCE|BUTTON_FORCE_DRAIN|BUTTON_FORCEGRIP))//not trying to attack + //&& (ps->forcePowersActive&(1<velocity, vec3_origin )//not moving + && !cg_usingInFrontOf )//use button wouldn't be used for anything else + { + return qtrue; + } + return qfalse; + */ +} +/* +================ +PM_UpdateViewAngles + +This can be used as another entry point when only the viewangles +are being updated isntead of a full move + +//FIXME: Now that they pmove twice per think, they snap-look really fast +================ +*/ +void PM_UpdateViewAngles( playerState_t *ps, usercmd_t *cmd, gentity_t *gent ) +{ + short temp; + float rootPitch = 0, pitchMin=-75, pitchMax=75, yawMin=0, yawMax=0, lockedYawValue = 0; //just to shut up warnings + int i; + vec3_t start, end, tmins, tmaxs, right; + trace_t trace; + qboolean lockedYaw = qfalse, clamped = qfalse; + + if ( ps->pm_type == PM_INTERMISSION ) + { + return; // no view changes at all + } + + //TEMP +#if 0 //rww 12/23/02 - I'm disabling this for now, I'm going to try to make it work with my new rag stuff + if ( gent != NULL ) + { + PM_IKUpdate( gent ); + } +#endif + + if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) + { + return; // no view changes at all + } + +// if ( player_locked ) +// {//can't turn +// return; +// } + if ( ps->clientNum != 0 && gent != NULL && gent->client != NULL ) + { + if(gent->client->renderInfo.renderFlags & RF_LOCKEDANGLE) + { + pitchMin = 0 - gent->client->renderInfo.headPitchRangeUp - gent->client->renderInfo.torsoPitchRangeUp; + pitchMax = gent->client->renderInfo.headPitchRangeDown + gent->client->renderInfo.torsoPitchRangeDown; + + yawMin = 0 - gent->client->renderInfo.headYawRangeLeft - gent->client->renderInfo.torsoYawRangeLeft; + yawMax = gent->client->renderInfo.headYawRangeRight + gent->client->renderInfo.torsoYawRangeRight; + + lockedYaw = qtrue; + lockedYawValue = gent->client->renderInfo.lockYaw; + } + else + { + pitchMin = -gent->client->renderInfo.headPitchRangeUp-gent->client->renderInfo.torsoPitchRangeUp; + pitchMax = gent->client->renderInfo.headPitchRangeDown+gent->client->renderInfo.torsoPitchRangeDown; + } + } + + if ( ps->eFlags & EF_LOCKED_TO_WEAPON ) + { + // Emplaced guns have different pitch capabilities + if ( gent && gent->owner && gent->owner->e_UseFunc == useF_eweb_use ) + { + pitchMin = -15; + pitchMax = 10; + /* + lockedYawValue = gent->owner->s.angles[YAW]; + yawMax = gent->owner->s.origin2[0]; + yawMin = yawMax *-1; + lockedYaw = qtrue; + */ + } + else + { + pitchMin = -35; + pitchMax = 30; + } + } + + Vehicle_t *pVeh = NULL; + + // If we're a vehicle, or we're riding a vehicle...? + if ( gent && gent->client && gent->client->NPC_class == CLASS_VEHICLE && gent->m_pVehicle) + { + pVeh = gent->m_pVehicle; + // If we're a vehicle... + if (pVeh->m_pVehicleInfo->Inhabited(pVeh) || pVeh->m_iBoarding!=0 || pVeh->m_pVehicleInfo->type!=VH_ANIMAL) + { + lockedYawValue = pVeh->m_vOrientation[YAW]; + yawMax = yawMin = 0; + rootPitch = pVeh->m_vOrientation[PITCH];//??? what if goes over 90 when add the min/max? + pitchMax = 0.0f;//pVeh->m_pVehicleInfo->pitchLimit; + pitchMin = 0.0f;//-pitchMax; + lockedYaw = qtrue; + } + // If we're riding a vehicle... + else if ( (pVeh = G_IsRidingVehicle( gent ) ) != NULL ) + { + if ( pVeh->m_pVehicleInfo->type != VH_ANIMAL ) + {//animals just turn normally, no clamping + lockedYawValue = 0;//gent->owner->client->ps.vehicleAngles[YAW]; + lockedYaw = qtrue; + yawMax = pVeh->m_pVehicleInfo->lookYaw; + yawMin = -yawMax; + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + rootPitch = pVeh->m_vOrientation[PITCH];//gent->owner->client->ps.vehicleAngles[PITCH];//??? what if goes over 90 when add the min/max? + pitchMax = pVeh->m_pVehicleInfo->pitchLimit; + pitchMin = -pitchMax; + } + else + { + rootPitch = 0;//gent->owner->client->ps.vehicleAngles[PITCH];//??? what if goes over 90 when add the min/max? + pitchMax = pVeh->m_pVehicleInfo->lookPitch; + pitchMin = -pitchMax; + } + } + } + + /*if ( vehIndex != -1 ) + { + //FIXME: read from NPCs.cfg or vehicles.cfg? + lockedYaw = qtrue; + }*/ + } + + const short pitchClampMin = ANGLE2SHORT(rootPitch+pitchMin); + const short pitchClampMax = ANGLE2SHORT(rootPitch+pitchMax); + const short yawClampMin = ANGLE2SHORT(lockedYawValue+yawMin); + const short yawClampMax = ANGLE2SHORT(lockedYawValue+yawMax); + + // circularly clamp the angles with deltas + for (i=0 ; i<3 ; i++) + { + temp = cmd->angles[i] + ps->delta_angles[i]; + if ( i == PITCH ) + { + //FIXME get this limit from the NPCs stats? + // don't let the player look up or down more than 90 degrees + if ( temp > pitchClampMax ) + { + ps->delta_angles[i] = (pitchClampMax - cmd->angles[i]) & 0xffff; //& clamp to short + temp = pitchClampMax; + clamped = qtrue; + } + else if ( temp < pitchClampMin ) + { + ps->delta_angles[i] = (pitchClampMin - cmd->angles[i]) & 0xffff; //& clamp to short + temp = pitchClampMin; + clamped = qtrue; + } + } + /* + if ( i == ROLL && ps->vehicleIndex != VEHICLE_NONE ) + { + if ( temp > pitchClampMax ) + { + ps->delta_angles[i] = (pitchClampMax - cmd->angles[i]) & 0xffff; + temp = pitchClampMax; + } + else if ( temp < pitchClampMin ) + { + ps->delta_angles[i] = (pitchClampMin - cmd->angles[i]) & 0xffff; + temp = pitchClampMin; + } + } + */ + //FIXME: Are we losing precision here? Is this why it jitters? + /* + if ( i == YAW && lockedYaw ) + { + float multiplier = 1.0f; + float newYaw = SHORT2ANGLE(temp); + lockedYawValue = AngleNormalize180( lockedYawValue ); + float yawDiff = AngleNormalize180( newYaw ) - lockedYawValue; + if ( yawDiff > 180 ) + { + multiplier = -1.0f; + yawDiff = 360 - yawDiff; + } + else if ( yawDiff < -180 ) + { + //multiplier = -1.0f; + yawDiff = 360 + yawDiff; + } + // don't let the player look left or right more than the clamp, if any + if ( yawDiff > yawMax ) + { + clamped = qtrue; + ps->viewangles[i] = AngleNormalize180( lockedYawValue+((yawMax-2)*multiplier) ); + } + else if ( yawDiff < yawMin ) + { + clamped = qtrue; + ps->viewangles[i] = AngleNormalize180( lockedYawValue+((yawMin+2)*multiplier) ); + } + else + { + ps->viewangles[i] = newYaw; + } + } + */ + if ( i == YAW && lockedYaw ) + { + //FIXME get this limit from the NPCs stats? + // don't let the player look up or down more than 90 degrees + if ( temp > yawClampMax ) + { + ps->delta_angles[i] = (yawClampMax - cmd->angles[i]) & 0xffff; //& clamp to short + temp = yawClampMax; + clamped = qtrue; + } + else if ( temp < yawClampMin ) + { + ps->delta_angles[i] = (yawClampMin - cmd->angles[i]) & 0xffff; //& clamp to short + temp = yawClampMin; + clamped = qtrue; + } + ps->viewangles[i] = SHORT2ANGLE(temp); + } + else + { + ps->viewangles[i] = SHORT2ANGLE(temp); + } + } + + if ( gent ) + { //only in the real pmove + if ( (cmd->buttons & BUTTON_USE) ) + {//check leaning + if ( cg.renderingThirdPerson ) + {//third person lean + if ( G_OkayToLean( ps, cmd, qtrue ) + && (cmd->rightmove || (cmd->forwardmove && g_debugMelee->integer ) ) )//pushing a direction + { + int anim = -1; + if ( cmd->rightmove > 0 ) + {//lean right + if ( cmd->forwardmove > 0 ) + {//lean forward right + if ( ps->torsoAnim == BOTH_DODGE_HOLD_FR ) + { + anim = ps->torsoAnim; + } + else + { + anim = BOTH_DODGE_FR; + } + } + else if ( cmd->forwardmove < 0 ) + {//lean backward right + if ( ps->torsoAnim == BOTH_DODGE_HOLD_BR ) + { + anim = ps->torsoAnim; + } + else + { + anim = BOTH_DODGE_BR; + } + } + else + {//lean right + if ( ps->torsoAnim == BOTH_DODGE_HOLD_R ) + { + anim = ps->torsoAnim; + } + else + { + anim = BOTH_DODGE_R; + } + } + } + else if ( cmd->rightmove < 0 ) + {//lean left + if ( cmd->forwardmove > 0 ) + {//lean forward left + if ( ps->torsoAnim == BOTH_DODGE_HOLD_FL ) + { + anim = ps->torsoAnim; + } + else + { + anim = BOTH_DODGE_FL; + } + } + else if ( cmd->forwardmove < 0 ) + {//lean backward left + if ( ps->torsoAnim == BOTH_DODGE_HOLD_BL ) + { + anim = ps->torsoAnim; + } + else + { + anim = BOTH_DODGE_BL; + } + } + else + {//lean left + if ( ps->torsoAnim == BOTH_DODGE_HOLD_L ) + { + anim = ps->torsoAnim; + } + else + { + anim = BOTH_DODGE_L; + } + } + } + else + {//not pressing either side + if ( cmd->forwardmove > 0 ) + {//lean forward + if ( PM_DodgeAnim( ps->torsoAnim ) ) + { + anim = ps->torsoAnim; + } + else if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_DODGE_FL; + } + else + { + anim = BOTH_DODGE_FR; + } + } + else if ( cmd->forwardmove < 0 ) + {//lean backward + if ( PM_DodgeAnim( ps->torsoAnim ) ) + { + anim = ps->torsoAnim; + } + else if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_DODGE_BL; + } + else + { + anim = BOTH_DODGE_BR; + } + } + } + if ( anim != -1 ) + { + int extraHoldTime = 0; + if ( PM_DodgeAnim( ps->torsoAnim ) + && !PM_DodgeHoldAnim( ps->torsoAnim ) ) + {//already in a dodge + //use the hold pose, don't start it all over again + anim = BOTH_DODGE_HOLD_FL+(anim-BOTH_DODGE_FL); + extraHoldTime = 200; + } + if ( anim == pm->ps->torsoAnim ) + { + if ( pm->ps->torsoAnimTimer < 100 ) + { + pm->ps->torsoAnimTimer = 100; + } + } + else + { + NPC_SetAnim( gent, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( extraHoldTime && ps->torsoAnimTimer < extraHoldTime ) + { + ps->torsoAnimTimer += extraHoldTime; + } + if ( ps->groundEntityNum != ENTITYNUM_NONE + && !cmd->upmove ) + { + NPC_SetAnim( gent, SETANIM_LEGS, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + ps->legsAnimTimer = ps->torsoAnimTimer; + } + else + { + NPC_SetAnim( gent, SETANIM_LEGS, anim, SETANIM_FLAG_NORMAL ); + } + ps->weaponTime = ps->torsoAnimTimer; + ps->leanStopDebounceTime = ceil((float)ps->torsoAnimTimer/50.0f);//20; + } + } + } + else if ( (!cg.renderingThirdPerson||cg.zoomMode) && cmd->rightmove != 0 && !cmd->forwardmove && cmd->upmove <= 0 ) + {//Only lean if holding use button, strafing and not moving forward or back and not jumping + /* + int leanofs = 0; + vec3_t viewangles; + + if ( cmd->rightmove > 0 ) + { + + // if( pm->ps->legsAnim != LEGS_LEAN_RIGHT1) + // { + // PM_SetAnim(pm, SETANIM_LEGS, LEGS_LEAN_RIGHT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + // } + // pm->ps->legsAnimTimer = 500;//Force it to hold the anim for at least half a sec + + + if ( ps->leanofs <= 28 ) + { + leanofs = ps->leanofs + 4; + } + else + { + leanofs = 32; + } + } + else + { + + // if ( pm->ps->legsAnim != LEGS_LEAN_LEFT1 ) + // { + // PM_SetAnim(pm, SETANIM_LEGS, LEGS_LEAN_LEFT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + // } + // pm->ps->legsAnimTimer = 500;//Force it to hold the anim for at least half a sec + // + + if ( ps->leanofs >= -28 ) + { + leanofs = ps->leanofs - 4; + } + else + { + leanofs = -32; + } + } + + VectorCopy( ps->origin, start ); + start[2] += ps->viewheight; + VectorCopy( ps->viewangles, viewangles ); + viewangles[ROLL] = 0; + AngleVectors( ps->viewangles, NULL, right, NULL ); + VectorNormalize( right ); + right[2] = (leanofs<0)?0.25:-0.25; + VectorMA( start, leanofs, right, end ); + VectorSet( tmins, -8, -8, -4 ); + VectorSet( tmaxs, 8, 8, 4 ); + //if we don't trace EVERY frame, can TURN while leaning and + //end up leaning into solid architecture (sigh) + gi.trace( &trace, start, tmins, tmaxs, end, gent->s.number, MASK_PLAYERSOLID ); + + ps->leanofs = floor((float)leanofs * trace.fraction); + + ps->leanStopDebounceTime = 20; + */ + } + else + { + /* + if ( (cmd->forwardmove || cmd->upmove > 0) ) + { + if( ( pm->ps->legsAnim == LEGS_LEAN_RIGHT1) || + ( pm->ps->legsAnim == LEGS_LEAN_LEFT1) ) + { + pm->ps->legsAnimTimer = 0;//Force it to stop the anim + } + + if ( ps->leanofs > 0 ) + { + ps->leanofs-=4; + if ( ps->leanofs < 0 ) + { + ps->leanofs = 0; + } + } + else if ( ps->leanofs < 0 ) + { + ps->leanofs+=4; + if ( ps->leanofs > 0 ) + { + ps->leanofs = 0; + } + } + } + */ + + } + } + else//BUTTON_USE + { + /* + if ( ps->leanofs > 0 ) + { + ps->leanofs-=4; + if ( ps->leanofs < 0 ) + { + ps->leanofs = 0; + } + } + else if ( ps->leanofs < 0 ) + { + ps->leanofs+=4; + if ( ps->leanofs > 0 ) + { + ps->leanofs = 0; + } + } + */ + } + } + + if ( ps->leanStopDebounceTime ) + { + ps->leanStopDebounceTime -= 1; + cmd->rightmove = 0; + cmd->buttons &= ~BUTTON_USE; + } +} \ No newline at end of file diff --git a/code/game/bg_panimate.cpp b/code/game/bg_panimate.cpp new file mode 100644 index 0000000..7707888 --- /dev/null +++ b/code/game/bg_panimate.cpp @@ -0,0 +1,6640 @@ +// this include must remain at the top of every bg_xxxx CPP file +#include "common_headers.h" + + +// define GAME_INCLUDE so that g_public.h does not define the +// short, server-visible gclient_t and gentity_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +//#include "../renderer/tr_local.h" + +#include "q_shared.h" +#include "g_shared.h" +#include "bg_local.h" +#include "../cgame/cg_local.h" +#include "anims.h" +#include "Q3_Interface.h" +#include "g_local.h" +#include "wp_saber.h" +#include "g_vehicles.h" + +extern pmove_t *pm; +extern pml_t pml; +extern cvar_t *g_ICARUSDebug; +extern cvar_t *g_timescale; +extern cvar_t *g_synchSplitAnims; +extern cvar_t *g_AnimWarning; +extern cvar_t *g_noFootSlide; +extern cvar_t *g_noFootSlideRunScale; +extern cvar_t *g_noFootSlideWalkScale; +extern cvar_t *g_saberAnimSpeed; +extern cvar_t *g_saberAutoAim; +extern cvar_t *g_speederControlScheme; +extern cvar_t *g_saberNewControlScheme; + +extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f ); +extern void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +extern qboolean ValidAnimFileIndex ( int index ); +extern qboolean PM_ControlledByPlayer( void ); +extern qboolean PM_DroidMelee( int npc_class ); +extern qboolean PM_PainAnim( int anim ); +extern qboolean PM_JumpingAnim( int anim ); +extern qboolean PM_FlippingAnim( int anim ); +extern qboolean PM_RollingAnim( int anim ); +extern qboolean PM_SwimmingAnim( int anim ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean PM_InRoll( playerState_t *ps ); +extern qboolean PM_DodgeAnim( int anim ); +extern qboolean PM_InSlopeAnim( int anim ); +extern qboolean PM_ForceAnim( int anim ); +extern qboolean PM_InKnockDownOnGround( playerState_t *ps ); +extern qboolean PM_InSpecialJump( int anim ); +extern qboolean PM_RunningAnim( int anim ); +extern qboolean PM_WalkingAnim( int anim ); +extern qboolean PM_SwimmingAnim( int anim ); +extern qboolean PM_JumpingAnim( int anim ); +extern qboolean PM_SaberStanceAnim( int anim ); +extern qboolean PM_SaberDrawPutawayAnim( int anim ); +extern void PM_SetJumped( float height, qboolean force ); +extern qboolean PM_InGetUpNoRoll( playerState_t *ps ); +extern qboolean PM_CrouchAnim( int anim ); +extern qboolean G_TryingKataAttack( gentity_t *self, usercmd_t *cmd ); +extern qboolean G_TryingCartwheel( gentity_t *self, usercmd_t *cmd ); +extern qboolean G_TryingSpecial( gentity_t *self, usercmd_t *cmd ); +extern qboolean G_TryingJumpAttack( gentity_t *self, usercmd_t *cmd ); +extern qboolean G_TryingJumpForwardAttack( gentity_t *self, usercmd_t *cmd ); +extern qboolean G_TryingLungeAttack( gentity_t *self, usercmd_t *cmd ); +extern qboolean G_TryingPullAttack( gentity_t *self, usercmd_t *cmd, qboolean amPulling ); +extern qboolean G_InCinematicSaberAnim( gentity_t *self ); +extern qboolean G_ControlledByPlayer( gentity_t *self ); + +extern int g_crosshairEntNum; + +int PM_AnimLength( int index, animNumber_t anim ); +qboolean PM_LockedAnim( int anim ); +qboolean PM_StandingAnim( int anim ); +qboolean PM_InOnGroundAnim ( playerState_t *ps ); +qboolean PM_SuperBreakWinAnim( int anim ); +qboolean PM_SuperBreakLoseAnim( int anim ); +qboolean PM_LockedAnim( int anim ); +saberMoveName_t PM_SaberFlipOverAttackMove( void ); +qboolean PM_CheckFlipOverAttackMove( qboolean checkEnemy ); +saberMoveName_t PM_SaberJumpForwardAttackMove( void ); +qboolean PM_CheckJumpForwardAttackMove( void ); +saberMoveName_t PM_SaberBackflipAttackMove( void ); +qboolean PM_CheckBackflipAttackMove( void ); +saberMoveName_t PM_SaberDualJumpAttackMove( void ); +qboolean PM_CheckDualJumpAttackMove( void ); +saberMoveName_t PM_SaberLungeAttackMove( qboolean fallbackToNormalLunge ); +qboolean PM_CheckLungeAttackMove( void ); +// Okay, here lies the much-dreaded Pat-created FSM movement chart... Heretic II strikes again! +// Why am I inflicting this on you? Well, it's better than hardcoded states. +// Ideally this will be replaced with an external file or more sophisticated move-picker +// once the game gets out of prototype stage. + +// Silly, but I'm replacing these macros so they are shorter! +#define AFLAG_IDLE (SETANIM_FLAG_NORMAL) +#define AFLAG_ACTIVE (SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD | SETANIM_FLAG_HOLDLESS) +#define AFLAG_WAIT (SETANIM_FLAG_HOLD | SETANIM_FLAG_HOLDLESS) +#define AFLAG_FINISH (SETANIM_FLAG_HOLD) + +//FIXME: add the alternate anims for each style? +saberMoveData_t saberMoveData[LS_MOVE_MAX] = {// NB:randomized + // name anim(do all styles?)startQ endQ setanimflag blend, blocking chain_idle chain_attack trailLen + {"None", BOTH_STAND1, Q_R, Q_R, AFLAG_IDLE, 350, BLK_NO, LS_NONE, LS_NONE, 0 }, // LS_NONE = 0, + + // General movements with saber + {"Ready", BOTH_STAND2, Q_R, Q_R, AFLAG_IDLE, 350, BLK_WIDE, LS_READY, LS_S_R2L, 0 }, // LS_READY, + {"Draw", BOTH_STAND1TO2, Q_R, Q_R, AFLAG_FINISH, 350, BLK_NO, LS_READY, LS_S_R2L, 0 }, // LS_DRAW, + {"Putaway", BOTH_STAND2TO1, Q_R, Q_R, AFLAG_FINISH, 350, BLK_NO, LS_READY, LS_S_R2L, 0 }, // LS_PUTAWAY, + + // Attacks + //UL2LR + {"TL2BR Att", BOTH_A1_TL_BR, Q_TL, Q_BR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_TL2BR, LS_R_TL2BR, 200 }, // LS_A_TL2BR + //SLASH LEFT + {"L2R Att", BOTH_A1__L__R, Q_L, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_L2R, LS_R_L2R, 200 }, // LS_A_L2R + //LL2UR + {"BL2TR Att", BOTH_A1_BL_TR, Q_BL, Q_TR, AFLAG_ACTIVE, 50, BLK_TIGHT, LS_R_BL2TR, LS_R_BL2TR, 200 }, // LS_A_BL2TR + //LR2UL + {"BR2TL Att", BOTH_A1_BR_TL, Q_BR, Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_BR2TL, LS_R_BR2TL, 200 }, // LS_A_BR2TL + //SLASH RIGHT + {"R2L Att", BOTH_A1__R__L, Q_R, Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_R2L, LS_R_R2L, 200 },// LS_A_R2L + //UR2LL + {"TR2BL Att", BOTH_A1_TR_BL, Q_TR, Q_BL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_TR2BL, LS_R_TR2BL, 200 }, // LS_A_TR2BL + //SLASH DOWN + {"T2B Att", BOTH_A1_T__B_, Q_T, Q_B, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_T2B, LS_R_T2B, 200 }, // LS_A_T2B + //special attacks + {"Back Stab", BOTH_A2_STABBACK1, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_BACKSTAB + {"Back Att", BOTH_ATTACK_BACK, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_BACK + {"CR Back Att", BOTH_CROUCHATTACKBACK1,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_BACK_CR + {"RollStab", BOTH_ROLL_STAB, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_ROLL_STAB + {"Lunge Att", BOTH_LUNGE2_B__T_, Q_B, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_LUNGE + {"Jump Att", BOTH_FORCELEAP2_T__B_,Q_T, Q_B, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_JUMP_T__B_ + {"Flip Stab", BOTH_JUMPFLIPSTABDOWN,Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_T___R, 200 }, // LS_A_FLIP_STAB + {"Flip Slash", BOTH_JUMPFLIPSLASHDOWN1,Q_L,Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__R_T_, 200 }, // LS_A_FLIP_SLASH + {"DualJump Atk",BOTH_JUMPATTACK6, Q_R, Q_BL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_BL_TR, 200 }, // LS_JUMPATTACK_DUAL + + {"DualJumpAtkL_A",BOTH_ARIAL_LEFT, Q_R, Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_A_TL2BR, 200 }, // LS_JUMPATTACK_ARIAL_LEFT + {"DualJumpAtkR_A",BOTH_ARIAL_RIGHT, Q_R, Q_TR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_A_TR2BL, 200 }, // LS_JUMPATTACK_ARIAL_RIGHT + + {"DualJumpAtkL_A",BOTH_CARTWHEEL_LEFT, Q_R,Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_TL_BR, 200 }, // LS_JUMPATTACK_CART_LEFT + {"DualJumpAtkR_A",BOTH_CARTWHEEL_RIGHT, Q_R,Q_TR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_TR_BL, 200 }, // LS_JUMPATTACK_CART_RIGHT + + {"DualJumpAtkLStaff", BOTH_BUTTERFLY_FL1,Q_R,Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__L__R, 200 }, // LS_JUMPATTACK_STAFF_LEFT + {"DualJumpAtkRStaff", BOTH_BUTTERFLY_FR1,Q_R,Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__R__L, 200 }, // LS_JUMPATTACK_STAFF_RIGHT + + {"ButterflyLeft", BOTH_BUTTERFLY_LEFT,Q_R,Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__L__R, 200 }, // LS_BUTTERFLY_LEFT + {"ButterflyRight", BOTH_BUTTERFLY_RIGHT,Q_R,Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__R__L, 200 }, // LS_BUTTERFLY_RIGHT + + {"BkFlip Atk", BOTH_JUMPATTACK7, Q_B, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_T___R, 200 }, // LS_A_BACKFLIP_ATK + {"DualSpinAtk", BOTH_SPINATTACK6, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SPINATTACK_DUAL + {"StfSpinAtk", BOTH_SPINATTACK7, Q_L, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SPINATTACK + {"LngLeapAtk", BOTH_FORCELONGLEAP_ATTACK,Q_R,Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_LEAP_ATTACK + {"SwoopAtkR", BOTH_VS_ATR_S, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 200 }, // LS_SWOOP_ATTACK_RIGHT + {"SwoopAtkL", BOTH_VS_ATL_S, Q_L, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 200 }, // LS_SWOOP_ATTACK_LEFT + {"TauntaunAtkR",BOTH_VT_ATR_S, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_TAUNTAUN_ATTACK_RIGHT + {"TauntaunAtkL",BOTH_VT_ATL_S, Q_L, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_TAUNTAUN_ATTACK_LEFT + {"StfKickFwd", BOTH_A7_KICK_F, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_F + {"StfKickBack", BOTH_A7_KICK_B, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_B + {"StfKickRight",BOTH_A7_KICK_R, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_R + {"StfKickLeft", BOTH_A7_KICK_L, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_L + {"StfKickSpin", BOTH_A7_KICK_S, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_S_R2L, 200 }, // LS_KICK_S + {"StfKickBkFwd",BOTH_A7_KICK_BF, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_S_R2L, 200 }, // LS_KICK_BF + {"StfKickSplit",BOTH_A7_KICK_RL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_S_R2L, 200 }, // LS_KICK_RL + {"StfKickFwdAir",BOTH_A7_KICK_F_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_F_AIR + {"StfKickBackAir",BOTH_A7_KICK_B_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_B_AIR + {"StfKickRightAir",BOTH_A7_KICK_R_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_R_AIR + {"StfKickLeftAir",BOTH_A7_KICK_L_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_L_AIR + {"StabDown", BOTH_STABDOWN, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_STABDOWN + {"StabDownStf", BOTH_STABDOWN_STAFF,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_STABDOWN_STAFF + {"StabDownDual",BOTH_STABDOWN_DUAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_STABDOWN_DUAL + {"dualspinprot",BOTH_A6_SABERPROTECT,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 500 }, // LS_DUAL_SPIN_PROTECT + {"StfSoulCal", BOTH_A7_SOULCAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 500 }, // LS_STAFF_SOULCAL + {"specialfast", BOTH_A1_SPECIAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 2000}, // LS_A1_SPECIAL + {"specialmed", BOTH_A2_SPECIAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 2000}, // LS_A2_SPECIAL + {"specialstr", BOTH_A3_SPECIAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 2000}, // LS_A3_SPECIAL + {"upsidedwnatk",BOTH_FLIP_ATTACK7, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200}, // LS_UPSIDE_DOWN_ATTACK + {"pullatkstab", BOTH_PULL_IMPALE_STAB,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200}, // LS_PULL_ATTACK_STAB + {"pullatkswing",BOTH_PULL_IMPALE_SWING,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200}, // LS_PULL_ATTACK_SWING + {"AloraSpinAtk",BOTH_ALORA_SPIN_SLASH,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SPINATTACK_ALORA + {"Dual FB Atk", BOTH_A6_FB, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_DUAL_FB + {"Dual LR Atk", BOTH_A6_LR, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_DUAL_LR + {"StfHiltBash", BOTH_A7_HILT, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_HILT_BASH + + //starts + {"TL2BR St", BOTH_S1_S1_TL, Q_R, Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_TL2BR, LS_A_TL2BR, 200 }, // LS_S_TL2BR + {"L2R St", BOTH_S1_S1__L, Q_R, Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_L2R, LS_A_L2R, 200 }, // LS_S_L2R + {"BL2TR St", BOTH_S1_S1_BL, Q_R, Q_BL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_BL2TR, LS_A_BL2TR, 200 }, // LS_S_BL2TR + {"BR2TL St", BOTH_S1_S1_BR, Q_R, Q_BR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_BR2TL, LS_A_BR2TL, 200 }, // LS_S_BR2TL + {"R2L St", BOTH_S1_S1__R, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_R2L, LS_A_R2L, 200 }, // LS_S_R2L + {"TR2BL St", BOTH_S1_S1_TR, Q_R, Q_TR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_TR2BL, LS_A_TR2BL, 200 }, // LS_S_TR2BL + {"T2B St", BOTH_S1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_T2B, LS_A_T2B, 200 }, // LS_S_T2B + + //returns + {"TL2BR Ret", BOTH_R1_BR_S1, Q_BR, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_TL2BR + {"L2R Ret", BOTH_R1__R_S1, Q_R, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_L2R + {"BL2TR Ret", BOTH_R1_TR_S1, Q_TR, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_BL2TR + {"BR2TL Ret", BOTH_R1_TL_S1, Q_TL, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_BR2TL + {"R2L Ret", BOTH_R1__L_S1, Q_L, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_R2L + {"TR2BL Ret", BOTH_R1_BL_S1, Q_BL, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_TR2BL + {"T2B Ret", BOTH_R1_B__S1, Q_B, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_T2B + + //Transitions + {"BR2R Trans", BOTH_T1_BR__R, Q_BR, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc bottom right to right + {"BR2TR Trans", BOTH_T1_BR_TR, Q_BR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc bottom right to top right (use: BOTH_T1_TR_BR) + {"BR2T Trans", BOTH_T1_BR_T_, Q_BR, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc bottom right to top (use: BOTH_T1_T__BR) + {"BR2TL Trans", BOTH_T1_BR_TL, Q_BR, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast weak spin bottom right to top left + {"BR2L Trans", BOTH_T1_BR__L, Q_BR, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast weak spin bottom right to left + {"BR2BL Trans", BOTH_T1_BR_BL, Q_BR, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast weak spin bottom right to bottom left + {"R2BR Trans", BOTH_T1__R_BR, Q_R, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast arc right to bottom right (use: BOTH_T1_BR__R) + {"R2TR Trans", BOTH_T1__R_TR, Q_R, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc right to top right + {"R2T Trans", BOTH_T1__R_T_, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast ar right to top (use: BOTH_T1_T___R) + {"R2TL Trans", BOTH_T1__R_TL, Q_R, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc right to top left + {"R2L Trans", BOTH_T1__R__L, Q_R, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast weak spin right to left + {"R2BL Trans", BOTH_T1__R_BL, Q_R, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast weak spin right to bottom left + {"TR2BR Trans", BOTH_T1_TR_BR, Q_TR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast arc top right to bottom right + {"TR2R Trans", BOTH_T1_TR__R, Q_TR, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc top right to right (use: BOTH_T1__R_TR) + {"TR2T Trans", BOTH_T1_TR_T_, Q_TR, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc top right to top (use: BOTH_T1_T__TR) + {"TR2TL Trans", BOTH_T1_TR_TL, Q_TR, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc top right to top left + {"TR2L Trans", BOTH_T1_TR__L, Q_TR, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc top right to left + {"TR2BL Trans", BOTH_T1_TR_BL, Q_TR, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast weak spin top right to bottom left + {"T2BR Trans", BOTH_T1_T__BR, Q_T, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast arc top to bottom right + {"T2R Trans", BOTH_T1_T___R, Q_T, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc top to right + {"T2TR Trans", BOTH_T1_T__TR, Q_T, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc top to top right + {"T2TL Trans", BOTH_T1_T__TL, Q_T, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc top to top left + {"T2L Trans", BOTH_T1_T___L, Q_T, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc top to left + {"T2BL Trans", BOTH_T1_T__BL, Q_T, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast arc top to bottom left + {"TL2BR Trans", BOTH_T1_TL_BR, Q_TL, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast weak spin top left to bottom right + {"TL2R Trans", BOTH_T1_TL__R, Q_TL, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc top left to right (use: BOTH_T1__R_TL) + {"TL2TR Trans", BOTH_T1_TL_TR, Q_TL, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc top left to top right (use: BOTH_T1_TR_TL) + {"TL2T Trans", BOTH_T1_TL_T_, Q_TL, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc top left to top (use: BOTH_T1_T__TL) + {"TL2L Trans", BOTH_T1_TL__L, Q_TL, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc top left to left (use: BOTH_T1__L_TL) + {"TL2BL Trans", BOTH_T1_TL_BL, Q_TL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast arc top left to bottom left + {"L2BR Trans", BOTH_T1__L_BR, Q_L, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast weak spin left to bottom right + {"L2R Trans", BOTH_T1__L__R, Q_L, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast weak spin left to right + {"L2TR Trans", BOTH_T1__L_TR, Q_L, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc left to top right (use: BOTH_T1_TR__L) + {"L2T Trans", BOTH_T1__L_T_, Q_L, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc left to top (use: BOTH_T1_T___L) + {"L2TL Trans", BOTH_T1__L_TL, Q_L, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc left to top left + {"L2BL Trans", BOTH_T1__L_BL, Q_L, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast arc left to bottom left (use: BOTH_T1_BL__L) + {"BL2BR Trans", BOTH_T1_BL_BR, Q_BL, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast weak spin bottom left to bottom right + {"BL2R Trans", BOTH_T1_BL__R, Q_BL, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast weak spin bottom left to right + {"BL2TR Trans", BOTH_T1_BL_TR, Q_BL, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast weak spin bottom left to top right + {"BL2T Trans", BOTH_T1_BL_T_, Q_BL, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc bottom left to top (use: BOTH_T1_T__BL) + {"BL2TL Trans", BOTH_T1_BL_TL, Q_BL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc bottom left to top left (use: BOTH_T1_TL_BL) + {"BL2L Trans", BOTH_T1_BL__L, Q_BL, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc bottom left to left + + //Bounces + {"Bounce BR", BOTH_B1_BR___, Q_BR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_T1_BR_TR, 150 }, + {"Bounce R", BOTH_B1__R___, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_T1__R__L, 150 }, + {"Bounce TR", BOTH_B1_TR___, Q_TR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_TR_TL, 150 }, + {"Bounce T", BOTH_B1_T____, Q_T, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_T__BL, 150 }, + {"Bounce TL", BOTH_B1_TL___, Q_TL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_T1_TL_TR, 150 }, + {"Bounce L", BOTH_B1__L___, Q_L, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_T1__L__R, 150 }, + {"Bounce BL", BOTH_B1_BL___, Q_BL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_T1_BL_TR, 150 }, + + //Deflected attacks (like bounces, but slide off enemy saber, not straight back) + {"Deflect BR", BOTH_D1_BR___, Q_BR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_T1_BR_TR, 150 }, + {"Deflect R", BOTH_D1__R___, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_T1__R__L, 150 }, + {"Deflect TR", BOTH_D1_TR___, Q_TR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_TR_TL, 150 }, + {"Deflect T", BOTH_B1_T____, Q_T, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_T__BL, 150 }, + {"Deflect TL", BOTH_D1_TL___, Q_TL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_T1_TL_TR, 150 }, + {"Deflect L", BOTH_D1__L___, Q_L, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_T1__L__R, 150 }, + {"Deflect BL", BOTH_D1_BL___, Q_BL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_T1_BL_TR, 150 }, + {"Deflect B", BOTH_D1_B____, Q_B, Q_B, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_T__BL, 150 }, + + //Reflected attacks + {"Reflected BR",BOTH_V1_BR_S1, Q_BR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_BR + {"Reflected R", BOTH_V1__R_S1, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1__R + {"Reflected TR",BOTH_V1_TR_S1, Q_TR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_TR + {"Reflected T", BOTH_V1_T__S1, Q_T, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_T_ + {"Reflected TL",BOTH_V1_TL_S1, Q_TL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_TL + {"Reflected L", BOTH_V1__L_S1, Q_L, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1__L + {"Reflected BL",BOTH_V1_BL_S1, Q_BL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_BL + {"Reflected B", BOTH_V1_B__S1, Q_B, Q_B, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_B_ + + // Broken parries + {"BParry Top", BOTH_H1_S1_T_, Q_T, Q_B, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_UP, + {"BParry UR", BOTH_H1_S1_TR, Q_TR, Q_BL, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_UR, + {"BParry UL", BOTH_H1_S1_TL, Q_TL, Q_BR, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_UL, + {"BParry LR", BOTH_H1_S1_BL, Q_BL, Q_TR, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_LR, + {"BParry Bot", BOTH_H1_S1_B_, Q_B, Q_T, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_LL + {"BParry LL", BOTH_H1_S1_BR, Q_BR, Q_TL, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_LL + + // Knockaways + {"Knock Top", BOTH_K1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_T1_T__BR, 150 }, // LS_PARRY_UP, + {"Knock UR", BOTH_K1_S1_TR, Q_R, Q_TR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_T1_TR__R, 150 }, // LS_PARRY_UR, + {"Knock UL", BOTH_K1_S1_TL, Q_R, Q_TL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BR2TL, LS_T1_TL__L, 150 }, // LS_PARRY_UL, + {"Knock LR", BOTH_K1_S1_BL, Q_R, Q_BL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TL2BR, LS_T1_BL_TL, 150 }, // LS_PARRY_LR, + {"Knock LL", BOTH_K1_S1_BR, Q_R, Q_BR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TR2BL, LS_T1_BR_TR, 150 }, // LS_PARRY_LL + + // Parry + {"Parry Top", BOTH_P1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_T2B, 150 }, // LS_PARRY_UP, + {"Parry UR", BOTH_P1_S1_TR, Q_R, Q_TL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_TR2BL, 150 }, // LS_PARRY_UR, + {"Parry UL", BOTH_P1_S1_TL, Q_R, Q_TR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BR2TL, LS_A_TL2BR, 150 }, // LS_PARRY_UL, + {"Parry LR", BOTH_P1_S1_BL, Q_R, Q_BR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TL2BR, LS_A_BR2TL, 150 }, // LS_PARRY_LR, + {"Parry LL", BOTH_P1_S1_BR, Q_R, Q_BL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TR2BL, LS_A_BL2TR, 150 }, // LS_PARRY_LL + + // Reflecting a missile + {"Reflect Top", BOTH_P1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_T2B, 300 }, // LS_PARRY_UP, + {"Reflect UR", BOTH_P1_S1_TL, Q_R, Q_TR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BR2TL, LS_A_TL2BR, 300 }, // LS_PARRY_UR, + {"Reflect UL", BOTH_P1_S1_TR, Q_R, Q_TL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_TR2BL, 300 }, // LS_PARRY_UL, + {"Reflect LR", BOTH_P1_S1_BR, Q_R, Q_BL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TR2BL, LS_A_BL2TR, 300 }, // LS_PARRY_LR + {"Reflect LL", BOTH_P1_S1_BL, Q_R, Q_BR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TL2BR, LS_A_BR2TL, 300 }, // LS_PARRY_LL, +}; + + +saberMoveName_t transitionMove[Q_NUM_QUADS][Q_NUM_QUADS] = +{ + LS_NONE, //Can't transition to same pos! + LS_T1_BR__R,//40 + LS_T1_BR_TR, + LS_T1_BR_T_, + LS_T1_BR_TL, + LS_T1_BR__L, + LS_T1_BR_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1__R_BR,//46 + LS_NONE, //Can't transition to same pos! + LS_T1__R_TR, + LS_T1__R_T_, + LS_T1__R_TL, + LS_T1__R__L, + LS_T1__R_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_TR_BR,//52 + LS_T1_TR__R, + LS_NONE, //Can't transition to same pos! + LS_T1_TR_T_, + LS_T1_TR_TL, + LS_T1_TR__L, + LS_T1_TR_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_T__BR,//58 + LS_T1_T___R, + LS_T1_T__TR, + LS_NONE, //Can't transition to same pos! + LS_T1_T__TL, + LS_T1_T___L, + LS_T1_T__BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_TL_BR,//64 + LS_T1_TL__R, + LS_T1_TL_TR, + LS_T1_TL_T_, + LS_NONE, //Can't transition to same pos! + LS_T1_TL__L, + LS_T1_TL_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1__L_BR,//70 + LS_T1__L__R, + LS_T1__L_TR, + LS_T1__L_T_, + LS_T1__L_TL, + LS_NONE, //Can't transition to same pos! + LS_T1__L_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_BL_BR,//76 + LS_T1_BL__R, + LS_T1_BL_TR, + LS_T1_BL_T_, + LS_T1_BL_TL, + LS_T1_BL__L, + LS_NONE, //Can't transition to same pos! + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_BL_BR,//NOTE: there are no transitions from bottom, so re-use the bottom right transitions + LS_T1_BR__R, + LS_T1_BR_TR, + LS_T1_BR_T_, + LS_T1_BR_TL, + LS_T1_BR__L, + LS_T1_BR_BL, + LS_NONE //No transitions to bottom, and no anims start there, so shouldn't need any +}; + +void PM_VelocityForSaberMove( playerState_t *ps, vec3_t throwDir ) +{ + vec3_t vForward, vRight, vUp, startQ, endQ; + + AngleVectors( ps->viewangles, vForward, vRight, vUp ); + + switch ( saberMoveData[ps->saberMove].startQuad ) + { + case Q_BR: + VectorScale( vRight, 1, startQ ); + VectorMA( startQ, -1, vUp, startQ ); + break; + case Q_R: + VectorScale( vRight, 2, startQ ); + break; + case Q_TR: + VectorScale( vRight, 1, startQ ); + VectorMA( startQ, 1, vUp, startQ ); + break; + case Q_T: + VectorScale( vUp, 2, startQ ); + break; + case Q_TL: + VectorScale( vRight, -1, startQ ); + VectorMA( startQ, 1, vUp, startQ ); + break; + case Q_L: + VectorScale( vRight, -2, startQ ); + break; + case Q_BL: + VectorScale( vRight, -1, startQ ); + VectorMA( startQ, -1, vUp, startQ ); + break; + case Q_B: + VectorScale( vUp, -2, startQ ); + break; + } + switch ( saberMoveData[ps->saberMove].endQuad ) + { + case Q_BR: + VectorScale( vRight, 1, endQ ); + VectorMA( endQ, -1, vUp, endQ ); + break; + case Q_R: + VectorScale( vRight, 2, endQ ); + break; + case Q_TR: + VectorScale( vRight, 1, endQ ); + VectorMA( endQ, 1, vUp, endQ ); + break; + case Q_T: + VectorScale( vUp, 2, endQ ); + break; + case Q_TL: + VectorScale( vRight, -1, endQ ); + VectorMA( endQ, 1, vUp, endQ ); + break; + case Q_L: + VectorScale( vRight, -2, endQ ); + break; + case Q_BL: + VectorScale( vRight, -1, endQ ); + VectorMA( endQ, -1, vUp, endQ ); + break; + case Q_B: + VectorScale( vUp, -2, endQ ); + break; + } + VectorMA( endQ, 2, vForward, endQ ); + VectorScale( throwDir, 125, throwDir );//FIXME: pass in the throw strength? + VectorSubtract( endQ, startQ, throwDir ); +} + +qboolean PM_VelocityForBlockedMove( playerState_t *ps, vec3_t throwDir ) +{ + vec3_t vForward, vRight, vUp; + AngleVectors( ps->viewangles, vForward, vRight, vUp ); + switch ( ps->saberBlocked ) + { + case BLOCKED_UPPER_RIGHT: + VectorScale( vRight, 1, throwDir ); + VectorMA( throwDir, 1, vUp, throwDir ); + break; + case BLOCKED_UPPER_LEFT: + VectorScale( vRight, -1, throwDir ); + VectorMA( throwDir, 1, vUp, throwDir ); + break; + case BLOCKED_LOWER_RIGHT: + VectorScale( vRight, 1, throwDir ); + VectorMA( throwDir, -1, vUp, throwDir ); + break; + case BLOCKED_LOWER_LEFT: + VectorScale( vRight, -1, throwDir ); + VectorMA( throwDir, -1, vUp, throwDir ); + break; + case BLOCKED_TOP: + VectorScale( vUp, 2, throwDir ); + break; + default: + return qfalse; + break; + } + VectorMA( throwDir, 2, vForward, throwDir ); + VectorScale( throwDir, 250, throwDir );//FIXME: pass in the throw strength? + return qtrue; +} + +int PM_AnimLevelForSaberAnim( int anim ) +{ + if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_D1_B____ ) + { + return FORCE_LEVEL_1; + } + if ( anim >= BOTH_A2_T__B_ && anim <= BOTH_D2_B____ ) + { + return FORCE_LEVEL_2; + } + if ( anim >= BOTH_A3_T__B_ && anim <= BOTH_D3_B____ ) + { + return FORCE_LEVEL_3; + } + if ( anim >= BOTH_A4_T__B_ && anim <= BOTH_D4_B____ ) + {//desann + return FORCE_LEVEL_4; + } + if ( anim >= BOTH_A5_T__B_ && anim <= BOTH_D5_B____ ) + {//tavion + return FORCE_LEVEL_5; + } + if ( anim >= BOTH_A6_T__B_ && anim <= BOTH_D6_B____ ) + {//dual + return SS_DUAL; + } + if ( anim >= BOTH_A7_T__B_ && anim <= BOTH_D7_B____ ) + {//staff + return SS_STAFF; + } + return FORCE_LEVEL_0; +} + +int PM_PowerLevelForSaberAnim( playerState_t *ps, int saberNum ) +{ + int anim = ps->torsoAnim; + int animTimeElapsed = PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)anim ) - ps->torsoAnimTimer; + if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_D1_B____ ) + { + //FIXME: these two need their own style + if ( ps->saber[0].type == SABER_LANCE ) + { + return FORCE_LEVEL_4; + } + else if ( ps->saber[0].type == SABER_TRIDENT ) + { + return FORCE_LEVEL_3; + } + return FORCE_LEVEL_1; + } + if ( anim >= BOTH_A2_T__B_ && anim <= BOTH_D2_B____ ) + { + return FORCE_LEVEL_2; + } + if ( anim >= BOTH_A3_T__B_ && anim <= BOTH_D3_B____ ) + { + return FORCE_LEVEL_3; + } + if ( anim >= BOTH_A4_T__B_ && anim <= BOTH_D4_B____ ) + {//desann + return FORCE_LEVEL_4; + } + if ( anim >= BOTH_A5_T__B_ && anim <= BOTH_D5_B____ ) + {//tavion + return FORCE_LEVEL_2; + } + if ( anim >= BOTH_A6_T__B_ && anim <= BOTH_D6_B____ ) + {//dual + return FORCE_LEVEL_2; + } + if ( anim >= BOTH_A7_T__B_ && anim <= BOTH_D7_B____ ) + {//staff + return FORCE_LEVEL_2; + } + if ( ( anim >= BOTH_P1_S1_T_ && anim <= BOTH_P1_S1_BR ) + || ( anim >= BOTH_P6_S6_T_ && anim <= BOTH_P6_S6_BR ) + || ( anim >= BOTH_P7_S7_T_ && anim <= BOTH_P7_S7_BR ) ) + {//parries + switch ( ps->saberAnimLevel ) + { + case SS_STRONG: + case SS_DESANN: + return FORCE_LEVEL_3; + break; + case SS_TAVION: + case SS_STAFF: + case SS_DUAL: + case SS_MEDIUM: + return FORCE_LEVEL_2; + break; + case SS_FAST: + return FORCE_LEVEL_1; + break; + default: + return FORCE_LEVEL_0; + break; + } + } + if ( ( anim >= BOTH_K1_S1_T_ && anim <= BOTH_K1_S1_BR ) + || ( anim >= BOTH_K6_S6_T_ && anim <= BOTH_K6_S6_BR ) + || ( anim >= BOTH_K7_S7_T_ && anim <= BOTH_K7_S7_BR ) ) + {//knockaways + return FORCE_LEVEL_3; + } + if ( ( anim >= BOTH_V1_BR_S1 && anim <= BOTH_V1_B__S1 ) + || ( anim >= BOTH_V6_BR_S6 && anim <= BOTH_V6_B__S6 ) + || ( anim >= BOTH_V7_BR_S7 && anim <= BOTH_V7_B__S7 ) ) + {//knocked-away attacks + return FORCE_LEVEL_1; + } + if ( ( anim >= BOTH_H1_S1_T_ && anim <= BOTH_H1_S1_BR ) + || ( anim >= BOTH_H6_S6_T_ && anim <= BOTH_H6_S6_BR ) + || ( anim >= BOTH_H7_S7_T_ && anim <= BOTH_H7_S7_BR ) ) + {//broken parries + return FORCE_LEVEL_0; + } + switch ( anim ) + { + case BOTH_A2_STABBACK1: + if ( ps->torsoAnimTimer < 450 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 400 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_ATTACK_BACK: + if ( ps->torsoAnimTimer < 500 ) + {//end of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_CROUCHATTACKBACK1: + if ( ps->torsoAnimTimer < 800 ) + {//end of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + //FIXME: break up? + return FORCE_LEVEL_3; + break; + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + //FIXME: break up? + return FORCE_LEVEL_3; + break; + case BOTH_K1_S1_T_: //# knockaway saber top + case BOTH_K1_S1_TR: //# knockaway saber top right + case BOTH_K1_S1_TL: //# knockaway saber top left + case BOTH_K1_S1_BL: //# knockaway saber bottom left + case BOTH_K1_S1_B_: //# knockaway saber bottom + case BOTH_K1_S1_BR: //# knockaway saber bottom right + //FIXME: break up? + return FORCE_LEVEL_3; + break; + case BOTH_LUNGE2_B__T_: + if ( ps->torsoAnimTimer < 400 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 150 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_FORCELEAP2_T__B_: + if ( ps->torsoAnimTimer < 400 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 550 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_VS_ATR_S: + case BOTH_VS_ATL_S: + case BOTH_VT_ATR_S: + case BOTH_VT_ATL_S: + return FORCE_LEVEL_3;//??? + break; + case BOTH_JUMPFLIPSLASHDOWN1: + if ( ps->torsoAnimTimer <= 900 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 550 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_JUMPFLIPSTABDOWN: + if ( ps->torsoAnimTimer <= 1200 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed <= 250 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_JUMPATTACK6: + /* + if (pm->ps) + { + if ( ( pm->ps->legsAnimTimer >= 1450 + && PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - pm->ps->legsAnimTimer >= 400 ) + ||(pm->ps->legsAnimTimer >= 400 + && PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - pm->ps->legsAnimTimer >= 1100 ) ) + {//pretty much sideways + return FORCE_LEVEL_3; + } + } + */ + if ( ( ps->torsoAnimTimer >= 1450 + && animTimeElapsed >= 400 ) + ||(ps->torsoAnimTimer >= 400 + && animTimeElapsed >= 1100 ) ) + {//pretty much sideways + return FORCE_LEVEL_3; + } + return FORCE_LEVEL_0; + break; + case BOTH_JUMPATTACK7: + if ( ps->torsoAnimTimer <= 1200 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 200 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_SPINATTACK6: + if ( animTimeElapsed <= 200 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_SPINATTACK7: + if ( ps->torsoAnimTimer <= 500 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 500 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_FORCELONGLEAP_ATTACK: + if ( animTimeElapsed <= 200 ) + {//1st four frames of anim + return FORCE_LEVEL_3; + } + break; + /* + case BOTH_A7_KICK_F://these kicks attack, too + case BOTH_A7_KICK_B: + case BOTH_A7_KICK_R: + case BOTH_A7_KICK_L: + //FIXME: break up + return FORCE_LEVEL_3; + break; + */ + case BOTH_STABDOWN: + if ( ps->torsoAnimTimer <= 900 ) + {//end of anim + return FORCE_LEVEL_3; + } + break; + case BOTH_STABDOWN_STAFF: + if ( ps->torsoAnimTimer <= 850 ) + {//end of anim + return FORCE_LEVEL_3; + } + break; + case BOTH_STABDOWN_DUAL: + if ( ps->torsoAnimTimer <= 900 ) + {//end of anim + return FORCE_LEVEL_3; + } + break; + case BOTH_A6_SABERPROTECT: + if ( ps->torsoAnimTimer < 650 ) + {//end of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_A7_SOULCAL: + if ( ps->torsoAnimTimer < 650 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 600 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_A1_SPECIAL: + if ( ps->torsoAnimTimer < 600 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 200 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_A2_SPECIAL: + if ( ps->torsoAnimTimer < 300 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 200 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_A3_SPECIAL: + if ( ps->torsoAnimTimer < 700 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 200 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_FLIP_ATTACK7: + return FORCE_LEVEL_3; + break; + case BOTH_PULL_IMPALE_STAB: + if ( ps->torsoAnimTimer < 1000 ) + {//end of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_PULL_IMPALE_SWING: + if ( ps->torsoAnimTimer < 500 )//750 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 650 )//600 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_ALORA_SPIN_SLASH: + if ( ps->torsoAnimTimer < 900 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 250 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_A6_FB: + if ( ps->torsoAnimTimer < 250 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 250 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_A6_LR: + if ( ps->torsoAnimTimer < 250 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 250 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_A7_HILT: + return FORCE_LEVEL_0; + break; +//===SABERLOCK SUPERBREAKS START=========================================================================== + case BOTH_LK_S_DL_T_SB_1_W: + if ( ps->torsoAnimTimer < 700 ) + {//end of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_5; + break; + case BOTH_LK_S_ST_S_SB_1_W: + if ( ps->torsoAnimTimer < 300 ) + {//end of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_5; + break; + case BOTH_LK_S_DL_S_SB_1_W: + case BOTH_LK_S_S_S_SB_1_W: + if ( ps->torsoAnimTimer < 700 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 400 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_5; + break; + case BOTH_LK_S_ST_T_SB_1_W: + case BOTH_LK_S_S_T_SB_1_W: + if ( ps->torsoAnimTimer < 150 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 400 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_5; + break; + case BOTH_LK_DL_DL_T_SB_1_W: + return FORCE_LEVEL_5; + break; + case BOTH_LK_DL_DL_S_SB_1_W: + case BOTH_LK_DL_ST_S_SB_1_W: + if ( animTimeElapsed < 1000 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_5; + break; + case BOTH_LK_DL_ST_T_SB_1_W: + if ( ps->torsoAnimTimer < 950 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 650 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_5; + break; + case BOTH_LK_DL_S_S_SB_1_W: + if ( saberNum != 0 ) + {//only right hand saber does damage in this suberbreak + return FORCE_LEVEL_0; + } + if ( ps->torsoAnimTimer < 900 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 450 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_5; + break; + case BOTH_LK_DL_S_T_SB_1_W: + if ( saberNum != 0 ) + {//only right hand saber does damage in this suberbreak + return FORCE_LEVEL_0; + } + if ( ps->torsoAnimTimer < 250 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 150 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_5; + break; + case BOTH_LK_ST_DL_S_SB_1_W: + return FORCE_LEVEL_5; + break; + case BOTH_LK_ST_DL_T_SB_1_W: + //special suberbreak - doesn't kill, just kicks them backwards + return FORCE_LEVEL_0; + break; + case BOTH_LK_ST_ST_S_SB_1_W: + case BOTH_LK_ST_S_S_SB_1_W: + if ( ps->torsoAnimTimer < 800 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 350 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_5; + break; + case BOTH_LK_ST_ST_T_SB_1_W: + case BOTH_LK_ST_S_T_SB_1_W: + return FORCE_LEVEL_5; + break; +//===SABERLOCK SUPERBREAKS START=========================================================================== + case BOTH_HANG_ATTACK: + //FIME: break up + if ( ps->torsoAnimTimer < 1000 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 250 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + else + {//sweet spot + return FORCE_LEVEL_5; + } + break; + case BOTH_ROLL_STAB: + if ( animTimeElapsed > 400 ) + {//end of anim + return FORCE_LEVEL_0; + } + else + { + return FORCE_LEVEL_3; + } + break; + } + return FORCE_LEVEL_0; +} + +qboolean PM_InAnimForSaberMove( int anim, int saberMove ) +{ + switch ( anim ) + {//special case anims + case BOTH_A2_STABBACK1: + case BOTH_ATTACK_BACK: + case BOTH_CROUCHATTACKBACK1: + case BOTH_ROLL_STAB: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_LUNGE2_B__T_: + case BOTH_FORCELEAP2_T__B_: + case BOTH_JUMPFLIPSLASHDOWN1://# + case BOTH_JUMPFLIPSTABDOWN://# + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + case BOTH_SPINATTACK6: + case BOTH_SPINATTACK7: + case BOTH_VS_ATR_S: + case BOTH_VS_ATL_S: + case BOTH_VT_ATR_S: + case BOTH_VT_ATL_S: + case BOTH_FORCELONGLEAP_ATTACK: + case BOTH_A7_KICK_F: + case BOTH_A7_KICK_B: + case BOTH_A7_KICK_R: + case BOTH_A7_KICK_L: + case BOTH_A7_KICK_S: + case BOTH_A7_KICK_BF: + case BOTH_A7_KICK_RL: + case BOTH_A7_KICK_F_AIR: + case BOTH_A7_KICK_B_AIR: + case BOTH_A7_KICK_R_AIR: + case BOTH_A7_KICK_L_AIR: + case BOTH_STABDOWN: + case BOTH_STABDOWN_STAFF: + case BOTH_STABDOWN_DUAL: + case BOTH_A6_SABERPROTECT: + case BOTH_A7_SOULCAL: + case BOTH_A1_SPECIAL: + case BOTH_A2_SPECIAL: + case BOTH_A3_SPECIAL: + case BOTH_FLIP_ATTACK7: + case BOTH_PULL_IMPALE_STAB: + case BOTH_PULL_IMPALE_SWING: + case BOTH_ALORA_SPIN_SLASH: + case BOTH_A6_FB: + case BOTH_A6_LR: + case BOTH_A7_HILT: + case BOTH_LK_S_DL_S_SB_1_W: + case BOTH_LK_S_DL_T_SB_1_W: + case BOTH_LK_S_ST_S_SB_1_W: + case BOTH_LK_S_ST_T_SB_1_W: + case BOTH_LK_S_S_S_SB_1_W: + case BOTH_LK_S_S_T_SB_1_W: + case BOTH_LK_DL_DL_S_SB_1_W: + case BOTH_LK_DL_DL_T_SB_1_W: + case BOTH_LK_DL_ST_S_SB_1_W: + case BOTH_LK_DL_ST_T_SB_1_W: + case BOTH_LK_DL_S_S_SB_1_W: + case BOTH_LK_DL_S_T_SB_1_W: + case BOTH_LK_ST_DL_S_SB_1_W: + case BOTH_LK_ST_DL_T_SB_1_W: + case BOTH_LK_ST_ST_S_SB_1_W: + case BOTH_LK_ST_ST_T_SB_1_W: + case BOTH_LK_ST_S_S_SB_1_W: + case BOTH_LK_ST_S_T_SB_1_W: + case BOTH_HANG_ATTACK: + return qtrue; + } + if ( PM_SaberDrawPutawayAnim( anim ) ) + { + if ( saberMove == LS_DRAW || saberMove == LS_PUTAWAY ) + { + return qtrue; + } + return qfalse; + } + else if ( PM_SaberStanceAnim( anim ) ) + { + if ( saberMove == LS_READY ) + { + return qtrue; + } + return qfalse; + } + int animLevel = PM_AnimLevelForSaberAnim( anim ); + if ( animLevel <= 0 ) + {//NOTE: this will always return false for the ready poses and putaway/draw... + return qfalse; + } + //drop the anim to the first level and start the checks there + anim -= (animLevel-FORCE_LEVEL_1)*SABER_ANIM_GROUP_SIZE; + //check level 1 + if ( anim == saberMoveData[saberMove].animToUse ) + { + return qtrue; + } + //check level 2 + anim += SABER_ANIM_GROUP_SIZE; + if ( anim == saberMoveData[saberMove].animToUse ) + { + return qtrue; + } + //check level 3 + anim += SABER_ANIM_GROUP_SIZE; + if ( anim == saberMoveData[saberMove].animToUse ) + { + return qtrue; + } + //check level 4 + anim += SABER_ANIM_GROUP_SIZE; + if ( anim == saberMoveData[saberMove].animToUse ) + { + return qtrue; + } + //check level 5 + anim += SABER_ANIM_GROUP_SIZE; + if ( anim == saberMoveData[saberMove].animToUse ) + { + return qtrue; + } + if ( anim >= BOTH_P1_S1_T_ && anim <= BOTH_H1_S1_BR ) + {//parries, knockaways and broken parries + return (anim==saberMoveData[saberMove].animToUse); + } + return qfalse; +} + +qboolean PM_SaberInIdle( int move ) +{ + switch ( move ) + { + case LS_NONE: + case LS_READY: + case LS_DRAW: + case LS_PUTAWAY: + return qtrue; + break; + } + return qfalse; +} +qboolean PM_SaberInSpecialAttack( int anim ) +{ + switch ( anim ) + { + case BOTH_A2_STABBACK1: + case BOTH_ATTACK_BACK: + case BOTH_CROUCHATTACKBACK1: + case BOTH_ROLL_STAB: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_LUNGE2_B__T_: + case BOTH_FORCELEAP2_T__B_: + case BOTH_JUMPFLIPSLASHDOWN1://# + case BOTH_JUMPFLIPSTABDOWN://# + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + case BOTH_SPINATTACK6: + case BOTH_SPINATTACK7: + case BOTH_FORCELONGLEAP_ATTACK: + case BOTH_VS_ATR_S: + case BOTH_VS_ATL_S: + case BOTH_VT_ATR_S: + case BOTH_VT_ATL_S: + case BOTH_A7_KICK_F: + case BOTH_A7_KICK_B: + case BOTH_A7_KICK_R: + case BOTH_A7_KICK_L: + case BOTH_A7_KICK_S: + case BOTH_A7_KICK_BF: + case BOTH_A7_KICK_RL: + case BOTH_A7_KICK_F_AIR: + case BOTH_A7_KICK_B_AIR: + case BOTH_A7_KICK_R_AIR: + case BOTH_A7_KICK_L_AIR: + case BOTH_STABDOWN: + case BOTH_STABDOWN_STAFF: + case BOTH_STABDOWN_DUAL: + case BOTH_A6_SABERPROTECT: + case BOTH_A7_SOULCAL: + case BOTH_A1_SPECIAL: + case BOTH_A2_SPECIAL: + case BOTH_A3_SPECIAL: + case BOTH_FLIP_ATTACK7: + case BOTH_PULL_IMPALE_STAB: + case BOTH_PULL_IMPALE_SWING: + case BOTH_ALORA_SPIN_SLASH: + case BOTH_A6_FB: + case BOTH_A6_LR: + case BOTH_A7_HILT: + case BOTH_LK_S_DL_S_SB_1_W: + case BOTH_LK_S_DL_T_SB_1_W: + case BOTH_LK_S_ST_S_SB_1_W: + case BOTH_LK_S_ST_T_SB_1_W: + case BOTH_LK_S_S_S_SB_1_W: + case BOTH_LK_S_S_T_SB_1_W: + case BOTH_LK_DL_DL_S_SB_1_W: + case BOTH_LK_DL_DL_T_SB_1_W: + case BOTH_LK_DL_ST_S_SB_1_W: + case BOTH_LK_DL_ST_T_SB_1_W: + case BOTH_LK_DL_S_S_SB_1_W: + case BOTH_LK_DL_S_T_SB_1_W: + case BOTH_LK_ST_DL_S_SB_1_W: + case BOTH_LK_ST_DL_T_SB_1_W: + case BOTH_LK_ST_ST_S_SB_1_W: + case BOTH_LK_ST_ST_T_SB_1_W: + case BOTH_LK_ST_S_S_SB_1_W: + case BOTH_LK_ST_S_T_SB_1_W: + case BOTH_HANG_ATTACK: + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInAttack( int move ) +{ + if ( move >= LS_A_TL2BR && move <= LS_A_T2B ) + { + return qtrue; + } + switch ( move ) + { + case LS_A_BACK: + case LS_A_BACK_CR: + case LS_A_BACKSTAB: + case LS_ROLL_STAB: + case LS_A_LUNGE: + case LS_A_JUMP_T__B_: + case LS_A_FLIP_STAB: + case LS_A_FLIP_SLASH: + case LS_JUMPATTACK_DUAL: + case LS_JUMPATTACK_ARIAL_LEFT: + case LS_JUMPATTACK_ARIAL_RIGHT: + case LS_JUMPATTACK_CART_LEFT: + case LS_JUMPATTACK_CART_RIGHT: + case LS_JUMPATTACK_STAFF_LEFT: + case LS_JUMPATTACK_STAFF_RIGHT: + case LS_BUTTERFLY_LEFT: + case LS_BUTTERFLY_RIGHT: + case LS_A_BACKFLIP_ATK: + case LS_SPINATTACK_DUAL: + case LS_SPINATTACK: + case LS_LEAP_ATTACK: + case LS_SWOOP_ATTACK_RIGHT: + case LS_SWOOP_ATTACK_LEFT: + case LS_TAUNTAUN_ATTACK_RIGHT: + case LS_TAUNTAUN_ATTACK_LEFT: + case LS_KICK_F: + case LS_KICK_B: + case LS_KICK_R: + case LS_KICK_L: + case LS_KICK_S: + case LS_KICK_BF: + case LS_KICK_RL: + case LS_KICK_F_AIR: + case LS_KICK_B_AIR: + case LS_KICK_R_AIR: + case LS_KICK_L_AIR: + case LS_STABDOWN: + case LS_STABDOWN_STAFF: + case LS_STABDOWN_DUAL: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_UPSIDE_DOWN_ATTACK: + case LS_PULL_ATTACK_STAB: + case LS_PULL_ATTACK_SWING: + case LS_SPINATTACK_ALORA: + case LS_DUAL_FB: + case LS_DUAL_LR: + case LS_HILT_BASH: + return qtrue; + break; + } + return qfalse; +} +qboolean PM_SaberInTransition( int move ) +{ + if ( move >= LS_T1_BR__R && move <= LS_T1_BL__L ) + { + return qtrue; + } + return qfalse; +} +qboolean PM_SaberInStart( int move ) +{ + if ( move >= LS_S_TL2BR && move <= LS_S_T2B ) + { + return qtrue; + } + return qfalse; +} +qboolean PM_SaberInReturn( int move ) +{ + if ( move >= LS_R_TL2BR && move <= LS_R_T2B ) + { + return qtrue; + } + return qfalse; +} +qboolean PM_SaberInTransitionAny( int move ) +{ + if ( PM_SaberInStart( move ) ) + { + return qtrue; + } + else if ( PM_SaberInTransition( move ) ) + { + return qtrue; + } + else if ( PM_SaberInReturn( move ) ) + { + return qtrue; + } + return qfalse; +} +qboolean PM_SaberInBounce( int move ) +{ + if ( move >= LS_B1_BR && move <= LS_B1_BL ) + { + return qtrue; + } + if ( move >= LS_D1_BR && move <= LS_D1_BL ) + { + return qtrue; + } + return qfalse; +} +qboolean PM_SaberInBrokenParry( int move ) +{ + if ( move >= LS_V1_BR && move <= LS_V1_B_ ) + { + return qtrue; + } + if ( move >= LS_H1_T_ && move <= LS_H1_BL ) + { + return qtrue; + } + return qfalse; +} +qboolean PM_SaberInDeflect( int move ) +{ + if ( move >= LS_D1_BR && move <= LS_D1_B_ ) + { + return qtrue; + } + return qfalse; +} +qboolean PM_SaberInParry( int move ) +{ + if ( move >= LS_PARRY_UP && move <= LS_PARRY_LL ) + { + return qtrue; + } + return qfalse; +} +qboolean PM_SaberInKnockaway( int move ) +{ + if ( move >= LS_K1_T_ && move <= LS_K1_BL ) + { + return qtrue; + } + return qfalse; +} +qboolean PM_SaberInReflect( int move ) +{ + if ( move >= LS_REFLECT_UP && move <= LS_REFLECT_LL ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInSpecial( int move ) +{ + switch( move ) + { + case LS_A_BACK: + case LS_A_BACK_CR: + case LS_A_BACKSTAB: + case LS_ROLL_STAB: + case LS_A_LUNGE: + case LS_A_JUMP_T__B_: + case LS_A_FLIP_STAB: + case LS_A_FLIP_SLASH: + case LS_JUMPATTACK_DUAL: + case LS_JUMPATTACK_ARIAL_LEFT: + case LS_JUMPATTACK_ARIAL_RIGHT: + case LS_JUMPATTACK_CART_LEFT: + case LS_JUMPATTACK_CART_RIGHT: + case LS_JUMPATTACK_STAFF_LEFT: + case LS_JUMPATTACK_STAFF_RIGHT: + case LS_BUTTERFLY_LEFT: + case LS_BUTTERFLY_RIGHT: + case LS_A_BACKFLIP_ATK: + case LS_SPINATTACK_DUAL: + case LS_SPINATTACK: + case LS_LEAP_ATTACK: + case LS_SWOOP_ATTACK_RIGHT: + case LS_SWOOP_ATTACK_LEFT: + case LS_TAUNTAUN_ATTACK_RIGHT: + case LS_TAUNTAUN_ATTACK_LEFT: + case LS_KICK_F: + case LS_KICK_B: + case LS_KICK_R: + case LS_KICK_L: + case LS_KICK_S: + case LS_KICK_BF: + case LS_KICK_RL: + case LS_KICK_F_AIR: + case LS_KICK_B_AIR: + case LS_KICK_R_AIR: + case LS_KICK_L_AIR: + case LS_STABDOWN: + case LS_STABDOWN_STAFF: + case LS_STABDOWN_DUAL: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_UPSIDE_DOWN_ATTACK: + case LS_PULL_ATTACK_STAB: + case LS_PULL_ATTACK_SWING: + case LS_SPINATTACK_ALORA: + case LS_DUAL_FB: + case LS_DUAL_LR: + case LS_HILT_BASH: + return qtrue; + } + return qfalse; +} + +qboolean PM_KickMove( int move ) +{ + switch( move ) + { + case LS_KICK_F: + case LS_KICK_B: + case LS_KICK_R: + case LS_KICK_L: + case LS_KICK_S: + case LS_KICK_BF: + case LS_KICK_RL: + case LS_HILT_BASH: + case LS_KICK_F_AIR: + case LS_KICK_B_AIR: + case LS_KICK_R_AIR: + case LS_KICK_L_AIR: + return qtrue; + } + return qfalse; +} + +saberMoveName_t PM_BrokenParryForAttack( int move ) +{ + //Our attack was knocked away by a knockaway parry + //FIXME: need actual anims for this + //FIXME: need to know which side of the saber was hit! For now, we presume the saber gets knocked away from the center + switch ( saberMoveData[move].startQuad ) + { + case Q_B: + return LS_V1_B_; + break; + case Q_BR: + return LS_V1_BR; + break; + case Q_R: + return LS_V1__R; + break; + case Q_TR: + return LS_V1_TR; + break; + case Q_T: + return LS_V1_T_; + break; + case Q_TL: + return LS_V1_TL; + break; + case Q_L: + return LS_V1__L; + break; + case Q_BL: + return LS_V1_BL; + break; + } + return LS_NONE; +} + +saberMoveName_t PM_BrokenParryForParry( int move ) +{ + //FIXME: need actual anims for this + //FIXME: need to know which side of the saber was hit! For now, we presume the saber gets knocked away from the center + switch ( move ) + { + case LS_PARRY_UP: + //Hmm... since we don't know what dir the hit came from, randomly pick knock down or knock back + if ( Q_irand( 0, 1 ) ) + { + return LS_H1_B_; + } + else + { + return LS_H1_T_; + } + break; + case LS_PARRY_UR: + return LS_H1_TR; + break; + case LS_PARRY_UL: + return LS_H1_TL; + break; + case LS_PARRY_LR: + return LS_H1_BR; + break; + case LS_PARRY_LL: + return LS_H1_BL; + break; + case LS_READY: + return LS_H1_B_;//??? + break; + } + return LS_NONE; +} + +saberMoveName_t PM_KnockawayForParry( int move ) +{ + //FIXME: need actual anims for this + //FIXME: need to know which side of the saber was hit! For now, we presume the saber gets knocked away from the center + switch ( move ) + { + case BLOCKED_TOP://LS_PARRY_UP: + return LS_K1_T_;//push up + break; + case BLOCKED_UPPER_RIGHT://LS_PARRY_UR: + default://case LS_READY: + return LS_K1_TR;//push up, slightly to right + break; + case BLOCKED_UPPER_LEFT://LS_PARRY_UL: + return LS_K1_TL;//push up and to left + break; + case BLOCKED_LOWER_RIGHT://LS_PARRY_LR: + return LS_K1_BR;//push down and to left + break; + case BLOCKED_LOWER_LEFT://LS_PARRY_LL: + return LS_K1_BL;//push down and to right + break; + } + //return LS_NONE; +} + +saberMoveName_t PM_SaberBounceForAttack( int move ) +{ + switch ( saberMoveData[move].startQuad ) + { + case Q_B: + case Q_BR: + return LS_B1_BR; + break; + case Q_R: + return LS_B1__R; + break; + case Q_TR: + return LS_B1_TR; + break; + case Q_T: + return LS_B1_T_; + break; + case Q_TL: + return LS_B1_TL; + break; + case Q_L: + return LS_B1__L; + break; + case Q_BL: + return LS_B1_BL; + break; + } + return LS_NONE; +} + +saberMoveName_t PM_AttackMoveForQuad( int quad ) +{ + switch ( quad ) + { + case Q_B: + case Q_BR: + return LS_A_BR2TL; + break; + case Q_R: + return LS_A_R2L; + break; + case Q_TR: + return LS_A_TR2BL; + break; + case Q_T: + return LS_A_T2B; + break; + case Q_TL: + return LS_A_TL2BR; + break; + case Q_L: + return LS_A_L2R; + break; + case Q_BL: + return LS_A_BL2TR; + break; + } + return LS_NONE; +} + +int saberMoveTransitionAngle[Q_NUM_QUADS][Q_NUM_QUADS] = +{ + 0,//Q_BR,Q_BR, + 45,//Q_BR,Q_R, + 90,//Q_BR,Q_TR, + 135,//Q_BR,Q_T, + 180,//Q_BR,Q_TL, + 215,//Q_BR,Q_L, + 270,//Q_BR,Q_BL, + 45,//Q_BR,Q_B, + 45,//Q_R,Q_BR, + 0,//Q_R,Q_R, + 45,//Q_R,Q_TR, + 90,//Q_R,Q_T, + 135,//Q_R,Q_TL, + 180,//Q_R,Q_L, + 215,//Q_R,Q_BL, + 90,//Q_R,Q_B, + 90,//Q_TR,Q_BR, + 45,//Q_TR,Q_R, + 0,//Q_TR,Q_TR, + 45,//Q_TR,Q_T, + 90,//Q_TR,Q_TL, + 135,//Q_TR,Q_L, + 180,//Q_TR,Q_BL, + 135,//Q_TR,Q_B, + 135,//Q_T,Q_BR, + 90,//Q_T,Q_R, + 45,//Q_T,Q_TR, + 0,//Q_T,Q_T, + 45,//Q_T,Q_TL, + 90,//Q_T,Q_L, + 135,//Q_T,Q_BL, + 180,//Q_T,Q_B, + 180,//Q_TL,Q_BR, + 135,//Q_TL,Q_R, + 90,//Q_TL,Q_TR, + 45,//Q_TL,Q_T, + 0,//Q_TL,Q_TL, + 45,//Q_TL,Q_L, + 90,//Q_TL,Q_BL, + 135,//Q_TL,Q_B, + 215,//Q_L,Q_BR, + 180,//Q_L,Q_R, + 135,//Q_L,Q_TR, + 90,//Q_L,Q_T, + 45,//Q_L,Q_TL, + 0,//Q_L,Q_L, + 45,//Q_L,Q_BL, + 90,//Q_L,Q_B, + 270,//Q_BL,Q_BR, + 215,//Q_BL,Q_R, + 180,//Q_BL,Q_TR, + 135,//Q_BL,Q_T, + 90,//Q_BL,Q_TL, + 45,//Q_BL,Q_L, + 0,//Q_BL,Q_BL, + 45,//Q_BL,Q_B, + 45,//Q_B,Q_BR, + 90,//Q_B,Q_R, + 135,//Q_B,Q_TR, + 180,//Q_B,Q_T, + 135,//Q_B,Q_TL, + 90,//Q_B,Q_L, + 45,//Q_B,Q_BL, + 0//Q_B,Q_B, +}; + +int PM_SaberAttackChainAngle( int move1, int move2 ) +{ + if ( move1 == -1 || move2 == -1 ) + { + return -1; + } + return saberMoveTransitionAngle[saberMoveData[move1].endQuad][saberMoveData[move2].startQuad]; +} + +qboolean PM_SaberKataDone( int curmove = LS_NONE, int newmove = LS_NONE ) +{ + if ( pm->ps->forceRageRecoveryTime > level.time ) + {//rage recovery, only 1 swing at a time (tired) + if ( pm->ps->saberAttackChainCount > 0 ) + {//swung once + return qtrue; + } + else + {//allow one attack + return qfalse; + } + } + else if ( (pm->ps->forcePowersActive&(1<ps->saber[0].maxChain == -1 ) + { + return qfalse; + } + else if ( pm->ps->saber[0].maxChain != 0 ) + { + if ( pm->ps->saberAttackChainCount >= pm->ps->saber[0].maxChain ) + { + return qtrue; + } + else + { + return qfalse; + } + } + + if ( pm->ps->saberAnimLevel == SS_DESANN || pm->ps->saberAnimLevel == SS_TAVION ) + {//desann and tavion can link up as many attacks as they want + return qfalse; + } + //FIXME: instead of random, apply some sort of logical conditions to whether or + // not you can chain? Like if you were completely missed, you can't chain as much, or...? + // And/Or based on FP_SABER_OFFENSE level? So number of attacks you can chain + // increases with your FP_SABER_OFFENSE skill? + if ( pm->ps->saberAnimLevel == SS_STAFF ) + { + //TEMP: for now, let staff attacks infinitely chain + return qfalse; + /* + if ( pm->ps->saberAttackChainCount > Q_irand( 3, 4 ) ) + { + return qtrue; + } + else if ( pm->ps->saberAttackChainCount > 0 ) + { + int chainAngle = PM_SaberAttackChainAngle( curmove, newmove ); + if ( chainAngle < 135 || chainAngle > 215 ) + {//if trying to chain to a move that doesn't continue the momentum + if ( pm->ps->saberAttackChainCount > 1 ) + { + return qtrue; + } + } + else if ( chainAngle == 180 ) + {//continues the momentum perfectly, allow it to chain 66% of the time + if ( pm->ps->saberAttackChainCount > 2 ) + { + return qtrue; + } + } + else + {//would continue the movement somewhat, 50% chance of continuing + if ( pm->ps->saberAttackChainCount > 3 ) + { + return qtrue; + } + } + } + */ + } + else if ( pm->ps->saberAnimLevel == SS_DUAL ) + { + //TEMP: for now, let staff attacks infinitely chain + return qfalse; + } + else if ( pm->ps->saberAnimLevel == FORCE_LEVEL_3 ) + { + if ( curmove == LS_NONE || newmove == LS_NONE ) + { + if ( pm->ps->saberAnimLevel >= FORCE_LEVEL_3 && pm->ps->saberAttackChainCount > Q_irand( 0, 1 ) ) + { + return qtrue; + } + } + else if ( pm->ps->saberAttackChainCount > Q_irand( 2, 3 ) ) + { + return qtrue; + } + else if ( pm->ps->saberAttackChainCount > 0 ) + { + int chainAngle = PM_SaberAttackChainAngle( curmove, newmove ); + if ( chainAngle < 135 || chainAngle > 215 ) + {//if trying to chain to a move that doesn't continue the momentum + return qtrue; + } + else if ( chainAngle == 180 ) + {//continues the momentum perfectly, allow it to chain 66% of the time + if ( pm->ps->saberAttackChainCount > 1 ) + { + return qtrue; + } + } + else + {//would continue the movement somewhat, 50% chance of continuing + if ( pm->ps->saberAttackChainCount > 2 ) + { + return qtrue; + } + } + } + } + else + {//FIXME: have chainAngle influence fast and medium chains as well? + if ( (pm->ps->saberAnimLevel == FORCE_LEVEL_2 || pm->ps->saberAnimLevel == SS_DUAL) + && pm->ps->saberAttackChainCount > Q_irand( 2, 5 ) ) + { + return qtrue; + } + } + return qfalse; +} + +qboolean PM_CheckEnemyInBack( float backCheckDist ) +{ + if ( !pm->gent || !pm->gent->client ) + { + return qfalse; + } + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + && !g_saberAutoAim->integer && pm->cmd.forwardmove >= 0 ) + {//don't auto-backstab + return qfalse; + } + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//only when on ground + return qfalse; + } + trace_t trace; + vec3_t end, fwd, fwdAngles = {0,pm->ps->viewangles[YAW],0}; + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + VectorMA( pm->ps->origin, -backCheckDist, fwd, end ); + + pm->trace( &trace, pm->ps->origin, vec3_origin, vec3_origin, end, pm->ps->clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + if ( trace.fraction < 1.0f && trace.entityNum < ENTITYNUM_WORLD ) + { + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( traceEnt + && traceEnt->health > 0 + && traceEnt->client + && traceEnt->client->playerTeam == pm->gent->client->enemyTeam + && traceEnt->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + {//player + if ( pm->gent ) + {//set player enemy to traceEnt so he auto-aims at him + pm->gent->enemy = traceEnt; + } + } + return qtrue; + } + } + return qfalse; +} + +saberMoveName_t PM_PickBackStab( void ) +{ + if ( !pm->gent || !pm->gent->client ) + { + return LS_READY; + } + if ( pm->ps->dualSabers + && pm->ps->saber[1].Active() ) + { + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + return LS_A_BACK_CR; + } + else + { + return LS_A_BACK; + } + } + if ( pm->gent->client->ps.saberAnimLevel == SS_TAVION ) + { + return LS_A_BACKSTAB; + } + else if ( pm->gent->client->ps.saberAnimLevel == SS_DESANN ) + { + if ( pm->ps->saberMove == LS_READY || !Q_irand( 0, 3 ) ) + { + return LS_A_BACKSTAB; + } + else if ( pm->ps->pm_flags & PMF_DUCKED ) + { + return LS_A_BACK_CR; + } + else + { + return LS_A_BACK; + } + } + else if ( pm->ps->saberAnimLevel == FORCE_LEVEL_2 + || pm->ps->saberAnimLevel == SS_DUAL ) + {//using medium attacks or dual sabers + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + return LS_A_BACK_CR; + } + else + { + return LS_A_BACK; + } + } + else + { + return LS_A_BACKSTAB; + } +} + +saberMoveName_t PM_CheckStabDown( void ) +{ + if ( !pm->gent || !pm->gent->enemy || !pm->gent->enemy->client ) + { + return LS_NONE; + } + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) //PLAYER ONLY + {//player + if ( G_TryingKataAttack( pm->gent, &pm->cmd ) )//(pm->cmd.buttons&BUTTON_FORCE_FOCUS) )//Holding focus + {//want to try a special + return LS_NONE; + } + } + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + {//player + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE )//in air + {//sorry must be on ground (or have just jumped) + if ( level.time-pm->ps->lastOnGround <= 50 && (pm->ps->pm_flags&PMF_JUMPING) ) + {//just jumped, it's okay + } + else + { + return LS_NONE; + } + } + /* + if ( pm->cmd.upmove > 0 ) + {//trying to jump + } + else if ( pm->ps->groundEntityNum == ENTITYNUM_NONE //in air + && level.time-pm->ps->lastOnGround <= 250 //just left ground + && (pm->ps->pm_flags&PMF_JUMPING) )//jumped + {//just jumped + } + else + { + return LS_NONE; + } + */ + pm->ps->velocity[2] = 0; + pm->cmd.upmove = 0; + } + else if ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) ) + {//NPC + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE )//in air + {//sorry must be on ground (or have just jumped) + if ( level.time-pm->ps->lastOnGround <= 250 && (pm->ps->pm_flags&PMF_JUMPING) ) + {//just jumped, it's okay + } + else + { + return LS_NONE; + } + } + if ( !pm->gent->NPC ) + {//wtf??? + return LS_NONE; + } + if ( Q_irand( 0, RANK_CAPTAIN ) > pm->gent->NPC->rank ) + {//lower ranks do this less often + return LS_NONE; + } + } + vec3_t enemyDir, faceFwd, facingAngles = {0, pm->ps->viewangles[YAW], 0}; + AngleVectors( facingAngles, faceFwd, NULL, NULL ); + VectorSubtract( pm->gent->enemy->currentOrigin, pm->ps->origin, enemyDir ); + float enemyZDiff = enemyDir[2]; + enemyDir[2] = 0; + float enemyHDist = VectorNormalize( enemyDir )-(pm->gent->maxs[0]+pm->gent->enemy->maxs[0]); + float dot = DotProduct( enemyDir, faceFwd ); + + if ( //(pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer()) + dot > 0.65f + //&& enemyHDist >= 32 //was 48 + && enemyHDist <= 164//was 112 + && PM_InKnockDownOnGround( &pm->gent->enemy->client->ps )//still on ground + && !PM_InGetUpNoRoll( &pm->gent->enemy->client->ps )//not getting up yet + && enemyZDiff <= 20 ) + {//guy is on the ground below me, do a top-down attack + if ( pm->gent->enemy->s.number >= MAX_CLIENTS + || !G_ControlledByPlayer( pm->gent->enemy ) ) + {//don't get up while I'm doing this + //stop them from trying to get up for at least another 3 seconds + TIMER_Set( pm->gent->enemy, "noGetUpStraight", 3000 ); + } + //pick the right anim + if ( pm->ps->saberAnimLevel == SS_DUAL + || (pm->ps->dualSabers&&pm->ps->saber[1].Active()) ) + { + return LS_STABDOWN_DUAL; + } + else if ( pm->ps->saberAnimLevel == SS_STAFF ) + { + return LS_STABDOWN_STAFF; + } + else + { + return LS_STABDOWN; + } + } + return LS_NONE; +} + +extern saberMoveName_t PM_NPCSaberAttackFromQuad( int quad ); +saberMoveName_t PM_SaberFlipOverAttackMove( void ); +saberMoveName_t PM_AttackForEnemyPos( qboolean allowFB, qboolean allowStabDown ) +{ + saberMoveName_t autoMove = LS_INVALID; + + if( !pm->gent->enemy ) + { + return LS_NONE; + } + + vec3_t enemy_org, enemyDir, faceFwd, faceRight, faceUp, facingAngles = {0, pm->ps->viewangles[YAW], 0}; + AngleVectors( facingAngles, faceFwd, faceRight, faceUp ); + //FIXME: predict enemy position? + if ( pm->gent->enemy->client ) + { + //VectorCopy( pm->gent->enemy->currentOrigin, enemy_org ); + //HMM... using this will adjust for bbox size, so let's do that... + vec3_t size; + VectorSubtract( pm->gent->enemy->absmax, pm->gent->enemy->absmin, size ); + VectorMA( pm->gent->enemy->absmin, 0.5, size, enemy_org ); + + VectorSubtract( pm->gent->enemy->client->renderInfo.eyePoint, pm->ps->origin, enemyDir ); + } + else + { + if ( pm->gent->enemy->bmodel && VectorCompare( vec3_origin, pm->gent->enemy->currentOrigin ) ) + {//a brush model without an origin brush + vec3_t size; + VectorSubtract( pm->gent->enemy->absmax, pm->gent->enemy->absmin, size ); + VectorMA( pm->gent->enemy->absmin, 0.5, size, enemy_org ); + } + else + { + VectorCopy( pm->gent->enemy->currentOrigin, enemy_org ); + } + VectorSubtract( enemy_org, pm->ps->origin, enemyDir ); + } + float enemyZDiff = enemyDir[2]; + float enemyDist = VectorNormalize( enemyDir ); + float dot = DotProduct( enemyDir, faceFwd ); + if ( dot > 0 ) + {//enemy is in front + if ( allowStabDown ) + {//okay to try this + saberMoveName_t stabDownMove = PM_CheckStabDown(); + if ( stabDownMove != LS_NONE ) + { + return stabDownMove; + } + } + if ( (pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer()) + && dot > 0.65f + && enemyDist <= 64 && pm->gent->enemy->client + && (enemyZDiff <= 20 || PM_InKnockDownOnGround( &pm->gent->enemy->client->ps ) || PM_CrouchAnim( pm->gent->enemy->client->ps.legsAnim ) ) ) + {//swing down at them + return LS_A_T2B; + } + if ( allowFB ) + {//directly in front anim allowed + if ( enemyDist > 200 || pm->gent->enemy->health <= 0 ) + {//hmm, look in back for an enemy + if ( pm->ps->clientNum && !PM_ControlledByPlayer() ) + {//player should never do this automatically + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//only when on ground + if ( pm->gent && pm->gent->client && pm->gent->NPC && pm->gent->NPC->rank >= RANK_LT_JG && Q_irand( 0, pm->gent->NPC->rank ) > RANK_ENSIGN ) + {//only fencers and higher can do this, higher rank does it more + if ( PM_CheckEnemyInBack( 100 ) ) + { + return PM_PickBackStab(); + } + } + } + } + } + //this is the default only if they're *right* in front... + if ( (pm->ps->clientNum&&!PM_ControlledByPlayer()) + || ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && cg.renderingThirdPerson && !cg.zoomMode) ) + {//NPC or player not in 1st person + if ( PM_CheckFlipOverAttackMove( qtrue ) ) + {//enemy must be close and in front + return PM_SaberFlipOverAttackMove(); + } + } + if ( PM_CheckLungeAttackMove() ) + {//NPC + autoMove = PM_SaberLungeAttackMove( qtrue ); + } + else + { + autoMove = LS_A_T2B; + } + } + else + {//pick a random one + if ( Q_irand( 0, 1 ) ) + { + autoMove = LS_A_TR2BL; + } + else + { + autoMove = LS_A_TL2BR; + } + } + float dotR = DotProduct( enemyDir, faceRight ); + if ( dotR > 0.35 ) + {//enemy is to far right + autoMove = LS_A_L2R; + } + else if ( dotR < -0.35 ) + {//far left + autoMove = LS_A_R2L; + } + else if ( dotR > 0.15 ) + {//enemy is to near right + autoMove = LS_A_TR2BL; + } + else if ( dotR < -0.15 ) + {//near left + autoMove = LS_A_TL2BR; + } + if ( DotProduct( enemyDir, faceUp ) > 0.5 ) + {//enemy is above me + if ( autoMove == LS_A_TR2BL ) + { + autoMove = LS_A_BL2TR; + } + else if ( autoMove == LS_A_TL2BR ) + { + autoMove = LS_A_BR2TL; + } + } + } + else if ( allowFB ) + {//back attack allowed + //if ( !PM_InKnockDown( pm->ps ) ) + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//only when on ground + if ( !pm->gent->enemy->client || pm->gent->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//enemy not a client or is a client and on ground + if ( dot < -0.75f + && enemyDist < 128 + && (pm->ps->saberAnimLevel == SS_FAST || pm->ps->saberAnimLevel == SS_STAFF || (pm->gent->client &&(pm->gent->client->NPC_class == CLASS_TAVION||pm->gent->client->NPC_class == CLASS_ALORA)&&Q_irand(0,2))) ) + {//fast back-stab + if ( !(pm->ps->pm_flags&PMF_DUCKED) && pm->cmd.upmove >= 0 ) + {//can't do it while ducked? + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) || (pm->gent->NPC && pm->gent->NPC->rank >= RANK_LT_JG) ) + {//only fencers and above can do this + autoMove = LS_A_BACKSTAB; + } + } + } + else if ( pm->ps->saberAnimLevel != SS_FAST + && pm->ps->saberAnimLevel != SS_STAFF ) + {//higher level back spin-attacks + if ( (pm->ps->clientNum&&!PM_ControlledByPlayer()) || ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && cg.renderingThirdPerson && !cg.zoomMode) ) + { + if ( (pm->ps->pm_flags&PMF_DUCKED) || pm->cmd.upmove < 0 ) + { + autoMove = LS_A_BACK_CR; + } + else + { + autoMove = LS_A_BACK; + } + } + } + } + } + } + return autoMove; +} + +qboolean PM_InSecondaryStyle( void ) +{ + if ( pm->ps->saber[0].numBlades > 1 + && pm->ps->saber[0].singleBladeStyle + && pm->ps->saber[0].singleBladeStyle != pm->ps->saber[0].style + && pm->ps->saberAnimLevel == pm->ps->saber[0].singleBladeStyle ) + { + return qtrue; + } + + if ( pm->ps->dualSabers + && !pm->ps->saber[1].Active() )//pm->ps->saberAnimLevel != SS_DUAL ) + { + return qtrue; + } + return qfalse; +} + +saberMoveName_t PM_SaberLungeAttackMove( qboolean fallbackToNormalLunge ) +{ + G_DrainPowerForSpecialMove( pm->gent, FP_SABER_OFFENSE, SABER_ALT_ATTACK_POWER_FB ); + if ( pm->gent->client->NPC_class == CLASS_ALORA && !Q_irand( 0, 3 ) ) + {//alora NPC + return LS_SPINATTACK_ALORA; + } + else + { + if ( pm->ps->dualSabers ) + { + return LS_SPINATTACK_DUAL; + } + switch ( pm->ps->saberAnimLevel ) + { + case SS_DUAL: + return LS_SPINATTACK_DUAL; + break; + case SS_STAFF: + return LS_SPINATTACK; + break; + default://normal lunge + if ( fallbackToNormalLunge ) + { + vec3_t fwdAngles, jumpFwd; + + VectorCopy( pm->ps->viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + //do the lunge + AngleVectors( fwdAngles, jumpFwd, NULL, NULL ); + VectorScale( jumpFwd, 150, pm->ps->velocity ); + pm->ps->velocity[2] = 50; + PM_AddEvent( EV_JUMP ); + + return LS_A_LUNGE; + } + break; + } + } + return LS_NONE; +} + +qboolean PM_CheckLungeAttackMove( void ) +{ + if ( pm->ps->saberAnimLevel == SS_FAST//fast + || pm->ps->saberAnimLevel == SS_DUAL//dual + || pm->ps->saberAnimLevel == SS_STAFF //staff + || pm->ps->saberAnimLevel == SS_DESANN + || pm->ps->dualSabers ) + {//alt+back+attack using fast, dual or staff attacks + if ( pm->ps->clientNum >= MAX_CLIENTS && !PM_ControlledByPlayer() ) + {//NPC + if ( pm->cmd.upmove < 0 || (pm->ps->pm_flags&PMF_DUCKED) ) + {//ducking + if ( pm->ps->legsAnim == BOTH_STAND2 + || pm->ps->legsAnim == BOTH_SABERFAST_STANCE + || pm->ps->legsAnim == BOTH_SABERSLOW_STANCE + || pm->ps->legsAnim == BOTH_SABERSTAFF_STANCE + || pm->ps->legsAnim == BOTH_SABERDUAL_STANCE + || (level.time-pm->ps->lastStationary) <= 500 ) + {//standing or just stopped standing + if ( pm->gent + && pm->gent->NPC //NPC + && pm->gent->NPC->rank >= RANK_LT_JG //high rank + && ( pm->gent->NPC->rank == RANK_LT_JG || Q_irand( -3, pm->gent->NPC->rank ) >= RANK_LT_JG )//Q_irand( 0, pm->gent->NPC->rank ) >= RANK_LT_JG ) + && !Q_irand( 0, 3-g_spskill->integer ) ) + {//only fencer and higher can do this + if ( pm->ps->saberAnimLevel == SS_DESANN ) + { + if ( !Q_irand( 0, 4 ) ) + { + return qtrue; + } + } + else + { + return qtrue; + } + } + } + } + } + else + {//player + if ( G_TryingLungeAttack( pm->gent, &pm->cmd ) + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER_FB )/*pm->ps->forcePower >= SABER_ALT_ATTACK_POWER_FB*/ )//have enough force power to pull it off + { + return qtrue; + } + } + } + + return qfalse; +} + +saberMoveName_t PM_SaberJumpForwardAttackMove( void ) +{ + G_DrainPowerForSpecialMove( pm->gent, FP_LEVITATION, SABER_ALT_ATTACK_POWER_FB ); + + if ( pm->ps->saberAnimLevel == SS_DUAL + || pm->ps->saberAnimLevel == SS_STAFF ) + { + pm->cmd.upmove = 0;//no jump just yet + + if ( pm->ps->saberAnimLevel == SS_STAFF ) + { + if ( Q_irand(0, 1) ) + { + return LS_JUMPATTACK_STAFF_LEFT; + } + else + { + return LS_JUMPATTACK_STAFF_RIGHT; + } + } + + return LS_JUMPATTACK_DUAL; + } + else + { + vec3_t fwdAngles, jumpFwd; + + VectorCopy( pm->ps->viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + AngleVectors( fwdAngles, jumpFwd, NULL, NULL ); + VectorScale( jumpFwd, 200, pm->ps->velocity ); + pm->ps->velocity[2] = 180; + pm->ps->forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + + //FIXME: NPCs yell? + PM_AddEvent( EV_JUMP ); + G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + pm->cmd.upmove = 0; + + return LS_A_JUMP_T__B_; + } +} + +qboolean PM_CheckJumpForwardAttackMove( void ) +{ + if ( pm->ps->clientNum < MAX_CLIENTS + && PM_InSecondaryStyle() ) + { + return qfalse; + } + + if ( pm->cmd.forwardmove > 0 //going forward + && pm->ps->forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 //can force jump + && pm->gent && !(pm->gent->flags&FL_LOCK_PLAYER_WEAPONS) // yes this locked weapons check also includes force powers, if we need a separate check later I'll make one + && (pm->ps->groundEntityNum != ENTITYNUM_NONE||level.time-pm->ps->lastOnGround<=250) //on ground or just jumped (if not player) + ) + { + if ( pm->ps->saberAnimLevel == SS_DUAL + || pm->ps->saberAnimLevel == SS_STAFF ) + {//dual and staff + if ( !PM_SaberInTransitionAny( pm->ps->saberMove ) //not going to/from/between an attack anim + && !PM_SaberInAttack( pm->ps->saberMove ) //not in attack anim + && pm->ps->weaponTime <= 0//not busy + && (pm->cmd.buttons&BUTTON_ATTACK) )//want to attack + { + if ( pm->ps->clientNum >= MAX_CLIENTS && !PM_ControlledByPlayer() ) + {//NPC + if ( pm->cmd.upmove > 0 || (pm->ps->pm_flags&PMF_JUMPING) )//jumping NPC + { + if ( pm->gent + && pm->gent->NPC + && (pm->gent->NPC->rank==RANK_CREWMAN||pm->gent->NPC->rank>=RANK_LT) ) + { + return qtrue; + } + } + } + else + {//PLAYER + if ( G_TryingJumpForwardAttack( pm->gent, &pm->cmd ) + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER_FB ) )//have enough power to attack + { + return qtrue; + } + } + } + } + //check strong + else if ( pm->ps->saberAnimLevel == SS_STRONG //strong style + || pm->ps->saberAnimLevel == SS_DESANN )//desann + { + if ( //&& !PM_InKnockDown( pm->ps ) + !pm->ps->dualSabers + //&& (pm->ps->legsAnim == BOTH_STAND2||pm->ps->legsAnim == BOTH_SABERFAST_STANCE||pm->ps->legsAnim == BOTH_SABERSLOW_STANCE||level.time-pm->ps->lastStationary<=500)//standing or just started moving + ) + {//strong attack: jump-hack + /* + if ( pm->ps->legsAnim == BOTH_STAND2 + || pm->ps->legsAnim == BOTH_SABERFAST_STANCE + || pm->ps->legsAnim == BOTH_SABERSLOW_STANCE + || level.time-pm->ps->lastStationary <= 250 )//standing or just started moving + */ + if ( pm->ps->clientNum >= MAX_CLIENTS && !PM_ControlledByPlayer() ) + {//NPC + if ( pm->cmd.upmove > 0 || (pm->ps->pm_flags&PMF_JUMPING) )//NPC jumping + { + if ( pm->gent + && pm->gent->NPC + && (pm->gent->NPC->rank==RANK_CREWMAN||pm->gent->NPC->rank>=RANK_LT) ) + {//only acrobat or boss and higher can do this + if ( pm->ps->legsAnim == BOTH_STAND2 + || pm->ps->legsAnim == BOTH_SABERFAST_STANCE + || pm->ps->legsAnim == BOTH_SABERSLOW_STANCE + || level.time-pm->ps->lastStationary <= 250 ) + {//standing or just started moving + if ( pm->gent->client + && pm->gent->client->NPC_class == CLASS_DESANN ) + { + if ( !Q_irand( 0, 1 ) ) + { + return qtrue; + } + } + else + { + return qtrue; + } + } + } + } + } + else + {//player + if ( G_TryingJumpForwardAttack( pm->gent, &pm->cmd ) + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER_FB ) ) + { + return qtrue; + } + } + } + } + } + return qfalse; +} + +saberMoveName_t PM_SaberFlipOverAttackMove( void ) +{ + //FIXME: check above for room enough to jump! + //FIXME: while in this jump, keep velocity[2] at a minimum until the end of the anim + vec3_t fwdAngles, jumpFwd; + + VectorCopy( pm->ps->viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + AngleVectors( fwdAngles, jumpFwd, NULL, NULL ); + VectorScale( jumpFwd, 150, pm->ps->velocity ); + pm->ps->velocity[2] = 250; + //250 is normalized for a standing enemy at your z level, about 64 tall... adjust for actual maxs[2]-mins[2] of enemy and for zdiff in origins + if ( pm->gent && pm->gent->enemy ) + { //go higher for taller enemies + pm->ps->velocity[2] *= (pm->gent->enemy->maxs[2]-pm->gent->enemy->mins[2])/64.0f; + //go higher for enemies higher than you, lower for those lower than you + float zDiff = pm->gent->enemy->currentOrigin[2] - pm->ps->origin[2]; + pm->ps->velocity[2] += (zDiff)*1.5f; + //clamp to decent-looking values + //FIXME: still jump too low sometimes + if ( zDiff <= 0 && pm->ps->velocity[2] < 200 ) + {//if we're on same level, don't let me jump so low, I clip into the ground + pm->ps->velocity[2] = 200; + } + else if ( pm->ps->velocity[2] < 50 ) + { + pm->ps->velocity[2] = 50; + } + else if ( pm->ps->velocity[2] > 400 ) + { + pm->ps->velocity[2] = 400; + } + } + pm->ps->forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + + //FIXME: NPCs yell? + PM_AddEvent( EV_JUMP ); + G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + pm->cmd.upmove = 0; + //FIXME: don't allow this to land on other people + + pm->gent->angle = pm->ps->viewangles[YAW];//so we know what yaw we started this at + + G_DrainPowerForSpecialMove( pm->gent, FP_LEVITATION, SABER_ALT_ATTACK_POWER_FB ); + + if ( Q_irand( 0, 1 ) ) + { + return LS_A_FLIP_STAB; + } + else + { + return LS_A_FLIP_SLASH; + } +} + +qboolean PM_CheckFlipOverAttackMove( qboolean checkEnemy ) +{ + if ( pm->ps->clientNum < MAX_CLIENTS + && PM_InSecondaryStyle() ) + { + return qfalse; + } + + if ( (pm->ps->saberAnimLevel == SS_MEDIUM //medium + || pm->ps->saberAnimLevel == SS_TAVION )//tavion + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 //can force jump + && !(pm->gent->flags&FL_LOCK_PLAYER_WEAPONS) // yes this locked weapons check also includes force powers, if we need a separate check later I'll make one + && (pm->ps->groundEntityNum != ENTITYNUM_NONE||level.time-pm->ps->lastOnGround<=250) //on ground or just jumped + ) + { + qboolean tryMove = qfalse; + if ( pm->ps->clientNum >= MAX_CLIENTS && !PM_ControlledByPlayer() ) + {//NPC + if ( pm->cmd.upmove > 0//want to jump + || (pm->ps->pm_flags&PMF_JUMPING) )//jumping + {//flip over-forward down-attack + if ( (pm->gent->NPC + && (pm->gent->NPC->rank==RANK_CREWMAN||pm->gent->NPC->rank>=RANK_LT) + && !Q_irand(0, 2) ) )//NPC who can do this, 33% chance + {//only player or acrobat or boss and higher can do this + tryMove = qtrue; + } + } + } + else + {//player + if ( G_TryingJumpForwardAttack( pm->gent, &pm->cmd ) + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER_FB ) )//have enough power + { + if ( !pm->cmd.rightmove ) + { + if ( pm->ps->legsAnim == BOTH_JUMP1 + || pm->ps->legsAnim == BOTH_FORCEJUMP1 + || pm->ps->legsAnim == BOTH_INAIR1 + || pm->ps->legsAnim == BOTH_FORCEINAIR1 ) + {//in a non-flip forward jump + tryMove = qtrue; + } + } + } + } + + if ( tryMove ) + { + if ( !checkEnemy ) + {//based just on command input + return qtrue; + } + else + {//based on presence of enemy + if ( pm->gent->enemy )//have an enemy + { + vec3_t fwdAngles = {0,pm->ps->viewangles[YAW],0}; + if ( pm->gent->enemy->health > 0 + && pm->ps->forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->gent->enemy->maxs[2] > 12 + && (!pm->gent->enemy->client || !PM_InKnockDownOnGround( &pm->gent->enemy->client->ps ) ) + && DistanceSquared( pm->gent->currentOrigin, pm->gent->enemy->currentOrigin ) < 10000 + && InFront( pm->gent->enemy->currentOrigin, pm->gent->currentOrigin, fwdAngles, 0.3f ) ) + {//enemy must be alive, not low to ground, close and in front + return qtrue; + } + } + return qfalse; + } + } + } + return qfalse; +} + +saberMoveName_t PM_SaberBackflipAttackMove( void ) +{ + pm->cmd.upmove = 0;//no jump just yet + return LS_A_BACKFLIP_ATK; +} + +qboolean PM_CheckBackflipAttackMove( void ) +{ + if ( pm->ps->clientNum < MAX_CLIENTS + && PM_InSecondaryStyle() ) + { + return qfalse; + } + + if ( pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 //can force jump + && pm->ps->forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->gent && !(pm->gent->flags&FL_LOCK_PLAYER_WEAPONS) // yes this locked weapons check also includes force powers, if we need a separate check later I'll make one + //&& (pm->ps->legsAnim == BOTH_SABERSTAFF_STANCE || level.time-pm->ps->lastStationary<=250)//standing or just started moving + && (pm->ps->groundEntityNum != ENTITYNUM_NONE||level.time-pm->ps->lastOnGround<=250) )//on ground or just jumped (if not player) + { + if ( pm->cmd.forwardmove < 0 //moving backwards + && pm->ps->saberAnimLevel == SS_STAFF //using staff + && (pm->cmd.upmove > 0 || (pm->ps->pm_flags&PMF_JUMPING)) )//jumping + {//jumping backwards and using staff + if ( !PM_SaberInTransitionAny( pm->ps->saberMove ) //not going to/from/between an attack anim + && !PM_SaberInAttack( pm->ps->saberMove ) //not in attack anim + && pm->ps->weaponTime <= 0//not busy + && (pm->cmd.buttons&BUTTON_ATTACK) )//want to attack + {//not already attacking + if ( pm->ps->clientNum >= MAX_CLIENTS && !PM_ControlledByPlayer() ) + {//NPC + if ( pm->gent + && pm->gent->NPC + && (pm->gent->NPC->rank==RANK_CREWMAN||pm->gent->NPC->rank>=RANK_LT) ) + {//acrobat or boss and higher can do this + return qtrue; + } + } + else + {//player + return qtrue; + } + } + } + } + return qfalse; +} + +saberMoveName_t PM_CheckDualSpinProtect( void ) +{ + if ( pm->ps->clientNum < MAX_CLIENTS + && PM_InSecondaryStyle() ) + { + return LS_NONE; + } + + if ( pm->ps->saberMove == LS_READY//ready + //&& (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())//PLAYER ONLY...? + //&& pm->ps->viewangles[0] > 30 //looking down + && pm->ps->saberAnimLevel == SS_DUAL//using dual saber style + && pm->ps->saber[0].Active() && pm->ps->saber[1].Active()//both sabers on + //&& pm->ps->forcePowerLevel[FP_PUSH]>=FORCE_LEVEL_3//force push 3 + //&& ((pm->ps->forcePowersActive&(1<ps->forcePowerDebounce[FP_PUSH]>level.time)//force-pushing + && G_TryingKataAttack( pm->gent, &pm->cmd )//(pm->cmd.buttons&BUTTON_FORCE_FOCUS)//holding focus + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER, qtrue )//pm->ps->forcePower >= SABER_ALT_ATTACK_POWER//DUAL_SPIN_PROTECT_POWER//force push 3 + && (pm->cmd.buttons&BUTTON_ATTACK)//pressing attack + ) + {//FIXME: some NPC logic to do this? + /* + if ( (pm->ps->pm_flags&PMF_DUCKED||pm->cmd.upmove<0)//crouching + && g_crosshairEntNum >= ENTITYNUM_WORLD ) + */ + { + if ( pm->gent ) + { + G_DrainPowerForSpecialMove( pm->gent, FP_PUSH, SABER_ALT_ATTACK_POWER, qtrue );//drain the required force power + } + return LS_DUAL_SPIN_PROTECT; + } + } + return LS_NONE; +} + +saberMoveName_t PM_CheckStaffKata( void ) +{ + if ( pm->ps->clientNum < MAX_CLIENTS + && PM_InSecondaryStyle() ) + { + return LS_NONE; + } + + if ( pm->ps->saberMove == LS_READY//ready + //&& (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())//PLAYER ONLY...? + //&& pm->ps->viewangles[0] > 30 //looking down + && pm->ps->saberAnimLevel == SS_STAFF//using dual saber style + && pm->ps->saber[0].Active()//saber on + //&& pm->ps->forcePowerLevel[FP_PUSH]>=FORCE_LEVEL_3//force push 3 + //&& ((pm->ps->forcePowersActive&(1<ps->forcePowerDebounce[FP_PUSH]>level.time)//force-pushing + && G_TryingKataAttack( pm->gent, &pm->cmd )//(pm->cmd.buttons&BUTTON_FORCE_FOCUS)//holding focus + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER, qtrue )//pm->ps->forcePower >= SABER_ALT_ATTACK_POWER//DUAL_SPIN_PROTECT_POWER//force push 3 + && (pm->cmd.buttons&BUTTON_ATTACK)//pressing attack + ) + {//FIXME: some NPC logic to do this? + /* + if ( (pm->ps->pm_flags&PMF_DUCKED||pm->cmd.upmove<0)//crouching + && g_crosshairEntNum >= ENTITYNUM_WORLD ) + */ + { + if ( pm->gent ) + { + G_DrainPowerForSpecialMove( pm->gent, FP_LEVITATION, SABER_ALT_ATTACK_POWER, qtrue );//drain the required force power + } + return LS_STAFF_SOULCAL; + } + } + return LS_NONE; +} + +extern qboolean WP_ForceThrowable( gentity_t *ent, gentity_t *forwardEnt, gentity_t *self, qboolean pull, float cone, float radius, vec3_t forward ); +saberMoveName_t PM_CheckPullAttack( void ) +{ + if ( pm->ps->clientNum < MAX_CLIENTS + && PM_InSecondaryStyle() ) + { + return LS_NONE; + } + + if ( (pm->ps->saberMove == LS_READY||PM_SaberInReturn(pm->ps->saberMove)||PM_SaberInReflect(pm->ps->saberMove))//ready + //&& (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())//PLAYER ONLY + && pm->ps->saberAnimLevel >= SS_FAST//single saber styles - FIXME: Tavion? + && pm->ps->saberAnimLevel <= SS_STRONG//single saber styles - FIXME: Tavion? + && G_TryingPullAttack( pm->gent, &pm->cmd, qfalse )//(pm->cmd.buttons&BUTTON_FORCE_FOCUS)//holding focus + //&& pm->cmd.forwardmove<0//pulling back + && (pm->cmd.buttons&BUTTON_ATTACK)//attacking + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER_FB )//pm->ps->forcePower >= SABER_ALT_ATTACK_POWER_FB//have enough power + ) + {//FIXME: some NPC logic to do this? + qboolean doMove = g_saberNewControlScheme->integer?qtrue:qfalse;//in new control scheme, can always do this, even if there's no-one to do it to + if ( g_saberNewControlScheme->integer + || g_crosshairEntNum < ENTITYNUM_WORLD )//in old control scheme, there has to be someone there + { + saberMoveName_t pullAttackMove = LS_NONE; + if ( pm->ps->saberAnimLevel == SS_FAST ) + { + pullAttackMove = LS_PULL_ATTACK_STAB; + } + else + { + pullAttackMove = LS_PULL_ATTACK_SWING; + } + + if ( g_crosshairEntNum < ENTITYNUM_WORLD + && pm->gent && pm->gent->client ) + { + gentity_t *targEnt = &g_entities[g_crosshairEntNum]; + if ( targEnt->client + && targEnt->health > 0 + //FIXME: check other things like in knockdown, saberlock, uninterruptable anims, etc. + && !PM_InOnGroundAnim( &targEnt->client->ps ) + && !PM_LockedAnim( targEnt->client->ps.legsAnim ) + && !PM_SuperBreakLoseAnim( targEnt->client->ps.legsAnim ) + && !PM_SuperBreakWinAnim( targEnt->client->ps.legsAnim ) + && targEnt->client->ps.saberLockTime <= 0 + && WP_ForceThrowable( targEnt, targEnt, pm->gent, qtrue, 1.0f, 0.0f, NULL ) ) + { + if ( !g_saberNewControlScheme->integer ) + {//in old control scheme, make sure they're close or far enough away for the move we'll be doing + float targDist = Distance( targEnt->currentOrigin, pm->ps->origin ); + if ( pullAttackMove == LS_PULL_ATTACK_STAB ) + {//must be closer than 512 + if ( targDist > 384.0f ) + { + return LS_NONE; + } + } + else//if ( pullAttackMove == LS_PULL_ATTACK_SWING ) + {//must be farther than 256 + if ( targDist > 512.0f ) + { + return LS_NONE; + } + if ( targDist < 192.0f ) + { + return LS_NONE; + } + } + } + + vec3_t targAngles = {0,targEnt->client->ps.viewangles[YAW],0}; + if ( InFront( pm->ps->origin, targEnt->currentOrigin, targAngles ) ) + { + NPC_SetAnim( targEnt, SETANIM_BOTH, BOTH_PULLED_INAIR_F, SETANIM_FLAG_OVERRIDE, SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( targEnt, SETANIM_BOTH, BOTH_PULLED_INAIR_B, SETANIM_FLAG_OVERRIDE, SETANIM_FLAG_HOLD ); + } + //hold the anim until I'm with done pull anim + targEnt->client->ps.legsAnimTimer = targEnt->client->ps.torsoAnimTimer = PM_AnimLength( pm->gent->client->clientInfo.animFileIndex, (animNumber_t)saberMoveData[pullAttackMove].animToUse ); + //set pullAttackTime + pm->gent->client->ps.pullAttackTime = targEnt->client->ps.pullAttackTime = level.time+targEnt->client->ps.legsAnimTimer; + //make us know about each other + pm->gent->client->ps.pullAttackEntNum = g_crosshairEntNum; + targEnt->client->ps.pullAttackEntNum = pm->ps->clientNum; + //do effect and sound on me + pm->ps->powerups[PW_FORCE_PUSH] = level.time + 1000; + if ( pm->gent ) + { + G_Sound( pm->gent, G_SoundIndex( "sound/weapons/force/pull.wav" ) ); + } + doMove = qtrue; + } + } + if ( doMove ) + { + if ( pm->gent ) + { + G_DrainPowerForSpecialMove( pm->gent, FP_PULL, SABER_ALT_ATTACK_POWER_FB ); + } + return pullAttackMove; + } + } + } + return LS_NONE; +} + +saberMoveName_t PM_CheckPlayerAttackFromParry( int curmove ) +{ + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) + { + if ( curmove >= LS_PARRY_UP + && curmove <= LS_REFLECT_LL ) + {//in a parry + switch ( saberMoveData[curmove].endQuad ) + { + case Q_T: + return LS_A_T2B; + break; + case Q_TR: + return LS_A_TR2BL; + break; + case Q_TL: + return LS_A_TL2BR; + break; + case Q_BR: + return LS_A_BR2TL; + break; + case Q_BL: + return LS_A_BL2TR; + break; + //shouldn't be a parry that ends at L, R or B + } + } + } + return LS_NONE; +} + + +saberMoveName_t PM_SaberAttackForMovement( int forwardmove, int rightmove, int curmove ) +{ + qboolean noSpecials = qfalse; + + if ( pm->ps->clientNum < MAX_CLIENTS + && PM_InSecondaryStyle() ) + { + noSpecials = qtrue; + } + +#ifdef _XBOX + if ( rightmove > 64 ) +#else + if ( rightmove > 0 ) +#endif // _XBOX + {//moving right + if ( !noSpecials + && (pm->ps->groundEntityNum != ENTITYNUM_NONE||level.time-pm->ps->lastOnGround<=250) //on ground or just jumped + && (pm->cmd.buttons&BUTTON_ATTACK)//hitting attack + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0//have force jump 1 at least + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER_LR )//pm->ps->forcePower >= SABER_ALT_ATTACK_POWER_LR//have enough power + && (((pm->ps->clientNum>=MAX_CLIENTS&&!PM_ControlledByPlayer())&&pm->cmd.upmove > 0)//jumping NPC + ||((pm->ps->clientNumgent, &pm->cmd)/*(pm->cmd.buttons&BUTTON_FORCE_FOCUS)*/)) )//focus-holding player + {//cartwheel right + vec3_t right, fwdAngles = {0, pm->ps->viewangles[YAW], 0}; + if ( pm->gent ) + { + G_DrainPowerForSpecialMove( pm->gent, FP_LEVITATION, SABER_ALT_ATTACK_POWER_LR ); + } + pm->cmd.upmove = 0; + if ( pm->ps->saberAnimLevel == SS_STAFF ) + { + AngleVectors( fwdAngles, NULL, right, NULL ); + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, 190, right, pm->ps->velocity ); + return LS_BUTTERFLY_RIGHT; + } + else + { + /* + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//still on ground + VectorClear( pm->ps->velocity ); + return LS_JUMPATTACK_CART_RIGHT; + } + else + */ + {//in air + AngleVectors( fwdAngles, NULL, right, NULL ); + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, 190, right, pm->ps->velocity ); + PM_SetJumped( JUMP_VELOCITY, qtrue ); + return LS_JUMPATTACK_ARIAL_RIGHT; + } + } + } + else if ( pm->ps->legsAnim != BOTH_CARTWHEEL_RIGHT + && pm->ps->legsAnim != BOTH_ARIAL_RIGHT ) + {//not in a cartwheel/arial + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) //PLAYER ONLY + {//player + if ( G_TryingSpecial(pm->gent, &pm->cmd)/*(pm->cmd.buttons&BUTTON_FORCE_FOCUS)*/ )//Holding focus + {//if no special worked, do nothing + return LS_NONE; + } + } + //checked all special attacks, if we're in a parry, attack from that move + saberMoveName_t parryAttackMove = PM_CheckPlayerAttackFromParry( curmove ); + if ( parryAttackMove != LS_NONE ) + { + return parryAttackMove; + } + //check regular attacks + if ( forwardmove > 0 ) + {//forward right = TL2BR slash + return LS_A_TL2BR; + } + else if ( forwardmove < 0 ) + {//backward right = BL2TR uppercut + return LS_A_BL2TR; + } + else + {//just right is a left slice + return LS_A_L2R; + } + } + } +#ifdef _XBOX + else if ( rightmove < -64 ) +#else + else if ( rightmove < 0 ) +#endif // _XBOX + {//moving left + if ( !noSpecials + && (pm->ps->groundEntityNum != ENTITYNUM_NONE||level.time-pm->ps->lastOnGround<=250) //on ground or just jumped + && (pm->cmd.buttons&BUTTON_ATTACK)//hitting attack + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0//have force jump 1 at least + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER_LR )//pm->ps->forcePower >= SABER_ALT_ATTACK_POWER_LR//have enough power + && (((pm->ps->clientNum>=MAX_CLIENTS&&!PM_ControlledByPlayer())&&pm->cmd.upmove > 0)//jumping NPC + ||((pm->ps->clientNumgent, &pm->cmd)/*(pm->cmd.buttons&BUTTON_FORCE_FOCUS)*/)) )//focus-holding player + {//cartwheel left + vec3_t right, fwdAngles = {0, pm->ps->viewangles[YAW], 0}; + if ( pm->gent ) + { + G_DrainPowerForSpecialMove( pm->gent, FP_LEVITATION, SABER_ALT_ATTACK_POWER_LR ); + } + pm->cmd.upmove = 0; + if ( pm->ps->saberAnimLevel == SS_STAFF ) + { + AngleVectors( fwdAngles, NULL, right, NULL ); + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -190, right, pm->ps->velocity ); + return LS_BUTTERFLY_LEFT; + } + else + { + /* + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//still on ground + VectorClear( pm->ps->velocity ); + return LS_JUMPATTACK_ARIAL_LEFT; + } + else + */ + { + AngleVectors( fwdAngles, NULL, right, NULL ); + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -190, right, pm->ps->velocity ); + PM_SetJumped( JUMP_VELOCITY, qtrue ); + return LS_JUMPATTACK_CART_LEFT; + } + } + } + else if ( pm->ps->legsAnim != BOTH_CARTWHEEL_LEFT + && pm->ps->legsAnim != BOTH_ARIAL_LEFT ) + {//not in a left cartwheel/arial + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) //PLAYER ONLY + {//player + if ( G_TryingSpecial(pm->gent, &pm->cmd)/*(pm->cmd.buttons&BUTTON_FORCE_FOCUS)*/ )//Holding focus + {//if no special worked, do nothing + return LS_NONE; + } + } + //checked all special attacks, if we're in a parry, attack from that move + saberMoveName_t parryAttackMove = PM_CheckPlayerAttackFromParry( curmove ); + if ( parryAttackMove != LS_NONE ) + { + return parryAttackMove; + } + //check regular attacks + if ( forwardmove > 0 ) + {//forward left = TR2BL slash + return LS_A_TR2BL; + } + else if ( forwardmove < 0 ) + {//backward left = BR2TL uppercut + return LS_A_BR2TL; + } + else + {//just left is a right slice + return LS_A_R2L; + } + } + } + else + {//not moving left or right +#ifdef _XBOX + if ( forwardmove > 64 ) +#else + if ( forwardmove > 0 ) +#endif // _XBOX + {//forward= T2B slash + saberMoveName_t stabDownMove = noSpecials?LS_NONE:PM_CheckStabDown(); + if ( stabDownMove != LS_NONE ) + { + return stabDownMove; + } + if ( ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && cg.renderingThirdPerson && !cg.zoomMode) )//player in third person, not zoomed in + {//player in thirdperson, not zoomed in + //flip-over attack logic + if ( !noSpecials && PM_CheckFlipOverAttackMove( qfalse ) ) + {//flip over-forward down-attack + return PM_SaberFlipOverAttackMove(); + } + //lunge attack logic + else if ( PM_CheckLungeAttackMove() ) + { + return PM_SaberLungeAttackMove( qtrue ); + } + //jump forward attack logic + else if ( !noSpecials && PM_CheckJumpForwardAttackMove() ) + { + return PM_SaberJumpForwardAttackMove(); + } + } + + //player NPC with enemy: autoMove logic + if ( pm->gent + && pm->gent->enemy + && pm->gent->enemy->client ) + {//I have an active enemy + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) + {//a player who is running at an enemy + //if the enemy is not a jedi, don't use top-down, pick a diagonal or side attack + if ( pm->gent->enemy->s.weapon != WP_SABER + && pm->gent->enemy->client->NPC_class != CLASS_REMOTE//too small to do auto-aiming accurately + && pm->gent->enemy->client->NPC_class != CLASS_SEEKER//too small to do auto-aiming accurately + && pm->gent->enemy->client->NPC_class != CLASS_GONK//too short to do auto-aiming accurately + && pm->gent->enemy->client->NPC_class != CLASS_HOWLER//too short to do auto-aiming accurately + && g_saberAutoAim->integer ) + { + saberMoveName_t autoMove = PM_AttackForEnemyPos( qfalse, (qboolean)(pm->ps->clientNum>=MAX_CLIENTS&&!PM_ControlledByPlayer()) ); + if ( autoMove != LS_INVALID ) + { + return autoMove; + } + } + } + + if ( pm->ps->clientNum>=MAX_CLIENTS && !PM_ControlledByPlayer() ) //NPC ONLY + {//NPC + if ( PM_CheckFlipOverAttackMove( qtrue ) ) + { + return PM_SaberFlipOverAttackMove(); + } + } + } + + //Regular NPCs + if ( pm->ps->clientNum >= MAX_CLIENTS && !PM_ControlledByPlayer() ) //NPC ONLY + {//NPC or player in third person, not zoomed in + //fwd jump attack logic + if ( PM_CheckJumpForwardAttackMove() ) + { + return PM_SaberJumpForwardAttackMove(); + } + //lunge attack logic + else if ( PM_CheckLungeAttackMove() ) + { + return PM_SaberLungeAttackMove( qtrue ); + } + } + + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) //PLAYER ONLY + {//player + if ( G_TryingSpecial(pm->gent,&pm->cmd) )//(pm->cmd.buttons&BUTTON_FORCE_FOCUS) )//Holding focus + {//if no special worked, do nothing + return LS_NONE; + } + } + + //checked all special attacks, if we're in a parry, attack from that move + saberMoveName_t parryAttackMove = PM_CheckPlayerAttackFromParry( curmove ); + if ( parryAttackMove != LS_NONE ) + { + return parryAttackMove; + } + //check regular attacks + return LS_A_T2B; + } +#ifdef _XBOX + else if ( forwardmove < -64 ) +#else + else if ( forwardmove < 0 ) +#endif // _XBOX + {//backward= T2B slash//B2T uppercut? + if ( g_saberNewControlScheme->integer ) + { + saberMoveName_t pullAtk = PM_CheckPullAttack(); + if ( pullAtk != LS_NONE ) + { + return pullAtk; + } + } + + if ( g_saberNewControlScheme->integer + && (pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer()) //PLAYER ONLY + && (pm->cmd.buttons&BUTTON_FORCE_FOCUS) )//Holding focus, trying special backwards attacks + {//player lunge attack logic + if ( ( pm->ps->dualSabers //or dual + || pm->ps->saberAnimLevel == SS_STAFF )//pm->ps->SaberStaff() )//or staff + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER_FB )/*pm->ps->forcePower >= SABER_ALT_ATTACK_POWER_FB*/ )//have enough force power to pull it off + {//alt+back+attack using fast, dual or staff attacks + PM_SaberLungeAttackMove( qfalse ); + } + } + else if ( (pm->ps->clientNum&&!PM_ControlledByPlayer()) //NPC + || ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && cg.renderingThirdPerson && !cg.zoomMode) )//player in third person, not zooomed + {//NPC or player in third person, not zoomed + if ( PM_CheckBackflipAttackMove() ) + { + return PM_SaberBackflipAttackMove();//backflip attack + } + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) //PLAYER ONLY + {//player + if ( G_TryingSpecial(pm->gent,&pm->cmd) )//(pm->cmd.buttons&BUTTON_FORCE_FOCUS) )//Holding focus + {//if no special worked, do nothing + return LS_NONE; + } + } + //if ( !PM_InKnockDown( pm->ps ) ) + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//only when on ground + if ( pm->gent && pm->gent->enemy ) + {//FIXME: or just trace for a valid enemy standing behind me? And no enemy in front? + vec3_t enemyDir, faceFwd, facingAngles = {0, pm->ps->viewangles[YAW], 0}; + AngleVectors( facingAngles, faceFwd, NULL, NULL ); + VectorSubtract( pm->gent->enemy->currentOrigin, pm->ps->origin, enemyDir ); + float dot = DotProduct( enemyDir, faceFwd ); + if ( dot < 0 ) + {//enemy is behind me + if ( dot < -0.75f + && DistanceSquared( pm->gent->currentOrigin, pm->gent->enemy->currentOrigin ) < 16384//128 squared + && (pm->ps->saberAnimLevel == SS_FAST || pm->ps->saberAnimLevel == SS_STAFF || (pm->gent->client &&(pm->gent->client->NPC_class == CLASS_TAVION||pm->gent->client->NPC_class == CLASS_ALORA)&&Q_irand(0,1))) ) + {//fast attacks and Tavion + if ( !(pm->ps->pm_flags&PMF_DUCKED) && pm->cmd.upmove >= 0 ) + {//can't do it while ducked? + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) || (pm->gent->NPC && pm->gent->NPC->rank >= RANK_LT_JG) ) + {//only fencers and above can do this + return LS_A_BACKSTAB; + } + } + } + else if ( pm->ps->saberAnimLevel != SS_FAST + && pm->ps->saberAnimLevel != SS_STAFF ) + {//medium and higher attacks + if ( (pm->ps->pm_flags&PMF_DUCKED) || pm->cmd.upmove < 0 ) + { + return LS_A_BACK_CR; + } + else + { + return LS_A_BACK; + } + } + } + else + {//enemy in front + float enemyDistSq = DistanceSquared( pm->gent->currentOrigin, pm->gent->enemy->currentOrigin ); + if ( (pm->ps->saberAnimLevel == FORCE_LEVEL_1||pm->ps->saberAnimLevel == SS_STAFF||pm->gent->client->NPC_class==CLASS_TAVION||pm->gent->client->NPC_class == CLASS_ALORA||(pm->gent->client->NPC_class==CLASS_DESANN&&!Q_irand(0,3))) && enemyDistSq > 16384 || pm->gent->enemy->health <= 0 )//128 squared + {//my enemy is pretty far in front of me and I'm using fast attacks + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) || + ( pm->gent && pm->gent->client && pm->gent->NPC && pm->gent->NPC->rank >= RANK_LT_JG && Q_irand( 0, pm->gent->NPC->rank ) > RANK_ENSIGN ) ) + {//only fencers and higher can do this, higher rank does it more + if ( PM_CheckEnemyInBack( 128 ) ) + { + return PM_PickBackStab(); + } + } + } + else if ( (pm->ps->saberAnimLevel >= FORCE_LEVEL_2 || pm->gent->client->NPC_class == CLASS_DESANN) && enemyDistSq > 40000 || pm->gent->enemy->health <= 0 )//200 squared + {//enemy is very faw away and I'm using medium/strong attacks + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) || + ( pm->gent && pm->gent->client && pm->gent->NPC && pm->gent->NPC->rank >= RANK_LT_JG && Q_irand( 0, pm->gent->NPC->rank ) > RANK_ENSIGN ) ) + {//only fencers and higher can do this, higher rank does it more + if ( PM_CheckEnemyInBack( 164 ) ) + { + return PM_PickBackStab(); + } + } + } + } + } + else + {//no current enemy + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && pm->gent && pm->gent->client ) + {//only player + if ( PM_CheckEnemyInBack( 128 ) ) + { + return PM_PickBackStab(); + } + } + } + } + } + + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) //PLAYER ONLY + {//player + if ( G_TryingSpecial( pm->gent, &pm->cmd ) )//(pm->cmd.buttons&BUTTON_FORCE_FOCUS) )//Holding focus + {//if no special worked, do nothing + return LS_NONE; + } + } + + //checked all special attacks, if we're in a parry, attack from that move + saberMoveName_t parryAttackMove = PM_CheckPlayerAttackFromParry( curmove ); + if ( parryAttackMove != LS_NONE ) + { + return parryAttackMove; + } + //check regular attacks + //else just swing down + return LS_A_T2B; + } + else + {//not moving in any direction + if ( PM_SaberInBounce( curmove ) ) + {//bounces should go to their default attack if you don't specify a direction but are attacking + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) //PLAYER ONLY + {//player + if ( G_TryingSpecial(pm->gent,&pm->cmd) )//(pm->cmd.buttons&BUTTON_FORCE_FOCUS) )//Holding focus + {//if no special worked, do nothing + return LS_NONE; + } + } + saberMoveName_t newmove; + if ( pm->ps->clientNum && !PM_ControlledByPlayer() && Q_irand( 0, 3 ) ) + {//use NPC random + newmove = PM_NPCSaberAttackFromQuad( saberMoveData[curmove].endQuad ); + } + else + {//player uses chain-attack + newmove = saberMoveData[curmove].chain_attack; + } + if ( PM_SaberKataDone( curmove, newmove ) ) + { + return saberMoveData[curmove].chain_idle; + } + else + { + return newmove; + } + } + else if ( PM_SaberInKnockaway( curmove ) ) + {//bounces should go to their default attack if you don't specify a direction but are attacking + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) //PLAYER ONLY + {//player + if ( G_TryingSpecial( pm->gent, &pm->cmd ) )//(pm->cmd.buttons&BUTTON_FORCE_FOCUS) )//Holding focus + {//if no special worked, do nothing + return LS_NONE; + } + } + saberMoveName_t newmove; + if ( pm->ps->clientNum && !PM_ControlledByPlayer() && Q_irand( 0, 3 ) ) + {//use NPC random + newmove = PM_NPCSaberAttackFromQuad( saberMoveData[curmove].endQuad ); + } + else + { + if ( pm->ps->saberAnimLevel == SS_FAST || + pm->ps->saberAnimLevel == SS_TAVION ) + {//player is in fast attacks, so come right back down from the same spot + newmove = PM_AttackMoveForQuad( saberMoveData[curmove].endQuad ); + } + else + {//use a transition to wrap to another attack from a different dir + newmove = saberMoveData[curmove].chain_attack; + } + } + if ( PM_SaberKataDone( curmove, newmove ) ) + { + return saberMoveData[curmove].chain_idle; + } + else + { + return newmove; + } + } + else if ( curmove == LS_READY + || curmove == LS_A_FLIP_STAB + || curmove == LS_A_FLIP_SLASH + || ( curmove >= LS_PARRY_UP + && curmove <= LS_REFLECT_LL ) ) + {//Not moving at all, not too busy to attack + //push + lookdown + attack + dual sabers = LS_DUAL_SPIN_PROTECT + if ( g_saberNewControlScheme->integer ) + { + if ( PM_CheckDualSpinProtect() ) + { + return LS_DUAL_SPIN_PROTECT; + } + if ( PM_CheckStaffKata() ) + { + return LS_STAFF_SOULCAL; + } + } + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) //PLAYER ONLY + {//player + if ( G_TryingSpecial( pm->gent, &pm->cmd ) )//(pm->cmd.buttons&BUTTON_FORCE_FOCUS) )//Holding focus + {//if no special worked, do nothing + return LS_NONE; + } + } + //checked all special attacks, if we're in a parry, attack from that move + saberMoveName_t parryAttackMove = PM_CheckPlayerAttackFromParry( curmove ); + if ( parryAttackMove != LS_NONE ) + { + return parryAttackMove; + } + //check regular attacks + if ( pm->ps->clientNum || g_saberAutoAim->integer ) + {//auto-aim + if ( pm->gent && pm->gent->enemy ) + {//based on enemy position, pick a proper attack + saberMoveName_t autoMove = PM_AttackForEnemyPos( qtrue, (qboolean)(pm->ps->clientNum>=MAX_CLIENTS) ); + if ( autoMove != LS_INVALID ) + { + return autoMove; + } + } + else if ( fabs(pm->ps->viewangles[0]) > 30 ) + {//looking far up or far down uses the top to bottom attack, presuming you want a vertical attack + return LS_A_T2B; + } + } + else + {//for now, just pick a random attack + return ((saberMoveName_t)Q_irand( LS_A_TL2BR, LS_A_T2B )); + } + } + } + } + //FIXME: pick a return? + return LS_NONE; +} + +saberMoveName_t PM_SaberAnimTransitionMove( saberMoveName_t curmove, saberMoveName_t newmove ) +{ + //FIXME: take FP_SABER_OFFENSE into account here somehow? + int retmove = newmove; + if ( curmove == LS_READY ) + {//just standing there + switch ( newmove ) + { + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + //transition is the start + retmove = LS_S_TL2BR + (newmove-LS_A_TL2BR); + break; + } + } + else + { + switch ( newmove ) + { + //transitioning to ready pose + case LS_READY: + switch ( curmove ) + { + //transitioning from an attack + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + //transition is the return + retmove = LS_R_TL2BR + (newmove-LS_A_TL2BR); + break; + } + break; + //transitioning to an attack + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + if ( newmove == curmove ) + {//FIXME: need a spin or something or go to next level, but for now, just play the return + //going into another attack... + //allow endless chaining in level 1 attacks, several in level 2 and only one or a few in level 3 + //FIXME: don't let strong attacks chain to an attack in the opposite direction ( > 45 degrees?) + if ( PM_SaberKataDone( curmove, newmove ) ) + {//done with this kata, must return to ready before attack again + retmove = LS_R_TL2BR + (newmove-LS_A_TL2BR); + } + else + {//okay to chain to another attack + retmove = transitionMove[saberMoveData[curmove].endQuad][saberMoveData[newmove].startQuad]; + } + } + else if ( saberMoveData[curmove].endQuad == saberMoveData[newmove].startQuad ) + {//new move starts from same quadrant + retmove = newmove; + } + else + { + switch ( curmove ) + { + //transitioning from an attack + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + case LS_D1_BR: + case LS_D1__R: + case LS_D1_TR: + case LS_D1_T_: + case LS_D1_TL: + case LS_D1__L: + case LS_D1_BL: + case LS_D1_B_: + retmove = transitionMove[saberMoveData[curmove].endQuad][saberMoveData[newmove].startQuad]; + break; + //transitioning from a return + case LS_R_TL2BR: + case LS_R_L2R: + case LS_R_BL2TR: + case LS_R_BR2TL: + case LS_R_R2L: + case LS_R_TR2BL: + case LS_R_T2B: + //transitioning from a bounce + /* + case LS_BOUNCE_UL2LL: + case LS_BOUNCE_LL2UL: + case LS_BOUNCE_L2LL: + case LS_BOUNCE_L2UL: + case LS_BOUNCE_UR2LR: + case LS_BOUNCE_LR2UR: + case LS_BOUNCE_R2LR: + case LS_BOUNCE_R2UR: + case LS_BOUNCE_TOP: + case LS_OVER_UR2UL: + case LS_OVER_UL2UR: + case LS_BOUNCE_UR: + case LS_BOUNCE_UL: + case LS_BOUNCE_LR: + case LS_BOUNCE_LL: + */ + //transitioning from a parry/reflection/knockaway/broken parry + case LS_PARRY_UP: + case LS_PARRY_UR: + case LS_PARRY_UL: + case LS_PARRY_LR: + case LS_PARRY_LL: + case LS_REFLECT_UP: + case LS_REFLECT_UR: + case LS_REFLECT_UL: + case LS_REFLECT_LR: + case LS_REFLECT_LL: + case LS_K1_T_: + case LS_K1_TR: + case LS_K1_TL: + case LS_K1_BR: + case LS_K1_BL: + case LS_V1_BR: + case LS_V1__R: + case LS_V1_TR: + case LS_V1_T_: + case LS_V1_TL: + case LS_V1__L: + case LS_V1_BL: + case LS_V1_B_: + case LS_H1_T_: + case LS_H1_TR: + case LS_H1_TL: + case LS_H1_BR: + case LS_H1_BL: + retmove = transitionMove[saberMoveData[curmove].endQuad][saberMoveData[newmove].startQuad]; + break; + //NB: transitioning from transitions is fine + } + } + break; + //transitioning to any other anim is not supported + } + } + + if ( retmove == LS_NONE ) + { + return newmove; + } + + return ((saberMoveName_t)retmove); +} + +/* +------------------------- +PM_LegsAnimForFrame +Returns animNumber for current frame +------------------------- +*/ +int PM_LegsAnimForFrame( gentity_t *ent, int legsFrame ) +{ + //Must be a valid client + if ( ent->client == NULL ) + return -1; + + //Must have a file index entry + if( ValidAnimFileIndex( ent->client->clientInfo.animFileIndex ) == qfalse ) + return -1; + + animation_t *animations = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations; + int glaIndex = gi.G2API_GetAnimIndex(&(ent->ghoul2[0])); + + for ( int animation = 0; animation < BOTH_CIN_1; animation++ ) //first anim after last legs + { + if ( animation >= TORSO_DROPWEAP1 && animation < LEGS_TURN1 ) //first legs only anim + {//not a possible legs anim + continue; + } + + if ( animations[animation].glaIndex != glaIndex ) + { + continue; + } + + if ( animations[animation].firstFrame > legsFrame ) + {//This anim starts after this frame + continue; + } + + if ( animations[animation].firstFrame + animations[animation].numFrames < legsFrame ) + {//This anim ends before this frame + continue; + } + //else, must be in this anim! + return animation; + } + + //Not in ANY torsoAnim? SHOULD NEVER HAPPEN +// assert(0); + return -1; +} + +int PM_ValidateAnimRange( const int startFrame, const int endFrame, const float animSpeed ) +{//given a startframe and endframe, see if that lines up with any known animation + animation_t *animations = level.knownAnimFileSets[0].animations; + + for ( int anim = 0; anim < MAX_ANIMATIONS; anim++ ) + { + if ( animSpeed < 0 ) + {//playing backwards + if ( animations[anim].firstFrame == endFrame ) + { + if ( animations[anim].numFrames + animations[anim].firstFrame == startFrame ) + { + //Com_Printf( "valid reverse anim: %s\n", animTable[anim].name ); + return anim; + } + } + } + else + {//playing forwards + if ( animations[anim].firstFrame == startFrame ) + {//This anim starts on this frame + if ( animations[anim].firstFrame + animations[anim].numFrames == endFrame ) + {//This anim ends on this frame + //Com_Printf( "valid forward anim: %s\n", animTable[anim].name ); + return anim; + } + } + } + //else, must not be this anim! + } + + //Not in ANY anim? SHOULD NEVER HAPPEN + Com_Printf( "invalid anim range %d to %d, speed %4.2f\n", startFrame, endFrame, animSpeed ); + return -1; +} +/* +------------------------- +PM_TorsoAnimForFrame +Returns animNumber for current frame +------------------------- +*/ +int PM_TorsoAnimForFrame( gentity_t *ent, int torsoFrame ) +{ + //Must be a valid client + if ( ent->client == NULL ) + return -1; + + //Must have a file index entry + if( ValidAnimFileIndex( ent->client->clientInfo.animFileIndex ) == qfalse ) + return -1; + + animation_t *animations = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations; + int glaIndex = gi.G2API_GetAnimIndex(&(ent->ghoul2[0])); + + for ( int animation = 0; animation < LEGS_TURN1; animation++ ) //first legs only anim + { + if ( animations[animation].glaIndex != glaIndex ) + { + continue; + } + + if ( animations[animation].firstFrame > torsoFrame ) + {//This anim starts after this frame + continue; + } + + if ( animations[animation].firstFrame + animations[animation].numFrames < torsoFrame ) + {//This anim ends before this frame + continue; + } + //else, must be in this anim! + return animation; + } + + //Not in ANY torsoAnim? SHOULD NEVER HAPPEN +// assert(0); + return -1; +} + +qboolean PM_FinishedCurrentLegsAnim( gentity_t *self ) +{ + int junk, curFrame; + float currentFrame, animSpeed; + + if ( !self->client ) + { + return qtrue; + } + + gi.G2API_GetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, (cg.time?cg.time:level.time), ¤tFrame, &junk, &junk, &junk, &animSpeed, NULL ); + curFrame = floor( currentFrame ); + + int legsAnim = self->client->ps.legsAnim; + animation_t *animations = level.knownAnimFileSets[self->client->clientInfo.animFileIndex].animations; + + if ( curFrame >= animations[legsAnim].firstFrame + (animations[legsAnim].numFrames - 2) ) + { + return qtrue; + } + + return qfalse; +} + +/* +------------------------- +PM_HasAnimation +------------------------- +*/ + +qboolean PM_HasAnimation( gentity_t *ent, int animation ) +{ + //Must be a valid client + if ( !ent || ent->client == NULL ) + return qfalse; + + //must be a valid anim number + if ( animation < 0 || animation >= MAX_ANIMATIONS ) + { + return qfalse; + } + //Must have a file index entry + if( ValidAnimFileIndex( ent->client->clientInfo.animFileIndex ) == qfalse ) + return qfalse; + + animation_t *animations = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations; + + //No frames, no anim + if ( animations[animation].numFrames == 0 ) + return qfalse; + + //Has the sequence + return qtrue; +} + +int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim ) +{ + int anim; + int count = 0; + + if ( !self ) + { + return Q_irand(minAnim, maxAnim); + } + + do + { + anim = Q_irand(minAnim, maxAnim); + count++; + } + while ( !PM_HasAnimation( self, anim ) && count < 1000 ); + + return anim; +} + +/* +------------------------- +PM_AnimLength +------------------------- +*/ + +int PM_AnimLength( int index, animNumber_t anim ) +{ + if ( ValidAnimFileIndex( index ) == false ) + return 0; + + return level.knownAnimFileSets[index].animations[anim].numFrames * abs(level.knownAnimFileSets[index].animations[anim].frameLerp); +} + +/* +------------------------- +PM_SetLegsAnimTimer +------------------------- +*/ + +void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ) +{ + *legsAnimTimer = time; + + if ( *legsAnimTimer < 0 && time != -1 ) + {//Cap timer to 0 if was counting down, but let it be -1 if that was intentional + *legsAnimTimer = 0; + } + + if ( !*legsAnimTimer && ent && Q3_TaskIDPending( ent, TID_ANIM_LOWER ) ) + {//Waiting for legsAnimTimer to complete, and it just got set to zero + if ( !Q3_TaskIDPending( ent, TID_ANIM_BOTH) ) + {//Not waiting for top + Q3_TaskIDComplete( ent, TID_ANIM_LOWER ); + } + else + {//Waiting for both to finish before complete + Q3_TaskIDClear( &ent->taskID[TID_ANIM_LOWER] );//Bottom is done, regardless + if ( !Q3_TaskIDPending( ent, TID_ANIM_UPPER) ) + {//top is done and we're done + Q3_TaskIDComplete( ent, TID_ANIM_BOTH ); + } + } + } +} + +/* +------------------------- +PM_SetTorsoAnimTimer +------------------------- +*/ + +void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ) +{ + *torsoAnimTimer = time; + + if ( *torsoAnimTimer < 0 && time != -1 ) + {//Cap timer to 0 if was counting down, but let it be -1 if that was intentional + *torsoAnimTimer = 0; + } + + if ( !*torsoAnimTimer && ent && Q3_TaskIDPending( ent, TID_ANIM_UPPER ) ) + {//Waiting for torsoAnimTimer to complete, and it just got set to zero + if ( !Q3_TaskIDPending( ent, TID_ANIM_BOTH) ) + {//Not waiting for bottom + Q3_TaskIDComplete( ent, TID_ANIM_UPPER ); + } + else + {//Waiting for both to finish before complete + Q3_TaskIDClear( &ent->taskID[TID_ANIM_UPPER] );//Top is done, regardless + if ( !Q3_TaskIDPending( ent, TID_ANIM_LOWER) ) + {//lower is done and we're done + Q3_TaskIDComplete( ent, TID_ANIM_BOTH ); + } + } + } +} + +extern qboolean PM_SpinningSaberAnim( int anim ); +extern float saberAnimSpeedMod[NUM_FORCE_POWER_LEVELS]; +void PM_SaberStartTransAnim( int saberAnimLevel, int anim, float *animSpeed, gentity_t *gent ) +{ + if ( g_saberAnimSpeed->value != 1.0f ) + { + if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_CROUCHATTACKBACK1 ) + { + *animSpeed *= g_saberAnimSpeed->value; + } + } + if ( gent + && gent->client + && gent->client->ps.stats[STAT_WEAPONS]&(1<client->ps.dualSabers + && saberAnimLevel == SS_DUAL + && gent->weaponModel[1] ) + {//using a scepter and dual style, slow down anims + if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_H7_S7_BR ) + { + *animSpeed *= 0.75; + } + } + if ( gent && gent->client && gent->client->ps.forceRageRecoveryTime > level.time ) + {//rage recovery + if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_H1_S1_BR ) + {//animate slower + *animSpeed *= 0.75; + } + } + else if ( gent && gent->NPC && gent->NPC->rank == RANK_CIVILIAN ) + {//grunt reborn + if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_R1_TR_S1 ) + {//his fast attacks are slower + if ( !PM_SpinningSaberAnim( anim ) ) + { + *animSpeed *= 0.75; + } + return; + } + } + else if ( gent && gent->client ) + { + if ( gent->client->ps.saber[0].type == SABER_LANCE || gent->client->ps.saber[0].type == SABER_TRIDENT ) + {//FIXME: hack for now - these use the fast anims, but slowed down. Should have own style + if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_R1_TR_S1 ) + {//his fast attacks are slower + if ( !PM_SpinningSaberAnim( anim ) ) + { + *animSpeed *= 0.75; + } + return; + } + } + } + + if ( ( anim >= BOTH_T1_BR__R && + anim <= BOTH_T1_BL_TL ) || + ( anim >= BOTH_T3_BR__R && + anim <= BOTH_T3_BL_TL ) || + ( anim >= BOTH_T5_BR__R && + anim <= BOTH_T5_BL_TL ) ) + { + if ( saberAnimLevel == FORCE_LEVEL_1 || saberAnimLevel == FORCE_LEVEL_5 ) + {//FIXME: should not be necc for FORCE_LEVEL_1's + *animSpeed *= 1.5; + } + else if ( saberAnimLevel == FORCE_LEVEL_3 ) + { + *animSpeed *= 0.75; + } + } +} +/* +void PM_SaberStartTransAnim( int anim, int entNum, int saberOffenseLevel, float *animSpeed ) +{ + //check starts + if ( ( anim >= BOTH_S1_S1_T_ && + anim <= BOTH_S1_S1_TR ) || + ( anim >= BOTH_S1_S1_T_ && + anim <= BOTH_S1_S1_TR ) || + ( anim >= BOTH_S3_S1_T_ && + anim <= BOTH_S3_S1_TR ) ) + { + if ( entNum == 0 ) + { + *animSpeed *= saberAnimSpeedMod[FORCE_LEVEL_3]; + } + else + { + *animSpeed *= saberAnimSpeedMod[saberOffenseLevel]; + } + } + //Check transitions + else if ( PM_SpinningSaberAnim( anim ) ) + {//spins stay normal speed + return; + } + else if ( ( anim >= BOTH_T1_BR__R && + anim <= BOTH_T1_BL_TL ) || + ( anim >= BOTH_T2_BR__R && + anim <= BOTH_T2_BL_TL ) || + ( anim >= BOTH_T3_BR__R && + anim <= BOTH_T3_BL_TL ) ) + {//slow down the transitions + if ( entNum == 0 && saberOffenseLevel <= FORCE_LEVEL_2 ) + { + *animSpeed *= saberAnimSpeedMod[saberOffenseLevel]; + } + else + { + *animSpeed *= saberAnimSpeedMod[saberOffenseLevel]/2.0f; + } + } + + return; +} +*/ +extern qboolean player_locked; +extern qboolean MatrixMode; +float PM_GetTimeScaleMod( gentity_t *gent ) +{ + if ( g_timescale->value ) + { + if ( !MatrixMode + && gent->client->ps.legsAnim != BOTH_FORCELONGLEAP_START + && gent->client->ps.legsAnim != BOTH_FORCELONGLEAP_ATTACK + && gent->client->ps.legsAnim != BOTH_FORCELONGLEAP_LAND ) + { + if ( gent && gent->s.clientNum == 0 && !player_locked && gent->client->ps.forcePowersActive&(1<value); + } + else if ( gent && gent->client && gent->client->ps.forcePowersActive&(1<value); + } + } + } + return 1.0f; +} + +static inline qboolean PM_IsHumanoid( CGhoul2Info *ghlInfo ) +{ + char *GLAName; + GLAName = gi.G2API_GetGLAName( ghlInfo ); + assert(GLAName); + + if ( !Q_stricmp( "models/players/_humanoid/_humanoid", GLAName ) ) + { + return qtrue; + } + + return qfalse; +} + +/* +------------------------- +PM_SetAnimFinal +------------------------- +*/ +#define G2_DEBUG_TIMING (0) +void PM_SetAnimFinal(int *torsoAnim,int *legsAnim, + int setAnimParts,int anim,int setAnimFlags, + int *torsoAnimTimer,int *legsAnimTimer, + gentity_t *gent,int blendTime) // default blendTime=350 +{ + +// BASIC SETUP AND SAFETY CHECKING +//================================= + + // If It Is A Busted Entity, Don't Do Anything Here. + //--------------------------------------------------- + if (!gent || !gent->client) + { + return; + } + + // Make Sure This Character Has Such An Anim And A Model + //------------------------------------------------------- + if (anim<0 || anim>=MAX_ANIMATIONS || !ValidAnimFileIndex(gent->client->clientInfo.animFileIndex)) + { + #ifndef FINAL_BUILD + if (g_AnimWarning->integer) + { + if (anim<0 || anim>=MAX_ANIMATIONS) + { + gi.Printf(S_COLOR_RED"PM_SetAnimFinal: Invalid Anim Index (%d)!\n", anim); + } + else + { + gi.Printf(S_COLOR_RED"PM_SetAnimFinal: Invalid Anim File Index (%d)!\n", gent->client->clientInfo.animFileIndex); + } + } + #endif + return; + } + + + // Get Global Time Properties + //---------------------------- + float timeScaleMod = PM_GetTimeScaleMod( gent ); + const int actualTime = (cg.time?cg.time:level.time); + const animation_t* animations = level.knownAnimFileSets[gent->client->clientInfo.animFileIndex].animations; + const animation_t& curAnim = animations[anim]; + + // Make Sure This Character Has Such An Anim And A Model + //------------------------------------------------------- + if (animations[anim].numFrames==0) + { + static int LastAnimWarningNum=0; + #ifndef FINAL_BUILD + if (LastAnimWarningNum!=anim) + { + if ((cg_debugAnim.integer==3) || // 3 = do everyone + (cg_debugAnim.integer==1 && gent->s.number==0) || // 1 = only the player + (cg_debugAnim.integer==2 && gent->s.number!=0) || // 2 = only everyone else + (cg_debugAnim.integer==4 && gent->s.number!=cg_debugAnimTarget.integer) // 4 = specific entnum + ) + { + gi.Printf(S_COLOR_RED"PM_SetAnimFinal: Anim %s does not exist in this model (%s)!\n", animTable[anim].name, gent->NPC_type ); + } + } + LastAnimWarningNum = anim; + #endif + return; + } + + // If It's Not A Ghoul 2 Model, Just Remember The Anims And Stop, Because Everything Beyond This Is Ghoul2 + //--------------------------------------------------------------------------------------------------------- + if (!gi.G2API_HaveWeGhoul2Models(gent->ghoul2)) + { + if (setAnimParts&SETANIM_TORSO) + { + (*torsoAnim) = anim; + } + if (setAnimParts&SETANIM_LEGS) + { + (*legsAnim) = anim; + } + return; + } + + + // Lower Offensive Skill Slows Down The Saber Start Attack Animations + //-------------------------------------------------------------------- + PM_SaberStartTransAnim( gent->client->ps.saberAnimLevel, anim, &timeScaleMod, gent ); + + + +// SETUP VALUES FOR INCOMMING ANIMATION +//====================================== + const bool animFootMove = (PM_WalkingAnim(anim) || PM_RunningAnim(anim) || anim==BOTH_CROUCH1WALK || anim==BOTH_CROUCH1WALKBACK); + const bool animHoldless = (setAnimFlags&SETANIM_FLAG_HOLDLESS)!=0; + const bool animHold = (setAnimFlags&SETANIM_FLAG_HOLD)!=0; + const bool animRestart = (setAnimFlags&SETANIM_FLAG_RESTART)!=0; + const bool animOverride = (setAnimFlags&SETANIM_FLAG_OVERRIDE)!=0; + const bool animSync = (g_synchSplitAnims->integer!=0 && !animRestart); + float animCurrent = (-1.0f); + float animSpeed = (50.0f / curAnim.frameLerp * timeScaleMod); // animSpeed is 1.0 if the frameLerp (ms/frame) is 50 (20 fps). + const float animFPS = (fabsf(curAnim.frameLerp)); + const int animDurMSec = (int)(((curAnim.numFrames - 1) * animFPS) / timeScaleMod); + const int animHoldMSec = ((animHoldless && timeScaleMod==1.0f)?((animDurMSec>1)?(animDurMSec-1):(animFPS)):(animDurMSec)); + int animFlags = (curAnim.loopFrames!=-1)?(BONE_ANIM_OVERRIDE_LOOP):(BONE_ANIM_OVERRIDE_FREEZE); + int animStart = (curAnim.firstFrame); + int animEnd = (curAnim.firstFrame)+(animations[anim].numFrames); + + // If We Have A Blend Timer, Add The Blend Flag + //---------------------------------------------- + if (blendTime > 0) + { + animFlags |= BONE_ANIM_BLEND; + } + + // If Animation Is Going Backwards, Swap Last And First Frames + //------------------------------------------------------------- + if (animSpeed<0.0f) + { +// #ifndef FINAL_BUILD + #if 0 + if (g_AnimWarning->integer==1) + { + if (animFlags&BONE_ANIM_OVERRIDE_LOOP) + { + gi.Printf(S_COLOR_YELLOW"PM_SetAnimFinal: WARNING: Anim (%s) looping backwards!\n", animTable[anim].name); + } + } + #endif + + int temp = animEnd; + animEnd = animStart; + animStart = temp; + blendTime = 0; + } + + // If The Animation Is Walking Or Running, Attempt To Scale The Playback Speed To Match + //-------------------------------------------------------------------------------------- + if (g_noFootSlide->integer + && animFootMove + && !(animSpeed<0.0f) + //FIXME: either read speed from animation.cfg or only do this for NPCs + // for whom we've specifically determined the proper numbers! + && gent->client->NPC_class != CLASS_HOWLER + && gent->client->NPC_class != CLASS_WAMPA + && gent->client->NPC_class != CLASS_GONK + && gent->client->NPC_class != CLASS_HOWLER + && gent->client->NPC_class != CLASS_MOUSE + && gent->client->NPC_class != CLASS_PROBE + && gent->client->NPC_class != CLASS_PROTOCOL + && gent->client->NPC_class != CLASS_R2D2 + && gent->client->NPC_class != CLASS_R5D2 + && gent->client->NPC_class != CLASS_SEEKER) + { + bool Walking = !!PM_WalkingAnim(anim); + bool HasDual = (gent->client->ps.saberAnimLevel==SS_DUAL); + bool HasStaff = (gent->client->ps.saberAnimLevel==SS_STAFF); + float moveSpeedOfAnim = 150.0f;//g_noFootSlideRunScale->value; + + if (anim==BOTH_CROUCH1WALK || anim==BOTH_CROUCH1WALKBACK) + { + moveSpeedOfAnim = 75.0f; + } + else + { + if (gent->client->NPC_class == CLASS_HAZARD_TROOPER) + { + moveSpeedOfAnim = 50.0f; + } + else if (gent->client->NPC_class == CLASS_RANCOR) + { + moveSpeedOfAnim = 173.0f; + } + else + { + if (Walking) + { + if (HasDual || HasStaff) + { + moveSpeedOfAnim = 100.0f; + } + else + { + moveSpeedOfAnim = 50.0f;// g_noFootSlideWalkScale->value; + } + } + else + { + if (HasStaff) + { + moveSpeedOfAnim = 250.0f; + } + else + { + moveSpeedOfAnim = 150.0f; + } + } + } + } + + + + + + + animSpeed *= (gent->resultspeed/moveSpeedOfAnim); + if (animSpeed<0.01f) + { + animSpeed = 0.01f; + } + + // Make Sure Not To Play Too Fast An Anim + //---------------------------------------- +// float maxPlaybackSpeed = (1.5f * timeScaleMod); + float maxPlaybackSpeed = (1.5f * timeScaleMod) / 2.0f; + if (animSpeed>maxPlaybackSpeed) + { + animSpeed = maxPlaybackSpeed; + } + } + + +// GET VALUES FOR EXISTING BODY ANIMATION +//========================================== + float bodySpeed = 0.0f; + float bodyCurrent = 0.0f; + int bodyStart = 0; + int bodyEnd = 0; + int bodyFlags = 0; + int bodyAnim = (*legsAnim); + int bodyBone = (gent->rootBone); + bool bodyTimerOn = ((*legsAnimTimer>0) || (*legsAnimTimer)==-1); + bool bodyPlay = ((setAnimParts&SETANIM_LEGS) && (bodyBone!=-1) && (animOverride || !bodyTimerOn)); + bool bodyAnimating = !!gi.G2API_GetBoneAnimIndex(&gent->ghoul2[gent->playerModel], bodyBone, actualTime, &bodyCurrent, &bodyStart, &bodyEnd, &bodyFlags, &bodySpeed, NULL); + bool bodyOnAnimNow = (bodyAnimating && bodyAnim==anim && bodyStart==animStart && bodyEnd==animEnd); + bool bodyMatchTorsFrame = false; + + +// GET VALUES FOR EXISTING TORSO ANIMATION +//=========================================== + float torsSpeed = 0.0f; + float torsCurrent = 0.0f; + int torsStart = 0; + int torsEnd = 0; + int torsFlags = 0; + int torsAnim = (*torsoAnim); + int torsBone = (gent->lowerLumbarBone); + bool torsTimerOn = ((*torsoAnimTimer)>0 || (*torsoAnimTimer)==-1); + bool torsPlay = (gent->client->NPC_class!=CLASS_RANCOR && (setAnimParts&SETANIM_TORSO) && (torsBone!=-1) && (animOverride || !torsTimerOn)); + bool torsAnimating = !!gi.G2API_GetBoneAnimIndex(&gent->ghoul2[gent->playerModel], torsBone, actualTime, &torsCurrent, &torsStart, &torsEnd, &torsFlags, &torsSpeed, NULL); + bool torsOnAnimNow = (torsAnimating && torsAnim==anim && torsStart==animStart && torsEnd==animEnd); + bool torsMatchBodyFrame = false; + + +// APPLY SYNC TO TORSO +//===================== + if (animSync && torsPlay && !bodyPlay && bodyOnAnimNow && (!torsOnAnimNow || torsCurrent!=bodyCurrent)) + { + torsMatchBodyFrame = true; + animCurrent = bodyCurrent; + } + if (animSync && bodyPlay && !torsPlay && torsOnAnimNow && (!bodyOnAnimNow || bodyCurrent!=torsCurrent)) + { + bodyMatchTorsFrame = true; + animCurrent = torsCurrent; + } + + // If Already Doing These Exact Parameters, Then Don't Play + //---------------------------------------------------------- + if (!animRestart) + { + torsPlay &= !(torsOnAnimNow && torsSpeed==animSpeed && !torsMatchBodyFrame); + bodyPlay &= !(bodyOnAnimNow && bodySpeed==animSpeed && !bodyMatchTorsFrame); + } + +#ifndef FINAL_BUILD + if ((cg_debugAnim.integer==3) || // 3 = do everyone + (cg_debugAnim.integer==1 && gent->s.number==0) || // 1 = only the player + (cg_debugAnim.integer==2 && gent->s.number!=0) || // 2 = only everyone else + (cg_debugAnim.integer==4 && gent->s.number!=cg_debugAnimTarget.integer) // 4 = specific entnum + ) + { + if (bodyPlay || torsPlay) + { + char* entName = gent->targetname; + char* location; + + // Select Entity Name + //-------------------- + if (!entName || !entName[0]) + { + entName = gent->NPC_targetname; + } + if (!entName || !entName[0]) + { + entName = gent->NPC_type; + } + if (!entName || !entName[0]) + { + entName = gent->classname; + } + if (!entName || !entName[0]) + { + entName = "UNKNOWN"; + } + + // Select Play Location + //---------------------- + if (bodyPlay && torsPlay) + { + location = "BOTH "; + } + else if (bodyPlay) + { + location = "LEGS "; + } + else + { + location = "TORSO"; + } + + // Print It! + //----------- + Com_Printf("[%10d] ent[%3d-%18s] %s anim[%3d] - %s\n", + actualTime, + gent->s.number, + entName, + location, + anim, + animTable[anim].name ); + } + } +#endif + + +// PLAY ON THE TORSO +//======================== + if (torsPlay) + { + *torsoAnim = anim; + float oldAnimCurrent = animCurrent; + if (animCurrent!=bodyCurrent && torsOnAnimNow && !animRestart && !torsMatchBodyFrame) + { + animCurrent = torsCurrent; + } + + gi.G2API_SetAnimIndex(&gent->ghoul2[gent->playerModel], curAnim.glaIndex); + gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], torsBone, + animStart, + animEnd, + (torsOnAnimNow && !animRestart)?(animFlags&~BONE_ANIM_BLEND):(animFlags), + animSpeed, + actualTime, + animCurrent, + blendTime); + + if (gent->motionBone!=-1) + { + gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], gent->motionBone, + animStart, + animEnd, + (torsOnAnimNow && !animRestart)?(animFlags&~BONE_ANIM_BLEND):(animFlags), + animSpeed, + actualTime, + animCurrent, + blendTime); + } + + animCurrent = oldAnimCurrent; + + // If This Animation Is To Be Locked And Held, Calculate The Duration And Set The Timer + //-------------------------------------------------------------------------------------- + if (animHold || animHoldless) + { + PM_SetTorsoAnimTimer(gent, torsoAnimTimer, animHoldMSec); + } + } + +// PLAY ON THE WHOLE BODY +//======================== + if (bodyPlay) + { + *legsAnim = anim; + + if (bodyOnAnimNow && !animRestart && !bodyMatchTorsFrame) + { + animCurrent = bodyCurrent; + } + + gi.G2API_SetAnimIndex(&gent->ghoul2[gent->playerModel], curAnim.glaIndex); + gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], bodyBone, + animStart, + animEnd, + (bodyOnAnimNow && !animRestart)?(animFlags&~BONE_ANIM_BLEND):(animFlags), + animSpeed, + actualTime, + animCurrent, + blendTime); + + // If This Animation Is To Be Locked And Held, Calculate The Duration And Set The Timer + //-------------------------------------------------------------------------------------- + if (animHold || animHoldless) + { + PM_SetLegsAnimTimer(gent, legsAnimTimer, animHoldMSec); + } + } + + + + + +// PRINT SOME DEBUG TEXT OF EXISTING VALUES +//========================================== + if (false) + { + gi.Printf("PLAYANIM: (%3d) Speed(%4.2f) ", anim, animSpeed); + if (bodyAnimating) + { + gi.Printf("BODY: (%4.2f) (%4.2f) ", bodyCurrent, bodySpeed); + } + else + { + gi.Printf(" "); + } + if (torsAnimating) + { + gi.Printf("TORS: (%4.2f) (%4.2f)\n", torsCurrent, torsSpeed); + } + else + { + gi.Printf("\n"); + } + } +} + + + +void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime) +{ // FIXME : once torsoAnim and legsAnim are in the same structure for NPC and Players + // rename PM_SetAnimFinal to PM_SetAnim and have both NPC and Players call PM_SetAnim + + if ( pm->ps->pm_type >= PM_DEAD ) + {//FIXME: sometimes we'll want to set anims when your dead... twitches, impacts, etc. + return; + } + + if ( pm->gent == NULL ) + { + return; + } + + if ( !pm->gent || pm->gent->health > 0 ) + {//don't lock anims if the guy is dead + if ( pm->ps->torsoAnimTimer + && PM_LockedAnim( pm->ps->torsoAnim ) + && !PM_LockedAnim( anim ) ) + {//nothing can override these special anims + setAnimParts &= ~SETANIM_TORSO; + } + + if ( pm->ps->legsAnimTimer + && PM_LockedAnim( pm->ps->legsAnim ) + && !PM_LockedAnim( anim ) ) + {//nothing can override these special anims + setAnimParts &= ~SETANIM_LEGS; + } + } + + if ( !setAnimParts ) + { + return; + } + + if (setAnimFlags&SETANIM_FLAG_OVERRIDE) + { +// pm->ps->animationTimer = 0; + + if (setAnimParts & SETANIM_TORSO) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || pm->ps->torsoAnim != anim ) + { + PM_SetTorsoAnimTimer( pm->gent, &pm->ps->torsoAnimTimer, 0 ); + } + } + if (setAnimParts & SETANIM_LEGS) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || pm->ps->legsAnim != anim ) + { + PM_SetLegsAnimTimer( pm->gent, &pm->ps->legsAnimTimer, 0 ); + } + } + } + + PM_SetAnimFinal(&pm->ps->torsoAnim,&pm->ps->legsAnim,setAnimParts,anim,setAnimFlags,&pm->ps->torsoAnimTimer,&pm->ps->legsAnimTimer,&g_entities[pm->ps->clientNum],blendTime);//was pm->gent +} + +bool TorsoAgainstWindTest( gentity_t* ent ) +{ + if (ent&&//valid ent + ent->client&&//a client + (ent->client->ps.weapon!=WP_SABER||ent->client->ps.saberMove==LS_READY)&&//either not holding a saber or the saber is in the ready pose + (ent->s.numbercurrentOrigin) && + gi.WE_IsOutside(ent->currentOrigin) ) + { + if (Q_stricmp(level.mapname, "t2_wedge")!=0) + { + vec3_t fwd; + vec3_t windDir; + if (gi.WE_GetWindVector(windDir, ent->currentOrigin)) + { + VectorScale(windDir, -1.0f, windDir); + AngleVectors(pm->gent->currentAngles, fwd, 0, 0); + if (DotProduct(fwd, windDir)>0.65f) + { + if (ent->client && ent->client->ps.torsoAnim!=BOTH_WIND) + { + NPC_SetAnim(ent, SETANIM_TORSO, BOTH_WIND, SETANIM_FLAG_NORMAL, 400); + } + return true; + } + } + } + } + return false; +} + +/* +------------------------- +PM_TorsoAnimLightsaber +------------------------- +*/ + + +// Note that this function is intended to set the animation for the player, but +// only does idle-ish anims. Anything that has a timer associated, such as attacks and blocks, +// are set by PM_WeaponLightsaber() + +extern Vehicle_t *G_IsRidingVehicle( gentity_t *pEnt ); +extern qboolean PM_LandingAnim( int anim ); +extern qboolean PM_JumpingAnim( int anim ); +qboolean PM_InCartwheel( int anim ); +void PM_TorsoAnimLightsaber() +{ + // ********************************************************* + // WEAPON_READY + // ********************************************************* + if ( pm->ps->forcePowersActive&(1<ps->forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//holding an enemy aloft with force-grip + return; + } + + if ( pm->ps->forcePowersActive&(1<ps->forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 ) + {//lightning + return; + } + + if ( pm->ps->forcePowersActive&(1<ps->saber[0].blade[0].active + && pm->ps->saber[0].blade[0].length < 3 + && !(pm->ps->saberEventFlags&SEF_HITWALL) + && pm->ps->weaponstate == WEAPON_RAISING ) + { + if (!G_IsRidingVehicle(pm->gent)) + { + PM_SetSaberMove(LS_DRAW); + } + return; + } + else if ( !pm->ps->SaberActive() && pm->ps->SaberLength() ) + { + if (!G_IsRidingVehicle(pm->gent)) + { + PM_SetSaberMove(LS_PUTAWAY); + } + return; + } + + if (pm->ps->weaponTime > 0) + { // weapon is already busy. + if ( pm->ps->torsoAnim == BOTH_TOSS1 + || pm->ps->torsoAnim == BOTH_TOSS2 ) + {//in toss + if ( !pm->ps->torsoAnimTimer ) + {//weird, get out of it, I guess + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + } + return; + } + + if ( pm->ps->weaponstate == WEAPON_READY || + pm->ps->weaponstate == WEAPON_CHARGING || + pm->ps->weaponstate == WEAPON_CHARGING_ALT ) + {//ready + if ( pm->ps->weapon == WP_SABER && (pm->ps->SaberLength()) ) + {//saber is on + // Select the proper idle Lightsaber attack move from the chart. + if (pm->ps->saberMove > LS_READY && pm->ps->saberMove < LS_MOVE_MAX) + { + PM_SetSaberMove(saberMoveData[pm->ps->saberMove].chain_idle); + } + else + { + if ( PM_JumpingAnim( pm->ps->legsAnim ) + || PM_LandingAnim( pm->ps->legsAnim ) + || PM_InCartwheel( pm->ps->legsAnim ) + || PM_FlippingAnim( pm->ps->legsAnim )) + { + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && pm->ps->torsoAnim == BOTH_BUTTON_HOLD ) + {//using something + if ( !pm->ps->useTime ) + {//stopped holding it, release + PM_SetAnim( pm, SETANIM_TORSO, BOTH_BUTTON_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + }//else still holding, leave it as it is + } + else + { + if ( (PM_RunningAnim( pm->ps->legsAnim ) + || pm->ps->legsAnim == BOTH_WALK_STAFF + || pm->ps->legsAnim == BOTH_WALK_DUAL + || pm->ps->legsAnim == BOTH_WALKBACK_STAFF + || pm->ps->legsAnim == BOTH_WALKBACK_DUAL ) + && pm->ps->saberBlockingTime < cg.time ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetSaberMove(LS_READY); + } + } + } + } + /* + if ( PM_JumpingAnim( pm->ps->legsAnim ) + || PM_LandingAnim( pm->ps->legsAnim ) + || PM_InCartwheel( pm->ps->legsAnim ) + || PM_FlippingAnim( pm->ps->legsAnim )) + {//jumping, landing cartwheel, flipping + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetSaberMove( LS_READY ); + } + */ + } + else if (TorsoAgainstWindTest(pm->gent)) + { + } + else if( pm->ps->legsAnim == BOTH_RUN1 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_RUN1,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_RUN2 )//&& pm->ps->saberAnimLevel != SS_STAFF ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_RUN2,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_RUN_STAFF ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_RUN_STAFF,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_RUN_DUAL ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_RUN_DUAL,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_WALK1 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_WALK1,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_WALK2 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_WALK2,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_WALK_STAFF ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_WALK_STAFF,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_WALK_DUAL ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_WALK_DUAL,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_CROUCH1IDLE && pm->ps->clientNum != 0 )//player falls through + { + //??? Why nothing? What if you were running??? + //PM_SetAnim(pm,SETANIM_TORSO,BOTH_CROUCH1IDLE,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_JUMP1 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_JUMP1,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else + {//Used to default to both_stand1 which is an arms-down anim +// PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_NORMAL);//TORSO_WEAPONREADY1 + // Select the next proper pose for the lightsaber assuming that there are no attacks. + if (pm->ps->saberMove > LS_READY && pm->ps->saberMove < LS_MOVE_MAX) + { + PM_SetSaberMove(saberMoveData[pm->ps->saberMove].chain_idle); + } + else + { + if ( PM_JumpingAnim( pm->ps->legsAnim ) + || PM_LandingAnim( pm->ps->legsAnim ) + || PM_InCartwheel( pm->ps->legsAnim ) + || PM_FlippingAnim( pm->ps->legsAnim )) + { + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && pm->ps->torsoAnim == BOTH_BUTTON_HOLD ) + {//using something + if ( !pm->ps->useTime ) + {//stopped holding it, release + PM_SetAnim( pm, SETANIM_TORSO, BOTH_BUTTON_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + }//else still holding, leave it as it is + } + else + { + PM_SetSaberMove(LS_READY); + } + } + } + } + } + + // ********************************************************* + // WEAPON_IDLE + // ********************************************************* + + else if ( pm->ps->weaponstate == WEAPON_IDLE ) + { + if (TorsoAgainstWindTest(pm->gent)) + { + } + else if( pm->ps->legsAnim == BOTH_GUARD_LOOKAROUND1 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_GUARD_LOOKAROUND1,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_GUARD_IDLE1 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_GUARD_IDLE1,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_STAND1IDLE1 + || pm->ps->legsAnim == BOTH_STAND2IDLE1 + || pm->ps->legsAnim == BOTH_STAND2IDLE2 + || pm->ps->legsAnim == BOTH_STAND3IDLE1 + || pm->ps->legsAnim == BOTH_STAND5IDLE1 ) + { + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_STAND2TO4 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND2TO4,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_STAND4TO2 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND4TO2,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_STAND4 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND4,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else + { +// This is now set in SetSaberMove. + // Idle for Lightsaber + if ( pm->gent && pm->gent->client ) + { +// pm->gent->client->saberTrail.inAction = qfalse; + } + + qboolean saberInAir = qtrue; + if ( pm->ps->saberInFlight ) + {//guiding saber + if ( PM_SaberInBrokenParry( pm->ps->saberMove ) || pm->ps->saberBlocked == BLOCKED_PARRY_BROKEN || PM_DodgeAnim( pm->ps->torsoAnim ) ) + {//we're stuck in a broken parry + saberInAir = qfalse; + } + if ( pm->ps->saberEntityNum < ENTITYNUM_NONE && pm->ps->saberEntityNum > 0 )//player is 0 + {// + if ( &g_entities[pm->ps->saberEntityNum] != NULL && g_entities[pm->ps->saberEntityNum].s.pos.trType == TR_STATIONARY ) + {//fell to the ground and we're not trying to pull it back + saberInAir = qfalse; + } + } + } + if ( pm->ps->saberInFlight + && saberInAir + && (!pm->ps->dualSabers || !pm->ps->saber[1].Active())) + { + if ( !PM_ForceAnim( pm->ps->torsoAnim ) + || pm->ps->torsoAnimTimer < 300 ) + {//don't interrupt a force power anim + if ( pm->ps->torsoAnim != BOTH_LOSE_SABER + || !pm->ps->torsoAnimTimer ) + { + PM_SetAnim( pm, SETANIM_TORSO,BOTH_SABERPULL,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + else + {//saber is on + // Idle for Lightsaber + if ( pm->gent && pm->gent->client ) + { + if ( !G_InCinematicSaberAnim( pm->gent ) ) + { + pm->gent->client->ps.SaberDeactivateTrail( 0 ); + } + } + // Idle for idle/ready Lightsaber +// PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_NORMAL);//TORSO_WEAPONIDLE1 + // Select the proper idle Lightsaber attack move from the chart. + if (pm->ps->saberMove > LS_READY && pm->ps->saberMove < LS_MOVE_MAX) + { + PM_SetSaberMove(saberMoveData[pm->ps->saberMove].chain_idle); + } + else + { + if ( PM_JumpingAnim( pm->ps->legsAnim ) + || PM_LandingAnim( pm->ps->legsAnim ) + || PM_InCartwheel( pm->ps->legsAnim ) + || PM_FlippingAnim( pm->ps->legsAnim )) + { + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && pm->ps->torsoAnim == BOTH_BUTTON_HOLD ) + {//using something + if ( !pm->ps->useTime ) + {//stopped holding it, release + PM_SetAnim( pm, SETANIM_TORSO, BOTH_BUTTON_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + }//else still holding, leave it as it is + } + else + { + if ( (PM_RunningAnim( pm->ps->legsAnim ) + || pm->ps->legsAnim == BOTH_WALK_STAFF + || pm->ps->legsAnim == BOTH_WALK_DUAL + || pm->ps->legsAnim == BOTH_WALKBACK_STAFF + || pm->ps->legsAnim == BOTH_WALKBACK_DUAL ) + && pm->ps->saberBlockingTime < cg.time ) + {//running w/1-handed weapon uses full-body anim + int setFlags = SETANIM_FLAG_NORMAL; + if ( PM_LandingAnim( pm->ps->torsoAnim ) ) + { + setFlags = SETANIM_FLAG_OVERRIDE; + } + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,setFlags); + } + else + { + PM_SetSaberMove(LS_READY); + } + } + } + } + } + } + } +} + + + + +/* +------------------------- +PM_TorsoAnimation +------------------------- +*/ + +void PM_TorsoAnimation( void ) +{//FIXME: Write a much smarter and more appropriate anim picking routine logic... +// int oldAnim; + if ( PM_InKnockDown( pm->ps ) || PM_InRoll( pm->ps )) + {//in knockdown + return; + } + + if ( (pm->ps->eFlags&EF_HELD_BY_WAMPA) ) + { + return; + } + + if ( (pm->ps->eFlags&EF_FORCE_DRAINED) ) + {//being drained + //PM_SetAnim( pm, SETANIM_TORSO, BOTH_HUGGEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + return; + } + if ( (pm->ps->forcePowersActive&(1<ps->forceDrainEntityNum < ENTITYNUM_WORLD ) + {//draining + //PM_SetAnim( pm, SETANIM_TORSO, BOTH_HUGGER1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + return; + } + + if( pm->gent && pm->gent->NPC && (pm->gent->NPC->scriptFlags & SCF_FORCED_MARCH) ) + { + return; + } + + if(pm->gent != NULL && pm->gent->client) + { + pm->gent->client->renderInfo.torsoFpsMod = 1.0f; + } + + if ( pm->gent && pm->ps && pm->ps->eFlags & EF_LOCKED_TO_WEAPON ) + { + if ( pm->gent->owner && pm->gent->owner->e_UseFunc == useF_emplaced_gun_use )//ugly way to tell, but... + {//full body + PM_SetAnim(pm,SETANIM_BOTH,BOTH_GUNSIT1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);//SETANIM_FLAG_NORMAL + } + else + {//torso + PM_SetAnim(pm,SETANIM_TORSO,BOTH_GUNSIT1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);//SETANIM_FLAG_NORMAL + } + return; + } +/* else if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE && pm->ps->clientNum < MAX_CLIENTS && (m_pVehicleInfo[((CVehicleNPC *)pm->gent->NPC)->m_iVehicleTypeID].numHands == 2 || g_speederControlScheme->value == 2) ) + {//can't look around + PM_SetAnim(pm,SETANIM_TORSO,m_pVehicleInfo[((CVehicleNPC *)pm->gent->NPC)->m_iVehicleTypeID].riderAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + return; + }*/ + + if ( pm->ps->taunting > level.time ) + { + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_ALORA ) + { + PM_SetAnim(pm,SETANIM_BOTH,BOTH_ALORA_TAUNT,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);//SETANIM_FLAG_NORMAL + } + else if ( pm->ps->weapon == WP_SABER && pm->ps->saberAnimLevel == SS_DUAL && PM_HasAnimation( pm->gent, BOTH_DUAL_TAUNT ) ) + { + PM_SetAnim(pm,SETANIM_BOTH,BOTH_DUAL_TAUNT,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);//SETANIM_FLAG_NORMAL + } + else if ( pm->ps->weapon == WP_SABER + && pm->ps->saberAnimLevel == SS_STAFF )//pm->ps->saber[0].type == SABER_STAFF ) + {//turn on the blades + if ( PM_HasAnimation( pm->gent, BOTH_STAFF_TAUNT ) ) + { + PM_SetAnim(pm,SETANIM_BOTH,BOTH_STAFF_TAUNT,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);//SETANIM_FLAG_NORMAL + } + /* + else + { + if ( !pm->ps->saber[0].blade[0].active ) + {//first blade is off + //turn it on + pm->ps->SaberBladeActivate( 0, 0, qtrue ); + if ( !pm->ps->saber[0].blade[1].active ) + {//second blade is also off, extend time of this taunt so we have enough time to turn them both on + pm->ps->taunting = level.time + 3000; + } + } + else if ( (pm->ps->taunting - level.time) < 1500 ) + {//only 1500ms left in taunt + if ( !pm->ps->saber[0].blade[1].active ) + {//second blade is off + //turn it on + pm->ps->SaberBladeActivate( 0, 1, qtrue ); + } + } + //pose + PM_SetAnim(pm,SETANIM_BOTH,BOTH_SABERSTAFF_STANCE,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + pm->ps->torsoAnimTimer = pm->ps->legsAnimTimer = (pm->ps->taunting - level.time); + } + */ + } + else if ( PM_HasAnimation( pm->gent, BOTH_GESTURE1 ) ) + { + PM_SetAnim(pm,SETANIM_BOTH,BOTH_GESTURE1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);//SETANIM_FLAG_NORMAL + pm->gent->client->ps.SaberActivateTrail( 100 ); + //FIXME: will this reset? + //FIXME: force-control (yellow glow) effect on hand and saber? + } + else + { + //PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE1,SETANIM_FLAG_NORMAL); + } + return; + } + + if (pm->ps->weapon == WP_SABER ) // WP_LIGHTSABER + { + qboolean saberInAir = qfalse; + if ( pm->ps->SaberLength() && !pm->ps->saberInFlight ) + { + PM_TorsoAnimLightsaber(); + } + else + { + if ( pm->ps->forcePowersActive&(1<ps->forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//holding an enemy aloft with force-grip + return; + } + if ( pm->ps->forcePowersActive&(1<ps->forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 ) + {//lightning + return; + } + if ( pm->ps->forcePowersActive&(1<ps->saberMove ) || pm->ps->saberBlocked == BLOCKED_PARRY_BROKEN || PM_DodgeAnim( pm->ps->torsoAnim ) ) + {//we're stuck in a broken parry + PM_TorsoAnimLightsaber(); + } + else + { + if ( pm->ps->saberEntityNum < ENTITYNUM_NONE && pm->ps->saberEntityNum > 0 )//player is 0 + {// + if ( &g_entities[pm->ps->saberEntityNum] != NULL && g_entities[pm->ps->saberEntityNum].s.pos.trType == TR_STATIONARY ) + {//fell to the ground and we're not trying to pull it back + saberInAir = qfalse; + } + } + + if ( pm->ps->saberInFlight + && saberInAir + && (!pm->ps->dualSabers //not using 2 sabers + || !pm->ps->saber[1].Active() //left one off + || pm->ps->torsoAnim == BOTH_SABERDUAL_STANCE//not attacking + || pm->ps->torsoAnim == BOTH_SABERPULL//not attacking + || pm->ps->torsoAnim == BOTH_STAND1//not attacking + || PM_RunningAnim( pm->ps->torsoAnim ) //not attacking + || PM_WalkingAnim( pm->ps->torsoAnim ) //not attacking + || PM_JumpingAnim( pm->ps->torsoAnim )//not attacking + || PM_SwimmingAnim( pm->ps->torsoAnim ) )//not attacking + ) + { + if ( !PM_ForceAnim( pm->ps->torsoAnim ) || pm->ps->torsoAnimTimer < 300 ) + {//don't interrupt a force power anim + if ( pm->ps->torsoAnim != BOTH_LOSE_SABER + || !pm->ps->torsoAnimTimer ) + { + PM_SetAnim( pm, SETANIM_TORSO,BOTH_SABERPULL,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + else + { + if ( PM_InSlopeAnim( pm->ps->legsAnim ) ) + {//HMM... this probably breaks the saber putaway and select anims + if ( pm->ps->SaberLength() > 0 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND2,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else + { + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && pm->ps->torsoAnim == BOTH_BUTTON_HOLD ) + {//using something + if ( !pm->ps->useTime ) + {//stopped holding it, release + PM_SetAnim( pm, SETANIM_TORSO, BOTH_BUTTON_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + }//else still holding, leave it as it is + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + } + } + } + } + + if (pm->ps->weaponTime<= 0 && (pm->ps->saberMove==LS_READY || pm->ps->SaberLength()==0) && !saberInAir) + { + TorsoAgainstWindTest(pm->gent); + } + return; + } + + if ( PM_ForceAnim( pm->ps->torsoAnim ) + && pm->ps->torsoAnimTimer > 0 ) + {//in a force anim, don't do a stand anim + return; + } + + + qboolean weaponBusy = qfalse; + + if ( pm->ps->weapon == WP_NONE ) + { + weaponBusy = qfalse; + } + else if ( pm->ps->weaponstate == WEAPON_FIRING || pm->ps->weaponstate == WEAPON_CHARGING || pm->ps->weaponstate == WEAPON_CHARGING_ALT ) + { + weaponBusy = qtrue; + } + else if ( pm->ps->lastShotTime > level.time - 3000 ) + { + weaponBusy = qtrue; + } + else if ( pm->ps->weaponTime > 0 ) + { + weaponBusy = qtrue; + } + else if ( pm->gent && pm->gent->client->fireDelay > 0 ) + { + weaponBusy = qtrue; + } + else if ( TorsoAgainstWindTest(pm->gent) ) + { + return; + } + else if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && cg.zoomTime > cg.time - 5000 ) + {//if we used binoculars recently, aim weapon + weaponBusy = qtrue; + pm->ps->weaponstate = WEAPON_IDLE; + } + else if ( pm->ps->pm_flags & PMF_DUCKED ) + {//ducking is considered on alert... plus looks stupid to have arms hanging down when crouched + weaponBusy = qtrue; + } + + if ( pm->ps->weapon == WP_NONE || + pm->ps->weaponstate == WEAPON_READY || + pm->ps->weaponstate == WEAPON_CHARGING || + pm->ps->weaponstate == WEAPON_CHARGING_ALT ) + { + if ( pm->ps->weapon == WP_SABER && pm->ps->SaberLength() ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_NORMAL);//TORSO_WEAPONREADY1 + } + else if( pm->ps->legsAnim == BOTH_RUN1 && !weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_RUN1,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_RUN2 && !weaponBusy )//&& pm->ps->saberAnimLevel != SS_STAFF ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_RUN2,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_RUN4 && !weaponBusy )//&& pm->ps->saberAnimLevel != SS_STAFF ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_RUN4,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_RUN_STAFF && !weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_RUN_STAFF,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_RUN_DUAL && !weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_RUN_DUAL,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_WALK1 && !weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_WALK1,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_WALK2 && !weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_WALK2,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_WALK_STAFF && !weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_WALK_STAFF,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_WALK_DUAL&& !weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_WALK_DUAL,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_CROUCH1IDLE && pm->ps->clientNum != 0 )//player falls through + { + //??? Why nothing? What if you were running??? + //PM_SetAnim(pm,SETANIM_TORSO,BOTH_CROUCH1IDLE,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_JUMP1 && !weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_JUMP1,SETANIM_FLAG_NORMAL, 100); // Only blend over 100ms + } + else if( pm->ps->legsAnim == BOTH_SWIM_IDLE1 && !weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_SWIM_IDLE1,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_SWIMFORWARD && !weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_SWIMFORWARD,SETANIM_FLAG_NORMAL); + } + else if ( pm->ps->weapon == WP_NONE ) + { + int legsAnim = pm->ps->legsAnim; + /* + if ( PM_RollingAnim( legsAnim ) || + PM_FlippingAnim( legsAnim ) || + PM_JumpingAnim( legsAnim ) || + PM_PainAnim( legsAnim ) || + PM_SwimmingAnim( legsAnim ) ) + */ + { + PM_SetAnim(pm, SETANIM_TORSO, legsAnim, SETANIM_FLAG_NORMAL ); + } + } + else + {//Used to default to both_stand1 which is an arms-down anim + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && pm->ps->torsoAnim == BOTH_BUTTON_HOLD ) + {//using something + if ( !pm->ps->useTime ) + {//stopped holding it, release + PM_SetAnim( pm, SETANIM_TORSO, BOTH_BUTTON_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + }//else still holding, leave it as it is + } + else if ( pm->gent != NULL + && (pm->gent->s.numbergent)) + && pm->ps->weaponstate != WEAPON_CHARGING + && pm->ps->weaponstate != WEAPON_CHARGING_ALT ) + {//PLayer- temp hack for weapon frame + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_RANCOR ) + {//ignore + } + else if ( pm->ps->weapon == WP_MELEE ) + {//hehe + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND6,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else if ( PM_InSpecialJump( pm->ps->legsAnim ) ) + {//use legs anim + //FIXME: or just use whatever's currently playing? + //PM_SetAnim( pm, SETANIM_TORSO, pm->ps->legsAnim, SETANIM_FLAG_NORMAL ); + } + else + { + switch(pm->ps->weapon) + { + // ******************************************************** + case WP_SABER: // WP_LIGHTSABER + // Ready pose for Lightsaber +// PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_NORMAL);//TORSO_WEAPONREADY1 + // Select the next proper pose for the lightsaber assuming that there are no attacks. + if (pm->ps->saberMove > LS_NONE && pm->ps->saberMove < LS_MOVE_MAX) + { + PM_SetSaberMove(saberMoveData[pm->ps->saberMove].chain_idle); + } + break; + // ******************************************************** + + case WP_BRYAR_PISTOL: + //FIXME: if recently fired, hold the ready! + if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT || weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY2,SETANIM_FLAG_NORMAL); + } + else if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY2,SETANIM_FLAG_NORMAL); + } + break; + case WP_BLASTER_PISTOL: + if ( pm->gent + && pm->gent->weaponModel[1] > 0 ) + {//dual pistols + if ( weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_GUNSIT1,SETANIM_FLAG_NORMAL); + } + else if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND6,SETANIM_FLAG_NORMAL); + } + } + else + {//single pistols + if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT || weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY2,SETANIM_FLAG_NORMAL); + } + else if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY2,SETANIM_FLAG_NORMAL); + } + } + break; + case WP_NONE: + //NOTE: should never get here + break; + case WP_MELEE: + if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_RANCOR ) + {//ignore + } + else if ( pm->gent && pm->gent->client && !PM_DroidMelee( pm->gent->client->NPC_class ) ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND6,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + break; + case WP_TUSKEN_STAFF: + if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm, SETANIM_TORSO, BOTH_STAND3, SETANIM_FLAG_NORMAL); + } + break; + + case WP_NOGHRI_STICK: + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_ATTACK2,SETANIM_FLAG_NORMAL); + break; + + case WP_BLASTER: + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_ATTACK2,SETANIM_FLAG_NORMAL); + break; + case WP_DISRUPTOR: + case WP_TUSKEN_RIFLE: + if ( (pm->ps->weaponstate != WEAPON_FIRING + && pm->ps->weaponstate != WEAPON_CHARGING + && pm->ps->weaponstate != WEAPON_CHARGING_ALT) + || PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running sniper weapon uses normal ready + if ( pm->ps->clientNum ) + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONREADY3, SETANIM_FLAG_NORMAL ); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONREADY3, SETANIM_FLAG_NORMAL ); + } + } + else + { + if ( pm->ps->clientNum ) + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONREADY4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );//TORSO_WEAPONREADY4//SETANIM_FLAG_RESTART| + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONREADY4, SETANIM_FLAG_NORMAL ); + } + } + break; + case WP_BOT_LASER: + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + break; + case WP_THERMAL: + if ( pm->ps->weaponstate != WEAPON_FIRING + && pm->ps->weaponstate != WEAPON_CHARGING + && pm->ps->weaponstate != WEAPON_CHARGING_ALT + && (PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim )) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && (pm->ps->weaponstate == WEAPON_CHARGING || pm->ps->weaponstate == WEAPON_CHARGING_ALT) ) + {//player pulling back to throw + if ( PM_StandingAnim( pm->ps->legsAnim ) ) + { + PM_SetAnim( pm, SETANIM_LEGS, BOTH_THERMAL_READY, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else if ( pm->ps->legsAnim == BOTH_THERMAL_READY ) + {//sigh... hold it so pm_footsteps doesn't override + if ( pm->ps->legsAnimTimer < 100 ) + { + pm->ps->legsAnimTimer = 100; + } + } + PM_SetAnim( pm, SETANIM_TORSO, BOTH_THERMAL_READY, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + { + if ( weaponBusy ) + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONREADY10, SETANIM_FLAG_NORMAL ); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, BOTH_STAND1, SETANIM_FLAG_NORMAL ); + } + } + } + break; + case WP_REPEATER: + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_GALAKMECH ) + {// + if ( pm->gent->alt_fire ) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); + } + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + break; + case WP_TRIP_MINE: + case WP_DET_PACK: + if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + if ( weaponBusy ) + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONREADY3, SETANIM_FLAG_NORMAL ); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, BOTH_STAND1, SETANIM_FLAG_NORMAL ); + } + } + break; + default: + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + break; + } + } + } + } + else if ( pm->ps->weaponstate == WEAPON_IDLE ) + { + if( pm->ps->legsAnim == BOTH_GUARD_LOOKAROUND1 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_GUARD_LOOKAROUND1,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_GUARD_IDLE1 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_GUARD_IDLE1,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_STAND1IDLE1 + || pm->ps->legsAnim == BOTH_STAND2IDLE1 + || pm->ps->legsAnim == BOTH_STAND2IDLE2 + || pm->ps->legsAnim == BOTH_STAND3IDLE1 + || pm->ps->legsAnim == BOTH_STAND5IDLE1 ) + { + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + pm->ps->saberMove = LS_READY; + } + else if( pm->ps->legsAnim == BOTH_STAND2TO4 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND2TO4,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_STAND4TO2 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND4TO2,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_STAND4 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND4,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_SWIM_IDLE1 ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_SWIM_IDLE1,SETANIM_FLAG_NORMAL); + } + else if( pm->ps->legsAnim == BOTH_SWIMFORWARD ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_SWIMFORWARD,SETANIM_FLAG_NORMAL); + } + else if ( PM_InSpecialJump( pm->ps->legsAnim ) ) + {//use legs anim + //FIXME: or just use whatever's currently playing? + //PM_SetAnim( pm, SETANIM_TORSO, pm->ps->legsAnim, SETANIM_FLAG_NORMAL ); + } + else if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && pm->ps->torsoAnim == BOTH_BUTTON_HOLD ) + {//using something + if ( !pm->ps->useTime ) + {//stopped holding it, release + PM_SetAnim( pm, SETANIM_TORSO, BOTH_BUTTON_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + }//else still holding, leave it as it is + } + else + { + if ( !weaponBusy + && pm->ps->weapon != WP_BOWCASTER + && pm->ps->weapon != WP_REPEATER + && pm->ps->weapon != WP_FLECHETTE + && pm->ps->weapon != WP_ROCKET_LAUNCHER + && pm->ps->weapon != WP_CONCUSSION + && ( PM_RunningAnim( pm->ps->legsAnim ) + || (PM_WalkingAnim( pm->ps->legsAnim ) && (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) ) + {//running w/1-handed or light 2-handed weapon uses full-body anim if you're not using the weapon right now + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + switch ( pm->ps->weapon ) + { + // ******************************************************** + case WP_SABER: // WP_LIGHTSABER + // Shouldn't get here, should go to TorsoAnimLightsaber + break; + // ******************************************************** + + case WP_BRYAR_PISTOL: + if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT || weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY2,SETANIM_FLAG_NORMAL); + } + else if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE2,SETANIM_FLAG_NORMAL); + } + break; + case WP_BLASTER_PISTOL: + if ( pm->gent + && pm->gent->weaponModel[1] > 0 ) + {//dual pistols + if ( weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_GUNSIT1,SETANIM_FLAG_NORMAL); + } + else if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else + {//single pistols + if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT || weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY2,SETANIM_FLAG_NORMAL); + } + else if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE2,SETANIM_FLAG_NORMAL); + } + } + break; + + case WP_NONE: + //NOTE: should never get here + break; + + case WP_MELEE: + if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_RANCOR ) + {//ignore + } + else if ( pm->gent && pm->gent->client && !PM_DroidMelee( pm->gent->client->NPC_class ) ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND6,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + break; + + case WP_TUSKEN_STAFF: + if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm, SETANIM_TORSO, BOTH_STAND3, SETANIM_FLAG_NORMAL); + } + break; + + case WP_NOGHRI_STICK: + if ( weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); + } + break; + + case WP_BLASTER: + if ( weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); + } + break; + + case WP_DISRUPTOR: + case WP_TUSKEN_RIFLE: + if ( (pm->ps->weaponstate != WEAPON_FIRING + && pm->ps->weaponstate != WEAPON_CHARGING + && pm->ps->weaponstate != WEAPON_CHARGING_ALT) + || PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running sniper weapon uses normal ready + if ( pm->ps->clientNum ) + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONREADY3, SETANIM_FLAG_NORMAL ); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONREADY3, SETANIM_FLAG_NORMAL ); + } + } + else + { + if ( pm->ps->clientNum ) + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONREADY4, SETANIM_FLAG_NORMAL ); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONREADY4, SETANIM_FLAG_NORMAL ); + } + } + break; + + case WP_BOT_LASER: + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + break; + + case WP_THERMAL: + if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + if ( weaponBusy ) + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONIDLE10, SETANIM_FLAG_NORMAL ); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, BOTH_STAND1, SETANIM_FLAG_NORMAL ); + } + } + break; + + case WP_REPEATER: + if ( weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); + } + break; + case WP_TRIP_MINE: + case WP_DET_PACK: + if ( PM_RunningAnim( pm->ps->legsAnim ) + || PM_WalkingAnim( pm->ps->legsAnim ) + || PM_JumpingAnim( pm->ps->legsAnim ) + || PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//running w/1-handed weapon uses full-body anim + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + } + else + { + if ( weaponBusy ) + { + PM_SetAnim( pm, SETANIM_TORSO, TORSO_WEAPONIDLE3, SETANIM_FLAG_NORMAL ); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, BOTH_STAND1, SETANIM_FLAG_NORMAL ); + } + } + break; + + default: + if ( weaponBusy ) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); + } + break; + } + } + } + } +} + +//========================================================================= +// Anim checking utils +//========================================================================= + +int PM_GetTurnAnim( gentity_t *gent, int anim ) +{ + if ( !gent ) + { + return -1; + } + + switch( anim ) + { + case BOTH_STAND1: //# Standing idle: no weapon: hands down + case BOTH_STAND1IDLE1: //# Random standing idle + case BOTH_STAND2: //# Standing idle with a weapon + case BOTH_SABERFAST_STANCE: + case BOTH_SABERSLOW_STANCE: + case BOTH_STAND2IDLE1: //# Random standing idle + case BOTH_STAND2IDLE2: //# Random standing idle + case BOTH_STAND3: //# Standing hands behind back: at ease: etc. + case BOTH_STAND3IDLE1: //# Random standing idle + case BOTH_STAND4: //# two handed: gun down: relaxed stand + case BOTH_STAND5: //# standing idle, no weapon, hand down, back straight + case BOTH_STAND5IDLE1: //# Random standing idle + case BOTH_STAND6: //# one handed: gun at side: relaxed stand + case BOTH_STAND2TO4: //# Transition from stand2 to stand4 + case BOTH_STAND4TO2: //# Transition from stand4 to stand2 + case BOTH_GESTURE1: //# Generic gesture: non-specific + case BOTH_GESTURE2: //# Generic gesture: non-specific + case BOTH_TALK1: //# Generic talk anim + case BOTH_TALK2: //# Generic talk anim + if ( PM_HasAnimation( gent, LEGS_TURN1 ) ) + { + return LEGS_TURN1; + } + else + { + return -1; + } + break; + case BOTH_ATTACK1: //# Attack with generic 1-handed weapon + case BOTH_ATTACK2: //# Attack with generic 2-handed weapon + case BOTH_ATTACK3: //# Attack with heavy 2-handed weapon + case BOTH_ATTACK4: //# Attack with ??? + case BOTH_MELEE1: //# First melee attack + case BOTH_MELEE2: //# Second melee attack + case BOTH_GUARD_LOOKAROUND1: //# Cradling weapon and looking around + case BOTH_GUARD_IDLE1: //# Cradling weapon and standing + if ( PM_HasAnimation( gent, LEGS_TURN2 ) ) + { + return LEGS_TURN2; + } + else + { + return -1; + } + break; + default: + return -1; + break; + } +} + +int PM_TurnAnimForLegsAnim( gentity_t *gent, int anim ) +{ + if ( !gent ) + { + return -1; + } + + switch( anim ) + { + case BOTH_STAND1: //# Standing idle: no weapon: hands down + case BOTH_STAND1IDLE1: //# Random standing idle + if ( PM_HasAnimation( gent, BOTH_TURNSTAND1 ) ) + { + return BOTH_TURNSTAND1; + } + else + { + return -1; + } + break; + case BOTH_STAND2: //# Standing idle with a weapon + case BOTH_SABERFAST_STANCE: + case BOTH_SABERSLOW_STANCE: + case BOTH_STAND2IDLE1: //# Random standing idle + case BOTH_STAND2IDLE2: //# Random standing idle + if ( PM_HasAnimation( gent, BOTH_TURNSTAND2 ) ) + { + return BOTH_TURNSTAND2; + } + else + { + return -1; + } + break; + case BOTH_STAND3: //# Standing hands behind back: at ease: etc. + case BOTH_STAND3IDLE1: //# Random standing idle + if ( PM_HasAnimation( gent, BOTH_TURNSTAND3 ) ) + { + return BOTH_TURNSTAND3; + } + else + { + return -1; + } + break; + case BOTH_STAND4: //# two handed: gun down: relaxed stand + if ( PM_HasAnimation( gent, BOTH_TURNSTAND4 ) ) + { + return BOTH_TURNSTAND4; + } + else + { + return -1; + } + break; + case BOTH_STAND5: //# standing idle, no weapon, hand down, back straight + case BOTH_STAND5IDLE1: //# Random standing idle + if ( PM_HasAnimation( gent, BOTH_TURNSTAND5 ) ) + { + return BOTH_TURNSTAND5; + } + else + { + return -1; + } + break; + case BOTH_CROUCH1: //# Transition from standing to crouch + case BOTH_CROUCH1IDLE: //# Crouching idle + /* + case BOTH_UNCROUCH1: //# Transition from crouch to standing + case BOTH_CROUCH2TOSTAND1: //# going from crouch2 to stand1 + case BOTH_CROUCH3: //# Desann crouching down to Kyle (cin 9) + case BOTH_UNCROUCH3: //# Desann uncrouching down to Kyle (cin 9) + case BOTH_CROUCH4: //# Slower version of crouch1 for cinematics + case BOTH_UNCROUCH4: //# Slower version of uncrouch1 for cinematics + */ + if ( PM_HasAnimation( gent, BOTH_TURNCROUCH1 ) ) + { + return BOTH_TURNCROUCH1; + } + else + { + return -1; + } + break; + default: + return -1; + break; + } +} + +qboolean PM_InOnGroundAnim ( playerState_t *ps ) +{ + switch( ps->legsAnim ) + { + case BOTH_DEAD1: + case BOTH_DEAD2: + case BOTH_DEAD3: + case BOTH_DEAD4: + case BOTH_DEAD5: + case BOTH_DEADFORWARD1: + case BOTH_DEADBACKWARD1: + case BOTH_DEADFORWARD2: + case BOTH_DEADBACKWARD2: + case BOTH_LYINGDEATH1: + case BOTH_LYINGDEAD1: + case BOTH_SLEEP1: //# laying on back-rknee up-rhand on torso + return qtrue; + break; + case BOTH_KNOCKDOWN1: //# + case BOTH_KNOCKDOWN2: //# + case BOTH_KNOCKDOWN3: //# + case BOTH_KNOCKDOWN4: //# + case BOTH_KNOCKDOWN5: //# + case BOTH_LK_DL_ST_T_SB_1_L: + case BOTH_RELEASED: + if ( ps->legsAnimTimer < 500 ) + {//pretty much horizontal by this point + return qtrue; + } + break; + case BOTH_PLAYER_PA_3_FLY: + if ( ps->legsAnimTimer < 300 ) + {//pretty much horizontal by this point + return qtrue; + } + /* + else if ( ps->clientNum < MAX_CLIENTS + && ps->legsAnimTimer < 300 + PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME ) + { + return qtrue; + } + */ + break; + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_GETUP_CROUCH_F1: + case BOTH_GETUP_CROUCH_B1: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_FORCE_GETUP_B6: + if ( ps->legsAnimTimer > PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)ps->legsAnim )-400 ) + {//still pretty much horizontal at this point + return qtrue; + } + break; + } + + return qfalse; +} + +qboolean PM_InSpecialDeathAnim( int anim ) +{ + switch( pm->ps->legsAnim ) + { + case BOTH_DEATH_ROLL: //# Death anim from a roll + case BOTH_DEATH_FLIP: //# Death anim from a flip + case BOTH_DEATH_SPIN_90_R: //# Death anim when facing 90 degrees right + case BOTH_DEATH_SPIN_90_L: //# Death anim when facing 90 degrees left + case BOTH_DEATH_SPIN_180: //# Death anim when facing backwards + case BOTH_DEATH_LYING_UP: //# Death anim when lying on back + case BOTH_DEATH_LYING_DN: //# Death anim when lying on front + case BOTH_DEATH_FALLING_DN: //# Death anim when falling on face + case BOTH_DEATH_FALLING_UP: //# Death anim when falling on back + case BOTH_DEATH_CROUCHED: //# Death anim when crouched + return qtrue; + break; + default: + return qfalse; + break; + } +} + +qboolean PM_InDeathAnim ( void ) +{//Purposely does not cover stumbledeath and falldeath... + switch( pm->ps->legsAnim ) + { + case BOTH_DEATH1: //# First Death anim + case BOTH_DEATH2: //# Second Death anim + case BOTH_DEATH3: //# Third Death anim + case BOTH_DEATH4: //# Fourth Death anim + case BOTH_DEATH5: //# Fifth Death anim + case BOTH_DEATH6: //# Sixth Death anim + case BOTH_DEATH7: //# Seventh Death anim + case BOTH_DEATH8: //# + case BOTH_DEATH9: //# + case BOTH_DEATH10: //# + case BOTH_DEATH11: //# + case BOTH_DEATH12: //# + case BOTH_DEATH13: //# + case BOTH_DEATH14: //# + case BOTH_DEATH14_UNGRIP: //# Desann's end death (cin #35) + case BOTH_DEATH14_SITUP: //# Tavion sitting up after having been thrown (cin #23) + case BOTH_DEATH15: //# + case BOTH_DEATH16: //# + case BOTH_DEATH17: //# + case BOTH_DEATH18: //# + case BOTH_DEATH19: //# + case BOTH_DEATH20: //# + case BOTH_DEATH21: //# + case BOTH_DEATH22: //# + case BOTH_DEATH23: //# + case BOTH_DEATH24: //# + case BOTH_DEATH25: //# + + case BOTH_DEATHFORWARD1: //# First Death in which they get thrown forward + case BOTH_DEATHFORWARD2: //# Second Death in which they get thrown forward + case BOTH_DEATHFORWARD3: //# Tavion's falling in cin# 23 + case BOTH_DEATHBACKWARD1: //# First Death in which they get thrown backward + case BOTH_DEATHBACKWARD2: //# Second Death in which they get thrown backward + + case BOTH_DEATH1IDLE: //# Idle while close to death + case BOTH_LYINGDEATH1: //# Death to play when killed lying down + case BOTH_STUMBLEDEATH1: //# Stumble forward and fall face first death + case BOTH_FALLDEATH1: //# Fall forward off a high cliff and splat death - start + case BOTH_FALLDEATH1INAIR: //# Fall forward off a high cliff and splat death - loop + case BOTH_FALLDEATH1LAND: //# Fall forward off a high cliff and splat death - hit bottom + //# #sep case BOTH_ DEAD POSES # Should be last frame of corresponding previous anims + case BOTH_DEAD1: //# First Death finished pose + case BOTH_DEAD2: //# Second Death finished pose + case BOTH_DEAD3: //# Third Death finished pose + case BOTH_DEAD4: //# Fourth Death finished pose + case BOTH_DEAD5: //# Fifth Death finished pose + case BOTH_DEAD6: //# Sixth Death finished pose + case BOTH_DEAD7: //# Seventh Death finished pose + case BOTH_DEAD8: //# + case BOTH_DEAD9: //# + case BOTH_DEAD10: //# + case BOTH_DEAD11: //# + case BOTH_DEAD12: //# + case BOTH_DEAD13: //# + case BOTH_DEAD14: //# + case BOTH_DEAD15: //# + case BOTH_DEAD16: //# + case BOTH_DEAD17: //# + case BOTH_DEAD18: //# + case BOTH_DEAD19: //# + case BOTH_DEAD20: //# + case BOTH_DEAD21: //# + case BOTH_DEAD22: //# + case BOTH_DEAD23: //# + case BOTH_DEAD24: //# + case BOTH_DEAD25: //# + case BOTH_DEADFORWARD1: //# First thrown forward death finished pose + case BOTH_DEADFORWARD2: //# Second thrown forward death finished pose + case BOTH_DEADBACKWARD1: //# First thrown backward death finished pose + case BOTH_DEADBACKWARD2: //# Second thrown backward death finished pose + case BOTH_LYINGDEAD1: //# Killed lying down death finished pose + case BOTH_STUMBLEDEAD1: //# Stumble forward death finished pose + case BOTH_FALLDEAD1LAND: //# Fall forward and splat death finished pose + //# #sep case BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + case BOTH_DEADFLOP1: //# React to being shot from First Death finished pose + case BOTH_DEADFLOP2: //# React to being shot from Second Death finished pose + case BOTH_DISMEMBER_HEAD1: //# + case BOTH_DISMEMBER_TORSO1: //# + case BOTH_DISMEMBER_LLEG: //# + case BOTH_DISMEMBER_RLEG: //# + case BOTH_DISMEMBER_RARM: //# + case BOTH_DISMEMBER_LARM: //# + return qtrue; + break; + default: + return PM_InSpecialDeathAnim( pm->ps->legsAnim ); + break; + } +} + +qboolean PM_InCartwheel( int anim ) +{ + switch ( anim ) + { + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_ARIAL_F1: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_InButterfly( int anim ) +{ + switch ( anim ) + { + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_StandingAnim( int anim ) +{//NOTE: does not check idles or special (cinematic) stands + switch ( anim ) + { + case BOTH_STAND1: + case BOTH_STAND2: + case BOTH_STAND3: + case BOTH_STAND4: + case BOTH_ATTACK3: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_InAirKickingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_A7_KICK_F_AIR: + case BOTH_A7_KICK_B_AIR: + case BOTH_A7_KICK_R_AIR: + case BOTH_A7_KICK_L_AIR: + return qtrue; + } + return qfalse; +} + +qboolean PM_KickingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_A7_KICK_F: + case BOTH_A7_KICK_B: + case BOTH_A7_KICK_R: + case BOTH_A7_KICK_L: + case BOTH_A7_KICK_S: + case BOTH_A7_KICK_BF: + case BOTH_A7_KICK_RL: + //NOT a kick, but acts like one: + case BOTH_A7_HILT: + //NOT kicks, but do kick traces anyway + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + return qtrue; + break; + default: + return PM_InAirKickingAnim( anim ); + break; + } + //return qfalse; +} + +qboolean PM_StabDownAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_STABDOWN: + case BOTH_STABDOWN_STAFF: + case BOTH_STABDOWN_DUAL: + return qtrue; + } + return qfalse; +} + +qboolean PM_GoingToAttackDown( playerState_t *ps ) +{ + if ( PM_StabDownAnim( ps->torsoAnim )//stabbing downward + || ps->saberMove == LS_A_LUNGE//lunge + || ps->saberMove == LS_A_JUMP_T__B_//death from above + || ps->saberMove == LS_A_T2B//attacking top to bottom + || ps->saberMove == LS_S_T2B//starting at attack downward + || (PM_SaberInTransition( ps->saberMove ) && saberMoveData[ps->saberMove].endQuad == Q_T) )//transitioning to a top to bottom attack + { + return qtrue; + } + return qfalse; +} + +qboolean PM_ForceUsingSaberAnim( int anim ) +{//saber/acrobatic anims that should prevent you from recharging force power while you're in them... + switch ( anim ) + { + case BOTH_JUMPFLIPSLASHDOWN1: + case BOTH_JUMPFLIPSTABDOWN: + case BOTH_FORCELEAP2_T__B_: + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + case BOTH_FORCELONGLEAP_START: + case BOTH_FORCELONGLEAP_ATTACK: + case BOTH_FORCEWALLRUNFLIP_START: + case BOTH_FORCEWALLRUNFLIP_END: + case BOTH_FORCEWALLRUNFLIP_ALT: + case BOTH_FORCEWALLREBOUND_FORWARD: + case BOTH_FORCEWALLREBOUND_LEFT: + case BOTH_FORCEWALLREBOUND_BACK: + case BOTH_FORCEWALLREBOUND_RIGHT: + case BOTH_FLIP_ATTACK7: + case BOTH_FLIP_HOLD7: + case BOTH_FLIP_LAND: + case BOTH_PULL_IMPALE_STAB: + case BOTH_PULL_IMPALE_SWING: + case BOTH_A6_SABERPROTECT: + case BOTH_A7_SOULCAL: + case BOTH_A1_SPECIAL: + case BOTH_A2_SPECIAL: + case BOTH_A3_SPECIAL: + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + case BOTH_FLIP_LEFT: + case BOTH_FLIP_BACK1: + case BOTH_FLIP_BACK2: + case BOTH_FLIP_BACK3: + case BOTH_ALORA_FLIP_B: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + case BOTH_WALL_RUN_RIGHT: + case BOTH_WALL_RUN_RIGHT_FLIP: + case BOTH_WALL_RUN_RIGHT_STOP: + case BOTH_WALL_RUN_LEFT: + case BOTH_WALL_RUN_LEFT_FLIP: + case BOTH_WALL_RUN_LEFT_STOP: + case BOTH_WALL_FLIP_RIGHT: + case BOTH_WALL_FLIP_LEFT: + case BOTH_FORCEJUMP1: + case BOTH_FORCEINAIR1: + case BOTH_FORCELAND1: + case BOTH_FORCEJUMPBACK1: + case BOTH_FORCEINAIRBACK1: + case BOTH_FORCELANDBACK1: + case BOTH_FORCEJUMPLEFT1: + case BOTH_FORCEINAIRLEFT1: + case BOTH_FORCELANDLEFT1: + case BOTH_FORCEJUMPRIGHT1: + case BOTH_FORCEINAIRRIGHT1: + case BOTH_FORCELANDRIGHT1: + case BOTH_FLIP_F: + case BOTH_FLIP_B: + case BOTH_FLIP_L: + case BOTH_FLIP_R: + case BOTH_ALORA_FLIP_1: + case BOTH_ALORA_FLIP_2: + case BOTH_ALORA_FLIP_3: + case BOTH_DODGE_FL: + case BOTH_DODGE_FR: + case BOTH_DODGE_BL: + case BOTH_DODGE_BR: + case BOTH_DODGE_L: + case BOTH_DODGE_R: + case BOTH_DODGE_HOLD_FL: + case BOTH_DODGE_HOLD_FR: + case BOTH_DODGE_HOLD_BL: + case BOTH_DODGE_HOLD_BR: + case BOTH_DODGE_HOLD_L: + case BOTH_DODGE_HOLD_R: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_FORCE_GETUP_B6: + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + case BOTH_WALL_FLIP_BACK1: + case BOTH_WALL_FLIP_BACK2: + case BOTH_SPIN1: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_DEFLECTSLASH__R__L_FIN: + case BOTH_ARIAL_F1: + return qtrue; + } + return qfalse; +} + +qboolean G_HasKnockdownAnims( gentity_t *ent ) +{ + if ( PM_HasAnimation( ent, BOTH_KNOCKDOWN1 ) + && PM_HasAnimation( ent, BOTH_KNOCKDOWN2 ) + && PM_HasAnimation( ent, BOTH_KNOCKDOWN3 ) + && PM_HasAnimation( ent, BOTH_KNOCKDOWN4 ) + && PM_HasAnimation( ent, BOTH_KNOCKDOWN5 ) ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_InAttackRoll( int anim ) +{ + switch ( anim ) + { + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + return qtrue; + } + return qfalse; +} + +qboolean PM_LockedAnim( int anim ) +{//anims that can *NEVER* be overridden, regardless + switch ( anim ) + { + case BOTH_KYLE_PA_1: + case BOTH_KYLE_PA_2: + case BOTH_KYLE_PA_3: + case BOTH_PLAYER_PA_1: + case BOTH_PLAYER_PA_2: + case BOTH_PLAYER_PA_3: + case BOTH_PLAYER_PA_3_FLY: + case BOTH_TAVION_SCEPTERGROUND: + case BOTH_TAVION_SWORDPOWER: + case BOTH_SCEPTER_START: + case BOTH_SCEPTER_HOLD: + case BOTH_SCEPTER_STOP: + //grabbed by wampa + case BOTH_GRABBED: //# + case BOTH_RELEASED: //# when Wampa drops player, transitions into fall on back + case BOTH_HANG_IDLE: //# + case BOTH_HANG_ATTACK: //# + case BOTH_HANG_PAIN: //# + return qtrue; + } + return qfalse; +} + +qboolean PM_SuperBreakLoseAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_LK_S_DL_S_SB_1_L: //super break I lost + case BOTH_LK_S_DL_T_SB_1_L: //super break I lost + case BOTH_LK_S_ST_S_SB_1_L: //super break I lost + case BOTH_LK_S_ST_T_SB_1_L: //super break I lost + case BOTH_LK_S_S_S_SB_1_L: //super break I lost + case BOTH_LK_S_S_T_SB_1_L: //super break I lost + case BOTH_LK_DL_DL_S_SB_1_L: //super break I lost + case BOTH_LK_DL_DL_T_SB_1_L: //super break I lost + case BOTH_LK_DL_ST_S_SB_1_L: //super break I lost + case BOTH_LK_DL_ST_T_SB_1_L: //super break I lost + case BOTH_LK_DL_S_S_SB_1_L: //super break I lost + case BOTH_LK_DL_S_T_SB_1_L: //super break I lost + case BOTH_LK_ST_DL_S_SB_1_L: //super break I lost + case BOTH_LK_ST_DL_T_SB_1_L: //super break I lost + case BOTH_LK_ST_ST_S_SB_1_L: //super break I lost + case BOTH_LK_ST_ST_T_SB_1_L: //super break I lost + case BOTH_LK_ST_S_S_SB_1_L: //super break I lost + case BOTH_LK_ST_S_T_SB_1_L: //super break I lost + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SuperBreakWinAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_LK_S_DL_S_SB_1_W: //super break I won + case BOTH_LK_S_DL_T_SB_1_W: //super break I won + case BOTH_LK_S_ST_S_SB_1_W: //super break I won + case BOTH_LK_S_ST_T_SB_1_W: //super break I won + case BOTH_LK_S_S_S_SB_1_W: //super break I won + case BOTH_LK_S_S_T_SB_1_W: //super break I won + case BOTH_LK_DL_DL_S_SB_1_W: //super break I won + case BOTH_LK_DL_DL_T_SB_1_W: //super break I won + case BOTH_LK_DL_ST_S_SB_1_W: //super break I won + case BOTH_LK_DL_ST_T_SB_1_W: //super break I won + case BOTH_LK_DL_S_S_SB_1_W: //super break I won + case BOTH_LK_DL_S_T_SB_1_W: //super break I won + case BOTH_LK_ST_DL_S_SB_1_W: //super break I won + case BOTH_LK_ST_DL_T_SB_1_W: //super break I won + case BOTH_LK_ST_ST_S_SB_1_W: //super break I won + case BOTH_LK_ST_ST_T_SB_1_W: //super break I won + case BOTH_LK_ST_S_S_SB_1_W: //super break I won + case BOTH_LK_ST_S_T_SB_1_W: //super break I won + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SaberLockBreakAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_BF1BREAK: + case BOTH_BF2BREAK: + case BOTH_CWCIRCLEBREAK: + case BOTH_CCWCIRCLEBREAK: + case BOTH_LK_S_DL_S_B_1_L: //normal break I lost + case BOTH_LK_S_DL_S_B_1_W: //normal break I won + case BOTH_LK_S_DL_T_B_1_L: //normal break I lost + case BOTH_LK_S_DL_T_B_1_W: //normal break I won + case BOTH_LK_S_ST_S_B_1_L: //normal break I lost + case BOTH_LK_S_ST_S_B_1_W: //normal break I won + case BOTH_LK_S_ST_T_B_1_L: //normal break I lost + case BOTH_LK_S_ST_T_B_1_W: //normal break I won + case BOTH_LK_S_S_S_B_1_L: //normal break I lost + case BOTH_LK_S_S_S_B_1_W: //normal break I won + case BOTH_LK_S_S_T_B_1_L: //normal break I lost + case BOTH_LK_S_S_T_B_1_W: //normal break I won + case BOTH_LK_DL_DL_S_B_1_L: //normal break I lost + case BOTH_LK_DL_DL_S_B_1_W: //normal break I won + case BOTH_LK_DL_DL_T_B_1_L: //normal break I lost + case BOTH_LK_DL_DL_T_B_1_W: //normal break I won + case BOTH_LK_DL_ST_S_B_1_L: //normal break I lost + case BOTH_LK_DL_ST_S_B_1_W: //normal break I won + case BOTH_LK_DL_ST_T_B_1_L: //normal break I lost + case BOTH_LK_DL_ST_T_B_1_W: //normal break I won + case BOTH_LK_DL_S_S_B_1_L: //normal break I lost + case BOTH_LK_DL_S_S_B_1_W: //normal break I won + case BOTH_LK_DL_S_T_B_1_L: //normal break I lost + case BOTH_LK_DL_S_T_B_1_W: //normal break I won + case BOTH_LK_ST_DL_S_B_1_L: //normal break I lost + case BOTH_LK_ST_DL_S_B_1_W: //normal break I won + case BOTH_LK_ST_DL_T_B_1_L: //normal break I lost + case BOTH_LK_ST_DL_T_B_1_W: //normal break I won + case BOTH_LK_ST_ST_S_B_1_L: //normal break I lost + case BOTH_LK_ST_ST_S_B_1_W: //normal break I won + case BOTH_LK_ST_ST_T_B_1_L: //normal break I lost + case BOTH_LK_ST_ST_T_B_1_W: //normal break I won + case BOTH_LK_ST_S_S_B_1_L: //normal break I lost + case BOTH_LK_ST_S_S_B_1_W: //normal break I won + case BOTH_LK_ST_S_T_B_1_L: //normal break I lost + case BOTH_LK_ST_S_T_B_1_W: //normal break I won + return (PM_SuperBreakLoseAnim(anim)||PM_SuperBreakWinAnim(anim)); + break; + } + return qfalse; +} + +qboolean PM_GetupAnimNoMove( int legsAnim ) +{ + switch( legsAnim ) + { + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_GETUP_CROUCH_F1: + case BOTH_GETUP_CROUCH_B1: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_FORCE_GETUP_B6: + return qtrue; + } + return qfalse; +} + +qboolean PM_KnockDownAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + /* + //special anims: + case BOTH_RELEASED: + case BOTH_LK_DL_ST_T_SB_1_L: + case BOTH_PLAYER_PA_3_FLY: + */ + return qtrue; + break; + /* + default: + return PM_InGetUp( ps ); + break; + */ + } + return qfalse; +} + +qboolean PM_KnockDownAnimExtended( int anim ) +{ + switch ( anim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + //special anims: + case BOTH_RELEASED: + case BOTH_LK_DL_ST_T_SB_1_L: + case BOTH_PLAYER_PA_3_FLY: + return qtrue; + break; + /* + default: + return PM_InGetUp( ps ); + break; + */ + } + return qfalse; +} + +qboolean PM_SaberInKata( saberMoveName_t saberMove ) +{ + switch ( saberMove ) + { + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + return qtrue; + } + return qfalse; +} + +qboolean PM_CanRollFromSoulCal( playerState_t *ps ) +{ + if ( ps->legsAnim == BOTH_A7_SOULCAL + && ps->legsAnimTimer < 700 + && ps->legsAnimTimer > 250 ) + { + return qtrue; + } + return qfalse; +} + +qboolean BG_FullBodyTauntAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_GESTURE1: + case BOTH_DUAL_TAUNT: + case BOTH_STAFF_TAUNT: + case BOTH_BOW: + case BOTH_MEDITATE: + case BOTH_SHOWOFF_FAST: + case BOTH_SHOWOFF_MEDIUM: + case BOTH_SHOWOFF_STRONG: + case BOTH_SHOWOFF_DUAL: + case BOTH_SHOWOFF_STAFF: + case BOTH_VICTORY_FAST: + case BOTH_VICTORY_MEDIUM: + case BOTH_VICTORY_STRONG: + case BOTH_VICTORY_DUAL: + case BOTH_VICTORY_STAFF: + return qtrue; + break; + } + return qfalse; +} diff --git a/code/game/bg_pmove.cpp b/code/game/bg_pmove.cpp new file mode 100644 index 0000000..5077754 --- /dev/null +++ b/code/game/bg_pmove.cpp @@ -0,0 +1,15091 @@ +// this include must remain at the top of every bg_xxxx CPP file +#include "common_headers.h" + +#include "../renderer/tr_public.h" + + +// bg_pmove.c -- both games player movement code +// takes a playerstate and a usercmd as input and returns a modifed playerstate + +// define GAME_INCLUDE so that g_public.h does not define the +// short, server-visible gclient_t and gentity_t structures, +// because we define the full size ones in this file + +#define GAME_INCLUDE +#include "q_shared.h" +#include "g_shared.h" +#include "bg_local.h" +#include "g_local.h" +#include "g_functions.h" +#include "anims.h" +#include "../cgame/cg_local.h" // yeah I know this is naughty, but we're shipping soon... + +#include "wp_saber.h" +#include "g_vehicles.h" +#include + +#ifdef _XBOX +#include "../client/fffx.h" +#endif + +extern qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force = qfalse ); +extern qboolean G_EntIsUnlockedDoor( int entityNum ); +extern qboolean G_EntIsDoor( int entityNum ); +extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); +extern qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir ); +extern int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType ); +extern qboolean PM_HasAnimation( gentity_t *ent, int animation ); +extern saberMoveName_t PM_SaberAnimTransitionMove( saberMoveName_t curmove, saberMoveName_t newmove ); +extern saberMoveName_t PM_AttackMoveForQuad( int quad ); +extern qboolean PM_SaberInTransition( int move ); +extern qboolean PM_SaberInTransitionAny( int move ); +extern qboolean PM_SaberInBounce( int move ); +extern qboolean PM_SaberInSpecialAttack( int anim ); +extern qboolean PM_SaberInAttack( int move ); +extern qboolean PM_InAnimForSaberMove( int anim, int saberMove ); +extern saberMoveName_t PM_SaberBounceForAttack( int move ); +extern saberMoveName_t PM_SaberAttackForMovement( int forwardmove, int rightmove, int curmove ); +extern saberMoveName_t PM_BrokenParryForParry( int move ); +extern saberMoveName_t PM_KnockawayForParry( int move ); +extern qboolean PM_SaberInParry( int move ); +extern qboolean PM_SaberInKnockaway( int move ); +extern qboolean PM_SaberInBrokenParry( int move ); +extern qboolean PM_SaberInReflect( int move ); +extern qboolean PM_SaberInIdle( int move ); +extern qboolean PM_SaberInStart( int move ); +extern qboolean PM_SaberInReturn( int move ); +extern qboolean PM_SaberKataDone( int curmove, int newmove ); +extern qboolean PM_SaberInSpecial( int move ); +extern qboolean PM_InDeathAnim ( void ); +extern qboolean PM_StandingAnim( int anim ); +extern qboolean PM_KickMove( int move ); +extern qboolean PM_KickingAnim( int anim ); +extern qboolean PM_InAirKickingAnim( int anim ); +extern qboolean PM_SuperBreakLoseAnim( int anim ); +extern qboolean PM_SuperBreakWinAnim( int anim ); +extern qboolean PM_InCartwheel( int anim ); +extern qboolean PM_InButterfly( int anim ); +extern qboolean PM_CanRollFromSoulCal( playerState_t *ps ); +extern saberMoveName_t PM_SaberFlipOverAttackMove( void ); +extern qboolean PM_CheckFlipOverAttackMove( qboolean checkEnemy ); +extern saberMoveName_t PM_SaberJumpForwardAttackMove( void ); +extern qboolean PM_CheckJumpForwardAttackMove( void ); +extern saberMoveName_t PM_SaberBackflipAttackMove( void ); +extern qboolean PM_CheckBackflipAttackMove( void ); +extern saberMoveName_t PM_SaberDualJumpAttackMove( void ); +extern qboolean PM_CheckDualJumpAttackMove( void ); +extern saberMoveName_t PM_SaberLungeAttackMove( qboolean fallbackToNormalLunge ); +extern qboolean PM_CheckLungeAttackMove( void ); +extern qboolean PM_InSecondaryStyle( void ); +extern qboolean PM_KnockDownAnimExtended( int anim ); +extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 ); +extern void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower ); +extern qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +extern void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +extern float G_ForceWallJumpStrength( void ); +extern int G_CheckRollSafety( gentity_t *self, int anim, float testDist ); +extern saberMoveName_t PM_CheckDualSpinProtect( void ); +extern saberMoveName_t PM_CheckPullAttack( void ); +extern qboolean JET_Flying( gentity_t *self ); +extern void JET_FlyStart( gentity_t *self ); +extern void JET_FlyStop( gentity_t *self ); +extern qboolean PM_LockedAnim( int anim ); +extern qboolean G_TryingKataAttack( gentity_t *self, usercmd_t *cmd ); +extern qboolean G_TryingCartwheel( gentity_t *self, usercmd_t *cmd ); +extern qboolean G_TryingSpecial( gentity_t *self, usercmd_t *cmd ); +extern qboolean G_TryingJumpAttack( gentity_t *self, usercmd_t *cmd ); +extern qboolean G_TryingJumpForwardAttack( gentity_t *self, usercmd_t *cmd ); +extern void WP_SaberSwingSound( gentity_t *ent, int saberNum, swingType_t swingType ); + +qboolean PM_InKnockDown( playerState_t *ps ); +qboolean PM_InKnockDownOnGround( playerState_t *ps ); +qboolean PM_InGetUp( playerState_t *ps ); +qboolean PM_InRoll( playerState_t *ps ); +qboolean PM_SpinningSaberAnim( int anim ); +qboolean PM_GettingUpFromKnockDown( float standheight, float crouchheight ); +qboolean PM_SpinningAnim( int anim ); +qboolean PM_FlippingAnim( int anim ); +qboolean PM_PainAnim( int anim ); +qboolean PM_RollingAnim( int anim ); +qboolean PM_SwimmingAnim( int anim ); +qboolean PM_InReboundJump( int anim ); +qboolean PM_ForceJumpingAnim( int anim ); +void PM_CmdForRoll( playerState_t *ps, usercmd_t *pCmd ); + +extern int parryDebounce[]; +extern qboolean cg_usingInFrontOf; +extern qboolean player_locked; +extern qboolean MatrixMode; +qboolean waterForceJump; +extern cvar_t *g_timescale; +extern cvar_t *g_speederControlScheme; +extern cvar_t *d_slowmodeath; +extern cvar_t *g_debugMelee; +extern cvar_t *g_saberNewControlScheme; +extern cvar_t *g_stepSlideFix; + +static void PM_SetWaterLevelAtPoint( vec3_t org, int *waterlevel, int *watertype ); + +#define FLY_NONE 0 +#define FLY_NORMAL 1 +#define FLY_VEHICLE 2 +#define FLY_HOVER 3 +int Flying = FLY_NONE; + +pmove_t *pm; +pml_t pml; + +// movement parameters +const float pm_stopspeed = 100.0f; +const float pm_duckScale = 0.50f; +const float pm_swimScale = 0.50f; +float pm_ladderScale = 0.7f; + +const float pm_vehicleaccelerate = 36.0f; +const float pm_accelerate = 12.0f; +const float pm_airaccelerate = 4.0f; +const float pm_wateraccelerate = 4.0f; +const float pm_flyaccelerate = 8.0f; + +const float pm_friction = 6.0f; +const float pm_waterfriction = 1.0f; +const float pm_flightfriction = 3.0f; + +const float pm_frictionModifier = 3.0f; //Used for "careful" mode (when pressing use) +const float pm_airDecelRate = 1.35f; //Used for air decelleration away from current movement velocity + +int c_pmove = 0; + +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); +//extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags); +extern void PM_TorsoAnimation( void ); +extern int PM_TorsoAnimForFrame( gentity_t *ent, int torsoFrame ); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern qboolean PM_InDeathAnim ( void ); +extern qboolean PM_InOnGroundAnim ( playerState_t *ps ); +extern weaponInfo_t cg_weapons[MAX_WEAPONS]; +extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim ); + +extern void DoImpact( gentity_t *self, gentity_t *other, qboolean damageSelf, trace_t *trace ); + +#define PHASER_RECHARGE_TIME 100 +extern saberMoveName_t transitionMove[Q_NUM_QUADS][Q_NUM_QUADS]; + +extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent ); +Vehicle_t *PM_RidingVehicle( void ) +{ + return (G_IsRidingVehicle( pm->gent )); +} + +extern qboolean G_ControlledByPlayer( gentity_t *self ); +qboolean PM_ControlledByPlayer( void ) +{ + return G_ControlledByPlayer( pm->gent ); +} + +qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ) +{ +/* + if ( 0 && ps->clientNum < MAX_CLIENTS //real client + && ps->m_iVehicleNum//in a vehicle + && pVeh //valid vehicle data pointer + && pVeh->m_pVehicleInfo//valid vehicle info + && pVeh->m_pVehicleInfo->type == VH_FIGHTER )//fighter + //FIXME: specify per vehicle instead of assuming true for all fighters + //FIXME: map/server setting? + {//can roll and pitch without limitation! + return qtrue; + }*/ + return qfalse; +} + + +/* +=============== +PM_AddEvent + +=============== +*/ +void PM_AddEvent( int newEvent ) +{ + AddEventToPlayerstate( newEvent, 0, pm->ps ); +} + +qboolean PM_PredictJumpSafe( vec3_t jumpHorizDir, float jumpHorizSpeed, float jumpVertSpeed, int predictTimeLength ) +{ + return qtrue; +} + + +void PM_GrabWallForJump( int anim ) +{//NOTE!!! assumes an appropriate anim is being passed in!!! + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_RESTART|SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + PM_AddEvent( EV_JUMP );//make sound for grab + pm->ps->pm_flags |= PMF_STUCK_TO_WALL; +} + +qboolean PM_CheckGrabWall( trace_t *trace ) +{ + if ( !pm->gent || !pm->gent->client ) + { + return qfalse; + } + if ( pm->gent->health <= 0 ) + {//must be alive + return qfalse; + } + if ( pm->gent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//must be in air + return qfalse; + } + if ( trace->plane.normal[2] != 0 ) + {//must be a flat wall + return qfalse; + } + if ( !trace->plane.normal[0] && !trace->plane.normal[1] ) + {//invalid normal + return qfalse; + } + if ( (trace->contents&(CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP)) ) + {//can't jump off of clip brushes + return qfalse; + } + if ( pm->gent->client->ps.forcePowerLevel[FP_LEVITATION] < FORCE_LEVEL_1 ) + {//must have at least FJ 1 + return qfalse; + } + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + && pm->gent->client->ps.forcePowerLevel[FP_LEVITATION] < FORCE_LEVEL_3 ) + {//player must have force jump 3 + return qfalse; + } + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + {//player + //only if we were in a longjump + if ( pm->ps->legsAnim != BOTH_FORCELONGLEAP_START + && pm->ps->legsAnim != BOTH_FORCELONGLEAP_ATTACK ) + { + return qfalse; + } + //hit a flat wall during our long jump, see if we should grab it + vec3_t moveDir; + VectorCopy( pm->ps->velocity, moveDir ); + VectorNormalize( moveDir ); + if ( DotProduct( moveDir, trace->plane.normal ) > -0.65f ) + {//not enough of a direct impact, just slide off + return qfalse; + } + if ( fabs(trace->plane.normal[2]) > MAX_WALL_GRAB_SLOPE ) + { + return qfalse; + } + //grab it! + //FIXME: stop Matrix effect! + VectorClear( pm->ps->velocity ); + //FIXME: stop slidemove! + //NOTE: we know it's forward, so... + PM_GrabWallForJump( BOTH_FORCEWALLREBOUND_FORWARD ); + return qtrue; + } + else + {//NPCs + if ( PM_InReboundJump( pm->ps->legsAnim ) ) + {//already in a rebound! + return qfalse; + } + if ( (pm->ps->eFlags&EF_FORCE_GRIPPED) ) + {//being gripped! + return qfalse; + } + /* + if ( pm->gent->painDebounceTime > level.time ) + {//can't move! + return qfalse; + } + if ( (pm->ps->pm_flags&PMF_TIME_KNOCKBACK) ) + {//being thrown back! + return qfalse; + } + */ + if ( pm->gent->NPC && (pm->gent->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + {//faling to our death! + return qfalse; + } + //FIXME: random chance, based on skill/rank? + if ( pm->ps->legsAnim != BOTH_FORCELONGLEAP_START + && pm->ps->legsAnim != BOTH_FORCELONGLEAP_ATTACK ) + {//not in a long-jump + if ( !pm->gent->enemy ) + {//no enemy + return qfalse; + } + else + {//see if the enemy is in the direction of the wall or above us + //if ( pm->gent->enemy->currentOrigin[2] < (pm->ps->origin[2]-128) ) + {//enemy is way below us + vec3_t enemyDir; + VectorSubtract( pm->gent->enemy->currentOrigin, pm->ps->origin, enemyDir ); + enemyDir[2] = 0; + VectorNormalize( enemyDir ); + if ( DotProduct( enemyDir, trace->plane.normal ) < 0.65f ) + {//jumping off this wall would not launch me in the general direction of my enemy + return qfalse; + } + } + } + } + //FIXME: check for ground close beneath us? + //FIXME: check for obstructions in the dir we're going to jump + // - including "do not enter" brushes! + //hit a flat wall during our long jump, see if we should grab it + vec3_t moveDir; + VectorCopy( pm->ps->velocity, moveDir ); + VectorNormalize( moveDir ); + if ( DotProduct( moveDir, trace->plane.normal ) > -0.65f ) + {//not enough of a direct impact, just slide off + return qfalse; + } + + //Okay, now see if jumping off this thing would send us into a do not enter brush + if ( !PM_PredictJumpSafe( trace->plane.normal, JUMP_OFF_WALL_SPEED, G_ForceWallJumpStrength(), 1500 ) ) + {//we would hit a do not enter brush, so don't grab the wall + return qfalse; + } + + //grab it! + //Pick the proper anim + int anim = BOTH_FORCEWALLREBOUND_FORWARD; + vec3_t facingAngles, wallDir, fwdDir, rtDir; + VectorSubtract( trace->endpos, pm->gent->lastOrigin, wallDir ); + wallDir[2] = 0; + VectorNormalize( wallDir ); + VectorSet( facingAngles, 0, pm->ps->viewangles[YAW], 0 ); + AngleVectors( facingAngles, fwdDir, rtDir, NULL ); + float fDot = DotProduct( fwdDir, wallDir ); + if ( fabs( fDot ) >= 0.5f ) + {//hit a wall in front/behind + if ( fDot > 0.0f ) + {//in front + anim = BOTH_FORCEWALLREBOUND_FORWARD; + } + else + {//behind + anim = BOTH_FORCEWALLREBOUND_BACK; + } + } + else if ( DotProduct( rtDir, wallDir ) > 0 ) + {//hit a wall on the right + anim = BOTH_FORCEWALLREBOUND_RIGHT; + } + else + {//hit a wall on the left + anim = BOTH_FORCEWALLREBOUND_LEFT; + } + VectorClear( pm->ps->velocity ); + //FIXME: stop slidemove! + PM_GrabWallForJump( anim ); + return qtrue; + } + //return qfalse; +} +/* +=============== +qboolean PM_ClientImpact( trace_t *trace, qboolean damageSelf ) + +=============== +*/ +qboolean PM_ClientImpact( trace_t *trace, qboolean damageSelf ) +{ + gentity_t *traceEnt; + int otherEntityNum = trace->entityNum; + + if ( !pm->gent ) + { + return qfalse; + } + + traceEnt = &g_entities[otherEntityNum]; + + if ( otherEntityNum == ENTITYNUM_WORLD + || (traceEnt->bmodel && traceEnt->s.pos.trType == TR_STATIONARY ) ) + {//hit world or a non-moving brush + if ( PM_CheckGrabWall( trace ) ) + {//stopped on the wall + return qtrue; + } + } + + if( (VectorLength( pm->ps->velocity )*(pm->gent->mass/10)) >= 100 && (pm->gent->client->NPC_class == CLASS_VEHICLE || pm->ps->lastOnGround+100material>=MAT_GLASS&&pm->gent->lastImpact+100<=level.time)) + { + DoImpact( pm->gent, &g_entities[otherEntityNum], damageSelf, trace ); + } + + if ( otherEntityNum >= ENTITYNUM_WORLD ) + { + return qfalse; + } + + if ( !traceEnt || !(traceEnt->contents&pm->tracemask) ) + {//it's dead or not in my way anymore + return qtrue; + } + + return qfalse; +} +/* +=============== +PM_AddTouchEnt +=============== +*/ +void PM_AddTouchEnt( int entityNum ) { + int i; + + if ( entityNum == ENTITYNUM_WORLD ) { + return; + } + if ( pm->numtouch == MAXTOUCH ) { + return; + } + + // see if it is already added + for ( i = 0 ; i < pm->numtouch ; i++ ) { + if ( pm->touchents[ i ] == entityNum ) { + return; + } + } + + // add it + pm->touchents[pm->numtouch] = entityNum; + pm->numtouch++; +} + + + + +/* +================== +PM_ClipVelocity + +Slide off of the impacting surface + + This will pull you down onto slopes if heading away from + them and push you up them as you go up them. + Also stops you when you hit walls. + +================== +*/ +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { + float backoff; + float change; + float oldInZ; + int i; + + if ( (pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//no sliding! + VectorCopy( in, out ); + return; + } + oldInZ = in[2]; + + backoff = DotProduct (in, normal); + + if ( backoff < 0 ) { + backoff *= overbounce; + } else { + backoff /= overbounce; + } + + for ( i=0 ; i<3 ; i++ ) + { + change = normal[i]*backoff; + /* + if ( i == 2 && Flying == FLY_HOVER && change > 0 ) + {//don't pull a hovercraft down + change = 0; + } + else + */ + { + out[i] = in[i] - change; + } + } + if ( g_stepSlideFix->integer ) + { + if ( pm->ps->clientNum < MAX_CLIENTS//normal player + && normal[2] < MIN_WALK_NORMAL )//sliding against a steep slope + { + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE )//on the ground + {//if walking on the ground, don't slide up slopes that are too steep to walk on + out[2] = oldInZ; + } + } + } +} + + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +static void PM_Friction( void ) { + vec3_t vec; + float *vel; + float speed, newspeed, control; + float drop, friction = pm->ps->friction; + + vel = pm->ps->velocity; + + VectorCopy( vel, vec ); + if ( pml.walking ) { + vec[2] = 0; // ignore slope movement + } + + speed = VectorLength(vec); + if (speed < 1) { + vel[0] = 0; + vel[1] = 0; // allow sinking underwater + // FIXME: still have z friction underwater? + return; + } + + drop = 0; + + // apply ground friction, even if on ladder + if ( pm->gent + && pm->gent->client + && pm->gent->client->NPC_class == CLASS_VEHICLE && pm->gent->m_pVehicle + && pm->gent->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL ) + { + friction = pm->gent->m_pVehicle->m_pVehicleInfo->friction; + + if ( pm->gent->m_pVehicle && pm->gent->m_pVehicle->m_pVehicleInfo->hoverHeight > 0 ) + {//in a hovering vehicle, have air control + if ( pm->gent->m_pVehicle->m_ulFlags & VEH_FLYING ) + { + friction = 0.10f; + } + } + + if ( !(pm->ps->pm_flags & PMF_TIME_KNOCKBACK) && !(pm->ps->pm_flags & PMF_TIME_NOFRICTION) ) + { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*friction*pml.frametime; + /* + if ( Flying == FLY_HOVER ) + { + if ( pm->cmd.rightmove ) + {//if turning, increase friction + control *= 2.0f; + } + if ( pm->ps->groundEntityNum < ENTITYNUM_NONE ) + {//on the ground + drop += control*friction*pml.frametime; + } + else if ( pml.groundPlane ) + {//on a slope + drop += control*friction*2.0f*pml.frametime; + } + else + {//in air + drop += control*2.0f*friction*pml.frametime; + } + } + */ + } + } + else if ( Flying != FLY_NORMAL ) + { + if ( (pm->watertype & CONTENTS_LADDER) || pm->waterlevel <= 1 ) + { + if ( (pm->watertype & CONTENTS_LADDER) || (pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK)) ) + { + // if getting knocked back, no friction + if ( !(pm->ps->pm_flags & PMF_TIME_KNOCKBACK) && !(pm->ps->pm_flags & PMF_TIME_NOFRICTION) ) + { + if ( pm->ps->legsAnim == BOTH_FORCELONGLEAP_START + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_ATTACK + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_LAND ) + {//super forward jump + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//not in air + if ( pm->cmd.forwardmove < 0 ) + {//trying to hold back some + friction *= 0.5f;//0.25f; + } + else + {//free slide + friction *= 0.2f;//0.1f; + } + pm->cmd.forwardmove = pm->cmd.rightmove = 0; + if ( pml.groundPlane && pm->ps->legsAnim == BOTH_FORCELONGLEAP_LAND ) + { + //slide effect + G_PlayEffect( "env/slide_dust", pml.groundTrace.endpos, pml.groundTrace.plane.normal ); + //FIXME: slide sound + } + } + } + /* + else if ( pm->cmd.buttons & BUTTON_USE ) + {//If the use key is pressed. slow the player more quickly + if ( pm->gent->client->NPC_class != CLASS_VEHICLE ) // if not in a vehicle... + {//in a vehicle, use key makes you turbo-boost + friction *= pm_frictionModifier; + } + } + */ + + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*friction*pml.frametime; + } + } + } + } + else if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + && pm->gent + && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_BOBAFETT || pm->gent->client->NPC_class == CLASS_ROCKETTROOPER) && pm->gent->client->moveType == MT_FLYSWIM ) + {//player as Boba + drop += speed*pm_waterfriction*pml.frametime; + } + + if ( Flying == FLY_VEHICLE ) + { + if ( !(pm->ps->pm_flags & PMF_TIME_KNOCKBACK) && !(pm->ps->pm_flags & PMF_TIME_NOFRICTION) ) + { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*friction*pml.frametime; + } + } + + // apply water friction even if just wading + if ( !waterForceJump ) + { + if ( pm->waterlevel && !(pm->watertype & CONTENTS_LADDER)) + { + drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; + } + } + + // apply flying friction + if ( pm->ps->pm_type == PM_SPECTATOR ) + { + drop += speed*pm_flightfriction*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 +============== +*/ + +static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) +{ + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct (pm->ps->velocity, wishdir); + + addspeed = wishspeed - currentspeed; + + if (addspeed <= 0) { + return; + } + accelspeed = ( accel * pml.frametime ) * wishspeed; + + if (accelspeed > addspeed) { + accelspeed = addspeed; + } + for (i=0 ; i<3 ; i++) { + pm->ps->velocity[i] += accelspeed * wishdir[i]; + } +} + +/* +============ +PM_CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +static float PM_CmdScale( usercmd_t *cmd ) +{ + int max; + float total; + float scale; + + max = abs( cmd->forwardmove ); + + if ( abs( cmd->rightmove ) > max ) { + max = abs( cmd->rightmove ); + } + if ( abs( cmd->upmove ) > max ) { + max = abs( cmd->upmove ); + } + if ( !max ) { + return 0; + } + total = sqrt( (float)(( cmd->forwardmove * cmd->forwardmove ) + + ( cmd->rightmove * cmd->rightmove ) + + ( cmd->upmove * cmd->upmove )) ); + + scale = (float) pm->ps->speed * max / ( 127.0f * total ); + + return scale; +} + + +/* +================ +PM_SetMovementDir + +Determine the rotation of the legs reletive +to the facing dir +================ +*/ +static void PM_SetMovementDir( void ) { + if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { + if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 0; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 1; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 2; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 3; + } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 4; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 5; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 6; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 7; + } + } else { + // if they aren't actively going directly sideways, + // change the animation to the diagonal so they + // don't stop too crooked + if ( pm->ps->movementDir == 2 ) { + pm->ps->movementDir = 1; + } else if ( pm->ps->movementDir == 6 ) { + pm->ps->movementDir = 7; + } + } +} + + +/* +============= +PM_CheckJump +============= +*/ +#define METROID_JUMP 1 +qboolean PM_InReboundJump( int anim ) +{ + switch ( anim ) + { + case BOTH_FORCEWALLREBOUND_FORWARD: + case BOTH_FORCEWALLREBOUND_LEFT: + case BOTH_FORCEWALLREBOUND_BACK: + case BOTH_FORCEWALLREBOUND_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_InReboundHold( int anim ) +{ + switch ( anim ) + { + case BOTH_FORCEWALLHOLD_FORWARD: + case BOTH_FORCEWALLHOLD_LEFT: + case BOTH_FORCEWALLHOLD_BACK: + case BOTH_FORCEWALLHOLD_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_InReboundRelease( int anim ) +{ + switch ( anim ) + { + case BOTH_FORCEWALLRELEASE_FORWARD: + case BOTH_FORCEWALLRELEASE_LEFT: + case BOTH_FORCEWALLRELEASE_BACK: + case BOTH_FORCEWALLRELEASE_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_InBackFlip( int anim ) +{ + switch ( anim ) + { + case BOTH_FLIP_BACK1: + case BOTH_FLIP_BACK2: + case BOTH_FLIP_BACK3: + case BOTH_ALORA_FLIP_B: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_InSpecialJump( int anim ) +{ + switch ( anim ) + { + case BOTH_WALL_RUN_RIGHT: + case BOTH_WALL_RUN_RIGHT_STOP: + case BOTH_WALL_RUN_RIGHT_FLIP: + case BOTH_WALL_RUN_LEFT: + case BOTH_WALL_RUN_LEFT_STOP: + case BOTH_WALL_RUN_LEFT_FLIP: + case BOTH_WALL_FLIP_RIGHT: + case BOTH_WALL_FLIP_LEFT: + case BOTH_WALL_FLIP_BACK1: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_FORCELEAP2_T__B_: + case BOTH_JUMPFLIPSLASHDOWN1://# + case BOTH_JUMPFLIPSTABDOWN://# + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_ARIAL_F1: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + + case BOTH_FORCELONGLEAP_START: + case BOTH_FORCELONGLEAP_ATTACK: + case BOTH_FORCEWALLRUNFLIP_START: + case BOTH_FORCEWALLRUNFLIP_END: + case BOTH_FORCEWALLRUNFLIP_ALT: + case BOTH_FLIP_ATTACK7: + case BOTH_FLIP_HOLD7: + case BOTH_FLIP_LAND: + case BOTH_A7_SOULCAL: + return qtrue; + } + if ( PM_InReboundJump( anim ) ) + { + return qtrue; + } + if ( PM_InReboundHold( anim ) ) + { + return qtrue; + } + if ( PM_InReboundRelease( anim ) ) + { + return qtrue; + } + if ( PM_InBackFlip( anim ) ) + { + return qtrue; + } + return qfalse; +} + +extern void CG_PlayerLockedWeaponSpeech( int jumping ); +qboolean PM_ForceJumpingUp( gentity_t *gent ) +{ + if ( !gent || !gent->client ) + { + return qfalse; + } + + if ( gent->NPC ) + {//this is ONLY for the player + if ( player + && player->client + && player->client->ps.viewEntity == gent->s.number ) + {//okay to jump if an NPC controlled by the player + } + else + { + return qfalse; + } + } + + if ( !(gent->client->ps.forcePowersActive&(1<client->ps.forceJumpCharge ) + {//already jumped and let go + return qfalse; + } + + if ( PM_InSpecialJump( gent->client->ps.legsAnim ) ) + { + return qfalse; + } + + if ( PM_InKnockDown( &gent->client->ps ) ) + { + return qfalse; + } + + if ( (gent->s.numberclient->ps.groundEntityNum == ENTITYNUM_NONE //in air + && ( (gent->client->ps.pm_flags&PMF_JUMPING) && gent->client->ps.velocity[2] > 0 )//jumped & going up or at water surface///*(gent->client->ps.waterHeightLevel==WHL_SHOULDERS&&gent->client->usercmd.upmove>0) ||*/ + && gent->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 //force-jump capable + && !(gent->client->ps.pm_flags&PMF_TRIGGER_PUSHED) )//not pushed by a trigger + { + if( gent->flags & FL_LOCK_PLAYER_WEAPONS ) // yes this locked weapons check also includes force powers, if we need a separate check later I'll make one + { + CG_PlayerLockedWeaponSpeech( qtrue ); + return qfalse; + } + return qtrue; + } + return qfalse; +} + +static void PM_JumpForDir( void ) +{ + int anim = BOTH_JUMP1; + if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_JUMP1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_JUMPBACK1; + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_JUMPRIGHT1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_JUMPLEFT1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + anim = BOTH_JUMP1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + if(!PM_InDeathAnim()) + { + PM_SetAnim(pm,SETANIM_LEGS,anim,SETANIM_FLAG_OVERRIDE, 100); // Only blend over 100ms + } +} + +qboolean PM_GentCantJump( gentity_t *gent ) +{//FIXME: ugh, hacky, set a flag on NPC or something, please... + if ( gent && gent->client && + ( gent->client->NPC_class == CLASS_ATST || + gent->client->NPC_class == CLASS_GONK || + gent->client->NPC_class == CLASS_MARK1 || + gent->client->NPC_class == CLASS_MARK2 || + gent->client->NPC_class == CLASS_MOUSE || + gent->client->NPC_class == CLASS_PROBE || + gent->client->NPC_class == CLASS_PROTOCOL || + gent->client->NPC_class == CLASS_R2D2 || + gent->client->NPC_class == CLASS_R5D2 || + gent->client->NPC_class == CLASS_SEEKER || + gent->client->NPC_class == CLASS_REMOTE || + gent->client->NPC_class == CLASS_SENTRY ) ) + { + return qtrue; + } + return qfalse; +} + +static qboolean PM_CheckJump( void ) +{ + //Don't allow jump until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return qfalse; + } + + if ( PM_InKnockDown( pm->ps ) || PM_InRoll( pm->ps ) ) + {//in knockdown + return qfalse; + } + + if ( PM_GentCantJump( pm->gent ) ) + { + return qfalse; + } + + if ( PM_KickingAnim( pm->ps->legsAnim ) && !PM_InAirKickingAnim( pm->ps->legsAnim ) ) + {//can't jump when in a kicking anim + return qfalse; + } + /* + if ( pm->cmd.buttons & BUTTON_FORCEJUMP ) + { + pm->ps->pm_flags |= PMF_JUMP_HELD; + } + */ + +#if METROID_JUMP + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + && pm->gent && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_BOBAFETT || pm->gent->client->NPC_class == CLASS_ROCKETTROOPER) ) + {//player playing as boba fett + if ( pm->cmd.upmove > 0 ) + {//turn on/go up + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE && !(pm->ps->pm_flags&PMF_JUMP_HELD) ) + {//double-tap - must activate while in air + if ( !JET_Flying( pm->gent ) ) + { + JET_FlyStart( pm->gent ); + } + } + } + else if ( pm->cmd.upmove < 0 ) + {//turn it off (or should we just go down)? + /* + if ( JET_Flying( pm->gent ) ) + { + JET_FlyStop( pm->gent ); + } + */ + } + } + else if ( pm->waterlevel < 3 )//|| (pm->ps->waterHeightLevel==WHL_SHOULDERS&&pm->cmd.upmove>0) ) + { + if ( pm->ps->gravity > 0 ) + {//can't do this in zero-G + if ( pm->ps->legsAnim == BOTH_FORCELONGLEAP_START + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_ATTACK + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_LAND ) + {//in the middle of a force long-jump + /* + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE + && pm->cmd.upmove > 0 + && pm->cmd.forwardmove > 0 ) + */ + if ( (pm->ps->legsAnim == BOTH_FORCELONGLEAP_START || pm->ps->legsAnim == BOTH_FORCELONGLEAP_ATTACK) + && pm->ps->legsAnimTimer > 0 ) + {//in the air + //FIXME: need an actual set time so it doesn't matter when the attack happens + //FIXME: make sure we don't jump further than force jump 3 allows + vec3_t jFwdAngs, jFwdVec; + VectorSet( jFwdAngs, 0, pm->ps->viewangles[YAW], 0 ); + AngleVectors( jFwdAngs, jFwdVec, NULL, NULL ); + float oldZVel = pm->ps->velocity[2]; + if ( pm->ps->legsAnimTimer > 150 && oldZVel < 0 ) + { + oldZVel = 0; + } + VectorScale( jFwdVec, FORCE_LONG_LEAP_SPEED, pm->ps->velocity ); + pm->ps->velocity[2] = oldZVel; + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + pm->ps->forcePowersActive |= (1<ps->legsAnim == BOTH_FORCELONGLEAP_START + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_ATTACK ) + {//still in start anim, but it's run out + pm->ps->forcePowersActive |= (1<ps->groundEntityNum == ENTITYNUM_NONE ) + {//still in air? + //hold it for another 50ms + //PM_SetAnim( pm, SETANIM_BOTH, BOTH_FORCELONGLEAP_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + else + {//in land-slide anim + //FIXME: force some forward movement? Less if holding back? + } + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE//still in air + && pm->ps->origin[2] < pm->ps->jumpZStart )//dropped below original jump start + {//slow down + pm->ps->velocity[0] *= 0.75f; + pm->ps->velocity[1] *= 0.75f; + if ( (pm->ps->velocity[0]+pm->ps->velocity[1])*0.5f<=10.0f ) + {//falling straight down + PM_SetAnim( pm, SETANIM_BOTH, BOTH_FORCEINAIR1, SETANIM_FLAG_OVERRIDE ); + } + } + return qfalse; + } + } + else if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) //player-only for now + && pm->cmd.upmove > 0 //trying to jump + && pm->ps->forcePowerLevel[FP_LEVITATION] >= FORCE_LEVEL_3 //force jump 3 or better + && pm->ps->forcePower >= FORCE_LONGJUMP_POWER //this costs 20 force to do + && (pm->ps->forcePowersActive&(1<cmd.forwardmove > 0 //pushing forward + && !pm->cmd.rightmove //not strafing + && pm->ps->groundEntityNum != ENTITYNUM_NONE//not in mid-air + && !(pm->ps->pm_flags&PMF_JUMP_HELD) + //&& (float)(level.time-pm->ps->lastStationary) >= (3000.0f*g_timescale->value)//have to have a 3 second running start - relative to force speed slowdown + && (level.time-pm->ps->forcePowerDebounce[FP_SPEED]) <= 250//have to have just started the force speed within the last half second + && pm->gent ) + {//start a force long-jump! + vec3_t jFwdAngs, jFwdVec; + //BOTH_FORCELONGLEAP_ATTACK if holding attack, too? + PM_SetAnim( pm, SETANIM_BOTH, BOTH_FORCELONGLEAP_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + VectorSet( jFwdAngs, 0, pm->ps->viewangles[YAW], 0 ); + AngleVectors( jFwdAngs, jFwdVec, NULL, NULL ); + VectorScale( jFwdVec, FORCE_LONG_LEAP_SPEED, pm->ps->velocity ); + pm->ps->velocity[2] = 320; + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps->jumpZStart = pm->ps->origin[2]; + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + //start force jump + pm->ps->forcePowersActive |= (1<cmd.upmove = 0; + // keep track of force jump stat + if(pm->ps->clientNum == 0) + { + if( pm->gent && pm->gent->client ) + { + pm->gent->client->sess.missionStats.forceUsed[(int)FP_LEVITATION]++; + } + } + G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + WP_ForcePowerStop( pm->gent, FP_SPEED ); + WP_ForcePowerDrain( pm->gent, FP_LEVITATION, FORCE_LONGJUMP_POWER );//drain the required force power + G_StartMatrixEffect( pm->gent, 0, pm->ps->legsAnimTimer+500 ); + return qtrue; + } + else if ( PM_InCartwheel( pm->ps->legsAnim ) + || PM_InButterfly( pm->ps->legsAnim ) ) + {//can't keep jumping up in cartwheels, ariels and butterflies + } + //FIXME: still able to pogo-jump... + else if ( PM_ForceJumpingUp( pm->gent ) && (pm->ps->pm_flags&PMF_JUMP_HELD) )//||pm->ps->waterHeightLevel==WHL_SHOULDERS) ) + {//force jumping && holding jump + /* + if ( !pm->ps->forceJumpZStart && (pm->ps->waterHeightLevel==WHL_SHOULDERS&&pm->cmd.upmove>0) ) + { + pm->ps->forceJumpZStart = pm->ps->origin[2]; + } + */ + float curHeight = pm->ps->origin[2] - pm->ps->forceJumpZStart; + //check for max force jump level and cap off & cut z vel + if ( ( curHeight<=forceJumpHeight[0] ||//still below minimum jump height + (pm->ps->forcePower&&pm->cmd.upmove>=10) ) &&////still have force power available and still trying to jump up + curHeight < forceJumpHeight[pm->ps->forcePowerLevel[FP_LEVITATION]] )//still below maximum jump height + {//can still go up + //FIXME: after a certain amount of time of held jump, play force jump sound and flip if a dir is being held + //FIXME: if hit a wall... should we cut velocity or allow them to slide up it? + //FIXME: constantly drain force power at a rate by which the usage for maximum height would use up the full cost of force jump + if ( curHeight > forceJumpHeight[0] ) + {//passed normal jump height *2? + if ( !(pm->ps->forcePowersActive&(1<ps->forcePowersActive |= (1<gent ) + { + G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + // keep track of force jump stat + if(pm->ps->clientNum == 0 && pm->gent->client) + { + pm->gent->client->sess.missionStats.forceUsed[(int)FP_LEVITATION]++; + } + } + //play flip + //FIXME: do this only when they stop the jump (below) or when they're just about to hit the peak of the jump + if ( PM_InAirKickingAnim( pm->ps->legsAnim ) + && pm->ps->legsAnimTimer ) + {//still in kick + } + else if ((pm->cmd.forwardmove || pm->cmd.rightmove) && //pushing in a dir + //pm->ps->legsAnim != BOTH_ARIAL_F1 &&//not already flipping + pm->ps->legsAnim != BOTH_FLIP_F && + pm->ps->legsAnim != BOTH_FLIP_B && + pm->ps->legsAnim != BOTH_FLIP_R && + pm->ps->legsAnim != BOTH_FLIP_L && + pm->ps->legsAnim != BOTH_ALORA_FLIP_1 && + pm->ps->legsAnim != BOTH_ALORA_FLIP_2 && + pm->ps->legsAnim != BOTH_ALORA_FLIP_3 + && cg.renderingThirdPerson//third person only + && !cg.zoomMode )//not zoomed in + {//FIXME: this could end up playing twice if the jump is very long... + int anim = BOTH_FORCEINAIR1; + int parts = SETANIM_BOTH; + + if ( pm->cmd.forwardmove > 0 ) + { + /* + if ( pm->ps->forcePowerLevel[FP_LEVITATION] < FORCE_LEVEL_2 ) + { + anim = BOTH_ARIAL_F1; + } + else + */ + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_ALORA && Q_irand( 0, 3 ) ) + { + anim = Q_irand( BOTH_ALORA_FLIP_1, BOTH_ALORA_FLIP_3 ); + } + else + { + anim = BOTH_FLIP_F; + } + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_FLIP_B; + } + else if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_FLIP_R; + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_FLIP_L; + } + if ( pm->ps->weaponTime ) + {//FIXME: really only care if we're in a saber attack anim... + parts = SETANIM_LEGS; + } + + PM_SetAnim( pm, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else if ( pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + {//FIXME: really want to know how far off ground we are, probably... + vec3_t facingFwd, facingRight, facingAngles = {0, pm->ps->viewangles[YAW], 0}; + int anim = -1; + AngleVectors( facingAngles, facingFwd, facingRight, NULL ); + float dotR = DotProduct( facingRight, pm->ps->velocity ); + float dotF = DotProduct( facingFwd, pm->ps->velocity ); + if ( fabs(dotR) > fabs(dotF) * 1.5 ) + { + if ( dotR > 150 ) + { + anim = BOTH_FORCEJUMPRIGHT1; + } + else if ( dotR < -150 ) + { + anim = BOTH_FORCEJUMPLEFT1; + } + } + else + { + if ( dotF > 150 ) + { + anim = BOTH_FORCEJUMP1; + } + else if ( dotF < -150 ) + { + anim = BOTH_FORCEJUMPBACK1; + } + } + if ( anim != -1 ) + { + int parts = SETANIM_BOTH; + if ( pm->ps->weaponTime ) + {//FIXME: really only care if we're in a saber attack anim... + parts = SETANIM_LEGS; + } + + PM_SetAnim( pm, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + else + { + if ( !pm->ps->legsAnimTimer ) + {//not in the middle of a legsAnim + int anim = pm->ps->legsAnim; + int newAnim = -1; + switch ( anim ) + { + case BOTH_FORCEJUMP1: + newAnim = BOTH_FORCELAND1;//BOTH_FORCEINAIR1; + break; + case BOTH_FORCEJUMPBACK1: + newAnim = BOTH_FORCELANDBACK1;//BOTH_FORCEINAIRBACK1; + break; + case BOTH_FORCEJUMPLEFT1: + newAnim = BOTH_FORCELANDLEFT1;//BOTH_FORCEINAIRLEFT1; + break; + case BOTH_FORCEJUMPRIGHT1: + newAnim = BOTH_FORCELANDRIGHT1;//BOTH_FORCEINAIRRIGHT1; + break; + } + if ( newAnim != -1 ) + { + int parts = SETANIM_BOTH; + if ( pm->ps->weaponTime ) + {//FIXME: really only care if we're in a saber attack anim... + parts = SETANIM_LEGS; + } + + PM_SetAnim( pm, parts, newAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + } + + //need to scale this down, start with height velocity (based on max force jump height) and scale down to regular jump vel + pm->ps->velocity[2] = (forceJumpHeight[pm->ps->forcePowerLevel[FP_LEVITATION]]-curHeight)/forceJumpHeight[pm->ps->forcePowerLevel[FP_LEVITATION]]*forceJumpStrength[pm->ps->forcePowerLevel[FP_LEVITATION]];//JUMP_VELOCITY; + pm->ps->velocity[2] /= 10; + pm->ps->velocity[2] += JUMP_VELOCITY; + pm->ps->pm_flags |= PMF_JUMP_HELD; + } + else if ( curHeight > forceJumpHeight[0] && curHeight < forceJumpHeight[pm->ps->forcePowerLevel[FP_LEVITATION]] - forceJumpHeight[0] ) + {//still have some headroom, don't totally stop it + if ( pm->ps->velocity[2] > JUMP_VELOCITY ) + { + pm->ps->velocity[2] = JUMP_VELOCITY; + } + } + else + { + pm->ps->velocity[2] = 0; + } + pm->cmd.upmove = 0; + return qfalse; + } + } + } + +#endif + + //Not jumping + if ( pm->cmd.upmove < 10 ) { + return qfalse; + } + + // must wait for jump to be released + if ( pm->ps->pm_flags & PMF_JUMP_HELD ) + { + // clear upmove so cmdscale doesn't lower running speed + pm->cmd.upmove = 0; + return qfalse; + } + + if ( pm->ps->gravity <= 0 ) + {//in low grav, you push in the dir you're facing as long as there is something behind you to shove off of + vec3_t forward, back; + trace_t trace; + + AngleVectors( pm->ps->viewangles, forward, NULL, NULL ); + VectorMA( pm->ps->origin, -8, forward, back ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, back, pm->ps->clientNum, pm->tracemask&~(CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ); + + pm->cmd.upmove = 0; + + if ( trace.fraction < 1.0f ) + { + VectorMA( pm->ps->velocity, JUMP_VELOCITY/2, forward, pm->ps->velocity ); + //FIXME: kicking off wall anim? At least check what anim we're in? + PM_SetAnim(pm,SETANIM_LEGS,BOTH_FORCEJUMP1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART); + } + else + {//else no surf close enough to push off of + return qfalse; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//need to set some things and return + //Jumping + pm->ps->forceJumpZStart = 0; + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->pm_flags |= (PMF_JUMPING|PMF_JUMP_HELD); + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps->jumpZStart = pm->ps->origin[2]; + + if ( pm->gent ) + { + if ( !Q3_TaskIDPending( pm->gent, TID_CHAN_VOICE ) ) + { + PM_AddEvent( EV_JUMP ); + } + } + else + { + PM_AddEvent( EV_JUMP ); + } + + return qtrue; + }//else no surf close enough to push off of + } + else if ( pm->cmd.upmove > 0 //want to jump + && pm->waterlevel < 2 //not in water above ankles + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 //have force jump ability + && !(pm->ps->pm_flags&PMF_JUMP_HELD)//not holding jump from a previous jump + //&& !PM_InKnockDown( pm->ps )//not in a knockdown + && pm->ps->forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->gent && WP_ForcePowerAvailable( pm->gent, FP_LEVITATION, 0 ) //have enough force power to jump + && ((pm->ps->clientNum&&!PM_ControlledByPlayer())||((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && cg.renderingThirdPerson && !cg.zoomMode && !(pm->gent->flags&FL_LOCK_PLAYER_WEAPONS) )) )// yes this locked weapons check also includes force powers, if we need a separate check later I'll make one + { + if ( pm->gent->NPC && pm->gent->NPC->rank != RANK_CREWMAN && pm->gent->NPC->rank <= RANK_LT_JG ) + {//reborn who are not acrobats can't do any of these acrobatics + //FIXME: extern these abilities in the .npc file! + } + else if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//on the ground + //check for left-wall and right-wall special jumps + int anim = -1; + float vertPush = 0; + int forcePowerCostOverride = 0; + + // Cartwheels/ariels/butterflies + if ( (pm->ps->weapon==WP_SABER&&G_TryingCartwheel(pm->gent,&pm->cmd)/*(pm->cmd.buttons&BUTTON_FORCE_FOCUS)*/&&(pm->cmd.buttons&BUTTON_ATTACK))//using saber and holding focus + attack +// ||(pm->ps->weapon!=WP_SABER&&((pm->cmd.buttons&BUTTON_ATTACK)||(pm->cmd.buttons&BUTTON_ALT_ATTACK)) ) )//using any other weapon and hitting either attack button + && (((pm->ps->clientNum>=MAX_CLIENTS&&!PM_ControlledByPlayer())&&pm->cmd.upmove > 0&& pm->ps->velocity[2] >= 0 )//jumping NPC, going up already + ||((pm->ps->clientNumgent,&pm->cmd)/*(pm->cmd.buttons&BUTTON_FORCE_FOCUS)*/))//focus-holding player + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER_LR )/*pm->ps->forcePower >= SABER_ALT_ATTACK_POWER_LR*/ )// have enough power + {//holding attack and jumping + if ( pm->cmd.rightmove > 0 ) + { + // If they're using the staff we do different anims. + if ( pm->ps->saberAnimLevel == SS_STAFF + && pm->ps->weapon == WP_SABER ) + { + if ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) + || pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_2 ) + { + anim = BOTH_BUTTERFLY_RIGHT; + forcePowerCostOverride = G_CostForSpecialMove( SABER_ALT_ATTACK_POWER_LR ); + } + } + else if ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) + || pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + { + if ( pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer() ) + {//player: since we're on the ground, always do a cartwheel + /* + anim = BOTH_CARTWHEEL_RIGHT; + forcePowerCostOverride = G_CostForSpecialMove( SABER_ALT_ATTACK_POWER_LR ); + */ + } + else + { + vertPush = JUMP_VELOCITY; + if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_ARIAL_RIGHT; + forcePowerCostOverride = G_CostForSpecialMove( SABER_ALT_ATTACK_POWER_LR ); + } + else + { + anim = BOTH_CARTWHEEL_RIGHT; + forcePowerCostOverride = G_CostForSpecialMove( SABER_ALT_ATTACK_POWER_LR ); + } + } + } + } + else if ( pm->cmd.rightmove < 0 ) + { + // If they're using the staff we do different anims. + if ( pm->ps->saberAnimLevel == SS_STAFF + && pm->ps->weapon == WP_SABER ) + { + if ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) + || pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_2 ) + { + anim = BOTH_BUTTERFLY_LEFT; + forcePowerCostOverride = G_CostForSpecialMove( SABER_ALT_ATTACK_POWER_LR ); + } + } + else if ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) + || pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + { + if ( pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer() ) + {//player: since we're on the ground, always do a cartwheel + /* + anim = BOTH_CARTWHEEL_LEFT; + forcePowerCostOverride = G_CostForSpecialMove( SABER_ALT_ATTACK_POWER_LR ); + */ + } + else + { + vertPush = JUMP_VELOCITY; + if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_ARIAL_LEFT; + forcePowerCostOverride = G_CostForSpecialMove( SABER_ALT_ATTACK_POWER_LR ); + } + else + { + anim = BOTH_CARTWHEEL_LEFT; + forcePowerCostOverride = G_CostForSpecialMove( SABER_ALT_ATTACK_POWER_LR ); + } + } + } + } + } + else if ( pm->cmd.rightmove > 0 && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + {//strafing right + if ( pm->cmd.forwardmove > 0 ) + {//wall-run + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.0f; + anim = BOTH_WALL_RUN_RIGHT; + } + else if ( pm->cmd.forwardmove == 0 ) + {//wall-flip + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + anim = BOTH_WALL_FLIP_RIGHT; + } + } + else if ( pm->cmd.rightmove < 0 && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + {//strafing left + if ( pm->cmd.forwardmove > 0 ) + {//wall-run + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.0f; + anim = BOTH_WALL_RUN_LEFT; + } + else if ( pm->cmd.forwardmove == 0 ) + {//wall-flip + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + anim = BOTH_WALL_FLIP_LEFT; + } + } + else if ( /*pm->ps->clientNum >= MAX_CLIENTS//not the player + && !PM_ControlledByPlayer() //not controlled by player + &&*/ pm->cmd.forwardmove > 0 //pushing forward + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 )//have jump 2 or higher + {//step off wall, flip backwards + if ( VectorLengthSquared( pm->ps->velocity ) > 40000 /*200*200*/) + {//have to be moving... FIXME: make sure it's opposite the wall... or at least forward? + if ( pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_2 ) + {//run all the way up wwall + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.0f; + anim = BOTH_FORCEWALLRUNFLIP_START; + } + else + {//run just a couple steps up + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + anim = BOTH_WALL_FLIP_BACK1; + } + } + } + else if ( pm->cmd.forwardmove < 0 //pushing back + //&& pm->ps->clientNum//not the player + && !(pm->cmd.buttons&BUTTON_ATTACK) )//not attacking + {//back-jump does backflip... FIXME: always?! What about just doing a normal jump backwards? + if ( pm->ps->velocity[2] >= 0 ) + {//must be going up already + vertPush = JUMP_VELOCITY; + if ( pm->gent->client && pm->gent->client->NPC_class == CLASS_ALORA && !Q_irand( 0, 2 ) ) + { + anim = BOTH_ALORA_FLIP_B; + } + else + { + anim = PM_PickAnim( pm->gent, BOTH_FLIP_BACK1, BOTH_FLIP_BACK3 ); + } + } + } + else if ( VectorLengthSquared( pm->ps->velocity ) < 256 /*16 squared*/) + {//not moving + if ( pm->ps->weapon == WP_SABER && (pm->cmd.buttons & BUTTON_ATTACK) && pm->ps->saberAnimLevel == SS_MEDIUM ) + { + /* + //Only tavion does these now + if ( pm->ps->clientNum && Q_irand( 0, 1 ) ) + {//butterfly... FIXME: does direction matter? + vertPush = JUMP_VELOCITY; + if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_BUTTERFLY_LEFT; + } + else + { + anim = BOTH_BUTTERFLY_RIGHT; + } + } + else + */if ( pm->ps->clientNum >= MAX_CLIENTS && !PM_ControlledByPlayer() )//NOTE: pretty much useless, so player never does these + {//jump-spin FIXME: does direction matter? + vertPush = forceJumpStrength[FORCE_LEVEL_2]/1.5f; + if ( pm->gent->client && pm->gent->client->NPC_class == CLASS_ALORA ) + { + anim = BOTH_ALORA_SPIN; + } + else + { + anim = Q_irand( BOTH_FJSS_TR_BL, BOTH_FJSS_TL_BR ); + } + } + } + } + + if ( anim != -1 && PM_HasAnimation( pm->gent, anim ) ) + { + vec3_t fwd, right, traceto, mins = {pm->mins[0],pm->mins[1],0}, maxs = {pm->maxs[0],pm->maxs[1],24}, fwdAngles = {0, pm->ps->viewangles[YAW], 0}; + trace_t trace; + qboolean doTrace = qfalse; + int contents = CONTENTS_SOLID; + + AngleVectors( fwdAngles, fwd, right, NULL ); + + //trace-check for a wall, if necc. + switch ( anim ) + { + case BOTH_WALL_FLIP_LEFT: + if ( g_debugMelee->integer ) + { + contents |= CONTENTS_BODY; + } + //NOTE: purposely falls through to next case! + case BOTH_WALL_RUN_LEFT: + doTrace = qtrue; + VectorMA( pm->ps->origin, -16, right, traceto ); + break; + + case BOTH_WALL_FLIP_RIGHT: + if ( g_debugMelee->integer ) + { + contents |= CONTENTS_BODY; + } + //NOTE: purposely falls through to next case! + case BOTH_WALL_RUN_RIGHT: + doTrace = qtrue; + VectorMA( pm->ps->origin, 16, right, traceto ); + break; + + case BOTH_WALL_FLIP_BACK1: + if ( g_debugMelee->integer ) + { + contents |= CONTENTS_BODY; + } + doTrace = qtrue; + VectorMA( pm->ps->origin, 32, fwd, traceto );//was 16 + break; + + case BOTH_FORCEWALLRUNFLIP_START: + if ( g_debugMelee->integer ) + { + contents |= CONTENTS_BODY; + } + doTrace = qtrue; + VectorMA( pm->ps->origin, 32, fwd, traceto );//was 16 + break; + } + + vec3_t idealNormal={0}, wallNormal={0}; + if ( doTrace ) + { + //FIXME: all these jump ones should check for head clearance + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, contents ); + VectorCopy( trace.plane.normal, wallNormal ); + VectorNormalize( wallNormal ); + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + if ( anim == BOTH_WALL_FLIP_LEFT ) + {//sigh.. check for bottomless pit to the right + trace_t trace2; + vec3_t start; + VectorMA( pm->ps->origin, 128, right, traceto ); + pm->trace( &trace2, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, contents ); + if ( !trace2.allsolid && !trace2.startsolid ) + { + VectorCopy( trace2.endpos, traceto ); + VectorCopy( traceto, start ); + traceto[2] -= 384; + pm->trace( &trace2, start, mins, maxs, traceto, pm->ps->clientNum, contents ); + if ( !trace2.allsolid && !trace2.startsolid && trace2.fraction >= 1.0f ) + {//bottomless pit! + trace.fraction = 1.0f;//way to stop it from doing the side-flip + } + } + } + else if ( anim == BOTH_WALL_FLIP_RIGHT ) + {//sigh.. check for bottomless pit to the left + trace_t trace2; + vec3_t start; + VectorMA( pm->ps->origin, -128, right, traceto ); + pm->trace( &trace2, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, contents ); + if ( !trace2.allsolid && !trace2.startsolid ) + { + VectorCopy( trace2.endpos, traceto ); + VectorCopy( traceto, start ); + traceto[2] -= 384; + pm->trace( &trace2, start, mins, maxs, traceto, pm->ps->clientNum, contents ); + if ( !trace2.allsolid && !trace2.startsolid && trace2.fraction >= 1.0f ) + {//bottomless pit! + trace.fraction = 1.0f;//way to stop it from doing the side-flip + } + } + } + else + { + if ( anim == BOTH_WALL_FLIP_BACK1 + || anim == BOTH_FORCEWALLRUNFLIP_START ) + {//trace up and forward a little to make sure the wall it at least 64 tall + if ( (contents&CONTENTS_BODY)//included entitied + && (trace.contents&CONTENTS_BODY) //hit an entity + && g_entities[trace.entityNum].client )//hit a client + {//no need to trace up, it's all good... + if ( PM_InOnGroundAnim( &g_entities[trace.entityNum].client->ps ) )//on the ground, no jump + {//can't jump off guys on ground + trace.fraction = 1.0f;//way to stop if from doing the jump + } + else if ( anim == BOTH_FORCEWALLRUNFLIP_START ) + {//instead of wall-running up, do the backflip + anim = BOTH_WALL_FLIP_BACK1; + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + } + } + else if ( anim == BOTH_WALL_FLIP_BACK1 ) + { + trace_t trace2; + vec3_t start; + VectorCopy( pm->ps->origin, start ); + start[2] += 64; + VectorMA( start, 32, fwd, traceto ); + pm->trace( &trace2, start, mins, maxs, traceto, pm->ps->clientNum, contents ); + if ( trace2.allsolid + || trace2.startsolid + || trace2.fraction >= 1.0f ) + {//no room above or no wall in front at that height + trace.fraction = 1.0f;//way to stop if from doing the jump + } + } + } + if ( trace.fraction < 1.0f ) + {//still valid to jump + if ( anim == BOTH_WALL_FLIP_BACK1 ) + {//sigh.. check for bottomless pit to the rear + trace_t trace2; + vec3_t start; + VectorMA( pm->ps->origin, -128, fwd, traceto ); + pm->trace( &trace2, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, contents ); + if ( !trace2.allsolid && !trace2.startsolid ) + { + VectorCopy( trace2.endpos, traceto ); + VectorCopy( traceto, start ); + traceto[2] -= 384; + pm->trace( &trace2, start, mins, maxs, traceto, pm->ps->clientNum, contents ); + if ( !trace2.allsolid && !trace2.startsolid && trace2.fraction >= 1.0f ) + {//bottomless pit! + trace.fraction = 1.0f;//way to stop it from doing the side-flip + } + } + } + } + } + } + gentity_t *traceEnt = &g_entities[trace.entityNum]; + + if ( !doTrace || (trace.fraction < 1.0f&&((trace.entityNums.solid!=SOLID_BMODEL)||DotProduct(wallNormal,idealNormal)>0.7)) ) + {//there is a wall there + + if ( (anim != BOTH_WALL_RUN_LEFT + && anim != BOTH_WALL_RUN_RIGHT + && anim != BOTH_FORCEWALLRUNFLIP_START) + || (wallNormal[2] >= 0.0f && wallNormal[2] <= MAX_WALL_RUN_Z_NORMAL) ) + {//wall-runs can only run on relatively flat walls, sorry. + if ( anim == BOTH_ARIAL_LEFT || anim == BOTH_CARTWHEEL_LEFT ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -185, right, pm->ps->velocity ); + } + else if ( anim == BOTH_ARIAL_RIGHT || anim == BOTH_CARTWHEEL_RIGHT ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, 185, right, pm->ps->velocity ); + } + else if ( anim == BOTH_BUTTERFLY_LEFT ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -190, right, pm->ps->velocity ); + } + else if ( anim == BOTH_BUTTERFLY_RIGHT ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, 190, right, pm->ps->velocity ); + } + //move me to side + else if ( anim == BOTH_WALL_FLIP_LEFT ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, 150, right, pm->ps->velocity ); + } + else if ( anim == BOTH_WALL_FLIP_RIGHT ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -150, right, pm->ps->velocity ); + } + else if ( anim == BOTH_FLIP_BACK1 + || anim == BOTH_FLIP_BACK2 + || anim == BOTH_FLIP_BACK3 + || anim == BOTH_ALORA_FLIP_B + || anim == BOTH_WALL_FLIP_BACK1 ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity ); + } + //kick if jumping off an ent + if ( doTrace + && anim != BOTH_WALL_RUN_LEFT + && anim != BOTH_WALL_RUN_RIGHT + && anim != BOTH_FORCEWALLRUNFLIP_START) + { + if ( pm->gent && trace.entityNum < ENTITYNUM_WORLD ) + { + if ( traceEnt + && traceEnt->client + && traceEnt->health > 0 + && traceEnt->takedamage + && traceEnt->client->NPC_class != CLASS_GALAKMECH + && traceEnt->client->NPC_class != CLASS_DESANN + && !(traceEnt->flags&FL_NO_KNOCKBACK) ) + {//push them away and do pain + vec3_t oppDir, fxDir; + float strength = VectorNormalize2( pm->ps->velocity, oppDir ); + VectorScale( oppDir, -1, oppDir ); + //FIXME: need knockdown anim + G_Damage( traceEnt, pm->gent, pm->gent, oppDir, traceEnt->currentOrigin, 10, DAMAGE_NO_ARMOR|DAMAGE_NO_HIT_LOC|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + VectorCopy( fwd, fxDir ); + VectorScale( fxDir, -1, fxDir ); + G_PlayEffect( G_EffectIndex( "melee/kick_impact" ), trace.endpos, fxDir ); + //G_Sound( traceEnt, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) ); + if ( traceEnt->health > 0 ) + {//didn't kill him + if ( (traceEnt->s.number==0&&!Q_irand(0,g_spskill->integer)) + || (traceEnt->NPC!=NULL&&Q_irand(RANK_CIVILIAN,traceEnt->NPC->rank)+Q_irand(-2,2)ps->velocity[2] = vertPush; + } + //animate me + if ( anim == BOTH_BUTTERFLY_RIGHT ) + { + PM_SetSaberMove( LS_BUTTERFLY_RIGHT ); + } + else if ( anim == BOTH_BUTTERFLY_LEFT ) + { + PM_SetSaberMove( LS_BUTTERFLY_LEFT ); + } + else + {//not a proper saberMove, so do set all the details manually + int parts = SETANIM_LEGS; + if ( /*anim == BOTH_BUTTERFLY_LEFT || + anim == BOTH_BUTTERFLY_RIGHT ||*/ + anim == BOTH_FJSS_TR_BL || + anim == BOTH_FJSS_TL_BR ) + { + parts = SETANIM_BOTH; + pm->cmd.buttons&=~BUTTON_ATTACK; + pm->ps->saberMove = LS_NONE; + pm->gent->client->ps.SaberActivateTrail( 300 ); + } + else if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + PM_SetAnim( pm, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + if ( /*anim == BOTH_BUTTERFLY_LEFT + || anim == BOTH_BUTTERFLY_RIGHT + ||*/ anim == BOTH_FJSS_TR_BL + || anim == BOTH_FJSS_TL_BR + || anim == BOTH_FORCEWALLRUNFLIP_START ) + { + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + else if ( anim == BOTH_WALL_FLIP_LEFT + || anim == BOTH_WALL_FLIP_RIGHT + || anim == BOTH_WALL_FLIP_BACK1 ) + {//let us do some more moves after this + pm->ps->saberAttackChainCount = 0; + } + } + pm->ps->forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + pm->ps->pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL); + pm->cmd.upmove = 0; + G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + WP_ForcePowerDrain( pm->gent, FP_LEVITATION, forcePowerCostOverride ); + } + } + } + } + else + {//in the air + int legsAnim = pm->ps->legsAnim; + + if ( legsAnim == BOTH_WALL_RUN_LEFT || legsAnim == BOTH_WALL_RUN_RIGHT ) + {//running on a wall + vec3_t right, traceto, mins = {pm->mins[0],pm->mins[0],0}, maxs = {pm->maxs[0],pm->maxs[0],24}, fwdAngles = {0, pm->ps->viewangles[YAW], 0}; + trace_t trace; + int anim = -1; + + AngleVectors( fwdAngles, NULL, right, NULL ); + + if ( legsAnim == BOTH_WALL_RUN_LEFT ) + { + if ( pm->ps->legsAnimTimer > 400 ) + {//not at the end of the anim + float animLen = PM_AnimLength( pm->gent->client->clientInfo.animFileIndex, BOTH_WALL_RUN_LEFT ); + if ( pm->ps->legsAnimTimer < animLen - 400 ) + {//not at start of anim + VectorMA( pm->ps->origin, -16, right, traceto ); + anim = BOTH_WALL_RUN_LEFT_FLIP; + } + } + } + else if ( legsAnim == BOTH_WALL_RUN_RIGHT ) + { + if ( pm->ps->legsAnimTimer > 400 ) + {//not at the end of the anim + float animLen = PM_AnimLength( pm->gent->client->clientInfo.animFileIndex, BOTH_WALL_RUN_RIGHT ); + if ( pm->ps->legsAnimTimer < animLen - 400 ) + {//not at start of anim + VectorMA( pm->ps->origin, 16, right, traceto ); + anim = BOTH_WALL_RUN_RIGHT_FLIP; + } + } + } + if ( anim != -1 ) + { + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + if ( trace.fraction < 1.0f ) + {//flip off wall + if ( anim == BOTH_WALL_RUN_LEFT_FLIP ) + { + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + VectorMA( pm->ps->velocity, 150, right, pm->ps->velocity ); + } + else if ( anim == BOTH_WALL_RUN_RIGHT_FLIP ) + { + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + VectorMA( pm->ps->velocity, -150, right, pm->ps->velocity ); + } + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + pm->cmd.upmove = 0; + } + } + if ( pm->cmd.upmove != 0 ) + {//jump failed, so don't try to do normal jump code, just return + return qfalse; + } + } + else if ( pm->ps->legsAnim == BOTH_FORCEWALLRUNFLIP_START ) + {//want to jump off wall + vec3_t fwd, traceto, mins = {pm->mins[0],pm->mins[0],0}, maxs = {pm->maxs[0],pm->maxs[0],24}, fwdAngles = {0, pm->ps->viewangles[YAW], 0}; + trace_t trace; + int anim = -1; + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + + float animLen = PM_AnimLength( pm->gent->client->clientInfo.animFileIndex, BOTH_FORCEWALLRUNFLIP_START ); + if ( pm->ps->legsAnimTimer < animLen - 250 )//was 400 + {//not at start of anim + VectorMA( pm->ps->origin, 16, fwd, traceto ); + anim = BOTH_FORCEWALLRUNFLIP_END; + } + if ( anim != -1 ) + { + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + if ( trace.fraction < 1.0f ) + {//flip off wall + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + VectorMA( pm->ps->velocity, WALL_RUN_UP_BACKFLIP_SPEED, fwd, pm->ps->velocity ); + pm->ps->velocity[2] += 200; + int parts = SETANIM_LEGS; + if ( !pm->ps->weaponTime ) + {//not attacking, set anim on both + parts = SETANIM_BOTH; + } + PM_SetAnim( pm, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + //FIXME: do damage to traceEnt, like above? + pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + pm->cmd.upmove = 0; + PM_AddEvent( EV_JUMP ); + } + } + if ( pm->cmd.upmove != 0 ) + {//jump failed, so don't try to do normal jump code, just return + return qfalse; + } + } + /* + else if ( pm->cmd.forwardmove < 0 + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 + && !(pm->ps->pm_flags&PMF_JUMP_HELD) //not holding jump + && (level.time - pm->ps->lastOnGround) <= 250 //just jumped + )//&& !(pm->cmd.buttons&BUTTON_ATTACK) ) + {//double-tap back-jump does backflip + vec3_t fwd, fwdAngles = {0, pm->ps->viewangles[YAW], 0}; + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity ); + //pm->ps->velocity[2] = JUMP_VELOCITY; + int parts = SETANIM_LEGS; + if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + PM_SetAnim( pm, parts, PM_PickAnim( pm->gent, BOTH_FLIP_BACK1, BOTH_FLIP_BACK3 ), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + pm->ps->forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + pm->cmd.upmove = 0; + G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + WP_ForcePowerDrain( pm->gent, FP_LEVITATION, 0 ); + } + */ + /* + else if ( pm->cmd.forwardmove > 0 //pushing forward + && pm->ps->forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 //have force jump 2 or higher + && (level.time - pm->ps->lastOnGround) <= 250//just jumped + && (pm->ps->legsAnim == BOTH_JUMP1 || pm->ps->legsAnim == BOTH_INAIR1 )//not in a flip or spin or anything + {//run up wall, flip backwards + //FIXME: have to be moving... make sure it's opposite the wall... or at least forward? + int wallWalkAnim = BOTH_WALL_FLIP_BACK1; + int parts = SETANIM_LEGS; + int contents = CONTENTS_SOLID; + qboolean kick = qtrue; + if ( pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_2 ) + { + wallWalkAnim = BOTH_FORCEWALLRUNFLIP_START; + parts = SETANIM_BOTH; + kick = qfalse; + } + else + { + contents |= CONTENTS_BODY; + if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + } + if ( PM_HasAnimation( pm->gent, wallWalkAnim ) ) + { + vec3_t fwd, traceto, mins = {pm->mins[0],pm->mins[1],0}, maxs = {pm->maxs[0],pm->maxs[1],24}, fwdAngles = {0, pm->ps->viewangles[YAW], 0}; + trace_t trace; + vec3_t idealNormal; + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + VectorMA( pm->ps->origin, 32, fwd, traceto ); + + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, contents );//FIXME: clip brushes too? + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + gentity_t *traceEnt = &g_entities[trace.entityNum]; + + if ( trace.fraction < 1.0f + &&((trace.entityNums.solid!=SOLID_BMODEL)||DotProduct(trace.plane.normal,idealNormal)>0.7) ) + {//there is a wall there + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + if ( wallWalkAnim == BOTH_FORCEWALLRUNFLIP_START ) + { + pm->ps->velocity[2] = forceJumpStrength[FORCE_LEVEL_3]/2.0f; + } + else + { + VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity ); + } + //animate me + PM_SetAnim( pm, parts, wallWalkAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + pm->ps->forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + pm->cmd.upmove = 0; + G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + WP_ForcePowerDrain( pm->gent, FP_LEVITATION, 0 ); + //kick if jumping off an ent + if ( kick && pm->gent && trace.entityNum < ENTITYNUM_WORLD ) + { + if ( traceEnt + && traceEnt->client + && traceEnt->health > 0 + && traceEnt->takedamage + && traceEnt->client->NPC_class != CLASS_GALAKMECH + && traceEnt->client->NPC_class != CLASS_DESANN + && !(traceEnt->flags&FL_NO_KNOCKBACK) ) + {//push them away and do pain + vec3_t oppDir; + float strength = VectorNormalize2( pm->ps->velocity, oppDir ); + VectorScale( oppDir, -1, oppDir ); + //FIXME: need knockdown anim + G_Damage( traceEnt, pm->gent, pm->gent, oppDir, traceEnt->currentOrigin, 10, DAMAGE_NO_ARMOR|DAMAGE_NO_HIT_LOC|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + G_Sound( traceEnt, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) ); + if ( trace.fraction <= 0.5f ) + {//close to him + if ( traceEnt->health > 0 ) + {//didn't kill him + if ( (traceEnt->s.number==0&&!Q_irand(0,g_spskill->integer)) + || (traceEnt->NPC!=NULL&&Q_irand(RANK_CIVILIAN,traceEnt->NPC->rank)+Q_irand(-2,2)ps->legsAnimTimer<=100 + ||!PM_InSpecialJump( legsAnim )//not in a special jump anim + ||PM_InReboundJump( legsAnim )//we're already in a rebound + ||PM_InBackFlip( legsAnim ) )//a backflip (needed so you can jump off a wall behind you) + //&& pm->ps->velocity[2] <= 0 + && pm->ps->velocity[2] > -1200 //not falling down very fast + && !(pm->ps->pm_flags&PMF_JUMP_HELD)//have to have released jump since last press + && (pm->cmd.forwardmove||pm->cmd.rightmove)//pushing in a direction + //&& pm->ps->forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_2//level 3 jump or better + && pm->ps->forcePower > 10 //have enough force power to do another one + && (level.time-pm->ps->lastOnGround) > 250 //haven't been on the ground in the last 1/4 of a second + && (!(pm->ps->pm_flags&PMF_JUMPING)//not jumping + || ( (level.time-pm->ps->lastOnGround) > 250 //we are jumping, but have been in the air for at least half a second + &&( g_debugMelee->integer//if you know kung fu, no height cap on wall-grab-jumps + || ((pm->ps->origin[2]-pm->ps->forceJumpZStart) < (forceJumpHeightMax[FORCE_LEVEL_3]-(G_ForceWallJumpStrength()/2.0f))) )//can fit at least one more wall jump in (yes, using "magic numbers"... for now) + ) + ) + //&& (pm->ps->legsAnim == BOTH_JUMP1 || pm->ps->legsAnim == BOTH_INAIR1 ) )//not in a flip or spin or anything + ) + {//see if we're pushing at a wall and jump off it if so + //FIXME: make sure we have enough force power + //FIXME: check to see if we can go any higher + //FIXME: limit to a certain number of these in a row? + //FIXME: maybe don't require a ucmd direction, just check all 4? + //FIXME: should stick to the wall for a second, then push off... + vec3_t checkDir, traceto, mins = {pm->mins[0],pm->mins[1],0}, maxs = {pm->maxs[0],pm->maxs[1],24}, fwdAngles = {0, pm->ps->viewangles[YAW], 0}; + trace_t trace; + vec3_t idealNormal; + int anim = -1; + + if ( pm->cmd.rightmove ) + { + if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_FORCEWALLREBOUND_RIGHT; + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_FORCEWALLREBOUND_LEFT; + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + VectorScale( checkDir, -1, checkDir ); + } + } + else if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_FORCEWALLREBOUND_FORWARD; + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_FORCEWALLREBOUND_BACK; + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + VectorScale( checkDir, -1, checkDir ); + } + if ( anim != -1 ) + {//trace in the dir we're pushing in and see if there's a vertical wall there + VectorMA( pm->ps->origin, 16, checkDir, traceto );//was 8 + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID );//FIXME: clip brushes too? + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( trace.fraction < 1.0f + && fabs(trace.plane.normal[2]) <= MAX_WALL_GRAB_SLOPE + &&((trace.entityNums.solid!=SOLID_BMODEL)||DotProduct(trace.plane.normal,idealNormal)>0.7) ) + {//there is a wall there + float dot = DotProduct( pm->ps->velocity, trace.plane.normal ); + if ( dot < 1.0f ) + {//can't be heading *away* from the wall! + //grab it! + PM_GrabWallForJump( anim ); + } + } + } + } + else + { + //FIXME: if in a butterfly, kick people away? + } + } + } + + if ( pm->gent + //&& pm->cmd.upmove > 0 + && pm->ps->forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->ps->weapon == WP_SABER + && (pm->ps->weaponTime > 0||(pm->cmd.buttons&BUTTON_ATTACK)) + && ((pm->ps->clientNum&&!PM_ControlledByPlayer())||((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && cg.renderingThirdPerson && !cg.zoomMode)) ) + {//okay, we just jumped and we're in an attack + if ( !PM_RollingAnim( pm->ps->legsAnim ) + && !PM_InKnockDown( pm->ps ) + && !PM_InDeathAnim() + && !PM_PainAnim( pm->ps->torsoAnim ) + && !PM_FlippingAnim( pm->ps->legsAnim ) + && !PM_SpinningAnim( pm->ps->legsAnim ) + && !PM_SaberInSpecialAttack( pm->ps->torsoAnim ) ) + {//HMM... do NPCs need this logic? + if ( !PM_SaberInTransitionAny( pm->ps->saberMove ) //not going to/from/between an attack anim + && !PM_SaberInAttack( pm->ps->saberMove ) //not in attack anim + && pm->ps->weaponTime <= 0//not busy + && (pm->cmd.buttons&BUTTON_ATTACK) )//want to attack + {//not in an attack or finishing/starting/transitioning one + if ( PM_CheckBackflipAttackMove() ) + { + PM_SetSaberMove( PM_SaberBackflipAttackMove() );//backflip attack + } + /* + else if ( PM_CheckSaberDualJumpAttackMove() ) + { + PM_SetSaberMove( PM_SaberDualJumpAttackMove() );//jump forward sideways flip attack + } + */ + } + /* + else if ( ( PM_SaberInTransitionAny( pm->ps->saberMove ) || PM_SaberInAttack( pm->ps->saberMove ) ) + && PM_InAnimForSaberMove( pm->ps->torsoAnim, pm->ps->saberMove ) ) + {//not in an anim we shouldn't interrupt + //see if it's not too late to start a special jump-attack + float animLength = PM_AnimLength( g_entities[pm->ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)pm->ps->torsoAnim ); + if ( animLength - pm->ps->torsoAnimTimer < 500 ) + {//just started the saberMove + //check for special-case jump attacks + if ( PM_CheckFlipOverAttackMove( qtrue ) ) + { + PM_SetSaberMove( PM_SaberFlipOverAttackMove() ); + } + else if ( PM_CheckJumpAttackMove() ) + { + PM_SetSaberMove( PM_SaberJumpAttackMove() ); + } + } + } + */ + } + } + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + return qfalse; + } + if ( pm->cmd.upmove > 0 ) + {//no special jumps + /* + gentity_t *groundEnt = &g_entities[pm->ps->groundEntityNum]; + if ( groundEnt && groundEnt->NPC ) + {//Can't jump off of someone's head + return qfalse; + } + */ + + pm->ps->velocity[2] = JUMP_VELOCITY; + pm->ps->forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + pm->ps->pm_flags |= PMF_JUMPING; + } + + if ( d_JediAI->integer ) + { + if ( pm->ps->clientNum && pm->ps->weapon == WP_SABER ) + { + Com_Printf( "jumping\n" ); + } + } + //Jumping + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps->jumpZStart = pm->ps->origin[2]; + + if ( pm->gent ) + { + if ( !Q3_TaskIDPending( pm->gent, TID_CHAN_VOICE ) ) + { + PM_AddEvent( EV_JUMP ); + } + } + else + { + PM_AddEvent( EV_JUMP ); + } + + //Set the animations + if ( pm->ps->gravity > 0 && !PM_InSpecialJump( pm->ps->legsAnim ) && !PM_InGetUp( pm->ps ) ) + { + PM_JumpForDir(); + } + + return qtrue; +} + +/* +============= +PM_CheckWaterJump +============= +*/ +static qboolean PM_CheckWaterJump( void ) { + vec3_t spot; + int cont; + vec3_t flatforward; + + if (pm->ps->pm_time) { + return qfalse; + } + + if ( pm->cmd.forwardmove <= 0 && pm->cmd.upmove <= 0 ) + {//they must not want to get out? + return qfalse; + } + // check for water jump + if ( pm->waterlevel != 2 ) { + return qfalse; + } + + if ( pm->watertype & CONTENTS_LADDER ) { + if (pm->ps->velocity[2] <= 0) + return qfalse; + } + + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + VectorNormalize( flatforward ); + + VectorMA( pm->ps->origin, 30, flatforward, spot ); + spot[2] += 24; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( !(cont & CONTENTS_SOLID) ) { + return qfalse; + } + + spot[2] += 16; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + if ( cont&(CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_BODY) ) { + return qfalse; + } + + // jump out of water + VectorScale( pml.forward, 200, pm->ps->velocity ); + pm->ps->velocity[2] = 350+((pm->ps->waterheight-pm->ps->origin[2])*2); + + pm->ps->pm_flags |= PMF_TIME_WATERJUMP; + pm->ps->pm_time = 2000; + + return qtrue; +} + +//============================================================================ + + +/* +=================== +PM_WaterJumpMove + +Flying out of the water +=================== +*/ +static void PM_WaterJumpMove( void ) +{ + // waterjump has no control, but falls + + PM_StepSlideMove( 1 ); + + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + if (pm->ps->velocity[2] < 0) + { + // cancel as soon as we are falling down again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +static void PM_WaterMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + if ( PM_CheckWaterJump() ) { + PM_WaterJumpMove(); + return; + } + else if ( pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 && pm->waterlevel < 3 ) + { + if ( PM_CheckJump () ) { + // jumped away + return; + } + } +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if (pm->ps->velocity[2] > -300) { + if ( pm->watertype == CONTENTS_WATER ) { + pm->ps->velocity[2] = 100; + } else if (pm->watertype == CONTENTS_SLIME) { + pm->ps->velocity[2] = 80; + } else { + pm->ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + if ( pm->watertype & CONTENTS_LADDER ) { + wishvel[2] = 0; + } else { + wishvel[2] = -60; // sink towards bottom + } + } else { + for (i=0 ; i<3 ; i++) { + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + } + wishvel[2] += scale * pm->cmd.upmove; + if ( !(pm->watertype&CONTENTS_LADDER) ) //ladder + { + float depth = (pm->ps->origin[2]+pm->gent->client->standheight)-pm->ps->waterheight; + if ( depth >= 12 ) + {//too high! + wishvel[2] -= 120; // sink towards bottom + if ( wishvel[2] > 0 ) + { + wishvel[2] = 0; + } + } + else if ( pm->ps->waterHeightLevel >= WHL_UNDER )//!depth && pm->waterlevel == 3 ) + { + } + else if ( depth < 12 ) + {//still deep + wishvel[2] -= 60; // sink towards bottom + if ( wishvel[2] > 30 ) + { + wishvel[2] = 30; + } + } + } + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + if ( pm->watertype & CONTENTS_LADDER ) //ladder + { + if ( wishspeed > pm->ps->speed * pm_ladderScale ) { + wishspeed = pm->ps->speed * pm_ladderScale; + } + PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate ); + } else { + if ( pm->ps->gravity < 0 ) + {//float up + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + } + if ( wishspeed > pm->ps->speed * pm_swimScale ) { + wishspeed = pm->ps->speed * pm_swimScale; + } + PM_Accelerate( wishdir, wishspeed, pm_wateraccelerate ); + } + + // make sure we can go up slopes easily under water + if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { + vel = VectorLength(pm->ps->velocity); + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + } + + PM_SlideMove( qfalse ); +} + + +/* +=================== +PM_FlyVehicleMove + +=================== +*/ +static void PM_FlyVehicleMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float zVel; + float fmove = 0.0f, smove = 0.0f; + + // We don't use these here because we pre-calculate the movedir in the vehicle update anyways, and if + // you leave this, you get strange motion during boarding (the player can move the vehicle). + //fmove = pm->cmd.forwardmove; + //smove = pm->cmd.rightmove; + + // normal slowdown + if ( pm->ps->gravity && pm->ps->velocity[2] < 0 && pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//falling + zVel = pm->ps->velocity[2]; + PM_Friction (); + pm->ps->velocity[2] = zVel; + } + else + { + PM_Friction (); + if ( pm->ps->velocity[2] < 0 && pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + pm->ps->velocity[2] = 0; // ignore slope movement + } + } + + scale = PM_CmdScale( &pm->cmd ); + + // Get The WishVel And WishSpeed + //------------------------------- + if ( pm->ps->clientNum && (USENEWNAVSYSTEM || !VectorCompare( pm->ps->moveDir, vec3_origin )) ) + {//NPC + + // If The UCmds Were Set, But Never Converted Into A MoveDir, Then Make The WishDir From UCmds + //-------------------------------------------------------------------------------------------- + if ((fmove!=0.0f || smove!=0.0f) && VectorCompare(pm->ps->moveDir, vec3_origin)) + { + //gi.Printf("Generating MoveDir\n"); + for ( i = 0 ; i < 3 ; i++ ) + { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + } + // Otherwise, Use The Move Dir + //----------------------------- + else + { + wishspeed = pm->ps->speed; + VectorScale( pm->ps->moveDir, pm->ps->speed, wishvel ); + VectorCopy( pm->ps->moveDir, wishdir ); + } + + } + else + { + for ( i = 0 ; i < 3 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + // when going up or down slopes the wish velocity should Not be zero + // wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + } + + // Handle negative speed. + if ( wishspeed < 0 ) + { + wishspeed = wishspeed * -1.0f; + VectorScale( wishvel, -1.0f, wishvel ); + VectorScale( wishdir, -1.0f, wishdir ); + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + PM_Accelerate( wishdir, wishspeed, 100 ); + + PM_StepSlideMove( 1 ); +} + +/* +=================== +PM_FlyMove + +Only with the flight powerup +=================== +*/ +static void PM_FlyMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float accel; + qboolean lowGravMove = qfalse; + qboolean jetPackMove = qfalse; + + // normal slowdown + PM_Friction (); + + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + && pm->gent + && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_BOBAFETT||pm->gent->client->NPC_class == CLASS_ROCKETTROOPER) && pm->gent->client->moveType == MT_FLYSWIM ) + {//jetpack accel + accel = pm_flyaccelerate; + jetPackMove = qtrue; + } + else if ( pm->ps->gravity <= 0 + && ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) || (pm->gent&&pm->gent->client&&pm->gent->client->moveType == MT_RUNJUMP)) ) + { + PM_CheckJump(); + accel = 1.0f; + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + pm->ps->jumpZStart = pm->ps->origin[2];//so we don't take a lot of damage when the gravity comes back on + lowGravMove = qtrue; + } + else + { + accel = pm_flyaccelerate; + } + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) + { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = 0; + } + else + { + for (i=0 ; i<3 ; i++) + { + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + } + if ( jetPackMove ) + { + wishvel[2] += pm->cmd.upmove; + } + else if ( lowGravMove ) + { + wishvel[2] += scale * pm->cmd.upmove; + VectorScale( wishvel, 0.5f, wishvel ); + } + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + PM_Accelerate( wishdir, wishspeed, accel ); + + PM_StepSlideMove( 1 ); +} + +qboolean PM_GroundSlideOkay( float zNormal ) +{ + if ( zNormal > 0 ) + { + if ( pm->ps->velocity[2] > 0 ) + { + if ( pm->ps->legsAnim == BOTH_WALL_RUN_RIGHT + || pm->ps->legsAnim == BOTH_WALL_RUN_LEFT + || pm->ps->legsAnim == BOTH_WALL_RUN_RIGHT_STOP + || pm->ps->legsAnim == BOTH_WALL_RUN_LEFT_STOP + || pm->ps->legsAnim == BOTH_FORCEWALLRUNFLIP_START + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_START + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_ATTACK + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_LAND + || PM_InReboundJump( pm->ps->legsAnim )) + { + return qfalse; + } + } + } + return qtrue; +} +/* +=================== +PM_AirMove + +=================== +*/ +static void PM_AirMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float gravMod = 1.0f; + +#if METROID_JUMP + PM_CheckJump(); +#endif + + PM_Friction(); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + Vehicle_t *pVeh = NULL; + + if ( pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE ) + { + pVeh = pm->gent->m_pVehicle; + } + + if ( pVeh && pVeh->m_pVehicleInfo->hoverHeight > 0 ) + {//in a hovering vehicle, have air control + + // Flying Or Breaking, No Control + //-------------------------------- + if ( pVeh->m_ulFlags&VEH_FLYING || pVeh->m_ulFlags&VEH_SLIDEBREAKING) + { + wishspeed = 0.0f; + VectorClear( wishvel ); + VectorClear( wishdir ); + } + + // Out Of Control - Maintain pos3 Velocity + //----------------------------------------- + else if ((pVeh->m_ulFlags&VEH_OUTOFCONTROL) || (pVeh->m_ulFlags&VEH_STRAFERAM)) + { + VectorCopy(pm->gent->pos3, wishvel); + VectorCopy(wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + } + + // Boarding - Maintain Boarding Velocity + //--------------------------------------- + else if (pVeh->m_iBoarding) + { + VectorCopy(pVeh->m_vBoardingVelocity, wishvel); + VectorCopy(wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + } + + // Otherwise, Normal Velocity + //---------------------------- + else + { + wishspeed = pm->ps->speed; + VectorScale( pm->ps->moveDir, pm->ps->speed, wishvel ); + VectorCopy( pm->ps->moveDir, wishdir ); + } + } + else if ( (pm->ps->pm_flags&PMF_SLOW_MO_FALL) ) + {//no air-control + VectorClear( wishvel ); + } + else + { + for ( i = 0 ; i < 2 ; i++ ) + { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + wishvel[2] = 0; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + if ( ( DotProduct (pm->ps->velocity, wishdir) ) < 0.0f ) + {//Encourage deceleration away from the current velocity + wishspeed *= pm_airDecelRate; + } + + // not on ground, so little effect on velocity + float accelerate = pm_airaccelerate; + if ( pVeh && pVeh->m_pVehicleInfo->type == VH_SPEEDER ) + {//speeders have more control in air + //in mid-air + accelerate = pVeh->m_pVehicleInfo->acceleration; + if ( pml.groundPlane ) + {//on a slope of some kind, shouldn't have much control and should slide a lot + accelerate *= 0.5f; + } + if (pVeh->m_ulFlags & VEH_SLIDEBREAKING) + { + VectorScale(pm->ps->velocity, 0.80f, pm->ps->velocity); + } + if (pm->ps->velocity[2]>1000.0f) + { + pm->ps->velocity[2] = 1000.0f; + } + } + PM_Accelerate( wishdir, wishspeed, accelerate ); + + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( pml.groundPlane ) + { + if ( PM_GroundSlideOkay( pml.groundTrace.plane.normal[2] ) ) + { + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 + && pm->ps->forceJumpZStart + && pm->ps->velocity[2] > 0 ) + {//I am force jumping and I'm not holding the button anymore + float curHeight = pm->ps->origin[2] - pm->ps->forceJumpZStart + (pm->ps->velocity[2]*pml.frametime); + float maxJumpHeight = forceJumpHeight[pm->ps->forcePowerLevel[FP_LEVITATION]]; + if ( curHeight >= maxJumpHeight ) + {//reached top, cut velocity + pm->ps->velocity[2] = 0; + } + } + if ( (pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + { + gravMod = 0.0f; + } + PM_StepSlideMove( gravMod ); + + if (pVeh && pm->ps->pm_flags&PMF_BUMPED) + { + +/* + // Turn Vehicle In Direction Of Collision + //---------------------------------------- + vec3_t nAngles; + vectoangles(pm->ps->velocity, nAngles); + nAngles[0] = pVeh->m_pParentEntity->client->ps.viewangles[0]; + nAngles[2] = pVeh->m_pParentEntity->client->ps.viewangles[2]; + + // toggle the teleport bit so the client knows to not lerp + player->client->ps.eFlags ^= EF_TELEPORT_BIT; + + // set angles + SetClientViewAngle( pVeh->m_pParentEntity, nAngles ); + if (pVeh->m_pPilot) + { + SetClientViewAngle( pVeh->m_pPilot, nAngles ); + } + + VectorCopy(nAngles, pVeh->m_vPrevOrientation); + VectorCopy(nAngles, pVeh->m_vOrientation); + pVeh->m_vAngularVelocity = 0.0f; +*/ + + // Reduce "Bounce Up Wall" Velocity + //---------------------------------- + if (pm->ps->velocity[2]>0) + { + pm->ps->velocity[2] *= 0.1f; + } + } +} + + +/* +=================== +PM_WalkMove + +=================== +*/ +static void PM_WalkMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + float vel; + + if ( pm->ps->gravity < 0 ) + {//float away + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + if ( pm->waterlevel > 1 ) { + PM_WaterMove(); + } else { + PM_AirMove(); + } + return; + } + + if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { + // begin swimming + PM_WaterMove(); + return; + } + + + if ( PM_CheckJump () ) { + // jumped away + if ( pm->waterlevel > 1 ) { + PM_WaterMove(); + } else { + PM_AirMove(); + } + return; + } + + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE &&//on ground + pm->ps->velocity[2] <= 0 &&//not going up + pm->ps->pm_flags&PMF_TIME_KNOCKBACK )//knockback fimter on (stops friction) + { + pm->ps->pm_flags &= ~PMF_TIME_KNOCKBACK; + } + + qboolean slide = qfalse; + if ( pm->ps->pm_type == PM_DEAD ) + {//corpse + if ( g_entities[pm->ps->groundEntityNum].client ) + {//on a client + if ( g_entities[pm->ps->groundEntityNum].health > 0 ) + {//a living client + //no friction + slide = qtrue; + } + } + } + if ( !slide ) + { + PM_Friction (); + } + + if ( g_debugMelee->integer ) + { + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())//player + && cg.renderingThirdPerson//in third person + && ((pm->cmd.buttons&BUTTON_USE)||pm->ps->leanStopDebounceTime)//holding use or leaning + //&& (pm->ps->forcePowersActive&(1<ps->groundEntityNum != ENTITYNUM_NONE//on ground + && !cg_usingInFrontOf )//nothing to use + {//holding use stops you from moving + return; + } + } + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + + // project the forward and right directions onto the ground plane + PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); + PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + // + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + +/* if ( ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE ) && pm->gent->NPC ) + {//speeder control scheme + vec3_t vfwd, vrt; + AngleVectors( ((CVehicleNPC *)pm->gent->NPC)->m_vOrientation, vfwd, vrt, NULL ); + + float speed = pm->ps->speed; + if ( fmove < 0 ) + {//going backwards + if ( speed < 0 ) + {//speed is negative, but since our command is reverse, make speed positive + speed = fabs( speed ); + } + else if ( speed > 0 ) + {//trying to move back but speed is still positive, so keep moving forward (we'll slow down eventually) + speed *= -1; + } + } + VectorScale( vfwd, speed*fmove/127.0f, wishvel ); + //VectorMA( wishvel, pm->ps->speed*smove/127.0f, vrt, wishvel ); + wishspeed = VectorNormalize2( wishvel, wishdir ); + } + else*/ + + // Get The WishVel And WishSpeed + //------------------------------- + if ( pm->ps->clientNum && (USENEWNAVSYSTEM || !VectorCompare( pm->ps->moveDir, vec3_origin )) ) + {//NPC + + // If The UCmds Were Set, But Never Converted Into A MoveDir, Then Make The WishDir From UCmds + //-------------------------------------------------------------------------------------------- + if ((fmove!=0.0f || smove!=0.0f) && VectorCompare(pm->ps->moveDir, vec3_origin)) + { + //gi.Printf("Generating MoveDir\n"); + for ( i = 0 ; i < 3 ; i++ ) + { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + } + // Otherwise, Use The Move Dir + //----------------------------- + else + { + wishspeed = pm->ps->speed; + VectorScale( pm->ps->moveDir, pm->ps->speed, wishvel ); + VectorCopy( pm->ps->moveDir, wishdir ); + } + + } + else + { + for ( i = 0 ; i < 3 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + // when going up or down slopes the wish velocity should Not be zero + // wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + } + + // Handle negative speed. + if ( wishspeed < 0 ) + { + wishspeed = wishspeed * -1.0f; + VectorScale( wishvel, -1.0f, wishvel ); + VectorScale( wishdir, -1.0f, wishdir ); + } + + // clamp the speed lower if ducking + if ( pm->ps->pm_flags & PMF_DUCKED && !PM_InKnockDown( pm->ps ) ) + { + if ( wishspeed > pm->ps->speed * pm_duckScale ) + { + wishspeed = pm->ps->speed * pm_duckScale; + } + } + + // clamp the speed lower if wading or walking on the bottom + if ( pm->waterlevel ) { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; + if ( wishspeed > pm->ps->speed * waterScale ) { + wishspeed = pm->ps->speed * waterScale; + } + } + + // when a player gets hit, they temporarily lose + // full control, which allows them to be moved a bit + if ( Flying == FLY_HOVER ) + { + accelerate = pm_vehicleaccelerate; + } + else if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || (pm->ps->pm_flags&PMF_TIME_KNOCKBACK) || (pm->ps->pm_flags&PMF_TIME_NOFRICTION) ) + { + accelerate = pm_airaccelerate; + } + else + { + accelerate = pm_accelerate; + + // Wind Affects Acceleration + //=================================================== + if (wishspeed>0.0f && pm->gent && !pml.walking) + { + if (gi.WE_GetWindGusting(pm->gent->currentOrigin)) + { + vec3_t windDir; + if (gi.WE_GetWindVector(windDir, pm->gent->currentOrigin)) + { + if (gi.WE_IsOutside(pm->gent->currentOrigin)) + { + VectorScale(windDir, -1.0f, windDir); + accelerate *= (1.0f - (DotProduct(wishdir, windDir)*0.55f)); + } + } + } + } + //=================================================== + } + + PM_Accelerate (wishdir, wishspeed, accelerate); + + //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); + //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); + + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK || (pm->ps->pm_flags&PMF_TIME_NOFRICTION) ) { + if ( pm->ps->gravity >= 0 && pm->ps->groundEntityNum != ENTITYNUM_NONE && !VectorLengthSquared( pm->ps->velocity ) && pml.groundTrace.plane.normal[2] == 1.0 ) + {//on ground and not moving and on level ground, no reason to do stupid fucking gravity with the clipvelocity!!!! + } + else + { + if ( !(pm->ps->eFlags&EF_FORCE_GRIPPED) && !(pm->ps->eFlags&EF_FORCE_DRAINED) ) + { + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + } + } + } else { + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + } + + vel = VectorLength(pm->ps->velocity); + + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + // don't decrease velocity when going up or down a slope + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + + // don't do anything if standing still + if ( !pm->ps->velocity[0] && !pm->ps->velocity[1] ) { + return; + } + + if ( pm->ps->gravity <= 0 ) + {//need to apply gravity since we're going to float up from ground + PM_StepSlideMove( 1 ); + } + else + { + PM_StepSlideMove( 0 ); + } + + //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); + +} + + +/* +============== +PM_DeadMove +============== +*/ +static void PM_DeadMove( void ) { + float forward; + + // If this is a vehicle, tell him he's dead, but give him a little while to do his things. +/* if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE && pm->gent->NPC && pm->gent->health != -99999 ) + { + pm->gent->health = 1; + ((CVehicleNPC *)pm->gent->NPC)->StartDeathDelay( 0 ); + } + else + { + pm->gent->health = 0; + }*/ + + if ( !pml.walking ) { + return; + } + + // extra friction + + forward = VectorLength (pm->ps->velocity); + forward -= 20; + if ( forward <= 0 ) { + VectorClear (pm->ps->velocity); + } else { + VectorNormalize (pm->ps->velocity); + VectorScale (pm->ps->velocity, forward, pm->ps->velocity); + } +} + + +/* +=============== +PM_NoclipMove +=============== +*/ +static void PM_NoclipMove( void ) { + float speed, drop, friction, control, newspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + + if(pm->gent && pm->gent->client) + { + pm->ps->viewheight = pm->gent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET; +// if ( !pm->gent->mins[0] || !pm->gent->mins[1] || !pm->gent->mins[2] || !pm->gent->maxs[0] || !pm->gent->maxs[1] || !pm->gent->maxs[2] ) +// { +// assert(0); +// } + + VectorCopy( pm->gent->mins, pm->mins ); + VectorCopy( pm->gent->maxs, pm->maxs ); + } + else + { + pm->ps->viewheight = DEFAULT_MAXS_2 + STANDARD_VIEWHEIGHT_OFFSET;//DEFAULT_VIEWHEIGHT; + + if ( !DEFAULT_MINS_0 || !DEFAULT_MINS_1 || !DEFAULT_MAXS_0 || !DEFAULT_MAXS_1 || !DEFAULT_MINS_2 || !DEFAULT_MAXS_2 ) + { + assert(0); + } + + pm->mins[0] = DEFAULT_MINS_0; + pm->mins[1] = DEFAULT_MINS_1; + pm->mins[2] = DEFAULT_MINS_2; + + pm->maxs[0] = DEFAULT_MAXS_0; + pm->maxs[1] = DEFAULT_MAXS_1; + pm->maxs[2] = DEFAULT_MAXS_2; + } + + // friction + + speed = VectorLength (pm->ps->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pm->ps->velocity); + } + else + { + drop = 0; + + friction = pm_friction*1.5; // 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; + + VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + if (pm->cmd.buttons & BUTTON_ATTACK) { //turbo boost + scale *= 10; + } + if (pm->cmd.buttons & BUTTON_ALT_ATTACK) { //turbo boost + scale *= 10; + } + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + for (i=0 ; i<3 ; i++) + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + wishvel[2] += pm->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + // move + VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); +} + +//============================================================================ + +static float PM_DamageForDelta( int delta ) +{ + float damage = delta; + if ( pm->gent->NPC ) + { + if ( pm->ps->weapon == WP_SABER + || (pm->gent->client && pm->gent->client->NPC_class == CLASS_REBORN) ) + {//FIXME: for now Jedi take no falling damage, but really they should if pushed off? + damage = 0; + } + } + else if ( pm->ps->clientNum < MAX_CLIENTS ) + { + if ( damage < 50 ) + { + if ( damage > 24 ) + { + damage = damage - 25; + } + } + else + { + damage *= 0.5f; + } + } + return damage * 0.5f; +} + +static void PM_CrashLandDamage( int damage ) +{ + if ( pm->gent ) + { + int dflags = DAMAGE_NO_ARMOR; + if ( pm->gent->NPC && (pm->gent->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + { + damage = 1000; + dflags |= DAMAGE_DIE_ON_IMPACT; + } + else if ( !(pm->gent->flags&FL_NO_IMPACT_DMG) ) + { + damage = PM_DamageForDelta( damage ); + } + + if ( damage ) + { + pm->gent->painDebounceTime = level.time + 200; // no normal pain sound + G_Damage( pm->gent, NULL, player, NULL, NULL, damage, dflags, MOD_FALLING ); + } + } +} + +/* +static float PM_CrashLandDelta( vec3_t org, vec3_t prevOrg, vec3_t prev_vel, float grav, int waterlevel ) +{ + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + + // calculate the exact velocity on landing + dist = org[2] - prevOrg[2]; + vel = prev_vel[2]; + acc = -grav; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if ( den < 0 ) + { + return 0; + } + t = (-b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta*delta * 0.0001; + + // never take falling damage if completely underwater + if ( waterlevel == 3 ) + { + return 0; + } + // reduce falling damage if there is standing water + if ( waterlevel == 2 ) + { + delta *= 0.25; + } + if ( waterlevel == 1 ) + { + delta *= 0.5; + } + + return delta; +} +*/ + +static float PM_CrashLandDelta( vec3_t prev_vel, int waterlevel ) +{ + float delta; + + if ( pm->waterlevel == 3 ) + { + return 0; + } + delta = fabs(prev_vel[2])/10;//VectorLength( prev_vel ) + + // reduce falling damage if there is standing water + if ( pm->waterlevel == 2 ) + { + delta *= 0.25; + } + if ( pm->waterlevel == 1 ) + { + delta *= 0.5; + } + + return delta; +} + +int PM_GetLandingAnim( void ) +{ + int anim = pm->ps->legsAnim; + + //special cases: + if ( anim == BOTH_FLIP_ATTACK7 + || anim == BOTH_FLIP_HOLD7 ) + { + return BOTH_FLIP_LAND; + } + else if ( anim == BOTH_FLIP_LAND ) + { + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + return BOTH_LAND1; + } + else if ( PM_InAirKickingAnim( anim ) ) + { + switch ( anim ) + { + case BOTH_A7_KICK_F_AIR: + return BOTH_FORCELAND1; + break; + case BOTH_A7_KICK_B_AIR: + return BOTH_FORCELANDBACK1; + break; + case BOTH_A7_KICK_R_AIR: + return BOTH_FORCELANDRIGHT1; + break; + case BOTH_A7_KICK_L_AIR: + return BOTH_FORCELANDLEFT1; + break; + } + } + + if ( PM_SpinningAnim( anim ) || PM_SaberInSpecialAttack( anim ) ) + { + return -1; + } + switch ( anim ) + { + case BOTH_FORCEJUMPLEFT1: + case BOTH_FORCEINAIRLEFT1: + anim = BOTH_FORCELANDLEFT1; + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + break; + case BOTH_FORCEJUMPRIGHT1: + case BOTH_FORCEINAIRRIGHT1: + anim = BOTH_FORCELANDRIGHT1; + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + break; + case BOTH_FORCEJUMP1: + case BOTH_FORCEINAIR1: + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + anim = BOTH_FORCELAND1; + break; + case BOTH_FORCEJUMPBACK1: + case BOTH_FORCEINAIRBACK1: + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + anim = BOTH_FORCELANDBACK1; + break; + case BOTH_JUMPLEFT1: + case BOTH_INAIRLEFT1: + anim = BOTH_LANDLEFT1; + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + break; + case BOTH_JUMPRIGHT1: + case BOTH_INAIRRIGHT1: + anim = BOTH_LANDRIGHT1; + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + break; + case BOTH_JUMP1: + case BOTH_INAIR1: + anim = BOTH_LAND1; + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + break; + case BOTH_JUMPBACK1: + case BOTH_INAIRBACK1: + anim = BOTH_LANDBACK1; + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + break; + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_LUNGE2_B__T_: + case BOTH_FORCELEAP2_T__B_: + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_ARIAL_F1: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + case BOTH_JUMPFLIPSLASHDOWN1://# + case BOTH_JUMPFLIPSTABDOWN://# + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + case BOTH_A7_KICK_F: + case BOTH_A7_KICK_B: + case BOTH_A7_KICK_R: + case BOTH_A7_KICK_L: + case BOTH_A7_KICK_S: + case BOTH_A7_KICK_BF: + case BOTH_A7_KICK_RL: + case BOTH_A7_KICK_F_AIR: + case BOTH_A7_KICK_B_AIR: + case BOTH_A7_KICK_R_AIR: + case BOTH_A7_KICK_L_AIR: + case BOTH_STABDOWN: + case BOTH_STABDOWN_STAFF: + case BOTH_STABDOWN_DUAL: + case BOTH_A6_SABERPROTECT: + case BOTH_A7_SOULCAL: + case BOTH_A1_SPECIAL: + case BOTH_A2_SPECIAL: + case BOTH_A3_SPECIAL: + case BOTH_PULL_IMPALE_STAB: + case BOTH_PULL_IMPALE_SWING: + anim = -1; + break; + case BOTH_FORCELONGLEAP_START: + case BOTH_FORCELONGLEAP_ATTACK: + return BOTH_FORCELONGLEAP_LAND; + break; + case BOTH_WALL_RUN_LEFT://# + case BOTH_WALL_RUN_RIGHT://# + if ( pm->ps->legsAnimTimer > 500 ) + {//only land at end of anim + return -1; + } + //NOTE: falls through on purpose! + default: + if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) + { + anim = BOTH_LANDBACK1; + } + else + { + anim = BOTH_LAND1; + } + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + break; + } + return anim; +} + +void G_StartRoll( gentity_t *ent, int anim ) +{ + NPC_SetAnim(ent,SETANIM_BOTH,anim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS); + ent->client->ps.weaponTime = ent->client->ps.torsoAnimTimer - 200;//just to make sure it's cleared when roll is done + G_AddEvent( ent, EV_ROLL, 0 ); + ent->client->ps.saberMove = LS_NONE; +} + +static qboolean PM_TryRoll( void ) +{ + float rollDist = 192;//was 64; + if ( PM_SaberInAttack( pm->ps->saberMove ) || PM_SaberInSpecialAttack( pm->ps->torsoAnim ) + || PM_SpinningSaberAnim( pm->ps->legsAnim ) + || ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())&&PM_SaberInStart( pm->ps->saberMove )) ) + {//attacking or spinning (or, if player, starting an attack) + if ( PM_CanRollFromSoulCal( pm->ps ) ) + {//hehe + } + else + { + return qfalse; + } + } + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && (!cg.renderingThirdPerson || cg.zoomMode) ) + {//player can't do this in 1st person + return qfalse; + } + if ( !pm->gent ) + { + return qfalse; + } + if ( pm->ps->clientNum && pm->gent->NPC ) + {//NPC + if ( pm->gent->NPC->scriptFlags&SCF_NO_ACROBATICS ) + {//scripted to never do acrobatics + return qfalse; + } + + if ( pm->ps->weapon == WP_SABER ) + {//jedi/reborn + if ( pm->gent->NPC->rank != RANK_CREWMAN && pm->gent->NPC->rank < RANK_LT_JG ) + {//reborn/jedi who are not acrobats or fencers can't do any of these acrobatics + return qfalse; + } + } + else + {//non-jedi/reborn + if ( pm->ps->weapon != WP_NONE )//not empty-handed...who would that be??? + {//only jedi/reborn NPCs should be able to do rolls (with a few exceptions) + if ( !pm->gent + || !pm->gent->client + || (pm->gent->client->NPC_class != CLASS_BOBAFETT //boba can roll with it, baby + && pm->gent->client->NPC_class != CLASS_REBORN //reborn using weapons other than saber can still roll + )) + {//can't roll + return qfalse; + } + } + } + } + + vec3_t fwd, right, traceto, mins = {pm->mins[0],pm->mins[1],pm->mins[2]+STEPSIZE}, maxs = {pm->maxs[0],pm->maxs[1],pm->gent->client->crouchheight}, fwdAngles = {0, pm->ps->viewangles[YAW], 0}; + trace_t trace; + int anim = -1; + AngleVectors( fwdAngles, fwd, right, NULL ); + //FIXME: trace ahead for clearance to roll + if ( pm->cmd.forwardmove ) + { + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + anim = BOTH_ROLL_B; + VectorMA( pm->ps->origin, -rollDist, fwd, traceto ); + } + else + { + anim = BOTH_ROLL_F; + VectorMA( pm->ps->origin, rollDist, fwd, traceto ); + } + } + else if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_ROLL_R; + VectorMA( pm->ps->origin, rollDist, right, traceto ); + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_ROLL_L; + VectorMA( pm->ps->origin, -rollDist, right, traceto ); + } + else + {//??? + } + if ( anim != -1 ) + { + qboolean roll = qfalse; + int clipmask = CONTENTS_SOLID; + if ( pm->ps->clientNum ) + { + clipmask |= (CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP); + } + else + { + if ( pm->gent && pm->gent->enemy && pm->gent->enemy->health > 0 ) + {//player can always roll in combat + roll = qtrue; + } + else + { + clipmask |= CONTENTS_PLAYERCLIP; + } + } + if ( !roll ) + { + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, clipmask ); + if ( trace.fraction >= 1.0f ) + {//okay, clear, check for a bottomless drop + vec3_t top; + VectorCopy( traceto, top ); + traceto[2] -= 256; + pm->trace( &trace, top, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID ); + if ( trace.fraction < 1.0f ) + {//not a bottomless drop + roll = qtrue; + } + } + else + {//hit an architectural obstruction + if ( pm->ps->clientNum ) + {//NPCs don't care about rolling into walls, just off ledges + if ( !(trace.contents&CONTENTS_BOTCLIP) ) + { + roll = qtrue; + } + } + else if ( G_EntIsDoor( trace.entityNum ) ) + {//okay to roll into a door + if ( G_EntIsUnlockedDoor( trace.entityNum ) ) + {//if it's an auto-door + roll = qtrue; + } + } + else + {//check other conditions + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( traceEnt && (traceEnt->svFlags&SVF_GLASS_BRUSH) ) + {//okay to roll through glass + roll = qtrue; + } + } + } + } + if ( roll ) + { + G_StartRoll( pm->gent, anim ); + return qtrue; + } + } + return qfalse; +} + +extern void CG_LandingEffect( vec3_t origin, vec3_t normal, int material ); +static void PM_CrashLandEffect( void ) +{ + if ( pm->waterlevel ) + { + return; + } + float delta = fabs(pml.previous_velocity[2])/10;//VectorLength( pml.previous_velocity );? + if ( delta >= 30 ) + { + vec3_t bottom = {pm->ps->origin[0],pm->ps->origin[1],pm->ps->origin[2]+pm->mins[2]+1}; + CG_LandingEffect( bottom, pml.groundTrace.plane.normal, (pml.groundTrace.surfaceFlags&MATERIAL_MASK) ); + } +} +/* +================= +PM_CrashLand + +Check for hard landings that generate sound events +================= +*/ +static void PM_CrashLand( void ) +{ + float delta = 0; + qboolean forceLanding = qfalse; + + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE ) + { + if ( pm->gent->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL ) + { + float dot = DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ); + //Com_Printf("%i:crashland %4.2f\n", c_pmove, pm->ps->velocity[2]); + if ( dot < -100.0f ) + { + //NOTE: never hits this anyway + if ( pm->gent->m_pVehicle->m_pVehicleInfo->iImpactFX ) + {//make sparks + if ( !Q_irand( 0, 3 ) ) + {//FIXME: debounce + G_PlayEffect( pm->gent->m_pVehicle->m_pVehicleInfo->iImpactFX, pm->ps->origin, pml.groundTrace.plane.normal ); + } + } + int damage = floor(fabs(dot+100)/10.0f); + if ( damage >= 0 ) + { + G_Damage( pm->gent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING ); + } + } + } + return; + } + + if ( (pm->ps->pm_flags&PMF_TRIGGER_PUSHED) ) + { + delta = 21;//? + forceLanding = qtrue; + } + else + { + if ( pm->gent && pm->gent->NPC && pm->gent->NPC->aiFlags & NPCAI_DIE_ON_IMPACT ) + {//have to do death on impact if we are falling to our death, FIXME: should we avoid any additional damage this func? + PM_CrashLandDamage( 1000 ); + } + else if ( pm->gent + && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_BOBAFETT||pm->gent->client->NPC_class == CLASS_ROCKETTROOPER) ) + { + if ( JET_Flying( pm->gent ) ) + { + if ( pm->gent->client->NPC_class == CLASS_BOBAFETT + || (pm->gent->client->NPC_class == CLASS_ROCKETTROOPER&&pm->gent->NPC&&pm->gent->NPC->rankgent ); + } + else + { + pm->ps->velocity[2] += Q_flrand( 100, 200 ); + } + PM_AddEvent( EV_FALL_SHORT ); + } + if ( pm->ps->forceJumpZStart ) + {//we were force-jumping + forceLanding = qtrue; + } + delta = 1; + } + else if ( pm->ps->jumpZStart && (pm->ps->forcePowerLevel[FP_LEVITATION] >= FORCE_LEVEL_1||(pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())) ) + {//we were force-jumping + if ( pm->ps->origin[2] >= pm->ps->jumpZStart ) + {//we landed at same height or higher than we landed + if ( pm->ps->forceJumpZStart ) + {//we were force-jumping + forceLanding = qtrue; + } + delta = 0; + } + else + {//take off some of it, at least + delta = (pm->ps->jumpZStart-pm->ps->origin[2]); + float dropAllow = forceJumpHeight[pm->ps->forcePowerLevel[FP_LEVITATION]]; + if ( dropAllow < 128 ) + {//always allow a drop from 128, at least + dropAllow = 128; + } + if ( delta > forceJumpHeight[FORCE_LEVEL_1] ) + {//will have to use force jump ability to absorb some of it + forceLanding = qtrue;//absorbed some - just to force the correct animation to play below + } + delta = (delta - dropAllow)/2; + } + if ( delta < 1 ) + { + delta = 1; + } + } + + if ( !delta ) + { + delta = PM_CrashLandDelta( pml.previous_velocity, pm->waterlevel ); + } + } + + PM_CrashLandEffect(); + + if ( (pm->ps->pm_flags&PMF_DUCKED) && (level.time-pm->ps->lastOnGround)>500 ) + {//must be crouched and have been inthe air for half a second minimum + if( !PM_InOnGroundAnim( pm->ps ) && !PM_InKnockDown( pm->ps ) ) + {//roll! + if ( PM_TryRoll() ) + {//absorb some impact + delta *= 0.5f; + } + } + } + + // If he just entered the water (from a fall presumably), absorb some of the impact. + if ( pm->waterlevel >= 2 ) + { + delta *= 0.4f; + } + + if ( delta < 1 ) + { + AddSoundEvent( pm->gent, pm->ps->origin, 32, AEL_MINOR, qfalse, qtrue ); + return; + } + + qboolean deadFallSound = qfalse; + if( !PM_InDeathAnim() ) + { + if ( PM_InAirKickingAnim( pm->ps->legsAnim ) + && pm->ps->torsoAnim == pm->ps->legsAnim ) + { + int anim = PM_GetLandingAnim(); + if ( anim != -1 ) + {//interrupting a kick clears everything + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100 ); // Only blend over 100ms + pm->ps->saberMove = LS_READY; + pm->ps->weaponTime = 0; + //stick landings some + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + } + } + else if ( pm->gent + && pm->gent->client + && pm->gent->client->NPC_class == CLASS_ROCKETTROOPER ) + {//rockettroopers are simpler + int anim = PM_GetLandingAnim(); + if ( anim != -1 ) + { + if ( pm->gent->NPC + && pm->gent->NPC->rank < RANK_LT ) + {//special case: ground-based rocket troopers *always* play land anim on whole body + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100 ); // Only blend over 100ms + } + else + { + PM_SetAnim( pm, SETANIM_LEGS, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100 ); // Only blend over 100ms + } + } + } + else if ( pm->cmd.upmove >= 0 && !PM_InKnockDown( pm->ps ) && !PM_InRoll( pm->ps )) + {//not crouching + if ( delta > 10 + || pm->ps->pm_flags & PMF_BACKWARDS_JUMP + || (pm->ps->forcePowersActive&(1<gent + && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_RANCOR || pm->gent->client->NPC_class == CLASS_WAMPA ) ) + { + } + else + { + int anim = PM_GetLandingAnim(); + if ( anim != -1 ) + { + if ( PM_FlippingAnim( pm->ps->torsoAnim ) + || PM_SpinningAnim( pm->ps->torsoAnim ) + || pm->ps->torsoAnim == BOTH_FLIP_LAND ) + {//interrupt these if we're going to play a land + pm->ps->torsoAnimTimer = 0; + } + if ( anim == BOTH_FORCELONGLEAP_LAND ) + { + if ( pm->gent ) + { + G_SoundOnEnt( pm->gent, CHAN_AUTO, "sound/player/slide.wav" ); + } + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100 ); // Only blend over 100ms + } + else if ( anim == BOTH_FLIP_LAND + || (pm->ps->torsoAnim == BOTH_FLIP_LAND) ) + { + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100 ); // Only blend over 100ms + } + else if ( PM_InAirKickingAnim( pm->ps->legsAnim ) + && pm->ps->torsoAnim == pm->ps->legsAnim ) + {//interrupting a kick clears everything + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100 ); // Only blend over 100ms + pm->ps->saberMove = LS_READY; + pm->ps->weaponTime = 0; + } + else if ( PM_ForceJumpingAnim( pm->ps->legsAnim ) + && pm->ps->torsoAnim == pm->ps->legsAnim ) + {//special case: if land during one of these, set the torso, too, if it's not doing something else + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100 ); // Only blend over 100ms + } + else + { + PM_SetAnim( pm, SETANIM_LEGS, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100 ); // Only blend over 100ms + } + } + } + } + } + } + else + { + pm->ps->gravity = 1.0; + //PM_CrashLandDamage( delta ); + if ( pm->gent ) + { + if ((!(pml.groundTrace.surfaceFlags & SURF_NODAMAGE)) && +// (!(pml.groundTrace.contents & CONTENTS_NODROP)) && + (!(pm->pointcontents(pm->ps->origin,pm->ps->clientNum) & CONTENTS_NODROP))) + { + if ( pm->waterlevel < 2 ) + {//don't play fallsplat when impact in the water + deadFallSound = qtrue; + if ( !(pm->ps->eFlags&EF_NODRAW) ) + {//no sound if no draw + if ( delta >= 75 ) + { + G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/player/fallsplat.wav" ); + } + else + { + G_SoundOnEnt( pm->gent, CHAN_BODY, va("sound/player/bodyfall_human%d.wav",Q_irand(1,3)) ); + } + } + } + else + { + G_SoundOnEnt( pm->gent, CHAN_BODY, va("sound/player/bodyfall_water%d.wav",Q_irand(1,3)) ); + } + if ( gi.VoiceVolume[pm->ps->clientNum] + && pm->gent->NPC && (pm->gent->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + {//I was talking, so cut it off... with a jump sound? + if ( !(pm->ps->eFlags&EF_NODRAW) ) + {//no sound if no draw + G_SoundOnEnt( pm->gent, CHAN_VOICE_ATTEN, "*pain100.wav" ); + } + } + } + } + if( pm->ps->legsAnim == BOTH_FALLDEATH1 || pm->ps->legsAnim == BOTH_FALLDEATH1INAIR) + {//FIXME: add a little bounce? + //FIXME: cut voice channel? + int old_pm_type = pm->ps->pm_type; + pm->ps->pm_type = PM_NORMAL; + //Hack because for some reason PM_SetAnim just returns if you're dead...??? + PM_SetAnim(pm, SETANIM_BOTH, BOTH_FALLDEATH1LAND, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + pm->ps->pm_type = old_pm_type; + AddSoundEvent( pm->gent, pm->ps->origin, 256, AEL_SUSPICIOUS, qfalse, qtrue ); + return; + } + } + + // create a local entity event to play the sound + + if ( pm->gent && pm->gent->client && pm->gent->client->respawnTime >= level.time - 500 ) + {//just spawned in, don't make a noise + return; + } + + if ( delta >= 75 ) + { + if ( !deadFallSound ) + { + if ( !(pm->ps->eFlags&EF_NODRAW) ) + {//no sound if no draw + PM_AddEvent( EV_FALL_FAR ); + } + } + if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) + { + PM_CrashLandDamage( delta ); + } + if ( pm->gent ) + { + if ( pm->gent->s.number == 0 ) + { + vec3_t bottom; + + VectorCopy( pm->ps->origin, bottom ); + bottom[2] += pm->mins[2]; + // if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) + { + AddSoundEvent( pm->gent, bottom, 256, AEL_SUSPICIOUS, qfalse, qtrue ); + } + } + else if ( pm->ps->stats[STAT_HEALTH] <= 0 && pm->gent && pm->gent->enemy ) + { + AddSoundEvent( pm->gent->enemy, pm->ps->origin, 256, AEL_DISCOVERED, qfalse, qtrue ); + } + } + } + else if ( delta >= 50 ) + { + // this is a pain grunt, so don't play it if dead + if ( pm->ps->stats[STAT_HEALTH] > 0 ) + { + if ( !deadFallSound ) + { + if ( !(pm->ps->eFlags&EF_NODRAW) ) + {//no sound if no draw + PM_AddEvent( EV_FALL_MEDIUM );//damage is dealt in g_active, ClientEvents + } + } + if ( pm->gent ) + { + if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) + { + PM_CrashLandDamage( delta ); + } + if ( pm->gent->s.number == 0 ) + { + vec3_t bottom; + + VectorCopy( pm->ps->origin, bottom ); + bottom[2] += pm->mins[2]; + // if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) + { + AddSoundEvent( pm->gent, bottom, 256, AEL_MINOR, qfalse, qtrue ); + } + } + } + } + } + else if ( delta >= 30 ) + { + if ( !deadFallSound ) + { + if ( !(pm->ps->eFlags&EF_NODRAW) ) + {//no sound if no draw + PM_AddEvent( EV_FALL_SHORT ); + } + } + if ( pm->gent ) + { + if ( pm->gent->s.number == 0 ) + { + vec3_t bottom; + + VectorCopy( pm->ps->origin, bottom ); + bottom[2] += pm->mins[2]; + // if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) + { + AddSoundEvent( pm->gent, bottom, 128, AEL_MINOR, qfalse, qtrue ); + } + } + else + { + if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) + { + PM_CrashLandDamage( delta ); + } + } + } + } + else + { + if ( !deadFallSound ) + { + if ( !(pm->ps->pm_flags&PMF_DUCKED) || !Q_irand( 0, 3 ) ) + {//only 25% chance of making landing alert when ducked + AddSoundEvent( pm->gent, pm->ps->origin, 32, AEL_MINOR, qfalse, qtrue ); + } + if ( !(pm->ps->eFlags&EF_NODRAW) ) + {//no sound if no draw + if ( forceLanding ) + {//we were force-jumping + PM_AddEvent( EV_FALL_SHORT ); + } + else + { +//moved to cg_player PM_AddEvent( PM_FootstepForSurface() ); + } + } + } + } + + // start footstep cycle over + pm->ps->bobCycle = 0; + if ( pm->gent && pm->gent->client ) + {//stop the force push effect when you land + pm->gent->forcePushTime = 0; + } +} + + + +/* +============= +PM_CorrectAllSolid +============= +*/ +static void PM_CorrectAllSolid( void ) { + if ( pm->debugLevel ) { + Com_Printf("%i:allsolid\n", c_pmove); //NOTENOTE: If this ever happens, I'd really like to see this print! + } + + // FIXME: jitter around + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + +qboolean FlyingCreature( gentity_t *ent ) +{ + if ( ent->client->ps.gravity <= 0 && (ent->svFlags&SVF_CUSTOM_GRAVITY) ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_RocketeersAvoidDangerousFalls( void ) +{ + if ( pm->gent->NPC + && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_BOBAFETT||pm->gent->client->NPC_class == CLASS_ROCKETTROOPER) ) + {//fixme: fall through if jetpack broken? + if ( JET_Flying( pm->gent ) ) + { + if ( pm->gent->client->NPC_class == CLASS_BOBAFETT ) + { + pm->gent->client->jetPackTime = level.time + 2000; + //Wait, what if the effect is already playing, how do we know??? + //G_PlayEffect( G_EffectIndex( "boba/jetSP" ), pm->gent->playerModel, pm->gent->genericBolt1, pm->gent->s.number, pm->gent->currentOrigin, pm->gent->client->jetPackTime-level.time ); + } + else + { + pm->gent->client->jetPackTime = Q3_INFINITE; + } + } + else + { + TIMER_Set( pm->gent, "jetRecharge", 0 ); + JET_FlyStart( pm->gent ); + } + return qtrue; + } + return qfalse; +} + +static void PM_FallToDeath( void ) +{ + if ( !pm->gent ) + { + return; + } + + if ( PM_RocketeersAvoidDangerousFalls() ) + { + return; + } + + // If this is a vehicle, more precisely an animal... + if ( pm->gent->client->NPC_class == CLASS_VEHICLE && pm->gent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL ) + { + Vehicle_t *pVeh = pm->gent->m_pVehicle; + pVeh->m_pVehicleInfo->EjectAll( pVeh ); + } + else + { + if ( PM_HasAnimation( pm->gent, BOTH_FALLDEATH1 ) ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_FALLDEATH1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_DEATH1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + } + G_SoundOnEnt( pm->gent, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN + } + + if ( pm->gent->NPC ) + { + pm->gent->NPC->aiFlags |= NPCAI_DIE_ON_IMPACT; + pm->gent->NPC->nextBStateThink = Q3_INFINITE; + } + pm->ps->friction = 1; +} + +int PM_ForceJumpAnimForJumpAnim( int anim ) +{ + switch( anim ) + { + case BOTH_JUMP1: //# Jump - wind-up and leave ground + anim = BOTH_FORCEJUMP1; //# Jump - wind-up and leave ground + break; + case BOTH_INAIR1: //# In air loop (from jump) + anim = BOTH_FORCEINAIR1; //# In air loop (from jump) + break; + case BOTH_LAND1: //# Landing (from in air loop) + anim = BOTH_FORCELAND1; //# Landing (from in air loop) + break; + case BOTH_JUMPBACK1: //# Jump backwards - wind-up and leave ground + anim = BOTH_FORCEJUMPBACK1; //# Jump backwards - wind-up and leave ground + break; + case BOTH_INAIRBACK1: //# In air loop (from jump back) + anim = BOTH_FORCEINAIRBACK1; //# In air loop (from jump back) + break; + case BOTH_LANDBACK1: //# Landing backwards(from in air loop) + anim = BOTH_FORCELANDBACK1; //# Landing backwards(from in air loop) + break; + case BOTH_JUMPLEFT1: //# Jump left - wind-up and leave ground + anim = BOTH_FORCEJUMPLEFT1; //# Jump left - wind-up and leave ground + break; + case BOTH_INAIRLEFT1: //# In air loop (from jump left) + anim = BOTH_FORCEINAIRLEFT1; //# In air loop (from jump left) + break; + case BOTH_LANDLEFT1: //# Landing left(from in air loop) + anim = BOTH_FORCELANDLEFT1; //# Landing left(from in air loop) + break; + case BOTH_JUMPRIGHT1: //# Jump right - wind-up and leave ground + anim = BOTH_FORCEJUMPRIGHT1; //# Jump right - wind-up and leave ground + break; + case BOTH_INAIRRIGHT1: //# In air loop (from jump right) + anim = BOTH_FORCEINAIRRIGHT1; //# In air loop (from jump right) + break; + case BOTH_LANDRIGHT1: //# Landing right(from in air loop) + anim = BOTH_FORCELANDRIGHT1; //# Landing right(from in air loop) + break; + } + return anim; +} + +static void PM_SetVehicleAngles( vec3_t normal ) +{ + if ( !pm->gent->client || pm->gent->client->NPC_class != CLASS_VEHICLE ) + return; + + Vehicle_t *pVeh = pm->gent->m_pVehicle; + + //float curVehicleBankingSpeed; + float vehicleBankingSpeed = pVeh->m_pVehicleInfo->bankingSpeed;//0.25f + vec3_t vAngles; + + if ( vehicleBankingSpeed <= 0 + || ( pVeh->m_pVehicleInfo->pitchLimit <= 0 && pVeh->m_pVehicleInfo->rollLimit <= 0 ) ) + {//don't bother, this vehicle doesn't bank + return; + } + //FIXME: do 3 traces to define a plane and use that... smoothes it out some, too... + //pitch_roll_for_slope( pm->gent, normal, vAngles ); + //FIXME: maybe have some pitch control in water and/or air? + + //center of gravity affects pitch in air/water (FIXME: what about roll?) + //float pitchBias = 90.0f*pVeh->m_pVehicleInfo->centerOfGravity[0];//if centerOfGravity is all the way back (-1.0f), vehicle pitches up 90 degrees when in air + + VectorClear( vAngles ); + + + if (pm->waterlevel>0 || (normal && (pml.groundTrace.contents&(CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)))) + {//in water + // vAngles[PITCH] += (pm->ps->viewangles[PITCH]-vAngles[PITCH])*0.75f + (pitchBias*0.5); + } + else if ( normal ) + {//have a valid surface below me + pitch_roll_for_slope( pm->gent, normal, vAngles ); + float deltaPitch = (vAngles[PITCH] - pVeh->m_vOrientation[PITCH]); + if (deltaPitch< -10.0f) + { + vAngles[PITCH] = pVeh->m_vOrientation[PITCH] - 10.0f; + } + else if (deltaPitch>10.0f) + { + vAngles[PITCH] = pVeh->m_vOrientation[PITCH] + 10.0f; + } + } + else + {//in air, let pitch match view...? + //FIXME: take center of gravity into account + vAngles[PITCH] = pVeh->m_vOrientation[PITCH] - 1; + if (vAngles[PITCH]<-15) + { + vAngles[PITCH]=-15; + } + //don't bank so fast when in the air + vehicleBankingSpeed *= 0.125f; + } + //NOTE: if angles are flat and we're moving through air (not on ground), + // then pitch/bank? + if (pVeh->m_ulFlags & VEH_SPINNING) + { + vAngles[ROLL] = pVeh->m_vOrientation[ROLL] - 25; + } + else if (pVeh->m_ulFlags & VEH_OUTOFCONTROL) + { + //vAngles[ROLL] = pVeh->m_vOrientation[ROLL] + 5; + } + else if ( pVeh->m_pVehicleInfo->rollLimit > 0 ) + { + //roll when banking + vec3_t velocity; + float speed; + VectorCopy( pm->ps->velocity, velocity ); + speed = VectorNormalize( velocity ); + if ( speed>0.01f ) + { + vec3_t rt, tempVAngles; + float side, dotp; + + + VectorCopy( pVeh->m_vOrientation, tempVAngles ); + tempVAngles[ROLL] = 0; + AngleVectors( tempVAngles, NULL, rt, NULL ); + dotp = DotProduct( velocity, rt ); + //if (fabsf(dotp)>0.5f) + { + speed *= dotp; + + + // Magic number fun! Speed is used for banking, so modulate the speed by a sine wave + //FIXME: this banks too early + //speed *= sin( (150 + pml.frametime) * 0.003 ); + if (level.time < pVeh->m_iTurboTime) + { + speed /= pVeh->m_pVehicleInfo->turboSpeed; + } + else + { + speed /= pVeh->m_pVehicleInfo->speedMax; + } + + /// if (pm->cmd.forwardmove==0) + // { + // speed *= 0.5; + // } + // if (pm->cmd.forwardmove<0) + // { + // speed *= 0.1f; + // } + if (pVeh->m_ulFlags & VEH_SLIDEBREAKING) + { + speed *= 3.0f; + } + + + side = speed * 75.0f; + // if (pVeh->m_ulFlags & VEH_STRAFERAM) + // { + // vAngles[ROLL] += side; + // } + // else + { + vAngles[ROLL] -= side; + } + + //gi.Printf("VAngles(%f)", vAngles[2]); + } + if (fabsf(vAngles[ROLL])<0.001f) + { + vAngles[ROLL] = 0.0f; + } + } + } + + //cap + if ( vAngles[PITCH] > pVeh->m_pVehicleInfo->pitchLimit ) + { + vAngles[PITCH] = pVeh->m_pVehicleInfo->pitchLimit; + } + else if ( vAngles[PITCH] < -pVeh->m_pVehicleInfo->pitchLimit ) + { + vAngles[PITCH] = -pVeh->m_pVehicleInfo->pitchLimit; + } + + if (!(pVeh->m_ulFlags & VEH_SPINNING)) + { + if ( vAngles[ROLL] > pVeh->m_pVehicleInfo->rollLimit ) + { + vAngles[ROLL] = pVeh->m_pVehicleInfo->rollLimit; + } + else if ( vAngles[ROLL] < -pVeh->m_pVehicleInfo->rollLimit ) + { + vAngles[ROLL] = -pVeh->m_pVehicleInfo->rollLimit; + } + } + + //do it + for ( int i = 0; i < 3; i++ ) + { + if ( i == YAW ) + {//yawing done elsewhere + continue; + } + + if ( i == ROLL && pVeh->m_ulFlags & VEH_STRAFERAM) + {//during strafe ram, roll is done elsewhere + continue; + } + //bank faster the higher the difference is + /* + else if ( i == PITCH ) + { + curVehicleBankingSpeed = vehicleBankingSpeed*fabs(AngleNormalize180(AngleSubtract( vAngles[PITCH], pVeh->m_vOrientation[PITCH] )))/(g_vehicleInfo[pm->ps->vehicleIndex].pitchLimit/2.0f); + } + else if ( i == ROLL ) + { + curVehicleBankingSpeed = vehicleBankingSpeed*fabs(AngleNormalize180(AngleSubtract( vAngles[ROLL], pVeh->m_vOrientation[ROLL] )))/(g_vehicleInfo[pm->ps->vehicleIndex].rollLimit/2.0f); + } + + if ( curVehicleBankingSpeed ) + */ + { + // if ( pVeh->m_vOrientation[i] >= vAngles[i] + vehicleBankingSpeed ) + // { + // pVeh->m_vOrientation[i] -= vehicleBankingSpeed; + // } + // else if ( pVeh->m_vOrientation[i] <= vAngles[i] - vehicleBankingSpeed ) + // { + // pVeh->m_vOrientation[i] += vehicleBankingSpeed; + // } + // else + { + pVeh->m_vOrientation[i] = vAngles[i]; + } + } + } + //gi.Printf("Orientation(%f)", pVeh->m_vOrientation[2]); +} + +void BG_ExternThisSoICanRecompileInDebug( Vehicle_t *pVeh, playerState_t *riderPS ) +{/* + float pitchSubtract, pitchDelta, yawDelta; + //Com_Printf( S_COLOR_RED"PITCH: %4.2f, YAW: %4.2f, ROLL: %4.2f\n", riderPS->viewangles[0],riderPS->viewangles[1],riderPS->viewangles[2]); + yawDelta = AngleSubtract(riderPS->viewangles[YAW],pVeh->m_vPrevRiderViewAngles[YAW]); +#ifndef QAGAME + if ( !cg_paused.integer ) + { + //Com_Printf( "%d - yawDelta %4.2f\n", pm->cmd.serverTime, yawDelta ); + } +#endif + yawDelta *= (4.0f*pVeh->m_fTimeModifier); + pVeh->m_vOrientation[ROLL] -= yawDelta; + + pitchDelta = AngleSubtract(riderPS->viewangles[PITCH],pVeh->m_vPrevRiderViewAngles[PITCH]); + pitchDelta *= (2.0f*pVeh->m_fTimeModifier); + pitchSubtract = pitchDelta * (fabs(pVeh->m_vOrientation[ROLL])/90.0f); + pVeh->m_vOrientation[PITCH] += pitchDelta-pitchSubtract; + if ( pVeh->m_vOrientation[ROLL] > 0 ) + { + pVeh->m_vOrientation[YAW] += pitchSubtract; + } + else + { + pVeh->m_vOrientation[YAW] -= pitchSubtract; + } + pVeh->m_vOrientation[PITCH] = AngleNormalize180( pVeh->m_vOrientation[PITCH] ); + pVeh->m_vOrientation[YAW] = AngleNormalize360( pVeh->m_vOrientation[YAW] ); + pVeh->m_vOrientation[ROLL] = AngleNormalize180( pVeh->m_vOrientation[ROLL] ); + + VectorCopy( riderPS->viewangles, pVeh->m_vPrevRiderViewAngles );*/ +} + +void BG_VehicleTurnRateForSpeed( Vehicle_t *pVeh, float speed, float *mPitchOverride, float *mYawOverride ) +{ + if ( pVeh && pVeh->m_pVehicleInfo ) + { + float speedFrac = 1.0f; + if ( pVeh->m_pVehicleInfo->speedDependantTurning ) + { + if ( pVeh->m_LandTrace.fraction >= 1.0f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + { + speedFrac = (speed/(pVeh->m_pVehicleInfo->speedMax*0.75f)); + if ( speedFrac < 0.25f ) + { + speedFrac = 0.25f; + } + else if ( speedFrac > 1.0f ) + { + speedFrac = 1.0f; + } + } + } + if ( pVeh->m_pVehicleInfo->mousePitch ) + { + *mPitchOverride = pVeh->m_pVehicleInfo->mousePitch*speedFrac; + } + if ( pVeh->m_pVehicleInfo->mouseYaw ) + { + *mYawOverride = pVeh->m_pVehicleInfo->mouseYaw*speedFrac; + } + } +} +/* +============= +PM_GroundTraceMissed + +The ground trace didn't hit a surface, so we are in freefall +============= +*/ +static void PM_GroundTraceMissed( void ) { + trace_t trace; + vec3_t point; + qboolean cliff_fall = qfalse; + + if ( Flying != FLY_HOVER ) + { + if ( !(pm->ps->eFlags&EF_FORCE_DRAINED) ) + { + //FIXME: if in a contents_falldeath brush, play the falling death anim and sound? + if ( pm->ps->clientNum != 0 && pm->gent && pm->gent->NPC && pm->gent->client && pm->gent->client->NPC_class != CLASS_DESANN )//desann never falls to his death + { + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + if ( pm->ps->stats[STAT_HEALTH] > 0 + && !(pm->gent->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) + && !(pm->gent->NPC->aiFlags&NPCAI_JUMP) // doing a path jump + && !(pm->gent->NPC->scriptFlags&SCF_NO_FALLTODEATH) + && pm->gent->NPC->behaviorState != BS_JUMP )//not being scripted to jump + { + if ( (level.time - pm->gent->client->respawnTime > 2000)//been in the world for at least 2 seconds + && (!pm->gent->NPC->timeOfDeath || level.time - pm->gent->NPC->timeOfDeath < 1000) && pm->gent->e_ThinkFunc != thinkF_NPC_RemoveBody //Have to do this now because timeOfDeath is used by thinkF_NPC_RemoveBody to debounce removal checks + && !(pm->gent->client->ps.forcePowersActive&(1<gent ) + && g_gravity->value > 0 ) + { + if ( !(pm->gent->flags&FL_UNDYING) + && !(pm->gent->flags&FL_GODMODE) ) + { + if ( !(pm->ps->eFlags&EF_FORCE_GRIPPED) + && !(pm->ps->eFlags&EF_FORCE_DRAINED) + && !(pm->ps->pm_flags&PMF_TRIGGER_PUSHED) ) + { + if ( !pm->ps->forceJumpZStart || pm->ps->forceJumpZStart > pm->ps->origin[2] )// && fabs(pm->ps->velocity[0])<10 && fabs(pm->ps->velocity[1])<10 && pm->ps->velocity[2]<0)//either not force-jumping or force-jumped and now fell below original jump start height + { + /*if ( pm->ps->legsAnim = BOTH_FALLDEATH1 + && pm->ps->legsAnim != BOTH_DEATH1 + && PM_HasAnimation( pm->gent, BOTH_FALLDEATH1 )*/ + //New method: predict impact, 400 ahead + vec3_t vel; + float time; + + VectorCopy( pm->ps->velocity, vel ); + float speed = VectorLength( vel ); + if ( !speed ) + {//damn divide by zero + speed = 1; + } + time = 400/speed; + vel[2] -= 0.5 * time * pm->ps->gravity; + speed = VectorLength( vel ); + if ( !speed ) + {//damn divide by zero + speed = 1; + } + time = 400/speed; + VectorScale( vel, time, vel ); + VectorAdd( pm->ps->origin, vel, point ); + + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + + if ( (trace.contents&CONTENTS_LAVA) + && PM_RocketeersAvoidDangerousFalls() ) + {//got out of it + } + else if ( !trace.allsolid && !trace.startsolid && (pm->ps->origin[2] - trace.endpos[2]) >= 128 )//>=128 so we don't die on steps! + { + if ( trace.fraction == 1.0 ) + {//didn't hit, we're probably going to die + if ( pm->ps->velocity[2] < 0 && pm->ps->origin[2] - point[2] > 256 ) + {//going down, into a bottomless pit, apparently + PM_FallToDeath(); + cliff_fall = qtrue; + } + } + else if ( trace.entityNum < ENTITYNUM_NONE + && pm->ps->weapon != WP_SABER + && (!pm->gent || !pm->gent->client || (pm->gent->client->NPC_class != CLASS_BOBAFETT&&pm->gent->client->NPC_class!=CLASS_REBORN&&pm->gent->client->NPC_class!=CLASS_ROCKETTROOPER)) ) + {//Jedi don't scream and die if they're heading for a hard impact + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( trace.entityNum == ENTITYNUM_WORLD || (traceEnt && traceEnt->bmodel) ) + {//hit architecture, find impact force + float dmg; + + VectorCopy( pm->ps->velocity, vel ); + time = Distance( trace.endpos, pm->ps->origin )/VectorLength( vel ); + vel[2] -= 0.5 * time * pm->ps->gravity; + + if ( trace.plane.normal[2] > 0.5 ) + {//use falling damage + int waterlevel, junk; + PM_SetWaterLevelAtPoint( trace.endpos, &waterlevel, &junk ); + dmg = PM_CrashLandDelta( vel, waterlevel ); + if ( dmg >= 30 ) + {//there is a minimum fall threshhold + dmg = PM_DamageForDelta( dmg ); + } + else + { + dmg = 0; + } + } + else + {//use impact damage + //guestimate + if ( pm->gent->client && pm->gent->client->ps.forceJumpZStart ) + {//we were force-jumping + if ( pm->gent->currentOrigin[2] >= pm->gent->client->ps.forceJumpZStart ) + {//we landed at same height or higher than we landed + dmg = 0; + } + else + {//FIXME: take off some of it, at least? + dmg = (pm->gent->client->ps.forceJumpZStart-pm->gent->currentOrigin[2])/3; + } + } + dmg = 10 * VectorLength( pm->ps->velocity ); + if ( pm->ps->clientNum < MAX_CLIENTS ) + { + dmg /= 2; + } + dmg *= 0.01875f;//magic number + } + float maxDmg = pm->ps->stats[STAT_HEALTH]>20?pm->ps->stats[STAT_HEALTH]:20;//a fall that would do less than 20 points of damage should never make us scream to our deaths + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_HOWLER ) + {//howlers can survive long falls, despire their health + maxDmg *= 5; + } + if ( dmg >= pm->ps->stats[STAT_HEALTH] )//armor? + { + PM_FallToDeath(); + cliff_fall = qtrue; + } + } + } + } + + /* + vec3_t start; + //okay, kind of expensive temp hack here, but let's check to see if we should scream + //FIXME: we should either do a better check (predict using actual velocity) or we should wait until they've been over a bottomless pit for a certain amount of time... + VectorCopy( pm->ps->origin, start ); + if ( pm->ps->forceJumpZStart < start[2] ) + {//Jedi who are force-jumping should only do this from landing point down? + start[2] = pm->ps->forceJumpZStart; + } + VectorCopy( start, point ); + point[2] -= 400;//320 + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + //FIXME: somehow, people can still get stuck on ledges and not splat when hit...? + if ( !trace.allsolid && !trace.startsolid && trace.fraction == 1.0 ) + { + PM_FallToDeath(); + cliff_fall = qtrue; + } + */ + } + } + } + } + } + } + } + } + if ( !cliff_fall ) + { + if ( pm->ps->legsAnim == BOTH_FORCELONGLEAP_START + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_ATTACK + || pm->ps->legsAnim == BOTH_FORCEWALLRUNFLIP_START + || pm->ps->legsAnim == BOTH_FLIP_LAND ) + {//let it stay on this anim + } + else if ( PM_KnockDownAnimExtended( pm->ps->legsAnim ) ) + {//no in-air anims when in knockdown anim + } + else if ( ( pm->ps->legsAnim == BOTH_WALL_RUN_RIGHT + || pm->ps->legsAnim == BOTH_WALL_RUN_LEFT + || pm->ps->legsAnim == BOTH_WALL_RUN_RIGHT_STOP + || pm->ps->legsAnim == BOTH_WALL_RUN_LEFT_STOP + || pm->ps->legsAnim == BOTH_WALL_RUN_RIGHT_FLIP + || pm->ps->legsAnim == BOTH_WALL_RUN_LEFT_FLIP + || pm->ps->legsAnim == BOTH_WALL_FLIP_RIGHT + || pm->ps->legsAnim == BOTH_WALL_FLIP_LEFT + || pm->ps->legsAnim == BOTH_FORCEWALLREBOUND_FORWARD + || pm->ps->legsAnim == BOTH_FORCEWALLREBOUND_LEFT + || pm->ps->legsAnim == BOTH_FORCEWALLREBOUND_BACK + || pm->ps->legsAnim == BOTH_FORCEWALLREBOUND_RIGHT + || pm->ps->legsAnim == BOTH_CEILING_DROP ) + && !pm->ps->legsAnimTimer ) + {//if flip anim is done, okay to use inair + PM_SetAnim( pm, SETANIM_LEGS, BOTH_FORCEINAIR1, SETANIM_FLAG_OVERRIDE, 350 ); // Only blend over 100ms + } + else if ( pm->ps->legsAnim == BOTH_FLIP_ATTACK7 + || pm->ps->legsAnim == BOTH_FLIP_HOLD7 ) + { + if ( !pm->ps->legsAnimTimer ) + {//done? + PM_SetAnim( pm, SETANIM_BOTH, BOTH_FLIP_HOLD7, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 350 ); // Only blend over 100ms + } + } + else if ( PM_InCartwheel( pm->ps->legsAnim ) ) + { + if ( pm->ps->legsAnimTimer > 0 ) + {//still playing on bottom + } + else + { + PM_SetAnim( pm, SETANIM_BOTH, BOTH_INAIR1, SETANIM_FLAG_NORMAL, 350 ); + } + } + else if ( PM_InAirKickingAnim( pm->ps->legsAnim ) ) + { + if ( pm->ps->legsAnimTimer > 0 ) + {//still playing on bottom + } + else + { + PM_SetAnim( pm, SETANIM_BOTH, BOTH_INAIR1, SETANIM_FLAG_NORMAL, 350 ); + pm->ps->saberMove = LS_READY; + pm->ps->weaponTime = 0; + } + } + else if ( !PM_InRoll( pm->ps ) + && !PM_SpinningAnim( pm->ps->legsAnim ) + && !PM_FlippingAnim( pm->ps->legsAnim ) + && !PM_InSpecialJump( pm->ps->legsAnim ) ) + { + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + // we just transitioned into freefall + if ( pm->debugLevel ) + { + Com_Printf("%i:lift\n", c_pmove); + } + + // if they aren't in a jumping animation and the ground is a ways away, force into it + // if we didn't do the trace, the player would be backflipping down staircases + VectorCopy( pm->ps->origin, point ); + point[2] -= 64; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 ) + {//FIXME: if velocity[2] < 0 and didn't jump, use some falling anim + if ( pm->ps->velocity[2] <= 0 && !(pm->ps->pm_flags&PMF_JUMP_HELD)) + { + if(!PM_InDeathAnim()) + { + // If we're a vehicle, set our falling flag. + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE ) + { + // We're flying in the air. + if ( pm->gent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL ) + { + pm->gent->m_pVehicle->m_ulFlags |= VEH_FLYING; + } + } + else + { + vec3_t moveDir, lookAngles, lookDir, lookRight; + int anim = BOTH_INAIR1; + + VectorCopy( pm->ps->velocity, moveDir ); + moveDir[2] = 0; + VectorNormalize( moveDir ); + + VectorCopy( pm->ps->viewangles, lookAngles ); + lookAngles[PITCH] = lookAngles[ROLL] = 0; + AngleVectors( lookAngles, lookDir, lookRight, NULL ); + + float dot = DotProduct( moveDir, lookDir ); + if ( dot > 0.5 ) + {//redundant + anim = BOTH_INAIR1; + } + else if ( dot < -0.5 ) + { + anim = BOTH_INAIRBACK1; + } + else + { + dot = DotProduct( moveDir, lookRight ); + if ( dot > 0.5 ) + { + anim = BOTH_INAIRRIGHT1; + } + else if ( dot < -0.5 ) + { + anim = BOTH_INAIRLEFT1; + } + else + {//redundant + anim = BOTH_INAIR1; + } + } + if ( pm->ps->forcePowersActive & ( 1 << FP_LEVITATION ) ) + { + anim = PM_ForceJumpAnimForJumpAnim( anim ); + } + PM_SetAnim( pm, SETANIM_LEGS, anim, SETANIM_FLAG_OVERRIDE, 100 ); // Only blend over 100ms + } + } + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else if ( !(pm->ps->forcePowersActive&(1<cmd.forwardmove >= 0 ) + { + if(!PM_InDeathAnim()) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_JUMP1,SETANIM_FLAG_OVERRIDE, 100); // Only blend over 100ms + } + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.forwardmove < 0 ) + { + if(!PM_InDeathAnim()) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_JUMPBACK1,SETANIM_FLAG_OVERRIDE, 100); // Only blend over 100ms + } + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + } + } + else + { + // If we're a vehicle, set our falling flag. + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE ) + { + // We're on the ground. + if ( pm->gent->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL ) + { + pm->gent->m_pVehicle->m_ulFlags &= ~VEH_FLYING; + } + } + } + } + } + } + } + + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + pm->ps->jumpZStart = pm->ps->origin[2]; + } + } + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + + +/* +============= +PM_GroundTrace +============= +*/ +static void PM_GroundTrace( void ) { + vec3_t point; + trace_t trace; + + if ( (pm->ps->eFlags&EF_LOCKED_TO_WEAPON) + || (pm->ps->eFlags&EF_HELD_BY_RANCOR) + || (pm->ps->eFlags&EF_HELD_BY_WAMPA) + || (pm->ps->eFlags&EF_HELD_BY_SAND_CREATURE) + || PM_RidingVehicle() ) + { + pml.groundPlane = qtrue; + pml.walking = qtrue; + pm->ps->groundEntityNum = ENTITYNUM_WORLD; + pm->ps->lastOnGround = level.time; + /* + pml.groundTrace.allsolid = qfalse; + pml.groundTrace.contents = CONTENTS_SOLID; + VectorCopy( pm->ps->origin, pml.groundTrace.endpos ); + pml.groundTrace.entityNum = ENTITYNUM_WORLD; + pml.groundTrace.fraction = 0.0f;; + pml.groundTrace.G2CollisionMap = NULL; + pml.groundTrace.plane.dist = 0.0f; + VectorSet( pml.groundTrace.plane.normal, 0, 0, 1 ); + pml.groundTrace.plane.pad = 0; + pml.groundTrace.plane.signbits = 0; + pml.groundTrace.plane.type = 0;; + pml.groundTrace.startsolid = qfalse; + pml.groundTrace.surfaceFlags = 0; + */ + return; + } + else if ( pm->ps->legsAnimTimer > 300 + && (pm->ps->legsAnim == BOTH_WALL_RUN_RIGHT + || pm->ps->legsAnim == BOTH_WALL_RUN_LEFT + || pm->ps->legsAnim == BOTH_FORCEWALLRUNFLIP_START) ) + {//wall-running forces you to be in the air + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + return; + } + + float minNormal = (float)MIN_WALK_NORMAL; + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE ) + {//FIXME: extern this as part of vehicle data + minNormal = pm->gent->m_pVehicle->m_pVehicleInfo->maxSlope; + } + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25f; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if ( trace.allsolid ) { + PM_CorrectAllSolid(); + return; + } + + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0 || g_gravity->value <= 0 ) + { + PM_GroundTraceMissed(); + pml.groundPlane = qfalse; + pml.walking = qfalse; + /* + if ( pm->ps->vehicleIndex != VEHICLE_NONE ) + { + PM_SetVehicleAngles( NULL ); + } + */ + return; + } + + // Not a vehicle and not riding one. + if ( pm->gent + && pm->gent->client + && pm->gent->client->NPC_class != CLASS_SAND_CREATURE + && (pm->gent->client->NPC_class != CLASS_VEHICLE && !PM_RidingVehicle() ) ) + { + // check if getting thrown off the ground + if ( ((pm->ps->velocity[2]>0&&(pm->ps->pm_flags&PMF_TIME_KNOCKBACK))||pm->ps->velocity[2]>100) && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) + {//either thrown off ground (PMF_TIME_KNOCKBACK) or going off the ground at a large velocity + if ( pm->debugLevel ) { + Com_Printf("%i:kickoff\n", c_pmove); + } + // go into jump animation + if ( PM_FlippingAnim( pm->ps->legsAnim) ) + {//we're flipping + } + else if ( PM_InSpecialJump( pm->ps->legsAnim ) ) + {//special jumps + } + else if ( PM_InKnockDown( pm->ps ) ) + {//in knockdown + } + else if ( PM_InRoll( pm->ps ) ) + {//in knockdown + } + else if ( PM_KickingAnim( pm->ps->legsAnim ) ) + {//in kick + } + else if ( pm->gent + && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_RANCOR || pm->gent->client->NPC_class == CLASS_WAMPA) ) + { + } + else + { + PM_JumpForDir(); + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + } + + /* + if ( pm->ps->vehicleIndex != VEHICLE_NONE ) + { + PM_SetVehicleAngles( trace.plane.normal ); + } + */ + + // slopes that are too steep will not be considered onground + if ( trace.plane.normal[2] < minNormal ) { + if ( pm->debugLevel ) { + Com_Printf("%i:steep\n", c_pmove); + } + // FIXME: if they can't slide down the slope, let them + // walk (sharp crevices) + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qtrue; + pml.walking = qfalse; + return; + } + + //FIXME: if the ground surface is a "cover surface (like tall grass), add a "cover" flag to me + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // hitting solid ground will end a waterjump + if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) + { + pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); + pm->ps->pm_time = 0; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + // just hit the ground + if ( pm->debugLevel ) { + Com_Printf("%i:Land\n", c_pmove); + } + + //if ( !PM_ClientImpact( trace.entityNum, qtrue ) ) + { + PM_CrashLand(); + + // don't do landing time if we were just going down a slope + if ( pml.previous_velocity[2] < -200 ) { + // don't allow another jump for a little while + pm->ps->pm_flags |= PMF_TIME_LAND; + pm->ps->pm_time = 250; + } + if (!pm->cmd.forwardmove && !pm->cmd.rightmove) { + if ( Flying != FLY_HOVER ) + { + pm->ps->velocity[2] = 0; //wouldn't normally want this because of slopes, but we aren't tyring to move... + } + } + } + } + + pm->ps->groundEntityNum = trace.entityNum; + pm->ps->lastOnGround = level.time; + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + {//if a player, clear the jumping "flag" so can't double-jump + pm->ps->forceJumpCharge = 0; + } + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + PM_AddTouchEnt( trace.entityNum ); +} + +int LastMatrixJumpTime = 0; +#define DEBUGMATRIXJUMP 0 + +void PM_HoverTrace( void ) +{ + if ( !pm->gent || !pm->gent->client || pm->gent->client->NPC_class != CLASS_VEHICLE ) + { + return; + } + + Vehicle_t *pVeh = pm->gent->m_pVehicle; + float hoverHeight = pVeh->m_pVehicleInfo->hoverHeight; + vec3_t point, vAng, fxAxis[3]; + trace_t *trace = &pml.groundTrace; + int traceContents = pm->tracemask; + + pml.groundPlane = qfalse; + + float relativeWaterLevel = (pm->ps->waterheight - (pm->ps->origin[2]+pm->mins[2])); + if ( pm->waterlevel && relativeWaterLevel >= 0 ) + {//in water + if ( pVeh->m_pVehicleInfo->bouyancy <= 0.0f ) + {//sink like a rock + } + else + {//rise up + float floatHeight = (pVeh->m_pVehicleInfo->bouyancy * ((pm->maxs[2]-pm->mins[2])*0.5f)) - (hoverHeight*0.5f);//1.0f should make you float half-in, half-out of water + if ( relativeWaterLevel > floatHeight ) + {//too low, should rise up + pm->ps->velocity[2] += (relativeWaterLevel - floatHeight) * pVeh->m_fTimeModifier; + } + } + if ( pm->ps->waterheight < pm->ps->origin[2]+pm->maxs[2] ) + {//part of us is sticking out of water + if ( fabs(pm->ps->velocity[0]) + fabs(pm->ps->velocity[1]) > 100 ) + {//moving at a decent speed + if ( Q_irand( pml.frametime, 100 ) >= 50 ) + {//splash + vAng[PITCH] = vAng[ROLL] = 0; + vAng[YAW] = pVeh->m_vOrientation[YAW]; + AngleVectors( vAng, fxAxis[2], fxAxis[1], fxAxis[0] ); + vec3_t wakeOrg; + VectorCopy( pm->ps->origin, wakeOrg ); + wakeOrg[2] = pm->ps->waterheight; + if ( pVeh->m_pVehicleInfo->iWakeFX ) + { + G_PlayEffect( pVeh->m_pVehicleInfo->iWakeFX, wakeOrg, fxAxis ); + } + } + } + pml.groundPlane = qtrue; + } + } + else + { + float minNormal = (float)MIN_WALK_NORMAL; + minNormal = pVeh->m_pVehicleInfo->maxSlope; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - (hoverHeight*3.0f); + + //FIXME: check for water, too? If over water, go slower and make wave effect + // If *in* water, go really slow and use bouyancy stat to determine how far below surface to float + + //NOTE: if bouyancy is 2.0f or higher, you float over water like it's solid ground. + // if it's 1.0f, you sink halfway into water. If it's 0, you sink... + if ( pVeh->m_pVehicleInfo->bouyancy >= 2.0f ) + {//sit on water + traceContents |= (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); + } + pm->trace( trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, traceContents ); + if ( trace->plane.normal[2] >= minNormal ) + {//not a steep slope, so push us up + if ( trace->fraction < 0.3f ) + {//push up off ground + float hoverForce = pVeh->m_pVehicleInfo->hoverStrength; + pm->ps->velocity[2] += (0.3f-trace->fraction)*hoverForce*pVeh->m_fTimeModifier; + + // if (pm->ps->velocity[2]>60.0f) + // { + // pm->ps->velocity[2] = 60.0f; + // } + + if ( (trace->contents&(CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) ) + {//hovering on water, make a spash if moving + if ( fabs(pm->ps->velocity[0]) + fabs(pm->ps->velocity[1]) > 100 ) + {//moving at a decent speed + if ( Q_irand( pml.frametime, 100 ) >= 50 ) + {//splash + vAng[PITCH] = vAng[ROLL] = 0; + vAng[YAW] = pVeh->m_vOrientation[YAW]; + AngleVectors( vAng, fxAxis[2], fxAxis[1], fxAxis[0] ); + if ( pVeh->m_pVehicleInfo->iWakeFX ) + { + G_PlayEffect( pVeh->m_pVehicleInfo->iWakeFX, trace->endpos, fxAxis ); + } + } + } + } + + if (pVeh->m_ulFlags & VEH_SLIDEBREAKING) + { + if ( Q_irand( pml.frametime, 100 ) >= 50 ) + {//dust + VectorClear(fxAxis[0]); + fxAxis[0][2] = 1; + + VectorCopy(pm->ps->velocity, fxAxis[1]); + fxAxis[1][2] *= 0.01f; + VectorMA(pm->ps->origin, 0.25f, fxAxis[1], point); + G_PlayEffect("ships/swoop_dust", point, fxAxis[0]); + } + } + pml.groundPlane = qtrue; + } + } + } + if ( pml.groundPlane ) + { + PM_SetVehicleAngles( pml.groundTrace.plane.normal ); + // We're on the ground. +// if (pVeh->m_ulFlags&VEH_FLYING && level.timem_iTurboTime) +// { +// pVeh->m_iTurboTime = 0; // stop turbo +// } + pVeh->m_ulFlags &= ~VEH_FLYING; + pVeh->m_vAngularVelocity = 0.0f; + } + else + { + PM_SetVehicleAngles( NULL ); + // We're flying in the air. + pVeh->m_ulFlags |= VEH_FLYING; + //groundTrace + + if (pVeh->m_vAngularVelocity==0.0f) + { + pVeh->m_vAngularVelocity = pVeh->m_vOrientation[YAW] - pVeh->m_vPrevOrientation[YAW]; + if (pVeh->m_vAngularVelocity<-15.0f) + { + pVeh->m_vAngularVelocity = -15.0f; + } + if (pVeh->m_vAngularVelocity> 15.0f) + { + pVeh->m_vAngularVelocity = 15.0f; + } + + // BEGIN MATRIX MODE INIT FOR JUMP + //================================= + if (pm->gent && + pm->gent->owner && + (pm->gent->owner->s.numbergent->owner)) && + pVeh->m_pVehicleInfo->type==VH_SPEEDER && + level.time>(LastMatrixJumpTime+5000) && VectorLength(pm->ps->velocity)>30.0f) + { + LastMatrixJumpTime = level.time; + vec3_t predictedApx; + vec3_t predictedFallVelocity; + vec3_t predictedLandPosition; + + VectorScale(pm->ps->velocity, 2.0f, predictedFallVelocity); // take friction into account + predictedFallVelocity[2] = -(pm->ps->gravity * 1.1f); // take gravity into account + + VectorMA(pm->ps->origin, 0.25f, pm->ps->velocity, predictedApx); + VectorMA(predictedApx, 0.25f, predictedFallVelocity, predictedLandPosition); + + + + + trace_t trace2; + gi.trace( &trace2, predictedApx, pm->mins, pm->maxs, predictedLandPosition, pm->ps->clientNum, traceContents); + if (!trace2.startsolid && !trace2.allsolid && trace2.fraction>0.75 && Q_irand(0, 3)==0) + { + LastMatrixJumpTime += 20000; + G_StartMatrixEffect(pm->gent, MEF_HIT_GROUND_STOP); +// CG_DrawEdge(pm->ps->origin, predictedApx, EDGE_WHITE_TWOSECOND); +// CG_DrawEdge(predictedApx, predictedLandPosition, EDGE_WHITE_TWOSECOND); + } +// else +// { +// CG_DrawEdge(pm->ps->origin, predictedApx, EDGE_RED_TWOSECOND); +// CG_DrawEdge(predictedApx, predictedLandPosition, EDGE_RED_TWOSECOND); +// } + } + //================================= + } + pVeh->m_vAngularVelocity *= 0.95f; // Angular Velocity Decays Over Time + } + PM_GroundTraceMissed(); +} + +#ifdef _XBOX + +short npcsToUpdate[MAX_NPC_WATER_UPDATE]; // queue of npcs +short npcsToUpdateTop = 0; // top of the queue +short npcsToUpdateCount = 0; // number of npcs in the queue + + +/* +============= +PM_SetWaterLevelAtPoint2 +Xbox version does not depend on a pmove structure +This function is used to update AI waterlevels every few +frames. +============= +*/ +static void PM_SetWaterLevelAtPoint2( vec3_t org, int *waterlevel, int *watertype, int clientNum, int viewHeight ) +{ + vec3_t point; + int cont; + int sample1; + int sample2; + + // use the server fucntion directly, we don't have a pmove ptr to work with right now + extern int SV_PointContents( const vec3_t p, int passEntityNum ); + + // + // get waterlevel, accounting for ducking + // + *waterlevel = 0; + *watertype = 0; + + point[0] = org[0]; + point[1] = org[1]; + point[2] = org[2] + DEFAULT_MINS_2 + 1; + cont = SV_PointContents( point, clientNum ); + + if ( cont & (MASK_WATER|CONTENTS_LADDER) ) + { + sample2 = viewHeight - DEFAULT_MINS_2; + sample1 = sample2 / 2; + + *watertype = cont; + *waterlevel = 1; + point[2] = org[2] + DEFAULT_MINS_2 + sample1; + cont = SV_PointContents( point, clientNum ); + if ( cont & (MASK_WATER|CONTENTS_LADDER) ) + { + *waterlevel = 2; + point[2] = org[2] + DEFAULT_MINS_2 + sample2; + cont = SV_PointContents( point, clientNum ); + if ( cont & (MASK_WATER|CONTENTS_LADDER) ) + { + *waterlevel = 3; + } + } + } +} + +/* +============= +AddNPCToWaterUpdate +This function adds an AI to the water level update queue +============= +*/ +static void AddNPCToWaterUpdate(int num) +{ + // get an entity pointer + gentity_t *ent = g_entities + num; + + // only add this client if it isn't already queued + if(ent->wupdate == 0) + { + // check to make sure we don't have too many NPCs in the queue + if((npcsToUpdateCount + 1) < MAX_NPC_WATER_UPDATE) + { + // figure out where to place this npc in the queue + int spot = npcsToUpdateTop + npcsToUpdateCount; + + if(spot < MAX_NPC_WATER_UPDATE) + { + npcsToUpdate[spot] = num; + } + else + { + npcsToUpdate[spot - MAX_NPC_WATER_UPDATE] = num; + } + + // set the water update flag + ent->wupdate = 1; + + // update the queue count + npcsToUpdateCount++; + } + else + { + // the queue isn't big enough + assert(0); + } + } +} + +/* +============= +UpdateNPCWaterLevels +This function updates the water level for MAX_NPC_WATER_UPDATES_PER_FRAME NPCs +============= +*/ +void UpdateNPCWaterLevels(void) +{ + int i; + gentity_t *ent; + + // update the maxium number per frame + for(i = 0; i < MAX_NPC_WATER_UPDATES_PER_FRAME; i++) + { + // make sure we have something to update + if(npcsToUpdateCount) + { + // if the clientnum is -1, we've got some serious problems + assert(npcsToUpdate[npcsToUpdateTop] != -1); + + // get an enitiy pointer + ent = g_entities + npcsToUpdate[npcsToUpdateTop]; + + // make sure this client isn't in Jedi heaven + if(ent->client) + { + // set the previous water level... this is used later to update the water state + ent->prev_waterlevel = ent->waterlevel; + // get our new state + PM_SetWaterLevelAtPoint2( ent->currentOrigin, &ent->waterlevel, &ent->watertype, npcsToUpdate[npcsToUpdateTop], ent->client->ps.viewheight ); + // flag this client as updated + ent->wupdate = 0; + } + + // clear this client from the queue + npcsToUpdate[npcsToUpdateTop] = -1; + // move our queue ptr and decr our count + npcsToUpdateTop++; + npcsToUpdateCount--; + + // if the top is pointing to the end wrap it around the the start + if(npcsToUpdateTop == MAX_NPC_WATER_UPDATE) + { + npcsToUpdateTop = 0; + } + } + } +} + +#endif // _XBOX + +/* +============= +PM_SetWaterLevelAtPoint FIXME: avoid this twice? certainly if not moving +============= +*/ +static void PM_SetWaterLevelAtPoint( vec3_t org, int *waterlevel, int *watertype ) +{ + vec3_t point; + int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + *waterlevel = 0; + *watertype = 0; + + point[0] = org[0]; + point[1] = org[1]; + point[2] = org[2] + DEFAULT_MINS_2 + 1; + if (gi.totalMapContents() & (MASK_WATER|CONTENTS_LADDER)) + { + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if ( cont & (MASK_WATER|CONTENTS_LADDER) ) + { + sample2 = pm->ps->viewheight - DEFAULT_MINS_2; + sample1 = sample2 / 2; + + *watertype = cont; + *waterlevel = 1; + point[2] = org[2] + DEFAULT_MINS_2 + sample1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + if ( cont & (MASK_WATER|CONTENTS_LADDER) ) + { + *waterlevel = 2; + point[2] = org[2] + DEFAULT_MINS_2 + sample2; + cont = pm->pointcontents( point, pm->ps->clientNum ); + if ( cont & (MASK_WATER|CONTENTS_LADDER) ) + { + *waterlevel = 3; + } + } + } + } +} + +void PM_SetWaterHeight( void ) +{ + pm->ps->waterHeightLevel = WHL_NONE; + if ( pm->waterlevel < 1 ) + { + pm->ps->waterheight = pm->ps->origin[2] + DEFAULT_MINS_2 - 4; + return; + } + trace_t trace; + vec3_t top, bottom; + + VectorCopy( pm->ps->origin, top ); + VectorCopy( pm->ps->origin, bottom ); + top[2] += pm->gent->client->standheight; + bottom[2] += DEFAULT_MINS_2; + + gi.trace( &trace, top, pm->mins, pm->maxs, bottom, pm->ps->clientNum, MASK_WATER ); + + if ( trace.startsolid ) + {//under water + pm->ps->waterheight = top[2] + 4; + } + else if ( trace.fraction < 1.0f ) + {//partially in and partially out of water + pm->ps->waterheight = trace.endpos[2]+pm->mins[2]; + } + else if ( trace.contents&MASK_WATER ) + {//water is above me + pm->ps->waterheight = top[2] + 4; + } + else + {//water is below me + pm->ps->waterheight = bottom[2] - 4; + } + float distFromEyes = (pm->ps->origin[2]+pm->gent->client->standheight)-pm->ps->waterheight; + + if ( distFromEyes < 0 ) + { + pm->ps->waterHeightLevel = WHL_UNDER; + } + else if ( distFromEyes < 6 ) + { + pm->ps->waterHeightLevel = WHL_HEAD; + } + else if ( distFromEyes < 18 ) + { + pm->ps->waterHeightLevel = WHL_SHOULDERS; + } + else if ( distFromEyes < pm->gent->client->standheight-8 ) + {//at least 8 above origin + pm->ps->waterHeightLevel = WHL_TORSO; + } + else + { + float distFromOrg = pm->ps->origin[2]-pm->ps->waterheight; + if ( distFromOrg < 6 ) + { + pm->ps->waterHeightLevel = WHL_WAIST; + } + else if ( distFromOrg < 16 ) + { + pm->ps->waterHeightLevel = WHL_KNEES; + } + else if ( distFromOrg > fabs(pm->mins[2]) ) + { + pm->ps->waterHeightLevel = WHL_NONE; + } + else + { + pm->ps->waterHeightLevel = WHL_ANKLES; + } + } +} + + +/* +============== +PM_SetBounds + +Sets mins, maxs +============== +*/ +static void PM_SetBounds (void) +{ + if ( pm->gent && pm->gent->client ) + { + if ( !pm->gent->mins[0] || !pm->gent->mins[1] || !pm->gent->mins[2] || !pm->gent->maxs[0] || !pm->gent->maxs[1] || !pm->gent->maxs[2] ) + { + //assert(0); + } + + VectorCopy( pm->gent->mins, pm->mins ); + VectorCopy( pm->gent->maxs, pm->maxs ); + } + else + { + if ( !DEFAULT_MINS_0 || !DEFAULT_MINS_1 || !DEFAULT_MAXS_0 || !DEFAULT_MAXS_1 || !DEFAULT_MINS_2 || !DEFAULT_MAXS_2 ) + { + assert(0); + } + + pm->mins[0] = DEFAULT_MINS_0; + pm->mins[1] = DEFAULT_MINS_1; + + pm->maxs[0] = DEFAULT_MAXS_0; + pm->maxs[1] = DEFAULT_MAXS_1; + + pm->mins[2] = DEFAULT_MINS_2; + pm->maxs[2] = DEFAULT_MAXS_2; + } +} + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->ps->viewheight +============== +*/ +static void PM_CheckDuck (void) +{ + trace_t trace; + int standheight; + int crouchheight; + int oldHeight; + + if ( pm->gent && pm->gent->client ) + { + if ( !pm->gent->mins[0] || !pm->gent->mins[1] || !pm->gent->mins[2] || !pm->gent->maxs[0] || !pm->gent->maxs[1] || !pm->gent->maxs[2] ) + { + //assert(0); + } + + if ( pm->ps->clientNum < MAX_CLIENTS + && (pm->gent->client->NPC_class == CLASS_ATST ||pm->gent->client->NPC_class == CLASS_RANCOR) + && !cg.renderingThirdPerson ) + { + standheight = crouchheight = 128; + } + else + { + standheight = pm->gent->client->standheight; + crouchheight = pm->gent->client->crouchheight; + } + } + else + { + if ( !DEFAULT_MINS_0 || !DEFAULT_MINS_1 || !DEFAULT_MAXS_0 || !DEFAULT_MAXS_1 || !DEFAULT_MINS_2 || !DEFAULT_MAXS_2 ) + { + assert(0); + } + + standheight = DEFAULT_MAXS_2; + crouchheight = CROUCH_MAXS_2; + } + + if ( PM_RidingVehicle() || (pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE) ) + {//riding a vehicle or are a vehicle + //no ducking or rolling when on a vehicle + //right? not even on ones that you just ride on top of? + pm->ps->pm_flags &= ~PMF_DUCKED; + pm->maxs[2] = standheight; + //FIXME: have a crouchviewheight and standviewheight on ent? + pm->ps->viewheight = standheight + STANDARD_VIEWHEIGHT_OFFSET;//DEFAULT_VIEWHEIGHT; + //NOTE: we don't clear the pm->cmd.upmove here because + //the vehicle code may need it later... but, for riders, + //it should have already been copied over to the vehicle, right? + return; + } + + if ( PM_InGetUp( pm->ps ) ) + {//can't do any kind of crouching when getting up + if ( pm->ps->legsAnim == BOTH_GETUP_CROUCH_B1 || pm->ps->legsAnim == BOTH_GETUP_CROUCH_F1 ) + {//crouched still + pm->ps->pm_flags |= PMF_DUCKED; + pm->maxs[2] = crouchheight; + } + pm->ps->viewheight = crouchheight + STANDARD_VIEWHEIGHT_OFFSET; + return; + } + + oldHeight = pm->maxs[2]; + + if ( PM_InRoll( pm->ps ) ) + { + /* + if ( pm->ps->clientNum && pm->gent && pm->gent->client ) + { + pm->maxs[2] = pm->gent->client->renderInfo.eyePoint[2]-pm->ps->origin[2] + 4; + if ( crouchheight > pm->maxs[2] ) + { + pm->maxs[2] = crouchheight; + } + } + else + */ + { + pm->maxs[2] = crouchheight; + } + pm->ps->viewheight = crouchheight + STANDARD_VIEWHEIGHT_OFFSET; + pm->ps->pm_flags |= PMF_DUCKED; + return; + } + if ( PM_GettingUpFromKnockDown( standheight, crouchheight ) ) + { + pm->ps->viewheight = crouchheight + STANDARD_VIEWHEIGHT_OFFSET; + return; + } + if ( PM_InKnockDown( pm->ps ) ) + {//forced crouch + if ( pm->gent && pm->gent->client ) + {//interrupted any potential delayed weapon fires + pm->gent->client->fireDelay = 0; + } + pm->maxs[2] = crouchheight; + pm->ps->viewheight = crouchheight + STANDARD_VIEWHEIGHT_OFFSET; + pm->ps->pm_flags |= PMF_DUCKED; + return; + } + if ( pm->cmd.upmove < 0 ) + { // trying to duck + pm->maxs[2] = crouchheight; + pm->ps->viewheight = crouchheight + STANDARD_VIEWHEIGHT_OFFSET;//CROUCH_VIEWHEIGHT; + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE && !PM_SwimmingAnim( pm->ps->legsAnim ) ) + {//Not ducked already and trying to duck in mid-air + //will raise your feet, unducking whilst in air will drop feet + if ( !(pm->ps->pm_flags&PMF_DUCKED) ) + { + pm->ps->eFlags ^= EF_TELEPORT_BIT; + } + if ( pm->gent ) + { + pm->ps->origin[2] += oldHeight - pm->maxs[2];//diff will be zero if were already ducking + //Don't worry, we know we fit in a smaller size + } + } + pm->ps->pm_flags |= PMF_DUCKED; + if ( d_JediAI->integer ) + { + if ( pm->ps->clientNum && pm->ps->weapon == WP_SABER ) + { + Com_Printf( "ducking\n" ); + } + } + } + else + { // want to stop ducking, stand up if possible + if ( pm->ps->pm_flags & PMF_DUCKED ) + {//Was ducking + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//unducking whilst in air will try to drop feet + pm->maxs[2] = standheight; + pm->ps->origin[2] += oldHeight - pm->maxs[2]; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if ( !trace.allsolid ) + { + pm->ps->eFlags ^= EF_TELEPORT_BIT; + pm->ps->pm_flags &= ~PMF_DUCKED; + } + else + {//Put us back + pm->ps->origin[2] -= oldHeight - pm->maxs[2]; + } + //NOTE: this isn't the best way to check this, you may have room to unduck + //while in air, but your feet are close to landing. Probably won't be a + //noticable shortcoming + } + else + { + // try to stand up + pm->maxs[2] = standheight; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if ( !trace.allsolid ) + { + pm->ps->pm_flags &= ~PMF_DUCKED; + } + } + } + + if ( pm->ps->pm_flags & PMF_DUCKED ) + {//Still ducking + pm->maxs[2] = crouchheight; + pm->ps->viewheight = crouchheight + STANDARD_VIEWHEIGHT_OFFSET;//CROUCH_VIEWHEIGHT; + } + else + {//standing now + pm->maxs[2] = standheight; + //FIXME: have a crouchviewheight and standviewheight on ent? + pm->ps->viewheight = standheight + STANDARD_VIEWHEIGHT_OFFSET;//DEFAULT_VIEWHEIGHT; + } + } +} + + + +//=================================================================== +qboolean PM_SaberLockAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_BF2LOCK: //# + case BOTH_BF1LOCK: //# + case BOTH_CWCIRCLELOCK: //# + case BOTH_CCWCIRCLELOCK: //# + return qtrue; + } + return qfalse; +} + +qboolean PM_ForceAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_CHOKE1: //being choked...??? + case BOTH_GESTURE1: //taunting... + case BOTH_RESISTPUSH: //# plant yourself to resist force push/pulls. + case BOTH_FORCEPUSH: //# Use off-hand to do force power. + case BOTH_FORCEPULL: //# Use off-hand to do force power. + case BOTH_MINDTRICK1: //# Use off-hand to do mind trick + case BOTH_MINDTRICK2: //# Use off-hand to do distraction + case BOTH_FORCELIGHTNING: //# Use off-hand to do lightning + case BOTH_FORCELIGHTNING_START: + case BOTH_FORCELIGHTNING_HOLD: //# Use off-hand to do lightning + case BOTH_FORCELIGHTNING_RELEASE: //# Use off-hand to do lightning + case BOTH_FORCEHEAL_START: //# Healing meditation pose start + case BOTH_FORCEHEAL_STOP: //# Healing meditation pose end + case BOTH_FORCEHEAL_QUICK: //# Healing meditation gesture + case BOTH_FORCEGRIP1: //# temp force-grip anim (actually re-using push) + case BOTH_FORCEGRIP_HOLD: //# temp force-grip anim (actually re-using push) + case BOTH_FORCEGRIP_RELEASE: //# temp force-grip anim (actually re-using push) + //case BOTH_FORCEGRIP3: //# force-gripping + case BOTH_FORCE_RAGE: + case BOTH_FORCE_2HANDEDLIGHTNING: + case BOTH_FORCE_2HANDEDLIGHTNING_START: + case BOTH_FORCE_2HANDEDLIGHTNING_HOLD: + case BOTH_FORCE_2HANDEDLIGHTNING_RELEASE: + case BOTH_FORCE_DRAIN: + case BOTH_FORCE_DRAIN_START: + case BOTH_FORCE_DRAIN_HOLD: + case BOTH_FORCE_DRAIN_RELEASE: + case BOTH_FORCE_ABSORB: + case BOTH_FORCE_ABSORB_START: + case BOTH_FORCE_ABSORB_END: + case BOTH_FORCE_PROTECT: + case BOTH_FORCE_PROTECT_FAST: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_InSaberAnim( int anim ) +{ + if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_H1_S1_BR ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_InForceGetUp( playerState_t *ps ) +{ + switch ( ps->legsAnim ) + { + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_FORCE_GETUP_B6: + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + if ( ps->legsAnimTimer ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean PM_InGetUp( playerState_t *ps ) +{ + switch ( ps->legsAnim ) + { + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_GETUP_CROUCH_F1: + case BOTH_GETUP_CROUCH_B1: + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + if ( ps->legsAnimTimer ) + { + return qtrue; + } + break; + default: + return PM_InForceGetUp( ps ); + break; + } + //what the hell, redundant, but... + return qfalse; +} + +qboolean PM_InGetUpNoRoll( playerState_t *ps ) +{ + switch ( ps->legsAnim ) + { + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_GETUP_CROUCH_F1: + case BOTH_GETUP_CROUCH_B1: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_FORCE_GETUP_B6: + if ( ps->legsAnimTimer ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean PM_InKnockDown( playerState_t *ps ) +{ + switch ( ps->legsAnim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + //special anims: + case BOTH_RELEASED: + return qtrue; + break; + case BOTH_LK_DL_ST_T_SB_1_L: + if ( ps->legsAnimTimer < 550 ) + { + return qtrue; + } + break; + case BOTH_PLAYER_PA_3_FLY: + if ( ps->legsAnimTimer < 300 ) + { + return qtrue; + } + /* + else if ( ps->clientNum < MAX_CLIENTS + && ps->legsAnimTimer < 300 + PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME ) + { + return qtrue; + } + */ + break; + default: + return PM_InGetUp( ps ); + break; + } + return qfalse; +} + +qboolean PM_InKnockDownNoGetup( playerState_t *ps ) +{ + switch ( ps->legsAnim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + //special anims: + case BOTH_RELEASED: + return qtrue; + break; + case BOTH_LK_DL_ST_T_SB_1_L: + if ( ps->legsAnimTimer < 550 ) + { + return qtrue; + } + break; + case BOTH_PLAYER_PA_3_FLY: + if ( ps->legsAnimTimer < 300 ) + { + return qtrue; + } + /* + else if ( ps->clientNum < MAX_CLIENTS + && ps->legsAnimTimer < 300 + PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME ) + { + return qtrue; + } + */ + break; + } + return qfalse; +} + +qboolean PM_InKnockDownOnGround( playerState_t *ps ) +{ + switch ( ps->legsAnim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + case BOTH_RELEASED: + //if ( PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)ps->legsAnim ) - ps->legsAnimTimer > 300 ) + {//at end of fall down anim + return qtrue; + } + break; + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_GETUP_CROUCH_F1: + case BOTH_GETUP_CROUCH_B1: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_FORCE_GETUP_B6: + if ( PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)ps->legsAnim ) - ps->legsAnimTimer < 500 ) + {//at beginning of getup anim + return qtrue; + } + break; + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + if ( PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)ps->legsAnim ) - ps->legsAnimTimer < 500 ) + {//at beginning of getup anim + return qtrue; + } + break; + case BOTH_LK_DL_ST_T_SB_1_L: + if ( ps->legsAnimTimer < 1000 ) + { + return qtrue; + } + break; + case BOTH_PLAYER_PA_3_FLY: + if ( ps->legsAnimTimer < 300 ) + { + return qtrue; + } + /* + else if ( ps->clientNum < MAX_CLIENTS + && ps->legsAnimTimer < 300 + PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME ) + { + return qtrue; + } + */ + break; + } + return qfalse; +} + +qboolean PM_CrouchGetup( float crouchheight ) +{ + pm->maxs[2] = crouchheight; + pm->ps->viewheight = crouchheight + STANDARD_VIEWHEIGHT_OFFSET; + int anim = -1; + switch ( pm->ps->legsAnim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN4: + case BOTH_RELEASED: + case BOTH_PLAYER_PA_3_FLY: + anim = BOTH_GETUP_CROUCH_B1; + break; + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN5: + case BOTH_LK_DL_ST_T_SB_1_L: + anim = BOTH_GETUP_CROUCH_F1; + break; + } + if ( anim == -1 ) + {//WTF? stay down? + pm->ps->legsAnimTimer = 100;//hold this anim for another 10th of a second + return qfalse; + } + else + {//get up into crouch anim + if ( PM_LockedAnim( pm->ps->torsoAnim ) ) + {//need to be able to override this anim + pm->ps->torsoAnimTimer = 0; + } + if ( PM_LockedAnim( pm->ps->legsAnim ) ) + {//need to be able to override this anim + pm->ps->legsAnimTimer = 0; + } + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS ); + pm->ps->saberMove = pm->ps->saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + pm->ps->saberBlocked = BLOCKED_NONE; + return qtrue; + } +} + +extern qboolean PM_GoingToAttackDown( playerState_t *ps ); +qboolean PM_CheckRollGetup( void ) +{ + if ( pm->ps->legsAnim == BOTH_KNOCKDOWN1 + || pm->ps->legsAnim == BOTH_KNOCKDOWN2 + || pm->ps->legsAnim == BOTH_KNOCKDOWN3 + || pm->ps->legsAnim == BOTH_KNOCKDOWN4 + || pm->ps->legsAnim == BOTH_KNOCKDOWN5 + || pm->ps->legsAnim == BOTH_LK_DL_ST_T_SB_1_L + || pm->ps->legsAnim == BOTH_PLAYER_PA_3_FLY + || pm->ps->legsAnim == BOTH_RELEASED ) + {//lying on back or front + if ( ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && ( pm->cmd.rightmove || (pm->cmd.forwardmove&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) ) )//player pressing left or right + || ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) && pm->gent->NPC//an NPC + && pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0//have at least force jump 1 + && pm->gent->enemy //I have an enemy + && pm->gent->enemy->client//a client + && pm->gent->enemy->enemy == pm->gent//he's mad at me! + && (PM_GoingToAttackDown( &pm->gent->enemy->client->ps )||!Q_irand(0,2))//he's attacking downward! (or we just feel like doing it this time) + && ((pm->gent->client&&pm->gent->client->NPC_class==CLASS_ALORA)||Q_irand( 0, RANK_CAPTAIN )gent->NPC->rank) ) )//higher rank I am, more likely I am to roll away! + {//roll away! + int anim; + qboolean forceGetUp = qfalse; + if ( pm->cmd.forwardmove > 0 ) + { + if ( pm->ps->legsAnim == BOTH_KNOCKDOWN3 + || pm->ps->legsAnim == BOTH_KNOCKDOWN5 + || pm->ps->legsAnim == BOTH_LK_DL_ST_T_SB_1_L ) + { + anim = BOTH_GETUP_FROLL_F; + } + else + { + anim = BOTH_GETUP_BROLL_F; + } + forceGetUp = qtrue; + } + else if ( pm->cmd.forwardmove < 0 ) + { + if ( pm->ps->legsAnim == BOTH_KNOCKDOWN3 + || pm->ps->legsAnim == BOTH_KNOCKDOWN5 + || pm->ps->legsAnim == BOTH_LK_DL_ST_T_SB_1_L ) + { + anim = BOTH_GETUP_FROLL_B; + } + else + { + anim = BOTH_GETUP_BROLL_B; + } + forceGetUp = qtrue; + } + else if ( pm->cmd.rightmove > 0 ) + { + if ( pm->ps->legsAnim == BOTH_KNOCKDOWN3 + || pm->ps->legsAnim == BOTH_KNOCKDOWN5 + || pm->ps->legsAnim == BOTH_LK_DL_ST_T_SB_1_L ) + { + anim = BOTH_GETUP_FROLL_R; + } + else + { + anim = BOTH_GETUP_BROLL_R; + } + } + else if ( pm->cmd.rightmove < 0 ) + { + if ( pm->ps->legsAnim == BOTH_KNOCKDOWN3 + || pm->ps->legsAnim == BOTH_KNOCKDOWN5 + || pm->ps->legsAnim == BOTH_LK_DL_ST_T_SB_1_L ) + { + anim = BOTH_GETUP_FROLL_L; + } + else + { + anim = BOTH_GETUP_BROLL_L; + } + } + else + { + if ( pm->ps->legsAnim == BOTH_KNOCKDOWN3 + || pm->ps->legsAnim == BOTH_KNOCKDOWN5 + || pm->ps->legsAnim == BOTH_LK_DL_ST_T_SB_1_L ) + {//on your front + anim = Q_irand( BOTH_GETUP_FROLL_B, BOTH_GETUP_FROLL_R ); + } + else + { + anim = Q_irand( BOTH_GETUP_BROLL_B, BOTH_GETUP_BROLL_R ); + } + } + if ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) ) + { + if ( !G_CheckRollSafety( pm->gent, anim, 64 ) ) + {//oops, try other one + if ( pm->ps->legsAnim == BOTH_KNOCKDOWN3 + || pm->ps->legsAnim == BOTH_KNOCKDOWN5 + || pm->ps->legsAnim == BOTH_LK_DL_ST_T_SB_1_L ) + { + if ( anim == BOTH_GETUP_FROLL_R ) + { + anim = BOTH_GETUP_FROLL_L; + } + else if ( anim == BOTH_GETUP_FROLL_F ) + { + anim = BOTH_GETUP_FROLL_B; + } + else if ( anim == BOTH_GETUP_FROLL_B ) + { + anim = BOTH_GETUP_FROLL_F; + } + else + { + anim = BOTH_GETUP_FROLL_L; + } + if ( !G_CheckRollSafety( pm->gent, anim, 64 ) ) + {//neither side is clear, screw it + return qfalse; + } + } + else + { + if ( anim == BOTH_GETUP_BROLL_R ) + { + anim = BOTH_GETUP_BROLL_L; + } + else if ( anim == BOTH_GETUP_BROLL_F ) + { + anim = BOTH_GETUP_BROLL_B; + } + else if ( anim == BOTH_GETUP_FROLL_B ) + { + anim = BOTH_GETUP_BROLL_F; + } + else + { + anim = BOTH_GETUP_BROLL_L; + } + if ( !G_CheckRollSafety( pm->gent, anim, 64 ) ) + {//neither side is clear, screw it + return qfalse; + } + } + } + } + pm->cmd.rightmove = pm->cmd.forwardmove = 0; + if ( PM_LockedAnim( pm->ps->torsoAnim ) ) + {//need to be able to override this anim + pm->ps->torsoAnimTimer = 0; + } + if ( PM_LockedAnim( pm->ps->legsAnim ) ) + {//need to be able to override this anim + pm->ps->legsAnimTimer = 0; + } + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS ); + pm->ps->weaponTime = pm->ps->torsoAnimTimer - 300;//don't attack until near end of this anim + pm->ps->saberMove = pm->ps->saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + pm->ps->saberBlocked = BLOCKED_NONE; + if ( forceGetUp ) + { + if ( pm->gent && pm->gent->client && pm->gent->client->playerTeam == TEAM_ENEMY + && pm->gent->NPC && pm->gent->NPC->blockedSpeechDebounceTime < level.time + && !Q_irand( 0, 1 ) ) + { + PM_AddEvent( Q_irand( EV_COMBAT1, EV_COMBAT3 ) ); + pm->gent->NPC->blockedSpeechDebounceTime = level.time + 1000; + } + G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + //launch off ground? + pm->ps->weaponTime = 300;//just to make sure it's cleared + } + return qtrue; + } + } + return qfalse; +} + +extern int G_MinGetUpTime( gentity_t *ent ); +qboolean PM_GettingUpFromKnockDown( float standheight, float crouchheight ) +{ + int legsAnim = pm->ps->legsAnim; + if ( legsAnim == BOTH_KNOCKDOWN1 + ||legsAnim == BOTH_KNOCKDOWN2 + ||legsAnim == BOTH_KNOCKDOWN3 + ||legsAnim == BOTH_KNOCKDOWN4 + ||legsAnim == BOTH_KNOCKDOWN5 + ||legsAnim == BOTH_PLAYER_PA_3_FLY + ||legsAnim == BOTH_LK_DL_ST_T_SB_1_L + ||legsAnim == BOTH_RELEASED ) + {//in a knockdown + int minTimeLeft = G_MinGetUpTime( pm->gent ); + if ( pm->ps->legsAnimTimer <= minTimeLeft ) + {//if only a quarter of a second left, allow roll-aways + if ( PM_CheckRollGetup() ) + { + pm->cmd.rightmove = pm->cmd.forwardmove = 0; + return qtrue; + } + } + if ( TIMER_Exists( pm->gent, "noGetUpStraight" ) ) + { + if ( !TIMER_Done2( pm->gent, "noGetUpStraight", qtrue ) ) + {//not allowed to do straight get-ups for another few seconds + if ( pm->ps->legsAnimTimer <= minTimeLeft ) + {//hold it for a bit + pm->ps->legsAnimTimer = minTimeLeft+1; + } + } + } + if ( !pm->ps->legsAnimTimer || (pm->ps->legsAnimTimer<=minTimeLeft&&(pm->cmd.upmove>0||(pm->gent&&pm->gent->client&&pm->gent->client->NPC_class==CLASS_ALORA))) ) + {//done with the knockdown - FIXME: somehow this is allowing an *instant* getup...??? + //FIXME: if trying to crouch (holding button?), just get up into a crouch? + if ( pm->cmd.upmove < 0 ) + { + return PM_CrouchGetup( crouchheight ); + } + else + { + trace_t trace; + // try to stand up + pm->maxs[2] = standheight; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if ( !trace.allsolid ) + {//stand up + int anim = BOTH_GETUP1; + qboolean forceGetUp = qfalse; + pm->maxs[2] = standheight; + pm->ps->viewheight = standheight + STANDARD_VIEWHEIGHT_OFFSET; + //NOTE: the force power checks will stop fencers and grunts from getting up using force jump + switch ( pm->ps->legsAnim ) + { + case BOTH_KNOCKDOWN1: + if ( (pm->ps->clientNum&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) || ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())&&pm->cmd.upmove>0&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) )//FORCE_LEVEL_1) )FORCE_LEVEL_1) ) + { + anim = Q_irand( BOTH_FORCE_GETUP_B1, BOTH_FORCE_GETUP_B6 );//NOTE: BOTH_FORCE_GETUP_B5 takes soe steps forward at end + forceGetUp = qtrue; + } + else + { + anim = BOTH_GETUP1; + } + break; + case BOTH_KNOCKDOWN2: + case BOTH_PLAYER_PA_3_FLY: + if ( (pm->ps->clientNum&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) || ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())&&pm->cmd.upmove>0&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) )//FORCE_LEVEL_1) )FORCE_LEVEL_1) ) + { + anim = Q_irand( BOTH_FORCE_GETUP_B1, BOTH_FORCE_GETUP_B6 );//NOTE: BOTH_FORCE_GETUP_B5 takes soe steps forward at end + forceGetUp = qtrue; + } + else + { + anim = BOTH_GETUP2; + } + break; + case BOTH_KNOCKDOWN3: + if ( (pm->ps->clientNum&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) || ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())&&pm->cmd.upmove>0&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) )//FORCE_LEVEL_1) ) + { + anim = Q_irand( BOTH_FORCE_GETUP_F1, BOTH_FORCE_GETUP_F2 ); + forceGetUp = qtrue; + } + else + { + anim = BOTH_GETUP3; + } + break; + case BOTH_KNOCKDOWN4: + case BOTH_RELEASED: + if ( (pm->ps->clientNum&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) || ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())&&pm->cmd.upmove>0&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) )//FORCE_LEVEL_1) )FORCE_LEVEL_1) ) + { + anim = Q_irand( BOTH_FORCE_GETUP_B1, BOTH_FORCE_GETUP_B6 );//NOTE: BOTH_FORCE_GETUP_B5 takes soe steps forward at end + forceGetUp = qtrue; + } + else + { + anim = BOTH_GETUP4; + } + break; + case BOTH_KNOCKDOWN5: + case BOTH_LK_DL_ST_T_SB_1_L: + if ( (pm->ps->clientNum&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) || ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())&&pm->cmd.upmove>0&&pm->ps->forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0) )//FORCE_LEVEL_1) )FORCE_LEVEL_1) ) + { + anim = Q_irand( BOTH_FORCE_GETUP_F1, BOTH_FORCE_GETUP_F2 ); + forceGetUp = qtrue; + } + else + { + anim = BOTH_GETUP5; + } + break; + } + //Com_Printf( "getupanim = %s\n", animTable[anim].name ); + if ( forceGetUp ) + { + if ( pm->gent && pm->gent->client && pm->gent->client->playerTeam == TEAM_ENEMY + && pm->gent->NPC && pm->gent->NPC->blockedSpeechDebounceTime < level.time + && !Q_irand( 0, 1 ) ) + { + PM_AddEvent( Q_irand( EV_COMBAT1, EV_COMBAT3 ) ); + pm->gent->NPC->blockedSpeechDebounceTime = level.time + 1000; + } + G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + //launch off ground? + pm->ps->weaponTime = 300;//just to make sure it's cleared + } + if ( PM_LockedAnim( pm->ps->torsoAnim ) ) + {//need to be able to override this anim + pm->ps->torsoAnimTimer = 0; + } + if ( PM_LockedAnim( pm->ps->legsAnim ) ) + {//need to be able to override this anim + pm->ps->legsAnimTimer = 0; + } + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS ); + pm->ps->saberMove = pm->ps->saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + pm->ps->saberBlocked = BLOCKED_NONE; + return qtrue; + } + else + { + return PM_CrouchGetup( crouchheight ); + } + } + } + else + { + if ( pm->ps->legsAnim == BOTH_LK_DL_ST_T_SB_1_L ) + { + PM_CmdForRoll( pm->ps, &pm->cmd ); + } + else + { + pm->cmd.rightmove = pm->cmd.forwardmove = 0; + } + } + } + return qfalse; +} + +void PM_CmdForRoll( playerState_t *ps, usercmd_t *pCmd ) +{ + switch ( ps->legsAnim ) + { + case BOTH_ROLL_F: + pCmd->forwardmove = 127; + pCmd->rightmove = 0; + break; + case BOTH_ROLL_B: + pCmd->forwardmove = -127; + pCmd->rightmove = 0; + break; + case BOTH_ROLL_R: + pCmd->forwardmove = 0; + pCmd->rightmove = 127; + break; + case BOTH_ROLL_L: + pCmd->forwardmove = 0; + pCmd->rightmove = -127; + break; + + case BOTH_GETUP_BROLL_R: + pCmd->forwardmove = 0; + pCmd->rightmove = 48; + //NOTE: speed is 400 + break; + + case BOTH_GETUP_FROLL_R: + if ( ps->legsAnimTimer <= 250 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + pCmd->forwardmove = 0; + pCmd->rightmove = 48; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_BROLL_L: + pCmd->forwardmove = 0; + pCmd->rightmove = -48; + //NOTE: speed is 400 + break; + + case BOTH_GETUP_FROLL_L: + if ( ps->legsAnimTimer <= 250 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + pCmd->forwardmove = 0; + pCmd->rightmove = -48; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_BROLL_B: + if ( ps->torsoAnimTimer <= 250 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else if ( PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)ps->legsAnim ) - ps->torsoAnimTimer < 350 ) + {//beginning of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + //FIXME: ramp down over length of anim + pCmd->forwardmove = -64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_FROLL_B: + if ( ps->torsoAnimTimer <= 100 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else if ( PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)ps->legsAnim ) - ps->torsoAnimTimer < 200 ) + {//beginning of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + //FIXME: ramp down over length of anim + pCmd->forwardmove = -64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_BROLL_F: + if ( ps->torsoAnimTimer <= 550 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else if ( PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)ps->legsAnim ) - ps->torsoAnimTimer < 150 ) + {//beginning of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + pCmd->forwardmove = 64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_FROLL_F: + if ( ps->torsoAnimTimer <= 100 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + //FIXME: ramp down over length of anim + pCmd->forwardmove = 64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + case BOTH_LK_DL_ST_T_SB_1_L: + //kicked backwards + if ( ps->legsAnimTimer < 3050//at least 10 frames in + && ps->legsAnimTimer > 550 )//at least 6 frames from end + {//move backwards + pCmd->forwardmove = -64; + pCmd->rightmove = 0; + } + else + { + pCmd->forwardmove = pCmd->rightmove = 0; + } + break; + } + + pCmd->upmove = 0; +} + +qboolean PM_InRollIgnoreTimer( playerState_t *ps ) +{ + switch ( ps->legsAnim ) + { + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + return qtrue; + } + return qfalse; +} + +qboolean PM_InRoll( playerState_t *ps ) +{ + if ( ps->legsAnimTimer && PM_InRollIgnoreTimer( ps ) ) + { + return qtrue; + } + + return qfalse; +} + +qboolean PM_CrouchAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_SIT1: //# Normal chair sit. + case BOTH_SIT2: //# Lotus position. + case BOTH_SIT3: //# Sitting in tired position: elbows on knees + case BOTH_CROUCH1: //# Transition from standing to crouch + case BOTH_CROUCH1IDLE: //# Crouching idle + case BOTH_CROUCH1WALK: //# Walking while crouched + case BOTH_CROUCH1WALKBACK: //# Walking while crouched + case BOTH_CROUCH2TOSTAND1: //# going from crouch2 to stand1 + case BOTH_CROUCH3: //# Desann crouching down to Kyle (cin 9) + case BOTH_KNEES1: //# Tavion on her knees + case BOTH_CROUCHATTACKBACK1://FIXME: not if in middle of anim? + case BOTH_ROLL_STAB: + //??? + case BOTH_STAND_TO_KNEEL: + case BOTH_KNEEL_TO_STAND: + case BOTH_TURNCROUCH1: + case BOTH_CROUCH4: + case BOTH_KNEES2: //# Tavion on her knees looking down + case BOTH_KNEES2TO1: //# Transition of KNEES2 to KNEES1 + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_PainAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_PAIN1: //# First take pain anim + case BOTH_PAIN2: //# Second take pain anim + case BOTH_PAIN3: //# Third take pain anim + case BOTH_PAIN4: //# Fourth take pain anim + case BOTH_PAIN5: //# Fifth take pain anim - from behind + case BOTH_PAIN6: //# Sixth take pain anim - from behind + case BOTH_PAIN7: //# Seventh take pain anim - from behind + case BOTH_PAIN8: //# Eigth take pain anim - from behind + case BOTH_PAIN9: //# + case BOTH_PAIN10: //# + case BOTH_PAIN11: //# + case BOTH_PAIN12: //# + case BOTH_PAIN13: //# + case BOTH_PAIN14: //# + case BOTH_PAIN15: //# + case BOTH_PAIN16: //# + case BOTH_PAIN17: //# + case BOTH_PAIN18: //# + return qtrue; + break; + } + return qfalse; +} + + +qboolean PM_DodgeHoldAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_DODGE_HOLD_FL: + case BOTH_DODGE_HOLD_FR: + case BOTH_DODGE_HOLD_BL: + case BOTH_DODGE_HOLD_BR: + case BOTH_DODGE_HOLD_L: + case BOTH_DODGE_HOLD_R: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_DodgeAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_DODGE_FL: //# lean-dodge forward left + case BOTH_DODGE_FR: //# lean-dodge forward right + case BOTH_DODGE_BL: //# lean-dodge backwards left + case BOTH_DODGE_BR: //# lean-dodge backwards right + case BOTH_DODGE_L: //# lean-dodge left + case BOTH_DODGE_R: //# lean-dodge right + return qtrue; + break; + default: + return PM_DodgeHoldAnim( anim ); + break; + } + //return qfalse; +} + +qboolean PM_ForceJumpingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_FORCEJUMP1: //# Jump - wind-up and leave ground + case BOTH_FORCEINAIR1: //# In air loop (from jump) + case BOTH_FORCELAND1: //# Landing (from in air loop) + case BOTH_FORCEJUMPBACK1: //# Jump backwards - wind-up and leave ground + case BOTH_FORCEINAIRBACK1: //# In air loop (from jump back) + case BOTH_FORCELANDBACK1: //# Landing backwards(from in air loop) + case BOTH_FORCEJUMPLEFT1: //# Jump left - wind-up and leave ground + case BOTH_FORCEINAIRLEFT1: //# In air loop (from jump left) + case BOTH_FORCELANDLEFT1: //# Landing left(from in air loop) + case BOTH_FORCEJUMPRIGHT1: //# Jump right - wind-up and leave ground + case BOTH_FORCEINAIRRIGHT1: //# In air loop (from jump right) + case BOTH_FORCELANDRIGHT1: //# Landing right(from in air loop) + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_JumpingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_JUMP1: //# Jump - wind-up and leave ground + case BOTH_INAIR1: //# In air loop (from jump) + case BOTH_LAND1: //# Landing (from in air loop) + case BOTH_LAND2: //# Landing Hard (from a great height) + case BOTH_JUMPBACK1: //# Jump backwards - wind-up and leave ground + case BOTH_INAIRBACK1: //# In air loop (from jump back) + case BOTH_LANDBACK1: //# Landing backwards(from in air loop) + case BOTH_JUMPLEFT1: //# Jump left - wind-up and leave ground + case BOTH_INAIRLEFT1: //# In air loop (from jump left) + case BOTH_LANDLEFT1: //# Landing left(from in air loop) + case BOTH_JUMPRIGHT1: //# Jump right - wind-up and leave ground + case BOTH_INAIRRIGHT1: //# In air loop (from jump right) + case BOTH_LANDRIGHT1: //# Landing right(from in air loop) + return qtrue; + break; + default: + if ( PM_InAirKickingAnim( anim ) ) + { + return qtrue; + } + return PM_ForceJumpingAnim( anim ); + break; + } + //return qfalse; +} + +qboolean PM_LandingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_LAND1: //# Landing (from in air loop) + case BOTH_LAND2: //# Landing Hard (from a great height) + case BOTH_LANDBACK1: //# Landing backwards(from in air loop) + case BOTH_LANDLEFT1: //# Landing left(from in air loop) + case BOTH_LANDRIGHT1: //# Landing right(from in air loop) + case BOTH_FORCELAND1: //# Landing (from in air loop) + case BOTH_FORCELANDBACK1: //# Landing backwards(from in air loop) + case BOTH_FORCELANDLEFT1: //# Landing left(from in air loop) + case BOTH_FORCELANDRIGHT1: //# Landing right(from in air loop) + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_FlippingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_FLIP_F: //# Flip forward + case BOTH_FLIP_B: //# Flip backwards + case BOTH_FLIP_L: //# Flip left + case BOTH_FLIP_R: //# Flip right + case BOTH_ALORA_FLIP_1: + case BOTH_ALORA_FLIP_2: + case BOTH_ALORA_FLIP_3: + case BOTH_WALL_RUN_RIGHT_FLIP: + case BOTH_WALL_RUN_LEFT_FLIP: + case BOTH_WALL_FLIP_RIGHT: + case BOTH_WALL_FLIP_LEFT: + case BOTH_FLIP_BACK1: + case BOTH_FLIP_BACK2: + case BOTH_FLIP_BACK3: + case BOTH_WALL_FLIP_BACK1: + case BOTH_ALORA_FLIP_B: + //Not really flips, but... + case BOTH_WALL_RUN_RIGHT: + case BOTH_WALL_RUN_LEFT: + case BOTH_WALL_RUN_RIGHT_STOP: + case BOTH_WALL_RUN_LEFT_STOP: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + // + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_ARIAL_F1: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + case BOTH_JUMPFLIPSLASHDOWN1: + case BOTH_JUMPFLIPSTABDOWN: + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + //JKA + case BOTH_FORCEWALLRUNFLIP_END: + case BOTH_FORCEWALLRUNFLIP_ALT: + case BOTH_FLIP_ATTACK7: + case BOTH_A7_SOULCAL: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_WalkingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_WALK1: //# Normal walk + case BOTH_WALK2: //# Normal walk with saber + case BOTH_WALK_STAFF: //# Normal walk with staff + case BOTH_WALK_DUAL: //# Normal walk with staff + case BOTH_WALK5: //# Tavion taunting Kyle (cin 22) + case BOTH_WALK6: //# Slow walk for Luke (cin 12) + case BOTH_WALK7: //# Fast walk + case BOTH_WALKBACK1: //# Walk1 backwards + case BOTH_WALKBACK2: //# Walk2 backwards + case BOTH_WALKBACK_STAFF: //# Walk backwards with staff + case BOTH_WALKBACK_DUAL: //# Walk backwards with dual + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_RunningAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_RUN1: + case BOTH_RUN2: + case BOTH_RUN4: + case BOTH_RUN_STAFF: + case BOTH_RUN_DUAL: + case BOTH_RUNBACK1: + case BOTH_RUNBACK2: + case BOTH_RUNBACK_STAFF: + case BOTH_RUN1START: //# Start into full run1 + case BOTH_RUN1STOP: //# Stop from full run1 + case BOTH_RUNSTRAFE_LEFT1: //# Sidestep left: should loop + case BOTH_RUNSTRAFE_RIGHT1: //# Sidestep right: should loop + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_RollingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_ROLL_F: //# Roll forward + case BOTH_ROLL_B: //# Roll backward + case BOTH_ROLL_L: //# Roll left + case BOTH_ROLL_R: //# Roll right + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SwimmingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_SWIM_IDLE1: //# Swimming Idle 1 + case BOTH_SWIMFORWARD: //# Swim forward loop + case BOTH_SWIMBACKWARD: //# Swim backward loop + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_LeapingSaberAnim( int anim ) +{ + switch ( anim ) + { + //level 7 + case BOTH_T7_BR_TL: + case BOTH_T7__L_BR: + case BOTH_T7__L__R: + case BOTH_T7_BL_BR: + case BOTH_T7_BL__R: + case BOTH_T7_BL_TR: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SpinningSaberAnim( int anim ) +{ + switch ( anim ) + { + //level 1 - FIXME: level 1 will have *no* spins + case BOTH_T1_BR_BL: + case BOTH_T1__R__L: + case BOTH_T1__R_BL: + case BOTH_T1_TR_BL: + case BOTH_T1_BR_TL: + case BOTH_T1_BR__L: + case BOTH_T1_TL_BR: + case BOTH_T1__L_BR: + case BOTH_T1__L__R: + case BOTH_T1_BL_BR: + case BOTH_T1_BL__R: + case BOTH_T1_BL_TR: + //level 2 + case BOTH_T2_BR__L: + case BOTH_T2_BR_BL: + case BOTH_T2__R_BL: + case BOTH_T2__L_BR: + case BOTH_T2_BL_BR: + case BOTH_T2_BL__R: + //level 3 + case BOTH_T3_BR__L: + case BOTH_T3_BR_BL: + case BOTH_T3__R_BL: + case BOTH_T3__L_BR: + case BOTH_T3_BL_BR: + case BOTH_T3_BL__R: + //level 4 + case BOTH_T4_BR__L: + case BOTH_T4_BR_BL: + case BOTH_T4__R_BL: + case BOTH_T4__L_BR: + case BOTH_T4_BL_BR: + case BOTH_T4_BL__R: + //level 5 + case BOTH_T5_BR_BL: + case BOTH_T5__R__L: + case BOTH_T5__R_BL: + case BOTH_T5_TR_BL: + case BOTH_T5_BR_TL: + case BOTH_T5_BR__L: + case BOTH_T5_TL_BR: + case BOTH_T5__L_BR: + case BOTH_T5__L__R: + case BOTH_T5_BL_BR: + case BOTH_T5_BL__R: + case BOTH_T5_BL_TR: + //level 6 + case BOTH_T6_BR_TL: + case BOTH_T6__R_TL: + case BOTH_T6__R__L: + case BOTH_T6__R_BL: + case BOTH_T6_TR_TL: + case BOTH_T6_TR__L: + case BOTH_T6_TR_BL: + case BOTH_T6_T__TL: + case BOTH_T6_T__BL: + case BOTH_T6_TL_BR: + case BOTH_T6__L_BR: + case BOTH_T6__L__R: + case BOTH_T6_TL__R: + case BOTH_T6_TL_TR: + case BOTH_T6__L_TR: + case BOTH_T6__L_T_: + case BOTH_T6_BL_T_: + case BOTH_T6_BR__L: + case BOTH_T6_BR_BL: + case BOTH_T6_BL_BR: + case BOTH_T6_BL__R: + case BOTH_T6_BL_TR: + //level 7 + case BOTH_T7_BR_TL: + case BOTH_T7_BR__L: + case BOTH_T7_BR_BL: + case BOTH_T7__R__L: + case BOTH_T7__R_BL: + case BOTH_T7_TR__L: + case BOTH_T7_T___R: + case BOTH_T7_TL_BR: + case BOTH_T7__L_BR: + case BOTH_T7__L__R: + case BOTH_T7_BL_BR: + case BOTH_T7_BL__R: + case BOTH_T7_BL_TR: + case BOTH_T7_TL_TR: + case BOTH_T7_T__BR: + case BOTH_T7__L_TR: + case BOTH_V7_BL_S7: + //special + //case BOTH_A2_STABBACK1: + case BOTH_ATTACK_BACK: + case BOTH_CROUCHATTACKBACK1: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_JUMPFLIPSLASHDOWN1: + case BOTH_JUMPFLIPSTABDOWN: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SpinningAnim( int anim ) +{ + /* + switch ( anim ) + { + //FIXME: list any other spinning anims + default: + break; + } + */ + return PM_SpinningSaberAnim( anim ); +} + +void PM_ResetAnkleAngles( void ) +{ + if ( !pm->gent || !pm->gent->client || pm->gent->client->NPC_class != CLASS_ATST ) + { + return; + } + if ( pm->gent->footLBone != -1 ) + { + gi.G2API_SetBoneAnglesIndex( &pm->gent->ghoul2[0], pm->gent->footLBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL ); + } + if ( pm->gent->footRBone != -1 ) + { + gi.G2API_SetBoneAnglesIndex( &pm->gent->ghoul2[0], pm->gent->footRBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL ); + } +} + +void PM_AnglesForSlope( const float yaw, const vec3_t slope, vec3_t angles ) +{ + vec3_t nvf, ovf, ovr, new_angles; + float pitch, mod, dot; + + VectorSet( angles, 0, yaw, 0 ); + AngleVectors( angles, ovf, ovr, NULL ); + + vectoangles( slope, new_angles ); + pitch = new_angles[PITCH] + 90; + new_angles[ROLL] = new_angles[PITCH] = 0; + + AngleVectors( new_angles, nvf, NULL, NULL ); + + mod = DotProduct( nvf, ovr ); + + if ( mod < 0 ) + mod = -1; + else + mod = 1; + + dot = DotProduct( nvf, ovf ); + + angles[YAW] = 0; + angles[PITCH] = dot * pitch; + angles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); +} + +void PM_FootSlopeTrace( float *pDiff, float *pInterval ) +{ + vec3_t footLOrg, footROrg, footLBot, footRBot; + trace_t trace; + float diff, interval; + if ( pm->gent->client->NPC_class == CLASS_ATST ) + { + interval = 10; + } + else + { + interval = 4;//? + } + + if ( pm->gent->footLBolt == -1 || pm->gent->footRBolt == -1 ) + { + if ( pDiff != NULL ) + { + *pDiff = 0; + } + if ( pInterval != NULL ) + { + *pInterval = interval; + } + return; + } +#if 1 + for ( int i = 0; i < 3; i++ ) + { + if ( _isnan( pm->gent->client->renderInfo.footLPoint[i] ) + || _isnan( pm->gent->client->renderInfo.footRPoint[i] ) ) + { + if ( pDiff != NULL ) + { + *pDiff = 0; + } + if ( pInterval != NULL ) + { + *pInterval = interval; + } + return; + } + } +#else + + //FIXME: these really should have been gotten on the cgame, but I guess sometimes they're not and we end up with qnan numbers! + mdxaBone_t boltMatrix; + vec3_t G2Angles = {0, pm->gent->client->ps.legsYaw, 0}; + //get the feet + gi.G2API_GetBoltMatrix( pm->gent->ghoul2, pm->gent->playerModel, pm->gent->footLBolt, + &boltMatrix, G2Angles, pm->ps->origin, (cg.time?cg.time:level.time), + NULL, pm->gent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, pm->gent->client->renderInfo.footLPoint ); + + gi.G2API_GetBoltMatrix( pm->gent->ghoul2, pm->gent->playerModel, pm->gent->footRBolt, + &boltMatrix, G2Angles, pm->ps->origin, (cg.time?cg.time:level.time), + NULL, pm->gent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, pm->gent->client->renderInfo.footRPoint ); +#endif + //NOTE: on AT-STs, rotating the foot moves this point, so it will wiggle... + // we have to do this extra work (more G2 transforms) to stop the wiggle... is it worth it? + /* + if ( pm->gent->client->NPC_class == CLASS_ATST ) + { + mdxaBone_t boltMatrix; + vec3_t G2Angles = {0, pm->gent->client->ps.legsYaw, 0}; + //get the feet + gi.G2API_SetBoneAnglesIndex( &pm->gent->ghoul2[0], pm->gent->footLBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL ); + gi.G2API_GetBoltMatrix( pm->gent->ghoul2, pm->gent->playerModel, pm->gent->footLBolt, + &boltMatrix, G2Angles, pm->ps->origin, (cg.time?cg.time:level.time), + NULL, pm->gent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, pm->gent->client->renderInfo.footLPoint ); + + gi.G2API_SetBoneAnglesIndex( &pm->gent->ghoul2[0], pm->gent->footRBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL ); + gi.G2API_GetBoltMatrix( pm->gent->ghoul2, pm->gent->playerModel, pm->gent->footRBolt, + &boltMatrix, G2Angles, pm->ps->origin, (cg.time?cg.time:level.time), + NULL, pm->gent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, pm->gent->client->renderInfo.footRPoint ); + } + */ + //get these on the cgame and store it, save ourselves a ghoul2 construct skel call + VectorCopy( pm->gent->client->renderInfo.footLPoint, footLOrg ); + VectorCopy( pm->gent->client->renderInfo.footRPoint, footROrg ); + + //step 2: adjust foot tag z height to bottom of bbox+1 + footLOrg[2] = pm->gent->currentOrigin[2] + pm->gent->mins[2] + 1; + footROrg[2] = pm->gent->currentOrigin[2] + pm->gent->mins[2] + 1; + VectorSet( footLBot, footLOrg[0], footLOrg[1], footLOrg[2] - interval*10 ); + VectorSet( footRBot, footROrg[0], footROrg[1], footROrg[2] - interval*10 ); + + //step 3: trace down from each, find difference + vec3_t footMins, footMaxs; + vec3_t footLSlope, footRSlope; + if ( pm->gent->client->NPC_class == CLASS_ATST ) + { + VectorSet( footMins, -16, -16, 0 ); + VectorSet( footMaxs, 16, 16, 1 ); + } + else + { + VectorSet( footMins, -3, -3, 0 ); + VectorSet( footMaxs, 3, 3, 1 ); + } + + pm->trace( &trace, footLOrg, footMins, footMaxs, footLBot, pm->ps->clientNum, pm->tracemask ); + VectorCopy( trace.endpos, footLBot ); + VectorCopy( trace.plane.normal, footLSlope ); + + pm->trace( &trace, footROrg, footMins, footMaxs, footRBot, pm->ps->clientNum, pm->tracemask ); + VectorCopy( trace.endpos, footRBot ); + VectorCopy( trace.plane.normal, footRSlope ); + + diff = footLBot[2] - footRBot[2]; + + //optional step: for atst, tilt the footpads to match the slopes under it... + if ( pm->gent->client->NPC_class == CLASS_ATST ) + { + vec3_t footAngles; + if ( !VectorCompare( footLSlope, vec3_origin ) ) + {//rotate the ATST's left foot pad to match the slope + PM_AnglesForSlope( pm->gent->client->renderInfo.legsYaw, footLSlope, footAngles ); + //Hmm... lerp this? + gi.G2API_SetBoneAnglesIndex( &pm->gent->ghoul2[0], pm->gent->footLBone, footAngles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL ); + } + if ( !VectorCompare( footRSlope, vec3_origin ) ) + {//rotate the ATST's right foot pad to match the slope + PM_AnglesForSlope( pm->gent->client->renderInfo.legsYaw, footRSlope, footAngles ); + //Hmm... lerp this? + gi.G2API_SetBoneAnglesIndex( &pm->gent->ghoul2[0], pm->gent->footRBone, footAngles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL ); + } + } + + if ( pDiff != NULL ) + { + *pDiff = diff; + } + if ( pInterval != NULL ) + { + *pInterval = interval; + } +} + +qboolean PM_InSlopeAnim( int anim ) +{ + switch ( anim ) + { + case LEGS_LEFTUP1: //# On a slope with left foot 4 higher than right + case LEGS_LEFTUP2: //# On a slope with left foot 8 higher than right + case LEGS_LEFTUP3: //# On a slope with left foot 12 higher than right + case LEGS_LEFTUP4: //# On a slope with left foot 16 higher than right + case LEGS_LEFTUP5: //# On a slope with left foot 20 higher than right + case LEGS_RIGHTUP1: //# On a slope with RIGHT foot 4 higher than left + case LEGS_RIGHTUP2: //# On a slope with RIGHT foot 8 higher than left + case LEGS_RIGHTUP3: //# On a slope with RIGHT foot 12 higher than left + case LEGS_RIGHTUP4: //# On a slope with RIGHT foot 16 higher than left + case LEGS_RIGHTUP5: //# On a slope with RIGHT foot 20 higher than left + case LEGS_S1_LUP1: + case LEGS_S1_LUP2: + case LEGS_S1_LUP3: + case LEGS_S1_LUP4: + case LEGS_S1_LUP5: + case LEGS_S1_RUP1: + case LEGS_S1_RUP2: + case LEGS_S1_RUP3: + case LEGS_S1_RUP4: + case LEGS_S1_RUP5: + case LEGS_S3_LUP1: + case LEGS_S3_LUP2: + case LEGS_S3_LUP3: + case LEGS_S3_LUP4: + case LEGS_S3_LUP5: + case LEGS_S3_RUP1: + case LEGS_S3_RUP2: + case LEGS_S3_RUP3: + case LEGS_S3_RUP4: + case LEGS_S3_RUP5: + case LEGS_S4_LUP1: + case LEGS_S4_LUP2: + case LEGS_S4_LUP3: + case LEGS_S4_LUP4: + case LEGS_S4_LUP5: + case LEGS_S4_RUP1: + case LEGS_S4_RUP2: + case LEGS_S4_RUP3: + case LEGS_S4_RUP4: + case LEGS_S4_RUP5: + case LEGS_S5_LUP1: + case LEGS_S5_LUP2: + case LEGS_S5_LUP3: + case LEGS_S5_LUP4: + case LEGS_S5_LUP5: + case LEGS_S5_RUP1: + case LEGS_S5_RUP2: + case LEGS_S5_RUP3: + case LEGS_S5_RUP4: + case LEGS_S5_RUP5: + case LEGS_S6_LUP1: + case LEGS_S6_LUP2: + case LEGS_S6_LUP3: + case LEGS_S6_LUP4: + case LEGS_S6_LUP5: + case LEGS_S6_RUP1: + case LEGS_S6_RUP2: + case LEGS_S6_RUP3: + case LEGS_S6_RUP4: + case LEGS_S6_RUP5: + case LEGS_S7_LUP1: + case LEGS_S7_LUP2: + case LEGS_S7_LUP3: + case LEGS_S7_LUP4: + case LEGS_S7_LUP5: + case LEGS_S7_RUP1: + case LEGS_S7_RUP2: + case LEGS_S7_RUP3: + case LEGS_S7_RUP4: + case LEGS_S7_RUP5: + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SaberStanceAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_STAND1://not really a saberstance anim, actually... "saber off" stance + case BOTH_STAND2://single-saber, medium style + case BOTH_SABERFAST_STANCE://single-saber, fast style + case BOTH_SABERSLOW_STANCE://single-saber, strong style + case BOTH_SABERSTAFF_STANCE://saber staff style + case BOTH_SABERDUAL_STANCE://dual saber style + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SaberDrawPutawayAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_STAND1TO2: + case BOTH_STAND2TO1: + case BOTH_S1_S7: + case BOTH_S7_S1: + case BOTH_S1_S6: + case BOTH_S6_S1: + return qtrue; + break; + } + return qfalse; +} + +#define SLOPE_RECALC_INT 100 +extern qboolean G_StandardHumanoid( gentity_t *self ); +qboolean PM_AdjustStandAnimForSlope( void ) +{ + if ( !pm->gent || !pm->gent->client ) + { + return qfalse; + } + if ( pm->gent->client->NPC_class != CLASS_ATST + && (!pm->gent||!G_StandardHumanoid( pm->gent )) ) + {//only ATST and player does this + return qfalse; + } + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + && (!cg.renderingThirdPerson || cg.zoomMode) ) + {//first person doesn't do this + return qfalse; + } + if ( pm->gent->footLBolt == -1 || pm->gent->footRBolt == -1 ) + {//need these bolts! + return qfalse; + } + //step 1: find the 2 foot tags + float diff; + float interval; + PM_FootSlopeTrace( &diff, &interval ); + + //step 4: based on difference, choose one of the left/right slope-match intervals + int destAnim; + if ( diff >= interval*5 ) + { + destAnim = LEGS_LEFTUP5; + } + else if ( diff >= interval*4 ) + { + destAnim = LEGS_LEFTUP4; + } + else if ( diff >= interval*3 ) + { + destAnim = LEGS_LEFTUP3; + } + else if ( diff >= interval*2 ) + { + destAnim = LEGS_LEFTUP2; + } + else if ( diff >= interval ) + { + destAnim = LEGS_LEFTUP1; + } + else if ( diff <= interval*-5 ) + { + destAnim = LEGS_RIGHTUP5; + } + else if ( diff <= interval*-4 ) + { + destAnim = LEGS_RIGHTUP4; + } + else if ( diff <= interval*-3 ) + { + destAnim = LEGS_RIGHTUP3; + } + else if ( diff <= interval*-2 ) + { + destAnim = LEGS_RIGHTUP2; + } + else if ( diff <= interval*-1 ) + { + destAnim = LEGS_RIGHTUP1; + } + else + { + return qfalse; + } + + int legsAnim = pm->ps->legsAnim; + if ( pm->gent->client->NPC_class != CLASS_ATST ) + { + //adjust for current legs anim + switch ( legsAnim ) + { + case BOTH_STAND1: + case LEGS_S1_LUP1: + case LEGS_S1_LUP2: + case LEGS_S1_LUP3: + case LEGS_S1_LUP4: + case LEGS_S1_LUP5: + case LEGS_S1_RUP1: + case LEGS_S1_RUP2: + case LEGS_S1_RUP3: + case LEGS_S1_RUP4: + case LEGS_S1_RUP5: + destAnim = LEGS_S1_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND2: + case BOTH_SABERFAST_STANCE: + case BOTH_SABERSLOW_STANCE: + case BOTH_CROUCH1IDLE: + case BOTH_CROUCH1: + case LEGS_LEFTUP1: //# On a slope with left foot 4 higher than right + case LEGS_LEFTUP2: //# On a slope with left foot 8 higher than right + case LEGS_LEFTUP3: //# On a slope with left foot 12 higher than right + case LEGS_LEFTUP4: //# On a slope with left foot 16 higher than right + case LEGS_LEFTUP5: //# On a slope with left foot 20 higher than right + case LEGS_RIGHTUP1: //# On a slope with RIGHT foot 4 higher than left + case LEGS_RIGHTUP2: //# On a slope with RIGHT foot 8 higher than left + case LEGS_RIGHTUP3: //# On a slope with RIGHT foot 12 higher than left + case LEGS_RIGHTUP4: //# On a slope with RIGHT foot 16 higher than left + case LEGS_RIGHTUP5: //# On a slope with RIGHT foot 20 higher than left + //fine + break; + case BOTH_STAND3: + case LEGS_S3_LUP1: + case LEGS_S3_LUP2: + case LEGS_S3_LUP3: + case LEGS_S3_LUP4: + case LEGS_S3_LUP5: + case LEGS_S3_RUP1: + case LEGS_S3_RUP2: + case LEGS_S3_RUP3: + case LEGS_S3_RUP4: + case LEGS_S3_RUP5: + destAnim = LEGS_S3_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND4: + case LEGS_S4_LUP1: + case LEGS_S4_LUP2: + case LEGS_S4_LUP3: + case LEGS_S4_LUP4: + case LEGS_S4_LUP5: + case LEGS_S4_RUP1: + case LEGS_S4_RUP2: + case LEGS_S4_RUP3: + case LEGS_S4_RUP4: + case LEGS_S4_RUP5: + destAnim = LEGS_S4_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND5: + case LEGS_S5_LUP1: + case LEGS_S5_LUP2: + case LEGS_S5_LUP3: + case LEGS_S5_LUP4: + case LEGS_S5_LUP5: + case LEGS_S5_RUP1: + case LEGS_S5_RUP2: + case LEGS_S5_RUP3: + case LEGS_S5_RUP4: + case LEGS_S5_RUP5: + destAnim = LEGS_S5_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_SABERDUAL_STANCE: + case LEGS_S6_LUP1: + case LEGS_S6_LUP2: + case LEGS_S6_LUP3: + case LEGS_S6_LUP4: + case LEGS_S6_LUP5: + case LEGS_S6_RUP1: + case LEGS_S6_RUP2: + case LEGS_S6_RUP3: + case LEGS_S6_RUP4: + case LEGS_S6_RUP5: + destAnim = LEGS_S6_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_SABERSTAFF_STANCE: + case LEGS_S7_LUP1: + case LEGS_S7_LUP2: + case LEGS_S7_LUP3: + case LEGS_S7_LUP4: + case LEGS_S7_LUP5: + case LEGS_S7_RUP1: + case LEGS_S7_RUP2: + case LEGS_S7_RUP3: + case LEGS_S7_RUP4: + case LEGS_S7_RUP5: + destAnim = LEGS_S7_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND6: + default: + return qfalse; + break; + } + } + //step 5: based on the chosen interval and the current legsAnim, pick the correct anim + //step 6: increment/decrement to the dest anim, not instant + if ( (legsAnim >= LEGS_LEFTUP1 && legsAnim <= LEGS_LEFTUP5) + || (legsAnim >= LEGS_S1_LUP1 && legsAnim <= LEGS_S1_LUP5) + || (legsAnim >= LEGS_S3_LUP1 && legsAnim <= LEGS_S3_LUP5) + || (legsAnim >= LEGS_S4_LUP1 && legsAnim <= LEGS_S4_LUP5) + || (legsAnim >= LEGS_S5_LUP1 && legsAnim <= LEGS_S5_LUP5) + || (legsAnim >= LEGS_S6_LUP1 && legsAnim <= LEGS_S6_LUP5) + || (legsAnim >= LEGS_S7_LUP1 && legsAnim <= LEGS_S7_LUP5) ) + {//already in left-side up + if ( destAnim > legsAnim && pm->gent->client->slopeRecalcTime < level.time ) + { + legsAnim++; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else if ( destAnim < legsAnim && pm->gent->client->slopeRecalcTime < level.time ) + { + legsAnim--; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else + { + destAnim = legsAnim; + } + } + else if ( (legsAnim >= LEGS_RIGHTUP1 && legsAnim <= LEGS_RIGHTUP5) + || (legsAnim >= LEGS_S1_RUP1 && legsAnim <= LEGS_S1_RUP5) + || (legsAnim >= LEGS_S3_RUP1 && legsAnim <= LEGS_S3_RUP5) + || (legsAnim >= LEGS_S4_RUP1 && legsAnim <= LEGS_S4_RUP5) + || (legsAnim >= LEGS_S5_RUP1 && legsAnim <= LEGS_S5_RUP5) + || (legsAnim >= LEGS_S6_RUP1 && legsAnim <= LEGS_S6_RUP5) + || (legsAnim >= LEGS_S7_RUP1 && legsAnim <= LEGS_S7_RUP5) ) + {//already in right-side up + if ( destAnim > legsAnim && pm->gent->client->slopeRecalcTime < level.time ) + { + legsAnim++; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else if ( destAnim < legsAnim && pm->gent->client->slopeRecalcTime < level.time ) + { + legsAnim--; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else + { + destAnim = legsAnim; + } + } + else + {//in a stand of some sort? + if ( pm->gent->client->NPC_class == CLASS_ATST ) + { + if ( legsAnim == BOTH_STAND1 || legsAnim == BOTH_STAND2 || legsAnim == BOTH_CROUCH1IDLE ) + { + if ( destAnim >= LEGS_LEFTUP1 && destAnim <= LEGS_LEFTUP5 ) + {//going into left side up + destAnim = LEGS_LEFTUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_RIGHTUP1 && destAnim <= LEGS_RIGHTUP5 ) + {//going into right side up + destAnim = LEGS_RIGHTUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + } + } + else + { + switch ( legsAnim ) + { + case BOTH_STAND1: + if ( destAnim >= LEGS_S1_LUP1 && destAnim <= LEGS_S1_LUP5 ) + {//going into left side up + destAnim = LEGS_S1_LUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S1_RUP1 && destAnim <= LEGS_S1_RUP5 ) + {//going into right side up + destAnim = LEGS_S1_RUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND2: + case BOTH_SABERFAST_STANCE: + case BOTH_SABERSLOW_STANCE: + case BOTH_CROUCH1IDLE: + if ( destAnim >= LEGS_LEFTUP1 && destAnim <= LEGS_LEFTUP5 ) + {//going into left side up + destAnim = LEGS_LEFTUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_RIGHTUP1 && destAnim <= LEGS_RIGHTUP5 ) + {//going into right side up + destAnim = LEGS_RIGHTUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND3: + if ( destAnim >= LEGS_S3_LUP1 && destAnim <= LEGS_S3_LUP5 ) + {//going into left side up + destAnim = LEGS_S3_LUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S3_RUP1 && destAnim <= LEGS_S3_RUP5 ) + {//going into right side up + destAnim = LEGS_S3_RUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND4: + if ( destAnim >= LEGS_S4_LUP1 && destAnim <= LEGS_S4_LUP5 ) + {//going into left side up + destAnim = LEGS_S4_LUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S4_RUP1 && destAnim <= LEGS_S4_RUP5 ) + {//going into right side up + destAnim = LEGS_S4_RUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND5: + if ( destAnim >= LEGS_S5_LUP1 && destAnim <= LEGS_S5_LUP5 ) + {//going into left side up + destAnim = LEGS_S5_LUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S5_RUP1 && destAnim <= LEGS_S5_RUP5 ) + {//going into right side up + destAnim = LEGS_S5_RUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_SABERDUAL_STANCE: + if ( destAnim >= LEGS_S6_LUP1 && destAnim <= LEGS_S6_LUP5 ) + {//going into left side up + destAnim = LEGS_S6_LUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S6_RUP1 && destAnim <= LEGS_S6_RUP5 ) + {//going into right side up + destAnim = LEGS_S6_RUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_SABERSTAFF_STANCE: + if ( destAnim >= LEGS_S7_LUP1 && destAnim <= LEGS_S7_LUP5 ) + {//going into left side up + destAnim = LEGS_S7_LUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S7_RUP1 && destAnim <= LEGS_S7_RUP5 ) + {//going into right side up + destAnim = LEGS_S7_RUP1; + pm->gent->client->slopeRecalcTime = level.time + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND6: + default: + return qfalse; + break; + } + } + } + //step 7: set the anim + PM_SetAnim( pm, SETANIM_LEGS, destAnim, SETANIM_FLAG_NORMAL ); + + return qtrue; +} + +void PM_JetPackAnim( void ) +{ + if ( !PM_ForceJumpingAnim( pm->ps->legsAnim ) )//haven't started forcejump yet + { + vec3_t facingFwd, facingRight, facingAngles = {0, pm->ps->viewangles[YAW], 0}; + int anim = BOTH_FORCEJUMP1; + AngleVectors( facingAngles, facingFwd, facingRight, NULL ); + float dotR = DotProduct( facingRight, pm->ps->velocity ); + float dotF = DotProduct( facingFwd, pm->ps->velocity ); + if ( fabs(dotR) > fabs(dotF) * 1.5 ) + { + if ( dotR > 150 ) + { + anim = BOTH_FORCEJUMPRIGHT1; + } + else if ( dotR < -150 ) + { + anim = BOTH_FORCEJUMPLEFT1; + } + } + else + { + if ( dotF > 150 ) + { + anim = BOTH_FORCEJUMP1; + } + else if ( dotF < -150 ) + { + anim = BOTH_FORCEJUMPBACK1; + } + } + int parts = SETANIM_BOTH; + if ( pm->ps->weaponTime ) + {//FIXME: really only care if we're in a saber attack anim... + parts = SETANIM_LEGS; + } + + PM_SetAnim( pm, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + /* + else + { + if ( !pm->ps->legsAnimTimer ) + {//not in the middle of a legsAnim + int anim = pm->ps->legsAnim; + int newAnim = -1; + switch ( anim ) + { + case BOTH_FORCEJUMP1: + newAnim = BOTH_FORCELAND1;//BOTH_FORCEINAIR1; + break; + case BOTH_FORCEJUMPBACK1: + newAnim = BOTH_FORCELANDBACK1;//BOTH_FORCEINAIRBACK1; + break; + case BOTH_FORCEJUMPLEFT1: + newAnim = BOTH_FORCELANDLEFT1;//BOTH_FORCEINAIRLEFT1; + break; + case BOTH_FORCEJUMPRIGHT1: + newAnim = BOTH_FORCELANDRIGHT1;//BOTH_FORCEINAIRRIGHT1; + break; + } + if ( newAnim != -1 ) + { + int parts = SETANIM_BOTH; + if ( pm->ps->weaponTime ) + {//FIXME: really only care if we're in a saber attack anim... + parts = SETANIM_LEGS; + } + + PM_SetAnim( pm, parts, newAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + */ +} +void PM_SwimFloatAnim( void ) +{ + int legsAnim = pm->ps->legsAnim; + //FIXME: no start or stop anims + if ( pm->cmd.forwardmove || pm->cmd.rightmove || pm->cmd.upmove ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_SWIMFORWARD,SETANIM_FLAG_NORMAL); + } + else + {//stopping + if ( legsAnim == BOTH_SWIMFORWARD ) + {//I was swimming + if ( !pm->ps->legsAnimTimer ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_SWIM_IDLE1,SETANIM_FLAG_NORMAL); + } + } + else + {//idle + if ( !(pm->ps->pm_flags&PMF_DUCKED) && pm->cmd.upmove >= 0 ) + {//not crouching + PM_SetAnim(pm,SETANIM_LEGS,BOTH_SWIM_IDLE1,SETANIM_FLAG_NORMAL); + } + } + } +} + +/* +=============== +PM_Footsteps +=============== +*/ +static void PM_Footsteps( void ) +{ + float bobmove; + int old, oldAnim; + qboolean footstep = qfalse; + qboolean validNPC = qfalse; + qboolean flipping = qfalse; + int setAnimFlags = SETANIM_FLAG_NORMAL; + + if( pm->gent == NULL || pm->gent->client == NULL ) + return; + + if ( (pm->ps->eFlags&EF_HELD_BY_WAMPA) ) + { + PM_SetAnim( pm, SETANIM_BOTH, BOTH_HANG_IDLE, SETANIM_FLAG_NORMAL ); + if ( pm->ps->legsAnim == BOTH_HANG_IDLE ) + { + if ( pm->ps->torsoAnimTimer < 100 ) + { + pm->ps->torsoAnimTimer = 100; + } + if ( pm->ps->legsAnimTimer < 100 ) + { + pm->ps->legsAnimTimer = 100; + } + } + return; + } + + if ( PM_SpinningSaberAnim( pm->ps->legsAnim ) && pm->ps->legsAnimTimer ) + {//spinning + return; + } + if ( PM_InKnockDown( pm->ps ) || PM_InRoll( pm->ps )) + {//in knockdown + return; + } + if ( (pm->ps->eFlags&EF_FORCE_DRAINED) ) + {//being drained + //PM_SetAnim( pm, SETANIM_LEGS, BOTH_HUGGEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + return; + } + if ( (pm->ps->forcePowersActive&(1<ps->forceDrainEntityNum < ENTITYNUM_WORLD ) + {//draining + //PM_SetAnim( pm, SETANIM_LEGS, BOTH_HUGGER1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + return; + } + else if (pm->gent->NPC && pm->gent->NPC->aiFlags & NPCAI_KNEEL) + {//kneeling + return; + } + + if( pm->gent->NPC != NULL ) + { + validNPC = qtrue; + } + + pm->gent->client->renderInfo.legsFpsMod = 1.0f; + //PM_ResetAnkleAngles(); + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0] + + pm->ps->velocity[1] * pm->ps->velocity[1] ); + + if ( pm->ps->legsAnim == BOTH_FLIP_F || + pm->ps->legsAnim == BOTH_FLIP_B || + pm->ps->legsAnim == BOTH_FLIP_L || + pm->ps->legsAnim == BOTH_FLIP_R || + pm->ps->legsAnim == BOTH_ALORA_FLIP_1 || + pm->ps->legsAnim == BOTH_ALORA_FLIP_2 || + pm->ps->legsAnim == BOTH_ALORA_FLIP_3 ) + { + flipping = qtrue; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE + || ( pm->watertype & CONTENTS_LADDER ) + || pm->ps->waterHeightLevel >= WHL_TORSO ) + {//in air or submerged in water or in ladder + // airborne leaves position in cycle intact, but doesn't advance + if ( pm->waterlevel > 0 ) + { + if ( pm->watertype & CONTENTS_LADDER ) + {//FIXME: check for watertype, save waterlevel for whether to play + //the get off ladder transition anim! + if ( pm->ps->velocity[2] ) + {//going up or down it + int anim; + if ( pm->ps->velocity[2] > 0 ) + { + anim = BOTH_LADDER_UP1; + } + else + { + anim = BOTH_LADDER_DWN1; + } + PM_SetAnim( pm, SETANIM_LEGS, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( pm->waterlevel >= 2 ) //arms on ladder + { + PM_SetAnim( pm, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if (fabs(pm->ps->velocity[2]) >5) { + bobmove = 0.005 * fabs(pm->ps->velocity[2]); // climbing bobs slow + if (bobmove > 0.3) + bobmove = 0.3F; + goto DoFootSteps; + } + } + else + { + PM_SetAnim( pm, SETANIM_LEGS, BOTH_LADDER_IDLE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + pm->ps->legsAnimTimer += 300; + if ( pm->waterlevel >= 2 ) //arms on ladder + { + PM_SetAnim( pm, SETANIM_TORSO, BOTH_LADDER_IDLE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + pm->ps->torsoAnimTimer += 300; + } + } + return; + } + else if ( pm->ps->waterHeightLevel >= WHL_TORSO + && ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + ||pm->ps->weapon==WP_SABER||pm->ps->weapon==WP_NONE||pm->ps->weapon==WP_MELEE) )//pm->waterlevel > 1 ) //in deep water + { + if ( !PM_ForceJumpingUp( pm->gent ) ) + { + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE && (pm->ps->pm_flags&PMF_DUCKED) ) + { + if ( !flipping ) + {//you can crouch under water if feet are on ground + if ( pm->cmd.forwardmove || pm->cmd.rightmove ) + { + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1WALKBACK,setAnimFlags); + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1WALK,setAnimFlags); + } + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1,SETANIM_FLAG_NORMAL); + } + return; + } + } + PM_SwimFloatAnim(); + if ( pm->ps->legsAnim != BOTH_SWIM_IDLE1 ) + {//moving + old = pm->ps->bobCycle; + bobmove = 0.15f; // swim is a slow cycle + pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; + + // if we just crossed a cycle boundary, play an apropriate footstep event + if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) + { + PM_AddEvent( EV_SWIM ); + } + } + } + return; + } + else + {//hmm, in water, but not high enough to swim + //fall through to walk/run/stand + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//unless in the air + //NOTE: this is a dupe of the code just below... for when you are not in the water at all + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + if ( !flipping ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1,SETANIM_FLAG_NORMAL); + } + } + else if ( pm->ps->gravity <= 0 )//FIXME: or just less than normal? + { + if ( pm->gent + && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_BOBAFETT ||pm->gent->client->NPC_class == CLASS_ROCKETTROOPER) + && pm->gent->client->moveType == MT_FLYSWIM ) + {//flying around with jetpack + //do something else? + PM_JetPackAnim(); + } + else + { + PM_SwimFloatAnim(); + } + } + return; + } + } + } + else + { + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + if ( !flipping ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1,SETANIM_FLAG_NORMAL); + } + } + else if ( pm->ps->gravity <= 0 )//FIXME: or just less than normal? + { + if ( pm->gent + && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_BOBAFETT||pm->gent->client->NPC_class == CLASS_ROCKETTROOPER) + && pm->gent->client->moveType == MT_FLYSWIM ) + {//flying around with jetpack + //do something else? + PM_JetPackAnim(); + } + else + { + PM_SwimFloatAnim(); + } + } + return; + } + } + + if ( PM_SwimmingAnim( pm->ps->legsAnim ) && pm->waterlevel < 2 ) + {//legs are in swim anim, and not swimming, be sure to override it + setAnimFlags |= SETANIM_FLAG_OVERRIDE; + } + + // if not trying to move + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) + { + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_ATST ) + { + if ( !PM_AdjustStandAnimForSlope() ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else if ( pm->ps->pm_flags & PMF_DUCKED ) + { + if( !PM_InOnGroundAnim( pm->ps ) ) + { + if ( !PM_AdjustStandAnimForSlope() ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1,SETANIM_FLAG_NORMAL); + } + } + } + else + { + if ( pm->ps->legsAnimTimer && PM_LandingAnim( pm->ps->legsAnim ) ) + {//still in a landing anim, let it play + return; + } + if ( pm->ps->legsAnimTimer + && (pm->ps->legsAnim == BOTH_THERMAL_READY + ||pm->ps->legsAnim == BOTH_THERMAL_THROW + ||pm->ps->legsAnim == BOTH_ATTACK10) ) + {//still in a thermal anim, let it play + return; + } + qboolean saberInAir = qtrue; + if ( pm->ps->saberInFlight ) + {//guiding saber + if ( PM_SaberInBrokenParry( pm->ps->saberMove ) || pm->ps->saberBlocked == BLOCKED_PARRY_BROKEN || PM_DodgeAnim( pm->ps->torsoAnim ) ) + {//we're stuck in a broken parry + saberInAir = qfalse; + } + if ( pm->ps->saberEntityNum < ENTITYNUM_NONE && pm->ps->saberEntityNum > 0 )//player is 0 + {// + if ( &g_entities[pm->ps->saberEntityNum] != NULL && g_entities[pm->ps->saberEntityNum].s.pos.trType == TR_STATIONARY ) + {//fell to the ground and we're not trying to pull it back + saberInAir = qfalse; + } + } + } + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_GALAKMECH ) + {//NOTE: stand1 is with the helmet retracted, stand1to2 is the helmet going into place + PM_SetAnim( pm, SETANIM_BOTH, BOTH_STAND2, SETANIM_FLAG_NORMAL ); + } + else if ( pm->ps->weapon == WP_SABER + && pm->ps->saberInFlight + && saberInAir + && (!pm->ps->dualSabers || !pm->ps->saber[1].Active())) + { + if ( !PM_AdjustStandAnimForSlope() ) + { + if ( pm->ps->legsAnim != BOTH_LOSE_SABER + || !pm->ps->legsAnimTimer ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_SABERPULL,SETANIM_FLAG_NORMAL); + } + } + } + else if ( (pm->ps->weapon == WP_SABER + &&pm->ps->SaberLength()>0 + &&!pm->ps->saberInFlight + &&!PM_SaberDrawPutawayAnim( pm->ps->legsAnim )) ) + { + if ( !PM_AdjustStandAnimForSlope() ) + { + int legsAnim; + if ( (pm->ps->torsoAnim == BOTH_SPINATTACK6 + || pm->ps->torsoAnim == BOTH_SPINATTACK7 + || PM_SaberInAttack( pm->ps->saberMove ) + || PM_SaberInTransitionAny( pm->ps->saberMove )) + && pm->ps->legsAnim != BOTH_FORCELONGLEAP_LAND + && (pm->ps->groundEntityNum == ENTITYNUM_NONE//in air + || (!PM_JumpingAnim( pm->ps->torsoAnim )&&!PM_InAirKickingAnim( pm->ps->torsoAnim ))) )//OR: on ground and torso not in a jump anim + { + legsAnim = pm->ps->torsoAnim; + } + else + { + switch ( pm->ps->saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + legsAnim = BOTH_SABERFAST_STANCE; + break; + case SS_STRONG: + legsAnim = BOTH_SABERSLOW_STANCE; + break; + case SS_DUAL: + legsAnim = BOTH_SABERDUAL_STANCE; + break; + case SS_STAFF: + legsAnim = BOTH_SABERSTAFF_STANCE; + break; + case SS_NONE: + case SS_MEDIUM: + case SS_DESANN: + default: + legsAnim = BOTH_STAND2; + break; + } + } + PM_SetAnim(pm,SETANIM_LEGS,legsAnim,SETANIM_FLAG_NORMAL); + } + } + else if( (validNPC && pm->ps->weapon > WP_SABER && pm->ps->weapon < WP_DET_PACK ))// && pm->gent->client->race != RACE_BORG))//Being careful or carrying a 2-handed weapon + {//Squadmates use BOTH_STAND3 + oldAnim = pm->ps->legsAnim; + if(oldAnim != BOTH_GUARD_LOOKAROUND1 && oldAnim != BOTH_GUARD_IDLE1 + && oldAnim != BOTH_STAND2TO4 + && oldAnim != BOTH_STAND4TO2 && oldAnim != BOTH_STAND4 ) + {//Don't auto-override the guard idles + if ( !PM_AdjustStandAnimForSlope() ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND3,SETANIM_FLAG_NORMAL); + //if(oldAnim != BOTH_STAND2 && pm->ps->legsAnim == BOTH_STAND2) + //{ + // pm->ps->legsAnimTimer = 500; + //} + } + } + } + else + { + if ( !PM_AdjustStandAnimForSlope() ) + { + // FIXME: Do we need this here... The imps stand is 4, not 1... + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_IMPERIAL ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND4,SETANIM_FLAG_NORMAL); + } + else if ( pm->ps->weapon == WP_TUSKEN_STAFF ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + else + { + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_RANCOR ) + { + if ( pm->gent->count ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND4,SETANIM_FLAG_NORMAL); + } + else if ( pm->gent->enemy || pm->gent->wait ) + {//have an enemy or have had one since we spawned + PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND2,SETANIM_FLAG_NORMAL); + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_WAMPA ) + { + if ( pm->gent->count ) + {//holding a victim + PM_SetAnim(pm,SETANIM_LEGS,BOTH_HOLD_IDLE/*BOTH_STAND2*/,SETANIM_FLAG_NORMAL); + } + else + {//not holding a victim + PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + } + } + } + return; + } + + //maybe call this every frame, even when moving? + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_ATST ) + { + PM_FootSlopeTrace( NULL, NULL ); + } + + //trying to move laterally + if ( (pm->ps->eFlags&EF_IN_ATST) + || (pm->gent&&pm->gent->client&&pm->gent->client->NPC_class==CLASS_RANCOR)//does this catch NPCs, too? + || (pm->gent&&pm->gent->client&&pm->gent->client->NPC_class==CLASS_WAMPA) )//does this catch NPCs, too? + {//atst, Rancor & Wampa, only override turn anims on legs (no torso) + if ( pm->ps->legsAnim == BOTH_TURN_LEFT1 || + pm->ps->legsAnim == BOTH_TURN_RIGHT1 ) + {//moving overrides turning + setAnimFlags |= SETANIM_FLAG_OVERRIDE; + } + } + else + {//all other NPCs... + if ( (PM_InSaberAnim( pm->ps->legsAnim ) && !PM_SpinningSaberAnim( pm->ps->legsAnim )) + || PM_SaberStanceAnim( pm->ps->legsAnim ) + || PM_SaberDrawPutawayAnim( pm->ps->legsAnim ) + || pm->ps->legsAnim == BOTH_SPINATTACK6//not a full-body spin, just spinning the saber + || pm->ps->legsAnim == BOTH_SPINATTACK7//not a full-body spin, just spinning the saber + || pm->ps->legsAnim == BOTH_BUTTON_HOLD + || pm->ps->legsAnim == BOTH_BUTTON_RELEASE + || pm->ps->legsAnim == BOTH_THERMAL_READY + || pm->ps->legsAnim == BOTH_THERMAL_THROW + || pm->ps->legsAnim == BOTH_ATTACK10 + || PM_LandingAnim( pm->ps->legsAnim ) + || PM_PainAnim( pm->ps->legsAnim ) + || PM_ForceAnim( pm->ps->legsAnim )) + {//legs are in a saber anim, and not spinning, be sure to override it + setAnimFlags |= SETANIM_FLAG_OVERRIDE; + } + } + + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + bobmove = 0.5; // ducked characters bob much faster + if( !PM_InOnGroundAnim( pm->ps ) //not on the ground + && ( !PM_InRollIgnoreTimer( pm->ps )||(!pm->ps->legsAnimTimer&&pm->cmd.upmove<0) ) )//not in a roll (or you just finished one and you're still holding crouch) + { + qboolean rolled = qfalse; + if ( PM_RunningAnim( pm->ps->legsAnim ) + || pm->ps->legsAnim == BOTH_FORCEHEAL_START + || PM_CanRollFromSoulCal( pm->ps )) + {//roll! + rolled = PM_TryRoll(); + } + if ( !rolled ) + { + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1WALKBACK,setAnimFlags); + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_CROUCH1WALK,setAnimFlags); + } + if ( !Q_irand( 0, 19 ) ) + {//5% chance of making an alert + AddSoundEvent( pm->gent, pm->ps->origin, 16, AEL_MINOR, qtrue, qtrue ); + } + } + else + {//rolling is a little noisy + AddSoundEvent( pm->gent, pm->ps->origin, 128, AEL_MINOR, qtrue, qtrue ); + } + } + // ducked characters never play footsteps + } + else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + {//Moving backwards + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) + {//running backwards + bobmove = 0.4F; // faster speeds bob faster + if ( pm->ps->weapon == WP_SABER && pm->ps->SaberActive() ) + { + /* + if ( pm->ps->saberAnimLevel == SS_STAFF ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUNBACK_STAFF,setAnimFlags); + } + else + */ + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUNBACK2,setAnimFlags); + } + } + else if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_RANCOR ) + {//no run anim + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALKBACK1,setAnimFlags); + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUNBACK1,setAnimFlags); + } + footstep = qtrue; + } + else + {//walking backwards + bobmove = 0.3F; // faster speeds bob faster + if ( pm->ps->weapon == WP_SABER && pm->ps->SaberActive() ) + { + if ( pm->ps->saberAnimLevel == SS_DUAL ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALKBACK_DUAL,setAnimFlags); + } + else if ( pm->ps->saberAnimLevel == SS_STAFF ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALKBACK_STAFF,setAnimFlags); + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALKBACK2,setAnimFlags); + } + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALKBACK1,setAnimFlags); + } + if ( !Q_irand( 0, 9 ) ) + {//10% chance of a small alert, mainly for the sand_creature + AddSoundEvent( pm->gent, pm->ps->origin, 16, AEL_MINOR, qtrue, qtrue ); + } + } + } + else + { + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_GALAKMECH ) + { + bobmove = 0.3F; // walking bobs slow + if ( pm->ps->weapon == WP_NONE ) + {//helmet retracted + PM_SetAnim( pm, SETANIM_BOTH, BOTH_WALK1, SETANIM_FLAG_NORMAL ); + } + else + {//helmet in place + PM_SetAnim( pm, SETANIM_BOTH, BOTH_WALK2, SETANIM_FLAG_NORMAL ); + } + AddSoundEvent( pm->gent, pm->ps->origin, 128, AEL_SUSPICIOUS, qtrue, qtrue ); + } + else + { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) + {//running + bobmove = 0.4F; // faster speeds bob faster + if ( pm->ps->weapon == WP_SABER && pm->ps->SaberActive() ) + { + if ( pm->ps->saberAnimLevel == SS_DUAL ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUN_DUAL,setAnimFlags); + } + else if ( pm->ps->saberAnimLevel == SS_STAFF ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUN_STAFF,setAnimFlags); + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUN2,setAnimFlags); + } + } + else + { + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_JAWA ) + { + //if ( pm->gent->enemy && (pm->ps->weapon == WP_NONE || pm->ps->weapon == WP_MELEE) ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUN4,setAnimFlags); + } + /* + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUN1,setAnimFlags); + } + */ + } + else if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_ATST ) + { + if ( pm->ps->legsAnim != BOTH_RUN1 ) + { + if ( pm->ps->legsAnim != BOTH_RUN1START ) + {//Hmm, he should really start slow and have to accelerate... also need to do this for stopping + PM_SetAnim( pm,SETANIM_LEGS, BOTH_RUN1START, setAnimFlags|SETANIM_FLAG_HOLD ); + } + else if ( !pm->ps->legsAnimTimer ) + { + PM_SetAnim( pm, SETANIM_LEGS, BOTH_RUN1, setAnimFlags ); + } + } + else + { + PM_SetAnim( pm, SETANIM_LEGS, BOTH_RUN1, setAnimFlags ); + } + } + else if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_WAMPA ) + { + if ( pm->gent->NPC && pm->gent->NPC->stats.runSpeed == 300 ) + {//full on run, on all fours + PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUN1,SETANIM_FLAG_NORMAL); + } + else + {//regular, upright run + PM_SetAnim(pm,SETANIM_LEGS,BOTH_RUN2,SETANIM_FLAG_NORMAL); + } + } + else if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_RANCOR ) + {//no run anim + PM_SetAnim( pm, SETANIM_LEGS, BOTH_WALK1, setAnimFlags ); + } + else + { + PM_SetAnim( pm, SETANIM_LEGS, BOTH_RUN1, setAnimFlags ); + } + } + footstep = qtrue; + } + else + {//walking forward + bobmove = 0.3F; // walking bobs slow + if ( pm->ps->weapon == WP_SABER && pm->ps->SaberActive() ) + { + if ( pm->ps->saberAnimLevel == SS_DUAL ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALK_DUAL,setAnimFlags); + } + else if ( pm->ps->saberAnimLevel == SS_STAFF ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALK_STAFF,setAnimFlags); + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALK2,setAnimFlags); + } + } + else if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_WAMPA ) + { + if ( pm->gent->health <= 50 ) + {//hurt walk + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALK2,SETANIM_FLAG_NORMAL); + } + else + {//normal walk + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALK1,SETANIM_FLAG_NORMAL); + } + } + else + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_WALK1,setAnimFlags); + } + //Enemy NPCs always make footsteps for the benefit of the player + if ( pm->gent + && pm->gent->NPC + && pm->gent->client + && pm->gent->client->playerTeam != TEAM_PLAYER ) + { + footstep = qtrue; + } + else if ( !Q_irand( 0, 9 ) ) + {//10% chance of a small alert, mainly for the sand_creature + AddSoundEvent( pm->gent, pm->ps->origin, 16, AEL_MINOR, qtrue, qtrue ); + } + } + } + } + + if(pm->gent != NULL) + { + if( pm->gent->client->renderInfo.legsFpsMod > 2 ) + { + pm->gent->client->renderInfo.legsFpsMod = 2; + } + else if(pm->gent->client->renderInfo.legsFpsMod < 0.5) + { + pm->gent->client->renderInfo.legsFpsMod = 0.5; + } + } + +DoFootSteps: + + // check for footstep / splash sounds + old = pm->ps->bobCycle; + pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; + + // if we just crossed a cycle boundary, play an apropriate footstep event + if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) + { + if ( pm->watertype & CONTENTS_LADDER ) + { + if ( !pm->noFootsteps ) + { + if (pm->ps->groundEntityNum == ENTITYNUM_NONE) {// on ladder + PM_AddEvent( EV_FOOTSTEP_METAL ); + } else { + //PM_AddEvent( PM_FootstepForSurface() ); //still on ground + } + } + if ( pm->gent && pm->gent->s.number == 0 ) + { +// if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) + { + AddSoundEvent( pm->gent, pm->ps->origin, 128, AEL_MINOR, qtrue ); + } + } + } + else if ( pm->waterlevel == 0 ) + { + // on ground will only play sounds if running + if ( footstep ) + { + if ( !pm->noFootsteps ) + { + //PM_AddEvent( PM_FootstepForSurface() ); + } + if ( pm->gent && pm->gent->s.number == 0 ) + { + vec3_t bottom; + + VectorCopy( pm->ps->origin, bottom ); + bottom[2] += pm->mins[2]; +// if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) + { + AddSoundEvent( pm->gent, bottom, 256, AEL_MINOR, qtrue, qtrue ); + } + } + } + } + else if ( pm->waterlevel == 1 ) + { + // splashing + if ( pm->ps->waterHeightLevel >= WHL_KNEES ) + { + PM_AddEvent( EV_FOOTWADE ); + } + else + { + PM_AddEvent( EV_FOOTSPLASH ); + } + if ( pm->gent && pm->gent->s.number == 0 ) + { + vec3_t bottom; + + VectorCopy( pm->ps->origin, bottom ); + bottom[2] += pm->mins[2]; +// if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) + { + AddSoundEvent( pm->gent, pm->ps->origin, 384, AEL_SUSPICIOUS, qfalse, qtrue );//was bottom + AddSightEvent( pm->gent, pm->ps->origin, 512, AEL_MINOR ); + } + } + } + else if ( pm->waterlevel == 2 ) + { + // wading / swimming at surface + /* + if ( pm->ps->waterHeightLevel >= WHL_TORSO ) + { + PM_AddEvent( EV_SWIM ); + } + else + */ + { + PM_AddEvent( EV_FOOTWADE ); + } + if ( pm->gent && pm->gent->s.number == 0 ) + { +// if ( pm->gent->client && pm->gent->client->playerTeam != TEAM_DISGUISE ) + { + AddSoundEvent( pm->gent, pm->ps->origin, 256, AEL_MINOR, qfalse, qtrue ); + AddSightEvent( pm->gent, pm->ps->origin, 512, AEL_SUSPICIOUS ); + } + } + } + else + {// or no sound when completely underwater...? + PM_AddEvent( EV_SWIM ); + } + } +} + +/* +============== +PM_WaterEvents + +Generate sound events for entering and leaving water +============== +*/ +static void PM_WaterEvents( void ) { // FIXME? + + qboolean impact_splash = qfalse; + + if ( pm->watertype & CONTENTS_LADDER ) //fake water for ladder + { + return; + } + // + // if just entered a water volume, play a sound + // + if (!pml.previous_waterlevel && pm->waterlevel) + { + if ( (pm->watertype&CONTENTS_LAVA) ) + { + PM_AddEvent( EV_LAVA_TOUCH ); + } + else + { + PM_AddEvent( EV_WATER_TOUCH ); + } + if ( pm->gent ) + { + if ( VectorLengthSquared( pm->ps->velocity ) > 40000 ) + { + impact_splash = qtrue; + } + + if ( pm->ps->clientNum < MAX_CLIENTS ) + { + AddSoundEvent( pm->gent, pm->ps->origin, 384, AEL_SUSPICIOUS ); + AddSightEvent( pm->gent, pm->ps->origin, 512, AEL_SUSPICIOUS ); + } + } + } + + // + // if just completely exited a water volume, play a sound + // + if (pml.previous_waterlevel && !pm->waterlevel) + { + if ( (pm->watertype&CONTENTS_LAVA) ) + { + PM_AddEvent( EV_LAVA_LEAVE ); + } + else + { + PM_AddEvent( EV_WATER_LEAVE ); + } + if ( pm->gent && VectorLengthSquared( pm->ps->velocity ) > 40000 ) + { + impact_splash = qtrue; + } + if ( pm->gent && pm->ps->clientNum < MAX_CLIENTS ) + { + AddSoundEvent( pm->gent, pm->ps->origin, 384, AEL_SUSPICIOUS ); + AddSightEvent( pm->gent, pm->ps->origin, 512, AEL_SUSPICIOUS ); + } + } + + if ( impact_splash ) + { + //play the splash effect + trace_t tr; + vec3_t axis[3], angs, start, end; + + VectorSet( angs, 0, pm->gent->currentAngles[YAW], 0 ); + AngleVectors( angs, axis[2], axis[1], axis[0] ); + + VectorCopy( pm->ps->origin, start ); + VectorCopy( pm->ps->origin, end ); + + // FIXME: set start and end better + start[2] += 10; + end[2] -= 40; + + gi.trace( &tr, start, vec3_origin, vec3_origin, end, pm->gent->s.number, MASK_WATER ); + + if ( tr.fraction < 1.0f ) + { + if ( (tr.contents&CONTENTS_LAVA) ) + { + G_PlayEffect( "env/lava_splash", tr.endpos, axis ); + } + else if ( (tr.contents&CONTENTS_SLIME) ) + { + G_PlayEffect( "env/acid_splash", tr.endpos, axis ); + } + else //must be water + { + G_PlayEffect( "env/water_impact", tr.endpos, axis ); + } + } + } + + // + // check for head just going under water + // + if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { + if ( (pm->watertype&CONTENTS_LAVA) ) + { + PM_AddEvent( EV_LAVA_UNDER ); + } + else + { + PM_AddEvent( EV_WATER_UNDER ); + } + + if ( pm->gent && pm->ps->clientNum < MAX_CLIENTS ) + { + AddSoundEvent( pm->gent, pm->ps->origin, 256, AEL_MINOR ); + AddSightEvent( pm->gent, pm->ps->origin, 384, AEL_MINOR ); + } + } + + // + // check for head just coming out of water + // + if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { + if ( !pm->gent || !pm->gent->client || pm->gent->client->airOutTime < level.time + 2000 ) + {//only do this if we were drowning or about to start drowning + PM_AddEvent( EV_WATER_CLEAR ); + } + else + { + if ( (pm->watertype&CONTENTS_LAVA) ) + { + PM_AddEvent( EV_LAVA_LEAVE ); + } + else + { + PM_AddEvent( EV_WATER_LEAVE ); + } + } + if ( pm->gent && pm->ps->clientNum < MAX_CLIENTS ) + { + AddSoundEvent( pm->gent, pm->ps->origin, 256, AEL_MINOR ); + AddSightEvent( pm->gent, pm->ps->origin, 384, AEL_SUSPICIOUS ); + } + } +} + + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +static void PM_BeginWeaponChange( int weapon ) { + + if ( pm->gent && pm->gent->client && pm->gent->client->pers.enterTime >= level.time - 500 ) + {//just entered map + if ( weapon == WP_NONE && pm->ps->weapon != weapon ) + {//don't switch to weapon none if just entered map + return; + } + } + + if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { + return; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + return; + } + + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + return; + } + + if ( cg.time > 0 ) + {//this way we don't get that annoying change weapon sound every time a map starts + PM_AddEvent( EV_CHANGE_WEAPON ); + } + + pm->ps->weaponstate = WEAPON_DROPPING; + pm->ps->weaponTime += 200; + if ( !(pm->ps->eFlags&EF_HELD_BY_WAMPA) && !G_IsRidingVehicle(pm->gent)) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_DROPWEAP1,SETANIM_FLAG_HOLD); + } + + // turn of any kind of zooming when weapon switching....except the LA Goggles + if ( pm->ps->clientNum == 0 ) + { + if ( cg.zoomMode > 0 && cg.zoomMode < 3 ) + { + cg.zoomMode = 0; + cg.zoomTime = cg.time; + } + } + + if ( pm->gent + && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_ATST||pm->gent->client->NPC_class == CLASS_RANCOR) ) + { + if ( pm->ps->clientNum < MAX_CLIENTS ) + { + gi.cvar_set( "cg_thirdperson", "1" ); + } + } + else if ( weapon == WP_SABER ) + {//going to switch to lightsaber + } + else + { + if ( pm->ps->weapon == WP_SABER ) + {//going to switch away from saber + if ( pm->gent ) + { + G_SoundOnEnt( pm->gent, CHAN_WEAPON, "sound/weapons/saber/saberoffquick.wav" ); +#ifdef _IMMERSION + G_Force( pm->gent, G_ForceIndex( "fffx/weapons/saber/saberoffquick", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + } + if (!G_IsRidingVehicle(pm->gent)) + { + PM_SetSaberMove(LS_PUTAWAY); + } + } + //put this back in because saberActive isn't being set somewhere else anymore + pm->ps->SaberDeactivate(); + pm->ps->SetSaberLength( 0.0f ); + } +} + + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +static void PM_FinishWeaponChange( void ) { + int weapon; + qboolean trueSwitch = qtrue; + + if ( pm->gent && pm->gent->client && pm->gent->client->pers.enterTime >= level.time - 500 ) + {//just entered map + if ( pm->cmd.weapon == WP_NONE && pm->ps->weapon != pm->cmd.weapon ) + {//don't switch to weapon none if just entered map + return; + } + } + weapon = pm->cmd.weapon; + if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { + weapon = WP_NONE; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + weapon = WP_NONE; + } + + if ( pm->ps->weapon == weapon ) + { + trueSwitch = qfalse; + } + //int oldWeap = pm->ps->weapon; + pm->ps->weapon = weapon; + pm->ps->weaponstate = WEAPON_RAISING; + pm->ps->weaponTime += 250; + + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_ATST ) + {//do nothing + } + else if ( weapon == WP_SABER ) + {//turn on the lightsaber + //FIXME: somehow sometimes I still end up with 2 weapons in hand... usually if I + // cycle weapons fast enough that I end up in 1st person lightsaber, then + // somehow throw the saber and switch to another weapon (all in 1st person), + // making my saber drop to the ground... when I switch back to the saber, it + // does not remove the current weapon model and then, when I pull the saber + // back to my hand, I have 2 weaponModels active...? + if ( pm->gent ) + {// remove gun if we had it. + G_RemoveWeaponModels( pm->gent ); + } + + if ( !pm->ps->saberInFlight || pm->ps->dualSabers ) + {//if it's not in flight or lying around, turn it on! + //FIXME: AddSound/Sight Event + //FIXME: don't do this if just loaded a game + if ( trueSwitch ) + {//actually did switch weapons, turn it on + if ( PM_RidingVehicle() ) + {//only turn on the first saber's first blade...? + pm->ps->SaberBladeActivate( 0, 0 ); + } + else + { + pm->ps->SaberActivate(); + } + pm->ps->SetSaberLength( 0.0f ); + } + + if ( pm->gent ) + { + WP_SaberAddG2SaberModels( pm->gent ); + } + } + else + {//FIXME: pull it back to us? + } + + if ( pm->gent ) + { + WP_SaberInitBladeData( pm->gent ); + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + { + gi.cvar_set( "cg_thirdperson", "1" ); + } + } + if ( trueSwitch ) + {//actually did switch weapons, play anim + if (!G_IsRidingVehicle(pm->gent)) + { + PM_SetSaberMove(LS_DRAW); + } + } + } + else + {//switched away from saber + if ( pm->gent ) + { + // remove the sabre if we had it. + G_RemoveWeaponModels( pm->gent ); + if (weaponData[weapon].weaponMdl[0]) { //might be NONE, so check if it has a model + G_CreateG2AttachedWeaponModel( pm->gent, weaponData[weapon].weaponMdl, pm->gent->handRBolt, 0 ); + } + } + + if ( !(pm->ps->eFlags&EF_HELD_BY_WAMPA) ) + { + if ( pm->ps->weapon != WP_THERMAL + && pm->ps->weapon != WP_TRIP_MINE + && pm->ps->weapon != WP_DET_PACK + && !G_IsRidingVehicle(pm->gent)) + { + PM_SetAnim(pm,SETANIM_TORSO,TORSO_RAISEWEAP1,SETANIM_FLAG_HOLD); + } + } + + if ( pm->ps->clientNum < MAX_CLIENTS + && cg_gunAutoFirst.integer + && !PM_RidingVehicle() +// && oldWeap == WP_SABER + && weapon != WP_NONE ) + { + gi.cvar_set( "cg_thirdperson", "0" ); + } + pm->ps->saberMove = LS_NONE; + pm->ps->saberBlocking = BLK_NO; + pm->ps->saberBlocked = BLOCKED_NONE; + } +} + +int PM_ReadyPoseForSaberAnimLevel( void ) +{ + int anim = BOTH_STAND2; + if (PM_RidingVehicle()) + { + return -1; + } + switch ( pm->ps->saberAnimLevel ) + { + case SS_DUAL: + anim = BOTH_SABERDUAL_STANCE; + break; + case SS_STAFF: + anim = BOTH_SABERSTAFF_STANCE; + break; + case SS_FAST: + case SS_TAVION: + anim = BOTH_SABERFAST_STANCE; + break; + case SS_STRONG: + anim = BOTH_SABERSLOW_STANCE; + break; + case SS_NONE: + case SS_MEDIUM: + case SS_DESANN: + default: + anim = BOTH_STAND2; + break; + } + return anim; +} + +qboolean PM_CanDoDualDoubleAttacks( void ) +{ + //NOTE: assumes you're using SS_DUAL style and have both sabers on... + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + {//player + return qtrue; + } + if ( pm->gent && pm->gent->NPC && pm->gent->NPC->rank >= Q_irand( RANK_LT_COMM, RANK_CAPTAIN+2 ) ) + {//high-rank NPC + return qtrue; + } + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_ALORA ) + {//Alora + return qtrue; + } + return qfalse; +} + +void PM_SetJumped( float height, qboolean force ) +{ + pm->ps->velocity[2] = height; + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->pm_flags |= PMF_JUMPING; + pm->cmd.upmove = 0; + + if ( force ) + { + pm->ps->jumpZStart = pm->ps->origin[2]; + pm->ps->pm_flags |= PMF_SLOW_MO_FALL; + //start force jump + pm->ps->forcePowersActive |= (1<gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + else + { + PM_AddEvent( EV_JUMP ); + } +} + + +void PM_SetSaberMove(saberMoveName_t newMove) +{ + unsigned int setflags; + int anim; + int parts = SETANIM_TORSO; + qboolean manualBlocking = qfalse; + + if ( newMove < LS_NONE || newMove >= LS_MOVE_MAX ) + { + assert(0); + return; + } + + setflags = saberMoveData[newMove].animSetFlags; + anim = saberMoveData[newMove].animToUse; + + if ( (pm->ps->eFlags&EF_HELD_BY_WAMPA) ) + {//no anim + return; + } + + if ( cg_debugSaber.integer&0x01 && (newMove != LS_READY) ) + { + Com_Printf("SetSaberMove: From '%s' to '%s'\n", + saberMoveData[pm->ps->saberMove].name, + saberMoveData[newMove].name); + } + + if ( newMove == LS_READY || newMove == LS_A_FLIP_STAB || newMove == LS_A_FLIP_SLASH ) + {//finished with a kata (or in a special move) reset attack counter + pm->ps->saberAttackChainCount = 0; + } + else if ( PM_SaberInAttack( newMove ) ) + {//continuing with a kata, increment attack counter + //FIXME: maybe some contextual/style-specific logic in here + pm->ps->saberAttackChainCount++; + } + + if ( newMove == LS_READY ) + { + if ( pm->ps->saberBlockingTime > cg.time ) + { + manualBlocking = qtrue; + if ( pm->ps->saber[0].type == SABER_CLAW ) + { + anim = BOTH_INAIR1;//FIXME: is there a better anim for this? + } + else if ( pm->ps->dualSabers && pm->ps->saber[1].Active() ) + { + anim = BOTH_INAIR1; + } + else + { + anim = BOTH_P1_S1_T_; + } + } + else if ( pm->ps->saber[0].type == SABER_ARC ) + {//FIXME: need it's own style? + anim = BOTH_SABERFAST_STANCE; + } + else if ( (pm->ps->dualSabers && pm->ps->saber[1].Active()) ) + { + anim = BOTH_SABERDUAL_STANCE; + } + else if ( (pm->ps->SaberStaff() && (!pm->ps->saber[0].singleBladeStyle||pm->ps->saber[0].blade[1].active))//saber staff with more than first blade active + || pm->ps->saber[0].type == SABER_ARC ) + { + anim = BOTH_SABERSTAFF_STANCE; + } + else if ( pm->ps->saber[0].type == SABER_LANCE || pm->ps->saber[0].type == SABER_TRIDENT ) + {//FIXME: need some 2-handed forward-pointing anim + anim = BOTH_STAND1; + } + else + { + anim = PM_ReadyPoseForSaberAnimLevel(); + } + } + else if ( newMove == LS_DRAW ) + { + if ( PM_RunningAnim( pm->ps->torsoAnim ) ) + { + pm->ps->saberMove = newMove; + return; + } + if ( pm->ps->saber[0].style == SS_STAFF ) + { + anim = BOTH_S1_S7; + } + else if ( pm->ps->dualSabers && pm->ps->saber[0].style == SS_NONE && pm->ps->saber[1].style == SS_NONE ) + { + anim = BOTH_S1_S6; + } + if ( pm->ps->torsoAnim == BOTH_STAND1IDLE1 ) + { + setflags |= SETANIM_FLAG_OVERRIDE; + } + } + else if ( newMove == LS_PUTAWAY ) + { + if ( pm->ps->saber[0].style == SS_STAFF + && pm->ps->saber[0].blade[1].active ) + { + anim = BOTH_S7_S1; + } + else if ( pm->ps->dualSabers + && pm->ps->saber[0].style == SS_NONE + && pm->ps->saber[1].style == SS_NONE + && pm->ps->saber[1].Active() ) + { + anim = BOTH_S6_S1; + } + if ( PM_SaberStanceAnim( pm->ps->legsAnim ) && pm->ps->legsAnim != BOTH_STAND1 ) + { + parts = SETANIM_BOTH; + } + else + { + if ( PM_RunningAnim( pm->ps->torsoAnim ) ) + { + pm->ps->saberMove = newMove; + return; + } + parts = SETANIM_TORSO; + } + //FIXME: also dual + } + else if ( pm->ps->saberAnimLevel == SS_STAFF && newMove >= LS_S_TL2BR && newMove < LS_REFLECT_LL ) + {//staff has an entirely new set of anims, besides special attacks + //FIXME: include ready and draw/putaway? + //FIXME: get hand-made bounces and deflections? + if ( newMove >= LS_V1_BR && newMove <= LS_REFLECT_LL ) + {//there aren't 1-7, just 1, 6 and 7, so just set it + anim = BOTH_P7_S7_T_ + (anim-BOTH_P1_S1_T_);//shift it up to the proper set + } + else + {//add the appropriate animLevel + anim += (pm->ps->saberAnimLevel-FORCE_LEVEL_1) * SABER_ANIM_GROUP_SIZE; + } + } + else if ( (pm->ps->saberAnimLevel == SS_DUAL + || (pm->ps->dualSabers&& pm->ps->saber[1].Active())) + && newMove >= LS_S_TL2BR + && newMove < LS_REFLECT_LL ) + {//staff has an entirely new set of anims, besides special attacks + //FIXME: include ready and draw/putaway? + //FIXME: get hand-made bounces and deflections? + //FIXME: only do the dual FB & LR attacks when on ground? + if ( newMove >= LS_V1_BR && newMove <= LS_REFLECT_LL ) + {//there aren't 1-7, just 1, 6 and 7, so just set it + anim = BOTH_P6_S6_T_ + (anim-BOTH_P1_S1_T_);//shift it up to the proper set + } + else if ( ( newMove == LS_A_R2L || newMove == LS_S_R2L + || newMove == LS_A_L2R || newMove == LS_S_L2R ) + && PM_CanDoDualDoubleAttacks() + && G_CheckEnemyPresence( pm->gent, DIR_RIGHT, 150.0f ) + && G_CheckEnemyPresence( pm->gent, DIR_LEFT, 150.0f ) ) + {//enemy both on left and right + newMove = LS_DUAL_LR; + anim = saberMoveData[newMove].animToUse; + //probably already moved, but... + pm->cmd.rightmove = 0; + } + else if ( (newMove == LS_A_T2B || newMove == LS_S_T2B + || newMove == LS_A_BACK || newMove == LS_A_BACK_CR ) + && PM_CanDoDualDoubleAttacks() + && G_CheckEnemyPresence( pm->gent, DIR_FRONT, 150.0f ) + && G_CheckEnemyPresence( pm->gent, DIR_BACK, 150.0f ) ) + {//enemy both in front and back + newMove = LS_DUAL_FB; + anim = saberMoveData[newMove].animToUse; + //probably already moved, but... + pm->cmd.forwardmove = 0; + } + else + {//add the appropriate animLevel + anim += (pm->ps->saberAnimLevel-FORCE_LEVEL_1) * SABER_ANIM_GROUP_SIZE; + } + } + /* + else if ( newMove == LS_DRAW && pm->ps->saberAnimLevel == SS_STAFF )//pm->ps->SaberStaff() ) + {//hold saber out front as we turn it on + //FIXME: need a real "draw" anim for this (and put-away) + anim = BOTH_SABERSTAFF_STANCE; + } + */ + else if ( pm->ps->saberAnimLevel > FORCE_LEVEL_1 && + !PM_SaberInIdle( newMove ) && !PM_SaberInParry( newMove ) && !PM_SaberInKnockaway( newMove ) && !PM_SaberInBrokenParry( newMove ) && !PM_SaberInReflect( newMove ) && !PM_SaberInSpecial( newMove )) + {//readies, parries and reflections have only 1 level + if ( pm->ps->saber[0].type == SABER_LANCE || pm->ps->saber[0].type == SABER_TRIDENT ) + {//FIXME: hack for now - these use the fast anims, but slowed down. Should have own style + } + else + {//increment the anim to the next level of saber anims + anim += (pm->ps->saberAnimLevel-FORCE_LEVEL_1) * SABER_ANIM_GROUP_SIZE; + } + } + else if ( newMove == LS_KICK_F_AIR + || newMove == LS_KICK_B_AIR + || newMove == LS_KICK_R_AIR + || newMove == LS_KICK_L_AIR ) + { + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + PM_SetJumped( 200, qtrue ); + } + } + + // If the move does the same animation as the last one, we need to force a restart... +// if ( saberMoveData[pm->ps->saberMove].animToUse == anim && newMove > LS_PUTAWAY) + if ( ( pm->ps->torsoAnim == anim || pm->ps->legsAnim == anim ) + && newMove > LS_PUTAWAY ) + { + setflags |= SETANIM_FLAG_RESTART; + } + + if ( (anim == BOTH_STAND1 && (pm->ps->saber[0].type == SABER_ARC || (pm->ps->dualSabers && pm->ps->saber[1].Active())) ) + || anim == BOTH_STAND2 + //FIXME: temp hack to stop it from using run2 with staff + || (0 && anim == BOTH_SABERSTAFF_STANCE) + || anim == BOTH_SABERDUAL_STANCE + || anim == BOTH_SABERFAST_STANCE + || anim == BOTH_SABERSLOW_STANCE ) + {//match torso anim to walk/run anim if newMove is just LS_READY + //FIXME: play both_stand2_random1 when you've been idle for a while + switch ( pm->ps->legsAnim ) + { + case BOTH_WALK1: + case BOTH_WALK2: + case BOTH_WALK_STAFF: + case BOTH_WALK_DUAL: + case BOTH_WALKBACK1: + case BOTH_WALKBACK2: + case BOTH_WALKBACK_STAFF: + case BOTH_WALKBACK_DUAL: + case BOTH_RUN1: + case BOTH_RUN2: + case BOTH_RUN_STAFF: + case BOTH_RUN_DUAL: + case BOTH_RUNBACK1: + case BOTH_RUNBACK2: + case BOTH_RUNBACK_STAFF: + anim = pm->ps->legsAnim; + break; + } + } + + if ( !PM_RidingVehicle() ) + { + if ( !manualBlocking ) + { + if ( newMove == LS_A_LUNGE + || newMove == LS_A_JUMP_T__B_ + || newMove == LS_A_BACKSTAB + || newMove == LS_A_BACK + || newMove == LS_A_BACK_CR + || newMove == LS_ROLL_STAB + || newMove == LS_A_FLIP_STAB + || newMove == LS_A_FLIP_SLASH + || newMove == LS_JUMPATTACK_DUAL + || newMove == LS_JUMPATTACK_ARIAL_LEFT + || newMove == LS_JUMPATTACK_ARIAL_RIGHT + || newMove == LS_JUMPATTACK_CART_LEFT + || newMove == LS_JUMPATTACK_CART_RIGHT + || newMove == LS_JUMPATTACK_STAFF_LEFT + || newMove == LS_JUMPATTACK_STAFF_RIGHT + || newMove == LS_BUTTERFLY_LEFT + || newMove == LS_BUTTERFLY_RIGHT + || newMove == LS_A_BACKFLIP_ATK + || newMove == LS_STABDOWN + || newMove == LS_STABDOWN_STAFF + || newMove == LS_STABDOWN_DUAL + || newMove == LS_DUAL_SPIN_PROTECT + || newMove == LS_STAFF_SOULCAL + || newMove == LS_A1_SPECIAL + || newMove == LS_A2_SPECIAL + || newMove == LS_A3_SPECIAL + || newMove == LS_UPSIDE_DOWN_ATTACK + || newMove == LS_PULL_ATTACK_STAB + || newMove == LS_PULL_ATTACK_SWING + || PM_KickMove( newMove ) ) + { + parts = SETANIM_BOTH; + } + else if ( PM_SpinningSaberAnim( anim ) ) + {//spins must be played on entire body + parts = SETANIM_BOTH; + } + else if ( (!pm->cmd.forwardmove&&!pm->cmd.rightmove&&!pm->cmd.upmove)) + {//not trying to run, duck or jump + if ( !PM_FlippingAnim( pm->ps->legsAnim ) && + !PM_InRoll( pm->ps ) && + !PM_InKnockDown( pm->ps ) && + !PM_JumpingAnim( pm->ps->legsAnim ) && + !PM_PainAnim( pm->ps->legsAnim ) && + !PM_InSpecialJump( pm->ps->legsAnim ) && + !PM_InSlopeAnim( pm->ps->legsAnim ) && + //!PM_CrouchAnim( pm->ps->legsAnim ) && + //pm->cmd.upmove >= 0 && + !(pm->ps->pm_flags & PMF_DUCKED) && + newMove != LS_PUTAWAY ) + { + parts = SETANIM_BOTH; + } + else if ( !(pm->ps->pm_flags & PMF_DUCKED) + && ( newMove == LS_SPINATTACK_DUAL || newMove == LS_SPINATTACK ) ) + { + parts = SETANIM_BOTH; + } + } + } + } + else + { + if (!pm->ps->saberBlocked) + { + parts = SETANIM_BOTH; + setflags &= ~SETANIM_FLAG_RESTART; + } + } + if (anim!=-1) + { + PM_SetAnim( pm, parts, anim, setflags, saberMoveData[newMove].blendTime ); + } + + if ( pm->ps->torsoAnim == anim ) + {//successfully changed anims + //special check for *starting* a saber swing + if ( pm->gent && pm->ps->SaberLength() > 1 ) + { + if ( PM_SaberInAttack( newMove ) || PM_SaberInSpecialAttack( anim ) ) + {//playing an attack + if ( pm->ps->saberMove != newMove ) + {//wasn't playing that attack before + if ( PM_SaberInSpecialAttack( anim ) ) + { + WP_SaberSwingSound( pm->gent, 0, SWING_FAST ); + if ( !PM_InCartwheel( pm->ps->torsoAnim ) ) + {//can still attack during a cartwheel/arial + pm->ps->weaponTime = pm->ps->torsoAnimTimer;//so we know our weapon is busy + } + } + else + { + switch ( pm->ps->saberAnimLevel ) + { + case SS_DESANN: + case SS_STRONG: + WP_SaberSwingSound( pm->gent, 0, SWING_STRONG ); + break; + case SS_MEDIUM: + case SS_DUAL: + case SS_STAFF: + WP_SaberSwingSound( pm->gent, 0, SWING_MEDIUM ); + break; + case SS_TAVION: + case SS_FAST: + WP_SaberSwingSound( pm->gent, 0, SWING_FAST ); + break; + } + } + } + else if ( (setflags&SETANIM_FLAG_RESTART) && PM_SaberInSpecialAttack( anim ) ) + {//sigh, if restarted a special, then set the weaponTime *again* + if ( !PM_InCartwheel( pm->ps->torsoAnim ) ) + {//can still attack during a cartwheel/arial + pm->ps->weaponTime = pm->ps->torsoAnimTimer;//so we know our weapon is busy + } + } + } + else if ( PM_SaberInStart( newMove ) && pm->ps->saberAnimLevel == SS_STRONG ) + { + WP_SaberSwingSound( pm->gent, 0, SWING_FAST ); + } + } + + /* + //wtf... getting stuck with weaponTime set even though we're not in an attack...? + if ( PM_SaberInAttack( pm->ps->saberMove ) + && !PM_SaberInAttack( newMove ) ) + { + pm->ps->weaponTime = 0; + } + */ + + + //Some special attacks can be started when sabers are off, make sure we turn them on, first! + switch ( newMove ) + {//make sure the saber is on! + case LS_A_LUNGE: + case LS_ROLL_STAB: + if ( PM_InSecondaryStyle() ) + {//staff as medium or dual as fast + if ( pm->ps->dualSabers ) + {//only force on the first saber + pm->ps->saber[0].Activate(); + } + else if ( pm->ps->saber[0].numBlades > 1 ) + {//only force on the first saber's first blade + pm->ps->SaberBladeActivate(0,0); + } + } + else + {//turn on all blades on all sabers + pm->ps->SaberActivate(); + } + break; + case LS_SPINATTACK_ALORA: + case LS_SPINATTACK_DUAL: + case LS_SPINATTACK: + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + //FIXME: probably more... + pm->ps->SaberActivate(); + break; + } + + pm->ps->saberMove = newMove; + pm->ps->saberBlocking = saberMoveData[newMove].blocking; + + if ( pm->ps->clientNum == 0 || PM_ControlledByPlayer() ) + { + if ( pm->ps->saberBlocked >= BLOCKED_UPPER_RIGHT_PROJ && pm->ps->saberBlocked <= BLOCKED_TOP_PROJ + && newMove >= LS_REFLECT_UP && newMove <= LS_REFLECT_LL ) + {//don't clear it when blocking projectiles + } + else + { + pm->ps->saberBlocked = BLOCKED_NONE; + } + } + else if ( pm->ps->saberBlocked <= BLOCKED_ATK_BOUNCE || !pm->ps->SaberActive() || (newMove < LS_PARRY_UR || newMove > LS_REFLECT_LL) ) + {//NPCs only clear blocked if not blocking? + pm->ps->saberBlocked = BLOCKED_NONE; + } + + if ( pm->gent && pm->gent->client ) + { + if ( saberMoveData[newMove].trailLength > 0 ) + { + pm->gent->client->ps.SaberActivateTrail( saberMoveData[newMove].trailLength ); // saber trail lasts for 75ms...feel free to change this if you want it longer or shorter + } + else + { + pm->gent->client->ps.SaberDeactivateTrail( 0 ); + } + } + } +} + + +/* +============== +PM_Use + +Generates a use event +============== +*/ +#define USE_DELAY 250 + +void PM_Use( void ) +{ + if ( pm->ps->useTime > 0 ) + { + pm->ps->useTime -= pml.msec; + if ( pm->ps->useTime < 0 ) + { + pm->ps->useTime = 0; + } + } + + if ( pm->ps->useTime > 0 ) { + return; + } + + if ( ! (pm->cmd.buttons & BUTTON_USE ) ) + { + pm->useEvent = 0; + pm->ps->useTime = 0; + return; + } + + pm->useEvent = EV_USE; + pm->ps->useTime = USE_DELAY; +} + +extern saberMoveName_t PM_AttackForEnemyPos( qboolean allowFB, qboolean allowStabDown ); +saberMoveName_t PM_NPCSaberAttackFromQuad( int quad ) +{ + //FIXME: this should be an AI decision + // It should be based on the enemy's current LS_ move, saberAnimLevel, + // the jedi's difficulty level, rank and FP_OFFENSE skill... + saberMoveName_t autoMove = LS_NONE; + if ( pm->gent && ((pm->gent->NPC && pm->gent->NPC->rank != RANK_ENSIGN && pm->gent->NPC->rank != RANK_CIVILIAN ) || (pm->gent->client && (pm->gent->client->NPC_class == CLASS_TAVION||pm->gent->client->NPC_class == CLASS_ALORA))) ) + { + autoMove = PM_AttackForEnemyPos( qtrue, qtrue ); + } + if ( autoMove != LS_NONE && PM_SaberInSpecial( autoMove ) ) + {//if have opportunity to do a special attack, do one + return autoMove; + } + else + {//pick another one + saberMoveName_t newmove = LS_NONE; + switch( quad ) + { + case Q_T://blocked top + if ( Q_irand( 0, 1 ) ) + { + newmove = LS_A_T2B; + } + else + { + newmove = LS_A_TR2BL; + } + break; + case Q_TR: + if ( !Q_irand( 0, 2 ) ) + { + newmove = LS_A_R2L; + } + else if ( !Q_irand( 0, 1 ) ) + { + newmove = LS_A_TR2BL; + } + else + { + newmove = LS_T1_TR_BR; + } + break; + case Q_TL: + if ( !Q_irand( 0, 2 ) ) + { + newmove = LS_A_L2R; + } + else if ( !Q_irand( 0, 1 ) ) + { + newmove = LS_A_TL2BR; + } + else + { + newmove = LS_T1_TL_BL; + } + break; + case Q_BR: + if ( !Q_irand( 0, 2 ) ) + { + newmove = LS_A_BR2TL; + } + else if ( !Q_irand( 0, 1 ) ) + { + newmove = LS_T1_BR_TR; + } + else + { + newmove = LS_A_R2L; + } + break; + case Q_BL: + if ( !Q_irand( 0, 2 ) ) + { + newmove = LS_A_BL2TR; + } + else if ( !Q_irand( 0, 1 ) ) + { + newmove = LS_T1_BL_TL; + } + else + { + newmove = LS_A_L2R; + } + break; + case Q_L: + if ( !Q_irand( 0, 2 ) ) + { + newmove = LS_A_L2R; + } + else if ( !Q_irand( 0, 1 ) ) + { + newmove = LS_T1__L_T_; + } + else + { + newmove = LS_A_R2L; + } + break; + case Q_R: + if ( !Q_irand( 0, 2 ) ) + { + newmove = LS_A_R2L; + } + else if ( !Q_irand( 0, 1 ) ) + { + newmove = LS_T1__R_T_; + } + else + { + newmove = LS_A_L2R; + } + break; + case Q_B: + if ( pm->gent + && pm->gent->NPC + && pm->gent->NPC->rank >= RANK_LT_JG ) + {//fencers and above can do bottom-up attack + if ( Q_irand( 0, pm->gent->NPC->rank ) >= RANK_LT_JG ) + {//but not overly likely + newmove = PM_SaberLungeAttackMove( qtrue ); + } + } + break; + default: + break; + } + return newmove; + } +} + +int PM_SaberMoveQuadrantForMovement( usercmd_t *ucmd ) +{ + if ( ucmd->rightmove > 0 ) + {//moving right + if ( ucmd->forwardmove > 0 ) + {//forward right = TL2BR slash + return Q_TL; + } + else if ( ucmd->forwardmove < 0 ) + {//backward right = BL2TR uppercut + return Q_BL; + } + else + {//just right is a left slice + return Q_L; + } + } + else if ( ucmd->rightmove < 0 ) + {//moving left + if ( ucmd->forwardmove > 0 ) + {//forward left = TR2BL slash + return Q_TR; + } + else if ( ucmd->forwardmove < 0 ) + {//backward left = BR2TL uppercut + return Q_BR; + } + else + {//just left is a right slice + return Q_R; + } + } + else + {//not moving left or right + if ( ucmd->forwardmove > 0 ) + {//forward= T2B slash + return Q_T; + } + else if ( ucmd->forwardmove < 0 ) + {//backward= T2B slash //or B2T uppercut? + return Q_T; + } + else //if ( curmove == LS_READY )//??? + {//Not moving at all + return Q_R; + } + } + //return Q_R;//???? +} + +void PM_SetAnimFrame( gentity_t *gent, int frame, qboolean torso, qboolean legs ) +{ + if ( !gi.G2API_HaveWeGhoul2Models( gent->ghoul2 ) ) + { + return; + } + int actualTime = (cg.time?cg.time:level.time); + if ( torso && gent->lowerLumbarBone != -1 )//gent->upperLumbarBone + { + gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], gent->lowerLumbarBone, //gent->upperLumbarBone + frame, frame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, 1, actualTime, frame, 150 ); + if ( gent->motionBone != -1 ) + { + gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], gent->motionBone, //gent->upperLumbarBone + frame, frame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, 1, actualTime, frame, 150 ); + } + } + if ( legs && gent->rootBone != -1 ) + { + gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], gent->rootBone, + frame, frame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, 1, actualTime, frame, 150 ); + } +} + +int PM_SaberLockWinAnim( saberLockResult_t result, int breakType ) +{ + int winAnim = -1; + switch ( pm->ps->torsoAnim ) + { +/* + default: +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR-PM_SaberLockBreak: %s not in saberlock anim, anim = (%d)%s\n", pm->gent->NPC_type, pm->ps->torsoAnim, animTable[pm->ps->torsoAnim].name ); +#endif +*/ + case BOTH_BF2LOCK: + if ( breakType == SABERLOCK_SUPERBREAK ) + { + winAnim = BOTH_LK_S_S_T_SB_1_W; + } + else if ( result == LOCK_DRAW ) + { + winAnim = BOTH_BF1BREAK; + } + else + { + pm->ps->saberMove = LS_A_T2B; + winAnim = BOTH_A3_T__B_; + } + break; + case BOTH_BF1LOCK: + if ( breakType == SABERLOCK_SUPERBREAK ) + { + winAnim = BOTH_LK_S_S_T_SB_1_W; + } + else if ( result == LOCK_DRAW ) + { + winAnim = BOTH_KNOCKDOWN4; + } + else + { + pm->ps->saberMove = LS_K1_T_; + winAnim = BOTH_K1_S1_T_; + } + break; + case BOTH_CWCIRCLELOCK: + if ( breakType == SABERLOCK_SUPERBREAK ) + { + winAnim = BOTH_LK_S_S_S_SB_1_W; + } + else if ( result == LOCK_DRAW ) + { + pm->ps->saberMove = pm->ps->saberBounceMove = LS_V1_BL; + pm->ps->saberBlocked = BLOCKED_PARRY_BROKEN; + winAnim = BOTH_V1_BL_S1; + } + else + { + winAnim = BOTH_CWCIRCLEBREAK; + } + break; + case BOTH_CCWCIRCLELOCK: + if ( breakType == SABERLOCK_SUPERBREAK ) + { + winAnim = BOTH_LK_S_S_S_SB_1_W; + } + else if ( result == LOCK_DRAW ) + { + pm->ps->saberMove = pm->ps->saberBounceMove = LS_V1_BR; + pm->ps->saberBlocked = BLOCKED_PARRY_BROKEN; + winAnim = BOTH_V1_BR_S1; + } + else + { + winAnim = BOTH_CCWCIRCLEBREAK; + } + break; + default: + //must be using new system: + break; + } + if ( winAnim != -1 ) + { + PM_SetAnim( pm, SETANIM_BOTH, winAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + pm->ps->saberBlocked = BLOCKED_NONE; + pm->ps->weaponstate = WEAPON_FIRING; + if ( breakType == SABERLOCK_SUPERBREAK + && winAnim != BOTH_LK_ST_DL_T_SB_1_W ) + {//going to attack with saber, do a saber trail + pm->ps->SaberActivateTrail( 200 ); + } + } + return winAnim; +} + +int PM_SaberLockLoseAnim( gentity_t *genemy, saberLockResult_t result, int breakType ) +{ + int loseAnim = -1; + switch ( genemy->client->ps.torsoAnim ) + { +/* + default: +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR-PM_SaberLockBreak: %s not in saberlock anim, anim = (%d)%s\n", genemy->NPC_type, genemy->client->ps.torsoAnim, animTable[genemy->client->ps.torsoAnim].name ); +#endif +*/ + case BOTH_BF2LOCK: + if ( breakType == SABERLOCK_SUPERBREAK ) + { + loseAnim = BOTH_LK_S_S_T_SB_1_L; + } + else if ( result == LOCK_DRAW ) + { + loseAnim = BOTH_BF1BREAK; + } + else + { + if ( result == LOCK_STALEMATE ) + {//no-one won + genemy->client->ps.saberMove = LS_K1_T_; + loseAnim = BOTH_K1_S1_T_; + } + else + {//FIXME: this anim needs to transition back to ready when done + loseAnim = BOTH_BF1BREAK; + } + } + break; + case BOTH_BF1LOCK: + if ( breakType == SABERLOCK_SUPERBREAK ) + { + loseAnim = BOTH_LK_S_S_T_SB_1_L; + } + else if ( result == LOCK_DRAW ) + { + loseAnim = BOTH_KNOCKDOWN4; + } + else + { + if ( result == LOCK_STALEMATE ) + {//no-one won + genemy->client->ps.saberMove = LS_A_T2B; + loseAnim = BOTH_A3_T__B_; + } + else + { + loseAnim = BOTH_KNOCKDOWN4; + } + } + break; + case BOTH_CWCIRCLELOCK: + if ( breakType == SABERLOCK_SUPERBREAK ) + { + loseAnim = BOTH_LK_S_S_S_SB_1_L; + } + else if ( result == LOCK_DRAW ) + { + genemy->client->ps.saberMove = genemy->client->ps.saberBounceMove = LS_V1_BL; + genemy->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BL_S1; + } + else + { + if ( result == LOCK_STALEMATE ) + {//no-one won + loseAnim = BOTH_CCWCIRCLEBREAK; + } + else + { + genemy->client->ps.saberMove = genemy->client->ps.saberBounceMove = LS_V1_BL; + genemy->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BL_S1; + /* + genemy->client->ps.saberMove = genemy->client->ps.saberBounceMove = LS_H1_BR; + genemy->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_H1_S1_BL; + */ + } + } + break; + case BOTH_CCWCIRCLELOCK: + if ( breakType == SABERLOCK_SUPERBREAK ) + { + loseAnim = BOTH_LK_S_S_S_SB_1_L; + } + else if ( result == LOCK_DRAW ) + { + genemy->client->ps.saberMove = genemy->client->ps.saberBounceMove = LS_V1_BR; + genemy->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BR_S1; + } + else + { + if ( result == LOCK_STALEMATE ) + {//no-one won + loseAnim = BOTH_CWCIRCLEBREAK; + } + else + { + genemy->client->ps.saberMove = genemy->client->ps.saberBounceMove = LS_V1_BR; + genemy->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BR_S1; + /* + genemy->client->ps.saberMove = genemy->client->ps.saberBounceMove = LS_H1_BL; + genemy->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_H1_S1_BR; + */ + } + } + break; + } + if ( loseAnim != -1 ) + { + NPC_SetAnim( genemy, SETANIM_BOTH, loseAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + genemy->client->ps.weaponTime = genemy->client->ps.torsoAnimTimer;// + 250; + genemy->client->ps.saberBlocked = BLOCKED_NONE; + genemy->client->ps.weaponstate = WEAPON_READY; + } + return loseAnim; +} + +int PM_SaberLockResultAnim( gentity_t *duelist, int lockOrBreakOrSuperBreak, int winOrLose ) +{ + int baseAnim = duelist->client->ps.torsoAnim; + switch ( baseAnim ) + { + case BOTH_LK_S_S_S_L_2: //lock if I'm using single vs. a single and other intitiated + baseAnim = BOTH_LK_S_S_S_L_1; + break; + case BOTH_LK_S_S_T_L_2: //lock if I'm using single vs. a single and other initiated + baseAnim = BOTH_LK_S_S_T_L_1; + break; + case BOTH_LK_DL_DL_S_L_2: //lock if I'm using dual vs. dual and other initiated + baseAnim = BOTH_LK_DL_DL_S_L_1; + break; + case BOTH_LK_DL_DL_T_L_2: //lock if I'm using dual vs. dual and other initiated + baseAnim = BOTH_LK_DL_DL_T_L_1; + break; + case BOTH_LK_ST_ST_S_L_2: //lock if I'm using staff vs. a staff and other initiated + baseAnim = BOTH_LK_ST_ST_S_L_1; + break; + case BOTH_LK_ST_ST_T_L_2: //lock if I'm using staff vs. a staff and other initiated + baseAnim = BOTH_LK_ST_ST_T_L_1; + break; + } + //what kind of break? + if ( lockOrBreakOrSuperBreak == SABERLOCK_BREAK ) + { + baseAnim -= 2; + } + else if ( lockOrBreakOrSuperBreak == SABERLOCK_SUPERBREAK ) + { + baseAnim += 1; + } + else + {//WTF? Not a valid result + return -1; + } + //win or lose? + if ( winOrLose == SABERLOCK_WIN ) + { + baseAnim += 1; + } + //play the anim and hold it + NPC_SetAnim( duelist, SETANIM_BOTH, baseAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + if ( lockOrBreakOrSuperBreak == SABERLOCK_SUPERBREAK + && winOrLose == SABERLOCK_LOSE ) + {//if you lose a superbreak, you're defenseless + //make saberent not block + gentity_t *saberent = &g_entities[duelist->client->ps.saberEntityNum]; + if ( saberent ) + { + VectorClear(saberent->mins); + VectorClear(saberent->maxs); + G_SetOrigin(saberent, duelist->currentOrigin); + } + //set sabermove to none + duelist->client->ps.saberMove = LS_NONE; + //Hold the anim a little longer than it is + duelist->client->ps.torsoAnimTimer += 250;; + } + + //no attacking during this anim + duelist->client->ps.weaponTime = duelist->client->ps.torsoAnimTimer; + duelist->client->ps.saberBlocked = BLOCKED_NONE; + if ( lockOrBreakOrSuperBreak == SABERLOCK_SUPERBREAK + && winOrLose == SABERLOCK_WIN + && baseAnim != BOTH_LK_ST_DL_T_SB_1_W ) + {//going to attack with saber, do a saber trail + duelist->client->ps.SaberActivateTrail( 200 ); + } + return baseAnim; +} + +void PM_SaberLockBreak( gentity_t *gent, gentity_t *genemy, saberLockResult_t result, int victoryStrength ) +{ + int winAnim = -1, loseAnim = -1; + int breakType = SABERLOCK_BREAK; + qboolean singleVsSingle = qtrue; + + if ( result == LOCK_VICTORY ) + //&& Q_irand(0,7) < victoryStrength ) + { + if ( genemy + && genemy->NPC + && ((genemy->NPC->aiFlags&NPCAI_BOSS_CHARACTER) + ||(genemy->NPC->aiFlags&NPCAI_SUBBOSS_CHARACTER) + ||(genemy->client&&genemy->client->NPC_class == CLASS_SHADOWTROOPER)) + && Q_irand(0, 4) + ) + {//less of a chance of getting a superbreak against a boss + breakType = SABERLOCK_BREAK; + } + else + { + breakType = SABERLOCK_SUPERBREAK; + } + } + else + { + breakType = SABERLOCK_BREAK; + } + winAnim = PM_SaberLockWinAnim( result, breakType ); + if ( winAnim != -1 ) + {//a single vs. single break + if ( genemy && genemy->client ) + { + loseAnim = PM_SaberLockLoseAnim( genemy, result, breakType ); + } + } + else + {//must be a saberlock that's not between single and single... + singleVsSingle = qfalse; + winAnim = PM_SaberLockResultAnim( gent, breakType, SABERLOCK_WIN ); + pm->ps->weaponstate = WEAPON_FIRING; + if ( genemy && genemy->client ) + { + loseAnim = PM_SaberLockResultAnim( genemy, breakType, SABERLOCK_LOSE ); + genemy->client->ps.weaponstate = WEAPON_READY; + } + } + + if ( d_saberCombat->integer ) + { + Com_Printf( "%s won saber lock, anim = %s!\n", gent->NPC_type, animTable[winAnim].name ); + Com_Printf( "%s lost saber lock, anim = %s!\n", genemy->NPC_type, animTable[loseAnim].name ); + } + + pm->ps->saberLockTime = genemy->client->ps.saberLockTime = 0; + pm->ps->saberLockEnemy = genemy->client->ps.saberLockEnemy = ENTITYNUM_NONE; + pm->ps->saberMoveNext = LS_NONE; + if ( genemy && genemy->client ) + { + genemy->client->ps.saberMoveNext = LS_NONE; + } + + PM_AddEvent( EV_JUMP ); + if ( result == LOCK_STALEMATE ) + {//no-one won + G_AddEvent( genemy, EV_JUMP, 0 ); + } + else + { + if ( pm->ps->clientNum ) + {//an NPC + pm->ps->saberEventFlags |= SEF_LOCK_WON;//tell the winner to press the advantage + } + //painDebounceTime will stop them from doing anything + genemy->painDebounceTime = level.time + genemy->client->ps.torsoAnimTimer + 500; + if ( Q_irand( 0, 1 ) ) + { + G_AddEvent( genemy, EV_PAIN, Q_irand( 0, 75 ) ); + } + else + { + if ( genemy->NPC ) + { + genemy->NPC->blockedSpeechDebounceTime = 0; + } + G_AddVoiceEvent( genemy, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 500 ); + } + if ( result == LOCK_VICTORY ) + {//one person won + if ( Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_2 ) < pm->ps->forcePowerLevel[FP_SABER_OFFENSE] ) + { + vec3_t throwDir = {0,0,350}; + int winMove = pm->ps->saberMove; + if ( !singleVsSingle ) + {//all others have their own super breaks + //so it doesn't try to set some other anim below + winAnim = -1; + } + else if ( winAnim == BOTH_LK_S_S_S_SB_1_W + || winAnim == BOTH_LK_S_S_T_SB_1_W ) + {//doing a superbreak on single-vs-single, don't do the old superbreaks this time + //so it doesn't try to set some other anim below + winAnim = -1; + } + else + {//JK2-style + switch ( winAnim ) + { + case BOTH_A3_T__B_: + winAnim = BOTH_D1_TL___; + winMove = LS_D1_TL; + //FIXME: mod throwDir? + break; + case BOTH_K1_S1_T_: + //FIXME: mod throwDir? + break; + case BOTH_CWCIRCLEBREAK: + //FIXME: mod throwDir? + break; + case BOTH_CCWCIRCLEBREAK: + winAnim = BOTH_A1_BR_TL; + winMove = LS_A_BR2TL; + //FIXME: mod throwDir? + break; + } + if ( winAnim != BOTH_CCWCIRCLEBREAK ) + { + if ( (!genemy->s.number&&genemy->health<=25)//player low on health + ||(genemy->s.number&&genemy->client->NPC_class!=CLASS_KYLE&&genemy->client->NPC_class!=CLASS_LUKE&&genemy->client->NPC_class!=CLASS_TAVION&&genemy->client->NPC_class!=CLASS_ALORA&&genemy->client->NPC_class!=CLASS_DESANN)//any NPC that's not a boss character + ||(genemy->s.number&&genemy->health<=50) )//boss character with less than 50 health left + {//possibly knock saber out of hand OR cut hand off! + if ( Q_irand( 0, 25 ) < victoryStrength + && ((!genemy->s.number&&genemy->health<=10)||genemy->s.number) ) + { + NPC_SetAnim( genemy, SETANIM_BOTH, BOTH_RIGHTHANDCHOPPEDOFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );//force this + genemy->client->dismembered = false; + G_DoDismemberment( genemy, genemy->client->renderInfo.handRPoint, MOD_SABER, 1000, HL_HAND_RT, qtrue ); + G_Damage( genemy, gent, gent, throwDir, genemy->client->renderInfo.handRPoint, genemy->health+10, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_SABER, HL_NONE ); + + PM_SetAnim( pm, SETANIM_BOTH, winAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + pm->ps->weaponTime = pm->ps->torsoAnimTimer + 500; + pm->ps->saberMove = winMove; + pm->ps->saberBlocked = BLOCKED_NONE; + pm->ps->weaponstate = WEAPON_FIRING; + return; + } + } + } + } + //else see if we can knock the saber out of their hand + //FIXME: for now, always disarm the right-hand saber + if ( genemy->client->ps.saber[0].disarmable ) + { + //add disarmBonus into this check + victoryStrength += pm->ps->SaberDisarmBonus()*2; + if ( genemy->client->ps.saber[0].twoHanded || (genemy->client->ps.dualSabers && genemy->client->ps.saber[1].Active()) ) + {//defender gets a bonus for using a 2-handed saber or 2 sabers + victoryStrength -= 2; + } + if ( pm->ps->forcePowersActive&(1<client->ps.forcePowerLevel[FP_RAGE]; + } + else if ( pm->ps->forceRageRecoveryTime > pm->cmd.serverTime ) + { + victoryStrength--; + } + if ( genemy->client->ps.forceRageRecoveryTime > pm->cmd.serverTime ) + { + victoryStrength++; + } + if ( Q_irand( 0, 10 ) < victoryStrength ) + { + if ( !genemy->client->ps.saber[0].twoHanded + || !Q_irand( 0, 1 ) ) + {//if it's a two-handed saber, it has a 50% chance of resisting a disarming + WP_SaberLose( genemy, throwDir ); + if ( winAnim != -1 ) + { + PM_SetAnim( pm, SETANIM_BOTH, winAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + pm->ps->saberMove = winMove; + pm->ps->saberBlocked = BLOCKED_NONE; + pm->ps->weaponstate = WEAPON_FIRING; + } + } + } + } + } + } + } +} + +int G_SaberLockStrength( gentity_t *gent ) +{ + int strength = gent->client->ps.saber[0].lockBonus; + if ( gent->client->ps.saber[0].twoHanded ) + { + strength += 1; + } + if ( gent->client->ps.dualSabers && gent->client->ps.saber[1].Active() ) + { + strength += 1 + gent->client->ps.saber[1].lockBonus; + } + if ( gent->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_RAGE]; + } + else if ( gent->client->ps.forceRageRecoveryTime > pm->cmd.serverTime ) + { + strength--; + } + if ( gent->s.number >= MAX_CLIENTS ) + { + if ( gent->client->NPC_class == CLASS_DESANN || gent->client->NPC_class == CLASS_LUKE ) + { + strength += 5+Q_irand(0,g_spskill->integer); + } + else + { + strength += gent->client->ps.forcePowerLevel[FP_SABER_OFFENSE]+Q_irand(0,g_spskill->integer); + if ( gent->NPC ) + { + if ( (gent->NPC->aiFlags&NPCAI_BOSS_CHARACTER) + || (gent->NPC->aiFlags&NPCAI_ROSH) + || gent->client->NPC_class == CLASS_SHADOWTROOPER ) + { + strength += Q_irand(0,2); + } + else if ( (gent->NPC->aiFlags&NPCAI_SUBBOSS_CHARACTER) ) + { + strength += Q_irand(-1,1); + } + } + } + } + else + {//player +// strength += (3-g_spskill->integer)+gent->client->ps.forcePowerLevel[FP_SABER_OFFENSE]+Q_irand(0,g_spskill->integer)+Q_irand(0,1); + strength += gent->client->ps.forcePowerLevel[FP_SABER_OFFENSE]+Q_irand(0,g_spskill->integer)+Q_irand(0,1); + } + return (strength+1)/2; +} + +qboolean PM_InSaberLockOld( int anim ) +{ + switch ( anim ) + { + case BOTH_BF2LOCK: + case BOTH_BF1LOCK: + case BOTH_CWCIRCLELOCK: + case BOTH_CCWCIRCLELOCK: + return qtrue; + } + return qfalse; +} + +qboolean PM_InSaberLock( int anim ) +{ + switch ( anim ) + { + case BOTH_LK_S_DL_S_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_DL_T_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_ST_S_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_S_ST_T_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_S_S_S_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_S_S_T_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_DL_DL_S_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_DL_T_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_ST_S_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_DL_ST_T_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_DL_S_S_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_DL_S_T_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_ST_DL_S_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_DL_T_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_ST_S_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_ST_T_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_S_S_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_ST_S_T_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_S_S_S_L_2: + case BOTH_LK_S_S_T_L_2: + case BOTH_LK_DL_DL_S_L_2: + case BOTH_LK_DL_DL_T_L_2: + case BOTH_LK_ST_ST_S_L_2: + case BOTH_LK_ST_ST_T_L_2: + return qtrue; + break; + default: + return PM_InSaberLockOld( anim ); + break; + } + //return qfalse; +} + +extern qboolean ValidAnimFileIndex ( int index ); +extern qboolean G_CheckIncrementLockAnim( int anim, int winOrLose ); +qboolean PM_SaberLocked( void ) +{ + //FIXME: maybe kick out of saberlock? + if ( pm->ps->saberLockEnemy == ENTITYNUM_NONE ) + { + if ( PM_InSaberLock( pm->ps->torsoAnim ) ) + {//wtf? Maybe enemy died? + PM_SaberLockWinAnim( LOCK_STALEMATE, SABERLOCK_BREAK ); + } + return qfalse; + } + gentity_t *gent = pm->gent; + if ( !gent ) + { + return qfalse; + } + gentity_t *genemy = &g_entities[pm->ps->saberLockEnemy]; + if ( !genemy ) + { + return qfalse; + } + if ( PM_InSaberLock( pm->ps->torsoAnim ) && PM_InSaberLock( genemy->client->ps.torsoAnim ) ) + { + if ( pm->ps->saberLockTime <= level.time + 500 + && pm->ps->saberLockEnemy != ENTITYNUM_NONE ) + {//lock just ended + int strength = G_SaberLockStrength( gent ); + int eStrength = G_SaberLockStrength( genemy ); + if ( strength > 1 && eStrength > 1 && !Q_irand( 0, abs(strength-eStrength)+1 ) ) + {//both knock each other down! + PM_SaberLockBreak( gent, genemy, LOCK_DRAW, 0 ); + } + else + {//both "win" + PM_SaberLockBreak( gent, genemy, LOCK_STALEMATE, 0 ); + } + return qtrue; + } + else if ( pm->ps->saberLockTime < level.time ) + {//done... tie breaker above should have handled this, but...? + if ( PM_InSaberLock( pm->ps->torsoAnim ) && pm->ps->torsoAnimTimer > 0 ) + { + pm->ps->torsoAnimTimer = 0; + } + if ( PM_InSaberLock( pm->ps->legsAnim ) && pm->ps->legsAnimTimer > 0 ) + { + pm->ps->legsAnimTimer = 0; + } + return qfalse; + } + else if ( pm->cmd.buttons & BUTTON_ATTACK ) + {//holding attack + if ( !(pm->ps->pm_flags&PMF_ATTACK_HELD) ) + {//tapping + int remaining = 0; + if( ValidAnimFileIndex( gent->client->clientInfo.animFileIndex ) ) + { + animation_t *anim; + float currentFrame, junk2; + int curFrame, junk; + int strength = 1; + anim = &level.knownAnimFileSets[gent->client->clientInfo.animFileIndex].animations[pm->ps->torsoAnim]; + qboolean ret; + ret=gi.G2API_GetBoneAnimIndex( &gent->ghoul2[gent->playerModel], gent->lowerLumbarBone, (cg.time?cg.time:level.time), ¤tFrame, &junk, &junk, &junk, &junk2, NULL ); + + assert(ret); // this would be pretty bad, the below code seems to assume the call succeeds. -gil + + strength = G_SaberLockStrength( gent ); + if ( PM_InSaberLockOld( pm->ps->torsoAnim ) ) + {//old locks + if ( pm->ps->torsoAnim == BOTH_CCWCIRCLELOCK || + pm->ps->torsoAnim == BOTH_BF2LOCK ) + { + curFrame = floor( currentFrame )-strength; + //drop my frame one + if ( curFrame <= anim->firstFrame ) + {//I won! Break out + PM_SaberLockBreak( gent, genemy, LOCK_VICTORY, strength ); + return qtrue; + } + else + { + PM_SetAnimFrame( gent, curFrame, qtrue, qtrue ); + remaining = curFrame-anim->firstFrame; + if ( d_saberCombat->integer ) + { + Com_Printf( "%s pushing in saber lock, %d frames to go!\n", gent->NPC_type, remaining ); + } + } + } + else + { + curFrame = ceil( currentFrame )+strength; + //advance my frame one + if ( curFrame >= anim->firstFrame+anim->numFrames ) + {//I won! Break out + PM_SaberLockBreak( gent, genemy, LOCK_VICTORY, strength ); + return qtrue; + } + else + { + PM_SetAnimFrame( gent, curFrame, qtrue, qtrue ); + remaining = anim->firstFrame+anim->numFrames-curFrame; + if ( d_saberCombat->integer ) + { + Com_Printf( "%s pushing in saber lock, %d frames to go!\n", gent->NPC_type, remaining ); + } + } + } + } + else + {//new locks + if ( G_CheckIncrementLockAnim( pm->ps->torsoAnim, SABERLOCK_WIN ) ) + { + curFrame = ceil( currentFrame )+strength; + //advance my frame one + if ( curFrame >= anim->firstFrame+anim->numFrames ) + {//I won! Break out + PM_SaberLockBreak( gent, genemy, LOCK_VICTORY, strength ); + return qtrue; + } + else + { + PM_SetAnimFrame( gent, curFrame, qtrue, qtrue ); + remaining = anim->firstFrame+anim->numFrames-curFrame; + if ( d_saberCombat->integer ) + { + Com_Printf( "%s pushing in saber lock, %d frames to go!\n", gent->NPC_type, remaining ); + } + } + } + else + { + curFrame = floor( currentFrame )-strength; + //drop my frame one + if ( curFrame <= anim->firstFrame ) + {//I won! Break out + PM_SaberLockBreak( gent, genemy, LOCK_VICTORY, strength ); + return qtrue; + } + else + { + PM_SetAnimFrame( gent, curFrame, qtrue, qtrue ); + remaining = curFrame-anim->firstFrame; + if ( d_saberCombat->integer ) + { + Com_Printf( "%s pushing in saber lock, %d frames to go!\n", gent->NPC_type, remaining ); + } + } + } + } + if ( !Q_irand( 0, 2 ) ) + { + if ( pm->ps->clientNum < MAX_CLIENTS ) + { + if ( !Q_irand( 0, 3 ) ) + { + PM_AddEvent( EV_JUMP ); + } + else + { + PM_AddEvent( Q_irand( EV_PUSHED1, EV_PUSHED3 ) ); + } + } + else + { + if ( gent->NPC && gent->NPC->blockedSpeechDebounceTime < level.time ) + { + switch ( Q_irand( 0, 3 ) ) + { + case 0: + PM_AddEvent( EV_JUMP ); + break; + case 1: + PM_AddEvent( Q_irand( EV_ANGER1, EV_ANGER3 ) ); + gent->NPC->blockedSpeechDebounceTime = level.time + 3000; + break; + case 2: + PM_AddEvent( Q_irand( EV_TAUNT1, EV_TAUNT3 ) ); + gent->NPC->blockedSpeechDebounceTime = level.time + 3000; + break; + case 3: + PM_AddEvent( Q_irand( EV_GLOAT1, EV_GLOAT3 ) ); + gent->NPC->blockedSpeechDebounceTime = level.time + 3000; + break; + } + } + } + } + } + else + { + return qfalse; + } + + if( ValidAnimFileIndex( genemy->client->clientInfo.animFileIndex ) ) + { + animation_t *anim; + anim = &level.knownAnimFileSets[genemy->client->clientInfo.animFileIndex].animations[genemy->client->ps.torsoAnim]; + /* + float currentFrame, junk2; + int junk; + + gi.G2API_GetBoneAnimIndex( &genemy->ghoul2[genemy->playerModel], genemy->lowerLumbarBone, (cg.time?cg.time:level.time), ¤tFrame, &junk, &junk, &junk, &junk2, NULL ); + */ + + if ( !Q_irand( 0, 2 ) ) + { + switch ( Q_irand( 0, 3 ) ) + { + case 0: + G_AddEvent( genemy, EV_PAIN, floor((float)genemy->health/genemy->max_health*100.0f) ); + break; + case 1: + G_AddVoiceEvent( genemy, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 500 ); + break; + case 2: + G_AddVoiceEvent( genemy, Q_irand( EV_CHOKE1, EV_CHOKE3 ), 500 ); + break; + case 3: + G_AddVoiceEvent( genemy, EV_PUSHFAIL, 2000 ); + break; + } + } + + if ( PM_InSaberLockOld( genemy->client->ps.torsoAnim ) ) + { + if ( genemy->client->ps.torsoAnim == BOTH_CCWCIRCLELOCK || + genemy->client->ps.torsoAnim == BOTH_BF2LOCK ) + { + PM_SetAnimFrame( genemy, anim->firstFrame+anim->numFrames-remaining, qtrue, qtrue ); + } + else + { + PM_SetAnimFrame( genemy, anim->firstFrame+remaining, qtrue, qtrue ); + } + } + else + {//new locks + //??? + if ( G_CheckIncrementLockAnim( genemy->client->ps.torsoAnim, SABERLOCK_LOSE ) ) + { + PM_SetAnimFrame( genemy, anim->firstFrame+anim->numFrames-remaining, qtrue, qtrue ); + } + else + { + PM_SetAnimFrame( genemy, anim->firstFrame+remaining, qtrue, qtrue ); + } + } + } + } + } + else + {//FIXME: other ways out of a saberlock? + //force-push? (requires more force power?) + //kick? (requires anim ... hit jump key?) + //roll? + //backflip? + } + } + else + {//something broke us out of it + if ( gent->painDebounceTime > level.time && genemy->painDebounceTime > level.time ) + { + PM_SaberLockBreak( gent, genemy, LOCK_DRAW, 0 ); + } + else if ( gent->painDebounceTime > level.time ) + { + PM_SaberLockBreak( genemy, gent, LOCK_VICTORY, 0 ); + } + else if ( genemy->painDebounceTime > level.time ) + { + PM_SaberLockBreak( gent, genemy, LOCK_VICTORY, 0 ); + } + else + { + PM_SaberLockBreak( gent, genemy, LOCK_STALEMATE, 0 ); + } + } + return qtrue; +} + +qboolean G_EnemyInKickRange( gentity_t *self, gentity_t *enemy ) +{ + if ( !self || !enemy ) + { + return qfalse; + } + if ( fabs(self->currentOrigin[2]-enemy->currentOrigin[2]) < 32 ) + {//generally at same height + if ( DistanceHorizontal( self->currentOrigin, enemy->currentOrigin ) <= (STAFF_KICK_RANGE+8.0f+(self->maxs[0]*1.5f)+(enemy->maxs[0]*1.5f)) ) + {//within kicking range! + return qtrue; + } + } + return qfalse; +} + +qboolean G_CanKickEntity( gentity_t *self, gentity_t *target ) +{ + if ( target && target->client + && !PM_InKnockDown( &target->client->ps ) + && G_EnemyInKickRange( self, target ) ) + { + return qtrue; + } + return qfalse; +} + +float PM_GroundDistance(void) +{ + trace_t tr; + vec3_t down; + + VectorCopy(pm->ps->origin, down); + + down[2] -= 4096; + + pm->trace(&tr, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + + VectorSubtract(pm->ps->origin, tr.endpos, down); + + return VectorLength(down); +} + +float G_GroundDistance(gentity_t *self) +{ + if ( !self ) + {//wtf?!! + return Q3_INFINITE; + } + trace_t tr; + vec3_t down; + + VectorCopy(self->currentOrigin, down); + + down[2] -= 4096; + + gi.trace(&tr, self->currentOrigin, self->mins, self->maxs, down, self->s.number, self->clipmask); + + VectorSubtract(self->currentOrigin, tr.endpos, down); + + return VectorLength(down); +} + +saberMoveName_t G_PickAutoKick( gentity_t *self, gentity_t *enemy, qboolean storeMove ) +{ + saberMoveName_t kickMove = LS_NONE; + if ( !self || !self->client ) + { + return LS_NONE; + } + if ( !enemy ) + { + return LS_NONE; + } + vec3_t v_fwd, v_rt, enemyDir, fwdAngs = {0,self->client->ps.viewangles[YAW],0}; + VectorSubtract( enemy->currentOrigin, self->currentOrigin, enemyDir ); + VectorNormalize( enemyDir );//not necessary, I guess, but doesn't happen often + AngleVectors( fwdAngs, v_fwd, v_rt, NULL ); + float fDot = DotProduct( enemyDir, v_fwd ); + float rDot = DotProduct( enemyDir, v_rt ); + if ( fabs( rDot ) > 0.5f && fabs( fDot ) < 0.5f ) + {//generally to one side + if ( rDot > 0 ) + {//kick right + kickMove = LS_KICK_R; + } + else + {//kick left + kickMove = LS_KICK_L; + } + } + else if ( fabs( fDot ) > 0.5f && fabs( rDot ) < 0.5f ) + {//generally in front or behind us + if ( fDot > 0 ) + {//kick fwd + kickMove = LS_KICK_F; + } + else + {//kick back + kickMove = LS_KICK_B; + } + } + else + {//diagonal to us, kick would miss + } + if ( kickMove != LS_NONE ) + {//have a valid one to do + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//if in air, convert kick to an in-air kick + float gDist = G_GroundDistance( self ); + //let's only allow air kicks if a certain distance from the ground + //it's silly to be able to do them right as you land. + //also looks wrong to transition from a non-complete flip anim... + if ((!PM_FlippingAnim( self->client->ps.legsAnim ) || self->client->ps.legsAnimTimer <= 0) && + gDist > 64.0f && //strict minimum + gDist > (-self->client->ps.velocity[2])-64.0f //make sure we are high to ground relative to downward velocity as well + ) + { + switch ( kickMove ) + { + case LS_KICK_F: + kickMove = LS_KICK_F_AIR; + break; + case LS_KICK_B: + kickMove = LS_KICK_B_AIR; + break; + case LS_KICK_R: + kickMove = LS_KICK_R_AIR; + break; + case LS_KICK_L: + kickMove = LS_KICK_L_AIR; + break; + default: //oh well, can't do any other kick move while in-air + kickMove = LS_NONE; + break; + } + } + else + {//leave it as a normal kick unless we're too high up + if ( gDist > 128.0f || self->client->ps.velocity[2] >= 0 ) + { //off ground, but too close to ground + kickMove = LS_NONE; + } + } + } + if ( storeMove ) + { + self->client->ps.saberMoveNext = kickMove; + } + } + return kickMove; +} + +saberMoveName_t PM_PickAutoKick( gentity_t *enemy ) +{ + return G_PickAutoKick( pm->gent, enemy, qfalse ); +} + +saberMoveName_t G_PickAutoMultiKick( gentity_t *self, qboolean allowSingles, qboolean storeMove ) +{ + gentity_t *ent; + gentity_t *entityList[MAX_GENTITIES]; + vec3_t mins, maxs; + int i, e; + int radius = ((self->maxs[0]*1.5f)+(self->maxs[0]*1.5f)+STAFF_KICK_RANGE+24.0f);//a little wide on purpose + vec3_t center; + saberMoveName_t kickMove, bestKick = LS_NONE; + float distToEnt, bestDistToEnt = Q3_INFINITE; + gentity_t *bestEnt = NULL; + int enemiesFront = 0; + int enemiesBack = 0; + int enemiesRight = 0; + int enemiesLeft = 0; + int enemiesSpin = 0; + + if ( !self || !self->client ) + { + return LS_NONE; + } + + VectorCopy( self->currentOrigin, center ); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + + int numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + + if (ent == self) + continue; + if (ent->owner == self) + continue; + if ( !(ent->inuse) ) + continue; + //not a client? + if ( !ent->client ) + continue; + //ally? + if ( ent->client->playerTeam == self->client->playerTeam ) + continue; + //dead? + if ( ent->health <= 0 ) + continue; + //too far? + distToEnt = DistanceSquared( ent->currentOrigin, center ); + if ( distToEnt > (radius*radius) ) + continue; + kickMove = G_PickAutoKick( self, ent, qfalse ); + if ( kickMove == LS_KICK_F_AIR + && kickMove == LS_KICK_B_AIR + && kickMove == LS_KICK_R_AIR + && kickMove == LS_KICK_L_AIR ) + {//in air? Can't do multikicks + } + else + { + switch ( kickMove ) + { + case LS_KICK_F: + enemiesFront++; + break; + case LS_KICK_B: + enemiesBack++; + break; + case LS_KICK_R: + enemiesRight++; + break; + case LS_KICK_L: + enemiesLeft++; + break; + default: + enemiesSpin++; + break; + } + } + if ( allowSingles ) + { + if ( kickMove != LS_NONE + && distToEnt < bestDistToEnt ) + { + bestKick = kickMove; + bestEnt = ent; + } + } + } + kickMove = LS_NONE; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//can't do the multikicks in air + if ( enemiesFront && enemiesBack + && (enemiesFront+enemiesBack)-(enemiesRight+enemiesLeft)>1 ) + {//more enemies in front/back than left/right + kickMove = LS_KICK_BF; + } + else if ( enemiesRight && enemiesLeft + && (enemiesRight+enemiesLeft)-(enemiesFront+enemiesBack)>1 ) + {//more enemies on left & right than front/back + kickMove = LS_KICK_RL; + } + else if ( (enemiesFront || enemiesBack) && (enemiesRight || enemiesLeft) ) + {//at least 2 enemies around us, not aligned + kickMove = LS_KICK_S; + } + else if ( enemiesSpin > 1 ) + {//at least 2 enemies around us, not aligned + kickMove = LS_KICK_S; + } + } + if ( kickMove == LS_NONE + && bestKick != LS_NONE ) + {//no good multi-kick move, but we do have a nice single-kick we found + kickMove = bestKick; + //get mad at him so he knows he's being targetted + if ( (self->s.number < MAX_CLIENTS||G_ControlledByPlayer(self)) + && bestEnt != NULL ) + {//player + G_SetEnemy( self, bestEnt ); + } + } + if ( kickMove != LS_NONE ) + { + if ( storeMove ) + { + self->client->ps.saberMoveNext = kickMove; + } + } + return kickMove; +} + +qboolean PM_PickAutoMultiKick( qboolean allowSingles ) +{ + saberMoveName_t kickMove = G_PickAutoMultiKick( pm->gent, allowSingles, qfalse ); + if ( kickMove != LS_NONE ) + { + PM_SetSaberMove( kickMove ); + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberThrowable( void ) +{ + //ugh, hard-coding this is bad... + if ( pm->ps->saberAnimLevel == SS_STAFF ) + { + return qfalse; + } + + if ( pm->ps->saber[0].throwable ) + {//yes, this saber is always throwable + return qtrue; + } + + //saber is not normally throwable + if ( pm->ps->saber[0].singleBladeThrowable ) + {//it is throwable if only one blade is on + if ( pm->ps->saber[0].numBlades > 1 ) + {//it has more than one blade + int numBladesActive = 0; + for ( int i = 0; i < pm->ps->saber[0].numBlades; i++ ) + { + if ( pm->ps->saber[0].blade[i].active ) + { + numBladesActive++; + } + } + if ( numBladesActive == 1 ) + {//only 1 blade is on + return qtrue; + } + } + } + //nope, can't throw it + return qfalse; +} + +qboolean PM_CheckAltKickAttack( void ) +{ + if ( (pm->cmd.buttons&BUTTON_ALT_ATTACK) + && (!(pm->ps->pm_flags&PMF_ALT_ATTACK_HELD) ||PM_SaberInReturn(pm->ps->saberMove)) + && (!PM_FlippingAnim(pm->ps->legsAnim)||pm->ps->legsAnimTimer<=250) + && (!PM_SaberThrowable()) && pm->ps->SaberActive() ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_CheckUpsideDownAttack( void ) +{ + if ( pm->ps->saberMove != LS_READY ) + { + return qfalse; + } + if ( !(pm->cmd.buttons&BUTTON_ATTACK) ) + { + return qfalse; + } + if ( pm->ps->saberAnimLevel < SS_FAST + || pm->ps->saberAnimLevel > SS_STRONG ) + { + return qfalse; + } + if ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) ) + {//FIXME: check ranks? + return qfalse; + } + //FIXME: enemy below + //FIXME: more than 64 off ground + if ( !g_debugMelee->integer ) + {//hmm, can't get this to work quite the way we wanted... secret move, then! + return qfalse; + } + + switch( pm->ps->legsAnim ) + { + case BOTH_WALL_RUN_RIGHT_FLIP: + case BOTH_WALL_RUN_LEFT_FLIP: + case BOTH_WALL_FLIP_RIGHT: + case BOTH_WALL_FLIP_LEFT: + case BOTH_FLIP_BACK1: + case BOTH_FLIP_BACK2: + case BOTH_FLIP_BACK3: + case BOTH_WALL_FLIP_BACK1: + case BOTH_ALORA_FLIP_B: + //JKA + case BOTH_FORCEWALLRUNFLIP_END: + { + float animLength = PM_AnimLength( pm->gent->client->clientInfo.animFileIndex, (animNumber_t)pm->ps->legsAnim ); + float elapsedTime = (float)(animLength-pm->ps->legsAnimTimer); + float midPoint = animLength/2.0f; + if ( elapsedTime < midPoint-100.0f + || elapsedTime > midPoint+100.0f ) + {//only a 200ms window (in middle of anim) of opportunity to do this move in these anims + return qfalse; + } + } + //NOTE: falls through on purpose + case BOTH_FLIP_HOLD7: + pm->ps->pm_flags |= PMF_SLOW_MO_FALL; + PM_SetSaberMove( LS_UPSIDE_DOWN_ATTACK ); + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SaberMoveOkayForKata( void ) +{ + if ( g_saberNewControlScheme->integer ) + { + if ( pm->ps->saberMove == LS_READY //not doing anything + || PM_SaberInReflect( pm->ps->saberMove ) )//interrupt a projectile blocking move + { + return qtrue; + } + else + { + return qfalse; + } + } + else + {//old control scheme, allow it to interrupt a start or ready + if ( pm->ps->saberMove == LS_READY + || PM_SaberInReflect( pm->ps->saberMove )//interrupt a projectile blocking move + || PM_SaberInStart( pm->ps->saberMove ) ) + { + return qtrue; + } + else + { + return qfalse; + } + } +} + +qboolean PM_CanDoKata( void ) +{ + if ( PM_InSecondaryStyle() ) + { + return qfalse; + } + if ( !pm->ps->saberInFlight//not throwing saber + && PM_SaberMoveOkayForKata() + /* + && pm->ps->saberAnimLevel >= SS_FAST//fast, med or strong style + && pm->ps->saberAnimLevel <= SS_STRONG//FIXME: Tavion, too? + */ + && pm->ps->groundEntityNum != ENTITYNUM_NONE//not in the air + && (pm->cmd.buttons&BUTTON_ATTACK)//pressing attack + && pm->cmd.forwardmove >=0 //not moving back (used to be !pm->cmd.forwardmove) + && !pm->cmd.rightmove//not moving r/l + && pm->cmd.upmove <= 0//not jumping...? + && G_TryingKataAttack(pm->gent,&pm->cmd)/*(pm->cmd.buttons&BUTTON_FORCE_FOCUS)*///holding focus + && G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER, qtrue )/*pm->ps->forcePower >= SABER_ALT_ATTACK_POWER*/ )//SINGLE_SPECIAL_POWER )// have enough power + {//FIXME: check rage, etc... + return qtrue; + } + return qfalse; +} + +void PM_SaberDroidWeapon( void ) +{ + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime <= 0 ) + { + pm->ps->weaponTime = 0; + } + } + + // Now we react to a block action by the player's lightsaber. + if ( pm->ps->saberBlocked ) + { + switch ( pm->ps->saberBlocked ) + { + case BLOCKED_PARRY_BROKEN: + PM_SetAnim( pm, SETANIM_BOTH, Q_irand(BOTH_PAIN1,BOTH_PAIN3), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + pm->ps->weaponTime = pm->ps->legsAnimTimer; + break; + case BLOCKED_ATK_BOUNCE: + PM_SetAnim( pm, SETANIM_BOTH, Q_irand(BOTH_PAIN1,BOTH_PAIN3), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + pm->ps->weaponTime = pm->ps->legsAnimTimer; + break; + case BLOCKED_UPPER_RIGHT: + case BLOCKED_UPPER_RIGHT_PROJ: + case BLOCKED_LOWER_RIGHT: + case BLOCKED_LOWER_RIGHT_PROJ: + PM_SetAnim( pm, SETANIM_BOTH, BOTH_P1_S1_TR, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + pm->ps->legsAnimTimer += Q_irand( 200, 1000 ); + pm->ps->weaponTime = pm->ps->legsAnimTimer; + break; + case BLOCKED_UPPER_LEFT: + case BLOCKED_UPPER_LEFT_PROJ: + case BLOCKED_LOWER_LEFT: + case BLOCKED_LOWER_LEFT_PROJ: + PM_SetAnim( pm, SETANIM_BOTH, BOTH_P1_S1_TL, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + pm->ps->legsAnimTimer += Q_irand( 200, 1000 ); + pm->ps->weaponTime = pm->ps->legsAnimTimer; + break; + case BLOCKED_TOP: + case BLOCKED_TOP_PROJ: + PM_SetAnim( pm, SETANIM_BOTH, BOTH_P1_S1_T_, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + pm->ps->legsAnimTimer += Q_irand( 200, 1000 ); + pm->ps->weaponTime = pm->ps->legsAnimTimer; + break; + default: + pm->ps->saberBlocked = BLOCKED_NONE; + break; + } + + pm->ps->saberBlocked = BLOCKED_NONE; + pm->ps->saberBounceMove = LS_NONE; + pm->ps->weaponstate = WEAPON_READY; + + // Done with block, so stop these active weapon branches. + return; + } +} + +void PM_TryGrab( void ) +{ + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE + //&& !pm->ps->saberInFlight + && pm->ps->weaponTime <= 0 )//< 200 ) + { + PM_SetAnim( pm, SETANIM_BOTH, BOTH_KYLE_GRAB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + pm->ps->torsoAnimTimer += 200; + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + pm->ps->saberMove = pm->ps->saberMoveNext = LS_READY; + VectorClear( pm->ps->velocity ); + VectorClear( pm->ps->moveDir ); + pm->cmd.rightmove = pm->cmd.forwardmove = pm->cmd.upmove = 0; + if ( pm->gent ) + { + pm->gent->painDebounceTime = level.time + pm->ps->torsoAnimTimer; + } + pm->ps->SaberDeactivate(); + } +} + +void PM_TryAirKick( saberMoveName_t kickMove ) +{ + if ( pm->ps->groundEntityNum < ENTITYNUM_NONE ) + {//just do it + PM_SetSaberMove( kickMove ); + } + else + { + float gDist = PM_GroundDistance(); + //let's only allow air kicks if a certain distance from the ground + //it's silly to be able to do them right as you land. + //also looks wrong to transition from a non-complete flip anim... + if ((!PM_FlippingAnim( pm->ps->legsAnim ) || pm->ps->legsAnimTimer <= 0) && + gDist > 64.0f && //strict minimum + gDist > (-pm->ps->velocity[2])-64.0f //make sure we are high to ground relative to downward velocity as well + ) + { + PM_SetSaberMove( kickMove ); + } + else + {//leave it as a normal kick unless we're too high up + if ( gDist > 128.0f || pm->ps->velocity[2] >= 0 ) + { //off ground, but too close to ground + } + else + {//high close enough to ground to do a normal kick, convert it + switch ( kickMove ) + { + case LS_KICK_F_AIR: + PM_SetSaberMove( LS_KICK_F ); + break; + case LS_KICK_B_AIR: + PM_SetSaberMove( LS_KICK_B ); + break; + case LS_KICK_R_AIR: + PM_SetSaberMove( LS_KICK_R ); + break; + case LS_KICK_L_AIR: + PM_SetSaberMove( LS_KICK_L ); + break; + } + } + } + } +} + +void PM_CheckKick( void ) +{ + if ( !PM_KickMove( pm->ps->saberMove )//not already in a kick + && !(pm->ps->pm_flags&PMF_DUCKED)//not ducked + && (pm->cmd.upmove >= 0 ) )//not trying to duck + {//player kicks + //FIXME: only if FP_SABER_OFFENSE >= 3 + if ( pm->cmd.rightmove ) + {//kick to side + if ( pm->cmd.rightmove > 0 ) + {//kick right + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE + || pm->cmd.upmove > 0 ) + { + PM_TryAirKick( LS_KICK_R_AIR ); + } + else + { + PM_SetSaberMove( LS_KICK_R ); + } + } + else + {//kick left + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE + || pm->cmd.upmove > 0 ) + { + PM_TryAirKick( LS_KICK_L_AIR ); + } + else + { + PM_SetSaberMove( LS_KICK_L ); + } + } + pm->cmd.rightmove = 0; + } + else if ( pm->cmd.forwardmove ) + {//kick front/back + if ( pm->cmd.forwardmove > 0 ) + {//kick fwd + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE + || pm->cmd.upmove > 0 ) + { + PM_TryAirKick( LS_KICK_F_AIR ); + } + /* + else if ( pm->ps->weapon == WP_SABER + && pm->ps->saberAnimLevel == SS_STAFF + && pm->gent + && G_CheckEnemyPresence( pm->gent, DIR_FRONT, 64, 0.8f ) ) + {//FIXME: don't jump while doing this move and don't do this move if in air + PM_SetSaberMove( LS_HILT_BASH ); + } + */ + else + { + PM_SetSaberMove( LS_KICK_F ); + } + } + else if ( pm->ps->groundEntityNum == ENTITYNUM_NONE + || pm->cmd.upmove > 0 ) + { + PM_TryAirKick( LS_KICK_B_AIR ); + } + else + {//kick back + PM_SetSaberMove( LS_KICK_B ); + } + pm->cmd.forwardmove = 0; + } + else if ( pm->gent + && pm->gent->enemy + && G_CanKickEntity( pm->gent, pm->gent->enemy ) ) + {//auto-pick? + if ( /*(pm->ps->pm_flags&PMF_ALT_ATTACK_HELD) + && (pm->cmd.buttons&BUTTON_ATTACK) + &&*/ PM_PickAutoMultiKick( qfalse ) ) + {//kicked! + if ( pm->ps->saberMove == LS_KICK_RL ) + {//just pull back + if ( d_slowmodeath->integer > 3 ) + { + G_StartMatrixEffect( pm->gent, MEF_NO_SPIN, pm->ps->legsAnimTimer+500 ); + } + } + else + {//normal spin + if ( d_slowmodeath->integer > 3 ) + { + G_StartMatrixEffect( pm->gent, 0, pm->ps->legsAnimTimer+500 ); + } + } + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE + &&( pm->ps->saberMove == LS_KICK_S + ||pm->ps->saberMove == LS_KICK_BF + ||pm->ps->saberMove == LS_KICK_RL ) ) + {//in the air and doing a jump-kick, which is a ground anim, so.... + //cut z velocity...? + pm->ps->velocity[2] = 0; + } + pm->cmd.upmove = 0; + } + else + { + saberMoveName_t kickMove = PM_PickAutoKick( pm->gent->enemy ); + if ( kickMove != LS_NONE ) + {//Matrix? + PM_SetSaberMove( kickMove ); + int meFlags = 0; + switch ( kickMove ) + { + case LS_KICK_B://just pull back + case LS_KICK_B_AIR://just pull back + meFlags = MEF_NO_SPIN; + break; + case LS_KICK_L://spin to the left + case LS_KICK_L_AIR://spin to the left + meFlags = MEF_REVERSE_SPIN; + break; + default: + break; + } + if ( d_slowmodeath->integer > 3 ) + { + G_StartMatrixEffect( pm->gent, meFlags, pm->ps->legsAnimTimer+500 ); + } + } + } + } + else + { + if ( PM_PickAutoMultiKick( qtrue ) ) + { + int meFlags = 0; + switch ( pm->ps->saberMove ) + { + case LS_KICK_RL://just pull back + case LS_KICK_B://just pull back + case LS_KICK_B_AIR://just pull back + meFlags = MEF_NO_SPIN; + break; + case LS_KICK_L://spin to the left + case LS_KICK_L_AIR://spin to the left + meFlags = MEF_REVERSE_SPIN; + break; + default: + break; + } + if ( d_slowmodeath->integer > 3 ) + { + G_StartMatrixEffect( pm->gent, meFlags, pm->ps->legsAnimTimer+500 ); + } + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE + &&( pm->ps->saberMove == LS_KICK_S + ||pm->ps->saberMove == LS_KICK_BF + ||pm->ps->saberMove == LS_KICK_RL ) ) + {//in the air and doing a jump-kick, which is a ground anim, so.... + //cut z velocity...? + pm->ps->velocity[2] = 0; + } + pm->cmd.upmove = 0; + } + } + } +} + +void PM_CheckClearSaberBlock( void ) +{ + if ( pm->ps->clientNum < MAX_CLIENTS || PM_ControlledByPlayer() ) + {//player + if ( pm->ps->saberBlocked >= BLOCKED_UPPER_RIGHT_PROJ && pm->ps->saberBlocked <= BLOCKED_TOP_PROJ ) + {//blocking a projectile + if ( pm->ps->forcePowerDebounce[FP_SABER_DEFENSE] < level.time ) + {//block is done or breaking out of it with an attack + pm->ps->weaponTime = 0; + pm->ps->saberBlocked = BLOCKED_NONE; + } + else if ( (pm->cmd.buttons&BUTTON_ATTACK) ) + {//block is done or breaking out of it with an attack + pm->ps->weaponTime = 0; + pm->ps->saberBlocked = BLOCKED_NONE; + } + } + else if ( pm->ps->saberBlocked == BLOCKED_UPPER_LEFT + && pm->ps->powerups[PW_SHOCKED] > level.time ) + {//probably blocking lightning + if ( (pm->cmd.buttons&BUTTON_ATTACK) ) + {//trying to attack + //allow the attack + pm->ps->weaponTime = 0; + pm->ps->saberBlocked = BLOCKED_NONE; + } + } + } +} + +qboolean PM_SaberBlocking( void ) +{ + // Now we react to a block action by the player's lightsaber. + if ( pm->ps->saberBlocked ) + { + if ( pm->ps->saberMove > LS_PUTAWAY && pm->ps->saberMove <= LS_A_BL2TR && pm->ps->saberBlocked != BLOCKED_PARRY_BROKEN && + (pm->ps->saberBlocked < BLOCKED_UPPER_RIGHT_PROJ || pm->ps->saberBlocked > BLOCKED_TOP_PROJ))//&& Q_irand( 0, 2 ) + {//we parried another lightsaber while attacking, so treat it as a bounce + pm->ps->saberBlocked = BLOCKED_ATK_BOUNCE; + } + else if ( pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer() )//player + { + if ( pm->ps->saberBlocked >= BLOCKED_UPPER_RIGHT_PROJ + && pm->ps->saberBlocked <= BLOCKED_TOP_PROJ ) + {//blocking a projectile + if ( (pm->cmd.buttons&BUTTON_ATTACK) ) + {//trying to attack + if ( pm->ps->saberMove == LS_READY + || PM_SaberInReflect( pm->ps->saberMove ) ) + {//not already busy or already in projectile deflection + //trying to attack during projectile blocking, so do the attack instead + pm->ps->saberBlocked = BLOCKED_NONE; + pm->ps->saberBounceMove = LS_NONE; + pm->ps->weaponstate = WEAPON_READY; + if ( PM_SaberInReflect( pm->ps->saberMove ) && pm->ps->weaponTime > 0 ) + {//interrupt the current deflection move + pm->ps->weaponTime = 0; + } + return qfalse; + } + } + } + } + /* + else if ( (pm->cmd.buttons&BUTTON_ATTACK) + && pm->ps->saberBlocked >= BLOCKED_UPPER_RIGHT + && pm->ps->saberBlocked <= BLOCKED_TOP + && !PM_SaberInKnockaway( pm->ps->saberBounceMove ) ) + {//if hitting attack during a parry (not already a knockaway) + if ( pm->ps->forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 ) + {//have high saber defense, turn the parry into a knockaway? + //FIXME: this could actually be bad for us...? Leaves us open + pm->ps->saberBounceMove = PM_KnockawayForParry( pm->ps->saberBlocked ); + } + } + */ + + if ( pm->ps->saberBlocked != BLOCKED_ATK_BOUNCE ) + {//can't attack for twice whatever your skill level's parry debounce time is + if ( pm->ps->clientNum == 0 || PM_ControlledByPlayer() ) + {//player + if ( pm->ps->forcePowerLevel[FP_SABER_DEFENSE] <= FORCE_LEVEL_1 ) + { + pm->ps->weaponTime = parryDebounce[pm->ps->forcePowerLevel[FP_SABER_DEFENSE]]; + } + } + else + {//NPC + //pm->ps->weaponTime = parryDebounce[pm->ps->forcePowerLevel[FP_SABER_DEFENSE]] * 2; + if ( pm->gent ) + { + pm->ps->weaponTime = Jedi_ReCalcParryTime( pm->gent, EVASION_PARRY ); + } + else + {//WTF??? + pm->ps->weaponTime = parryDebounce[pm->ps->forcePowerLevel[FP_SABER_DEFENSE]] * 2; + } + } + } + switch ( pm->ps->saberBlocked ) + { + case BLOCKED_PARRY_BROKEN: + //whatever parry we were is in now broken, play the appropriate knocked-away anim + { + saberMoveName_t nextMove; + if ( PM_SaberInBrokenParry( pm->ps->saberBounceMove ) ) + {//already have one...? + nextMove = (saberMoveName_t)pm->ps->saberBounceMove; + } + else + { + nextMove = PM_BrokenParryForParry( (saberMoveName_t)pm->ps->saberMove ); + } + if ( nextMove != LS_NONE ) + { + PM_SetSaberMove( nextMove ); + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + else + {//Maybe in a knockaway? + } + //pm->ps->saberBounceMove = LS_NONE; + } + break; + case BLOCKED_ATK_BOUNCE: + // If there is absolutely no blocked move in the chart, don't even mess with the animation. + // OR if we are already in a block or parry. + if ( pm->ps->saberMove >= LS_T1_BR__R/*LS_BOUNCE_TOP*/ )//|| saberMoveData[pm->ps->saberMove].bounceMove == LS_NONE ) + {//an actual bounce? Other bounces before this are actually transitions? + pm->ps->saberBlocked = BLOCKED_NONE; + } + else if ( PM_SaberInBounce( pm->ps->saberMove ) || !PM_SaberInAttack( pm->ps->saberMove ) ) + {//already in the bounce, go into an attack or transition to ready.. should never get here since can't be blocked in a bounce! + int nextMove; + + if ( pm->cmd.buttons & BUTTON_ATTACK ) + {//transition to a new attack + if ( pm->ps->clientNum && !PM_ControlledByPlayer() ) + {//NPC + nextMove = saberMoveData[pm->ps->saberMove].chain_attack; + } + else + {//player + int newQuad = PM_SaberMoveQuadrantForMovement( &pm->cmd ); + while ( newQuad == saberMoveData[pm->ps->saberMove].startQuad ) + {//player is still in same attack quad, don't repeat that attack because it looks bad, + //FIXME: try to pick one that might look cool? + newQuad = Q_irand( Q_BR, Q_BL ); + //FIXME: sanity check, just in case? + }//else player is switching up anyway, take the new attack dir + nextMove = transitionMove[saberMoveData[pm->ps->saberMove].startQuad][newQuad]; + } + } + else + {//return to ready + if ( pm->ps->clientNum && !PM_ControlledByPlayer() ) + {//NPC + nextMove = saberMoveData[pm->ps->saberMove].chain_idle; + } + else + {//player + if ( saberMoveData[pm->ps->saberMove].startQuad == Q_T ) + { + nextMove = LS_R_BL2TR; + } + else if ( saberMoveData[pm->ps->saberMove].startQuad < Q_T ) + { + nextMove = LS_R_TL2BR+(saberMoveName_t)(saberMoveData[pm->ps->saberMove].startQuad-Q_BR); + } + else// if ( saberMoveData[pm->ps->saberMove].startQuad > Q_T ) + { + nextMove = LS_R_BR2TL+(saberMoveName_t)(saberMoveData[pm->ps->saberMove].startQuad-Q_TL); + } + } + } + PM_SetSaberMove( (saberMoveName_t)nextMove ); + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + else + {//start the bounce move + saberMoveName_t bounceMove; + if ( pm->ps->saberBounceMove != LS_NONE ) + { + bounceMove = (saberMoveName_t)pm->ps->saberBounceMove; + } + else + { + bounceMove = PM_SaberBounceForAttack( (saberMoveName_t)pm->ps->saberMove ); + } + PM_SetSaberMove( bounceMove ); + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + //clear the saberBounceMove + //pm->ps->saberBounceMove = LS_NONE; + + if (cg_debugSaber.integer>=2) + { + Com_Printf("Saber Block: Bounce\n"); + } + break; + case BLOCKED_UPPER_RIGHT: + if ( pm->ps->saberBounceMove != LS_NONE ) + { + PM_SetSaberMove( (saberMoveName_t)pm->ps->saberBounceMove ); + //pm->ps->saberBounceMove = LS_NONE; + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + else + { + PM_SetSaberMove( LS_PARRY_UR ); + } + + if ( cg_debugSaber.integer >= 2 ) + { + Com_Printf( "Saber Block: Parry UR\n" ); + } + break; + case BLOCKED_UPPER_RIGHT_PROJ: + PM_SetSaberMove( LS_REFLECT_UR ); + + if ( cg_debugSaber.integer >= 2 ) + { + Com_Printf("Saber Block: Deflect UR\n"); + } + break; + case BLOCKED_UPPER_LEFT: + if ( pm->ps->saberBounceMove != LS_NONE ) + { + PM_SetSaberMove( (saberMoveName_t)pm->ps->saberBounceMove ); + //pm->ps->saberBounceMove = LS_NONE; + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + else + { + PM_SetSaberMove( LS_PARRY_UL ); + } + + if ( cg_debugSaber.integer >= 2 ) + { + Com_Printf( "Saber Block: Parry UL\n" ); + } + break; + case BLOCKED_UPPER_LEFT_PROJ: + PM_SetSaberMove( LS_REFLECT_UL ); + + if ( cg_debugSaber.integer >= 2 ) + { + Com_Printf("Saber Block: Deflect UL\n"); + } + break; + case BLOCKED_LOWER_RIGHT: + if ( pm->ps->saberBounceMove != LS_NONE ) + { + PM_SetSaberMove( (saberMoveName_t)pm->ps->saberBounceMove ); + //pm->ps->saberBounceMove = LS_NONE; + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + else + { + PM_SetSaberMove( LS_PARRY_LR ); + } + + if ( cg_debugSaber.integer >= 2 ) + { + Com_Printf("Saber Block: Parry LR\n"); + } + break; + case BLOCKED_LOWER_RIGHT_PROJ: + PM_SetSaberMove( LS_REFLECT_LR ); + + if ( cg_debugSaber.integer >= 2 ) + { + Com_Printf("Saber Block: Deflect LR\n"); + } + break; + case BLOCKED_LOWER_LEFT: + if ( pm->ps->saberBounceMove != LS_NONE ) + { + PM_SetSaberMove( (saberMoveName_t)pm->ps->saberBounceMove ); + //pm->ps->saberBounceMove = LS_NONE; + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + else + { + PM_SetSaberMove( LS_PARRY_LL ); + } + + if ( cg_debugSaber.integer >= 2 ) + { + Com_Printf("Saber Block: Parry LL\n"); + } + break; + case BLOCKED_LOWER_LEFT_PROJ: + PM_SetSaberMove( LS_REFLECT_LL); + + if ( cg_debugSaber.integer >= 2 ) + { + Com_Printf("Saber Block: Deflect LL\n"); + } + break; + case BLOCKED_TOP: + if ( pm->ps->saberBounceMove != LS_NONE ) + { + PM_SetSaberMove( (saberMoveName_t)pm->ps->saberBounceMove ); + //pm->ps->saberBounceMove = LS_NONE; + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + else + { + PM_SetSaberMove( LS_PARRY_UP ); + } + + if ( cg_debugSaber.integer >= 2 ) + { + Com_Printf("Saber Block: Parry Top\n"); + } + break; + case BLOCKED_TOP_PROJ: + PM_SetSaberMove( LS_REFLECT_UP ); + + if ( cg_debugSaber.integer >= 2 ) + { + Com_Printf("Saber Block: Deflect Top\n"); + } + break; + default: + pm->ps->saberBlocked = BLOCKED_NONE; + break; + } + + // Charging is like a lead-up before attacking again. This is an appropriate use, or we can create a new weaponstate for blocking + pm->ps->saberBounceMove = LS_NONE; + pm->ps->weaponstate = WEAPON_READY; + + // Done with block, so stop these active weapon branches. + return qtrue; + } + return qfalse; +} + +qboolean PM_NPCCheckAttackRoll( void ) +{ + if ( pm->ps->clientNum >= MAX_CLIENTS && !PM_ControlledByPlayer()//NPC + && pm->gent + && pm->gent->NPC + //&& Q_irand(-3,pm->gent->NPC->rank)>RANK_CREWMAN) + && ( pm->gent->NPC->rank > RANK_CREWMAN && !Q_irand(0,3-g_spskill->integer) ) + && pm->gent->enemy + && fabs(pm->gent->enemy->currentOrigin[2]-pm->ps->origin[2])<32 + && DistanceHorizontalSquared(pm->gent->enemy->currentOrigin, pm->ps->origin ) < (128.0f*128.0f) + && InFOV( pm->gent->enemy->currentOrigin, pm->ps->origin, pm->ps->viewangles, 30, 90 ) ) + {//stab! + return qtrue; + } + return qfalse; +} +/* +================= +PM_WeaponLightsaber + +Consults a chart to choose what to do with the lightsaber. +While this is a little different than the Quake 3 code, there is no clean way of using the Q3 code for this kind of thing. +================= +*/ +// Ultimate goal is to set the sabermove to the proper next location +// Note that if the resultant animation is NONE, then the animation is essentially "idle", and is set in WP_TorsoAnim +void PM_WeaponLightsaber(void) +{ + int addTime,amount; + qboolean delayed_fire = qfalse, animLevelOverridden = qfalse; + weaponInfo_t *weapon; + int anim=-1; + int curmove, newmove=LS_NONE; + + if ( pm->gent + && pm->gent->client + && pm->gent->client->NPC_class == CLASS_SABER_DROID ) + {//Saber droid does it's own attack logic + PM_SaberDroidWeapon(); + return; + } + + // don't allow attack until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) + { + if ( pm->gent ) + { + pm->gent->s.loopSound = 0; + } + return; + } + + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime <= 0 ) + { + pm->ps->weaponTime = 0; + } + } + + if ( pm->ps->stats[STAT_WEAPONS]&(1<ps->dualSabers + && pm->gent + && pm->gent->weaponModel[1] ) + {//holding scepter in left hand, use dual style + pm->ps->saberAnimLevel = SS_DUAL; + animLevelOverridden = qtrue; + } + else if ( pm->ps->saber[0].singleBladeStyle != SS_NONE//SaberStaff() + && !pm->ps->dualSabers + && pm->ps->saber[0].blade[0].active + && !pm->ps->saber[0].blade[1].active ) + {//using a staff, but only with first blade turned on, so use is as a normal saber...? + //override so that single-blade staff must be used with specified style + pm->ps->saberAnimLevel = pm->ps->saber[0].singleBladeStyle;//SS_STRONG; + animLevelOverridden = qtrue; + } + else if ( pm->ps->saber[0].Active() && pm->ps->saber[0].style != SS_NONE ) + {//one of the sabers I'm using forces me to use a single style + pm->ps->saberAnimLevel = pm->ps->saber[0].style; + animLevelOverridden = qtrue; + } + else if ( pm->ps->dualSabers ) + { + if ( pm->ps->saber[1].Active() && pm->ps->saber[1].style != SS_NONE ) + {//one of the sabers I'm using forces me to use a single style + pm->ps->saberAnimLevel = pm->ps->saber[1].style; + animLevelOverridden = qtrue; + } + else if ( pm->ps->saber[1].Active() ) + {//if second saber is on, must use dual style + pm->ps->saberAnimLevel = SS_DUAL; + animLevelOverridden = qtrue; + } + else if ( pm->ps->saber[0].Active() ) + {//with only one saber on, use fast style + pm->ps->saberAnimLevel = SS_FAST; + animLevelOverridden = qtrue; + } + } + if ( !animLevelOverridden ) + { + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + && cg.saberAnimLevelPending > SS_NONE + && cg.saberAnimLevelPending != pm->ps->saberAnimLevel ) + { + if ( !PM_SaberInStart( pm->ps->saberMove ) + && !PM_SaberInTransition( pm->ps->saberMove ) + && !PM_SaberInAttack( pm->ps->saberMove ) ) + {//don't allow changes when in the middle of an attack set...(or delay the change until it's done) + pm->ps->saberAnimLevel = cg.saberAnimLevelPending; + } + } + } + else if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + {//if overrid the player's saberAnimLevel, let the cgame know + cg.saberAnimLevelPending = pm->ps->saberAnimLevel; + } + /* + if ( PM_InForceGetUp( pm->ps ) ) + {//if mostly up, can start attack + if ( pm->ps->torsoAnimTimer > 800 ) + {//not up enough yet + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime <= 0 ) + { + pm->ps->weaponTime = 0; + } + } + return; + } + else + { + if ( pm->cmd.buttons & BUTTON_ATTACK ) + {//let an attack interrupt the torso part of this force getup + pm->ps->weaponTime = 0; + } + } + } + else + */ + if ( PM_InKnockDown( pm->ps ) || PM_InRoll( pm->ps )) + {//in knockdown + if ( pm->ps->legsAnim == BOTH_ROLL_F + && pm->ps->legsAnimTimer <= 250 ) + { + if ( (pm->cmd.buttons&BUTTON_ATTACK) + || PM_NPCCheckAttackRoll() ) + { + if ( G_EnoughPowerForSpecialMove( pm->ps->forcePower, SABER_ALT_ATTACK_POWER_FB ) ) + { + PM_SetSaberMove( LS_ROLL_STAB ); + if ( pm->gent ) + { + G_DrainPowerForSpecialMove( pm->gent, FP_SABER_OFFENSE, SABER_ALT_ATTACK_POWER_FB ); + } + } + } + } + return; + } + + if ( PM_SaberLocked() ) + { + pm->ps->saberMove = LS_NONE; + return; + } + + if ( pm->ps->torsoAnim == BOTH_FORCELONGLEAP_LAND + || (pm->ps->torsoAnim == BOTH_FORCELONGLEAP_START && !(pm->cmd.buttons&BUTTON_ATTACK)) ) + {//if you're in the long-jump and you're not attacking (or are landing), you're not doing anything + if ( pm->ps->torsoAnimTimer ) + { + return; + } + } + + if ( pm->ps->legsAnim == BOTH_FLIP_HOLD7 + && !(pm->cmd.buttons&BUTTON_ATTACK) ) + {//if you're in the upside-down attack hold, don't do anything unless you're attacking + return; + } + + if ( PM_KickingAnim( pm->ps->legsAnim ) ) + { + if ( pm->ps->legsAnimTimer ) + {//you're kicking, no interruptions + return; + } + //done? be immeditately ready to do an attack + pm->ps->saberMove = LS_READY; + pm->ps->weaponTime = 0; + } + + if ( pm->ps->saberMoveNext != LS_NONE + && (pm->ps->saberMove == LS_READY||pm->ps->saberMove == LS_NONE))//ready for another one + {//something is forcing us to set a specific next saberMove + //FIXME: if this is a NPC kick, re-verify it before executing it! + PM_SetSaberMove( (saberMoveName_t)pm->ps->saberMoveNext ); + pm->ps->saberMoveNext = LS_NONE;//clear it now that we played it + return; + } + + if ( pm->ps->saberEventFlags&SEF_INWATER )//saber in water + { + pm->cmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS); + } + + qboolean saberInAir = qtrue; + if ( !PM_SaberInBrokenParry( pm->ps->saberMove ) && pm->ps->saberBlocked != BLOCKED_PARRY_BROKEN && !PM_DodgeAnim( pm->ps->torsoAnim ) ) + {//we're not stuck in a broken parry + if ( pm->ps->saberInFlight ) + {//guiding saber + if ( pm->ps->saberEntityNum < ENTITYNUM_NONE && pm->ps->saberEntityNum > 0 )//player is 0 + {// + if ( &g_entities[pm->ps->saberEntityNum] != NULL && g_entities[pm->ps->saberEntityNum].s.pos.trType == TR_STATIONARY ) + {//fell to the ground and we're not trying to pull it back + saberInAir = qfalse; + } + } + if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) + { + if ( pm->ps->weapon != pm->cmd.weapon ) + { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + else if ( pm->ps->weapon == WP_SABER + && (!pm->ps->dualSabers || !pm->ps->saber[1].Active()) ) + {//guiding saber + if ( saberInAir ) + { + if ( !PM_ForceAnim( pm->ps->torsoAnim ) || pm->ps->torsoAnimTimer < 300 ) + {//don't interrupt a force power anim + if ( pm->ps->torsoAnim != BOTH_LOSE_SABER + || !pm->ps->torsoAnimTimer ) + { + PM_SetAnim( pm, SETANIM_TORSO, BOTH_SABERPULL, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + return; + } + } + } + + if ( pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0 ) + {//FIXME: this is going to fire off one frame before you expect, actually + pm->gent->client->fireDelay -= pml.msec; + if ( pm->gent->client->fireDelay <= 0 ) + {//just finished delay timer + pm->gent->client->fireDelay = 0; + delayed_fire = qtrue; + } + } + + PM_CheckClearSaberBlock(); + + if ( PM_LockedAnim( pm->ps->torsoAnim ) + && pm->ps->torsoAnimTimer ) + {//can't interrupt these anims ever + return; + } + if ( PM_SuperBreakLoseAnim( pm->ps->torsoAnim ) + && pm->ps->torsoAnimTimer ) + {//don't interrupt these anims + return; + } + if ( PM_SuperBreakWinAnim( pm->ps->torsoAnim ) + && pm->ps->torsoAnimTimer ) + {//don't interrupt these anims + return; + } + + if ( PM_SaberBlocking() ) + {//busy blocking, don't do attacks + return; + } + + // check for weapon change + // can't change if weapon is firing, but can change again if lowering or raising + if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { + if ( pm->ps->weapon != pm->cmd.weapon ) { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + + if ( pm->ps->weaponTime > 0 ) + { + //FIXME: allow some window of opportunity to change your attack + // if it just started and your directional input is different + // than it was before... but only 100 milliseconds at most? + //OR: Make it so that attacks don't start until 100ms after you + // press the attack button...??? + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) //player + && PM_SaberInReturn( pm->ps->saberMove )//in a saber return move - FIXME: what about transitions? + //&& pm->ps->torsoAnimTimer<=250//towards the end of a saber return anim + && pm->ps->saberBlocked == BLOCKED_NONE//not interacting with any other saber + && !(pm->cmd.buttons&BUTTON_ATTACK)//not trying to swing the saber + && (pm->cmd.forwardmove||pm->cmd.rightmove) )//trying to kick in a specific direction + { + if ( PM_CheckAltKickAttack() )//trying to do a kick + {//allow them to do the kick now! + pm->ps->weaponTime = 0; + PM_CheckKick(); + return; + } + } + else + { + if ( !pm->cmd.rightmove + &&!pm->cmd.forwardmove + &&(pm->cmd.buttons&BUTTON_ATTACK) ) + { + /* + if ( PM_CheckDualSpinProtect() ) + {//check to see if we're going to do the special dual push protect move + PM_SetSaberMove( LS_DUAL_SPIN_PROTECT ); + pm->ps->weaponstate = WEAPON_FIRING; + return; + } + else + */ + if ( !g_saberNewControlScheme->integer ) + { + saberMoveName_t pullAtk = PM_CheckPullAttack(); + if ( pullAtk != LS_NONE ) + { + PM_SetSaberMove( pullAtk ); + pm->ps->weaponstate = WEAPON_FIRING; + return; + } + } + } + else + { + return; + } + } + } + + // ********************************************************* + // WEAPON_DROPPING + // ********************************************************* + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + // ********************************************************* + // WEAPON_RAISING + // ********************************************************* + + if ( pm->ps->weaponstate == WEAPON_RAISING ) + {//Just selected the weapon + pm->ps->weaponstate = WEAPON_IDLE; + if(pm->gent && (pm->gent->s.numbergent))) + { + switch ( pm->ps->legsAnim ) + { + case BOTH_WALK1: + case BOTH_WALK2: + case BOTH_WALK_STAFF: + case BOTH_WALK_DUAL: + case BOTH_WALKBACK1: + case BOTH_WALKBACK2: + case BOTH_WALKBACK_STAFF: + case BOTH_WALKBACK_DUAL: + case BOTH_RUN1: + case BOTH_RUN2: + case BOTH_RUN_STAFF: + case BOTH_RUN_DUAL: + case BOTH_RUNBACK1: + case BOTH_RUNBACK2: + case BOTH_RUNBACK_STAFF: + PM_SetAnim(pm,SETANIM_TORSO,pm->ps->legsAnim,SETANIM_FLAG_NORMAL); + break; + default: + int anim = PM_ReadyPoseForSaberAnimLevel(); + if (anim!=-1) + { + PM_SetAnim(pm,SETANIM_TORSO,anim,SETANIM_FLAG_NORMAL); + } + break; + } + } + else + { + qboolean saberInAir = qtrue; + if ( pm->ps->saberInFlight ) + {//guiding saber + if ( PM_SaberInBrokenParry( pm->ps->saberMove ) || pm->ps->saberBlocked == BLOCKED_PARRY_BROKEN || PM_DodgeAnim( pm->ps->torsoAnim ) ) + {//we're stuck in a broken parry + saberInAir = qfalse; + } + if ( pm->ps->saberEntityNum < ENTITYNUM_NONE && pm->ps->saberEntityNum > 0 )//player is 0 + {// + if ( &g_entities[pm->ps->saberEntityNum] != NULL && g_entities[pm->ps->saberEntityNum].s.pos.trType == TR_STATIONARY ) + {//fell to the ground and we're not trying to pull it back + saberInAir = qfalse; + } + } + } + if ( pm->ps->weapon == WP_SABER + && pm->ps->saberInFlight + && saberInAir + && (!pm->ps->dualSabers || !pm->ps->saber[1].Active())) + {//guiding saber + if ( !PM_ForceAnim( pm->ps->torsoAnim ) || pm->ps->torsoAnimTimer < 300 ) + {//don't interrupt a force power anim + if ( pm->ps->torsoAnim != BOTH_LOSE_SABER + || !pm->ps->torsoAnimTimer ) + { + PM_SetAnim( pm, SETANIM_TORSO, BOTH_SABERPULL, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + else + { + // PM_SetAnim(pm, SETANIM_TORSO, BOTH_ATTACK1, SETANIM_FLAG_NORMAL); + // Select the proper idle Lightsaber attack move from the chart. + PM_SetSaberMove(LS_READY); + } + } + return; + } + + // ********************************************************* + // Check for WEAPON ATTACK + // ********************************************************* + + if ( PM_CanDoKata() ) + { + //FIXME: make sure to turn on saber(s)! + switch ( pm->ps->saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + PM_SetSaberMove( LS_A1_SPECIAL ); + break; + case SS_MEDIUM: + PM_SetSaberMove( LS_A2_SPECIAL ); + break; + case SS_STRONG: + case SS_DESANN: + PM_SetSaberMove( LS_A3_SPECIAL ); + break; + case SS_DUAL: + PM_SetSaberMove( LS_DUAL_SPIN_PROTECT );//PM_CheckDualSpinProtect(); + break; + case SS_STAFF: + PM_SetSaberMove( LS_STAFF_SOULCAL ); + break; + } + pm->ps->weaponstate = WEAPON_FIRING; + if ( pm->gent ) + { + G_DrainPowerForSpecialMove( pm->gent, FP_SABER_OFFENSE, SABER_ALT_ATTACK_POWER, qtrue );//FP_SPEED, SINGLE_SPECIAL_POWER ); + //G_StartMatrixEffect( pm->gent, MEF_REVERSE_SPIN, pm->ps->torsoAnimTimer ); + } + return; + } + + if ( PM_CheckAltKickAttack() ) + {//trying to do a kick + //FIXME: in-air kicks? + if ( pm->ps->saberAnimLevel == SS_STAFF + && (pm->ps->clientNum >= MAX_CLIENTS||PM_ControlledByPlayer()) ) + {//NPCs spin the staff + //NOTE: only NPCs can do it the easy way... they kick directly, not through ucmds... + PM_SetSaberMove( LS_SPINATTACK ); + return; + } + else + { + PM_CheckKick(); + } + return; + } + //this is never a valid regular saber attack button + //pm->cmd.buttons &= ~BUTTON_FORCE_FOCUS; + + if ( PM_CheckUpsideDownAttack() ) + { + return; + } + + weapon = &cg_weapons[pm->ps->weapon]; + + if(!delayed_fire) + { + // Start with the current move, and cross index it with the current control states. + if ( pm->ps->saberMove > LS_NONE && pm->ps->saberMove < LS_MOVE_MAX ) + { + curmove = (saberMoveName_t)pm->ps->saberMove; + } + else + { + curmove = LS_READY; + } + if ( curmove == LS_A_JUMP_T__B_ || pm->ps->torsoAnim == BOTH_FORCELEAP2_T__B_ ) + {//must transition back to ready from this anim + newmove = LS_R_T2B; + } + // check for fire + else if ( !(pm->cmd.buttons & BUTTON_ATTACK) )//(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS)) ) + {//not attacking + pm->ps->weaponTime = 0; + + if ( pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0 ) + {//Still firing + pm->ps->weaponstate = WEAPON_FIRING; + } + else if ( pm->ps->weaponstate != WEAPON_READY ) + { + pm->ps->weaponstate = WEAPON_IDLE; + } + //Check for finishing an anim if necc. + if ( curmove >= LS_S_TL2BR && curmove <= LS_S_T2B ) + {//started a swing, must continue from here + newmove = LS_A_TL2BR + (curmove-LS_S_TL2BR); + } + else if ( curmove >= LS_A_TL2BR && curmove <= LS_A_T2B ) + {//finished an attack, must continue from here + newmove = LS_R_TL2BR + (curmove-LS_A_TL2BR); + } + else if ( PM_SaberInTransition( curmove ) ) + {//in a transition, must play sequential attack + newmove = saberMoveData[curmove].chain_attack; + } + else if ( PM_SaberInBounce( curmove ) ) + {//in a bounce + if ( pm->ps->clientNum && !PM_ControlledByPlayer() ) + {//NPCs must play sequential attack + //going into another attack... + //allow endless chaining in level 1 attacks, several in level 2 and only one or a few in level 3 + if ( PM_SaberKataDone( LS_NONE, LS_NONE ) ) + {//done with this kata, must return to ready before attack again + newmove = saberMoveData[curmove].chain_idle; + } + else + {//okay to chain to another attack + newmove = saberMoveData[curmove].chain_attack;//we assume they're attacking, even if they're not + pm->ps->saberAttackChainCount++; + } + } + else + {//player gets his by directional control + newmove = saberMoveData[curmove].chain_idle;//oops, not attacking, so don't chain + } + } + else + {//FIXME: what about returning from a parry? + //PM_SetSaberMove( LS_READY ); + if ( pm->ps->saberBlockingTime > cg.time ) + { + PM_SetSaberMove( LS_READY ); + } + return; + } + } + + // *************************************************** + // Pressing attack, so we must look up the proper attack move. + qboolean saberInAir = qtrue; + if ( pm->ps->saberInFlight ) + {//guiding saber + if ( PM_SaberInBrokenParry( pm->ps->saberMove ) || pm->ps->saberBlocked == BLOCKED_PARRY_BROKEN || PM_DodgeAnim( pm->ps->torsoAnim ) ) + {//we're stuck in a broken parry + saberInAir = qfalse; + } + if ( pm->ps->saberEntityNum < ENTITYNUM_NONE && pm->ps->saberEntityNum > 0 )//player is 0 + {// + if ( &g_entities[pm->ps->saberEntityNum] != NULL && g_entities[pm->ps->saberEntityNum].s.pos.trType == TR_STATIONARY ) + {//fell to the ground and we're not trying to pull it back + saberInAir = qfalse; + } + } + } + + if ( pm->ps->weapon == WP_SABER + && pm->ps->saberInFlight + && saberInAir + && (!pm->ps->dualSabers || !pm->ps->saber[1].Active())) + {//guiding saber + if ( !PM_ForceAnim( pm->ps->torsoAnim ) || pm->ps->torsoAnimTimer < 300 ) + {//don't interrupt a force power anim + if ( pm->ps->torsoAnim != BOTH_LOSE_SABER + || !pm->ps->torsoAnimTimer ) + { + PM_SetAnim( pm, SETANIM_TORSO,BOTH_SABERPULL,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + else if ( pm->ps->weaponTime > 0 ) + { // Last attack is not yet complete. + pm->ps->weaponstate = WEAPON_FIRING; + return; + } + else + { + int both = qfalse; + if ( pm->ps->torsoAnim == BOTH_FORCELONGLEAP_ATTACK + || pm->ps->torsoAnim == BOTH_FORCELONGLEAP_LAND ) + {//can't attack in these anims + return; + } + else if ( pm->ps->torsoAnim == BOTH_FORCELONGLEAP_START ) + {//only 1 attack you can do from this anim + if ( pm->ps->torsoAnimTimer >= 200 ) + {//hit it early enough to do the attack + PM_SetSaberMove( LS_LEAP_ATTACK ); + } + return; + } + if ( curmove >= LS_PARRY_UP && curmove <= LS_REFLECT_LL ) + {//from a parry or reflection, can go directly into an attack + if ( pm->ps->clientNum >= MAX_CLIENTS && !PM_ControlledByPlayer() ) + {//NPCs + newmove = PM_NPCSaberAttackFromQuad( saberMoveData[curmove].endQuad ); + } + else + { + newmove = PM_SaberAttackForMovement( pm->cmd.forwardmove, pm->cmd.rightmove, curmove ); + } + } + + if ( newmove != LS_NONE ) + {//have a valid, final LS_ move picked, so skip findingt he transition move and just get the anim + if (PM_HasAnimation( pm->gent, saberMoveData[newmove].animToUse)) + { + anim = saberMoveData[newmove].animToUse; + } + } + + //FIXME: diagonal dirs use the figure-eight attacks from ready pose? + if ( anim == -1 ) + { + //FIXME: take FP_SABER_OFFENSE into account here somehow? + if ( PM_SaberInTransition( curmove ) ) + {//in a transition, must play sequential attack + newmove = saberMoveData[curmove].chain_attack; + } + else if ( curmove >= LS_S_TL2BR && curmove <= LS_S_T2B ) + {//started a swing, must continue from here + newmove = LS_A_TL2BR + (curmove-LS_S_TL2BR); + } + else if ( PM_SaberInBrokenParry( curmove ) ) + {//broken parries must always return to ready + newmove = LS_READY; + } + else//if ( pm->cmd.buttons&BUTTON_ATTACK && !(pm->ps->pm_flags&PMF_ATTACK_HELD) )//only do this if just pressed attack button? + {//get attack move from movement command + /* + if ( PM_SaberKataDone() ) + {//we came from a bounce and cannot chain to another attack because our kata is done + newmove = saberMoveData[curmove].chain_idle; + } + else */ + if ( pm->ps->clientNum >= MAX_CLIENTS + && !PM_ControlledByPlayer() + && (Q_irand( 0, pm->ps->forcePowerLevel[FP_SABER_OFFENSE]-1 ) + || (pm->gent&&pm->gent->enemy&&pm->gent->enemy->client&&PM_InKnockDownOnGround(&pm->gent->enemy->client->ps))//enemy knocked down, use some logic + || ( pm->ps->saberAnimLevel == SS_FAST && pm->gent && pm->gent->NPC && pm->gent->NPC->rank >= RANK_LT_JG && Q_irand( 0, 1 ) ) ) )//minor change to make fast-attack users use the special attacks more + {//NPCs use more randomized attacks the more skilled they are + newmove = PM_NPCSaberAttackFromQuad( saberMoveData[curmove].endQuad ); + } + else + { + newmove = PM_SaberAttackForMovement( pm->cmd.forwardmove, pm->cmd.rightmove, curmove ); + if ( (PM_SaberInBounce( curmove )||PM_SaberInBrokenParry( curmove )) + && saberMoveData[newmove].startQuad == saberMoveData[curmove].endQuad ) + {//this attack would be a repeat of the last (which was blocked), so don't actually use it, use the default chain attack for this bounce + newmove = saberMoveData[curmove].chain_attack; + } + } + if ( PM_SaberKataDone( curmove, newmove ) ) + {//cannot chain this time + newmove = saberMoveData[curmove].chain_idle; + } + } + /* + if ( newmove == LS_NONE ) + {//FIXME: should we allow this? Are there some anims that you should never be able to chain into an attack? + //only curmove that might get in here is LS_NONE, LS_DRAW, LS_PUTAWAY and the LS_R_ returns... all of which are in Q_R + newmove = PM_AttackMoveForQuad( saberMoveData[curmove].endQuad ); + } + */ + if ( newmove != LS_NONE ) + { + if ( !PM_InCartwheel( pm->ps->legsAnim ) ) + {//don't do transitions when cartwheeling - could make you spin! + //Now get the proper transition move + newmove = PM_SaberAnimTransitionMove( (saberMoveName_t)curmove, (saberMoveName_t)newmove ); + if ( PM_HasAnimation( pm->gent, saberMoveData[newmove].animToUse ) ) + { + anim = saberMoveData[newmove].animToUse; + } + } + } + } + + if (anim == -1) + {//not side-stepping, pick neutral anim + if ( !G_TryingSpecial(pm->gent,&pm->cmd)/*(pm->cmd.buttons&BUTTON_FORCE_FOCUS)*/ ) + {//but only if not trying one of the special attacks! + if ( PM_InCartwheel( pm->ps->legsAnim ) + && pm->ps->legsAnimTimer > 100 ) + {//if in the middle of a cartwheel, the chain attack is just a normal attack + //NOTE: this should match the switch in PM_InCartwheel! + switch( pm->ps->legsAnim ) + { + case BOTH_ARIAL_LEFT://swing from l to r + case BOTH_CARTWHEEL_LEFT: + newmove = LS_A_L2R; + break; + case BOTH_ARIAL_RIGHT://swing from r to l + case BOTH_CARTWHEEL_RIGHT: + newmove = LS_A_R2L; + break; + case BOTH_ARIAL_F1://random l/r attack + if ( Q_irand( 0, 1 ) ) + { + newmove = LS_A_L2R; + } + else + { + newmove = LS_A_R2L; + } + break; + } + } + else + { + // Add randomness for prototype? + newmove = saberMoveData[curmove].chain_attack; + } + if ( newmove != LS_NONE ) + { + if (PM_HasAnimation( pm->gent, saberMoveData[newmove].animToUse)) + { + anim= saberMoveData[newmove].animToUse; + } + } + } + + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove && pm->cmd.upmove >= 0 && pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//not moving at all, so set the anim on entire body + both = qtrue; + } + + } + + if ( anim == -1) + { + switch ( pm->ps->legsAnim ) + { + case BOTH_WALK1: + case BOTH_WALK2: + case BOTH_WALK_STAFF: + case BOTH_WALK_DUAL: + case BOTH_WALKBACK1: + case BOTH_WALKBACK2: + case BOTH_WALKBACK_STAFF: + case BOTH_WALKBACK_DUAL: + case BOTH_RUN1: + case BOTH_RUN2: + case BOTH_RUN_STAFF: + case BOTH_RUN_DUAL: + case BOTH_RUNBACK1: + case BOTH_RUNBACK2: + case BOTH_RUNBACK_STAFF: + anim = pm->ps->legsAnim; + break; + default: + anim = PM_ReadyPoseForSaberAnimLevel(); + break; + } + newmove = LS_READY; + } + + if ( !pm->ps->SaberActive() ) + {//turn on the saber if it's not on + pm->ps->SaberActivate(); + } + + PM_SetSaberMove( (saberMoveName_t)newmove ); + + if ( both && pm->ps->legsAnim != pm->ps->torsoAnim ) + { + PM_SetAnim( pm,SETANIM_LEGS,pm->ps->torsoAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( pm->gent && pm->gent->client ) + { +// pm->gent->client->saberTrail.inAction = qtrue; +// pm->gent->client->saberTrail.duration = 75; // saber trail lasts for 75ms...feel free to change this if you want it longer or shorter + } + if ( !PM_InCartwheel( pm->ps->torsoAnim ) ) + {//can still attack during a cartwheel/arial + //don't fire again until anim is done + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + /* + //FIXME: this may be making it so sometimes you can't swing again right away... + if ( newmove == LS_READY ) + { + pm->ps->weaponTime = 500; + } + */ + } + } + + // ********************************************************* + // WEAPON_FIRING + // ********************************************************* + + pm->ps->weaponstate = WEAPON_FIRING; + + /* + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + amount = weaponData[pm->ps->weapon].altEnergyPerShot; + else*/ + amount = weaponData[pm->ps->weapon].energyPerShot; + + if ( pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0 ) + {//FIXME: this is going to fire off one frame before you expect, actually + // Clear these out since we're not actually firing yet + pm->ps->eFlags &= ~EF_FIRING; + pm->ps->eFlags &= ~EF_ALT_FIRING; + return; + } + + addTime = pm->ps->weaponTime; + /*if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) { + PM_AddEvent( EV_ALT_FIRE ); + if ( !addTime ) + { + addTime = weaponData[pm->ps->weapon].altFireTime; + if ( g_timescale != NULL ) + { + if ( g_timescale->value < 1.0f ) + { + if ( !MatrixMode ) + {//Special test for Matrix Mode (tm) + if ( pm->ps->clientNum == 0 && !player_locked && (pm->ps->forcePowersActive&(1<ps->forcePowersActive&(1<value; + } + else if ( g_entities[pm->ps->clientNum].client && (pm->ps->forcePowersActive&(1<ps->forcePowersActive&(1<value; + } + } + } + } + } + } + else */{ + PM_AddEvent( EV_FIRE_WEAPON ); + if ( !addTime ) + { + addTime = weaponData[pm->ps->weapon].fireTime; + if ( g_timescale != NULL ) + { + if ( g_timescale->value < 1.0f ) + { + if ( !MatrixMode ) + {//Special test for Matrix Mode (tm) + if ( pm->ps->clientNum == 0 && !player_locked && (pm->ps->forcePowersActive&(1<ps->forcePowersActive&(1<value; + } + else if ( g_entities[pm->ps->clientNum].client + && (pm->ps->forcePowersActive&(1<ps->forcePowersActive&(1<value; + } + } + } + } + } + } + + if (pm->ps->forcePowersActive & (1 << FP_RAGE)) + { + addTime *= 0.75; + } + else if (pm->ps->forceRageRecoveryTime > pm->cmd.serverTime) + { + addTime *= 1.5; + } + + //If the phaser has been fired, delay the next recharge time + if ( !PM_ControlledByPlayer() ) + { + if( pm->gent && pm->gent->NPC != NULL ) + {//NPCs have their own refire logic + //FIXME: this really should be universal... + return; + } + } + + if ( !PM_InCartwheel( pm->ps->torsoAnim ) ) + {//can still attack during a cartwheel/arial + pm->ps->weaponTime = addTime; + } +} + +//--------------------------------------- +static bool PM_DoChargedWeapons( void ) +//--------------------------------------- +{ + qboolean charging = qfalse, + altFire = qfalse; + + //FIXME: make jedi aware they're being aimed at with a charged-up weapon (strafe and be evasive?) + // If you want your weapon to be a charging weapon, just set this bit up + switch( pm->ps->weapon ) + { + //------------------ + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: + + // alt-fire charges the weapon + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + charging = qtrue; + altFire = qtrue; + } + break; + + //------------------ + case WP_DISRUPTOR: + + // alt-fire charges the weapon...but due to zooming being controlled by the alt-button, the main button actually charges...but only when zoomed. + // lovely, eh? + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + { + if ( cg.zoomMode == 2 ) + { + if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + charging = qtrue; + altFire = qtrue; // believe it or not, it really is an alt-fire in this case! +#ifndef _XBOX + } +#else + cgi_FF_StartFX( fffx_StartConst ); + } + else + { + cgi_FF_StartFX( fffx_StopConst ); + } +#endif + + } + } + else if ( pm->gent && pm->gent->NPC ) + { + if ( (pm->gent->NPC->scriptFlags&SCF_ALT_FIRE) ) + { + if ( pm->gent->fly_sound_debounce_time > level.time ) + { + charging = qtrue; + altFire = qtrue; + } + } + } + break; + + //------------------ + case WP_BOWCASTER: + + // main-fire charges the weapon + if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + charging = qtrue; + } + break; + + //------------------ + case WP_DEMP2: + + // alt-fire charges the weapon + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + charging = qtrue; + altFire = qtrue; + } + break; + + //------------------ + case WP_ROCKET_LAUNCHER: + + // Not really a charge weapon, but we still want to delay fire until the button comes up so that we can + // implement our alt-fire locking stuff + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + charging = qtrue; + altFire = qtrue; + } + break; + + //------------------ + case WP_THERMAL: + // FIXME: Really should have a wind-up anim for player + // as he holds down the fire button to throw, then play + // the actual throw when he lets go... + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + altFire = qtrue; // override default of not being an alt-fire + charging = qtrue; + } + else if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + charging = qtrue; + } + break; + + } // end switch + + // set up the appropriate weapon state based on the button that's down. + // Note that we ALWAYS return if charging is set ( meaning the buttons are still down ) + if ( charging ) + { + + + if ( altFire ) + { + if ( pm->ps->weaponstate != WEAPON_CHARGING_ALT && pm->ps->weaponstate != WEAPON_DROPPING ) + { + if ( pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] <= 0) + { + PM_AddEvent( EV_NOAMMO ); + pm->ps->weaponTime += 500; + return true; + } + + // charge isn't started, so do it now + pm->ps->weaponstate = WEAPON_CHARGING_ALT; + pm->ps->weaponChargeTime = level.time; + + if ( cg_weapons[pm->ps->weapon].altChargeSound ) + { + G_SoundOnEnt( pm->gent, CHAN_WEAPON, weaponData[pm->ps->weapon].altChargeSnd ); + } +#ifdef _IMMERSION + if ( cg_weapons[pm->ps->weapon].altChargeForce ) + { + G_Force( pm->gent, G_ForceIndex( weaponData[pm->ps->weapon].altChargeFrc, FF_CHANNEL_WEAPON ) ); + } +#endif // _IMMERSION + } + } + else + { + + if ( pm->ps->weaponstate != WEAPON_CHARGING && pm->ps->weaponstate != WEAPON_DROPPING ) + { + if ( pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] <= 0) + { + PM_AddEvent( EV_NOAMMO ); + pm->ps->weaponTime += 500; + return true; + } + + // charge isn't started, so do it now + pm->ps->weaponstate = WEAPON_CHARGING; + pm->ps->weaponChargeTime = level.time; + +#ifdef _IMMERSION + if ( pm->gent && !pm->gent->NPC ) // HACK: !NPC mostly for bowcaster and weequay + { + if ( cg_weapons[pm->ps->weapon].chargeSound ) + { + G_SoundOnEnt( pm->gent, CHAN_WEAPON, weaponData[pm->ps->weapon].chargeSnd ); + } + + if ( cg_weapons[pm->ps->weapon].chargeForce ) + { + G_Force( pm->gent, G_ForceIndex( weaponData[pm->ps->weapon].chargeFrc, FF_CHANNEL_WEAPON ) ); + } + } +#else + if ( cg_weapons[pm->ps->weapon].chargeSound && pm->gent && !pm->gent->NPC ) // HACK: !NPC mostly for bowcaster and weequay + { + G_SoundOnEnt( pm->gent, CHAN_WEAPON, weaponData[pm->ps->weapon].chargeSnd ); + } +#endif // _IMMERSION + } + } + + return true; // short-circuit rest of weapon code + } + + // Only charging weapons should be able to set these states...so.... + // let's see which fire mode we need to set up now that the buttons are up + if ( pm->ps->weaponstate == WEAPON_CHARGING ) + { + // weapon has a charge, so let us do an attack + // dumb, but since we shoot a charged weapon on button-up, we need to repress this button for now + pm->cmd.buttons |= BUTTON_ATTACK; + pm->ps->eFlags |= EF_FIRING; +#ifdef _IMMERSION + // HACKHACKHACK - charging sound effect stops when button released, + // but I can't find the place where this stop occurs... it's here for now... + G_ForceStop( pm->gent, G_ForceIndex( weaponData[pm->ps->weapon].chargeFrc, FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + } + else if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT ) + { + // weapon has a charge, so let us do an alt-attack + // dumb, but since we shoot a charged weapon on button-up, we need to repress this button for now + pm->cmd.buttons |= BUTTON_ALT_ATTACK; + pm->ps->eFlags |= (EF_FIRING|EF_ALT_FIRING); +#ifdef _IMMERSION + // HACKHACKHACK - charging sound effect stops when button released, + // but I can't find the place where this stop occurs... it's here for now... + G_ForceStop( pm->gent, G_ForceIndex( weaponData[pm->ps->weapon].altChargeFrc, FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + } + + return false; // continue with the rest of the weapon code +} + + +#define BOWCASTER_CHARGE_UNIT 200.0f // bowcaster charging gives us one more unit every 200ms--if you change this, you'll have to do the same in g_weapon +#define BRYAR_CHARGE_UNIT 200.0f // bryar charging gives us one more unit every 200ms--if you change this, you'll have to do the same in g_weapon +#define DEMP2_CHARGE_UNIT 500.0f // ditto +#define DISRUPTOR_CHARGE_UNIT 150.0f // ditto + +// Specific weapons can opt to modify the ammo usage based on charges, otherwise if no special case code +// is handled below, regular ammo usage will happen +//--------------------------------------- +static int PM_DoChargingAmmoUsage( int *amount ) +//--------------------------------------- +{ + int count = 0; + + if ( pm->ps->weapon == WP_BOWCASTER && !( pm->cmd.buttons & BUTTON_ALT_ATTACK )) + { + // this code is duplicated ( I know, I know ) in G_weapon.cpp for the bowcaster alt-fire + count = ( level.time - pm->ps->weaponChargeTime ) / BOWCASTER_CHARGE_UNIT; + + if ( count < 1 ) + { + count = 1; + } + else if ( count > 5 ) + { + count = 5; + } + + if ( !(count & 1 )) + { + // if we aren't odd, knock us down a level + count--; + } + + // Only bother with these checks if we don't have infinite ammo + if ( pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 ) + { + int dif = pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] - *amount * count; + + // If we have enough ammo to do the full charged shot, we are ok + if ( dif < 0 ) + { + // we are not ok, so hack our chargetime and ammo usage, note that DIF is going to be negative + count += floor(dif / (float)*amount); + + if ( count < 1 ) + { + count = 1; + } + + // now get a real chargeTime so the duplicated code in g_weapon doesn't get freaked + pm->ps->weaponChargeTime = level.time - ( count * BOWCASTER_CHARGE_UNIT ); + } + } + + // now that count is cool, get the real ammo usage + *amount *= count; + } + else if( ( pm->ps->weapon == WP_BRYAR_PISTOL && pm->cmd.buttons & BUTTON_ALT_ATTACK ) + || ( pm->ps->weapon == WP_BLASTER_PISTOL && pm->cmd.buttons & BUTTON_ALT_ATTACK ) ) + { + // this code is duplicated ( I know, I know ) in G_weapon.cpp for the bryar alt-fire + count = ( level.time - pm->ps->weaponChargeTime ) / BRYAR_CHARGE_UNIT; + + if ( count < 1 ) + { + count = 1; + } + else if ( count > 5 ) + { + count = 5; + } + + // Only bother with these checks if we don't have infinite ammo + if ( pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 ) + { + int dif = pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] - *amount * count; + + // If we have enough ammo to do the full charged shot, we are ok + if ( dif < 0 ) + { + // we are not ok, so hack our chargetime and ammo usage, note that DIF is going to be negative + count += floor(dif / (float)*amount); + + if ( count < 1 ) + { + count = 1; + } + + // now get a real chargeTime so the duplicated code in g_weapon doesn't get freaked + pm->ps->weaponChargeTime = level.time - ( count * BRYAR_CHARGE_UNIT ); + } + } + + // now that count is cool, get the real ammo usage + *amount *= count; + } + else if ( pm->ps->weapon == WP_DEMP2 && pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + // this code is duplicated ( I know, I know ) in G_weapon.cpp for the demp2 alt-fire + count = ( level.time - pm->ps->weaponChargeTime ) / DEMP2_CHARGE_UNIT; + + if ( count < 1 ) + { + count = 1; + } + else if ( count > 3 ) + { + count = 3; + } + + // Only bother with these checks if we don't have infinite ammo + if ( pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 ) + { + int dif = pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] - *amount * count; + + // If we have enough ammo to do the full charged shot, we are ok + if ( dif < 0 ) + { + // we are not ok, so hack our chargetime and ammo usage, note that DIF is going to be negative + count += floor(dif / (float)*amount); + + if ( count < 1 ) + { + count = 1; + } + + // now get a real chargeTime so the duplicated code in g_weapon doesn't get freaked + pm->ps->weaponChargeTime = level.time - ( count * DEMP2_CHARGE_UNIT ); + } + } + + // now that count is cool, get the real ammo usage + *amount *= count; + + // this is an after-thought. should probably re-write the function to do this naturally. + if ( *amount > pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] ) + { + *amount = pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex]; + } + } + else if ( pm->ps->weapon == WP_DISRUPTOR && pm->cmd.buttons & BUTTON_ALT_ATTACK ) // BUTTON_ATTACK will have been mapped to BUTTON_ALT_ATTACK if we are zoomed + { + // this code is duplicated ( I know, I know ) in G_weapon.cpp for the disruptor alt-fire + count = ( level.time - pm->ps->weaponChargeTime ) / DISRUPTOR_CHARGE_UNIT; + + if ( count < 1 ) + { + count = 1; + } + else if ( count > 10 ) + { + count = 10; + } + + // Only bother with these checks if we don't have infinite ammo + if ( pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 ) + { + int dif = pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] - *amount * count; + + // If we have enough ammo to do the full charged shot, we are ok + if ( dif < 0 ) + { + // we are not ok, so hack our chargetime and ammo usage, note that DIF is going to be negative + count += floor(dif / (float)*amount); + + if ( count < 1 ) + { + count = 1; + } + + // now get a real chargeTime so the duplicated code in g_weapon doesn't get freaked + pm->ps->weaponChargeTime = level.time - ( count * DISRUPTOR_CHARGE_UNIT ); + } + } + + // now that count is cool, get the real ammo usage + *amount *= count; + + // this is an after-thought. should probably re-write the function to do this naturally. + if ( *amount > pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] ) + { + *amount = pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex]; + } + } + + return count; +} + +qboolean PM_DroidMelee( int npc_class ) +{ + if ( npc_class == CLASS_PROBE + || npc_class == CLASS_SEEKER + || npc_class == CLASS_INTERROGATOR + || npc_class == CLASS_SENTRY + || npc_class == CLASS_REMOTE ) + { + return qtrue; + } + return qfalse; +} + +void PM_WeaponWampa( void ) +{ + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime <= 0 ) + { + pm->ps->weaponTime = 0; + } + } + + // check for weapon change + // can't change if weapon is firing, but can change again if lowering or raising + if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { + if ( pm->ps->weapon != pm->cmd.weapon ) { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + + if ( pm->ps->weaponTime > 0 ) + { + return; + } + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + if ( pm->ps->weapon == WP_SABER + && (pm->cmd.buttons&BUTTON_ATTACK) + && pm->ps->torsoAnim == BOTH_HANG_IDLE ) + { + pm->ps->SaberActivate(); + pm->ps->SaberActivateTrail( 150 ); + PM_SetAnim( pm, SETANIM_BOTH, BOTH_HANG_ATTACK, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + pm->ps->weaponstate = WEAPON_FIRING; + pm->ps->saberBlocked = BLOCKED_NONE; + pm->ps->saberMove = LS_READY; + pm->ps->saberMoveNext = LS_NONE; + } + else if ( pm->ps->torsoAnim == BOTH_HANG_IDLE ) + { + pm->ps->SaberDeactivateTrail( 0 ); + pm->ps->weaponstate = WEAPON_READY; + pm->ps->saberMove = LS_READY; + pm->ps->saberMoveNext = LS_NONE; + } +} +/* +============== +PM_Weapon + +Generates weapon events and modifes the weapon counter +============== +*/ +static void PM_Weapon( void ) +{ + int addTime, amount, trueCount = 1; + qboolean delayed_fire = qfalse; + + if ( (pm->ps->eFlags&EF_HELD_BY_WAMPA) ) + { + PM_WeaponWampa(); + return; + } + if ( pm->ps->eFlags&EF_FORCE_DRAINED ) + {//being drained + return; + } + if ( (pm->ps->forcePowersActive&(1<ps->forceDrainEntityNum < ENTITYNUM_WORLD ) + {//draining + return; + } + if (pm->ps->weapon == WP_SABER && (cg.zoomMode==3||!cg.zoomMode||pm->ps->clientNum) ) // WP_LIGHTSABER + { // Separate logic for lightsaber, but not for player when zoomed + PM_WeaponLightsaber(); + if ( pm->gent && pm->gent->client && pm->ps->saber[0].Active() && pm->ps->saberInFlight ) + {//FIXME: put saberTrail in playerState + if ( pm->gent->client->ps.saberEntityState == SES_RETURNING ) + {//turn off the saber trail + pm->gent->client->ps.SaberDeactivateTrail( 75 ); + } + else + {//turn on the saber trail + pm->gent->client->ps.SaberActivateTrail( 150 ); + } + } + return; + } + + if ( PM_InKnockDown( pm->ps ) || PM_InRoll( pm->ps )) + {//in knockdown + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime <= 0 ) + { + pm->ps->weaponTime = 0; + } + } + return; + } + + if( pm->gent && pm->gent->client ) + { + if ( pm->gent->client->fireDelay > 0 ) + {//FIXME: this is going to fire off one frame before you expect, actually + pm->gent->client->fireDelay -= pml.msec; + if(pm->gent->client->fireDelay <= 0) + {//just finished delay timer + if ( pm->ps->clientNum && pm->ps->weapon == WP_ROCKET_LAUNCHER ) + { + G_SoundOnEnt( pm->gent, CHAN_WEAPON, "sound/weapons/rocket/lock.wav" ); + pm->cmd.buttons |= BUTTON_ALT_ATTACK; + } + pm->gent->client->fireDelay = 0; + delayed_fire = qtrue; + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + && pm->ps->weapon == WP_THERMAL + && pm->gent->alt_fire ) + { + pm->cmd.buttons |= BUTTON_ALT_ATTACK; + } + } + else + { + if ( pm->ps->clientNum && pm->ps->weapon == WP_ROCKET_LAUNCHER && Q_irand( 0, 1 ) ) + { + G_SoundOnEnt( pm->gent, CHAN_WEAPON, "sound/weapons/rocket/tick.wav" ); + } + } + } + } + + // don't allow attack until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) + { + if ( pm->gent && pm->gent->client ) + { + // borg no longer exist, use NPC_class to check for any npc's that don't drop their weapons (if there are any) + // Sigh..borg shouldn't drop their weapon attachments when they die. Also, never drop a lightsaber! + // if ( pm->gent->client->playerTeam != TEAM_BORG) + { + pm->ps->weapon = WP_NONE; + } + } + + if ( pm->gent ) + { + pm->gent->s.loopSound = 0; + } + return; + } + + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime <= 0 ) + { + pm->ps->weaponTime = 0; + } + } + + // check for weapon change + // can't change if weapon is firing, but can change again if lowering or raising + if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { + if ( pm->ps->weapon != pm->cmd.weapon ) { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + + if ( pm->ps->weaponTime > 0 ) + { + return; + } + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + if ( pm->ps->weapon == WP_NONE ) + { + return; + } + + if ( pm->ps->weaponstate == WEAPON_RAISING ) + { + //Just selected the weapon + pm->ps->weaponstate = WEAPON_IDLE; + + if(pm->gent && (pm->gent->s.numbergent))) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + else + { + switch(pm->ps->weapon) + { + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: + if ( pm->gent + && pm->gent->weaponModel[1] > 0 ) + {//dual pistols + //FIXME: should be a better way of detecting a dual-pistols user so it's not hardcoded to the saboteurcommando... + PM_SetAnim(pm,SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + else + {//single pistol + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE2,SETANIM_FLAG_NORMAL); + } + break; + default: + PM_SetAnim(pm,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); + break; + } + } + return; + } + + if ( pm->gent ) + {//ready to throw thermal again, add it + if ( pm->ps->weapon == WP_THERMAL + && pm->gent->weaponModel[0] == -1 ) + {//add the thermal model back in our hand + // remove anything if we have anything already + G_RemoveWeaponModels( pm->gent ); + if (weaponData[pm->ps->weapon].weaponMdl[0]) { //might be NONE, so check if it has a model + G_CreateG2AttachedWeaponModel( pm->gent, weaponData[pm->ps->weapon].weaponMdl, pm->gent->handRBolt, 0 ); + //make it sound like we took another one out from... uh.. somewhere... + if ( cg.time > 0 ) + {//this way we don't get that annoying change weapon sound every time a map starts + PM_AddEvent( EV_CHANGE_WEAPON ); + } + } + } + } + + if ( !delayed_fire ) + {//didn't just finish a fire delay + if ( PM_DoChargedWeapons()) + { + // In some cases the charged weapon code may want us to short circuit the rest of the firing code + return; + } + else + { + if ( !pm->gent->client->fireDelay//not already waiting to fire + && (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())//player + && pm->ps->weapon == WP_THERMAL//holding thermal + && pm->gent//gent + && pm->gent->client//client + && (pm->cmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) )//holding fire + {//delay the actual firing of the missile until the anim has played some + if ( PM_StandingAnim( pm->ps->legsAnim ) + || pm->ps->legsAnim == BOTH_THERMAL_READY ) + { + PM_SetAnim( pm, SETANIM_LEGS, BOTH_THERMAL_THROW, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + PM_SetAnim(pm,SETANIM_TORSO,BOTH_THERMAL_THROW,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + pm->gent->client->fireDelay = 300; + pm->ps->weaponstate = WEAPON_FIRING; + pm->gent->alt_fire = (qboolean)(pm->cmd.buttons&BUTTON_ALT_ATTACK); + return; + } + } + } + + if(!delayed_fire) + { + if ( pm->ps->weapon == WP_MELEE && (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + {//melee + if ( (pm->cmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) != (BUTTON_ATTACK|BUTTON_ALT_ATTACK) ) + {//not holding both buttons + if ( (pm->cmd.buttons&BUTTON_ATTACK)&&(pm->ps->pm_flags&PMF_ATTACK_HELD) ) + {//held button + //clear it + pm->cmd.buttons &= ~BUTTON_ATTACK; + } + if ( (pm->cmd.buttons&BUTTON_ALT_ATTACK)&&(pm->ps->pm_flags&PMF_ALT_ATTACK_HELD) ) + {//held button + //clear it + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + } + } + } + // check for fire + if ( !(pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) + { + pm->ps->weaponTime = 0; + + if ( pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0 ) + {//Still firing + pm->ps->weaponstate = WEAPON_FIRING; + } + else if ( pm->ps->weaponstate != WEAPON_READY ) + { + if ( !pm->gent || !pm->gent->NPC || pm->gent->attackDebounceTime < level.time ) + { + pm->ps->weaponstate = WEAPON_IDLE; + } + } + + if ( pm->ps->weapon == WP_MELEE + && (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) + && PM_KickMove( pm->ps->saberMove ) ) + {//melee, not attacking, clear move + pm->ps->saberMove = LS_NONE; + } + return; + } + if (pm->gent->s.m_iVehicleNum!=0) + { + // No Anims if on Veh + } + + // start the animation even if out of ammo + else if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_ROCKETTROOPER ) + { + if ( pm->gent->client->moveType == MT_FLYSWIM ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + } + } + else if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_ASSASSIN_DROID ) + { + // Crouched Attack + if (PM_CrouchAnim(pm->gent->client->ps.legsAnim)) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLDLESS); + } + + // Standing Attack + //----------------- + else + { + // if (PM_StandingAnim(pm->gent->client->ps.legsAnim)) + // { + // PM_SetAnim(pm,SETANIM_BOTH,BOTH_ATTACK3,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLDLESS); + // } + // else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK3,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLDLESS); + } + } + } + else + { + switch(pm->ps->weapon) + { + /* + case WP_SABER://1 - handed + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + break; + */ + case WP_BRYAR_PISTOL://1-handed + case WP_BLASTER_PISTOL://1-handed + if ( pm->gent && pm->gent->weaponModel[1] > 0 ) + {//dual pistols + PM_SetAnim(pm,SETANIM_TORSO,BOTH_GUNSIT1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + } + else + {//single pistol + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + } + break; + + case WP_MELEE: + + // since there's no RACE_BOTS, I listed all the droids that have might have melee attacks - dmv + if ( pm->gent && pm->gent->client ) + { + if ( PM_DroidMelee( pm->gent->client->NPC_class ) ) + { + if ( rand() & 1 ) + PM_SetAnim(pm,SETANIM_BOTH,BOTH_MELEE1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + else + PM_SetAnim(pm,SETANIM_BOTH,BOTH_MELEE2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + } + else + { + int anim = -1; + if ( (pm->ps->clientNum < MAX_CLIENTS ||PM_ControlledByPlayer()) + && g_debugMelee->integer ) + { + if ( (pm->cmd.buttons&BUTTON_ALT_ATTACK) ) + { + if ( (pm->cmd.buttons&BUTTON_ATTACK) ) + { + PM_TryGrab(); + } + else if ( !(pm->ps->pm_flags&PMF_ALT_ATTACK_HELD) ) + { + PM_CheckKick(); + } + } + else if ( !(pm->ps->pm_flags&PMF_ATTACK_HELD) ) + { + anim = PM_PickAnim( pm->gent, BOTH_MELEE1, BOTH_MELEE2 ); + } + } + else + { + anim = PM_PickAnim( pm->gent, BOTH_MELEE1, BOTH_MELEE2 ); + } + if ( anim != -1 ) + { + if ( VectorCompare( pm->ps->velocity, vec3_origin ) && pm->cmd.upmove >= 0 ) + { + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + } + } + } + } + break; + + case WP_TUSKEN_RIFLE: + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + {//shoot + //in alt-fire, sniper mode + PM_SetAnim( pm, SETANIM_TORSO, BOTH_ATTACK4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + {//melee + int anim = PM_PickAnim( pm->gent, BOTH_TUSKENATTACK1, BOTH_TUSKENATTACK3 ); // Rifle + if ( VectorCompare( pm->ps->velocity, vec3_origin ) && pm->cmd.upmove >= 0 ) + { + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + } + } + break; + + case WP_TUSKEN_STAFF: + + if ( pm->gent && pm->gent->client ) + { + int anim; + int flags = (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART); + if ( (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) ) + {//player + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + anim = BOTH_TUSKENATTACK3; + } + else + { + anim = BOTH_TUSKENATTACK2; + } + } + else + { + anim = BOTH_TUSKENATTACK1; + } + } + else + {// npc + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + anim = BOTH_TUSKENLUNGE1; + if (pm->ps->torsoAnimTimer>0) + { + flags &= ~SETANIM_FLAG_RESTART; + } + } + else + { + anim = PM_PickAnim( pm->gent, BOTH_TUSKENATTACK1, BOTH_TUSKENATTACK3 ); + } + } + if ( VectorCompare( pm->ps->velocity, vec3_origin ) && pm->cmd.upmove >= 0 ) + { + PM_SetAnim( pm, SETANIM_BOTH, anim, flags, 0); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, anim, flags, 0); + } + } + break; + + case WP_NOGHRI_STICK: + + if ( pm->gent && pm->gent->client ) + { + int anim; + if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + anim = BOTH_ATTACK3; + } + else + { + anim = PM_PickAnim( pm->gent, BOTH_TUSKENATTACK1, BOTH_TUSKENATTACK3 ); + } + if ( anim != BOTH_ATTACK3 && VectorCompare( pm->ps->velocity, vec3_origin ) && pm->cmd.upmove >= 0 ) + { + PM_SetAnim( pm, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + } + else + { + PM_SetAnim( pm, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + } + } + break; + + case WP_BLASTER: + PM_SetAnim( pm, SETANIM_TORSO, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART); + break; + + case WP_DISRUPTOR: + if ( ((pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer())&& pm->gent && pm->gent->NPC && (pm->gent->NPC->scriptFlags&SCF_ALT_FIRE)) || + ((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) && cg.zoomMode == 2 ) ) + {//NPC or player in alt-fire, sniper mode + PM_SetAnim( pm, SETANIM_TORSO, BOTH_ATTACK4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + {//in primary fire mode + PM_SetAnim( pm, SETANIM_TORSO, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART); + } + break; + + case WP_BOT_LASER: + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + break; + + case WP_THERMAL: + if ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) ) + { + if ( PM_StandingAnim( pm->ps->legsAnim ) ) + { + PM_SetAnim( pm, SETANIM_LEGS, BOTH_ATTACK10, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK10,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + } + else + { + if ( cg.renderingThirdPerson ) + { + if ( PM_StandingAnim( pm->ps->legsAnim ) + || pm->ps->legsAnim == BOTH_THERMAL_READY ) + { + PM_SetAnim( pm, SETANIM_LEGS, BOTH_THERMAL_THROW, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + PM_SetAnim(pm,SETANIM_TORSO,BOTH_THERMAL_THROW,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);//|SETANIM_FLAG_RESTART + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + } + } + break; + + case WP_EMPLACED_GUN: + // Guess we don't play an attack animation? Maybe we should have a custom one?? + break; + + case WP_NONE: + // no anim + break; + + case WP_REPEATER: + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_GALAKMECH ) + {// + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK3,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + } + } + else + { + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK3,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + } + break; + + case WP_TRIP_MINE: + case WP_DET_PACK: + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK11,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + break; + + default://2-handed heavy weapon + PM_SetAnim(pm,SETANIM_TORSO,BOTH_ATTACK3,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + break; + } + } + } + + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + amount = weaponData[pm->ps->weapon].altEnergyPerShot; + } + else + { + amount = weaponData[pm->ps->weapon].energyPerShot; + } + + if ( (pm->ps->weaponstate == WEAPON_CHARGING) || (pm->ps->weaponstate == WEAPON_CHARGING_ALT) ) + { + // charging weapons may want to do their own ammo logic. + trueCount = PM_DoChargingAmmoUsage( &amount ); + } + + pm->ps->weaponstate = WEAPON_FIRING; + + // take an ammo away if not infinite + if ( pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 ) + { + // enough energy to fire this weapon? + if ((pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] - amount) >= 0) + { + pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= amount; + } + else // Not enough energy + { + if ( !( pm->ps->eFlags & EF_LOCKED_TO_WEAPON )) + { + // Switch weapons + PM_AddEvent( EV_NOAMMO ); + pm->ps->weaponTime += 500; + } + return; + } + } + + if ( pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0 ) + {//FIXME: this is going to fire off one frame before you expect, actually + // Clear these out since we're not actually firing yet + pm->ps->eFlags &= ~EF_FIRING; + pm->ps->eFlags &= ~EF_ALT_FIRING; + return; + } + + if ( pm->ps->weapon == WP_EMPLACED_GUN ) + { + if ( pm->gent + && pm->gent->owner + && pm->gent->owner->e_UseFunc == useF_eweb_use ) + {//eweb always shoots alt-fire, for proper effects and sounds + PM_AddEvent( EV_ALT_FIRE ); + addTime = weaponData[pm->ps->weapon].altFireTime; + } + else + {//emplaced gun always shoots normal fire + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = weaponData[pm->ps->weapon].fireTime; + } + } + else if ( (pm->ps->weapon == WP_MELEE && (pm->ps->clientNum>=MAX_CLIENTS||!g_debugMelee->integer) ) + || pm->ps->weapon == WP_TUSKEN_STAFF + || (pm->ps->weapon == WP_TUSKEN_RIFLE&&!(pm->cmd.buttons&BUTTON_ALT_ATTACK)) ) + { + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = pm->ps->torsoAnimTimer; + } + else if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + PM_AddEvent( EV_ALT_FIRE ); + addTime = weaponData[pm->ps->weapon].altFireTime; + if ( pm->ps->weapon == WP_THERMAL ) + {//threw our thermal + if ( pm->gent ) + {// remove the thermal model if we had it. + G_RemoveWeaponModels( pm->gent ); + if ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) ) + {//NPCs need to know when to put the thermal back in their hand + pm->ps->weaponTime = pm->ps->torsoAnimTimer-500; + } + } + } + } + else + { + if ( pm->ps->clientNum //NPC + && !PM_ControlledByPlayer() //not under player control + && pm->ps->weapon == WP_THERMAL //using thermals + && pm->ps->torsoAnim != BOTH_ATTACK10 )//not in the throw anim + {//oops, got knocked out of the anim, don't throw the thermal + return; + } + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = weaponData[pm->ps->weapon].fireTime; + + switch( pm->ps->weapon) + { + case WP_REPEATER: + // repeater is supposed to do smoke after sustained bursts + pm->ps->weaponShotCount++; + break; + case WP_BOWCASTER: + addTime *= (( trueCount < 3 ) ? 0.35f : 1.0f );// if you only did a small charge shot with the bowcaster, use less time between shots + break; + case WP_THERMAL: + if ( pm->gent ) + {// remove the thermal model if we had it. + G_RemoveWeaponModels( pm->gent ); + if ( (pm->ps->clientNum >= MAX_CLIENTS&&!PM_ControlledByPlayer()) ) + {//NPCs need to know when to put the thermal back in their hand + pm->ps->weaponTime = pm->ps->torsoAnimTimer-500; + } + } + break; + } + } + + + if(pm->gent && pm->gent->NPC != NULL ) + {//NPCs have their own refire logic + return; + } + + if ( g_timescale != NULL ) + { + if ( g_timescale->value < 1.0f ) + { + if ( !MatrixMode ) + {//Special test for Matrix Mode (tm) + if ( pm->ps->clientNum == 0 && !player_locked && (pm->ps->forcePowersActive&(1<ps->forcePowersActive&(1<value; + } + else if ( g_entities[pm->ps->clientNum].client + && (pm->ps->forcePowersActive&(1<ps->forcePowersActive&(1<value; + } + } + } + } + + pm->ps->weaponTime += addTime; + pm->ps->lastShotTime = level.time;//so we know when the last time we fired our gun is + + // HACK!!!!! + if ( pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] <= 0 ) + { + if ( pm->ps->weapon == WP_THERMAL || pm->ps->weapon == WP_TRIP_MINE ) + { + // because these weapons have the ammo attached to the hand, we should switch weapons when the last one is thrown, otherwise it will look silly + // NOTE: could also switch to an empty had version, but was told we aren't getting any new models at this point + CG_OutOfAmmoChange(); + PM_SetAnim(pm,SETANIM_TORSO,TORSO_DROPWEAP1 + 2,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); // hack weapon down! + pm->ps->weaponTime = 50; + } + } +} + +/* +============== +PM_VehicleWeapon + +Generates weapon events and modifes the weapon counter +============== +*/ +static void PM_VehicleWeapon( void ) +{ + int addTime = 0, amount; + qboolean delayed_fire = qfalse; + + if ( pm->ps->weapon == WP_NONE ) + { + return; + } + + if(pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0) + {//FIXME: this is going to fire off one frame before you expect, actually + pm->gent->client->fireDelay -= pml.msec; + if(pm->gent->client->fireDelay <= 0) + {//just finished delay timer + if ( pm->ps->clientNum && pm->ps->weapon == WP_ROCKET_LAUNCHER ) + { + G_SoundOnEnt( pm->gent, CHAN_WEAPON, "sound/weapons/rocket/lock.wav" ); + pm->cmd.buttons |= BUTTON_ALT_ATTACK; + } + pm->gent->client->fireDelay = 0; + delayed_fire = qtrue; + } + else + { + if ( pm->ps->clientNum && pm->ps->weapon == WP_ROCKET_LAUNCHER && Q_irand( 0, 1 ) ) + { + G_SoundOnEnt( pm->gent, CHAN_WEAPON, "sound/weapons/rocket/tick.wav" ); + } + } + } + + // don't allow attack until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) + { + if ( pm->gent && pm->gent->client ) + { + // borg no longer exist, use NPC_class to check for any npc's that don't drop their weapons (if there are any) + // Sigh..borg shouldn't drop their weapon attachments when they die. Also, never drop a lightsaber! + // if ( pm->gent->client->playerTeam != TEAM_BORG) + { + // pm->ps->weapon = WP_NONE; + } + } + + if ( pm->gent ) + { + pm->gent->s.loopSound = 0; + } + return; + } + + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime <= 0 ) + { + pm->ps->weaponTime = 0; + } + } + + if ( pm->ps->weaponTime > 0 ) + { + return; + } + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + if ( PM_DoChargedWeapons()) + { + // In some cases the charged weapon code may want us to short circuit the rest of the firing code + return; + } + + if(!delayed_fire) + { + // check for fire + if ( !(pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) + { + pm->ps->weaponTime = 0; + + if ( pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0 ) + {//Still firing + pm->ps->weaponstate = WEAPON_FIRING; + } + else if ( pm->ps->weaponstate != WEAPON_READY ) + { + if ( !pm->gent || !pm->gent->NPC || pm->gent->attackDebounceTime < level.time ) + { + pm->ps->weaponstate = WEAPON_IDLE; + } + } + + return; + } + } + + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + amount = weaponData[pm->ps->weapon].altEnergyPerShot; + } + else + { + amount = weaponData[pm->ps->weapon].energyPerShot; + } + + pm->ps->weaponstate = WEAPON_FIRING; + + if ( pm->gent && pm->gent->client && pm->gent->client->fireDelay > 0 ) + {//FIXME: this is going to fire off one frame before you expect, actually + // Clear these out since we're not actually firing yet + pm->ps->eFlags &= ~EF_FIRING; + pm->ps->eFlags &= ~EF_ALT_FIRING; + return; + } + + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + PM_AddEvent( EV_ALT_FIRE ); + //addTime = weaponData[pm->ps->weapon].altFireTime; + } + else + { + PM_AddEvent( EV_FIRE_WEAPON ); + // TODO: Use the real weapon fire time from the vehicle cfg file. + //addTime = weaponData[pm->ps->weapon].fireTime; + } + +/* if(pm->gent && pm->gent->NPC != NULL ) + {//NPCs have their own refire logic + return; + }*/ + + if ( g_timescale != NULL ) + { + if ( g_timescale->value < 1.0f ) + { + if ( !MatrixMode ) + {//Special test for Matrix Mode (tm) + if ( pm->ps->clientNum == 0 && !player_locked && (pm->ps->forcePowersActive&(1<ps->forcePowersActive&(1<value; + } + else if ( g_entities[pm->ps->clientNum].client + && (pm->ps->forcePowersActive&(1<ps->forcePowersActive&(1<value; + } + } + } + } + + pm->ps->weaponTime += addTime; + pm->ps->lastShotTime = level.time;//so we know when the last time we fired our gun is +} + + +extern void ForceHeal( gentity_t *self ); +extern void ForceTelepathy( gentity_t *self ); +extern void ForceRage( gentity_t *self ); +extern void ForceProtect( gentity_t *self ); +extern void ForceAbsorb( gentity_t *self ); +extern void ForceSeeing( gentity_t *self ); +void PM_CheckForceUseButton( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( !ent ) + { + return; + } + if ( ucmd->buttons & BUTTON_USE_FORCE ) + { + if (!(ent->client->ps.pm_flags & PMF_USEFORCE_HELD)) + { + //impulse one shot + switch ( showPowers[cg.forcepowerSelect] ) + { + case FP_HEAL: + ForceHeal( ent ); + break; + case FP_SPEED: + ForceSpeed( ent ); + break; + case FP_PUSH: + ForceThrow( ent, qfalse ); + break; + case FP_PULL: + ForceThrow( ent, qtrue ); + break; + case FP_TELEPATHY: + ForceTelepathy( ent ); + break; + // Added 01/20/03 by AReis. + // New Jedi Academy powers. + case FP_RAGE: //duration - speed, invincibility and extra damage for short period, drains your health and leaves you weak and slow afterwards. + ForceRage( ent ); + break; + case FP_PROTECT: //duration - protect against physical/energy (level 1 stops blaster/energy bolts, level 2 stops projectiles, level 3 protects against explosions) + ForceProtect( ent ); + break; + case FP_ABSORB: //duration - protect against dark force powers (grip, lightning, drain - maybe push/pull, too?) + ForceAbsorb( ent ); + break; + case FP_SEE: //duration - detect/see hidden enemies + ForceSeeing( ent ); + break; + } + } + //these stay are okay to call every frame button is down + switch ( showPowers[cg.forcepowerSelect] ) + { + case FP_LEVITATION: + ucmd->upmove = 127; + break; + case FP_GRIP: + ucmd->buttons |= BUTTON_FORCEGRIP; + break; + case FP_LIGHTNING: + ucmd->buttons |= BUTTON_FORCE_LIGHTNING; + break; + case FP_DRAIN: + // FIXME! Failing at WP_ForcePowerUsable(). -AReis + ucmd->buttons |= BUTTON_FORCE_DRAIN; + break; +// default: +// Com_Printf( "Use Force: Unhandled force: %d\n", showPowers[cg.forcepowerSelect]); +// break; + } + ent->client->ps.pm_flags |= PMF_USEFORCE_HELD; + } + else//not pressing USE_FORCE + { + ent->client->ps.pm_flags &= ~PMF_USEFORCE_HELD; + } +} + +/* +================ +PM_ForcePower +================ +sends event to client for client side fx, not used +*/ + +/* +static void PM_ForcePower(void) +{ + // check for item using + if ( pm->cmd.buttons & BUTTON_USE_FORCE ) + { + if ( ! ( pm->ps->pm_flags & PMF_USE_FORCE ) ) + { + pm->ps->pm_flags |= PMF_USE_FORCE; + PM_AddEvent( EV_USE_FORCE); + return; + } + } + else + { + pm->ps->pm_flags &= ~PMF_USE_FORCE; + } +} +*/ + +/* +================ +PM_DropTimers +================ +*/ +static void PM_DropTimers( void ) +{ + // drop misc timing counter + if ( pm->ps->pm_time ) + { + if ( pml.msec >= pm->ps->pm_time ) + { + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } + else + { + pm->ps->pm_time -= pml.msec; + } + } + + // drop legs animation counter + if ( pm->ps->legsAnimTimer > 0 ) + { + int newTime = pm->ps->legsAnimTimer - pml.msec; + + if ( newTime < 0 ) + { + newTime = 0; + } + + PM_SetLegsAnimTimer( pm->gent, &pm->ps->legsAnimTimer, newTime ); + } + + // drop torso animation counter + if ( pm->ps->torsoAnimTimer > 0 ) + { + int newTime = pm->ps->torsoAnimTimer - pml.msec; + + if ( newTime < 0 ) + { + newTime = 0; + } + + PM_SetTorsoAnimTimer( pm->gent, &pm->ps->torsoAnimTimer, newTime ); + } +} + +void PM_SetSpecialMoveValues (void ) +{ + Flying = 0; + if ( pm->gent ) + { + if ( pm->gent->client && pm->gent->client->moveType == MT_FLYSWIM ) + { + Flying = FLY_NORMAL; + } + else if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE ) + { + if ( pm->gent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + { + Flying = FLY_VEHICLE; + } + else if ( pm->gent->m_pVehicle->m_pVehicleInfo->hoverHeight > 0 ) + {//FIXME: or just check for hoverHeight? + Flying = FLY_HOVER; + } + } + } + + if ( g_timescale != NULL ) + { + if ( g_timescale->value < 1.0f ) + { + if ( !MatrixMode ) + { + if ( pm->ps->clientNum == 0 && !player_locked && (pm->ps->forcePowersActive&(1<ps->forcePowersActive&(1<value); + } + else if ( g_entities[pm->ps->clientNum].client + && (pm->ps->forcePowersActive&(1<ps->forcePowersActive&(1<value); + } + } + } + } +} + +extern float cg_zoomFov; //from cg_view.cpp + +//------------------------------------------- +void PM_AdjustAttackStates( pmove_t *pm ) +//------------------------------------------- +{ + int amount; + + // get ammo usage + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - weaponData[pm->ps->weapon].altEnergyPerShot; + } + else + { + amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - weaponData[pm->ps->weapon].energyPerShot; + } + + if ( pm->ps->weapon == WP_SABER && (!cg.zoomMode||pm->ps->clientNum) ) + {//don't let the alt-attack be interpreted as an actual attack command + if ( pm->ps->saberInFlight ) + { + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + //FIXME: what about alt-attack modifier button? + if ( (!pm->ps->dualSabers || !pm->ps->saber[1].Active()) ) + {//saber not in hand, can't swing it + pm->cmd.buttons &= ~BUTTON_ATTACK; + } + } + //saber staff alt-attack does a special attack anim, non-throwable sabers do kicks + if ( pm->ps->saberAnimLevel != SS_STAFF + && pm->ps->saber[0].throwable ) + {//using a throwable saber, so remove the saber throw button + if ( !g_saberNewControlScheme->integer + && PM_CanDoKata() ) + {//old control scheme - alt-attack + attack does kata + } + else + {//new control scheme - alt-attack doesn't have anything to do with katas, safe to clear it here + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + } + } + } + + // disruptor alt-fire should toggle the zoom mode, but only bother doing this for the player? + if ( pm->ps->weapon == WP_DISRUPTOR && pm->gent && (pm->gent->s.numbergent)) && pm->ps->weaponstate != WEAPON_DROPPING ) + { + // we are not alt-firing yet, but the alt-attack button was just pressed and + // we either are ducking ( in which case we don't care if they are moving )...or they are not ducking...and also not moving right/forward. + if ( !(pm->ps->eFlags & EF_ALT_FIRING) && (pm->cmd.buttons & BUTTON_ALT_ATTACK) + && ( pm->cmd.upmove < 0 || ( !pm->cmd.forwardmove && !pm->cmd.rightmove ))) + { + // We just pressed the alt-fire key + if ( cg.zoomMode == 0 || cg.zoomMode == 3 ) + { + G_SoundOnEnt( pm->gent, CHAN_AUTO, "sound/weapons/disruptor/zoomstart.wav" ); +#ifdef _IMMERSION + G_Force( pm->gent, G_ForceIndex( "fffx/weapons/disruptor/zoomstart", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + // not already zooming, so do it now + cg.zoomMode = 2; + cg.zoomLocked = qfalse; + cg_zoomFov = 80.0f;//(cg.overrides.active&CG_OVERRIDE_FOV) ? cg.overrides.fov : cg_fov.value; + } + else if ( cg.zoomMode == 2 ) + { + G_SoundOnEnt( pm->gent, CHAN_AUTO, "sound/weapons/disruptor/zoomend.wav" ); +#ifdef _IMMERSION + G_Force( pm->gent, G_ForceIndex( "fffx/weapons/disruptor/zoomend", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + // already zooming, so must be wanting to turn it off + cg.zoomMode = 0; + cg.zoomTime = cg.time; + cg.zoomLocked = qfalse; + } + } + else if ( !(pm->cmd.buttons & BUTTON_ALT_ATTACK )) + { + // Not pressing zoom any more + if ( cg.zoomMode == 2 ) + { + // were zooming in, so now lock the zoom + cg.zoomLocked = qtrue; + } + } + + if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + // If we are zoomed, we should switch the ammo usage to the alt-fire, otherwise, we'll + // just use whatever ammo was selected from above + if ( cg.zoomMode == 2 ) + { + amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - + weaponData[pm->ps->weapon].altEnergyPerShot; + } + } + else + { + // alt-fire button pressing doesn't use any ammo + amount = 0; + } + + } + + // Check for binocular specific mode + if ( cg.zoomMode == 1 && pm->gent && (pm->gent->s.numbergent)) ) // + { + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK && pm->ps->batteryCharge ) + { + // zooming out + cg.zoomLocked = qfalse; + cg.zoomDir = 1; + } + else if ( pm->cmd.buttons & BUTTON_ATTACK && pm->ps->batteryCharge ) + { + // zooming in + cg.zoomLocked = qfalse; + cg.zoomDir = -1; + } + else + { + // if no buttons are down, we should be in a locked state + cg.zoomLocked = qtrue; + } + + // kill buttons and associated firing flags so we can't fire + pm->ps->eFlags &= ~EF_FIRING; + pm->ps->eFlags &= ~EF_ALT_FIRING; + pm->cmd.buttons &= ~(BUTTON_ALT_ATTACK|BUTTON_ATTACK); + } + + // set the firing flag for continuous beam weapons, phaser will fire even if out of ammo + if ( (( pm->cmd.buttons & BUTTON_ATTACK || pm->cmd.buttons & BUTTON_ALT_ATTACK ) && ( amount >= 0 || pm->ps->weapon == WP_SABER )) ) + { + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + pm->ps->eFlags |= EF_ALT_FIRING; + if ( pm->ps->clientNum < MAX_CLIENTS && pm->gent && (pm->ps->eFlags&EF_IN_ATST) ) + {//switch ATST barrels + pm->gent->alt_fire = qtrue; + } + } + else + { + pm->ps->eFlags &= ~EF_ALT_FIRING; + if ( pm->ps->clientNum < MAX_CLIENTS && pm->gent && (pm->ps->eFlags&EF_IN_ATST) ) + {//switch ATST barrels + pm->gent->alt_fire = qfalse; + } + } + + // This flag should always get set, even when alt-firing + pm->ps->eFlags |= EF_FIRING; + } + else + { +// int iFlags = pm->ps->eFlags; + + // Clear 'em out + pm->ps->eFlags &= ~EF_FIRING; + pm->ps->eFlags &= ~EF_ALT_FIRING; + + // if I don't check the flags before stopping FX then it switches them off too often, which tones down + // the stronger FFFX so you can hardly feel them. However, if you only do iton these flags then the + // repeat-fire weapons like tetrion and dreadnought don't switch off quick enough. So... + // +/* // Might need this for beam type weapons + if ( pm->ps->weapon == WP_DREADNOUGHT || (iFlags & (EF_FIRING|EF_ALT_FIRING) ) + { + cgi_FF_StopAllFX(); + } + */ + } + + // disruptor should convert a main fire to an alt-fire if the gun is currently zoomed + if ( pm->ps->weapon == WP_DISRUPTOR && pm->gent && (pm->gent->s.numbergent)) ) + { + if ( pm->cmd.buttons & BUTTON_ATTACK && cg.zoomMode == 2 ) + { + // converting the main fire to an alt-fire + pm->cmd.buttons |= BUTTON_ALT_ATTACK; + pm->ps->eFlags |= EF_ALT_FIRING; + } + else + { + // don't let an alt-fire through + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + } + } +} + +qboolean PM_WeaponOkOnVehicle( int weapon ) +{ + //FIXME: check g_vehicleInfo for our vehicle? + switch ( weapon ) + { + case WP_NONE: + case WP_SABER: + case WP_BLASTER: + case WP_THERMAL: + return qtrue; + break; + } + return qfalse; +} + +void PM_CheckInVehicleSaberAttackAnim( void ) +{//A bit of a hack, but makes the vehicle saber attacks act like any other saber attack... + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime <= 0 ) + { + pm->ps->weaponTime = 0; + } + } + PM_CheckClearSaberBlock(); + +/* if ( PM_SaberBlocking() ) + {//busy blocking, don't do attacks + return; + } +*/ + saberMoveName_t saberMove = LS_INVALID; + switch ( pm->ps->torsoAnim ) + { + case BOTH_VS_ATR_S: + saberMove = LS_SWOOP_ATTACK_RIGHT; + break; + case BOTH_VS_ATL_S: + saberMove = LS_SWOOP_ATTACK_LEFT; + break; + case BOTH_VT_ATR_S: + saberMove = LS_TAUNTAUN_ATTACK_RIGHT; + break; + case BOTH_VT_ATL_S: + saberMove = LS_TAUNTAUN_ATTACK_LEFT; + break; + } + if ( saberMove != LS_INVALID ) + { + if ( pm->ps->saberMove == saberMove ) + {//already playing it + if ( !pm->ps->torsoAnimTimer ) + {//anim was done, set it back to ready + PM_SetSaberMove( LS_READY ); + pm->ps->saberMove = LS_READY; + pm->ps->weaponstate = WEAPON_IDLE; + if (pm->cmd.buttons&BUTTON_ATTACK) + { + if ( !pm->ps->weaponTime ) + { + PM_SetSaberMove( saberMove ); + pm->ps->weaponstate = WEAPON_FIRING; + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + } + } + } + else if ( pm->ps->torsoAnimTimer + && !pm->ps->weaponTime ) + { + PM_SetSaberMove( LS_READY ); + pm->ps->saberMove = LS_READY; + pm->ps->weaponstate = WEAPON_IDLE; + PM_SetSaberMove( saberMove ); + pm->ps->weaponstate = WEAPON_FIRING; + pm->ps->weaponTime = pm->ps->torsoAnimTimer; + } + } + pm->ps->saberBlocking = saberMoveData[pm->ps->saberMove].blocking; +} + +//force the vehicle to turn and travel to its forced destination point +void PM_VehForcedTurning( gentity_t *veh ) +{ + gentity_t *dst = &g_entities[pm->ps->vehTurnaroundIndex]; + float pitchD, yawD; + vec3_t dir; + + if (!veh || !veh->m_pVehicle) + { + return; + } + + if (!dst) + { //can't find dest ent? + return; + } + + pm->cmd.upmove = veh->m_pVehicle->m_ucmd.upmove = 127; + pm->cmd.forwardmove = veh->m_pVehicle->m_ucmd.forwardmove = 0; + pm->cmd.rightmove = veh->m_pVehicle->m_ucmd.rightmove = 0; + + VectorSubtract(dst->s.origin, veh->currentOrigin, dir); + vectoangles(dir, dir); + + yawD = AngleSubtract(pm->ps->viewangles[YAW], dir[YAW]); + pitchD = AngleSubtract(pm->ps->viewangles[PITCH], dir[PITCH]); + + yawD *= 0.2f*pml.frametime; + pitchD *= 0.6f*pml.frametime; + + pm->ps->viewangles[YAW] = AngleSubtract(pm->ps->viewangles[YAW], yawD); + pm->ps->viewangles[PITCH] = AngleSubtract(pm->ps->viewangles[PITCH], pitchD); + + //PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + SetClientViewAngle(pm->gent, pm->ps->viewangles); +} +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void Pmove( pmove_t *pmove ) +{ + Vehicle_t *pVeh = NULL; + + pm = pmove; + + // this counter lets us debug movement problems with a journal by setting a conditional breakpoint fot the previous frame + c_pmove++; + + // clear results + pm->numtouch = 0; +#ifndef _XBOX + pm->watertype = 0; + pm->waterlevel = 0; +#endif + + // Clear the blocked flag + //pm->ps->pm_flags &= ~PMF_BLOCKED; + pm->ps->pm_flags &= ~PMF_BUMPED; + + // In certain situations, we may want to control which attack buttons are pressed and what kind of functionality + // is attached to them + PM_AdjustAttackStates( pm ); + + // clear the respawned flag if attack and use are cleared + if ( pm->ps->stats[STAT_HEALTH] > 0 && + !( pm->cmd.buttons & BUTTON_ATTACK ) ) + { + pm->ps->pm_flags &= ~PMF_RESPAWNED; + } + + // clear all pmove local vars + memset (&pml, 0, sizeof(pml)); + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; + if ( pml.msec < 1 ) { + pml.msec = 1; + } else if ( pml.msec > 200 ) { + pml.msec = 200; + } + + pm->ps->commandTime = pmove->cmd.serverTime; + + // save old org in case we get stuck + VectorCopy (pm->ps->origin, pml.previous_origin); + + // save old velocity for crashlanding + VectorCopy (pm->ps->velocity, pml.previous_velocity); + + pml.frametime = pml.msec * 0.001; + + if ( pm->ps->clientNum >= MAX_CLIENTS && + pm->gent && + pm->gent->client && + pm->gent->client->NPC_class == CLASS_VEHICLE ) + { //we are a vehicle + pVeh = pm->gent->m_pVehicle; + assert( pVeh ); + if ( pVeh ) + { + pVeh->m_fTimeModifier = (pml.frametime*60.0f);//at 16.67ms (60fps), should be 1.0f + } + } + else if ( pm->gent && PM_RidingVehicle() ) + { + if ( pm->ps->vehTurnaroundIndex + && pm->ps->vehTurnaroundTime > pm->cmd.serverTime ) + { //riding this vehicle, turn my view too + PM_VehForcedTurning( &g_entities[pm->gent->s.m_iVehicleNum] ); + } + } + + PM_SetSpecialMoveValues(); + + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd, pm->gent); + + AngleVectors ( pm->ps->viewangles, pml.forward, pml.right, pml.up ); + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + pm->ps->pm_flags &= ~PMF_JUMP_HELD; + } + + // decide if backpedaling animations should be used + if ( pm->cmd.forwardmove < 0 ) { + pm->ps->pm_flags |= PMF_BACKWARDS_RUN; + } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { + pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; + } + + if ( pm->ps->pm_type >= PM_DEAD ) { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + if ( pm->ps->viewheight > -12 ) + {//slowly sink view to ground + pm->ps->viewheight -= 1; + } + } + + if ( pm->ps->pm_type == PM_SPECTATOR ) { + PM_CheckDuck (); + PM_FlyMove (); + PM_DropTimers (); + return; + } + + if ( pm->ps->pm_type == PM_NOCLIP ) { + PM_NoclipMove (); + PM_DropTimers (); + return; + } + + if (pm->ps->pm_type == PM_FREEZE) { + return; // no movement at all + } + + if ( pm->ps->pm_type == PM_INTERMISSION ) { + return; // no movement at all + } + + if ( pm->ps->pm_flags & PMF_SLOW_MO_FALL ) + {//half grav + pm->ps->gravity *= 0.5; + } + + // set watertype, and waterlevel +#ifdef _XBOX + // if the client is the player do a normal water test + // if it's an npc add the client to the npc water test queue + // we don't want to update too many npcs in one frame + if(pm->ps->clientNum == 0) + { + PM_SetWaterLevelAtPoint( pm->ps->origin, &pm->waterlevel, &pm->watertype ); + } + else + { + AddNPCToWaterUpdate(pm->ps->clientNum); + } +#else + PM_SetWaterLevelAtPoint( pm->ps->origin, &pm->waterlevel, &pm->watertype ); +#endif + + PM_SetWaterHeight(); + +#ifdef _XBOX + if ( !(pm->watertype & CONTENTS_LADDER) ) + {//Don't want to remember this for ladders, is only for waterlevel change events (sounds) + + // if we're the player set the water test from the pmove + // otherwise set it from the entitity structure + if(pm->ps->clientNum == 0 ) + { + pml.previous_waterlevel = pmove->waterlevel; + } + else + { + pml.previous_waterlevel = (g_entities + pm->ps->clientNum)->prev_waterlevel; + } + } +#else + if ( !(pm->watertype & CONTENTS_LADDER) ) + {//Don't want to remember this for ladders, is only for waterlevel change events (sounds) + pml.previous_waterlevel = pmove->waterlevel; + } +#endif + + + waterForceJump = qfalse; + if ( pmove->waterlevel && pm->ps->clientNum ) + { + if ( pm->ps->forceJumpZStart//force jumping + ||(pm->gent&&pm->gent->NPC && level.timegent->NPC->jumpTime)) //TIMER_Done(pm->gent, "forceJumpChasing" )) )//force-jumping + { + waterForceJump = qtrue; + } + } + + // set mins, maxs, and viewheight + PM_SetBounds(); + + if ( !Flying && !(pm->watertype & CONTENTS_LADDER) && pm->ps->pm_type != PM_DEAD ) + {//NOTE: noclippers shouldn't jump or duck either, no? + PM_CheckDuck(); + } + + // set groundentity + PM_GroundTrace(); + if ( Flying == FLY_HOVER ) + {//never stick to the ground + PM_HoverTrace(); + } + + if ( pm->ps->pm_type == PM_DEAD ) { + PM_DeadMove (); + } + + PM_DropTimers(); + + /* + if ( PM_RidingVehicle() ) + { + PM_NoclipMove(); + } + else */if ( pm->ps && ( (pm->ps->eFlags&EF_LOCKED_TO_WEAPON) + || (pm->ps->eFlags&EF_HELD_BY_RANCOR) + || (pm->ps->eFlags&EF_HELD_BY_WAMPA) + || (pm->ps->eFlags&EF_HELD_BY_SAND_CREATURE) ) ) + {//in an emplaced gun + PM_NoclipMove(); + } + else if ( Flying == FLY_NORMAL )//|| pm->ps->gravity <= 0 ) + { + // flight powerup doesn't allow jump and has different friction + PM_FlyMove(); + } + else if ( Flying == FLY_VEHICLE ) + { + PM_FlyVehicleMove(); + } + else if ( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) + { + PM_WaterJumpMove(); + } + else if ( pm->waterlevel > 1 //in water + &&((pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer()) || !waterForceJump) )//player or NPC not force jumping + {//force-jumping NPCs should + // swimming or in ladder + PM_WaterMove(); + } + else if (pm->gent && pm->gent->NPC && pm->gent->NPC->jumpTime!=0) + { + ucmd.forwardmove = 0; + ucmd.rightmove = 0; + ucmd.upmove = 0; + pm->ps->speed = 0; + VectorClear(pm->ps->moveDir); + + PM_AirMove(); + } + else if ( pml.walking ) + {// walking on ground + vec3_t oldOrg; + + VectorCopy( pm->ps->origin, oldOrg ); + + PM_WalkMove(); + + + float threshHold = 0.001f, movedDist = DistanceSquared( oldOrg, pm->ps->origin ); + if ( PM_StandingAnim( pm->ps->legsAnim ) || pm->ps->legsAnim == BOTH_CROUCH1 ) + { + threshHold = 0.005f; + } + + if ( movedDist < threshHold ) + {//didn't move, play no legs anim + // pm->cmd.forwardmove = pm->cmd.rightmove = 0; + } + } + else + { + if ( pm->ps->gravity <= 0 ) + { + PM_FlyMove(); + } + else + { + // airborne + PM_AirMove(); + } + } + + //PM_Animate(); + + // If we didn't move at all, then why bother doing this again -MW. + if(!(VectorCompare(pm->ps->origin,pml.previous_origin))) + { + PM_GroundTrace(); + if ( Flying == FLY_HOVER ) + {//never stick to the ground + PM_HoverTrace(); + } + } + + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//on ground + pm->ps->forceJumpZStart = 0; + pm->ps->jumpZStart = 0; + pm->ps->pm_flags &= ~PMF_JUMPING; + pm->ps->pm_flags &= ~PMF_TRIGGER_PUSHED; + pm->ps->pm_flags &= ~PMF_SLOW_MO_FALL; + } + + // If we didn't move at all, then why bother doing this again -MW. + // Note: ok, so long as we don't have water levels that change. + if(!(VectorCompare(pm->ps->origin,pml.previous_origin))) + { +#ifdef _XBOX + // only do this on xbox if it's a player not an npc + if(pm->ps->clientNum == 0) + { + PM_SetWaterLevelAtPoint( pm->ps->origin, &pm->waterlevel, &pm->watertype ); + } +#else + PM_SetWaterLevelAtPoint( pm->ps->origin, &pm->waterlevel, &pm->watertype ); +#endif + PM_SetWaterHeight(); + } + +// PM_ForcePower(); sends event to client for client side fx, not used + + // If we're a vehicle, do our special weapon function. + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE ) + { + pVeh = pm->gent->m_pVehicle; + + // Using vehicle weapon... + //if ( pm->cmd.weapon == WP_NONE ) + { + //PM_Weapon(); + //PM_AddEvent( EV_FIRE_WEAPON ); + PM_VehicleWeapon(); + } + } + // If we are riding a vehicle... + else if ( PM_RidingVehicle() ) + { + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + {// alt attack always does other stuff when riding a vehicle (turbo) + } + else if ( (pm->ps->eFlags&EF_NODRAW) ) + {//inside a vehicle? don't do any weapon stuff + } + else if ( pm->ps->weapon == WP_BLASTER//using blaster + || pm->ps->weapon == WP_THERMAL//using thermal + || pm->ps->weaponstate == WEAPON_DROPPING//changing weapon - dropping + || pm->ps->weaponstate == WEAPON_RAISING//changing weapon - raising + || (pm->cmd.weapon != pm->ps->weapon && PM_WeaponOkOnVehicle( pm->cmd.weapon )) )//FIXME: make this a vehicle call to see if this new weapon is valid for this vehicle + {//either weilding a weapon we can fire with normal weapon logic, or trying to change to a valid weapon + // call normal weapons code... should we override the normal fire anims with vehicle fire anims in here or in a subsequent call to VehicleWeapons or something? + //Maybe break PM_Weapon into PM_Weapon and PM_WeaponAnimate (then call our own PM_VehicleWeaponAnimate)? + PM_Weapon(); + } + //BUT: now call Vehicle's weapon code, to handle lightsaber and (maybe) overriding weapon ready/firing anims? + } + // otherwise do the normal weapon function. + else + { + // weapons + PM_Weapon(); + } + if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + pm->ps->pm_flags |= PMF_ATTACK_HELD; + } + else + { + pm->ps->pm_flags &= ~PMF_ATTACK_HELD; + } + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + pm->ps->pm_flags |= PMF_ALT_ATTACK_HELD; + } + else + { + pm->ps->pm_flags &= ~PMF_ALT_ATTACK_HELD; + } + if ( pm->cmd.buttons & BUTTON_FORCE_FOCUS ) + { + pm->ps->pm_flags |= PMF_FORCE_FOCUS_HELD; + } + else + { + pm->ps->pm_flags &= ~PMF_FORCE_FOCUS_HELD; + } + + if ( pm->gent )//&& pm->gent->s.number == 0 )//player only? + { + // Use + PM_Use(); + } + + // Calculate the resulting speed of the last pmove + //------------------------------------------------- + if ( pm->gent ) + { + pm->gent->resultspeed = ((Distance(pm->ps->origin, pm->gent->currentOrigin) / pml.msec) * 1000); + if (pm->gent->resultspeed>5.0f) + { + pm->gent->lastMoveTime = level.time; + } + + // If Have Not Been Moving For A While, Stop + //------------------------------------------- + if (pml.walking && (level.time - pm->gent->lastMoveTime)>1000) + { + pm->cmd.forwardmove = pm->cmd.rightmove = 0; + } + } + + + // ANIMATION + //================================ + + // TEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMP + if ( pm->gent && pm->ps && pm->ps->eFlags & EF_LOCKED_TO_WEAPON ) + { + if ( pm->gent->owner && pm->gent->owner->e_UseFunc == useF_emplaced_gun_use )//ugly way to tell, but... + {//full body + PM_SetAnim(pm,SETANIM_BOTH,BOTH_GUNSIT1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);//SETANIM_FLAG_NORMAL + } + else + {//stand (or could be overridden by strafe anims) + PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else if ( pm->gent && pm->ps && (pm->ps->eFlags&EF_HELD_BY_RANCOR) ) + { + PM_SetAnim(pm,SETANIM_LEGS,BOTH_SWIM_IDLE1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);//SETANIM_FLAG_NORMAL + } + // If we are a vehicle, animate... + else if ( pVeh ) + { + pVeh->m_pVehicleInfo->Animate( pVeh ); + } + // If we're riding a vehicle, don't do anything!. + else if ( ( pVeh = PM_RidingVehicle() ) != 0 ) + { + PM_CheckInVehicleSaberAttackAnim(); + } + else // TEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMPTEMP + { + // footstep events / legs animations + PM_Footsteps(); + } + // torso animation + if ( !pVeh ) + {//not riding a vehicle + PM_TorsoAnimation(); + } + + // entering / leaving water splashes + PM_WaterEvents(); + + // snap some parts of playerstate to save network bandwidth + // SnapVector( pm->ps->velocity ); + + if ( !pm->cmd.rightmove && !pm->cmd.forwardmove && pm->cmd.upmove <= 0 ) + { + if ( VectorCompare( pm->ps->velocity, vec3_origin ) ) + { + pm->ps->lastStationary = level.time; + } + } + + if ( pm->ps->pm_flags & PMF_SLOW_MO_FALL ) + {//half grav + pm->ps->gravity *= 2; + } +} \ No newline at end of file diff --git a/code/game/bg_public.h b/code/game/bg_public.h new file mode 100644 index 0000000..a4657ab --- /dev/null +++ b/code/game/bg_public.h @@ -0,0 +1,736 @@ +#ifndef __BG_PUBLIC_H__ +#define __BG_PUBLIC_H__ +// bg_public.h -- definitions shared by both the server game and client game modules +#include "weapons.h" +#include "g_items.h" +#include "teams.h" +#include "statindex.h" + +#define DEFAULT_GRAVITY 800 +#define GIB_HEALTH -40 +#define ARMOR_PROTECTION 0.40 + +#define MAX_ITEMS 128 + +#define RANK_TIED_FLAG 0x4000 + +#define DEFAULT_SHOTGUN_SPREAD 700 +#define DEFAULT_SHOTGUN_COUNT 11 + +#define ITEM_RADIUS 15 // item sizes are needed for client side pickup detection + +//Player sizes +extern float DEFAULT_MINS_0; +extern float DEFAULT_MINS_1; +extern float DEFAULT_MAXS_0; +extern float DEFAULT_MAXS_1; +extern float DEFAULT_PLAYER_RADIUS; +#define DEFAULT_MINS_2 -24 +#define DEFAULT_MAXS_2 40// was 32, but too short for player +#define CROUCH_MAXS_2 16 + +#define ATST_MINS0 -40 +#define ATST_MINS1 -40 +#define ATST_MINS2 -24 +#define ATST_MAXS0 40 +#define ATST_MAXS1 40 +#define ATST_MAXS2 248 + +//Player viewheights +#define STANDARD_VIEWHEIGHT_OFFSET -4 +//#define RAVEN_VIEWHEIGHT_ADJ 2 +//#define DEFAULT_VIEWHEIGHT (26+RAVEN_VIEWHEIGHT_ADJ) +//#define CROUCH_VIEWHEIGHT 12 +#define DEAD_VIEWHEIGHT -16 +//Player movement values +#define MIN_WALK_NORMAL 0.7 // can't walk on very steep slopes +#define JUMP_VELOCITY 225 // 270 +#define STEPSIZE 18 + + + +/* +=================================================================================== + +PMOVE MODULE + +The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t +and some other output data. Used for local prediction on the client game and true +movement on the server game. +=================================================================================== +*/ + +typedef enum { + PM_NORMAL, // can accelerate and turn + PM_NOCLIP, // noclip movement + PM_SPECTATOR, // still run into walls + PM_DEAD, // no acceleration or turning, but free falling + PM_FREEZE, // stuck in place with no control + PM_INTERMISSION // no movement or status bar +} pmtype_t; + +typedef enum { + WEAPON_READY, + WEAPON_RAISING, + WEAPON_DROPPING, + WEAPON_FIRING, + WEAPON_CHARGING, + WEAPON_CHARGING_ALT, + WEAPON_IDLE, //lowered +} weaponstate_t; + +// pmove->pm_flags +#define PMF_DUCKED (1<<0)//1 +#define PMF_JUMP_HELD (1<<1)//2 +#define PMF_JUMPING (1<<2)//4 // yes, I really am in a jump -- Mike, you may want to come up with something better here since this is really a temp fix. +#define PMF_BACKWARDS_JUMP (1<<3)//8 // go into backwards land +#define PMF_BACKWARDS_RUN (1<<4)//16 // coast down to backwards run +#define PMF_TIME_LAND (1<<5)//32 // pm_time is time before rejump +#define PMF_TIME_KNOCKBACK (1<<6)//64 // pm_time is an air-accelerate only time +#define PMF_TIME_NOFRICTION (1<<7)//128 // pm_time is a no-friction time +#define PMF_TIME_WATERJUMP (1<<8)//256 // pm_time is waterjump +#define PMF_RESPAWNED (1<<9)//512 // clear after attack and jump buttons come up +#define PMF_USEFORCE_HELD (1<<10)//1024 // for debouncing the button +#define PMF_JUMP_DUCKED (1<<11)//2048 // viewheight changes in mid-air +#define PMF_TRIGGER_PUSHED (1<<12)//4096 // pushed by a trigger_push or other such thing - cannot force jump and will not take impact damage +#define PMF_STUCK_TO_WALL (1<<13)//8192 // grabbing a wall +#define PMF_SLOW_MO_FALL (1<<14)//16384 // Fall slower until hit ground +#define PMF_ATTACK_HELD (1<<15)//32768 // Holding down the attack button +#define PMF_ALT_ATTACK_HELD (1<<16)//65536 // Holding down the alt-attack button +#define PMF_BUMPED (1<<17)//131072 // Bumped into something +#define PMF_FORCE_FOCUS_HELD (1<<18)//262144 // Holding down the saberthrow/kick button +#define PMF_FIX_MINS (1<<19)//524288 // Mins raised for dual forward jump, fix them +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK|PMF_TIME_NOFRICTION) + +#if defined(_XBOX) && !defined(_TRACE_FUNCTOR_T_DEFINED_) +// Function objects to replace the function pointers used for trace in pmove_t +// We can't have default arguments on function pointers, but this allows us to +// do the same thing with minimal impact elsewhere. +struct Trace_Functor_t +{ + typedef void (*trace_func_t)(trace_t *, const vec3_t, const vec3_t, const vec3_t, const vec3_t, + const int, const int, const EG2_Collision, const int); + trace_func_t trace_func; + void operator()( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + const int passEntityNum, const int contentMask, const EG2_Collision eG2TraceType = (EG2_Collision)0, const int useLod = 0 ) + { trace_func(results, start, mins, maxs, end, passEntityNum, contentMask, eG2TraceType, useLod); } + const Trace_Functor_t &operator=(trace_func_t traceRHS) + { + trace_func = traceRHS; + return *this; + } +}; + +// Always create this class exactly once +#define _TRACE_FUNCTOR_T_DEFINED_ +#endif + +#define MAXTOUCH 32 +typedef struct gentity_s gentity_t; +typedef struct { + // state (in / out) + playerState_t *ps; + + // command (in) + usercmd_t cmd; + int tracemask; // collide against these types of surfaces + int debugLevel; // if set, diagnostic output will be printed + qboolean noFootsteps; // if the game is setup for no footsteps by the server + + // results (out) + int numtouch; + int touchents[MAXTOUCH]; + + int useEvent; + + vec3_t mins, maxs; // bounding box size + + int watertype; + int waterlevel; + + float xyspeed; + gentity_s *gent; // Pointer to entity in g_entities[] + + // callbacks to test the world + // these will be different functions during game and cgame +#ifdef _XBOX + Trace_Functor_t trace; +#else + void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + const int passEntityNum, const int contentMask, const EG2_Collision eG2TraceType = (EG2_Collision)0, const int useLod = 0 ); +#endif + int (*pointcontents)( const vec3_t point, int passEntityNum ); +} pmove_t; + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t *ps, usercmd_t *cmd, gentity_t *gent ); +void Pmove( pmove_t *pmove ); + + +#define SETANIM_TORSO 1 +#define SETANIM_LEGS 2 +#define SETANIM_BOTH (SETANIM_TORSO|SETANIM_LEGS)//3 + +#define SETANIM_FLAG_NORMAL 0//Only set if timer is 0 +#define SETANIM_FLAG_OVERRIDE 1//Override previous +#define SETANIM_FLAG_HOLD 2//Set the new timer +#define SETANIM_FLAG_RESTART 4//Allow restarting the anim if playing the same one (weapon fires) +#define SETANIM_FLAG_HOLDLESS 8//Set the new timer + +#define SETANIM_BLEND_DEFAULT 100 + +void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime=SETANIM_BLEND_DEFAULT); +void PM_SetAnimFinal(int *torsoAnim,int *legsAnim,int type,int anim,int priority,int *torsoAnimTimer,int *legsAnimTimer,gentity_t *gent,int blendTime=SETANIM_BLEND_DEFAULT); + +//=================================================================================== + + +// player_state->persistant[] indexes +// these fields are the only part of player_state that isn't +// cleared on respawn +// +// NOTE!!! Even though this is an enum, the array that contains these uses #define MAX_PERSISTANT 16 in q_shared.h, +// so be careful how many you add since it'll just overflow without telling you -slc +// +typedef enum { + PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + PERS_HITS, // total points damage inflicted so damage beeps can sound on change + PERS_TEAM, + PERS_SPAWN_COUNT, // incremented every respawn +// PERS_REWARD_COUNT, // incremented for each reward sound + PERS_ATTACKER, // clientnum of last damage inflicter + PERS_KILLED, // count of the number of times you died + + PERS_ACCURACY_SHOTS, // scoreboard - number of player shots + PERS_ACCURACY_HITS, // scoreboard - number of player shots that hit an enemy + PERS_ENEMIES_KILLED, // scoreboard - number of enemies player killed + PERS_TEAMMATES_KILLED // scoreboard - number of teammates killed +} persEnum_t; + + +// entityState_t->eFlags +#define EF_HELD_BY_SAND_CREATURE 0x00000001 // In a sand creature's mouth +#define EF_HELD_BY_RANCOR 0x00000002 // Being held by Rancor +#define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes +#define EF_SHADER_ANIM 0x00000008 // Animating shader (by s.frame) +#define EF_BOUNCE 0x00000010 // for missiles +#define EF_BOUNCE_HALF 0x00000020 // for missiles +#define EF_MISSILE_STICK 0x00000040 // missiles that stick to the wall. +#define EF_NODRAW 0x00000080 // may have an event, but no model (unspawned items) +#define EF_FIRING 0x00000100 // for lightning gun +#define EF_ALT_FIRING 0x00000200 // for alt-fires, mostly for lightning guns though +#define EF_VEH_BOARDING 0x00000400 // Whether a vehicle is being boarded or not. +#define EF_AUTO_SIZE 0x00000800 // CG_Ents will create the mins & max itself based on model bounds +#define EF_BOUNCE_SHRAPNEL 0x00001000 // special shrapnel flag +#define EF_USE_ANGLEDELTA 0x00002000 // Not used. +#define EF_ANIM_ALLFAST 0x00004000 // automatically cycle through all frames at 10hz +#define EF_ANIM_ONCE 0x00008000 // cycle through all frames just once then stop +#define EF_HELD_BY_WAMPA 0x00010000 // being held by the Wampa +#define EF_PROX_TRIP 0x00020000 // Proximity trip mine has been activated +#define EF_LOCKED_TO_WEAPON 0x00040000 // When we use an emplaced weapon, we turn this on to lock us to that weapon + +//rest not sent over net? + +#define EF_PERMANENT 0x00080000 // this entity is permanent and is never updated (sent only in the game state) +#define EF_SPOTLIGHT 0x00100000 // Your lights are on... +#define EF_PLANTED_CHARGE 0x00200000 // For detpack charge +#define EF_POWERING_ROSH 0x00400000 // Only for Twins powering up Rosh +#define EF_FORCE_VISIBLE 0x00800000 // Always visible with force sight +#define EF_IN_ATST 0x01000000 // Driving an ATST +#define EF_DISINTEGRATION 0x02000000 // Disruptor effect +#define EF_LESS_ATTEN 0x04000000 // Use less sound attenuation (louder even when farther). +#define EF_JETPACK_ACTIVE 0x08000000 // Not used +#define EF_DISABLE_SHADER_ANIM 0x10000000 // Normally shader animation chugs along, but movers can force shader animation to be on frame 1 +#define EF_FORCE_GRIPPED 0x20000000 // Force gripped effect +#define EF_FORCE_DRAINED 0x40000000 // Force drained effect +#define EF_BLOCKED_MOVER 0x80000000 // for movers that are blocked - shared with previous + +typedef enum { + PW_NONE, + PW_QUAD,// This can go away + PW_BATTLESUIT, + PW_HASTE,// This can go away + PW_CLOAKED, + PW_UNCLOAKING, + PW_DISRUPTION, + PW_GALAK_SHIELD, +// PW_WEAPON_OVERCHARGE, + PW_SEEKER, + PW_SHOCKED,//electricity effect + PW_DRAINED,//drain effect + PW_DISINT_2,//ghost + PW_INVINCIBLE, + PW_FORCE_PUSH, + PW_FORCE_PUSH_RHAND, + + PW_NUM_POWERUPS +} powerup_t; + +#define PW_REMOVE_AT_DEATH ((1<event values +// entity events are for effects that take place relative +// to an existing entities origin. Very network efficient. + +// two bits at the top of the entityState->event field +// will be incremented with each change in the event so +// that an identical event started twice in a row can +// be distinguished. And off the value with ~EV_EVENT_BITS +// to retrieve the actual event number +#define EV_EVENT_BIT1 0x00000100 +#define EV_EVENT_BIT2 0x00000200 +#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) + +typedef enum { + EV_NONE, + + EV_FOOTSTEP, + EV_FOOTSTEP_METAL, + EV_FOOTSPLASH, + EV_FOOTWADE, + EV_SWIM, + + EV_STEP_4, + EV_STEP_8, + EV_STEP_12, + EV_STEP_16, + + EV_FALL_SHORT, + EV_FALL_MEDIUM, + EV_FALL_FAR, + + EV_JUMP, + EV_ROLL, + EV_WATER_TOUCH, // foot touches + EV_WATER_LEAVE, // foot leaves + EV_WATER_UNDER, // head touches + EV_WATER_CLEAR, // head leaves + EV_WATER_GURP1, // need air 1 + EV_WATER_GURP2, // need air 2 + EV_WATER_DROWN, // drowned + EV_LAVA_TOUCH, // foot touches + EV_LAVA_LEAVE, // foot leaves + EV_LAVA_UNDER, // head touches + + EV_ITEM_PICKUP, + + EV_NOAMMO, + EV_CHANGE_WEAPON, + EV_FIRE_WEAPON, + EV_ALT_FIRE, + EV_POWERUP_SEEKER_FIRE, + EV_POWERUP_BATTLESUIT, + EV_USE, + + EV_REPLICATOR, + + EV_BATTERIES_CHARGED, + + EV_GRENADE_BOUNCE, // eventParm will be the soundindex + EV_MISSILE_STICK, // eventParm will be the soundindex + + EV_BMODEL_SOUND, + EV_GENERAL_SOUND, + EV_GLOBAL_SOUND, // no attenuation + +#ifdef _IMMERSION + EV_ENTITY_FORCE, + EV_AREA_FORCE, + EV_GLOBAL_FORCE, + EV_FORCE_STOP, +#endif // _IMMERSION + EV_PLAY_EFFECT, + EV_PLAY_MUZZLE_EFFECT, + EV_STOP_EFFECT, + + EV_TARGET_BEAM_DRAW, + + EV_DISRUPTOR_MAIN_SHOT, + EV_DISRUPTOR_SNIPER_SHOT, + EV_DISRUPTOR_SNIPER_MISS, + + EV_DEMP2_ALT_IMPACT, +//NEW for JKA weapons: + EV_CONC_ALT_SHOT, + EV_CONC_ALT_MISS, +//END JKA weapons + EV_PAIN, + EV_DEATH1, + EV_DEATH2, + EV_DEATH3, + + EV_MISSILE_HIT, + EV_MISSILE_MISS, + + EV_DISINTEGRATION, + + EV_ANGER1, //Say when acquire an enemy when didn't have one before + EV_ANGER2, + EV_ANGER3, + + EV_VICTORY1, //Say when killed an enemy + EV_VICTORY2, + EV_VICTORY3, + + EV_CONFUSE1, //Say when confused + EV_CONFUSE2, + EV_CONFUSE3, + + EV_PUSHED1, //Say when pushed + EV_PUSHED2, + EV_PUSHED3, + + EV_CHOKE1, //Say when choking + EV_CHOKE2, + EV_CHOKE3, + + EV_FFWARN, //ffire founds + EV_FFTURN, + //extra sounds for ST + EV_CHASE1, + EV_CHASE2, + EV_CHASE3, + EV_COVER1, + EV_COVER2, + EV_COVER3, + EV_COVER4, + EV_COVER5, + EV_DETECTED1, + EV_DETECTED2, + EV_DETECTED3, + EV_DETECTED4, + EV_DETECTED5, + EV_LOST1, + EV_OUTFLANK1, + EV_OUTFLANK2, + EV_ESCAPING1, + EV_ESCAPING2, + EV_ESCAPING3, + EV_GIVEUP1, + EV_GIVEUP2, + EV_GIVEUP3, + EV_GIVEUP4, + EV_LOOK1, + EV_LOOK2, + EV_SIGHT1, + EV_SIGHT2, + EV_SIGHT3, + EV_SOUND1, + EV_SOUND2, + EV_SOUND3, + EV_SUSPICIOUS1, + EV_SUSPICIOUS2, + EV_SUSPICIOUS3, + EV_SUSPICIOUS4, + EV_SUSPICIOUS5, + //extra sounds for Jedi + EV_COMBAT1, + EV_COMBAT2, + EV_COMBAT3, + EV_JDETECTED1, + EV_JDETECTED2, + EV_JDETECTED3, + EV_TAUNT1, + EV_TAUNT2, + EV_TAUNT3, + EV_JCHASE1, + EV_JCHASE2, + EV_JCHASE3, + EV_JLOST1, + EV_JLOST2, + EV_JLOST3, + EV_DEFLECT1, + EV_DEFLECT2, + EV_DEFLECT3, + EV_GLOAT1, + EV_GLOAT2, + EV_GLOAT3, + EV_PUSHFAIL, + + EV_USE_ITEM, + + EV_USE_INV_BINOCULARS, + EV_USE_INV_BACTA, + EV_USE_INV_SEEKER, + EV_USE_INV_LIGHTAMP_GOGGLES, + EV_USE_INV_SENTRY, + + EV_USE_FORCE, + + EV_DRUGGED, // hit by an interrogator + + EV_DEBUG_LINE, + EV_KOTHOS_BEAM, + + +} entity_event_t; + +#pragma pack(push, 1) +typedef struct animation_s { + unsigned short firstFrame; + unsigned short numFrames; + short frameLerp; // msec between frames + //initial lerp is abs(frameLerp) + signed char loopFrames; // 0 to numFrames, -1 = no loop + unsigned char glaIndex; +} animation_t; +#pragma pack(pop) + +#ifdef _XBOX +// Feel free to re-increase this if necessary, worst case right now is vjun3 -> 9 +#define MAX_ANIM_FILES 10 +#else +#define MAX_ANIM_FILES 16 +#endif +#define MAX_ANIM_EVENTS 300 + +//size of Anim eventData array... +#define MAX_RANDOM_ANIM_SOUNDS 4 +#define AED_ARRAY_SIZE (MAX_RANDOM_ANIM_SOUNDS+3) +//indices for AEV_SOUND data +#define AED_SOUNDINDEX_START 0 +#define AED_SOUNDINDEX_END (MAX_RANDOM_ANIM_SOUNDS-1) +#define AED_SOUND_NUMRANDOMSNDS (MAX_RANDOM_ANIM_SOUNDS) +#define AED_SOUND_PROBABILITY (MAX_RANDOM_ANIM_SOUNDS+1) +//indices for AEV_SOUNDCHAN data +#define AED_SOUNDCHANNEL (MAX_RANDOM_ANIM_SOUNDS+2) +//indices for AEV_FOOTSTEP data +#define AED_FOOTSTEP_TYPE 0 +#define AED_FOOTSTEP_PROBABILITY 1 +//indices for AEV_EFFECT data +#define AED_EFFECTINDEX 0 +#define AED_BOLTINDEX 1 +#define AED_EFFECT_PROBABILITY 2 +#define AED_MODELINDEX 3 +//indices for AEV_FIRE data +#define AED_FIRE_ALT 0 +#define AED_FIRE_PROBABILITY 1 +//indices for AEV_MOVE data +#define AED_MOVE_FWD 0 +#define AED_MOVE_RT 1 +#define AED_MOVE_UP 2 + +typedef enum +{//NOTENOTE: Be sure to update animEventTypeTable and ParseAnimationEvtBlock(...) if you change this enum list! + AEV_NONE, + AEV_SOUND, //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay + AEV_FOOTSTEP, //# animID AEV_FOOTSTEP framenum footstepType chancetoplay + AEV_EFFECT, //# animID AEV_EFFECT framenum effectpath boltName chancetoplay + AEV_FIRE, //# animID AEV_FIRE framenum altfire chancetofire + AEV_MOVE, //# animID AEV_MOVE framenum forwardpush rightpush uppush + AEV_SOUNDCHAN, //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay + AEV_NUM_AEV +} animEventType_t; + +#ifdef _XBOX +#pragma pack(push, 1) +#endif +typedef struct animevent_s +{ + animEventType_t eventType; + signed short modelOnly; //event is specific to a modelname to skeleton + unsigned short glaIndex; + unsigned short keyFrame; //Frame to play event on + signed short eventData[AED_ARRAY_SIZE]; //Unique IDs, can be soundIndex of sound file to play OR effect index or footstep type, etc. + char *stringData; //we allow storage of one string, temporarily (in case we have to look up an index later, then make sure to set stringData to NULL so we only do the look-up once) +} animevent_t; +#ifdef _XBOX +#pragma pack(pop) +#endif + +typedef enum +{ + FOOTSTEP_R, + FOOTSTEP_L, + FOOTSTEP_HEAVY_R, + FOOTSTEP_HEAVY_L, + NUM_FOOTSTEP_TYPES +} footstepType_t; + +// means of death +typedef enum { + + MOD_UNKNOWN, + +// weapons + MOD_SABER, + MOD_BRYAR, + MOD_BRYAR_ALT, + MOD_BLASTER, + MOD_BLASTER_ALT, + MOD_DISRUPTOR, + MOD_SNIPER, + MOD_BOWCASTER, + MOD_BOWCASTER_ALT, + MOD_REPEATER, + MOD_REPEATER_ALT, + MOD_DEMP2, + MOD_DEMP2_ALT, + MOD_FLECHETTE, + MOD_FLECHETTE_ALT, + MOD_ROCKET, + MOD_ROCKET_ALT, +//NEW for JKA weapons: + MOD_CONC, + MOD_CONC_ALT, +//END JKA weapons. + MOD_THERMAL, + MOD_THERMAL_ALT, + MOD_DETPACK, + MOD_LASERTRIP, + MOD_LASERTRIP_ALT, + MOD_MELEE, + MOD_SEEKER, + MOD_FORCE_GRIP, + MOD_FORCE_LIGHTNING, + MOD_FORCE_DRAIN, + MOD_EMPLACED, + +// world / generic + MOD_ELECTROCUTE, + MOD_EXPLOSIVE, + MOD_EXPLOSIVE_SPLASH, + MOD_KNOCKOUT, + MOD_ENERGY, + MOD_ENERGY_SPLASH, + MOD_WATER, + MOD_SLIME, + MOD_LAVA, + MOD_CRUSH, + MOD_IMPACT, + MOD_FALLING, + MOD_SUICIDE, + MOD_TRIGGER_HURT, + MOD_GAS, + + NUM_MODS, + +} meansOfDeath_t; + + +//--------------------------------------------------------- + +// gitem_t->type +typedef enum +{ + IT_BAD, + IT_WEAPON, + IT_AMMO, + IT_ARMOR, + IT_HEALTH, + IT_HOLDABLE, + IT_BATTERY, + IT_HOLOCRON, + +} itemType_t; + + + +typedef struct gitem_s { + char *classname; // spawning name + char *pickup_sound; + char *world_model; + + char *icon; + + int quantity; // for ammo how much, or duration of powerup + itemType_t giType; // IT_* flags + + int giTag; + + char *precaches; // string of all models and images this item will use + char *sounds; // string of all sounds this item will use + vec3_t mins; // Bbox + vec3_t maxs; // Bbox +#ifdef _IMMERSION + char *pickup_force; + char *forces; +#endif // _IMMERSION +} gitem_t; + +// included in both the game dll and the client +extern gitem_t bg_itemlist[]; +extern const int bg_numItems; + + +//============================================================================== + +/* +typedef struct ginfoitem_s +{ + char *infoString;// Text message + vec3_t color; // Text color + +} ginfoitem_t; +*/ + +//============================================================================== + +extern weaponData_t weaponData[]; + +//============================================================================== +extern ammoData_t ammoData[]; + +//============================================================================== + +gitem_t *FindItem( const char *className ); +gitem_t *FindItemForWeapon( weapon_t weapon ); +gitem_t *FindItemForInventory( int inv ); + +#define ITEM_INDEX(x) ((x)-bg_itemlist) + +qboolean BG_CanItemBeGrabbed( const entityState_t *ent, const playerState_t *ps ); + + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_TERRAIN) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY|CONTENTS_TERRAIN) +#define MASK_NPCSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BODY|CONTENTS_TERRAIN) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_TERRAIN) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_OPAQUE|CONTENTS_SLIME|CONTENTS_LAVA)//was CONTENTS_SOLID, not CONTENTS_OPAQUE...? +/* +Ghoul2 Insert Start +*/ +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE|CONTENTS_SHOTCLIP|CONTENTS_TERRAIN) +/* +Ghoul2 Insert End +*/ + +// +// entityState_t->eType +// +typedef enum { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_MOVER, + ET_BEAM, + ET_PORTAL, + ET_SPEAKER, + ET_PUSH_TRIGGER, + ET_TELEPORT_TRIGGER, + ET_INVISIBLE, + ET_THINKER, + ET_CLOUD, // dumb + ET_TERRAIN, + + ET_EVENTS // any of the EV_* events can be added freestanding + // by setting eType to ET_EVENTS + eventNum + // this avoids having to set eFlags and eventNum +} entityType_t; + + + +void EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); +void EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); + +void AddEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); +int CurrentPlayerstateEvent( playerState_t *ps ); + +void PlayerStateToEntityState( playerState_t *ps, entityState_t *s ); + +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); + +#endif//#ifndef __BG_PUBLIC_H__ \ No newline at end of file diff --git a/code/game/bg_slidemove.cpp b/code/game/bg_slidemove.cpp new file mode 100644 index 0000000..71d843a --- /dev/null +++ b/code/game/bg_slidemove.cpp @@ -0,0 +1,573 @@ +// this include must remain at the top of every bg_xxxx CPP file +#include "common_headers.h" + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" +#include "g_vehicles.h" + +extern qboolean PM_ClientImpact( trace_t *trace, qboolean damageSelf ); +extern qboolean PM_ControlledByPlayer( void ); +extern qboolean PM_InReboundHold( int anim ); +extern cvar_t *g_stepSlideFix; + +/* + +input: origin, velocity, bounds, groundPlane, trace function + +output: origin, velocity, impacts, stairup boolean + +*/ + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 +extern qboolean PM_GroundSlideOkay( float zNormal ); +extern qboolean PM_InSpecialJump( int anim ); +qboolean PM_SlideMove( float gravMod ) { + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t normal, planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + vec3_t clipVelocity; + int i, j, k; + trace_t trace; + vec3_t end; + float time_left; + float into; + vec3_t endVelocity; + vec3_t endClipVelocity; + qboolean damageSelf = qtrue; + int slideMoveContents = pm->tracemask; + + if ( pm->ps->clientNum >= MAX_CLIENTS + && !PM_ControlledByPlayer() ) + {//a non-player client, not an NPC under player control + if ( pml.walking //walking on the ground + || (pm->ps->groundEntityNum != ENTITYNUM_NONE //in air + && PM_InSpecialJump( pm->ps->legsAnim )//in a special jump + && !(pm->ps->eFlags&EF_FORCE_GRIPPED)//not being gripped + && !(pm->ps->pm_flags&PMF_TIME_KNOCKBACK) + && pm->gent + && pm->gent->forcePushTime < level.time) )//not being pushed + {// + // If we're a vehicle, ignore this if we're being driven + if ( !pm->gent //not an game ent + || !pm->gent->client //not a client + || pm->gent->client->NPC_class != CLASS_VEHICLE//not a vehicle + || !pm->gent->m_pVehicle //no vehicle + || !pm->gent->m_pVehicle->m_pPilot//no pilot + || pm->gent->m_pVehicle->m_pPilot->s.number >= MAX_CLIENTS )//pilot is not the player + {//then treat do not enter brushes as SOLID + slideMoveContents |= CONTENTS_BOTCLIP; + } + } + } + + numbumps = 4; + + VectorCopy (pm->ps->velocity, primal_velocity); + + if ( gravMod ) + { + VectorCopy( pm->ps->velocity, endVelocity ); + if ( !(pm->ps->eFlags&EF_FORCE_GRIPPED) && !(pm->ps->eFlags&EF_FORCE_DRAINED) ) + { + endVelocity[2] -= pm->ps->gravity * pml.frametime * gravMod; + } + pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; + primal_velocity[2] = endVelocity[2]; + if ( pml.groundPlane ) + { + if ( PM_GroundSlideOkay( pml.groundTrace.plane.normal[2] ) ) + {// slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + } + + time_left = pml.frametime; + + // never turn against the ground plane + if ( pml.groundPlane ) + { + numplanes = 1; + VectorCopy( pml.groundTrace.plane.normal, planes[0] ); + if ( !PM_GroundSlideOkay( planes[0][2] ) ) + { + planes[0][2] = 0; + VectorNormalize( planes[0] ); + } + } + else + { + numplanes = 0; + } + + // never turn against original velocity + VectorNormalize2( pm->ps->velocity, planes[numplanes] ); + numplanes++; + + for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) { + + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // see if we can make it there + pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, slideMoveContents ); + if ( (trace.contents&CONTENTS_BOTCLIP) + && (slideMoveContents&CONTENTS_BOTCLIP) ) + {//hit a do not enter brush + if ( trace.allsolid || trace.startsolid )//inside the botclip + {//crap, we're in a do not enter brush, take it out for the remainder of the traces and re-trace this one right now without it + slideMoveContents &= ~CONTENTS_BOTCLIP; + pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, slideMoveContents ); + } + else if ( trace.plane.normal[2] > 0.0f ) + {//on top of a do not enter brush, it, just redo this one trace without it + pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, (slideMoveContents&~CONTENTS_BOTCLIP) ); + } + } + + if ( trace.allsolid ) + {// entity is completely trapped in another solid + pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration + return qtrue; + } + + if ( trace.fraction > 0 ) + {// actually covered some distance + VectorCopy( trace.endpos, pm->ps->origin ); + } + + if ( trace.fraction == 1 ) + { + break; // moved the entire distance + } + + + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + //Hit it + if ( trace.surfaceFlags&SURF_NODAMAGE ) + { + damageSelf = qfalse; + } + else if ( trace.entityNum == ENTITYNUM_WORLD && trace.plane.normal[2] > 0.5f ) + {//if we land on the ground, let falling damage do it's thing itself, otherwise do impact damage + damageSelf = qfalse; + } + else + { + damageSelf = qtrue; + } + + if ( PM_ClientImpact( &trace, damageSelf ) ) + { + continue; + } + + if (pm->gent->client && + pm->gent->client->NPC_class == CLASS_VEHICLE && + trace.plane.normal[2]gent->m_pVehicle->m_pVehicleInfo->maxSlope + ) + { + pm->ps->pm_flags |= PMF_BUMPED; + } + + time_left -= time_left * trace.fraction; + + if ( numplanes >= MAX_CLIP_PLANES ) + {// this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + VectorCopy( trace.plane.normal, normal ); + + if ( !PM_GroundSlideOkay( normal[2] ) ) + {//wall-running + //never push up off a sloped wall + normal[2] = 0; + VectorNormalize( normal ); + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + if ( !(pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//no sliding if stuck to wall! + for ( i = 0 ; i < numplanes ; i++ ) { + if ( DotProduct( normal, planes[i] ) > 0.99 ) { + VectorAdd( normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + if ( i < numplanes ) { + continue; + } + } + VectorCopy( normal, planes[numplanes] ); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0 ; i < numplanes ; i++ ) { + into = DotProduct( pm->ps->velocity, planes[i] ); + if ( into >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // see how hard we are hitting things + if ( -into > pml.impactSpeed ) { + pml.impactSpeed = -into; + } + + // slide along the plane + PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); + + // slide along the plane + PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0 ; j < numplanes ; j++ ) { + if ( j == i ) { + continue; + } + if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); + PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + + // see if it goes back into the first clip plane + if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { + continue; + } + + // slide the original velocity along the crease + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, pm->ps->velocity ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the the new move enters + for ( k = 0 ; k < numplanes ; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a triple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if ( gravMod ) { + VectorCopy( endVelocity, pm->ps->velocity ); + } + + // don't change velocity if in a timer (FIXME: is this correct?) + if ( pm->ps->pm_time ) { + VectorCopy( primal_velocity, pm->ps->velocity ); + } + + return ( bumpcount != 0 ); +} + +/* +================== +PM_StepSlideMove + +================== +*/ +void PM_StepSlideMove( float gravMod ) +{ + vec3_t start_o, start_v; + vec3_t down_o, down_v; + vec3_t slideMove, stepUpMove; + trace_t trace; + vec3_t up, down; + qboolean cantStepUpFwd, isGiant = qfalse;; + int stepSize = STEPSIZE; + + VectorCopy (pm->ps->origin, start_o); + VectorCopy (pm->ps->velocity, start_v); + + if ( PM_InReboundHold( pm->ps->legsAnim ) ) + { + gravMod = 0.0f; + } + + if ( PM_SlideMove( gravMod ) == 0 ) { + return; // we got exactly where we wanted to go first try + }//else Bumped into something, see if we can step over it + + if ( pm->gent && pm->gent->client && pm->gent->client->NPC_class == CLASS_VEHICLE && pm->gent->m_pVehicle->m_pVehicleInfo->hoverHeight > 0 ) + {//Hovering vehicles don't do steps + //FIXME: maybe make hovering vehicles go up steps, but not down them? + return; + } + + if ( pm->gent + && pm->gent->client + && (pm->gent->client->NPC_class == CLASS_ATST||pm->gent->client->NPC_class == CLASS_RANCOR) ) + { + isGiant = qtrue; + if ( pm->gent->client->NPC_class == CLASS_RANCOR ) + { + if ( (pm->gent->spawnflags&1) ) + { + stepSize = 64;//hack for Mutant Rancor stepping + } + else + { + stepSize = 48;//hack for Rancor stepping + } + } + else + { + stepSize = 70;//hack for AT-ST stepping, slightly taller than a standing stormtrooper + } + } + else if ( pm->maxs[2] <= 0 ) + {//short little guys can't go up steps... FIXME: just make this a flag for certain NPCs- especially ones that roll? + stepSize = 4; + } + + //Q3Final addition... + VectorCopy(start_o, down); + down[2] -= stepSize; + pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + VectorSet(up, 0, 0, 1); + // never step up when you still have up velocity + if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || + DotProduct(trace.plane.normal, up) < 0.7)) { + return; + } + + if ( !pm->ps->velocity[0] && !pm->ps->velocity[1] ) + {//All our velocity was cancelled sliding + return; + } + + VectorCopy (pm->ps->origin, down_o); + VectorCopy (pm->ps->velocity, down_v); + + VectorCopy (start_o, up); + up[2] += stepSize; + + // test the player position if they were a stepheight higher + + pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); + if ( trace.allsolid || trace.startsolid || trace.fraction == 0) { + if ( pm->debugLevel ) { + Com_Printf("%i:bend can't step\n", c_pmove); + } + return; // can't step up + } + + if ( pm->debugLevel ) + { + G_DebugLine(start_o,trace.endpos,2000,0xffffff,qtrue); + } + +//===Another slidemove forward================================================================================ + // try slidemove from this position + VectorCopy( trace.endpos, pm->ps->origin ); + VectorCopy( start_v, pm->ps->velocity ); + cantStepUpFwd = PM_SlideMove( gravMod ); +//===Another slidemove forward================================================================================ + + if ( pm->debugLevel ) + { + G_DebugLine(trace.endpos,pm->ps->origin,2000,0xffffff,qtrue); + } + //compare the initial slidemove and this slidemove from a step up position + VectorSubtract( down_o, start_o, slideMove ); + VectorSubtract( trace.endpos, pm->ps->origin, stepUpMove ); + + if ( fabs(stepUpMove[0]) < 0.1 && fabs(stepUpMove[1]) < 0.1 && VectorLengthSquared( slideMove ) > VectorLengthSquared( stepUpMove ) ) + { + //slideMove was better, use it + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + } + else + { + qboolean skipStep = qfalse; + // push down the final amount + VectorCopy (pm->ps->origin, down); + down[2] -= stepSize; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + if ( pm->debugLevel ) + { + G_DebugLine(pm->ps->origin,trace.endpos,2000,0xffffff,qtrue); + } + if ( g_stepSlideFix->integer ) + { + if ( pm->ps->clientNum < MAX_CLIENTS + && trace.plane.normal[2] < MIN_WALK_NORMAL ) + {//normal players cannot step up slopes that are too steep to walk on! + vec3_t stepVec; + //okay, the step up ends on a slope that it too steep to step up onto, + //BUT: + //If the step looks like this: + // (B)\__ + // \_____(A) + //Then it might still be okay, so we figure out the slope of the entire move + //from (A) to (B) and if that slope is walk-upabble, then it's okay + VectorSubtract( trace.endpos, down_o, stepVec ); + VectorNormalize( stepVec ); + if ( stepVec[2] > (1.0f-MIN_WALK_NORMAL) ) + { + if ( pm->debugLevel ) + { + G_DebugLine(down_o,trace.endpos,2000,0x0000ff,qtrue); + } + skipStep = qtrue; + } + } + } + + if ( !trace.allsolid + && !skipStep ) //normal players cannot step up slopes that are too steep to walk on! + { + if ( pm->ps->clientNum + && isGiant + && g_entities[trace.entityNum].client + && pm->gent + && pm->gent->client + && pm->gent->client->NPC_class == CLASS_RANCOR ) + {//Rancor don't step on clients + if ( g_stepSlideFix->integer ) + { + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + } + else + { + VectorCopy (start_o, pm->ps->origin); + VectorCopy (start_v, pm->ps->velocity); + } + } + else if ( pm->ps->clientNum + && isGiant + && g_entities[trace.entityNum].client + && g_entities[trace.entityNum].client->playerTeam == pm->gent->client->playerTeam ) + {//AT-ST's don't step up on allies + if ( g_stepSlideFix->integer ) + { + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + } + else + { + VectorCopy (start_o, pm->ps->origin); + VectorCopy (start_v, pm->ps->velocity); + } + } + else + { + VectorCopy( trace.endpos, pm->ps->origin ); + if ( g_stepSlideFix->integer ) + { + if ( trace.fraction < 1.0 ) + { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + } + } + } + else + { + if ( g_stepSlideFix->integer ) + { + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + } + } + if ( !g_stepSlideFix->integer ) + { + if ( trace.fraction < 1.0 ) + { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + } + } + + /* + if(cantStepUpFwd && pm->ps->origin[2] < start_o[2] + stepSize && pm->ps->origin[2] >= start_o[2]) + {//We bumped into something we could not step up + pm->ps->pm_flags |= PMF_BLOCKED; + } + else + {//We did step up, clear the bumped flag + } + */ +#if 0 + // if the down trace can trace back to the original position directly, don't step + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, start_o, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 ) { + // use the original move + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + if ( pm->debugLevel ) { + Com_Printf("%i:bend\n", c_pmove); + } + } else +#endif + { + // use the step move + float delta; + + delta = pm->ps->origin[2] - start_o[2]; + if ( delta > 2 ) { + if ( delta < 7 ) { + PM_AddEvent( EV_STEP_4 ); + } else if ( delta < 11 ) { + PM_AddEvent( EV_STEP_8 ); + } else if ( delta < 15 ) { + PM_AddEvent( EV_STEP_12 ); + } else { + PM_AddEvent( EV_STEP_16 ); + } + } + if ( pm->debugLevel ) { + Com_Printf("%i:stepped\n", c_pmove); + } + } +} + diff --git a/code/game/bg_vehicleload.c b/code/game/bg_vehicleload.c new file mode 100644 index 0000000..8662f81 --- /dev/null +++ b/code/game/bg_vehicleload.c @@ -0,0 +1,1678 @@ +//bg_vehicleLoad.c + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifdef _JK2MP + #include "q_shared.h" + #include "bg_public.h" + #include "bg_vehicles.h" + #include "bg_weapons.h" + + //Could use strap stuff but I don't particularly care at the moment anyway. +#include "../namespace_begin.h" + extern int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); + extern void trap_FS_Read( void *buffer, int len, fileHandle_t f ); + extern void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); + extern void trap_FS_FCloseFile( fileHandle_t f ); + extern int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +#include "../namespace_end.h" +#else + #include "g_local.h" + #define QAGAME +#endif + + +#ifdef _JK2MP +#ifndef QAGAME +#ifndef CGAME +#define WE_ARE_IN_THE_UI +#include "../ui/ui_local.h" +#endif +#endif +#endif + +#ifndef _JK2MP +#include "..\Ratl\string_vs.h" +#endif + +#ifdef QAGAME +extern void G_SetSharedVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern int G_ModelIndex( const char *name ); +extern int G_SoundIndex( const char *name ); + #ifdef _JK2MP + extern int G_EffectIndex( const char *name ); + #endif +#elif CGAME +#include "../namespace_begin.h" +extern qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +extern qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +extern qhandle_t trap_R_RegisterShader( const char *name ); +extern qhandle_t trap_R_RegisterShaderNoMip( const char *name ); +extern int trap_FX_RegisterEffect(const char *file); +extern sfxHandle_t trap_S_RegisterSound( const char *sample); // returns buzz if not found +#include "../namespace_end.h" +#else//UI +#include "../namespace_begin.h" +extern qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +extern qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +extern qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +extern qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found +extern sfxHandle_t trap_S_RegisterSound( const char *sample); // returns buzz if not found +#include "../namespace_end.h" +#endif + +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +// These buffers are filled in with the same contents and then just read from in +// a few places. We only need one copy on Xbox. +#define MAX_VEH_WEAPON_DATA_SIZE 0x4000 +#define MAX_VEHICLE_DATA_SIZE 0x10000 + +#if !defined(_XBOX) || defined(QAGAME) + char VehWeaponParms[MAX_VEH_WEAPON_DATA_SIZE]; + char VehicleParms[MAX_VEHICLE_DATA_SIZE]; + +void BG_ClearVehicleParseParms(void) +{ + //You can't strcat to these forever without clearing them! + VehWeaponParms[0] = 0; + VehicleParms[0] = 0; +} + +#else + extern char VehWeaponParms[MAX_VEH_WEAPON_DATA_SIZE]; + extern char VehicleParms[MAX_VEHICLE_DATA_SIZE]; +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +#ifndef WE_ARE_IN_THE_UI +//These funcs are actually shared in both projects +extern void G_SetAnimalVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetSpeederVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetWalkerVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetFighterVehicleFunctions( vehicleInfo_t *pVehInfo ); +#endif + +vehWeaponInfo_t g_vehWeaponInfo[MAX_VEH_WEAPONS]; +int numVehicleWeapons = 1;//first one is null/default + +vehicleInfo_t g_vehicleInfo[MAX_VEHICLES]; +int numVehicles = 0;//first one is null/default + +// Used by Xbox to reset all the kruft between levels +void BG_ClearVehicles(void) +{ + numVehicleWeapons = 1; + memset( g_vehWeaponInfo, 0, sizeof(g_vehWeaponInfo) ); + + numVehicles = 0; + memset( g_vehicleInfo, 0, sizeof(g_vehicleInfo) ); +} + +void BG_VehicleLoadParms( void ); + +typedef enum { + VF_IGNORE, + VF_INT, + VF_FLOAT, + VF_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + VF_VECTOR, + VF_BOOL, + VF_VEHTYPE, + VF_ANIM, + VF_WEAPON, // take string, resolve into index into VehWeaponParms + VF_MODEL, // take the string, get the G_ModelIndex + VF_MODEL_CLIENT, // (cgame only) take the string, get the G_ModelIndex + VF_EFFECT, // take the string, get the G_EffectIndex + VF_EFFECT_CLIENT, // (cgame only) take the string, get the index + VF_SHADER, // (cgame only) take the string, call trap_R_RegisterShader + VF_SHADER_NOMIP,// (cgame only) take the string, call trap_R_RegisterShaderNoMip + VF_SOUND, // take the string, get the G_SoundIndex + VF_SOUND_CLIENT // (cgame only) take the string, get the index +} vehFieldType_t; + +typedef struct +{ + char *name; + int ofs; + vehFieldType_t type; +} vehField_t; + +vehField_t vehWeaponFields[NUM_VWEAP_PARMS] = +{ + {"name", VWFOFS(name), VF_LSTRING}, //unique name of the vehicle + {"projectile", VWFOFS(bIsProjectile), VF_BOOL}, //traceline or entity? + {"hasGravity", VWFOFS(bHasGravity), VF_BOOL}, //if a projectile, drops + {"ionWeapon", VWFOFS(bIonWeapon), VF_BOOL}, //disables ship shields and sends them out of control + {"saberBlockable", VWFOFS(bSaberBlockable), VF_BOOL}, //lightsabers can deflect this projectile + {"muzzleFX", VWFOFS(iMuzzleFX), VF_EFFECT_CLIENT}, //index of Muzzle Effect + {"model", VWFOFS(iModel), VF_MODEL_CLIENT}, //handle to the model used by this projectile + {"shotFX", VWFOFS(iShotFX), VF_EFFECT_CLIENT}, //index of Shot Effect + {"impactFX", VWFOFS(iImpactFX), VF_EFFECT_CLIENT}, //index of Impact Effect + {"g2MarkShader", VWFOFS(iG2MarkShaderHandle), VF_SHADER}, //index of shader to use for G2 marks made on other models when hit by this projectile + {"g2MarkSize", VWFOFS(fG2MarkSize), VF_FLOAT}, //size (diameter) of the ghoul2 mark + {"loopSound", VWFOFS(iLoopSound), VF_SOUND_CLIENT}, //index of loopSound + {"speed", VWFOFS(fSpeed), VF_FLOAT}, //speed of projectile/range of traceline + {"homing", VWFOFS(fHoming), VF_FLOAT}, //0.0 = not homing, 0.5 = half vel to targ, half cur vel, 1.0 = all vel to targ + {"lockOnTime", VWFOFS(iLockOnTime), VF_INT}, //0 = no lock time needed, else # of ms needed to lock on + {"damage", VWFOFS(iDamage), VF_INT}, //damage done when traceline or projectile directly hits target + {"splashDamage", VWFOFS(iSplashDamage), VF_INT},//damage done to ents in splashRadius of end of traceline or projectile origin on impact + {"splashRadius", VWFOFS(fSplashRadius), VF_FLOAT},//radius that ent must be in to take splashDamage (linear fall-off) + {"ammoPerShot", VWFOFS(iAmmoPerShot), VF_INT},//how much "ammo" each shot takes + {"health", VWFOFS(iHealth), VF_INT}, //if non-zero, projectile can be shot, takes this much damage before being destroyed + {"width", VWFOFS(fWidth), VF_FLOAT}, //width of traceline or bounding box of projecile (non-rotating!) + {"height", VWFOFS(fHeight), VF_FLOAT}, //height of traceline or bounding box of projecile (non-rotating!) + {"lifetime", VWFOFS(iLifeTime), VF_INT}, //removes itself after this amount of time + {"explodeOnExpire", VWFOFS(bExplodeOnExpire), VF_BOOL}, //when iLifeTime is up, explodes rather than simply removing itself +}; + +static qboolean BG_ParseVehWeaponParm( vehWeaponInfo_t *vehWeapon, char *parmName, char *pValue ) +{ + int i; + vec3_t vec; + byte *b = (byte *)vehWeapon; + int _iFieldsRead = 0; + vehicleType_t vehType; + char value[1024]; + + Q_strncpyz( value, pValue, sizeof(value) ); + + // Loop through possible parameters + for ( i = 0; i < NUM_VWEAP_PARMS; i++ ) + { + if ( vehWeaponFields[i].name && !Q_stricmp( vehWeaponFields[i].name, parmName ) ) + { + // found it + switch( vehWeaponFields[i].type ) + { + case VF_INT: + *(int *)(b+vehWeaponFields[i].ofs) = atoi(value); + break; + case VF_FLOAT: + *(float *)(b+vehWeaponFields[i].ofs) = atof(value); + break; + case VF_LSTRING: // string on disk, pointer in memory, TAG_LEVEL + if (!*(char **)(b+vehWeaponFields[i].ofs)) + { //just use 1024 bytes in case we want to write over the string +#ifdef _JK2MP + *(char **)(b+vehWeaponFields[i].ofs) = (char *)BG_Alloc(1024);//(char *)BG_Alloc(strlen(value)); + strcpy(*(char **)(b+vehWeaponFields[i].ofs), value); +#else + (*(char **)(b+vehWeaponFields[i].ofs)) = G_NewString( value ); +#endif + } + + break; + case VF_VECTOR: + _iFieldsRead = sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + assert(_iFieldsRead==3 ); + if (_iFieldsRead!=3) + { + Com_Printf (S_COLOR_YELLOW"BG_ParseVehWeaponParm: VEC3 sscanf() failed to read 3 floats ('angle' key bug?)\n"); + } + ((float *)(b+vehWeaponFields[i].ofs))[0] = vec[0]; + ((float *)(b+vehWeaponFields[i].ofs))[1] = vec[1]; + ((float *)(b+vehWeaponFields[i].ofs))[2] = vec[2]; + break; + case VF_BOOL: + *(qboolean *)(b+vehWeaponFields[i].ofs) = (qboolean)(atof(value)!=0); + break; + case VF_VEHTYPE: + vehType = (vehicleType_t)GetIDForString( VehicleTable, value ); + *(vehicleType_t *)(b+vehWeaponFields[i].ofs) = vehType; + break; + case VF_ANIM: + { + int anim = GetIDForString( animTable, value ); + *(int *)(b+vehWeaponFields[i].ofs) = anim; + } + break; + case VF_WEAPON: // take string, resolve into index into VehWeaponParms + //*(int *)(b+vehWeaponFields[i].ofs) = VEH_VehWeaponIndexForName( value ); + break; + case VF_MODEL:// take the string, get the G_ModelIndex +#ifdef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_MODEL_CLIENT: // (MP cgame only) take the string, get the G_ModelIndex +#ifndef _JK2MP + *(int *)(b+vehWeaponFields[i].ofs) = G_ModelIndex( value ); +#elif QAGAME + //*(int *)(b+vehWeaponFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_EFFECT: // take the string, get the G_EffectIndex +#ifdef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_EFFECT_CLIENT: // (MP cgame only) take the string, get the index +#ifndef _JK2MP + *(int *)(b+vehWeaponFields[i].ofs) = G_EffectIndex( value ); +#elif QAGAME + //*(int *)(b+vehWeaponFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_SHADER: // (cgame only) take the string, call trap_R_RegisterShader +#ifdef WE_ARE_IN_THE_UI + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#elif CGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterShader( value ); +#endif + break; + case VF_SHADER_NOMIP:// (cgame only) take the string, call trap_R_RegisterShaderNoMip +#ifndef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#endif + break; + case VF_SOUND: // take the string, get the G_SoundIndex +#ifdef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + case VF_SOUND_CLIENT: // (MP cgame only) take the string, get the index +#ifndef _JK2MP + *(int *)(b+vehWeaponFields[i].ofs) = G_SoundIndex( value ); +#elif QAGAME + //*(int *)(b+vehWeaponFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + default: + //Unknown type? + return qfalse; + break; + } + break; + } + } + if ( i == NUM_VWEAP_PARMS ) + { + return qfalse; + } + else + { + return qtrue; + } +} + +int VEH_LoadVehWeapon( const char *vehWeaponName ) +{//load up specified vehWeapon and save in array: g_vehWeaponInfo + const char *token; + char parmName[128];//we'll assume that no parm name is longer than 128 + char *value; + const char *p; + vehWeaponInfo_t *vehWeapon = NULL; + + //BG_VehWeaponSetDefaults( &g_vehWeaponInfo[0] );//set the first vehicle to default data + + //try to parse data out + p = VehWeaponParms; + +#ifdef _JK2MP + COM_BeginParseSession("vehWeapons"); +#else + COM_BeginParseSession(); +#endif + + vehWeapon = &g_vehWeaponInfo[numVehicleWeapons]; + // look for the right vehicle weapon + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return qfalse; + } + + if ( !Q_stricmp( token, vehWeaponName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { + return qfalse; + } + + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + {//barf + return VEH_WEAPON_NONE; + } + + if ( Q_stricmp( token, "{" ) != 0 ) + { + return VEH_WEAPON_NONE; + } + + // parse the vehWeapon info block + while ( 1 ) + { + SkipRestOfLine( &p ); + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing Vehicle Weapon '%s'\n", vehWeaponName ); + return VEH_WEAPON_NONE; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + Q_strncpyz( parmName, token, sizeof(parmName) ); + value = COM_ParseExt( &p, qtrue ); + if ( !value || !value[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Vehicle Weapon token '%s' has no value!\n", parmName ); + } + else + { + if ( !BG_ParseVehWeaponParm( vehWeapon, parmName, value ) ) + { + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle Weapon key/value pair '%s','%s'!\n", parmName, value ); + } + } + } + if ( vehWeapon->fHoming ) + {//all lock-on weapons use these 2 sounds +#ifdef QAGAME + //Hmm, no need fo have server register this, is there? + //G_SoundIndex( "sound/weapons/torpedo/tick.wav" ); + //G_SoundIndex( "sound/weapons/torpedo/lock.wav" ); +#elif CGAME + trap_S_RegisterSound( "sound/vehicles/weapons/common/tick.wav" ); + trap_S_RegisterSound( "sound/vehicles/weapons/common/lock.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm1.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm2.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm3.wav" ); +#else + trap_S_RegisterSound( "sound/vehicles/weapons/common/tick.wav" ); + trap_S_RegisterSound( "sound/vehicles/weapons/common/lock.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm1.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm2.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm3.wav" ); +#endif + } + return (numVehicleWeapons++); +} + +int VEH_VehWeaponIndexForName( const char *vehWeaponName ) +{ + int vw; + if ( !vehWeaponName || !vehWeaponName[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Trying to read Vehicle Weapon with no name!\n" ); + return VEH_WEAPON_NONE; + } + for ( vw = VEH_WEAPON_BASE; vw < numVehicleWeapons; vw++ ) + { + if ( g_vehWeaponInfo[vw].name + && Q_stricmp( g_vehWeaponInfo[vw].name, vehWeaponName ) == 0 ) + {//already loaded this one + return vw; + } + } + //haven't loaded it yet + if ( vw >= MAX_VEH_WEAPONS ) + {//no more room! + Com_Printf( S_COLOR_RED"ERROR: Too many Vehicle Weapons (max 16), aborting load on %s!\n", vehWeaponName ); + return VEH_WEAPON_NONE; + } + //we have room for another one, load it up and return the index + //HMM... should we not even load the .vwp file until we want to? + vw = VEH_LoadVehWeapon( vehWeaponName ); + if ( vw == VEH_WEAPON_NONE ) + { + Com_Printf( S_COLOR_RED"ERROR: Could not find Vehicle Weapon %s!\n", vehWeaponName ); + } + return vw; +} + +vehField_t vehicleFields[] = +{ + {"name", VFOFS(name), VF_LSTRING}, //unique name of the vehicle + + //general data + {"type", VFOFS(type), VF_VEHTYPE}, //what kind of vehicle + {"numHands", VFOFS(numHands), VF_INT}, //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + {"lookPitch", VFOFS(lookPitch), VF_FLOAT}, //How far you can look up and down off the forward of the vehicle + {"lookYaw", VFOFS(lookYaw), VF_FLOAT}, //How far you can look left and right off the forward of the vehicle + {"length", VFOFS(length), VF_FLOAT}, //how long it is - used for body length traces when turning/moving? + {"width", VFOFS(width), VF_FLOAT}, //how wide it is - used for body length traces when turning/moving? + {"height", VFOFS(height), VF_FLOAT}, //how tall it is - used for body length traces when turning/moving? + {"centerOfGravity", VFOFS(centerOfGravity), VF_VECTOR},//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats + {"speedMax", VFOFS(speedMax), VF_FLOAT}, //top speed + {"turboSpeed", VFOFS(turboSpeed), VF_FLOAT}, //turbo speed + {"speedMin", VFOFS(speedMin), VF_FLOAT}, //if < 0, can go in reverse + {"speedIdle", VFOFS(speedIdle), VF_FLOAT}, //what speed it drifts to when no accel/decel input is given + {"accelIdle", VFOFS(accelIdle), VF_FLOAT}, //if speedIdle > 0, how quickly it goes up to that speed + {"acceleration", VFOFS(acceleration), VF_FLOAT}, //when pressing on accelerator + {"decelIdle", VFOFS(decelIdle), VF_FLOAT}, //when giving no input, how quickly it drops to speedIdle + {"throttleSticks", VFOFS(throttleSticks), VF_BOOL},//if true, speed stays at whatever you accel/decel to, unless you turbo or brake + {"strafePerc", VFOFS(strafePerc), VF_FLOAT}, //multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + {"bankingSpeed", VFOFS(bankingSpeed), VF_FLOAT}, //how quickly it pitches and rolls (not under player control) + {"pitchLimit", VFOFS(pitchLimit), VF_FLOAT}, //how far it can roll forward or backward + {"rollLimit", VFOFS(rollLimit), VF_FLOAT}, //how far it can roll to either side + {"braking", VFOFS(braking), VF_FLOAT}, //when pressing on decelerator + {"mouseYaw", VFOFS(mouseYaw), VF_FLOAT}, // The mouse yaw override. + {"mousePitch", VFOFS(mousePitch), VF_FLOAT}, // The mouse yaw override. + {"turningSpeed", VFOFS(turningSpeed), VF_FLOAT}, //how quickly you can turn + {"turnWhenStopped", VFOFS(turnWhenStopped), VF_BOOL},//whether or not you can turn when not moving + {"traction", VFOFS(traction), VF_FLOAT}, //how much your command input affects velocity + {"friction", VFOFS(friction), VF_FLOAT}, //how much velocity is cut on its own + {"maxSlope", VFOFS(maxSlope), VF_FLOAT}, //the max slope that it can go up with control + {"speedDependantTurning", VFOFS(speedDependantTurning), VF_BOOL},//vehicle turns faster the faster it's going + + //durability stats + {"mass", VFOFS(mass), VF_INT}, //for momentum and impact force (player mass is 10) + {"armor", VFOFS(armor), VF_INT}, //total points of damage it can take + {"shields", VFOFS(shields), VF_INT}, //energy shield damage points + {"shieldRechargeMS", VFOFS(shieldRechargeMS), VF_INT},//energy shield milliseconds per point recharged + {"toughness", VFOFS(toughness), VF_FLOAT}, //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + {"malfunctionArmorLevel", VFOFS(malfunctionArmorLevel), VF_INT},//when armor drops to or below this point, start malfunctioning + {"surfDestruction", VFOFS(surfDestruction), VF_INT}, + + //visuals & sounds + {"model", VFOFS(model), VF_LSTRING}, //what model to use - if make it an NPC's primary model, don't need this? + {"skin", VFOFS(skin), VF_LSTRING}, //what skin to use - if make it an NPC's primary model, don't need this? + {"g2radius", VFOFS(g2radius), VF_INT}, //render radius (really diameter, but...) for the ghoul2 model + {"riderAnim", VFOFS(riderAnim), VF_ANIM}, //what animation the rider uses + {"droidNPC", VFOFS(droidNPC), VF_LSTRING}, //NPC to attach to *droidunit tag (if it exists in the model) + +#ifdef _JK2MP + {"radarIcon", VFOFS(radarIconHandle), VF_SHADER_NOMIP}, //what icon to show on radar in MP + {"dmgIndicFrame", VFOFS(dmgIndicFrameHandle), VF_SHADER_NOMIP}, //what image to use for the frame of the damage indicator + {"dmgIndicShield", VFOFS(dmgIndicShieldHandle), VF_SHADER_NOMIP},//what image to use for the shield of the damage indicator + {"dmgIndicBackground", VFOFS(dmgIndicBackgroundHandle), VF_SHADER_NOMIP},//what image to use for the background of the damage indicator + {"icon_front", VFOFS(iconFrontHandle), VF_SHADER_NOMIP}, //what image to use for the front of the ship on the damage indicator + {"icon_back", VFOFS(iconBackHandle), VF_SHADER_NOMIP}, //what image to use for the back of the ship on the damage indicator + {"icon_right", VFOFS(iconRightHandle), VF_SHADER_NOMIP}, //what image to use for the right of the ship on the damage indicator + {"icon_left", VFOFS(iconLeftHandle), VF_SHADER_NOMIP}, //what image to use for the left of the ship on the damage indicator + {"crosshairShader", VFOFS(crosshairShaderHandle), VF_SHADER_NOMIP}, //what image to use as the crosshair + {"shieldShader", VFOFS(shieldShaderHandle), VF_SHADER}, //What shader to use when drawing the shield shell + + //individual "area" health -rww + {"health_front", VFOFS(health_front), VF_INT}, + {"health_back", VFOFS(health_back), VF_INT}, + {"health_right", VFOFS(health_right), VF_INT}, + {"health_left", VFOFS(health_left), VF_INT}, +#else + {"radarIcon", 0, VF_IGNORE}, //what icon to show on radar in MP +#endif + + {"soundOn", VFOFS(soundOn), VF_SOUND},//sound to play when get on it + {"soundOff", VFOFS(soundOff), VF_SOUND},//sound to play when get off + {"soundLoop", VFOFS(soundLoop), VF_SOUND},//sound to loop while riding it + {"soundTakeOff", VFOFS(soundTakeOff), VF_SOUND},//sound to play when ship takes off + {"soundEngineStart",VFOFS(soundEngineStart),VF_SOUND_CLIENT},//sound to play when ship's thrusters first activate + {"soundSpin", VFOFS(soundSpin), VF_SOUND},//sound to loop while spiraling out of control + {"soundTurbo", VFOFS(soundTurbo), VF_SOUND},//sound to play when turbo/afterburner kicks in + {"soundHyper", VFOFS(soundHyper), VF_SOUND_CLIENT},//sound to play when hits hyperspace + {"soundLand", VFOFS(soundLand), VF_SOUND},//sound to play when ship lands + {"soundFlyBy", VFOFS(soundFlyBy), VF_SOUND_CLIENT},//sound to play when they buzz you + {"soundFlyBy2", VFOFS(soundFlyBy2), VF_SOUND_CLIENT},//alternate sound to play when they buzz you + {"soundShift1", VFOFS(soundShift1), VF_SOUND},//sound to play when changing speeds + {"soundShift2", VFOFS(soundShift2), VF_SOUND},//sound to play when changing speeds + {"soundShift3", VFOFS(soundShift3), VF_SOUND},//sound to play when changing speeds + {"soundShift4", VFOFS(soundShift4), VF_SOUND},//sound to play when changing speeds + + {"exhaustFX", VFOFS(iExhaustFX), VF_EFFECT_CLIENT}, //exhaust effect, played from "*exhaust" bolt(s) + {"turboFX", VFOFS(iTurboFX), VF_EFFECT_CLIENT}, //turbo exhaust effect, played from "*exhaust" bolt(s) when ship is in "turbo" mode + {"turboStartFX", VFOFS(iTurboStartFX), VF_EFFECT}, //turbo start effect, played from "*exhaust" bolt(s) when ship is in "turbo" mode + {"trailFX", VFOFS(iTrailFX), VF_EFFECT_CLIENT}, //trail effect, played from "*trail" bolt(s) + {"impactFX", VFOFS(iImpactFX), VF_EFFECT_CLIENT}, //impact effect, for when it bumps into something + {"explodeFX", VFOFS(iExplodeFX), VF_EFFECT}, //explosion effect, for when it blows up (should have the sound built into explosion effect) + {"wakeFX", VFOFS(iWakeFX), VF_EFFECT_CLIENT}, //effect it makes when going across water + {"dmgFX", VFOFS(iDmgFX), VF_EFFECT_CLIENT}, //effect to play on damage from a weapon or something +#ifdef _JK2MP + {"injureFX", VFOFS(iInjureFX), VF_EFFECT_CLIENT}, //effect to play on partially damaged ship surface + {"noseFX", VFOFS(iNoseFX), VF_EFFECT_CLIENT}, //effect for nose piece flying away when blown off + {"lwingFX", VFOFS(iLWingFX), VF_EFFECT_CLIENT}, //effect for left wing piece flying away when blown off + {"rwingFX", VFOFS(iRWingFX), VF_EFFECT_CLIENT}, //effect for right wing piece flying away when blown off +#else + {"armorLowFX", VFOFS(iArmorLowFX), VF_EFFECT_CLIENT}, //effect to play on damage from a weapon or something + {"armorGoneFX", VFOFS(iArmorGoneFX), VF_EFFECT_CLIENT}, //effect to play on damage from a weapon or something +#endif + + // Weapon stuff: + {"weap1", VFOFS(weapon[0].ID), VF_WEAPON}, //weapon used when press fire + {"weap2", VFOFS(weapon[1].ID), VF_WEAPON},//weapon used when press alt-fire + // The delay between shots for this weapon. + {"weap1Delay", VFOFS(weapon[0].delay), VF_INT}, + {"weap2Delay", VFOFS(weapon[1].delay), VF_INT}, + // Whether or not all the muzzles for each weapon can be linked together (linked delay = weapon delay * number of muzzles linked!) + {"weap1Link", VFOFS(weapon[0].linkable), VF_INT}, + {"weap2Link", VFOFS(weapon[1].linkable), VF_INT}, + // Whether or not to auto-aim the projectiles at the thing under the crosshair when we fire + {"weap1Aim", VFOFS(weapon[0].aimCorrect), VF_BOOL}, + {"weap2Aim", VFOFS(weapon[1].aimCorrect), VF_BOOL}, + //maximum ammo + {"weap1AmmoMax", VFOFS(weapon[0].ammoMax), VF_INT}, + {"weap2AmmoMax", VFOFS(weapon[1].ammoMax), VF_INT}, + //ammo recharge rate - milliseconds per unit (minimum of 100, which is 10 ammo per second) + {"weap1AmmoRechargeMS", VFOFS(weapon[0].ammoRechargeMS), VF_INT}, + {"weap2AmmoRechargeMS", VFOFS(weapon[1].ammoRechargeMS), VF_INT}, + //sound to play when out of ammo (plays default "no ammo" sound if none specified) + {"weap1SoundNoAmmo", VFOFS(weapon[0].soundNoAmmo), VF_SOUND_CLIENT},//sound to play when try to fire weapon 1 with no ammo + {"weap2SoundNoAmmo", VFOFS(weapon[1].soundNoAmmo), VF_SOUND_CLIENT},//sound to play when try to fire weapon 2 with no ammo + + // Which weapon a muzzle fires (has to match one of the weapons this vehicle has). + {"weapMuzzle1", VFOFS(weapMuzzle[0]), VF_WEAPON}, + {"weapMuzzle2", VFOFS(weapMuzzle[1]), VF_WEAPON}, + {"weapMuzzle3", VFOFS(weapMuzzle[2]), VF_WEAPON}, + {"weapMuzzle4", VFOFS(weapMuzzle[3]), VF_WEAPON}, + {"weapMuzzle5", VFOFS(weapMuzzle[4]), VF_WEAPON}, + {"weapMuzzle6", VFOFS(weapMuzzle[5]), VF_WEAPON}, + {"weapMuzzle7", VFOFS(weapMuzzle[6]), VF_WEAPON}, + {"weapMuzzle8", VFOFS(weapMuzzle[7]), VF_WEAPON}, + {"weapMuzzle9", VFOFS(weapMuzzle[8]), VF_WEAPON}, + {"weapMuzzle10", VFOFS(weapMuzzle[9]), VF_WEAPON}, + + // The max height before this ship (?) starts (auto)landing. + {"landingHeight", VFOFS(landingHeight), VF_FLOAT}, + + //other misc stats + {"gravity", VFOFS(gravity), VF_INT}, //normal is 800 + {"hoverHeight", VFOFS(hoverHeight), VF_FLOAT}, //if 0, it's a ground vehicle + {"hoverStrength", VFOFS(hoverStrength), VF_FLOAT}, //how hard it pushes off ground when less than hover height... causes "bounce", like shocks + {"waterProof", VFOFS(waterProof), VF_BOOL}, //can drive underwater if it has to + {"bouyancy", VFOFS(bouyancy), VF_FLOAT}, //when in water, how high it floats (1 is neutral bouyancy) + {"fuelMax", VFOFS(fuelMax), VF_INT}, //how much fuel it can hold (capacity) + {"fuelRate", VFOFS(fuelRate), VF_INT}, //how quickly is uses up fuel + {"turboDuration", VFOFS(turboDuration), VF_INT}, //how long turbo lasts + {"turboRecharge", VFOFS(turboRecharge), VF_INT}, //how long turbo takes to recharge + {"visibility", VFOFS(visibility), VF_INT}, //for sight alerts + {"loudness", VFOFS(loudness), VF_INT}, //for sound alerts + {"explosionRadius", VFOFS(explosionRadius), VF_FLOAT},//range of explosion + {"explosionDamage", VFOFS(explosionDamage), VF_INT},//damage of explosion + + //new stuff + {"maxPassengers", VFOFS(maxPassengers), VF_INT}, // The max number of passengers this vehicle may have (Default = 0). + {"hideRider", VFOFS(hideRider), VF_BOOL}, // rider (and passengers?) should not be drawn + {"killRiderOnDeath", VFOFS(killRiderOnDeath), VF_BOOL},//if rider is on vehicle when it dies, they should die + {"flammable", VFOFS(flammable), VF_BOOL}, //whether or not the vehicle should catch on fire before it explodes + {"explosionDelay", VFOFS(explosionDelay), VF_INT}, //how long the vehicle should be on fire/dying before it explodes + //camera stuff + {"cameraOverride", VFOFS(cameraOverride), VF_BOOL},//override the third person camera with the below values - normal is 0 (off) + {"cameraRange", VFOFS(cameraRange), VF_FLOAT}, //how far back the camera should be - normal is 80 + {"cameraVertOffset", VFOFS(cameraVertOffset), VF_FLOAT},//how high over the vehicle origin the camera should be - normal is 16 + {"cameraHorzOffset", VFOFS(cameraHorzOffset), VF_FLOAT},//how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + {"cameraPitchOffset", VFOFS(cameraPitchOffset), VF_FLOAT},//a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + {"cameraFOV", VFOFS(cameraFOV), VF_FLOAT}, //third person camera FOV, default is 80 + {"cameraAlpha", VFOFS(cameraAlpha), VF_FLOAT}, //fade out the vehicle to this alpha (0.1-1.0f) if it's in the way of the crosshair + {"cameraPitchDependantVertOffset", VFOFS(cameraPitchDependantVertOffset), VF_BOOL}, //use the hacky AT-ST pitch dependant vertical offset +//===TURRETS=========================================================================== + //Turret 1 + {"turret1Weap", VFOFS(turret[0].iWeapon), VF_WEAPON}, + {"turret1Delay", VFOFS(turret[0].iDelay), VF_INT}, + {"turret1AmmoMax", VFOFS(turret[0].iAmmoMax), VF_INT}, + {"turret1AmmoRechargeMS", VFOFS(turret[0].iAmmoRechargeMS), VF_INT}, + {"turret1YawBone", VFOFS(turret[0].yawBone), VF_LSTRING}, + {"turret1PitchBone", VFOFS(turret[0].pitchBone), VF_LSTRING}, + {"turret1YawAxis", VFOFS(turret[0].yawAxis), VF_INT}, + {"turret1PitchAxis", VFOFS(turret[0].pitchAxis), VF_INT}, + {"turret1ClampYawL", VFOFS(turret[0].yawClampLeft), VF_FLOAT}, //how far the turret is allowed to turn left + {"turret1ClampYawR", VFOFS(turret[0].yawClampRight), VF_FLOAT}, //how far the turret is allowed to turn right + {"turret1ClampPitchU", VFOFS(turret[0].pitchClampUp), VF_FLOAT}, //how far the turret is allowed to title up + {"turret1ClampPitchD", VFOFS(turret[0].pitchClampDown), VF_FLOAT}, //how far the turret is allowed to tilt down + {"turret1Muzzle1", VFOFS(turret[0].iMuzzle[0]), VF_INT}, + {"turret1Muzzle2", VFOFS(turret[0].iMuzzle[1]), VF_INT}, + {"turret1TurnSpeed", VFOFS(turret[0].fTurnSpeed), VF_FLOAT}, + {"turret1AI", VFOFS(turret[0].bAI), VF_BOOL}, + {"turret1AILead", VFOFS(turret[0].bAILead), VF_BOOL}, + {"turret1AIRange", VFOFS(turret[0].fAIRange), VF_FLOAT}, + {"turret1PassengerNum", VFOFS(turret[0].passengerNum), VF_INT},//which number passenger can control this turret + {"turret1GunnerViewTag", VFOFS(turret[0].gunnerViewTag), VF_LSTRING}, + + //Turret 2 + {"turret2Weap", VFOFS(turret[1].iWeapon), VF_WEAPON}, + {"turret2Delay", VFOFS(turret[1].iDelay), VF_INT}, + {"turret2AmmoMax", VFOFS(turret[1].iAmmoMax), VF_INT}, + {"turret2AmmoRechargeMS", VFOFS(turret[1].iAmmoRechargeMS), VF_INT}, + {"turret2YawBone", VFOFS(turret[1].yawBone), VF_LSTRING}, + {"turret2PitchBone", VFOFS(turret[1].pitchBone), VF_LSTRING}, + {"turret2YawAxis", VFOFS(turret[1].yawAxis), VF_INT}, + {"turret2PitchAxis", VFOFS(turret[1].pitchAxis), VF_INT}, + {"turret2ClampYawL", VFOFS(turret[1].yawClampLeft), VF_FLOAT}, //how far the turret is allowed to turn left + {"turret2ClampYawR", VFOFS(turret[1].yawClampRight), VF_FLOAT}, //how far the turret is allowed to turn right + {"turret2ClampPitchU", VFOFS(turret[1].pitchClampUp), VF_FLOAT}, //how far the turret is allowed to title up + {"turret2ClampPitchD", VFOFS(turret[1].pitchClampDown), VF_FLOAT}, //how far the turret is allowed to tilt down + {"turret2Muzzle1", VFOFS(turret[1].iMuzzle[0]), VF_INT}, + {"turret2Muzzle2", VFOFS(turret[1].iMuzzle[1]), VF_INT}, + {"turret2TurnSpeed", VFOFS(turret[1].fTurnSpeed), VF_FLOAT}, + {"turret2AI", VFOFS(turret[1].bAI), VF_BOOL}, + {"turret2AILead", VFOFS(turret[1].bAILead), VF_BOOL}, + {"turret2AIRange", VFOFS(turret[1].fAIRange), VF_FLOAT}, + {"turret2PassengerNum", VFOFS(turret[1].passengerNum), VF_INT},//which number passenger can control this turret + {"turret2GunnerViewTag", VFOFS(turret[1].gunnerViewTag), VF_LSTRING}, +//===END TURRETS=========================================================================== + //terminating entry + {0, -1, VF_INT} +}; + +stringID_table_t VehicleTable[VH_NUM_VEHICLES+1] = +{ + ENUM2STRING(VH_NONE), + ENUM2STRING(VH_WALKER), //something you ride inside of, it walks like you, like an AT-ST + ENUM2STRING(VH_FIGHTER), //something you fly inside of, like an X-Wing or TIE fighter + ENUM2STRING(VH_SPEEDER), //something you ride on that hovers, like a speeder or swoop + ENUM2STRING(VH_ANIMAL), //animal you ride on top of that walks, like a tauntaun + ENUM2STRING(VH_FLIER), //animal you ride on top of that flies, like a giant mynoc? + 0, -1 +}; + +// Setup the shared functions (one's that all vehicles would generally use). +void BG_SetSharedVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + //only do the whole thing if we're on game + G_SetSharedVehicleFunctions(pVehInfo); +#endif + +#ifndef WE_ARE_IN_THE_UI + switch( pVehInfo->type ) + { + case VH_SPEEDER: + G_SetSpeederVehicleFunctions( pVehInfo ); + break; + case VH_ANIMAL: + G_SetAnimalVehicleFunctions( pVehInfo ); + break; + case VH_FIGHTER: + G_SetFighterVehicleFunctions( pVehInfo ); + break; + case VH_WALKER: + G_SetWalkerVehicleFunctions( pVehInfo ); + break; + } +#endif +} + +void BG_VehicleSetDefaults( vehicleInfo_t *vehicle ) +{ + memset(vehicle, 0, sizeof(vehicleInfo_t)); +/* +#if _JK2MP + if (!vehicle->name) + { + vehicle->name = (char *)BG_Alloc(1024); + } + strcpy(vehicle->name, "default"); +#else + vehicle->name = G_NewString( "default" ); +#endif + + //general data + vehicle->type = VH_SPEEDER; //what kind of vehicle + //FIXME: no saber or weapons if numHands = 2, should switch to speeder weapon, no attack anim on player + vehicle->numHands = 0; //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + vehicle->lookPitch = 0; //How far you can look up and down off the forward of the vehicle + vehicle->lookYaw = 5; //How far you can look left and right off the forward of the vehicle + vehicle->length = 0; //how long it is - used for body length traces when turning/moving? + vehicle->width = 0; //how wide it is - used for body length traces when turning/moving? + vehicle->height = 0; //how tall it is - used for body length traces when turning/moving? + VectorClear( vehicle->centerOfGravity );//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats - note: these are DESIRED speed, not actual current speed/velocity + vehicle->speedMax = VEH_DEFAULT_SPEED_MAX; //top speed + vehicle->turboSpeed = 0; //turboBoost + vehicle->speedMin = 0; //if < 0, can go in reverse + vehicle->speedIdle = 0; //what speed it drifts to when no accel/decel input is given + vehicle->accelIdle = 0; //if speedIdle > 0, how quickly it goes up to that speed + vehicle->acceleration = VEH_DEFAULT_ACCEL; //when pressing on accelerator (1/2 this when going in reverse) + vehicle->decelIdle = VEH_DEFAULT_DECEL; //when giving no input, how quickly it desired speed drops to speedIdle + vehicle->strafePerc = VEH_DEFAULT_STRAFE_PERC;//multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + vehicle->bankingSpeed = VEH_DEFAULT_BANKING_SPEED; //how quickly it pitches and rolls (not under player control) + vehicle->rollLimit = VEH_DEFAULT_ROLL_LIMIT; //how far it can roll to either side + vehicle->pitchLimit = VEH_DEFAULT_PITCH_LIMIT; //how far it can pitch forward or backward + vehicle->braking = VEH_DEFAULT_BRAKING; //when pressing on decelerator (backwards) + vehicle->turningSpeed = VEH_DEFAULT_TURNING_SPEED; //how quickly you can turn + vehicle->turnWhenStopped = qfalse; //whether or not you can turn when not moving + vehicle->traction = VEH_DEFAULT_TRACTION; //how much your command input affects velocity + vehicle->friction = VEH_DEFAULT_FRICTION; //how much velocity is cut on its own + vehicle->maxSlope = VEH_DEFAULT_MAX_SLOPE; //the max slope that it can go up with control + + //durability stats + vehicle->mass = VEH_DEFAULT_MASS; //for momentum and impact force (player mass is 10) + vehicle->armor = VEH_DEFAULT_MAX_ARMOR; //total points of damage it can take + vehicle->toughness = VEH_DEFAULT_TOUGHNESS; //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + vehicle->malfunctionArmorLevel = 0; //when armor drops to or below this point, start malfunctioning + + //visuals & sounds + //vehicle->model = "models/map_objects/ships/swoop.md3"; //what model to use - if make it an NPC's primary model, don't need this? + if (!vehicle->model) + { + vehicle->model = (char *)BG_Alloc(1024); + } + strcpy(vehicle->model, "models/map_objects/ships/swoop.md3"); + + vehicle->modelIndex = 0; //set internally, not until this vehicle is spawned into the level + vehicle->skin = NULL; //what skin to use - if make it an NPC's primary model, don't need this? + vehicle->riderAnim = BOTH_GUNSIT1; //what animation the rider uses + + vehicle->soundOn = NULL; //sound to play when get on it + vehicle->soundLoop = NULL; //sound to loop while riding it + vehicle->soundOff = NULL; //sound to play when get off + vehicle->exhaustFX = NULL; //exhaust effect, played from "*exhaust" bolt(s) + vehicle->trailFX = NULL; //trail effect, played from "*trail" bolt(s) + vehicle->impactFX = NULL; //explosion effect, for when it blows up (should have the sound built into explosion effect) + vehicle->explodeFX = NULL; //explosion effect, for when it blows up (should have the sound built into explosion effect) + vehicle->wakeFX = NULL; //effect itmakes when going across water + + //other misc stats + vehicle->gravity = VEH_DEFAULT_GRAVITY; //normal is 800 + vehicle->hoverHeight = 0;//VEH_DEFAULT_HOVER_HEIGHT; //if 0, it's a ground vehicle + vehicle->hoverStrength = 0;//VEH_DEFAULT_HOVER_STRENGTH;//how hard it pushes off ground when less than hover height... causes "bounce", like shocks + vehicle->waterProof = qtrue; //can drive underwater if it has to + vehicle->bouyancy = 1.0f; //when in water, how high it floats (1 is neutral bouyancy) + vehicle->fuelMax = 1000; //how much fuel it can hold (capacity) + vehicle->fuelRate = 1; //how quickly is uses up fuel + vehicle->visibility = VEH_DEFAULT_VISIBILITY; //radius for sight alerts + vehicle->loudness = VEH_DEFAULT_LOUDNESS; //radius for sound alerts + vehicle->explosionRadius = VEH_DEFAULT_EXP_RAD; + vehicle->explosionDamage = VEH_DEFAULT_EXP_DMG; + vehicle->maxPassengers = 0; + + //new stuff + vehicle->hideRider = qfalse; // rider (and passengers?) should not be drawn + vehicle->killRiderOnDeath = qfalse; //if rider is on vehicle when it dies, they should die + vehicle->flammable = qfalse; //whether or not the vehicle should catch on fire before it explodes + vehicle->explosionDelay = 0; //how long the vehicle should be on fire/dying before it explodes + //camera stuff + vehicle->cameraOverride = qfalse; //whether or not to use all of the following 3rd person camera override values + vehicle->cameraRange = 0.0f; //how far back the camera should be - normal is 80 + vehicle->cameraVertOffset = 0.0f; //how high over the vehicle origin the camera should be - normal is 16 + vehicle->cameraHorzOffset = 0.0f; //how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + vehicle->cameraPitchOffset = 0.0f; //a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + vehicle->cameraFOV = 0.0f; //third person camera FOV, default is 80 + vehicle->cameraAlpha = qfalse; //fade out the vehicle if it's in the way of the crosshair +*/ +} + +void BG_VehicleClampData( vehicleInfo_t *vehicle ) +{//sanity check and clamp the vehicle's data + int i; + + for ( i = 0; i < 3; i++ ) + { + if ( vehicle->centerOfGravity[i] > 1.0f ) + { + vehicle->centerOfGravity[i] = 1.0f; + } + else if ( vehicle->centerOfGravity[i] < -1.0f ) + { + vehicle->centerOfGravity[i] = -1.0f; + } + } + + // Validate passenger max. + if ( vehicle->maxPassengers > VEH_MAX_PASSENGERS ) + { + vehicle->maxPassengers = VEH_MAX_PASSENGERS; + } + else if ( vehicle->maxPassengers < 0 ) + { + vehicle->maxPassengers = 0; + } +} + +static qboolean BG_ParseVehicleParm( vehicleInfo_t *vehicle, char *parmName, char *pValue ) +{ + int i; + vec3_t vec; + byte *b = (byte *)vehicle; + int _iFieldsRead = 0; + vehicleType_t vehType; + char value[1024]; + + Q_strncpyz( value, pValue, sizeof(value) ); + + // Loop through possible parameters + for ( i = 0; vehicleFields[i].ofs != -1; i++ ) + { + if ( !Q_stricmp( vehicleFields[i].name, parmName ) ) + { + // found it + switch( vehicleFields[i].type ) + { + case VF_IGNORE: + break; + case VF_INT: + *(int *)(b+vehicleFields[i].ofs) = atoi(value); + break; + case VF_FLOAT: + *(float *)(b+vehicleFields[i].ofs) = atof(value); + break; + case VF_LSTRING: // string on disk, pointer in memory, TAG_LEVEL + if (!*(char **)(b+vehicleFields[i].ofs)) + { //just use 128 bytes in case we want to write over the string +#ifdef _JK2MP + *(char **)(b+vehicleFields[i].ofs) = (char *)BG_Alloc(128);//(char *)BG_Alloc(strlen(value)); + strcpy(*(char **)(b+vehicleFields[i].ofs), value); +#else + (*(char **)(b+vehicleFields[i].ofs)) = G_NewString( value ); +#endif + } + + break; + case VF_VECTOR: + _iFieldsRead = sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + assert(_iFieldsRead==3 ); + if (_iFieldsRead!=3) + { + Com_Printf (S_COLOR_YELLOW"BG_ParseVehicleParm: VEC3 sscanf() failed to read 3 floats ('angle' key bug?)\n"); + } + ((float *)(b+vehWeaponFields[i].ofs))[0] = vec[0]; + ((float *)(b+vehWeaponFields[i].ofs))[1] = vec[1]; + ((float *)(b+vehWeaponFields[i].ofs))[2] = vec[2]; + break; + case VF_BOOL: + *(qboolean *)(b+vehicleFields[i].ofs) = (qboolean)(atof(value)!=0); + break; + case VF_VEHTYPE: + vehType = (vehicleType_t)GetIDForString( VehicleTable, value ); + *(vehicleType_t *)(b+vehicleFields[i].ofs) = vehType; + break; + case VF_ANIM: + { + int anim = GetIDForString( animTable, value ); + *(int *)(b+vehicleFields[i].ofs) = anim; + } + break; + case VF_WEAPON: // take string, resolve into index into VehWeaponParms + *(int *)(b+vehicleFields[i].ofs) = VEH_VehWeaponIndexForName( value ); + break; + case VF_MODEL: // take the string, get the G_ModelIndex +#ifdef QAGAME + *(int *)(b+vehicleFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_MODEL_CLIENT: // (MP cgame only) take the string, get the G_ModelIndex +#ifndef _JK2MP + *(int *)(b+vehicleFields[i].ofs) = G_ModelIndex( value ); +#elif QAGAME + //*(int *)(b+vehicleFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_EFFECT: // take the string, get the G_EffectIndex +#ifdef QAGAME + *(int *)(b+vehicleFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehicleFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_EFFECT_CLIENT: // (MP cgame only) take the string, get the G_EffectIndex +#ifndef _JK2MP + *(int *)(b+vehicleFields[i].ofs) = G_EffectIndex( value ); +#elif QAGAME + //*(int *)(b+vehicleFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehicleFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_SHADER: // (cgame only) take the string, call trap_R_RegisterShader +#ifdef WE_ARE_IN_THE_UI + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#elif CGAME + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterShader( value ); +#endif + break; + case VF_SHADER_NOMIP:// (cgame only) take the string, call trap_R_RegisterShaderNoMip +#ifndef QAGAME + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#endif + break; + case VF_SOUND: // take the string, get the G_SoundIndex +#ifdef QAGAME + *(int *)(b+vehicleFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + case VF_SOUND_CLIENT: // (MP cgame only) take the string, get the G_SoundIndex +#ifndef _JK2MP + *(int *)(b+vehicleFields[i].ofs) = G_SoundIndex( value ); +#elif QAGAME + //*(int *)(b+vehicleFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + default: + //Unknown type? + return qfalse; + break; + } + break; + } + } + if ( vehicleFields[i].ofs == -1 ) + { + return qfalse; + } + else + { + return qtrue; + } +} + +int VEH_LoadVehicle( const char *vehicleName ) +{//load up specified vehicle and save in array: g_vehicleInfo + const char *token; + //we'll assume that no parm name is longer than 128 + char parmName[128] = { 0 }; + char weap1[128] = { 0 }, weap2[128] = { 0 }; + char weapMuzzle1[128] = { 0 }; + char weapMuzzle2[128] = { 0 }; + char weapMuzzle3[128] = { 0 }; + char weapMuzzle4[128] = { 0 }; + char weapMuzzle5[128] = { 0 }; + char weapMuzzle6[128] = { 0 }; + char weapMuzzle7[128] = { 0 }; + char weapMuzzle8[128] = { 0 }; + char weapMuzzle9[128] = { 0 }; + char weapMuzzle10[128] = { 0 }; + char *value = NULL; + const char *p = NULL; + vehicleInfo_t *vehicle = NULL; + + // Load the vehicle parms if no vehicles have been loaded yet. + if ( numVehicles == 0 ) + { + BG_VehicleLoadParms(); + } + + //try to parse data out + p = VehicleParms; + +#ifdef _JK2MP + COM_BeginParseSession("vehicles"); +#else + COM_BeginParseSession(); +#endif + + vehicle = &g_vehicleInfo[numVehicles]; + // look for the right vehicle + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return VEHICLE_NONE; + } + + if ( !Q_stricmp( token, vehicleName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + + if ( !p ) + { + return VEHICLE_NONE; + } + + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + {//barf + return VEHICLE_NONE; + } + + if ( Q_stricmp( token, "{" ) != 0 ) + { + return VEHICLE_NONE; + } + + BG_VehicleSetDefaults( vehicle ); + // parse the vehicle info block + while ( 1 ) + { + SkipRestOfLine( &p ); + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing Vehicle '%s'\n", vehicleName ); + return VEHICLE_NONE; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + Q_strncpyz( parmName, token, sizeof(parmName) ); + value = COM_ParseExt( &p, qtrue ); + if ( !value || !value[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Vehicle token '%s' has no value!\n", parmName ); + } + else if ( Q_stricmp( "weap1", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weap1, value, sizeof(weap1) ); + } + else if ( Q_stricmp( "weap2", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weap2, value, sizeof(weap2) ); + } + else if ( Q_stricmp( "weapMuzzle1", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle1, value, sizeof(weapMuzzle1) ); + } + else if ( Q_stricmp( "weapMuzzle2", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle2, value, sizeof(weapMuzzle2) ); + } + else if ( Q_stricmp( "weapMuzzle3", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle3, value, sizeof(weapMuzzle3) ); + } + else if ( Q_stricmp( "weapMuzzle4", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle4, value, sizeof(weapMuzzle4) ); + } + else if ( Q_stricmp( "weapMuzzle5", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle5, value, sizeof(weapMuzzle5) ); + } + else if ( Q_stricmp( "weapMuzzle6", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle6, value, sizeof(weapMuzzle6) ); + } + else if ( Q_stricmp( "weapMuzzle7", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle7, value, sizeof(weapMuzzle7) ); + } + else if ( Q_stricmp( "weapMuzzle8", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle8, value, sizeof(weapMuzzle8) ); + } + else if ( Q_stricmp( "weapMuzzle9", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle9, value, sizeof(weapMuzzle9) ); + } + else if ( Q_stricmp( "weapMuzzle10", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle10, value, sizeof(weapMuzzle10) ); + } + else + { + if ( !BG_ParseVehicleParm( vehicle, parmName, value ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair '%s', '%s'!\n", parmName, value ); +#endif + } + } + } + //NOW: if we have any weapons, go ahead and load them + if ( weap1[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weap1", weap1 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weap1', '%s'!\n", weap1 ); +#endif + } + } + if ( weap2[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weap2", weap2 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weap2', '%s'!\n", weap2 ); +#endif + } + } + if ( weapMuzzle1[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle1", weapMuzzle1 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle1', '%s'!\n", weapMuzzle1 ); +#endif + } + } + if ( weapMuzzle2[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle2", weapMuzzle2 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle2', '%s'!\n", weapMuzzle2 ); +#endif + } + } + if ( weapMuzzle3[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle3", weapMuzzle3 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle3', '%s'!\n", weapMuzzle3 ); +#endif + } + } + if ( weapMuzzle4[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle4", weapMuzzle4 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle4', '%s'!\n", weapMuzzle4 ); +#endif + } + } + if ( weapMuzzle5[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle5", weapMuzzle5 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle5', '%s'!\n", weapMuzzle5 ); +#endif + } + } + if ( weapMuzzle6[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle6", weapMuzzle6 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle6', '%s'!\n", weapMuzzle6 ); +#endif + } + } + if ( weapMuzzle7[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle7", weapMuzzle7 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle7', '%s'!\n", weapMuzzle7 ); +#endif + } + } + if ( weapMuzzle8[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle8", weapMuzzle8 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle8', '%s'!\n", weapMuzzle8 ); +#endif + } + } + if ( weapMuzzle9[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle9", weapMuzzle9 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle9', '%s'!\n", weapMuzzle9 ); +#endif + } + } + if ( weapMuzzle10[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle10", weapMuzzle10 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle10', '%s'!\n", weapMuzzle10 ); +#endif + } + } + +#ifdef _JK2MP + //let's give these guys some defaults + if (!vehicle->health_front) + { + vehicle->health_front = vehicle->armor/4; + } + if (!vehicle->health_back) + { + vehicle->health_back = vehicle->armor/4; + } + if (!vehicle->health_right) + { + vehicle->health_right = vehicle->armor/4; + } + if (!vehicle->health_left) + { + vehicle->health_left = vehicle->armor/4; + } +#endif + + if ( vehicle->model ) + { +#ifdef QAGAME + vehicle->modelIndex = G_ModelIndex( va( "models/players/%s/model.glm", vehicle->model ) ); +#else + vehicle->modelIndex = trap_R_RegisterModel( va( "models/players/%s/model.glm", vehicle->model ) ); +#endif + } + +#ifndef _JK2MP + //SP + if ( vehicle->skin + && vehicle->skin[0] ) + { + ratl::string_vs<256> skins(vehicle->skin); + for (ratl::string_vs<256>::tokenizer i = skins.begin("|"); i!=skins.end(); i++) + { + //this will just turn off surfs if there is a *off shader on a surf, the skin will actually get thrown away when cgame starts + gi.RE_RegisterSkin( va( "models/players/%s/model_%s.skin", vehicle->model, *i) ); + //this is for the server-side call, it will propgate down to cgame with configstrings and register it at the same time as all the other skins for ghoul2 models + G_SkinIndex( va( "models/players/%s/model_%s.skin", vehicle->model, *i) ); + } + } + else + { + //this will just turn off surfs if there is a *off shader on a surf, the skin will actually get thrown away when cgame starts + gi.RE_RegisterSkin( va( "models/players/%s/model_default.skin", vehicle->model) ); + //this is for the server-side call, it will propgate down to cgame with configstrings and register it at the same time as all the other skins for ghoul2 models + G_SkinIndex( va( "models/players/%s/model_default.skin", vehicle->model) ); + } +#else +#ifndef QAGAME + if ( vehicle->skin + && vehicle->skin[0] ) + { + trap_R_RegisterSkin( va( "models/players/%s/model_%s.skin", vehicle->model, vehicle->skin) ); + } +#endif +#endif + //sanity check and clamp the vehicle's data + BG_VehicleClampData( vehicle ); + // Setup the shared function pointers. + BG_SetSharedVehicleFunctions( vehicle ); + //misc effects... FIXME: not even used in MP, are they? + if ( vehicle->explosionDamage ) + { +#ifdef QAGAME + G_EffectIndex( "ships/ship_explosion_mark" ); +#elif CGAME + trap_FX_RegisterEffect( "ships/ship_explosion_mark" ); +#endif + } + if ( vehicle->flammable ) + { +#ifdef QAGAME + G_SoundIndex( "sound/vehicles/common/fire_lp.wav" ); +#elif CGAME + trap_S_RegisterSound( "sound/vehicles/common/fire_lp.wav" ); +#else + trap_S_RegisterSound( "sound/vehicles/common/fire_lp.wav" ); +#endif + } + + if ( vehicle->hoverHeight > 0 ) + { +#ifndef _JK2MP + G_EffectIndex( "ships/swoop_dust" ); +#elif QAGAME + G_EffectIndex( "ships/swoop_dust" ); +#elif CGAME + trap_FX_RegisterEffect( "ships/swoop_dust" ); +#endif + } + +#ifdef QAGAME + G_EffectIndex( "volumetric/black_smoke" ); + G_EffectIndex( "ships/fire" ); + G_SoundIndex( "sound/vehicles/common/release.wav" ); +#elif CGAME + trap_R_RegisterShader( "gfx/menus/radar/bracket" ); + trap_R_RegisterShaderNoMip( "gfx/menus/radar/asteroid" ); + trap_S_RegisterSound( "sound/vehicles/common/impactalarm.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/linkweaps.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/release.wav" ); + trap_FX_RegisterEffect("effects/ships/dest_burning.efx"); + trap_FX_RegisterEffect("effects/ships/dest_destroyed.efx"); + trap_FX_RegisterEffect( "volumetric/black_smoke" ); + trap_FX_RegisterEffect( "ships/fire" ); + trap_FX_RegisterEffect("ships/hyperspace_stars"); + + if ( vehicle->hideRider ) + { + trap_R_RegisterShaderNoMip( "gfx/menus/radar/circle_base" ); + trap_R_RegisterShaderNoMip( "gfx/menus/radar/circle_base_frame" ); + trap_R_RegisterShaderNoMip( "gfx/menus/radar/circle_base_shield" ); + } +#endif + + return (numVehicles++); +} + +int VEH_VehicleIndexForName( const char *vehicleName ) +{ + int v; + if ( !vehicleName || !vehicleName[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Trying to read Vehicle with no name!\n" ); + return VEHICLE_NONE; + } + for ( v = VEHICLE_BASE; v < numVehicles; v++ ) + { + if ( g_vehicleInfo[v].name + && Q_stricmp( g_vehicleInfo[v].name, vehicleName ) == 0 ) + {//already loaded this one + return v; + } + } + //haven't loaded it yet + if ( v >= MAX_VEHICLES ) + {//no more room! + Com_Printf( S_COLOR_RED"ERROR: Too many Vehicles (max 64), aborting load on %s!\n", vehicleName ); + return VEHICLE_NONE; + } + //we have room for another one, load it up and return the index + //HMM... should we not even load the .veh file until we want to? + v = VEH_LoadVehicle( vehicleName ); + if ( v == VEHICLE_NONE ) + { + Com_Printf( S_COLOR_RED"ERROR: Could not find Vehicle %s!\n", vehicleName ); + } + return v; +} + +void BG_VehWeaponLoadParms( void ) +{ + int len, totallen, vehExtFNLen, mainBlockLen, fileCnt, i; + char *holdChar, *marker; + char vehWeaponExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + char *tempReadBuffer; + + len = 0; + + //remember where to store the next one + totallen = mainBlockLen = len; + marker = VehWeaponParms+totallen; + *marker = 0; + + //now load in the extra .veh extensions +#ifdef _JK2MP + fileCnt = trap_FS_GetFileList("ext_data/vehicles/weapons", ".vwp", vehWeaponExtensionListBuf, sizeof(vehWeaponExtensionListBuf) ); +#else + fileCnt = gi.FS_GetFileList("ext_data/vehicles/weapons", ".vwp", vehWeaponExtensionListBuf, sizeof(vehWeaponExtensionListBuf) ); +#endif + + holdChar = vehWeaponExtensionListBuf; + +#ifdef _JK2MP + tempReadBuffer = (char *)BG_TempAlloc(MAX_VEH_WEAPON_DATA_SIZE); +#else + tempReadBuffer = (char *)gi.Malloc( MAX_VEH_WEAPON_DATA_SIZE, TAG_G_ALLOC, qtrue ); +#endif + + // NOTE: Not use TempAlloc anymore... + //Make ABSOLUTELY CERTAIN that BG_Alloc/etc. is not used before + //the subsequent BG_TempFree or the pool will be screwed. + + for ( i = 0; i < fileCnt; i++, holdChar += vehExtFNLen + 1 ) + { + vehExtFNLen = strlen( holdChar ); + +// Com_Printf( "Parsing %s\n", holdChar ); + +#ifdef _JK2MP + len = trap_FS_FOpenFile(va( "ext_data/vehicles/weapons/%s", holdChar), &f, FS_READ); +#else +// len = gi.FS_ReadFile( va( "ext_data/vehicles/weapons/%s", holdChar), (void **) &buffer ); + len = gi.FS_FOpenFile(va( "ext_data/vehicles/weapons/%s", holdChar), &f, FS_READ); +#endif + + if ( len == -1 ) + { + Com_Printf( "error reading file\n" ); + } + else + { +#ifdef _JK2MP + trap_FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#else + gi.FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#endif + + // Don't let it end on a } because that should be a stand-alone token. + if ( totallen && *(marker-1) == '}' ) + { + strcat( marker, " " ); + totallen++; + marker++; + } + + if ( totallen + len >= MAX_VEH_WEAPON_DATA_SIZE ) { + Com_Error(ERR_DROP, "Vehicle Weapon extensions (*.vwp) are too large" ); + } + strcat( marker, tempReadBuffer ); +#ifdef _JK2MP + trap_FS_FCloseFile( f ); +#else + gi.FS_FCloseFile( f ); +#endif + + totallen += len; + marker = VehWeaponParms+totallen; + } + } + +#ifdef _JK2MP + BG_TempFree(MAX_VEH_WEAPON_DATA_SIZE); +#else + gi.Free(tempReadBuffer); tempReadBuffer = NULL; +#endif +} + +void BG_VehicleLoadParms( void ) +{//HMM... only do this if there's a vehicle on the level? + int len, totallen, vehExtFNLen, mainBlockLen, fileCnt, i; +// const char *filename = "ext_data/vehicles.dat"; + char *holdChar, *marker; + char vehExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + char *tempReadBuffer; + + len = 0; + + //remember where to store the next one + totallen = mainBlockLen = len; + marker = VehicleParms+totallen; + *marker = 0; + + //now load in the extra .veh extensions +#ifdef _JK2MP + fileCnt = trap_FS_GetFileList("ext_data/vehicles", ".veh", vehExtensionListBuf, sizeof(vehExtensionListBuf) ); +#else + fileCnt = gi.FS_GetFileList("ext_data/vehicles", ".veh", vehExtensionListBuf, sizeof(vehExtensionListBuf) ); +#endif + + holdChar = vehExtensionListBuf; + +#ifdef _JK2MP + tempReadBuffer = (char *)BG_TempAlloc(MAX_VEHICLE_DATA_SIZE); +#else + tempReadBuffer = (char *)gi.Malloc( MAX_VEHICLE_DATA_SIZE, TAG_G_ALLOC, qtrue ); +#endif + + // NOTE: Not use TempAlloc anymore... + //Make ABSOLUTELY CERTAIN that BG_Alloc/etc. is not used before + //the subsequent BG_TempFree or the pool will be screwed. + + for ( i = 0; i < fileCnt; i++, holdChar += vehExtFNLen + 1 ) + { + vehExtFNLen = strlen( holdChar ); + +// Com_Printf( "Parsing %s\n", holdChar ); + +#ifdef _JK2MP + len = trap_FS_FOpenFile(va( "ext_data/vehicles/%s", holdChar), &f, FS_READ); +#else +// len = gi.FS_ReadFile( va( "ext_data/vehicles/%s", holdChar), (void **) &buffer ); + len = gi.FS_FOpenFile(va( "ext_data/vehicles/%s", holdChar), &f, FS_READ); +#endif + + if ( len == -1 ) + { + Com_Printf( "error reading file\n" ); + } + else + { +#ifdef _JK2MP + trap_FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#else + gi.FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#endif + + // Don't let it end on a } because that should be a stand-alone token. + if ( totallen && *(marker-1) == '}' ) + { + strcat( marker, " " ); + totallen++; + marker++; + } + + if ( totallen + len >= MAX_VEHICLE_DATA_SIZE ) { + Com_Error(ERR_DROP, "Vehicle extensions (*.veh) are too large" ); + } + strcat( marker, tempReadBuffer ); +#ifdef _JK2MP + trap_FS_FCloseFile( f ); +#else + gi.FS_FCloseFile( f ); +#endif + + totallen += len; + marker = VehicleParms+totallen; + } + } + +#ifdef _JK2MP + BG_TempFree(MAX_VEHICLE_DATA_SIZE); +#else + gi.Free(tempReadBuffer); tempReadBuffer = NULL; +#endif + + numVehicles = 1;//first one is null/default + //set the first vehicle to default data + BG_VehicleSetDefaults( &g_vehicleInfo[VEHICLE_BASE] ); + //sanity check and clamp the vehicle's data + BG_VehicleClampData( &g_vehicleInfo[VEHICLE_BASE] ); + // Setup the shared function pointers. + BG_SetSharedVehicleFunctions( &g_vehicleInfo[VEHICLE_BASE] ); + + //Load the Vehicle Weapons data, too + BG_VehWeaponLoadParms(); +} + +int BG_VehicleGetIndex( const char *vehicleName ) +{ + return (VEH_VehicleIndexForName( vehicleName )); +} + +//We get the vehicle name passed in as modelname +//with a $ in front of it. +//we are expected to then get the model for the +//vehicle and stomp over modelname with it. +void BG_GetVehicleModelName(char *modelname) +{ + char *vehName = &modelname[1]; + int vIndex = BG_VehicleGetIndex(vehName); + assert(modelname[0] == '$'); + + if (vIndex == VEHICLE_NONE) + { + Com_Error(ERR_DROP, "BG_GetVehicleModelName: couldn't find vehicle %s", vehName); + } + + strcpy(modelname, g_vehicleInfo[vIndex].model); +} + +void BG_GetVehicleSkinName(char *skinname) +{ + char *vehName = &skinname[1]; + int vIndex = BG_VehicleGetIndex(vehName); + assert(skinname[0] == '$'); + + if (vIndex == VEHICLE_NONE) + { + Com_Error(ERR_DROP, "BG_GetVehicleSkinName: couldn't find vehicle %s", vehName); + } + + if ( !g_vehicleInfo[vIndex].skin + || !g_vehicleInfo[vIndex].skin[0] ) + { + skinname[0] = 0; + } + else + { + strcpy(skinname, g_vehicleInfo[vIndex].skin); + } +} + +#ifdef _JK2MP +#ifndef WE_ARE_IN_THE_UI +//so cgame can assign the function pointer for the vehicle attachment without having to +//bother with all the other funcs that don't really exist cgame-side. +extern int BG_GetTime(void); +extern int trap_G2API_AddBolt(void *ghoul2, int modelIndex, const char *boneName); +extern qboolean trap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); +void AttachRidersGeneric( Vehicle_t *pVeh ) +{ + // If we have a pilot, attach him to the driver tag. + if ( pVeh->m_pPilot ) + { + mdxaBone_t boltMatrix; + vec3_t yawOnlyAngles; + bgEntity_t *parent = pVeh->m_pParentEntity; + bgEntity_t *pilot = pVeh->m_pPilot; + int crotchBolt = trap_G2API_AddBolt(parent->ghoul2, 0, "*driver"); + + assert(parent->playerState); + + VectorSet(yawOnlyAngles, 0, parent->playerState->viewangles[YAW], 0); + + // Get the driver tag. + trap_G2API_GetBoltMatrix( parent->ghoul2, 0, crotchBolt, &boltMatrix, + yawOnlyAngles, parent->playerState->origin, + BG_GetTime(), NULL, parent->modelScale ); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, pilot->playerState->origin ); + } +} +#endif + +#include "../namespace_end.h" + +#endif // _JK2MP + diff --git a/code/game/bset.h b/code/game/bset.h new file mode 100644 index 0000000..066b3b8 --- /dev/null +++ b/code/game/bset.h @@ -0,0 +1,24 @@ +typedef enum //# bSet_e +{//This should check to matching a behavior state name first, then look for a script + BSET_INVALID = -1, + BSET_FIRST = 0, + BSET_SPAWN = 0,//# script to use when first spawned + BSET_USE,//# script to use when used + BSET_AWAKE,//# script to use when awoken/startled + BSET_ANGER,//# script to use when aquire an enemy + BSET_ATTACK,//# script to run when you attack + BSET_VICTORY,//# script to run when you kill someone + BSET_LOSTENEMY,//# script to run when you can't find your enemy + BSET_PAIN,//# script to use when take pain + BSET_FLEE,//# script to use when take pain below 50% of health + BSET_DEATH,//# script to use when killed + BSET_DELAYED,//# script to run when self->delayScriptTime is reached + BSET_BLOCKED,//# script to run when blocked by a friendly NPC or player + BSET_BUMPED,//# script to run when bumped into a friendly NPC or player (can set bumpRadius) + BSET_STUCK,//# script to run when blocked by a wall + BSET_FFIRE,//# script to run when player shoots their own teammates + BSET_FFDEATH,//# script to run when player kills a teammate + BSET_MINDTRICK,//# script to run when player does a mind trick on this NPC + + NUM_BSETS +} bSet_t; diff --git a/code/game/bstate.h b/code/game/bstate.h new file mode 100644 index 0000000..82e5acf --- /dev/null +++ b/code/game/bstate.h @@ -0,0 +1,29 @@ +#ifndef __BSTATE_H__ +#define __BSTATE_H__ + +//bstate.h +typedef enum //# bState_e +{//These take over only if script allows them to be autonomous + BS_DEFAULT = 0,//# default behavior for that NPC + BS_ADVANCE_FIGHT,//# Advance to captureGoal and shoot enemies if you can + BS_SLEEP,//# Play awake script when startled by sound + BS_FOLLOW_LEADER,//# Follow your leader and shoot any enemies you come across + BS_JUMP,//# Face navgoal and jump to it. + BS_SEARCH,//# Using current waypoint as a base, search the immediate branches of waypoints for enemies + BS_WANDER,//# Wander down random waypoint paths + BS_NOCLIP,//# Moves through walls, etc. + BS_REMOVE,//# Waits for player to leave PVS then removes itself + BS_CINEMATIC,//# Does nothing but face it's angles and move to a goal if it has one + BS_FLEE,//# Run away! + //# #eol + //internal bStates only + BS_WAIT,//# Does nothing but face it's angles + BS_STAND_GUARD, + BS_PATROL, + BS_INVESTIGATE,//# head towards temp goal and look for enemies and listen for sounds + BS_STAND_AND_SHOOT, + BS_HUNT_AND_KILL, + NUM_BSTATES +} bState_t; + +#endif //#ifndef __BSTATE_H__ diff --git a/code/game/channels.h b/code/game/channels.h new file mode 100644 index 0000000..4c41f16 --- /dev/null +++ b/code/game/channels.h @@ -0,0 +1,22 @@ +// These entries are now also duplicated in ModView, so tell me if you need any adding or removing. +// Note that the order is ok to change, I only read/write text strings of them anyway, but tell me if there +// are different choices to offer in the pulldown box. I know, it's tacky, but ModView wasn't planned as an +// editor and this was never an external file. A great combination... - Ste. +// +typedef enum //# soundChannel_e +{ + CHAN_AUTO, //## %s !!"W:\game\base\!!sound\*.wav;*.mp3" # Auto-picks an empty channel to play sound on + CHAN_LOCAL, //## %s !!"W:\game\base\!!sound\*.wav;*.mp3" # menu sounds, etc + CHAN_WEAPON,//## %s !!"W:\game\base\!!sound\*.wav;*.mp3" + CHAN_VOICE, //## %s !!"W:\game\base\!!sound\voice\*.wav;*.mp3" # Voice sounds cause mouth animation + CHAN_VOICE_ATTEN, //## %s !!"W:\game\base\!!sound\voice\*.wav;*.mp3" # Causes mouth animation but still use normal sound falloff + CHAN_VOICE_GLOBAL, //## %s !!"W:\game\base\!!sound\voice\*.wav;*.mp3" # Causes mouth animation and is broadcast with no separation + CHAN_ITEM, //## %s !!"W:\game\base\!!sound\*.wav;*.mp3" + CHAN_BODY, //## %s !!"W:\game\base\!!sound\*.wav;*.mp3" + CHAN_AMBIENT,//## %s !!"W:\game\base\!!sound\*.wav;*.mp3" # added for ambient sounds + CHAN_LOCAL_SOUND, //## %s !!"W:\game\base\!!sound\*.wav;*.mp3" #chat messages, etc + CHAN_ANNOUNCER, //## %s !!"W:\game\base\!!sound\*.wav;*.mp3" #announcer voices, etc + CHAN_LESS_ATTEN, //## %s !!"W:\game\base\!!sound\*.wav;*.mp3" #attenuates similar to chan_voice, but uses empty channel auto-pick behaviour + CHAN_MUSIC, //played as a looping sound - added by BTO (VV) +} soundChannel_t; + diff --git a/code/game/characters.h b/code/game/characters.h new file mode 100644 index 0000000..53ff022 --- /dev/null +++ b/code/game/characters.h @@ -0,0 +1,52 @@ +typedef enum //# characters_e +{ + //HazTeam Alpha + //CHARACTER_MUNRO = 0, + CHARACTER_FOSTER = 0, + CHARACTER_TELSIA, + CHARACTER_BIESSMAN, + CHARACTER_CHANG, + CHARACTER_CHELL, + CHARACTER_JUROT, + //HazTeam Beta + CHARACTER_LAIRD, + CHARACTER_KENN, + CHARACTER_OVIEDO, + CHARACTER_ODELL, + CHARACTER_NELSON, + CHARACTER_JAWORSKI, + CHARACTER_CSATLOS, + //Senior Crew + CHARACTER_JANEWAY, + CHARACTER_CHAKOTAY, + CHARACTER_TUVOK, + CHARACTER_TUVOKHAZ, + CHARACTER_TORRES, + CHARACTER_PARIS, + CHARACTER_KIM, + CHARACTER_DOCTOR, + CHARACTER_SEVEN, + CHARACTER_SEVENHAZ, + CHARACTER_NEELIX, + //Other Crew + CHARACTER_PELLETIER, + //Generic Crew + CHARACTER_CREWMAN, + //CHARACTER_ENSIGN, + CHARACTER_LT, + CHARACTER_COMM, + CHARACTER_CAPT, + CHARACTER_GENERIC1, + CHARACTER_GENERIC2, + CHARACTER_GENERIC3, + CHARACTER_GENERIC4, + //# #eol + CHARACTER_NUM_CHARS +} characters_t; + +typedef struct +{ + char *name; + char *sound; + sfxHandle_t soundIndex; +} characterName_t; diff --git a/code/game/common_headers.h b/code/game/common_headers.h new file mode 100644 index 0000000..dcdb925 --- /dev/null +++ b/code/game/common_headers.h @@ -0,0 +1,26 @@ +#pragma once +#if !defined(COMMON_HEADERS_H_INC) +#define COMMON_HEADERS_H_INC + +#if !defined(__Q_SHARED_H) + #include "../game/q_shared.h" +#endif + +//#if !defined(Q_SHAREDBASIC_H_INC) +// #include "../game/q_sharedbasic.h" // fileHandle_t +//#endif + +//#if !defined(Q_MATH_H_INC) +// #include "../game/q_math.h" +//#endif + +#ifdef _XBOX +#define GAME_INCLUDE + #include "../game/b_local.h" + #include "../cgame/cg_local.h" + #include "../game/g_navigator.h" + #include "../game/g_shared.h" + #include "../game/g_functions.h" +#endif + +#endif // COMMON_HEADERS_H_INC diff --git a/code/game/dmstates.h b/code/game/dmstates.h new file mode 100644 index 0000000..31d24b9 --- /dev/null +++ b/code/game/dmstates.h @@ -0,0 +1,15 @@ +#ifndef __DMSTATES_H__ +#define __DMSTATES_H__ + +//dynamic music +typedef enum //# dynamicMusic_e +{ + DM_AUTO, //# let the game determine the dynamic music as normal + DM_SILENCE, //# stop the music + DM_EXPLORE, //# force the exploration music to play + DM_ACTION, //# force the action music to play + DM_BOSS, //# force the boss battle music to play (if there is any) + DM_DEATH //# force the "player dead" music to play +} dynamicMusic_t; + +#endif//#ifndef __DMSTATES_H__ diff --git a/code/game/events.h b/code/game/events.h new file mode 100644 index 0000000..b86ec55 --- /dev/null +++ b/code/game/events.h @@ -0,0 +1,10 @@ +#ifndef __EVENTS__ +#define __EVENTS__ + +typedef enum //# eventType_e +{ + EV_BAD = 0, + +} eventType_t; + +#endif //__EVENTS__ \ No newline at end of file diff --git a/code/game/fields.h b/code/game/fields.h new file mode 100644 index 0000000..ac2ca31 --- /dev/null +++ b/code/game/fields.h @@ -0,0 +1,64 @@ +// Filename:- fields.h +// + +#ifndef FIELDS_H +#define FIELDS_H + +// +// fields are needed for spawning from the entity string +// and saving / loading games +// +#define FFL_SPAWNTEMP 1 +#define MAX_GHOULINST_SIZE 16384 + +#ifndef FOFS +#define FOFS(x) ((int)&(((gentity_t *)0)->x)) // usually already defined in qshared.h +#endif +#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 NPCOFS(x) ((int)&(((gNPC_t *)0)->x)) +#define VHOFS(x) ((int)&(((Vehicle_t *)0)->x)) + +// +#define strFOFS(x) #x,FOFS(x) +#define strSTOFS(x) #x,STOFS(x) +#define strLLOFS(x) #x,LLOFS(x) +#define strCLOFS(x) #x,CLOFS(x) +#define strNPCOFS(x) #x,NPCOFS(x) +#define strVHICOFS(x) #x,VHOFS(x) + +typedef enum +{ +// F_INT, +// F_SHORT, +// F_FLOAT, + F_STRING, // string +// F_VECTOR, + F_NULL, // A ptr to null out + F_ITEM, // Item pointer handling +// F_MMOVE, // Mmove pointer handling + F_GCLIENT, // Client pointer handling + F_GENTITY, // gentity_t ptr handling + F_BOOLPTR, // Generic pointer that is recreated later, could be left alone, but clearer if only 0/1 rather than 0/alloc + + F_BEHAVIORSET, // special scripting string ptr array handler + F_ALERTEVENT, // special handler for alertevent struct in level_locals_t + F_AIGROUPS, // some AI grouping stuff of Mike's + F_ANIMFILESETS, // animfileset animevent strings + + F_GROUP, + F_VEHINFO, + F_IGNORE +} fieldtypeSAVE_t; + +typedef struct +{ + char *psName; + int iOffset; + fieldtypeSAVE_t eFieldType; +} save_field_t; + +#endif // #ifndef FIELDS_H + +//////////////////////// eof ////////////////////////// \ No newline at end of file diff --git a/code/game/g_active.cpp b/code/game/g_active.cpp new file mode 100644 index 0000000..b24fa5b --- /dev/null +++ b/code/game/g_active.cpp @@ -0,0 +1,5804 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "g_functions.h" +#include "..\cgame\cg_local.h" +#include "Q3_Interface.h" +#include "wp_saber.h" +#include "g_vehicles.h" + +#ifdef _DEBUG + #include +#endif //_DEBUG + +#define SLOWDOWN_DIST 128.0f +#define MIN_NPC_SPEED 16.0f + +#ifdef _XBOX +int g_lastFireTime = 0; +#endif +extern void VehicleExplosionDelay( gentity_t *self ); +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); +extern void G_MaintainFormations(gentity_t *self); +extern void BG_CalculateOffsetAngles( gentity_t *ent, usercmd_t *ucmd );//in bg_pangles.cpp +extern void TryUse( gentity_t *ent ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void ScoreBoardReset(void); +extern void WP_SaberReflectCheck( gentity_t *self, usercmd_t *ucmd ); +extern void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd ); +extern void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd ); +extern void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd ); +extern gentity_t *SeekerAcquiresTarget ( gentity_t *ent, vec3_t pos ); +extern void FireSeeker( gentity_t *owner, gentity_t *target, vec3_t origin, vec3_t dir ); +extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f ); +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +extern qboolean PM_LockAngles( gentity_t *ent, usercmd_t *ucmd ); +extern qboolean PM_AdjustAnglesToGripper( gentity_t *gent, usercmd_t *cmd ); +extern qboolean PM_AdjustAnglesToPuller( gentity_t *ent, gentity_t *puller, usercmd_t *ucmd, qboolean faceAway ); +extern qboolean PM_AdjustAngleForWallRun( gentity_t *ent, usercmd_t *ucmd, qboolean doMove ); +extern qboolean PM_AdjustAngleForWallRunUp( gentity_t *ent, usercmd_t *ucmd, qboolean doMove ); +extern qboolean PM_AdjustAnglesForSpinningFlip( gentity_t *ent, usercmd_t *ucmd, qboolean anglesOnly ); +extern qboolean PM_AdjustAnglesForBackAttack( gentity_t *ent, usercmd_t *ucmd ); +extern qboolean PM_AdjustAnglesForSaberLock( gentity_t *ent, usercmd_t *ucmd ); +extern qboolean PM_AdjustAnglesForKnockdown( gentity_t *ent, usercmd_t *ucmd, qboolean angleClampOnly ); +extern qboolean PM_AdjustAnglesForDualJumpAttack( gentity_t *ent, usercmd_t *ucmd ); +extern qboolean PM_AdjustAnglesForLongJump( gentity_t *ent, usercmd_t *ucmd ); +extern qboolean PM_AdjustAnglesForGrapple( gentity_t *ent, usercmd_t *ucmd ); +extern qboolean PM_AdjustAngleForWallJump( gentity_t *ent, usercmd_t *ucmd, qboolean doMove ); +extern qboolean PM_AdjustAnglesForBFKick( gentity_t *ent, usercmd_t *ucmd, vec3_t fwdAngs, qboolean aimFront ); +extern qboolean PM_AdjustAnglesForStabDown( gentity_t *ent, usercmd_t *ucmd ); +extern qboolean PM_AdjustAnglesForSpinProtect( gentity_t *ent, usercmd_t *ucmd ); +extern qboolean PM_AdjustAnglesForWallRunUpFlipAlt( gentity_t *ent, usercmd_t *ucmd ); +extern qboolean PM_AdjustAnglesForHeldByMonster( gentity_t *ent, gentity_t *monster, usercmd_t *ucmd ); +extern qboolean PM_HasAnimation( gentity_t *ent, int animation ); +extern qboolean PM_LeapingSaberAnim( int anim ); +extern qboolean PM_SpinningSaberAnim( int anim ); +extern qboolean PM_SaberInAttack( int move ); +extern qboolean PM_KickingAnim( int anim ); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean PM_InGetUp( playerState_t *ps ); +extern qboolean PM_InRoll( playerState_t *ps ); +extern void PM_CmdForRoll( playerState_t *ps, usercmd_t *pCmd ); +extern qboolean PM_InAttackRoll( int anim ); +extern qboolean PM_CrouchAnim( int anim ); +extern qboolean PM_FlippingAnim( int anim ); +extern qboolean PM_InCartwheel( int anim ); +extern qboolean PM_StandingAnim( int anim ); +extern qboolean PM_InForceGetUp( playerState_t *ps ); +extern qboolean PM_GetupAnimNoMove( int legsAnim ); +extern qboolean PM_SuperBreakLoseAnim( int anim ); +extern qboolean PM_SuperBreakWinAnim( int anim ); +extern qboolean PM_CanRollFromSoulCal( playerState_t *ps ); +extern qboolean BG_FullBodyTauntAnim( int anim ); +extern qboolean FlyingCreature( gentity_t *ent ); +extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent ); +extern void G_AttachToVehicle( gentity_t *ent, usercmd_t **ucmd ); +extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex = 0 ); +extern void G_UpdateEmplacedWeaponData( gentity_t *ent ); +extern void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd ); +extern qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs ); +extern void NPC_SetPainEvent( gentity_t *self ); +extern qboolean G_HasKnockdownAnims( gentity_t *ent ); +extern int G_GetEntsNearBolt( gentity_t *self, gentity_t **radiusEnts, float radius, int boltIndex, vec3_t boltOrg ); +extern qboolean PM_InOnGroundAnim ( playerState_t *ps ); +extern qboolean PM_LockedAnim( int anim ); +extern qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode ); +extern qboolean G_JediInNormalAI( gentity_t *ent ); + +extern bool in_camera; +extern qboolean player_locked; +extern qboolean stop_icarus; +extern qboolean MatrixMode; + +extern cvar_t *g_spskill; +extern cvar_t *g_timescale; +extern cvar_t *g_saberMoveSpeed; +extern cvar_t *g_saberAutoBlocking; +extern cvar_t *g_speederControlScheme; +extern cvar_t *d_slowmodeath; +extern cvar_t *g_debugMelee; +extern vmCvar_t cg_thirdPersonAlpha; +extern vmCvar_t cg_thirdPersonAutoAlpha; + +void ClientEndPowerUps( gentity_t *ent ); + +int G_FindLookItem( gentity_t *self ) +{ +//FIXME: should be a more intelligent way of doing this, like auto aim? +//closest, most in front... did damage to... took damage from? How do we know who the player is focusing on? + gentity_t *ent; + int bestEntNum = ENTITYNUM_NONE; + gentity_t *entityList[MAX_GENTITIES]; + int numListedEntities; + vec3_t center, mins, maxs, fwdangles, forward, dir; + int i, e; + float radius = 256; + float rating, bestRating = 0.0f; + + //FIXME: no need to do this in 1st person? + fwdangles[1] = self->client->ps.viewangles[1]; + AngleVectors( fwdangles, forward, NULL, NULL ); + + VectorCopy( self->currentOrigin, center ); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + if ( !numListedEntities ) + { + return ENTITYNUM_NONE; + } + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + + if ( !ent->item ) + { + continue; + } + if ( ent->s.eFlags&EF_NODRAW ) + { + continue; + } + if ( (ent->spawnflags&4/*ITMSF_MONSTER*/) ) + {//NPCs only + continue; + } + if ( !BG_CanItemBeGrabbed( &ent->s, &self->client->ps ) ) + {//don't need it + continue; + } + if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) ) + {//not even potentially visible + continue; + } + + if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) ) + {//can't see him + continue; + } + //rate him based on how close & how in front he is + VectorSubtract( ent->currentOrigin, center, dir ); + rating = (1.0f-(VectorNormalize( dir )/radius)); + rating *= DotProduct( forward, dir ); + if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == INV_SECURITY_KEY ) + {//security keys are of the highest importance + rating *= 2.0f; + } + if ( rating > bestRating ) + { + bestEntNum = ent->s.number; + bestRating = rating; + } + } + return bestEntNum; +} + +extern void CG_SetClientViewAngles( vec3_t angles, qboolean overrideViewEnt ); +qboolean G_ClearViewEntity( gentity_t *ent ) +{ + if ( !ent->client->ps.viewEntity ) + return qfalse; + + if ( ent->client->ps.viewEntity > 0 && ent->client->ps.viewEntity < ENTITYNUM_NONE ) + { + if ( &g_entities[ent->client->ps.viewEntity] ) + { + g_entities[ent->client->ps.viewEntity].svFlags &= ~SVF_BROADCAST; + if ( g_entities[ent->client->ps.viewEntity].NPC ) + { + g_entities[ent->client->ps.viewEntity].NPC->controlledTime = 0; + SetClientViewAngle( &g_entities[ent->client->ps.viewEntity], g_entities[ent->client->ps.viewEntity].currentAngles ); + G_SetAngles( &g_entities[ent->client->ps.viewEntity], g_entities[ent->client->ps.viewEntity].currentAngles ); + VectorCopy( g_entities[ent->client->ps.viewEntity].currentAngles, g_entities[ent->client->ps.viewEntity].NPC->lastPathAngles ); + g_entities[ent->client->ps.viewEntity].NPC->desiredYaw = g_entities[ent->client->ps.viewEntity].currentAngles[YAW]; + } + } + CG_SetClientViewAngles( ent->pos4, qtrue ); + SetClientViewAngle( ent, ent->pos4 ); + } + ent->client->ps.viewEntity = 0; + return qtrue; +} + +void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity ) +{ + if ( !self || !self->client || !viewEntity ) + { + return; + } + + if ( self->s.number == 0 && cg.zoomMode ) + { + // yeah, it should really toggle them so it plays the end sound.... + cg.zoomMode = 0; + } + if ( viewEntity->s.number == self->client->ps.viewEntity ) + { + return; + } + //clear old one first + G_ClearViewEntity( self ); + //set new one + self->client->ps.viewEntity = viewEntity->s.number; + viewEntity->svFlags |= SVF_BROADCAST; + //remember current angles + VectorCopy( self->client->ps.viewangles, self->pos4 ); + if ( viewEntity->client ) + { + //vec3_t clear = {0,0,0}; + CG_SetClientViewAngles( viewEntity->client->ps.viewangles, qtrue ); + //SetClientViewAngle( self, viewEntity->client->ps.viewangles ); + //SetClientViewAngle( viewEntity, clear ); + /* + VectorCopy( viewEntity->client->ps.viewangles, self->client->ps.viewangles ); + for ( int i = 0; i < 3; i++ ) + { + self->client->ps.delta_angles[i] = viewEntity->client->ps.delta_angles[i]; + } + */ + } + if ( !self->s.number ) + { + CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.87 ); + } +} + +qboolean G_ControlledByPlayer( gentity_t *self ) +{ + if ( self && self->NPC && self->NPC->controlledTime > level.time ) + {//being controlled + gentity_t *controller = &g_entities[0]; + if ( controller->client && controller->client->ps.viewEntity == self->s.number ) + {//we're the player's viewEntity + return qtrue; + } + } + return qfalse; +} + +qboolean G_ValidateLookEnemy( gentity_t *self, gentity_t *enemy ) +{ + if ( !enemy ) + { + return qfalse; + } + + if ( enemy->flags&FL_NOTARGET ) + { + return qfalse; + } + + if ( (enemy->s.eFlags&EF_NODRAW) ) + { + return qfalse; + } + + if ( !enemy || enemy == self || !enemy->inuse ) + { + return qfalse; + } + if ( !enemy->client || !enemy->NPC ) + {//not valid + if ( (enemy->svFlags&SVF_NONNPC_ENEMY) + && enemy->s.weapon == WP_TURRET + && enemy->noDamageTeam != self->client->playerTeam + && enemy->health > 0 ) + {//a turret + //return qtrue; + } + else + { + return qfalse; + } + } + else + { + if ( self->client->playerTeam != TEAM_FREE//evil player hates everybody + && enemy->client->playerTeam == self->client->playerTeam ) + {//on same team + return qfalse; + } + + Vehicle_t *pVeh = G_IsRidingVehicle( self ); + if ( pVeh && pVeh == enemy->m_pVehicle ) + { + return qfalse; + } + + if ( enemy->health <= 0 && ((level.time-enemy->s.time) > 3000||!InFront(enemy->currentOrigin,self->currentOrigin,self->client->ps.viewangles,0.2f)||DistanceHorizontal(enemy->currentOrigin,self->currentOrigin)>16384))//>128 + {//corpse, been dead too long or too out of sight to be interesting + if ( !enemy->message ) + { + return qfalse; + } + } + } + + if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) ) + && ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 ) ) + {//(not in front or not clear LOS) & greater than 256 away + return qfalse; + } + + //LOS? + + return qtrue; +} + +void G_ChooseLookEnemy( gentity_t *self, usercmd_t *ucmd ) +{ +//FIXME: should be a more intelligent way of doing this, like auto aim? +//closest, most in front... did damage to... took damage from? How do we know who the player is focusing on? + gentity_t *ent, *bestEnt = NULL; + gentity_t *entityList[MAX_GENTITIES]; + int numListedEntities; + vec3_t center, mins, maxs, fwdangles, forward, dir; + int i, e; + float radius = 256; + float rating, bestRating = 0.0f; + + //FIXME: no need to do this in 1st person? + fwdangles[0] = 0; //Must initialize data! + fwdangles[1] = self->client->ps.viewangles[1]; + fwdangles[2] = 0; + AngleVectors( fwdangles, forward, NULL, NULL ); + + VectorCopy( self->currentOrigin, center ); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + if ( !numListedEntities ) + {//should we clear the enemy? + return; + } + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + + if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) ) + {//not even potentially visible + continue; + } + + if ( !G_ValidateLookEnemy( self, ent ) ) + {//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call + continue; + } + + if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) ) + {//can't see him + continue; + } + //rate him based on how close & how in front he is + VectorSubtract( ent->currentOrigin, center, dir ); + rating = (1.0f-(VectorNormalize( dir )/radius)); + rating *= DotProduct( forward, dir )+1.0f; + if ( ent->health <= 0 ) + { + if ( (ucmd->buttons&BUTTON_ATTACK) + || (ucmd->buttons&BUTTON_ALT_ATTACK) + || (ucmd->buttons&BUTTON_FORCE_FOCUS) ) + {//if attacking, don't consider dead enemies + continue; + } + if ( ent->message ) + {//keyholder + rating *= 0.5f; + } + else + { + rating *= 0.1f; + } + } + if ( ent->s.weapon == WP_SABER ) + { + rating *= 2.0f; + } + if ( ent->enemy == self ) + {//he's mad at me, he's more important + rating *= 2.0f; + } + else if ( ent->NPC && ent->NPC->blockedSpeechDebounceTime > level.time - 6000 ) + {//he's detected me, he's more important + if ( ent->NPC->blockedSpeechDebounceTime > level.time + 4000 ) + { + rating *= 1.5f; + } + else + {//from 1.0f to 1.5f + rating += rating * ((float)(ent->NPC->blockedSpeechDebounceTime-level.time) + 6000.0f)/20000.0f; + } + } + /* + if ( g_crosshairEntNum == ent && !self->enemy ) + {//we don't have an enemy and we are aiming at this guy + rading *= 2.0f; + } + */ + if ( rating > bestRating ) + { + bestEnt = ent; + bestRating = rating; + } + } + if ( bestEnt ) + { + self->enemy = bestEnt; + } +} + +/* +=============== +G_DamageFeedback + +Called just before a snapshot is sent to the given player. +Totals up all damage and generates both the player_state_t +damage values to that client for pain blends and kicks, and +global pain sound events for all clients. +=============== +*/ +void P_DamageFeedback( gentity_t *player ) { + gclient_t *client; + float count; + vec3_t angles; + + client = player->client; + if ( client->ps.pm_type == PM_DEAD ) { + return; + } + + // total points of damage shot at the player this frame + count = client->damage_blood + client->damage_armor; + if ( count == 0 ) { + return; // didn't take any damage + } + + if ( count > 255 ) { + count = 255; + } + + // send the information to the client + + // world damage (falling, slime, etc) uses a special code + // to make the blend blob centered instead of positional + if ( client->damage_fromWorld ) + { + client->ps.damagePitch = 255; + client->ps.damageYaw = 255; + + client->damage_fromWorld = false; + } + else + { + vectoangles( client->damage_from, angles ); + client->ps.damagePitch = angles[PITCH]/360.0 * 256; + client->ps.damageYaw = angles[YAW]/360.0 * 256; + } + + client->ps.damageCount = count; + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; +} + + + +/* +============= +P_WorldEffects + +Check for lava / slime contents and drowning +============= +*/ + +extern void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +void P_WorldEffects( gentity_t *ent ) { + int mouthContents = 0; + + if ( ent->client->noclip ) + { + ent->client->airOutTime = level.time + 12000; // don't need air + return; + } + + if ( !in_camera ) + { +#ifndef _XBOX + if (gi.totalMapContents() & (CONTENTS_WATER|CONTENTS_SLIME)) + { + mouthContents = gi.pointcontents( ent->client->renderInfo.eyePoint, ent->s.number ); + } +#endif // _XBOX + } + // + // check for drowning + // + +#ifdef _XBOX + // using waterlevel 3 should be good enough + // this saves us from doing an expensive trace + if ( ent->waterlevel == 3 ) +#else + if ( (mouthContents&(CONTENTS_WATER|CONTENTS_SLIME)) ) +#endif // _XBOX + { + + if ( ent->client->NPC_class == CLASS_SWAMPTROOPER ) + {//they have air tanks + ent->client->airOutTime = level.time + 12000; // don't need air + ent->damage = 2; + } + else if ( ent->client->airOutTime < level.time) + {// if out of air, start drowning + // drown! + ent->client->airOutTime += 1000; + if ( ent->health > 0 ) { + // take more damage the longer underwater + ent->damage += 2; + if (ent->damage > 15) + ent->damage = 15; + + // play a gurp sound instead of a normal pain sound + if (ent->health <= ent->damage) + { + G_AddEvent( ent, EV_WATER_DROWN, 0 ); + } + else + { + G_AddEvent( ent, Q_irand(EV_WATER_GURP1, EV_WATER_GURP2), 0 ); + } + + // don't play a normal pain sound + ent->painDebounceTime = level.time + 200; + + G_Damage (ent, NULL, NULL, NULL, NULL, + ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } + else + { + ent->client->airOutTime = level.time + 12000; + ent->damage = 2; + } + + // + // check for sizzle damage (move to pmove?) + // + if (ent->waterlevel && + (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { + if (ent->health > 0 + && ent->painDebounceTime < level.time ) { + + if (ent->watertype & CONTENTS_LAVA) { + G_Damage (ent, NULL, NULL, NULL, NULL, + 15*ent->waterlevel, 0, MOD_LAVA); + } + + if (ent->watertype & CONTENTS_SLIME) { + G_Damage (ent, NULL, NULL, NULL, NULL, + 1, 0, MOD_SLIME); + } + } + } + + if ((ent->health > 0) && + (ent->painDebounceTime < level.time) && + gi.WE_IsOutsideCausingPain(ent->currentOrigin) && + TIMER_Done(ent, "AcidPainDebounce")) + { + if (ent->NPC && ent->client && (ent->client->ps.forcePowersKnown&(1<< FP_PROTECT))) + { + if (!(ent->client->ps.forcePowersActive & (1<client->poisonDamage) && (ent->client->poisonTime < level.time)) + { + ent->client->poisonDamage -= 2; + ent->client->poisonTime = level.time + 1000; + G_Damage( ent, NULL, NULL, 0, 0, 2, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_ARMOR, MOD_UNKNOWN );//FIXME: MOD_POISON? + + if (ent->client->poisonDamage<0) + { + ent->client->poisonDamage = 0; + } + } + + //in space? + if (ent->client->inSpaceIndex && ent->client->inSpaceIndex != ENTITYNUM_NONE) + { //we're in space, check for suffocating and for exiting + gentity_t *spacetrigger = &g_entities[ent->client->inSpaceIndex]; + + if (!spacetrigger->inuse || + !G_PointInBounds(ent->client->ps.origin, spacetrigger->absmin, spacetrigger->absmax)) + { //no longer in space then I suppose + ent->client->inSpaceIndex = 0; + } + else + { //check for suffocation + if (ent->client->inSpaceSuffocation < level.time) + { //suffocate! + if (ent->health > 0) + { //if they're still alive.. + G_Damage(ent, spacetrigger, spacetrigger, NULL, ent->client->ps.origin, Q_irand(20, 40), DAMAGE_NO_ARMOR, MOD_SUICIDE); + + if (ent->health > 0) + { //did that last one kill them? + //play the choking sound + G_SoundOnEnt( ent, CHAN_VOICE, va( "*choke%d.wav", Q_irand( 1, 3 ) ) ); + + int anim = BOTH_CHOKE3; //left-handed choke + if ( ent->client->ps.weapon == WP_NONE || ent->client->ps.weapon == WP_MELEE ) + { + anim = BOTH_CHOKE1; //two-handed choke + } + //make them grasp their throat + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_CHOKE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + + ent->client->inSpaceSuffocation = level.time + Q_irand(1000, 2000); + } + } + } + + +} + + + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) { +// if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) +// ent->s.loopSound = G_SoundIndex("sound/weapons/stasis/electricloop.wav"); + +// else +// ent->s.loopSound = 0; +} + + + +//============================================================== +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 ); +void G_GetMassAndVelocityForEnt( gentity_t *ent, float *mass, vec3_t velocity ) +{ + if( ent->client ) + { + VectorCopy( ent->client->ps.velocity, velocity ); + *mass = ent->mass; + } + else + { + VectorCopy( ent->s.pos.trDelta, velocity ); + if ( ent->s.pos.trType == TR_GRAVITY ) + { + velocity[2] -= 0.25f * g_gravity->value; + } + if( !ent->mass ) + { + *mass = 1; + } + else if ( ent->mass <= 10 ) + { + *mass = 10; + } + else + { + *mass = ent->mass;///10; + } + } +} + +void DoImpact( gentity_t *self, gentity_t *other, qboolean damageSelf, trace_t *trace ) +{ + float magnitude, my_mass; + bool thrown = false; + vec3_t velocity; + + Vehicle_t *pSelfVeh = NULL; + Vehicle_t *pOtherVeh = NULL; + + // See if either of these guys are vehicles, if so, keep a pointer to the vehicle npc. + if ( self->client && self->client->NPC_class == CLASS_VEHICLE ) + { + pSelfVeh = self->m_pVehicle; + } + if ( other->client && other->client->NPC_class == CLASS_VEHICLE ) + { + pOtherVeh = other->m_pVehicle; + } + + G_GetMassAndVelocityForEnt( self, &my_mass, velocity ); + + if ( pSelfVeh ) + { + magnitude = VectorLength( velocity ) * pSelfVeh->m_pVehicleInfo->mass / 50.0f; + } + else + { + magnitude = VectorLength( velocity ) * my_mass / 50; + } + + //if vehicle hit another vehicle, factor in their data, too + // TODO: Bring this back in later on, it's not critical right now... +/* if ( self->client && self->client->NPC_class == CLASS_VEHICLE ) + {//we're in a vehicle + if ( other->client && other->client->ps.vehicleIndex != VEHICLE_NONE ) + {//they're in a vehicle + float o_mass; + vec3_t o_velocity; + + G_GetMassAndVelocityForEnt( other, &o_mass, o_velocity ); + + //now combine + if ( DotProduct( o_velocity, velocity ) < 0 ) + {//were heading towards each other, this is going to be a STRONG impact... + vec3_t velocityMod; + + //incorportate mass into each velocity to get directional force + VectorScale( velocity, my_mass/50, velocityMod ); + VectorScale( o_velocity, o_mass/50, o_velocity ); + //figure out the overall magnitude of those 2 directed forces impacting + magnitude = (DotProduct( o_velocity, velocityMod ) * -1.0f)/500.0f; + } + } + }*/ + + + + // Check For Vehicle On Vehicle Impact (Ramming) + //----------------------------------------------- + if ( pSelfVeh && + pSelfVeh->m_pVehicleInfo->type!=VH_ANIMAL && + pOtherVeh && + pSelfVeh->m_pVehicleInfo==pOtherVeh->m_pVehicleInfo + ) + { + gentity_t* attacker = self; + Vehicle_t* attackerVeh = pSelfVeh; + gentity_t* victim = other; + Vehicle_t* victimVeh = pOtherVeh; + + // Is The Attacker Actually Not Attacking? + //----------------------------------------- + if (!(attackerVeh->m_ulFlags&VEH_STRAFERAM)) + { + // Ok, So Is The Victim Actually Attacking? + //------------------------------------------ + if (victimVeh->m_ulFlags&VEH_STRAFERAM) + { + // Ah, Ok. Swap Who Is The Attacker Then + //---------------------------------------- + attacker = other; + attackerVeh = pOtherVeh; + victim = self; + victimVeh = pSelfVeh; + } + else + { + // No Attackers, So Stop + //----------------------- + attacker = victim = 0; + } + } + + if (attacker && victim) + { + // float maxMoveSpeed = pSelfVeh->m_pVehicleInfo->speedMax; + // float minLockingSpeed = maxMoveSpeed * 0.75; + + vec3_t attackerMoveDir; + float attackerMoveSpeed; + + vec3_t victimMoveDir; + float victimMoveSpeed; + vec3_t victimTowardAttacker; + float victimTowardAttackerDistance; + vec3_t victimRight; + float victimRightAccuracy; + + VectorCopy(attacker->client->ps.velocity, attackerMoveDir); + VectorCopy(victim->client->ps.velocity, victimMoveDir); + + attackerMoveSpeed = VectorNormalize(attackerMoveDir); + victimMoveSpeed = VectorNormalize(victimMoveDir); + + AngleVectors(victim->currentAngles, 0, victimRight, 0); + + VectorSubtract(victim->currentOrigin, attacker->currentOrigin, victimTowardAttacker); + victimTowardAttackerDistance = VectorNormalize(victimTowardAttacker); + + victimRightAccuracy = DotProduct(victimTowardAttacker, victimRight); + + if ( + fabsf(victimRightAccuracy)>0.25 // Must Be Exactly Right Or Left + // && victimTowardAttackerDistance<100.0f // Must Be Close Enough + // && attackerMoveSpeed>minLockingSpeed // Must be moving fast enough + // && fabsf(attackerMoveSpeed - victimMoveSpeed)<100 // Both must be going about the same speed + ) + { + thrown = true; + + vec3_t victimRight; + vec3_t victimAngles; + VectorCopy(victim->currentAngles, victimAngles); + victimAngles[2] = 0; + AngleVectors(victimAngles, 0, victimRight, 0); + + if (attackerVeh->m_fStrafeTime<0) + { + VectorScale(victimRight, -1.0f, victimRight); + } + if ( !(victim->flags&FL_NO_KNOCKBACK) ) + { + G_Throw(victim, victimRight, 250); + } +// if (false) +// { +// VectorMA(victim->currentOrigin, 250.0f, victimRight, victimRight); +// CG_DrawEdge(victim->currentOrigin, victimRight, EDGE_IMPACT_POSSIBLE); +// } + if (victimVeh->m_pVehicleInfo->iImpactFX) + { + G_PlayEffect(victimVeh->m_pVehicleInfo->iImpactFX, victim->currentOrigin, trace->plane.normal ); + } + } + } + } + + + if ( !self->client || self->client->ps.lastOnGround+300client->ps.lastOnGround+100 < level.time ) ) + { + vec3_t dir1, dir2; + float force = 0, dot; + qboolean vehicleHitOwner = qfalse; + + if ( other->material == MAT_GLASS || other->material == MAT_GLASS_METAL || other->material == MAT_GRATE1 || ((other->svFlags&SVF_BBRUSH)&&(other->spawnflags&8/*THIN*/)) )//(other->absmax[0]-other->absmin[0]<=32||other->absmax[1]-other->absmin[1]<=32||other->absmax[2]-other->absmin[2]<=32)) ) + {//glass and thin breakable brushes (axially aligned only, unfortunately) take more impact damage + magnitude *= 2; + } + + // See if the vehicle has crashed into the ground. + if ( pSelfVeh && pSelfVeh->m_pVehicleInfo->type!=VH_ANIMAL) + { + if ((magnitude >= 80) && (self->painDebounceTime < level.time)) + { + + // Setup Some Variables + //---------------------- + vec3_t vehFwd; + VectorCopy(velocity, vehFwd); + float vehSpeed = VectorNormalize(vehFwd); + float vehToughnessAgainstOther = pSelfVeh->m_pVehicleInfo->toughness; + float vehHitPercent = fabsf(DotProduct(vehFwd, trace->plane.normal)); + int vehDFlags = DAMAGE_NO_ARMOR; + bool vehPilotedByPlayer = (pSelfVeh->m_pPilot && pSelfVeh->m_pPilot->s.numberm_iTurboTime>level.time); + + self->painDebounceTime = level.time + 200; + + + // Modify Magnitude By Hit Percent And Toughness Against Other + //------------------------------------------------------------- + if (pSelfVeh->m_ulFlags & VEH_OUTOFCONTROL) + { + vehToughnessAgainstOther *= 0.01f; // If Out Of Control, No Damage Resistance + } + else + { + if (vehPilotedByPlayer) + { + vehToughnessAgainstOther *= 1.5f; + } + if (other && other->client) + { + vehToughnessAgainstOther *= 15.0f; // Very Tough against other clients (NPCS, Player, etc) + } + } + if (vehToughnessAgainstOther>0.0f) + { + magnitude *= (vehHitPercent / vehToughnessAgainstOther); + } + else + { + magnitude *= vehHitPercent; + } + + + // If We Hit Architecture + //------------------------ + if (!other || !other->client) + { + // Turbo Hurts + //------------- + if (vehInTurbo) + { + magnitude *= 5.0f; + } + + else if (trace->plane.normal[2]>0.75f && vehHitPercent<0.2f) + { + magnitude /= 10.0f; + } + + + // If No Pilot, Blow This Thing Now + //---------------------------------- + if (vehHitPercent>0.9f && !pSelfVeh->m_pPilot && vehSpeed>1000.0f) + { + vehDFlags |= DAMAGE_IMPACT_DIE; + } + + // If Out Of Control, And We Hit A Wall Or Landed Or Head On + //------------------------------------------------------------ + if ((pSelfVeh->m_ulFlags&VEH_OUTOFCONTROL) && (vehHitPercent>0.5f || trace->plane.normal[2]<0.5f || velocity[2]<-50.0f)) + { + vehDFlags |= DAMAGE_IMPACT_DIE; + } + + // If This Is A Direct Impact (Debounced By 4 Seconds) + //----------------------------------------------------- + if (vehHitPercent>0.9f && (level.time - self->lastImpact)>2000 && vehSpeed>300.0f) + { + self->lastImpact = level.time; + + // The Player Has Harder Requirements to Explode + //----------------------------------------------- + if (vehPilotedByPlayer) + { + if ((vehHitPercent>0.99f && vehSpeed>1000.0f && !Q_irand(0,30)) || + (vehHitPercent>0.999f && vehInTurbo)) + { + vehDFlags |= DAMAGE_IMPACT_DIE; + } + } + else if (player && G_IsRidingVehicle(player) && + (Distance(self->currentOrigin, player->currentOrigin)<800.0f) && + (vehInTurbo || !Q_irand(0,1) || vehHitPercent>0.999f)) + { + vehDFlags |= DAMAGE_IMPACT_DIE; + } + } + + // Make Sure He Dies This Time. I will accept no excuses. + //--------------------------------------------------------- + if (vehDFlags&DAMAGE_IMPACT_DIE) + { + // If close enough To The PLayer + if (player && + G_IsRidingVehicle(player) && + self->owner && + Distance(self->currentOrigin, player->currentOrigin)<500.0f) + { + player->lastEnemy = self->owner; + G_StartMatrixEffect(player, MEF_LOOK_AT_ENEMY|MEF_NO_RANGEVAR|MEF_NO_VERTBOB|MEF_NO_SPIN, 1000); + } + magnitude = 100000.0f; + } + } + + if (magnitude>10.0f) + { + // Play The Impact Effect + //------------------------ + if (pSelfVeh->m_pVehicleInfo->iImpactFX && vehSpeed>100.0f) + { + G_PlayEffect( pSelfVeh->m_pVehicleInfo->iImpactFX, self->currentOrigin, trace->plane.normal ); + } + + // Set The Crashing Flag And Pain Debounce Time + //---------------------------------------------- + pSelfVeh->m_ulFlags |= VEH_CRASHING; + } + + G_Damage( self, player, player, NULL, self->currentOrigin, magnitude, vehDFlags, MOD_FALLING );//FIXME: MOD_IMPACT + } + + if ( self->owner == other || self->activator == other ) + {//hit owner/activator + if ( self->m_pVehicle && !self->m_pVehicle->m_pVehicleInfo->Inhabited( self->m_pVehicle ) ) + {//empty swoop + if ( self->client->respawnTime - level.time < 1000 ) + {//just spawned in a second ago + //don't actually damage or throw him... + vehicleHitOwner = qtrue; + } + } + } + //if 2 vehicles on same side hit each other, tone it down + //NOTE: we do this here because we still want the impact effect + if ( pOtherVeh ) + { + if ( self->client->playerTeam == other->client->playerTeam ) + { + magnitude /= 25; + } + } + } + else if ( self->client + && (PM_InKnockDown( &self->client->ps )||(self->client->ps.eFlags&EF_FORCE_GRIPPED)) + && magnitude >= 120 ) + {//FORCE-SMACKED into something + if ( TIMER_Done( self, "impactEffect" ) ) + { + G_PlayEffect( G_EffectIndex( "env/impact_dustonly" ), trace->endpos, trace->plane.normal ); + G_Sound( self, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + TIMER_Set( self, "impactEffect", 1000 ); + } + } + + //damage them + if ( magnitude >= 100 && other->s.number < ENTITYNUM_WORLD ) + { + VectorCopy( velocity, dir1 ); + VectorNormalize( dir1 ); + if( VectorCompare( other->currentOrigin, vec3_origin ) ) + {//a brush with no origin + VectorCopy ( dir1, dir2 ); + } + else + { + VectorSubtract( other->currentOrigin, self->currentOrigin, dir2 ); + VectorNormalize( dir2 ); + } + + dot = DotProduct( dir1, dir2 ); + + if ( dot >= 0.2 ) + { + force = dot; + } + else + { + force = 0; + } + + force *= (magnitude/50); + + int cont = gi.pointcontents( other->absmax, other->s.number ); + if( (cont&CONTENTS_WATER) ) + {//water absorbs 2/3 velocity + force *= 0.33333f; + } + + if ( self->NPC && other->s.number == ENTITYNUM_WORLD ) + {//NPCs take less damage + force *= 0.5f; + } + + if ( self->s.number >= MAX_CLIENTS && self->client && (PM_InKnockDown( &self->client->ps )||self->client->ps.eFlags&EF_FORCE_GRIPPED) ) + {//NPC: I was knocked down or being gripped, impact should be harder + //FIXME: what if I was just thrown - force pushed/pulled or thrown from a grip? + force *= 10; + } + + //FIXME: certain NPCs/entities should be TOUGH - like Ion Cannons, AT-STs, Mark1 droids, etc. + if ( pOtherVeh ) + {//if hit another vehicle, take their toughness into account, too + force /= pOtherVeh->m_pVehicleInfo->toughness; + } + + if( ( (force >= 1 || pSelfVeh) && other->s.number>=MAX_CLIENTS ) || force >= 10) + { + /* + dprint("Damage other ("); + dprint(loser.classname); + dprint("): "); + dprint(ftos(force)); + dprint("\n"); + */ + if ( other->svFlags & SVF_GLASS_BRUSH ) + { + other->splashRadius = (float)(self->maxs[0] - self->mins[0])/4.0f; + } + + if ( pSelfVeh ) + {//if in a vehicle when land on someone, always knockdown, throw, damage + if ( !vehicleHitOwner ) + {//didn't hit owner + // If the player was hit don't make the damage so bad... + if ( other && other->s.numberflags&FL_NO_KNOCKBACK) ) + { + G_Throw( other, dir2, force ); + } + G_Knockdown( other, self, dir2, force, qtrue ); + G_Damage( other, self, self, velocity, self->currentOrigin, force, DAMAGE_NO_ARMOR|DAMAGE_EXTRA_KNOCKBACK, MOD_IMPACT ); + } + } + else if ( self->forcePushTime > level.time - 1000//was force pushed/pulled in the last 1600 milliseconds + && self->forcePuller == other->s.number>=MAX_CLIENTS )//hit the person who pushed/pulled me + {//ignore the impact + } + else if ( other->takedamage ) + { + if ( !self->client || other->s.numberclient ) + {//aw, fuck it, clients no longer take impact damage from other clients, unless you're the player + if ( other->client //he's a client + && self->client //I'm a client + && other->client->ps.forceGripEntityNum == self->s.number )//he's force-gripping me + {//don't damage the other guy if he's gripping me + } + else + { + G_Damage( other, self, self, velocity, self->currentOrigin, floor(force), DAMAGE_NO_ARMOR, MOD_IMPACT ); + } + } + else + { + GEntity_PainFunc( other, self, self, self->currentOrigin, force, MOD_IMPACT ); + //Hmm, maybe knockdown? + if (!thrown) + { + if ( !(other->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( other, dir2, force ); + } + } + } + if ( other->health > 0 ) + {//still alive? + //TODO: if someone was thrown through the air (in a knockdown or being gripped) + // and they hit me hard enough, knock me down + if ( other->client ) + { + if ( self->client ) + { + if ( PM_InKnockDown( &self->client->ps ) || (self->client->ps.eFlags&EF_FORCE_GRIPPED) ) + { + G_Knockdown( other, self, dir2, Q_irand( 200, 400 ), qtrue ); + } + } + else if ( self->forcePuller != ENTITYNUM_NONE + && g_entities[self->forcePuller].client + && self->mass > Q_irand( 50, 100 ) ) + { + G_Knockdown( other, &g_entities[self->forcePuller], dir2, Q_irand( 200, 400 ), qtrue ); + } + } + } + } + else + { + //Hmm, maybe knockdown? + if (!thrown) + { + if ( !(other->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( other, dir2, force ); + } + } + } + } + } + + if ( damageSelf && self->takedamage && !(self->flags&FL_NO_IMPACT_DMG)) + { + //Now damage me + //FIXME: more lenient falling damage, especially for when driving a vehicle + if ( pSelfVeh && self->client->ps.forceJumpZStart ) + {//we were force-jumping + if ( self->currentOrigin[2] >= self->client->ps.forceJumpZStart ) + {//we landed at same height or higher than we landed + magnitude = 0; + } + else + {//FIXME: take off some of it, at least? + magnitude = (self->client->ps.forceJumpZStart-self->currentOrigin[2])/3; + } + } + + if( ( magnitude >= 100 + self->health + && self->s.number >= MAX_CLIENTS + && self->s.weapon != WP_SABER ) + || self->client->NPC_class == CLASS_VEHICLE + || ( magnitude >= 700 ) )//health here is used to simulate structural integrity + { + if ( (self->s.weapon == WP_SABER || self->s.numberclient&&(self->client->NPC_class==CLASS_BOBAFETT||self->client->NPC_class==CLASS_ROCKETTROOPER))) && self->client && self->client->ps.groundEntityNum < ENTITYNUM_NONE && magnitude < 1000 ) + {//players and jedi take less impact damage + //allow for some lenience on high falls + magnitude /= 2; + } + //drop it some (magic number... sigh) + magnitude /= 40; + //If damage other, subtract half of that damage off of own injury + if ( other->bmodel && other->material != MAT_GLASS ) + {//take off only a little because we broke architecture (not including glass), that should hurt + magnitude = magnitude - force/8; + } + else + {//take off half damage we did to it + magnitude = magnitude - force/2; + } + + if ( pSelfVeh ) + { + //FIXME: if hit another vehicle, take their toughness into + // account, too? Or should their toughness only matter + // when they hit me? + magnitude /= pSelfVeh->m_pVehicleInfo->toughness * 1000.0f; + if ( other->bmodel && other->material != MAT_GLASS ) + {//broke through some architecture, take a good amount of damage + } + else if ( pOtherVeh ) + {//they're tougher + //magnitude /= 4.0f;//FIXME: get the toughness of other from vehicles.cfg + } + else + {//take some off because of give + //NOTE: this covers all other entities and impact with world... + //FIXME: certain NPCs/entities should be TOUGH - like Ion Cannons, AT-STs, Mark1 droids, etc. + magnitude /= 10.0f; + } + if ( magnitude < 1.0f ) + { + magnitude = 0; + } + } + if ( magnitude >= 1 ) + { + //FIXME: Put in a thingtype impact sound function + /* + dprint("Damage self ("); + dprint(self.classname); + dprint("): "); + dprint(ftos(magnitude)); + dprint("\n"); + */ + if ( self->NPC && self->s.weapon == WP_SABER ) + {//FIXME: for now Jedi take no falling damage, but really they should if pushed off? + magnitude = 0; + } + G_Damage( self, NULL, NULL, NULL, self->currentOrigin, magnitude/2, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT + } + } + } + + //FIXME: slow my velocity some? + + + + /* + if(self.flags&FL_ONGROUND) + self.last_onground=time; + */ + } +} + +/* +============== +ClientImpacts +============== +*/ +void ClientImpacts( gentity_t *ent, pmove_t *pm ) { + int i, j; + trace_t trace; + gentity_t *other; + + memset( &trace, 0, sizeof( trace ) ); + for (i=0 ; inumtouch ; i++) { + for (j=0 ; jtouchents[j] == pm->touchents[i] ) { + break; + } + } + if (j != i) { + continue; // duplicated + } + other = &g_entities[ pm->touchents[i] ]; + + if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) { // last check unneccessary + GEntity_TouchFunc( ent, other, &trace ); + } + + if ( other->e_TouchFunc == touchF_NULL ) { // not needed, but I'll leave it I guess (cache-hit issues) + continue; + } + GEntity_TouchFunc( other, ent, &trace ); + } + +} + +/* +============ +G_TouchTriggersLerped + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. + +This version checks at 6 unit steps between last and current origins +============ +*/ +void G_TouchTriggersLerped( gentity_t *ent ) { + int i, num; + float dist, curDist = 0; + gentity_t *touch[MAX_GENTITIES], *hit; + trace_t trace; + vec3_t end, mins, maxs, diff; + const vec3_t range = { 40, 40, 52 }; + qboolean touched[MAX_GENTITIES]; + qboolean done = qfalse; + + if ( !ent->client ) { + return; + } + + // dead NPCs don't activate triggers! + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) + { + if ( ent->s.number>=MAX_CLIENTS ) + { + return; + } + } + +#ifdef _DEBUG + for ( int j = 0; j < 3; j++ ) + { + assert( !_isnan(ent->currentOrigin[j])); + assert( !_isnan(ent->lastOrigin[j])); + } +#endif// _DEBUG + VectorSubtract( ent->currentOrigin, ent->lastOrigin, diff ); + dist = VectorNormalize( diff ); +#ifdef _DEBUG + assert( (dist<1024) && "insane distance in G_TouchTriggersLerped!" ); +#endif// _DEBUG + + if ( dist > 1024 ) + { + return; + } + memset (touched, qfalse, sizeof(touched) ); + + for ( curDist = 0; !done && ent->maxs[1]>0; curDist += (float)ent->maxs[1]/2.0f ) + { + if ( curDist >= dist ) + { + VectorCopy( ent->currentOrigin, end ); + done = qtrue; + } + else + { + VectorMA( ent->lastOrigin, curDist, diff, end ); + } + VectorSubtract( end, range, mins ); + VectorAdd( end, range, maxs ); + + num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( end, ent->mins, mins ); + VectorAdd( end, ent->maxs, maxs ); + + for ( i=0 ; ie_TouchFunc == touchF_NULL) && (ent->e_TouchFunc == touchF_NULL) ) { + continue; + } + if ( !( hit->contents & CONTENTS_TRIGGER ) ) { + continue; + } + + if ( touched[i] == qtrue ) { + continue;//already touched this move + } + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) + { + if ( Q_stricmp( "trigger_teleport", hit->classname ) || !(hit->spawnflags&16/*TTSF_DEAD_OK*/) ) + {//dead clients can only touch tiogger_teleports that are marked as touchable + continue; + } + } + // use seperate code for determining if an item is picked up + // so you don't have to actually contact its bounding box + /* + if ( hit->s.eType == ET_ITEM ) { + if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { + continue; + } + } else */ + { + if ( !gi.EntityContact( mins, maxs, hit ) ) { + continue; + } + } + + touched[i] = qtrue; + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->e_TouchFunc != touchF_NULL ) { + GEntity_TouchFunc(hit, ent, &trace); + } + + //WTF? Why would a trigger ever fire off the NPC's touch func??!!! + /* + if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) { + GEntity_TouchFunc( ent, hit, &trace ); + } + */ + } + } +} + +/* +============ +G_TouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_TouchTriggers( gentity_t *ent ) { + int i, num; + gentity_t *touch[MAX_GENTITIES], *hit; + trace_t trace; + vec3_t mins, maxs; + const vec3_t range = { 40, 40, 52 }; + + if ( !ent->client ) { + return; + } + + // dead clients don't activate triggers! + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { + return; + } + + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + + num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->mins, mins ); + VectorAdd( ent->client->ps.origin, ent->maxs, maxs ); + + for ( i=0 ; ie_TouchFunc == touchF_NULL) && (ent->e_TouchFunc == touchF_NULL) ) { + continue; + } + if ( !( hit->contents & CONTENTS_TRIGGER ) ) { + continue; + } + + // use seperate code for determining if an item is picked up + // so you don't have to actually contact its bounding box + /* + if ( hit->s.eType == ET_ITEM ) { + if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { + continue; + } + } else */ + { + if ( !gi.EntityContact( mins, maxs, hit ) ) { + continue; + } + } + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->e_TouchFunc != touchF_NULL ) { + GEntity_TouchFunc(hit, ent, &trace); + } + + if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) { + GEntity_TouchFunc( ent, hit, &trace ); + } + } +} + + +/* +============ +G_MoverTouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg ) +{ + int i, num; + float step, stepSize, dist; + gentity_t *touch[MAX_GENTITIES], *hit; + trace_t trace; + vec3_t mins, maxs, dir, size, checkSpot; + const vec3_t range = { 40, 40, 52 }; + + // non-moving movers don't hit triggers! + if ( !VectorLengthSquared( ent->s.pos.trDelta ) ) + { + return; + } + + VectorSubtract( ent->mins, ent->maxs, size ); + stepSize = VectorLength( size ); + if ( stepSize < 1 ) + { + stepSize = 1; + } + + VectorSubtract( ent->currentOrigin, oldOrg, dir ); + dist = VectorNormalize( dir ); + for ( step = 0; step <= dist; step += stepSize ) + { + VectorMA( ent->currentOrigin, step, dir, checkSpot ); + VectorSubtract( checkSpot, range, mins ); + VectorAdd( checkSpot, range, maxs ); + + num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( checkSpot, ent->mins, mins ); + VectorAdd( checkSpot, ent->maxs, maxs ); + + for ( i=0 ; is.eType != ET_PUSH_TRIGGER ) + { + continue; + } + + if ( hit->e_TouchFunc == touchF_NULL ) + { + continue; + } + + if ( !( hit->contents & CONTENTS_TRIGGER ) ) + { + continue; + } + + + if ( !gi.EntityContact( mins, maxs, hit ) ) + { + continue; + } + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->e_TouchFunc != touchF_NULL ) + { + GEntity_TouchFunc(hit, ent, &trace); + } + } + } +} + +void G_MatchPlayerWeapon( gentity_t *ent ) +{ + if ( g_entities[0].inuse && g_entities[0].client ) + {//player is around + int newWeap; + if ( g_entities[0].client->ps.weapon > WP_CONCUSSION ) + { + newWeap = WP_BLASTER_PISTOL; + } + else + { + newWeap = g_entities[0].client->ps.weapon; + } + if ( newWeap != WP_NONE && ent->client->ps.weapon != newWeap ) + { + G_RemoveWeaponModels( ent ); + ent->client->ps.stats[STAT_WEAPONS] = ( 1 << newWeap ); + ent->client->ps.ammo[weaponData[newWeap].ammoIndex] = 999; + ChangeWeapon( ent, newWeap ); + ent->client->ps.weapon = newWeap; + ent->client->ps.weaponstate = WEAPON_READY; + if ( newWeap == WP_SABER ) + { + //FIXME: AddSound/Sight Event + int numSabers = WP_SaberInitBladeData( ent ); + WP_SaberAddG2SaberModels( ent ); + for ( int saberNum = 0; saberNum < numSabers; saberNum++ ) + { + //G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saber[saberNum].model, ent->handRBolt, 0 ); + ent->client->ps.saber[saberNum].type = g_entities[0].client->ps.saber[saberNum].type; + for ( int bladeNum = 0; bladeNum < ent->client->ps.saber[saberNum].numBlades; bladeNum++ ) + { + ent->client->ps.saber[saberNum].blade[0].active = g_entities[0].client->ps.saber[saberNum].blade[bladeNum].active; + ent->client->ps.saber[saberNum].blade[0].length = g_entities[0].client->ps.saber[saberNum].blade[bladeNum].length; + } + } + ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel; + ent->client->ps.saberStylesKnown = g_entities[0].client->ps.saberStylesKnown; + } + else + { + G_CreateG2AttachedWeaponModel( ent, weaponData[newWeap].weaponMdl, ent->handRBolt, 0 ); + } + } + } +} + +void G_NPCMunroMatchPlayerWeapon( gentity_t *ent ) +{ + //special uber hack for cinematic players to match player's weapon + if ( !in_camera ) + { + if ( ent && ent->client && ent->NPC && (ent->NPC->aiFlags&NPCAI_MATCHPLAYERWEAPON) ) + {//we're a Player NPC + G_MatchPlayerWeapon( ent ); + } + } +} + +/* +================== +ClientTimerActions + +Actions that happen once a second +================== +*/ +void ClientTimerActions( gentity_t *ent, int msec ) { + gclient_t *client; + + client = ent->client; + client->timeResidual += msec; + + while ( client->timeResidual >= 1000 ) + { + client->timeResidual -= 1000; + + if ( ent->s.weapon != WP_NONE ) + { + ent->client->sess.missionStats.weaponUsed[ent->s.weapon]++; + } + // if we've got the seeker powerup, see if we can shoot it at someone +/* if ( ent->client->ps.powerups[PW_SEEKER] > level.time ) + { + vec3_t seekerPos, dir; + gentity_t *enemy = SeekerAcquiresTarget( ent, seekerPos ); + + if ( enemy != NULL ) // set the client's enemy to a valid target + { + FireSeeker( ent, enemy, seekerPos, dir ); + + gentity_t *tent; + tent = G_TempEntity( seekerPos, EV_POWERUP_SEEKER_FIRE ); + VectorCopy( dir, tent->pos1 ); + tent->s.eventParm = ent->s.number; + } + }*/ + if ( (ent->flags&FL_OVERCHARGED_HEALTH) ) + {//need to gradually reduce health back to max + if ( ent->health > ent->client->ps.stats[STAT_MAX_HEALTH] ) + {//decrement it + ent->health--; + ent->client->ps.stats[STAT_HEALTH] = ent->health; + } + else + {//done + ent->flags &= ~FL_OVERCHARGED_HEALTH; + } + } + } +} + +/* +==================== +ClientIntermissionThink +==================== +*/ +static qboolean ClientCinematicThink( gclient_t *client ) { + client->ps.eFlags &= ~EF_FIRING; + + // swap button actions + client->oldbuttons = client->buttons; + client->buttons = client->usercmd.buttons; + if ( client->buttons & ( BUTTON_USE ) & ( client->oldbuttons ^ client->buttons ) ) { + return( qtrue ); + } + return( qfalse ); +} + + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +extern void WP_SabersDamageTrace( gentity_t *ent, qboolean noEffects = qfalse ); +extern void WP_SaberUpdateOldBladeData( gentity_t *ent ); +void ClientEvents( gentity_t *ent, int oldEventSequence ) { + int i; + int event; + gclient_t *client; + //int damage; + qboolean fired; + + client = ent->client; + + fired = qfalse; + + for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { + event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; + + switch ( event ) { + case EV_FALL_MEDIUM: + case EV_FALL_FAR://these come from bg_pmove, PM_CrashLand + if ( ent->s.eType != ET_PLAYER ) { + break; // not in the player model + } + /* + //FIXME: isn't there a more accurate way to calculate damage from falls? + if ( event == EV_FALL_FAR ) + { + damage = 50; + } + else + { + damage = 25; + } + ent->painDebounceTime = level.time + 200; // no normal pain sound + G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); + */ + break; + + case EV_FIRE_WEAPON: +#ifndef FINAL_BUILD + if ( fired ) { + gi.Printf( "DOUBLE EV_FIRE_WEAPON AND-OR EV_ALT_FIRE!!\n" ); + } +#endif + fired = qtrue; + FireWeapon( ent, qfalse ); +#ifdef _XBOX + extern int Sys_Milliseconds(); + if (ent->s.clientNum == 0) + g_lastFireTime = Sys_Milliseconds(); +#endif + break; + + case EV_ALT_FIRE: +#ifndef FINAL_BUILD + if ( fired ) { + gi.Printf( "DOUBLE EV_FIRE_WEAPON AND-OR EV_ALT_FIRE!!\n" ); + } +#endif + fired = qtrue; + FireWeapon( ent, qtrue ); +#ifdef _XBOX + if (ent->s.clientNum == 0) + g_lastFireTime = Sys_Milliseconds(); +#endif + break; + + default: + break; + } + } + //by the way, if you have your saber in hand and it's on, do the damage trace + if ( client->ps.weapon == WP_SABER ) + { + if ( g_timescale->value >= 1.0f || !(client->ps.forcePowersActive&(1<ps.saberDamageDebounceTime - level.time > wait ) + {//when you unpause the game with force speed on, the time gets *really* wiggy... + client->ps.saberDamageDebounceTime = level.time + wait; + } + if ( client->ps.saberDamageDebounceTime <= level.time ) + { + WP_SabersDamageTrace( ent ); + WP_SaberUpdateOldBladeData( ent ); + /* + if ( g_timescale->value&&client->ps.clientNum==0&&!player_locked&&!MatrixMode&&client->ps.forcePowersActive&(1<value ); + } + */ + client->ps.saberDamageDebounceTime = level.time + wait; + } + } + } +} + +void G_ThrownDeathAnimForDeathAnim( gentity_t *hitEnt, vec3_t impactPoint ) +{ + int anim = -1; + if ( !hitEnt || !hitEnt->client ) + { + return; + } + switch ( hitEnt->client->ps.legsAnim ) + { + case BOTH_DEATH9://fall to knees, fall over + case BOTH_DEATH10://fall to knees, fall over + case BOTH_DEATH11://fall to knees, fall over + case BOTH_DEATH13://stumble back, fall over + case BOTH_DEATH17://jerky fall to knees, fall over + case BOTH_DEATH18://grab gut, fall to knees, fall over + case BOTH_DEATH19://grab gut, fall to knees, fall over + case BOTH_DEATH20://grab shoulder, fall forward + case BOTH_DEATH21://grab shoulder, fall forward + case BOTH_DEATH3://knee collapse, twist & fall forward + case BOTH_DEATH7://knee collapse, twist & fall forward + { + vec3_t dir2Impact, fwdAngles, facing; + VectorSubtract( impactPoint, hitEnt->currentOrigin, dir2Impact ); + dir2Impact[2] = 0; + VectorNormalize( dir2Impact ); + VectorSet( fwdAngles, 0, hitEnt->client->ps.viewangles[YAW], 0 ); + AngleVectors( fwdAngles, facing, NULL, NULL ); + float dot = DotProduct( facing, dir2Impact );//-1 = hit in front, 0 = hit on side, 1 = hit in back + if ( dot > 0.5f ) + {//kicked in chest, fly backward + switch ( Q_irand( 0, 4 ) ) + {//FIXME: don't start at beginning of anim? + case 0: + anim = BOTH_DEATH1;//thrown backwards + break; + case 1: + anim = BOTH_DEATH2;//fall backwards + break; + case 2: + anim = BOTH_DEATH15;//roll over backwards + break; + case 3: + anim = BOTH_DEATH22;//fast fall back + break; + case 4: + anim = BOTH_DEATH23;//fast fall back + break; + } + } + else if ( dot < -0.5f ) + {//kicked in back, fly forward + switch ( Q_irand( 0, 5 ) ) + {//FIXME: don't start at beginning of anim? + case 0: + anim = BOTH_DEATH14; + break; + case 1: + anim = BOTH_DEATH24; + break; + case 2: + anim = BOTH_DEATH25; + break; + case 3: + anim = BOTH_DEATH4;//thrown forwards + break; + case 4: + anim = BOTH_DEATH5;//thrown forwards + break; + case 5: + anim = BOTH_DEATH16;//thrown forwards + break; + } + } + else + {//hit on side, spin + switch ( Q_irand( 0, 2 ) ) + {//FIXME: don't start at beginning of anim? + case 0: + anim = BOTH_DEATH12; + break; + case 1: + anim = BOTH_DEATH14; + break; + case 2: + anim = BOTH_DEATH15; + break; + case 3: + anim = BOTH_DEATH6; + break; + case 4: + anim = BOTH_DEATH8; + break; + } + } + } + break; + } + if ( anim != -1 ) + { + NPC_SetAnim( hitEnt, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } +} + +gentity_t *G_KickTrace( gentity_t *ent, vec3_t kickDir, float kickDist, vec3_t kickEnd, int kickDamage, float kickPush, qboolean doSoundOnWalls ) +{ + vec3_t traceOrg, traceEnd, kickMins={-2,-2,-2}, kickMaxs={2,2,2}; + trace_t trace; + gentity_t *hitEnt = NULL; + //FIXME: variable kick height? + if ( kickEnd && !VectorCompare( kickEnd, vec3_origin ) ) + {//they passed us the end point of the trace, just use that + //this makes the trace flat + VectorSet( traceOrg, ent->currentOrigin[0], ent->currentOrigin[1], kickEnd[2] ); + VectorCopy( kickEnd, traceEnd ); + } + else + {//extrude + VectorSet( traceOrg, ent->currentOrigin[0], ent->currentOrigin[1], ent->currentOrigin[2]+ent->maxs[2]*0.5f ); + VectorMA( traceOrg, kickDist, kickDir, traceEnd ); + } + + gi.trace( &trace, traceOrg, kickMins, kickMaxs, traceEnd, ent->s.number, MASK_SHOT );//clipmask ok? + if ( trace.fraction < 1.0f && !trace.startsolid && !trace.allsolid && trace.entityNum < ENTITYNUM_NONE ) + { + hitEnt = &g_entities[trace.entityNum]; + if ( ent->client->ps.lastKickedEntNum != trace.entityNum ) + { + TIMER_Remove( ent, "kickSoundDebounce" ); + ent->client->ps.lastKickedEntNum = trace.entityNum; + } + if ( hitEnt ) + {//we hit an entity + if ( hitEnt->client ) + { + if ( !(hitEnt->client->ps.pm_flags&PMF_TIME_KNOCKBACK) + && TIMER_Done( hitEnt, "kickedDebounce" ) )//not already flying through air? Intended to stop multiple hits, but... + {//FIXME: this should not always work + if ( PM_InKnockDown( &hitEnt->client->ps ) + && !PM_InGetUp( &hitEnt->client->ps ) ) + {//don't hit people who are knocked down or being knocked down (okay to hit people getting up, though) + return NULL; + } + if ( PM_InRoll( &hitEnt->client->ps ) ) + {//can't hit people who are rolling + return NULL; + } + //don't hit same ent more than once per kick + if ( hitEnt->takedamage ) + {//hurt it + G_Damage( hitEnt, ent, ent, kickDir, trace.endpos, kickDamage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_KILL, MOD_MELEE ); + } + //do kick hit sound and impact effect + if ( TIMER_Done( ent, "kickSoundDebounce" ) ) + { + if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) + { + G_Sound( ent, G_SoundIndex( "sound/movers/objects/saber_slam" ) ); + } + else + { + vec3_t fxOrg, fxDir; + VectorCopy( kickDir, fxDir ); + VectorMA( trace.endpos, Q_flrand( 5.0f, 10.0f ), fxDir, fxOrg ); + VectorScale( fxDir, -1, fxDir ); + G_PlayEffect( G_EffectIndex( "melee/kick_impact" ), fxOrg, fxDir ); + //G_Sound( ent, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + } + TIMER_Set( ent, "kickSoundDebounce", 2000 ); + } + TIMER_Set( hitEnt, "kickedDebounce", 1000 ); + if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) + {//hit in head + if ( hitEnt->health > 0 ) + {//knock down + if ( kickPush >= 150.0f/*75.0f*/ && !Q_irand( 0, 1 ) ) + {//knock them down + if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( hitEnt, kickDir, kickPush/3.0f ); + } + G_Knockdown( hitEnt, ent, kickDir, 300, qtrue ); + } + else + {//force them to play a pain anim + if ( hitEnt->s.number < MAX_CLIENTS ) + { + NPC_SetPainEvent( hitEnt ); + } + else + { + GEntity_PainFunc( hitEnt, ent, ent, hitEnt->currentOrigin, 0, MOD_MELEE ); + } + } + //just so we don't hit him again... + hitEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + hitEnt->client->ps.pm_time = 100; + } + else + { + if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( hitEnt, kickDir, kickPush ); + } + //see if we should play a better looking death on them + G_ThrownDeathAnimForDeathAnim( hitEnt, trace.endpos ); + } + } + else if ( ent->client->ps.legsAnim == BOTH_GETUP_BROLL_B + || ent->client->ps.legsAnim == BOTH_GETUP_BROLL_F + || ent->client->ps.legsAnim == BOTH_GETUP_FROLL_B + || ent->client->ps.legsAnim == BOTH_GETUP_FROLL_F ) + { + if ( hitEnt->health > 0 ) + {//knock down + if ( hitEnt->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//he's in the air? Send him flying back + if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( hitEnt, kickDir, kickPush ); + } + } + else + { + //just so we don't hit him again... + hitEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + hitEnt->client->ps.pm_time = 100; + } + //knock them down + G_Knockdown( hitEnt, ent, kickDir, 300, qtrue ); + } + else + { + if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( hitEnt, kickDir, kickPush ); + } + //see if we should play a better looking death on them + G_ThrownDeathAnimForDeathAnim( hitEnt, trace.endpos ); + } + } + else if ( hitEnt->health <= 0 ) + {//we kicked a dead guy + //throw harder - FIXME: no matter how hard I push them, they don't go anywhere... corpses use less physics??? + if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( hitEnt, kickDir, kickPush*4 ); + } + //see if we should play a better looking death on them + G_ThrownDeathAnimForDeathAnim( hitEnt, trace.endpos ); + } + else + { + if ( !(hitEnt->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( hitEnt, kickDir, kickPush ); + } + if ( kickPush >= 150.0f/*75.0f*/ && !Q_irand( 0, 2 ) ) + { + G_Knockdown( hitEnt, ent, kickDir, 300, qtrue ); + } + else + { + G_Knockdown( hitEnt, ent, kickDir, kickPush, qtrue ); + } + } + } + } + else + {//FIXME: don't do this in repeated frames... only allow 1 frame in kick to hit wall? The most extended one? Pass in a bool on that frame. + if ( doSoundOnWalls ) + {//do kick hit sound and impact effect + if ( TIMER_Done( ent, "kickSoundDebounce" ) ) + { + if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) + { + G_Sound( ent, G_SoundIndex( "sound/movers/objects/saber_slam" ) ); + } + else + { + G_PlayEffect( G_EffectIndex( "melee/kick_impact" ), trace.endpos, trace.plane.normal ); + //G_Sound( ent, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + } + TIMER_Set( ent, "kickSoundDebounce", 2000 ); + } + } + } + } + } + return (hitEnt); +} + +qboolean G_CheckRollSafety( gentity_t *self, int anim, float testDist ) +{ + vec3_t forward, right, testPos, angles; + trace_t trace; + int contents = (CONTENTS_SOLID|CONTENTS_BOTCLIP); + + if ( !self || !self->client ) + { + return qfalse; + } + + if ( self->s.number < MAX_CLIENTS ) + {//player + contents |= CONTENTS_PLAYERCLIP; + } + else + {//NPC + contents |= CONTENTS_MONSTERCLIP; + } + if ( PM_InAttackRoll( self->client->ps.legsAnim ) ) + {//we don't care if people are in the way, we'll knock them down! + contents &= ~CONTENTS_BODY; + } + angles[PITCH] = angles[ROLL] = 0; + angles[YAW] = self->client->ps.viewangles[YAW];//Add ucmd.angles[YAW]? + AngleVectors( angles, forward, right, NULL ); + + switch ( anim ) + { + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_R: + VectorMA( self->currentOrigin, testDist, right, testPos ); + break; + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_FROLL_L: + VectorMA( self->currentOrigin, -testDist, right, testPos ); + break; + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_FROLL_F: + VectorMA( self->currentOrigin, testDist, forward, testPos ); + break; + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_FROLL_B: + VectorMA( self->currentOrigin, -testDist, forward, testPos ); + break; + default://FIXME: add normal rolls? Make generic for any forced-movement anim? + return qtrue; + break; + } + + gi.trace( &trace, self->currentOrigin, self->mins, self->maxs, testPos, self->s.number, contents ); + if ( trace.fraction < 1.0f + || trace.allsolid + || trace.startsolid ) + {//inside something or will hit something + return qfalse; + } + return qtrue; +} + +void G_CamPullBackForLegsAnim( gentity_t *ent, qboolean useTorso = qfalse ) +{ + if ( (ent->s.number < MAX_CLIENTS||G_ControlledByPlayer(ent)) ) + { + float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (useTorso?(animNumber_t)ent->client->ps.torsoAnim:(animNumber_t)ent->client->ps.legsAnim) ); + float elapsedTime = (float)(animLength-(useTorso?ent->client->ps.torsoAnimTimer:ent->client->ps.legsAnimTimer)); + float backDist = 0; + if ( elapsedTime < animLength/2.0f ) + {//starting anim + backDist = (elapsedTime/animLength)*120.0f; + } + else + {//ending anim + backDist = ((animLength-elapsedTime)/animLength)*120.0f; + } + cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG; + cg.overrides.thirdPersonRange = cg_thirdPersonRange.value+backDist; + } +} + +void G_CamCircleForLegsAnim( gentity_t *ent ) +{ + if ( (ent->s.number < MAX_CLIENTS||G_ControlledByPlayer(ent)) ) + { + float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.legsAnim ); + float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer); + float angle = 0; + angle = (elapsedTime/animLength)*360.0f; + cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; + cg.overrides.thirdPersonAngle = cg_thirdPersonAngle.value+angle; + } +} + +qboolean G_GrabClient( gentity_t *ent, usercmd_t *ucmd ) +{ + gentity_t *bestEnt = NULL, *radiusEnts[ 128 ]; + int numEnts; + const float radius = 100.0f; + const float radiusSquared = (radius*radius); + float bestDistSq = (radiusSquared+1.0f), distSq; + int i; + vec3_t boltOrg; + + numEnts = G_GetEntsNearBolt( ent, radiusEnts, radius, ent->handRBolt, boltOrg ); + + for ( i = 0; i < numEnts; i++ ) + { + if ( !radiusEnts[i]->inuse ) + { + continue; + } + + if ( radiusEnts[i] == ent ) + {//Skip the rancor ent + continue; + } + + if ( !radiusEnts[i]->inuse || radiusEnts[i]->health <= 0 ) + {//must be alive + continue; + } + + if ( radiusEnts[i]->client == NULL ) + {//must be a client + continue; + } + + if ( (radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_RANCOR) + ||(radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_WAMPA) + ||(radiusEnts[i]->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) ) + {//can't be one being held + continue; + } + + if ( PM_LockedAnim( radiusEnts[i]->client->ps.torsoAnim ) + || PM_LockedAnim( radiusEnts[i]->client->ps.legsAnim ) ) + {//don't interrupt + continue; + } + if ( radiusEnts[i]->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//must be on ground + continue; + } + + if ( PM_InOnGroundAnim( &radiusEnts[i]->client->ps ) ) + {//must be standing up + continue; + } + + if ( fabs(radiusEnts[i]->currentOrigin[2]-ent->currentOrigin[2])>8.0f ) + {//have to be close in Z + continue; + } + + if ( !PM_HasAnimation( radiusEnts[i], BOTH_PLAYER_PA_1 ) ) + {//doesn't have matching anims + continue; + } + + distSq = DistanceSquared( radiusEnts[i]->currentOrigin, boltOrg ); + if ( distSq < bestDistSq ) + { + bestDistSq = distSq; + bestEnt = radiusEnts[i]; + } + } + + if ( bestEnt != NULL ) + { + int lockType = LOCK_KYLE_GRAB1; + if ( ucmd->forwardmove > 0 ) + { + lockType = LOCK_KYLE_GRAB3; + } + else if ( ucmd->forwardmove < 0 ) + { + lockType = LOCK_KYLE_GRAB2; + } + WP_SabersCheckLock2( ent, bestEnt, (sabersLockMode_t)lockType ); + return qtrue; + } + return qfalse; +} + +qboolean G_PullAttack( gentity_t *ent, usercmd_t *ucmd ) +{ + qboolean overridAngles = qfalse; + if ( ent->client->ps.torsoAnim == BOTH_PULL_IMPALE_STAB + || ent->client->ps.torsoAnim == BOTH_PULL_IMPALE_SWING ) + {//pulling + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + } + else if ( ent->client->ps.torsoAnim == BOTH_PULLED_INAIR_B + || ent->client->ps.torsoAnim == BOTH_PULLED_INAIR_F ) + {//being pulled + gentity_t *puller = &g_entities[ent->client->ps.pullAttackEntNum]; + if ( puller + && puller->inuse + && puller->client + && ( puller->client->ps.torsoAnim == BOTH_PULL_IMPALE_STAB + || puller->client->ps.torsoAnim == BOTH_PULL_IMPALE_SWING ) ) + { + vec3_t pullDir; + vec3_t pullPos; + //calc where to pull me to + /* + VectorCopy( puller->client->ps.saber[0].blade[0].muzzlePoint, pullPos ); + VectorMA( pullPos, puller->client->ps.saber[0].blade[0].length*0.5f, puller->client->ps.saber[0].blade[0].muzzleDir, pullPos ); + */ + vec3_t pullerFwd; + AngleVectors( puller->client->ps.viewangles, pullerFwd, NULL, NULL ); + VectorMA( puller->currentOrigin, (puller->maxs[0]*1.5f)+16.0f, pullerFwd, pullPos ); + //pull me towards that pos + VectorSubtract( pullPos, ent->currentOrigin, pullDir ); + float pullDist = VectorNormalize( pullDir ); + int sweetSpotTime = (puller->client->ps.torsoAnim == BOTH_PULL_IMPALE_STAB)?1250:1350; + float pullLength = PM_AnimLength( puller->client->clientInfo.animFileIndex, (animNumber_t)puller->client->ps.torsoAnim )-sweetSpotTime; + if ( pullLength <= 0.25f ) + { + pullLength = 0.25f; + } + //float pullTimeRemaining = ent->client->ps.pullAttackTime - level.time; + + //float pullSpeed = pullDist * (pullTimeRemaining/pullLength); + float pullSpeed = (pullDist*1000.0f)/pullLength; + + VectorScale( pullDir, pullSpeed, ent->client->ps.velocity ); + //slide, if necessary + ent->client->ps.pm_flags |= PMF_TIME_NOFRICTION; + ent->client->ps.pm_time = 100; + //make it so I don't actually hurt them when pulled at them... + ent->forcePuller = puller->s.number; + ent->forcePushTime = level.time + 100; // let the push effect last for 100 more ms + //look at him + PM_AdjustAnglesToPuller( ent, puller, ucmd, qboolean(ent->client->ps.legsAnim==BOTH_PULLED_INAIR_B) ); + overridAngles = qtrue; + //don't move + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + } + } + return overridAngles; +} + +void G_FixMins( gentity_t *ent ) +{ + //do a trace to make sure it's okay + float downdist = (DEFAULT_MINS_2-ent->mins[2]); + vec3_t end={ent->currentOrigin[0],ent->currentOrigin[1],ent->currentOrigin[2]+downdist}; + trace_t trace; + gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, end, ent->s.number, ent->clipmask ); + if ( !trace.allsolid && !trace.startsolid ) + { + if ( trace.fraction >= 1.0f ) + {//all clear + //drop the bottom of my bbox back down + ent->mins[2] = DEFAULT_MINS_2; + if ( ent->client ) + { + ent->client->ps.pm_flags &= ~PMF_FIX_MINS; + } + } + else + {//move me up so the bottom of my bbox will be where the trace ended, at least + //need to trace up, too + float updist = ((1.0f-trace.fraction) * -downdist); + end[2] = ent->currentOrigin[2]+updist; + gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, end, ent->s.number, ent->clipmask ); + if ( !trace.allsolid && !trace.startsolid ) + { + if ( trace.fraction >= 1.0f ) + {//all clear + //move me up + ent->currentOrigin[2] += updist; + //drop the bottom of my bbox back down + ent->mins[2] = DEFAULT_MINS_2; + G_SetOrigin( ent, ent->currentOrigin ); + gi.linkentity( ent ); + if ( ent->client ) + { + ent->client->ps.pm_flags &= ~PMF_FIX_MINS; + } + } + else + {//crap, no room to expand! + if ( ent->client->ps.legsAnimTimer <= 200 ) + {//at the end of the anim, and we can't leave ourselves like this + //so drop the maxs, put the mins back and move us up + ent->maxs[2] += downdist; + ent->currentOrigin[2] -= downdist; + ent->mins[2] = DEFAULT_MINS_2; + G_SetOrigin( ent, ent->currentOrigin ); + gi.linkentity( ent ); + //this way we'll be in a crouch when we're done + ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 0; + ent->client->ps.pm_flags |= PMF_DUCKED; + //FIXME: do we need to set a crouch anim here? + if ( ent->client ) + { + ent->client->ps.pm_flags &= ~PMF_FIX_MINS; + } + } + } + }//crap, stuck + } + }//crap, stuck! +} + + +qboolean G_CheckClampUcmd( gentity_t *ent, usercmd_t *ucmd ) +{ + qboolean overridAngles = qfalse; + + if ( ent->client->NPC_class == CLASS_PROTOCOL + || ent->client->NPC_class == CLASS_R2D2 + || ent->client->NPC_class == CLASS_R5D2 + || ent->client->NPC_class == CLASS_GONK + || ent->client->NPC_class == CLASS_MOUSE ) + {//these droids *cannot* strafe (looks bad) + if ( ucmd->rightmove ) + { + //clear the strafe + ucmd->rightmove = 0; + //movedir is invalid now, but PM_WalkMove will rebuild it from the ucmds, with the appropriate speed + VectorClear( ent->client->ps.moveDir ); + } + } + + if ( ent->client->ps.pullAttackEntNum < ENTITYNUM_WORLD + && ent->client->ps.pullAttackTime > level.time ) + { + return G_PullAttack( ent, ucmd ); + } + + if ( (ent->s.number < MAX_CLIENTS||G_ControlledByPlayer(ent)) + && ent->s.weapon == WP_MELEE + && ent->client->ps.torsoAnim == BOTH_KYLE_GRAB ) + {//see if we grabbed enemy + if ( ent->client->ps.torsoAnimTimer <= 200 ) + {//close to end of anim + if ( G_GrabClient( ent, ucmd ) ) + {//grabbed someone! + } + else + {//missed + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + ent->client->ps.weaponTime = ent->client->ps.torsoAnimTimer; + } + } + ucmd->rightmove = ucmd->forwardmove = ucmd->upmove = 0; + } + + if ( (!ent->s.number&&ent->aimDebounceTime>level.time) + || (ent->client->ps.pm_time && (ent->client->ps.pm_flags&PMF_TIME_KNOCKBACK)) + || ent->forcePushTime > level.time ) + {//being knocked back, can't do anything! + ucmd->buttons = 0; + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + ucmd->upmove = 0; + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + } + + overridAngles = (PM_AdjustAnglesForKnockdown( ent, ucmd, qfalse )?qtrue:overridAngles); + if ( PM_GetupAnimNoMove( ent->client->ps.legsAnim ) ) + { + ucmd->forwardmove = ucmd->rightmove = 0;//ucmd->upmove = ? + } + //check saberlock + if ( ent->client->ps.saberLockTime > level.time ) + {//in saberlock + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + if ( ent->client->ps.saberLockTime - level.time > SABER_LOCK_DELAYED_TIME ) + {//2 second delay before either can push + //FIXME: base on difficulty + ucmd->buttons = 0; + } + else + { + ucmd->buttons &= ~(ucmd->buttons&~BUTTON_ATTACK); + } + overridAngles = (PM_AdjustAnglesForSaberLock( ent, ucmd )?qtrue:overridAngles); + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + } + //check force drain + if ( (ent->client->ps.forcePowersActive&(1<forwardmove = ucmd->rightmove = ucmd->upmove = 0; + ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS); + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + } + + if ( ent->client->ps.saberMove == LS_A_LUNGE ) + {//can't move during lunge + ucmd->rightmove = ucmd->upmove = 0; + if ( ent->client->ps.legsAnimTimer > 500 && (ent->s.number || !player_locked) ) + { + ucmd->forwardmove = 127; + } + else + { + ucmd->forwardmove = 0; + } + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + } + + if ( ent->client->ps.legsAnim == BOTH_FORCEWALLRUNFLIP_ALT + && ent->client->ps.legsAnimTimer ) + { + vec3_t vFwd, fwdAng = {0,ent->currentAngles[YAW],0}; + AngleVectors( fwdAng, vFwd, NULL, NULL ); + if ( ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) + { + float savZ = ent->client->ps.velocity[2]; + VectorScale( vFwd, 100, ent->client->ps.velocity ); + ent->client->ps.velocity[2] = savZ; + } + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + overridAngles = (PM_AdjustAnglesForWallRunUpFlipAlt( ent, ucmd )?qtrue:overridAngles); + } + + if ( ent->client->ps.saberMove == LS_A_JUMP_T__B_ ) + {//can't move during leap + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE || (!ent->s.number && player_locked) ) + {//hit the ground + ucmd->forwardmove = 0; + } + ucmd->rightmove = ucmd->upmove = 0; + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + } + + if ( ent->client->ps.saberMove == LS_A_BACKFLIP_ATK + && ent->client->ps.legsAnim == BOTH_JUMPATTACK7 ) + {//backflip attack + if ( ent->client->ps.legsAnimTimer > 800 //not at end + && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK7 ) - ent->client->ps.legsAnimTimer >= 400 )//not in beginning + {//middle of anim + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//still on ground? + vec3_t yawAngles, backDir; + + //push backwards some? + VectorSet( yawAngles, 0, ent->client->ps.viewangles[YAW]+180, 0 ); + AngleVectors( yawAngles, backDir, 0, 0 ); + VectorScale( backDir, 100, ent->client->ps.velocity ); + + //jump! + ent->client->ps.velocity[2] = 180; + ent->client->ps.forceJumpZStart = ent->client->ps.origin[2];//so we don't take damage if we land at same height + ent->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + + //FIXME: NPCs yell? + G_AddEvent( ent, EV_JUMP, 0 ); + G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + ucmd->upmove = 0;//clear any actual jump command + } + } + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + } + + + if ( ent->client->ps.legsAnim == BOTH_JUMPATTACK6 + && ent->client->ps.legsAnimTimer > 0 ) + { + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + //FIXME: don't slide off people/obstacles? + if ( ent->client->ps.legsAnimTimer >= 100 //not at end + && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - ent->client->ps.legsAnimTimer >= 250 )//not in beginning + {//middle of anim + //push forward + ucmd->forwardmove = 127; + } + + if ( (ent->client->ps.legsAnimTimer >= 900 //not at end + && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - ent->client->ps.legsAnimTimer >= 950 ) //not in beginning + || ( ent->client->ps.legsAnimTimer >= 1600 + && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - ent->client->ps.legsAnimTimer >= 400 ) )//not in beginning + {//one of the two jumps + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//still on ground? + //jump! + ent->client->ps.velocity[2] = 250; + ent->client->ps.forceJumpZStart = ent->client->ps.origin[2];//so we don't take damage if we land at same height + ent->client->ps.pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; + //FIXME: NPCs yell? + G_AddEvent( ent, EV_JUMP, 0 ); + G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + else + {//FIXME: if this is the second jump, maybe we should just stop the anim? + } + } + //else + {//disallow turning unless in the middle frame when you're on the ground + //overridAngles = (PM_AdjustAnglesForDualJumpAttack( ent, ucmd )?qtrue:overridAngles); + } + + //dynamically reduce bounding box to let character sail over heads of enemies + if ( ( ent->client->ps.legsAnimTimer >= 1450 + && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - ent->client->ps.legsAnimTimer >= 400 ) + ||(ent->client->ps.legsAnimTimer >= 400 + && PM_AnimLength( ent->client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - ent->client->ps.legsAnimTimer >= 1100 ) ) + {//in a part of the anim that we're pretty much sideways in, raise up the mins + ent->mins[2] = 0; + ent->client->ps.pm_flags |= PMF_FIX_MINS; + } + else if ( (ent->client->ps.pm_flags&PMF_FIX_MINS) ) + {//drop the mins back down + G_FixMins( ent ); + } + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + } + else if ( (ent->client->ps.pm_flags&PMF_FIX_MINS) ) + { + G_FixMins( ent ); + } + + if ( ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_FL1 + && ent->client->ps.saberMove == LS_JUMPATTACK_STAFF_LEFT ) + || ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_FR1 + && ent->client->ps.saberMove == LS_JUMPATTACK_STAFF_RIGHT ) + || ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_RIGHT + && ent->client->ps.saberMove == LS_BUTTERFLY_RIGHT ) + || ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_LEFT + && ent->client->ps.saberMove == LS_BUTTERFLY_LEFT ) ) + {//forward flip/spin attack + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + + /*if ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_FL1 + || ent->client->ps.legsAnim == BOTH_BUTTERFLY_FR1 + || ent->client->ps.legsAnim == BOTH_BUTTERFLY_LEFT + || ent->client->ps.legsAnim == BOTH_BUTTERFLY RIGHT )*/ + { + //FIXME: don't slide off people/obstacles? + if ( ent->client->ps.legsAnim != BOTH_BUTTERFLY_LEFT + && ent->client->ps.legsAnim != BOTH_BUTTERFLY_RIGHT ) + { + if ( ent->client->ps.legsAnimTimer >= 100 //not at end + && PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.legsAnim ) - ent->client->ps.legsAnimTimer >= 250 )//not in beginning + {//middle of anim + //push forward + ucmd->forwardmove = 127; + } + } + + if ( ent->client->ps.legsAnimTimer >= 1700 && ent->client->ps.legsAnimTimer < 1800 ) + {//one of the two jumps + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//still on ground? + //jump! + ent->client->ps.velocity[2] = 250; + ent->client->ps.forceJumpZStart = ent->client->ps.origin[2];//so we don't take damage if we land at same height + ent->client->ps.pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; + //FIXME: NPCs yell? + G_AddEvent( ent, EV_JUMP, 0 ); + G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + else + {//FIXME: if this is the second jump, maybe we should just stop the anim? + } + } + + //disallow turning unless in the middle frame when you're on the ground + //overridAngles = (PM_AdjustAnglesForDualJumpAttack( ent, ucmd )?qtrue:overridAngles); + } + + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + } + + if ( ent->client->ps.legsAnim == BOTH_A7_SOULCAL + && ent->client->ps.saberMove == LS_STAFF_SOULCAL ) + {//forward spinning staff attack + ucmd->upmove = 0; + + if ( PM_CanRollFromSoulCal( &ent->client->ps ) ) + { + ucmd->upmove = -127; + } + else + { + ucmd->rightmove = 0; + //FIXME: don't slide off people/obstacles? + if ( ent->client->ps.legsAnimTimer >= 2750 ) + {//not at end + //push forward + ucmd->forwardmove = 64; + } + else + { + ucmd->forwardmove = 0; + } + } + if ( ent->client->ps.legsAnimTimer >= 2650 + && ent->client->ps.legsAnimTimer < 2850 ) + {//the jump + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//still on ground? + //jump! + ent->client->ps.velocity[2] = 250; + ent->client->ps.forceJumpZStart = ent->client->ps.origin[2];//so we don't take damage if we land at same height + ent->client->ps.pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; + //FIXME: NPCs yell? + G_AddEvent( ent, EV_JUMP, 0 ); + G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + } + + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + } + + if ( ent->client->ps.torsoAnim == BOTH_LK_DL_S_T_SB_1_W ) + { + G_CamPullBackForLegsAnim( ent, qtrue ); + } + + if ( ent->client->ps.torsoAnim == BOTH_A6_FB + || ent->client->ps.torsoAnim == BOTH_A6_LR ) + {//can't turn or move during dual attack + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + } + else if ( ent->client->ps.legsAnim == BOTH_ROLL_STAB ) + { + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + if ( ent->client->ps.legsAnimTimer ) + { + ucmd->upmove = -127; + } + if ( ent->client->ps.dualSabers && ent->client->ps.saber[1].Active() ) + { + G_CamPullBackForLegsAnim( ent ); + } + } + else if ( PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim ) ) + {//can't turn during Kyle's grappling + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + if ( ent->client->ps.legsAnim == BOTH_LK_DL_ST_T_SB_1_L ) + { + PM_CmdForRoll( &ent->client->ps, ucmd ); + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + ent->client->ps.speed = 400; + } + } + else if ( ent->client->ps.torsoAnim==BOTH_FORCE_DRAIN_GRAB_START + || ent->client->ps.torsoAnim==BOTH_FORCE_DRAIN_GRAB_HOLD + || ent->client->ps.torsoAnim==BOTH_FORCE_DRAIN_GRAB_END + || ent->client->ps.legsAnim==BOTH_FORCE_DRAIN_GRABBED ) + {//can't turn or move + if ( ent->s.number < MAX_CLIENTS + || G_ControlledByPlayer(ent) ) + {//player + float forceDrainAngle = 90.0f; + if ( ent->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START ) + {//starting drain + float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); + float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer); + float angle = (elapsedTime/animLength)*forceDrainAngle; + cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; + cg.overrides.thirdPersonAngle = cg_thirdPersonAngle.value+angle; + } + else if ( ent->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_HOLD ) + {//draining + cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; + cg.overrides.thirdPersonAngle = cg_thirdPersonAngle.value+forceDrainAngle; + } + else if ( ent->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_END ) + {//ending drain + float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); + float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer); + float angle = forceDrainAngle-((elapsedTime/animLength)*forceDrainAngle); + cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; + cg.overrides.thirdPersonAngle = cg_thirdPersonAngle.value+angle; + } + } + + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + overridAngles = PM_LockAngles( ent, ucmd )?qtrue:overridAngles; + } + else if ( ent->client->ps.legsAnim==BOTH_PLAYER_PA_1 + || ent->client->ps.legsAnim==BOTH_PLAYER_PA_2 + || ent->client->ps.legsAnim==BOTH_PLAYER_PA_3 + || ent->client->ps.legsAnim==BOTH_PLAYER_PA_3_FLY + || ent->client->ps.legsAnim==BOTH_KYLE_PA_1 + || ent->client->ps.legsAnim==BOTH_KYLE_PA_2 + || ent->client->ps.legsAnim==BOTH_KYLE_PA_3 ) + {//can't turn during Kyle's grappling + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + overridAngles = PM_AdjustAnglesForGrapple( ent, ucmd )?qtrue:overridAngles; + //if ( g_debugMelee->integer ) + {//actually do some damage during sequence + int damage = 0; + int dflags = (DAMAGE_NO_KNOCKBACK|DAMAGE_NO_ARMOR|DAMAGE_NO_KILL); + if ( TIMER_Done( ent, "grappleDamageDebounce" ) ) + { + switch ( ent->client->ps.legsAnim ) + { + case BOTH_PLAYER_PA_1: + if ( ent->client->ps.legsAnimTimer <= 3150 + && ent->client->ps.legsAnimTimer > 3050 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + if ( ent->s.number < MAX_CLIENTS ) + {//special case + damage = Q_irand( 1, 3 ); + } + else + { + damage = Q_irand( 3, 5 ); + } + } + if ( ent->client->ps.legsAnimTimer <= 1150 + && ent->client->ps.legsAnimTimer > 10500 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + if ( ent->s.number < MAX_CLIENTS ) + {//special case + damage = Q_irand( 3, 5 ); + } + else + { + damage = Q_irand( 5, 8 ); + } + } + if ( ent->client->ps.legsAnimTimer <= 100 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + if ( ent->s.number < MAX_CLIENTS ) + {//special case + damage = Q_irand( 5, 8 ); + } + else + { + damage = Q_irand( 10, 20 ); + } + dflags &= ~DAMAGE_NO_KILL; + } + break; + case BOTH_PLAYER_PA_2: + if ( ent->client->ps.legsAnimTimer <= 5700 + && ent->client->ps.legsAnimTimer > 5600 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + if ( ent->s.number < MAX_CLIENTS ) + {//special case + damage = Q_irand( 3, 6 ); + } + else + { + damage = Q_irand( 7, 10 ); + } + } + if ( ent->client->ps.legsAnimTimer <= 5200 + && ent->client->ps.legsAnimTimer > 5100 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + if ( ent->s.number < MAX_CLIENTS ) + {//special case + damage = Q_irand( 1, 3 ); + } + else + { + damage = Q_irand( 3, 5 ); + } + } + if ( ent->client->ps.legsAnimTimer <= 4550 + && ent->client->ps.legsAnimTimer > 4450 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + if ( ent->s.number < MAX_CLIENTS ) + {//special case + damage = Q_irand( 3, 6 ); + } + else + { + damage = Q_irand( 10, 15 ); + } + } + if ( ent->client->ps.legsAnimTimer <= 3550 + && ent->client->ps.legsAnimTimer > 3450 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + if ( ent->s.number < MAX_CLIENTS ) + {//special case + damage = Q_irand( 3, 6 ); + } + else + { + damage = Q_irand( 10, 20 ); + } + dflags &= ~DAMAGE_NO_KILL; + } + break; + case BOTH_PLAYER_PA_3: + if ( ent->client->ps.legsAnimTimer <= 3800 + && ent->client->ps.legsAnimTimer > 3700 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + if ( ent->s.number < MAX_CLIENTS ) + {//special case + damage = Q_irand( 2, 5 ); + } + else + { + damage = Q_irand( 5, 8 ); + } + } + if ( ent->client->ps.legsAnimTimer <= 3100 + && ent->client->ps.legsAnimTimer > 3000 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + if ( ent->s.number < MAX_CLIENTS ) + {//special case + damage = Q_irand( 2, 5 ); + } + else + { + damage = Q_irand( 5, 8 ); + } + } + if ( ent->client->ps.legsAnimTimer <= 1650 + && ent->client->ps.legsAnimTimer > 1550 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + if ( ent->s.number < MAX_CLIENTS ) + {//special case + damage = Q_irand( 3, 6 ); + } + else + { + damage = Q_irand( 7, 12 ); + } + } + break; + case BOTH_PLAYER_PA_3_FLY: + /* + if ( ent->s.number < MAX_CLIENTS ) + {//special case + if ( ent->client->ps.legsAnimTimer > PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME + && ent->client->ps.legsAnimTimer <= PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME + 100 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + damage = Q_irand( 4, 8 ); + dflags &= ~DAMAGE_NO_KILL; + } + } + else*/ if ( ent->client->ps.legsAnimTimer <= 100 ) + { + TIMER_Set( ent, "grappleDamageDebounce", 150 ); + damage = Q_irand( 10, 20 ); + dflags &= ~DAMAGE_NO_KILL; + } + break; + } + if ( damage ) + { + G_Damage( ent, ent->enemy, ent->enemy, NULL, ent->currentOrigin, damage, dflags, MOD_MELEE );//MOD_IMPACT? + } + } + } + qboolean clearMove = qtrue; + int endOfAnimTime = 100; + if ( ent->s.number < MAX_CLIENTS + && ent->client->ps.legsAnim == BOTH_PLAYER_PA_3_FLY ) + {//player holds extra long so you have more time to decide to do the quick getup + //endOfAnimTime += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + if ( ent->client->ps.legsAnimTimer <= endOfAnimTime )//PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME ) + { + clearMove = qfalse; + } + } + if ( clearMove ) + { + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + } + + if ( ent->client->ps.legsAnimTimer <= endOfAnimTime ) + {//pretty much done with the anim, so get up now + if ( ent->client->ps.legsAnim==BOTH_PLAYER_PA_3 ) + { + vec3_t ang = {10,ent->currentAngles[YAW],0}; + vec3_t throwDir; + AngleVectors( ang, throwDir, NULL, NULL ); + if ( !(ent->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( ent, throwDir, -100 ); + } + ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 0; + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_PLAYER_PA_3_FLY, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + ent->client->ps.weaponTime = ent->client->ps.legsAnimTimer; + /* + if ( ent->s.number < MAX_CLIENTS ) + {//player holds extra long so you have more time to decide to do the quick getup + ent->client->ps.legsAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + ent->client->ps.torsoAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + } + */ + //force-thrown - FIXME: start earlier? + ent->forcePushTime = level.time + 500; + } + else if ( ent->client->ps.legsAnim==BOTH_PLAYER_PA_1 ) + { + vec3_t ang = {10,ent->currentAngles[YAW],0}; + vec3_t throwDir; + AngleVectors( ang, throwDir, NULL, NULL ); + if ( !(ent->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( ent, throwDir, -100 ); + } + ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 0; + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_KNOCKDOWN2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + ent->client->ps.weaponTime = ent->client->ps.legsAnimTimer; + if ( ent->s.number < MAX_CLIENTS ) + {//player holds extra long so you have more time to decide to do the quick getup + ent->client->ps.legsAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + ent->client->ps.torsoAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + } + } + //FIXME: should end so you can do a getup + /* + else if ( ent->client->ps.legsAnim==BOTH_PLAYER_PA_2 ) + { + ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 0; + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_GETUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + ent->client->ps.weaponTime = ent->client->ps.legsAnimTimer; + } + */ + } + if ( d_slowmodeath->integer <= 3 + && ent->s.number < MAX_CLIENTS ) + {//no matrix effect, so slide the camera back and to the side + G_CamPullBackForLegsAnim( ent ); + G_CamCircleForLegsAnim( ent ); + } + } + else if ( ent->client->ps.legsAnim == BOTH_FORCELONGLEAP_START + || ent->client->ps.legsAnim == BOTH_FORCELONGLEAP_ATTACK + || ent->client->ps.legsAnim == BOTH_FORCELONGLEAP_LAND ) + {//can't turn during force leap + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + overridAngles = PM_AdjustAnglesForLongJump( ent, ucmd )?qtrue:overridAngles; + } + else if ( PM_KickingAnim( ent->client->ps.legsAnim ) + || ent->client->ps.legsAnim == BOTH_ARIAL_F1 + || ent->client->ps.legsAnim == BOTH_ARIAL_LEFT + || ent->client->ps.legsAnim == BOTH_ARIAL_RIGHT + || ent->client->ps.legsAnim == BOTH_CARTWHEEL_LEFT + || ent->client->ps.legsAnim == BOTH_CARTWHEEL_RIGHT + || ent->client->ps.legsAnim == BOTH_JUMPATTACK7 + || ent->client->ps.legsAnim == BOTH_BUTTERFLY_FL1 + || ent->client->ps.legsAnim == BOTH_BUTTERFLY_FR1 + || ent->client->ps.legsAnim == BOTH_BUTTERFLY_RIGHT + || ent->client->ps.legsAnim == BOTH_BUTTERFLY_LEFT + || ent->client->ps.legsAnim == BOTH_A7_SOULCAL ) + {//can't move, check for damage frame + if ( PM_KickingAnim( ent->client->ps.legsAnim ) ) + { + ucmd->forwardmove = ucmd->rightmove = 0; + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + } + vec3_t kickDir={0,0,0}, kickDir2={0,0,0}, kickEnd={0,0,0}, kickEnd2={0,0,0}, fwdAngs = {0,ent->client->ps.viewangles[YAW],0}; + float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.legsAnim ); + float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer); + float remainingTime = (animLength-elapsedTime); + float kickDist = (ent->maxs[0]*1.5f)+STAFF_KICK_RANGE+8.0f;//fudge factor of 8 + float kickDist2 = kickDist; + int kickDamage = Q_irand( 3, 8 ); + int kickDamage2 = Q_irand( 3, 8 ); + int kickPush = Q_flrand( 100.0f, 200.0f );//Q_flrand( 50.0f, 100.0f ); + int kickPush2 = Q_flrand( 100.0f, 200.0f );//Q_flrand( 50.0f, 100.0f ); + qboolean doKick = qfalse; + qboolean doKick2 = qfalse; + qboolean kickSoundOnWalls = qfalse; + //HMM... or maybe trace from origin to footRBolt/footLBolt? Which one? G2 trace? Will do hitLoc, if so... + if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) + { + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//front + doKick = qtrue; + if ( ent->handRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->handRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + } + else + { + switch ( ent->client->ps.legsAnim ) + { + case BOTH_A7_SOULCAL: + kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); + if ( elapsedTime >= 1400 && elapsedTime <= 1500 ) + {//right leg + doKick = qtrue; + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + break; + case BOTH_ARIAL_F1: + if ( elapsedTime >= 550 && elapsedTime <= 1000 ) + {//right leg + doKick = qtrue; + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + if ( elapsedTime >= 800 && elapsedTime <= 1200 ) + {//left leg + doKick2 = qtrue; + if ( ent->footLBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footLBolt, kickEnd2 ); + VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); + kickDir2[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir2 ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir2, NULL, NULL ); + } + } + break; + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + if ( elapsedTime >= 200 && elapsedTime <= 600 ) + {//lead leg + int footBolt = (ent->client->ps.legsAnim==BOTH_ARIAL_LEFT)?ent->footRBolt:ent->footLBolt;//mirrored anims + doKick = qtrue; + if ( footBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, footBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + if ( elapsedTime >= 400 && elapsedTime <= 850 ) + {//trailing leg + int footBolt = (ent->client->ps.legsAnim==BOTH_ARIAL_LEFT)?ent->footLBolt:ent->footRBolt;//mirrored anims + doKick2 = qtrue; + if ( footBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, footBolt, kickEnd2 ); + VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); + kickDir2[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir2 ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir2, NULL, NULL ); + } + } + break; + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + if ( elapsedTime >= 200 && elapsedTime <= 600 ) + {//lead leg + int footBolt = (ent->client->ps.legsAnim==BOTH_CARTWHEEL_LEFT)?ent->footRBolt:ent->footLBolt;//mirrored anims + doKick = qtrue; + if ( footBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, footBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + if ( elapsedTime >= 350 && elapsedTime <= 650 ) + {//trailing leg + int footBolt = (ent->client->ps.legsAnim==BOTH_CARTWHEEL_LEFT)?ent->footLBolt:ent->footRBolt;//mirrored anims + doKick2 = qtrue; + if ( footBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, footBolt, kickEnd2 ); + VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); + kickDir2[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir2 ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir2, NULL, NULL ); + } + } + break; + case BOTH_JUMPATTACK7: + if ( elapsedTime >= 300 && elapsedTime <= 900 ) + {//right knee + doKick = qtrue; + if ( ent->kneeRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->kneeRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + if ( elapsedTime >= 600 && elapsedTime <= 900 ) + {//left leg + doKick2 = qtrue; + if ( ent->footLBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footLBolt, kickEnd2 ); + VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); + kickDir2[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir2 ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir2, NULL, NULL ); + } + } + break; + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + if ( elapsedTime >= 950 && elapsedTime <= 1300 ) + {//lead leg + int footBolt = (ent->client->ps.legsAnim==BOTH_BUTTERFLY_FL1)?ent->footRBolt:ent->footLBolt;//mirrored anims + doKick = qtrue; + if ( footBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, footBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + if ( elapsedTime >= 1150 && elapsedTime <= 1600 ) + {//trailing leg + int footBolt = (ent->client->ps.legsAnim==BOTH_BUTTERFLY_FL1)?ent->footLBolt:ent->footRBolt;//mirrored anims + doKick2 = qtrue; + if ( footBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, footBolt, kickEnd2 ); + VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); + kickDir2[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir2 ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir2, NULL, NULL ); + } + } + break; + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + if ( (elapsedTime >= 100 && elapsedTime <= 450) + || (elapsedTime >= 1100 && elapsedTime <= 1350) ) + {//lead leg + int footBolt = (ent->client->ps.legsAnim==BOTH_BUTTERFLY_LEFT)?ent->footLBolt:ent->footRBolt;//mirrored anims + doKick = qtrue; + if ( footBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, footBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + if ( elapsedTime >= 900 && elapsedTime <= 1600 ) + {//trailing leg + int footBolt = (ent->client->ps.legsAnim==BOTH_BUTTERFLY_LEFT)?ent->footRBolt:ent->footLBolt;//mirrored anims + doKick2 = qtrue; + if ( footBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, footBolt, kickEnd2 ); + VectorSubtract( kickEnd2, ent->currentOrigin, kickDir2 ); + kickDir2[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir2 ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir2, NULL, NULL ); + } + } + break; + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//front + doKick = qtrue; + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + if ( ent->client->ps.legsAnim == BOTH_GETUP_BROLL_B + || ent->client->ps.legsAnim == BOTH_GETUP_FROLL_B ) + {//rolling back, pull back the view + G_CamPullBackForLegsAnim( ent ); + } + break; + case BOTH_A7_KICK_F_AIR: + kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); + kickSoundOnWalls = qtrue; + if ( elapsedTime >= 100 && remainingTime >= 500 ) + {//front + doKick = qtrue; + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + break; + case BOTH_A7_KICK_F: + kickSoundOnWalls = qtrue; + //FIXME: push forward? + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//front + doKick = qtrue; + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + break; + case BOTH_A7_KICK_B_AIR: + kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); + kickSoundOnWalls = qtrue; + if ( elapsedTime >= 100 && remainingTime >= 400 ) + {//back + doKick = qtrue; + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + VectorScale( kickDir, -1, kickDir ); + } + } + break; + case BOTH_A7_KICK_B: + kickSoundOnWalls = qtrue; + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//back + doKick = qtrue; + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + VectorScale( kickDir, -1, kickDir ); + } + } + break; + case BOTH_A7_KICK_R_AIR: + kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); + kickSoundOnWalls = qtrue; + if ( elapsedTime >= 150 && remainingTime >= 300 ) + {//left + doKick = qtrue; + if ( ent->footLBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footLBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, NULL, kickDir, NULL ); + VectorScale( kickDir, -1, kickDir ); + } + } + break; + case BOTH_A7_KICK_R: + kickSoundOnWalls = qtrue; + //FIXME: push right? + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//right + doKick = qtrue; + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, NULL, kickDir, NULL ); + } + } + break; + case BOTH_A7_KICK_L_AIR: + kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); + kickSoundOnWalls = qtrue; + if ( elapsedTime >= 150 && remainingTime >= 300 ) + {//left + doKick = qtrue; + if ( ent->footLBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footLBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, NULL, kickDir, NULL ); + VectorScale( kickDir, -1, kickDir ); + } + } + break; + case BOTH_A7_KICK_L: + kickSoundOnWalls = qtrue; + //FIXME: push left? + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//left + doKick = qtrue; + if ( ent->footLBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footLBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, NULL, kickDir, NULL ); + VectorScale( kickDir, -1, kickDir ); + } + } + break; + case BOTH_A7_KICK_S: + kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + if ( elapsedTime >= 550 + && elapsedTime <= 1050 ) + { + doKick = qtrue; + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is + VectorMA( kickEnd, 8.0f, kickDir, kickEnd ); + } + } + else + {//guess + if ( elapsedTime >= 400 && elapsedTime < 500 ) + {//front + doKick = qtrue; + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + else if ( elapsedTime >= 500 && elapsedTime < 600 ) + {//front-right? + doKick = qtrue; + fwdAngs[YAW] += 45; + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + else if ( elapsedTime >= 600 && elapsedTime < 700 ) + {//right + doKick = qtrue; + AngleVectors( fwdAngs, NULL, kickDir, NULL ); + } + else if ( elapsedTime >= 700 && elapsedTime < 800 ) + {//back-right? + doKick = qtrue; + fwdAngs[YAW] += 45; + AngleVectors( fwdAngs, NULL, kickDir, NULL ); + } + else if ( elapsedTime >= 800 && elapsedTime < 900 ) + {//back + doKick = qtrue; + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + VectorScale( kickDir, -1, kickDir ); + } + else if ( elapsedTime >= 900 && elapsedTime < 1000 ) + {//back-left? + doKick = qtrue; + fwdAngs[YAW] += 45; + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + else if ( elapsedTime >= 1000 && elapsedTime < 1100 ) + {//left + doKick = qtrue; + AngleVectors( fwdAngs, NULL, kickDir, NULL ); + VectorScale( kickDir, -1, kickDir ); + } + else if ( elapsedTime >= 1100 && elapsedTime < 1200 ) + {//front-left? + doKick = qtrue; + fwdAngs[YAW] += 45; + AngleVectors( fwdAngs, NULL, kickDir, NULL ); + VectorScale( kickDir, -1, kickDir ); + } + } + break; + case BOTH_A7_KICK_BF: + kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); + if ( elapsedTime < 1500 ) + {//auto-aim! + overridAngles = PM_AdjustAnglesForBFKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles; + //FIXME: if we haven't done the back kick yet and there's no-one there to + // kick anymore, go into some anim that returns us to our base stance + } + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + if ( ( elapsedTime >= 750 && elapsedTime < 850 ) + || ( elapsedTime >= 1400 && elapsedTime < 1500 ) ) + {//right, though either would do + doKick = qtrue; + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is + VectorMA( kickEnd, 8, kickDir, kickEnd ); + } + } + else + {//guess + if ( elapsedTime >= 250 && elapsedTime < 350 ) + {//front + doKick = qtrue; + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + else if ( elapsedTime >= 350 && elapsedTime < 450 ) + {//back + doKick = qtrue; + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + VectorScale( kickDir, -1, kickDir ); + } + } + break; + case BOTH_A7_KICK_RL: + kickSoundOnWalls = qtrue; + kickPush = Q_flrand( 150.0f, 250.0f );//Q_flrand( 75.0f, 125.0f ); + //FIXME: auto aim at enemies on the side of us? + //overridAngles = PM_AdjustAnglesForRLKick( ent, ucmd, fwdAngs, qboolean(elapsedTime<850) )?qtrue:overridAngles; + if ( elapsedTime >= 250 && elapsedTime < 350 ) + {//right + doKick = qtrue; + if ( ent->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footRBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is + VectorMA( kickEnd, 8, kickDir, kickEnd ); + } + else + {//guess + AngleVectors( fwdAngs, NULL, kickDir, NULL ); + } + } + else if ( elapsedTime >= 350 && elapsedTime < 450 ) + {//left + doKick = qtrue; + if ( ent->footLBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ent->footLBolt, kickEnd ); + VectorSubtract( kickEnd, ent->currentOrigin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + //NOTE: have to fudge this a little because it's not getting enough range with the anim as-is + VectorMA( kickEnd, 8, kickDir, kickEnd ); + } + else + {//guess + AngleVectors( fwdAngs, NULL, kickDir, NULL ); + VectorScale( kickDir, -1, kickDir ); + } + } + break; + } + } + if ( doKick ) + { + G_KickTrace( ent, kickDir, kickDist, kickEnd, kickDamage, kickPush, kickSoundOnWalls ); + } + if ( doKick2 ) + { + G_KickTrace( ent, kickDir2, kickDist2, kickEnd2, kickDamage2, kickPush2, kickSoundOnWalls ); + } + } + else if ( ent->client->ps.saberMove == LS_DUAL_FB ) + { + //pull back the view + G_CamPullBackForLegsAnim( ent ); + } + else if ( ent->client->ps.saberMove == LS_A_BACK || ent->client->ps.saberMove == LS_A_BACK_CR + || ent->client->ps.saberMove == LS_A_BACKSTAB ) + {//can't move or turn during back attacks + ucmd->forwardmove = ucmd->rightmove = 0; + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + } + if ( (overridAngles = (PM_AdjustAnglesForBackAttack( ent, ucmd )?qtrue:overridAngles)) == qtrue ) + { + //pull back the view + G_CamPullBackForLegsAnim( ent ); + } + } + else if ( ent->client->ps.torsoAnim == BOTH_WALL_FLIP_BACK1 + || ent->client->ps.torsoAnim == BOTH_WALL_FLIP_BACK2 + || ent->client->ps.legsAnim == BOTH_FORCEWALLRUNFLIP_END + || ent->client->ps.legsAnim == BOTH_FORCEWALLREBOUND_BACK ) + { + //pull back the view + G_CamPullBackForLegsAnim( ent ); + } + else if ( ent->client->ps.torsoAnim == BOTH_A6_SABERPROTECT ) + { + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + ent->client->ps.forceJumpCharge = 0; + } + if ( !ent->s.number ) + { + float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); + float elapsedTime = (float)(animLength-ent->client->ps.torsoAnimTimer); + float backDist = 0; + if ( elapsedTime <= 300.0f ) + {//starting anim + backDist = (elapsedTime/300.0f)*90.0f; + } + else if ( ent->client->ps.torsoAnimTimer <= 300.0f ) + {//ending anim + backDist = (ent->client->ps.torsoAnimTimer/300.0f)*90.0f; + } + else + {//in middle of anim + backDist = 90.0f; + } + //back off and look down + cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_POF); + cg.overrides.thirdPersonRange = cg_thirdPersonRange.value+backDist; + cg.overrides.thirdPersonPitchOffset = cg_thirdPersonPitchOffset.value+(backDist/2.0f); + } + overridAngles = (PM_AdjustAnglesForSpinProtect( ent, ucmd )?qtrue:overridAngles); + } + else if ( ent->client->ps.legsAnim == BOTH_A3_SPECIAL ) + {//push forward + float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); + float elapsedTime = (float)(animLength-ent->client->ps.torsoAnimTimer); + ucmd->upmove = ucmd->rightmove = 0; + ucmd->forwardmove = 0; + if ( elapsedTime >= 350 && elapsedTime < 1500 ) + {//push forward + ucmd->forwardmove = 64; + ent->client->ps.speed = 200.0f; + } + //FIXME: pull back camera? + } + else if ( ent->client->ps.legsAnim == BOTH_A2_SPECIAL ) + {//push forward + ucmd->upmove = ucmd->rightmove = 0; + ucmd->forwardmove = 0; + if ( ent->client->ps.legsAnimTimer > 200.0f ) + { + float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); + float elapsedTime = (float)(animLength-ent->client->ps.torsoAnimTimer); + if ( elapsedTime < 750 + || (elapsedTime >= 1650 && elapsedTime < 2400) ) + {//push forward + ucmd->forwardmove = 64; + ent->client->ps.speed = 200.0f; + } + } + //FIXME: pull back camera? + }//FIXME: fast special? + else if ( ent->client->ps.legsAnim == BOTH_A1_SPECIAL + && (ucmd->forwardmove || ucmd->rightmove || (VectorCompare( ent->client->ps.moveDir, vec3_origin )&&ent->client->ps.speed>0)) ) + {//moving during full-body fast special + ent->client->ps.legsAnimTimer = 0;//don't hold this legsAnim, allow them to run + //FIXME: just add this to the list of overridable special moves in PM_Footsteps? + } + else if ( ent->client->ps.legsAnim == BOTH_FLIP_LAND ) + {//moving during full-body fast special + float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim ); + float elapsedTime = (float)(animLength-ent->client->ps.torsoAnimTimer); + ucmd->upmove = ucmd->rightmove = ucmd->forwardmove = 0; + if ( elapsedTime > 600 && elapsedTime < 800 + && ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//jump - FIXME: how do we stop double-jumps? + ent->client->ps.pm_flags |= PMF_JUMP_HELD; + ent->client->ps.groundEntityNum = ENTITYNUM_NONE; + ent->client->ps.jumpZStart = ent->currentOrigin[2]; + ent->client->ps.velocity[2] = JUMP_VELOCITY; + G_AddEvent( ent, EV_JUMP, 0 ); + } + } + else if ( ( PM_SuperBreakWinAnim( ent->client->ps.torsoAnim ) + || PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim ) ) + && ent->client->ps.torsoAnimTimer ) + {//can't move or turn + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + ent->client->ps.forceJumpCharge = 0; + } + overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); + } + else if ( BG_FullBodyTauntAnim( ent->client->ps.legsAnim ) + && BG_FullBodyTauntAnim( ent->client->ps.torsoAnim ) ) + { + if ( (ucmd->buttons&BUTTON_ATTACK) + || (ucmd->buttons&BUTTON_ALT_ATTACK) + || (ucmd->buttons&BUTTON_USE_FORCE) + || (ucmd->buttons&BUTTON_FORCEGRIP) + || (ucmd->buttons&BUTTON_FORCE_LIGHTNING) + || (ucmd->buttons&BUTTON_FORCE_DRAIN) + || ucmd->upmove ) + {//stop the anim + if ( ent->client->ps.legsAnim == BOTH_MEDITATE + && ent->client->ps.torsoAnim == BOTH_MEDITATE ) + { + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_MEDITATE_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + } + else + { + ent->client->ps.legsAnimTimer = ent->client->ps.torsoAnimTimer = 0; + } + } + else + { + if ( ent->client->ps.legsAnim == BOTH_MEDITATE ) + { + if ( ent->client->ps.legsAnimTimer < 100 ) + { + ent->client->ps.legsAnimTimer = 100; + } + } + if ( ent->client->ps.torsoAnim == BOTH_MEDITATE ) + { + if ( ent->client->ps.torsoAnimTimer < 100 ) + { + ent->client->ps.legsAnimTimer = 100; + } + } + if ( ent->client->ps.legsAnimTimer > 0 || ent->client->ps.torsoAnimTimer > 0 ) + { + ucmd->rightmove = 0; + ucmd->upmove = 0; + ucmd->forwardmove = 0; + ucmd->buttons = 0; + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + ent->client->ps.forceJumpCharge = 0; + } + overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); + } + } + } + else if ( ent->client->ps.legsAnim == BOTH_MEDITATE_END + && ent->client->ps.legsAnimTimer > 0 ) + { + ucmd->rightmove = 0; + ucmd->upmove = 0; + ucmd->forwardmove = 0; + ucmd->buttons = 0; + if ( ent->NPC ) + { + VectorClear( ent->client->ps.moveDir ); + ent->client->ps.forceJumpCharge = 0; + } + overridAngles = (PM_LockAngles( ent, ucmd )?qtrue:overridAngles); + } + else if ( !ent->s.number ) + { + if ( ent->client->NPC_class != CLASS_ATST ) + { + // Not in a vehicle. + if ( ent->s.m_iVehicleNum == 0 ) + { + if ( !MatrixMode ) + { + cg.overrides.active &= ~(CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_POF|CG_OVERRIDE_3RD_PERSON_ANG); + cg.overrides.thirdPersonRange = 0; + } + } + } + } + + + if ( PM_InRoll( &ent->client->ps ) ) + { + if ( ent->s.number >= MAX_CLIENTS || !player_locked ) + { + //FIXME: NPCs should try to face us during this roll, so they roll around us...? + PM_CmdForRoll( &ent->client->ps, ucmd ); + if ( ent->s.number >= MAX_CLIENTS ) + {//make sure it doesn't roll me off a ledge + if ( !G_CheckRollSafety( ent, ent->client->ps.legsAnim, 24 ) ) + {//crap! I guess all we can do is stop... UGH + ucmd->rightmove = ucmd->forwardmove = 0; + } + } + } + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + ent->client->ps.speed = 400; + } + + if ( PM_InCartwheel( ent->client->ps.legsAnim ) ) + {//can't keep moving in cartwheel + if ( ent->client->ps.legsAnimTimer > 100 ) + {//still have time left in the anim + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + if ( ent->s.number || !player_locked ) + { + switch ( ent->client->ps.legsAnim ) + { + case BOTH_ARIAL_LEFT: + case BOTH_CARTWHEEL_LEFT: + ucmd->rightmove = -127; + break; + case BOTH_ARIAL_RIGHT: + case BOTH_CARTWHEEL_RIGHT: + ucmd->rightmove = 127; + break; + case BOTH_ARIAL_F1: + ucmd->forwardmove = 127; + break; + default: + break; + } + } + } + } + + if ( ent->client->ps.legsAnim == BOTH_BUTTERFLY_LEFT + || ent->client->ps.legsAnim == BOTH_BUTTERFLY_RIGHT ) + { + if ( ent->client->ps.legsAnimTimer > 100 ) + {//still have time left in the anim + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + if ( ent->NPC ) + {//invalid now + VectorClear( ent->client->ps.moveDir ); + } + if ( ent->s.number || !player_locked ) + { + if ( ent->client->ps.legsAnimTimer > 450 ) + { + switch ( ent->client->ps.legsAnim ) + { + case BOTH_BUTTERFLY_LEFT: + ucmd->rightmove = -127; + break; + case BOTH_BUTTERFLY_RIGHT: + ucmd->rightmove = 127; + break; + default: + break; + } + } + } + } + } + + overridAngles = (PM_AdjustAnglesForStabDown( ent, ucmd )?qtrue:overridAngles); + overridAngles = (PM_AdjustAngleForWallJump( ent, ucmd, qtrue )?qtrue:overridAngles); + overridAngles = (PM_AdjustAngleForWallRunUp( ent, ucmd, qtrue )?qtrue:overridAngles); + overridAngles = (PM_AdjustAngleForWallRun( ent, ucmd, qtrue )?qtrue:overridAngles); + + return overridAngles; +} + +void BG_AddPushVecToUcmd( gentity_t *self, usercmd_t *ucmd ) +{ + vec3_t forward, right, moveDir; + float pushSpeed, fMove, rMove; + + if ( !self->client ) + { + return; + } + pushSpeed = VectorLengthSquared(self->client->pushVec); + if(!pushSpeed) + {//not being pushed + return; + } + + AngleVectors(self->client->ps.viewangles, forward, right, NULL); + VectorScale(forward, ucmd->forwardmove/127.0f * self->client->ps.speed, moveDir); + VectorMA(moveDir, ucmd->rightmove/127.0f * self->client->ps.speed, right, moveDir); + //moveDir is now our intended move velocity + + VectorAdd(moveDir, self->client->pushVec, moveDir); + self->client->ps.speed = VectorNormalize(moveDir); + //moveDir is now our intended move velocity plus our push Vector + + fMove = 127.0 * DotProduct(forward, moveDir); + rMove = 127.0 * DotProduct(right, moveDir); + ucmd->forwardmove = floor(fMove);//If in the same dir , will be positive + ucmd->rightmove = floor(rMove);//If in the same dir , will be positive + + if ( self->client->pushVecTime < level.time ) + { + VectorClear( self->client->pushVec ); + } +} + +void NPC_Accelerate( gentity_t *ent, qboolean fullWalkAcc, qboolean fullRunAcc ) +{ + if ( !ent->client || !ent->NPC ) + { + return; + } + + if ( !ent->NPC->stats.acceleration ) + {//No acceleration means just start and stop + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + //FIXME: in cinematics always accel/decel? + else if ( ent->NPC->desiredSpeed <= ent->NPC->stats.walkSpeed ) + {//Only accelerate if at walkSpeeds + if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration ) + { + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed += ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed ) + { + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + else if ( fullWalkAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration ) + {//decelerate even when walking + ent->NPC->currentSpeed -= ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) + {//stop on a dime + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + } + else// if ( ent->NPC->desiredSpeed > ent->NPC->stats.walkSpeed ) + {//Only decelerate if at runSpeeds + if ( fullRunAcc && ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration ) + {//Accelerate to runspeed + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed += ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed ) + {//accelerate instantly + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + else if ( fullRunAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration ) + { + ent->NPC->currentSpeed -= ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) + { + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + } +} + +/* +------------------------- +NPC_GetWalkSpeed +------------------------- +*/ + +static int NPC_GetWalkSpeed( gentity_t *ent ) +{ + int walkSpeed = 0; + + if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) ) + return 0; + + switch ( ent->client->playerTeam ) + { + case TEAM_PLAYER: //To shutup compiler, will add entries later (this is stub code) + default: + walkSpeed = ent->NPC->stats.walkSpeed; + break; + } + + return walkSpeed; +} + +/* +------------------------- +NPC_GetRunSpeed +------------------------- +*/ +#define BORG_RUN_INCR 25 +#define SPECIES_RUN_INCR 25 +#define STASIS_RUN_INCR 20 +#define WARBOT_RUN_INCR 20 + +static int NPC_GetRunSpeed( gentity_t *ent ) +{ + int runSpeed = 0; + + if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) ) + return 0; +/* + switch ( ent->client->playerTeam ) + { + case TEAM_BORG: + runSpeed = ent->NPC->stats.runSpeed; + runSpeed += BORG_RUN_INCR * (g_spskill->integer%3); + break; + + case TEAM_8472: + runSpeed = ent->NPC->stats.runSpeed; + runSpeed += SPECIES_RUN_INCR * (g_spskill->integer%3); + break; + + case TEAM_STASIS: + runSpeed = ent->NPC->stats.runSpeed; + runSpeed += STASIS_RUN_INCR * (g_spskill->integer%3); + break; + + case TEAM_BOTS: + runSpeed = ent->NPC->stats.runSpeed; + break; + + default: + runSpeed = ent->NPC->stats.runSpeed; + break; + } +*/ + // team no longer indicates species/race. Use NPC_class to adjust speed for specific npc types + switch( ent->client->NPC_class) + { + case CLASS_PROBE: // droid cases here to shut-up compiler + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_PROTOCOL: + case CLASS_ATST: // hmm, not really your average droid + case CLASS_MOUSE: + case CLASS_SEEKER: + case CLASS_REMOTE: + runSpeed = ent->NPC->stats.runSpeed; + break; + + default: + runSpeed = ent->NPC->stats.runSpeed; + break; + } + + return runSpeed; +} + +void G_HeldByMonster( gentity_t *ent, usercmd_t **ucmd ) +{ + if ( ent && ent->activator && ent->activator->inuse && ent->activator->health > 0 ) + { + gentity_t *monster = ent->activator; + //take the monster's waypoint as your own + ent->waypoint = monster->waypoint; + + //update the actual origin of the victim + mdxaBone_t boltMatrix; + + // Getting the bolt here + int boltIndex = monster->gutBolt;//default to being held in his mouth + if ( monster->count == 1 ) + {//being held in hand rather than the mouth, so use *that* bolt + boltIndex = monster->handRBolt; + } + vec3_t monAngles = {0}; + monAngles[YAW] = monster->currentAngles[YAW];//only use YAW when passing angles to G2 + gi.G2API_GetBoltMatrix( monster->ghoul2, monster->playerModel, boltIndex, + &boltMatrix, monAngles, monster->currentOrigin, (cg.time?cg.time:level.time), + NULL, monster->s.modelScale ); + // Storing ent position, bolt position, and bolt axis + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->ps.origin ); + gi.linkentity( ent ); + //lock view angles + PM_AdjustAnglesForHeldByMonster( ent, monster, *ucmd ); + if ( monster->client && monster->client->NPC_class == CLASS_WAMPA ) + {//can only hit attack button + (*ucmd)->buttons &= ~((*ucmd)->buttons&~BUTTON_ATTACK); + } + } + else if (ent) + {//doh, my captor died! + ent->activator = NULL; + if (ent->client) + { + ent->client->ps.eFlags &= ~(EF_HELD_BY_WAMPA|EF_HELD_BY_RANCOR); + } + } + // don't allow movement, weapon switching, and most kinds of button presses + (*ucmd)->forwardmove = 0; + (*ucmd)->rightmove = 0; + (*ucmd)->upmove = 0; +} + +// yes... so stop skipping... +void G_StopCinematicSkip( void ) +{ + gi.cvar_set("skippingCinematic", "0"); + gi.cvar_set("timescale", "1"); +} + +void G_StartCinematicSkip( void ) +{ + + if (cinematicSkipScript[0]) + { + Quake3Game()->RunScript( &g_entities[0], cinematicSkipScript ); + + cinematicSkipScript[0] = 0; + gi.cvar_set("skippingCinematic", "1"); + gi.cvar_set("timescale", "100"); + } + else + { + // no... so start skipping... + gi.cvar_set("skippingCinematic", "1"); + gi.cvar_set("timescale", "100"); + } +} + +void G_CheckClientIdle( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( !ent || !ent->client || ent->health <= 0 ) + { + return; + } + if ( !ent->s.number && ( !cg.renderingThirdPerson || cg.zoomMode ) ) + { + if ( ent->client->idleTime < level.time ) + { + ent->client->idleTime = level.time; + } + return; + } + if ( !VectorCompare( vec3_origin, ent->client->ps.velocity ) + || ucmd->buttons || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove + || !PM_StandingAnim( ent->client->ps.legsAnim ) + || ent->enemy + || ent->client->ps.legsAnimTimer + || ent->client->ps.torsoAnimTimer ) + {//FIXME: also check for turning? + if ( !VectorCompare( vec3_origin, ent->client->ps.velocity ) + || ucmd->buttons || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove + || ent->enemy ) + { + //if in an idle, break out + switch ( ent->client->ps.legsAnim ) + { + case BOTH_STAND1IDLE1: + case BOTH_STAND2IDLE1: + case BOTH_STAND2IDLE2: + case BOTH_STAND3IDLE1: + case BOTH_STAND5IDLE1: + ent->client->ps.legsAnimTimer = 0; + break; + } + switch ( ent->client->ps.torsoAnim ) + { + case BOTH_STAND1IDLE1: + case BOTH_STAND2IDLE1: + case BOTH_STAND2IDLE2: + case BOTH_STAND3IDLE1: + case BOTH_STAND5IDLE1: + ent->client->ps.torsoAnimTimer = 0; + break; + } + } + // + if ( ent->client->idleTime < level.time ) + { + ent->client->idleTime = level.time; + } + } + else if ( level.time - ent->client->idleTime > 5000 ) + {//been idle for 5 seconds + int idleAnim = -1; + switch ( ent->client->ps.legsAnim ) + { + case BOTH_STAND1: + idleAnim = BOTH_STAND1IDLE1; + break; + case BOTH_STAND2: + idleAnim = Q_irand(BOTH_STAND2IDLE1,BOTH_STAND2IDLE2); + break; + case BOTH_STAND3: + idleAnim = BOTH_STAND3IDLE1; + break; + case BOTH_STAND5: + idleAnim = BOTH_STAND5IDLE1; + break; + } + if ( idleAnim != -1 && PM_HasAnimation( ent, idleAnim ) ) + { + NPC_SetAnim( ent, SETANIM_BOTH, idleAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + //don't idle again after this anim for a while + ent->client->idleTime = level.time + PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)idleAnim ) + Q_irand( 0, 2000 ); + } + } +} + +void G_CheckMovingLoopingSounds( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( ent->client ) + { + if ( (ent->NPC&&!VectorCompare( vec3_origin, ent->client->ps.moveDir ))//moving using moveDir + || ucmd->forwardmove || ucmd->rightmove//moving using ucmds + || (ucmd->upmove&&FlyingCreature( ent ))//flier using ucmds to move + || (FlyingCreature( ent )&&!VectorCompare( vec3_origin, ent->client->ps.velocity )&&ent->health>0))//flier using velocity to move + { + switch( ent->client->NPC_class ) + { + case CLASS_R2D2: + ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" ); + break; + case CLASS_R5D2: + ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" ); + break; + case CLASS_MARK2: + ent->s.loopSound = G_SoundIndex( "sound/chars/mark2/misc/mark2_move_lp" ); + break; + case CLASS_MOUSE: + ent->s.loopSound = G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" ); + break; + case CLASS_PROBE: + ent->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); + } + } + else + {//not moving under your own control, stop loopSound + if ( ent->client->NPC_class == CLASS_R2D2 || ent->client->NPC_class == CLASS_R5D2 + || ent->client->NPC_class == CLASS_MARK2 || ent->client->NPC_class == CLASS_MOUSE + || ent->client->NPC_class == CLASS_PROBE ) + { + ent->s.loopSound = 0; + } + } + } +} + + +/* +============== +ClientAlterSpeed + +This function is called ONLY from ClientThinkReal, and is responsible for setting client ps.speed +============== +*/ +void ClientAlterSpeed(gentity_t *ent, usercmd_t *ucmd, qboolean controlledByPlayer, float vehicleFrameTimeModifier) +{ + gclient_t *client = ent->client; + Vehicle_t *pVeh = NULL; + + if ( ent->client && ent->client->NPC_class == CLASS_VEHICLE ) + { + pVeh = ent->m_pVehicle; + } + + // set speed + // This may be wrong: If we're an npc and we are in a vehicle??? + if ( ent->NPC != NULL && ent->client && ( ent->s.m_iVehicleNum != 0 )/*&& ent->client->NPC_class == CLASS_VEHICLE*/ ) + {//we don't actually scale the ucmd, we use actual speeds + //FIXME: swoop should keep turning (and moving forward?) for a little bit? + if ( ent->NPC->combatMove == qfalse ) + { + if ( !(ucmd->buttons & BUTTON_USE) ) + {//Not leaning + qboolean Flying = (ucmd->upmove && ent->client->moveType == MT_FLYSWIM); + qboolean Climbing = (ucmd->upmove && ent->watertype&CONTENTS_LADDER ); + + client->ps.friction = 6; + + if ( ucmd->forwardmove || ucmd->rightmove || Flying ) + { + //if ( ent->NPC->behaviorState != BS_FORMATION ) + {//In - Formation NPCs set thier desiredSpeed themselves + if ( ucmd->buttons & BUTTON_WALKING ) + { + ent->NPC->desiredSpeed = NPC_GetWalkSpeed( ent );//ent->NPC->stats.walkSpeed; + } + else//running + { + ent->NPC->desiredSpeed = NPC_GetRunSpeed( ent );//ent->NPC->stats.runSpeed; + } + + if ( ent->NPC->currentSpeed >= 80 && !controlledByPlayer ) + {//At higher speeds, need to slow down close to stuff + //Slow down as you approach your goal + // if ( ent->NPC->distToGoal < SLOWDOWN_DIST && client->race != RACE_BORG && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128 + if ( ent->NPC->distToGoal < SLOWDOWN_DIST && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128 + { + if ( ent->NPC->desiredSpeed > MIN_NPC_SPEED ) + { + float slowdownSpeed = ((float)ent->NPC->desiredSpeed) * ent->NPC->distToGoal / SLOWDOWN_DIST; + + ent->NPC->desiredSpeed = ceil(slowdownSpeed); + if ( ent->NPC->desiredSpeed < MIN_NPC_SPEED ) + {//don't slow down too much + ent->NPC->desiredSpeed = MIN_NPC_SPEED; + } + } + } + } + } + } + else if ( Climbing ) + { + ent->NPC->desiredSpeed = ent->NPC->stats.walkSpeed; + } + else + {//We want to stop + ent->NPC->desiredSpeed = 0; + } + + NPC_Accelerate( ent, qfalse, qfalse ); + + if ( ent->NPC->currentSpeed <= 24 && ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) + {//No-one walks this slow + client->ps.speed = ent->NPC->currentSpeed = 0;//Full stop + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + } + else + { + if ( ent->NPC->currentSpeed <= ent->NPC->stats.walkSpeed ) + {//Play the walkanim + ucmd->buttons |= BUTTON_WALKING; + } + else + { + ucmd->buttons &= ~BUTTON_WALKING; + } + + if ( ent->NPC->currentSpeed > 0 ) + {//We should be moving + if ( Climbing || Flying ) + { + if ( !ucmd->upmove ) + {//We need to force them to take a couple more steps until stopped + ucmd->upmove = ent->NPC->last_ucmd.upmove;//was last_upmove; + } + } + else if ( !ucmd->forwardmove && !ucmd->rightmove ) + {//We need to force them to take a couple more steps until stopped + ucmd->forwardmove = ent->NPC->last_ucmd.forwardmove;//was last_forwardmove; + ucmd->rightmove = ent->NPC->last_ucmd.rightmove;//was last_rightmove; + } + } + + client->ps.speed = ent->NPC->currentSpeed; + if ( player && player->client && player->client->ps.viewEntity == ent->s.number ) + { + } + else + { + //Slow down on turns - don't orbit!!! + float turndelta = 0; + // if the NPC is locked into a Yaw, we want to check the lockedDesiredYaw...otherwise the NPC can't walk backwards, because it always thinks it trying to turn according to desiredYaw + if( client->renderInfo.renderFlags & RF_LOCKEDANGLE ) // yeah I know the RF_ flag is a pretty ugly hack... + { + turndelta = (180 - fabs( AngleDelta( ent->currentAngles[YAW], ent->NPC->lockedDesiredYaw ) ))/180; + } + else + { + turndelta = (180 - fabs( AngleDelta( ent->currentAngles[YAW], ent->NPC->desiredYaw ) ))/180; + } + + if ( turndelta < 0.75f ) + { + client->ps.speed = 0; + } + else if ( ent->NPC->distToGoal < 100 && turndelta < 1.0 ) + {//Turn is greater than 45 degrees or closer than 100 to goal + client->ps.speed = floor(((float)(client->ps.speed))*turndelta); + } + } + } + } + } + else + { + ent->NPC->desiredSpeed = ( ucmd->buttons & BUTTON_WALKING ) ? NPC_GetWalkSpeed( ent ) : NPC_GetRunSpeed( ent ); + + client->ps.speed = ent->NPC->desiredSpeed; + } + } + else + {//Client sets ucmds and such for speed alterations + { + client->ps.speed = g_speed->value;//default is 320 + /*if ( !ent->s.number && ent->painDebounceTime>level.time ) + { + client->ps.speed *= 0.25f; + } + else */if (ent->client->ps.heldClient < ENTITYNUM_WORLD) + { + client->ps.speed *= 0.3f; + } + else if ( PM_SaberInAttack( ent->client->ps.saberMove ) && ucmd->forwardmove < 0 ) + {//if running backwards while attacking, don't run as fast. + switch( client->ps.saberAnimLevel ) + { + case SS_FAST: + client->ps.speed *= 0.75f; + break; + case SS_MEDIUM: + case SS_DUAL: + case SS_STAFF: + client->ps.speed *= 0.60f; + break; + case SS_STRONG: + client->ps.speed *= 0.45f; + break; + } + if ( g_saberMoveSpeed->value != 1.0f ) + { + client->ps.speed *= g_saberMoveSpeed->value; + } + } + else if ( PM_LeapingSaberAnim( client->ps.legsAnim ) ) + {//no mod on speed when leaping + //FIXME: maybe jump? + } + else if ( PM_SpinningSaberAnim( client->ps.legsAnim ) ) + { + client->ps.speed *= 0.5f; + if ( g_saberMoveSpeed->value != 1.0f ) + { + client->ps.speed *= g_saberMoveSpeed->value; + } + } + else if ( client->ps.weapon == WP_SABER && ( ucmd->buttons & BUTTON_ATTACK ) ) + {//if attacking with saber while running, drop your speed + //FIXME: should be weaponTime? Or in certain anims? + switch( client->ps.saberAnimLevel ) + { + case SS_MEDIUM: + case SS_DUAL: + case SS_STAFF: + client->ps.speed *= 0.85f; + break; + case SS_STRONG: + client->ps.speed *= 0.70f; + break; + } + if ( g_saberMoveSpeed->value != 1.0f ) + { + client->ps.speed *= g_saberMoveSpeed->value; + } + } + } + } + if ( client->NPC_class == CLASS_ATST && client->ps.legsAnim == BOTH_RUN1START ) + {//HACK: when starting to move as atst, ramp up speed + //float animLength = PM_AnimLength( client->clientInfo.animFileIndex, (animNumber_t)client->ps.legsAnim); + //client->ps.speed *= ( animLength - client->ps.legsAnimTimer)/animLength; + if ( client->ps.legsAnimTimer > 100 ) + { + client->ps.speed = 0; + } + } + + //Apply forced movement + if ( client->forced_forwardmove ) + { + ucmd->forwardmove = client->forced_forwardmove; + if ( !client->ps.speed ) + { + if ( ent->NPC != NULL ) + { + client->ps.speed = ent->NPC->stats.runSpeed; + } + else + { + client->ps.speed = g_speed->value;//default is 320 + } + } + } + + if ( client->forced_rightmove ) + { + ucmd->rightmove = client->forced_rightmove; + if ( !client->ps.speed ) + { + if ( ent->NPC != NULL ) + { + client->ps.speed = ent->NPC->stats.runSpeed; + } + else + { + client->ps.speed = g_speed->value;//default is 320 + } + } + } + + if ( !pVeh ) + { + if ( ucmd->forwardmove < 0 && !(ucmd->buttons&BUTTON_WALKING) && client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//running backwards is slower than running forwards + client->ps.speed *= 0.75; + } + + if (client->ps.forceRageRecoveryTime > level.time ) + { + client->ps.speed *= 0.75; + } + } +} + + +extern qboolean ForceDrain2(gentity_t *ent); +extern void ForceGrip(gentity_t *ent); +extern void ForceLightning(gentity_t *ent); +extern void ForceProtect(gentity_t *ent); +extern void ForceRage(gentity_t *ent); +extern void ForceSeeing(gentity_t *ent); +extern void ForceTelepathy(gentity_t *ent); +extern void ForceAbsorb(gentity_t *ent); +extern void ForceHeal(gentity_t *ent); +static void ProcessGenericCmd(gentity_t *ent, byte cmd) +{ + switch(cmd) { + case 0: + break; + case GENCMD_FORCE_DRAIN: + ForceDrain2(ent); + break; + case GENCMD_FORCE_THROW: + ForceThrow(ent, qfalse); + break; + case GENCMD_FORCE_SPEED: + ForceSpeed(ent); + break; + case GENCMD_FORCE_PULL: + ForceThrow(ent, qtrue); + break; + case GENCMD_FORCE_DISTRACT: + ForceTelepathy(ent); + break; + case GENCMD_FORCE_GRIP: + ForceGrip(ent); + break; + case GENCMD_FORCE_LIGHTNING: + ForceLightning(ent); + break; + case GENCMD_FORCE_RAGE: + ForceRage(ent); + break; + case GENCMD_FORCE_PROTECT: + ForceProtect(ent); + break; + case GENCMD_FORCE_ABSORB: + ForceAbsorb(ent); + break; + case GENCMD_FORCE_SEEING: + ForceSeeing(ent); + break; + case GENCMD_FORCE_HEAL: + ForceHeal(ent); + break; + } +} + + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame on fast clients. + +============== +*/ + +extern int G_FindLocalInterestPoint( gentity_t *self ); +extern float G_CanJumpToEnemyVeh(Vehicle_t *pVeh, const usercmd_t *pUmcd ); +extern void Cbuf_ExecuteText( int exec_when, const char *text ); + +void ClientThink_real( gentity_t *ent, usercmd_t *ucmd ) +{ + gclient_t *client; + pmove_t pm; + vec3_t oldOrigin; + int oldEventSequence; + int msec; + qboolean inSpinFlipAttack = PM_AdjustAnglesForSpinningFlip( ent, ucmd, qfalse ); + qboolean controlledByPlayer = qfalse; + + Vehicle_t *pVeh = NULL; + + if ( ent->client && ent->client->NPC_class == CLASS_VEHICLE ) + { + pVeh = ent->m_pVehicle; + } + + //Don't let the player do anything if in a camera + if ( (ent->s.eFlags&EF_HELD_BY_RANCOR) + || (ent->s.eFlags&EF_HELD_BY_WAMPA) ) + { + G_HeldByMonster( ent, &ucmd ); + } + + if ( ent->s.number == 0 ) + { +extern cvar_t *g_skippingcin; + + if ( ent->s.eFlags & EF_LOCKED_TO_WEAPON ) + { + G_UpdateEmplacedWeaponData( ent ); + RunEmplacedWeapon( ent, &ucmd ); + } + if ( ent->client->ps.saberLockTime > level.time && ent->client->ps.saberLockEnemy != ENTITYNUM_NONE ) + { + NPC_SetLookTarget( ent, ent->client->ps.saberLockEnemy, level.time+1000 ); + } + if ( ent->client->renderInfo.lookTargetClearTime < level.time //NOTE: here this is used as a debounce, not an actual timer + && ent->health > 0 //must be alive + && (!ent->enemy || ent->client->ps.saberMove != LS_A_BACKSTAB) )//don't update if in backstab unless don't currently have an enemy + {//NOTE: doesn't keep updating to nearest enemy once you're dead + int newLookTarget; + if ( !G_ValidateLookEnemy( ent, ent->enemy ) ) + { + ent->enemy = NULL; + } + //FIXME: make this a little prescient? + G_ChooseLookEnemy( ent, ucmd ); + if ( ent->enemy ) + {//target + newLookTarget = ent->enemy->s.number; + //so we don't change our minds in the next 1 second + ent->client->renderInfo.lookTargetClearTime = level.time+1000; + ent->client->renderInfo.lookMode = LM_ENT; + } + else + {//no target + //FIXME: what about sightalerts and missiles? + newLookTarget = ENTITYNUM_NONE; + newLookTarget = G_FindLocalInterestPoint( ent ); + if ( newLookTarget != ENTITYNUM_NONE ) + {//found something of interest + ent->client->renderInfo.lookMode = LM_INTEREST; + } + else + {//okay, no interesting things and no enemies, so look for items + newLookTarget = G_FindLookItem( ent ); + ent->client->renderInfo.lookMode = LM_ENT; + } + } + if ( ent->client->renderInfo.lookTarget != newLookTarget ) + {//transitioning + NPC_SetLookTarget( ent, newLookTarget, level.time+1000 ); + } + } + if ( in_camera ) + { + // watch the code here, you MUST "return" within this IF(), *unless* you're stopping the cinematic skip. + // + if ( ClientCinematicThink(ent->client) ) + { + if (g_skippingcin->integer) // already doing cinematic skip? + {// yes... so stop skipping... + G_StopCinematicSkip(); + } + else + {// no... so start skipping... + G_StartCinematicSkip(); + return; + } + } + else + { + return; + } + } + // If he's riding the vehicle... + else if ( ent->s.m_iVehicleNum != 0 && ent->health > 0 ) + { + } + else + { + if ( g_skippingcin->integer ) + {//We're skipping the cinematic and it's over now + gi.cvar_set("timescale", "1"); + gi.cvar_set("skippingCinematic", "0"); + } + if ( ent->client->ps.pm_type == PM_DEAD && cg.missionStatusDeadTime < level.time ) + {//mission status screen is up because player is dead, stop all scripts + if (Q_stricmpn(level.mapname,"_holo",5)) { + stop_icarus = qtrue; + } + } + } + +// // Don't allow the player to adjust the pitch when they are in third person overhead cam. +//extern vmCvar_t cg_thirdPerson; +// if ( cg_thirdPerson.integer == 2 ) +// { +// ucmd->angles[PITCH] = 0; +// } + + if ( cg.zoomMode == 2 ) + { + // Any kind of movement when the player is NOT ducked when the disruptor gun is zoomed will cause us to auto-magically un-zoom + if ( ( (ucmd->forwardmove||ucmd->rightmove) + && ucmd->upmove >= 0 //crouching-moving is ok + && !(ucmd->buttons&BUTTON_USE)/*leaning is ok*/ + ) + || ucmd->upmove > 0 //jumping not allowed + ) + { + // already zooming, so must be wanting to turn it off + G_Sound( ent, G_SoundIndex( "sound/weapons/disruptor/zoomend.wav" )); +#ifdef _IMMERSION + G_Force( ent, G_ForceIndex( "fffx/weapons/disruptor/zoomend", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + cg.zoomMode = 0; + cg.zoomTime = cg.time; + cg.zoomLocked = qfalse; + } + } + + if ( (player_locked + || (ent->client->ps.eFlags&EF_FORCE_GRIPPED) + || (ent->client->ps.eFlags&EF_FORCE_DRAINED) + || (ent->client->ps.legsAnim==BOTH_PLAYER_PA_1) + || (ent->client->ps.legsAnim==BOTH_PLAYER_PA_2) + || (ent->client->ps.legsAnim==BOTH_PLAYER_PA_3)) + && ent->client->ps.pm_type < PM_DEAD ) // unless dead + {//lock out player control + if ( !player_locked ) + { + VectorClear( ucmd->angles ); + } + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + ucmd->buttons = 0; + ucmd->upmove = 0; + PM_AdjustAnglesToGripper( ent, ucmd ); + } + if ( ent->client->ps.leanofs ) + {//no shooting while leaning + ucmd->buttons &= ~BUTTON_ATTACK; + if ( ent->client->ps.weapon != WP_DISRUPTOR ) + {//can still zoom around corners + ucmd->buttons &= ~BUTTON_ALT_ATTACK; + } + } + } + else + { + if ( ent->s.eFlags & EF_LOCKED_TO_WEAPON ) + { + G_UpdateEmplacedWeaponData( ent ); + } + if ( player && player->client && player->client->ps.viewEntity == ent->s.number ) + { + controlledByPlayer = qtrue; + int sav_weapon = ucmd->weapon; + memcpy( ucmd, &player->client->usercmd, sizeof( usercmd_t ) ); + ucmd->weapon = sav_weapon; + ent->client->usercmd = *ucmd; + } + + // Transfer over our driver's commands to us (the vehicle). + if ( ent->owner && ent->client && ent->client->NPC_class == CLASS_VEHICLE ) + { + memcpy( ucmd, &ent->owner->client->usercmd, sizeof( usercmd_t ) ); + ucmd->buttons &= ~BUTTON_USE;//Vehicles NEVER try to use ANYTHING!!! + //ucmd->weapon = ent->client->ps.weapon; // but keep our weapon. + ent->client->usercmd = *ucmd; + } + + G_NPCMunroMatchPlayerWeapon( ent ); + } + + // If we are a vehicle, update ourself. + if ( pVeh + && (pVeh->m_pVehicleInfo->Inhabited(pVeh) + || pVeh->m_iBoarding!=0 + || pVeh->m_pVehicleInfo->type!=VH_ANIMAL) ) + { + pVeh->m_pVehicleInfo->Update( pVeh, ucmd ); + } + else if ( ent->client ) + {//this is any client that is not a vehicle (OR: is a vehicle and it not being ridden, is not being boarded, or is a TaunTaun...! + if ( ent->client->NPC_class == CLASS_GONK || + ent->client->NPC_class == CLASS_MOUSE || + ent->client->NPC_class == CLASS_R2D2 || + ent->client->NPC_class == CLASS_R5D2 ) + {//no jumping or strafing in these guys + ucmd->upmove = ucmd->rightmove = 0; + } + else if ( ent->client->NPC_class == CLASS_ATST || ent->client->NPC_class == CLASS_RANCOR ) + {//no jumping in atst + if (ent->client->ps.pm_type != PM_NOCLIP) + { + ucmd->upmove = 0; + } + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//ATST crushes anything underneath it + gentity_t *under = &g_entities[ent->client->ps.groundEntityNum]; + if ( under && under->health && under->takedamage ) + { + vec3_t down = {0,0,-1}; + //FIXME: we'll be doing traces down from each foot, so we'll have a real impact origin + G_Damage( under, ent, ent, down, under->currentOrigin, 100, 0, MOD_CRUSH ); + } + //so they know to run like hell when I get close + //FIXME: project this in the direction I'm moving? + if ( ent->client->NPC_class != CLASS_RANCOR && !Q_irand( 0, 10 ) ) + {//not so often... + AddSoundEvent( ent, ent->currentOrigin, ent->maxs[1]*5, AEL_DANGER, qfalse, qtrue ); + AddSightEvent( ent, ent->currentOrigin, ent->maxs[1]*5, AEL_DANGER, 100 ); + } + } + } + else if ( ent->client->ps.groundEntityNum < ENTITYNUM_WORLD && !ent->client->ps.forceJumpCharge ) + {//standing on an entity and not currently force jumping + gentity_t *groundEnt = &g_entities[ent->client->ps.groundEntityNum]; + if ( groundEnt && groundEnt->client ) + { + // If you landed on a speeder or animal vehicle... + if ( groundEnt->client && groundEnt->client->NPC_class == CLASS_VEHICLE ) + { + if ( ent->client->NPC_class != CLASS_VEHICLE ) + {//um... vehicles shouldn't ride other vehicles, mmkay? + if ( (groundEnt->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL && PM_HasAnimation( ent, BOTH_VT_IDLE )) + || (groundEnt->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER && PM_HasAnimation( ent, BOTH_VS_IDLE )) ) + { + //groundEnt->m_pVehicle->m_iBoarding = -3; // Land From Behind + groundEnt->m_pVehicle->m_pVehicleInfo->Board( groundEnt->m_pVehicle, ent ); + } + } + } + else if ( groundEnt->client && groundEnt->client->NPC_class == CLASS_SAND_CREATURE + && G_HasKnockdownAnims( ent ) ) + {//step on a sand creature = *you* fall down + G_Knockdown( ent, groundEnt, vec3_origin, 300, qtrue ); + } + else if ( groundEnt->client && groundEnt->client->NPC_class == CLASS_RANCOR ) + {//step on a Rancor, it bucks & throws you off + if ( groundEnt->client->ps.legsAnim != BOTH_ATTACK3 + && groundEnt->client->ps.legsAnim != BOTH_ATTACK4 + && groundEnt->client->ps.legsAnim != BOTH_BUCK_RIDER ) + {//don't interrupt special anims + vec3_t throwDir, right; + AngleVectors( groundEnt->currentAngles, throwDir, right, NULL ); + VectorScale(throwDir,-1,throwDir); + VectorMA( throwDir, Q_flrand( -0.5f, 0.5f), right, throwDir ); + throwDir[2] = 0.2f; + VectorNormalize( throwDir ); + if ( !(ent->flags&FL_NO_KNOCKBACK) ) + { + G_Throw( ent, throwDir, Q_flrand( 50, 200 ) ); + } + if ( G_HasKnockdownAnims( ent ) ) + { + G_Knockdown( ent, groundEnt, vec3_origin, 300, qtrue ); + } + NPC_SetAnim( groundEnt, SETANIM_BOTH, BOTH_BUCK_RIDER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + } + } + else if ( groundEnt->client->ps.groundEntityNum != ENTITYNUM_NONE && + groundEnt->health > 0 && !PM_InRoll( &groundEnt->client->ps ) + && !(groundEnt->client->ps.eFlags&EF_LOCKED_TO_WEAPON) + && !(groundEnt->client->ps.eFlags&EF_HELD_BY_RANCOR) + && !(groundEnt->client->ps.eFlags&EF_HELD_BY_WAMPA) + && !(groundEnt->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) + && !inSpinFlipAttack ) + + {//landed on a live client who is on the ground, jump off them and knock them down + qboolean forceKnockdown = qfalse; + + // If in a vehicle when land on someone, always knockdown. + if ( pVeh ) + { + forceKnockdown = qtrue; + } + else if ( ent->s.number + && ent->NPC + && ent->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//ent->s.weapon == WP_SABER ) + {//force-jumper landed on someone + //don't jump off, too many ledges, plus looks weird + if ( groundEnt->client->playerTeam != ent->client->playerTeam ) + {//don't knock down own guys + forceKnockdown = (qboolean)(Q_irand( 0, RANK_CAPTAIN+4 )NPC->rank); + } + //now what... push the groundEnt out of the way? + if ( !ent->client->ps.velocity[0] + && !ent->client->ps.velocity[1] ) + {//not moving, shove us a little + vec3_t slideFwd; + AngleVectors( ent->client->ps.viewangles, slideFwd, NULL, NULL ); + slideFwd[2] = 0.0f; + VectorNormalize( slideFwd ); + ent->client->ps.velocity[0] = slideFwd[0]*10.0f; + ent->client->ps.velocity[1] = slideFwd[1]*10.0f; + } + //slide for a little + ent->client->ps.pm_flags |= PMF_TIME_NOFRICTION; + ent->client->ps.pm_time = 100; + } + else if ( ent->health > 0 ) + { + if ( !PM_InRoll( &ent->client->ps ) + && !PM_FlippingAnim( ent->client->ps.legsAnim ) ) + { + if ( ent->s.number && ent->s.weapon == WP_SABER ) + { + ent->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently? + } + else if ( !ucmd->upmove ) + {//if not ducking (which should cause a roll), then jump + ucmd->upmove = 127; + } + if ( !ucmd->forwardmove && !ucmd->rightmove ) + {// If not moving, don't want to jump straight up + //FIXME: trace for clear di? + if ( !Q_irand( 0, 3 ) ) + { + ucmd->forwardmove = 127; + } + else if ( !Q_irand( 0, 3 ) ) + { + ucmd->forwardmove = -127; + } + else if ( !Q_irand( 0, 1 ) ) + { + ucmd->rightmove = 127; + } + else + { + ucmd->rightmove = -127; + } + } + if ( !ent->s.number && ucmd->upmove < 0 ) + {//player who should roll- force it + int rollAnim = BOTH_ROLL_F; + if ( ucmd->forwardmove >= 0 ) + { + rollAnim = BOTH_ROLL_F; + } + else if ( ucmd->forwardmove < 0 ) + { + rollAnim = BOTH_ROLL_B; + } + else if ( ucmd->rightmove > 0 ) + { + rollAnim = BOTH_ROLL_R; + } + else if ( ucmd->rightmove < 0 ) + { + rollAnim = BOTH_ROLL_L; + } + NPC_SetAnim(ent,SETANIM_BOTH,rollAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + G_AddEvent( ent, EV_ROLL, 0 ); + ent->client->ps.saberMove = LS_NONE; + } + } + } + else + {//a corpse? Shit + //Hmm, corpses should probably *always* knockdown... + forceKnockdown = qtrue; + ent->clipmask &= ~CONTENTS_BODY; + } + //FIXME: need impact sound event + GEntity_PainFunc( groundEnt, ent, ent, groundEnt->currentOrigin, 0, MOD_CRUSH ); + if ( !forceKnockdown + && groundEnt->client->NPC_class == CLASS_DESANN + && ent->client->NPC_class != CLASS_LUKE ) + {//can't knock down desann unless you're luke + //FIXME: should he smack you away like Galak Mech? + } + else if ( forceKnockdown //forced + || ent->client->NPC_class == CLASS_DESANN //desann always knocks people down + || ( ( (groundEnt->s.number&&(groundEnt->s.weapon!=WP_SABER||!groundEnt->NPC||groundEnt->NPC->ranks.number||G_ControlledByPlayer(groundEnt)) && !Q_irand( 0, 3 )&&cg.renderingThirdPerson&&!cg.zoomMode) )//or a player in third person, 25% of the time + && groundEnt->client->playerTeam != ent->client->playerTeam//and not on the same team + && ent->client->ps.legsAnim != BOTH_JUMPATTACK6 ) )//not in the sideways-spinning jump attack + { + int knockAnim = BOTH_KNOCKDOWN1; + if ( PM_CrouchAnim( groundEnt->client->ps.legsAnim ) ) + {//knockdown from crouch + knockAnim = BOTH_KNOCKDOWN4; + } + else + { + vec3_t gEFwd, gEAngles = {0,groundEnt->client->ps.viewangles[YAW],0}; + AngleVectors( gEAngles, gEFwd, NULL, NULL ); + if ( DotProduct( ent->client->ps.velocity, gEFwd ) > 50 ) + {//pushing him forward + knockAnim = BOTH_KNOCKDOWN3; + } + } + NPC_SetAnim( groundEnt, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + } + } + + + client = ent->client; + + // mark the time, so the connection sprite can be removed + client->lastCmdTime = level.time; + client->pers.lastCommand = *ucmd; + + // sanity check the command time to prevent speedup cheating + if ( ucmd->serverTime > level.time + 200 ) + { + ucmd->serverTime = level.time + 200; + } + if ( ucmd->serverTime < level.time - 1000 ) + { + ucmd->serverTime = level.time - 1000; + } + + msec = ucmd->serverTime - client->ps.commandTime; + if ( msec < 1 ) + { + msec = 1; + } + if ( msec > 200 ) + { + msec = 200; + } + + if ( client->noclip ) + { + client->ps.pm_type = PM_NOCLIP; + } + else if ( client->ps.stats[STAT_HEALTH] <= 0 ) + { + client->ps.pm_type = PM_DEAD; + } + else + { + client->ps.pm_type = PM_NORMAL; + } + + //FIXME: if global gravity changes this should update everyone's personal gravity... + if ( !(ent->svFlags & SVF_CUSTOM_GRAVITY) ) + { + if (ent->client->inSpaceIndex) + { //in space, so no gravity... + client->ps.gravity = 0.0f; + } + else + { + client->ps.gravity = g_gravity->value; + } + } + + if (!USENEWNAVSYSTEM || ent->s.number==0) + { + ClientAlterSpeed(ent, ucmd, controlledByPlayer, 0); + } + + + //FIXME: need to do this before check to avoid walls and cliffs (or just cliffs?) + BG_AddPushVecToUcmd( ent, ucmd ); + + G_CheckClampUcmd( ent, ucmd ); + + WP_ForcePowersUpdate( ent, ucmd ); + + //if we have the saber in hand, check for starting a block to reflect shots + if ( ent->s.number < MAX_CLIENTS//player + || ( ent->NPC && G_JediInNormalAI( ent ) ) )//NPC jedi not in a special AI mode + { + WP_SaberStartMissileBlockCheck( ent, ucmd ); + } + + // Update the position of the saber, and check to see if we're throwing it + if ( client->ps.saberEntityNum != ENTITYNUM_NONE ) + { + int updates = 1; + if ( ent->NPC ) + { + updates = 3;//simulate player update rate? + } + for ( int update = 0; update < updates; update++ ) + { + WP_SaberUpdate( ent, ucmd ); + } + } + + //NEED to do this every frame, since these overrides do not go into the save/load data + if ( ent->client && ent->s.m_iVehicleNum != 0 && !ent->s.number && !MatrixMode) + {//FIXME: extern and read from g_vehicleInfo? + Vehicle_t *pPlayerVeh = ent->owner->m_pVehicle; + if ( pPlayerVeh && pPlayerVeh->m_pVehicleInfo->cameraOverride ) + { + // Vehicle Camera Overrides + //-------------------------- + cg.overrides.active |= ( CG_OVERRIDE_3RD_PERSON_RNG | CG_OVERRIDE_FOV | CG_OVERRIDE_3RD_PERSON_VOF | CG_OVERRIDE_3RD_PERSON_POF ); + cg.overrides.thirdPersonRange = pPlayerVeh->m_pVehicleInfo->cameraRange; + cg.overrides.fov = pPlayerVeh->m_pVehicleInfo->cameraFOV; + cg.overrides.thirdPersonVertOffset = pPlayerVeh->m_pVehicleInfo->cameraVertOffset; + cg.overrides.thirdPersonPitchOffset = pPlayerVeh->m_pVehicleInfo->cameraPitchOffset; + + if ( pPlayerVeh->m_pVehicleInfo->cameraAlpha ) + { + cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_APH; + } + + // If In A Speeder (NOT DURING TURBO) + //------------------------------------ + if ((level.time>pPlayerVeh->m_iTurboTime) && pPlayerVeh->m_pVehicleInfo->type==VH_SPEEDER) + { + // If Using Strafe And Use Keys + //------------------------------ + if ((pPlayerVeh->m_ucmd.rightmove!=0) && +// (pPlayerVeh->m_pParentEntity->client->ps.speed>=0) && + (pPlayerVeh->m_ucmd.buttons&BUTTON_USE) + ) + { + cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; // Turn On Angle Offset + cg.overrides.thirdPersonRange *= -2; // Camera In Front Of Player + cg.overrides.thirdPersonAngle = (pPlayerVeh->m_ucmd.rightmove>0)?(20):(-20); + } + + // Auto Pullback Of Camera To Show Enemy + //--------------------------------------- + else + { + cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_ANG; // Turn Off Angle Offset + if (ent->enemy) + { + vec3_t actorDirection; + vec3_t enemyDirection; + + AngleVectors(ent->currentAngles, actorDirection, 0, 0); + VectorSubtract(ent->enemy->currentOrigin, ent->currentOrigin, enemyDirection); + float enemyDistance = VectorNormalize(enemyDirection); + + if (enemyDistance>cg.overrides.thirdPersonRange && enemyDistance<400 && DotProduct(actorDirection, enemyDirection)<-0.5f) + { + cg.overrides.thirdPersonRange = enemyDistance; + } + } + } + } + } + } + else if ( client->ps.eFlags&EF_IN_ATST ) + { + cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_POF|CG_OVERRIDE_3RD_PERSON_VOF); + cg.overrides.thirdPersonRange = 240; + if ( cg_thirdPersonAutoAlpha.integer ) + { + if ( ent->health > 0 && ent->client->ps.viewangles[PITCH] < 15 && ent->client->ps.viewangles[PITCH] > 0 ) + { + cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_APH; + if ( cg.overrides.thirdPersonAlpha > 0.525f ) + { + cg.overrides.thirdPersonAlpha -= 0.025f; + } + else if ( cg.overrides.thirdPersonAlpha > 0.5f ) + { + cg.overrides.thirdPersonAlpha = 0.5f; + } + } + else if ( cg.overrides.active&CG_OVERRIDE_3RD_PERSON_APH ) + { + if ( cg.overrides.thirdPersonAlpha > cg_thirdPersonAlpha.value ) + { + cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_APH; + } + else if ( cg.overrides.thirdPersonAlpha < cg_thirdPersonAlpha.value-0.1f ) + { + cg.overrides.thirdPersonAlpha += 0.1f; + } + else if ( cg.overrides.thirdPersonAlpha < cg_thirdPersonAlpha.value ) + { + cg.overrides.thirdPersonAlpha = cg_thirdPersonAlpha.value; + cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_APH; + } + } + } + if ( ent->client->ps.viewangles[PITCH] > 0 ) + { + cg.overrides.thirdPersonPitchOffset = ent->client->ps.viewangles[PITCH]*-0.75; + cg.overrides.thirdPersonVertOffset = 300+ent->client->ps.viewangles[PITCH]*-10; + if ( cg.overrides.thirdPersonVertOffset < 0 ) + { + cg.overrides.thirdPersonVertOffset = 0; + } + } + else if ( ent->client->ps.viewangles[PITCH] < 0 ) + { + cg.overrides.thirdPersonPitchOffset = ent->client->ps.viewangles[PITCH]*-0.75; + cg.overrides.thirdPersonVertOffset = 300+ent->client->ps.viewangles[PITCH]*-5; + if ( cg.overrides.thirdPersonVertOffset > 300 ) + { + cg.overrides.thirdPersonVertOffset = 300; + } + } + else + { + cg.overrides.thirdPersonPitchOffset = 0; + cg.overrides.thirdPersonVertOffset = 200; + } + } + + //play/stop any looping sounds tied to controlled movement + G_CheckMovingLoopingSounds( ent, ucmd ); + + //remember your last angles + VectorCopy ( ent->client->ps.viewangles, ent->lastAngles ); + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + memset( &pm, 0, sizeof(pm) ); + + pm.gent = ent; + pm.ps = &client->ps; + pm.cmd = *ucmd; +// pm.tracemask = MASK_PLAYERSOLID; // used differently for navgen + pm.tracemask = ent->clipmask; + pm.trace = gi.trace; + pm.pointcontents = gi.pointcontents; + pm.debugLevel = g_debugMove->integer; + pm.noFootsteps = 0;//( g_dmflags->integer & DF_NO_FOOTSTEPS ) > 0; + + if ( ent->client && ent->NPC ) + { + pm.cmd.weapon = ent->client->ps.weapon; + } + + VectorCopy( client->ps.origin, oldOrigin ); + +#ifdef _XBOX + // if we're an npc then set the waterlevel + // based on the entity structure + // otherwise, zero it + if(ent->s.number != 0) + { + pm.waterlevel = ent->waterlevel; + pm.watertype = ent->watertype; + } + else + { + pm.waterlevel = 0; + pm.watertype = 0; + } +#endif + + // perform a pmove + Pmove( &pm ); + pm.gent = 0; + + ProcessGenericCmd(ent, pm.cmd.generic_cmd); + + // save results of pmove + if ( ent->client->ps.eventSequence != oldEventSequence ) + { + ent->eventTime = level.time; + { + int seq; + + seq = (ent->client->ps.eventSequence-1) & (MAX_PS_EVENTS-1); + ent->s.event = ent->client->ps.events[ seq ] | ( ( ent->client->ps.eventSequence & 3 ) << 8 ); + ent->s.eventParm = ent->client->ps.eventParms[ seq ]; + } + } + PlayerStateToEntityState( &ent->client->ps, &ent->s ); + + VectorCopy ( ent->currentOrigin, ent->lastOrigin ); +#if 1 + // use the precise origin for linking + VectorCopy( ent->client->ps.origin, ent->currentOrigin ); +#else + //We don't use prediction anymore, so screw this + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->currentOrigin ); +#endif + + + //Had to leave this in, some legacy code must still be using s.angles + //Shouldn't interfere with interpolation of angles, should it? + VectorCopy(ent->client->ps.viewangles , ent->currentAngles ); +// if (pVeh) +// { +// gi.Printf("%d\n", ucmd->angles[2]); +// } + + VectorCopy( pm.mins, ent->mins ); + VectorCopy( pm.maxs, ent->maxs ); + +#ifdef _XBOX + // if this is the player then set the ent water level + // npcs are updated elsewhere + if(ent->s.number == 0) + { + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + } +#else + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; +#endif + + + + _VectorCopy( ucmd->angles, client->pers.cmd_angles ); + + // execute client events + ClientEvents( ent, oldEventSequence ); + + if ( pm.useEvent ) + { + //TODO: Use + TryUse( ent ); + } + + // link entity now, after any personal teleporters have been used + gi.linkentity( ent ); + ent->client->hiddenDist = 0; + if ( !ent->client->noclip ) + { + G_TouchTriggersLerped( ent ); + } + + // touch other objects + ClientImpacts( ent, &pm ); + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // check for respawning + if ( client->ps.stats[STAT_HEALTH] <= 0 ) + { + if( client->ps.clientNum == 0 ) + { + Cbuf_ExecuteText(EXEC_NOW, "-moveup"); + Cbuf_ExecuteText(EXEC_NOW, "-movedown"); + Cbuf_ExecuteText(EXEC_NOW, "-hotswap1"); + Cbuf_ExecuteText(EXEC_NOW, "-hotswap2"); + Cbuf_ExecuteText(EXEC_NOW, "-hotswap3"); + Cbuf_ExecuteText(EXEC_NOW, "-attack"); + Cbuf_ExecuteText(EXEC_NOW, "-altattack"); + Cbuf_ExecuteText(EXEC_NOW, "-use"); + Cbuf_ExecuteText(EXEC_NOW, "-useforce"); + } + + // wait for the attack button to be pressed + if ( ent->NPC == NULL && level.time > client->respawnTime ) + { + // don't allow respawn if they are still flying through the + // air, unless 10 extra seconds have passed, meaning something + // strange is going on, like the corpse is caught in a wind tunnel + /* + if ( level.time < client->respawnTime + 10000 ) + { + if ( client->ps.groundEntityNum == ENTITYNUM_NONE ) + { + return; + } + } + */ + + // pressing attack or use is the normal respawn method + //if ( ucmd->buttons & ( BUTTON_ATTACK ) ) + //{ + + // respawn( ent ); + //} + } + if ( ent + && !ent->s.number + && ent->enemy + && ent->enemy != ent + && ent->enemy->s.number < ENTITYNUM_WORLD + && ent->enemy->inuse + && !(cg.overrides.active&CG_OVERRIDE_3RD_PERSON_ANG) ) + {//keep facing enemy + vec3_t deadDir; + float deadYaw; + VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, deadDir ); + deadYaw = AngleNormalize180( vectoyaw ( deadDir ) ); + if ( deadYaw > ent->client->ps.stats[STAT_DEAD_YAW] + 1 ) + { + ent->client->ps.stats[STAT_DEAD_YAW]++; + } + else if ( deadYaw < ent->client->ps.stats[STAT_DEAD_YAW] - 1 ) + { + ent->client->ps.stats[STAT_DEAD_YAW]--; + } + else + { + ent->client->ps.stats[STAT_DEAD_YAW] = deadYaw; + } + } + return; + } + + // perform once-a-second actions + ClientTimerActions( ent, msec ); + + ClientEndPowerUps( ent ); + //try some idle anims on ent if getting no input and not moving for some time + G_CheckClientIdle( ent, ucmd ); + + extern void FF_XboxSaberRumble( void ); + if(ent->s.number == 0) // player + { + if(ent->client->ps.saberLockTime > level.time) + { + FF_XboxSaberRumble(); + } + } +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +extern void PM_CheckForceUseButton( gentity_t *ent, usercmd_t *ucmd ); +extern qboolean PM_GentCantJump( gentity_t *gent ); +extern qboolean PM_WeaponOkOnVehicle( int weapon ); +void ClientThink( int clientNum, usercmd_t *ucmd ) { + gentity_t *ent; + qboolean restore_ucmd = qfalse; + usercmd_t sav_ucmd = {0}; + + ent = g_entities + clientNum; + + if ( ent->s.numberclient->ps.viewEntity > 0 && ent->client->ps.viewEntity < ENTITYNUM_WORLD ) + {//you're controlling another NPC + gentity_t *controlled = &g_entities[ent->client->ps.viewEntity]; + qboolean freed = qfalse; + if ( controlled->NPC + && controlled->NPC->controlledTime + && ent->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 ) + {//An NPC I'm controlling with mind trick + if ( controlled->NPC->controlledTime < level.time ) + {//time's up! + G_ClearViewEntity( ent ); + freed = qtrue; + } + } + else if ( controlled->client //an NPC + && PM_GentCantJump( controlled ) //that cannot jump + && controlled->client->moveType != MT_FLYSWIM ) //and does not use upmove to fly + {//these types use jump to get out + if ( ucmd->upmove > 0 ) + {//jumping gets you out of it FIXME: check some other button instead... like ESCAPE... so you could even have total control over an NPC? + G_ClearViewEntity( ent ); + ucmd->upmove = 0;//ucmd->buttons = 0; + //stop player from doing anything for a half second after + ent->aimDebounceTime = level.time + 500; + freed = qtrue; + } + } + + if ( !freed ) + {//still controlling, save off my ucmd and clear it for my actual run through pmove + restore_ucmd = qtrue; + memcpy( &sav_ucmd, ucmd, sizeof( usercmd_t ) ); + memset( ucmd, 0, sizeof( usercmd_t ) ); + //to keep pointing in same dir, need to set ucmd->angles + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + ucmd->angles[ROLL] = 0; + if ( controlled->NPC ) + { + VectorClear( controlled->client->ps.moveDir ); + controlled->client->ps.speed = (sav_ucmd.buttons&BUTTON_WALKING)?controlled->NPC->stats.walkSpeed:controlled->NPC->stats.runSpeed; + } + } + else + { + ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + ucmd->angles[ROLL] = 0; + } + } + else if ( ent->client->NPC_class == CLASS_ATST ) + { + if ( ucmd->upmove > 0 ) + {//get out of ATST + GEntity_UseFunc( ent->activator, ent, ent ); + ucmd->upmove = 0;//ucmd->buttons = 0; + } + } + + + PM_CheckForceUseButton( ent, ucmd ); + } + + Vehicle_t *pVeh = NULL; + + // Rider logic. + // NOTE: Maybe this should be extracted into a RiderUpdate() within the vehicle. + if ( ( pVeh = G_IsRidingVehicle( ent ) ) != 0 ) + { + // If we're still in the vehicle... + if ( pVeh->m_pVehicleInfo->UpdateRider( pVeh, ent, ucmd ) ) + { + restore_ucmd = qtrue; + memcpy( &sav_ucmd, ucmd, sizeof( usercmd_t ) ); + memset( ucmd, 0, sizeof( usercmd_t ) ); + //to keep pointing in same dir, need to set ucmd->angles + //ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH]; + //ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW]; + //ucmd->angles[ROLL] = 0; + ucmd->angles[PITCH] = sav_ucmd.angles[PITCH]; + ucmd->angles[YAW] = sav_ucmd.angles[YAW]; + ucmd->angles[ROLL] = sav_ucmd.angles[ROLL]; + //if ( sav_ucmd.weapon != ent->client->ps.weapon && PM_WeaponOkOnVehicle( sav_ucmd.weapon ) ) + {//trying to change weapons to a valid weapon for this vehicle, to preserve this weapon change command + ucmd->weapon = sav_ucmd.weapon; + } + //else + {//keep our current weapon + // ucmd->weapon = ent->client->ps.weapon; + // if ( ent->client->ps.weapon != WP_NONE ) + {//not changing weapons and we are using one of our weapons, not using vehicle weapon + //so we actually want to do our fire weapon on us, not the vehicle + ucmd->buttons = (sav_ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)); + // sav_ucmd.buttons &= ~ucmd->buttons; + } + } + } + } + + ent->client->usercmd = *ucmd; + +// if ( !g_syncronousClients->integer ) + { + ClientThink_real( ent, ucmd ); + } + + // If a vehicle, make sure to attach our driver and passengers here (after we pmove, which is done in Think_Real)) + if ( ent->client && ent->client->NPC_class == CLASS_VEHICLE ) + { + pVeh = ent->m_pVehicle; + pVeh->m_pVehicleInfo->AttachRiders( pVeh ); + } + + + // ClientThink_real can end up freeing this ent, need to check + if ( restore_ucmd && ent->client ) + {//restore ucmd for later so NPC you're controlling can refer to them + memcpy( &ent->client->usercmd, &sav_ucmd, sizeof( usercmd_t ) ); + } + + if ( ent->s.number ) + {//NPCs drown, burn from lava, etc, also + P_WorldEffects( ent ); + } +} + +void ClientEndPowerUps( gentity_t *ent ) +{ + int i; + + if ( ent == NULL || ent->client == NULL ) + { + return; + } + // turn off any expired powerups + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) + { + if ( ent->client->ps.powerups[ i ] < level.time ) + { + ent->client->ps.powerups[ i ] = 0; + } + } +} +/* +============== +ClientEndFrame + +Called at the end of each server frame for each connected client +A fast client will have multiple ClientThink for each ClientEdFrame, +while a slow client may have multiple ClientEndFrame between ClientThink. +============== +*/ +void ClientEndFrame( gentity_t *ent ) +{ + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + + // burn from lava, etc + P_WorldEffects (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // add the EF_CONNECTION flag if we haven't gotten commands recently + /* + if ( level.time - ent->client->lastCmdTime > 1000 ) { + ent->s.eFlags |= EF_CONNECTION; + } else { + ent->s.eFlags &= ~EF_CONNECTION; + } + */ + + ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... + +// G_SetClientSound (ent); +} + + diff --git a/code/game/g_breakable.cpp b/code/game/g_breakable.cpp new file mode 100644 index 0000000..571c332 --- /dev/null +++ b/code/game/g_breakable.cpp @@ -0,0 +1,1535 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "g_functions.h" +#include "..\cgame\cg_media.h" + +//client side shortcut hacks from cg_local.h +//extern void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke ); +extern void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType ); +extern void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs, + float speed, int numChunks, material_t chunkType, int customChunk, float baseScale, int customSound = 0 ); +extern void G_SetEnemy( gentity_t *self, gentity_t *enemy ); + +extern gentity_t *G_CreateObject ( gentity_t *owner, vec3_t origin, vec3_t angles, int modelIndex, int frame, trType_t trType, int effectID ); + +extern qboolean player_locked; + +//--------------------------------------------------- +static void CacheChunkEffects( material_t material ) +{ + switch( material ) + { + case MAT_GLASS: + G_EffectIndex( "chunks/glassbreak" ); + break; + case MAT_GLASS_METAL: + G_EffectIndex( "chunks/glassbreak" ); + G_EffectIndex( "chunks/metalexplode" ); + break; + case MAT_ELECTRICAL: + case MAT_ELEC_METAL: + G_EffectIndex( "chunks/sparkexplode" ); + break; + case MAT_METAL: + case MAT_METAL2: + case MAT_METAL3: + case MAT_CRATE1: + case MAT_CRATE2: + G_EffectIndex( "chunks/metalexplode" ); + break; + case MAT_GRATE1: + G_EffectIndex( "chunks/grateexplode" ); + break; + case MAT_DRK_STONE: + case MAT_LT_STONE: + case MAT_GREY_STONE: + case MAT_WHITE_METAL: // what is this crap really supposed to be?? + G_EffectIndex( "chunks/rockbreaklg" ); + G_EffectIndex( "chunks/rockbreakmed" ); + break; + case MAT_ROPE: + G_EffectIndex( "chunks/ropebreak" ); +// G_SoundIndex(); // FIXME: give it a sound + break; + } +} + +//-------------------------------------- +void funcBBrushDieGo (gentity_t *self) +{ + vec3_t org, dir, up; + gentity_t *attacker = self->enemy; + float scale; + int numChunks, size = 0; + material_t chunkType = self->material; + + // if a missile is stuck to us, blow it up so we don't look dumb + // FIXME: flag me so I should know to do this check! + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK )) + { + G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD? + } + } + + //NOTE: MUST do this BEFORE clearing contents, or you may not open the area portal!!! + gi.AdjustAreaPortalState( self, qtrue ); + + //So chunks don't get stuck inside me + self->s.solid = 0; + self->contents = 0; + self->clipmask = 0; + gi.linkentity(self); + + VectorSet(up, 0, 0, 1); + + if ( self->target && attacker != NULL ) + { + G_UseTargets(self, attacker); + } + + VectorSubtract( self->absmax, self->absmin, org );// size + + numChunks = random() * 6 + 18; + + // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted. + // Volume is length * width * height...then break that volume down based on how many chunks we have + scale = sqrt( sqrt( org[0] * org[1] * org[2] )) * 1.75f; + + if ( scale > 48 ) + { + size = 2; + } + else if ( scale > 24 ) + { + size = 1; + } + + scale = scale / numChunks; + + if ( self->radius > 0.0f ) + { + // designer wants to scale number of chunks, helpful because the above scale code is far from perfect + // I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak + numChunks *= self->radius; + } + + VectorMA( self->absmin, 0.5, org, org ); + VectorAdd( self->absmin,self->absmax, org ); + VectorScale( org, 0.5f, org ); + + if ( attacker != NULL && attacker->client ) + { + VectorSubtract( org, attacker->currentOrigin, dir ); + VectorNormalize( dir ); + } + else + { + VectorCopy(up, dir); + } + + if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION + { + // we are allowed to explode + CG_MiscModelExplosion( self->absmin, self->absmax, size, chunkType ); + } + + if ( self->splashDamage > 0 && self->splashRadius > 0 ) + { + //explode + AddSightEvent( attacker, org, 256, AEL_DISCOVERED, 100 ); + AddSoundEvent( attacker, org, 128, AEL_DISCOVERED, qfalse, qtrue );//FIXME: am I on ground or not? + G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN ); + + gentity_t *te = G_TempEntity( org, EV_GENERAL_SOUND ); + te->s.eventParm = G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"); + } + else + {//just break + AddSightEvent( attacker, org, 128, AEL_DISCOVERED ); + AddSoundEvent( attacker, org, 64, AEL_SUSPICIOUS, qfalse, qtrue );//FIXME: am I on ground or not? + } + + //FIXME: base numChunks off size? + CG_Chunks( self->s.number, org, dir, self->absmin, self->absmax, 300, numChunks, chunkType, 0, scale, self->noise_index ); + + self->e_ThinkFunc = thinkF_G_FreeEntity; + self->nextthink = level.time + 50; + //G_FreeEntity( self ); +} + +void funcBBrushDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc) +{ + self->takedamage = qfalse;//stop chain reaction runaway loops + + G_SetEnemy(self, attacker); + + if(self->delay) + { + self->e_ThinkFunc = thinkF_funcBBrushDieGo; + self->nextthink = level.time + floor(self->delay * 1000.0f); + return; + } + + funcBBrushDieGo(self); +} + +void funcBBrushUse (gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior( self, BSET_USE ); + if(self->spawnflags & 64) + {//Using it doesn't break it, makes it use it's targets + if(self->target && self->target[0]) + { + G_UseTargets(self, activator); + } + } + else + { + funcBBrushDie(self, other, activator, self->health, MOD_UNKNOWN); + } +} + +void funcBBrushPain(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc) +{ + if ( self->painDebounceTime > level.time ) + { + return; + } + + if ( self->paintarget ) + { + G_UseTargets2 (self, self->activator, self->paintarget); + } + + G_ActivateBehavior( self, BSET_PAIN ); + + if ( self->material == MAT_DRK_STONE + || self->material == MAT_LT_STONE + || self->material == MAT_GREY_STONE ) + { + vec3_t org, dir; + float scale; + VectorSubtract( self->absmax, self->absmin, org );// size + // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted. + // Volume is length * width * height...then break that volume down based on how many chunks we have + scale = VectorLength( org ) / 100.0f; + VectorMA( self->absmin, 0.5, org, org ); + VectorAdd( self->absmin,self->absmax, org ); + VectorScale( org, 0.5f, org ); + if ( attacker != NULL && attacker->client ) + { + VectorSubtract( attacker->currentOrigin, org, dir ); + VectorNormalize( dir ); + } + else + { + VectorSet( dir, 0, 0, 1 ); + } + CG_Chunks( self->s.number, org, dir, self->absmin, self->absmax, 300, Q_irand( 1, 3 ), self->material, 0, scale ); + } + + if ( self->wait == -1 ) + { + self->e_PainFunc = painF_NULL; + return; + } + + self->painDebounceTime = level.time + self->wait; +} + +static void InitBBrush ( gentity_t *ent ) +{ + float light; + vec3_t color; + qboolean lightSet, colorSet; + + VectorCopy( ent->s.origin, ent->pos1 ); + + gi.SetBrushModel( ent, ent->model ); + + ent->e_DieFunc = dieF_funcBBrushDie; + + ent->svFlags |= SVF_BBRUSH; + + // if the "model2" key is set, use a seperate model + // for drawing, but clip against the brushes + if ( ent->model2 ) + { + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + } + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + if ( lightSet || colorSet ) + { + int r, g, b, i; + + r = color[0] * 255; + if ( r > 255 ) { + r = 255; + } + g = color[1] * 255; + if ( g > 255 ) { + g = 255; + } + b = color[2] * 255; + if ( b > 255 ) { + b = 255; + } + i = light / 4; + if ( i > 255 ) { + i = 255; + } + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + if(ent->spawnflags & 128) + {//Can be used by the player's BUTTON_USE + ent->svFlags |= SVF_PLAYER_USABLE; + } + + ent->s.eType = ET_MOVER; + gi.linkentity (ent); + + ent->s.pos.trType = TR_STATIONARY; + VectorCopy( ent->pos1, ent->s.pos.trBase ); +} + +void funcBBrushTouch( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ +} + +/*QUAKED func_breakable (0 .8 .5) ? INVINCIBLE IMPACT CRUSHER THIN SABERONLY HEAVY_WEAP USE_NOT_BREAK PLAYER_USE NO_EXPLOSION +INVINCIBLE - can only be broken by being used +IMPACT - does damage on impact +CRUSHER - won't reverse movement when hit an obstacle +THIN - can be broken by impact damage, like glass +SABERONLY - only takes damage from sabers +HEAVY_WEAP - only takes damage by a heavy weapon, like an emplaced gun or AT-ST gun. +USE_NOT_BREAK - Using it doesn't make it break, still can be destroyed by damage +PLAYER_USE - Player can use it with the use button +NO_EXPLOSION - Does not play an explosion effect, though will still create chunks if specified + +When destroyed, fires it's trigger and chunks and plays sound "noise" or sound for type if no noise specified + +"targetname" entities with matching target will fire it +"paintarget" target to fire when hit (but not destroyed) +"wait" how long minimum to wait between firing paintarget each time hit +"delay" When killed or used, how long (in seconds) to wait before blowing up (none by default) +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"target" all entities with a matching targetname will be used when this is destoryed +"health" default is 10 +"radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks +"NPC_targetname" - Only the NPC with this name can damage this +"forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level... +"redCrosshair" - crosshair turns red when you look at this + +Damage: default is none +"splashDamage" - damage to do +"splashRadius" - radius for above damage + +"team" - This cannot take damage from members of this team: + "player" + "neutral" + "enemy" + +Don't know if these work: +"color" constantLight color +"light" constantLight radius + +"material" - default is "0 - MAT_METAL" - choose from this list: +0 = MAT_METAL (basic blue-grey scorched-DEFAULT) +1 = MAT_GLASS +2 = MAT_ELECTRICAL (sparks only) +3 = MAT_ELEC_METAL (METAL2 chunks and sparks) +4 = MAT_DRK_STONE (brown stone chunks) +5 = MAT_LT_STONE (tan stone chunks) +6 = MAT_GLASS_METAL (glass and METAL2 chunks) +7 = MAT_METAL2 (electronic type of metal) +8 = MAT_NONE (no chunks) +9 = MAT_GREY_STONE (grey colored stone) +10 = MAT_METAL3 (METAL and METAL2 chunk combo) +11 = MAT_CRATE1 (yellow multi-colored crate chunks) +12 = MAT_GRATE1 (grate chunks--looks horrible right now) +13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits ) +14 = MAT_CRATE2 (red multi-colored crate chunks) +15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout ) + +*/ +void SP_func_breakable( gentity_t *self ) +{ + if(!(self->spawnflags & 1)) + { + if(!self->health) + { + self->health = 10; + } + } + + if ( self->spawnflags & 16 ) // saber only + { + self->flags |= FL_DMG_BY_SABER_ONLY; + } + else if ( self->spawnflags & 32 ) // heavy weap + { + self->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY; + } + + if (self->health) + { + self->takedamage = qtrue; + } + + G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");//precaching + G_SpawnFloat( "radius", "1", &self->radius ); // used to scale chunk code if desired by a designer + G_SpawnInt( "material", "0", (int*)&self->material ); + CacheChunkEffects( self->material ); + + self->e_UseFunc = useF_funcBBrushUse; + + //if ( self->paintarget ) + { + self->e_PainFunc = painF_funcBBrushPain; + } + + self->e_TouchFunc = touchF_funcBBrushTouch; + + if ( self->team && self->team[0] ) + { + self->noDamageTeam = (team_t)GetIDForString( TeamTable, self->team ); + if(self->noDamageTeam == TEAM_FREE) + { + G_Error("team name %s not recognized\n", self->team); + } + } + self->team = NULL; + if (!self->model) { + G_Error("func_breakable with NULL model\n"); + } + InitBBrush( self ); + + char buffer[MAX_QPATH]; + char *s; + if ( G_SpawnString( "noise", "*NOSOUND*", &s ) ) + { + Q_strncpyz( buffer, s, sizeof(buffer) ); + COM_DefaultExtension( buffer, sizeof(buffer), ".wav"); + self->noise_index = G_SoundIndex(buffer); + } + + int forceVisible = 0; + G_SpawnInt( "forcevisible", "0", &forceVisible ); + if ( forceVisible ) + {//can see these through walls with force sight, so must be broadcast + if ( VectorCompare( self->s.origin, vec3_origin ) ) + {//no origin brush + self->svFlags |= SVF_BROADCAST; + } + self->s.eFlags |= EF_FORCE_VISIBLE; + } + + int redCrosshair = 0; + G_SpawnInt( "redCrosshair", "0", &redCrosshair ); + if ( redCrosshair ) + {//can see these through walls with force sight, so must be broadcast + self->flags |= FL_RED_CROSSHAIR; + } +} + + +void misc_model_breakable_pain ( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ + if ( self->health > 0 ) + { + // still alive, react to the pain + if ( self->paintarget ) + { + G_UseTargets2 (self, self->activator, self->paintarget); + } + + // Don't do script if dead + G_ActivateBehavior( self, BSET_PAIN ); + } +} + +void misc_model_breakable_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc ) +{ + int numChunks; + float size = 0, scale; + vec3_t dir, up, dis; + + if (self->e_DieFunc == dieF_NULL) //i was probably already killed since my die func was removed + { +#ifndef FINAL_BUILD + gi.Printf(S_COLOR_YELLOW"Recursive misc_model_breakable_die. Use targets probably pointing back at self.\n"); +#endif + return; //this happens when you have a cyclic target chain! + } + //NOTE: Stop any scripts that are currently running (FLUSH)... ? + //Turn off animation + self->s.frame = self->startFrame = self->endFrame = 0; + self->svFlags &= ~SVF_ANIMATING; + + self->health = 0; + //Throw some chunks + AngleVectors( self->s.apos.trBase, dir, NULL, NULL ); + VectorNormalize( dir ); + + numChunks = random() * 6 + 20; + + VectorSubtract( self->absmax, self->absmin, dis ); + + // This formula really has no logical basis other than the fact that it seemed to be the closest to yielding the results that I wanted. + // Volume is length * width * height...then break that volume down based on how many chunks we have + scale = sqrt( sqrt( dis[0] * dis[1] * dis[2] )) * 1.75f; + + if ( scale > 48 ) + { + size = 2; + } + else if ( scale > 24 ) + { + size = 1; + } + + scale = scale / numChunks; + + if ( self->radius > 0.0f ) + { + // designer wants to scale number of chunks, helpful because the above scale code is far from perfect + // I do this after the scale calculation because it seems that the chunk size generally seems to be very close, it's just the number of chunks is a bit weak + numChunks *= self->radius; + } + + VectorAdd( self->absmax, self->absmin, dis ); + VectorScale( dis, 0.5f, dis ); + + CG_Chunks( self->s.number, dis, dir, self->absmin, self->absmax, 300, numChunks, self->material, self->s.modelindex3, scale ); + + self->e_PainFunc = painF_NULL; + self->e_DieFunc = dieF_NULL; +// self->e_UseFunc = useF_NULL; + + self->takedamage = qfalse; + + if ( !(self->spawnflags & 4) ) + {//We don't want to stay solid + self->s.solid = 0; + self->contents = 0; + self->clipmask = 0; + if (self!=0) + { + NAV::WayEdgesNowClear(self); + } + gi.linkentity(self); + } + + VectorSet(up, 0, 0, 1); + + if(self->target) + { + G_UseTargets(self, attacker); + } + + if(inflictor->client) + { + VectorSubtract( self->currentOrigin, inflictor->currentOrigin, dir ); + VectorNormalize( dir ); + } + else + { + VectorCopy(up, dir); + } + + if ( !(self->spawnflags & 2048) ) // NO_EXPLOSION + { + // Ok, we are allowed to explode, so do it now! + if(self->splashDamage > 0 && self->splashRadius > 0) + {//explode + vec3_t org; + AddSightEvent( attacker, self->currentOrigin, 256, AEL_DISCOVERED, 100 ); + AddSoundEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );//FIXME: am I on ground or not? + //FIXME: specify type of explosion? (barrel, electrical, etc.) Also, maybe just use the explosion effect below since it's + // a bit better? + // up the origin a little for the damage check, because several models have their origin on the ground, so they don't alwasy do damage, not the optimal solution... + VectorCopy( self->currentOrigin, org ); + if ( self->mins[2] > -4 ) + {//origin is going to be below it or very very low in the model + //center the origin + org[2] = self->currentOrigin[2] + self->mins[2] + (self->maxs[2] - self->mins[2])/2.0f; + } + G_RadiusDamage( org, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN ); + + if ( self->model && ( Q_stricmp( "models/map_objects/ships/tie_fighter.md3", self->model ) == 0 || + Q_stricmp( "models/map_objects/ships/tie_bomber.md3", self->model ) == 0 ) ) + {//TEMP HACK for Tie Fighters- they're HUGE + G_PlayEffect( "explosions/fighter_explosion2", self->currentOrigin ); + G_Sound( self, G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ) ); + self->s.loopSound = 0; + } + else + { + CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material ); + G_Sound( self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav") ); + self->s.loopSound = 0; + } + } + else + {//just break + AddSightEvent( attacker, self->currentOrigin, 128, AEL_DISCOVERED ); + AddSoundEvent( attacker, self->currentOrigin, 64, AEL_SUSPICIOUS, qfalse, qtrue );//FIXME: am I on ground or not? + // This is the default explosion + CG_MiscModelExplosion( self->absmin, self->absmax, size, self->material ); + G_Sound(self, G_SoundIndex("sound/weapons/explosions/cargoexplode.wav")); + } + } + + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = -1; + + if(self->s.modelindex2 != -1 && !(self->spawnflags & 8)) + {//FIXME: modelindex doesn't get set to -1 if the damage model doesn't exist + self->svFlags |= SVF_BROKEN; + self->s.modelindex = self->s.modelindex2; + G_ActivateBehavior( self, BSET_DEATH ); + } + else + { + G_FreeEntity( self ); + } +} + +void misc_model_throw_at_target4( gentity_t *self, gentity_t *activator ) +{ + vec3_t pushDir, kvel; + float knockback = 200; + float mass = self->mass; + gentity_t *target = G_Find( NULL, FOFS(targetname), self->target4 ); + if ( !target ) + {//nothing to throw ourselves at... + return; + } + VectorSubtract( target->currentOrigin, self->currentOrigin, pushDir ); + knockback -= VectorNormalize( pushDir ); + if ( knockback < 100 ) + { + knockback = 100; + } + VectorCopy( self->currentOrigin, self->s.pos.trBase ); + self->s.pos.trTime = level.time; // move a bit on the very first frame + if ( self->s.pos.trType != TR_INTERPOLATE ) + {//don't do this to rolling missiles + self->s.pos.trType = TR_GRAVITY; + } + + if ( mass < 50 ) + {//??? + mass = 50; + } + + if ( g_gravity->value > 0 ) + { + VectorScale( pushDir, g_knockback->value * knockback / mass * 0.8, kvel ); + kvel[2] = pushDir[2] * g_knockback->value * knockback / mass * 1.5; + } + else + { + VectorScale( pushDir, g_knockback->value * knockback / mass, kvel ); + } + + VectorAdd( self->s.pos.trDelta, kvel, self->s.pos.trDelta ); + if ( g_gravity->value > 0 ) + { + if ( self->s.pos.trDelta[2] < knockback ) + { + self->s.pos.trDelta[2] = knockback; + } + } + //no trDuration? + if ( self->e_ThinkFunc != thinkF_G_RunObject ) + {//objects spin themselves? + //spin it + //FIXME: messing with roll ruins the rotational center??? + self->s.apos.trTime = level.time; + self->s.apos.trType = TR_LINEAR; + VectorClear( self->s.apos.trDelta ); + self->s.apos.trDelta[1] = Q_irand( -800, 800 ); + } + + self->forcePushTime = level.time + 600; // let the push effect last for 600 ms + if ( activator ) + { + self->forcePuller = activator->s.number;//remember this regardless + } + else + { + self->forcePuller = NULL; + } +} + +void misc_model_use (gentity_t *self, gentity_t *other, gentity_t *activator) +{ + if ( self->target4 ) + {//throw me at my target! + misc_model_throw_at_target4( self, activator ); + return; + } + + if ( self->health <= 0 && self->max_health > 0) + {//used while broken fired target3 + G_UseTargets2( self, activator, self->target3 ); + return; + } + + // Become solid again. + if ( !self->count ) + { + self->count = 1; + self->activator = activator; + self->svFlags &= ~SVF_NOCLIENT; + self->s.eFlags &= ~EF_NODRAW; + } + + G_ActivateBehavior( self, BSET_USE ); + //Don't explode if they've requested it to not + if ( self->spawnflags & 64 ) + {//Usemodels toggling + if ( self->spawnflags & 32 ) + { + if( self->s.modelindex == self->sound1to2 ) + { + self->s.modelindex = self->sound2to1; + } + else + { + self->s.modelindex = self->sound1to2; + } + } + + return; + } + + self->e_DieFunc = dieF_misc_model_breakable_die; + misc_model_breakable_die( self, other, activator, self->health, MOD_UNKNOWN ); +} + +#define MDL_OTHER 0 +#define MDL_ARMOR_HEALTH 1 +#define MDL_AMMO 2 + +void misc_model_breakable_init( gentity_t *ent ) +{ + int type; + + type = MDL_OTHER; + + if (!ent->model) { + G_Error("no model set on %s at (%.1f %.1f %.1f)\n", ent->classname, ent->s.origin[0],ent->s.origin[1],ent->s.origin[2]); + } + //Main model + ent->s.modelindex = ent->sound2to1 = G_ModelIndex( ent->model ); + + if ( ent->spawnflags & 1 ) + {//Blocks movement + ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this + } + else if ( ent->health ) + {//Can only be shot + ent->contents = CONTENTS_SHOTCLIP; + } + + if (type == MDL_OTHER) + { + ent->e_UseFunc = useF_misc_model_use; + } + else if ( type == MDL_ARMOR_HEALTH ) + { +// G_SoundIndex("sound/player/suithealth.wav"); + ent->e_UseFunc = useF_health_use; + if (!ent->count) + { + ent->count = 100; + } + ent->health = 60; + } + else if ( type == MDL_AMMO ) + { +// G_SoundIndex("sound/player/suitenergy.wav"); + ent->e_UseFunc = useF_ammo_use; + if (!ent->count) + { + ent->count = 100; + } + ent->health = 60; + } + + if ( ent->health ) + { + G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"); + ent->max_health = ent->health; + ent->takedamage = qtrue; + ent->e_PainFunc = painF_misc_model_breakable_pain; + ent->e_DieFunc = dieF_misc_model_breakable_die; + } +} + +void TieFighterThink ( gentity_t *self ) +{ + gentity_t *player = &g_entities[0]; + + if ( self->health <= 0 ) + { + return; + } + + self->nextthink = level.time + FRAMETIME; + if ( player ) + { + vec3_t playerDir, fighterDir, fwd, rt; + float playerDist, fighterSpeed; + + //use player eyepoint + VectorSubtract( player->currentOrigin, self->currentOrigin, playerDir ); + playerDist = VectorNormalize( playerDir ); + VectorSubtract( self->currentOrigin, self->lastOrigin, fighterDir ); + VectorCopy( self->currentOrigin, self->lastOrigin ); + fighterSpeed = VectorNormalize( fighterDir )*1000; + AngleVectors( self->currentAngles, fwd, rt, NULL ); + + if ( fighterSpeed ) + { + float side; + + // Magic number fun! Speed is used for banking, so modulate the speed by a sine wave + fighterSpeed *= sin( ( 100 ) * 0.003 ); + + // Clamp to prevent harsh rolling + if ( fighterSpeed > 10 ) + fighterSpeed = 10; + + side = fighterSpeed * DotProduct( fighterDir, rt ); + self->s.apos.trBase[2] -= side; + } + + //FIXME: bob up/down, strafe left/right some + float dot = DotProduct( playerDir, fighterDir ); + if ( dot > 0 ) + {//heading toward the player + if ( playerDist < 1024 ) + { + if ( DotProduct( playerDir, fwd ) > 0.7 ) + {//facing the player + if ( self->attackDebounceTime < level.time ) + { + gentity_t *bolt; + + bolt = G_Spawn(); + + bolt->classname = "tie_proj"; + bolt->nextthink = level.time + 10000; + bolt->e_ThinkFunc = thinkF_G_FreeEntity; + bolt->s.eType = ET_MISSILE; + bolt->s.weapon = WP_BLASTER; + bolt->owner = self; + bolt->damage = 30; + bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->methodOfDeath = MOD_ENERGY; // ? + bolt->clipmask = MASK_SHOT; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time; // move a bit on the very first frame + VectorCopy( self->currentOrigin, bolt->s.pos.trBase ); + VectorScale( fwd, 8000, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( self->currentOrigin, bolt->currentOrigin); + + if ( !Q_irand( 0, 2 ) ) + { + G_SoundOnEnt( bolt, CHAN_VOICE, "sound/weapons/tie_fighter/tie_fire.wav" ); + } + else + { + G_SoundOnEnt( bolt, CHAN_VOICE, va( "sound/weapons/tie_fighter/tie_fire%d.wav", Q_irand( 2, 3 ) ) ); + } + self->attackDebounceTime = level.time + Q_irand( 300, 2000 ); + } + } + } + } + + if ( playerDist < 1024 )//512 ) + {//within range to start our sound + if ( dot > 0 ) + { + if ( !self->fly_sound_debounce_time ) + {//start sound + G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/tie_fighter/tiepass%d.wav", Q_irand( 1, 5 ) ) ); + self->fly_sound_debounce_time = 2000; + } + else + {//sound already started + self->fly_sound_debounce_time = -1; + } + } + } + else if ( self->fly_sound_debounce_time < level.time ) + { + self->fly_sound_debounce_time = 0; + } + } +} + +void TieFighterUse( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( !self || !other || !activator ) + return; + + vec3_t fwd, rt; + AngleVectors( self->currentAngles, fwd, rt, NULL ); + + gentity_t *bolt; + bolt = G_Spawn(); + + bolt->classname = "tie_proj"; + bolt->nextthink = level.time + 10000; + bolt->e_ThinkFunc = thinkF_G_FreeEntity; + bolt->s.eType = ET_MISSILE; + bolt->s.weapon = WP_TIE_FIGHTER; + bolt->owner = self; + bolt->damage = 30; + bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->methodOfDeath = MOD_ENERGY; // ? + bolt->clipmask = MASK_SHOT; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time; // move a bit on the very first frame + VectorCopy( self->currentOrigin, bolt->s.pos.trBase ); + rt[2] += 2.0f; + VectorMA( bolt->s.pos.trBase, -15.0, rt, bolt->s.pos.trBase ); + VectorScale( fwd, 3000, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( self->currentOrigin, bolt->currentOrigin); + + bolt = G_Spawn(); + + bolt->classname = "tie_proj"; + bolt->nextthink = level.time + 10000; + bolt->e_ThinkFunc = thinkF_G_FreeEntity; + bolt->s.eType = ET_MISSILE; + bolt->s.weapon = WP_TIE_FIGHTER; + bolt->owner = self; + bolt->damage = 30; + bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->methodOfDeath = MOD_ENERGY; // ? + bolt->clipmask = MASK_SHOT; + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time; // move a bit on the very first frame + VectorCopy( self->currentOrigin, bolt->s.pos.trBase ); + rt[2] -= 4.0f; + VectorMA( bolt->s.pos.trBase, 15.0, rt, bolt->s.pos.trBase ); + VectorScale( fwd, 3000, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( self->currentOrigin, bolt->currentOrigin); +} + +void TouchTieBomb( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + // Stop the effect. + G_StopEffect( G_EffectIndex( "ships/tiebomber_bomb_falling" ), self->playerModel, gi.G2API_AddBolt( &self->ghoul2[0], "model_root" ), self->s.number ); + + self->e_ThinkFunc = thinkF_G_FreeEntity; + self->nextthink = level.time + FRAMETIME; + G_PlayEffect( G_EffectIndex( "ships/tiebomber_explosion2" ), self->currentOrigin, self->currentAngles ); + G_RadiusDamage( self->currentOrigin, self, 900, 500, self, MOD_EXPLOSIVE_SPLASH ); +} + +#define MIN_PLAYER_DIST 1600 + +void TieBomberThink( gentity_t *self ) +{ + // NOTE: Lerp2Angles will think this thinkfunc if the model is a misc_model_breakable. Watchout + // for that in a script (try to just use ROFF's?). + + // Stop thinking, you're dead. + if ( self->health <= 0 ) + { + return; + } + + // Needed every think... + self->nextthink = level.time + FRAMETIME; + + gentity_t *player = &g_entities[0]; + vec3_t playerDir; + float playerDist; + + //use player eyepoint + VectorSubtract( player->currentOrigin, self->currentOrigin, playerDir ); + playerDist = VectorNormalize( playerDir ); + + // Time to attack? + if ( player->health > 0 && playerDist < MIN_PLAYER_DIST && self->attackDebounceTime < level.time ) + { + char name1[200] = "models/players/remote/model.glm"; + gentity_t *bomb = G_CreateObject( self, self->s.pos.trBase, self->s.apos.trBase, 0, 0, TR_GRAVITY, 0 ); + bomb->s.modelindex = G_ModelIndex( name1 ); + gi.G2API_InitGhoul2Model( bomb->ghoul2, name1, bomb->s.modelindex); + bomb->s.radius = 50; + bomb->s.eFlags |= EF_NODRAW; + + // Make the bombs go forward in the bombers direction a little. + vec3_t fwd, rt; + AngleVectors( self->currentAngles, fwd, rt, NULL ); + rt[2] -= 0.5f; + VectorMA( bomb->s.pos.trBase, -30.0, rt, bomb->s.pos.trBase ); + VectorScale( fwd, 300, bomb->s.pos.trDelta ); + SnapVector( bomb->s.pos.trDelta ); // save net bandwidth + + // Start the effect. + G_PlayEffect( G_EffectIndex( "ships/tiebomber_bomb_falling" ), bomb->playerModel, gi.G2API_AddBolt( &bomb->ghoul2[0], "model_root" ), bomb->s.number, bomb->currentOrigin, 1000, qtrue ); + + // Set the tie bomb to have a touch function, so when it hits the ground (or whatever), there's a nice 'boom'. + bomb->e_TouchFunc = touchF_TouchTieBomb; + + self->attackDebounceTime = level.time + 1000; + } +} + +void misc_model_breakable_gravity_init( gentity_t *ent, qboolean dropToFloor ) +{ + //G_SoundIndex( "sound/movers/objects/objectHurt.wav" ); + G_EffectIndex( "melee/kick_impact" ); + G_EffectIndex( "melee/kick_impact_silent" ); + //G_SoundIndex( "sound/weapons/melee/punch1.mp3" ); + //G_SoundIndex( "sound/weapons/melee/punch2.mp3" ); + //G_SoundIndex( "sound/weapons/melee/punch3.mp3" ); + //G_SoundIndex( "sound/weapons/melee/punch4.mp3" ); + G_SoundIndex( "sound/movers/objects/objectHit.wav" ); + G_SoundIndex( "sound/movers/objects/objectHitHeavy.wav" ); + G_SoundIndex( "sound/movers/objects/objectBreak.wav" ); + //FIXME: dust impact effect when hits ground? + ent->s.eType = ET_GENERAL; + ent->s.eFlags |= EF_BOUNCE_HALF; + ent->clipmask = MASK_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//? + if ( !ent->mass ) + {//not overridden by designer + ent->mass = VectorLength( ent->maxs ) + VectorLength( ent->mins ); + } + ent->physicsBounce = ent->mass; + //drop to floor + trace_t tr; + vec3_t top, bottom; + + if ( dropToFloor ) + { + VectorCopy( ent->currentOrigin, top ); + top[2] += 1; + VectorCopy( ent->currentOrigin, bottom ); + bottom[2] = MIN_WORLD_COORD; + gi.trace( &tr, top, ent->mins, ent->maxs, bottom, ent->s.number, MASK_NPCSOLID ); + if ( !tr.allsolid && !tr.startsolid && tr.fraction < 1.0 ) + { + G_SetOrigin( ent, tr.endpos ); + gi.linkentity( ent ); + } + } + else + { + G_SetOrigin( ent, ent->currentOrigin ); + gi.linkentity( ent ); + } + //set up for object thinking + if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) ) + {//not moving + ent->s.pos.trType = TR_STATIONARY; + } + else + { + ent->s.pos.trType = TR_GRAVITY; + } + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + VectorClear( ent->s.pos.trDelta ); + ent->s.pos.trTime = level.time; + if ( VectorCompare( ent->s.apos.trDelta, vec3_origin ) ) + {//not moving + ent->s.apos.trType = TR_STATIONARY; + } + else + { + ent->s.apos.trType = TR_LINEAR; + } + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + VectorClear( ent->s.apos.trDelta ); + ent->s.apos.trTime = level.time; + ent->nextthink = level.time + FRAMETIME; + ent->e_ThinkFunc = thinkF_G_RunObject; +} + +/*QUAKED misc_model_breakable (1 0 0) (-16 -16 -16) (16 16 16) SOLID AUTOANIMATE DEADSOLID NO_DMODEL NO_SMOKE USE_MODEL USE_NOT_BREAK PLAYER_USE NO_EXPLOSION START_OFF +SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health +AUTOANIMATE - Will cycle it's anim +DEADSOLID - Stay solid even when destroyed (in case damage model is rather large). +NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists +USE_MODEL - When used, will toggle to it's usemodel (model + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked +USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage +PLAYER_USE - Player can use it with the use button +NO_EXPLOSION - By default, will explode when it dies...this is your override. +START_OFF - Will start off and will not appear until used. + +"model" arbitrary .md3 file to display +"modelscale" "x" uniform scale +"modelscale_vec" "x y z" scale model in each axis - height, width and length - bbox will scale with it +"health" how much health to have - default is zero (not breakable) If you don't set the SOLID flag, but give it health, it can be shot but will not block NPCs or players from moving +"targetname" when used, dies and displays damagemodel (model + "_d1.md3"), if any (if not, removes itself) +"target" What to use when it dies +"target2" What to use when it's repaired +"target3" What to use when it's used while it's broken +"paintarget" target to fire when hit (but not destroyed) +"count" the amount of armor/health/ammo given (default 50) +"radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks +"NPC_targetname" - Only the NPC with this name can damage this +"forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level... +"redCrosshair" - crosshair turns red when you look at this + +"gravity" if set to 1, this will be affected by gravity +"throwtarget" if set (along with gravity), this thing, when used, will throw itself at the entity whose targetname matches this string +"mass" if gravity is on, this determines how much damage this thing does when it hits someone. Default is the size of the object from one corner to the other, that works very well. Only override if this is an object whose mass should be very high or low for it's size (density) + +Damage: default is none +"splashDamage" - damage to do (will make it explode on death) +"splashRadius" - radius for above damage + +"team" - This cannot take damage from members of this team: + "player" + "neutral" + "enemy" + +"material" - default is "8 - MAT_NONE" - choose from this list: +0 = MAT_METAL (grey metal) +1 = MAT_GLASS +2 = MAT_ELECTRICAL (sparks only) +3 = MAT_ELEC_METAL (METAL chunks and sparks) +4 = MAT_DRK_STONE (brown stone chunks) +5 = MAT_LT_STONE (tan stone chunks) +6 = MAT_GLASS_METAL (glass and METAL chunks) +7 = MAT_METAL2 (blue/grey metal) +8 = MAT_NONE (no chunks-DEFAULT) +9 = MAT_GREY_STONE (grey colored stone) +10 = MAT_METAL3 (METAL and METAL2 chunk combo) +11 = MAT_CRATE1 (yellow multi-colored crate chunks) +12 = MAT_GRATE1 (grate chunks--looks horrible right now) +13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits ) +14 = MAT_CRATE2 (red multi-colored crate chunks) +15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout ) +*/ +void SP_misc_model_breakable( gentity_t *ent ) +{ + char damageModel[MAX_QPATH]; + char chunkModel[MAX_QPATH]; + char useModel[MAX_QPATH]; + int len; + + // Chris F. requested default for misc_model_breakable to be NONE...so don't arbitrarily change this. + G_SpawnInt( "material", "8", (int*)&ent->material ); + G_SpawnFloat( "radius", "1", &ent->radius ); // used to scale chunk code if desired by a designer + qboolean bHasScale = G_SpawnVector("modelscale_vec", "0 0 0", ent->s.modelScale); + if (!bHasScale) + { + float temp; + G_SpawnFloat( "modelscale", "0", &temp); + if (temp != 0.0f) + { + ent->s.modelScale[ 0 ] = ent->s.modelScale[ 1 ] = ent->s.modelScale[ 2 ] = temp; + bHasScale = qtrue; + } + } + + CacheChunkEffects( ent->material ); + misc_model_breakable_init( ent ); + + len = strlen( ent->model ) - 4; + assert(ent->model[len]=='.');//we're expecting ".md3" + strncpy( damageModel, ent->model, sizeof(damageModel) ); + damageModel[len] = 0; //chop extension + strncpy( chunkModel, damageModel, sizeof(chunkModel)); + strncpy( useModel, damageModel, sizeof(useModel)); + + if (ent->takedamage) { + //Dead/damaged model + if( !(ent->spawnflags & 8) ) { //no dmodel + strcat( damageModel, "_d1.md3" ); + ent->s.modelindex2 = G_ModelIndex( damageModel ); + } + + //Chunk model + strcat( chunkModel, "_c1.md3" ); + ent->s.modelindex3 = G_ModelIndex( chunkModel ); + } + + //Use model + if( ent->spawnflags & 32 ) { //has umodel + strcat( useModel, "_u1.md3" ); + ent->sound1to2 = G_ModelIndex( useModel ); + } + if ( !ent->mins[0] && !ent->mins[1] && !ent->mins[2] ) + { + VectorSet (ent->mins, -16, -16, -16); + } + if ( !ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2] ) + { + VectorSet (ent->maxs, 16, 16, 16); + } + + // Scale up the tie-bomber bbox a little. + if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_bomber.md3", ent->model ) == 0 ) + { + VectorSet (ent->mins, -80, -80, -80); + VectorSet (ent->maxs, 80, 80, 80); + + //ent->s.modelScale[ 0 ] = ent->s.modelScale[ 1 ] = ent->s.modelScale[ 2 ] *= 2.0f; + //bHasScale = qtrue; + } + + if (bHasScale) + { + //scale the x axis of the bbox up. + ent->maxs[0] *= ent->s.modelScale[0];//*scaleFactor; + ent->mins[0] *= ent->s.modelScale[0];//*scaleFactor; + + //scale the y axis of the bbox up. + ent->maxs[1] *= ent->s.modelScale[1];//*scaleFactor; + ent->mins[1] *= ent->s.modelScale[1];//*scaleFactor; + + //scale the z axis of the bbox up and adjust origin accordingly + ent->maxs[2] *= ent->s.modelScale[2]; + float oldMins2 = ent->mins[2]; + ent->mins[2] *= ent->s.modelScale[2]; + ent->s.origin[2] += (oldMins2-ent->mins[2]); + } + + if ( ent->spawnflags & 2 ) + { + ent->s.eFlags |= EF_ANIM_ALLFAST; + } + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngles( ent, ent->s.angles ); + gi.linkentity (ent); + + if ( ent->spawnflags & 128 ) + {//Can be used by the player's BUTTON_USE + ent->svFlags |= SVF_PLAYER_USABLE; + } + + if ( ent->team && ent->team[0] ) + { + ent->noDamageTeam = (team_t)GetIDForString( TeamTable, ent->team ); + if ( ent->noDamageTeam == TEAM_FREE ) + { + G_Error("team name %s not recognized\n", ent->team); + } + } + + ent->team = NULL; + + //HACK + if ( ent->model && Q_stricmp( "models/map_objects/ships/x_wing_nogear.md3", ent->model ) == 0 ) + { + if( ent->splashDamage > 0 && ent->splashRadius > 0 ) + { + ent->s.loopSound = G_SoundIndex( "sound/vehicles/x-wing/loop.wav" ); + ent->s.eFlags |= EF_LESS_ATTEN; + } + } + else if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_fighter.md3", ent->model ) == 0 ) + {//run a think + G_EffectIndex( "explosions/fighter_explosion2" ); + G_SoundIndex( "sound/weapons/tie_fighter/tiepass1.wav" ); +/* G_SoundIndex( "sound/weapons/tie_fighter/tiepass2.wav" ); + G_SoundIndex( "sound/weapons/tie_fighter/tiepass3.wav" ); + G_SoundIndex( "sound/weapons/tie_fighter/tiepass4.wav" ); + G_SoundIndex( "sound/weapons/tie_fighter/tiepass5.wav" );*/ + G_SoundIndex( "sound/weapons/tie_fighter/tie_fire.wav" ); +/* G_SoundIndex( "sound/weapons/tie_fighter/tie_fire2.wav" ); + G_SoundIndex( "sound/weapons/tie_fighter/tie_fire3.wav" );*/ + G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ); + RegisterItem( FindItemForWeapon( WP_TIE_FIGHTER )); + + ent->s.eFlags |= EF_LESS_ATTEN; + + if( ent->splashDamage > 0 && ent->splashRadius > 0 ) + { + ent->s.loopSound = G_SoundIndex( "sound/vehicles/tie-bomber/loop.wav" ); + //ent->e_ThinkFunc = thinkF_TieFighterThink; + //ent->e_UseFunc = thinkF_TieFighterThink; + //ent->nextthink = level.time + FRAMETIME; + ent->e_UseFunc = useF_TieFighterUse; + + // Yeah, I could have just made this value changable from the editor, but I + // need it immediately! + float light; + vec3_t color; + qboolean lightSet, colorSet; + + // if the "color" or "light" keys are set, setup constantLight + lightSet = qtrue;//G_SpawnFloat( "light", "100", &light ); + light = 255; + //colorSet = "1 1 1"//G_SpawnVector( "color", "1 1 1", color ); + colorSet = qtrue; + color[0] = 1; color[1] = 1; color[2] = 1; + if ( lightSet || colorSet ) + { + int r, g, b, i; + + r = color[0] * 255; + if ( r > 255 ) { + r = 255; + } + g = color[1] * 255; + if ( g > 255 ) { + g = 255; + } + b = color[2] * 255; + if ( b > 255 ) { + b = 255; + } + i = light / 4; + if ( i > 255 ) { + i = 255; + } + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + } + } + else if ( ent->model && Q_stricmp( "models/map_objects/ships/tie_bomber.md3", ent->model ) == 0 ) + { + G_EffectIndex( "ships/tiebomber_bomb_falling" ); + G_EffectIndex( "ships/tiebomber_explosion2" ); + G_EffectIndex( "explosions/fighter_explosion2" ); + G_SoundIndex( "sound/weapons/tie_fighter/TIEexplode.wav" ); + ent->e_ThinkFunc = thinkF_TieBomberThink; + ent->nextthink = level.time + FRAMETIME; + ent->attackDebounceTime = level.time + 1000; + // We only take damage from a heavy weapon class missiles. + ent->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY; + ent->s.loopSound = G_SoundIndex( "sound/vehicles/tie-bomber/loop.wav" ); + ent->s.eFlags |= EF_LESS_ATTEN; + } + + float grav = 0; + G_SpawnFloat( "gravity", "0", &grav ); + if ( grav ) + {//affected by gravity + G_SetAngles( ent, ent->s.angles ); + G_SetOrigin( ent, ent->currentOrigin ); + G_SpawnString( "throwtarget", NULL, &ent->target4 ); // used to throw itself at something + misc_model_breakable_gravity_init( ent, qtrue ); + } + + // Start off. + if ( ent->spawnflags & 4096 ) + { + ent->spawnContents = ent->contents; // It Navs can temporarly turn it "on" + ent->s.solid = 0; + ent->contents = 0; + ent->clipmask = 0; + ent->svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + ent->count = 0; + } + + int forceVisible = 0; + G_SpawnInt( "forcevisible", "0", &forceVisible ); + if ( forceVisible ) + {//can see these through walls with force sight, so must be broadcast + //ent->svFlags |= SVF_BROADCAST; + ent->s.eFlags |= EF_FORCE_VISIBLE; + } + + int redCrosshair = 0; + G_SpawnInt( "redCrosshair", "0", &redCrosshair ); + if ( redCrosshair ) + {//can see these through walls with force sight, so must be broadcast + ent->flags |= FL_RED_CROSSHAIR; + } +} + + +//---------------------------------- +// +// Breaking Glass Technology +// +//---------------------------------- + +// Really naughty cheating. Put in an EVENT at some point... +extern void cgi_R_GetBModelVerts(int bmodelIndex, vec3_t *verts, vec3_t normal ); +extern void CG_DoGlass( vec3_t verts[4], vec3_t normal, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius ); +extern cgs_t cgs; + +//----------------------------------------------------- +void funcGlassDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) +{ + vec3_t verts[4], normal; + + // if a missile is stuck to us, blow it up so we don't look dumb....we could, alternately, just let the missile drop off?? + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + if ( g_entities[i].s.groundEntityNum == self->s.number && ( g_entities[i].s.eFlags & EF_MISSILE_STICK )) + { + G_Damage( &g_entities[i], self, self, NULL, NULL, 99999, 0, MOD_CRUSH ); //?? MOD? + } + } + + // Really naughty cheating. Put in an EVENT at some point... + cgi_R_GetBModelVerts( cgs.inlineDrawModel[self->s.modelindex], verts, normal ); + CG_DoGlass( verts, normal, self->pos1, self->pos2, self->splashRadius ); + + self->takedamage = qfalse;//stop chain reaction runaway loops + + G_SetEnemy( self, self->enemy ); + + //NOTE: MUST do this BEFORE clearing contents, or you may not open the area portal!!! + gi.AdjustAreaPortalState( self, qtrue ); + + //So chunks don't get stuck inside me + self->s.solid = 0; + self->contents = 0; + self->clipmask = 0; + gi.linkentity(self); + + if ( self->target && attacker != NULL ) + { + G_UseTargets( self, attacker ); + } + + G_FreeEntity( self ); +} + +//----------------------------------------------------- +void funcGlassUse( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + vec3_t temp1, temp2; + + // For now, we just break on use + G_ActivateBehavior( self, BSET_USE ); + + VectorAdd( self->mins, self->maxs, temp1 ); + VectorScale( temp1, 0.5f, temp1 ); + + VectorAdd( other->mins, other->maxs, temp2 ); + VectorScale( temp2, 0.5f, temp2 ); + + VectorSubtract( temp1, temp2, self->pos2 ); + VectorCopy( temp1, self->pos1 ); + + VectorNormalize( self->pos2 ); + VectorScale( self->pos2, 390, self->pos2 ); + + self->splashRadius = 40; // ?? some random number, maybe it's ok? + + funcGlassDie( self, other, activator, self->health, MOD_UNKNOWN ); +} + +//----------------------------------------------------- +/*QUAKED func_glass (0 .8 .5) ? INVINCIBLE +When destroyed, fires it's target, breaks into glass chunks and plays glass noise +For now, instantly breaks on impact + +INVINCIBLE - can only be broken by being used + +"targetname" entities with matching target will fire it +"target" all entities with a matching targetname will be used when this is destroyed +"health" default is 1 +*/ +//----------------------------------------------------- +void SP_func_glass( gentity_t *self ) +{ + if ( !(self->spawnflags & 1 )) + { + if ( !self->health ) + { + self->health = 1; + } + } + + if ( self->health ) + { + self->takedamage = qtrue; + } + + self->e_UseFunc = useF_funcGlassUse; + self->e_DieFunc = dieF_funcGlassDie; + + VectorCopy( self->s.origin, self->pos1 ); + + gi.SetBrushModel( self, self->model ); + self->svFlags |= (SVF_GLASS_BRUSH|SVF_BBRUSH); + self->material = MAT_GLASS; + + self->s.eType = ET_MOVER; + + self->s.pos.trType = TR_STATIONARY; + VectorCopy( self->pos1, self->s.pos.trBase ); + + G_SoundIndex( "sound/effects/glassbreak1.wav" ); + G_EffectIndex( "misc/glass_impact" ); + + gi.linkentity( self ); +} + +qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker ) +{//breakable brush/model that can actually be broken + if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD ) + { + return qfalse; + } + + gentity_t *ent = &g_entities[entityNum]; + if ( !ent->takedamage ) + { + return qfalse; + } + + if ( ent->NPC_targetname ) + {//only a specific entity can break this! + if ( !breaker + || !breaker->targetname + || Q_stricmp( ent->NPC_targetname, breaker->targetname ) != 0 ) + {//I'm not the one who can break it + return qfalse; + } + } + + if ( (ent->svFlags&SVF_GLASS_BRUSH) ) + { + return qtrue; + } + if ( (ent->svFlags&SVF_BBRUSH) ) + { + return qtrue; + } + if ( !Q_stricmp( "misc_model_breakable", ent->classname ) ) + { + return qtrue; + } + if ( !Q_stricmp( "misc_maglock", ent->classname ) ) + { + return qtrue; + } + + return qfalse; +} diff --git a/code/game/g_camera.cpp b/code/game/g_camera.cpp new file mode 100644 index 0000000..3535229 --- /dev/null +++ b/code/game/g_camera.cpp @@ -0,0 +1,256 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +//g_camera.cpp +#include "g_local.h" +//#include "Q3_Interface.h" +//#include "anims.h" +//#include "b_local.h" +#include "..\cgame\cg_camera.h" +#include "g_functions.h" + +/* +#define MAX_CAMERA_GROUP_SUBJECTS 16 +void misc_camera_focus_think (gentity_t *self) +{ + //Check to see if I should stop? + gi.linkentity(self); + self->nextthink = level.time + FRAMETIME; +} + +void misc_camera_focus_use (gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + //First, find everyone in my cameraGroup, if I have one + + //Now find my first path_corner, if I have one + + //Start thinking + + self->e_ThinkFunc = thinkF_misc_camera_focus_think; + misc_camera_focus_think(self); + + self->e_clThinkFunc = clThinkF_CG_MiscCameraFocusThink; // blurgh!... + self->s.eType = ET_THINKER; + + self->aimDebounceTime = level.time; +} +*/ +/*QUAK-ED misc_camera_focus (0 0 1) (-4 -4 -4) (4 4 4) lerptostart + +LERPTOSTART - With interpolate from camera's current angles to this thing's start angle instead of snapping to it, which is the default behavior + +The focal point for a camera in a scene + +"targetname" - Use it to get it to find it's cameraGroup and start tracking it, also get started on it's path, if any + +"cameraGroup" - will find all ents in this group and pick a point in the center of that group. + +"speed" angular speed modifier - 100 is normal +*/ +void SP_misc_camera_focus (gentity_t *self) +{ + if(!self->targetname) + { + gi.Printf(S_COLOR_RED"ERROR: misc_camera_focus with no targetname\n"); + G_FreeEntity(self); + return; + } + + /* + if(self->speed > 0) + { + self->moveInfo.aspeed = self->speed; + } + else + { + self->moveInfo.aspeed = 100.f; + } + */ + self->speed = 0; + self->script_targetname = G_NewString(self->targetname); +// self->e_UseFunc = useF_misc_camera_focus_use; +} + +/* +void misc_camera_track_think (gentity_t *self) +{ + vec3_t vec; + float dist; + //Check to see if I should stop? + + gi.linkentity(self); + + if(self->enemy) + {//We're already heading to a path_corner + VectorSubtract(self->currentOrigin, self->s.origin2, vec); + dist = VectorLengthSquared(vec); + if(dist < 256)//16 squared + { + G_UseTargets(self, self); + self->target = self->enemy->target; + self->enemy = NULL; + } + } + + if( !self->enemy) + { + if( self->target && self->target[0] ) + {//Find out next path_corner + self->enemy = G_Find(NULL, FOFS(targetname), self->target); + if(self->enemy) + { + if(self->enemy->radius < 0) + {//Don't bother trying to maintain a radius + self->radius = 0; + self->moveInfo.speed = self->speed/10.0f; + } + else if(self->enemy->radius > 0) + { + self->radius = self->enemy->radius; + } + + if(self->enemy->speed < 0) + {//go back to our default speed + self->moveInfo.speed = self->speed/10.0f; + } + else if(self->enemy->speed > 0) + { + self->moveInfo.speed = self->enemy->speed/10.0f; + } + } + } + else + {//stop thinking if this is the last one + self->e_ThinkFunc = thinkF_NULL; + self->e_clThinkFunc = clThinkF_NULL; + self->s.eType = ET_GENERAL; + self->nextthink = -1; + } + } + + if(self->enemy) + {//clThink will lerp this + VectorCopy(self->enemy->currentOrigin, self->s.origin2); + } + + self->nextthink = level.time + FRAMETIME; +} + +void misc_camera_track_use (gentity_t *self, gentity_t *other, gentity_t *activator) +{ + + G_ActivateBehavior(self,BSET_USE); + + //Start thinking + + self->e_ThinkFunc = thinkF_misc_camera_track_think; + misc_camera_track_think(self); + + self->e_clThinkFunc = clThinkF_CG_MiscCameraTrackThink; + self->s.eType = ET_THINKER; +} +*/ +/*QUAK-ED misc_camera_track (0 0 1) (-4 -4 -4) (4 4 4) + +The track for a camera to stay on + +"targetname" - Use it to get it started on it's path + +"target" - First point on it's path - if misc_camera_focus is on a path, it will pick the point on it's path closest to the above calced point +use "path_corner"s - path it should stay on- if that path_corner has a speed value, it will use this as it's speed to the next path_corner + +"speed" - How quickly to move, 0 by default + +"radius" - How far camera should try to stay from it's subject, default is 0 (dist doesn't matter), can pick this up from a path_corner too +*/ +void SP_misc_camera_track (gentity_t *self) +{ + if(!self->targetname || !self->targetname[0]) + { + gi.Printf(S_COLOR_RED"ERROR: misc_camera_track with no targetname\n"); + G_FreeEntity(self); + return; + } + + self->script_targetname = G_NewString(self->targetname); + //self->moveInfo.speed = self->speed/10; + +// self->e_UseFunc = useF_misc_camera_track_use; +} + + +//------------------------------------------------- +// Bezier camera stuff +//------------------------------------------------- + +void cam_point_link( gentity_t *ent ) +{ + +} + +void cam_ctrl_point_link( gentity_t *ent ) +{ +/* gentity_t *target2 = NULL; + + target2 = G_Find( NULL, FOFS(targetname), ent->target2 ); + + if ( !target2 ) + { + // Bah, you fool! Target2 not found + Com_Printf( "cam_point_link: target2 specified but not found: %s\n", ent->target2 ); + G_FreeEntity( ent ); + return; + } + + // Store the control point here + VectorCopy( target2->s.origin, ent->pos1 ); + + //--------------------- + if ( ent->target ) + { + gentity_t *target = NULL; + + target = G_Find( NULL, FOFS(targetname), ent->target ); + + if ( !target ) + { + // Bah, you fool! Target not found + Com_Printf( "cam_point_link: target specified but not found: %s\n", ent->target ); + G_FreeEntity( ent ); + return; + } + + ent->nextTrain = target; + } +*/ +} + +/*QUAK-ED cam_point (0.25 0 0.5) (-2 -2 -2) (2 2 2) +Under development -- DONT USE ME!!!!! +A camera point used to construct a camera bezier path + +Every cam_point MUST be targeted (target2) at one and only one control point +*/ +void SP_cam_point( gentity_t *ent ) +{ +/* if ( !ent->target2 ) + { + // Bah, you fool! Target2 not found so we have no idea how to make the curve + Com_Printf( "cam_point_link: target2 was required but not found\n" ); + G_FreeEntity( ent ); + return; + + } + + // The thing we are targeting may not be spawned in yet so, wait a bit to try and link to it + ent->e_ThinkFunc = thinkF_cam_ctrl_point_link; + ent->nextthink = level.time + 200; + + // Save our position and link us up! + G_SetOrigin( ent, ent->s.origin ); + gi.linkentity( ent ); +*/ +} \ No newline at end of file diff --git a/code/game/g_client.cpp b/code/game/g_client.cpp new file mode 100644 index 0000000..3a59010 --- /dev/null +++ b/code/game/g_client.cpp @@ -0,0 +1,2424 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "IcarusInterface.h" +#include "Q3_Interface.h" +#include "g_local.h" +#include "g_functions.h" +#include "anims.h" +#include "wp_saber.h" +#include "g_vehicles.h" +#include "objectives.h" + +extern int WP_SaberInitBladeData( gentity_t *ent ); +extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum ); +extern qboolean CheatsOk( gentity_t *ent ); +extern void Boba_Precache( void ); + +extern cvar_t *g_char_model; +extern cvar_t *g_char_skin_head; +extern cvar_t *g_char_skin_torso; +extern cvar_t *g_char_skin_legs; +extern cvar_t *g_char_color_red; +extern cvar_t *g_char_color_green; +extern cvar_t *g_char_color_blue; +extern cvar_t *g_saber; +extern cvar_t *g_saber2; +extern cvar_t *g_saber_color; +extern cvar_t *g_saber2_color; +extern cvar_t *g_saberDarkSideSaberColor; + +// g_client.c -- client functions that don't happen every frame + +float DEFAULT_MINS_0 = -16; +float DEFAULT_MINS_1 = -16; +float DEFAULT_MAXS_0 = 16; +float DEFAULT_MAXS_1 = 16; +float DEFAULT_PLAYER_RADIUS = sqrt((DEFAULT_MAXS_0*DEFAULT_MAXS_0) + (DEFAULT_MAXS_1*DEFAULT_MAXS_1)); +vec3_t playerMins = {DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2}; +vec3_t playerMinsStep = {DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2+STEPSIZE}; +vec3_t playerMaxs = {DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2}; + +void SP_misc_teleporter_dest (gentity_t *ent); + +/*QUAK-ED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) KEEP_PREV DROPTOFLOOR x x x STUN_BATON NOWEAPON x +potential spawning position for deathmatch games. +Targets will be fired when someone spawns in on them. +*/ +void SP_info_player_deathmatch(gentity_t *ent) { + SP_misc_teleporter_dest (ent); + + if ( ent->spawnflags & 32 ) // STUN_BATON + { + RegisterItem( FindItemForWeapon( WP_STUN_BATON )); + } + else + { + RegisterItem( FindItemForWeapon( WP_SABER ) ); //these are given in ClientSpawn(), but we register them now before cgame starts + saberInfo_t saber; + WP_SaberParseParms( g_saber->string, &saber );//get saber sounds and models cached before client begins + if (saber.model) G_ModelIndex( saber.model ); + if (saber.brokenSaber1) G_ModelIndex( saber.brokenSaber1 ); + if (saber.brokenSaber2) G_ModelIndex( saber.brokenSaber2 ); + if (saber.skin) G_SkinIndex( saber.skin ); + WP_SaberFreeStrings(saber); + } +} + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) KEEP_PREV DROPTOFLOOR x x x STUN_BATON NOWEAPON x +KEEP_PREV - keep previous health + armor +DROPTOFLOOR - Player will start on the first solid structure under it +STUN_BATON - Gives player the stun baton and bryar pistol, but not the saber, plus any weapons they may have carried over from previous levels. + +Targets will be fired when someone spawns in on them. +equivalant to info_player_deathmatch +*/ +void SP_info_player_start(gentity_t *ent) { + ent->classname = "info_player_deathmatch"; + + SP_info_player_deathmatch( ent ); +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +SpotWouldTelefrag + +================ +*/ +qboolean SpotWouldTelefrag( gentity_t *spot, team_t checkteam ) +{ + int i, num; + gentity_t *touch[MAX_GENTITIES], *hit; + vec3_t mins, maxs; + + // If we have a mins, use that instead of the hardcoded bounding box + if ( spot->mins && VectorLength( spot->mins ) ) + VectorAdd( spot->s.origin, spot->mins, mins ); + else + VectorAdd( spot->s.origin, playerMins, mins ); + + // If we have a maxs, use that instead of the hardcoded bounding box + if ( spot->maxs && VectorLength( spot->maxs ) ) + VectorAdd( spot->s.origin, spot->maxs, maxs ); + else + VectorAdd( spot->s.origin, playerMaxs, maxs ); + + num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) + { + if ( hit->contents & CONTENTS_BODY ) + { + if( checkteam == TEAM_FREE || hit->client->playerTeam == checkteam ) + {//checking against teammates only...? + return qtrue; + } + } + } + } + + return qfalse; +} + +qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ) +{ + int i, num; + gentity_t *touch[MAX_GENTITIES], *hit; + vec3_t mins, maxs; + + VectorAdd( dest, mover->mins, mins ); + VectorAdd( dest, mover->maxs, maxs ); + num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; icontents & mover->contents ) + { + return qtrue; + } + } + + return qfalse; +} +/* +================ +SelectNearestDeathmatchSpawnPoint + +Find the spot that we DON'T want to use +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from, team_t team ) { + gentity_t *spot; + float dist, nearestDist; + gentity_t *nearestSpot; + + nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE; + nearestSpot = NULL; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + /*if ( team == TEAM_RED && ( spot->spawnflags & 2 ) ) { + continue; + } + if ( team == TEAM_BLUE && ( spot->spawnflags & 1 ) ) { + continue; + }*/ + + if ( spot->targetname != NULL ) { + //this search routine should never find a spot that is targetted + continue; + } + dist = DistanceSquared( spot->s.origin, from ); + if ( dist < nearestDist ) { + nearestDist = dist; + nearestSpot = spot; + } + } + + return nearestSpot; +} + + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectRandomDeathmatchSpawnPoint( team_t team ) { + gentity_t *spot; + int count; + int selection; + gentity_t *spots[MAX_SPAWN_POINTS]; + + count = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + /*if ( team == TEAM_RED && ( spot->spawnflags & 2 ) ) { + continue; + } + if ( team == TEAM_BLUE && ( spot->spawnflags & 1 ) ) { + continue; + }*/ + + if ( spot->targetname != NULL ) { + //this search routine should never find a spot that is targetted + continue; + } + if ( SpotWouldTelefrag( spot, TEAM_FREE ) ) { + continue; + } + spots[ count ] = spot; + count++; + } + + if ( !count ) { // no spots that won't telefrag + spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + if ( !spot ) + { + return NULL; + } + if ( spot->targetname != NULL ) + { + //this search routine should never find a spot that is targetted + return NULL; + } + else + { + return spot; + } + } + + selection = rand() % count; + return spots[ selection ]; +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, team_t team, vec3_t origin, vec3_t angles ) { + gentity_t *spot; + gentity_t *nearestSpot; + + if ( level.spawntarget != NULL && level.spawntarget[0] ) + {//we have a spawnpoint specified, try to find it + if ( (nearestSpot = spot = G_Find( NULL, FOFS(targetname), level.spawntarget )) == NULL ) + {//you HAVE to be able to find the desired spot + G_Error( "Couldn't find spawntarget %s\n", level.spawntarget ); + return NULL; + } + } + else + {//not looking for a special startspot + nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint, team ); + + spot = SelectRandomDeathmatchSpawnPoint ( team ); + if ( spot == nearestSpot ) { + // roll again if it would be real close to point of death + spot = SelectRandomDeathmatchSpawnPoint ( team ); + } + } + + // find a single player start spot + if (!spot) { + G_Error( "Couldn't find a spawn point\n" ); + } + + + VectorCopy( spot->s.origin, origin ); + if ( spot->spawnflags & 2 ) + { + trace_t tr; + + origin[2] = MIN_WORLD_COORD; + gi.trace(&tr, spot->s.origin, playerMins, playerMaxs, origin, ENTITYNUM_NONE, MASK_PLAYERSOLID ); + if ( tr.fraction < 1.0 && !tr.allsolid && !tr.startsolid ) + {//found a floor + VectorCopy(tr.endpos, origin ); + } + else + {//In solid or too far + VectorCopy( spot->s.origin, origin ); + } + } + + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; +} + + +//====================================================================== + + +/* +================== +SetClientViewAngle + +================== +*/ +void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { + int i; + + // set the delta angle + for (i=0 ; i<3 ; i++) + { + ent->client->ps.delta_angles[i] = (ANGLE2SHORT(angle[i]) - ent->client->pers.cmd_angles[i])&0xffff; + } + VectorCopy( angle, ent->s.angles ); + VectorCopy (ent->s.angles, ent->client->ps.viewangles); +} + +/* +================ +respawn +================ +*/ +void respawn( gentity_t *ent ) { + + if (Q_stricmpn(level.mapname,"_holo",5)) { + gi.SendConsoleCommand("load *respawn\n"); // special case + } + else {//we're on the holodeck + int flags; + + // toggle the teleport bit so the client knows to not lerp + flags = ent->client->ps.eFlags; + ClientSpawn(ent, eNO/*qfalse*/); // SavedGameJustLoaded_e + ent->client->ps.eFlags = flags ^ EF_TELEPORT_BIT; + } +} + + +/* +================ +PickTeam + +================ +*/ +team_t PickTeam( int ignoreClientNum ) { + int i; + int counts[TEAM_NUM_TEAMS]; + + memset( counts, 0, sizeof( counts ) ); + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( i == ignoreClientNum ) { + continue; + } + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + } + + return TEAM_FREE; +} + +/* +=========== +ForceClientSkin + +Forces a client's skin (for teamplay) +=========== +*/ +void ForceClientSkin( gclient_t *client, char *model, const char *skin ) { + char *p; + + if ((p = strchr(model, '/')) != NULL) { + *p = 0; + } + + Q_strcat(model, MAX_QPATH, "/"); + Q_strcat(model, MAX_QPATH, skin); +} + +/* +=========== +ClientUserInfoChanged + +Called from ClientConnect when the player first connects and +directly by the server system when the player updates a userinfo variable. + +The game can override any of the settings and call gi.SetUserinfo +if desired. +============ +*/ +void ClientUserinfoChanged( int clientNum ) { + gentity_t *ent; + char *s; + char headModel[MAX_QPATH]; + char torsoModel[MAX_QPATH]; + char legsModel[MAX_QPATH]; + char sound[MAX_QPATH]; + char oldname[MAX_STRING_CHARS]; + gclient_t *client; + char *sex; + char userinfo[MAX_INFO_STRING]; + + ent = g_entities + clientNum; + client = ent->client; + + gi.GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if ( !Info_Validate(userinfo) ) { + strcpy (userinfo, "\\name\\badinfo"); + } + + // set name + Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey (userinfo, "name"); + Q_strncpyz( client->pers.netname, s, sizeof(client->pers.netname) ); + +/* if ( client->pers.connected == CON_CONNECTED ) { + if ( strcmp( oldname, client->pers.netname ) ) { + gi.SendServerCommand( -1, "print \"%s renamed to %s\n\"", oldname, client->pers.netname ); + } + } +*/ + // set max health + client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + + // sounds + Q_strncpyz( sound, Info_ValueForKey (userinfo, "snd"), sizeof( sound ) ); + + + // set model + //Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headModel"), sizeof( headModel ) ); + //Q_strncpyz( torsoModel, Info_ValueForKey (userinfo, "torsoModel"), sizeof( torsoModel ) ); + //Q_strncpyz( legsModel, Info_ValueForKey (userinfo, "legsModel"), sizeof( legsModel ) ); + headModel[0]=0; + torsoModel[0]=0; + legsModel[0]=0; + + // sex + sex = Info_ValueForKey( userinfo, "sex" ); + if ( !sex[0] ) { + sex = "m"; + } + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + + s = va("n\\%s\\t\\%i\\headModel\\%s\\torsoModel\\%s\\legsModel\\%s\\hc\\%i\\snd\\%s", + client->pers.netname, client->sess.sessionTeam, headModel, torsoModel, legsModel, + client->pers.maxHealth, sound ); + + gi.SetConfigstring( CS_PLAYERS+clientNum, s ); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and tournement +restarts. +============ +*/ +char *ClientConnect( int clientNum, qboolean firstTime, SavedGameJustLoaded_e eSavedGameJustLoaded ) +{ + gclient_t *client; + char userinfo[MAX_INFO_STRING]; + gentity_t *ent; + clientSession_t savedSess; + + ent = &g_entities[ clientNum ]; + gi.GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + +// if (!qbFromSavedGame) + if (eSavedGameJustLoaded != eFULL) + { + savedSess = client->sess; // + memset( client, 0, sizeof(*client) ); + client->sess = savedSess; + if ( firstTime ) { //not loading full, and directconnect + client->playerTeam = TEAM_PLAYER; //set these now because after an auto_load kyle can see your team for a bit before you really join. + client->enemyTeam = TEAM_ENEMY; + } + } + + client->pers.connected = CON_CONNECTING; + + if (eSavedGameJustLoaded == eFULL)//qbFromSavedGame) + { + // G_WriteClientSessionData( client ); // forget it, this is DM stuff anyway + // get and distribute relevent paramters + ClientUserinfoChanged( clientNum ); + } + else + { + // read or initialize the session data + if ( firstTime ) { + G_InitSessionData( client, userinfo ); + } + G_ReadSessionData( client ); + + // get and distribute relevent paramters + ClientUserinfoChanged( clientNum ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if ( firstTime ) { + gi.SendServerCommand( -1, "print \"%s connected\n\"", client->pers.netname); + } + } + + return NULL; +} + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the level. This will happen every level load, +and on transition between teams, but doesn't happen on respawns +============ +*/ +void ClientBegin( int clientNum, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded) +// qboolean qbFromSavedGame +{ + gentity_t *ent; + gclient_t *client; + + ent = g_entities + clientNum; + client = level.clients + clientNum; + + if (eSavedGameJustLoaded == eFULL)//qbFromSavedGame) + { + client->pers.connected = CON_CONNECTED; + ent->client = client; + ClientSpawn( ent, eSavedGameJustLoaded ); + } + else + { + if ( ent->linked ) { + gi.unlinkentity( ent ); + } + G_InitGentity( ent, qfalse ); + ent->e_TouchFunc = touchF_NULL; + ent->e_PainFunc = painF_PlayerPain;//painF_NULL; + ent->client = client; + + client->pers.connected = CON_CONNECTED; + client->pers.teamState.state = TEAM_BEGIN; + _VectorCopy( cmd->angles, client->pers.cmd_angles ); + + memset( &client->ps, 0, sizeof( client->ps ) ); + if( gi.Cvar_VariableIntegerValue( "g_clearstats" ) ) + { + memset( &client->sess.missionStats, 0, sizeof( client->sess.missionStats ) ); + client->sess.missionStats.totalSecrets = gi.Cvar_VariableIntegerValue("newTotalSecrets"); + } + + // locate ent at a spawn point + if ( ClientSpawn( ent, eSavedGameJustLoaded) ) // SavedGameJustLoaded_e + { + // send teleport event + } + client->ps.inventory[INV_GOODIE_KEY] = 0; + client->ps.inventory[INV_SECURITY_KEY] = 0; + + } + extern bool autosaveTrigger; + extern bool doAutoSave; + autosaveTrigger = true; + if(eSavedGameJustLoaded == eNO) + doAutoSave = true; + else + doAutoSave = false; +} + + + +/* +============ +Player_CacheFromPrevLevel + Description : just need to grab the weapon items we're going to have when we spawn so they'll be cached + Return type : void + Argument : void +============ +*/ +void Player_CacheFromPrevLevel(void) +{ + char s[MAX_STRING_CHARS]; + + gi.Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) ); + + if (s[0]) // actually this would be safe anyway because of the way sscanf() works, but this is clearer + { + int iDummy, bits, ibits; + + sscanf( s, "%i %i %i %i", + &iDummy,//client->ps.stats[STAT_HEALTH], + &iDummy,//client->ps.stats[STAT_ARMOR], + &bits, //client->ps.stats[STAT_WEAPONS] + &ibits //client->ps.stats[STAT_ITEMS] + ); + + for ( int i = 1 ; i < 16 ; i++ ) + { + if ( bits & ( 1 << i ) ) + { + RegisterItem( FindItemForWeapon( (weapon_t)i ) ); + } + } + +extern gitem_t *FindItemForInventory( int inv ); + + for ( i = 1 ; i < 16 ; i++ ) + { + if ( ibits & ( 1 << i ) ) + { + RegisterItem( FindItemForInventory( i-1 )); + } + } + } +} + +/* +============ +Player_RestoreFromPrevLevel + Description : retrieve maptransition data recorded by server when exiting previous level (to carry over weapons/ammo/health/etc) + Return type : void + Argument : gentity_t *ent +============ +*/ +void Player_RestoreFromPrevLevel(gentity_t *ent) +{ + gclient_t *client = ent->client; + int i; + + assert(client); + if (client) // though I can't see it not being true... + { + char s[MAX_STRING_CHARS]; + char saber0Name[MAX_QPATH]; + char saber1Name[MAX_QPATH]; + const char *var; + + gi.Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) ); + + if (strlen(s)) // actually this would be safe anyway because of the way sscanf() works, but this is clearer + {// |general info |-force powers |-saber 1 |-saber 2 |-general saber + sscanf( s, "%i %i %i %i %i %i %i %f %f %f %i %i %i %i %i %s %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %s %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i", + &client->ps.stats[STAT_HEALTH], + &client->ps.stats[STAT_ARMOR], + &client->ps.stats[STAT_WEAPONS], + &client->ps.stats[STAT_ITEMS], + &client->ps.weapon, + &client->ps.weaponstate, + &client->ps.batteryCharge, + &client->ps.viewangles[0], + &client->ps.viewangles[1], + &client->ps.viewangles[2], + //force power data + &client->ps.forcePowersKnown, + &client->ps.forcePower, + &client->ps.forcePowerMax, + &client->ps.forcePowerRegenRate, + &client->ps.forcePowerRegenAmount, + //saber 1 data + &saber0Name, + &client->ps.saber[0].blade[0].active, + &client->ps.saber[0].blade[1].active, + &client->ps.saber[0].blade[2].active, + &client->ps.saber[0].blade[3].active, + &client->ps.saber[0].blade[4].active, + &client->ps.saber[0].blade[5].active, + &client->ps.saber[0].blade[6].active, + &client->ps.saber[0].blade[7].active, + &client->ps.saber[0].blade[0].color, + &client->ps.saber[0].blade[1].color, + &client->ps.saber[0].blade[2].color, + &client->ps.saber[0].blade[3].color, + &client->ps.saber[0].blade[4].color, + &client->ps.saber[0].blade[5].color, + &client->ps.saber[0].blade[6].color, + &client->ps.saber[0].blade[7].color, + //saber 2 data + &saber1Name, + &client->ps.saber[1].blade[0].active, + &client->ps.saber[1].blade[1].active, + &client->ps.saber[1].blade[2].active, + &client->ps.saber[1].blade[3].active, + &client->ps.saber[1].blade[4].active, + &client->ps.saber[1].blade[5].active, + &client->ps.saber[1].blade[6].active, + &client->ps.saber[1].blade[7].active, + &client->ps.saber[1].blade[0].color, + &client->ps.saber[1].blade[1].color, + &client->ps.saber[1].blade[2].color, + &client->ps.saber[1].blade[3].color, + &client->ps.saber[1].blade[4].color, + &client->ps.saber[1].blade[5].color, + &client->ps.saber[1].blade[6].color, + &client->ps.saber[1].blade[7].color, + //general saber data + &client->ps.saberStylesKnown, + &client->ps.saberAnimLevel, + &client->ps.saberLockEnemy, + &client->ps.saberLockTime + ); + ent->health = client->ps.stats[STAT_HEALTH]; + + if(ent->client->ps.saber[0].name && gi.bIsFromZone(ent->client->ps.saber[0].name, TAG_G_ALLOC)) { + gi.Free(ent->client->ps.saber[0].name); + } + ent->client->ps.saber[0].name=0; + + if(ent->client->ps.saber[1].name && gi.bIsFromZone(ent->client->ps.saber[1].name, TAG_G_ALLOC) ) { + gi.Free(ent->client->ps.saber[1].name); + } + ent->client->ps.saber[1].name=0; + //NOTE: if sscanf can get a "(null)" out of strings that had NULL string pointers plugged into the original string + if ( saber0Name[0] && ( Q_stricmp( saber0Name, "(null)" ) != 0 && Q_stricmp( saber0Name, "none" ) != 0) ) + { + ent->client->ps.saber[0].name = G_NewString( saber0Name ); + } + if ( saber1Name[0] && ( Q_stricmp( saber1Name, "(null)" ) != 0 && Q_stricmp( saber1Name, "none" ) != 0) ) + {//have a second saber + ent->client->ps.saber[1].name = G_NewString( saber1Name ); + ent->client->ps.dualSabers = qtrue; + } + else + {//have only 1 saber + ent->client->ps.dualSabers = qfalse; + } + +// slight issue with ths for the moment in that although it'll correctly restore angles it doesn't take into account +// the overall map orientation, so (eg) exiting east to enter south will be out by 90 degrees, best keep spawn angles for now +// +// VectorClear (ent->client->pers.cmd_angles); +// +// SetClientViewAngle( ent, ent->client->ps.viewangles); + + //ammo + gi.Cvar_VariableStringBuffer( "playerammo", s, sizeof(s) ); + i=0; + var = strtok( s, " " ); + while( var != NULL ) + { + /* While there are tokens in "s" */ + client->ps.ammo[i++] = atoi(var); + /* Get next token: */ + var = strtok( NULL, " " ); + } + assert (i==AMMO_MAX); + + //inventory + gi.Cvar_VariableStringBuffer( "playerinv", s, sizeof(s) ); + i=0; + var = strtok( s, " " ); + while( var != NULL ) + { + /* While there are tokens in "s" */ + client->ps.inventory[i++] = atoi(var); + /* Get next token: */ + var = strtok( NULL, " " ); + } + assert (i==INV_MAX); + + + // the new JK2 stuff - force powers, etc... + // + gi.Cvar_VariableStringBuffer( "playerfplvl", s, sizeof(s) ); + i=0; + var = strtok( s, " " ); + while( var != NULL ) + { + /* While there are tokens in "s" */ + client->ps.forcePowerLevel[i++] = atoi(var); + /* Get next token: */ + var = strtok( NULL, " " ); + } + assert (i==NUM_FORCE_POWERS); + + client->ps.forceGripEntityNum = client->ps.forceDrainEntityNum = ENTITYNUM_NONE; + } + } +} + +/* +Ghoul2 Insert Start +*/ +static void G_SetSkin( gentity_t *ent ) +{ + char skinName[MAX_QPATH]; + //ok, lets register the skin name, and then pass that name to the config strings so the client can get it too. + if (Q_stricmp( "hoth2", level.mapname ) == 0 //hack, is this the only map? + || + Q_stricmp( "hoth3", level.mapname ) == 0 // no! ;-) + ) + { + Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/|%s|%s|%s", g_char_model->string, g_char_skin_head->string, "torso_g1", "lower_e1" ); + } + else + { + Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/|%s|%s|%s", g_char_model->string, g_char_skin_head->string, g_char_skin_torso->string, g_char_skin_legs->string ); + } + + // lets see if it's out there + int skin = gi.RE_RegisterSkin( skinName ); + if ( skin ) + {//what if this returns 0 because *one* part of a multi-skin didn't load? + // put it in the config strings + // and set the ghoul2 model to use it + gi.G2API_SetSkin( &ent->ghoul2[ent->playerModel], G_SkinIndex( skinName ), skin ); + } + + //color tinting + if ( g_char_color_red->integer + || g_char_color_green->integer + || g_char_color_blue->integer ) + { + ent->client->renderInfo.customRGBA[0] = g_char_color_red->integer; + ent->client->renderInfo.customRGBA[1] = g_char_color_green->integer; + ent->client->renderInfo.customRGBA[2] = g_char_color_blue->integer; + ent->client->renderInfo.customRGBA[3] = 255; + } +} + +qboolean G_StandardHumanoid( gentity_t *self ) +{ + if ( !self || !self->ghoul2.size() ) + { + return qfalse; + } + if ( self->playerModel < 0 || self->playerModel >= self->ghoul2.size() ) + { + return qfalse; + } + const char *GLAName = gi.G2API_GetGLAName( &self->ghoul2[self->playerModel] ); + assert(GLAName); + if (GLAName) + { + if ( !Q_stricmpn( "models/players/_humanoid", GLAName, 24 ) )///_humanoid", GLAName, 36) ) + {//only _humanoid skeleton is expected to have these + return qtrue; + } + if ( !Q_stricmp( "models/players/protocol/protocol", GLAName ) ) + {//protocol droid duplicates many of these + return qtrue; + } + if ( !Q_stricmp( "models/players/assassin_droid/model", GLAName ) ) + {//assassin_droid duplicates many of these + return qtrue; + } + if ( !Q_stricmp( "models/players/saber_droid/model", GLAName ) ) + {//saber_droid duplicates many of these + return qtrue; + } + if ( !Q_stricmp( "models/players/hazardtrooper/hazardtrooper", GLAName ) ) + {//hazardtrooper duplicates many of these + return qtrue; + } + if ( !Q_stricmp( "models/players/rockettrooper/rockettrooper", GLAName ) ) + {//rockettrooper duplicates many of these + return qtrue; + } + if ( !Q_stricmp( "models/players/wampa/wampa", GLAName ) ) + {//rockettrooper duplicates many of these + return qtrue; + } + } + return qfalse; +} + +qboolean G_ClassHasBadBones( int NPC_class ) +{ + switch ( NPC_class ) + { + case CLASS_WAMPA: + case CLASS_ROCKETTROOPER: + case CLASS_SABER_DROID: + case CLASS_HAZARD_TROOPER: + case CLASS_ASSASSIN_DROID: + case CLASS_RANCOR: + return qtrue; + } + return qfalse; +} + +char *AxesNames[] = +{ + "ORIGIN",//ORIGIN, + "POSITIVE_X",//POSITIVE_X, + "POSITIVE_Z",//POSITIVE_Z, + "POSITIVE_Y",//POSITIVE_Y, + "NEGATIVE_X",//NEGATIVE_X, + "NEGATIVE_Z",//NEGATIVE_Z, + "NEGATIVE_Y"//NEGATIVE_Y +}; + +Eorientations testAxes[3]={POSITIVE_X,POSITIVE_Z,POSITIVE_Y}; +int axes_0 = POSITIVE_X; +int axes_1 = POSITIVE_Z; +int axes_2 = POSITIVE_Y; +void G_NextTestAxes( void ) +{ + static int whichAxes = 0; + int axesCount = 0; + do + { + whichAxes++; + if ( whichAxes > 216 ) + { + whichAxes = 0; + Com_Printf( S_COLOR_RED"WRAPPED\n" ); + break; + } + axesCount = 0; + axes_0 = 0; + axes_1 = 0; + axes_2 = 0; + for ( axes_0 = 0; axes_0 < 6 && (axesCountplayerModel != -1 ) + {// we found the model ok + vec3_t angles = {0,0,0}; + const char *token; + const char *p; + + //Now turn on/off any surfaces + if ( surfOff && surfOff[0] ) + { + p = surfOff; + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + {//reached end of list + break; + } + //turn off this surf + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], token, 0x00000002/*G2SURFACEFLAG_OFF*/ ); + } + } + if ( surfOn && surfOn[0] ) + { + p = surfOn; + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + {//reached end of list + break; + } + //turn on this surf + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], token, 0 ); + } + } + if ( ent->client->NPC_class == CLASS_IMPERIAL && ent->message ) + {//carrying a key, turn on the key sleeve surface (assuming we have one) + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "l_arm_key", 0 ); + } + + G_LoadAnimFileSet( ent, modelName ); + + ent->headBolt = ent->cervicalBolt = ent->torsoBolt = ent->gutBolt = ent->chestBolt = + ent->crotchBolt = ent->elbowLBolt = ent->elbowRBolt = ent->handLBolt = + ent->handRBolt = ent->kneeLBolt = ent->kneeRBolt = ent->footLBolt = + ent->footRBolt = -1; + // now turn on the bolt in the hand - this one would be best always turned on. + if ( G_StandardHumanoid( ent ) ) + {//only _humanoid skeleton is expected to have these + ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_eyes"); + ent->cervicalBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cervical" ); + if ( !Q_stricmp("protocol", modelName ) ) + {//*sigh*, no thoracic bone + ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_lumbar"); + ent->chestBolt = ent->gutBolt; + } + else + { + ent->chestBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "thoracic"); + ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_lumbar"); + } + ent->torsoBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "lower_lumbar"); + ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "pelvis"); + ent->elbowLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_arm_elbow"); + ent->elbowRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_arm_elbow"); + ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_hand"); + ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_hand"); + ent->kneeLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hips_l_knee"); + ent->kneeRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hips_r_knee"); + ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_leg_foot"); + ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_leg_foot"); + if ( ent->client->NPC_class == CLASS_BOBAFETT + || ent->client->NPC_class == CLASS_ROCKETTROOPER ) + {//get jet bolts + ent->genericBolt1 = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*jet1" ); + ent->genericBolt2 = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*jet2" ); + } + if ( ent->client->NPC_class == CLASS_BOBAFETT ) + {//get the flamethrower bolt + ent->genericBolt3 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flamethrower"); + } + } + else + { + if ( ent->client->NPC_class == CLASS_VEHICLE ) + {//do vehicles tags + + // Setup the driver tag (where the driver is mounted to). + ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*driver"); + + // Setup the droid unit (or other misc tag we're using this for). + ent->m_pVehicle->m_iDroidUnitTag = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*droidunit"); + + char strTemp[128]; + + // Setup the Exhausts. + for ( int i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ ) + { + _snprintf( strTemp, 128, "*exhaust%d", i + 1 ); + ent->m_pVehicle->m_iExhaustTag[i] = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], strTemp ); + } + + // Setup the Muzzles. + for ( int i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) + { + _snprintf( strTemp, 128, "*muzzle%d", i + 1 ); + ent->m_pVehicle->m_iMuzzleTag[i] = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], strTemp ); + } + } + else if ( ent->client->NPC_class == CLASS_HOWLER ) + { + ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "Tongue01" );// tongue base + ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "Tongue08" );// tongue tip + } + else if ( !Q_stricmp( "gonk", modelName ) || !Q_stricmp( "seeker", modelName ) || !Q_stricmp( "remote", modelName ) + || !Q_stricmpn( "r2d2", modelName, 4 ) || !Q_stricmpn( "r5d2", modelName, 4 ) ) + {//TEMP HACK: not a non-humanoid droid + ent->headBolt = -1; + } + else if (!Q_stricmp( "interrogator",modelName)) + { + ent->headBolt = -1; + } + else if (!Q_stricmpn( "probe",modelName, 5)) + { + ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cranium"); // head pivot point + ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash"); // Gun 1 + } + else if (!Q_stricmp( "sentry",modelName)) + { + ent->headBolt = -1; + ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash1"); // Gun 1 + ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash2"); // Gun 2 + ent->genericBolt3 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash03"); // Gun 3 + } + else if (!Q_stricmp( "mark1",modelName)) + { + ent->headBolt = -1; + ent->handRBolt = ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash1"); // Blaster Gun 1 + ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash2"); // Blaster Gun 2 + ent->genericBolt3 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash3"); // Blaster Gun 3 + ent->genericBolt4 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash4"); // Blaster Gun 4 + ent->genericBolt5 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash5"); // Missile Gun 1 + } + else if (!Q_stricmp( "mark2",modelName)) + { + ent->headBolt = -1; + ent->handRBolt = ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash"); // Blaster Gun 1 + } + else if (!Q_stricmp( "atst",modelName) )//&& (ent->client->playerTeam != TEAM_PLAYER)) + { + ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head"); + + ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash1"); // Front guns + ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash2"); + + ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash3"); // Left side gun + ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash4"); // Right side missle launcher + + ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_foot"); + ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_foot"); + } + else if ( !Q_stricmp( "minemonster", modelName )) + { + ent->handRBolt = ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_f1"); + } + else if ( !Q_stricmp( "rancor", modelName ) + || !Q_stricmp( "mutant_rancor", modelName )) + { + ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_hand"); + ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_hand"); + ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_eyes"); + ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_mouth"); + } + else if ( !Q_stricmp( "sand_creature", modelName )) + { + ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*mouth"); + ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*ground"); + } + else if ( !Q_stricmp( "wampa", modelName )) + { + ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_eyes"); + ent->cervicalBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "neck_bone" ); + ent->chestBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_spine"); + ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "mid_spine"); + ent->torsoBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "lower_spine"); + ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "rear_bone"); + ent->elbowLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_arm_elbow"); + ent->elbowRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_arm_elbow"); + ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_hand"); + ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_hand"); + ent->kneeLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hips_l_knee"); + ent->kneeRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hips_r_knee"); + ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_leg_foot"); + ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_leg_foot"); + } + else + {//TEMP HACK: not a non-humanoid droid + ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*weapon");//should be r_hand + if ( Q_stricmp( "atst", modelName ) ) + {//not an ATST + ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*headg"); + ent->cervicalBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cervical" ); + ent->torsoBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "lower_lumbar"); + ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_lumbar"); + ent->chestBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "thoracic"); + ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "pelvis"); + ent->elbowLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*bicep_lg"); + ent->elbowRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*bicep_rg"); + ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hand_l"); + ent->kneeLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*thigh_lg"); + ent->kneeRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*thigh_rg"); + ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*foot_lg"); + ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*foot_rg"); + } + } + } + + ent->faceBone = BONE_INDEX_INVALID; + ent->craniumBone = BONE_INDEX_INVALID; + ent->cervicalBone = BONE_INDEX_INVALID; + ent->thoracicBone = BONE_INDEX_INVALID; + ent->upperLumbarBone = BONE_INDEX_INVALID; + ent->lowerLumbarBone = BONE_INDEX_INVALID; + ent->motionBone = BONE_INDEX_INVALID; + ent->hipsBone = BONE_INDEX_INVALID; + ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue ); +#ifndef FINAL_BUILD + if ( g_developer->integer && ent->rootBone == -1 ) + { + Com_Error(ERR_DROP,"ERROR: model %s has no model_root bone (and hence cannot animate)!!!\n", modelName ); + } +#endif + ent->footLBone = BONE_INDEX_INVALID; + ent->footRBone = BONE_INDEX_INVALID; + ent->humerusRBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "rhumerus", qtrue ); + + // now add overrides on specific joints so the client can set angle overrides on the legs, torso and head + if ( ent->client->NPC_class == CLASS_VEHICLE ) + {//do vehicles tags + //vehicleInfo_t *vehicle = ent->m_pVehicle->m_pVehicleInfo; + } + else if ( ent->client->NPC_class == CLASS_HOWLER ) + { + } + else if ( !Q_stricmp( "gonk", modelName ) || !Q_stricmp( "seeker", modelName ) || !Q_stricmp( "remote", modelName ) ) + {// + } + else if (!Q_stricmp( "sentry",modelName)) + { + } + else if (!Q_stricmpn( "probe", modelName, 5 )) + { + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->craniumBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "pelvis", qtrue ); + if (ent->thoracicBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + } + else if (!Q_stricmp( "interrogator", modelName )) + { + ent->genericBone1 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "left_arm", qtrue ); + if (ent->genericBone1>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone1, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL ); + } + ent->genericBone2 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "right_arm", qtrue ); + if (ent->genericBone2>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone2, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL ); + } + ent->genericBone3 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "claw", qtrue ); + if (ent->genericBone3>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone3, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL ); + } + } + else if (!Q_stricmpn( "r2d2", modelName, 4 )) + { + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->craniumBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "body", qtrue ); + if (ent->thoracicBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->genericBone1 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "f_eye", qtrue ); + if (ent->genericBone1>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone1, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL ); + } + } + else if (!Q_stricmpn( "r5d2", modelName, 4 )) + { + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->craniumBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "body", qtrue ); + if (ent->thoracicBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + } + else if ( !Q_stricmp( "atst", modelName )) + { + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->craniumBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue ); + if (ent->thoracicBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->footLBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "l_tarsal", qtrue ); + if (ent->footLBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->footLBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL ); + } + ent->footRBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "r_tarsal", qtrue ); + if (ent->footRBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->footRBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL ); + } + } + else if ( !Q_stricmp( "mark1", modelName )) + { + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->craniumBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->upperLumbarBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + } + else if ( !Q_stricmp( "mark2", modelName )) + { + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->craniumBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue ); + if (ent->thoracicBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + } + else if ( !Q_stricmp( "minemonster", modelName )) + { + ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic1", qtrue ); + if (ent->thoracicBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->craniumBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + } + else if ( ent->client->NPC_class == CLASS_RANCOR ) + /*!Q_stricmp( "rancor", modelName ) || !Q_stricmp( "mutant_rancor", modelName ) )*/ + { + Eorientations oUp, oRt, oFwd; + //regular bones we need + ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "lower_spine", qtrue ); + if (ent->lowerLumbarBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "lower_lumbar", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "mid_spine", qtrue ); + if (ent->upperLumbarBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "upper_lumbar", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "upper_spine", qtrue ); + if (ent->thoracicBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "thoracic", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + } + else if ( !Q_stricmp( "sand_creature", modelName )) + { + } + else if ( !Q_stricmp( "wampa", modelName ) ) + { + //Eorientations oUp, oRt, oFwd; + //bone needed for turning anims + /* + //SIGH... fucks him up BAD + ent->hipsBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "pelvis", qtrue ); + if (ent->hipsBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "pelvis", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->hipsBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + */ + /* + //SIGH... no anim split + ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "lower_lumbar", qtrue ); + if (ent->lowerLumbarBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "lower_lumbar", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + */ + /* + //SIGH... spine wiggles fuck all this shit + ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "upper_lumbar", qtrue ); + if (ent->upperLumbarBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "upper_lumbar", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue ); + if (ent->thoracicBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "thoracic", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + ent->cervicalBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cervical", qtrue ); + if (ent->cervicalBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "cervical", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->cervicalBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->craniumBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "cranium", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + */ + } + else if ( !Q_stricmp( "rockettrooper", modelName ) + || !Q_stricmp( "hazardtrooper", modelName ) + || !Q_stricmp( "saber_droid", modelName ) + || !Q_stricmp( "assassin_droid", modelName ) ) + { + Eorientations oUp, oRt, oFwd; + if ( Q_stricmp( "saber_droid", modelName ) ) + {//saber droid doesn't use these lower bones + //regular bones we need + ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "upper_lumbar", qtrue ); + if (ent->upperLumbarBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "upper_lumbar", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "lower_lumbar", qtrue ); + if (ent->lowerLumbarBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "lower_lumbar", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + } + if ( Q_stricmp( "hazardtrooper", modelName ) ) + {//hazard trooper doesn't have these upper bones + if ( Q_stricmp( "saber_droid", modelName ) ) + {//saber droid doesn't use thoracic bone + ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue ); + if (ent->thoracicBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "thoracic", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + } + ent->cervicalBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cervical", qtrue ); + if (ent->cervicalBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "cervical", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->cervicalBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->craniumBone>=0) + { + G_BoneOrientationsForClass( ent->client->NPC_class, "cranium", &oUp, &oRt, &oFwd ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, oUp, oRt, oFwd, NULL ); + } + } + } + else + { + //special case motion bone - to match up split anims + ent->motionBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "Motion", qtrue ); + if (ent->motionBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->motionBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, NULL ); + } + ent->motionBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "Motion"); + //bone needed for turning anims + ent->hipsBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "pelvis", qtrue ); + if (ent->hipsBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->hipsBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + //regular bones we need + ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "upper_lumbar", qtrue ); + if (ent->upperLumbarBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "lower_lumbar", qtrue ); + if (ent->lowerLumbarBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + + ent->faceBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "face", qtrue ); + if (ent->faceBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->faceBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); + if (ent->craniumBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->cervicalBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cervical", qtrue ); + if (ent->cervicalBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->cervicalBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue ); + if (ent->thoracicBone>=0) + { + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + } + ent->client->clientInfo.infoValid = qtrue; + + } + + if ( ent->client->NPC_class == CLASS_SAND_CREATURE ) + { + ent->s.radius = 256; + } + else if ( ent->client->NPC_class == CLASS_RANCOR ) + { + if ( (ent->spawnflags&1) ) + {//mutant + ent->s.radius = 300; + } + else + { + ent->s.radius = 150; + } + } + else if ( ent->s.radius <= 0 )//radius cannot be negative or zero + {//set the radius to be the largest axial distance on the entity + float max; + max = ent->mins[0];//NOTE: mins is always negative + if ( max > ent->mins[1] ) + { + max = ent->mins[1]; + } + + if ( max > ent->mins[2] ) + { + max = ent->mins[2]; + } + + max = fabs(max);//convert to positive to compare with maxs + if ( max < ent->maxs[0] ) + { + max = ent->maxs[0]; + } + + if ( max < ent->maxs[1] ) + { + max = ent->maxs[1]; + } + + if ( max < ent->maxs[2] ) + { + max = ent->maxs[2]; + } + + ent->s.radius = (int)max; + + if (!ent->s.radius) // Still no radius? + { + ent->s.radius = 60; + } + } + + // set the weaponmodel to -1 so we don't try to remove it in Pmove before we have it built + ent->weaponModel[0] = -1; + + if ( ent->playerModel == -1 ) + { + return qfalse; + } + return qtrue; +} + +void G_SetG2PlayerModel( gentity_t * const ent, const char *modelName, const char *customSkin, const char *surfOff, const char *surfOn ) +{ + char skinName[MAX_QPATH]; + + //ok, lets register the skin name, and then pass that name to the config strings so the client can get it too. + if ( !customSkin ) + {//use the default + Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_default.skin", modelName ); + } + else + { + if (strchr(customSkin, '|')) + {//three part skin + Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/|%s", modelName, customSkin ); + } + else + { + Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_%s.skin", modelName, customSkin ); + } + } + int skin = gi.RE_RegisterSkin( skinName ); + assert(skin); + //now generate the ghoul2 model this client should be. + if ( ent->client->NPC_class == CLASS_VEHICLE ) + {//vehicles actually grab their model from the appropriate vehicle data entry + + // This will register the model and other assets. + Vehicle_t *pVeh = ent->m_pVehicle; + pVeh->m_pVehicleInfo->RegisterAssets( pVeh ); + ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, va("models/players/%s/model.glm", modelName), pVeh->m_pVehicleInfo->modelIndex, G_SkinIndex( skinName ) ); + } + else + { + //NOTE: it still loads the default skin's tga's because they're referenced in the .glm. + ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, va("models/players/%s/model.glm", modelName), G_ModelIndex( va("models/players/%s/model.glm", modelName) ), G_SkinIndex( skinName ) ); + } + if (ent->playerModel == -1) + {//try the stormtrooper as a default + gi.Printf( S_COLOR_RED"G_SetG2PlayerModel: cannot load model %s\n", modelName ); + modelName = "stormtrooper"; + Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_default.skin", modelName ); + skin = gi.RE_RegisterSkin( skinName ); + assert(skin); + ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, va("models/players/%s/model.glm", modelName), G_ModelIndex( va("models/players/%s/model.glm", modelName) ) ); + } + if (ent->playerModel == -1) + {//very bad thing here! + Com_Error(ERR_DROP, "Cannot fall back to default model %s!", modelName); + } + + gi.G2API_SetSkin( &ent->ghoul2[ent->playerModel], G_SkinIndex( skinName ), skin );//this is going to set the surfs on/off matching the skin file + + // did we find a ghoul2 model? if so, load the animation.cfg file + if ( !G_SetG2PlayerModelInfo( ent, modelName, customSkin, surfOff, surfOn ) ) + {//couldn't set g2 info, fall back to a mouse md3 + NPC_ParseParms( "mouse", ent ); + Com_Printf( S_COLOR_RED"couldn't load playerModel %s!\n", va("models/players/%s/model.glm", modelName) ); + } +} +/* +Ghoul2 Insert End +*/ + +void G_RemovePlayerModel( gentity_t *ent ) +{ + if ( ent->playerModel >= 0 && ent->ghoul2.size() ) + { + gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->playerModel ); + ent->playerModel = -1; + } +} + +void G_RemoveWeaponModels( gentity_t *ent ) +{ + if ( ent->ghoul2.size() ) + { + if ( ent->weaponModel[0] > 0 ) + { + gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[0] ); + ent->weaponModel[0] = -1; + } + if ( ent->weaponModel[1] > 0 ) + { + gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[1] ); + ent->weaponModel[1] = -1; + } + } +} + +void G_AddWeaponModels( gentity_t *ent ) +{ + if ( !ent || !ent->client ) + { + return; + } + if ( ent->weaponModel[0] == -1 ) + { + if ( ent->client->ps.weapon == WP_SABER ) + { + WP_SaberAddG2SaberModels( ent ); + } + else if ( ent->client->ps.weapon != WP_NONE ) + { + G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 ); + } + } +} + +extern saber_colors_t TranslateSaberColor( const char *name ); +extern void WP_RemoveSaber( gentity_t *ent, int saberNum ); +void G_ChangePlayerModel( gentity_t *ent, const char *newModel ); +void G_SetSabersFromCVars( gentity_t *ent ) +{ + if ( g_saber->string + && g_saber->string[0] + && Q_stricmp( "none", g_saber->string ) + && Q_stricmp( "NULL", g_saber->string ) ) + {//FIXME: how to specify second saber? + WP_SaberParseParms( g_saber->string, &ent->client->ps.saber[0] ); + if ( ent->client->ps.saber[0].style ) + { + ent->client->ps.saberStylesKnown |= (1<client->ps.saber[0].style); + } + } + + if ( player + && player->client + && player->client->sess.mission_objectives[LIGHTSIDE_OBJ].status == 2 + && g_saberDarkSideSaberColor->integer ) + {//dark side! + //always use red + for ( int n = 0; n < MAX_BLADES; n++ ) + { + ent->client->ps.saber[0].blade[n].color = SABER_RED; + } + } + else if ( g_saber_color->string ) + {//FIXME: how to specify color for each blade and/or color for second saber? + saber_colors_t color = TranslateSaberColor( g_saber_color->string ); + for ( int n = 0; n < MAX_BLADES; n++ ) + { + ent->client->ps.saber[0].blade[n].color = color; + } + } + if ( g_saber2->string + && g_saber2->string[0] + && Q_stricmp( "none", g_saber2->string ) + && Q_stricmp( "NULL", g_saber2->string ) ) + { + if ( !ent->client->ps.saber[0].twoHanded ) + {//can't use a second saber if first one is a two-handed saber...? + WP_SaberParseParms( g_saber2->string, &ent->client->ps.saber[1] ); + if ( ent->client->ps.saber[1].style ) + { + ent->client->ps.saberStylesKnown |= (1<client->ps.saber[1].style); + } + if ( ent->client->ps.saber[1].twoHanded ) + {//tsk tsk, can't use a twoHanded saber as second saber + WP_RemoveSaber( ent, 1 ); + } + else + { + ent->client->ps.dualSabers = qtrue; + if ( player + && player->client + && player->client->sess.mission_objectives[LIGHTSIDE_OBJ].status == 2 + && g_saberDarkSideSaberColor->integer ) + {//dark side! + //always use red + for ( int n = 0; n < MAX_BLADES; n++ ) + { + ent->client->ps.saber[1].blade[n].color = SABER_RED; + } + } + else if ( g_saber2_color->string ) + {//FIXME: how to specify color for each blade and/or color for second saber? + saber_colors_t color = TranslateSaberColor( g_saber2_color->string ); + for ( int n = 0; n < MAX_BLADES; n++ ) + { + ent->client->ps.saber[1].blade[n].color = color; + } + } + } + } + } +} + +void G_InitPlayerFromCvars( gentity_t *ent ) +{ + //set model based on cvars + G_ChangePlayerModel( ent, va("%s|%s|%s|%s", g_char_model->string, g_char_skin_head->string, g_char_skin_torso->string, g_char_skin_legs->string) ); + + //FIXME: parse these 2 from some cvar or require playermodel to be in a *.npc? + if( ent->NPC_type && gi.bIsFromZone(ent->NPC_type, TAG_G_ALLOC) ) { + gi.Free(ent->NPC_type); + } + ent->NPC_type = "player";//default for now + if( ent->client->clientInfo.customBasicSoundDir && gi.bIsFromZone(ent->client->clientInfo.customBasicSoundDir, TAG_G_ALLOC) ) { + gi.Free(ent->client->clientInfo.customBasicSoundDir); + } + + char snd[512]; + gi.Cvar_VariableStringBuffer( "snd", snd, sizeof(snd) ); + + ent->client->clientInfo.customBasicSoundDir = G_NewString(snd); //copy current cvar + + //set the lightsaber + G_RemoveWeaponModels( ent ); + G_SetSabersFromCVars( ent ); + //set up weapon models, etc. + G_AddWeaponModels( ent ); + NPC_SetAnim( ent, SETANIM_LEGS, ent->client->ps.legsAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART ); + NPC_SetAnim( ent, SETANIM_TORSO, ent->client->ps.torsoAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART ); + if ( !ent->s.number ) + {//the actual player, not an NPC pretending to be a player + ClientUserinfoChanged( ent->s.number ); + } + //color tinting + //FIXME: the customRGBA shouldn't be set if the shader this guys .skin is using doesn't have the tinting on it + if ( g_char_color_red->integer + || g_char_color_green->integer + || g_char_color_blue->integer ) + { + ent->client->renderInfo.customRGBA[0] = g_char_color_red->integer; + ent->client->renderInfo.customRGBA[1] = g_char_color_green->integer; + ent->client->renderInfo.customRGBA[2] = g_char_color_blue->integer; + ent->client->renderInfo.customRGBA[3] = 255; + } +} + +void G_ChangePlayerModel( gentity_t *ent, const char *newModel ) +{ + if ( !ent || !ent->client || !newModel ) + { + return; + } + + G_RemovePlayerModel( ent ); + if ( Q_stricmp( "player", newModel ) == 0 ) + { + G_InitPlayerFromCvars( ent ); + return; + } + + //attempt to free the string (currently can't since it's always "player" ) + if( ent->NPC_type && gi.bIsFromZone(ent->NPC_type, TAG_G_ALLOC) ) { + gi.Free(ent->NPC_type); + } + ent->NPC_type = G_NewString( newModel ); + G_RemoveWeaponModels( ent ); + + if ( strchr(newModel,'|') ) + { + char name[MAX_QPATH]; + strcpy(name, newModel); + char *p = strchr(name, '|'); + *p=0; + p++; + + G_SetG2PlayerModel( ent, name, p, NULL, NULL ); + } + else + { + //FIXME: everything but force powers gets reset, those should, too... + // currently leaves them as is except where otherwise noted in the NPCs.cfg? + //FIXME: remove all weapons? + if ( NPC_ParseParms( ent->NPC_type, ent ) ) + { + G_AddWeaponModels( ent ); + NPC_SetAnim( ent, SETANIM_LEGS, ent->client->ps.legsAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART ); + NPC_SetAnim( ent, SETANIM_TORSO, ent->client->ps.torsoAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART ); + ClientUserinfoChanged( ent->s.number ); + //Ugh, kind of a hack for now: + if ( ent->client->NPC_class == CLASS_BOBAFETT + || ent->client->NPC_class == CLASS_ROCKETTROOPER ) + { + //FIXME: remove saber, too? + Boba_Precache(); // player as boba? + } + } + else + { + gi.Printf( S_COLOR_RED"G_ChangePlayerModel: cannot find NPC %s\n", newModel ); + G_ChangePlayerModel( ent, "stormtrooper" ); //need a better fallback? + } + } +} + +void G_ReloadSaberData( gentity_t *ent ) +{ + //dualSabers should already be set + if ( ent->client->ps.saber[0].name != NULL ) + { + WP_SaberParseParms( ent->client->ps.saber[0].name, &ent->client->ps.saber[0], qfalse ); + if ( ent->client->ps.saber[0].style ) + { + ent->client->ps.saberStylesKnown |= (1<client->ps.saber[0].style); + } + } + if ( ent->client->ps.saber[1].name != NULL ) + { + WP_SaberParseParms( ent->client->ps.saber[1].name, &ent->client->ps.saber[1], qfalse ); + if ( ent->client->ps.saber[1].style ) + { + ent->client->ps.saberStylesKnown |= (1<client->ps.saber[1].style); + } + } +} + +qboolean G_PlayerSpawned( void ) +{ + if ( !player + || !player->client + || player->client->pers.teamState.state != TEAM_ACTIVE + || level.time - player->client->pers.enterTime < 100 ) + {//player hasn't spawned yet + return qfalse; + } + return qtrue; +} + +/* +=========== +ClientSpawn + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState +============ +*/ + +qboolean G_CheckPlayerDarkSide( void ) +{ + if ( player && player->client && player->client->sess.mission_objectives[LIGHTSIDE_OBJ].status == 2 ) + {//dark side player! + player->client->playerTeam = TEAM_FREE; + player->client->enemyTeam = TEAM_FREE; + if ( g_saberDarkSideSaberColor->integer ) + {//dark side! + //always use red + for ( int n = 0; n < MAX_BLADES; n++ ) + { + player->client->ps.saber[0].blade[n].color = player->client->ps.saber[1].blade[n].color = SABER_RED; + } + } + G_SoundIndex( "sound/chars/jedi2/28je2008.wav" ); + G_SoundIndex( "sound/chars/jedi2/28je2009.wav" ); + G_SoundIndex( "sound/chars/jedi2/28je2012.wav" ); + return qtrue; + } + return qfalse; +} + +qboolean ClientSpawn(gentity_t *ent, SavedGameJustLoaded_e eSavedGameJustLoaded ) +{ + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + clientPersistant_t saved; + clientSession_t savedSess; + clientInfo_t savedCi; + int persistant[MAX_PERSISTANT]; + usercmd_t ucmd; + gentity_t *spawnPoint; + qboolean beamInEffect = qfalse; + extern qboolean g_qbLoadTransition; + + index = ent - g_entities; + client = ent->client; + + if ( eSavedGameJustLoaded == eFULL && g_qbLoadTransition == qfalse )//qbFromSavedGame) + {//loading up a full save game + ent->client->pers.teamState.state = TEAM_ACTIVE; + + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; + + client->airOutTime = level.time + 12000; + + for (i=0; i<3; i++) + { + ent->client->pers.cmd_angles[i] = 0.0f; + } + + SetClientViewAngle( ent, ent->client->ps.viewangles);//spawn_angles ); + + gi.linkentity (ent); + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + PlayerStateToEntityState( &client->ps, &ent->s ); + + //FIXME: make sure ent->NPC_type is saved out + G_LoadAnimFileSet( ent, ent->NPC_type ); + G_SetSkin( ent ); + + //setup sabers + G_ReloadSaberData( ent ); + //force power levels should already be set + } + else + { + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + // don't spawn near existing origin if possible + spawnPoint = SelectSpawnPoint ( ent->client->ps.origin, + (team_t) ent->client->ps.persistant[PERS_TEAM], spawn_origin, spawn_angles); + + ent->client->pers.teamState.state = TEAM_ACTIVE; + + // clear everything but the persistant data + saved = client->pers; + savedSess = client->sess; + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) + { + persistant[i] = client->ps.persistant[i]; + } + //Preserve clientInfo + memcpy (&savedCi, &client->clientInfo, sizeof(clientInfo_t)); + + memset (client, 0, sizeof(*client)); + + memcpy (&client->clientInfo, &savedCi, sizeof(clientInfo_t)); + + client->pers = saved; + client->sess = savedSess; + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) + { + client->ps.persistant[i] = persistant[i]; + } + + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; + + client->airOutTime = level.time + 12000; + + // clear entity values + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[index]; + ent->mass = 10; + ent->takedamage = qtrue; + ent->inuse = qtrue; + SetInUse(ent); + ent->m_iIcarusID = IIcarusInterface::ICARUS_INVALID; + if ( !ent->NPC_type ) + { + ent->NPC_type = "player"; + } + ent->classname = "player"; + ent->targetname = ent->script_targetname = "player"; + if ( ent->client->NPC_class == CLASS_NONE ) + { + ent->client->NPC_class = CLASS_PLAYER; + } + client->playerTeam = TEAM_PLAYER; + client->enemyTeam = TEAM_ENEMY; + ent->contents = CONTENTS_BODY; + ent->clipmask = MASK_PLAYERSOLID; + ent->e_DieFunc = dieF_player_die; + ent->waterlevel = 0; + ent->watertype = 0; + client->ps.friction = 6; + client->ps.gravity = g_gravity->value; + ent->flags &= ~FL_NO_KNOCKBACK; + client->renderInfo.lookTarget = ENTITYNUM_NONE; + client->renderInfo.lookTargetClearTime = 0; + client->renderInfo.lookMode = LM_ENT; + + VectorCopy (playerMins, ent->mins); + VectorCopy (playerMaxs, ent->maxs); + client->crouchheight = CROUCH_MAXS_2; + client->standheight = DEFAULT_MAXS_2; + + client->ps.clientNum = index; + + // give default weapons + //these are precached in g_items, ClearRegisteredItems() + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE ); + //client->ps.inventory[INV_ELECTROBINOCULARS] = 1; + //ent->client->ps.inventory[INV_BACTA_CANISTER] = 1; + + // give EITHER the saber or the stun baton..never both + if ( spawnPoint->spawnflags & 32 ) // STUN_BATON + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_STUN_BATON ); + client->ps.weapon = WP_STUN_BATON; + } + else + { // give the saber because most test maps will not have the STUN BATON flag set + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER ); //this is precached in SP_info_player_deathmatch + client->ps.weapon = WP_SABER; + } + // force the base weapon up + client->ps.weaponstate = WEAPON_READY; + + for ( i = FIRST_WEAPON; i < MAX_PLAYER_WEAPONS; i++ ) // don't give ammo for explosives + { + if ( (client->ps.stats[STAT_WEAPONS]&(1<ps.ammo[weaponData[i].ammoIndex] = ammoData[weaponData[i].ammoIndex].max; + } + } + + if ( eSavedGameJustLoaded == eNO ) + { + //FIXME: get player's info from NPCs.cfg + client->ps.dualSabers = qfalse; + WP_SaberParseParms( g_saber->string, &client->ps.saber[0] );//get saber info + + client->ps.saberStylesKnown |= (1<ps.saber[0].style ) +// { +// client->ps.saberStylesKnown |= (1<ps.saber[0].style); +// } + WP_InitForcePowers( ent );//Initialize force powers + } + else + {//autoload, will be taken care of below + } + // + + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; + ent->client->dismemberProbHead = 0; + ent->client->dismemberProbArms = 5; + ent->client->dismemberProbHands = 20; + ent->client->dismemberProbWaist = 0; + ent->client->dismemberProbLegs = 0; + + ent->client->ps.batteryCharge = 2500; + + VectorCopy( spawn_origin, client->ps.origin ); + VectorCopy( spawn_origin, ent->currentOrigin ); + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + SetClientViewAngle( ent, spawn_angles ); + + G_KillBox( ent ); + gi.linkentity (ent); + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + client->latched_buttons = 0; + + // set default animations + client->ps.torsoAnim = BOTH_STAND2; + client->ps.legsAnim = BOTH_STAND2; + + //clear IK grabbing stuff + client->ps.heldClient = client->ps.heldByClient = ENTITYNUM_NONE; + client->ps.saberLockEnemy = ENTITYNUM_NONE; //duh, don't think i'm locking with myself + + // restore some player data + // + Player_RestoreFromPrevLevel(ent); + + //FIXME: put this BEFORE the Player_RestoreFromPrevLevel check above? + if (eSavedGameJustLoaded == eNO) + {//fresh start + if (!(spawnPoint->spawnflags&1)) // not KEEP_PREV + {//then restore health and armor + ent->health = client->ps.stats[STAT_ARMOR] = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; + ent->client->ps.forcePower = ent->client->ps.forcePowerMax; + } + G_InitPlayerFromCvars( ent ); + } + else + {//autoload + G_LoadAnimFileSet( ent, ent->NPC_type ); + G_SetSkin( ent ); + G_ReloadSaberData( ent ); + //force power levels should already be set + } + + //NEVER start a map with either of your sabers or blades on... + ent->client->ps.SaberDeactivate(); + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ucmd = client->pers.lastCommand; + ucmd.serverTime = level.time; + _VectorCopy( client->pers.cmd_angles, ucmd.angles ); + ucmd.weapon = client->ps.weapon; // client think calls Pmove which sets the client->ps.weapon to ucmd.weapon, so ... + ent->client->ps.groundEntityNum = ENTITYNUM_NONE; + ClientThink( ent-g_entities, &ucmd ); + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + PlayerStateToEntityState( &client->ps, &ent->s ); + + //ICARUS include + Quake3Game()->FreeEntity( ent ); + Quake3Game()->InitEntity( ent ); + + // Make sure no Sequencer exists then Get a new one. + IIcarusInterface::GetIcarus()->DeleteIcarusID( ent->m_iIcarusID ); + ent->m_iIcarusID = IIcarusInterface::GetIcarus()->GetIcarusID( ent->s.number ); + + if ( spawnPoint->spawnflags & 64 ) //NOWEAPON + {//player starts with absolutely no weapons + ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE ); + ent->client->ps.ammo[weaponData[WP_NONE].ammoIndex] = 32000; + ent->client->ps.weapon = WP_NONE; + ent->client->ps.weaponstate = WEAPON_READY; + } + + if ( ent->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_SABER ) ) + {//set up so has lightsaber + WP_SaberInitBladeData( ent ); + if ( (ent->weaponModel[0] <= 0 || (ent->weaponModel[1]<=0&&ent->client->ps.dualSabers)) //one or both of the saber models is not initialized + && ent->client->ps.weapon == WP_SABER )//current weapon is saber + {//add the proper models + WP_SaberAddG2SaberModels( ent ); + } + } + if ( ent->weaponModel[0] == -1 && ent->client->ps.weapon != WP_NONE ) + { + G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 ); + } + + { + // fire the targets of the spawn point + G_UseTargets( spawnPoint, ent ); + //Designers needed them to fire off target2's as well... this is kind of messy + G_UseTargets2( spawnPoint, ent, spawnPoint->target2 ); + + /* + // select the highest weapon number available, after any + // spawn given items have fired + client->ps.weapon = 1; + for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { + if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { + client->ps.weapon = i; + break; + } + }*/ + } + } + + client->pers.enterTime = level.time;//needed mainly to stop the weapon switch to WP_NONE that happens on loads + ent->max_health = client->ps.stats[STAT_MAX_HEALTH]; + + if ( eSavedGameJustLoaded == eNO ) + {//on map transitions, Ghoul2 frame gets reset to zero, restart our anim + NPC_SetAnim( ent, SETANIM_LEGS, ent->client->ps.legsAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART ); + NPC_SetAnim( ent, SETANIM_TORSO, ent->client->ps.torsoAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART ); + } + + if ( ent->s.number == 0 ) + {//player + G_CheckPlayerDarkSide(); + } + + if ( (ent->client->ps.stats[STAT_WEAPONS]&(1<client->ps.saberStylesKnown ) + {//um, if you have a saber, you need at least 1 style to use it with... + ent->client->ps.saberStylesKnown |= (1<client ) { + return; + } + + // send effect if they were completely connected +/* if ( ent->client->pers.connected == CON_CONNECTED ) { + // They don't get to take powerups with them! + // Especially important for stuff like CTF flags + TossClientItems ( ent ); + } +*/ + gi.unlinkentity (ent); + ent->s.modelindex = 0; + ent->inuse = qfalse; + ClearInUse(ent); + ent->classname = "disconnected"; + ent->client->pers.connected = CON_DISCONNECTED; + ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; + + gi.SetConfigstring( CS_PLAYERS + clientNum, ""); + + IIcarusInterface::GetIcarus()->DeleteIcarusID(ent->m_iIcarusID); + +} + + + diff --git a/code/game/g_cmds.cpp b/code/game/g_cmds.cpp new file mode 100644 index 0000000..eff4ac4 --- /dev/null +++ b/code/game/g_cmds.cpp @@ -0,0 +1,1456 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "objectives.h" +#include "wp_saber.h" +#include "g_vehicles.h" + +extern bool in_camera; +extern stringID_table_t SaberStyleTable[]; + +extern void ForceHeal( gentity_t *self ); +extern void ForceGrip( gentity_t *self ); +extern void ForceTelepathy( gentity_t *self ); +extern void ForceRage( gentity_t *self ); +extern void ForceProtect( gentity_t *self ); +extern void ForceAbsorb( gentity_t *self ); +extern void ForceSeeing( gentity_t *self ); +extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *psWeaponModel, int boltNum, int weaponNum ); +extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 ); +extern void ItemUse_Bacta(gentity_t *ent); +extern gentity_t *G_GetSelfForPlayerCmd( void ); + +/* +================== +CheatsOk +================== +*/ +qboolean CheatsOk( gentity_t *ent ) { + if ( !g_cheats->integer ) { + gi.SendServerCommand( ent-g_entities, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + if ( ent->health <= 0 ) { + gi.SendServerCommand( ent-g_entities, "print \"You must be alive to use this command.\n\""); + return qfalse; + } + return qtrue; +} + + +/* +================== +ConcatArgs +================== +*/ +char *ConcatArgs( int start ) { + int i, c, tlen; + static char line[MAX_STRING_CHARS]; + int len; + char *arg; + + len = 0; + c = gi.argc(); + for ( i = start ; i < c ; i++ ) { + arg = gi.argv( i ); + tlen = strlen( arg ); + if ( len + tlen >= MAX_STRING_CHARS - 1 ) { + break; + } + memcpy( line + len, arg, tlen ); + len += tlen; + if ( i != c - 1 ) { + line[len] = ' '; + len++; + } + } + + line[len] = 0; + + return line; +} + +/* +================== +SanitizeString + +Remove case and control characters +================== +*/ +void SanitizeString( char *in, char *out ) { + while ( *in ) { + if ( *in == 27 ) { + in += 2; // skip color code + continue; + } + if ( *in < 32 ) { + in++; + continue; + } + *out++ = tolower( *in++ ); + } + + *out = 0; +} + +/* +================== +ClientNumberFromString + +Returns a player number for either a number or name string +Returns -1 if invalid +================== +*/ +int ClientNumberFromString( gentity_t *to, char *s ) { + gclient_t *cl; + int idnum; + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + + // numeric values are just slot numbers + if (s[0] >= '0' && s[0] <= '9') { + idnum = atoi( s ); + if ( idnum < 0 || idnum >= level.maxclients ) { + gi.SendServerCommand( to-g_entities, "print \"Bad client slot: %i\n\"", idnum); + return -1; + } + + cl = &level.clients[idnum]; + if ( cl->pers.connected != CON_CONNECTED ) { + gi.SendServerCommand( to-g_entities, "print \"Client %i is not active\n\"", idnum); + return -1; + } + return idnum; + } + + // check for a name match + SanitizeString( s, s2 ); + for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + SanitizeString( cl->pers.netname, n2 ); + if ( !strcmp( n2, s2 ) ) { + return idnum; + } + } + + gi.SendServerCommand( to-g_entities, "print \"User %s is not on the server\n\"", s); + return -1; +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (gentity_t *ent) +{ + char *name; + gitem_t *it; + int i; + qboolean give_all; + + if ( !CheatsOk( ent ) ) { + return; + } + + name = ConcatArgs( 1 ); + + if (Q_stricmp(name, "all") == 0) + give_all = qtrue; + else + give_all = qfalse; + + if (give_all || Q_stricmp(name, "force") == 0) + { + if ( ent->client ) + { + ent->client->ps.forcePower = FORCE_POWER_MAX; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(gi.argv(1), "health") == 0) + { + if (gi.argc() == 3) { + ent->health = atoi(gi.argv(2)); + if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]) { + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + } + else { + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + if (!give_all) + return; + } + + +/* if (give_all || Q_stricmp(name, "inventory") == 0) + { + // Huh? Was doing a INV_MAX+1 which was wrong because then you'd actually have every inventory item including INV_MAX + ent->client->ps.stats[STAT_ITEMS] = (1 << (INV_MAX)) - ( 1 << INV_ELECTROBINOCULARS ); + + ent->client->ps.inventory[INV_ELECTROBINOCULARS] = 1; + //ent->client->ps.inventory[INV_BACTA_CANISTER] = 5; + //ent->client->ps.inventory[INV_SEEKER] = 5; + ent->client->ps.inventory[INV_LIGHTAMP_GOGGLES] = 1; + //ent->client->ps.inventory[INV_SENTRY] = 5; + //ent->client->ps.inventory[INV_GOODIE_KEY] = 5; + //ent->client->ps.inventory[INV_SECURITY_KEY] = 5; + + if (!give_all) + { + return; + } + } +*/ + if (give_all || Q_stricmp(name, "weapons") == 0) + { + ent->client->ps.stats[STAT_WEAPONS] = (1 << (WP_MELEE)) - ( 1 << WP_NONE ); + if (!give_all) + return; + } + + if ( !give_all && Q_stricmp(gi.argv(1), "weaponnum") == 0 ) + { + ent->client->ps.stats[STAT_WEAPONS] |= (1 << atoi(gi.argv(2))); + return; + } + + if ( Q_stricmp(name, "eweaps") == 0) //for developing, gives you all the weapons, including enemy + { + ent->client->ps.stats[STAT_WEAPONS] = (unsigned)(1 << WP_NUM_WEAPONS) - ( 1 << WP_NONE ); // NOTE: this wasn't giving the last weapon in the list + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for ( i = 0 ; i < AMMO_MAX ; i++ ) { + ent->client->ps.ammo[i] = ammoData[i].max; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(gi.argv(1), "batteries") == 0) + { + if (gi.argc() == 3) + ent->client->ps.batteryCharge = atoi(gi.argv(2)); + else + ent->client->ps.batteryCharge = MAX_BATTERIES; + + if (!give_all) + return; + } + + if (give_all || Q_stricmp(gi.argv(1), "armor") == 0) + { + if (gi.argc() == 3) + ent->client->ps.stats[STAT_ARMOR] = atoi(gi.argv(2)); + else + ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH]; + + if ( ent->client->ps.stats[STAT_ARMOR] > 0 ) + { + ent->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE; + } + else + { + ent->client->ps.powerups[PW_BATTLESUIT] = 0; + } + + if (!give_all) + return; + } + + // spawn a specific item right on the player + if ( !give_all ) { + gentity_t *it_ent; + trace_t trace; + it = FindItem (name); + if (!it) { + name = gi.argv(1); + it = FindItem (name); + if (!it) { + gi.SendServerCommand( ent-g_entities, "print \"unknown item\n\""); + return; + } + } + + it_ent = G_Spawn(); + VectorCopy( ent->currentOrigin, it_ent->s.origin ); + it_ent->classname = G_NewString(it->classname); + G_SpawnItem (it_ent, it); + FinishSpawningItem(it_ent ); + memset( &trace, 0, sizeof( trace ) ); + Touch_Item (it_ent, ent, &trace); + if (it_ent->inuse) { + G_FreeEntity( it_ent ); + } + } +} + +//------------------ +void Cmd_Fx( gentity_t *ent ) +{ + vec3_t dir; + gentity_t *fx_ent = NULL; + + if ( Q_stricmp( gi.argv(1), "play" ) == 0 ) + { + if ( gi.argc() == 3 ) + { + // I guess, only allow one active at a time + while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) + { + G_FreeEntity( fx_ent ); + } + + fx_ent = G_Spawn(); + + fx_ent->fxFile = gi.argv( 2 ); + + // Move out in front of the person spawning the effect + AngleVectors( ent->currentAngles, dir, NULL, NULL ); + VectorMA( ent->currentOrigin, 32, dir, fx_ent->s.origin ); + +extern void SP_fx_runner( gentity_t *ent ); + + SP_fx_runner( fx_ent ); + fx_ent->delay = 2000; // adjusting delay + fx_ent->classname = "cmd_fx"; // and classname + + return; + } + } + else if ( Q_stricmp( gi.argv(1), "stop" ) == 0 ) + { + while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) + { + G_FreeEntity( fx_ent ); + } + + return; + } + else if ( Q_stricmp( gi.argv(1), "delay" ) == 0 ) + { + while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) + { + if ( gi.argc() == 3 ) + { + fx_ent->delay = atoi( gi.argv( 2 )); + } + else + { + gi.Printf( S_COLOR_GREEN"FX: current delay is: %i\n", fx_ent->delay ); + } + + return; + } + } + else if ( Q_stricmp( gi.argv(1), "random" ) == 0 ) + { + while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) + { + if ( gi.argc() == 3 ) + { + fx_ent->random = atoi( gi.argv( 2 )); + } + else + { + gi.Printf( S_COLOR_GREEN"FX: current random is: %6.2f\n", fx_ent->random ); + } + + return; + } + } + else if ( Q_stricmp( gi.argv(1), "origin" ) == 0 ) + { + while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) + { + if ( gi.argc() == 5 ) + { + fx_ent->s.origin[0] = atof( gi.argv( 2 )); + fx_ent->s.origin[1] = atof( gi.argv( 3 )); + fx_ent->s.origin[2] = atof( gi.argv( 4 )); + + G_SetOrigin( fx_ent, fx_ent->s.origin ); + } + else + { + gi.Printf( S_COLOR_GREEN"FX: current origin is: <%6.2f %6.2f %6.2f>\n", + fx_ent->currentOrigin[0], fx_ent->currentOrigin[1], fx_ent->currentOrigin[2] ); + } + + return; + } + } + else if ( Q_stricmp( gi.argv(1), "dir" ) == 0 ) + { + while (( fx_ent = G_Find( fx_ent, FOFS(classname), "cmd_fx")) != NULL ) + { + if ( gi.argc() == 5 ) + { + fx_ent->s.angles[0] = atof( gi.argv( 2 )); + fx_ent->s.angles[1] = atof( gi.argv( 3 )); + fx_ent->s.angles[2] = atof( gi.argv( 4 )); + + if ( !VectorNormalize( fx_ent->s.angles )) + { + // must have been zero length + fx_ent->s.angles[2] = 1; + } + } + else + { + gi.Printf( S_COLOR_GREEN"FX: current dir is: <%6.2f %6.2f %6.2f>\n", + fx_ent->s.angles[0], fx_ent->s.angles[1], fx_ent->s.angles[2] ); + } + + return; + } + } + + gi.Printf( S_COLOR_CYAN"Fx--------------------------------------------------------\n" ); + gi.Printf( S_COLOR_CYAN"commands: sample usage:\n" ); + gi.Printf( S_COLOR_CYAN"----------------------------------------------------------\n" ); + gi.Printf( S_COLOR_CYAN"fx play fx play sparks, fx play env/fire\n" ); + gi.Printf( S_COLOR_CYAN"fx stop fx stop\n" ); + gi.Printf( S_COLOR_CYAN"fx delay <#> fx delay 1000\n" ); + gi.Printf( S_COLOR_CYAN"fx random <#> fx random 200\n" ); + gi.Printf( S_COLOR_CYAN"fx origin <#><#><#> fx origin 10 20 30\n" ); + gi.Printf( S_COLOR_CYAN"fx dir <#><#><#> fx dir 0 0 -1\n\n" ); +} + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (gentity_t *ent) +{ + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + gi.SendServerCommand( ent-g_entities, "print \"%s\"", msg); +} + +/* +================== +Cmd_Undying_f + +Sets client to undead mode + +argv(0) undying +================== +*/ +void Cmd_Undying_f (gentity_t *ent) +{ + char *msg; + + if ( !CheatsOk( ent ) ) + { + return; + } + + ent->flags ^= FL_UNDYING; + if (!(ent->flags & FL_UNDYING) ) + { + msg = "undead mode OFF\n"; + } + else + { + int max; + char *cmd; + + cmd = gi.argv(1); + if ( cmd && atoi( cmd ) ) + { + max = atoi( cmd ); + } + else + { + max = 999; + } + + ent->health = ent->max_health = max; + + msg = "undead mode ON\n"; + + if ( ent->client ) + { + ent->client->ps.stats[STAT_HEALTH] = ent->client->ps.stats[STAT_MAX_HEALTH] = 999; + } + } + + gi.SendServerCommand( ent-g_entities, "print \"%s\"", msg); +} + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + gi.SendServerCommand( ent-g_entities, "print \"%s\"", msg); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + if ( ent->client->noclip ) { + msg = "noclip OFF\n"; + } else { + msg = "noclip ON\n"; + } + ent->client->noclip = !ent->client->noclip; + + gi.SendServerCommand( ent-g_entities, "print \"%s\"", msg); +} + + +/* +================== +Cmd_LevelShot_f + +This is just to help generate the level pictures +for the menus. It goes to the intermission immediately +and sends over a command to the client to resize the view, +hide the scoreboard, and take a special screenshot +================== +*/ +void Cmd_LevelShot_f( gentity_t *ent ) { + if ( !CheatsOk( ent ) ) { + return; + } + + gi.SendServerCommand( ent-g_entities, "clientLevelShot" ); +} + + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( gentity_t *ent ) { + if( ( level.time - ent->client->respawnTime ) < 5000 ) { + gi.SendServerCommand( ent-g_entities, "cp @SP_INGAME_ONE_KILL_PER_5_SECONDS"); + return; + } + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + player_die (ent, ent, ent, 100000, MOD_SUICIDE); +} + + +/* +================== +Cmd_Where_f +================== +*/ +void Cmd_Where_f( gentity_t *ent ) { + const char *s = gi.argv(1); + const len = strlen(s); + gentity_t *check; + + if ( gi.argc () < 2 ) { + gi.Printf("usage: where classname\n"); + return; + } + for (int i = 0; i < globals.num_entities; i++) + { + if(!PInUse(i)) + continue; +// if(!check || !check->inuse) { +// continue; +// } + check = &g_entities[i]; + if (!Q_stricmpn(s, check->classname, len) ) { + gi.SendServerCommand( ent-g_entities, "print \"%s %s\n\"", check->classname, vtos( check->s.pos.trBase ) ); + } + } +} + + +/* +------------------------- +UserSpawn +------------------------- +*/ + +extern qboolean G_CallSpawn( gentity_t *ent ); + +void UserSpawn( gentity_t *ent, const char *name ) +{ + vec3_t origin; + vec3_t vf; + vec3_t angles; + gentity_t *ent2; + + //Spawn the ent + ent2 = G_Spawn(); + ent2->classname = G_NewString( name ); + + //TODO: This should ultimately make sure this is a safe spawn! + + //Spawn the entity and place it there + VectorSet( angles, 0, ent->s.apos.trBase[YAW], 0 ); + AngleVectors( angles, vf, NULL, NULL ); + VectorMA( ent->s.pos.trBase, 96, vf, origin ); //FIXME: Find the radius size of the object, and push out 32 + radius + + origin[2] += 8; + VectorCopy( origin, ent2->s.pos.trBase ); + VectorCopy( origin, ent2->s.origin ); + VectorCopy( ent->s.apos.trBase, ent2->s.angles ); + + gi.linkentity( ent2 ); + + //Find a valid spawning spot + if ( G_CallSpawn( ent2 ) == qfalse ) + { + gi.SendServerCommand( ent-g_entities, "print \"Failed to spawn '%s'\n\"", name ); + G_FreeEntity( ent2 ); + return; + } +} + +/* +------------------------- +Cmd_Spawn +------------------------- +*/ + +void Cmd_Spawn( gentity_t *ent ) +{ + char *name; + + name = ConcatArgs( 1 ); + + gi.SendServerCommand( ent-g_entities, "print \"Spawning '%s'\n\"", name ); + + UserSpawn( ent, name ); +} + +/* +================= +Cmd_SetViewpos_f +================= +*/ +void Cmd_SetViewpos_f( gentity_t *ent ) { + vec3_t origin, angles; + int i; + + if ( !g_cheats->integer ) { + gi.SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\"")); + return; + } + if ( gi.argc() != 5 ) { + gi.SendServerCommand( ent-g_entities, va("print \"usage: setviewpos x y z yaw\n\"")); + return; + } + + VectorClear( angles ); + for ( i = 0 ; i < 3 ; i++ ) { + origin[i] = atof( gi.argv( i+1 ) ); + } + origin[2] -= 25; //acount for eye height from viewpos cmd + + angles[YAW] = atof( gi.argv( 4 ) ); + + TeleportPlayer( ent, origin, angles ); +} + + + +/* +================= +Cmd_SetObjective_f +================= +*/ +qboolean G_CheckPlayerDarkSide( void ); + +void Cmd_SetObjective_f( gentity_t *ent ) +{ + int objectiveI,status,displayStatus; + + if ( gi.argc() == 2 ) { + objectiveI = atoi(gi.argv(1)); + gi.Printf("objective #%d display status=%d, status=%d\n",objectiveI, + ent->client->sess.mission_objectives[objectiveI].display, + ent->client->sess.mission_objectives[objectiveI].status + ); + return; + } + if ( gi.argc() != 4 ) { + gi.SendServerCommand( ent-g_entities, va("print \"usage: setobjective \n\"")); + return; + } + + if ( !CheatsOk( ent ) ) + { + return; + } + + objectiveI = atoi(gi.argv(1)); + displayStatus = atoi(gi.argv(2)); + status = atoi(gi.argv(3)); + + ent->client->sess.mission_objectives[objectiveI].display = displayStatus; + ent->client->sess.mission_objectives[objectiveI].status = status; + G_CheckPlayerDarkSide(); +} + +/* +================= +Cmd_ViewObjective_f +================= +*/ +void Cmd_ViewObjective_f( gentity_t *ent ) +{ + int objectiveI; + + if ( gi.argc() != 2 ) { + gi.SendServerCommand( ent-g_entities, va("print \"usage: viewobjective \n\"")); + return; + } + + objectiveI = atoi(gi.argv(1)); + + gi.SendServerCommand( ent-g_entities, va("print \"Objective %d Display Status(1=show): %d Status:%d\n\"",objectiveI,ent->client->sess.mission_objectives[objectiveI].display,ent->client->sess.mission_objectives[objectiveI].status)); +} + + +/* +================ +Cmd_UseElectrobinoculars_f +================ +*/ +void Cmd_UseElectrobinoculars_f(gentity_t *ent) +{ + if ( ent->health < 1 || in_camera ) + { + return; + } + + if ( ent->client->ps.inventory[INV_ELECTROBINOCULARS] <= 0 ) + { + // have none to place...play sound? + return; + } + + G_AddEvent( ent, EV_USE_INV_BINOCULARS, 0 ); +} + +/* +================ +Cmd_UseBacta_f +================ +*/ +void Cmd_UseBacta_f(gentity_t *ent) +{ + if ( ent->health < 1 || in_camera ) + { + return; + } + + ItemUse_Bacta(ent); +} + +//---------------------------------------------------------------------------------- +qboolean PickSeekerSpawnPoint( vec3_t org, vec3_t fwd, vec3_t right, int skip, vec3_t spot ) +{ + vec3_t mins, maxs, forward, end; + trace_t tr; + + VectorSet( maxs, -8, -8, -24); // ?? size + VectorSet( maxs, 8, 8, 8 ); + + VectorCopy( fwd, forward ); + + // to the front and side a bit + forward[2] = 0.3f; // start up a bit + + VectorMA( org, 48, forward, end ); + VectorMA( end, -8, right, end ); + + gi.trace( &tr, org, mins, maxs, end, skip, MASK_PLAYERSOLID ); + + if ( !tr.startsolid && !tr.allsolid && tr.fraction >= 1.0f ) + { + VectorCopy( tr.endpos, spot ); + return qtrue; + } + + // side + VectorMA( org, 48, right, end ); + + gi.trace( &tr, org, mins, maxs, end, skip, MASK_PLAYERSOLID ); + + if ( !tr.startsolid && !tr.allsolid && tr.fraction >= 1.0f ) + { + VectorCopy( tr.endpos, spot ); + return qtrue; + } + + // other side + VectorMA( org, -48, right, end ); + + gi.trace( &tr, org, mins, maxs, end, skip, MASK_PLAYERSOLID ); + + if ( !tr.startsolid && !tr.allsolid && tr.fraction >= 1.0f ) + { + VectorCopy( tr.endpos, spot ); + return qtrue; + } + + // behind + VectorMA( org, -48, fwd, end ); + + gi.trace( &tr, org, mins, maxs, end, skip, MASK_PLAYERSOLID ); + + if ( !tr.startsolid && !tr.allsolid && tr.fraction >= 1.0f ) + { + VectorCopy( tr.endpos, spot ); + return qtrue; + } + + return qfalse; +} + +/* +================ +Cmd_UseSeeker_f +================ +*/ +void Cmd_UseSeeker_f( gentity_t *ent ) +{ + if ( ent->health < 1 || in_camera ) + { + return; + } + + // don't use them if we don't have any...also don't use them if one is already going + if ( ent->client && ent->client->ps.inventory[INV_SEEKER] > 0 && level.time > ent->client->ps.powerups[PW_SEEKER] ) + { + gentity_t *tent = G_Spawn(); + + if ( tent ) + { + vec3_t fwd, right, spot; + + AngleVectors( ent->client->ps.viewangles, fwd, right, NULL ); + + VectorCopy( ent->currentOrigin, spot ); // does nothing really, just initialize the goods... + + if ( PickSeekerSpawnPoint( ent->currentOrigin, fwd, right, ent->s.number, spot )) + { + VectorCopy( spot, tent->s.origin ); + G_SetOrigin( tent, spot ); + G_SetAngles( tent, ent->currentAngles ); + +extern void SP_NPC_Droid_Seeker( gentity_t *ent ); + + SP_NPC_Droid_Seeker( tent ); + G_Sound( tent, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); + + // make sure that we even have some + ent->client->ps.inventory[INV_SEEKER]--; + ent->client->ps.powerups[PW_SEEKER] = level.time + 1000;// can only drop one every second..maybe this is annoying? + + } + } + } +} + +/* +================ +Cmd_UseGoggles_f +================ +*/ +void Cmd_UseGoggles_f(gentity_t *ent) +{ + if ( ent->health < 1 || in_camera ) + { + return; + } + + if ( ent->client && ent->client->ps.inventory[INV_LIGHTAMP_GOGGLES] > 0 ) + { + G_AddEvent( ent, EV_USE_INV_LIGHTAMP_GOGGLES, 0 ); + } +} + +/* +================ +Cmd_UseSentry_f +================ +*/ +qboolean place_portable_assault_sentry( gentity_t *self, vec3_t origin, vec3_t dir ); +void Cmd_UseSentry_f(gentity_t *ent) +{ + if ( ent->health < 1 || in_camera ) + { + return; + } + + if ( ent->client->ps.inventory[INV_SENTRY] <= 0 ) + { + // have none to place...play sound? + return; + } + + if ( place_portable_assault_sentry( ent, ent->currentOrigin, ent->client->ps.viewangles )) + { + ent->client->ps.inventory[INV_SENTRY]--; + G_AddEvent( ent, EV_USE_INV_SENTRY, 0 ); + } + else + { + // couldn't be placed....play a notification sound!! + } +} + +/* +================ +Cmd_UseInventory_f +================ +*/ +void Cmd_UseInventory_f(gentity_t *ent) +{ + switch (cg.inventorySelect) + { + case INV_ELECTROBINOCULARS : + Cmd_UseElectrobinoculars_f(ent); + return; + //case INV_BACTA_CANISTER : + // Cmd_UseBacta_f(ent); + // return; + case INV_SEEKER : + Cmd_UseSeeker_f(ent); + return; + case INV_LIGHTAMP_GOGGLES : + Cmd_UseGoggles_f(ent); + return; + case INV_SENTRY : + Cmd_UseSentry_f(ent); + return; + default : + return; + + } +} + +void Cmd_FlushCamFile_f(gentity_t *ent) +{ + gi.FlushCamFile(); +} + +void G_Taunt( gentity_t *ent ) +{ + if ( ent->client ) + { + if ( ent->client->ps.weapon == WP_SABER + && (ent->client->ps.saberAnimLevel == SS_STAFF //ent->client->ps.saber[0].type == SABER_STAFF + || ent->client->ps.dualSabers) ) + { + ent->client->ps.taunting = level.time + 100; + //make sure all sabers are on + ent->client->ps.SaberActivate(); + } + else + { + ent->client->ps.taunting = level.time + 100; + } + } +} + +void G_Victory( gentity_t *ent ) +{ + if ( ent->health > 0 ) + {//say something and put away saber + G_SoundOnEnt( ent, CHAN_VOICE, "sound/chars/kyle/misc/taunt1.wav" ); + if ( ent->client ) + { + ent->client->ps.SaberDeactivate(); + } + } +} + +typedef enum +{ + TAUNT_TAUNT = 0, + TAUNT_BOW, + TAUNT_MEDITATE, + TAUNT_FLOURISH, + TAUNT_GLOAT +}; + +extern void G_SpeechEvent( gentity_t *self, int event ); +void G_TauntSound( gentity_t *ent, int taunt ) +{ + switch ( taunt ) + { + case TAUNT_TAUNT: + default: + if ( Q_irand( 0, 1 ) ) + { + G_SpeechEvent( ent, Q_irand( EV_ANGER1, EV_ANGER3 ) ); + } + else + { + G_SpeechEvent( ent, Q_irand( EV_TAUNT1, EV_TAUNT3 ) ); + } + break; + case TAUNT_BOW: + break; + case TAUNT_MEDITATE: + break; + case TAUNT_FLOURISH: + if ( Q_irand( 0, 1 ) ) + { + G_SpeechEvent( ent, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ) ); + } + else + { + G_SpeechEvent( ent, Q_irand( EV_GLOAT1, EV_GLOAT3 ) ); + } + break; + case TAUNT_GLOAT: + G_SpeechEvent( ent, Q_irand( EV_VICTORY1, EV_VICTORY3 ) ); + break; + } +} + +void G_SetTauntAnim( gentity_t *ent, int taunt ) +{ + if ( !ent || !ent->client ) + { + return; + } + if ( !ent->client->ps.torsoAnimTimer + && !ent->client->ps.legsAnimTimer + && !ent->client->ps.weaponTime + && ent->client->ps.saberLockTime < level.time ) + { + int anim = -1; + switch ( taunt ) + { + case TAUNT_TAUNT: + if ( ent->client->ps.weapon != WP_SABER ) + { + anim = BOTH_ENGAGETAUNT; + } + else + { + switch ( ent->client->ps.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + if ( ent->client->ps.saber[1].Active() ) + {//turn off second saber + G_Sound( ent, ent->client->ps.saber[1].soundOff ); + } + else if ( ent->client->ps.saber[0].Active() ) + {//turn off first + G_Sound( ent, ent->client->ps.saber[0].soundOff ); + } + ent->client->ps.SaberDeactivate(); + anim = BOTH_GESTURE1; + break; + case SS_MEDIUM: + case SS_STRONG: + case SS_DESANN: + anim = BOTH_ENGAGETAUNT; + break; + case SS_DUAL: + ent->client->ps.SaberActivate(); + anim = BOTH_DUAL_TAUNT; + break; + case SS_STAFF: + ent->client->ps.SaberActivate(); + anim = BOTH_STAFF_TAUNT; + break; + } + } + break; + case TAUNT_BOW: + anim = BOTH_BOW; + if ( ent->client->ps.saber[1].Active() ) + {//turn off second saber + G_Sound( ent, ent->client->ps.saber[1].soundOff ); + } + else if ( ent->client->ps.saber[0].Active() ) + {//turn off first + G_Sound( ent, ent->client->ps.saber[0].soundOff ); + } + ent->client->ps.SaberDeactivate(); + break; + case TAUNT_MEDITATE: + anim = BOTH_MEDITATE; + if ( ent->client->ps.saber[1].Active() ) + {//turn off second saber + G_Sound( ent, ent->client->ps.saber[1].soundOff ); + } + else if ( ent->client->ps.saber[0].Active() ) + {//turn off first + G_Sound( ent, ent->client->ps.saber[0].soundOff ); + } + ent->client->ps.SaberDeactivate(); + break; + case TAUNT_FLOURISH: + if ( ent->client->ps.weapon == WP_SABER ) + { + ent->client->ps.SaberActivate(); + switch ( ent->client->ps.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + anim = BOTH_SHOWOFF_FAST; + break; + case SS_MEDIUM: + anim = BOTH_SHOWOFF_MEDIUM; + break; + case SS_STRONG: + case SS_DESANN: + anim = BOTH_SHOWOFF_STRONG; + break; + case SS_DUAL: + anim = BOTH_SHOWOFF_DUAL; + break; + case SS_STAFF: + anim = BOTH_SHOWOFF_STAFF; + break; + } + } + break; + case TAUNT_GLOAT: + switch ( ent->client->ps.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + anim = BOTH_VICTORY_FAST; + break; + case SS_MEDIUM: + anim = BOTH_VICTORY_MEDIUM; + break; + case SS_STRONG: + case SS_DESANN: + ent->client->ps.SaberActivate(); + anim = BOTH_VICTORY_STRONG; + break; + case SS_DUAL: + ent->client->ps.SaberActivate(); + anim = BOTH_VICTORY_DUAL; + break; + case SS_STAFF: + ent->client->ps.SaberActivate(); + anim = BOTH_VICTORY_STAFF; + break; + } + break; + } + if ( anim != -1 ) + { + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + int parts = SETANIM_TORSO; + if ( anim != BOTH_ENGAGETAUNT ) + { + parts = SETANIM_BOTH; + VectorClear( ent->client->ps.velocity ); + } + NPC_SetAnim( ent, parts, anim, (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD) ); + } + if ( taunt != TAUNT_MEDITATE + && taunt != TAUNT_BOW ) + {//no sound for meditate or bow + G_TauntSound( ent, taunt ); + } + } + } +} +/* +================= +ClientCommand +================= +*/ +void ClientCommand( int clientNum ) { + gentity_t *ent; + char *cmd; + + ent = g_entities + clientNum; + if ( !ent->client ) { + return; // not fully in game yet + } + + cmd = gi.argv(0); + + if (Q_stricmp (cmd, "spawn") == 0) + { + Cmd_Spawn( ent ); + return; + } + + 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, "undying") == 0) + Cmd_Undying_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, "kill") == 0) + { + if ( !CheatsOk( ent ) ) + { + return; + } + Cmd_Kill_f (ent); + } + else if (Q_stricmp (cmd, "levelshot") == 0) + Cmd_LevelShot_f (ent); + else if (Q_stricmp (cmd, "where") == 0) + Cmd_Where_f (ent); + else if (Q_stricmp (cmd, "setviewpos") == 0) + Cmd_SetViewpos_f( ent ); + else if (Q_stricmp (cmd, "setobjective") == 0) + Cmd_SetObjective_f( ent ); + else if (Q_stricmp (cmd, "viewobjective") == 0) + Cmd_ViewObjective_f( ent ); + else if (Q_stricmp (cmd, "force_throw") == 0) + { + ent = G_GetSelfForPlayerCmd(); + ForceThrow( ent, qfalse ); + } + else if (Q_stricmp (cmd, "force_pull") == 0) + { + ent = G_GetSelfForPlayerCmd(); + ForceThrow( ent, qtrue ); + } + else if (Q_stricmp (cmd, "force_speed") == 0) + { + ent = G_GetSelfForPlayerCmd(); + ForceSpeed( ent ); + } + else if (Q_stricmp (cmd, "force_heal") == 0) + { + ent = G_GetSelfForPlayerCmd(); + ForceHeal( ent ); + } + else if (Q_stricmp (cmd, "force_grip") == 0) + { + ent = G_GetSelfForPlayerCmd(); + ForceGrip( ent ); + } + else if (Q_stricmp (cmd, "force_distract") == 0) + { + ent = G_GetSelfForPlayerCmd(); + ForceTelepathy( ent ); + } + else if (Q_stricmp (cmd, "force_rage") == 0) + { + ent = G_GetSelfForPlayerCmd(); + ForceRage(ent); + } + else if (Q_stricmp (cmd, "force_protect") == 0) + { + ent = G_GetSelfForPlayerCmd(); + ForceProtect(ent); + } + else if (Q_stricmp (cmd, "force_absorb") == 0) + { + ent = G_GetSelfForPlayerCmd(); + ForceAbsorb(ent); + } + else if (Q_stricmp (cmd, "force_sight") == 0) + { + ent = G_GetSelfForPlayerCmd(); + ForceSeeing(ent); + } + else if (Q_stricmp (cmd, "addsaberstyle") == 0) + { + ent = G_GetSelfForPlayerCmd(); + if ( !ent || !ent->client ) + {//wtf? + return; + } + if ( gi.argc() < 2 ) + { + gi.SendServerCommand( ent-g_entities, va("print \"usage: addsaberstyle \n\"")); + gi.SendServerCommand( ent-g_entities, va("print \"Valid styles: SS_FAST, SS_MEDIUM, SS_STRONG, SS_DESANN, SS_TAVION, SS_DUAL and SS_STAFF\n\"")); + return; + } + + int addStyle = GetIDForString( SaberStyleTable, gi.argv(1) ); + if ( addStyle > SS_NONE && addStyle < SS_STAFF ) + { + ent->client->ps.saberStylesKnown |= (1<client ) + {//wtf? + return; + } + if ( gi.argc() < 2 ) + { + gi.SendServerCommand( ent-g_entities, va("print \"usage: setsaberstyle \n\"")); + gi.SendServerCommand( ent-g_entities, va("print \"Valid styles: SS_FAST, SS_MEDIUM, SS_STRONG, SS_DESANN, SS_TAVION, SS_DUAL and SS_STAFF\n\"")); + return; + } + + int setStyle = GetIDForString( SaberStyleTable, gi.argv(1) ); + if ( setStyle > SS_NONE && setStyle < SS_STAFF ) + { + ent->client->ps.saberStylesKnown = (1<client->ps.saberAnimLevel = setStyle; + } + } + else if (Q_stricmp (cmd, "taunt") == 0) + { + ent = G_GetSelfForPlayerCmd(); +// G_Taunt( ent ); + G_SetTauntAnim( ent, TAUNT_TAUNT ); + } + else if (Q_stricmp (cmd, "bow") == 0) + { + ent = G_GetSelfForPlayerCmd(); + G_SetTauntAnim( ent, TAUNT_BOW ); + } + else if (Q_stricmp (cmd, "meditate") == 0) + { + ent = G_GetSelfForPlayerCmd(); + G_SetTauntAnim( ent, TAUNT_MEDITATE ); + } + else if (Q_stricmp (cmd, "flourish") == 0) + { + ent = G_GetSelfForPlayerCmd(); + G_SetTauntAnim( ent, TAUNT_FLOURISH ); + } + else if (Q_stricmp (cmd, "gloat") == 0) + { + ent = G_GetSelfForPlayerCmd(); + G_SetTauntAnim( ent, TAUNT_GLOAT ); + } + /* + else if (Q_stricmp (cmd, "drive") == 0) + { + if ( !CheatsOk( ent ) ) + { + return; + } + if ( gi.argc() < 2 ) + { + gi.SendServerCommand( ent-g_entities, va("print \"usage: drive \n\"")); + gi.SendServerCommand( ent-g_entities, va("print \"Vehicles will be in vehicles.cfg, try using 'speeder' for now\n\"")); + return; + } + G_DriveVehicle( ent, NULL, gi.argv(1) ); + } + */ + else if (Q_stricmp (cmd, "NPCdrive") == 0) + { + if ( !CheatsOk( ent ) ) + { + return; + } + if ( gi.argc() < 3 ) + { + gi.SendServerCommand( ent-g_entities, va("print \"usage: drive \n\"")); + gi.SendServerCommand( ent-g_entities, va("print \"Vehicles will be in vehicles.cfg, try using 'speeder' for now\n\"")); + return; + } + gentity_t *found = G_Find( NULL, FOFS(targetname), gi.argv(1) ); + if ( found && found->health > 0 && found->client ) + { + // TEMPORARY! BRING BACK LATER!!! + //G_DriveVehicle( found, NULL, gi.argv(2) ); + } + } + else if (Q_stricmp (cmd, "thereisnospoon") == 0) + G_StartMatrixEffect( ent ); + else if (Q_stricmp (cmd, "use_electrobinoculars") == 0) + Cmd_UseElectrobinoculars_f( ent ); + else if (Q_stricmp (cmd, "use_bacta") == 0) + Cmd_UseBacta_f( ent ); + else if (Q_stricmp (cmd, "use_seeker") == 0) + Cmd_UseSeeker_f( ent ); + else if (Q_stricmp (cmd, "use_lightamp_goggles") == 0) + Cmd_UseGoggles_f( ent ); + else if (Q_stricmp (cmd, "use_sentry") == 0) + Cmd_UseSentry_f( ent ); + else if (Q_stricmp (cmd, "fx") == 0) + Cmd_Fx( ent ); + else if (Q_stricmp (cmd, "invuse") == 0) + { + Cmd_UseInventory_f( ent ); + } + else if (Q_stricmp (cmd, "playmusic") == 0) + { + char *cmd2 = gi.argv(1); + if ( cmd2 ) + { + gi.SetConfigstring( CS_MUSIC, cmd2 ); + } + } + else if (Q_stricmp (cmd, "flushcam") == 0) + { + Cmd_FlushCamFile_f( ent ); + } +} diff --git a/code/game/g_combat.cpp b/code/game/g_combat.cpp new file mode 100644 index 0000000..5309c9a --- /dev/null +++ b/code/game/g_combat.cpp @@ -0,0 +1,6966 @@ +// g_combat.c + +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + + + +#include "g_local.h" +#include "b_local.h" +#include "g_functions.h" +#include "anims.h" +#include "objectives.h" +#include "../cgame/cg_local.h" +#include "wp_saber.h" +#include "g_vehicles.h" +#include "Q3_Interface.h" + +#define TURN_OFF 0x00000100 + +extern qboolean Rosh_TwinPresent( gentity_t *self ); +extern void G_CheckCharmed( gentity_t *self ); +extern qboolean Wampa_CheckDropVictim( gentity_t *self, qboolean excludeMe ); + +extern cvar_t *g_debugDamage; +extern qboolean stop_icarus; +extern cvar_t *g_dismemberment; +extern cvar_t *g_dismemberProbabilities; +extern cvar_t *g_saberRealisticCombat; +extern cvar_t *g_timescale; +extern cvar_t *d_slowmodeath; +extern gentity_t *player; + +gentity_t *g_lastClientDamaged; + +extern int killPlayerTimer; + +extern void G_VehicleStartExplosionDelay( gentity_t *self ); +extern void NPC_TempLookTarget ( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern qboolean PM_HasAnimation( gentity_t *ent, int animation ); +extern qboolean G_TeamEnemy( gentity_t *self ); +extern void CG_ChangeWeapon( int num ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); + +extern void G_SetEnemy( gentity_t *self, gentity_t *enemy ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim ); +extern qboolean PM_InOnGroundAnim ( playerState_t *ps ); +extern void G_ATSTCheckPain( gentity_t *self, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ); +extern qboolean Jedi_WaitingAmbush( gentity_t *self ); +extern qboolean G_ClearViewEntity( gentity_t *ent ); +extern qboolean PM_CrouchAnim( int anim ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean PM_InRoll( playerState_t *ps ); +extern qboolean PM_SpinningAnim( int anim ); +extern qboolean PM_RunningAnim( int anim ); +extern int PM_PowerLevelForSaberAnim( playerState_t *ps, int saberNum = 0 ); +extern qboolean PM_SaberInSpecialAttack( int anim ); +extern qboolean PM_SpinningSaberAnim( int anim ); +extern qboolean PM_FlippingAnim( int anim ); +extern qboolean PM_InSpecialJump( int anim ); +extern qboolean PM_RollingAnim( int anim ); +extern qboolean PM_InAnimForSaberMove( int anim, int saberMove ); +extern qboolean PM_SaberInStart( int move ); +extern qboolean PM_SaberInReturn( int move ); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern qboolean PM_LockedAnim( int anim ); +extern qboolean PM_KnockDownAnim( int anim ); +extern void G_SpeechEvent( gentity_t *self, int event ); +extern qboolean Rosh_BeingHealed( gentity_t *self ); + +static int G_CheckForLedge( gentity_t *self, vec3_t fallCheckDir, float checkDist ); +static int G_CheckSpecialDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc ); +static int G_PickDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc ); +static void G_TrackWeaponUsage( gentity_t *self, gentity_t *inflictor, int add, int mod ); +static qboolean G_Dismemberable( gentity_t *self, int hitLoc ); +extern gitem_t *FindItemForAmmo( ammo_t ammo ); + + +qboolean G_GetRootSurfNameWithVariant( gentity_t *ent, const char *rootSurfName, char *returnSurfName, int returnSize ); +/* +============ +AddScore + +Adds score to both the client and his team +============ +*/ +void AddScore( gentity_t *ent, int score ) { + if ( !ent->client ) { + return; + } + // no scoring during pre-match warmup + ent->client->ps.persistant[PERS_SCORE] += score; +} + +/* +================= +TossClientItems + +Toss the weapon and powerups for the killed player +================= +*/ +extern gentity_t *WP_DropThermal( gentity_t *ent ); +extern qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir ); +gentity_t *TossClientItems( gentity_t *self ) +{ + //FIXME: drop left-hand weapon, too? + gentity_t *dropped = NULL; + gitem_t *item = NULL; + int weapon; + + if ( self->client->NPC_class == CLASS_SEEKER + || self->client->NPC_class == CLASS_REMOTE + || self->client->NPC_class == CLASS_SABER_DROID + || self->client->NPC_class == CLASS_VEHICLE + || self->client->NPC_class == CLASS_ATST) + { + // these things are so small that they shouldn't bother throwing anything + return NULL; + } + + // drop the weapon if not a saber or enemy-only weapon + weapon = self->s.weapon; + if ( weapon == WP_SABER ) + { + if ( self->weaponModel[0] < 0 || (self->client->ps.saber[0].disarmable && WP_SaberLose( self, NULL )) ) + { + self->s.weapon = WP_NONE; + } + } + else if ( weapon == WP_BLASTER_PISTOL ) + {//FIXME: either drop the pistol and make the pickup only give ammo or drop ammo + } + else if ( weapon == WP_STUN_BATON + || weapon == WP_MELEE ) + {//never drop these + } + else if ( weapon > WP_SABER && weapon <= MAX_PLAYER_WEAPONS )//&& self->client->ps.ammo[ weaponData[weapon].ammoIndex ] + { + self->s.weapon = WP_NONE; + + if ( weapon == WP_THERMAL && self->client->ps.torsoAnim == BOTH_ATTACK10 ) + {//we were getting ready to throw the thermal, drop it! + self->client->ps.weaponChargeTime = level.time - FRAMETIME;//so it just kind of drops it + dropped = WP_DropThermal( self ); + } + else + {// find the item type for this weapon + item = FindItemForWeapon( (weapon_t) weapon ); + } + if ( item && !dropped ) + { + // spawn the item + dropped = Drop_Item( self, item, 0, qtrue ); + //TEST: dropped items never go away + dropped->e_ThinkFunc = thinkF_NULL; + dropped->nextthink = -1; + + if ( !self->s.number ) + {//player's dropped items never go away + //dropped->e_ThinkFunc = thinkF_NULL; + //dropped->nextthink = -1; + dropped->count = 0;//no ammo + } + else + {//FIXME: base this on the NPC's actual amount of ammo he's used up... + switch ( weapon ) + { + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: + dropped->count = 20; + break; + case WP_BLASTER: + dropped->count = 15; + break; + case WP_DISRUPTOR: + dropped->count = 20; + break; + case WP_BOWCASTER: + dropped->count = 5; + break; + case WP_REPEATER: + dropped->count = 20; + break; + case WP_DEMP2: + dropped->count = 10; + break; + case WP_FLECHETTE: + dropped->count = 30; + break; + case WP_ROCKET_LAUNCHER: + dropped->count = 3; + break; + case WP_CONCUSSION: + dropped->count = 200;//12; + break; + case WP_THERMAL: + dropped->count = 4; + break; + case WP_TRIP_MINE: + dropped->count = 3; + break; + case WP_DET_PACK: + dropped->count = 1; + break; + case WP_STUN_BATON: + dropped->count = 20; + break; + default: + dropped->count = 0; + break; + } + } + // well, dropped weapons are G2 models, so they have to be initialised if they want to draw..give us a radius so we don't get prematurely culled + if ( weapon != WP_THERMAL + && weapon != WP_TRIP_MINE + && weapon != WP_DET_PACK ) + { + gi.G2API_InitGhoul2Model( dropped->ghoul2, item->world_model, G_ModelIndex( item->world_model )); + dropped->s.radius = 10; + } + } + } +// else if (( self->client->NPC_class == CLASS_SENTRY ) || ( self->client->NPC_class == CLASS_PROBE )) // Looks dumb, Steve told us to take it out. +// { +// item = FindItemForAmmo( AMMO_BLASTER ); +// Drop_Item( self, item, 0, qtrue ); +// } + else if ( self->client->NPC_class == CLASS_MARK1 ) + { + + if (Q_irand( 1, 2 )>1) + { + item = FindItemForAmmo( AMMO_METAL_BOLTS ); + } + else + { + item = FindItemForAmmo( AMMO_BLASTER ); + } + Drop_Item( self, item, 0, qtrue ); + } + else if ( self->client->NPC_class == CLASS_MARK2 ) + { + + if (Q_irand( 1, 2 )>1) + { + item = FindItemForAmmo( AMMO_METAL_BOLTS ); + } + else + { + item = FindItemForAmmo( AMMO_POWERCELL ); + } + Drop_Item( self, item, 0, qtrue ); + } + + return dropped;//NOTE: presumes only drop one thing +} + +void G_DropKey( gentity_t *self ) +{//drop whatever security key I was holding + gitem_t *item = NULL; + if ( !Q_stricmp( "goodie", self->message ) ) + { + item = FindItemForInventory( INV_GOODIE_KEY ); + } + else + { + item = FindItemForInventory( INV_SECURITY_KEY ); + } + gentity_t *dropped = Drop_Item( self, item, 0, qtrue ); + //Don't throw the key + VectorClear( dropped->s.pos.trDelta ); + dropped->message = self->message; + self->message = NULL; +} + +void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) +{ + if(self->target) + G_UseTargets(self, attacker); + + //remove my script_targetname + G_FreeEntity( self ); +} +/* +================== +ExplodeDeath +================== +*/ + +//FIXME: all hacked up... + +//void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke ); +void ExplodeDeath( gentity_t *self ) +{ +// gentity_t *tent; + vec3_t forward; + + self->takedamage = qfalse;//stop chain reaction runaway loops + + self->s.loopSound = 0; + + VectorCopy( self->currentOrigin, self->s.pos.trBase ); + +// tent = G_TempEntity( self->s.origin, EV_FX_EXPLOSION ); + AngleVectors(self->s.angles, forward, NULL, NULL); // FIXME: letting effect always shoot up? Might be ok. + + if ( self->fxID > 0 ) + { + G_PlayEffect( self->fxID, self->currentOrigin, forward ); + } +// else +// { +// CG_SurfaceExplosion( self->currentOrigin, forward, 20.0f, 12.0f, ((self->spawnflags&4)==qfalse) ); //FIXME: This needs to be consistent to all exploders! +// G_Sound(self, self->sounds ); +// } + + if(self->splashDamage > 0 && self->splashRadius > 0) + { + gentity_t *attacker = self; + if ( self->owner ) + { + attacker = self->owner; + } + G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, + attacker, MOD_UNKNOWN ); + } + + ObjectDie( self, self, self, 20, 0 ); +} + +void ExplodeDeath_Wait( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc ) +{ + self->e_DieFunc = dieF_NULL; + self->nextthink = level.time + Q_irand(100, 500); + self->e_ThinkFunc = thinkF_ExplodeDeath; +} + +void ExplodeDeath( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc ) +{ + self->currentOrigin[2] += 16; // me bad for hacking this. should either do it in the effect file or make a custom explode death?? + ExplodeDeath( self ); +} + +void GoExplodeDeath( gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + self->targetname = NULL; //Make sure this entity cannot be told to explode again (recursive death fix) + + ExplodeDeath( self ); +} + +qboolean G_ActivateBehavior (gentity_t *self, int bset ); +void G_CheckVictoryScript(gentity_t *self) +{ + if ( !G_ActivateBehavior( self, BSET_VICTORY ) ) + { + if ( self->NPC && self->s.weapon == WP_SABER ) + {//Jedi taunt from within their AI + self->NPC->blockedSpeechDebounceTime = 0;//get them ready to taunt + return; + } + if ( self->client && self->client->NPC_class == CLASS_GALAKMECH ) + { + self->wait = 1; + TIMER_Set( self, "gloatTime", Q_irand( 5000, 8000 ) ); + self->NPC->blockedSpeechDebounceTime = 0;//get him ready to taunt + return; + } + //FIXME: any way to not say this *right away*? Wait for victim's death anim/scream to finish? + if ( self->NPC && self->NPC->group && self->NPC->group->commander && self->NPC->group->commander->NPC && self->NPC->group->commander->NPC->rank > self->NPC->rank && !Q_irand( 0, 2 ) ) + {//sometimes have the group commander speak instead + self->NPC->group->commander->NPC->greetingDebounceTime = level.time + Q_irand( 2000, 5000 ); + //G_AddVoiceEvent( self->NPC->group->commander, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 ); + } + else if ( self->NPC ) + { + self->NPC->greetingDebounceTime = level.time + Q_irand( 2000, 5000 ); + //G_AddVoiceEvent( self, Q_irand(EV_VICTORY1, EV_VICTORY3), 2000 ); + } + } +} + +qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 ) +{ + if ( ent1->s.number < MAX_CLIENTS + && ent1->client + && ent1->client->playerTeam == TEAM_FREE ) + {//evil player *has* no allies + return qfalse; + } + if ( ent2->s.number < MAX_CLIENTS + && ent2->client + && ent2->client->playerTeam == TEAM_FREE ) + {//evil player *has* no allies + return qfalse; + } + if ( !ent1->client || !ent2->client ) + { + if ( ent1->noDamageTeam ) + { + if ( ent2->client && ent2->client->playerTeam == ent1->noDamageTeam ) + { + return qtrue; + } + else if ( ent2->noDamageTeam == ent1->noDamageTeam ) + { + if ( ent1->splashDamage && ent2->splashDamage && Q_stricmp("ambient_etherian_fliers", ent1->classname) != 0 ) + {//Barrels, exploding breakables and mines will blow each other up + return qfalse; + } + else + { + return qtrue; + } + } + } + return qfalse; + } + + // shouldn't need this anymore, there were problems with certain droids, but now they have been labeled TEAM_ENEMY so this isn't needed +// if ((( ent1->client->playerTeam == TEAM_IMPERIAL ) && ( ent1->client->playerTeam == TEAM_BOTS )) || +// (( ent1->client->playerTeam == TEAM_BOTS ) && ( ent1->client->playerTeam == TEAM_IMPERIAL ))) +// { +// return qtrue; +// } + + return ( ent1->client->playerTeam == ent2->client->playerTeam ); +} + + +/* +------------------------- +G_AlertTeam +------------------------- +*/ + +void G_AlertTeam( gentity_t *victim, gentity_t *attacker, float radius, float soundDist ) +{ + gentity_t *radiusEnts[ 128 ]; + vec3_t mins, maxs; + int numEnts; + float distSq, sndDistSq = (soundDist*soundDist); + + if ( attacker == NULL || attacker->client == NULL ) + return; + + //Setup the bbox to search in + for ( int i = 0; i < 3; i++ ) + { + mins[i] = victim->currentOrigin[i] - radius; + maxs[i] = victim->currentOrigin[i] + radius; + } + + //Get the number of entities in a given space + numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, 128 ); + + //Cull this list + for ( i = 0; i < numEnts; i++ ) + { + //Validate clients + if ( radiusEnts[i]->client == NULL ) + continue; + + //only want NPCs + if ( radiusEnts[i]->NPC == NULL ) + continue; + + //Don't bother if they're ignoring enemies + if ( radiusEnts[i]->svFlags & SVF_IGNORE_ENEMIES ) + continue; + + //This NPC specifically flagged to ignore alerts + if ( radiusEnts[i]->NPC->scriptFlags & SCF_IGNORE_ALERTS ) + continue; + + //This NPC specifically flagged to ignore alerts + if ( !(radiusEnts[i]->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + continue; + + //this ent does not participate in group AI + if ( (radiusEnts[i]->NPC->scriptFlags&SCF_NO_GROUPS) ) + continue; + + //Skip the requested avoid radiusEnts[i] if present + if ( radiusEnts[i] == victim ) + continue; + + //Skip the attacker + if ( radiusEnts[i] == attacker ) + continue; + + //Must be on the same team + if ( radiusEnts[i]->client->playerTeam != victim->client->playerTeam ) + continue; + + //Must be alive + if ( radiusEnts[i]->health <= 0 ) + continue; + + if ( radiusEnts[i]->enemy == NULL ) + {//only do this if they're not already mad at someone + distSq = DistanceSquared( radiusEnts[i]->currentOrigin, victim->currentOrigin ); + if ( distSq > 16384 /*128 squared*/ && !gi.inPVS( victim->currentOrigin, radiusEnts[i]->currentOrigin ) ) + {//not even potentially visible/hearable + continue; + } + //NOTE: this allows sound alerts to still go through doors/PVS if the teammate is within 128 of the victim... + if ( soundDist <= 0 || distSq > sndDistSq ) + {//out of sound range + if ( !InFOV( victim, radiusEnts[i], radiusEnts[i]->NPC->stats.hfov, radiusEnts[i]->NPC->stats.vfov ) + || !NPC_ClearLOS( radiusEnts[i], victim->currentOrigin ) ) + {//out of FOV or no LOS + continue; + } + } + + //FIXME: This can have a nasty cascading effect if setup wrong... + G_SetEnemy( radiusEnts[i], attacker ); + } + } +} + +/* +------------------------- +G_DeathAlert +------------------------- +*/ + +#define DEATH_ALERT_RADIUS 512 +#define DEATH_ALERT_SOUND_RADIUS 512 + +void G_DeathAlert( gentity_t *victim, gentity_t *attacker ) +{//FIXME: with all the other alert stuff, do we really need this? + G_AlertTeam( victim, attacker, DEATH_ALERT_RADIUS, DEATH_ALERT_SOUND_RADIUS ); +} + +/* +---------------------------------------- +DeathFX + +Applies appropriate special effects that occur while the entity is dying +Not to be confused with NPC_RemoveBodyEffects (NPC.cpp), which only applies effect when removing the body +---------------------------------------- +*/ + +void DeathFX( gentity_t *ent ) +{ + if ( !ent || !ent->client ) + return; +/* + switch( ent->client->playerTeam ) + { + case TEAM_BOTS: + if (!Q_stricmp( ent->NPC_type, "mouse" )) + { + vec3_t effectPos; + VectorCopy( ent->currentOrigin, effectPos ); + effectPos[2] -= 20; + + G_PlayEffect( "mouseexplosion1", effectPos ); + G_PlayEffect( "smaller_chunks", effectPos ); + + } + else if (!Q_stricmp( ent->NPC_type, "probe" )) + { + vec3_t effectPos; + VectorCopy( ent->currentOrigin, effectPos ); + effectPos[2] += 50; + + G_PlayEffect( "probeexplosion1", effectPos ); + G_PlayEffect( "small_chunks", effectPos ); + } + else + { + vec3_t effectPos; + VectorCopy( ent->currentOrigin, effectPos ); + effectPos[2] -= 15; + G_PlayEffect( "droidexplosion1", effectPos ); + G_PlayEffect( "small_chunks", effectPos ); + } + + break; + + default: + break; + } +*/ + // team no longer indicates species/race. NPC_class should be used to identify certain npc types + vec3_t effectPos, right; + switch(ent->client->NPC_class) + { + case CLASS_MOUSE: + VectorCopy( ent->currentOrigin, effectPos ); + effectPos[2] -= 20; + G_PlayEffect( "env/small_explode", effectPos ); + G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mouse/misc/death1" ); + break; + + case CLASS_PROBE: + VectorCopy( ent->currentOrigin, effectPos ); + effectPos[2] += 50; + G_PlayEffect( "explosions/probeexplosion1", effectPos ); + break; + + case CLASS_ATST: + AngleVectors( ent->currentAngles, NULL, right, NULL ); + VectorMA( ent->currentOrigin, 20, right, effectPos ); + effectPos[2] += 180; + G_PlayEffect( "explosions/droidexplosion1", effectPos ); + VectorMA( effectPos, -40, right, effectPos ); + G_PlayEffect( "explosions/droidexplosion1", effectPos ); + break; + + case CLASS_SEEKER: + case CLASS_REMOTE: + G_PlayEffect( "env/small_explode", ent->currentOrigin ); + break; + + case CLASS_GONK: + VectorCopy( ent->currentOrigin, effectPos ); + effectPos[2] -= 5; +// statusTextIndex = Q_irand( IGT_RESISTANCEISFUTILE, IGT_NAMEIS8OF12 ); + G_SoundOnEnt( ent, CHAN_AUTO, va("sound/chars/gonk/misc/death%d.wav",Q_irand( 1, 3 )) ); + G_PlayEffect( "env/med_explode", effectPos ); + break; + + // should list all remaining droids here, hope I didn't miss any + case CLASS_R2D2: + VectorCopy( ent->currentOrigin, effectPos ); + effectPos[2] -= 10; + G_PlayEffect( "env/med_explode", effectPos ); + G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark2/misc/mark2_explo" ); + break; + + case CLASS_PROTOCOL://?? + case CLASS_R5D2: + VectorCopy( ent->currentOrigin, effectPos ); + effectPos[2] -= 10; + G_PlayEffect( "env/med_explode", effectPos ); + G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark2/misc/mark2_explo" ); + break; + + case CLASS_MARK2: + VectorCopy( ent->currentOrigin, effectPos ); + effectPos[2] -= 15; + G_PlayEffect( "explosions/droidexplosion1", effectPos ); + G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark2/misc/mark2_explo" ); + break; + + case CLASS_INTERROGATOR: + VectorCopy( ent->currentOrigin, effectPos ); + effectPos[2] -= 15; + G_PlayEffect( "explosions/droidexplosion1", effectPos ); + G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/interrogator/misc/int_droid_explo" ); + break; + + case CLASS_MARK1: + AngleVectors( ent->currentAngles, NULL, right, NULL ); + VectorMA( ent->currentOrigin, 10, right, effectPos ); + effectPos[2] -= 15; + G_PlayEffect( "explosions/droidexplosion1", effectPos ); + VectorMA( effectPos, -20, right, effectPos ); + G_PlayEffect( "explosions/droidexplosion1", effectPos ); + VectorMA( effectPos, -20, right, effectPos ); + G_PlayEffect( "explosions/droidexplosion1", effectPos ); + G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/mark1/misc/mark1_explo" ); + break; + + case CLASS_SENTRY: + G_SoundOnEnt( ent, CHAN_AUTO, "sound/chars/sentry/misc/sentry_explo" ); + VectorCopy( ent->currentOrigin, effectPos ); + G_PlayEffect( "env/med_explode", effectPos ); + break; + + default: + break; + + } + +} + +void G_SetMissionStatusText( gentity_t *attacker, int mod ) +{ + if ( statusTextIndex >= 0 ) + { + return; + } + + if ( mod == MOD_FALLING ) + {//fell to your death + statusTextIndex = STAT_WATCHYOURSTEP; + } + else if ( mod == MOD_CRUSH ) + {//crushed + statusTextIndex = STAT_JUDGEMENTMUCHDESIRED; + } + // borg no longer exist +// else if ( attacker && attacker->client && attacker->client->playerTeam == TEAM_BORG ) +// {//assimilated +// statusTextIndex = Q_irand( IGT_RESISTANCEISFUTILE, IGT_NAMEIS8OF12 ); +// } + else if ( attacker && Q_stricmp( "trigger_hurt", attacker->classname ) == 0 ) + {//Killed by something that should have been clearly dangerous +// statusTextIndex = Q_irand( IGT_JUDGEMENTDESIRED, IGT_JUDGEMENTMUCHDESIRED ); + statusTextIndex = STAT_JUDGEMENTMUCHDESIRED; + } + else if ( attacker && attacker->s.number != 0 && attacker->client && attacker->client->playerTeam == TEAM_PLAYER ) + {//killed by a teammate + statusTextIndex = STAT_INSUBORDINATION; + } + /* + else if () + {//killed a teammate- note: handled above + if ( Q_irand( 0, 1 ) ) + { + statusTextIndex = IGT_YOUCAUSEDDEATHOFTEAMMATE; + } + else + { + statusTextIndex = IGT_KILLEDANINNOCENTCREWMAN; + } + } + else + { + //This next block is not contiguous + IGT_INADEQUATE, + IGT_RESPONSETIME, + IGT_SHOOTINRANGE, + IGT_TRYAGAIN, + IGT_TRAINONHOLODECK, + IGT_WHATCOLORSHIRT, + IGT_NOTIMPRESS7OF9, + IGT_NEELIXFAREDBETTER, + IGT_THATMUSTHURT, + IGT_TUVOKDISAPPOINTED, + IGT_STARFLEETNOTIFYFAMILY, + IGT_TEAMMATESWILLMISSYOU, + IGT_LESSTHANEXEMPLARY, + IGT_SACRIFICEDFORTHEWHOLE, + IGT_NOTLIVELONGANDPROSPER, + IGT_BETTERUSEOFSIMULATIONS, + } + */ + + /* + //These can be set by designers + IGT_INSUBORDINATION, + IGT_YOUCAUSEDDEATHOFTEAMMATE, + IGT_DIDNTPROTECTTECH, + IGT_DIDNTPROTECT7OF9, + IGT_NOTSTEALTHYENOUGH, + IGT_STEALTHTACTICSNECESSARY, + */ +} + +void G_MakeTeamVulnerable( void ) +{ + int i, newhealth; + gentity_t *ent; + gentity_t *self = &g_entities[0]; + if ( !self->client ) + { + return; + } + +// for ( i = 0; i < globals.num_entities ; i++, ent++) + for ( i = 0; i < globals.num_entities ; i++) + { + if(!PInUse(i)) + continue; +// if ( !ent->inuse ) +// { +// continue; +// } +// if ( !ent ) +// { +// continue; +// } + ent=&g_entities[i]; + if ( !ent->client ) + { + continue; + } + if ( ent->client->playerTeam != TEAM_PLAYER ) + { + continue; + } + if ( !(ent->flags&FL_UNDYING) ) + { + continue; + } + ent->flags &= ~FL_UNDYING; + newhealth = Q_irand( 5, 40 ); + if ( ent->health > newhealth ) + { + ent->health = newhealth; + } + } +} + +void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 ) +{ + //FIXME: allow them to specify a different focal entity or point? + if ( g_timescale->value != 1.0 || in_camera ) + {//already in some slow-mo mode or in_camera + return; + } + + gentity_t *matrix = G_Spawn(); + if ( matrix ) + { + G_SetOrigin( matrix, ent->currentOrigin ); + gi.linkentity( matrix ); + matrix->s.otherEntityNum = ent->s.number; + matrix->e_clThinkFunc = clThinkF_CG_MatrixEffect; + matrix->s.eType = ET_THINKER; + matrix->svFlags |= SVF_BROADCAST;// Broadcast to all clients + matrix->s.time = level.time; + matrix->s.eventParm = length; + //now the cgame decides when to remove us... in case the framerate chugs so severely that it never finishes the effect before it removes itself! + //matrix->e_ThinkFunc = thinkF_G_FreeEntity; + //matrix->nextthink = level.time + length + 500; + matrix->s.boltInfo = meFlags; + matrix->s.time2 = spinTime; + matrix->s.angles2[0] = timeScale; + } +} + +qboolean G_JediInRoom( vec3_t from ) +{ + gentity_t *ent; + int i; +// for ( i = 1, ent = &g_entities[1]; i < globals.num_entities; i++, ent++ ) + for ( i = 1; i < globals.num_entities; i++) + { + if(!PInUse(i)) + continue; +// if ( !ent->inuse ) +// { +// continue; +// } +// if ( !ent ) +// { +// continue; +// } + ent = &g_entities[i]; + if ( !ent->NPC ) + { + continue; + } + if ( ent->health <= 0 ) + { + continue; + } + if ( ent->s.eFlags&EF_NODRAW ) + { + continue; + } + if ( ent->s.weapon != WP_SABER ) + { + continue; + } + if ( !gi.inPVS( ent->currentOrigin, from ) ) + { + continue; + } + return qtrue; + } + return qfalse; +} + +qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod, saberType_t saberType ) +{ + qboolean dismember = qfalse; + + *hitLoc = HL_NONE; + + if ( !surfName || !surfName[0] ) + { + return qfalse; + } + + if( !ent->client ) + { + return qfalse; + } + + if ( ent->client + && ( ent->client->NPC_class == CLASS_R2D2 + || ent->client->NPC_class == CLASS_R2D2 + || ent->client->NPC_class == CLASS_GONK + || ent->client->NPC_class == CLASS_MOUSE + || ent->client->NPC_class == CLASS_SENTRY + || ent->client->NPC_class == CLASS_INTERROGATOR + || ent->client->NPC_class == CLASS_SENTRY + || ent->client->NPC_class == CLASS_PROBE ) ) + {//we don't care about per-surface hit-locations or dismemberment for these guys + return qfalse; + } + + if ( ent->client && (ent->client->NPC_class == CLASS_ATST) ) + { + //FIXME: almost impossible to hit these... perhaps we should + // check for splashDamage and do radius damage to these parts? + // Or, if we ever get bbox G2 traces, that may fix it, too + if (!Q_stricmp("head_light_blaster_cann",surfName)) + { + *hitLoc = HL_ARM_LT; + } + else if (!Q_stricmp("head_concussion_charger",surfName)) + { + *hitLoc = HL_ARM_RT; + } + return(qfalse); + } + else if ( ent->client && (ent->client->NPC_class == CLASS_MARK1) ) + { + if (!Q_stricmp("l_arm",surfName)) + { + *hitLoc = HL_ARM_LT; + } + else if (!Q_stricmp("r_arm",surfName)) + { + *hitLoc = HL_ARM_RT; + } + else if (!Q_stricmp("torso_front",surfName)) + { + *hitLoc = HL_CHEST; + } + else if (!Q_stricmp("torso_tube1",surfName)) + { + *hitLoc = HL_GENERIC1; + } + else if (!Q_stricmp("torso_tube2",surfName)) + { + *hitLoc = HL_GENERIC2; + } + else if (!Q_stricmp("torso_tube3",surfName)) + { + *hitLoc = HL_GENERIC3; + } + else if (!Q_stricmp("torso_tube4",surfName)) + { + *hitLoc = HL_GENERIC4; + } + else if (!Q_stricmp("torso_tube5",surfName)) + { + *hitLoc = HL_GENERIC5; + } + else if (!Q_stricmp("torso_tube6",surfName)) + { + *hitLoc = HL_GENERIC6; + } + return(qfalse); + } + else if ( ent->client && (ent->client->NPC_class == CLASS_MARK2) ) + { + if (!Q_stricmp("torso_canister1",surfName)) + { + *hitLoc = HL_GENERIC1; + } + else if (!Q_stricmp("torso_canister2",surfName)) + { + *hitLoc = HL_GENERIC2; + } + else if (!Q_stricmp("torso_canister3",surfName)) + { + *hitLoc = HL_GENERIC3; + } + return(qfalse); + } + else if ( ent->client && (ent->client->NPC_class == CLASS_GALAKMECH) ) + { + if (!Q_stricmp("torso_antenna",surfName)||!Q_stricmp("torso_antenna_base",surfName)) + { + *hitLoc = HL_GENERIC1; + } + else if (!Q_stricmp("torso_shield",surfName)) + { + *hitLoc = HL_GENERIC2; + } + else + { + *hitLoc = HL_CHEST; + } + return(qfalse); + } + + + //FIXME: check the hitLoc and hitDir against the cap tag for the place + //where the split will be- if the hit dir is roughly perpendicular to + //the direction of the cap, then the split is allowed, otherwise we + //hit it at the wrong angle and should not dismember... + int actualTime = (cg.time?cg.time:level.time); + if ( !Q_stricmpn( "hips", surfName, 4 ) ) + {//FIXME: test properly for legs + *hitLoc = HL_WAIST; + if ( ent->client != NULL && ent->ghoul2.size() ) + { + mdxaBone_t boltMatrix; + vec3_t tagOrg, angles; + + VectorSet( angles, 0, ent->currentAngles[YAW], 0 ); + if (ent->kneeLBolt>=0) + { + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->kneeLBolt, + &boltMatrix, angles, ent->currentOrigin, + actualTime, NULL, ent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg ); + if ( DistanceSquared( point, tagOrg ) < 100 ) + {//actually hit the knee + *hitLoc = HL_LEG_LT; + } + } + if (*hitLoc == HL_WAIST) + { + if (ent->kneeRBolt>=0) + { + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->kneeRBolt, + &boltMatrix, angles, ent->currentOrigin, + actualTime, NULL, ent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg ); + if ( DistanceSquared( point, tagOrg ) < 100 ) + {//actually hit the knee + *hitLoc = HL_LEG_RT; + } + } + } + } + } + else if ( !Q_stricmpn( "torso", surfName, 5 ) ) + { + if ( !ent->client ) + { + *hitLoc = HL_CHEST; + } + else + { + vec3_t t_fwd, t_rt, t_up, dirToImpact; + float frontSide, rightSide, upSide; + AngleVectors( ent->client->renderInfo.torsoAngles, t_fwd, t_rt, t_up ); + VectorSubtract( point, ent->client->renderInfo.torsoPoint, dirToImpact ); + frontSide = DotProduct( t_fwd, dirToImpact ); + rightSide = DotProduct( t_rt, dirToImpact ); + upSide = DotProduct( t_up, dirToImpact ); + if ( upSide < -10 ) + {//hit at waist + *hitLoc = HL_WAIST; + } + else + {//hit on upper torso + if ( rightSide > 4 ) + { + *hitLoc = HL_ARM_RT; + } + else if ( rightSide < -4 ) + { + *hitLoc = HL_ARM_LT; + } + else if ( rightSide > 2 ) + { + if ( frontSide > 0 ) + { + *hitLoc = HL_CHEST_RT; + } + else + { + *hitLoc = HL_BACK_RT; + } + } + else if ( rightSide < -2 ) + { + if ( frontSide > 0 ) + { + *hitLoc = HL_CHEST_LT; + } + else + { + *hitLoc = HL_BACK_LT; + } + } + else if ( upSide > -3 && mod == MOD_SABER ) + { + *hitLoc = HL_HEAD; + } + else if ( frontSide > 0 ) + { + *hitLoc = HL_CHEST; + } + else + { + *hitLoc = HL_BACK; + } + } + } + } + else if ( !Q_stricmpn( "head", surfName, 4 ) ) + { + *hitLoc = HL_HEAD; + } + else if ( !Q_stricmpn( "r_arm", surfName, 5 ) ) + { + *hitLoc = HL_ARM_RT; + if ( ent->client != NULL && ent->ghoul2.size() ) + { + mdxaBone_t boltMatrix; + vec3_t tagOrg, angles; + + VectorSet( angles, 0, ent->currentAngles[YAW], 0 ); + if (ent->handRBolt>=0) + { + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->handRBolt, + &boltMatrix, angles, ent->currentOrigin, + actualTime, NULL, ent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg ); + if ( DistanceSquared( point, tagOrg ) < 256 ) + {//actually hit the hand + *hitLoc = HL_HAND_RT; + } + } + } + } + else if ( !Q_stricmpn( "l_arm", surfName, 5 ) ) + { + *hitLoc = HL_ARM_LT; + if ( ent->client != NULL && ent->ghoul2.size() ) + { + mdxaBone_t boltMatrix; + vec3_t tagOrg, angles; + + VectorSet( angles, 0, ent->currentAngles[YAW], 0 ); + if (ent->handLBolt>=0) + { + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->handLBolt, + &boltMatrix, angles, ent->currentOrigin, + actualTime, NULL, ent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg ); + if ( DistanceSquared( point, tagOrg ) < 256 ) + {//actually hit the hand + *hitLoc = HL_HAND_LT; + } + } + } + } + else if ( !Q_stricmpn( "r_leg", surfName, 5 ) ) + { + *hitLoc = HL_LEG_RT; + if ( ent->client != NULL && ent->ghoul2.size() ) + { + mdxaBone_t boltMatrix; + vec3_t tagOrg, angles; + + VectorSet( angles, 0, ent->currentAngles[YAW], 0 ); + if (ent->footRBolt>=0) + { + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->footRBolt, + &boltMatrix, angles, ent->currentOrigin, + actualTime, NULL, ent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg ); + if ( DistanceSquared( point, tagOrg ) < 100 ) + {//actually hit the foot + *hitLoc = HL_FOOT_RT; + } + } + } + } + else if ( !Q_stricmpn( "l_leg", surfName, 5 ) ) + { + *hitLoc = HL_LEG_LT; + if ( ent->client != NULL && ent->ghoul2.size() ) + { + mdxaBone_t boltMatrix; + vec3_t tagOrg, angles; + + VectorSet( angles, 0, ent->currentAngles[YAW], 0 ); + if (ent->footLBolt>=0) + { + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, ent->footLBolt, + &boltMatrix, angles, ent->currentOrigin, + actualTime, NULL, ent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg ); + if ( DistanceSquared( point, tagOrg ) < 100 ) + {//actually hit the foot + *hitLoc = HL_FOOT_LT; + } + } + } + } + else if ( mod == MOD_SABER && WP_BreakSaber( ent, surfName, saberType ) ) + {//saber hit and broken + *hitLoc = HL_HAND_RT; + } + else if ( !Q_stricmpn( "r_hand", surfName, 6 ) || !Q_stricmpn( "w_", surfName, 2 ) ) + {//right hand or weapon + //FIXME: if hit weapon, chance of breaking saber (if sabers.cfg entry shows it as breakable) + // if breaks, remove saber and replace with the 2 replacement sabers (preserve color, length, etc.) + *hitLoc = HL_HAND_RT; + } + else if ( !Q_stricmpn( "l_hand", surfName, 6 ) ) + { + *hitLoc = HL_HAND_LT; + } + else if ( ent->client && ent->client->ps.powerups[PW_GALAK_SHIELD] && !Q_stricmp( "force_shield", surfName ) ) + { + *hitLoc = HL_GENERIC2; + + } +#ifdef _DEBUG + else + { + Com_Printf( "ERROR: surface %s does not belong to any hitLocation!!!\n", surfName ); + } +#endif //_DEBUG + + if ( g_saberRealisticCombat->integer > 1 ) + { + dismember = qtrue; + } + else if ( ent->client && ent->client->NPC_class == CLASS_PROTOCOL ) + { + dismember = qtrue; + } + else if ( ent->client && ent->client->NPC_class == CLASS_ASSASSIN_DROID ) + { + dismember = qtrue; + } + else if ( ent->client && ent->client->NPC_class == CLASS_SABER_DROID ) + { + dismember = qtrue; + } + else if ( g_dismemberment->integer >= 11381138 || !ent->client->dismembered ) + { + if ( dir && (dir[0] || dir[1] || dir[2]) && + bladeDir && (bladeDir[0] || bladeDir[1] || bladeDir[2]) ) + {//we care about direction (presumably for dismemberment) + if ( g_dismemberProbabilities->value<=0.0f||G_Dismemberable( ent, *hitLoc ) ) + {//either we don't care about probabilties or the probability let us continue + char *tagName = NULL; + float aoa = 0.5f; + //dir must be roughly perpendicular to the hitLoc's cap bolt + switch ( *hitLoc ) + { + case HL_LEG_RT: + tagName = "*hips_cap_r_leg"; + break; + case HL_LEG_LT: + tagName = "*hips_cap_l_leg"; + break; + case HL_WAIST: + tagName = "*hips_cap_torso"; + aoa = 0.25f; + break; + case HL_CHEST_RT: + case HL_ARM_RT: + case HL_BACK_LT: + tagName = "*torso_cap_r_arm"; + break; + case HL_CHEST_LT: + case HL_ARM_LT: + case HL_BACK_RT: + tagName = "*torso_cap_l_arm"; + break; + case HL_HAND_RT: + tagName = "*r_arm_cap_r_hand"; + break; + case HL_HAND_LT: + tagName = "*l_arm_cap_l_hand"; + break; + case HL_HEAD: + tagName = "*torso_cap_head"; + aoa = 0.25f; + break; + case HL_CHEST: + case HL_BACK: + case HL_FOOT_RT: + case HL_FOOT_LT: + default: + //no dismemberment possible with these, so no checks needed + break; + } + if ( tagName ) + { + int tagBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], tagName ); + if ( tagBolt != -1 ) + { + mdxaBone_t boltMatrix; + vec3_t tagOrg, tagDir, angles; + VectorSet( angles, 0, ent->currentAngles[YAW], 0 ); + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, tagBolt, + &boltMatrix, angles, ent->currentOrigin, + actualTime, NULL, ent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, tagOrg ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, tagDir ); + if ( DistanceSquared( point, tagOrg ) < 256 ) + {//hit close + float dot = DotProduct( dir, tagDir ); + if ( dot < aoa && dot > -aoa ) + {//hit roughly perpendicular + dot = DotProduct( bladeDir, tagDir ); + if ( dot < aoa && dot > -aoa ) + {//blade was roughly perpendicular + dismember = qtrue; + } + } + } + } + } + } + } + } + return dismember; +} + +int G_GetHitLocation ( gentity_t *target, const vec3_t ppoint ) +{ + vec3_t point, point_dir; + vec3_t forward, right, up; + vec3_t tangles, tcenter; + float tradius; + float udot, fdot, rdot; + int Vertical, Forward, Lateral; + int HitLoc; + +//get target forward, right and up + if(target->client) + {//ignore player's pitch and roll + VectorSet(tangles, 0, target->currentAngles[YAW], 0); + } + + AngleVectors(tangles, forward, right, up); + +//get center of target + VectorAdd(target->absmin, target->absmax, tcenter); + VectorScale(tcenter, 0.5, tcenter); + +//get radius width of target + tradius = (fabs(target->maxs[0]) + fabs(target->maxs[1]) + fabs(target->mins[0]) + fabs(target->mins[1]))/4; + +//get impact point + if(ppoint && !VectorCompare(ppoint, vec3_origin)) + { + VectorCopy(ppoint, point); + } + else + { + return HL_NONE; + } + +/* +//get impact dir + if(pdir && !VectorCompare(pdir, vec3_origin)) + { + VectorCopy(pdir, dir); + } + else + { + return; + } + +//put point at controlled distance from center + VectorSubtract(point, tcenter, tempvec); + tempvec[2] = 0; + hdist = VectorLength(tempvec); + + VectorMA(point, hdist - tradius, dir, point); + //now a point on the surface of a cylinder with a radius of tradius +*/ + VectorSubtract(point, tcenter, point_dir); + VectorNormalize(point_dir); + + //Get bottom to top (Vertical) position index + udot = DotProduct(up, point_dir); + if(udot>.800) + Vertical = 4; + else if(udot>.400) + Vertical = 3; + else if(udot>-.333) + Vertical = 2; + else if(udot>-.666) + Vertical = 1; + else + Vertical = 0; + + //Get back to front (Forward) position index + fdot = DotProduct(forward, point_dir); + if(fdot>.666) + Forward = 4; + else if(fdot>.333) + Forward = 3; + else if(fdot>-.333) + Forward = 2; + else if(fdot>-.666) + Forward = 1; + else + Forward = 0; + + //Get left to right (Lateral) position index + rdot = DotProduct(right, point_dir); + if(rdot>.666) + Lateral = 4; + else if(rdot>.333) + Lateral = 3; + else if(rdot>-.333) + Lateral = 2; + else if(rdot>-.666) + Lateral = 1; + else + Lateral = 0; + + HitLoc = Vertical * 25 + Forward * 5 + Lateral; + + if(HitLoc <= 10) + {//feet + if ( rdot > 0 ) + { + return HL_FOOT_RT; + } + else + { + return HL_FOOT_LT; + } + } + else if(HitLoc <= 50) + {//legs + if ( rdot > 0 ) + { + return HL_LEG_RT; + } + else + { + return HL_LEG_LT; + } + } + else if ( HitLoc == 56||HitLoc == 60||HitLoc == 61||HitLoc == 65||HitLoc == 66||HitLoc == 70 ) + {//hands + if ( rdot > 0 ) + { + return HL_HAND_RT; + } + else + { + return HL_HAND_LT; + } + } + else if ( HitLoc == 83||HitLoc == 87||HitLoc == 88||HitLoc == 92||HitLoc == 93||HitLoc == 97 ) + {//arms + if ( rdot > 0 ) + { + return HL_ARM_RT; + } + else + { + return HL_ARM_LT; + } + } + else if((HitLoc >= 107 && HitLoc <= 109)|| + (HitLoc >= 112 && HitLoc <= 114)|| + (HitLoc >= 117 && HitLoc <= 119)) + {//head + return HL_HEAD; + } + else + { + if ( udot < 0.3 ) + { + return HL_WAIST; + } + else if ( fdot < 0 ) + { + if ( rdot > 0.4 ) + { + return HL_BACK_RT; + } + else if ( rdot < -0.4 ) + { + return HL_BACK_LT; + } + else + { + return HL_BACK; + } + } + else + { + if ( rdot > 0.3 ) + { + return HL_CHEST_RT; + } + else if ( rdot < -0.3 ) + { + return HL_CHEST_LT; + } + else + { + return HL_CHEST; + } + } + } + //return HL_NONE; +} + +int G_PickPainAnim( gentity_t *self, const vec3_t point, int damage, int hitLoc = HL_NONE ) +{ + if ( hitLoc == HL_NONE ) + { + hitLoc = G_GetHitLocation( self, point ); + } + switch( hitLoc ) + { + case HL_FOOT_RT: + return BOTH_PAIN12; + //PAIN12 = right foot + break; + case HL_FOOT_LT: + return -1; + break; + case HL_LEG_RT: + if ( !Q_irand( 0, 1 ) ) + { + return BOTH_PAIN11; + } + else + { + return BOTH_PAIN13; + } + //PAIN11 = twitch right leg + //PAIN13 = right knee + break; + case HL_LEG_LT: + return BOTH_PAIN14; + //PAIN14 = twitch left leg + break; + case HL_BACK_RT: + return BOTH_PAIN7; + //PAIN7 = med left shoulder + break; + case HL_BACK_LT: + return Q_irand( BOTH_PAIN15, BOTH_PAIN16 ); + //PAIN15 = med right shoulder + //PAIN16 = twitch right shoulder + break; + case HL_BACK: + if ( !Q_irand( 0, 1 ) ) + { + return BOTH_PAIN1; + } + else + { + return BOTH_PAIN5; + } + //PAIN1 = back + //PAIN5 = same as 1 + break; + case HL_CHEST_RT: + return BOTH_PAIN3; + //PAIN3 = long, right shoulder + break; + case HL_CHEST_LT: + return BOTH_PAIN2; + //PAIN2 = long, left shoulder + break; + case HL_WAIST: + case HL_CHEST: + if ( !Q_irand( 0, 3 ) ) + { + return BOTH_PAIN6; + } + else if ( !Q_irand( 0, 2 ) ) + { + return BOTH_PAIN8; + } + else if ( !Q_irand( 0, 1 ) ) + { + return BOTH_PAIN17; + } + else + { + return BOTH_PAIN18; + } + //PAIN6 = gut + //PAIN8 = chest + //PAIN17 = twitch crotch + //PAIN19 = med crotch + break; + case HL_ARM_RT: + case HL_HAND_RT: + return BOTH_PAIN9; + //PAIN9 = twitch right arm + break; + case HL_ARM_LT: + case HL_HAND_LT: + return BOTH_PAIN10; + //PAIN10 = twitch left arm + break; + case HL_HEAD: + return BOTH_PAIN4; + //PAIN4 = head + break; + default: + return -1; + break; + } +} + +extern void G_BounceMissile( gentity_t *ent, trace_t *trace ); +void LimbThink( gentity_t *ent ) +{//FIXME: just use object thinking? + vec3_t origin; + trace_t tr; + + + ent->nextthink = level.time + FRAMETIME; + if ( ent->owner + && ent->owner->client + && (ent->owner->client->ps.eFlags&EF_HELD_BY_RANCOR) ) + { + ent->e_ThinkFunc = thinkF_G_FreeEntity; + return; + } + + if ( ent->enemy ) + {//alert people that I am a piece of one of their friends + AddSightEvent( ent->enemy, ent->currentOrigin, 384, AEL_DISCOVERED ); + } + + if ( ent->s.pos.trType == TR_STATIONARY ) + {//stopped + if ( level.time > ent->s.apos.trTime + ent->s.apos.trDuration ) + { + if (ent->owner && ent->owner->m_pVehicle) + { + ent->nextthink = level.time + Q_irand( 10000, 15000 ); + } + else + { + ent->nextthink = level.time + Q_irand( 5000, 15000 ); + } + + ent->e_ThinkFunc = thinkF_G_FreeEntity; + //FIXME: these keep drawing for a frame or so after being freed?! See them lerp to origin of world... + } + else + { + EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles ); + } + return; + } + + // get current position + EvaluateTrajectory( &ent->s.pos, level.time, origin ); + // get current angles + EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles ); + + // trace a line from the previous position to the current position, + // ignoring interactions with the missile owner + gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, + ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask ); + + VectorCopy( tr.endpos, ent->currentOrigin ); + if ( tr.startsolid ) + { + tr.fraction = 0; + } + + + gi.linkentity( ent ); + + if ( tr.fraction != 1 ) + { + G_BounceMissile( ent, &tr ); + if ( ent->s.pos.trType == TR_STATIONARY ) + {//stopped, stop spinning + //lay flat + //pitch + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + vec3_t flatAngles; + if ( ent->s.angles2[0] == -1 ) + {//any pitch is okay + flatAngles[0] = ent->currentAngles[0]; + } + else + {//lay flat + if ( ent->owner + && ent->owner->client + && ent->owner->client->NPC_class == CLASS_PROTOCOL + && ent->count == BOTH_DISMEMBER_TORSO1 ) + { + if ( ent->currentAngles[0] > 0 || ent->currentAngles[0] < -180 ) + { + flatAngles[0] = -90; + } + else + { + flatAngles[0] = 90; + } + } + else + { + if ( ent->currentAngles[0] > 90 || ent->currentAngles[0] < -90 ) + { + flatAngles[0] = 180; + } + else + { + flatAngles[0] = 0; + } + } + } + //yaw + flatAngles[1] = ent->currentAngles[1]; + //roll + if ( ent->s.angles2[2] == -1 ) + {//any roll is okay + flatAngles[2] = ent->currentAngles[2]; + } + else + { + if ( ent->currentAngles[2] > 90 || ent->currentAngles[2] < -90 ) + { + flatAngles[2] = 180; + } + else + { + flatAngles[2] = 0; + } + } + VectorSubtract( flatAngles, ent->s.apos.trBase, ent->s.apos.trDelta ); + for ( int i = 0; i < 3; i++ ) + { + ent->s.apos.trDelta[i] = AngleNormalize180( ent->s.apos.trDelta[i] ); + } + ent->s.apos.trTime = level.time; + ent->s.apos.trDuration = 1000; + ent->s.apos.trType = TR_LINEAR_STOP; + //VectorClear( ent->s.apos.trDelta ); + } + } +} + +float hitLocHealthPercentage[HL_MAX] = +{ + 0.0f, //HL_NONE = 0, + 0.05f, //HL_FOOT_RT, + 0.05f, //HL_FOOT_LT, + 0.20f, //HL_LEG_RT, + 0.20f, //HL_LEG_LT, + 0.30f, //HL_WAIST, + 0.15f, //HL_BACK_RT, + 0.15f, //HL_BACK_LT, + 0.30f, //HL_BACK, + 0.15f, //HL_CHEST_RT, + 0.15f, //HL_CHEST_LT, + 0.30f, //HL_CHEST, + 0.05f, //HL_ARM_RT, + 0.05f, //HL_ARM_LT, + 0.01f, //HL_HAND_RT, + 0.01f, //HL_HAND_LT, + 0.10f, //HL_HEAD + 0.0f, //HL_GENERIC1, + 0.0f, //HL_GENERIC2, + 0.0f, //HL_GENERIC3, + 0.0f, //HL_GENERIC4, + 0.0f, //HL_GENERIC5, + 0.0f //HL_GENERIC6 +}; + +char *hitLocName[HL_MAX] = +{ + "none", //HL_NONE = 0, + "right foot", //HL_FOOT_RT, + "left foot", //HL_FOOT_LT, + "right leg", //HL_LEG_RT, + "left leg", //HL_LEG_LT, + "waist", //HL_WAIST, + "back right shoulder", //HL_BACK_RT, + "back left shoulder", //HL_BACK_LT, + "back", //HL_BACK, + "front right shouler", //HL_CHEST_RT, + "front left shoulder", //HL_CHEST_LT, + "chest", //HL_CHEST, + "right arm", //HL_ARM_RT, + "left arm", //HL_ARM_LT, + "right hand", //HL_HAND_RT, + "left hand", //HL_HAND_LT, + "head", //HL_HEAD + "generic1", //HL_GENERIC1, + "generic2", //HL_GENERIC2, + "generic3", //HL_GENERIC3, + "generic4", //HL_GENERIC4, + "generic5", //HL_GENERIC5, + "generic6" //HL_GENERIC6 +}; + +qboolean G_LimbLost( gentity_t *ent, int hitLoc ) +{ + switch ( hitLoc ) + { + case HL_FOOT_RT: + if ( ent->locationDamage[HL_FOOT_RT] >= Q3_INFINITE ) + { + return qtrue; + } + //NOTE: falls through + case HL_LEG_RT: + //NOTE: feet fall through + if ( ent->locationDamage[HL_LEG_RT] >= Q3_INFINITE ) + { + return qtrue; + } + return qfalse; + break; + + case HL_FOOT_LT: + if ( ent->locationDamage[HL_FOOT_LT] >= Q3_INFINITE ) + { + return qtrue; + } + //NOTE: falls through + case HL_LEG_LT: + //NOTE: feet fall through + if ( ent->locationDamage[HL_LEG_LT] >= Q3_INFINITE ) + { + return qtrue; + } + return qfalse; + break; + + case HL_HAND_LT: + if ( ent->locationDamage[HL_HAND_LT] >= Q3_INFINITE ) + { + return qtrue; + } + //NOTE: falls through + case HL_ARM_LT: + case HL_CHEST_LT: + case HL_BACK_RT: + //NOTE: hand falls through + if ( ent->locationDamage[HL_ARM_LT] >= Q3_INFINITE + || ent->locationDamage[HL_CHEST_LT] >= Q3_INFINITE + || ent->locationDamage[HL_BACK_RT] >= Q3_INFINITE + || ent->locationDamage[HL_WAIST] >= Q3_INFINITE ) + { + return qtrue; + } + return qfalse; + break; + + case HL_HAND_RT: + if ( ent->locationDamage[HL_HAND_RT] >= Q3_INFINITE ) + { + return qtrue; + } + //NOTE: falls through + case HL_ARM_RT: + case HL_CHEST_RT: + case HL_BACK_LT: + //NOTE: hand falls through + if ( ent->locationDamage[HL_ARM_RT] >= Q3_INFINITE + || ent->locationDamage[HL_CHEST_RT] >= Q3_INFINITE + || ent->locationDamage[HL_BACK_LT] >= Q3_INFINITE + || ent->locationDamage[HL_WAIST] >= Q3_INFINITE ) + { + return qtrue; + } + return qfalse; + break; + + case HL_HEAD: + if ( ent->locationDamage[HL_HEAD] >= Q3_INFINITE ) + { + return qtrue; + } + //NOTE: falls through + case HL_WAIST: + //NOTE: head falls through + if ( ent->locationDamage[HL_WAIST] >= Q3_INFINITE ) + { + return qtrue; + } + return qfalse; + break; + default: + return (ent->locationDamage[hitLoc]>=Q3_INFINITE); + break; + } +} + +extern qboolean G_GetRootSurfNameWithVariant( gentity_t *ent, const char *rootSurfName, char *returnSurfName, int returnSize ); +void G_RemoveWeaponsWithLimbs( gentity_t *ent, gentity_t *limb, int limbAnim ) +{ + int weaponModelNum = 0, checkAnim; + char handName[MAX_QPATH]; + + for ( weaponModelNum = 0; weaponModelNum < MAX_INHAND_WEAPONS; weaponModelNum++ ) + { + if ( ent->weaponModel[weaponModelNum] >= 0 ) + {//have a weapon in this hand + if ( weaponModelNum == 0 && ent->client->ps.saberInFlight ) + {//this is the right-hand weapon and it's a saber in-flight (i.e.: not in-hand, so no need to worry about it) + continue; + } + //otherwise, the corpse hasn't dropped their weapon + switch ( weaponModelNum ) + { + case 0://right hand + checkAnim = BOTH_DISMEMBER_RARM; + G_GetRootSurfNameWithVariant( ent, "r_hand", handName, sizeof(handName) ); + break; + case 1://left hand + checkAnim = BOTH_DISMEMBER_LARM; + G_GetRootSurfNameWithVariant( ent, "l_hand", handName, sizeof(handName) ); + break; + default://not handled/valid + continue; + break; + } + + if ( limbAnim == checkAnim || limbAnim == BOTH_DISMEMBER_TORSO1 )//either/both hands + {//FIXME: is this first check needed with this lower one? + if ( !gi.G2API_GetSurfaceRenderStatus( &limb->ghoul2[0], handName ) ) + {//only copy the weapon over if the hand is actually on this limb... + //copy it to limb + if ( ent->s.weapon != WP_NONE ) + {//only if they actually still have a weapon + limb->s.weapon = ent->s.weapon; + limb->weaponModel[weaponModelNum] = ent->weaponModel[weaponModelNum]; + }//else - weaponModel is not -1 but don't have a weapon? Oops, somehow G2 model wasn't removed? + //remove it on owner + if ( ent->weaponModel[weaponModelNum] > 0 ) + { + gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[weaponModelNum] ); + ent->weaponModel[weaponModelNum] = -1; + } + if ( !ent->client->ps.saberInFlight ) + {//saberent isn't flying through the air, it's in-hand and attached to player + if ( ent->client->ps.saberEntityNum != ENTITYNUM_NONE && ent->client->ps.saberEntityNum > 0 ) + {//remove the owner ent's saber model and entity + if ( g_entities[ent->client->ps.saberEntityNum].inuse ) + { + G_FreeEntity( &g_entities[ent->client->ps.saberEntityNum] ); + } + ent->client->ps.saberEntityNum = ENTITYNUM_NONE; + } + } + } + else + {//the hand had already been removed + if ( ent->weaponModel[weaponModelNum] > 0 ) + {//still a weapon associated with it, remove it from the limb + gi.G2API_RemoveGhoul2Model( limb->ghoul2, ent->weaponModel[weaponModelNum] ); + limb->weaponModel[weaponModelNum] = -1; + } + } + } + else + {//this weapon isn't affected by this dismemberment + if ( ent->weaponModel[weaponModelNum] > 0 ) + {//but a weapon was copied over to the limb, so remove it + gi.G2API_RemoveGhoul2Model( limb->ghoul2, ent->weaponModel[weaponModelNum] ); + limb->weaponModel[weaponModelNum] = -1; + } + } + } + } +} + +static qboolean G_Dismember( gentity_t *ent, vec3_t point, + const char *limbBone, const char *rotateBone, const char *limbName, + const char *limbCapName, const char *stubCapName, const char *limbTagName, const char *stubTagName, + int limbAnim, float limbRollBase, float limbPitchBase, + int damage, int hitLoc ) +{ + int newBolt; + vec3_t dir, newPoint, limbAngles = {0,ent->client->ps.legsYaw,0}; + gentity_t *limb; + trace_t trace; + + //make sure this limb hasn't been lopped off already! + if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], limbName ) == 0x00000100/*G2SURFACEFLAG_NODESCENDANTS*/ ) + {//already lost this limb + //NOTE: we now check for off wth no decendants + //because the torso surface can be off with + //the torso variations on when this is one of + //our "choose your own jedi" models + return qfalse; + } + + //NOTE: only reason I have this next part is because G2API_GetSurfaceRenderStatus is *not* working + if ( G_LimbLost( ent, hitLoc ) ) + {//already lost this limb + return qfalse; + } + + //FIXME: when timescale is high, can sometimes cut off a surf that includes a surf that was already cut off +//0) create a limb ent + VectorCopy( point, newPoint ); + newPoint[2] += 6; + limb = G_Spawn(); + G_SetOrigin( limb, newPoint ); + //VectorCopy(ent->currentAngles,limbAngles); + //G_SetAngles( limb, ent->currentAngles ); + VectorCopy( newPoint, limb->s.pos.trBase ); +//1) copy the g2 instance of the victim into the limb + gi.G2API_CopyGhoul2Instance( ent->ghoul2, limb->ghoul2 ); + limb->playerModel = 0;//assumption! + limb->craniumBone = ent->craniumBone; + limb->cervicalBone = ent->cervicalBone; + limb->thoracicBone = ent->thoracicBone; + limb->upperLumbarBone = ent->upperLumbarBone; + limb->lowerLumbarBone = ent->lowerLumbarBone; + limb->hipsBone = ent->hipsBone; + limb->rootBone = ent->rootBone; +//2) set the root surf on the limb + if ( limbTagName ) + {//add smoke to cap tag + newBolt = gi.G2API_AddBolt( &limb->ghoul2[limb->playerModel], limbTagName ); + if ( newBolt != -1 ) + { + G_PlayEffect( G_EffectIndex("blaster/smoke_bolton"), limb->playerModel, newBolt, limb->s.number, newPoint); + } + } + /* + if ( limbBone && hitLoc == HL_HEAD ) + {//stop the current anim on the limb? + gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "model_root" ); + gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "motion" ); + gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "upper_lumbar" ); + } + */ + gi.G2API_StopBoneAnimIndex( &limb->ghoul2[limb->playerModel], limb->hipsBone ); + + gi.G2API_SetRootSurface( limb->ghoul2, limb->playerModel, limbName ); + /* + if ( limbBone && hitLoc != HL_WAIST ) + {//play the dismember anim on the limb? + //FIXME: screws up origin + animation_t *animations = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations; + //play the proper dismember anim on the limb + gi.G2API_SetBoneAnim(&limb->ghoul2[limb->playerModel], 0, animations[limbAnim].firstFrame - 1, + animations[limbAnim].numFrames + animations[limbAnim].firstFrame - 1, + BONE_ANIM_OVERRIDE_FREEZE, 1, cg.time); + } + */ + if ( limbBone && hitLoc == HL_WAIST && ent->client->NPC_class == CLASS_PROTOCOL ) + {//play the dismember anim on the limb? + gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "model_root" ); + gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "motion" ); + gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "pelvis" ); + gi.G2API_StopBoneAnim( &limb->ghoul2[limb->playerModel], "upper_lumbar" ); + //FIXME: screws up origin + animation_t *animations = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations; + //play the proper dismember anim on the limb + gi.G2API_SetBoneAnim(&limb->ghoul2[limb->playerModel], 0, animations[limbAnim].firstFrame, + animations[limbAnim].numFrames + animations[limbAnim].firstFrame, + BONE_ANIM_OVERRIDE_FREEZE, 1, cg.time ); + } + if ( rotateBone ) + { + gi.G2API_SetNewOrigin( &limb->ghoul2[0], gi.G2API_AddBolt( &limb->ghoul2[0], rotateBone ) ); + + //now let's try to position the limb at the *exact* right spot + int newBolt = gi.G2API_AddBolt( &ent->ghoul2[0], rotateBone ); + if ( newBolt != -1 ) + { + int actualTime = (cg.time?cg.time:level.time); + mdxaBone_t boltMatrix; + vec3_t angles; + + VectorSet( angles, 0, ent->currentAngles[YAW], 0 ); + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, newBolt, + &boltMatrix, angles, ent->currentOrigin, + actualTime, NULL, ent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, limb->s.origin ); + G_SetOrigin( limb, limb->s.origin ); + VectorCopy( limb->s.origin, limb->s.pos.trBase ); + //angles, too + /* + vec3_t limbF, limbR; + newBolt = gi.G2API_AddBolt( &ent->ghoul2[0], limbBone ); + if ( newBolt != -1 ) + { + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, newBolt, + &boltMatrix, angles, ent->currentOrigin, + actualTime, NULL, ent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_X, limbF ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, limbR ); + vectoangles( limbF, limbAngles ); + vectoangles( limbR, angles ); + limbAngles[YAW] += 180; + limbAngles[ROLL] = angles[PITCH]*-1.0f; + } + */ + } + } + if ( limbCapName ) + {//turn on caps + gi.G2API_SetSurfaceOnOff( &limb->ghoul2[limb->playerModel], limbCapName, 0 ); + } +//3) turn off w/descendants that surf in original model +//NOTE: we actually change the ent's stuff on the cgame side so that there is no 50ms lag +// this is neccessary because the Ghoul2 info does not have to go over the network, +// cgame looks at game's data. The new limb ent will be delayed so we will see +// that a limb is missing before the chopped off limb appears. +// also, if the limb was going to start in solid, we can delete it and return + if ( stubTagName ) + {//add smoke to cap surf, spawn effect + //do it later + limb->target = G_NewString( stubTagName ); + /* + newBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], stubTagName ); + if ( newBolt != -1 ) + { + G_PlayEffect( "blaster/smoke_bolton", ent->playerModel, newBolt, ent->s.number); + } + */ + } + if ( limbName ) + { + limb->target2 = G_NewString( limbName ); + //gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], limbName, 0x00000100 );//G2SURFACEFLAG_NODESCENDANTS + } + if ( stubCapName ) + {//turn on caps + limb->target3 = G_NewString( stubCapName ); + //gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], stubCapName, 0 ); + } + limb->count = limbAnim; +// + limb->s.radius = 60; +//4) toss the limb away + limb->classname = "limb"; + limb->owner = ent; + limb->enemy = ent->enemy; + + //remove weapons/move them to limb (if applicable in this dismemberment) + G_RemoveWeaponsWithLimbs( ent, limb, limbAnim ); + + limb->e_clThinkFunc = clThinkF_CG_Limb; + limb->e_ThinkFunc = thinkF_LimbThink; + limb->nextthink = level.time + FRAMETIME; + gi.linkentity( limb ); + //need size, contents, clipmask + limb->svFlags = SVF_USE_CURRENT_ORIGIN; + limb->clipmask = MASK_SOLID; + limb->contents = CONTENTS_CORPSE; + VectorSet( limb->mins, -3.0f, -3.0f, -6.0f ); + VectorSet( limb->maxs, 3.0f, 3.0f, 6.0f ); + + //make sure it doesn't start in solid + gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask ); + if ( trace.startsolid ) + { + limb->s.pos.trBase[2] -= limb->mins[2]; + gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask ); + if ( trace.startsolid ) + { + limb->s.pos.trBase[2] += limb->mins[2]; + gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask ); + if ( trace.startsolid ) + {//stuck? don't remove + G_FreeEntity( limb ); + return qfalse; + } + } + } + + //move it + VectorCopy( limb->s.pos.trBase, limb->currentOrigin ); + gi.linkentity( limb ); + + limb->s.eType = ET_THINKER;//ET_GENERAL; + limb->physicsBounce = 0.2f; + limb->s.pos.trType = TR_GRAVITY; + limb->s.pos.trTime = level.time; // move a bit on the very first frame + VectorSubtract( point, ent->currentOrigin, dir ); + VectorNormalize( dir ); + //no trDuration? + //spin it + //new way- try to preserve the exact angle and position of the limb as it was when attached + VectorSet( limb->s.angles2, limbPitchBase, 0, limbRollBase ); + VectorCopy( limbAngles, limb->s.apos.trBase ); + /* + //old way- just set an angle... + limb->s.apos.trBase[0] += limbPitchBase; + limb->s.apos.trBase[1] = ent->client->ps.viewangles[1]; + limb->s.apos.trBase[2] += limbRollBase; + */ + limb->s.apos.trTime = level.time; + limb->s.apos.trType = TR_LINEAR; + VectorClear( limb->s.apos.trDelta ); + + if ( hitLoc == HL_HAND_RT || hitLoc == HL_HAND_LT ) + {//hands fly farther + VectorMA( ent->client->ps.velocity, 200, dir, limb->s.pos.trDelta ); + //make it bounce some + limb->s.eFlags |= EF_BOUNCE_HALF; + limb->s.apos.trDelta[0] = Q_irand( -300, 300 ); + limb->s.apos.trDelta[1] = Q_irand( -800, 800 ); + } + else if ( limbAnim == BOTH_DISMEMBER_HEAD1 + || limbAnim == BOTH_DISMEMBER_LARM + || limbAnim == BOTH_DISMEMBER_RARM ) + {//head and arms don't fly as far + limb->s.eFlags |= EF_BOUNCE_SHRAPNEL; + VectorMA( ent->client->ps.velocity, 150, dir, limb->s.pos.trDelta ); + limb->s.apos.trDelta[0] = Q_irand( -200, 200 ); + limb->s.apos.trDelta[1] = Q_irand( -400, 400 ); + } + else// if ( limbAnim == BOTH_DISMEMBER_TORSO1 || limbAnim == BOTH_DISMEMBER_LLEG || limbAnim == BOTH_DISMEMBER_RLEG ) + {//everything else just kinda falls off + limb->s.eFlags |= EF_BOUNCE_SHRAPNEL; + VectorMA( ent->client->ps.velocity, 100, dir, limb->s.pos.trDelta ); + limb->s.apos.trDelta[0] = Q_irand( -100, 100 ); + limb->s.apos.trDelta[1] = Q_irand( -200, 200 ); + } + //roll? No, doesn't work... + //limb->s.apos.trDelta[2] = Q_irand( -300, 300 );//FIXME: this scales it down @ 80% and does weird stuff in timescale != 1.0 + //limb->s.apos.trDelta[2] = limbRoll; + + //preserve scale so giants don't have tiny limbs + VectorCopy( ent->s.modelScale, limb->s.modelScale ); + + //mark ent as dismembered + ent->locationDamage[hitLoc] = Q3_INFINITE;//mark this limb as gone + ent->client->dismembered = true; + + //copy the custom RGB to the limb (for skin coloring) + limb->startRGBA[0] = ent->client->renderInfo.customRGBA[0]; + limb->startRGBA[1] = ent->client->renderInfo.customRGBA[1]; + limb->startRGBA[2] = ent->client->renderInfo.customRGBA[2]; + + return qtrue; +} + +static qboolean G_Dismemberable( gentity_t *self, int hitLoc ) +{ + if ( self->client->dismembered ) + {//cannot dismember me right now + return qfalse; + } + if ( g_dismemberment->integer < 11381138 && g_saberRealisticCombat->integer < 2 ) + { + if ( g_dismemberProbabilities->value > 0.0f ) + {//use the ent-specific dismemberProbabilities + float dismemberProb = 0; + // check which part of the body it is. Then check the npc's probability + // of that body part coming off, if it doesn't pass, return out. + switch ( hitLoc ) + { + case HL_LEG_RT: + case HL_LEG_LT: + dismemberProb = self->client->dismemberProbLegs; + break; + case HL_WAIST: + dismemberProb = self->client->dismemberProbWaist; + break; + case HL_BACK_RT: + case HL_BACK_LT: + case HL_CHEST_RT: + case HL_CHEST_LT: + case HL_ARM_RT: + case HL_ARM_LT: + dismemberProb = self->client->dismemberProbArms; + break; + case HL_HAND_RT: + case HL_HAND_LT: + dismemberProb = self->client->dismemberProbHands; + break; + case HL_HEAD: + dismemberProb = self->client->dismemberProbHead; + break; + default: + return qfalse; + break; + } + + //check probability of this happening on this npc + if ( floor((Q_flrand( 1, 100 )*g_dismemberProbabilities->value)) > dismemberProb*2.0f )//probabilities seemed really really low, had to crank them up + { + return qfalse; + } + } + } + return qtrue; +} + +static qboolean G_Dismemberable2( gentity_t *self, int hitLoc ) +{ + if ( self->client->dismembered ) + {//cannot dismember me right now + return qfalse; + } + if ( g_dismemberment->integer < 11381138 && g_saberRealisticCombat->integer < 2 ) + { + if ( g_dismemberProbabilities->value <= 0.0f ) + {//add the passed-in damage to the locationDamage array, check to see if it's taken enough damage to actually dismember + if ( self->locationDamage[hitLoc] < (self->client->ps.stats[STAT_MAX_HEALTH]*hitLocHealthPercentage[hitLoc]) ) + {//this location has not taken enough damage to dismember + return qfalse; + } + } + } + return qtrue; +} + +#define MAX_VARIANTS 8 +qboolean G_GetRootSurfNameWithVariant( gentity_t *ent, const char *rootSurfName, char *returnSurfName, int returnSize ) +{ + if ( !gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], rootSurfName ) ) + {//see if the basic name without variants is on + Q_strncpyz( returnSurfName, rootSurfName, returnSize, qtrue ); + return qtrue; + } + else + {//check variants + int i; + for ( i = 0; i < MAX_VARIANTS; i++ ) + { + Com_sprintf( returnSurfName, returnSize, "%s%c", rootSurfName, 'a'+i ); + if ( !gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], returnSurfName ) ) + { + return qtrue; + } + } + } + Q_strncpyz( returnSurfName, rootSurfName, returnSize, qtrue ); + return qfalse; +} + +extern qboolean G_StandardHumanoid( gentity_t *self ); +qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force = qfalse ) +{ +//extern cvar_t *g_iscensored; + // dismemberment -- FIXME: should have a check for how long npc has been dead so people can't + // continue to dismember a dead body long after it's been dead + //NOTE that you can only cut one thing off unless the dismemberment is >= 11381138 +#ifdef GERMAN_CENSORED + if ( 0 ) //germany == censorship +#else + if ( /*!g_iscensored->integer &&*/ ( g_dismemberment->integer || g_saberRealisticCombat->integer > 1 ) && mod == MOD_SABER )//only lightsaber +#endif + {//FIXME: don't do strcmps here + if ( G_StandardHumanoid( self ) + && (force||g_dismemberProbabilities->value>0.0f||G_Dismemberable2( self, hitLoc )) ) + {//either it's a forced dismemberment or we're using probabilities (which are checked before this) or we've done enough damage to this location + //FIXME: check the hitLoc and hitDir against the cap tag for the place + //where the split will be- if the hit dir is roughly perpendicular to + //the direction of the cap, then the split is allowed, otherwise we + //hit it at the wrong angle and should not dismember... + const char *limbBone = NULL, *rotateBone = NULL, *limbTagName = NULL, *stubTagName = NULL; + int anim = -1; + float limbRollBase = 0, limbPitchBase = 0; + qboolean doDismemberment = qfalse; + char limbName[MAX_QPATH]; + char stubName[MAX_QPATH]; + char limbCapName[MAX_QPATH]; + char stubCapName[MAX_QPATH]; + + switch( hitLoc )//self->hitLoc + { + case HL_LEG_RT: + if ( g_dismemberment->integer > 1 ) + { + doDismemberment = qtrue; + limbBone = "rtibia"; + rotateBone = "rtalus"; + G_GetRootSurfNameWithVariant( self, "r_leg", limbName, sizeof(limbName) ); + G_GetRootSurfNameWithVariant( self, "hips", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_hips", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_leg", stubName ); + limbTagName = "*r_leg_cap_hips"; + stubTagName = "*hips_cap_r_leg"; + anim = BOTH_DISMEMBER_RLEG; + limbRollBase = 0; + limbPitchBase = 0; + } + break; + case HL_LEG_LT: + if ( g_dismemberment->integer > 1 ) + { + doDismemberment = qtrue; + limbBone = "ltibia"; + rotateBone = "ltalus"; + G_GetRootSurfNameWithVariant( self, "l_leg", limbName, sizeof(limbName) ); + G_GetRootSurfNameWithVariant( self, "hips", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_hips", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_leg", stubName ); + limbTagName = "*l_leg_cap_hips"; + stubTagName = "*hips_cap_l_leg"; + anim = BOTH_DISMEMBER_LLEG; + limbRollBase = 0; + limbPitchBase = 0; + } + break; + case HL_WAIST: + if ( g_dismemberment->integer > 2 && + (!self->s.number||!self->message)) + { + doDismemberment = qtrue; + limbBone = "pelvis"; + rotateBone = "thoracic"; + Q_strncpyz( limbName, "torso", sizeof( limbName ) ); + Q_strncpyz( limbCapName, "torso_cap_hips", sizeof( limbCapName ) ); + Q_strncpyz( stubCapName, "hips_cap_torso", sizeof( stubCapName ) ); + limbTagName = "*torso_cap_hips"; + stubTagName = "*hips_cap_torso"; + anim = BOTH_DISMEMBER_TORSO1; + limbRollBase = 0; + limbPitchBase = 0; + } + break; + case HL_CHEST_RT: + case HL_ARM_RT: + case HL_BACK_RT: + if ( g_dismemberment->integer ) + { + doDismemberment = qtrue; + limbBone = "rhumerus"; + rotateBone = "rradius"; + G_GetRootSurfNameWithVariant( self, "r_arm", limbName, sizeof(limbName) ); + G_GetRootSurfNameWithVariant( self, "torso", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_torso", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_arm", stubName ); + limbTagName = "*r_arm_cap_torso"; + stubTagName = "*torso_cap_r_arm"; + anim = BOTH_DISMEMBER_RARM; + limbRollBase = 0; + limbPitchBase = 0; + } + break; + case HL_CHEST_LT: + case HL_ARM_LT: + case HL_BACK_LT: + if ( g_dismemberment->integer && + (!self->s.number||!self->message)) + {//either the player or not carrying a key on my arm + doDismemberment = qtrue; + limbBone = "lhumerus"; + rotateBone = "lradius"; + G_GetRootSurfNameWithVariant( self, "l_arm", limbName, sizeof(limbName) ); + G_GetRootSurfNameWithVariant( self, "torso", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_torso", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_arm", stubName ); + limbTagName = "*l_arm_cap_torso"; + stubTagName = "*torso_cap_l_arm"; + anim = BOTH_DISMEMBER_LARM; + limbRollBase = 0; + limbPitchBase = 0; + } + break; + case HL_HAND_RT: + if ( g_dismemberment->integer ) + { + doDismemberment = qtrue; + limbBone = "rradiusX"; + rotateBone = "rhand"; + G_GetRootSurfNameWithVariant( self, "r_hand", limbName, sizeof(limbName) ); + G_GetRootSurfNameWithVariant( self, "r_arm", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_r_arm", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_hand", stubName ); + limbTagName = "*r_hand_cap_r_arm"; + stubTagName = "*r_arm_cap_r_hand"; + anim = BOTH_DISMEMBER_RARM; + limbRollBase = 0; + limbPitchBase = 0; + } + break; + case HL_HAND_LT: + if ( g_dismemberment->integer ) + { + doDismemberment = qtrue; + limbBone = "lradiusX"; + rotateBone = "lhand"; + G_GetRootSurfNameWithVariant( self, "l_hand", limbName, sizeof(limbName) ); + G_GetRootSurfNameWithVariant( self, "l_arm", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_l_arm", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_hand", stubName ); + limbTagName = "*l_hand_cap_l_arm"; + stubTagName = "*l_arm_cap_l_hand"; + anim = BOTH_DISMEMBER_LARM; + limbRollBase = 0; + limbPitchBase = 0; + } + break; + case HL_HEAD: + if ( g_dismemberment->integer > 2 ) + { + doDismemberment = qtrue; + limbBone = "cervical"; + rotateBone = "cranium"; + Q_strncpyz( limbName, "head", sizeof( limbName ) ); + Q_strncpyz( limbCapName, "head_cap_torso", sizeof( limbCapName ) ); + Q_strncpyz( stubCapName, "torso_cap_head", sizeof( stubCapName ) ); + limbTagName = "*head_cap_torso"; + stubTagName = "*torso_cap_head"; + anim = BOTH_DISMEMBER_HEAD1; + limbRollBase = -1; + limbPitchBase = -1; + } + break; + case HL_FOOT_RT: + case HL_FOOT_LT: + case HL_CHEST: + case HL_BACK: + default: + break; + } + if ( doDismemberment ) + { + return G_Dismember( self, point, limbBone, rotateBone, limbName, + limbCapName, stubCapName, limbTagName, stubTagName, + anim, limbRollBase, limbPitchBase, damage, hitLoc ); + } + } + } + return qfalse; +} + +static int G_CheckSpecialDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc ) +{ + int deathAnim = -1; + + if ( self->client->ps.legsAnim == BOTH_GETUP_BROLL_L + || self->client->ps.legsAnim == BOTH_GETUP_BROLL_R ) + {//rolling away to the side on our back + deathAnim = BOTH_DEATH_LYING_UP; + } + else if ( self->client->ps.legsAnim == BOTH_GETUP_FROLL_L + || self->client->ps.legsAnim == BOTH_GETUP_FROLL_R ) + {//rolling away to the side on our front + deathAnim = BOTH_DEATH_LYING_DN; + } + else if ( self->client->ps.legsAnim == BOTH_GETUP_BROLL_F + && self->client->ps.legsAnimTimer > 350 ) + {//kicking up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else if ( self->client->ps.legsAnim == BOTH_GETUP_BROLL_B + && self->client->ps.legsAnimTimer > 950 ) + {//on back, rolling back to get up + deathAnim = BOTH_DEATH_LYING_UP; + } + else if ( self->client->ps.legsAnim == BOTH_GETUP_BROLL_B + && self->client->ps.legsAnimTimer <= 950 + && self->client->ps.legsAnimTimer > 250 ) + {//flipping over backwards + deathAnim = BOTH_FALLDEATH1LAND; + } + else if ( self->client->ps.legsAnim == BOTH_GETUP_FROLL_B + && self->client->ps.legsAnimTimer <= 1100 + && self->client->ps.legsAnimTimer > 250 ) + {//flipping over backwards + deathAnim = BOTH_FALLDEATH1LAND; + } + else if ( PM_InRoll( &self->client->ps ) ) + { + deathAnim = BOTH_DEATH_ROLL; //# Death anim from a roll + } + else if ( PM_FlippingAnim( self->client->ps.legsAnim ) ) + { + deathAnim = BOTH_DEATH_FLIP; //# Death anim from a flip + } + else if ( PM_SpinningAnim( self->client->ps.legsAnim ) ) + { + float yawDiff = AngleNormalize180(AngleNormalize180(self->client->renderInfo.torsoAngles[YAW]) - AngleNormalize180(self->client->ps.viewangles[YAW])); + if ( yawDiff > 135 || yawDiff < -135 ) + { + deathAnim = BOTH_DEATH_SPIN_180; //# Death anim when facing backwards + } + else if ( yawDiff < -60 ) + { + deathAnim = BOTH_DEATH_SPIN_90_R; //# Death anim when facing 90 degrees right + } + else if ( yawDiff > 60 ) + { + deathAnim = BOTH_DEATH_SPIN_90_L; //# Death anim when facing 90 degrees left + } + } + else if ( PM_InKnockDown( &self->client->ps ) ) + {//since these happen a lot, let's handle them case by case + int animLength = PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.legsAnim ); + if ( self->s.number < MAX_CLIENTS ) + { + switch ( self->client->ps.legsAnim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + //case BOTH_PLAYER_PA_3_FLY: + animLength += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + break; + } + } + switch ( self->client->ps.legsAnim ) + { + case BOTH_KNOCKDOWN1: + if ( animLength - self->client->ps.legsAnimTimer > 100 ) + {//on our way down + if ( self->client->ps.legsAnimTimer > 600 ) + {//still partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + break; + case BOTH_PLAYER_PA_3_FLY: + case BOTH_KNOCKDOWN2: + if ( animLength - self->client->ps.legsAnimTimer > 700 ) + {//on our way down + if ( self->client->ps.legsAnimTimer > 600 ) + {//still partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + break; + case BOTH_KNOCKDOWN3: + if ( animLength - self->client->ps.legsAnimTimer > 100 ) + {//on our way down + if ( self->client->ps.legsAnimTimer > 1300 ) + {//still partially up + deathAnim = BOTH_DEATH_FALLING_DN; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_DN; + } + } + break; + case BOTH_KNOCKDOWN4: + case BOTH_RELEASED: + if ( animLength - self->client->ps.legsAnimTimer > 300 ) + {//on our way down + if ( self->client->ps.legsAnimTimer > 350 ) + {//still partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + else + {//crouch death + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -150 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + break; + case BOTH_KNOCKDOWN5: + case BOTH_LK_DL_ST_T_SB_1_L: + if ( self->client->ps.legsAnimTimer < 750 ) + {//flat + deathAnim = BOTH_DEATH_LYING_DN; + } + break; + case BOTH_GETUP1: + if ( self->client->ps.legsAnimTimer < 350 ) + {//standing up + } + else if ( self->client->ps.legsAnimTimer < 800 ) + {//crouching + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -150 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 450 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + break; + case BOTH_GETUP2: + if ( self->client->ps.legsAnimTimer < 150 ) + {//standing up + } + else if ( self->client->ps.legsAnimTimer < 850 ) + {//crouching + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -150 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 500 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + break; + case BOTH_GETUP3: + if ( self->client->ps.legsAnimTimer < 250 ) + {//standing up + } + else if ( self->client->ps.legsAnimTimer < 600 ) + {//crouching + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -150 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 150 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_DN; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_DN; + } + } + break; + case BOTH_GETUP4: + if ( self->client->ps.legsAnimTimer < 250 ) + {//standing up + } + else if ( self->client->ps.legsAnimTimer < 600 ) + {//crouching + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -150 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 850 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_DN; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + break; + case BOTH_GETUP5: + if ( self->client->ps.legsAnimTimer > 850 ) + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 1500 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_DN; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_DN; + } + } + break; + case BOTH_GETUP_CROUCH_B1: + if ( self->client->ps.legsAnimTimer < 800 ) + {//crouching + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -150 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 400 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + break; + case BOTH_GETUP_CROUCH_F1: + if ( self->client->ps.legsAnimTimer < 800 ) + {//crouching + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -150 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 150 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_DN; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_DN; + } + } + break; + case BOTH_FORCE_GETUP_B1: + if ( self->client->ps.legsAnimTimer < 325 ) + {//standing up + } + else if ( self->client->ps.legsAnimTimer < 725 ) + {//spinning up + deathAnim = BOTH_DEATH_SPIN_180; //# Death anim when facing backwards + } + else if ( self->client->ps.legsAnimTimer < 900 ) + {//crouching + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -150 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 50 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + break; + case BOTH_FORCE_GETUP_B2: + if ( self->client->ps.legsAnimTimer < 575 ) + {//standing up + } + else if ( self->client->ps.legsAnimTimer < 875 ) + {//spinning up + deathAnim = BOTH_DEATH_SPIN_180; //# Death anim when facing backwards + } + else if ( self->client->ps.legsAnimTimer < 900 ) + {//crouching + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -150 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + else + {//lying down + //partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + break; + case BOTH_FORCE_GETUP_B3: + if ( self->client->ps.legsAnimTimer < 150 ) + {//standing up + } + else if ( self->client->ps.legsAnimTimer < 775 ) + {//flipping + deathAnim = BOTH_DEATHBACKWARD2; //backflip + } + else + {//lying down + //partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + break; + case BOTH_FORCE_GETUP_B4: + if ( self->client->ps.legsAnimTimer < 325 ) + {//standing up + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 150 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + break; + case BOTH_FORCE_GETUP_B5: + if ( self->client->ps.legsAnimTimer < 550 ) + {//standing up + } + else if ( self->client->ps.legsAnimTimer < 1025 ) + {//kicking up + deathAnim = BOTH_DEATHBACKWARD2; //backflip + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 50 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + break; + case BOTH_FORCE_GETUP_B6: + if ( self->client->ps.legsAnimTimer < 225 ) + {//standing up + } + else if ( self->client->ps.legsAnimTimer < 425 ) + {//crouching up + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -150 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + else if ( self->client->ps.legsAnimTimer < 825 ) + {//flipping up + deathAnim = BOTH_DEATHFORWARD3; //backflip + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 225 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_UP; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_UP; + } + } + break; + case BOTH_FORCE_GETUP_F1: + if ( self->client->ps.legsAnimTimer < 275 ) + {//standing up + } + else if ( self->client->ps.legsAnimTimer < 750 ) + {//flipping + deathAnim = BOTH_DEATH14; + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 100 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_DN; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_DN; + } + } + break; + case BOTH_FORCE_GETUP_F2: + if ( self->client->ps.legsAnimTimer < 1200 ) + {//standing + } + else + {//lying down + if ( animLength - self->client->ps.legsAnimTimer > 225 ) + {//partially up + deathAnim = BOTH_DEATH_FALLING_DN; + } + else + {//down + deathAnim = BOTH_DEATH_LYING_DN; + } + } + break; + } + } + else if ( PM_InOnGroundAnim( &self->client->ps ) ) + { + if ( AngleNormalize180(self->client->renderInfo.torsoAngles[PITCH]) < 0 ) + { + deathAnim = BOTH_DEATH_LYING_UP; //# Death anim when lying on back + } + else + { + deathAnim = BOTH_DEATH_LYING_DN; //# Death anim when lying on front + } + } + else if ( PM_CrouchAnim( self->client->ps.legsAnim ) ) + { + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + if ( thrown < -200 ) + { + deathAnim = BOTH_DEATHBACKWARD1; //# Death anim when crouched and thrown back + if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 ) + { + self->client->ps.velocity[2] = 100; + } + } + else + { + deathAnim = BOTH_DEATH_CROUCHED; //# Death anim when crouched + } + } + + return deathAnim; +} +extern qboolean PM_FinishedCurrentLegsAnim( gentity_t *self ); +static int G_PickDeathAnim( gentity_t *self, vec3_t point, int damage, int mod, int hitLoc ) +{//FIXME: play dead flop anims on body if in an appropriate _DEAD anim when this func is called + int deathAnim = -1; + if ( hitLoc == HL_NONE ) + { + hitLoc = G_GetHitLocation( self, point );//self->hitLoc + } + //dead flops...if you are already playing a death animation, I guess it can just return directly + switch( self->client->ps.legsAnim ) + { + case BOTH_DEATH1: //# First Death anim + case BOTH_DEAD1: + case BOTH_DEATH2: //# Second Death anim + case BOTH_DEAD2: + case BOTH_DEATH8: //# + case BOTH_DEAD8: + case BOTH_DEATH13: //# + case BOTH_DEAD13: + case BOTH_DEATH14: //# + case BOTH_DEAD14: + case BOTH_DEATH16: //# + case BOTH_DEAD16: + case BOTH_DEADBACKWARD1: //# First thrown backward death finished pose + case BOTH_DEADBACKWARD2: //# Second thrown backward death finished pose + //return -2; + //break; + if ( PM_FinishedCurrentLegsAnim( self ) ) + {//done with the anim + deathAnim = BOTH_DEADFLOP2; + } + else + { + deathAnim = -2; + } + return deathAnim; + break; + case BOTH_DEADFLOP2: + //return -2; + return BOTH_DEADFLOP2; + break; + case BOTH_DEATH10: //# + case BOTH_DEAD10: + case BOTH_DEATH15: //# + case BOTH_DEAD15: + case BOTH_DEADFORWARD1: //# First thrown forward death finished pose + case BOTH_DEADFORWARD2: //# Second thrown forward death finished pose + //return -2; + //break; + if ( PM_FinishedCurrentLegsAnim( self ) ) + {//done with the anim + deathAnim = BOTH_DEADFLOP1; + } + else + { + deathAnim = -2; + } + return deathAnim; + break; + case BOTH_DEADFLOP1: + //return -2; + return BOTH_DEADFLOP1; + break; + case BOTH_DEAD3: //# Third Death finished pose + case BOTH_DEAD4: //# Fourth Death finished pose + case BOTH_DEAD5: //# Fifth Death finished pose + case BOTH_DEAD6: //# Sixth Death finished pose + case BOTH_DEAD7: //# Seventh Death finished pose + case BOTH_DEAD9: //# + case BOTH_DEAD11: //# + case BOTH_DEAD12: //# + case BOTH_DEAD17: //# + case BOTH_DEAD18: //# + case BOTH_DEAD19: //# + case BOTH_DEAD20: //# + case BOTH_DEAD21: //# + case BOTH_DEAD22: //# + case BOTH_DEAD23: //# + case BOTH_DEAD24: //# + case BOTH_DEAD25: //# + case BOTH_LYINGDEAD1: //# Killed lying down death finished pose + case BOTH_STUMBLEDEAD1: //# Stumble forward death finished pose + case BOTH_FALLDEAD1LAND: //# Fall forward and splat death finished pose + case BOTH_DEATH3: //# Third Death anim + case BOTH_DEATH4: //# Fourth Death anim + case BOTH_DEATH5: //# Fifth Death anim + case BOTH_DEATH6: //# Sixth Death anim + case BOTH_DEATH7: //# Seventh Death anim + case BOTH_DEATH9: //# + case BOTH_DEATH11: //# + case BOTH_DEATH12: //# + case BOTH_DEATH17: //# + case BOTH_DEATH18: //# + case BOTH_DEATH19: //# + case BOTH_DEATH20: //# + case BOTH_DEATH21: //# + case BOTH_DEATH22: //# + case BOTH_DEATH23: //# + case BOTH_DEATH24: //# + case BOTH_DEATH25: //# + case BOTH_DEATHFORWARD1: //# First Death in which they get thrown forward + case BOTH_DEATHFORWARD2: //# Second Death in which they get thrown forward + case BOTH_DEATHFORWARD3: //# Second Death in which they get thrown forward + case BOTH_DEATHBACKWARD1: //# First Death in which they get thrown backward + case BOTH_DEATHBACKWARD2: //# Second Death in which they get thrown backward + case BOTH_DEATH1IDLE: //# Idle while close to death + case BOTH_LYINGDEATH1: //# Death to play when killed lying down + case BOTH_STUMBLEDEATH1: //# Stumble forward and fall face first death + case BOTH_FALLDEATH1: //# Fall forward off a high cliff and splat death - start + case BOTH_FALLDEATH1INAIR: //# Fall forward off a high cliff and splat death - loop + case BOTH_FALLDEATH1LAND: //# Fall forward off a high cliff and splat death - hit bottom + return -2; + break; + case BOTH_DEATH_ROLL: //# Death anim from a roll + case BOTH_DEATH_FLIP: //# Death anim from a flip + case BOTH_DEATH_SPIN_90_R: //# Death anim when facing 90 degrees right + case BOTH_DEATH_SPIN_90_L: //# Death anim when facing 90 degrees left + case BOTH_DEATH_SPIN_180: //# Death anim when facing backwards + case BOTH_DEATH_LYING_UP: //# Death anim when lying on back + case BOTH_DEATH_LYING_DN: //# Death anim when lying on front + case BOTH_DEATH_FALLING_DN: //# Death anim when falling on face + case BOTH_DEATH_FALLING_UP: //# Death anim when falling on back + case BOTH_DEATH_CROUCHED: //# Death anim when crouched + case BOTH_RIGHTHANDCHOPPEDOFF: + return -2; + break; + } + // Not currently playing a death animation, so try and get an appropriate one now. + if ( deathAnim == -1 ) + { + deathAnim = G_CheckSpecialDeathAnim( self, point, damage, mod, hitLoc ); + + if ( deathAnim == -1 ) + {//base on hitLoc + vec3_t fwd; + AngleVectors( self->currentAngles, fwd, NULL, NULL ); + float thrown = DotProduct( fwd, self->client->ps.velocity ); + //death anims + switch( hitLoc ) + { + case HL_FOOT_RT: + if ( !Q_irand( 0, 2 ) && thrown < 250 ) + { + deathAnim = BOTH_DEATH24;//right foot trips up, spin + } + else if ( !Q_irand( 0, 1 ) ) + { + if ( !Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH4;//back: forward + } + else + { + deathAnim = BOTH_DEATH16;//same as 1 + } + } + else + { + deathAnim = BOTH_DEATH5;//same as 4 + } + break; + case HL_FOOT_LT: + if ( !Q_irand( 0, 2 ) && thrown < 250 ) + { + deathAnim = BOTH_DEATH25;//left foot trips up, spin + } + else if ( !Q_irand( 0, 1 ) ) + { + if ( !Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH4;//back: forward + } + else + { + deathAnim = BOTH_DEATH16;//same as 1 + } + } + else + { + deathAnim = BOTH_DEATH5;//same as 4 + } + break; + case HL_LEG_RT: + if ( !Q_irand( 0, 2 ) && thrown < 250 ) + { + deathAnim = BOTH_DEATH3;//right leg collapse + } + else if ( !Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH5;//same as 4 + } + else + { + if ( !Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH4;//back: forward + } + else + { + deathAnim = BOTH_DEATH16;//same as 1 + } + } + break; + case HL_LEG_LT: + if ( !Q_irand( 0, 2 ) && thrown < 250 ) + { + deathAnim = BOTH_DEATH7;//left leg collapse + } + else if ( !Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH5;//same as 4 + } + else + { + if ( !Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH4;//back: forward + } + else + { + deathAnim = BOTH_DEATH16;//same as 1 + } + } + break; + case HL_BACK: + if ( fabs(thrown) < 50 || (fabs(thrown) < 200&&!Q_irand(0,3)) ) + { + if ( Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH17;//head/back: croak + } + else + { + deathAnim = BOTH_DEATH10;//back: bend back, fall forward + } + } + else + { + if ( !Q_irand( 0, 2 ) ) + { + deathAnim = BOTH_DEATH4;//back: forward + } + else if ( !Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH5;//back: forward + } + else + { + deathAnim = BOTH_DEATH16;//same as 1 + } + } + break; + case HL_HAND_RT: + case HL_CHEST_RT: + case HL_ARM_RT: + case HL_BACK_LT: + if ( (damage <= self->max_health*0.25&&Q_irand(0,1)) || (fabs(thrown)<200&&!Q_irand(0,2)) || !Q_irand( 0, 10 ) ) + { + if ( Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH9;//chest right: snap, fall forward + } + else + { + deathAnim = BOTH_DEATH20;//chest right: snap, fall forward + } + } + else if ( (damage <= self->max_health*0.5&&Q_irand(0,1)) || !Q_irand( 0, 10 ) ) + { + deathAnim = BOTH_DEATH3;//chest right: back + } + else if ( (damage <= self->max_health*0.75&&Q_irand(0,1)) || !Q_irand( 0, 10 ) ) + { + deathAnim = BOTH_DEATH6;//chest right: spin + } + else + { + //TEMP HACK: play spinny deaths less often + if ( Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH8;//chest right: spin high + } + else + { + switch ( Q_irand( 0, 3 ) ) + { + default: + case 0: + deathAnim = BOTH_DEATH9;//chest right: snap, fall forward + break; + case 1: + deathAnim = BOTH_DEATH3;//chest right: back + break; + case 2: + deathAnim = BOTH_DEATH6;//chest right: spin + break; + case 3: + deathAnim = BOTH_DEATH20;//chest right: spin + break; + } + } + } + break; + case HL_CHEST_LT: + case HL_ARM_LT: + case HL_HAND_LT: + case HL_BACK_RT: + if ( (damage <= self->max_health*0.25&&Q_irand(0,1)) || (fabs(thrown)<200&&!Q_irand(0,2)) || !Q_irand(0, 10) ) + { + if ( Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH11;//chest left: snap, fall forward + } + else + { + deathAnim = BOTH_DEATH21;//chest left: snap, fall forward + } + } + else if ( (damage <= self->max_health*0.5&&Q_irand(0,1)) || !Q_irand(0, 10) ) + { + deathAnim = BOTH_DEATH7;//chest left: back + } + else if ( (damage <= self->max_health*0.75&&Q_irand(0,1)) || !Q_irand(0, 10) ) + { + deathAnim = BOTH_DEATH12;//chest left: spin + } + else + { + //TEMP HACK: play spinny deaths less often + if ( Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH14;//chest left: spin high + } + else + { + switch ( Q_irand( 0, 3 ) ) + { + default: + case 0: + deathAnim = BOTH_DEATH11;//chest left: snap, fall forward + break; + case 1: + deathAnim = BOTH_DEATH7;//chest left: back + break; + case 2: + deathAnim = BOTH_DEATH12;//chest left: spin + break; + case 3: + deathAnim = BOTH_DEATH21;//chest left: spin + break; + } + } + } + break; + case HL_CHEST: + case HL_WAIST: + if ( (damage <= self->max_health*0.25&&Q_irand(0,1)) || thrown > -50 ) + { + if ( !Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH18;//gut: fall right + } + else + { + deathAnim = BOTH_DEATH19;//gut: fall left + } + } + else if ( (damage <= self->max_health*0.5&&!Q_irand(0,1)) || (fabs(thrown)<200&&!Q_irand(0,3)) ) + { + if ( Q_irand( 0, 2 ) ) + { + deathAnim = BOTH_DEATH2;//chest: backward short + } + else if ( Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH22;//chest: backward short + } + else + { + deathAnim = BOTH_DEATH23;//chest: backward short + } + } + else if ( thrown < -300 && Q_irand( 0, 1 ) ) + { + if ( Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATHBACKWARD1;//chest: fly back + } + else + { + deathAnim = BOTH_DEATHBACKWARD2;//chest: flip back + } + } + else if ( thrown < -200 && Q_irand( 0, 1 ) ) + { + deathAnim = BOTH_DEATH15;//chest: roll backward + } + else + { + deathAnim = BOTH_DEATH1;//chest: backward med + } + break; + case HL_HEAD: + if ( damage <= self->max_health*0.5 && Q_irand(0,2) ) + { + deathAnim = BOTH_DEATH17;//head/back: croak + } + else + { + if ( Q_irand( 0, 2 ) ) + { + deathAnim = BOTH_DEATH13;//head: stumble, fall back + } + else + { + deathAnim = BOTH_DEATH10;//head: stumble, fall back + } + } + break; + default: + break; + } + } + } + + // Validate..... + if ( deathAnim == -1 || !PM_HasAnimation( self, deathAnim )) + { + if ( deathAnim == BOTH_DEADFLOP1 + || deathAnim == BOTH_DEADFLOP2 ) + {//if don't have deadflop, don't do anything + deathAnim = -1; + } + else + { + // I guess we'll take what we can get..... + deathAnim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 ); + } + } + + return deathAnim; +} + +int G_CheckLedgeDive( gentity_t *self, float checkDist, const vec3_t checkVel, qboolean tryOpposite, qboolean tryPerp ) +{ + // Intelligent Ledge-Diving Deaths: + // If I'm an NPC, check for nearby ledges and fall off it if possible + // How should/would/could this interact with knockback if we already have some? + // Ideally - apply knockback if there are no ledges or a ledge in that dir + // But if there is a ledge and it's not in the dir of my knockback, fall off the ledge instead + if ( !self || !self->client ) + { + return 0; + } + + vec3_t fallForwardDir, fallRightDir; + vec3_t angles = {0}; + int cliff_fall = 0; + + if ( checkVel && !VectorCompare( checkVel, vec3_origin ) ) + {//already moving in a dir + angles[1] = vectoyaw( self->client->ps.velocity ); + AngleVectors( angles, fallForwardDir, fallRightDir, NULL ); + } + else + {//try forward first + angles[1] = self->client->ps.viewangles[1]; + AngleVectors( angles, fallForwardDir, fallRightDir, NULL ); + } + VectorNormalize( fallForwardDir ); + float fallDist = G_CheckForLedge( self, fallForwardDir, checkDist ); + if ( fallDist >= 128 ) + { + VectorClear( self->client->ps.velocity ); + G_Throw( self, fallForwardDir, 85 ); + self->client->ps.velocity[2] = 100; + self->client->ps.groundEntityNum = ENTITYNUM_NONE; + } + else if ( tryOpposite ) + { + VectorScale( fallForwardDir, -1, fallForwardDir ); + fallDist = G_CheckForLedge( self, fallForwardDir, checkDist ); + if ( fallDist >= 128 ) + { + VectorClear( self->client->ps.velocity ); + G_Throw( self, fallForwardDir, 85 ); + self->client->ps.velocity[2] = 100; + self->client->ps.groundEntityNum = ENTITYNUM_NONE; + } + } + if ( !cliff_fall && tryPerp ) + {//try sides + VectorNormalize( fallRightDir ); + fallDist = G_CheckForLedge( self, fallRightDir, checkDist ); + if ( fallDist >= 128 ) + { + VectorClear( self->client->ps.velocity ); + G_Throw( self, fallRightDir, 85 ); + self->client->ps.velocity[2] = 100; + } + else + { + VectorScale( fallRightDir, -1, fallRightDir ); + fallDist = G_CheckForLedge( self, fallRightDir, checkDist ); + if ( fallDist >= 128 ) + { + VectorClear( self->client->ps.velocity ); + G_Throw( self, fallRightDir, 85 ); + self->client->ps.velocity[2] = 100; + } + } + } + if ( fallDist >= 256 ) + { + cliff_fall = 2; + } + else if ( fallDist >= 128 ) + { + cliff_fall = 1; + } + return cliff_fall; +} + +/* +================== +player_die +================== +*/ +void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +extern void AI_DeleteSelfFromGroup( gentity_t *self ); +extern void AI_GroupMemberKilled( gentity_t *self ); +extern qboolean FlyingCreature( gentity_t *ent ); +extern void G_DrivableATSTDie( gentity_t *self ); +extern void JET_FlyStop( gentity_t *self ); +extern void VehicleExplosionDelay( gentity_t *self ); +extern void NPC_LeaveTroop(gentity_t* actor); +extern void Rancor_DropVictim( gentity_t *self ); +extern void Wampa_DropVictim( gentity_t *self ); +extern void WP_StopForceHealEffects( gentity_t *self ); +void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath, int dflags, int hitLoc ) +{ + int anim; + int contents; + qboolean deathScript = qfalse; + qboolean lastInGroup = qfalse; + qboolean specialAnim = qfalse; + qboolean holdingSaber = qfalse; + int cliff_fall = 0; + + //FIXME: somehow people are sometimes not completely dying??? + if ( self->client->ps.pm_type == PM_DEAD && (meansOfDeath != MOD_SNIPER || (self->flags & FL_DISINTEGRATED)) ) + {//do dismemberment/twitching + if ( self->client->NPC_class == CLASS_MARK1 ) + { + DeathFX(self); + self->takedamage = qfalse; + self->client->ps.eFlags |= EF_NODRAW; + self->contents = 0; + // G_FreeEntity( self ); // Is this safe? I can't see why we'd mark it nodraw and then just leave it around?? + self->e_ThinkFunc = thinkF_G_FreeEntity; + self->nextthink = level.time + FRAMETIME; + } + else + { + anim = G_PickDeathAnim( self, self->pos1, damage, meansOfDeath, hitLoc ); + if ( dflags & DAMAGE_DISMEMBER ) + { + G_DoDismemberment( self, self->pos1, meansOfDeath, damage, hitLoc ); + } + if ( anim >= 0 ) + { + NPC_SetAnim(self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD); + } + } + return; + } + + // If the entity is in a vehicle. + if ( self->client && self->client->NPC_class != CLASS_VEHICLE && self->s.m_iVehicleNum != 0 ) + { + Vehicle_t *pVeh = g_entities[self->s.m_iVehicleNum].m_pVehicle; + if (pVeh) + { + if ( pVeh->m_pOldPilot != self + && pVeh->m_pPilot != self ) + {//whaaa? I'm not on this bike? er.... + assert(!!"How did we get to this point?"); + } + else + { // Get thrown out. + pVeh->m_pVehicleInfo->Eject( pVeh, self, qtrue ); + + // Now Send The Vehicle Flying To It's Death + if (pVeh->m_pVehicleInfo->type==VH_SPEEDER && pVeh->m_pParentEntity && pVeh->m_pParentEntity->client) + { + gentity_t* parent = pVeh->m_pParentEntity; + float CurSpeed = VectorLength(parent->client->ps.velocity); + + // If Moving + //----------- + if (CurSpeed>(pVeh->m_pVehicleInfo->speedMax*0.5f)) + { + // Send The Bike Out Of Control + //------------------------------ + pVeh->m_pVehicleInfo->StartDeathDelay(pVeh, 10000); + pVeh->m_ulFlags |= (VEH_OUTOFCONTROL); + VectorScale(parent->client->ps.velocity, 1.25f, parent->pos3); + + + // Try To Accelerate A Slowing Moving Vehicle To Full Speed + //---------------------------------------------------------- + if (CurSpeed<(pVeh->m_pVehicleInfo->speedMax*0.9f)) + { + VectorNormalize(parent->pos3); + if (fabsf(parent->pos3[2])<0.3f) + { + VectorScale(parent->pos3, (pVeh->m_pVehicleInfo->speedMax * 1.25f), parent->pos3); + } + else + { + VectorClear(parent->pos3); + } + } + + // Throw The Pilot + //---------------- + if (parent->pos3[0] || parent->pos3[1]) + { + vec3_t throwDir; + + VectorCopy(parent->client->ps.velocity, throwDir); + VectorNormalize(throwDir); + throwDir[2] += 0.3f; // up a little + + self->client->noRagTime = -1; // no ragdoll for you + CurSpeed /= 10.0f; + if (CurSpeed<50.0) + { + CurSpeed = 50.0f; + } + if (throwDir[2]<0.0f) + { + throwDir[2] = fabsf(throwDir[2]); + } + if (fabsf(throwDir[0])<0.2f) + { + throwDir[0] = Q_flrand(-0.5f, 0.5f); + } + if (fabsf(throwDir[1])<0.2f) + { + throwDir[1] = Q_flrand(-0.5f, 0.5f); + } + G_Throw(self, throwDir, CurSpeed); + } + } + } + } + } + else + { + assert(!!"How did we get to this point?"); + } + } + +#ifndef FINAL_BUILD + if ( d_saberCombat->integer && attacker && attacker->client ) + { + gi.Printf( S_COLOR_YELLOW"combatant %s died, killer anim = %s\n", self->targetname, animTable[attacker->client->ps.torsoAnim].name ); + } +#endif//FINAL_BUILD + if ( self->NPC ) + { + if (NAV::HasPath(self)) + { + NAV::ClearPath(self); + } + if (self->NPC->troop) + { + NPC_LeaveTroop(self); + } + // STEER_TODO: Do we need to free the steer user too? + + //clear charmed + G_CheckCharmed( self ); + + // Remove The Bubble Shield From The Assassin Droid + if (self->client && self->client->NPC_class==CLASS_ASSASSIN_DROID && (self->flags&FL_SHIELDED)) + { + self->flags &= ~FL_SHIELDED; + self->client->ps.stats[STAT_ARMOR] = 0; + self->client->ps.powerups[PW_GALAK_SHIELD] = 0; + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "force_shield", TURN_OFF ); + } + + if (self->client && self->client->NPC_class==CLASS_HOWLER) + { + G_StopEffect( G_EffectIndex( "howler/sonic" ), self->playerModel, self->genericBolt1, self->s.number ); + } + + + + if ( self->client && Jedi_WaitingAmbush( self ) ) + {//ambushing trooper + self->client->noclip = false; + } + NPC_FreeCombatPoint( self->NPC->combatPoint ); + if ( self->NPC->group ) + { + lastInGroup = (self->NPC->group->numGroup < 2); + AI_GroupMemberKilled( self ); + AI_DeleteSelfFromGroup( self ); + } + + if ( self->NPC->tempGoal ) + { + G_FreeEntity( self->NPC->tempGoal ); + self->NPC->tempGoal = NULL; + } + if ( self->s.eFlags & EF_LOCKED_TO_WEAPON ) + { + // dumb, just get the NPC out of the chair +extern void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd ); + + usercmd_t cmd, *ad_cmd; + + memset( &cmd, 0, sizeof( usercmd_t )); + + //gentity_t *old = self->owner; + + if ( self->owner ) + { + self->owner->s.frame = self->owner->startFrame = self->owner->endFrame = 0; + self->owner->svFlags &= ~SVF_ANIMATING; + } + + cmd.buttons |= BUTTON_USE; + ad_cmd = &cmd; + RunEmplacedWeapon( self, &ad_cmd ); + //self->owner = old; + } + if ( self->client->NPC_class == CLASS_BOBAFETT + || self->client->NPC_class == CLASS_ROCKETTROOPER ) + { + if ( self->client->moveType == MT_FLYSWIM ) + { + JET_FlyStop( self ); + } + } + if ( self->client->NPC_class == CLASS_ROCKETTROOPER ) + { + self->client->ps.eFlags &= ~EF_SPOTLIGHT; + } + if ( self->client->NPC_class == CLASS_SAND_CREATURE ) + { + self->client->ps.eFlags &= ~EF_NODRAW; + self->s.eFlags &= ~EF_NODRAW; + } + if ( self->client->NPC_class == CLASS_RANCOR ) + { + if ( self->count ) + { + Rancor_DropVictim( self ); + } + } + if ( self->client->NPC_class == CLASS_WAMPA ) + { + if ( self->count ) + { + if ( self->activator && attacker == self->activator && meansOfDeath == MOD_SABER ) + { + self->client->dismembered = false; + //FIXME: the limb should just disappear, cuz I ate it + G_DoDismemberment( self, self->currentOrigin, MOD_SABER, 1000, HL_ARM_RT, qtrue ); + } + Wampa_DropVictim( self ); + } + } + if ( (self->NPC->aiFlags&NPCAI_HEAL_ROSH) ) + { + if ( self->client->leader ) + { + self->client->leader->flags &= ~FL_UNDYING; + if ( self->client->leader->client ) + { + self->client->leader->client->ps.forcePowersKnown &= ~FORCE_POWERS_ROSH_FROM_TWINS; + } + } + } + if ( (self->client->ps.stats[STAT_WEAPONS]&(1<weaponModel[1], self->genericBolt1, self->s.number ); + G_StopEffect( G_EffectIndex( "scepter/beam.efx" ), self->weaponModel[1], self->genericBolt1, self->s.number ); + G_StopEffect( G_EffectIndex( "scepter/slam_warmup.efx" ), self->weaponModel[1], self->genericBolt1, self->s.number ); + self->s.loopSound = 0; + } + } + if ( attacker && attacker->NPC && attacker->NPC->group && attacker->NPC->group->enemy == self ) + { + attacker->NPC->group->enemy = NULL; + } + if ( self->s.weapon == WP_SABER ) + { + holdingSaber = qtrue; + } + if ( self->client->ps.saberEntityNum != ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 ) + { + if ( self->client->ps.saberInFlight ) + {//just drop it + self->client->ps.saber[0].Deactivate(); + } + else + { + if ( ( + (hitLoc != HL_HAND_RT&&hitLoc !=HL_CHEST_RT&&hitLoc!=HL_ARM_RT&&hitLoc!=HL_BACK_LT) + || self->client->dismembered + || meansOfDeath != MOD_SABER + )//if might get hand cut off, leave saber in hand + && holdingSaber + && ( Q_irand( 0, 1 ) + || meansOfDeath == MOD_EXPLOSIVE + || meansOfDeath == MOD_REPEATER_ALT + || meansOfDeath == MOD_FLECHETTE_ALT + || meansOfDeath == MOD_ROCKET + || meansOfDeath == MOD_ROCKET_ALT + || meansOfDeath == MOD_CONC + || meansOfDeath == MOD_CONC_ALT + || meansOfDeath == MOD_THERMAL + || meansOfDeath == MOD_THERMAL_ALT + || meansOfDeath == MOD_DETPACK + || meansOfDeath == MOD_LASERTRIP + || meansOfDeath == MOD_LASERTRIP_ALT + || meansOfDeath == MOD_MELEE + || meansOfDeath == MOD_FORCE_GRIP + || meansOfDeath == MOD_KNOCKOUT + || meansOfDeath == MOD_CRUSH + || meansOfDeath == MOD_IMPACT + || meansOfDeath == MOD_FALLING + || meansOfDeath == MOD_EXPLOSIVE_SPLASH ) ) + {//drop it + TossClientItems( self ); + self->client->ps.weapon = self->s.weapon = WP_NONE; + } + else + {//just free it + if ( g_entities[self->client->ps.saberEntityNum].inuse ) + { + G_FreeEntity( &g_entities[self->client->ps.saberEntityNum] ); + } + self->client->ps.saberEntityNum = ENTITYNUM_NONE; + } + } + } + if ( self->client->NPC_class == CLASS_SHADOWTROOPER ) + {//drop a force crystal + if ( Q_stricmpn("shadowtrooper", self->NPC_type, 13 ) == 0 ) + { + gitem_t *item; + item = FindItemForAmmo( AMMO_FORCE ); + Drop_Item( self, item, 0, qtrue ); + } + } + //Use any target we had + if ( meansOfDeath != MOD_KNOCKOUT ) + { + G_UseTargets( self, self ); + } + + if ( attacker ) + { + if ( attacker->client && !attacker->s.number ) + { + if ( self->client ) + {//killed a client + if ( self->client->playerTeam == TEAM_ENEMY + || self->client->playerTeam == TEAM_FREE + || (self->NPC && self->NPC->charmedTime > level.time) ) + {//killed an enemy + attacker->client->sess.missionStats.enemiesKilled++; + } + } + if ( attacker != self ) + { + G_TrackWeaponUsage( attacker, inflictor, 30, meansOfDeath ); + } + } + G_CheckVictoryScript(attacker); + //player killing a jedi with a lightsaber spawns a matrix-effect entity + if ( d_slowmodeath->integer ) + { + if ( !self->s.number ) + {//what the hell, always do slow-mo when player dies + //FIXME: don't do this when crushed to death? + if ( meansOfDeath == MOD_FALLING && self->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//falling to death, have not hit yet + G_StartMatrixEffect( self, (MEF_NO_VERTBOB|MEF_HIT_GROUND_STOP|MEF_MULTI_SPIN), 10000, 0.25f ); + } + else if ( meansOfDeath != MOD_CRUSH ) + {//for all deaths except being crushed + G_StartMatrixEffect( self ); + } + } + else if ( d_slowmodeath->integer < 4 ) + {//any jedi killed by player-saber + if ( d_slowmodeath->integer < 3 ) + {//must be the last jedi in the room + if ( !G_JediInRoom( attacker->currentOrigin ) ) + { + lastInGroup = qtrue; + } + else + { + lastInGroup = qfalse; + } + } + if ( !attacker->s.number + && (holdingSaber||self->client->NPC_class==CLASS_WAMPA) + && meansOfDeath == MOD_SABER + && attacker->client + && attacker->client->ps.weapon == WP_SABER + && !attacker->client->ps.saberInFlight //FIXME: if dualSabers, should still do slowmo if this killing blow was struck with the left-hand saber... + && (d_slowmodeath->integer > 2||lastInGroup) )//either slow mo death level 3 (any jedi) or 2 and I was the last jedi in the room + {//Matrix! + if ( attacker->client->ps.torsoAnim == BOTH_A6_SABERPROTECT ) + {//don't override the range and vertbob + G_StartMatrixEffect( self, (MEF_NO_RANGEVAR|MEF_NO_VERTBOB) ); + } + else + { + G_StartMatrixEffect( self ); + } + } + } + else + {//all player-saber kills + if ( !attacker->s.number + && meansOfDeath == MOD_SABER + && attacker->client + && attacker->client->ps.weapon == WP_SABER + && !attacker->client->ps.saberInFlight + && (d_slowmodeath->integer > 4||lastInGroup||holdingSaber||self->client->NPC_class==CLASS_WAMPA))//either slow mo death level 5 (any enemy) or 4 and I was the last in my group or I'm a saber user + {//Matrix! + if ( attacker->client->ps.torsoAnim == BOTH_A6_SABERPROTECT ) + {//don't override the range and vertbob + G_StartMatrixEffect( self, (MEF_NO_RANGEVAR|MEF_NO_VERTBOB) ); + } + else + { + G_StartMatrixEffect( self ); + } + } + } + } + } + + self->enemy = attacker; + self->client->renderInfo.lookTarget = ENTITYNUM_NONE; + + self->client->ps.persistant[PERS_KILLED]++; + if ( self->client->playerTeam == TEAM_PLAYER ) + {//FIXME: just HazTeam members in formation on away missions? + //or more controlled- via deathscripts? + // Don't count player + if (( &g_entities[0] != NULL && g_entities[0].client ) && (self->s.number != 0)) + {//add to the number of teammates lost + g_entities[0].client->ps.persistant[PERS_TEAMMATES_KILLED]++; + } + else // Player died, fire off scoreboard soon + { + cg.missionStatusDeadTime = level.time + 1000; // Too long?? Too short?? + cg.zoomMode = 0; // turn off zooming when we die + } + } + + if ( self->s.number == 0 && attacker ) + { +// G_SetMissionStatusText( attacker, meansOfDeath ); + //TEST: If player killed, unmark all teammates from being undying so they can buy it too + //NOTE: we want this to happen ONLY on our squad ONLY on missions... in the tutorial or on voyager levels this could be really weird. + G_MakeTeamVulnerable(); + } + + if ( attacker && attacker->client) + { + if ( attacker == self || OnSameTeam (self, attacker ) ) + { + AddScore( attacker, -1 ); + } + else + { + AddScore( attacker, 1 ); + } + } + else + { + AddScore( self, -1 ); + } + + // if client is in a nodrop area, don't drop anything + contents = gi.pointcontents( self->currentOrigin, -1 ); + if ( !holdingSaber + //&& self->s.number != 0 + && !( contents & CONTENTS_NODROP ) + && meansOfDeath != MOD_SNIPER + && (!self->client||self->client->NPC_class!=CLASS_GALAKMECH)) + { + TossClientItems( self ); + } + + if ( meansOfDeath == MOD_SNIPER ) + {//I was disintegrated + if ( self->message ) + {//I was holding a key + //drop the key + G_DropKey( self ); + } + } + + if ( holdingSaber ) + {//never drop a lightsaber! + if ( self->client->ps.SaberActive() ) + { + self->client->ps.SaberDeactivate(); + G_SoundIndexOnEnt( self, CHAN_AUTO, self->client->ps.saber[0].soundOff ); +#ifdef _IMMERSION + if ( self->client->playerTeam == TEAM_PLAYER ) + { + G_Force( self, G_ForceIndex( "fffx/weapons/saber/saberoff", FF_CHANNEL_WEAPON ) ); + } + else + { + G_Force( self, G_ForceIndex( "fffx/weapons/saber/enemy_saber_off", FF_CHANNEL_WEAPON ) ); + } +#endif // _IMMERSION + } + } + else if ( self->s.weapon != WP_BRYAR_PISTOL ) + {//since player can't pick up bryar pistols, never drop those + self->s.weapon = WP_NONE; + G_RemoveWeaponModels( self ); + } + + self->s.powerups &= ~PW_REMOVE_AT_DEATH;//removes everything but electricity and force push + + //FIXME: do this on a callback? So people can't walk through long death anims? + //Maybe set on last frame? Would be cool for big blocking corpses if the never got set? + //self->contents = CONTENTS_CORPSE;//now done a second after death + /* + self->takedamage = qfalse; // no gibbing + if ( self->client->playerTeam == TEAM_PARASITE ) + { + self->contents = CONTENTS_NONE; // FIXME: temp fix + } + else + { + self->contents = CONTENTS_CORPSE; + self->maxs[2] = -8; + } + */ + if ( !self->s.number ) + {//player + self->contents = CONTENTS_CORPSE; + self->maxs[2] = -8; + } + self->clipmask&=~(CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP);//so dead NPC can fly off ledges + + //FACING========================================================== + if ( attacker && self->s.number == 0 ) + { + self->client->ps.stats[STAT_DEAD_YAW] = AngleNormalize180( self->client->ps.viewangles[YAW] ); + } + self->currentAngles[PITCH] = 0; + self->currentAngles[ROLL] = 0; + if ( self->NPC ) + { + self->NPC->desiredYaw = 0; + self->NPC->desiredPitch = 0; + self->NPC->confusionTime = 0; + self->NPC->charmedTime = 0; + if ( self->ghoul2.size() ) + { + if ( self->chestBolt != -1 ) + { + G_StopEffect("force/rage2", self->playerModel, self->chestBolt, self->s.number ); + } + if ( self->headBolt != -1 ) + { + G_StopEffect("force/confusion", self->playerModel, self->headBolt, self->s.number ); + } + WP_StopForceHealEffects( self ); + } + } + VectorCopy( self->currentAngles, self->client->ps.viewangles ); + //FACING========================================================== + if ( player && player->client && player->client->ps.viewEntity == self->s.number ) + {//I was the player's viewentity and I died, kick him back to his normal view + G_ClearViewEntity( player ); + } + else if ( !self->s.number && self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_NONE ) + { + G_ClearViewEntity( self ); + } + else if ( !self->s.number && self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_NONE ) + { + G_ClearViewEntity( self ); + } + + self->s.loopSound = 0; + + // remove powerups + memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) ); + + if ( (self->client->ps.eFlags&EF_HELD_BY_RANCOR) + || (self->client->ps.eFlags&EF_HELD_BY_SAND_CREATURE) + || (self->client->ps.eFlags&EF_HELD_BY_WAMPA) ) + {//do nothing special here + } + else if ( self->client->NPC_class == CLASS_MARK1 ) + { + Mark1_die( self, inflictor, attacker, damage, meansOfDeath, dflags, hitLoc ); + } + else if ( self->client->NPC_class == CLASS_INTERROGATOR ) + { + Interrogator_die( self, inflictor, attacker, damage, meansOfDeath, dflags, hitLoc ); + } + else if ( self->client->NPC_class == CLASS_GALAKMECH ) + {//FIXME: need keyframed explosions? + NPC_SetAnim( self, SETANIM_BOTH, BOTH_DEATH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + G_AddEvent( self, Q_irand(EV_DEATH1, EV_DEATH3), self->health ); + } + else if ( self->client->NPC_class == CLASS_ATST ) + {//FIXME: need keyframed explosions + if ( !self->s.number ) + { + G_DrivableATSTDie( self ); + } + anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 ); //initialize to good data + if ( anim != -1 ) + { + NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + else if ( self->s.number && self->message && meansOfDeath != MOD_SNIPER ) + {//imp with a key on his arm + //pick a death anim that leaves key visible + switch ( Q_irand( 0, 3 ) ) + { + case 0: + anim = BOTH_DEATH4; + break; + case 1: + anim = BOTH_DEATH21; + break; + case 2: + anim = BOTH_DEATH17; + break; + case 3: + default: + anim = BOTH_DEATH18; + break; + } + //FIXME: verify we have this anim? + NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( meansOfDeath == MOD_KNOCKOUT || meansOfDeath == MOD_MELEE ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else if ( meansOfDeath == MOD_FORCE_DRAIN ) + { + G_AddEvent( self, EV_WATER_DROWN, 0 ); + } + else if ( meansOfDeath == MOD_GAS ) + { + G_AddEvent( self, EV_WATER_DROWN, 0 ); + } + else + { + G_AddEvent( self, Q_irand(EV_DEATH1, EV_DEATH3), self->health ); + } + } + else if ( meansOfDeath == MOD_FALLING || (self->client->ps.legsAnim == BOTH_FALLDEATH1INAIR && self->client->ps.torsoAnim == BOTH_FALLDEATH1INAIR) || (self->client->ps.legsAnim == BOTH_FALLDEATH1 && self->client->ps.torsoAnim == BOTH_FALLDEATH1) ) + { + //FIXME: no good way to predict you're going to fall to your death... need falling bushes/triggers? + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE //in the air + && self->client->ps.velocity[2] < 0 //falling + && self->client->ps.legsAnim != BOTH_FALLDEATH1INAIR //not already in falling loop + && self->client->ps.torsoAnim != BOTH_FALLDEATH1INAIR )//not already in falling loop + { + NPC_SetAnim(self, SETANIM_BOTH, BOTH_FALLDEATH1INAIR, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + if ( !self->NPC ) + { + G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN + } + else if (!(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + { + G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN + //so we don't do this again + self->NPC->aiFlags |= NPCAI_DIE_ON_IMPACT; + //self->client->ps.gravity *= 0.5;//Fall a bit slower + self->client->ps.friction = 1; + } + } + else + { + int deathAnim = BOTH_FALLDEATH1LAND; + if ( PM_InOnGroundAnim( &self->client->ps ) ) + { + if ( AngleNormalize180(self->client->renderInfo.torsoAngles[PITCH]) < 0 ) + { + deathAnim = BOTH_DEATH_LYING_UP; //# Death anim when lying on back + } + else + { + deathAnim = BOTH_DEATH_LYING_DN; //# Death anim when lying on front + } + } + else if ( PM_InKnockDown( &self->client->ps ) ) + { + if ( AngleNormalize180(self->client->renderInfo.torsoAngles[PITCH]) < 0 ) + { + deathAnim = BOTH_DEATH_FALLING_UP; //# Death anim when falling on back + } + else + { + deathAnim = BOTH_DEATH_FALLING_DN; //# Death anim when falling on face + } + } + NPC_SetAnim(self, SETANIM_BOTH, deathAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + //HMM: check for nodrop? + G_SoundOnEnt( self, CHAN_BODY, "sound/player/fallsplat.wav" ); + if ( gi.VoiceVolume[self->s.number] + && self->NPC && (self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + {//I was talking, so cut it off... with a jump sound? + G_SoundOnEnt( self, CHAN_VOICE_ATTEN, "*pain100.wav" ); + } + } + } + else + {// normal death + anim = G_CheckSpecialDeathAnim( self, self->pos1, damage, meansOfDeath, hitLoc ); + if ( anim == -1 ) + { + if ( PM_InOnGroundAnim( &self->client->ps ) && PM_HasAnimation( self, BOTH_LYINGDEATH1 ) ) + {//on ground, need different death anim + anim = BOTH_LYINGDEATH1; + } + else if ( meansOfDeath == MOD_TRIGGER_HURT && (self->s.powerups&(1<client->ps.velocity, qtrue, qfalse ); + if ( cliff_fall == 2 ) + { + if ( !FlyingCreature( self ) && g_gravity->value > 0 ) + { + if ( !self->NPC ) + { + G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN + } + else if (!(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + { + G_SoundOnEnt( self, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN + self->NPC->aiFlags |= NPCAI_DIE_ON_IMPACT; + self->client->ps.friction = 0; + } + } + } + if ( self->client->ps.pm_time > 0 && self->client->ps.pm_flags & PMF_TIME_KNOCKBACK && self->client->ps.velocity[2] > 0 ) + { + float thrown, dot; + vec3_t throwdir, forward; + + AngleVectors(self->currentAngles, forward, NULL, NULL); + thrown = VectorNormalize2(self->client->ps.velocity, throwdir); + dot = DotProduct(forward, throwdir); + if ( thrown > 100 ) + { + if ( dot > 0.3 ) + {//falling forward + if ( cliff_fall == 2 && PM_HasAnimation( self, BOTH_FALLDEATH1 ) ) + { + anim = BOTH_FALLDEATH1; + } + else + { + switch ( Q_irand( 0, 7 ) ) + { + case 0: + case 1: + anim = BOTH_DEATH4; + break; + case 2: + anim = BOTH_DEATH16; + break; + case 3: + case 4: + case 5: + anim = BOTH_DEATH5; + break; + case 6: + anim = BOTH_DEATH8; + break; + case 7: + anim = BOTH_DEATH14; + break; + } + if ( PM_HasAnimation( self, anim )) + { + self->client->ps.gravity *= 0.8; + self->client->ps.friction = 0; + if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 ) + { + self->client->ps.velocity[2] = 100; + } + } + else + { + anim = -1; + } + } + } + else if ( dot < -0.3 ) + { + if ( thrown >= 250 && !Q_irand( 0, 3 ) ) + { + if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_DEATHBACKWARD1; + } + else + { + anim = BOTH_DEATHBACKWARD2; + } + } + else + { + switch ( Q_irand( 0, 7 ) ) + { + case 0: + case 1: + anim = BOTH_DEATH1; + break; + case 2: + case 3: + anim = BOTH_DEATH2; + break; + case 4: + case 5: + anim = BOTH_DEATH22; + break; + case 6: + case 7: + anim = BOTH_DEATH23; + break; + } + } + if ( PM_HasAnimation( self, anim ) ) + { + self->client->ps.gravity *= 0.8; + self->client->ps.friction = 0; + if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 ) + { + self->client->ps.velocity[2] = 100; + } + } + else + { + anim = -1; + } + } + else + {//falling to one of the sides + if ( cliff_fall == 2 && PM_HasAnimation( self, BOTH_FALLDEATH1 ) ) + { + anim = BOTH_FALLDEATH1; + if ( self->client->ps.velocity[2] > 0 && self->client->ps.velocity[2] < 100 ) + { + self->client->ps.velocity[2] = 100; + } + } + } + } + } + } + } + else + { + specialAnim = qtrue; + } + + if ( anim == -1 ) + { + if ( meansOfDeath == MOD_ELECTROCUTE + || (meansOfDeath == MOD_CRUSH && self->s.eFlags&EF_FORCE_GRIPPED) + || (meansOfDeath == MOD_FORCE_DRAIN && self->s.eFlags&EF_FORCE_DRAINED)) + {//electrocuted or choked to death + anim = BOTH_DEATH17; + } + else + { + anim = G_PickDeathAnim( self, self->pos1, damage, meansOfDeath, hitLoc ); + } + } + if ( anim == -1 ) + { + anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 ); //initialize to good data + //TEMP HACK: these spinny deaths should happen less often + if ( ( anim == BOTH_DEATH8 || anim == BOTH_DEATH14 ) && Q_irand( 0, 1 ) ) + { + anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 ); //initialize to good data + } + } + else if ( !PM_HasAnimation( self, anim ) ) + {//crap, still missing an anim, so pick one that we do have + anim = PM_PickAnim( self, BOTH_DEATH1, BOTH_DEATH25 ); //initialize to good data + } + + + if ( meansOfDeath == MOD_KNOCKOUT ) + { + //FIXME: knock-out sound, and don't remove me + G_AddEvent( self, EV_JUMP, 0 ); + G_UseTargets2( self, self, self->target2 ); + G_AlertTeam( self, attacker, 512, 32 ); + if ( self->NPC ) + {//stick around for a while + self->NPC->timeOfDeath = level.time + 10000; + } + } + else if ( meansOfDeath == MOD_GAS || meansOfDeath == MOD_FORCE_DRAIN ) + { + G_AddEvent( self, EV_WATER_DROWN, 0 ); + G_AlertTeam( self, attacker, 512, 32 ); + if ( self->NPC ) + {//stick around for a while + self->NPC->timeOfDeath = level.time + 10000; + } + } + else if ( meansOfDeath == MOD_SNIPER ) + { + gentity_t *tent; + vec3_t spot; + + VectorCopy( self->currentOrigin, spot ); + + self->flags |= FL_DISINTEGRATED; + self->svFlags |= SVF_BROADCAST; + tent = G_TempEntity( spot, EV_DISINTEGRATION ); + tent->s.eventParm = PW_DISRUPTION; + tent->svFlags |= SVF_BROADCAST; + tent->owner = self; + + G_AlertTeam( self, attacker, 512, 88 ); + + if ( self->playerModel >= 0 ) + { + // don't let 'em animate + gi.G2API_PauseBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, cg.time ); + gi.G2API_PauseBoneAnimIndex( &self->ghoul2[self->playerModel], self->motionBone, cg.time ); + gi.G2API_PauseBoneAnimIndex( &self->ghoul2[self->playerModel], self->lowerLumbarBone, cg.time ); + anim = -1; + } + + //not solid anymore + self->contents = 0; + self->maxs[2] = -8; + + if ( self->NPC ) + { + //need to pad deathtime some to stick around long enough for death effect to play + self->NPC->timeOfDeath = level.time + 2000; + } + } + else + { + if ( hitLoc == HL_HEAD + && !(dflags&DAMAGE_RADIUS) + && meansOfDeath!=MOD_REPEATER_ALT + && meansOfDeath!=MOD_FLECHETTE_ALT + && meansOfDeath!=MOD_ROCKET + && meansOfDeath!=MOD_ROCKET_ALT + && meansOfDeath!=MOD_CONC + && meansOfDeath!=MOD_THERMAL + && meansOfDeath!=MOD_THERMAL_ALT + && meansOfDeath!=MOD_DETPACK + && meansOfDeath!=MOD_LASERTRIP + && meansOfDeath!=MOD_LASERTRIP_ALT + && meansOfDeath!=MOD_EXPLOSIVE + && meansOfDeath!=MOD_EXPLOSIVE_SPLASH ) + {//no sound when killed by headshot (explosions don't count) + G_AlertTeam( self, attacker, 512, 0 ); + if ( gi.VoiceVolume[self->s.number] ) + {//I was talking, so cut it off... with a jump sound? + G_SoundOnEnt( self, CHAN_VOICE, "*jump1.wav" ); + } + } + else + { + if ( (self->client->ps.eFlags&EF_FORCE_GRIPPED) || (self->client->ps.eFlags&EF_FORCE_DRAINED) ) + {//killed while gripped - no loud scream + G_AlertTeam( self, attacker, 512, 32 ); + } + else if ( cliff_fall != 2 ) + { + if ( meansOfDeath == MOD_KNOCKOUT || meansOfDeath == MOD_MELEE ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else if ( meansOfDeath == MOD_GAS || meansOfDeath == MOD_FORCE_DRAIN ) + { + G_AddEvent( self, EV_WATER_DROWN, 0 ); + } + else + { + G_AddEvent( self, Q_irand(EV_DEATH1, EV_DEATH3), self->health ); + } + G_DeathAlert( self, attacker ); + } + else + {//screaming death is louder + G_AlertTeam( self, attacker, 512, 1024 ); + } + } + } + + if ( attacker && attacker->s.number == 0 ) + {//killed by player + //FIXME: this should really be wherever my body comes to rest... + AddSightEvent( attacker, self->currentOrigin, 384, AEL_DISCOVERED, 10 ); + //FIXME: danger event so that others will run away from this area since it's obviously dangerous + } + + if ( anim >= 0 )//can be -1 if it fails, -2 if it's already in a death anim + { + NPC_SetAnim(self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + } + } + + //do any dismemberment if there's any to do... + if ( (dflags&DAMAGE_DISMEMBER) + && G_DoDismemberment( self, self->pos1, meansOfDeath, damage, hitLoc ) + && !specialAnim ) + {//we did dismemberment and our death anim is okay to override + if ( hitLoc == HL_HAND_RT && self->locationDamage[hitLoc] >= Q3_INFINITE && cliff_fall != 2 && self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//just lost our right hand and we're on the ground, use the special anim + NPC_SetAnim( self, SETANIM_BOTH, BOTH_RIGHTHANDCHOPPEDOFF, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + + // don't allow player to respawn for a few seconds + self->client->respawnTime = level.time + 2000;//self->client->ps.legsAnimTimer; + +//rww - RAGDOLL_BEGIN + if (gi.Cvar_VariableIntegerValue("broadsword")) + { + if ( self->client && (!self->NPC || !G_StandardHumanoid( self ) ) ) + { + PM_SetLegsAnimTimer( self, &self->client->ps.legsAnimTimer, -1 ); + PM_SetTorsoAnimTimer( self, &self->client->ps.torsoAnimTimer, -1 ); + } + } + else + { + if ( self->client ) + { + PM_SetLegsAnimTimer( self, &self->client->ps.legsAnimTimer, -1 ); + PM_SetTorsoAnimTimer( self, &self->client->ps.torsoAnimTimer, -1 ); + } + } +//rww - RAGDOLL_END + + //Flying creatures should drop when killed + //FIXME: This may screw up certain things that expect to float even while dead + self->svFlags &= ~SVF_CUSTOM_GRAVITY; + + self->client->ps.pm_type = PM_DEAD; + self->client->ps.pm_flags &= ~PMF_STUCK_TO_WALL; + //need to update STAT_HEALTH here because ClientThink_real for self may happen before STAT_HEALTH is updated from self->health and pmove will stomp death anim with a move anim + self->client->ps.stats[STAT_HEALTH] = self->health; + + if ( self->NPC ) + {//If an NPC, make sure we start running our scripts again- this gets set to infinite while we fall to our deaths + self->NPC->nextBStateThink = level.time; + } + + if ( G_ActivateBehavior( self, BSET_DEATH ) ) + { + deathScript = qtrue; + } + + if ( self->NPC && (self->NPC->scriptFlags&SCF_FFDEATH) ) + { + if ( G_ActivateBehavior( self, BSET_FFDEATH ) ) + {//FIXME: should running this preclude running the normal deathscript? + deathScript = qtrue; + } + G_UseTargets2( self, self, self->target4 ); + } + + if ( !deathScript && !(self->svFlags&SVF_KILLED_SELF) ) + { + //Should no longer run scripts + //WARNING!!! DO NOT DO THIS WHILE RUNNING A SCRIPT, ICARUS WILL HANDLE IT, but it's bad + Quake3Game()->FreeEntity( self ); + } + + // Free up any timers we may have on us. + TIMER_Clear( self->s.number ); + + // Set pending objectives to failed + OBJ_SetPendingObjectives(self); + + gi.linkentity (self); + + self->bounceCount = -1; // This is a cheap hack for optimizing the pointcontents check in deadthink + if ( self->NPC ) + { + self->NPC->timeOfDeath = level.time;//this will change - used for debouncing post-death events + self->s.time = level.time;//this will not chage- this is actual time of death + } + + // Start any necessary death fx for this entity + DeathFX( self ); +} + +qboolean G_CheckForStrongAttackMomentum( gentity_t *self ) +{//see if our saber attack has too much momentum to be interrupted + if ( PM_PowerLevelForSaberAnim( &self->client->ps ) > FORCE_LEVEL_2 ) + {//strong attacks can't be interrupted + if ( PM_InAnimForSaberMove( self->client->ps.torsoAnim, self->client->ps.saberMove ) ) + {//our saberMove was not already interupted by some other anim (like pain) + if ( PM_SaberInStart( self->client->ps.saberMove ) ) + { + float animLength = PM_AnimLength( self->client->clientInfo.animFileIndex, (animNumber_t)self->client->ps.torsoAnim ); + if ( animLength - self->client->ps.torsoAnimTimer > 750 ) + {//start anim is already 3/4 of a second into it, can't interrupt it now + return qtrue; + } + } + else if ( PM_SaberInReturn( self->client->ps.saberMove ) ) + { + if ( self->client->ps.torsoAnimTimer > 750 ) + {//still have a good amount of time left in the return anim, can't interrupt it + return qtrue; + } + } + else + {//cannot interrupt actual transitions and attacks + return qtrue; + } + } + } + return qfalse; +} + +void PlayerPain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod, int hitLoc ) +{ + if ( self->client->NPC_class == CLASS_ATST ) + {//different kind of pain checking altogether + G_ATSTCheckPain( self, other, point, damage, mod, hitLoc ); + int blasterTest = gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "head_light_blaster_cann" ); + int chargerTest = gi.G2API_GetSurfaceRenderStatus( &self->ghoul2[self->playerModel], "head_concussion_charger" ); + if ( blasterTest && chargerTest ) + {//lost both side guns + //take away that weapon + self->client->ps.stats[STAT_WEAPONS] &= ~( 1 << WP_ATST_SIDE ); + //switch to primary guns + if ( self->client->ps.weapon == WP_ATST_SIDE ) + { + CG_ChangeWeapon( WP_ATST_MAIN ); + } + } + } + else + { + // play an apropriate pain sound + if ( level.time > self->painDebounceTime && !(self->flags & FL_GODMODE) ) + {//first time hit this frame and not in godmode + self->client->ps.damageEvent++; + if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) ) + { + if ( self->client->damage_blood ) + {//took damage myself, not just armor + if ( mod == MOD_GAS ) + { + //SIGH... because our choke sounds are inappropriately long, I have to debounce them in code! + if ( TIMER_Done( self, "gasChokeSound" ) ) + { + TIMER_Set( self, "gasChokeSound", Q_irand( 1000, 2000 ) ); + G_SpeechEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3) ); + } + if ( self->painDebounceTime <= level.time ) + { + self->painDebounceTime = level.time + 50; + } + } + else + { + G_AddEvent( self, EV_PAIN, self->health ); + } + } + } + } + if ( damage != -1 && (mod==MOD_MELEE || damage==0/*fake damage*/ || (Q_irand( 0, 10 ) <= damage && self->client->damage_blood)) ) + {//-1 == don't play pain anim + if ( ( ((mod==MOD_SABER||mod==MOD_MELEE)&&self->client->damage_blood) || mod == MOD_CRUSH ) && (self->s.weapon == WP_SABER||self->s.weapon==WP_MELEE||cg.renderingThirdPerson) )//FIXME: not only if using saber, but if in third person at all? But then 1st/third person functionality is different... + {//FIXME: only strong-level saber attacks should make me play pain anim? + if ( !G_CheckForStrongAttackMomentum( self ) && !PM_SpinningSaberAnim( self->client->ps.legsAnim ) + && !PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) + && !PM_InKnockDown( &self->client->ps ) ) + {//strong attacks and spins cannot be interrupted by pain, no pain when in knockdown + int parts = SETANIM_BOTH; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && + !PM_SpinningSaberAnim( self->client->ps.legsAnim ) && + !PM_FlippingAnim( self->client->ps.legsAnim ) && + !PM_InSpecialJump( self->client->ps.legsAnim ) && + !PM_RollingAnim( self->client->ps.legsAnim )&& + !PM_CrouchAnim( self->client->ps.legsAnim )&& + !PM_RunningAnim( self->client->ps.legsAnim )) + {//if on a surface and not in a spin or flip, play full body pain + parts = SETANIM_BOTH; + } + else + {//play pain just in torso + parts = SETANIM_TORSO; + } + if ( self->painDebounceTime < level.time ) + { + //temp HACK: these are the only 2 pain anims that look good when holding a saber + NPC_SetAnim( self, parts, PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in + //WTF - insn't working + if ( self->health < 10 && d_slowmodeath->integer > 5 ) + { + G_StartMatrixEffect( self ); + } + } + if ( parts == SETANIM_BOTH && damage > 30 || (self->painDebounceTime>level.time&&damage>10)) + {//took a lot of damage in 1 hit //or took 2 hits in quick succession + self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer; + self->client->ps.pm_time = self->client->ps.torsoAnimTimer; + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } + self->client->ps.weaponTime = self->client->ps.torsoAnimTimer; + self->attackDebounceTime = level.time + self->client->ps.torsoAnimTimer; + } + self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer; + } + } + } + if ( mod != MOD_GAS && self->painDebounceTime <= level.time ) + { + self->painDebounceTime = level.time + 700; + } +} +/* +================ +CheckArmor +================ +*/ +int CheckArmor (gentity_t *ent, int damage, int dflags, int mod) +{ + gclient_t *client; + int save; + int count; + + if (!damage) + return 0; + + client = ent->client; + + if (!client) + return 0; + + if ( (dflags&DAMAGE_NO_ARMOR) ) + { + // If this isn't a vehicle, leave. + if ( client->NPC_class != CLASS_VEHICLE ) + { + return 0; + } + } + + if (client->NPC_class==CLASS_ASSASSIN_DROID) + { + // The Assassin Always Completely Ignores These Damage Types + //----------------------------------------------------------- + if (mod==MOD_GAS || mod==MOD_IMPACT || mod==MOD_LAVA || mod==MOD_SLIME || mod==MOD_WATER || + mod==MOD_FORCE_GRIP || mod==MOD_FORCE_DRAIN || mod==MOD_SEEKER || mod==MOD_MELEE || + mod==MOD_BOWCASTER || mod==MOD_BRYAR || mod==MOD_BRYAR_ALT || mod==MOD_BLASTER || mod==MOD_BLASTER_ALT || + mod==MOD_SNIPER || mod==MOD_BOWCASTER || mod==MOD_BOWCASTER_ALT || mod==MOD_REPEATER || mod==MOD_REPEATER_ALT) + { + return damage; + } + + // The Assassin Always Takes Half Of These Damage Types + //------------------------------------------------------ + if (mod==MOD_GAS || mod==MOD_IMPACT || mod==MOD_LAVA || mod==MOD_SLIME || mod==MOD_WATER) + { + return damage/2; + } + + // If The Shield Is Not On, No Additional Protection + //--------------------------------------------------- + if (!(ent->flags&FL_SHIELDED)) + { + // He Does Ignore Half Saber Damage, Even Shield Down + //---------------------------------------------------- + if (mod==MOD_SABER) + { + return (int)((float)(damage)*0.75f); + } + return 0; + } + + // If The Shield Is Up, He Ignores These Damage Types + //---------------------------------------------------- + if (mod==MOD_SABER || mod==MOD_FLECHETTE || mod==MOD_FLECHETTE_ALT || mod==MOD_DISRUPTOR) + { + return damage; + } + + // The Demp Completely Destroys The Shield + //----------------------------------------- + if (mod==MOD_DEMP2 || mod==MOD_DEMP2_ALT) + { + client->ps.stats[STAT_ARMOR] = 0; + return 0; + } + + // Otherwise, The Shield Absorbs As Much Damage As Possible + //---------------------------------------------------------- + int previousArmor = client->ps.stats[STAT_ARMOR]; + client->ps.stats[STAT_ARMOR] -= damage; + if (client->ps.stats[STAT_ARMOR]<0) + { + client->ps.stats[STAT_ARMOR] = 0; + } + return (previousArmor - client->ps.stats[STAT_ARMOR]); + } + + + + if ( client->NPC_class == CLASS_GALAKMECH) + {//special case + if ( client->ps.stats[STAT_ARMOR] <= 0 ) + {//no shields + client->ps.powerups[PW_GALAK_SHIELD] = 0; + return 0; + } + else + {//shields take all the damage + client->ps.stats[STAT_ARMOR] -= damage; + if ( client->ps.stats[STAT_ARMOR] <= 0 ) + { + client->ps.powerups[PW_GALAK_SHIELD] = 0; + client->ps.stats[STAT_ARMOR] = 0; + } + return damage; + } + } + else + { + // armor + count = client->ps.stats[STAT_ARMOR]; + + // No damage to entity until armor is at less than 50% strength + if (count > (client->ps.stats[STAT_MAX_HEALTH]/2)) // MAX_HEALTH is considered max armor. Or so I'm told. + { + save = damage; + } + else + { + if ( !ent->s.number && client->NPC_class == CLASS_ATST ) + {//player in ATST... armor takes *all* the damage + save = damage; + } + else + { + save = ceil( (float) damage * ARMOR_PROTECTION ); + } + } + + //Always round up + if (damage == 1) + { + if ( client->ps.stats[STAT_ARMOR] > 0 ) + client->ps.stats[STAT_ARMOR] -= save; + //WTF? returns false 0 if armor absorbs only 1 point of damage + return 0; + } + + if (save >= count) + save = count; + + if (!save) + return 0; + + client->ps.stats[STAT_ARMOR] -= save; + + return save; + } +} + +extern void NPC_SetPainEvent( gentity_t *self ); +extern qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown = qfalse ); +extern qboolean Jedi_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir ); +void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ) +{ + if ( !self || !self->client || !attacker || !attacker->client ) + { + return; + } + + if ( self->client->NPC_class == CLASS_ROCKETTROOPER ) + { + return; + } + + if ( Boba_StopKnockdown( self, attacker, pushDir ) ) + { + return; + } + else if ( Jedi_StopKnockdown( self, attacker, pushDir ) ) + {//They can sometimes backflip instead of be knocked down + return; + } + else if ( PM_LockedAnim( self->client->ps.legsAnim ) ) + {//stuck doing something else + return; + } + else if ( Rosh_BeingHealed( self ) ) + { + return; + } + + //break out of a saberLock? + if ( self->client->ps.saberLockTime > level.time ) + { + if ( breakSaberLock ) + { + self->client->ps.saberLockTime = 0; + self->client->ps.saberLockEnemy = ENTITYNUM_NONE; + } + else + { + return; + } + } + + if ( self->health > 0 ) + { + if ( !self->s.number ) + { + NPC_SetPainEvent( self ); + } + else + { + GEntity_PainFunc( self, attacker, attacker, self->currentOrigin, 0, MOD_MELEE ); + } + + G_CheckLedgeDive( self, 72, pushDir, qfalse, qfalse ); + + if ( !PM_SpinningSaberAnim( self->client->ps.legsAnim ) + && !PM_FlippingAnim( self->client->ps.legsAnim ) + && !PM_RollingAnim( self->client->ps.legsAnim ) + && !PM_InKnockDown( &self->client->ps ) ) + { + int knockAnim = BOTH_KNOCKDOWN1;//default knockdown + if ( !self->s.number && ( strength < 300 ) )//!g_spskill->integer || + {//player only knocked down if pushed *hard* + return; + } + else if ( PM_CrouchAnim( self->client->ps.legsAnim ) ) + {//crouched knockdown + knockAnim = BOTH_KNOCKDOWN4; + } + else + {//plain old knockdown + vec3_t pLFwd, pLAngles = {0,self->client->ps.viewangles[YAW],0}; + AngleVectors( pLAngles, pLFwd, NULL, NULL ); + if ( DotProduct( pLFwd, pushDir ) > 0.2f ) + {//pushing him from behind + knockAnim = BOTH_KNOCKDOWN3; + } + else + {//pushing him from front + knockAnim = BOTH_KNOCKDOWN1; + } + } + if ( knockAnim == BOTH_KNOCKDOWN1 && strength > 150 ) + {//push *hard* + knockAnim = BOTH_KNOCKDOWN2; + } + NPC_SetAnim( self, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( self->s.number >= MAX_CLIENTS ) + {//randomize getup times + int addTime = Q_irand( -200, 200 ); + self->client->ps.legsAnimTimer += addTime; + self->client->ps.torsoAnimTimer += addTime; + } + else + {//player holds extra long so you have more time to decide to do the quick getup + if ( PM_KnockDownAnim( self->client->ps.legsAnim ) ) + { + self->client->ps.legsAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + self->client->ps.torsoAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + } + } + } + } +} + +void G_CheckKnockdown( gentity_t *targ, gentity_t *attacker, vec3_t newDir, int dflags, int mod ) +{ + if ( !targ || !attacker ) + { + return; + } + if ( !(dflags&DAMAGE_RADIUS) ) + {//not inherently explosive damage, check mod + if ( mod!=MOD_REPEATER_ALT + &&mod!=MOD_FLECHETTE_ALT + &&mod!=MOD_ROCKET + &&mod!=MOD_ROCKET_ALT + &&mod!=MOD_CONC + &&mod!=MOD_CONC_ALT + &&mod!=MOD_THERMAL + &&mod!=MOD_THERMAL_ALT + &&mod!=MOD_DETPACK + &&mod!=MOD_LASERTRIP + &&mod!=MOD_LASERTRIP_ALT + &&mod!=MOD_EXPLOSIVE + &&mod!=MOD_EXPLOSIVE_SPLASH ) + { + return; + } + } + + if ( !targ->client || targ->client->NPC_class == CLASS_PROTOCOL || !G_StandardHumanoid( targ ) ) + { + return; + } + + if ( targ->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//already in air + return; + } + + if ( !targ->s.number ) + {//player less likely to be knocked down + if ( !g_spskill->integer ) + {//never in easy + return; + } + if ( !cg.renderingThirdPerson || cg.zoomMode ) + {//never if not in chase camera view (so more likely with saber out) + return; + } + if ( g_spskill->integer == 1 ) + {//33% chance on medium + if ( Q_irand( 0, 2 ) ) + { + return; + } + } + else + {//50% chance on hard + if ( Q_irand( 0, 1 ) ) + { + return; + } + } + } + + float strength = VectorLength( targ->client->ps.velocity ); + if ( targ->client->ps.velocity[2] > 100 && strength > Q_irand( 150, 350 ) )//600 ) ) + {//explosive concussion possibly do a knockdown? + G_Knockdown( targ, attacker, newDir, strength, qtrue ); + } +} + +void G_ApplyKnockback( gentity_t *targ, vec3_t newDir, float knockback ) +{ + vec3_t kvel; + float mass; + + if ( targ + && targ->client + && ( targ->client->NPC_class == CLASS_ATST + || targ->client->NPC_class == CLASS_RANCOR + || targ->client->NPC_class == CLASS_SAND_CREATURE ) ) + {//much to large to *ever* throw + return; + } + + //--- TEMP TEST + if ( newDir[2] <= 0.0f ) + { + + newDir[2] += (( 0.0f - newDir[2] ) * 1.2f ); + } + + knockback *= 2.0f; + + if ( knockback > 120 ) + { + knockback = 120; + } + //--- TEMP TEST + + if ( targ->physicsBounce > 0 ) //overide the mass + mass = targ->physicsBounce; + else + mass = 200; + + if ( g_gravity->value > 0 ) + { + VectorScale( newDir, g_knockback->value * (float)knockback / mass * 0.8, kvel ); + kvel[2] = newDir[2] * ( g_knockback->value * (float)knockback ) / ( mass * 1.5 ) + 20; + } + else + { + VectorScale( newDir, g_knockback->value * (float)knockback / mass, kvel ); + } + + if ( targ->client ) + { + VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity ); + } + else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP ) + { + VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta ); + VectorCopy( targ->currentOrigin, targ->s.pos.trBase ); + targ->s.pos.trTime = level.time; + } + + // set the timer so that the other client can't cancel + // out the movement immediately + if ( targ->client && !targ->client->ps.pm_time ) + { + int t; + + t = knockback * 2; + if ( t < 50 ) { + t = 50; + } + if ( t > 200 ) { + t = 200; + } + targ->client->ps.pm_time = t; + targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } +} + +static int G_CheckForLedge( gentity_t *self, vec3_t fallCheckDir, float checkDist ) +{ + vec3_t start, end; + trace_t tr; + + VectorMA( self->currentOrigin, checkDist, fallCheckDir, end ); + //Should have clip burshes masked out by now and have bbox resized to death size + gi.trace( &tr, self->currentOrigin, self->mins, self->maxs, end, self->s.number, self->clipmask ); + if ( tr.allsolid || tr.startsolid ) + { + return 0; + } + VectorCopy( tr.endpos, start ); + VectorCopy( start, end ); + end[2] -= 256; + + gi.trace( &tr, start, self->mins, self->maxs, end, self->s.number, self->clipmask ); + if ( tr.allsolid || tr.startsolid ) + { + return 0; + } + if ( tr.fraction >= 1.0 ) + { + return (start[2]-tr.endpos[2]); + } + return 0; +} + +static void G_FriendlyFireReaction( gentity_t *self, gentity_t *other, int dflags ) +{ + if ( (!player->client->ps.viewEntity || other->s.number != player->client->ps.viewEntity)) + {//hit by a teammate + if ( other != self->enemy && self != other->enemy ) + {//we weren't already enemies + if ( self->enemy || other->enemy || (other->s.number&&other->s.number!=player->client->ps.viewEntity) ) + {//if one of us actually has an enemy already, it's okay, just an accident OR wasn't hit by player or someone controlled by player OR player hit ally and didn't get 25% chance of getting mad (FIXME:accumulate anger+base on diff?) + return; + } + else if ( self->NPC && !other->s.number )//should be assumed, but... + {//dammit, stop that! + if ( !(dflags&DAMAGE_RADIUS) ) + { + //if it's radius damage, ignore it + if ( self->NPC->ffireDebounce < level.time ) + { + //FIXME: way something? NEED DIALOGUE + self->NPC->ffireCount++; + //Com_Printf( "incr: %d < %d\n", self->NPC->ffireCount, 3+((2-g_spskill->integer)*2) ); + self->NPC->ffireDebounce = level.time + 500; + } + } + } + } + } +} + +float damageModifier[HL_MAX] = +{ + 1.0f, //HL_NONE, + 0.25f, //HL_FOOT_RT, + 0.25f, //HL_FOOT_LT, + 0.75f, //HL_LEG_RT, + 0.75f, //HL_LEG_LT, + 1.0f, //HL_WAIST, + 1.0f, //HL_BACK_RT, + 1.0f, //HL_BACK_LT, + 1.0f, //HL_BACK, + 1.0f, //HL_CHEST_RT, + 1.0f, //HL_CHEST_LT, + 1.0f, //HL_CHEST, + 0.5f, //HL_ARM_RT, + 0.5f, //HL_ARM_LT, + 0.25f, //HL_HAND_RT, + 0.25f, //HL_HAND_LT, + 2.0f, //HL_HEAD, + 1.0f, //HL_GENERIC1, + 1.0f, //HL_GENERIC2, + 1.0f, //HL_GENERIC3, + 1.0f, //HL_GENERIC4, + 1.0f, //HL_GENERIC5, + 1.0f, //HL_GENERIC6, +}; + +void G_TrackWeaponUsage( gentity_t *self, gentity_t *inflictor, int add, int mod ) +{ + if ( !self || !self->client || self->s.number ) + {//player only + return; + } + int weapon = WP_NONE; + //FIXME: need to check the MOD to find out what weapon (if *any*) actually did the killing + if ( inflictor && !inflictor->client && mod != MOD_SABER && inflictor->lastEnemy && inflictor->lastEnemy != self ) + {//a missile that was reflected, ie: not owned by me originally + if ( inflictor->owner == self && self->s.weapon == WP_SABER ) + {//we reflected it + weapon = WP_SABER; + } + } + if ( weapon == WP_NONE ) + { + switch ( mod ) + { + case MOD_SABER: + weapon = WP_SABER; + break; + case MOD_BRYAR: + case MOD_BRYAR_ALT: + weapon = WP_BRYAR_PISTOL; + break; + case MOD_BLASTER: + case MOD_BLASTER_ALT: + weapon = WP_BLASTER; + break; + case MOD_DISRUPTOR: + case MOD_SNIPER: + weapon = WP_DISRUPTOR; + break; + case MOD_BOWCASTER: + case MOD_BOWCASTER_ALT: + weapon = WP_BOWCASTER; + break; + case MOD_REPEATER: + case MOD_REPEATER_ALT: + weapon = WP_REPEATER; + break; + case MOD_DEMP2: + case MOD_DEMP2_ALT: + weapon = WP_DEMP2; + break; + case MOD_FLECHETTE: + case MOD_FLECHETTE_ALT: + weapon = WP_FLECHETTE; + break; + case MOD_ROCKET: + case MOD_ROCKET_ALT: + weapon = WP_ROCKET_LAUNCHER; + break; + case MOD_CONC: + case MOD_CONC_ALT: + weapon = WP_CONCUSSION; + break; + case MOD_THERMAL: + case MOD_THERMAL_ALT: + weapon = WP_THERMAL; + break; + case MOD_DETPACK: + weapon = WP_DET_PACK; + break; + case MOD_LASERTRIP: + case MOD_LASERTRIP_ALT: + weapon = WP_TRIP_MINE; + break; + case MOD_MELEE: + if ( self->s.weapon == WP_STUN_BATON ) + { + weapon = WP_STUN_BATON; + } + else if ( self->s.weapon == WP_MELEE ) + { + weapon = WP_MELEE; + } + break; + } + } + if ( weapon != WP_NONE ) + { + self->client->sess.missionStats.weaponUsed[weapon] += add; + } +} + +qboolean G_NonLocationSpecificDamage( int meansOfDeath ) +{ + if ( meansOfDeath == MOD_EXPLOSIVE + || meansOfDeath == MOD_REPEATER_ALT + || meansOfDeath == MOD_FLECHETTE_ALT + || meansOfDeath == MOD_ROCKET + || meansOfDeath == MOD_ROCKET_ALT + || meansOfDeath == MOD_CONC + || meansOfDeath == MOD_THERMAL + || meansOfDeath == MOD_THERMAL_ALT + || meansOfDeath == MOD_DETPACK + || meansOfDeath == MOD_LASERTRIP + || meansOfDeath == MOD_LASERTRIP_ALT + || meansOfDeath == MOD_MELEE + || meansOfDeath == MOD_FORCE_GRIP + || meansOfDeath == MOD_KNOCKOUT + || meansOfDeath == MOD_CRUSH + || meansOfDeath == MOD_EXPLOSIVE_SPLASH ) + { + return qtrue; + } + return qfalse; +} + +qboolean G_ImmuneToGas( gentity_t *ent ) +{ + if ( !ent || !ent->client ) + {//only effects living clients + return qtrue; + } + if ( ent->s.weapon == WP_NOGHRI_STICK//assumes user is immune + || ent->client->NPC_class == CLASS_HAZARD_TROOPER + || ent->client->NPC_class == CLASS_ATST + || ent->client->NPC_class == CLASS_GONK + || ent->client->NPC_class == CLASS_SAND_CREATURE + || ent->client->NPC_class == CLASS_INTERROGATOR + || ent->client->NPC_class == CLASS_MARK1 + || ent->client->NPC_class == CLASS_MARK2 + || ent->client->NPC_class == CLASS_GALAKMECH + || ent->client->NPC_class == CLASS_MOUSE + || ent->client->NPC_class == CLASS_PROBE // droid + || ent->client->NPC_class == CLASS_PROTOCOL // droid + || ent->client->NPC_class == CLASS_R2D2 // droid + || ent->client->NPC_class == CLASS_R5D2 // droid + || ent->client->NPC_class == CLASS_REMOTE + || ent->client->NPC_class == CLASS_SEEKER // droid + || ent->client->NPC_class == CLASS_SENTRY + || ent->client->NPC_class == CLASS_SWAMPTROOPER + || ent->client->NPC_class == CLASS_TUSKEN + || ent->client->NPC_class == CLASS_BOBAFETT + || ent->client->NPC_class == CLASS_ROCKETTROOPER + || ent->client->NPC_class == CLASS_SABER_DROID + || ent->client->NPC_class == CLASS_ASSASSIN_DROID + || ent->client->NPC_class == CLASS_HAZARD_TROOPER + || ent->client->NPC_class == CLASS_VEHICLE ) + { + return qtrue; + } + return qfalse; +} + +extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent ); +extern void G_StartRoll( gentity_t *ent, int anim ); +extern void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); + +/* +============ +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 for knockback +point point at which the damage is being inflicted, used for headshots +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +inflictor, attacker, dir, and point can be NULL for environmental effects + +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_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_NO_PROTECTION kills godmode, armor, everything + DAMAGE_NO_HIT_LOC Damage not based on hit location +============ +*/ +void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, const vec3_t dir, const vec3_t point, int damage, int dflags, int mod, int hitLoc ) +{ + gclient_t *client; + int take; + int save; + int asave = 0; + int knockback; + vec3_t newDir; + qboolean alreadyDead = qfalse; + + if (!targ->takedamage) { + if ( targ->client //client + && targ->client->NPC_class == CLASS_SAND_CREATURE//sand creature + && targ->activator//something in our mouth + && targ->activator == inflictor )//inflictor of damage is the thing in our mouth + {//being damaged by the thing in our mouth, allow the damage + } + else + { + return; + } + } + + if ( targ->health <= 0 && !targ->client ) + { // allow corpses to be disintegrated + if( mod != MOD_SNIPER || (targ->flags & FL_DISINTEGRATED) ) + return; + } + + // if we are the player and we are locked to an emplaced gun, we have to reroute damage to the gun....sigh. + if ( targ->s.eFlags & EF_LOCKED_TO_WEAPON + && targ->s.number == 0 + && targ->owner + && !targ->owner->bounceCount //not an EWeb + && !( targ->owner->flags & FL_GODMODE )) + { + // swapping the gun into our place to absorb our damage + targ = targ->owner; + } + + if ( (targ->flags&FL_SHIELDED) && mod != MOD_SABER && !targ->client) + {//magnetically protected, this thing can only be damaged by lightsabers + return; + } + + if ( (targ->flags&FL_DMG_BY_SABER_ONLY) && mod != MOD_SABER ) + {//can only be damaged by lightsabers (but no shield... yeah, it's redundant, but whattayagonnado?) + return; + } + + if (( targ->flags & FL_DMG_BY_HEAVY_WEAP_ONLY ) && !( dflags & DAMAGE_HEAVY_WEAP_CLASS )) + { + // can only be damaged by an heavy type weapon...but impacting missile was in the heavy weap class...so we just aren't taking damage from this missile + return; + } + + if ( (targ->svFlags&SVF_BBRUSH) + || (!targ->client && Q_stricmp( "misc_model_breakable", targ->classname ) == 0 ) )//FIXME: flag misc_model_breakables? + {//breakable brush or misc_model_breakable + if ( targ->NPC_targetname ) + {//only a certain attacker can destroy this + if ( !attacker + || !attacker->targetname + || Q_stricmp( targ->NPC_targetname, attacker->targetname ) != 0 ) + {//and it's not this one, do nothing + return; + } + } + } + + if ( targ->client && targ->client->NPC_class == CLASS_ATST ) + { + // extra checks can be done here + if ( mod == MOD_SNIPER + || mod == MOD_DISRUPTOR + || mod == MOD_CONC_ALT ) + { + // disruptor does not hurt an atst + return; + } + } + if ( targ->client + && targ->client->NPC_class == CLASS_RANCOR + && (!attacker||!attacker->client||attacker->client->NPC_class!=CLASS_RANCOR) ) + { + // I guess always do 10 points of damage...feel free to tweak as needed + if ( damage < 10 ) + {//ignore piddly little damage + damage = 0; + } + else if ( damage >= 10 ) + { + damage = 10; + } + } + else if ( mod == MOD_SABER ) + {//sabers do less damage to mark1's and atst's, and to hazard troopers and assassin droids + if ( targ->client ) + { + if ( targ->client->NPC_class == CLASS_ATST + || targ->client->NPC_class == CLASS_MARK1 ) + { + // I guess always do 5 points of damage...feel free to tweak as needed + if ( damage > 5 ) + { + damage = 5; + } + } + /* + //NOTE: a more controlled way to do the class-specific saber immunities, if we want + else if ( targ->client->NPC_class == CLASS_ASSASSIN_DROID ) + {//takes 2 hits to kill on easy, 3 on medium, 4 on hard + int maxDamage = ceil((float)targ->max_health/(2.0f+g_spskill->value)); + if ( damage > maxDamage ) + { + damage = maxDamage; + } + } + else if ( targ->client->NPC_class == CLASS_HAZARD_TROOPER ) + {//takes 3 hits to kill on easy, 4 on medium, 5 on hard + int maxDamage = ceil((float)targ->max_health/(3.0f+g_spskill->value)); + if ( damage > maxDamage ) + { + damage = maxDamage; + } + } + */ + } + } + + if ( !inflictor ) { + inflictor = &g_entities[ENTITYNUM_WORLD]; + } + if ( !attacker ) { + attacker = &g_entities[ENTITYNUM_WORLD]; + } + + // no more weakling allies! +// if ( attacker->s.number != 0 && damage >= 2 && targ->s.number != 0 && attacker->client && attacker->client->playerTeam == TEAM_PLAYER ) +// {//player-helpers do only half damage to enemies +// damage = ceil((float)damage/2.0f); +// } + + client = targ->client; + + if ( client ) { + if ( client->noclip && !targ->s.number ) { + return; + } + } + + if ( mod == MOD_GAS ) + {//certain NPCs are immune + if ( G_ImmuneToGas( targ ) ) + {//immune + return; + } + dflags |= DAMAGE_NO_ARMOR; + } + if ( dflags&DAMAGE_NO_DAMAGE ) + { + damage = 0; + } + + if ( dir == NULL ) + { + dflags |= DAMAGE_NO_KNOCKBACK; + } + else + { + VectorNormalize2( dir, newDir ); + } + + if ( targ->s.number != 0 ) + {//not the player + if ( (targ->flags&FL_GODMODE) || (targ->flags&FL_UNDYING) ) + {//have god or undying on, so ignore no protection flag + dflags &= ~DAMAGE_NO_PROTECTION; + } + } + + if ( client && PM_InOnGroundAnim( &client->ps )) + { + dflags |= DAMAGE_NO_KNOCKBACK; + } + if ( !attacker->s.number && targ->client && attacker->client && targ->client->playerTeam == attacker->client->playerTeam ) + {//player doesn't do knockback against allies unless he kills them + dflags |= DAMAGE_DEATH_KNOCKBACK; + } + + if (targ->client && (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) ) + { + TIMER_Set(targ, "DEMP2_StunTime", Q_irand(1000, 2000)); + } + + if ((client) && + (mod==MOD_DEMP2 || mod==MOD_DEMP2_ALT) && + ( + client->NPC_class == CLASS_SABER_DROID || + client->NPC_class == CLASS_ASSASSIN_DROID || + client->NPC_class == CLASS_GONK || + client->NPC_class == CLASS_MOUSE || + client->NPC_class == CLASS_PROBE || + client->NPC_class == CLASS_PROTOCOL || + client->NPC_class == CLASS_R2D2 || + client->NPC_class == CLASS_R5D2 || + client->NPC_class == CLASS_SEEKER || + client->NPC_class == CLASS_INTERROGATOR + ) + ) + { + damage *= 7; + } + + if ( client && client->NPC_class == CLASS_HAZARD_TROOPER ) + { + if ( mod == MOD_SABER + && damage>0 + && !(dflags&DAMAGE_NO_PROTECTION) ) + { + damage /= 10; + } + } + + if ( client + && client->NPC_class == CLASS_GALAKMECH + && !(dflags&DAMAGE_NO_PROTECTION) ) + {//hit Galak + if ( client->ps.stats[STAT_ARMOR] > 0 ) + {//shields are up + dflags &= ~DAMAGE_NO_ARMOR;//always affect armor + if ( mod == MOD_ELECTROCUTE + || mod == MOD_DEMP2 + || mod == MOD_DEMP2_ALT ) + {//shield protects us from this + damage = 0; + } + } + else + {//shields down + if ( mod == MOD_MELEE + || (mod == MOD_CRUSH && attacker && attacker->client) ) + {//Galak takes no impact damage + return; + } + if ( (dflags & DAMAGE_RADIUS) + || mod == MOD_REPEATER_ALT + || mod == MOD_FLECHETTE_ALT + || mod == MOD_ROCKET + || mod == MOD_ROCKET_ALT + || mod == MOD_CONC + || mod == MOD_THERMAL + || mod == MOD_THERMAL_ALT + || mod == MOD_DETPACK + || mod == MOD_LASERTRIP + || mod == MOD_LASERTRIP_ALT + || mod == MOD_EXPLOSIVE_SPLASH + || mod == MOD_ENERGY_SPLASH + || mod == MOD_SABER ) + {//galak without shields takes quarter damage from explosives and lightsaber + damage = ceil((float)damage/4.0f); + } + } + } + + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) + { + if ( client ) + { + if ( client->NPC_class == CLASS_PROTOCOL || client->NPC_class == CLASS_SEEKER || + client->NPC_class == CLASS_R2D2 || client->NPC_class == CLASS_R5D2 || + client->NPC_class == CLASS_MOUSE || client->NPC_class == CLASS_GONK ) + { + // DEMP2 does more damage to these guys. + damage *= 2; + } + else if ( client->NPC_class == CLASS_PROBE || client->NPC_class == CLASS_INTERROGATOR || + client->NPC_class == CLASS_MARK1 || client->NPC_class == CLASS_MARK2 || client->NPC_class == CLASS_SENTRY || + client->NPC_class == CLASS_ATST ) + { + // DEMP2 does way more damage to these guys. + damage *= 5; + } + } + else if ( targ->s.weapon == WP_TURRET ) + { + damage *= 6;// more damage to turret things + } + } + + if (targ + && targ->client + && !(dflags&DAMAGE_NO_PROTECTION) + && !(dflags&DAMAGE_DIE_ON_IMPACT) )//falling to you death ignores force protect and force rage (but obeys godmode and undying flags) + {//force protections + //rage + if ( (targ->client->ps.forcePowersActive & (1 << FP_RAGE))) + { + damage = floor((float)damage/(float)(targ->client->ps.forcePowerLevel[FP_RAGE]*2)); + } + //protect + if ( (targ->client->ps.forcePowersActive & (1 << FP_PROTECT)) ) + { + /* + qboolean doSound = qfalse; + switch ( targ->client->ps.forcePowerLevel[FP_PROTECT] ) + { + case FORCE_LEVEL_3: + //NOTE: purposely falls through + switch ( mod ) + { + case MOD_REPEATER_ALT: + case MOD_FLECHETTE_ALT: + case MOD_ROCKET: + case MOD_ROCKET_ALT: + case MOD_CONC: + case MOD_THERMAL: + case MOD_THERMAL_ALT: + case MOD_DETPACK: + case MOD_LASERTRIP: + case MOD_LASERTRIP_ALT: + case MOD_EMPLACED: + case MOD_EXPLOSIVE: + case MOD_EXPLOSIVE_SPLASH: + case MOD_CRUSH: + doSound = (Q_irand(0,4)==0); + damage = floor((float)damage/(float)(targ->client->ps.forcePowerLevel[FP_PROTECT]-1)); + break; + } + case FORCE_LEVEL_2: + //NOTE: purposely falls through + switch ( mod ) + { + case MOD_SABER: + case MOD_DISRUPTOR: + case MOD_SNIPER: + case MOD_CONC_ALT: + case MOD_BOWCASTER: + case MOD_BOWCASTER_ALT: + case MOD_DEMP2: + case MOD_DEMP2_ALT: + case MOD_ENERGY: + case MOD_ENERGY_SPLASH: + case MOD_ELECTROCUTE: + doSound = (Q_irand(0,4)==0); + damage = floor((float)damage/(float)(targ->client->ps.forcePowerLevel[FP_PROTECT])); + break; + } + case FORCE_LEVEL_1: + switch ( mod ) + { + case MOD_BRYAR: + case MOD_BRYAR_ALT: + case MOD_BLASTER: + case MOD_BLASTER_ALT: + case MOD_REPEATER: + case MOD_FLECHETTE: + case MOD_WATER: + case MOD_SLIME: + case MOD_LAVA: + case MOD_FALLING: + doSound = (Q_irand(0,4)==0); + damage = floor((float)damage/(float)(targ->client->ps.forcePowerLevel[FP_PROTECT]+1)); + break; + } + break; + } + */ + //New way: just cut all physical damage by force level + if ( mod == MOD_FALLING + && targ->NPC + && (targ->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + {//if falling to your death, protect can't save you + } + else + { + qboolean doSound = qfalse; + switch ( mod ) + { + case MOD_CRUSH: + if ( attacker && attacker->client ) + {//need to still be crushed by AT-ST + break; + } + case MOD_REPEATER_ALT: + case MOD_FLECHETTE_ALT: + case MOD_ROCKET: + case MOD_ROCKET_ALT: + case MOD_CONC: + case MOD_THERMAL: + case MOD_THERMAL_ALT: + case MOD_DETPACK: + case MOD_LASERTRIP: + case MOD_LASERTRIP_ALT: + case MOD_EMPLACED: + case MOD_EXPLOSIVE: + case MOD_EXPLOSIVE_SPLASH: + case MOD_SABER: + case MOD_DISRUPTOR: + case MOD_SNIPER: + case MOD_CONC_ALT: + case MOD_BOWCASTER: + case MOD_BOWCASTER_ALT: + case MOD_DEMP2: + case MOD_DEMP2_ALT: + case MOD_ENERGY: + case MOD_ENERGY_SPLASH: + case MOD_ELECTROCUTE: + case MOD_BRYAR: + case MOD_BRYAR_ALT: + case MOD_BLASTER: + case MOD_BLASTER_ALT: + case MOD_REPEATER: + case MOD_FLECHETTE: + case MOD_WATER: + case MOD_SLIME: + case MOD_LAVA: + case MOD_FALLING: + case MOD_MELEE: + doSound = (Q_irand(0,4)==0); + switch ( targ->client->ps.forcePowerLevel[FP_PROTECT] ) + { + case FORCE_LEVEL_4: + //je suis invincible!!! + if ( targ->client + && attacker->client + && targ->client->playerTeam == attacker->client->playerTeam + && (!targ->NPC || !targ->NPC->charmedTime) ) + {//complain, but don't turn on them + G_FriendlyFireReaction( targ, attacker, dflags ); + } + return; + break; + case FORCE_LEVEL_3: + //one-tenth damage + if ( damage <= 1 ) + { + damage = 0; + } + else + { + damage = ceil((float)damage*0.25f);//was 0.1f); + } + break; + case FORCE_LEVEL_2: + //half damage + if ( damage <= 1 ) + { + damage = 0; + } + else + { + damage = ceil((float)damage*0.5f); + } + break; + case FORCE_LEVEL_1: + //three-quarters damage + if ( damage <= 1 ) + { + damage = 0; + } + else + { + damage = ceil((float)damage*0.75f); + } + break; + } + break; + } + if ( doSound ) + { + //make protect sound + G_SoundOnEnt( targ, CHAN_ITEM, "sound/weapons/force/protecthit.wav" ); + } + } + } + //absorb + /* + if ( (targ->client->ps.forcePowersActive & (1 << FP_ABSORB)) ) + { + if ( mod == MOD_FORCE_LIGHTNING + || mod == MOD_FORCE_GRIP + || mod == MOD_FORCE_DRAIN ) + { + int absorbed = targ->client->ps.forcePowerLevel[FP_ABSORB]*5; + damage -= absorbed; + if ( damage < 0 ) + { + absorbed += damage; + damage = 0; + } + //absorb the energy + //make absorb sound + G_SoundOnEnt( targ, CHAN_ITEM, "sound/weapons/force/absorbhit.wav" ); + targ->client->ps.forcePower += absorbed; + } + } + */ + } + + knockback = damage; + + //Attempt to apply extra knockback + if ( dflags & DAMAGE_EXTRA_KNOCKBACK ) + { + knockback *= 2; + } + + if ( knockback > 200 ) { + knockback = 200; + } + + if ( targ->client + && (targ->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_PROTECT] == FORCE_LEVEL_3 ) + {//pretend there was no damage? + knockback = 0; + } + else if ( mod == MOD_CRUSH ) + { + knockback = 0; + } + else if ( targ->flags & FL_NO_KNOCKBACK ) + { + knockback = 0; + } + else if ( targ->NPC + && targ->NPC->jumpState == JS_JUMPING ) + { + knockback = 0; + } + else if ( attacker->s.number >= MAX_CLIENTS//an NPC fired + && targ->client //hit a client + && attacker->client //attacker is a client + && targ->client->playerTeam == attacker->client->playerTeam )//on same team + {//crap, ignore knockback + knockback = 0; + } + else if ( dflags & DAMAGE_NO_KNOCKBACK ) + { + knockback = 0; + } + // figure momentum add, even if the damage won't be taken + if ( knockback && !(dflags&DAMAGE_DEATH_KNOCKBACK) ) //&& targ->client + { + G_ApplyKnockback( targ, newDir, knockback ); + G_CheckKnockdown( targ, attacker, newDir, dflags, mod ); + } + + // check for godmode, completely getting out of the damage + if ( ( (targ->flags&FL_GODMODE) || (targ->client&&targ->client->ps.powerups[PW_INVINCIBLE]>level.time) ) + && !(dflags&DAMAGE_NO_PROTECTION) ) + { + if ( targ->client + && attacker->client + && targ->client->playerTeam == attacker->client->playerTeam + && (!targ->NPC || !targ->NPC->charmedTime) ) + {//complain, but don't turn on them + G_FriendlyFireReaction( targ, attacker, dflags ); + } + return; + } + + // Check for team damage + /* + if ( targ != attacker && !(dflags&DAMAGE_IGNORE_TEAM) && OnSameTeam (targ, attacker) ) + {//on same team + if ( !targ->client ) + {//a non-player object should never take damage from an ent on the same team + return; + } + + if ( attacker->client && attacker->client->playerTeam == targ->noDamageTeam ) + {//NPC or player shot an object on his own team + return; + } + + if ( attacker->s.number != 0 && targ->s.number != 0 &&//player not involved in any way in this exchange + attacker->client && targ->client &&//two NPCs + attacker->client->playerTeam == targ->client->playerTeam ) //on the same team + {//NPCs on same team don't hurt each other + return; + } + + if ( targ->s.number == 0 &&//player was hit + attacker->client && targ->client &&//by an NPC + attacker->client->playerTeam == TEAM_PLAYER ) //on the same team + { + if ( attacker->enemy != targ )//by accident + {//do no damage, no armor loss, no reaction, run no scripts + return; + } + } + } + */ + + // add to the attacker's hit counter + if ( attacker->client && targ != attacker && targ->health > 0 ) { + if ( OnSameTeam( targ, attacker ) ) { +// attacker->client->ps.persistant[PERS_HITS] -= damage; + } else { +// attacker->client->ps.persistant[PERS_HITS] += damage; + } + } + + take = damage; + save = 0; + + //FIXME: Do not use this method of difficulty changing + // Scale the amount of damage given to the player based on the skill setting + /* + if ( targ->s.number == 0 && targ != attacker ) + { + take *= ( g_spskill->integer + 1) * 0.75; + } + + if ( take < 1 ) { + take = 1; + } + */ + if ( client ) + { + //don't lose armor if on same team + // save some from armor + asave = CheckArmor (targ, take, dflags, mod); + if ( !asave ) + {//nothing was absorbed (or just ran out?) + } + else if ( targ->client->NPC_class != CLASS_VEHICLE ) + {//vehicles don't have personal shields + targ->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; + if ( targ->client->ps.stats[STAT_ARMOR] <= 0 ) + {//all out of armor + //remove Galak's shield + targ->client->ps.powerups[PW_BATTLESUIT] = 0; + } + } + + if (mod==MOD_SLIME || mod==MOD_LAVA) + { + // Hazard Troopers Don't Mind Acid Rain + if (targ->client->NPC_class == CLASS_HAZARD_TROOPER + && !(dflags&DAMAGE_NO_PROTECTION) ) + { + take = 0; + } + + if (mod==MOD_SLIME) + { + trace_t testTrace; + vec3_t testDirection; + vec3_t testStartPos; + vec3_t testEndPos; + //int numPuffs = Q_irand(1, 2); + + //for (int i=0; icurrentOrigin, 60.0f, testDirection, testStartPos); + VectorCopy(targ->currentOrigin, testEndPos); + testEndPos[0] += (random() * 8.0f) - 4.0f; + testEndPos[1] += (random() * 8.0f) - 4.0f; + testEndPos[2] += (random() * 8.0f); + + gi.trace (&testTrace, testStartPos, NULL, NULL, testEndPos, ENTITYNUM_NONE, MASK_SHOT, G2_COLLIDE); + + if (!testTrace.startsolid && + !testTrace.allsolid && + testTrace.entityNum==targ->s.number && + testTrace.G2CollisionMap[0].mEntityNum!=-1) + { + G_PlayEffect( "world/acid_fizz", testTrace.G2CollisionMap[0].mCollisionPosition ); + } +// CG_DrawEdge(testStartPos, testEndPos, EDGE_IMPACT_POSSIBLE); + float chanceOfFizz = gi.WE_GetChanceOfSaberFizz(); + TIMER_Set(targ, "AcidPainDebounce", 200 + (10000.0f * random() * chanceOfFizz)); + hitLoc = HL_CHEST; + } + } + } + + take -= asave; + + if ( targ->client->NPC_class == CLASS_VEHICLE ) + { + if ( ( targ->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL ) ) + { + //((CVehicleNPC *)targ->NPC)->m_ulFlags |= CVehicleNPC::VEH_BUCKING; + } + + if ( (damage > 0) && // Actually took some damage + (mod!=MOD_SABER) && // and damage didn't come from a saber + (targ->m_pVehicle->m_pVehicleInfo->type==VH_SPEEDER) && // and is a speeder + // (targ->client->ps.speed > 30.0f) && // and is moving + (attacker) && // and there is an attacker + (attacker->client) && // who is a client + (attacker->s.numbercurrentAngles, actorFwd, 0, 0); + + + Vehicle_t* pVeh = G_IsRidingVehicle(attacker); + VectorCopy(pVeh->m_pParentEntity->client->ps.velocity, vehFwd); + VectorNormalize(vehFwd); + + if (DotProduct(vehFwd, actorFwd)>0.5) + { + damage *= 10.0f; + } + } + + if ( (damage > 0) && // Actually took some damage + (mod==MOD_SABER) && // If Attacked By A Saber + (targ->m_pVehicle->m_pVehicleInfo->type==VH_SPEEDER) && // and is a speeder + !(targ->m_pVehicle->m_ulFlags & VEH_OUTOFCONTROL) && // and is not already spinning + (targ->client->ps.speed > 30.0f) && // and is moving + (attacker==inflictor || Q_irand(0, 30)==0) // and EITHER saber is held, or 1 in 30 chance of hitting when thrown + ) + { + Vehicle_t* pVeh = targ->m_pVehicle; + gentity_t* parent = pVeh->m_pParentEntity; + float CurSpeed = VectorLength(parent->client->ps.velocity); + pVeh->m_iArmor = 0; // Remove all remaining Armor + pVeh->m_pVehicleInfo->StartDeathDelay(pVeh, 10000); + pVeh->m_ulFlags |= (VEH_OUTOFCONTROL|VEH_SPINNING); + VectorScale(parent->client->ps.velocity, 1.25f, parent->pos3); + if (CurSpeedm_pVehicleInfo->speedMax) + { + VectorNormalize(parent->pos3); + if (CurSpeedm_pVehicleInfo->speedMax) + { + VectorNormalize(parent->pos3); + if (fabsf(parent->pos3[2])<0.25f) + { + VectorScale(parent->pos3, (pVeh->m_pVehicleInfo->speedMax * 1.25f), parent->pos3); + } + else + { + VectorScale(parent->client->ps.velocity, 1.25f, parent->pos3); + } + } + } + + + // TODO: Play Huge Spark Effect & Start Rolling Sound + if (attacker==inflictor && (!G_IsRidingVehicle(attacker) || Q_irand(0, 3)==0)) + { + attacker->lastEnemy = targ; + G_StartMatrixEffect(attacker, MEF_LOOK_AT_ENEMY|MEF_NO_RANGEVAR|MEF_NO_VERTBOB|MEF_NO_SPIN, 1000); + if (!G_IsRidingVehicle(attacker)) + { + G_StartRoll(attacker, (Q_irand(0,1)==0)?(BOTH_ROLL_L):(BOTH_ROLL_R)); + } + } + + if (targ->m_pVehicle->m_pPilot && targ->m_pVehicle->m_pPilot->s.number>=MAX_CLIENTS) + { + G_SoundOnEnt(targ->m_pVehicle->m_pPilot, CHAN_VOICE, "*falling1.wav" ); + } + + + + // DISMEMBER THE FRONT PART OF THE MODEL + { + trace_t trace; + + gentity_t *limb = G_Spawn(); + + + // Setup Basic Limb Entity Properties + //------------------------------------ + limb->s.radius = 60; + limb->s.eType = ET_THINKER; + limb->s.eFlags |= EF_BOUNCE_HALF; + limb->classname = "limb"; + limb->owner = targ; + limb->enemy = targ->enemy; + limb->svFlags = SVF_USE_CURRENT_ORIGIN; + limb->playerModel = 0; + limb->clipmask = MASK_SOLID; + limb->contents = CONTENTS_CORPSE; + limb->e_clThinkFunc = clThinkF_CG_Limb; + limb->e_ThinkFunc = thinkF_LimbThink; + limb->nextthink = level.time + FRAMETIME; + limb->physicsBounce = 0.2f; + limb->craniumBone = targ->craniumBone; + limb->cervicalBone = targ->cervicalBone; + limb->thoracicBone = targ->thoracicBone; + limb->upperLumbarBone = targ->upperLumbarBone; + limb->lowerLumbarBone = targ->lowerLumbarBone; + limb->hipsBone = targ->hipsBone; + limb->rootBone = targ->rootBone; + + + // Calculate The Location Of The New Limb + //---------------------------------------- + G_SetOrigin( limb, targ->currentOrigin ); + + VectorCopy( targ->currentOrigin, limb->s.pos.trBase ); + VectorSet( limb->mins, -3.0f, -3.0f, -6.0f ); + VectorSet( limb->maxs, 3.0f, 3.0f, 6.0f ); + VectorCopy( targ->s.modelScale, limb->s.modelScale ); + + + + + //copy the g2 instance of the victim into the limb + //------------------------------------------------- + gi.G2API_CopyGhoul2Instance(targ->ghoul2, limb->ghoul2); + gi.G2API_SetRootSurface(limb->ghoul2, limb->playerModel, "lfront"); + gi.G2API_SetSurfaceOnOff(&targ->ghoul2[targ->playerModel], "lfront", TURN_OFF); + animation_t *animations = level.knownAnimFileSets[targ->client->clientInfo.animFileIndex].animations; + + //play the proper dismember anim on the limb + gi.G2API_SetBoneAnim(&limb->ghoul2[limb->playerModel], 0, animations[BOTH_A1_BL_TR].firstFrame, + animations[BOTH_A1_BL_TR].numFrames + animations[BOTH_A1_BL_TR].firstFrame, + BONE_ANIM_OVERRIDE_FREEZE, 1, level.time ); + + + // Check For Start In Solid + //-------------------------- + gi.linkentity( limb ); + gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask ); + if ( trace.startsolid ) + { + limb->s.pos.trBase[2] -= limb->mins[2]; + gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask ); + if ( trace.startsolid ) + { + limb->s.pos.trBase[2] += limb->mins[2]; + gi.trace( &trace, limb->s.pos.trBase, limb->mins, limb->maxs, limb->s.pos.trBase, limb->s.number, limb->clipmask ); + + } + } + + // If Started In Solid, Remove + //----------------------------- + if ( trace.startsolid ) + { + G_FreeEntity( limb ); + } + + // Otherwise, Send It Flying + //--------------------------- + else + { + VectorCopy( limb->s.pos.trBase, limb->currentOrigin ); + VectorScale( targ->client->ps.velocity, 1.0f, limb->s.pos.trDelta ); + limb->s.pos.trType = TR_GRAVITY; + limb->s.pos.trTime = level.time; + + VectorCopy( targ->currentAngles, limb->s.apos.trBase ); + VectorClear( limb->s.apos.trDelta ); + limb->s.apos.trTime = level.time; + limb->s.apos.trType = TR_LINEAR; + limb->s.apos.trDelta[0] = Q_irand( -300, 300 ); + limb->s.apos.trDelta[1] = Q_irand( -800, 800 ); + + gi.linkentity( limb ); + } + } + } + + targ->m_pVehicle->m_iShields = targ->client->ps.stats[STAT_ARMOR]; + targ->m_pVehicle->m_iArmor -= take; + if ( targ->m_pVehicle->m_iArmor < 0 ) + { + targ->m_pVehicle->m_iArmor = 0; + } + if ( ( targ->m_pVehicle->m_iArmor <= 0 ) + && targ->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL ) + {//vehicle all out of armor + Vehicle_t *pVeh = targ->m_pVehicle; + if (dflags&DAMAGE_IMPACT_DIE) + { + // kill it now + pVeh->m_pVehicleInfo->StartDeathDelay( pVeh, -1/* -1 causes instant death */ ); + } + else + { + if ( pVeh->m_iDieTime == 0 ) + {//just start the flaming effect and explosion delay, if it's not going already... + pVeh->m_pVehicleInfo->StartDeathDelay( pVeh, Q_irand( 4000, 5500 ) ); + } + } + } + else if (targ->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL) + { + take = 0; + } + } + } + if ( !(dflags&DAMAGE_NO_HIT_LOC) || !(dflags&DAMAGE_RADIUS)) + { + if ( !G_NonLocationSpecificDamage( mod ) ) + {//certain kinds of damage don't care about hitlocation + take = ceil( (float)take*damageModifier[hitLoc] ); + } + } + + if ( g_debugDamage->integer ) { + gi.Printf( "[%d]client:%i health:%i damage:%i armor:%i hitloc:%s\n", level.time, targ->s.number, targ->health, take, asave, hitLocName[hitLoc] ); + } + + // 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->ps.persistant[PERS_ATTACKER] = attacker->s.number; //attack can be the world ent + client->damage_armor += asave; + client->damage_blood += take; + if ( dir ) { //can't check newdir since it's local, newdir is dir normalized + VectorCopy ( newDir, client->damage_from ); + client->damage_fromWorld = false; + } else { + VectorCopy ( targ->currentOrigin, client->damage_from ); + client->damage_fromWorld = true; + } + } + + // do the damage + if ( targ->health <= 0 ) + { + alreadyDead = qtrue; + } + + // Undying If: + //-------------------------------------------------------------------------- + qboolean targUndying = (!alreadyDead + && !(dflags&DAMAGE_NO_PROTECTION) + && ( + (targ->flags&FL_UNDYING) + || (dflags&DAMAGE_NO_KILL) + || ((targ->client) && (targ->client->ps.forcePowersActive & (1<client + && targ->client->NPC_class == CLASS_WAMPA + && targ->count + && take >= targ->health ) + {//wampa holding someone, don't die unless you can release them! + qboolean removeArm = qfalse; + if ( targ->activator + && attacker == targ->activator + && mod == MOD_SABER ) + { + removeArm = qtrue; + } + if ( Wampa_CheckDropVictim( targ, qtrue ) ) + {//released our victim + if ( removeArm ) + { + targ->client->dismembered = false; + //FIXME: the limb should just disappear, cuz I ate it + G_DoDismemberment( targ, targ->currentOrigin, MOD_SABER, 1000, HL_ARM_RT, qtrue ); + } + } + else + {//couldn't release him + targUndying = qtrue; + } + } + + if ( attacker && attacker->client && !attacker->s.number ) + { + if ( !alreadyDead ) + { + int add; + if ( take > targ->health ) + { + add = targ->health; + } + else + { + add = take; + } + add += asave; + add = ceil(add/10.0f); + if ( attacker != targ ) + { + G_TrackWeaponUsage( attacker, inflictor, add, mod ); + } + } + } + + if ( take || (dflags&DAMAGE_NO_DAMAGE) ) + { + if ( !targ->client || !attacker->client ) + { + targ->health = targ->health - take; + if (targ->health < 0) + { + targ->health = 0; + } + if ( targUndying ) + { + if(targ->health < 1) + { + G_ActivateBehavior( targ, BSET_DEATH ); + targ->health = 1; + } + } + } + else + {//two clients + team_t targTeam = TEAM_FREE; + team_t attackerTeam = TEAM_FREE; + + if ( player->client->ps.viewEntity && targ->s.number == player->client->ps.viewEntity ) + { + targTeam = player->client->playerTeam; + } + else if ( targ->client ) { + targTeam = targ->client->playerTeam; + } + else { + targTeam = targ->noDamageTeam; + } + // if ( targTeam == TEAM_DISGUISE ) { + // targTeam = TEAM_PLAYER; + // } + if ( player->client->ps.viewEntity && attacker->s.number == player->client->ps.viewEntity ) + { + attackerTeam = player->client->playerTeam; + } + else if ( attacker->client ) { + attackerTeam = attacker->client->playerTeam; + } + else { + attackerTeam = attacker->noDamageTeam; + } + // if ( attackerTeam == TEAM_DISGUISE ) { + // attackerTeam = TEAM_PLAYER; + // } + + if ( targTeam != attackerTeam + || (targ->s.number < MAX_CLIENTS && targTeam == TEAM_FREE)//evil player hit + || (attacker && attacker->s.number < MAX_CLIENTS && attackerTeam == TEAM_FREE) )//evil player attacked + {//on opposite team + targ->health = targ->health - take; + + //MCG - Falling should never kill player- only if a trigger_hurt does so. + if ( mod == MOD_FALLING && targ->s.number == 0 && targ->health < 1 ) + { + targ->health = 1; + } + else if (targ->health < 0) + { + targ->health = 0; + } + + if (targUndying) + { + if ( targ->health < 1 ) + { + if ( targ->NPC == NULL || !(targ->NPC->aiFlags&NPCAI_ROSH) || !Rosh_TwinPresent( targ ) ) + {//NOTE: Rosh won't run his deathscript until he doesn't have the twins to heal him + G_ActivateBehavior( targ, BSET_DEATH ); + } + targ->health = 1; + } + } + else if ( targ->health < 1 && attacker->client ) + { // The player or NPC just killed an enemy so increment the kills counter + attacker->client->ps.persistant[PERS_ENEMIES_KILLED]++; + } + } + else if ( targTeam == TEAM_PLAYER ) + {//on the same team, and target is an ally + qboolean takeDamage = qtrue; + qboolean yellAtAttacker = qtrue; + + //1) player doesn't take damage from teammates unless they're angry at him + if ( targ->s.number == 0 ) + {//the player + if ( attacker->enemy != targ && attacker != targ ) + {//an NPC shot the player by accident + takeDamage = qfalse; + } + } + //2) NPCs don't take any damage from player during combat + else + {//an NPC + if ( ((dflags & DAMAGE_RADIUS)) && !(dflags&DAMAGE_IGNORE_TEAM) ) + {//An NPC got hit by player and this is during combat or it was slash damage + //NOTE: though it's not realistic to have teammates not take splash damage, + // even when not in combat, it feels really bad to have them able to + // actually be killed by the player's splash damage + takeDamage = qfalse; + } + + if ( (dflags & DAMAGE_RADIUS) ) + {//you're fighting and it's just radius damage, so don't even mention it + yellAtAttacker = qfalse; + } + } + + if ( takeDamage ) + { + targ->health = targ->health - take; + if ( !alreadyDead && ((((targ->flags&FL_UNDYING)||targ->client->ps.forcePowersActive & (1 << FP_RAGE)) && !(dflags&DAMAGE_NO_PROTECTION) && attacker->s.number != 0) || (dflags&DAMAGE_NO_KILL) ) ) + {//guy is marked undying and we're not the player or we're in combat + if ( targ->health < 1 ) + { + G_ActivateBehavior( targ, BSET_DEATH ); + + targ->health = 1; + } + } + else if ( !alreadyDead && ((((targ->flags&FL_UNDYING)||targ->client->ps.forcePowersActive & (1 << FP_RAGE)) && !(dflags&DAMAGE_NO_PROTECTION) && !attacker->s.number && !targ->s.number) || (dflags&DAMAGE_NO_KILL)) ) + {// player is undying and he's attacking himself, don't let him die + if ( targ->health < 1 ) + { + G_ActivateBehavior( targ, BSET_DEATH ); + + targ->health = 1; + } + } + else if ( targ->health < 0 ) + { + targ->health = 0; + if ( attacker->s.number == 0 && targ->NPC ) + { + targ->NPC->scriptFlags |= SCF_FFDEATH; + } + } + } + + if ( yellAtAttacker ) + { + if ( !targ->NPC || !targ->NPC->charmedTime ) + { + G_FriendlyFireReaction( targ, attacker, dflags ); + } + } + } + else + { + + } + } + + if ( targ->client ) { + targ->client->ps.stats[STAT_HEALTH] = targ->health; + g_lastClientDamaged = targ; + } + + //TEMP HACK FOR PLAYER LOOK AT ENEMY CODE + //FIXME: move this to a player pain func? + if ( targ->s.number == 0 ) + { + if ( !targ->enemy //player does not have an enemy yet + || targ->enemy->s.weapon != WP_SABER //or player's enemy is not a jedi + || attacker->s.weapon == WP_SABER )//and attacker is a jedi + //keep enemy jedi over shooters + { + if ( attacker->enemy == targ || !OnSameTeam( targ, attacker ) ) + {//don't set player's enemy to teammates that hit him by accident + targ->enemy = attacker; + } + NPC_SetLookTarget( targ, attacker->s.number, level.time+1000 ); + } + } + else if ( attacker->s.number == 0 && (!targ->NPC || !targ->NPC->timeOfDeath) && (mod == MOD_SABER || attacker->s.weapon != WP_SABER || !attacker->enemy || attacker->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters + {//this looks dumb when they're on the ground and you keep hitting them, so only do this when first kill them + if ( !OnSameTeam( targ, attacker ) ) + {//don't set player's enemy to teammates that he hits by accident + attacker->enemy = targ; + } + NPC_SetLookTarget( attacker, targ->s.number, level.time+1000 ); + } + //TEMP HACK FOR PLAYER LOOK AT ENEMY CODE + + //add up the damage to the location + if ( targ->client ) + { + if ( targ->locationDamage[hitLoc] < Q3_INFINITE ) + { + targ->locationDamage[hitLoc] += take; + } + } + + + if ( targ->health > 0 && targ->NPC && targ->NPC->surrenderTime > level.time ) + {//he was surrendering, goes down with one hit + if (!targ->client || targ->client->NPC_class!=CLASS_BOBAFETT) + { + targ->health = 0; + } + } + + if ( targ->health <= 0 ) + { + if ( knockback && (dflags&DAMAGE_DEATH_KNOCKBACK) )//&& targ->client + {//only do knockback on death + if ( mod == MOD_FLECHETTE ) + {//special case because this is shotgun-ish damage, we need to multiply the knockback + knockback *= 12;//*6 for 6 flechette shots + } + G_ApplyKnockback( targ, newDir, knockback ); + } + + /* + if ( client ) + targ->flags |= FL_NO_KNOCKBACK; + */ + + if (targ->health < -999) + targ->health = -999; + + // If we are a breaking glass brush, store the damage point so we can do cool things with it. + if ( targ->svFlags & SVF_GLASS_BRUSH ) + { + VectorCopy( point, targ->pos1 ); + VectorCopy( dir, targ->pos2 ); + } + if ( targ->client ) + {//HACK + if ( point ) + { + VectorCopy( point, targ->pos1 ); + } + else + { + VectorCopy( targ->currentOrigin, targ->pos1 ); + } + } + if ( !alreadyDead && !targ->enemy ) + {//just killed and didn't have an enemy before + targ->enemy = attacker; + } + + GEntity_DieFunc( targ, inflictor, attacker, take, mod, dflags, hitLoc ); + } + else + { + GEntity_PainFunc( targ, inflictor, attacker, point, take, mod, hitLoc ); + if ( targ->s.number == 0 ) + {//player run painscript + G_ActivateBehavior( targ, BSET_PAIN ); + if ( targ->health <= 25 ) + { + G_ActivateBehavior( targ, BSET_FLEE ); + } + } + } + } +} + + +/* +============ +CanDamage + +Returns qtrue if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage (gentity_t *targ, const vec3_t origin) { + vec3_t dest; + trace_t tr; + vec3_t midpoint; + qboolean cantHitEnt = qtrue; + + if ( (targ->contents&MASK_SOLID) ) + {//can hit it + if ( targ->s.solid == SOLID_BMODEL ) + {//but only if it's a brushmodel + cantHitEnt = qfalse; + } + } + + // use the midpoint of the bounds instead of the origin, because + // bmodels may have their origin at 0,0,0 + VectorAdd (targ->absmin, targ->absmax, midpoint); + VectorScale (midpoint, 0.5, midpoint); + + VectorCopy (midpoint, dest); + /* + vec3_t blah; + VectorCopy( origin, blah); + G_DebugLine(blah, dest, 5000, 0x0000ff, qtrue ); + */ + gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID); + if (( tr.fraction == 1.0 && cantHitEnt) || tr.entityNum == targ->s.number ) // if we also test the entitynum's we can bust up bbrushes better! + return qtrue; + + // this should probably check in the plane of projection, + // rather than in world coordinate, and also include Z + VectorCopy (midpoint, dest); + dest[0] += 15.0; + dest[1] += 15.0; + gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID); + if (( tr.fraction == 1.0 && cantHitEnt) || tr.entityNum == targ->s.number ) + return qtrue; + + VectorCopy (midpoint, dest); + dest[0] += 15.0; + dest[1] -= 15.0; + gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID); + if (( tr.fraction == 1.0 && cantHitEnt) || tr.entityNum == targ->s.number ) + return qtrue; + + VectorCopy (midpoint, dest); + dest[0] -= 15.0; + dest[1] += 15.0; + gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID); + if (( tr.fraction == 1.0 && cantHitEnt) || tr.entityNum == targ->s.number ) + return qtrue; + + VectorCopy (midpoint, dest); + dest[0] -= 15.0; + dest[1] -= 15.0; + gi.trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID); + if (( tr.fraction == 1.0 && cantHitEnt) || tr.entityNum == targ->s.number ) + return qtrue; + + + return qfalse; +} + +extern void Boba_DustFallNear(const vec3_t origin, int dustcount); +extern void G_GetMassAndVelocityForEnt( gentity_t *ent, float *mass, vec3_t velocity ); +/* +============ +G_RadiusDamage +============ +*/ +void G_RadiusDamage ( const vec3_t origin, gentity_t *attacker, float damage, float radius, + gentity_t *ignore, int mod) { + float points, dist; + gentity_t *ent; + gentity_t *entityList[MAX_GENTITIES]; + int numListedEntities; + vec3_t mins, maxs; + vec3_t v; + vec3_t dir; + int i, e; + int dFlags = DAMAGE_RADIUS; + + if ( radius < 1 ) { + radius = 1; + } + + for ( i = 0 ; i < 3 ; i++ ) { + mins[i] = origin[i] - radius; + maxs[i] = origin[i] + radius; + } + + if (mod==MOD_ROCKET) + { + Boba_DustFallNear(origin, 10); + } + + if ( mod == MOD_GAS ) + { + dFlags |= DAMAGE_NO_KNOCKBACK; + } + + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) { + ent = entityList[ e ]; + + if ( ent == ignore ) + continue; + if ( !ent->takedamage ) + continue; + if ( !ent->contents ) + continue; + + // find the distance from the edge of the bounding box + for ( i = 0 ; i < 3 ; i++ ) { + if ( origin[i] < ent->absmin[i] ) { + v[i] = ent->absmin[i] - origin[i]; + } else if ( origin[i] > ent->absmax[i] ) { + v[i] = origin[i] - ent->absmax[i]; + } else { + v[i] = 0; + } + } + + dist = VectorLength( v ); + if ( dist >= radius ) { + continue; + } + + points = damage * ( 1.0 - dist / radius ); + + // Lessen damage to vehicles that are moving away from the explosion + if (ent->client && (ent->client->NPC_class==CLASS_VEHICLE || G_IsRidingVehicle(ent))) + { + gentity_t* bike = ent; + + if (G_IsRidingVehicle(ent) && ent->owner) + { + bike = ent->owner; + } + + vec3_t vehMoveDirection; + float vehMoveSpeed; + + vec3_t explosionDirection; + float explosionDirectionSimilarity; + + float mass; + G_GetMassAndVelocityForEnt( bike, &mass, vehMoveDirection ); + vehMoveSpeed = VectorNormalize(vehMoveDirection); + if (vehMoveSpeed>300.0f) + { + VectorSubtract(bike->currentOrigin, origin, explosionDirection); + VectorNormalize(explosionDirection); + + explosionDirectionSimilarity = DotProduct(vehMoveDirection, explosionDirection); + if (explosionDirectionSimilarity>0.0f) + { + points *= (1.0f - explosionDirectionSimilarity); + } + } + } + + if (CanDamage (ent, origin)) + {//FIXME: still do a little damage in in PVS and close? + if ( ent->svFlags & (SVF_GLASS_BRUSH|SVF_BBRUSH) ) + { + VectorAdd( ent->absmin, ent->absmax, v ); + VectorScale( v, 0.5f, v ); + } + else + { + VectorCopy( ent->currentOrigin, v ); + } + + VectorSubtract( v, origin, dir); + // push the center of mass higher than the origin so players + // get knocked into the air more + dir[2] += 24; + + if ( ent->svFlags & SVF_GLASS_BRUSH ) + { + if ( points > 1.0f ) + { + // we want to cap this at some point, otherwise it just gets crazy + if ( points > 6.0f ) + { + VectorScale( dir, 6.0f, dir ); + } + else + { + VectorScale( dir, points, dir ); + } + } + + ent->splashRadius = radius;// * ( 1.0 - dist / radius ); + } + + G_Damage (ent, NULL, attacker, dir, origin, (int)points, dFlags, mod); + } + } +} diff --git a/code/game/g_emplaced.cpp b/code/game/g_emplaced.cpp new file mode 100644 index 0000000..9d1d795 --- /dev/null +++ b/code/game/g_emplaced.cpp @@ -0,0 +1,1133 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + + +#include "g_local.h" +#include "g_functions.h" +#include "anims.h" +#include "wp_saber.h" + +extern Vehicle_t *G_IsRidingVehicle( gentity_t *pEnt ); + +//lock the owner into place relative to the cannon pos +void EWebPositionUser(gentity_t *owner, gentity_t *eweb) +{ + mdxaBone_t boltMatrix; + vec3_t p, p2, d; + trace_t tr; + qboolean traceOver = qtrue; + + if ( owner->s.number < MAX_CLIENTS ) + {//extra checks + gi.trace(&tr, owner->currentOrigin, owner->mins, owner->maxs, owner->currentOrigin, owner->s.number, owner->clipmask); + if ( tr.startsolid || tr.allsolid ) + {//crap, they're already in solid somehow, don't bother tracing over + traceOver = qfalse; + } + } + if ( traceOver ) + {//trace up + VectorCopy( owner->currentOrigin, p2 ); + p2[2] += STEPSIZE; + gi.trace(&tr, owner->currentOrigin, owner->mins, owner->maxs, p2, owner->s.number, owner->clipmask); + if (!tr.startsolid && !tr.allsolid ) + { + VectorCopy( tr.endpos, p2 ); + } + else + { + VectorCopy( owner->currentOrigin, p2 ); + } + } + //trace over + gi.G2API_GetBoltMatrix( eweb->ghoul2, 0, eweb->headBolt, &boltMatrix, + eweb->s.apos.trBase, eweb->currentOrigin, + (cg.time?cg.time:level.time), NULL, eweb->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, p ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, d ); + d[2] = 0; + VectorNormalize( d ); + VectorMA( p, -44.0f, d, p ); + if ( !traceOver ) + { + VectorCopy( p, tr.endpos ); + tr.allsolid = tr.startsolid = qfalse; + } + else + { + p[2] = p2[2]; + if ( owner->s.number < MAX_CLIENTS ) + {//extra checks + //just see if end point is not in solid + gi.trace(&tr, p, owner->mins, owner->maxs, p, owner->s.number, owner->clipmask); + if ( tr.startsolid || tr.allsolid ) + {//would be in solid there, so just trace over, I guess? + gi.trace(&tr, p2, owner->mins, owner->maxs, p, owner->s.number, owner->clipmask); + } + } + else + {//trace over + gi.trace(&tr, p2, owner->mins, owner->maxs, p, owner->s.number, owner->clipmask); + } + } + if (!tr.startsolid && !tr.allsolid ) + { + //trace down + VectorCopy( tr.endpos, p ); + VectorCopy( p, p2 ); + p2[2] -= STEPSIZE; + gi.trace(&tr, p, owner->mins, owner->maxs, p2, owner->s.number, owner->clipmask); + + if (!tr.startsolid && !tr.allsolid )//&& tr.fraction == 1.0f) + { //all clear, we can move there + vec3_t moveDir; + float moveDist; + VectorCopy( tr.endpos, p ); + VectorSubtract( p, eweb->pos4, moveDir ); + moveDist = VectorNormalize( moveDir ); + if ( moveDist > 4.0f ) + {//moved past the threshold from last position + vec3_t oRight; + int strafeAnim; + + VectorCopy( p, eweb->pos4 );//update the position + //find out what direction he moved in + AngleVectors( owner->currentAngles, NULL, oRight, NULL ); + if ( DotProduct( moveDir, oRight ) > 0 ) + {//moved to his right, play right strafe + strafeAnim = BOTH_STRAFE_RIGHT1; + } + else + {//moved left, play left strafe + strafeAnim = BOTH_STRAFE_LEFT1; + } + NPC_SetAnim( owner, SETANIM_LEGS, strafeAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + } + + G_SetOrigin(owner, p); + VectorCopy(p, owner->client->ps.origin); + gi.linkentity( owner ); + } + } + //FIXME: IK the hands to the handles of the gun? +} + +//=============================================== +//End E-Web +//=============================================== + +//---------------------------------------------------------- + +//=============================================== +//Emplaced Gun +//=============================================== + +// spawnflag +#define EMPLACED_INACTIVE 1 +#define EMPLACED_FACING 2 +#define EMPLACED_VULNERABLE 4 +#define EWEB_INVULNERABLE 4 +#define EMPLACED_PLAYERUSE 8 + +/*QUAKED emplaced_eweb (0 0 1) (-12 -12 -24) (12 12 24) INACTIVE FACING INVULNERABLE PLAYERUSE + + INACTIVE cannot be used until used by a target_activate + FACING - player must be facing relatively in the same direction as the gun in order to use it + VULNERABLE - allow the gun to take damage + PLAYERUSE - only the player makes it run its usescript + + count - how much ammo to give this gun ( default 999 ) + health - how much damage the gun can take before it blows ( default 250 ) + delay - ONLY AFFECTS NPCs - time between shots ( default 200 on hardest setting ) + wait - ONLY AFFECTS NPCs - time between bursts ( default 800 on hardest setting ) + splashdamage - how much damage a blowing up gun deals ( default 80 ) + splashradius - radius for exploding damage ( default 128 ) + + scripts: + will run usescript, painscript and deathscript +*/ +//---------------------------------------------------------- +void eweb_pain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc ) +{ + if ( self->health <= 0 ) + { + // play pain effect? + } + else + { + if ( self->paintarget ) + { + G_UseTargets2( self, self->activator, self->paintarget ); + } + + // Don't do script if dead + G_ActivateBehavior( self, BSET_PAIN ); + } +} +//---------------------------------------------------------- +void eweb_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) +{ + vec3_t org; + + // turn off any firing animations it may have been doing + self->s.frame = self->startFrame = self->endFrame = 0; + self->svFlags &= ~(SVF_ANIMATING|SVF_PLAYER_USABLE); + + + self->health = 0; +// self->s.weapon = WP_EMPLACED_GUN; // we need to be able to switch back to the old weapon + + self->takedamage = qfalse; + self->lastEnemy = attacker; + + if ( self->activator && self->activator->client ) + { + if ( self->activator->NPC ) + { + vec3_t right; + + // radius damage seems to throw them, but add an extra bit to throw them away from the weapon + AngleVectors( self->currentAngles, NULL, right, NULL ); + VectorMA( self->activator->client->ps.velocity, 140, right, self->activator->client->ps.velocity ); + self->activator->client->ps.velocity[2] = -100; + + // kill them + self->activator->health = 0; + self->activator->client->ps.stats[STAT_HEALTH] = 0; + } + + // kill the players emplaced ammo, cheesy way to keep the gun from firing + self->activator->client->ps.ammo[weaponData[WP_EMPLACED_GUN].ammoIndex] = 0; + } + + self->e_PainFunc = painF_NULL; + + if ( self->target ) + { + G_UseTargets( self, attacker ); + } + + G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN ); + + VectorCopy( self->currentOrigin, org ); + org[2] += 20; + + G_PlayEffect( "emplaced/explode", org ); + + // Turn the top of the eweb off. +#define TURN_OFF 0x00000100//G2SURFACEFLAG_NODESCENDANTS + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "eweb_damage", TURN_OFF ); + + // create some persistent smoke by using a dynamically created fx runner + gentity_t *ent = G_Spawn(); + + if ( ent ) + { + ent->delay = 200; + ent->random = 100; + + ent->fxID = G_EffectIndex( "emplaced/dead_smoke" ); + + ent->e_ThinkFunc = thinkF_fx_runner_think; + ent->nextthink = level.time + 50; + + // move up above the gun origin + VectorCopy( self->currentOrigin, org ); + org[2] += 35; + G_SetOrigin( ent, org ); + VectorCopy( org, ent->s.origin ); + + VectorSet( ent->s.angles, -90, 0, 0 ); // up + G_SetAngles( ent, ent->s.angles ); + + gi.linkentity( ent ); + } + + G_ActivateBehavior( self, BSET_DEATH ); +} + +qboolean eweb_can_be_used( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( self->health <= 0 ) + { + // can't use a dead gun. + return qfalse; + } + + if ( self->svFlags & SVF_INACTIVE ) + { + return qfalse; // can't use inactive gun + } + + if ( !activator->client ) + { + return qfalse; // only a client can use it. + } + + if ( self->activator ) + { + // someone is already in the gun. + return qfalse; + } + + if ( other && other->client && G_IsRidingVehicle( other ) ) + {//can't use eweb when on a vehicle + return qfalse; + } + + if ( activator && activator->client && G_IsRidingVehicle( activator ) ) + {//can't use eweb when on a vehicle + return qfalse; + } + + if ( activator && activator->client && (activator->client->ps.pm_flags&PMF_DUCKED) ) + {//stand up, ya cowardly varmint! + return qfalse; + } + + if ( activator && activator->health <= 0 ) + {//dead men ain't got no more use fer guns... + return qfalse; + } + + vec3_t fwd1, fwd2; + vec3_t facingAngles; + + VectorAdd( self->s.angles, self->pos1, facingAngles ); + if ( activator->s.number < MAX_CLIENTS ) + {//player must be facing general direction of the turret head + // Let's get some direction vectors for the users + AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL ); + fwd1[2] = 0; + + // Get the gun's direction vector + AngleVectors( facingAngles, fwd2, NULL, NULL ); + fwd2[2] = 0; + + float dot = DotProduct( fwd1, fwd2 ); + + // Must be reasonably facing the way the gun points ( 90 degrees or so ), otherwise we don't allow to use it. + if ( dot < 0.75f ) + { + return qfalse; + } + } + + if ( self->delay + 500 < level.time ) + { + return qtrue; + } + return qfalse; +} + +void eweb_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( !eweb_can_be_used( self, other, activator ) ) + { + return; + } + + int oldWeapon = activator->s.weapon; + + if ( oldWeapon == WP_SABER ) + { + self->alt_fire = activator->client->ps.SaberActive(); + } + + // swap the users weapon with the emplaced gun and add the ammo the gun has to the player + activator->client->ps.weapon = self->s.weapon; + Add_Ammo( activator, WP_EMPLACED_GUN, self->count ); + activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN ); + + // Allow us to point from one to the other + activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. + self->activator = activator; + + G_RemoveWeaponModels( activator ); + +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); + if ( activator->NPC ) + { + ChangeWeapon( activator, WP_EMPLACED_GUN ); + } + else if ( activator->s.number == 0 ) + { + // we don't want for it to draw the weapon select stuff + cg.weaponSelect = WP_EMPLACED_GUN; + CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.87 ); + } + + VectorCopy( activator->currentOrigin, self->pos4 );//keep this around so we know when to make them play the strafe anim + + // the gun will track which weapon we used to have + self->s.weapon = oldWeapon; + + // Lock the player + activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON; + activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. + self->activator = activator; + self->delay = level.time; // can't disconnect from the thing for half a second + + // Let the gun be considered an enemy + //Ugh, so much AI code seems to assume enemies are clients, maybe this shouldn't be on, but it's too late in the game to change it now without knowing what side-effects this will have + self->svFlags |= SVF_NONNPC_ENEMY; + self->noDamageTeam = activator->client->playerTeam; + + //FIXME: should really wait a bit after spawn and get this just once? + self->waypoint = NAV::GetNearestNode(self); +#ifdef _DEBUG + if ( self->waypoint == -1 ) + { + gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) ); + } +#endif + + G_Sound( self, G_SoundIndex( "sound/weapons/eweb/eweb_mount.mp3" )); +#ifdef _IMMERSION + G_Force( self, G_ForceIndex( "fffx/weapons/emplaced/emplaced_mount", FF_CHANNEL_TOUCH ) ); +#endif // _IMMERSION + + if ( !(self->spawnflags&EMPLACED_PLAYERUSE) || activator->s.number == 0 ) + {//player-only usescript or any usescript + // Run use script + G_ActivateBehavior( self, BSET_USE ); + } +} + +//---------------------------------------------------------- +void SP_emplaced_eweb( gentity_t *ent ) +{ + char name[] = "models/map_objects/hoth/eweb_model.glm"; + + ent->svFlags |= SVF_PLAYER_USABLE; + ent->contents = CONTENTS_BODY; + + if ( ent->spawnflags & EMPLACED_INACTIVE ) + { + ent->svFlags |= SVF_INACTIVE; + } + + VectorSet( ent->mins, -12, -12, -24 ); + VectorSet( ent->maxs, 12, 12, 24 ); + + ent->takedamage = qtrue; + + if ( ( ent->spawnflags & EWEB_INVULNERABLE )) + { + ent->flags |= FL_GODMODE; + } + + ent->s.radius = 80; + ent->spawnflags |= 4; // deadsolid + + //ent->e_ThinkFunc = thinkF_NULL; + ent->e_PainFunc = painF_eweb_pain; + ent->e_DieFunc = dieF_eweb_die; + + G_EffectIndex( "emplaced/explode" ); + G_EffectIndex( "emplaced/dead_smoke" ); + + G_SoundIndex( "sound/weapons/eweb/eweb_aim.wav" ); + G_SoundIndex( "sound/weapons/eweb/eweb_dismount.mp3" ); + //G_SoundIndex( "sound/weapons/eweb/eweb_empty.wav" ); + G_SoundIndex( "sound/weapons/eweb/eweb_fire.wav" ); + G_SoundIndex( "sound/weapons/eweb/eweb_hitplayer.wav" ); + G_SoundIndex( "sound/weapons/eweb/eweb_hitsurface.wav" ); + //G_SoundIndex( "sound/weapons/eweb/eweb_load.wav" ); + G_SoundIndex( "sound/weapons/eweb/eweb_mount.mp3" ); + + // Set up our defaults and override with custom amounts as necessary + G_SpawnInt( "count", "999", &ent->count ); + G_SpawnInt( "health", "250", &ent->health ); + G_SpawnInt( "splashDamage", "40", &ent->splashDamage ); + G_SpawnInt( "splashRadius", "100", &ent->splashRadius ); + G_SpawnFloat( "delay", "200", &ent->random ); // NOTE: spawning into a different field!! + G_SpawnFloat( "wait", "800", &ent->wait ); + + ent->max_health = ent->health; + ent->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud + + ent->s.modelindex = G_ModelIndex( name ); + ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, name, ent->s.modelindex ); + + // Activate our tags and bones + ent->handLBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*cannonflash" ); //muzzle bolt + ent->headBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "cannon_Xrot" ); //for placing the owner relative to rotation + ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue ); + ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cannon_Yrot", qtrue ); + ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cannon_Xrot", qtrue ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, NULL); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, NULL); + //gi.G2API_SetBoneAngles( &ent->ghoul2[0], "cannon_Yrot", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL); + //set the constraints for this guy as an emplaced weapon, and his constraint angles + //ent->s.origin2[0] = 60.0f; //60 degrees in either direction + + RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN )); + ent->s.weapon = WP_EMPLACED_GUN; + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngles( ent, ent->s.angles ); + VectorCopy( ent->s.angles, ent->lastAngles ); + + // store base angles for later + VectorClear( ent->pos1 ); + + ent->e_UseFunc = useF_eweb_use; + ent->bounceCount = 1;//to distinguish it from the emplaced gun + + gi.linkentity (ent); +} + +/*QUAKED emplaced_gun (0 0 1) (-24 -24 0) (24 24 64) INACTIVE x VULNERABLE PLAYERUSE + + INACTIVE cannot be used until used by a target_activate + VULNERABLE - allow the gun to take damage + PLAYERUSE - only the player makes it run its usescript + + count - how much ammo to give this gun ( default 999 ) + health - how much damage the gun can take before it blows ( default 250 ) + delay - ONLY AFFECTS NPCs - time between shots ( default 200 on hardest setting ) + wait - ONLY AFFECTS NPCs - time between bursts ( default 800 on hardest setting ) + splashdamage - how much damage a blowing up gun deals ( default 80 ) + splashradius - radius for exploding damage ( default 128 ) + + scripts: + will run usescript, painscript and deathscript +*/ + +//---------------------------------------------------------- +void emplaced_gun_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + vec3_t fwd1, fwd2; + + if ( self->health <= 0 ) + { + // can't use a dead gun. + return; + } + + if ( self->svFlags & SVF_INACTIVE ) + { + return; // can't use inactive gun + } + + if ( !activator->client ) + { + return; // only a client can use it. + } + + if ( self->activator ) + { + // someone is already in the gun. + return; + } + + if ( other && other->client && G_IsRidingVehicle( other ) ) + {//can't use eweb when on a vehicle + return; + } + + if ( activator && activator->client && G_IsRidingVehicle( activator ) ) + {//can't use eweb when on a vehicle + return; + } + + // We'll just let the designers duke this one out....I mean, as to whether they even want to limit such a thing. + if ( self->spawnflags & EMPLACED_FACING ) + { + // Let's get some direction vectors for the users + AngleVectors( activator->client->ps.viewangles, fwd1, NULL, NULL ); + + // Get the guns direction vector + AngleVectors( self->pos1, fwd2, NULL, NULL ); + + float dot = DotProduct( fwd1, fwd2 ); + + // Must be reasonably facing the way the gun points ( 90 degrees or so ), otherwise we don't allow to use it. + if ( dot < 0.0f ) + { + return; + } + } + + // don't allow using it again for half a second + if ( self->delay + 500 < level.time ) + { + int oldWeapon = activator->s.weapon; + + if ( oldWeapon == WP_SABER ) + { + self->alt_fire = activator->client->ps.SaberActive(); + } + + // swap the users weapon with the emplaced gun and add the ammo the gun has to the player + activator->client->ps.weapon = self->s.weapon; + Add_Ammo( activator, WP_EMPLACED_GUN, self->count ); + activator->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_EMPLACED_GUN ); + + // Allow us to point from one to the other + activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. + self->activator = activator; + + G_RemoveWeaponModels( activator ); + +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); + if ( activator->NPC ) + { + ChangeWeapon( activator, WP_EMPLACED_GUN ); + } + else if ( activator->s.number == 0 ) + { + // we don't want for it to draw the weapon select stuff + cg.weaponSelect = WP_EMPLACED_GUN; + CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.87 ); + } + // Since we move the activator inside of the gun, we reserve a solid spot where they were standing in order to be able to get back out without being in solid + if ( self->nextTrain ) + {//you never know + G_FreeEntity( self->nextTrain ); + } + self->nextTrain = G_Spawn(); + //self->nextTrain->classname = "emp_placeholder"; + self->nextTrain->contents = CONTENTS_MONSTERCLIP|CONTENTS_PLAYERCLIP;//hmm... playerclip too now that we're doing it for NPCs? + G_SetOrigin( self->nextTrain, activator->client->ps.origin ); + VectorCopy( activator->mins, self->nextTrain->mins ); + VectorCopy( activator->maxs, self->nextTrain->maxs ); + gi.linkentity( self->nextTrain ); + + //need to inflate the activator's mins/maxs since the gunsit anim puts them outside of their bbox + VectorSet( activator->mins, -24, -24, -24 ); + VectorSet( activator->maxs, 24, 24, 40 ); + + // Move the activator into the center of the gun. For NPC's the only way the can get out of the gun is to die. + VectorCopy( self->s.origin, activator->client->ps.origin ); + activator->client->ps.origin[2] += 30; // move them up so they aren't standing in the floor + gi.linkentity( activator ); + + // the gun will track which weapon we used to have + self->s.weapon = oldWeapon; + + // Lock the player + activator->client->ps.eFlags |= EF_LOCKED_TO_WEAPON; + activator->owner = self; // kind of dumb, but when we are locked to the weapon, we are owned by it. + self->activator = activator; + self->delay = level.time; // can't disconnect from the thing for half a second + + // Let the gun be considered an enemy + //Ugh, so much AI code seems to assume enemies are clients, maybe this shouldn't be on, but it's too late in the game to change it now without knowing what side-effects this will have + self->svFlags |= SVF_NONNPC_ENEMY; + self->noDamageTeam = activator->client->playerTeam; + + // FIXME: don't do this, we'll try and actually put the player in this beast + // move the player to the center of the gun +// activator->contents = 0; +// VectorCopy( self->currentOrigin, activator->client->ps.origin ); + + SetClientViewAngle( activator, self->pos1 ); + + //FIXME: should really wait a bit after spawn and get this just once? + self->waypoint = NAV::GetNearestNode(self); +#ifdef _DEBUG + if ( self->waypoint == -1 ) + { + gi.Printf( S_COLOR_RED"ERROR: no waypoint for emplaced_gun %s at %s\n", self->targetname, vtos(self->currentOrigin) ); + } +#endif + + G_Sound( self, G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" )); +#ifdef _IMMERSION + G_Force( self, G_ForceIndex( "fffx/weapons/emplaced/emplaced_mount", FF_CHANNEL_TOUCH ) ); +#endif // _IMMERSION + + if ( !(self->spawnflags&EMPLACED_PLAYERUSE) || activator->s.number == 0 ) + {//player-only usescript or any usescript + // Run use script + G_ActivateBehavior( self, BSET_USE ); + } + } +} + +//---------------------------------------------------------- +void emplaced_gun_pain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc ) +{ + if ( self->health <= 0 ) + { + // play pain effect? + } + else + { + if ( self->paintarget ) + { + G_UseTargets2( self, self->activator, self->paintarget ); + } + + // Don't do script if dead + G_ActivateBehavior( self, BSET_PAIN ); + } +} + +//---------------------------------------------------------- +void emplaced_blow( gentity_t *ent ) +{ + ent->e_DieFunc = dieF_NULL; + emplaced_gun_die( ent, ent->lastEnemy, ent->lastEnemy, 0, MOD_UNKNOWN ); +} + +//---------------------------------------------------------- +void emplaced_gun_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) +{ + vec3_t org; + + // turn off any firing animations it may have been doing + self->s.frame = self->startFrame = self->endFrame = 0; + self->svFlags &= ~SVF_ANIMATING; + + self->health = 0; +// self->s.weapon = WP_EMPLACED_GUN; // we need to be able to switch back to the old weapon + + self->takedamage = qfalse; + self->lastEnemy = attacker; + + // we defer explosion so the player has time to get out + if ( self->e_DieFunc ) + { + self->e_ThinkFunc = thinkF_emplaced_blow; + self->nextthink = level.time + 3000; // don't blow for a couple of seconds + return; + } + + if ( self->activator && self->activator->client ) + { + if ( self->activator->NPC ) + { + vec3_t right; + + // radius damage seems to throw them, but add an extra bit to throw them away from the weapon + AngleVectors( self->currentAngles, NULL, right, NULL ); + VectorMA( self->activator->client->ps.velocity, 140, right, self->activator->client->ps.velocity ); + self->activator->client->ps.velocity[2] = -100; + + // kill them + self->activator->health = 0; + self->activator->client->ps.stats[STAT_HEALTH] = 0; + } + + // kill the players emplaced ammo, cheesy way to keep the gun from firing + self->activator->client->ps.ammo[weaponData[WP_EMPLACED_GUN].ammoIndex] = 0; + } + + self->e_PainFunc = painF_NULL; + self->e_ThinkFunc = thinkF_NULL; + + if ( self->target ) + { + G_UseTargets( self, attacker ); + } + + G_RadiusDamage( self->currentOrigin, self, self->splashDamage, self->splashRadius, self, MOD_UNKNOWN ); + + // when the gun is dead, add some ugliness to it. + vec3_t ugly; + + ugly[YAW] = 4; + ugly[PITCH] = self->lastAngles[PITCH] * 0.8f + crandom() * 6; + ugly[ROLL] = crandom() * 7; + gi.G2API_SetBoneAnglesIndex( &self->ghoul2[self->playerModel], self->lowerLumbarBone, ugly, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL ); + + VectorCopy( self->currentOrigin, org ); + org[2] += 20; + + G_PlayEffect( "emplaced/explode", org ); + + // create some persistent smoke by using a dynamically created fx runner + gentity_t *ent = G_Spawn(); + + if ( ent ) + { + ent->delay = 200; + ent->random = 100; + + ent->fxID = G_EffectIndex( "emplaced/dead_smoke" ); + + ent->e_ThinkFunc = thinkF_fx_runner_think; + ent->nextthink = level.time + 50; + + // move up above the gun origin + VectorCopy( self->currentOrigin, org ); + org[2] += 35; + G_SetOrigin( ent, org ); + VectorCopy( org, ent->s.origin ); + + VectorSet( ent->s.angles, -90, 0, 0 ); // up + G_SetAngles( ent, ent->s.angles ); + + gi.linkentity( ent ); + } + + G_ActivateBehavior( self, BSET_DEATH ); +} + +//---------------------------------------------------------- +void SP_emplaced_gun( gentity_t *ent ) +{ + char name[] = "models/map_objects/imp_mine/turret_chair.glm"; + + ent->svFlags |= SVF_PLAYER_USABLE; + ent->contents = CONTENTS_BODY;//CONTENTS_SHOTCLIP|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP;//CONTENTS_SOLID; + + if ( ent->spawnflags & EMPLACED_INACTIVE ) + { + ent->svFlags |= SVF_INACTIVE; + } + + VectorSet( ent->mins, -30, -30, -5 ); + VectorSet( ent->maxs, 30, 30, 60 ); + + ent->takedamage = qtrue; + + if ( !( ent->spawnflags & EMPLACED_VULNERABLE )) + { + ent->flags |= FL_GODMODE; + } + + ent->s.radius = 110; + ent->spawnflags |= 4; // deadsolid + + //ent->e_ThinkFunc = thinkF_NULL; + ent->e_PainFunc = painF_emplaced_gun_pain; + ent->e_DieFunc = dieF_emplaced_gun_die; + + G_EffectIndex( "emplaced/explode" ); + G_EffectIndex( "emplaced/dead_smoke" ); + + G_SoundIndex( "sound/weapons/emplaced/emplaced_mount.mp3" ); + G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" ); + G_SoundIndex( "sound/weapons/emplaced/emplaced_move_lp.wav" ); + + // Set up our defaults and override with custom amounts as necessary + G_SpawnInt( "count", "999", &ent->count ); + G_SpawnInt( "health", "250", &ent->health ); + G_SpawnInt( "splashDamage", "80", &ent->splashDamage ); + G_SpawnInt( "splashRadius", "128", &ent->splashRadius ); + G_SpawnFloat( "delay", "200", &ent->random ); // NOTE: spawning into a different field!! + G_SpawnFloat( "wait", "800", &ent->wait ); + + ent->max_health = ent->health; + ent->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud + + ent->s.modelindex = G_ModelIndex( name ); + ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, name, ent->s.modelindex ); + + // Activate our tags and bones + ent->headBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*seat" ); + ent->handLBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*flash01" ); + ent->handRBolt = gi.G2API_AddBolt( &ent->ghoul2[ent->playerModel], "*flash02" ); + ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "base_bone", qtrue ); + ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "swivel_bone", qtrue ); + gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL); + + RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN )); + ent->s.weapon = WP_EMPLACED_GUN; + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngles( ent, ent->s.angles ); + VectorCopy( ent->s.angles, ent->lastAngles ); + + // store base angles for later + VectorCopy( ent->s.angles, ent->pos1 ); + + ent->e_UseFunc = useF_emplaced_gun_use; + ent->bounceCount = 0;//to distinguish it from the eweb + + gi.linkentity (ent); +} + +//==================================================== +//General Emplaced Weapon Funcs called in g_active.cpp +//==================================================== + +void G_UpdateEmplacedWeaponData( gentity_t *ent ) +{ + if ( ent && ent->owner && ent->health > 0 ) + { + gentity_t *chair = ent->owner; + if ( chair->e_UseFunc == useF_emplaced_gun_use )//yeah, crappy way to check this, but... + {//one that you sit in + //take the emplaced gun's waypoint as your own + ent->waypoint = chair->waypoint; + + //update the actual origin of the sitter + mdxaBone_t boltMatrix; + vec3_t chairAng = {0, ent->client->ps.viewangles[YAW], 0}; + + // Getting the seat bolt here + gi.G2API_GetBoltMatrix( chair->ghoul2, chair->playerModel, chair->headBolt, + &boltMatrix, chairAng, chair->currentOrigin, (cg.time?cg.time:level.time), + NULL, chair->s.modelScale ); + // Storing ent position, bolt position, and bolt axis + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->ps.origin ); + gi.linkentity( ent ); + } + else if ( chair->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but... + {//standing at an E-Web + EWebPositionUser( ent, chair ); + } + } +} + +void ExitEmplacedWeapon( gentity_t *ent ) +{ + // requesting to unlock from the weapon + // We'll leave the gun pointed in the direction it was last facing, though we'll cut out the pitch + if ( ent->client ) + { + // if we are the player we will have put down a brush that blocks NPCs so that we have a clear spot to get back out. + //gentity_t *place = G_Find( NULL, FOFS(classname), "emp_placeholder" ); + + if ( ent->health > 0 ) + {//he's still alive, and we have a placeholder, so put him back + if ( ent->owner->nextTrain ) + { + // reset the players position + VectorCopy( ent->owner->nextTrain->currentOrigin, ent->client->ps.origin ); + //reset ent's size to normal + VectorCopy( ent->owner->nextTrain->mins, ent->mins ); + VectorCopy( ent->owner->nextTrain->maxs, ent->maxs ); + //free the placeholder + G_FreeEntity( ent->owner->nextTrain ); + //re-link the ent + gi.linkentity( ent ); + } + else if ( ent->owner->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but... + { + // so give 'em a push away from us + vec3_t backDir, start, end; + trace_t trace; + gentity_t *eweb = ent->owner; + float curRadius = 0.0f; + float minRadius, maxRadius; + qboolean safeExit = qfalse; + + VectorSubtract( ent->currentOrigin, eweb->currentOrigin, backDir ); + backDir[2] = 0; + minRadius = VectorNormalize( backDir )-8.0f; + + maxRadius = (ent->maxs[0]+ent->maxs[1])*0.5f; + maxRadius += (eweb->maxs[0]+eweb->maxs[1])*0.5f; + maxRadius *= 1.5f; + + if ( minRadius >= maxRadius - 1.0f ) + { + maxRadius = minRadius + 8.0f; + } + + ent->owner = NULL;//so his trace hits me + + for ( curRadius = minRadius; curRadius <= maxRadius; curRadius += 4.0f ) + { + VectorMA( ent->currentOrigin, curRadius, backDir, start ); + //make sure they're not in the ground + VectorCopy( start, end ); + start[2] += 18; + end[2] -= 18; + gi.trace(&trace, start, ent->mins, ent->maxs, end, ent->s.number, ent->clipmask); + if ( !trace.allsolid && !trace.startsolid ) + { + G_SetOrigin( ent, trace.endpos ); + gi.linkentity( ent ); + safeExit = qtrue; + break; + } + } + //Hmm... otherwise, don't allow them to get off? + ent->owner = eweb; + if ( !safeExit ) + {//don't try again for a second + ent->owner->delay = level.time + 500; + return; + } + } + } + else if ( ent->health <= 0 ) + { + // dead, so give 'em a push out of the chair + vec3_t dir; + AngleVectors( ent->owner->s.angles, NULL, dir, NULL ); + + if ( rand() & 1 ) + { + VectorScale( dir, -1, dir ); + } + + VectorMA( ent->client->ps.velocity, 75, dir, ent->client->ps.velocity ); + } + //don't let them move towards me for a couple frames so they don't step back into me while I'm becoming solid to them + if ( ent->s.number < MAX_CLIENTS ) + { + if ( ent->client->ps.pm_time < 100 ) + { + ent->client->ps.pm_time = 100; + } + ent->client->ps.pm_flags |= (PMF_TIME_NOFRICTION|PMF_TIME_KNOCKBACK); + } + + if ( !ent->owner->bounceCount ) + {//not an EWeb - the overridden bone angles will remember the angle we left it at + VectorCopy( ent->client->ps.viewangles, ent->owner->s.angles ); + ent->owner->s.angles[PITCH] = 0; + G_SetAngles( ent->owner, ent->owner->s.angles ); + VectorCopy( ent->owner->s.angles, ent->owner->pos1 ); + } + } + + // Remove the emplaced gun from our inventory + ent->client->ps.stats[STAT_WEAPONS] &= ~( 1 << WP_EMPLACED_GUN ); + +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void CG_ChangeWeapon( int num ); + if ( ent->health <= 0 ) + {//when die, don't set weapon back on when ejected from emplaced/eweb + //empty hands + ent->client->ps.weapon = WP_NONE; + if ( ent->NPC ) + { + ChangeWeapon( ent, ent->client->ps.weapon ); // should be OK actually. + } + else + { + CG_ChangeWeapon( ent->client->ps.weapon ); + } + if ( ent->s.number < MAX_CLIENTS ) + { + gi.cvar_set( "cg_thirdperson", "1" ); + } + } + else + { + // when we lock or unlock from the the gun, we get our old weapon back + ent->client->ps.weapon = ent->owner->s.weapon; + + if ( ent->NPC ) + {//BTW, if a saber-using NPC ever gets off of an emplaced gun/eweb, this will not work, look at NPC_ChangeWeapon for the proper way + ChangeWeapon( ent, ent->client->ps.weapon ); + } + else + { + G_RemoveWeaponModels( ent ); + CG_ChangeWeapon( ent->client->ps.weapon ); + if ( ent->client->ps.weapon == WP_SABER ) + { + WP_SaberAddG2SaberModels( ent ); + } + else + { + G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 ); + } + + if ( ent->s.number < MAX_CLIENTS ) + { + if ( ent->client->ps.weapon == WP_SABER ) + { + gi.cvar_set( "cg_thirdperson", "1" ); + } + else if ( ent->client->ps.weapon != WP_SABER && cg_gunAutoFirst.integer ) + { + gi.cvar_set( "cg_thirdperson", "0" ); + } + } + } + + if ( ent->client->ps.weapon == WP_SABER ) + { + if ( ent->owner->alt_fire ) + { + ent->client->ps.SaberActivate(); + } + else + { + ent->client->ps.SaberDeactivate(); + } + } + } + //set the emplaced gun/eweb's weapon back to the emplaced gun + ent->owner->s.weapon = WP_EMPLACED_GUN; +// gi.G2API_DetachG2Model( &ent->ghoul2[ent->playerModel] ); + + ent->s.eFlags &= ~EF_LOCKED_TO_WEAPON; + ent->client->ps.eFlags &= ~EF_LOCKED_TO_WEAPON; + + ent->owner->noDamageTeam = TEAM_FREE; + ent->owner->svFlags &= ~SVF_NONNPC_ENEMY; + ent->owner->delay = level.time; + ent->owner->activator = NULL; + + if ( !ent->NPC ) + { + // by keeping the owner, a dead npc can be pushed out of the chair without colliding with it + ent->owner = NULL; + } +} + +void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd ) +{ + if (( (*ucmd)->buttons & BUTTON_USE || (*ucmd)->forwardmove < 0 || (*ucmd)->upmove > 0 ) && ent->owner && ent->owner->delay + 500 < level.time ) + { + ent->owner->s.loopSound = 0; + + if ( ent->owner->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but... + { + G_Sound( ent, G_SoundIndex( "sound/weapons/eweb/eweb_dismount.mp3" )); + } + else + { + G_Sound( ent, G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" )); + + } +#ifdef _IMMERSION + G_Force( ent, G_ForceIndex( "fffx/weapons/emplaced/emplaced_dismount", FF_CHANNEL_TOUCH ) ); +#endif // _IMMERSION + + ExitEmplacedWeapon( ent ); + (*ucmd)->buttons &= ~BUTTON_USE; + if ( (*ucmd)->upmove > 0 ) + {//don't actually jump + (*ucmd)->upmove = 0; + } + } + else + { + // this is a crappy way to put sounds on a moving eweb.... + if ( ent->owner + && ent->owner->e_UseFunc == useF_eweb_use )//yeah, crappy way to check this, but... + { + if ( !VectorCompare( ent->client->ps.viewangles, ent->owner->movedir )) + { + ent->owner->s.loopSound = G_SoundIndex( "sound/weapons/eweb/eweb_aim.wav" ); + ent->owner->fly_sound_debounce_time = level.time; + } + else + { + if ( ent->owner->fly_sound_debounce_time + 100 <= level.time ) + { + ent->owner->s.loopSound = 0; + } + } + + VectorCopy( ent->client->ps.viewangles, ent->owner->movedir ); + } + + // don't allow movement, weapon switching, and most kinds of button presses + (*ucmd)->forwardmove = 0; + (*ucmd)->rightmove = 0; + (*ucmd)->upmove = 0; + (*ucmd)->buttons &= (BUTTON_ATTACK|BUTTON_ALT_ATTACK); + + (*ucmd)->weapon = ent->client->ps.weapon; //WP_EMPLACED_GUN; + + if ( ent->health <= 0 ) + { + ExitEmplacedWeapon( ent ); + } + } +} diff --git a/code/game/g_functions.cpp b/code/game/g_functions.cpp new file mode 100644 index 0000000..ff09188 --- /dev/null +++ b/code/game/g_functions.cpp @@ -0,0 +1,412 @@ +// Filename:- g_functions.cpp +// + +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + + + + +// This file contains the 8 (so far) function calls that replace the 8 function ptrs in the gentity_t structure + +#include "g_local.h" +#include "..\cgame\cg_local.h" +#include "g_functions.h" + +void GEntity_ThinkFunc(gentity_t *self) +{ +//#define THINKCASE(blah) case thinkF_ ## blah: blah(self); OutputDebugString(va("%s\n",#blah));break; +#define THINKCASE(blah) case thinkF_ ## blah: blah(self); break; + + switch (self->e_ThinkFunc) + { + case thinkF_NULL: + break; + + THINKCASE( funcBBrushDieGo ) + THINKCASE( ExplodeDeath ) + THINKCASE( RespawnItem ) + THINKCASE( G_FreeEntity ) + THINKCASE( FinishSpawningItem ) + THINKCASE( locateCamera ) + THINKCASE( G_RunObject ) + THINKCASE( ReturnToPos1 ) + THINKCASE( Use_BinaryMover_Go ) + THINKCASE( Think_MatchTeam ) + THINKCASE( Think_BeginMoving ) + THINKCASE( Think_SetupTrainTargets ) + THINKCASE( Think_SpawnNewDoorTrigger ) + THINKCASE( ref_link ) + THINKCASE( Think_Target_Delay ) + THINKCASE( target_laser_think ) + THINKCASE( target_laser_start ) + THINKCASE( target_location_linkup ) + THINKCASE( scriptrunner_run ) + THINKCASE( multi_wait ) + THINKCASE( multi_trigger_run ) + THINKCASE( trigger_always_think ) + THINKCASE( AimAtTarget ) + THINKCASE( func_timer_think ) + THINKCASE( NPC_RemoveBody ) + THINKCASE( Disappear ) + THINKCASE( NPC_Think ) + THINKCASE( NPC_Spawn_Go ) + THINKCASE( NPC_Begin ) + THINKCASE( moverCallback ) + THINKCASE( anglerCallback ) + // This RemoveOwner need to exist here anymore??? + THINKCASE( RemoveOwner ) + THINKCASE( MakeOwnerInvis ) + THINKCASE( MakeOwnerEnergy ) + THINKCASE( func_usable_think ) + THINKCASE( misc_dlight_think ) + THINKCASE( health_think ) + THINKCASE( ammo_think ) + THINKCASE( trigger_teleporter_find_closest_portal ) + THINKCASE( thermalDetonatorExplode ) + THINKCASE( WP_ThermalThink ) + THINKCASE( trigger_hurt_reset ) + THINKCASE( turret_base_think ) + THINKCASE( turret_head_think ) + THINKCASE( laser_arm_fire ) + THINKCASE( laser_arm_start ) + THINKCASE( trigger_visible_check_player_visibility ) + THINKCASE( target_relay_use_go ) + THINKCASE( trigger_cleared_fire ) + THINKCASE( MoveOwner ) + THINKCASE( SolidifyOwner ) + THINKCASE( cycleCamera ) + THINKCASE( spawn_ammo_crystal_trigger ) + THINKCASE( NPC_ShySpawn ) + THINKCASE( func_wait_return_solid ) + THINKCASE( InflateOwner ) + THINKCASE( mega_ammo_think ) + THINKCASE( misc_replicator_item_finish_spawn ) + THINKCASE( fx_runner_link ) + THINKCASE( fx_runner_think ) + THINKCASE( fx_rain_think ) // delay flagging entities as portal entities (for sky portals) + THINKCASE( removeBoltSurface) + THINKCASE( set_MiscAnim) + THINKCASE( LimbThink ) + THINKCASE( laserTrapThink ) + THINKCASE( TieFighterThink ) + THINKCASE( TieBomberThink ) + THINKCASE( rocketThink ) + THINKCASE( prox_mine_think ) + THINKCASE( emplaced_blow ) + THINKCASE( WP_Explode ) + THINKCASE( pas_think ) // personal assault sentry + THINKCASE( ion_cannon_think ) + THINKCASE( maglock_link ) + THINKCASE( WP_flechette_alt_blow ) + THINKCASE( WP_prox_mine_think ) + THINKCASE( camera_aim ) + THINKCASE( fx_explosion_trail_link ) + THINKCASE( fx_explosion_trail_think ) + THINKCASE( fx_target_beam_link ) + THINKCASE( fx_target_beam_think ) + THINKCASE( spotlight_think ) + THINKCASE( spotlight_link ) + THINKCASE( trigger_push_checkclear ) + THINKCASE( DEMP2_AltDetonate ) + THINKCASE( DEMP2_AltRadiusDamage ) + THINKCASE( panel_turret_think ) + THINKCASE( welder_think ) + THINKCASE( gas_random_jet ) + THINKCASE( poll_converter ) // dumb loop sound handling + THINKCASE( spawn_rack_goods ) // delay spawn of goods to help on ents + THINKCASE( NoghriGasCloudThink ) + + THINKCASE( G_PortalifyEntities ) // delay flagging entities as portal entities (for sky portals) + + THINKCASE( misc_weapon_shooter_aim ) + THINKCASE( misc_weapon_shooter_fire ) + + THINKCASE( beacon_think ) + + default: + Com_Error(ERR_DROP, "GEntity_ThinkFunc: case %d not handled!\n",self->e_ThinkFunc); + break; + } +} + +// note different switch-case code for CEntity as opposed to GEntity (CEntity goes through parent GEntity first)... +// +void CEntity_ThinkFunc(centity_s *cent) +{ +//#define CLTHINKCASE(blah) case clThinkF_ ## blah: blah(cent); OutputDebugString(va("%s\n",#blah));break; +#define CLTHINKCASE(blah) case clThinkF_ ## blah: blah(cent); break; + + switch (cent->gent->e_clThinkFunc) + { + case clThinkF_NULL: + break; + + CLTHINKCASE( CG_DLightThink ) + CLTHINKCASE( CG_MatrixEffect ) + CLTHINKCASE( CG_Limb ) + + default: + Com_Error(ERR_DROP, "CEntity_ThinkFunc: case %d not handled!\n",cent->gent->e_clThinkFunc); + break; + } +} + + +void GEntity_ReachedFunc(gentity_t *self) +{ +//#define REACHEDCASE(blah) case reachedF_ ## blah: blah(self); OutputDebugString(va("%s\n",#blah));break; +#define REACHEDCASE(blah) case reachedF_ ## blah: blah(self); break; + + switch (self->e_ReachedFunc) + { + case reachedF_NULL: + break; + + REACHEDCASE( Reached_BinaryMover ) + REACHEDCASE( Reached_Train ) + REACHEDCASE( moverCallback ) + REACHEDCASE( moveAndRotateCallback ) + + default: + Com_Error(ERR_DROP, "GEntity_ReachedFunc: case %d not handled!\n",self->e_ReachedFunc); + break; + } +} + + + +void GEntity_BlockedFunc(gentity_t *self, gentity_t *other) +{ +//#define BLOCKEDCASE(blah) case blockedF_ ## blah: blah(self,other); OutputDebugString(va("%s\n",#blah));break; +#define BLOCKEDCASE(blah) case blockedF_ ## blah: blah(self,other); break; + + switch (self->e_BlockedFunc) + { + case blockedF_NULL: + break; + + BLOCKEDCASE( Blocked_Door ) + BLOCKEDCASE( Blocked_Mover ) + + default: + Com_Error(ERR_DROP, "GEntity_BlockedFunc: case %d not handled!\n",self->e_BlockedFunc); + break; + } +} + +void GEntity_TouchFunc(gentity_t *self, gentity_t *other, trace_t *trace) +{ +//#define TOUCHCASE(blah) case touchF_ ## blah: blah(self,other,trace); OutputDebugString(va("%s\n",#blah));break; +#define TOUCHCASE(blah) case touchF_ ## blah: blah(self,other,trace); break; + + switch (self->e_TouchFunc) + { + case touchF_NULL: + break; + + TOUCHCASE( Touch_Item ) + TOUCHCASE( teleporter_touch ) + TOUCHCASE( charge_stick ) + TOUCHCASE( Touch_DoorTrigger ) + TOUCHCASE( Touch_PlatCenterTrigger ) + TOUCHCASE( Touch_Plat ) + TOUCHCASE( Touch_Button ) + TOUCHCASE( Touch_Multi ) + TOUCHCASE( trigger_push_touch ) + TOUCHCASE( trigger_teleporter_touch ) + TOUCHCASE( hurt_touch ) + TOUCHCASE( NPC_Touch ) + TOUCHCASE( touch_ammo_crystal_tigger ) + TOUCHCASE( funcBBrushTouch ) + TOUCHCASE( touchLaserTrap ) + TOUCHCASE( prox_mine_stick ) + TOUCHCASE( func_rotating_touch ) + TOUCHCASE( TouchTieBomb ) + + default: + Com_Error(ERR_DROP, "GEntity_TouchFunc: case %d not handled!\n",self->e_TouchFunc); + } +} + +void GEntity_UseFunc(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + if ( !self || (self->svFlags&SVF_INACTIVE) ) + { + return; + } +//#define USECASE(blah) case useF_ ## blah: blah(self,other,activator); OutputDebugString(va("%s\n",#blah));break; +#define USECASE(blah) case useF_ ## blah: blah(self,other,activator); break; + + switch (self->e_UseFunc) + { + case useF_NULL: + break; + + USECASE( funcBBrushUse ) + USECASE( misc_model_use ) + USECASE( Use_Item ) + USECASE( Use_Shooter ) + USECASE( GoExplodeDeath ) + USECASE( Use_BinaryMover ) + USECASE( use_wall ) + USECASE( Use_Target_Give ) + USECASE( Use_Target_Delay ) + USECASE( Use_Target_Score ) + USECASE( Use_Target_Print ) + USECASE( Use_Target_Speaker ) + USECASE( target_laser_use ) + USECASE( target_relay_use ) + USECASE( target_kill_use ) + USECASE( target_counter_use ) + USECASE( target_random_use ) + USECASE( target_scriptrunner_use ) + USECASE( target_gravity_change_use ) + USECASE( target_friction_change_use ) + USECASE( target_teleporter_use ) + USECASE( Use_Multi ) + USECASE( Use_target_push ) + USECASE( hurt_use ) + USECASE( func_timer_use ) + USECASE( trigger_entdist_use ) + USECASE( func_usable_use ) + USECASE( target_activate_use ) + USECASE( target_deactivate_use ) + USECASE( NPC_Use ) + USECASE( NPC_Spawn ) + USECASE( misc_dlight_use ) + USECASE( health_use ) + USECASE( ammo_use ) + USECASE( mega_ammo_use ) + USECASE( target_level_change_use ) + USECASE( target_change_parm_use ) + USECASE( turret_base_use ) + USECASE( laser_arm_use ) + USECASE( func_static_use ) + USECASE( target_play_music_use ) + USECASE( misc_model_useup ) + USECASE( misc_portal_use ) + USECASE( target_autosave_use ) + USECASE( switch_models ) + USECASE( misc_replicator_item_spawn ) + USECASE( misc_replicator_item_remove ) + USECASE( target_secret_use) + USECASE( func_bobbing_use ) + USECASE( func_rotating_use ) + USECASE( fx_runner_use ) + USECASE( funcGlassUse ) + USECASE( TrainUse ) + USECASE( misc_trip_mine_activate ) + USECASE( emplaced_gun_use ) + USECASE( shield_power_converter_use ) + USECASE( ammo_power_converter_use ) + USECASE( bomb_planted_use ) + USECASE( beacon_use ) + USECASE( security_panel_use ) + USECASE( ion_cannon_use ) + USECASE( camera_use ) + USECASE( fx_explosion_trail_use ) + USECASE( fx_target_beam_use ) + USECASE( sentry_use ) + USECASE( spotlight_use ) + USECASE( misc_atst_use ) + USECASE( panel_turret_use ) + USECASE( welder_use ) + USECASE( jabba_cam_use ) + USECASE( misc_use ) + USECASE( pas_use ) + USECASE( item_spawn_use ) + USECASE( NPC_VehicleSpawnUse ) + USECASE( misc_weapon_shooter_use ) + USECASE( eweb_use ) + USECASE( TieFighterUse ); + + default: + Com_Error(ERR_DROP, "GEntity_UseFunc: case %d not handled!\n",self->e_UseFunc); + } +} + +void GEntity_PainFunc(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc) +{ +//#define PAINCASE(blah) case painF_ ## blah: blah(self,attacker,damage); OutputDebugString(va("%s\n",#blah));break; +#define PAINCASE(blah) case painF_ ## blah: blah(self,inflictor,attacker,point,damage,mod,hitLoc); break; + + switch (self->e_PainFunc) + { + case painF_NULL: + break; + + PAINCASE( funcBBrushPain ) + PAINCASE( misc_model_breakable_pain ) + PAINCASE( NPC_Pain ) + PAINCASE( station_pain ) + PAINCASE( func_usable_pain ) + PAINCASE( NPC_ATST_Pain ) + PAINCASE( NPC_ST_Pain ) + PAINCASE( NPC_Jedi_Pain ) + PAINCASE( NPC_Droid_Pain ) + PAINCASE( NPC_Probe_Pain ) + PAINCASE( NPC_MineMonster_Pain ) + PAINCASE( NPC_Howler_Pain ) + PAINCASE( NPC_Rancor_Pain ) + PAINCASE( NPC_Wampa_Pain ) + PAINCASE( NPC_SandCreature_Pain ) + PAINCASE( NPC_Seeker_Pain ) + PAINCASE( NPC_Remote_Pain ) + PAINCASE( emplaced_gun_pain ) + PAINCASE( NPC_Mark1_Pain ) + PAINCASE( NPC_Sentry_Pain ) + PAINCASE( NPC_Mark2_Pain ) + PAINCASE( PlayerPain ) + PAINCASE( GasBurst ) + PAINCASE( CrystalCratePain ) + PAINCASE( TurretPain ) + PAINCASE( eweb_pain ) + + default: + Com_Error(ERR_DROP, "GEntity_PainFunc: case %d not handled!\n",self->e_PainFunc); + } +} + + +void GEntity_DieFunc(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc) +{ +//#define DIECASE(blah) case dieF_ ## blah: blah(self,inflictor,attacker,damage,mod); OutputDebugString(va("%s\n",#blah));break; +#define DIECASE(blah) case dieF_ ## blah: blah(self,inflictor,attacker,damage,mod,dFlags,hitLoc); break; + + switch (self->e_DieFunc) + { + case dieF_NULL: + break; + + DIECASE( funcBBrushDie ) + DIECASE( misc_model_breakable_die ) + DIECASE( misc_model_cargo_die ) + DIECASE( func_train_die ) + DIECASE( player_die ) + DIECASE( ExplodeDeath_Wait ) + DIECASE( ExplodeDeath ) + DIECASE( func_usable_die ) + DIECASE( turret_die ) + DIECASE( funcGlassDie ) +// DIECASE( laserTrapDelayedExplode ) + DIECASE( emplaced_gun_die ) + DIECASE( WP_ExplosiveDie ) + DIECASE( ion_cannon_die ) + DIECASE( maglock_die ) + DIECASE( camera_die ) + DIECASE( Mark1_die ) + DIECASE( Interrogator_die ) + DIECASE( misc_atst_die ) + DIECASE( misc_panel_turret_die ) + DIECASE( thermal_die ) + DIECASE( eweb_die ) + + default: + Com_Error(ERR_DROP, "GEntity_DieFunc: case %d not handled!\n",self->e_DieFunc); + } +} + +//////////////////// eof ///////////////////// + diff --git a/code/game/g_functions.h b/code/game/g_functions.h new file mode 100644 index 0000000..f96a31e --- /dev/null +++ b/code/game/g_functions.h @@ -0,0 +1,630 @@ +// Filename:- g_functions.h +// + +#ifndef G_FUNCTIONS +#define G_FUNCTIONS + +#undef thinkFunc_t +#undef clThinkFunc_t +#undef reachedFunc_t +#undef blockedFunc_t +#undef touchFunc_t +#undef useFunc_t +#undef painFunc_t +#undef dieFunc_t + +// void (*think)(gentity_t *self); +typedef enum +{ + thinkF_NULL = 0, + // + thinkF_teleporter_think, + thinkF_funcBBrushDieGo, + thinkF_ExplodeDeath, + thinkF_RespawnItem, + thinkF_G_FreeEntity, + thinkF_FinishSpawningItem, + thinkF_locateCamera, + thinkF_G_RunObject, + thinkF_ReturnToPos1, + thinkF_Use_BinaryMover_Go, + thinkF_Think_MatchTeam, + thinkF_Think_BeginMoving, + thinkF_Think_SetupTrainTargets, + thinkF_Think_SpawnNewDoorTrigger, + thinkF_ref_link, + thinkF_Think_Target_Delay, + thinkF_target_laser_think, + thinkF_target_laser_start, + thinkF_target_location_linkup, + thinkF_scriptrunner_run, + thinkF_multi_wait, + thinkF_multi_trigger_run, + thinkF_trigger_always_think, + thinkF_AimAtTarget, + thinkF_func_timer_think, + thinkF_NPC_RemoveBody, + thinkF_Disappear, + thinkF_NPC_Think, + thinkF_NPC_Spawn_Go, + thinkF_NPC_Begin, + thinkF_moverCallback, + thinkF_anglerCallback, + thinkF_RemoveOwner, + thinkF_MakeOwnerInvis, + thinkF_MakeOwnerEnergy, + thinkF_transporter_stream_think, + thinkF_func_usable_think, + thinkF_misc_dlight_think, + thinkF_health_think, + thinkF_ammo_think, + thinkF_trigger_teleporter_find_closest_portal, + thinkF_thermalDetonatorExplode, + thinkF_WP_ThermalThink, + thinkF_trigger_hurt_reset, + thinkF_turret_base_think, + thinkF_turret_head_think, + thinkF_HS_Think, + thinkF_laser_arm_fire, + thinkF_laser_arm_start, + thinkF_trigger_visible_check_player_visibility, + thinkF_target_relay_use_go, + thinkF_trigger_cleared_fire, + thinkF_MoveOwner, + thinkF_SolidifyOwner, + thinkF_cycleCamera, + thinkF_spawn_ammo_crystal_trigger, + thinkF_NPC_ShySpawn, + thinkF_func_wait_return_solid, + thinkF_InflateOwner, + thinkF_mega_ammo_think, + thinkF_misc_replicator_item_finish_spawn, + thinkF_fx_runner_link, + thinkF_fx_runner_think, + thinkF_fx_rain_think, // cdr added + thinkF_removeBoltSurface, + thinkF_set_MiscAnim, + thinkF_LimbThink, + thinkF_laserTrapThink, + thinkF_TieFighterThink, + thinkF_TieBomberThink, + thinkF_rocketThink, + thinkF_prox_mine_think, + thinkF_emplaced_blow, + thinkF_WP_Explode, + thinkF_pas_think, //personal assault sentry + thinkF_ion_cannon_think, + thinkF_maglock_link, + thinkF_WP_flechette_alt_blow, + thinkF_WP_prox_mine_think, + thinkF_camera_aim, + thinkF_fx_explosion_trail_link, + thinkF_fx_explosion_trail_think, + thinkF_fx_target_beam_link, + thinkF_fx_target_beam_think, + thinkF_spotlight_think, + thinkF_spotlight_link, + thinkF_trigger_push_checkclear, + thinkF_DEMP2_AltDetonate, + thinkF_DEMP2_AltRadiusDamage, + thinkF_panel_turret_think, + thinkF_welder_think, + thinkF_gas_random_jet, + thinkF_poll_converter, + thinkF_spawn_rack_goods, + thinkF_misc_weapon_shooter_aim, + thinkF_misc_weapon_shooter_fire, + thinkF_beacon_think, + thinkF_NoghriGasCloudThink, + + //rww - added for sky portals + thinkF_G_PortalifyEntities, + +} thinkFunc_t; + +// THINK functions... +// +extern void teleporter_think ( gentity_t *ent ); +extern void funcBBrushDieGo ( gentity_t *ent ); +extern void ExplodeDeath ( gentity_t *ent ); +extern void RespawnItem ( gentity_t *ent ); +extern void G_FreeEntity ( gentity_t *ent ); +extern void FinishSpawningItem ( gentity_t *ent ); +extern void locateCamera ( gentity_t *ent ); +extern void G_RunObject ( gentity_t *ent ); +extern void ReturnToPos1 ( gentity_t *ent ); +extern void Use_BinaryMover_Go ( gentity_t *ent ); +extern void Think_MatchTeam ( gentity_t *ent ); +extern void Think_MatchTeam ( gentity_t *ent ); +extern void Think_BeginMoving ( gentity_t *ent ); +extern void Think_SetupTrainTargets ( gentity_t *ent ); +extern void Think_SpawnNewDoorTrigger ( gentity_t *ent ); +extern void ref_link ( gentity_t *ent ); +extern void Think_Target_Delay ( gentity_t *ent ); +extern void target_laser_think ( gentity_t *ent ); +extern void target_laser_start ( gentity_t *ent ); +extern void target_location_linkup ( gentity_t *ent ); +extern void scriptrunner_run ( gentity_t *ent ); +extern void multi_wait ( gentity_t *ent ); +extern void multi_trigger_run ( gentity_t *ent ); +extern void trigger_always_think ( gentity_t *ent ); +extern void AimAtTarget ( gentity_t *ent ); +extern void func_timer_think ( gentity_t *ent ); +extern void NPC_RemoveBody ( gentity_t *ent ); +extern void Disappear ( gentity_t *ent ); +extern void NPC_Think ( gentity_t *ent ); +extern void NPC_Spawn_Go ( gentity_t *ent ); +extern void NPC_Begin ( gentity_t *ent ); +extern void moverCallback ( gentity_t *ent ); +extern void anglerCallback ( gentity_t *ent ); +extern void RemoveOwner ( gentity_t *ent ); +extern void MakeOwnerInvis ( gentity_t *ent ); +extern void MakeOwnerEnergy ( gentity_t *ent ); +extern void func_usable_think ( gentity_t *self ); +extern void misc_dlight_think ( gentity_t *ent ); +extern void laser_link ( gentity_t *ent ); +extern void blow_chunks_link ( gentity_t *ent ); +extern void health_think ( gentity_t *ent ); +extern void ammo_think ( gentity_t *ent ); +extern void trigger_teleporter_find_closest_portal ( gentity_t *self ); +extern void thermalDetonatorExplode ( gentity_t *ent ); +extern void WP_ThermalThink ( gentity_t *ent ); +extern void trigger_hurt_reset ( gentity_t *self ); +extern void turret_base_think ( gentity_t *self ); +extern void turret_head_think ( gentity_t *self ); +extern void laser_arm_fire ( gentity_t *ent ); +extern void laser_arm_start ( gentity_t *base ); +extern void trigger_visible_check_player_visibility ( gentity_t *self ); +extern void target_relay_use_go ( gentity_t *self ); +extern void trigger_cleared_fire ( gentity_t *self ); +extern void MoveOwner ( gentity_t *self ); +extern void SolidifyOwner ( gentity_t *self ); +extern void cycleCamera ( gentity_t *self ); +extern void spawn_ammo_crystal_trigger ( gentity_t *ent ); +extern void NPC_ShySpawn ( gentity_t *ent ); +extern void func_wait_return_solid ( gentity_t *self ); +extern void InflateOwner ( gentity_t *self ); +extern void mega_ammo_think ( gentity_t *self ); +extern void misc_replicator_item_finish_spawn( gentity_t *self ); +extern void fx_runner_link ( gentity_t *self ); +extern void fx_runner_think ( gentity_t *self ); +extern void fx_rain_think ( gentity_t *self ); +extern void set_MiscAnim ( gentity_t *self); +extern void removeBoltSurface ( gentity_t *self); +extern void LimbThink ( gentity_t *ent ); +extern void laserTrapThink ( gentity_t *self ); +extern void TieFighterThink ( gentity_t *self ); +extern void TieBomberThink ( gentity_t *self ); +extern void rocketThink ( gentity_t *ent ); +extern void prox_mine_think ( gentity_t *ent ); +extern void emplaced_blow ( gentity_t *self ); +extern void WP_Explode ( gentity_t *self ); +extern void pas_think ( gentity_t *self ); +extern void ion_cannon_think ( gentity_t *self ); +extern void maglock_link ( gentity_t *self ); +extern void WP_flechette_alt_blow ( gentity_t *self ); +extern void WP_prox_mine_think ( gentity_t *self ); +extern void camera_aim ( gentity_t *self ); +extern void fx_explosion_trail_link ( gentity_t *self ); +extern void fx_explosion_trail_think( gentity_t *self ); +extern void fx_target_beam_link ( gentity_t *self ); +extern void fx_target_beam_think ( gentity_t *self ); +extern void spotlight_think ( gentity_t *self ); +extern void spotlight_link ( gentity_t *self ); +extern void trigger_push_checkclear ( gentity_t *self ); +extern void DEMP2_AltDetonate ( gentity_t *self ); +extern void DEMP2_AltRadiusDamage ( gentity_t *self ); +extern void panel_turret_think ( gentity_t *self ); +extern void welder_think ( gentity_t *self ); +extern void gas_random_jet ( gentity_t *self ); +extern void poll_converter ( gentity_t *self ); +extern void spawn_rack_goods ( gentity_t *self ); +extern void NoghriGasCloudThink ( gentity_t *self ); + +extern void G_PortalifyEntities ( gentity_t *ent ); +extern void misc_weapon_shooter_aim ( gentity_t *self ); +extern void misc_weapon_shooter_fire( gentity_t *self ); + +extern void beacon_think ( gentity_t *self ); + + +// void (*clThink)(centity_s *cent); //Think func for equivalent centity +typedef enum +{ + clThinkF_NULL = 0, + // + clThinkF_CG_DLightThink, + clThinkF_CG_MatrixEffect, + clThinkF_CG_Limb, + +} clThinkFunc_t; + +// CEntity THINK functions... +// +extern void CG_DLightThink ( centity_t *cent ); +extern void CG_MatrixEffect ( centity_t *cent ); +extern void CG_Limb ( centity_t *cent ); + +// void (*reached)(gentity_t *self); // movers call this when hitting endpoint +typedef enum +{ + reachedF_NULL = 0, + // + reachedF_Reached_BinaryMover, + reachedF_Reached_Train, + reachedF_moverCallback, + reachedF_moveAndRotateCallback + +} reachedFunc_t; + +// REACHED functions... +// +extern void Reached_BinaryMover ( gentity_t *ent ); +extern void Reached_Train ( gentity_t *ent ); +extern void moverCallback ( gentity_t *ent ); +extern void moveAndRotateCallback( gentity_t *ent ); + + +// void (*blocked)(gentity_t *self, gentity_t *other); +typedef enum +{ + blockedF_NULL = 0, + // + blockedF_Blocked_Door, + blockedF_Blocked_Mover + +} blockedFunc_t; + +// BLOCKED functions... +// +extern void Blocked_Door (gentity_t *self, gentity_t *other); +extern void Blocked_Mover (gentity_t *self, gentity_t *other); + + + +// void (*touch)(gentity_t *self, gentity_t *other, trace_t *trace); +typedef enum +{ + touchF_NULL = 0, + // + touchF_Touch_Item, + touchF_teleporter_touch, + touchF_charge_stick, + touchF_Touch_DoorTrigger, + touchF_Touch_PlatCenterTrigger, + touchF_Touch_Plat, + touchF_Touch_Button, + touchF_Touch_Multi, + touchF_trigger_push_touch, + touchF_trigger_teleporter_touch, + touchF_hurt_touch, + touchF_NPC_Touch, + touchF_touch_ammo_crystal_tigger, + touchF_funcBBrushTouch, + touchF_touchLaserTrap, + touchF_prox_mine_stick, + touchF_func_rotating_touch, + touchF_TouchTieBomb, +} touchFunc_t; + +// TOUCH functions... +// +extern void Touch_Item (gentity_t *self, gentity_t *other, trace_t *trace); +extern void teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace); +extern void charge_stick (gentity_t *self, gentity_t *other, trace_t *trace); +extern void Touch_DoorTrigger (gentity_t *self, gentity_t *other, trace_t *trace); +extern void Touch_PlatCenterTrigger (gentity_t *self, gentity_t *other, trace_t *trace); +extern void Touch_Plat (gentity_t *self, gentity_t *other, trace_t *trace); +extern void Touch_Button (gentity_t *self, gentity_t *other, trace_t *trace); +extern void Touch_Multi (gentity_t *self, gentity_t *other, trace_t *trace); +extern void trigger_push_touch (gentity_t *self, gentity_t *other, trace_t *trace); +extern void trigger_teleporter_touch(gentity_t *self, gentity_t *other, trace_t *trace); +extern void hurt_touch (gentity_t *self, gentity_t *other, trace_t *trace); +extern void NPC_Touch (gentity_t *self, gentity_t *other, trace_t *trace); +extern void touch_ammo_crystal_tigger ( gentity_t *self, gentity_t *other, trace_t *trace ); +extern void funcBBrushTouch ( gentity_t *ent, gentity_t *other, trace_t *trace ); +extern void touchLaserTrap ( gentity_t *ent, gentity_t *other, trace_t *trace ); +extern void prox_mine_stick( gentity_t *self, gentity_t *other, trace_t *trace ); +extern void func_rotating_touch (gentity_t *self, gentity_t *other, trace_t *trace); +extern void TouchTieBomb( gentity_t *self, gentity_t *other, trace_t *trace ); +extern void TieFighterUse( gentity_t *self, gentity_t *other, gentity_t *activator ); + +// void (*use)(gentity_t *self, gentity_t *other, gentity_t *activator); +typedef enum +{ + useF_NULL = 0, + // + useF_funcBBrushUse, + useF_misc_model_use, + useF_Use_Item, + useF_Use_Shooter, + useF_GoExplodeDeath, + useF_Use_BinaryMover, + useF_use_wall, + useF_Use_Target_Give, + useF_Use_Target_Delay, + useF_Use_Target_Score, + useF_Use_Target_Print, + useF_Use_Target_Speaker, + useF_target_laser_use, + useF_target_relay_use, + useF_target_kill_use, + useF_target_counter_use, + useF_target_random_use, + useF_target_scriptrunner_use, + useF_target_gravity_change_use, + useF_target_friction_change_use, + useF_target_teleporter_use, + useF_Use_Multi, + useF_Use_target_push, + useF_hurt_use, + useF_func_timer_use, + useF_trigger_entdist_use, + useF_func_usable_use, + useF_target_activate_use, + useF_target_deactivate_use, + useF_NPC_Use, + useF_NPC_Spawn, + useF_misc_dlight_use, + useF_health_use, + useF_ammo_use, + useF_mega_ammo_use, + useF_target_level_change_use, + useF_target_change_parm_use, + useF_crew_beam_in_use, + useF_turret_base_use, + useF_laser_arm_use, + useF_func_static_use, + useF_target_play_music_use, + useF_misc_model_useup, + useF_misc_portal_use, + useF_target_autosave_use, + useF_switch_models, + useF_misc_replicator_item_spawn, + useF_misc_replicator_item_remove, + useF_target_secret_use, + useF_func_bobbing_use, + useF_func_rotating_use, + useF_fx_runner_use, + useF_funcGlassUse, + useF_TrainUse, + useF_misc_trip_mine_activate, + useF_emplaced_gun_use, + useF_shield_power_converter_use, + useF_ammo_power_converter_use, + useF_bomb_planted_use, + useF_beacon_use, + useF_security_panel_use, + useF_ion_cannon_use, + useF_camera_use, + useF_fx_explosion_trail_use, + useF_fx_target_beam_use, + useF_sentry_use, + useF_spotlight_use, + useF_misc_atst_use, + useF_panel_turret_use, + useF_welder_use, + useF_jabba_cam_use, + useF_misc_use, + useF_pas_use, + useF_item_spawn_use, + useF_NPC_VehicleSpawnUse, + useF_misc_weapon_shooter_use, + useF_eweb_use, + useF_TieFighterUse, +} useFunc_t; + +// USE functions... +// +extern void funcBBrushUse ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void misc_model_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void Use_Item ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void Use_Shooter ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void GoExplodeDeath ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void Use_BinaryMover ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void use_wall ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void Use_Target_Give ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void Use_Target_Delay ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void Use_Target_Score ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void Use_Target_Print ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void Use_Target_Speaker ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_laser_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_relay_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_kill_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_counter_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_random_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_scriptrunner_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_gravity_change_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_friction_change_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_teleporter_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void Use_Multi ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void Use_target_push ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void hurt_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void func_timer_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void trigger_entdist_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void func_usable_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_activate_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void target_deactivate_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void NPC_Use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void NPC_Spawn ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void transporter_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void teleporter_use ( gentity_t *self, gentity_t *other, gentity_t *activator); +extern void misc_dlight_use ( gentity_t *ent, gentity_t *other, gentity_t *activator ); +extern void health_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void ammo_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void mega_ammo_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void target_level_change_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void target_change_parm_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void turret_base_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void laser_arm_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void func_static_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void target_play_music_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void misc_model_useup ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void misc_portal_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void target_autosave_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void switch_models ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void misc_replicator_item_spawn ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void misc_replicator_item_remove ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void target_secret_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void func_bobbing_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void func_rotating_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void fx_runner_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void funcGlassUse ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void TrainUse ( gentity_t *ent, gentity_t *other, gentity_t *activator ); +extern void misc_trip_mine_activate ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void emplaced_gun_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void shield_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void ammo_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void bomb_planted_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void beacon_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void security_panel_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void ion_cannon_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void camera_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void fx_explosion_trail_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void fx_target_beam_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void sentry_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void spotlight_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void misc_atst_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void panel_turret_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void welder_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void jabba_cam_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void misc_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void pas_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void item_spawn_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void NPC_VehicleSpawnUse ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void misc_weapon_shooter_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern void eweb_use ( gentity_t *self, gentity_t *other, gentity_t *activator ); + +// void (*pain)(gentity_t *self, gentity_t *attacker, int damage,int mod,int hitLoc); +typedef enum +{ + painF_NULL = 0, + // + painF_funcBBrushPain, + painF_misc_model_breakable_pain, + painF_NPC_Pain, + painF_station_pain, + painF_func_usable_pain, + painF_NPC_ATST_Pain, + painF_NPC_ST_Pain, + painF_NPC_Jedi_Pain, + painF_NPC_Droid_Pain, + painF_NPC_Probe_Pain, + painF_NPC_MineMonster_Pain, + painF_NPC_Howler_Pain, + painF_NPC_Rancor_Pain, + painF_NPC_Wampa_Pain, + painF_NPC_SandCreature_Pain, + painF_NPC_Seeker_Pain, + painF_NPC_Remote_Pain, + painF_emplaced_gun_pain, + painF_NPC_Mark1_Pain, + painF_NPC_GM_Pain, + painF_NPC_Sentry_Pain, + painF_NPC_Mark2_Pain, + painF_PlayerPain, + painF_GasBurst, + painF_CrystalCratePain, + painF_TurretPain, + painF_eweb_pain, +} painFunc_t; + +// PAIN functions... +// +extern void funcBBrushPain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void misc_model_breakable_pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void station_pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void func_usable_pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_ATST_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_ST_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Jedi_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Droid_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Probe_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_MineMonster_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Howler_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Rancor_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Wampa_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_SandCreature_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Seeker_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Remote_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void emplaced_gun_pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Mark1_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Sentry_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void NPC_Mark2_Pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void PlayerPain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +extern void GasBurst (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc=HL_NONE ); +extern void CrystalCratePain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc=HL_NONE); +extern void TurretPain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc=HL_NONE ); +extern void eweb_pain (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); + +// void (*die)(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod); +typedef enum +{ + dieF_NULL = 0, + // + dieF_funcBBrushDie, + dieF_misc_model_breakable_die, + dieF_misc_model_cargo_die, + dieF_func_train_die, + dieF_player_die, + dieF_ExplodeDeath_Wait, + dieF_ExplodeDeath, + dieF_func_usable_die, + dieF_turret_die, + dieF_funcGlassDie, +// dieF_laserTrapDelayedExplode, + dieF_emplaced_gun_die, + dieF_WP_ExplosiveDie, + dieF_ion_cannon_die, + dieF_maglock_die, + dieF_camera_die, + dieF_Mark1_die, + dieF_Interrogator_die, + dieF_misc_atst_die, + dieF_misc_panel_turret_die, + dieF_thermal_die, + dieF_eweb_die, +} dieFunc_t; + +// DIE functions... +// +extern void funcBBrushDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void misc_model_breakable_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void misc_model_cargo_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void func_train_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void player_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void ExplodeDeath_Wait (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void ExplodeDeath (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void func_usable_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void turret_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void funcGlassDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void laserTrapDelayedExplode (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void emplaced_gun_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void WP_ExplosiveDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void ion_cannon_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void maglock_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void camera_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void Mark1_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void Interrogator_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void misc_atst_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void misc_panel_turret_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void thermal_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); +extern void eweb_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); + +void GEntity_ThinkFunc(gentity_t *self); +void CEntity_ThinkFunc(centity_s *cent); //Think func for equivalent centity +void GEntity_ReachedFunc(gentity_t *self); // movers call this when hitting endpoint +void GEntity_BlockedFunc(gentity_t *self, gentity_t *other); +void GEntity_TouchFunc(gentity_t *self, gentity_t *other, trace_t *trace); +void GEntity_UseFunc(gentity_t *self, gentity_t *other, gentity_t *activator); +void GEntity_PainFunc(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc=HL_NONE); +void GEntity_DieFunc(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags=0,int hitLoc=HL_NONE); + +// external functions that I now refer to... + + +#endif // #ifndef G_FUNCTIONS + +/////////////////// eof /////////////////// + diff --git a/code/game/g_fx.cpp b/code/game/g_fx.cpp new file mode 100644 index 0000000..d204f36 --- /dev/null +++ b/code/game/g_fx.cpp @@ -0,0 +1,1236 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "g_functions.h" + +extern int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ); + +#define FX_ENT_RADIUS 32 + +extern int BMS_START; +extern int BMS_MID; +extern int BMS_END; + +//---------------------------------------------------------- + +/*QUAKED fx_runner (0 0 1) (-8 -8 -8) (8 8 8) STARTOFF ONESHOT DAMAGE +Runs the specified effect, can also be targeted at an info_notnull to orient the effect + + STARTOFF - effect starts off, toggles on/off when used + ONESHOT - effect fires only when used + DAMAGE - does radius damage around effect every "delay" milliseonds + + "fxFile" - name of the effect file to play + "target" - direction to aim the effect in, otherwise defaults to up + "target2" - uses its target2 when the fx gets triggered + "delay" - how often to call the effect, don't over-do this ( default 200 ) + "random" - random amount of time to add to delay, ( default 0, 200 = 0ms to 200ms ) + "splashRadius" - only works when damage is checked ( default 16 ) + "splashDamage" - only works when damage is checked ( default 5 ) + "soundset" - bmodel set to use, plays start sound when toggled on, loop sound while on ( doesn't play on a oneshot), and a stop sound when turned off +*/ +#define FX_RUNNER_RESERVED 0x800000 + +//---------------------------------------------------------- +void fx_runner_think( gentity_t *ent ) +{ + vec3_t temp; + + EvaluateTrajectory( &ent->s.pos, level.time, ent->currentOrigin ); + EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles ); + + // call the effect with the desired position and orientation + G_AddEvent( ent, EV_PLAY_EFFECT, ent->fxID ); + + // Assume angles, we'll do a cross product on the other end to finish up + AngleVectors( ent->currentAngles, ent->pos3, NULL, NULL ); + MakeNormalVectors( ent->pos3, ent->pos4, temp ); // there IS a reason this is done...it's so that it doesn't break every effect in the game... + + ent->nextthink = level.time + ent->delay + random() * ent->random; + + if ( ent->spawnflags & 4 ) // damage + { + G_RadiusDamage( ent->currentOrigin, ent, ent->splashDamage, ent->splashRadius, ent, MOD_UNKNOWN ); + } + + if ( ent->target2 ) + { + // let our target know that we have spawned an effect + G_UseTargets2( ent, ent, ent->target2 ); + } + + if ( !(ent->spawnflags & 2 ) && !ent->s.loopSound ) // NOT ONESHOT...this is an assy thing to do + { + if ( VALIDSTRING( ent->soundSet ) == true ) + { + ent->s.loopSound = CAS_GetBModelSound( ent->soundSet, BMS_MID ); + + if ( ent->s.loopSound < 0 ) + { + ent->s.loopSound = 0; + } + } + } + +} + +//---------------------------------------------------------- +void fx_runner_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if (self->s.isPortalEnt) + { //rww - mark it as broadcast upon first use if it's within the area of a skyportal + self->svFlags |= SVF_BROADCAST; + } + + if ( self->spawnflags & 2 ) // ONESHOT + { + // call the effect with the desired position and orientation, as a safety thing, + // make sure we aren't thinking at all. + fx_runner_think( self ); + self->nextthink = -1; + + if ( self->target2 ) + { + // let our target know that we have spawned an effect + G_UseTargets2( self, self, self->target2 ); + } + + if ( VALIDSTRING( self->soundSet ) == true ) + { + G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START )); + } + } + else + { + // ensure we are working with the right think function + self->e_ThinkFunc = thinkF_fx_runner_think; + + // toggle our state + if ( self->nextthink == -1 ) + { + // NOTE: we fire the effect immediately on use, the fx_runner_think func will set + // up the nextthink time. + fx_runner_think( self ); + + if ( VALIDSTRING( self->soundSet ) == true ) + { + G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START )); + self->s.loopSound = CAS_GetBModelSound( self->soundSet, BMS_MID ); + + if ( self->s.loopSound < 0 ) + { + self->s.loopSound = 0; + } + } + } + else + { + // turn off for now + self->nextthink = -1; + + if ( VALIDSTRING( self->soundSet ) == true ) + { + G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_END )); + self->s.loopSound = 0; + } + } + } +} + +//---------------------------------------------------------- +void fx_runner_link( gentity_t *ent ) +{ + vec3_t dir; + + if ( ent->target ) + { + // try to use the target to override the orientation + gentity_t *target = NULL; + + target = G_Find( target, FOFS(targetname), ent->target ); + + if ( !target ) + { + // Bah, no good, dump a warning, but continue on and use the UP vector + Com_Printf( "fx_runner_link: target specified but not found: %s\n", ent->target ); + Com_Printf( " -assuming UP orientation.\n" ); + } + else + { + // Our target is valid so let's override the default UP vector + VectorSubtract( target->s.origin, ent->s.origin, dir ); + VectorNormalize( dir ); + vectoangles( dir, ent->s.angles ); + } + } + + // don't really do anything with this right now other than do a check to warn the designers if the target2 is bogus + if ( ent->target2 ) + { + gentity_t *target = NULL; + + target = G_Find( target, FOFS(targetname), ent->target2 ); + + if ( !target ) + { + // Target2 is bogus, but we can still continue + Com_Printf( "fx_runner_link: target2 was specified but is not valid: %s\n", ent->target2 ); + } + } + + G_SetAngles( ent, ent->s.angles ); + + if ( ent->spawnflags & 1 || ent->spawnflags & 2 ) // STARTOFF || ONESHOT + { + // We won't even consider thinking until we are used + ent->nextthink = -1; + } + else + { + if ( VALIDSTRING( ent->soundSet ) == true ) + { + ent->s.loopSound = CAS_GetBModelSound( ent->soundSet, BMS_MID ); + + if ( ent->s.loopSound < 0 ) + { + ent->s.loopSound = 0; + } + } + + // Let's get to work right now! + ent->e_ThinkFunc = thinkF_fx_runner_think; + ent->nextthink = level.time + 200; // wait a small bit, then start working + } + + // make us useable if we can be targeted + if ( ent->targetname ) + { + ent->e_UseFunc = useF_fx_runner_use; + } +} + +//---------------------------------------------------------- +void SP_fx_runner( gentity_t *ent ) +{ + // Get our defaults + G_SpawnInt( "delay", "200", &ent->delay ); + G_SpawnFloat( "random", "0", &ent->random ); + G_SpawnInt( "splashRadius", "16", &ent->splashRadius ); + G_SpawnInt( "splashDamage", "5", &ent->splashDamage ); + + if ( !G_SpawnAngleHack( "angle", "0", ent->s.angles )) + { + // didn't have angles, so give us the default of up + VectorSet( ent->s.angles, -90, 0, 0 ); + } + + if ( !ent->fxFile ) + { + gi.Printf( S_COLOR_RED"ERROR: fx_runner %s at %s has no fxFile specified\n", ent->targetname, vtos(ent->s.origin) ); + G_FreeEntity( ent ); + return; + } + + // Try and associate an effect file, unfortunately we won't know if this worked or not + // until the CGAME trys to register it... + ent->fxID = G_EffectIndex( ent->fxFile ); + + ent->s.eType = ET_MOVER; + + // Give us a bit of time to spawn in the other entities, since we may have to target one of 'em + ent->e_ThinkFunc = thinkF_fx_runner_link; + ent->nextthink = level.time + 400; + + // Save our position and link us up! + G_SetOrigin( ent, ent->s.origin ); + + VectorSet( ent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS ); + VectorScale( ent->maxs, -1, ent->mins ); + + gi.linkentity( ent ); +} + + +/*QUAKED fx_snow (1 0 0) (-16 -16 -16) (16 16 16) LIGHT MEDIUM HEAVY MISTY_FOG +This world effect will spawn snow globally into the level. +*/ +void SP_CreateSnow( gentity_t *ent ) +{ + cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE ); + if ( r_weatherScale->value == 0.0f ) + { + return; + } + + + // Different Types Of Rain + //------------------------- + if (ent->spawnflags & 1) + { + G_FindConfigstringIndex("lightsnow", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + else if (ent->spawnflags & 2) + { + G_FindConfigstringIndex("snow", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + else if (ent->spawnflags & 4) + { + G_FindConfigstringIndex("heavysnow", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + else + { + G_FindConfigstringIndex("snow", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + + + // MISTY FOG + //=========== + if (ent->spawnflags & 8) + { + G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } +} + +/*QUAKED fx_wind (0 .5 .8) (-16 -16 -16) (16 16 16) NORMAL CONSTANT GUSTING SWIRLING x FOG LIGHT_FOG +Generates global wind forces + +NORMAL creates a random light global wind +CONSTANT forces all wind to go in a specified direction +GUSTING causes random gusts of wind +SWIRLING causes random swirls of wind + +"angles" the direction for constant wind +"speed" the speed for constant wind +*/ +void SP_CreateWind( gentity_t *ent ) +{ + cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE ); + if ( r_weatherScale->value <= 0.0f ) + { + return; + } + + char temp[256]; + + // Normal Wind + //------------- + if (ent->spawnflags & 1) + { + G_FindConfigstringIndex("wind", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + + // Constant Wind + //--------------- + if (ent->spawnflags & 2) + { + vec3_t windDir; + AngleVectors(ent->s.angles, windDir, 0, 0); + G_SpawnFloat( "speed", "500", &ent->speed ); + VectorScale(windDir, ent->speed, windDir); + + sprintf( temp, "constantwind ( %f %f %f )", windDir[0], windDir[1], windDir[2] ); + G_FindConfigstringIndex(temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + + // Gusting Wind + //-------------- + if (ent->spawnflags & 4) + { + G_FindConfigstringIndex("gustingwind", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + + // Swirling Wind + //--------------- + if (ent->spawnflags & 8) + { + G_FindConfigstringIndex("swirlingwind", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + + + // MISTY FOG + //=========== + if (ent->spawnflags & 32) + { + G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + + // MISTY FOG + //=========== + if (ent->spawnflags & 64) + { + G_FindConfigstringIndex("light_fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } +} + +/*QUAKED fx_wind_zone (0 .5 .8) ? Creates a constant wind in a local area +Generates local wind forces + +"angles" the direction for constant wind +"speed" the speed for constant wind +*/ +void SP_CreateWindZone( gentity_t *ent ) +{ + cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE ); + if ( r_weatherScale->value <= 0.0f ) + { + return; + } + + gi.SetBrushModel(ent, ent->model); + + vec3_t windDir; + AngleVectors(ent->s.angles, windDir, 0, 0); + G_SpawnFloat( "speed", "500", &ent->speed ); + VectorScale(windDir, ent->speed, windDir); + + char temp[256]; + sprintf( temp, "windzone ( %f %f %f ) ( %f %f %f ) ( %f %f %f )", + ent->mins[0], ent->mins[1], ent->mins[2], + ent->maxs[0], ent->maxs[1], ent->maxs[2], + windDir[0], windDir[1], windDir[2] + ); + G_FindConfigstringIndex(temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue); +} + + + +/*QUAKED fx_rain (1 0 0) (-16 -16 -16) (16 16 16) LIGHT MEDIUM HEAVY ACID OUTSIDE_SHAKE MISTY_FOG +This world effect will spawn rain globally into the level. + +LIGHT create light drizzle +MEDIUM create average medium rain +HEAVY create heavy downpour (with fog and lightning automatically) +ACID create acid rain + +OUTSIDE_SHAKE will cause the camera to shake slightly whenever outside +MISTY_FOG causes clouds of misty fog to float through the level +LIGHTNING causes random bursts of lightning and thunder in the level + +The following fields are for lightning: +"flashcolor" "200 200 200" (r g b) (values 0.0-255.0) +"flashdelay" "12000" maximum time delay between lightning strikes +"chanceflicker" "2" 1 in 2 chance of flickering fog +"chancesound" "3" 1 in 3 chance of playing a sound +"chanceeffect" "4" 1 in 4 chance of playing the effect +*/ +//---------------------------------------------------------- + +//---------------------------------------------------------- +extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast ); + +void fx_rain_think( gentity_t *ent ) +{ + if (player) + { + if (ent->count!=0) + { + ent->count--; + if (ent->count==0 || (ent->count%2)==0) + { + gi.WE_SetTempGlobalFogColor(ent->pos2); // Turn Off + if (ent->count==0) + { + ent->nextthink = level.time + Q_irand(1000, 12000); + } + else if (ent->count==2) + { + ent->nextthink = level.time + Q_irand(150, 450); + } + else + { + ent->nextthink = level.time + Q_irand(50, 150); + } + } + else + { + gi.WE_SetTempGlobalFogColor(ent->pos3); // Turn On + ent->nextthink = level.time + 50; + } + } + else if (gi.WE_IsOutside(player->currentOrigin)) + { + vec3_t effectPos; + vec3_t effectDir; + VectorClear(effectDir); + effectDir[0] += Q_flrand(-1.0f, 1.0f); + effectDir[1] += Q_flrand(-1.0f, 1.0f); + + bool PlayEffect = Q_irand(1,ent->aimDebounceTime)==1; + bool PlayFlicker = Q_irand(1,ent->attackDebounceTime)==1; + bool PlaySound = (PlayEffect || PlayFlicker || Q_irand(1,ent->pushDebounceTime)==1); + + // Play The Sound + //---------------- + if (PlaySound && !PlayEffect) + { + VectorMA(player->currentOrigin, 250.0f, effectDir, effectPos); + G_SoundAtSpot(effectPos, G_SoundIndex(va("sound/ambience/thunder%d", Q_irand(1,4))), qtrue); + } + + // Play The Effect + //----------------- + if (PlayEffect) + { + VectorMA(player->currentOrigin, 400.0f, effectDir, effectPos); + if (PlaySound) + { + G_Sound(player, G_SoundIndex(va("sound/ambience/thunder_close%d", Q_irand(1,2)))); + } + + // Raise It Up Into The Sky + //-------------------------- + effectPos[2] += Q_flrand(600.0f, 1000.0f); + + VectorClear(effectDir); + effectDir[2] = -1.0f; + + G_PlayEffect("env/huge_lightning", effectPos, effectDir); + ent->nextthink = level.time + Q_irand(100, 200); + } + + // Change The Fog Color + //---------------------- + if (PlayFlicker) + { + ent->count = (Q_irand(1,4) * 2); + ent->nextthink = level.time + 50; + gi.WE_SetTempGlobalFogColor(ent->pos3); + } + else + { + ent->nextthink = level.time + Q_irand(1000, ent->delay); + } + } + else + { + ent->nextthink = level.time + Q_irand(1000, ent->delay); + } + } + else + { + ent->nextthink = level.time + Q_irand(1000, ent->delay); + } +} + +void SP_CreateRain( gentity_t *ent ) +{ + // Different Types Of Rain + //------------------------- + if (ent->spawnflags & 1) + { + G_FindConfigstringIndex("lightrain", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + else if (ent->spawnflags & 2) + { + G_FindConfigstringIndex("rain", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + else if (ent->spawnflags & 4) + { + G_FindConfigstringIndex("heavyrain", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + + // Automatically Get Heavy Fog + //----------------------------- + G_FindConfigstringIndex("heavyrainfog", CS_WORLD_FX, MAX_WORLD_FX, qtrue ); + + // Automatically Get Lightning & Thunder + //--------------------------------------- + ent->spawnflags |= 64; + } + else if (ent->spawnflags & 8) + { + G_EffectIndex( "world/acid_fizz" ); + G_FindConfigstringIndex("acidrain", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + + + // OUTSIDE SHAKE + //=============== + if (ent->spawnflags & 16) + { + G_FindConfigstringIndex("outsideShake", CS_WORLD_FX, MAX_WORLD_FX, qtrue); + } + + // MISTY FOG + //=========== + if (ent->spawnflags & 32) + { + G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue ); + } + + // LIGHTNING + //=========== + if (ent->spawnflags & 64) + { + G_SoundIndex("sound/ambience/thunder1"); + G_SoundIndex("sound/ambience/thunder2"); + G_SoundIndex("sound/ambience/thunder3"); + G_SoundIndex("sound/ambience/thunder4"); + G_SoundIndex("sound/ambience/thunder_close1"); + G_SoundIndex("sound/ambience/thunder_close2"); + G_EffectIndex( "env/huge_lightning" ); + ent->e_ThinkFunc = thinkF_fx_rain_think; + ent->nextthink = level.time + Q_irand(4000, 8000); + + if (!G_SpawnVector( "flashcolor", "200 200 200", ent->pos3)) + { + VectorSet(ent->pos3, 200, 200, 200); + } + VectorClear(ent->pos2); // the "off" color + + G_SpawnInt("flashdelay", "12000", &ent->delay); + G_SpawnInt("chanceflicker", "2", &ent->attackDebounceTime); + G_SpawnInt("chancesound", "3", &ent->pushDebounceTime); + G_SpawnInt("chanceeffect", "4", &ent->aimDebounceTime); + } +} + +// Added by Aurelio Reis on 10/20/02. +/*QUAKED fx_puff (1 0 0) (-16 -16 -16) (16 16 16) +This world effect will spawn a puff system globally into the level. +Enter any valid puff command as a key and value to setup the puff +system properties. + +"count" The number of puffs/particles (default of 1000). + +"whichsystem" Which puff system to use (currently 0 and 1. Default 0). + +// Apply a default puff system. +default +Current defaults are "snowstorm", "sandstorm", "foggy", and "smokey" + +// Set the color of the particles (0-1.0). +color ( , , ) +default ( 0.5, 0.5, 0.5 ) + +// Set the alpha (transparency) value for the particles (0-1.0). +alpha +default 0.5 + +// Set which texture to use for the particles (make sure to include full path). +texture +default gfx/effects/alpha_smoke2b.tga + +// Set the size of particles (from center, like a radius) (MIN 4, MAX 2048). +size +default 100 + +// Whether the saber should flicker and spark or not (0 false, 1 true). +sabersparks +default 0 + +// Set texture filtering mode (0 = Bilinear(default), 1 = Nearest(less quality). +filtermode +default 0 + +// Set the alpha blending mode (0 = src, src-1, 1 = one, one (additive)). +blendmode +default 0 + +// How much to rotate particles per second (in degree's). +rotate ( , ) +default ( 0, 0 ) + +// Set the area around the player the puffs cover: +spread ( minX minY minZ ) ( maxX maxY maxZ ) +default: ( -600 -600 -500 ) ( 600 600 550 ) + +// Set the random range that sets the speed the puffs fall: +velocity ( minX minY minZ ) ( maxX maxY maxZ ) +default: ( -15 -15 -20 ) ( 15 15 -70 ) + +// Set an area of puff blowing: +wind ( windOriginX windOriginY windOriginZ ) ( windVelocityX windVelocityY windVelocityZ ) ( sizeX sizeY sizeZ ) + +// Set puff blowing data: +blowing duration +blowing low + default: 3 +blowing velocity ( min max ) + default: ( 30 70 ) +blowing size ( minX minY minZ ) + default: ( 1000 300 300 ) +*/ +//---------------------------------------------------------- +void SP_CreatePuffSystem( gentity_t *ent ) +{ + char temp[128]; + + // Initialize the puff system to either 1000 particles or whatever they choose. + G_SpawnInt( "count", "1000", &ent->count ); + cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE ); + + // See which puff system to use. + int iPuffSystem = 0; + int iVal = 0; + if ( G_SpawnInt( "whichsystem", "0", &iVal ) ) + { + iPuffSystem = iVal; + if ( iPuffSystem < 0 || iPuffSystem > 1 ) + { + iPuffSystem = 0; + //ri.Error( ERR_DROP, "Weather Effect: Invalid value for whichsystem key" ); + Com_Printf( "Weather Effect: Invalid value for whichsystem key\n" ); + } + } + + if ( r_weatherScale->value > 0.0f ) + { + sprintf( temp, "puff%i init %i", iPuffSystem, (int)( ent->count * r_weatherScale->value )); + + G_FindConfigstringIndex( temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue ); + + // Should return here??? It didn't originally... + } + + // See whether we should have the saber spark from the puff system. + iVal = 0; + G_SpawnInt( "sabersparks", "0", &iVal ); + if ( iVal == 1 ) + level.worldFlags |= WF_PUFFING; + else + level.worldFlags &= ~WF_PUFFING; + + // Go through all the fields and assign the values to the created puff system now. + for ( int i = 0; i < 20; i++ ) + { + char *key = NULL; + char *value = NULL; + // Fetch a field. + if ( !G_SpawnField( i, &key, &value ) ) + continue; + + // Make sure we don't get key's that are worthless. + if ( Q_stricmp( key, "origin" ) == 0 || Q_stricmp( key, "classname" ) == 0 || + Q_stricmp( key, "count" ) == 0 || Q_stricmp( key, "targetname" ) == 0 || + Q_stricmp( key, "sabersparks" ) == 0 || Q_stricmp( key, "whichsystem" ) == 0 ) + continue; + + // Send the command. + _snprintf( temp, 128, "puff%i %s %s", iPuffSystem, key, value ); + G_FindConfigstringIndex( temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue ); + } +} + +// Don't use this! Too powerful! - Aurelio +/*NOTEINUSE! fx_command (1 0 0) (-16 -16 -16) (16 16 16) +//This effect allows you to issue console commands from within the world editor. +//Use the variables c00 to c99 to issue a maximum of 100 console commands. + +//example: c00 r_showtris 1 + +*/ +//---------------------------------------------------------- +/*void SP_Command( gentity_t *ent ) +{ + char *strCommand; + + // Go through all the commands. + for ( int i = 0; i < 100; i++ ) + { + strCommand = NULL; + + // Fetch a command. + G_SpawnString( va("c%02d", i), NULL, &strCommand ); + + // If it's valid, issue it. + if ( strCommand && strCommand[0] ) + { + gi.SendConsoleCommand( strCommand ); + } + } +}*/ + +//----------------- +// Explosion Trail +//----------------- + +//---------------------------------------------------------- +void fx_explosion_trail_think( gentity_t *ent ) +{ + vec3_t origin; + trace_t tr; + + if ( ent->spawnflags & 1 ) // gravity + { + ent->s.pos.trType = TR_GRAVITY; + } + else + { + ent->s.pos.trType = TR_LINEAR; + } + + EvaluateTrajectory( &ent->s.pos, level.time, origin ); + + gi.trace( &tr, ent->currentOrigin, vec3_origin, vec3_origin, origin, + ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask, G2_RETURNONHIT, 10 ); + + if ( tr.fraction < 1.0f ) + { + // never explode or bounce on sky + if ( !( tr.surfaceFlags & SURF_NOIMPACT )) + { + if ( ent->splashDamage && ent->splashRadius ) + { + G_RadiusDamage( tr.endpos, ent, ent->splashDamage, ent->splashRadius, ent, MOD_EXPLOSIVE_SPLASH ); + } + } + + if ( ent->cameraGroup ) + { + // fxFile2....in other words, impact fx + G_PlayEffect( ent->cameraGroup, tr.endpos, tr.plane.normal ); + } + + if ( VALIDSTRING( ent->soundSet ) == true ) + { + G_AddEvent( ent, EV_BMODEL_SOUND, CAS_GetBModelSound( ent->soundSet, BMS_END )); + } + + G_FreeEntity( ent ); + return; + } + + G_RadiusDamage( origin, ent, ent->damage, ent->radius, ent, MOD_EXPLOSIVE_SPLASH ); + + // call the effect with the desired position and orientation + G_PlayEffect( ent->fxID, origin, ent->currentAngles ); + + ent->nextthink = level.time + 50; + gi.linkentity( ent ); +} + +//---------------------------------------------------------- +void fx_explosion_trail_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + gentity_t *missile = G_Spawn(); + + // We aren't a missile in the truest sense, rather we just move through the world and spawn effects + if ( missile ) + { + missile->classname = "fx_exp_trail"; + + missile->nextthink = level.time + 50; + missile->e_ThinkFunc = thinkF_fx_explosion_trail_think; + + missile->s.eType = ET_MOVER; + + missile->owner = self; + + missile->s.modelindex = self->s.modelindex2; + + missile->s.pos.trTime = level.time; + G_SetOrigin( missile, self->currentOrigin ); + if ( self->spawnflags & 1 ) // gravity + { + missile->s.pos.trType = TR_GRAVITY; + } + else + { + missile->s.pos.trType = TR_LINEAR; + } + + missile->spawnflags = self->spawnflags; + + G_SetAngles( missile, self->currentAngles ); + VectorScale( self->currentAngles, self->speed, missile->s.pos.trDelta ); + missile->s.pos.trTime = level.time; + missile->radius = self->radius; + missile->damage = self->damage; + missile->splashDamage = self->splashDamage; + missile->splashRadius = self->splashRadius; + missile->fxID = self->fxID; + missile->cameraGroup = self->cameraGroup; //fxfile2 + + missile->clipmask = MASK_SHOT; + + gi.linkentity( missile ); + + if ( VALIDSTRING( self->soundSet ) == true ) + { + G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START )); + missile->s.loopSound = CAS_GetBModelSound( self->soundSet, BMS_MID ); + missile->soundSet = G_NewString(self->soundSet);//get my own copy so i can free it when i die + + if ( missile->s.loopSound < 0 ) + { + missile->s.loopSound = 0; + } + } + } +} + +//---------------------------------------------------------- +void fx_explosion_trail_link( gentity_t *ent ) +{ + vec3_t dir; + gentity_t *target = NULL; + + // we ony activate when used + ent->e_UseFunc = useF_fx_explosion_trail_use; + + if ( ent->target ) + { + // try to use the target to override the orientation + target = G_Find( target, FOFS(targetname), ent->target ); + + if ( !target ) + { + gi.Printf( S_COLOR_RED"ERROR: fx_explosion_trail %s could not find target %s\n", ent->targetname, ent->target ); + G_FreeEntity( ent ); + return; + } + + // Our target is valid so lets use that + VectorSubtract( target->s.origin, ent->s.origin, dir ); + VectorNormalize( dir ); + } + else + { + // we are assuming that we have angles, but there are no checks to verify this + AngleVectors( ent->s.angles, dir, NULL, NULL ); + } + + // NOTE: this really isn't an angle, but rather an orientation vector + G_SetAngles( ent, dir ); +} + +/*QUAKED fx_explosion_trail (0 0 1) (-8 -8 -8) (8 8 8) GRAVITY +Creates an explosion type trail using the specified effect file, damaging things as it moves through the environment +Can also be used for something like a meteor, just add an impact effect ( fxFile2 ) and a splashDamage and splashRadius + + GRAVITY - object uses gravity instead of linear motion + + "fxFile" - name of the effect to play for the trail ( default "env/exp_trail_comp" ) + "fxFile2" - effect file to play on impact + + "model" - model to attach to the trail + + "target" - direction to aim the trail in, required unless you specify angles + "targetname" - (required) trail effect spawns only when used. + "speed" - velocity through the world, ( default 350 ) + + "radius" - damage radius around trail as it travels through the world ( default 128 ) + "damage" - radius damage ( default 128 ) + "splashDamage" - damage when thing impacts ( default 0 ) + "splashRadius" - damage radius on impact ( default 0 ) + "soundset" - soundset to use, start sound plays when explosion trail starts, loop sound plays on explosion trail, end sound plays when it impacts + +*/ +//---------------------------------------------------------- +void SP_fx_explosion_trail( gentity_t *ent ) +{ + // We have to be useable, otherwise we won't spawn in + if ( !ent->targetname ) + { + gi.Printf( S_COLOR_RED"ERROR: fx_explosion_trail at %s has no targetname specified\n", vtos( ent->s.origin )); + G_FreeEntity( ent ); + return; + } + + // Get our defaults + G_SpawnString( "fxFile", "env/exp_trail_comp", &ent->fxFile ); + G_SpawnInt( "damage", "128", &ent->damage ); + G_SpawnFloat( "radius", "128", &ent->radius ); + G_SpawnFloat( "speed", "350", &ent->speed ); + + // Try to associate an effect file, unfortunately we won't know if this worked or not until the CGAME trys to register it... + ent->fxID = G_EffectIndex( ent->fxFile ); + + if ( ent->cameraGroup ) + { + G_EffectIndex( ent->cameraGroup ); + } + + if ( ent->model ) + { + ent->s.modelindex2 = G_ModelIndex( ent->model ); + } + + // Give us a bit of time to spawn in the other entities, since we may have to target one of 'em + ent->e_ThinkFunc = thinkF_fx_explosion_trail_link; + ent->nextthink = level.time + 500; + + // Save our position and link us up! + G_SetOrigin( ent, ent->s.origin ); + + VectorSet( ent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS ); + VectorScale( ent->maxs, -1, ent->mins ); + + gi.linkentity( ent ); +} + + +// +// + +//------------------------------------------ +void fx_target_beam_set_debounce( gentity_t *self ) +{ + if ( self->wait >= FRAMETIME ) + { + self->attackDebounceTime = level.time + self->wait + Q_irand( -self->random, self->random ); + } + else if ( self->wait < 0 ) + { + self->e_UseFunc = useF_NULL; + } + else + { + self->attackDebounceTime = level.time + FRAMETIME + Q_irand( -self->random, self->random ); + } +} + +//------------------------------------------ +void fx_target_beam_fire( gentity_t *ent ) +{ + trace_t trace; + vec3_t dir, org, end; + int ignore; + qboolean open; + + if ( !ent->enemy || !ent->enemy->inuse ) + {//info_null most likely + ignore = ent->s.number; + ent->enemy = NULL; + VectorCopy( ent->s.origin2, org ); + } + else + { + ignore = ent->enemy->s.number; + VectorCopy( ent->enemy->currentOrigin, org ); + } + + VectorCopy( org, ent->s.origin2 ); + VectorSubtract( org, ent->s.origin, dir ); + VectorNormalize( dir ); + + gi.trace( &trace, ent->s.origin, NULL, NULL, org, ENTITYNUM_NONE, MASK_SHOT );//ignore + if ( ent->spawnflags & 2 ) + { + open = qtrue; + VectorCopy( org, end ); + } + else + { + open = qfalse; + VectorCopy( trace.endpos, end ); + } + + if ( trace.fraction < 1.0 ) + { + if ( trace.entityNum < ENTITYNUM_WORLD ) + { + gentity_t *victim = &g_entities[trace.entityNum]; + if ( victim && victim->takedamage ) + { + if ( ent->spawnflags & 4 ) // NO_KNOCKBACK + { + G_Damage( victim, ent, ent->activator, dir, trace.endpos, ent->damage, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); + } + else + { + G_Damage( victim, ent, ent->activator, dir, trace.endpos, ent->damage, 0, MOD_UNKNOWN ); + } + } + } + } + + G_AddEvent( ent, EV_TARGET_BEAM_DRAW, ent->fxID ); + VectorCopy( end, ent->s.origin2 ); + + if ( open ) + { + VectorScale( dir, -1, ent->pos1 ); + } + else + { + VectorCopy( trace.plane.normal, ent->pos1 ); + } + + ent->e_ThinkFunc = thinkF_fx_target_beam_think; + ent->nextthink = level.time + FRAMETIME; +} + +//------------------------------------------ +void fx_target_beam_fire_start( gentity_t *self ) +{ + fx_target_beam_set_debounce( self ); + self->e_ThinkFunc = thinkF_fx_target_beam_think; + self->nextthink = level.time + FRAMETIME; + self->painDebounceTime = level.time + self->speed + Q_irand( -500, 500 ); + fx_target_beam_fire( self ); +} + +//------------------------------------------ +void fx_target_beam_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( self->spawnflags & 8 ) // one shot + { + fx_target_beam_fire( self ); + self->e_ThinkFunc = thinkF_NULL; + } + else if ( self->e_ThinkFunc == thinkF_NULL ) + { + self->e_ThinkFunc = thinkF_fx_target_beam_think; + self->nextthink = level.time + 50; + } + else + { + self->e_ThinkFunc = thinkF_NULL; + } + + self->activator = activator; +} + +//------------------------------------------ +void fx_target_beam_think( gentity_t *ent ) +{ + if ( ent->attackDebounceTime > level.time ) + { + ent->nextthink = level.time + FRAMETIME; + return; + } + + fx_target_beam_fire_start( ent ); +} + +//------------------------------------------ +void fx_target_beam_link( gentity_t *ent ) +{ + gentity_t *target = NULL; + vec3_t dir; + float len; + + target = G_Find( target, FOFS(targetname), ent->target ); + + if ( !target ) + { + Com_Printf( "bolt_link: unable to find target %s\n", ent->target ); + G_FreeEntity( ent ); + return; + } + + ent->attackDebounceTime = level.time; + + if ( !target->classname || Q_stricmp( "info_null", target->classname ) ) + {//don't want to set enemy to something that's going to free itself... actually, this could be bad in other ways, too... ent pointer could be freed up and re-used by the time we check it next + G_SetEnemy( ent, target ); + } + VectorSubtract( target->s.origin, ent->s.origin, dir );//er, does it ever use dir? + len = VectorNormalize( dir );//er, does it use len or dir? + vectoangles( dir, ent->s.angles );//er, does it use s.angles? + + VectorCopy( target->s.origin, ent->s.origin2 ); + + if ( ent->spawnflags & 1 ) + { + // Do nothing + ent->e_ThinkFunc = thinkF_NULL; + } + else + { + if ( !(ent->spawnflags & 8 )) // one_shot, only calls when used + { + // switch think functions to avoid doing the bolt_link every time + ent->e_ThinkFunc = thinkF_fx_target_beam_think; + ent->nextthink = level.time + FRAMETIME; + } + } + + ent->e_UseFunc = useF_fx_target_beam_use; + gi.linkentity( ent ); +} + +/*QUAKED fx_target_beam (1 0.5 0.5) (-8 -8 -8) (8 8 8) STARTOFF OPEN NO_KNOCKBACK ONE_SHOT NO_IMPACT + Emits specified effect file, doing damage if required + +STARTOFF - must be used before it's on +OPEN - will draw all the way to the target, regardless of where the trace hits +NO_KNOCKBACK - beam damage does no knockback + + "fxFile" - Effect used to draw the beam, ( default "env/targ_beam" ) + "fxFile2" - Effect used for the beam impact effect, ( default "env/targ_beam_impact" ) + "targetname" - Fires only when used + "duration" - How many seconds each burst lasts, -1 will make it stay on forever + "wait" - If always on, how long to wait between blasts, in MILLISECONDS - default/min is 100 (1 frame at 10 fps), -1 means it will never fire again + "random" - random amount of seconds added to/subtracted from "wait" each firing + "damage" - How much damage to inflict PER FRAME (so probably want it kind of low), default is none + "target" - ent to point at- you MUST have this. This can be anything you want, including a moving ent - for static beams, just use info_null +*/ +//------------------------------------------ +void SP_fx_target_beam( gentity_t *ent ) +{ + G_SetOrigin( ent, ent->s.origin ); + + ent->speed *= 1000; + ent->wait *= 1000; + ent->random *= 1000; + + if ( ent->speed < FRAMETIME ) + { + ent->speed = FRAMETIME; + } + + G_SpawnInt( "damage", "0", &ent->damage ); + G_SpawnString( "fxFile", "env/targ_beam", &ent->fxFile ); + + if ( ent->spawnflags & 16 ) // NO_IMPACT FX + { + ent->delay = 0; + } + else + { + G_SpawnString( "fxFile2", "env/targ_beam_impact", &ent->cameraGroup ); + ent->delay = G_EffectIndex( ent->cameraGroup ); + } + + ent->fxID = G_EffectIndex( ent->fxFile ); + + ent->activator = ent; + ent->owner = NULL; + + ent->e_ThinkFunc = thinkF_fx_target_beam_link; + ent->nextthink = level.time + START_TIME_LINK_ENTS; + + VectorSet( ent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS ); + VectorScale( ent->maxs, -1, ent->mins ); + + gi.linkentity( ent ); +} + + +/*QUAKED fx_cloudlayer (1 0.3 0.5) (-8 -8 -8) (8 8 8) TUBE ALT + + Creates a scalable scrolling cloud layer, mostly for bespin undercity but could be used other places + + TUBE - creates cloud layer with tube opening in the middle, must an INNER radius also + ALT - uses slightly different shader, good if using two layers sort of close together + +"radius" - outer radius of cloud layer, (default 2048) +"random" - inner radius of cloud layer, (default 128) only works for TUBE type +"wait" - adds curvature as it moves out to the edge of the layer. ( default 0 ), 1 = small up, 3 = up more, -1 = small down, -3 = down more, etc. + +*/ + +void SP_fx_cloudlayer( gentity_t *ent ) +{ + // HACK: this effect is never played, rather it just caches the shaders I need cgame side + G_EffectIndex( "world/haze_cache" ); + + G_SpawnFloat( "radius", "2048", &ent->radius ); + G_SpawnFloat( "random", "128", &ent->random ); + G_SpawnFloat( "wait", "0", &ent->wait ); + + ent->s.eType = ET_CLOUD; // dumb + + G_SetOrigin( ent, ent->s.origin ); + + ent->contents = 0; + VectorSet( ent->maxs, 200, 200, 200 ); + VectorScale( ent->maxs, -1, ent->mins ); + + gi.linkentity( ent ); +} \ No newline at end of file diff --git a/code/game/g_headers.cpp b/code/game/g_headers.cpp new file mode 100644 index 0000000..418c0e1 --- /dev/null +++ b/code/game/g_headers.cpp @@ -0,0 +1,8 @@ +// Precompiled header file for the game dll + +#include "g_headers.h" +#include "NPC_Headers.h" +#include "../cgame/cg_headers.h" + + +// end diff --git a/code/game/g_headers.h b/code/game/g_headers.h new file mode 100644 index 0000000..b449ae8 --- /dev/null +++ b/code/game/g_headers.h @@ -0,0 +1,32 @@ +#pragma once +#if !defined(G_HEADERS_H_INC) +#define G_HEADERS_H_INC + +#if !defined(G_LOCAL_H_INC) + #include "../game/g_local.h" +#endif + +//#if !defined(G_WRAITH_H_INC) +// #include "../game/g_Wraith.h" +//#endif + +#if !defined(TEAMS_H_INC) + #include "../game/Teams.h" +#endif + +//#if !defined(IGINTERFACE_H_INC) +// #include "../game/IGInterface.h" +//#endif + +// More stuff that we "need" on Xbox, as we don't use PCH +#ifdef _XBOX + #include "../game/b_local.h" + #include "../game/g_functions.h" + #include "../game/g_nav.h" + #include "../game/g_navigator.h" + #include "../game/g_vehicles.h" + #include "../cgame/cg_camera.h" // Just for AI_Rancor + #include "../cgame/cg_local.h" // Evil? Maybe. Necessary? Absolutely. +#endif + +#endif // G_HEADERS_H_INC \ No newline at end of file diff --git a/code/game/g_inventory.cpp b/code/game/g_inventory.cpp new file mode 100644 index 0000000..e87eb72 --- /dev/null +++ b/code/game/g_inventory.cpp @@ -0,0 +1,137 @@ +//g_inventory.cpp + +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +#include "g_local.h" + +/* +================ +Goodie Keys +================ +*/ +qboolean INV_GoodieKeyGive( gentity_t *target ) +{ + if ( !target || !target->client ) + { + return (qfalse); + } + + target->client->ps.inventory[INV_GOODIE_KEY]++; + return (qtrue); + +} + +qboolean INV_GoodieKeyTake( gentity_t *target ) +{ + if ( !target || !target->client ) + { + return (qfalse); + } + + if (target->client->ps.inventory[INV_GOODIE_KEY]) + { + target->client->ps.inventory[INV_GOODIE_KEY]--; + return (qtrue); + } + + //had no keys + return (qfalse); +} + +int INV_GoodieKeyCheck( gentity_t *target ) +{ + if ( !target || !target->client ) + { + return (qfalse); + } + + if ( target->client->ps.inventory[INV_GOODIE_KEY] ) + {//found a key + return (INV_GOODIE_KEY); + } + + //no keys + return (qfalse); +} + +/* +================ +Security Keys +================ +*/ +qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname ) +{ + if ( target == NULL || keyname == NULL || target->client == NULL ) + { + return qfalse; + } + + for ( int i = 0; i <= 4; i++ ) + { + if ( target->client->ps.security_key_message[i][0] == NULL ) + {//fill in the first empty slot we find with this key + target->client->ps.inventory[INV_SECURITY_KEY]++; // He got the key + Q_strncpyz( target->client->ps.security_key_message[i], keyname, MAX_SECURITY_KEY_MESSSAGE, qtrue ); + return qtrue; + } + } + //couldn't find an empty slot + return qfalse; +} + +void INV_SecurityKeyTake( gentity_t *target, char *keyname ) +{ + if ( !target || !keyname || !target->client ) + { + return; + } + + for ( int i = 0; i <= 4; i++ ) + { + if ( target->client->ps.security_key_message[i] ) + { + if ( !Q_stricmp( keyname, target->client->ps.security_key_message[i] ) ) + { + target->client->ps.inventory[INV_SECURITY_KEY]--; // Take the key + target->client->ps.security_key_message[i][0] = NULL; + return; + } + } + /* + //don't do this because we could have removed one that's between 2 valid ones + else + { + break; + } + */ + } +} + +qboolean INV_SecurityKeyCheck( gentity_t *target, char *keyname ) +{ + if ( !target || !keyname || !target->client ) + { + return (qfalse); + } + + for ( int i = 0; i <= 4; i++ ) + { + if ( target->client->ps.inventory[INV_SECURITY_KEY] && target->client->ps.security_key_message[i] ) + { + if ( !Q_stricmp( keyname, target->client->ps.security_key_message[i] ) ) + { + return (qtrue); + } + } + /* + //don't do this because we could have removed one that's between 2 valid ones + else + { + break; + } + */ + } + + return (qfalse); +} diff --git a/code/game/g_itemLoad.cpp b/code/game/g_itemLoad.cpp new file mode 100644 index 0000000..d8f9d7c --- /dev/null +++ b/code/game/g_itemLoad.cpp @@ -0,0 +1,730 @@ +//g_itemLoad.cpp +//reads in ext_data\items.dat to bg_itemlist[] + +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + + + +#include "g_local.h" +#include "g_items.h" + +#define PICKUPSOUND "sound/weapons/w_pkup.wav" +#ifdef _IMMERSION +#define PICKUPFORCE "fffx/weapons/w_pkup" +#endif // _IMMERSION + +//qboolean COM_ParseInt( char **data, int *i ); +//qboolean COM_ParseString( char **data, char **s ); +//qboolean COM_ParseFloat( char **data, float *f ); + +extern gitem_t bg_itemlist[]; + +struct +{ + int itemNum; +} itemParms; + + +static void IT_ClassName (const char **holdBuf); +static void IT_Count (const char **holdBuf); +static void IT_Icon (const char **holdBuf); +static void IT_Min (const char **holdBuf); +static void IT_Max (const char **holdBuf); +static void IT_Name (const char **holdBuf); +static void IT_PickupSound (const char **holdBuf); +static void IT_Tag (const char **holdBuf); +static void IT_Type (const char **holdBuf); +static void IT_WorldModel (const char **holdBuf); +#ifdef _IMMERSION +static void IT_PickupForce( const char **holdBuf); +#endif // _IMMERSION + + +typedef struct +{ + char *parmName; + void (*func)(const char **holdBuf); +} itemParms_t; + + +#ifdef _IMMERSION +#define IT_PARM_MAX 11 +#else +#define IT_PARM_MAX 10 +#endif // _IMMERSION + +itemParms_t ItemParms[IT_PARM_MAX] = +{ + "itemname", IT_Name, + "classname", IT_ClassName, + "count", IT_Count, + "icon", IT_Icon, + "min", IT_Min, + "max", IT_Max, + "pickupsound", IT_PickupSound, + "tag", IT_Tag, + "type", IT_Type, + "worldmodel", IT_WorldModel, +#ifdef _IMMERSION + "pickupforce", IT_PickupForce, +#endif // _IMMERSION +}; + +static void IT_SetDefaults() +{ + + bg_itemlist[itemParms.itemNum].mins[0] = -16; + bg_itemlist[itemParms.itemNum].mins[1] = -16; + bg_itemlist[itemParms.itemNum].mins[2] = -2; + + bg_itemlist[itemParms.itemNum].maxs[0] = 16; + bg_itemlist[itemParms.itemNum].maxs[1] = 16; + bg_itemlist[itemParms.itemNum].maxs[2] = 16; + + + bg_itemlist[itemParms.itemNum].pickup_sound = PICKUPSOUND; //give it a default sound + bg_itemlist[itemParms.itemNum].precaches = NULL; + bg_itemlist[itemParms.itemNum].sounds = NULL; +#ifdef _IMMERSION + bg_itemlist[itemParms.itemNum].pickup_force = PICKUPFORCE; + bg_itemlist[itemParms.itemNum].forces = NULL; +#endif // _IMMERSION +} + +static void IT_Name(const char **holdBuf) +{ + int itemNum; + const char *tokenStr; + + if (COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + + if (!Q_stricmp(tokenStr,"ITM_NONE")) + itemNum = ITM_NONE; + else if (!Q_stricmp(tokenStr,"ITM_STUN_BATON_PICKUP")) + itemNum = ITM_STUN_BATON_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_SABER_PICKUP")) + itemNum = ITM_SABER_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_BRYAR_PISTOL_PICKUP")) + itemNum = ITM_BRYAR_PISTOL_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_BLASTER_PICKUP")) + itemNum = ITM_BLASTER_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_DISRUPTOR_PICKUP")) + itemNum = ITM_DISRUPTOR_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_BOWCASTER_PICKUP")) + itemNum = ITM_BOWCASTER_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_REPEATER_PICKUP")) + itemNum = ITM_REPEATER_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_DEMP2_PICKUP")) + itemNum = ITM_DEMP2_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_FLECHETTE_PICKUP")) + itemNum = ITM_FLECHETTE_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_ROCKET_LAUNCHER_PICKUP")) + itemNum = ITM_ROCKET_LAUNCHER_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_THERMAL_DET_PICKUP")) + itemNum = ITM_THERMAL_DET_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_TRIP_MINE_PICKUP")) + itemNum = ITM_TRIP_MINE_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_DET_PACK_PICKUP")) + itemNum = ITM_DET_PACK_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_BOT_LASER_PICKUP")) + itemNum = ITM_BOT_LASER_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_EMPLACED_GUN_PICKUP")) + itemNum = ITM_EMPLACED_GUN_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_TURRET_PICKUP")) + itemNum = ITM_TURRET_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_MELEE")) + itemNum = ITM_MELEE; + else if (!Q_stricmp(tokenStr,"ITM_ATST_MAIN_PICKUP")) + itemNum = ITM_ATST_MAIN_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_ATST_SIDE_PICKUP")) + itemNum = ITM_ATST_SIDE_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_TIE_FIGHTER_PICKUP")) + itemNum = ITM_TIE_FIGHTER_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_RAPID_FIRE_CONC_PICKUP")) + itemNum = ITM_RAPID_FIRE_CONC_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_JAWA_PICKUP")) + itemNum = ITM_JAWA_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_TUSKEN_RIFLE_PICKUP")) + itemNum = ITM_TUSKEN_RIFLE_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_TUSKEN_STAFF_PICKUP")) + itemNum = ITM_TUSKEN_STAFF_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_SCEPTER_PICKUP")) + itemNum = ITM_SCEPTER_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_NOGHRI_STICK_PICKUP")) + itemNum = ITM_NOGHRI_STICK_PICKUP; + //ammo + else if (!Q_stricmp(tokenStr,"ITM_AMMO_FORCE_PICKUP")) + itemNum = ITM_AMMO_FORCE_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_AMMO_BLASTER_PICKUP")) + itemNum = ITM_AMMO_BLASTER_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_AMMO_POWERCELL_PICKUP")) + itemNum = ITM_AMMO_POWERCELL_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_AMMO_METAL_BOLTS_PICKUP")) + itemNum = ITM_AMMO_METAL_BOLTS_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_AMMO_ROCKETS_PICKUP")) + itemNum = ITM_AMMO_ROCKETS_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_AMMO_EMPLACED_PICKUP")) + itemNum = ITM_AMMO_EMPLACED_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_AMMO_THERMAL_PICKUP")) + itemNum = ITM_AMMO_THERMAL_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_AMMO_TRIPMINE_PICKUP")) + itemNum = ITM_AMMO_TRIPMINE_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_AMMO_DETPACK_PICKUP")) + itemNum = ITM_AMMO_DETPACK_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_FORCE_HEAL_PICKUP")) + { + itemNum = ITM_FORCE_HEAL_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_FORCE_LEVITATION_PICKUP")) + { + itemNum = ITM_FORCE_LEVITATION_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_FORCE_SPEED_PICKUP")) + { + itemNum = ITM_FORCE_SPEED_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_FORCE_PUSH_PICKUP")) + { + itemNum = ITM_FORCE_PUSH_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_FORCE_PULL_PICKUP")) + { + itemNum = ITM_FORCE_PULL_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_FORCE_TELEPATHY_PICKUP")) + { + itemNum = ITM_FORCE_TELEPATHY_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_FORCE_GRIP_PICKUP")) + { + itemNum = ITM_FORCE_GRIP_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_FORCE_LIGHTNING_PICKUP")) + { + itemNum = ITM_FORCE_LIGHTNING_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_FORCE_SABERTHROW_PICKUP")) + { + itemNum = ITM_FORCE_SABERTHROW_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_BATTERY_PICKUP")) + { + itemNum = ITM_BATTERY_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_SEEKER_PICKUP")) + itemNum = ITM_SEEKER_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_SHIELD_PICKUP")) + itemNum = ITM_SHIELD_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_BACTA_PICKUP")) + itemNum = ITM_BACTA_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_DATAPAD_PICKUP")) + itemNum = ITM_DATAPAD_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_BINOCULARS_PICKUP")) + itemNum = ITM_BINOCULARS_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_SENTRY_GUN_PICKUP")) + itemNum = ITM_SENTRY_GUN_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_LA_GOGGLES_PICKUP")) + itemNum = ITM_LA_GOGGLES_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_BLASTER_PISTOL_PICKUP")) + itemNum = ITM_BLASTER_PISTOL_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_CONCUSSION_RIFLE_PICKUP")) + itemNum = ITM_CONCUSSION_RIFLE_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_MEDPAK_PICKUP")) + itemNum = ITM_MEDPAK_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_SHIELD_SM_PICKUP")) + itemNum = ITM_SHIELD_SM_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_SHIELD_LRG_PICKUP")) + itemNum = ITM_SHIELD_LRG_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_GOODIE_KEY_PICKUP")) + itemNum = ITM_GOODIE_KEY_PICKUP; + else if (!Q_stricmp(tokenStr,"ITM_SECURITY_KEY_PICKUP")) + itemNum = ITM_SECURITY_KEY_PICKUP; + else + { + itemNum = 0; + gi.Printf("WARNING: bad itemname in external item data '%s'\n", tokenStr); + } + + itemParms.itemNum = itemNum; +// ++bg_numItems; + + IT_SetDefaults(); +} + +static void IT_ClassName(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if (COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 32) + { + len = 32; + gi.Printf("WARNING: weaponclass too long in external ITEMS.DAT '%s'\n", tokenStr); + } + + bg_itemlist[itemParms.itemNum].classname = G_NewString(tokenStr); + +// Q_strncpyz(bg_itemlist[itemParms.itemNum].classname,tokenStr,len); + +} + +static void IT_WorldModel(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if (COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf("WARNING: world model too long in external ITEMS.DAT '%s'\n", tokenStr); + } + + bg_itemlist[itemParms.itemNum].world_model = G_NewString(tokenStr); + +// Q_strncpyz(bg_itemlist[itemParms.itemNum].world_model[0],tokenStr,len); + +} + +static void IT_Tag(const char **holdBuf) +{ + int tag; + const char *tokenStr; + + if (COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + if (!Q_stricmp(tokenStr,"WP_NONE")) + tag = WP_NONE; + else if (!Q_stricmp(tokenStr,"WP_STUN_BATON")) + tag = WP_STUN_BATON; + else if (!Q_stricmp(tokenStr,"WP_SABER")) + tag = WP_SABER; + else if (!Q_stricmp(tokenStr,"WP_BLASTER_PISTOL")) + tag = WP_BLASTER_PISTOL; + else if (!Q_stricmp(tokenStr,"WP_BRYAR_PISTOL")) + tag = WP_BRYAR_PISTOL; + else if (!Q_stricmp(tokenStr,"WP_BLASTER")) + tag = WP_BLASTER; + else if (!Q_stricmp(tokenStr,"WP_DISRUPTOR")) + tag = WP_DISRUPTOR; + else if (!Q_stricmp(tokenStr,"WP_BOWCASTER")) + tag = WP_BOWCASTER; + else if (!Q_stricmp(tokenStr,"WP_REPEATER")) + tag = WP_REPEATER; + else if (!Q_stricmp(tokenStr,"WP_DEMP2")) + tag = WP_DEMP2; + else if (!Q_stricmp(tokenStr,"WP_FLECHETTE")) + tag = WP_FLECHETTE; + else if (!Q_stricmp(tokenStr,"WP_ROCKET_LAUNCHER")) + tag = WP_ROCKET_LAUNCHER; + else if (!Q_stricmp(tokenStr,"WP_CONCUSSION")) + tag = WP_CONCUSSION; + else if (!Q_stricmp(tokenStr,"WP_THERMAL")) + tag = WP_THERMAL; + else if (!Q_stricmp(tokenStr,"WP_TRIP_MINE")) + tag = WP_TRIP_MINE; + else if (!Q_stricmp(tokenStr,"WP_DET_PACK")) + tag = WP_DET_PACK; +// else if (!Q_stricmp(tokenStr,"WP_TRICORDER")) +// tag = WP_TRICORDER; + else if (!Q_stricmp(tokenStr,"WP_BOT_LASER")) + tag = WP_BOT_LASER; + else if (!Q_stricmp(tokenStr,"WP_EMPLACED_GUN")) + tag = WP_EMPLACED_GUN; + else if (!Q_stricmp(tokenStr,"WP_MELEE")) + tag = WP_MELEE; + else if (!Q_stricmp(tokenStr,"WP_TURRET")) + tag = WP_TURRET; + else if (!Q_stricmp(tokenStr,"WP_ATST_MAIN")) + tag = WP_ATST_MAIN; + else if (!Q_stricmp(tokenStr,"WP_ATST_SIDE")) + tag = WP_ATST_SIDE; + else if (!Q_stricmp(tokenStr,"WP_TIE_FIGHTER")) + tag = WP_TIE_FIGHTER; + else if (!Q_stricmp(tokenStr,"WP_RAPID_FIRE_CONC")) + tag = WP_RAPID_FIRE_CONC; + else if (!Q_stricmp(tokenStr,"WP_BLASTER_PISTOL")) + tag = WP_BLASTER_PISTOL; + else if (!Q_stricmp(tokenStr,"WP_JAWA")) + tag = WP_JAWA; + else if (!Q_stricmp(tokenStr,"WP_TUSKEN_RIFLE")) + tag = WP_TUSKEN_RIFLE; + else if (!Q_stricmp(tokenStr,"WP_TUSKEN_STAFF")) + tag = WP_TUSKEN_STAFF; + else if (!Q_stricmp(tokenStr,"WP_SCEPTER")) + tag = WP_SCEPTER; + else if (!Q_stricmp(tokenStr,"WP_NOGHRI_STICK")) + tag = WP_NOGHRI_STICK; + else if (!Q_stricmp(tokenStr,"AMMO_FORCE")) + tag = AMMO_FORCE; + else if (!Q_stricmp(tokenStr,"AMMO_BLASTER")) + tag = AMMO_BLASTER; + else if (!Q_stricmp(tokenStr,"AMMO_POWERCELL")) + tag = AMMO_POWERCELL; + else if (!Q_stricmp(tokenStr,"AMMO_METAL_BOLTS")) + tag = AMMO_METAL_BOLTS; + else if (!Q_stricmp(tokenStr,"AMMO_ROCKETS")) + tag = AMMO_ROCKETS; + else if (!Q_stricmp(tokenStr,"AMMO_EMPLACED")) + tag = AMMO_EMPLACED; + else if (!Q_stricmp(tokenStr,"AMMO_THERMAL")) + tag = AMMO_THERMAL; + else if (!Q_stricmp(tokenStr,"AMMO_TRIPMINE")) + tag = AMMO_TRIPMINE; + else if (!Q_stricmp(tokenStr,"AMMO_DETPACK")) + tag = AMMO_DETPACK; + else if (!Q_stricmp(tokenStr,"FP_HEAL")) + { + tag = FP_HEAL; + } + else if (!Q_stricmp(tokenStr,"FP_LEVITATION")) + { + tag = FP_LEVITATION; + } + else if (!Q_stricmp(tokenStr,"FP_SPEED")) + { + tag = FP_SPEED; + } + else if (!Q_stricmp(tokenStr,"FP_PUSH")) + { + tag = FP_PUSH; + } + else if (!Q_stricmp(tokenStr,"FP_PULL")) + { + tag = FP_PULL; + } + else if (!Q_stricmp(tokenStr,"FP_TELEPATHY")) + { + tag = FP_TELEPATHY; + } + else if (!Q_stricmp(tokenStr,"FP_GRIP")) + { + tag = FP_GRIP; + } + else if (!Q_stricmp(tokenStr,"FP_LIGHTNING")) + { + tag = FP_LIGHTNING; + } + else if (!Q_stricmp(tokenStr,"FP_SABERTHROW")) + { + tag = FP_SABERTHROW; + } + else if (!Q_stricmp(tokenStr,"ITM_BATTERY_PICKUP")) + { + tag = ITM_BATTERY_PICKUP; + } + else if (!Q_stricmp(tokenStr,"INV_SEEKER")) + { + tag = INV_SEEKER; + } + else if (!Q_stricmp(tokenStr,"ITM_SHIELD_PICKUP")) + { + tag = ITM_SHIELD_PICKUP; + } + else if (!Q_stricmp(tokenStr,"INV_BACTA_CANISTER")) + { + tag = INV_BACTA_CANISTER; + } + else if (!Q_stricmp(tokenStr,"ITM_DATAPAD_PICKUP")) + { + tag = ITM_DATAPAD_PICKUP; + } + else if (!Q_stricmp(tokenStr,"INV_ELECTROBINOCULARS")) + { + tag = INV_ELECTROBINOCULARS; + } + else if (!Q_stricmp(tokenStr,"INV_SENTRY")) + { + tag = INV_SENTRY; + } + else if (!Q_stricmp(tokenStr,"INV_LIGHTAMP_GOGGLES")) + { + tag = INV_LIGHTAMP_GOGGLES; + } + else if (!Q_stricmp(tokenStr,"INV_GOODIE_KEY")) + { + tag = INV_GOODIE_KEY; + } + else if (!Q_stricmp(tokenStr,"INV_SECURITY_KEY")) + { + tag = INV_SECURITY_KEY; + } + else if (!Q_stricmp(tokenStr,"ITM_MEDPAK_PICKUP")) + { + tag = ITM_MEDPAK_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_SHIELD_SM_PICKUP")) + { + tag = ITM_SHIELD_SM_PICKUP; + } + else if (!Q_stricmp(tokenStr,"ITM_SHIELD_LRG_PICKUP")) + { + tag = ITM_SHIELD_LRG_PICKUP; + } + else + { + tag = WP_BRYAR_PISTOL; + //This error was slipping through too much, causing runaway exceptions and shutting down, so now it's a real error when not in Final +#ifndef FINAL_BUILD + G_Error("ERROR: bad tagname in external item data '%s'\n", tokenStr); +#else + gi.Printf("WARNING: bad tagname in external item data '%s'\n", tokenStr); +#endif + } + + bg_itemlist[itemParms.itemNum].giTag = tag; + +} + +static void IT_Type(const char **holdBuf) +{ + int type; + const char *tokenStr; + + if (COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + if (!Q_stricmp(tokenStr,"IT_BAD")) + type = IT_BAD; + else if (!Q_stricmp(tokenStr,"IT_WEAPON")) + type = IT_WEAPON; + else if (!Q_stricmp(tokenStr,"IT_AMMO")) + type = IT_AMMO; + else if (!Q_stricmp(tokenStr,"IT_ARMOR")) + type = IT_ARMOR; + else if (!Q_stricmp(tokenStr,"IT_HEALTH")) + type = IT_HEALTH; + else if (!Q_stricmp(tokenStr,"IT_HOLDABLE")) + type = IT_HOLDABLE; + else if (!Q_stricmp(tokenStr,"IT_BATTERY")) + type = IT_BATTERY; + else if (!Q_stricmp(tokenStr,"IT_HOLOCRON")) + type = IT_HOLOCRON; + else + { + type = IT_BAD; + gi.Printf("WARNING: bad itemname in external item data '%s'\n", tokenStr); + } + + bg_itemlist[itemParms.itemNum].giType = (itemType_t) type; + +} + +static void IT_Icon(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if (COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 32) + { + len = 32; + gi.Printf("WARNING: icon too long in external ITEMS.DAT '%s'\n", tokenStr); + } + + bg_itemlist[itemParms.itemNum].icon = G_NewString(tokenStr); +} + +static void IT_Count(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < 0) || (tokenInt > 1000 )) // FIXME :What are the right values? + { + gi.Printf("WARNING: bad Count in external item data '%d'\n", tokenInt); + return; + } + bg_itemlist[itemParms.itemNum].quantity = tokenInt; + +} + + +static void IT_Min(const char **holdBuf) +{ + int tokenInt; + int i; + + for (i=0;i<3;++i) + { + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + bg_itemlist[itemParms.itemNum].mins[i] = tokenInt; + } + +} + +static void IT_Max(const char **holdBuf) +{ + int tokenInt; + int i; + + for (i=0;i<3;++i) + { + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + bg_itemlist[itemParms.itemNum].maxs[i] = tokenInt; + } + +} + +static void IT_PickupSound(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if (COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 32) + { + len = 32; + gi.Printf("WARNING: Pickup Sound too long in external ITEMS.DAT '%s'\n", tokenStr); + } + + bg_itemlist[itemParms.itemNum].pickup_sound = G_NewString(tokenStr); +} + +#ifdef _IMMERSION +static void IT_PickupForce(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if (COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 32) + { + len = 32; + gi.Printf("WARNING: Pickup Force too long in external ITEMS.DAT '%s'\n", tokenStr); + } + + bg_itemlist[itemParms.itemNum].pickup_force = G_NewString(tokenStr); +} +#endif // _IMMERSION +static void IT_ParseWeaponParms(const char **holdBuf) +{ + static int weaponNum,ammoNum; + const char *token; + int i; + + + while (holdBuf) + { + token = COM_ParseExt( holdBuf, qtrue ); + + if (!Q_stricmp( token, "}" )) // End of data for this weapon + break; + + // Loop through possible parameters + for (i=0;iclient->ps.inventory[index]) + { + return qtrue; + } + + return qfalse; +} + +extern qboolean INV_GoodieKeyGive( gentity_t *target ); +extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname ); +int Pickup_Holdable( gentity_t *ent, gentity_t *other ) +{ + int i,original; + + other->client->ps.stats[STAT_ITEMS] |= (1<item->giTag); + + if ( ent->item->giTag == INV_SECURITY_KEY ) + {//give the key + //FIXME: temp message + gi.SendServerCommand( NULL, "cp @SP_INGAME_YOU_TOOK_SECURITY_KEY" ); + INV_SecurityKeyGive( other, ent->message ); + } + else if ( ent->item->giTag == INV_GOODIE_KEY ) + {//give the key + //FIXME: temp message + gi.SendServerCommand( NULL, "cp @SP_INGAME_YOU_TOOK_SUPPLY_KEY" ); + INV_GoodieKeyGive( other ); + } + else + {// Picking up a normal item? + other->client->ps.inventory[ent->item->giTag]++; + } + // Got a security key + + // Set the inventory select, just in case it hasn't + original = cg.inventorySelect; + for ( i = 0 ; i < INV_MAX ; i++ ) + { + if ((cg.inventorySelect < INV_ELECTROBINOCULARS) || (cg.inventorySelect >= INV_MAX)) + { + cg.inventorySelect = (INV_MAX - 1); + } + + if ( G_InventorySelectable( cg.inventorySelect,other ) ) + { + return 60; + } + cg.inventorySelect++; + } + + cg.inventorySelect = original; + + return 60; +} + + +//====================================================================== +int Add_Ammo2 (gentity_t *ent, int ammoType, int count) +{ + + if (ammoType != AMMO_FORCE) + { + ent->client->ps.ammo[ammoType] += count; + + // since the ammo is the weapon in this case, picking up ammo should actually give you the weapon + switch( ammoType ) + { + case AMMO_THERMAL: + ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_THERMAL ); + break; + case AMMO_DETPACK: + ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_DET_PACK ); + break; + case AMMO_TRIPMINE: + ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_TRIP_MINE ); + break; + } + + if ( ent->client->ps.ammo[ammoType] > ammoData[ammoType].max ) + { + ent->client->ps.ammo[ammoType] = ammoData[ammoType].max; + return qfalse; + } + } + else + { + if ( ent->client->ps.forcePower >= ammoData[ammoType].max ) + {//if have full force, just get 25 extra per crystal + ent->client->ps.forcePower += 25; + } + else + {//else if don't have full charge, give full amount, up to max + 25 + ent->client->ps.forcePower += count; + if ( ent->client->ps.forcePower >= ammoData[ammoType].max + 25 ) + {//cap at max + 25 + ent->client->ps.forcePower = ammoData[ammoType].max + 25; + } + } + + if ( ent->client->ps.forcePower >= ammoData[ammoType].max*2 ) + {//always cap at twice a full charge + ent->client->ps.forcePower = ammoData[ammoType].max*2; + return qfalse; // can't hold any more + } + } + return qtrue; +} + +//------------------------------------------------------- +void Add_Ammo (gentity_t *ent, int weapon, int count) +{ + Add_Ammo2(ent,weaponData[weapon].ammoIndex,count); +} + +//------------------------------------------------------- +int Pickup_Ammo (gentity_t *ent, gentity_t *other) +{ + int quantity; + + if ( ent->count ) { + quantity = ent->count; + } else { + quantity = ent->item->quantity; + } + + Add_Ammo2 (other, ent->item->giTag, quantity); + + return 30; +} + +//====================================================================== +void Add_Batteries( gentity_t *ent, int *count ) +{ + if ( ent->client && ent->client->ps.batteryCharge < MAX_BATTERIES && *count ) + { + if ( *count + ent->client->ps.batteryCharge > MAX_BATTERIES ) + { + // steal what we need, then leave the rest for later + *count -= ( MAX_BATTERIES - ent->client->ps.batteryCharge ); + ent->client->ps.batteryCharge = MAX_BATTERIES; + } + else + { + // just drain all of the batteries + ent->client->ps.batteryCharge += *count; + *count = 0; + } + + G_AddEvent( ent, EV_BATTERIES_CHARGED, 0 ); + } +} + +//------------------------------------------------------- +int Pickup_Battery( gentity_t *ent, gentity_t *other ) +{ + int quantity; + + if ( ent->count ) + { + quantity = ent->count; + } + else + { + quantity = ent->item->quantity; + } + + // There may be some left over in quantity if the player is close to full, but with pickup items, this amount will just be lost + Add_Batteries( other, &quantity ); + + return 30; +} + +//====================================================================== + +extern void G_SetSabersFromCVars( gentity_t *ent ); +void Pickup_Saber( gentity_t *self, qboolean hadSaber, qboolean saberSolo, char *saberType, char *saberColor ) +{ + //G_RemoveWeaponModels( ent );//??? + if ( Q_stricmp( "player", saberType ) == 0 ) + {//"player" means use cvar info + G_SetSabersFromCVars( self ); + } + else + { + saberInfo_t newSaber={0}; + if ( WP_SaberParseParms( saberType, &newSaber ) ) + {//successfully found a saber .sab entry to use + //FIXME: what about dual sabers? + int saberNum = 0; + if ( saberSolo//only supposed to use this one saber when grab this pickup + || newSaber.twoHanded //new saber is two-handed + || (hadSaber && self->client->ps.saber[0].twoHanded) )//old saber is two-handed + {//replace the old right-hand saber and remove the left hand one + WP_RemoveSaber( self, 1 ); + } + else + {//add it as a second saber + saberNum = 1; + } + WP_SetSaber( self, saberNum, saberType ); + WP_SaberInitBladeData( self ); + if ( self->client->ps.saber[saberNum].style ) + { + self->client->ps.saberStylesKnown |= (1<client->ps.saber[saberNum].style); + } + if ( saberColor != NULL ) + {//NPC_targetname = saberColor + saber_colors_t saber_color = TranslateSaberColor( saberColor ); + for ( int bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ ) + { + self->client->ps.saber[saberNum].blade[bladeNum].color = saber_color; + } + } + } + WP_SaberFreeStrings(newSaber); + } +} + +extern void CG_ChangeWeapon( int num ); +int Pickup_Weapon (gentity_t *ent, gentity_t *other) +{ + int quantity; + qboolean hadWeapon = qfalse; + + /* + if ( ent->count || (ent->activator && !ent->activator->s.number) ) + { + quantity = ent->count; + } + else + { + quantity = ent->item->quantity; + } + */ + + // dropped items are always picked up + if ( ent->flags & FL_DROPPED_ITEM ) + { + quantity = ent->count; + } + else + {//wasn't dropped + quantity = ent->item->quantity?ent->item->quantity:50; + } + + // add the weapon + if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) ) + { + hadWeapon = qtrue; + } + other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag ); + + if ( ent->item->giTag == WP_SABER && (!hadWeapon || ent->NPC_type != NULL) ) + {//didn't have a saber or it is specifying a certain kind of saber to use + //NOTE: alt_fire = saberSolo, NPC_type = saberType, NPC_targetname = saberColor + Pickup_Saber( other, hadWeapon, ent->alt_fire, ent->NPC_type, ent->NPC_targetname ); + } + + if ( other->s.number ) + {//NPC + if ( other->s.weapon == WP_NONE ) + {//NPC with no weapon picked up a weapon, change to this weapon + //FIXME: clear/set the alt-fire flag based on the picked up weapon and my class? + other->client->ps.weapon = ent->item->giTag; + other->client->ps.weaponstate = WEAPON_RAISING; + ChangeWeapon( other, ent->item->giTag ); + if ( ent->item->giTag == WP_SABER ) + { + other->client->ps.SaberActivate(); + WP_SaberAddG2SaberModels( other ); + } + else + { + G_CreateG2AttachedWeaponModel( other, weaponData[ent->item->giTag].weaponMdl, other->handRBolt, 0 ); + } + } + } + + if ( quantity ) + { + // Give ammo + Add_Ammo( other, ent->item->giTag, quantity ); + } + return 5; +} + + +//====================================================================== + +int ITM_AddHealth (gentity_t *ent, int count) +{ + + ent->health += count; + + if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]) // Past max health + { + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + + return qfalse; + } + + return qtrue; + +} + +int Pickup_Health (gentity_t *ent, gentity_t *other) { + int max; + int quantity; + + max = other->client->ps.stats[STAT_MAX_HEALTH]; + + if ( ent->count ) { + quantity = ent->count; + } else { + quantity = ent->item->quantity; + } + + other->health += quantity; + + if (other->health > max ) { + other->health = max; + } + + if ( ent->item->giTag == 100 ) { // mega health respawns slow + return 120; + } + + return 30; +} + +//====================================================================== + +int ITM_AddArmor (gentity_t *ent, int count) +{ + + ent->client->ps.stats[STAT_ARMOR] += count; + + if (ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_HEALTH]) + { + ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH]; + return qfalse; + } + + return qtrue; +} + + +int Pickup_Armor( gentity_t *ent, gentity_t *other ) { + + // make sure that the shield effect is on + other->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE; + + other->client->ps.stats[STAT_ARMOR] += ent->item->quantity; + if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] ) { + other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH]; + } + + return 30; +} + + + +//====================================================================== + +int Pickup_Holocron( gentity_t *ent, gentity_t *other ) +{ + int forcePower = ent->item->giTag; + int forceLevel = ent->count; + // check if out of range + if( forceLevel < 0 || forceLevel >= NUM_FORCE_POWER_LEVELS ) + { + gi.Printf(" Pickup_Holocron : count %d not in valid range\n", forceLevel ); + return 1; + } + + // don't pick up if already known AND your level is higher than pickup level + if ( ( other->client->ps.forcePowersKnown & ( 1 << forcePower )) ) + { + //don't pickup if item is lower than current level + if( other->client->ps.forcePowerLevel[forcePower] >= forceLevel ) + { + return 1; + } + } + + other->client->ps.forcePowerLevel[forcePower] = forceLevel; + other->client->ps.forcePowersKnown |= ( 1 << forcePower ); + + missionInfo_Updated = qtrue; // Activate flashing text + gi.cvar_set("cg_updatedDataPadForcePower1", va("%d",forcePower+1)); // The +1 is offset in the print routine. + cg_updatedDataPadForcePower1.integer = forcePower+1; + gi.cvar_set("cg_updatedDataPadForcePower2", "0"); // The +1 is offset in the print routine. + cg_updatedDataPadForcePower2.integer = 0; + gi.cvar_set("cg_updatedDataPadForcePower3", "0"); // The +1 is offset in the print routine. + cg_updatedDataPadForcePower3.integer = 0; + + return 1; +} + + +//====================================================================== + +/* +=============== +RespawnItem +=============== +*/ +void RespawnItem( gentity_t *ent ) { +} + + +qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper ) +{ + if ( (item->flags&FL_DROPPED_ITEM) + && item->activator != &g_entities[0] + && pickerupper->s.number + && pickerupper->s.weapon == WP_NONE + && pickerupper->enemy + && pickerupper->painDebounceTime < level.time + && pickerupper->NPC && pickerupper->NPC->surrenderTime < level.time //not surrendering + && !(pickerupper->NPC->scriptFlags&SCF_FORCED_MARCH) //not being forced to march + && item->item->giTag != INV_SECURITY_KEY ) + {//non-player, in combat, picking up a dropped item that does NOT belong to the player and it *not* a security key + if ( level.time - item->s.time < 3000 )//was 5000 + { + return qfalse; + } + return qtrue; + } + return qfalse; +} + +qboolean G_CanPickUpWeapons( gentity_t *other ) +{ + if ( !other || !other->client ) + { + return qfalse; + } + switch ( other->client->NPC_class ) + { + case CLASS_ATST: + case CLASS_GONK: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_MOUSE: + case CLASS_PROBE: + case CLASS_PROTOCOL: + case CLASS_R2D2: + case CLASS_R5D2: + case CLASS_SEEKER: + case CLASS_REMOTE: + case CLASS_RANCOR: + case CLASS_WAMPA: + case CLASS_JAWA: //FIXME: in some cases it's okay? + case CLASS_UGNAUGHT: //FIXME: in some cases it's okay? + case CLASS_SENTRY: + return qfalse; + break; + } + return qtrue; +} +/* +=============== +Touch_Item +=============== +*/ +extern cvar_t *g_timescale; +void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) { + int respawn = 0; + + if (!other->client) + return; + if (other->health < 1) + return; // dead people can't pickup + + if ( other->client->ps.pm_time > 0 ) + {//cant pick up when out of control + return; + } + + // NPCs can pick it up + if ((ent->spawnflags & ITMSF_ALLOWNPC) && (!other->s.number)) + { + return; + } + + // Players cannot pick it up + if ( (ent->spawnflags & ITMSF_NOPLAYER) && (other->s.number) ) + { + return; + } + + if ( ent->noDamageTeam != TEAM_FREE && other->client->playerTeam != ent->noDamageTeam ) + {//only one team can pick it up + return; + } + + if ( !G_CanPickUpWeapons( other ) ) + {//FIXME: some flag would be better + //droids can't pick up items/weapons! + return; + } + + //FIXME: need to make them run toward a dropped weapon when fleeing without one? + //FIXME: need to make them come out of flee mode when pick up their old weapon? + if ( CheckItemCanBePickedUpByNPC( ent, other ) ) + { + if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity == ent ) + {//they were running to pick me up, they did, so clear goal + other->NPC->goalEntity = NULL; + other->NPC->squadState = SQUAD_STAND_AND_SHOOT; + NPCInfo->tempBehavior = BS_DEFAULT; + TIMER_Set(other, "flee", -1); + } + else + { + return; + } + } + else if ( !(ent->spawnflags & ITMSF_ALLOWNPC) ) + {// NPCs cannot pick it up + if ( other->s.number != 0 ) + {// Not the player? + return; + } + } + + // the same pickup rules are used for client side and server side + if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) { + return; + } + + if ( other->client ) + { + if ( (other->client->ps.eFlags&EF_FORCE_GRIPPED) || (other->client->ps.eFlags&EF_FORCE_DRAINED) ) + {//can't pick up anything while being gripped + return; + } + if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) ) + {//can't pick up while in a knockdown + return; + } + } + if (!ent->item) { //not an item! + gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname); + return; + } + qboolean bHadWeapon = qfalse; + // call the item-specific pickup function + switch( ent->item->giType ) + { + case IT_WEAPON: + if ( other->NPC && other->s.weapon == WP_NONE ) + {//Make them duck and sit here for a few seconds + int pickUpTime = Q_irand( 1000, 3000 ); + TIMER_Set( other, "duck", pickUpTime ); + TIMER_Set( other, "roamTime", pickUpTime ); + TIMER_Set( other, "stick", pickUpTime ); + TIMER_Set( other, "verifyCP", pickUpTime ); + TIMER_Set( other, "attackDelay", 600 ); + respawn = 0; + } + if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) ) + { + bHadWeapon = qtrue; + } + respawn = Pickup_Weapon(ent, other); + break; + case IT_AMMO: + respawn = Pickup_Ammo(ent, other); + break; + case IT_ARMOR: + respawn = Pickup_Armor(ent, other); + break; + case IT_HEALTH: + respawn = Pickup_Health(ent, other); + break; + case IT_HOLDABLE: + respawn = Pickup_Holdable(ent, other); + break; + case IT_BATTERY: + respawn = Pickup_Battery( ent, other ); + break; + case IT_HOLOCRON: + respawn = Pickup_Holocron( ent, other ); + break; + default: + return; + } + + if ( !respawn ) + { + return; + } + + // play the normal pickup sound + if ( !other->s.number && g_timescale->value < 1.0f ) + {//SIGH... with timescale on, you lose events left and right +extern void CG_ItemPickup( int itemNum, qboolean bHadItem ); + // but we're SP so we'll cheat + cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO, cgi_S_RegisterSound( ent->item->pickup_sound ) ); + // show icon and name on status bar + CG_ItemPickup( ent->s.modelindex, bHadWeapon ); + } + else + { + if ( bHadWeapon ) + { + G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex ); + } + else + { + G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex ); + } + } + + // fire item targets + G_UseTargets (ent, other); + + // wait of -1 will not respawn +// if ( ent->wait == -1 ) + { + //why not just remove me? + G_FreeEntity( ent ); + /* + //NOTE: used to do this: (for respawning?) + ent->svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + ent->contents = 0; + ent->unlinkAfterEvent = qtrue; + */ + return; + } +} + + +//====================================================================== + +/* +================ +LaunchItem + +Spawns an item and tosses it forward +================ +*/ +gentity_t *LaunchItem( gitem_t *item, const vec3_t origin, const vec3_t velocity, char *target ) { + gentity_t *dropped; + + dropped = G_Spawn(); + + dropped->s.eType = ET_ITEM; + dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex + dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item + + dropped->classname = G_NewString(item->classname); //copy it so it can be freed safely + dropped->item = item; + + // try using the "correct" mins/maxs first + VectorSet( dropped->mins, item->mins[0], item->mins[1], item->mins[2] ); + VectorSet( dropped->maxs, item->maxs[0], item->maxs[1], item->maxs[2] ); + + if ((!dropped->mins[0] && !dropped->mins[1] && !dropped->mins[2]) && + (!dropped->maxs[0] && !dropped->maxs[1] && !dropped->maxs[2])) + { + VectorSet( dropped->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS ); + VectorScale( dropped->maxs, -1, dropped->mins ); + } + + dropped->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_TRIGGER;//not CONTENTS_BODY for dropped items, don't need to ID them + + if ( target && target[0] ) + { + dropped->target = G_NewString( target ); + } + else + { + // if not targeting something, auto-remove after 30 seconds + // only if it's NOT a security or goodie key + if (dropped->item->giTag != INV_SECURITY_KEY ) + { + dropped->e_ThinkFunc = thinkF_G_FreeEntity; + dropped->nextthink = level.time + 30000; + } + + if ( dropped->item->giType == IT_AMMO && dropped->item->giTag == AMMO_FORCE ) + { + dropped->nextthink = -1; + dropped->e_ThinkFunc = thinkF_NULL; + } + } + + dropped->e_TouchFunc = touchF_Touch_Item; + + if ( item->giType == IT_WEAPON ) + { + // give weapon items zero pitch, a random yaw, and rolled onto their sides...but would be bad to do this for a bowcaster + if ( item->giTag != WP_BOWCASTER + && item->giTag != WP_THERMAL + && item->giTag != WP_TRIP_MINE + && item->giTag != WP_DET_PACK ) + { + VectorSet( dropped->s.angles, 0, crandom() * 180, 90.0f ); + G_SetAngles( dropped, dropped->s.angles ); + } + } + + G_SetOrigin( dropped, origin ); + dropped->s.pos.trType = TR_GRAVITY; + dropped->s.pos.trTime = level.time; + VectorCopy( velocity, dropped->s.pos.trDelta ); + + dropped->s.eFlags |= EF_BOUNCE_HALF; + + dropped->flags = FL_DROPPED_ITEM; + + gi.linkentity (dropped); + + return dropped; +} + +/* +================ +Drop_Item + +Spawns an item and tosses it forward +================ +*/ +gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle, qboolean copytarget ) { + gentity_t *dropped = NULL; + vec3_t velocity; + vec3_t angles; + + VectorCopy( ent->s.apos.trBase, angles ); + angles[YAW] += angle; + angles[PITCH] = 0; // always forward + + AngleVectors( angles, velocity, NULL, NULL ); + VectorScale( velocity, 150, velocity ); + velocity[2] += 200 + crandom() * 50; + + if ( copytarget ) + { + dropped = LaunchItem( item, ent->s.pos.trBase, velocity, ent->opentarget ); + } + else + { + dropped = LaunchItem( item, ent->s.pos.trBase, velocity, NULL ); + } + + dropped->activator = ent;//so we know who we belonged to so they can pick it back up later + dropped->s.time = level.time;//mark this time so we aren't picked up instantly by the guy who dropped us + return dropped; +} + + +/* +================ +Use_Item + +Respawn the item +================ +*/ +void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + if ( (ent->svFlags&SVF_PLAYER_USABLE) && other && !other->s.number ) + {//used directly by the player, pick me up + GEntity_TouchFunc( ent, other, NULL ); + } + else + {//use me + if ( ent->spawnflags & 32 ) // invisible + { + // If it was invisible, first use makes it visible.... + ent->s.eFlags &= ~EF_NODRAW; + ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM; + + ent->spawnflags &= ~32; + return; + } + + G_ActivateBehavior( ent, BSET_USE ); + RespawnItem( ent ); + } +} + +//====================================================================== + +/* +================ +FinishSpawningItem + +Traces down to find where an item should rest, instead of letting them +free fall from their spawn points +================ +*/ +extern int delayedShutDown; +extern cvar_t *g_saber; +void FinishSpawningItem( gentity_t *ent ) { + trace_t tr; + vec3_t dest; + gitem_t *item; + int itemNum; + + itemNum=1; + for ( item = bg_itemlist + 1 ; item->classname ; item++,itemNum++) + { + if (!strcmp(item->classname,ent->classname)) + { + break; + } + } + + // Set bounding box for item + VectorSet( ent->mins, item->mins[0],item->mins[1] ,item->mins[2]); + VectorSet( ent->maxs, item->maxs[0],item->maxs[1] ,item->maxs[2]); + + if ((!ent->mins[0] && !ent->mins[1] && !ent->mins[2]) && + (!ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2])) + { + VectorSet (ent->mins, -ITEM_RADIUS, -ITEM_RADIUS, -2);//to match the comments in the items.dat file! + VectorSet (ent->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); + } + + if ((item->quantity) && (item->giType == IT_AMMO)) + { + ent->count = item->quantity; + } + + if ((item->quantity) && (item->giType == IT_BATTERY)) + { + ent->count = item->quantity; + } + + ent->s.radius = 20; + VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f ); + + if ( ent->item->giType == IT_WEAPON + && ent->item->giTag == WP_SABER + && ent->NPC_type + && ent->NPC_type[0] + && Q_stricmp( "player", ent->NPC_type ) == 0 + && g_saber->string + && g_saber->string[0] + && Q_stricmp( "none", g_saber->string ) + && Q_stricmp( "NULL", g_saber->string ) ) + {//player's saber + saberInfo_t itemSaber; + WP_SaberParseParms( g_saber->string, &itemSaber ); + //NOTE: should I keep this string around for any reason? Will I ever need it later? + //ent->??? = G_NewString( itemSaber.model ); + gi.G2API_InitGhoul2Model( ent->ghoul2, itemSaber.model, G_ModelIndex( itemSaber.model )); + WP_SaberFreeStrings(itemSaber); + } + else + { + gi.G2API_InitGhoul2Model( ent->ghoul2, ent->item->world_model, G_ModelIndex( ent->item->world_model )); + } + + // Set crystal ammo amount based on skill level +/* if ((itemNum == ITM_AMMO_CRYSTAL_BORG) || + (itemNum == ITM_AMMO_CRYSTAL_DN) || + (itemNum == ITM_AMMO_CRYSTAL_FORGE) || + (itemNum == ITM_AMMO_CRYSTAL_SCAVENGER) || + (itemNum == ITM_AMMO_CRYSTAL_STASIS)) + { + CrystalAmmoSettings(ent); + } +*/ + ent->s.eType = ET_ITEM; + ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex + ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item + + ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_BODY;//CONTENTS_TRIGGER| + ent->e_TouchFunc = touchF_Touch_Item; + // useing an item causes it to respawn + ent->e_UseFunc = useF_Use_Item; + ent->svFlags |= SVF_PLAYER_USABLE;//so player can pick it up + + // Hang in air? + ent->s.origin[2] += 1;//just to get it off the damn ground because coplanar = insolid + if ( ent->spawnflags & ITMSF_SUSPEND) + { + // suspended + G_SetOrigin( ent, ent->s.origin ); + } + else + { + // drop to floor + VectorSet( dest, ent->s.origin[0], ent->s.origin[1], MIN_WORLD_COORD ); + gi.trace( &tr, ent->s.origin, ent->mins, ent->maxs, dest, ent->s.number, MASK_SOLID|CONTENTS_PLAYERCLIP ); + if ( tr.startsolid ) + { + if ( &g_entities[tr.entityNum] != NULL ) + { + gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin), g_entities[tr.entityNum].classname ); + } + else + { + gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin) ); + } + assert( 0 && "item starting in solid"); + if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region + delayedShutDown = level.time + 100; + } + G_FreeEntity( ent ); + return; + } + + // allow to ride movers + ent->s.groundEntityNum = tr.entityNum; + + G_SetOrigin( ent, tr.endpos ); + } + +/* ? don't need this + // team slaves and targeted items aren't present at start + if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) { + ent->s.eFlags |= EF_NODRAW; + ent->contents = 0; + return; + } +*/ + if ( ent->spawnflags & ITMSF_INVISIBLE ) // invisible + { + ent->s.eFlags |= EF_NODRAW; + ent->contents = 0; + } + + if ( ent->spawnflags & ITMSF_NOTSOLID ) // not solid + { + ent->contents = 0; + } + + gi.linkentity (ent); +} + + +char itemRegistered[MAX_ITEMS+1]; + + +/* +============== +ClearRegisteredItems +============== +*/ +void ClearRegisteredItems( void ) { + memset( itemRegistered, '0', bg_numItems ); + itemRegistered[ bg_numItems ] = 0; + + //these are given in g_client, ClientSpawn(), but MUST be registered HERE, BEFORE cgame starts. + //RegisterItem( FindItemForWeapon( WP_NONE ) ); //has no item + RegisterItem( FindItemForInventory( INV_ELECTROBINOCULARS )); + //RegisterItem( FindItemForInventory( INV_BACTA_CANISTER )); + // saber or baton is cached in SP_info_player_deathmatch now. + +extern void Player_CacheFromPrevLevel(void);//g_client.cpp + Player_CacheFromPrevLevel(); //reads from transition carry-over; +} + +/* +=============== +RegisterItem + +The item will be added to the precache list +=============== +*/ +void RegisterItem( gitem_t *item ) { + if ( !item ) { + G_Error( "RegisterItem: NULL" ); + } + itemRegistered[ item - bg_itemlist ] = '1'; + gi.SetConfigstring(CS_ITEMS, itemRegistered); //Write the needed items to a config string +} + + +/* +=============== +SaveRegisteredItems + +Write the needed items to a config string +so the client will know which ones to precache +=============== +*/ +void SaveRegisteredItems( void ) { +/* char string[MAX_ITEMS+1]; + int i; + int count; + + count = 0; + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( itemRegistered[i] ) { + count++; + string[i] = '1'; + } else { + string[i] = '0'; + } + } + string[ bg_numItems ] = 0; + + gi.Printf( "%i items registered\n", count ); + gi.SetConfigstring(CS_ITEMS, string); +*/ + gi.SetConfigstring(CS_ITEMS, itemRegistered); +} + +/* +============ +item_spawn_use + + if an item is given a targetname, it will be spawned in when used +============ +*/ +void item_spawn_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +//----------------------------------------------------------------------------- +{ + self->nextthink = level.time + 50; + self->e_ThinkFunc = thinkF_FinishSpawningItem; + // I could be fancy and add a count or something like that to be able to spawn the item numerous times... + self->e_UseFunc = NULL; +} + +/* +============ +G_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 G_SpawnItem (gentity_t *ent, gitem_t *item) { + G_SpawnFloat( "random", "0", &ent->random ); + G_SpawnFloat( "wait", "0", &ent->wait ); + + RegisterItem( item ); + ent->item = item; + + // targetname indicates they want to spawn it later + if( ent->targetname ) + { + ent->e_UseFunc = useF_item_spawn_use; + } + else + { // some movers spawn on the second frame, so delay item + // spawns until the third frame so they can ride trains + ent->nextthink = level.time + START_TIME_MOVERS_SPAWNED + 50; + ent->e_ThinkFunc = thinkF_FinishSpawningItem; + } + + ent->physicsBounce = 0.50; // items are bouncy + + // Set a default infoString text color + // NOTE: if we want to do cool cross-hair colors for items, we can just modify this, but for now, don't do it + VectorSet( ent->startRGBA, 1.0f, 1.0f, 1.0f ); + + if ( ent->team && ent->team[0] ) + { + ent->noDamageTeam = (team_t)GetIDForString( TeamTable, ent->team ); + if ( ent->noDamageTeam == TEAM_FREE ) + { + G_Error("team name %s not recognized\n", ent->team); + } + } + ent->team = NULL; +} + + +/* +================ +G_BounceItem + +================ +*/ +void G_BounceItem( gentity_t *ent, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; + EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta ); + + // cut the velocity to keep from bouncing forever + VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta ); + + // check for stop + if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) { + G_SetOrigin( ent, trace->endpos ); + ent->s.groundEntityNum = trace->entityNum; + return; + } + + VectorAdd( ent->currentOrigin, trace->plane.normal, ent->currentOrigin); + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; +} + + +/* +================ +G_RunItem + +================ +*/ +void G_RunItem( gentity_t *ent ) { + vec3_t origin; + trace_t tr; + int contents; + int mask; + + // if groundentity has been set to -1, it may have been pushed off an edge + if ( ent->s.groundEntityNum == ENTITYNUM_NONE ) + { + if ( ent->s.pos.trType != TR_GRAVITY ) + { + ent->s.pos.trType = TR_GRAVITY; + ent->s.pos.trTime = level.time; + } + } + + if ( ent->s.pos.trType == TR_STATIONARY ) + { + // check think function + G_RunThink( ent ); + if ( !g_gravity->value ) + { + ent->s.pos.trType = TR_GRAVITY; + ent->s.pos.trTime = level.time; + ent->s.pos.trDelta[0] += crandom() * 40.0f; // I dunno, just do this?? + ent->s.pos.trDelta[1] += crandom() * 40.0f; + ent->s.pos.trDelta[2] += random() * 20.0f; + } + return; + } + + // get current position + EvaluateTrajectory( &ent->s.pos, level.time, origin ); + + // trace a line from the previous position to the current position + if ( ent->clipmask ) + { + mask = ent->clipmask; + } + else + { + mask = MASK_SOLID|CONTENTS_PLAYERCLIP;//shouldn't be able to get anywhere player can't + } + + int ignore = ENTITYNUM_NONE; + if ( ent->owner ) + { + ignore = ent->owner->s.number; + } + else if ( ent->activator ) + { + ignore = ent->activator->s.number; + } + gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ignore, mask ); + + VectorCopy( tr.endpos, ent->currentOrigin ); + + if ( tr.startsolid ) + { + tr.fraction = 0; + } + + gi.linkentity( ent ); // FIXME: avoid this for stationary? + + // check think function + G_RunThink( ent ); + + if ( tr.fraction == 1 ) + { + if ( g_gravity->value <= 0 ) + { + if ( ent->s.apos.trType != TR_LINEAR ) + { + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + ent->s.apos.trType = TR_LINEAR; + ent->s.apos.trDelta[1] = Q_flrand( -300, 300 ); + ent->s.apos.trDelta[0] = Q_flrand( -10, 10 ); + ent->s.apos.trDelta[2] = Q_flrand( -10, 10 ); + ent->s.apos.trTime = level.time; + } + } + //friction in zero-G + if ( !g_gravity->value ) + { + float friction = 0.975f; + /*friction -= ent->mass/1000.0f; + if ( friction < 0.1 ) + { + friction = 0.1f; + } + */ + VectorScale( ent->s.pos.trDelta, friction, ent->s.pos.trDelta ); + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; + } + return; + } + + // if it is in a nodrop volume, remove it + contents = gi.pointcontents( ent->currentOrigin, -1 ); + if ( contents & CONTENTS_NODROP ) + { + G_FreeEntity( ent ); + return; + } + + if ( !tr.startsolid ) + { + G_BounceItem( ent, &tr ); + } +} + +/* +================ +ItemUse_Bacta + +================ +*/ +void ItemUse_Bacta(gentity_t *ent) +{ + if (!ent || !ent->client) + { + return; + } + + if (ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH] || !ent->client->ps.inventory[INV_BACTA_CANISTER] ) + { + return; + } + + ent->health += MAX_BACTA_HEAL_AMOUNT; + + if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]) + { + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + + ent->client->ps.inventory[INV_BACTA_CANISTER]--; + + G_SoundOnEnt( ent, CHAN_VOICE, va( "sound/weapons/force/heal%d_%c.mp3", Q_irand( 1, 4 ), g_sex->string[0] ) ); +} diff --git a/code/game/g_items.h b/code/game/g_items.h new file mode 100644 index 0000000..e57b774 --- /dev/null +++ b/code/game/g_items.h @@ -0,0 +1,94 @@ +//g_items.h + +#ifndef __ITEMS_H__ +#define __ITEMS_H__ + +// Items enums +typedef enum +{ +ITM_NONE, + +ITM_SABER_PICKUP, +ITM_BLASTER_PISTOL_PICKUP, +ITM_BLASTER_PICKUP, +ITM_DISRUPTOR_PICKUP, +ITM_BOWCASTER_PICKUP, +ITM_REPEATER_PICKUP, +ITM_DEMP2_PICKUP, +ITM_FLECHETTE_PICKUP, +ITM_CONCUSSION_RIFLE_PICKUP, +ITM_ROCKET_LAUNCHER_PICKUP, +ITM_THERMAL_DET_PICKUP, +ITM_TRIP_MINE_PICKUP, +ITM_DET_PACK_PICKUP, +ITM_STUN_BATON_PICKUP, +ITM_MELEE, + +ITM_BRYAR_PISTOL_PICKUP, +ITM_EMPLACED_GUN_PICKUP, +ITM_BOT_LASER_PICKUP, +ITM_TURRET_PICKUP, +ITM_ATST_MAIN_PICKUP, +ITM_ATST_SIDE_PICKUP, +ITM_TIE_FIGHTER_PICKUP, +ITM_RAPID_FIRE_CONC_PICKUP, +ITM_JAWA_PICKUP, +ITM_TUSKEN_RIFLE_PICKUP, +ITM_TUSKEN_STAFF_PICKUP, +ITM_SCEPTER_PICKUP, +ITM_NOGHRI_STICK_PICKUP, + +ITM_AMMO_FORCE_PICKUP, +ITM_AMMO_BLASTER_PICKUP, +ITM_AMMO_POWERCELL_PICKUP, +ITM_AMMO_METAL_BOLTS_PICKUP, +ITM_AMMO_ROCKETS_PICKUP, +ITM_AMMO_EMPLACED_PICKUP, +ITM_AMMO_THERMAL_PICKUP, +ITM_AMMO_TRIPMINE_PICKUP, +ITM_AMMO_DETPACK_PICKUP, + +ITM_FORCE_HEAL_PICKUP, +ITM_FORCE_LEVITATION_PICKUP, +ITM_FORCE_SPEED_PICKUP, +ITM_FORCE_PUSH_PICKUP, +ITM_FORCE_PULL_PICKUP, +ITM_FORCE_TELEPATHY_PICKUP, +ITM_FORCE_GRIP_PICKUP, +ITM_FORCE_LIGHTNING_PICKUP, +ITM_FORCE_SABERTHROW_PICKUP, + +ITM_BATTERY_PICKUP, +ITM_SEEKER_PICKUP, +ITM_SHIELD_PICKUP, +ITM_BACTA_PICKUP, +ITM_DATAPAD_PICKUP, +ITM_BINOCULARS_PICKUP, +ITM_SENTRY_GUN_PICKUP, +ITM_LA_GOGGLES_PICKUP, + +ITM_MEDPAK_PICKUP, +ITM_SHIELD_SM_PICKUP, +ITM_SHIELD_LRG_PICKUP, +ITM_GOODIE_KEY_PICKUP, +ITM_SECURITY_KEY_PICKUP, + +ITM_NUM_ITEMS +}; + +// Inventory item enums +typedef enum //# item_e +{ + INV_ELECTROBINOCULARS, + INV_BACTA_CANISTER, + INV_SEEKER, + INV_LIGHTAMP_GOGGLES, + INV_SENTRY, + //# #eol + INV_GOODIE_KEY, // don't want to include keys in the icarus list + INV_SECURITY_KEY, + + INV_MAX // Be sure to update MAX_INVENTORY +}; + +#endif diff --git a/code/game/g_local.h b/code/game/g_local.h new file mode 100644 index 0000000..ad46695 --- /dev/null +++ b/code/game/g_local.h @@ -0,0 +1,627 @@ +#ifndef __G_LOCAL_H__ +#define __G_LOCAL_H__ +// g_local.h -- local definitions for game module + +// define GAME_INCLUDE so that g_public.h does not define the +// short, server-visible gclient_t and gentity_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "../ui/gameinfo.h" +#include "g_shared.h" +#include "anims.h" +#include "dmstates.h" + +//================================================================== + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "base" + +#define BODY_QUEUE_SIZE 8 + +#define Q3_INFINITE 16777216 + +#define FRAMETIME 100 // msec +#define EVENT_VALID_MSEC 300 +#define CARNAGE_REWARD_TIME 3000 + +#define INTERMISSION_DELAY_TIME 1000 + +#define START_TIME_LINK_ENTS FRAMETIME*1 // time-delay after map start at which all ents have been spawned, so can link them +#define START_TIME_FIND_LINKS FRAMETIME*2 // time-delay after map start at which you can find linked entities +#define START_TIME_MOVERS_SPAWNED FRAMETIME*2 // time-delay after map start at which all movers should be spawned +#define START_TIME_REMOVE_ENTS FRAMETIME*3 // time-delay after map start to remove temporary ents +#define START_TIME_NAV_CALC FRAMETIME*4 // time-delay after map start to connect waypoints and calc routes +#define START_TIME_FIND_WAYPOINT FRAMETIME*5 // time-delay after map start after which it's okay to try to find your best waypoint + +// gentity->flags +#define FL_SHIELDED 0x00000001 // protected from all damage except lightsabers +#define FL_DMG_BY_HEAVY_WEAP_ONLY 0x00000002 // protected from all damage except heavy weap class missiles +#define FL_DMG_BY_SABER_ONLY 0x00000004 //protected from all damage except saber damage +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_DROPPED_ITEM 0x00001000 +#define FL_DONT_SHOOT 0x00002000 // Can target him, but not shoot him +#define FL_UNDYING 0x00004000 // Takes damage down to 1 point, but does not die +//#define FL_OVERCHARGED 0x00008000 // weapon shot is an overcharged version....probably a lame place to be putting this flag... +#define FL_LOCK_PLAYER_WEAPONS 0x00010000 // player can't switch weapons... ask james if there's a better spot for this +#define FL_DISINTEGRATED 0x00020000 // marks that the corpse has already been disintegrated +#define FL_FORCE_PULLABLE_ONLY 0x00040000 // cannot be force pushed +#define FL_NO_IMPACT_DMG 0x00080000 // Will not take impact damage +#define FL_OVERCHARGED_HEALTH 0x00100000 // Reduce health back to max +#define FL_NO_ANGLES 0x00200000 // No bone angle overrides, no pitch or roll in full angles +#define FL_RED_CROSSHAIR 0x00400000 // Crosshair red on me + + +//Pointer safety utilities +#define VALID( a ) ( a != NULL ) +#define VALIDATE( a ) ( assert( a ) ) + +#define VALIDATEV( a ) if ( a == NULL ) { assert(0); return; } +#define VALIDATEB( a ) if ( a == NULL ) { assert(0); return qfalse; } +#define VALIDATEP( a ) if ( a == NULL ) { assert(0); return NULL; } + +#define VALIDSTRING( a ) ( ( a != NULL ) && ( a[0] != NULL ) ) + +//animations +typedef struct +{ + char filename[MAX_QPATH]; + animation_t animations[MAX_ANIMATIONS]; + animevent_t torsoAnimEvents[MAX_ANIM_EVENTS]; + animevent_t legsAnimEvents[MAX_ANIM_EVENTS]; + unsigned char torsoAnimEventCount; + unsigned char legsAnimEventCount; +} animFileSet_t; + +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +//Interest points + +#define MAX_INTEREST_POINTS 64 + +typedef struct +{ + vec3_t origin; + char *target; +} interestPoint_t; + +//Combat points + +#define MAX_COMBAT_POINTS 512 + +typedef struct +{ + vec3_t origin; + int flags; +// char *NPC_targetname; +// team_t team; + qboolean occupied; + int waypoint; + int dangerTime; +} combatPoint_t; + +// Alert events + +#define MAX_ALERT_EVENTS 32 + +enum alertEventType_e +{ + AET_SIGHT, + AET_SOUND, +}; + +enum alertEventLevel_e +{ + AEL_MINOR, //Enemy responds to the sound, but only by looking + AEL_SUSPICIOUS, //Enemy looks at the sound, and will also investigate it + AEL_DISCOVERED, //Enemy knows the player is around, and will actively hunt + AEL_DANGER, //Enemy should try to find cover + AEL_DANGER_GREAT, //Enemy should run like hell! +}; + +// !!!!!!!!! LOADSAVE-affecting struct !!!!!!!!!! +typedef struct alertEvent_s +{ + vec3_t position; //Where the event is located + float radius; //Consideration radius + alertEventLevel_e level; //Priority level of the event + alertEventType_e type; //Event type (sound,sight) + gentity_t *owner; //Who made the sound + float light; //ambient light level at point + float addLight; //additional light- makes it more noticable, even in darkness + int ID; //unique... if get a ridiculous number, this will repeat, but should not be a problem as it's just comparing it to your lastAlertID + int timestamp; //when it was created + qboolean onGround; //alert is on the ground (only used for sounds) +} alertEvent_t; + +// +// this structure is cleared as each map is entered +// + +#define MAX_SPAWN_VARS 64 +#define MAX_SPAWN_VARS_CHARS 2048 + +typedef struct +{ + char targetname[MAX_QPATH]; + char target[MAX_QPATH]; + char target2[MAX_QPATH]; + char target3[MAX_QPATH]; + char target4[MAX_QPATH]; + int nodeID; +} waypointData_t; + +#define WF_RAINING 0x00000001 // raining +#define WF_SNOWING 0x00000002 // snowing +#define WF_PUFFING 0x00000004 // puffing something + +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct +{ + gclient_t *clients; // [maxclients] + + // store latched cvars here that we want to get at often + int maxclients; + + int framenum; + int time; // in msec + int previousTime; // so movers can back up when blocked + + int globalTime; // global time at level initialization + + char mapname[MAX_QPATH]; // the server name (base1, etc) + + qboolean locationLinked; // target_locations get linked + gentity_t *locationHead; // head of the location list + + alertEvent_t alertEvents[ MAX_ALERT_EVENTS ]; + int numAlertEvents; + int curAlertID; + + AIGroupInfo_t groups[MAX_FRAME_GROUPS]; + + animFileSet_t knownAnimFileSets[MAX_ANIM_FILES]; + int numKnownAnimFileSets; + + int worldFlags; + + int dmState; //actually, we do want save/load the dynamic music state +// ===================================== +// +// NOTE!!!!!! The only things beyond this point in the structure should be the ones you do NOT wish to be +// affected by loading saved-games. Since loading a game first starts the map and then loads +// over things like entities etc then these fields are usually the ones setup by the map loader. +// If they ever get modified in-game let me know and I'll include them in the save. -Ste +// +#define LEVEL_LOCALS_T_SAVESTOP logFile // name of whichever field is next below this in the source + + fileHandle_t logFile; + + //Interest points- squadmates automatically look at these if standing around and close to them + interestPoint_t interestPoints[MAX_INTEREST_POINTS]; + int numInterestPoints; + + //Combat points- NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point + combatPoint_t combatPoints[MAX_COMBAT_POINTS]; + int numCombatPoints; + char spawntarget[MAX_QPATH]; // the targetname of the spawnpoint you want the player to start at + + int dmDebounceTime; + int dmBeatTime; + + int mNumBSPInstances; + int mBSPInstanceDepth; + vec3_t mOriginAdjust; + float mRotationAdjust; + char *mTargetAdjust; + qboolean hasBspInstances; +} level_locals_t; + +extern level_locals_t level; +extern game_export_t globals; + +extern cvar_t *g_gravity; +extern cvar_t *g_speed; +extern cvar_t *g_cheats; +extern cvar_t *g_developer; +extern cvar_t *g_knockback; +extern cvar_t *g_inactivity; +extern cvar_t *g_debugMove; +extern cvar_t *g_subtitles; +extern cvar_t *g_removeDoors; + +extern cvar_t *g_ICARUSDebug; + +extern cvar_t *g_npcdebug; + +extern gentity_t *player; +// +// g_spawn.c +// +qboolean G_SpawnField( unsigned int uiField, char **ppKey, char **ppValue ); +qboolean G_SpawnString( const char *key, const char *defaultString, char **out ); +// spawn string returns a temporary reference, you must CopyString() if you want to keep it +qboolean G_SpawnFloat( const char *key, const char *defaultString, float *out ); +qboolean G_SpawnInt( const char *key, const char *defaultString, int *out ); +qboolean G_SpawnVector( const char *key, const char *defaultString, float *out ); +qboolean G_SpawnVector4( const char *key, const char *defaultString, float *out ); +qboolean G_SpawnAngleHack( const char *key, const char *defaultString, float *out ); +void G_SpawnEntitiesFromString( const char *entities ); + +// +// g_cmds.c +// +void Cmd_Score_f (gentity_t *ent); + +// +// g_items.c +// +void G_RunItem( gentity_t *ent ); +void RespawnItem( gentity_t *ent ); + +void UseHoldableItem( gentity_t *ent ); +void PrecacheItem (gitem_t *it); +gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle, qboolean copytarget ); +void SetRespawn (gentity_t *ent, float delay); +void G_SpawnItem (gentity_t *ent, gitem_t *item); +void FinishSpawningItem( gentity_t *ent ); +void Think_Weapon (gentity_t *ent); +int ArmorIndex (gentity_t *ent); +void Add_Ammo (gentity_t *ent, int weapon, int count); +void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace); + +void ClearRegisteredItems( void ); +void RegisterItem( gitem_t *item ); +void SaveRegisteredItems( void ); + +// +// g_utils.c +// +int G_ModelIndex( const char *name ); +int G_SoundIndex( const char *name ); +/* +Ghoul2 Insert Start +*/ +int G_SkinIndex( const char *name ); +void G_SetBoltSurfaceRemoval( const int entNum, const int modelIndex, const int boltIndex, const int surfaceIndex = -1, float duration = 5000 ); +/* +Ghoul2 Insert End +*/ + +int G_EffectIndex( const char *name ); +void G_PlayEffect( const char *name, const vec3_t origin ); +void G_PlayEffect( const char *name, int clientNum ); +void G_PlayEffect( const char *name, const vec3_t origin, const vec3_t fwd ); +void G_PlayEffect( const char *name, const vec3_t origin, const vec3_t axis[3] ); +void G_PlayEffect( int fxID, const vec3_t origin, const vec3_t fwd ); +void G_PlayEffect( int fxID, const vec3_t origin, const vec3_t axis[3] ); +void G_PlayEffect( int fxID, const int modelIndex, const int boltIndex, const int entNum, const vec3_t origin, int iLoopTime = qfalse, qboolean isRelative = qfalse );//iLoopTime 0 = not looping, 1 for infinite, else duration +void G_PlayEffect( int fxID, int entNum, const vec3_t fwd ); +#ifdef _IMMERSION +void G_PlayEffect( const char *name, int clientNum, const vec3_t origin, const vec3_t fwd ); +void G_PlayEffect( int fxID, int clientNum, const vec3_t origin, const vec3_t fwd ); +#endif // _IMMERSION +void G_StopEffect( int fxID, const int modelIndex, const int boltIndex, const int entNum ); +void G_StopEffect(const char *name, const int modelIndex, const int boltIndex, const int entNum ); + +int G_BSPIndex( char *name ); + +void G_KillBox (gentity_t *ent); +gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match); +int G_RadiusList ( vec3_t origin, float radius, gentity_t *ignore, qboolean takeDamage, gentity_t *ent_list[MAX_GENTITIES]); +gentity_t *G_PickTarget (char *targetname); +void G_UseTargets (gentity_t *ent, gentity_t *activator); +void G_UseTargets2 (gentity_t *ent, gentity_t *activator, const char *string); +void G_SetMovedir ( vec3_t angles, vec3_t movedir); + +void G_InitGentity( gentity_t *e, qboolean bFreeG2 ); +gentity_t *G_Spawn ( int itr = 1); +gentity_t *G_TempEntity( const vec3_t origin, int event ); +void G_Sound( gentity_t *ent, int soundIndex ); +void G_FreeEntity( gentity_t *e ); + +#ifdef _IMMERSION +int G_ForceIndex( const char *name, int channel ); +void G_Force( gentity_t *ent, int forceIndex ); +void G_ForceArea( gentity_t *ent, int forceIndex ); +void G_ForceBroadcast( gentity_t *ent, int forceIndex ); +void G_ForceStop( gentity_t* ent, int forceIndex ); +#endif // _IMMERSION +void G_TouchTriggers (gentity_t *ent); +void G_TouchTeamClients (gentity_t *ent); +void G_TouchSolids (gentity_t *ent); + +char *vtos( const vec3_t v ); + +float vectoyaw( const vec3_t vec ); + +void G_AddEvent( gentity_t *ent, int event, int eventParm ); +void G_SetOrigin( gentity_t *ent, const vec3_t origin ); +void G_SetAngles( gentity_t *ent, const vec3_t angles ); + +void G_DebugLine(vec3_t A, vec3_t B, int duration, int color, qboolean deleteornot); + +// +// g_combat.c +// +qboolean CanDamage (gentity_t *targ, const vec3_t origin); +void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, const vec3_t dir, const vec3_t point, int damage, int dflags, int mod, int hitLoc=HL_NONE ); +void G_RadiusDamage (const vec3_t origin, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int mod); +gentity_t *TossClientItems( gentity_t *self ); +void ExplodeDeath_Wait( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc ); +void ExplodeDeath( gentity_t *self ); +void GoExplodeDeath( gentity_t *self, gentity_t *other, gentity_t *activator); +void G_ApplyKnockback( gentity_t *targ, const vec3_t newDir, float knockback ); +void G_Throw( gentity_t *targ, const vec3_t newDir, float push ); + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_NO_HIT_LOC 0x00000010 // do not modify damage by hit loc +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect +#define DAMAGE_EXTRA_KNOCKBACK 0x00000040 // add extra knockback to this damage +#define DAMAGE_DEATH_KNOCKBACK 0x00000080 // only does knockback on death of target +#define DAMAGE_IGNORE_TEAM 0x00000100 // damage is always done, regardless of teams +#define DAMAGE_NO_DAMAGE 0x00000200 // do no actual damage but react as if damage was taken +#define DAMAGE_DISMEMBER 0x00000400 // do dismemberment +#define DAMAGE_NO_KILL 0x00000800 // do damage, but don't kill them +#define DAMAGE_HEAVY_WEAP_CLASS 0x00001000 // doing heavy weapon type damage, certain objects may only take damage by missiles containing this flag +#define DAMAGE_CUSTOM_HUD 0x00002000 // really dumb, but.... +#define DAMAGE_IMPACT_DIE 0x00004000 // if a vehicle hits a wall it should instantly die +#define DAMAGE_DIE_ON_IMPACT 0x00008000 // ignores force-power based protection + +// +// g_missile.c +// +void G_RunMissile( gentity_t *ent ); + +gentity_t *fire_blaster (gentity_t *self, vec3_t start, vec3_t aimdir); +gentity_t *fire_plasma (gentity_t *self, vec3_t start, vec3_t aimdir); +gentity_t *fire_grenade (gentity_t *self, vec3_t start, vec3_t aimdir); +gentity_t *fire_rocket (gentity_t *self, vec3_t start, vec3_t dir); + + +// +// g_mover.c +// +#define MOVER_START_ON 1 +#define MOVER_FORCE_ACTIVATE 2 +#define MOVER_CRUSHER 4 +#define MOVER_TOGGLE 8 +#define MOVER_LOCKED 16 +#define MOVER_GOODIE 32 +#define MOVER_PLAYER_USE 64 +#define MOVER_INACTIVE 128 +void G_RunMover( gentity_t *ent ); + + +// +// g_misc.c +// +void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ); + + +// +// g_weapon.c +// +//void CalcMuzzlePoint ( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ); +//void SnapVectorTowards( vec3_t v, vec3_t to ); +//qboolean CheckGauntletAttack( gentity_t *ent ); +void WP_LoadWeaponParms (void); + +void IT_LoadItemParms( void ); + +// +// g_client.c +// +team_t PickTeam( int ignoreClientNum ); +void SetClientViewAngle( gentity_t *ent, vec3_t angle ); +gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, team_t team, vec3_t origin, vec3_t angles ); +void respawn (gentity_t *ent); +void InitClientPersistant (gclient_t *client); +void InitClientResp (gclient_t *client); +qboolean ClientSpawn( gentity_t *ent, SavedGameJustLoaded_e eSavedGameJustLoaded ); +void player_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc); +void AddScore( gentity_t *ent, int score ); +qboolean SpotWouldTelefrag( gentity_t *spot, team_t checkteam ); +void G_RemoveWeaponModels( gentity_t *ent ); + +// +// g_svcmds.c +// +qboolean ConsoleCommand( void ); + +// +// g_weapon.c +// +void FireWeapon( gentity_t *ent, qboolean alt_fire ); + +// +// p_hud.c +// +void MoveClientToIntermission (gentity_t *client); +void G_SetStats (gentity_t *ent); +void DeathmatchScoreboardMessage (gentity_t *client); + +// +// g_cmds.c +// +static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message ); + +// +// g_pweapon.c +// + + +// +// g_main.c +// +void G_RunThink (gentity_t *ent); +void QDECL G_Error( const char *fmt, ... ); +void SetInUse(gentity_t *ent); +void ClearInUse(gentity_t *ent); +qboolean PInUse(unsigned int entNum); +qboolean PInUse2(gentity_t *ent); +void WriteInUseBits(void); +void ReadInUseBits(void); + +// +// g_nav.cpp +// +void Svcmd_Nav_f (void); + +// +// g_squad.cpp +// +void Svcmd_Comm_f (void); +void Svcmd_Hail_f (void); +void Svcmd_Form_f (void); + + +// +// g_utils.cpp +// +void Svcmd_Use_f (void); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +extern void G_SoundIndexOnEnt( gentity_t *ent, soundChannel_t channel, int index ); + +// +// g_weapons.cpp +// + +// +// g_client.c +// +char *ClientConnect( int clientNum, qboolean firstTime, SavedGameJustLoaded_e eSavedGameJustLoaded ); +void ClientUserinfoChanged( int clientNum ); +void ClientDisconnect( int clientNum ); +void ClientBegin( int clientNum, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded ); +void ClientCommand( int clientNum ); + +// +// g_active.c +// +void ClientThink( int clientNum, usercmd_t *cmd ); +void ClientEndFrame (gentity_t *ent); + +// +// g_inventory.cpp +// +extern qboolean INV_GoodieKeyGive( gentity_t *target ); +extern qboolean INV_GoodieKeyTake( gentity_t *target ); +extern int INV_GoodieKeyCheck( gentity_t *target ); +extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname ); +extern void INV_SecurityKeyTake( gentity_t *target, char *keyname ); +extern qboolean INV_SecurityKeyCheck( gentity_t *target, char *keyname ); + +// +// g_team.c +// +qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 ); + + +// +// g_mem.c +// +void *G_Alloc( int size ); +void G_InitMemory( void ); +void Svcmd_GameMem_f( void ); + +// +// g_session.c +// +void G_ReadSessionData( gclient_t *client ); +void G_InitSessionData( gclient_t *client, char *userinfo ); + +void G_InitWorldSession( void ); +void G_WriteSessionData( void ); + + +// +// NPC_senses.cpp +// +extern void AddSightEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, float addLight=0.0f ); +extern void AddSoundEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, qboolean needLOS = qfalse, qboolean onGround = qfalse ); +extern qboolean G_CheckForDanger( gentity_t *self, int alertEvent ); +extern int G_CheckAlertEvents( gentity_t *self, qboolean checkSight, qboolean checkSound, float maxSeeDist, float maxHearDist, int ignoreAlert = -1, qboolean mustHaveOwner = qfalse, int minAlertLevel = AEL_MINOR, qboolean onGroundOnly = qfalse ); +extern qboolean G_CheckForDanger( gentity_t *self, int alertEvent ); +extern qboolean G_ClearLOS( gentity_t *self, const vec3_t start, const vec3_t end ); +extern qboolean G_ClearLOS( gentity_t *self, gentity_t *ent, const vec3_t end ); +extern qboolean G_ClearLOS( gentity_t *self, const vec3_t start, gentity_t *ent ); +extern qboolean G_ClearLOS( gentity_t *self, gentity_t *ent ); +extern qboolean G_ClearLOS( gentity_t *self, const vec3_t end ); + +//============================================================================ + +//Tags + +// Reference tags + +#define MAX_REFTAGS 128 // Probably more than we'll ever need +#define MAX_REFNAME 32 + +#define RTF_NONE 0 +#define RTF_NAVGOAL 0x00000001 + +typedef struct reference_tag_s +{ + char name[MAX_REFNAME]; + vec3_t origin; + vec3_t angles; + int flags; //Just in case + int radius; //For nav goals +} reference_tag_t; + +extern void TAG_Init( void ); +extern reference_tag_t *TAG_Add( const char *name, const char *owner, vec3_t origin, vec3_t angles, int radius, int flags ); + +extern int TAG_GetOrigin( const char *owner, const char *name, vec3_t origin ); +extern int TAG_GetAngles( const char *owner, const char *name, vec3_t angles ); +extern int TAG_GetRadius( const char *owner, const char *name ); +extern int TAG_GetFlags( const char *owner, const char *name ); + +void TAG_ShowTags( int flags ); + +// Reference tags END + +extern char *G_NewString( const char *string ); + +// some stuff for savegames... +// +void WriteLevel(qboolean qbAutosave); +void ReadLevel(qboolean qbAutosave, qboolean qbLoadTransition); +qboolean GameAllowedToSaveHere(void); + +extern qboolean G_ActivateBehavior( gentity_t *ent, int bset ); + +//Timing information +void TIMER_Clear( void ); +void TIMER_Clear( int idx ); +void TIMER_Save( void ); +void TIMER_Load( void ); +void TIMER_Set( gentity_t *ent, const char *identifier, int duration ); +int TIMER_Get( gentity_t *ent, const char *identifier ); +qboolean TIMER_Done( gentity_t *ent, const char *identifier ); +qboolean TIMER_Start( gentity_t *self, const char *identifier, int duration ); +qboolean TIMER_Done2( gentity_t *ent, const char *identifier, qboolean remove = qfalse ); +qboolean TIMER_Exists( gentity_t *ent, const char *identifier ); +void TIMER_Remove( gentity_t *ent, const char *identifier ); + +float NPC_GetHFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float hFOV ); +float NPC_GetVFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float vFOV ); + +#ifdef _XBOX +// data used for NPC water detection +#define MAX_NPC_WATER_UPDATE 64 // maximum npcs that can be waiting for a water update +#define MAX_NPC_WATER_UPDATES_PER_FRAME 2 // updates per frame + +extern short npcsToUpdate[MAX_NPC_WATER_UPDATE]; // queue of npcs +extern short npcsToUpdateTop; // top of the queue +extern short npcsToUpdateCount; // number of npcs in the queue + +#endif // _XBOX + +#endif//#ifndef __G_LOCAL_H__ diff --git a/code/game/g_main.cpp b/code/game/g_main.cpp new file mode 100644 index 0000000..11ac7c9 --- /dev/null +++ b/code/game/g_main.cpp @@ -0,0 +1,2300 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" +#include + + +#include "g_local.h" +#include "g_functions.h" +#include "Q3_Interface.h" +#include "g_nav.h" +#include "g_roff.h" +#include "g_navigator.h" +#include "b_local.h" +#include "anims.h" +#include "objectives.h" +#include "../cgame/cg_local.h" // yeah I know this is naughty, but we're shipping soon... + +//rww - RAGDOLL_BEGIN +#include "../ghoul2/ghoul2_gore.h" +//rww - RAGDOLL_END + +extern void WP_SaberLoadParms( void ); +extern qboolean G_PlayerSpawned( void ); + +extern void Rail_Initialize( void ); +extern void Rail_Update( void ); +extern void Rail_Reset(void); + +extern void Troop_Initialize( void ); +extern void Troop_Update( void ); +extern void Troop_Reset(void); + +extern void Pilot_Reset(void); +extern void Pilot_Update(void); + +extern void G_ASPreCacheFree(void); + + +static int navCalcPathTime = 0; +int eventClearTime = 0; + +extern qboolean g_bCollidableRoffs; + + +#define STEPSIZE 18 + +level_locals_t level; +game_import_t gi; +game_export_t globals; +//gentity_t g_entities[MAX_GENTITIES]; +gentity_t *g_entities = NULL; +unsigned int g_entityInUseBits[MAX_GENTITIES/32]; + +static void ClearAllInUse(void) +{ + memset(g_entityInUseBits,0,sizeof(g_entityInUseBits)); +} + +void SetInUse(gentity_t *ent) +{ + assert(((unsigned int)ent)>=(unsigned int)g_entities); + assert(((unsigned int)ent)<=(unsigned int)(g_entities+MAX_GENTITIES-1)); + unsigned int entNum=ent-g_entities; + g_entityInUseBits[entNum/32]|=((unsigned int)1)<<(entNum&0x1f); +} + +void ClearInUse(gentity_t *ent) +{ + assert(((unsigned int)ent)>=(unsigned int)g_entities); + assert(((unsigned int)ent)<=(unsigned int)(g_entities+MAX_GENTITIES-1)); + unsigned int entNum=ent-g_entities; + g_entityInUseBits[entNum/32]&=~(((unsigned int)1)<<(entNum&0x1f)); +} + +qboolean PInUse(unsigned int entNum) +{ + assert(entNum>=0); + assert(entNum=(unsigned int)g_entities); + assert(((unsigned int)ent)<=(unsigned int)(g_entities+MAX_GENTITIES-1)); + unsigned int entNum=ent-g_entities; + return((g_entityInUseBits[entNum/32]&(((unsigned int)1)<<(entNum&0x1f)))!=0); +} +*/ + +void WriteInUseBits(void) +{ + gi.AppendToSaveGame('INUS', &g_entityInUseBits, sizeof(g_entityInUseBits) ); +} + +void ReadInUseBits(void) +{ + gi.ReadFromSaveGame('INUS', &g_entityInUseBits, sizeof(g_entityInUseBits)); + // This is only temporary. Once I have converted all the ent->inuse refs, + // it won;t be needed -MW. + for(int i=0;ihealth <= 0 && player->max_health > 0 ) + {//defeat music + if ( level.dmState != DM_DEATH ) + { + level.dmState = DM_DEATH; + } + } + + if ( level.dmState == DM_DEATH ) + { + gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "death" ); + return; + } + + if ( level.dmState == DM_BOSS ) + { + gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "boss" ); + return; + } + + if ( level.dmState == DM_SILENCE ) + { + gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "silence" ); + return; + } + + if ( level.dmBeatTime > level.time ) + {//not on a beat + return; + } + + level.dmBeatTime = level.time + 1000;//1 second beats + + if ( player->health <= 20 ) + { + danger = 1; + } + + //enemy-based + VectorCopy( player->currentOrigin, center ); + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + if ( !ent || !ent->inuse ) + { + continue; + } + + if ( !ent->client || !ent->NPC ) + { + if ( ent->classname && (!Q_stricmp( "PAS", ent->classname )||!Q_stricmp( "misc_turret", ent->classname )) ) + {//a turret + entTeam = ent->noDamageTeam; + } + else + { + continue; + } + } + else + {//an NPC + entTeam = ent->client->playerTeam; + } + + if ( entTeam == player->client->playerTeam ) + {//ally + continue; + } + + if ( entTeam == TEAM_NEUTRAL && (!ent->enemy || !ent->enemy->client || ent->enemy->client->playerTeam != player->client->playerTeam) ) + {//a droid that is not mad at me or my allies + continue; + } + + if ( !gi.inPVS( player->currentOrigin, ent->currentOrigin ) ) + {//not potentially visible + continue; + } + + if ( ent->client && ent->s.weapon == WP_NONE ) + {//they don't have a weapon... FIXME: only do this for droids? + continue; + } + + LOScalced = clearLOS = qfalse; + if ( (ent->enemy==player&&(!ent->NPC||ent->NPC->confusionTimeclient&&ent->client->ps.weaponTime) || (!ent->client&&ent->attackDebounceTime>level.time)) + {//mad + if ( ent->health > 0 ) + {//alive + //FIXME: do I really need this check? + if ( ent->s.weapon == WP_SABER && ent->client && !ent->client->ps.SaberActive() && ent->enemy != player ) + {//a Jedi who has not yet gotten made at me + continue; + } + if ( ent->NPC && ent->NPC->behaviorState == BS_CINEMATIC ) + {//they're not actually going to do anything about being mad at me... + continue; + } + //okay, they're in my PVS, but how close are they? Are they actively attacking me? + if ( !ent->client && ent->s.weapon == WP_TURRET && ent->fly_sound_debounce_time && ent->fly_sound_debounce_time - level.time < 10000 ) + {//a turret that shot at me less than ten seconds ago + } + else if ( ent->client && ent->client->ps.lastShotTime && ent->client->ps.lastShotTime - level.time < 10000 ) + {//an NPC that shot at me less than ten seconds ago + } + else + {//not actively attacking me lately, see how far away they are + distSq = DistanceSquared( ent->currentOrigin, player->currentOrigin ); + if ( distSq > 4194304/*2048*2048*/ ) + {//> 2048 away + continue; + } + else if ( distSq > 1048576/*1024*1024*/ ) + {//> 1024 away + clearLOS = G_ClearLOS( player, player->client->renderInfo.eyePoint, ent ); + LOScalced = qtrue; + if ( clearLOS == qfalse ) + {//No LOS + continue; + } + } + } + battle++; + } + } + + if ( level.dmState == DM_EXPLORE ) + {//only do these visibility checks if you're still in exploration mode + if ( !InFront( ent->currentOrigin, player->currentOrigin, player->client->ps.viewangles, 0.0f) ) + {//not in front + continue; + } + + if ( !LOScalced ) + { + clearLOS = G_ClearLOS( player, player->client->renderInfo.eyePoint, ent ); + } + if ( !clearLOS ) + {//can't see them directly + continue; + } + } + + if ( ent->health <= 0 ) + {//dead + if ( !ent->client || level.time - ent->s.time > 10000 ) + {//corpse has been dead for more than 10 seconds + //FIXME: coming across corpses should cause danger sounds too? + continue; + } + } + //we see enemies and/or corpses + danger++; + } + + if ( !battle ) + {//no active enemies, but look for missiles, shot impacts, etc... + int alert = G_CheckAlertEvents( player, qtrue, qtrue, 1024, 1024, -1, qfalse, AEL_SUSPICIOUS ); + if ( alert != -1 ) + {//FIXME: maybe tripwires and other FIXED things need their own sound, some kind of danger/caution theme + if ( G_CheckForDanger( player, alert ) ) + {//found danger near by + danger++; + battle = 1; + } + else if ( level.alertEvents[alert].owner && (level.alertEvents[alert].owner == player->enemy || (level.alertEvents[alert].owner->client && level.alertEvents[alert].owner->client->playerTeam == player->client->enemyTeam) ) ) + {//NPC on enemy team of player made some noise + switch ( level.alertEvents[alert].level ) + { + case AEL_DISCOVERED: + dangerNear = qtrue; + break; + case AEL_SUSPICIOUS: + suspicious = qtrue; + break; + case AEL_MINOR: + //distraction = qtrue; + break; + } + } + } + } + + if ( battle ) + {//battle - this can interrupt level.dmDebounceTime of lower intensity levels + //play battle + if ( level.dmState != DM_ACTION ) + { + gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "action" ); + } + level.dmState = DM_ACTION; + if ( battle > 5 ) + { + //level.dmDebounceTime = level.time + 8000;//don't change again for 5 seconds + } + else + { + //level.dmDebounceTime = level.time + 3000 + 1000*battle; + } + } + else + { + if ( level.dmDebounceTime > level.time ) + {//not ready to switch yet + return; + } + else + {//at least 1 second (for beats) + //level.dmDebounceTime = level.time + 1000;//FIXME: define beat time? + } + /* + if ( danger || dangerNear ) + {//danger + //stay on whatever we were on, action or exploration + if ( !danger ) + {//minimum + danger = 1; + } + if ( danger > 3 ) + { + level.dmDebounceTime = level.time + 5000; + } + else + { + level.dmDebounceTime = level.time + 2000 + 1000*danger; + } + } + else + */ + {//still nothing dangerous going on + if ( level.dmState != DM_EXPLORE ) + {//just went to explore, hold it for a couple seconds at least + //level.dmDebounceTime = level.time + 2000; + gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "explore" ); + } + level.dmState = DM_EXPLORE; + //FIXME: look for interest points and play "mysterious" music instead of exploration? + //FIXME: suspicious and distraction sounds should play some cue or change music in a subtle way? + //play exploration + } + //FIXME: when do we go to silence? + } +} + +void G_ConnectNavs( const char *mapname, int checkSum ) +{ + NAV::LoadFromEntitiesAndSaveToFile(mapname, checkSum); + CP_FindCombatPointWaypoints(); +} + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. +Entity teams are used for item groups and multi-entity mover groups. + +All but the first will have the FL_TEAMSLAVE flag set and teammaster field set +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams( void ) { + gentity_t *e, *e2; + int i, j; + int c, c2; + + c = 0; + c2 = 0; +// for ( i=1, e=g_entities,i ; i < globals.num_entities ; i++,e++ ) + for ( i=1 ; i < globals.num_entities ; i++ ) + { +// if (!e->inuse) +// continue; + if(!PInUse(i)) + continue; + e=&g_entities[i]; + + if (!e->team) + continue; + if (e->flags & FL_TEAMSLAVE) + continue; + e->teammaster = e; + c++; + c2++; +// for (j=i+1, e2=e+1 ; j < globals.num_entities ; j++,e2++) + for (j=i+1; j < globals.num_entities ; j++) + { +// if (!e2->inuse) +// continue; + if(!PInUse(j)) + continue; + + e2=&g_entities[j]; + if (!e2->team) + continue; + if (e2->flags & FL_TEAMSLAVE) + continue; + if (!strcmp(e->team, e2->team)) + { + c2++; + e2->teamchain = e->teamchain; + e->teamchain = e2; + e2->teammaster = e; + e2->flags |= FL_TEAMSLAVE; + + // make sure that targets only point at the master + if ( e2->targetname ) { + e->targetname = G_NewString(e2->targetname); + e2->targetname = NULL; + } + } + } + } + + //gi.Printf ("%i teams with %i entities\n", c, c2); +} + + +/* +============ +G_InitCvars + +============ +*/ +void G_InitCvars( void ) { + // don't override the cheat state set by the system + g_cheats = gi.cvar ("helpUsObi", "", 0); + g_developer = gi.cvar ("developer", "", 0); + + // noset vars + gi.cvar( "gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_ROM ); + gi.cvar( "gamedate", __DATE__ , CVAR_ROM ); + g_skippingcin = gi.cvar ("skippingCinematic", "0", CVAR_ROM); + + // latched vars + + // change anytime vars + g_speed = gi.cvar( "g_speed", "250", CVAR_CHEAT ); + g_gravity = gi.cvar( "g_gravity", "800", CVAR_SAVEGAME|CVAR_ROM ); + g_stepSlideFix = gi.cvar( "g_stepSlideFix", "1", CVAR_ARCHIVE ); + g_sex = gi.cvar ("sex", "f", CVAR_USERINFO | CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_spskill = gi.cvar ("g_spskill", "0", CVAR_ARCHIVE | CVAR_SAVEGAME|CVAR_NORESTART); + g_knockback = gi.cvar( "g_knockback", "1000", CVAR_CHEAT ); + g_dismemberment = gi.cvar ( "g_dismemberment", "3", CVAR_ARCHIVE );//0 = none, 1 = arms and hands, 2 = legs, 3 = waist and head, 4 = mega dismemberment + g_dismemberProbabilities = gi.cvar ( "g_dismemberProbabilities", "1", CVAR_ARCHIVE );//0 = ignore probabilities, 1 = use probabilities + // for now I'm making default 10 seconds + g_corpseRemovalTime = gi.cvar ( "g_corpseRemovalTime", "10", CVAR_ARCHIVE );//number of seconds bodies stick around for, at least... 0 = never go away + g_synchSplitAnims = gi.cvar ( "g_synchSplitAnims", "1", 0 ); +#ifndef FINAL_BUILD + g_AnimWarning = gi.cvar ( "g_AnimWarning", "1", 0 ); +#endif + g_noFootSlide = gi.cvar ( "g_noFootSlide", "1", 0 ); + g_noFootSlideRunScale = gi.cvar ( "g_noFootSlideRunScale", "150.0", 0 ); + g_noFootSlideWalkScale = gi.cvar ( "g_noFootSlideWalkScale", "50.0", 0 ); + + g_nav1 = gi.cvar ( "g_nav1", "", 0 ); + g_nav2 = gi.cvar ( "g_nav2", "", 0 ); + + g_bobaDebug = gi.cvar ( "g_bobaDebug", "", 0 ); + +#if defined(FINAL_BUILD) || defined(_XBOX) + g_delayedShutdown = gi.cvar ( "g_delayedShutdown", "0", 0 ); +#else + g_delayedShutdown = gi.cvar ( "g_delayedShutdown", "1", 0 ); +#endif + + g_inactivity = gi.cvar ("g_inactivity", "0", 0); + g_debugMove = gi.cvar ("g_debugMove", "0", CVAR_CHEAT ); + g_debugDamage = gi.cvar ("g_debugDamage", "0", CVAR_CHEAT ); + g_ICARUSDebug = gi.cvar( "g_ICARUSDebug", "0", CVAR_CHEAT ); + g_timescale = gi.cvar( "timescale", "1", 0 ); + g_npcdebug = gi.cvar( "g_npcdebug", "0", 0 ); + g_navSafetyChecks = gi.cvar( "g_navSafetyChecks", "0", 0 ); + // NOTE : I also create this is UI_Init() + g_subtitles = gi.cvar( "g_subtitles", "0", CVAR_ARCHIVE ); + com_buildScript = gi.cvar ("com_buildscript", "0", 0); + + g_saberAutoBlocking = gi.cvar( "g_saberAutoBlocking", "1", CVAR_CHEAT );//must press +block button to do any blocking + g_saberRealisticCombat = gi.cvar( "g_saberRealisticCombat", "0", CVAR_CHEAT );//makes collision more precise, increases damage + g_saberDamageCapping = gi.cvar( "g_saberDamageCapping", "1", CVAR_CHEAT );//caps damage of sabers vs players and NPC who use sabers + g_saberMoveSpeed = gi.cvar( "g_saberMoveSpeed", "1", CVAR_CHEAT );//how fast you run while attacking with a saber + g_saberAnimSpeed = gi.cvar( "g_saberAnimSpeed", "1", CVAR_CHEAT );//how fast saber animations run + g_saberAutoAim = gi.cvar( "g_saberAutoAim", "1", CVAR_CHEAT );//auto-aims at enemies when not moving or when just running forward + g_saberNewControlScheme = gi.cvar( "g_saberNewControlScheme", "0", CVAR_ARCHIVE );//use +forcefocus to pull off all the special moves + g_debugSaberLock = gi.cvar( "g_debugSaberLock", "0", CVAR_CHEAT );//just for debugging/development, makes saberlocks happen all the time + g_saberLockRandomNess = gi.cvar( "g_saberLockRandomNess", "2", CVAR_ARCHIVE );//just for debugging/development, controls frequency of saberlocks + g_debugMelee = gi.cvar( "g_debugMelee", "0", CVAR_CHEAT );//just for debugging/development, test kicks and grabs + g_saberRestrictForce = gi.cvar( "g_saberRestrictForce", "0", CVAR_ARCHIVE );//restricts certain force powers when using a 2-handed saber or 2 sabers + + g_AIsurrender = gi.cvar( "g_AIsurrender", "0", CVAR_CHEAT ); + g_numEntities = gi.cvar( "g_numEntities", "0", 0 ); + + gi.cvar( "newTotalSecrets", "0", CVAR_ROM ); + gi.cvar_set("newTotalSecrets", "0");//used to carry over the count from SP_target_secret to ClientBegin + //g_iscensored = gi.cvar( "ui_iscensored", "0", CVAR_ARCHIVE|CVAR_ROM|CVAR_INIT|CVAR_CHEAT|CVAR_NORESTART ); + + g_speederControlScheme = gi.cvar( "g_speederControlScheme", "2", CVAR_ARCHIVE|CVAR_ROM );//2 is default, 1 is alternate + + g_char_model = gi.cvar( "g_char_model", "jedi_tf", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_char_skin_head = gi.cvar( "g_char_skin_head", "head_a1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_char_skin_torso = gi.cvar( "g_char_skin_torso", "torso_a1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_char_skin_legs = gi.cvar( "g_char_skin_legs", "lower_a1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_char_color_red = gi.cvar( "g_char_color_red", "255", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_char_color_green = gi.cvar( "g_char_color_green", "255", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_char_color_blue = gi.cvar( "g_char_color_blue", "255", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_saber = gi.cvar( "g_saber", "single_1", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_saber2 = gi.cvar( "g_saber2", "", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_saber_color = gi.cvar( "g_saber_color", "yellow", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_saber2_color = gi.cvar( "g_saber2_color", "yellow", CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART ); + g_saberDarkSideSaberColor = gi.cvar( "g_saberDarkSideSaberColor", "0", CVAR_ARCHIVE ); //when you turn evil, it turns your saber red! + + g_broadsword = gi.cvar( "broadsword", "1", 0); + + gi.cvar( "tier_storyinfo", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART); + gi.cvar( "tiers_complete", "", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART); + + gi.cvar( "ui_prisonerobj_currtotal", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART); + gi.cvar( "ui_prisonerobj_maxtotal", "0", CVAR_ROM|CVAR_SAVEGAME|CVAR_NORESTART); + + gi.cvar( "g_clearstats", "1", CVAR_ROM|CVAR_NORESTART); + +} +/* +============ +InitGame + +============ +*/ + +// I'm just declaring a global here which I need to get at in NAV_GenerateSquadPaths for deciding if pre-calc'd +// data is valid, and this saves changing the proto of G_SpawnEntitiesFromString() to include a checksum param which +// may get changed anyway if a new nav system is ever used. This way saves messing with g_local.h each time -slc +int giMapChecksum; +SavedGameJustLoaded_e g_eSavedGameJustLoaded; +qboolean g_qbLoadTransition = qfalse; +void InitGame( const char *mapname, const char *spawntarget, int checkSum, const char *entities, int levelTime, int randomSeed, int globalTime, SavedGameJustLoaded_e eSavedGameJustLoaded, qboolean qbLoadTransition ) +{ + //rww - default this to 0, we will auto-set it to 1 if we run into a terrain ent + gi.cvar_set("RMG", "0"); + + g_bCollidableRoffs = false; + + giMapChecksum = checkSum; + g_eSavedGameJustLoaded = eSavedGameJustLoaded; + g_qbLoadTransition = qbLoadTransition; + + gi.Printf ("------- Game Initialization -------\n"); + gi.Printf ("gamename: %s\n", GAMEVERSION); + gi.Printf ("gamedate: %s\n", __DATE__); + + srand( randomSeed ); + + G_InitCvars(); + + G_InitMemory(); + + // set some level globals + memset( &level, 0, sizeof( level ) ); + level.time = levelTime; + level.globalTime = globalTime; + Q_strncpyz( level.mapname, mapname, sizeof(level.mapname) ); + if ( spawntarget != NULL && spawntarget[0] ) + { + Q_strncpyz( level.spawntarget, spawntarget, sizeof(level.spawntarget) ); + } + else + { + level.spawntarget[0] = 0; + } + + + G_InitWorldSession(); + + // initialize all entities for this game + memset( g_entities, 0, MAX_GENTITIES * sizeof(g_entities[0]) ); + globals.gentities = g_entities; + ClearAllInUse(); + // initialize all clients for this game + level.maxclients = 1; + level.clients = (struct gclient_s *) G_Alloc( level.maxclients * sizeof(level.clients[0]) ); + memset(level.clients, 0, level.maxclients * sizeof(level.clients[0])); + + // set client fields on player + g_entities[0].client = level.clients; + + // always leave room for the max number of clients, + // even if they aren't all used, so numbers inside that + // range are NEVER anything but clients + globals.num_entities = MAX_CLIENTS; + + //Load sabers.cfg data + WP_SaberLoadParms(); + //Set up NPC init data + NPC_InitGame(); + + TIMER_Clear(); + Rail_Reset(); + Troop_Reset(); + Pilot_Reset(); + + IT_LoadItemParms (); + + ClearRegisteredItems(); + + // clear out old nav info, attempt to load from file + NAV::LoadFromFile(level.mapname, giMapChecksum); + + // parse the key/value pairs and spawn gentities + G_SpawnEntitiesFromString( entities ); + + // general initialization + G_FindTeams(); + +// SaveRegisteredItems(); + + gi.Printf ("-----------------------------------\n"); + + Rail_Initialize(); + Troop_Initialize(); + + + player = &g_entities[0]; + + //Init dynamic music + level.dmState = DM_EXPLORE; + level.dmDebounceTime = 0; + level.dmBeatTime = 0; + + level.curAlertID = 1;//0 is default for lastAlertEvent, so... + eventClearTime = 0; + +#ifdef _XBOX + // clear out NPC water detection data + npcsToUpdateTop = 0; + npcsToUpdateCount = 0; + memset(npcsToUpdate, -1, 2 * MAX_NPC_WATER_UPDATE); +#endif // _XBOX + +} + +/* +================= +ShutdownGame +================= +*/ +void ShutdownGame( void ) +{ + // write all the client session data so we can get it back + G_WriteSessionData(); + +#ifdef _XBOX + // The following functions, cleverly disguised as memory freeing and dealloction, + // actually allocate small blocks. Fooled you! + extern void Z_SetNewDeleteTemporary(bool bTemp); + Z_SetNewDeleteTemporary( true ); +#endif + + // Destroy the Game Interface. + IGameInterface::Destroy(); + + // Shut ICARUS down. + IIcarusInterface::DestroyIcarus(); + + // Destroy the Game Interface again. Only way to really free everything. + IGameInterface::Destroy(); + +#ifdef _XBOX + Z_SetNewDeleteTemporary( false ); +#endif + + TAG_Init(); //Clear the reference tags +/* +Ghoul2 Insert Start +*/ + for (int i=0; is.number] ) + {//not playing a voice sound + //return task_complete + Q3_TaskIDComplete( ent, TID_CHAN_VOICE ); + } + } + + if ( Q3_TaskIDPending( ent, TID_LOCATION ) ) + { + char *currentLoc = G_GetLocationForEnt( ent ); + + if ( currentLoc && currentLoc[0] && Q_stricmp( ent->message, currentLoc ) == 0 ) + {//we're in the desired location + Q3_TaskIDComplete( ent, TID_LOCATION ); + } + //FIXME: else see if were in other trigger_locations? + } +} + +static void G_CheckSpecialPersistentEvents( gentity_t *ent ) +{//special-case alerts that would be a pain in the ass to have the ent's think funcs generate + if ( ent == NULL ) + { + return; + } + if ( ent->s.eType == ET_MISSILE && ent->s.weapon == WP_THERMAL && ent->s.pos.trType == TR_STATIONARY ) + { + if ( eventClearTime == level.time + ALERT_CLEAR_TIME ) + {//events were just cleared out so add me again + AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, qfalse, qtrue ); + AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER ); + } + } + if ( ent->forcePushTime >= level.time ) + {//being pushed + if ( eventClearTime == level.time + ALERT_CLEAR_TIME ) + {//events were just cleared out so add me again + //NOTE: presumes the player did the pushing, this is not always true, but shouldn't really matter? + if ( ent->item && ent->item->giTag == INV_SECURITY_KEY ) + { + AddSightEvent( player, ent->currentOrigin, 128, AEL_DISCOVERED );//security keys are more important + } + else + { + AddSightEvent( player, ent->currentOrigin, 128, AEL_SUSPICIOUS );//hmm... or should this always be discovered? + } + } + } + if ( ent->contents == CONTENTS_LIGHTSABER && !Q_stricmp( "lightsaber", ent->classname ) ) + {//lightsaber + if( ent->owner && ent->owner->client ) + { + if ( ent->owner->client->ps.SaberLength() > 0 ) + {//it's on + //sight event + AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED ); + } + } + } +} +/* +============= +G_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +#include "../client/client.h" +void G_RunThink (gentity_t *ent) +{ + if ( (ent->nextthink <= 0) || (ent->nextthink > level.time) ) + { + goto runicarus; + } + + ent->nextthink = 0; + if ( ent->e_ThinkFunc == thinkF_NULL ) // actually you don't need this since I check for it in the next function -slc + { + goto runicarus; + } + + GEntity_ThinkFunc( ent ); + +runicarus: + if ( ent->inuse ) // GEntity_ThinkFunc( ent ) can have freed up this ent if it was a type flier_child (stasis1 crash) + { + if ( ent->NPC == NULL ) + { + if ( ent->m_iIcarusID != IIcarusInterface::ICARUS_INVALID && !stop_icarus && cls.state != CA_CONNECTED ) + { + IIcarusInterface::GetIcarus()->Update( ent->m_iIcarusID ); + } + } + } +} + +/* +------------------------- +G_Animate +------------------------- +*/ + +static void G_Animate ( gentity_t *self ) +{ + if ( self->s.eFlags & EF_SHADER_ANIM ) + { + return; + } + if ( self->s.frame == self->endFrame ) + { + if ( self->svFlags & SVF_ANIMATING ) + { + // ghoul2 requires some extra checks to see if the animation is done since it doesn't set the current frame directly + if ( self->ghoul2.size() ) + { + float frame, junk2; + int junk; + + // I guess query ghoul2 to find out what the current frame is and see if we are done. + gi.G2API_GetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, + (cg.time?cg.time:level.time), &frame, &junk, &junk, &junk, &junk2, NULL ); + + // It NEVER seems to get to what you'd think the last frame would be, so I'm doing this to try and catch when the animation has stopped + if ( frame + 1 >= self->endFrame ) + { + self->svFlags &= ~SVF_ANIMATING; + Q3_TaskIDComplete( self, TID_ANIM_BOTH ); + } + } + else // not ghoul2 + { + if ( self->loopAnim ) + { + self->s.frame = self->startFrame; + } + else + { + self->svFlags &= ~SVF_ANIMATING; + } + + //Finished sequence - FIXME: only do this once even on looping anims? + Q3_TaskIDComplete( self, TID_ANIM_BOTH ); + } + } + return; + } + + self->svFlags |= SVF_ANIMATING; + + // With ghoul2, we'll just set the desired start and end frame and let it do it's thing. + if ( self->ghoul2.size()) + { + self->s.frame = self->endFrame; + + gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, + self->startFrame, self->endFrame, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time ); + return; + } + + if ( self->startFrame < self->endFrame ) + { + if ( self->s.frame < self->startFrame || self->s.frame > self->endFrame ) + { + self->s.frame = self->startFrame; + } + else + { + self->s.frame++; + } + } + else if ( self->startFrame > self->endFrame ) + { + if ( self->s.frame > self->startFrame || self->s.frame < self->endFrame ) + { + self->s.frame = self->startFrame; + } + else + { + self->s.frame--; + } + } + else + { + self->s.frame = self->endFrame; + } +} + +/* +------------------------- +ResetTeamCounters +------------------------- +*/ + +/* +void ResetTeamCounters( void ) +{ + //clear team enemy counters + for ( int team = TEAM_FREE; team < TEAM_NUM_TEAMS; team++ ) + { + teamEnemyCount[team] = 0; + teamCount[team] = 0; + } +} +*/ + +/* +------------------------- +UpdateTeamCounters +------------------------- +*/ +/* +void UpdateTeamCounters( gentity_t *ent ) +{ + if ( !ent->NPC ) + { + return; + } + if ( !ent->client ) + { + return; + } + if ( ent->health <= 0 ) + { + return; + } + if ( (ent->s.eFlags&EF_NODRAW) ) + { + return; + } + if ( ent->client->playerTeam == TEAM_FREE ) + { + return; + } + //this is an NPC who is alive and visible and is on a specific team + + teamCount[ent->client->playerTeam]++; + if ( !ent->enemy ) + { + return; + } + + //ent has an enemy + if ( !ent->enemy->client ) + {//enemy is a normal ent + if ( ent->noDamageTeam == ent->client->playerTeam ) + {//it's on my team, don't count it as an enemy + return; + } + } + else + {//enemy is another NPC/player + if ( ent->enemy->client->playerTeam == ent->client->playerTeam) + {//enemy is on the same team, don't count it as an enemy + return; + } + } + + //ent's enemy is not on the same team + teamLastEnemyTime[ent->client->playerTeam] = level.time; + teamEnemyCount[ent->client->playerTeam]++; +} +*/ +void G_PlayerGuiltDeath( void ) +{ + if ( player && player->client ) + {//simulate death + player->client->ps.stats[STAT_HEALTH] = 0; + //turn off saber + if ( player->client->ps.weapon == WP_SABER && player->client->ps.SaberActive() ) + { + G_SoundIndexOnEnt( player, CHAN_WEAPON, player->client->ps.saber[0].soundOff ); + player->client->ps.SaberDeactivate(); + } + //play the "what have I done?!" anim + NPC_SetAnim( player, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + player->client->ps.legsAnimTimer = player->client->ps.torsoAnimTimer = -1; + //look at yourself + player->client->ps.stats[STAT_DEAD_YAW] = player->client->ps.viewangles[YAW]+180; + } +} +extern void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +extern void G_MakeTeamVulnerable( void ); +int killPlayerTimer = 0; +static void G_CheckEndLevelTimers( gentity_t *ent ) +{ + if ( killPlayerTimer && level.time > killPlayerTimer ) + { + killPlayerTimer = 0; + ent->health = 0; + if ( ent->client && ent->client->ps.stats[STAT_HEALTH] > 0 ) + { + G_PlayerGuiltDeath(); + //cg.missionStatusShow = qtrue; + statusTextIndex = MISSIONFAILED_TURNED; + //debounce respawn time + ent->client->respawnTime = level.time + 2000; + //stop all scripts + stop_icarus = qtrue; + //make the team killable + G_MakeTeamVulnerable(); + } + } +} + + + +//rww - RAGDOLL_BEGIN +class CGameRagDollUpdateParams : public CRagDollUpdateParams +{ + void EffectorCollision(const SRagDollEffectorCollision &data) + { + //Com_Printf("Effector Collision at (%f %f %f)\n",data.effectorPosition[0],data.effectorPosition[1],data.effectorPosition[2]); + vec3_t effectorPosDif; + + if (data.useTracePlane) + { + float magicFactor42=64.0f; + VectorScale(data.tr.plane.normal,magicFactor42,effectorPosDif); + } + else + { + gentity_t *thisguy = &g_entities[me]; + + if (thisguy && thisguy->client) + { + VectorSubtract(thisguy->client->ps.origin, data.effectorPosition, effectorPosDif); + } + else + { + return; + } + } + + VectorAdd(effectorTotal, effectorPosDif, effectorTotal); + + hasEffectorData = qtrue; + return; + } + void RagDollBegin() + { + return; + } + virtual void RagDollSettled() + { + return; + } + void Collision() // we had a collision, please stop animating and (sometime soon) call SetRagDoll RP_DEATH_COLLISION + { + return; + } + +#ifdef _DEBUG + void DebugLine(vec3_t p1,vec3_t p2,int color,bool bbox) + { + if (!bbox) + { + CG_TestLine(p1, p2, 50, color, 1); + } + return; + } +#endif +public: + vec3_t effectorTotal; + qboolean hasEffectorData; +}; + +//list of valid ragdoll effectors +static const char *g_effectorStringTable[] = +{ //commented out the ones I don't want dragging to affect +// "thoracic", +// "rhand", + "lhand", + "rtibia", + "ltibia", + "rtalus", + "ltalus", +// "rradiusX", + "lradiusX", + "rfemurX", + "lfemurX", +// "ceyebrow", + NULL //always terminate +}; + +extern qboolean G_StandardHumanoid( gentity_t *self ); +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); +extern qboolean G_ReleaseEntity( gentity_t *grabber ); + +static void G_BodyDragUpdate(gentity_t *ent, gentity_t *dragger) +{ + vec3_t handVec; + float handDist; + + assert(ent && ent->inuse && ent->client && ent->ghoul2.size() && + dragger && dragger->inuse && dragger->client && dragger->ghoul2.size()); + + VectorSubtract(dragger->client->renderInfo.handRPoint, ent->client->renderInfo.torsoPoint, handVec); + handDist = VectorLength(handVec); + + if (handDist > 64.0f) + { + G_ReleaseEntity(dragger); + } + else if (handDist > 12.0f) + { + VectorNormalize(handVec); + VectorScale(handVec, 256.0f, handVec); + handVec[2] = 0; + + //VectorAdd(ent->client->ps.velocity, handVec, ent->client->ps.velocity); + //VectorCopy(handVec, ent->client->ps.velocity); + ent->client->ps.velocity[0] = handVec[0]; + ent->client->ps.velocity[1] = handVec[1]; + } +} + +//we want to see which way the pelvis is facing to get a relatively oriented base settling frame +//this is to avoid the arms stretching in opposite directions on the body trying to reach the base +//pose if the pelvis is flipped opposite of the base pose or something -rww +static int G_RagAnimForPositioning(gentity_t *ent) +{ + vec3_t dir; + mdxaBone_t matrix; + assert(ent->client); + vec3_t G2Angles = {0, ent->client->ps.viewangles[YAW], 0}; + + assert(ent->ghoul2.size() > 0); + assert(ent->crotchBolt > -1); + + gi.G2API_GetBoltMatrix(ent->ghoul2, ent->playerModel, ent->crotchBolt, &matrix, G2Angles, ent->client->ps.origin, + (cg.time?cg.time:level.time), NULL, ent->s.modelScale); + gi.G2API_GiveMeVectorFromMatrix(matrix, NEGATIVE_Z, dir); + + if (dir[2] > 0.1f) + { //facing up + return BOTH_DEADFLOP2; + } + else + { //facing down + return BOTH_DEADFLOP1; + } +} + +static inline qboolean G_RagWantsHumanoidsOnly( CGhoul2Info *ghlInfo ) +{ + char *GLAName; + GLAName = gi.G2API_GetGLAName( ghlInfo ); + assert(GLAName); + + if ( !Q_stricmp( "models/players/_humanoid/_humanoid", GLAName ) ) + {//only _humanoid skeleton is expected to have these + return qtrue; + } + + return qfalse; +} + +//rww - game interface for the ragdoll stuff. +//Returns qtrue if the entity is now in a ragdoll state, otherwise qfalse. +//(ported from MP's CG version) + +qboolean G_RagDoll(gentity_t *ent, vec3_t forcedAngles) +{ + vec3_t G2Angles; + vec3_t usedOrg; + qboolean inSomething = qfalse; + int ragAnim; + //int ragVar = gi.Cvar_VariableIntegerValue("broadsword"); + int ragVar = g_broadsword->integer; + + if (!ragVar) + { + return qfalse; + } + + if (!ent || + !ent->inuse || + !ent->client || + ent->health > 0 || + ent->client->noRagTime >= level.time || + ent->client->noRagTime==-1 || + (ent->s.powerups & (1 << PW_DISRUPTION)) || + !ent->e_DieFunc || + ent->playerModel < 0 || + !ent->ghoul2.size() || + !G_RagWantsHumanoidsOnly(&ent->ghoul2[ent->playerModel]) + ) + { + return qfalse; + } + + VectorCopy(forcedAngles, G2Angles); + forcedAngles[0] = forcedAngles[2] = 0; + + if (ent->client->ps.heldByClient <= ENTITYNUM_WORLD) + { + gentity_t *grabbedBy = &g_entities[ent->client->ps.heldByClient]; + + if (grabbedBy->inuse && grabbedBy->client && + grabbedBy->ghoul2.size()) + { + G_BodyDragUpdate(ent, grabbedBy); + } + } + + //--FIXME: do not go into ragdoll if in a spinning death anim or something, it really messes things up + //rww 12/17/02 - should be ok now actually with my new stuff + + VectorCopy(ent->client->ps.origin, usedOrg); + + if (!ent->client->isRagging) + { //If we're not in a ragdoll state, perform the checks. + if (ent->client->ps.heldByClient <= ENTITYNUM_WORLD) + { //want to rag no matter what then + inSomething = qtrue; + } + else if (ent->client->ps.groundEntityNum == ENTITYNUM_NONE) + { + vec3_t cVel; + + VectorCopy(ent->client->ps.velocity, cVel); + + if (VectorNormalize(cVel) > 400) + { //if he's flying through the air at a good enough speed, switch into ragdoll + inSomething = qtrue; + } + } + + if (ragVar > 1) + { //go into rag instantly upon death + inSomething = qtrue; + + //also shove them a tad so they don't just collapse onto the floor + //VectorScale(ent->client->ps.velocity, 1.3f, ent->client->ps.velocity); + ent->client->ps.velocity[2] += 32; + } + + if (!inSomething) + { + int i = 0; + int boltChecks[5]; + vec3_t boltPoints[5]; + vec3_t trStart, trEnd; + vec3_t tAng; + qboolean deathDone = qfalse; + trace_t tr; + mdxaBone_t boltMatrix; + + VectorSet( tAng, 0, ent->client->ps.viewangles[YAW], 0 ); + + if (ent->client->ps.legsAnimTimer <= 0) + { //Looks like the death anim is done playing + deathDone = qtrue; + } + + //if (deathDone) + if (1) + { //only trace from the hands if the death anim is already done. + boltChecks[0] = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "rhand"); + boltChecks[1] = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "lhand"); + } + else + { //otherwise start the trace loop at the cranium. + i = 2; + } + boltChecks[2] = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cranium"); + boltChecks[3] = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "rtalus"); + boltChecks[4] = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "ltalus"); + + //Do the head first, because the hands reference it anyway. + gi.G2API_GetBoltMatrix(ent->ghoul2, ent->playerModel, boltChecks[2], &boltMatrix, tAng, ent->client->ps.origin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale); + gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, boltPoints[2]); + + while (i < 5) + { + if (i < 2) + { //when doing hands, trace to the head instead of origin + gi.G2API_GetBoltMatrix(ent->ghoul2, ent->playerModel, boltChecks[i], &boltMatrix, tAng, ent->client->ps.origin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale); + gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, boltPoints[i]); + VectorCopy(boltPoints[i], trStart); + VectorCopy(boltPoints[2], trEnd); + } + else + { + if (i > 2) + { //2 is the head, which already has the bolt point. + gi.G2API_GetBoltMatrix(ent->ghoul2, ent->playerModel, boltChecks[i], &boltMatrix, tAng, ent->client->ps.origin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale); + gi.G2API_GiveMeVectorFromMatrix(boltMatrix, ORIGIN, boltPoints[i]); + } + VectorCopy(boltPoints[i], trStart); + VectorCopy(ent->client->ps.origin, trEnd); + } + + //Now that we have all that sorted out, trace between the two points we desire. + gi.trace(&tr, trStart, NULL, NULL, trEnd, ent->s.number, MASK_SOLID); + + if (tr.fraction != 1.0 || tr.startsolid || tr.allsolid) + { //Hit something or start in solid, so flag it and break. + //This is a slight hack, but if we aren't done with the death anim, we don't really want to + //go into ragdoll unless our body has a relatively "flat" pitch. +#if 0 + vec3_t vSub; + + //Check the pitch from the head to the right foot (should be reasonable) + VectorSubtract(boltPoints[2], boltPoints[3], vSub); + VectorNormalize(vSub); + vectoangles(vSub, vSub); + + if (deathDone || (vSub[PITCH] < 50 && vSub[PITCH] > -50)) + { + inSomething = qtrue; + } +#else + inSomething = qtrue; +#endif + break; + } + + i++; + } + } + + if (inSomething) + { + /* + PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoAnimTimer, 0 ); + PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, 0 ); + + PM_SetAnimFinal(&ent->client->ps.torsoAnim,&ent->client->ps.legsAnim,SETANIM_BOTH,ragAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, + &ent->client->ps.torsoAnimTimer,&ent->client->ps.legsAnimTimer,ent,500); + */ + ent->client->isRagging = qtrue; + } + } + + if (ent->client->isRagging) + { //We're in a ragdoll state, so make the call to keep our positions updated and whatnot. + CRagDollParams tParms; + CGameRagDollUpdateParams tuParms; + + ragAnim = G_RagAnimForPositioning(ent); + + /* + if (ent->ikStatus) + { //ik must be reset before ragdoll is started, or you'll get some interesting results. + trap_G2API_SetBoneIKState(cent->ghoul2, cg.time, NULL, IKS_NONE, NULL); + cent->ikStatus = qfalse; + } + */ + + //these will be used as "base" frames for the ragoll settling. + tParms.startFrame = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations[ragAnim].firstFrame; + tParms.endFrame = level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations[ragAnim].firstFrame+level.knownAnimFileSets[ent->client->clientInfo.animFileIndex].animations[ragAnim].numFrames; +#if 1 + { + float currentFrame; + int startFrame, endFrame; + int flags; + float animSpeed; + + if (gi.G2API_GetBoneAnim(&ent->ghoul2[0], "model_root", (cg.time?cg.time:level.time), ¤tFrame, &startFrame, &endFrame, &flags, &animSpeed, NULL)) + { //lock the anim on the current frame. + int blendTime = 500; + + gi.G2API_SetBoneAnim(&ent->ghoul2[0], "lower_lumbar", currentFrame, currentFrame+1, flags, animSpeed,(cg.time?cg.time:level.time), currentFrame, blendTime); + gi.G2API_SetBoneAnim(&ent->ghoul2[0], "model_root", currentFrame, currentFrame+1, flags, animSpeed, (cg.time?cg.time:level.time), currentFrame, blendTime); + gi.G2API_SetBoneAnim(&ent->ghoul2[0], "Motion", currentFrame, currentFrame+1, flags, animSpeed, (cg.time?cg.time:level.time), currentFrame, blendTime); + } + } +#endif + + gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 100, (cg.time?cg.time:level.time) ); + gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 100, (cg.time?cg.time:level.time) ); + gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 100, (cg.time?cg.time:level.time) ); + gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "cervical", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 100, (cg.time?cg.time:level.time) ); + + VectorCopy(G2Angles, tParms.angles); + VectorCopy(usedOrg, tParms.position); + VectorCopy(ent->s.modelScale, tParms.scale); + tParms.me = ent->s.number; + tParms.groundEnt = ent->client->ps.groundEntityNum; + + tParms.collisionType = 1; + tParms.RagPhase=CRagDollParams::ERagPhase::RP_DEATH_COLLISION; + tParms.fShotStrength = 4; + + gi.G2API_SetRagDoll(ent->ghoul2, &tParms); + + + tuParms.hasEffectorData = qfalse; + VectorClear(tuParms.effectorTotal); + + VectorCopy(G2Angles, tuParms.angles); + VectorCopy(usedOrg, tuParms.position); + VectorCopy(ent->s.modelScale, tuParms.scale); + tuParms.me = ent->s.number; + tuParms.settleFrame = tParms.endFrame-1; + tuParms.groundEnt = ent->client->ps.groundEntityNum; + + if (ent->client->ps.groundEntityNum != ENTITYNUM_NONE) + { + VectorClear(tuParms.velocity); + } + else + { + VectorScale(ent->client->ps.velocity, 0.4f, tuParms.velocity); + } + + gi.G2API_AnimateG2Models(ent->ghoul2, (cg.time?cg.time:level.time), &tuParms); + + if (ent->client->ps.heldByClient <= ENTITYNUM_WORLD) + { + gentity_t *grabEnt; + + grabEnt = &g_entities[ent->client->ps.heldByClient]; + + if (grabEnt->client && grabEnt->ghoul2.size()) + { + vec3_t bOrg; + vec3_t thisHand; + vec3_t hands; + vec3_t pcjMin, pcjMax; + vec3_t pDif; + vec3_t thorPoint; + float difLen; + + //Get the person who is holding our hand's hand location + //gi.G2API_GetBoltMatrix(grabEnt->ghoul2, 0, grabEnt->gent->client->renderInfo.handRPoint, &matrix, grabEnt->turAngles, grabEnt->lerpOrigin, + // cg.time, cgs.gameModels, grabEnt->modelScale); + //BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, bOrg); + VectorCopy(grabEnt->client->renderInfo.handRPoint, bOrg); + + //Get our hand's location + //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, 0, &matrix, cent->turAngles, cent->lerpOrigin, + // cg.time, cgs.gameModels, cent->modelScale); + //BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thisHand); + VectorCopy(ent->client->renderInfo.handRPoint, thisHand); + + //Get the position of the thoracic bone for hinting its velocity later on + //thorBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "thoracic"); + //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, thorBolt, &matrix, cent->turAngles, cent->lerpOrigin, + // cg.time, cgs.gameModels, cent->modelScale); + //BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thorPoint); + VectorCopy(ent->client->renderInfo.torsoPoint, thorPoint); + + VectorSubtract(bOrg, thisHand, hands); + + if (VectorLength(hands) < 3.0f) + { + gi.G2API_RagForceSolve(ent->ghoul2, qfalse); + } + else + { + gi.G2API_RagForceSolve(ent->ghoul2, qtrue); + } + + //got the hand pos of him, now we want to make our hand go to it + gi.G2API_RagEffectorGoal(ent->ghoul2, "rhand", bOrg); + gi.G2API_RagEffectorGoal(ent->ghoul2, "rradius", bOrg); + gi.G2API_RagEffectorGoal(ent->ghoul2, "rradiusX", bOrg); + gi.G2API_RagEffectorGoal(ent->ghoul2, "rhumerusX", bOrg); + gi.G2API_RagEffectorGoal(ent->ghoul2, "rhumerus", bOrg); + + //Make these two solve quickly so we can update decently + gi.G2API_RagPCJGradientSpeed(ent->ghoul2, "rhumerus", 1.5f); + gi.G2API_RagPCJGradientSpeed(ent->ghoul2, "rradius", 1.5f); + + //Break the constraints on them I suppose + VectorSet(pcjMin, -999, -999, -999); + VectorSet(pcjMax, 999, 999, 999); + gi.G2API_RagPCJConstraint(ent->ghoul2, "rhumerus", pcjMin, pcjMax); + gi.G2API_RagPCJConstraint(ent->ghoul2, "rradius", pcjMin, pcjMax); + + ent->client->overridingBones = level.time + 2000; + + //hit the thoracic velocity to the hand point + VectorSubtract(bOrg, thorPoint, hands); + VectorNormalize(hands); + VectorScale(hands, 2048.0f, hands); + gi.G2API_RagEffectorKick(ent->ghoul2, "thoracic", hands); + gi.G2API_RagEffectorKick(ent->ghoul2, "ceyebrow", hands); + + VectorSubtract(ent->client->ragLastOrigin, ent->client->ps.origin, pDif); + VectorCopy(ent->client->ps.origin, ent->client->ragLastOrigin); + + if (ent->client->ragLastOriginTime >= level.time && ent->client->ps.groundEntityNum != ENTITYNUM_NONE) + { //make sure it's reasonably updated + difLen = VectorLength(pDif); + if (difLen > 0.0f) + { //if we're being dragged, then kick all the bones around a bit + vec3_t dVel; + vec3_t rVel; + int i = 0; + + if (difLen < 12.0f) + { + VectorScale(pDif, 12.0f/difLen, pDif); + difLen = 12.0f; + } + + while (g_effectorStringTable[i]) + { + VectorCopy(pDif, dVel); + dVel[2] = 0; + + //Factor in a random velocity + VectorSet(rVel, Q_flrand(-0.1f, 0.1f), Q_flrand(-0.1f, 0.1f), Q_flrand(0.1f, 0.5)); + VectorScale(rVel, 8.0f, rVel); + + VectorAdd(dVel, rVel, dVel); + VectorScale(dVel, 10.0f, dVel); + + gi.G2API_RagEffectorKick(ent->ghoul2, g_effectorStringTable[i], dVel); + + i++; + } + } + } + ent->client->ragLastOriginTime = level.time + 1000; + } + } + else if (ent->client->overridingBones) + { //reset things to their normal rag state + vec3_t pcjMin, pcjMax; + vec3_t dVel; + + //got the hand pos of him, now we want to make our hand go to it + gi.G2API_RagEffectorGoal(ent->ghoul2, "rhand", NULL); + gi.G2API_RagEffectorGoal(ent->ghoul2, "rradius", NULL); + gi.G2API_RagEffectorGoal(ent->ghoul2, "rradiusX", NULL); + gi.G2API_RagEffectorGoal(ent->ghoul2, "rhumerusX", NULL); + gi.G2API_RagEffectorGoal(ent->ghoul2, "rhumerus", NULL); + + VectorSet(dVel, 0.0f, 0.0f, -64.0f); + gi.G2API_RagEffectorKick(ent->ghoul2, "rhand", dVel); + + gi.G2API_RagPCJGradientSpeed(ent->ghoul2, "rhumerus", 0.0f); + gi.G2API_RagPCJGradientSpeed(ent->ghoul2, "rradius", 0.0f); + + VectorSet(pcjMin,-100.0f,-40.0f,-15.0f); + VectorSet(pcjMax,-15.0f,80.0f,15.0f); + gi.G2API_RagPCJConstraint(ent->ghoul2, "rhumerus", pcjMin, pcjMax); + + VectorSet(pcjMin,-25.0f,-20.0f,-20.0f); + VectorSet(pcjMax,90.0f,20.0f,-20.0f); + gi.G2API_RagPCJConstraint(ent->ghoul2, "rradius", pcjMin, pcjMax); + + if (ent->client->overridingBones < level.time) + { + gi.G2API_RagForceSolve(ent->ghoul2, qfalse); + ent->client->overridingBones = 0; + } + else + { + gi.G2API_RagForceSolve(ent->ghoul2, qtrue); + } + } + + if (tuParms.hasEffectorData) + { + /* + vec3_t efDir; + vec3_t existingVelocity; + float evValue = 0; + + VectorCopy(tuParms.effectorTotal, efDir); + VectorNormalize(efDir); + VectorCopy(ent->client->ps.velocity, existingVelocity); + evValue = VectorNormalize(existingVelocity); + + if (evValue < 32) + { + ent->client->ps.velocity[0] += efDir[0]*64; + ent->client->ps.velocity[1] += efDir[1]*64; + ent->client->ps.velocity[2] += efDir[2]*64; + } + */ + + VectorNormalize(tuParms.effectorTotal); + VectorScale(tuParms.effectorTotal, 7.0f, tuParms.effectorTotal); + + VectorAdd(ent->client->ps.velocity, tuParms.effectorTotal, ent->client->ps.velocity); + } + + return qtrue; + } + + return qfalse; +} +//rww - RAGDOLL_END + +/* +================ +G_FreeUselessEnemies + +Continually monitors for enemies that are no longer relevant, +and kills them off. TODO: Also kill off allies (jedi in kor[12]) +================ +*/ +char current_speeders = 0; + +void G_FreeUselessEnemies(void) +{ + // We check three entities per frame, cuts down on CPU impact, still + // ends up checking every entity within about 10 seconds + static int freeEntNum = 1; + + for( int j = 0; j < 3; ++j ) + { + + if( current_speeders > 24 && // too many speeders + PInUse(freeEntNum) && // not in use + g_entities[freeEntNum].health && // has health + g_entities[freeEntNum].m_pVehicle && // valid vehicle + g_entities[freeEntNum].m_pVehicle->m_pVehicleInfo && // valid vehicle info + g_entities[freeEntNum].m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER && // a speeder + !g_entities[freeEntNum].m_pVehicle->m_pPilot && // doesn't have a pilot + strcmp(g_entities[freeEntNum].m_pVehicle->m_pVehicleInfo->name, "Swoop_cin")) // not needed for a cinematic + { + // blow it up + G_Damage(&g_entities[freeEntNum], NULL, NULL, 0, 0, 200000, DAMAGE_NO_PROTECTION, MOD_UNKNOWN); + // decrement the number of speeders + current_speeders--; + // set the alreadyCleaned flag so that when the entitiy is removed current_speeders is not decremented again + g_entities[freeEntNum].m_pVehicle->alreadyCleaned = true; + } + + // If the entity is an NPC which has no key, isn't invulnerable, + // has fired, but not in a while, has health, isn't on the player's team, + // and is far away, destroy it. + else if( PInUse(freeEntNum) && + g_entities[freeEntNum].NPC && + !g_entities[freeEntNum].message && + g_entities[freeEntNum].client && + g_entities[freeEntNum].health && + g_entities[freeEntNum].client->playerTeam != TEAM_PLAYER && + !(g_entities[freeEntNum].client->ps.powerups[PW_INVINCIBLE]>level.time) && + !(g_entities[freeEntNum].flags & FL_UNDYING) && +// !(g_entities[freeEntNum].client->ps.eFlags & EF_INVULNERABLE) && + g_entities[freeEntNum].NPC->shotTime && + g_entities[freeEntNum].NPC->shotTime + 10000 < level.time && + DistanceSquared(g_entities[freeEntNum].currentOrigin, + g_entities[0].currentOrigin) > (2000 * 2000)) + { + G_Damage(&g_entities[freeEntNum], NULL, NULL, 0, 0, 100000, + DAMAGE_NO_PROTECTION, MOD_UNKNOWN); + g_entities[freeEntNum].NPC->timeOfDeath = 0; + } + freeEntNum++; + freeEntNum %= MAX_GENTITIES; + } +} + +/* +================ +G_RunFrame + +Advances the non-player objects in the world +================ +*/ +void G_MassFreeUselessThings( void ) +{ + // We check three entities per frame, cuts down on CPU impact, still + // ends up checking every entity within about 10 seconds + + for( int j = 0; j < MAX_GENTITIES; ++j ) + { + if( PInUse(j) && // not in use + g_entities[j].health && // has health + g_entities[j].m_pVehicle && // valid vehicle + g_entities[j].m_pVehicle->m_pVehicleInfo && // valid vehicle info + g_entities[j].m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER && // a speeder + !g_entities[j].m_pVehicle->m_pPilot && // doesn't have a pilot + strcmp(g_entities[j].m_pVehicle->m_pVehicleInfo->name, "Swoop_cin")) // not needed for a cinematic + { + G_Damage(&g_entities[j], NULL, NULL, 0, 0, 200000, DAMAGE_NO_PROTECTION, MOD_UNKNOWN); + } + + // If the entity is an NPC which has no key, isn't invulnerable, + // has fired, but not in a while, has health, isn't on the player's team, + // and is far away, destroy it. + else if( (PInUse(j) && + g_entities[j].NPC && + !g_entities[j].message && + g_entities[j].client && + g_entities[j].health && + g_entities[j].client->playerTeam != TEAM_PLAYER && + !(g_entities[j].client->ps.powerups[PW_INVINCIBLE]>level.time) && + g_entities[j].NPC->shotTime && + g_entities[j].NPC->shotTime + 10000 < level.time && + DistanceSquared(g_entities[j].currentOrigin, + g_entities[0].currentOrigin) > (2000 * 2000)) ) + { + G_Damage(&g_entities[j], NULL, NULL, 0, 0, 100000, + DAMAGE_NO_PROTECTION, MOD_UNKNOWN); + g_entities[j].NPC->timeOfDeath = 0; + } + } +} + + +/* +================ +G_RunFrame + +Advances the non-player objects in the world +================ +*/ +#if AI_TIMERS +int AITime = 0; +int navTime = 0; +#endif// AI_TIMERS + +extern cvar_t* in_shaking_rumble; +void G_RunFrame( int levelTime ) { + int i; + gentity_t *ent; + int msec; + int ents_inuse=0; // someone's gonna be pissed I put this here... +#if AI_TIMERS + AITime = 0; + navTime = 0; +#endif// AI_TIMERS + + level.framenum++; + level.previousTime = level.time; + level.time = levelTime; + msec = level.time - level.previousTime; + + //ResetTeamCounters(); + NAV::DecayDangerSenses(); + Rail_Update(); + Troop_Update(); + Pilot_Update(); + + + if (player && gi.WE_IsShaking(player->currentOrigin)) + { + CGCam_Shake(0.45f, 100, in_shaking_rumble->integer); + } + + + AI_UpdateGroups(); + + + + + //Look to clear out old events + ClearPlayerAlertEvents(); + + //Run the frame for all entities +// for ( i = 0, ent = &g_entities[0]; i < globals.num_entities ; i++, ent++) + for ( i = 0; i < globals.num_entities ; i++) + { +// if ( !ent->inuse ) +// continue; + + if(!PInUse(i)) + continue; + ents_inuse++; + ent = &g_entities[i]; + + // clear events that are too old + if ( level.time - ent->eventTime > EVENT_VALID_MSEC ) { + if ( ent->s.event ) { + ent->s.event = 0; // &= EV_EVENT_BITS; + if ( ent->client ) { + ent->client->ps.externalEvent = 0; + } + } + if ( ent->freeAfterEvent ) { + // tempEntities or dropped items completely go away after their event + G_FreeEntity( ent ); + continue; + } + /* // This is never set to true anywhere. Killing the field (BTO - VV) + else if ( ent->unlinkAfterEvent ) { + // items that will respawn will hide themselves after their pickup event + ent->unlinkAfterEvent = qfalse; + gi.unlinkentity( ent ); + } + */ + } + + // temporary entities don't think + if ( ent->freeAfterEvent ) + continue; + + G_CheckTasksCompleted(ent); + + G_Roff( ent ); + + if( !ent->client ) + { + if ( !(ent->svFlags & SVF_SELF_ANIMATING) ) + {//FIXME: make sure this is done only for models with frames? + //Or just flag as animating? + if ( ent->s.eFlags & EF_ANIM_ONCE ) + { + ent->s.frame++; + } + else if ( !(ent->s.eFlags & EF_ANIM_ALLFAST) ) + { + G_Animate( ent ); + } + } + } + G_CheckSpecialPersistentEvents( ent ); + + if ( ent->s.eType == ET_MISSILE ) + { + G_RunMissile( ent ); + continue; + } + + if ( ent->s.eType == ET_ITEM ) + { + G_RunItem( ent ); + continue; + } + + if ( ent->s.eType == ET_MOVER ) + { + if ( ent->model && Q_stricmp( "models/test/mikeg/tie_fighter.md3", ent->model ) == 0 ) + { + TieFighterThink( ent ); + } + G_RunMover( ent ); + continue; + } + + //The player + if ( i == 0 ) + { + // decay batteries if the goggles are active + if ( cg.zoomMode == 1 && ent->client->ps.batteryCharge > 0 ) + { + ent->client->ps.batteryCharge--; + } + else if ( cg.zoomMode == 3 && ent->client->ps.batteryCharge > 0 ) + { + ent->client->ps.batteryCharge -= 2; + + if ( ent->client->ps.batteryCharge < 0 ) + { + ent->client->ps.batteryCharge = 0; + } + } + + G_CheckEndLevelTimers( ent ); + //Recalculate the nearest waypoint for the coming NPC updates + NAV::GetNearestNode( ent ); + + + if( ent->m_iIcarusID != IIcarusInterface::ICARUS_INVALID && !stop_icarus ) + { + IIcarusInterface::GetIcarus()->Update( ent->m_iIcarusID ); + } + //dead + if ( ent->health <= 0 ) + { + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//on the ground + pitch_roll_for_slope( ent ); + } + } + + continue; // players are ucmd driven + } + + G_RunThink( ent ); // be aware that ent may be free after returning from here, at least one func frees them + ClearNPCGlobals(); // but these 2 funcs are ok + //UpdateTeamCounters( ent ); // to call anyway on a freed ent. + } + + // perform final fixups on the player + ent = &g_entities[0]; + if ( ent->inuse ) + { + ClientEndFrame( ent ); + } + if( g_numEntities->integer ) + { + gi.Printf( S_COLOR_WHITE"Number of Entities in use : %d\n", ents_inuse ); + } + //DEBUG STUFF + NAV::ShowDebugInfo(ent->currentOrigin, ent->waypoint); + NPC_ShowDebugInfo(); + + G_DynamicMusicUpdate(); + +#if AI_TIMERS + AITime -= navTime; + if ( AITime > 20 ) + { + gi.Printf( S_COLOR_RED"ERROR: total AI time: %d\n", AITime ); + } + else if ( AITime > 10 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: total AI time: %d\n", AITime ); + } + else if ( AITime > 2 ) + { + gi.Printf( S_COLOR_GREEN"total AI time: %d\n", AITime ); + } + if ( navTime > 20 ) + { + gi.Printf( S_COLOR_RED"ERROR: total nav time: %d\n", navTime ); + } + else if ( navTime > 10 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: total nav time: %d\n", navTime ); + } + else if ( navTime > 2 ) + { + gi.Printf( S_COLOR_GREEN"total nav time: %d\n", navTime ); + } +#endif// AI_TIMERS + +extern int delayedShutDown; + if ( g_delayedShutdown->integer && delayedShutDown != 0 && delayedShutDown < level.time ) + { + assert(0); + G_Error( "Game Errors. Scroll up the console to read them.\n" ); + } + +#ifdef _DEBUG + if(!(level.framenum&0xff)) + { + ValidateInUseBits(); + } +#endif + +#ifdef _XBOX + // update the water levels for npcs + extern void UpdateNPCWaterLevels(void); + UpdateNPCWaterLevels(); + + // Kill off any AI that are lingering! + G_FreeUselessEnemies(); +#endif // _XBOX +} + + + +extern qboolean player_locked; + +void G_LoadSave_WriteMiscData(void) +{ + gi.AppendToSaveGame('LCKD', &player_locked, sizeof(player_locked)); +} + + + +void G_LoadSave_ReadMiscData(void) +{ + gi.ReadFromSaveGame('LCKD', &player_locked, sizeof(player_locked)); +} + + +/* +void PrintEntClassname( int gentNum ) +{ + Com_Printf( "%d: %s in snapshot\n", gentNum, g_entities[gentNum].classname ); +} +*/ +IGhoul2InfoArray &TheGameGhoul2InfoArray() +{ + return gi.TheGhoul2InfoArray(); +} + +extern bool bHadPersistedSurface; +gentity_t *pReservedZoneGentities = NULL; + +void G_ReserveZoneGentities( void ) +{ + pReservedZoneGentities = (gentity_t *) Z_Malloc( sizeof(gentity_t) * MAX_GENTITIES, TAG_TEMP_WORKSPACE, qfalse, 4 ); +} + +void G_AllocGentities( void ) +{ + g_entities = (gentity_t *) HeapAlloc( GetProcessHeap(), 0, sizeof(gentity_t) * MAX_GENTITIES ); + + // If it worked... + if( g_entities ) + { + // And we had a persisted surface. (Normal case). Free the reserved zone: + if( bHadPersistedSurface ) + { + Z_Free( pReservedZoneGentities ); + pReservedZoneGentities = NULL; + return; + } + + // Really bad case #1: + Com_PrintfAlways( "G_AllocGentities: Extreme failure #1\n" ); + return; + } + else + { + // It failed. Hopefully that means that there was no persisted surface. + if( !bHadPersistedSurface ) + { + g_entities = pReservedZoneGentities; + pReservedZoneGentities = NULL; + return; + } + + // Really bad case #2: + Com_PrintfAlways( "G_AllocGentities: Extreme failure #2\n" ); + return; + } +} diff --git a/code/game/g_mem.cpp b/code/game/g_mem.cpp new file mode 100644 index 0000000..635efb7 --- /dev/null +++ b/code/game/g_mem.cpp @@ -0,0 +1,38 @@ +// +// g_mem.c +// +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + + + +#include "g_local.h" + + +/*#define POOLSIZE (2 * 1024 * 1024) + +static char memoryPool[POOLSIZE]; +*/ +static int allocPoint; +static cvar_t *g_debugalloc; + +void *G_Alloc( int size ) { + if ( g_debugalloc->integer ) { + gi.Printf( "G_Alloc of %i bytes\n", size ); + } + + + allocPoint += size; + + return gi.Malloc(size, TAG_G_ALLOC, qfalse); +} + +void G_InitMemory( void ) { + allocPoint = 0; + g_debugalloc = gi.cvar ("g_debugalloc", "0", 0); +} + +void Svcmd_GameMem_f( void ) { + gi.Printf( "Game memory status: %i allocated\n", allocPoint ); +} diff --git a/code/game/g_misc.cpp b/code/game/g_misc.cpp new file mode 100644 index 0000000..bb0aaa8 --- /dev/null +++ b/code/game/g_misc.cpp @@ -0,0 +1,3297 @@ +// g_misc.c + +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "g_functions.h" +#include "g_nav.h" +#include "g_items.h" + +extern gentity_t *G_FindDoorTrigger( gentity_t *door ); +extern void G_SetEnemy( gentity_t *self, gentity_t *enemy ); +extern void SetMiscModelDefaults( gentity_t *ent, useFunc_t use_func, char *material, int solid_mask,int animFlag, + qboolean take_damage, qboolean damage_model); + +#define MAX_AMMO_GIVE 4 + + + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities. + +q3map_onlyvertexlighting 1 = brush only gets vertex lighting (reduces bsp size!) +*/ + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) LIGHT +Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay. + +LIGHT - If this info_null is only targeted by a non-switchable light (a light without a targetname), it does NOT spawn in at all and doesn't count towards the # of entities on the map, even at map spawn/load +*/ +void SP_info_null( gentity_t *self ) { + if ( (self->spawnflags&1) ) + {//only used as a light target, so bugger off + G_FreeEntity( self ); + return; + } + //FIXME: store targetname and vector (origin) in a list for further reference... remove after 1st second of game? + G_SetOrigin( self, self->s.origin ); + self->e_ThinkFunc = thinkF_G_FreeEntity; + //Give other ents time to link + self->nextthink = level.time + START_TIME_REMOVE_ENTS; +} + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for in-game calculation, like jumppad targets. +target_position does the same thing +*/ +void SP_info_notnull( gentity_t *self ){ + //FIXME: store in ref_tag system? + G_SetOrigin( self, self->s.origin ); +} + + +/*QUAKED lightJunior (0 0.7 0.3) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point +Non-displayed light that only affects dynamic game models, but does not contribute to lightmaps +"light" overrides the default 300 intensity. +Nonlinear checkbox gives inverse square falloff instead of linear +Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf) +Lights pointed at a target will be spotlights. +"radius" overrides the default 64 unit radius of a spotlight at the target point. +"fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf) +*/ + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear noIncidence START_OFF +Non-displayed light. +"light" overrides the default 300 intensity. - affects size +a negative "light" will subtract the light's color +'Linear' checkbox gives linear falloff instead of inverse square +'noIncidence' checkbox makes lighting smoother +Lights pointed at a target will be spotlights. +"radius" overrides the default 64 unit radius of a spotlight at the target point. +"scale" multiplier for the light intensity - does not affect size (default 1) + greater than 1 is brighter, between 0 and 1 is dimmer. +"color" sets the light's color +"targetname" to indicate a switchable light - NOTE that all lights with the same targetname will be grouped together and act as one light (ie: don't mix colors, styles or start_off flag) +"style" to specify a specify light style, even for switchable lights! +"style_off" light style to use when switched off (Only for switchable lights) + + 1 FLICKER (first variety) + 2 SLOW STRONG PULSE + 3 CANDLE (first variety) + 4 FAST STROBE + 5 GENTLE PULSE 1 + 6 FLICKER (second variety) + 7 CANDLE (second variety) + 8 CANDLE (third variety) + 9 SLOW STROBE (fourth variety) + 10 FLUORESCENT FLICKER + 11 SLOW PULSE NOT FADE TO BLACK + 12 FAST PULSE FOR JEREMY + 13 Test Blending +*/ +static void misc_lightstyle_set ( gentity_t *ent) +{ + const int mLightStyle = ent->count; + const int mLightSwitchStyle = ent->bounceCount; + const int mLightOffStyle = ent->fly_sound_debounce_time; + if (!ent->misc_dlight_active) + { //turn off + if (mLightOffStyle) //i have a light style i'd like to use when off + { + char lightstyle[32]; + gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+0, lightstyle, 32); + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle); + + gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+1, lightstyle, 32); + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle); + + gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+2, lightstyle, 32); + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle); + }else + { + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "a"); + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "a"); + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "a"); + } + } + else + { //Turn myself on now + if (mLightSwitchStyle) //i have a light style i'd like to use when on + { + char lightstyle[32]; + gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+0, lightstyle, 32); + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle); + + gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+1, lightstyle, 32); + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle); + + gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+2, lightstyle, 32); + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle); + } + else + { + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "z"); + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "z"); + gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "z"); + } + } +} +void SP_light( gentity_t *self ) { + if (!self->targetname ) + {//if i don't have a light style switch, the i go away + G_FreeEntity( self ); + return; + } + + G_SpawnInt( "style", "0", &self->count ); + G_SpawnInt( "switch_style", "0", &self->bounceCount ); + G_SpawnInt( "style_off", "0", &self->fly_sound_debounce_time ); + G_SetOrigin( self, self->s.origin ); + gi.linkentity( self ); + + self->e_UseFunc = useF_misc_dlight_use; + self->e_clThinkFunc = clThinkF_NULL; + + self->s.eType = ET_GENERAL; + self->misc_dlight_active = qfalse; + self->svFlags |= SVF_NOCLIENT; + + if ( !(self->spawnflags & 4) ) + { //turn myself on now + self->misc_dlight_active = qtrue; + } + misc_lightstyle_set (self); +} + +void misc_dlight_use ( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + G_ActivateBehavior(ent,BSET_USE); + + ent->misc_dlight_active = !ent->misc_dlight_active; //toggle + misc_lightstyle_set (ent); +} + + +/* +================================================================================= + +TELEPORTERS + +================================================================================= +*/ + +void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) +{ + if ( player->NPC && ( player->NPC->aiFlags&NPCAI_FORM_TELE_NAV ) ) + { + //My leader teleported, I was trying to catch up, take this off + player->NPC->aiFlags &= ~NPCAI_FORM_TELE_NAV; + + } + + // unlink to make sure it can't possibly interfere with G_KillBox + gi.unlinkentity (player); + + VectorCopy ( origin, player->client->ps.origin ); + player->client->ps.origin[2] += 1; + VectorCopy ( player->client->ps.origin, player->currentOrigin ); + + // spit the player out + AngleVectors( angles, player->client->ps.velocity, NULL, NULL ); + VectorScale( player->client->ps.velocity, 0, player->client->ps.velocity ); + //player->client->ps.pm_time = 160; // hold time + //player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + + // toggle the teleport bit so the client knows to not lerp + player->client->ps.eFlags ^= EF_TELEPORT_BIT; + + // set angles + SetClientViewAngle( player, angles ); + + // kill anything at the destination + G_KillBox (player); + + // save results of pmove + PlayerStateToEntityState( &player->client->ps, &player->s ); + + gi.linkentity (player); +} + +void TeleportMover( gentity_t *mover, vec3_t origin, vec3_t diffAngles, qboolean snapAngle ) +{//FIXME: need an effect + vec3_t oldAngle, newAngle; + float speed; + + // unlink to make sure it can't possibly interfere with G_KillBox + gi.unlinkentity (mover); + + //reposition it + VectorCopy( origin, mover->s.pos.trBase ); + VectorCopy( origin, mover->currentOrigin ); + + //Maintain their previous speed, but adjusted for new direction + if ( snapAngle ) + {//not a diffAngle, actually an absolute angle + vec3_t dir; + + VectorCopy( diffAngles, newAngle ); + AngleVectors( newAngle, dir, NULL, NULL ); + VectorNormalize( dir );//necessary? + speed = VectorLength( mover->s.pos.trDelta ); + VectorScale( dir, speed, mover->s.pos.trDelta ); + mover->s.pos.trTime = level.time; + + VectorSubtract( newAngle, mover->s.apos.trBase, diffAngles ); + VectorCopy( newAngle, mover->s.apos.trBase ); + } + else + { + speed = VectorNormalize( mover->s.pos.trDelta ); + + vectoangles( mover->s.pos.trDelta, oldAngle ); + VectorAdd( oldAngle, diffAngles, newAngle ); + + AngleVectors( newAngle, mover->s.pos.trDelta, NULL, NULL ); + VectorNormalize( mover->s.pos.trDelta ); + + VectorScale( mover->s.pos.trDelta, speed, mover->s.pos.trDelta ); + mover->s.pos.trTime = level.time; + + //Maintain their previous angles, but adjusted to new orientation + VectorAdd( mover->s.apos.trBase, diffAngles, mover->s.apos.trBase ); + } + + //Maintain their previous anglespeed, but adjusted to new orientation + speed = VectorNormalize( mover->s.apos.trDelta ); + VectorAdd( mover->s.apos.trDelta, diffAngles, mover->s.apos.trDelta ); + VectorNormalize( mover->s.apos.trDelta ); + VectorScale( mover->s.apos.trDelta, speed, mover->s.apos.trDelta ); + + mover->s.apos.trTime = level.time; + + //Tell them it was teleported this move + mover->s.eFlags |= EF_TELEPORT_BIT; + + // kill anything at the destination + //G_KillBox (mover); + //FIXME: call touch func instead of killbox? + + gi.linkentity (mover); +} + +void teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace) +{ + gentity_t *dest; + + if (!other->client) + return; + dest = G_PickTarget( self->target ); + if (!dest) { + gi.Printf ("Couldn't find teleporter destination\n"); + return; + } + + TeleportPlayer( other, dest->s.origin, dest->s.angles ); +} + +/*QUAK-D 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 (gentity_t *ent) +{ + gentity_t *trig; + + if (!ent->target) + { + gi.Printf ("teleporter without a target.\n"); + G_FreeEntity( ent ); + return; + } + + ent->s.modelindex = G_ModelIndex( "models/objects/dmspot.md3" ); + ent->s.clientNum = 1; +// ent->s.loopSound = G_SoundIndex("sound/world/amb10.wav"); + ent->contents = CONTENTS_SOLID; + + G_SetOrigin( ent, ent->s.origin ); + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); + + trig = G_Spawn (); + trig->e_TouchFunc = touchF_teleporter_touch; + trig->contents = CONTENTS_TRIGGER; + trig->target = ent->target; + trig->owner = ent; + G_SetOrigin( trig, ent->s.origin ); + VectorSet (trig->mins, -8, -8, 8); + VectorSet (trig->maxs, 8, 8, 24); + gi.linkentity (trig); + +} + +/*QUAK-D misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) - - NODRAW +Point teleporters at these. +*/ +void SP_misc_teleporter_dest( gentity_t *ent ) { + if ( ent->spawnflags & 4 ){ + return; + } + + G_SetOrigin( ent, ent->s.origin ); + + gi.linkentity (ent); +} + + +//=========================================================== + +/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) RMG SOLID +"model" arbitrary .md3 or .ase file to display +"_frame" "x" which frame from an animated md3 +"modelscale" "x" uniform scale +"modelscale_vec" "x y z" scale model in each axis +"_remap" "from to" remap a shader in this model +turns into BSP triangles - not solid by default (click SOLID or use _clipmodel shader) +*/ +void SP_misc_model( gentity_t *ent ) { + G_FreeEntity( ent ); +} + +/*QUAKED misc_model_static (1 0 0) (-16 -16 0) (16 16 16) +"model" arbitrary .md3 file to display +"_frame" "x" which frame from an animated md3 +"modelscale" "x" uniform scale +"modelscale_vec" "x y z" scale model in each axis +"zoffset" units to offset vertical culling position by, can be + negative or positive. This does not affect the actual + position of the model, only the culling position. Use + it for models with stupid origins that go below the + ground and whatnot. + +loaded as a model in the renderer - does not take up precious bsp space! +*/ +extern void CG_CreateMiscEntFromGent(gentity_t *ent, const vec3_t scale, float zOff); //cg_main.cpp +void SP_misc_model_static(gentity_t *ent) +{ + char *value; + float temp; + float zOff; + vec3_t scale; + + G_SpawnString("modelscale_vec", "1 1 1", &value); + sscanf( value, "%f %f %f", &scale[ 0 ], &scale[ 1 ], &scale[ 2 ] ); + + G_SpawnFloat( "modelscale", "0", &temp); + if (temp != 0.0f) + { + scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = temp; + } + + G_SpawnFloat( "zoffset", "0", &zOff); + + if (!ent->model) + { + Com_Error( ERR_DROP,"misc_model_static at %s with out a MODEL!\n", vtos(ent->s.origin) ); + } + //we can be horrible and cheat since this is SP! + CG_CreateMiscEntFromGent(ent, scale, zOff); + G_FreeEntity( ent ); +} + +//=========================================================== + +void setCamera ( gentity_t *ent ) +{ + vec3_t dir; + gentity_t *target = 0; + + // frame holds the rotate speed + if ( ent->owner->spawnflags & 1 ) + { + ent->s.frame = 25; + } + else if ( ent->owner->spawnflags & 2 ) + { + ent->s.frame = 75; + } + + // clientNum holds the rotate offset + ent->s.clientNum = ent->owner->s.clientNum; + + VectorCopy( ent->owner->s.origin, ent->s.origin2 ); + + // see if the portal_camera has a target + if (ent->owner->target) { + target = G_PickTarget( ent->owner->target ); + } + if ( target ) + { + VectorSubtract( target->s.origin, ent->owner->s.origin, dir ); + VectorNormalize( dir ); + } + else + { + G_SetMovedir( ent->owner->s.angles, dir ); + } + + ent->s.eventParm = DirToByte( dir ); +} + +void cycleCamera( gentity_t *self ) +{ + self->owner = G_Find( self->owner, FOFS(targetname), self->target ); + if ( self->owner == NULL ) + { + //Uh oh! Not targeted at any ents! Or reached end of list? Which is it? + //for now assume reached end of list and are cycling + self->owner = G_Find( self->owner, FOFS(targetname), self->target ); + if ( self->owner == NULL ) + {//still didn't find one + gi.Printf( "Couldn't find target for misc_portal_surface\n" ); + G_FreeEntity( self ); + return; + } + } + setCamera( self ); + + if ( self->e_ThinkFunc == thinkF_cycleCamera ) + { + if ( self->owner->wait > 0 ) + { + self->nextthink = level.time + self->owner->wait; + } + else + { + self->nextthink = level.time + self->wait; + } + } +} + +void misc_portal_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + cycleCamera( self ); +} + +void locateCamera( gentity_t *ent ) +{//FIXME: make this fadeout with distance from misc_camera_portal + + ent->owner = G_Find(NULL, FOFS(targetname), ent->target); + if ( !ent->owner ) + { + gi.Printf( "Couldn't find target for misc_portal_surface\n" ); + G_FreeEntity( ent ); + return; + } + + setCamera( ent ); + + if ( !ent->targetname ) + {//not targetted, so auto-cycle + if ( G_Find(ent->owner, FOFS(targetname), ent->target) != NULL ) + {//targeted at more than one thing + ent->e_ThinkFunc = thinkF_cycleCamera; + if ( ent->owner->wait > 0 ) + { + ent->nextthink = level.time + ent->owner->wait; + } + else + { + ent->nextthink = level.time + ent->wait; + } + } + } +} + +/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8) +The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted. +This must be within 64 world units of the surface! + +targetname - When used, cycles to the next misc_portal_camera it's targeted +wait - makes it auto-cycle between all cameras it's pointed at at intevervals of specified number of seconds. + + cameras will be cycled through in the order they were created on the map. +*/ +void SP_misc_portal_surface(gentity_t *ent) +{ + VectorClear( ent->mins ); + VectorClear( ent->maxs ); + gi.linkentity (ent); + + ent->svFlags = SVF_PORTAL; + ent->s.eType = ET_PORTAL; + ent->wait *= 1000; + + if ( !ent->target ) + {//mirror? + VectorCopy( ent->s.origin, ent->s.origin2 ); + } + else + { + ent->e_ThinkFunc = thinkF_locateCamera; + ent->nextthink = level.time + 100; + + if ( ent->targetname ) + { + ent->e_UseFunc = useF_misc_portal_use; + } + } +} + +/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate +The target for a misc_portal_surface. You can set either angles or target another entity (NOT an info_null) to determine the direction of view. +"roll" an angle modifier to orient the camera around the target vector; +*/ +void SP_misc_portal_camera(gentity_t *ent) { + float roll; + + VectorClear( ent->mins ); + VectorClear( ent->maxs ); + gi.linkentity (ent); + + G_SpawnFloat( "roll", "0", &roll ); + + ent->s.clientNum = roll/360.0 * 256; + ent->wait *= 1000; +} + +void G_SubBSPSpawnEntitiesFromString(const char *entityString, vec3_t posOffset, vec3_t angOffset); + +/*QUAKED misc_bsp (1 0 0) (-16 -16 -16) (16 16 16) +"bspmodel" arbitrary .bsp file to display +*/ +void SP_misc_bsp(gentity_t *ent) +{ + char temp[MAX_QPATH]; + char *out; + float newAngle; + int tempint; + + G_SpawnFloat( "angle", "0", &newAngle ); + if (newAngle != 0.0) + { + ent->s.angles[1] = newAngle; + } + // don't support rotation any other way + ent->s.angles[0] = 0.0; + ent->s.angles[2] = 0.0; + + G_SpawnString("bspmodel", "", &out); + + ent->s.eFlags = EF_PERMANENT; + + // Mainly for debugging + G_SpawnInt( "spacing", "0", &tempint); + ent->s.time2 = tempint; + G_SpawnInt( "flatten", "0", &tempint); + ent->s.time = tempint; + + Com_sprintf(temp, MAX_QPATH, "#%s", out); + gi.SetBrushModel( ent, temp ); // SV_SetBrushModel -- sets mins and maxs + G_BSPIndex(temp); + + level.mNumBSPInstances++; + Com_sprintf(temp, MAX_QPATH, "%d-", level.mNumBSPInstances); + VectorCopy(ent->s.origin, level.mOriginAdjust); + level.mRotationAdjust = ent->s.angles[1]; + level.mTargetAdjust = temp; + level.hasBspInstances = qtrue; + level.mBSPInstanceDepth++; + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->currentOrigin ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + VectorCopy( ent->s.angles, ent->currentAngles ); + + ent->s.eType = ET_MOVER; + + gi.linkentity (ent); + + const char *ents = gi.SetActiveSubBSP(ent->s.modelindex); + if (ents) + { + G_SubBSPSpawnEntitiesFromString(ents, ent->s.origin, ent->s.angles); + } + gi.SetActiveSubBSP(-1); + + level.mBSPInstanceDepth--; +} + +#define MAX_INSTANCE_TYPES 16 + +void AddSpawnField(char *field, char *value); + +/*QUAKED terrain (1.0 1.0 1.0) ? NOVEHDMG + +NOVEHDMG - don't damage vehicles upon impact with this terrain + +Terrain entity +It will stretch to the full height of the brush + +numPatches - integer number of patches to split the terrain brush into (default 200) +terxels - integer number of terxels on a patch side (default 4) (2 <= count <= 8) +seed - integer seed for random terrain generation (default 0) +textureScale - float scale of texture (default 0.005) +heightmap - name of heightmap data image to use, located in heightmaps/*.png. (must be PNG format) +terrainDef - defines how the game textures the terrain (file is base/ext_data/rmg/*.terrain - default is grassyhills) +instanceDef - defines which bsp instances appear +miscentDef - defines which client models spawn on the terrain (file is base/ext_data/rmg/*.miscents) +densityMap - how dense the client models are packed + +*/ +void SP_terrain(gentity_t *ent) +{ +#ifdef _XBOX + assert(0); +#else + char temp[MAX_INFO_STRING]; + char final[MAX_QPATH]; +// char seed[MAX_QPATH]; +// char missionType[MAX_QPATH]; +// char soundSet[MAX_QPATH]; + int shaderNum, i; + char *value; + int terrainID; + + //k, found a terrain, just set rmg to 1. + //This should always get set before RE_LoadWorldMap and all that is + //called which is all that matters. + //gi.cvar_set("RMG", "1"); + + VectorClear (ent->s.angles); + gi.SetBrushModel( ent, ent->model ); + + // Get the shader from the top of the brush +// shaderNum = gi.CM_GetShaderNum(s.modelindex); + shaderNum = 0; + + //rww - Why not do this all the time? Not like terrain entities are used when you don't want them to be terrain. +/* if (g_RMG->integer) + { + gi.Cvar_VariableStringBuffer("RMG_seed", seed, MAX_QPATH); + gi.Cvar_VariableStringBuffer("RMG_mission", missionType, MAX_QPATH); + + // gi.Cvar_VariableStringBuffer("RMG_soundset", soundSet, MAX_QPATH); + // gi.SetConfigstring(CS_AMBIENT_SOUNDSETS, soundSet ); + } +*/ + // Arbitrary (but sane) limits to the number of terxels +// if((mTerxels < MIN_TERXELS) || (mTerxels > MAX_TERXELS)) + { +// Com_printf("G_Terrain: terxels out of range - defaulting to 4\n"); +// mTerxels = 4; + } + + // Get info required for the common init + temp[0] = 0; + G_SpawnString("heightmap", "", &value); + Info_SetValueForKey(temp, "heightMap", value); + + G_SpawnString("numpatches", "400", &value); + Info_SetValueForKey(temp, "numPatches", va("%d", atoi(value))); + + G_SpawnString("terxels", "4", &value); + Info_SetValueForKey(temp, "terxels", va("%d", atoi(value))); + + //Info_SetValueForKey(temp, "seed", seed); + Info_SetValueForKey(temp, "minx", va("%f", ent->mins[0])); + Info_SetValueForKey(temp, "miny", va("%f", ent->mins[1])); + Info_SetValueForKey(temp, "minz", va("%f", ent->mins[2])); + Info_SetValueForKey(temp, "maxx", va("%f", ent->maxs[0])); + Info_SetValueForKey(temp, "maxy", va("%f", ent->maxs[1])); + Info_SetValueForKey(temp, "maxz", va("%f", ent->maxs[2])); + + Info_SetValueForKey(temp, "modelIndex", va("%d", ent->s.modelindex)); + + G_SpawnString("terraindef", "grassyhills", &value); + Info_SetValueForKey(temp, "terrainDef", value); + + G_SpawnString("instancedef", "", &value); + Info_SetValueForKey(temp, "instanceDef", value); + + G_SpawnString("miscentdef", "", &value); + Info_SetValueForKey(temp, "miscentDef", value); + + //Info_SetValueForKey(temp, "missionType", missionType); + + for(i = 0; i < MAX_INSTANCE_TYPES; i++) + { + gi.Cvar_VariableStringBuffer(va("RMG_instance%d", i), final, MAX_QPATH); + if(strlen(final)) + { + Info_SetValueForKey(temp, va("inst%d", i), final); + } + } + + // Set additional data required on the client only + G_SpawnString("densitymap", "", &value); + Info_SetValueForKey(temp, "densityMap", value); + + Info_SetValueForKey(temp, "shader", va("%d", shaderNum)); + G_SpawnString("texturescale", "0.005", &value); + Info_SetValueForKey(temp, "texturescale", va("%f", atof(value))); + + // Initialise the common aspects of the terrain + terrainID = gi.CM_RegisterTerrain(temp); +// SetCommon(common); + + Info_SetValueForKey(temp, "terrainId", va("%d", terrainID)); + + // Let the entity know if it is random generated or not +// SetIsRandom(common->GetIsRandom()); + + // Let the game remember everything +// level.landScapes[terrainID] = ent; + //rww - I'm not doing this. Because it didn't even appear to be used. Is it? + + // Send all the data down to the client + gi.SetConfigstring(CS_TERRAINS + terrainID, temp); + + // Make sure the contents are properly set + ent->contents = CONTENTS_TERRAIN; + ent->svFlags = SVF_NOCLIENT; + ent->s.eFlags = EF_PERMANENT; + ent->s.eType = ET_TERRAIN; + + // Hook into the world so physics will work + gi.linkentity(ent); + + // If running RMG then initialize the terrain and handle team skins + //rww - Why not do this all the time? Not like terrain entities are used when you don't want them to be terrain. +/* not using RMG + if ( g_RMG->integer ) + { + gi.RMG_Init(terrainID); + } +*/ +#endif // _XBOX +} + +//rww - Called by skyportal entities. This will check through entities and flag them +//as portal ents if they are in the same pvs as a skyportal entity and pass +//a direct point trace check between origins. I really wanted to use an eFlag for +//flagging portal entities, but too many entities like to reset their eFlags. +//Note that this was not part of the original wolf sky portal stuff. +void G_PortalifyEntities(gentity_t *ent) +{ + int i = 0; + gentity_t *scan = NULL; + + while (i < MAX_GENTITIES) + { + scan = &g_entities[i]; + + if (scan && scan->inuse && scan->s.number != ent->s.number && gi.inPVS(ent->s.origin, scan->currentOrigin)) + { + trace_t tr; + + gi.trace(&tr, ent->s.origin, vec3_origin, vec3_origin, scan->currentOrigin, ent->s.number, CONTENTS_SOLID, G2_NOCOLLIDE, 0); + + if (tr.fraction == 1.0 || (tr.entityNum == scan->s.number && tr.entityNum != ENTITYNUM_NONE && tr.entityNum != ENTITYNUM_WORLD)) + { + scan->s.isPortalEnt = qtrue; //he's flagged now + } + } + + i++; + } + + ent->e_ThinkFunc = thinkF_G_FreeEntity; //the portal entity is no longer needed because its information is stored in a config string. + ent->nextthink = level.time; +} + +/*QUAKED misc_skyportal (.6 .7 .7) (-8 -8 0) (8 8 16) +To have the portal sky fogged, enter any of the following values: +"fogcolor" (r g b) (values 0.0-1.0) +"fognear" distance from entity to start fogging +"fogfar" distance from entity that fog is opaque +rww - NOTE: fog doesn't work with these currently (at least not in this way). +Use a fog brush instead. +*/ +void SP_misc_skyportal (gentity_t *ent) +{ + vec3_t fogv; //----(SA) + int fogn; //----(SA) + int fogf; //----(SA) + int isfog = 0; // (SA) + + isfog += G_SpawnVector ("fogcolor", "0 0 0", fogv); + isfog += G_SpawnInt ("fognear", "0", &fogn); + isfog += G_SpawnInt ("fogfar", "300", &fogf); + + gi.SetConfigstring( CS_SKYBOXORG, va("%.2f %.2f %.2f %i %.2f %.2f %.2f %i %i", ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], isfog, fogv[0], fogv[1], fogv[2], fogn, fogf ) ); + + ent->e_ThinkFunc = thinkF_G_PortalifyEntities; + ent->nextthink = level.time + 1050; //give it some time first so that all other entities are spawned. +} + +extern qboolean G_ClearViewEntity( gentity_t *ent ); +extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity ); +extern void SP_fx_runner( gentity_t *ent ); +void camera_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) +{ + if ( player && player->client && player->client->ps.viewEntity == self->s.number ) + { + G_UseTargets2( self, player, self->target4 ); + G_ClearViewEntity( player ); + G_Sound( player, self->soundPos2 ); + } + G_UseTargets2( self, player, self->closetarget ); + //FIXME: explosion fx/sound + //leave sparks at origin- where base's pole is still at? + gentity_t *sparks = G_Spawn(); + if ( sparks ) + { + sparks->fxFile = "sparks/spark"; + sparks->delay = 100; + sparks->random = 500; + sparks->s.angles[0] = 180;//point down + VectorCopy( self->s.origin, sparks->s.origin ); + SP_fx_runner( sparks ); + } + + //bye! + self->takedamage = qfalse; + self->contents = 0; + self->s.eFlags |= EF_NODRAW; + self->s.modelindex = 0; +} + +void camera_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( !activator || !activator->client || activator->s.number ) + {//really only usable by the player + return; + } + self->painDebounceTime = level.time + (self->wait*1000);//FRAMETIME*5;//don't check for player buttons for 500 ms + + // FIXME: I guess we are allowing them to switch to a dead camera. Maybe we should conditionally do this though? + if ( /*self->health <= 0 ||*/ (player && player->client && player->client->ps.viewEntity == self->s.number) ) + {//I'm already viewEntity, or I'm destroyed, find next + gentity_t *next = NULL; + if ( self->target2 != NULL ) + { + next = G_Find( NULL, FOFS(targetname), self->target2 ); + } + if ( next ) + {//found another one + if ( !Q_stricmp( "misc_camera", next->classname ) ) + {//make sure it's another camera + camera_use( next, other, activator ); + } + } + else //if ( self->health > 0 ) + {//I was the last (only?) one, clear out the viewentity + G_UseTargets2( self, activator, self->target4 ); + G_ClearViewEntity( activator ); + G_Sound( activator, self->soundPos2 ); + } + } + else + {//set me as view entity + G_UseTargets2( self, activator, self->target3 ); + self->s.eFlags |= EF_NODRAW; + self->s.modelindex = 0; + G_SetViewEntity( activator, self ); + G_Sound( activator, self->soundPos1 ); + } +} + +void camera_aim( gentity_t *self ) +{ + self->nextthink = level.time + FRAMETIME; + if ( player && player->client && player->client->ps.viewEntity == self->s.number ) + {//I am the viewEntity + if ( player->client->usercmd.forwardmove || player->client->usercmd.rightmove || player->client->usercmd.upmove ) + {//player wants to back out of camera + G_UseTargets2( self, player, self->target4 ); + G_ClearViewEntity( player ); + G_Sound( player, self->soundPos2 ); + self->painDebounceTime = level.time + (self->wait*1000);//FRAMETIME*5;//don't check for player buttons for 500 ms + if ( player->client->usercmd.upmove > 0 ) + {//stop player from doing anything for a half second after + player->aimDebounceTime = level.time + 500; + } + } + else if ( self->painDebounceTime < level.time ) + {//check for use button + if ( (player->client->usercmd.buttons&BUTTON_USE) ) + {//player pressed use button, wants to cycle to next + camera_use( self, player, player ); + } + } + else + {//don't draw me when being looked through + self->s.eFlags |= EF_NODRAW; + self->s.modelindex = 0; + } + } + else if ( self->health > 0 ) + {//still alive, can draw me again + self->s.eFlags &= ~EF_NODRAW; + self->s.modelindex = self->s.modelindex3; + } + //update my aim + if ( self->target ) + { + gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target ); + if ( targ ) + { + vec3_t angles, dir; + VectorSubtract( targ->currentOrigin, self->currentOrigin, dir ); + vectoangles( dir, angles ); + //FIXME: if a G2 model, do a bone override..??? + VectorCopy( self->currentAngles, self->s.apos.trBase ); + + for( int i = 0; i < 3; i++ ) + { + angles[i] = AngleNormalize180( angles[i] ); + self->s.apos.trDelta[i] = AngleNormalize180( (angles[i]-self->currentAngles[i])*10 ); + } + //VectorSubtract( angles, self->currentAngles, self->s.apos.trDelta ); + //VectorScale( self->s.apos.trDelta, 10, self->s.apos.trDelta ); + self->s.apos.trTime = level.time; + self->s.apos.trDuration = FRAMETIME; + VectorCopy( angles, self->currentAngles ); + + if ( DistanceSquared( self->currentAngles, self->lastAngles ) > 0.01f ) // if it moved at all, start a loop sound? not exactly the "bestest" solution + { + self->s.loopSound = G_SoundIndex( "sound/movers/objects/cameramove_lp2" ); + } + else + { + self->s.loopSound = 0; // not moving so don't bother + } + + VectorCopy( self->currentAngles, self->lastAngles ); + //G_SetAngles( self, angles ); + } + } +} +/*QUAKED misc_camera (0 0 1) (-8 -8 -12) (8 8 16) VULNERABLE +A model in the world that can be used by the player to look through it's viewpoint + +There will be a video overlay instead of the regular HUD and the FOV will be wider + +VULNERABLE - allow camera to be destroyed + +"target" - camera will remain pointed at this entity (if it's a train or some other moving object, it will keep following it) +"target2" - when player is in this camera and hits the use button, it will cycle to this next camera (if no target2, returns to normal view ) +"target3" - thing to use when player enters this camera view +"target4" - thing to use when player leaves this camera view +"closetarget" - (sigh...) yet another target, fired this when it's destroyed +"wait" - how long to wait between being used (default 0.5) +*/ +void SP_misc_camera( gentity_t *self ) +{ + G_SpawnFloat( "wait", "0.5", &self->wait ); + + //FIXME: spawn base, too + gentity_t *base = G_Spawn(); + if ( base ) + { + base->s.modelindex = G_ModelIndex( "models/map_objects/kejim/impcam_base.md3" ); + VectorCopy( self->s.origin, base->s.origin ); + base->s.origin[2] += 16; + G_SetOrigin( base, base->s.origin ); + G_SetAngles( base, self->s.angles ); + gi.linkentity( base ); + } + self->s.modelindex3 = self->s.modelindex = G_ModelIndex( "models/map_objects/kejim/impcam.md3" ); + self->soundPos1 = G_SoundIndex( "sound/movers/camera_on.mp3" ); + self->soundPos2 = G_SoundIndex( "sound/movers/camera_off.mp3" ); + G_SoundIndex( "sound/movers/objects/cameramove_lp2" ); + + G_SetOrigin( self, self->s.origin ); + G_SetAngles( self, self->s.angles ); + self->s.apos.trType = TR_LINEAR_STOP;//TR_INTERPOLATE;// + self->alt_fire = qtrue; + VectorSet( self->mins, -8, -8, -12 ); + VectorSet( self->maxs, 8, 8, 0 ); + self->contents = CONTENTS_SOLID; + gi.linkentity( self ); + + self->fxID = G_EffectIndex( "sparks/spark" ); + + if ( self->spawnflags & 1 ) // VULNERABLE + { + self->takedamage = qtrue; + } + + self->health = 10; + self->e_DieFunc = dieF_camera_die; + + self->e_UseFunc = useF_camera_use; + + self->e_ThinkFunc = thinkF_camera_aim; + self->nextthink = level.time + START_TIME_LINK_ENTS; +} +/* +====================================================================== + + SHOOTERS + +====================================================================== +*/ + +void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ +/* vec3_t dir; + float deg; + vec3_t up, right; +*/ + G_ActivateBehavior(ent,BSET_USE); +/* + // see if we have a target + if ( ent->enemy ) { + VectorSubtract( ent->enemy->currentOrigin, ent->s.origin, dir ); + VectorNormalize( dir ); + } else { + VectorCopy( ent->movedir, dir ); + } + + // randomize a bit + PerpendicularVector( up, dir ); + CrossProduct( up, dir, right ); + + deg = crandom() * ent->random; + VectorMA( dir, deg, up, dir ); + + deg = crandom() * ent->random; + VectorMA( dir, deg, right, dir ); + + VectorNormalize( dir ); + + switch ( ent->s.weapon ) + { + case WP_GRENADE_LAUNCHER: + fire_grenade( ent, ent->s.origin, dir ); + break; + case WP_ROCKET_LAUNCHER: + fire_rocket( ent, ent->s.origin, dir ); + break; + case WP_PLASMAGUN: + fire_plasma( ent, ent->s.origin, dir ); + break; + } + + G_AddEvent( ent, EV_FIRE_WEAPON, 0 ); +*/ +} + +void InitShooter( gentity_t *ent, int weapon ) { + ent->e_UseFunc = useF_Use_Shooter; + ent->s.weapon = weapon; + + RegisterItem( FindItemForWeapon( (weapon_t) weapon ) ); + + G_SetMovedir( ent->s.angles, ent->movedir ); + + if ( !ent->random ) { + ent->random = 1.0; + } + ent->random = sin( M_PI * ent->random / 180 ); + // target might be a moving object, so we can't set movedir for it + if ( ent->target ) { + G_SetEnemy(ent, G_PickTarget( ent->target )); + } + gi.linkentity( ent ); +} + +/*QUAK-ED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16) +Fires at either the target or the current direction. +"random" the number of degrees of deviance from the taget. (1.0 default) +*/ +void SP_shooter_rocket( gentity_t *ent ) +{ +// InitShooter( ent, WP_TETRION_DISRUPTOR ); +} + +/*QUAK-ED shooter_plasma (1 0 0) (-16 -16 -16) (16 16 16) +Fires at either the target or the current direction. +"random" is the number of degrees of deviance from the taget. (1.0 default) +*/ +void SP_shooter_plasma( gentity_t *ent ) +{ + InitShooter( ent, WP_BRYAR_PISTOL); +} + +/*QUAK-ED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16) +Fires at either the target or the current direction. +"random" is the number of degrees of deviance from the taget. (1.0 default) +*/ +void SP_shooter_grenade( gentity_t *ent ) +{ +// InitShooter( ent, WP_GRENADE_LAUNCHER); +} + + +/*QUAKED object_cargo_barrel1 (1 0 0) (-16 -16 -16) (16 16 29) SMALLER KLINGON NO_SMOKE POWDERKEG +Cargo Barrel +if given a targetname, using it makes it explode + +SMALLER - (-8, -8, -16) (8, 8, 8) +KLINGON - klingon style barrel +NO_SMOKE - will not leave lingering smoke cloud when killed +POWDERKEG - wooden explosive barrel + +health default = 20 +splashDamage default = 100 +splashRadius default = 200 +*/ +void SP_object_cargo_barrel1(gentity_t *ent) +{ + if(ent->spawnflags & 8) + { + ent->s.modelindex = G_ModelIndex( "/models/mapobjects/cargo/barrel_wood2.md3" ); +// ent->sounds = G_SoundIndex("sound/weapons/explosions/explode3.wav"); + } + else if(ent->spawnflags & 2) + { + ent->s.modelindex = G_ModelIndex( "/models/mapobjects/scavenger/k_barrel.md3" ); +// ent->sounds = G_SoundIndex("sound/weapons/explosions/explode4.wav"); + } + else + { + ent->s.modelindex = G_ModelIndex( va("/models/mapobjects/cargo/barrel%i.md3", Q_irand( 0, 2 )) ); +// ent->sounds = G_SoundIndex("sound/weapons/explosions/explode1.wav"); + } + + ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE; + + if ( ent->spawnflags & 1 ) + { + VectorSet (ent->mins, -8, -8, -16); + VectorSet (ent->maxs, 8, 8, 8); + } + else + { + VectorSet (ent->mins, -16, -16, -16); + VectorSet (ent->maxs, 16, 16, 29); + } + + G_SetOrigin( ent, ent->s.origin ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + + if(!ent->health) + ent->health = 20; + + if(!ent->splashDamage) + ent->splashDamage = 100; + + if(!ent->splashRadius) + ent->splashRadius = 200; + + ent->takedamage = qtrue; + + ent->e_DieFunc = dieF_ExplodeDeath_Wait; + + if(ent->targetname) + ent->e_UseFunc = useF_GoExplodeDeath; + + gi.linkentity (ent); +} + + +/*QUAKED misc_dlight (0.2 0.8 0.2) (-4 -4 -4) (4 4 4) STARTOFF FADEON FADEOFF PULSE +Dynamic light, toggles on and off when used + +STARTOFF - Starts off +FADEON - Fades from 0 Radius to start Radius +FADEOFF - Fades from current Radius to 0 Radius before turning off +PULSE - This flag must be checked if you want it to fade/switch between start and final RGBA, otherwise it will just sit at startRGBA + +ownername - Will display the light at the origin of the entity with this targetname + +startRGBA - Red Green Blue Radius to start with - This MUST be set or your light won't do anything + +These next values are used only if you want to fade/switch between 2 values (PULSE flag on) +finalRGBA - Red Green Blue Radius to end with +speed - how long to take to fade from start to final and final to start. Also how long to fade on and off if appropriate flags are checked (seconds) +finaltime - how long to hold at final (seconds) +starttime - how long to hold at start (seconds) + +TODO: Add random to speed/radius? +*/ +void SP_misc_dlight(gentity_t *ent) +{ + G_SetOrigin( ent, ent->s.origin ); + gi.linkentity( ent ); + + ent->speed *= 1000; + ent->wait *= 1000; + ent->radius *= 1000; + + //FIXME: attach self to a train or something? + ent->e_UseFunc = useF_misc_dlight_use; + + ent->misc_dlight_active = qfalse; + ent->e_clThinkFunc = clThinkF_NULL; + + ent->s.eType = ET_GENERAL; + //Delay first think so we can find owner + if ( ent->ownername ) + { + ent->e_ThinkFunc = thinkF_misc_dlight_think; + ent->nextthink = level.time + START_TIME_LINK_ENTS; + } + + if ( !(ent->spawnflags & 1) ) + {//Turn myself on now + GEntity_UseFunc( ent, ent, ent ); + } +} + +void misc_dlight_use_old ( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + G_ActivateBehavior(ent,BSET_USE); + + if ( ent->misc_dlight_active ) + {//We're on, turn off + if ( ent->spawnflags & 4 ) + {//fade off + ent->pushDebounceTime = 3; + } + else + { + ent->misc_dlight_active = qfalse; + ent->e_clThinkFunc = clThinkF_NULL; + + ent->s.eType = ET_GENERAL; + ent->svFlags &= ~SVF_BROADCAST; + } + } + else + { + //Start at start regardless of when we were turned off + if ( ent->spawnflags & 4 ) + {//fade on + ent->pushDebounceTime = 2; + } + else + {//Just start on + ent->pushDebounceTime = 0; + } + ent->painDebounceTime = level.time; + + ent->misc_dlight_active = qtrue; + + ent->e_ThinkFunc = thinkF_misc_dlight_think; + ent->nextthink = level.time + FRAMETIME; + + ent->e_clThinkFunc = clThinkF_CG_DLightThink; + + ent->s.eType = ET_THINKER; + ent->svFlags |= SVF_BROADCAST;// Broadcast to all clients + } +} + +void misc_dlight_think ( gentity_t *ent ) +{ + //Stay Attached to owner + if ( ent->owner ) + { + G_SetOrigin( ent, ent->owner->currentOrigin ); + gi.linkentity( ent ); + } + else if ( ent->ownername ) + { + ent->owner = G_Find( NULL, FOFS(targetname), ent->ownername ); + ent->ownername = NULL; + } + ent->nextthink = level.time + FRAMETIME; +} + + +void station_pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +{ +// self->s.modelindex = G_ModelIndex("/models/mapobjects/stasis/plugin2_in.md3"); +// self->s.eFlags &= ~ EF_ANIM_ALLFAST; +// self->s.eFlags |= EF_ANIM_ONCE; +// gi.linkentity (self); + self->s.modelindex = self->s.modelindex2; + gi.linkentity (self); +} + +// -------------------------------------------------------------------- +// +// HEALTH/ARMOR plugin functions +// +// -------------------------------------------------------------------- + +void health_use( gentity_t *self, gentity_t *other, gentity_t *activator); +int ITM_AddArmor (gentity_t *ent, int count); +int ITM_AddHealth (gentity_t *ent, int count); + +void health_shutdown( gentity_t *self ) +{ + if (!(self->s.eFlags & EF_ANIM_ONCE)) + { + self->s.eFlags &= ~ EF_ANIM_ALLFAST; + self->s.eFlags |= EF_ANIM_ONCE; + + // Switch to and animate its used up model. + if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin2.md3")) + { + self->s.modelindex = self->s.modelindex2; + } + else if (!Q_stricmp(self->model,"models/mapobjects/borg/plugin2.md3")) + { + self->s.modelindex = self->s.modelindex2; + } + else if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin2_floor.md3")) + { + self->s.modelindex = self->s.modelindex2; +// G_Sound(self, G_SoundIndex("sound/ambience/stasis/shrinkage1.wav") ); + } + else if (!Q_stricmp(self->model,"models/mapobjects/forge/panels.md3")) + { + self->s.modelindex = self->s.modelindex2; + } + + gi.linkentity (self); + } +} + +void health_think( gentity_t *ent ) +{ + int dif; + + // He's dead, Jim. Don't give him health + if (ent->enemy->health<1) + { + ent->count = 0; + ent->e_ThinkFunc = thinkF_NULL; + } + + // Still has power to give + if (ent->count > 0) + { + // For every 3 points of health, you get 1 point of armor + // BUT!!! after health is filled up, you get the full energy going to armor + + dif = ent->enemy->client->ps.stats[STAT_MAX_HEALTH] - ent->enemy->health; + + if (dif > 3 ) + { + dif= 3; + } + else if (dif < 0) + { + dif= 0; + } + + if (dif > ent->count) // Can't give more than count + { + dif = ent->count; + } + + if ((ITM_AddHealth (ent->enemy,dif)) && (dif>0)) + { + ITM_AddArmor (ent->enemy,1); // 1 armor for every 3 health + + ent->count-=dif; + ent->nextthink = level.time + 10; + } + else // User has taken all health he can hold, see about giving it all to armor + { + dif = ent->enemy->client->ps.stats[STAT_MAX_HEALTH] - + ent->enemy->client->ps.stats[STAT_ARMOR]; + + if (dif > 3) + { + dif = 3; + } + else if (dif < 0) + { + dif= 0; + } + + if (ent->count < dif) // Can't give more than count + { + dif = ent->count; + } + + if ((!ITM_AddArmor(ent->enemy,dif)) || (dif<=0)) + { + ent->e_UseFunc = useF_health_use; + ent->e_ThinkFunc = thinkF_NULL; + } + else + { + ent->count-=dif; + ent->nextthink = level.time + 10; + } + } + } + + if (ent->count < 1) + { + health_shutdown(ent); + } +} + +void misc_model_useup( gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + self->s.eFlags &= ~ EF_ANIM_ALLFAST; + self->s.eFlags |= EF_ANIM_ONCE; + + // Switch to and animate its used up model. + self->s.modelindex = self->s.modelindex2; + + gi.linkentity (self); + + // Use target when used + if (self->spawnflags & 8) + { + G_UseTargets( self, activator ); + } + + self->e_UseFunc = useF_NULL; + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = -1; +} + +void health_use( gentity_t *self, gentity_t *other, gentity_t *activator) +{//FIXME: Heal entire team? Or only those that are undying...? + int dif; + int dif2; + int hold; + + G_ActivateBehavior(self,BSET_USE); + + if (self->e_ThinkFunc != thinkF_NULL) + { + self->e_ThinkFunc = thinkF_NULL; + } + else + { + + if (other->client) + { + // He's dead, Jim. Don't give him health + if (other->client->ps.stats[STAT_HEALTH]<1) + { + dif = 1; + self->count = 0; + } + else + { // Health + dif = other->client->ps.stats[STAT_MAX_HEALTH] - other->client->ps.stats[STAT_HEALTH]; + // Armor + dif2 = other->client->ps.stats[STAT_MAX_HEALTH] - other->client->ps.stats[STAT_ARMOR]; + hold = (dif2 - dif); + // For every 3 points of health, you get 1 point of armor + // BUT!!! after health is filled up, you get the full energy going to armor + if (hold>0) // Need more armor than health + { + // Calculate total amount of station energy needed. + + hold = dif / 3; // For every 3 points of health, you get 1 point of armor + dif2 -= hold; + dif2 += dif; + + dif = dif2; + } + } + } + else + { // Being triggered to be used up + dif = 1; + self->count = 0; + } + + // Does player already have full health and full armor? + if (dif > 0) + { +// G_Sound(self, G_SoundIndex("sound/player/suithealth.wav") ); + + if ((dif >= self->count) || (self->count<1)) // use it all up? + { + health_shutdown(self); + } + // Use target when used + if (self->spawnflags & 8) + { + G_UseTargets( self, activator ); + } + + self->e_UseFunc = useF_NULL; + self->enemy = other; + self->e_ThinkFunc = thinkF_health_think; + self->nextthink = level.time + 50; + } + else + { +// G_Sound(self, G_SoundIndex("sound/weapons/noammo.wav") ); + } + } +} + +// -------------------------------------------------------------------- +// +// AMMO plugin functions +// +// -------------------------------------------------------------------- +void ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator); +int Add_Ammo2 (gentity_t *ent, int ammoType, int count); + +void ammo_shutdown( gentity_t *self ) +{ + if (!(self->s.eFlags & EF_ANIM_ONCE)) + { + self->s.eFlags &= ~ EF_ANIM_ALLFAST; + self->s.eFlags |= EF_ANIM_ONCE; + + gi.linkentity (self); + } +} +void ammo_think( gentity_t *ent ) +{ + int dif; + + // Still has ammo to give + if (ent->count > 0 && ent->enemy ) + { + dif = ammoData[AMMO_BLASTER].max - ent->enemy->client->ps.ammo[AMMO_BLASTER]; + + if (dif > 2 ) + { + dif= 2; + } + else if (dif < 0) + { + dif= 0; + } + + if (ent->count < dif) // Can't give more than count + { + dif = ent->count; + } + + // Give player ammo + if (Add_Ammo2(ent->enemy,AMMO_BLASTER,dif) && (dif!=0)) + { + ent->count-=dif; + ent->nextthink = level.time + 10; + } + else // User has taken all ammo he can hold + { + ent->e_UseFunc = useF_ammo_use; + ent->e_ThinkFunc = thinkF_NULL; + } + } + + if (ent->count < 1) + { + ammo_shutdown(ent); + } +} + +//------------------------------------------------------------ +void ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator) +{ + int dif; + + G_ActivateBehavior(self,BSET_USE); + + if (self->e_ThinkFunc != thinkF_NULL) + { + if (self->e_UseFunc != useF_NULL) + { + self->e_ThinkFunc = thinkF_NULL; + } + } + else + { + if (other->client) + { + dif = ammoData[AMMO_BLASTER].max - other->client->ps.ammo[AMMO_BLASTER]; + } + else + { // Being triggered to be used up + dif = 1; + self->count = 0; + } + + // Does player already have full ammo? + if (dif > 0) + { +// G_Sound(self, G_SoundIndex("sound/player/suitenergy.wav") ); + + if ((dif >= self->count) || (self->count<1)) // use it all up? + { + ammo_shutdown(self); + } + } + else + { +// G_Sound(self, G_SoundIndex("sound/weapons/noammo.wav") ); + } + // Use target when used + if (self->spawnflags & 8) + { + G_UseTargets( self, activator ); + } + + self->e_UseFunc = useF_NULL; + G_SetEnemy( self, other ); + self->e_ThinkFunc = thinkF_ammo_think; + self->nextthink = level.time + 50; + } +} + +//------------------------------------------------------------ +void mega_ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + G_ActivateBehavior( self, BSET_USE ); + + // Use target when used + G_UseTargets( self, activator ); + + // first use, adjust the max ammo a person can hold for each type of ammo + ammoData[AMMO_BLASTER].max = 999; + ammoData[AMMO_POWERCELL].max = 999; + + // Set up our count with whatever the max difference will be + if ( other->client->ps.ammo[AMMO_POWERCELL] > other->client->ps.ammo[AMMO_BLASTER] ) + self->count = ammoData[AMMO_BLASTER].max - other->client->ps.ammo[AMMO_BLASTER]; + else + self->count = ammoData[AMMO_POWERCELL].max - other->client->ps.ammo[AMMO_POWERCELL]; + +// G_Sound( self, G_SoundIndex("sound/player/superenergy.wav") ); + + // Clear our usefunc, then think until our ammo is full + self->e_UseFunc = useF_NULL; + G_SetEnemy( self, other ); + self->e_ThinkFunc = thinkF_mega_ammo_think; + self->nextthink = level.time + 50; + + self->s.frame = 0; + self->s.eFlags |= EF_ANIM_ONCE; +} + +//------------------------------------------------------------ +void mega_ammo_think( gentity_t *self ) +{ + int ammo_add = 5; + + // If the middle model is done animating, and we haven't switched to the last model yet... + // chuck up the last model. + + if (!Q_stricmp(self->model,"models/mapobjects/forge/power_up_boss.md3")) // Because the normal forge_ammo model can use this too + { + if ( self->s.frame > 16 && self->s.modelindex != self->s.modelindex2 ) + self->s.modelindex = self->s.modelindex2; + } + + if ( self->enemy && self->count > 0 ) + { + // Add an equal ammount of ammo to each type + self->enemy->client->ps.ammo[AMMO_BLASTER] += ammo_add; + self->enemy->client->ps.ammo[AMMO_POWERCELL] += ammo_add; + + // Now cap to prevent overflows + if ( self->enemy->client->ps.ammo[AMMO_BLASTER] > ammoData[AMMO_BLASTER].max ) + self->enemy->client->ps.ammo[AMMO_BLASTER] = ammoData[AMMO_BLASTER].max; + + if ( self->enemy->client->ps.ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max ) + self->enemy->client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max; + + // Decrement the count given counter + self->count -= ammo_add; + + // If we've given all we should, prevent giving any more, even if they player is no longer full + if ( self->count <= 0 ) + { + self->count = 0; + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = -1; + } + else + self->nextthink = 20; + } +} + + +//------------------------------------------------------------ +void switch_models( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + // FIXME: need a sound here!! + if ( self->s.modelindex2 ) + self->s.modelindex = self->s.modelindex2; +} + +//------------------------------------------------------------ +void touch_ammo_crystal_tigger( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + if ( !other->client ) + return; + + // dead people can't pick things up + if ( other->health < 1 ) + return; + + // Only player can pick it up + if ( !other->s.number == 0 ) + { + return; + } + + if ( other->client->ps.ammo[ AMMO_POWERCELL ] >= ammoData[AMMO_POWERCELL].max ) + { + return; // can't hold any more + } + + // Add the ammo + other->client->ps.ammo[AMMO_POWERCELL] += self->owner->count; + + if ( other->client->ps.ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max ) + { + other->client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max; + } + + // Trigger once only + self->e_TouchFunc = touchF_NULL; + + // swap the model to the version without the crystal and ditch the infostring + self->owner->s.modelindex = self->owner->s.modelindex2; + + // play the normal pickup sound +// G_AddEvent( other, EV_ITEM_PICKUP, ITM_AMMO_CRYSTAL_BORG ); + + // fire item targets + G_UseTargets( self->owner, other ); +} + +//------------------------------------------------------------ +void spawn_ammo_crystal_trigger( gentity_t *ent ) +{ + gentity_t *other; + vec3_t mins, maxs; + + // Set the base bounds + VectorCopy( ent->s.origin, mins ); + VectorCopy( ent->s.origin, maxs ); + + // Now add an area of influence around the thing + for ( int i = 0; i < 3; i++ ) + { + maxs[i] += 48; + mins[i] -= 48; + } + + // create a trigger with this size + other = G_Spawn( ); + + VectorCopy( mins, other->mins ); + VectorCopy( maxs, other->maxs ); + + // set up the other bits that the engine needs to know + other->owner = ent; + other->contents = CONTENTS_TRIGGER; + other->e_TouchFunc = touchF_touch_ammo_crystal_tigger; + + gi.linkentity( other ); +} + +void misc_replicator_item_remove ( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + self->s.eFlags |= EF_NODRAW; + //self->contents = 0; + self->s.modelindex = 0; + self->e_UseFunc = useF_misc_replicator_item_spawn; + //FIXME: pickup sound? + if ( activator->client ) + { + activator->health += 5; + if ( activator->health > activator->client->ps.stats[STAT_MAX_HEALTH] ) // Past max health + { + activator->health = activator->client->ps.stats[STAT_MAX_HEALTH]; + } + } +} + +void misc_replicator_item_finish_spawn( gentity_t *self ) +{ + //self->contents = CONTENTS_ITEM; + //FIXME: blinks out for a couple frames when transporter effect is done? + self->e_UseFunc = useF_misc_replicator_item_remove; +} + +void misc_replicator_item_spawn ( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + switch ( Q_irand( 1, self->count ) ) + { + case 1: + self->s.modelindex = self->bounceCount; + break; + case 2: + self->s.modelindex = self->fly_sound_debounce_time; + break; + case 3: + self->s.modelindex = self->painDebounceTime; + break; + case 4: + self->s.modelindex = self->disconnectDebounceTime; + break; + case 5: + self->s.modelindex = self->attackDebounceTime; + break; + case 6://max + self->s.modelindex = self->pushDebounceTime; + break; + } + self->s.eFlags &= ~EF_NODRAW; + self->e_ThinkFunc = thinkF_misc_replicator_item_finish_spawn; + self->nextthink = level.time + 4000;//shorter? + self->e_UseFunc = useF_NULL; + + gentity_t *tent = G_TempEntity( self->currentOrigin, EV_REPLICATOR ); + tent->owner = self; +} + +/*QUAK-ED misc_replicator_item (0.2 0.8 0.2) (-4 -4 0) (4 4 8) +When used. this will "spawn" an entity with a random model from the ones provided below... + +Using it again removes the item as if it were picked up. + +model - first random model key +model2 - second random model key +model3 - third random model key +model4 - fourth random model key +model5 - fifth random model key +model6 - sixth random model key + +NOTE: do not skip one of these model names, start with the lowest and fill in each next highest one with a value. A gap will cause the item to not work correctly. + +NOTE: if you use an invalid model, it will still try to use it and show the NULL axis model (or nothing at all) + +targetname - how you refer to it for using it +*/ +void SP_misc_replicator_item ( gentity_t *self ) +{ + if ( self->model ) + { + self->bounceCount = G_ModelIndex( self->model ); + self->count++; + if ( self->model2 ) + { + self->fly_sound_debounce_time = G_ModelIndex( self->model2 ); + self->count++; + if ( self->target ) + { + self->painDebounceTime = G_ModelIndex( self->target ); + self->count++; + if ( self->target2 ) + { + self->disconnectDebounceTime = G_ModelIndex( self->target2 ); + self->count++; + if ( self->target3 ) + { + self->attackDebounceTime = G_ModelIndex( self->target3 ); + self->count++; + if ( self->target4 ) + { + self->pushDebounceTime = G_ModelIndex( self->target4 ); + self->count++; + } + } + } + } + } + } +// G_SoundIndex( "sound/movers/switches/replicator.wav" ); + self->e_UseFunc = useF_misc_replicator_item_spawn; + + self->s.eFlags |= EF_NODRAW; + //self->contents = 0; + + VectorSet( self->mins, -4, -4, 0 ); + VectorSet( self->maxs, 4, 4, 8 ); + G_SetOrigin( self, self->s.origin ); + G_SetAngles( self, self->s.angles ); + + gi.linkentity( self ); +} + +/*QUAKED misc_trip_mine (0.2 0.8 0.2) (-4 -4 -4) (4 4 4) START_ON BROADCAST START_OFF +Place in a map and point the angles at whatever surface you want it to attach to. + +START_ON - If you give it a targetname to make it toggle-able, but want it to start on, set this flag +BROADCAST - ONLY USE THIS IF YOU HAVE TO! causes the trip wire and loop sound to always happen, use this if the beam drops out in certain situations +START_OFF - If you give it a targetname, it will start completely off (laser AND base unit) until used. + +The trip mine will attach to that surface and fire it's beam away from the surface at an angle perpendicular to it. + +targetname - starts off, when used, turns on (toggles) + +FIXME: sometimes we want these to not be shootable... maybe just put them behind a force field? +*/ +extern void touchLaserTrap( gentity_t *ent, gentity_t *other, trace_t *trace ); +extern void CreateLaserTrap( gentity_t *laserTrap, vec3_t start, gentity_t *owner ); + +void misc_trip_mine_activate( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( self->e_ThinkFunc == thinkF_laserTrapThink ) + { + self->s.eFlags &= ~EF_FIRING; + self->s.loopSound = 0; + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = -1; + } + else + { + self->e_ThinkFunc = thinkF_laserTrapThink; + self->nextthink = level.time + FRAMETIME; + + self->s.eFlags &= ~EF_NODRAW; + self->contents = CONTENTS_SHOTCLIP;//CAN'T USE CONTENTS_SOLID because only ARCHITECTURE is contents_solid!!! + self->takedamage = qtrue; + } +} + +void SP_misc_trip_mine( gentity_t *self ) +{ + vec3_t forward, end; + trace_t trace; + + AngleVectors( self->s.angles, forward, NULL, NULL ); + VectorMA( self->s.origin, 128, forward, end ); + + gi.trace( &trace, self->s.origin, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT ); + + if ( trace.allsolid || trace.startsolid ) + { + Com_Error( ERR_DROP,"misc_trip_mine at %s in solid\n", vtos(self->s.origin) ); + G_FreeEntity( self ); + return; + } + if ( trace.fraction == 1.0 ) + { + Com_Error( ERR_DROP,"misc_trip_mine at %s pointed at no surface\n", vtos(self->s.origin) ); + G_FreeEntity( self ); + return; + } + + RegisterItem( FindItemForWeapon( WP_TRIP_MINE )); //precache the weapon + + self->count = 2/*TRIPWIRE_STYLE*/; + + vectoangles( trace.plane.normal, end ); + G_SetOrigin( self, trace.endpos ); + G_SetAngles( self, end ); + + CreateLaserTrap( self, trace.endpos, self ); + touchLaserTrap( self, self, &trace ); + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = -1; + + if ( !self->targetname || (self->spawnflags&1) ) + {//starts on + misc_trip_mine_activate( self, self, self ); + } + if ( self->targetname ) + { + self->e_UseFunc = useF_misc_trip_mine_activate; + } + + if (( self->spawnflags & 2 )) // broadcast...should only be used in very rare cases. could fix networking, perhaps, but james suggested this because it's easier + { + self->svFlags |= SVF_BROADCAST; + } + + // Whether to start completelly off or not. + if ( self->targetname && self->spawnflags & 4 ) + { + self->s.eFlags = EF_NODRAW; + self->contents = 0; + self->takedamage = qfalse; + } + + gi.linkentity( self ); +} + +/*QUAKED misc_maglock (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x x x x +Place facing a door (using the angle, not a targetname) and it will lock that door. Can only be destroyed by lightsaber and will automatically unlock the door it's attached to + +NOTE: place these half-way in the door to make it flush with the door's surface. + +"target" thing to use when destoryed (not doors - it automatically unlocks the door it was angled at) +"health" default is 10 +*/ +void maglock_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc ) +{ + //unlock our door if we're the last lock pointed at the door + if ( self->activator ) + { + self->activator->lockCount--; + if ( !self->activator->lockCount ) + { + self->activator->svFlags &= ~SVF_INACTIVE; + } + } + + //use targets + G_UseTargets( self, attacker ); + //die + WP_Explode( self ); +} +void SP_misc_maglock ( gentity_t *self ) +{ + //NOTE: May have to make these only work on doors that are either untargeted + // or are targeted by a trigger, not doors fired off by scripts, counters + // or other such things? + self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" ); + self->fxID = G_EffectIndex( "maglock/explosion" ); + + G_SetOrigin( self, self->s.origin ); + + self->e_ThinkFunc = thinkF_maglock_link; + //FIXME: for some reason, when you re-load a level, these fail to find their doors...? Random? Testing an additional 200ms after the START_TIME_FIND_LINKS + self->nextthink = level.time + START_TIME_FIND_LINKS+200;//START_TIME_FIND_LINKS;//because we need to let the doors link up and spawn their triggers first! +} +void maglock_link( gentity_t *self ) +{ + //find what we're supposed to be attached to + vec3_t forward, start, end; + trace_t trace; + + AngleVectors( self->s.angles, forward, NULL, NULL ); + VectorMA( self->s.origin, 128, forward, end ); + VectorMA( self->s.origin, -4, forward, start ); + + gi.trace( &trace, start, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT ); + + if ( trace.allsolid || trace.startsolid ) + { + Com_Error( ERR_DROP,"misc_maglock at %s in solid\n", vtos(self->s.origin) ); + G_FreeEntity( self ); + return; + } + if ( trace.fraction == 1.0 ) + { + self->e_ThinkFunc = thinkF_maglock_link; + self->nextthink = level.time + 100; + /* + Com_Error( ERR_DROP,"misc_maglock at %s pointed at no surface\n", vtos(self->s.origin) ); + G_FreeEntity( self ); + */ + return; + } + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( trace.entityNum >= ENTITYNUM_WORLD || !traceEnt || Q_stricmp( "func_door", traceEnt->classname ) ) + { + self->e_ThinkFunc = thinkF_maglock_link; + self->nextthink = level.time + 100; + //Com_Error( ERR_DROP,"misc_maglock at %s not pointed at a door\n", vtos(self->s.origin) ); + //G_FreeEntity( self ); + return; + } + + //check the traceEnt, make sure it's a door and give it a lockCount and deactivate it + //find the trigger for the door + self->activator = G_FindDoorTrigger( traceEnt ); + if ( !self->activator ) + { + self->activator = traceEnt; + } + self->activator->lockCount++; + self->activator->svFlags |= SVF_INACTIVE; + + //now position and orient it + vectoangles( trace.plane.normal, end ); + G_SetOrigin( self, trace.endpos ); + G_SetAngles( self, end ); + + //make it hittable + //FIXME: if rotated/inclined this bbox may be off... but okay if we're a ghoul model? + //self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" ); + VectorSet( self->mins, -8, -8, -8 ); + VectorSet( self->maxs, 8, 8, 8 ); + self->contents = CONTENTS_CORPSE; + + //make it destroyable + self->flags |= FL_SHIELDED;//only damagable by lightsabers + self->takedamage = qtrue; + self->health = 10; + self->e_DieFunc = dieF_maglock_die; + //self->fxID = G_EffectIndex( "maglock/explosion" ); + + gi.linkentity( self ); +} + +/* +================ +EnergyShieldStationSettings +================ +*/ +void EnergyShieldStationSettings(gentity_t *ent) +{ + G_SpawnInt( "count", "0", &ent->count ); + + if (!ent->count) + { + switch (g_spskill->integer) + { + case 0: // EASY + ent->count = 100; + break; + case 1: // MEDIUM + ent->count = 75; + break; + default : + case 2: // HARD + ent->count = 50; + break; + } + } +} + +/* +================ +shield_power_converter_use +================ +*/ +void shield_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator) +{ + int dif,add; + + if ( !activator || activator->s.number != 0 ) + { + //only the player gets to use these + return; + } + + G_ActivateBehavior( self,BSET_USE ); + + if ( self->setTime < level.time ) + { + self->setTime = level.time + 100; + + dif = 100 - activator->client->ps.stats[STAT_ARMOR]; // FIXME: define for max armor? + + if ( dif > 0 && self->count ) // Already at full armor?..and do I even have anything to give + { + if ( dif > MAX_AMMO_GIVE ) + { + add = MAX_AMMO_GIVE; + } + else + { + add = dif; + } + + if ( self->count < add ) + { + add = self->count; + } + + self->count -= add; + + activator->client->ps.stats[STAT_ARMOR] += add; + + self->s.loopSound = G_SoundIndex( "sound/interface/shieldcon_run.wav" ); + } + + if ( self->count <= 0 ) + { + // play empty sound + self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much + G_Sound( self, G_SoundIndex( "sound/interface/shieldcon_empty.mp3" )); + self->s.loopSound = 0; + + if ( self->s.eFlags & EF_SHADER_ANIM ) + { + self->s.frame = 1; + } + } + else if ( activator->client->ps.stats[STAT_ARMOR] >= 100 ) // FIXME: define for max + { + // play full sound + G_Sound( self, G_SoundIndex( "sound/interface/shieldcon_done.mp3" )); + self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much + self->s.loopSound = 0; + } + } + + if ( self->s.loopSound ) + { + // we will have to shut of the loop sound, so I guess try and do it intelligently...NOTE: this could get completely stomped every time through the loop + // this is fine, since it just controls shutting off the sound when there are situations that could start the sound but not shut it off + self->e_ThinkFunc = thinkF_poll_converter; + self->nextthink = level.time + 500; + } + else + { + // sound is already off, so we don't need to "think" about it. + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = 0; + } + + if ( activator->client->ps.stats[STAT_ARMOR] > 0 ) + { + activator->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE; + } +} + + +/*QUAKED misc_model_shield_power_converter (1 0 0) (-16 -16 -16) (16 16 16) x x x USETARGET +model="models/items/psd_big.md3" +Gives shield energy when used. + +USETARGET - when used it fires off target + +"health" - how much health the model has - default 60 (zero makes non-breakable) +"targetname" - dies and displays damagemodel when used, if any (if not, removes itself) +"target" - what to use when it dies +"paintarget" - target to fire when hit (but not destroyed) +"count" - the amount of ammo given when used (default 100) +*/ +//------------------------------------------------------------ +void SP_misc_model_shield_power_converter( gentity_t *ent ) +{ + SetMiscModelDefaults( ent, useF_shield_power_converter_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL ); + + ent->takedamage = qfalse; + + EnergyShieldStationSettings(ent); + + G_SoundIndex("sound/interface/shieldcon_run.wav"); + G_SoundIndex("sound/interface/shieldcon_done.mp3"); + G_SoundIndex("sound/interface/shieldcon_empty.mp3"); + + ent->s.modelindex = G_ModelIndex("models/items/psd_big.md3"); // Precache model + ent->s.modelindex2 = G_ModelIndex("models/items/psd_big.md3"); // Precache model +} + +void bomb_planted_use( gentity_t *self, gentity_t *other, gentity_t *activator) +{ + if ( self->count == 2 ) + { + self->s.eFlags &= ~EF_NODRAW; + self->contents = CONTENTS_SOLID; + self->count = 1; + self->s.loopSound = self->noise_index; + } + else if ( self->count == 1 ) + { + self->count = 0; + // play disarm sound + self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much + G_Sound( self, G_SoundIndex("sound/weapons/overchargeend")); + self->s.loopSound = 0; + + // this pauses the shader on one frame (more or less) + self->s.eFlags |= EF_DISABLE_SHADER_ANIM; + + // this starts the animation for the model + self->s.eFlags |= EF_ANIM_ONCE; + self->s.frame = 0; + + //use targets + G_UseTargets( self, activator ); + } +} + +/*QUAKED misc_model_bomb_planted (1 0 0) (-16 -16 0) (16 16 70) x x x USETARGET +model="models/map_objects/factory/bomb_new_deact.md3" +Planted by evil men for evil purposes. + +"health" - please don't shoot the thermonuclear device +"forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level... +*/ +//------------------------------------------------------------ +void SP_misc_model_bomb_planted( gentity_t *ent ) +{ + VectorSet( ent->mins, -16, -16, 0 ); + VectorSet( ent->maxs, 16, 16, 70 ); + + SetMiscModelDefaults( ent, useF_bomb_planted_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL ); + + ent->takedamage = qfalse; + + G_SoundIndex("sound/weapons/overchargeend"); + + ent->s.modelindex = G_ModelIndex("models/map_objects/factory/bomb_new_deact.md3"); // Precache model + ent->s.modelindex2 = G_ModelIndex("models/map_objects/factory/bomb_new_deact.md3"); // Precache model + ent->noise_index = G_SoundIndex("sound/interface/ammocon_run"); + ent->s.loopSound = ent->noise_index; + //ent->s.eFlags |= EF_SHADER_ANIM; + //ent->s.frame = ent->startFrame = 0; + ent->count = 1; + + // If we have a targetname, we're are invisible until we are spawned in by being used. + if ( ent->targetname ) + { + ent->s.eFlags = EF_NODRAW; + ent->contents = 0; + ent->count = 2; + ent->s.loopSound = 0; + } + + int forceVisible = 0; + G_SpawnInt( "forcevisible", "0", &forceVisible ); + if ( forceVisible ) + {//can see these through walls with force sight, so must be broadcast + //ent->svFlags |= SVF_BROADCAST; + ent->s.eFlags |= EF_FORCE_VISIBLE; + } +} + +void beacon_deploy( gentity_t *ent ) +{ + ent->e_ThinkFunc = thinkF_beacon_think; + ent->nextthink = level.time + FRAMETIME * 0.5f; + + ent->s.frame = 0; + ent->startFrame = 0; + ent->endFrame = 30; + ent->loopAnim = qfalse; +} + +void beacon_think( gentity_t *ent ) +{ + ent->nextthink = level.time + FRAMETIME * 0.5f; + + // Deploy animation complete? Stop thinking and just animate signal forever. + if ( ent->s.frame == 30 ) + { + ent->e_ThinkFunc = thinkF_NULL; + ent->nextthink = -1; + + ent->startFrame = 31; + ent->endFrame = 60; + ent->loopAnim = qtrue; + + ent->s.loopSound = ent->noise_index; + } +} + +void beacon_use( gentity_t *self, gentity_t *other, gentity_t *activator) +{ + // Every time it's used it will be toggled on or off. + if ( self->count == 0 ) + { + self->s.eFlags &= ~EF_NODRAW; + self->contents = CONTENTS_SOLID; + self->count = 1; + self->svFlags = SVF_ANIMATING; + beacon_deploy( self ); + } + else + { + self->s.eFlags = EF_NODRAW; + self->contents = 0; + self->count = 0; + self->s.loopSound = 0; + self->svFlags = 0; + } +} + +/*QUAKED misc_model_beacon (1 0 0) (-16 -16 0) (16 16 24) x x x +model="models/map_objects/wedge/beacon.md3" +An animating beacon model. + +"forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level... +*/ +//------------------------------------------------------------ +void SP_misc_model_beacon( gentity_t *ent ) +{ + VectorSet( ent->mins, -16, -16, 0 ); + VectorSet( ent->maxs, 16, 16, 24 ); + + SetMiscModelDefaults( ent, useF_beacon_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL ); + + ent->takedamage = qfalse; + + //G_SoundIndex("sound/weapons/overchargeend"); + + ent->s.modelindex = G_ModelIndex("models/map_objects/wedge/beacon.md3"); // Precache model + ent->s.modelindex2 = G_ModelIndex("models/map_objects/wedge/beacon.md3"); // Precache model + ent->noise_index = G_SoundIndex("sound/interface/ammocon_run"); + + // If we have a targetname, we're are invisible until we are spawned in by being used. + if ( ent->targetname ) + { + ent->s.eFlags = EF_NODRAW; + ent->contents = 0; + ent->s.loopSound = 0; + ent->count = 0; + } + else + { + ent->count = 1; + beacon_deploy( ent ); + } + + int forceVisible = 0; + G_SpawnInt( "forcevisible", "0", &forceVisible ); + if ( forceVisible ) + {//can see these through walls with force sight, so must be broadcast + //ent->svFlags |= SVF_BROADCAST; + ent->s.eFlags |= EF_FORCE_VISIBLE; + } +} + +/*QUAKED misc_shield_floor_unit (1 0 0) (-16 -16 0) (16 16 32) x x x USETARGET +model="models/items/a_shield_converter.md3" +Gives shield energy when used. + +USETARGET - when used it fires off target + +"health" - how much health the model has - default 60 (zero makes non-breakable) +"targetname" - dies and displays damagemodel when used, if any (if not, removes itself) +"target" - what to use when it dies +"paintarget" - target to fire when hit (but not destroyed) +"count" - the amount of ammo given when used (default 100) +*/ +//------------------------------------------------------------ +void SP_misc_shield_floor_unit( gentity_t *ent ) +{ + VectorSet( ent->mins, -16, -16, 0 ); + VectorSet( ent->maxs, 16, 16, 32 ); + + SetMiscModelDefaults( ent, useF_shield_power_converter_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL ); + + ent->takedamage = qfalse; + + EnergyShieldStationSettings(ent); + + G_SoundIndex("sound/interface/shieldcon_run.wav"); + G_SoundIndex("sound/interface/shieldcon_done.mp3"); + G_SoundIndex("sound/interface/shieldcon_empty.mp3"); + + ent->s.modelindex = G_ModelIndex( "models/items/a_shield_converter.md3" ); // Precache model + ent->s.eFlags |= EF_SHADER_ANIM; +} + + +/* +================ +EnergyAmmoShieldStationSettings +================ +*/ +void EnergyAmmoStationSettings(gentity_t *ent) +{ + G_SpawnInt( "count", "0", &ent->count ); + + if (!ent->count) + { + switch (g_spskill->integer) + { + case 0: // EASY + ent->count = 100; + break; + case 1: // MEDIUM + ent->count = 75; + break; + default : + case 2: // HARD + ent->count = 50; + break; + } + } +} + +// There has to be an easier way to turn off the looping sound...but +// it's the night before beta and my brain is fried +//------------------------------------------------------------------ +void poll_converter( gentity_t *self ) +{ + self->s.loopSound = 0; + self->nextthink = 0; + self->e_ThinkFunc = thinkF_NULL; +} + +/* +================ +ammo_power_converter_use +================ +*/ +void ammo_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator) +{ + int add; + int difBlaster,difPowerCell,difMetalBolts; + playerState_t *ps; + + if ( !activator || activator->s.number != 0 ) + {//only the player gets to use these + return; + } + + G_ActivateBehavior( self, BSET_USE ); + + ps = &activator->client->ps; + + if ( self->setTime < level.time ) + { + difBlaster = ammoData[AMMO_BLASTER].max - ps->ammo[AMMO_BLASTER]; + difPowerCell = ammoData[AMMO_POWERCELL].max - ps->ammo[AMMO_POWERCELL]; + difMetalBolts = ammoData[AMMO_METAL_BOLTS].max - ps->ammo[AMMO_METAL_BOLTS]; + + // Has it got any power left...and can we even use any of it? + if ( self->count && ( difBlaster > 0 || difPowerCell > 0 || difMetalBolts > 0 )) + { + // at least one of the ammo types could stand to take on a bit more ammo + self->setTime = level.time + 100; + self->s.loopSound = G_SoundIndex( "sound/interface/ammocon_run.wav" ); + + // dole out ammo in little packets + if ( self->count > MAX_AMMO_GIVE ) + { + add = MAX_AMMO_GIVE; + } + else if ( self->count < 0 ) + { + add = 0; + } + else + { + add = self->count; + } + + // all weapons fill at same rate... + ps->ammo[AMMO_BLASTER] += add; + ps->ammo[AMMO_POWERCELL] += add; + ps->ammo[AMMO_METAL_BOLTS] += add; + + // ...then get clamped to max + if ( ps->ammo[AMMO_BLASTER] > ammoData[AMMO_BLASTER].max ) + { + ps->ammo[AMMO_BLASTER] = ammoData[AMMO_BLASTER].max; + } + + if ( ps->ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max ) + { + ps->ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max; + } + + if ( ps->ammo[AMMO_METAL_BOLTS] > ammoData[AMMO_METAL_BOLTS].max ) + { + ps->ammo[AMMO_METAL_BOLTS] = ammoData[AMMO_METAL_BOLTS].max; + } + + self->count -= add; + } + + if ( self->count <= 0 ) + { + // play empty sound + self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much + G_Sound( self, G_SoundIndex( "sound/interface/ammocon_empty.mp3" )); + self->s.loopSound = 0; + + if ( self->s.eFlags & EF_SHADER_ANIM ) + { + self->s.frame = 1; + } + } + else if ( ps->ammo[AMMO_BLASTER] >= ammoData[AMMO_BLASTER].max + && ps->ammo[AMMO_POWERCELL] >= ammoData[AMMO_POWERCELL].max + && ps->ammo[AMMO_METAL_BOLTS] >= ammoData[AMMO_METAL_BOLTS].max ) + { + // play full sound + G_Sound( self, G_SoundIndex( "sound/interface/ammocon_done.wav" )); + self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much + self->s.loopSound = 0; + } + } + + + if ( self->s.loopSound ) + { + // we will have to shut of the loop sound, so I guess try and do it intelligently...NOTE: this could get completely stomped every time through the loop + // this is fine, since it just controls shutting off the sound when there are situations that could start the sound but not shut it off + self->e_ThinkFunc = thinkF_poll_converter; + self->nextthink = level.time + 500; + } + else + { + // sound is already off, so we don't need to "think" about it. + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = 0; + } +} + +/*QUAKED misc_model_ammo_power_converter (1 0 0) (-16 -16 -16) (16 16 16) x x x USETARGET +model="models/items/power_converter.md3" +Gives ammo energy when used. + +USETARGET - when used it fires off target + +"health" - how much health the model has - default 60 (zero makes non-breakable) +"targetname" - dies and displays damagemodel when used, if any (if not, removes itself) +"target" - what to use when it dies +"paintarget" - target to fire when hit (but not destroyed) +"count" - the amount of ammo given when used (default 100) +*/ +//------------------------------------------------------------ +void SP_misc_model_ammo_power_converter( gentity_t *ent ) +{ + SetMiscModelDefaults( ent, useF_ammo_power_converter_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL ); + + ent->takedamage = qfalse; + + EnergyAmmoStationSettings(ent); + + G_SoundIndex("sound/interface/ammocon_run.wav"); + G_SoundIndex("sound/interface/ammocon_done.mp3"); + G_SoundIndex("sound/interface/ammocon_empty.mp3"); + + ent->s.modelindex = G_ModelIndex("models/items/power_converter.md3"); // Precache model + ent->s.modelindex2 = G_ModelIndex("models/items/power_converter.md3"); // Precache model +} + +/*QUAKED misc_ammo_floor_unit (1 0 0) (-16 -16 0) (16 16 32) x x x USETARGET +model="models/items/a_pwr_converter.md3" +Gives ammo energy when used. + +USETARGET - when used it fires off target + +"health" - how much health the model has - default 60 (zero makes non-breakable) +"targetname" - dies and displays damagemodel when used, if any (if not, removes itself) +"target" - what to use when it dies +"paintarget" - target to fire when hit (but not destroyed) +"count" - the amount of ammo given when used (default 100) +*/ +//------------------------------------------------------------ +void SP_misc_ammo_floor_unit( gentity_t *ent ) +{ + VectorSet( ent->mins, -16, -16, 0 ); + VectorSet( ent->maxs, 16, 16, 32 ); + + SetMiscModelDefaults( ent, useF_ammo_power_converter_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL ); + + ent->takedamage = qfalse; + + EnergyAmmoStationSettings(ent); + + G_SoundIndex("sound/interface/ammocon_run.wav"); + G_SoundIndex("sound/interface/ammocon_done.mp3"); + G_SoundIndex("sound/interface/ammocon_empty.mp3"); + + ent->s.modelindex = G_ModelIndex("models/items/a_pwr_converter.md3"); // Precache model + ent->s.eFlags |= EF_SHADER_ANIM; +} + + +/* +================ +welder_think +================ +*/ +void welder_think( gentity_t *self ) +{ + self->nextthink = level.time + 200; + vec3_t org, + dir; + mdxaBone_t boltMatrix; + + if( self->svFlags & SVF_INACTIVE ) + { + return; + } + + int newBolt; + + // could alternate between the two... or make it random... ? + newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash" ); +// newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash01" ); + + if ( newBolt != -1 ) + { + G_Sound( self, self->noise_index ); + // G_PlayEffect( "blueWeldSparks", self->playerModel, newBolt, self->s.number); + // The welder gets rotated around a lot, and since the origin is offset by 352 I have to make this super expensive call to position the hurt... + gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, newBolt, + &boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time), + NULL, self->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + VectorSubtract( self->currentOrigin, org, dir ); + VectorNormalize( dir ); + // we want the welder effect to face inwards.... + G_PlayEffect( "sparks/blueWeldSparks", org, dir ); + G_RadiusDamage( org, self, 10, 45, self, MOD_UNKNOWN ); + } + +} + +/* +================ +welder_use +================ +*/ +void welder_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + // Toggle on and off + if( self->spawnflags & 1 ) + { + self->nextthink = level.time + FRAMETIME; + } + else + { + self->nextthink = -1; + } + self->spawnflags = (self->spawnflags ^ 1); + +} + +/*QUAKED misc_model_welder (1 0 0) (-16 -16 -16) (16 16 16) START_OFF +model="models/map_objects/cairn/welder.md3" +When 'on' emits sparks from it's welding points + +START_OFF - welder starts off, using it toggles it on/off + +*/ +//------------------------------------------------------------ +void SP_misc_model_welder( gentity_t *ent ) +{ + VectorSet( ent->mins, 336, -16, 0 ); + VectorSet( ent->maxs, 368, 16, 32 ); + + SetMiscModelDefaults( ent, useF_welder_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL ); + + ent->takedamage = qfalse; + ent->contents = 0; + G_EffectIndex( "sparks/blueWeldSparks" ); + ent->noise_index = G_SoundIndex( "sound/movers/objects/welding.wav" ); + + ent->s.modelindex = G_ModelIndex( "models/map_objects/cairn/welder.glm" ); +// ent->s.modelindex2 = G_ModelIndex( "models/map_objects/cairn/welder.md3" ); + ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/map_objects/cairn/welder.glm", ent->s.modelindex ); + ent->s.radius = 400.0f;// the origin of the welder is offset by approximately 352, so we need the big radius + + ent->e_ThinkFunc = thinkF_welder_think; + + ent->nextthink = level.time + 1000; + if( ent->spawnflags & 1 ) + { + ent->nextthink = -1; + } +} + + +/* +================ +jabba_cam_use +================ +*/ +void jabba_cam_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( self->spawnflags & 1 ) + { + self->spawnflags &= ~1; + gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 15, 0, BONE_ANIM_OVERRIDE_FREEZE, -1.5f, (cg.time?cg.time:level.time), -1, 0 ); + + } + else + { + self->spawnflags |= 1; + gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 0, 15, BONE_ANIM_OVERRIDE_FREEZE, 1.5f, (cg.time?cg.time:level.time), -1, 0 ); + } +} + +/*QUAKED misc_model_jabba_cam (1 0 0) ( 0 -8 0) (60 8 16) EXTENDED +model="models/map_objects/nar_shaddar/jabacam.md3" + +The eye camera that popped out of Jabba's front door + + EXTENDED - Starts in the extended position + + targetname - Toggles it on/off + +*/ +//----------------------------------------------------- +void SP_misc_model_jabba_cam( gentity_t *ent ) +//----------------------------------------------------- +{ + + VectorSet( ent->mins, -60.0f, -8.0f, 0.0f ); + VectorSet( ent->maxs, 60.0f, 8.0f, 16.0f ); + + SetMiscModelDefaults( ent, useF_jabba_cam_use, "4", 0, NULL, qfalse, NULL ); + G_SetAngles( ent, ent->s.angles ); + + ent->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/jabacam/jabacam.glm" ); + ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/map_objects/nar_shaddar/jabacam/jabacam.glm", ent->s.modelindex ); + ent->s.radius = 150.0f; //...... + VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f ); + + ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue ); + + ent->e_UseFunc = useF_jabba_cam_use; + + ent->takedamage = qfalse; + + // start extended.. + if ( ent->spawnflags & 1 ) + { + gi.G2API_SetBoneAnimIndex( &ent->ghoul2[ent->playerModel], ent->rootBone, 0, 15, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time ); + } + + gi.linkentity( ent ); +} + +//------------------------------------------------------------------------ +void misc_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + misc_model_breakable_die( self, other, activator, 100, MOD_UNKNOWN ); +} + +/*QUAKED misc_exploding_crate (1 0 0.25) (-24 -24 0) (24 24 64) +model="models/map_objects/nar_shaddar/crate_xplode.md3" +Basic exploding crate + +"health" - how much health the model has - default 40 (zero makes non-breakable) + +"splashRadius" - radius to do damage in - default 128 +"splashDamage" - amount of damage to do when it explodes - default 50 + +"targetname" - auto-explodes +"target" - what to use when it dies + +*/ +//------------------------------------------------------------ +void SP_misc_exploding_crate( gentity_t *ent ) +{ + G_SpawnInt( "health", "40", &ent->health ); + G_SpawnInt( "splashRadius", "128", &ent->splashRadius ); + G_SpawnInt( "splashDamage", "50", &ent->splashDamage ); + + ent->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/crate_xplode.md3" ); + G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"); + G_EffectIndex( "chunks/metalexplode" ); + + VectorSet( ent->mins, -24, -24, 0 ); + VectorSet( ent->maxs, 24, 24, 64 ); + + ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//CONTENTS_SOLID; + ent->takedamage = qtrue; + + G_SetOrigin( ent, ent->s.origin ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + gi.linkentity (ent); + + if ( ent->targetname ) + { + ent->e_UseFunc = useF_misc_use; + } + + ent->material = MAT_CRATE1; + ent->e_DieFunc = dieF_misc_model_breakable_die;//ExplodeDeath; +} + +/*QUAKED misc_gas_tank (1 0 0.25) (-4 -4 0) (4 4 40) +model="models/map_objects/imp_mine/tank.md3" +Basic exploding oxygen tank + +"health" - how much health the model has - default 20 (zero makes non-breakable) + +"splashRadius" - radius to do damage in - default 48 +"splashDamage" - amount of damage to do when it explodes - default 32 + +"targetname" - auto-explodes +"target" - what to use when it dies + +*/ + +void gas_random_jet( gentity_t *self ) +{ + vec3_t pt; + + VectorCopy( self->currentOrigin, pt ); + pt[2] += 50; + + G_PlayEffect( "env/mini_gasjet", pt ); + + self->nextthink = level.time + random() * 16000 + 12000; // do this rarely +} + +//------------------------------------------------------------ +void GasBurst( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc ) +{ + vec3_t pt; + + VectorCopy( self->currentOrigin, pt ); + pt[2] += 46; + + G_PlayEffect( "env/mini_flamejet", pt ); + + // do some damage to anything that may be standing on top of it when it bursts into flame + pt[2] += 32; + G_RadiusDamage( pt, self, 32, 32, self, MOD_UNKNOWN ); + + // only get one burst + self->e_PainFunc = painF_NULL; +} + +//------------------------------------------------------------ +void SP_misc_gas_tank( gentity_t *ent ) +{ + G_SpawnInt( "health", "20", &ent->health ); + G_SpawnInt( "splashRadius", "48", &ent->splashRadius ); + G_SpawnInt( "splashDamage", "32", &ent->splashDamage ); + + ent->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/tank.md3" ); + G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"); + G_EffectIndex( "chunks/metalexplode" ); + G_EffectIndex( "env/mini_flamejet" ); + G_EffectIndex( "env/mini_gasjet" ); + + VectorSet( ent->mins, -4, -4, 0 ); + VectorSet( ent->maxs, 4, 4, 40 ); + + ent->contents = CONTENTS_SOLID; + ent->takedamage = qtrue; + + G_SetOrigin( ent, ent->s.origin ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + gi.linkentity (ent); + + ent->e_PainFunc = painF_GasBurst; + + if ( ent->targetname ) + { + ent->e_UseFunc = useF_misc_use; + } + + ent->material = MAT_METAL3; + + ent->e_DieFunc = dieF_misc_model_breakable_die; + + ent->e_ThinkFunc = thinkF_gas_random_jet; + ent->nextthink = level.time + random() * 12000 + 6000; // do this rarely +} + +/*QUAKED misc_crystal_crate (1 0 0.25) (-34 -34 0) (34 34 44) NON_SOLID +model="models/map_objects/imp_mine/crate_open.md3" +Open crate of crystals that explode when shot + +NON_SOLID - can only be shot + +"health" - how much health the crate has, default 80 + +"splashRadius" - radius to do damage in, default 80 +"splashDamage" - amount of damage to do when it explodes, default 40 + +"targetname" - auto-explodes +"target" - what to use when it dies + +*/ + +//------------------------------------------------------------ +void CrystalCratePain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc ) +{ + vec3_t pt; + + VectorCopy( self->currentOrigin, pt ); + pt[2] += 36; + + G_PlayEffect( "env/crystal_crate", pt ); + + // do some damage, heh + pt[2] += 32; + G_RadiusDamage( pt, self, 16, 32, self, MOD_UNKNOWN ); + +} + +//------------------------------------------------------------ +void SP_misc_crystal_crate( gentity_t *ent ) +{ + G_SpawnInt( "health", "80", &ent->health ); + G_SpawnInt( "splashRadius", "80", &ent->splashRadius ); + G_SpawnInt( "splashDamage", "40", &ent->splashDamage ); + + ent->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/crate_open.md3" ); + ent->fxID = G_EffectIndex( "thermal/explosion" ); // FIXME: temp + G_EffectIndex( "env/crystal_crate" ); + G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"); + + VectorSet( ent->mins, -34, -34, 0 ); + VectorSet( ent->maxs, 34, 34, 44 ); + + //Blocks movement + ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this + + if ( ent->spawnflags & 1 ) // non-solid + { // Override earlier contents flag... + //Can only be shot + ent->contents = CONTENTS_SHOTCLIP; + } + + + ent->takedamage = qtrue; + + G_SetOrigin( ent, ent->s.origin ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + gi.linkentity (ent); + + ent->e_PainFunc = painF_CrystalCratePain; + + if ( ent->targetname ) + { + ent->e_UseFunc = useF_misc_use; + } + + ent->material = MAT_CRATE2; + + ent->e_DieFunc = dieF_misc_model_breakable_die; +} + +/*QUAKED misc_atst_drivable (1 0 0.25) (-40 -40 -24) (40 40 248) +model="models/players/atst/model.glm" + +Drivable ATST, when used by player, they become the ATST. When the player hits use again, they get out. + +"health" - how much health the atst has - default 800 + +"target" - what to use when it dies +*/ +void misc_atst_setanim( gentity_t *self, int bone, int anim ) +{ + if ( bone < 0 || anim < 0 ) + { + return; + } + int firstFrame = -1; + int lastFrame = -1; + float animSpeed = 0; + //try to get anim ranges from the animation.cfg for an AT-ST + for ( int i = 0; i < level.numKnownAnimFileSets; i++ ) + { + if ( !Q_stricmp( "atst", level.knownAnimFileSets[i].filename ) ) + { + firstFrame = level.knownAnimFileSets[i].animations[anim].firstFrame; + lastFrame = firstFrame+level.knownAnimFileSets[i].animations[anim].numFrames; + animSpeed = 50.0f / level.knownAnimFileSets[i].animations[anim].frameLerp; + break; + } + } + if ( firstFrame != -1 && lastFrame != -1 && animSpeed != 0 ) + { + if (!gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], bone, firstFrame, + lastFrame, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeed, + (cg.time?cg.time:level.time), -1, 150 )) + { + gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], bone, firstFrame, + lastFrame, BONE_ANIM_OVERRIDE_FREEZE, animSpeed, + (cg.time?cg.time:level.time), -1, 150 ); + } + } +} +void misc_atst_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) +{//ATST was destroyed while you weren't in it + //can't be used anymore + self->e_UseFunc = useF_NULL; + //sigh... remove contents so we don't block the player's path... + self->contents = CONTENTS_CORPSE; + self->takedamage = qfalse; + self->maxs[2] = 48; + + //FIXME: match to slope + vec3_t effectPos; + VectorCopy( self->currentOrigin, effectPos ); + effectPos[2] -= 15; + G_PlayEffect( "explosions/droidexplosion1", effectPos ); +// G_PlayEffect( "small_chunks", effectPos ); + //set these to defaults that work in a worst-case scenario (according to current animation.cfg) + gi.G2API_StopBoneAnimIndex( &self->ghoul2[self->playerModel], self->craniumBone ); + misc_atst_setanim( self, self->rootBone, BOTH_DEATH1 ); +} + +extern void G_DriveATST( gentity_t *ent, gentity_t *atst ); +extern void SetClientViewAngle( gentity_t *ent, vec3_t angle ); +extern qboolean PM_InSlopeAnim( int anim ); +void misc_atst_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( !activator || activator->s.number ) + {//only player can do this + return; + } + + int tempLocDmg[HL_MAX]; + int hl, tempHealth; + + if ( activator->client->NPC_class != CLASS_ATST ) + {//get in the ATST + if ( activator->client->ps.groundEntityNum != self->s.number ) + {//can only get in if on top of me... + //we *could* even check for the hatch surf...? + return; + } + //copy origin + G_SetOrigin( activator, self->currentOrigin ); + + //copy angles + VectorCopy( self->s.angles2, self->currentAngles ); + G_SetAngles( activator, self->currentAngles ); + SetClientViewAngle( activator, self->currentAngles ); + + //set player to my g2 instance + gi.G2API_StopBoneAnimIndex( &self->ghoul2[self->playerModel], self->craniumBone ); + G_DriveATST( activator, self ); + activator->activator = self; + self->s.eFlags |= EF_NODRAW; + self->svFlags |= SVF_NOCLIENT; + self->contents = 0; + self->takedamage = qfalse; + //transfer armor + tempHealth = self->health; + self->health = activator->client->ps.stats[STAT_ARMOR]; + activator->client->ps.stats[STAT_ARMOR] = tempHealth; + //transfer locationDamage + for ( hl = HL_NONE; hl < HL_MAX; hl++ ) + { + tempLocDmg[hl] = activator->locationDamage[hl]; + activator->locationDamage[hl] = self->locationDamage[hl]; + self->locationDamage[hl] = tempLocDmg[hl]; + } + if ( !self->s.number ) + { + CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.87 ); + } + } + else + {//get out of ATST + int legsAnim = activator->client->ps.legsAnim; + if ( legsAnim != BOTH_STAND1 + && !PM_InSlopeAnim( legsAnim ) + && legsAnim != BOTH_TURN_RIGHT1 && legsAnim != BOTH_TURN_LEFT1 ) + {//can't get out of it while it's still moving + return; + } + //FIXME: after a load/save, this crashes, BAD... somewhere in G2 + G_SetOrigin( self, activator->currentOrigin ); + VectorSet( self->currentAngles, 0, activator->client->ps.legsYaw, 0 ); + //self->currentAngles[PITCH] = activator->currentAngles[ROLL] = 0; + G_SetAngles( self, self->currentAngles ); + VectorCopy( activator->currentAngles, self->s.angles2 ); + //remove my G2 + if ( self->playerModel >= 0 ) + { + gi.G2API_RemoveGhoul2Model( self->ghoul2, self->playerModel ); + self->playerModel = -1; + } + //copy player's + gi.G2API_CopyGhoul2Instance( activator->ghoul2, self->ghoul2 ); + self->playerModel = 0;//assumption + //reset player to kyle + G_DriveATST( activator, NULL ); + activator->activator = NULL; + self->s.eFlags &= ~EF_NODRAW; + self->svFlags &= ~SVF_NOCLIENT; + self->contents = CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP; + self->takedamage = qtrue; + //transfer armor + tempHealth = self->health; + self->health = activator->client->ps.stats[STAT_ARMOR]; + activator->client->ps.stats[STAT_ARMOR] = tempHealth; + //transfer locationDamage + for ( hl = HL_NONE; hl < HL_MAX; hl++ ) + { + tempLocDmg[hl] = self->locationDamage[hl]; + self->locationDamage[hl] = activator->locationDamage[hl]; + activator->locationDamage[hl] = tempLocDmg[hl]; + } + //link me back in + gi.linkentity ( self ); + //put activator on top of me? + vec3_t newOrg = {activator->currentOrigin[0], activator->currentOrigin[1], activator->currentOrigin[2] + (self->maxs[2]-self->mins[2]) + 1 }; + G_SetOrigin( activator, newOrg ); + //open the hatch + misc_atst_setanim( self, self->craniumBone, BOTH_STAND2 ); + gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head_hatchcover", 0 ); + G_Sound( self, G_SoundIndex( "sound/chars/atst/atst_hatch_open" )); + } +} + +void SP_misc_atst_drivable( gentity_t *ent ) +{ + extern void NPC_ATST_Precache(void); + extern void NPC_PrecacheAnimationCFG( const char *NPC_type ); + + ent->s.modelindex = G_ModelIndex( "models/players/atst/model.glm" ); + ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/players/atst/model.glm", ent->s.modelindex ); + ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue ); + ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); //FIXME: need to somehow set the anim/frame to the equivalent of BOTH_STAND1... use to be that BOTH_STAND1 was the first frame of the glm, but not anymore + ent->s.radius = 320; + VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f ); + + //register my weapons, sounds and model + RegisterItem( FindItemForWeapon( WP_ATST_MAIN )); //precache the weapon + RegisterItem( FindItemForWeapon( WP_ATST_SIDE )); //precache the weapon + //HACKHACKHACKTEMP - until ATST gets real weapons of it's own? + RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN )); //precache the weapon +// RegisterItem( FindItemForWeapon( WP_ROCKET_LAUNCHER )); //precache the weapon +// RegisterItem( FindItemForWeapon( WP_BOWCASTER )); //precache the weapon + //HACKHACKHACKTEMP - until ATST gets real weapons of it's own? + + G_SoundIndex( "sound/chars/atst/atst_hatch_open" ); + G_SoundIndex( "sound/chars/atst/atst_hatch_close" ); + + NPC_ATST_Precache(); + ent->NPC_type = "atst"; + NPC_PrecacheAnimationCFG( ent->NPC_type ); + //open the hatch + misc_atst_setanim( ent, ent->rootBone, BOTH_STAND2 ); + gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "head_hatchcover", 0 ); + + VectorSet( ent->mins, ATST_MINS0, ATST_MINS1, ATST_MINS2 ); + VectorSet( ent->maxs, ATST_MAXS0, ATST_MAXS1, ATST_MAXS2 ); + + ent->contents = CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP; + ent->flags |= FL_SHIELDED; + ent->takedamage = qtrue; + if ( !ent->health ) + { + ent->health = 800; + } + ent->s.radius = 320; + + ent->max_health = ent->health; // cg_draw needs this + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngles( ent, ent->s.angles ); + VectorCopy( ent->currentAngles, ent->s.angles2 ); + + gi.linkentity ( ent ); + + //FIXME: test the origin to make sure I'm clear? + + ent->e_UseFunc = useF_misc_atst_use; + ent->svFlags |= SVF_PLAYER_USABLE; + + //make it able to take damage and die when you're not in it... + //do an explosion and play the death anim, remove use func. + ent->e_DieFunc = dieF_misc_atst_die; +} + +extern int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ); + +/*QUAKED misc_weather_zone (0 .5 .8) ? +Determines a region to check for weather contents - (optional, used to reduce load times) +Place surrounding your inside/outside brushes. It will not check for weather contents outside of these zones. +*/ +void SP_misc_weather_zone( gentity_t *ent ) +{ + gi.SetBrushModel(ent, ent->model); + + char temp[256]; + sprintf( temp, "zone ( %f %f %f ) ( %f %f %f )", + ent->mins[0], ent->mins[1], ent->mins[2], + ent->maxs[0], ent->maxs[1], ent->maxs[2] ); + + G_FindConfigstringIndex(temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue); + +// gi.WE_AddWeatherZone(ent->mins, ent->maxs); + G_FreeEntity(ent); +} diff --git a/code/game/g_misc_model.cpp b/code/game/g_misc_model.cpp new file mode 100644 index 0000000..21ba255 --- /dev/null +++ b/code/game/g_misc_model.cpp @@ -0,0 +1,792 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "g_functions.h" +#include "bg_public.h" + +extern cvar_t *g_spskill; + +// +// Helper functions +// +//------------------------------------------------------------ +void SetMiscModelModels( char *modelNameString, gentity_t *ent, qboolean damage_model ) +{ + char damageModel[MAX_QPATH]; + char chunkModel[MAX_QPATH]; + int len; + + //Main model + ent->s.modelindex = G_ModelIndex( modelNameString ); + + if ( damage_model ) + { + len = strlen( modelNameString ) - 4; // extract the extension + + //Dead/damaged model + strncpy( damageModel, modelNameString, len ); + damageModel[len] = 0; + strncpy( chunkModel, damageModel, sizeof(chunkModel)); + strcat( damageModel, "_d1.md3" ); + ent->s.modelindex2 = G_ModelIndex( damageModel ); + + ent->spawnflags |= 4; // deadsolid + + //Chunk model + strcat( chunkModel, "_c1.md3" ); + ent->s.modelindex3 = G_ModelIndex( chunkModel ); + } +} + +//------------------------------------------------------------ +void SetMiscModelDefaults( gentity_t *ent, useFunc_t use_func, char *material, int solid_mask,int animFlag, + qboolean take_damage, qboolean damage_model = qfalse ) +{ + // Apply damage and chunk models if they exist + SetMiscModelModels( ent->model, ent, damage_model ); + + ent->s.eFlags = animFlag; + ent->svFlags |= SVF_PLAYER_USABLE; + ent->contents = solid_mask; + + G_SetOrigin( ent, ent->s.origin ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + gi.linkentity (ent); + + // Set a generic use function + + ent->e_UseFunc = use_func; +/* if (use_func == useF_health_use) + { + G_SoundIndex("sound/player/suithealth.wav"); + } + else if (use_func == useF_ammo_use ) + { + G_SoundIndex("sound/player/suitenergy.wav"); + } +*/ + G_SpawnInt( "material", material, (int *)&ent->material ); + + if (ent->health) + { + ent->max_health = ent->health; + ent->takedamage = take_damage; + ent->e_PainFunc = painF_misc_model_breakable_pain; + ent->e_DieFunc = dieF_misc_model_breakable_die; + } +} + +void HealthStationSettings(gentity_t *ent) +{ + G_SpawnInt( "count", "0", &ent->count ); + + if (!ent->count) + { + switch (g_spskill->integer) + { + case 0: // EASY + ent->count = 100; + break; + case 1: // MEDIUM + ent->count = 75; + break; + default : + case 2: // HARD + ent->count = 50; + break; + } + } +} + + +void CrystalAmmoSettings(gentity_t *ent) +{ + G_SpawnInt( "count", "0", &ent->count ); + + if (!ent->count) + { + switch (g_spskill->integer) + { + case 0: // EASY + ent->count = 75; + break; + case 1: // MEDIUM + ent->count = 75; + break; + default : + case 2: // HARD + ent->count = 75; + break; + } + } +} + + +//------------------------------------------------------------ + +//------------------------------------------------------------ +/*QUAKED misc_model_ghoul (1 0 0) (-16 -16 -37) (16 16 32) +"model" arbitrary .glm file to display +"health" - how much health the model has - default 60 (zero makes non-breakable) +*/ +//------------------------------------------------------------ +#include "anims.h" +extern int G_ParseAnimFileSet( const char *skeletonName, const char *modelName=0); +int temp_animFileIndex; +void set_MiscAnim( gentity_t *ent) +{ + animation_t *animations = level.knownAnimFileSets[temp_animFileIndex].animations; + if (ent->playerModel & 1) + { + int anim = BOTH_STAND3; + float animSpeed = 50.0f / animations[anim].frameLerp; + + // yes, its the same animation, so work out where we are in the leg anim, and blend us + gi.G2API_SetBoneAnim(&ent->ghoul2[0], "model_root", animations[anim].firstFrame, + (animations[anim].numFrames -1 )+ animations[anim].firstFrame, + BONE_ANIM_OVERRIDE_FREEZE | BONE_ANIM_BLEND , animSpeed, (cg.time?cg.time:level.time), -1, 350); + } + else + { + int anim = BOTH_PAIN3; + float animSpeed = 50.0f / animations[anim].frameLerp; + gi.G2API_SetBoneAnim(&ent->ghoul2[0], "model_root", animations[anim].firstFrame, + (animations[anim].numFrames -1 )+ animations[anim].firstFrame, + BONE_ANIM_OVERRIDE_FREEZE | BONE_ANIM_BLEND, animSpeed, (cg.time?cg.time:level.time), -1, 350); + } + ent->nextthink = level.time + 900; + ent->playerModel++; + +} + +void SP_misc_model_ghoul( gentity_t *ent ) +{ +#if 1 + ent->s.modelindex = G_ModelIndex( ent->model ); + gi.G2API_InitGhoul2Model(ent->ghoul2, ent->model, ent->s.modelindex); + ent->s.radius = 50; +#else + char name1[200] = "models/players/kyle/model.glm"; + ent->s.modelindex = G_ModelIndex( name1 ); + + gi.G2API_InitGhoul2Model(ent->ghoul2, name1, ent->s.modelindex); + ent->s.radius = 150; + + // we found the model ok - load it's animation config + temp_animFileIndex = G_ParseAnimFileSet("_humanoid", "kyle"); + if ( temp_animFileIndex<0 ) + { + Com_Printf( S_COLOR_RED"Failed to load animation file set models/players/jedi/animation.cfg\n"); + } + + + ent->s.angles[0] = 0; + ent->s.angles[1] = 90; + ent->s.angles[2] = 0; + + ent->s.origin[2] = 20; + ent->s.origin[1] = 80; +// ent->s.modelScale[0] = ent->s.modelScale[1] = ent->s.modelScale[2] = 0.8f; + + VectorSet (ent->mins, -16, -16, -37); + VectorSet (ent->maxs, 16, 16, 32); +//#if _DEBUG +//loadsavecrash +// VectorCopy(ent->mins, ent->s.mins); +// VectorCopy(ent->maxs, ent->s.maxs); +//#endif + ent->contents = CONTENTS_BODY; + ent->clipmask = MASK_NPCSOLID; + + G_SetOrigin( ent, ent->s.origin ); + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + ent->health = 1000; + +// ent->s.modelindex = G_ModelIndex( "models/weapons2/blaster_r/g2blaster_w.glm" ); +// gi.G2API_InitGhoul2Model(ent->ghoul2, "models/weapons2/blaster_r/g2blaster_w.glm", ent->s.modelindex); +// gi.G2API_AddBolt(&ent->ghoul2[0], "*weapon"); +// gi.G2API_AttachG2Model(&ent->ghoul2[1],&ent->ghoul2[0], 0, 0); + + gi.linkentity (ent); + + animation_t *animations = level.knownAnimFileSets[temp_animFileIndex].animations; + int anim = BOTH_STAND3; + float animSpeed = 50.0f / animations[anim].frameLerp; + gi.G2API_SetBoneAnim(&ent->ghoul2[0], "model_root", animations[anim].firstFrame, + (animations[anim].numFrames -1 )+ animations[anim].firstFrame, + BONE_ANIM_OVERRIDE_FREEZE , animSpeed, cg.time); + +// int test = gi.G2API_GetSurfaceRenderStatus(&ent->ghoul2[0], "l_hand"); +// gi.G2API_SetSurfaceOnOff(&ent->ghoul2[0], "l_arm",0x00000100); +// test = gi.G2API_GetSurfaceRenderStatus(&ent->ghoul2[0], "l_hand"); + +// gi.G2API_SetNewOrigin(&ent->ghoul2[0], gi.G2API_AddBolt(&ent->ghoul2[0], "rhang_tag_bone")); +// ent->s.apos.trDelta[1] = 10; +// ent->s.apos.trType = TR_LINEAR; + + + ent->nextthink = level.time + 1000; + ent->e_ThinkFunc = thinkF_set_MiscAnim; +#endif +} + + +#define RACK_BLASTER 1 +#define RACK_REPEATER 2 +#define RACK_ROCKET 4 + +/*QUAKED misc_model_gun_rack (1 0 0.25) (-14 -14 -4) (14 14 30) BLASTER REPEATER ROCKET +model="models/map_objects/kejim/weaponsrack.md3" + +NOTE: can mix and match these spawnflags to get multi-weapon racks. If only one type is checked the rack will be full of those weapons +BLASTER - Puts one or more blaster guns on the rack. +REPEATER - Puts one or more repeater guns on the rack. +ROCKET - Puts one or more rocket launchers on the rack. +*/ + +void GunRackAddItem( gitem_t *gun, vec3_t org, vec3_t angs, float ffwd, float fright, float fup ) +{ + vec3_t fwd, right; + gentity_t *it_ent = G_Spawn(); + qboolean rotate = qtrue; + + AngleVectors( angs, fwd, right, NULL ); + + if ( it_ent && gun ) + { + // FIXME: scaling the ammo will probably need to be tweaked to a reasonable amount...adjust as needed + // Set base ammo per type + if ( gun->giType == IT_WEAPON ) + { + it_ent->spawnflags |= 16;// VERTICAL + + switch( gun->giTag ) + { + case WP_BLASTER: + it_ent->count = 15; + break; + case WP_REPEATER: + it_ent->count = 100; + break; + case WP_ROCKET_LAUNCHER: + it_ent->count = 4; + break; + } + } + else + { + rotate = qfalse; + + // must deliberately make it small, or else the objects will spawn inside of each other. + VectorSet( it_ent->maxs, 6.75f, 6.75f, 6.75f ); + VectorScale( it_ent->maxs, -1, it_ent->mins ); + } + + it_ent->spawnflags |= 1;// ITMSF_SUSPEND + it_ent->classname = G_NewString(gun->classname); //copy it so it can be freed safely + G_SpawnItem( it_ent, gun ); + + // FinishSpawningItem handles everything, so clear the thinkFunc that was set in G_SpawnItem + FinishSpawningItem( it_ent ); + + if ( gun->giType == IT_AMMO ) + { + if ( gun->giTag == AMMO_BLASTER ) // I guess this just has to use different logic?? + { + if ( g_spskill->integer >= 2 ) + { + it_ent->count += 10; // give more on higher difficulty because there will be more/harder enemies? + } + } + else + { + // scale ammo based on skill + switch ( g_spskill->integer ) + { + case 0: // do default + break; + case 1: + it_ent->count *= 0.75f; + break; + case 2: + it_ent->count *= 0.5f; + break; + } + } + } + + it_ent->nextthink = 0; + + VectorCopy( org, it_ent->s.origin ); + VectorMA( it_ent->s.origin, fright, right, it_ent->s.origin ); + VectorMA( it_ent->s.origin, ffwd, fwd, it_ent->s.origin ); + it_ent->s.origin[2] += fup; + + VectorCopy( angs, it_ent->s.angles ); + + // by doing this, we can force the amount of ammo we desire onto the weapon for when it gets picked-up + it_ent->flags |= ( FL_DROPPED_ITEM | FL_FORCE_PULLABLE_ONLY ); + it_ent->physicsBounce = 0.1f; + + for ( int t = 0; t < 3; t++ ) + { + if ( rotate ) + { + if ( t == YAW ) + { + it_ent->s.angles[t] = AngleNormalize180( it_ent->s.angles[t] + 180 + crandom() * 14 ); + } + else + { + it_ent->s.angles[t] = AngleNormalize180( it_ent->s.angles[t] + crandom() * 4 ); + } + } + else + { + if ( t == YAW ) + { + it_ent->s.angles[t] = AngleNormalize180( it_ent->s.angles[t] + 90 + crandom() * 4 ); + } + } + } + + G_SetAngles( it_ent, it_ent->s.angles ); + G_SetOrigin( it_ent, it_ent->s.origin ); + gi.linkentity( it_ent ); + } +} + +//--------------------------------------------- +void SP_misc_model_gun_rack( gentity_t *ent ) +{ + gitem_t *blaster = NULL, *repeater = NULL, *rocket = NULL; + int ct = 0; + float ofz[3]; + gitem_t *itemList[3]; + + // If BLASTER is checked...or nothing is checked then we'll do blasters + if (( ent->spawnflags & RACK_BLASTER ) || !(ent->spawnflags & ( RACK_BLASTER | RACK_REPEATER | RACK_ROCKET ))) + { + blaster = FindItemForWeapon( WP_BLASTER ); + } + + if (( ent->spawnflags & RACK_REPEATER )) + { + repeater = FindItemForWeapon( WP_REPEATER ); + } + + if (( ent->spawnflags & RACK_ROCKET )) + { + rocket = FindItemForWeapon( WP_ROCKET_LAUNCHER ); + } + + //---------weapon types + if ( blaster ) + { + ofz[ct] = 23.0f; + itemList[ct++] = blaster; + } + + if ( repeater ) + { + ofz[ct] = 24.5f; + itemList[ct++] = repeater; + } + + if ( rocket ) + { + ofz[ct] = 25.5f; + itemList[ct++] = rocket; + } + + if ( ct ) //..should always have at least one item on their, but just being safe + { + for ( ; ct < 3 ; ct++ ) + { + ofz[ct] = ofz[0]; + itemList[ct] = itemList[0]; // first weapon ALWAYS propagates to fill up the shelf + } + } + + // now actually add the items to the shelf...validate that we have a list to add + if ( ct ) + { + for ( int i = 0; i < ct; i++ ) + { + GunRackAddItem( itemList[i], ent->s.origin, ent->s.angles, crandom() * 2, ( i - 1 ) * 9 + crandom() * 2, ofz[i] ); + } + } + + ent->s.modelindex = G_ModelIndex( "models/map_objects/kejim/weaponsrack.md3" ); + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngles( ent, ent->s.angles ); + + ent->contents = CONTENTS_SOLID; + + gi.linkentity( ent ); +} + +#define RACK_METAL_BOLTS 2 +#define RACK_ROCKETS 4 +#define RACK_WEAPONS 8 +#define RACK_HEALTH 16 +#define RACK_PWR_CELL 32 +#define RACK_NO_FILL 64 + +/*QUAKED misc_model_ammo_rack (1 0 0.25) (-14 -14 -4) (14 14 30) BLASTER METAL_BOLTS ROCKETS WEAPON HEALTH PWR_CELL NO_FILL +model="models/map_objects/kejim/weaponsrung.md3" + +NOTE: can mix and match these spawnflags to get multi-ammo racks. If only one type is checked the rack will be full of that ammo. Only three ammo packs max can be displayed. + + +BLASTER - Adds one or more ammo packs that are compatible with Blasters and the Bryar pistol. +METAL_BOLTS - Adds one or more metal bolt ammo packs that are compatible with the heavy repeater and the flechette gun +ROCKETS - Puts one or more rocket packs on a rack. +WEAPON - adds a weapon matching a selected ammo type to the rack. +HEALTH - adds a health pack to the top shelf of the ammo rack +PWR_CELL - Adds one or more power cell packs that are compatible with the Disuptor, bowcaster, and demp2 +NO_FILL - Only puts selected ammo on the rack, it never fills up all three slots if only one or two items were checked +*/ + +extern gitem_t *FindItemForAmmo( ammo_t ammo ); + +//--------------------------------------------- +void SP_misc_model_ammo_rack( gentity_t *ent ) +{ +// If BLASTER is checked...or nothing is checked then we'll do blasters + if (( ent->spawnflags & RACK_BLASTER ) || !(ent->spawnflags & ( RACK_BLASTER | RACK_METAL_BOLTS | RACK_ROCKETS | RACK_PWR_CELL ))) + { + if ( ent->spawnflags & RACK_WEAPONS ) + { + RegisterItem( FindItemForWeapon( WP_BLASTER )); + } + RegisterItem( FindItemForAmmo( AMMO_BLASTER )); + } + + if (( ent->spawnflags & RACK_METAL_BOLTS )) + { + if ( ent->spawnflags & RACK_WEAPONS ) + { + RegisterItem( FindItemForWeapon( WP_REPEATER )); + } + RegisterItem( FindItemForAmmo( AMMO_METAL_BOLTS )); + } + + if (( ent->spawnflags & RACK_ROCKETS )) + { + if ( ent->spawnflags & RACK_WEAPONS ) + { + RegisterItem( FindItemForWeapon( WP_ROCKET_LAUNCHER )); + } + RegisterItem( FindItemForAmmo( AMMO_ROCKETS )); + } + + if (( ent->spawnflags & RACK_PWR_CELL )) + { + RegisterItem( FindItemForAmmo( AMMO_POWERCELL )); + } + + if (( ent->spawnflags & RACK_HEALTH )) + { + RegisterItem( FindItem( "item_medpak_instant" )); + } + + ent->e_ThinkFunc = thinkF_spawn_rack_goods; + ent->nextthink = level.time + 100; + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngles( ent, ent->s.angles ); + + ent->contents = CONTENTS_SHOTCLIP|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//CONTENTS_SOLID;//so use traces can go through them + + gi.linkentity( ent ); +} + +// AMMO RACK!! +void spawn_rack_goods( gentity_t *ent ) +{ + float v_off = 0; + gitem_t *blaster = NULL, *metal_bolts = NULL, *rockets = NULL, *it = NULL; + gitem_t *am_blaster = NULL, *am_metal_bolts = NULL, *am_rockets = NULL, *am_pwr_cell = NULL; + gitem_t *health = NULL; + int pos = 0, ct = 0; + gitem_t *itemList[4]; // allocating 4, but we only use 3. done so I don't have to validate that the array isn't full before I add another + + gi.unlinkentity( ent ); + + // If BLASTER is checked...or nothing is checked then we'll do blasters + if (( ent->spawnflags & RACK_BLASTER ) || !(ent->spawnflags & ( RACK_BLASTER | RACK_METAL_BOLTS | RACK_ROCKETS | RACK_PWR_CELL ))) + { + if ( ent->spawnflags & RACK_WEAPONS ) + { + blaster = FindItemForWeapon( WP_BLASTER ); + } + am_blaster = FindItemForAmmo( AMMO_BLASTER ); + } + + if (( ent->spawnflags & RACK_METAL_BOLTS )) + { + if ( ent->spawnflags & RACK_WEAPONS ) + { + metal_bolts = FindItemForWeapon( WP_REPEATER ); + } + am_metal_bolts = FindItemForAmmo( AMMO_METAL_BOLTS ); + } + + if (( ent->spawnflags & RACK_ROCKETS )) + { + if ( ent->spawnflags & RACK_WEAPONS ) + { + rockets = FindItemForWeapon( WP_ROCKET_LAUNCHER ); + } + am_rockets = FindItemForAmmo( AMMO_ROCKETS ); + } + + if (( ent->spawnflags & RACK_PWR_CELL )) + { + am_pwr_cell = FindItemForAmmo( AMMO_POWERCELL ); + } + + if (( ent->spawnflags & RACK_HEALTH )) + { + health = FindItem( "item_medpak_instant" ); + RegisterItem( health ); + } + + //---------Ammo types + if ( am_blaster ) + { + itemList[ct++] = am_blaster; + } + + if ( am_metal_bolts ) + { + itemList[ct++] = am_metal_bolts; + } + + if ( am_pwr_cell ) + { + itemList[ct++] = am_pwr_cell; + } + + if ( am_rockets ) + { + itemList[ct++] = am_rockets; + } + + if ( !(ent->spawnflags & RACK_NO_FILL) && ct ) //double negative..should always have at least one item on there, but just being safe + { + for ( ; ct < 3 ; ct++ ) + { + itemList[ct] = itemList[0]; // first item ALWAYS propagates to fill up the shelf + } + } + + // now actually add the items to the shelf...validate that we have a list to add + if ( ct ) + { + for ( int i = 0; i < ct; i++ ) + { + GunRackAddItem( itemList[i], ent->s.origin, ent->s.angles, crandom() * 0.5f, (i-1)* 8, 7.0f ); + } + } + + // -----Weapon option + if ( ent->spawnflags & RACK_WEAPONS ) + { + if ( !(ent->spawnflags & ( RACK_BLASTER | RACK_METAL_BOLTS | RACK_ROCKETS | RACK_PWR_CELL ))) + { + // nothing was selected, so we assume blaster pack + it = blaster; + } + else + { + // if weapon is checked...and so are one or more ammo types, then pick a random weapon to display..always give weaker weapons first + if ( blaster ) + { + it = blaster; + v_off = 25.5f; + } + else if ( metal_bolts ) + { + it = metal_bolts; + v_off = 27.0f; + } + else if ( rockets ) + { + it = rockets; + v_off = 28.0f; + } + } + + if ( it ) + { + // since we may have to put up a health pack on the shelf, we should know where we randomly put + // the gun so we don't put the pack on the same spot..so pick either the left or right side + pos = ( random() > .5 ) ? -1 : 1; + + GunRackAddItem( it, ent->s.origin, ent->s.angles, crandom() * 2, ( random() * 6 + 4 ) * pos, v_off ); + } + } + + // ------Medpack + if (( ent->spawnflags & RACK_HEALTH ) && health ) + { + if ( !pos ) + { + // we haven't picked a side already... + pos = ( random() > .5 ) ? -1 : 1; + } + else + { + // switch to the opposite side + pos *= -1; + } + + GunRackAddItem( health, ent->s.origin, ent->s.angles, crandom() * 0.5f, ( random() * 4 + 4 ) * pos, 24 ); + } + + ent->s.modelindex = G_ModelIndex( "models/map_objects/kejim/weaponsrung.md3" ); + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngles( ent, ent->s.angles ); + + gi.linkentity( ent ); +} + +#define DROP_MEDPACK 1 +#define DROP_SHIELDS 2 +#define DROP_BACTA 4 +#define DROP_BATTERIES 8 + +/*QUAKED misc_model_cargo_small (1 0 0.25) (-14 -14 -4) (14 14 30) MEDPACK SHIELDS BACTA BATTERIES +model="models/map_objects/kejim/cargo_small.md3" + + Cargo crate that can only be destroyed by heavy class weapons ( turrets, emplaced guns, at-st ) Can spawn useful things when it breaks + +MEDPACK - instant use medpacks +SHIELDS - instant shields +BACTA - bacta tanks +BATTERIES - + +"health" - how much damage to take before blowing up ( default 25 ) +"splashRadius" - damage range when it explodes ( default 96 ) +"splashDamage" - damage to do within explode range ( default 1 ) + +*/ +extern gentity_t *LaunchItem( gitem_t *item, const vec3_t origin, const vec3_t velocity, char *target ); + +void misc_model_cargo_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc ) +{ + int flags; + vec3_t org, temp; + gitem_t *health = NULL, *shields = NULL, *bacta = NULL, *batteries = NULL; + + // copy these for later + flags = self->spawnflags; + VectorCopy( self->currentOrigin, org ); + + // we already had spawn flags, but we don't care what they were...we just need to set up the flags we want for misc_model_breakable_die + self->spawnflags = 8; // NO_DMODEL + + // pass through to get the effects and such + misc_model_breakable_die( self, inflictor, attacker, damage, mod ); + + // now that the model is broken, we can safely spawn these in it's place without them being in solid + temp[2] = org[2] + 16; + + // annoying, but spawn each thing in its own little quadrant so that they don't end up on top of each other + if (( flags & DROP_MEDPACK )) + { + health = FindItem( "item_medpak_instant" ); + + if ( health ) + { + temp[0] = org[0] + crandom() * 8 + 16; + temp[1] = org[1] + crandom() * 8 + 16; + + LaunchItem( health, temp, vec3_origin, NULL ); + } + } + if (( flags & DROP_SHIELDS )) + { + shields = FindItem( "item_shield_sm_instant" ); + + if ( shields ) + { + temp[0] = org[0] + crandom() * 8 - 16; + temp[1] = org[1] + crandom() * 8 + 16; + + LaunchItem( shields, temp, vec3_origin, NULL ); + } + } + + if (( flags & DROP_BACTA )) + { + bacta = FindItem( "item_bacta" ); + + if ( bacta ) + { + temp[0] = org[0] + crandom() * 8 - 16; + temp[1] = org[1] + crandom() * 8 - 16; + + LaunchItem( bacta, temp, vec3_origin, NULL ); + } + } + + if (( flags & DROP_BATTERIES )) + { + batteries = FindItem( "item_battery" ); + + if ( batteries ) + { + temp[0] = org[0] + crandom() * 8 + 16; + temp[1] = org[1] + crandom() * 8 - 16; + + LaunchItem( batteries, temp, vec3_origin, NULL ); + } + } +} + +//--------------------------------------------- +void SP_misc_model_cargo_small( gentity_t *ent ) +{ + G_SpawnInt( "splashRadius", "96", &ent->splashRadius ); + G_SpawnInt( "splashDamage", "1", &ent->splashDamage ); + + if (( ent->spawnflags & DROP_MEDPACK )) + { + RegisterItem( FindItem( "item_medpak_instant" )); + } + + if (( ent->spawnflags & DROP_SHIELDS )) + { + RegisterItem( FindItem( "item_shield_sm_instant" )); + } + + if (( ent->spawnflags & DROP_BACTA )) + { +// RegisterItem( FindItem( "item_bacta" )); + } + + if (( ent->spawnflags & DROP_BATTERIES )) + { + RegisterItem( FindItem( "item_battery" )); + } + + G_SpawnInt( "health", "25", &ent->health ); + + SetMiscModelDefaults( ent, useF_NULL, "11", CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, NULL, qtrue, NULL ); + ent->s.modelindex2 = G_ModelIndex("/models/map_objects/kejim/cargo_small.md3"); // Precache model + + // we only take damage from a heavy weapon class missile + ent->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY; + + ent->e_DieFunc = dieF_misc_model_cargo_die; + + ent->radius = 1.5f; // scale number of chunks spawned +} + diff --git a/code/game/g_missile.cpp b/code/game/g_missile.cpp new file mode 100644 index 0000000..24fc788 --- /dev/null +++ b/code/game/g_missile.cpp @@ -0,0 +1,1536 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "g_functions.h" +#include "wp_saber.h" +#include "bg_local.h" + +#ifdef _DEBUG + #include +#endif //_DEBUG + +#include "../client/fffx.h" + +extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f ); +qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ); +extern qboolean PM_SaberInParry( int move ); +extern qboolean PM_SaberInReflect( int move ); +extern qboolean PM_SaberInIdle( int move ); +extern qboolean PM_SaberInAttack( int move ); +extern qboolean PM_SaberInTransitionAny( int move ); +extern qboolean PM_SaberInSpecialAttack( int anim ); + +//------------------------------------------------------------------------- +#ifdef _IMMERSION +void G_MissileBounceEffect( gentity_t *ent, int hitEntNum, vec3_t org, vec3_t dir, qboolean hitWorld ) +#else +void G_MissileBounceEffect( gentity_t *ent, vec3_t org, vec3_t dir, qboolean hitWorld ) +#endif // _IMMERSION +{ + //FIXME: have an EV_BOUNCE_MISSILE event that checks the s.weapon and does the appropriate effect + switch( ent->s.weapon ) + { + case WP_BOWCASTER: +#ifdef _IMMERSION + if ( hitWorld ) + { + G_PlayEffect( "bowcaster/bounce_wall", hitEntNum, org, dir ); + } + else + { + G_PlayEffect( "bowcaster/deflect", hitEntNum, ent->currentOrigin, dir ); + } +#else + if ( hitWorld ) + { + G_PlayEffect( "bowcaster/bounce_wall", org, dir ); + } + else + { + G_PlayEffect( "bowcaster/deflect", ent->currentOrigin, dir ); + } +#endif // _IMMERSION + break; + case WP_BLASTER: + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: +#ifdef _IMMERSION + G_PlayEffect( "blaster/deflect", hitEntNum, ent->currentOrigin, dir ); +#else + G_PlayEffect( "blaster/deflect", ent->currentOrigin, dir ); +#endif // _IMMERSION + break; + default: + { + gentity_t *tent = G_TempEntity( org, EV_GRENADE_BOUNCE ); + VectorCopy( dir, tent->pos1 ); + tent->s.weapon = ent->s.weapon; +#ifdef _IMMERSION + if ( hitEntNum != -1 ) + { + tent->s.saberActive = 1; + tent->s.otherEntityNum = hitEntNum; + } +#endif // _IMMERSION + } + break; + } +} + +#ifdef _IMMERSION +void G_MissileReflectEffect( gentity_t *ent, int hitEntNum, vec3_t org, vec3_t dir ) +#else +void G_MissileReflectEffect( gentity_t *ent, vec3_t org, vec3_t dir ) +#endif // _IMMERSION +{ + //FIXME: have an EV_BOUNCE_MISSILE event that checks the s.weapon and does the appropriate effect + switch( ent->s.weapon ) + { + case WP_BOWCASTER: +#ifdef _IMMERSION + G_PlayEffect( "bowcaster/deflect", hitEntNum, ent->currentOrigin, dir ); +#else + G_PlayEffect( "bowcaster/deflect", ent->currentOrigin, dir ); +#endif // _IMMERSION + break; + case WP_BLASTER: + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: + default: +#ifdef _IMMERSION + G_PlayEffect( "blaster/deflect", hitEntNum, ent->currentOrigin, dir ); +#else + G_PlayEffect( "blaster/deflect", ent->currentOrigin, dir ); +#endif // _IMMERSION + break; + } +} + +//------------------------------------------------------------------------- +static void G_MissileStick( gentity_t *missile, gentity_t *other, trace_t *tr ) +{ + if ( other->NPC || !Q_stricmp( other->classname, "misc_model_breakable" )) + { + // we bounce off of NPC's and misc model breakables because sticking to them requires too much effort + vec3_t velocity; + + int hitTime = level.previousTime + ( level.time - level.previousTime ) * tr->fraction; + + EvaluateTrajectoryDelta( &missile->s.pos, hitTime, velocity ); + + float dot = DotProduct( velocity, tr->plane.normal ); + G_SetOrigin( missile, tr->endpos ); + VectorMA( velocity, -1.6f * dot, tr->plane.normal, missile->s.pos.trDelta ); + VectorMA( missile->s.pos.trDelta, 10, tr->plane.normal, missile->s.pos.trDelta ); + missile->s.pos.trTime = level.time - 10; // move a bit on the first frame + + // check for stop + if ( tr->entityNum >= 0 && tr->entityNum < ENTITYNUM_WORLD && + tr->plane.normal[2] > 0.7 && missile->s.pos.trDelta[2] < 40 ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7 + { + missile->nextthink = level.time + 100; + } + else + { + // fall till we hit the ground + missile->s.pos.trType = TR_GRAVITY; + } + + return; // don't stick yet + } + + if ( missile->e_TouchFunc != touchF_NULL ) + { + GEntity_TouchFunc( missile, other, tr ); + } + + G_AddEvent( missile, EV_MISSILE_STICK, 0 ); + + if ( other->s.eType == ET_MOVER || other->e_DieFunc == dieF_funcBBrushDie || other->e_DieFunc == dieF_funcGlassDie) + { + // movers and breakable brushes need extra info...so sticky missiles can ride lifts and blow up when the thing they are attached to goes away. + missile->s.groundEntityNum = tr->entityNum; + } +} + +/* +================ +G_ReflectMissile + + Reflect the missile roughly back at it's owner +================ +*/ +extern gentity_t *Jedi_FindEnemyInCone( gentity_t *self, gentity_t *fallback, float minDot ); +void G_ReflectMissile( gentity_t *ent, gentity_t *missile, vec3_t forward ) +{ + vec3_t bounce_dir; + int i; + float speed; + qboolean reflected = qfalse; + gentity_t *owner = ent; + + if ( ent->owner ) + { + owner = ent->owner; + } + + //save the original speed + speed = VectorNormalize( missile->s.pos.trDelta ); + + if ( ent && owner && owner->client && !owner->client->ps.saberInFlight && + (owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 || (owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_1&&!Q_irand( 0, 3 )) ) ) + {//if high enough defense skill and saber in-hand (100% at level 3, 25% at level 2, 0% at level 1), reflections are perfectly deflected toward an enemy + gentity_t *enemy; + if ( owner->enemy && Q_irand( 0, 3 ) ) + {//toward current enemy 75% of the time + enemy = owner->enemy; + } + else + {//find another enemy + enemy = Jedi_FindEnemyInCone( owner, owner->enemy, 0.3f ); + } + if ( enemy ) + { + vec3_t bullseye; + CalcEntitySpot( enemy, SPOT_HEAD, bullseye ); + bullseye[0] += Q_irand( -4, 4 ); + bullseye[1] += Q_irand( -4, 4 ); + bullseye[2] += Q_irand( -16, 4 ); + VectorSubtract( bullseye, missile->currentOrigin, bounce_dir ); + VectorNormalize( bounce_dir ); + if ( !PM_SaberInParry( owner->client->ps.saberMove ) + && !PM_SaberInReflect( owner->client->ps.saberMove ) + && !PM_SaberInIdle( owner->client->ps.saberMove ) ) + {//a bit more wild + if ( PM_SaberInAttack( owner->client->ps.saberMove ) + || PM_SaberInTransitionAny( owner->client->ps.saberMove ) + || PM_SaberInSpecialAttack( owner->client->ps.torsoAnim ) ) + {//moderately more wild + for ( i = 0; i < 3; i++ ) + { + bounce_dir[i] += Q_flrand( -0.2f, 0.2f ); + } + } + else + {//mildly more wild + for ( i = 0; i < 3; i++ ) + { + bounce_dir[i] += Q_flrand( -0.1f, 0.1f ); + } + } + } + VectorNormalize( bounce_dir ); + reflected = qtrue; + } + } + if ( !reflected ) + { + if ( missile->owner && missile->s.weapon != WP_SABER ) + {//bounce back at them if you can + VectorSubtract( missile->owner->currentOrigin, missile->currentOrigin, bounce_dir ); + VectorNormalize( bounce_dir ); + } + else + { + vec3_t missile_dir; + + VectorSubtract( ent->currentOrigin, missile->currentOrigin, missile_dir ); + VectorCopy( missile->s.pos.trDelta, bounce_dir ); + VectorScale( bounce_dir, DotProduct( forward, missile_dir ), bounce_dir ); + VectorNormalize( bounce_dir ); + } + if ( owner->s.weapon == WP_SABER && owner->client ) + {//saber + if ( owner->client->ps.saberInFlight ) + {//reflecting off a thrown saber is totally wild + for ( i = 0; i < 3; i++ ) + { + bounce_dir[i] += Q_flrand( -0.8f, 0.8f ); + } + } + else if ( owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] <= FORCE_LEVEL_1 ) + {// at level 1 + for ( i = 0; i < 3; i++ ) + { + bounce_dir[i] += Q_flrand( -0.4f, 0.4f ); + } + } + else + {// at level 2 + for ( i = 0; i < 3; i++ ) + { + bounce_dir[i] += Q_flrand( -0.2f, 0.2f ); + } + } + if ( !PM_SaberInParry( owner->client->ps.saberMove ) + && !PM_SaberInReflect( owner->client->ps.saberMove ) + && !PM_SaberInIdle( owner->client->ps.saberMove ) ) + {//a bit more wild + if ( PM_SaberInAttack( owner->client->ps.saberMove ) + || PM_SaberInTransitionAny( owner->client->ps.saberMove ) + || PM_SaberInSpecialAttack( owner->client->ps.torsoAnim ) ) + {//really wild + for ( i = 0; i < 3; i++ ) + { + bounce_dir[i] += Q_flrand( -0.3f, 0.3f ); + } + } + else + {//mildly more wild + for ( i = 0; i < 3; i++ ) + { + bounce_dir[i] += Q_flrand( -0.1f, 0.1f ); + } + } + } + } + else + {//some other kind of reflection + for ( i = 0; i < 3; i++ ) + { + bounce_dir[i] += Q_flrand( -0.2f, 0.2f ); + } + } + } + VectorNormalize( bounce_dir ); + VectorScale( bounce_dir, speed, missile->s.pos.trDelta ); +#ifdef _DEBUG + assert( !_isnan(missile->s.pos.trDelta[0])&&!_isnan(missile->s.pos.trDelta[1])&&!_isnan(missile->s.pos.trDelta[2])); +#endif// _DEBUG + missile->s.pos.trTime = level.time - 10; // move a bit on the very first frame + VectorCopy( missile->currentOrigin, missile->s.pos.trBase ); + if ( missile->s.weapon != WP_SABER ) + {//you are mine, now! + if ( !missile->lastEnemy ) + {//remember who originally shot this missile + missile->lastEnemy = missile->owner; + } + missile->owner = owner; + } + if ( missile->s.weapon == WP_ROCKET_LAUNCHER ) + {//stop homing + missile->e_ThinkFunc = thinkF_NULL; + } +} + +/* +================ +G_BounceRollMissile + +================ +*/ +void G_BounceRollMissile( gentity_t *ent, trace_t *trace ) +{ + vec3_t velocity, normal; + float dot, speedXY, velocityZ, normalZ; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; + EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); + //Do horizontal + //FIXME: Need to roll up, down slopes + velocityZ = velocity[2]; + velocity[2] = 0; + speedXY = VectorLength( velocity );//friction + VectorCopy( trace->plane.normal, normal ); + normalZ = normal[2]; + normal[2] = 0; + dot = DotProduct( velocity, normal ); + VectorMA( velocity, -2*dot, normal, ent->s.pos.trDelta ); + //now do the z reflection + //FIXME: Bobbles when it stops + VectorSet( velocity, 0, 0, velocityZ ); + VectorSet( normal, 0, 0, normalZ ); + dot = DotProduct( velocity, normal )*-1; + if ( dot > 10 ) + { + ent->s.pos.trDelta[2] = dot*0.3f;//not very bouncy + } + else + { + ent->s.pos.trDelta[2] = 0; + } + + // check for stop + if ( speedXY <= 0 ) + { + G_SetOrigin( ent, trace->endpos ); + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + VectorClear( ent->s.apos.trDelta ); + ent->s.apos.trType = TR_STATIONARY; + return; + } + + //FIXME: rolling needs to match direction + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + VectorCopy( ent->s.pos.trDelta, ent->s.apos.trDelta ); + + //remember this spot + VectorCopy( trace->endpos, ent->currentOrigin ); + ent->s.pos.trTime = hitTime - 10; + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + //VectorCopy( trace->plane.normal, ent->pos1 ); +} + +/* +================ +G_BounceMissile + +================ +*/ +void G_BounceMissile( gentity_t *ent, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; + EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta ); + + if ( ent->s.eFlags & EF_BOUNCE_SHRAPNEL ) + { + VectorScale( ent->s.pos.trDelta, 0.25f, ent->s.pos.trDelta ); + ent->s.pos.trType = TR_GRAVITY; + + // check for stop + if ( trace->plane.normal[2] > 0.7 && ent->s.pos.trDelta[2] < 40 ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7 + { + G_SetOrigin( ent, trace->endpos ); + ent->nextthink = level.time + 100; + return; + } + } + else if ( ent->s.eFlags & EF_BOUNCE_HALF ) + { + VectorScale( ent->s.pos.trDelta, 0.5, ent->s.pos.trDelta ); + + // check for stop + if ( trace->plane.normal[2] > 0.7 && ent->s.pos.trDelta[2] < 40 ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7 + { + if ( ent->s.weapon == WP_THERMAL ) + {//roll when you "stop" + ent->s.pos.trType = TR_INTERPOLATE; + } + else + { + G_SetOrigin( ent, trace->endpos ); + ent->nextthink = level.time + 500; + return; + } + } + + if ( ent->s.weapon == WP_THERMAL ) + { + ent->has_bounced = qtrue; + } + } + +#if 0 + // OLD--this looks so wrong. It looked wrong in EF. It just must be wrong. + VectorAdd( ent->currentOrigin, trace->plane.normal, ent->currentOrigin); + + ent->s.pos.trTime = level.time - 10; +#else + // NEW--It would seem that we want to set our trBase to the trace endpos + // and set the trTime to the actual time of impact.... + VectorAdd( trace->endpos, trace->plane.normal, ent->currentOrigin ); + if ( hitTime >= level.time ) + {//trace fraction must have been 1 + ent->s.pos.trTime = level.time - 10; + } + else + { + ent->s.pos.trTime = hitTime - 10; // this is kinda dumb hacking, but it pushes the missile away from the impact plane a bit + } +#endif + + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + VectorCopy( trace->plane.normal, ent->pos1 ); + + if ( ent->s.weapon != WP_SABER + && ent->s.weapon != WP_THERMAL + && ent->e_clThinkFunc != clThinkF_CG_Limb + && ent->e_ThinkFunc != thinkF_LimbThink ) + {//not a saber, bouncing thermal or limb + //now you can damage the guy you came from + ent->owner = NULL; + } +} + +/* +================ +G_MissileImpact + +================ +*/ + +void NoghriGasCloudThink( gentity_t *self ) +{ + self->nextthink = level.time + FRAMETIME; + + AddSightEvent( self->owner, self->currentOrigin, 200, AEL_DANGER, 50 ); + + if ( self->fx_time < level.time ) + { + vec3_t up = {0,0,1}; + G_PlayEffect( "noghri_stick/gas_cloud", self->currentOrigin, up ); + self->fx_time = level.time + 250; + } + + if ( level.time - self->s.time <= 2500 ) + { + if ( !Q_irand( 0, 3-g_spskill->integer ) ) + { + G_RadiusDamage( self->currentOrigin, self->owner, Q_irand( 1, 4 ), self->splashRadius, + self->owner, self->splashMethodOfDeath ); + } + } + + if ( level.time - self->s.time > 3000 ) + { + G_FreeEntity( self ); + } +} + +void G_SpawnNoghriGasCloud( gentity_t *ent ) +{//FIXME: force-pushable/dispersable? + ent->freeAfterEvent = qfalse; + ent->e_TouchFunc = NULL; + //ent->s.loopSound = G_SoundIndex( "sound/weapons/noghri/smoke.wav" ); + //G_SoundOnEnt( ent, CHAN_AUTO, "sound/weapons/noghri/smoke.wav" ); + + G_SetOrigin( ent, ent->currentOrigin ); + ent->e_ThinkFunc = thinkF_NoghriGasCloudThink; + ent->nextthink = level.time + FRAMETIME; + + vec3_t up = {0,0,1}; + G_PlayEffect( "noghri_stick/gas_cloud", ent->currentOrigin, up ); + + ent->fx_time = level.time + 250; + ent->s.time = level.time; +} + +extern void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missleBlock ); +extern void laserTrapStick( gentity_t *ent, vec3_t endpos, vec3_t normal ); +extern qboolean W_AccuracyLoggableWeapon( int weapon, qboolean alt_fire, int mod ); +void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE ) +{ + // impact damage + if ( other->takedamage ) + { + // FIXME: wrong damage direction? + if ( ent->damage ) + { + vec3_t velocity; + + EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity ); + if ( VectorLength( velocity ) == 0 ) + { + velocity[2] = 1; // stepped on a grenade + } + + int damage = ent->damage; + + if( other->client ) + { + class_t npc_class = other->client->NPC_class; + + // If we are a robot and we aren't currently doing the full body electricity... + if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || + npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE || + npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 || //npc_class == CLASS_PROTOCOL ||//no protocol, looks odd + npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY ) + { + // special droid only behaviors + if ( other->client->ps.powerups[PW_SHOCKED] < level.time + 100 ) + { + // ... do the effect for a split second for some more feedback + other->s.powerups |= ( 1 << PW_SHOCKED ); + other->client->ps.powerups[PW_SHOCKED] = level.time + 450; + } + //FIXME: throw some sparks off droids,too + } + } + + G_Damage( other, ent, ent->owner, velocity, + impactPos, damage, + ent->dflags, ent->methodOfDeath, hitLoc); + + if ( ent->s.weapon == WP_DEMP2 ) + {//a hit with demp2 decloaks saboteurs + if ( other && other->client && other->client->NPC_class == CLASS_SABOTEUR ) + {//FIXME: make this disabled cloak hold for some amount of time? + Saboteur_Decloak( other, Q_irand( 3000, 10000 ) ); + if ( ent->methodOfDeath == MOD_DEMP2_ALT ) + {//direct hit with alt disabled cloak forever + if ( other->NPC ) + {//permanently disable the saboteur's cloak + other->NPC->aiFlags &= ~NPCAI_SHIELDS; + } + } + } + } + } + } + + // is it cheaper in bandwidth to just remove this ent and create a new + // one, rather than changing the missile into the explosion? + //G_FreeEntity(ent); + + if ( (other->takedamage && other->client ) || (ent->s.weapon == WP_FLECHETTE && other->contents&CONTENTS_LIGHTSABER) ) + { + G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( normal ) ); + ent->s.otherEntityNum = other->s.number; + } + else + { + G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( normal ) ); + ent->s.otherEntityNum = other->s.number; + } + + VectorCopy( normal, ent->pos1 ); + + if ( ent->owner )//&& ent->owner->s.number == 0 ) + { + //Add the event + AddSoundEvent( ent->owner, ent->currentOrigin, 256, AEL_SUSPICIOUS, qfalse, qtrue ); + AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 75 ); + } + + ent->freeAfterEvent = qtrue; + + // change over to a normal entity right at the point of impact + ent->s.eType = ET_GENERAL; + + //SnapVectorTowards( trace->endpos, ent->s.pos.trBase ); // save net bandwidth + VectorCopy( impactPos, ent->s.pos.trBase ); + + G_SetOrigin( ent, impactPos ); + + // splash damage (doesn't apply to person directly hit) + if ( ent->splashDamage ) + { + G_RadiusDamage( impactPos, ent->owner, ent->splashDamage, ent->splashRadius, + other, ent->splashMethodOfDeath ); + } + + if ( ent->s.weapon == WP_NOGHRI_STICK ) + { + G_SpawnNoghriGasCloud( ent ); + } + + gi.linkentity( ent ); +} + +//------------------------------------------------ +static void G_MissileAddAlerts( gentity_t *ent ) +{ + //Add the event + if ( ent->s.weapon == WP_THERMAL && ((ent->delay-level.time) < 2000 || ent->s.pos.trType == TR_INTERPOLATE) ) + {//a thermal about to explode or rolling + if ( (ent->delay-level.time) < 500 ) + {//half a second before it explodes! + AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER_GREAT, qfalse, qtrue ); + AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER_GREAT, 20 ); + } + else + {//2 seconds until it explodes or it's rolling + AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, qfalse, qtrue ); + AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, 20 ); + } + } + else + { + AddSoundEvent( ent->owner, ent->currentOrigin, 128, AEL_DISCOVERED ); + AddSightEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, 40 ); + } +} + +//------------------------------------------------------ +void G_MissileImpact( gentity_t *ent, trace_t *trace, int hitLoc=HL_NONE ) +{ + gentity_t *other; + vec3_t diff; + + other = &g_entities[trace->entityNum]; + if ( other == ent ) + { + assert(0&&"missile hit itself!!!"); + return; + } + if ( trace->plane.normal[0] == 0.0f && + trace->plane.normal[1] == 0.0f && + trace->plane.normal[2] == 0.0f + ) + {//model moved into missile in flight probably... + trace->plane.normal[0] = -ent->s.pos.trDelta[0]; + trace->plane.normal[1] = -ent->s.pos.trDelta[1]; + trace->plane.normal[2] = -ent->s.pos.trDelta[2]; + VectorNormalize(trace->plane.normal); + } + + if ( ent->owner && (other->takedamage||other->client) ) + { + if ( !ent->lastEnemy || ent->lastEnemy == ent->owner ) + {//a missile that was not reflected or, if so, still is owned by original owner + if( LogAccuracyHit( other, ent->owner ) ) + { + ent->owner->client->ps.persistant[PERS_ACCURACY_HITS]++; + } + if ( ent->owner->client && !ent->owner->s.number ) + { + if ( W_AccuracyLoggableWeapon( ent->s.weapon, qfalse, ent->methodOfDeath ) ) + { + ent->owner->client->sess.missionStats.hits++; + } + } + } + } + // check for bounce + //OR: if the surfaceParm is has a reflect property (magnetic shielding) and the missile isn't an exploding missile + qboolean bounce = !!( (!other->takedamage && (ent->s.eFlags&(EF_BOUNCE|EF_BOUNCE_HALF))) || (((trace->surfaceFlags&SURF_FORCEFIELD)||(other->flags&FL_SHIELDED))&&!ent->splashDamage&&!ent->splashRadius&&ent->s.weapon != WP_NOGHRI_STICK) ); + + if ( ent->dflags & DAMAGE_HEAVY_WEAP_CLASS ) + { + // heavy class missiles generally never bounce. + bounce = qfalse; + } + + if ( other->flags & (FL_DMG_BY_HEAVY_WEAP_ONLY | FL_SHIELDED )) + { + // Dumb assumption, but I guess we must be a shielded ion_cannon?? We should probably verify + // if it's an ion_cannon that's Heavy Weapon only, we don't want to make it shielded do we...? + if ( (!strcmp( "misc_ion_cannon", other->classname )) && (other->flags & FL_SHIELDED) ) + { + // Anything will bounce off of us. + bounce = qtrue; + + // Not exactly the debounce time, but rather the impact time for the shield effect...play effect for 1 second + other->painDebounceTime = level.time + 1000; + } + } + + if ( ent->s.weapon == WP_DEMP2 ) + { + // demp2 shots can never bounce + bounce = qfalse; + + // in fact, alt-charge shots will not call the regular impact functions + if ( ent->alt_fire ) + { + // detonate at the trace end + VectorCopy( trace->endpos, ent->currentOrigin ); + VectorCopy( trace->plane.normal, ent->pos1 ); + DEMP2_AltDetonate( ent ); + return; + } + } + + if ( bounce ) + { + // Check to see if there is a bounce count + if ( ent->bounceCount ) + { + // decrement number of bounces and then see if it should be done bouncing + if ( !(--ent->bounceCount) ) { + // He (or she) will bounce no more (after this current bounce, that is). + ent->s.eFlags &= ~( EF_BOUNCE | EF_BOUNCE_HALF ); + } + } + + if ( other->NPC ) + { + G_Damage( other, ent, ent->owner, ent->currentOrigin, ent->s.pos.trDelta, 0, DAMAGE_NO_DAMAGE, MOD_UNKNOWN ); + } + + G_BounceMissile( ent, trace ); + + if ( ent->owner )//&& ent->owner->s.number == 0 ) + { + G_MissileAddAlerts( ent ); + } +#ifdef _IMMERSION + G_MissileBounceEffect( ent, (other->contents & CONTENTS_LIGHTSABER && !other->owner->s.saberInFlight ? other->owner->s.number : -1), trace->endpos, trace->plane.normal, trace->entityNum==ENTITYNUM_WORLD ); +#else + G_MissileBounceEffect( ent, trace->endpos, trace->plane.normal, trace->entityNum==ENTITYNUM_WORLD ); +#endif // _IMMERSION + + return; + } + + // I would glom onto the EF_BOUNCE code section above, but don't feel like risking breaking something else + if ( (!other->takedamage && ( ent->s.eFlags&(EF_BOUNCE_SHRAPNEL) ) ) + || ((trace->surfaceFlags&SURF_FORCEFIELD)&&!ent->splashDamage&&!ent->splashRadius) ) + { + if ( !(other->contents&CONTENTS_LIGHTSABER) + || g_spskill->integer <= 0//on easy, it reflects all shots + || (g_spskill->integer == 1 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 )//on medium it won't reflect flechette or demp shots + || (g_spskill->integer >= 2 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 && ent->s.weapon != WP_BOWCASTER && ent->s.weapon != WP_REPEATER )//on hard it won't reflect flechette, demp, repeater or bowcaster shots + ) + { + G_BounceMissile( ent, trace ); + + if ( --ent->bounceCount < 0 ) + { + ent->s.eFlags &= ~EF_BOUNCE_SHRAPNEL; + } +#ifdef _IMMERSION + // might deflect flechette spam in bespin_platform.bsp + G_MissileBounceEffect( ent, (other->contents & CONTENTS_LIGHTSABER && !other->owner->s.saberInFlight ? other->owner->s.number : -1), trace->endpos, trace->plane.normal, trace->entityNum==ENTITYNUM_WORLD ); +#else + G_MissileBounceEffect( ent, trace->endpos, trace->plane.normal, trace->entityNum==ENTITYNUM_WORLD ); +#endif // _IMMERSION + return; + } + } + + if ( (!other->takedamage || (other->client && other->health <= 0)) + && ent->s.weapon == WP_THERMAL + && !ent->alt_fire ) + {//rolling thermal det - FIXME: make this an eFlag like bounce & stick!!! + //G_BounceRollMissile( ent, trace ); + if ( ent->owner )//&& ent->owner->s.number == 0 ) + { + G_MissileAddAlerts( ent ); + } + //gi.linkentity( ent ); + return; + } + + // check for sticking + if ( ent->s.eFlags & EF_MISSILE_STICK ) + { + if ( ent->owner )//&& ent->owner->s.number == 0 ) + { + //Add the event + if ( ent->s.weapon == WP_TRIP_MINE ) + { + AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius/2, AEL_DISCOVERED/*AEL_DANGER*/, qfalse, qtrue ); + AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DISCOVERED/*AEL_DANGER*/, 60 ); + /* + AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, qfalse, qtrue ); + AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, 60 ); + */ + } + else + { + AddSoundEvent( ent->owner, ent->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue ); + AddSightEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, 10 ); + } + } + + G_MissileStick( ent, other, trace ); + return; + } + + // check for hitting a lightsaber + if ( other->contents & CONTENTS_LIGHTSABER ) + { + if ( other->owner && !other->owner->s.number && other->owner->client ) + { + other->owner->client->sess.missionStats.saberBlocksCnt++; + } + if ( ( g_spskill->integer <= 0//on easy, it reflects all shots + || (g_spskill->integer == 1 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 )//on medium it won't reflect flechette or demp shots + || (g_spskill->integer >= 2 && ent->s.weapon != WP_FLECHETTE && ent->s.weapon != WP_DEMP2 && ent->s.weapon != WP_BOWCASTER && ent->s.weapon != WP_REPEATER )//on hard it won't reflect flechette, demp, repeater or bowcaster shots + ) + && (!ent->splashDamage || !ent->splashRadius) //this would be cool, though, to "bat" the thermal det away... + && ent->s.weapon != WP_NOGHRI_STICK )//gas bomb, don't reflect + { + //FIXME: take other's owner's FP_SABER_DEFENSE into account here somehow? + if ( !other->owner || !other->owner->client || other->owner->client->ps.saberInFlight || InFront( ent->currentOrigin, other->owner->currentOrigin, other->owner->client->ps.viewangles, SABER_REFLECT_MISSILE_CONE ) )//other->owner->s.number != 0 || + {//Jedi cannot block shots from behind! + int blockChance = 0; + switch ( other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) + {//level 1 reflects 50% of the time, level 2 reflects 75% of the time + case FORCE_LEVEL_3: + blockChance = 10; + break; + case FORCE_LEVEL_2: + blockChance = 3; + break; + case FORCE_LEVEL_1: + blockChance = 1; + break; + } + if ( blockChance && (other->owner->client->ps.forcePowersActive&(1<owner->client->ps.forcePowerLevel[FP_SPEED]*2; + } + if ( Q_irand( 0, blockChance ) ) + { + VectorSubtract(ent->currentOrigin, other->currentOrigin, diff); + VectorNormalize(diff); + G_ReflectMissile( other, ent, diff); + //WP_SaberBlock( other, ent->currentOrigin, qtrue ); + if ( other->owner && other->owner->client ) + { + other->owner->client->ps.saberEventFlags |= SEF_DEFLECTED; + } + //do the effect + VectorCopy( ent->s.pos.trDelta, diff ); + VectorNormalize( diff ); +#ifdef _IMMERSION + G_MissileReflectEffect( ent, other->owner->s.number, trace->endpos, trace->plane.normal ); +#else + G_MissileReflectEffect( ent, trace->endpos, trace->plane.normal ); +#endif // _IMMERSION + return; + } + } + } + else + {//still do the bounce effect +#ifdef _IMMERSION + G_MissileReflectEffect( ent, other->owner->s.number, trace->endpos, trace->plane.normal ); +#else + G_MissileReflectEffect( ent, trace->endpos, trace->plane.normal ); +#endif // _IMMERSION + } + } + + G_MissileImpacted( ent, other, trace->endpos, trace->plane.normal, hitLoc ); +} + +/* +================ +G_ExplodeMissile + +Explode a missile without an impact +================ +*/ +void G_ExplodeMissile( gentity_t *ent ) +{ + vec3_t dir; + vec3_t origin; + + EvaluateTrajectory( &ent->s.pos, level.time, origin ); + SnapVector( origin ); + G_SetOrigin( ent, origin ); + + // we don't have a valid direction, so just point straight up + dir[0] = dir[1] = 0; + dir[2] = 1; + + if ( ent->owner )//&& ent->owner->s.number == 0 ) + { + //Add the event + AddSoundEvent( ent->owner, ent->currentOrigin, 256, AEL_DISCOVERED, qfalse, qtrue );//FIXME: are we on ground or not? + AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 100 ); + } +/* ent->s.eType = ET_GENERAL; + G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) ); + + ent->freeAfterEvent = qtrue;*/ + + // splash damage + if ( ent->splashDamage ) + { + G_RadiusDamage( ent->currentOrigin, ent->owner, ent->splashDamage, ent->splashRadius, NULL + , ent->splashMethodOfDeath ); + } + + G_FreeEntity(ent); + //gi.linkentity( ent ); +} + + +void G_RunStuckMissile( gentity_t *ent ) +{ + if ( ent->takedamage ) + { + if ( ent->s.groundEntityNum >= 0 && ent->s.groundEntityNum < ENTITYNUM_WORLD ) + { + gentity_t *other = &g_entities[ent->s.groundEntityNum]; + + if ( (!VectorCompare( vec3_origin, other->s.pos.trDelta ) && other->s.pos.trType != TR_STATIONARY) || + (!VectorCompare( vec3_origin, other->s.apos.trDelta ) && other->s.apos.trType != TR_STATIONARY) ) + {//thing I stuck to is moving or rotating now, kill me + G_Damage( ent, other, other, NULL, NULL, 99999, 0, MOD_CRUSH ); + return; + } + } + } + // check think function + G_RunThink( ent ); +} + +/* +================== + +G_GroundTrace + +================== +*/ +int G_GroundTrace( gentity_t *ent, pml_t *pPml ) +{ + vec3_t point; + trace_t trace; + + point[0] = ent->currentOrigin[0]; + point[1] = ent->currentOrigin[1]; + point[2] = ent->currentOrigin[2] - 0.25; + + gi.trace ( &trace, ent->currentOrigin, ent->mins, ent->maxs, point, ent->s.number, ent->clipmask ); + pPml->groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if ( trace.allsolid ) + { + pPml->groundPlane = qfalse; + pPml->walking = qfalse; + return ENTITYNUM_NONE; + } + + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0 ) + { + pPml->groundPlane = qfalse; + pPml->walking = qfalse; + return ENTITYNUM_NONE; + } + + // check if getting thrown off the ground + if ( ent->s.pos.trDelta[2] > 0 && DotProduct( ent->s.pos.trDelta, trace.plane.normal ) > 10 ) + { + pPml->groundPlane = qfalse; + pPml->walking = qfalse; + return ENTITYNUM_NONE; + } + + // slopes that are too steep will not be considered onground + if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) + { + pPml->groundPlane = qtrue; + pPml->walking = qfalse; + return ENTITYNUM_NONE; + } + + pPml->groundPlane = qtrue; + pPml->walking = qtrue; + + /* + if ( ent->s.groundEntityNum == ENTITYNUM_NONE ) + { + // just hit the ground + } + */ + + return trace.entityNum; +} + +void G_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) +{ + float backoff; + float change; + int i; + + backoff = DotProduct (in, normal); + + if ( backoff < 0 ) { + backoff *= overbounce; + } else { + backoff /= overbounce; + } + + for ( i=0 ; i<3 ; i++ ) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + } +} +/* +================== + +G_RollMissile + +reworking the rolling object code, +still needs to stop bobbling up & down, +need to get roll angles right, +and need to maybe make the transfer of velocity happen on impacts? +Also need bounce sound for bounces off a floor. +Also need to not bounce as much off of enemies +Also gets stuck inside thrower if looking down when thrown + +================== +*/ +#define MAX_CLIP_PLANES 5 +#define BUMPCLIP 1.5f +void G_RollMissile( gentity_t *ent ) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + vec3_t clipVelocity; + int i, j, k; + trace_t trace; + vec3_t end; + float time_left; + float into; + vec3_t endVelocity; + vec3_t endClipVelocity; + pml_t objPML; + float bounceAmt = BUMPCLIP; + gentity_t *hitEnt = NULL; + + memset( &objPML, 0, sizeof( objPML ) ); + + G_GroundTrace( ent, &objPML ); + + objPML.frametime = (level.time - level.previousTime)*0.001; + + numbumps = 4; + + VectorCopy ( ent->s.pos.trDelta, primal_velocity ); + + VectorCopy( ent->s.pos.trDelta, endVelocity ); + endVelocity[2] -= g_gravity->value * objPML.frametime; + ent->s.pos.trDelta[2] = ( ent->s.pos.trDelta[2] + endVelocity[2] ) * 0.5; + primal_velocity[2] = endVelocity[2]; + if ( objPML.groundPlane ) + {//FIXME: never happens! + // slide along the ground plane + G_ClipVelocity( ent->s.pos.trDelta, objPML.groundTrace.plane.normal, ent->s.pos.trDelta, BUMPCLIP ); + VectorScale( ent->s.pos.trDelta, 0.9f, ent->s.pos.trDelta ); + } + + time_left = objPML.frametime; + + // never turn against the ground plane + if ( objPML.groundPlane ) + { + numplanes = 1; + VectorCopy( objPML.groundTrace.plane.normal, planes[0] ); + } + else + { + numplanes = 0; + } + + // never turn against original velocity + /* + VectorNormalize2( ent->s.pos.trDelta, planes[numplanes] ); + numplanes++; + */ + + for ( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) + { + // calculate position we are trying to move to + VectorMA( ent->currentOrigin, time_left, ent->s.pos.trDelta, end ); + + // see if we can make it there + gi.trace ( &trace, ent->currentOrigin, ent->mins, ent->maxs, end, ent->s.number, ent->clipmask, G2_RETURNONHIT, 10 ); + + //TEMP HACK: + //had to move this up above the trace.allsolid check now that for some reason ghoul2 impacts tell me I'm allsolid?! + //this needs to be fixed, really + if ( trace.entityNum < ENTITYNUM_WORLD ) + {//hit another ent + hitEnt = &g_entities[trace.entityNum]; + if ( hitEnt && (hitEnt->takedamage || (hitEnt->contents&CONTENTS_LIGHTSABER) ) ) + { + G_MissileImpact( ent, &trace ); + if ( ent->s.eType == ET_GENERAL ) + {//exploded + return; + } + } + } + + if ( trace.allsolid ) + { + // entity is completely trapped in another solid + //FIXME: this happens a lot now when we hit a G2 ent... WTF? + ent->s.pos.trDelta[2] = 0; // don't build up falling damage, but allow sideways acceleration + return;// qtrue; + } + + if ( trace.fraction > 0 ) + { + // actually covered some distance + VectorCopy( trace.endpos, ent->currentOrigin ); + } + + if ( trace.fraction == 1 ) + { + break; // moved the entire distance + } + + //pm->ps->pm_flags |= PMF_BUMPED; + + // save entity for contact + //PM_AddTouchEnt( trace.entityNum ); + + //Hit it + /* + if ( PM_ClientImpact( trace.entityNum, qtrue ) ) + { + continue; + } + */ + + time_left -= time_left * trace.fraction; + + if ( numplanes >= MAX_CLIP_PLANES ) + { + // this shouldn't really happen + VectorClear( ent->s.pos.trDelta ); + return;// qtrue; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for ( i = 0 ; i < numplanes ; i++ ) + { + if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) + { + VectorAdd( trace.plane.normal, ent->s.pos.trDelta, ent->s.pos.trDelta ); + break; + } + } + if ( i < numplanes ) + { + continue; + } + VectorCopy( trace.plane.normal, planes[numplanes] ); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + if ( &g_entities[trace.entityNum] != NULL && g_entities[trace.entityNum].client ) + {//hit a person, bounce off much less + bounceAmt = OVERCLIP; + } + else + { + bounceAmt = BUMPCLIP; + } + + // find a plane that it enters + for ( i = 0 ; i < numplanes ; i++ ) + { + into = DotProduct( ent->s.pos.trDelta, planes[i] ); + if ( into >= 0.1 ) + { + continue; // move doesn't interact with the plane + } + + // see how hard we are hitting things + if ( -into > pml.impactSpeed ) + { + pml.impactSpeed = -into; + } + + // slide along the plane + G_ClipVelocity( ent->s.pos.trDelta, planes[i], clipVelocity, bounceAmt ); + + // slide along the plane + G_ClipVelocity( endVelocity, planes[i], endClipVelocity, bounceAmt ); + + // see if there is a second plane that the new move enters + for ( j = 0 ; j < numplanes ; j++ ) + { + if ( j == i ) + { + continue; + } + if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) + { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + G_ClipVelocity( clipVelocity, planes[j], clipVelocity, bounceAmt ); + G_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, bounceAmt ); + + // see if it goes back into the first clip plane + if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) + { + continue; + } + + // slide the original velocity along the crease + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, ent->s.pos.trDelta ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the the new move enters + for ( k = 0 ; k < numplanes ; k++ ) + { + if ( k == i || k == j ) + { + continue; + } + if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) + { + continue; // move doesn't interact with the plane + } + + // stop dead at a triple plane interaction + VectorClear( ent->s.pos.trDelta ); + return;// qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, ent->s.pos.trDelta ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + VectorScale( endVelocity, 0.975f, endVelocity ); + } + + VectorCopy( endVelocity, ent->s.pos.trDelta ); + + // don't change velocity if in a timer (FIXME: is this correct?) + /* + if ( pm->ps->pm_time ) + { + VectorCopy( primal_velocity, ent->s.pos.trDelta ); + } + */ + + return;// ( bumpcount != 0 ); +} +/* +================ +G_RunMissile + +================ +*/ +void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg ); +void G_RunMissile( gentity_t *ent ) +{ + vec3_t oldOrg; + trace_t tr; + int trHitLoc=HL_NONE; + + if ( (ent->s.eFlags&EF_HELD_BY_SAND_CREATURE) ) + {//in a sand creature's mouth + if ( ent->activator ) + { + mdxaBone_t boltMatrix; + // Getting the bolt here + //in hand + vec3_t scAngles = {0}; + scAngles[YAW] = ent->activator->currentAngles[YAW]; + gi.G2API_GetBoltMatrix( ent->activator->ghoul2, ent->activator->playerModel, ent->activator->gutBolt, + &boltMatrix, scAngles, ent->activator->currentOrigin, (cg.time?cg.time:level.time), + NULL, ent->activator->s.modelScale ); + // Storing ent position, bolt position, and bolt axis + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->currentOrigin ); + G_SetOrigin( ent, ent->currentOrigin ); + } + // check think function + G_RunThink( ent ); + return; + } + + VectorCopy( ent->currentOrigin, oldOrg ); + + // get current position + if ( ent->s.pos.trType == TR_INTERPOLATE ) + {//rolling missile? + //FIXME: WTF?!! Sticks to stick missiles? + //FIXME: they stick inside the player + G_RollMissile( ent ); + if ( ent->s.eType != ET_GENERAL ) + {//didn't explode + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + gi.trace( &tr, oldOrg, ent->mins, ent->maxs, ent->currentOrigin, ent->s.number, ent->clipmask, G2_RETURNONHIT, 10 ); + if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) ) + { + //VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + VectorClear( ent->s.apos.trDelta ); + } + else + { + vec3_t ang, fwdDir, rtDir; + float speed; + + ent->s.apos.trType = TR_INTERPOLATE; + VectorSet( ang, 0, ent->s.apos.trBase[1], 0 ); + AngleVectors( ang, fwdDir, rtDir, NULL ); + speed = VectorLength( ent->s.pos.trDelta )*4; + + //HMM, this works along an axis-aligned dir, but not along diagonals + //This is because when roll gets to 90, pitch becomes yaw, and vice-versa + //Maybe need to just set the angles directly? + ent->s.apos.trDelta[0] = DotProduct( fwdDir, ent->s.pos.trDelta ); + ent->s.apos.trDelta[1] = 0;//never spin! + ent->s.apos.trDelta[2] = DotProduct( rtDir, ent->s.pos.trDelta ); + + VectorNormalize( ent->s.apos.trDelta ); + VectorScale( ent->s.apos.trDelta, speed, ent->s.apos.trDelta ); + + ent->s.apos.trTime = level.previousTime; + } + } + } + else + { + vec3_t origin; + EvaluateTrajectory( &ent->s.pos, level.time, origin ); + // trace a line from the previous position to the current position, + // ignoring interactions with the missile owner + gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, + ent->owner ? ent->owner->s.number : ent->s.number, ent->clipmask, G2_COLLIDE, 10 ); + + if ( tr.entityNum != ENTITYNUM_NONE ) + { + gentity_t *other = &g_entities[tr.entityNum]; + // check for hitting a lightsaber + if ( other->contents & CONTENTS_LIGHTSABER ) + {//hit a lightsaber bbox + if ( other->owner + && other->owner->client + && !other->owner->client->ps.saberInFlight + && ( Q_irand( 0, (other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]*other->owner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) ) == 0 + || !InFront( ent->currentOrigin, other->owner->currentOrigin, other->owner->client->ps.viewangles, SABER_REFLECT_MISSILE_CONE ) ) )//other->owner->s.number == 0 && + {//Jedi cannot block shots from behind! + //re-trace from here, ignoring the lightsaber + gi.trace( &tr, tr.endpos, ent->mins, ent->maxs, origin, tr.entityNum, ent->clipmask, G2_RETURNONHIT, 10 ); + } + } + } + + VectorCopy( tr.endpos, ent->currentOrigin ); + } + + // get current angles + VectorMA( ent->s.apos.trBase, (level.time - ent->s.apos.trTime) * 0.001, ent->s.apos.trDelta, ent->s.apos.trBase ); + + //FIXME: Rolling things hitting G2 polys is weird + /////////////////////////////////////////////////////// +//? if ( tr.fraction != 1 ) + { + // did we hit or go near a Ghoul2 model? +// qboolean hitModel = qfalse; + for (int i=0; i < MAX_G2_COLLISIONS; i++) + { + if (tr.G2CollisionMap[i].mEntityNum == -1) + { + break; + } + + CCollisionRecord &coll = tr.G2CollisionMap[i]; + gentity_t *hitEnt = &g_entities[coll.mEntityNum]; + + // process collision records here... + // make sure we only do this once, not for all the entrance wounds we might generate + if ((coll.mFlags & G2_FRONTFACE)/* && !(hitModel)*/ && hitEnt->health) + { + if (trHitLoc==HL_NONE) + { + G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &trHitLoc, coll.mCollisionPosition, NULL, NULL, ent->methodOfDeath ); + } + + break; // NOTE: the way this whole section was working, it would only get inside of this IF once anyway, might as well break out now + } + } + } +///////////////////////////////////////////////////////// + + if ( tr.startsolid ) + { + tr.fraction = 0; + } + + gi.linkentity( ent ); + + if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) ) + {//stuck missiles should check some special stuff + G_RunStuckMissile( ent ); + return; + } + + // check think function + G_RunThink( ent ); + + if ( ent->s.eType != ET_MISSILE ) + { + return; // exploded + } + + if ( ent->mass ) + { + G_MoverTouchPushTriggers( ent, oldOrg ); + } + /* + if ( !(ent->s.eFlags & EF_TELEPORT_BIT) ) + { + G_MoverTouchTeleportTriggers( ent, oldOrg ); + if ( ent->s.eFlags & EF_TELEPORT_BIT ) + {//was teleported + return; + } + } + else + { + ent->s.eFlags &= ~EF_TELEPORT_BIT; + } + */ + + AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED, 75 );//wakes them up when see a shot passes in front of them + if ( !Q_irand( 0, 10 ) ) + {//not so often... + if ( ent->splashDamage && ent->splashRadius ) + {//I'm an exploder, let people around me know danger is coming + if ( ent->s.weapon == WP_TRIP_MINE ) + {//??? + } + else + { + if ( ent->s.weapon == WP_ROCKET_LAUNCHER && ent->e_ThinkFunc == thinkF_rocketThink ) + {//homing rocket- run like hell! + AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER_GREAT, 50 ); + } + else + { + AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER, 50 ); + } + AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius, AEL_DANGER ); + } + } + else + {//makes them run from near misses + AddSightEvent( ent->owner, ent->currentOrigin, 48, AEL_DANGER, 50 ); + } + } + + if ( tr.fraction == 1 ) + { + if ( ent->s.weapon == WP_THERMAL && ent->s.pos.trType == TR_INTERPOLATE ) + {//a rolling thermal that didn't hit anything + G_MissileAddAlerts( ent ); + } + return; + } + + // never explode or bounce on sky + if ( tr.surfaceFlags & SURF_NOIMPACT ) + { + G_FreeEntity( ent ); + return; + } + + G_MissileImpact( ent, &tr, trHitLoc ); +} \ No newline at end of file diff --git a/code/game/g_mover.cpp b/code/game/g_mover.cpp new file mode 100644 index 0000000..25fe1b7 --- /dev/null +++ b/code/game/g_mover.cpp @@ -0,0 +1,2657 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +#include "g_functions.h" +#include "objectives.h" + +#include "../Icarus/IcarusInterface.h" + + +int BMS_START = 0; +int BMS_MID = 1; +int BMS_END = 2; + +extern void G_SetEnemy( gentity_t *self, gentity_t *enemy ); +void CalcTeamDoorCenter ( gentity_t *ent, vec3_t center ); +void InitMover( gentity_t *ent ) ; + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +void MatchTeam( gentity_t *teamLeader, int moverState, int time ); +extern qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2); + +typedef struct { + gentity_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_GENTITIES], *pushed_p; + + +/* +------------------------- +G_SetLoopSound +------------------------- +*/ + +void G_PlayDoorLoopSound( gentity_t *ent ) +{ + if ( VALIDSTRING( ent->soundSet ) == false ) + return; + + sfxHandle_t sfx = CAS_GetBModelSound( ent->soundSet, BMS_MID ); + + if ( sfx == -1 ) + { + ent->s.loopSound = 0; + return; + } + + ent->s.loopSound = sfx; +} + +/* +------------------------- +G_PlayDoorSound +------------------------- +*/ + +void G_PlayDoorSound( gentity_t *ent, int type ) +{ + if ( VALIDSTRING( ent->soundSet ) == false ) + return; + + sfxHandle_t sfx = CAS_GetBModelSound( ent->soundSet, type ); + + if ( sfx == -1 ) + return; + + vec3_t doorcenter; + CalcTeamDoorCenter( ent, doorcenter ); + if ( ent->activator && ent->activator->client && ent->activator->client->playerTeam == TEAM_PLAYER ) + { + AddSoundEvent( ent->activator, doorcenter, 128, AEL_MINOR, qfalse, qtrue ); + } + + G_AddEvent( ent, EV_BMODEL_SOUND, sfx ); +} + +/* +============ +G_TestEntityPosition + +============ +*/ +gentity_t *G_TestEntityPosition( gentity_t *ent ) { + trace_t tr; + int mask; + + if ( (ent->client && ent->health <= 0) || !ent->clipmask ) + {//corpse or something with no clipmask + mask = MASK_SOLID; + } + else + { + mask = ent->clipmask; + } + if ( ent->client ) + { + gi.trace( &tr, ent->client->ps.origin, ent->mins, ent->maxs, ent->client->ps.origin, ent->s.number, mask ); + } + else + { + if ( ent->s.eFlags & EF_MISSILE_STICK )//Arggh, this is dumb...but when it used the bbox, it was pretty much always in solid when it is riding something..which is wrong..so I changed it to basically be a point contents check + { + gi.trace( &tr, ent->s.pos.trBase, vec3_origin, vec3_origin, ent->s.pos.trBase, ent->s.number, mask ); + } + else + { + gi.trace( &tr, ent->s.pos.trBase, ent->mins, ent->maxs, ent->s.pos.trBase, ent->s.number, mask ); + } + } + + if (tr.startsolid) + return &g_entities[ tr.entityNum ]; + + return NULL; +} + + +/* +================== +G_TryPushingEntity + +Returns qfalse if the move is blocked +================== +*/ +extern qboolean G_OkayToRemoveCorpse( gentity_t *self ); + +qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, vec3_t amove ) { + vec3_t forward, right, up; + vec3_t org, org2, move2; + gentity_t *block; + + /* + // EF_MOVER_STOP will just stop when contacting another entity + // instead of pushing it, but entities can still ride on top of it + if ( ( pusher->s.eFlags & EF_MOVER_STOP ) && + check->s.groundEntityNum != pusher->s.number ) { + return qfalse; + } + */ + + // save off the old position + if (pushed_p > &pushed[MAX_GENTITIES]) { + G_Error( "pushed_p > &pushed[MAX_GENTITIES]" ); + } + pushed_p->ent = check; + VectorCopy (check->s.pos.trBase, pushed_p->origin); + VectorCopy (check->s.apos.trBase, pushed_p->angles); + if ( check->client ) { + pushed_p->deltayaw = check->client->ps.delta_angles[YAW]; + VectorCopy (check->client->ps.origin, pushed_p->origin); + } + pushed_p++; + + // we need this for pushing things later + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + + // try moving the contacted entity + VectorAdd (check->s.pos.trBase, move, check->s.pos.trBase); + if (check->client) { + // make sure the client's view rotates when on a rotating mover + check->client->ps.delta_angles[YAW] += ANGLE2SHORT(amove[YAW]); + } + + // figure movement due to the pusher's amove + VectorSubtract (check->s.pos.trBase, pusher->currentOrigin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (check->s.pos.trBase, move2, check->s.pos.trBase); + if ( check->client ) { + VectorAdd (check->client->ps.origin, move, check->client->ps.origin); + VectorAdd (check->client->ps.origin, move2, check->client->ps.origin); + } + + // may have pushed them off an edge + if ( check->s.groundEntityNum != pusher->s.number ) { + check->s.groundEntityNum = ENTITYNUM_NONE; + } + + /* + if ( check->client && check->health <= 0 && check->contents == CONTENTS_CORPSE ) + {//sigh... allow pushing corpses into walls... problem is, they'll be stuck in there... maybe just remove them? + return qtrue; + } + */ + block = G_TestEntityPosition( check ); + if (!block) { + // pushed ok + if ( check->client ) { + VectorCopy( check->client->ps.origin, check->currentOrigin ); + } else { + VectorCopy( check->s.pos.trBase, check->currentOrigin ); + } + gi.linkentity (check); + return qtrue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // Sliding trapdoors can cause this. + VectorCopy( (pushed_p-1)->origin, check->s.pos.trBase); + if ( check->client ) { + VectorCopy( (pushed_p-1)->origin, check->client->ps.origin); + } + VectorCopy( (pushed_p-1)->angles, check->s.apos.trBase ); + block = G_TestEntityPosition (check); + if ( !block ) { + check->s.groundEntityNum = ENTITYNUM_NONE; + pushed_p--; + return qtrue; + } + + // blocked + if ( pusher->damage ) + {//Do damage + if ( (pusher->spawnflags&MOVER_CRUSHER)//a crusher + && check->s.clientNum >= MAX_CLIENTS//not the player + && check->client //NPC + && check->health <= 0 //dead + && G_OkayToRemoveCorpse( check ) )//okay to remove him + {//crusher stuck on a non->player corpse that does not have a key and is not running a script + G_FreeEntity( check ); + } + else + { + G_Damage(check, pusher, pusher->activator, move, check->currentOrigin, pusher->damage, 0, MOD_CRUSH ); + } + } + + return qfalse; +} + + +/* +============ +G_MoverPush + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +If qfalse is returned, *obstacle will be the blocking entity +============ +*/ +qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **obstacle ) { + qboolean notMoving; + int i, e; + int listedEntities; + vec3_t mins, maxs; + vec3_t pusherMins, pusherMaxs, totalMins, totalMaxs; + pushed_t *p; + gentity_t *entityList[MAX_GENTITIES]; + gentity_t *check; + + *obstacle = NULL; + + + if ( !pusher->bmodel ) + {//misc_model_breakable + VectorAdd( pusher->currentOrigin, pusher->mins, pusherMins ); + VectorAdd( pusher->currentOrigin, pusher->maxs, pusherMaxs ); + } + + // mins/maxs are the bounds at the destination + // totalMins / totalMaxs are the bounds for the entire move + if ( pusher->currentAngles[0] || pusher->currentAngles[1] || pusher->currentAngles[2] + || amove[0] || amove[1] || amove[2] ) + { + float radius; + + radius = RadiusFromBounds( pusher->mins, pusher->maxs ); + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = pusher->currentOrigin[i] + move[i] - radius; + maxs[i] = pusher->currentOrigin[i] + move[i] + radius; + totalMins[i] = mins[i] - move[i]; + totalMaxs[i] = maxs[i] - move[i]; + } + } + else + { + for (i=0 ; i<3 ; i++) + { + mins[i] = pusher->absmin[i] + move[i]; + maxs[i] = pusher->absmax[i] + move[i]; + } + + VectorCopy( pusher->absmin, totalMins ); + VectorCopy( pusher->absmax, totalMaxs ); + for (i=0 ; i<3 ; i++) + { + if ( move[i] > 0 ) + { + totalMaxs[i] += move[i]; + } + else + { + totalMins[i] += move[i]; + } + } + } + + // unlink the pusher so we don't get it in the entityList + gi.unlinkentity( pusher ); + + listedEntities = gi.EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES ); + + // move the pusher to it's final position + VectorAdd( pusher->currentOrigin, move, pusher->currentOrigin ); + VectorAdd( pusher->currentAngles, amove, pusher->currentAngles ); + gi.linkentity( pusher ); + + notMoving = (VectorCompare( vec3_origin, move )&&VectorCompare( vec3_origin, amove )); + + // see if any solid entities are inside the final position + for ( e = 0 ; e < listedEntities ; e++ ) { + check = entityList[ e ]; + + if (( check->s.eFlags & EF_MISSILE_STICK ) && (notMoving || check->s.groundEntityNum < 0 || check->s.groundEntityNum >= ENTITYNUM_NONE )) + { + // special case hack for sticky things, destroy it if we aren't attached to the thing that is moving, but the moving thing is pushing us + G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH ); + continue; + } + + // only push items and players + if ( check->s.eType != ET_ITEM ) + { + //FIXME: however it allows items to be pushed through stuff, do same for corpses? + if ( check->s.eType != ET_PLAYER ) + { + if ( !( check->s.eFlags & EF_MISSILE_STICK )) + { + // cannot be pushed by this mover + continue; + } + } + /* + else if ( check->health <= 0 ) + {//For now, don't push on dead players + continue; + } + */ + else if ( !pusher->bmodel ) + { + vec3_t checkMins, checkMaxs; + + VectorAdd( check->currentOrigin, check->mins, checkMins ); + VectorAdd( check->currentOrigin, check->maxs, checkMaxs ); + + if ( G_BoundsOverlap( checkMins, checkMaxs, pusherMins, pusherMaxs ) ) + {//They're inside me already, no push - FIXME: we're testing a moves spot, aren't we, so we could have just moved inside them? + continue; + } + } + } + + + if ( check->maxs[0] - check->mins[0] <= 0 && + check->maxs[1] - check->mins[1] <= 0 && + check->maxs[2] - check->mins[2] <= 0 ) + {//no size, don't push + continue; + } + + // if the entity is standing on the pusher, it will definitely be moved + if ( check->s.groundEntityNum != pusher->s.number ) { + // 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 + // this does allow a fast moving object to pass through a thin entity... + if ( G_TestEntityPosition( check ) != pusher ) + { + continue; + } + } + + if ( ((pusher->spawnflags&2)&&!Q_stricmp("func_breakable",pusher->classname)) + ||((pusher->spawnflags&16)&&!Q_stricmp("func_static",pusher->classname)) ) + {//ugh, avoid stricmp with a unique flag + //Damage on impact + if ( pusher->damage ) + {//Do damage + G_Damage( check, pusher, pusher->activator, move, check->currentOrigin, pusher->damage, 0, MOD_CRUSH ); + if ( pusher->health >= 0 && pusher->takedamage && !(pusher->spawnflags&1) ) + {//do some damage to me, too + G_Damage( pusher, check, pusher->activator, move, pusher->s.pos.trBase, floor(pusher->damage/4.0f), 0, MOD_CRUSH ); + } + } + } + // really need a flag like MOVER_TOUCH that calls the ent's touch function here, instead of this stricmp crap + else if ( (pusher->spawnflags&2) && !Q_stricmp( "func_rotating", pusher->classname ) ) + { + GEntity_TouchFunc( pusher, check, NULL ); + continue; // don't want it blocking so skip past it + } + + vec3_t oldOrg; + + VectorCopy( check->s.pos.trBase, oldOrg ); + + // the entity needs to be pushed + if ( G_TryPushingEntity( check, pusher, move, amove ) ) + { + // the mover wasn't blocked + if ( check->s.eFlags & EF_MISSILE_STICK ) + { + if ( !VectorCompare( oldOrg, check->s.pos.trBase )) + { + // and the rider was actually pushed, so interpolate position change to smooth out the ride + check->s.pos.trType = TR_INTERPOLATE; + continue; + } + //else..the mover wasn't blocked & we are riding the mover & but the rider did not move even though the mover itself did...so + // drop through and let it blow up the rider up + } + else + { + continue; + } + } + + // the move was blocked even after pushing this rider + if ( check->s.eFlags & EF_MISSILE_STICK ) + { + // so nuke 'em so they don't block us anymore + G_Damage( check, pusher, pusher, NULL, NULL, 99999, 0, MOD_CRUSH ); + continue; + } + + // save off the obstacle so we can call the block function (crush, etc) + *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.pos.trBase); + VectorCopy (p->angles, p->ent->s.apos.trBase); + if ( p->ent->client ) { + p->ent->client->ps.delta_angles[YAW] = p->deltayaw; + VectorCopy (p->origin, p->ent->client->ps.origin); + } + gi.linkentity (p->ent); + } + return qfalse; + } + + return qtrue; +} + + +/* +================= +G_MoverTeam +================= +*/ +void G_MoverTeam( gentity_t *ent ) { + vec3_t move, amove; + gentity_t *part, *obstacle; + vec3_t origin, angles; + + obstacle = NULL; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out + pushed_p = pushed; + for (part = ent ; part ; part=part->teamchain) + { + // get current position + part->s.eFlags &= ~EF_BLOCKED_MOVER; + EvaluateTrajectory( &part->s.pos, level.time, origin ); + EvaluateTrajectory( &part->s.apos, level.time, angles ); + VectorSubtract( origin, part->currentOrigin, move ); + VectorSubtract( angles, part->currentAngles, amove ); + if ( !G_MoverPush( part, move, amove, &obstacle ) ) + { + break; // move was blocked + } + } + + if (part) + { + // if the pusher has a "blocked" function, call it + // go back to the previous position + for ( part = ent ; part ; part = part->teamchain ) + { + //Push up time so it doesn't wiggle when blocked + part->s.pos.trTime += level.time - level.previousTime; + part->s.apos.trTime += level.time - level.previousTime; + EvaluateTrajectory( &part->s.pos, level.time, part->currentOrigin ); + EvaluateTrajectory( &part->s.apos, level.time, part->currentAngles ); + gi.linkentity( part ); + part->s.eFlags |= EF_BLOCKED_MOVER; + } + + if ( ent->e_BlockedFunc != blockedF_NULL ) + {// this check no longer necessary, done internally below, but it's here for reference + GEntity_BlockedFunc( ent, obstacle ); + } + return; + } + + // the move succeeded + for ( part = ent ; part ; part = part->teamchain ) + { + // call the reached function if time is at or past end point + if ( part->s.pos.trType == TR_LINEAR_STOP || + part->s.pos.trType == TR_NONLINEAR_STOP ) + { + if ( level.time >= part->s.pos.trTime + part->s.pos.trDuration ) + { + GEntity_ReachedFunc( part ); + } + } + } +} + +/* +================ +G_RunMover + +================ +*/ +//void rebolt_turret( gentity_t *base ); +void G_RunMover( gentity_t *ent ) { + // if not a team captain, don't do anything, because + // the captain will handle everything + if ( ent->flags & FL_TEAMSLAVE ) { + return; + } + + // if stationary at one of the positions, don't move anything + if ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) { + G_MoverTeam( ent ); + } + +/* if ( ent->classname && Q_stricmp("misc_turret", ent->classname ) == 0 ) + { + rebolt_turret( ent ); + } +*/ + // check think function + G_RunThink( ent ); +} + +/* +============================================================================ + +GENERAL MOVERS + +Doors, plats, and buttons are all binary (two position) movers +Pos1 is "at rest", pos2 is "activated" +============================================================================ +*/ + +/* +CalcTeamDoorCenter + +Finds all the doors of a team and returns their center position +*/ + +void CalcTeamDoorCenter ( gentity_t *ent, vec3_t center ) +{ + vec3_t slavecenter; + gentity_t *slave; + + //Start with our center + VectorAdd(ent->mins, ent->maxs, center); + VectorScale(center, 0.5, center); + for ( slave = ent->teamchain ; slave ; slave = slave->teamchain ) + { + //Find slave's center + VectorAdd(slave->mins, slave->maxs, slavecenter); + VectorScale(slavecenter, 0.5, slavecenter); + //Add that to our own, find middle + VectorAdd(center, slavecenter, center); + VectorScale(center, 0.5, center); + } +} + +/* +=============== +SetMoverState +=============== +*/ +void SetMoverState( gentity_t *ent, moverState_t moverState, int time ) { + vec3_t delta; + float f; + + ent->moverState = moverState; + + ent->s.pos.trTime = time; + + if ( ent->s.pos.trDuration <= 0 ) + {//Don't allow divide by zero! + ent->s.pos.trDuration = 1; + } + + switch( moverState ) { + case MOVER_POS1: + VectorCopy( ent->pos1, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + break; + case MOVER_POS2: + VectorCopy( ent->pos2, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + break; + case MOVER_1TO2: + VectorCopy( ent->pos1, ent->s.pos.trBase ); + VectorSubtract( ent->pos2, ent->pos1, delta ); + f = 1000.0 / ent->s.pos.trDuration; + VectorScale( delta, f, ent->s.pos.trDelta ); + if ( ent->alt_fire ) + { + ent->s.pos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.pos.trType = TR_NONLINEAR_STOP; + } + ent->s.eFlags &= ~EF_BLOCKED_MOVER; + break; + case MOVER_2TO1: + VectorCopy( ent->pos2, ent->s.pos.trBase ); + VectorSubtract( ent->pos1, ent->pos2, delta ); + f = 1000.0 / ent->s.pos.trDuration; + VectorScale( delta, f, ent->s.pos.trDelta ); + if ( ent->alt_fire ) + { + ent->s.pos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.pos.trType = TR_NONLINEAR_STOP; + } + ent->s.eFlags &= ~EF_BLOCKED_MOVER; + break; + } + EvaluateTrajectory( &ent->s.pos, level.time, ent->currentOrigin ); + gi.linkentity( ent ); +} + +/* +================ +MatchTeam + +All entities in a mover team will move from pos1 to pos2 +in the same amount of time +================ +*/ +void MatchTeam( gentity_t *teamLeader, int moverState, int time ) { + gentity_t *slave; + + for ( slave = teamLeader ; slave ; slave = slave->teamchain ) { + SetMoverState( slave, (moverState_t) moverState, time ); + } +} + + + +/* +================ +ReturnToPos1 +================ +*/ +void ReturnToPos1( gentity_t *ent ) { + ent->e_ThinkFunc = thinkF_NULL; + ent->nextthink = 0; + ent->s.time = level.time; + + MatchTeam( ent, MOVER_2TO1, level.time ); + + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? +} + + +/* +================ +Reached_BinaryMover +================ +*/ + +void Reached_BinaryMover( gentity_t *ent ) +{ + // stop the looping sound + ent->s.loopSound = 0; + + if ( ent->moverState == MOVER_1TO2 ) + {//reached open + // reached pos2 + SetMoverState( ent, MOVER_POS2, level.time ); + + vec3_t doorcenter; + CalcTeamDoorCenter( ent, doorcenter ); + if ( ent->activator && ent->activator->client && ent->activator->client->playerTeam == TEAM_PLAYER ) + { + AddSightEvent( ent->activator, doorcenter, 256, AEL_MINOR, 1 ); + } + + // play sound + G_PlayDoorSound( ent, BMS_END ); + + if ( ent->wait < 0 ) + {//Done for good + ent->e_ThinkFunc = thinkF_NULL; + ent->nextthink = -1; + ent->e_UseFunc = useF_NULL; + } + else + { + // return to pos1 after a delay + ent->e_ThinkFunc = thinkF_ReturnToPos1; + if(ent->spawnflags & 8) + {//Toggle, keep think, wait for next use? + ent->nextthink = -1; + } + else + { + ent->nextthink = level.time + ent->wait; + } + } + + // fire targets + if ( !ent->activator ) + { + ent->activator = ent; + } + G_UseTargets2( ent, ent->activator, ent->opentarget ); + } + else if ( ent->moverState == MOVER_2TO1 ) + {//closed + // reached pos1 + SetMoverState( ent, MOVER_POS1, level.time ); + + vec3_t doorcenter; + CalcTeamDoorCenter( ent, doorcenter ); + if ( ent->activator && ent->activator->client && ent->activator->client->playerTeam == TEAM_PLAYER ) + { + AddSightEvent( ent->activator, doorcenter, 256, AEL_MINOR, 1 ); + } + // play sound + G_PlayDoorSound( ent, BMS_END ); + + // close areaportals + if ( ent->teammaster == ent || !ent->teammaster ) + { + gi.AdjustAreaPortalState( ent, qfalse ); + } + G_UseTargets2( ent, ent->activator, ent->closetarget ); + } + else + { + G_Error( "Reached_BinaryMover: bad moverState" ); + } +} + + +/* +================ +Use_BinaryMover_Go +================ +*/ +void Use_BinaryMover_Go( gentity_t *ent ) +{ + int total; + int partial; +// gentity_t *other = ent->enemy; + gentity_t *activator = ent->activator; + + ent->activator = activator; + + if ( ent->moverState == MOVER_POS1 ) + { + // start moving 50 msec later, becase if this was player + // triggered, level.time hasn't been advanced yet + MatchTeam( ent, MOVER_1TO2, level.time + 50 ); + + vec3_t doorcenter; + CalcTeamDoorCenter( ent, doorcenter ); + if ( ent->activator && ent->activator->client && ent->activator->client->playerTeam == TEAM_PLAYER ) + { + AddSightEvent( ent->activator, doorcenter, 256, AEL_MINOR, 1 ); + } + + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); + ent->s.time = level.time; + + // open areaportal + if ( ent->teammaster == ent || !ent->teammaster ) { + gi.AdjustAreaPortalState( ent, qtrue ); + } + G_UseTargets( ent, ent->activator ); + return; + } + + // if all the way up, just delay before coming down + if ( ent->moverState == MOVER_POS2 ) { + //have to do this because the delay sets our think to Use_BinaryMover_Go + ent->e_ThinkFunc = thinkF_ReturnToPos1; + if ( ent->spawnflags & 8 ) + {//TOGGLE doors don't use wait! + ent->nextthink = level.time + FRAMETIME; + } + else + { + ent->nextthink = level.time + ent->wait; + } + G_UseTargets2( ent, ent->activator, ent->target2 ); + return; + } + + // only partway down before reversing + if ( ent->moverState == MOVER_2TO1 ) + { + total = ent->s.pos.trDuration-50; + if ( ent->s.pos.trType == TR_NONLINEAR_STOP ) + { + vec3_t curDelta; + VectorSubtract( ent->currentOrigin, ent->pos1, curDelta ); + float fPartial = VectorLength( curDelta )/VectorLength( ent->s.pos.trDelta ); + VectorScale( ent->s.pos.trDelta, fPartial, curDelta ); + fPartial /= ent->s.pos.trDuration; + fPartial /= 0.001f; + fPartial = acos( fPartial ); + fPartial = RAD2DEG( fPartial ); + fPartial = (90.0f - fPartial)/90.0f*ent->s.pos.trDuration; + partial = total - floor( fPartial ); + } + else + { + partial = level.time - ent->s.pos.trTime;//ent->s.time; + } + + if ( partial > total ) { + partial = total; + } + ent->s.pos.trTime = level.time - ( total - partial );//ent->s.time; + + MatchTeam( ent, MOVER_1TO2, ent->s.pos.trTime ); + + G_PlayDoorSound( ent, BMS_START ); + + return; + } + + // only partway up before reversing + if ( ent->moverState == MOVER_1TO2 ) + { + total = ent->s.pos.trDuration-50; + if ( ent->s.pos.trType == TR_NONLINEAR_STOP ) + { + vec3_t curDelta; + VectorSubtract( ent->currentOrigin, ent->pos2, curDelta ); + float fPartial = VectorLength( curDelta )/VectorLength( ent->s.pos.trDelta ); + VectorScale( ent->s.pos.trDelta, fPartial, curDelta ); + fPartial /= ent->s.pos.trDuration; + fPartial /= 0.001f; + fPartial = acos( fPartial ); + fPartial = RAD2DEG( fPartial ); + fPartial = (90.0f - fPartial)/90.0f*ent->s.pos.trDuration; + partial = total - floor( fPartial ); + } + else + { + partial = level.time - ent->s.pos.trTime;//ent->s.time; + } + if ( partial > total ) { + partial = total; + } + + ent->s.pos.trTime = level.time - ( total - partial );//ent->s.time; + MatchTeam( ent, MOVER_2TO1, ent->s.pos.trTime ); + + G_PlayDoorSound( ent, BMS_START ); + + return; + } +} + +void UnLockDoors(gentity_t *const ent) +{ + //noise? + //go through and unlock the door and all the slaves + gentity_t *slave = ent; + do + { // want to allow locked toggle doors, so keep the targetname + if( !(slave->spawnflags & MOVER_TOGGLE) ) + { + slave->targetname = NULL;//not usable ever again + } + slave->spawnflags &= ~MOVER_LOCKED; + slave->s.frame = 1;//second stage of anim + slave = slave->teamchain; + } while ( slave ); +} +void LockDoors(gentity_t *const ent) +{ + //noise? + //go through and lock the door and all the slaves + gentity_t *slave = ent; + do + { + slave->spawnflags |= MOVER_LOCKED; + slave->s.frame = 0;//first stage of anim + slave = slave->teamchain; + } while ( slave ); +} +/* +================ +Use_BinaryMover +================ +*/ +void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + int key; + char *text; + + if ( ent->e_UseFunc == useF_NULL ) + {//I cannot be used anymore, must be a door with a wait of -1 that's opened. + return; + } + + // only the master should be used + if ( ent->flags & FL_TEAMSLAVE ) + { + Use_BinaryMover( ent->teammaster, other, activator ); + return; + } + + if ( ent->svFlags & SVF_INACTIVE ) + { + return; + } + + if ( ent->spawnflags & MOVER_LOCKED ) + {//a locked door, unlock it + UnLockDoors(ent); + return; + } + + if ( ent->spawnflags & MOVER_GOODIE ) + { + if ( ent->fly_sound_debounce_time > level.time ) + { + return; + } + else + { + key = INV_GoodieKeyCheck( activator ); + if (key) + {//activator has a goodie key, remove it + activator->client->ps.inventory[key]--; + G_Sound( activator, G_SoundIndex( "sound/movers/goodie_pass.wav" ) ); + // once the goodie mover has been used, it no longer requires a goodie key + ent->spawnflags &= ~MOVER_GOODIE; + } + else + { //don't have a goodie key + G_Sound( activator, G_SoundIndex( "sound/movers/goodie_fail.wav" ) ); + ent->fly_sound_debounce_time = level.time + 5000; + text = "cp @SP_INGAME_NEED_KEY_TO_OPEN"; + //FIXME: temp message, only on certain difficulties?, graphic instead of text? + gi.SendServerCommand( NULL, text ); + return; + } + } + } + + G_ActivateBehavior(ent,BSET_USE); + + G_SetEnemy( ent, other ); + ent->activator = activator; + if(ent->delay) + { + ent->e_ThinkFunc = thinkF_Use_BinaryMover_Go; + ent->nextthink = level.time + ent->delay; + } + else + { + Use_BinaryMover_Go(ent); + } +} + + + +/* +================ +InitMover + +"pos1", "pos2", and "speed" should be set before calling, +so the movement delta can be calculated +================ +*/ +void InitMoverTrData( gentity_t *ent ) +{ + vec3_t move; + float distance; + + ent->s.pos.trType = TR_STATIONARY; + VectorCopy( ent->pos1, ent->s.pos.trBase ); + + // calculate time to reach second position from speed + VectorSubtract( ent->pos2, ent->pos1, move ); + distance = VectorLength( move ); + if ( ! ent->speed ) + { + ent->speed = 100; + } + VectorScale( move, ent->speed, ent->s.pos.trDelta ); + ent->s.pos.trDuration = distance * 1000 / ent->speed; + if ( ent->s.pos.trDuration <= 0 ) + { + ent->s.pos.trDuration = 1; + } +} + +void InitMover( gentity_t *ent ) +{ + float light; + vec3_t color; + qboolean lightSet, colorSet; + + // if the "model2" key is set, use a seperate model + // for drawing, but clip against the brushes + if ( ent->model2 ) + { + if ( strstr( ent->model2, ".glm" )) + { + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, ent->model2, ent->s.modelindex2 ); + if ( ent->playerModel >= 0 ) + { + ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue ); + } + + ent->s.radius = 120; + } + else + { + ent->s.modelindex2 = G_ModelIndex( ent->model2 ); + } + } + + // if the "color" or "light" keys are set, setup constantLight + lightSet = G_SpawnFloat( "light", "100", &light ); + colorSet = G_SpawnVector( "color", "1 1 1", color ); + if ( lightSet || colorSet ) { + int r, g, b, i; + + r = color[0] * 255; + if ( r > 255 ) { + r = 255; + } + g = color[1] * 255; + if ( g > 255 ) { + g = 255; + } + b = color[2] * 255; + if ( b > 255 ) { + b = 255; + } + i = light / 4; + if ( i > 255 ) { + i = 255; + } + ent->s.constantLight = r | ( g << 8 ) | ( b << 16 ) | ( i << 24 ); + } + + ent->e_UseFunc = useF_Use_BinaryMover; + ent->e_ReachedFunc = reachedF_Reached_BinaryMover; + + ent->moverState = MOVER_POS1; + ent->svFlags = SVF_USE_CURRENT_ORIGIN; + if ( ent->spawnflags & MOVER_INACTIVE ) + {// Make it inactive + ent->svFlags |= SVF_INACTIVE; + } + if(ent->spawnflags & MOVER_PLAYER_USE) + {//Can be used by the player's BUTTON_USE + ent->svFlags |= SVF_PLAYER_USABLE; + } + ent->s.eType = ET_MOVER; + VectorCopy( ent->pos1, ent->currentOrigin ); + gi.linkentity( ent ); + + InitMoverTrData( ent ); +} + + +/* +=============================================================================== + +DOOR + +A use can be triggered either by a touch function, by being shot, or by being +targeted by another entity. + +=============================================================================== +*/ + +/* +================ +Blocked_Door +================ +*/ +void Blocked_Door( gentity_t *ent, gentity_t *other ) { + + // remove anything other than a client -- no longer the case + + // don't remove security keys or goodie keys + if ( (other->s.eType == ET_ITEM) && (other->item->giTag >= INV_GOODIE_KEY && other->item->giTag <= INV_SECURITY_KEY) ) + { + // should we be doing anything special if a key blocks it... move it somehow..? + } + // if your not a client, or your a dead client remove yourself... + else if ( other->s.number && (!other->client || (other->client && other->health <= 0 && other->contents == CONTENTS_CORPSE && !other->message)) ) + { + if ( !IIcarusInterface::GetIcarus()->IsRunning( other->m_iIcarusID ) /*!other->taskManager || !other->taskManager->IsRunning()*/ ) + { + // if an item or weapon can we do a little explosion..? + G_FreeEntity( other ); + return; + } + } + + if ( ent->damage ) + { + if ( (ent->spawnflags&MOVER_CRUSHER)//a crusher + && other->s.clientNum >= MAX_CLIENTS//not the player + && other->client //NPC + && other->health <= 0 //dead + && G_OkayToRemoveCorpse( other ) )//okay to remove him + {//crusher stuck on a non->player corpse that does not have a key and is not running a script + G_FreeEntity( other ); + } + else + { + G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH ); + } + } + if ( ent->spawnflags & MOVER_CRUSHER ) { + return; // crushers don't reverse + } + + // reverse direction + Use_BinaryMover( ent, ent, other ); +} + + +/* +================ +Touch_DoorTrigger +================ +*/ + +void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace ) +{ + if ( ent->svFlags & SVF_INACTIVE ) + { + return; + } + + if ( ent->owner->spawnflags & MOVER_LOCKED ) + {//don't even try to use the door if it's locked + return; + } + + if ( ent->owner->moverState != MOVER_1TO2 ) + {//Door is not already opening + //if ( ent->owner->moverState == MOVER_POS1 || ent->owner->moverState == MOVER_2TO1 ) + //{//only check these if closed or closing + + //If door is closed, opening or open, check this + Use_BinaryMover( ent->owner, ent, other ); + } + + /* + //Old style + if ( ent->owner->moverState != MOVER_1TO2 ) { + Use_BinaryMover( ent->owner, ent, other ); + } + */ +} + +/* +====================== +Think_SpawnNewDoorTrigger + +All of the parts of a door have been spawned, so create +a trigger that encloses all of them +====================== +*/ +void Think_SpawnNewDoorTrigger( gentity_t *ent ) +{ + gentity_t *other; + vec3_t mins, maxs; + int i, best; + + // set all of the slaves as shootable + if ( ent->takedamage ) + { + for ( other = ent ; other ; other = other->teamchain ) + { + other->takedamage = qtrue; + } + } + + // find the bounds of everything on the team + 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); + } + + // find the thinnest axis, which will be the one we expand + best = 0; + for ( i = 1 ; i < 3 ; i++ ) { + if ( maxs[i] - mins[i] < maxs[best] - mins[best] ) { + best = i; + } + } + maxs[best] += 120; + mins[best] -= 120; + + // create a trigger with this size + other = G_Spawn (); + VectorCopy (mins, other->mins); + VectorCopy (maxs, other->maxs); + other->owner = ent; + other->contents = CONTENTS_TRIGGER; + other->e_TouchFunc = touchF_Touch_DoorTrigger; + gi.linkentity (other); + other->classname = "trigger_door"; + + MatchTeam( ent, ent->moverState, level.time ); +} + +void Think_MatchTeam( gentity_t *ent ) +{ + MatchTeam( ent, ent->moverState, level.time ); +} + +qboolean G_EntIsDoor( int entityNum ) +{ + if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD ) + { + return qfalse; + } + + gentity_t *ent = &g_entities[entityNum]; + if ( ent && !Q_stricmp( "func_door", ent->classname ) ) + {//blocked by a door + return qtrue; + } + return qfalse; +} + +gentity_t *G_FindDoorTrigger( gentity_t *ent ) +{ + gentity_t *owner = NULL; + gentity_t *door = ent; + if ( door->flags & FL_TEAMSLAVE ) + {//not the master door, get the master door + while ( door->teammaster && (door->flags&FL_TEAMSLAVE)) + { + door = door->teammaster; + } + } + if ( door->targetname ) + {//find out what is targeting it + //FIXME: if ent->targetname, check what kind of trigger/ent is targetting it? If a normal trigger (active, etc), then it's okay? + while ( (owner = G_Find( owner, FOFS( target ), door->targetname )) != NULL ) + { + if ( owner && (owner->contents&CONTENTS_TRIGGER) ) + { + return owner; + } + } + owner = NULL; + while ( (owner = G_Find( owner, FOFS( target2 ), door->targetname )) != NULL ) + { + if ( owner && (owner->contents&CONTENTS_TRIGGER) ) + { + return owner; + } + } + } + + owner = NULL; + while ( (owner = G_Find( owner, FOFS( classname ), "trigger_door" )) != NULL ) + { + if ( owner->owner == door ) + { + return owner; + } + } + + return NULL; +} + +qboolean G_TriggerActive( gentity_t *self ); +qboolean G_EntIsUnlockedDoor( int entityNum ) +{ + if ( entityNum < 0 || entityNum >= ENTITYNUM_WORLD ) + { + return qfalse; + } + + if ( G_EntIsDoor( entityNum ) ) + { + gentity_t *ent = &g_entities[entityNum]; + gentity_t *owner = NULL; + if ( ent->flags & FL_TEAMSLAVE ) + {//not the master door, get the master door + while ( ent->teammaster && (ent->flags&FL_TEAMSLAVE)) + { + ent = ent->teammaster; + } + } + if ( ent->targetname ) + {//find out what is targetting it + owner = NULL; + //FIXME: if ent->targetname, check what kind of trigger/ent is targetting it? If a normal trigger (active, etc), then it's okay? + while ( (owner = G_Find( owner, FOFS( target ), ent->targetname )) != NULL ) + { + if ( !Q_stricmp( "trigger_multiple", owner->classname ) + || !Q_stricmp( "trigger_once", owner->classname ) )//FIXME: other triggers okay too? + { + if ( G_TriggerActive( owner ) ) + { + return qtrue; + } + } + } + owner = NULL; + while ( (owner = G_Find( owner, FOFS( target2 ), ent->targetname )) != NULL ) + { + if ( !Q_stricmp( "trigger_multiple", owner->classname ) )//FIXME: other triggers okay too? + { + if ( G_TriggerActive( owner ) ) + { + return qtrue; + } + } + } + return qfalse; + } + else + {//check the door's auto-created trigger instead + owner = G_FindDoorTrigger( ent ); + if ( owner && (owner->svFlags&SVF_INACTIVE) ) + {//owning auto-created trigger is inactive + return qfalse; + } + } + if ( !(ent->svFlags & SVF_INACTIVE) && //assumes that the reactivate trigger isn't right next to the door! + !ent->health && + !(ent->spawnflags & MOVER_PLAYER_USE) && + !(ent->spawnflags & MOVER_FORCE_ACTIVATE) && + !(ent->spawnflags & MOVER_LOCKED)) + //FIXME: what about MOVER_GOODIE? + { + return qtrue; + } + } + return qfalse; +} + + +/*QUAKED func_door (0 .5 .8) ? START_OPEN FORCE_ACTIVATE CRUSHER TOGGLE LOCKED GOODIE PLAYER_USE INACTIVE +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). +FORCE_ACTIVATE Can only be activated by a force push or pull +CRUSHER ? +TOGGLE wait in both the start and end states for a trigger event - does NOT work on Trek doors. +LOCKED Starts locked, with the shader animmap at the first frame and inactive. Once used, the animmap changes to the second frame and the door operates normally. Note that you cannot use the door again after this. +GOODIE Door will not work unless activator has a "goodie key" in his inventory +PLAYER_USE Player can use it with the use button +INACTIVE must be used by a target_activate before it can be used + +"target" Door fires this when it starts moving from it's closed position to its open position. +"opentarget" Door fires this after reaching its "open" position +"target2" Door fires this when it starts moving from it's open position to its closed position. +"closetarget" Door fires this after reaching its "closed" position +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"delay" when used, how many seconds to wait before moving - default is none +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default, set to negative for no damage) +"color" constantLight color +"light" constantLight radius +"health" if set, the door must be shot open +"sounds" - sound door makes when opening/closing +"linear" set to 1 and it will move linearly rather than with acceleration (default is 0) +0 - no sound (default) +*/ +void SP_func_door (gentity_t *ent) +{ + vec3_t abs_movedir; + float distance; + vec3_t size; + float lip; + + ent->e_BlockedFunc = blockedF_Blocked_Door; + + if ( ent->spawnflags & MOVER_GOODIE ) + { + G_SoundIndex( "sound/movers/goodie_fail.wav" ); + G_SoundIndex( "sound/movers/goodie_pass.wav" ); + } + + // default speed of 400 + if (!ent->speed) + ent->speed = 400; + + // default wait of 2 seconds + if (!ent->wait) + ent->wait = 2; + ent->wait *= 1000; + + ent->delay *= 1000; + + // default lip of 8 units + G_SpawnFloat( "lip", "8", &lip ); + + // default damage of 2 points + G_SpawnInt( "dmg", "2", &ent->damage ); + if ( ent->damage < 0 ) + { + ent->damage = 0; + } + + // first position at start + VectorCopy( ent->s.origin, ent->pos1 ); + + // calculate second position + gi.SetBrushModel( ent, ent->model ); + G_SetMovedir( ent->s.angles, ent->movedir ); + abs_movedir[0] = fabs( ent->movedir[0] ); + abs_movedir[1] = fabs( ent->movedir[1] ); + abs_movedir[2] = fabs( ent->movedir[2] ); + VectorSubtract( ent->maxs, ent->mins, size ); + distance = DotProduct( abs_movedir, size ) - lip; + VectorMA( ent->pos1, distance, ent->movedir, ent->pos2 ); + + // if "start_open", reverse position 1 and 2 + if ( ent->spawnflags & 1 ) + { + vec3_t temp; + + VectorCopy( ent->pos2, temp ); + VectorCopy( ent->s.origin, ent->pos2 ); + VectorCopy( temp, ent->pos1 ); + } + + if ( ent->spawnflags & MOVER_LOCKED ) + {//a locked door, set up as locked until used directly + ent->s.eFlags |= EF_SHADER_ANIM;//use frame-controlled shader anim + ent->s.frame = 0;//first stage of anim + } + InitMover( ent ); + + ent->nextthink = level.time + FRAMETIME; + + if ( !(ent->flags&FL_TEAMSLAVE) ) + { + int health; + + G_SpawnInt( "health", "0", &health ); + + if ( health ) + { + ent->takedamage = qtrue; + } + + if ( !(ent->spawnflags&MOVER_LOCKED) && (ent->targetname || health || ent->spawnflags & MOVER_PLAYER_USE || ent->spawnflags & MOVER_FORCE_ACTIVATE) ) + { + // non touch/shoot doors + ent->e_ThinkFunc = thinkF_Think_MatchTeam; + } + else + {//locked doors still spawn a trigger + ent->e_ThinkFunc = thinkF_Think_SpawnNewDoorTrigger; + } + } +} + +/* +=============================================================================== + +PLAT + +=============================================================================== +*/ + +/* +============== +Touch_Plat + +Don't allow decent if a living player is on it +=============== +*/ +void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) { + if ( !other->client || other->client->ps.stats[STAT_HEALTH] <= 0 ) { + return; + } + + // delay return-to-pos1 by one second + if ( ent->moverState == MOVER_POS2 ) { + ent->nextthink = level.time + 1000; + } +} + +/* +============== +Touch_PlatCenterTrigger + +If the plat is at the bottom position, start it going up +=============== +*/ +void Touch_PlatCenterTrigger(gentity_t *ent, gentity_t *other, trace_t *trace ) { + if ( !other->client ) { + return; + } + + if ( ent->owner->moverState == MOVER_POS1 ) { + Use_BinaryMover( ent->owner, ent, other ); + } +} + + +/* +================ +SpawnPlatTrigger + +Spawn a trigger in the middle of the plat's low position +Elevator cars require that the trigger extend through the entire low position, +not just sit on top of it. +================ +*/ +void SpawnPlatTrigger( gentity_t *ent ) { + gentity_t *trigger; + vec3_t tmin, tmax; + + // the middle trigger will be a thin trigger just + // above the starting position + trigger = G_Spawn(); + trigger->e_TouchFunc = touchF_Touch_PlatCenterTrigger; + trigger->contents = CONTENTS_TRIGGER; + trigger->owner = ent; + + tmin[0] = ent->pos1[0] + ent->mins[0] + 33; + tmin[1] = ent->pos1[1] + ent->mins[1] + 33; + tmin[2] = ent->pos1[2] + ent->mins[2]; + + tmax[0] = ent->pos1[0] + ent->maxs[0] - 33; + tmax[1] = ent->pos1[1] + ent->maxs[1] - 33; + tmax[2] = ent->pos1[2] + ent->maxs[2] + 8; + + if ( tmax[0] <= tmin[0] ) { + tmin[0] = ent->pos1[0] + (ent->mins[0] + ent->maxs[0]) *0.5; + tmax[0] = tmin[0] + 1; + } + if ( tmax[1] <= tmin[1] ) { + tmin[1] = ent->pos1[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) ? x x x x x x PLAYER_USE INACTIVE +PLAYER_USE Player can use it with the use button +INACTIVE must be used by a target_activate before it can be used + +Plats are always drawn in the extended position so they will light correctly. + +"lip" default 8, protrusion above rest position +"height" total height of movement, defaults to model height +"speed" overrides default 200. +"dmg" overrides default 2 +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_plat (gentity_t *ent) { + float lip, height; + +// ent->sound1to2 = ent->sound2to1 = G_SoundIndex("sound/movers/plats/pt1_strt.wav"); +// ent->soundPos1 = ent->soundPos2 = G_SoundIndex("sound/movers/plats/pt1_end.wav"); + + VectorClear (ent->s.angles); + + G_SpawnFloat( "speed", "200", &ent->speed ); + G_SpawnInt( "dmg", "2", &ent->damage ); + G_SpawnFloat( "wait", "1", &ent->wait ); + G_SpawnFloat( "lip", "8", &lip ); + + ent->wait = 1000; + + // create second position + gi.SetBrushModel( ent, ent->model ); + + if ( !G_SpawnFloat( "height", "0", &height ) ) { + height = (ent->maxs[2] - ent->mins[2]) - lip; + } + + // pos1 is the rest (bottom) position, pos2 is the top + VectorCopy( ent->s.origin, ent->pos2 ); + VectorCopy( ent->pos2, ent->pos1 ); + ent->pos1[2] -= height; + + InitMover( ent ); + + // touch function keeps the plat from returning while + // a live player is standing on it + ent->e_TouchFunc = touchF_Touch_Plat; + + ent->e_BlockedFunc = blockedF_Blocked_Door; + + ent->owner = ent; // so it can be treated as a door + + // spawn the trigger if one hasn't been custom made + if ( !ent->targetname ) { + SpawnPlatTrigger(ent); + } +} + + +/* +=============================================================================== + +BUTTON + +=============================================================================== +*/ + +/* +============== +Touch_Button + +=============== +*/ +void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace ) { + if ( !other->client ) { + return; + } + + if ( ent->moverState == MOVER_POS1 ) { + Use_BinaryMover( ent, other, other ); + } +} + + +/*QUAKED func_button (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE +PLAYER_USE Player can use it with the use button +INACTIVE must be used by a target_activate before it can be used + +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. + +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"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 +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_button( gentity_t *ent ) { + vec3_t abs_movedir; + float distance; + vec3_t size; + float lip; + +// ent->sound1to2 = G_SoundIndex("sound/movers/switches/butn2.wav"); + + if ( !ent->speed ) { + ent->speed = 40; + } + + if ( !ent->wait ) { + ent->wait = 1; + } + ent->wait *= 1000; + + // first position + VectorCopy( ent->s.origin, ent->pos1 ); + + // calculate second position + gi.SetBrushModel( ent, ent->model ); + + G_SpawnFloat( "lip", "4", &lip ); + + G_SetMovedir( ent->s.angles, ent->movedir ); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + VectorSubtract( ent->maxs, ent->mins, size ); + distance = abs_movedir[0] * size[0] + abs_movedir[1] * size[1] + abs_movedir[2] * size[2] - lip; + VectorMA (ent->pos1, distance, ent->movedir, ent->pos2); + + if (ent->health) { + // shootable button + ent->takedamage = qtrue; + } else { + // touchable button + ent->e_TouchFunc = touchF_Touch_Button; + } + + InitMover( ent ); +} + + + +/* +=============================================================================== + +TRAIN + +=============================================================================== +*/ + + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +/* +=============== +Think_BeginMoving + +The wait time at a corner has completed, so start moving again +=============== +*/ +void Think_BeginMoving( gentity_t *ent ) { + + if ( ent->spawnflags & 2048 ) + { + // this tie fighter hack is done for doom_detention, where the shooting gallery takes place. let them draw again when they start moving + ent->s.eFlags &= ~EF_NODRAW; + } + + ent->s.pos.trTime = level.time; + if ( ent->alt_fire ) + { + ent->s.pos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.pos.trType = TR_NONLINEAR_STOP; + } +} + +/* +=============== +Reached_Train +=============== +*/ +void Reached_Train( gentity_t *ent ) { + gentity_t *next; + float speed; + vec3_t move; + float length; + + // copy the apropriate values + next = ent->nextTrain; + if ( !next || !next->nextTrain ) { + //FIXME: can we go backwards- say if we are toggle-able? + return; // just stop + } + + // fire all other targets + G_UseTargets( next, ent ); + + // set the new trajectory + ent->nextTrain = next->nextTrain; + VectorCopy( next->s.origin, ent->pos1 ); + VectorCopy( next->nextTrain->s.origin, ent->pos2 ); + + // if the path_corner has a speed, use that + if ( next->speed ) { + speed = next->speed; + } else { + // otherwise use the train's speed + speed = ent->speed; + } + if ( speed < 1 ) { + speed = 1; + } + + // calculate duration + VectorSubtract( ent->pos2, ent->pos1, move ); + length = VectorLength( move ); + + ent->s.pos.trDuration = length * 1000 / speed; + + // looping sound +/* + if ( VALIDSTRING( next->soundSet ) ) + { + ent->s.loopSound = CAS_GetBModelSound( next->soundSet, BMS_MID );//ent->soundLoop; + } +*/ + G_PlayDoorLoopSound( ent ); + + // start it going + SetMoverState( ent, MOVER_1TO2, level.time ); + + if (( next->spawnflags & 1 )) + { + vec3_t angs; + + vectoangles( move, angs ); + AnglesSubtract( angs, ent->currentAngles, angs ); + + for ( int i = 0; i < 3; i++ ) + { + AngleNormalize360( angs[i] ); + } + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + VectorScale( angs, 0.5f, ent->s.apos.trDelta ); + ent->s.apos.trTime = level.time; + ent->s.apos.trDuration = 2000; + if ( ent->alt_fire ) + { + ent->s.apos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.apos.trType = TR_NONLINEAR_STOP; + } + } + else + { + if (( next->spawnflags & 4 )) + {//yaw + vec3_t angs; + + vectoangles( move, angs ); + AnglesSubtract( angs, ent->currentAngles, angs ); + + for ( int i = 0; i < 3; i++ ) + { + AngleNormalize360( angs[i] ); + } + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + ent->s.apos.trDelta[YAW] = angs[YAW] * 0.5f; + if (( next->spawnflags & 8 )) + {//roll, too + ent->s.apos.trDelta[ROLL] = angs[YAW] * -0.1f; + } + ent->s.apos.trTime = level.time; + ent->s.apos.trDuration = 2000; + if ( ent->alt_fire ) + { + ent->s.apos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.apos.trType = TR_NONLINEAR_STOP; + } + } + } + + // This is for the tie fighter shooting gallery on doom detention, you could see them waiting under the bay, but the architecture couldn't easily be changed.. + if (( next->spawnflags & 2 )) + { + ent->s.eFlags |= EF_NODRAW; // make us invisible until we start moving again + } + + // if there is a "wait" value on the target, don't start moving yet + if ( next->wait ) + { + ent->nextthink = level.time + next->wait * 1000; + ent->e_ThinkFunc = thinkF_Think_BeginMoving; + ent->s.pos.trType = TR_STATIONARY; + } + else if (!( next->spawnflags & 2 )) + { + // we aren't waiting to start, so let us draw right away + ent->s.eFlags &= ~EF_NODRAW; + } +} + +void TrainUse( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + Reached_Train( ent ); +} + +/* +=============== +Think_SetupTrainTargets + +Link all the corners together +=============== +*/ +void Think_SetupTrainTargets( gentity_t *ent ) { + gentity_t *path, *next, *start; + + ent->nextTrain = G_Find( NULL, FOFS(targetname), ent->target ); + if ( !ent->nextTrain ) { + gi.Printf( "func_train at %s with an unfound target\n", vtos(ent->absmin) ); + //Free me?` + return; + } + + //FIXME: this can go into an infinite loop if last path_corner doesn't link to first + //path_corner, like so: + // t1---->t2---->t3 + // ^ | + // \_____| + start = NULL; + next = NULL; + int iterations = 2000; //max attempts to find our path start + for ( path = ent->nextTrain ; path != start ; path = next ) { + if (!iterations--) + { + G_Error( "Think_SetupTrainTargets: last path_corner doesn't link back to first on func_train(%s)", vtos(ent->absmin) ); + } + if (!start) + { + start = path; + } + if ( !path->target ) { +// gi.Printf( "Train corner at %s without a target\n", vtos(path->s.origin) ); + //end of path + break; + } + + // find a path_corner among the targets + // there may also be other targets that get fired when the corner + // is reached + next = NULL; + do { + next = G_Find( next, FOFS(targetname), path->target ); + if ( !next ) { +// gi.Printf( "Train corner at %s without a target path_corner\n", vtos(path->s.origin) ); + //end of path + break; + } + } while ( strcmp( next->classname, "path_corner" ) ); + + if ( next ) + { + path->nextTrain = next; + } + else + { + break; + } + } + + if ( !ent->targetname || (ent->spawnflags&1) /*start on*/) + { + // start the train moving from the first corner + Reached_Train( ent ); + } + else + { + G_SetOrigin( ent, ent->s.origin ); + } +} + + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TURN_TRAIN INVISIBLE YAW_TRAIN ROLL_TRAIN + +TURN_TRAIN func_train moving on this path will turn to face the next path_corner within 2 seconds +INVISIBLE - train will become invisible ( but still solid ) when it reaches this path_corner. + It will become visible again at the next path_corner that does not have this option checked + +Train path corners. +Target: next path corner and other targets to fire +"speed" speed to move to the next corner +"wait" seconds to wait before behining move to next corner +*/ +void SP_path_corner( gentity_t *self ) { + if ( !self->targetname ) { + gi.Printf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); + G_FreeEntity( self ); + return; + } + // path corners don't need to be linked in + VectorCopy(self->s.origin, self->currentOrigin); +} + + +void func_train_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc ) +{ + if ( self->target3 ) + { + G_UseTargets2( self, self, self->target3 ); + } + + //HACKHACKHACKHACK + G_PlayEffect( "explosions/fighter_explosion2", self->currentOrigin ); + G_FreeEntity( self ); + //HACKHACKHACKHACK +} + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS x x LOOP PLAYER_USE INACTIVE TIE + +LOOP - if it's a ghoul2 model, it will just loop the animation +PLAYER_USE Player can use it with the use button +INACTIVE must be used by a target_activate before it can be used +TIE flying Tie-fighter hack, should be made more flexible so other things can use this if needed + +A train is a mover that moves between path_corner target points. +Trains MUST HAVE AN ORIGIN BRUSH. +The train spawns at the first target it is pointing at. +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"speed" default 100 +"dmg" default 2 +"health" default 0 +"noise" looping sound to play when the train is in motion +"targetname" will not start until used +"target" next path corner +"target3" what to use when it breaks +"color" constantLight color +"light" constantLight radius +"linear" set to 1 and it will move linearly rather than with acceleration (default is 0) +"startframe" default 0...ghoul2 animation start frame +"endframe" default 0...ghoul2 animation end frame +*/ +void SP_func_train (gentity_t *self) { + VectorClear (self->s.angles); + + if (self->spawnflags & TRAIN_BLOCK_STOPS) { + self->damage = 0; + } else { + if (!self->damage) { + self->damage = 2; + } + } + + if ( !self->speed ) { + self->speed = 100; + } + + if ( !self->target ) { + gi.Printf ("func_train without a target at %s\n", vtos(self->absmin)); + G_FreeEntity( self ); + return; + } + + char *noise; + + G_SpawnInt( "startframe", "0", &self->startFrame ); + G_SpawnInt( "endframe", "0", &self->endFrame ); + + if ( G_SpawnString( "noise", "", &noise ) ) + { + if ( VALIDSTRING( noise ) ) + { + self->s.loopSound = cgi_S_RegisterSound( noise );//G_SoundIndex( noise ); + } + } + + gi.SetBrushModel( self, self->model ); + InitMover( self ); + + if ( self->spawnflags & 2048 ) // TIE HACK + { + //HACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACK + self->s.modelindex2 = G_ModelIndex( "models/map_objects/ships/tie_fighter.md3" ); + G_EffectIndex( "explosions/fighter_explosion2" ); + + self->contents = CONTENTS_SHOTCLIP; + self->takedamage = qtrue; + VectorSet( self->maxs, 112, 112, 112 ); + VectorSet( self->mins, -112, -112, -112 ); + self->e_DieFunc = dieF_func_train_die; + gi.linkentity( self ); + //HACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACKHACK + } + + if ( self->targetname ) + { + self->e_UseFunc = useF_TrainUse; + } + + self->e_ReachedFunc = reachedF_Reached_Train; + + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + START_TIME_LINK_ENTS; + self->e_ThinkFunc = thinkF_Think_SetupTrainTargets; + + + if ( self->playerModel >= 0 && self->spawnflags & 32 ) // loop...dunno, could support it on other things, but for now I need it for the glider...so...kill the flag + { + self->spawnflags &= ~32; // once only + + gi.G2API_SetBoneAnim( &self->ghoul2[self->playerModel], "model_root", self->startFrame, self->endFrame, BONE_ANIM_OVERRIDE_LOOP, 1.0f + crandom() * 0.1f, 0 ); + self->endFrame = 0; // don't allow it to do anything with the animation function in G_main + } +} + +/* +=============================================================================== + +STATIC + +=============================================================================== +*/ + +/*QUAKED func_static (0 .5 .8) ? F_PUSH F_PULL SWITCH_SHADER CRUSHER IMPACT SOLITARY PLAYER_USE INACTIVE BROADCAST +F_PUSH Will be used when you Force-Push it +F_PULL Will be used when you Force-Pull it +SWITCH_SHADER Toggle the shader anim frame between 1 and 2 when used +CRUSHER Make it do damage when it's blocked +IMPACT Make it do damage when it hits any entity +SOLITARY Can only be pushed when directly under crosshair, when pushed you shall push nothing else BUT me. +PLAYER_USE Player can use it with the use button +INACTIVE must be used by a target_activate before it can be used +BROADCAST don't ever use this, it's evil + +A bmodel that just sits there, doing nothing. Can be used for conditional walls and models. +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"color" constantLight color +"light" constantLight radius +"dmg" how much damage to do when it crushes (use with CRUSHER spawnflag default is 2) +"linear" set to 1 and it will move linearly rather than with acceleration (default is 0) +"NPC_targetname" if set up to be push/pullable, only this NPC can push/pull it (for the player, use "player") +*/ +void SP_func_static( gentity_t *ent ) +{ + gi.SetBrushModel( ent, ent->model ); + + VectorCopy( ent->s.origin, ent->pos1 ); + VectorCopy( ent->s.origin, ent->pos2 ); + + InitMover( ent ); + + G_SetOrigin( ent, ent->s.origin ); + G_SetAngles( ent, ent->s.angles ); + + ent->e_UseFunc = useF_func_static_use; + ent->e_ReachedFunc = reachedF_NULL; + + + if( ent->spawnflags & 2048 ) + { // yes this is very very evil, but for now (pre-alpha) it's a solution + ent->svFlags |= SVF_BROADCAST; // I need to rotate something that is huge and it's touching too many area portals... + } + + if ( ent->spawnflags & 4/*SWITCH_SHADER*/ ) + { + ent->s.eFlags |= EF_SHADER_ANIM;//use frame-controlled shader anim + ent->s.frame = 0;//first stage of anim + ent->spawnflags &= ~4;//this is the CRUSHER spawnflag! remove it! + } + if ( ent->spawnflags & 8 ) + {//!!! 8 is NOT the crusher spawnflag, 4 is!!! + ent->spawnflags &= ~8; + ent->spawnflags |= MOVER_CRUSHER; + if ( !ent->damage ) + { + ent->damage = 2; + } + } + gi.linkentity( ent ); + + if (level.mBSPInstanceDepth) + { // this means that this guy will never be updated, moved, changed, etc. + ent->s.eFlags = EF_PERMANENT; + } +} + +void func_static_use ( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + G_ActivateBehavior( self, BSET_USE ); + + if ( self->spawnflags & 4/*SWITCH_SHADER*/ ) + { + self->s.frame = self->s.frame ? 0 : 1;//toggle frame + } + G_UseTargets( self, activator ); +} + +/* +=============================================================================== + +ROTATING + +=============================================================================== +*/ +void func_rotating_touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ +// vec3_t spot; +// gentity_t *tent; + if( !other->client ) // don't want to disintegrate items or weapons, etc... + { + return; + } + // yeah, this is a bit hacky... + if( self->s.apos.trType != TR_STATIONARY && !(other->flags & FL_DISINTEGRATED) ) // only damage if it's moving + { +// VectorCopy( self->currentOrigin, spot ); +// tent = G_TempEntity( spot, EV_DISINTEGRATION ); +// tent->s.eventParm = PW_DISRUPTION; +// tent->owner = self; + // let G_Damage call the fx instead, beside, this way you can disintegrate a corpse this way + G_Sound( other, G_SoundIndex( "sound/effects/energy_crackle.wav" ) ); + G_Damage( other, self, self, NULL, NULL, 10000, DAMAGE_NO_KNOCKBACK, MOD_SNIPER ); + } +} + + +void func_rotating_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( self->s.apos.trType == TR_LINEAR ) + { + self->s.apos.trType = TR_STATIONARY; + // stop the sound if it stops moving + self->s.loopSound = 0; + // play stop sound too? + if ( VALIDSTRING( self->soundSet ) == true ) + { + G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_END )); + } + } + else + { + if ( VALIDSTRING( self->soundSet ) == true ) + { + G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START )); + self->s.loopSound = CAS_GetBModelSound( self->soundSet, BMS_MID ); + if ( self->s.loopSound < 0 ) + { + self->s.loopSound = 0; + } + } + self->s.apos.trType = TR_LINEAR; + } +} + + +/*QUAKED func_rotating (0 .5 .8) ? START_ON TOUCH_KILL X_AXIS Y_AXIS x x PLAYER_USE INACTIVE +PLAYER_USE Player can use it with the use button +TOUCH_KILL Inflicts massive damage upon touching it, disitegrates bodies +INACTIVE must be used by a target_activate before it can be used + +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. + +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_rotating (gentity_t *ent) { + if ( !ent->speed ) { + ent->speed = 100; + } + + + ent->s.apos.trType = TR_STATIONARY; + if( ent->spawnflags & 1 ) // start on + { + ent->s.apos.trType = TR_LINEAR; + } + // set the axis of rotation + if ( ent->spawnflags & 4 ) { + ent->s.apos.trDelta[2] = ent->speed; + } else if ( ent->spawnflags & 8 ) { + ent->s.apos.trDelta[0] = ent->speed; + } else { + ent->s.apos.trDelta[1] = ent->speed; + } + + if (!ent->damage) { + ent->damage = 2; + } + + + gi.SetBrushModel( ent, ent->model ); + InitMover( ent ); + + if ( ent->targetname ) + { + ent->e_UseFunc = useF_func_rotating_use; + } + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.pos.trBase, ent->currentOrigin ); + VectorCopy( ent->s.apos.trBase, ent->currentAngles ); + if( ent->spawnflags & 2 ) + { + ent->e_TouchFunc = touchF_func_rotating_touch; + G_SoundIndex( "sound/effects/energy_crackle.wav" ); + } + + gi.linkentity( ent ); +} + + +/* +=============================================================================== + +BOBBING + +=============================================================================== +*/ + +void func_bobbing_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + // Toggle our move state + if ( self->s.pos.trType == TR_SINE ) + { + self->s.pos.trType = TR_INTERPOLATE; + + // Save off roughly where we were + VectorCopy( self->currentOrigin, self->s.pos.trBase ); + // Store the current phase value so we know how to start up where we left off. + self->radius = ( level.time - self->s.pos.trTime ) / (float)self->s.pos.trDuration; + } + else + { + self->s.pos.trType = TR_SINE; + + // Set the time based on the saved phase value + self->s.pos.trTime = level.time - self->s.pos.trDuration * self->radius; + // Always make sure we are starting with a fresh base + VectorCopy( self->s.origin, self->s.pos.trBase ); + } +} + +/*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS START_OFF x x x PLAYER_USE INACTIVE +PLAYER_USE Player can use it with the use button +INACTIVE must be used by a target_activate before it can be used + +Normally bobs on the Z axis +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"height" amplitude of bob (32 default) +"speed" seconds to complete a bob cycle (4 default) +"phase" the 0.0 to 1.0 offset in the cycle to start at +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +"targetname" turns on/off when used +*/ +void SP_func_bobbing (gentity_t *ent) { + float height; + float phase; + + G_SpawnFloat( "speed", "4", &ent->speed ); + G_SpawnFloat( "height", "32", &height ); + G_SpawnInt( "dmg", "2", &ent->damage ); + G_SpawnFloat( "phase", "0", &phase ); + + gi.SetBrushModel( ent, ent->model ); + InitMover( ent ); + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->currentOrigin ); + + // set the axis of bobbing + if ( ent->spawnflags & 1 ) { + ent->s.pos.trDelta[0] = height; + } else if ( ent->spawnflags & 2 ) { + ent->s.pos.trDelta[1] = height; + } else { + ent->s.pos.trDelta[2] = height; + } + + ent->s.pos.trDuration = ent->speed * 1000; + ent->s.pos.trTime = ent->s.pos.trDuration * phase; + + if ( ent->spawnflags & 4 ) // start_off + { + ent->s.pos.trType = TR_INTERPOLATE; + + // Now use the phase to calculate where it should be at the start. + ent->radius = phase; + phase = (float)sin( phase * M_PI * 2 ); + VectorMA( ent->s.pos.trBase, phase, ent->s.pos.trDelta, ent->s.pos.trBase ); + + if ( ent->targetname ) + { + ent->e_UseFunc = useF_func_bobbing_use; + } + } + else + { + ent->s.pos.trType = TR_SINE; + } +} + +/* +=============================================================================== + +PENDULUM + +=============================================================================== +*/ + + +/*QUAKED func_pendulum (0 .5 .8) ? x x x x x x PLAYER_USE INACTIVE +PLAYER_USE Player can use it with the use button +INACTIVE must be used by a target_activate before it can be used + +You need to have an origin brush as part of this entity. +Pendulums always swing north / south on unrotated models. Add an angles field to the model to allow rotation in other directions. +Pendulum frequency is a physical constant based on the length of the beam and gravity. +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"speed" the number of degrees each way the pendulum swings, (30 default) +"phase" the 0.0 to 1.0 offset in the cycle to start at +"dmg" damage to inflict when blocked (2 default) +"color" constantLight color +"light" constantLight radius +*/ +void SP_func_pendulum(gentity_t *ent) { + float freq; + float length; + float phase; + float speed; + + G_SpawnFloat( "speed", "30", &speed ); + G_SpawnInt( "dmg", "2", &ent->damage ); + G_SpawnFloat( "phase", "0", &phase ); + + gi.SetBrushModel( ent, ent->model ); + + // find pendulum length + length = fabs( ent->mins[2] ); + if ( length < 8 ) { + length = 8; + } + + freq = 1 / ( M_PI * 2 ) * sqrt( g_gravity->value / ( 3 * length ) ); + + ent->s.pos.trDuration = ( 1000 / freq ); + + InitMover( ent ); + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->currentOrigin ); + + VectorCopy( ent->s.angles, ent->s.apos.trBase ); + + ent->s.apos.trDuration = 1000 / freq; + ent->s.apos.trTime = ent->s.apos.trDuration * phase; + ent->s.apos.trType = TR_SINE; + + ent->s.apos.trDelta[2] = speed; +} + +/* +=============================================================================== + +WALL + +=============================================================================== +*/ + +//static -slc +void use_wall( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + G_ActivateBehavior(ent,BSET_USE); + + // Not there so make it there + //if (!(ent->contents & CONTENTS_SOLID)) + if (!ent->count) // off + { + ent->svFlags &= ~SVF_NOCLIENT; + ent->s.eFlags &= ~EF_NODRAW; + ent->count = 1; + //NOTE: reset the brush model because we need to get *all* the contents back + //ent->contents |= CONTENTS_SOLID; + gi.SetBrushModel( ent, ent->model ); + if ( !(ent->spawnflags&1) ) + {//START_OFF doesn't effect area portals + gi.AdjustAreaPortalState( ent, qfalse ); + } + } + // Make it go away + else + { + //NOTE: MUST do this BEFORE clearing contents, or you may not open the area portal!!! + if ( !(ent->spawnflags&1) ) + {//START_OFF doesn't effect area portals + gi.AdjustAreaPortalState( ent, qtrue ); + } + ent->contents = 0; + ent->svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + ent->count = 0; // off + } +} + +#define FUNC_WALL_OFF 1 +#define FUNC_WALL_ANIM 2 + + +/*QUAKED func_wall (0 .5 .8) ? START_OFF AUTOANIMATE x x x x PLAYER_USE INACTIVE +PLAYER_USE Player can use it with the use button +INACTIVE must be used by a target_activate before it can be used + +A bmodel that just sits there, doing nothing. Can be used for conditional walls and models. +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"color" constantLight color +"light" constantLight radius + +START_OFF - the wall will not be there +AUTOANIMATE - if a model is used it will animate +*/ +void SP_func_wall( gentity_t *ent ) +{ + gi.SetBrushModel( ent, ent->model ); + + VectorCopy( ent->s.origin, ent->pos1 ); + VectorCopy( ent->s.origin, ent->pos2 ); + + InitMover( ent ); + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->currentOrigin ); + + // count is used as an on/off switch (start it on) + ent->count = 1; + + // it must be START_OFF + if (ent->spawnflags & FUNC_WALL_OFF) + { + ent->spawnContents = ent->contents; + ent->contents = 0; + ent->svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + // turn it off + ent->count = 0; + } + + if (!(ent->spawnflags & FUNC_WALL_ANIM)) + { + ent->s.eFlags |= EF_ANIM_ALLFAST; + } + ent->e_UseFunc = useF_use_wall; + + gi.linkentity (ent); + +} + + +void security_panel_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( !activator ) + { + return; + } + if ( INV_SecurityKeyCheck( activator, self->message ) ) + {//congrats! + gi.SendServerCommand( NULL, "cp @SP_INGAME_SECURITY_KEY_UNLOCKEDDOOR" ); + //use targets + G_UseTargets( self, activator ); + //take key + INV_SecurityKeyTake( activator, self->message ); + if ( activator->ghoul2.size() ) + { + gi.G2API_SetSurfaceOnOff( &activator->ghoul2[activator->playerModel], "l_arm_key", 0x00000002 ); + } + //FIXME: maybe set frame on me to have key sticking out? + //self->s.frame = 1; + //play sound + G_Sound( self, self->soundPos2 ); + //unusable + self->e_UseFunc = useF_NULL; + } + else + {//failure sound/display + if ( activator->message ) + {//have a key, just the wrong one + gi.SendServerCommand( NULL, "cp @SP_INGAME_INCORRECT_KEY" ); + } + else + {//don't have a key at all + gi.SendServerCommand( NULL, "cp @SP_INGAME_NEED_SECURITY_KEY" ); + } + G_UseTargets2( self, activator, self->target2 ); + //FIXME: change display? Maybe shader animmap change? + //play sound + G_Sound( self, self->soundPos1 ); + } +} + +/*QUAKED misc_security_panel (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x x x INACTIVE +model="models/map_objects/kejim/sec_panel.md3" + A model that just sits there and opens when a player uses it and has right key + +INACTIVE - Start off, has to be activated to be usable + +"message" name of the key player must have +"target" thing to use when successfully opened +"target2" thing to use when player uses the panel without the key +*/ +void SP_misc_security_panel ( gentity_t *self ) +{ + self->s.modelindex = G_ModelIndex( "models/map_objects/kejim/sec_panel.md3" ); + self->soundPos1 = G_SoundIndex( "sound/movers/sec_panel_fail.mp3" ); + self->soundPos2 = G_SoundIndex( "sound/movers/sec_panel_pass.mp3" ); + G_SetOrigin( self, self->s.origin ); + G_SetAngles( self, self->s.angles ); + VectorSet( self->mins, -8, -8, -8 ); + VectorSet( self->maxs, 8, 8, 8 ); + self->contents = CONTENTS_SOLID; + gi.linkentity( self ); + + //NOTE: self->message is the key + self->svFlags |= SVF_PLAYER_USABLE; + if(self->spawnflags & 128) + { + self->svFlags |= SVF_INACTIVE; + } + self->e_UseFunc = useF_security_panel_use; +} \ No newline at end of file diff --git a/code/game/g_nav.cpp b/code/game/g_nav.cpp new file mode 100644 index 0000000..2fbdebe --- /dev/null +++ b/code/game/g_nav.cpp @@ -0,0 +1,407 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "b_local.h" +#include "g_nav.h" +#include "g_navigator.h" + +//Global navigator +//CNavigator navigator; + +extern qboolean G_EntIsUnlockedDoor( int entityNum ); +extern qboolean G_EntIsDoor( int entityNum ); +extern qboolean G_EntIsRemovableUsable( int entNum ); +extern qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +//For debug graphics +extern void CG_Line( vec3_t start, vec3_t end, vec3_t color, float alpha ); +extern void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ); +extern void CG_CubeOutline( vec3_t mins, vec3_t maxs, int time, unsigned int color, float alpha ); +extern qboolean FlyingCreature( gentity_t *ent ); + + +extern vec3_t NPCDEBUG_RED; + + +/* +------------------------- +NPC_SetMoveGoal +------------------------- +*/ + +void NPC_SetMoveGoal( gentity_t *ent, vec3_t point, int radius, qboolean isNavGoal, int combatPoint, gentity_t *targetEnt ) +{ + //Must be an NPC + if ( ent->NPC == NULL ) + { + return; + } + + if ( ent->NPC->tempGoal == NULL ) + {//must still have a goal + return; + } + + //Copy the origin + //VectorCopy( point, ent->NPC->goalPoint ); //FIXME: Make it use this, and this alone! + VectorCopy( point, ent->NPC->tempGoal->currentOrigin ); + + //Copy the mins and maxs to the tempGoal + VectorCopy( ent->mins, ent->NPC->tempGoal->mins ); + VectorCopy( ent->mins, ent->NPC->tempGoal->maxs ); + + //FIXME: TESTING let's try making sure the tempGoal isn't stuck in the ground? + if ( 0 ) + { + trace_t trace; + vec3_t bottom = {ent->NPC->tempGoal->currentOrigin[0],ent->NPC->tempGoal->currentOrigin[1],ent->NPC->tempGoal->currentOrigin[2]+ent->NPC->tempGoal->mins[2]}; + gi.trace( &trace, ent->NPC->tempGoal->currentOrigin, vec3_origin, vec3_origin, bottom, ent->s.number, ent->clipmask ); + if ( trace.fraction < 1.0f ) + {//in the ground, raise it up + ent->NPC->tempGoal->currentOrigin[2] -= ent->NPC->tempGoal->mins[2]*(1.0f-trace.fraction)-0.125f; + } + } + + ent->NPC->tempGoal->target = NULL; + ent->NPC->tempGoal->clipmask = ent->clipmask; + ent->NPC->tempGoal->svFlags &= ~SVF_NAVGOAL; + if ( targetEnt && targetEnt->waypoint >= 0 ) + { + ent->NPC->tempGoal->waypoint = targetEnt->waypoint; + } + else + { + ent->NPC->tempGoal->waypoint = WAYPOINT_NONE; + } + ent->NPC->tempGoal->noWaypointTime = 0; + + if ( isNavGoal ) + { + assert(ent->NPC->tempGoal->owner); + ent->NPC->tempGoal->svFlags |= SVF_NAVGOAL; + } + + ent->NPC->tempGoal->combatPoint = combatPoint; + ent->NPC->tempGoal->enemy = targetEnt; + + ent->NPC->goalEntity = ent->NPC->tempGoal; + ent->NPC->goalRadius = radius; + ent->NPC->aiFlags &= ~NPCAI_STOP_AT_LOS; + + gi.linkentity( ent->NPC->goalEntity ); +} +/* +------------------------- +waypoint_testDirection +------------------------- +*/ + +static float waypoint_testDirection( vec3_t origin, float yaw, float minDist ) +{ + vec3_t trace_dir, test_pos; + vec3_t maxs, mins; + trace_t tr; + + //Setup the mins and max + VectorSet( maxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2 ); + VectorSet( mins, DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2 + STEPSIZE ); + + //Get our test direction + vec3_t angles = { 0, yaw, 0 }; + AngleVectors( angles, trace_dir, NULL, NULL ); + + //Move ahead + VectorMA( origin, minDist, trace_dir, test_pos ); + + gi.trace( &tr, origin, mins, maxs, test_pos, ENTITYNUM_NONE, ( CONTENTS_SOLID | CONTENTS_MONSTERCLIP | CONTENTS_BOTCLIP ) ); + + return ( minDist * tr.fraction ); //return actual dist completed +} + +/* +------------------------- +waypoint_getRadius +------------------------- +*/ + +static float waypoint_getRadius( gentity_t *ent ) +{ + float minDist = MAX_RADIUS_CHECK + 1; // (unsigned int) -1; + float dist; + + for ( int i = 0; i < YAW_ITERATIONS; i++ ) + { + dist = waypoint_testDirection( ent->currentOrigin, ((360.0f/YAW_ITERATIONS) * i), minDist ); + + if ( dist < minDist ) + minDist = dist; + } + + return minDist + DEFAULT_MAXS_0; +} + +/*QUAKED waypoint (0.7 0.7 0) (-20 -20 -24) (20 20 45) SOLID_OK DROP_TO_FLOOR +a place to go. + +SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position) +DROP_TO_FLOOR - will cause the point to auto drop to the floor + +radius is automatically calculated in-world. +"targetJump" is a special edge that only guys who can jump will cross (so basically Jedi) +*/ +extern int delayedShutDown; +void SP_waypoint ( gentity_t *ent ) +{ + VectorSet(ent->mins, DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2); + VectorSet(ent->maxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2); + + ent->contents = CONTENTS_TRIGGER; + ent->clipmask = MASK_DEADSOLID; + + gi.linkentity( ent ); + + ent->count = -1; + ent->classname = "waypoint"; + + if (ent->spawnflags&2) + { + ent->currentOrigin[2] += 128.0f; + } + + if( !(ent->spawnflags&1) && G_CheckInSolid (ent, qtrue)) + {//if not SOLID_OK, and in solid + ent->maxs[2] = CROUCH_MAXS_2; + if(G_CheckInSolid (ent, qtrue)) + { + gi.Printf(S_COLOR_RED"ERROR: Waypoint %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin)); + assert(0 && "Waypoint in solid!"); +// if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region +// G_Error("Waypoint %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin)); +// } + delayedShutDown = level.time + 100; + G_FreeEntity(ent); + return; + } + } + + //G_SpawnString("targetJump", "", &ent->targetJump); + ent->radius = waypoint_getRadius( ent ); + NAV::SpawnedPoint(ent); + + G_FreeEntity(ent); + return; +} + +/*QUAKED waypoint_small (0.7 0.7 0) (-2 -2 -24) (2 2 32) SOLID_OK +SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position) +DROP_TO_FLOOR - will cause the point to auto drop to the floor +*/ +void SP_waypoint_small (gentity_t *ent) +{ + VectorSet(ent->mins, -2, -2, DEFAULT_MINS_2); + VectorSet(ent->maxs, 2, 2, DEFAULT_MAXS_2); + + ent->contents = CONTENTS_TRIGGER; + ent->clipmask = MASK_DEADSOLID; + + gi.linkentity( ent ); + + ent->count = -1; + ent->classname = "waypoint"; + + if ( !(ent->spawnflags&1) && G_CheckInSolid( ent, qtrue ) ) + { + ent->maxs[2] = CROUCH_MAXS_2; + if ( G_CheckInSolid( ent, qtrue ) ) + { + gi.Printf(S_COLOR_RED"ERROR: Waypoint_small %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin)); + assert(0); +#ifndef FINAL_BUILD + if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region + G_Error("Waypoint_small %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin)); + } +#endif + G_FreeEntity(ent); + return; + } + } + + ent->radius = 2; // radius + NAV::SpawnedPoint(ent); + + G_FreeEntity(ent); + return; +} + + +/*QUAKED waypoint_navgoal (0.3 1 0.3) (-20 -20 -24) (20 20 40) SOLID_OK DROP_TO_FLOOR NO_AUTO_CONNECT +A waypoint for script navgoals +Not included in navigation data + +DROP_TO_FLOOR - will cause the point to auto drop to the floor +NO_AUTO_CONNECT - will not automatically connect to any other points, you must then connect it by hand + + +SOLID_OK - only use if placing inside solid is unavoidable in map, but may be clear in-game (ie: at the bottom of a tall, solid lift that starts at the top position) + +targetname - name you would use in script when setting a navgoal (like so:) + + For example: if you give this waypoint a targetname of "console", make an NPC go to it in a script like so: + + set ("navgoal", "console"); + +radius - how far from the navgoal an ent can be before it thinks it reached it - default is "0" which means no radius check, just have to touch it + +*/ + +void SP_waypoint_navgoal( gentity_t *ent ) +{ + int radius = ( ent->radius ) ? (ent->radius) : 12; + + VectorSet( ent->mins, -16, -16, -24 ); + VectorSet( ent->maxs, 16, 16, 32 ); + ent->s.origin[2] += 0.125; + if ( !(ent->spawnflags&1) && G_CheckInSolid( ent, qfalse ) ) + { + gi.Printf(S_COLOR_RED"ERROR: Waypoint_navgoal %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin)); + assert(0); +#ifndef FINAL_BUILD + if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region + G_Error("Waypoint_navgoal %s at %s in solid!\n", ent->targetname, vtos(ent->currentOrigin)); + } +#endif + } + TAG_Add( ent->targetname, NULL, ent->s.origin, ent->s.angles, radius, RTF_NAVGOAL ); + + ent->classname = "navgoal"; + + NAV::SpawnedPoint(ent, NAV::PT_GOALNODE); + + G_FreeEntity( ent );//can't do this, they need to be found later by some functions, though those could be fixed, maybe? +} + +/* +------------------------- +Svcmd_Nav_f +------------------------- +*/ + +void Svcmd_Nav_f( void ) +{ + char *cmd = gi.argv( 1 ); + + if ( Q_stricmp( cmd, "show" ) == 0 ) + { + cmd = gi.argv( 2 ); + + if ( Q_stricmp( cmd, "all" ) == 0 ) + { + NAVDEBUG_showNodes = !NAVDEBUG_showNodes; + + //NOTENOTE: This causes the two states to sync up if they aren't already + NAVDEBUG_showCollision = NAVDEBUG_showNavGoals = + NAVDEBUG_showCombatPoints = NAVDEBUG_showEnemyPath = + NAVDEBUG_showEdges = NAVDEBUG_showNearest = NAVDEBUG_showRadius = NAVDEBUG_showNodes; + } + else if ( Q_stricmp( cmd, "nodes" ) == 0 ) + { + NAVDEBUG_showNodes = !NAVDEBUG_showNodes; + } + else if ( Q_stricmp( cmd, "radius" ) == 0 ) + { + NAVDEBUG_showRadius = !NAVDEBUG_showRadius; + } + else if ( Q_stricmp( cmd, "edges" ) == 0 ) + { + NAVDEBUG_showEdges = !NAVDEBUG_showEdges; + } + else if ( Q_stricmp( cmd, "testpath" ) == 0 ) + { + NAVDEBUG_showTestPath = !NAVDEBUG_showTestPath; + } + else if ( Q_stricmp( cmd, "enemypath" ) == 0 ) + { + NAVDEBUG_showEnemyPath = !NAVDEBUG_showEnemyPath; + } + else if ( Q_stricmp( cmd, "combatpoints" ) == 0 ) + { + NAVDEBUG_showCombatPoints = !NAVDEBUG_showCombatPoints; + } + else if ( Q_stricmp( cmd, "navgoals" ) == 0 ) + { + NAVDEBUG_showNavGoals = !NAVDEBUG_showNavGoals; + } + else if ( Q_stricmp( cmd, "collision" ) == 0 ) + { + NAVDEBUG_showCollision = !NAVDEBUG_showCollision; + } + else if ( Q_stricmp( cmd, "grid" ) == 0 ) + { + NAVDEBUG_showGrid = !NAVDEBUG_showGrid; + } + else if ( Q_stricmp( cmd, "nearest" ) == 0 ) + { + NAVDEBUG_showNearest = !NAVDEBUG_showNearest; + } + else if ( Q_stricmp( cmd, "lines" ) == 0 ) + { + NAVDEBUG_showPointLines = !NAVDEBUG_showPointLines; + } + } + else if ( Q_stricmp( cmd, "set" ) == 0 ) + { + cmd = gi.argv( 2 ); + + if ( Q_stricmp( cmd, "testgoal" ) == 0 ) + { + // NAVDEBUG_curGoal = navigator.GetNearestNode( &g_entities[0], g_entities[0].waypoint, NF_CLEAR_PATH, WAYPOINT_NONE ); + } + } + else if ( Q_stricmp( cmd, "goto" ) == 0 ) + { + cmd = gi.argv( 2 ); + NAV::TeleportTo(&(g_entities[0]), cmd); + } + else if ( Q_stricmp( cmd, "gotonum" ) == 0 ) + { + cmd = gi.argv( 2 ); + NAV::TeleportTo(&(g_entities[0]), atoi(cmd)); + } + else if ( Q_stricmp( cmd, "totals" ) == 0 ) + { + NAV::ShowStats(); + } + else + { + //Print the available commands + Com_Printf("nav - valid commands\n---\n" ); + Com_Printf("show\n - nodes\n - edges\n - testpath\n - enemypath\n - combatpoints\n - navgoals\n---\n"); + Com_Printf("goto\n ---\n" ); + Com_Printf("gotonum\n ---\n" ); + Com_Printf("totals\n ---\n" ); + Com_Printf("set\n - testgoal\n---\n" ); + } +} + +// +//JWEIER ADDITIONS START + +bool navCalculatePaths = false; + +bool NAVDEBUG_showNodes = false; +bool NAVDEBUG_showRadius = false; +bool NAVDEBUG_showEdges = false; +bool NAVDEBUG_showTestPath = false; +bool NAVDEBUG_showEnemyPath = false; +bool NAVDEBUG_showCombatPoints = false; +bool NAVDEBUG_showNavGoals = false; +bool NAVDEBUG_showCollision = false; +int NAVDEBUG_curGoal = 0; +bool NAVDEBUG_showGrid = false; +bool NAVDEBUG_showNearest = false; +bool NAVDEBUG_showPointLines = false; + + +// +//JWEIER ADDITIONS END \ No newline at end of file diff --git a/code/game/g_nav.h b/code/game/g_nav.h new file mode 100644 index 0000000..4e6c30e --- /dev/null +++ b/code/game/g_nav.h @@ -0,0 +1,30 @@ +#ifndef __G_NAV_H__ +#define __G_NAV_H__ + +#define WAYPOINT_NONE 0 + +#define MAX_RADIUS_CHECK 1024 +#define YAW_ITERATIONS 16 + +extern bool navCalculatePaths; + +extern bool NAVDEBUG_showNodes; +extern bool NAVDEBUG_showRadius; +extern bool NAVDEBUG_showEdges; +extern bool NAVDEBUG_showTestPath; +extern bool NAVDEBUG_showEnemyPath; +extern bool NAVDEBUG_showCombatPoints; +extern bool NAVDEBUG_showNavGoals; +extern bool NAVDEBUG_showCollision; +extern bool NAVDEBUG_showGrid; +extern bool NAVDEBUG_showNearest; +extern int NAVDEBUG_curGoal; +extern bool NAVDEBUG_showPointLines; + + +void CG_DrawNode( vec3_t origin, int type ); +void CG_DrawEdge( vec3_t start, vec3_t end, int type ); +void CG_DrawRadius( vec3_t origin, unsigned int radius, int type ); +void CG_DrawCombatPoint( vec3_t origin, int type ); + +#endif //#ifndef __G_NAV_H__ diff --git a/code/game/g_navigator.cpp b/code/game/g_navigator.cpp new file mode 100644 index 0000000..24f599a --- /dev/null +++ b/code/game/g_navigator.cpp @@ -0,0 +1,5553 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN SOFTWARE - STAR WARS: JK II +// (c) 2002 Activision +// +// +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#include "g_headers.h" + + + +//////////////////////////////////////////////////////////////////////////////////////// +// HFile Bindings +//////////////////////////////////////////////////////////////////////////////////////// +bool HFILEopen_read(int& handle, const char* filepath) {gi.FS_FOpenFile(filepath, &handle, FS_READ); return (handle!=0);} +bool HFILEopen_write(int& handle, const char* filepath) {gi.FS_FOpenFile(filepath, &handle, FS_WRITE); return (handle!=0);} +bool HFILEread(int& handle, void* data, int size) {return (gi.FS_Read(data, size, handle)!=0);} +bool HFILEwrite(int& handle, const void* data, int size) {return (gi.FS_Write(data, size, handle)!=0);} +bool HFILEclose(int& handle) {gi.FS_FCloseFile(handle); return true;} + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Externs +//////////////////////////////////////////////////////////////////////////////////////// +extern gentity_t* G_FindDoorTrigger( gentity_t *ent ); +extern qboolean G_EntIsBreakable( int entityNum, gentity_t *breaker ); +extern qboolean G_CheckInSolidTeleport (const vec3_t& teleportPos, gentity_t *self); + +extern cvar_t* g_nav1; +extern cvar_t* g_nav2; +extern cvar_t* g_developer; +extern int delayedShutDown; +extern vec3_t playerMinsStep; +extern vec3_t playerMaxs; + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#include "b_local.h" +#include "g_navigator.h" +#if !defined(RAGL_GRAPH_VS_INC) + #include "..\Ragl\graph_vs.h" +#endif +#if !defined(RATL_GRAPH_REGION_INC) + #include "..\Ragl\graph_region.h" +#endif +//#if !defined(RATL_GRAPH_TRIANGULATE_INC) +// #include "..\Ragl\graph_triangulate.h" +//#endif +#if !defined(RATL_VECTOR_VS_INC) + #include "..\Ratl\vector_vs.h" +#endif +#if !defined(RUFL_HSTRING_INC) + #include "..\Rufl\hstring.h" +#endif +#if !defined(RUFL_HFILE_INC) + #include "..\Rufl\hfile.h" +#endif +#if !defined(RAVL_BOUNDS_INC) + #include "..\Ravl\CBounds.h" +#endif + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Defines +//////////////////////////////////////////////////////////////////////////////////////// +#define NAV_VERSION 1.3f +#define NEIGHBORING_DIST 200.0f +#define SAFE_NEIGHBORINGPOINT_DIST 400.0f +#define SAFE_AT_NAV_DIST_SQ 6400.0f //80*80 +#define SAFE_GOTO_DIST_SQ 19600.0f //140*140 + +namespace NAV +{ + enum + { +#ifdef _XBOX + // now 11 bytes each + NUM_NODES = 900, // Question for VV- is this big enough for all the levels? if so, we should use it too... +#else + NUM_NODES = 1024, +#endif + // now 5 bytes each + NUM_EDGES = 3*NUM_NODES, + NUM_EDGES_PER_NODE = 20, + + + NUM_REGIONS = NUM_NODES/3, // Had to raise this up for bounty + NUM_CELLS = 32, // should be the square root of NUM_NODES + NUM_NODES_PER_CELL = 60, // had to raise this for t3_bounty + NUM_TARGETS = 5, // max number of outgoing edges from a given node + + CELL_RANGE = 1000, + VIEW_RANGE = 550, + + BIAS_NONWAYPOINT = 500, + BIAS_DANGER = 8000, + BIAS_TOOSMALL = 10000, + + NULL_PATH_USER_INDEX= -1, +#ifdef _XBOX + // This may not be safe, but I REALLY need memory. Better testing will reveal that + // these don't work, but in quick tests these numbers were sufficient. + MAX_PATH_USERS = 60, + MAX_PATH_SIZE = 50, +#else + MAX_PATH_USERS = 100, + MAX_PATH_SIZE = NUM_NODES/7, +#endif + + Z_CULL_OFFSET = 60, + + MAX_NODES_PER_NAME = 30, + MAX_EDGE_SEG_LEN = 100, + MAX_EDGE_FLOOR_DIST = 60, + MAX_EDGE_AUTO_LEN = 500, + + MAX_EDGES_PER_ENT = 10, + MAX_BLOCKING_ENTS = 100, + MAX_ALERTS_PER_AGENT= 10, + MAX_ALERT_TIME = 10000, + + MIN_WAY_NEIGHBORS = 4, + MAX_NONWP_NEIGHBORS = 1, + + + // Human Sized + //------------- + SC_MEDIUM_RADIUS = 20, + SC_MEDIUM_HEIGHT = 60, + + // Rancor Sized + //-------------- + SC_LARGE_RADIUS = 60, + SC_LARGE_HEIGHT = 120, + + + SAVE_LOAD = 0, + + CHECK_JUMP = 0, + CHECK_START_OPEN = 1, + CHECK_START_SOLID = 1, + }; +} + +namespace STEER +{ + enum + { + NULL_STEER_USER_INDEX= -1, + MAX_NEIGHBORS = 20, + Z_CULL_OFFSET = 60, + SIDE_LOCKED_TIMER = 2000, + NEIGHBOR_RANGE = 60, + }; +} + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Total Memory - 11 Bytes (can save 5 bytes by removing Name and Targets) +//////////////////////////////////////////////////////////////////////////////////////// +class CWayNode +{ +public: + CVec3 mPoint; + float mRadius; + NAV::EPointType mType; + hstring mName; // TODO OPTIMIZATION: Remove This? + hstring mTargets[NAV::NUM_TARGETS]; // TODO OPTIMIZATION: Remove This + enum EWayNodeFlags + { + WN_NONE = 0, + WN_ISLAND, + WN_FLOATING, + WN_DROPTOFLOOR, + WN_NOAUTOCONNECT, + WN_MAX + }; + ratl::bits_vs mFlags; + + //////////////////////////////////////////////////////////////////////////////////// + // Access Operator (For Cells)(For Triangulation) + //////////////////////////////////////////////////////////////////////////////////// + float operator[](int dimension) + { + return mPoint[dimension]; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Left Right Test (For Triangulation) + //////////////////////////////////////////////////////////////////////////////////// + virtual ESide LRTest(const CWayNode& A, const CWayNode& B) const + { + return (mPoint.LRTest(A.mPoint, B.mPoint)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Point In Circle (For Triangulation) + //////////////////////////////////////////////////////////////////////////////////// + virtual bool InCircle(const CWayNode& A, const CWayNode& B, const CWayNode& C) const + { + return (mPoint.PtInCircle(A.mPoint, B.mPoint, C.mPoint)); + } +}; +const CWayNode& GetNode(int Handle); + +//////////////////////////////////////////////////////////////////////////////////////// +// Total Memory - 5 bytes +//////////////////////////////////////////////////////////////////////////////////////// +class CWayEdge +{ +public: + int mNodeA; // DO NOT REMOVE THIS: Handles are full ints because upper bits are used + int mNodeB; // DO NOT REMOVE THIS: Handles are full ints because upper bits are used + float mDistance; // DO NOT REMOVE THIS: It's a serious runtime optimization for A* + + unsigned short mOwnerNum; // Converted to short. Largest entity number is 1024 + unsigned short mEntityNum; // Converted to short. Largest entity number is 1024 + enum EWayEdgeFlags + { + WE_NONE = 0, + + WE_SIZE_MEDIUM, + WE_SIZE_LARGE, + + WE_BLOCKING_DOOR, + WE_BLOCKING_WALL, + WE_BLOCKING_BREAK, + + WE_VALID, + WE_ONHULL, + WE_FLYING, + WE_JUMPING, + WE_CANBEINVAL, + WE_DESIGNERPLACED, + + WE_MAX + }; + ratl::bits_vs mFlags; // Should be only one int + + + //////////////////////////////////////////////////////////////////////////////////// + // Size Function + //////////////////////////////////////////////////////////////////////////////////// + inline int Blocking() + { + if (mFlags.get_bit(WE_BLOCKING_BREAK)) + { + return WE_BLOCKING_BREAK; + } + if (mFlags.get_bit(WE_BLOCKING_WALL)) + { + return WE_BLOCKING_WALL; + } + if (mFlags.get_bit(WE_BLOCKING_DOOR)) + { + return WE_BLOCKING_DOOR; + } + return 0; + } + inline bool BlockingBreakable() {return (mFlags.get_bit(WE_BLOCKING_BREAK));} + inline bool BlockingWall() {return (mFlags.get_bit(WE_BLOCKING_WALL));} + inline bool BlockingDoor() {return (mFlags.get_bit(WE_BLOCKING_DOOR));} + + + //////////////////////////////////////////////////////////////////////////////////// + // Size Function + //////////////////////////////////////////////////////////////////////////////////// + inline int Size() const + { + return (mFlags.get_bit(WE_SIZE_MEDIUM)?(WE_SIZE_MEDIUM):(WE_SIZE_LARGE)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Access Operator (For Cells)(For Triangulation) + //////////////////////////////////////////////////////////////////////////////////// + float operator[](int dimension) const + { + CVec3 Half(GetNode(mNodeA).mPoint + GetNode(mNodeB).mPoint); + return (Half[dimension] * 0.5f); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Point - returns the center of the edge + //////////////////////////////////////////////////////////////////////////////////// + void Point(CVec3& Half) const + { + Half = GetNode(mNodeA).mPoint; + Half += GetNode(mNodeB).mPoint; + Half *= 0.5f; + + } + + //////////////////////////////////////////////////////////////////////////////////// + // GetPoint A + //////////////////////////////////////////////////////////////////////////////////// + const CVec3& PointA() const + { + return GetNode(mNodeA).mPoint; + } + + //////////////////////////////////////////////////////////////////////////////////// + // GetPoint B + //////////////////////////////////////////////////////////////////////////////////// + const CVec3& PointB() const + { + return GetNode(mNodeB).mPoint; + } + +}; +const CWayEdge& GetEdge(int Handle); + + +struct SNodeSort +{ + NAV::TNodeHandle mHandle; + float mDistance; + bool mInRadius; + + + bool operator < (const SNodeSort& other) const + { + return (mDistance TGraph; +typedef ragl::graph_region TGraphRegion; +typedef TGraph::cells TGraphCells; +typedef ratl::vector_vs TNearestNavSort; + +typedef ratl::array_vs TAlertList; +typedef ratl::array_vs TEntityAlertList; +typedef ratl::vector_vs TNamedNodeList; +typedef ratl::map_vs TNameToNodeMap; + +typedef ratl::vector_vs TEdgesPerEnt; +typedef ratl::map_vs TEntEdgeMap; + + +//////////////////////////////////////////////////////////////////////////////////////// +// Path Point +// +// This is actual vector and speed location (as well as node handle of that the agent +// has planned to go to. +//////////////////////////////////////////////////////////////////////////////////////// +struct SPathPoint +{ + CVec3 mPoint; + float mSpeed; + float mSlowingRadius; + float mReachedRadius; + float mDist; + float mETA; + NAV::TNodeHandle mNode; +}; +typedef ratl::vector_vs TPath; + + +//////////////////////////////////////////////////////////////////////////////////////// +// Path User +// +// This is the cached path for a given actor +//////////////////////////////////////////////////////////////////////////////////////// +struct SPathUser +{ + int mEnd; + bool mSuccess; + int mLastUseTime; + int mLastAStarTime; + TPath mPath; +}; +typedef ratl::pool_vs TPathUsers; +typedef ratl::array_vs TPathUserIndex; + + +typedef ratl::vector_vs TNeighbors; + +//////////////////////////////////////////////////////////////////////////////////////// +// Steer User +// +// This is the cached steering data for a given actor +//////////////////////////////////////////////////////////////////////////////////////// +struct SSteerUser +{ + // Constant Values In Entity + //--------------------------- + float mMaxForce; + float mMaxSpeed; + float mRadius; + float mMass; + + + // Current Values + //---------------- + TNeighbors mNeighbors; + + CVec3 mOrientation; + CVec3 mPosition; + + CVec3 mVelocity; + float mSpeed; + + + // Values Projected From Current Values + //-------------------------------------- + CVec3 mProjectFwd; + CVec3 mProjectSide; + CVec3 mProjectPath; + + + // Temporary Values + //------------------ + CVec3 mDesiredVelocity; + float mDesiredSpeed; + float mDistance; + CVec3 mSeekLocation; + + int mIgnoreEntity; + + bool mBlocked; + int mBlockedTgtEntity; + CVec3 mBlockedTgtPosition; + + // Steering + //---------- + CVec3 mSteering; + float mNewtons; +}; + +typedef ratl::pool_vs TSteerUsers; +typedef ratl::array_vs TSteerUserIndex; +typedef ratl::bits_vs TEntBits; + + +TAlertList& GetAlerts(gentity_t* actor); +TGraph& GetGraph(); +int GetAirRegion(); +int GetIslandRegion(); + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Graph User +// +// Here we define our own user class, which can invalidate edges and generate unique +// costs for node traversal based on the nodes, and possibly the actor attempting to +// cross the nodes at any given time. +// +//////////////////////////////////////////////////////////////////////////////////////// +class CGraphUser : public TGraph::user +{ +private: + gentity_t* mActor; + int mActorSize; + + CVec3 mDangerSpot; + float mDangerSpotRadiusSq; + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + void SetActor(gentity_t* actor) + { + mActor = actor; + mActorSize = NAV::ClassifyEntSize(actor); + mDangerSpotRadiusSq = 0; + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + void ClearActor() + { + mActor = 0; + mActorSize = 0; + mDangerSpotRadiusSq = 0; + } + + gentity_t* GetActor() + { + return mActor; + } + + void SetDangerSpot(const CVec3& Spot, float RadiusSq) + { + mDangerSpot = Spot; + mDangerSpotRadiusSq = RadiusSq; + } + void ClearDangerSpot() + { + mDangerSpotRadiusSq = 0; + } + + + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + virtual bool can_be_invalid(const CWayEdge& Edge) const + { + return (Edge.mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_CANBEINVAL)); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + virtual bool is_valid(CWayEdge& Edge, int EndPoint=0) const + { + // If The Actor Can't Fly, But This Is A Flying Edge, It's Invalid + //----------------------------------------------------------------- + if (mActor && Edge.mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_FLYING) && mActor->NPC && !(mActor->NPC->scriptFlags&SCF_NAV_CAN_FLY)) + { + return false; + } + + // If The Actor Can't Fly, But This Is A Flying Edge, It's Invalid + //----------------------------------------------------------------- + if (mActor && Edge.mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_JUMPING) && mActor->NPC && !(mActor->NPC->scriptFlags&SCF_NAV_CAN_JUMP)) + { + return false; + } + + + // If The Actor Is Too Big, This Is Not A Valid Edge For Him + //----------------------------------------------------------- + if (mActor && Edge.Size()NPC) && + (mActor->NPC->aiFlags&NPCAI_NAV_THROUGH_BREAKABLES) && + (Edge.BlockingBreakable()) && + (G_EntIsBreakable(Edge.mEntityNum, mActor)) + ) + { + return true; + } + + // Is This A Door? + //----------------- + if (Edge.BlockingDoor()) + { + bool StartOpen = (ent->spawnflags & 1); + bool Closed = (StartOpen)?(ent->moverState==MOVER_POS2):(ent->moverState==MOVER_POS1); + + // If It Is Closed, We Want To Check If It Will Auto Open For Us + //--------------------------------------------------------------- + if (Closed) + { + gentity_t* owner = &g_entities[Edge.mOwnerNum]; + if (owner) + { + // Check To See If The Owner Is Inactive Or Locked, Or Unavailable To The NPC + //---------------------------------------------------------------------------- + if ((owner->svFlags & SVF_INACTIVE) || + (owner==ent && (owner->spawnflags & (MOVER_PLAYER_USE|MOVER_FORCE_ACTIVATE|MOVER_LOCKED))) || + (owner!=ent && (owner->spawnflags & (1 /*PLAYERONLY*/|4 /*USE_BOTTON*/)))) + { + return false; + } + + + // Look For A Key + //---------------- + if (mActor!=0 && (owner->spawnflags & MOVER_GOODIE)) + { + int key = INV_GoodieKeyCheck(mActor); + if (!key) + { + return false; + } + } + } + + // No Owner? This Must Be A Scripted Door Or Other Contraption + //-------------------------------------------------------------- + else + { + return false; + } + } + return true; + } + + // If This Is A Wall, Check If It Has Contents Now + //------------------------------------------------- + else if (Edge.BlockingWall()) + { + return !(ent->contents&CONTENTS_SOLID); + } + } + } + else if ( Edge.BlockingBreakable()) + {//we had a breakable in our way, now it's gone, see if there is anything else in the way + if ( NAV::TestEdge( Edge.mNodeA, Edge.mNodeB, false ) ) + {//clear it + Edge.mFlags.clear_bit(CWayEdge::EWayEdgeFlags::WE_BLOCKING_BREAK); + } + //NOTE: if this fails with the SC_LARGE size + } + + return (Edge.mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_VALID)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // This is the cost estimate from any node to any other node (usually the goal) + //////////////////////////////////////////////////////////////////////////////////// + virtual float cost(const CWayNode& A, const CWayNode& B) const + { + return (A.mPoint.Dist(B.mPoint)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // This is the cost estimate for traversing a particular edge + //////////////////////////////////////////////////////////////////////////////////// + virtual float cost(const CWayEdge& Edge, const CWayNode& B) const + { + float DangerBias = 0.0f; + if (mActor) + { + int eHandle = GetGraph().edge_index(Edge); + TAlertList& al = GetAlerts(mActor); + for (int alIndex=0; alIndex0.0f) + { + DangerBias += (al[alIndex].mDanger*NAV::BIAS_DANGER); + } + } + + // If The Actor Is Too Big, Bias This Edge For Him + //------------------------------------------------- + if (Edge.Size() mDangerSpot.DistToLine2(Edge.PointA(), Edge.PointB())) + { + DangerBias += NAV::BIAS_DANGER; + } + + + if (B.mType==NAV::PT_WAYNODE) + { + return (Edge.mDistance + DangerBias); + } + return ((Edge.mDistance + DangerBias) + NAV::BIAS_NONWAYPOINT); + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + virtual bool on_same_floor(const CWayNode& A, const CWayNode& B) const + { + return (fabsf(A.mPoint[2] - B.mPoint[2])<100.0f); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // setup the edge (For Triangulation) + // + // This function is here because it evaluates the base cost from NodeA to NodeB, which + // + //////////////////////////////////////////////////////////////////////////////////// + virtual void setup_edge(CWayEdge& Edge, int A, int B, bool OnHull, const CWayNode& NodeA, const CWayNode& NodeB, bool CanBeInvalid=false) + { + Edge.mNodeA = A; + Edge.mNodeB = B; + Edge.mDistance = NodeA.mPoint.Dist(NodeB.mPoint); + Edge.mEntityNum = ENTITYNUM_NONE; + Edge.mOwnerNum = ENTITYNUM_NONE; + Edge.mFlags.clear(); + Edge.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_VALID); + if (CanBeInvalid) + { + Edge.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_CANBEINVAL); + } + if (OnHull) + { + Edge.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_ONHULL); + } + } +}; + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Global Public Objects +//////////////////////////////////////////////////////////////////////////////////////// +TGraph mGraph; +TGraphRegion mRegion(mGraph); +TGraphCells mCells(mGraph); + +TGraph::search mSearch; +CGraphUser mUser; + + +TNameToNodeMap mNodeNames; +TEntEdgeMap mEntEdgeMap; + +TNearestNavSort mNearestNavSort; + +TPathUsers mPathUsers; +TPathUserIndex mPathUserIndex; +SPathUser mPathUserMaster; + +TSteerUsers mSteerUsers; +TSteerUserIndex mSteerUserIndex; + +TEntityAlertList mEntityAlertList; + +vec3_t mZeroVec; +trace_t mMoveTrace; +trace_t mViewTrace; + +int mMoveTraceCount = 0; +int mViewTraceCount = 0; +int mConnectTraceCount = 0; +int mConnectTime = 0; +int mIslandCount = 0; +int mIslandRegion = 0; +int mAirRegion = 0; +char mLocStringA[256] = {0}; +char mLocStringB[256] = {0}; + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +TAlertList& GetAlerts(gentity_t* actor) +{ + return mEntityAlertList[actor->s.number]; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +TGraph& GetGraph() +{ + return mGraph; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +const CWayNode& GetNode(int Handle) +{ + return mGraph.get_node(Handle); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +const CWayEdge& GetEdge(int Handle) +{ + return mGraph.get_edge(Handle); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +int GetAirRegion() +{ + return mAirRegion; +} +int GetIslandRegion() +{ + return mIslandRegion; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Helper Function : View Trace +//////////////////////////////////////////////////////////////////////////////////////// +bool ViewTrace(const CVec3& a, const CVec3& b) +{ + int contents = (CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_MONSTERCLIP); + + mViewTraceCount++; + gi.trace(&mViewTrace, a.v, 0, 0, b.v, ENTITYNUM_NONE, contents); + + if ((mViewTrace.allsolid==qfalse) && (mViewTrace.startsolid==qfalse ) && (mViewTrace.fraction==1.0f)) + { + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Helper Function : View Trace +//////////////////////////////////////////////////////////////////////////////////////// +bool ViewNavTrace(const CVec3& a, const CVec3& b) +{ + int contents = (CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP); + + mViewTraceCount++; + gi.trace(&mViewTrace, a.v, 0, 0, b.v, ENTITYNUM_NONE, contents); + + if ((mViewTrace.allsolid==qfalse) && (mViewTrace.startsolid==qfalse ) && (mViewTrace.fraction==1.0f)) + { + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Helper Function : Move Trace +//////////////////////////////////////////////////////////////////////////////////////// +bool MoveTrace(const CVec3& Start, const CVec3& Stop, const CVec3& Mins, const CVec3& Maxs, + int IgnoreEnt=0, + bool CheckForDoNotEnter=false, + bool RetryIfStartInDoNotEnter=true, + bool IgnoreAllEnts=false, + int OverrideContents=0) +{ + int contents = (MASK_NPCSOLID); + if (OverrideContents) + { + contents = OverrideContents; + } + if (CheckForDoNotEnter) + { + contents |= CONTENTS_BOTCLIP; + } + if (IgnoreAllEnts) + { + contents &= ~CONTENTS_BODY; + } + + + // Run The Trace + //--------------- + mMoveTraceCount++; + gi.trace(&mMoveTrace, Start.v, Mins.v, Maxs.v, Stop.v, IgnoreEnt, contents); + + + // Did It Make It? + //----------------- + if ((mMoveTrace.allsolid==qfalse) && (mMoveTrace.startsolid==qfalse ) && (mMoveTrace.fraction==1.0f)) + { + return true; + } + + // If We Started In Solid, Try Removing The "Do Not Enter" Contents Type, And Trace Again + //---------------------------------------------------------------------------------------- + if (CheckForDoNotEnter && RetryIfStartInDoNotEnter && ((mMoveTrace.allsolid==qtrue) || (mMoveTrace.startsolid==qtrue))) + { + contents &= ~CONTENTS_BOTCLIP; + + // Run The Trace + //--------------- + mMoveTraceCount++; + gi.trace(&mMoveTrace, Start.v, Mins.v, Maxs.v, Stop.v, IgnoreEnt, contents); + + // Did It Make It? + //----------------- + if ((mMoveTrace.allsolid==qfalse) && (mMoveTrace.startsolid==qfalse ) && (mMoveTrace.fraction==1.0f)) + { + return true; + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Helper Function : Move Trace (with actor) +//////////////////////////////////////////////////////////////////////////////////////// +bool MoveTrace(gentity_t* actor, const CVec3& goalPosition, bool IgnoreAllEnts=false) +{ + assert(actor!=0); + CVec3 Mins(actor->mins); + CVec3 Maxs(actor->maxs); + + Mins[2] += (STEPSIZE*1); + + return MoveTrace(actor->currentOrigin, goalPosition, Mins, Maxs, actor->s.number, true, true, IgnoreAllEnts/*, actor->contents*/); +} + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// GoTo +// +// This Function serves as a master control for finding, updating, and following a path +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::GoTo(gentity_t* actor, TNodeHandle target, float MaxDangerLevel) +{ + assert(actor!=0 && actor->client!=0); + + // Check If We Already Have A Path + //--------------------------------- + bool HasPath = NAV::HasPath(actor); + + // If So Update It + //----------------- + if (HasPath) + { + HasPath = NAV::UpdatePath(actor, target, MaxDangerLevel); + } + + // If No Path, Try To Find One + //----------------------------- + if (!HasPath) + { + HasPath = NAV::FindPath(actor, target, MaxDangerLevel); + } + + // If We Have A Path, Now Try To Follow It + //----------------------------------------- + if (HasPath) + { + HasPath = (STEER::Path(actor)!=0.0f); + if (HasPath) + { + if (STEER::AvoidCollisions(actor, actor->client->leader)) + { + STEER::Blocked(actor, NAV::NextPosition(actor)); + } + } + else + { + STEER::Blocked(actor, NAV::GetNodePosition(target)); + } + } + + // Nope, No Path At All... Bad + //------------------------------ + else + { + STEER::Blocked(actor, NAV::GetNodePosition(target)); + } + + return HasPath; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::GoTo(gentity_t* actor, gentity_t* target, float MaxDangerLevel) +{ + assert(actor!=0 && actor->client!=0); + + bool HasPath = false; + TNodeHandle targetNode = GetNearestNode(target, true); + + // If The Target Has No Nearest Nav Point, Go To His Last Valid Waypoint Instead + //------------------------------------------------------------------------------- + if (targetNode==0) + { + targetNode = target->lastWaypoint; + } + + // Has He EVER Had A Valid Waypoint? + //----------------------------------- + if (targetNode!=0) + { + // If On An Edge, Pick The Safest Of The Two Points + //-------------------------------------------------- + if (targetNode<0) + { + targetNode = (Q_irand(0,1)==0)?(mGraph.get_edge(abs(targetNode)).mNodeA):(mGraph.get_edge(abs(targetNode)).mNodeB); + } + + // Check If We Already Have A Path + //--------------------------------- + HasPath = NAV::HasPath(actor); + + // If So Update It + //----------------- + if (HasPath) + { + HasPath = NAV::UpdatePath(actor, targetNode, MaxDangerLevel); + } + + // If No Path, Try To Find One + //----------------------------- + if (!HasPath) + { + HasPath = NAV::FindPath(actor, targetNode, MaxDangerLevel); + } + + // If We Have A Path, Now Try To Follow It + //----------------------------------------- + if (HasPath) + { + HasPath = (STEER::Path(actor)!=0.0f); + if (HasPath) + { + // Attempt To Avoid Collisions Along The Path + //-------------------------------------------- + if (STEER::AvoidCollisions(actor, actor->client->leader)) + { + // Have A Path, Currently Blocked By Something + //--------------------------------------------- + STEER::Blocked(actor, NAV::NextPosition(actor)); + } + } + else + { + STEER::Blocked(actor, target); + } + } + + // Nope, No Path At All... Bad + //------------------------------ + else + { + STEER::Blocked(actor, target); + } + } + + // No Waypoint Near + //------------------ + else + { + STEER::Blocked(actor, target); + } + + return HasPath; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::GoTo(gentity_t* actor, const vec3_t& position, float MaxDangerLevel) +{ + assert(actor!=0 && actor->client!=0); + + bool HasPath = false; + TNodeHandle targetNode = GetNearestNode(position); + if (targetNode!=0) + { + // If On An Edge, Pick The Safest Of The Two Points + //-------------------------------------------------- + if (targetNode<0) + { + targetNode = (Q_irand(0,1)==0)?(mGraph.get_edge(abs(targetNode)).mNodeA):(mGraph.get_edge(abs(targetNode)).mNodeB); + } + + // Check If We Already Have A Path + //--------------------------------- + HasPath = NAV::HasPath(actor); + + // If So Update It + //----------------- + if (HasPath) + { + HasPath = NAV::UpdatePath(actor, targetNode, MaxDangerLevel); + } + + // If No Path, Try To Find One + //----------------------------- + if (!HasPath) + { + HasPath = NAV::FindPath(actor, targetNode, MaxDangerLevel); + } + + // If We Have A Path, Now Try To Follow It + //----------------------------------------- + if (HasPath) + { + HasPath = (STEER::Path(actor)!=0.0f); + if (HasPath) + { + // Attempt To Avoid Collisions Along The Path + //-------------------------------------------- + if (STEER::AvoidCollisions(actor, actor->client->leader)) + { + // Have A Path, Currently Blocked By Something + //--------------------------------------------- + STEER::Blocked(actor, NAV::NextPosition(actor)); + } + } + else + { + STEER::Blocked(actor, NAV::NextPosition(actor)); + } + } + + // Nope, No Path At All... Bad + //------------------------------ + else + { + STEER::Blocked(actor, position); + } + } + + // No Waypoint Near + //------------------ + else + { + STEER::Blocked(actor, position); + } + + return HasPath; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// This function exists as a wrapper so that the graph can write to the gi.Printf() +//////////////////////////////////////////////////////////////////////////////////////// +void stupid_print(const char* data) +{ + gi.Printf(data); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::LoadFromFile(const char *filename, int checksum) +{ + mZeroVec[0] = 0; + mZeroVec[1] = 0; + mZeroVec[2] = 0; + + mPathUserIndex.fill(NULL_PATH_USER_INDEX); + mSteerUserIndex.fill(STEER::NULL_STEER_USER_INDEX); + + mMoveTraceCount = 0; + mViewTraceCount = 0; + mConnectTraceCount = 0; + mConnectTime = 0; + mIslandCount = 0; + mIslandRegion = 0; + mAirRegion = 0; + + memset(&mEntityAlertList, 0, sizeof(mEntityAlertList)); + +#if !defined(FINAL_BUILD) + ratl::ratl_base::OutputPrint = stupid_print; +#endif + + mGraph.clear(); + mRegion.clear(); + mCells.clear(); + mNodeNames.clear(); + mNearestNavSort.clear(); + +#ifndef _XBOX + if (SAVE_LOAD) + { + hfile navFile(va("maps/%s.navNEW")); + if (!navFile.open_read(NAV_VERSION, checksum)) + { + return false; + } + navFile.load(&mGraph, sizeof(mGraph)); + navFile.load(&mRegion, sizeof(mRegion)); + navFile.load(&mCells, sizeof(mCells)); + navFile.close(); + return true; + } +#endif + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::TestEdge( TNodeHandle NodeA, TNodeHandle NodeB, qboolean IsDebugEdge ) +{ + int atHandle = mGraph.get_edge_across( NodeA, NodeB ); + CWayEdge& at = mGraph.get_edge(atHandle); + CWayNode& a = mGraph.get_node(at.mNodeA); + CWayNode& b = mGraph.get_node(at.mNodeB); + CVec3 Mins(-15.0f, -15.0f, 0.0f); // These were the old "sizeless" defaults + CVec3 Maxs(15.0f, 15.0f, 40.0f); + bool CanGo = false; + bool HitCharacter = false; + int EntHit = ENTITYNUM_NONE; + int i = (int)(at.Size()); + + a.mPoint.ToStr(mLocStringA); + b.mPoint.ToStr(mLocStringB); + + const char* aName = (a.mName.empty())?(mLocStringA):(a.mName.c_str()); + const char* bName = (b.mName.empty())?(mLocStringB):(b.mName.c_str()); + + float radius = (at.Size()==CWayEdge::EWayEdgeFlags::WE_SIZE_LARGE)?(SC_LARGE_RADIUS):(SC_MEDIUM_RADIUS); + float height = (at.Size()==CWayEdge::EWayEdgeFlags::WE_SIZE_LARGE)?(SC_LARGE_HEIGHT):(SC_MEDIUM_HEIGHT); + + Mins[0] = Mins[1] = (radius) * -1.0f; + Maxs[0] = Maxs[1] = (radius); + Maxs[2] = (height); + + + // If Either Start Or End Points Are Too Small, Don' Bother At This Size + //----------------------------------------------------------------------- + if ((a.mType==PT_WAYNODE && a.mRadius(%s): Size Too Big\n", aName, bName, i); + } + CanGo = false; + return CanGo; + } + + + // Try It + //-------- + CanGo = MoveTrace(a.mPoint, b.mPoint, Mins, Maxs, 0, true, false); + EntHit = mMoveTrace.entityNum; + + // Check For A Flying Edge + //------------------------- + if (a.mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING) || b.mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING)) + { + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_FLYING); + if (!a.mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING) || !b.mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING)) + { + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_CANBEINVAL); + } + } + + + // Well, It' Can't Go, But Possibly If We Hit An Entity And Remove That Entity, We Can Go? + //----------------------------------------------------------------------------------------- + if (!CanGo && + !mMoveTrace.startsolid && + EntHit!=ENTITYNUM_WORLD && + EntHit!=ENTITYNUM_NONE && + (&g_entities[EntHit])!=0) + { + gentity_t* ent = &g_entities[EntHit]; + + if (IsDebugEdge) + { + gi.Printf("Nav(%s)<->(%s): Hit Entity Type (%s), TargetName (%s)\n", aName, bName, ent->classname, ent->targetname); + } + + + // Find Out What Type Of Entity This Is + //-------------------------------------- + if (!Q_stricmp("func_door", ent->classname)) + { + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_BLOCKING_DOOR); + } + else if ( + !Q_stricmp("func_wall", ent->classname) || + !Q_stricmp("func_static", ent->classname) || + !Q_stricmp("func_usable", ent->classname)) + { + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_BLOCKING_WALL); + } + else if ( + !Q_stricmp("func_glass", ent->classname) || + !Q_stricmp("func_breakable", ent->classname) || + !Q_stricmp("misc_model_breakable", ent->classname)) + { + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_BLOCKING_BREAK); + } + else if (ent->NPC || ent->s.number==0) + { + HitCharacter = true; + } + + // Don't Care About Any Other Entity Types + //----------------------------------------- + else + { + if (IsDebugEdge) + { + gi.Printf("Nav(%s)<->(%s): Unable To Ignore Ent, Going A Size Down\n", aName, bName); + } + return CanGo; // Go To Next Size Down + } + + + // If It Is A Door, Try Opening The Door To See If We Can Get In + //--------------------------------------------------------------- + if (at.BlockingDoor()) + { + // Find The Master + //----------------- + gentity_t *master = ent; + while (master && master->teammaster && (master->flags&FL_TEAMSLAVE)) + { + master = master->teammaster; + } + bool DoorIsStartOpen = master->spawnflags&1; + + // Open The Chain + //---------------- + gentity_t *slave = master; + while (slave) + { + VectorCopy((DoorIsStartOpen)?(slave->pos1):(slave->pos2), slave->currentOrigin); + gi.linkentity(slave); + slave = slave->teamchain; + } + + // Try The Trace + //--------------- + CanGo = MoveTrace(a.mPoint, b.mPoint, Mins, Maxs, 0, true, false); + if (CanGo) + { + ent = master; + EntHit = master->s.number; + } + else + { + if (IsDebugEdge) + { + gi.Printf("Nav(%s)<->(%s): Unable Pass Through Door Even When Open, Going A Size Down\n", aName, bName); + } + } + + // Close The Door + //---------------- + slave = master; + while (slave) + { + VectorCopy((DoorIsStartOpen)?(slave->pos2):(slave->pos1), slave->currentOrigin); + gi.linkentity(slave); + slave = slave->teamchain; + } + } + + // Assume Breakable Walls Will Be Clear Later + //----------------------------------------------------- + else if (at.BlockingBreakable()) + {//we'll do the trace again later if this ent gets broken + CanGo = true; + } + + // Otherwise, Try It, Pretending The Ent is Not There + //---------------------------------------------------- + else + { + CanGo = MoveTrace(a.mPoint, b.mPoint, Mins, Maxs, EntHit, true, false); + if (IsDebugEdge) + { + gi.Printf("Nav(%s)<->(%s): Unable Pass Through Even If Entity Was Gone, Going A Size Down\n", aName, bName); + } + } + + + + // If We Can Now Go, Ignoring The Entity, Remember That (But Don't Remember Characters - They Won't Stay Anyway) + //--------------------------------------------------------------------------------------------------------------- + if (CanGo && !HitCharacter) + { + ent->wayedge = atHandle; + at.mEntityNum = EntHit; + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_CANBEINVAL); + + // Add It To The Edge Map + //------------------------ + TEntEdgeMap::iterator eemiter = mEntEdgeMap.find(EntHit); + if (eemiter==mEntEdgeMap.end()) + { + TEdgesPerEnt EdgesPerEnt; + EdgesPerEnt.push_back(atHandle); + mEntEdgeMap.insert(EntHit, EdgesPerEnt); + } + else + { + if (!eemiter->full()) + { + eemiter->push_back(atHandle); + } + else + { +#ifndef FINAL_BUILD + assert("Max Edges Perh Handle Reached, Unable To Add To Edge Map!"==0); + gi.Printf("WARNING: Too many nav edges pass through entity %d (%s)\n", EntHit, g_entities[EntHit].targetname); +#endif + } + } + + // Check For Special Conditions For The Different Types + //------------------------------------------------------- + if (at.BlockingDoor()) + { + // Doors Need To Know Their "owner" Entity - The Thing That Controlls + // When The Door Is Open (or can "auto open") + // + // We'll start by assuming the door is it's own master + //----------------------------------------------------- + at.mOwnerNum = ent->s.number; + gentity_t* owner = 0; + + // If There Is A Target Name, See If This Thing Is Controlled From A Switch Or Something + //--------------------------------------------------------------------------------------- + if (ent->targetname) + { + // Try Target + //------------ + owner = G_Find(owner, FOFS(target), ent->targetname); + if (owner && + (!Q_stricmp("trigger_multiple", owner->classname) || !Q_stricmp("trigger_once", owner->classname))) + { + at.mOwnerNum = owner->s.number; + } + else + { + // Try Target2 + //------------- + owner = G_Find(owner, FOFS(target2), ent->targetname); + if (owner && + (!Q_stricmp("trigger_multiple", owner->classname) || !Q_stricmp("trigger_once", owner->classname))) + { + at.mOwnerNum = owner->s.number; + } + } + } + + // Otherwise, See If There Is An Auto Door Opener Trigger + //-------------------------------------------------------- + else + { + owner = G_FindDoorTrigger(ent); + if (owner) + { + at.mOwnerNum = owner->s.number; + } + } + } + + // Breakable Walls Are Not Valid Until Broken. Period + //----------------------------------------------------- + else if (at.BlockingBreakable()) + {//we'll do the trace again later if this ent gets broken + at.mFlags.clear_bit(CWayEdge::EWayEdgeFlags::WE_VALID); + } + } + } + + // Now Search For Any Holes In The Ground + //---------------------------------------- + if (CHECK_JUMP && CanGo) + { + CVec3 Mins(-15.0f, -15.0f, 0.0f); // These were the old "sizeless" defaults + CVec3 Maxs(15.0f, 15.0f, 40.0f); + + CVec3 AtoB(b.mPoint - a.mPoint); + float AtoBDist = AtoB.SafeNorm(); + int AtoBSegs = (AtoBDist / MAX_EDGE_SEG_LEN); + + AtoB *= MAX_EDGE_SEG_LEN; + CVec3 Start(a.mPoint); + CVec3 Stop; + + for (int curSeg=1; (curSeginuse) + { + continue; + } + + // Is It An NPC or Vehicle? + //-------------------------------------- + if (ent->NPC || (ent->NPC_type && Q_stricmp(ent->NPC_type, "atst")==0)) + { + NPCs.set_bit(curEnt); + ent->lastMoveTime = ent->contents; + ent->contents = 0; + gi.linkentity(ent); + } + + + + // Is This Ent "Start Off" or "Start Open"? + //------------------------------------------ + if (!(ent->spawnflags&1)) + { + continue; + } + + + // Is It A Door? Then Close It + //-------------------------------------- + if (!Q_stricmp("func_door", ent->classname)) + { + Doors.set_bit(curEnt); + VectorCopy(ent->pos2, ent->currentOrigin); + gi.linkentity(ent); + } + + // Is It A Wall? Then Turn It On + //-------------------------------------- + else if (!Q_stricmp("func_wall", ent->classname) || !Q_stricmp("func_usable", ent->classname)) + { + Walls.set_bit(curEnt); + ent->contents = ent->spawnContents; + gi.linkentity(ent); + } + } + } + + + // PHASE I: TRIANGULATE ALL NODES IN THE GRAPH + //============================================= +/* TGraphTriang *Triang = new TGraphTriang(mGraph); + Triang->clear(); + Triang->insertion_hull(); + Triang->delaunay_edge_flip(); + Triang->floor_shape(mUser, 1000.0f); + Triang->alpha_shape(mUser, 1000.0f); + Triang->finish(mUser); + delete Triang; +*/ + mCells.fill_cells_nodes(NAV::CELL_RANGE); + + + + + + + // PHASE II: SCAN THROUGH EXISTING NODES AND GENERATE EDGES TO OTHER NODES WAY POINTS + //==================================================================================== + CWayNode* at; + int atHandle; + const char* atNameStr; + int tgtNum; + int tgtHandle; + hstring tgtName; + const char* tgtNameStr; + + CWayEdge atToTgt; + CVec3 atFloor; + CVec3 atRoof; + bool atOnFloor; + + + TNameToNodeMap::iterator nameFinder; + TGraph::TNodes::iterator nodeIter; + TGraph::TEdges::iterator edgeIter; + + ratl::ratl_compare closestNbrs[MIN_WAY_NEIGHBORS]; + + // Drop To Floor And Mark Floating + //--------------------------------- + for (nodeIter=mGraph.nodes_begin(); nodeIter!=mGraph.nodes_end(); nodeIter++) + { + at = &(*nodeIter); + atRoof = at->mPoint; + atFloor = at->mPoint; + if (at->mFlags.get_bit(CWayNode::EWayNodeFlags::WN_DROPTOFLOOR)) + { + atFloor[2] -= MAX_EDGE_FLOOR_DIST; + } + atFloor[2] -= (MAX_EDGE_FLOOR_DIST * 1.5f); + atOnFloor = !ViewTrace(atRoof, atFloor); + if (at->mFlags.get_bit(CWayNode::EWayNodeFlags::WN_DROPTOFLOOR)) + { + at->mPoint = mViewTrace.endpos; + at->mPoint[2] += 5.0f; + } + else if (!atOnFloor && (at->mType==PT_WAYNODE || at->mType==PT_GOALNODE)) + { + at->mFlags.set_bit(CWayNode::EWayNodeFlags::WN_FLOATING); + } + } + + + for (nodeIter=mGraph.nodes_begin(); nodeIter!=mGraph.nodes_end(); nodeIter++) + { + nodeIter->mPoint.ToStr(mLocStringA); + + at = &(*nodeIter); + atHandle = (nodeIter.index()); + atNameStr = (at->mName.empty())?(mLocStringA):(at->mName.c_str()); + + // Connect To Hand Designed Targets + //---------------------------------- + for (tgtNum=0; tgtNummTargets[tgtNum]; + if (!tgtName || tgtName.empty()) + { + continue; + } + tgtNameStr = tgtName.c_str(); + + // Clear The Name In The Array, So Save Is Not Corrupted + //------------------------------------------------------- + at->mTargets[tgtNum] = 0; + + + // Try To Find The Node This Target Name Refers To + //------------------------------------------------- + nameFinder = mNodeNames.find(tgtName); + if (nameFinder==mNodeNames.end()) + { +#ifndef FINAL_BUILD + gi.Printf(S_COLOR_YELLOW"WARNING: nav unable to locate target (%s) from node (%s)\n", tgtNameStr, atNameStr); +#endif + continue; + } + + // For Each One + //-------------- + for (int tgtNameIndex=0; tgtNameIndex<(*nameFinder).size(); tgtNameIndex++) + { + // Connect The Two Nodes In The Graph + //------------------------------------ + tgtHandle = (*nameFinder)[tgtNameIndex]; + + // Only If The Target Is NOT The Same As The Source + //-------------------------------------------------- + if (atHandle!=tgtHandle) + { + int edge = mGraph.get_edge_across(atHandle, tgtHandle); + + // If The Edge Already Exists, Just Make Sure To Add Any Flags + //------------------------------------------------------------- + if (edge) + { + // If It Is The Jump Edge (Last Target), Mark It + //----------------------------------------------- + if (tgtNum == (NAV::NUM_TARGETS-1)) + { + mGraph.get_edge(edge).mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_JUMPING); + } + mGraph.get_edge(edge).mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_DESIGNERPLACED); + continue; + } + + // Setup The Edge + //---------------- + mUser.setup_edge(atToTgt, atHandle, tgtHandle, false, mGraph.get_node(atHandle), mGraph.get_node(tgtHandle), false); + atToTgt.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_DESIGNERPLACED); + + // If It Is The Jump Edge (Last Target), Mark It + //----------------------------------------------- + if (tgtNum == (NAV::NUM_TARGETS-1)) + { + atToTgt.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_JUMPING); + } + + // Now Tell The Graph Which Edge Index To Store Between The Two Points + //--------------------------------------------------------------------- + mGraph.connect_node(atToTgt, atHandle, tgtHandle); + } + } + } + + + // If It Is A Combat Or Goal Nav, Try To "Auto Connect" To A Few Nearby Way Points + //--------------------------------------------------------------------------------- + if (!at->mFlags.get_bit(CWayNode::EWayNodeFlags::WN_NOAUTOCONNECT) && + (at->mType==NAV::PT_COMBATNODE || at->mType==NAV::PT_GOALNODE)) + { + // Get The List Of Nodes For This Cell Of The Map + //------------------------------------------------ + TGraphCells::SCell& Cell = mCells.get_cell(at->mPoint[0], at->mPoint[1]); + + // Create A Closest Neighbors Array And Initialize It Empty + //---------------------------------------------------------- + for (int i=0; imPoint[2])>NAV::MAX_EDGE_FLOOR_DIST) + { + continue; + } + + // Ignore Ones That Are Too Far + //------------------------------ + float cost = node.mPoint.Dist(at->mPoint); + if (cost>NAV::MAX_EDGE_AUTO_LEN) + { + continue; + } + + // Connecting To Another Combat Point Or Goal Node It Must Be Half The Max Connect Distance + //------------------------------------------------------------------------------------------ + if (node.mType==NAV::PT_COMBATNODE || node.mType==NAV::PT_GOALNODE) + { + nonWPCount++; + if (nonWPCount>NAV::MAX_NONWP_NEIGHBORS) + { + continue; + } + + if (cost>(NAV::MAX_EDGE_AUTO_LEN/2.0f)) + { + continue; + } + } + + // If We Already Have Points, Ignore Anything Farther Than The Current Farthest + //------------------------------------------------------------------------------ + if (closestNbrs[highestCost].mHandle!=0 && closestNbrs[highestCost].mCostmPoint.v))) + { + continue; + } + + + // Now Record This Point Over The One With The Highest Cost + //---------------------------------------------------------- + closestNbrs[highestCost].mHandle = tgtHandle; + closestNbrs[highestCost].mCost = node.mPoint.Dist(at->mPoint); + + // Find The New Highest Cost + //--------------------------- + for (int i=0; iclosestNbrs[highestCost].mCost) + { + highestCost = i; + } + } + } + + // Now Connect All The Closest Neighbors + //--------------------------------------- + for (int i=0; i *ToBeRemoved = new ratl::vector_vs; + for (edgeIter=mGraph.edges_begin(); edgeIter!=mGraph.edges_end(); edgeIter++) + { + CWayEdge& at = (*edgeIter); + int atHandle = edgeIter.index(); + CWayNode& a = mGraph.get_node(at.mNodeA); + CWayNode& b = mGraph.get_node(at.mNodeB); + + mGraph.get_node(at.mNodeA).mPoint.ToStr(mLocStringA); + mGraph.get_node(at.mNodeB).mPoint.ToStr(mLocStringB); + + const char* aName = (a.mName.empty())?(mLocStringA):(a.mName.c_str()); + const char* bName = (b.mName.empty())?(mLocStringB):(b.mName.c_str()); + + + if (at.mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_JUMPING)) + { + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_SIZE_LARGE); + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_CANBEINVAL); + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_DESIGNERPLACED); + continue; + } + + + // Cycle through the different sizes, starting with the largest + //-------------------------------------------------------------- + bool CanGo = false; + bool IsDebugEdge = + (g_nav1->string[0] && g_nav2->string[0] && + (!stricmp(*(a.mName), g_nav1->string) || !stricmp(*(b.mName), g_nav1->string)) && + (!stricmp(*(a.mName), g_nav2->string) || !stricmp(*(b.mName), g_nav2->string))); + + // For debugging a connection between two known points: + //------------------------------------------------------ + if (IsDebugEdge) + { + gi.Printf("===============================\n"); + gi.Printf("Nav(%s)<->(%s): DEBUGGING START\n", aName, bName); + assert(0); // Break Here + } + + // Try Large + //----------- + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_SIZE_LARGE); + if (IsDebugEdge) + { + gi.Printf("Nav(%s)<->(%s): Attempting Size Large...\n", aName, bName); + } + + // Try Medium + //------------ + CanGo = TestEdge( at.mNodeA, at.mNodeB, IsDebugEdge ); + if (!CanGo) + { + at.mFlags.clear_bit(CWayEdge::EWayEdgeFlags::WE_SIZE_LARGE); + at.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_SIZE_MEDIUM); + if (IsDebugEdge) + { + gi.Printf("Nav(%s)<->(%s): Attempting Size Medium...\n", aName, bName); + } + CanGo = TestEdge( at.mNodeA, at.mNodeB, IsDebugEdge ); + } + + // If This Edge Can't Go At Any Size, Dump It + //-------------------------------------------- + if (!CanGo) + { + ToBeRemoved->push_back(atHandle); + if (IsDebugEdge) + { + CVec3 ContactNormal(mMoveTrace.plane.normal); + CVec3 ContactPoint( mMoveTrace.endpos); + + char cpointstr[256] = {0}; + char cnormstr[256] = {0}; + + ContactNormal.ToStr(cnormstr); + ContactPoint.ToStr(cpointstr); + + gi.Printf("Nav(%s)<->(%s): FAILED, NO SMALLER SIZE POSSIBLE\n", aName, bName); + gi.Printf("Nav(%s)<->(%s): The last trace hit:\n", aName, bName); + gi.Printf("Nav(%s)<->(%s): at %s,\n", aName, bName, cpointstr); + gi.Printf("Nav(%s)<->(%s): normal %s\n", aName, bName, cnormstr); + if (mMoveTrace.entityNum!=ENTITYNUM_WORLD) + { + gentity_t* ent = &g_entities[mMoveTrace.entityNum]; + gi.Printf("Nav(%s)<->(%s): on entity Type (%s), TargetName (%s)\n", aName, bName, ent->classname, ent->targetname); + } + if ((mMoveTrace.contents)&CONTENTS_MONSTERCLIP) + { + gi.Printf("Nav(%s)<->(%s): with contents BLOCKNPC\n", aName, bName); + } + else if ((mMoveTrace.contents)&CONTENTS_BOTCLIP) + { + gi.Printf("Nav(%s)<->(%s): with contents DONOTENTER\n", aName, bName); + } + else if ((mMoveTrace.contents)&CONTENTS_SOLID) + { + gi.Printf("Nav(%s)<->(%s): with contents SOLID\n", aName, bName); + } + else if ((mMoveTrace.contents)&CONTENTS_WATER) + { + gi.Printf("Nav(%s)<->(%s): with contents WATER\n", aName, bName); + } + } + } + else + { + if (IsDebugEdge) + { + gi.Printf("Nav(%s)<->(%s): Success!\n", aName, bName); + } + } + + if (IsDebugEdge) + { + gi.Printf("Nav(%s)<->(%s): DEBUGGING END\n", aName, bName); + gi.Printf("===============================\n"); + } + } + + // Now Go Ahead And Remove Dead Edges + //------------------------------------ + for (int RemIndex=0;RemIndexsize(); RemIndex++) + { + CWayEdge& at = mGraph.get_edge((*ToBeRemoved)[RemIndex]); + if (at.mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_DESIGNERPLACED)) + { + hstring aHstr = mGraph.get_node(at.mNodeA).mName; + hstring bHstr = mGraph.get_node(at.mNodeB).mName; + + mGraph.get_node(at.mNodeA).mPoint.ToStr(mLocStringA); + mGraph.get_node(at.mNodeB).mPoint.ToStr(mLocStringB); + +#ifndef FINAL_BUILD + gi.Printf( S_COLOR_RED"ERROR: Nav connect failed: %s@%s <-> %s@%s\n", aHstr.c_str(), mLocStringA, bHstr.c_str(), mLocStringB); +#endif + delayedShutDown = level.time + 100; + } + mGraph.remove_edge(at.mNodeA, at.mNodeB); + } + + delete ToBeRemoved; + + // Detect Point Islands + //---------------------- + for (nodeIter=mGraph.nodes_begin(); nodeIter!=mGraph.nodes_end(); nodeIter++) + { + at = &(*nodeIter); + atHandle = nodeIter.index(); + + if (!mGraph.node_has_neighbors(atHandle)) + { + at->mPoint.ToStr(mLocStringA); + + at->mFlags.set_bit(CWayNode::EWayNodeFlags::WN_ISLAND); + mIslandCount++; + if (at->mType==NAV::PT_COMBATNODE) + { +#ifndef FINAL_BUILD + gi.Printf( S_COLOR_RED"ERROR: Combat Point %s@%s Is Not Connected To Anything\n", at->mName.c_str(), mLocStringA); + delayedShutDown = level.time + 100; +#endif + } + if (at->mType==NAV::PT_GOALNODE) + { + // Try To Trace Down, If We Don't Hit Any Ground, Assume This Is An "Air Point" + //------------------------------------------------------------------------------ + CVec3 Down(at->mPoint); + Down[2] -= 100; + if (!ViewTrace(at->mPoint, Down)) + { +#ifndef FINAL_BUILD + gi.Printf( S_COLOR_RED"ERROR: Nav Goal %s@%s Is Not Connected To Anything\n", at->mName.c_str(), mLocStringA); + delayedShutDown = level.time + 100; +#endif + } + } + } + } + + + + // PHASE IV: SCAN EDGES FOR REGIONS + //================================== + mRegion.clear(); + + mIslandRegion = mRegion.reserve(); +// mAirRegion = mRegion.reserve(); + for (nodeIter=mGraph.nodes_begin(); nodeIter!=mGraph.nodes_end(); nodeIter++) + { + at = &(*nodeIter); + if (at->mFlags.get_bit(CWayNode::EWayNodeFlags::WN_ISLAND)) + { + mRegion.assign_region(nodeIter.index(), mIslandRegion); + } + // else if (at->mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING)) + // { + // mRegion.assign_region(nodeIter.index(), mAirRegion); + // } + } + if (!mRegion.find_regions(mUser)) + { +#ifndef FINAL_BUILD + gi.Printf( S_COLOR_RED"ERROR: Too Many Regions!\n"); + delayedShutDown = level.time + 100; +#endif + } + if (!mRegion.find_region_edges()) + { +#ifndef FINAL_BUILD + gi.Printf( S_COLOR_RED"ERROR: Too Many Region Edges!\n"); + delayedShutDown = level.time + 100; +#endif + } + + + // PHASE V: SCAN NODES AND FILL CELLS + //=================================== + mCells.fill_cells_edges(NAV::CELL_RANGE); + + + // PHASE VI: SCAN ALL ENTITIES AND RE OPEN / TURN THEM OFF + //========================================================= + if (CHECK_START_OPEN) + { + for (int curEnt=0; curEntinuse) + { + continue; + } + + + // Is It A Door? + //-------------------------------------- + if (Doors.get_bit(curEnt)) + { + VectorCopy(ent->pos1, ent->currentOrigin); + gi.linkentity(ent); + } + + // Is It A Wall? + //-------------------------------------- + else if (Walls.get_bit(curEnt)) + { + ent->contents = 0; + gi.linkentity(ent); + } + + // Is It An NPC? + //-------------------------------------- + else if (NPCs.get_bit(curEnt)) + { + ent->contents = ent->lastMoveTime; + ent->lastMoveTime = 0; + gi.linkentity(ent); + } + + + } + } + mConnectTraceCount = mMoveTraceCount; + mMoveTraceCount = 0; + + mConnectTime = gi.Milliseconds() - mConnectTime; + + + // PHASE VI: SAVE TO FILE + //======================== +#ifndef _XBOX + if (SAVE_LOAD) + { + hfile navFile(va("maps/%s.navNEW")); + if (!navFile.open_write(NAV_VERSION, checksum)) + { + return false; + } + navFile.save(&mGraph, sizeof(mGraph)); + navFile.save(&mRegion, sizeof(mRegion)); + navFile.save(&mCells, sizeof(mCells)); + navFile.close(); + } +#endif + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void NAV::DecayDangerSenses() +{ + float PerFrameDecay = (50.0f) / (float)(NAV::MAX_ALERT_TIME); + for (int entIndex=0; entIndexs.number]; + alertEvent_t& ae = level.alertEvents[alertEventIndex]; + + if (ae.radius<=0.0f) + { + return; + } + + // DEBUG GRAPHICS + //===================================================== + if (NAVDEBUG_showRadius) + { + CG_DrawRadius(ae.position, ae.radius, NODE_GOAL); + } + //===================================================== + + + CVec3 DangerPoint(ae.position); + + // Look Through The Nearby Edges And Record Any That Are Affected + //---------------------------------------------------------------- + TGraphCells::TCellNodes& cellEdges = mCells.get_cell(DangerPoint[0], DangerPoint[1]).mEdges; + for (int cellEdgeIndex=0; cellEdgeIndex0.0f) + { + + // Record The Square, So That Danger Drops Off Quadradically Rather Than Linearly + //-------------------------------------------------------------------------------- + edgeDanger *= edgeDanger; + + + // Now Find The Index We Will "Replace" With This New Information + //---------------------------------------------------------------- + int replaceIndex = -1; + for (int alIndex=0; alIndexwayedge = 0; + + int EdgeHandle; + int EntNum = ent->s.number; + + TEntEdgeMap::iterator finder = mEntEdgeMap.find(EntNum); + if (finder!=mEntEdgeMap.end()) + { + for (int i=0; isize(); i++) + { + EdgeHandle = (*finder)[i]; + if (EdgeHandle!=0) + { + CWayEdge& edge = mGraph.get_edge(EdgeHandle); + edge.mFlags.set_bit(CWayEdge::EWayEdgeFlags::WE_VALID); + edge.mEntityNum = ENTITYNUM_NONE; + edge.mOwnerNum = ENTITYNUM_NONE; + } + } + mEntEdgeMap.erase(EntNum); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void NAV::SpawnedPoint(gentity_t* ent, NAV::EPointType type) +{ + if (mGraph.size_nodes()>=NUM_NODES) + { +#ifndef FINAL_BUILD + gi.Printf( "SpawnedPoint: Max Nav points reached (%d)!\n",NUM_NODES ); +#endif + return; + } + + CVec3 Mins; + CVec3 Maxs; + + + Mins[0] = Mins[1] = (SC_MEDIUM_RADIUS) * -1.0f; + Maxs[0] = Maxs[1] = (SC_MEDIUM_RADIUS); + Mins[2] = 0.0f; + Maxs[2] = SC_MEDIUM_HEIGHT; + + + CVec3 Start(ent->currentOrigin); + CVec3 Stop(ent->currentOrigin); + Stop[2] += 5.0f; + + Start.ToStr(mLocStringA); + const char* pointName = (ent->targetname && ent->targetname[0])?(ent->targetname):"?"; + + if (CHECK_START_SOLID) + { + // Try It + //-------- + if (!MoveTrace(Start, Stop, Mins, Maxs, 0, true, false)) + { + assert("ERROR: Nav in solid!"==0); + gi.Printf( S_COLOR_RED"ERROR: Nav(%d) in solid: %s@%s\n", type, pointName, mLocStringA); + delayedShutDown = level.time + 100; + return; + } + } + + CWayNode node; + + node.mPoint = ent->currentOrigin; + node.mRadius = ent->radius; + node.mType = type; + node.mFlags.clear(); + if (type==NAV::PT_WAYNODE && (ent->spawnflags & 2)) + { + node.mFlags.set_bit(CWayNode::EWayNodeFlags::WN_DROPTOFLOOR); + } + if (ent->spawnflags & 4) + { + node.mFlags.set_bit(CWayNode::EWayNodeFlags::WN_NOAUTOCONNECT); + } + + // TO AVOID PROBLEMS WITH THE TRIANGULATION, WE MOVE THE POINTS AROUND JUST A BIT + //================================================================================ + //node.mPoint += CVec3(Q_flrand(-RANDOM_PERMUTE, RANDOM_PERMUTE), Q_flrand(-RANDOM_PERMUTE, RANDOM_PERMUTE), 0.0f); + //================================================================================ + + // Validate That The New Location Is Still Safe + //---------------------------------------------- + if (false && CHECK_START_SOLID) + { + Start = (node.mPoint); + Stop = (node.mPoint); + Stop[2] += 5.0f; + + // Try It Again + //-------------- + if (!MoveTrace(Start, Stop, Mins, Maxs, 0, true, false)) + { + gi.Printf( S_COLOR_YELLOW"WARNING: Nav Moved To Solid, Resetting: (%s)\n", pointName); + assert("WARNING: Nav Moved To Solid, Resetting!"==0); + node.mPoint = ent->currentOrigin; + } + } + + node.mTargets[0] = ent->target; + node.mTargets[1] = ent->target2; + node.mTargets[2] = ent->target3; + node.mTargets[3] = ent->target4; + node.mTargets[4] = ent->targetJump; + node.mName = ent->targetname; + + int NodeHandle = mGraph.insert_node(node); + + ent->waypoint = NodeHandle; + + mCells.expand_bounds(NodeHandle); + + if (!node.mName.empty()) + { + TNameToNodeMap::iterator nameFinder = mNodeNames.find(node.mName); + + if (nameFinder==mNodeNames.end()) + { + TNamedNodeList list; + list.clear(); + list.push_back(NodeHandle); + mNodeNames.insert(node.mName, list); + } + else + { + (*nameFinder).push_back(NodeHandle); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +NAV::TNodeHandle NAV::GetNearestNode(gentity_t* ent, bool forceRecalcNow, NAV::TNodeHandle goal) +{ + if (!ent) + { + return 0; + } + + if (ent->waypoint==WAYPOINT_NONE || forceRecalcNow || (level.time>ent->noWaypointTime)) + { + if (ent->waypoint) + { + ent->lastWaypoint = ent->waypoint; + } + ent->waypoint = + GetNearestNode( + ent->currentOrigin, + ent->waypoint, + goal, + ent->s.number, + (ent->client && ent->client->moveType==MT_FLYSWIM)); + ent->noWaypointTime = level.time + 1000; // Don't Erase This Result For 5 Seconds + } + + return ent->waypoint; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +NAV::TNodeHandle NAV::GetNearestNode(const vec3_t& position, NAV::TNodeHandle previous, NAV::TNodeHandle goal, int ignoreEnt, bool allowZOffset) +{ + if (mGraph.size_edges()>0) + { + // Get The List Of Nodes For This Cell Of The Map + //------------------------------------------------ + TGraphCells::SCell& Cell = mCells.get_cell(position[0], position[1]); + if (Cell.mNodes.empty() && Cell.mEdges.empty()) + { +#ifndef FINAL_BUILD + if (g_developer->value) + { + gi.Printf("WARNING: Failure To Find A Node Here, Examine Cell Layout\n"); + } +#endif + return WAYPOINT_NONE; + } + + CVec3 Pos(position); + SNodeSort NodeSort; + + // PHASE I - TEST NAV POINTS + //=========================== + { + mNearestNavSort.clear(); + for (int i=0; i(VIEW_RANGE / 4)) + { + continue; + } + if (ZOff>30.0f) + { + NodeSort.mDistance += (ZOff*ZOff); + } + } + + // Ignore Points That Are Too Far + //-------------------------------- + if (NodeSort.mDistance>(NAV::VIEW_RANGE*NAV::VIEW_RANGE)) + { + continue; + } + + // Bias Points That Are Not Connected To Anything + //------------------------------------------------ + if (node.mFlags.get_bit(CWayNode::EWayNodeFlags::WN_ISLAND)) + { + NodeSort.mDistance *= 3.0f; + } + + if (previous && previous!=NodeSort.mHandle && !NAV::InSameRegion(previous, NodeSort.mHandle)) + { + NodeSort.mDistance += (100.0f*100.0f); + } + if (previous>0 && previous!=NodeSort.mHandle && !mGraph.get_edge_across(previous, NodeSort.mHandle)) + { + NodeSort.mDistance += (200.0f*200.0f); + } + if (goal && goal!=NodeSort.mHandle && !NAV::InSameRegion(goal, NodeSort.mHandle)) + { + NodeSort.mDistance += (300.0f*300.0f); + } + + + // Bias Combat And Goal Nodes Some + //--------------------------------- + // if (node.mType==NAV::PT_COMBATNODE || node.mType==NAV::PT_GOALNODE) + // { + // NodeSort.mDistance += 50.0f; + // } + + mNearestNavSort.push_back(NodeSort); + } + + // Sort Them By Distance + //----------------------- + mNearestNavSort.sort(); + + // Now , Run Through Each Of The Sorted Nodes, Starting With The Closest One + //--------------------------------------------------------------------------- + for (int j=0; j(VIEW_RANGE / 4)) + { + continue; + } + if (ZOff>30.0f) + { + NodeSort.mDistance += (ZOff*ZOff); + } + } + + // Ignore Points That Are Too Far + //-------------------------------- + if (NodeSort.mDistance>(NAV::VIEW_RANGE*NAV::VIEW_RANGE)) + { + continue; + } + mNearestNavSort.push_back(NodeSort); + } + + // Sort Them By Distance + //----------------------- + mNearestNavSort.sort(); + + // Now , Run Through Each Of The Sorted Edges, Starting With The Closest One + //--------------------------------------------------------------------------- + for (int j=0; j0.0f && PointOnEdgeRange<1.0f) + { + // Otherwise, We Need To Trace To It + //----------------------------------- + if (ViewNavTrace(Pos, PointOnEdge)) + { + return (mNearestNavSort[j].mHandle * -1); // "Edges" have negative IDs + } + } + } + } + } + return WAYPOINT_NONE; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +NAV::TNodeHandle NAV::ChooseRandomNeighbor(NAV::TNodeHandle NodeHandle) +{ + if (NodeHandle!=WAYPOINT_NONE && NodeHandle>0) + { + TGraph::TNodeNeighbors& neighbors = mGraph.get_node_neighbors(NodeHandle); + if (neighbors.size()>0) + { + return (neighbors[Q_irand(0, neighbors.size()-1)].mNode); + } + } + return WAYPOINT_NONE; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +NAV::TNodeHandle NAV::ChooseRandomNeighbor(TNodeHandle NodeHandle, const vec3_t& position, float maxDistance) +{ + if (NodeHandle!=WAYPOINT_NONE && NodeHandle>0) + { + CVec3 Pos(position); + + TGraph::TNodeNeighbors& neighbors = mGraph.get_node_neighbors(NodeHandle); + + // Remove All Neighbors That Are Too Far + //--------------------------------------- + for (int i=0; imaxDistance) + { + neighbors.erase_swap(i); + i--; + if (neighbors.empty()) + { + return WAYPOINT_NONE; + } + } + } + + // Now, Randomly Pick From What Is Left + //-------------------------------------- + if (neighbors.size()>0) + { + return (neighbors[Q_irand(0, neighbors.size()-1)].mNode); + } + } + return WAYPOINT_NONE; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +NAV::TNodeHandle NAV::ChooseClosestNeighbor(NAV::TNodeHandle NodeHandle, const vec3_t& position) +{ + if (NodeHandle!=WAYPOINT_NONE && NodeHandle>0) + { + CVec3 pos(position); + TGraph::TNodeNeighbors& neighbors = mGraph.get_node_neighbors(NodeHandle); + + NAV::TNodeHandle Cur = WAYPOINT_NONE; + float CurDist = 0.0f; + NAV::TNodeHandle Best = NodeHandle; + float BestDist = mGraph.get_node(Cur).mPoint.Dist2(pos); + + for (int i=0; i0) + { + CVec3 pos(position); + TGraph::TNodeNeighbors& neighbors = mGraph.get_node_neighbors(NodeHandle); + + NAV::TNodeHandle Cur = WAYPOINT_NONE; + float CurDist = 0.0f; + NAV::TNodeHandle Best = NodeHandle; + float BestDist = mGraph.get_node(Cur).mPoint.Dist2(pos); + + for (int i=0; iCurDist) + { + Best = Cur; + BestDist = CurDist; + } + } + return Best; + } + return WAYPOINT_NONE; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +NAV::TNodeHandle NAV::ChooseFarthestNeighbor(gentity_t* actor, const vec3_t& target, float maxSafeDot) +{ + CVec3 actorPos(actor->currentOrigin); + CVec3 targetPos(target); + CVec3 actorToTgt(targetPos - actorPos); + float actorToTgtDist = actorToTgt.Norm(); + + NAV::TNodeHandle cur = GetNearestNode(actor); + + // If Not Anywhere, Give Up + //-------------------------- + if (cur==WAYPOINT_NONE) + { + return WAYPOINT_NONE; + } + + // If On An Edge, Pick The Safest Of The Two Points + //-------------------------------------------------- + if (cur<0) + { + CWayEdge& edge = mGraph.get_edge(abs(cur)); + if (edge.PointA().Dist2(targetPos)>edge.PointA().Dist2(actorPos)) + { + return edge.mNodeA; + } + return edge.mNodeB; + } + + CVec3 curPos(mGraph.get_node(cur).mPoint); + CVec3 curToTgt(targetPos - curPos); + float curDist = curToTgt.SafeNorm(); +// float curDot = curToTgt.Dot(actorToTgt); + NAV::TNodeHandle best = WAYPOINT_NONE; + float bestDist = 0.0f; + TGraph::TNodeNeighbors& neighbors = mGraph.get_node_neighbors(cur); + + + // If The Actor's Current Point Is Valid, Initialize The Best One To That + //------------------------------------------------------------------------ + if (curDist>actorToTgtDist && actorPos.Dist(curPos)>300.0f)//curDotbestDist && curDist>actorToTgtDist)//curDots.number]; + if (pathUserNum==NULL_PATH_USER_INDEX) + { + if (mPathUsers.full()) + { + assert("NAV: No more unused path users, possibly change MAX_PATH_USERS"==0); + return false; + } + + pathUserNum = mPathUsers.alloc(); + mPathUsers[pathUserNum].mEnd = WAYPOINT_NONE; + mPathUsers[pathUserNum].mSuccess = false; + mPathUsers[pathUserNum].mLastAStarTime = 0; + mPathUserIndex[actor->s.number] = pathUserNum; + } + SPathUser& puser = mPathUsers[pathUserNum]; + puser.mLastUseTime = level.time; + + + // Now, Check To See If He Already Has Found A Path To This Target + //----------------------------------------------------------------- + if (puser.mEnd==target && level.time0 && !mRegion.has_valid_edge(mSearch.mStart, mSearch.mEnd, mUser)) + { + puser.mSuccess = false; + return puser.mSuccess; + } + + + + // Now, Run A* + //------------- + if (actor->enemy && actor->enemy->client) + { + if (actor->enemy->client->ps.weapon==WP_SABER) + { + mUser.SetDangerSpot(actor->enemy->currentOrigin, 200.0f); + } + else if ( + actor->enemy->client->NPC_class==CLASS_RANCOR || + actor->enemy->client->NPC_class==CLASS_WAMPA) + { + mUser.SetDangerSpot(actor->enemy->currentOrigin, 400.0f); + } + } + mGraph.astar(mSearch, mUser); + mUser.ClearDangerSpot(); + + puser.mLastAStarTime = level.time + Q_irand(3000, 6000); + puser.mSuccess = mSearch.success(); + if (!puser.mSuccess) + { + return puser.mSuccess; + } + + + // Grab A Couple "Current Conditions" + //------------------------------------ + CVec3 At(actor->currentOrigin); + float AtTime = level.time; + float AtSpeed = actor->NPC->stats.runSpeed; + if (!(actor->NPC->scriptFlags&SCF_RUNNING) && + ((actor->NPC->scriptFlags&SCF_WALKING) || + (actor->NPC->aiFlags&NPCAI_WALKING) || + (ucmd.buttons&BUTTON_WALKING) + )) + { + AtSpeed = actor->NPC->stats.walkSpeed; + } + + AtSpeed *= 0.001f; // Convert units/sec to units/millisec for comparison against level.time + AtSpeed *= 0.25; // Cut the speed in half to account for accel & decel & some slop + + + + // Get The Size Of This Actor + //---------------------------- + float minRadius = Min(actor->mins[0], actor->mins[1]); + float maxRadius = Max(actor->maxs[0], actor->maxs[1]); + float radius = Max(fabsf(minRadius), maxRadius); + + if (radius>20.0f) + { + radius = 20.0f; + } + + + // Copy The Search Results Into The Path + //--------------------------------------- + { + SPathPoint PPoint; + puser.mPath.clear(); + for (mSearch.path_begin(); !mSearch.path_end() && !puser.mPath.full(); mSearch.path_inc()) + { + if (puser.mPath.full()) + { + // NAV_TODO: If the path is longer than the max length, we want to store the first valid ones, instead of returning false + assert("This Is A Test To See If We Hit This Condition Anymore... It Should Be Handled Properly"==0); + mPathUsers.free(pathUserNum); + mPathUserIndex[actor->s.number] = NULL_PATH_USER_INDEX; + return false; + } + + PPoint.mNode = mSearch.path_at(); + PPoint.mPoint = mGraph.get_node(PPoint.mNode).mPoint; + PPoint.mSpeed = AtSpeed; + PPoint.mSlowingRadius = 0.0f; + PPoint.mReachedRadius = Max(radius*3.0f, (mGraph.get_node(PPoint.mNode).mRadius * 0.40f)); + if (mGraph.get_node(PPoint.mNode).mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING)) + { + PPoint.mReachedRadius = 20.0f; + } + PPoint.mReachedRadius *= PPoint.mReachedRadius; // squared, for faster checks later + PPoint.mDist = 0.0f; + PPoint.mETA = 0.0f; + + puser.mPath.push_back(PPoint); + } + assert(puser.mPath.size()>0); + + + // Last Point On The Path Always Gets A Slowing Radius + //----------------------------------------------------- + puser.mPath[0].mSlowingRadius = Max(70.0f, (mGraph.get_node(PPoint.mNode).mRadius * 0.75f)); + puser.mPath[0].mReachedRadius = Max(radius, (mGraph.get_node(PPoint.mNode).mRadius * 0.15f)); + puser.mPath[0].mReachedRadius *= puser.mPath[0].mReachedRadius; // squared, for faster checks later + } + + int numEdges = (puser.mPath.size()-1); + + + // Trim Out Backtracking Edges + //----------------------------- + for (int edge=0; edge0.1f && AtOnEdgeScale<0.9f) + { + if (AtDistToEdge<(radius) || (AtDistToEdge<(radius*20.0f) && MoveTrace(At, AtOnEdge, actor->mins, actor->maxs, actor->s.number, true, true, false))) + { + puser.mPath.resize(edge+2); // +2 because every edge needs at least 2 points + puser.mPath[edge+1].mPoint = AtOnEdge; + break; + } + } + } + + + + // For All Points On The Path, Compute ETA, And Check For Sharp Corners + //---------------------------------------------------------------------- + CVec3 AtToNext; + CVec3 NextToBeyond; + float NextToBeyondDistance; + float NextToBeyondDot; + + for (int i=puser.mPath.size()-1; i>-1; i--) + { + SPathPoint& PPoint = puser.mPath[i]; // For Debugging And A Tad Speed Improvement, Get A Ref Directly + + AtToNext = (PPoint.mPoint - At); + if (fabsf(AtToNext[2])>Z_CULL_OFFSET) + { + AtToNext[2] = 0.0f; + } + + PPoint.mDist = AtToNext.Norm(); // Get The Distance And Norm The Direction + PPoint.mETA = (PPoint.mDist / PPoint.mSpeed); // Estimate Our Eta By Distance/Speed + PPoint.mETA += AtTime; + + + // Check To See If This Is The Apex Of A Sharp Turn + //-------------------------------------------------- + if (i!=0 && //is there a next point? + !mGraph.get_node(PPoint.mNode).mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING) + ) + { + NextToBeyond = (puser.mPath[i-1].mPoint - PPoint.mPoint); + if (fabsf(NextToBeyond[2])>Z_CULL_OFFSET) + { + NextToBeyond[2] = 0.0f; + } + NextToBeyondDistance = NextToBeyond.Norm(); + NextToBeyondDot = NextToBeyond.Dot(AtToNext); + + if ((NextToBeyondDistance>150.0f && PPoint.mDist>150.0f && NextToBeyondDot<0.64f) || + (NextToBeyondDistance>30.0f && NextToBeyondDot<0.5f)) + { + PPoint.mSlowingRadius = Max(40.0f, mGraph.get_node(PPoint.mNode).mRadius); // Force A Stop Here + } + } + + // Update Our Time And Location For The Next Point + //------------------------------------------------- + AtTime = PPoint.mETA; + At = PPoint.mPoint; + } + + // Failed To Find An Acceptibly Safe Path + //---------------------------------------- + if (MaxDangerLevel!=1.0f && NAV::PathDangerLevel(NPC)>MaxDangerLevel) + { + puser.mSuccess = false; + } + + + assert(puser.mPath.size()>0); + return puser.mSuccess; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::SafePathExists(const CVec3& startVec, const CVec3& stopVec, const CVec3& danger, float dangerDistSq) +{ + mUser.ClearActor(); + + + // If Either Start Or End Is Invalid, We Can't Do Any Pathing + //------------------------------------------------------------ + NAV::TNodeHandle target = GetNearestNode(stopVec.v, 0, 0, 0, true); + if (target==WAYPOINT_NONE) + { + return false; + } + + NAV::TNodeHandle start = GetNearestNode(startVec.v, 0, target, 0, true); + if (start==WAYPOINT_NONE) + { + return false; + } + + // Convert Edges To Points + //------------------------ + if (start<0) + { + start = mGraph.get_edge(abs(start)).mNodeA; + } + if (target<0) + { + target = mGraph.get_edge(abs(target)).mNodeA; + } + if (start==target) + { + return true; + } + + + // First Step: Find The Actor And Make Sure He Has A Path User Struct + //-------------------------------------------------------------------- + SPathUser& puser = mPathUserMaster; + puser.mLastUseTime = level.time; + + + // Now, Check To See If He Already Has Found A Path To This Target + //----------------------------------------------------------------- + if (puser.mEnd==target && level.time0 && !mRegion.has_valid_edge(mSearch.mStart, mSearch.mEnd, mUser)) + { + puser.mSuccess = false; + return puser.mSuccess; + } + + // Now, Run A* + //------------- +// mUser.SetDangerSpot(danger, dangerDistSq); + mGraph.astar(mSearch, mUser); +// mUser.ClearDangerSpot(); + + puser.mLastAStarTime = level.time + Q_irand(3000, 6000);; + puser.mSuccess = mSearch.success(); + if (!puser.mSuccess) + { + return puser.mSuccess; + } + + + // Failed To Find An Acceptibly Safe Path + //---------------------------------------- + CVec3 Prev(stopVec); + CVec3 Next; + for (mSearch.path_begin(); !mSearch.path_end(); mSearch.path_inc()) + { + Next = mGraph.get_node(mSearch.path_at()).mPoint; + if (dangerDistSq > danger.DistToLine2(Next, Prev)) + { + puser.mSuccess = false; + break; + } + Prev = Next; + } + if (puser.mSuccess) + { + Next = startVec; + if (dangerDistSq > danger.DistToLine2(Prev, Next)) + { + puser.mSuccess = false; + } + } + + return puser.mSuccess; +} + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +float NAV::EstimateCostToGoal(const vec3_t& position, TNodeHandle Goal) +{ + if (Goal!=0 && Goal!=WAYPOINT_NONE) + { + return (Distance(position, GetNodePosition(Goal))); + } + return 0.0f; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +float NAV::EstimateCostToGoal(TNodeHandle Start, TNodeHandle Goal) +{ + mUser.ClearActor(); + if (Goal!=0 && Goal!=WAYPOINT_NONE && Start!=0 && Start!=WAYPOINT_NONE) + { + return (Distance(GetNodePosition(Start), GetNodePosition(Goal))); + } + return 0.0f; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::OnSamePoint(gentity_t* actor, gentity_t* target) +{ + return (GetNearestNode(actor)==GetNearestNode(target)); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::InSameRegion(gentity_t* actor, gentity_t* target) +{ + mUser.ClearActor(); + if (mRegion.size()>0) + { + NAV::TNodeHandle actNode = GetNearestNode(actor); + NAV::TNodeHandle tgtNode = GetNearestNode(target); + if (actNode==WAYPOINT_NONE || tgtNode==WAYPOINT_NONE) + { + return false; + } + if (actNode==tgtNode) + { + return true; + } + + if (actNode<0) + { + actNode = mGraph.get_edge(abs(actNode)).mNodeA; + } + if (tgtNode<0) + { + tgtNode = mGraph.get_edge(abs(tgtNode)).mNodeA; + } + + mUser.SetActor(actor); + + return (mRegion.has_valid_edge(actNode, tgtNode, mUser)); + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::InSameRegion(gentity_t* actor, const vec3_t& position) +{ + mUser.ClearActor(); + if (mRegion.size()>0) + { + NAV::TNodeHandle actNode = GetNearestNode(actor); + NAV::TNodeHandle tgtNode = GetNearestNode(position); + + if (actNode==WAYPOINT_NONE || tgtNode==WAYPOINT_NONE) + { + return false; + } + if (actNode==tgtNode) + { + return true; + } + + if (actNode<0) + { + actNode = mGraph.get_edge(abs(actNode)).mNodeA; + } + if (tgtNode<0) + { + tgtNode = mGraph.get_edge(abs(tgtNode)).mNodeA; + } + + mUser.SetActor(actor); + + return (mRegion.has_valid_edge(actNode, tgtNode, mUser)); + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::InSameRegion(NAV::TNodeHandle A, NAV::TNodeHandle B) +{ + if (mRegion.size()>0) + { + NAV::TNodeHandle actNode = A; + NAV::TNodeHandle tgtNode = B; + + if (actNode==WAYPOINT_NONE || tgtNode==WAYPOINT_NONE) + { + return false; + } + if (actNode==tgtNode) + { + return true; + } + + if (actNode<0) + { + actNode = mGraph.get_edge(abs(actNode)).mNodeA; + } + if (tgtNode<0) + { + tgtNode = mGraph.get_edge(abs(tgtNode)).mNodeA; + } + + gentity_t* tempActor = mUser.GetActor(); + mUser.ClearActor(); + bool hasEdge = mRegion.has_valid_edge(actNode, tgtNode, mUser); + if (tempActor) + { + mUser.SetActor(tempActor); + } + return hasEdge; + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::OnNeighboringPoints(TNodeHandle A, TNodeHandle B) +{ + if (A==B) + { + return true; + } + + if (A<=0 || B<=0) + { + return false; + } + int edgeNum = mGraph.get_edge_across(A, B); + if (edgeNum && + !mGraph.get_edge(edgeNum).mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_JUMPING) && + !mGraph.get_edge(edgeNum).mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_FLYING) && + mGraph.get_edge(edgeNum).mDistancecurrentOrigin, target->currentOrigin)currentOrigin, target->currentOrigin)) + { + return true; + } + } + } + return false; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::OnNeighboringPoints(gentity_t* actor, const vec3_t& position) +{ + if (OnNeighboringPoints(GetNearestNode(actor), GetNearestNode(position))) + { + if (Distance(actor->currentOrigin, position)currentOrigin, position)) + { + return true; + } + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Is The Position (at) within the safe radius of the atNode, targetNode, or the edge +// between? +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::InSafeRadius(CVec3 at, TNodeHandle atNode, TNodeHandle targetNode) +{ + // Uh, No + //-------- + if (atNode<=0) + { + return false; + } + + // If In The Radius Of Nearest Nav Point + //--------------------------------------- + if (Distance(at.v, GetNodePosition(atNode))0 && atNode!=targetNode) + { + // If In The Radius Of Target Nav Point + //--------------------------------------- + if (Distance(at.v, GetNodePosition(targetNode))waypoint==WAYPOINT_NONE) + { + GetNearestNode(target); + } + if (target->waypoint!=WAYPOINT_NONE) + { + return FindPath(actor, target->waypoint, MaxDangerLevel); + } + else if (target->lastWaypoint!=WAYPOINT_NONE) + { + return FindPath(actor, target->lastWaypoint, MaxDangerLevel); + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::FindPath(gentity_t* actor, const vec3_t& position, float MaxDangerLevel) +{ + return FindPath(actor, GetNearestNode(position), MaxDangerLevel); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +const vec3_t& NAV::NextPosition(gentity_t* actor) +{ + assert(HasPath(actor)); + TPath& path = mPathUsers[mPathUserIndex[actor->s.number]].mPath; + + return (path[path.size()-1].mPoint.v); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::NextPosition(gentity_t* actor, CVec3& Position) +{ + assert(HasPath(actor)); + TPath& path = mPathUsers[mPathUserIndex[actor->s.number]].mPath; + Position = path[path.size()-1].mPoint; + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::NextPosition(gentity_t* actor, CVec3& Position, float& SlowingRadius, bool& Fly, bool& Jump) +{ + assert(HasPath(actor)); + TPath& path = mPathUsers[mPathUserIndex[actor->s.number]].mPath; + SPathPoint& next = path[path.size()-1]; + CWayNode& node = mGraph.get_node(next.mNode); + int curNodeIndex = NAV::GetNearestNode(actor); + int edgeIndex = (curNodeIndex>0)?(mGraph.get_edge_across(curNodeIndex, next.mNode)):(abs(curNodeIndex)); + + SlowingRadius = next.mSlowingRadius; + Position = next.mPoint; + Fly = node.mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING); + + if (edgeIndex) + { + Jump = mGraph.get_edge(edgeIndex).mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_JUMPING); + } + + return true; +} +//////////////////////////////////////////////////////////////////////////////////////// +// This function completely removes any pathfinding information related to this agent +// and frees up this path user structure for someone else to use. +//////////////////////////////////////////////////////////////////////////////////////// +void NAV::ClearPath(gentity_t* actor) +{ + int pathUserNum = mPathUserIndex[actor->s.number]; + if (pathUserNum==NULL_PATH_USER_INDEX) + { + return; + } + + mPathUsers.free(pathUserNum); + mPathUserIndex[actor->s.number] = NULL_PATH_USER_INDEX; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Update Path +// +// Removes points that have been reached, and frees the user if no points remain. +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::UpdatePath(gentity_t* actor, TNodeHandle target, float MaxDangerLevel) +{ + int pathUserNum = mPathUserIndex[actor->s.number]; + if (pathUserNum==NULL_PATH_USER_INDEX) + { + return false; + } + if (!mPathUsers[pathUserNum].mSuccess) + { + return false; + } + if (!mPathUsers[pathUserNum].mPath.size()) + { + return false; + } + + + // Remove Any Points We Have Reached + //----------------------------------- + CVec3 At(actor->currentOrigin); + TPath& path = mPathUsers[pathUserNum].mPath; + bool InReachedRadius = false; + bool ReachedAnything = false; + assert(path.size()>0); + + do + { + SPathPoint& PPoint = path[path.size()-1]; + CVec3 Dir(PPoint.mPoint - At); + if (fabsf(At[2] - PPoint.mPoint[2])0); + + + // If We've Reached The End Of The Path, Return With no path! + //------------------------------------------------------------ + if (path.empty()) + { + return false; + } + + if (ReachedAnything) + { + if (target!=PT_NONE && mPathUsers[pathUserNum].mEnd!=target) + { + return false; + } + } + + // If Not, Time To Double Check To See If The Path Is Still Valid + //---------------------------------------------------------------- + if (path[path.size()-1].mETAMaxDangerLevel)) + { + // Hmmm. Should Have Reached This Point By Now, Or Too Dangerous. Try To Recompute The Path + //-------------------------------------------------------------------------------------------- + NAV::TNodeHandle target = mPathUsers[pathUserNum].mEnd; // Remember The End As Our Target + if (target==WAYPOINT_NONE) + { + ClearPath(actor); + return false; + } + mPathUsers[pathUserNum].mEnd = WAYPOINT_NONE; // Clear Out The Old End + if (!FindPath(actor, target, MaxDangerLevel)) + { + mPathUsers[pathUserNum].mEnd = target; + return false; + } + return true; + } + + + // Ok, We Have A Path, And Are On-Route + //-------------------------------------- + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool NAV::HasPath(gentity_t* actor, TNodeHandle target) +{ + int pathUserNum = mPathUserIndex[actor->s.number]; + if (pathUserNum==NULL_PATH_USER_INDEX) + { + return false; + } + if (!mPathUsers[pathUserNum].mSuccess) + { + return false; + } + if (!mPathUsers[pathUserNum].mPath.size()) + { + return false; + } + if (target!=PT_NONE && mPathUsers[pathUserNum].mEnd!=target) + { + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +float NAV::PathDangerLevel(gentity_t* actor) +{ + if (!actor) + { + assert("No Actor!!!"==0); + return 0.0f; + } + int pathUserNum = mPathUserIndex[actor->s.number]; + if (pathUserNum==NULL_PATH_USER_INDEX) + { + return 0.0f; + } + TPath& Path = mPathUsers[pathUserNum].mPath; + + // If it only has one point, let's say it's not dangerous + //-------------------------------------------------------- + if (Path.size()<2) + { + return 0.0f; + } + + float DangerLevel = 0.0f; + CVec3 enemyPos; + float enemySafeDist = 0.0f; + float enemyDangerLevel = 0.0f; + TEdgeHandle curEdge = 0; + int curPathAt = Path.size()-1; + TAlertList& al = mEntityAlertList[actor->s.number]; + int alIndex = 0; + TNodeHandle prevNode = (GetNearestNode(actor)); + CVec3 prevPoint(actor->currentOrigin); + + + // Some Special Enemies Always Cause Persistant Danger + //----------------------------------------------------- + if (actor->enemy && actor->enemy->client) + { + if (actor->enemy->client->ps.weapon==WP_SABER || + actor->enemy->client->NPC_class==CLASS_RANCOR || + actor->enemy->client->NPC_class==CLASS_WAMPA) + { + enemyPos = (actor->enemy->currentOrigin); + enemySafeDist = actor->enemy->radius * 10.0f; + } + } + + + // Go Through All Remaining Points On The Path + //--------------------------------------------- + for (; curPathAt>=0; curPathAt--) + { + SPathPoint& PPoint = Path[curPathAt]; + + // If Any Edges On The Path Have Been Registered As Dangerous + //------------------------------------------------------------- + if (prevNode<0 || mGraph.get_edge_across(prevNode, PPoint.mNode)) + { + if (prevNode<0) + { + curEdge = prevNode; + } + else + { + curEdge = mGraph.get_edge_across(prevNode, PPoint.mNode); + } + for (alIndex=0; alIndexDangerLevel) + { + DangerLevel = al[alIndex].mDanger; + } + } + } + + // Check For Enemy Position Proximity To This Next Edge (using actual Edge Locations) + //------------------------------------------------------------------------------------ + if (enemySafeDist!=0.0f) + { + enemyDangerLevel = enemyPos.DistToLine(prevPoint, PPoint.mPoint)/enemySafeDist; + if (enemyDangerLevel>DangerLevel) + { + DangerLevel = enemyDangerLevel; + } + } + + prevNode = PPoint.mNode; + prevPoint = PPoint.mPoint; + } + return DangerLevel; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +int NAV::PathNodesRemaining(gentity_t* actor) +{ + int pathUserNum = mPathUserIndex[actor->s.number]; + if (pathUserNum==NULL_PATH_USER_INDEX) + { + return false; + } + return mPathUsers[pathUserNum].mPath.size(); + +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +const vec3_t& NAV::GetNodePosition(TNodeHandle NodeHandle) +{ + if (NodeHandle!=0) + { + if (NodeHandle>0) + { + return (mGraph.get_node(NodeHandle).mPoint.v); + } + else + { + return (mGraph.get_edge(abs(NodeHandle)).PointA().v); + } + } + return mZeroVec; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void NAV::GetNodePosition(TNodeHandle NodeHandle, vec3_t& position) +{ + if (NodeHandle!=0) + { + if (NodeHandle>0) + { + VectorCopy(mGraph.get_node(NodeHandle).mPoint.v, position); + } + else + { + VectorCopy(mGraph.get_edge(abs(NodeHandle)).PointA().v, position); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Call This function to get the size classification for a given entity +//////////////////////////////////////////////////////////////////////////////////////// +unsigned int NAV::ClassifyEntSize(gentity_t* ent) +{ + if (ent) + { + float minRadius = Min(ent->mins[0], ent->mins[1]); + float maxRadius = Max(ent->maxs[0], ent->maxs[1]); + float radius = Max(fabsf(minRadius), maxRadius); + float height = ent->maxs[2]; + + if ((radius > SC_MEDIUM_RADIUS) || height > (SC_MEDIUM_HEIGHT)) + { + return CWayEdge::EWayEdgeFlags::WE_SIZE_LARGE; + } + return CWayEdge::EWayEdgeFlags::WE_SIZE_MEDIUM; + } + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void NAV::ShowDebugInfo(const vec3_t& PlayerPosition, int PlayerWaypoint) +{ + mUser.ClearActor(); + CVec3 atEnd; + + // Show Nodes + //------------ + if (NAVDEBUG_showNodes || NAVDEBUG_showCombatPoints || NAVDEBUG_showNavGoals) + { + for (TGraph::TNodes::iterator atIter=mGraph.nodes_begin(); atIter!=mGraph.nodes_end(); atIter++) + { + CWayNode& at = (*atIter); + atEnd = at.mPoint; + atEnd[2] += 30.0f; + + + if (gi.inPVS(PlayerPosition, at.mPoint.v)) + { + if (at.mType==NAV::PT_WAYNODE && NAVDEBUG_showNodes) + { + if (NAVDEBUG_showPointLines) + { + if (at.mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING)) + { + CG_DrawEdge(at.mPoint.v, atEnd.v, EDGE_NODE_FLOATING ); + } + else + { + CG_DrawEdge(at.mPoint.v, atEnd.v, EDGE_NODE_NORMAL ); + } + } + else + { + if (at.mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING)) + { + CG_DrawNode(at.mPoint.v, NODE_FLOATING ); + } + else + { + CG_DrawNode(at.mPoint.v, NODE_NORMAL ); + } + } + if (NAVDEBUG_showRadius && at.mPoint.Dist2(PlayerPosition)<(at.mRadius*at.mRadius)) + { + if (at.mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING)) + { + CG_DrawRadius(at.mPoint.v, at.mRadius, NODE_FLOATING ); + } + else + { + CG_DrawRadius(at.mPoint.v, at.mRadius, NODE_NORMAL ); + } + } + } + else if (at.mType==NAV::PT_COMBATNODE && NAVDEBUG_showCombatPoints) + { + if (NAVDEBUG_showPointLines) + { + CG_DrawEdge(at.mPoint.v, atEnd.v, EDGE_NODE_COMBAT ); + } + else + { + CG_DrawCombatPoint(at.mPoint.v, 0); + } + } + else if (at.mType==NAV::PT_GOALNODE && NAVDEBUG_showNavGoals) + { + if (NAVDEBUG_showPointLines) + { + CG_DrawEdge(at.mPoint.v, atEnd.v, EDGE_NODE_GOAL ); + } + else + { + CG_DrawNode(at.mPoint.v, NODE_NAVGOAL); + } + } + } + } + } + + + // Show Edges + //------------ + if (NAVDEBUG_showEdges) + { + for (TGraph::TEdges::iterator atIter=mGraph.edges_begin(); atIter!=mGraph.edges_end(); atIter++) + { + CWayEdge& at = (*atIter); + CWayNode& a = mGraph.get_node(at.mNodeA); + CWayNode& b = mGraph.get_node(at.mNodeB); + CVec3 AvePos = a.mPoint + b.mPoint; + AvePos *= 0.5f; + + if (AvePos.Dist2(PlayerPosition)<(500.0f*500.0f) && gi.inPVS(PlayerPosition, AvePos.v)) + { + if (mUser.is_valid(at)) + { + if (at.mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_JUMPING)) + { + CG_DrawEdge(a.mPoint.v, b.mPoint.v, EDGE_JUMP); + } + else if (at.mFlags.get_bit(CWayEdge::EWayEdgeFlags::WE_FLYING)) + { + CG_DrawEdge(a.mPoint.v, b.mPoint.v, EDGE_FLY); + } + else if (at.Size()==CWayEdge::EWayEdgeFlags::WE_SIZE_LARGE) + { + CG_DrawEdge(a.mPoint.v, b.mPoint.v, EDGE_LARGE); + } + else + { + CG_DrawEdge(a.mPoint.v, b.mPoint.v, EDGE_NORMAL); + } + } + else + { + CG_DrawEdge(a.mPoint.v, b.mPoint.v, EDGE_BLOCKED); + } + } + } + } + if (NAVDEBUG_showGrid) + { + float x1, y1; + float x2, y2; + float z = 0.0f; + + for (int x=0; xwaypoint!=0 || player->lastWaypoint!=0)) + { + PlayerWaypoint = (player->waypoint)?(player->waypoint):(player->lastWaypoint); + CVec3 PPos(PlayerPosition); + if (PlayerWaypoint>0) + { + CWayNode& node = mGraph.get_node(PlayerWaypoint); + + CG_DrawEdge(PPos.v, node.mPoint.v, (player->waypoint)?(EDGE_NEARESTVALID):(EDGE_NEARESTINVALID)); + } + else + { + CWayEdge& edge = mGraph.get_edge(abs(PlayerWaypoint)); + CVec3 PosOnLine(PlayerPosition); + PosOnLine.ProjectToLineSeg(edge.PointA(), edge.PointB()); + + CG_DrawEdge(PPos.v, PosOnLine.v, (player->waypoint)?(EDGE_NEARESTVALID):(EDGE_NEARESTINVALID)); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////// +// Show Stats +//////////////////////////////////////////////////////////////////////////////////// +void NAV::ShowStats() +{ +#if !defined(FINAL_BUILD) + mGraph.ProfileSpew(); + mRegion.ProfileSpew(); + + mGraph.ProfilePrint("Point Islands: (%d)", mIslandCount); + mGraph.ProfilePrint(""); + mGraph.ProfilePrint(""); + mGraph.ProfilePrint("--------------------------------------------------------"); + mGraph.ProfilePrint(" Star Wars - Jedi Academy "); + mGraph.ProfilePrint(" Additional Statistics "); + mGraph.ProfilePrint("--------------------------------------------------------"); + mGraph.ProfilePrint(""); + mGraph.ProfilePrint("MEMORY CONSUMPTION (In Bytes)"); + mGraph.ProfilePrint("Cells : (%d)", (sizeof(mCells))); + mGraph.ProfilePrint("Path : (%d)", (sizeof(mPathUsers)+sizeof(mPathUserIndex))); + mGraph.ProfilePrint("Steer : (%d)", (sizeof(mSteerUsers)+sizeof(mSteerUserIndex))); + mGraph.ProfilePrint("Alerts : (%d)", (sizeof(mEntityAlertList))); + float totalBytes = ( + sizeof(mCells)+ + sizeof(mGraph)+ + sizeof(mRegion)+ + sizeof(mPathUsers)+ + sizeof(mPathUserIndex)+ + sizeof(mSteerUsers)+ + sizeof(mSteerUserIndex)+ + sizeof(mEntityAlertList)); + + mGraph.ProfilePrint("TOTAL : (KiloBytes): (%5.3f) MeggaBytes(%3.3f)", + ((float)(totalBytes)/1024.0f), + ((float)(totalBytes)/1048576.0f) + ); + mGraph.ProfilePrint(""); + + + mGraph.ProfilePrint("Connect Stats: Milliseconds(%d) Traces(%d)", mConnectTime, mConnectTraceCount); + mGraph.ProfilePrint(""); + mGraph.ProfilePrint("Move Trace: Count(%d) PerFrame(%f)", mMoveTraceCount, (float)(mMoveTraceCount)/(float)(level.time)); + mGraph.ProfilePrint("View Trace: Count(%d) PerFrame(%f)", mViewTraceCount, (float)(mViewTraceCount)/(float)(level.time)); + +#endif +} + +//////////////////////////////////////////////////////////////////////////////////// +// TeleportTo +//////////////////////////////////////////////////////////////////////////////////// +void NAV::TeleportTo(gentity_t* actor, const char* pointName) +{ + assert(actor!=0); + hstring nName(pointName); + TNameToNodeMap::iterator nameFinder= mNodeNames.find(nName); + if (nameFinder!=mNodeNames.end()) + { + if ((*nameFinder).size()>1) + { + gi.Printf("WARNING: More than one point named (%s). Going to first one./n", pointName); + } + TeleportPlayer(actor, mGraph.get_node((*nameFinder)[0]).mPoint.v, actor->currentAngles); + return; + } + gi.Printf("Unable To Locate Point (%s)\n", pointName); +} + +//////////////////////////////////////////////////////////////////////////////////// +// TeleportTo +//////////////////////////////////////////////////////////////////////////////////// +void NAV::TeleportTo(gentity_t* actor, int pointNum) +{ + assert(actor!=0); + TeleportPlayer(actor, mGraph.get_node(pointNum).mPoint.v, actor->currentAngles); + return; +} + + + + +//////////////////////////////////////////////////////////////////////////////////// +// Activate +//////////////////////////////////////////////////////////////////////////////////// +void STEER::Activate(gentity_t* actor) +{ + assert(!Active(actor) && actor && actor->client && actor->NPC); // Can't Activate If Already Active + + +// PHASE I - ACTIVATE THE STEER USER FOR THIS ACTOR +//================================================== + if (mSteerUsers.full()) + { + assert("STEER: No more unused steer users, possibly change size"==0); + return; + } + + // Get A Steer User From The Pool + //-------------------------------- + int steerUserNum = mSteerUsers.alloc(); + mSteerUserIndex[actor->s.number] = steerUserNum; + SSteerUser& suser = mSteerUsers[steerUserNum]; + + +// PHASE II - Copy Data For This Actor Into The SUser +//==================================================== + suser.mPosition = actor->currentOrigin; + suser.mOrientation = actor->currentAngles; + suser.mVelocity = actor->client->ps.velocity; + suser.mSpeed = suser.mVelocity.Len(); + + suser.mBlocked = false; + + suser.mMaxSpeed = actor->NPC->stats.runSpeed; + suser.mRadius = RadiusFromBounds(actor->mins, actor->maxs); + suser.mMaxForce = 150.0f; //STEER_TODO: Get From actor Somehow + suser.mMass = 1.0f; //STEER_TODO: Get From actor Somehow + if (!(actor->NPC->scriptFlags&SCF_RUNNING) && + ((actor->NPC->scriptFlags&SCF_WALKING) || + (actor->NPC->aiFlags&NPCAI_WALKING) || + (ucmd.buttons&BUTTON_WALKING) + )) + { + suser.mMaxSpeed = actor->NPC->stats.walkSpeed; + } + + assert(suser.mPosition.IsFinite()); + assert(suser.mOrientation.IsFinite()); + assert(suser.mVelocity.IsFinite()); + + + // Find Our Neighbors + //-------------------- + suser.mNeighbors.clear(); + float RangeSize = suser.mRadius + STEER::NEIGHBOR_RANGE; + + CVec3 Range(RangeSize, RangeSize, (actor->client->moveType==MT_FLYSWIM)?(RangeSize):(suser.mRadius*2.0f)); + CVec3 Mins(suser.mPosition - Range); + CVec3 Maxs(suser.mPosition + Range); + + gentity_t* EntityList[MAX_GENTITIES]; + gentity_t* neighbor = 0; + + int numFound = gi.EntitiesInBox(Mins.v, Maxs.v, EntityList, MAX_GENTITIES); + for (int i=0; is.number==actor->s.number || neighbor==actor->enemy || !neighbor->client || neighbor->health<=0 || !neighbor->inuse) + { + continue; + } + + suser.mNeighbors.push_back(neighbor); + } + + + // Clear Out Steering, So If No STEER Operations Are Called, Net Effect Is Zero + //------------------------------------------------------------------------------ + suser.mSteering.Clear(); + suser.mNewtons = 0.0f; + + VectorClear(actor->client->ps.moveDir); + actor->client->ps.speed = 0; + + +// PHASE III - Project The Current Velocity Forward, To The Side, And Onto The Path +//================================================================================== + suser.mProjectFwd = suser.mPosition + (suser.mVelocity * 1.0f); + suser.mProjectSide = (suser.mVelocity * 0.3f); + suser.mProjectSide.Reposition(suser.mPosition, (actor->NPC->avoidSide==Side_Left)?(40.0f):(-40.0f)); + + + // STEER_TODO: Project The Point The Path (If The character has one) + //------------------------------------------------------------------- + //suser.mProjectPath = ; + +} + +////////////////////////////////////////////////////////////////////// +// DeActivate +// +// This function first scales back the composite steering vector to +// the max force range, and if there is any steering left, it +// applies the steering to the velocity. Velocity is also truncated +// to be less than Max Speed. +// +// Finaly, the results are copied onto the entity and the steer user +// struct is freed for use by another entity. +////////////////////////////////////////////////////////////////////// +void STEER::DeActivate(gentity_t* actor, usercmd_t* ucmd) +{ + assert(Active(actor) && actor && actor->client && actor->NPC); // Can't Deactivate If Never Activated + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + + assert(suser.mPosition.IsFinite()); + assert(suser.mOrientation.IsFinite()); + assert(suser.mSteering.IsFinite()); + assert(suser.mMass!=0.0f); + + + +// PHASE I - TRUNCATE STEERING AND APPLY TO VELOCITY +//=================================================== + suser.mNewtons = suser.mSteering.Truncate(suser.mMaxForce); + if (suser.mNewtons>1E-10) + { + suser.mSteering /= suser.mMass; + suser.mVelocity += suser.mSteering; + suser.mSpeed = suser.mVelocity.Truncate(suser.mMaxSpeed); + + // DEBUG GRAPHICS + //================================================================= + if (NAVDEBUG_showCollision) + { + CVec3 EndThrust(suser.mPosition+suser.mSteering); + CVec3 EndVelocity(suser.mPosition+suser.mVelocity); + + CG_DrawEdge(suser.mPosition.v, EndThrust.v, EDGE_THRUST); + CG_DrawEdge(suser.mPosition.v, EndVelocity.v, EDGE_VELOCITY); + } + //================================================================= + } + if (suser.mSpeed<10.0f) + { + suser.mSpeed = 0.0f; + } + + + + + + + +// PHASE II - CONVERT VELOCITY TO MOVE DIRECTION & ANGLES +//======================================================== + if (!NPC_Jumping()) + { + CVec3 MoveDir(suser.mVelocity); + CVec3 Angles(actor->NPC->lastPathAngles); + + if (suser.mSpeed>0.0f && MoveDir!=CVec3::mZero) + { + MoveDir.Norm(); + CVec3 NewAngles(suser.mVelocity); + NewAngles.VecToAng(); + + Angles = NewAngles;//((Angles + NewAngles)*0.75f); + } + assert(MoveDir.IsFinite()); + assert(Angles.IsFinite()); + + + +// PHASE III - ASSIGN ALL THIS DATA TO THE ACTOR ENTITY +//====================================================== + actor->NPC->aiFlags |= NPCAI_NO_SLOWDOWN; + + VectorCopy(MoveDir.v, + actor->client->ps.moveDir); + actor->client->ps.speed = suser.mSpeed; + + VectorCopy(Angles.v, + actor->NPC->lastPathAngles); + actor->NPC->desiredPitch = 0.0f; + actor->NPC->desiredYaw = AngleNormalize360(Angles[YAW]); + + + // Convert Movement To User Command + //---------------------------------- + if (suser.mSpeed > 0.0f) + { + vec3_t forward, right, up; + AngleVectors(actor->currentAngles, forward, right, up); // Use Current Angles + + float fDot = Com_Clamp(-127.0f, 127.0f, DotProduct(forward, MoveDir.v)*127.0f); + float rDot = Com_Clamp(-127.0f, 127.0f, DotProduct(right, MoveDir.v)*127.0f); + + ucmd->forwardmove = floor(fDot); + ucmd->rightmove = floor(rDot); + ucmd->upmove = 0.0f; + + if (suser.mSpeed<(actor->NPC->stats.walkSpeed + 5.0f)) + { + ucmd->buttons |= BUTTON_WALKING; + } + else + { + ucmd->buttons &= ~BUTTON_WALKING; + } + + // Handle Fly Swim Movement + //-------------------------- + if (actor->client->moveType==MT_FLYSWIM) + { + ucmd->forwardmove = 0.0f; + ucmd->rightmove = 0.0f; + + VectorCopy(suser.mVelocity.v, actor->client->ps.velocity); + } + } + else + { + ucmd->forwardmove = 0.0f; + ucmd->rightmove = 0.0f; + ucmd->upmove = 0.0f; + + // Handle Fly Swim Movement + //-------------------------- + if (actor->client->moveType==MT_FLYSWIM) + { + VectorClear(actor->client->ps.velocity); + } + } + + // Slow Down If Going Backwards + //------------------------------ + if (ucmd->forwardmove<0.0f) + { + client->ps.speed *= 0.75f; + suser.mSpeed *= 0.75f; + } + + +// PHASE IV - UPDATE BLOCKING INFORMATION +//======================================== + if (suser.mBlocked) + { + // If Not Previously Blocked, Record The Start Time And Any Entites Involved + //--------------------------------------------------------------------------- + if (!(actor->NPC->aiFlags&NPCAI_BLOCKED)) + { + actor->NPC->aiFlags |= NPCAI_BLOCKED; + actor->NPC->blockedDebounceTime = level.time; + } + + // If Navigation Or Steering Had A Target Entity (Trying To Go To), Record That Here + //----------------------------------------------------------------------------------- + actor->NPC->blockedTargetEntity = 0; + if (suser.mBlockedTgtEntity!=ENTITYNUM_NONE) + { + actor->NPC->blockedTargetEntity = &g_entities[suser.mBlockedTgtEntity]; + } + VectorCopy(suser.mBlockedTgtPosition.v, actor->NPC->blockedTargetPosition); + } + else + { + // Nothing Blocking, So Turn Off The Blocked Stuff If It Is There + //---------------------------------------------------------------- + if (actor->NPC->aiFlags&NPCAI_BLOCKED) + { + actor->NPC->aiFlags &= ~NPCAI_BLOCKED; + actor->NPC->blockedDebounceTime = 0; + actor->NPC->blockedTargetEntity = 0; + } + } + + // If We've Been Blocked For More Than 2 Seconds, May Want To Take Corrective Action + //----------------------------------------------------------------------------------- + if (STEER::HasBeenBlockedFor(actor, 2000)) + { + if (NAVDEBUG_showEnemyPath) + { + CG_DrawEdge(actor->currentOrigin, actor->NPC->blockedTargetPosition, EDGE_PATHBLOCKED); + if (actor->waypoint) + { + CVec3 Dumb(NAV::GetNodePosition(actor->waypoint)); + CG_DrawEdge(actor->currentOrigin, Dumb.v, EDGE_BLOCKED); + } + } + + // If He Can Fly Or Jump, Try That + //--------------------------------- + if ((actor->NPC->scriptFlags&SCF_NAV_CAN_FLY) || + (actor->NPC->scriptFlags&SCF_NAV_CAN_JUMP) + ) + { + // Ok, Well, How About Jumping To The Last Waypoint We Had That Was Valid + //------------------------------------------------------------------------ + if ((STEER::HasBeenBlockedFor(actor, 8000)) && + (!actor->waypoint || Distance(NAV::GetNodePosition(actor->waypoint), actor->currentOrigin)>150.0f) && + (actor->lastWaypoint)) + { + if (player && + (STEER::HasBeenBlockedFor(actor, 15000)) && + !gi.inPVS(player->currentOrigin, NAV::GetNodePosition(actor->lastWaypoint)) && + !gi.inPVS(player->currentOrigin, actor->currentOrigin) && + !G_CheckInSolidTeleport(NAV::GetNodePosition(actor->lastWaypoint), actor) + ) + { + G_SetOrigin(actor, NAV::GetNodePosition(actor->lastWaypoint)); + G_SoundOnEnt( NPC, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + else + { + NPC_TryJump(NAV::GetNodePosition(actor->lastWaypoint)); + } + } + + // First, Try Jumping Directly To Our Target Desired Location + //------------------------------------------------------------ + else + { + // If We Had A Target Entity, Try Jumping There + //---------------------------------------------- + if (NPCInfo->blockedTargetEntity) + { + NPC_TryJump(NPCInfo->blockedTargetEntity); + } + + // Otherwise Try Jumping To The Target Position + //---------------------------------------------- + else + { + NPC_TryJump(NPCInfo->blockedTargetPosition); + } + } + } + } + } + +// PHASE V - FREE UP THE STEER USER +//=================================== + mSteerUsers.free(mSteerUserIndex[actor->s.number]); + mSteerUserIndex[actor->s.number] = NULL_STEER_USER_INDEX; +} + +////////////////////////////////////////////////////////////////////// +// Active? +// +// Simple way for external systemm to check if this object is active +// already. +// +////////////////////////////////////////////////////////////////////// +bool STEER::Active(gentity_t* actor) +{ + return (mSteerUserIndex[actor->s.number]!=NULL_STEER_USER_INDEX); +} + +//////////////////////////////////////////////////////////////////////////////////// +// SafeToGoTo - returns true if it is safe for the actor to steer toward the target +// position +//////////////////////////////////////////////////////////////////////////////////// +bool STEER::SafeToGoTo(gentity_t* actor, const vec3_t& targetPosition, int targetNode) +{ + int actorNode = NAV::GetNearestNode(actor, true, targetNode); + float actorToTargetDistance = Distance(actor->currentOrigin, targetPosition); + + + // Are They Close Enough To Just Go There + //---------------------------------------- + if (actorToTargetDistance<110.0f && fabsf(targetPosition[2]-actor->currentOrigin[2])<50.0f) + { + return true; + } + + // Are They Both Within The Radius Of Their Nearest Nav Point? + //------------------------------------------------------------- + if (actorToTargetDistance<500.0f) + { + // Are Both Actor And Target In Safe Radius? + //------------------------------------------- + if (NAV::OnNeighboringPoints(actorNode, targetNode) && + NAV::InSafeRadius(actor->currentOrigin, actorNode, targetNode) && + NAV::InSafeRadius(targetPosition, targetNode, actorNode)) + { + return true; + } + + // If Close Enough, We May Be Able To Go Anyway, So Try A Trace + //-------------------------------------------------------------- + if (actorToTargetDistance<400.0f)//250.0f) + { + if (!TIMER_Done(actor, "SafeToGoToDURATION")) + { + return true; + } + + if (TIMER_Done(actor, "SafeToGoToCHECK")) + { + TIMER_Set( actor, "SafeToGoToCHECK", 1500); // Check Every 1.5 Seconds + if (MoveTrace(actor, targetPosition, true)) + { + TIMER_Set(actor, "SafeToGoToDURATION", 2000); // Safe For 2 Seconds + + if (NAVDEBUG_showCollision) + { + CVec3 Dumb(targetPosition); + CG_DrawEdge(actor->currentOrigin, Dumb.v, EDGE_WHITE_TWOSECOND); + } + } + else + { + if (NAVDEBUG_showCollision) + { + CVec3 Dumb(targetPosition); + CG_DrawEdge(actor->currentOrigin, Dumb.v, EDGE_RED_TWOSECOND); + } + } + } + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Master Functions +//////////////////////////////////////////////////////////////////////////////////// +static int mutantRancorSteerTimer = 0; // HACK so that mutant rancor can't get stuck + +bool STEER::GoTo(gentity_t* actor, gentity_t* target, float reachedRadius, bool avoidCollisions) +{ + // Can't Steer To A Guy In The Air + //--------------------------------- + if (!target) + { + NAV::ClearPath(actor); + STEER::Stop(actor); + return true; + } + + // If Within Reached Radius, Just Stop Right Here + //------------------------------------------------ + if (STEER::Reached(actor, target->currentOrigin, reachedRadius, (actor->client && actor->client->moveType==MT_FLYSWIM))) + { + NAV::ClearPath(actor); + STEER::Stop(actor); + return true; + } + + // Check To See If It Is Safe To Attempt Steering Toward The Target Position + //--------------------------------------------------------------------------- + if ( + //(target->client && target->client->ps.groundEntityNum==ENTITYNUM_NONE) || + !STEER::SafeToGoTo(actor, target->currentOrigin, NAV::GetNearestNode(target)) + ) + { + + // HACK so that mutant rancor can't get stuck + if((level.time - mutantRancorSteerTimer) > 6000) + { + if( actor->client && actor->client->NPC_class == CLASS_RANCOR ) + { + return STEER::Wander(actor); + } + } + + return false; + } + + mutantRancorSteerTimer = level.time; + + // Ok, It Is, So Clear The Path, And Go Toward Our Target + //-------------------------------------------------------- + NAV::ClearPath(actor); + STEER::Persue(actor, target, reachedRadius*4.0f); + if (avoidCollisions) + { + if (STEER::AvoidCollisions(actor, actor->client->leader)!=0.0f) + { + STEER::Blocked(actor, target); // Collision Detected + } + } + + if (NAVDEBUG_showEnemyPath) + { + CG_DrawEdge(actor->currentOrigin, target->currentOrigin, EDGE_FOLLOWPOS); + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Master Function- GoTo +//////////////////////////////////////////////////////////////////////////////////// +bool STEER::GoTo(gentity_t* actor, const vec3_t& position, float reachedRadius, bool avoidCollisions) +{ + // If Within Reached Radius, Just Stop Right Here + //------------------------------------------------ + if (STEER::Reached(actor, position, reachedRadius, (actor->client && actor->client->moveType==MT_FLYSWIM))) + { + NAV::ClearPath(actor); + STEER::Stop(actor); + return true; + } + + // Check To See If It Is Safe To Attempt Steering Toward The Target Position + //--------------------------------------------------------------------------- + if (!STEER::SafeToGoTo(actor, position, NAV::GetNearestNode(position))) + { + return false; + } + + // Ok, It Is, So Clear The Path, And Go Toward Our Target + //-------------------------------------------------------- + NAV::ClearPath(actor); + STEER::Seek(actor, position, reachedRadius*2.0f); + if (avoidCollisions) + { + if (STEER::AvoidCollisions(actor, actor->client->leader)!=0.0f) + { + STEER::Blocked(actor, position); // Collision Detected + } + } + + if (NAVDEBUG_showEnemyPath) + { + CVec3 Dumb(position); + CG_DrawEdge(actor->currentOrigin, Dumb.v, EDGE_FOLLOWPOS); + } + return true; +} + + +//////////////////////////////////////////////////////////////////////////////////// +// Blocked Recorder +//////////////////////////////////////////////////////////////////////////////////// +void STEER::Blocked(gentity_t* actor, gentity_t* target) +{ + assert(Active(actor)); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + suser.mBlocked = true; + suser.mBlockedTgtEntity = target->s.number; + suser.mBlockedTgtPosition = target->currentOrigin; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Blocked Recorder +//////////////////////////////////////////////////////////////////////////////////// +void STEER::Blocked(gentity_t* actor, const vec3_t& target) +{ + assert(Active(actor)); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + suser.mBlocked = true; + suser.mBlockedTgtEntity = ENTITYNUM_NONE; + suser.mBlockedTgtPosition = target; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Blocked Recorder +//////////////////////////////////////////////////////////////////////////////////// +bool STEER::HasBeenBlockedFor(gentity_t* actor, int duration) +{ + return ( + (actor->NPC->aiFlags&NPCAI_BLOCKED) && + (level.time - actor->NPC->blockedDebounceTime)>duration + ); +} + +////////////////////////////////////////////////////////////////////// +// Stop +// +// Just Decelerate. +// +////////////////////////////////////////////////////////////////////// +float STEER::Stop(gentity_t* actor, float weight) +{ + assert(Active(actor)); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + suser.mDesiredVelocity.Clear(); + suser.mDistance = 0.0f; + suser.mDesiredSpeed = 0.0f; + suser.mSteering += ((suser.mDesiredVelocity - suser.mVelocity) * weight); + + if (actor->NPC->aiFlags&NPCAI_FLY) + { + int nearestNode = NAV::GetNearestNode(actor); + if (nearestNode>0 && !mGraph.get_node(nearestNode).mFlags.get_bit(CWayNode::EWayNodeFlags::WN_FLOATING)) + { + actor->NPC->aiFlags &= ~NPCAI_FLY; + } + } + + assert(suser.mSteering.IsFinite()); + + return 0.0f; +} + +////////////////////////////////////////////////////////////////////// +// +////////////////////////////////////////////////////////////////////// +float STEER::MatchSpeed(gentity_t* actor, float speed, float weight) +{ + assert(Active(actor)); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + suser.mDesiredVelocity = suser.mVelocity; + suser.mDesiredVelocity.Truncate(speed); + suser.mDistance = 0.0f; + suser.mDesiredSpeed = 0.0f; + suser.mSteering += ((suser.mDesiredVelocity - suser.mVelocity) * weight); + + assert(suser.mSteering.IsFinite()); + + return 0.0f; + +} + + +//////////////////////////////////////////////////////////////////////////////////// +// Seek +// +// The most basic of all steering operations, this function applies a change to +// the steering vector +// +//////////////////////////////////////////////////////////////////////////////////// +float STEER::Seek(gentity_t* actor, const CVec3& pos, float slowingDistance, float weight, float desiredSpeed) +{ + assert(Active(actor)); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + suser.mSeekLocation = pos; + suser.mDesiredVelocity = suser.mSeekLocation - suser.mPosition; + + //If The Difference In Height Is Small Enough, Just Kill It + //---------------------------------------------------------- + if (fabsf(suser.mDesiredVelocity[2]) < 10.0f) + { + suser.mDesiredVelocity[2] = 0.0f; + } + + + suser.mDistance = suser.mDesiredVelocity.SafeNorm(); + if (suser.mDistance>0.0f) + { + suser.mDesiredSpeed = (desiredSpeed!=0.0f)?(desiredSpeed):(suser.mMaxSpeed); + if (slowingDistance!=0.0f && suser.mDistance < slowingDistance) + { + suser.mDesiredSpeed *= (suser.mDistance/slowingDistance); + } + suser.mDesiredVelocity *= suser.mDesiredSpeed; + } + else + { + suser.mDesiredSpeed = 0.0f; + suser.mDesiredVelocity.Clear(); + } + + suser.mSteering += ((suser.mDesiredVelocity - suser.mVelocity) * weight); + + assert(suser.mSteering.IsFinite()); + + return suser.mDistance; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Flee +// +// Similar to seek, except there is no concept of a slowing distance. Adds the +// position vectors instead of subtracting them to obtain a desired velocity away +// from the target. +// +//////////////////////////////////////////////////////////////////////////////////// +float STEER::Flee(gentity_t* actor, const CVec3& pos, float weight) +{ + assert(Active(actor)); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + suser.mDesiredVelocity = suser.mPosition - pos; + suser.mDistance = suser.mDesiredVelocity.SafeNorm(); + suser.mDesiredSpeed = suser.mMaxSpeed; + suser.mDesiredVelocity *= suser.mDesiredSpeed; + suser.mSteering += ((suser.mDesiredVelocity - suser.mVelocity) * weight); + suser.mSeekLocation = pos + suser.mDesiredVelocity; + + + assert(suser.mSteering.IsFinite()); + + return suser.mDistance; +} + + +//////////////////////////////////////////////////////////////////////////////////// +// Persue +// +// Predict the target's position and seek that. +// +//////////////////////////////////////////////////////////////////////////////////// +float STEER::Persue(gentity_t* actor, gentity_t* target, float slowingDistance) +{ + assert(Active(actor) && target); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + CVec3 ProjectedTargetPosition(target->currentOrigin); + + if (target->client) + { + float DistToTarget = ProjectedTargetPosition.Dist(suser.mPosition); + + CVec3 TargetVelocity(target->client->ps.velocity); + float TargetSpeed = TargetVelocity.SafeNorm(); + if (TargetSpeed>0.0f) + { + TargetVelocity *= (DistToTarget + 5.0f); + ProjectedTargetPosition += TargetVelocity; + } + } + + return Seek(actor, ProjectedTargetPosition, slowingDistance); +} + + +//////////////////////////////////////////////////////////////////////////////////// +// Persue +// +// Predict the target's position and seek that. +// +//////////////////////////////////////////////////////////////////////////////////// +float STEER::Persue(gentity_t* actor, gentity_t* target, float slowingDistance, float offsetForward, float offsetRight, float offsetUp, bool relativeToTargetFacing) +{ + assert(Active(actor) && target); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + CVec3 ProjectedTargetPosition(target->currentOrigin); + + // If Target Is A Client, Take His Velocity Into Account And Project His Position + //-------------------------------------------------------------------------------- + if (target->client) + { + float DistToTarget = ProjectedTargetPosition.Dist(suser.mPosition); + + CVec3 TargetVelocity(target->client->ps.velocity); + float TargetSpeed = TargetVelocity.SafeNorm(); + if (TargetSpeed>0.0f) + { + TargetVelocity[2] *= 0.1f; + TargetVelocity *= (DistToTarget + 5.0f); + ProjectedTargetPosition += TargetVelocity; + } + } + + // Get The Direction Toward The Target + //------------------------------------- + CVec3 DirectionToTarget(ProjectedTargetPosition); + DirectionToTarget -= suser.mPosition; + DirectionToTarget.SafeNorm(); + + + CVec3 ProjectForward(DirectionToTarget); + CVec3 ProjectRight; + CVec3 ProjectUp; + + if (relativeToTargetFacing) + { + AngleVectors(target->currentAngles, ProjectForward.v, ProjectRight.v, ProjectUp.v); + if (ProjectRight.Dot(DirectionToTarget)>0.0f) + { + ProjectRight *= -1.0f; // If Going In Same Direction As Target Right, Project Toward Target Left + } + } + else + { + MakeNormalVectors(ProjectForward.v, ProjectRight.v, ProjectUp.v); + } + + ProjectedTargetPosition.ScaleAdd(ProjectForward, offsetForward); + ProjectedTargetPosition.ScaleAdd(ProjectRight, offsetRight); + ProjectedTargetPosition.ScaleAdd(ProjectUp, offsetUp); + + return Seek(actor, ProjectedTargetPosition, slowingDistance); +} + + +//////////////////////////////////////////////////////////////////////////////////// +// Evade +// +// Predict the target's position and flee that. +// +//////////////////////////////////////////////////////////////////////////////////// +float STEER::Evade(gentity_t* actor, gentity_t* target) +{ + assert(Active(actor) && target); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + CVec3 ProjectedTargetPosition(target->currentOrigin); + + if (target->client) + { + float DistToTarget = ProjectedTargetPosition.Dist(suser.mPosition); + + + CVec3 TargetVelocity(target->client->ps.velocity); + float TargetSpeed = TargetVelocity.SafeNorm(); + if (TargetSpeed>0.0f) + { + TargetVelocity *= (DistToTarget + 5.0f); + ProjectedTargetPosition += TargetVelocity; + } + } + + return Flee(actor, ProjectedTargetPosition); +} + + +//////////////////////////////////////////////////////////////////////////////////// +// Separation +//////////////////////////////////////////////////////////////////////////////////// +float STEER::Separation(gentity_t* actor, float Scale) +{ + assert(Active(actor) && actor); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + if (!suser.mNeighbors.empty()) + { + for (int i=0; is.number>actor->s.number) + { + CVec3 NbrPos(suser.mNeighbors[i]->currentOrigin); + CVec3 NbrToAct(suser.mPosition - NbrPos); + float NbrToActDist = NbrToAct.Len2(); + if (NbrToActDist>1.0f) + { + NbrToActDist = (1.0f/NbrToActDist); + NbrToAct *= NbrToActDist * (suser.mMaxSpeed * 10.0f) * Scale; + suser.mSteering += NbrToAct; + + if (NAVDEBUG_showCollision) + { + CVec3 Prj(suser.mPosition + NbrToAct); + CG_DrawEdge(suser.mPosition.v, Prj.v, EDGE_IMPACT_POSSIBLE); // Separation + } + } + } + } + } + + assert(suser.mSteering.IsFinite()); + + return 0.0f; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Alignment +//////////////////////////////////////////////////////////////////////////////////// +float STEER::Alignment(gentity_t* actor, float Scale) +{ + return 0.0f; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Cohesion +//////////////////////////////////////////////////////////////////////////////////// +float STEER::Cohesion(gentity_t* actor, float Scale) +{ + assert(Active(actor) && actor); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + if (!suser.mNeighbors.empty()) + { + CVec3 AvePosition; + for (int i=0; icurrentOrigin); + } + AvePosition *= 1.0f/suser.mNeighbors.size(); + return Seek(actor, AvePosition); + } + return 0.0f; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Find Nearest Leader +//////////////////////////////////////////////////////////////////////////////////// +gentity_t* STEER::SelectLeader(gentity_t* actor) +{ + assert(Active(actor) && actor); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + for (int i=0; is.number>actor->s.number && !Q_stricmp(suser.mNeighbors[i]->NPC_type, actor->NPC_type )) + { + return suser.mNeighbors[i]; + } + + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Path - Seek to the next position on the path (or jump) +//////////////////////////////////////////////////////////////////////////////////// +float STEER::Path(gentity_t* actor) +{ + if (NAV::HasPath(actor)) + { + CVec3 NextPosition; + float NextSlowingRadius; + bool Fly = false; + bool Jump = false; + + if (!NAV::NextPosition(actor, NextPosition, NextSlowingRadius, Fly, Jump)) + { + assert("STEER: Unable to obtain the next path position"==0); + return 0.0f; + } + + // Start Flying If Next Point Is In The Air + //------------------------------------------ + if (Fly) + { + actor->NPC->aiFlags |= NPCAI_FLY; + } + + // Otherwise, If Next Point Is On The Ground, No Need To Fly Any Longer + //---------------------------------------------------------------------- + else if (actor->NPC->aiFlags&NPCAI_FLY) + { + actor->NPC->aiFlags &= ~NPCAI_FLY; + } + + // Start Jumping If Next Point Is A Jump + //--------------------------------------- + if (Jump) + { + if (NPC_TryJump(NextPosition.v)) + { + actor->NPC->aiFlags |= NPCAI_JUMP; + return 1.0f; + } + } + actor->NPC->aiFlags &=~NPCAI_JUMP; + + + // Preview His Path + //------------------ + if (NAVDEBUG_showEnemyPath) + { + CVec3 Prev(actor->currentOrigin); + TPath& path = mPathUsers[mPathUserIndex[actor->s.number]].mPath; + for (int i=path.size()-1; i>=0; i--) + { + CG_DrawEdge(Prev.v, path[i].mPoint.v, EDGE_PATH); + Prev = path[i].mPoint; + } + } + + // If Jump Was On, But We Reached This Point, It Must Have Failed + //---------------------------------------------------------------- + if (Jump) + { + Stop(actor); + return 0.0f; // We've Failed! + } + + // Otherwise, Go! + //---------------- + return Seek(actor, NextPosition, NextSlowingRadius); + } + return 0.0f; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Wander +//////////////////////////////////////////////////////////////////////////////////// +float STEER::Wander(gentity_t* actor) +{ + assert(Active(actor) && actor); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + + CVec3 Direction(CVec3::mX); + + if (suser.mSpeed>0.1f) + { + Direction = suser.mVelocity; + Direction.VecToAng(); + Direction[2] += Q_irand(-5, 5); + Direction.AngToVec(); + } + + Direction *= 70.0f; + Seek(actor, suser.mPosition+Direction); + + return 0.0f; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Follow A Leader Entity +//////////////////////////////////////////////////////////////////////////////////// +float STEER::FollowLeader(gentity_t* actor, gentity_t* leader, float dist) +{ + assert(Active(actor) && actor && leader && leader->client); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + + float LeaderSpeed = leader->resultspeed; + int TimeRemaining = (leader->followPosRecalcTime - level.time); + + if (TimeRemaining<0 || (LeaderSpeed>0.0f && TimeRemaining>1000)) + { + CVec3 LeaderPosition(leader->currentOrigin); + CVec3 LeaderDirection(leader->currentAngles); + LeaderDirection.pitch() = 0; + LeaderDirection.AngToVec(); + + if (!actor->enemy && !leader->enemy) + { + LeaderDirection = (LeaderPosition - suser.mPosition); + LeaderDirection.Norm(); + } + + CVec3 FollowPosition(LeaderDirection); + FollowPosition *= (-1.0f * (fabsf(dist)+suser.mRadius)); + FollowPosition += LeaderPosition; + + MoveTrace(leader, FollowPosition, true); + if (mMoveTrace.fraction>0.1) + { + FollowPosition = mMoveTrace.endpos; + FollowPosition += (LeaderDirection * suser.mRadius); + + VectorCopy(FollowPosition.v, leader->followPos); + leader->followPosWaypoint = NAV::GetNearestNode(leader->followPos, leader->waypoint, 0, leader->s.number); + } + + float MaxSpeed = (g_speed->value); + if (LeaderSpeed>MaxSpeed) + { + MaxSpeed = LeaderSpeed; + } + float SpeedScale = (1.0f - (LeaderSpeed / MaxSpeed)); + + leader->followPosRecalcTime = + (level.time) + + (Q_irand(50, 500)) + + (SpeedScale * Q_irand(3000, 8000)) + + ((!actor->enemy && !leader->enemy)?(Q_irand(8000, 15000)):(0)); + } + + if (NAVDEBUG_showEnemyPath) + { + CG_DrawEdge(leader->currentOrigin, leader->followPos, EDGE_FOLLOWPOS); + } + + return 0.0; +} + + +////////////////////////////////////////////////////////////////////// +// Test For A Collision +// +// This is a helper function used by collision avoidance. +// +// Returns true when a collision is detected (not safe) +// +////////////////////////////////////////////////////////////////////// +bool TestCollision(gentity_t* actor, SSteerUser& suser, const CVec3& ProjectVelocity, float ProjectSpeed, ESide Side, float weight=1.0f) +{ + // Test To See If The Projected Position Is Safe + //----------------------------------------------- + bool Safe = (Side==Side_None)?(MoveTrace(actor, suser.mProjectFwd)):(MoveTrace(actor, suser.mProjectSide)); + if (mMoveTrace.entityNum!=ENTITYNUM_NONE && mMoveTrace.entityNum!=ENTITYNUM_WORLD) + { + // The Ignore Entity Is Safe + //--------------------------- + if (mMoveTrace.entityNum==suser.mIgnoreEntity) + { + Safe = true; + } + + // Doors Are Always Safe + //----------------------- + if (g_entities[mMoveTrace.entityNum].classname && + Q_stricmp(g_entities[mMoveTrace.entityNum].classname, "func_door")==0) + { + Safe = true; + } + + // If It's Breakable And We Can Go Through It, Then That's Safe Too + //------------------------------------------------------------------ + if ((actor->NPC->aiFlags&NPCAI_NAV_THROUGH_BREAKABLES) && + G_EntIsBreakable(mMoveTrace.entityNum, actor)) + { + Safe = true; + } + + // TODO: Put Other Ignore Cases Below + //------------------------------------ + } + + // Need These Vectors To Draw The Lines Below + //-------------------------------------------- + CVec3 ContactNormal(mMoveTrace.plane.normal); + CVec3 ContactPoint( mMoveTrace.endpos); + int ContactNum = mMoveTrace.entityNum; + + + if (!Safe && Side==Side_None) + { + // Did We Hit A Client? + //---------------------- + if (ContactNum!=ENTITYNUM_WORLD && ContactNum!=ENTITYNUM_NONE && g_entities[ContactNum].client!=0) + { + gentity_t* Contact = &g_entities[ContactNum]; + //bool ContactIsPlayer = (Contact->client->ps.clientNum==0); + CVec3 ContactVelocity (Contact->client->ps.velocity); + CVec3 ContactPosition (Contact->currentOrigin); + float ContactSpeed = (ContactVelocity.Len()); + + // If He Is Moving, We Might Be Able To Just Slow Down Some And Stay Behind Him + //------------------------------------------------------------------------------ + if (ContactSpeed>0.01f) + { + if (ContactSpeed0.5) + { + // Match Speed + //------------- + suser.mDesiredVelocity = suser.mVelocity; + suser.mDesiredVelocity.Truncate(ContactSpeed); + suser.mSteering += ((suser.mDesiredVelocity - ProjectVelocity) * DirectionSimilarity); + suser.mIgnoreEntity = ContactNum; // So The Side Trace Does Not Care About This Guy + assert(suser.mSteering.IsFinite()); + Safe = true; // We'll Say It's Safe For Now + } + } + } + + // Ok, He's Just Standing There... + //--------------------------------- + else + { + CVec3 Next(suser.mSeekLocation); + if (NAV::HasPath(actor)) + { + Next = CVec3(NAV::NextPosition(actor)); + } + + CVec3 AbsMin(Contact->absmin); + CVec3 AbsMax(Contact->absmax); + + // Is The Contact Standing Over Our Next Position? + //------------------------------------------------- + if (Next>AbsMin && Next0.0f && ContactNormal[2]NPC->lastAvoidSteerSide!=Side_None && actor->NPC->lastAvoidSteerSideDebouncerNPC->lastAvoidSteerSide = Side_None; + actor->NPC->lastAvoidSteerSideDebouncer = level.time + Q_irand(500, STEER::SIDE_LOCKED_TIMER); + } + + // Make Sure The Normal Is Going The Same Way As The Velocity + //----------------------------------------------------------- + if (((ESide)(actor->NPC->lastAvoidSteerSide)==Side_Right) || + ((ESide)(actor->NPC->lastAvoidSteerSide)==Side_None && ContactNormal.Dot(ProjectDirection)<0.0f)) + { + ContactNormal *= -1.0f; + actor->NPC->lastAvoidSteerSide = Side_Right; + } + else + { + actor->NPC->lastAvoidSteerSide = Side_Left; + } + ContactNormal[2] = 0.0f; + + // Scale Up The Normal A Bit And Translate The Contact Point Out + //--------------------------------------------------------------- + ContactNormal *= (ProjectSpeed * weight * 0.5); + ContactPoint += ContactNormal; + + // Move Toward The Contact Point + //------------------------------- + STEER::Seek(actor, ContactPoint, weight); + } + + // If It Was Safe, Reset Our Avoid Side Data + //------------------------------------------- + else if (Side==Side_None) + { + actor->NPC->lastAvoidSteerSide = Side_None; + } + } + + + + + + // DEBUG GRAPHICS + //================================================================= + if (NAVDEBUG_showCollision) + { + CVec3 Prj((Side==Side_None)?(suser.mProjectFwd):(suser.mProjectSide)); + + if (Safe) + { + CG_DrawEdge(suser.mPosition.v, Prj.v, EDGE_IMPACT_SAFE); // WHITE LINE + } + else + { + CG_DrawEdge(suser.mPosition.v, mMoveTrace.endpos, EDGE_IMPACT_POSSIBLE); // RED LINE + CG_DrawEdge(mMoveTrace.endpos, ContactPoint.v, EDGE_IMPACT_POSSIBLE); // RED LINE + } + } + //================================================================= + + return !Safe; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Collision Avoidance +// +// Usually the last steering operation to call before finialization, this operation +// attempts to avoid collisions with nearby entities and architecture by thrusing +// away from them. +//////////////////////////////////////////////////////////////////////////////////// +float STEER::AvoidCollisions(gentity_t* actor, gentity_t* leader) +{ + assert(Active(actor) && actor && actor->client); + SSteerUser& suser = mSteerUsers[mSteerUserIndex[actor->s.number]]; + + suser.mIgnoreEntity = -5; + + + // Simulate The Results Of Any Current Steering To The Velocity + //-------------------------------------------------------------- + CVec3 ProjectedSteering(suser.mSteering); + CVec3 ProjectedVelocity(suser.mVelocity); + float ProjectedSpeed = suser.mSpeed; + float Newtons; + + Newtons = ProjectedSteering.Truncate(suser.mMaxForce); + if (Newtons>1E-10) + { + ProjectedSteering /= suser.mMass; + ProjectedVelocity += ProjectedSteering; + ProjectedSpeed = ProjectedVelocity.Truncate(suser.mMaxSpeed); + } + + + // Select An Ignore Entity + //------------------------- + if (actor->NPC->behaviorState!=BS_CINEMATIC) + { + if (actor->NPC->greetEnt && actor->NPC->greetEnt->owner==NPC) + { + suser.mIgnoreEntity = actor->NPC->greetEnt->s.clientNum; + } + else if (actor->enemy) + { + suser.mIgnoreEntity = actor->enemy->s.clientNum; + } + else if (leader) + { + suser.mIgnoreEntity = leader->s.clientNum; + } + } + + + // If Moving + //----------- + if (ProjectedSpeed>0.01f) + { + CVec3 ProjectVelocitySide(ProjectedVelocity); + ProjectVelocitySide.Reposition(CVec3::mZero, (actor->NPC->avoidSide==Side_Left)?(40.0f):(-40.0f)); + + // Project Based On Our ProjectedVelocity + //----------------------------------------- + suser.mProjectFwd = suser.mPosition + (ProjectedVelocity * 1.0f); + suser.mProjectSide = suser.mPosition + (ProjectVelocitySide * 0.3f); + + // Test For Collisions In The Front And On The Sides + //--------------------------------------------------- + bool HitFront = TestCollision(actor, suser, ProjectedVelocity, ProjectedSpeed, Side_None, 1.0f); + bool HitSide = TestCollision(actor, suser, ProjectedVelocity, ProjectedSpeed, (ESide)actor->NPC->avoidSide, 0.5f); + if (!HitSide) + { + // If The Side Is Clear, Try The Other Side + //------------------------------------------ + actor->NPC->avoidSide = (actor->NPC->avoidSide==Side_Left)?(Side_Right):(Side_Left); + } + + // Hit Something! + //---------------- + if (HitFront || HitSide) + { + return ProjectedSpeed; + } + } + return 0.0f; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Reached +//////////////////////////////////////////////////////////////////////////////////// +bool STEER::Reached(gentity_t* actor, gentity_t* target, float targetRadius, bool flying) +{ + if (!actor || !target) + { + return false; + } + + CVec3 ActorPos(actor->currentOrigin); + CVec3 ActorMins(actor->absmin); + CVec3 ActorMaxs(actor->absmax); + + CVec3 TargetPos(target->currentOrigin); + + if (TargetPos.Dist2(ActorPos)<(targetRadius*targetRadius) || (TargetPos>ActorMins && TargetPoscurrentOrigin); + CVec3 ActorMins(actor->absmin); + CVec3 ActorMaxs(actor->absmax); + + CVec3 TargetPos(NAV::GetNodePosition(target)); + + if (TargetPos.Dist2(ActorPos)<(targetRadius*targetRadius) || (TargetPos>ActorMins && TargetPoscurrentOrigin); + CVec3 ActorMins(actor->absmin); + CVec3 ActorMaxs(actor->absmax); + + CVec3 TargetPos(target); + + if (TargetPos.Dist2(ActorPos)<(targetRadius*targetRadius) || (TargetPos>ActorMins && TargetPosclient && target->client->ps.groundEntityNum == ENTITYNUM_NONE) +// { +// TargetPos -= ActorPos; +// if (fabsf(TargetPos[2]<(targetRadius*8))) +// { +// TargetPos[2] = 0.0f; +// if (TargetPos.Len2()<((targetRadius*2.0f)*(targetRadius*2.0f))) +// { +// return true; +// } +// } +// } + + return false; +} + + + +// Clean up all of the krufty structures that only grow, never shrink, eventually +// causing asserts and subsequent memory trashing. +void ClearAllNavStructures(void) +{ + // Goddamn. + mGraph.clear(); + mRegion.clear(); + mCells.clear(); + + mSearch.clear(); + mUser.ClearActor(); + mUser.ClearDangerSpot(); + + mNodeNames.clear(); + TEntEdgeMap::iterator i = mEntEdgeMap.begin(); + for ( ; i != mEntEdgeMap.end(); i++) + { + i->clear(); + } + mEntEdgeMap.clear(); + + + mNearestNavSort.clear(); + + mPathUsers.clear(); + mPathUserIndex.clear(); + + mSteerUsers.clear(); + mSteerUserIndex.clear(); + + mEntityAlertList.clear(); + + mMoveTraceCount = 0; + mViewTraceCount = 0; + mConnectTraceCount = 0; + mConnectTime = 0; + mIslandCount = 0; + mIslandRegion = 0; + mAirRegion = 0; + mLocStringA[0] = 0; + mLocStringB[0] = 0; +} + + + + + + + diff --git a/code/game/g_navigator.h b/code/game/g_navigator.h new file mode 100644 index 0000000..6e847af --- /dev/null +++ b/code/game/g_navigator.h @@ -0,0 +1,269 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN SOFTWARE - STAR WARS: JK II +// (c) 2002 Activision +// +// +// +// NAVIGATOR +// --------- +// This file provides an interface to two actor related systems: +// - Path Finding +// - Steering +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#ifndef __G_NAVIGATOR__ +#define __G_NAVIGATOR__ + + +#define USENEWNAVSYSTEM 1 + + +#if !defined(RAVL_VEC_INC) + #include "..\Ravl\CVec.h" +#endif + + +//////////////////////////////////////////////////////////////////////////////////////// +// The NAV Namespace +// +// This namespace provides the public interface to the NPC Navigation and Pathfinding +// system. This system is a bidirectional graph of nodes and weighted edges. Finding +// a path from one node to another is accomplished with A*, and cached internally for +// each actor who requests a path. +//////////////////////////////////////////////////////////////////////////////////////// +namespace NAV +{ + typedef int TNodeHandle; + typedef int TEdgeHandle; + + + enum EPointType + { + PT_NONE = 0, + + PT_WAYNODE, + PT_COMBATNODE, + PT_GOALNODE, + + PT_MAX + }; + + + + + //////////////////////////////////////////////////////////////////////////////////// + // Save, Load, Construct + //////////////////////////////////////////////////////////////////////////////////// + bool LoadFromFile(const char *filename, int checksum); + bool TestEdge( TNodeHandle NodeA, TNodeHandle NodeB, qboolean IsDebugEdge ); + bool LoadFromEntitiesAndSaveToFile(const char *filename, int checksum); + void SpawnedPoint(gentity_t* ent, EPointType type=PT_WAYNODE); + + + //////////////////////////////////////////////////////////////////////////////////// + // Finding Nav Points + //////////////////////////////////////////////////////////////////////////////////// + TNodeHandle GetNearestNode(gentity_t* ent, bool forceRecalcNow=false, NAV::TNodeHandle goal=0); + TNodeHandle GetNearestNode(const vec3_t& position, TNodeHandle previous=0, NAV::TNodeHandle goal=0, int ignoreEnt=ENTITYNUM_NONE, bool allowZOffset=false); + + TNodeHandle ChooseRandomNeighbor(TNodeHandle NodeHandle); + TNodeHandle ChooseRandomNeighbor(TNodeHandle NodeHandle, const vec3_t& position, float maxDistance); + TNodeHandle ChooseClosestNeighbor(TNodeHandle NodeHandle, const vec3_t& position); + TNodeHandle ChooseFarthestNeighbor(TNodeHandle NodeHandle, const vec3_t& position); + TNodeHandle ChooseFarthestNeighbor(gentity_t* actor, const vec3_t& target, float maxSafeDot); + + + //////////////////////////////////////////////////////////////////////////////////// + // Get The Location Of A Given Node Handle + //////////////////////////////////////////////////////////////////////////////////// + const vec3_t& GetNodePosition(TNodeHandle NodeHandle); + void GetNodePosition(TNodeHandle NodeHandle, vec3_t& position); + + + //////////////////////////////////////////////////////////////////////////////////// + // Testing Nearness + //////////////////////////////////////////////////////////////////////////////////// + float EstimateCostToGoal(const vec3_t& position, TNodeHandle Goal); + float EstimateCostToGoal(TNodeHandle Start, TNodeHandle Goal); + + bool OnSamePoint(gentity_t* actor, gentity_t* target); + bool OnNeighboringPoints(TNodeHandle A, TNodeHandle B); + bool OnNeighboringPoints(gentity_t* actor, gentity_t* target); + bool OnNeighboringPoints(gentity_t* actor, const vec3_t& position); + bool InSameRegion(gentity_t* actor, gentity_t* target); + bool InSameRegion(gentity_t* actor, const vec3_t& position); + bool InSameRegion(TNodeHandle A, TNodeHandle B); + + bool InSafeRadius(CVec3 at, TNodeHandle atNode, TNodeHandle targetNode=0); + + //////////////////////////////////////////////////////////////////////////////////// + // Finding A Path + //////////////////////////////////////////////////////////////////////////////////// + bool GoTo(gentity_t* actor, TNodeHandle target, float MaxDangerLevel=1.0f); + bool GoTo(gentity_t* actor, gentity_t* target, float MaxDangerLevel=1.0f); + bool GoTo(gentity_t* actor, const vec3_t& position, float MaxDangerLevel=1.0f); + + bool FindPath(gentity_t* actor, TNodeHandle target, float MaxDangerLevel=1.0f); + bool FindPath(gentity_t* actor, gentity_t* target, float MaxDangerLevel=1.0f); + bool FindPath(gentity_t* actor, const vec3_t& position, float MaxDangerLevel=1.0f); + + bool SafePathExists(const CVec3& start, const CVec3& stop, const CVec3& danger, float dangerDistSq); + + bool HasPath(gentity_t* actor, TNodeHandle target=PT_NONE); + void ClearPath(gentity_t* actor); + bool UpdatePath(gentity_t* actor, TNodeHandle target=PT_NONE, float MaxDangerLevel=1.0f); + float PathDangerLevel(gentity_t* actor); + int PathNodesRemaining(gentity_t* actor); + + const vec3_t& NextPosition(gentity_t* actor); + bool NextPosition(gentity_t* actor, CVec3& Position); + bool NextPosition(gentity_t* actor, CVec3& Position, float& SlowingRadius, bool& Fly, bool& Jump); + + + //////////////////////////////////////////////////////////////////////////////////// + // Update One Or More Edges As A Result Of An Entity Getting Removed + //////////////////////////////////////////////////////////////////////////////////// + void WayEdgesNowClear(gentity_t* ent); + + + //////////////////////////////////////////////////////////////////////////////////// + // How Big Is The Given Ent + //////////////////////////////////////////////////////////////////////////////////// + unsigned int ClassifyEntSize(gentity_t* ent); + void RegisterDangerSense(gentity_t* actor, int alertEventIndex); + void DecayDangerSenses(); + + + //////////////////////////////////////////////////////////////////////////////////// + // Debugging Information + //////////////////////////////////////////////////////////////////////////////////// + void ShowDebugInfo(const vec3_t& PlayerPosition, TNodeHandle PlayerWaypoint); + void ShowStats(); + + void TeleportTo(gentity_t* actor, const char* pointName); + void TeleportTo(gentity_t* actor, int pointNum); +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The STEER Namespace +// +// These functions allow access to the steering system. +// +// The Reset() and Finalize() functions MUST be called before and after any other steering +// operations. Beyond that, all other steering operations can be called in any order +// and any number of times. Once Finalize() is called, the results of all these +// operations will be summed up and applied as accelleration to the actor's velocity. +//////////////////////////////////////////////////////////////////////////////////////// +namespace STEER +{ + //////////////////////////////////////////////////////////////////////////////////// + // Reset & Finalize + // + // Call these two operations before and after all other STEER operations. They + // clear out and setup the thrust vector for use by the entity. + //////////////////////////////////////////////////////////////////////////////////// + void Activate(gentity_t* actor); + void DeActivate(gentity_t* actor, usercmd_t* ucmd); + bool Active(gentity_t* actor); + + + //////////////////////////////////////////////////////////////////////////////////// + // Master Functions + //////////////////////////////////////////////////////////////////////////////////// + bool GoTo(gentity_t* actor, gentity_t* target, float reachedRadius, bool avoidCollisions=true); + bool GoTo(gentity_t* actor, const vec3_t& position, float reachedRadius, bool avoidCollisions=true); + + bool SafeToGoTo(gentity_t* actor, const vec3_t& targetPosition, int targetNode); + + + //////////////////////////////////////////////////////////////////////////////////// + // Stop + // + // Slow down and come to a stop. + // + //////////////////////////////////////////////////////////////////////////////////// + float Stop(gentity_t* actor, float weight=1.0f); + float MatchSpeed(gentity_t* actor, float speed, float weight=1.0f); + + + //////////////////////////////////////////////////////////////////////////////////// + // Seek & Flee + // + // These two operations form the root of all steering. They do simple + // vector operations and add to the thrust vector. + //////////////////////////////////////////////////////////////////////////////////// + float Seek(gentity_t* actor, const CVec3& pos, float slowingDistance=0.0f, float weight=1.0f, float desiredSpeed=0.0f); + float Flee(gentity_t* actor, const CVec3& pos, float weight=1.0f); + + //////////////////////////////////////////////////////////////////////////////////// + // Persue & Evade + // + // Slightly more complicated than Seek & Flee, these operations predict the position + // of the target entitiy. + //////////////////////////////////////////////////////////////////////////////////// + float Persue(gentity_t* actor, gentity_t* target, float slowingDistance); + float Persue(gentity_t* actor, gentity_t* target, float slowingDistance, float offsetForward, float offsetRight=0.0f, float offsetUp=0.0f, bool relativeToTargetFacing=false); + float Evade(gentity_t* actor, gentity_t* target); + + //////////////////////////////////////////////////////////////////////////////////// + // Separation, Alignment, Cohesion + // + // These standard steering operations will apply thrust to achieve a group oriented + // position or direction. + //////////////////////////////////////////////////////////////////////////////////// + float Separation(gentity_t* actor, float Scale=1.0f); + float Alignment(gentity_t* actor, float Scale=1.0f); + float Cohesion(gentity_t* actor, float Scale=1.0f); + + //////////////////////////////////////////////////////////////////////////////////// + // Wander & Path + // + // By far the most common way to alter a character's thrust, path maintaines motion + // along a navigational path (see NAV namespace), and a random wander path. + //////////////////////////////////////////////////////////////////////////////////// + float Path(gentity_t* actor); + float Wander(gentity_t* actor); + float FollowLeader(gentity_t* actor, gentity_t* leader, float dist); + + + //////////////////////////////////////////////////////////////////////////////////// + // Collision Avoidance + // + // Usually the last steering operation to call before finialization, this operation + // attempts to avoid collisions with nearby entities and architecture by thrusing + // away from them. + //////////////////////////////////////////////////////////////////////////////////// + float AvoidCollisions(gentity_t* actor, gentity_t* leader=0); + gentity_t* SelectLeader(gentity_t* actor); + + //////////////////////////////////////////////////////////////////////////////////// + // Blocked + // + // This function records whether AI is blocked while the steering is active + //////////////////////////////////////////////////////////////////////////////////// + void Blocked(gentity_t* actor, gentity_t* target); + void Blocked(gentity_t* actor, const vec3_t& target); + bool HasBeenBlockedFor(gentity_t* actor, int duration); + + + //////////////////////////////////////////////////////////////////////////////////// + // Reached + // + // A quick function to see if a target location has been reached by an actor + //////////////////////////////////////////////////////////////////////////////////// + bool Reached(gentity_t* actor, gentity_t* target, float targetRadius, bool flying=false); + bool Reached(gentity_t* actor, NAV::TNodeHandle target, float targetRadius, bool flying=false); + bool Reached(gentity_t* actor, const vec3_t& target, float targetRadius, bool flying=false); +} + + + + + +#endif //__G_NAVIGATOR__ \ No newline at end of file diff --git a/code/game/g_navnew.cpp b/code/game/g_navnew.cpp new file mode 100644 index 0000000..d25d387 --- /dev/null +++ b/code/game/g_navnew.cpp @@ -0,0 +1,227 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +extern qboolean G_EntIsUnlockedDoor( int entityNum ); +extern qboolean FlyingCreature( gentity_t *ent ); + +#define MIN_DOOR_BLOCK_DIST 16 +#define MIN_DOOR_BLOCK_DIST_SQR ( MIN_DOOR_BLOCK_DIST * MIN_DOOR_BLOCK_DIST ) +/* +------------------------- +NAV_HitNavGoal +------------------------- +*/ + +qboolean NAV_HitNavGoal( vec3_t point, vec3_t mins, vec3_t maxs, vec3_t dest, int radius, qboolean flying ) +{ + vec3_t dmins, dmaxs, pmins, pmaxs; + + if ( radius ) + { + radius; + //NOTE: This needs to do a DistanceSquared on navgoals that had + // a radius manually set! We can't do the smaller navgoals against + // walls to get around this because player-sized traces to them + // from angles will not work... - MCG + if ( !flying ) + {//Allow for a little z difference + vec3_t diff; + VectorSubtract( point, dest, diff ); + if ( fabs(diff[2]) <= 24 ) + { + diff[2] = 0; + } + return ( VectorLengthSquared( diff ) <= (radius*radius) ); + } + else + {//must hit exactly + return ( DistanceSquared(dest, point) <= (radius*radius) ); + } + //There is probably a better way to do this, either by preserving the original + // mins and maxs of the navgoal and doing this check ONLY if the radius + // is non-zero (like the original implementation) or some boolean to + // tell us to do this check rather than the fake bbox overlap check... + } + else + { + //Construct a dummy bounding box from our radius value + VectorSet( dmins, -radius, -radius, -radius ); + VectorSet( dmaxs, radius, radius, radius ); + + //Translate it + VectorAdd( dmins, dest, dmins ); + VectorAdd( dmaxs, dest, dmaxs ); + + //Translate the starting box + VectorAdd( point, mins, pmins ); + VectorAdd( point, maxs, pmaxs ); + + //See if they overlap + return G_BoundsOverlap( pmins, pmaxs, dmins, dmaxs ); + } +} + +/* +------------------------- +NAV_CheckAhead +------------------------- +*/ + +qboolean NAV_CheckAhead( gentity_t *self, vec3_t end, trace_t &trace, int clipmask ) +{ + vec3_t mins; + + //Offset the step height + VectorSet( mins, self->mins[0], self->mins[1], self->mins[2] + STEPSIZE ); + + gi.trace( &trace, self->currentOrigin, mins, self->maxs, end, self->s.number, clipmask ); + + if ( trace.startsolid&&(trace.contents&CONTENTS_BOTCLIP) ) + {//started inside do not enter, so ignore them + clipmask &= ~CONTENTS_BOTCLIP; + gi.trace( &trace, self->currentOrigin, mins, self->maxs, end, self->s.number, clipmask ); + } + //Do a simple check + if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) ) + return qtrue; + + //See if we're too far above + if ( fabs( self->currentOrigin[2] - end[2] ) > 48 ) + return qfalse; + + //This is a work around + float radius = ( self->maxs[0] > self->maxs[1] ) ? self->maxs[0] : self->maxs[1]; + float dist = Distance( self->currentOrigin, end ); + float tFrac = 1.0f - ( radius / dist ); + + if ( trace.fraction >= tFrac ) + return qtrue; + + //Do a special check for doors + if ( trace.entityNum < ENTITYNUM_WORLD ) + { + gentity_t *blocker = &g_entities[trace.entityNum]; + + if VALIDSTRING( blocker->classname ) + { + if ( G_EntIsUnlockedDoor( blocker->s.number ) ) + //if ( Q_stricmp( blocker->classname, "func_door" ) == 0 ) + { + //We're too close, try and avoid the door (most likely stuck on a lip) + if ( DistanceSquared( self->currentOrigin, trace.endpos ) < MIN_DOOR_BLOCK_DIST_SQR ) + return qfalse; + + return qtrue; + } + } + } + + return qfalse; +} + +/* +------------------------- +NPC_ClearPathToGoal +------------------------- +*/ + +qboolean NPC_ClearPathToGoal( vec3_t dir, gentity_t *goal ) +{ + trace_t trace; + + //FIXME: What does do about area portals? THIS IS BROKEN + //if ( gi.inPVS( NPC->currentOrigin, goal->currentOrigin ) == qfalse ) + // return qfalse; + + //Look ahead and see if we're clear to move to our goal position + if ( NAV_CheckAhead( NPC, goal->currentOrigin, trace, ( NPC->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) ) + { + //VectorSubtract( goal->currentOrigin, NPC->currentOrigin, dir ); + return qtrue; + } + + if (!FlyingCreature(NPC)) + { + //See if we're too far above + if ( fabs( NPC->currentOrigin[2] - goal->currentOrigin[2] ) > 48 ) + return qfalse; + } + + //This is a work around + float radius = ( NPC->maxs[0] > NPC->maxs[1] ) ? NPC->maxs[0] : NPC->maxs[1]; + float dist = Distance( NPC->currentOrigin, goal->currentOrigin ); + float tFrac = 1.0f - ( radius / dist ); + + if ( trace.fraction >= tFrac ) + return qtrue; + + //See if we're looking for a navgoal + if ( goal->svFlags & SVF_NAVGOAL ) + { + //Okay, didn't get all the way there, let's see if we got close enough: + if ( NAV_HitNavGoal( trace.endpos, NPC->mins, NPC->maxs, goal->currentOrigin, NPCInfo->goalRadius, FlyingCreature( NPC ) ) ) + { + //VectorSubtract(goal->currentOrigin, NPC->currentOrigin, dir); + return qtrue; + } + } + + return qfalse; +} + +qboolean NAV_DirSafe( gentity_t *self, vec3_t dir, float dist ) +{ + vec3_t mins, end; + trace_t trace; + + VectorMA( self->currentOrigin, dist, dir, end ); + + //Offset the step height + VectorSet( mins, self->mins[0], self->mins[1], self->mins[2] + STEPSIZE ); + + gi.trace( &trace, self->currentOrigin, mins, self->maxs, end, self->s.number, CONTENTS_BOTCLIP ); + + //Do a simple check + if ( ( trace.allsolid == qfalse ) && ( trace.startsolid == qfalse ) && ( trace.fraction == 1.0f ) ) + { + return qtrue; + } + + return qfalse; +} + +qboolean NAV_MoveDirSafe( gentity_t *self, usercmd_t *cmd, float distScale = 1.0f ) +{ + vec3_t moveDir; + + if ( !self || !self->client ) + { + return qtrue; + } + if ( !self->client->ps.speed ) + { + return qtrue; + } + if ( FlyingCreature( self ) ) + { + return qtrue; + } + if ( VectorCompare( self->client->ps.moveDir, vec3_origin ) ) + {//no movedir, build from cmd + if ( !cmd->forwardmove && !cmd->rightmove ) + {//not moving at all + return qtrue; + } + vec3_t fwd, right, fwdAngs = {0, self->currentAngles[YAW], 0}; + AngleVectors( fwdAngs, fwd, right, NULL ); + VectorScale( fwd, cmd->forwardmove, fwd ); + VectorScale( right, cmd->rightmove, right ); + VectorAdd( fwd, right, moveDir ); + VectorNormalize( moveDir ); + } + else + { + VectorCopy( self->client->ps.moveDir, moveDir ); + } + return (NAV_DirSafe( self, moveDir, (self->client->ps.speed/10.0f)*distScale )); +} diff --git a/code/game/g_object.cpp b/code/game/g_object.cpp new file mode 100644 index 0000000..a76216f --- /dev/null +++ b/code/game/g_object.cpp @@ -0,0 +1,356 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "g_functions.h" + +extern void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg ); +void G_StopObjectMoving( gentity_t *object ); + +/* +================ +G_BounceMissile + +================ +*/ +void G_BounceObject( gentity_t *ent, trace_t *trace ) +{ + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction; + EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + float bounceFactor = 60/ent->mass; + if ( bounceFactor > 1.0f ) + { + bounceFactor = 1.0f; + } + VectorMA( velocity, -2*dot*bounceFactor, trace->plane.normal, ent->s.pos.trDelta ); + + //FIXME: customized or material-based impact/bounce sounds + if ( ent->s.eFlags & EF_BOUNCE_HALF ) + { + VectorScale( ent->s.pos.trDelta, 0.5, ent->s.pos.trDelta ); + + // check for stop + if ( ((trace->plane.normal[2] > 0.7&&g_gravity->value>0) || (trace->plane.normal[2]<-0.7&&g_gravity->value<0)) && ((ent->s.pos.trDelta[2]<40&&g_gravity->value>0)||(ent->s.pos.trDelta[2]>-40&&g_gravity->value<0)) ) //this can happen even on very slightly sloped walls, so changed it from > 0 to > 0.7 + { + //G_SetOrigin( ent, trace->endpos ); + //ent->nextthink = level.time + 500; + ent->s.apos.trType = TR_STATIONARY; + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + VectorCopy( trace->endpos, ent->currentOrigin ); + VectorCopy( trace->endpos, ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; + return; + } + } + + // NEW--It would seem that we want to set our trBase to the trace endpos + // and set the trTime to the actual time of impact.... + // FIXME: Should we still consider adding the normal though?? + VectorCopy( trace->endpos, ent->currentOrigin ); + ent->s.pos.trTime = hitTime; + + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + VectorCopy( trace->plane.normal, ent->pos1 );//??? +} + +/* +================ +G_RunObject + + TODO: When transition to 0 grav, push away from surface you were resting on + TODO: When free-floating in air, apply some friction to your trDelta (based on mass?) +================ +*/ +extern void DoImpact( gentity_t *self, gentity_t *other, qboolean damageSelf, trace_t *trace ); +void G_RunObject( gentity_t *ent ) +{ + vec3_t origin, oldOrg; + trace_t tr; + gentity_t *traceEnt = NULL; + + //FIXME: floaters need to stop floating up after a while, even if gravity stays negative? + if ( ent->s.pos.trType == TR_STATIONARY )//g_gravity->value <= 0 && + { + ent->s.pos.trType = TR_GRAVITY; + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + ent->s.pos.trTime = level.previousTime;//?necc? + if ( !g_gravity->value ) + { + ent->s.pos.trDelta[2] += 100; + } + } + + ent->nextthink = level.time + FRAMETIME; + + VectorCopy( ent->currentOrigin, oldOrg ); + // get current position + EvaluateTrajectory( &ent->s.pos, level.time, origin ); + //Get current angles? + EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles ); + + if ( VectorCompare( ent->currentOrigin, origin ) ) + {//error - didn't move at all! + return; + } + // trace a line from the previous position to the current position, + // ignoring interactions with the missile owner + gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, + ent->owner ? ent->owner->s.number : ent->s.number, ent->clipmask ); + + if ( !tr.startsolid && !tr.allsolid && tr.fraction ) + { + VectorCopy( tr.endpos, ent->currentOrigin ); + gi.linkentity( ent ); + } + else + //if ( tr.startsolid ) + { + tr.fraction = 0; + } + + G_MoverTouchPushTriggers( ent, oldOrg ); + /* + if ( !(ent->s.eFlags & EF_TELEPORT_BIT) && !(ent->svFlags & SVF_NO_TELEPORT) ) + { + G_MoverTouchTeleportTriggers( ent, oldOrg ); + if ( ent->s.eFlags & EF_TELEPORT_BIT ) + {//was teleported + return; + } + } + else + { + ent->s.eFlags &= ~EF_TELEPORT_BIT; + } + */ + + if ( tr.fraction == 1 ) + { + if ( g_gravity->value <= 0 ) + { + if ( ent->s.apos.trType == TR_STATIONARY ) + { + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + ent->s.apos.trType = TR_LINEAR; + ent->s.apos.trDelta[1] = Q_flrand( -300, 300 ); + ent->s.apos.trDelta[0] = Q_flrand( -10, 10 ); + ent->s.apos.trDelta[2] = Q_flrand( -10, 10 ); + ent->s.apos.trTime = level.time; + } + } + //friction in zero-G + if ( !g_gravity->value ) + { + float friction = 0.975f; + /*friction -= ent->mass/1000.0f; + if ( friction < 0.1 ) + { + friction = 0.1f; + } + */ + VectorScale( ent->s.pos.trDelta, friction, ent->s.pos.trDelta ); + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; + } + return; + } + + //hit something + + //Do impact damage + traceEnt = &g_entities[tr.entityNum]; + if ( tr.fraction || (traceEnt && traceEnt->takedamage) ) + { + if ( !VectorCompare( ent->currentOrigin, oldOrg ) ) + {//moved and impacted + if ( (traceEnt && traceEnt->takedamage) ) + {//hurt someone + vec3_t fxDir; + VectorNormalize2( ent->s.pos.trDelta, fxDir ); + VectorScale( fxDir, -1, fxDir ); + G_PlayEffect( G_EffectIndex( "melee/kick_impact" ), tr.endpos, fxDir ); + //G_Sound( ent, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + } + else + { + G_PlayEffect( G_EffectIndex( "melee/kick_impact_silent" ), tr.endpos, tr.plane.normal ); + } + if ( ent->mass > 100 ) + { + G_Sound( ent, G_SoundIndex( "sound/movers/objects/objectHitHeavy.wav" ) ); + } + else + { + G_Sound( ent, G_SoundIndex( "sound/movers/objects/objectHit.wav" ) ); + } + } + DoImpact( ent, traceEnt, !(tr.surfaceFlags&SURF_NODAMAGE), &tr ); + } + + if ( !ent || (ent->takedamage&&ent->health <= 0) ) + {//been destroyed by impact + //chunks? + G_Sound( ent, G_SoundIndex( "sound/movers/objects/objectBreak.wav" ) ); + return; + } + + //do impact physics + if ( ent->s.pos.trType == TR_GRAVITY )//tr.fraction < 1.0 && + {//FIXME: only do this if no trDelta + if ( g_gravity->value <= 0 || tr.plane.normal[2] < 0.7 ) + { + if ( ent->s.eFlags&(EF_BOUNCE|EF_BOUNCE_HALF) ) + { + if ( tr.fraction <= 0.0f ) + { + VectorCopy( tr.endpos, ent->currentOrigin ); + VectorCopy( tr.endpos, ent->s.pos.trBase ); + VectorClear( ent->s.pos.trDelta ); + ent->s.pos.trTime = level.time; + } + else + { + G_BounceObject( ent, &tr ); + } + } + else + {//slide down? + //FIXME: slide off the slope + } + } + else + { + ent->s.apos.trType = TR_STATIONARY; + pitch_roll_for_slope( ent, tr.plane.normal ); + //ent->currentAngles[0] = 0;//FIXME: match to slope + //ent->currentAngles[2] = 0;//FIXME: match to slope + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + //okay, we hit the floor, might as well stop or prediction will + //make us go through the floor! + //FIXME: this means we can't fall if something is pulled out from under us... + G_StopObjectMoving( ent ); + } + } + else + { + ent->s.apos.trType = TR_STATIONARY; + pitch_roll_for_slope( ent, tr.plane.normal ); + //ent->currentAngles[0] = 0;//FIXME: match to slope + //ent->currentAngles[2] = 0;//FIXME: match to slope + VectorCopy( ent->currentAngles, ent->s.apos.trBase ); + } + + //call touch func + GEntity_TouchFunc( ent, &g_entities[tr.entityNum], &tr ); +} + +void G_StopObjectMoving( gentity_t *object ) +{ + object->s.pos.trType = TR_STATIONARY; + VectorCopy( object->currentOrigin, object->s.origin ); + VectorCopy( object->currentOrigin, object->s.pos.trBase ); + VectorClear( object->s.pos.trDelta ); + + /* + //Stop spinning + VectorClear( self->s.apos.trDelta ); + vectoangles(trace->plane.normal, self->s.angles); + VectorCopy(self->s.angles, self->currentAngles ); + VectorCopy(self->s.angles, self->s.apos.trBase); + */ +} + +void G_StartObjectMoving( gentity_t *object, vec3_t dir, float speed, trType_t trType ) +{ + VectorNormalize (dir); + + //object->s.eType = ET_GENERAL; + object->s.pos.trType = trType; + VectorCopy( object->currentOrigin, object->s.pos.trBase ); + VectorScale(dir, speed, object->s.pos.trDelta ); + object->s.pos.trTime = level.time; + + /* + //FIXME: incorporate spin? + vectoangles(dir, object->s.angles); + VectorCopy(object->s.angles, object->s.apos.trBase); + VectorSet(object->s.apos.trDelta, 300, 0, 0 ); + object->s.apos.trTime = level.time; + */ + + //FIXME: make these objects go through G_RunObject automatically, like missiles do + if ( object->e_ThinkFunc == thinkF_NULL ) + { + object->nextthink = level.time + FRAMETIME; + object->e_ThinkFunc = thinkF_G_RunObject; + } + else + {//You're responsible for calling RunObject + } +} + +gentity_t *G_CreateObject ( gentity_t *owner, vec3_t origin, vec3_t angles, int modelIndex, int frame, trType_t trType, int effectID = 0 ) +{ + gentity_t *object; + + object = G_Spawn(); + + if ( object == NULL ) + { + return NULL; + } + + object->classname = "object";//? + object->nextthink = level.time + FRAMETIME; + object->e_ThinkFunc = thinkF_G_RunObject; + object->s.eType = ET_GENERAL; + object->s.eFlags |= EF_AUTO_SIZE;//CG_Ents will create the mins & max itself based on model bounds + object->s.modelindex = modelIndex; + //FIXME: allow to set a targetname/script_targetname and animation info? + object->s.frame = object->startFrame = object->endFrame = frame; + object->owner = owner; + //object->damage = 100; + //object->splashDamage = 200; + //object->splashRadius = 200; + //object->methodOfDeath = MOD_EXPLOSIVE; + //object->splashMethodOfDeath = MOD_EXPLOSIVE_SPLASH; + object->clipmask = MASK_SOLID;//? + //object->e_TouchFunc = touchF_charge_stick; + + // The effect to play. + object->count = effectID; + + //Give it SOME size for now + VectorSet( object->mins, -4, -4, -4 ); + VectorSet( object->maxs, 4, 4, 4 ); + + //Origin + G_SetOrigin( object, origin ); + object->s.pos.trType = trType; + VectorCopy( origin, object->s.pos.trBase ); + //Velocity + VectorClear( object->s.pos.trDelta ); + object->s.pos.trTime = level.time; + //VectorScale( dir, 300, object->s.pos.trDelta ); + //object->s.pos.trTime = level.time; + + //Angles + VectorCopy( angles, object->s.angles ); + VectorCopy( object->s.angles, object->s.apos.trBase ); + //Angular Velocity + VectorClear( object->s.apos.trDelta ); + object->s.apos.trTime = level.time; + //VectorSet( object->s.apos.trDelta, 300, 0, 0 ); + //object->s.apos.trTime = level.time; + + gi.linkentity( object ); + + return object; +} diff --git a/code/game/g_objectives.cpp b/code/game/g_objectives.cpp new file mode 100644 index 0000000..03280da --- /dev/null +++ b/code/game/g_objectives.cpp @@ -0,0 +1,85 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +//g_objectives.cpp +//reads in ext_data\objectives.dat to objectives[] + +#include "g_local.h" +#include "g_items.h" + +#define G_OBJECTIVES_CPP + +#include "objectives.h" + +qboolean missionInfo_Updated; + + +/* +============ +OBJ_SetPendingObjectives +============ +*/ +void OBJ_SetPendingObjectives(gentity_t *ent) +{ + int i; + + for (i=0;iclient->sess.mission_objectives[i].status == OBJECTIVE_STAT_PENDING) && + (ent->client->sess.mission_objectives[i].display)) + { + ent->client->sess.mission_objectives[i].status = OBJECTIVE_STAT_FAILED; + } + } +} + +/* +============ +OBJ_SaveMissionObjectives +============ +*/ +void OBJ_SaveMissionObjectives( gclient_t *client ) +{ + gi.AppendToSaveGame('OBJT', client->sess.mission_objectives, sizeof(client->sess.mission_objectives)); +} + + +/* +============ +OBJ_SaveObjectiveData +============ +*/ +void OBJ_SaveObjectiveData(void) +{ + gclient_t *client; + + client = &level.clients[0]; + + OBJ_SaveMissionObjectives( client ); +} + +/* +============ +OBJ_LoadMissionObjectives +============ +*/ +void OBJ_LoadMissionObjectives( gclient_t *client ) +{ + gi.ReadFromSaveGame('OBJT', (void *) &client->sess.mission_objectives, sizeof(client->sess.mission_objectives)); +} + + +/* +============ +OBJ_LoadObjectiveData +============ +*/ +void OBJ_LoadObjectiveData(void) +{ + gclient_t *client; + + client = &level.clients[0]; + + OBJ_LoadMissionObjectives( client ); +} diff --git a/code/game/g_public.h b/code/game/g_public.h new file mode 100644 index 0000000..26f0cd1 --- /dev/null +++ b/code/game/g_public.h @@ -0,0 +1,531 @@ +#ifndef __G_PUBLIC_H__ +#define __G_PUBLIC_H__ +// g_public.h -- game module information visible to server + +#define GAME_API_VERSION 8 + +// entity->svFlags +// the server does not know how to interpret most of the values +// in entityStates (level eType), so the game must explicitly flag +// special server behaviors +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects +#define SVF_INACTIVE 0x00000002 // Can't be used when this flag is on +#define SVF_NPC 0x00000004 +#define SVF_BOT 0x00000008 +#define SVF_PLAYER_USABLE 0x00000010 // player can use this with the use button +#define SVF_BROADCAST 0x00000020 // send to all connected clients +#define SVF_PORTAL 0x00000040 // merge a second pvs at origin2 into snapshots +#define SVF_USE_CURRENT_ORIGIN 0x00000080 // entity->currentOrigin instead of entity->s.origin + // for link position (missiles and movers) +#define SVF_TRIMODEL 0x00000100 // Use a three piece model make up like a player does +#define SVF_OBJECTIVE 0x00000200 // Draw it's name if crosshair comes across it +#define SVF_ANIMATING 0x00000400 // Currently animating from startFrame to endFrame +#define SVF_NPC_PRECACHE 0x00000800 // Tell cgame to precache this spawner's NPC stuff +#define SVF_KILLED_SELF 0x00001000 // ent killed itself in a script, so don't do ICARUS_FreeEnt on it... or else! +#define SVF_NAVGOAL 0x00002000 // Navgoal +#define SVF_NOPUSH 0x00004000 // Can't be pushed around +#define SVF_ICARUS_FREEZE 0x00008000 // NPCs are frozen, ents don't execute ICARUS commands +#define SVF_PLAT 0x00010000 // A func_plat or door acting like one +#define SVF_BBRUSH 0x00020000 // breakable brush +#define SVF_LOCKEDENEMY 0x00040000 // keep current enemy until dead +#define SVF_IGNORE_ENEMIES 0x00080000 // Ignore all enemies +#define SVF_BEAMING 0x00100000 // Being transported +#define SVF_PLAYING_SOUND 0x00200000 // In the middle of playing a sound +#define SVF_CUSTOM_GRAVITY 0x00400000 // Use personal gravity +#define SVF_BROKEN 0x00800000 // A broken misc_model_breakable +#define SVF_NO_TELEPORT 0x01000000 // Will not be teleported +#define SVF_NONNPC_ENEMY 0x02000000 // Not a client/NPC, but can still be considered a hostile lifeform +#define SVF_SELF_ANIMATING 0x04000000 // Ent will control it's animation itself in it's think func +#define SVF_GLASS_BRUSH 0x08000000 // Ent is a glass brush +#define SVF_NO_BASIC_SOUNDS 0x10000000 // Don't load basic custom sound set +#define SVF_NO_COMBAT_SOUNDS 0x20000000 // Don't load combat custom sound set +#define SVF_NO_EXTRA_SOUNDS 0x40000000 // Don't load extra custom sound set +#define SVF_MOVER_ADJ_AREA_PORTALS 0x80000000 // For scripted movers only- must *explicitly instruct* them to affect area portals +//=============================================================== + +//rww - RAGDOLL_BEGIN +class CRagDollUpdateParams; +class CRagDollParams; +//rww - RAGDOLL_END + +typedef struct gentity_s gentity_t; +typedef struct gclient_s gclient_t; + +typedef enum +{ + eNO = 0, + eFULL, + eAUTO, +} SavedGameJustLoaded_e; + + + +#ifndef GAME_INCLUDE + +// the server needs to know enough information to handle collision and snapshot generation + +struct gentity_s { + entityState_t s; // communicated by server to clients + struct playerState_s *client; + qboolean inuse; + qboolean linked; // qfalse if not in any good cluster + + int svFlags; // SVF_NOCLIENT, SVF_BROADCAST, etc + + qboolean bmodel; // if false, assume an explicit mins / maxs bounding box + // only set by gi.SetBrushModel + vec3_t mins, maxs; + int contents; // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc + // a non-solid entity should set to 0 + + vec3_t absmin, absmax; // derived from mins/maxs and origin + rotation + + // currentOrigin will be used for all collision detection and world linking. + // it will not necessarily be the same as the trajectory evaluation for the current + // time, because each entity must be moved one at a time after time is advanced + // to avoid simultanious collision issues + vec3_t currentOrigin; + vec3_t currentAngles; + + gentity_t *owner; // objects never interact with their owners, to + // prevent player missiles from immediately + // colliding with their owner +/* +Ghoul2 Insert Start +*/ + // this marker thing of Jake's is used for memcpy() length calcs, so don't put any ordinary fields (like above) + // below this point or they won't work, and will mess up all sorts of stuff. + // + CGhoul2Info_v ghoul2; +/* +Ghoul2 Insert End +*/ + // the game dll can add anything it wants after + // this point in the structure +}; + +#endif // GAME_INCLUDE + +//=============================================================== + +#if defined(_XBOX) && !defined(_TRACE_FUNCTOR_T_DEFINED_) +// Function objects to replace the function pointers used for trace +// We can't have default arguments on function pointers, but this allows us to +// do the same thing with minimal impact elsewhere. +struct Trace_Functor_t +{ + typedef void (*trace_func_t)(trace_t *, const vec3_t, const vec3_t, const vec3_t, const vec3_t, + const int, const int, const EG2_Collision, const int); + trace_func_t trace_func; + void operator()( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + const int passEntityNum, const int contentMask, const EG2_Collision eG2TraceType = (EG2_Collision)0, const int useLod = 0 ) + { trace_func(results, start, mins, maxs, end, passEntityNum, contentMask, eG2TraceType, useLod); } + const Trace_Functor_t &operator=(trace_func_t traceRHS) + { + trace_func = traceRHS; + return *this; + } +}; + +// Always create this class exactly once +#define _TRACE_FUNCTOR_T_DEFINED_ +#endif + +#ifdef _XBOX +// Declare all the functions that we use in the inlined member functions +/* +extern int G2API_InitGhoul2Model(CGhoul2Info_v &ghoul2, const char *fileName, int modelIndex, qhandle_t customSkin = NULL, + qhandle_t customShader = NULL, int modelFlags = 0, int lodBias = 0); +extern qboolean G2API_SetSkin(CGhoul2Info *ghlInfo, qhandle_t customSkin, qhandle_t renderSkin = 0); +extern qboolean G2API_SetBoneAnim(CGhoul2Info *ghlInfo, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame = -1, const int blendTime = -1); +extern qboolean G2API_SetBoneAngles(CGhoul2Info *ghlInfo, const char *boneName, const vec3_t angles, const int flags, + const Eorientations up, const Eorientations right, const Eorientations forward, qhandle_t *modelList, + int blendTime = 0, int currentTime = 0); +extern qboolean G2API_SetBoneAnglesMatrix(CGhoul2Info *ghlInfo, const char *boneName, const mdxaBone_t &matrix, const int flags, + qhandle_t *modelList, int blendTime = 0, int currentTime = 0); +extern void G2API_CopyGhoul2Instance(CGhoul2Info_v &Ghoul2From, CGhoul2Info_v &Ghoul2To, int modelIndex = -1); +extern qboolean G2API_SetBoneAnglesIndex(CGhoul2Info *ghlInfo, const int index, const vec3_t angles, const int flags, + const Eorientations yaw, const Eorientations pitch, const Eorientations roll, + qhandle_t *modelList, int blendTime, int currentTime); +extern qboolean G2API_SetBoneAnimIndex(CGhoul2Info *ghlInfo, const int index, const int startFrame, const int endFrame, const int flags, const float animSpeed, const int currentTime, const float setFrame, const int blendTime); +*/ +#include "../ghoul2/g2.h" +extern int SG_Read (unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr); +extern int SG_ReadOptional (unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr); +#endif + +// +// functions provided by the main engine +// +/* +Ghoul2 Insert Start +*/ +class CMiniHeap; +/* +Ghoul2 Insert End +*/ +typedef struct { + //============== general Quake services ================== + + // print message on the local console + void (*Printf)( const char *fmt, ... ); + + // Write a camera ref_tag to cameras.map + void (*WriteCam)( const char *text ); + void (*FlushCamFile)(); + + // abort the game + void (*Error)( int, const char *fmt, ... ); + + // get current time for profiling reasons + // this should NOT be used for any game related tasks, + // because it is not journaled + int (*Milliseconds)( void ); + + // console variable interaction + cvar_t *(*cvar)( const char *var_name, const char *value, int flags ); + void (*cvar_set)( const char *var_name, const char *value ); + int (*Cvar_VariableIntegerValue)( const char *var_name ); + void (*Cvar_VariableStringBuffer)( const char *var_name, char *buffer, int bufsize ); + + // ClientCommand and ServerCommand parameter access + int (*argc)( void ); + char *(*argv)( int n ); + + int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); + int (*FS_Read)( void *buffer, int len, fileHandle_t f ); + int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); + void (*FS_FCloseFile)( fileHandle_t f ); + int (*FS_ReadFile)( const char *name, void **buf ); + void (*FS_FreeFile)( void *buf ); + int (*FS_GetFileList)( const char *path, const char *extension, char *listbuf, int bufsize ); + + // Savegame handling + // + qboolean (*AppendToSaveGame)(unsigned long chid, const void *data, int length); +#ifdef _XBOX // No default arguments through function pointers + int ReadFromSaveGame(unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr = NULL) + { + return SG_Read(chid, pvAddress, iLength, ppvAddressPtr); + } + + int ReadFromSaveGameOptional(unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr = NULL) + { + return SG_ReadOptional(chid, pvAddress, iLength, ppvAddressPtr); + } +#else + int (*ReadFromSaveGame)(unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr = NULL); + int (*ReadFromSaveGameOptional)(unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr = NULL); +#endif + // add commands to the console as if they were typed in + // for map changing, etc + void (*SendConsoleCommand)( const char *text ); + + + //=========== server specific functionality ============= + + // kick a client off the server with a message + void (*DropClient)( int clientNum, const char *reason ); + + // reliably sends a command string to be interpreted by the given + // client. If clientNum is -1, it will be sent to all clients + void (*SendServerCommand)( int clientNum, const char *fmt, ... ); + + // config strings hold all the index strings, and various other information + // that is reliably communicated to all clients + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + // All confgstrings are cleared at each level start. + void (*SetConfigstring)( int num, const char *string ); + void (*GetConfigstring)( int num, char *buffer, int bufferSize ); + + // userinfo strings are maintained by the server system, so they + // are persistant across level loads, while all other game visible + // data is completely reset + void (*GetUserinfo)( int num, char *buffer, int bufferSize ); + void (*SetUserinfo)( int num, const char *buffer ); + + // the serverinfo info string has all the cvars visible to server browsers + void (*GetServerinfo)( char *buffer, int bufferSize ); + + // sets mins and maxs based on the brushmodel name + void (*SetBrushModel)( gentity_t *ent, const char *name ); + + // collision detection against all linked entities +#ifdef _XBOX + Trace_Functor_t trace; +#else + void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + const int passEntityNum, const int contentmask , const EG2_Collision eG2TraceType = (EG2_Collision)0, const int useLod = 0); +#endif + + // point contents against all linked entities + int (*pointcontents)( const vec3_t point, int passEntityNum ); + // what contents are on the map? + int (*totalMapContents)(); + + qboolean (*inPVS)( const vec3_t p1, const vec3_t p2 ); + qboolean (*inPVSIgnorePortals)( const vec3_t p1, const vec3_t p2 ); + void (*AdjustAreaPortalState)( gentity_t *ent, 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)( gentity_t *ent ); + void (*unlinkentity)( gentity_t *ent ); // call before removing an interactive entity + + // EntitiesInBox will return brush models based on their bounding box, + // so exact determination must still be done with EntityContact + int (*EntitiesInBox)( const vec3_t mins, const vec3_t maxs, gentity_t **list, int maxcount ); + + // perform an exact check against inline brush models of non-square shape + qboolean (*EntityContact)( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); + + // sound volume values + int *VoiceVolume; + + // dynamic memory allocator for things that need to be freed + void *(*Malloc)( int iSize, memtag_t eTag, qboolean bZeroIt); // see qcommon/tags.h for choices + int (*Free)( void *buf ); + qboolean (*bIsFromZone)( void *buf, memtag_t eTag); // see qcommon/tags.h for choices + +/* +Ghoul2 Insert Start +*/ +qhandle_t (*G2API_PrecacheGhoul2Model)(const char *fileName); + +#ifdef _XBOX // No default arguments through function pointers + int G2API_InitGhoul2Model(CGhoul2Info_v &ghoul2, const char *fileName, int modelIndex, qhandle_t customSkin = NULL, + qhandle_t customShader = NULL, int modelFlags = 0, int lodBias = 0) + { + return ::G2API_InitGhoul2Model(ghoul2, fileName, modelIndex, customSkin, customShader, modelFlags, lodBias); + } + + qboolean G2API_SetSkin(CGhoul2Info *ghlInfo, qhandle_t customSkin, qhandle_t renderSkin = 0 ) + { + return ::G2API_SetSkin(ghlInfo, customSkin, renderSkin); + } + + qboolean G2API_SetBoneAnim(CGhoul2Info *ghlInfo, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame = -1, const int blendTime = -1) + { + return ::G2API_SetBoneAnim(ghlInfo, boneName, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime); + } + + qboolean G2API_SetBoneAngles(CGhoul2Info *ghlInfo, const char *boneName, const vec3_t angles, + const int flags, const Eorientations up, const Eorientations right, const Eorientations forward, + qhandle_t *modelList, int blendTime = 0, int blendStart = 0) + { + return ::G2API_SetBoneAngles(ghlInfo, boneName, angles, flags, up, right, forward, modelList, blendTime, blendStart); + } + + qboolean G2API_SetBoneAnglesMatrix(CGhoul2Info *ghlInfo, const char *boneName, const mdxaBone_t &matrix, const int flags, + qhandle_t *modelList, int blendTime = 0, int currentTime = 0) + { + return ::G2API_SetBoneAnglesMatrix(ghlInfo, boneName, matrix, flags, modelList, blendTime, currentTime); + } + + void G2API_CopyGhoul2Instance(CGhoul2Info_v &ghoul2From, CGhoul2Info_v &ghoul2To, int modelIndex = -1) + { + ::G2API_CopyGhoul2Instance(ghoul2From, ghoul2To, modelIndex); + } + + qboolean G2API_SetBoneAnglesIndex(CGhoul2Info *ghlInfo, const int index, const vec3_t angles, const int flags, + const Eorientations yaw, const Eorientations pitch, const Eorientations roll, + qhandle_t *modelList, int blendTime = 0, int currentTime = 0) + { + return ::G2API_SetBoneAnglesIndex(ghlInfo, index, angles, flags, yaw, pitch, roll, modelList, blendTime, currentTime); + } + + qboolean G2API_SetBoneAnimIndex(CGhoul2Info *ghlInfo, const int index, const int startFrame, const int endFrame, const int flags, const float animSpeed, const int currentTime, const float setFrame = -1, const int blendTime = -1) + { + return ::G2API_SetBoneAnimIndex(ghlInfo, index, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime); + } + +#else + +int (*G2API_InitGhoul2Model)(CGhoul2Info_v &ghoul2, const char *fileName, int modelIndex, qhandle_t customSkin = NULL, + qhandle_t customShader = NULL, int modelFlags = 0, int lodBias = 0); +qboolean (*G2API_SetSkin)(CGhoul2Info *ghlInfo, qhandle_t customSkin, qhandle_t renderSkin = 0 ); +qboolean (*G2API_SetBoneAnim)(CGhoul2Info *ghlInfo, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame = -1, const int blendTime = -1); +qboolean (*G2API_SetBoneAngles)(CGhoul2Info *ghlInfo, const char *boneName, const vec3_t angles, + const int flags, const Eorientations up, const Eorientations right, const Eorientations forward, + qhandle_t *modelList, int blendTime = 0, int blendStart = 0); +qboolean (*G2API_SetBoneAnglesIndex)(CGhoul2Info *ghlInfo, const int index, const vec3_t angles, const int flags, + const Eorientations yaw, const Eorientations pitch, const Eorientations roll, + qhandle_t *modelList, int blendTime = 0, int currentTime = 0); +qboolean (*G2API_SetBoneAnglesMatrix)(CGhoul2Info *ghlInfo, const char *boneName, const mdxaBone_t &matrix, const int flags, + qhandle_t *modelList, int blendTime = 0, int currentTime = 0); +void (*G2API_CopyGhoul2Instance)(CGhoul2Info_v &ghoul2From, CGhoul2Info_v &ghoul2To, int modelIndex = -1); +qboolean (*G2API_SetBoneAnimIndex)(CGhoul2Info *ghlInfo, const int index, const int startFrame, const int endFrame, const int flags, const float animSpeed, const int currentTime, const float setFrame = -1, const int blendTime = -1); +#endif + +qboolean (*G2API_SetLodBias)(CGhoul2Info *ghlInfo, int lodBias); +qboolean (*G2API_SetShader)(CGhoul2Info *ghlInfo, qhandle_t customShader); +qboolean (*G2API_RemoveGhoul2Model)(CGhoul2Info_v &ghlInfo, const int modelIndex); +qboolean (*G2API_SetSurfaceOnOff)(CGhoul2Info *ghlInfo, const char *surfaceName, const int flags); +qboolean (*G2API_SetRootSurface)(CGhoul2Info_v &ghlInfo, const int modelIndex, const char *surfaceName); +qboolean (*G2API_RemoveSurface)(CGhoul2Info *ghlInfo, const int index); +int (*G2API_AddSurface)(CGhoul2Info *ghlInfo, int surfaceNumber, int polyNumber, float BarycentricI, float BarycentricJ, int lod ); +qboolean (*G2API_GetBoneAnim)(CGhoul2Info *ghlInfo, const char *boneName, const int currentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList); +qboolean (*G2API_GetBoneAnimIndex)(CGhoul2Info *ghlInfo, const int iBoneIndex, const int currentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList); +qboolean (*G2API_GetAnimRange)(CGhoul2Info *ghlInfo, const char *boneName, int *startFrame, int *endFrame); +qboolean (*G2API_GetAnimRangeIndex)(CGhoul2Info *ghlInfo, const int boneIndex, int *startFrame, int *endFrame); + +qboolean (*G2API_PauseBoneAnim)(CGhoul2Info *ghlInfo, const char *boneName, const int currentTime); +qboolean (*G2API_PauseBoneAnimIndex)(CGhoul2Info *ghlInfo, const int boneIndex, const int currentTime); +qboolean (*G2API_IsPaused)(CGhoul2Info *ghlInfo, const char *boneName); +qboolean (*G2API_StopBoneAnim)(CGhoul2Info *ghlInfo, const char *boneName); +qboolean (*G2API_StopBoneAngles)(CGhoul2Info *ghlInfo, const char *boneName); +qboolean (*G2API_RemoveBone)(CGhoul2Info *ghlInfo, const char *boneName); +qboolean (*G2API_RemoveBolt)(CGhoul2Info *ghlInfo, const int index); +int (*G2API_AddBolt)(CGhoul2Info *ghlInfo, const char *boneName); +int (*G2API_AddBoltSurfNum)(CGhoul2Info *ghlInfo, const int surfIndex); +qboolean (*G2API_AttachG2Model)(CGhoul2Info *ghlInfo, CGhoul2Info *ghlInfoTo, int toBoltIndex, int toModel); +qboolean (*G2API_DetachG2Model)(CGhoul2Info *ghlInfo); +qboolean (*G2API_AttachEnt)(int *boltInfo, CGhoul2Info *ghlInfoTo, int toBoltIndex, int entNum, int toModelNum); +void (*G2API_DetachEnt)(int *boltInfo); + +qboolean (*G2API_GetBoltMatrix)(CGhoul2Info_v &ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, const vec3_t scale); + +void (*G2API_ListSurfaces)(CGhoul2Info *ghlInfo); +void (*G2API_ListBones)(CGhoul2Info *ghlInfo, int frame); +qboolean (*G2API_HaveWeGhoul2Models)(CGhoul2Info_v &ghoul2); +qboolean (*G2API_SetGhoul2ModelFlags)(CGhoul2Info *ghlInfo, const int flags); +int (*G2API_GetGhoul2ModelFlags)(CGhoul2Info *ghlInfo); + +qboolean (*G2API_GetAnimFileName)(CGhoul2Info *ghlInfo, char **filename); +void (*G2API_CollisionDetect)(CCollisionRecord *collRecMap, CGhoul2Info_v &ghoul2, const vec3_t angles, const vec3_t position, + int frameNumber, int entNum, vec3_t rayStart, vec3_t rayEnd, vec3_t scale, CMiniHeap *G2VertSpace, + EG2_Collision eG2TraceType, int useLod, float fRadius); +void (*G2API_GiveMeVectorFromMatrix)(mdxaBone_t &boltMatrix, Eorientations flags, vec3_t &vec); +void (*G2API_CleanGhoul2Models)(CGhoul2Info_v &ghoul2); +IGhoul2InfoArray & (*TheGhoul2InfoArray)(); +int (*G2API_GetParentSurface)(CGhoul2Info *ghlInfo, const int index); +int (*G2API_GetSurfaceIndex)(CGhoul2Info *ghlInfo, const char *surfaceName); +char *(*G2API_GetSurfaceName)(CGhoul2Info *ghlInfo, int surfNumber); +char *(*G2API_GetGLAName)(CGhoul2Info *ghlInfo); +qboolean (*G2API_SetNewOrigin)(CGhoul2Info *ghlInfo, const int boltIndex); +int (*G2API_GetBoneIndex)(CGhoul2Info *ghlInfo, const char *boneName, qboolean bAddIfNotFound); +qboolean (*G2API_StopBoneAnglesIndex)(CGhoul2Info *ghlInfo, const int index); +qboolean (*G2API_StopBoneAnimIndex)(CGhoul2Info *ghlInfo, const int index); +qboolean (*G2API_SetBoneAnglesMatrixIndex)(CGhoul2Info *ghlInfo, const int index, const mdxaBone_t &matrix, + const int flags, qhandle_t *modelList, int blendTime, int currentTime); +qboolean (*G2API_SetAnimIndex)(CGhoul2Info *ghlInfo, const int index); +int (*G2API_GetAnimIndex)(CGhoul2Info *ghlInfo); +void (*G2API_SaveGhoul2Models)(CGhoul2Info_v &ghoul2); +void (*G2API_LoadGhoul2Models)(CGhoul2Info_v &ghoul2, char *buffer); +void (*G2API_LoadSaveCodeDestructGhoul2Info)(CGhoul2Info_v &ghoul2); +char *(*G2API_GetAnimFileNameIndex)(qhandle_t modelIndex); +char *(*G2API_GetAnimFileInternalNameIndex)(qhandle_t modelIndex); +int (*G2API_GetSurfaceRenderStatus)(CGhoul2Info *ghlInfo, const char *surfaceName); + +//rww - RAGDOLL_BEGIN +void (*G2API_SetRagDoll)(CGhoul2Info_v &ghoul2,CRagDollParams *parms); +void (*G2API_AnimateG2Models)(CGhoul2Info_v &ghoul2, int AcurrentTime,CRagDollUpdateParams *params); + +qboolean (*G2API_RagPCJConstraint)(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t min, vec3_t max); +qboolean (*G2API_RagPCJGradientSpeed)(CGhoul2Info_v &ghoul2, const char *boneName, const float speed); +qboolean (*G2API_RagEffectorGoal)(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t pos); +qboolean (*G2API_GetRagBonePos)(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t pos, vec3_t entAngles, vec3_t entPos, vec3_t entScale); +qboolean (*G2API_RagEffectorKick)(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t velocity); +qboolean (*G2API_RagForceSolve)(CGhoul2Info_v &ghoul2, qboolean force); + +qboolean (*G2API_SetBoneIKState)(CGhoul2Info_v &ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params); +qboolean (*G2API_IKMove) (CGhoul2Info_v &ghoul2, int time, sharedIKMoveParams_t *params); +//rww - RAGDOLL_END + +void (*G2API_AddSkinGore)(CGhoul2Info_v &ghoul2,SSkinGoreData &gore); +void (*G2API_ClearSkinGore)( CGhoul2Info_v &ghoul2 ); + +void (*RMG_Init)(int terrainID); +#ifndef _XBOX +int (*CM_RegisterTerrain)(const char *info); +#endif +const char *(*SetActiveSubBSP)(int index); + + +int (*RE_RegisterSkin)(const char *name); +int (*RE_GetAnimationCFG)(const char *psCFGFilename, char *psDest, int iDestSize); + +bool (*WE_GetWindVector)(vec3_t windVector, vec3_t atpoint); +bool (*WE_GetWindGusting)(vec3_t atpoint); +bool (*WE_IsOutside)(vec3_t pos); +float (*WE_IsOutsideCausingPain)(vec3_t pos); +float (*WE_GetChanceOfSaberFizz)(void); +bool (*WE_IsShaking)(vec3_t pos); +void (*WE_AddWeatherZone)(vec3_t mins, vec3_t maxs); +bool (*WE_SetTempGlobalFogColor)(vec3_t color); + + +/* +Ghoul2 Insert End +*/ + + +} game_import_t; + +// +// functions exported by the game subsystem +// +typedef struct { + int apiversion; + + // init and shutdown will be called every single level + // levelTime will be near zero, while globalTime will be a large number + // that can be used to track spectator entry times across restarts + void (*Init)( const char *mapname, const char *spawntarget, int checkSum, const char *entstring, int levelTime, int randomSeed, int globalTime, SavedGameJustLoaded_e eSavedGameJustLoaded, qboolean qbLoadTransition ); + void (*Shutdown) (void); + + // ReadLevel is called after the default map information has been + // loaded with SpawnEntities + void (*WriteLevel) (qboolean qbAutosave); + void (*ReadLevel) (qboolean qbAutosave, qboolean qbLoadTransition); + qboolean (*GameAllowedToSaveHere)(void); + + // return NULL if the client is allowed to connect, otherwise return + // a text string with the reason for denial + char *(*ClientConnect)( int clientNum, qboolean firstTime, SavedGameJustLoaded_e eSavedGameJustLoaded ); + + void (*ClientBegin)( int clientNum, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded); + void (*ClientUserinfoChanged)( int clientNum ); + void (*ClientDisconnect)( int clientNum ); + void (*ClientCommand)( int clientNum ); + void (*ClientThink)( int clientNum, usercmd_t *cmd ); + + void (*RunFrame)( int levelTime ); + void (*ConnectNavs)( const char *mapname, int checkSum ); + + // ConsoleCommand will be called when a command has been issued + // that is not recognized as a builtin function. + // The game can issue gi.argc() / gi.argv() commands to get the command + // and parameters. Return qfalse if the game doesn't recognize it as a command. + qboolean (*ConsoleCommand)( void ); + + //void (*PrintEntClassname)( int clientNum ); + //int (*ValidateAnimRange)( int startFrame, int endFrame, float animSpeed ); + + void (*GameSpawnRMGEntity)(char *s); + // + // global variables shared between game and server + // + + // The gentities 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 + // the server can't just use pointer arithmetic on gentities, because the + // server's sizeof(struct gentity_s) doesn't equal gentitySize + struct gentity_s *gentities; + int gentitySize; + int num_entities; // current number, <= MAX_GENTITIES +} game_export_t; + +game_export_t *GetGameApi (game_import_t *import); + +#endif//#ifndef __G_PUBLIC_H__ diff --git a/code/game/g_rail.cpp b/code/game/g_rail.cpp new file mode 100644 index 0000000..8fa21c4 --- /dev/null +++ b/code/game/g_rail.cpp @@ -0,0 +1,975 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN SOFTWARE - STAR WARS: JK II +// (c) 2002 Activision +// +// Rail System +// +// The rail system is intended to provide a means for generating moving entities along +// tracks of varying speed and direction. The entities are pulled from the map based +// upon their targets and recycled in random positions and order +// +//////////////////////////////////////////////////////////////////////////////////////// +#include "g_headers.h" + + +//////////////////////////////////////////////////////////////////////////////////////// +// Externs & Fwd Decl. +//////////////////////////////////////////////////////////////////////////////////////// +//extern cvar_t* g_nav1; +extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast ); + +class CRailTrack; +class CRailLane; +class CRailMover; + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#include "b_local.h" +#if !defined(RATL_ARRAY_VS_INC) + #include "..\Ratl\array_vs.h" +#endif +#if !defined(RATL_VECTOR_VS_INC) + #include "..\Ratl\vector_vs.h" +#endif +#if !defined(RAVL_VEC_INC) + #include "..\Ravl\CVec.h" +#endif +#if !defined(RUFL_HSTRING_INC) + #include "..\Rufl\hstring.h" +#endif +#if !defined(RATL_GRID_VS_INC) + #include "..\Ratl\grid_vs.h" +#endif +#if !defined(RATL_POOL_VS_INC) + #include "..\Ratl\pool_vs.h" +#endif + +#ifdef _XBOX +using dllNamespace::hstring; +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// Constants +//////////////////////////////////////////////////////////////////////////////////////// +#define MAX_TRACKS 4 +#define MAX_LANES 8 +#define MAX_MOVERS 150 +#define MAX_MOVER_ENTS 10 +#define MAX_MOVERS_TRACK 80 +#define MAX_COLS 32 +#define MAX_ROWS 96 +#define MAX_ROW_HISTORY 10 + +#define WOOSH_DEBUG 0 +#define WOOSH_ALL_RANGE 1500.0f +#define WOOSH_SUPPORT_RANGE 2500.0f +#define WOOSH_TUNNEL_RANGE 3000.0f + + +bool mRailSystemActive = false; + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Rail Track +// +// Tracks are the central component to the rails system. They provide the master +// repositry of all movers and maintain the list of available movers as well as +// +//////////////////////////////////////////////////////////////////////////////////////// +class CRailTrack +{ +public: + void Setup(gentity_t *ent) + { + mName = ent->targetname; + mSpeedGridCellsPerSecond = ent->speed; + mNumMoversPerRow = ent->count; + mMins = ent->mins; + mMaxs = ent->maxs; + mStartTime = ent->delay + level.time; + mGridCellSize = (ent->radius!=0.0f)?(ent->radius):(1.0f); + mVertical = (ent->s.angles[1]==90.0f || ent->s.angles[1]==270.0f); + mNegative = (ent->s.angles[1]==180.0f || ent->s.angles[1]==270.0f); // From Maxs To Mins + mWAxis = (mVertical)?(0):(1); + mHAxis = (mVertical)?(1):(0); + mTravelDistanceUnits = ent->maxs[mHAxis] - ent->mins[mHAxis]; + + mRow = 0; + mNextUpdateTime = 0; + + mCenterLocked = false; + + SnapVectorToGrid(mMins); + SnapVectorToGrid(mMaxs); + + // Calculate Number Of Rows And Columns + //-------------------------------------- + mRows = ((mMaxs[mHAxis] - mMins[mHAxis]) / mGridCellSize); + mCols = ((mMaxs[mWAxis] - mMins[mWAxis]) / mGridCellSize); + + // Calculate Grid Center + //----------------------- + mGridCenter = ((mMins+mMaxs)*0.5f); + SnapVectorToGrid(mGridCenter); + + // Calculate Speed & Velocity + //---------------------------- + mSpeedUnitsPerMillisecond = mSpeedGridCellsPerSecond * mGridCellSize / 1000.0f; + mTravelTimeMilliseconds = mTravelDistanceUnits / mSpeedUnitsPerMillisecond; + + AngleVectors(ent->s.angles, mDirection.v, 0, 0); + mDirection.SafeNorm(); + mVelocity = mDirection; + mVelocity *= (mSpeedGridCellsPerSecond * mGridCellSize); + + mNextUpdateDelay = 1000.0f / (float)(mSpeedGridCellsPerSecond); + + + // Calculate Bottom Left Corner + //------------------------------ + mGridBottomLeftCorner = ent->mins; + if (ent->s.angles[1]==180.0f) + { + mGridBottomLeftCorner[0] = mMaxs[0]; + } + else if (ent->s.angles[1]==270.0f) + { + mGridBottomLeftCorner[1] = mMaxs[1]; + } + SnapVectorToGrid(mGridBottomLeftCorner); + + + mCells.set_size(mCols/*xSize*/, mRows/*ySize*/); + mCells.init(0); + + mMovers.clear(); + + + if (!mNumMoversPerRow) + { + mNumMoversPerRow = 3; + } + + // Safe Clamp Number Of Rows & Cols + //---------------------------------- + if (mRows>(MAX_ROWS-1)) + { + mRows = (MAX_ROWS-1); + assert(0); + } + if (mCols>(MAX_COLS-1)) + { + mCols = (MAX_COLS-1); + assert(0); + } + } + + void SnapVectorToGrid(CVec3& Vec) + { + SnapFloatToGrid(Vec[0]); + SnapFloatToGrid(Vec[1]); + } + + void SnapFloatToGrid(float& f) + { + f = (int)(f); + + bool fNeg = (f<0); + if (fNeg) + { + f *= -1; // Temporarly make it positive + } + + int Offset = ((int)(f) % (int)(mGridCellSize)); + int OffsetAbs = abs(Offset); + if (OffsetAbs>(mGridCellSize/2)) + { + Offset = (mGridCellSize - OffsetAbs) * -1; + } + + f -= Offset; + + if (fNeg) + { + f *= -1; // Put It Back To Negative + } + + f = (int)(f); + + assert(((int)(f)%(int)(mGridCellSize)) == 0); + } + + + + + + void Update(); + bool TestMoverInCells(CRailMover* mover, int atCol); + void InsertMoverInCells(CRailMover* mover, int atCol); + void RandomizeTestCols(int startCol, int stopCol); + + + + + + + +public: + hstring mName; + + int mRow; + int mNumMoversPerRow; + + int mNextUpdateTime; + int mNextUpdateDelay; + int mStartTime; + + int mRows; + int mCols; + + bool mVertical; + bool mNegative; + int mHAxis; + int mWAxis; + + int mSpeedGridCellsPerSecond; + float mSpeedUnitsPerMillisecond; + int mTravelTimeMilliseconds; + float mTravelDistanceUnits; + CVec3 mDirection; + CVec3 mVelocity; + + CVec3 mMins; + CVec3 mMaxs; + + CVec3 mGridBottomLeftCorner; + CVec3 mGridCenter; + float mGridCellSize; + + bool mCenterLocked; + + ratl::grid2_vs mCells; + ratl::vector_vs mMovers; + ratl::vector_vs mTestCols; +}; +ratl::vector_vs mRailTracks; + +//////////////////////////////////////////////////////////////////////////////////////// +/*QUAKED rail_track (0 .5 .8) ? x x x x x x x x +A rail track determines what location and direction rail_mover entities go. Don't bother with any origin brushes. Make sure to set: + +"radius" Number of units to break down into grid size +"speed" Number of grid sized units per second rail_movers will go at +"angle" The direction rail_movers will go +"count" The number of mover ents the track will try to add per row +"delay" How long the ent will wait from the start of the level before placing movers +*/ +//////////////////////////////////////////////////////////////////////////////////////// +void SP_rail_track(gentity_t *ent) +{ + gi.SetBrushModel(ent, ent->model); + G_SpawnInt("delay", "0", &ent->delay); + mRailTracks.push_back().Setup(ent); + G_FreeEntity(ent); + mRailSystemActive = true; +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Rail Lane +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +class CRailLane +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // From Entity Setup Spawn + //////////////////////////////////////////////////////////////////////////////////// + void Setup(gentity_t* ent) + { + mName = ent->targetname; + mNameTrack = ent->target; + mMins = ent->mins; + mMaxs = ent->maxs; + mStartTime = ent->delay + level.time; + } + + hstring mName; + hstring mNameTrack; + CVec3 mMins; + CVec3 mMaxs; + int mStartTime; + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Initialize + // + // This function scans through the list of tracks and hooks itself up with the + // track + //////////////////////////////////////////////////////////////////////////////////// + void Initialize() + { + mTrack = 0; + mMinCol = 0; + mMaxCol = 0; + +// int dummy; + for (int i=0; iSnapVectorToGrid(mMins); + mTrack->SnapVectorToGrid(mMaxs); + + mMinCol = (int)((mMins[mTrack->mWAxis] - mTrack->mMins[mTrack->mWAxis])/mTrack->mGridCellSize); + mMaxCol = (int)((mMaxs[mTrack->mWAxis] - mTrack->mMins[mTrack->mWAxis] - (mTrack->mGridCellSize/2.0f))/mTrack->mGridCellSize); + + //if (mTrack->mNegative) + //{ + // mMinCol = (mTrack->mCols - mMinCol - 1); + // mMaxCol = (mTrack->mCols - mMaxCol - 1); + //} + + +// mTrack->mCells.get_cell_coords(mMins[mTrack->mWAxis], 0, mMinCol, dummy); +// mTrack->mCells.get_cell_coords((mMaxs[mTrack->mWAxis]-10.0f), 0, mMaxCol, dummy); + break; + } + } + assert(mTrack!=0); + } + + CRailTrack* mTrack; + int mMinCol; + int mMaxCol; +}; +ratl::vector_vs mRailLanes; + +//////////////////////////////////////////////////////////////////////////////////////// +/*QUAKED rail_lane (0 .5 .8) ? x x x x x x x x +Use rail lanes to split up tracks. Just target it to a track that you want to break up into pieces + +"delay" How long the ent will wait from the start of the level before placing movers +*/ +//////////////////////////////////////////////////////////////////////////////////////// +void SP_rail_lane(gentity_t *ent) +{ + gi.SetBrushModel(ent, ent->model); + G_SpawnInt("delay", "0", &ent->delay); + mRailLanes.push_back().Setup(ent); + G_FreeEntity(ent); +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +class CRailMover +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // From Entity Setup Spawn + //////////////////////////////////////////////////////////////////////////////////// + void Setup(gentity_t* ent) + { + mEnt = ent; + mCenter = (ent->spawnflags&1); + mSoundPlayed = false; + mOriginOffset = ent->mins; + mOriginOffset += ent->maxs; + mOriginOffset *= 0.5f; + mOriginOffset[2] = 0;//((ent->maxs[2] - ent->mins[2]) * 0.5); + + ent->e_ReachedFunc = reachedF_NULL; + ent->moverState = MOVER_POS1; + ent->svFlags = SVF_USE_CURRENT_ORIGIN; + ent->s.eType = ET_MOVER; + ent->s.eFlags |= EF_NODRAW; + ent->contents = 0; + ent->clipmask = 0; + + ent->s.pos.trType = TR_STATIONARY; + ent->s.pos.trDuration = 0; + ent->s.pos.trTime = 0; + + VectorCopy( ent->pos1, ent->currentOrigin ); + VectorCopy( ent->pos1, ent->s.pos.trBase ); + + + gi.linkentity(ent); + } + gentity_t* mEnt; + bool mCenter; + CVec3 mOriginOffset; + bool mSoundPlayed; + + + bool Active() + { + assert(mEnt!=0); + return (level.time<(mEnt->s.pos.trDuration + mEnt->s.pos.trTime)); + } + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Initialize + // + // This function scans through the list of tracks and hooks itself up with the + // track (and possibly lane) + //////////////////////////////////////////////////////////////////////////////////// + void Initialize() + { + mTrack = 0; + mLane = 0; + mCols = 0; + mRows = 0; + + hstring target = mEnt->target; + for (int track=0; trackmTrack; + break; + } + } + } + assert(mTrack!=0); + if (mTrack) + { + mTrack->mMovers.push_back(this); + mCols = (int)((mEnt->maxs[mTrack->mWAxis] - mEnt->mins[mTrack->mWAxis]) / mTrack->mGridCellSize) + 1; + mRows = (int)((mEnt->maxs[mTrack->mHAxis] - mEnt->mins[mTrack->mHAxis]) / mTrack->mGridCellSize) + 1; + + // Make Sure The Mover Fits In The Track And Lane + //------------------------------------------------ + if (mRows>mTrack->mRows) + { +// assert(0); + mRows = mTrack->mRows; + } + if (mCols>mTrack->mCols) + { +// assert(0); + mCols = mTrack->mCols; + } + if (mLane && mCols>(mLane->mMaxCol - mLane->mMinCol + 1)) + { +// assert(0); + mCols = (mLane->mMaxCol - mLane->mMinCol + 1); + } + } + } + + CRailTrack* mTrack; + CRailLane* mLane; + int mCols; + int mRows; +}; +ratl::vector_vs mRailMovers; + +//////////////////////////////////////////////////////////////////////////////////////// +/*QUAKED rail_mover (0 .5 .8) ? CENTER x x x x x x x +Rail Mover will go along the track and lane of your choice. Just target it to either a track or a lane. Don't bother with any origin brushes. + +CENTER Will force this mover to attempt to center in the track or lane + +"target" The track or lane you want this entity to move through +"model" A model you wish to use, not necessary - can be just a brush +"angle" Random angle rotation allowable on this thing +*/ +//////////////////////////////////////////////////////////////////////////////////////// +void SP_rail_mover(gentity_t *ent) +{ + gi.SetBrushModel(ent, ent->model); + mRailMovers.push_back().Setup(ent); +} + + +ratl::vector_vs mWooshSml; // Small Building +ratl::vector_vs mWooshMed; // Medium Building +ratl::vector_vs mWooshLar; // Large Building +ratl::vector_vs mWooshSup; // Track Support +ratl::vector_vs mWooshTun; // Tunnel + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Rail_Reset() +{ + mRailSystemActive = false; + mRailTracks.clear(); + mRailLanes.clear(); + mRailMovers.clear(); + + mWooshSml.clear(); + mWooshMed.clear(); + mWooshLar.clear(); + mWooshSup.clear(); + mWooshTun.clear(); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Rail_Initialize() +{ + for (int lane=0; lanemRailTracks[track].mNextUpdateTime && !mRailTracks[track].mMovers.empty()) + { + mRailTracks[track].Update(); + } + } + + // Is The Player Outside? + //------------------------ + if (player && gi.WE_IsOutside(player->currentOrigin)) + { + int wooshSound; + vec3_t wooshSoundPos; + vec3_t moverOrigin; + vec3_t playerToMover; + float playerToMoverDistance; + float playerToMoverDistanceFraction; + + // Iterate Over All The Movers + //----------------------------- + for (int moverIndex=0; moverIndexcurrentOrigin, mover.mOriginOffset.v, moverOrigin); + VectorSubtract(moverOrigin, player->currentOrigin, playerToMover); + playerToMover[2] = 0.0f; + playerToMoverDistance = VectorNormalize(playerToMover); + + + // Is It Close Enough? + //--------------------- + if ((( mover.mLane || !mover.mCenter) && // Not Center Track + (playerToMoverDistancemDirection.v)>-0.45f)) // And On The Side + || //OR + ((!mover.mLane && mover.mCenter) && // Is Center Track + (playerToMoverDistance10)) // Or Close Enough For Tunnel + )) + { + mover.mSoundPlayed = true; + wooshSound = 0; + + // The Centered Entities Play Right On The Player's Head For Full Volume + //----------------------------------------------------------------------- + if (mover.mCenter && !mover.mLane) + { + VectorCopy(player->currentOrigin, wooshSoundPos); + wooshSoundPos[2] += 50; + + // If It Is Very Long, Play The Tunnel Sound + //------------------------------------------- + if (mover.mRows>10) + { + wooshSound = mWooshTun[Q_irand(0, mWooshTun.size()-1)]; + } + + // Otherwise It Is A Support + //--------------------------- + else + { + wooshSound = mWooshSup[Q_irand(0, mWooshSup.size()-1)]; + } + } + + // All Other Entities Play At A Fraction Of Their Normal Range + //------------------------------------------------------------- + else + { + // Scale The Play Pos By The Square Of The Distance + //-------------------------------------------------- + playerToMoverDistanceFraction = playerToMoverDistance/WOOSH_ALL_RANGE; + playerToMoverDistanceFraction *= playerToMoverDistanceFraction; + playerToMoverDistanceFraction *= 0.6f; + playerToMoverDistance *= playerToMoverDistanceFraction; + VectorMA(player->currentOrigin, playerToMoverDistance, playerToMover, wooshSoundPos); + + // Large Building + //---------------- + if (mover.mRows>4) + { + wooshSound = mWooshLar[Q_irand(0, mWooshLar.size()-1)]; + } + + // Medium Building + //----------------- + else if (mover.mRows>2) + { + wooshSound = mWooshMed[Q_irand(0, mWooshMed.size()-1)]; + } + + // Small Building + //---------------- + else + { + wooshSound = mWooshSml[Q_irand(0, mWooshSml.size()-1)]; + } + } + + // If A Woosh Sound Was Selected, Play It Now + //-------------------------------------------- + if (wooshSound) + { + G_SoundAtSpot(wooshSoundPos, wooshSound, qfalse); + if (WOOSH_DEBUG) + { + CG_DrawEdge(player->currentOrigin, wooshSoundPos, EDGE_WHITE_TWOSECOND); + } + } + } + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void Rail_LockCenterOfTrack(const char* trackName) +{ + hstring name = trackName; + for (int track=0; trackActive()) + { + continue; + } + + // Don't Spawn Until Start Time Has Expired + //------------------------------------------ + if (level.time < ((mover->mLane)?(mover->mLane->mStartTime):(mStartTime))) + { + continue; + } + + // If Center Locked, Stop Spawning Center Track Movers + //----------------------------------------------------- + if (mover->mCenter && mCenterLocked) + { + continue; + } + + + // Restrict It To A Lane + //----------------------- + if (mover->mLane) + { + startCol = mover->mLane->mMinCol; + stopCol = mover->mLane->mMaxCol+1; + } + + // Or Let It Go Anywhere On The Track + //------------------------------------ + else + { + startCol = 0; + stopCol = mCols; + } + stopCol -= (mover->mCols-1); + + + // If The Mover Is Too Big To Fit In The Lane, Go On To Next Attempt + //------------------------------------------------------------------- + if (stopCol<=startCol) + { + assert(0); // Should Not Happen + continue; + } + + // Force It To Center + //-------------------- + if (mover->mCenter && stopCol!=(startCol+1)) + { + startCol = ((mCols/2) - (mover->mCols/2)); + stopCol = startCol+1; + } + + + // Construct A List Of Columns To Test For Insertion + //--------------------------------------------------- + mTestCols.clear(); + for (int i=startCol; imCols/2.0f) * mGridCellSize)); + StartPos[mHAxis] += (((mover->mRows/2.0f) * mGridCellSize) * ((mNegative)?(1):(-1))); + StartPos[2] = 0; + + // If Centered, Actually Put It At EXACTLY The Right Position On The Width Axis + //------------------------------------------------------------------------------ + if (mover->mCenter) + { + StartPos[mWAxis] = mGridCenter[mWAxis]; + float deltaOffset = mGridCenter[mWAxis] - mover->mOriginOffset[mWAxis]; + if (deltaOffset<(mGridCellSize*0.5f) ) + { + StartPos[mWAxis] -= deltaOffset; + } + } + + StartPos -= mover->mOriginOffset; + G_SetOrigin(mover->mEnt, StartPos.v); + + // Start It Moving + //----------------- + VectorCopy(StartPos.v, mover->mEnt->s.pos.trBase); + VectorCopy(mVelocity.v, mover->mEnt->s.pos.trDelta); + mover->mEnt->s.pos.trTime = level.time; + mover->mEnt->s.pos.trDuration = mTravelTimeMilliseconds + (mNextUpdateDelay*mover->mRows); + mover->mEnt->s.pos.trType = TR_LINEAR_STOP; + mover->mEnt->s.eFlags &= ~EF_NODRAW; + + mover->mSoundPlayed = false; + + + // Successfully Inserted This Mover. Now Move On To The Next Mover + //------------------------------------------------------------------ + break; + } + } + } + + // Incriment The Current Row + //--------------------------- + mRow++; + if (mRow>=mRows) + { + mRow = 0; + } + + // Erase The Erase Row + //--------------------- + int EraseRow = mRow - MAX_ROW_HISTORY; + if (EraseRow<0) + { + EraseRow += mRows; + } + for (int col=0; colmRows); moverRow++) + //{ + for (int moverCol=0; (moverColmCols); moverCol++) + { + if (mCells.get(atCol+moverCol, mRow/*+moverRow*/)!=0) + { + return false; + } + } + //} + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void CRailTrack::InsertMoverInCells(CRailMover* mover, int atCol) +{ + for (int moverCol=0; (moverColmCols); moverCol++) + { + int col = atCol+moverCol; + for (int moverRow=0; (moverRowmRows); moverRow++) + { + int row = mRow+moverRow; + if (row>=mRows) + { + row -= mRows; + } + assert(mCells.get(col, row)==0); + mCells.get(col, row) = mover; + } + } +} + diff --git a/code/game/g_ref.cpp b/code/game/g_ref.cpp new file mode 100644 index 0000000..af05d3c --- /dev/null +++ b/code/game/g_ref.cpp @@ -0,0 +1,402 @@ +// Reference tag utility functions +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + + +#include "g_local.h" +#include "g_functions.h" +#include "g_nav.h" + +extern int delayedShutDown; + +#define TAG_GENERIC_NAME "__WORLD__" //If a designer chooses this name, cut a finger off as an example to the others + +typedef vector < reference_tag_t * > refTag_v; +typedef map < string, reference_tag_t * > refTag_m; + +typedef struct tagOwner_s +{ + refTag_v tags; + refTag_m tagMap; +} tagOwner_t; + +typedef map < string, tagOwner_t * > refTagOwner_m; + +refTagOwner_m refTagOwnerMap; + +/* +------------------------- +TAG_ShowTags +------------------------- +*/ + +void TAG_ShowTags( int flags ) +{ + refTagOwner_m::iterator rtoi; + + STL_ITERATE( rtoi, refTagOwnerMap ) + { + refTag_v::iterator rti; + STL_ITERATE( rti, (((*rtoi).second)->tags) ) + { + if ( (*rti)->flags & RTF_NAVGOAL ) + { + if ( gi.inPVS( g_entities[0].currentOrigin, (*rti)->origin ) ) + CG_DrawNode( (*rti)->origin, NODE_NAVGOAL ); + } + } + } +} + +/* +------------------------- +TAG_Init +------------------------- +*/ + +void TAG_Init( void ) +{ + refTagOwner_m::iterator rtoi; + + //Delete all owners + for ( rtoi = refTagOwnerMap.begin(); rtoi != refTagOwnerMap.end(); rtoi++ ) + { + if ( (*rtoi).second == NULL ) + { + assert( 0 ); //FIXME: This is not good + continue; + } + + refTag_v::iterator rti; + + //Delete all tags within the owner's scope + for ( rti = ((*rtoi).second)->tags.begin(); rti != ((*rtoi).second)->tags.end(); rti++ ) + { + if ( (*rti) == NULL ) + { + assert( 0 ); //FIXME: Bad bad + continue; + } + + //Free it + delete (*rti); + } + + //Clear the containers + ((*rtoi).second)->tags.clear(); + ((*rtoi).second)->tagMap.clear(); + + //Delete the owner + delete ((*rtoi).second); + } + + //Clear the container + refTagOwnerMap.clear(); +} + +/* +------------------------- +TAG_FindOwner +------------------------- +*/ + +tagOwner_t *TAG_FindOwner( const char *owner ) +{ + refTagOwner_m::iterator rtoi; + + rtoi = refTagOwnerMap.find( owner ); + + if ( rtoi == refTagOwnerMap.end() ) + return NULL; + + return (*rtoi).second; +} + +/* +------------------------- +TAG_Find +------------------------- +*/ + +reference_tag_t *TAG_Find( const char *owner, const char *name ) +{ + tagOwner_t *tagOwner; + + tagOwner = VALIDSTRING( owner ) ? TAG_FindOwner( owner ) : TAG_FindOwner( TAG_GENERIC_NAME ); + + //Not found... + if ( tagOwner == NULL ) + { + tagOwner = TAG_FindOwner( TAG_GENERIC_NAME ); + + if ( tagOwner == NULL ) + return NULL; + } + + refTag_m::iterator rti; + + rti = tagOwner->tagMap.find( name ); + + if ( rti == tagOwner->tagMap.end() ) + { + //Try the generic owner instead + tagOwner = TAG_FindOwner( TAG_GENERIC_NAME ); + + if ( tagOwner == NULL ) + return NULL; + + char tempName[ MAX_REFNAME ]; + + Q_strncpyz( (char *) tempName, name, MAX_REFNAME ); + strlwr( (char *) tempName ); //NOTENOTE: For case insensitive searches on a map + + rti = tagOwner->tagMap.find( tempName ); + + if ( rti == tagOwner->tagMap.end() ) + return NULL; + } + + return (*rti).second; +} + +/* +------------------------- +TAG_Add +------------------------- +*/ + +reference_tag_t *TAG_Add( const char *name, const char *owner, vec3_t origin, vec3_t angles, int radius, int flags ) +{ + reference_tag_t *tag = new reference_tag_t; + VALIDATEP( tag ); + + //Copy the information + VectorCopy( origin, tag->origin ); + VectorCopy( angles, tag->angles ); + tag->radius = radius; + tag->flags = flags; + + if ( VALIDSTRING( name ) == false ) + { + //gi.Error("Nameless ref_tag found at (%i %i %i)", (int)origin[0], (int)origin[1], (int)origin[2]); + gi.Printf(S_COLOR_RED"ERROR: Nameless ref_tag found at (%i %i %i)\n", (int)origin[0], (int)origin[1], (int)origin[2]); + delayedShutDown = level.time + 100; + return NULL; + } + + //Copy the name + Q_strncpyz( (char *) tag->name, name, MAX_REFNAME ); + strlwr( (char *) tag->name ); //NOTENOTE: For case insensitive searches on a map + + //Make sure this tag's name isn't alread in use + if ( TAG_Find( owner, name ) ) + { + delayedShutDown = level.time + 100; + gi.Printf(S_COLOR_RED"ERROR: Duplicate tag name \"%s\"\n", name ); + return NULL; + } + + //Attempt to add this to the owner's list + if ( VALIDSTRING( owner ) == false ) + { + //If the owner isn't found, use the generic world name + owner = TAG_GENERIC_NAME; + } + + tagOwner_t *tagOwner = TAG_FindOwner( owner ); + + //If the owner is valid, add this tag to it + if VALID( tagOwner ) + { + tagOwner->tags.insert( tagOwner->tags.end(), tag ); + tagOwner->tagMap[ (char*) &tag->name ] = tag; + } + else + { + //Create a new owner list + tagOwner_t *tagOwner = new tagOwner_t; + + VALIDATEP( tagOwner ); + + //Insert the information + tagOwner->tags.insert( tagOwner->tags.end(), tag ); + tagOwner->tagMap[ (char *) tag->name ] = tag; + + //Map it + refTagOwnerMap[ owner ] = tagOwner; + } + + return tag; +} + +/* +------------------------- +TAG_GetOrigin +------------------------- +*/ + +int TAG_GetOrigin( const char *owner, const char *name, vec3_t origin ) +{ + reference_tag_t *tag = TAG_Find( owner, name ); + + if (!tag) + { + VectorClear(origin); + return false; + } + + VALIDATEB( tag ); + + VectorCopy( tag->origin, origin ); + + return true; +} + +/* +------------------------- +TAG_GetOrigin2 +Had to get rid of that damn assert for dev +------------------------- +*/ + +int TAG_GetOrigin2( const char *owner, const char *name, vec3_t origin ) +{ + reference_tag_t *tag = TAG_Find( owner, name ); + + if( tag == NULL ) + { + return qfalse; + } + + VectorCopy( tag->origin, origin ); + + return qtrue; +} +/* +------------------------- +TAG_GetAngles +------------------------- +*/ + +int TAG_GetAngles( const char *owner, const char *name, vec3_t angles ) +{ + reference_tag_t *tag = TAG_Find( owner, name ); + + VALIDATEB( tag ); + + VectorCopy( tag->angles, angles ); + + return true; +} + +/* +------------------------- +TAG_GetRadius +------------------------- +*/ + +int TAG_GetRadius( const char *owner, const char *name ) +{ + reference_tag_t *tag = TAG_Find( owner, name ); + + VALIDATEB( tag ); + + return tag->radius; +} + +/* +------------------------- +TAG_GetFlags +------------------------- +*/ + +int TAG_GetFlags( const char *owner, const char *name ) +{ + reference_tag_t *tag = TAG_Find( owner, name ); + + VALIDATEB( tag ); + + return tag->flags; +} + +/* +============================================================================== + +Spawn functions + +============================================================================== +*/ + +/*QUAKED ref_tag (0.5 0.5 1) (-8 -8 -8) (8 8 8) + +Reference tags which can be positioned throughout the level. +These tags can later be refered to by the scripting system +so that their origins and angles can be referred to. + +If you set angles on the tag, these will be retained. + +If you target a ref_tag at an entity, that will set the ref_tag's +angles toward that entity. + +If you set the ref_tag's ownername to the ownername of an entity, +it makes that entity is the owner of the ref_tag. This means +that the owner, and only the owner, may refer to that tag. + +Tags may not have the same name as another tag with the same +owner. However, tags with different owners may have the same +name as one another. In this way, scripts can generically +refer to tags by name, and their owners will automatically +specifiy which tag is being referred to. + +targetname - the name of this tag +ownername - the owner of this tag +target - use to point the tag at something for angles +*/ + +void ref_link ( gentity_t *ent ) +{ + reference_tag_t *tag; + + if ( ent->target ) + { + //TODO: Find the target and set our angles to that direction + gentity_t *target = G_Find( NULL, FOFS(targetname), ent->target ); + vec3_t dir; + + if ( target ) + { + //Find the direction to the target + VectorSubtract( target->s.origin, ent->s.origin, dir ); + VectorNormalize( dir ); + vectoangles( dir, ent->s.angles ); + + //FIXME: Does pitch get flipped? + } + else + { + gi.Printf( S_COLOR_RED"ERROR: ref_tag (%s) has invalid target (%s)", ent->targetname, ent->target ); + } + } + + //Add the tag + tag = TAG_Add( ent->targetname, ent->ownername, ent->s.origin, ent->s.angles, 16, 0 ); + + //Delete immediately, cannot be refered to as an entity again + //NOTE: this means if you wanted to link them in a chain for, say, a path, you can't + G_FreeEntity( ent ); +} + +void SP_reference_tag ( gentity_t *ent ) +{ + if ( ent->target ) + { + //Init cannot occur until all entities have been spawned + ent->e_ThinkFunc = thinkF_ref_link; + ent->nextthink = level.time + START_TIME_LINK_ENTS; + } + else + { + ref_link( ent ); + } +} \ No newline at end of file diff --git a/code/game/g_roff.cpp b/code/game/g_roff.cpp new file mode 100644 index 0000000..7c04d67 --- /dev/null +++ b/code/game/g_roff.cpp @@ -0,0 +1,646 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "g_roff.h" +#include "Q3_Interface.h" +// The list of precached ROFFs +roff_list_t roffs[MAX_ROFFS]; +int num_roffs = 0; + +qboolean g_bCollidableRoffs = qfalse; + +extern void Q3_TaskIDComplete( gentity_t *ent, taskID_t taskType ); + +static void G_RoffNotetrackCallback( gentity_t *cent, const char *notetrack) +{ + int i = 0, r = 0, r2 = 0, objectID = 0, anglesGathered = 0, posoffsetGathered = 0; + char type[256]; + char argument[512]; + char addlArg[512]; + char errMsg[256]; + char t[64]; + char teststr[256]; + int addlArgs = 0; + vec3_t parsedAngles, parsedOffset, useAngles, useOrigin, forward, right, up; + + if (!cent || !notetrack) + { + return; + } + + //notetrack = "effect effects/explosion1.efx 0+0+64 0-0-1"; + + while (notetrack[i] && notetrack[i] != ' ') + { + type[i] = notetrack[i]; + i++; + } + + type[i] = '\0'; + + if (notetrack[i] != ' ') + { //didn't pass in a valid notetrack type, or forgot the argument for it + return; + } + + i++; + + while (notetrack[i] && notetrack[i] != ' ') + { + if (notetrack[i] != '\n' && notetrack[i] != '\r') + { //don't read line ends for an argument + argument[r] = notetrack[i]; + r++; + } + i++; + } + argument[r] = '\0'; + + if (!r) + { + return; + } + + if (notetrack[i] == ' ') + { //additional arguments... + addlArgs = 1; + + i++; + r = 0; + while (notetrack[i]) + { + addlArg[r] = notetrack[i]; + r++; + i++; + } + addlArg[r] = '\0'; + } + + if (strcmp(type, "effect") == 0) + { + if (!addlArgs) + { + VectorClear(parsedOffset); + goto defaultoffsetposition; + } + + i = 0; + + while (posoffsetGathered < 3) + { + r = 0; + while (addlArg[i] && addlArg[i] != '+' && addlArg[i] != ' ') + { + t[r] = addlArg[i]; + r++; + i++; + } + t[r] = '\0'; + i++; + if (!r) + { //failure.. + VectorClear(parsedOffset); + i = 0; + goto defaultoffsetposition; + } + parsedOffset[posoffsetGathered] = atof(t); + posoffsetGathered++; + } + + if (posoffsetGathered < 3) + { + sprintf(errMsg, "Offset position argument for 'effect' type is invalid."); + goto functionend; + } + + i--; + + if (addlArg[i] != ' ') + { + addlArgs = 0; + } + +defaultoffsetposition: + + r = 0; + if (argument[r] == '/') + { + r++; + } + while (argument[r] && argument[r] != '/') + { + teststr[r2] = argument[r]; + r2++; + r++; + } + teststr[r2] = '\0'; + + if (r2 && strstr(teststr, "effects")) + { //get rid of the leading "effects" since it's auto-inserted + r++; + r2 = 0; + + while (argument[r]) + { + teststr[r2] = argument[r]; + r2++; + r++; + } + teststr[r2] = '\0'; + + strcpy(argument, teststr); + } + + objectID = G_EffectIndex(argument); + r = 0; + + if (objectID) + { + if (addlArgs) + { //if there is an additional argument for an effect it is expected to be XANGLE-YANGLE-ZANGLE + i++; + while (anglesGathered < 3) + { + r = 0; + while (addlArg[i] && addlArg[i] != '-') + { + t[r] = addlArg[i]; + r++; + i++; + } + t[r] = '\0'; + i++; + + if (!r) + { //failed to get a new part of the vector + anglesGathered = 0; + break; + } + + parsedAngles[anglesGathered] = atof(t); + anglesGathered++; + } + + if (anglesGathered) + { + VectorCopy(parsedAngles, useAngles); + } + else + { //failed to parse angles from the extra argument provided.. + VectorCopy(cent->s.apos.trBase, useAngles); + } + } + else + { //if no constant angles, play in direction entity is facing + VectorCopy(cent->s.apos.trBase, useAngles); + } + + AngleVectors(useAngles, forward, right, up); + + VectorCopy(cent->s.pos.trBase, useOrigin); + + //forward + useOrigin[0] += forward[0]*parsedOffset[0]; + useOrigin[1] += forward[1]*parsedOffset[0]; + useOrigin[2] += forward[2]*parsedOffset[0]; + + //right + useOrigin[0] += right[0]*parsedOffset[1]; + useOrigin[1] += right[1]*parsedOffset[1]; + useOrigin[2] += right[2]*parsedOffset[1]; + + //up + useOrigin[0] += up[0]*parsedOffset[2]; + useOrigin[1] += up[1]*parsedOffset[2]; + useOrigin[2] += up[2]*parsedOffset[2]; + + G_PlayEffect(objectID, useOrigin, useAngles); + } + } + else if (strcmp(type, "sound") == 0) + { + objectID = G_SoundIndex(argument); + cgi_S_StartSound(cent->s.pos.trBase, cent->s.number, CHAN_BODY, objectID); + } + //else if ... + else + { + if (type[0]) + { + Com_Printf("Warning: \"%s\" is an invalid ROFF notetrack function\n", type); + } + else + { + Com_Printf("Warning: Notetrack is missing function and/or arguments\n"); + } + } + + return; + +functionend: + Com_Printf("Type-specific notetrack error: %s\n", errMsg); + return; +} + +static qboolean G_ValidRoff( roff_hdr2_t *header ) +{ + if ( !strncmp( header->mHeader, "ROFF", 4 )) + { + if ( header->mCount > 0 && header->mVersion == ROFF_VERSION2 ) + { + return qtrue; + } + else if ( header->mVersion == ROFF_VERSION && ((roff_hdr_t*)header)->mCount > 0.0f ) + { // version 1 defines the count as a float, so we best do the count check as a float or we'll get bogus results + return qtrue; + } + } + + return qfalse; +} + +static void G_FreeRoff(int index) +{ + if(roffs[index].mNumNoteTracks) { + delete [] roffs[index].mNoteTrackIndexes[0]; + delete [] roffs[index].mNoteTrackIndexes; + } +} + +static qboolean G_InitRoff( char *file, unsigned char *data ) +{ + roff_hdr_t *header = (roff_hdr_t *)data; + int count = (int)header->mCount; + + roffs[num_roffs].fileName = G_NewString( file ); + + if ( header->mVersion == ROFF_VERSION ) + { + // We are Old School(tm) + roffs[num_roffs].type = 1; + + roffs[num_roffs].data = (void *) G_Alloc( count * sizeof( move_rotate_t ) ); + move_rotate_t *mem = (move_rotate_t *)roffs[num_roffs].data; + + roffs[num_roffs].mFrameTime = 100; // old school ones have a hard-coded frame time + roffs[num_roffs].mLerp = 10; + roffs[num_roffs].mNumNoteTracks = 0; + roffs[num_roffs].mNoteTrackIndexes = NULL; + + if ( mem ) + { + // The allocation worked, so stash this stuff off so we can reference the data later if needed + roffs[num_roffs].frames = count; + + // Step past the header to get to the goods + move_rotate_t *roff_data = ( move_rotate_t *)&header[1]; + + // Copy all of the goods into our ROFF cache + for ( int i = 0; i < count; i++, roff_data++, mem++ ) + { + // Copy just the delta position and orientation which can be applied to anything at a later point + VectorCopy( roff_data->origin_delta, mem->origin_delta ); + VectorCopy( roff_data->rotate_delta, mem->rotate_delta ); + } + return qtrue; + } + } + else if ( header->mVersion == ROFF_VERSION2 ) + { + // Version 2.0, heck yeah! + roff_hdr2_t *hdr = (roff_hdr2_t *)data; + count = hdr->mCount; + + roffs[num_roffs].frames = count; + roffs[num_roffs].data = (void *) G_Alloc( count * sizeof( move_rotate2_t )); + move_rotate2_t *mem = (move_rotate2_t *)roffs[num_roffs].data; + + if ( mem ) + { + roffs[num_roffs].mFrameTime = hdr->mFrameRate; + roffs[num_roffs].mLerp = 1000 / hdr->mFrameRate; + roffs[num_roffs].mNumNoteTracks = hdr->mNumNotes; + + if (roffs[num_roffs].mFrameTime < 50) + { + Com_Printf(S_COLOR_RED"Error: \"%s\" has an invalid ROFF framerate (%d < 50)\n", file, roffs[num_roffs].mFrameTime); + } + assert( roffs[num_roffs].mFrameTime >= 50 );//HAS to be at least 50 to be reliable + + // Step past the header to get to the goods + move_rotate2_t *roff_data = ( move_rotate2_t *)&hdr[1]; + + roffs[num_roffs].type = 2; //rww - any reason this wasn't being set already? + + // Copy all of the goods into our ROFF cache + for ( int i = 0; i < count; i++ ) + { + VectorCopy( roff_data[i].origin_delta, mem[i].origin_delta ); + VectorCopy( roff_data[i].rotate_delta, mem[i].rotate_delta ); + + mem[i].mStartNote = roff_data[i].mStartNote; + mem[i].mNumNotes = roff_data[i].mNumNotes; + } + + if ( hdr->mNumNotes ) + { + int size; + char *ptr, *start; + + ptr = start = (char *)&roff_data[i]; + size = 0; + + for( i = 0; i < hdr->mNumNotes; i++ ) + { + size += strlen(ptr) + 1; + ptr += strlen(ptr) + 1; + } + + // ? Get rid of dynamic memory ? + roffs[num_roffs].mNoteTrackIndexes = new char *[hdr->mNumNotes]; + ptr = roffs[num_roffs].mNoteTrackIndexes[0] = new char[size]; + memcpy(roffs[num_roffs].mNoteTrackIndexes[0], start, size); + + for( i = 1; i < hdr->mNumNotes; i++ ) + { + ptr += strlen(ptr) + 1; + roffs[num_roffs].mNoteTrackIndexes[i] = ptr; + } + } + return qtrue; + } + } + + return false; +} + +//------------------------------------------------------- +// G_LoadRoff +// +// Does the fun work of loading and caching a roff file +// If the file is already cached, it just returns an +// ID to the cached file. +//------------------------------------------------------- + +int G_LoadRoff( const char *fileName ) +{ + char file[MAX_QPATH]; + byte *data; + int len, i, roff_id = 0; + + // Before even bothering with all of this, make sure we have a place to store it. + if ( num_roffs >= MAX_ROFFS ) + { + Com_Printf( S_COLOR_RED"MAX_ROFFS count exceeded. Skipping load of .ROF '%s'\n", fileName ); + return roff_id; + } + + // The actual path + sprintf( file, "%s/%s.rof", Q3_SCRIPT_DIR, fileName ); + + // See if I'm already precached + for ( i = 0; i < num_roffs; i++ ) + { + if ( stricmp( file, roffs[i].fileName ) == 0 ) + { + // Good, just return me...avoid zero index + return i + 1; + } + } + +#ifdef _DEBUG +// Com_Printf( S_COLOR_GREEN"Caching ROF: '%s'\n", file ); +#endif + + // Read the file in one fell swoop + len = gi.FS_ReadFile( file, (void**) &data); + + if ( len <= 0 ) + { + Com_Printf( S_COLOR_RED"Could not open .ROF file '%s'\n", fileName ); + return roff_id; + } + + // Now let's check the header info... + roff_hdr2_t *header = (roff_hdr2_t *)data; + + // ..and make sure it's reasonably valid + if ( !G_ValidRoff( header )) + { + Com_Printf( S_COLOR_RED"Invalid roff format '%s'\n", fileName ); + } + else + { + G_InitRoff( file, data ); + + // Done loading this roff, so save off an id to it..increment first to avoid zero index + roff_id = ++num_roffs; + } + + gi.FS_FreeFile( data ); + + return roff_id; +} + + +void G_FreeRoffs(void) +{ + while(num_roffs) { + G_FreeRoff(num_roffs - 1); + num_roffs--; + } +} + + +//------------------------------------------------------- +// G_Roff +// +// Handles applying the roff data to the specified ent +//------------------------------------------------------- + +void G_Roff( gentity_t *ent ) +{ + if ( !ent->next_roff_time ) + { + return; + } + + if ( ent->next_roff_time > level.time ) + {// either I don't think or it's just not time to have me think yet + return; + } + + const int roff_id = G_LoadRoff( ent->roff ); + + if ( !roff_id ) + { // Couldn't cache this rof + return; + } + + // The ID is one higher than the array index + const roff_list_t * roff = &roffs[ roff_id - 1 ]; + vec3_t org, ang; + + if ( roff->type == 2 ) + { + move_rotate2_t *data = &((move_rotate2_t *)roff->data)[ ent->roff_ctr ]; + VectorCopy( data->origin_delta, org ); + VectorCopy( data->rotate_delta, ang ); + if (data->mStartNote != -1 || data->mNumNotes) + { + G_RoffNotetrackCallback(ent, roffs[roff_id - 1].mNoteTrackIndexes[data->mStartNote]); + } + } + else + { + move_rotate_t *data = &((move_rotate_t *)roff->data)[ ent->roff_ctr ]; + VectorCopy( data->origin_delta, org ); + VectorCopy( data->rotate_delta, ang ); + } + +#ifdef _DEBUG + if ( g_developer->integer ) + { + Com_Printf( S_COLOR_GREEN"ROFF dat: num: %d o:<%.2f %.2f %.2f> a:<%.2f %.2f %.2f>\n", + ent->roff_ctr, + org[0], org[1], org[2], + ang[0], ang[1], ang[2] ); + } +#endif + + if ( ent->client ) + { + // Set up the angle interpolation + //------------------------------------- + VectorAdd( ent->s.apos.trBase, ang, ent->s.apos.trBase ); + ent->s.apos.trTime = level.time; + ent->s.apos.trType = TR_INTERPOLATE; + + // Store what the next apos->trBase should be + VectorCopy( ent->s.apos.trBase, ent->client->ps.viewangles ); + VectorCopy( ent->s.apos.trBase, ent->currentAngles ); + VectorCopy( ent->s.apos.trBase, ent->s.angles ); + if ( ent->NPC ) + { + //ent->NPC->desiredPitch = ent->s.apos.trBase[PITCH]; + ent->NPC->desiredYaw = ent->s.apos.trBase[YAW]; + } + + // Set up the origin interpolation + //------------------------------------- + VectorAdd( ent->s.pos.trBase, org, ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; + ent->s.pos.trType = TR_INTERPOLATE; + + // Store what the next pos->trBase should be + VectorCopy( ent->s.pos.trBase, ent->client->ps.origin ); + VectorCopy( ent->s.pos.trBase, ent->currentOrigin ); + //VectorCopy( ent->s.pos.trBase, ent->s.origin ); + } + else + { + // Set up the angle interpolation + //------------------------------------- + VectorScale( ang, roff->mLerp, ent->s.apos.trDelta ); + VectorCopy( ent->pos2, ent->s.apos.trBase ); + ent->s.apos.trTime = level.time; + ent->s.apos.trType = TR_LINEAR; + + // Store what the next apos->trBase should be + VectorAdd( ent->pos2, ang, ent->pos2 ); + + // Set up the origin interpolation + //------------------------------------- + VectorScale( org, roff->mLerp, ent->s.pos.trDelta ); + VectorCopy( ent->pos1, ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; + ent->s.pos.trType = TR_LINEAR; + + // Store what the next apos->trBase should be + VectorAdd( ent->pos1, org, ent->pos1 ); + + //make it true linear... FIXME: sticks around after ROFF is done, but do we really care? + ent->alt_fire = qtrue; + + if ( ent->e_ThinkFunc == thinkF_TieFighterThink || ent->e_ThinkFunc == thinkF_TieBomberThink || + ( !ent->e_ThinkFunc + && ent->s.eType != ET_MISSILE + && ent->s.eType != ET_ITEM + && ent->s.eType != ET_MOVER ) ) + {//will never set currentAngles & currentOrigin itself ( why do we limit which one's get set?, just set all the time? ) + EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles ); + EvaluateTrajectory( &ent->s.pos, level.time, ent->currentOrigin ); + } + } + + // Link just in case. + gi.linkentity( ent ); + + // See if the ROFF playback is done + //------------------------------------- + if ( ++ent->roff_ctr >= roff->frames ) + { + // We are done, so let me think no more, then tell the task that we're done. + ent->next_roff_time = 0; + + // Stop any rotation or movement. + VectorClear( ent->s.pos.trDelta ); + VectorClear( ent->s.apos.trDelta ); + + Q3_TaskIDComplete( ent, TID_MOVE_NAV ); + + return; + } + + ent->next_roff_time = level.time + roff->mFrameTime; +} + + +//------------------------------------------------------- +// G_SaveCachedRoffs +// +// Really fun savegame stuff +//------------------------------------------------------- + +void G_SaveCachedRoffs() +{ + int i, len; + + // Write out the number of cached ROFFs + gi.AppendToSaveGame( 'ROFF', (void *)&num_roffs, sizeof(num_roffs) ); + + // Now dump out the cached ROFF file names in order so they can be loaded on the other end + for ( i = 0; i < num_roffs; i++ ) + { + // Dump out the string length to make things a bit easier on the other end...heh heh. + len = strlen( roffs[i].fileName ) + 1; + gi.AppendToSaveGame( 'SLEN', (void *)&len, sizeof(len) ); + gi.AppendToSaveGame( 'RSTR', (void *)(*roffs[i].fileName), len ); + } +} + + +//------------------------------------------------------- +// G_LoadCachedRoffs +// +// Really fun loadgame stuff +//------------------------------------------------------- + +void G_LoadCachedRoffs() +{ + int i, count, len; + char buffer[MAX_QPATH]; + + // Get the count of goodies we need to revive + gi.ReadFromSaveGame( 'ROFF', (void *)&count, sizeof(count) ); + + // Now bring 'em back to life + for ( i = 0; i < count; i++ ) + { + gi.ReadFromSaveGame( 'SLEN', (void *)&len, sizeof(len) ); + gi.ReadFromSaveGame( 'RSTR', (void *)(buffer), len ); + G_LoadRoff( buffer ); + } +} diff --git a/code/game/g_roff.h b/code/game/g_roff.h new file mode 100644 index 0000000..016fa9e --- /dev/null +++ b/code/game/g_roff.h @@ -0,0 +1,88 @@ +#ifndef __G_ROFF_H__ +#define __G_ROFF_H__ + + +#include "q_shared.h" + + +// ROFF Defines +//------------------- +#define ROFF_VERSION 1 // ver # for the (R)otation (O)bject (F)ile (F)ormat +#define ROFF_VERSION2 2 // ver # for the (R)otation (O)bject (F)ile (F)ormat +#define MAX_ROFFS 32 // hard coded number of max roffs per level, sigh.. +#define ROFF_SAMPLE_RATE 20 // 10hz + + +// ROFF Header file definition +//------------------------------- +typedef struct roff_hdr_s +{ + char mHeader[4]; // should be "ROFF" (Rotation, Origin File Format) + long mVersion; + float mCount; // There isn't any reason for this to be anything other than an int, sigh... + // + // Move - Rotate data follows....vec3_t delta_origin, vec3_t delta_rotation + // +} roff_hdr_t; + + +// ROFF move rotate data element +//-------------------------------- +typedef struct move_rotate_s +{ + vec3_t origin_delta; + vec3_t rotate_delta; + +} move_rotate_t; + +typedef struct roff_hdr2_s +//------------------------------- +{ + char mHeader[4]; // should match roff_string defined above + long mVersion; // version num, supported version defined above + int mCount; // I think this is a float because of a limitation of the roff exporter + int mFrameRate; // Frame rate the roff should be played at + int mNumNotes; // number of notes (null terminated strings) after the roff data + +} roff_hdr2_t; + + +typedef struct move_rotate2_s +//------------------------------- +{ + vec3_t origin_delta; + vec3_t rotate_delta; + int mStartNote, mNumNotes; // note track info + +} move_rotate2_t; + + +// a precached ROFF list +//------------------------- +typedef struct roff_list_s +{ + int type; // roff type number, 1-old, 2-new + char *fileName; // roff filename + int frames; // number of roff entries + void *data; // delta move and rotate vector list + int mFrameTime; // frame rate + int mLerp; // Lerp rate (FPS) + int mNumNoteTracks; + char **mNoteTrackIndexes; + +} roff_list_t; + + + +extern roff_list_t roffs[]; +extern int num_roffs; + + +// Function prototypes +//------------------------- +int G_LoadRoff( const char *fileName ); +void G_Roff( gentity_t *ent ); +void G_SaveCachedRoffs(); +void G_LoadCachedRoffs(); + +#endif` \ No newline at end of file diff --git a/code/game/g_savegame.cpp b/code/game/g_savegame.cpp new file mode 100644 index 0000000..c871000 --- /dev/null +++ b/code/game/g_savegame.cpp @@ -0,0 +1,1275 @@ +// Filename:- g_savegame.cpp +// +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "IcarusInterface.h" +#include "Q3_Interface.h" +#include "g_local.h" +#include "fields.h" +#include "objectives.h" +#include "../cgame/cg_camera.h" +#include "../qcommon/sstring.h" + +extern void OBJ_LoadTacticalInfo(void); + +extern void G_LoadSave_WriteMiscData(void); +extern void G_LoadSave_ReadMiscData(void); +extern void G_ReloadSaberData( gentity_t *ent ); +extern void FX_Read( void ); +extern void FX_Write( void ); + + +static const save_field_t savefields_gEntity[] = +{ + {strFOFS(client), F_GCLIENT}, + {strFOFS(owner), F_GENTITY}, + {strFOFS(classname), F_STRING}, + {strFOFS(model), F_STRING}, + {strFOFS(model2), F_STRING}, +// {strFOFS(model3), F_STRING}, - MCG + {strFOFS(nextTrain), F_GENTITY}, + {strFOFS(prevTrain), F_GENTITY}, + {strFOFS(message), F_STRING}, + {strFOFS(target), F_STRING}, + {strFOFS(target2), F_STRING}, + {strFOFS(target3), F_STRING}, + {strFOFS(target4), F_STRING}, + {strFOFS(targetJump), F_STRING}, + {strFOFS(targetname), F_STRING}, + {strFOFS(team), F_STRING}, + {strFOFS(roff), F_STRING}, +// {strFOFS(target_ent), F_GENTITY}, - MCG + {strFOFS(chain), F_GENTITY}, + {strFOFS(enemy), F_GENTITY}, + {strFOFS(activator), F_GENTITY}, + {strFOFS(teamchain), F_GENTITY}, + {strFOFS(teammaster), F_GENTITY}, + {strFOFS(item), F_ITEM}, + {strFOFS(NPC_type), F_STRING}, + {strFOFS(closetarget), F_STRING}, + {strFOFS(opentarget), F_STRING}, + {strFOFS(paintarget), F_STRING}, + {strFOFS(NPC_targetname), F_STRING}, + {strFOFS(NPC_target), F_STRING}, + {strFOFS(ownername), F_STRING}, + {strFOFS(lastEnemy), F_GENTITY}, + {strFOFS(behaviorSet), F_BEHAVIORSET}, + {strFOFS(script_targetname),F_STRING}, + {strFOFS(m_iIcarusID), F_NULL}, + {strFOFS(NPC), F_BOOLPTR}, + {strFOFS(soundSet), F_STRING}, + {strFOFS(cameraGroup), F_STRING}, + {strFOFS(parms), F_BOOLPTR}, + {strFOFS(m_pVehicle), F_BOOLPTR}, + + {NULL, 0, F_IGNORE} +}; + +static const save_field_t savefields_gNPC[] = +{ +// {strNPCOFS(pendingEnemy), F_GENTITY}, + {strNPCOFS(touchedByPlayer), F_GENTITY}, + {strNPCOFS(aimingBeam), F_GENTITY}, + {strNPCOFS(eventOwner), F_GENTITY}, + {strNPCOFS(coverTarg), F_GENTITY}, + {strNPCOFS(tempGoal), F_GENTITY}, + {strNPCOFS(goalEntity), F_GENTITY}, + {strNPCOFS(lastGoalEntity), F_GENTITY}, + {strNPCOFS(eventualGoal), F_GENTITY}, + {strNPCOFS(captureGoal), F_GENTITY}, + {strNPCOFS(defendEnt), F_GENTITY}, + {strNPCOFS(greetEnt), F_GENTITY}, + {strNPCOFS(group), F_GROUP}, + {strNPCOFS(blockedEntity), F_GENTITY}, + {strNPCOFS(blockedTargetEntity),F_GENTITY}, + {strNPCOFS(jumpTarget), F_GENTITY}, + {strNPCOFS(watchTarget), F_GENTITY}, + {NULL, 0, F_IGNORE} +}; + +static const save_field_t savefields_LevelLocals[] = +{ + {strLLOFS(locationHead), F_GENTITY}, + {strLLOFS(alertEvents), F_ALERTEVENT}, + {strLLOFS(groups), F_AIGROUPS}, + {strLLOFS(knownAnimFileSets),F_ANIMFILESETS}, + {NULL, 0, F_IGNORE} +}; + +static const save_field_t savefields_gVHIC[] = +{ + {strVHICOFS(m_pPilot), F_GENTITY}, + {strVHICOFS(m_pOldPilot), F_GENTITY}, + {strVHICOFS(m_pDroidUnit), F_GENTITY}, + {strVHICOFS(m_pParentEntity), F_GENTITY}, + + //m_ppPassengers //!ptr array?! + {strVHICOFS(m_pVehicleInfo), F_VEHINFO}, //!another ptr! store name field instead and re-hook on load? + + {NULL, 0, F_IGNORE} +}; + +static const save_field_t savefields_gClient[] = +{ + // sabers are stomped over by specific code elsewhere, it seems, but the first two fields MUST be saved + // or it crashes on reload + {strCLOFS(ps.saber[0].name),F_STRING}, +/* {strCLOFS(ps.saber[0].model),F_STRING}, + {strCLOFS(ps.saber[0].skin),F_STRING}, + {strCLOFS(ps.saber[0].brokenSaber1),F_STRING}, + {strCLOFS(ps.saber[0].brokenSaber2),F_STRING}, +*/ + {strCLOFS(ps.saber[1].name),F_STRING}, +/* {strCLOFS(ps.saber[1].model),F_STRING}, + {strCLOFS(ps.saber[1].skin),F_STRING}, + {strCLOFS(ps.saber[1].brokenSaber1),F_STRING}, + {strCLOFS(ps.saber[1].brokenSaber2),F_STRING}, +*/ + {strCLOFS(leader), F_GENTITY}, + {strCLOFS(clientInfo.customBasicSoundDir),F_STRING}, + {strCLOFS(clientInfo.customCombatSoundDir),F_STRING}, + {strCLOFS(clientInfo.customExtraSoundDir),F_STRING}, + {strCLOFS(clientInfo.customJediSoundDir),F_STRING}, + + {NULL, 0, F_IGNORE} +}; + +list *strList = NULL; + + +/////////// char * ///////////// +// +// +static int GetStringNum(const char *psString) +{ + assert( psString != (char *)0xcdcdcdcd ); + + // NULL ptrs I'll write out as a strlen of -1... + // + if (!psString) + { + return -1; + } + + strList->push_back( psString ); + return strlen(psString) + 1; // this gives us the chunk length for the reader later +} + +static char *GetStringPtr(int iStrlen, char *psOriginal/*may be NULL*/) +{ + if (iStrlen != -1) + { + char sString[768]; // arb, inc if nec. + + sString[0]=0; + + assert(iStrlen+1<=sizeof(sString)); + + gi.ReadFromSaveGame('STRG', sString, iStrlen); + +#ifndef _XBOX // TAG_G_ALLOC is always blown away, we can never recycle + if (psOriginal && gi.bIsFromZone(psOriginal, TAG_G_ALLOC)) { + if (!strcmp(psOriginal,sString)) + {//it's a legal ptr and they're the same so let's just reuse it instead of free/alloc + return psOriginal; + } + gi.Free(psOriginal); + } +#endif + + return G_NewString(sString); + } + + return NULL; +} +// +// +//////////////////////////////// + + + + +/////////// gentity_t * //////// +// +// +static int GetGEntityNum(gentity_t* ent) +{ + assert( ent != (gentity_t *) 0xcdcdcdcd); + + if (ent == NULL) + { + return -1; + } + + // note that I now validate the return value (to avoid triggering asserts on re-load) because of the + // way that the level_locals_t alertEvents struct contains a count of which ones are valid, so I'm guessing + // that some of them aren't (valid)... + // + int iReturnIndex = ent - g_entities; + + if (iReturnIndex < 0 || iReturnIndex >= MAX_GENTITIES) + { + iReturnIndex = -1; // will get a NULL ptr on reload + } + return iReturnIndex; +} + +static gentity_t *GetGEntityPtr(int iEntNum) +{ + if (iEntNum == -1) + { + return NULL; + } + assert(iEntNum >= 0); + assert(iEntNum < MAX_GENTITIES); + return (g_entities + iEntNum); +} +// +// +//////////////////////////////// + + + +static int GetGroupNumber(AIGroupInfo_t *pGroup) +{ + assert( pGroup != (AIGroupInfo_t *) 0xcdcdcdcd); + + if (pGroup == NULL) + { + return -1; + } + + int iReturnIndex = pGroup - level.groups; + if (iReturnIndex < 0 || iReturnIndex >= (sizeof(level.groups) / sizeof(level.groups[0])) ) + { + iReturnIndex = -1; // will get a NULL ptr on reload + } + return iReturnIndex; +} + +static AIGroupInfo_t *GetGroupPtr(int iGroupNum) +{ + if (iGroupNum == -1) + { + return NULL; + } + assert(iGroupNum >= 0); + assert(iGroupNum < (sizeof(level.groups) / sizeof(level.groups[0]))); + return (level.groups + iGroupNum); +} + + + +/////////// gclient_t * //////// +// +// +static int GetGClientNum(gclient_t *c, gentity_t *ent) +{ + // unfortunately, I now need to see if this is a 'real' client (and therefore resolve to an enum), or + // whether it's one of the NPC or SP_misc_weapon_shooter + // + assert(c != (gclient_t *)0xcdcdcdcd); + + if (c == NULL) + { + return -1; + } + + if (ent->s.number < MAX_CLIENTS) + { // regular client... + return (c - level.clients); + } + else + { // this must be an NPC or weapon_shooter, so mark it as special... + return -2; // yeuch, but distinguishes it from a valid 0 index, or -1 for client==NULL + } +} + +static gclient_t *GetGClientPtr(int c) +{ + if (c == -1) + { + return NULL; + } + if (c == -2) + { + return (gclient_t *) -2; // preserve this value so that I know to load in one of Mike's private NPC clients later + } + + assert(c >= 0); + assert(c < level.maxclients); + return (level.clients + c); +} +// +// +//////////////////////////////// + + +/////////// gitem_t * ////////// +// +// +static int GetGItemNum (gitem_t *pItem) +{ + assert(pItem != (gitem_t*) 0xcdcdcdcd); + + if (pItem == NULL) + { + return -1; + } + + return pItem - bg_itemlist; +} + +static gitem_t *GetGItemPtr(int iItem) +{ + if (iItem == -1) + { + return NULL; + } + + assert(iItem >= 0); + assert(iItem < bg_numItems); + return &bg_itemlist[iItem]; +} +// +// +//////////////////////////////// + + +/////////// vehicleInfo_t * ////////// +// +// +static int GetVehicleInfoNum(vehicleInfo_t *pVehicleInfo) +{ + assert(pVehicleInfo != (vehicleInfo_t*) 0xcdcdcdcd); + + if (pVehicleInfo == NULL) + { + return -1; + } + + return pVehicleInfo - g_vehicleInfo; +} + +static vehicleInfo_t *GetVehicleInfoPtr(int iVehicleIndex) +{ + if (iVehicleIndex == -1) + { + return NULL; + } + + assert(iVehicleIndex > 0); + assert(iVehicleIndex < numVehicles); + return &g_vehicleInfo[iVehicleIndex]; +} +// +// +//////////////////////////////// + + +static void EnumerateField(const save_field_t *pField, const byte *pbBase) +{ + void *pv = (void *)(pbBase + pField->iOffset); + + switch (pField->eFieldType) + { + case F_STRING: + *(int *)pv = GetStringNum(*(char **)pv); + break; + + case F_GENTITY: + *(int *)pv = GetGEntityNum(*(gentity_t **)pv); + break; + + case F_GROUP: + *(int *)pv = GetGroupNumber(*(AIGroupInfo_t **)pv); + break; + + case F_GCLIENT: + *(int *)pv = GetGClientNum(*(gclient_t **)pv, (gentity_t *) pbBase); + break; + + case F_ITEM: + *(int *)pv = GetGItemNum(*(gitem_t **)pv); + break; + + case F_VEHINFO: + *(int *)pv = GetVehicleInfoNum(*(vehicleInfo_t **)pv); + break; + + case F_BEHAVIORSET: + { + const char **p = (const char **) pv; + for (int i=0; i; + + // enumerate all the fields... + // + if (pFields) + { + for (const save_field_t *pField = pFields; pField->psName; pField++) + { + assert(pField->iOffset < iLen); + EnumerateField(pField, pbData); + } + } + + // save out raw data... + // + gi.AppendToSaveGame(ulChid, pbData, iLen); + + // save out any associated strings.. + // + list::iterator it = strList->begin(); + for (unsigned int i=0; isize(); i++, ++it) + { + gi.AppendToSaveGame('STRG', (void *)(*it).c_str(), (*it).length() + 1); + } + + delete strList; + strList = NULL; +} + + +static void EvaluateField(const save_field_t *pField, byte *pbBase, byte *pbOriginalRefData/* may be NULL*/) +{ + void *pv = (void *)(pbBase + pField->iOffset); + void *pvOriginal = (void *)(pbOriginalRefData + pField->iOffset); + + switch (pField->eFieldType) + { + case F_STRING: + *(char **)pv = GetStringPtr(*(int *)pv, pbOriginalRefData?*(char**)pvOriginal:NULL); + break; + + case F_GENTITY: + *(gentity_t **)pv = GetGEntityPtr(*(int *)pv); + break; + + case F_GROUP: + *(AIGroupInfo_t **)pv = GetGroupPtr(*(int *)pv); + break; + + case F_GCLIENT: + *(gclient_t **)pv = GetGClientPtr(*(int *)pv); + break; + + case F_ITEM: + *(gitem_t **)pv = GetGItemPtr(*(int *)pv); + break; + + case F_VEHINFO: + *(vehicleInfo_t **)pv = GetVehicleInfoPtr(*(int *)pv); + break; + + case F_BEHAVIORSET: + { + char **p = (char **) pv; + char **pO= (char **) pvOriginal; + for (int i=0; iiReadSize); + memset(&pbData[iReadSize], 0, iSize-iReadSize); // zero out new objectives that weren't in old-format save file + break; +*/ + default: + // won't return... + // + G_Error(va("EvaluateFields(): variable-sized chunk '%s' without handler!",SG_GetChidText(ulChid))); + break; + } + } + + if (pFields) + { + for (const save_field_t *pField = pFields; pField->psName; pField++) + { + EvaluateField(pField, pbData, pbOriginalRefData); + } + } +} + +/* +============== +WriteLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +static void WriteLevelLocals () +{ + level_locals_t *temp = (level_locals_t *)gi.Malloc(sizeof(level_locals_t), TAG_TEMP_WORKSPACE, qfalse); + *temp = level; // copy out all data into a temp space + + EnumerateFields(savefields_LevelLocals, (byte *)temp, 'LVLC', LLOFS(LEVEL_LOCALS_T_SAVESTOP)); // sizeof(temp)); + gi.Free(temp); +} + +/* +============== +ReadLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +static void ReadLevelLocals () +{ + // preserve client ptr either side of the load, because clients are already saved/loaded through Read/Writegame... + // + gclient_t *pClients = level.clients; // save clients + + level_locals_t *temp = (level_locals_t *)gi.Malloc(sizeof(level_locals_t), TAG_TEMP_WORKSPACE, qfalse); + *temp = level; // struct copy + EvaluateFields(savefields_LevelLocals, (byte *)temp, (byte *)&level, 'LVLC', LLOFS(LEVEL_LOCALS_T_SAVESTOP),qfalse); // sizeof(level_locals_t)); + level = *temp; // struct copy + + level.clients = pClients; // restore clients + gi.Free(temp); +} + +static void WriteGEntities(qboolean qbAutosave) +{ + int iCount = 0; + + for (int i=0; i<(qbAutosave?1:globals.num_entities); i++) + { + gentity_t* ent = &g_entities[i]; + + if ( ent->inuse ) + { + iCount++; + } + } + + gi.AppendToSaveGame('NMED', &iCount, sizeof(iCount)); + + for (i=0; i<(qbAutosave?1:globals.num_entities); i++) + { + gentity_t* ent = &g_entities[i]; + + if ( ent->inuse) + { + gi.AppendToSaveGame('EDNM', (void *)&i, sizeof(i)); + + qboolean qbLinked = ent->linked; + gi.unlinkentity( ent ); + gentity_t tempEnt = *ent; // make local copy + tempEnt.linked = qbLinked; + + if (qbLinked) + { + gi.linkentity( ent ); + } + + EnumerateFields(savefields_gEntity, (byte *)&tempEnt, 'GENT', sizeof(tempEnt)); + + // now for any fiddly bits that would be rather awkward to build into the enumerator... + // + if (tempEnt.NPC) + { + gNPC_t npc = *ent->NPC; // NOT *tempEnt.NPC; !! :-) + + EnumerateFields(savefields_gNPC, (byte *)&npc, 'GNPC', sizeof(npc)); + } + + if (tempEnt.client == (gclient_t *)-2) // I know, I know... + { + gclient_t client = *ent->client; // NOT *tempEnt.client!! + EnumerateFields(savefields_gClient, (byte *)&client, 'GCLI', sizeof(client)); + } + + if (tempEnt.parms) + { + gi.AppendToSaveGame('PARM', ent->parms, sizeof(*ent->parms)); + } + + if (tempEnt.m_pVehicle) + { + Vehicle_t vehicle = *ent->m_pVehicle; // NOT *tempEnt.m_pVehicle!! + EnumerateFields(savefields_gVHIC, (byte *)&vehicle, 'VHIC', sizeof(vehicle)); + } + + // the scary ghoul2 saver stuff... (fingers crossed) + // + gi.G2API_SaveGhoul2Models(tempEnt.ghoul2); + tempEnt.ghoul2.kill(); // this handle was shallow copied from an ent. We don't want it destroyed + } + } + + //Write out all entity timers + TIMER_Save();//WriteEntityTimers(); + + if (!qbAutosave) + { + //Save out ICARUS information + IIcarusInterface::GetIcarus()->Save(); + + // this marker needs to be here, it lets me know if Icarus doesn't load everything back later, + // which has happened, and doesn't always show up onscreen until certain game situations. + // This saves time debugging, and makes things easier to track. + // + static int iBlah = 1234; + gi.AppendToSaveGame('ICOK', &iBlah, sizeof(iBlah)); + } + if (!qbAutosave )//really shouldn't need to write these bits at all, just restore them from the ents... + { + WriteInUseBits(); + } +} + +static void ReadGEntities(qboolean qbAutosave) +{ + int iCount; + + gi.ReadFromSaveGame('NMED', (void *)&iCount, sizeof(iCount)); + + int iPreviousEntRead = -1; + for (int i=0; i= globals.num_entities) + { + globals.num_entities = iEntIndex + 1; + } + + if (iPreviousEntRead != iEntIndex-1) + { + for (int j=iPreviousEntRead+1; j!=iEntIndex; j++) + { + if ( g_entities[j].inuse ) // not actually necessary + { + G_FreeEntity(&g_entities[j]); + } + } + } + iPreviousEntRead = iEntIndex; + + // slightly naff syntax here, but makes a few ops clearer later... + // + gentity_t entity; + gentity_t* pEntOriginal = &entity; + gentity_t* pEnt = &g_entities[iEntIndex]; + *pEntOriginal = *pEnt; // struct copy, so we can refer to original + + pEntOriginal->ghoul2.kill(); + gi.unlinkentity(pEnt); + Quake3Game()->FreeEntity( pEnt ); + + // + // sneaky: destroy the ghoul2 object within this struct before binary-loading over the top of it... + // + gi.G2API_LoadSaveCodeDestructGhoul2Info(pEnt->ghoul2); + pEnt->ghoul2.kill(); + EvaluateFields(savefields_gEntity, (byte *)pEnt, (byte *)pEntOriginal, 'GENT', sizeof(*pEnt),qfalse); + pEnt->ghoul2.kill(); + + // now for any fiddly bits... + // + if (pEnt->NPC) // will be qtrue/qfalse + { + gNPC_t tempNPC; + + EvaluateFields(savefields_gNPC, (byte *)&tempNPC,(byte *)pEntOriginal->NPC, 'GNPC', sizeof (*pEnt->NPC),qfalse); + + // so can we pinch the original's one or do we have to alloc a new one?... + // + if (pEntOriginal->NPC) + { + // pinch this G_Alloc handle... + // + pEnt->NPC = pEntOriginal->NPC; + } + else + { + // original didn't have one (hmmm...), so make a new one... + // + //assert(0); // I want to know about this, though not in release + pEnt->NPC = (gNPC_t *) G_Alloc(sizeof(*pEnt->NPC)); + } + + // copy over the one we've just loaded... + // + *pEnt->NPC = tempNPC; // struct copy + + //FIXME: do we need to do these too? + /* + if ( pEnt->s.number ) + {//not player + G_LoadAnimFileSet( *pEnt, *pEnt->NPC_type ); + G_SetSkin( *pEnt, *pEnt->NPC_type, NULL );// it probably wasn't the default skin, do we need this at all? + } + */ + } + + if (pEnt->client == (gclient_t*) -2) // one of Mike G's NPC clients? + { + gclient_t tempGClient; + + EvaluateFields(savefields_gClient, (byte *)&tempGClient, (byte *)pEntOriginal->client, 'GCLI', sizeof(*pEnt->client),qfalse); + + // can we pinch the original's client handle or do we have to alloc a new one?... + // + if (pEntOriginal->client) + { + // pinch this G_Alloc handle... + // + pEnt->client = pEntOriginal->client; + } + else + { + // original didn't have one (hmmm...) so make a new one... + // + pEnt->client = (gclient_t *) G_Alloc(sizeof(*pEnt->client)); + } + + // copy over the one we've just loaded.... + // + *pEnt->client = tempGClient; // struct copy + + if ( pEnt->s.number ) + {//not player + G_ReloadSaberData( pEnt ); + } + } + + // Some Icarus thing... (probably) + // + if (pEnt->parms) // will be qtrue/qfalse + { + parms_t tempParms; + + gi.ReadFromSaveGame('PARM', &tempParms, sizeof(tempParms)); + + // so can we pinch the original's one or do we have to alloc a new one?... + // + if (pEntOriginal->parms) + { + // pinch this G_Alloc handle... + // + pEnt->parms = pEntOriginal->parms; + } + else + { + // original didn't have one, so make a new one... + // + pEnt->parms = (parms_t *) G_Alloc(sizeof(*pEnt->parms)); + } + + // copy over the one we've just loaded... + // + *pEnt->parms = tempParms; // struct copy + } + + if (pEnt->m_pVehicle) // will be qtrue/qfalse + { + Vehicle_t tempVehicle; + + EvaluateFields(savefields_gVHIC, (byte *)&tempVehicle,(byte *)pEntOriginal->m_pVehicle, 'VHIC', sizeof (*pEnt->m_pVehicle),qfalse); + + // so can we pinch the original's one or do we have to alloc a new one?... + // + if (pEntOriginal->m_pVehicle) + { + // pinch this G_Alloc handle... + // + pEnt->m_pVehicle = pEntOriginal->m_pVehicle; + } + else + { + // original didn't have one, so make a new one... + // + pEnt->m_pVehicle = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qfalse ); + } + + // copy over the one we've just loaded... + // + *pEnt->m_pVehicle = tempVehicle; // struct copy + } + + // the scary ghoul2 stuff... (fingers crossed) + // + { + char *pGhoul2Data = NULL; + gi.ReadFromSaveGame('GHL2', 0, 0, (void**)&pGhoul2Data); + gi.G2API_LoadGhoul2Models(pEnt->ghoul2, pGhoul2Data); // if it's going to crash anywhere... + gi.Free(pGhoul2Data); + } + +// gi.unlinkentity (pEntOriginal); +// ICARUS_FreeEnt( pEntOriginal ); +// *pEntOriginal = *pEnt; // struct copy +// qboolean qbLinked = pEntOriginal->linked; +// pEntOriginal->linked = qfalse; +// if (qbLinked) +// { +// gi.linkentity (pEntOriginal); +// } + + // because the sytem stores sfx_t handles directly instead of the set, we have to reget the set's sfx_t... + // + if (pEnt->s.eType == ET_MOVER && pEnt->s.loopSound>0) + { + if ( VALIDSTRING( pEnt->soundSet )) + { + extern int BMS_MID; // from g_mover + pEnt->s.loopSound = CAS_GetBModelSound( pEnt->soundSet, BMS_MID ); + if (pEnt->s.loopSound == -1) + { + pEnt->s.loopSound = 0; + } + } + } + + // NPCs and other ents store waypoints that aren't valid after a load + pEnt->waypoint = 0; + // Hazard troopers store a troop value that isn't valid either: + if( pEnt->NPC ) + pEnt->NPC->troop = 0; + + qboolean qbLinked = pEnt->linked; + pEnt->linked = qfalse; + if (qbLinked) + { + gi.linkentity (pEnt); + } + } + + //Read in all the entity timers + TIMER_Load();//ReadEntityTimers(); + + if (!qbAutosave) + { + // now zap any g_ents that were inuse when the level was loaded, but are no longer in use in the saved version + // that we've just loaded... + // + for (i=iPreviousEntRead+1; iClearEntityList(); + + IIcarusInterface::GetIcarus()->Load(); + + // check that Icarus has loaded everything it saved out by having a marker chunk after it... + // + static int iBlah = 1234; + gi.ReadFromSaveGame('ICOK', &iBlah, sizeof(iBlah)); + } + if (!qbAutosave) + { + ReadInUseBits();//really shouldn't need to read these bits in at all, just restore them from the ents... + } +} + + +void WriteLevel(qboolean qbAutosave) +{ + if (!qbAutosave) //-always save the client + { + // write out one client - us! + // + assert(level.maxclients == 1); // I'll need to know if this changes, otherwise I'll need to change the way ReadGame works + gclient_t client = level.clients[0]; + EnumerateFields(savefields_gClient, (byte *)&client, 'GCLI', sizeof(client)); + WriteLevelLocals(); // level_locals_t level + } + + OBJ_SaveObjectiveData(); + FX_Write(); + + ///////////// + WriteGEntities(qbAutosave); + Quake3Game()->VariableSave(); + G_LoadSave_WriteMiscData(); + + extern void CG_WriteTheEvilCGHackStuff(void); + CG_WriteTheEvilCGHackStuff(); + + // (Do NOT put any write-code below this line) + // + // put out an end-marker so that the load code can check everything was read in... + // + static int iDONE = 1234; + gi.AppendToSaveGame('DONE', &iDONE, sizeof(iDONE)); +} + +void ReadLevel(qboolean qbAutosave, qboolean qbLoadTransition) +{ + if ( qbLoadTransition ) + { + // I STRONGLY SUSPECT THAT THIS WILL JUST ERR_DROP BECAUSE OF THE LOAD SWAPPING OF THE CHUNK-ORDER + // BELOW BETWEEN OBJECTIVES AND LEVEL_LOCALS, SO I'M GUESSING THIS IS SOME OLD EF1 JUNK? + // IN ANY CASE, LET'S MAKE SURE... // -ste (no idea who wrote the comment stuff below, did it ever work?) + // + assert(0); + // + //loadtransitions do not need to read the objectives and client data from the level they're going to + //In a loadtransition, client data is carried over on the server and will be stomped later anyway. + //The objective info (in client->sess data), however, is read in from G_ReadSessionData which is called before this func, + //we do NOT want to stomp that session data when doing a load transition + + //However, we should still save this info out because these savegames may need to be + //loaded normally later- perhaps if you die and need to respawn, perhaps as some kind + //of emergency savegame for resuming, etc. + + //SO: We read it in, but throw it away. + + //Read & throw away gclient info + gclient_t junkClient; + EvaluateFields(savefields_gClient, (byte *)&junkClient, (byte *)&level.clients[0], 'GCLI', sizeof(*level.clients), qfalse); + + //Read & throw away objective info + objectives_t junkObj[MAX_MISSION_OBJ]; + gi.ReadFromSaveGame('OBJT', (void *) &junkObj, 0); + + ReadLevelLocals(); // level_locals_t level + } + else + { + if (!qbAutosave )//always load the client unless it's an autosave + { + assert(level.maxclients == 1); // I'll need to know if this changes, otherwise I'll need to change the way things work + + gclient_t GClient; + EvaluateFields(savefields_gClient, (byte *)&GClient, (byte *)&level.clients[0], 'GCLI', sizeof(*level.clients), qfalse); + level.clients[0] = GClient; // struct copy + ReadLevelLocals(); // level_locals_t level + } + + OBJ_LoadObjectiveData();//loads mission objectives AND tactical info + } + + FX_Read(); + + ///////////// + + ReadGEntities(qbAutosave); + Quake3Game()->VariableLoad(); + G_LoadSave_ReadMiscData(); + + extern void CG_ReadTheEvilCGHackStuff(void); + CG_ReadTheEvilCGHackStuff(); + + // (Do NOT put any read-code below this line) + // + // check that the whole file content was loaded by specifically requesting an end-marker... + // + static int iDONE = 1234; + gi.ReadFromSaveGame('DONE', &iDONE, sizeof(iDONE)); +} + +extern int killPlayerTimer; +qboolean GameAllowedToSaveHere(void) +{ + return (!in_camera&&!killPlayerTimer); +} + +//////////////////// eof ///////////////////// + +#if 0 +// !!!!!!!!!!!!!!!!!! loadsave affecting structure !!!!!!!!!!!!!!!!!!!!!!! +struct Vehicle_t +{ + // The entity who pilots/drives this vehicle. + // NOTE: This is redundant (since m_pParentEntity->owner _should_ be the pilot). This makes things clearer though. + gentity_t *m_pPilot; + + int m_iPilotTime; //if spawnflag to die without pilot and this < level.time then die. + qboolean m_bHasHadPilot; //qtrue once the vehicle gets its first pilot + + // The passengers of this vehicle. + gentity_t **m_ppPassengers; + + // The number of passengers currently in this vehicle. + int m_iNumPassengers; + + //the droid unit NPC for this vehicle, if any + gentity_t *m_pDroidUnit; + + // The entity from which this NPC comes from. + gentity_t *m_pParentEntity; + + // If not zero, how long to wait before we can do anything with the vehicle (we're getting on still). + // -1 = board from left, -2 = board from right, -3 = jump/quick board. -4 & -5 = throw off existing pilot + int m_iBoarding; + + // Used to check if we've just started the boarding process + bool m_bWasBoarding; + + // The speed the vehicle maintains while boarding occurs (often zero) + vec3_t m_vBoardingVelocity; + + // Time modifier (must only be used in ProcessMoveCommands() and ProcessOrientCommands() and is updated in Update()). + float m_fTimeModifier; + + // Ghoul2 Animation info. + // NOTE: Since each vehicle has their own model instance, these bolts must be local to each vehicle as well. + int m_iLeftWingBone; + int m_iRightWingBone; + //int m_iDriverTag; + int m_iExhaustTag[MAX_VEHICLE_EXHAUSTS]; + int m_iMuzzleTag[MAX_VEHICLE_MUZZLES]; + int m_iDroidUnitTag; + int m_iGunnerViewTag[MAX_VEHICLE_TURRETS];//Where to put the view origin of the gunner (index) + + // This vehicles weapon muzzles. + Muzzle m_Muzzles[MAX_VEHICLE_MUZZLES]; + + // The user commands structure. + usercmd_t m_ucmd; + + // The direction an entity will eject from the vehicle towards. + int m_EjectDir; + + // Flags that describe the vehicles behavior. + unsigned long m_ulFlags; + + // NOTE: Vehicle Type ID, Orientation, and Armor MUST be transmitted over the net. + + // Current angles of this vehicle. + vec3_t m_vOrientation; + + // How long you have strafed left or right (increments every frame that you strafe to right, decrements every frame you strafe left) + int m_fStrafeTime; + + // Previous angles of this vehicle. + vec3_t m_vPrevOrientation; + + // When control is lost on a speeder, current angular velocity is stored here and applied until landing + float m_vAngularVelocity; + + vec3_t m_vFullAngleVelocity; + + // Current armor and shields of your vehicle (explodes if armor to 0). + int m_iArmor; //hull strength - STAT_HEALTH on NPC + int m_iShields; //energy shielding - STAT_ARMOR on NPC + + // Timer for all cgame-FX...? ex: exhaust? + int m_iLastFXTime; + + // When to die. + int m_iDieTime; + + // This pointer is to a valid VehicleInfo (which could be an animal, speeder, fighter, whatever). This + // contains the functions actually used to do things to this specific kind of vehicle as well as shared + // information (max speed, type, etc...). + vehicleInfo_t *m_pVehicleInfo; + + // This trace tells us if we're within landing height. + trace_t m_LandTrace; + + //bitflag of surfaces that have broken off + int m_iRemovedSurfaces; + + // the last time this vehicle fired a turbo burst + int m_iTurboTime; + + //how long it should drop like a rock for after freed from SUSPEND + int m_iDropTime; + + int m_iSoundDebounceTimer; + + //last time we incremented the shields + int lastShieldInc; + + //so we don't hold it down and toggle it back and forth + qboolean linkWeaponToggleHeld; + + //info about our weapons (linked, ammo, etc.) + vehWeaponStatus_t weaponStatus[MAX_VEHICLE_WEAPONS]; + vehTurretStatus_t turretStatus[MAX_VEHICLE_TURRETS]; + + //the guy who was previously the pilot + gentity_t* m_pOldPilot; + + // don't need these in mp + int m_safeJumpMountTime; + float m_safeJumpMountRightDot; +}; + +#endif diff --git a/code/game/g_session.cpp b/code/game/g_session.cpp new file mode 100644 index 0000000..554b46a --- /dev/null +++ b/code/game/g_session.cpp @@ -0,0 +1,221 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "objectives.h" + + +/* +======================================================================= + + SESSION DATA + +Session data is the only data that stays persistant across level loads +and tournament restarts. +======================================================================= +*/ + +/* +================ +G_WriteClientSessionData + +Called on game shutdown +================ +*/ +void G_WriteClientSessionData( gclient_t *client ) { + const char *s; + const char *s2; + const char *var; + int i; + + s = va("%i", client->sess.sessionTeam ); + var = va( "session%i", client - level.clients ); + gi.cvar_set( var, s ); + + s2 = ""; + // Throw all status info into a string +// for (i=0;i< MAX_OBJECTIVES; i++) +// { +// s2 = va("%s %i %i", s2, client->sess.mission_objectives[i].display, client->sess.mission_objectives[i].status); +// } + + // We're saving only one objective + s2 = va("%i %i", client->sess.mission_objectives[LIGHTSIDE_OBJ].display, client->sess.mission_objectives[LIGHTSIDE_OBJ].status); + + var = va( "sessionobj%i", client - level.clients ); + gi.cvar_set( var, s2 ); + + // Throw all mission stats in to a string + s2 = va("%i %i %i %i %i %i %i %i %i %i %i %i", + client->sess.missionStats.secretsFound, + client->sess.missionStats.totalSecrets, + client->sess.missionStats.shotsFired, + client->sess.missionStats.hits, + client->sess.missionStats.enemiesSpawned, + client->sess.missionStats.enemiesKilled, + client->sess.missionStats.saberThrownCnt, + client->sess.missionStats.saberBlocksCnt, + client->sess.missionStats.legAttacksCnt, + client->sess.missionStats.armAttacksCnt, + client->sess.missionStats.torsoAttacksCnt, + client->sess.missionStats.otherAttacksCnt + ); + + var = va( "missionstats%i", client - level.clients ); + gi.cvar_set( var, s2 ); + + + s2 = ""; + for (i=0;i< NUM_FORCE_POWERS; i++) + { + s2 = va("%s %i",s2, client->sess.missionStats.forceUsed[i]); + } + var = va( "sessionpowers%i", client - level.clients ); + gi.cvar_set( var, s2 ); + + + s2 = ""; + for (i=0;i< WP_NUM_WEAPONS; i++) + { + s2 = va("%s %i",s2, client->sess.missionStats.weaponUsed[i]); + } + var = va( "sessionweapons%i", client - level.clients ); + gi.cvar_set( var, s2 ); + + +} + +/* +================ +G_ReadSessionData + +Called on a reconnect +================ +*/ +void G_ReadSessionData( gclient_t *client ) { + char s[MAX_STRING_CHARS]; + const char *var; + int i; + + var = va( "session%i", client - level.clients ); + gi.Cvar_VariableStringBuffer( var, s, sizeof(s) ); + + sscanf( s, "%i", &client->sess.sessionTeam ); + + var = va( "sessionobj%i", client - level.clients ); + gi.Cvar_VariableStringBuffer( var, s, sizeof(s) ); + + var = s; +// var++; + +// for (i=0;i< MAX_OBJECTIVES; i++) +// { +// sscanf( var, "%i %i", +// &client->sess.mission_objectives[i].display, +// &client->sess.mission_objectives[i].status); +// var+=4; +// } + // Clear the objectives out + for (i=0;i< MAX_OBJECTIVES; i++) + { + client->sess.mission_objectives[i].display = 0; + client->sess.mission_objectives[i].status = OBJECTIVE_STAT_PENDING; + } + + // Now load the LIGHTSIDE objective. That's the only cross level objective. + sscanf( var, "%i %i", + &client->sess.mission_objectives[LIGHTSIDE_OBJ].display, + &client->sess.mission_objectives[LIGHTSIDE_OBJ].status); + + var = va( "missionstats%i", client - level.clients ); + gi.Cvar_VariableStringBuffer( var, s, sizeof(s) ); + sscanf( s, "%i %i %i %i %i %i %i %i %i %i %i %i", + &client->sess.missionStats.secretsFound, + &client->sess.missionStats.totalSecrets, + &client->sess.missionStats.shotsFired, + &client->sess.missionStats.hits, + &client->sess.missionStats.enemiesSpawned, + &client->sess.missionStats.enemiesKilled, + &client->sess.missionStats.saberThrownCnt, + &client->sess.missionStats.saberBlocksCnt, + &client->sess.missionStats.legAttacksCnt, + &client->sess.missionStats.armAttacksCnt, + &client->sess.missionStats.torsoAttacksCnt, + &client->sess.missionStats.otherAttacksCnt); + + + var = va( "sessionpowers%i", client - level.clients ); + gi.Cvar_VariableStringBuffer( var, s, sizeof(s) ); + + i=0; + var = strtok( s, " " ); + while( var != NULL ) + { + /* While there are tokens in "s" */ + client->sess.missionStats.forceUsed[i++] = atoi(var); + /* Get next token: */ + var = strtok( NULL, " " ); + } + assert (i==NUM_FORCE_POWERS); + + var = va( "sessionweapons%i", client - level.clients ); + gi.Cvar_VariableStringBuffer( var, s, sizeof(s) ); + + i=0; + var = strtok( s, " " ); + while( var != NULL ) + { + /* While there are tokens in "s" */ + client->sess.missionStats.weaponUsed[i++] = atoi(var); + /* Get next token: */ + var = strtok( NULL, " " ); + } + assert (i==WP_NUM_WEAPONS); +} + + +/* +================ +G_InitSessionData + +Called on a first-time connect +================ +*/ +void G_InitSessionData( gclient_t *client, char *userinfo ) { + clientSession_t *sess; + + sess = &client->sess; + + sess->sessionTeam = TEAM_FREE; + + G_WriteClientSessionData( client ); +} + + +/* +================== +G_InitWorldSession + +================== +*/ +void G_InitWorldSession( void ) { +} + +/* +================== +G_WriteSessionData + +================== +*/ +void G_WriteSessionData( void ) { + int i; + + gi.cvar_set( "session", 0) ; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.connected == CON_CONNECTED ) { + G_WriteClientSessionData( &level.clients[i] ); + } + } +} diff --git a/code/game/g_shared.h b/code/game/g_shared.h new file mode 100644 index 0000000..734ffd2 --- /dev/null +++ b/code/game/g_shared.h @@ -0,0 +1,931 @@ +#ifndef __G_SHARED_H__ +#define __G_SHARED_H__ + +#include "bg_public.h" +#include "g_public.h" +#include "b_public.h" +#include "../Icarus/Stdafx.h" //need stl +#include "../renderer/tr_types.h" +#include "../cgame/cg_public.h" +#include "G_Vehicles.h" +#include "hitlocs.h" +#include "bset.h" + +#define FOFS(x) ((int)&(((gentity_t *)0)->x)) + +#ifdef _XBOX +#define MAX_NPC_WATER_UPDATE_PER_FRAME 2 // maxmum number of NPCs that will get updated water infromation per frame +#endif + +typedef enum //# taskID_e +{ + TID_CHAN_VOICE = 0, // Waiting for a voice sound to complete + TID_ANIM_UPPER, // Waiting to finish a lower anim holdtime + TID_ANIM_LOWER, // Waiting to finish a lower anim holdtime + TID_ANIM_BOTH, // Waiting to finish lower and upper anim holdtimes or normal md3 animating + TID_MOVE_NAV, // Trying to get to a navgoal or For ET_MOVERS + TID_ANGLE_FACE, // Turning to an angle or facing + TID_BSTATE, // Waiting for a certain bState to finish + TID_LOCATION, // Waiting for ent to enter a specific trigger_location +// TID_MISSIONSTATUS, // Waiting for player to finish reading MISSION STATUS SCREEN + TID_RESIZE, // Waiting for clear bbox to inflate size + TID_SHOOT, // Waiting for fire event + NUM_TIDS, // for def of taskID array +} taskID_t; + + +typedef enum //# material_e +{ + MAT_METAL = 0, // scorched blue-grey metal + MAT_GLASS, // not a real chunk type, just plays an effect with glass sprites + MAT_ELECTRICAL, // sparks only + MAT_ELEC_METAL, // sparks/electrical type metal + MAT_DRK_STONE, // brown + MAT_LT_STONE, // tan + MAT_GLASS_METAL,// glass sprites and METAl chunk + MAT_METAL2, // electrical metal type + MAT_NONE, // no chunks + MAT_GREY_STONE, // grey + MAT_METAL3, // METAL and METAL2 chunks + MAT_CRATE1, // yellow multi-colored crate chunks + MAT_GRATE1, // grate chunks + MAT_ROPE, // for yavin trial...no chunks, just wispy bits + MAT_CRATE2, // read multi-colored crate chunks + MAT_WHITE_METAL,// white angular chunks + + NUM_MATERIALS + +} material_t; + +//===From cg_local.h================================================ +#define DEFAULT_HEADMODEL "" +#define DEFAULT_TORSOMODEL "" +#define DEFAULT_LEGSMODEL "mouse" + +// each client has an associated clientInfo_t +// that contains media references necessary to present the +// client model and other color coded effects +// this is regenerated each time a userinfo configstring changes + +#define MAX_CUSTOM_BASIC_SOUNDS 14 +#define MAX_CUSTOM_COMBAT_SOUNDS 17 +#define MAX_CUSTOM_EXTRA_SOUNDS 36 +#define MAX_CUSTOM_JEDI_SOUNDS 22 +#define MAX_CUSTOM_SOUNDS (MAX_CUSTOM_JEDI_SOUNDS + MAX_CUSTOM_EXTRA_SOUNDS + MAX_CUSTOM_COMBAT_SOUNDS + MAX_CUSTOM_BASIC_SOUNDS) +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct { + qboolean infoValid; + + char name[MAX_QPATH]; + team_t team; + + int score; // updated by score servercmds + + int handicap; + + qhandle_t legsModel; + qhandle_t legsSkin; + + qhandle_t torsoModel; + qhandle_t torsoSkin; + + qhandle_t headModel; + qhandle_t headSkin; + + int animFileIndex; + + sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; + + char *customBasicSoundDir; + char *customCombatSoundDir; + char *customExtraSoundDir; + char *customJediSoundDir; +} clientInfo_t; + + +//================================================================== +typedef enum +{ + MOVER_POS1, + MOVER_POS2, + MOVER_1TO2, + MOVER_2TO1 +} moverState_t; + +// Rendering information structure + + +typedef enum +{ + MODEL_LEGS = 0, + MODEL_TORSO, + MODEL_HEAD, + MODEL_WEAPON1, + MODEL_WEAPON2, + MODEL_WEAPON3, + MODEL_EXTRA1, + MODEL_EXTRA2, + NUM_TARGET_MODELS +} targetModel_t; + +//renderFlags +#define RF_LOCKEDANGLE 1 + +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct renderInfo_s +{ + // Legs model, or full model on one piece entities + + union + { + char legsModelName[32]; // -slc[] + char modelName[32]; // -slc[] + }; + + char torsoModelName[32]; // -slc[] + char headModelName[32]; // -slc[] + + //In whole degrees, How far to let the different model parts yaw and pitch + int headYawRangeLeft; + int headYawRangeRight; + int headPitchRangeUp; + int headPitchRangeDown; + + int torsoYawRangeLeft; + int torsoYawRangeRight; + int torsoPitchRangeUp; + int torsoPitchRangeDown; + + int legsFrame; + int torsoFrame; + + float legsFpsMod; + float torsoFpsMod; + + //Fields to apply to entire model set, individual model's equivalents will modify this value + byte customRGBA[4];//Red Green Blue, 0 = don't apply + + //Allow up to 4 PCJ lookup values to be stored here. + //The resolve to configstrings which contain the name of the + //desired bone. + int boneIndex1; + int boneIndex2; + int boneIndex3; + int boneIndex4; + + //packed with x, y, z orientations for bone angles + int boneOrient; + + //I.. feel bad for doing this, but NPCs really just need to + //be able to control this sort of thing from the server sometimes. + //At least it's at the end so this stuff is never going to get sent + //over for anything that isn't an NPC. + vec3_t boneAngles1; //angles of boneIndex1 + vec3_t boneAngles2; //angles of boneIndex2 + vec3_t boneAngles3; //angles of boneIndex3 + vec3_t boneAngles4; //angles of boneIndex4 + + //RF? + int renderFlags; + + // + vec3_t muzzlePoint; + vec3_t muzzleDir; + vec3_t muzzlePointOld; + vec3_t muzzleDirOld; + //vec3_t muzzlePointNext; // Muzzle point one server frame in the future! + //vec3_t muzzleDirNext; + int mPCalcTime;//Last time muzzle point was calced + + // + float lockYaw;// + + // + vec3_t headPoint;//Where your tag_head is + vec3_t headAngles;//where the tag_head in the torso is pointing + vec3_t handRPoint;//where your right hand is + vec3_t handLPoint;//where your left hand is + vec3_t crotchPoint;//Where your crotch is + vec3_t footRPoint;//where your right hand is + vec3_t footLPoint;//where your left hand is + vec3_t torsoPoint;//Where your chest is + vec3_t torsoAngles;//Where the chest is pointing + vec3_t eyePoint;//Where your eyes are + vec3_t eyeAngles;//Where your eyes face + int lookTarget;//Which ent to look at with lookAngles + lookMode_t lookMode; + int lookTargetClearTime;//Time to clear the lookTarget + int lastVoiceVolume;//Last frame's voice volume + vec3_t lastHeadAngles;//Last headAngles, NOT actual facing of head model + vec3_t headBobAngles;//headAngle offsets + vec3_t targetHeadBobAngles;//head bob angles will try to get to targetHeadBobAngles + int lookingDebounceTime;//When we can stop using head looking angle behavior + float legsYaw;//yaw angle your legs are actually rendering at +} renderInfo_t; + +// Movement information structure + +/* +typedef struct moveInfo_s // !!!!!!!!!! LOADSAVE-affecting struct !!!!!!!! +{ + vec3_t desiredAngles; // Desired facing angles + float speed; // Speed of movement + float aspeed; // Speed of angular movement + vec3_t moveDir; // Direction of movement + vec3_t velocity; // movement velocity + int flags; // Special state flags +} moveInfo_t; +*/ + +typedef enum { + CON_DISCONNECTED, + CON_CONNECTING, + CON_CONNECTED +} clientConnected_t; + +typedef enum { + TEAM_BEGIN, // Beginning a team game, spawn at base + TEAM_ACTIVE // Now actively playing +} playerTeamStateState_t; +/* +typedef enum //# race_e +{ + RACE_NONE = 0, + RACE_HUMAN, + RACE_BORG, + RACE_KLINGON, + RACE_HIROGEN, + RACE_MALON, + RACE_STASIS, + RACE_8472, + RACE_BOT, + RACE_HARVESTER, + RACE_REAVER, + RACE_AVATAR, + RACE_PARASITE, + RACE_VULCAN, + RACE_BETAZOID, + RACE_BOLIAN, + RACE_TALAXIAN, + RACE_BAJORAN, + RACE_HOLOGRAM +} race_t; +*/ +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct { + playerTeamStateState_t state; + + int captures; + int basedefense; + int carrierdefense; + int flagrecovery; + int fragcarrier; + int assists; + + float lasthurtcarrier; + float lastreturnedflag; + float flagsince; + float lastfraggedcarrier; +} playerTeamState_t; + +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct objectives_s +{ + qboolean display; // A displayable objective? + int status; // Succeed or fail or pending +} objectives_t; +// NOTE: This is an arbitrary number greater than our current number of objectives with +// some fluff just in case we add more in the future. +#define MAX_MISSION_OBJ 100 + +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct missionStats_s +{ + int secretsFound; // # of secret areas found + int totalSecrets; // # of secret areas that could have been found + int shotsFired; // total number of shots fired + int hits; // Shots that did damage + int enemiesSpawned; // # of enemies spawned + int enemiesKilled; // # of enemies killed + int saberThrownCnt; // # of times saber was thrown + int saberBlocksCnt; // # of times saber was used to block + int legAttacksCnt; // # of times legs were hit with saber + int armAttacksCnt; // # of times arm were hit with saber + int torsoAttacksCnt; // # of times torso was hit with saber + int otherAttacksCnt; // # of times anything else on a monster was hit with saber + int forceUsed[NUM_FORCE_POWERS]; // # of times each force power was used + int weaponUsed[WP_NUM_WEAPONS]; // # of times each weapon was used +} missionStats_t; + +// the auto following clients don't follow a specific client +// number, but instead follow the first two active players +#define FOLLOW_ACTIVE1 -1 +#define FOLLOW_ACTIVE2 -2 + +// client data that stays across multiple levels or tournament restarts +// this is achieved by writing all the data to cvar strings at game shutdown +// time and reading them back at connection time. Anything added here +// MUST be dealt with in G_InitSessionData() / G_ReadSessionData() / G_WriteSessionData() +// +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct { + int missionObjectivesShown; // Number of times mission objectives have been updated + team_t sessionTeam; + objectives_t mission_objectives[MAX_MISSION_OBJ]; + missionStats_t missionStats; // Various totals while on a mission +} clientSession_t; + +// client data that stays across multiple respawns, but is cleared +// on each level change or team change at ClientBegin() +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct { + clientConnected_t connected; + usercmd_t lastCommand; + char netname[34]; + int maxHealth; // for handicapping + int enterTime; // level.time the client entered the game + short cmd_angles[3]; // angles sent over in the last command + + playerTeamState_t teamState; // status in teamplay games +} clientPersistant_t; + +typedef enum { + BLK_NO, + BLK_TIGHT, // Block only attacks and shots around the saber itself, a bbox of around 12x12x12 + BLK_WIDE // Block all attacks in an area around the player in a rough arc of 180 degrees +} saberBlockType_t; + +typedef enum { + BLOCKED_NONE, + BLOCKED_PARRY_BROKEN, + BLOCKED_ATK_BOUNCE, + BLOCKED_UPPER_RIGHT, + BLOCKED_UPPER_LEFT, + BLOCKED_LOWER_RIGHT, + BLOCKED_LOWER_LEFT, + BLOCKED_TOP, + BLOCKED_UPPER_RIGHT_PROJ, + BLOCKED_UPPER_LEFT_PROJ, + BLOCKED_LOWER_RIGHT_PROJ, + BLOCKED_LOWER_LEFT_PROJ, + BLOCKED_TOP_PROJ +} saberBlockedType_t; + +typedef enum //# movetype_e +{ + MT_STATIC = 0, + MT_WALK, + MT_RUNJUMP, + MT_FLYSWIM, + NUM_MOVETYPES +} movetype_t; + +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! + +// this structure is cleared on each ClientSpawn(), +// except for 'client->pers' and 'client->sess' +struct gclient_s { + // ps MUST be the first element, because the server expects it + playerState_t ps; // communicated by server to clients + + // private to game + clientPersistant_t pers; + clientSession_t sess; + + int lastCmdTime; // level.time of last usercmd_t, for EF_CONNECTION + + usercmd_t usercmd; // most recent usercmd + + int buttons; + int oldbuttons; + int latched_buttons; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_blood; // damage taken out of health + vec3_t damage_from; // origin for vector calculation + bool damage_fromWorld; // if true, don't use the damage_from vector + bool noclip; +//icarus forced moving. is this still used? + signed char forced_forwardmove; + signed char forced_rightmove; + + // timers + int respawnTime; // can respawn when time > this, force after g_forcerespwan + int idleTime; // for playing idleAnims + + int airOutTime; + + // timeResidual is used to handle events that happen every second + // like health / armor countdowns and regeneration + int timeResidual; + + // Facial Expression Timers + + float facial_blink; // time before next blink. If a minus value, we are in blink mode + float facial_timer; // time before next alert, frown or smile. If a minus value, we are in anim mode + int facial_anim; // anim to show in anim mode + + //Client info - updated when ClientInfoChanged is called, instead of using configstrings + clientInfo_t clientInfo; + movetype_t moveType; + int jetPackTime; + int fireDelay; //msec to delay calling G_FireWeapon after EV_FIREWEAPON event is called + + // The time at which a breath should be triggered. -Aurelio + int breathPuffTime; + + //Used to be in gentity_t, now here.. mostly formation stuff + team_t playerTeam; + team_t enemyTeam; + gentity_t *leader; + class_t NPC_class; + + //FIXME: could combine these + float hiddenDist;//How close ents have to be to pick you up as an enemy + vec3_t hiddenDir;//Normalized direction in which NPCs can't see you (you are hidden) + + renderInfo_t renderInfo; + + //dismember tracker + bool dismembered; + char dismemberProbLegs; // probability of the legs being dismembered (located in NPC.cfg, 0 = never, 100 = always) + char dismemberProbHead; // probability of the head being dismembered (located in NPC.cfg, 0 = never, 100 = always) + char dismemberProbArms; // probability of the arms being dismembered (located in NPC.cfg, 0 = never, 100 = always) + char dismemberProbHands; // probability of the hands being dismembered (located in NPC.cfg, 0 = never, 100 = always) + char dismemberProbWaist; // probability of the waist being dismembered (located in NPC.cfg, 0 = never, 100 = always) + + int standheight; + int crouchheight; + int poisonDamage; // Amount of poison damage to be given + int poisonTime; // When to apply poison damage + int slopeRecalcTime; // debouncer for slope-foot-height-diff calcing + + vec3_t pushVec; + int pushVecTime; + + int noRagTime; //don't do ragdoll stuff if > level.time + qboolean isRagging; + int overridingBones; //dragging body or doing something else to override one or more ragdoll effector's/pcj's + + vec3_t ragLastOrigin; //keeping track of positions between rags while dragging corpses + int ragLastOriginTime; + + //push refraction effect vars + int pushEffectFadeTime; + vec3_t pushEffectOrigin; + + //Rocket locking vars for non-player clients (only Vehicles use these right now...) + int rocketLockIndex; + float rocketLastValidTime; + float rocketLockTime; + float rocketTargetTime; + + //for trigger_space brushes + int inSpaceSuffocation; + int inSpaceIndex; +}; + +#define MAX_PARMS 16 +#define MAX_PARM_STRING_LENGTH MAX_QPATH//was 16, had to lengthen it so they could take a valid file path +typedef struct +{ + char parm[MAX_PARMS][MAX_PARM_STRING_LENGTH]; +} parms_t; + +#ifdef GAME_INCLUDE +//these hold the place for the enums in functions.h so i don't have to recompile everytime it changes +#define thinkFunc_t int +#define clThinkFunc_t int +#define reachedFunc_t int +#define blockedFunc_t int +#define touchFunc_t int +#define useFunc_t int +#define painFunc_t int +#define dieFunc_t int + +#define MAX_FAILED_NODES 8 +#define MAX_INHAND_WEAPONS 2 + + +typedef struct centity_s centity_t; +// !!!!!!!!!!! LOADSAVE-affecting struct !!!!!!!!!!!!! +struct gentity_s { + entityState_t s; // communicated by server to clients + struct gclient_s *client; // NULL if not a player (unless it's NPC ( if (this->NPC != NULL) ) ... -slc) + qboolean inuse; + qboolean linked; // qfalse if not in any good cluster + + int svFlags; // SVF_NOCLIENT, SVF_BROADCAST, etc + + qboolean bmodel; // if false, assume an explicit mins / maxs bounding box + // only set by gi.SetBrushModel + vec3_t mins, maxs; + int contents; // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc + // a non-solid entity should set to 0 + + vec3_t absmin, absmax; // derived from mins/maxs and origin + rotation + + // currentOrigin will be used for all collision detection and world linking. + // it will not necessarily be the same as the trajectory evaluation for the current + // time, because each entity must be moved one at a time after time is advanced + // to avoid simultanious collision issues + vec3_t currentOrigin; + vec3_t currentAngles; + + gentity_t *owner; // objects never interact with their owners, to + // prevent player missiles from immediately + // colliding with their owner +/* +Ghoul2 Insert Start +*/ + // this marker thing of Jake's is used for memcpy() length calcs, so don't put any ordinary fields (like above) + // below this point or they won't work, and will mess up all sorts of stuff. + // + CGhoul2Info_v ghoul2; + + vec3_t modelScale; //needed for g2 collision +/* +Ghoul2 Insert End +*/ + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + +//========================================================================================== + +//Essential entity fields + // note: all the char* fields from here on should be left as ptrs, not declared, because of the way that ent-parsing + // works by forcing field offset ptrs as char* and using G_NewString()!! (see G_ParseField() in gmae/g_spawn.cpp -slc + // + char *classname; // set in QuakeEd + int spawnflags; // set in QuakeEd + + int flags; // FL_* variables + + char *model; // Normal model, or legs model on tri-models + char *model2; // Torso model + + int freetime; // sv.time when the object was freed + + int eventTime; // events will be cleared EVENT_VALID_MSEC after set + qboolean freeAfterEvent; +// qboolean unlinkAfterEvent; + +//Physics and movement fields + float physicsBounce; // 1.0 = continuous bounce, 0.0 = no bounce + int clipmask; // brushes with this content value will be collided against + // when moving. items and corpses do not collide against + // players, for instance +// moveInfo_t moveInfo; //FIXME: use this more? + float speed; + float resultspeed; + int lastMoveTime; + vec3_t movedir; + vec3_t lastOrigin; //Where you were last frame + vec3_t lastAngles; //Where you were looking last frame + float mass; //How heavy you are + int lastImpact; //Last time you impacted something + +//Variables reflecting environment + int watertype; + int waterlevel; + short wupdate; + short prev_waterlevel; + +//Targeting/linking fields + float angle; // set in editor, -1 = up, -2 = down + char *target; + char *target2; //For multiple targets, not used for firing/triggering/using, though, only for path branches + char *target3; //For multiple targets, not used for firing/triggering/using, though, only for path branches + char *target4; //For multiple targets, not used for firing/triggering/using, though, only for path branches + char *targetJump; + char *targetname; + char *team; + + union + { + char *roff; // the roff file to use, if there is one + char *fxFile; // name of the external effect file + }; + + int roff_ctr; // current roff frame we are playing + + int next_roff_time; + int fx_time; // timer for beam in/out effects. + +//Think Functions + int nextthink;//Used to determine if it's time to call e_ThinkFunc again + thinkFunc_t e_ThinkFunc;//Called once every game frame for every ent + clThinkFunc_t e_clThinkFunc;//Think func for equivalent centity + reachedFunc_t e_ReachedFunc;// movers call this when hitting endpoint + blockedFunc_t e_BlockedFunc; + touchFunc_t e_TouchFunc; + useFunc_t e_UseFunc; //Called by G_UseTargets + painFunc_t e_PainFunc; //Called by G_Damage when damage is taken + dieFunc_t e_DieFunc; //Called by G_Damage when health reaches <= 0 + +//Health and damage fields + int health; + int max_health; + qboolean takedamage; + material_t material; + int damage; + int dflags; + //explosives, breakable brushes + int splashDamage; // quad will increase this without increasing radius + int splashRadius; + int methodOfDeath; + int splashMethodOfDeath; + //int hitLoc;//where you were last hit + int locationDamage[HL_MAX]; // Damage accumulated on different body locations + +//Entity pointers + gentity_t *chain; + gentity_t *enemy; + gentity_t *activator; + gentity_t *teamchain; // next entity in team + gentity_t *teammaster; // master of the team + gentity_t *lastEnemy; + +//Timing variables, counters and debounce times + float wait; + float random; + int delay; + qboolean alt_fire; + int count; + int bounceCount; + int fly_sound_debounce_time; // wind tunnel + int painDebounceTime; + int disconnectDebounceTime; + int attackDebounceTime; + int pushDebounceTime; + int aimDebounceTime; + int useDebounceTime; + +//Unions for miscellaneous fields used under very specific circumstances + union + { + qboolean trigger_formation; + qboolean misc_dlight_active; + qboolean has_bounced; // for thermal Det. we force at least one bounce to happen before it can do proximity checks + }; + +//Navigation + int spawnContents; // store contents of ents on spawn so nav system can restore them + int waypoint; //Set once per frame, if you've moved, and if someone asks + int wayedge; //Used by doors and breakable things to know what edge goes through them + int lastWaypoint; //To make sure you don't double-back + int lastInAirTime; + int noWaypointTime; //Debouncer - so don't keep checking every waypoint in existance every frame that you can't find one + int combatPoint; + vec3_t followPos; + int followPosRecalcTime; + int followPosWaypoint; + +//Animation + qboolean loopAnim; + int startFrame; + int endFrame; + +//Script/ICARUS-related fields + int m_iIcarusID; + int taskID[NUM_TIDS]; + parms_t *parms; + char *behaviorSet[NUM_BSETS]; + char *script_targetname; + int delayScriptTime; + +// Ambient sound info + char *soundSet; //Only used for local sets + int setTime; + +//Used by cameras to locate subjects + char *cameraGroup; + +//For damage + team_t noDamageTeam; + +// Ghoul2 Animation info + short playerModel; + short weaponModel[MAX_INHAND_WEAPONS]; + short handRBolt; + short handLBolt; + short headBolt; + short cervicalBolt; + short chestBolt; + short gutBolt; + short torsoBolt; + short crotchBolt; + short motionBolt; + short kneeLBolt; + short kneeRBolt; + short elbowLBolt; + short elbowRBolt; + short footLBolt; + short footRBolt; + short faceBone; + short craniumBone; + short cervicalBone; + short thoracicBone; + short upperLumbarBone; + short lowerLumbarBone; + short hipsBone; + short motionBone; + short rootBone; + short footLBone; + short footRBone; + short humerusRBone; + + short genericBone1; // For bones special to an entity + short genericBone2; + short genericBone3; + + short genericBolt1; // For bolts special to an entity + short genericBolt2; + short genericBolt3; + short genericBolt4; + short genericBolt5; + + qhandle_t cinematicModel; + +//========================================================================================== + +//FIELDS USED EXCLUSIVELY BY SPECIFIC CLASSES OF ENTITIES + // Vehicle information. + // The vehicle object. + Vehicle_t *m_pVehicle; + + //NPC/Player entity fields + //FIXME: Make these client only? + gNPC_t *NPC;//Only allocated if the entity becomes an NPC + + //Other NPC/Player-related entity fields + char *ownername;//Used by squadpaths to locate owning NPC + +//FIXME: Only used by NPCs, move it to gNPC_t + int cantHitEnemyCounter;//HACK - Makes them look for another enemy on the same team if the one they're after can't be hit + +//Only used by NPC_spawners + char *NPC_type; + char *NPC_targetname; + char *NPC_target; + +//Variables used by movers (most likely exclusively by them) + moverState_t moverState; + int soundPos1; + int sound1to2; + int sound2to1; + int soundPos2; + int soundLoop; + gentity_t *nextTrain; + gentity_t *prevTrain; + vec3_t pos1, pos2; + vec3_t pos3; + int sounds; + char *closetarget; + char *opentarget; + char *paintarget; + int lockCount; //for maglocks- actually get put on the trigger for the door + +//Variables used only by waypoints (for the most part) + float radius; + + union + { + int wpIndex; + int fxID; // id of the external effect file + }; + + int noise_index; + + vec4_t startRGBA; + + union + { + vec4_t finalRGBA; + vec3_t pos4; + vec3_t modelAngles; //for brush entities with an attached md3 model, as an offset to the brush's angles + }; + +//FIXME: Are these being used anymore? + gitem_t *item; // for bonus items - + char *message; //Used by triggers to print a message when activated + + float lightLevel; + + //FIXME: can these be removed/condensed/absorbed? + //Rendering info + //int color; + + //Force effects + int forcePushTime; + int forcePuller; //who force-pulled me (so we don't damage them if we hit them) +}; +#endif //#ifdef GAME_INCLUDE + +//extern gentity_t g_entities[MAX_GENTITIES]; +extern gentity_t *g_entities; +#ifndef _USRDLL +extern game_import_t gi; +#endif + +// each WP_* weapon enum has an associated weaponInfo_t +// that contains media references necessary to present the +// weapon and its effects +typedef struct weaponInfo_s { + qboolean registered; + gitem_t *item; + + qhandle_t handsModel; // the hands don't actually draw, they just position the weapon + qhandle_t weaponModel; //for in view + qhandle_t weaponWorldModel; //for in their hands + qhandle_t barrelModel[4]; + + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag + + qhandle_t weaponIcon; // The version of the icon with a glowy background + qhandle_t weaponIconNoAmmo; // The version of the icon with no ammo warning + qhandle_t ammoIcon; + + qhandle_t ammoModel; + + qhandle_t missileModel; + sfxHandle_t missileSound; + void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + + qhandle_t alt_missileModel; + sfxHandle_t alt_missileSound; + void (*alt_missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + +// sfxHandle_t flashSound; +// sfxHandle_t altFlashSound; + + sfxHandle_t firingSound; + sfxHandle_t altFiringSound; + + sfxHandle_t stopSound; + + sfxHandle_t missileHitSound; + sfxHandle_t altmissileHitSound; + + sfxHandle_t chargeSound; + sfxHandle_t altChargeSound; + + sfxHandle_t selectSound; // sound played when weapon is selected + +#ifdef _IMMERSION + ffHandle_t firingForce; + ffHandle_t altFiringForce; + ffHandle_t stopForce; + ffHandle_t chargeForce; + ffHandle_t altChargeForce; + ffHandle_t selectForce; +#endif // _IMMERSION +} weaponInfo_t; + +extern sfxHandle_t CAS_GetBModelSound( const char *name, int stage ); + +enum +{ + EDGE_NORMAL, + EDGE_PATH, + EDGE_BLOCKED, + EDGE_FAILED, + EDGE_FLY, + EDGE_JUMP, + EDGE_LARGE, + EDGE_PATHBLOCKED, + EDGE_NEARESTVALID, + EDGE_NEARESTINVALID, + + EDGE_NODE_FLOATING, + EDGE_NODE_NORMAL, + EDGE_NODE_GOAL, + EDGE_NODE_COMBAT, + + EDGE_CELL, + EDGE_CELL_EMPTY, + EDGE_IMPACT_SAFE, + EDGE_IMPACT_POSSIBLE, + EDGE_THRUST, + EDGE_VELOCITY, + + EDGE_FOLLOWPOS, + + EDGE_WHITE_ONESECOND, + EDGE_WHITE_TWOSECOND, + EDGE_RED_ONESECOND, + EDGE_RED_TWOSECOND, +}; + +enum +{ + NODE_NORMAL, + NODE_FLOATING, + NODE_GOAL, + NODE_NAVGOAL, +}; + +#endif // #ifndef __G_SHARED_H__ diff --git a/code/game/g_spawn.cpp b/code/game/g_spawn.cpp new file mode 100644 index 0000000..6eadc9c --- /dev/null +++ b/code/game/g_spawn.cpp @@ -0,0 +1,1655 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "Q3_Interface.h" +#include "g_local.h" +#include "g_functions.h" + +extern cvar_t *g_spskill; +extern cvar_t *g_delayedShutdown; + +// these vars I moved here out of the level_locals_t struct simply because it's pointless to try saving them, +// and the level_locals_t struct is included in the save process... -slc +// +qboolean spawning = qfalse; // the G_Spawn*() functions are valid (only turned on during one function) +int numSpawnVars; +char *spawnVars[MAX_SPAWN_VARS][2]; // key / value pairs +int numSpawnVarChars; +char spawnVarChars[MAX_SPAWN_VARS_CHARS]; + +int delayedShutDown = 0; + +#include "../qcommon/sstring.h" + +//NOTENOTE: Be sure to change the mirrored code in cgmain.cpp +typedef map< sstring_t, unsigned char, less, allocator< unsigned char > > namePrecache_m; +namePrecache_m *as_preCacheMap = NULL; + +char *G_AddSpawnVarToken( const char *string ); + +void AddSpawnField(char *field, char *value) +{ + int i; + + for(i=0;i= numSpawnVars ) + return qfalse; + + (*ppKey) = spawnVars[uiField][0]; + *ppValue = spawnVars[uiField][1]; + + return qtrue; +} + +qboolean G_SpawnString( const char *key, const char *defaultString, char **out ) { + int i; + + if ( !spawning ) { + *out = (char *)defaultString; +// G_Error( "G_SpawnString() called while not spawning" ); + } + + for ( i = 0 ; i < numSpawnVars ; i++ ) { + if ( !Q_stricmp( key, spawnVars[i][0] ) ) { + *out = spawnVars[i][1]; + return qtrue; + } + } + + *out = (char *)defaultString; + return qfalse; +} + +qboolean G_SpawnFloat( const char *key, const char *defaultString, float *out ) { + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + *out = atof( s ); + return present; +} + +qboolean G_SpawnInt( const char *key, const char *defaultString, int *out ) { + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + *out = atoi( s ); + return present; +} + +qboolean G_SpawnVector( const char *key, const char *defaultString, float *out ) { + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + sscanf( s, "%f %f %f", &out[0], &out[1], &out[2] ); + return present; +} + +qboolean G_SpawnVector4( const char *key, const char *defaultString, float *out ) { + char *s; + qboolean present; + + present = G_SpawnString( key, defaultString, &s ); + sscanf( s, "%f %f %f %f", &out[0], &out[1], &out[2], &out[3] ); + return present; +} + +qboolean G_SpawnFlag( const char *key, int flag, int *out ) +{ + //find that key + for ( int i = 0 ; i < numSpawnVars ; i++ ) + { + if ( !strcmp( key, spawnVars[i][0] ) ) + { + //found the key + if ( atoi( spawnVars[i][1] ) != 0 ) + {//if it's non-zero, and in the flag + *out |= flag; + } + else + {//if it's zero, or out the flag + *out &= ~flag; + } + return qtrue; + } + } + + return qfalse; +} + +qboolean G_SpawnAngleHack( const char *key, const char *defaultString, float *out ) +{ + char *s; + qboolean present; + float temp = 0; + + present = G_SpawnString( key, defaultString, &s ); + sscanf( s, "%f", &temp ); + + out[0] = 0; + out[1] = temp; + out[2] = 0; + + return present; +} + +stringID_table_t flagTable [] = +{ + //"noTED", EF_NO_TED, + //stringID_table_t Must end with a null entry + "", NULL +}; + +// +// fields are needed for spawning from the entity string +// +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_VECTOR4, + F_ANGLEHACK, + F_ENTITY, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_PARM1, // Special case for parms + F_PARM2, // Special case for parms + F_PARM3, // Special case for parms + F_PARM4, // Special case for parms + F_PARM5, // Special case for parms + F_PARM6, // Special case for parms + F_PARM7, // Special case for parms + F_PARM8, // Special case for parms + F_PARM9, // Special case for parms + F_PARM10, // Special case for parms + F_PARM11, // Special case for parms + F_PARM12, // Special case for parms + F_PARM13, // Special case for parms + F_PARM14, // Special case for parms + F_PARM15, // Special case for parms + F_PARM16, // Special case for parms + F_FLAG, // special case for flags + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + +field_t fields[] = { + //Fields for benefit of Radiant only + {"autobound", FOFS(classname), F_IGNORE}, + {"groupname", FOFS(classname), F_IGNORE}, + {"noBasicSounds", FOFS(classname), F_IGNORE},//will be looked at separately + {"noCombatSounds", FOFS(classname), F_IGNORE},//will be looked at separately + {"noExtraSounds", FOFS(classname), F_IGNORE},//will be looked at separately + + {"classname", FOFS(classname), F_LSTRING}, + {"origin", FOFS(s.origin), F_VECTOR}, + {"mins", FOFS(mins), F_VECTOR}, + {"maxs", FOFS(maxs), F_VECTOR}, + {"model", FOFS(model), F_LSTRING}, + {"model2", FOFS(model2), F_LSTRING}, + {"model3", FOFS(target), F_LSTRING},//for misc_replicator_item only!!! + {"model4", FOFS(target2), F_LSTRING},//for misc_replicator_item only!!! + {"model5", FOFS(target3), F_LSTRING},//for misc_replicator_item only!!! + {"model6", FOFS(target4), F_LSTRING},//for misc_replicator_item only!!! + {"spawnflags", FOFS(spawnflags), F_INT}, + {"speed", FOFS(speed), F_FLOAT}, + {"duration", FOFS(speed), F_FLOAT},//for psycho jism + {"interest", FOFS(health), F_INT},//For target_interest + {"target", FOFS(target), F_LSTRING}, + {"target2", FOFS(target2), F_LSTRING}, + {"target3", FOFS(target3), F_LSTRING}, + {"target4", FOFS(target4), F_LSTRING}, + {"targetJump", FOFS(targetJump), F_LSTRING}, + {"targetname", FOFS(targetname), F_LSTRING}, + {"material", FOFS(material), F_INT}, + {"message", FOFS(message), F_LSTRING}, + {"team", FOFS(team), F_LSTRING}, + {"mapname", FOFS(message), F_LSTRING}, + {"wait", FOFS(wait), F_FLOAT}, + {"finaltime", FOFS(wait), F_FLOAT},//For dlight + {"random", FOFS(random), F_FLOAT}, + {"FOV", FOFS(random), F_FLOAT},//for ref_tags and trigger_visibles + {"count", FOFS(count), F_INT}, + {"bounceCount", FOFS(bounceCount), F_INT}, + {"health", FOFS(health), F_INT}, + {"friction", FOFS(health), F_INT},//For target_friction_change + {"light", 0, F_IGNORE}, + {"dmg", FOFS(damage), F_INT}, + {"angles", FOFS(s.angles), F_VECTOR}, + {"angle", FOFS(s.angles), F_ANGLEHACK}, + {"modelAngles", FOFS(modelAngles), F_VECTOR}, + {"cameraGroup", FOFS(cameraGroup), F_LSTRING}, + {"radius", FOFS(radius), F_FLOAT}, + {"hiderange", FOFS(radius), F_FLOAT},//for triggers only + {"starttime", FOFS(radius), F_FLOAT},//for dlight + {"turfrange", FOFS(radius), F_FLOAT},//for sand creatures + {"type", FOFS(count), F_FLOAT},//for fx_crew_beam_in + {"fxfile", FOFS(fxFile), F_LSTRING}, + {"fxfile2", FOFS(cameraGroup), F_LSTRING}, + {"noVisTime", FOFS(endFrame), F_INT},//for NPC_Vehicle + {"endFrame", FOFS(endFrame), F_INT},//for func_usable shader animation + {"linear", FOFS(alt_fire), F_INT},//for movers to use linear movement + {"weapon",FOFS(paintarget),F_LSTRING},//for misc_weapon_shooter only + +//Script parms - will this handle clamping to 16 or whatever length of parm[0] is? + {"parm1", 0, F_PARM1}, + {"parm2", 0, F_PARM2}, + {"parm3", 0, F_PARM3}, + {"parm4", 0, F_PARM4}, + {"parm5", 0, F_PARM5}, + {"parm6", 0, F_PARM6}, + {"parm7", 0, F_PARM7}, + {"parm8", 0, F_PARM8}, + {"parm9", 0, F_PARM9}, + {"parm10", 0, F_PARM10}, + {"parm11", 0, F_PARM11}, + {"parm12", 0, F_PARM12}, + {"parm13", 0, F_PARM13}, + {"parm14", 0, F_PARM14}, + {"parm15", 0, F_PARM15}, + {"parm16", 0, F_PARM16}, + //{"noTED", FOFS(s.eFlags), F_FLAG}, + +//MCG - Begin + //extra fields for ents + {"delay", FOFS(delay), F_INT}, + {"sounds", FOFS(sounds), F_INT}, + {"closetarget", FOFS(closetarget), F_LSTRING},//for doors + {"opentarget", FOFS(opentarget), F_LSTRING},//for doors + {"paintarget", FOFS(paintarget), F_LSTRING},//for doors + {"soundGroup", FOFS(paintarget), F_LSTRING},//for target_speakers + {"backwardstarget", FOFS(paintarget), F_LSTRING},//for trigger_bidirectional + {"splashDamage", FOFS(splashDamage), F_INT}, + {"splashRadius", FOFS(splashRadius), F_INT}, + //Script stuff + {"spawnscript", FOFS(behaviorSet[BSET_SPAWN]), F_LSTRING},//name of script to run + {"usescript", FOFS(behaviorSet[BSET_USE]), F_LSTRING},//name of script to run + {"awakescript", FOFS(behaviorSet[BSET_AWAKE]), F_LSTRING},//name of script to run + {"angerscript", FOFS(behaviorSet[BSET_ANGER]), F_LSTRING},//name of script to run + {"attackscript", FOFS(behaviorSet[BSET_ATTACK]), F_LSTRING},//name of script to run + {"victoryscript", FOFS(behaviorSet[BSET_VICTORY]), F_LSTRING},//name of script to run + {"lostenemyscript", FOFS(behaviorSet[BSET_LOSTENEMY]), F_LSTRING},//name of script to run + {"painscript", FOFS(behaviorSet[BSET_PAIN]), F_LSTRING},//name of script to run + {"fleescript", FOFS(behaviorSet[BSET_FLEE]), F_LSTRING},//name of script to run + {"deathscript", FOFS(behaviorSet[BSET_DEATH]), F_LSTRING},//name of script to run + {"delayscript", FOFS(behaviorSet[BSET_DELAYED]), F_LSTRING},//name of script to run + {"delayscripttime", FOFS(delayScriptTime), F_INT},//name of script to run + {"blockedscript", FOFS(behaviorSet[BSET_BLOCKED]), F_LSTRING},//name of script to run + {"ffirescript", FOFS(behaviorSet[BSET_FFIRE]), F_LSTRING},//name of script to run + {"ffdeathscript", FOFS(behaviorSet[BSET_FFDEATH]), F_LSTRING},//name of script to run + {"mindtrickscript", FOFS(behaviorSet[BSET_MINDTRICK]), F_LSTRING},//name of script to run + {"script_targetname", FOFS(script_targetname), F_LSTRING},//scripts look for this when "affecting" + //For NPCs + //{"playerTeam", FOFS(playerTeam), F_INT}, + //{"enemyTeam", FOFS(enemyTeam), F_INT}, + {"NPC_targetname", FOFS(NPC_targetname), F_LSTRING}, + {"NPC_target", FOFS(NPC_target), F_LSTRING}, + {"NPC_target2", FOFS(target2), F_LSTRING},//NPC_spawner only + {"NPC_target4", FOFS(target4), F_LSTRING},//NPC_spawner only + {"NPC_type", FOFS(NPC_type), F_LSTRING}, + {"ownername", FOFS(ownername), F_LSTRING}, + //for saber + {"saberType", FOFS(NPC_type), F_LSTRING}, + {"saberColor", FOFS(NPC_targetname), F_LSTRING}, + {"saberSolo", FOFS(alt_fire), F_INT}, + //freaky camera shit + {"startRGBA", FOFS(startRGBA), F_VECTOR4}, + {"finalRGBA", FOFS(finalRGBA), F_VECTOR4}, +//MCG - End + + {"soundSet", FOFS(soundSet), F_LSTRING}, + {"mass", FOFS(mass), F_FLOAT}, //really only used for pushable misc_model_breakables + +//q3map stuff + {"scale", 0, F_IGNORE}, + {"modelscale", 0, F_IGNORE}, + {"modelscale_vec", 0, F_IGNORE}, + {"style", 0, F_IGNORE}, + {"lip", 0, F_IGNORE}, + {"switch_style", 0, F_IGNORE}, + {"height", 0, F_IGNORE}, + {"noise", 0, F_IGNORE}, //SP_target_speaker + {"gravity", 0, F_IGNORE}, //SP_target_gravity_change + + {"storyhead", 0, F_IGNORE}, //SP_target_level_change + {"tier_storyinfo", 0, F_IGNORE}, //SP_target_level_change + {"zoffset", 0, F_IGNORE}, //used by misc_model_static + {"music", 0, F_IGNORE}, //used by target_play_music + {"forcevisible", 0, F_IGNORE}, //for force sight on multiple model entities + {"redcrosshair", 0, F_IGNORE}, //for red crosshairs on breakables + {"nodelay", 0, F_IGNORE}, //for Reborn & Cultist NPCs + + {NULL} +}; + + +typedef struct { + char *name; + void (*spawn)(gentity_t *ent); +} spawn_t; + +void SP_info_player_start (gentity_t *ent); +void SP_info_player_deathmatch (gentity_t *ent); +void SP_info_player_intermission (gentity_t *ent); +void SP_info_firstplace(gentity_t *ent); +void SP_info_secondplace(gentity_t *ent); +void SP_info_thirdplace(gentity_t *ent); + +void SP_func_plat (gentity_t *ent); +void SP_func_static (gentity_t *ent); +void SP_func_rotating (gentity_t *ent); +void SP_func_bobbing (gentity_t *ent); +void SP_func_breakable (gentity_t *self); +void SP_func_glass( gentity_t *self ); +void SP_func_pendulum( gentity_t *ent ); +void SP_func_button (gentity_t *ent); +void SP_func_door (gentity_t *ent); +void SP_func_train (gentity_t *ent); +void SP_func_timer (gentity_t *self); +void SP_func_wall (gentity_t *ent); +void SP_func_usable( gentity_t *self ); +void SP_rail_mover( gentity_t *self ); +void SP_rail_track( gentity_t *self ); +void SP_rail_lane( gentity_t *self ); + + + +void SP_trigger_always (gentity_t *ent); +void SP_trigger_multiple (gentity_t *ent); +void SP_trigger_once (gentity_t *ent); +void SP_trigger_push (gentity_t *ent); +void SP_trigger_teleport (gentity_t *ent); +void SP_trigger_hurt (gentity_t *ent); +void SP_trigger_bidirectional (gentity_t *ent); +void SP_trigger_entdist (gentity_t *self); +void SP_trigger_location( gentity_t *ent ); +void SP_trigger_visible( gentity_t *self ); +void SP_trigger_space(gentity_t *self); +void SP_trigger_shipboundary(gentity_t *self); + +void SP_target_give (gentity_t *ent); +void SP_target_delay (gentity_t *ent); +void SP_target_speaker (gentity_t *ent); +void SP_target_print (gentity_t *ent); +void SP_target_laser (gentity_t *self); +void SP_target_character (gentity_t *ent); +void SP_target_score( gentity_t *ent ); +void SP_target_teleporter( gentity_t *ent ); +void SP_target_relay (gentity_t *ent); +void SP_target_kill (gentity_t *ent); +void SP_target_position (gentity_t *ent); +void SP_target_location (gentity_t *ent); +void SP_target_push (gentity_t *ent); +void SP_target_random (gentity_t *self); +void SP_target_counter (gentity_t *self); +void SP_target_scriptrunner (gentity_t *self); +void SP_target_interest (gentity_t *self); +void SP_target_activate (gentity_t *self); +void SP_target_deactivate (gentity_t *self); +void SP_target_gravity_change( gentity_t *self ); +void SP_target_friction_change( gentity_t *self ); +void SP_target_level_change( gentity_t *self ); +void SP_target_change_parm( gentity_t *self ); +void SP_target_play_music( gentity_t *self ); +void SP_target_autosave( gentity_t *self ); +void SP_target_secret( gentity_t *self ); + +void SP_light (gentity_t *self); +void SP_info_null (gentity_t *self); +void SP_info_notnull (gentity_t *self); +void SP_path_corner (gentity_t *self); + +void SP_misc_teleporter (gentity_t *self); +void SP_misc_teleporter_dest (gentity_t *self); +void SP_misc_model(gentity_t *ent); +void SP_misc_model_static(gentity_t *ent); +void SP_misc_turret (gentity_t *base); +void SP_misc_ns_turret (gentity_t *base); +void SP_laser_arm (gentity_t *base); +void SP_misc_ion_cannon( gentity_t *ent ); +void SP_misc_maglock( gentity_t *ent ); +void SP_misc_panel_turret( gentity_t *ent ); +void SP_misc_model_welder( gentity_t *ent ); +void SP_misc_model_jabba_cam( gentity_t *ent ); + +void SP_misc_model_shield_power_converter( gentity_t *ent ); +void SP_misc_model_ammo_power_converter( gentity_t *ent ); +void SP_misc_model_bomb_planted( gentity_t *ent ); +void SP_misc_model_beacon( gentity_t *ent ); + +void SP_misc_shield_floor_unit( gentity_t *ent ); +void SP_misc_ammo_floor_unit( gentity_t *ent ); + +void SP_misc_model_gun_rack( gentity_t *ent ); +void SP_misc_model_ammo_rack( gentity_t *ent ); +void SP_misc_model_cargo_small( gentity_t *ent ); + +void SP_misc_exploding_crate( gentity_t *ent ); +void SP_misc_gas_tank( gentity_t *ent ); +void SP_misc_crystal_crate( gentity_t *ent ); +void SP_misc_atst_drivable( gentity_t *ent ); + +void SP_misc_model_breakable(gentity_t *ent);//stays as an ent +void SP_misc_model_ghoul(gentity_t *ent);//stays as an ent +void SP_misc_portal_camera(gentity_t *ent); + +void SP_misc_bsp(gentity_t *ent); +void SP_terrain(gentity_t *ent); +void SP_misc_skyportal (gentity_t *ent); + +void SP_misc_portal_surface(gentity_t *ent); +void SP_misc_camera_focus (gentity_t *self); +void SP_misc_camera_track (gentity_t *self); +void SP_misc_dlight (gentity_t *ent); +void SP_misc_security_panel (gentity_t *ent); +void SP_misc_camera( gentity_t *ent ); +void SP_misc_spotlight( gentity_t *ent ); + +void SP_shooter_rocket( gentity_t *ent ); +void SP_shooter_plasma( gentity_t *ent ); +void SP_shooter_grenade( gentity_t *ent ); +void SP_misc_replicator_item( gentity_t *ent ); +void SP_misc_trip_mine( gentity_t *self ); +void SP_PAS( gentity_t *ent ); +void SP_misc_weapon_shooter( gentity_t *self ); +void SP_misc_weather_zone( gentity_t *ent ); + +//New spawn functions +void SP_reference_tag ( gentity_t *ent ); + +void SP_NPC_spawner( gentity_t *self ); + +void SP_NPC_Vehicle( gentity_t *self ); +void SP_NPC_Player( gentity_t *self ); +void SP_NPC_Kyle( gentity_t *self ); +void SP_NPC_Lando( gentity_t *self ); +void SP_NPC_Jan( gentity_t *self ); +void SP_NPC_Luke( gentity_t *self ); +void SP_NPC_MonMothma( gentity_t *self ); +void SP_NPC_Rosh_Penin( gentity_t *self ); +void SP_NPC_Tavion( gentity_t *self ); +void SP_NPC_Tavion_New( gentity_t *self ); +void SP_NPC_Alora( gentity_t *self ); +void SP_NPC_Reelo( gentity_t *self ); +void SP_NPC_Galak( gentity_t *self ); +void SP_NPC_Desann( gentity_t *self ); +void SP_NPC_Rax( gentity_t *self ); +void SP_NPC_BobaFett( gentity_t *self ); +void SP_NPC_Ragnos( gentity_t *self ); +void SP_NPC_Lannik_Racto( gentity_t *self ); +void SP_NPC_Kothos( gentity_t *self ); +void SP_NPC_Chewbacca( gentity_t *self ); +void SP_NPC_Bartender( gentity_t *self ); +void SP_NPC_MorganKatarn( gentity_t *self ); +void SP_NPC_Jedi( gentity_t *self ); +void SP_NPC_Prisoner( gentity_t *self ); +void SP_NPC_Merchant( gentity_t *self ); +void SP_NPC_Rebel( gentity_t *self ); +void SP_NPC_Human_Merc( gentity_t *self ); +void SP_NPC_Stormtrooper( gentity_t *self ); +void SP_NPC_StormtrooperOfficer( gentity_t *self ); +void SP_NPC_Tie_Pilot( gentity_t *self ); +void SP_NPC_Snowtrooper( gentity_t *self ); +void SP_NPC_RocketTrooper( gentity_t *self); +void SP_NPC_HazardTrooper( gentity_t *self); +void SP_NPC_Ugnaught( gentity_t *self ); +void SP_NPC_Jawa( gentity_t *self ); +void SP_NPC_Gran( gentity_t *self ); +void SP_NPC_Rodian( gentity_t *self ); +void SP_NPC_Weequay( gentity_t *self ); +void SP_NPC_Trandoshan( gentity_t *self ); +void SP_NPC_Tusken( gentity_t *self ); +void SP_NPC_Noghri( gentity_t *self ); +void SP_NPC_SwampTrooper( gentity_t *self ); +void SP_NPC_Imperial( gentity_t *self ); +void SP_NPC_ImpWorker( gentity_t *self ); +void SP_NPC_BespinCop( gentity_t *self ); +void SP_NPC_Reborn( gentity_t *self ); +void SP_NPC_Reborn_New( gentity_t *self); +void SP_NPC_Cultist( gentity_t *self ); +void SP_NPC_Cultist_Saber( gentity_t *self ); +void SP_NPC_Cultist_Saber_Powers( gentity_t *self ); +void SP_NPC_Cultist_Destroyer( gentity_t *self ); +void SP_NPC_Cultist_Commando( gentity_t *self ); +void SP_NPC_ShadowTrooper( gentity_t *self ); +void SP_NPC_Saboteur( gentity_t *self ); +void SP_NPC_Monster_Murjj( gentity_t *self ); +void SP_NPC_Monster_Swamp( gentity_t *self ); +void SP_NPC_Monster_Howler( gentity_t *self ); +void SP_NPC_Monster_Rancor( gentity_t *self ); +void SP_NPC_Monster_Mutant_Rancor( gentity_t *self ); +void SP_NPC_Monster_Wampa( gentity_t *self ); +void SP_NPC_Monster_Claw( gentity_t *self ); +void SP_NPC_Monster_Glider( gentity_t *self ); +void SP_NPC_Monster_Flier2( gentity_t *self ); +void SP_NPC_Monster_Lizard( gentity_t *self ); +void SP_NPC_Monster_Fish( gentity_t *self ); +void SP_NPC_Monster_Sand_Creature( gentity_t *self ); +void SP_NPC_MineMonster( gentity_t *self ); +void SP_NPC_Droid_Interrogator( gentity_t *self ); +void SP_NPC_Droid_Probe( gentity_t *self ); +void SP_NPC_Droid_Mark1( gentity_t *self ); +void SP_NPC_Droid_Mark2( gentity_t *self ); +void SP_NPC_Droid_ATST( gentity_t *self ); +void SP_NPC_Droid_Seeker( gentity_t *self ); +void SP_NPC_Droid_Remote( gentity_t *self ); +void SP_NPC_Droid_Sentry( gentity_t *self ); +void SP_NPC_Droid_Gonk( gentity_t *self ); +void SP_NPC_Droid_Mouse( gentity_t *self ); +void SP_NPC_Droid_R2D2( gentity_t *self ); +void SP_NPC_Droid_R5D2( gentity_t *self ); +void SP_NPC_Droid_Protocol( gentity_t *self ); +void SP_NPC_Droid_Assassin( gentity_t *self); +void SP_NPC_Droid_Saber( gentity_t *self); + +void SP_waypoint (gentity_t *ent); +void SP_waypoint_small (gentity_t *ent); +void SP_waypoint_navgoal (gentity_t *ent); + +void SP_fx_runner( gentity_t *ent ); +void SP_fx_explosion_trail( gentity_t *ent ); +void SP_fx_target_beam( gentity_t *ent ); +void SP_fx_cloudlayer( gentity_t *ent ); + +void SP_CreateSnow( gentity_t *ent ); +void SP_CreateRain( gentity_t *ent ); +void SP_CreateWind( gentity_t *ent ); +void SP_CreateWindZone( gentity_t *ent ); +// Added 10/20/02 by Aurelio Reis +void SP_CreatePuffSystem( gentity_t *ent ); + +void SP_object_cargo_barrel1( gentity_t *ent ); + +void SP_point_combat (gentity_t *self); + +void SP_emplaced_eweb( gentity_t *self ); +void SP_emplaced_gun( gentity_t *self ); + +void SP_misc_turbobattery( gentity_t *base ); + + +spawn_t spawns[] = { + {"info_player_start", SP_info_player_start}, + {"info_player_deathmatch", SP_info_player_deathmatch}, + + {"func_plat", SP_func_plat}, + {"func_button", SP_func_button}, + {"func_door", SP_func_door}, + {"func_static", SP_func_static}, + {"func_rotating", SP_func_rotating}, + {"func_bobbing", SP_func_bobbing}, + {"func_breakable", SP_func_breakable}, + {"func_pendulum", SP_func_pendulum}, + {"func_train", SP_func_train}, + {"func_timer", SP_func_timer}, // rename trigger_timer? + {"func_wall", SP_func_wall}, + {"func_usable", SP_func_usable}, + {"func_glass", SP_func_glass}, + + {"rail_mover", SP_rail_mover}, + {"rail_track", SP_rail_track}, + {"rail_lane", SP_rail_lane}, + + {"trigger_always", SP_trigger_always}, + {"trigger_multiple", SP_trigger_multiple}, + {"trigger_once", SP_trigger_once}, + {"trigger_push", SP_trigger_push}, + {"trigger_teleport", SP_trigger_teleport}, + {"trigger_hurt", SP_trigger_hurt}, + {"trigger_bidirectional", SP_trigger_bidirectional}, + {"trigger_entdist", SP_trigger_entdist}, + {"trigger_location", SP_trigger_location}, + {"trigger_visible", SP_trigger_visible}, + {"trigger_space", SP_trigger_space}, + {"trigger_shipboundary", SP_trigger_shipboundary}, + + {"target_give", SP_target_give}, + {"target_delay", SP_target_delay}, + {"target_speaker", SP_target_speaker}, + {"target_print", SP_target_print}, + {"target_laser", SP_target_laser}, + {"target_score", SP_target_score}, + {"target_teleporter", SP_target_teleporter}, + {"target_relay", SP_target_relay}, + {"target_kill", SP_target_kill}, + {"target_position", SP_target_position}, + {"target_location", SP_target_location}, + {"target_push", SP_target_push}, + {"target_random", SP_target_random}, + {"target_counter", SP_target_counter}, + {"target_scriptrunner", SP_target_scriptrunner}, + {"target_interest", SP_target_interest}, + {"target_activate", SP_target_activate}, + {"target_deactivate", SP_target_deactivate}, + {"target_gravity_change", SP_target_gravity_change}, + {"target_friction_change", SP_target_friction_change}, + {"target_level_change", SP_target_level_change}, + {"target_change_parm", SP_target_change_parm}, + {"target_play_music", SP_target_play_music}, + {"target_autosave", SP_target_autosave}, + {"target_secret", SP_target_secret}, + + {"light", SP_light}, + {"info_null", SP_info_null}, + {"func_group", SP_info_null}, + {"info_notnull", SP_info_notnull}, // use target_position instead + {"path_corner", SP_path_corner}, + + {"misc_teleporter", SP_misc_teleporter}, + {"misc_teleporter_dest", SP_misc_teleporter_dest}, + {"misc_model", SP_misc_model}, + {"misc_model_static", SP_misc_model_static}, + {"misc_turret", SP_misc_turret}, + {"misc_ns_turret", SP_misc_ns_turret}, + {"misc_laser_arm", SP_laser_arm}, + {"misc_ion_cannon", SP_misc_ion_cannon}, + {"misc_sentry_turret", SP_PAS}, + {"misc_maglock", SP_misc_maglock}, + {"misc_weapon_shooter", SP_misc_weapon_shooter}, + {"misc_weather_zone", SP_misc_weather_zone}, + + {"misc_model_ghoul", SP_misc_model_ghoul}, + {"misc_model_breakable", SP_misc_model_breakable}, + {"misc_portal_surface", SP_misc_portal_surface}, + {"misc_portal_camera", SP_misc_portal_camera}, + + {"misc_bsp", SP_misc_bsp}, + {"terrain", SP_terrain}, + {"misc_skyportal", SP_misc_skyportal}, + + {"misc_camera_focus", SP_misc_camera_focus}, + {"misc_camera_track", SP_misc_camera_track}, + {"misc_dlight", SP_misc_dlight}, + {"misc_replicator_item", SP_misc_replicator_item}, + {"misc_trip_mine", SP_misc_trip_mine}, + {"misc_security_panel", SP_misc_security_panel}, + {"misc_camera", SP_misc_camera}, + {"misc_spotlight", SP_misc_spotlight}, + {"misc_panel_turret", SP_misc_panel_turret}, + {"misc_model_welder", SP_misc_model_welder}, + {"misc_model_jabba_cam", SP_misc_model_jabba_cam}, + {"misc_model_shield_power_converter", SP_misc_model_shield_power_converter}, + {"misc_model_ammo_power_converter", SP_misc_model_ammo_power_converter}, + {"misc_model_bomb_planted", SP_misc_model_bomb_planted}, + {"misc_model_beacon", SP_misc_model_beacon}, + {"misc_shield_floor_unit", SP_misc_shield_floor_unit}, + {"misc_ammo_floor_unit", SP_misc_ammo_floor_unit}, + + {"misc_model_gun_rack", SP_misc_model_gun_rack}, + {"misc_model_ammo_rack", SP_misc_model_ammo_rack}, + {"misc_model_cargo_small", SP_misc_model_cargo_small}, + + {"misc_exploding_crate", SP_misc_exploding_crate}, + {"misc_gas_tank", SP_misc_gas_tank}, + {"misc_crystal_crate", SP_misc_crystal_crate}, + {"misc_atst_drivable", SP_misc_atst_drivable}, + + {"shooter_rocket", SP_shooter_rocket}, + {"shooter_grenade", SP_shooter_grenade}, + {"shooter_plasma", SP_shooter_plasma}, + + {"ref_tag", SP_reference_tag}, + + //new NPC ents + {"NPC_spawner", SP_NPC_spawner}, + + {"NPC_Vehicle", SP_NPC_Vehicle }, + {"NPC_Player", SP_NPC_Player }, + {"NPC_Kyle", SP_NPC_Kyle }, + {"NPC_Lando", SP_NPC_Lando }, + {"NPC_Jan", SP_NPC_Jan }, + {"NPC_Luke", SP_NPC_Luke }, + {"NPC_MonMothma", SP_NPC_MonMothma }, + {"NPC_Rosh_Penin", SP_NPC_Rosh_Penin }, + {"NPC_Tavion", SP_NPC_Tavion }, + {"NPC_Tavion_New", SP_NPC_Tavion_New }, + {"NPC_Alora", SP_NPC_Alora }, + {"NPC_Reelo", SP_NPC_Reelo }, + {"NPC_Galak", SP_NPC_Galak }, + {"NPC_Desann", SP_NPC_Desann }, + {"NPC_Rax", SP_NPC_Rax }, + {"NPC_BobaFett", SP_NPC_BobaFett }, + {"NPC_Ragnos", SP_NPC_Ragnos }, + {"NPC_Lannik_Racto", SP_NPC_Lannik_Racto }, + {"NPC_Kothos", SP_NPC_Kothos }, + {"NPC_Chewbacca", SP_NPC_Chewbacca }, + {"NPC_Bartender", SP_NPC_Bartender }, + {"NPC_MorganKatarn", SP_NPC_MorganKatarn }, + {"NPC_Jedi", SP_NPC_Jedi }, + {"NPC_Prisoner", SP_NPC_Prisoner }, + {"NPC_Merchant", SP_NPC_Merchant }, + {"NPC_Rebel", SP_NPC_Rebel }, + {"NPC_Human_Merc", SP_NPC_Human_Merc }, + {"NPC_Stormtrooper", SP_NPC_Stormtrooper }, + {"NPC_StormtrooperOfficer", SP_NPC_StormtrooperOfficer }, + {"NPC_Tie_Pilot", SP_NPC_Tie_Pilot }, + {"NPC_Snowtrooper", SP_NPC_Snowtrooper }, + {"NPC_RocketTrooper", SP_NPC_RocketTrooper }, + {"NPC_HazardTrooper", SP_NPC_HazardTrooper }, + + {"NPC_Ugnaught", SP_NPC_Ugnaught }, + {"NPC_Jawa", SP_NPC_Jawa }, + {"NPC_Gran", SP_NPC_Gran }, + {"NPC_Rodian", SP_NPC_Rodian }, + {"NPC_Weequay", SP_NPC_Weequay }, + {"NPC_Trandoshan", SP_NPC_Trandoshan }, + {"NPC_Tusken", SP_NPC_Tusken }, + {"NPC_Noghri", SP_NPC_Noghri }, + {"NPC_SwampTrooper", SP_NPC_SwampTrooper }, + {"NPC_Imperial", SP_NPC_Imperial }, + {"NPC_ImpWorker", SP_NPC_ImpWorker }, + {"NPC_BespinCop", SP_NPC_BespinCop }, + {"NPC_Reborn", SP_NPC_Reborn }, + {"NPC_Reborn_New", SP_NPC_Reborn_New }, + {"NPC_Cultist", SP_NPC_Cultist }, + {"NPC_Cultist_Saber", SP_NPC_Cultist_Saber }, + {"NPC_Cultist_Saber_Powers", SP_NPC_Cultist_Saber_Powers }, + {"NPC_Cultist_Destroyer", SP_NPC_Cultist_Destroyer }, + {"NPC_Cultist_Commando", SP_NPC_Cultist_Commando }, + {"NPC_ShadowTrooper", SP_NPC_ShadowTrooper }, + {"NPC_Saboteur", SP_NPC_Saboteur }, + {"NPC_Monster_Murjj", SP_NPC_Monster_Murjj }, + {"NPC_Monster_Swamp", SP_NPC_Monster_Swamp }, + {"NPC_Monster_Howler", SP_NPC_Monster_Howler }, + {"NPC_Monster_Rancor", SP_NPC_Monster_Rancor }, + {"NPC_Monster_Mutant_Rancor", SP_NPC_Monster_Mutant_Rancor }, + {"NPC_Monster_Wampa", SP_NPC_Monster_Wampa }, + {"NPC_MineMonster", SP_NPC_MineMonster }, + {"NPC_Monster_Claw", SP_NPC_Monster_Claw }, + {"NPC_Monster_Glider", SP_NPC_Monster_Glider }, + {"NPC_Monster_Flier2", SP_NPC_Monster_Flier2 }, + {"NPC_Monster_Lizard", SP_NPC_Monster_Lizard }, + {"NPC_Monster_Fish", SP_NPC_Monster_Fish }, + {"NPC_Monster_Sand_Creature", SP_NPC_Monster_Sand_Creature }, + {"NPC_Droid_Interrogator", SP_NPC_Droid_Interrogator }, + {"NPC_Droid_Probe", SP_NPC_Droid_Probe }, + {"NPC_Droid_Mark1", SP_NPC_Droid_Mark1 }, + {"NPC_Droid_Mark2", SP_NPC_Droid_Mark2 }, + {"NPC_Droid_ATST", SP_NPC_Droid_ATST }, + {"NPC_Droid_Seeker", SP_NPC_Droid_Seeker }, + {"NPC_Droid_Remote", SP_NPC_Droid_Remote }, + {"NPC_Droid_Sentry", SP_NPC_Droid_Sentry }, + {"NPC_Droid_Gonk", SP_NPC_Droid_Gonk }, + {"NPC_Droid_Mouse", SP_NPC_Droid_Mouse }, + {"NPC_Droid_R2D2", SP_NPC_Droid_R2D2 }, + {"NPC_Droid_R5D2", SP_NPC_Droid_R5D2 }, + {"NPC_Droid_Protocol", SP_NPC_Droid_Protocol }, + {"NPC_Droid_Assassin", SP_NPC_Droid_Assassin }, + {"NPC_Droid_Saber", SP_NPC_Droid_Saber }, + + //rwwFIXMEFIXME: Faked for testing NPCs (another other things) in RMG with sof2 assets + {"NPC_Colombian_Soldier", SP_NPC_Reborn }, + {"NPC_Colombian_Rebel", SP_NPC_Reborn }, + {"NPC_Colombian_EmplacedGunner", SP_NPC_ShadowTrooper }, + {"NPC_Manuel_Vergara_RMG", SP_NPC_Desann }, +// {"info_NPCnav", SP_waypoint}, + + {"waypoint", SP_waypoint}, + {"waypoint_small", SP_waypoint_small}, + {"waypoint_navgoal", SP_waypoint_navgoal}, + + {"fx_runner", SP_fx_runner}, + {"fx_explosion_trail", SP_fx_explosion_trail}, + {"fx_target_beam", SP_fx_target_beam}, + {"fx_cloudlayer", SP_fx_cloudlayer}, + {"fx_rain", SP_CreateRain}, + {"fx_wind", SP_CreateWind}, + {"fx_snow", SP_CreateSnow}, + {"fx_puff", SP_CreatePuffSystem}, + {"fx_wind_zone", SP_CreateWindZone}, + + {"object_cargo_barrel1", SP_object_cargo_barrel1}, + {"point_combat", SP_point_combat}, + + {"emplaced_gun", SP_emplaced_gun}, + {"emplaced_eweb", SP_emplaced_eweb}, + + {NULL, NULL} +}; + +/* +=============== +G_CallSpawn + +Finds the spawn function for the entity and calls it, +returning qfalse if not found +=============== +*/ +qboolean G_CallSpawn( gentity_t *ent ) { + spawn_t *s; + gitem_t *item; + + if ( !ent->classname ) { + gi.Printf (S_COLOR_RED"G_CallSpawn: NULL classname\n"); + return qfalse; + } + + // check item spawn functions + for ( item=bg_itemlist+1 ; item->classname ; item++ ) { + if ( !strcmp(item->classname, ent->classname) ) { + // found it + G_SpawnItem( ent, item ); + return qtrue; + } + } + + // check normal spawn functions + for ( s=spawns ; s->name ; s++ ) { + if ( !strcmp(s->name, ent->classname) ) { + // found it + s->spawn(ent); + return qtrue; + } + } + char* str; + G_SpawnString( "origin", "?", &str ); + gi.Printf (S_COLOR_RED"ERROR: %s is not a spawn function @(%s)\n", ent->classname, str); + delayedShutDown = level.time + 100; + return qfalse; +} + +/* +============= +G_NewString + +Builds a copy of the string, translating \n to real linefeeds +so message texts can be multi-line +============= +*/ +char *G_NewString( const char *string ) { + char *newb, *new_p; + int i,l; + + if(!string || !string[0]) + { + //gi.Printf(S_COLOR_RED"Error: G_NewString called with NULL string!\n"); + return NULL; + } + + l = strlen(string) + 1; + + newb = (char *) G_Alloc( l ); + + new_p = newb; + + // turn \n into a real linefeed + 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; +} + + + + +/* +=============== +G_ParseField + +Takes a key/value pair and sets the binary values +in a gentity +=============== +*/ +void Q3_SetParm (int entID, int parmNum, const char *parmValue); +void G_ParseField( const char *key, const char *value, gentity_t *ent ) { + field_t *f; + byte *b; + float v; + vec3_t vec; + vec4_t vec4; + + for ( f=fields ; f->name ; f++ ) { + if ( !Q_stricmp(f->name, key) ) { + // found it + b = (byte *)ent; + + switch( f->type ) { + case F_LSTRING: + *(char **)(b+f->ofs) = G_NewString (value); + break; + case F_VECTOR: + { + int _iFieldsRead = sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + assert(_iFieldsRead==3); + if (_iFieldsRead!=3) + { + gi.Printf (S_COLOR_YELLOW"G_ParseField: VEC3 sscanf() failed to read 3 floats ('angle' key bug?)\n"); + delayedShutDown = level.time + 100; + } + ((float *)(b+f->ofs))[0] = vec[0]; + ((float *)(b+f->ofs))[1] = vec[1]; + ((float *)(b+f->ofs))[2] = vec[2]; + break; + } + case F_VECTOR4: + { + int _iFieldsRead = sscanf (value, "%f %f %f %f", &vec4[0], &vec4[1], &vec4[2], &vec4[3]); + assert(_iFieldsRead==4); + if (_iFieldsRead!=4) + { + gi.Printf (S_COLOR_YELLOW"G_ParseField: VEC4 sscanf() failed to read 4 floats\n"); + delayedShutDown = level.time + 100; + } + ((float *)(b+f->ofs))[0] = vec4[0]; + ((float *)(b+f->ofs))[1] = vec4[1]; + ((float *)(b+f->ofs))[2] = vec4[2]; + ((float *)(b+f->ofs))[3] = vec4[3]; + 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_PARM1: + case F_PARM2: + case F_PARM3: + case F_PARM4: + case F_PARM5: + case F_PARM6: + case F_PARM7: + case F_PARM8: + case F_PARM9: + case F_PARM10: + case F_PARM11: + case F_PARM12: + case F_PARM13: + case F_PARM14: + case F_PARM15: + case F_PARM16: + Q3_SetParm( ent->s.number, (f->type - F_PARM1), (char *) value ); + break; + case F_FLAG: + {//try to find the proper flag for that key: + int flag = GetIDForString ( flagTable, key ); + + if ( flag > 0 ) + { + G_SpawnFlag( key, flag, (int *)(b+f->ofs) ); + } + else + { +#ifndef FINAL_BUILD + gi.Printf (S_COLOR_YELLOW"WARNING: G_ParseField: can't find flag for key %s\n", key); +#endif + } + } + break; + default: + case F_IGNORE: + break; + } + return; + } + } +#ifndef FINAL_BUILD + //didn't find it? + if (key[0]!='_') + { + gi.Printf ( S_COLOR_YELLOW"WARNING: G_ParseField: no such field: %s\n", key ); + } +#endif +} + +static qboolean SpawnForCurrentDifficultySetting( gentity_t *ent ) +{ +extern cvar_t *com_buildScript; + if (com_buildScript->integer) { //always spawn when building a pak file + return qtrue; + } + if ( ent->spawnflags & ( 1 << (8 + g_spskill->integer )) ) {// easy -256 medium -512 hard -1024 + return qfalse; + } else { + return qtrue; + } +} + +/* +=================== +G_SpawnGEntityFromSpawnVars + +Spawn an entity and fill in all of the level fields from +level.spawnVars[], then call the class specfic spawn function +=================== +*/ + +void G_SpawnGEntityFromSpawnVars( void ) { + int i; + gentity_t *ent; + + // get the next free entity + ent = G_Spawn(); + + for ( i = 0 ; i < numSpawnVars ; i++ ) { + G_ParseField( spawnVars[i][0], spawnVars[i][1], ent ); + } + + G_SpawnInt( "notsingle", "0", &i ); + if ( i || !SpawnForCurrentDifficultySetting( ent ) ) { + G_FreeEntity( ent ); + return; + } + + // move editor origin to pos + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->currentOrigin ); + + // if we didn't get a classname, don't bother spawning anything + if ( !G_CallSpawn( ent ) ) { + G_FreeEntity( ent ); + return; + } + + //Tag on the ICARUS scripting information only to valid recipients + if ( Quake3Game()->ValidEntity( ent ) ) + { + Quake3Game()->InitEntity( ent ); //ICARUS_InitEnt( ent ); + + if ( ent->classname && ent->classname[0] ) + { + if ( Q_strncmp( "NPC_", ent->classname, 4 ) != 0 ) + {//Not an NPC_spawner + G_ActivateBehavior( ent, BSET_SPAWN ); + } + } + } +} + +void G_SpawnSubBSPGEntityFromSpawnVars( vec3_t posOffset, vec3_t angOffset ) { + int i; + gentity_t *ent; + + // get the next free entity + ent = G_Spawn(); + + for ( i = 0 ; i < numSpawnVars ; i++ ) { + G_ParseField( spawnVars[i][0], spawnVars[i][1], ent ); + } + + G_SpawnInt( "notsingle", "0", &i ); + if ( i || !SpawnForCurrentDifficultySetting( ent ) ) { + G_FreeEntity( ent ); + return; + } + + VectorAdd(ent->s.origin, posOffset, ent->s.origin); + VectorAdd(ent->s.angles, angOffset, ent->s.angles); + + VectorCopy(ent->s.angles, ent->s.apos.trBase); + VectorCopy(ent->s.angles, ent->currentAngles); + + // move editor origin to pos + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + VectorCopy( ent->s.origin, ent->currentOrigin ); + + // if we didn't get a classname, don't bother spawning anything + if ( !G_CallSpawn( ent ) ) { + G_FreeEntity( ent ); + return; + } + + //Tag on the ICARUS scripting information only to valid recipients + if ( Quake3Game()->ValidEntity( ent ) ) + { + + Quake3Game()->InitEntity( ent ); // ICARUS_InitEnt( ent ); + + if ( ent->classname && ent->classname[0] ) + { + if ( Q_strncmp( "NPC_", ent->classname, 4 ) != 0 ) + {//Not an NPC_spawner + G_ActivateBehavior( ent, BSET_SPAWN ); + } + } + } +} + + +/* +==================== +G_AddSpawnVarToken +==================== +*/ +char *G_AddSpawnVarToken( const char *string ) { + int l; + char *dest; + + l = strlen( string ); + if ( numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS ) { + G_Error( "G_AddSpawnVarToken: MAX_SPAWN_VARS" ); + } + + dest = spawnVarChars + numSpawnVarChars; + memcpy( dest, string, l+1 ); + + numSpawnVarChars += l + 1; + + return dest; +} + +/* +==================== +G_ParseSpawnVars + +Parses a brace bounded set of key / value pairs out of the +level's entity strings into level.spawnVars[] + +This does not actually spawn an entity. +==================== +*/ +qboolean G_ParseSpawnVars( const char **data ) { + char keyname[MAX_STRING_CHARS]; + const char *com_token; + + numSpawnVars = 0; + numSpawnVarChars = 0; + + // parse the opening brace + com_token = COM_Parse( data ); + if ( !*data ) { + // end of spawn string + return qfalse; + } + if ( com_token[0] != '{' ) { + G_Error( "G_ParseSpawnVars: found %s when expecting {",com_token ); + } + + // go through all the key / value pairs + while ( 1 ) { + // parse key + com_token = COM_Parse( data ); + if ( com_token[0] == '}' ) { + break; + } + if ( !data ) { + G_Error( "G_ParseSpawnVars: EOF without closing brace" ); + } + + Q_strncpyz( keyname, com_token, sizeof(keyname) ); + + // parse value + com_token = COM_Parse( data ); + if ( com_token[0] == '}' ) { + G_Error( "G_ParseSpawnVars: closing brace without data" ); + } + if ( !data ) { + G_Error( "G_ParseSpawnVars: EOF without closing brace" ); + } + if ( numSpawnVars == MAX_SPAWN_VARS ) { + G_Error( "G_ParseSpawnVars: MAX_SPAWN_VARS" ); + } + spawnVars[ numSpawnVars ][0] = G_AddSpawnVarToken( keyname ); + spawnVars[ numSpawnVars ][1] = G_AddSpawnVarToken( com_token ); + numSpawnVars++; + } + + return qtrue; +} + +static char *defaultStyles[LS_NUM_STYLES][3] = +{ + { // 0 normal + "z", + "z", + "z" + }, + { // 1 FLICKER (first variety) + "mmnmmommommnonmmonqnmmo", + "mmnmmommommnonmmonqnmmo", + "mmnmmommommnonmmonqnmmo" + }, + { // 2 SLOW STRONG PULSE + "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcb", + "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcb", + "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcb" + }, + { // 3 CANDLE (first variety) + "mmmmmaaaaammmmmaaaaaabcdefgabcdefg", + "mmmmmaaaaammmmmaaaaaabcdefgabcdefg", + "mmmmmaaaaammmmmaaaaaabcdefgabcdefg" + }, + { // 4 FAST STROBE + "mamamamamama", + "mamamamamama", + "mamamamamama" + }, + { // 5 GENTLE PULSE 1 + "jklmnopqrstuvwxyzyxwvutsrqponmlkj", + "jklmnopqrstuvwxyzyxwvutsrqponmlkj", + "jklmnopqrstuvwxyzyxwvutsrqponmlkj" + }, + { // 6 FLICKER (second variety) + "nmonqnmomnmomomno", + "nmonqnmomnmomomno", + "nmonqnmomnmomomno" + }, + { // 7 CANDLE (second variety) + "mmmaaaabcdefgmmmmaaaammmaamm", + "mmmaaaabcdefgmmmmaaaammmaamm", + "mmmaaaabcdefgmmmmaaaammmaamm" + }, + { // 8 CANDLE (third variety) + "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa", + "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa", + "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa" + }, + { // 9 SLOW STROBE (fourth variety) + "aaaaaaaazzzzzzzz", + "aaaaaaaazzzzzzzz", + "aaaaaaaazzzzzzzz" + }, + { // 10 FLUORESCENT FLICKER + "mmamammmmammamamaaamammma", + "mmamammmmammamamaaamammma", + "mmamammmmammamamaaamammma" + }, + { // 11 SLOW PULSE NOT FADE TO BLACK + "abcdefghijklmnopqrrqponmlkjihgfedcba", + "abcdefghijklmnopqrrqponmlkjihgfedcba", + "abcdefghijklmnopqrrqponmlkjihgfedcba" + }, + { // 12 FAST PULSE FOR JEREMY + "mkigegik", + "mkigegik", + "mkigegik" + }, + { // 13 Test Blending + "abcdefghijklmqrstuvwxyz", + "zyxwvutsrqmlkjihgfedcba", + "aammbbzzccllcckkffyyggp" + }, + { // 14 + "", + "", + "" + }, + { // 15 + "", + "", + "" + }, + { // 16 + "", + "", + "" + }, + { // 17 + "", + "", + "" + }, + { // 18 + "", + "", + "" + }, + { // 19 + "", + "", + "" + }, + { // 20 + "", + "", + "" + }, + { // 21 + "", + "", + "" + }, + { // 22 + "", + "", + "" + }, + { // 23 + "", + "", + "" + }, + { // 24 + "", + "", + "" + }, + { // 25 + "", + "", + "" + }, + { // 26 + "", + "", + "" + }, + { // 27 + "", + "", + "" + }, + { // 28 + "", + "", + "" + }, + { // 29 + "", + "", + "" + }, + { // 30 + "", + "", + "" + }, + { // 31 + "", + "", + "" + } +}; + + +/*QUAKED worldspawn (0 0 0) ? +Every map should have exactly one worldspawn. +"music" path to WAV or MP3 files (e.g. "music\intro.mp3 music\loopfile.mp3") +"gravity" 800 is default gravity +"message" Text to print during connection +"soundSet" Ambient sound set to play +"spawnscript" runs this script + +BSP Options +"gridsize" size of lighting grid to "X Y Z". default="64 64 128" +"ambient" amount of global light to add to each surf (uses _color) +"chopsize" value for bsp on the maximum polygon / portal size +"distancecull" value for vis for the maximum viewing distance +"_minlight" minimum lighting on a surf. overrides _mingridlight and _minvertexlight + +Game Options +"fog" shader name of the global fog texture - must include the full path, such as "textures/rj/fog1" +"ls_Xr" override lightstyle X with this pattern for Red. +"ls_Xg" green (valid patterns are "a-z") +"ls_Xb" blue (a is OFF, z is ON) +"breath" Whether the entity's have breath puffs or not (0 = No, 1 = All, 2 = Just cold breath, 3 = Just under water bubbles ). +"clearstats" default 1, if 0 loading this map will not clear the stats for player +"tier_storyinfo" sets 'tier_storyinfo' cvar +*/ +void SP_worldspawn( void ) { + char *s; + int i; + + g_entities[ENTITYNUM_WORLD].max_health = 0; + + for ( i = 0 ; i < numSpawnVars ; i++ ) + { + if ( Q_stricmp( "spawnscript", spawnVars[i][0] ) == 0 ) + {//ONly let them set spawnscript, we don't want them setting an angle or something on the world. + G_ParseField( spawnVars[i][0], spawnVars[i][1], &g_entities[ENTITYNUM_WORLD] ); + } + if ( Q_stricmp( "region", spawnVars[i][0] ) == 0 ) + { + g_entities[ENTITYNUM_WORLD].s.radius = atoi(spawnVars[i][1]); + } + if ( Q_stricmp( "distancecull", spawnVars[i][0] ) == 0 ) + { + g_entities[ENTITYNUM_WORLD].max_health = (int)((float)(atoi(spawnVars[i][1])) * 0.7f); + } + } + + G_SpawnString( "classname", "", &s ); + if ( Q_stricmp( s, "worldspawn" ) ) { + G_Error( "SP_worldspawn: The first entity isn't 'worldspawn'" ); + } + + // make some data visible to connecting client + G_SpawnString( "music", "", &s ); + gi.SetConfigstring( CS_MUSIC, s ); + + G_SpawnString( "message", "", &s ); + gi.SetConfigstring( CS_MESSAGE, s ); // map specific message + + G_SpawnString( "gravity", "800", &s ); + extern SavedGameJustLoaded_e g_eSavedGameJustLoaded; + if (g_eSavedGameJustLoaded != eFULL) + { + gi.cvar_set( "g_gravity", s ); + } + + G_SpawnString( "soundSet", "default", &s ); + gi.SetConfigstring( CS_AMBIENT_SET, s ); + + //Lightstyles + gi.SetConfigstring(CS_LIGHT_STYLES+(LS_STYLES_START*3)+0, defaultStyles[0][0]); + gi.SetConfigstring(CS_LIGHT_STYLES+(LS_STYLES_START*3)+1, defaultStyles[0][1]); + gi.SetConfigstring(CS_LIGHT_STYLES+(LS_STYLES_START*3)+2, defaultStyles[0][2]); + + for(i=1;iclear(); + + for ( int i = 0; i < globals.num_entities; i++ ) + { + ent = &g_entities[i]; + + if VALIDSTRING( ent->soundSet ) + { + (*as_preCacheMap)[ (char *) ent->soundSet ] = 1; + } + } +} + + +void G_ASPreCacheFree(void) +{ + if(as_preCacheMap) { + delete as_preCacheMap; + as_preCacheMap = NULL; + } +} + +/* +============== +G_SpawnEntitiesFromString + +Parses textual entity definitions out of an entstring and spawns gentities. +============== +*/ +extern int num_waypoints; +extern void RG_RouteGen(void); +extern qboolean NPCsPrecached; + +qboolean SP_bsp_worldspawn ( void ) +{ + return qtrue; +} + +void G_SubBSPSpawnEntitiesFromString(const char *entityString, vec3_t posOffset, vec3_t angOffset) +{ + const char *entities; + + entities = entityString; + + // allow calls to G_Spawn*() + spawning = qtrue; + NPCsPrecached = qfalse; + numSpawnVars = 0; + + // the worldspawn is not an actual entity, but it still + // has a "spawn" function to perform any global setup + // needed by a level (setting configstrings or cvars, etc) + if ( !G_ParseSpawnVars( &entities ) ) { + G_Error( "SpawnEntities: no entities" ); + } + + // Skip this guy if its worldspawn fails + if ( !SP_bsp_worldspawn() ) + { + return; + } + + // parse ents + while( G_ParseSpawnVars(&entities) ) + { + G_SpawnSubBSPGEntityFromSpawnVars(posOffset, angOffset); + } +} + +void G_SpawnEntitiesFromString( const char *entityString ) { + const char *entities; + + entities = entityString; + + // allow calls to G_Spawn*() + spawning = qtrue; + NPCsPrecached = qfalse; + numSpawnVars = 0; + + // the worldspawn is not an actual entity, but it still + // has a "spawn" function to perform any global setup + // needed by a level (setting configstrings or cvars, etc) + if ( !G_ParseSpawnVars( &entities ) ) { + G_Error( "SpawnEntities: no entities" ); + } + + SP_worldspawn(); + + // parse ents + while( G_ParseSpawnVars( &entities ) ) + { + G_SpawnGEntityFromSpawnVars(); + } + + //Search the entities for precache information + G_ParsePrecaches(); + + + if( g_entities[ENTITYNUM_WORLD].behaviorSet[BSET_SPAWN] && g_entities[ENTITYNUM_WORLD].behaviorSet[BSET_SPAWN][0] ) + {//World has a spawn script, but we don't want the world in ICARUS and running scripts, + //so make a scriptrunner and start it going. + gentity_t *script_runner = G_Spawn(); + if ( script_runner ) + { + script_runner->behaviorSet[BSET_USE] = g_entities[ENTITYNUM_WORLD].behaviorSet[BSET_SPAWN]; + script_runner->count = 1; + script_runner->e_ThinkFunc = thinkF_scriptrunner_run; + script_runner->nextthink = level.time + 100; + + if ( Quake3Game()->ValidEntity( script_runner ) ) + { + Quake3Game()->InitEntity( script_runner ); //ICARUS_InitEnt( script_runner ); + } + } + } + + //gi.Printf(S_COLOR_YELLOW"Total waypoints: %d\n", num_waypoints); + //Automatically run routegen + //RG_RouteGen(); + + spawning = qfalse; // any future calls to G_Spawn*() will be errors + + if ( g_delayedShutdown->integer && delayedShutDown ) + { + assert(0); + G_Error( "Errors loading map, check the console for them." ); + } +} + diff --git a/code/game/g_svcmds.cpp b/code/game/g_svcmds.cpp new file mode 100644 index 0000000..b75ba15 --- /dev/null +++ b/code/game/g_svcmds.cpp @@ -0,0 +1,1344 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + + +#include "Q3_Interface.h" + +#include "g_local.h" +#include "wp_saber.h" + +extern void G_NextTestAxes( void ); +extern void G_ChangePlayerModel( gentity_t *ent, const char *newModel ); +extern void G_InitPlayerFromCvars( gentity_t *ent ); +extern void Q3_SetViewEntity(int entID, const char *name); +extern qboolean G_ClearViewEntity( gentity_t *ent ); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); + +extern void WP_SetSaber( gentity_t *ent, int saberNum, char *saberName ); +extern void WP_RemoveSaber( gentity_t *ent, int saberNum ); +extern saber_colors_t TranslateSaberColor( const char *name ); + +extern void G_SetWeapon( gentity_t *self, int wp ); +extern stringID_table_t WPTable[]; + +extern cvar_t *g_char_model; +extern cvar_t *g_char_skin_head; +extern cvar_t *g_char_skin_torso; +extern cvar_t *g_char_skin_legs; +extern cvar_t *g_char_color_red; +extern cvar_t *g_char_color_green; +extern cvar_t *g_char_color_blue; +extern cvar_t *g_saber; +extern cvar_t *g_saber2; +extern cvar_t *g_saber_color; +extern cvar_t *g_saber2_color; + +/* +=================== +Svcmd_EntityList_f +=================== +*/ +void Svcmd_EntityList_f (void) { + int e; + gentity_t *check; + + check = g_entities+1; + for (e = 1; e < globals.num_entities ; e++, check++) { + if ( !check->inuse ) { + continue; + } + gi.Printf("%3i:", e); + switch ( check->s.eType ) { + case ET_GENERAL: + gi.Printf("ET_GENERAL "); + break; + case ET_PLAYER: + gi.Printf("ET_PLAYER "); + break; + case ET_ITEM: + gi.Printf("ET_ITEM "); + break; + case ET_MISSILE: + gi.Printf("ET_MISSILE "); + break; + case ET_MOVER: + gi.Printf("ET_MOVER "); + break; + case ET_BEAM: + gi.Printf("ET_BEAM "); + break; + default: + gi.Printf("#%i", check->s.eType); + break; + } + + if ( check->classname ) { + gi.Printf("%s", check->classname); + } + gi.Printf("\n"); + } +} + +gclient_t *ClientForString( const char *s ) { + gclient_t *cl; + int i; + int idnum; + + // numeric values are just slot numbers + if ( s[0] >= '0' && s[0] <= '9' ) { + idnum = atoi( s ); + if ( idnum < 0 || idnum >= level.maxclients ) { + Com_Printf( "Bad client slot: %i\n", idnum ); + return NULL; + } + + cl = &level.clients[idnum]; + if ( cl->pers.connected == CON_DISCONNECTED ) { + gi.Printf( "Client %i is not connected\n", idnum ); + return NULL; + } + return cl; + } + + // check for a name match + for ( i=0 ; i < level.maxclients ; i++ ) { + cl = &level.clients[i]; + if ( cl->pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( !Q_stricmp( cl->pers.netname, s ) ) { + return cl; + } + } + + gi.Printf( "User %s is not on the server\n", s ); + + return NULL; +} + +//--------------------------- +extern void G_StopCinematicSkip( void ); +extern void G_StartCinematicSkip( void ); +extern void ExitEmplacedWeapon( gentity_t *ent ); +static void Svcmd_ExitView_f( void ) +{ +extern cvar_t *g_skippingcin; + static int exitViewDebounce = 0; + if ( exitViewDebounce > level.time ) + { + return; + } + exitViewDebounce = level.time + 500; + if ( in_camera ) + {//see if we need to exit an in-game cinematic + if ( g_skippingcin->integer ) // already doing cinematic skip? + {// yes... so stop skipping... + G_StopCinematicSkip(); + } + else + {// no... so start skipping... + G_StartCinematicSkip(); + } + } + else if ( !G_ClearViewEntity( player ) ) + {//didn't exit control of a droid or turret + //okay, now try exiting emplaced guns or AT-ST's + if ( player->s.eFlags & EF_LOCKED_TO_WEAPON ) + {//get out of emplaced gun + ExitEmplacedWeapon( player ); + } + else if ( player->client && player->client->NPC_class == CLASS_ATST ) + {//a player trying to get out of his ATST + GEntity_UseFunc( player->activator, player, player ); + } + } +} + +gentity_t *G_GetSelfForPlayerCmd( void ) +{ + if ( g_entities[0].client->ps.viewEntity > 0 + && g_entities[0].client->ps.viewEntity < ENTITYNUM_WORLD + && g_entities[g_entities[0].client->ps.viewEntity].client + && g_entities[g_entities[0].client->ps.viewEntity].s.weapon == WP_SABER ) + {//you're controlling another NPC + return (&g_entities[g_entities[0].client->ps.viewEntity]); + } + else + { + return (&g_entities[0]); + } +} + +static void Svcmd_Saber_f() +{ + char *saber = gi.argv(1); + char *saber2 = gi.argv(2); + + if ( !g_entities[0].client || !saber || !saber[0] ) + { + return; + } + + gi.cvar_set( "g_saber", saber ); + WP_SetSaber( &g_entities[0], 0, saber ); + if ( saber2 && saber2[0] && !g_entities[0].client->ps.saber[0].twoHanded ) + {//want to use a second saber and first one is not twoHanded + gi.cvar_set( "g_saber2", saber2 ); + WP_SetSaber( &g_entities[0], 1, saber2 ); + } + else + { + gi.cvar_set( "g_saber2", "" ); + WP_RemoveSaber( &g_entities[0], 1 ); + } +} + +static void Svcmd_SaberBlade_f() +{ + if ( gi.argc() < 2 ) + { + gi.Printf( "USAGE: saberblade [0 = off, 1 = on, no arg = toggle]\n" ); + return; + } + if ( &g_entities[0] == NULL || &g_entities[0].client == NULL ) + { + return; + } + int sabernum = atoi(gi.argv(1)) - 1; + if ( sabernum < 0 || sabernum > 1 ) + { + return; + } + if ( sabernum > 0 && !g_entities[0].client->ps.dualSabers ) + { + return; + } + //FIXME: what if don't even have a single saber at all? + int bladenum = atoi(gi.argv(2)) - 1; + if ( bladenum < 0 || bladenum >= g_entities[0].client->ps.saber[sabernum].numBlades ) + { + return; + } + qboolean turnOn; + if ( gi.argc() > 2 ) + {//explicit + turnOn = (atoi(gi.argv(3))!=0); + } + else + {//toggle + turnOn = (g_entities[0].client->ps.saber[sabernum].blade[bladenum].active==qfalse); + } + + g_entities[0].client->ps.SaberBladeActivate( sabernum, bladenum, turnOn ); +} + +static void Svcmd_SaberColor_f() +{//FIXME: just list the colors, each additional listing sets that blade + int saberNum = atoi(gi.argv(1)); + char *color[MAX_BLADES]; + int bladeNum; + + for ( bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ ) + { + color[bladeNum] = gi.argv(2+bladeNum); + } + + if ( !VALIDSTRING( color ) || saberNum < 1 || saberNum > 2 ) + { + gi.Printf( "Usage: saberColor ... \n" ); + gi.Printf( "valid saberNums: 1 or 2\n" ); + gi.Printf( "valid colors: red, orange, yellow, green, blue, and purple\n" ); + + return; + } + saberNum--; + + gentity_t *self = G_GetSelfForPlayerCmd(); + + for ( bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ ) + { + if ( !color[bladeNum] || !color[bladeNum][0] ) + { + break; + } + else + { + self->client->ps.saber[saberNum].blade[bladeNum].color = TranslateSaberColor( color[bladeNum] ); + } + } + + if ( saberNum == 0 ) + { + gi.cvar_set( "g_saber_color", color[0] ); + } + else if ( saberNum == 1 ) + { + gi.cvar_set( "g_saber2_color", color[0] ); + } +} + +static void Svcmd_ForceJump_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current forceJump level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_LEVITATION] ); + gi.Printf( "Usage: setForceJump (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_LEVITATION ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_LEVITATION ); + } + g_entities[0].client->ps.forcePowerLevel[FP_LEVITATION] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_LEVITATION] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_3 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3; + } +} + +static void Svcmd_SaberThrow_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current saberThrow level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_SABERTHROW] ); + gi.Printf( "Usage: setSaberThrow (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_SABERTHROW ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_SABERTHROW ); + } + g_entities[0].client->ps.forcePowerLevel[FP_SABERTHROW] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_SABERTHROW] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_3 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_3; + } +} + +static void Svcmd_ForceHeal_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current forceHeal level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_HEAL] ); + gi.Printf( "Usage: forceHeal (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_HEAL ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_HEAL ); + } + g_entities[0].client->ps.forcePowerLevel[FP_HEAL] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_HEAL] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_3 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_HEAL] = FORCE_LEVEL_3; + } +} + +static void Svcmd_ForcePush_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current forcePush level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_PUSH] ); + gi.Printf( "Usage: forcePush (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_PUSH ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_PUSH ); + } + g_entities[0].client->ps.forcePowerLevel[FP_PUSH] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_3 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_3; + } +} + +static void Svcmd_ForcePull_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current forcePull level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_PULL] ); + gi.Printf( "Usage: forcePull (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_PULL ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_PULL ); + } + g_entities[0].client->ps.forcePowerLevel[FP_PULL] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_3 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_3; + } +} + +static void Svcmd_ForceSpeed_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current forceSpeed level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_SPEED] ); + gi.Printf( "Usage: forceSpeed (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_SPEED ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_SPEED ); + } + g_entities[0].client->ps.forcePowerLevel[FP_SPEED] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_SPEED] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_SPEED] > FORCE_LEVEL_3 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_3; + } +} + +static void Svcmd_ForceGrip_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current forceGrip level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_GRIP] ); + gi.Printf( "Usage: forceGrip (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_GRIP ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_GRIP ); + } + g_entities[0].client->ps.forcePowerLevel[FP_GRIP] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_3 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_3; + } +} + +static void Svcmd_ForceLightning_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current forceLightning level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_LIGHTNING] ); + gi.Printf( "Usage: forceLightning (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_LIGHTNING ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_LIGHTNING ); + } + g_entities[0].client->ps.forcePowerLevel[FP_LIGHTNING] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_3 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_3; + } +} + +static void Svcmd_MindTrick_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current mindTrick level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_TELEPATHY] ); + gi.Printf( "Usage: mindTrick (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_TELEPATHY ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_TELEPATHY ); + } + g_entities[0].client->ps.forcePowerLevel[FP_TELEPATHY] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_TELEPATHY] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_TELEPATHY] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_4 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_TELEPATHY] = FORCE_LEVEL_4; + } +} + +static void Svcmd_SaberDefense_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current saberDefense level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_SABER_DEFENSE] ); + gi.Printf( "Usage: saberDefense (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_SABER_DEFENSE ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_SABER_DEFENSE ); + } + g_entities[0].client->ps.forcePowerLevel[FP_SABER_DEFENSE] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_3 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3; + } +} + +static void Svcmd_SaberOffense_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current saberOffense level is %d\n", g_entities[0].client->ps.forcePowerLevel[FP_SABER_OFFENSE] ); + gi.Printf( "Usage: saberOffense (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << FP_SABER_OFFENSE ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << FP_SABER_OFFENSE ); + } + g_entities[0].client->ps.forcePowerLevel[FP_SABER_OFFENSE] = val; + if ( g_entities[0].client->ps.forcePowerLevel[FP_SABER_OFFENSE] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[FP_SABER_OFFENSE] >= SS_NUM_SABER_STYLES ) + { + g_entities[0].client->ps.forcePowerLevel[FP_SABER_OFFENSE] = SS_NUM_SABER_STYLES-1; + } +} + +static void Svcmd_ForceSetLevel_f( int forcePower ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return; + } + char *newVal = gi.argv(1); + if ( !VALIDSTRING( newVal ) ) + { + gi.Printf( "Current force level is %d\n", g_entities[0].client->ps.forcePowerLevel[forcePower] ); + gi.Printf( "Usage: force (1 - 3)\n" ); + return; + } + int val = atoi(newVal); + if ( val > FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << forcePower ); + } + else + { + g_entities[0].client->ps.forcePowersKnown &= ~( 1 << forcePower ); + } + g_entities[0].client->ps.forcePowerLevel[forcePower] = val; + if ( g_entities[0].client->ps.forcePowerLevel[forcePower] < FORCE_LEVEL_0 ) + { + g_entities[0].client->ps.forcePowerLevel[forcePower] = FORCE_LEVEL_0; + } + else if ( g_entities[0].client->ps.forcePowerLevel[forcePower] > FORCE_LEVEL_3 ) + { + g_entities[0].client->ps.forcePowerLevel[forcePower] = FORCE_LEVEL_3; + } +} + +extern qboolean PM_SaberInStart( int move ); +extern qboolean PM_SaberInTransition( int move ); +extern qboolean PM_SaberInAttack( int move ); +void Svcmd_SaberAttackCycle_f( void ) +{ + if ( !&g_entities[0] || !g_entities[0].client ) + { + return; + } + + gentity_t *self = G_GetSelfForPlayerCmd(); + if ( self->s.weapon != WP_SABER ) + {// saberAttackCycle button also switches to saber + gi.SendConsoleCommand("weapon 1" ); + return; + } + + if ( self->client->ps.dualSabers ) + {//can't cycle styles with dualSabers, so just toggle second saber on/off + if ( self->client->ps.saber[1].Active() ) + {//turn it off + self->client->ps.saber[1].Deactivate(); + G_SoundIndexOnEnt( self, CHAN_WEAPON, self->client->ps.saber[0].soundOff ); + } + else if ( !self->client->ps.saber[0].Active() ) + {//first one is off, too, so just turn that one on + if ( !self->client->ps.saberInFlight ) + {//but only if it's in your hand! + self->client->ps.saber[0].Activate(); + } + } + else + {//turn on the second one + self->client->ps.saber[1].Activate(); + } + return; + } + else if ( self->client->ps.saber[0].numBlades > 1 )//self->client->ps.saber[0].type == SABER_STAFF ) + {//can't cycle styles with saberstaff, so just toggles saber blades on/off + if ( self->client->ps.saberInFlight ) + {//can't turn second blade back on if it's in the air, you naughty boy! + return; + } + qboolean playedSound = qfalse; + if ( !self->client->ps.saber[0].blade[0].active ) + {//first one is not even on + //turn only it on + self->client->ps.SaberBladeActivate( 0, 0, qtrue ); + return; + } + + for ( int i = 1; i < self->client->ps.saber[0].numBlades; i++ ) + { + if ( !self->client->ps.saber[0].blade[i].active ) + {//extra is off, turn it on + self->client->ps.SaberBladeActivate( 0, i, qtrue ); + } + else + {//turn extra off + self->client->ps.SaberBladeActivate( 0, i, qfalse ); + if ( !playedSound ) + { + G_SoundIndexOnEnt( self, CHAN_WEAPON, self->client->ps.saber[0].soundOff ); + playedSound = qtrue; + } + } + } + return; + } + + //FIXME: if dualSabers and both on, do something here, too... maybe toggle the second one on/off? + + if ( !self->client->ps.saberStylesKnown ) + { + return; + } + + int saberAnimLevel; + if ( !self->s.number ) + { + saberAnimLevel = cg.saberAnimLevelPending; + } + else + { + saberAnimLevel = self->client->ps.saberAnimLevel; + } + saberAnimLevel++; + int sanityCheck = 0; + while ( self->client->ps.saberAnimLevel != saberAnimLevel + && !(self->client->ps.saberStylesKnown&(1< SS_STAFF ) + { + saberAnimLevel = SS_FAST; + } + sanityCheck++; + } + if ( !(self->client->ps.saberStylesKnown&(1<s.number ) + { + cg.saberAnimLevelPending = saberAnimLevel; + } + else + { + self->client->ps.saberAnimLevel = saberAnimLevel; + } + +#ifndef FINAL_BUILD + switch ( saberAnimLevel ) + { + case SS_FAST: + gi.Printf( S_COLOR_BLUE"Lightsaber Combat Style: Fast\n" ); + //LIGHTSABERCOMBATSTYLE_FAST + break; + case SS_MEDIUM: + gi.Printf( S_COLOR_YELLOW"Lightsaber Combat Style: Medium\n" ); + //LIGHTSABERCOMBATSTYLE_MEDIUM + break; + case SS_STRONG: + gi.Printf( S_COLOR_RED"Lightsaber Combat Style: Strong\n" ); + //LIGHTSABERCOMBATSTYLE_STRONG + break; + case SS_DESANN: + gi.Printf( S_COLOR_CYAN"Lightsaber Combat Style: Desann\n" ); + //LIGHTSABERCOMBATSTYLE_DESANN + break; + case SS_TAVION: + gi.Printf( S_COLOR_MAGENTA"Lightsaber Combat Style: Tavion\n" ); + //LIGHTSABERCOMBATSTYLE_TAVION + break; + case SS_DUAL: + gi.Printf( S_COLOR_MAGENTA"Lightsaber Combat Style: Dual\n" ); + //LIGHTSABERCOMBATSTYLE_TAVION + break; + case SS_STAFF: + gi.Printf( S_COLOR_MAGENTA"Lightsaber Combat Style: Staff\n" ); + //LIGHTSABERCOMBATSTYLE_TAVION + break; + } + //gi.Printf("\n"); +#endif +} + +qboolean G_ReleaseEntity( gentity_t *grabber ) +{ + if ( grabber && grabber->client && grabber->client->ps.heldClient < ENTITYNUM_WORLD ) + { + gentity_t *heldClient = &g_entities[grabber->client->ps.heldClient]; + grabber->client->ps.heldClient = ENTITYNUM_NONE; + if ( heldClient && heldClient->client ) + { + heldClient->client->ps.heldByClient = ENTITYNUM_NONE; + + heldClient->owner = NULL; + } + return qtrue; + } + return qfalse; +} + +void G_GrabEntity( gentity_t *grabber, char *target ) +{ + if ( !grabber || !grabber->client ) + { + return; + } + gentity_t *heldClient = G_Find( NULL, FOFS(targetname), (char *)target ); + if ( heldClient && heldClient->client && heldClient != grabber )//don't grab yourself, it's not polite + {//found him + grabber->client->ps.heldClient = heldClient->s.number; + heldClient->client->ps.heldByClient = grabber->s.number; + + heldClient->owner = grabber; + } +} + +/* +================= +ConsoleCommand +// these are added in cg_main, CG_Init so they tab-complete +================= +*/ +qboolean ConsoleCommand( void ) { + char *cmd; + + cmd = gi.argv(0); + + if ( Q_stricmp (cmd, "entitylist") == 0 ) + { + Svcmd_EntityList_f(); + return qtrue; + } + + if (Q_stricmp (cmd, "game_memory") == 0) { + Svcmd_GameMem_f(); + return qtrue; + } + +// if (Q_stricmp (cmd, "addbot") == 0) { +// Svcmd_AddBot_f(); +// return qtrue; +// } + + if (Q_stricmp (cmd, "nav") == 0) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + Svcmd_Nav_f (); + return qtrue; + } + + if (Q_stricmp (cmd, "npc") == 0) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + Svcmd_NPC_f (); + return qtrue; + } + + if (Q_stricmp (cmd, "use") == 0) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + Svcmd_Use_f (); + return qtrue; + } + + if ( Q_stricmp( cmd, "ICARUS" ) == 0 ) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + + Quake3Game()->Svcmd(); + + return qtrue; + } + + if ( Q_stricmp( cmd, "saberColor" ) == 0 ) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + Svcmd_SaberColor_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "saber" ) == 0 ) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + Svcmd_Saber_f(); + return qtrue; + } + + if ( Q_stricmp( cmd, "saberblade" ) == 0 ) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + Svcmd_SaberBlade_f(); + return qtrue; + } + + + if ( Q_stricmp( cmd, "setForceJump" ) == 0 ) + { + Svcmd_ForceJump_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setSaberThrow" ) == 0 ) + { + Svcmd_SaberThrow_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setForceHeal" ) == 0 ) + { + Svcmd_ForceHeal_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setForcePush" ) == 0 ) + { + Svcmd_ForcePush_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setForcePull" ) == 0 ) + { + Svcmd_ForcePull_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setForceSpeed" ) == 0 ) + { + Svcmd_ForceSpeed_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setForceGrip" ) == 0 ) + { + Svcmd_ForceGrip_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setForceLightning" ) == 0 ) + { + Svcmd_ForceLightning_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setMindTrick" ) == 0 ) + { + Svcmd_MindTrick_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setSaberDefense" ) == 0 ) + { + Svcmd_SaberDefense_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setSaberOffense" ) == 0 ) + { + Svcmd_SaberOffense_f(); + return qtrue; + } + if ( Q_stricmp( cmd, "setForceRage" ) == 0 ) + { + Svcmd_ForceSetLevel_f( FP_RAGE ); + return qtrue; + } + if ( Q_stricmp( cmd, "setForceDrain" ) == 0 ) + { + Svcmd_ForceSetLevel_f( FP_DRAIN ); + return qtrue; + } + if ( Q_stricmp( cmd, "setForceProtect" ) == 0 ) + { + Svcmd_ForceSetLevel_f( FP_PROTECT ); + return qtrue; + } + if ( Q_stricmp( cmd, "setForceAbsorb" ) == 0 ) + { + Svcmd_ForceSetLevel_f( FP_ABSORB ); + return qtrue; + } + if ( Q_stricmp( cmd, "setForceSight" ) == 0 ) + { + Svcmd_ForceSetLevel_f( FP_SEE ); + return qtrue; + } + if ( Q_stricmp( cmd, "setForceAll" ) == 0 ) + { + Svcmd_ForceJump_f(); + Svcmd_SaberThrow_f(); + Svcmd_ForceHeal_f(); + Svcmd_ForcePush_f(); + Svcmd_ForcePull_f(); + Svcmd_ForceSpeed_f(); + Svcmd_ForceGrip_f(); + Svcmd_ForceLightning_f(); + Svcmd_MindTrick_f(); + Svcmd_SaberDefense_f(); + Svcmd_SaberOffense_f(); + Svcmd_ForceSetLevel_f( FP_RAGE ); + Svcmd_ForceSetLevel_f( FP_DRAIN ); + Svcmd_ForceSetLevel_f( FP_PROTECT ); + Svcmd_ForceSetLevel_f( FP_ABSORB ); + Svcmd_ForceSetLevel_f( FP_SEE ); + for ( int i = SS_FAST; i < SS_NUM_SABER_STYLES; i++ ) + { + g_entities[0].client->ps.saberStylesKnown |= (1<integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + char *cmd2 = gi.argv(1); + + if ( cmd2 && cmd2[0] ) + { + char *cmd3 = gi.argv(2); + if ( cmd3 && cmd3[0] ) + { + gentity_t *found = NULL; + if ( (found = G_Find(NULL, FOFS(targetname), cmd2 ) ) != NULL ) + { + Quake3Game()->RunScript( found, cmd3 ); + } + else + { + //can't find cmd2 + gi.Printf( S_COLOR_RED"runscript: can't find targetname %s\n", cmd2 ); + } + } + else + { + Quake3Game()->RunScript( &g_entities[0], cmd2 ); + } + } + else + { + gi.Printf( S_COLOR_RED"usage: runscript scriptname\n" ); + } + //FIXME: else warning + return qtrue; + } + + if ( Q_stricmp( cmd, "playerteam" ) == 0 ) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + char *cmd2 = gi.argv(1); + int n; + + if ( !*cmd2 || !cmd2[0] ) + { + gi.Printf( S_COLOR_RED"'playerteam' - change player team, requires a team name!\n" ); + gi.Printf( S_COLOR_RED"Valid team names are:\n"); + for ( n = (TEAM_FREE + 1); n < TEAM_NUM_TEAMS; n++ ) + { + gi.Printf( S_COLOR_RED"%s\n", GetStringForID( TeamTable, n ) ); + } + } + else + { + team_t team; + + team = (team_t)GetIDForString( TeamTable, cmd2 ); + if ( team == -1 ) + { + gi.Printf( S_COLOR_RED"'playerteam' unrecognized team name %s!\n", cmd2 ); + gi.Printf( S_COLOR_RED"Valid team names are:\n"); + for ( n = TEAM_FREE; n < TEAM_NUM_TEAMS; n++ ) + { + gi.Printf( S_COLOR_RED"%s\n", GetStringForID( TeamTable, n ) ); + } + } + else + { + g_entities[0].client->playerTeam = team; + //FIXME: convert Imperial, Malon, Hirogen and Klingon to Scavenger? + } + } + return qtrue; + } + + if ( Q_stricmp( cmd, "control" ) == 0 ) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + char *cmd2 = gi.argv(1); + if ( !*cmd2 || !cmd2[0] ) + { + if ( !G_ClearViewEntity( &g_entities[0] ) ) + { + gi.Printf( S_COLOR_RED"control \n", cmd2 ); + } + } + else + { + Q3_SetViewEntity( 0, cmd2 ); + } + return qtrue; + } + + if ( Q_stricmp( cmd, "grab" ) == 0 ) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + char *cmd2 = gi.argv(1); + if ( !*cmd2 || !cmd2[0] ) + { + if ( !G_ReleaseEntity( &g_entities[0] ) ) + { + gi.Printf( S_COLOR_RED"grab \n", cmd2 ); + } + } + else + { + G_GrabEntity( &g_entities[0], cmd2 ); + } + return qtrue; + } + + if ( Q_stricmp( cmd, "knockdown" ) == 0 ) + { + if ( !g_cheats->integer ) + { + gi.SendServerCommand( 0, "print \"Cheats are not enabled on this server.\n\""); + return qfalse; + } + G_Knockdown( &g_entities[0], &g_entities[0], vec3_origin, 300, qtrue ); + return qtrue; + } + + if ( Q_stricmp( cmd, "playerModel" ) == 0 ) + { + if ( gi.argc() == 1 ) + { + gi.Printf( S_COLOR_RED"USAGE: playerModel \n playerModel \n playerModel player (builds player from customized menu settings)\n" ); + gi.Printf( "playerModel = %s ", va("%s %s %s %s\n", g_char_model->string, g_char_skin_head->string, g_char_skin_torso->string, g_char_skin_legs->string ) ); + } + else if ( gi.argc() == 2 ) + { + G_ChangePlayerModel( &g_entities[0], gi.argv(1) ); + } + else if ( gi.argc() == 5 ) + { + //instead of setting it directly via a command, we now store it in cvars + //G_ChangePlayerModel( &g_entities[0], va("%s|%s|%s|%s", gi.argv(1), gi.argv(2), gi.argv(3), gi.argv(4)) ); + gi.cvar_set("g_char_model", gi.argv(1) ); + gi.cvar_set("g_char_skin_head", gi.argv(2) ); + gi.cvar_set("g_char_skin_torso", gi.argv(3) ); + gi.cvar_set("g_char_skin_legs", gi.argv(4) ); + G_InitPlayerFromCvars( &g_entities[0] ); + } + return qtrue; + } + + if ( Q_stricmp( cmd, "playerTint" ) == 0 ) + { + if ( gi.argc() == 4 ) + { + g_entities[0].client->renderInfo.customRGBA[0] = atoi(gi.argv(1)); + g_entities[0].client->renderInfo.customRGBA[1] = atoi(gi.argv(2)); + g_entities[0].client->renderInfo.customRGBA[2] = atoi(gi.argv(3)); + gi.cvar_set("g_char_color_red", gi.argv(1) ); + gi.cvar_set("g_char_color_green", gi.argv(2) ); + gi.cvar_set("g_char_color_blue", gi.argv(3) ); + } + else + { + gi.Printf( S_COLOR_RED"USAGE: playerTint \n" ); + gi.Printf( "playerTint = %s\n", va("%d %d %d", g_char_color_red->integer, g_char_color_green->integer, g_char_color_blue->integer ) ); + } + return qtrue; + } + if ( Q_stricmp( cmd, "nexttestaxes" ) == 0 ) + { + G_NextTestAxes(); + } + + if ( Q_stricmp( cmd, "exitview" ) == 0 ) + { + Svcmd_ExitView_f(); + } + + if (Q_stricmp (cmd, "iknowkungfu") == 0) + { + gi.cvar_set( "g_debugMelee", "1" ); + G_SetWeapon( &g_entities[0], WP_MELEE ); + /* + for ( int i = FP_FIRST; i < NUM_FORCE_POWERS; i++ ) + { + g_entities[0].client->ps.forcePowersKnown |= ( 1 << i ); + if ( i == FP_TELEPATHY ) + { + g_entities[0].client->ps.forcePowerLevel[i] = FORCE_LEVEL_4; + } + else + { + g_entities[0].client->ps.forcePowerLevel[i] = FORCE_LEVEL_3; + } + } + */ + } + + return qfalse; +} + diff --git a/code/game/g_target.cpp b/code/game/g_target.cpp new file mode 100644 index 0000000..5010431 --- /dev/null +++ b/code/game/g_target.cpp @@ -0,0 +1,1239 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "Q3_Interface.h" +#include "g_local.h" +#include "g_functions.h" +extern void G_SetEnemy( gentity_t *self, gentity_t *enemy ); + +//========================================================== + +/*QUAKED target_give (1 0 0) (-8 -8 -8) (8 8 8) +Gives the activator all the items pointed to. +*/ +void Use_Target_Give( gentity_t *ent, gentity_t *other, gentity_t *activator ) { + gentity_t *t; + trace_t trace; + + if ( !activator->client ) { + return; + } + + if ( !ent->target ) { + return; + } + + G_ActivateBehavior(ent,BSET_USE); + + memset( &trace, 0, sizeof( trace ) ); + t = NULL; + while ( (t = G_Find (t, FOFS(targetname), ent->target)) != NULL ) { + if ( !t->item ) { + continue; + } + Touch_Item( t, activator, &trace ); + + // make sure it isn't going to respawn or show any events + t->nextthink = 0; + gi.unlinkentity( t ); + } +} + +void SP_target_give( gentity_t *ent ) { + ent->e_UseFunc = useF_Use_Target_Give; +} + +//========================================================== + +/*QUAKED target_delay (1 0 0) (-8 -8 -8) (8 8 8) +"wait" seconds to pause before firing targets. +"random" delay variance, total delay = delay +/- random seconds +*/ +void Think_Target_Delay( gentity_t *ent ) +{ + G_UseTargets( ent, ent->activator ); +} + +void Use_Target_Delay( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + G_ActivateBehavior(ent,BSET_USE); + + ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000; + ent->e_ThinkFunc = thinkF_Think_Target_Delay; + ent->activator = activator; +} + +void SP_target_delay( gentity_t *ent ) +{ + // check delay for backwards compatability + if ( !G_SpawnFloat( "delay", "0", &ent->wait ) ) + { + G_SpawnFloat( "wait", "1", &ent->wait ); + } + + if ( !ent->wait ) + { + ent->wait = 1; + } + + ent->e_UseFunc = useF_Use_Target_Delay; +} + + +//========================================================== + +/*QUAKED target_score (1 0 0) (-8 -8 -8) (8 8 8) +"count" number of points to add, default 1 + +The activator is given this many points. +*/ +void Use_Target_Score (gentity_t *ent, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(ent,BSET_USE); + + AddScore( activator, ent->count ); +} + +void SP_target_score( gentity_t *ent ) { + if ( !ent->count ) { + ent->count = 1; + } + ent->e_UseFunc = useF_Use_Target_Score; +} + + +//========================================================== + +/*QUAKED target_print (1 0 0) (-8 -8 -8) (8 8 8) +"message" text to print +If "private", only the activator gets the message. If no checks, all clients get the message. +*/ +void Use_Target_Print (gentity_t *ent, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(ent,BSET_USE); + + if ( activator->client ) { + gi.SendServerCommand( activator-g_entities, "cp \"%s\"", ent->message ); + } +} + +void SP_target_print( gentity_t *ent ) { + ent->e_UseFunc = useF_Use_Target_Print; +} + + +//========================================================== + + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off global activator +"noise" wav file to play + +"sounds" va() min max, so if your sound string is borgtalk%d.wav, and you set a "sounds" value of 4, it will randomly play borgtalk1.wav - borgtalk4.wav when triggered +to use this, you must store the wav name in "soundGroup", NOT "noise" + +A global sound will play full volume throughout the level. +Activator sounds will play on the player that activated the target. +Global and activator sounds can't be combined with looping. +Normal sounds play each time the target is used. +Looped sounds will be toggled by use functions. +Multiple identical looping sounds will just increase volume without any speed cost. +"wait" : Seconds between triggerings, 0 = don't auto trigger +"random" wait variance, default is 0 +*/ +void Use_Target_Speaker (gentity_t *ent, gentity_t *other, gentity_t *activator) { + if(ent->painDebounceTime > level.time) + { + return; + } + + G_ActivateBehavior(ent,BSET_USE); + + if ( ent->sounds ) + { + ent->noise_index = G_SoundIndex( va( ent->paintarget, Q_irand(1, ent->sounds ) ) ); + } + + if (ent->spawnflags & 3) { // looping sound toggles + gentity_t *looper = ent; + if ( ent->spawnflags & 8 ) { + looper = activator; + } + if (looper->s.loopSound) + looper->s.loopSound = 0; // turn it off + else + looper->s.loopSound = ent->noise_index; // start it + }else { // normal sound + if ( ent->spawnflags & 8 ) { + G_AddEvent( activator, EV_GENERAL_SOUND, ent->noise_index ); + } else if (ent->spawnflags & 4) { + G_AddEvent( ent, EV_GLOBAL_SOUND, ent->noise_index ); + } else { + G_AddEvent( ent, EV_GENERAL_SOUND, ent->noise_index ); + } + } + + if(ent->wait < 0) + {//BYE! + ent->e_UseFunc = useF_NULL; + } + else + { + ent->painDebounceTime = level.time + ent->wait; + } +} + +void SP_target_speaker( gentity_t *ent ) { + char buffer[MAX_QPATH]; + char *s; + int i; + + if ( VALIDSTRING( ent->soundSet ) ) + { + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + gi.linkentity (ent); + return; + } + + G_SpawnFloat( "wait", "0", &ent->wait ); + G_SpawnFloat( "random", "0", &ent->random ); + + if(!ent->sounds) + { + if ( !G_SpawnString( "noise", "*NOSOUND*", &s ) ) { + G_Error( "target_speaker without a noise key at %s", vtos( ent->s.origin ) ); + } + + Q_strncpyz( buffer, s, sizeof(buffer) ); + COM_DefaultExtension( buffer, sizeof(buffer), ".wav"); + ent->noise_index = G_SoundIndex(buffer); + } + else + {//Precache all possible sounds + for( i = 0; i < ent->sounds; i++ ) + { + ent->noise_index = G_SoundIndex( va( ent->paintarget, i+1 ) ); + } + } + + // a repeating speaker can be done completely client side + ent->s.eType = ET_SPEAKER; + ent->s.eventParm = ent->noise_index; + ent->s.frame = ent->wait * 10; + ent->s.clientNum = ent->random * 10; + + ent->wait *= 1000; + + // check for prestarted looping sound + if ( ent->spawnflags & 1 ) { + ent->s.loopSound = ent->noise_index; + } + + ent->e_UseFunc = useF_Use_Target_Speaker; + + if (ent->spawnflags & 4) { + ent->svFlags |= SVF_BROADCAST; + } + + VectorCopy( ent->s.origin, ent->s.pos.trBase ); + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + gi.linkentity (ent); +} + + + +//========================================================== + +/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON +When triggered, fires a laser. You can either set a target or a direction. +*/ +void target_laser_think (gentity_t *self) { + vec3_t end; + trace_t tr; + vec3_t point; + + // if pointed at another entity, set movedir to point at it + if ( self->enemy ) { + VectorMA (self->enemy->s.origin, 0.5, self->enemy->mins, point); + VectorMA (point, 0.5, self->enemy->maxs, point); + VectorSubtract (point, self->s.origin, self->movedir); + VectorNormalize (self->movedir); + } + + // fire forward and see what we hit + VectorMA (self->s.origin, 2048, self->movedir, end); + + gi.trace( &tr, self->s.origin, NULL, NULL, end, self->s.number, CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE); + + if ( tr.entityNum ) { + // hurt it if we can + G_Damage ( &g_entities[tr.entityNum], self, self->activator, self->movedir, + tr.endpos, self->damage, DAMAGE_NO_KNOCKBACK, MOD_ENERGY ); + } + + VectorCopy (tr.endpos, self->s.origin2); + + gi.linkentity( self ); + self->nextthink = level.time + FRAMETIME; +} + +void target_laser_on (gentity_t *self) +{ + if (!self->activator) + self->activator = self; + target_laser_think (self); +} + +void target_laser_off (gentity_t *self) +{ + gi.unlinkentity( self ); + self->nextthink = 0; +} + +void target_laser_use (gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + self->activator = activator; + if ( self->nextthink > 0 ) + target_laser_off (self); + else + target_laser_on (self); +} + +void target_laser_start (gentity_t *self) +{ + gentity_t *ent; + + self->s.eType = ET_BEAM; + + if (self->target) { + ent = G_Find (NULL, FOFS(targetname), self->target); + if (!ent) { + gi.Printf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target); + } + G_SetEnemy( self, ent ); + } else { + G_SetMovedir (self->s.angles, self->movedir); + } + + self->e_UseFunc = useF_target_laser_use; + self->e_ThinkFunc = thinkF_target_laser_think; + + if ( !self->damage ) { + self->damage = 1; + } + + if (self->spawnflags & 1) + target_laser_on (self); + else + target_laser_off (self); +} + +void SP_target_laser (gentity_t *self) +{ + // let everything else get spawned before we start firing + self->e_ThinkFunc = thinkF_target_laser_start; + self->nextthink = level.time + START_TIME_LINK_ENTS; +} + + +//========================================================== + +void target_teleporter_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + gentity_t *dest; + + if (!activator->client) + return; + + G_ActivateBehavior(self,BSET_USE); + + dest = G_PickTarget( self->target ); + if (!dest) { + gi.Printf ("Couldn't find teleporter destination\n"); + return; + } + + TeleportPlayer( activator, dest->s.origin, dest->s.angles ); +} + +/*QUAK-ED target_teleporter (1 0 0) (-8 -8 -8) (8 8 8) +The activator will be teleported away. +*/ +void SP_target_teleporter( gentity_t *self ) { + if (!self->targetname) + gi.Printf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); + + self->e_UseFunc = useF_target_teleporter_use; +} + +//========================================================== + + +/*QUAKED target_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) RED_ONLY BLUE_ONLY RANDOM x x x x INACTIVE +This doesn't perform any actions except fire its targets. +The activator can be forced to be from a certain team. +if RANDOM is checked, only one of the targets will be fired, not all of them + +INACTIVE Can't be used until activated + + "delay" - Will actually fire this many seconds after being used + "wait" - Cannot be fired again until this many seconds after the last time it was used +*/ +void target_relay_use_go (gentity_t *self ) +{ + G_ActivateBehavior( self, BSET_USE ); + + if ( self->spawnflags & 4 ) + { + gentity_t *ent; + + ent = G_PickTarget( self->target ); + if ( ent && (ent->e_UseFunc != useF_NULL) ) + { // e_UseFunc check can be omitted + GEntity_UseFunc( ent, self, self->activator ); + } + return; + } + + G_UseTargets( self, self->activator ); +} + +void target_relay_use (gentity_t *self, gentity_t *other, gentity_t *activator) +{ + if ( ( self->spawnflags & 1 ) && activator->client ) + {//&& activator->client->ps.persistant[PERS_TEAM] != TEAM_RED ) { + return; + } + + if ( ( self->spawnflags & 2 ) && activator->client ) + {//&& activator->client->ps.persistant[PERS_TEAM] != TEAM_BLUE ) { + return; + } + + if ( self->svFlags & SVF_INACTIVE ) + {//set by target_deactivate + return; + } + + if ( self->painDebounceTime > level.time ) + { + return; + } + + G_SetEnemy( self, other ); + self->activator = activator; + + if ( self->delay ) + { + self->e_ThinkFunc = thinkF_target_relay_use_go; + self->nextthink = level.time + self->delay; + return; + } + + target_relay_use_go( self ); + + if ( self->wait < 0 ) + { + self->e_UseFunc = useF_NULL; + } + else + { + self->painDebounceTime = level.time + self->wait; + } +} + +void SP_target_relay (gentity_t *self) +{ + self->e_UseFunc = useF_target_relay_use; + self->wait *= 1000; + self->delay *= 1000; + if ( self->spawnflags&128 ) + { + self->svFlags |= SVF_INACTIVE; + } +} + + +//========================================================== + +/*QUAKED target_kill (.5 .5 .5) (-8 -8 -8) (8 8 8) FALLING ELECTRICAL +Kills the activator. +*/ +void target_kill_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + + G_ActivateBehavior(self,BSET_USE); + + if ( self->spawnflags & 1 ) + {//falling death + G_Damage ( activator, NULL, NULL, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_FALLING ); + if ( !activator->s.number && activator->health <= 0 && 1 ) + { + extern void CGCam_Fade( vec4_t source, vec4_t dest, float duration ); + float src[4] = {0,0,0,0},dst[4]={0,0,0,1}; + CGCam_Fade( src, dst, 10000 ); + } + } + else if ( self->spawnflags & 2 ) // electrical + { + G_Damage ( activator, NULL, NULL, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_ELECTROCUTE ); + + if ( activator->client ) + { + activator->s.powerups |= ( 1 << PW_SHOCKED ); + activator->client->ps.powerups[PW_SHOCKED] = level.time + 4000; + } + } + else + { + G_Damage ( activator, NULL, NULL, NULL, NULL, 100000, DAMAGE_NO_PROTECTION, MOD_UNKNOWN); + } +} + +void SP_target_kill( gentity_t *self ) +{ + self->e_UseFunc = useF_target_kill_use; +} + +/*QUAKED target_position (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for in-game calculation, like jumppad targets. +info_notnull does the same thing +*/ +void SP_target_position( gentity_t *self ){ + G_SetOrigin( self, self->s.origin ); +} + +//static -slc +void target_location_linkup(gentity_t *ent) +{ + int i; + + if (level.locationLinked) + return; + + level.locationLinked = qtrue; + + level.locationHead = NULL; + + for (i = 0, ent = g_entities; i < globals.num_entities; i++, ent++) { + if (ent->classname && !Q_stricmp(ent->classname, "target_location")) { + ent->nextTrain = level.locationHead; + level.locationHead = ent; + } + } + + // All linked together now +} + +/*QUAKED target_location (0 0.5 0) (-8 -8 -8) (8 8 8) +Set "message" to the name of this location. +Set "count" to 0-7 for color. +0:white 1:red 2:green 3:yellow 4:blue 5:cyan 6:magenta 7:white + +Closest target_location in sight used for the location, if none +in site, closest in distance +*/ +void SP_target_location( gentity_t *self ){ + self->e_ThinkFunc = thinkF_target_location_linkup; + self->nextthink = level.time + 1000; // Let them all spawn first + + G_SetOrigin( self, self->s.origin ); +} + +//===NEW=================================================================== + +/*QUAKED target_counter (1.0 0 0) (-4 -4 -4) (4 4 4) x x x x x x x INACTIVE +Acts as an intermediary for an action that takes multiple inputs. + +INACTIVE cannot be used until used by a target_activate + +target2 - what the counter should fire each time it's incremented and does NOT reach it's count + +After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself. + +bounceCount - number of times the counter should reset to it's full count when it's done +*/ + +void target_counter_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( self->count == 0 ) + { + return; + } + + //gi.Printf("target_counter %s used by %s, entnum %d\n", self->targetname, activator->targetname, activator->s.number ); + self->count--; + + if ( activator ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_VERBOSE, "target_counter %s used by %s (%d/%d)\n", self->targetname, activator->targetname, (self->max_health-self->count), self->max_health ); + } + + if ( self->count ) + { + if ( self->target2 ) + { + //gi.Printf("target_counter %s firing target2 from %s, entnum %d\n", self->targetname, activator->targetname, activator->s.number ); + G_UseTargets2( self, activator, self->target2 ); + } + return; + } + + G_ActivateBehavior( self,BSET_USE ); + + if ( self->spawnflags & 128 ) + { + self->svFlags |= SVF_INACTIVE; + } + + self->activator = activator; + G_UseTargets( self, activator ); + + if ( self->count == 0 ) + { + if ( self->bounceCount == 0 ) + { + return; + } + self->count = self->max_health; + if ( self->bounceCount > 0 ) + {//-1 means bounce back forever + self->bounceCount--; + } + } +} + +void SP_target_counter (gentity_t *self) +{ + self->wait = -1; + if (!self->count) + { + self->count = 2; + } + //if ( self->bounceCount > 0 )//let's always set this anyway + {//we will reset when we use up our count, remember our initial count + self->max_health = self->count; + } + + self->e_UseFunc = useF_target_counter_use; +} + +/*QUAKED target_random (.5 .5 .5) (-4 -4 -4) (4 4 4) USEONCE +Randomly fires off only one of it's targets each time used + +USEONCE set to never fire again +*/ + +void target_random_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + int t_count = 0, pick; + gentity_t *t = NULL; + + //gi.Printf("target_random %s used by %s (entnum %d)\n", self->targetname, activator->targetname, activator->s.number ); + G_ActivateBehavior(self,BSET_USE); + + if(self->spawnflags & 1) + { + self->e_UseFunc = useF_NULL; + } + + while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL ) + { + if (t != self) + { + t_count++; + } + } + + if(!t_count) + { + return; + } + + if(t_count == 1) + { + G_UseTargets (self, activator); + return; + } + + //FIXME: need a seed + pick = Q_irand(1, t_count); + t_count = 0; + while ( (t = G_Find (t, FOFS(targetname), self->target)) != NULL ) + { + if (t != self) + { + t_count++; + } + else + { + continue; + } + + if (t == self) + { +// gi.Printf ("WARNING: Entity used itself.\n"); + } + else if(t_count == pick) + { + if (t->e_UseFunc != useF_NULL) // check can be omitted + { + GEntity_UseFunc(t, self, activator); + return; + } + } + + if (!self->inuse) + { + gi.Printf("entity was removed while using targets\n"); + return; + } + } +} + +void SP_target_random (gentity_t *self) +{ + self->e_UseFunc = useF_target_random_use; +} + +int numNewICARUSEnts = 0; +void scriptrunner_run (gentity_t *self) +{ + /* + if (self->behaviorSet[BSET_USE]) + { + char newname[MAX_FILENAME_LENGTH]; + + sprintf((char *) &newname, "%s/%s", Q3_SCRIPT_DIR, self->behaviorSet[BSET_USE] ); + + ICARUS_RunScript( self, newname ); + } + */ + + if ( self->count != -1 ) + { + if ( self->count <= 0 ) + { + self->e_UseFunc = useF_NULL; + self->behaviorSet[BSET_USE] = NULL; + return; + } + else + { + --self->count; + } + } + + if (self->behaviorSet[BSET_USE]) + { + if ( self->spawnflags & 1 ) + { + if ( !self->activator ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "target_scriptrunner tried to run on invalid entity!\n"); + return; + } + + if ( self->activator->m_iIcarusID == IIcarusInterface::ICARUS_INVALID ) + {//Need to be initialized through ICARUS + if ( !self->activator->script_targetname || !self->activator->script_targetname[0] ) + { + //We don't have a script_targetname, so create a new one + self->activator->script_targetname = va( "newICARUSEnt%d", numNewICARUSEnts++ ); + } + + if ( Quake3Game()->ValidEntity( self->activator ) ) + { + Quake3Game()->InitEntity( self->activator ); + } + else + { + Quake3Game()->DebugPrint( IGameInterface::WL_ERROR, "target_scriptrunner tried to run on invalid ICARUS activator!\n"); + return; + } + } + + Quake3Game()->DebugPrint( IGameInterface::WL_VERBOSE, "target_scriptrunner running %s on activator %s\n", self->behaviorSet[BSET_USE], self->activator->targetname ); + + Quake3Game()->RunScript( self->activator, self->behaviorSet[BSET_USE] ); + } + else + { + if ( self->activator ) + { + Quake3Game()->DebugPrint( IGameInterface::WL_VERBOSE, "target_scriptrunner %s used by %s\n", self->targetname, self->activator->targetname ); + } + G_ActivateBehavior( self, BSET_USE ); + } + } + + if ( self->wait ) + { + self->nextthink = level.time + self->wait; + } +} + +void target_scriptrunner_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + if ( self->nextthink > level.time ) + { + return; + } + + self->activator = activator; + G_SetEnemy( self, other ); + if ( self->delay ) + {//delay before firing scriptrunner + self->e_ThinkFunc = thinkF_scriptrunner_run; + self->nextthink = level.time + self->delay; + } + else + { + scriptrunner_run (self); + } +} + +/*QUAKED target_scriptrunner (1 0 0) (-4 -4 -4) (4 4 4) runonactivator x x x x x x INACTIVE +--- SPAWNFLAGS --- +runonactivator - Will run the script on the entity that used this or tripped the trigger that used this +INACTIVE - start off + +----- KEYS ------ +Usescript - Script to run when used +count - how many times to run, -1 = infinite. Default is once +wait - can't be used again in this amount of seconds (Default is 1 second if it's multiple-use) +delay - how long to wait after use to run script + +*/ +void SP_target_scriptrunner( gentity_t *self ) +{ + if (!self->behaviorSet[BSET_USE]) + { + gi.Printf(S_COLOR_RED "SP_target_scriptrunner %s has no USESCRIPT\n", self->targetname ); + } + if ( self->spawnflags & 128 ) + { + self->svFlags |= SVF_INACTIVE; + } + + if ( !self->count ) + { + self->count = 1;//default 1 use only + } + /* + else if ( !self->wait ) + { + self->wait = 1;//default wait of 1 sec + } + */ + // FIXME: this is a hack... because delay is read in as an int, so I'm bypassing that because it's too late in the project to change it and I want to be able to set less than a second delays + // no one should be setting a radius on a scriptrunner, if they are this would be bad, take this out for the next project + self->radius = 0.0f; + G_SpawnFloat( "delay", "0", &self->radius ); + self->delay = self->radius * 1000;//sec to ms + self->wait *= 1000;//sec to ms + + G_SetOrigin( self, self->s.origin ); + self->e_UseFunc = useF_target_scriptrunner_use; +} + +void target_gravity_change_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + if ( self->spawnflags & 1 ) + { + gi.cvar_set("g_gravity", va("%f", self->speed)); + } + else if ( activator->client ) + { + int grav = floor(self->speed); + /* + if ( activator->client->ps.gravity != grav ) + { + gi.Printf("%s gravity changed to %d\n", activator->targetname, grav ); + } + */ + activator->client->ps.gravity = grav; + activator->svFlags |= SVF_CUSTOM_GRAVITY; + //FIXME: need a way to set this back to normal? + } +} + +/*QUAKED target_gravity_change (1 0 0) (-4 -4 -4) (4 4 4) GLOBAL + +"gravity" - Normal = 800, Valid range: any + +GLOBAL - Apply to entire world, not just the activator +*/ +void SP_target_gravity_change( gentity_t *self ) +{ + G_SetOrigin( self, self->s.origin ); + G_SpawnFloat( "gravity", "0", &self->speed ); + self->e_UseFunc = useF_target_gravity_change_use; +} + +void target_friction_change_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + if(self->spawnflags & 1) + {//FIXME - make a global? + //gi.Cvar_Set("g_friction", va("%d", self->health)); + } + else if(activator->client) + { + activator->client->ps.friction = self->health; + } +} + +/*QUAKED target_friction_change (1 0 0) (-4 -4 -4) (4 4 4) + +"friction" Normal = 6, Valid range 0 - 10 + +*/ +void SP_target_friction_change( gentity_t *self ) +{ + G_SetOrigin( self, self->s.origin ); + self->e_UseFunc = useF_target_friction_change_use; +} + +void set_mission_stats_cvars( void ) +{ + char text[1024]={0}; + + //we'll assume that the activator is the player + gclient_t* const client = &level.clients[0]; + + if (!client) + { + return; + } + cg_entities[0].gent->client->sess.missionStats.enemiesKilled; + + gi.cvar_set("ui_stats_enemieskilled", va("%d",client->sess.missionStats.enemiesKilled)); //pass this on to the menu + + if (cg_entities[0].gent->client->sess.missionStats.totalSecrets) + { + cgi_SP_GetStringTextString( "SP_INGAME_SECRETAREAS_OF", text, sizeof(text) ); + gi.cvar_set("ui_stats_secretsfound", va("%d %s %d", + cg_entities[0].gent->client->sess.missionStats.secretsFound, + text, + cg_entities[0].gent->client->sess.missionStats.totalSecrets)); + } + else // Setting ui_stats_secretsfound to 0 will hide the text on screen + { + gi.cvar_set("ui_stats_secretsfound", "0"); + } + + // Find the favorite weapon + int wpn=0,i; + int max_wpn = cg_entities[0].gent->client->sess.missionStats.weaponUsed[0]; + for (i = 1; iclient->sess.missionStats.weaponUsed[i] > max_wpn) + { + max_wpn = cg_entities[0].gent->client->sess.missionStats.weaponUsed[i]; + wpn = i; + } + } + + if ( wpn ) + { + gitem_t *wItem= FindItemForWeapon( (weapon_t)wpn); + cgi_SP_GetStringTextString( va("SP_INGAME_%s",wItem->classname ), text, sizeof( text )); + gi.cvar_set("ui_stats_fave", va("%s",text)); //pass this on to the menu + } + + gi.cvar_set("ui_stats_shots", va("%d",client->sess.missionStats.shotsFired)); //pass this on to the menu + + gi.cvar_set("ui_stats_hits", va("%d",client->sess.missionStats.hits)); //pass this on to the menu + + const float percent = cg_entities[0].gent->client->sess.missionStats.shotsFired? 100.0f * (float)cg_entities[0].gent->client->sess.missionStats.hits / cg_entities[0].gent->client->sess.missionStats.shotsFired : 0; + gi.cvar_set("ui_stats_accuracy", va("%.2f%%",percent)); //pass this on to the menu + + gi.cvar_set("ui_stats_thrown", va("%d",client->sess.missionStats.saberThrownCnt)); //pass this on to the menu + + gi.cvar_set("ui_stats_blocks", va("%d",client->sess.missionStats.saberBlocksCnt)); + gi.cvar_set("ui_stats_legattacks", va("%d",client->sess.missionStats.legAttacksCnt)); + gi.cvar_set("ui_stats_armattacks", va("%d",client->sess.missionStats.armAttacksCnt)); + gi.cvar_set("ui_stats_bodyattacks", va("%d",client->sess.missionStats.torsoAttacksCnt)); + + gi.cvar_set("ui_stats_absorb", va("%d",client->sess.missionStats.forceUsed[FP_ABSORB])); + gi.cvar_set("ui_stats_heal", va("%d",client->sess.missionStats.forceUsed[FP_HEAL])); + gi.cvar_set("ui_stats_mindtrick", va("%d",client->sess.missionStats.forceUsed[FP_TELEPATHY])); + gi.cvar_set("ui_stats_protect", va("%d",client->sess.missionStats.forceUsed[FP_PROTECT])); + + gi.cvar_set("ui_stats_jump", va("%d",client->sess.missionStats.forceUsed[FP_LEVITATION])); + gi.cvar_set("ui_stats_pull", va("%d",client->sess.missionStats.forceUsed[FP_PULL])); + gi.cvar_set("ui_stats_push", va("%d",client->sess.missionStats.forceUsed[FP_PUSH])); + gi.cvar_set("ui_stats_sense", va("%d",client->sess.missionStats.forceUsed[FP_SEE])); + gi.cvar_set("ui_stats_speed", va("%d",client->sess.missionStats.forceUsed[FP_SPEED])); + gi.cvar_set("ui_stats_defense", va("%d",client->sess.missionStats.forceUsed[FP_SABER_DEFENSE])); + gi.cvar_set("ui_stats_offense", va("%d",client->sess.missionStats.forceUsed[FP_SABER_OFFENSE])); + gi.cvar_set("ui_stats_throw", va("%d",client->sess.missionStats.forceUsed[FP_SABERTHROW])); + + gi.cvar_set("ui_stats_drain", va("%d",client->sess.missionStats.forceUsed[FP_DRAIN])); + gi.cvar_set("ui_stats_grip", va("%d",client->sess.missionStats.forceUsed[FP_GRIP])); + gi.cvar_set("ui_stats_lightning", va("%d",client->sess.missionStats.forceUsed[FP_LIGHTNING])); + gi.cvar_set("ui_stats_rage", va("%d",client->sess.missionStats.forceUsed[FP_RAGE])); + +} + +#include "..\cgame\cg_media.h" //access to cgs +extern void G_ChangeMap (const char *mapname, const char *spawntarget, qboolean hub); //g_utils +void target_level_change_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + if( self->message && !Q_stricmp( "disconnect", self->message ) ) + { + gi.SendConsoleCommand( "disconnect\n"); + } + else + { + G_ChangeMap( self->message, self->target, (self->spawnflags&1) ); + } + if (self->count>=0) + { + gi.cvar_set("tier_storyinfo", va("%i",self->count)); + if (level.mapname[0] == 't' && level.mapname[2] == '_' + && ( level.mapname[1] == '1' || level.mapname[1] == '2' || level.mapname[1] == '3' ) + ) + { + char s[2048]; + gi.Cvar_VariableStringBuffer("tiers_complete", s, sizeof(s)); //get the current list + if (*s) + { + gi.cvar_set("tiers_complete", va("%s %s", s, level.mapname)); //strcat this level into the existing list + } + else + { + gi.cvar_set("tiers_complete", level.mapname); //set this level into the list + } + } + if (self->noise_index) + { + cgi_S_StopSounds(); + cgi_S_StartSound( NULL, 0, CHAN_VOICE, cgs.sound_precache[ self->noise_index ] ); + } + } + + set_mission_stats_cvars(); + +} + +/*QUAKED target_level_change (1 0 0) (-4 -4 -4) (4 4 4) HUB NO_STORYSOUND +HUB - Will save the current map's status and load the next map with any saved status it may have +NO_STORYSOUND - will not play storyinfo wav file, even if you '++' or set tier_storyinfo + +"mapname" - Name of map to change to or "+menuname" to launch a menu instead +"target" - Name of spawnpoint to start at in the new map +"tier_storyinfo" - integer to set cvar or "++" to just increment +"storyhead" - which head to show on menu [luke, kyle, or prot] +"saber_menu" - integer to set cvar for menu +"weapon_menu" - integer to set cvar for ingame weapon menu +*/ +void SP_target_level_change( gentity_t *self ) +{ + if ( !self->message ) + { + G_Error( "target_level_change with no mapname!\n"); + return; + } + + char *s; + if (G_SpawnString( "tier_storyinfo", "", &s )) + { + if (*s == '+') + { + self->noise_index = G_SoundIndex(va("sound/chars/tiervictory/%s.mp3",level.mapname) ); + self->count = gi.Cvar_VariableIntegerValue("tier_storyinfo")+1; + G_SoundIndex(va("sound/chars/storyinfo/%d.mp3",self->count)); //cache for menu + } + else + { + self->count = atoi(s); + if( !(self->spawnflags & 2) ) + { + self->noise_index = G_SoundIndex(va("sound/chars/storyinfo/%d.mp3",self->count) ); + } + } + + if (G_SpawnString( "storyhead", "", &s )) + { //[luke, kyle, or prot] + gi.cvar_set("storyhead", s); //pass this on to the menu + } + else + { //show head based on mapname + gi.cvar_set("storyhead", level.mapname); //pass this on to the menu + } + } + if (G_SpawnString( "saber_menu", "", &s )) + { + gi.cvar_set("saber_menu", s); //pass this on to the menu + } + + if (G_SpawnString( "weapon_menu", "1", &s )) + { + gi.cvar_set("weapon_menu", s); //pass this on to the menu + } + else + { + gi.cvar_set("weapon_menu", "0"); //pass this on to the menu + } + + G_SetOrigin( self, self->s.origin ); + self->e_UseFunc = useF_target_level_change_use; +} + +/*QUAKED target_change_parm (1 0 0) (-4 -4 -4) (4 4 4) +Copies any parms set on this ent to the entity that fired the trigger/button/whatever that uses this +parm1 +parm2 +parm3 +parm4 +parm5 +parm6 +parm7 +parm8 +parm9 +parm10 +parm11 +parm12 +parm13 +parm14 +parm15 +parm16 +*/ +void Q3_SetParm (int entID, int parmNum, const char *parmValue); +void target_change_parm_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + if ( !activator || !self ) + { + return; + } + + //FIXME: call capyparms + if ( self->parms ) + { + for ( int parmNum = 0; parmNum < MAX_PARMS; parmNum++ ) + { + if ( self->parms->parm[parmNum] && self->parms->parm[parmNum][0] ) + { + Q3_SetParm( activator->s.number, parmNum, self->parms->parm[parmNum] ); + } + } + } +} + +void SP_target_change_parm( gentity_t *self ) +{ + if ( !self->parms ) + {//ERROR! + return; + } + G_SetOrigin( self, self->s.origin ); + self->e_UseFunc = useF_target_change_parm_use; +} + +void target_play_music_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + gi.SetConfigstring( CS_MUSIC, self->message ); +} + +/*QUAKED target_play_music (1 0 0) (-4 -4 -4) (4 4 4) +target_play_music +Plays the requested music files when this target is used. + +"targetname" +"music" music WAV or MP3 file ( music/introfile.mp3 [optional] music/loopfile.mp3 ) + +If an intro file and loop file are specified, the intro plays first, then the looping +portion will start and loop indefinetly. If no introfile is entered, only the loopfile +will play. +*/ +void SP_target_play_music( gentity_t *self ) +{ + char *s; + G_SetOrigin( self, self->s.origin ); + if (!G_SpawnString( "music", "", &s )) { + G_Error( "target_play_music without a music key at %s", vtos( self->s.origin ) ); + } + self->message = G_NewString (s); + self->e_UseFunc = useF_target_play_music_use; +extern cvar_t *com_buildScript; + //Precache! + if (com_buildScript->integer) {//copy this puppy over + char buffer[MAX_QPATH]; + fileHandle_t hFile; + + Q_strncpyz( buffer, s, sizeof(buffer) ); + COM_DefaultExtension( buffer, sizeof(buffer), ".mp3"); + + gi.FS_FOpenFile(buffer, &hFile, FS_READ); + if (hFile) { + gi.FS_FCloseFile( hFile ); + } + } +} + + +extern bool allowNormalAutosave; +void target_autosave_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + if(!allowNormalAutosave) + return; + + G_ActivateBehavior(self,BSET_USE); + //gi.SendServerCommand( NULL, "cp @SP_INGAME_CHECKPOINT" ); + CG_CenterPrint( "@SP_INGAME_CHECKPOINT", SCREEN_HEIGHT * 0.25 ); //jump the network + + gi.SendConsoleCommand( "wait 2;save auto\n" ); +} + +/*QUAKED target_autosave (1 0 0) (-4 -4 -4) (4 4 4) +Auto save the game in two frames. +Make sure it won't trigger during dialogue or cinematic or it will stutter! +*/ +void SP_target_autosave( gentity_t *self ) +{ + G_SetOrigin( self, self->s.origin ); + self->e_UseFunc = useF_target_autosave_use; +} + +void target_secret_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + //we'll assume that the activator is the player + gclient_t* const client = &level.clients[0]; + client->sess.missionStats.secretsFound++; + if ( activator ) + { + G_Sound( activator, self->noise_index ); + } + else + { + G_Sound( self, self->noise_index ); + } + gi.SendServerCommand( NULL, "cp @SP_INGAME_SECRET_AREA" ); + assert(client->sess.missionStats.totalSecrets); +} + +/*QUAKED target_secret (1 0 1) (-4 -4 -4) (4 4 4) +You found a Secret! +"count" - how many secrets on this level, + if more than one on a level, be sure they all have the same count! +*/ +void SP_target_secret( gentity_t *self ) +{ + G_SetOrigin( self, self->s.origin ); + self->e_UseFunc = useF_target_secret_use; + self->noise_index = G_SoundIndex("sound/interface/secret_area"); + if (self->count) + { + gi.cvar_set("newTotalSecrets", va("%i",self->count)); + } +} diff --git a/code/game/g_trigger.cpp b/code/game/g_trigger.cpp new file mode 100644 index 0000000..1ed133d --- /dev/null +++ b/code/game/g_trigger.cpp @@ -0,0 +1,1685 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "g_functions.h" +#include "b_local.h" +#include "anims.h" + +#define ENTDIST_PLAYER 1 +#define ENTDIST_NPC 2 + +extern qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs ); +extern qboolean G_ClearTrace( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int ignore, int clipmask ); +extern qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ); +extern qboolean PM_CrouchAnim( int anim ); +extern void Boba_FlyStart( gentity_t *self ); +extern qboolean Boba_Flying( gentity_t *self ); + +void InitTrigger( gentity_t *self ) { + if (!VectorCompare (self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + gi.SetBrushModel( self, self->model ); + self->contents = CONTENTS_TRIGGER; // replaces the -1 from gi.SetBrushModel + self->svFlags = SVF_NOCLIENT; + + if(self->spawnflags & 128) + { + self->svFlags |= SVF_INACTIVE; + } +} + + +// the wait time has passed, so set back up for another activation +void multi_wait( gentity_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_run( gentity_t *ent ) +{ + ent->e_ThinkFunc = thinkF_NULL; + + G_ActivateBehavior( ent, BSET_USE ); + + if ( ent->soundSet && ent->soundSet[0] ) + { + gi.SetConfigstring( CS_AMBIENT_SET, ent->soundSet ); + } + + G_UseTargets (ent, ent->activator); + if ( ent->noise_index ) + { + G_Sound( ent->activator, ent->noise_index ); + } + + if ( ent->target2 && ent->target2[0] && ent->wait >= 0 ) + { + ent->e_ThinkFunc = thinkF_trigger_cleared_fire; + ent->nextthink = level.time + ent->speed; + } + else if ( ent->wait > 0 ) + { + if ( ent->painDebounceTime != level.time ) + {//first ent to touch it this frame + //ent->e_ThinkFunc = thinkF_multi_wait; + ent->nextthink = level.time + ( ent->wait + ent->random * crandom() ) * 1000; + ent->painDebounceTime = level.time; + } + } + else if ( ent->wait < 0 ) + { + // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->contents &= ~CONTENTS_TRIGGER;//so the EntityContact trace doesn't have to be done against me + ent->e_TouchFunc = touchF_NULL; + ent->e_UseFunc = useF_NULL; + //Don't remove, Icarus may barf? + //ent->nextthink = level.time + FRAMETIME; + //ent->think = G_FreeEntity; + } + if( ent->activator && ent->activator->s.number == 0 ) + { // mark the trigger as being touched by the player + ent->aimDebounceTime = level.time; + } +} + + +void multi_trigger( gentity_t *ent, gentity_t *activator ) +{ + if ( ent->e_ThinkFunc == thinkF_multi_trigger_run ) + {//already triggered, just waiting to run + return; + } + + if ( ent->nextthink > level.time ) + { + if( ent->spawnflags & 2048 ) // MULTIPLE - allow multiple entities to touch this trigger in a single frame + { + if ( ent->painDebounceTime && ent->painDebounceTime != level.time ) + {//this should still allow subsequent ents to fire this trigger in the current frame + return; // can't retrigger until the wait is over + } + } + else + { + return; + } + + } + if ( ent->spawnflags & 32) + { + ent->nextthink = level.time + ent->delay; + + // trace_t viewTrace; + // gi.trace(&viewTrace, ent->currentOrigin, 0, 0, activator->currentOrigin, ent->s.number, MASK_SHOT); + // if ((viewTrace.allsolid) || (viewTrace.startsolid) || (viewTrace.entityNum!=activator->s.number)) + // { + // return; + // } + } + + + // if the player has already activated this trigger this frame + if( activator && !activator->s.number && ent->aimDebounceTime == level.time ) + { + return; + } + + if ( ent->svFlags & SVF_INACTIVE ) + {//Not active at this time + return; + } + + ent->activator = activator; + + if(ent->delay && ent->painDebounceTime < (level.time + ent->delay) ) + {//delay before firing trigger + ent->e_ThinkFunc = thinkF_multi_trigger_run; + ent->nextthink = level.time + ent->delay; + ent->painDebounceTime = level.time; + + } + else + { + multi_trigger_run (ent); + } +} + +void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + multi_trigger( ent, activator ); +} + +extern int Pilot_ActivePilotCount(void); + +void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + if( !other->client ) + { + return; + } + + if ( self->svFlags & SVF_INACTIVE ) + {//set by target_deactivate + return; + } + + if( self->noDamageTeam ) + { + if ( other->client->playerTeam != self->noDamageTeam ) + { + return; + } + } + +// moved to just above multi_trigger because up here it just checks if the trigger is not being touched +// we want it to check any conditions set on the trigger, if one of those isn't met, the trigger is considered to be "cleared" +// if ( self->e_ThinkFunc == thinkF_trigger_cleared_fire ) +// {//We're waiting to fire our target2 first +// self->nextthink = level.time + self->speed; +// return; +// } + + if ( self->spawnflags & 1 ) + { + if ( other->s.number != 0 ) + { + return; + } + } + else + { + if ( self->spawnflags & 16 ) + {//NPCONLY + if ( other->NPC == NULL ) + { + return; + } + } + + if ( self->NPC_targetname && self->NPC_targetname[0] ) + { + if ( other->script_targetname && other->script_targetname[0] ) + { + if ( Q_stricmp( self->NPC_targetname, other->script_targetname ) != 0 ) + {//not the right guy to fire me off + return; + } + } + else + { + return; + } + } + } + + if ( self->spawnflags & 4 ) + {//USE_BUTTON + if ( !other->client ) + { + return; + } + + if( !( other->client->usercmd.buttons & BUTTON_USE ) ) + {//not pressing use button + return; + } + } + + if ( self->spawnflags & 2 ) + {//FACING + vec3_t forward; + + if ( other->client ) + { + AngleVectors( other->client->ps.viewangles, forward, NULL, NULL ); + } + else + { + AngleVectors( other->currentAngles, forward, NULL, NULL ); + } + + if ( DotProduct( self->movedir, forward ) < 0.5 ) + {//Not Within 45 degrees + return; + } + } + + if ( self->spawnflags & 8 ) + {//FIRE_BUTTON + if ( !other->client ) + { + return; + } + + if( !( other->client->ps.eFlags & EF_FIRING /*usercmd.buttons & BUTTON_ATTACK*/ ) && + !( other->client->ps.eFlags & EF_ALT_FIRING/*usercmd.buttons & BUTTON_ALT_ATTACK*/ ) ) + {//not pressing fire button or altfire button + return; + } + + //FIXME: do we care about the sniper rifle or not? + + if( other->s.number == 0 && ( other->client->ps.weapon > MAX_PLAYER_WEAPONS || other->client->ps.weapon <= WP_NONE ) ) + {//don't care about non-player weapons if this is the player + return; + } + } + + if ( other->client && self->radius ) + { + vec3_t eyeSpot; + + //Only works if your head is in it, but we allow leaning out + //NOTE: We don't use CalcEntitySpot SPOT_HEAD because we don't want this + //to be reliant on the physical model the player uses. + VectorCopy(other->currentOrigin, eyeSpot); + eyeSpot[2] += other->client->ps.viewheight; + + if ( G_PointInBounds( eyeSpot, self->absmin, self->absmax ) ) + { + if( !( other->client->ps.eFlags & EF_FIRING ) && + !( other->client->ps.eFlags & EF_ALT_FIRING ) ) + {//not attacking, so hiding bonus + //FIXME: should really have sound events clear the hiddenDist + other->client->hiddenDist = self->radius; + //NOTE: movedir HAS to be normalized! + if ( VectorLength( self->movedir ) ) + {//They can only be hidden from enemies looking in this direction + VectorCopy( self->movedir, other->client->hiddenDir ); + } + else + { + VectorClear( other->client->hiddenDir ); + } + } + } + } + + if ( self->spawnflags & 4 ) + {//USE_BUTTON + NPC_SetAnim( other, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + /* + if ( !VectorLengthSquared( other->client->ps.velocity ) && !PM_CrouchAnim( other->client->ps.legsAnim ) ) + { + NPC_SetAnim( other, SETANIM_LEGS, BOTH_BUTTON_HOLD, SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD ); + } + */ + //other->client->ps.weaponTime = other->client->ps.torsoAnimTimer; + } + + if ( self->e_ThinkFunc == thinkF_trigger_cleared_fire ) + {//We're waiting to fire our target2 first + self->nextthink = level.time + self->speed; + return; + } + + if ( self->spawnflags & 32) + { + if (Pilot_ActivePilotCount()>=self->lastInAirTime) + { + return; + } + } + + multi_trigger( self, other ); +} + +void trigger_cleared_fire (gentity_t *self) +{ + G_UseTargets2( self, self->activator, self->target2 ); + self->e_ThinkFunc = thinkF_NULL; + // should start the wait timer now, because the trigger's just been cleared, so we must "wait" from this point + if ( self->wait > 0 ) + { + self->nextthink = level.time + ( self->wait + self->random * crandom() ) * 1000; + } +} + +qboolean G_TriggerActive( gentity_t *self ) +{ + if ( self->svFlags & SVF_INACTIVE ) + {//set by target_deactivate + return qfalse; + } + + if ( self->spawnflags & 1 ) + {//player only + return qfalse; + } + + /* + ??? + if ( self->spawnflags & 4 ) + {//USE_BUTTON + return qfalse; + } + */ + + /* + ??? + if ( self->spawnflags & 8 ) + {//FIRE_BUTTON + return qfalse; + } + */ + + /* + if ( self->radius ) + {//Only works if your head is in it, but we allow leaning out + //NOTE: We don't use CalcEntitySpot SPOT_HEAD because we don't want this + //to be reliant on the physical model the player uses. + return qfalse; + } + */ + return qtrue; +} + +/*QUAKED trigger_multiple (.1 .5 .1) ? PLAYERONLY FACING USE_BUTTON FIRE_BUTTON NPCONLY LIMITED_PILOT x INACTIVE MULTIPLE +PLAYERONLY - only a player can trigger this by touch +FACING - Won't fire unless triggering ent's view angles are within 45 degrees of trigger's angles (in addition to any other conditions) +USE_BUTTON - Won't fire unless player is in it and pressing use button (in addition to any other conditions) +FIRE_BUTTON - Won't fire unless player/NPC is in it and pressing fire button (in addition to any other conditions) +NPCONLY - only non-player NPCs can trigger this by touch +LIMITED_PILOT - only spawn if there are open pilot slots +INACTIVE - Start off, has to be activated to be touchable/usable +MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0 + +"wait" Seconds between triggerings, 0 default, number < 0 means one time only. +"random" wait variance, default is 0 +"delay" how many seconds to wait to fire targets after tripped +"hiderange" As long as NPC's head is in this trigger, NPCs out of this hiderange cannot see him. If you set an angle on the trigger, they're only hidden from enemies looking in that direction. the player's crouch viewheight is 36, his standing viewheight is 54. So a trigger thast should hide you when crouched but not standing should be 48 tall. +"target2" The trigger will fire this only when the trigger has been activated and subsequently 'cleared'( once any of the conditions on the trigger have not been satisfied). This will not fire the "target" more than once until the "target2" is fired (trigger field is 'cleared') +"speed" How many seconds to wait to fire the target2, default is 1 +"noise" Sound to play when the trigger fires (plays at activator's origin) +"max_pilots" Number of pilots this spawner will allow + +Variable sized repeatable trigger. Must be targeted at one or more entities. +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +"NPC_targetname" - If set, only an NPC with a matching NPC_targetname will trip this trigger +"team" - If set, only this team can trip this trigger + player + enemy + neutral + +"soundSet" Ambient sound set to play when this trigger is activated +*/ +void SP_trigger_multiple( gentity_t *ent ) +{ + char buffer[MAX_QPATH]; + char *s; + if ( G_SpawnString( "noise", "*NOSOUND*", &s ) ) + { + Q_strncpyz( buffer, s, sizeof(buffer) ); + COM_DefaultExtension( buffer, sizeof(buffer), ".wav"); + ent->noise_index = G_SoundIndex(buffer); + } + + G_SpawnFloat( "wait", "0", &ent->wait );//was 0.5 ... but that means wait can never be zero... we should probably put it back to 0.5, though... + G_SpawnFloat( "random", "0", &ent->random ); + G_SpawnInt( "max_pilots", "2", &ent->lastInAirTime ); + + + if ( (ent->wait > 0) && (ent->random >= ent->wait) ) { + ent->random = ent->wait - FRAMETIME; + gi.Printf(S_COLOR_YELLOW"trigger_multiple has random >= wait\n"); + } + + ent->delay *= 1000;//1 = 1 msec, 1000 = 1 sec + if ( !ent->speed && ent->target2 && ent->target2[0] ) + { + ent->speed = 1000; + } + else + { + ent->speed *= 1000; + } + + ent->e_TouchFunc = touchF_Touch_Multi; + ent->e_UseFunc = useF_Use_Multi; + + if ( ent->team && ent->team[0] ) + { + ent->noDamageTeam = (team_t)GetIDForString( TeamTable, ent->team ); + ent->team = NULL; + } + + InitTrigger( ent ); + gi.linkentity (ent); +} + +/*QUAKED trigger_once (.5 1 .5) ? PLAYERONLY FACING USE_BUTTON FIRE_BUTTON NPCONLY x x INACTIVE MULTIPLE +PLAYERONLY - only a player can trigger this by touch +FACING - Won't fire unless triggering ent's view angles are within 45 degrees of trigger's angles (in addition to any other conditions) +USE_BUTTON - Won't fire unless player is in it and pressing use button (in addition to any other conditions) +FIRE_BUTTON - Won't fire unless player/NPC is in it and pressing fire button (in addition to any other conditions) +NPCONLY - only non-player NPCs can trigger this by touch +INACTIVE - Start off, has to be activated to be touchable/usable +MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0 + +"random" wait variance, default is 0 +"delay" how many seconds to wait to fire targets after tripped +Variable sized repeatable trigger. Must be targeted at one or more entities. +so, the basic time between firing is a random time between +(wait - random) and (wait + random) +"noise" Sound to play when the trigger fires (plays at activator's origin) + +"NPC_targetname" - If set, only an NPC with a matching NPC_targetname will trip this trigger +"team" - If set, only this team can trip this trigger + player + enemy + neutral + +"soundSet" Ambient sound set to play when this trigger is activated +*/ +void SP_trigger_once( gentity_t *ent ) +{ + char buffer[MAX_QPATH]; + char *s; + if ( G_SpawnString( "noise", "*NOSOUND*", &s ) ) + { + Q_strncpyz( buffer, s, sizeof(buffer) ); + COM_DefaultExtension( buffer, sizeof(buffer), ".wav"); + ent->noise_index = G_SoundIndex(buffer); + } + + ent->wait = -1; + + ent->e_TouchFunc = touchF_Touch_Multi; + ent->e_UseFunc = useF_Use_Multi; + + if ( ent->team && ent->team[0] ) + { + ent->noDamageTeam = (team_t)GetIDForString( TeamTable, ent->team ); + ent->team = NULL; + } + + ent->delay *= 1000;//1 = 1 msec, 1000 = 1 sec + + InitTrigger( ent ); + gi.linkentity (ent); +} + + +/*QUAKED trigger_bidirectional (.1 .5 .1) ? PLAYER_ONLY x x x x x x INACTIVE +NOT IMPLEMENTED +INACTIVE - Start off, has to be activated to be touchable/usable + +set "angle" for forward direction +Fires "target" when someone moves through it in direction of angle +Fires "backwardstarget" when someone moves through it in the opposite direction of angle + +"NPC_targetname" - If set, only an NPC with a matching NPC_targetname will trip this trigger + +"wait" - how long to wait between triggerings + + TODO: + count +*/ +void SP_trigger_bidirectional( gentity_t *ent ) +{ + G_FreeEntity(ent); + //FIXME: Implement +/* if(!ent->wait) + { + ent->wait = -1; + } + + ent->touch = Touch_Multi; + ent->use = Use_Multi; + + InitTrigger( ent ); + gi.linkentity (ent); +*/ +} + +/*QUAKED trigger_location (.1 .5 .1) ? +When an ent is asked for it's location, it will return this ent's "message" field if it is in it. + "message" - location name + + NOTE: always rectangular +*/ +char *G_GetLocationForEnt( gentity_t *ent ) +{ + vec3_t mins, maxs; + gentity_t *found = NULL; + + VectorAdd( ent->currentOrigin, ent->mins, mins ); + VectorAdd( ent->currentOrigin, ent->maxs, maxs ); + + while( (found = G_Find(found, FOFS(classname), "trigger_location")) != NULL ) + { + if ( gi.EntityContact( mins, maxs, found ) ) + { + return found->message; + } + } + + return NULL; +} + +void SP_trigger_location( gentity_t *ent ) +{ + if ( !ent->message || !ent->message[0] ) + { + gi.Printf("WARNING: trigger_location with no message!\n"); + G_FreeEntity(ent); + return; + } + + gi.SetBrushModel( ent, ent->model ); + ent->contents = 0; + ent->svFlags = SVF_NOCLIENT; + + gi.linkentity (ent); +} +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +void trigger_always_think( gentity_t *ent ) { + G_UseTargets(ent, ent); + G_FreeEntity( ent ); +} + +/*QUAKED trigger_always (.1 .5 .1) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always (gentity_t *ent) { + // we must have some delay to make sure our use targets are present + ent->nextthink = level.time + 300; + ent->e_ThinkFunc = thinkF_trigger_always_think; +} + + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ +#define PUSH_CONVEYOR 32 +void trigger_push_touch (gentity_t *self, gentity_t *other, trace_t *trace ) { + if ( self->svFlags & SVF_INACTIVE ) + {//set by target_deactivate + return; + } + + if( level.time < self->painDebounceTime + self->wait ) // normal 'wait' check + { + if( self->spawnflags & 2048 ) // MULTIPLE - allow multiple entities to touch this trigger in one frame + { + if ( self->painDebounceTime && level.time > self->painDebounceTime ) // if we haven't reached the next frame continue to let ents touch the trigger + { + return; + } + } + else // only allowing one ent per frame to touch trigger + { + return; + } + } + + // if the player has already activated this trigger this frame + if( other && !other->s.number && self->aimDebounceTime == level.time ) + { + return; + } + + + if( self->spawnflags & PUSH_CONVEYOR ) + { // only push player if he's on the ground + if( other->s.groundEntityNum == ENTITYNUM_NONE ) + { + return; + } + } + + if ( self->spawnflags & 1 ) + {//PLAYERONLY + if ( other->s.number != 0 ) + { + return; + } + } + else + { + if ( self->spawnflags & 8 ) + {//NPCONLY + if ( other->NPC == NULL ) + { + return; + } + } + } + + if ( !other->client ) { + if ( other->s.pos.trType != TR_STATIONARY && other->s.pos.trType != TR_LINEAR_STOP && other->s.pos.trType != TR_NONLINEAR_STOP && VectorLengthSquared( other->s.pos.trDelta ) ) + {//already moving + VectorCopy( other->currentOrigin, other->s.pos.trBase ); + VectorCopy( self->s.origin2, other->s.pos.trDelta ); + other->s.pos.trTime = level.time; + } + return; + } + + if ( other->client->ps.pm_type != PM_NORMAL ) { + return; + } + + if ( (self->spawnflags&16) ) + {//relative, dir to it * speed + vec3_t dir; + VectorSubtract( self->s.origin2, other->currentOrigin, dir ); + if ( self->speed ) + { + VectorNormalize( dir ); + VectorScale( dir, self->speed, dir ); + } + VectorCopy( dir, other->client->ps.velocity ); + } + else if ( (self->spawnflags&4) ) + {//linear dir * speed + VectorScale( self->s.origin2, self->speed, other->client->ps.velocity ); + } + else + { + VectorCopy( self->s.origin2, other->client->ps.velocity ); + } + //so we don't take damage unless we land lower than we start here... + other->client->ps.forceJumpZStart = 0; + other->client->ps.pm_flags |= PMF_TRIGGER_PUSHED;//pushed by a trigger + other->client->ps.jumpZStart = other->client->ps.origin[2]; + + if ( self->wait == -1 ) + { + self->e_TouchFunc = touchF_NULL; + } + else if ( self->wait > 0 ) + { + self->painDebounceTime = level.time; + + } + if( other && !other->s.number ) + { // mark that the player has activated this trigger this frame + self->aimDebounceTime =level.time; + } +} + +#define PUSH_CONSTANT 2 + +/* +================= +AimAtTarget + +Calculate origin2 so the target apogee will be hit +================= +*/ +void AimAtTarget( gentity_t *self ) +{ + gentity_t *ent; + vec3_t origin; + float height, gravity, time, forward; + float dist; + + VectorAdd( self->absmin, self->absmax, origin ); + VectorScale ( origin, 0.5, origin ); + + ent = G_PickTarget( self->target ); + if ( !ent ) + { + G_FreeEntity( self ); + return; + } + + if ( self->classname && !Q_stricmp( "trigger_push", self->classname ) ) + { + if ( (self->spawnflags&2) ) + {//check once a second to see if we should activate or deactivate ourselves + self->e_ThinkFunc = thinkF_trigger_push_checkclear; + self->nextthink = level.time + FRAMETIME; + } + + if ( (self->spawnflags&16) ) + {//relative, not an arc or linear + VectorCopy( ent->currentOrigin, self->s.origin2 ); + return; + } + else if ( (self->spawnflags&4) ) + {//linear, not an arc + VectorSubtract( ent->currentOrigin, origin, self->s.origin2 ); + VectorNormalize( self->s.origin2 ); + return; + } + } + + if ( self->classname && !Q_stricmp( "target_push", self->classname ) ) + { + if( self->spawnflags & PUSH_CONSTANT ) + { + VectorSubtract ( ent->s.origin, self->s.origin, self->s.origin2 ); + VectorNormalize( self->s.origin2); + VectorScale (self->s.origin2, self->speed, self->s.origin2); + return; + } + } + height = ent->s.origin[2] - origin[2]; + if ( height < 0 ) + {//sqrt of negative is bad! + height = 0; + } + gravity = g_gravity->value; + if ( gravity < 0 ) + { + gravity = 0; + } + time = sqrt( height / ( .5 * gravity ) ); + if ( !time ) { + G_FreeEntity( self ); + return; + } + + // set s.origin2 to the push velocity + VectorSubtract ( ent->s.origin, origin, self->s.origin2 ); + self->s.origin2[2] = 0; + dist = VectorNormalize( self->s.origin2); + + forward = dist / time; + VectorScale( self->s.origin2, forward, self->s.origin2 ); + + self->s.origin2[2] = time * gravity; +} + +void trigger_push_checkclear( gentity_t *self ) +{ + trace_t trace; + vec3_t center; + + self->nextthink = level.time + 500; + + VectorAdd( self->absmin, self->absmax, center ); + VectorScale( center, 0.5, center ); + + gentity_t *target = G_Find( NULL, FOFS(targetname), self->target ); + gi.trace( &trace, center, vec3_origin, vec3_origin, target->currentOrigin, ENTITYNUM_NONE, CONTENTS_SOLID ); + + if ( trace.fraction >= 1.0f ) + {//can trace, turn on + self->contents |= CONTENTS_TRIGGER;//so the EntityContact trace doesn't have to be done against me + self->e_TouchFunc = touchF_trigger_push_touch; + gi.linkentity( self ); + } + else + {//no trace, turn off + self->contents &= ~CONTENTS_TRIGGER;//so the EntityContact trace doesn't have to be done against me + self->e_TouchFunc = touchF_NULL; + gi.unlinkentity( self ); + } +} +/*QUAKED trigger_push (.1 .5 .1) ? PLAYERONLY CHECKCLEAR LINEAR NPCONLY RELATIVE CONVEYOR x INACTIVE MULTIPLE +Must point at a target_position, which will be the apex of the leap. +This will be client side predicted, unlike target_push +PLAYERONLY - only the player is affected +LINEAR - Instead of tossing the client at the target_position, it will push them towards it. Must set a "speed" (see below) +CHECKCLEAR - Every 1 second, it will check to see if it can trace to the target_position, if it can, the trigger is touchable, if it can't, the trigger is not touchable +NPCONLY - only NPCs are affected +RELATIVE - instead of pushing you in a direction that is always from the center of the trigger to the target_position, it pushes *you* toward the target position, relative to your current location (can use with "speed"... if don't set a speed, it will use the distance from you to the target_position) +CONVEYOR - acts like a conveyor belt, will only push if player is on the ground ( should probably use RELATIVE also, if you want a true conveyor belt ) +INACTIVE - not active until targeted by a target_activate +MULTIPLE - multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0 + +wait - how long to wait between pushes: -1 = push only once +speed - when used with the LINEAR spawnflag, pushes the client toward the position at a constant speed (default is 1000) +*/ +void SP_trigger_push( gentity_t *self ) { + InitTrigger (self); + + if ( self->wait > 0 ) + { + self->wait *= 1000; + } + + // unlike other triggers, we need to send this one to the client + self->svFlags &= ~SVF_NOCLIENT; + + self->s.eType = ET_PUSH_TRIGGER; + if ( !(self->spawnflags&2) ) + {//start on + self->e_TouchFunc = touchF_trigger_push_touch; + } + if ( self->spawnflags & 4 ) + {//linear + self->speed = 1000; + } + self->e_ThinkFunc = thinkF_AimAtTarget; + self->nextthink = level.time + START_TIME_LINK_ENTS; + gi.linkentity (self); +} + +void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator ) { + if ( !activator->client ) { + return; + } + + if ( activator->client->ps.pm_type != PM_NORMAL ) { + return; + } + + G_ActivateBehavior(self,BSET_USE); + + VectorCopy( self->s.origin2, activator->client->ps.velocity ); + + if( self->spawnflags & 4 ) // lower + { + // reset this so I don't take falling damage when I land + activator->client->ps.jumpZStart = activator->currentOrigin[2]; + } + + //so we don't take damage unless we land lower than we start here... + activator->client->ps.forceJumpZStart = 0; + activator->client->ps.pm_flags |= PMF_TRIGGER_PUSHED;//pushed by a trigger + + // play fly sound every 1.5 seconds + if ( self->noise_index && activator->fly_sound_debounce_time < level.time ) { + activator->fly_sound_debounce_time = level.time + 1500; + G_Sound( activator, self->noise_index ); + } +} + + +/*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) ENERGYNOISE CONSTANT NO_DAMAGE +When triggered, pushes the activator in the direction of angles +"speed" defaults to 1000 +ENERGYNOISE plays energy noise +CONSTANT will push activator in direction of 'target' at constant 'speed' +NO_DAMAGE the activator won't take falling damage after being pushed +*/ +void SP_target_push( gentity_t *self ) { + + + if (!self->speed) { + self->speed = 1000; + } + G_SetMovedir (self->s.angles, self->s.origin2); + VectorScale (self->s.origin2, self->speed, self->s.origin2); + + if ( self->spawnflags & 1 ) { + //self->noise_index = G_SoundIndex("sound/ambience/forge/antigrav.wav"); + } + if ( self->target ) { + + VectorCopy( self->s.origin, self->absmin ); + VectorCopy( self->s.origin, self->absmax ); + self->e_ThinkFunc = thinkF_AimAtTarget; + self->nextthink = level.time + START_TIME_LINK_ENTS; + + } + self->e_UseFunc = useF_Use_target_push; +} + +/* +============================================================================== + +trigger_teleport + +============================================================================== +*/ +#define SNAP_ANGLES 1 +#define NO_MISSILES 2 +#define NO_NPCS 4 +#define TTSF_STASIS 8 +#define TTSF_DEAD_OK 16 +void TeleportMover( gentity_t *mover, vec3_t origin, vec3_t diffAngles, qboolean snapAngle ); +void trigger_teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace ) +{ + gentity_t *dest; + + if ( self->svFlags & SVF_INACTIVE ) + {//set by target_deactivate + return; + } + + dest = G_PickTarget( self->target ); + if (!dest) + { + gi.Printf ("Couldn't find teleporter destination\n"); + return; + } + + if ( other->client ) + { + if ( other->client->ps.pm_type == PM_DEAD ) + { + if ( !(self->spawnflags&TTSF_DEAD_OK) ) + {//dead men can't teleport + return; + } + } + if ( other->NPC ) + { + if ( self->spawnflags & NO_NPCS ) + { + return; + } + } + + if ( other->client->playerTeam != TEAM_FREE && SpotWouldTelefrag2( other, dest->currentOrigin ) )//SpotWouldTelefrag( dest, other->client->playerTeam ) ) + {//Don't go through if something blocking on the other side + return; + } + + TeleportPlayer( other, dest->s.origin, dest->s.angles ); + } + //FIXME: check for SVF_NO_TELEPORT + else if ( !(self->svFlags & SVF_NO_TELEPORT) && !(self->spawnflags & NO_MISSILES) && VectorLengthSquared( other->s.pos.trDelta ) ) + {//It's a mover of some sort and is currently moving + vec3_t diffAngles = {0, 0, 0}; + qboolean snap = qfalse; + + if ( self->lastEnemy ) + { + VectorSubtract( dest->s.angles, self->lastEnemy->s.angles, diffAngles ); + } + else + {//snaps to angle + VectorSubtract( dest->s.angles, other->currentAngles, diffAngles ); + snap = qtrue; + } + + TeleportMover( other, dest->s.origin, diffAngles, snap ); + } +} + +void trigger_teleporter_find_closest_portal( gentity_t *self ) +{ + gentity_t *found = NULL; + vec3_t org, vec; + float dist, bestDist = 64*64; + + VectorAdd( self->mins, self->maxs, org ); + VectorScale( org, 0.5, org ); + while ( (found = G_Find( found, FOFS(classname), "misc_portal_surface" )) != NULL ) + { + VectorSubtract( found->currentOrigin, org, vec ); + dist = VectorLengthSquared( vec ); + if ( dist < bestDist ) + { + self->lastEnemy = found; + bestDist = dist; + } + } + + if ( self->lastEnemy ) + { + gi.Printf("trigger_teleporter found misc_portal_surface\n"); + } + self->e_ThinkFunc = thinkF_NULL; +} + +/*QUAKED trigger_teleport (.1 .5 .1) ? SNAP_ANGLES NO_MISSILES NO_NPCS STASIS DEAD_OK x x INACTIVE +Allows client side prediction of teleportation events. +Must point at a target_position, which will be the teleport destination. + +SNAP_ANGLES - Make the entity that passes through snap to the target_position's angles +NO_MISSILES - Missiles and thrown objects cannot pass through +NO_NPCS - NPCs cannot pass through +STASIS - will play stasis teleport sound and fx instead of starfleet +DEAD_OK - even if dead, you will teleport +*/ +void SP_trigger_teleport( gentity_t *self ) +{ + InitTrigger (self); + + // unlike other triggers, we need to send this one to the client + self->svFlags &= ~SVF_NOCLIENT; + + self->s.eType = ET_TELEPORT_TRIGGER; + self->e_TouchFunc = touchF_trigger_teleporter_touch; + + self->e_ThinkFunc = thinkF_trigger_teleporter_find_closest_portal; + self->nextthink = level.time + START_TIME_LINK_ENTS; + + gi.linkentity (self); +} + + + +/* +============================================================================== + +trigger_hurt + +============================================================================== +*/ + +/*QUAKED trigger_hurt (.1 .5 .1) ? START_OFF PLAYERONLY SILENT NO_PROTECTION LOCKCAM FALLING ELECTRICAL INACTIVE MULTIPLE +Any entity that touches this will be hurt. +It does dmg points of damage each server frame + +PLAYERONLY only the player is hurt by it +SILENT supresses playing the sound +NO_PROTECTION *nothing* stops the damage +LOCKCAM Falling death results in camera locking in place +FALLING Forces a falling scream and anim +ELECTRICAL does electrical damage +INACTIVE Cannot be triggered until used by a target_activate +MULTIPLE multiple entities can touch this trigger in a single frame *and* if needed, the trigger can have a wait of > 0 + +"dmg" default 5 (whole numbers only) +"delay" How many seconds it takes to get from 0 to "dmg" (default is 0) +"wait" Use in instead of "SLOW" - determines how often the player gets hurt, 0.1 is every frame, 1.0 is once per second. -1 will stop after one use +"count" If set, FALLING death causes a fade to black in this many milliseconds (default is 10000 = 10 seconds) +"NPC_targetname" - If set, only an NPC with a matching NPC_targetname will trip this trigger +"noise" sound to play when it hurts something ( default: "sound/world/electro" ) +*/ +void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + + G_ActivateBehavior(self,BSET_USE); + + //FIXME: Targeting the trigger will toggle its on / off state??? + if ( self->linked ) { + gi.unlinkentity( self ); + } else { + gi.linkentity( self ); + } +} + +void trigger_hurt_reset (gentity_t *self) +{ + self->attackDebounceTime = 0; + self->e_ThinkFunc = thinkF_NULL; +} +extern void JET_FlyStart(gentity_t* actor); +void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + int dflags; + int actualDmg = self->damage; + + if ( self->svFlags & SVF_INACTIVE ) + {//set by target_deactivate + return; + } + + if ( !other->takedamage ) + { + return; + } + + if( level.time < self->painDebounceTime + self->wait ) // normal 'wait' check + { + if( self->spawnflags & 2048 ) // MULTIPLE - allow multiple entities to touch this trigger in one frame + { + if ( self->painDebounceTime && level.time > self->painDebounceTime ) // if we haven't reached the next frame continue to let ents touch the trigger + { + return; + } + } + else // only allowing one ent per frame to touch trigger + { + return; + } + } + + // if the player has already activated this trigger this frame + if( other && !other->s.number && self->aimDebounceTime == level.time ) + { + return; + } + + + if ( self->spawnflags & 2 ) + {//player only + if ( other->s.number ) + { + return; + } + } + + if ( self->NPC_targetname && self->NPC_targetname[0] ) + {//I am for you, Kirk + if ( other->script_targetname && other->script_targetname[0] ) + {//must have a name + if ( Q_stricmp( self->NPC_targetname, other->script_targetname ) != 0 ) + {//not the right guy to fire me off + return; + } + } + else + {//no name? No trigger. + return; + } + } + + // play sound + if ( !(self->spawnflags & 4) ) + { + G_Sound( other, self->noise_index ); + } + + if ( self->spawnflags & 8 ) + { + dflags = DAMAGE_NO_PROTECTION; + } + else + { + dflags = 0; + } + + if ( self->delay ) + {//Increase dmg over time + if ( self->attackDebounceTime < self->delay ) + {//FIXME: this is for the entire trigger, not per person, so if someone else jumped in after you were in it for 5 seconds, they'd get damaged faster + actualDmg = floor( (float)(self->damage * self->attackDebounceTime / self->delay) ); + } + self->attackDebounceTime += FRAMETIME; + + self->e_ThinkFunc = thinkF_trigger_hurt_reset; + self->nextthink = level.time + FRAMETIME*2; + } + + if ( actualDmg ) + { + if (( self->spawnflags & 64 ) && other->client )//electrical damage + { + // zap effect + other->s.powerups |= ( 1 << PW_SHOCKED ); + other->client->ps.powerups[PW_SHOCKED] = level.time + 1000; + } + + if ( self->spawnflags & 32 ) + {//falling death + if ( other->NPC && other->client && + (other->client->NPC_class == CLASS_BOBAFETT || other->client->NPC_class == CLASS_ROCKETTROOPER )) + {//boba never falls to his death! + //FIXME: fall through if jetpack broken? + JET_FlyStart(other); + } + else + { + G_Damage (other, self, self, NULL, NULL, actualDmg, dflags|DAMAGE_NO_ARMOR, MOD_FALLING); + // G_Damage will free this ent, which makes it s.number 0, so we must check inuse... + if ( !other->s.number && other->health <= 0 ) + { + if ( self->count ) + { + extern void CGCam_Fade( vec4_t source, vec4_t dest, float duration ); + float src[4] = {0,0,0,0},dst[4]={0,0,0,1}; + CGCam_Fade( src, dst, self->count ); + } + if ( self->spawnflags & 16 ) + {//lock cam + cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_CDP; + cg.overrides.thirdPersonCameraDamp = 0; + } + if ( other->client ) + { + other->client->ps.pm_flags |= PMF_SLOW_MO_FALL; + } + //G_SoundOnEnt( other, CHAN_VOICE, "*falling1.wav" );//CHAN_VOICE_ATTEN? + } + } + } + else + { + G_Damage (other, self, self, NULL, NULL, actualDmg, dflags, MOD_TRIGGER_HURT); + } + if( other && !other->s.number ) + { + self->aimDebounceTime = level.time; + } + if (( self->spawnflags & 64 ) && other->client && other->health <= 0 )//electrical damage + {//just killed them, make the effect last longer since dead clients don't touch triggers + other->client->ps.powerups[PW_SHOCKED] = level.time + 10000; + } + self->painDebounceTime = level.time; + } + + if ( self->wait < 0 ) + { + self->e_TouchFunc = touchF_NULL; + } +} + +void SP_trigger_hurt( gentity_t *self ) +{ + char buffer[MAX_QPATH]; + char *s; + + InitTrigger (self); + + if ( !( self->spawnflags & 4 )) + { + G_SpawnString( "noise", "sound/world/electro", &s ); + + Q_strncpyz( buffer, s, sizeof(buffer) ); + self->noise_index = G_SoundIndex(buffer); + } + + self->e_TouchFunc = touchF_hurt_touch; + + if ( !self->damage ) { + self->damage = 5; + } + + self->delay *= 1000; + self->wait *= 1000; + + self->contents = CONTENTS_TRIGGER; + + if ( self->targetname ) {//NOTE: for some reason, this used to be: if(self->spawnflags&2) + self->e_UseFunc = useF_hurt_use; + } + + // link in to the world if starting active + if ( !(self->spawnflags & 1) ) + { + gi.linkentity (self); + } + else // triggers automatically get linked into the world by SetBrushModel, so we have to unlink it here + { + gi.unlinkentity(self); + } +} + +#define INITIAL_SUFFOCATION_DELAY 5000 //5 seconds +void space_touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + if (!other || !other->inuse || !other->client ) + //NOTE: we need vehicles to know this, too... + //|| other->s.number >= MAX_CLIENTS) + { + return; + } + + if (other->s.m_iVehicleNum + && other->s.m_iVehicleNum <= MAX_CLIENTS ) + {//a player client inside a vehicle + gentity_t *veh = &g_entities[other->s.m_iVehicleNum]; + + if (veh->inuse && veh->client && veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->hideRider) + { //if they are "inside" a vehicle, then let that protect them from THE HORRORS OF SPACE. + return; + } + } + + if (!G_PointInBounds(other->client->ps.origin, self->absmin, self->absmax)) + { //his origin must be inside the trigger + return; + } + + if (!other->client->inSpaceIndex || + other->client->inSpaceIndex == ENTITYNUM_NONE) + { //freshly entering space + other->client->inSpaceSuffocation = level.time + INITIAL_SUFFOCATION_DELAY; + } + + other->client->inSpaceIndex = self->s.number; +} + +/*QUAKED trigger_space (.5 .5 .5) ? +causes human clients to suffocate and have no gravity. + +*/ +void SP_trigger_space(gentity_t *self) +{ + InitTrigger(self); + self->contents = CONTENTS_TRIGGER; + + //FIXME: implement!!! + //self->e_TouchFunc = touchF_space_touch; + + gi.linkentity(self); +} + +void shipboundary_touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + gentity_t *ent; + + if (!other || !other->inuse || !other->client || + other->s.number < MAX_CLIENTS || + !other->m_pVehicle) + { //only let vehicles touch + return; + } + + ent = G_Find (NULL, FOFS(targetname), self->target); + if (!ent || !ent->inuse) + { //this is bad + G_Error("trigger_shipboundary has invalid target '%s'\n", self->target); + return; + } + + if (!other->s.m_iVehicleNum || other->m_pVehicle->m_iRemovedSurfaces) + { //if a vehicle touches a boundary without a pilot in it or with parts missing, just blow the thing up + G_Damage(other, other, other, NULL, other->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE); + return; + } + + other->client->ps.vehTurnaroundIndex = ent->s.number; + other->client->ps.vehTurnaroundTime = level.time + self->count; +} + +/*QUAKED trigger_shipboundary (.5 .5 .5) ? +causes vehicle to turn toward target and travel in that direction for a set time when hit. + +"target" name of entity to turn toward (can be info_notnull, or whatever). +"traveltime" time to travel in this direction + +*/ +void SP_trigger_shipboundary(gentity_t *self) +{ + InitTrigger(self); + self->contents = CONTENTS_TRIGGER; + + if (!self->target || !self->target[0]) + { + G_Error("trigger_shipboundary without a target."); + } + G_SpawnInt("traveltime", "0", &self->count); + + if (!self->count) + { + G_Error("trigger_shipboundary without traveltime."); + } + + //FIXME: implement! + //self->e_TouchFunc = touchF_shipboundary_touch; + + gi.linkentity(self); +} +/* +============================================================================== + +timer + +============================================================================== +*/ + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +This should be renamed trigger_timer... +Repeatedly fires its targets. +Can be turned on or off by using. + +"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) + +*/ +void func_timer_think( gentity_t *self ) { + G_UseTargets (self, self->activator); + // set time before next firing + self->nextthink = level.time + 1000 * ( self->wait + crandom() * self->random ); +} + +void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator ) { + self->activator = activator; + + G_ActivateBehavior(self,BSET_USE); + + + // if on, turn it off + if ( self->nextthink ) { + self->nextthink = 0; + return; + } + + // turn it on + func_timer_think (self); +} + +void SP_func_timer( gentity_t *self ) { + G_SpawnFloat( "random", "1", &self->random); + G_SpawnFloat( "wait", "1", &self->wait ); + + self->e_UseFunc = useF_func_timer_use; + self->e_ThinkFunc = thinkF_func_timer_think; + + if ( self->random >= self->wait ) { + self->random = self->wait - 1;//NOTE: was - FRAMETIME, but FRAMETIME is in msec (100) and these numbers are in *seconds*! + gi.Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) ); + } + + if ( self->spawnflags & 1 ) { + self->nextthink = level.time + FRAMETIME; + self->activator = self; + } + + self->svFlags = SVF_NOCLIENT; +} + +/* +============================================================================== + +timer + +============================================================================== +*/ + + +/*QUAKED trigger_entdist (.1 .5 .1) (-8 -8 -8) (8 8 8) PLAYER NPC +fires if the given entity is within the given distance. Sets itself inactive after one use. +----- KEYS ----- +distance - radius entity can be away to fire trigger +target - fired if entity is within distance +target2 - fired if entity not within distance + +NPC_target - NPC_types to look for +ownername - If any, who to calc the distance from- default is the trigger_entdist himself +example: target "biessman telsia" will look for the biessman and telsia NPC +if it finds either of these within distance it will fire. + + todo - + add delay, count + add monster classnames????? + add LOS to it??? +*/ + +void trigger_entdist_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + vec3_t diff; + gentity_t *found = NULL; + gentity_t *owner = NULL; + qboolean useflag; + const char *token, *holdString; + + if ( self->svFlags & SVF_INACTIVE ) // Don't use INACTIVE + return; + + G_ActivateBehavior(self,BSET_USE); + + if(self->ownername && self->ownername[0]) + { + owner = G_Find(NULL, FOFS(targetname), self->ownername); + } + + if(owner == NULL) + { + owner = self; + } + + self->activator = activator; + + useflag = qfalse; + + self->svFlags |= SVF_INACTIVE; // Make it inactive after one use + + if (self->spawnflags & ENTDIST_PLAYER) // Look for player??? + { + found = &g_entities[0]; + + if (found) + { + VectorSubtract(owner->currentOrigin, found->currentOrigin, diff); + if(VectorLength(diff) < self->count) + { + useflag = qtrue; + } + } + } + + if ((self->spawnflags & ENTDIST_NPC) && (!useflag)) + { + holdString = self->NPC_target; + + while (holdString) + { + token = COM_Parse( &holdString); + if ( !token ) // Nothing left to look at + { + break; + } + + found = G_Find(found, FOFS(targetname), token); // Look for the specified NPC + if (found) //Found??? + { + VectorSubtract(owner->currentOrigin, found->currentOrigin, diff); + if(VectorLength(diff) < self->count) // Within distance + { + useflag = qtrue; + break; + } + } + } + } + + if (useflag) + { + G_UseTargets2 (self, self->activator, self->target); + } + else if (self->target2) + { + // This is the negative target + G_UseTargets2 (self, self->activator, self->target2); + } + + +} + +void SP_trigger_entdist( gentity_t *self ) +{ + G_SpawnInt( "distance", "0", &self->count); + + self->e_UseFunc = useF_trigger_entdist_use; + +} + +// spawnflag +#define TRIGGERVISIBLE_FORCESIGHT 2 + +void trigger_visible_check_player_visibility( gentity_t *self ) +{ + //Check every FRAMETIME*2 + self->nextthink = level.time + FRAMETIME*2; + + if ( self->svFlags & SVF_INACTIVE ) + { + return; + } + + vec3_t dir; + float dist; + gentity_t *player = &g_entities[0]; + + if (!player || !player->client ) + { + return; + } + + // Added 01/20/03 by AReis + // If this trigger can only be used if the players force sight is on... + if ( self->spawnflags & TRIGGERVISIBLE_FORCESIGHT ) + { + // If their force sight is not on, leave... + if ( !( player->client->ps.forcePowersActive & (1 << FP_SEE) ) ) + { + return; + } + } + + //1: see if player is within 512*512 range + VectorSubtract( self->currentOrigin, player->client->renderInfo.eyePoint, dir ); + dist = VectorNormalize( dir ); + if ( dist < self->radius ) + {//Within range + vec3_t forward; + float dot; + //2: see if dot to us and player viewangles is > 0.7 + AngleVectors( player->client->renderInfo.eyeAngles, forward, NULL, NULL ); + dot = DotProduct( forward, dir ); + if ( dot > self->random ) + {//Within the desired FOV + //3: see if player is in PVS + if ( gi.inPVS( self->currentOrigin, player->client->renderInfo.eyePoint ) ) + { + vec3_t mins = {-1, -1, -1}; + vec3_t maxs = {1, 1, 1}; + //4: If needbe, trace to see if there is clear LOS from player viewpos + if ( (self->spawnflags&1) || G_ClearTrace( player->client->renderInfo.eyePoint, mins, maxs, self->currentOrigin, 0, MASK_OPAQUE ) ) + { + //5: Fire! + G_UseTargets( self, player ); + //6: Remove yourself + G_FreeEntity( self ); + } + } + } + } + +} + +/*QUAKED trigger_visible (.1 .5 .1) (-8 -8 -8) (8 8 8) NOTRACE FORCESIGHT x x x x x INACTIVE + + Only fires when player is looking at it, fires only once then removes itself. + + NOTRACE - Doesn't check to make sure the line of sight is completely clear (penetrates walls, forcefields, etc) + FORCESIGHT - Only activates this trigger if force sight is on. + INACTIVE - won't check for player visibility until activated + + radius - how far this ent can be from player's eyes, max, and still be considered "seen" + FOV - how far off to the side of the player's field of view this can be, max, and still be considered "seen". Player FOV is 80, so the default for this value is 30. + + "target" - What to use when it fires. +*/ +void SP_trigger_visible( gentity_t *self ) +{ + if ( self->radius <= 0 ) + { + self->radius = 512; + } + + if ( self->random <= 0 ) + {//about 30 degrees + self->random = 0.7f; + } + else + {//convert from FOV degrees to number meaningful for dot products + self->random = 1.0f - (self->random/90.0f); + } + + if ( self->spawnflags & 128 ) + {// Make it inactive + self->svFlags |= SVF_INACTIVE; + } + + G_SetOrigin( self, self->s.origin ); + gi.linkentity( self ); + + self->e_ThinkFunc = thinkF_trigger_visible_check_player_visibility; + self->nextthink = level.time + FRAMETIME*2; +} \ No newline at end of file diff --git a/code/game/g_turret.cpp b/code/game/g_turret.cpp new file mode 100644 index 0000000..e6fd865 --- /dev/null +++ b/code/game/g_turret.cpp @@ -0,0 +1,2516 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +#include "g_local.h" +#include "g_functions.h" +#include "b_local.h" + +extern cvar_t *g_spskill; + +void G_SetEnemy( gentity_t *self, gentity_t *enemy ); +void finish_spawning_turret( gentity_t *base ); +void ObjectDie (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ); +//special routine for tracking angles between client and server -rww +void turret_SetBoneAngles(gentity_t *ent, char *bone, const vec3_t angles); + +#define ARM_ANGLE_RANGE 60 +#define HEAD_ANGLE_RANGE 90 + +#define SPF_TURRETG2_TURBO 4 +#define SPF_TURRETG2_LEAD_ENEMY 8 + +#define name "models/map_objects/imp_mine/turret_canon.glm" +#define name2 "models/map_objects/imp_mine/turret_damage.md3" +#define name3 "models/map_objects/wedge/laser_cannon_model.glm" + +//------------------------------------------------------------------------------------------------------------ +void TurretPain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc ) +//------------------------------------------------------------------------------------------------------------ +{ + vec3_t dir; + + VectorSubtract( point, self->currentOrigin, dir ); + VectorNormalize( dir ); + + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) + { + // DEMP2 makes the turret stop shooting for a bit..and does extra feedback + self->attackDebounceTime = level.time + 800 + random() * 500; + G_PlayEffect( "sparks/spark_exp_nosnd", point, dir ); + } + + if ( !self->enemy ) + {//react to being hit + G_SetEnemy( self, attacker ); + } + + G_PlayEffect( "sparks/spark_exp_nosnd", point, dir ); +} + +//------------------------------------------------------------------------------------------------------------ +void turret_die ( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc ) +//------------------------------------------------------------------------------------------------------------ +{ + vec3_t forward = { 0,0,-1 }, pos; + + // Turn off the thinking of the base & use it's targets + self->e_ThinkFunc = thinkF_NULL; + self->e_UseFunc = useF_NULL; + + // clear my data + self->e_DieFunc = dieF_NULL; + self->takedamage = qfalse; + self->health = 0; + self->s.loopSound = 0; + + // hack the effect angle so that explode death can orient the effect properly + if ( self->spawnflags & 2 ) + { + VectorSet( forward, 0, 0, 1 ); + } + +// VectorCopy( self->currentOrigin, self->s.pos.trBase ); + + if ( self->spawnflags & SPF_TURRETG2_TURBO ) + { + G_PlayEffect( G_EffectIndex( "explosions/fighter_explosion2" ), self->currentOrigin, self->currentAngles ); + } + else + { + if ( self->fxID > 0 ) + { + VectorMA( self->currentOrigin, 12, forward, pos ); + G_PlayEffect( self->fxID, pos, forward ); + } + } + + if ( self->splashDamage > 0 && self->splashRadius > 0 ) + { + G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, attacker, MOD_UNKNOWN ); + } + + if ( self->s.eFlags & EF_SHADER_ANIM ) + { + self->s.frame = 1; // black + } + + self->s.weapon = 0; // crosshair code uses this to mark crosshair red + + if ( self->s.modelindex2 ) + { + // switch to damage model if we should + self->s.modelindex = self->s.modelindex2; + + VectorCopy( self->currentAngles, self->s.apos.trBase ); + VectorClear( self->s.apos.trDelta ); + + if ( self->target ) + { + G_UseTargets( self, attacker ); + } + } + else + { + ObjectDie( self, inflictor, attacker, damage, meansOfDeath ); + } +} + +//start an animation on model_root both server side and client side +void TurboLaser_SetBoneAnim(gentity_t *eweb, int startFrame, int endFrame) +{ + //set info on the entity so it knows to start the anim on the client next snapshot. + //eweb->s.eFlags |= EF_G2ANIMATING; + + if (eweb->s.torsoAnim == startFrame && eweb->s.legsAnim == endFrame) + { //already playing this anim, let's flag it to restart + //eweb->s.torsoFlip = !eweb->s.torsoFlip; + } + else + { + eweb->s.torsoAnim = startFrame; + eweb->s.legsAnim = endFrame; + } + + //now set the animation on the server ghoul2 instance. + assert(&eweb->ghoul2[0]); + gi.G2API_SetBoneAnim(&eweb->ghoul2[0], "model_root", startFrame, endFrame, + (BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND), 1.0f, level.time, -1, 100); +} + +#define START_DIS 15 + +extern void WP_FireTurboLaserMissile( gentity_t *ent, vec3_t start, vec3_t dir ); + +//---------------------------------------------------------------- +static void turret_fire ( gentity_t *ent, vec3_t start, vec3_t dir ) +//---------------------------------------------------------------- +{ + vec3_t org, ang; + gentity_t *bolt; + + if ( (gi.pointcontents( start, ent->s.number )&MASK_SHOT) ) + { + return; + } + + VectorMA( start, -START_DIS, dir, org ); // dumb.... + + if ( ent->random ) + { + vectoangles( dir, ang ); + ang[PITCH] += Q_flrand( -ent->random, ent->random ); + ang[YAW] += Q_flrand( -ent->random, ent->random ); + AngleVectors( ang, dir, NULL, NULL ); + } + + vectoangles(dir, ang); + + if ( (ent->spawnflags&SPF_TURRETG2_TURBO) ) + { + //muzzle flash + G_PlayEffect( G_EffectIndex( "turret/turb_muzzle_flash" ), org, ang ); + G_SoundOnEnt( ent, CHAN_LESS_ATTEN, "sound/vehicles/weapons/turbolaser/fire1" ); + + WP_FireTurboLaserMissile( ent, start, dir ); + if ( ent->alt_fire ) + { + TurboLaser_SetBoneAnim( ent, 2, 3 ); + } + else + { + TurboLaser_SetBoneAnim( ent, 0, 1 ); + } + } + else + { + G_PlayEffect( "blaster/muzzle_flash", org, dir ); + + bolt = G_Spawn(); + + bolt->classname = "turret_proj"; + bolt->nextthink = level.time + 10000; + bolt->e_ThinkFunc = thinkF_G_FreeEntity; + bolt->s.eType = ET_MISSILE; + bolt->s.weapon = WP_BLASTER; + bolt->owner = ent; + bolt->damage = ent->damage; + bolt->dflags = DAMAGE_NO_KNOCKBACK | DAMAGE_HEAVY_WEAP_CLASS; // Don't push them around, or else we are constantly re-aiming + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->methodOfDeath = MOD_ENERGY; + bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + bolt->trigger_formation = qfalse; // don't draw tail on first frame + + VectorSet( bolt->maxs, 1.5, 1.5, 1.5 ); + VectorScale( bolt->maxs, -1, bolt->mins ); + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time; + VectorCopy( start, bolt->s.pos.trBase ); + VectorScale( dir, 1100, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( start, bolt->currentOrigin); + } +} + +//----------------------------------------------------- +void turret_head_think( gentity_t *self ) +//----------------------------------------------------- +{ + // if it's time to fire and we have an enemy, then gun 'em down! pushDebounce time controls next fire time + if ( self->enemy && self->pushDebounceTime < level.time && self->attackDebounceTime < level.time ) + { + // set up our next fire time + self->pushDebounceTime = level.time + self->wait; + + vec3_t fwd, org; + mdxaBone_t boltMatrix; + + // Getting the flash bolt here + gi.G2API_GetBoltMatrix( self->ghoul2, + 0, + (self->spawnflags&SPF_TURRETG2_TURBO) ? ( (self->alt_fire ? gi.G2API_AddBolt( &self->ghoul2[0], "*muzzle2" ) : gi.G2API_AddBolt( &self->ghoul2[0], "*muzzle1" )) ) : gi.G2API_AddBolt( &self->ghoul2[0], "*flash03" ), + &boltMatrix, + self->currentAngles, + self->currentOrigin, + level.time, + NULL, + self->modelScale ); + if ( (self->spawnflags&SPF_TURRETG2_TURBO) ) + { + self->alt_fire = !self->alt_fire; + } + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + + if ( (self->spawnflags&SPF_TURRETG2_TURBO) ) + { + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, fwd ); + } + else + { + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd ); + } + + VectorMA( org, START_DIS, fwd, org ); + + turret_fire( self, org, fwd ); + self->fly_sound_debounce_time = level.time;//used as lastShotTime + } +} + +//----------------------------------------------------- +static void turret_aim( gentity_t *self ) +//----------------------------------------------------- +{ + vec3_t enemyDir, org, org2; + vec3_t desiredAngles, setAngle; + float diffYaw = 0.0f, diffPitch = 0.0f; + float maxYawSpeed = ( self->spawnflags & SPF_TURRETG2_TURBO ) ? 30.0f : 14.0f; + float maxPitchSpeed = ( self->spawnflags & SPF_TURRETG2_TURBO ) ? 15.0f : 3.0f; + + // move our gun base yaw to where we should be at this time.... + EvaluateTrajectory( &self->s.apos, level.time, self->currentAngles ); + self->currentAngles[YAW] = AngleNormalize360( self->currentAngles[YAW] ); + self->speed = AngleNormalize360( self->speed ); + + if ( self->enemy ) + { + // ...then we'll calculate what new aim adjustments we should attempt to make this frame + // Aim at enemy + if ( self->enemy->client ) + { + VectorCopy( self->enemy->client->renderInfo.eyePoint, org ); + } + else + { + VectorCopy( self->enemy->currentOrigin, org ); + } + if ( self->spawnflags & 2 ) + { + org[2] -= 15; + } + else + { + org[2] -= 5; + } + mdxaBone_t boltMatrix; + + // Getting the "eye" here + gi.G2API_GetBoltMatrix( self->ghoul2, + 0, + (self->spawnflags&SPF_TURRETG2_TURBO) ? ( (self->alt_fire ? gi.G2API_AddBolt( &self->ghoul2[0], "*muzzle2" ) : gi.G2API_AddBolt( &self->ghoul2[0], "*muzzle1" )) ) : gi.G2API_AddBolt( &self->ghoul2[0], "*flash03" ), + &boltMatrix, + self->currentAngles, + self->s.origin, + level.time, + NULL, + self->modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 ); + + VectorSubtract( org, org2, enemyDir ); + vectoangles( enemyDir, desiredAngles ); + + diffYaw = AngleSubtract( self->currentAngles[YAW], desiredAngles[YAW] ); + diffPitch = AngleSubtract( self->speed, desiredAngles[PITCH] ); + } + else + { + // no enemy, so make us slowly sweep back and forth as if searching for a new one +// diffYaw = sin( level.time * 0.0001f + self->count ) * 5.0f; // don't do this for now since it can make it go into walls. + } + + if ( diffYaw ) + { + // cap max speed.... + if ( fabs(diffYaw) > maxYawSpeed ) + { + diffYaw = ( diffYaw >= 0 ? maxYawSpeed : -maxYawSpeed ); + } + + // ...then set up our desired yaw + VectorSet( setAngle, 0.0f, diffYaw, 0.0f ); + + VectorCopy( self->currentAngles, self->s.apos.trBase ); + VectorScale( setAngle,- 5, self->s.apos.trDelta ); + self->s.apos.trTime = level.time; + self->s.apos.trType = TR_LINEAR; + } + + if ( diffPitch ) + { + if ( fabs(diffPitch) > maxPitchSpeed ) + { + // cap max speed + self->speed += (diffPitch > 0.0f) ? -maxPitchSpeed : maxPitchSpeed; + } + else + { + // small enough, so just add half the diff so we smooth out the stopping + self->speed -= ( diffPitch );//desiredAngles[PITCH]; + } + + // Note that this is NOT interpolated, so it will be less smooth...On the other hand, it does use Ghoul2 to blend, so it may smooth it out a bit? + if ( (self->spawnflags&SPF_TURRETG2_TURBO) ) + { + if ( self->spawnflags & 2 ) + { + VectorSet( desiredAngles, 0.0f, 0.0f, -self->speed ); + } + else + { + VectorSet( desiredAngles, 0.0f, 0.0f, self->speed ); + } + turret_SetBoneAngles(self, "pitch", desiredAngles); + } + else + { + // Note that this is NOT interpolated, so it will be less smooth...On the other hand, it does use Ghoul2 to blend, so it may smooth it out a bit? + if ( self->spawnflags & 2 ) + { + VectorSet( desiredAngles, self->speed, 0.0f, 0.0f ); + } + else + { + VectorSet( desiredAngles, -self->speed, 0.0f, 0.0f ); + } + gi.G2API_SetBoneAngles( &self->ghoul2[0], "Bone_body", desiredAngles, + BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, cg.time ); + } + } + + if ( diffYaw || diffPitch ) + { + self->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" ); + } + else + { + self->s.loopSound = 0; + } +} + +//----------------------------------------------------- +static void turret_turnoff( gentity_t *self ) +//----------------------------------------------------- +{ + if ( self->enemy == NULL ) + { + // we don't need to turnoff + return; + } + + if ( (self->spawnflags&SPF_TURRETG2_TURBO) ) + { + TurboLaser_SetBoneAnim( self, 4, 5 ); + } + + // shut-down sound + G_Sound( self, G_SoundIndex( "sound/chars/turret/shutdown.wav" )); + + // make turret play ping sound for 5 seconds + self->aimDebounceTime = level.time + 5000; + + // Clear enemy + self->enemy = NULL; +} + +//----------------------------------------------------- +static qboolean turret_find_enemies( gentity_t *self ) +//----------------------------------------------------- +{ + // HACK for t2_wedge!!! + if ( self->spawnflags & SPF_TURRETG2_TURBO ) + return qfalse; + + qboolean found = qfalse; + int count; + float bestDist = self->radius * self->radius; + float enemyDist; + vec3_t enemyDir, org, org2; + gentity_t *entity_list[MAX_GENTITIES], *target, *bestTarget = NULL; + + if ( self->aimDebounceTime > level.time ) // time since we've been shut off + { + // We were active and alert, i.e. had an enemy in the last 3 secs + if ( self->painDebounceTime < level.time ) + { + G_Sound(self, G_SoundIndex( "sound/chars/turret/ping.wav" )); + self->painDebounceTime = level.time + 1000; + } + } + + VectorCopy( self->currentOrigin, org2 ); + if ( self->spawnflags & 2 ) + { + org2[2] += 20; + } + else + { + org2[2] -= 20; + } + + count = G_RadiusList( org2, self->radius, self, qtrue, entity_list ); + + for ( int i = 0; i < count; i++ ) + { + target = entity_list[i]; + + if ( !target->client ) + { + // only attack clients + continue; + } + if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET )) + { + continue; + } + if ( target->client->playerTeam == self->noDamageTeam ) + { + // A bot we don't want to shoot + continue; + } + if ( !gi.inPVS( org2, target->currentOrigin )) + { + continue; + } + + VectorCopy( target->client->renderInfo.eyePoint, org ); + + if ( self->spawnflags & 2 ) + { + org[2] -= 15; + } + else + { + org[2] += 5; + } + + trace_t tr; + gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); + + if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number )) + { + // Only acquire if have a clear shot, Is it in range and closer than our best? + VectorSubtract( target->currentOrigin, self->currentOrigin, enemyDir ); + enemyDist = VectorLengthSquared( enemyDir ); + + if ( enemyDist < bestDist )// all things equal, keep current + { + if ( self->attackDebounceTime < level.time ) + { + // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound + G_Sound( self, G_SoundIndex( "sound/chars/turret/startup.wav" )); + + // Wind up turrets for a bit + self->attackDebounceTime = level.time + 1400; + } + + bestTarget = target; + bestDist = enemyDist; + found = qtrue; + } + } + } + + if ( found ) + { + if ( !self->enemy ) + {//just aquired one + AddSoundEvent( bestTarget, self->currentOrigin, 256, AEL_DISCOVERED ); + AddSightEvent( bestTarget, self->currentOrigin, 512, AEL_DISCOVERED, 20 ); + } + G_SetEnemy( self, bestTarget ); + if ( VALIDSTRING( self->target2 )) + { + G_UseTargets2( self, self, self->target2 ); + } + } + + return found; +} + +//----------------------------------------------------- +void turret_base_think( gentity_t *self ) +//----------------------------------------------------- +{ + qboolean turnOff = qtrue; + float enemyDist; + vec3_t enemyDir, org, org2; + + self->nextthink = level.time + FRAMETIME; + + if ( self->spawnflags & 1 ) + { + // not turned on + turret_turnoff( self ); + turret_aim( self ); + + // No target + self->flags |= FL_NOTARGET; + return; + } + else + { + // I'm all hot and bothered + self->flags &= ~FL_NOTARGET; + } + + if ( !self->enemy ) + { + if ( turret_find_enemies( self )) + { + turnOff = qfalse; + } + } + else + { + if ( self->enemy->health > 0 ) + { + // enemy is alive + VectorSubtract( self->enemy->currentOrigin, self->currentOrigin, enemyDir ); + enemyDist = VectorLengthSquared( enemyDir ); + + if ( enemyDist < self->radius * self->radius ) + { + // was in valid radius + if ( gi.inPVS( self->currentOrigin, self->enemy->currentOrigin ) ) + { + // Every now and again, check to see if we can even trace to the enemy + trace_t tr; + + if ( self->enemy->client ) + { + VectorCopy( self->enemy->client->renderInfo.eyePoint, org ); + } + else + { + VectorCopy( self->enemy->currentOrigin, org ); + } + VectorCopy( self->currentOrigin, org2 ); + if ( self->spawnflags & 2 ) + { + org2[2] += 10; + } + else + { + org2[2] -= 10; + } + gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); + + if ( self->spawnflags & SPF_TURRETG2_TURBO || ( !tr.allsolid && !tr.startsolid && tr.entityNum == self->enemy->s.number ) ) + { + turnOff = qfalse; // Can see our enemy + } + } + } + } + + turret_head_think( self ); + } + + if ( turnOff ) + { + if ( self->bounceCount < level.time ) // bounceCount is used to keep the thing from ping-ponging from on to off + { + turret_turnoff( self ); + } + } + else + { + // keep our enemy for a minimum of 2 seconds from now + self->bounceCount = level.time + 2000 + random() * 150; + } + + turret_aim( self ); +} + +//----------------------------------------------------------------------------- +void turret_base_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +//----------------------------------------------------------------------------- +{ + // Toggle on and off + self->spawnflags = (self->spawnflags ^ 1); + + if (( self->s.eFlags & EF_SHADER_ANIM ) && ( self->spawnflags & 1 )) // Start_Off + { + self->s.frame = 1; // black + } + else + { + self->s.frame = 0; // glow + } +} + +//special routine for tracking angles between client and server -rww +void turret_SetBoneAngles(gentity_t *ent, char *bone, const vec3_t angles) +{ + /* +#ifdef _XBOX + byte *thebone = &ent->s.boneIndex1; + byte *firstFree = NULL; +#else + int *thebone = &ent->s.boneIndex1; + int *firstFree = NULL; +#endif + int i = 0; + int boneIndex = G_BoneIndex(bone); + int flags; + Eorientations up, right, forward; + vec3_t *boneVector = &ent->s.boneAngles1; + vec3_t *freeBoneVec = NULL; + + while (thebone) + { + if (!*thebone && !firstFree) + { //if the value is 0 then this index is clear, we can use it if we don't find the bone we want already existing. + firstFree = thebone; + freeBoneVec = boneVector; + } + else if (*thebone) + { + if (*thebone == boneIndex) + { //this is it + break; + } + } + + switch (i) + { + case 0: + thebone = &ent->s.boneIndex2; + boneVector = &ent->s.boneAngles2; + break; + case 1: + thebone = &ent->s.boneIndex3; + boneVector = &ent->s.boneAngles3; + break; + case 2: + thebone = &ent->s.boneIndex4; + boneVector = &ent->s.boneAngles4; + break; + default: + thebone = NULL; + boneVector = NULL; + break; + } + + i++; + } + + if (!thebone) + { //didn't find it, create it + if (!firstFree) + { //no free bones.. can't do a thing then. + Com_Printf("WARNING: NPC has no free bone indexes\n"); + return; + } + + thebone = firstFree; + + *thebone = boneIndex; + boneVector = freeBoneVec; + } + + //If we got here then we have a vector and an index. + + //Copy the angles over the vector in the entitystate, so we can use the corresponding index + //to set the bone angles on the client. + VectorCopy(angles, *boneVector); +*/ + //Now set the angles on our server instance if we have one. + + if ( !ent->ghoul2.size() ) + { + return; + } + + int flags = BONE_ANGLES_POSTMULT; + Eorientations up, right, forward; + up = POSITIVE_Y; + right = NEGATIVE_Z; + forward = NEGATIVE_X; + + //first 3 bits is forward, second 3 bits is right, third 3 bits is up + //ent->s.boneOrient = ((forward)|(right<<3)|(up<<6)); + + gi.G2API_SetBoneAngles( &ent->ghoul2[0], bone, angles, flags, up, + right, forward, NULL, 100, level.time ); +} + +void turret_set_models( gentity_t *self, qboolean dying ) +{ + if ( dying ) + { + if ( !(self->spawnflags&SPF_TURRETG2_TURBO) ) + { + self->s.modelindex = G_ModelIndex( name2 ); + self->s.modelindex2 = G_ModelIndex( name ); + } + + gi.G2API_RemoveGhoul2Model( self->ghoul2, 0 ); + /*G_KillG2Queue( self->s.number ); + self->s.modelGhoul2 = 0; + + gi.G2API_InitGhoul2Model( &self->ghoul2, + name2, + 0, //base->s.modelindex, + //note, this is not the same kind of index - this one's referring to the actual + //index of the model in the g2 instance, whereas modelindex is the index of a + //configstring -rww + 0, + 0, + 0, + 0); + */ + } + else + { + if ( !(self->spawnflags&SPF_TURRETG2_TURBO) ) + { + self->s.modelindex = G_ModelIndex( name ); + self->s.modelindex2 = G_ModelIndex( name2 ); + //set the new onw + gi.G2API_InitGhoul2Model( self->ghoul2, + name, + 0, //base->s.modelindex, + //note, this is not the same kind of index - this one's referring to the actual + //index of the model in the g2 instance, whereas modelindex is the index of a + //configstring -rww + 0, + 0, + 0, + 0); + } + else + { + self->s.modelindex = G_ModelIndex( name3 ); + //set the new onw + gi.G2API_InitGhoul2Model( self->ghoul2, + name3, + 0, //base->s.modelindex, + //note, this is not the same kind of index - this one's referring to the actual + //index of the model in the g2 instance, whereas modelindex is the index of a + //configstring -rww + 0, + 0, + 0, + 0); + } + + /*self->s.modelGhoul2 = 1; + if ( (self->spawnflags&SPF_TURRETG2_TURBO) ) + {//larger + self->s.g2radius = 128; + } + else + { + self->s.g2radius = 80; + }*/ + + if ( (self->spawnflags&SPF_TURRETG2_TURBO) ) + {//different pitch bone and muzzle flash points + turret_SetBoneAngles(self, "pitch", vec3_origin); + //self->genericValue11 = gi.G2API_AddBolt( self->ghoul2, 0, "*muzzle1" ); + //self->genericValue12 = gi.G2API_AddBolt( self->ghoul2, 0, "*muzzle2" ); + } + else + { + turret_SetBoneAngles(self, "Bone_body", vec3_origin); + //self->genericValue11 = gi.G2API_AddBolt( self->ghoul2, 0, "*flash03" ); + } + } +} + +/*QUAKED misc_turret (1 0 0) (-8 -8 -22) (8 8 0) START_OFF UPSIDE_DOWN TURBO +Turret that hangs from the ceiling, will aim and shoot at enemies + + START_OFF - Starts off + UPSIDE_DOWN - make it rest on a surface/floor instead of hanging from the ceiling + TURBO - Big-ass, Boxy Death Star Turbo Laser version + + radius - How far away an enemy can be for it to pick it up (default 512) + wait - Time between shots (default 150 ms) + dmg - How much damage each shot does (default 5) + health - How much damage it can take before exploding (default 100) + + splashDamage - How much damage the explosion does + splashRadius - The radius of the explosion + NOTE: If either of the above two are 0, it will not make an explosion + + targetname - Toggles it on/off + target - What to use when destroyed + target2 - What to use when it decides to start shooting at an enemy + + team - team that is not targeted by and does not take damage from this turret + "player", + "enemy", (default) + "neutral" +*/ +//----------------------------------------------------- +void SP_misc_turret( gentity_t *base ) +//----------------------------------------------------- +{ + /*base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/turret_canon.glm" ); + base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/turret_damage.md3" ); + base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/imp_mine/turret_canon.glm", base->s.modelindex ); + base->s.radius = 80.0f;*/ + turret_set_models( base, qfalse ); + + gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "Bone_body", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL ); + base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash03" ); + + finish_spawning_turret( base ); + + if (( base->spawnflags & 1 )) // Start_Off + { + base->s.frame = 1; // black + } + else + { + base->s.frame = 0; // glow + } + base->s.eFlags |= EF_SHADER_ANIM; +} + +//----------------------------------------------------- +void finish_spawning_turret( gentity_t *base ) +{ + vec3_t fwd; + + if ( base->spawnflags & 2 ) + { + base->s.angles[ROLL] += 180; + base->s.origin[2] -= 22.0f; + } + + G_SetAngles( base, base->s.angles ); + AngleVectors( base->currentAngles, fwd, NULL, NULL ); + + G_SetOrigin(base, base->s.origin); + + base->noDamageTeam = TEAM_ENEMY; + + base->s.eType = ET_GENERAL; + + if ( base->team && base->team[0] ) + { + base->noDamageTeam = (team_t)GetIDForString( TeamTable, base->team ); + base->team = NULL; + } + + // Set up our explosion effect for the ExplodeDeath code.... + base->fxID = G_EffectIndex( "turret/explode" ); + G_EffectIndex( "sparks/spark_exp_nosnd" ); + + base->e_UseFunc = useF_turret_base_use; + base->e_PainFunc = painF_TurretPain; + + // don't start working right away + base->e_ThinkFunc = thinkF_turret_base_think; + base->nextthink = level.time + FRAMETIME * 5; + + // this is really the pitch angle..... + base->speed = 0; + + G_SpawnFloat( "shotspeed", "0", &base->mass ); + if ( (base->spawnflags&SPF_TURRETG2_TURBO) ) + { + if ( !base->random ) + {//error worked into projectile direction + base->random = 2.0f; + } + + if ( !base->mass ) + {//misnomer: speed of projectile + base->mass = 4000; + } + + if ( !base->health ) + { + base->health = 2000; + } + + // search radius + if ( !base->radius ) + { + base->radius = 32768; + } + + // How quickly to fire + if ( !base->wait ) + { + base->wait = 500;// + random() * 500; + } + + if ( !base->splashDamage ) + { + base->splashDamage = 200; + } + + if ( !base->splashRadius ) + { + base->splashRadius = 500; + } + + // how much damage each shot does + if ( !base->damage ) + { + base->damage = 10; + } + + VectorSet( base->s.modelScale, 2.0f, 2.0f, 2.0f ); + VectorSet( base->maxs, 128.0f, 128.0f, 120.0f ); + VectorSet( base->mins, -128.0f, -128.0f, -120.0f ); + + // Cull Radius. + base->s.radius = 256; + + //start in "off" anim + TurboLaser_SetBoneAnim( base, 4, 5 ); + + // Make sure it doesn't do sparks and such when saber contacts with it. + base->flags = FL_DMG_BY_HEAVY_WEAP_ONLY; + base->takedamage = qfalse; + base->contents = CONTENTS_BODY|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_SHOTCLIP; + + base->noDamageTeam = TEAM_NEUTRAL; + base->team = NULL; + } + else + { + // this is a random time offset for the no-enemy-search-around-mode + base->count = random() * 9000; + + if ( !base->health ) + { + base->health = 100; + } + + // search radius + if ( !base->radius ) + { + base->radius = 512; + } + + // How quickly to fire + if ( !base->wait ) + { + base->wait = 150 + random() * 55; + } + + if ( !base->splashDamage ) + { + base->splashDamage = 10; + } + + if ( !base->splashRadius ) + { + base->splashRadius = 25; + } + + // how much damage each shot does + if ( !base->damage ) + { + base->damage = 5; + } + + if ( base->spawnflags & 2 ) + {//upside-down, invert mins and maxe + VectorSet( base->maxs, 10.0f, 10.0f, 30.0f ); + VectorSet( base->mins, -10.0f, -10.0f, 0.0f ); + } + else + { + VectorSet( base->maxs, 10.0f, 10.0f, 0.0f ); + VectorSet( base->mins, -10.0f, -10.0f, -30.0f ); + } + + base->takedamage = qtrue; + base->contents = CONTENTS_BODY|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_SHOTCLIP; + } + + // Precache special FX and moving sounds + if ( (base->spawnflags&SPF_TURRETG2_TURBO) ) + { + G_EffectIndex( "turret/turb_muzzle_flash" ); + G_EffectIndex( "turret/turb_shot" ); + G_EffectIndex( "turret/turb_impact" ); + //FIXME: Turbo Laser Cannon sounds! + G_SoundIndex( "sound/vehicles/weapons/turbolaser/turn.wav" ); + G_EffectIndex( "explosions/fighter_explosion2" ); + RegisterItem( FindItemForWeapon( WP_TIE_FIGHTER )); + } + else + { + // Precache moving sounds + G_SoundIndex( "sound/chars/turret/startup.wav" ); + G_SoundIndex( "sound/chars/turret/shutdown.wav" ); + G_SoundIndex( "sound/chars/turret/ping.wav" ); + G_SoundIndex( "sound/chars/turret/move.wav" ); + } + + base->max_health = base->health; + base->e_DieFunc = dieF_turret_die; + + base->material = MAT_METAL; + + if ( (base->spawnflags&SPF_TURRETG2_TURBO) ) + { + RegisterItem( FindItemForWeapon( WP_TURRET )); + + base->svFlags |= SVF_NO_TELEPORT|SVF_SELF_ANIMATING; + } + else + { + // Register this so that we can use it for the missile effect + RegisterItem( FindItemForWeapon( WP_BLASTER )); + + base->svFlags |= SVF_NO_TELEPORT|SVF_NONNPC_ENEMY|SVF_SELF_ANIMATING; + } + + // But set us as a turret so that we can be identified as a turret + base->s.weapon = WP_TURRET; + + gi.linkentity( base ); +} + +/*QUAKED misc_ns_turret (1 0 0) (-8 -8 -32) (8 8 29) START_OFF +NS turret that only hangs from the ceiling, will aim and shoot at enemies + + START_OFF - Starts off + + radius - How far away an enemy can be for it to pick it up (default 512) + wait - Time between shots (default 150 ms) + dmg - How much damage each shot does (default 5) + health - How much damage it can take before exploding (default 100) + + splashDamage - How much damage the explosion does + splashRadius - The radius of the explosion + NOTE: If either of the above two are 0, it will not make an explosion + + targetname - Toggles it on/off + target - What to use when destroyed + + team - team that is not targeted by and does not take damage from this turret + "player", + "enemy", (default) + "neutral" +*/ +//----------------------------------------------------- +void SP_misc_ns_turret( gentity_t *base ) +//----------------------------------------------------- +{ + base->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/turret/turret.glm" ); + base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/turret_damage.md3" ); // FIXME! + base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/nar_shaddar/turret/turret.glm", base->s.modelindex ); + base->s.radius = 80.0f; + + gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "Bone_body", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL ); + base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" ); + + finish_spawning_turret( base ); +} + +//-------------------------------------- + +void laser_arm_fire (gentity_t *ent) +{ + vec3_t start, end, fwd, rt, up; + trace_t trace; + + if ( ent->attackDebounceTime < level.time && ent->alt_fire ) + { + // If I'm firing the laser and it's time to quit....then quit! + ent->alt_fire = qfalse; +// ent->e_ThinkFunc = thinkF_NULL; +// return; + } + + ent->nextthink = level.time + FRAMETIME; + + // If a fool gets in the laser path, fry 'em + AngleVectors( ent->currentAngles, fwd, rt, up ); + + VectorMA( ent->currentOrigin, 20, fwd, start ); + //VectorMA( start, -6, rt, start ); + //VectorMA( start, -3, up, start ); + VectorMA( start, 4096, fwd, end ); + + gi.trace( &trace, start, NULL, NULL, end, ENTITYNUM_NONE, MASK_SHOT );//ignore + ent->fly_sound_debounce_time = level.time;//used as lastShotTime + + // Only deal damage when in alt-fire mode + if ( trace.fraction < 1.0 && ent->alt_fire ) + { + if ( trace.entityNum < ENTITYNUM_WORLD ) + { + gentity_t *hapless_victim = &g_entities[trace.entityNum]; + if ( hapless_victim && hapless_victim->takedamage && ent->damage ) + { + G_Damage( hapless_victim, ent, ent->nextTrain->activator, fwd, trace.endpos, ent->damage, DAMAGE_IGNORE_TEAM, MOD_UNKNOWN ); + } + } + } + + if ( ent->alt_fire ) + { +// CG_FireLaser( start, trace.endpos, trace.plane.normal, ent->nextTrain->startRGBA, qfalse ); + } + else + { +// CG_AimLaser( start, trace.endpos, trace.plane.normal ); + } +} + +void laser_arm_use (gentity_t *self, gentity_t *other, gentity_t *activator) +{ + vec3_t newAngles; + + self->activator = activator; + switch( self->count ) + { + case 0: + default: + //Fire + //gi.Printf("FIRE!\n"); +// self->lastEnemy->lastEnemy->e_ThinkFunc = thinkF_laser_arm_fire; +// self->lastEnemy->lastEnemy->nextthink = level.time + FRAMETIME; + //For 3 seconds + self->lastEnemy->lastEnemy->alt_fire = qtrue; // Let 'er rip! + self->lastEnemy->lastEnemy->attackDebounceTime = level.time + self->lastEnemy->lastEnemy->wait; + G_Sound(self->lastEnemy->lastEnemy, G_SoundIndex("sound/chars/l_arm/fire.wav")); + break; + case 1: + //Yaw left + //gi.Printf("LEFT...\n"); + VectorCopy( self->lastEnemy->currentAngles, newAngles ); + newAngles[1] += self->speed; + G_SetAngles( self->lastEnemy, newAngles ); +// bolt_head_to_arm( self->lastEnemy, self->lastEnemy->lastEnemy, LARM_FOFS, LARM_ROFS, LARM_UOFS ); + G_Sound( self->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) ); + break; + case 2: + //Yaw right + //gi.Printf("RIGHT...\n"); + VectorCopy( self->lastEnemy->currentAngles, newAngles ); + newAngles[1] -= self->speed; + G_SetAngles( self->lastEnemy, newAngles ); +// bolt_head_to_arm( self->lastEnemy, self->lastEnemy->lastEnemy, LARM_FOFS, LARM_ROFS, LARM_UOFS ); + G_Sound( self->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) ); + break; + case 3: + //pitch up + //gi.Printf("UP...\n"); + //FIXME: Clamp + VectorCopy( self->lastEnemy->lastEnemy->currentAngles, newAngles ); + newAngles[0] -= self->speed; + if ( newAngles[0] < -45 ) + { + newAngles[0] = -45; + } + G_SetAngles( self->lastEnemy->lastEnemy, newAngles ); + G_Sound( self->lastEnemy->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) ); + break; + case 4: + //pitch down + //gi.Printf("DOWN...\n"); + //FIXME: Clamp + VectorCopy( self->lastEnemy->lastEnemy->currentAngles, newAngles ); + newAngles[0] += self->speed; + if ( newAngles[0] > 90 ) + { + newAngles[0] = 90; + } + G_SetAngles( self->lastEnemy->lastEnemy, newAngles ); + G_Sound( self->lastEnemy->lastEnemy, G_SoundIndex( "sound/chars/l_arm/move.wav" ) ); + break; + } +} +/*QUAKED misc_laser_arm (1 0 0) (-8 -8 -8) (8 8 8) + +What it does when used depends on it's "count" (can be set by a script) + count: + 0 (default) - Fire in direction facing + 1 turn left + 2 turn right + 3 aim up + 4 aim down + + speed - How fast it turns (degrees per second, default 30) + dmg - How much damage the laser does 10 times a second (default 5 = 50 points per second) + wait - How long the beam lasts, in seconds (default is 3) + + targetname - to use it + target - What thing for it to be pointing at to start with + + "startRGBA" - laser color, Red Green Blue Alpha, range 0 to 1 (default 1.0 0.85 0.15 0.75 = Yellow-Orange) +*/ +void laser_arm_start (gentity_t *base) +{ + vec3_t armAngles; + vec3_t headAngles; + + base->e_ThinkFunc = thinkF_NULL; + //We're the base, spawn the arm and head + gentity_t *arm = G_Spawn(); + gentity_t *head = G_Spawn(); + + VectorCopy( base->s.angles, armAngles ); + VectorCopy( base->s.angles, headAngles ); + if ( base->target && base->target[0] ) + {//Start out pointing at something + gentity_t *targ = G_Find( NULL, FOFS(targetname), base->target ); + if ( !targ ) + {//couldn't find it! + Com_Printf(S_COLOR_RED "ERROR : laser_arm can't find target %s!\n", base->target); + } + else + {//point at it + vec3_t dir, angles; + + VectorSubtract(targ->currentOrigin, base->s.origin, dir ); + vectoangles( dir, angles ); + armAngles[1] = angles[1]; + headAngles[0] = angles[0]; + headAngles[1] = angles[1]; + } + } + + //Base + //Base does the looking for enemies and pointing the arm and head + G_SetAngles( base, base->s.angles ); + //base->s.origin[2] += 4; + G_SetOrigin(base, base->s.origin); + gi.linkentity(base); + //FIXME: need an actual model + base->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_base.md3"); + base->s.eType = ET_GENERAL; + G_SpawnVector4( "startRGBA", "1.0 0.85 0.15 0.75", (float *)&base->startRGBA ); + //anglespeed - how fast it can track the player, entered in degrees per second, so we divide by FRAMETIME/1000 + if ( !base->speed ) + { + base->speed = 3.0f; + } + else + { + base->speed *= FRAMETIME/1000.0f; + } + base->e_UseFunc = useF_laser_arm_use; + base->nextthink = level.time + FRAMETIME; + + //Arm + //Does nothing, not solid, gets removed when head explodes + G_SetOrigin( arm, base->s.origin ); + gi.linkentity(arm); + G_SetAngles( arm, armAngles ); +// bolt_head_to_arm( arm, head, LARM_FOFS, LARM_ROFS, LARM_UOFS ); + arm->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_arm.md3"); + + //Head + //Fires when enemy detected, animates, can be blown up + //Need to normalize the headAngles pitch for the clamping later + if ( headAngles[0] < -180 ) + { + headAngles[0] += 360; + } + else if ( headAngles[0] > 180 ) + { + headAngles[0] -= 360; + } + G_SetAngles( head, headAngles ); + head->s.modelindex = G_ModelIndex("models/mapobjects/dn/laser_head.md3"); + head->s.eType = ET_GENERAL; +// head->svFlags |= SVF_BROADCAST;// Broadcast to all clients + VectorSet( head->mins, -8, -8, -8 ); + VectorSet( head->maxs, 8, 8, 8 ); + head->contents = CONTENTS_BODY; + gi.linkentity(head); + + //dmg + if ( !base->damage ) + { + head->damage = 5; + } + else + { + head->damage = base->damage; + } + base->damage = 0; + //lifespan of beam + if ( !base->wait ) + { + head->wait = 3000; + } + else + { + head->wait = base->wait * 1000; + } + base->wait = 0; + + //Precache firing and explode sounds + G_SoundIndex("sound/weapons/explosions/cargoexplode.wav"); + G_SoundIndex("sound/chars/l_arm/fire.wav"); + G_SoundIndex("sound/chars/l_arm/move.wav"); + + //Link them up + base->lastEnemy = arm; + arm->lastEnemy = head; + head->owner = arm; + arm->nextTrain = head->nextTrain = base; + + // The head should always think, since it will be either firing a damage laser or just a target laser + head->e_ThinkFunc = thinkF_laser_arm_fire; + head->nextthink = level.time + FRAMETIME; + head->alt_fire = qfalse; // Don't do damage until told to +} + +void SP_laser_arm (gentity_t *base) +{ + base->e_ThinkFunc = thinkF_laser_arm_start; + base->nextthink = level.time + START_TIME_LINK_ENTS; +} + +//-------------------------- +// PERSONAL ASSAULT SENTRY +//-------------------------- + +#define PAS_DAMAGE 2 + +//----------------------------------------------------------------------------- +void pas_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +//----------------------------------------------------------------------------- +{ + // Toggle on and off + self->spawnflags = (self->spawnflags ^ 1); + + if ( self->spawnflags & 1 ) + { + self->nextthink = 0; // turn off and do nothing + self->e_ThinkFunc = thinkF_NULL; + } + else + { + self->nextthink = level.time + 50; + self->e_ThinkFunc = thinkF_pas_think; + } +} + +//---------------------------------------------------------------- +void pas_fire( gentity_t *ent ) +//---------------------------------------------------------------- +{ + vec3_t fwd, org; + mdxaBone_t boltMatrix; + + // Getting the flash bolt here + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, + ent->torsoBolt, + &boltMatrix, ent->currentAngles, ent->s.origin, (cg.time?cg.time:level.time), + NULL, ent->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd ); + + G_PlayEffect( "turret/muzzle_flash", org, fwd ); + + gentity_t *bolt; + + bolt = G_Spawn(); + + bolt->classname = "turret_proj"; + bolt->nextthink = level.time + 10000; + bolt->e_ThinkFunc = thinkF_G_FreeEntity; + bolt->s.eType = ET_MISSILE; + bolt->s.weapon = WP_TURRET; + bolt->owner = ent; + bolt->damage = PAS_DAMAGE; + bolt->dflags = DAMAGE_NO_KNOCKBACK; // Don't push them around, or else we are constantly re-aiming + bolt->splashDamage = 0; + bolt->splashRadius = 0; + bolt->methodOfDeath = MOD_ENERGY; + bolt->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + VectorSet( bolt->maxs, 1, 1, 1 ); + VectorScale( bolt->maxs, -1, bolt->mins ); + + bolt->s.pos.trType = TR_LINEAR; + bolt->s.pos.trTime = level.time; // move a bit on the very first frame + VectorCopy( org, bolt->s.pos.trBase ); + VectorScale( fwd, 900, bolt->s.pos.trDelta ); + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy( org, bolt->currentOrigin); +} + +//----------------------------------------------------- +static qboolean pas_find_enemies( gentity_t *self ) +//----------------------------------------------------- +{ + qboolean found = qfalse; + int count; + float bestDist = self->radius * self->radius; + float enemyDist; + vec3_t enemyDir, org, org2; + gentity_t *entity_list[MAX_GENTITIES], *target; + + if ( self->aimDebounceTime > level.time ) // time since we've been shut off + { + // We were active and alert, i.e. had an enemy in the last 3 secs + if ( self->painDebounceTime < level.time ) + { + G_Sound(self, G_SoundIndex( "sound/chars/turret/ping.wav" )); + self->painDebounceTime = level.time + 1000; + } + } + + mdxaBone_t boltMatrix; + + // Getting the "eye" here + gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, + self->torsoBolt, + &boltMatrix, self->currentAngles, self->s.origin, (cg.time?cg.time:level.time), + NULL, self->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 ); + + count = G_RadiusList( org2, self->radius, self, qtrue, entity_list ); + + for ( int i = 0; i < count; i++ ) + { + target = entity_list[i]; + + if ( !target->client ) + { + continue; + } + if ( target == self || !target->takedamage || target->health <= 0 || ( target->flags & FL_NOTARGET )) + { + continue; + } + if ( target->client->playerTeam == self->noDamageTeam ) + { + // A bot we don't want to shoot + continue; + } + if ( !gi.inPVS( org2, target->currentOrigin )) + { + continue; + } + + if ( target->client ) + { + VectorCopy( target->client->renderInfo.eyePoint, org ); + org[2] -= 15; + } + else + { + VectorCopy( target->currentOrigin, org ); + } + + trace_t tr; + gi.trace( &tr, org2, NULL, NULL, org, self->s.number, MASK_SHOT ); + + if ( !tr.allsolid && !tr.startsolid && ( tr.fraction == 1.0 || tr.entityNum == target->s.number )) + { + // Only acquire if have a clear shot, Is it in range and closer than our best? + VectorSubtract( target->currentOrigin, self->currentOrigin, enemyDir ); + enemyDist = VectorLengthSquared( enemyDir ); + + if ( target->s.number ) // don't do this for the player + { + G_StartFlee( target, self, self->currentOrigin, AEL_DANGER, 3000, 5000 ); + } + + if ( enemyDist < bestDist )// all things equal, keep current + { + if ( self->attackDebounceTime + 2000 < level.time ) + { + // We haven't fired or acquired an enemy in the last 2 seconds-start-up sound + G_Sound( self, G_SoundIndex( "sound/chars/turret/startup.wav" )); + + // Wind up turrets for a bit + self->attackDebounceTime = level.time + 900 + random() * 200; + } + + G_SetEnemy( self, target ); + bestDist = enemyDist; + found = qtrue; + } + } + } + + if ( found && VALIDSTRING( self->target2 )) + { + G_UseTargets2( self, self, self->target2 ); + } + + return found; +} + +//--------------------------------- +void pas_adjust_enemy( gentity_t *ent ) +//--------------------------------- +{ + qboolean keep = qtrue; + + if ( ent->enemy->health <= 0 ) + { + keep = qfalse; + } + else// if ( random() > 0.5f ) + { + // do a trace every now and then. + mdxaBone_t boltMatrix; + vec3_t org, org2; + + // Getting the "eye" here + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, + ent->torsoBolt, + &boltMatrix, ent->currentAngles, ent->s.origin, (cg.time?cg.time:level.time), + NULL, ent->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org2 ); + + if ( ent->enemy->client ) + { + VectorCopy( ent->enemy->client->renderInfo.eyePoint, org ); + org[2] -= 15; + } + else + { + VectorCopy( ent->enemy->currentOrigin, org ); + } + + trace_t tr; + gi.trace( &tr, org2, NULL, NULL, org, ent->s.number, MASK_SHOT ); + + if ( tr.allsolid || tr.startsolid || tr.entityNum != ent->enemy->s.number ) + { + // trace failed + keep = qfalse; + } + } + + if ( keep ) + { + ent->bounceCount = level.time + 500 + random() * 150; + } + else if ( ent->bounceCount < level.time ) // don't ping pong on and off + { + ent->enemy = NULL; + // shut-down sound + G_Sound( ent, G_SoundIndex( "sound/chars/turret/shutdown.wav" )); + + // make turret play ping sound for 5 seconds + ent->aimDebounceTime = level.time + 5000; + } +} + +//--------------------------------- +void pas_think( gentity_t *ent ) +//--------------------------------- +{ + if ( !ent->damage ) + { + // let us do our animation, then we are good to go in terms of pounding the crap out of enemies. + ent->damage = 1; + gi.G2API_SetBoneAnimIndex( &ent->ghoul2[ent->playerModel], ent->rootBone, 0, 11, BONE_ANIM_OVERRIDE_FREEZE, 0.8f, cg.time ); + ent->nextthink = level.time + 1200; + return; + } + + if ( !ent->count ) + { + // turrets that have no ammo may as well do nothing + return; + } + + ent->nextthink = level.time + FRAMETIME; + + if ( ent->enemy ) + { + // make sure that the enemy is still valid + pas_adjust_enemy( ent ); + } + + if ( !ent->enemy ) + { + pas_find_enemies( ent ); + } + + qboolean moved = qfalse; + float diffYaw = 0.0f, diffPitch = 0.0f; + vec3_t enemyDir, org; + vec3_t frontAngles, backAngles; + vec3_t desiredAngles; + + ent->speed = AngleNormalize360( ent->speed ); + ent->random = AngleNormalize360( ent->random ); + + if ( ent->enemy ) + { + // ...then we'll calculate what new aim adjustments we should attempt to make this frame + // Aim at enemy + if ( ent->enemy->client ) + { + VectorCopy( ent->enemy->client->renderInfo.eyePoint, org ); + org[2] -= 40; + } + else + { + VectorCopy( ent->enemy->currentOrigin, org ); + } + + VectorSubtract( org, ent->currentOrigin, enemyDir ); + vectoangles( enemyDir, desiredAngles ); + + diffYaw = AngleSubtract( ent->speed, desiredAngles[YAW] ); + diffPitch = AngleSubtract( ent->random, desiredAngles[PITCH] ); + } + else + { + // no enemy, so make us slowly sweep back and forth as if searching for a new one + diffYaw = sin( level.time * 0.0001f + ent->count ) * 2.0f; + } + + if ( fabs(diffYaw) > 0.25f ) + { + moved = qtrue; + + if ( fabs(diffYaw) > 10.0f ) + { + // cap max speed + ent->speed += (diffYaw > 0.0f) ? -10.0f : 10.0f; + } + else + { + // small enough + ent->speed -= diffYaw; + } + } + + + if ( fabs(diffPitch) > 0.25f ) + { + moved = qtrue; + + if ( fabs(diffPitch) > 4.0f ) + { + // cap max speed + ent->random += (diffPitch > 0.0f) ? -4.0f : 4.0f; + } + else + { + // small enough + ent->random -= diffPitch; + } + } + + // the bone axes are messed up, so hence some dumbness here + VectorSet( frontAngles, -ent->random, 0.0f, 0.0f ); + VectorSet( backAngles, 0.0f, 0.0f, ent->speed - ent->s.angles[YAW] ); + + gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_barrel", frontAngles, + BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, NEGATIVE_X, NULL,100,cg.time); + gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_gback", frontAngles, + BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, NEGATIVE_X, NULL,100,cg.time); + gi.G2API_SetBoneAngles( &ent->ghoul2[ent->playerModel], "bone_hinge", backAngles, + BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL,100,cg.time); + + if ( moved ) + { + //ent->s.loopSound = G_SoundIndex( "sound/chars/turret/move.wav" ); + } + else + { + ent->s.loopSound = 0; + } + + if ( ent->enemy && ent->attackDebounceTime < level.time && random() > 0.3f ) + { + ent->count--; + + if ( ent->count ) + { + pas_fire( ent ); + ent->fly_sound_debounce_time = level.time;//used as lastShotTime + } + else + { + ent->nextthink = 0; + G_Sound( ent, G_SoundIndex( "sound/chars/turret/shutdown.wav" )); + } + } +} + +/*QUAKED misc_sentry_turret (1 0 0) (-16 -16 0) (16 16 24) START_OFF RESERVED +personal assault sentry, like the ones you can carry in your inventory + + RESERVED - do no use this flag for anything, does nothing..etc. + + radius - How far away an enemy can be for it to pick it up (default 512) + count - number of shots before thing deactivates. -1 = infinite, default 150 + health - How much damage it can take before exploding (default 50) + + splashDamage - How much damage the explosion does + splashRadius - The radius of the explosion + NOTE: If either of the above two are 0, it will not make an explosion + + target - What to use when destroyed + target2 - What to use when it decides to fire at an enemy + + team - team that does not take damage from this item + "player", + "enemy", + "neutral" + +*/ + +//--------------------------------- +void SP_PAS( gentity_t *base ) +//--------------------------------- +{ + base->classname = "PAS"; + G_SetOrigin( base, base->s.origin ); + G_SetAngles( base, base->s.angles ); + + base->speed = base->s.angles[YAW]; + + base->s.modelindex = G_ModelIndex( "models/items/psgun.glm" ); + base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/items/psgun.glm", base->s.modelindex ); + base->s.radius = 30.0f; + VectorSet( base->s.modelScale, 1.0f, 1.0f, 1.0f ); + + base->rootBone = gi.G2API_GetBoneIndex( &base->ghoul2[base->playerModel], "model_root", qtrue ); + gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_hinge", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL ); + gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_gback", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL ); + gi.G2API_SetBoneAngles( &base->ghoul2[base->playerModel], "bone_barrel", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL ); + + base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" ); + + base->s.eType = ET_GENERAL; + + if ( !base->radius ) + { + base->radius = 512; + } + + if ( base->count == 0 ) + { + // give ammo + base->count = 150; + } + + base->e_UseFunc = useF_pas_use; + + base->damage = 0; // start animation flag + + base->contents = CONTENTS_SHOTCLIP|CONTENTS_CORPSE;//for certain traces + VectorSet( base->mins, -8, -8, 0 ); + VectorSet( base->maxs, 8, 8, 18 ); + + if ( !(base->spawnflags & 1 )) // START_OFF + { + base->nextthink = level.time + 1000; // we aren't starting off, so start working right away + base->e_ThinkFunc = thinkF_pas_think; + } + + // Set up our explosion effect for the ExplodeDeath code.... + base->fxID = G_EffectIndex( "turret/explode" ); + G_EffectIndex( "sparks/spark_exp_nosnd" ); + + if ( !base->health ) + { + base->health = 50; + } + base->max_health = base->health; + + base->takedamage = qtrue; + base->e_PainFunc = painF_TurretPain; + base->e_DieFunc = dieF_turret_die; + + // hack this flag on so that when it calls the turret die code, it will orient the effect up + // HACK + //-------------------------------------- + base->spawnflags |= 2; + + // Use this for our missile effect + RegisterItem( FindItemForWeapon( WP_TURRET )); + base->s.weapon = WP_TURRET; + + base->svFlags |= SVF_NONNPC_ENEMY; + + base->noDamageTeam = TEAM_NEUTRAL; + if ( base->team && base->team[0] ) + { + base->noDamageTeam = (team_t)GetIDForString( TeamTable, base->team ); + base->team = NULL; + } + + gi.linkentity( base ); +} + +//------------------------------------------------------------------------ +qboolean place_portable_assault_sentry( gentity_t *self, vec3_t origin, vec3_t angs ) +//------------------------------------------------------------------------ +{ + vec3_t fwd, pos; + vec3_t mins, maxs; + trace_t tr; + gentity_t *pas; + + VectorSet( maxs, 9, 9, 0 ); + VectorScale( maxs, -1, mins ); + + angs[PITCH] = 0; + angs[ROLL] = 0; + AngleVectors( angs, fwd, NULL, NULL ); + + // and move a consistent distance away from us so we don't have the dumb thing spawning inside of us. + VectorMA( origin, 30, fwd, pos ); + gi.trace( &tr, origin, NULL, NULL, pos, self->s.number, MASK_SHOT ); + + // find the ground + tr.endpos[2] += 20; + VectorCopy( tr.endpos, pos ); + pos[2] -= 64; + + gi.trace( &tr, tr.endpos, mins, maxs, pos, self->s.number, MASK_SHOT ); + + // check for a decent surface, meaning mostly flat...should probably also check surface parms so we don't set us down on lava or something. + if ( !tr.startsolid && !tr.allsolid && tr.fraction < 1.0f && tr.plane.normal[2] > 0.9f && tr.entityNum >= ENTITYNUM_WORLD ) + { + // Then spawn us if it seems cool. + pas = G_Spawn(); + + if ( pas ) + { + VectorCopy( tr.endpos, pas->s.origin ); + SP_PAS( pas ); + + pas->contents |= CONTENTS_PLAYERCLIP; // player placed ones can block players but not npcs + + pas->e_UseFunc = useF_NULL; // placeable ones never need to be used + + // we don't hurt us or anyone who belongs to the same team as us. + if ( self->client ) + { + pas->noDamageTeam = self->client->playerTeam; + } + + G_Sound( self, G_SoundIndex( "sound/player/use_sentry" )); + pas->activator = self; + return qtrue; + } + } + return qfalse; +} + + +//------------- +// ION CANNON +//------------- + + +//---------------------------------------- +void ion_cannon_think( gentity_t *self ) +//---------------------------------------- +{ + if ( self->spawnflags & 2 ) + { + if ( self->count ) + { + // still have bursts left, so keep going + self->count--; + } + else + { + // done with burst, so wait delay amount, plus a random bit + self->nextthink = level.time + ( self->delay + crandom() * self->random ); + self->count = Q_irand(0,5); // 0-5 bursts + + // Not firing this time + return; + } + } + + if ( self->fxID ) + { + vec3_t fwd, org; + mdxaBone_t boltMatrix; + + // Getting the flash bolt here + gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, + self->torsoBolt, + &boltMatrix, self->s.angles, self->s.origin, (cg.time?cg.time:level.time), + NULL, self->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, fwd ); + + G_PlayEffect( self->fxID, org, fwd ); + } + + if ( self->target2 ) + { + // If we have a target2 fire it off in sync with our gun firing + G_UseTargets2( self, self, self->target2 ); + } + + gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 0, 8, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time ); + self->nextthink = level.time + self->wait + crandom() * self->random; +} + +//---------------------------------------------------------------------------------------------- +void ion_cannon_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) +//---------------------------------------------------------------------------------------------- +{ + vec3_t org; + + // dead, so nuke the ghoul model and put in the damage md3 version + if ( self->playerModel >= 0 ) + { + gi.G2API_RemoveGhoul2Model( self->ghoul2, self->playerModel ); + } + self->s.modelindex = self->s.modelindex2; + self->s.modelindex2 = 0; + + // Turn off the thinking of the base & use it's targets + self->e_ThinkFunc = thinkF_NULL; + self->e_UseFunc = useF_NULL; + + if ( self->target ) + { + G_UseTargets( self, attacker ); + } + + // clear my data + self->e_DieFunc = dieF_NULL; + self->takedamage = qfalse; + self->health = 0; + + self->takedamage = qfalse;//stop chain reaction runaway loops + self->s.loopSound = 0; + + // not solid anymore + self->contents = 0; + + VectorCopy( self->currentOrigin, self->s.pos.trBase ); + + VectorCopy( self->currentOrigin, org ); + org[2] += 20; + G_PlayEffect( "env/ion_cannon_explosion", org ); + + if ( self->splashDamage > 0 && self->splashRadius > 0 ) + { + G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, + attacker, MOD_UNKNOWN ); + } + + gi.linkentity( self ); +} + +//---------------------------------------------------------------------------- +void ion_cannon_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +//---------------------------------------------------------------------------- +{ + // toggle + if ( self->e_ThinkFunc == thinkF_NULL ) + { + // start thinking now + self->e_ThinkFunc = thinkF_ion_cannon_think; + self->nextthink = level.time + FRAMETIME; // fires right on being used + } + else + { + self->e_ThinkFunc = thinkF_NULL; + } +} + +/*QUAKED misc_ion_cannon (1 0 0) (-280 -280 0) (280 280 640) START_OFF BURSTS SHIELDED +Huge ion cannon, like the ones at the rebel base on Hoth. + + START_OFF - Starts off + BURSTS - adds more variation, shots come out in bursts + SHIELDED - cannon is shielded, any kind of shot bounces off. + + wait - How fast it shoots (default 1500 ms between shots, can't be less than 500 ms) + random - milliseconds wait variation (default 400 ms...up to plus or minus .4 seconds) + delay - Number of milliseconds between bursts (default 6000 ms, can't be less than 1000 ms, only works when BURSTS checked) + + health - default 2000 + splashDamage - how much damage to do when it dies, must be greater than 0 to actually work + splashRadius - damage radius, must be greater than 0 to actually work + + targetname - Toggles it on/off + target - What to use when destroyed + target2 - What to use when it fires a shot. +*/ +//----------------------------------------------------- +void SP_misc_ion_cannon( gentity_t *base ) +//----------------------------------------------------- +{ + G_SetAngles( base, base->s.angles ); + + G_SetOrigin(base, base->s.origin); + + base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/ion_cannon.glm" ); + base->playerModel = gi.G2API_InitGhoul2Model( base->ghoul2, "models/map_objects/imp_mine/ion_cannon.glm", base->s.modelindex ); + base->s.radius = 320.0f; + VectorSet( base->s.modelScale, 2.0f, 2.0f, 2.0f ); + + base->rootBone = gi.G2API_GetBoneIndex( &base->ghoul2[base->playerModel], "model_root", qtrue ); + base->torsoBolt = gi.G2API_AddBolt( &base->ghoul2[base->playerModel], "*flash02" ); + + // register damage model + base->s.modelindex2 = G_ModelIndex( "models/map_objects/imp_mine/ion_cannon_damage.md3" ); + + base->e_UseFunc = useF_ion_cannon_use; + + // How quickly to fire + if ( base->wait == 0.0f ) + { + base->wait = 1500.0f; + } + else if ( base->wait < 500.0f ) + { + base->wait = 500.0f; + } + + if ( base->random == 0.0f ) + { + base->random = 400.0f; + } + + if ( base->delay == 0 ) + { + base->delay = 6000; + } + else if ( base->delay < 1000 ) + { + base->delay = 1000; + } + + // we only take damage from a heavy weapon class missile + base->flags |= FL_DMG_BY_HEAVY_WEAP_ONLY; + + if ( base->spawnflags & 4 )//shielded + { + base->flags |= FL_SHIELDED; //technically, this would only take damage from a lightsaber, but the other flag just above would cancel that out too. + } + + G_SpawnInt( "health", "2000", &base->health ); + base->e_DieFunc = dieF_ion_cannon_die; + base->takedamage = qtrue; + + // Start Off? + if ( base->spawnflags & 1 ) + { + base->e_ThinkFunc = thinkF_NULL; + } + else + { + // start thinking now, otherwise, we'll wait until we are used + base->e_ThinkFunc = thinkF_ion_cannon_think; + base->nextthink = level.time + base->wait + crandom() * base->random; + } + + // Bursts? + if ( base->spawnflags & 2 ) + { + base->count = Q_irand(0,5); // 0-5 bursts + } + + // precache + base->fxID = G_EffectIndex( "env/ion_cannon" ); + + // Set up our explosion effect for the ExplodeDeath code.... + G_EffectIndex( "env/ion_cannon_explosion" ); + + base->contents = CONTENTS_BODY; + + VectorSet( base->mins, -141.0f, -148.0f, 0.0f ); + VectorSet( base->maxs, 142.0f, 135.0f, 245.0f ); + + gi.linkentity( base ); +} + + +//----------------------------------------------------- +void spotlight_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( self->e_ThinkFunc == thinkF_NULL ) + { + // start thinking now, otherwise, we'll wait until we are used + self->e_ThinkFunc = thinkF_spotlight_think; + self->nextthink = level.time + FRAMETIME; + } + else + { + self->e_ThinkFunc = thinkF_NULL; + self->s.eFlags &= ~EF_ALT_FIRING; + } +} + +//----------------------------------------------------- +void spotlight_think( gentity_t *ent ) +{ + vec3_t dir, end; + trace_t tr; + + // dumb hack flag so that we can draw an interpolated light cone cgame side. + ent->s.eFlags |= EF_ALT_FIRING; + + VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, dir ); + VectorNormalize( dir ); + vectoangles( dir, ent->s.apos.trBase ); + ent->s.apos.trType = TR_INTERPOLATE; + + VectorMA( ent->currentOrigin, 2048, dir, end ); // just pick some max trace distance + gi.trace( &tr, ent->currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, CONTENTS_SOLID ); + + ent->radius = tr.fraction * 2048.0f; + + if ( tr.fraction < 1 ) + { + if ( DistanceSquared( tr.endpos, g_entities[0].currentOrigin ) < 140 * 140 ) + { + // hit player--use target2 + G_UseTargets2( ent, &g_entities[0], ent->target2 ); + +#ifndef FINAL_BUILD + if ( g_developer->integer == PRINT_DEVELOPER ) + { + Com_Printf( S_COLOR_MAGENTA "Spotlight hit player at time: %d!!!\n", level.time ); + } +#endif + } + } + + ent->nextthink = level.time + 50; +} + +//----------------------------------------------------- +void spotlight_link( gentity_t *ent ) +{ + gentity_t *target = 0; + + target = G_Find( target, FOFS(targetname), ent->target ); + + if ( !target ) + { + Com_Printf( S_COLOR_RED "ERROR: spotlight_link: bogus target %s\n", ent->target ); + G_FreeEntity( ent ); + return; + } + + ent->enemy = target; + + // Start Off? + if ( ent->spawnflags & 1 ) + { + ent->e_ThinkFunc = thinkF_NULL; + ent->s.eFlags &= ~EF_ALT_FIRING; + } + else + { + // start thinking now, otherwise, we'll wait until we are used + ent->e_ThinkFunc = thinkF_spotlight_think; + ent->nextthink = level.time + FRAMETIME; + } +} + +/*QUAKED misc_spotlight (1 0 0) (-10 -10 0) (10 10 10) START_OFF +model="models/map_objects/imp_mine/spotlight.md3" +Search spotlight that must be targeted at a func_train or other entity +Uses its target2 when it detects the player + + START_OFF - Starts off + + targetname - Toggles it on/off + target - What to point at + target2 - What to use when detects player +*/ + +//----------------------------------------------------- +void SP_misc_spotlight( gentity_t *base ) +//----------------------------------------------------- +{ + if ( !base->target ) + { + Com_Printf( S_COLOR_RED "ERROR: misc_spotlight must have a target\n" ); + G_FreeEntity( base ); + return; + } + + G_SetAngles( base, base->s.angles ); + G_SetOrigin( base, base->s.origin ); + + base->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/spotlight.md3" ); + + G_SpawnInt( "health", "300", &base->health ); + + // Set up our lightcone effect, though we will have it draw cgame side so it looks better + G_EffectIndex( "env/light_cone" ); + + base->contents = CONTENTS_BODY; + base->e_UseFunc = useF_spotlight_use; + + // the thing we need to target may not have spawned yet, so try back in a bit + base->e_ThinkFunc = thinkF_spotlight_link; + base->nextthink = level.time + 100; + + gi.linkentity( base ); +} + +/*QUAKED misc_panel_turret (0 0 1) (-8 -8 -12) (8 8 16) HEALTH +Creates a turret that, when the player uses a panel, takes control of this turret and adopts the turret view + + HEALTH - gun turret has health and displays a hud with its current health + +"target" - thing to use when player enters the turret view +"target2" - thing to use when player leaves the turret view +"target3" - thing to use when it dies. + + radius - the max yaw range in degrees, (default 90) which means you can move 90 degrees on either side of the start angles. + random - the max pitch range in degrees, (default 60) which means you can move 60 degrees above or below the start angles. + delay - time between shots, in milliseconds (default 200). + damage - amount of damage shots do, (default 50). + speed - missile speed, (default 3000) + + heatlh - how much heatlh the thing has, (default 200) only works if HEALTH is checked, otherwise it can't be destroyed. +*/ + +extern gentity_t *player; +extern qboolean G_ClearViewEntity( gentity_t *ent ); +extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity ); +extern gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse ); + +void panel_turret_shoot( gentity_t *self, vec3_t org, vec3_t dir) +{ + gentity_t *missile = CreateMissile( org, dir, self->speed, 10000, self ); + + missile->classname = "b_proj"; + missile->s.weapon = WP_TIE_FIGHTER; + + VectorSet( missile->maxs, 9, 9, 9 ); + VectorScale( missile->maxs, -1, missile->mins ); + + missile->bounceCount = 0; + + missile->damage = self->damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + G_SoundOnEnt( self, CHAN_AUTO, "sound/movers/objects/ladygun_fire" ); + + VectorMA( org, 32, dir, org ); + org[2] -= 4; + G_PlayEffect( "ships/imp_blastermuzzleflash", org, dir ); +} + +//----------------------------------------- +void misc_panel_turret_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc ) +{ + if ( self->target3 ) + { + G_UseTargets2( self, player, self->target3 ); + } + + // FIXME: might need some other kind of logic or functionality in here?? + G_UseTargets2( self, player, self->target2 ); + G_ClearViewEntity( player ); + cg.overrides.active &= ~CG_OVERRIDE_FOV; + cg.overrides.fov = 0; +} + +//----------------------------------------- +void panel_turret_think( gentity_t *self ) +{ + // Ensure that I am the viewEntity + if ( player && player->client && player->client->ps.viewEntity == self->s.number ) + { + usercmd_t *ucmd = &player->client->usercmd; + + // We are the viewEnt so update our new viewangles based on the sum of our start angles and the ucmd angles + for ( int i = 0; i < 3; i++ ) + { + // convert our base angle to a short, add with the usercmd.angle ( a short ), then switch use back to a real angle + self->s.apos.trBase[i] = AngleNormalize180( SHORT2ANGLE( ucmd->angles[i] + ANGLE2SHORT( self->s.angles[i] ) + self->pos3[i] )); + } + + // Only clamp if we have a PITCH clamp + if ( self->random != 0.0f ) + // Angle clamping -- PITCH + { + if ( self->s.apos.trBase[PITCH] > self->random ) // random is PITCH + { + self->pos3[PITCH] += ANGLE2SHORT( AngleNormalize180( self->random - self->s.apos.trBase[PITCH])); + self->s.apos.trBase[PITCH] = self->random; + } + else if ( self->s.apos.trBase[PITCH] < -self->random ) + { + self->pos3[PITCH] -= ANGLE2SHORT( AngleNormalize180( self->random + self->s.apos.trBase[PITCH])); + self->s.apos.trBase[PITCH] = -self->random; + } + } + + // Only clamp if we have a YAW clamp + if ( self->radius != 0.0f ) + { + float yawDif = AngleSubtract( self->s.apos.trBase[YAW], self->s.angles[YAW] ); + + // Angle clamping -- YAW + if ( yawDif > self->radius ) // radius is YAW + { + self->pos3[YAW] += ANGLE2SHORT( self->radius - yawDif ); + self->s.apos.trBase[YAW] = AngleNormalize180( self->s.angles[YAW] + self->radius ); + } + else if ( yawDif < -self->radius ) // radius is YAW + { + self->pos3[YAW] -= ANGLE2SHORT( self->radius + yawDif ); + self->s.apos.trBase[YAW] = AngleNormalize180( self->s.angles[YAW] - self->radius ); + } + } + + // Let cgame interpolation smooth out the angle changes + self->s.apos.trType = TR_INTERPOLATE; + self->s.pos.trType = TR_INTERPOLATE; // not really moving, but this fixes an interpolation bug in cg_ents. + + // Check for backing out of turret + if ( ( self->useDebounceTime < level.time ) && ((ucmd->buttons & BUTTON_USE) || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove) ) + { + self->useDebounceTime = level.time + 200; + + G_UseTargets2( self, player, self->target2 ); + G_ClearViewEntity( player ); + G_Sound( player, self->soundPos2 ); + + cg.overrides.active &= ~CG_OVERRIDE_FOV; + cg.overrides.fov = 0; + if ( ucmd->upmove > 0 ) + {//stop player from doing anything for a half second after + player->aimDebounceTime = level.time + 500; + } + + // can be drawn +// self->s.eFlags &= ~EF_NODRAW; + } + else + { + // don't draw me when being looked through +// self->s.eFlags |= EF_NODRAW; +// self->s.modelindex = 0; + + // we only need to think when we are being used + self->nextthink = level.time + 50; + + cg.overrides.active |= CG_OVERRIDE_FOV; + cg.overrides.fov = 90; + } + + if ( ucmd->buttons & BUTTON_ATTACK || ucmd->buttons & BUTTON_ALT_ATTACK ) + { + if ( self->attackDebounceTime < level.time ) + { + vec3_t dir, pt; + + AngleVectors( self->s.apos.trBase, dir, NULL, NULL ); + + VectorCopy( self->currentOrigin, pt ); + pt[2] -= 4; + panel_turret_shoot( self, pt, dir ); + + self->attackDebounceTime = level.time + self->delay; + } + } + } +} + +//----------------------------------------- +void panel_turret_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + // really only usable by the player + if ( !activator || !activator->client || activator->s.number ) + { + return; + } + + if ( self->useDebounceTime > level.time ) + { + // can't use it again right away. + return; + } + + if ( self->spawnflags & 1 ) // health...presumably the lady luck gun + { + G_Sound( self, G_SoundIndex( "sound/movers/objects/ladygun_on" )); + } + + self->useDebounceTime = level.time + 200; + + // Compensating for the difference between the players view at the time of use and the start angles that the gun object has + self->pos3[PITCH] = -activator->client->usercmd.angles[PITCH]; + self->pos3[YAW] = -activator->client->usercmd.angles[YAW]; + self->pos3[ROLL] = 0; + + // set me as view entity + G_UseTargets2( self, activator, self->target ); + G_SetViewEntity( activator, self ); + + G_Sound( activator, self->soundPos1 ); + + self->e_ThinkFunc = thinkF_panel_turret_think; +// panel_turret_think( self ); + self->nextthink = level.time + 150; +} + +//----------------------------------------- +void SP_misc_panel_turret( gentity_t *self ) +{ + G_SpawnFloat( "radius", "90", &self->radius ); // yaw + G_SpawnFloat( "random", "60", &self->random ); // pitch + G_SpawnFloat( "speed" , "3000", &self->speed ); + G_SpawnInt( "delay", "200", &self->delay ); + G_SpawnInt( "damage", "50", &self->damage ); + + VectorSet( self->pos3, 0.0f, 0.0f, 0.0f ); + + if ( self->spawnflags & 1 ) // heatlh + { + self->takedamage = qtrue; + self->contents = CONTENTS_SHOTCLIP; + G_SpawnInt( "health", "200", &self->health ); + + self->max_health = self->health; + self->dflags |= DAMAGE_CUSTOM_HUD; // dumb, but we draw a custom hud + G_SoundIndex( "sound/movers/objects/ladygun_on" ); + } + + self->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/ladyluck_gun.md3" ); + + self->soundPos1 = G_SoundIndex( "sound/movers/camera_on.mp3" ); + self->soundPos2 = G_SoundIndex( "sound/movers/camera_off.mp3" ); + + G_SoundIndex( "sound/movers/objects/ladygun_fire" ); + G_EffectIndex("ships/imp_blastermuzzleflash"); + + G_SetOrigin( self, self->s.origin ); + G_SetAngles( self, self->s.angles ); + + VectorSet( self->mins, -8, -8, -12 ); + VectorSet( self->maxs, 8, 8, 0 ); + self->contents = CONTENTS_SOLID; + + self->s.weapon = WP_TURRET; + + RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN )); + gi.linkentity( self ); + + self->e_UseFunc = useF_panel_turret_use; + self->e_DieFunc = dieF_misc_panel_turret_die; +} + +#undef name +#undef name2 +#undef name3 \ No newline at end of file diff --git a/code/game/g_usable.cpp b/code/game/g_usable.cpp new file mode 100644 index 0000000..4a3c2f0 --- /dev/null +++ b/code/game/g_usable.cpp @@ -0,0 +1,251 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "g_local.h" +#include "g_functions.h" + +extern void InitMover( gentity_t *ent ); + +extern gentity_t *G_TestEntityPosition( gentity_t *ent ); +void func_wait_return_solid( gentity_t *self ) +{ + //once a frame, see if it's clear. + self->clipmask = CONTENTS_BODY;//|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP; + if ( !(self->spawnflags&16) || G_TestEntityPosition( self ) == NULL ) + { + gi.SetBrushModel( self, self->model ); + VectorCopy( self->currentOrigin, self->pos1 ); + InitMover( self ); + /* + VectorCopy( self->s.origin, self->s.pos.trBase ); + VectorCopy( self->s.origin, self->currentOrigin ); + */ + //if we moved, we want the *current* origin, not our start origin! + VectorCopy( self->currentOrigin, self->s.pos.trBase ); + gi.linkentity( self ); + self->svFlags &= ~SVF_NOCLIENT; + self->s.eFlags &= ~EF_NODRAW; + self->e_UseFunc = useF_func_usable_use; + self->clipmask = 0; + if ( self->target2 && self->target2[0] ) + { + G_UseTargets2( self, self->activator, self->target2 ); + } + if ( self->s.eFlags & EF_ANIM_ONCE ) + {//Start our anim + self->s.frame = 0; + } + //NOTE: be sure to reset the brushmodel before doing this or else CONTENTS_OPAQUE may not be on when you call this + if ( !(self->spawnflags&1) ) + {//START_OFF doesn't effect area portals + gi.AdjustAreaPortalState( self, qfalse ); + } + } + else + { + self->clipmask = 0; + self->e_ThinkFunc = thinkF_func_wait_return_solid; + self->nextthink = level.time + FRAMETIME; + } +} + +void func_usable_think( gentity_t *self ) +{ + if ( self->spawnflags & 8 ) + { + self->svFlags |= SVF_PLAYER_USABLE; //Replace the usable flag + self->e_UseFunc = useF_func_usable_use; + self->e_ThinkFunc = thinkF_NULL; + } +} + +qboolean G_EntIsRemovableUsable( int entNum ) +{ + gentity_t *ent = &g_entities[entNum]; + if ( ent->classname && !Q_stricmp( "func_usable", ent->classname ) ) + { + if ( !(ent->s.eFlags&EF_SHADER_ANIM) && !(ent->spawnflags&8) && ent->targetname ) + {//not just a shader-animator and not ALWAYS_ON, so it must be removable somehow + return qtrue; + } + } + return qfalse; +} + +void func_usable_use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{//Toggle on and off + if ( other == activator ) + {//directly used by use button trace + if ( (self->spawnflags&32) ) + {//only usable by NPCs + if ( activator->NPC == NULL ) + {//Not an NPC + return; + } + } + } + + G_ActivateBehavior( self, BSET_USE ); + if ( self->s.eFlags & EF_SHADER_ANIM ) + {//animate shader when used + self->s.frame++;//inc frame + if ( self->s.frame > self->endFrame ) + {//wrap around + self->s.frame = 0; + } + if ( self->target && self->target[0] ) + { + G_UseTargets( self, activator ); + } + } + else if ( self->spawnflags & 8 ) + {//ALWAYS_ON + //Remove the ability to use the entity directly + self->svFlags &= ~SVF_PLAYER_USABLE; + //also remove ability to call any use func at all! + self->e_UseFunc = useF_NULL; + + if(self->target && self->target[0]) + { + G_UseTargets(self, activator); + } + + if ( self->wait ) + { + self->e_ThinkFunc = thinkF_func_usable_think; + self->nextthink = level.time + ( self->wait * 1000 ); + } + + return; + } + else if ( !self->count ) + {//become solid again + self->count = 1; + self->activator = activator; + func_wait_return_solid( self ); + } + else + { + //NOTE: MUST do this BEFORE clearing contents, or you may not open the area portal!!! + if ( !(self->spawnflags&1) ) + {//START_OFF doesn't effect area portals + gi.AdjustAreaPortalState( self, qtrue ); + } + self->s.solid = 0; + self->contents = 0; + self->clipmask = 0; + self->svFlags |= SVF_NOCLIENT; + self->s.eFlags |= EF_NODRAW; + self->count = 0; + + if(self->target && self->target[0]) + { + G_UseTargets(self, activator); + } + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = -1; + } +} + +void func_usable_pain(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod,int hitLoc) +{ + if ( self->paintarget ) + { + G_UseTargets2 (self, self->activator, self->paintarget); + } + else + { + GEntity_UseFunc( self, attacker, attacker ); + } +} + +void func_usable_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc) +{ + self->takedamage = qfalse; + GEntity_UseFunc( self, inflictor, attacker ); +} + +/*QUAKED func_usable (0 .5 .8) ? STARTOFF AUTOANIMATE ANIM_ONCE ALWAYS_ON BLOCKCHECK NPC_USE PLAYER_USE INACTIVE +START_OFF - the wall will not be there +AUTOANIMATE - if a model is used it will animate +ANIM_ONCE - When turned on, goes through anim once +ALWAYS_ON - Doesn't toggle on and off when used, just runs usescript and fires target +NPC_ONLY - Only NPCs can directly use this +PLAYER_USE - Player can use it with the use button +BLOCKCHECK - Will not turn on while something is inside it + +A bmodel that just sits there, doing nothing. Can be used for conditional walls and models. +"targetname" - When used, will toggle on and off +"target" Will fire this target every time it is toggled OFF +"target2" Will fire this target every time it is toggled ON +"model2" .md3 model to also draw +"modelAngles" md3 model's angles (in addition to any rotation on the part of the brush entity itself) +"color" constantLight color +"light" constantLight radius +"usescript" script to run when turned on +"deathscript" script to run when turned off +"wait" amount of time before the object is usable again (only valid with ALWAYS_ON flag) +"health" if it has health, it will be used whenever shot at/killed - if you want it to only be used once this way, set health to 1 +"endframe" Will make it animate to next shader frame when used, not turn on/off... set this to number of frames in the shader, minus 1 +"forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level... +*/ + +void SP_func_usable( gentity_t *self ) +{ + gi.SetBrushModel( self, self->model ); + InitMover( self ); + VectorCopy( self->s.origin, self->s.pos.trBase ); + VectorCopy( self->s.origin, self->currentOrigin ); + VectorCopy( self->s.origin, self->pos1 ); + + self->count = 1; + if (self->spawnflags & 1) + { + self->spawnContents = self->contents; // It Navs can temporarly turn it "on" + self->s.solid = 0; + self->contents = 0; + self->clipmask = 0; + self->svFlags |= SVF_NOCLIENT; + self->s.eFlags |= EF_NODRAW; + self->count = 0; + } + + if (self->spawnflags & 2) + { + self->s.eFlags |= EF_ANIM_ALLFAST; + } + + if (self->spawnflags & 4) + {//FIXME: need to be able to do change to something when it's done? Or not be usable until it's done? + self->s.eFlags |= EF_ANIM_ONCE; + } + + self->e_UseFunc = useF_func_usable_use; + + if ( self->health ) + { + self->takedamage = qtrue; + self->e_DieFunc = dieF_func_usable_die; + self->e_PainFunc = painF_func_usable_pain; + } + + if ( self->endFrame > 0 ) + { + self->s.frame = self->startFrame = 0; + self->s.eFlags |= EF_SHADER_ANIM; + } + + gi.linkentity (self); + + int forceVisible = 0; + G_SpawnInt( "forcevisible", "0", &forceVisible ); + if ( forceVisible ) + {//can see these through walls with force sight, so must be broadcast + if ( VectorCompare( self->s.origin, vec3_origin ) ) + {//no origin brush + self->svFlags |= SVF_BROADCAST; + } + self->s.eFlags |= EF_FORCE_VISIBLE; + } +} \ No newline at end of file diff --git a/code/game/g_utils.cpp b/code/game/g_utils.cpp new file mode 100644 index 0000000..660ab74 --- /dev/null +++ b/code/game/g_utils.cpp @@ -0,0 +1,2181 @@ +// g_utils.c -- misc utility functions for game module + +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + +#include "Q3_Interface.h" +#include "g_local.h" +#include "g_functions.h" +#ifdef _XBOX +#include "anims.h" +#include "..\renderer\mdx_format.h" +#include "..\qcommon\cm_public.h" +#include "..\qcommon\cm_local.h" +#endif + +#define ACT_ACTIVE qtrue +#define ACT_INACTIVE qfalse +extern void NPC_UseResponse ( gentity_t *self, gentity_t *user, qboolean useWhenDone ); +extern qboolean PM_CrouchAnim( int anim ); +/* +========================================================================= + +model / sound configstring indexes + +========================================================================= +*/ + +/* +================ +G_FindConfigstringIndex + +================ +*/ +extern void ForceTelepathy( gentity_t *self ); +int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ) { + int i; + char s[MAX_STRING_CHARS]; + + if ( !name || !name[0] ) { + return 0; + } + + for ( i=1 ; is.eventParm = fxID; + + VectorSet( tent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS ); + VectorScale( tent->maxs, -1, tent->mins ); + + VectorCopy( fwd, tent->pos3 ); + + // Assume angles, we'll do a cross product on the other end to finish up + MakeNormalVectors( fwd, tent->pos4, temp ); + gi.linkentity( tent ); +} + +// Play an effect at the origin of the specified entity +//---------------------------- +void G_PlayEffect( int fxID, int entNum, const vec3_t fwd ) +{ + gentity_t *tent; + vec3_t temp; + + tent = G_TempEntity( g_entities[entNum].currentOrigin, EV_PLAY_EFFECT ); + tent->s.eventParm = fxID; + tent->s.otherEntityNum = entNum; + VectorSet( tent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS ); + VectorScale( tent->maxs, -1, tent->mins ); + VectorCopy( fwd, tent->pos3 ); + + // Assume angles, we'll do a cross product on the other end to finish up + MakeNormalVectors( fwd, tent->pos4, temp ); +} + +// Play an effect bolted onto the muzzle of the specified client +//---------------------------- +void G_PlayEffect( const char *name, int clientNum ) +{ + gentity_t *tent; + + tent = G_TempEntity( g_entities[clientNum].currentOrigin, EV_PLAY_MUZZLE_EFFECT ); + tent->s.eventParm = G_EffectIndex( name ); + tent->s.otherEntityNum = clientNum; + VectorSet( tent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS ); + VectorScale( tent->maxs, -1, tent->mins ); +} + +#ifdef _IMMERSION +// Play an effect at a position on an entity. +// Mostly for G_MissileBounceEffect so we can play an effect on the bouncee. +//----------------------------- +void G_PlayEffect( int fxID, int clientNum, const vec3_t origin, const vec3_t fwd ) +{ + gentity_t *tent; + vec3_t temp; + + tent = G_TempEntity( origin, EV_PLAY_EFFECT ); + tent->s.eventParm = fxID; + if ( clientNum != -1 ) + { + tent->s.saberActive = 1; + tent->s.otherEntityNum = clientNum; + } + VectorSet( tent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS ); + VectorScale( tent->maxs, -1, tent->mins ); + VectorCopy( fwd, tent->pos3 ); + + // Assume angles, we'll do a cross product on the other end to finish up + MakeNormalVectors( fwd, tent->pos4, temp ); +} + +//----------------------------- +void G_PlayEffect( const char *name, int clientNum, const vec3_t origin, const vec3_t fwd ) +{ + G_PlayEffect( G_EffectIndex( name ), clientNum, origin, fwd ); +} + +#endif // _IMMERSION +//----------------------------- +void G_PlayEffect( int fxID, const vec3_t origin, const vec3_t axis[3] ) +{ + gentity_t *tent; + + tent = G_TempEntity( origin, EV_PLAY_EFFECT ); + tent->s.eventParm = fxID; + + VectorSet( tent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS ); + VectorScale( tent->maxs, -1, tent->mins ); + + // We can just assume axis[2] from doing a cross product on these. + VectorCopy( axis[0], tent->pos3 ); + VectorCopy( axis[1], tent->pos4 ); +} + +// Effect playing utilities - bolt an effect to a ghoul2 models bolton point +//----------------------------- +void G_PlayEffect( int fxID, const int modelIndex, const int boltIndex, const int entNum, const vec3_t origin, int iLoopTime, qboolean isRelative )//iLoopTime 0 = not looping, 1 for infinite, else duration +{ + gentity_t *tent; + + tent = G_TempEntity( origin, EV_PLAY_EFFECT ); + tent->s.eventParm = fxID; + + tent->s.loopSound = iLoopTime; + tent->s.weapon = isRelative; + + tent->svFlags |=SVF_BROADCAST; + gi.G2API_AttachEnt(&tent->s.boltInfo, &g_entities[entNum].ghoul2[modelIndex], boltIndex, entNum, modelIndex); +} + +//----------------------------- +void G_PlayEffect( const char *name, const vec3_t origin ) +{ + vec3_t up = {0, 0, 1}; + + G_PlayEffect( G_EffectIndex( name ), origin, up ); +} + +//----------------------------- +void G_PlayEffect( const char *name, const vec3_t origin, const vec3_t fwd ) +{ + G_PlayEffect( G_EffectIndex( name ), origin, fwd ); +} + +//----------------------------- +void G_PlayEffect( const char *name, const vec3_t origin, const vec3_t axis[3] ) +{ + G_PlayEffect( G_EffectIndex( name ), origin, axis ); +} + +void G_StopEffect( int fxID, const int modelIndex, const int boltIndex, const int entNum ) +{ + gentity_t *tent; + + tent = G_TempEntity( g_entities[entNum].currentOrigin, EV_STOP_EFFECT ); + tent->s.eventParm = fxID; + tent->svFlags |= SVF_BROADCAST; + gi.G2API_AttachEnt( &tent->s.boltInfo, &g_entities[entNum].ghoul2[modelIndex], boltIndex, entNum, modelIndex ); +} + +void G_StopEffect(const char *name, const int modelIndex, const int boltIndex, const int entNum ) +{ + G_StopEffect( G_EffectIndex( name ), modelIndex, boltIndex, entNum ); +} + +#ifdef _XBOX +//----------------------------- +void G_EntityPosition( int i, vec3_t ret ) +{ + if ( /*g_entities &&*/ i >= 0 && i < MAX_GENTITIES && g_entities[i].inuse) + { + gentity_t *ent = g_entities + i; + + if (ent->bmodel) + { + vec3_t mins, maxs; + clipHandle_t h = CM_InlineModel( ent->s.modelindex ); + CM_ModelBounds( cmg, h, mins, maxs ); + ret[0] = (mins[0] + maxs[0]) / 2 + ent->currentOrigin[0]; + ret[1] = (mins[1] + maxs[1]) / 2 + ent->currentOrigin[1]; + ret[2] = (mins[2] + maxs[2]) / 2 + ent->currentOrigin[2]; + } + else + { + VectorCopy(g_entities[i].currentOrigin, ret); + } + } + else + { + ret[0] = ret[1] = ret[2] = 0; + } +} +#endif + +//===Bypass network for sounds on specific channels==================== + +extern void cgi_S_StartSound( const vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +#include "..\cgame\cg_media.h" //access to cgs +extern qboolean CG_TryPlayCustomSound( vec3_t origin, int entityNum, soundChannel_t channel, const char *soundName, int customSoundSet ); +extern cvar_t *g_timescale; +//NOTE: Do NOT Try to use this before the cgame DLL is valid, it will NOT work! +void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ) +{ + int index = G_SoundIndex( (char *)soundPath ); + + if ( !ent ) + { + return; + } + if ( g_timescale->integer > 50 ) + {//Skip the sound! + return; + } + + cgi_S_UpdateEntityPosition( ent->s.number, ent->currentOrigin ); + if ( cgs.sound_precache[ index ] ) + { + cgi_S_StartSound( NULL, ent->s.number, channel, cgs.sound_precache[ index ] ); + } + else + { + CG_TryPlayCustomSound( NULL, ent->s.number, channel, soundPath, -1 ); + } +} + +void G_SoundIndexOnEnt( gentity_t *ent, soundChannel_t channel, int index ) +{ + if ( !ent ) + { + return; + } + + cgi_S_UpdateEntityPosition( ent->s.number, ent->currentOrigin ); + if ( cgs.sound_precache[ index ] ) + { + cgi_S_StartSound( NULL, ent->s.number, channel, cgs.sound_precache[ index ] ); + } +} + +extern cvar_t *g_skippingcin; +void G_SpeechEvent( gentity_t *self, int event ) +{ + if ( in_camera + && g_skippingcin + && g_skippingcin->integer ) + {//Skipping a cinematic, so skip NPC voice sounds... + return; + } + //update entity pos, too + cgi_S_UpdateEntityPosition( self->s.number, self->currentOrigin ); + switch ( event ) + { + case EV_ANGER1: //Say when acquire an enemy when didn't have one before + case EV_ANGER2: + case EV_ANGER3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*anger%i.wav", event - EV_ANGER1 + 1), CS_COMBAT ); + break; + case EV_VICTORY1: //Say when killed an enemy + case EV_VICTORY2: + case EV_VICTORY3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*victory%i.wav", event - EV_VICTORY1 + 1), CS_COMBAT ); + break; + case EV_CONFUSE1: //Say when confused + case EV_CONFUSE2: + case EV_CONFUSE3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*confuse%i.wav", event - EV_CONFUSE1 + 1), CS_COMBAT ); + break; + case EV_PUSHED1: //Say when pushed + case EV_PUSHED2: + case EV_PUSHED3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*pushed%i.wav", event - EV_PUSHED1 + 1), CS_COMBAT ); + break; + case EV_CHOKE1: //Say when choking + case EV_CHOKE2: + case EV_CHOKE3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*choke%i.wav", event - EV_CHOKE1 + 1), CS_COMBAT ); + break; + case EV_FFWARN: //Warn ally to stop shooting you + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, "*ffwarn.wav", CS_COMBAT ); + break; + case EV_FFTURN: //Turn on ally after being shot by them + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, "*ffturn.wav", CS_COMBAT ); + break; + //extra sounds for ST + case EV_CHASE1: + case EV_CHASE2: + case EV_CHASE3: + if ( !CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*chase%i.wav", event - EV_CHASE1 + 1), CS_EXTRA ) ) + { + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*anger%i.wav", Q_irand(1,3)), CS_COMBAT ); + } + break; + case EV_COVER1: + case EV_COVER2: + case EV_COVER3: + case EV_COVER4: + case EV_COVER5: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*cover%i.wav", event - EV_COVER1 + 1), CS_EXTRA ); + break; + case EV_DETECTED1: + case EV_DETECTED2: + case EV_DETECTED3: + case EV_DETECTED4: + case EV_DETECTED5: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*detected%i.wav", event - EV_DETECTED1 + 1), CS_EXTRA ); + break; + case EV_GIVEUP1: + case EV_GIVEUP2: + case EV_GIVEUP3: + case EV_GIVEUP4: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*giveup%i.wav", event - EV_GIVEUP1 + 1), CS_EXTRA ); + break; + case EV_LOOK1: + case EV_LOOK2: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*look%i.wav", event - EV_LOOK1 + 1), CS_EXTRA ); + break; + case EV_LOST1: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, "*lost1.wav", CS_EXTRA ); + break; + case EV_OUTFLANK1: + case EV_OUTFLANK2: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*outflank%i.wav", event - EV_OUTFLANK1 + 1), CS_EXTRA ); + break; + case EV_ESCAPING1: + case EV_ESCAPING2: + case EV_ESCAPING3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*escaping%i.wav", event - EV_ESCAPING1 + 1), CS_EXTRA ); + break; + case EV_SIGHT1: + case EV_SIGHT2: + case EV_SIGHT3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*sight%i.wav", event - EV_SIGHT1 + 1), CS_EXTRA ); + break; + case EV_SOUND1: + case EV_SOUND2: + case EV_SOUND3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*sound%i.wav", event - EV_SOUND1 + 1), CS_EXTRA ); + break; + case EV_SUSPICIOUS1: + case EV_SUSPICIOUS2: + case EV_SUSPICIOUS3: + case EV_SUSPICIOUS4: + case EV_SUSPICIOUS5: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*suspicious%i.wav", event - EV_SUSPICIOUS1 + 1), CS_EXTRA ); + break; + //extra sounds for Jedi + case EV_COMBAT1: + case EV_COMBAT2: + case EV_COMBAT3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*combat%i.wav", event - EV_COMBAT1 + 1), CS_JEDI ); + break; + case EV_JDETECTED1: + case EV_JDETECTED2: + case EV_JDETECTED3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*jdetected%i.wav", event - EV_JDETECTED1 + 1), CS_JEDI ); + break; + case EV_TAUNT1: + case EV_TAUNT2: + case EV_TAUNT3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*taunt%i.wav", event - EV_TAUNT1 + 1), CS_JEDI ); + break; + case EV_JCHASE1: + case EV_JCHASE2: + case EV_JCHASE3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*jchase%i.wav", event - EV_JCHASE1 + 1), CS_JEDI ); + break; + case EV_JLOST1: + case EV_JLOST2: + case EV_JLOST3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*jlost%i.wav", event - EV_JLOST1 + 1), CS_JEDI ); + break; + case EV_DEFLECT1: + case EV_DEFLECT2: + case EV_DEFLECT3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*deflect%i.wav", event - EV_DEFLECT1 + 1), CS_JEDI ); + break; + case EV_GLOAT1: + case EV_GLOAT2: + case EV_GLOAT3: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, va("*gloat%i.wav", event - EV_GLOAT1 + 1), CS_JEDI ); + break; + case EV_PUSHFAIL: + CG_TryPlayCustomSound( NULL, self->s.number, CHAN_VOICE, "*pushfail.wav", CS_JEDI ); + break; + } +} +//===================================================================== + + + +/* +============= +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 entity after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match) +{ + char *s; + + if(!match || !match[0]) + { + return NULL; + } + + if (!from) + from = g_entities; + else + from++; + +// for ( ; from < &g_entities[globals.num_entities] ; from++) + int i=from-g_entities; + for ( ; i < globals.num_entities ; i++) + { +// if (!from->inuse) + if(!PInUse(i)) + continue; + + from=&g_entities[i]; + s = *(char **) ((byte *)from + fieldofs); + if (!s) + continue; + if (!Q_stricmp (s, match)) + return from; + } + + return NULL; +} + + +/* +============ +G_RadiusList - given an origin and a radius, return all entities that are in use that are within the list +============ +*/ +int G_RadiusList ( vec3_t origin, float radius, gentity_t *ignore, qboolean takeDamage, gentity_t *ent_list[MAX_GENTITIES]) +{ + float dist; + gentity_t *ent; + gentity_t *entityList[MAX_GENTITIES]; + int numListedEntities; + vec3_t mins, maxs; + vec3_t v; + int i, e; + int ent_count = 0; + + if ( radius < 1 ) + { + radius = 1; + } + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = origin[i] - radius; + maxs[i] = origin[i] + radius; + } + + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + radius *= radius; //square for the length squared below + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + + if ((ent == ignore) || !(ent->inuse) || ent->takedamage != takeDamage) + continue; + + // find the distance from the edge of the bounding box + for ( i = 0 ; i < 3 ; i++ ) + { + if ( origin[i] < ent->absmin[i] ) + { + v[i] = ent->absmin[i] - origin[i]; + } else if ( origin[i] > ent->absmax[i] ) + { + v[i] = origin[i] - ent->absmax[i]; + } else + { + v[i] = 0; + } + } + + dist = VectorLengthSquared( v ); + if ( dist >= radius ) + { + continue; + } + + // ok, we are within the radius, add us to the incoming list + ent_list[ent_count] = ent; + ent_count++; + + } + // we are done, return how many we found + return(ent_count); +} + + +/* +============= +G_PickTarget + +Selects a random entity from among the targets +============= +*/ +#define MAXCHOICES 32 + +gentity_t *G_PickTarget (char *targetname) +{ + gentity_t *ent = NULL; + int num_choices = 0; + gentity_t *choice[MAXCHOICES]; + + if (!targetname) + { + gi.Printf("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.Printf("G_PickTarget: target %s not found\n", targetname); + return NULL; + } + + return choice[rand() % num_choices]; +} + +void G_UseTargets2 (gentity_t *ent, gentity_t *activator, const char *string) +{ + gentity_t *t; + +// +// fire targets +// + if (string) + { + if( !stricmp( string, "self") ) + { + t = ent; + if (t->e_UseFunc != useF_NULL) // check can be omitted + { + GEntity_UseFunc(t, ent, activator); + } + + if (!ent->inuse) + { + gi.Printf("entity was removed while using targets\n"); + return; + } + } + else + { + t = NULL; + while ( (t = G_Find (t, FOFS(targetname), (char *) string)) != NULL ) + { + if (t == ent) + { + // gi.Printf ("WARNING: Entity used itself.\n"); + } + if (t->e_UseFunc != useF_NULL) // check can be omitted + { + GEntity_UseFunc(t, ent, activator); + } + + if (!ent->inuse) + { + gi.Printf("entity was removed while using targets\n"); + return; + } + } + } + } +} + +/* +============================== +G_UseTargets + +"activator" should be set to the entity that initiated the firing. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets (gentity_t *ent, gentity_t *activator) +{ +// +// fire targets +// + G_UseTargets2 (ent, activator, ent->target); +} + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos( const 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, "(%4.2f %4.2f %4.2f)", v[0], v[1], v[2]); + + return s; +} + + +/* +=============== +G_SetMovedir + +The editor only specifies a single value for angles (yaw), +but we have special constants to generate an up or down direction. +Angles will be cleared, because it is being used to represent a direction +instead of an orientation. +=============== +*/ +void G_SetMovedir( vec3_t angles, vec3_t movedir ) { + static vec3_t VEC_UP = {0, -1, 0}; + static vec3_t MOVEDIR_UP = {0, 0, 1}; + static vec3_t VEC_DOWN = {0, -2, 0}; + static vec3_t MOVEDIR_DOWN = {0, 0, -1}; + + 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( const vec3_t vec ) { + float yaw; + + if (vec[YAW] == 0 && vec[PITCH] == 0) { + yaw = 0; + } else { + if (vec[PITCH]) { + yaw = ( atan2( vec[YAW], vec[PITCH]) * 180 / M_PI ); + } else if (vec[YAW] > 0) { + yaw = 90; + } else { + yaw = 270; + } + if (yaw < 0) { + yaw += 360; + } + } + + return yaw; +} + + +void G_InitGentity( gentity_t *e, qboolean bFreeG2 ) +{ + e->inuse = qtrue; + SetInUse(e); + e->m_iIcarusID = IIcarusInterface::ICARUS_INVALID; + e->classname = "noclass"; + e->s.number = e - g_entities; + + // remove any ghoul2 models here in case we're reusing + if (bFreeG2 && e->ghoul2.IsValid()) + { + gi.G2API_CleanGhoul2Models(e->ghoul2); + } + //Navigational setups + e->waypoint = WAYPOINT_NONE; + e->lastWaypoint = WAYPOINT_NONE; +} + +/* +================= +G_Spawn + +Either finds a free entity, or allocates a new one. + + The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will +never be used by anything else. + +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. +================= +*/ +extern void G_MassFreeUselessThings( void ); + +gentity_t *G_Spawn( int itr ) +{ + int i, force; + gentity_t *e; + + e = NULL; // shut up warning + i = 0; // shut up warning + for ( force = 0 ; force < 2 ; force++ ) + { + // if we go through all entities and can't find one to free, + // override the normal minimum times before use + e = &g_entities[MAX_CLIENTS]; +// for ( i = MAX_CLIENTS ; iinuse ) +// { +// continue; +// } + for ( i = MAX_CLIENTS ; ifreetime > 2000 && level.time - e->freetime < 1000 ) { + continue; + } + + // reuse this slot + G_InitGentity( e, qtrue ); + return e; + } + e=&g_entities[i]; + if ( i != ENTITYNUM_MAX_NORMAL ) + { + break; + } + } + if ( i == ENTITYNUM_MAX_NORMAL ) + { +#ifndef _XBOX +//#ifndef FINAL_BUILD + e = &g_entities[0]; + +//--------------Use this to dump directly to a file + char buff[256]; + FILE *fp; + + fp = fopen( "c:/nofreeentities.txt", "w" ); + for ( i = 0 ; iclassname ) + { + sprintf( buff, "%d: %s\n", i, e->classname ); + } + + fputs( buff, fp ); + } + fclose( fp ); +/* +//---------------Or use this to dump to the console -- beware though, the console will fill quickly and you probably won't see the full list + for ( i = 0 ; iclassname ) + { + Com_Printf( "%d: %s\n", i, e->classname ); + + } + } +*/ +#endif//FINAL_BUILD + if(itr) + { + G_MassFreeUselessThings(); + return G_Spawn( 0 ); + } + else + { + G_Error( "G_Spawn: no free entities" ); + } + } + + // open up a new slot + globals.num_entities++; + G_InitGentity( e, qtrue ); + return e; +} + +extern void Vehicle_Remove(gentity_t *ent); + +/* +================= +G_FreeEntity + +Marks the entity as free +================= +*/ +extern short max_speeders; +extern char current_speeders; + + +void G_FreeEntity( gentity_t *ed ) { + + if( ed->m_pVehicle && + ed->m_pVehicle->m_pVehicleInfo && + ed->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER) + { + // if this speeder was destroyed by cleanup code, alreadCleaned will be set, so + // don't account for it again, otherwise the death occured somewhere else + if(!ed->m_pVehicle->alreadyCleaned) + current_speeders--; + } + + gi.unlinkentity (ed); // unlink from world + + // Free the Game Element (the entity) and delete the Icarus ID. + Quake3Game()->FreeEntity( ed ); + + /*if ( ed->neverFree ) { + return; + }*/ + + if (ed->wayedge!=0) + { + NAV::WayEdgesNowClear(ed); + } + + + // remove any ghoul2 models here + gi.G2API_CleanGhoul2Models(ed->ghoul2); + + if (ed->client && ed->client->NPC_class == CLASS_VEHICLE) + { + int iVehIndex = BG_VehicleGetIndex( ed->NPC_type ); + + Vehicle_Remove(ed); + + if ( ed->m_pVehicle ) + { + gi.Free( ed->m_pVehicle ); + } + //CVehicleNPC *pVeh = static_cast< CVehicleNPC * >( ed->NPC ); + //delete pVeh; + //gi.Free((char*)ed->NPC-4);//crazy hack for class vtables + } + + //free this stuff now, rather than waiting until the level ends. + if (ed->NPC) + { + gi.Free(ed->NPC); + + if(ed->client->clientInfo.customBasicSoundDir && gi.bIsFromZone(ed->client->clientInfo.customBasicSoundDir, TAG_G_ALLOC)) { + gi.Free(ed->client->clientInfo.customBasicSoundDir); + } + if(ed->client->clientInfo.customCombatSoundDir) { + assert(*(int*)ed->client->clientInfo.customCombatSoundDir != 0xfeeefeee); + gi.Free(ed->client->clientInfo.customCombatSoundDir); + } + if(ed->client->clientInfo.customExtraSoundDir) { + assert(*(int*)ed->client->clientInfo.customExtraSoundDir != 0xfeeefeee); + gi.Free(ed->client->clientInfo.customExtraSoundDir); + } + if(ed->client->clientInfo.customJediSoundDir) { + gi.Free(ed->client->clientInfo.customJediSoundDir); + } + if(ed->client->ps.saber[0].name && gi.bIsFromZone(ed->client->ps.saber[0].name, TAG_G_ALLOC) ) { + gi.Free(ed->client->ps.saber[0].name); + } + if(ed->client->ps.saber[0].model && gi.bIsFromZone(ed->client->ps.saber[0].model, TAG_G_ALLOC) ) { + gi.Free(ed->client->ps.saber[0].model); + } + if(ed->client->ps.saber[1].name && gi.bIsFromZone(ed->client->ps.saber[1].name, TAG_G_ALLOC) ) { + gi.Free(ed->client->ps.saber[1].name); + } + if(ed->client->ps.saber[1].model && gi.bIsFromZone(ed->client->ps.saber[1].model, TAG_G_ALLOC) ) { + gi.Free(ed->client->ps.saber[1].model); + } + + gi.Free(ed->client); + } + + if (ed->soundSet && gi.bIsFromZone(ed->soundSet, TAG_G_ALLOC)) { + gi.Free(ed->soundSet); + } + if (ed->targetname && gi.bIsFromZone(ed->targetname, TAG_G_ALLOC)) { + gi.Free(ed->targetname); + } + if (ed->NPC_targetname && gi.bIsFromZone(ed->NPC_targetname, TAG_G_ALLOC)) { + gi.Free(ed->NPC_targetname); + } + if (ed->NPC_type && gi.bIsFromZone(ed->NPC_type, TAG_G_ALLOC)) { + gi.Free(ed->NPC_type); + } + if (ed->classname && gi.bIsFromZone(ed->classname, TAG_G_ALLOC)) { + gi.Free(ed->classname ); + } + if (ed->message && gi.bIsFromZone(ed->message, TAG_G_ALLOC)) { + gi.Free(ed->message); + } + if (ed->model && gi.bIsFromZone(ed->model, TAG_G_ALLOC)) { + gi.Free(ed->model); + } + +//scripting + if (ed->script_targetname && gi.bIsFromZone(ed->script_targetname, TAG_G_ALLOC)) { + gi.Free(ed->script_targetname); + } + if (ed->cameraGroup && gi.bIsFromZone(ed->cameraGroup, TAG_G_ALLOC)) { + gi.Free(ed->cameraGroup); + } + if (ed->paintarget && gi.bIsFromZone(ed->paintarget, TAG_G_ALLOC)) { + gi.Free(ed->paintarget); + } + if(ed->parms) { + gi.Free(ed->parms); + } + +//Limbs + if (ed->target && gi.bIsFromZone(ed->target , TAG_G_ALLOC)) { + gi.Free(ed->target); + } + if (ed->target2 && gi.bIsFromZone(ed->target2 , TAG_G_ALLOC)) { + gi.Free(ed->target2); + } + if (ed->target3 && gi.bIsFromZone(ed->target3 , TAG_G_ALLOC)) { + gi.Free(ed->target3); + } + if (ed->target4 && gi.bIsFromZone(ed->target4 , TAG_G_ALLOC)) { + gi.Free(ed->target4); + } + if (ed->opentarget) { + gi.Free(ed->opentarget); + } + if (ed->closetarget) { + gi.Free(ed->closetarget); + } + // Free any associated timers + TIMER_Clear(ed->s.number); + + memset (ed, 0, sizeof(*ed)); + ed->s.number = ENTITYNUM_NONE; + ed->classname = "freed"; + ed->freetime = level.time; + ed->inuse = qfalse; + ClearInUse(ed); +} + +/* +================= +G_TempEntity + +Spawns an event entity that will be auto-removed +The origin will be snapped to save net bandwidth, so care +must be taken if the origin is right on a surface (snap towards start vector first) +================= +*/ +gentity_t *G_TempEntity( const vec3_t origin, int event ) { + gentity_t *e; + vec3_t snapped; + + e = G_Spawn(); + e->s.eType = ET_EVENTS + event; + + e->classname = "tempEntity"; + e->eventTime = level.time; + e->freeAfterEvent = qtrue; + + VectorCopy( origin, snapped ); + SnapVector( snapped ); // save network bandwidth + G_SetOrigin( e, snapped ); + + // find cluster for PVS + gi.linkentity( e ); + + return e; +} + + + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +G_KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +void G_KillBox (gentity_t *ent) { + int i, num; + gentity_t *touch[MAX_GENTITIES], *hit; + vec3_t mins, maxs; + + VectorAdd( ent->client->ps.origin, ent->mins, mins ); + VectorAdd( ent->client->ps.origin, ent->maxs, maxs ); + num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; iclient ) { + continue; + } + if ( hit == ent ) { + continue; + } + if ( ent->s.number && hit->client->ps.stats[STAT_HEALTH] <= 0 ) + {//NPC + continue; + } + if ( ent->s.number ) + {//NPC + if ( !(hit->contents&CONTENTS_BODY) ) + { + continue; + } + } + else + {//player + if ( !(hit->contents & ent->contents) ) + { + continue; + } + } + + // nail it + G_Damage ( hit, ent, ent, NULL, NULL, + 100000, DAMAGE_NO_PROTECTION, MOD_UNKNOWN); + } + +} + +//============================================================================== + + +/* +=============== +G_AddEvent + +Adds an event+parm and twiddles the event counter +=============== +*/ +void G_AddEvent( gentity_t *ent, int event, int eventParm ) { + int bits; + + if ( !event ) { + gi.Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number ); + return; + } + +#if 0 // FIXME: allow multiple events on an entity + // if the entity has an event that hasn't expired yet, don't overwrite + // it unless it is identical (repeated footsteps / muzzleflashes / etc ) + if ( ent->s.event && ent->s.event != event ) { + gentity_t *temp; + + // generate a temp entity that references the original entity + gi.Printf( "eventPush\n" ); + + temp = G_Spawn(); + temp->s.eType = ET_EVENT_ONLY; + temp->s.otherEntityNum = ent->s.number; + G_SetOrigin( temp, ent->s.origin ); + G_AddEvent( temp, event, eventParm ); + temp->freeAfterEvent = qtrue; + gi.linkentity( temp ); + return; + } +#endif + + // clients need to add the event in playerState_t instead of entityState_t + if ( !ent->s.number ) //only one client + { +#if 0 + bits = ent->client->ps.externalEvent & EV_EVENT_BITS; + bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS; + ent->client->ps.externalEvent = event | bits; + ent->client->ps.externalEventParm = eventParm; + ent->client->ps.externalEventTime = level.time; +#endif + if ( eventParm > 255 ) + { + if ( event == EV_PAIN ) + {//must have cheated, in undying? + eventParm = 255; + } + else + { + assert( eventParm < 256 ); + } + } + AddEventToPlayerstate( event, eventParm, &ent->client->ps ); + } else { + bits = ent->s.event & EV_EVENT_BITS; + bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS; + ent->s.event = event | bits; + ent->s.eventParm = eventParm; + } + ent->eventTime = level.time; +} + + +/* +============= +G_Sound +============= +*/ +void G_Sound( gentity_t *ent, int soundIndex ) +{ + gentity_t *te; + + te = G_TempEntity( ent->currentOrigin, EV_GENERAL_SOUND ); + te->s.eventParm = soundIndex; +} + +/* +============= +G_Sound +============= +*/ +void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast ) +{ + gentity_t *te; + + te = G_TempEntity( org, EV_GENERAL_SOUND ); + te->s.eventParm = soundIndex; + if ( broadcast ) + { + te->svFlags |= SVF_BROADCAST; + } +} + +/* +============= +G_SoundBroadcast + + Plays sound that can permeate PVS blockage +============= +*/ +void G_SoundBroadcast( gentity_t *ent, int soundIndex ) +{ + gentity_t *te; + + te = G_TempEntity( ent->currentOrigin, EV_GLOBAL_SOUND ); //full volume + te->s.eventParm = soundIndex; + te->svFlags |= SVF_BROADCAST; +} + +//============================================================================== + +#ifdef _IMMERSION +int G_ForceIndex( const char *name, int channel ) +{ + if ( channel >= 0 && channel < FF_CHANNEL_MAX ) + { + // no point in storing a name with a bogus channel + // (registering an effect on multiple channels will eat up memory) + char temp[512]; + sprintf( temp, "%d,%s", channel, name ); + return G_FindConfigstringIndex ( temp, CS_FORCES, MAX_FORCES, qtrue ); + } + + return 0; +} + +gentity_t *G_OwnerClient( gentity_t *ent ) +{ + // This test is not adequate... should check for the following... + // 1) owned entity is touching client entity + // 2) owned entity is a weapon being used by client + for + ( + ; ent + && !ent->client + ; ent = ent->owner + ); + + return ent; +} + +void G_Force( gentity_t *ent, int forceIndex ) +{ + gentity_t *client = G_OwnerClient( ent ); + if ( client ) + { + G_AddEvent( client, EV_ENTITY_FORCE, forceIndex ); + } +} + +void G_ForceArea( gentity_t *ent, int forceIndex ) +{ + gentity_t *te; + + te = G_TempEntity( ent->s.origin, EV_AREA_FORCE ); + te->s.eventParm = forceIndex; +} + +void G_ForceBroadcast( gentity_t *ent, int forceIndex ) +{ + gentity_t *te; + + te = G_TempEntity( ent->s.origin, EV_GLOBAL_FORCE ); + te->s.eventParm = forceIndex; + te->svFlags |= SVF_BROADCAST; +} + +void G_ForceStop( gentity_t *ent, int forceIndex ) +{ + G_AddEvent( ent, EV_FORCE_STOP, forceIndex ); +} +#endif // _IMMERSION + +/* +================ +G_SetOrigin + +Sets the pos trajectory for a fixed position +================ +*/ +void G_SetOrigin( gentity_t *ent, const vec3_t origin ) +{ + VectorCopy( origin, ent->s.pos.trBase ); + if(ent->client) + { + VectorCopy( origin, ent->client->ps.origin ); + VectorCopy( origin, ent->s.origin ); + } + else + { + ent->s.pos.trType = TR_STATIONARY; + } + ent->s.pos.trTime = 0; + ent->s.pos.trDuration = 0; + VectorClear( ent->s.pos.trDelta ); + + VectorCopy( origin, ent->currentOrigin ); + + // clear waypoints + if( ent->client && ent->NPC ) + { + ent->waypoint = 0; + ent->lastWaypoint = 0; + if( NAV::HasPath( ent ) ) + { + NAV::ClearPath( ent ); + } + } + +} + +qboolean G_CheckInSolidTeleport (const vec3_t& teleportPos, gentity_t *self) +{ + trace_t trace; + vec3_t end, mins; + + VectorCopy(teleportPos, end); + end[2] += self->mins[2]; + VectorCopy(self->mins, mins); + mins[2] = 0; + + gi.trace(&trace, teleportPos, mins, self->maxs, end, self->s.number, self->clipmask); + if(trace.allsolid || trace.startsolid) + { + return qtrue; + } + return qfalse; +} + +//=============================================================================== +qboolean G_CheckInSolid (gentity_t *self, qboolean fix) +{ + trace_t trace; + vec3_t end, mins; + + VectorCopy(self->currentOrigin, end); + end[2] += self->mins[2]; + VectorCopy(self->mins, mins); + mins[2] = 0; + + gi.trace(&trace, self->currentOrigin, mins, self->maxs, end, self->s.number, self->clipmask); + if(trace.allsolid || trace.startsolid) + { + return qtrue; + } + +#ifdef _DEBUG + if(trace.fraction < 0.99999713) +#else + if(trace.fraction < 1.0) +#endif + { + if(fix) + {//Put them at end of trace and check again + vec3_t neworg; + + VectorCopy(trace.endpos, neworg); + neworg[2] -= self->mins[2]; + G_SetOrigin(self, neworg); + gi.linkentity(self); + + return G_CheckInSolid(self, qfalse); + } + else + { + return qtrue; + } + } + + return qfalse; +} + +qboolean infront(gentity_t *from, gentity_t *to) +{ + vec3_t angles, dir, forward; + float dot; + + angles[PITCH] = angles[ROLL] = 0; + angles[YAW] = from->s.angles[YAW]; + AngleVectors(angles, forward, NULL, NULL); + + VectorSubtract(to->s.origin, from->s.origin, dir); + VectorNormalize(dir); + + dot = DotProduct(forward, dir); + if(dot < 0.0f) + { + return qfalse; + } + + return qtrue; +} + +void Svcmd_Use_f( void ) +{ + char *cmd1 = gi.argv(1); + + if ( !cmd1 || !cmd1[0] ) + { + //FIXME: warning message + gi.Printf( "'use' takes targetname of ent or 'list' (lists all usable ents)\n" ); + return; + } + else if ( !Q_stricmp("list", cmd1) ) + { + gentity_t *ent; + + gi.Printf("Listing all usable entities:\n"); + + for ( int i = 1; i < ENTITYNUM_WORLD; i++ ) + { + ent = &g_entities[i]; + if ( ent ) + { + if ( ent->targetname && ent->targetname[0] ) + { + if ( ent->e_UseFunc != useF_NULL ) + { + if ( ent->NPC ) + { + gi.Printf( "%s (NPC)\n", ent->targetname ); + } + else + { + gi.Printf( "%s\n", ent->targetname ); + } + } + } + } + } + + gi.Printf("End of list.\n"); + } + else + { + G_UseTargets2( &g_entities[0], &g_entities[0], cmd1 ); + } +} + +//====================================================== + +void G_SetActiveState(char *targetstring, qboolean actState) +{ + gentity_t *target = NULL; + while( NULL != (target = G_Find(target, FOFS(targetname), targetstring)) ) + { + target->svFlags = actState ? (target->svFlags&~SVF_INACTIVE) : (target->svFlags|SVF_INACTIVE); + } +} + +void target_activate_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + G_SetActiveState(self->target, ACT_ACTIVE); +} + +void target_deactivate_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + G_SetActiveState(self->target, ACT_INACTIVE); +} + +//FIXME: make these apply to doors, etc too? +/*QUAKED target_activate (1 0 0) (-4 -4 -4) (4 4 4) +Will set the target(s) to be usable/triggerable +*/ +void SP_target_activate( gentity_t *self ) +{ + G_SetOrigin( self, self->s.origin ); + self->e_UseFunc = useF_target_activate_use; +} + +/*QUAKED target_deactivate (1 0 0) (-4 -4 -4) (4 4 4) +Will set the target(s) to be non-usable/triggerable +*/ +void SP_target_deactivate( gentity_t *self ) +{ + G_SetOrigin( self, self->s.origin ); + self->e_UseFunc = useF_target_deactivate_use; +} + + +//====================================================== + +/* +============== +ValidUseTarget + +Returns whether or not the targeted entity is useable +============== +*/ +qboolean ValidUseTarget( gentity_t *ent ) +{ + if ( ent->e_UseFunc == useF_NULL ) + { + return qfalse; + } + + if ( ent->svFlags & SVF_INACTIVE ) + {//set by target_deactivate + return qfalse; + } + + if ( !(ent->svFlags & SVF_PLAYER_USABLE) ) + {//Check for flag that denotes BUTTON_USE useability + return qfalse; + } + + //FIXME: This is only a temp fix.. + if ( !Q_strncmp( ent->classname, "trigger", 7) ) + { + return qfalse; + } + + return qtrue; +} + +static void DebugTraceForNPC(gentity_t *ent) +{ + trace_t trace; + vec3_t src, dest, vf; + + VectorCopy( ent->client->renderInfo.eyePoint, src ); + + AngleVectors( ent->client->ps.viewangles, vf, NULL, NULL );//ent->client->renderInfo.eyeAngles was cg.refdef.viewangles, basically + //extend to find end of use trace + VectorMA( src, 4096, vf, dest ); + + //Trace ahead to find a valid target + gi.trace( &trace, src, vec3_origin, vec3_origin, dest, ent->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE ); + + if (trace.fraction < 0.99f) + { + gentity_t *found = &g_entities[trace.entityNum]; + + if (found) + { + const char *targetName = found->targetname; + const char *className = found->classname; + + if (targetName == 0) + { + targetName = ""; + } + if (className == 0) + { + className = ""; + } + Com_Printf("found targetname '%s', classname '%s'\n", targetName, className); + } + } +} + +static qboolean G_ValidActivateBehavior (gentity_t* self, int bset) +{ + if ( !self ) + { + return qfalse; + } + + const char *bs_name = self->behaviorSet[bset]; + + if( !(VALIDSTRING( bs_name )) ) + { + return qfalse; + } + + return qtrue; +} + +static qboolean G_IsTriggerUsable(gentity_t* self, gentity_t* other) +{ + if ( self->svFlags & SVF_INACTIVE ) + {//set by target_deactivate + return qfalse; + } + + if( self->noDamageTeam ) + { + if ( other->client->playerTeam != self->noDamageTeam ) + { + return qfalse; + } + } + + + if ( self->spawnflags & 4 ) + {//USE_BUTTON + if ( !other->client ) + { + return qfalse; + } + } + else + { + return qfalse; + } + + if ( self->spawnflags & 2 ) + {//FACING + vec3_t forward; + + if ( other->client ) + { + AngleVectors( other->client->ps.viewangles, forward, NULL, NULL ); + } + else + { + AngleVectors( other->currentAngles, forward, NULL, NULL ); + } + + if ( DotProduct( self->movedir, forward ) < 0.5 ) + {//Not Within 45 degrees + return qfalse; + } + } + + if ((!G_ValidActivateBehavior (self, BSET_USE) && !self->target) || + (self->target && + (stricmp(self->target, "n") == 0 || + (stricmp(self->target, "neveropen") == 0 || + (stricmp(self->target, "run_gran_drop") == 0) || + (stricmp(self->target, "speaker") == 0) || + (stricmp(self->target, "locked") == 0) + )))) + { + return qfalse; + } + + + /* + //NOTE: This doesn't stop you from using it, just delays the use action! + if(self->delay && self->painDebounceTime < (level.time + self->delay) ) + { + return qfalse; + } + */ + + return qtrue; +} + +static qboolean CanUseInfrontOfPartOfLevel(gentity_t* ent ) //originally from VV +{ + int i, num; + gentity_t *touch[MAX_GENTITIES], *hit; + vec3_t mins, maxs; + const vec3_t range = { 40, 40, 52 }; + + if ( !ent->client ) { + return qfalse; + } + + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + + num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->mins, mins ); + VectorAdd( ent->client->ps.origin, ent->maxs, maxs ); + + for ( i=0 ; ie_TouchFunc == touchF_NULL) && (ent->e_TouchFunc == touchF_NULL) ) { + continue; + } + if ( !( hit->contents & CONTENTS_TRIGGER ) ) { + continue; + } + + if ( !gi.EntityContact( mins, maxs, hit ) ) { + continue; + } + + if ( hit->e_TouchFunc != touchF_NULL ) { + switch (hit->e_TouchFunc ) + { + case touchF_Touch_Multi: + if (G_IsTriggerUsable(hit, ent)) + { + return qtrue; + } + continue; + break; + default: + continue; + } + } + } + return qfalse; +} + +#define USE_DISTANCE 64.0f +extern qboolean eweb_can_be_used( gentity_t *self, gentity_t *other, gentity_t *activator ); +qboolean CanUseInfrontOf(gentity_t *ent) +{ + gentity_t *target; + trace_t trace; + vec3_t src, dest, vf; + + if ( ent->s.number && ent->client->NPC_class == CLASS_ATST ) + {//a player trying to get out of his ATST +// GEntity_UseFunc( ent->activator, ent, ent ); + return qfalse; + } + + if (ent->client->ps.viewEntity != ent->s.number) + { + ent = &g_entities[ent->client->ps.viewEntity]; + + if ( !Q_stricmp( "misc_camera", ent->classname ) ) + { // we are in a camera + gentity_t *next = 0; + if ( ent->target2 != NULL ) + { + next = G_Find( NULL, FOFS(targetname), ent->target2 ); + } + if ( next ) + {//found another one + if ( !Q_stricmp( "misc_camera", next->classname ) ) + {//make sure it's another camera + return qtrue; + } + } + else //if ( ent->health > 0 ) + {//I was the last (only?) one, clear out the viewentity + return qfalse; + } + } + } + + if ( !ent->client ) { + return qfalse; + } + + + //FIXME: this does not match where the new accurate crosshair aims... + //cg.refdef.vieworg, basically + VectorCopy( ent->client->renderInfo.eyePoint, src ); + + AngleVectors( ent->client->ps.viewangles, vf, NULL, NULL ); + //extend to find end of use trace + VectorMA( src, USE_DISTANCE, vf, dest ); + + //Trace ahead to find a valid target + gi.trace( &trace, src, vec3_origin, vec3_origin, dest, ent->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE , G2_NOCOLLIDE, 10); + + if ( trace.fraction == 1.0f || trace.entityNum >= ENTITYNUM_WORLD ) + { + return (CanUseInfrontOfPartOfLevel(ent)); + } + + target = &g_entities[trace.entityNum]; + + if ( target && target->client && target->client->NPC_class == CLASS_VEHICLE ) + { + // Attempt to board this vehicle. + return qtrue; + } + //Check for a use command + if (ValidUseTarget( target )) { + if ( target->s.eType == ET_ITEM ) + {//item, see if we could actually pick it up + if ( !BG_CanItemBeGrabbed( &target->s, &ent->client->ps ) ) + {//nope, so don't indicate that we can use it + return qfalse; + } + } + else if ( target->e_UseFunc == useF_misc_atst_use ) + {//drivable AT-ST from JK2 + if ( ent->client->ps.groundEntityNum != target->s.number ) + {//must be standing on it to use it + return qfalse; + } + } + else if ( target->NPC!=NULL && target->health<=0 ) + { + return qfalse; + } + else if ( target->e_UseFunc == useF_eweb_use ) + { + if ( !eweb_can_be_used( target, ent, ent ) ) + { + return qfalse; + } + } + return qtrue; + } + + if ( target->client + && target->client->ps.pm_type < PM_DEAD + && target->NPC!=NULL + && target->client->playerTeam + && (target->client->playerTeam == ent->client->playerTeam || target->client->playerTeam == TEAM_NEUTRAL) + && !(target->NPC->scriptFlags&SCF_NO_RESPONSE) + && G_ValidActivateBehavior (target, BSET_USE)) + { + return qtrue; + } + + if (CanUseInfrontOfPartOfLevel(ent)) { + return qtrue; + } + + return qfalse; +} + +/* +============== +TryUse + +Try and use an entity in the world, directly ahead of us +============== +*/ + + +void TryUse( gentity_t *ent ) +{ + gentity_t *target; + trace_t trace; + vec3_t src, dest, vf; + + if (ent->s.number == 0 && g_npcdebug->integer == 1) + { + DebugTraceForNPC(ent); + } + + if ( ent->s.number == 0 && ent->client->NPC_class == CLASS_ATST ) + {//a player trying to get out of his ATST + GEntity_UseFunc( ent->activator, ent, ent ); + return; + } + + // TODO: turo-boost. +/* if ( ent->client->ps.vehicleIndex != VEHICLE_NONE ) + {//in a vehicle, use key makes you turbo-boost + return; + }*/ + + //FIXME: this does not match where the new accurate crosshair aims... + //cg.refdef.vieworg, basically + VectorCopy( ent->client->renderInfo.eyePoint, src ); + + AngleVectors( ent->client->ps.viewangles, vf, NULL, NULL );//ent->client->renderInfo.eyeAngles was cg.refdef.viewangles, basically + //extend to find end of use trace + VectorMA( src, USE_DISTANCE, vf, dest ); + + //Trace ahead to find a valid target + gi.trace( &trace, src, vec3_origin, vec3_origin, dest, ent->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE , G2_NOCOLLIDE, 10); + + if ( trace.fraction == 1.0f || trace.entityNum >= ENTITYNUM_WORLD ) + { + //TODO: Play a failure sound + /* + if ( ent->s.number == 0 ) + {//if nothing else, try the force telepathy power + ForceTelepathy( ent ); + } + */ + return; + } + + target = &g_entities[trace.entityNum]; + + if ( target && target->client && target->client->NPC_class == CLASS_VEHICLE ) + { + // Attempt to board this vehicle. + target->m_pVehicle->m_pVehicleInfo->Board( target->m_pVehicle, ent ); + + return; + } + + //Check for a use command + if ( ValidUseTarget( target ) ) + { + NPC_SetAnim( ent, SETANIM_TORSO, BOTH_BUTTON_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + /* + if ( !VectorLengthSquared( ent->client->ps.velocity ) && !PM_CrouchAnim( ent->client->ps.legsAnim ) ) + { + NPC_SetAnim( ent, SETANIM_LEGS, BOTH_BUTTON_HOLD, SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD ); + } + */ + //ent->client->ps.weaponTime = ent->client->ps.torsoAnimTimer; + GEntity_UseFunc( target, ent, ent ); + return; + } + else if ( target->client + && target->client->ps.pm_type < PM_DEAD + && target->NPC!=NULL + && target->client->playerTeam + && (target->client->playerTeam == ent->client->playerTeam || target->client->playerTeam == TEAM_NEUTRAL) + && !(target->NPC->scriptFlags&SCF_NO_RESPONSE) ) + { + NPC_UseResponse ( target, ent, qfalse ); + return; + } + /* + if ( ent->s.number == 0 ) + {//if nothing else, try the force telepathy power + ForceTelepathy( ent ); + } + */ +} + +extern int killPlayerTimer; +void G_ChangeMap (const char *mapname, const char *spawntarget, qboolean hub) +{ +// gi.Printf("Loading..."); + //ignore if player is dead + if (g_entities[0].client->ps.pm_type == PM_DEAD) + return; + if ( killPlayerTimer ) + {//can't go to next map if your allies have turned on you + return; + } + + if (mapname[0] == '+') //fire up the menu instead + { + // Flush sound memory to make room for any UI loading that doesn't happen + // until the end of the level: + extern int SND_FreeOldestSound( void ); + SND_FreeOldestSound(); + + gi.SendConsoleCommand( va("uimenu %s\n", mapname+1) ); + gi.cvar_set("skippingCinematic", "0"); + gi.cvar_set("timescale", "1"); + return; + } + + if ( spawntarget == NULL ) { + spawntarget = ""; //prevent it from becoming "(null)" + } + if ( hub == qtrue ) + { + gi.SendConsoleCommand( va("loadtransition %s %s\n", mapname, spawntarget) ); + } + else + { + gi.SendConsoleCommand( va("maptransition %s %s\n", mapname, spawntarget) ); + } +} + +qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs ) +{ + for(int i = 0; i < 3; i++ ) + { + if ( point[i] < mins[i] ) + { + return qfalse; + } + if ( point[i] > maxs[i] ) + { + return qfalse; + } + } + + return qtrue; +} + +qboolean G_BoxInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs, const vec3_t boundsMins, const vec3_t boundsMaxs ) +{ + vec3_t boxMins; + vec3_t boxMaxs; + + VectorAdd( point, mins, boxMins ); + VectorAdd( point, maxs, boxMaxs ); + + if(boxMaxs[0]>boundsMaxs[0]) + return qfalse; + + if(boxMaxs[1]>boundsMaxs[1]) + return qfalse; + + if(boxMaxs[2]>boundsMaxs[2]) + return qfalse; + + if(boxMins[0]currentAngles ); + VectorCopy( angles, ent->s.angles ); + VectorCopy( angles, ent->s.apos.trBase ); +} + +qboolean G_ClearTrace( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int ignore, int clipmask ) +{ + static trace_t tr; + + gi.trace( &tr, start, mins, maxs, end, ignore, clipmask ); + + if ( tr.allsolid || tr.startsolid || tr.fraction < 1.0 ) + { + return qfalse; + } + + return qtrue; +} + +extern void CG_TestLine( vec3_t start, vec3_t end, int time, unsigned int color, int radius); +void G_DebugLine(vec3_t A, vec3_t B, int duration, int color, qboolean deleteornot) +{ + /* + gentity_t *tent = G_TempEntity( A, EV_DEBUG_LINE ); + VectorCopy(B, tent->s.origin2 ); + tent->s.time = duration; // Pause + tent->s.time2 = color; // Color + tent->s.weapon = 1; // Dimater + tent->freeAfterEvent = deleteornot; + */ + + CG_TestLine( A, B, duration, color, 1 ); +} + +qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ) +{ + trace_t tr; + vec3_t start, end; + + VectorCopy( point, start ); + + for ( int i = 0; i < 3; i++ ) + { + VectorCopy( start, end ); + end[i] += mins[i]; + gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, clipmask ); + if ( tr.allsolid || tr.startsolid ) + { + return qfalse; + } + if ( tr.fraction < 1.0 ) + { + VectorCopy( start, end ); + end[i] += maxs[i]-(mins[i]*tr.fraction); + gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, clipmask ); + if ( tr.allsolid || tr.startsolid ) + { + return qfalse; + } + if ( tr.fraction < 1.0 ) + { + return qfalse; + } + VectorCopy( end, start ); + } + } + //expanded it, now see if it's all clear + gi.trace( &tr, start, mins, maxs, start, ignore, clipmask ); + if ( tr.allsolid || tr.startsolid ) + { + return qfalse; + } + VectorCopy( start, point ); + return qtrue; +} +/* +Ghoul2 Insert Start +*/ + +void removeBoltSurface( gentity_t *ent) +{ + gentity_t *hitEnt = &g_entities[ent->cantHitEnemyCounter]; + + // check first to be sure the bolt is still there on the model + if ((hitEnt->ghoul2.size() > ent->damage) && + (hitEnt->ghoul2[ent->damage].mModelindex != -1) && + (hitEnt->ghoul2[ent->damage].mSlist.size() > ent->aimDebounceTime) && + (hitEnt->ghoul2[ent->damage].mSlist[ent->aimDebounceTime].surface != -1) && + (hitEnt->ghoul2[ent->damage].mSlist[ent->aimDebounceTime].offFlags == G2SURFACEFLAG_GENERATED)) + { + // remove the bolt + gi.G2API_RemoveBolt(&hitEnt->ghoul2[ent->damage], ent->attackDebounceTime); + // now remove a surface if there is one + if (ent->aimDebounceTime != -1) + { + gi.G2API_RemoveSurface(&hitEnt->ghoul2[ent->damage], ent->aimDebounceTime); + } + } + // we are done with this entity. + G_FreeEntity(ent); +} + +void G_SetBoltSurfaceRemoval( const int entNum, const int modelIndex, const int boltIndex, const int surfaceIndex , float duration ) { + gentity_t *e; + vec3_t snapped = {0,0,0}; + + e = G_Spawn(); + + e->classname = "BoltRemoval"; + e->cantHitEnemyCounter = entNum; + e->damage = modelIndex; + e->attackDebounceTime = boltIndex; + e->aimDebounceTime = surfaceIndex; + + G_SetOrigin( e, snapped ); + + // find cluster for PVS + gi.linkentity( e ); + + e->nextthink = level.time + duration; + e->e_ThinkFunc = thinkF_removeBoltSurface; + +} + +/* +Ghoul2 Insert End +*/ + diff --git a/code/game/g_vehicleLoad.cpp b/code/game/g_vehicleLoad.cpp new file mode 100644 index 0000000..1453519 --- /dev/null +++ b/code/game/g_vehicleLoad.cpp @@ -0,0 +1,432 @@ +//g_vehicleLoad.cpp +// leave this line at the top for all NPC_xxxx.cpp files... +#include "g_headers.h" +#include "q_shared.h" +#include "anims.h" +#include "g_vehicles.h" + +extern qboolean G_ParseLiteral( const char **data, const char *string ); +extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum ); + +vehicleInfo_t g_vehicleInfo[MAX_VEHICLES]; +int numVehicles = 1;//first one is null/default + +typedef enum { + VF_INT, + VF_FLOAT, + VF_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + VF_VECTOR, + VF_BOOL, + VF_VEHTYPE, + VF_ANIM +} vehFieldType_t; + +typedef struct +{ + char *name; + int ofs; + vehFieldType_t type; +} vehField_t; + +vehField_t vehFields[VEH_PARM_MAX] = +{ + {"name", VFOFS(name), VF_LSTRING}, //unique name of the vehicle + + //general data + {"type", VFOFS(type), VF_VEHTYPE}, //what kind of vehicle + + {"numHands", VFOFS(numHands), VF_INT}, //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + {"lookPitch", VFOFS(lookPitch), VF_FLOAT}, //How far you can look up and down off the forward of the vehicle + {"lookYaw", VFOFS(lookYaw), VF_FLOAT}, //How far you can look left and right off the forward of the vehicle + {"length", VFOFS(length), VF_FLOAT}, //how long it is - used for body length traces when turning/moving? + {"width", VFOFS(width), VF_FLOAT}, //how wide it is - used for body length traces when turning/moving? + {"height", VFOFS(height), VF_FLOAT}, //how tall it is - used for body length traces when turning/moving? + {"centerOfGravity", VFOFS(centerOfGravity), VF_VECTOR},//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats + {"speedMax", VFOFS(speedMax), VF_FLOAT}, //top speed + {"turboSpeed", VFOFS(turboSpeed), VF_FLOAT}, //turbo speed + {"speedMin", VFOFS(speedMin), VF_FLOAT}, //if < 0, can go in reverse + {"speedIdle", VFOFS(speedIdle), VF_FLOAT}, //what speed it drifts to when no accel/decel input is given + {"accelIdle", VFOFS(accelIdle), VF_FLOAT}, //if speedIdle > 0, how quickly it goes up to that speed + {"acceleration", VFOFS(acceleration), VF_FLOAT}, //when pressing on accelerator + {"decelIdle", VFOFS(decelIdle), VF_FLOAT}, //when giving no input, how quickly it drops to speedIdle + {"strafePerc", VFOFS(strafePerc), VF_FLOAT}, //multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + {"bankingSpeed", VFOFS(bankingSpeed), VF_FLOAT}, //how quickly it pitches and rolls (not under player control) + {"pitchLimit", VFOFS(pitchLimit), VF_FLOAT}, //how far it can roll forward or backward + {"rollLimit", VFOFS(rollLimit), VF_FLOAT}, //how far it can roll to either side + {"braking", VFOFS(braking), VF_FLOAT}, //when pressing on decelerator + {"turningSpeed", VFOFS(turningSpeed), VF_FLOAT}, //how quickly you can turn + {"turnWhenStopped", VFOFS(turnWhenStopped), VF_BOOL},//whether or not you can turn when not moving + {"traction", VFOFS(traction), VF_FLOAT}, //how much your command input affects velocity + {"friction", VFOFS(friction), VF_FLOAT}, //how much velocity is cut on its own + {"maxSlope", VFOFS(maxSlope), VF_FLOAT}, //the max slope that it can go up with control + + //durability stats + {"mass", VFOFS(mass), VF_INT}, //for momentum and impact force (player mass is 10) + {"armor", VFOFS(armor), VF_INT}, //total points of damage it can take + {"toughness", VFOFS(toughness), VF_FLOAT}, //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + {"malfunctionArmorLevel", VFOFS(malfunctionArmorLevel), VF_INT},//when armor drops to or below this point, start malfunctioning + + //visuals & sounds + {"model", VFOFS(model), VF_LSTRING}, //what model to use - if make it an NPC's primary model, don't need this? + {"skin", VFOFS(skin), VF_LSTRING}, //what skin to use - if make it an NPC's primary model, don't need this? + {"riderAnim", VFOFS(riderAnim), VF_ANIM}, //what animation the rider uses + {"gunswivelBone", VFOFS(gunswivelBone), VF_LSTRING},//gun swivel bones + {"lFinBone", VFOFS(lFinBone), VF_LSTRING}, //left fin bone + {"rFinBone", VFOFS(rFinBone), VF_LSTRING}, //right fin bone + {"lExhaustTag", VFOFS(lExhaustTag), VF_LSTRING}, //left exhaust tag + {"rExhaustTag", VFOFS(rExhaustTag), VF_LSTRING}, //right exhaust tag + + {"soundOn", VFOFS(soundOn), VF_LSTRING}, //sound to play when get on it + {"soundLoop", VFOFS(soundLoop), VF_LSTRING}, //sound to loop while riding it + {"soundOff", VFOFS(soundOff), VF_LSTRING}, //sound to play when get off + {"exhaustFX", VFOFS(exhaustFX), VF_LSTRING}, //exhaust effect, played from "*exhaust" bolt(s) + {"trailFX", VFOFS(trailFX), VF_LSTRING}, //trail effect, played from "*trail" bolt(s) + {"impactFX", VFOFS(impactFX), VF_LSTRING}, //impact effect, for when it bumps into something + {"explodeFX", VFOFS(explodeFX), VF_LSTRING}, //explosion effect, for when it blows up (should have the sound built into explosion effect) + {"wakeFX", VFOFS(wakeFX), VF_LSTRING}, //effect it makes when going across water + + //other misc stats + {"gravity", VFOFS(gravity), VF_INT}, //normal is 800 + {"hoverHeight", VFOFS(hoverHeight), VF_FLOAT}, //if 0, it's a ground vehicle + {"hoverStrength", VFOFS(hoverStrength), VF_FLOAT}, //how hard it pushes off ground when less than hover height... causes "bounce", like shocks + {"waterProof", VFOFS(waterProof), VF_BOOL}, //can drive underwater if it has to + {"bouyancy", VFOFS(bouyancy), VF_FLOAT}, //when in water, how high it floats (1 is neutral bouyancy) + {"fuelMax", VFOFS(fuelMax), VF_INT}, //how much fuel it can hold (capacity) + {"fuelRate", VFOFS(fuelRate), VF_INT}, //how quickly is uses up fuel + {"visibility", VFOFS(visibility), VF_INT}, //for sight alerts + {"loudness", VFOFS(loudness), VF_INT}, //for sound alerts + {"explosionRadius", VFOFS(explosionRadius), VF_FLOAT},//range of explosion + {"explosionDamage", VFOFS(explosionDamage), VF_INT},//damage of explosion + + //new stuff + {"maxPassengers", VFOFS(maxPassengers), VF_INT}, // The max number of passengers this vehicle may have (Default = 0). + {"hideRider", VFOFS(hideRider), VF_BOOL }, // rider (and passengers?) should not be drawn + {"killRiderOnDeath", VFOFS(killRiderOnDeath), VF_BOOL },//if rider is on vehicle when it dies, they should die + {"flammable", VFOFS(flammable), VF_BOOL }, //whether or not the vehicle should catch on fire before it explodes + {"explosionDelay", VFOFS(explosionDelay), VF_INT}, //how long the vehicle should be on fire/dying before it explodes + //camera stuff + {"cameraOverride", VFOFS(cameraOverride), VF_BOOL }, //override the third person camera with the below values - normal is 0 (off) + {"cameraRange", VFOFS(cameraRange), VF_FLOAT}, //how far back the camera should be - normal is 80 + {"cameraVertOffset", VFOFS(cameraVertOffset), VF_FLOAT},//how high over the vehicle origin the camera should be - normal is 16 + {"cameraHorzOffset", VFOFS(cameraHorzOffset), VF_FLOAT},//how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + {"cameraPitchOffset", VFOFS(cameraPitchOffset), VF_FLOAT},//a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + {"cameraFOV", VFOFS(cameraFOV), VF_FLOAT}, //third person camera FOV, default is 80 + {"cameraAlpha", VFOFS(cameraAlpha), VF_BOOL }, //fade out the vehicle if it's in the way of the crosshair +}; + +stringID_table_t VehicleTable[VH_NUM_VEHICLES+1] = +{ + ENUM2STRING(VH_WALKER), //something you ride inside of, it walks like you, like an AT-ST + ENUM2STRING(VH_FIGHTER), //something you fly inside of, like an X-Wing or TIE fighter + ENUM2STRING(VH_SPEEDER), //something you ride on that hovers, like a speeder or swoop + ENUM2STRING(VH_ANIMAL), //animal you ride on top of that walks, like a tauntaun + ENUM2STRING(VH_FLIER), //animal you ride on top of that flies, like a giant mynoc? + "", -1 +}; + +void G_VehicleSetDefaults( vehicleInfo_t *vehicle ) +{ + vehicle->name = "default"; //unique name of the vehicle +/* + //general data + vehicle->type = VH_SPEEDER; //what kind of vehicle + //FIXME: no saber or weapons if numHands = 2, should switch to speeder weapon, no attack anim on player + vehicle->numHands = 2; //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + vehicle->lookPitch = 35; //How far you can look up and down off the forward of the vehicle + vehicle->lookYaw = 5; //How far you can look left and right off the forward of the vehicle + vehicle->length = 0; //how long it is - used for body length traces when turning/moving? + vehicle->width = 0; //how wide it is - used for body length traces when turning/moving? + vehicle->height = 0; //how tall it is - used for body length traces when turning/moving? + VectorClear( vehicle->centerOfGravity );//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats - note: these are DESIRED speed, not actual current speed/velocity + vehicle->speedMax = VEH_DEFAULT_SPEED_MAX; //top speed + vehicle->turboSpeed = 0; //turboBoost + vehicle->speedMin = 0; //if < 0, can go in reverse + vehicle->speedIdle = 0; //what speed it drifts to when no accel/decel input is given + vehicle->accelIdle = 0; //if speedIdle > 0, how quickly it goes up to that speed + vehicle->acceleration = VEH_DEFAULT_ACCEL; //when pressing on accelerator (1/2 this when going in reverse) + vehicle->decelIdle = VEH_DEFAULT_DECEL; //when giving no input, how quickly it desired speed drops to speedIdle + vehicle->strafePerc = VEH_DEFAULT_STRAFE_PERC;//multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + vehicle->bankingSpeed = VEH_DEFAULT_BANKING_SPEED; //how quickly it pitches and rolls (not under player control) + vehicle->rollLimit = VEH_DEFAULT_ROLL_LIMIT; //how far it can roll to either side + vehicle->pitchLimit = VEH_DEFAULT_PITCH_LIMIT; //how far it can pitch forward or backward + vehicle->braking = VEH_DEFAULT_BRAKING; //when pressing on decelerator (backwards) + vehicle->turningSpeed = VEH_DEFAULT_TURNING_SPEED; //how quickly you can turn + vehicle->turnWhenStopped = qfalse; //whether or not you can turn when not moving + vehicle->traction = VEH_DEFAULT_TRACTION; //how much your command input affects velocity + vehicle->friction = VEH_DEFAULT_FRICTION; //how much velocity is cut on its own + vehicle->maxSlope = VEH_DEFAULT_MAX_SLOPE; //the max slope that it can go up with control + + //durability stats + vehicle->mass = VEH_DEFAULT_MASS; //for momentum and impact force (player mass is 10) + vehicle->armor = VEH_DEFAULT_MAX_ARMOR; //total points of damage it can take + vehicle->toughness = VEH_DEFAULT_TOUGHNESS; //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + vehicle->malfunctionArmorLevel = 0; //when armor drops to or below this point, start malfunctioning + + //visuals & sounds + vehicle->model = "swoop"; //what model to use - if make it an NPC's primary model, don't need this? + vehicle->modelIndex = 0; //set internally, not until this vehicle is spawned into the level + vehicle->skin = NULL; //what skin to use - if make it an NPC's primary model, don't need this? + vehicle->riderAnim = BOTH_GUNSIT1; //what animation the rider uses + vehicle->gunswivelBone = NULL; //gun swivel bones + vehicle->lFinBone = NULL; //left fin bone + vehicle->rFinBone = NULL; //right fin bone + vehicle->lExhaustTag = NULL; //left exhaust tag + vehicle->rExhaustTag = NULL; //right exhaust tag + + vehicle->soundOn = NULL; //sound to play when get on it + vehicle->soundLoop = NULL; //sound to loop while riding it + vehicle->soundOff = NULL; //sound to play when get off + vehicle->exhaustFX = NULL; //exhaust effect, played from "*exhaust" bolt(s) + vehicle->trailFX = NULL; //trail effect, played from "*trail" bolt(s) + vehicle->impactFX = NULL; //explosion effect, for when it blows up (should have the sound built into explosion effect) + vehicle->explodeFX = NULL; //explosion effect, for when it blows up (should have the sound built into explosion effect) + vehicle->wakeFX = NULL; //effect itmakes when going across water + + //other misc stats + vehicle->gravity = VEH_DEFAULT_GRAVITY; //normal is 800 + vehicle->hoverHeight = 0; //if 0, it's a ground vehicle + vehicle->hoverStrength = 0;//how hard it pushes off ground when less than hover height... causes "bounce", like shocks + vehicle->waterProof = qtrue; //can drive underwater if it has to + vehicle->bouyancy = 1.0f; //when in water, how high it floats (1 is neutral bouyancy) + vehicle->fuelMax = 1000; //how much fuel it can hold (capacity) + vehicle->fuelRate = 1; //how quickly is uses up fuel + vehicle->visibility = VEH_DEFAULT_VISIBILITY; //radius for sight alerts + vehicle->loudness = VEH_DEFAULT_LOUDNESS; //radius for sound alerts + vehicle->explosionRadius = VEH_DEFAULT_EXP_RAD; + vehicle->explosionDamage = VEH_DEFAULT_EXP_DMG; + + //new stuff + vehicle->maxPassengers = 0; + vehicle->hideRider = qfalse; // rider (and passengers?) should not be drawn + vehicle->killRiderOnDeath = qfalse; //if rider is on vehicle when it dies, they should die + vehicle->flammable = qfalse; //whether or not the vehicle should catch on fire before it explodes + vehicle->explosionDelay = 0; //how long the vehicle should be on fire/dying before it explodes + //camera stuff + vehicle->cameraOverride = qfalse; //whether or not to use all of the following 3rd person camera override values + vehicle->cameraRange = 0.0f; //how far back the camera should be - normal is 80 + vehicle->cameraVertOffset = 0.0f; //how high over the vehicle origin the camera should be - normal is 16 + vehicle->cameraHorzOffset = 0.0f; //how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + vehicle->cameraPitchOffset = 0.0f; //a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + vehicle->cameraFOV = 0.0f; //third person camera FOV, default is 80 + vehicle->cameraAlpha = qfalse; //fade out the vehicle if it's in the way of the crosshair +*/ +} + +void G_VehicleClampData( vehicleInfo_t *vehicle ) +{//sanity check and clamp the vehicle's data + int i; + + for ( i = 0; i < 3; i++ ) + { + if ( vehicle->centerOfGravity[i] > 1.0f ) + { + vehicle->centerOfGravity[i] = 1.0f; + } + else if ( vehicle->centerOfGravity[i] < -1.0f ) + { + vehicle->centerOfGravity[i] = -1.0f; + } + } + + // Validate passenger max. + if ( vehicle->maxPassengers > VEH_MAX_PASSENGERS ) + { + vehicle->maxPassengers = VEH_MAX_PASSENGERS; + } + else if ( vehicle->maxPassengers < 0 ) + { + vehicle->maxPassengers = 0; + } +} + + +static void G_ParseVehicleParms( vehicleInfo_t *vehicle, const char **holdBuf ) +{ + const char *token; + const char *value; + int i; + vec3_t vec; + byte *b = (byte *)vehicle; + int _iFieldsRead = 0; + vehicleType_t vehType; + + while ( holdBuf ) + { + token = COM_ParseExt( holdBuf, qtrue ); + if ( !token[0] ) + { + gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing vehicles!\n" ); + return; + } + + if ( !Q_stricmp( token, "}" ) ) // End of data for this vehicle + { + break; + } + + // Loop through possible parameters + for ( i = 0; i < VEH_PARM_MAX; i++ ) + { + if ( vehFields[i].name && !Q_stricmp( vehFields[i].name, token ) ) + { + // found it + if ( COM_ParseString( holdBuf, &value ) ) + { + continue; + } + switch( vehFields[i].type ) + { + case VF_INT: + *(int *)(b+vehFields[i].ofs) = atoi(value); + break; + case VF_FLOAT: + *(float *)(b+vehFields[i].ofs) = atof(value); + break; + case VF_LSTRING: // string on disk, pointer in memory, TAG_LEVEL + *(char **)(b+vehFields[i].ofs) = G_NewString( value ); + break; + case VF_VECTOR: + _iFieldsRead = sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + assert(_iFieldsRead==3 ); + if (_iFieldsRead!=3) + { + gi.Printf (S_COLOR_YELLOW"G_ParseVehicleParms: VEC3 sscanf() failed to read 3 floats ('angle' key bug?)\n"); + } + ((float *)(b+vehFields[i].ofs))[0] = vec[0]; + ((float *)(b+vehFields[i].ofs))[1] = vec[1]; + ((float *)(b+vehFields[i].ofs))[2] = vec[2]; + break; + case VF_BOOL: + *(qboolean *)(b+vehFields[i].ofs) = (atof(value)!=0); + break; + case VF_VEHTYPE: + vehType = (vehicleType_t)GetIDForString( VehicleTable, value ); + *(vehicleType_t *)(b+vehFields[i].ofs) = vehType; + break; + case VF_ANIM: + int anim = GetIDForString( animTable, value ); + *(int *)(b+vehFields[i].ofs) = anim; + break; + } + break; + } + } + } +} + +static void G_VehicleStoreParms( const char *p ) +{//load up all into a table: g_vehicleInfo + const char *token; + vehicleInfo_t *vehicle; + + ////////////////// HERE ////////////////////// + // The first vehicle just contains all the base level (not 'overridden') function calls. + G_SetSharedVehicleFunctions( &g_vehicleInfo[0] ); + numVehicles = 1; + + //try to parse data out + COM_BeginParseSession(); + + //look for an open brace + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + {//barf + return; + } + + if ( !Q_stricmp( token, "{" ) ) + {//found one, parse out the goodies + if ( numVehicles >= MAX_VEHICLES ) + {//sorry, no more vehicle slots! + gi.Printf( S_COLOR_RED"Too many vehicles in *.veh (limit %d)\n", MAX_VEHICLES ); + break; + } + //token = token; + vehicle = &g_vehicleInfo[numVehicles++]; + G_VehicleSetDefaults( vehicle ); + G_ParseVehicleParms( vehicle, &p ); + //sanity check and clamp the vehicle's data + G_VehicleClampData( vehicle ); + ////////////////// HERE ////////////////////// + // Setup the shared function pointers. + G_SetSharedVehicleFunctions( vehicle ); + switch( vehicle->type ) + { + case VH_SPEEDER: + G_SetSpeederVehicleFunctions( vehicle ); + break; + case VH_ANIMAL: + G_SetAnimalVehicleFunctions( vehicle ); + break; + case VH_FIGHTER: + G_SetFighterVehicleFunctions( vehicle ); + break; + case VH_WALKER: + G_SetAnimalVehicleFunctions( vehicle ); + break; + } + } + } +} + +void G_VehicleLoadParms( void ) +{//HMM... only do this if there's a vehicle on the level? + int len, totallen, vehExtFNLen, fileCnt, i; + char *buffer, *holdChar, *marker; + char vehExtensionListBuf[2048]; // The list of file names read in + + #define MAX_VEHICLE_DATA_SIZE 0x20000 + char VehicleParms[MAX_VEHICLE_DATA_SIZE]={0}; + + // gi.Printf( "Parsing *.veh vehicle definitions\n" ); + + //set where to store the first one + totallen = 0; + marker = VehicleParms; + + //now load in the .veh vehicle definitions + fileCnt = gi.FS_GetFileList("ext_data/vehicles", ".veh", vehExtensionListBuf, sizeof(vehExtensionListBuf) ); + + holdChar = vehExtensionListBuf; + for ( i = 0; i < fileCnt; i++, holdChar += vehExtFNLen + 1 ) + { + vehExtFNLen = strlen( holdChar ); + + //gi.Printf( "Parsing %s\n", holdChar ); + + len = gi.FS_ReadFile( va( "ext_data/vehicles/%s", holdChar), (void **) &buffer ); + + if ( len == -1 ) + { + gi.Printf( "G_VehicleLoadParms: error reading file %s\n", holdChar ); + } + else + { + if ( totallen && *(marker-1) == '}' ) + {//don't let it end on a } because that should be a stand-alone token + strcat( marker, " " ); + totallen++; + marker++; + } + if ( totallen + len >= MAX_VEHICLE_DATA_SIZE ) { + G_Error( "G_VehicleLoadParms: ran out of space before reading %s\n(you must make the .npc files smaller)", holdChar ); + } + strcat( marker, buffer ); + gi.FS_FreeFile( buffer ); + + totallen += len; + marker += len; + } + } + G_VehicleStoreParms(VehicleParms); +} diff --git a/code/game/g_vehicles.c b/code/game/g_vehicles.c new file mode 100644 index 0000000..d4505c3 --- /dev/null +++ b/code/game/g_vehicles.c @@ -0,0 +1,3201 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +#include "q_shared.h" +#include "g_local.h" + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#include "../CGame/cg_Local.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt + +#define MOD_EXPLOSIVE MOD_SUICIDE +#endif + +#ifndef _JK2MP +#define GAME_INLINE inline +#define bgEntity_t gentity_t +#endif + +#ifdef _JK2MP +extern gentity_t *NPC_Spawn_Do( gentity_t *ent ); +extern void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags); +#else +extern gentity_t *NPC_Spawn_Do( gentity_t *pEnt, qboolean fullSpawnNow ); +extern qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask); + +extern qboolean G_SetG2PlayerModelInfo( gentity_t *pEnt, const char *modelName, const char *customSkin, const char *surfOff, const char *surfOn ); +extern void G_RemovePlayerModel( gentity_t *pEnt ); +extern void G_ChangePlayerModel( gentity_t *pEnt, const char *newModel ); +extern void G_RemoveWeaponModels( gentity_t *pEnt ); +extern void CG_ChangeWeapon( int num ); +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); +extern void SetClientViewAngle( gentity_t *ent, vec3_t angle ); + +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; +extern cvar_t *in_joystick; +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +extern void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern void BG_SetLegsAnimTimer(playerState_t *ps, int time ); +extern void BG_SetTorsoAnimTimer(playerState_t *ps, int time ); +#include "../namespace_end.h" +void G_VehUpdateShields( gentity_t *targ ); +#ifdef QAGAME +extern void VEH_TurretThink( Vehicle_t *pVeh, gentity_t *parent, int turretNum ); +#endif +#else +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); +#endif + +extern qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ); + +void Vehicle_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend) +{ +#ifdef _JK2MP + assert(ent->client); + BG_SetAnim(&ent->client->ps, bgAllAnims[ent->localAnimIndex].anims, setAnimParts, anim, setAnimFlags, iBlend); + ent->s.legsAnim = ent->client->ps.legsAnim; +#else + NPC_SetAnim(ent, setAnimParts, anim, setAnimFlags, iBlend); +#endif +} + +void G_VehicleTrace( trace_t *results, const vec3_t start, const vec3_t tMins, const vec3_t tMaxs, const vec3_t end, int passEntityNum, int contentmask ) +{ +#ifdef _JK2MP + trap_Trace(results, start, tMins, tMaxs, end, passEntityNum, contentmask); +#else + gi.trace( results, start, tMins, tMaxs, end, passEntityNum, contentmask ); +#endif +} + +Vehicle_t *G_IsRidingVehicle( gentity_t *pEnt ) +{ + gentity_t *ent = (gentity_t *)pEnt; + + if ( ent && ent->client && ent->client->NPC_class != CLASS_VEHICLE && ent->s.m_iVehicleNum != 0 ) //ent->client && ( ent->client->ps.eFlags & EF_IN_VEHICLE ) && ent->owner ) + { + return g_entities[ent->s.m_iVehicleNum].m_pVehicle; + } + return NULL; +} + +bool G_IsRidingTurboVehicle( gentity_t *pEnt ) +{ + gentity_t *ent = (gentity_t *)pEnt; + + if ( ent && ent->client && ent->client->NPC_class != CLASS_VEHICLE && ent->s.m_iVehicleNum != 0 ) //ent->client && ( ent->client->ps.eFlags & EF_IN_VEHICLE ) && ent->owner ) + { + return (level.times.m_iVehicleNum].m_pVehicle->m_iTurboTime); + } + return false; +} + + + +float G_CanJumpToEnemyVeh(Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ +#ifndef _JK2MP + gentity_t* rider = pVeh->m_pPilot; + + // If There Is An Enemy And We Are At The Same Z Height + //------------------------------------------------------ + if (rider && + rider->enemy && + pUcmd->rightmove && + fabsf(rider->enemy->currentOrigin[2] - rider->currentOrigin[2])<50.0f) + { + if (level.timem_safeJumpMountTime) + { + return pVeh->m_safeJumpMountRightDot; + } + + + // If The Enemy Is Riding Another Vehicle + //---------------------------------------- + Vehicle_t* enemyVeh = G_IsRidingVehicle(rider->enemy); + if (enemyVeh) + { + vec3_t enemyFwd; + vec3_t toEnemy; + float toEnemyDistance; + vec3_t riderFwd; + vec3_t riderRight; + float riderRightDot; + + // If He Is Close Enough And Going The Same Speed + //------------------------------------------------ + VectorSubtract(rider->enemy->currentOrigin, rider->currentOrigin, toEnemy); + toEnemyDistance = VectorNormalize(toEnemy); + if (toEnemyDistance<70.0f && + pVeh->m_pParentEntity->resultspeed>100.0f && + fabsf(pVeh->m_pParentEntity->resultspeed - enemyVeh->m_pParentEntity->resultspeed)<100.0f) + { + // If He Is Either To The Left Or Right Of Me + //-------------------------------------------- + AngleVectors(rider->currentAngles, riderFwd, riderRight, 0); + riderRightDot = DotProduct(riderRight, toEnemy); + if ((pUcmd->rightmove>0 && riderRightDot>0.2) || (pUcmd->rightmove<0 &&riderRightDot<-0.2)) + { + // If We Are Both Going About The Same Direction + //----------------------------------------------- + AngleVectors(rider->enemy->currentAngles, enemyFwd, 0, 0); + if (DotProduct(enemyFwd, riderFwd)>0.2f) + { + pVeh->m_safeJumpMountTime = level.time + Q_irand(3000, 4000); // Ok, now you get a 3 sec window + pVeh->m_safeJumpMountRightDot = riderRightDot; + return riderRightDot; + }// Same Direction? + }// To Left Or Right? + }// Close Enough & Same Speed? + }// Enemy Riding A Vehicle? + }// Has Enemy And On Same Z-Height +#endif + return 0.0f; +} + +// Spawn this vehicle into the world. +void G_VehicleSpawn( gentity_t *self ) +{ + float yaw; + gentity_t *vehEnt; + + VectorCopy( self->currentOrigin, self->s.origin ); + +#ifdef _JK2MP + trap_LinkEntity( self ); +#else + gi.linkentity( self ); +#endif + + if ( !self->count ) + { + self->count = 1; + } + + //save this because self gets removed in next func + yaw = self->s.angles[YAW]; + +#ifdef _JK2MP + vehEnt = NPC_Spawn_Do( self ); +#else + vehEnt = NPC_Spawn_Do( self, qtrue ); +#endif + + if ( !vehEnt ) + { + return;//return NULL; + } + + vehEnt->s.angles[YAW] = yaw; + if ( vehEnt->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL ) + { + vehEnt->NPC->behaviorState = BS_CINEMATIC; + } + +#ifdef _JK2MP //special check in case someone disconnects/dies while boarding + if (vehEnt->spawnflags & 1) + { //die without pilot + if (!vehEnt->damage) + { //default 10 sec + vehEnt->damage = 10000; + } + if (!vehEnt->speed) + { //default 512 units + vehEnt->speed = 512.0f; + } + vehEnt->m_pVehicle->m_iPilotTime = level.time + vehEnt->damage; + } +#else + if (vehEnt->spawnflags & 1) + { //die without pilot + vehEnt->m_pVehicle->m_iPilotTime = level.time + vehEnt->endFrame; + } +#endif + //return vehEnt; +} + +// Attachs an entity to the vehicle it's riding (it's owner). +void G_AttachToVehicle( gentity_t *pEnt, usercmd_t **ucmd ) +{ + gentity_t *vehEnt; + mdxaBone_t boltMatrix; + gentity_t *ent; +#ifdef _JK2MP + int crotchBolt; +#endif + + if ( !pEnt || !ucmd ) + return; + + ent = (gentity_t *)pEnt; + +#ifdef _JK2MP + vehEnt = &g_entities[ent->r.ownerNum]; +#else + vehEnt = ent->owner; +#endif + ent->waypoint = vehEnt->waypoint; // take the veh's waypoint as your own + + if ( !vehEnt->m_pVehicle ) + return; + +#ifdef _JK2MP + crotchBolt = trap_G2API_AddBolt(vehEnt->ghoul2, 0, "*driver"); + + // Get the driver tag. + trap_G2API_GetBoltMatrix( vehEnt->ghoul2, 0, crotchBolt, &boltMatrix, + vehEnt->m_pVehicle->m_vOrientation, vehEnt->currentOrigin, + level.time, NULL, vehEnt->modelScale ); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, ent->client->ps.origin ); + G_SetOrigin(ent, ent->client->ps.origin); + trap_LinkEntity( ent ); +#else + // Get the driver tag. + gi.G2API_GetBoltMatrix( vehEnt->ghoul2, vehEnt->playerModel, vehEnt->crotchBolt, &boltMatrix, + vehEnt->m_pVehicle->m_vOrientation, vehEnt->currentOrigin, + (cg.time?cg.time:level.time), NULL, vehEnt->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->ps.origin ); + gi.linkentity( ent ); +#endif +} + +#ifndef _JK2MP +void G_KnockOffVehicle( gentity_t *pRider, gentity_t *self, qboolean bPull ) +{ + Vehicle_t *pVeh = NULL; + vec3_t riderAngles, fDir, rDir, dir2Me; + float fDot, rDot; + + if ( !pRider || !pRider->client ) + { + return; + } + + pVeh = G_IsRidingVehicle( pRider ); + + if ( !pVeh || !pVeh->m_pVehicleInfo ) + { + return; + } + + VectorCopy( pRider->currentAngles, riderAngles ); + riderAngles[0] = 0; + AngleVectors( riderAngles, fDir, rDir, NULL ); + VectorSubtract( self->currentOrigin, pRider->currentOrigin, dir2Me ); + dir2Me[2] = 0; + VectorNormalize( dir2Me ); + fDot = DotProduct( fDir, dir2Me ); + if ( fDot >= 0.5f ) + {//I'm in front of them + if ( bPull ) + {//pull them foward + pVeh->m_EjectDir = VEH_EJECT_FRONT; + } + else + {//push them back + pVeh->m_EjectDir = VEH_EJECT_REAR; + } + } + else if ( fDot <= -0.5f ) + {//I'm behind them + if ( bPull ) + {//pull them back + pVeh->m_EjectDir = VEH_EJECT_REAR; + } + else + {//push them forward + pVeh->m_EjectDir = VEH_EJECT_FRONT; + } + } + else + {//to the side of them + rDot = DotProduct( fDir, dir2Me ); + if ( rDot >= 0.0f ) + {//to the right + if ( bPull ) + {//pull them right + pVeh->m_EjectDir = VEH_EJECT_RIGHT; + } + else + {//push them left + pVeh->m_EjectDir = VEH_EJECT_LEFT; + } + } + else + {//to the left + if ( bPull ) + {//pull them left + pVeh->m_EjectDir = VEH_EJECT_LEFT; + } + else + {//push them right + pVeh->m_EjectDir = VEH_EJECT_RIGHT; + } + } + } + //now forcibly eject them + pVeh->m_pVehicleInfo->Eject( pVeh, pRider, qtrue ); +} +#endif + +#ifndef _JK2MP //don't want this in mp at least for now +void G_DrivableATSTDie( gentity_t *self ) +{ +} + +void G_DriveATST( gentity_t *pEnt, gentity_t *atst ) +{ + if ( pEnt->NPC_type && pEnt->client && (pEnt->client->NPC_class == CLASS_ATST) ) + {//already an atst, switch back + //open hatch + G_RemovePlayerModel( pEnt ); + pEnt->NPC_type = "player"; + pEnt->client->NPC_class = CLASS_PLAYER; + pEnt->flags &= ~FL_SHIELDED; + pEnt->client->ps.eFlags &= ~EF_IN_ATST; + //size + VectorCopy( playerMins, pEnt->mins ); + VectorCopy( playerMaxs, pEnt->maxs ); + pEnt->client->crouchheight = CROUCH_MAXS_2; + pEnt->client->standheight = DEFAULT_MAXS_2; + pEnt->s.radius = 0;//clear it so the G2-model-setting stuff will recalc it + G_ChangePlayerModel( pEnt, pEnt->NPC_type ); + //G_SetG2PlayerModel( pEnt, pEnt->NPC_type, NULL, NULL, NULL ); + + //FIXME: reset/4 their weapon + pEnt->client->ps.stats[STAT_WEAPONS] &= ~(( 1 << WP_ATST_MAIN )|( 1 << WP_ATST_SIDE )); + pEnt->client->ps.ammo[weaponData[WP_ATST_MAIN].ammoIndex] = 0; + pEnt->client->ps.ammo[weaponData[WP_ATST_SIDE].ammoIndex] = 0; + if ( pEnt->client->ps.stats[STAT_WEAPONS] & (1<client->ps.viewheight = pEnt->maxs[2] + STANDARD_VIEWHEIGHT_OFFSET; + //pEnt->mass = 10; + } + else + {//become an atst + pEnt->NPC_type = "atst"; + pEnt->client->NPC_class = CLASS_ATST; + pEnt->client->ps.eFlags |= EF_IN_ATST; + pEnt->flags |= FL_SHIELDED; + //size + VectorSet( pEnt->mins, ATST_MINS0, ATST_MINS1, ATST_MINS2 ); + VectorSet( pEnt->maxs, ATST_MAXS0, ATST_MAXS1, ATST_MAXS2 ); + pEnt->client->crouchheight = ATST_MAXS2; + pEnt->client->standheight = ATST_MAXS2; + if ( !atst ) + {//no pEnt to copy from + G_ChangePlayerModel( pEnt, "atst" ); + //G_SetG2PlayerModel( pEnt, "atst", NULL, NULL, NULL ); + NPC_SetAnim( pEnt, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE, 200 ); + } + else + { + G_RemovePlayerModel( pEnt ); + G_RemoveWeaponModels( pEnt ); + gi.G2API_CopyGhoul2Instance( atst->ghoul2, pEnt->ghoul2 ); + pEnt->playerModel = 0; + G_SetG2PlayerModelInfo( pEnt, "atst", NULL, NULL, NULL ); + //turn off hatch underside + gi.G2API_SetSurfaceOnOff( &pEnt->ghoul2[pEnt->playerModel], "head_hatchcover", 0x00000002/*G2SURFACEFLAG_OFF*/ ); + G_Sound( pEnt, G_SoundIndex( "sound/chars/atst/atst_hatch_close" )); + } + pEnt->s.radius = 320; + //weapon + gitem_t *item = FindItemForWeapon( WP_ATST_MAIN ); //precache the weapon + CG_RegisterItemSounds( (item-bg_itemlist) ); + CG_RegisterItemVisuals( (item-bg_itemlist) ); + item = FindItemForWeapon( WP_ATST_SIDE ); //precache the weapon + CG_RegisterItemSounds( (item-bg_itemlist) ); + CG_RegisterItemVisuals( (item-bg_itemlist) ); + pEnt->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_ATST_MAIN )|( 1 << WP_ATST_SIDE ); + pEnt->client->ps.ammo[weaponData[WP_ATST_MAIN].ammoIndex] = ammoData[weaponData[WP_ATST_MAIN].ammoIndex].max; + pEnt->client->ps.ammo[weaponData[WP_ATST_SIDE].ammoIndex] = ammoData[weaponData[WP_ATST_SIDE].ammoIndex].max; + CG_ChangeWeapon( WP_ATST_MAIN ); + //HACKHACKHACKTEMP + item = FindItemForWeapon( WP_EMPLACED_GUN ); + CG_RegisterItemSounds( (item-bg_itemlist) ); + CG_RegisterItemVisuals( (item-bg_itemlist) ); + item = FindItemForWeapon( WP_ROCKET_LAUNCHER ); + CG_RegisterItemSounds( (item-bg_itemlist) ); + CG_RegisterItemVisuals( (item-bg_itemlist) ); + item = FindItemForWeapon( WP_BOWCASTER ); + CG_RegisterItemSounds( (item-bg_itemlist) ); + CG_RegisterItemVisuals( (item-bg_itemlist) ); + //HACKHACKHACKTEMP + //FIXME: these get lost in load/save! Must use variables that are set every frame or saved/loaded + //camera + gi.cvar_set( "cg_thirdperson", "1" ); + cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG; + cg.overrides.thirdPersonRange = 240; + //cg.overrides.thirdPersonVertOffset = 100; + //cg.overrides.thirdPersonPitchOffset = -30; + //FIXME: this gets stomped in pmove? + pEnt->client->ps.viewheight = 120; + //FIXME: setting these broke things very badly...? + //pEnt->client->standheight = 200; + //pEnt->client->crouchheight = 200; + //pEnt->mass = 300; + //movement + //pEnt->client->ps.speed = 0;//FIXME: override speed? + //FIXME: slow turn turning/can't turn if not moving? + } +} +#endif //_JK2MP + +// Animate the vehicle and it's riders. +void Animate( Vehicle_t *pVeh ) +{ + // Validate a pilot rider. + if ( pVeh->m_pPilot ) + { + if (pVeh->m_pVehicleInfo->AnimateRiders) + { + pVeh->m_pVehicleInfo->AnimateRiders( pVeh ); + } + } + + pVeh->m_pVehicleInfo->AnimateVehicle( pVeh ); +} + +// Determine whether this entity is able to board this vehicle or not. +bool ValidateBoard( Vehicle_t *pVeh, bgEntity_t *pEnt ) +{ + // Determine where the entity is entering the vehicle from (left, right, or back). + vec3_t vVehToEnt; + vec3_t vVehDir; + const gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + const gentity_t *ent = (gentity_t *)pEnt; + vec3_t vVehAngles; + float fDot; + + if ( pVeh->m_iDieTime>0) + { + return false; + } + + if ( ent->health <= 0 ) + {//dead men can't ride vehicles + return false; + } + + if ( pVeh->m_pPilot != NULL ) + {//already have a driver! + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + {//I know, I know, this should by in the fighters's validateboard() + //can never steal a fighter from it's pilot + return false; + } + else if ( pVeh->m_pVehicleInfo->type == VH_WALKER ) + {//I know, I know, this should by in the walker's validateboard() + if ( !ent->client || ent->client->ps.groundEntityNum != parent->s.number ) + {//can only steal an occupied AT-ST if you're on top (by the hatch) + return false; + } + } + else if (pVeh->m_pVehicleInfo->type == VH_SPEEDER) + {//you can only steal the bike from the driver if you landed on the driver or bike + return (pVeh->m_iBoarding==VEH_MOUNT_THROW_LEFT || pVeh->m_iBoarding==VEH_MOUNT_THROW_RIGHT); + } + } + // Yes, you shouldn't have put this here (you 'should' have made an 'overriden' ValidateBoard func), but in this + // instance it's more than adequate (which is why I do it too :-). Making a whole other function for this is silly. + else if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + // If you're a fighter, you allow everyone to enter you from all directions. + return true; + } + + // Clear out all orientation axis except for the yaw. + VectorSet(vVehAngles, 0, parent->currentAngles[YAW], 0); + + // Vector from Entity to Vehicle. + VectorSubtract( ent->currentOrigin, parent->currentOrigin, vVehToEnt ); + vVehToEnt[2] = 0; + VectorNormalize( vVehToEnt ); + + // Get the right vector. + AngleVectors( vVehAngles, NULL, vVehDir, NULL ); + VectorNormalize( vVehDir ); + + // Find the angle between the vehicle right vector and the vehicle to entity vector. + fDot = DotProduct( vVehToEnt, vVehDir ); + + // If the entity is within a certain angle to the left of the vehicle... + if ( fDot >= 0.5f ) + { + // Right board. + pVeh->m_iBoarding = -2; + } + else if ( fDot <= -0.5f ) + { + // Left board. + pVeh->m_iBoarding = -1; + } + // Maybe they're trying to board from the back... + else + { + // The forward vector of the vehicle. + // AngleVectors( vVehAngles, vVehDir, NULL, NULL ); + // VectorNormalize( vVehDir ); + + // Find the angle between the vehicle forward and the vehicle to entity vector. + // fDot = DotProduct( vVehToEnt, vVehDir ); + + // If the entity is within a certain angle behind the vehicle... + //if ( fDot <= -0.85f ) + { + // Jump board. + pVeh->m_iBoarding = -3; + } + } + + // If for some reason we couldn't board, leave... + if ( pVeh->m_iBoarding > -1 ) + return false; + + return true; +} + +// Board this Vehicle (get on). The first entity to board an empty vehicle becomes the Pilot. +bool Board( Vehicle_t *pVeh, bgEntity_t *pEnt ) +{ + vec3_t vPlayerDir; + gentity_t *ent = (gentity_t *)pEnt; + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + + // If it's not a valid entity, OR if the vehicle is blowing up (it's dead), OR it's not + // empty, OR we're already being boarded, OR the person trying to get on us is already + // in a vehicle (that was a fun bug :-), leave! + if ( !ent || parent->health <= 0 /*|| !( parent->client->ps.eFlags & EF_EMPTY_VEHICLE )*/ || (pVeh->m_iBoarding > 0) || +#ifdef _JK2MP + ( ent->client->ps.m_iVehicleNum ) ) +#else + ( ent->s.m_iVehicleNum != 0 ) ) +#endif + return false; + + // Bucking so we can't do anything (NOTE: Should probably be a better name since fighters don't buck...). + if ( pVeh->m_ulFlags & VEH_BUCKING ) + return false; + + // Validate the entity's ability to board this vehicle. + if ( !pVeh->m_pVehicleInfo->ValidateBoard( pVeh, pEnt ) ) + return false; + + // FIXME FIXME!!! Ask Mike monday where ent->client->ps.eFlags might be getting changed!!! It is always 0 (when it should + // be 1024) so a person riding a vehicle is able to ride another vehicle!!!!!!!! + + // Tell everybody their status. + // ALWAYS let the player be the pilot. + if ( ent->s.number < MAX_CLIENTS ) + { + pVeh->m_pOldPilot = pVeh->m_pPilot; + + +#ifdef _JK2MP + if ( !pVeh->m_pPilot ) + { //become the pilot, if there isn't one now + pVeh->m_pVehicleInfo->SetPilot( pVeh, (bgEntity_t *)ent ); + } + // If we're not yet full... + else if ( pVeh->m_iNumPassengers < pVeh->m_pVehicleInfo->maxPassengers ) + { + int i; + // Find an empty slot and put that passenger here. + for ( i = 0; i < pVeh->m_pVehicleInfo->maxPassengers; i++ ) + { + if ( pVeh->m_ppPassengers[i] == NULL ) + { + pVeh->m_ppPassengers[i] = (bgEntity_t *)ent; +#ifdef QAGAME + //Server just needs to tell client which passengernum he is + if ( ent->client ) + { + ent->client->ps.generic1 = i+1; + } +#endif + break; + } + } + pVeh->m_iNumPassengers++; + } + // We're full, sorry... + else + { + return false; + } + ent->s.m_iVehicleNum = parent->s.number; + if (ent->client) + { + ent->client->ps.m_iVehicleNum = ent->s.m_iVehicleNum; + } + if ( pVeh->m_pPilot == (bgEntity_t *)ent ) + { + parent->r.ownerNum = ent->s.number; + parent->s.owner = parent->r.ownerNum; //for prediction + } +#else + pVeh->m_pVehicleInfo->SetPilot( pVeh, ent ); + ent->s.m_iVehicleNum = parent->s.number; + parent->owner = ent; +#endif + +#ifdef QAGAME + { + gentity_t *gParent = (gentity_t *)parent; + if ( (gParent->spawnflags&2) ) + {//was being suspended + gParent->spawnflags &= ~2;//SUSPENDED - clear this spawnflag, no longer docked, okay to free-fall if not in space + //gParent->client->ps.eFlags &= ~EF_RADAROBJECT; + G_Sound( gParent, CHAN_AUTO, G_SoundIndex( "sound/vehicles/common/release.wav" ) ); + if ( gParent->fly_sound_debounce_time ) + {//we should drop like a rock for a few seconds + pVeh->m_iDropTime = level.time + gParent->fly_sound_debounce_time; + } + } + } +#endif + +#ifndef _JK2MP + gi.cvar_set( "cg_thirdperson", "1" ); //go to third person + CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.87 ); //tell them how to get out! +#endif + + //FIXME: rider needs to look in vehicle's direction when he gets in + // Clear these since they're used to turn the vehicle now. + /*SetClientViewAngle( ent, pVeh->m_vOrientation ); + memset( &parent->client->usercmd, 0, sizeof( usercmd_t ) ); + memset( &pVeh->m_ucmd, 0, sizeof( usercmd_t ) ); + VectorClear( parent->client->ps.viewangles ); + VectorClear( parent->client->ps.delta_angles );*/ + + // Set the looping sound only when there is a pilot (when the vehicle is "on"). + if ( pVeh->m_pVehicleInfo->soundLoop ) + { +#ifdef _JK2MP + parent->client->ps.loopSound = parent->s.loopSound = pVeh->m_pVehicleInfo->soundLoop; +#else + parent->s.loopSound = pVeh->m_pVehicleInfo->soundLoop; +#endif + } + } + else + { + // If there's no pilot, try to drive this vehicle. + if ( pVeh->m_pPilot == NULL ) + { +#ifdef _JK2MP + pVeh->m_pVehicleInfo->SetPilot( pVeh, (bgEntity_t *)ent ); + // TODO: Set pilot should do all this stuff.... + parent->r.ownerNum = ent->s.number; + parent->s.owner = parent->r.ownerNum; //for prediction +#else + pVeh->m_pVehicleInfo->SetPilot( pVeh, ent ); + // TODO: Set pilot should do all this stuff.... + parent->owner = ent; +#endif + // Set the looping sound only when there is a pilot (when the vehicle is "on"). + if ( pVeh->m_pVehicleInfo->soundLoop ) + { +#ifdef _JK2MP + parent->client->ps.loopSound = parent->s.loopSound = pVeh->m_pVehicleInfo->soundLoop; +#else + parent->s.loopSound = pVeh->m_pVehicleInfo->soundLoop; +#endif + } + + parent->client->ps.speed = 0; + memset( &pVeh->m_ucmd, 0, sizeof( usercmd_t ) ); + } + // We're full, sorry... + else + { + return false; + } + } + + // Make sure the entity knows it's in a vehicle. +#ifdef _JK2MP + ent->client->ps.m_iVehicleNum = parent->s.number; + ent->r.ownerNum = parent->s.number; + ent->s.owner = ent->r.ownerNum; //for prediction + if (pVeh->m_pPilot == (bgEntity_t *)ent) + { + parent->client->ps.m_iVehicleNum = ent->s.number+1; //always gonna be under MAX_CLIENTS so no worries about 1 byte overflow + } +#else + ent->s.m_iVehicleNum = parent->s.number; + ent->owner = parent; + parent->s.m_iVehicleNum = ent->s.number+1; +#endif + + //memset( &ent->client->usercmd, 0, sizeof( usercmd_t ) ); + + //FIXME: no saber or weapons if numHands = 2, should switch to speeder weapon, no attack anim on player + if ( pVeh->m_pVehicleInfo->numHands == 2 ) + {//switch to vehicle weapon +#ifndef _JK2MP //rwwFIXMEFIXMEFIXME + if (ent->s.numberclient->ps.stats[ STAT_WEAPONS ] |= (1<client->ps.weapon != WP_SABER + && ent->client->ps.weapon != WP_BLASTER ) + {//switch to weapon none? + if (ent->s.numberclient->ps.weapon = WP_NONE; + G_RemoveWeaponModels( ent ); + } +#endif + } + + if ( pVeh->m_pVehicleInfo->hideRider ) + {//hide the rider + pVeh->m_pVehicleInfo->Ghost( pVeh, (bgEntity_t *)ent ); + } + + // Play the start sounds + if ( pVeh->m_pVehicleInfo->soundOn ) + { +#ifdef _JK2MP + G_Sound( parent, CHAN_AUTO, pVeh->m_pVehicleInfo->soundOn ); +#else + // NOTE: Use this type so it's spatialized and updates play origin as bike moves - MCG + G_SoundIndexOnEnt( parent, CHAN_AUTO, pVeh->m_pVehicleInfo->soundOn ); +#endif + } + + VectorCopy( pVeh->m_vOrientation, vPlayerDir ); + vPlayerDir[ROLL] = 0; + SetClientViewAngle( ent, vPlayerDir ); + + return true; +} + +bool VEH_TryEject( Vehicle_t *pVeh, + gentity_t *parent, + gentity_t *ent, + int ejectDir, + vec3_t vExitPos ) +{ + float fBias; + float fVehDiag; + float fEntDiag; + vec3_t vEntMins, vEntMaxs, vVehLeaveDir, vVehAngles; + trace_t m_ExitTrace; + + // Make sure that the entity is not 'stuck' inside the vehicle (since their bboxes will now intersect). + // This makes the entity leave the vehicle from the right side. + VectorSet(vVehAngles, 0, parent->currentAngles[YAW], 0); + switch ( ejectDir ) + { + // Left. + case VEH_EJECT_LEFT: + AngleVectors( vVehAngles, NULL, vVehLeaveDir, NULL ); + vVehLeaveDir[0] = -vVehLeaveDir[0]; + vVehLeaveDir[1] = -vVehLeaveDir[1]; + vVehLeaveDir[2] = -vVehLeaveDir[2]; + break; + // Right. + case VEH_EJECT_RIGHT: + AngleVectors( vVehAngles, NULL, vVehLeaveDir, NULL ); + break; + // Front. + case VEH_EJECT_FRONT: + AngleVectors( vVehAngles, vVehLeaveDir, NULL, NULL ); + break; + // Rear. + case VEH_EJECT_REAR: + AngleVectors( vVehAngles, vVehLeaveDir, NULL, NULL ); + vVehLeaveDir[0] = -vVehLeaveDir[0]; + vVehLeaveDir[1] = -vVehLeaveDir[1]; + vVehLeaveDir[2] = -vVehLeaveDir[2]; + break; + // Top. + case VEH_EJECT_TOP: + AngleVectors( vVehAngles, NULL, NULL, vVehLeaveDir ); + break; + // Bottom?. + case VEH_EJECT_BOTTOM: + break; + } + VectorNormalize( vVehLeaveDir ); + //NOTE: not sure why following line was needed - MCG + //pVeh->m_EjectDir = VEH_EJECT_LEFT; + + // Since (as of this time) the collidable geometry of the entity is just an axis + // aligned box, we need to get the diagonal length of it in case we come out on that side. + // Diagonal Length == squareroot( squared( Sidex / 2 ) + squared( Sidey / 2 ) ); + + // TODO: DO diagonal for entity. + + fBias = 1.0f; + if (pVeh->m_pVehicleInfo->type == VH_WALKER) + { //hacktastic! + fBias += 0.2f; + } + VectorCopy( ent->currentOrigin, vExitPos ); + fVehDiag = sqrtf( ( parent->maxs[0] * parent->maxs[0] ) + ( parent->maxs[1] * parent->maxs[1] ) ); + VectorCopy( ent->maxs, vEntMaxs ); +#ifdef _JK2MP + if ( ent->s.number < MAX_CLIENTS ) + {//for some reason, in MP, player client mins and maxs are never stored permanently, just set to these hardcoded numbers in PMove + vEntMaxs[0] = 15; + vEntMaxs[1] = 15; + } +#endif + fEntDiag = sqrtf( ( vEntMaxs[0] * vEntMaxs[0] ) + ( vEntMaxs[1] * vEntMaxs[1] ) ); + vVehLeaveDir[0] *= ( fVehDiag + fEntDiag ) * fBias; // x + vVehLeaveDir[1] *= ( fVehDiag + fEntDiag ) * fBias; // y + vVehLeaveDir[2] *= ( fVehDiag + fEntDiag ) * fBias; + VectorAdd( vExitPos, vVehLeaveDir, vExitPos ); + + //we actually could end up *not* getting off if the trace fails... + // Check to see if this new position is a valid place for our entity to go. +#ifdef _JK2MP + VectorSet(vEntMins, -15.0f, -15.0f, DEFAULT_MINS_2); + VectorSet(vEntMaxs, 15.0f, 15.0f, DEFAULT_MAXS_2); +#else + VectorCopy(ent->mins, vEntMins); + VectorCopy(ent->maxs, vEntMaxs); +#endif + G_VehicleTrace( &m_ExitTrace, ent->currentOrigin, vEntMins, vEntMaxs, vExitPos, ent->s.number, ent->clipmask ); + + if ( m_ExitTrace.allsolid//in solid + || m_ExitTrace.startsolid) + { + return false; + } + // If the trace hit something, we can't go there! + if ( m_ExitTrace.fraction < 1.0f ) + {//not totally clear +#ifdef _JK2MP + if ( (parent->clipmask&ent->r.contents) )//vehicle could actually get stuck on body +#else + if ( (parent->clipmask&ent->contents) )//vehicle could actually get stuck on body +#endif + {//the trace hit the vehicle, don't let them get out, just in case + return false; + } + //otherwise, use the trace.endpos + VectorCopy( m_ExitTrace.endpos, vExitPos ); + } + return true; +} + +void G_EjectDroidUnit( Vehicle_t *pVeh, qboolean kill ) +{ + pVeh->m_pDroidUnit->s.m_iVehicleNum = ENTITYNUM_NONE; +#ifdef _JK2MP + pVeh->m_pDroidUnit->s.owner = ENTITYNUM_NONE; +#else + pVeh->m_pDroidUnit->owner = NULL; +#endif +// pVeh->m_pDroidUnit->s.otherEntityNum2 = ENTITYNUM_NONE; +#ifdef QAGAME + { + gentity_t *droidEnt = (gentity_t *)pVeh->m_pDroidUnit; + droidEnt->flags &= ~FL_UNDYING; + droidEnt->r.ownerNum = ENTITYNUM_NONE; + if ( droidEnt->client ) + { + droidEnt->client->ps.m_iVehicleNum = ENTITYNUM_NONE; + } + if ( kill ) + {//Kill them, too + //FIXME: proper origin, MOD and attacker (for credit/death message)? Get from vehicle? + G_MuteSound(droidEnt->s.number, CHAN_VOICE); + G_Damage( droidEnt, NULL, NULL, NULL, droidEnt->s.origin, 10000, 0, MOD_SUICIDE );//FIXME: proper MOD? Get from vehicle? + } + } +#endif + pVeh->m_pDroidUnit = NULL; +} + +// Eject the pilot from the vehicle. +bool Eject( Vehicle_t *pVeh, bgEntity_t *pEnt, qboolean forceEject ) +{ + gentity_t *parent; + vec3_t vExitPos; +#ifndef _JK2MP + vec3_t vPlayerDir; +#endif + gentity_t *ent = (gentity_t *)pEnt; + int firstEjectDir; + +#ifdef _JK2MP + qboolean taintedRider = qfalse; + qboolean deadRider = qfalse; + + if ( pEnt == pVeh->m_pDroidUnit ) + { + G_EjectDroidUnit( pVeh, qfalse ); + return true; + } + + if (ent) + { + if (!ent->inuse || !ent->client || ent->client->pers.connected != CON_CONNECTED) + { + taintedRider = qtrue; + parent = (gentity_t *)pVeh->m_pParentEntity; + goto getItOutOfMe; + } + else if (ent->health < 1) + { + deadRider = qtrue; + } + } +#endif + + // Validate. + if ( !ent ) + { + return false; + } + if ( !forceEject ) + { + if ( !( pVeh->m_iBoarding == 0 || pVeh->m_iBoarding == -999 || ( pVeh->m_iBoarding < -3 && pVeh->m_iBoarding >= -9 ) ) ) + { +#ifdef _JK2MP //I don't care, if he's dead get him off even if he died while boarding + deadRider = qtrue; + pVeh->m_iBoarding = 0; + pVeh->m_bWasBoarding = false; +#else + return false; +#endif + } + } + +/*#ifndef _JK2MP //rwwFIXMEFIXMEFIXME + if (ent->s.numberclient->ps.weapon = WP_NONE; + G_RemoveWeaponModels( ent ); +#endif*/ + + parent = (gentity_t *)pVeh->m_pParentEntity; + + //Try ejecting in every direction + if ( pVeh->m_EjectDir < VEH_EJECT_LEFT ) + { + pVeh->m_EjectDir = VEH_EJECT_LEFT; + } + else if ( pVeh->m_EjectDir > VEH_EJECT_BOTTOM ) + { + pVeh->m_EjectDir = VEH_EJECT_BOTTOM; + } + firstEjectDir = pVeh->m_EjectDir; + while ( !VEH_TryEject( pVeh, parent, ent, pVeh->m_EjectDir, vExitPos ) ) + { + pVeh->m_EjectDir++; + if ( pVeh->m_EjectDir > VEH_EJECT_BOTTOM ) + { + pVeh->m_EjectDir = VEH_EJECT_LEFT; + } + if ( pVeh->m_EjectDir == firstEjectDir ) + {//they all failed +#ifdef _JK2MP + if (!deadRider) + { //if he's dead.. just shove him in solid, who cares. + return false; + } +#endif + if ( forceEject ) + {//we want to always get out, just eject him here + VectorCopy( ent->currentOrigin, vExitPos ); + break; + } + else + {//can't eject + return false; + } + } + } + + // Move them to the exit position. + G_SetOrigin( ent, vExitPos ); +#ifdef _JK2MP + VectorCopy(ent->currentOrigin, ent->client->ps.origin); + trap_LinkEntity( ent ); +#else + gi.linkentity( ent ); +#endif + + // If it's the player, stop overrides. + if ( ent->s.number < MAX_CLIENTS ) + { +#ifndef _JK2MP + cg.overrides.active = 0; +#else + +#endif + } + +#ifdef _JK2MP //in MP if someone disconnects on us, we still have to clear our owner +getItOutOfMe: +#endif + + // If he's the pilot... + if ( (gentity_t *)pVeh->m_pPilot == ent ) + { +#ifdef _JK2MP + int j = 0; +#endif + + pVeh->m_pPilot = NULL; +#ifdef _JK2MP + parent->r.ownerNum = ENTITYNUM_NONE; + parent->s.owner = parent->r.ownerNum; //for prediction + + //keep these current angles + //SetClientViewAngle( parent, pVeh->m_vOrientation ); + memset( &parent->client->pers.cmd, 0, sizeof( usercmd_t ) ); +#else + parent->owner = NULL; + + //keep these current angles + //SetClientViewAngle( parent, pVeh->m_vOrientation ); + memset( &parent->client->usercmd, 0, sizeof( usercmd_t ) ); +#endif + memset( &pVeh->m_ucmd, 0, sizeof( usercmd_t ) ); + +#ifdef _JK2MP //if there are some passengers, promote the first passenger to pilot + while (j < pVeh->m_iNumPassengers) + { + if (pVeh->m_ppPassengers[j]) + { + int k = 1; + pVeh->m_pVehicleInfo->SetPilot( pVeh, pVeh->m_ppPassengers[j] ); + parent->r.ownerNum = pVeh->m_ppPassengers[j]->s.number; + parent->s.owner = parent->r.ownerNum; //for prediction + parent->client->ps.m_iVehicleNum = pVeh->m_ppPassengers[j]->s.number+1; + + //rearrange the passenger slots now.. +#ifdef QAGAME + //Server just needs to tell client he's not a passenger anymore + if ( ((gentity_t *)pVeh->m_ppPassengers[j])->client ) + { + ((gentity_t *)pVeh->m_ppPassengers[j])->client->ps.generic1 = 0; + } +#endif + pVeh->m_ppPassengers[j] = NULL; + while (k < pVeh->m_iNumPassengers) + { + if (!pVeh->m_ppPassengers[k-1]) + { //move down + pVeh->m_ppPassengers[k-1] = pVeh->m_ppPassengers[k]; + pVeh->m_ppPassengers[k] = NULL; +#ifdef QAGAME + //Server just needs to tell client which passenger he is + if ( ((gentity_t *)pVeh->m_ppPassengers[k-1])->client ) + { + ((gentity_t *)pVeh->m_ppPassengers[k-1])->client->ps.generic1 = k; + } +#endif + } + k++; + } + pVeh->m_iNumPassengers--; + + break; + } + j++; + } +#endif + } + else if (ent==(gentity_t *)pVeh->m_pOldPilot) + { + pVeh->m_pOldPilot = 0; + } + +#ifdef _JK2MP //I hate adding these! + if (!taintedRider) + { +#endif + if ( pVeh->m_pVehicleInfo->hideRider ) + { + pVeh->m_pVehicleInfo->UnGhost( pVeh, (bgEntity_t *)ent ); + } +#ifdef _JK2MP + } +#endif + + // If the vehicle now has no pilot... + if ( pVeh->m_pPilot == NULL ) + { +#ifdef _JK2MP + parent->client->ps.loopSound = parent->s.loopSound = 0; +#else + parent->s.loopSound = 0; +#endif + // Completely empty vehicle...? +#ifdef _JK2MP + parent->client->ps.m_iVehicleNum = 0; +#else + parent->s.m_iVehicleNum = 0; +#endif + } + +#ifdef _JK2MP + if (taintedRider) + { //you can go now + pVeh->m_iBoarding = level.time + 1000; + return true; + } +#endif + + // Client not in a vehicle. +#ifdef _JK2MP + ent->client->ps.m_iVehicleNum = 0; + ent->r.ownerNum = ENTITYNUM_NONE; + ent->s.owner = ent->r.ownerNum; //for prediction + + ent->client->ps.viewangles[PITCH] = 0.0f; + ent->client->ps.viewangles[ROLL] = 0.0f; + ent->client->ps.viewangles[YAW] = pVeh->m_vOrientation[YAW]; + SetClientViewAngle(ent, ent->client->ps.viewangles); + + if (ent->client->solidHack) + { + ent->client->solidHack = 0; + ent->r.contents = CONTENTS_BODY; + } +#else + ent->owner = NULL; +#endif + ent->s.m_iVehicleNum = 0; + + // Jump out. +/* if ( ent->client->ps.velocity[2] < JUMP_VELOCITY ) + { + ent->client->ps.velocity[2] = JUMP_VELOCITY; + } + else + { + ent->client->ps.velocity[2] += JUMP_VELOCITY; + }*/ + + // Make sure entity is facing the direction it got off at. +#ifndef _JK2MP + VectorCopy( pVeh->m_vOrientation, vPlayerDir ); + vPlayerDir[ROLL] = 0; + SetClientViewAngle( ent, vPlayerDir ); +#endif + + //if was using vehicle weapon, remove it and switch to normal weapon when hop out... + if ( ent->client->ps.weapon == WP_NONE ) + {//FIXME: check against this vehicle's gun from the g_vehicleInfo table + //remove the vehicle's weapon from me + //ent->client->ps.stats[STAT_WEAPONS] &= ~( 1 << WP_EMPLACED_GUN ); + //ent->client->ps.ammo[weaponData[WP_EMPLACED_GUN].ammoIndex] = 0;//maybe store this ammo on the vehicle before clearing it? + //switch back to a normal weapon we're carrying + + //FIXME: store the weapon we were using when we got on and restore that when hop off +/* if ( (ent->client->ps.stats[STAT_WEAPONS]&(1< WP_NONE; checkWp-- ) + { + if ( (ent->client->ps.stats[STAT_WEAPONS]&(1<s.number && ent->client->ps.weapon != WP_SABER + && cg_gunAutoFirst.value ) + { + gi.cvar_set( "cg_thirdperson", "0" ); + }*/ +#ifdef _JK2MP + BG_SetLegsAnimTimer( &ent->client->ps, 0 ); + BG_SetTorsoAnimTimer( &ent->client->ps, 0 ); +#else + PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, 0 ); + PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoAnimTimer, 0 ); +#endif + + // Set how long until this vehicle can be boarded again. + pVeh->m_iBoarding = level.time + 1000; + + return true; +} + +// Eject all the inhabitants of this vehicle. +bool EjectAll( Vehicle_t *pVeh ) +{ + // TODO: Setup a default escape for ever vehicle type. + + pVeh->m_EjectDir = VEH_EJECT_TOP; + // Make sure no other boarding calls exist. We MUST exit. + pVeh->m_iBoarding = 0; + pVeh->m_bWasBoarding = false; + + // Throw them off. + if ( pVeh->m_pPilot ) + { +#ifdef QAGAME + gentity_t *pilot = (gentity_t*)pVeh->m_pPilot; +#endif + pVeh->m_pVehicleInfo->Eject( pVeh, pVeh->m_pPilot, qtrue ); +#ifdef QAGAME + if ( pVeh->m_pVehicleInfo->killRiderOnDeath && pilot ) + {//Kill them, too + //FIXME: proper origin, MOD and attacker (for credit/death message)? Get from vehicle? + G_MuteSound(pilot->s.number, CHAN_VOICE); + G_Damage( pilot, player, player, NULL, pilot->s.origin, 10000, 0, MOD_SUICIDE ); + } +#endif + } + if ( pVeh->m_pOldPilot ) + { +#ifdef QAGAME + gentity_t *pilot = (gentity_t*)pVeh->m_pOldPilot; +#endif + pVeh->m_pVehicleInfo->Eject( pVeh, pVeh->m_pOldPilot, qtrue ); +#ifdef QAGAME + if ( pVeh->m_pVehicleInfo->killRiderOnDeath && pilot ) + {//Kill them, too + //FIXME: proper origin, MOD and attacker (for credit/death message)? Get from vehicle? + G_MuteSound(pilot->s.number, CHAN_VOICE); + G_Damage( pilot, player, player, NULL, pilot->s.origin, 10000, 0, MOD_SUICIDE ); + } +#endif + } + + if ( pVeh->m_pDroidUnit ) + { + G_EjectDroidUnit( pVeh, pVeh->m_pVehicleInfo->killRiderOnDeath ); + } + + return true; +} + +// Start a delay until the vehicle explodes. +static void StartDeathDelay( Vehicle_t *pVeh, int iDelayTimeOverride ) +{ + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + + if ( iDelayTimeOverride ) + { + pVeh->m_iDieTime = level.time + iDelayTimeOverride; + } + else + { + pVeh->m_iDieTime = level.time + pVeh->m_pVehicleInfo->explosionDelay; + } + +#ifdef _JK2MP + if ( pVeh->m_pVehicleInfo->flammable ) + { + parent->client->ps.loopSound = parent->s.loopSound = G_SoundIndex( "sound/vehicles/common/fire_lp.wav" ); + } +#else + // Armor Gone Effects (Fire) + //--------------------------- + if (pVeh->m_pVehicleInfo->iArmorGoneFX) + { + if (!(pVeh->m_ulFlags&VEH_ARMORGONE) && (pVeh->m_iArmor <= 0)) + { + pVeh->m_ulFlags |= VEH_ARMORGONE; + G_PlayEffect(pVeh->m_pVehicleInfo->iArmorGoneFX, parent->playerModel, parent->crotchBolt, parent->s.number, parent->currentOrigin, 1, qtrue); + parent->s.loopSound = G_SoundIndex( "sound/vehicles/common/fire_lp.wav" ); + } + } +#endif +} +extern char current_speeders; +// Decide whether to explode the vehicle or not. +static void DeathUpdate( Vehicle_t *pVeh ) +{ + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + + if ( level.time >= pVeh->m_iDieTime ) + { + // If the vehicle is not empty. + if ( pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) + { +#ifndef _JK2MP + if (pVeh->m_pPilot) + { + pVeh->m_pPilot->client->noRagTime = -1; // no ragdoll for you + } +#endif + + pVeh->m_pVehicleInfo->EjectAll( pVeh ); +#ifdef _JK2MP + if ( pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) + { //if we've still got people in us, just kill the bastards + if ( pVeh->m_pPilot ) + { + //FIXME: does this give proper credit to the enemy who shot you down? + G_Damage((gentity_t *)pVeh->m_pPilot, (gentity_t *)pVeh->m_pParentEntity, (gentity_t *)pVeh->m_pParentEntity, + NULL, pVeh->m_pParentEntity->playerState->origin, 999, DAMAGE_NO_PROTECTION, MOD_EXPLOSIVE); + } + if ( pVeh->m_iNumPassengers ) + { + int i; + + for ( i = 0; i < pVeh->m_pVehicleInfo->maxPassengers; i++ ) + { + if ( pVeh->m_ppPassengers[i] ) + { + //FIXME: does this give proper credit to the enemy who shot you down? + G_Damage((gentity_t *)pVeh->m_ppPassengers[i], (gentity_t *)pVeh->m_pParentEntity, (gentity_t *)pVeh->m_pParentEntity, + NULL, pVeh->m_pParentEntity->playerState->origin, 999, DAMAGE_NO_PROTECTION, MOD_EXPLOSIVE); + } + } + } + } +#endif + } + + if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) + { //explode now as long as we managed to kick everyone out + vec3_t lMins, lMaxs, bottom; + trace_t trace; + + +#ifndef _JK2MP + // Kill All Client Side Looping Effects + //-------------------------------------- + if (pVeh->m_pVehicleInfo->iExhaustFX) + { + for (int i=0; (im_iExhaustTag[i]!=-1); i++) + { + G_StopEffect(pVeh->m_pVehicleInfo->iExhaustFX, parent->playerModel, pVeh->m_iExhaustTag[i], parent->s.number); + } + } + if (pVeh->m_pVehicleInfo->iArmorLowFX) + { + G_StopEffect(pVeh->m_pVehicleInfo->iArmorLowFX, parent->playerModel, parent->crotchBolt, parent->s.number); + } + if (pVeh->m_pVehicleInfo->iArmorGoneFX) + { + G_StopEffect(pVeh->m_pVehicleInfo->iArmorGoneFX, parent->playerModel, parent->crotchBolt, parent->s.number); + } + //-------------------------------------- +#endif + if ( pVeh->m_pVehicleInfo->iExplodeFX ) + { +#ifdef _JK2MP + vec3_t fxAng; + + VectorSet(fxAng, -90.0f, 0.0f, 0.0f); + G_PlayEffectID( pVeh->m_pVehicleInfo->iExplodeFX, parent->currentOrigin, fxAng ); +#else + G_PlayEffect( pVeh->m_pVehicleInfo->iExplodeFX, parent->currentOrigin, vec3_origin ); +#endif + //trace down and place mark + VectorCopy( parent->currentOrigin, bottom ); + bottom[2] -= 80; + G_VehicleTrace( &trace, parent->currentOrigin, vec3_origin, vec3_origin, bottom, parent->s.number, CONTENTS_SOLID ); + if ( trace.fraction < 1.0f ) + { + VectorCopy( trace.endpos, bottom ); + bottom[2] += 2; +#ifdef _JK2MP + VectorSet(fxAng, -90.0f, 0.0f, 0.0f); + G_PlayEffectID( G_EffectIndex("ships/ship_explosion_mark"), trace.endpos, fxAng ); +#else + G_PlayEffect( "ships/ship_explosion_mark", trace.endpos ); +#endif + } + } + + parent->takedamage = qfalse;//so we don't recursively damage ourselves + if ( pVeh->m_pVehicleInfo->explosionRadius > 0 && pVeh->m_pVehicleInfo->explosionDamage > 0 ) + { + VectorCopy( parent->mins, lMins ); + lMins[2] = -4;//to keep it off the ground a *little* + VectorCopy( parent->maxs, lMaxs ); + VectorCopy( parent->currentOrigin, bottom ); + bottom[2] += parent->mins[2] - 32; + G_VehicleTrace( &trace, parent->currentOrigin, lMins, lMaxs, bottom, parent->s.number, CONTENTS_SOLID ); +#ifdef _JK2MP + G_RadiusDamage( trace.endpos, NULL, pVeh->m_pVehicleInfo->explosionDamage, pVeh->m_pVehicleInfo->explosionRadius, NULL, NULL, MOD_EXPLOSIVE );//FIXME: extern damage and radius or base on fuel +#else + G_RadiusDamage( trace.endpos, player, pVeh->m_pVehicleInfo->explosionDamage, pVeh->m_pVehicleInfo->explosionRadius, NULL, MOD_EXPLOSIVE );//FIXME: extern damage and radius or base on fuel +#endif + } + +#ifdef _JK2MP + parent->think = G_FreeEntity; +#else + parent->e_ThinkFunc = thinkF_G_FreeEntity; +#endif + parent->nextthink = level.time + FRAMETIME; + } + } +#ifndef _JK2MP + else + {//let everyone around me know I'm gonna blow! + if ( !Q_irand( 0, 10 ) ) + {//not so often... + AddSoundEvent( parent, parent->currentOrigin, 512, AEL_DANGER ); + AddSightEvent( parent, parent->currentOrigin, 512, AEL_DANGER, 100 ); + } + } +#endif +} + +// Register all the assets used by this vehicle. +void RegisterAssets( Vehicle_t *pVeh ) +{ +} + +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); + +// Initialize the vehicle. +bool Initialize( Vehicle_t *pVeh ) +{ + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + int i = 0; + + if ( !parent || !parent->client ) + return false; + +#ifdef _JK2MP + parent->client->ps.m_iVehicleNum = 0; +#endif + parent->s.m_iVehicleNum = 0; + { + pVeh->m_iArmor = pVeh->m_pVehicleInfo->armor; + parent->client->pers.maxHealth = parent->client->ps.stats[STAT_MAX_HEALTH] = parent->NPC->stats.health = parent->health = parent->client->ps.stats[STAT_HEALTH] = pVeh->m_iArmor; + pVeh->m_iShields = pVeh->m_pVehicleInfo->shields; +#ifdef _JK2MP + G_VehUpdateShields( parent ); +#endif + parent->client->ps.stats[STAT_ARMOR] = pVeh->m_iShields; + } + parent->mass = pVeh->m_pVehicleInfo->mass; + //initialize the ammo to max + for ( i = 0; i < MAX_VEHICLE_WEAPONS; i++ ) + { + parent->client->ps.ammo[i] = pVeh->weaponStatus[i].ammo = pVeh->m_pVehicleInfo->weapon[i].ammoMax; + } + for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ ) + { + pVeh->turretStatus[i].nextMuzzle = (pVeh->m_pVehicleInfo->turret[i].iMuzzle[i]-1); + parent->client->ps.ammo[MAX_VEHICLE_WEAPONS+i] = pVeh->turretStatus[i].ammo = pVeh->m_pVehicleInfo->turret[i].iAmmoMax; + if ( pVeh->m_pVehicleInfo->turret[i].bAI ) + {//they're going to be finding enemies, init this to NONE + pVeh->turretStatus[i].enemyEntNum = ENTITYNUM_NONE; + } + } + //begin stopped...? + parent->client->ps.speed = 0; + + VectorClear( pVeh->m_vOrientation ); + pVeh->m_vOrientation[YAW] = parent->s.angles[YAW]; + +#ifdef _JK2MP + if ( pVeh->m_pVehicleInfo->gravity && + pVeh->m_pVehicleInfo->gravity != g_gravity.value ) + {//not normal gravity + if ( parent->NPC ) + { + parent->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY; + } + parent->client->ps.gravity = pVeh->m_pVehicleInfo->gravity; + } +#else + if ( pVeh->m_pVehicleInfo->gravity && + pVeh->m_pVehicleInfo->gravity != g_gravity->value ) + {//not normal gravity + parent->svFlags |= SVF_CUSTOM_GRAVITY; + parent->client->ps.gravity = pVeh->m_pVehicleInfo->gravity; + } +#endif + + /* + if ( pVeh->m_iVehicleTypeID == VH_FIGHTER ) + { + pVeh->m_ulFlags = VEH_GEARSOPEN; + } + else + */ + //why?! -rww + { + pVeh->m_ulFlags = 0; + } + pVeh->m_fTimeModifier = 1.0f; + pVeh->m_iBoarding = 0; + pVeh->m_bWasBoarding = false; + pVeh->m_pOldPilot = NULL; + VectorClear(pVeh->m_vBoardingVelocity); + pVeh->m_pPilot = NULL; + memset( &pVeh->m_ucmd, 0, sizeof( usercmd_t ) ); + pVeh->m_iDieTime = 0; + pVeh->m_EjectDir = VEH_EJECT_LEFT; + + //pVeh->m_iDriverTag = -1; + //pVeh->m_iLeftExhaustTag = -1; + //pVeh->m_iRightExhaustTag = -1; + //pVeh->m_iGun1Tag = -1; + //pVeh->m_iGun1Bone = -1; + //pVeh->m_iLeftWingBone = -1; + //pVeh->m_iRightWingBone = -1; + memset( pVeh->m_iExhaustTag, -1, sizeof( int ) * MAX_VEHICLE_EXHAUSTS ); + memset( pVeh->m_iMuzzleTag, -1, sizeof( int ) * MAX_VEHICLE_MUZZLES ); + // FIXME! Use external values read from the vehicle data file! +#ifndef _JK2MP //blargh, fixme + memset( pVeh->m_Muzzles, 0, sizeof( Muzzle ) * MAX_VEHICLE_MUZZLES ); +#endif + pVeh->m_iDroidUnitTag = -1; + + //initialize to blaster, just since it's a basic weapon and there's no lightsaber crap...? + parent->client->ps.weapon = WP_BLASTER; + parent->client->ps.weaponstate = WEAPON_READY; + parent->client->ps.stats[STAT_WEAPONS] |= (1<m_ulFlags |= VEH_GEARSOPEN; + BG_SetAnim(pVeh->m_pParentEntity->playerState, + bgAllAnims[pVeh->m_pParentEntity->localAnimIndex].anims, + SETANIM_BOTH, BOTH_VS_IDLE, iFlags, iBlend); +#else + NPC_SetAnim( pVeh->m_pParentEntity, SETANIM_BOTH, BOTH_VS_IDLE, iFlags, iBlend ); +#endif + } + + return true; +} + + + +// Like a think or move command, this updates various vehicle properties. +#ifdef _JK2MP +void G_VehicleDamageBoxSizing(Vehicle_t *pVeh); //declared below +#endif +static bool Update( Vehicle_t *pVeh, const usercmd_t *pUmcd ) +{ + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + gentity_t *pilotEnt; + //static float fMod = 1000.0f / 60.0f; + vec3_t vVehAngles; + int i; + int prevSpeed; + int nextSpeed; + int curTime; + int halfMaxSpeed; + playerState_t *parentPS; + qboolean linkHeld = qfalse; + + +#ifdef _JK2MP + parentPS = pVeh->m_pParentEntity->playerState; +#else + parentPS = &pVeh->m_pParentEntity->client->ps; +#endif + +#ifndef _JK2MP//SP + curTime = level.time; +#elif QAGAME//MP GAME + curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + curTime = pm->cmd.serverTime; +#endif + + //increment the ammo for all rechargeable weapons + for ( i = 0; i < MAX_VEHICLE_WEAPONS; i++ ) + { + if ( pVeh->m_pVehicleInfo->weapon[i].ID > VEH_WEAPON_BASE//have a weapon in this slot + && pVeh->m_pVehicleInfo->weapon[i].ammoRechargeMS//its ammo is rechargable + && pVeh->weaponStatus[i].ammo < pVeh->m_pVehicleInfo->weapon[i].ammoMax//its ammo is below max + && pUmcd->serverTime-pVeh->weaponStatus[i].lastAmmoInc >= pVeh->m_pVehicleInfo->weapon[i].ammoRechargeMS )//enough time has passed + {//add 1 to the ammo + pVeh->weaponStatus[i].lastAmmoInc = pUmcd->serverTime; + pVeh->weaponStatus[i].ammo++; + //NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array + if ( parent && parent->client ) + { + parent->client->ps.ammo[i] = pVeh->weaponStatus[i].ammo; + } + } + } + for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ ) + { + if ( pVeh->m_pVehicleInfo->turret[i].iWeapon > VEH_WEAPON_BASE//have a weapon in this slot + && pVeh->m_pVehicleInfo->turret[i].iAmmoRechargeMS//its ammo is rechargable + && pVeh->turretStatus[i].ammo < pVeh->m_pVehicleInfo->turret[i].iAmmoMax//its ammo is below max + && pUmcd->serverTime-pVeh->turretStatus[i].lastAmmoInc >= pVeh->m_pVehicleInfo->turret[i].iAmmoRechargeMS )//enough time has passed + {//add 1 to the ammo + pVeh->turretStatus[i].lastAmmoInc = pUmcd->serverTime; + pVeh->turretStatus[i].ammo++; + //NOTE: in order to send the vehicle's ammo info to the client, we copy the ammo into the first 2 ammo slots on the vehicle NPC's client->ps.ammo array + if ( parent && parent->client ) + { + parent->client->ps.ammo[MAX_VEHICLE_WEAPONS+i] = pVeh->turretStatus[i].ammo; + } + } + } + + //increment shields for rechargable shields + if ( pVeh->m_pVehicleInfo->shieldRechargeMS + && parentPS->stats[STAT_ARMOR] > 0 //still have some shields left + && parentPS->stats[STAT_ARMOR] < pVeh->m_pVehicleInfo->shields//its below max + && pUmcd->serverTime-pVeh->lastShieldInc >= pVeh->m_pVehicleInfo->shieldRechargeMS )//enough time has passed + { + parentPS->stats[STAT_ARMOR]++; + if ( parentPS->stats[STAT_ARMOR] > pVeh->m_pVehicleInfo->shields ) + { + parentPS->stats[STAT_ARMOR] = pVeh->m_pVehicleInfo->shields; + } + pVeh->m_iShields = parentPS->stats[STAT_ARMOR]; +#ifdef _JK2MP + G_VehUpdateShields( parent ); +#endif + } + +#ifdef _JK2MP //sometimes this gets out of whack, probably init'ing + if (parent && parent->r.ownerNum != parent->s.owner) + { + parent->s.owner = parent->r.ownerNum; + } + + //keep the PS value in sync. set it up here in case we return below at some point. + if (pVeh->m_iBoarding) + { + parent->client->ps.vehBoarding = qtrue; + } + else + { + parent->client->ps.vehBoarding = qfalse; + } +#endif + + // See whether this vehicle should be dieing or dead. + if ( pVeh->m_iDieTime != 0 +#ifndef _JK2MP //sometimes this gets out of whack, probably init'ing + || (parent->health <= 0) +#endif + ) + {//NOTE!!!: This HAS to be consistent with cgame!!! + // Keep track of the old orientation. + VectorCopy( pVeh->m_vOrientation, pVeh->m_vPrevOrientation ); + + // Process the orient commands. + pVeh->m_pVehicleInfo->ProcessOrientCommands( pVeh ); + // Need to copy orientation to our entity's viewangles so that it renders at the proper angle and currentAngles is correct. + SetClientViewAngle( parent, pVeh->m_vOrientation ); + if ( pVeh->m_pPilot ) + { + SetClientViewAngle( (gentity_t *)pVeh->m_pPilot, pVeh->m_vOrientation ); + } + /* + for ( i = 0; i < pVeh->m_pVehicleInfo->maxPassengers; i++ ) + { + if ( pVeh->m_ppPassengers[i] ) + { + SetClientViewAngle( (gentity_t *)pVeh->m_ppPassengers[i], pVeh->m_vOrientation ); + } + } + */ + + // Process the move commands. + pVeh->m_pVehicleInfo->ProcessMoveCommands( pVeh ); + + // Setup the move direction. + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + AngleVectors( pVeh->m_vOrientation, parent->client->ps.moveDir, NULL, NULL ); + } + else + { + VectorSet(vVehAngles, 0, pVeh->m_vOrientation[YAW], 0); + AngleVectors( vVehAngles, parent->client->ps.moveDir, NULL, NULL ); + } + pVeh->m_pVehicleInfo->DeathUpdate( pVeh ); + return false; + } + // Vehicle dead! + +#ifdef _JK2MP + else if ( parent->health <= 0 ) + { + // Instant kill. + if (pVeh->m_pVehicleInfo->type == VH_FIGHTER && + pVeh->m_iLastImpactDmg > 500) + { //explode instantly in inferno-y death + pVeh->m_pVehicleInfo->StartDeathDelay( pVeh, -1/* -1 causes instant death */); + } + else + { + pVeh->m_pVehicleInfo->StartDeathDelay( pVeh, 0 ); + } + pVeh->m_pVehicleInfo->DeathUpdate( pVeh ); + return false; + } +#endif + +#ifdef _JK2MP //special check in case someone disconnects/dies while boarding +#ifdef QAGAME + if (parent->spawnflags & 1) + { + if (pVeh->m_pPilot || !pVeh->m_bHasHadPilot) + { + if (pVeh->m_pPilot && !pVeh->m_bHasHadPilot) + { + pVeh->m_bHasHadPilot = qtrue; + pVeh->m_iPilotLastIndex = pVeh->m_pPilot->s.number; + } + pVeh->m_iPilotTime = level.time + parent->damage; + } + else if (pVeh->m_iPilotTime) + { //die + gentity_t *oldPilot = &g_entities[pVeh->m_iPilotLastIndex]; + + if (!oldPilot->inuse || !oldPilot->client || + oldPilot->client->pers.connected != CON_CONNECTED) + { //no longer in the game? + G_Damage(parent, parent, parent, NULL, parent->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE); + } + else + { + vec3_t v; + VectorSubtract(parent->client->ps.origin, oldPilot->client->ps.origin, v); + + if (VectorLength(v) < parent->speed) + { //they are still within the minimum distance to their vehicle + pVeh->m_iPilotTime = level.time + parent->damage; + } + else if (pVeh->m_iPilotTime < level.time) + { //dying time + G_Damage(parent, parent, parent, NULL, parent->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE); + } + } + } + } +#endif +#else + if (parent->spawnflags & 1) + {//NOTE: in SP, this actually just checks LOS to the Player + if (pVeh->m_iPilotTime < level.time) + {//do another check? + if ( !player || G_ClearLineOfSight(pVeh->m_pParentEntity->currentOrigin, player->currentOrigin, pVeh->m_pParentEntity->s.number, MASK_OPAQUE ) ) + { + pVeh->m_iPilotTime = level.time + pVeh->m_pParentEntity->endFrame; + } + } + if (pVeh->m_iPilotTime && pVeh->m_iPilotTime < level.time) + { //die + //FIXME: does this give proper credit to the enemy who shot you down? + G_Damage(parent, player, player, NULL, parent->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE); + } + } +#endif + +#ifndef _JK2MP +// if (level.timem_iTurboTime || pVeh->m_pVehicleInfo->type==VH_ANIMAL) + // always knock guys around now... + { + vec3_t dir; + vec3_t projectedPosition; + VectorCopy(parent->client->ps.velocity, dir); + VectorMA(parent->currentOrigin, 0.1f, dir, projectedPosition); + + float force = VectorNormalize(dir); + force /= 10.0f; + if (force>30.0f) + { + trace_t tr; + G_VehicleTrace(&tr, parent->currentOrigin, parent->mins, parent->maxs, projectedPosition, parent->s.number, CONTENTS_BODY); + if (tr.fraction<1.0f && + !tr.allsolid && + !tr.startsolid && + tr.entityNum!=ENTITYNUM_NONE && + tr.entityNum!=ENTITYNUM_WORLD && + (level.timem_iTurboTime || Q_irand(0,3)==0)) + { + gentity_t* other = &g_entities[tr.entityNum]; + if (other && other->client && !other->s.m_iVehicleNum) + { + G_Throw( other, dir, force/10.0f ); + G_Knockdown( other, parent, dir, force, qtrue ); + G_Damage( other, player, player, parent->client->ps.velocity, parent->currentOrigin, force, DAMAGE_NO_ARMOR|DAMAGE_EXTRA_KNOCKBACK, MOD_IMPACT); + } + } + } + } +#endif + +#ifdef _JK2MP //special check in case someone disconnects/dies while boarding + if (pVeh->m_iBoarding != 0) + { + pilotEnt = (gentity_t *)pVeh->m_pPilot; + if (pilotEnt) + { + if (!pilotEnt->inuse || !pilotEnt->client || pilotEnt->health <= 0 || + pilotEnt->client->pers.connected != CON_CONNECTED) + { + pVeh->m_pVehicleInfo->Eject( pVeh, pVeh->m_pPilot, qtrue ); + return false; + } + } + } +#endif + + // If we're not done mounting, can't do anything. + if ( pVeh->m_iBoarding != 0 ) + { + if (!pVeh->m_bWasBoarding) + { + VectorCopy(parentPS->velocity, pVeh->m_vBoardingVelocity); + pVeh->m_bWasBoarding = true; + } + + // See if we're done boarding. + if ( pVeh->m_iBoarding > -1 && pVeh->m_iBoarding <= level.time ) + { + pVeh->m_bWasBoarding = false; + pVeh->m_iBoarding = 0; + } + else + { +#ifdef _JK2MP + goto maintainSelfDuringBoarding; +#else + return false; +#endif + } + } + + parent = (gentity_t *)pVeh->m_pParentEntity; + + // Validate vehicle. + if ( !parent || !parent->client || parent->health <= 0 ) + return false; + + // See if any of the riders are dead and if so kick em off. + if ( pVeh->m_pPilot ) + { + pilotEnt = (gentity_t *)pVeh->m_pPilot; + +#ifdef _JK2MP + if (!pilotEnt->inuse || !pilotEnt->client || pilotEnt->health <= 0 || + pilotEnt->client->pers.connected != CON_CONNECTED) +#else + if (pilotEnt->health <= 0) +#endif + { + pVeh->m_pVehicleInfo->Eject( pVeh, pVeh->m_pPilot, qtrue ); + } + } + +#ifdef _JK2MP + // Copy over the commands for local storage. + memcpy( &parent->client->pers.cmd, &pVeh->m_ucmd, sizeof( usercmd_t ) ); + pVeh->m_ucmd.buttons &= ~(BUTTON_TALK);//|BUTTON_GESTURE); //don't want some of these buttons +#else + // Copy over the commands for local storage. + memcpy( &pVeh->m_ucmd, pUmcd, sizeof( usercmd_t ) ); + memcpy( &parent->client->pers.lastCommand, pUmcd, sizeof( usercmd_t ) ); +#endif + + /* + // Update time modifier. + pVeh->m_fTimeModifier = pVeh->m_ucmd.serverTime - parent->client->ps.commandTime; + //sanity check + if ( pVeh->m_fTimeModifier < 1 ) + { + pVeh->m_fTimeModifier = 1; + } + else if ( pVeh->m_fTimeModifier > 200 ) + { + pVeh->m_fTimeModifier = 200; + } + //normalize to 1.0f at 20fps + pVeh->m_fTimeModifier = pVeh->m_fTimeModifier / fMod; + */ + + //check for weapon linking/unlinking command + for ( i = 0; i < MAX_VEHICLE_WEAPONS; i++ ) + {//HMM... can't get a seperate command for each weapon, so do them all...? + if ( pVeh->m_pVehicleInfo->weapon[i].linkable == 2 ) + {//always linked + //FIXME: just set this once, on Initialize...? + if ( !pVeh->weaponStatus[i].linked ) + { + pVeh->weaponStatus[i].linked = qtrue; + } + } +#ifdef _JK2MP + else if ( (pVeh->m_ucmd.buttons&BUTTON_USE_HOLDABLE) ) +#else + //FIXME: implement... just a console command bound to a key? + else if ( 0 ) +#endif + {//pilot pressed the "weapon link" toggle button + playerState_t *pilotPS; +#ifdef _JK2MP + bgEntity_t *rider = NULL; + if (parent->s.owner != ENTITYNUM_NONE) + { + rider = PM_BGEntForNum(parent->s.owner); //&g_entities[parent->r.ownerNum]; + } + pilotPS = rider->playerState; +#else + gentity_t *rider = parent->owner; + pilotPS = &rider->client->ps; +#endif + if ( !pVeh->linkWeaponToggleHeld )//so we don't hold it down and toggle it back and forth + {//okay to toggle + if ( pVeh->m_pVehicleInfo->weapon[i].linkable == 1 ) + {//link-toggleable + pVeh->weaponStatus[i].linked = !pVeh->weaponStatus[i].linked; + } + } + linkHeld = qtrue; + } + } + if ( linkHeld ) + { + //so we don't hold it down and toggle it back and forth + pVeh->linkWeaponToggleHeld = qtrue; + } + else + { + //so we don't hold it down and toggle it back and forth + pVeh->linkWeaponToggleHeld = qfalse; + } +#ifdef _JK2MP + //now pass it over the network so cgame knows about it + //NOTE: SP can just cheat and check directly + parentPS->vehWeaponsLinked = qfalse; + for ( i = 0; i < MAX_VEHICLE_WEAPONS; i++ ) + {//HMM... can't get a seperate command for each weapon, so do them all...? + if ( pVeh->weaponStatus[i].linked ) + { + parentPS->vehWeaponsLinked = qtrue; + } + } +#endif + +#ifdef QAGAME + for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ ) + {//HMM... can't get a seperate command for each weapon, so do them all...? + VEH_TurretThink( pVeh, parent, i ); + } +#endif + +#ifdef _JK2MP +maintainSelfDuringBoarding: + + if (pVeh->m_pPilot && pVeh->m_pPilot->playerState && pVeh->m_iBoarding != 0) + { + VectorCopy(pVeh->m_vOrientation, pVeh->m_pPilot->playerState->viewangles); + pVeh->m_ucmd.buttons = 0; + pVeh->m_ucmd.forwardmove = 0; + pVeh->m_ucmd.rightmove = 0; + pVeh->m_ucmd.upmove = 0; + } +#endif + + // Keep track of the old orientation. + VectorCopy( pVeh->m_vOrientation, pVeh->m_vPrevOrientation ); + + // Process the orient commands. + pVeh->m_pVehicleInfo->ProcessOrientCommands( pVeh ); + // Need to copy orientation to our entity's viewangles so that it renders at the proper angle and currentAngles is correct. + SetClientViewAngle( parent, pVeh->m_vOrientation ); + if ( pVeh->m_pPilot ) + { +#ifdef _JK2MP + if ( !BG_UnrestrainedPitchRoll( pVeh->m_pPilot->playerState, pVeh ) ) + { + vec3_t newVAngle; + newVAngle[PITCH] = pVeh->m_pPilot->playerState->viewangles[PITCH]; + newVAngle[YAW] = pVeh->m_pPilot->playerState->viewangles[YAW]; + newVAngle[ROLL] = pVeh->m_vOrientation[ROLL]; + SetClientViewAngle( (gentity_t *)pVeh->m_pPilot, newVAngle ); + } +#else + if ( !BG_UnrestrainedPitchRoll( &pVeh->m_pPilot->client->ps, pVeh ) ) + { + SetClientViewAngle( (gentity_t *)pVeh->m_pPilot, pVeh->m_vOrientation ); + } +#endif + } + /* + for ( i = 0; i < pVeh->m_pVehicleInfo->maxPassengers; i++ ) + { + if ( pVeh->m_ppPassengers[i] ) + { + SetClientViewAngle( (gentity_t *)pVeh->m_ppPassengers[i], pVeh->m_vOrientation ); + } + } + */ + + // Process the move commands. + prevSpeed = parentPS->speed; + pVeh->m_pVehicleInfo->ProcessMoveCommands( pVeh ); + nextSpeed = parentPS->speed; + halfMaxSpeed = pVeh->m_pVehicleInfo->speedMax*0.5f; + + +// Shifting Sounds +//===================================================================== + if (pVeh->m_iTurboTimem_iSoundDebounceTimerprevSpeed && nextSpeed>halfMaxSpeed && prevSpeedhalfMaxSpeed && !Q_irand(0,1000))) + ) + { + int shiftSound = Q_irand(1,4); + switch (shiftSound) + { + case 1: shiftSound=pVeh->m_pVehicleInfo->soundShift1; break; + case 2: shiftSound=pVeh->m_pVehicleInfo->soundShift2; break; + case 3: shiftSound=pVeh->m_pVehicleInfo->soundShift3; break; + case 4: shiftSound=pVeh->m_pVehicleInfo->soundShift4; break; + } + if (shiftSound) + { + pVeh->m_iSoundDebounceTimer = curTime + Q_irand(1000, 4000); +#ifdef _JK2MP + // TODO: MP Shift Sound Playback +#else + // NOTE: Use this type so it's spatialized and updates play origin as bike moves - MCG + G_SoundIndexOnEnt( pVeh->m_pParentEntity, CHAN_AUTO, shiftSound); +#endif + } + } +//===================================================================== + + + // Setup the move direction. + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + AngleVectors( pVeh->m_vOrientation, parent->client->ps.moveDir, NULL, NULL ); + } + else + { + VectorSet(vVehAngles, 0, pVeh->m_vOrientation[YAW], 0); + AngleVectors( vVehAngles, parent->client->ps.moveDir, NULL, NULL ); + } + +#ifdef _JK2MP + if (pVeh->m_pVehicleInfo->surfDestruction) + { + if (pVeh->m_iRemovedSurfaces) + { + gentity_t *killer = parent; + G_VehicleDamageBoxSizing(pVeh); + + //damage him constantly if any chunks are currently taken off + if (parent->client->ps.otherKiller < ENTITYNUM_WORLD && + parent->client->ps.otherKillerTime > level.time) + { + gentity_t *potentialKiller = &g_entities[parent->client->ps.otherKiller]; + + if (potentialKiller->inuse && potentialKiller->client) + { //he's valid I guess + killer = potentialKiller; + } + } + //FIXME: aside from bypassing shields, maybe set m_iShields to 0, too... ? + G_Damage(parent, killer, killer, NULL, parent->client->ps.origin, Q_irand(2, 5), DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR, MOD_SUICIDE); + } + + //make sure playerstate value stays in sync + parent->client->ps.vehSurfaces = pVeh->m_iRemovedSurfaces; + } +#endif + +#ifdef _JK2MP + //keep the PS value in sync + if (pVeh->m_iBoarding) + { + parent->client->ps.vehBoarding = qtrue; + } + else + { + parent->client->ps.vehBoarding = qfalse; + } +#endif + +#ifndef _JK2MP + // Make sure the vehicle takes on the enemy of it's rider (for homing missles for instance). + if ( pVeh->m_pPilot ) + { + parent->enemy = pVeh->m_pPilot->enemy; + } +#endif + + + return true; +} + + +// Update the properties of a Rider (that may reflect what happens to the vehicle). +static bool UpdateRider( Vehicle_t *pVeh, bgEntity_t *pRider, usercmd_t *pUmcd ) +{ + gentity_t *parent; + gentity_t *rider; + + if ( pVeh->m_iBoarding != 0 && pVeh->m_iDieTime==0) + return true; + + parent = (gentity_t *)pVeh->m_pParentEntity; + rider = (gentity_t *)pRider; +#ifdef _JK2MP + //MG FIXME !! Single player needs update! + if ( rider && rider->client + && parent && parent->client ) + {//so they know who we're locking onto with our rockets, if anyone + rider->client->ps.rocketLockIndex = parent->client->ps.rocketLockIndex; + rider->client->ps.rocketLockTime = parent->client->ps.rocketLockTime; + rider->client->ps.rocketTargetTime = parent->client->ps.rocketTargetTime; + } +#endif + // Regular exit. + if ( pUmcd->buttons & BUTTON_USE && pVeh->m_pVehicleInfo->type!=VH_SPEEDER) + { + if ( pVeh->m_pVehicleInfo->type == VH_WALKER ) + {//just get the fuck out + pVeh->m_EjectDir = VEH_EJECT_REAR; + if ( pVeh->m_pVehicleInfo->Eject( pVeh, pRider, qfalse ) ) + return false; + } + else if ( !(pVeh->m_ulFlags & VEH_FLYING)) + { + // If going too fast, roll off. + if ((parent->client->ps.speed<=600) && pUmcd->rightmove!=0) + { + if ( pVeh->m_pVehicleInfo->Eject( pVeh, pRider, qfalse ) ) + { + animNumber_t Anim; + int iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD | SETANIM_FLAG_HOLDLESS, iBlend = 300; + if ( pUmcd->rightmove > 0 ) + { + Anim = BOTH_ROLL_R; + pVeh->m_EjectDir = VEH_EJECT_RIGHT; + } + else + { + Anim = BOTH_ROLL_L; + pVeh->m_EjectDir = VEH_EJECT_LEFT; + } + VectorScale( parent->client->ps.velocity, 0.25f, rider->client->ps.velocity ); +#if 1 + Vehicle_SetAnim( rider, SETANIM_BOTH, Anim, iFlags, iBlend ); +#else + +#endif + //PM_SetAnim(pm,SETANIM_BOTH,anim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS); + rider->client->ps.weaponTime = rider->client->ps.torsoAnimTimer - 200;//just to make sure it's cleared when roll is done + G_AddEvent( rider, EV_ROLL, 0 ); + return false; + } + } + else + { + // FIXME: Check trace to see if we should start playing the animation. + animNumber_t Anim; + int iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD, iBlend = 500; + if ( pUmcd->rightmove > 0 ) + { + Anim = BOTH_VS_DISMOUNT_R; + pVeh->m_EjectDir = VEH_EJECT_RIGHT; + } + else + { + Anim = BOTH_VS_DISMOUNT_L; + pVeh->m_EjectDir = VEH_EJECT_LEFT; + } + + if ( pVeh->m_iBoarding <= 1 ) + { + int iAnimLen; + // NOTE: I know I shouldn't reuse pVeh->m_iBoarding so many times for so many different + // purposes, but it's not used anywhere else right here so why waste memory??? +#ifdef _JK2MP + iAnimLen = BG_AnimLength( rider->localAnimIndex, Anim ); +#else + iAnimLen = PM_AnimLength( pRider->client->clientInfo.animFileIndex, Anim ); +#endif + pVeh->m_iBoarding = level.time + iAnimLen; + // Weird huh? Well I wanted to reuse flags and this should never be set in an + // entity, so what the heck. +#ifdef _JK2MP + rider->flags |= FL_VEH_BOARDING; +#else + rider->client->ps.eFlags |= EF_VEH_BOARDING; +#endif + + // Make sure they can't fire when leaving. + rider->client->ps.weaponTime = iAnimLen; + } + + VectorScale( parent->client->ps.velocity, 0.25f, rider->client->ps.velocity ); + + Vehicle_SetAnim( rider, SETANIM_BOTH, Anim, iFlags, iBlend ); + } + } + // Flying, so just fall off. + else + { + pVeh->m_EjectDir = VEH_EJECT_LEFT; + if ( pVeh->m_pVehicleInfo->Eject( pVeh, pRider, qfalse ) ) + return false; + } + } + + // Getting off animation complete (if we had one going)? +#ifdef _JK2MP + if ( pVeh->m_iBoarding < level.time && (rider->flags & FL_VEH_BOARDING) ) + { + rider->flags &= ~FL_VEH_BOARDING; +#else + if ( pVeh->m_iBoarding < level.time && (rider->client->ps.eFlags & EF_VEH_BOARDING) ) + { + rider->client->ps.eFlags &= ~EF_VEH_BOARDING; +#endif + // Eject this guy now. + if ( pVeh->m_pVehicleInfo->Eject( pVeh, pRider, qfalse ) ) + { + return false; + } + } + + if ( pVeh->m_pVehicleInfo->type != VH_FIGHTER + && pVeh->m_pVehicleInfo->type != VH_WALKER ) + { + // Jump off. + if ( pUmcd->upmove > 0 ) + { + +// NOT IN MULTI PLAYER! +//=================================================================== +#ifndef _JK2MP + float riderRightDot = G_CanJumpToEnemyVeh(pVeh, pUmcd); + if (riderRightDot!=0.0f) + { + // Eject Player From Current Vehicle + //----------------------------------- + pVeh->m_EjectDir = VEH_EJECT_TOP; + pVeh->m_pVehicleInfo->Eject( pVeh, pRider, qtrue ); + + // Send Current Vehicle Spinning Out Of Control + //---------------------------------------------- + pVeh->m_pVehicleInfo->StartDeathDelay(pVeh, 10000); + pVeh->m_ulFlags |= (VEH_OUTOFCONTROL); + VectorScale(pVeh->m_pParentEntity->client->ps.velocity, 1.0f, pVeh->m_pParentEntity->pos3); + + // Todo: Throw Old Vehicle Away From The New Vehicle Some + //------------------------------------------------------- + vec3_t toEnemy; + VectorSubtract(pVeh->m_pParentEntity->currentOrigin, rider->enemy->currentOrigin, toEnemy); + VectorNormalize(toEnemy); + G_Throw(pVeh->m_pParentEntity, toEnemy, 50); + + // Start Boarding On Enemy's Vehicle + //----------------------------------- + Vehicle_t* enemyVeh = G_IsRidingVehicle(rider->enemy); + enemyVeh->m_iBoarding = (riderRightDot>0)?(VEH_MOUNT_THROW_RIGHT):(VEH_MOUNT_THROW_LEFT); + enemyVeh->m_pVehicleInfo->Board(enemyVeh, rider); + } + + // Don't Jump Off If Holding Strafe Key and Moving Fast + else if (pUmcd->rightmove && (parent->client->ps.speed>=10)) + { + return true; + } +#endif +//=================================================================== + + if ( pVeh->m_pVehicleInfo->Eject( pVeh, pRider, qfalse ) ) + { + // Allow them to force jump off. + VectorScale( parent->client->ps.velocity, 0.5f, rider->client->ps.velocity ); + rider->client->ps.velocity[2] += JUMP_VELOCITY; +#ifdef _JK2MP + rider->client->ps.fd.forceJumpZStart = rider->client->ps.origin[2]; + + if (!trap_ICARUS_TaskIDPending(rider, TID_CHAN_VOICE)) +#else + rider->client->ps.pm_flags |= ( PMF_JUMPING | PMF_JUMP_HELD ); + rider->client->ps.forceJumpZStart = rider->client->ps.origin[2]; + + if ( !Q3_TaskIDPending( rider, TID_CHAN_VOICE ) ) +#endif + { + G_AddEvent( rider, EV_JUMP, 0 ); + } +#if 1 + Vehicle_SetAnim( rider, SETANIM_BOTH, BOTH_JUMP1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD, 300 ); + + // Force first person mode when exiting the vehicle + // If you're carrying a saber, it will get reset later + extern void Cvar_SetValue(const char *var_name, float value); + Cvar_SetValue("cg_thirdPerson", 0); +#else + +#endif + return false; + } + } + + // Roll off. +#ifdef _JK2MP + if ( pUmcd->upmove < 0 ) + { + animNumber_t Anim = BOTH_ROLL_B; + pVeh->m_EjectDir = VEH_EJECT_REAR; + if ( pUmcd->rightmove > 0 ) + { + Anim = BOTH_ROLL_R; + pVeh->m_EjectDir = VEH_EJECT_RIGHT; + } + else if ( pUmcd->rightmove < 0 ) + { + Anim = BOTH_ROLL_L; + pVeh->m_EjectDir = VEH_EJECT_LEFT; + } + else if ( pUmcd->forwardmove < 0 ) + { + Anim = BOTH_ROLL_B; + pVeh->m_EjectDir = VEH_EJECT_REAR; + } + else if ( pUmcd->forwardmove > 0 ) + { + Anim = BOTH_ROLL_F; + pVeh->m_EjectDir = VEH_EJECT_FRONT; + } + + if ( pVeh->m_pVehicleInfo->Eject( pVeh, pRider, qfalse ) ) + { + if ( !(pVeh->m_ulFlags & VEH_FLYING) ) + { + VectorScale( parent->client->ps.velocity, 0.25f, rider->client->ps.velocity ); +#if 1 + Vehicle_SetAnim( rider, SETANIM_BOTH, Anim, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD | SETANIM_FLAG_HOLDLESS, 300 ); +#else + +#endif + //PM_SetAnim(pm,SETANIM_BOTH,anim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS); + rider->client->ps.weaponTime = rider->client->ps.torsoAnimTimer - 200;//just to make sure it's cleared when roll is done + G_AddEvent( rider, EV_ROLL, 0 ); + } + return false; + } + + } +#endif + } + + return true; +} + +#ifdef _JK2MP //we want access to this one clientside, but it's the only +//generic vehicle function we care about over there +#include "../namespace_begin.h" +extern void AttachRidersGeneric( Vehicle_t *pVeh ); +#include "../namespace_end.h" +#endif + +// Attachs all the riders of this vehicle to their appropriate tag (*driver, *pass1, *pass2, whatever...). +static void AttachRiders( Vehicle_t *pVeh ) +{ +#ifdef _JK2MP + int i = 0; + + AttachRidersGeneric(pVeh); + + if (pVeh->m_pPilot) + { + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + gentity_t *pilot = (gentity_t *)pVeh->m_pPilot; + pilot->waypoint = parent->waypoint; // take the veh's waypoint as your own + + //assuming we updated him relative to the bolt in AttachRidersGeneric + G_SetOrigin( pilot, pilot->client->ps.origin ); + trap_LinkEntity( pilot ); + } + + if (pVeh->m_pOldPilot) + { + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + gentity_t *oldpilot = (gentity_t *)pVeh->m_pOldPilot; + oldpilot->waypoint = parent->waypoint; // take the veh's waypoint as your own + + //assuming we updated him relative to the bolt in AttachRidersGeneric + G_SetOrigin( oldpilot, oldpilot->client->ps.origin ); + trap_LinkEntity( oldpilot ); + } + + //attach passengers + while (i < pVeh->m_iNumPassengers) + { + if (pVeh->m_ppPassengers[i]) + { + mdxaBone_t boltMatrix; + vec3_t yawOnlyAngles; + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + gentity_t *pilot = (gentity_t *)pVeh->m_ppPassengers[i]; + int crotchBolt; + + assert(parent->ghoul2); + crotchBolt = trap_G2API_AddBolt(parent->ghoul2, 0, "*driver"); + assert(parent->client); + assert(pilot->client); + + VectorSet(yawOnlyAngles, 0, parent->client->ps.viewangles[YAW], 0); + + // Get the driver tag. + trap_G2API_GetBoltMatrix( parent->ghoul2, 0, crotchBolt, &boltMatrix, + yawOnlyAngles, parent->client->ps.origin, + level.time, NULL, parent->modelScale ); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, pilot->client->ps.origin ); + + G_SetOrigin( pilot, pilot->client->ps.origin ); + trap_LinkEntity( pilot ); + } + i++; + } + + //attach droid + if (pVeh->m_pDroidUnit + && pVeh->m_iDroidUnitTag != -1) + { + mdxaBone_t boltMatrix; + vec3_t yawOnlyAngles, fwd; + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + gentity_t *droid = (gentity_t *)pVeh->m_pDroidUnit; + + assert(parent->ghoul2); + assert(parent->client); + //assert(droid->client); + + if ( droid->client ) + { + VectorSet(yawOnlyAngles, 0, parent->client->ps.viewangles[YAW], 0); + + // Get the droid tag. + trap_G2API_GetBoltMatrix( parent->ghoul2, 0, pVeh->m_iDroidUnitTag, &boltMatrix, + yawOnlyAngles, parent->currentOrigin, + level.time, NULL, parent->modelScale ); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, droid->client->ps.origin ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, fwd ); + vectoangles( fwd, droid->client->ps.viewangles ); + + G_SetOrigin( droid, droid->client->ps.origin ); + G_SetAngles( droid, droid->client->ps.viewangles); + SetClientViewAngle( droid, droid->client->ps.viewangles ); + trap_LinkEntity( droid ); + + if ( droid->NPC ) + { + NPC_SetAnim( droid, SETANIM_BOTH, BOTH_STAND2, (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD) ); + droid->client->ps.legsTimer = 500; + droid->client->ps.torsoTimer = 500; + } + } + } +#else + // If we have a pilot, attach him to the driver tag. + if ( pVeh->m_pPilot ) + { + gentity_t * const parent = pVeh->m_pParentEntity; + gentity_t * const pilot = pVeh->m_pPilot; + mdxaBone_t boltMatrix; + + pilot->waypoint = parent->waypoint; // take the veh's waypoint as your own + + // Get the driver tag. + gi.G2API_GetBoltMatrix( parent->ghoul2, parent->playerModel, parent->crotchBolt, &boltMatrix, + pVeh->m_vOrientation, parent->currentOrigin, + (cg.time?cg.time:level.time), NULL, parent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, pilot->client->ps.origin ); + G_SetOrigin( pilot, pilot->client->ps.origin ); + gi.linkentity( pilot ); + } + // If we have a pilot, attach him to the driver tag. + if ( pVeh->m_pOldPilot ) + { + gentity_t * const parent = pVeh->m_pParentEntity; + gentity_t * const pilot = pVeh->m_pOldPilot; + mdxaBone_t boltMatrix; + + pilot->waypoint = parent->waypoint; // take the veh's waypoint as your own + + // Get the driver tag. + gi.G2API_GetBoltMatrix( parent->ghoul2, parent->playerModel, parent->crotchBolt, &boltMatrix, + pVeh->m_vOrientation, parent->currentOrigin, + (cg.time?cg.time:level.time), NULL, parent->s.modelScale ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, pilot->client->ps.origin ); + G_SetOrigin( pilot, pilot->client->ps.origin ); + gi.linkentity( pilot ); + } +#endif +} + +// Make someone invisible and un-collidable. +static void Ghost( Vehicle_t *pVeh, bgEntity_t *pEnt ) +{ + gentity_t *ent; + + if ( !pEnt ) + return; + + ent = (gentity_t *)pEnt; + + ent->s.eFlags |= EF_NODRAW; + if ( ent->client ) + { + ent->client->ps.eFlags |= EF_NODRAW; + } +#ifdef _JK2MP + ent->r.contents = 0; +#else + ent->contents = 0; +#endif +} + +// Make someone visible and collidable. +static void UnGhost( Vehicle_t *pVeh, bgEntity_t *pEnt ) +{ + gentity_t *ent; + + if ( !pEnt ) + return; + + ent = (gentity_t *)pEnt; + + ent->s.eFlags &= ~EF_NODRAW; + if ( ent->client ) + { + ent->client->ps.eFlags &= ~EF_NODRAW; + } +#ifdef _JK2MP + ent->r.contents = CONTENTS_BODY; +#else + ent->contents = CONTENTS_BODY; +#endif +} + +#ifdef _JK2MP +//try to resize the bounding box around a torn apart ship +void G_VehicleDamageBoxSizing(Vehicle_t *pVeh) +{ + vec3_t fwd, right, up; + vec3_t nose; //maxs + vec3_t back; //mins + trace_t trace; + const float fDist = 256.0f; //estimated distance to nose from origin + const float bDist = 256.0f; //estimated distance to back from origin + const float wDist = 32.0f; //width on each side from origin + const float hDist = 32.0f; //height on each side from origin + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + + if (!parent->ghoul2 || !parent->m_pVehicle || !parent->client) + { //shouldn't have gotten in here then + return; + } + + //for now, let's only do anything if all wings are stripped off. + //this is because I want to be able to tear my wings off and fling + //myself down narrow hallways to my death. Because it's fun! -rww + if (!(pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) || + !(pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D) || + !(pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) || + !(pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F) ) + { + return; + } + + //get directions based on orientation + AngleVectors(pVeh->m_vOrientation, fwd, right, up); + + //get the nose and back positions (relative to 0, they're gonna be mins/maxs) + VectorMA(vec3_origin, fDist, fwd, nose); + VectorMA(vec3_origin, -bDist, fwd, back); + + //move the nose and back to opposite right/left, they will end up as our relative mins and maxs + VectorMA(nose, wDist, right, nose); + VectorMA(nose, -wDist, right, back); + + //use the same concept for up/down now + VectorMA(nose, hDist, up, nose); + VectorMA(nose, -hDist, up, back); + + //and now, let's trace and see if our new mins/maxs are safe.. + trap_Trace(&trace, parent->client->ps.origin, back, nose, parent->client->ps.origin, parent->s.number, parent->clipmask); + if (!trace.allsolid && !trace.startsolid && trace.fraction == 1.0f) + { //all clear! + VectorCopy(nose, parent->maxs); + VectorCopy(back, parent->mins); + } + else + { //oh well, DIE! + //FIXME: does this give proper credit to the enemy who shot you down? + G_Damage(parent, parent, parent, NULL, parent->client->ps.origin, 9999, DAMAGE_NO_PROTECTION, MOD_SUICIDE); + } +} + +//get one of 4 possible impact locations based on the trace direction +int G_FlyVehicleImpactDir(gentity_t *veh, trace_t *trace) +{ + float impactAngle; + float relativeAngle; + trace_t localTrace; + vec3_t testMins, testMaxs; + vec3_t rWing, lWing; + vec3_t fwd, right; + vec3_t fPos; + Vehicle_t *pVeh = veh->m_pVehicle; + qboolean noseClear = qfalse; + + if (!trace || !pVeh || !veh->client) + { + return -1; + } + + AngleVectors(veh->client->ps.viewangles, fwd, right, 0); + VectorSet(testMins, -24.0f, -24.0f, -24.0f); + VectorSet(testMaxs, 24.0f, 24.0f, 24.0f); + + //do a trace to determine if the nose is clear + VectorMA(veh->client->ps.origin, 256.0f, fwd, fPos); + trap_Trace(&localTrace, veh->client->ps.origin, testMins, testMaxs, fPos, veh->s.number, veh->clipmask); + if (!localTrace.startsolid && !localTrace.allsolid && localTrace.fraction == 1.0f) + { //otherwise I guess it's not clear.. + noseClear = qtrue; + } + + if (noseClear) + { //if nose is clear check for tearing the wings off + //sadly, the trace endpos given always matches the vehicle origin, so we + //can't get a real impact direction. First we'll trace forward and see if the wings are colliding + //with anything, and if not, we'll fall back to checking the trace plane normal. + VectorMA(veh->client->ps.origin, 128.0f, right, rWing); + VectorMA(veh->client->ps.origin, -128.0f, right, lWing); + + //test the right wing - unless it's already removed + if (!(pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) || + !(pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) + { + VectorMA(rWing, 256.0f, fwd, fPos); + trap_Trace(&localTrace, rWing, testMins, testMaxs, fPos, veh->s.number, veh->clipmask); + if (localTrace.startsolid || localTrace.allsolid || localTrace.fraction != 1.0f) + { //impact + return SHIPSURF_RIGHT; + } + } + + //test the left wing - unless it's already removed + if (!(pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) || + !(pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) + { + VectorMA(lWing, 256.0f, fwd, fPos); + trap_Trace(&localTrace, lWing, testMins, testMaxs, fPos, veh->s.number, veh->clipmask); + if (localTrace.startsolid || localTrace.allsolid || localTrace.fraction != 1.0f) + { //impact + return SHIPSURF_LEFT; + } + } + } + + //try to use the trace plane normal + impactAngle = vectoyaw(trace->plane.normal); + relativeAngle = AngleSubtract(impactAngle, veh->client->ps.viewangles[YAW]); + + if (relativeAngle > 130 || + relativeAngle < -130) + { //consider this front + return SHIPSURF_FRONT; + } + else if (relativeAngle > 0) + { + return SHIPSURF_RIGHT; + } + else if (relativeAngle < 0) + { + return SHIPSURF_LEFT; + } + + return SHIPSURF_BACK; +} + +//try to break surfaces off the ship on impact +#define TURN_ON 0x00000000 +#define TURN_OFF 0x00000100 +extern void NPC_SetSurfaceOnOff(gentity_t *ent, const char *surfaceName, int surfaceFlags); //NPC_utils.c +int G_ShipSurfaceForSurfName( const char *surfaceName ) +{ + if ( !surfaceName ) + { + return -1; + } + if ( !Q_strncmp( "nose", surfaceName, 4 ) + || !Q_strncmp( "f_gear", surfaceName, 6 ) + || !Q_strncmp( "glass", surfaceName, 5 ) ) + { + return SHIPSURF_FRONT; + } + if ( !Q_strncmp( "body", surfaceName, 4 ) ) + { + return SHIPSURF_BACK; + } + if ( !Q_strncmp( "r_wing1", surfaceName, 7 ) + || !Q_strncmp( "r_wing2", surfaceName, 7 ) + || !Q_strncmp( "r_gear", surfaceName, 6 ) ) + { + return SHIPSURF_RIGHT; + } + if ( !Q_strncmp( "l_wing1", surfaceName, 7 ) + || !Q_strncmp( "l_wing2", surfaceName, 7 ) + || !Q_strncmp( "l_gear", surfaceName, 6 ) ) + { + return SHIPSURF_LEFT; + } + return -1; +} + +void G_SetVehDamageFlags( gentity_t *veh, int shipSurf, int damageLevel ) +{ + int dmgFlag; + switch ( damageLevel ) + { + case 3://destroyed + //add both flags so cgame side knows this surf is GONE + //add heavy + dmgFlag = SHIPSURF_DAMAGE_FRONT_HEAVY+(shipSurf-SHIPSURF_FRONT); + veh->client->ps.brokenLimbs |= (1<client->ps.brokenLimbs |= (1<s.brokenLimbs = veh->client->ps.brokenLimbs; + //check droid + if ( shipSurf == SHIPSURF_BACK ) + {//destroy the droid if we have one + if ( veh->m_pVehicle + && veh->m_pVehicle->m_pDroidUnit ) + {//we have one + gentity_t *droidEnt = (gentity_t *)veh->m_pVehicle->m_pDroidUnit; + if ( droidEnt + && ((droidEnt->flags&FL_UNDYING) || droidEnt->health > 0) ) + {//boom + //make it vulnerable + droidEnt->flags &= ~FL_UNDYING; + //blow it up + G_Damage( droidEnt, veh->enemy, veh->enemy, NULL, NULL, 99999, 0, MOD_UNKNOWN ); + } + } + } + break; + case 2://heavy only + dmgFlag = SHIPSURF_DAMAGE_FRONT_HEAVY+(shipSurf-SHIPSURF_FRONT); + veh->client->ps.brokenLimbs |= (1<client->ps.brokenLimbs &= ~(1<s.brokenLimbs = veh->client->ps.brokenLimbs; + //check droid + if ( shipSurf == SHIPSURF_BACK ) + {//make the droid vulnerable if we have one + if ( veh->m_pVehicle + && veh->m_pVehicle->m_pDroidUnit ) + {//we have one + gentity_t *droidEnt = (gentity_t *)veh->m_pVehicle->m_pDroidUnit; + if ( droidEnt + && (droidEnt->flags&FL_UNDYING) ) + {//make it vulnerab;e + droidEnt->flags &= ~FL_UNDYING; + } + } + } + break; + case 1://light only + //add light + dmgFlag = SHIPSURF_DAMAGE_FRONT_LIGHT+(shipSurf-SHIPSURF_FRONT); + veh->client->ps.brokenLimbs |= (1<client->ps.brokenLimbs &= ~(1<s.brokenLimbs = veh->client->ps.brokenLimbs; + break; + case 0://no damage + default: + //remove heavy + dmgFlag = SHIPSURF_DAMAGE_FRONT_HEAVY+(shipSurf-SHIPSURF_FRONT); + veh->client->ps.brokenLimbs &= ~(1<client->ps.brokenLimbs &= ~(1<s.brokenLimbs = veh->client->ps.brokenLimbs; + break; + } +} + +void G_VehicleSetDamageLocFlags( gentity_t *veh, int impactDir, int deathPoint ) +{ + if ( !veh->client ) + { + return; + } + else + { + int deathPoint, heavyDamagePoint, lightDamagePoint; + switch(impactDir) + { + case SHIPSURF_FRONT: + deathPoint = veh->m_pVehicle->m_pVehicleInfo->health_front; + break; + case SHIPSURF_BACK: + deathPoint = veh->m_pVehicle->m_pVehicleInfo->health_back; + break; + case SHIPSURF_RIGHT: + deathPoint = veh->m_pVehicle->m_pVehicleInfo->health_right; + break; + case SHIPSURF_LEFT: + deathPoint = veh->m_pVehicle->m_pVehicleInfo->health_left; + break; + default: + return; + break; + } + if ( veh->m_pVehicle + && veh->m_pVehicle->m_pVehicleInfo + && veh->m_pVehicle->m_pVehicleInfo->malfunctionArmorLevel + && veh->m_pVehicle->m_pVehicleInfo->armor ) + { + float perc = ((float)veh->m_pVehicle->m_pVehicleInfo->malfunctionArmorLevel/(float)veh->m_pVehicle->m_pVehicleInfo->armor); + if ( perc > 0.99f ) + { + perc = 0.99f; + } + heavyDamagePoint = ceil( deathPoint*perc*0.25f ); + lightDamagePoint = ceil( deathPoint*perc ); + } + else + { + heavyDamagePoint = ceil( deathPoint*0.66f ); + lightDamagePoint = ceil( deathPoint*0.14f ); + } + + if ( veh->locationDamage[impactDir] >= deathPoint) + {//destroyed + G_SetVehDamageFlags( veh, impactDir, 3 ); + } + else if ( veh->locationDamage[impactDir] <= heavyDamagePoint ) + {//heavy only + G_SetVehDamageFlags( veh, impactDir, 2 ); + } + else if ( veh->locationDamage[impactDir] <= lightDamagePoint ) + {//light only + G_SetVehDamageFlags( veh, impactDir, 1 ); + } + } +} + +qboolean G_FlyVehicleDestroySurface( gentity_t *veh, int surface ) +{ + char *surfName[4]; //up to 4 surfs at once + int numSurfs = 0; + int smashedBits = 0; + + if (surface == -1) + { //not valid? + return qfalse; + } + + switch(surface) + { + case SHIPSURF_FRONT: //break the nose off + surfName[0] = "nose"; + + smashedBits = (SHIPSURF_BROKEN_G); + + numSurfs = 1; + break; + case SHIPSURF_BACK: //break both the bottom wings off for a backward impact I guess + surfName[0] = "r_wing2"; + surfName[1] = "l_wing2"; + + //get rid of the landing gear + surfName[2] = "r_gear"; + surfName[3] = "l_gear"; + + smashedBits = (SHIPSURF_BROKEN_A|SHIPSURF_BROKEN_B|SHIPSURF_BROKEN_D|SHIPSURF_BROKEN_F); + + numSurfs = 4; + break; + case SHIPSURF_RIGHT: //break both right wings off + surfName[0] = "r_wing1"; + surfName[1] = "r_wing2"; + + //get rid of the landing gear + surfName[2] = "r_gear"; + + smashedBits = (SHIPSURF_BROKEN_B|SHIPSURF_BROKEN_E|SHIPSURF_BROKEN_F); + + numSurfs = 3; + break; + case SHIPSURF_LEFT: //break both left wings off + surfName[0] = "l_wing1"; + surfName[1] = "l_wing2"; + + //get rid of the landing gear + surfName[2] = "l_gear"; + + smashedBits = (SHIPSURF_BROKEN_A|SHIPSURF_BROKEN_C|SHIPSURF_BROKEN_D); + + numSurfs = 3; + break; + default: + break; + } + + if (numSurfs < 1) + { //didn't get any valid surfs.. + return qfalse; + } + + while (numSurfs > 0) + { //use my silly system of automatically managing surf status on both client and server + numSurfs--; + NPC_SetSurfaceOnOff(veh, surfName[numSurfs], TURN_OFF); + } + + if ( !veh->m_pVehicle->m_iRemovedSurfaces ) + {//first time something got blown off + if ( veh->m_pVehicle->m_pPilot ) + {//make the pilot scream to his death + G_EntitySound((gentity_t*)veh->m_pVehicle->m_pPilot, CHAN_VOICE, G_SoundIndex("*falling1.wav")); + } + } + //so we can check what's broken + veh->m_pVehicle->m_iRemovedSurfaces |= smashedBits; + + //do some explosive damage, but don't damage this ship with it + G_RadiusDamage(veh->client->ps.origin, veh, 100, 500, veh, NULL, MOD_SUICIDE); + + //when spiraling to your death, do the electical shader + veh->client->ps.electrifyTime = level.time + 10000; + + return qtrue; +} + +void G_FlyVehicleSurfaceDestruction(gentity_t *veh, trace_t *trace, int magnitude, qboolean force) +{ + int impactDir; + int secondImpact; + int deathPoint = -1; + qboolean alreadyRebroken = qfalse; + + if (!veh->ghoul2 || !veh->m_pVehicle) + { //no g2 instance.. or no vehicle instance + return; + } + + impactDir = G_FlyVehicleImpactDir(veh, trace); + +anotherImpact: + if (impactDir == -1) + { //not valid? + return; + } + + veh->locationDamage[impactDir] += magnitude*7; + + switch(impactDir) + { + case SHIPSURF_FRONT: + deathPoint = veh->m_pVehicle->m_pVehicleInfo->health_front; + break; + case SHIPSURF_BACK: + deathPoint = veh->m_pVehicle->m_pVehicleInfo->health_back; + break; + case SHIPSURF_RIGHT: + deathPoint = veh->m_pVehicle->m_pVehicleInfo->health_right; + break; + case SHIPSURF_LEFT: + deathPoint = veh->m_pVehicle->m_pVehicleInfo->health_left; + break; + default: + break; + } + + if ( deathPoint != -1 ) + {//got a valid health value + if ( force && veh->locationDamage[impactDir] < deathPoint ) + {//force that surf to be destroyed + veh->locationDamage[impactDir] = deathPoint; + } + if ( veh->locationDamage[impactDir] >= deathPoint) + { //do it + if ( G_FlyVehicleDestroySurface( veh, impactDir ) ) + {//actually took off a surface + G_VehicleSetDamageLocFlags( veh, impactDir, deathPoint ); + } + } + else + { + G_VehicleSetDamageLocFlags( veh, impactDir, deathPoint ); + } + } + + if (!alreadyRebroken) + { + secondImpact = G_FlyVehicleImpactDir(veh, trace); + if (impactDir != secondImpact) + { //can break off another piece in this same impact.. but only break off up to 2 at once + alreadyRebroken = qtrue; + impactDir = secondImpact; + goto anotherImpact; + } + } +} + +void G_VehUpdateShields( gentity_t *targ ) +{ + if ( !targ || !targ->client + || !targ->m_pVehicle || !targ->m_pVehicle->m_pVehicleInfo ) + { + return; + } + if ( targ->m_pVehicle->m_pVehicleInfo->shields <= 0 ) + {//doesn't have shields, so don't have to send it + return; + } + targ->client->ps.activeForcePass = floor(((float)targ->m_pVehicle->m_iShields/(float)targ->m_pVehicle->m_pVehicleInfo->shields)*10.0f); +} +#endif + +// Set the parent entity of this Vehicle NPC. +GAME_INLINE void SetParent( Vehicle_t *pVeh, bgEntity_t *pParentEntity ) { pVeh->m_pParentEntity = pParentEntity; } + +// Add a pilot to the vehicle. +GAME_INLINE void SetPilot( Vehicle_t *pVeh, bgEntity_t *pPilot ) { pVeh->m_pPilot = pPilot; } + +// Add a passenger to the vehicle (false if we're full). +GAME_INLINE bool AddPassenger( Vehicle_t *pVeh ) { return false; } + +// Whether this vehicle is currently inhabited (by anyone) or not. +GAME_INLINE bool Inhabited( Vehicle_t *pVeh ) { return ( pVeh->m_pPilot ) ? true : false; } + + +// Setup the shared functions (one's that all vehicles would generally use). +void G_SetSharedVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +// pVehInfo->AnimateVehicle = AnimateVehicle; +// pVehInfo->AnimateRiders = AnimateRiders; + pVehInfo->ValidateBoard = ValidateBoard; + pVehInfo->SetParent = SetParent; + pVehInfo->SetPilot = SetPilot; + pVehInfo->AddPassenger = AddPassenger; + pVehInfo->Animate = Animate; + pVehInfo->Board = Board; + pVehInfo->Eject = Eject; + pVehInfo->EjectAll = EjectAll; + pVehInfo->StartDeathDelay = StartDeathDelay; + pVehInfo->DeathUpdate = DeathUpdate; + pVehInfo->RegisterAssets = RegisterAssets; + pVehInfo->Initialize = Initialize; + pVehInfo->Update = Update; + pVehInfo->UpdateRider = UpdateRider; +// pVehInfo->ProcessMoveCommands = ProcessMoveCommands; +// pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + pVehInfo->AttachRiders = AttachRiders; + pVehInfo->Ghost = Ghost; + pVehInfo->UnGhost = UnGhost; + pVehInfo->Inhabited = Inhabited; +} + +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf + +#undef MOD_EXPLOSIVE +#endif diff --git a/code/game/g_vehicles.h b/code/game/g_vehicles.h new file mode 100644 index 0000000..bab39d3 --- /dev/null +++ b/code/game/g_vehicles.h @@ -0,0 +1,626 @@ +#ifndef __G_VEHICLES_H +#define __G_VEHICLES_H + +#include "q_shared.h" +#include "g_public.h" + +typedef enum +{ + VH_NONE = 0, + VH_WALKER, //something you ride inside of, it walks like you, like an AT-ST + VH_FIGHTER, //something you fly inside of, like an X-Wing or TIE fighter + VH_SPEEDER, //something you ride on that hovers, like a speeder or swoop + VH_ANIMAL, //animal you ride on top of that walks, like a tauntaun + VH_FLIER, //animal you ride on top of that flies, like a giant mynoc? + VH_NUM_VEHICLES +} vehicleType_t; + +enum EWeaponPose +{ + WPOSE_NONE = 0, + WPOSE_BLASTER, + WPOSE_SABERLEFT, + WPOSE_SABERRIGHT, +}; + +extern stringID_table_t VehicleTable[VH_NUM_VEHICLES+1]; + +#define NO_PILOT_DIE_TIME 10000 + +//=========================================================================================================== +//START VEHICLE WEAPONS +//=========================================================================================================== +typedef struct +{ +//*** IMPORTANT!!! *** the number of variables in the vehWeaponStats_t struct (including all elements of arrays) must be reflected by NUM_VWEAP_PARMS!!! +//*** IMPORTANT!!! *** vWeapFields table correponds to this structure! + char *name; + qboolean bIsProjectile; //traceline or entity? + qboolean bHasGravity; //if a projectile, drops + qboolean bIonWeapon;//disables ship shields and sends them out of control + qboolean bSaberBlockable;//lightsabers can deflect this projectile + int iMuzzleFX; //index of Muzzle Effect + int iModel; //handle to the model used by this projectile + int iShotFX; //index of Shot Effect + int iImpactFX; //index of Impact Effect + int iG2MarkShaderHandle; //index of shader to use for G2 marks made on other models when hit by this projectile + float fG2MarkSize;//size (diameter) of the ghoul2 mark + int iLoopSound; //index of loopSound + float fSpeed; //speed of projectile/range of traceline + float fHoming; //0.0 = not homing, 0.5 = half vel to targ, half cur vel, 1.0 = all vel to targ + int iLockOnTime; //0 = no lock time needed, else # of ms needed to lock on + int iDamage; //damage done when traceline or projectile directly hits target + int iSplashDamage;//damage done to ents in splashRadius of end of traceline or projectile origin on impact + float fSplashRadius;//radius that ent must be in to take splashDamage (linear fall-off) + int iAmmoPerShot; //how much "ammo" each shot takes + int iHealth; //if non-zero, projectile can be shot, takes this much damage before being destroyed + float fWidth; //width of traceline or bounding box of projecile (non-rotating!) + float fHeight; //height of traceline or bounding box of projecile (non-rotating!) + int iLifeTime; //removes itself after this amount of time + qboolean bExplodeOnExpire; //when iLifeTime is up, explodes rather than simply removing itself +} vehWeaponInfo_t; +//NOTE: this MUST stay up to date with the number of variables in the vehFields table!!! +#define NUM_VWEAP_PARMS 24 + +#define VWFOFS(x) ((int)&(((vehWeaponInfo_t *)0)->x)) + +#define MAX_VEH_WEAPONS 16 //sigh... no more than 16 different vehicle weapons +#define VEH_WEAPON_BASE 0 +#define VEH_WEAPON_NONE -1 + +extern vehWeaponInfo_t g_vehWeaponInfo[MAX_VEH_WEAPONS]; +extern int numVehicleWeapons; + +//=========================================================================================================== +//END VEHICLE WEAPONS +//=========================================================================================================== + +// The maximum number of muzzles a vehicle may have. +#define MAX_VEHICLE_MUZZLES 10 + +// The maximum number of exhausts a vehicle may have. +#define MAX_VEHICLE_EXHAUSTS 4 + +// The maxiumum number of different weapons a vehicle may have +#define MAX_VEHICLE_WEAPONS 2 +#define MAX_VEHICLE_TURRETS 2 +#define MAX_VEHICLE_TURRET_MUZZLES 2 + +typedef struct +{ + int iWeapon; //what vehWeaponInfo index to use + int iDelay; //delay between turret muzzle shots + int iAmmoMax; //how much ammo it has + int iAmmoRechargeMS; //how many MS between every point of recharged ammo + char *yawBone; //bone on ship that this turret uses to yaw + char *pitchBone; //bone on ship that this turret uses to pitch + int yawAxis; //axis on yawBone to which we should to apply the yaw angles + int pitchAxis; //axis on pitchBone to which we should to apply the pitch angles + float yawClampLeft; //how far the turret is allowed to turn left + float yawClampRight; //how far the turret is allowed to turn right + float pitchClampUp; //how far the turret is allowed to title up + float pitchClampDown; //how far the turret is allowed to tilt down + int iMuzzle[MAX_VEHICLE_TURRET_MUZZLES];//iMuzzle-1 = index of ship's muzzle to fire this turret's 1st and 2nd shots from + char *gunnerViewTag;//Where to put the view origin of the gunner (name) + float fTurnSpeed; //how quickly the turret can turn + qboolean bAI; //whether or not the turret auto-targets enemies when it's not manned + qboolean bAILead;//whether + float fAIRange; //how far away the AI will look for enemies + int passengerNum;//which passenger, if any, has control of this turret (overrides AI) +} turretStats_t; + +typedef struct +{ +//*** IMPORTANT!!! *** See note at top of next structure!!! *** + // Weapon stuff. + int ID;//index into the weapon data + // The delay between shots for each weapon. + int delay; + // Whether or not all the muzzles for each weapon can be linked together (linked delay = weapon delay * number of muzzles linked!) + int linkable; + // Whether or not to auto-aim the projectiles/tracelines at the thing under the crosshair when we fire + qboolean aimCorrect; + //maximum ammo + int ammoMax; + //ammo recharge rate - milliseconds per unit (minimum of 100, which is 10 ammo per second) + int ammoRechargeMS; + //sound to play when out of ammo (plays default "no ammo" sound if none specified) + int soundNoAmmo; +} vehWeaponStats_t; + +// Compiler pre-define. +struct Vehicle_t; + +typedef struct +{ +//*** IMPORTANT!!! *** vehFields table correponds to this structure! + char *name; //unique name of the vehicle + + //general data + vehicleType_t type; //what kind of vehicle + int numHands; //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + float lookPitch; //How far you can look up and down off the forward of the vehicle + float lookYaw; //How far you can look left and right off the forward of the vehicle + float length; //how long it is - used for body length traces when turning/moving? + float width; //how wide it is - used for body length traces when turning/moving? + float height; //how tall it is - used for body length traces when turning/moving? + vec3_t centerOfGravity;//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats + float speedMax; //top speed + float turboSpeed; //turbo speed + float speedMin; //if < 0, can go in reverse + float speedIdle; //what speed it drifts to when no accel/decel input is given + float accelIdle; //if speedIdle > 0, how quickly it goes up to that speed + float acceleration; //when pressing on accelerator + float decelIdle; //when giving no input, how quickly it drops to speedIdle + float throttleSticks; //if true, speed stays at whatever you accel/decel to, unless you turbo or brake + float strafePerc; //multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + float bankingSpeed; //how quickly it pitches and rolls (not under player control) + float rollLimit; //how far it can roll to either side + float pitchLimit; //how far it can roll forward or backward + float braking; //when pressing on decelerator + float mouseYaw; // The mouse yaw override. + float mousePitch; // The mouse pitch override. + float turningSpeed; //how quickly you can turn + qboolean turnWhenStopped;//whether or not you can turn when not moving + float traction; //how much your command input affects velocity + float friction; //how much velocity is cut on its own + float maxSlope; //the max slope that it can go up with control + qboolean speedDependantTurning;//vehicle turns faster the faster it's going + + //durability stats + int mass; //for momentum and impact force (player mass is 10) + int armor; //total points of damage it can take + int shields; //energy shield damage points + int shieldRechargeMS;//energy shield milliseconds per point recharged + float toughness; //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + int malfunctionArmorLevel;//when armor drops to or below this point, start malfunctioning + int surfDestruction; //can parts of this thing be torn off on impact? -rww + + //individual "area" health -rww + int health_front; + int health_back; + int health_right; + int health_left; + + //visuals & sounds + char *model; //what model to use - if make it an NPC's primary model, don't need this? + char *skin; //what skin to use - if make it an NPC's primary model, don't need this? + int g2radius; //render radius for the ghoul2 model + int riderAnim; //what animation the rider uses + int radarIconHandle;//what icon to show on radar in MP + char *droidNPC; //NPC to attach to *droidunit tag (if it exists in the model) + + int soundOn; //sound to play when get on it + int soundOff; //sound to play when get off + int soundLoop; //sound to loop while riding it + int soundTakeOff; //sound to play when ship takes off + int soundEngineStart;//sound to play when ship's thrusters first activate + int soundSpin; //sound to loop while spiraling out of control + int soundTurbo; //sound to play when turbo/afterburner kicks in + int soundHyper; //sound to play when ship lands + int soundLand; //sound to play when ship lands + int soundFlyBy; //sound to play when they buzz you + int soundFlyBy2; //alternate sound to play when they buzz you + int soundShift1; //sound to play when accelerating + int soundShift2; //sound to play when accelerating + int soundShift3; //sound to play when decelerating + int soundShift4; //sound to play when decelerating + + int iExhaustFX; //exhaust effect, played from "*exhaust" bolt(s) + int iTurboFX; //turbo exhaust effect, played from "*exhaust" bolt(s) when ship is in "turbo" mode + int iTurboStartFX; //turbo begin effect, played from "*exhaust" bolts when "turbo" mode begins + int iTrailFX; //trail effect, played from "*trail" bolt(s) + int iImpactFX; //impact effect, for when it bumps into something + int iExplodeFX; //explosion effect, for when it blows up (should have the sound built into explosion effect) + int iWakeFX; //effect it makes when going across water + int iDmgFX; //effect to play on damage from a weapon or something + int iArmorLowFX; //played when armor is less than 30% of full + int iArmorGoneFX; //played when on armor is completely gone + + //Weapon stats + vehWeaponStats_t weapon[MAX_VEHICLE_WEAPONS]; + + // Which weapon a muzzle fires (has to match one of the weapons this vehicle has). So 1 would be weapon 1, + // 2 would be weapon 2 and so on. + int weapMuzzle[MAX_VEHICLE_MUZZLES]; + + //turrets (if any) on the vehicle + turretStats_t turret[MAX_VEHICLE_TURRETS]; + + // The max height before this ship (?) starts (auto)landing. + float landingHeight; + + //other misc stats + int gravity; //normal is 800 + float hoverHeight; //if 0, it's a ground vehicle + float hoverStrength; //how hard it pushes off ground when less than hover height... causes "bounce", like shocks + qboolean waterProof; //can drive underwater if it has to + float bouyancy; //when in water, how high it floats (1 is neutral bouyancy) + int fuelMax; //how much fuel it can hold (capacity) + int fuelRate; //how quickly is uses up fuel + int turboDuration; //how long turbo lasts + int turboRecharge; //how long turbo takes to recharge + int visibility; //for sight alerts + int loudness; //for sound alerts + float explosionRadius;//range of explosion + int explosionDamage;//damage of explosion + + int maxPassengers; // The max number of passengers this vehicle may have (Default = 0). + qboolean hideRider; // rider (and passengers?) should not be drawn + qboolean killRiderOnDeath;//if rider is on vehicle when it dies, they should die + qboolean flammable; //whether or not the vehicle should catch on fire before it explodes + int explosionDelay; //how long the vehicle should be on fire/dying before it explodes + //camera stuff + qboolean cameraOverride; //whether or not to use all of the following 3rd person camera override values + float cameraRange; //how far back the camera should be - normal is 80 + float cameraVertOffset;//how high over the vehicle origin the camera should be - normal is 16 + float cameraHorzOffset;//how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + float cameraPitchOffset;//a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + float cameraFOV; //third person camera FOV, default is 80 + float cameraAlpha; //fade out the vehicle to this alpha (0.1-1.0f) if it's in the way of the crosshair + qboolean cameraPitchDependantVertOffset;//use the hacky AT-ST pitch dependant vertical offset + + //NOTE: some info on what vehicle weapon to use? Like ATST or TIE bomber or TIE fighter or X-Wing...? + +//===VEH_PARM_MAX======================================================================== +//*** IMPORTANT!!! *** vehFields table correponds to this structure! + +//THE FOLLOWING FIELDS are not in the vehFields table because they are internal variables, not read in from the .veh file + int modelIndex; //set internally, not until this vehicle is spawned into the level + + // NOTE: Please note that most of this stuff has been converted from C++ classes to generic C. + // This part of the structure is used to simulate inheritance for vehicles. The basic idea is that all vehicle use + // this vehicle interface since they declare their own functions and assign the function pointer to the + // corresponding function. Meanwhile, the base logic can still call the appropriate functions. In C++ talk all + // of these functions (pointers) are pure virtuals and this is an abstract base class (although it cannot be + // inherited from, only contained and reimplemented (through an object and a setup function respectively)). -AReis + + // Makes sure that the vehicle is properly animated. + void (*AnimateVehicle)( Vehicle_t *pVeh ); + + // Makes sure that the rider's in this vehicle are properly animated. + void (*AnimateRiders)( Vehicle_t *pVeh ); + + // Determine whether this entity is able to board this vehicle or not. + bool (*ValidateBoard)( Vehicle_t *pVeh, gentity_t *pEnt ); + + // Set the parent entity of this Vehicle NPC. + void (*SetParent)( Vehicle_t *pVeh, gentity_t *pParentEntity ); + + // Add a pilot to the vehicle. + void (*SetPilot)( Vehicle_t *pVeh, gentity_t *pPilot ); + + // Add a passenger to the vehicle (false if we're full). + bool (*AddPassenger)( Vehicle_t *pVeh ); + + // Animate the vehicle and it's riders. + void (*Animate)( Vehicle_t *pVeh ); + + // Board this Vehicle (get on). The first entity to board an empty vehicle becomes the Pilot. + bool (*Board)( Vehicle_t *pVeh, gentity_t *pEnt ); + + // Eject an entity from the vehicle. + bool (*Eject)( Vehicle_t *pVeh, gentity_t *pEnt, qboolean forceEject ); + + // Eject all the inhabitants of this vehicle. + bool (*EjectAll)( Vehicle_t *pVeh ); + + // Start a delay until the vehicle dies. + void (*StartDeathDelay)( Vehicle_t *pVeh, int iDelayTime ); + + // Update death sequence. + void (*DeathUpdate)( Vehicle_t *pVeh ); + + // Register all the assets used by this vehicle. + void (*RegisterAssets)( Vehicle_t *pVeh ); + + // Initialize the vehicle (should be called by Spawn?). + bool (*Initialize)( Vehicle_t *pVeh ); + + // Like a think or move command, this updates various vehicle properties. + bool (*Update)( Vehicle_t *pVeh, const usercmd_t *pUcmd ); + + // Update the properties of a Rider (that may reflect what happens to the vehicle). + // + // [return] bool True if still in vehicle, false if otherwise. + bool (*UpdateRider)( Vehicle_t *pVeh, gentity_t *pRider, usercmd_t *pUcmd ); + + // ProcessMoveCommands the Vehicle. + void (*ProcessMoveCommands)( Vehicle_t *pVeh ); + + // ProcessOrientCommands the Vehicle. + void (*ProcessOrientCommands)( Vehicle_t *pVeh ); + + // Attachs all the riders of this vehicle to their appropriate position/tag (*driver, *pass1, *pass2, whatever...). + void (*AttachRiders)( Vehicle_t *pVeh ); + + // Make someone invisible and un-collidable. + void (*Ghost)( Vehicle_t *pVeh, gentity_t *pEnt ); + + // Make someone visible and collidable. + void (*UnGhost)( Vehicle_t *pVeh, gentity_t *pEnt ); + + // Get the pilot of this vehicle. + const gentity_t *(*GetPilot)( Vehicle_t *pVeh ); + + // Whether this vehicle is currently inhabited (by anyone) or not. + bool (*Inhabited)( Vehicle_t *pVeh ); +} vehicleInfo_t; + +#define VFOFS(x) ((int)&(((vehicleInfo_t *)0)->x)) + +#define MAX_VEHICLES 16 //sigh... no more than 64 individual vehicles +extern vehicleInfo_t g_vehicleInfo[MAX_VEHICLES]; +extern int numVehicles; + +// Load the function pointers for a vehicle into this shared vehicle info structure. +extern void G_SetSpeederVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetAnimalVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetFighterVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetWalkerVehicleFunctions( vehicleInfo_t *pVehInfo ); + +// Setup the shared functions (one's that all vehicles would generally use). +extern void G_SetSharedVehicleFunctions( vehicleInfo_t *pVehInfo ); + +// Create/Allocate a new Animal Vehicle (initializing it as well). +extern void G_CreateSpeederNPC( Vehicle_t **pVeh, const char *strType ); +extern void G_CreateAnimalNPC( Vehicle_t **pVeh, const char *strType ); +extern void G_CreateFighterNPC( Vehicle_t **pVeh, const char *strType ); +extern void G_CreateWalkerNPC( Vehicle_t **pVeh, const char *strType ); + +#define VEH_DEFAULT_SPEED_MAX 800.0f +#define VEH_DEFAULT_ACCEL 10.0f +#define VEH_DEFAULT_DECEL 10.0f +#define VEH_DEFAULT_STRAFE_PERC 0.5f +#define VEH_DEFAULT_BANKING_SPEED 0.5f +#define VEH_DEFAULT_ROLL_LIMIT 60.0f +#define VEH_DEFAULT_PITCH_LIMIT 90.0f +#define VEH_DEFAULT_BRAKING 10.0f +#define VEH_DEFAULT_TURNING_SPEED 1.0f +#define VEH_DEFAULT_TRACTION 8.0f +#define VEH_DEFAULT_FRICTION 1.0f +#define VEH_DEFAULT_MAX_SLOPE 0.85f +#define VEH_DEFAULT_MASS 200 +#define VEH_DEFAULT_MAX_ARMOR 200 +#define VEH_DEFAULT_TOUGHNESS 2.5f +#define VEH_DEFAULT_GRAVITY 800 +#define VEH_DEFAULT_HOVER_HEIGHT 64.0f +#define VEH_DEFAULT_HOVER_STRENGTH 10.0f +#define VEH_DEFAULT_VISIBILITY 0 +#define VEH_DEFAULT_LOUDNESS 0 +#define VEH_DEFAULT_EXP_RAD 400.0f +#define VEH_DEFAULT_EXP_DMG 1000 +#define VEH_MAX_PASSENGERS 10 + +#define VEH_MOUNT_THROW_LEFT -5 +#define VEH_MOUNT_THROW_RIGHT -6 + +#define MAX_STRAFE_TIME 2000.0f//FIXME: extern? +#define MIN_LANDING_SPEED 200//equal to or less than this and close to ground = auto-slow-down to land +#define MIN_LANDING_SLOPE 0.8f//must be pretty flat to land on the surf + +#define VEHICLE_BASE 0 +#define VEHICLE_NONE -1 + +enum +{ + VEH_EJECT_LEFT, + VEH_EJECT_RIGHT, + VEH_EJECT_FRONT, + VEH_EJECT_REAR, + VEH_EJECT_TOP, + VEH_EJECT_BOTTOM +}; + +// Vehicle flags. +enum +{ + VEH_NONE = 0, VEH_FLYING = 0x00000001, VEH_CRASHING = 0x00000002, + VEH_LANDING = 0x00000004, VEH_BUCKING = 0x00000010, VEH_WINGSOPEN = 0x00000020, + VEH_GEARSOPEN = 0x00000040, VEH_SLIDEBREAKING = 0x00000080, VEH_SPINNING = 0x00000100, + VEH_OUTOFCONTROL = 0x00000200, + VEH_SABERINLEFTHAND = 0x00000400, + VEH_STRAFERAM = 0x00000800, + VEH_ACCELERATORON = 0x00001000, + VEH_ARMORLOW = 0x00002000, + VEH_ARMORGONE = 0x00004000 +}; +//externed functions +//extern void G_DriveVehicle( gentity_t *ent, gentity_t *vehEnt, char *vehicleName ); +/*extern void G_VehicleStartExplosionDelay( gentity_t *self ); +extern void VehicleExplosionDelay( gentity_t *self ); +extern void G_VehicleRegisterAssets( int vehicleIndex ); +extern void G_DriveATST( gentity_t *ent, gentity_t *atst ); +extern void G_VehicleInitialize( gentity_t *vehEnt );*/ +extern void G_VehicleSpawn( gentity_t *self ); + +// A vehicle weapon muzzle. +struct Muzzle +{ + // These are updated every frame and represent the current position and direction for the specific muzzle. + vec3_t m_vMuzzlePos; + vec3_t m_vMuzzleDir; + + // This is how long to wait before being able to fire a specific muzzle again. This is based on the firing rate + // so that a firing rate of 10 rounds/sec would make this value initially 100 miliseconds. + int m_iMuzzleWait; + + // whether this Muzzle was just fired or not (reset at muzzle flash code). + bool m_bFired; + +}; + +//defines for impact damage surface stuff +#define SHIPSURF_FRONT 1 +#define SHIPSURF_BACK 2 +#define SHIPSURF_RIGHT 3 +#define SHIPSURF_LEFT 4 + +#define SHIPSURF_DAMAGE_FRONT_LIGHT 1 +#define SHIPSURF_DAMAGE_BACK_LIGHT 2 +#define SHIPSURF_DAMAGE_RIGHT_LIGHT 3 +#define SHIPSURF_DAMAGE_LEFT_LIGHT 4 +#define SHIPSURF_DAMAGE_FRONT_HEAVY 5 +#define SHIPSURF_DAMAGE_BACK_HEAVY 6 +#define SHIPSURF_DAMAGE_RIGHT_HEAVY 7 +#define SHIPSURF_DAMAGE_LEFT_HEAVY 8 + +//generic part bits +#define SHIPSURF_BROKEN_A (1<<0) //gear 1 +#define SHIPSURF_BROKEN_B (1<<1) //gear 1 +#define SHIPSURF_BROKEN_C (1<<2) //wing 1 +#define SHIPSURF_BROKEN_D (1<<3) //wing 2 +#define SHIPSURF_BROKEN_E (1<<4) //wing 3 +#define SHIPSURF_BROKEN_F (1<<5) //wing 4 + +typedef struct +{ + //linked firing mode + qboolean linked;//weapon 1's muzzles are in linked firing mode + //current weapon ammo + int ammo; + //debouncer for ammo recharge + int lastAmmoInc; + //which muzzle will fire next + int nextMuzzle; +} vehWeaponStatus_t; + +typedef struct +{ + //current weapon ammo + int ammo; + //debouncer for ammo recharge + int lastAmmoInc; + //which muzzle will fire next + int nextMuzzle; + //which entity they're after + int enemyEntNum; + //how long to hold on to our current enemy + int enemyHoldTime; +} vehTurretStatus_t; + +// This is the implementation of the vehicle interface and any of the other variables needed. This +// is what actually represents a vehicle. -AReis. +// !!!!!!!!!!!!!!!!!! loadsave affecting structure !!!!!!!!!!!!!!!!!!!!!!! +struct Vehicle_t +{ + // The entity who pilots/drives this vehicle. + // NOTE: This is redundant (since m_pParentEntity->owner _should_ be the pilot). This makes things clearer though. + gentity_t *m_pPilot; + + int m_iPilotTime; //if spawnflag to die without pilot and this < level.time then die. + qboolean m_bHasHadPilot; //qtrue once the vehicle gets its first pilot + + //the droid unit NPC for this vehicle, if any + gentity_t *m_pDroidUnit; + + // The entity from which this NPC comes from. + gentity_t *m_pParentEntity; + + // If not zero, how long to wait before we can do anything with the vehicle (we're getting on still). + // -1 = board from left, -2 = board from right, -3 = jump/quick board. -4 & -5 = throw off existing pilot + int m_iBoarding; + + // Used to check if we've just started the boarding process + bool m_bWasBoarding; + + // The speed the vehicle maintains while boarding occurs (often zero) + vec3_t m_vBoardingVelocity; + + // Time modifier (must only be used in ProcessMoveCommands() and ProcessOrientCommands() and is updated in Update()). + float m_fTimeModifier; + + // Ghoul2 Animation info. + // NOTE: Since each vehicle has their own model instance, these bolts must be local to each vehicle as well. + int m_iLeftWingBone; + int m_iRightWingBone; + //int m_iDriverTag; + int m_iExhaustTag[MAX_VEHICLE_EXHAUSTS]; + int m_iMuzzleTag[MAX_VEHICLE_MUZZLES]; + int m_iDroidUnitTag; + int m_iGunnerViewTag[MAX_VEHICLE_TURRETS];//Where to put the view origin of the gunner (index) + + // This vehicles weapon muzzles. + Muzzle m_Muzzles[MAX_VEHICLE_MUZZLES]; + + // The user commands structure. + usercmd_t m_ucmd; + + // The direction an entity will eject from the vehicle towards. + int m_EjectDir; + + // Flags that describe the vehicles behavior. + unsigned long m_ulFlags; + + // NOTE: Vehicle Type ID, Orientation, and Armor MUST be transmitted over the net. + + // Current angles of this vehicle. + vec3_t m_vOrientation; + + // How long you have strafed left or right (increments every frame that you strafe to right, decrements every frame you strafe left) + int m_fStrafeTime; + + // Previous angles of this vehicle. + vec3_t m_vPrevOrientation; + + // When control is lost on a speeder, current angular velocity is stored here and applied until landing + float m_vAngularVelocity; + + vec3_t m_vFullAngleVelocity; + + // Current armor and shields of your vehicle (explodes if armor to 0). + int m_iArmor; //hull strength - STAT_HEALTH on NPC + int m_iShields; //energy shielding - STAT_ARMOR on NPC + + // Timer for all cgame-FX...? ex: exhaust? + int m_iLastFXTime; + + // When to die. + int m_iDieTime; + + // This pointer is to a valid VehicleInfo (which could be an animal, speeder, fighter, whatever). This + // contains the functions actually used to do things to this specific kind of vehicle as well as shared + // information (max speed, type, etc...). + vehicleInfo_t *m_pVehicleInfo; + + // This trace tells us if we're within landing height. + trace_t m_LandTrace; + + //bitflag of surfaces that have broken off + int m_iRemovedSurfaces; + + // the last time this vehicle fired a turbo burst + int m_iTurboTime; + + //how long it should drop like a rock for after freed from SUSPEND + int m_iDropTime; + + int m_iSoundDebounceTimer; + + //last time we incremented the shields + int lastShieldInc; + + //so we don't hold it down and toggle it back and forth + qboolean linkWeaponToggleHeld; + + //info about our weapons (linked, ammo, etc.) + vehWeaponStatus_t weaponStatus[MAX_VEHICLE_WEAPONS]; + vehTurretStatus_t turretStatus[MAX_VEHICLE_TURRETS]; + + //the guy who was previously the pilot + gentity_t* m_pOldPilot; + + // don't need these in mp + int m_safeJumpMountTime; + float m_safeJumpMountRightDot; + + bool alreadyCleaned; +}; + +extern int BG_VehicleGetIndex( const char *vehicleName ); + +#endif // __G_VEHICLES_H diff --git a/code/game/g_weapon.cpp b/code/game/g_weapon.cpp new file mode 100644 index 0000000..b1c52e1 --- /dev/null +++ b/code/game/g_weapon.cpp @@ -0,0 +1,5377 @@ +// g_weapon.c +// perform the server side effects of a weapon firing + +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + + +#include "g_local.h" +#include "g_functions.h" +#include "anims.h" +#include "b_local.h" +#include "wp_saber.h" +#include "g_vehicles.h" + +static vec3_t forward, vright, up; +static vec3_t muzzle; + +void drop_charge(gentity_t *ent, vec3_t start, vec3_t dir); +void ViewHeightFix( const gentity_t * const ent ); +qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ); +extern qboolean G_BoxInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs, const vec3_t boundsMins, const vec3_t boundsMaxs ); +extern qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc ); +extern qboolean PM_DroidMelee( int npc_class ); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern qboolean G_HasKnockdownAnims( gentity_t *ent ); + +static gentity_t *ent_list[MAX_GENTITIES]; +extern cvar_t *g_debugMelee; + +// Bryar Pistol +//-------- +#define BRYAR_PISTOL_VEL 1800 +#define BRYAR_PISTOL_DAMAGE 14 +#define BRYAR_CHARGE_UNIT 200.0f // bryar charging gives us one more unit every 200ms--if you change this, you'll have to do the same in bg_pmove + +// E11 Blaster +//--------- +#define BLASTER_MAIN_SPREAD 0.5f +#define BLASTER_ALT_SPREAD 1.5f +#define BLASTER_NPC_SPREAD 0.5f +#define BLASTER_VELOCITY 2300 +#define BLASTER_NPC_VEL_CUT 0.5f +#define BLASTER_NPC_HARD_VEL_CUT 0.7f +#define BLASTER_DAMAGE 20 +#define BLASTER_NPC_DAMAGE_EASY 6 +#define BLASTER_NPC_DAMAGE_NORMAL 12 // 14 +#define BLASTER_NPC_DAMAGE_HARD 16 // 18 + +// Tenloss Disruptor +//---------- +#define DISRUPTOR_MAIN_DAMAGE 14 +#define DISRUPTOR_NPC_MAIN_DAMAGE_EASY 5 +#define DISRUPTOR_NPC_MAIN_DAMAGE_MEDIUM 10 +#define DISRUPTOR_NPC_MAIN_DAMAGE_HARD 15 + +#define DISRUPTOR_ALT_DAMAGE 12 +#define DISRUPTOR_NPC_ALT_DAMAGE_EASY 15 +#define DISRUPTOR_NPC_ALT_DAMAGE_MEDIUM 25 +#define DISRUPTOR_NPC_ALT_DAMAGE_HARD 30 +#define DISRUPTOR_ALT_TRACES 3 // can go through a max of 3 entities +#define DISRUPTOR_CHARGE_UNIT 150.0f // distruptor charging gives us one more unit every 150ms--if you change this, you'll have to do the same in bg_pmove + +// Wookie Bowcaster +//---------- +#define BOWCASTER_DAMAGE 45 +#define BOWCASTER_VELOCITY 1300 +#define BOWCASTER_NPC_DAMAGE_EASY 12 +#define BOWCASTER_NPC_DAMAGE_NORMAL 24 +#define BOWCASTER_NPC_DAMAGE_HARD 36 +#define BOWCASTER_SPLASH_DAMAGE 0 +#define BOWCASTER_SPLASH_RADIUS 0 +#define BOWCASTER_SIZE 2 + +#define BOWCASTER_ALT_SPREAD 5.0f +#define BOWCASTER_VEL_RANGE 0.3f +#define BOWCASTER_CHARGE_UNIT 200.0f // bowcaster charging gives us one more unit every 200ms--if you change this, you'll have to do the same in bg_pmove + +// Heavy Repeater +//---------- +#define REPEATER_SPREAD 1.4f +#define REPEATER_NPC_SPREAD 0.7f +#define REPEATER_DAMAGE 8 +#define REPEATER_VELOCITY 1600 +#define REPEATER_NPC_DAMAGE_EASY 2 +#define REPEATER_NPC_DAMAGE_NORMAL 4 +#define REPEATER_NPC_DAMAGE_HARD 6 + +#define REPEATER_ALT_SIZE 3 // half of bbox size +#define REPEATER_ALT_DAMAGE 60 +#define REPEATER_ALT_SPLASH_DAMAGE 60 +#define REPEATER_ALT_SPLASH_RADIUS 128 +#define REPEATER_ALT_VELOCITY 1100 +#define REPEATER_ALT_NPC_DAMAGE_EASY 15 +#define REPEATER_ALT_NPC_DAMAGE_NORMAL 30 +#define REPEATER_ALT_NPC_DAMAGE_HARD 45 + +// DEMP2 +//---------- +#define DEMP2_DAMAGE 15 +#define DEMP2_VELOCITY 1800 +#define DEMP2_NPC_DAMAGE_EASY 6 +#define DEMP2_NPC_DAMAGE_NORMAL 12 +#define DEMP2_NPC_DAMAGE_HARD 18 +#define DEMP2_SIZE 2 // half of bbox size + +#define DEMP2_ALT_DAMAGE 15 +#define DEMP2_CHARGE_UNIT 500.0f // demp2 charging gives us one more unit every 500ms--if you change this, you'll have to do the same in bg_pmove +#define DEMP2_ALT_RANGE 4096 +#define DEMP2_ALT_SPLASHRADIUS 256 + +// Golan Arms Flechette +//--------- +#define FLECHETTE_SHOTS 6 +#define FLECHETTE_SPREAD 4.0f +#define FLECHETTE_DAMAGE 15 +#define FLECHETTE_VEL 3500 +#define FLECHETTE_SIZE 1 + +#define FLECHETTE_ALT_DAMAGE 20 +#define FLECHETTE_ALT_SPLASH_DAM 20 +#define FLECHETTE_ALT_SPLASH_RAD 128 + +// NOT CURRENTLY USED +#define FLECHETTE_MINE_RADIUS_CHECK 200 +#define FLECHETTE_MINE_VEL 1000 +#define FLECHETTE_MINE_DAMAGE 100 +#define FLECHETTE_MINE_SPLASH_DAMAGE 200 +#define FLECHETTE_MINE_SPLASH_RADIUS 200 + +// Personal Rocket Launcher +//--------- +#define ROCKET_VELOCITY 900 +#define ROCKET_DAMAGE 100 +#define ROCKET_SPLASH_DAMAGE 100 +#define ROCKET_SPLASH_RADIUS 160 +#define ROCKET_NPC_DAMAGE_EASY 20 +#define ROCKET_NPC_DAMAGE_NORMAL 40 +#define ROCKET_NPC_DAMAGE_HARD 60 +#define ROCKET_SIZE 3 + +#define ROCKET_ALT_VELOCITY (ROCKET_VELOCITY*0.5) +#define ROCKET_ALT_THINK_TIME 100 + +// some naughty little things that are used cg side +int g_rocketLockEntNum = ENTITYNUM_NONE; +int g_rocketLockTime = 0; +int g_rocketSlackTime = 0; + +// Concussion Rifle +//--------- +//primary +#define CONC_VELOCITY 3000 +#define CONC_DAMAGE 150 +#define CONC_NPC_SPREAD 0.7f +#define CONC_NPC_DAMAGE_EASY 15 +#define CONC_NPC_DAMAGE_NORMAL 30 +#define CONC_NPC_DAMAGE_HARD 50 +#define CONC_SPLASH_DAMAGE 50 +#define CONC_SPLASH_RADIUS 300 +//alt +#define CONC_ALT_DAMAGE 225//100 +#define CONC_ALT_NPC_DAMAGE_EASY 10 +#define CONC_ALT_NPC_DAMAGE_MEDIUM 20 +#define CONC_ALT_NPC_DAMAGE_HARD 30 + +// Emplaced Gun +//-------------- +#define EMPLACED_VEL 6000 // very fast +#define EMPLACED_DAMAGE 150 // and very damaging +#define EMPLACED_SIZE 5 // make it easier to hit things + +// ATST Main Gun +//-------------- +#define ATST_MAIN_VEL 4000 // +#define ATST_MAIN_DAMAGE 25 // +#define ATST_MAIN_SIZE 3 // make it easier to hit things + +// ATST Side Gun +//--------------- +#define ATST_SIDE_MAIN_DAMAGE 75 +#define ATST_SIDE_MAIN_VELOCITY 1300 +#define ATST_SIDE_MAIN_NPC_DAMAGE_EASY 30 +#define ATST_SIDE_MAIN_NPC_DAMAGE_NORMAL 40 +#define ATST_SIDE_MAIN_NPC_DAMAGE_HARD 50 +#define ATST_SIDE_MAIN_SIZE 4 +#define ATST_SIDE_MAIN_SPLASH_DAMAGE 10 // yeah, pretty small, either zero out or make it worth having? +#define ATST_SIDE_MAIN_SPLASH_RADIUS 16 // yeah, pretty small, either zero out or make it worth having? + +#define ATST_SIDE_ALT_VELOCITY 1100 +#define ATST_SIDE_ALT_NPC_VELOCITY 600 +#define ATST_SIDE_ALT_DAMAGE 130 + +#define ATST_SIDE_ROCKET_NPC_DAMAGE_EASY 30 +#define ATST_SIDE_ROCKET_NPC_DAMAGE_NORMAL 50 +#define ATST_SIDE_ROCKET_NPC_DAMAGE_HARD 90 + +#define ATST_SIDE_ALT_SPLASH_DAMAGE 130 +#define ATST_SIDE_ALT_SPLASH_RADIUS 200 +#define ATST_SIDE_ALT_ROCKET_SIZE 5 +#define ATST_SIDE_ALT_ROCKET_SPLASH_SCALE 0.5f // scales splash for NPC's + +// Stun Baton +//-------------- +#define STUN_BATON_DAMAGE 22 +#define STUN_BATON_ALT_DAMAGE 22 +#define STUN_BATON_RANGE 25 + +// Laser Trip Mine +//-------------- +#define LT_DAMAGE 150 +#define LT_SPLASH_RAD 256.0f +#define LT_SPLASH_DAM 90 + +#define LT_VELOCITY 250.0f +#define LT_ALT_VELOCITY 1000.0f + +#define PROX_MINE_RADIUS_CHECK 190 + +#define LT_SIZE 3.0f +#define LT_ALT_TIME 2000 +#define LT_ACTIVATION_DELAY 1000 +#define LT_DELAY_TIME 50 + +// Thermal Detonator +//-------------- +#define TD_DAMAGE 100 +#define TD_NPC_DAMAGE_CUT 0.6f // NPC thrown dets deliver only 60% of the damage that a player thrown one does +#define TD_SPLASH_RAD 128 +#define TD_SPLASH_DAM 90 +#define TD_VELOCITY 900 +#define TD_MIN_CHARGE 0.15f +#define TD_TIME 4000 +#define TD_THINK_TIME 300 // don't think too often? +#define TD_TEST_RAD (TD_SPLASH_RAD * 0.8f) // no sense in auto-blowing up if exactly on the radius edge--it would hardly do any damage +#define TD_ALT_TIME 3000 + +#define TD_ALT_DAMAGE 100 +#define TD_ALT_SPLASH_RAD 128 +#define TD_ALT_SPLASH_DAM 90 +#define TD_ALT_VELOCITY 600 +#define TD_ALT_MIN_CHARGE 0.15f +#define TD_ALT_TIME 3000 + +// Tusken Rifle Shot +//-------------- +#define TUSKEN_RIFLE_VEL 3000 // fast +#define TUSKEN_RIFLE_DAMAGE_EASY 20 // damaging +#define TUSKEN_RIFLE_DAMAGE_MEDIUM 30 // very damaging +#define TUSKEN_RIFLE_DAMAGE_HARD 50 // extremely damaging + +// Weapon Helper Functions +float weaponSpeed[WP_NUM_WEAPONS][2] = +{ + 0,0,//WP_NONE, + 0,0,//WP_SABER, // NOTE: lots of code assumes this is the first weapon (... which is crap) so be careful -Ste. + BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL,//WP_BLASTER_PISTOL, + BLASTER_VELOCITY,BLASTER_VELOCITY,//WP_BLASTER, + Q3_INFINITE,Q3_INFINITE,//WP_DISRUPTOR, + BOWCASTER_VELOCITY,BOWCASTER_VELOCITY,//WP_BOWCASTER, + REPEATER_VELOCITY,REPEATER_ALT_VELOCITY,//WP_REPEATER, + DEMP2_VELOCITY,DEMP2_ALT_RANGE,//WP_DEMP2, + FLECHETTE_VEL,FLECHETTE_MINE_VEL,//WP_FLECHETTE, + ROCKET_VELOCITY,ROCKET_ALT_VELOCITY,//WP_ROCKET_LAUNCHER, + TD_VELOCITY,TD_ALT_VELOCITY,//WP_THERMAL, + 0,0,//WP_TRIP_MINE, + 0,0,//WP_DET_PACK, + CONC_VELOCITY,Q3_INFINITE,//WP_CONCUSSION, + 0,0,//WP_MELEE, // Any ol' melee attack + 0,0,//WP_STUN_BATON, + BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL,//WP_BRYAR_PISTOL, + EMPLACED_VEL,EMPLACED_VEL,//WP_EMPLACED_GUN, + BRYAR_PISTOL_VEL,BRYAR_PISTOL_VEL,//WP_BOT_LASER, // Probe droid - Laser blast + 0,0,//WP_TURRET, // turret guns + ATST_MAIN_VEL,ATST_MAIN_VEL,//WP_ATST_MAIN, + ATST_SIDE_MAIN_VELOCITY,ATST_SIDE_ALT_NPC_VELOCITY,//WP_ATST_SIDE, + EMPLACED_VEL,EMPLACED_VEL,//WP_TIE_FIGHTER, + EMPLACED_VEL,REPEATER_ALT_VELOCITY,//WP_RAPID_FIRE_CONC, + 0,0,//WP_JAWA, + TUSKEN_RIFLE_VEL,TUSKEN_RIFLE_VEL,//WP_TUSKEN_RIFLE, + 0,0,//WP_TUSKEN_STAFF, + 0,0,//WP_SCEPTER, + 0,0,//WP_NOGHRI_STICK, +}; + +float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire ) +{ + if ( alt_fire ) + { + return weaponSpeed[wp][1]; + } + return weaponSpeed[wp][0]; +} + +//----------------------------------------------------------------------------- +static void WP_TraceSetStart( const gentity_t *ent, vec3_t start, const vec3_t mins, const vec3_t maxs ) +//----------------------------------------------------------------------------- +{ + //make sure our start point isn't on the other side of a wall + trace_t tr; + vec3_t entMins, newstart; + vec3_t entMaxs; + + VectorSet( entMaxs, 5, 5, 5 ); + VectorScale( entMaxs, -1, entMins ); + + if ( !ent->client ) + { + return; + } + + VectorCopy( ent->currentOrigin, newstart ); + newstart[2] = start[2]; // force newstart to be on the same plane as the muzzle ( start ) + + gi.trace( &tr, newstart, entMins, entMaxs, start, ent->s.number, MASK_SOLID|CONTENTS_SHOTCLIP ); + + if ( tr.startsolid || tr.allsolid ) + { + // there is a problem here.. + return; + } + + if ( tr.fraction < 1.0f ) + { + VectorCopy( tr.endpos, start ); + } +} + +extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent ); +//----------------------------------------------------------------------------- +gentity_t *CreateMissile( vec3_t org, vec3_t dir, float vel, int life, gentity_t *owner, qboolean altFire = qfalse ) +//----------------------------------------------------------------------------- +{ + gentity_t *missile; + + missile = G_Spawn(); + + missile->nextthink = level.time + life; + missile->e_ThinkFunc = thinkF_G_FreeEntity; + missile->s.eType = ET_MISSILE; + missile->owner = owner; + + Vehicle_t* pVeh = G_IsRidingVehicle(owner); + + missile->alt_fire = altFire; + + missile->s.pos.trType = TR_LINEAR; + missile->s.pos.trTime = level.time;// - 10; // move a bit on the very first frame + VectorCopy( org, missile->s.pos.trBase ); + VectorScale( dir, vel, missile->s.pos.trDelta ); + if (pVeh) + { + missile->s.eFlags |= EF_USE_ANGLEDELTA; + vectoangles(missile->s.pos.trDelta, missile->s.angles); + VectorMA(missile->s.pos.trDelta, 2.0f, pVeh->m_pParentEntity->client->ps.velocity, missile->s.pos.trDelta); + } + + VectorCopy( org, missile->currentOrigin); + gi.linkentity( missile ); + + return missile; +} + + +//----------------------------------------------------------------------------- +static void WP_Stick( gentity_t *missile, trace_t *trace, float fudge_distance = 0.0f ) +//----------------------------------------------------------------------------- +{ + vec3_t org, ang; + + // not moving or rotating + missile->s.pos.trType = TR_STATIONARY; + VectorClear( missile->s.pos.trDelta ); + VectorClear( missile->s.apos.trDelta ); + + // so we don't stick into the wall + VectorMA( trace->endpos, fudge_distance, trace->plane.normal, org ); + G_SetOrigin( missile, org ); + + vectoangles( trace->plane.normal, ang ); + G_SetAngles( missile, ang ); + + // I guess explode death wants me as the normal? +// VectorCopy( trace->plane.normal, missile->pos1 ); + gi.linkentity( missile ); +} + +// This version shares is in the thinkFunc format +//----------------------------------------------------------------------------- +void WP_Explode( gentity_t *self ) +//----------------------------------------------------------------------------- +{ + gentity_t *attacker = self; + vec3_t forward={0,0,1}; + + // stop chain reaction runaway loops + self->takedamage = qfalse; + + self->s.loopSound = 0; + +// VectorCopy( self->currentOrigin, self->s.pos.trBase ); + if ( !self->client ) + { + AngleVectors( self->s.angles, forward, NULL, NULL ); + } + + if ( self->fxID > 0 ) + { + G_PlayEffect( self->fxID, self->currentOrigin, forward ); + } + + if ( self->owner ) + { + attacker = self->owner; + } + else if ( self->activator ) + { + attacker = self->activator; + } + + if ( self->splashDamage > 0 && self->splashRadius > 0 ) + { + G_RadiusDamage( self->currentOrigin, attacker, self->splashDamage, self->splashRadius, 0/*don't ignore attacker*/, MOD_EXPLOSIVE_SPLASH ); + } + + if ( self->target ) + { + G_UseTargets( self, attacker ); + } + + G_SetOrigin( self, self->currentOrigin ); + + self->nextthink = level.time + 50; + self->e_ThinkFunc = thinkF_G_FreeEntity; +} + +// We need to have a dieFunc, otherwise G_Damage won't actually make us die. I could modify G_Damage, but that entails too many changes +//----------------------------------------------------------------------------- +void WP_ExplosiveDie( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath,int dFlags,int hitLoc ) +//----------------------------------------------------------------------------- +{ + self->enemy = attacker; + + if ( attacker && !attacker->s.number ) + { + // less damage when shot by player + self->splashDamage /= 3; + self->splashRadius /= 3; + } + + self->s.eFlags &= ~EF_FIRING; // don't draw beam if we are dead + + WP_Explode( self ); +} + + +/* +---------------------------------------------- + PLAYER ITEMS +---------------------------------------------- +*/ +/* +#define SEEKER_RADIUS 500 + +gentity_t *SeekerAcquiresTarget ( gentity_t *ent, vec3_t pos ) +{ + vec3_t seekerPos; + float angle; + gentity_t *entityList[MAX_GENTITIES]; // targets within inital radius + gentity_t *visibleTargets[MAX_GENTITIES]; // final filtered target list + int numListedEntities; + int i, e; + gentity_t *target; + vec3_t mins, maxs; + + angle = cg.time * 0.004f; + + // must match cg_effects ( CG_Seeker ) & g_weapon ( SeekerAcquiresTarget ) & cg_weapons ( CG_FireSeeker ) + seekerPos[0] = ent->currentOrigin[0] + 18 * cos(angle); + seekerPos[1] = ent->currentOrigin[1] + 18 * sin(angle); + seekerPos[2] = ent->currentOrigin[2] + ent->client->ps.viewheight + 8 + (3*cos(level.time*0.001f)); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = seekerPos[i] - SEEKER_RADIUS; + maxs[i] = seekerPos[i] + SEEKER_RADIUS; + } + + // get potential targets within radius + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + i = 0; // reset counter + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + target = entityList[e]; + + // seeker owner not a valid target + if ( target == ent ) + { + continue; + } + + // only players are valid targets + if ( !target->client ) + { + continue; + } + + // teammates not valid targets + if ( OnSameTeam( ent, target ) ) + { + continue; + } + + // don't shoot at dead things + if ( target->health <= 0 ) + { + continue; + } + + if( CanDamage( target, seekerPos ) ) // visible target, so add it to the list + { + visibleTargets[i++] = entityList[e]; + } + } + + if ( i ) + { + // ok, now we know there are i visible targets. Pick one as the seeker's target + target = visibleTargets[Q_irand(0,i-1)]; + VectorCopy( seekerPos, pos ); + return target; + } + + return NULL; +} + +static void WP_FireBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire ); +void FireSeeker( gentity_t *owner, gentity_t *target, vec3_t origin, vec3_t dir ) +{ + VectorSubtract( target->currentOrigin, origin, dir ); + VectorNormalize( dir ); + + // for now I'm just using the scavenger bullet. + WP_FireBlasterMissile( owner, origin, dir, qfalse ); +} +*/ + + +#ifdef _XBOX // Auto-aim + +static float VectorDistanceSquared(vec3_t p1, vec3_t p2) +{ + vec3_t dir; + VectorSubtract(p2, p1, dir); + return VectorLengthSquared(dir); +} + +int WP_FindClosestBodyPart(gentity_t *ent, gentity_t *other, vec3_t point, vec3_t out, int c = 0) +{ + int shortestLen = 509999; + char where = -1; + int len; + renderInfo_t *ri = NULL; + + ri = &ent->client->renderInfo; + + if (ent->client) + { + if (c > 0) + { + where = c - 1; // Fail safe, set to torso + } + else + { + len = VectorDistanceSquared(point, ri->eyePoint); + if (len < shortestLen) { + shortestLen = len; where = 0; + } + + len = VectorDistanceSquared(point, ri->headPoint); + if (len < shortestLen) { + shortestLen = len; where = 1; + } + + len = VectorDistanceSquared(point, ri->handRPoint); + if (len < shortestLen) { + shortestLen = len; where = 2; + } + + len = VectorDistanceSquared(point, ri->handLPoint); + if (len < shortestLen) { + shortestLen = len; where = 3; + } + + len = VectorDistanceSquared(point, ri->crotchPoint); + if (len < shortestLen) { + shortestLen = len; where = 4; + } + + len = VectorDistanceSquared(point, ri->footRPoint); + if (len < shortestLen) { + shortestLen = len; where = 5; + } + + len = VectorDistanceSquared(point, ri->footLPoint); + if (len < shortestLen) { + shortestLen = len; where = 6; + } + + len = VectorDistanceSquared(point, ri->torsoPoint); + if (len < shortestLen) { + shortestLen = len; where = 7; + } + } + + if (where < 2 && c == 0) + { + if (random() < .75f) // 25% chance to actualy hit the head or eye + where = 7; + } + + switch (where) + { + case 0: + VectorCopy(ri->eyePoint, out); + break; + case 1: + VectorCopy(ri->headPoint, out); + break; + case 2: + VectorCopy(ri->handRPoint, out); + break; + case 3: + VectorCopy(ri->handLPoint, out); + break; + case 4: + VectorCopy(ri->crotchPoint, out); + break; + case 5: + VectorCopy(ri->footRPoint, out); + break; + case 6: + VectorCopy(ri->footLPoint, out); + break; + case 7: + VectorCopy(ri->torsoPoint, out); + break; + } + } + else + { + VectorCopy(ent->s.pos.trBase, out); + // Really bad hack + if (ent->classname && (strcmp(ent->classname, "misc_turret") == 0)) + { + out[2] = point[2]; + } + + } + + if (ent && ent->client && ent->client->NPC_class == CLASS_MINEMONSTER) + { + out[2] -= 24; // not a clue??? + return shortestLen; // mine critters are too small to randomize + } + + if (ent->NPC_type && !Q_stricmp(ent->NPC_type, "atst")) + { + // Dont randomize those atst's they have some pretty small legs + return shortestLen; + } + + if (c == 0) + { + // Add a bit of chance to the actual location + float r = random() * 8.0f - 4.0f; + float r2 = random() * 8.0f - 4.0f; + float r3 = random() * 10.0f - 5.0f; + + out[0] += r; + out[1] += r2; + out[2] += r3; + } + + return shortestLen; +} +#endif // Auto-aim + +//extern cvar_t *cv_autoAim; +#ifdef _XBOX // Auto-aim +static bool cv_autoAim = qtrue; +#endif // Auto-aim + +bool WP_MissileTargetHint(gentity_t* shooter, vec3_t start, vec3_t out) +{ +#ifdef _XBOX + extern short cg_crossHairStatus; + extern int g_crosshairEntNum; +// int allow = 0; +// allow = Cvar_VariableIntegerValue("cv_autoAim"); + +// if ((!cg.snap) || !allow ) return false; + if ((!cg.snap) || !cv_autoAim ) return false; + if (shooter->s.clientNum != 0) return false; // assuming shooter must be client, using 0 for cg_entities[0] a few lines down if you change this +// if (cg_crossHairStatus != 1 || cg_crosshairEntNum < 0 || cg_crosshairEntNum >= ENTITYNUM_WORLD) return false; + if (cg_crossHairStatus != 1 || g_crosshairEntNum < 0 || g_crosshairEntNum >= ENTITYNUM_WORLD) return false; + + gentity_t* traceEnt = &g_entities[g_crosshairEntNum]; + + vec3_t d_f, d_rt, d_up; + vec3_t end; + trace_t trace; + + // Calculate the end point + AngleVectors( cg_entities[0].lerpAngles, d_f, d_rt, d_up ); + VectorMA( start, 8192, d_f, end );//4028 is max for mind trick + + // This will get a detailed trace + gi.trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, MASK_OPAQUE|CONTENTS_SHOTCLIP|CONTENTS_BODY|CONTENTS_ITEM, G2_COLLIDE, 10); + // If the trace came up with a different entity then our crosshair, then you are not actualy over the enemy + if (trace.entityNum != g_crosshairEntNum) + { + // Must trace again to find out where the crosshair will end up + gi.trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, MASK_OPAQUE|CONTENTS_SHOTCLIP|CONTENTS_BODY|CONTENTS_ITEM, G2_NOCOLLIDE, 10 ); + + // Find the closest body part to the trace + WP_FindClosestBodyPart(traceEnt, shooter, trace.endpos, out); + + // Compute the direction vector between the shooter and the guy being shot + VectorSubtract(out, start, out); + VectorNormalize(out); + + for (int i = 1; i < 8; i++) /// do this 7 times to make sure we get it + { + /// Where will this direction end up? + VectorMA( start, 8192, out, end );//4028 is max for mind trick + + // Try it one more time, ??? are we trying to shoot through solid space?? + gi.trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, MASK_OPAQUE|CONTENTS_SHOTCLIP|CONTENTS_BODY|CONTENTS_ITEM, G2_COLLIDE, 10); + if (trace.entityNum != g_crosshairEntNum) + { + // Find the closest body part to the trace + WP_FindClosestBodyPart(traceEnt, shooter, trace.endpos, out, i); + + // Computer the direction vector between the shooter and the guy being shot + VectorSubtract(out, start, out); + VectorNormalize(out); + } + else + { + break; /// a hit wahoo + } + } + } + + return true; +#else // Auto-aim + return false; +#endif +} + +/* +---------------------------------------------- + PLAYER WEAPONS +---------------------------------------------- +*/ + +//--------------- +// Bryar Pistol +//--------------- + +//--------------------------------------------------------- +static void WP_FireBryarPistol( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + vec3_t start; + int damage = BRYAR_PISTOL_DAMAGE; + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + if ( !(ent->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 ) + {//force sight 2+ gives perfect aim + //FIXME: maybe force sight level 3 autoaims some? + if ( ent->NPC && ent->NPC->currentAim < 5 ) + { + vec3_t angs; + + vectoangles( forward, angs ); + + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + {//*sigh*, hack to make impworkers less accurate without affecteing imperial officer accuracy + angs[PITCH] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + angs[YAW] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + } + else + { + angs[PITCH] += ( crandom() * ((5-ent->NPC->currentAim)*0.25f) ); + angs[YAW] += ( crandom() * ((5-ent->NPC->currentAim)*0.25f) ); + } + + AngleVectors( angs, forward, NULL, NULL ); + } + } + + WP_MissileTargetHint(ent, start, forward); + + gentity_t *missile = CreateMissile( start, forward, BRYAR_PISTOL_VEL, 10000, ent, alt_fire ); + + missile->classname = "bryar_proj"; + if ( ent->s.weapon == WP_BLASTER_PISTOL + || ent->s.weapon == WP_JAWA ) + {//*SIGH*... I hate our weapon system... + missile->s.weapon = ent->s.weapon; + } + else + { + missile->s.weapon = WP_BRYAR_PISTOL; + } + + if ( alt_fire ) + { + int count = ( level.time - ent->client->ps.weaponChargeTime ) / BRYAR_CHARGE_UNIT; + + if ( count < 1 ) + { + count = 1; + } + else if ( count > 5 ) + { + count = 5; + } + + damage *= count; + missile->count = count; // this will get used in the projectile rendering code to make a beefier effect + } + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// missile->flags |= FL_OVERCHARGED; +// damage *= 2; +// } + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + + if ( alt_fire ) + { + missile->methodOfDeath = MOD_BRYAR_ALT; + } + else + { + missile->methodOfDeath = MOD_BRYAR; + } + + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + // we don't want it to bounce forever + missile->bounceCount = 8; + + if ( ent->weaponModel[1] > 0 ) + {//dual pistols, toggle the muzzle point back and forth between the two pistols each time he fires + ent->count = (ent->count)?0:1; + } +} + + +//--------------- +// Blaster +//--------------- + +//--------------------------------------------------------- +static void WP_FireBlasterMissile( gentity_t *ent, vec3_t start, vec3_t dir, qboolean altFire ) +//--------------------------------------------------------- +{ + int velocity = BLASTER_VELOCITY; + int damage = BLASTER_DAMAGE; + + if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE ) + { + damage *= 3; + velocity = ATST_MAIN_VEL + ent->client->ps.speed; + } + else + { + // If an enemy is shooting at us, lower the velocity so you have a chance to evade + if ( ent->client && ent->client->ps.clientNum != 0 && ent->client->NPC_class != CLASS_BOBAFETT ) + { + if ( g_spskill->integer < 2 ) + { + velocity *= BLASTER_NPC_VEL_CUT; + } + else + { + velocity *= BLASTER_NPC_HARD_VEL_CUT; + } + } + } + + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + WP_MissileTargetHint(ent, start, dir); + + gentity_t *missile = CreateMissile( start, dir, velocity, 10000, ent, altFire ); + + missile->classname = "blaster_proj"; + missile->s.weapon = WP_BLASTER; + + // Do the damages + if ( ent->s.number != 0 && ent->client->NPC_class != CLASS_BOBAFETT ) + { + if ( g_spskill->integer == 0 ) + { + damage = BLASTER_NPC_DAMAGE_EASY; + } + else if ( g_spskill->integer == 1 ) + { + damage = BLASTER_NPC_DAMAGE_NORMAL; + } + else + { + damage = BLASTER_NPC_DAMAGE_HARD; + } + } + +// if ( ent->client ) +// { +// if ( ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// missile->flags |= FL_OVERCHARGED; +// damage *= 2; +// } +// } + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + if ( altFire ) + { + missile->methodOfDeath = MOD_BLASTER_ALT; + } + else + { + missile->methodOfDeath = MOD_BLASTER; + } + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + // we don't want it to bounce forever + missile->bounceCount = 8; +} + +//--------------------------------------------------------- +void WP_FireTurboLaserMissile( gentity_t *ent, vec3_t start, vec3_t dir ) +//--------------------------------------------------------- +{ + int velocity = ent->mass; //FIXME: externalize + gentity_t *missile; + + missile = CreateMissile( start, dir, velocity, 10000, ent, qfalse ); + + //use a custom shot effect + //missile->s.otherEntityNum2 = G_EffectIndex( "turret/turb_shot" ); + //use a custom impact effect + //missile->s.emplacedOwner = G_EffectIndex( "turret/turb_impact" ); + + missile->classname = "turbo_proj"; + missile->s.weapon = WP_TIE_FIGHTER; + + missile->damage = ent->damage; //FIXME: externalize + missile->splashDamage = ent->splashDamage; //FIXME: externalize + missile->splashRadius = ent->splashRadius; //FIXME: externalize + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_EMPLACED; //MOD_TURBLAST; //count as a heavy weap + missile->splashMethodOfDeath = MOD_EMPLACED; //MOD_TURBLAST;// ?SPLASH; + missile->clipmask = MASK_SHOT; + + // we don't want it to bounce forever + missile->bounceCount = 8; + + //set veh as cgame side owner for purpose of fx overrides + //missile->s.owner = ent->s.number; + + //don't let them last forever + missile->e_ThinkFunc = thinkF_G_FreeEntity; + missile->nextthink = level.time + 10000;//at 20000 speed, that should be more than enough +} + +//--------------------------------------------------------- +static void WP_FireBlaster( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + vec3_t dir, angs; + + vectoangles( forward, angs ); + + if ( ent->client && ent->client->NPC_class == CLASS_VEHICLE ) + {//no inherent aim screw up + } + else if ( !(ent->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 ) + {//force sight 2+ gives perfect aim + //FIXME: maybe force sight level 3 autoaims some? + if ( alt_fire ) + { + // add some slop to the alt-fire direction + angs[PITCH] += crandom() * BLASTER_ALT_SPREAD; + angs[YAW] += crandom() * BLASTER_ALT_SPREAD; + } + else + { + // Troopers use their aim values as well as the gun's inherent inaccuracy + // so check for all classes of stormtroopers and anyone else that has aim error + if ( ent->client && ent->NPC && + ( ent->client->NPC_class == CLASS_STORMTROOPER || + ent->client->NPC_class == CLASS_SWAMPTROOPER ) ) + { + angs[PITCH] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + angs[YAW] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + } + else + { + // add some slop to the main-fire direction + angs[PITCH] += crandom() * BLASTER_MAIN_SPREAD; + angs[YAW] += crandom() * BLASTER_MAIN_SPREAD; + } + } + } + + AngleVectors( angs, dir, NULL, NULL ); + + // FIXME: if temp_org does not have clear trace to inside the bbox, don't shoot! + WP_FireBlasterMissile( ent, muzzle, dir, alt_fire ); +} + + +//--------------------- +// Tenloss Disruptor +//--------------------- +int G_GetHitLocFromTrace( trace_t *trace, int mod ) +{ + int hitLoc = HL_NONE; + for (int i=0; i < MAX_G2_COLLISIONS; i++) + { + if ( trace->G2CollisionMap[i].mEntityNum == -1 ) + { + break; + } + + CCollisionRecord &coll = trace->G2CollisionMap[i]; + if ( (coll.mFlags & G2_FRONTFACE) ) + { + G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ), &hitLoc, coll.mCollisionPosition, NULL, NULL, mod ); + //we only want the first "entrance wound", so break + break; + } + } + return hitLoc; +} + +//--------------------------------------------------------- +static void WP_DisruptorMainFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + int damage = DISRUPTOR_MAIN_DAMAGE; + qboolean render_impact = qtrue; + vec3_t start, end, spot; + trace_t tr; + gentity_t *traceEnt = NULL, *tent; + float dist, shotDist, shotRange = 8192; + + if ( ent->NPC ) + { + switch ( g_spskill->integer ) + { + case 0: + damage = DISRUPTOR_NPC_MAIN_DAMAGE_EASY; + break; + case 1: + damage = DISRUPTOR_NPC_MAIN_DAMAGE_MEDIUM; + break; + case 2: + default: + damage = DISRUPTOR_NPC_MAIN_DAMAGE_HARD; + break; + } + } + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin ); + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// damage *= 2; +// } + + WP_MissileTargetHint(ent, start, forward); + VectorMA( start, shotRange, forward, end ); + + int ignore = ent->s.number; + int traces = 0; + while ( traces < 10 ) + {//need to loop this in case we hit a Jedi who dodges the shot + gi.trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT, G2_RETURNONHIT, 0 ); + + traceEnt = &g_entities[tr.entityNum]; + if ( traceEnt + && ( traceEnt->s.weapon == WP_SABER || (traceEnt->client && (traceEnt->client->NPC_class == CLASS_BOBAFETT||traceEnt->client->NPC_class == CLASS_REBORN) ) ) ) + {//FIXME: need a more reliable way to know we hit a jedi? + if ( Jedi_DodgeEvasion( traceEnt, ent, &tr, HL_NONE ) ) + {//act like we didn't even hit him + VectorCopy( tr.endpos, start ); + ignore = tr.entityNum; + traces++; + continue; + } + } + //a Jedi is not dodging this shot + break; + } + + if ( tr.surfaceFlags & SURF_NOIMPACT ) + { + render_impact = qfalse; + } + + // always render a shot beam, doing this the old way because I don't much feel like overriding the effect. + tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT ); + tent->svFlags |= SVF_BROADCAST; + VectorCopy( muzzle, tent->s.origin2 ); + + if ( render_impact ) + { + if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage ) + { + // Create a simple impact type mark that doesn't last long in the world + G_PlayEffect( G_EffectIndex( "disruptor/flesh_impact" ), tr.endpos, tr.plane.normal ); + + if ( traceEnt->client && LogAccuracyHit( traceEnt, ent )) + { + ent->client->ps.persistant[PERS_ACCURACY_HITS]++; + } + + int hitLoc = G_GetHitLocFromTrace( &tr, MOD_DISRUPTOR ); + if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAKMECH ) + {//hehe + G_Damage( traceEnt, ent, ent, forward, tr.endpos, 3, DAMAGE_DEATH_KNOCKBACK, MOD_DISRUPTOR, hitLoc ); + } + else + { + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_DEATH_KNOCKBACK, MOD_DISRUPTOR, hitLoc ); + } + } + else + { + G_PlayEffect( G_EffectIndex( "disruptor/wall_impact" ), tr.endpos, tr.plane.normal ); + } + } + + shotDist = shotRange * tr.fraction; + + for ( dist = 0; dist < shotDist; dist += 64 ) + { + //FIXME: on a really long shot, this could make a LOT of alerts in one frame... + VectorMA( start, dist, forward, spot ); + AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 ); + } + VectorMA( start, shotDist-4, forward, spot ); + AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 ); +} + +//--------------------------------------------------------- +void WP_DisruptorAltFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + int damage = DISRUPTOR_ALT_DAMAGE, skip, traces = DISRUPTOR_ALT_TRACES; + qboolean render_impact = qtrue; + vec3_t start, end; + vec3_t muzzle2, spot, dir; + trace_t tr; + gentity_t *traceEnt, *tent; + float dist, shotDist, shotRange = 8192; + qboolean hitDodged = qfalse, fullCharge = qfalse; + + VectorCopy( muzzle, muzzle2 ); // making a backup copy + + // The trace start will originate at the eye so we can ensure that it hits the crosshair. + if ( ent->NPC ) + { + switch ( g_spskill->integer ) + { + case 0: + damage = DISRUPTOR_NPC_ALT_DAMAGE_EASY; + break; + case 1: + damage = DISRUPTOR_NPC_ALT_DAMAGE_MEDIUM; + break; + case 2: + default: + damage = DISRUPTOR_NPC_ALT_DAMAGE_HARD; + break; + } + VectorCopy( muzzle, start ); + + fullCharge = qtrue; + } + else + { + VectorCopy( ent->client->renderInfo.eyePoint, start ); + AngleVectors( ent->client->renderInfo.eyeAngles, forward, NULL, NULL ); + + // don't let NPC's do charging + int count = ( level.time - ent->client->ps.weaponChargeTime - 50 ) / DISRUPTOR_CHARGE_UNIT; + + if ( count < 1 ) + { + count = 1; + } + else if ( count >= 10 ) + { + count = 10; + fullCharge = qtrue; + } + + // more powerful charges go through more things + if ( count < 3 ) + { + traces = 1; + } + else if ( count < 6 ) + { + traces = 2; + } + //else do full traces + + damage = damage * count + DISRUPTOR_MAIN_DAMAGE * 0.5f; // give a boost to low charge shots + } + + skip = ent->s.number; + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// damage *= 2; +// } + + for ( int i = 0; i < traces; i++ ) + { + VectorMA( start, shotRange, forward, end ); + + //NOTE: if you want to be able to hit guys in emplaced guns, use "G2_COLLIDE, 10" instead of "G2_RETURNONHIT, 0" + //alternately, if you end up hitting an emplaced_gun that has a sitter, just redo this one trace with the "G2_COLLIDE, 10" to see if we it the sitter + gi.trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, G2_COLLIDE, 10 );//G2_RETURNONHIT, 0 ); + + if ( tr.surfaceFlags & SURF_NOIMPACT ) + { + render_impact = qfalse; + } + + if ( tr.entityNum == ent->s.number ) + { + // should never happen, but basically we don't want to consider a hit to ourselves? + // Get ready for an attempt to trace through another person + VectorCopy( tr.endpos, muzzle2 ); + VectorCopy( tr.endpos, start ); + skip = tr.entityNum; +#ifdef _DEBUG + gi.Printf( "BAD! Disruptor gun shot somehow traced back and hit the owner!\n" ); +#endif + continue; + } + + // always render a shot beam, doing this the old way because I don't much feel like overriding the effect. + //NOTE: let's just draw one beam, at the end + //tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT ); + //tent->svFlags |= SVF_BROADCAST; + //tent->alt_fire = fullCharge; // mark us so we can alter the effect + + //VectorCopy( muzzle2, tent->s.origin2 ); + + if ( tr.fraction >= 1.0f ) + { + // draw the beam but don't do anything else + break; + } + + traceEnt = &g_entities[tr.entityNum]; + + if ( traceEnt //&& traceEnt->NPC + && ( traceEnt->s.weapon == WP_SABER || (traceEnt->client && (traceEnt->client->NPC_class == CLASS_BOBAFETT||traceEnt->client->NPC_class == CLASS_REBORN) ) ) ) + {//FIXME: need a more reliable way to know we hit a jedi? + hitDodged = Jedi_DodgeEvasion( traceEnt, ent, &tr, HL_NONE ); + //acts like we didn't even hit him + } + if ( !hitDodged ) + { + if ( render_impact ) + { + if (( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage ) + || !Q_stricmp( traceEnt->classname, "misc_model_breakable" ) + || traceEnt->s.eType == ET_MOVER ) + { + // Create a simple impact type mark that doesn't last long in the world + G_PlayEffect( G_EffectIndex( "disruptor/alt_hit" ), tr.endpos, tr.plane.normal ); + + if ( traceEnt->client && LogAccuracyHit( traceEnt, ent )) + {//NOTE: hitting multiple ents can still get you over 100% accuracy + ent->client->ps.persistant[PERS_ACCURACY_HITS]++; + } + + int hitLoc = G_GetHitLocFromTrace( &tr, MOD_DISRUPTOR ); + if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAKMECH ) + {//hehe + G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, fullCharge ? MOD_SNIPER : MOD_DISRUPTOR, hitLoc ); + break; + } + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, fullCharge ? MOD_SNIPER : MOD_DISRUPTOR, hitLoc ); + if ( traceEnt->s.eType == ET_MOVER ) + {//stop the traces on any mover + break; + } + } + else + { + // we only make this mark on things that can't break or move + tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_MISS ); + tent->svFlags |= SVF_BROADCAST; + VectorCopy( tr.plane.normal, tent->pos1 ); + break; // hit solid, but doesn't take damage, so stop the shot...we _could_ allow it to shoot through walls, might be cool? + } + } + else // not rendering impact, must be a skybox or other similar thing? + { + break; // don't try anymore traces + } + } + // Get ready for an attempt to trace through another person + VectorCopy( tr.endpos, muzzle2 ); + VectorCopy( tr.endpos, start ); + skip = tr.entityNum; + hitDodged = qfalse; + } + //just draw one solid beam all the way to the end... + tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_SHOT ); + tent->svFlags |= SVF_BROADCAST; + tent->alt_fire = fullCharge; // mark us so we can alter the effect + VectorCopy( muzzle, tent->s.origin2 ); + + // now go along the trail and make sight events + VectorSubtract( tr.endpos, muzzle, dir ); + + shotDist = VectorNormalize( dir ); + + //FIXME: if shoot *really* close to someone, the alert could be way out of their FOV + for ( dist = 0; dist < shotDist; dist += 64 ) + { + //FIXME: on a really long shot, this could make a LOT of alerts in one frame... + VectorMA( muzzle, dist, dir, spot ); + AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 ); + } + //FIXME: spawn a temp ent that continuously spawns sight alerts here? And 1 sound alert to draw their attention? + VectorMA( start, shotDist-4, forward, spot ); + AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 ); +} + +//--------------------------------------------------------- +static void WP_FireDisruptor( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + if ( alt_fire ) + { + WP_DisruptorAltFire( ent ); + } + else + { + WP_DisruptorMainFire( ent ); + } + + G_PlayEffect( G_EffectIndex( "disruptor/line_cap" ), muzzle, forward ); +} + + +//------------------- +// Wookiee Bowcaster +//------------------- + +//--------------------------------------------------------- +static void WP_BowcasterMainFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + int damage = BOWCASTER_DAMAGE, count; + float vel; + vec3_t angs, dir, start; + gentity_t *missile; + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + // Do the damages + if ( ent->s.number != 0 ) + { + if ( g_spskill->integer == 0 ) + { + damage = BOWCASTER_NPC_DAMAGE_EASY; + } + else if ( g_spskill->integer == 1 ) + { + damage = BOWCASTER_NPC_DAMAGE_NORMAL; + } + else + { + damage = BOWCASTER_NPC_DAMAGE_HARD; + } + } + + count = ( level.time - ent->client->ps.weaponChargeTime ) / BOWCASTER_CHARGE_UNIT; + + if ( count < 1 ) + { + count = 1; + } + else if ( count > 5 ) + { + count = 5; + } + + if ( !(count & 1 )) + { + // if we aren't odd, knock us down a level + count--; + } + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// damage *= 2; +// } + + WP_MissileTargetHint(ent, start, forward); + for ( int i = 0; i < count; i++ ) + { + // create a range of different velocities + vel = BOWCASTER_VELOCITY * ( crandom() * BOWCASTER_VEL_RANGE + 1.0f ); + + vectoangles( forward, angs ); + + if ( !(ent->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 ) + {//force sight 2+ gives perfect aim + //FIXME: maybe force sight level 3 autoaims some? + // add some slop to the fire direction + angs[PITCH] += crandom() * BOWCASTER_ALT_SPREAD * 0.2f; + angs[YAW] += ((i+0.5f) * BOWCASTER_ALT_SPREAD - count * 0.5f * BOWCASTER_ALT_SPREAD ); + if ( ent->NPC ) + { + angs[PITCH] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f) ); + angs[YAW] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f) ); + } + } + + AngleVectors( angs, dir, NULL, NULL ); + + missile = CreateMissile( start, dir, vel, 10000, ent ); + + missile->classname = "bowcaster_proj"; + missile->s.weapon = WP_BOWCASTER; + + VectorSet( missile->maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// missile->flags |= FL_OVERCHARGED; +// } + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BOWCASTER; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + missile->splashDamage = BOWCASTER_SPLASH_DAMAGE; + missile->splashRadius = BOWCASTER_SPLASH_RADIUS; + + // we don't want it to bounce + missile->bounceCount = 0; + ent->client->sess.missionStats.shotsFired++; + } +} + +//--------------------------------------------------------- +static void WP_BowcasterAltFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + vec3_t start; + int damage = BOWCASTER_DAMAGE; + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + WP_MissileTargetHint(ent, start, forward); + + gentity_t *missile = CreateMissile( start, forward, BOWCASTER_VELOCITY, 10000, ent, qtrue ); + + missile->classname = "bowcaster_alt_proj"; + missile->s.weapon = WP_BOWCASTER; + + // Do the damages + if ( ent->s.number != 0 ) + { + if ( g_spskill->integer == 0 ) + { + damage = BOWCASTER_NPC_DAMAGE_EASY; + } + else if ( g_spskill->integer == 1 ) + { + damage = BOWCASTER_NPC_DAMAGE_NORMAL; + } + else + { + damage = BOWCASTER_NPC_DAMAGE_HARD; + } + } + + VectorSet( missile->maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// missile->flags |= FL_OVERCHARGED; +// damage *= 2; +// } + + missile->s.eFlags |= EF_BOUNCE; + missile->bounceCount = 3; + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BOWCASTER_ALT; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + missile->splashDamage = BOWCASTER_SPLASH_DAMAGE; + missile->splashRadius = BOWCASTER_SPLASH_RADIUS; +} + +//--------------------------------------------------------- +static void WP_FireBowcaster( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + if ( alt_fire ) + { + WP_BowcasterAltFire( ent ); + } + else + { + WP_BowcasterMainFire( ent ); + } +} + + +//------------------- +// Heavy Repeater +//------------------- + +//--------------------------------------------------------- +static void WP_RepeaterMainFire( gentity_t *ent, vec3_t dir ) +//--------------------------------------------------------- +{ + vec3_t start; + int damage = REPEATER_DAMAGE; + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + WP_MissileTargetHint(ent, start, dir); + + gentity_t *missile = CreateMissile( start, dir, REPEATER_VELOCITY, 10000, ent ); + + missile->classname = "repeater_proj"; + missile->s.weapon = WP_REPEATER; + + // Do the damages + if ( ent->s.number != 0 ) + { + if ( g_spskill->integer == 0 ) + { + damage = REPEATER_NPC_DAMAGE_EASY; + } + else if ( g_spskill->integer == 1 ) + { + damage = REPEATER_NPC_DAMAGE_NORMAL; + } + else + { + damage = REPEATER_NPC_DAMAGE_HARD; + } + } + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// missile->flags |= FL_OVERCHARGED; +// damage *= 2; +// } + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_REPEATER; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + // we don't want it to bounce forever + missile->bounceCount = 8; +} + +//--------------------------------------------------------- +static void WP_RepeaterAltFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + vec3_t start; + int damage = REPEATER_ALT_DAMAGE; + gentity_t *missile = NULL; + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + if ( ent->client && ent->client->NPC_class == CLASS_GALAKMECH ) + { + missile = CreateMissile( start, ent->client->hiddenDir, ent->client->hiddenDist, 10000, ent, qtrue ); + } + else + { + WP_MissileTargetHint(ent, start, forward); + missile = CreateMissile( start, forward, REPEATER_ALT_VELOCITY, 10000, ent, qtrue ); + } + + missile->classname = "repeater_alt_proj"; + missile->s.weapon = WP_REPEATER; + missile->mass = 10; + + // Do the damages + if ( ent->s.number != 0 ) + { + if ( g_spskill->integer == 0 ) + { + damage = REPEATER_ALT_NPC_DAMAGE_EASY; + } + else if ( g_spskill->integer == 1 ) + { + damage = REPEATER_ALT_NPC_DAMAGE_NORMAL; + } + else + { + damage = REPEATER_ALT_NPC_DAMAGE_HARD; + } + } + + VectorSet( missile->maxs, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE, REPEATER_ALT_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + missile->s.pos.trType = TR_GRAVITY; + missile->s.pos.trDelta[2] += 40.0f; //give a slight boost in the upward direction + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// missile->flags |= FL_OVERCHARGED; +// damage *= 2; +// } + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_REPEATER_ALT; + missile->splashMethodOfDeath = MOD_REPEATER_ALT; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + missile->splashDamage = REPEATER_ALT_SPLASH_DAMAGE; + missile->splashRadius = REPEATER_ALT_SPLASH_RADIUS; + + // we don't want it to bounce forever + missile->bounceCount = 8; +} + +//--------------------------------------------------------- +static void WP_FireRepeater( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + vec3_t dir, angs; + + vectoangles( forward, angs ); + + if ( alt_fire ) + { + WP_RepeaterAltFire( ent ); + } + else + { + if ( !(ent->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 ) + {//force sight 2+ gives perfect aim + //FIXME: maybe force sight level 3 autoaims some? + // Troopers use their aim values as well as the gun's inherent inaccuracy + // so check for all classes of stormtroopers and anyone else that has aim error + if ( ent->client && ent->NPC && + ( ent->client->NPC_class == CLASS_STORMTROOPER || + ent->client->NPC_class == CLASS_SWAMPTROOPER || + ent->client->NPC_class == CLASS_SHADOWTROOPER ) ) + { + angs[PITCH] += ( crandom() * (REPEATER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f) ); + angs[YAW] += ( crandom() * (REPEATER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f) ); + } + else + { + // add some slop to the alt-fire direction + angs[PITCH] += crandom() * REPEATER_SPREAD; + angs[YAW] += crandom() * REPEATER_SPREAD; + } + } + + AngleVectors( angs, dir, NULL, NULL ); + + // FIXME: if temp_org does not have clear trace to inside the bbox, don't shoot! + WP_RepeaterMainFire( ent, dir ); + } +} + +//------------------- +// DEMP2 +//------------------- + +//--------------------------------------------------------- +static void WP_DEMP2_MainFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + vec3_t start; + int damage = DEMP2_DAMAGE; + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + WP_MissileTargetHint(ent, start, forward); + + gentity_t *missile = CreateMissile( start, forward, DEMP2_VELOCITY, 10000, ent ); + + missile->classname = "demp2_proj"; + missile->s.weapon = WP_DEMP2; + + // Do the damages + if ( ent->s.number != 0 ) + { + if ( g_spskill->integer == 0 ) + { + damage = DEMP2_NPC_DAMAGE_EASY; + } + else if ( g_spskill->integer == 1 ) + { + damage = DEMP2_NPC_DAMAGE_NORMAL; + } + else + { + damage = DEMP2_NPC_DAMAGE_HARD; + } + } + + VectorSet( missile->maxs, DEMP2_SIZE, DEMP2_SIZE, DEMP2_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// missile->flags |= FL_OVERCHARGED; +// damage *= 2; +// } + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_DEMP2; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + // we don't want it to ever bounce + missile->bounceCount = 0; +} + +// NOTE: this is 100% for the demp2 alt-fire effect, so changes to the visual effect will affect game side demp2 code +//-------------------------------------------------- +void DEMP2_AltRadiusDamage( gentity_t *ent ) +{ + float frac = ( level.time - ent->fx_time ) / 1300.0f; // synchronize with demp2 effect + float dist, radius; + gentity_t *gent; + gentity_t *entityList[MAX_GENTITIES]; + int numListedEntities, i, e; + vec3_t mins, maxs; + vec3_t v, dir; + + frac *= frac * frac; // yes, this is completely ridiculous...but it causes the shell to grow slowly then "explode" at the end + + radius = frac * 200.0f; // 200 is max radius...the model is aprox. 100 units tall...the fx draw code mults. this by 2. + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = ent->currentOrigin[i] - radius; + maxs[i] = ent->currentOrigin[i] + radius; + } + + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + gent = entityList[ e ]; + + if ( !gent->takedamage || !gent->contents ) + { + continue; + } + + // find the distance from the edge of the bounding box + for ( i = 0 ; i < 3 ; i++ ) + { + if ( ent->currentOrigin[i] < gent->absmin[i] ) + { + v[i] = gent->absmin[i] - ent->currentOrigin[i]; + } + else if ( ent->currentOrigin[i] > gent->absmax[i] ) + { + v[i] = ent->currentOrigin[i] - gent->absmax[i]; + } + else + { + v[i] = 0; + } + } + + // shape is an ellipsoid, so cut vertical distance in half` + v[2] *= 0.5f; + + dist = VectorLength( v ); + + if ( dist >= radius ) + { + // shockwave hasn't hit them yet + continue; + } + + if ( dist < ent->radius ) + { + // shockwave has already hit this thing... + continue; + } + + VectorCopy( gent->currentOrigin, v ); + VectorSubtract( v, ent->currentOrigin, dir); + + // push the center of mass higher than the origin so players get knocked into the air more + dir[2] += 12; + + G_Damage( gent, ent, ent->owner, dir, ent->currentOrigin, DEMP2_ALT_DAMAGE, DAMAGE_DEATH_KNOCKBACK, ent->splashMethodOfDeath ); + if ( gent->takedamage && gent->client ) + { + gent->s.powerups |= ( 1 << PW_SHOCKED ); + gent->client->ps.powerups[PW_SHOCKED] = level.time + 2000; + Saboteur_Decloak( gent, Q_irand( 3000, 10000 ) ); + } + } + + // store the last fraction so that next time around we can test against those things that fall between that last point and where the current shockwave edge is + ent->radius = radius; + + if ( frac < 1.0f ) + { + // shock is still happening so continue letting it expand + ent->nextthink = level.time + 50; + } +} + + +//--------------------------------------------------------- +void DEMP2_AltDetonate( gentity_t *ent ) +//--------------------------------------------------------- +{ + G_SetOrigin( ent, ent->currentOrigin ); + + // start the effects, unfortunately, I wanted to do some custom things that I couldn't easily do with the fx system, so part of it uses an event and localEntities + G_PlayEffect( "demp2/altDetonate", ent->currentOrigin, ent->pos1 ); + G_AddEvent( ent, EV_DEMP2_ALT_IMPACT, ent->count * 2 ); + + ent->fx_time = level.time; + ent->radius = 0; + ent->nextthink = level.time + 50; + ent->e_ThinkFunc = thinkF_DEMP2_AltRadiusDamage; + ent->s.eType = ET_GENERAL; // make us a missile no longer +} + +//--------------------------------------------------------- +static void WP_DEMP2_AltFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + int damage = DEMP2_ALT_DAMAGE; + int count; + vec3_t start; + trace_t tr; + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + count = ( level.time - ent->client->ps.weaponChargeTime ) / DEMP2_CHARGE_UNIT; + + if ( count < 1 ) + { + count = 1; + } + else if ( count > 3 ) + { + count = 3; + } + + damage *= ( 1 + ( count * ( count - 1 )));// yields damage of 12,36,84...gives a higher bonus for longer charge + + // the shot can travel a whopping 4096 units in 1 second. Note that the shot will auto-detonate at 4096 units...we'll see if this looks cool or not + WP_MissileTargetHint(ent, start, forward); + gentity_t *missile = CreateMissile( start, forward, DEMP2_ALT_RANGE, 1000, ent, qtrue ); + + // letting it know what the charge size is. + missile->count = count; + +// missile->speed = missile->nextthink; + VectorCopy( tr.plane.normal, missile->pos1 ); + + missile->classname = "demp2_alt_proj"; + missile->s.weapon = WP_DEMP2; + + missile->e_ThinkFunc = thinkF_DEMP2_AltDetonate; + + missile->splashDamage = missile->damage = damage; + missile->splashMethodOfDeath = missile->methodOfDeath = MOD_DEMP2_ALT; + missile->splashRadius = DEMP2_ALT_SPLASHRADIUS; + + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + // we don't want it to ever bounce + missile->bounceCount = 0; +} + +//--------------------------------------------------------- +static void WP_FireDEMP2( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + if ( alt_fire ) + { + WP_DEMP2_AltFire( ent ); + } + else + { + WP_DEMP2_MainFire( ent ); + } +} + + +//----------------------- +// Golan Arms Flechette +//----------------------- + +//--------------------------------------------------------- +static void WP_FlechetteMainFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + vec3_t fwd, angs, start; + gentity_t *missile; + float damage = FLECHETTE_DAMAGE, vel = FLECHETTE_VEL; + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + // If we aren't the player, we will cut the velocity and damage of the shots + if ( ent->s.number ) + { + damage *= 0.75f; + vel *= 0.5f; + } + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// damage *= 2; +// } + + for ( int i = 0; i < FLECHETTE_SHOTS; i++ ) + { + vectoangles( forward, angs ); + + if ( i == 0 && ent->s.number == 0 ) + { + // do nothing on the first shot for the player, this one will hit the crosshairs + } + else + { + angs[PITCH] += crandom() * FLECHETTE_SPREAD; + angs[YAW] += crandom() * FLECHETTE_SPREAD; + } + + AngleVectors( angs, fwd, NULL, NULL ); + + WP_MissileTargetHint(ent, start, fwd); + + missile = CreateMissile( start, fwd, vel, 10000, ent ); + + missile->classname = "flech_proj"; + missile->s.weapon = WP_FLECHETTE; + + VectorSet( missile->maxs, FLECHETTE_SIZE, FLECHETTE_SIZE, FLECHETTE_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + + missile->damage = damage; + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// missile->flags |= FL_OVERCHARGED; +// } + + missile->dflags = (DAMAGE_DEATH_KNOCKBACK|DAMAGE_EXTRA_KNOCKBACK); + + missile->methodOfDeath = MOD_FLECHETTE; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + // we don't want it to bounce forever + missile->bounceCount = Q_irand(1,2); + + missile->s.eFlags |= EF_BOUNCE_SHRAPNEL; + ent->client->sess.missionStats.shotsFired++; + } +} + +//--------------------------------------------------------- +void prox_mine_think( gentity_t *ent ) +//--------------------------------------------------------- +{ + int count; + qboolean blow = qfalse; + + // if it isn't time to auto-explode, do a small proximity check + if ( ent->delay > level.time ) + { + count = G_RadiusList( ent->currentOrigin, FLECHETTE_MINE_RADIUS_CHECK, ent, qtrue, ent_list ); + + for ( int i = 0; i < count; i++ ) + { + if ( ent_list[i]->client && ent_list[i]->health > 0 && ent->activator && ent_list[i]->s.number != ent->activator->s.number ) + { + blow = qtrue; + break; + } + } + } + else + { + // well, we must die now + blow = qtrue; + } + + if ( blow ) + { +// G_Sound( ent, G_SoundIndex( "sound/weapons/flechette/warning.wav" )); + ent->e_ThinkFunc = thinkF_WP_Explode; + ent->nextthink = level.time + 200; + } + else + { + // we probably don't need to do this thinking logic very often...maybe this is fast enough? + ent->nextthink = level.time + 500; + } +} + +//--------------------------------------------------------- +void prox_mine_stick( gentity_t *self, gentity_t *other, trace_t *trace ) +//--------------------------------------------------------- +{ + // turn us into a generic entity so we aren't running missile code + self->s.eType = ET_GENERAL; + + self->s.modelindex = G_ModelIndex("models/weapons2/golan_arms/prox_mine.md3"); + self->e_TouchFunc = touchF_NULL; + + self->contents = CONTENTS_SOLID; + self->takedamage = qtrue; + self->health = 5; + self->e_DieFunc = dieF_WP_ExplosiveDie; + + VectorSet( self->maxs, 5, 5, 5 ); + VectorScale( self->maxs, -1, self->mins ); + + self->activator = self->owner; + self->owner = NULL; + + WP_Stick( self, trace ); + + self->e_ThinkFunc = thinkF_prox_mine_think; + self->nextthink = level.time + 450; + + // sticks for twenty seconds, then auto blows. + self->delay = level.time + 20000; + + gi.linkentity( self ); +} +/* Old Flechette alt-fire code.... +//--------------------------------------------------------- +static void WP_FlechetteProxMine( gentity_t *ent ) +//--------------------------------------------------------- +{ + gentity_t *missile = CreateMissile( muzzle, forward, FLECHETTE_MINE_VEL, 10000, ent, qtrue ); + + missile->fxID = G_EffectIndex( "flechette/explosion" ); + + missile->classname = "proxMine"; + missile->s.weapon = WP_FLECHETTE; + + missile->s.pos.trType = TR_GRAVITY; + + missile->s.eFlags |= EF_MISSILE_STICK; + missile->e_TouchFunc = touchF_prox_mine_stick; + + missile->damage = FLECHETTE_MINE_DAMAGE; + missile->methodOfDeath = MOD_EXPLOSIVE; + + missile->splashDamage = FLECHETTE_MINE_SPLASH_DAMAGE; + missile->splashRadius = FLECHETTE_MINE_SPLASH_RADIUS; + missile->splashMethodOfDeath = MOD_EXPLOSIVE_SPLASH; + + missile->clipmask = MASK_SHOT; + + // we don't want it to bounce forever + missile->bounceCount = 0; +} +*/ +//---------------------------------------------- +void WP_flechette_alt_blow( gentity_t *ent ) +//---------------------------------------------- +{ + EvaluateTrajectory( &ent->s.pos, level.time, ent->currentOrigin ); // Not sure if this is even necessary, but correct origins are cool? + + G_RadiusDamage( ent->currentOrigin, ent->owner, ent->splashDamage, ent->splashRadius, NULL, MOD_EXPLOSIVE_SPLASH ); + G_PlayEffect( "flechette/alt_blow", ent->currentOrigin ); + + G_FreeEntity( ent ); +} + +//------------------------------------------------------------------------------ +static void WP_CreateFlechetteBouncyThing( vec3_t start, vec3_t fwd, gentity_t *self ) +//------------------------------------------------------------------------------ +{ + gentity_t *missile = CreateMissile( start, fwd, 950 + random() * 700, 1500 + random() * 2000, self, qtrue ); + + missile->e_ThinkFunc = thinkF_WP_flechette_alt_blow; + + missile->s.weapon = WP_FLECHETTE; + missile->classname = "flech_alt"; + missile->mass = 4; + + // How 'bout we give this thing a size... + VectorSet( missile->mins, -3.0f, -3.0f, -3.0f ); + VectorSet( missile->maxs, 3.0f, 3.0f, 3.0f ); + missile->clipmask = MASK_SHOT; + missile->clipmask &= ~CONTENTS_CORPSE; + + // normal ones bounce, alt ones explode on impact + missile->s.pos.trType = TR_GRAVITY; + + missile->s.eFlags |= EF_BOUNCE_HALF; + + missile->damage = FLECHETTE_ALT_DAMAGE; + missile->dflags = 0; + missile->splashDamage = FLECHETTE_ALT_SPLASH_DAM; + missile->splashRadius = FLECHETTE_ALT_SPLASH_RAD; + + missile->svFlags = SVF_USE_CURRENT_ORIGIN; + + missile->methodOfDeath = MOD_FLECHETTE_ALT; + missile->splashMethodOfDeath = MOD_FLECHETTE_ALT; + + VectorCopy( start, missile->pos2 ); +} + +//--------------------------------------------------------- +static void WP_FlechetteAltFire( gentity_t *self ) +//--------------------------------------------------------- +{ + vec3_t dir, fwd, start, angs; + + vectoangles( forward, angs ); + VectorCopy( muzzle, start ); + + WP_TraceSetStart( self, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + for ( int i = 0; i < 2; i++ ) + { + VectorCopy( angs, dir ); + + dir[PITCH] -= random() * 4 + 8; // make it fly upwards + dir[YAW] += crandom() * 2; + AngleVectors( dir, fwd, NULL, NULL ); + + WP_CreateFlechetteBouncyThing( start, fwd, self ); + self->client->sess.missionStats.shotsFired++; + } +} + +//--------------------------------------------------------- +static void WP_FireFlechette( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + if ( alt_fire ) + { + WP_FlechetteAltFire( ent ); + } + else + { + WP_FlechetteMainFire( ent ); + } +} + + +//----------------------- +// Rocket Launcher +//----------------------- + +//--------------------------------------------------------- +void rocketThink( gentity_t *ent ) +//--------------------------------------------------------- +{ + vec3_t newdir, targetdir, + up={0,0,1}, right; + vec3_t org; + float dot, dot2; + + if ( ent->disconnectDebounceTime && ent->disconnectDebounceTime < level.time ) + {//time's up, we're done, remove us + if ( ent->lockCount ) + {//explode when die + WP_ExplosiveDie( ent, ent->owner, ent->owner, 0, MOD_UNKNOWN, 0, HL_NONE ); + } + else + {//just remove when die + G_FreeEntity( ent ); + } + return; + } + if ( ent->enemy && ent->enemy->inuse ) + { + float vel = (ent->spawnflags&1)?ent->speed:ROCKET_VELOCITY; + float newDirMult = ent->angle?ent->angle*2.0f:1.0f; + float oldDirMult = ent->angle?(1.0f-ent->angle)*2.0f:1.0f; + + if ( (ent->spawnflags&1) ) + {//vehicle rocket + if ( ent->enemy->client && ent->enemy->client->NPC_class == CLASS_VEHICLE ) + {//tracking another vehicle + if ( ent->enemy->client->ps.speed+ent->speed > vel ) + { + vel = ent->enemy->client->ps.speed+ent->speed; + } + } + } + + VectorCopy( ent->enemy->currentOrigin, org ); + org[2] += (ent->enemy->mins[2] + ent->enemy->maxs[2]) * 0.5f; + + if ( ent->enemy->client ) + { + switch( ent->enemy->client->NPC_class ) + { + case CLASS_ATST: + org[2] += 80; + break; + case CLASS_MARK1: + org[2] += 40; + break; + case CLASS_PROBE: + org[2] += 60; + break; + } + if ( !TIMER_Done( ent->enemy, "flee" ) ) + { + TIMER_Set( ent->enemy, "rocketChasing", 500 ); + } + } + + VectorSubtract( org, ent->currentOrigin, targetdir ); + VectorNormalize( targetdir ); + + // Now the rocket can't do a 180 in space, so we'll limit the turn to about 45 degrees. + dot = DotProduct( targetdir, ent->movedir ); + + // a dot of 1.0 means right-on-target. + if ( dot < 0.0f ) + { + // Go in the direction opposite, start a 180. + CrossProduct( ent->movedir, up, right ); + dot2 = DotProduct( targetdir, right ); + + if ( dot2 > 0 ) + { + // Turn 45 degrees right. + VectorMA( ent->movedir, 0.3f*newDirMult, right, newdir ); + } + else + { + // Turn 45 degrees left. + VectorMA(ent->movedir, -0.3f*newDirMult, right, newdir); + } + + // Yeah we've adjusted horizontally, but let's split the difference vertically, so we kinda try to move towards it. + newdir[2] = ( (targetdir[2]*newDirMult) + (ent->movedir[2]*oldDirMult) ) * 0.5; + + // slowing down coupled with fairly tight turns can lead us to orbit an enemy..looks bad so don't do it! +// vel *= 0.5f; + } + else if ( dot < 0.70f ) + { + // Still a bit off, so we turn a bit softer + VectorMA( ent->movedir, 0.5f*newDirMult, targetdir, newdir ); + } + else + { + // getting close, so turn a bit harder + VectorMA( ent->movedir, 0.9f*newDirMult, targetdir, newdir ); + } + + // add crazy drunkenness + for ( int i = 0; i < 3; i++ ) + { + newdir[i] += crandom() * ent->random * 0.25f; + } + + // decay the randomness + ent->random *= 0.9f; + + if ( ent->enemy->client + && ent->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//tracking a client who's on the ground, aim at the floor...? + // Try to crash into the ground if we get close enough to do splash damage + float dis = Distance( ent->currentOrigin, org ); + + if ( dis < 128 ) + { + // the closer we get, the more we push the rocket down, heh heh. + newdir[2] -= (1.0f - (dis / 128.0f)) * 0.6f; + } + } + + VectorNormalize( newdir ); + + VectorScale( newdir, vel * 0.5f, ent->s.pos.trDelta ); + VectorCopy( newdir, ent->movedir ); + SnapVector( ent->s.pos.trDelta ); // save net bandwidth + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + ent->s.pos.trTime = level.time; + } + + ent->nextthink = level.time + ROCKET_ALT_THINK_TIME; // Nothing at all spectacular happened, continue. + return; +} + +//--------------------------------------------------------- +static void WP_FireRocket( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + vec3_t start; + int damage = ROCKET_DAMAGE; + float vel = ROCKET_VELOCITY; + + if ( alt_fire ) + { + vel *= 0.5f; + } + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + gentity_t *missile = CreateMissile( start, forward, vel, 10000, ent, alt_fire ); + + missile->classname = "rocket_proj"; + missile->s.weapon = WP_ROCKET_LAUNCHER; + missile->mass = 10; + + // Do the damages + if ( ent->s.number != 0 ) + { + if ( g_spskill->integer == 0 ) + { + damage = ROCKET_NPC_DAMAGE_EASY; + } + else if ( g_spskill->integer == 1 ) + { + damage = ROCKET_NPC_DAMAGE_NORMAL; + } + else + { + damage = ROCKET_NPC_DAMAGE_HARD; + } + if (ent->client && ent->client->NPC_class==CLASS_BOBAFETT) + { + damage = damage/2; + } + } + + if ( alt_fire ) + { + int lockEntNum, lockTime; + if ( ent->NPC && ent->enemy ) + { + lockEntNum = ent->enemy->s.number; + lockTime = Q_irand( 600, 1200 ); + } + else + { + lockEntNum = g_rocketLockEntNum; + lockTime = g_rocketLockTime; + } + // we'll consider attempting to lock this little poochie onto some baddie. + if ( (lockEntNum > 0||ent->NPC&&lockEntNum>=0) && lockEntNum < ENTITYNUM_WORLD && lockTime > 0 ) + { + // take our current lock time and divide that by 8 wedge slices to get the current lock amount + int dif = ( level.time - lockTime ) / ( 1200.0f / 8.0f ); + + if ( dif < 0 ) + { + dif = 0; + } + else if ( dif > 8 ) + { + dif = 8; + } + + // if we are fully locked, always take on the enemy. + // Also give a slight advantage to higher, but not quite full charges. + // Finally, just give any amount of charge a very slight random chance of locking. + if ( dif == 8 || random() * dif > 2 || random() > 0.97f ) + { + missile->enemy = &g_entities[lockEntNum]; + + if ( missile->enemy + && missile->enemy->inuse )//&& DistanceSquared( missile->currentOrigin, missile->enemy->currentOrigin ) < 262144 && InFOV( missile->currentOrigin, missile->enemy->currentOrigin, missile->enemy->client->ps.viewangles, 45, 45 ) ) + { + if ( missile->enemy->client + && (missile->enemy->client->ps.forcePowersKnown&(1<enemy->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_0 ) + {//have force push, don't flee from homing rockets + } + else + { + vec3_t dir, dir2; + + AngleVectors( missile->enemy->currentAngles, dir, NULL, NULL ); + AngleVectors( ent->client->renderInfo.eyeAngles, dir2, NULL, NULL ); + + if ( DotProduct( dir, dir2 ) < 0.0f ) + { + G_StartFlee( missile->enemy, ent, missile->enemy->currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); + if ( !TIMER_Done( missile->enemy, "flee" ) ) + { + TIMER_Set( missile->enemy, "rocketChasing", 500 ); + } + } + } + } + } + } + + VectorCopy( forward, missile->movedir ); + + missile->e_ThinkFunc = thinkF_rocketThink; + missile->random = 1.0f; + missile->nextthink = level.time + ROCKET_ALT_THINK_TIME; + } + + // Make it easier to hit things + VectorSet( missile->maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + + if ( alt_fire ) + { + missile->methodOfDeath = MOD_ROCKET_ALT; + missile->splashMethodOfDeath = MOD_ROCKET_ALT;// ?SPLASH; + } + else + { + missile->methodOfDeath = MOD_ROCKET; + missile->splashMethodOfDeath = MOD_ROCKET;// ?SPLASH; + } + + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + missile->splashDamage = ROCKET_SPLASH_DAMAGE; + missile->splashRadius = ROCKET_SPLASH_RADIUS; + + // we don't want it to ever bounce + missile->bounceCount = 0; +} + +static void WP_FireConcussionAlt( gentity_t *ent ) +{//a rail-gun-like beam + int damage = CONC_ALT_DAMAGE, skip, traces = DISRUPTOR_ALT_TRACES; + qboolean render_impact = qtrue; + vec3_t start, end; + vec3_t muzzle2, spot, dir; + trace_t tr; + gentity_t *traceEnt, *tent; + float dist, shotDist, shotRange = 8192; + qboolean hitDodged = qfalse; + + if (ent->s.number >= MAX_CLIENTS) + { + vec3_t angles; + vectoangles(forward, angles); + angles[PITCH] += ( crandom() * (CONC_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + angles[YAW] += ( crandom() * (CONC_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + AngleVectors(angles, forward, vright, up); + } + + //Shove us backwards for half a second + VectorMA( ent->client->ps.velocity, -200, forward, ent->client->ps.velocity ); + ent->client->ps.groundEntityNum = ENTITYNUM_NONE; + if ( (ent->client->ps.pm_flags&PMF_DUCKED) ) + {//hunkered down + ent->client->ps.pm_time = 100; + } + else + { + ent->client->ps.pm_time = 250; + } + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK|PMF_TIME_NOFRICTION; + //FIXME: only if on ground? So no "rocket jump"? Or: (see next FIXME) + //FIXME: instead, set a forced ucmd backmove instead of this sliding + + VectorCopy( muzzle, muzzle2 ); // making a backup copy + + // The trace start will originate at the eye so we can ensure that it hits the crosshair. + if ( ent->NPC ) + { + switch ( g_spskill->integer ) + { + case 0: + damage = CONC_ALT_NPC_DAMAGE_EASY; + break; + case 1: + damage = CONC_ALT_NPC_DAMAGE_MEDIUM; + break; + case 2: + default: + damage = CONC_ALT_NPC_DAMAGE_HARD; + break; + } + } + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin ); + + skip = ent->s.number; + +// if ( ent->client && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// damage *= 2; +// } + + //Make it a little easier to hit guys at long range + vec3_t shot_mins, shot_maxs; + VectorSet( shot_mins, -1, -1, -1 ); + VectorSet( shot_maxs, 1, 1, 1 ); + + for ( int i = 0; i < traces; i++ ) + { + VectorMA( start, shotRange, forward, end ); + + //NOTE: if you want to be able to hit guys in emplaced guns, use "G2_COLLIDE, 10" instead of "G2_RETURNONHIT, 0" + //alternately, if you end up hitting an emplaced_gun that has a sitter, just redo this one trace with the "G2_COLLIDE, 10" to see if we it the sitter + //gi.trace( &tr, start, NULL, NULL, end, skip, MASK_SHOT, G2_COLLIDE, 10 );//G2_RETURNONHIT, 0 ); + gi.trace( &tr, start, shot_mins, shot_maxs, end, skip, MASK_SHOT, G2_COLLIDE, 10 );//G2_RETURNONHIT, 0 ); + + if ( tr.surfaceFlags & SURF_NOIMPACT ) + { + render_impact = qfalse; + } + + if ( tr.entityNum == ent->s.number ) + { + // should never happen, but basically we don't want to consider a hit to ourselves? + // Get ready for an attempt to trace through another person + VectorCopy( tr.endpos, muzzle2 ); + VectorCopy( tr.endpos, start ); + skip = tr.entityNum; +#ifdef _DEBUG + gi.Printf( "BAD! Concussion gun shot somehow traced back and hit the owner!\n" ); +#endif + continue; + } + + // always render a shot beam, doing this the old way because I don't much feel like overriding the effect. + //NOTE: let's just draw one beam at the end + //tent = G_TempEntity( tr.endpos, EV_CONC_ALT_SHOT ); + //tent->svFlags |= SVF_BROADCAST; + + //VectorCopy( muzzle2, tent->s.origin2 ); + + if ( tr.fraction >= 1.0f ) + { + // draw the beam but don't do anything else + break; + } + + traceEnt = &g_entities[tr.entityNum]; + + if ( traceEnt //&& traceEnt->NPC + && ( traceEnt->s.weapon == WP_SABER || (traceEnt->client && (traceEnt->client->NPC_class == CLASS_BOBAFETT||traceEnt->client->NPC_class == CLASS_REBORN) ) ) ) + {//FIXME: need a more reliable way to know we hit a jedi? + hitDodged = Jedi_DodgeEvasion( traceEnt, ent, &tr, HL_NONE ); + //acts like we didn't even hit him + } + if ( !hitDodged ) + { + if ( render_impact ) + { + if (( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage ) + || !Q_stricmp( traceEnt->classname, "misc_model_breakable" ) + || traceEnt->s.eType == ET_MOVER ) + { + // Create a simple impact type mark that doesn't last long in the world + G_PlayEffect( G_EffectIndex( "concussion/alt_hit" ), tr.endpos, tr.plane.normal ); + + if ( traceEnt->client && LogAccuracyHit( traceEnt, ent )) + {//NOTE: hitting multiple ents can still get you over 100% accuracy + ent->client->ps.persistant[PERS_ACCURACY_HITS]++; + } + + int hitLoc = G_GetHitLocFromTrace( &tr, MOD_CONC_ALT ); + qboolean noKnockBack = (traceEnt->flags&FL_NO_KNOCKBACK);//will be set if they die, I want to know if it was on *before* they died + if ( traceEnt && traceEnt->client && traceEnt->client->NPC_class == CLASS_GALAKMECH ) + {//hehe + G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT, hitLoc ); + break; + } + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_CONC_ALT, hitLoc ); + + //do knockback and knockdown manually + if ( traceEnt->client ) + {//only if we hit a client + vec3_t pushDir; + VectorCopy( forward, pushDir ); + if ( pushDir[2] < 0.2f ) + { + pushDir[2] = 0.2f; + }//hmm, re-normalize? nah... + //if ( traceEnt->NPC || Q_irand(0,g_spskill->integer+1) ) + { + if ( !noKnockBack ) + {//knock-backable + G_Throw( traceEnt, pushDir, 200 ); + if ( traceEnt->client->NPC_class == CLASS_ROCKETTROOPER ) + { + traceEnt->client->ps.pm_time = Q_irand( 1500, 3000 ); + } + } + if ( traceEnt->health > 0 ) + {//alive + if ( G_HasKnockdownAnims( traceEnt ) ) + {//knock-downable + G_Knockdown( traceEnt, ent, pushDir, 400, qtrue ); + } + } + } + } + + if ( traceEnt->s.eType == ET_MOVER ) + {//stop the traces on any mover + break; + } + } + else + { + // we only make this mark on things that can't break or move + tent = G_TempEntity( tr.endpos, EV_CONC_ALT_MISS ); + tent->svFlags |= SVF_BROADCAST; + VectorCopy( tr.plane.normal, tent->pos1 ); + break; // hit solid, but doesn't take damage, so stop the shot...we _could_ allow it to shoot through walls, might be cool? + } + } + else // not rendering impact, must be a skybox or other similar thing? + { + break; // don't try anymore traces + } + } + // Get ready for an attempt to trace through another person + VectorCopy( tr.endpos, muzzle2 ); + VectorCopy( tr.endpos, start ); + skip = tr.entityNum; + hitDodged = qfalse; + } + //just draw one beam all the way to the end + tent = G_TempEntity( tr.endpos, EV_CONC_ALT_SHOT ); + tent->svFlags |= SVF_BROADCAST; + VectorCopy( muzzle, tent->s.origin2 ); + + // now go along the trail and make sight events + VectorSubtract( tr.endpos, muzzle, dir ); + + shotDist = VectorNormalize( dir ); + + //FIXME: if shoot *really* close to someone, the alert could be way out of their FOV + for ( dist = 0; dist < shotDist; dist += 64 ) + { + //FIXME: on a really long shot, this could make a LOT of alerts in one frame... + VectorMA( muzzle, dist, dir, spot ); + AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 ); + //FIXME: creates *way* too many effects, make it one effect somehow? + G_PlayEffect( G_EffectIndex( "concussion/alt_ring" ), spot, forward ); + } + //FIXME: spawn a temp ent that continuously spawns sight alerts here? And 1 sound alert to draw their attention? + VectorMA( start, shotDist-4, forward, spot ); + AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 ); + + G_PlayEffect( G_EffectIndex( "concussion/altmuzzle_flash" ), muzzle, forward ); +} + +static void WP_FireConcussion( gentity_t *ent ) +{//a fast rocket-like projectile + vec3_t start; + int damage = CONC_DAMAGE; + float vel = CONC_VELOCITY; + + if (ent->s.number >= MAX_CLIENTS) + { + vec3_t angles; + vectoangles(forward, angles); + angles[PITCH] += ( crandom() * (CONC_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + angles[YAW] += ( crandom() * (CONC_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + AngleVectors(angles, forward, vright, up); + } + + //hold us still for a bit + ent->client->ps.pm_time = 300; + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + //add viewkick + if ( ent->s.number < MAX_CLIENTS//player only + && !cg.renderingThirdPerson )//gives an advantage to being in 3rd person, but would look silly otherwise + {//kick the view back + cg.kick_angles[PITCH] = Q_flrand( -10, -15 ); + cg.kick_time = level.time; + } + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + gentity_t *missile = CreateMissile( start, forward, vel, 10000, ent, qfalse ); + + missile->classname = "conc_proj"; + missile->s.weapon = WP_CONCUSSION; + missile->mass = 10; + + // Do the damages + if ( ent->s.number != 0 ) + { + if ( g_spskill->integer == 0 ) + { + damage = CONC_NPC_DAMAGE_EASY; + } + else if ( g_spskill->integer == 1 ) + { + damage = CONC_NPC_DAMAGE_NORMAL; + } + else + { + damage = CONC_NPC_DAMAGE_HARD; + } + } + + // Make it easier to hit things + VectorSet( missile->maxs, ROCKET_SIZE, ROCKET_SIZE, ROCKET_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + + missile->damage = damage; + missile->dflags = DAMAGE_EXTRA_KNOCKBACK; + + missile->methodOfDeath = MOD_CONC; + missile->splashMethodOfDeath = MOD_CONC; + + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + missile->splashDamage = CONC_SPLASH_DAMAGE; + missile->splashRadius = CONC_SPLASH_RADIUS; + + // we don't want it to ever bounce + missile->bounceCount = 0; +} + +//----------------------- +// Det Pack +//----------------------- + +//--------------------------------------------------------- +void charge_stick( gentity_t *self, gentity_t *other, trace_t *trace ) +//--------------------------------------------------------- +{ + self->s.eType = ET_GENERAL; + + // make us so we can take damage + self->clipmask = MASK_SHOT; + self->contents = CONTENTS_SHOTCLIP; + self->takedamage = qtrue; + self->health = 25; + + self->e_DieFunc = dieF_WP_ExplosiveDie; + + VectorSet( self->maxs, 10, 10, 10 ); + VectorScale( self->maxs, -1, self->mins ); + + self->activator = self->owner; + self->owner = NULL; + + self->e_TouchFunc = touchF_NULL; + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = -1; + + WP_Stick( self, trace, 1.0f ); +} + +//--------------------------------------------------------- +static void WP_DropDetPack( gentity_t *self, vec3_t start, vec3_t dir ) +//--------------------------------------------------------- +{ + // Chucking a new one + AngleVectors( self->client->ps.viewangles, forward, vright, up ); + CalcMuzzlePoint( self, forward, vright, up, muzzle, 0 ); + VectorNormalize( forward ); + VectorMA( muzzle, -4, forward, muzzle ); + + VectorCopy( muzzle, start ); + WP_TraceSetStart( self, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + gentity_t *missile = CreateMissile( start, forward, 300, 10000, self, qfalse ); + + missile->fxID = G_EffectIndex( "detpack/explosion" ); // if we set an explosion effect, explode death can use that instead + + missile->classname = "detpack"; + missile->s.weapon = WP_DET_PACK; + + missile->s.pos.trType = TR_GRAVITY; + + missile->s.eFlags |= EF_MISSILE_STICK; + missile->e_TouchFunc = touchF_charge_stick; + + missile->damage = FLECHETTE_MINE_DAMAGE; + missile->methodOfDeath = MOD_DETPACK; + + missile->splashDamage = FLECHETTE_MINE_SPLASH_DAMAGE; + missile->splashRadius = FLECHETTE_MINE_SPLASH_RADIUS; + missile->splashMethodOfDeath = MOD_DETPACK;// ?SPLASH; + + missile->clipmask = (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_SHOTCLIP);//MASK_SHOT; + + // we don't want it to ever bounce + missile->bounceCount = 0; + + //Hack! Need to track spawn times. + missile->useDebounceTime = level.time; + + missile->s.radius = 30; + VectorSet( missile->s.modelScale, 1.0f, 1.0f, 1.0f ); + gi.G2API_InitGhoul2Model( missile->ghoul2, weaponData[WP_DET_PACK].missileMdl, G_ModelIndex( weaponData[WP_DET_PACK].missileMdl )); + + AddSoundEvent( NULL, missile->currentOrigin, 128, AEL_MINOR, qtrue ); + AddSightEvent( NULL, missile->currentOrigin, 128, AEL_SUSPICIOUS, 10 ); +} + +//--------------------------------------------------------- +static void WP_FireDetPack( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + if ( !ent || !ent->client ) + { + return; + } + + if ( alt_fire ) + { + if ( ent->client->ps.eFlags & EF_PLANTED_CHARGE ) + { + gentity_t *found = NULL; + + // loop through all ents and blow the crap out of them! + while (( found = G_Find( found, FOFS( classname ), "detpack" )) != NULL ) + { + if ( found->activator == ent ) + { + VectorCopy( found->currentOrigin, found->s.origin ); + found->e_ThinkFunc = thinkF_WP_Explode; + found->nextthink = level.time + 100 + random() * 100; + G_Sound( found, G_SoundIndex( "sound/weapons/detpack/warning.wav" )); + + // would be nice if this actually worked? + AddSoundEvent( NULL, found->currentOrigin, found->splashRadius*2, AEL_DANGER, qfalse, qtrue );//FIXME: are we on ground or not? + AddSightEvent( NULL, found->currentOrigin, found->splashRadius*2, AEL_DISCOVERED, 100 ); + } + } + + ent->client->ps.eFlags &= ~EF_PLANTED_CHARGE; + } + } + else + { + //Can't have more than 5 active at once. + int count = 0; + gentity_t *found = NULL; + gentity_t *oldest = NULL; + while((found = G_Find( found, FOFS( classname ), "detpack" )) != NULL) { + count++; + if(!oldest || found->useDebounceTime < oldest->useDebounceTime) { + oldest = found; + } + } + + if(count == 5 && oldest) { + void ObjectDie(gentity_t*, gentity_t*, gentity_t*, int, int); + ObjectDie(oldest, NULL, NULL, 0, 0); + } + + WP_DropDetPack( ent, muzzle, forward ); + + ent->client->ps.eFlags |= EF_PLANTED_CHARGE; + } +} + + +//----------------------- +// Laser Trip Mine +//----------------------- + +#define PROXIMITY_STYLE 1 +#define TRIPWIRE_STYLE 2 + +//--------------------------------------------------------- +void touchLaserTrap( gentity_t *ent, gentity_t *other, trace_t *trace ) +//--------------------------------------------------------- +{ + ent->s.eType = ET_GENERAL; + + // a tripwire so add draw line flag + VectorCopy( trace->plane.normal, ent->movedir ); + + // make it shootable + VectorSet( ent->mins, -4, -4, -4 ); + VectorSet( ent->maxs, 4, 4, 4 ); + + ent->clipmask = MASK_SHOT; + ent->contents = CONTENTS_SHOTCLIP; + ent->takedamage = qtrue; + ent->health = 15; + + ent->e_DieFunc = dieF_WP_ExplosiveDie; + ent->e_TouchFunc = touchF_NULL; + + // so we can trip it too + ent->activator = ent->owner; + ent->owner = NULL; + + WP_Stick( ent, trace ); + + if ( ent->count == TRIPWIRE_STYLE ) + { + vec3_t mins = {-4,-4,-4}, maxs = {4,4,4};//FIXME: global these + trace_t tr; + VectorMA( ent->currentOrigin, 32, ent->movedir, ent->s.origin2 ); + gi.trace( &tr, ent->s.origin2, mins, maxs, ent->currentOrigin, ent->s.number, MASK_SHOT, G2_RETURNONHIT, 0 ); + VectorCopy( tr.endpos, ent->s.origin2 ); + + ent->e_ThinkFunc = thinkF_laserTrapThink; + } + else + { + ent->e_ThinkFunc = thinkF_WP_prox_mine_think; + } + + ent->nextthink = level.time + LT_ACTIVATION_DELAY; +} + +// copied from flechette prox above...which will not be used if this gets approved +//--------------------------------------------------------- +void WP_prox_mine_think( gentity_t *ent ) +//--------------------------------------------------------- +{ + int count; + qboolean blow = qfalse; + + // first time through? + if ( ent->count ) + { + // play activated warning + ent->count = 0; + ent->s.eFlags |= EF_PROX_TRIP; + G_Sound( ent, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" )); + } + + // if it isn't time to auto-explode, do a small proximity check + if ( ent->delay > level.time ) + { + count = G_RadiusList( ent->currentOrigin, PROX_MINE_RADIUS_CHECK, ent, qtrue, ent_list ); + + for ( int i = 0; i < count; i++ ) + { + if ( ent_list[i]->client && ent_list[i]->health > 0 && ent->activator && ent_list[i]->s.number != ent->activator->s.number ) + { + blow = qtrue; + break; + } + } + } + else + { + // well, we must die now + blow = qtrue; + } + + if ( blow ) + { +// G_Sound( ent, G_SoundIndex( "sound/weapons/flechette/warning.wav" )); + ent->e_ThinkFunc = thinkF_WP_Explode; + ent->nextthink = level.time + 200; + } + else + { + // we probably don't need to do this thinking logic very often...maybe this is fast enough? + ent->nextthink = level.time + 500; + } +} + +//--------------------------------------------------------- +void laserTrapThink( gentity_t *ent ) +//--------------------------------------------------------- +{ + gentity_t *traceEnt; + vec3_t end, mins = {-4,-4,-4}, maxs = {4,4,4}; + trace_t tr; + + // turn on the beam effect + if ( !(ent->s.eFlags & EF_FIRING )) + { + // arm me + G_Sound( ent, G_SoundIndex( "sound/weapons/laser_trap/warning.wav" )); + ent->s.loopSound = G_SoundIndex( "sound/weapons/laser_trap/hum_loop.wav" ); + ent->s.eFlags |= EF_FIRING; + } + + ent->e_ThinkFunc = thinkF_laserTrapThink; + ent->nextthink = level.time + FRAMETIME; + + // Find the main impact point + VectorMA( ent->s.pos.trBase, 2048, ent->movedir, end ); + gi.trace( &tr, ent->s.origin2, mins, maxs, end, ent->s.number, MASK_SHOT, G2_RETURNONHIT, 0 ); + + traceEnt = &g_entities[ tr.entityNum ]; + + // Adjust this so that the effect has a relatively fresh endpoint + VectorCopy( tr.endpos, ent->pos4 ); + + if ( traceEnt->client || tr.startsolid ) + { + // go boom + WP_Explode( ent ); + ent->s.eFlags &= ~EF_FIRING; // don't draw beam if we are dead + } + else + { + /* + // FIXME: they need to avoid the beam! + AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER ); + AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER, 50 ); + */ + } +} + +//--------------------------------------------------------- +void CreateLaserTrap( gentity_t *laserTrap, vec3_t start, gentity_t *owner ) +//--------------------------------------------------------- +{ + if ( !VALIDSTRING( laserTrap->classname ) || + !Q_stricmp(laserTrap->classname, "noclass")) + { + // since we may be coming from a map placed trip mine, we don't want to override that class name.... + // That would be bad because the player drop code tries to limit number of placed items...so it would have removed map placed ones as well. + laserTrap->classname = "tripmine"; + } + + laserTrap->splashDamage = LT_SPLASH_DAM; + laserTrap->splashRadius = LT_SPLASH_RAD; + laserTrap->damage = LT_DAMAGE; + laserTrap->methodOfDeath = MOD_LASERTRIP; + laserTrap->splashMethodOfDeath = MOD_LASERTRIP;//? SPLASH; + + laserTrap->s.eType = ET_MISSILE; + laserTrap->svFlags = SVF_USE_CURRENT_ORIGIN; + laserTrap->s.weapon = WP_TRIP_MINE; + + laserTrap->owner = owner; +// VectorSet( laserTrap->mins, -LT_SIZE, -LT_SIZE, -LT_SIZE ); +// VectorSet( laserTrap->maxs, LT_SIZE, LT_SIZE, LT_SIZE ); + laserTrap->clipmask = (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_SHOTCLIP);//MASK_SHOT; + + laserTrap->s.pos.trTime = level.time; // move a bit on the very first frame + VectorCopy( start, laserTrap->s.pos.trBase ); + VectorCopy( start, laserTrap->currentOrigin); + + VectorCopy( start, laserTrap->pos2 ); // ?? wtf ? + + laserTrap->fxID = G_EffectIndex( "tripMine/explosion" ); + + laserTrap->e_TouchFunc = touchF_touchLaserTrap; + + laserTrap->s.radius = 60; + VectorSet( laserTrap->s.modelScale, 1.0f, 1.0f, 1.0f ); + gi.G2API_InitGhoul2Model( laserTrap->ghoul2, weaponData[WP_TRIP_MINE].missileMdl, G_ModelIndex( weaponData[WP_TRIP_MINE].missileMdl )); +} + +//--------------------------------------------------------- +static void WP_RemoveOldTraps( gentity_t *ent ) +//--------------------------------------------------------- +{ + gentity_t *found = NULL; + int trapcount = 0, i; + int foundLaserTraps[MAX_GENTITIES] = {ENTITYNUM_NONE}; + int trapcount_org, lowestTimeStamp; + int removeMe; + + // see how many there are now + while (( found = G_Find( found, FOFS(classname), "tripmine" )) != NULL ) + { + if ( found->activator != ent ) // activator is really the owner? + { + continue; + } + foundLaserTraps[trapcount++] = found->s.number; + } + + // now remove first ones we find until there are only 9 left + found = NULL; + trapcount_org = trapcount; + lowestTimeStamp = level.time; + + while ( trapcount > 4 ) + { + removeMe = -1; + for ( i = 0; i < trapcount_org; i++ ) + { + if ( foundLaserTraps[i] == ENTITYNUM_NONE ) + { + continue; + } + found = &g_entities[foundLaserTraps[i]]; + if ( found->setTime < lowestTimeStamp ) + { + removeMe = i; + lowestTimeStamp = found->setTime; + } + } + if ( removeMe != -1 ) + { + //remove it... or blow it? + if ( &g_entities[foundLaserTraps[removeMe]] == NULL ) + { + break; + } + else + { + G_FreeEntity( &g_entities[foundLaserTraps[removeMe]] ); + } + foundLaserTraps[removeMe] = ENTITYNUM_NONE; + trapcount--; + } + else + { + break; + } + } +} + +//--------------------------------------------------------- +void WP_PlaceLaserTrap( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + vec3_t start; + gentity_t *laserTrap; + + // limit to 5 placed at any one time + WP_RemoveOldTraps( ent ); + + //FIXME: surface must be within 64 + laserTrap = G_Spawn(); + + if ( laserTrap ) + { + // now make the new one + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + CreateLaserTrap( laserTrap, start, ent ); + + // set player-created-specific fields + laserTrap->setTime = level.time;//remember when we placed it + + laserTrap->s.eFlags |= EF_MISSILE_STICK; + laserTrap->s.pos.trType = TR_GRAVITY; + VectorScale( forward, LT_VELOCITY, laserTrap->s.pos.trDelta ); + + if ( alt_fire ) + { + laserTrap->count = PROXIMITY_STYLE; + laserTrap->delay = level.time + 40000; // will auto-blow in 40 seconds. + laserTrap->methodOfDeath = MOD_LASERTRIP_ALT; + laserTrap->splashMethodOfDeath = MOD_LASERTRIP_ALT;//? SPLASH; + } + else + { + laserTrap->count = TRIPWIRE_STYLE; + } + } +} + + +//--------------------- +// Thermal Detonator +//--------------------- + +//--------------------------------------------------------- +void thermalDetonatorExplode( gentity_t *ent ) +//--------------------------------------------------------- +{ + if ( (ent->s.eFlags&EF_HELD_BY_SAND_CREATURE) ) + { + ent->takedamage = qfalse; // don't allow double deaths! + + G_Damage( ent->activator, ent, ent->owner, vec3_origin, ent->currentOrigin, TD_ALT_DAMAGE, 0, MOD_EXPLOSIVE ); + G_PlayEffect( "thermal/explosion", ent->currentOrigin ); + G_PlayEffect( "thermal/shockwave", ent->currentOrigin ); + + G_FreeEntity( ent ); + } + else if ( !ent->count ) + { + G_Sound( ent, G_SoundIndex( "sound/weapons/thermal/warning.wav" ) ); + ent->count = 1; + ent->nextthink = level.time + 800; + ent->svFlags |= SVF_BROADCAST;//so everyone hears/sees the explosion? + } + else + { + vec3_t pos; + + VectorSet( pos, ent->currentOrigin[0], ent->currentOrigin[1], ent->currentOrigin[2] + 8 ); + + ent->takedamage = qfalse; // don't allow double deaths! + + G_RadiusDamage( ent->currentOrigin, ent->owner, TD_SPLASH_DAM, TD_SPLASH_RAD, NULL, MOD_EXPLOSIVE_SPLASH ); + + G_PlayEffect( "thermal/explosion", ent->currentOrigin ); + G_PlayEffect( "thermal/shockwave", ent->currentOrigin ); + + G_FreeEntity( ent ); + } +} + +//------------------------------------------------------------------------------------------------------------- +void thermal_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc ) +//------------------------------------------------------------------------------------------------------------- +{ + thermalDetonatorExplode( self ); +} + +//--------------------------------------------------------- +qboolean WP_LobFire( gentity_t *self, vec3_t start, vec3_t target, vec3_t mins, vec3_t maxs, int clipmask, + vec3_t velocity, qboolean tracePath, int ignoreEntNum, int enemyNum, + float minSpeed = 0, float maxSpeed = 0, float idealSpeed = 0, qboolean mustHit = qfalse ) +//--------------------------------------------------------- +{ + float targetDist, shotSpeed, speedInc = 100, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed, + vec3_t targetDir, shotVel, failCase; + trace_t trace; + trajectory_t tr; + qboolean blocked; + int elapsedTime, skipNum, timeStep = 500, hitCount = 0, maxHits = 7; + vec3_t lastPos, testPos; + gentity_t *traceEnt; + + if ( !idealSpeed ) + { + idealSpeed = 300; + } + else if ( idealSpeed < speedInc ) + { + idealSpeed = speedInc; + } + shotSpeed = idealSpeed; + skipNum = (idealSpeed-speedInc)/speedInc; + if ( !minSpeed ) + { + minSpeed = 100; + } + if ( !maxSpeed ) + { + maxSpeed = 900; + } + while ( hitCount < maxHits ) + { + VectorSubtract( target, start, targetDir ); + targetDist = VectorNormalize( targetDir ); + + VectorScale( targetDir, shotSpeed, shotVel ); + travelTime = targetDist/shotSpeed; + shotVel[2] += travelTime * 0.5 * g_gravity->value; + + if ( !hitCount ) + {//save the first (ideal) one as the failCase (fallback value) + if ( !mustHit ) + {//default is fine as a return value + VectorCopy( shotVel, failCase ); + } + } + + if ( tracePath ) + {//do a rough trace of the path + blocked = qfalse; + + VectorCopy( start, tr.trBase ); + VectorCopy( shotVel, tr.trDelta ); + tr.trType = TR_GRAVITY; + tr.trTime = level.time; + travelTime *= 1000.0f; + VectorCopy( start, lastPos ); + + //This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down? + for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep ) + { + if ( (float)elapsedTime > travelTime ) + {//cap it + elapsedTime = floor( travelTime ); + } + EvaluateTrajectory( &tr, level.time + elapsedTime, testPos ); + gi.trace( &trace, lastPos, mins, maxs, testPos, ignoreEntNum, clipmask ); + + if ( trace.allsolid || trace.startsolid ) + { + blocked = qtrue; + break; + } + if ( trace.fraction < 1.0f ) + {//hit something + if ( trace.entityNum == enemyNum ) + {//hit the enemy, that's perfect! + break; + } + else if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, target ) < 4096 )//hit within 64 of desired location, should be okay + {//close enough! + break; + } + else + {//FIXME: maybe find the extents of this brush and go above or below it on next try somehow? + impactDist = DistanceSquared( trace.endpos, target ); + if ( impactDist < bestImpactDist ) + { + bestImpactDist = impactDist; + VectorCopy( shotVel, failCase ); + } + blocked = qtrue; + //see if we should store this as the failCase + if ( trace.entityNum < ENTITYNUM_WORLD ) + {//hit an ent + traceEnt = &g_entities[trace.entityNum]; + if ( traceEnt && traceEnt->takedamage && !OnSameTeam( self, traceEnt ) ) + {//hit something breakable, so that's okay + //we haven't found a clear shot yet so use this as the failcase + VectorCopy( shotVel, failCase ); + } + } + break; + } + } + if ( elapsedTime == floor( travelTime ) ) + {//reached end, all clear + break; + } + else + { + //all clear, try next slice + VectorCopy( testPos, lastPos ); + } + } + if ( blocked ) + {//hit something, adjust speed (which will change arc) + hitCount++; + shotSpeed = idealSpeed + ((hitCount-skipNum) * speedInc);//from min to max (skipping ideal) + if ( hitCount >= skipNum ) + {//skip ideal since that was the first value we tested + shotSpeed += speedInc; + } + } + else + {//made it! + break; + } + } + else + {//no need to check the path, go with first calc + break; + } + } + + if ( hitCount >= maxHits ) + {//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?) + VectorCopy( failCase, velocity ); + return qfalse; + } + VectorCopy( shotVel, velocity ); + return qtrue; +} + +//--------------------------------------------------------- +void WP_ThermalThink( gentity_t *ent ) +//--------------------------------------------------------- +{ + int count; + qboolean blow = qfalse; + + // Thermal detonators for the player do occasional radius checks and blow up if there are entities in the blast radius + // This is done so that the main fire is actually useful as an attack. We explode anyway after delay expires. + + if ( (ent->s.eFlags&EF_HELD_BY_SAND_CREATURE) ) + {//blow once creature is underground (done with anim) + //FIXME: chance of being spit out? Especially if lots of delay left... + ent->e_TouchFunc = NULL;//don't impact on anything + if ( !ent->activator + || !ent->activator->client + || !ent->activator->client->ps.legsAnimTimer ) + {//either something happened to the sand creature or it's done with it's attack anim + //blow! + ent->e_ThinkFunc = thinkF_thermalDetonatorExplode; + ent->nextthink = level.time + Q_irand( 50, 2000 ); + } + else + {//keep checking + ent->nextthink = level.time + TD_THINK_TIME; + } + return; + } + else if ( ent->delay > level.time ) + { + // Finally, we force it to bounce at least once before doing the special checks, otherwise it's just too easy for the player? + if ( ent->has_bounced ) + { + count = G_RadiusList( ent->currentOrigin, TD_TEST_RAD, ent, qtrue, ent_list ); + + for ( int i = 0; i < count; i++ ) + { + if ( ent_list[i]->s.number == 0 ) + { + // avoid deliberately blowing up next to the player, no matter how close any enemy is.. + // ...if the delay time expires though, there is no saving the player...muwhaaa haa ha + blow = qfalse; + break; + } + else if ( ent_list[i]->client + && ent_list[i]->client->NPC_class != CLASS_SAND_CREATURE//ignore sand creatures + && ent_list[i]->health > 0 ) + { + //FIXME! sometimes the ent_list order changes, so we should make sure that the player isn't anywhere in this list + blow = qtrue; + } + } + } + } + else + { + // our death time has arrived, even if nothing is near us + blow = qtrue; + } + + if ( blow ) + { + ent->e_ThinkFunc = thinkF_thermalDetonatorExplode; + ent->nextthink = level.time + 50; + } + else + { + // we probably don't need to do this thinking logic very often...maybe this is fast enough? + ent->nextthink = level.time + TD_THINK_TIME; + } +} + +//--------------------------------------------------------- +gentity_t *WP_FireThermalDetonator( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + gentity_t *bolt; + vec3_t dir, start; + float damageScale = 1.0f; + + VectorCopy( forward, dir ); + VectorCopy( muzzle, start ); + + bolt = G_Spawn(); + + bolt->classname = "thermal_detonator"; + + if ( ent->s.number != 0 ) + { + // If not the player, cut the damage a bit so we don't get pounded on so much + damageScale = TD_NPC_DAMAGE_CUT; + } + + if ( !alt_fire && ent->s.number == 0 ) + { + // Main fires for the players do a little bit of extra thinking + bolt->e_ThinkFunc = thinkF_WP_ThermalThink; + bolt->nextthink = level.time + TD_THINK_TIME; + bolt->delay = level.time + TD_TIME; // How long 'til she blows + } + else + { + bolt->e_ThinkFunc = thinkF_thermalDetonatorExplode; + bolt->nextthink = level.time + TD_TIME; // How long 'til she blows + } + + bolt->mass = 10; + + // How 'bout we give this thing a size... + VectorSet( bolt->mins, -4.0f, -4.0f, -4.0f ); + VectorSet( bolt->maxs, 4.0f, 4.0f, 4.0f ); + bolt->clipmask = MASK_SHOT; + bolt->clipmask &= ~CONTENTS_CORPSE; + bolt->contents = CONTENTS_SHOTCLIP; + bolt->takedamage = qtrue; + bolt->health = 15; + bolt->e_DieFunc = dieF_thermal_die; + + WP_TraceSetStart( ent, start, bolt->mins, bolt->maxs );//make sure our start point isn't on the other side of a wall + + float chargeAmount = 1.0f; // default of full charge + + if ( ent->client ) + { + chargeAmount = level.time - ent->client->ps.weaponChargeTime; + } + + // get charge amount + chargeAmount = chargeAmount / (float)TD_VELOCITY; + + if ( chargeAmount > 1.0f ) + { + chargeAmount = 1.0f; + } + else if ( chargeAmount < TD_MIN_CHARGE ) + { + chargeAmount = TD_MIN_CHARGE; + } + + float thrownSpeed = TD_VELOCITY; + const qboolean thisIsAShooter = !Q_stricmp( "misc_weapon_shooter", ent->classname); + + if (thisIsAShooter) + { + if (ent->delay != 0) + { + thrownSpeed = ent->delay; + } + } + + // normal ones bounce, alt ones explode on impact + bolt->s.pos.trType = TR_GRAVITY; + bolt->owner = ent; + VectorScale( dir, thrownSpeed * chargeAmount, bolt->s.pos.trDelta ); + + if ( ent->health > 0 ) + { + bolt->s.pos.trDelta[2] += 120; + + if ( (ent->NPC || (ent->s.number && thisIsAShooter) ) + && ent->enemy ) + {//NPC or misc_weapon_shooter + //FIXME: we're assuming he's actually facing this direction... + vec3_t target; + + VectorCopy( ent->enemy->currentOrigin, target ); + if ( target[2] <= start[2] ) + { + vec3_t vec; + VectorSubtract( target, start, vec ); + VectorNormalize( vec ); + VectorMA( target, Q_flrand( 0, -32 ), vec, target );//throw a little short + } + + target[0] += Q_flrand( -5, 5 )+(crandom()*(6-ent->NPC->currentAim)*2); + target[1] += Q_flrand( -5, 5 )+(crandom()*(6-ent->NPC->currentAim)*2); + target[2] += Q_flrand( -5, 5 )+(crandom()*(6-ent->NPC->currentAim)*2); + + WP_LobFire( ent, start, target, bolt->mins, bolt->maxs, bolt->clipmask, bolt->s.pos.trDelta, qtrue, ent->s.number, ent->enemy->s.number ); + } + else if ( thisIsAShooter && ent->target && !VectorCompare( ent->pos1, vec3_origin ) ) + {//misc_weapon_shooter firing at a position + WP_LobFire( ent, start, ent->pos1, bolt->mins, bolt->maxs, bolt->clipmask, bolt->s.pos.trDelta, qtrue, ent->s.number, ent->enemy->s.number ); + } + } + + if ( alt_fire ) + { + bolt->alt_fire = qtrue; + } + else + { + bolt->s.eFlags |= EF_BOUNCE_HALF; + } + + bolt->s.loopSound = G_SoundIndex( "sound/weapons/thermal/thermloop.wav" ); + + bolt->damage = TD_DAMAGE * damageScale; + bolt->dflags = 0; + bolt->splashDamage = TD_SPLASH_DAM * damageScale; + bolt->splashRadius = TD_SPLASH_RAD; + + bolt->s.eType = ET_MISSILE; + bolt->svFlags = SVF_USE_CURRENT_ORIGIN; + bolt->s.weapon = WP_THERMAL; + + if ( alt_fire ) + { + bolt->methodOfDeath = MOD_THERMAL_ALT; + bolt->splashMethodOfDeath = MOD_THERMAL_ALT;//? SPLASH; + } + else + { + bolt->methodOfDeath = MOD_THERMAL; + bolt->splashMethodOfDeath = MOD_THERMAL;//? SPLASH; + } + + bolt->s.pos.trTime = level.time; // move a bit on the very first frame + VectorCopy( start, bolt->s.pos.trBase ); + + SnapVector( bolt->s.pos.trDelta ); // save net bandwidth + VectorCopy (start, bolt->currentOrigin); + + VectorCopy( start, bolt->pos2 ); + + return bolt; +} + +//--------------------------------------------------------- +gentity_t *WP_DropThermal( gentity_t *ent ) +//--------------------------------------------------------- +{ + AngleVectors( ent->client->ps.viewangles, forward, vright, up ); + CalcEntitySpot( ent, SPOT_WEAPON, muzzle ); + return (WP_FireThermalDetonator( ent, qfalse )); +} + + +// Bot Laser +//--------------------------------------------------------- +void WP_BotLaser( gentity_t *ent ) +//--------------------------------------------------------- +{ + gentity_t *missile = CreateMissile( muzzle, forward, BRYAR_PISTOL_VEL, 10000, ent ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->damage = BRYAR_PISTOL_DAMAGE; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; +} + + +// Emplaced Gun +//--------------------------------------------------------- +void WP_EmplacedFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + float damage = EMPLACED_DAMAGE * ( ent->NPC ? 0.1f : 1.0f ); + float vel = EMPLACED_VEL * ( ent->NPC ? 0.4f : 1.0f ); + + WP_MissileTargetHint(ent, muzzle, forward); + + gentity_t *missile = CreateMissile( muzzle, forward, vel, 10000, ent ); + + missile->classname = "emplaced_proj"; + missile->s.weapon = WP_EMPLACED_GUN; + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK | DAMAGE_HEAVY_WEAP_CLASS; + missile->methodOfDeath = MOD_EMPLACED; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + // do some weird switchery on who the real owner is, we do this so the projectiles don't hit the gun object + if ( ent && ent->client && !(ent->client->ps.eFlags&EF_LOCKED_TO_WEAPON) ) + { + missile->owner = ent; + } + else + { + missile->owner = ent->owner; + } + + if ( missile->owner->e_UseFunc == useF_eweb_use ) + { + missile->alt_fire = qtrue; + } + + VectorSet( missile->maxs, EMPLACED_SIZE, EMPLACED_SIZE, EMPLACED_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + + // alternate muzzles + ent->fxID = !ent->fxID; +} + +// ATST Main +//--------------------------------------------------------- +void WP_ATSTMainFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + float vel = ATST_MAIN_VEL; + +// if ( ent->client && (ent->client->ps.eFlags & EF_IN_ATST )) +// { +// vel = 4500.0f; +// } + + if ( !ent->s.number ) + { + // player shoots faster + vel *= 1.6f; + } + + WP_MissileTargetHint(ent, muzzle, forward); + + gentity_t *missile = CreateMissile( muzzle, forward, vel, 10000, ent ); + + missile->classname = "atst_main_proj"; + missile->s.weapon = WP_ATST_MAIN; + + missile->damage = ATST_MAIN_DAMAGE; + missile->dflags = DAMAGE_DEATH_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + missile->owner = ent; + + VectorSet( missile->maxs, ATST_MAIN_SIZE, ATST_MAIN_SIZE, ATST_MAIN_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + +} + +// ATST Alt Side +//--------------------------------------------------------- +void WP_ATSTSideAltFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + int damage = ATST_SIDE_ALT_DAMAGE; + float vel = ATST_SIDE_ALT_NPC_VELOCITY; + + if ( ent->client && (ent->client->ps.eFlags & EF_IN_ATST )) + { + vel = ATST_SIDE_ALT_VELOCITY; + } + + gentity_t *missile = CreateMissile( muzzle, forward, vel, 10000, ent, qtrue ); + + missile->classname = "atst_rocket"; + missile->s.weapon = WP_ATST_SIDE; + + missile->mass = 10; + + // Do the damages + if ( ent->s.number != 0 ) + { + if ( g_spskill->integer == 0 ) + { + damage = ATST_SIDE_ROCKET_NPC_DAMAGE_EASY; + } + else if ( g_spskill->integer == 1 ) + { + damage = ATST_SIDE_ROCKET_NPC_DAMAGE_NORMAL; + } + else + { + damage = ATST_SIDE_ROCKET_NPC_DAMAGE_HARD; + } + } + + VectorCopy( forward, missile->movedir ); + + // Make it easier to hit things + VectorSet( missile->maxs, ATST_SIDE_ALT_ROCKET_SIZE, ATST_SIDE_ALT_ROCKET_SIZE, ATST_SIDE_ALT_ROCKET_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK | DAMAGE_HEAVY_WEAP_CLASS; + missile->methodOfDeath = MOD_EXPLOSIVE; + missile->splashMethodOfDeath = MOD_EXPLOSIVE_SPLASH; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + // Scale damage down a bit if it is coming from an NPC + missile->splashDamage = ATST_SIDE_ALT_SPLASH_DAMAGE * ( ent->s.number == 0 ? 1.0f : ATST_SIDE_ALT_ROCKET_SPLASH_SCALE ); + missile->splashRadius = ATST_SIDE_ALT_SPLASH_RADIUS; + + // we don't want it to ever bounce + missile->bounceCount = 0; +} + +// ATST Side +//--------------------------------------------------------- +void WP_ATSTSideFire( gentity_t *ent ) +//--------------------------------------------------------- +{ + int damage = ATST_SIDE_MAIN_DAMAGE; + + gentity_t *missile = CreateMissile( muzzle, forward, ATST_SIDE_MAIN_VELOCITY, 10000, ent, qfalse ); + + missile->classname = "atst_side_proj"; + missile->s.weapon = WP_ATST_SIDE; + + // Do the damages + if ( ent->s.number != 0 ) + { + if ( g_spskill->integer == 0 ) + { + damage = ATST_SIDE_MAIN_NPC_DAMAGE_EASY; + } + else if ( g_spskill->integer == 1 ) + { + damage = ATST_SIDE_MAIN_NPC_DAMAGE_NORMAL; + } + else + { + damage = ATST_SIDE_MAIN_NPC_DAMAGE_HARD; + } + } + + VectorSet( missile->maxs, ATST_SIDE_MAIN_SIZE, ATST_SIDE_MAIN_SIZE, ATST_SIDE_MAIN_SIZE ); + VectorScale( missile->maxs, -1, missile->mins ); + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK|DAMAGE_HEAVY_WEAP_CLASS; + missile->methodOfDeath = MOD_ENERGY; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + missile->splashDamage = ATST_SIDE_MAIN_SPLASH_DAMAGE * ( ent->s.number == 0 ? 1.0f : 0.6f ); + missile->splashRadius = ATST_SIDE_MAIN_SPLASH_RADIUS; + + // we don't want it to bounce + missile->bounceCount = 0; +} + +//--------------------------------------------------------- +void WP_FireStunBaton( gentity_t *ent, qboolean alt_fire ) +{ + gentity_t *tr_ent; + trace_t tr; + vec3_t mins, maxs, end, start; + + G_Sound( ent, G_SoundIndex( "sound/weapons/baton/fire" )); +#ifdef _IMMERSION + G_Force( ent, G_ForceIndex( "fffx/weapons/baton/fire", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin ); + + VectorMA( start, STUN_BATON_RANGE, forward, end ); + + VectorSet( maxs, 5, 5, 5 ); + VectorScale( maxs, -1, mins ); + + gi.trace ( &tr, start, mins, maxs, end, ent->s.number, CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_SHOTCLIP ); + + if ( tr.entityNum >= ENTITYNUM_WORLD || tr.entityNum < 0 ) + { + return; + } + + tr_ent = &g_entities[tr.entityNum]; + + if ( tr_ent && tr_ent->takedamage && tr_ent->client ) + { + G_PlayEffect( "stunBaton/flesh_impact", tr.endpos, tr.plane.normal ); + + // TEMP! +// G_Sound( tr_ent, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) ); + tr_ent->client->ps.powerups[PW_SHOCKED] = level.time + 1500; + + G_Damage( tr_ent, ent, ent, forward, tr.endpos, STUN_BATON_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + else if ( tr_ent->svFlags & SVF_GLASS_BRUSH || ( tr_ent->svFlags & SVF_BBRUSH && tr_ent->material == 12 )) // material grate...we are breaking a grate! + { + G_Damage( tr_ent, ent, ent, forward, tr.endpos, 999, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); // smash that puppy + } +} + +void WP_Melee( gentity_t *ent ) +//--------------------------------------------------------- +{ + gentity_t *tr_ent; + trace_t tr; + vec3_t mins, maxs, end; + int damage = ent->s.number ? (g_spskill->integer*2)+1 : 3; + float range = ent->s.number ? 64 : 32; + + VectorMA( muzzle, range, forward, end ); + + VectorSet( maxs, 6, 6, 6 ); + VectorScale( maxs, -1, mins ); + + gi.trace ( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + + if ( tr.entityNum >= ENTITYNUM_WORLD ) + { + return; + } + + tr_ent = &g_entities[tr.entityNum]; + + if ( ent->client && !PM_DroidMelee( ent->client->NPC_class ) ) + { + if ( ent->s.number || ent->alt_fire ) + { + damage *= Q_irand( 2, 3 ); + } + else + { + damage *= Q_irand( 1, 2 ); + } + } + + if ( tr_ent && tr_ent->takedamage ) + { + int dflags = DAMAGE_NO_KNOCKBACK; + G_PlayEffect( G_EffectIndex( "melee/punch_impact" ), tr.endpos, forward ); + //G_Sound( tr_ent, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) ); + if ( ent->NPC && (ent->NPC->aiFlags&NPCAI_HEAVY_MELEE) ) + { //4x damage for heavy melee class + damage *= 4; + dflags &= ~DAMAGE_NO_KNOCKBACK; + dflags |= DAMAGE_DISMEMBER; + } + + G_Damage( tr_ent, ent, ent, forward, tr.endpos, damage, dflags, MOD_MELEE ); + } +} + +//--------------------------------------------------------- +static void WP_FireTuskenRifle( gentity_t *ent ) +//--------------------------------------------------------- +{ + vec3_t start; + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + if ( !(ent->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 ) + {//force sight 2+ gives perfect aim + //FIXME: maybe force sight level 3 autoaims some? + if ( ent->NPC && ent->NPC->currentAim < 5 ) + { + vec3_t angs; + + vectoangles( forward, angs ); + + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + {//*sigh*, hack to make impworkers less accurate without affecteing imperial officer accuracy + angs[PITCH] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + angs[YAW] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + } + else + { + angs[PITCH] += ( crandom() * ((5-ent->NPC->currentAim)*0.25f) ); + angs[YAW] += ( crandom() * ((5-ent->NPC->currentAim)*0.25f) ); + } + + AngleVectors( angs, forward, NULL, NULL ); + } + } + + WP_MissileTargetHint(ent, start, forward); + + gentity_t *missile = CreateMissile( start, forward, TUSKEN_RIFLE_VEL, 10000, ent, qfalse ); + + missile->classname = "trifle_proj"; + missile->s.weapon = WP_TUSKEN_RIFLE; + + if ( ent->s.number < MAX_CLIENTS || g_spskill->integer >= 2 ) + { + missile->damage = TUSKEN_RIFLE_DAMAGE_HARD; + } + else if ( g_spskill->integer > 0 ) + { + missile->damage = TUSKEN_RIFLE_DAMAGE_MEDIUM; + } + else + { + missile->damage = TUSKEN_RIFLE_DAMAGE_EASY; + } + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + + missile->methodOfDeath = MOD_BRYAR;//??? + + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + // we don't want it to bounce forever + missile->bounceCount = 8; +} + +//--------------------------------------------------------- +static void WP_FireNoghriStick( gentity_t *ent ) +//--------------------------------------------------------- +{ + vec3_t dir, angs; + + vectoangles( forward, angs ); + + if ( !(ent->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2 ) + {//force sight 2+ gives perfect aim + //FIXME: maybe force sight level 3 autoaims some? + // add some slop to the main-fire direction + angs[PITCH] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + angs[YAW] += ( crandom() * (BLASTER_NPC_SPREAD+(6-ent->NPC->currentAim)*0.25f));//was 0.5f + } + + AngleVectors( angs, dir, NULL, NULL ); + + // FIXME: if temp_org does not have clear trace to inside the bbox, don't shoot! + int velocity = 1200; + + WP_TraceSetStart( ent, muzzle, vec3_origin, vec3_origin );//make sure our start point isn't on the other side of a wall + + WP_MissileTargetHint(ent, muzzle, dir); + + gentity_t *missile = CreateMissile( muzzle, dir, velocity, 10000, ent, qfalse ); + + missile->classname = "noghri_proj"; + missile->s.weapon = WP_NOGHRI_STICK; + + // Do the damages + if ( ent->s.number != 0 ) + { + if ( g_spskill->integer == 0 ) + { + missile->damage = 1; + } + else if ( g_spskill->integer == 1 ) + { + missile->damage = 5; + } + else + { + missile->damage = 10; + } + } + +// if ( ent->client ) +// { +// if ( ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > 0 && ent->client->ps.powerups[PW_WEAPON_OVERCHARGE] > cg.time ) +// { +// // in overcharge mode, so doing double damage +// missile->flags |= FL_OVERCHARGED; +// damage *= 2; +// } +// } + + missile->dflags = DAMAGE_NO_KNOCKBACK; + missile->methodOfDeath = MOD_BLASTER; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + missile->splashDamage = 0; + missile->splashRadius = 100; + missile->splashMethodOfDeath = MOD_GAS; + + //Hmm: maybe spew gas on impact? +} + +//--------------------------------------------------------- +void AddLeanOfs(const gentity_t *const ent, vec3_t point) +//--------------------------------------------------------- +{ + if(ent->client) + { + if(ent->client->ps.leanofs) + { + vec3_t right; + //add leaning offset + AngleVectors(ent->client->ps.viewangles, NULL, right, NULL); + VectorMA(point, (float)ent->client->ps.leanofs, right, point); + } + } +} + +//--------------------------------------------------------- +void SubtractLeanOfs(const gentity_t *const ent, vec3_t point) +//--------------------------------------------------------- +{ + if(ent->client) + { + if(ent->client->ps.leanofs) + { + vec3_t right; + //add leaning offset + AngleVectors( ent->client->ps.viewangles, NULL, right, NULL ); + VectorMA( point, ent->client->ps.leanofs*-1, right, point ); + } + } +} + +//--------------------------------------------------------- +void ViewHeightFix(const gentity_t *const ent) +//--------------------------------------------------------- +{ + //FIXME: this is hacky and doesn't need to be here. Was only put here to make up + //for the times a crouch anim would be used but not actually crouching. + //When we start calcing eyepos (SPOT_HEAD) from the tag_eyes, we won't need + //this (or viewheight at all?) + if ( !ent ) + return; + + if ( !ent->client || !ent->NPC ) + return; + + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) + return;//dead + + if ( ent->client->ps.legsAnim == BOTH_CROUCH1IDLE || ent->client->ps.legsAnim == BOTH_CROUCH1 || ent->client->ps.legsAnim == BOTH_CROUCH1WALK ) + { + if ( ent->client->ps.viewheight!=ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET ) + ent->client->ps.viewheight = ent->client->crouchheight + STANDARD_VIEWHEIGHT_OFFSET; + } + else + { + if ( ent->client->ps.viewheight!=ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET ) + ent->client->ps.viewheight = ent->client->standheight + STANDARD_VIEWHEIGHT_OFFSET; + } +} + +qboolean W_AccuracyLoggableWeapon( int weapon, qboolean alt_fire, int mod ) +{ + if ( mod != MOD_UNKNOWN ) + { + switch( mod ) + { + //standard weapons + case MOD_BRYAR: + case MOD_BRYAR_ALT: + case MOD_BLASTER: + case MOD_BLASTER_ALT: + case MOD_DISRUPTOR: + case MOD_SNIPER: + case MOD_BOWCASTER: + case MOD_BOWCASTER_ALT: + case MOD_ROCKET: + case MOD_ROCKET_ALT: + case MOD_CONC: + case MOD_CONC_ALT: + return qtrue; + break; + //non-alt standard + case MOD_REPEATER: + case MOD_DEMP2: + case MOD_FLECHETTE: + return qtrue; + break; + //emplaced gun + case MOD_EMPLACED: + return qtrue; + break; + //atst + case MOD_ENERGY: + case MOD_EXPLOSIVE: + if ( weapon == WP_ATST_MAIN || weapon == WP_ATST_SIDE ) + { + return qtrue; + } + break; + } + } + else if ( weapon != WP_NONE ) + { + switch( weapon ) + { + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: + case WP_BLASTER: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_ROCKET_LAUNCHER: + case WP_CONCUSSION: + return qtrue; + break; + //non-alt standard + case WP_REPEATER: + case WP_DEMP2: + case WP_FLECHETTE: + if ( !alt_fire ) + { + return qtrue; + } + break; + //emplaced gun + case WP_EMPLACED_GUN: + return qtrue; + break; + //atst + case WP_ATST_MAIN: + case WP_ATST_SIDE: + return qtrue; + break; + } + } + return qfalse; +} + +/* +=============== +LogAccuracyHit +=============== +*/ +qboolean LogAccuracyHit( gentity_t *target, gentity_t *attacker ) { + if( !target->takedamage ) { + return qfalse; + } + + if ( target == attacker ) { + return qfalse; + } + + if( !target->client ) { + return qfalse; + } + + if( !attacker->client ) { + return qfalse; + } + + if( target->client->ps.stats[STAT_HEALTH] <= 0 ) { + return qfalse; + } + + if ( OnSameTeam( target, attacker ) ) { + return qfalse; + } + + return qtrue; +} + +//--------------------------------------------------------- +void CalcMuzzlePoint( gentity_t *const ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint, float lead_in ) +//--------------------------------------------------------- +{ + vec3_t org; + mdxaBone_t boltMatrix; + + if( !lead_in ) //&& ent->s.number != 0 + {//Not players or melee + if( ent->client ) + { + if ( ent->client->renderInfo.mPCalcTime >= level.time - FRAMETIME*2 ) + {//Our muzz point was calced no more than 2 frames ago + VectorCopy(ent->client->renderInfo.muzzlePoint, muzzlePoint); + return; + } + } + } + + VectorCopy( ent->currentOrigin, muzzlePoint ); + + switch( ent->s.weapon ) + { + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: + ViewHeightFix(ent); + muzzlePoint[2] += ent->client->ps.viewheight;//By eyes + muzzlePoint[2] -= 16; + VectorMA( muzzlePoint, 28, forward, muzzlePoint ); + VectorMA( muzzlePoint, 6, vright, muzzlePoint ); + break; + + case WP_ROCKET_LAUNCHER: + case WP_CONCUSSION: + case WP_THERMAL: + ViewHeightFix(ent); + muzzlePoint[2] += ent->client->ps.viewheight;//By eyes + muzzlePoint[2] -= 2; + break; + + case WP_BLASTER: + ViewHeightFix(ent); + muzzlePoint[2] += ent->client->ps.viewheight;//By eyes + muzzlePoint[2] -= 1; + if ( ent->s.number == 0 ) + VectorMA( muzzlePoint, 12, forward, muzzlePoint ); // player, don't set this any lower otherwise the projectile will impact immediately when your back is to a wall + else + VectorMA( muzzlePoint, 2, forward, muzzlePoint ); // NPC, don't set too far forward otherwise the projectile can go through doors + + VectorMA( muzzlePoint, 1, vright, muzzlePoint ); + break; + + case WP_SABER: + if(ent->NPC!=NULL && + (ent->client->ps.torsoAnim == TORSO_WEAPONREADY2 || + ent->client->ps.torsoAnim == BOTH_ATTACK2))//Sniper pose + { + ViewHeightFix(ent); + muzzle[2] += ent->client->ps.viewheight;//By eyes + } + else + { + muzzlePoint[2] += 16; + } + VectorMA( muzzlePoint, 8, forward, muzzlePoint ); + VectorMA( muzzlePoint, 16, vright, muzzlePoint ); + break; + + case WP_BOT_LASER: + muzzlePoint[2] -= 16; // + break; + case WP_ATST_MAIN: + + if (ent->count > 0) + { + ent->count = 0; + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, + ent->handLBolt, + &boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time), + NULL, ent->s.modelScale ); + } + else + { + ent->count = 1; + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, + ent->handRBolt, + &boltMatrix, ent->s.angles, ent->s.origin, (cg.time?cg.time:level.time), + NULL, ent->s.modelScale ); + } + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + + VectorCopy(org,muzzlePoint); + + break; + } + + AddLeanOfs(ent, muzzlePoint); +} + +// Muzzle point table... +vec3_t WP_MuzzlePoint[WP_NUM_WEAPONS] = +{// Fwd, right, up. + {0, 0, 0 }, // WP_NONE, + {8 , 16, 0 }, // WP_SABER, + {12, 6, -6 }, // WP_BLASTER_PISTOL, + {12, 6, -6 }, // WP_BLASTER, + {12, 6, -6 }, // WP_DISRUPTOR, + {12, 2, -6 }, // WP_BOWCASTER, + {12, 4.5, -6 }, // WP_REPEATER, + {12, 6, -6 }, // WP_DEMP2, + {12, 6, -6 }, // WP_FLECHETTE, + {12, 8, -4 }, // WP_ROCKET_LAUNCHER, + {12, 0, -4 }, // WP_THERMAL, + {12, 0, -10 }, // WP_TRIP_MINE, + {12, 0, -4 }, // WP_DET_PACK, + {12, 8, -4 }, // WP_CONCUSSION, + {0 , 8, 0 }, // WP_MELEE, + {0, 0, 0 }, // WP_ATST_MAIN, + {0, 0, 0 }, // WP_ATST_SIDE, + {0 , 8, 0 }, // WP_STUN_BATON, + {12, 6, -6 }, // WP_BRYAR_PISTOL, +}; + +void WP_RocketLock( gentity_t *ent, float lockDist ) +{ + // Not really a charge weapon, but we still want to delay fire until the button comes up so that we can + // implement our alt-fire locking stuff + vec3_t ang; + trace_t tr; + + vec3_t muzzleOffPoint, muzzlePoint, forward, right, up; + + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + + AngleVectors(ent->client->ps.viewangles, ang, NULL, NULL); + + VectorCopy( ent->client->ps.origin, muzzlePoint ); + VectorCopy(WP_MuzzlePoint[WP_ROCKET_LAUNCHER], muzzleOffPoint); + + VectorMA(muzzlePoint, muzzleOffPoint[0], forward, muzzlePoint); + VectorMA(muzzlePoint, muzzleOffPoint[1], right, muzzlePoint); + muzzlePoint[2] += ent->client->ps.viewheight + muzzleOffPoint[2]; + + ang[0] = muzzlePoint[0] + ang[0]*lockDist; + ang[1] = muzzlePoint[1] + ang[1]*lockDist; + ang[2] = muzzlePoint[2] + ang[2]*lockDist; + + gi.trace(&tr, muzzlePoint, NULL, NULL, ang, ent->client->ps.clientNum, MASK_PLAYERSOLID); + + if (tr.fraction != 1 && tr.entityNum < ENTITYNUM_NONE && tr.entityNum != ent->client->ps.clientNum) + { + gentity_t *bgEnt = &g_entities[tr.entityNum]; + if ( bgEnt && (bgEnt->s.powerups&PW_CLOAKED) ) + { + ent->client->rocketLockIndex = ENTITYNUM_NONE; + ent->client->rocketLockTime = 0; + } + else if (bgEnt && bgEnt->s.eType == ET_PLAYER ) + { + if (ent->client->rocketLockIndex == ENTITYNUM_NONE) + { + ent->client->rocketLockIndex = tr.entityNum; + ent->client->rocketLockTime = level.time; + } + else if (ent->client->rocketLockIndex != tr.entityNum && ent->client->rocketTargetTime < level.time) + { + ent->client->rocketLockIndex = tr.entityNum; + ent->client->rocketLockTime = level.time; + } + else if (ent->client->rocketLockIndex == tr.entityNum) + { + if (ent->client->rocketLockTime == -1) + { + ent->client->rocketLockTime = ent->client->rocketLastValidTime; + } + } + + if (ent->client->rocketLockIndex == tr.entityNum) + { + ent->client->rocketTargetTime = level.time + 500; + } + } + } + else if (ent->client->rocketTargetTime < level.time) + { + ent->client->rocketLockIndex = ENTITYNUM_NONE; + ent->client->rocketLockTime = 0; + } + else + { + if (ent->client->rocketLockTime != -1) + { + ent->client->rocketLastValidTime = ent->client->rocketLockTime; + } + ent->client->rocketLockTime = -1; + } +} + +#define VEH_HOMING_MISSILE_THINK_TIME 100 +void WP_FireVehicleWeapon( gentity_t *ent, vec3_t start, vec3_t dir, vehWeaponInfo_t *vehWeapon ) +{ + if ( !vehWeapon ) + {//invalid vehicle weapon + return; + } + else if ( vehWeapon->bIsProjectile ) + {//projectile entity + gentity_t *missile; + vec3_t mins, maxs; + + VectorSet( maxs, vehWeapon->fWidth/2.0f,vehWeapon->fWidth/2.0f,vehWeapon->fHeight/2.0f ); + VectorScale( maxs, -1, mins ); + + //make sure our start point isn't on the other side of a wall + WP_TraceSetStart( ent, start, mins, maxs ); + + //QUERY: alt_fire true or not? Does it matter? + missile = CreateMissile( start, dir, vehWeapon->fSpeed, 10000, ent, qfalse ); + if ( vehWeapon->bHasGravity ) + {//TESTME: is this all we need to do? + missile->s.pos.trType = TR_GRAVITY; + } + + missile->classname = "vehicle_proj"; + + missile->damage = vehWeapon->iDamage; + missile->splashDamage = vehWeapon->iSplashDamage; + missile->splashRadius = vehWeapon->fSplashRadius; + + // HUGE HORRIBLE HACK + if (ent->owner && ent->owner->s.number==0) + { + missile->damage *= 20.0f; + missile->splashDamage *= 20.0f; + missile->splashRadius *= 20.0f; + } + + //FIXME: externalize some of these properties? + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->clipmask = MASK_SHOT; + //Maybe by checking flags...? + if ( vehWeapon->bSaberBlockable ) + { + missile->clipmask |= CONTENTS_LIGHTSABER; + } + /* + if ( (vehWeapon->iFlags&VWF_KNOCKBACK) ) + { + missile->dflags &= ~DAMAGE_DEATH_KNOCKBACK; + } + if ( (vehWeapon->iFlags&VWF_DISTORTION_TRAIL) ) + { + missile->s.eFlags |= EF_DISTORTION_TRAIL; + } + if ( (vehWeapon->iFlags&VWF_RADAR) ) + { + missile->s.eFlags |= EF_RADAROBJECT; + } + */ + missile->s.weapon = WP_BLASTER;//does this really matter? + + // Make it easier to hit things + VectorCopy( mins, missile->mins ); + VectorCopy( maxs, missile->maxs ); + //some slightly different stuff for things with bboxes + if ( vehWeapon->fWidth || vehWeapon->fHeight ) + {//we assume it's a rocket-like thing + missile->methodOfDeath = MOD_ROCKET; + missile->splashMethodOfDeath = MOD_ROCKET;// ?SPLASH; + + // we don't want it to ever bounce + missile->bounceCount = 0; + + missile->mass = 10; + } + else + {//a blaster-laser-like thing + missile->s.weapon = WP_BLASTER;//does this really matter? + missile->methodOfDeath = MOD_EMPLACED;//MOD_TURBLAST; //count as a heavy weap + missile->splashMethodOfDeath = MOD_EMPLACED;//MOD_TURBLAST;// ?SPLASH; + // we don't want it to bounce forever + missile->bounceCount = 8; + } + + if ( vehWeapon->iHealth ) + {//the missile can take damage + missile->health = vehWeapon->iHealth; + missile->takedamage = qtrue; + missile->contents = MASK_SHOT; + missile->e_DieFunc = dieF_WP_ExplosiveDie;//dieF_RocketDie; + } + + //set veh as cgame side owner for purpose of fx overrides + if (ent->m_pVehicle && ent->m_pVehicle->m_pPilot) + { + missile->owner = ent->m_pVehicle->m_pPilot; + } + else + { + missile->owner = ent; + } + missile->s.otherEntityNum = ent->s.number; + + if ( vehWeapon->iLifeTime ) + {//expire after a time + if ( vehWeapon->bExplodeOnExpire ) + {//blow up when your lifetime is up + missile->e_ThinkFunc = thinkF_WP_Explode;//FIXME: custom func? + } + else + {//just remove yourself + missile->e_ThinkFunc = thinkF_G_FreeEntity;//FIXME: custom func? + } + missile->nextthink = level.time + vehWeapon->iLifeTime; + } + if ( vehWeapon->fHoming ) + {//homing missile + //crap, we need to set up the homing stuff like it is in MP... + WP_RocketLock( ent, 16384 ); + if ( ent->client && ent->client->rocketLockIndex != ENTITYNUM_NONE ) + { + int dif = 0; + float rTime; + rTime = ent->client->rocketLockTime; + + if (rTime == -1) + { + rTime = ent->client->rocketLastValidTime; + } + + if ( !vehWeapon->iLockOnTime ) + {//no minimum lock-on time + dif = 10;//guaranteed lock-on + } + else + { + float lockTimeInterval = vehWeapon->iLockOnTime/16.0f; + dif = ( level.time - rTime ) / lockTimeInterval; + } + + if (dif < 0) + { + dif = 0; + } + + //It's 10 even though it locks client-side at 8, because we want them to have a sturdy lock first, and because there's a slight difference in time between server and client + if ( dif >= 10 && rTime != -1 ) + { + missile->enemy = &g_entities[ent->client->rocketLockIndex]; + + if (missile->enemy && missile->enemy->client && missile->enemy->health > 0 && !OnSameTeam(ent, missile->enemy)) + { //if enemy became invalid, died, or is on the same team, then don't seek it + missile->spawnflags |= 1;//just to let it know it should be faster... FIXME: EXTERNALIZE + missile->speed = vehWeapon->fSpeed; + missile->angle = vehWeapon->fHoming; + if ( vehWeapon->iLifeTime ) + {//expire after a time + missile->disconnectDebounceTime = level.time + vehWeapon->iLifeTime; + missile->lockCount = (int)(vehWeapon->bExplodeOnExpire); + } + missile->e_ThinkFunc = thinkF_rocketThink; + missile->nextthink = level.time + VEH_HOMING_MISSILE_THINK_TIME; + //FIXME: implement radar in SP? + //missile->s.eFlags |= EF_RADAROBJECT; + } + } + + ent->client->rocketLockIndex = ENTITYNUM_NONE; + ent->client->rocketLockTime = 0; + ent->client->rocketTargetTime = 0; + + VectorCopy( dir, missile->movedir ); + missile->random = 1.0f;//FIXME: externalize? + } + } + } + else + {//traceline + //FIXME: implement + } +} + +//--------------------------------------------------------- +void FireVehicleWeapon( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + Vehicle_t *pVeh = ent->m_pVehicle; + + if ( !pVeh ) + { + return; + } + + if (pVeh->m_iRemovedSurfaces) + { //can't fire when the thing is breaking apart + return; + } + + + if (ent->owner && ent->owner->client && ent->owner->client->ps.weapon!=WP_NONE) + { + return; + } + + // TODO?: If possible (probably not enough time), it would be nice if secondary fire was actually a mode switch/toggle + // so that, for instance, an x-wing can have 4-gun fire, or individual muzzle fire. If you wanted a different weapon, you + // would actually have to press the 2 key or something like that (I doubt I'd get a graphic for it anyways though). -AReis + + // If this is not the alternate fire, fire a normal blaster shot... + if ( true )//(pVeh->m_ulFlags & VEH_FLYING) || (pVeh->m_ulFlags & VEH_WINGSOPEN) ) // NOTE: Wings open also denotes that it has already launched. + { + int weaponNum = 0, vehWeaponIndex = VEH_WEAPON_NONE; + int delay = 1000; + qboolean aimCorrect = qfalse; + qboolean linkedFiring = qfalse; + + if ( !alt_fire ) + { + weaponNum = 0; + } + else + { + weaponNum = 1; + } + vehWeaponIndex = pVeh->m_pVehicleInfo->weapon[weaponNum].ID; + delay = pVeh->m_pVehicleInfo->weapon[weaponNum].delay; + aimCorrect = pVeh->m_pVehicleInfo->weapon[weaponNum].aimCorrect; + if ( pVeh->m_pVehicleInfo->weapon[weaponNum].linkable == 1//optionally linkable + && pVeh->weaponStatus[weaponNum].linked )//linked + {//we're linking the primary or alternate weapons, so we'll do *all* the muzzles + linkedFiring = qtrue; + } + + if ( vehWeaponIndex <= VEH_WEAPON_BASE || vehWeaponIndex >= MAX_VEH_WEAPONS ) + {//invalid vehicle weapon + return; + } + else + { + vehWeaponInfo_t *vehWeapon = &g_vehWeaponInfo[vehWeaponIndex]; + int i, cumulativeDelay = 0; + + for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) + { + if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex ) + {//this muzzle doesn't match the weapon we're trying to use + continue; + } + + // Fire this muzzle. + if ( pVeh->m_iMuzzleTag[i] != -1 && pVeh->m_Muzzles[i].m_iMuzzleWait < level.time ) + { + vec3_t start, dir; + // Prepare weapon delay. + if ( linkedFiring ) + {//linked firing, add the delay up for each muzzle, then apply to all of them at the end + cumulativeDelay += delay; + } + else + {//normal delay - NOTE: always-linked muzzles use normal delay, not cumulative + pVeh->m_Muzzles[i].m_iMuzzleWait = level.time + delay; + } + + if ( pVeh->weaponStatus[weaponNum].ammo < vehWeapon->iAmmoPerShot ) + {//out of ammo! + } + else + {//have enough ammo to shoot + //take the ammo + pVeh->weaponStatus[weaponNum].ammo -= vehWeapon->iAmmoPerShot; + //do the firing + //FIXME: do we still need to calc the muzzle here in SP? + //WP_CalcVehMuzzle(ent, i); + VectorCopy( pVeh->m_Muzzles[i].m_vMuzzlePos, start ); + VectorCopy( pVeh->m_Muzzles[i].m_vMuzzleDir, dir ); + if ( aimCorrect ) + {//auto-aim the missile at the crosshair + trace_t trace; + vec3_t end; + AngleVectors( pVeh->m_vOrientation, dir, NULL, NULL ); + //VectorMA( ent->currentOrigin, 32768, dir, end ); + VectorMA( ent->currentOrigin, 8192, dir, end ); + gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, end, ent->s.number, MASK_SHOT ); + //if ( trace.fraction < 1.0f && !trace.allsolid && !trace.startsolid ) + //bah, always point at end of trace + { + VectorSubtract( trace.endpos, start, dir ); + VectorNormalize( dir ); + } + + // Mounted lazer cannon auto aiming at enemy + //------------------------------------------- + if (ent->enemy) + { + Vehicle_t* enemyVeh = G_IsRidingVehicle(ent->enemy); + + // Don't Auto Aim At A Person Who Is Slide Breaking + if (!enemyVeh || !(enemyVeh->m_ulFlags&VEH_SLIDEBREAKING)) + { + vec3_t dir2; + VectorSubtract( ent->enemy->currentOrigin, start, dir2 ); + VectorNormalize( dir2 ); + if (DotProduct(dir, dir2)>0.95f) + { + VectorCopy(dir2, dir); + } + } + } + } + + //play the weapon's muzzle effect if we have one + if ( vehWeapon->iMuzzleFX ) + { + G_PlayEffect( vehWeapon->iMuzzleFX, pVeh->m_Muzzles[i].m_vMuzzlePos, pVeh->m_Muzzles[i].m_vMuzzleDir ); + } + WP_FireVehicleWeapon( ent, start, dir, vehWeapon ); + } + + if ( pVeh->weaponStatus[weaponNum].linked )//NOTE: we don't check linkedFiring here because that does *not* get set if the cannons are *always* linked + {//we're linking the weapon, so continue on and fire all appropriate muzzles + continue; + } + //ok, just break, we'll get in here again next frame and try the next muzzle... + break; + } + } + if ( cumulativeDelay ) + {//we linked muzzles so we need to apply the cumulative delay now, to each of the linked muzzles + for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) + { + if ( pVeh->m_pVehicleInfo->weapMuzzle[i] != vehWeaponIndex ) + {//this muzzle doesn't match the weapon we're trying to use + continue; + } + //apply the cumulative delay + pVeh->m_Muzzles[i].m_iMuzzleWait = level.time + cumulativeDelay; + } + } + } + } +} + +void WP_FireScepter( gentity_t *ent, qboolean alt_fire ) +{//just a straight beam + int damage = 1; + vec3_t start, end; + trace_t tr; + gentity_t *traceEnt = NULL, *tent; + float shotRange = 8192; + qboolean render_impact = qtrue; + + VectorCopy( muzzle, start ); + WP_TraceSetStart( ent, start, vec3_origin, vec3_origin ); + + WP_MissileTargetHint(ent, start, forward); + VectorMA( start, shotRange, forward, end ); + + gi.trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_SHOT, G2_RETURNONHIT, 10 ); + traceEnt = &g_entities[tr.entityNum]; + + if ( tr.surfaceFlags & SURF_NOIMPACT ) + { + render_impact = qfalse; + } + + // always render a shot beam, doing this the old way because I don't much feel like overriding the effect. + tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT ); + tent->svFlags |= SVF_BROADCAST; + VectorCopy( muzzle, tent->s.origin2 ); + + if ( render_impact ) + { + if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage ) + { + // Create a simple impact type mark that doesn't last long in the world + G_PlayEffect( G_EffectIndex( "disruptor/flesh_impact" ), tr.endpos, tr.plane.normal ); + + int hitLoc = G_GetHitLocFromTrace( &tr, MOD_DISRUPTOR ); + G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_EXTRA_KNOCKBACK, MOD_DISRUPTOR, hitLoc ); + } + else + { + G_PlayEffect( G_EffectIndex( "disruptor/wall_impact" ), tr.endpos, tr.plane.normal ); + } + } + + /* + shotDist = shotRange * tr.fraction; + + for ( dist = 0; dist < shotDist; dist += 64 ) + { + //FIXME: on a really long shot, this could make a LOT of alerts in one frame... + VectorMA( start, dist, forward, spot ); + AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 ); + } + VectorMA( start, shotDist-4, forward, spot ); + AddSightEvent( ent, spot, 256, AEL_DISCOVERED, 50 ); + */ +} + +extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent ); +//--------------------------------------------------------- +void FireWeapon( gentity_t *ent, qboolean alt_fire ) +//--------------------------------------------------------- +{ + float alert = 256; + Vehicle_t *pVeh = NULL; + + // track shots taken for accuracy tracking. + ent->client->ps.persistant[PERS_ACCURACY_SHOTS]++; + + // If this is a vehicle, fire it's weapon and we're done. + if ( ent && ent->client && ent->client->NPC_class == CLASS_VEHICLE ) + { + FireVehicleWeapon( ent, alt_fire ); + return; + } + + // set aiming directions + if ( ent->s.weapon == WP_DISRUPTOR && alt_fire ) + { + if ( ent->NPC ) + { + //snipers must use the angles they actually did their shot trace with + AngleVectors( ent->lastAngles, forward, vright, up ); + } + } + else if ( ent->s.weapon == WP_ATST_SIDE || ent->s.weapon == WP_ATST_MAIN ) + { + vec3_t delta1, enemy_org1, muzzle1; + vec3_t angleToEnemy1; + + VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle1 ); + + if ( !ent->s.number ) + {//player driving an AT-ST + //SIGH... because we can't anticipate alt-fire, must calc muzzle here and now + mdxaBone_t boltMatrix; + int bolt; + + if ( ent->client->ps.weapon == WP_ATST_MAIN ) + {//FIXME: alt_fire should fire both barrels, but slower? + if ( ent->alt_fire ) + { + bolt = ent->handRBolt; + } + else + { + bolt = ent->handLBolt; + } + } + else + {// ATST SIDE weapons + if ( ent->alt_fire ) + { + if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_light_blaster_cann" ) ) + {//don't have it! + return; + } + bolt = ent->genericBolt2; + } + else + { + if ( gi.G2API_GetSurfaceRenderStatus( &ent->ghoul2[ent->playerModel], "head_concussion_charger" ) ) + {//don't have it! + return; + } + bolt = ent->genericBolt1; + } + } + + vec3_t yawOnlyAngles = {0, ent->currentAngles[YAW], 0}; + if ( ent->currentAngles[YAW] != ent->client->ps.legsYaw ) + { + yawOnlyAngles[YAW] = ent->client->ps.legsYaw; + } + gi.G2API_GetBoltMatrix( ent->ghoul2, ent->playerModel, bolt, &boltMatrix, yawOnlyAngles, ent->currentOrigin, (cg.time?cg.time:level.time), NULL, ent->s.modelScale ); + + // work the matrix axis stuff into the original axis and origins used. + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->renderInfo.muzzlePoint ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, ent->client->renderInfo.muzzleDir ); + ent->client->renderInfo.mPCalcTime = level.time; + + AngleVectors( ent->client->ps.viewangles, forward, vright, up ); + //CalcMuzzlePoint( ent, forward, vright, up, muzzle, 0 ); + } + else if ( !ent->enemy ) + {//an NPC with no enemy to auto-aim at + VectorCopy( ent->client->renderInfo.muzzleDir, forward ); + } + else + {//NPC, auto-aim at enemy + CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 ); + + VectorSubtract (enemy_org1, muzzle1, delta1); + + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + } + } + else if ( ent->s.weapon == WP_BOT_LASER && ent->enemy ) + { + vec3_t delta1, enemy_org1, muzzle1; + vec3_t angleToEnemy1; + + CalcEntitySpot( ent->enemy, SPOT_HEAD, enemy_org1 ); + CalcEntitySpot( ent, SPOT_WEAPON, muzzle1 ); + + VectorSubtract (enemy_org1, muzzle1, delta1); + + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + } + else + { + if ( (pVeh = G_IsRidingVehicle( ent )) != NULL) //riding a vehicle + {//use our muzzleDir, can't use viewangles or vehicle m_vOrientation because we may be animated to shoot left or right... + if ((ent->s.eFlags&EF_NODRAW))//we're inside it + { + vec3_t aimAngles; + VectorCopy( ent->client->renderInfo.muzzleDir, forward ); + vectoangles( forward, aimAngles ); + //we're only keeping the yaw + aimAngles[PITCH] = ent->client->ps.viewangles[PITCH]; + aimAngles[ROLL] = 0; + AngleVectors( aimAngles, forward, vright, up ); + } + else + { + vec3_t actorRight; + vec3_t actorFwd; + + VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle ); + AngleVectors(ent->currentAngles, actorFwd, actorRight, 0); + + // Aiming Left + //------------- + if (ent->client->ps.torsoAnim==BOTH_VT_ATL_G || ent->client->ps.torsoAnim==BOTH_VS_ATL_G) + { + VectorScale(actorRight, -1.0f, forward); + } + + // Aiming Right + //-------------- + else if (ent->client->ps.torsoAnim==BOTH_VT_ATR_G || ent->client->ps.torsoAnim==BOTH_VS_ATR_G) + { + VectorCopy(actorRight, forward); + } + + // Aiming Forward + //---------------- + else + { + VectorCopy(actorFwd, forward); + } + + // If We Have An Enemy, Fudge The Aim To Hit The Enemy + if (ent->enemy) + { + vec3_t toEnemy; + VectorSubtract(ent->enemy->currentOrigin, ent->currentOrigin, toEnemy); + VectorNormalize(toEnemy); + if (DotProduct(toEnemy, forward)>0.75f && + ((ent->s.number==0 && !Q_irand(0,2)) || // the player has a 1 in 3 chance + (ent->s.number!=0 && !Q_irand(0,5)))) // other guys have a 1 in 6 chance + { + VectorCopy(toEnemy, forward); + } + else + { + forward[0] += Q_flrand(-0.1f, 0.1f); + forward[1] += Q_flrand(-0.1f, 0.1f); + forward[2] += Q_flrand(-0.1f, 0.1f); + } + } + } + } + else + { + AngleVectors( ent->client->ps.viewangles, forward, vright, up ); + } + } + + ent->alt_fire = alt_fire; + if (!pVeh) + { + if (ent->NPC && (ent->NPC->scriptFlags&SCF_FIRE_WEAPON_NO_ANIM)) + { + VectorCopy( ent->client->renderInfo.muzzlePoint, muzzle ); + VectorCopy( ent->client->renderInfo.muzzleDir, forward ); + MakeNormalVectors(forward, vright, up); + } + else + { + CalcMuzzlePoint ( ent, forward, vright, up, muzzle , 0); + } + } + + // fire the specific weapon + switch( ent->s.weapon ) + { + // Player weapons + //----------------- + case WP_SABER: + return; + break; + + case WP_BRYAR_PISTOL: + case WP_BLASTER_PISTOL: + WP_FireBryarPistol( ent, alt_fire ); + break; + + case WP_BLASTER: + WP_FireBlaster( ent, alt_fire ); + break; + + case WP_TUSKEN_RIFLE: + if ( alt_fire ) + { + WP_FireTuskenRifle( ent ); + } + else + { + WP_Melee( ent ); + } + break; + + case WP_DISRUPTOR: + alert = 50; // if you want it to alert enemies, remove this + WP_FireDisruptor( ent, alt_fire ); + break; + + case WP_BOWCASTER: + WP_FireBowcaster( ent, alt_fire ); + break; + + case WP_REPEATER: + WP_FireRepeater( ent, alt_fire ); + break; + + case WP_DEMP2: + WP_FireDEMP2( ent, alt_fire ); + break; + + case WP_FLECHETTE: + WP_FireFlechette( ent, alt_fire ); + break; + + case WP_ROCKET_LAUNCHER: + WP_FireRocket( ent, alt_fire ); + break; + + case WP_CONCUSSION: + if ( alt_fire ) + { + WP_FireConcussionAlt( ent ); + } + else + { + WP_FireConcussion( ent ); + } + break; + + case WP_THERMAL: + WP_FireThermalDetonator( ent, alt_fire ); + break; + + case WP_TRIP_MINE: + alert = 0; // if you want it to alert enemies, remove this + WP_PlaceLaserTrap( ent, alt_fire ); + break; + + case WP_DET_PACK: + alert = 0; // if you want it to alert enemies, remove this + WP_FireDetPack( ent, alt_fire ); + break; + + case WP_BOT_LASER: + WP_BotLaser( ent ); + break; + + case WP_EMPLACED_GUN: + // doesn't care about whether it's alt-fire or not. We can do an alt-fire if needed + WP_EmplacedFire( ent ); + break; + + case WP_MELEE: + alert = 0; // if you want it to alert enemies, remove this + if ( !alt_fire || !g_debugMelee->integer ) + { + WP_Melee( ent ); + } + break; + + case WP_ATST_MAIN: + WP_ATSTMainFire( ent ); + break; + + case WP_ATST_SIDE: + + // TEMP + if ( alt_fire ) + { +// WP_FireRocket( ent, qfalse ); + WP_ATSTSideAltFire(ent); + } + else + { + // FIXME! + /* if ( ent->s.number == 0 + && ent->client->NPC_class == CLASS_VEHICLE + && vehicleData[((CVehicleNPC *)ent->NPC)->m_iVehicleTypeID].type == VH_FIGHTER ) + { + WP_ATSTMainFire( ent ); + } + else*/ + { + WP_ATSTSideFire(ent); + } + } + break; + + case WP_TIE_FIGHTER: + // TEMP + WP_EmplacedFire( ent ); + break; + + case WP_RAPID_FIRE_CONC: + // TEMP + if ( alt_fire ) + { + WP_FireRepeater( ent, alt_fire ); + } + else + { + WP_EmplacedFire( ent ); + } + break; + + case WP_STUN_BATON: + WP_FireStunBaton( ent, alt_fire ); + break; + +// case WP_BLASTER_PISTOL: + case WP_JAWA: + WP_FireBryarPistol( ent, qfalse ); // never an alt-fire? + break; + + case WP_SCEPTER: + WP_FireScepter( ent, alt_fire ); + break; + + case WP_NOGHRI_STICK: + if ( !alt_fire ) + { + WP_FireNoghriStick( ent ); + } + //else does melee attack/damage/func + break; + + case WP_TUSKEN_STAFF: + default: + return; + break; + } + + if ( !ent->s.number ) + { + if ( ent->s.weapon == WP_FLECHETTE || (ent->s.weapon == WP_BOWCASTER && !alt_fire) ) + {//these can fire multiple shots, count them individually within the firing functions + } + else if ( W_AccuracyLoggableWeapon( ent->s.weapon, alt_fire, MOD_UNKNOWN ) ) + { + ent->client->sess.missionStats.shotsFired++; + } + } + // We should probably just use this as a default behavior, in special cases, just set alert to false. + if ( ent->s.number == 0 && alert > 0 ) + { + if ( ent->client->ps.groundEntityNum == ENTITYNUM_WORLD//FIXME: check for sand contents type? + && ent->s.weapon != WP_STUN_BATON + && ent->s.weapon != WP_MELEE + && ent->s.weapon != WP_TUSKEN_STAFF + && ent->s.weapon != WP_THERMAL + && ent->s.weapon != WP_TRIP_MINE + && ent->s.weapon != WP_DET_PACK ) + {//the vibration of the shot carries through your feet into the ground + AddSoundEvent( ent, muzzle, alert, AEL_DISCOVERED, qfalse, qtrue ); + } + else + {//an in-air alert + AddSoundEvent( ent, muzzle, alert, AEL_DISCOVERED ); + } + AddSightEvent( ent, muzzle, alert*2, AEL_DISCOVERED, 20 ); + } +} + +//NOTE: Emplaced gun moved to g_emplaced.cpp + +/*QUAKED misc_weapon_shooter (1 0 0) (-8 -8 -8) (8 8 8) ALTFIRE TOGGLE +ALTFIRE - fire the alt-fire of the chosen weapon +TOGGLE - keep firing until used again (fires at intervals of "wait") + +"wait" - debounce time between refires (defaults to 500) +"delay" - speed of WP_THERMAL (default is 900) +"random" - ranges from 0 to random, added to wait (defaults to 0) + +"target" - what to aim at (will update aim every frame if it's a moving target) + +"weapon" - specify the weapon to use (default is WP_BLASTER) + WP_BRYAR_PISTOL + WP_BLASTER + WP_DISRUPTOR + WP_BOWCASTER + WP_REPEATER + WP_DEMP2 + WP_FLECHETTE + WP_ROCKET_LAUNCHER + WP_CONCUSSION + WP_THERMAL + WP_TRIP_MINE + WP_DET_PACK + WP_STUN_BATON + WP_EMPLACED_GUN + WP_BOT_LASER + WP_TURRET + WP_ATST_MAIN + WP_ATST_SIDE + WP_TIE_FIGHTER + WP_RAPID_FIRE_CONC + WP_BLASTER_PISTOL +*/ +void misc_weapon_shooter_fire( gentity_t *self ) +{ + FireWeapon( self, (self->spawnflags&1) ); + if ( (self->spawnflags&2) ) + {//repeat + self->e_ThinkFunc = thinkF_misc_weapon_shooter_fire; + if (self->random) + { + self->nextthink = level.time + self->wait + (int)(random()*self->random); + } + else + { + self->nextthink = level.time + self->wait; + } + } +} + +void misc_weapon_shooter_use ( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( self->e_ThinkFunc == thinkF_misc_weapon_shooter_fire ) + {//repeating fire, stop + self->e_ThinkFunc = thinkF_NULL; + self->nextthink = -1; + return; + } + //otherwise, fire + misc_weapon_shooter_fire( self ); +} + +void misc_weapon_shooter_aim( gentity_t *self ) +{ + //update my aim + if ( self->target ) + { + gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target ); + if ( targ ) + { + self->enemy = targ; + VectorSubtract( targ->currentOrigin, self->currentOrigin, self->client->renderInfo.muzzleDir ); + VectorCopy( targ->currentOrigin, self->pos1 ); + vectoangles( self->client->renderInfo.muzzleDir, self->client->ps.viewangles ); + SetClientViewAngle( self, self->client->ps.viewangles ); + //FIXME: don't keep doing this unless target is a moving target? + self->nextthink = level.time + FRAMETIME; + } + else + { + self->enemy = NULL; + } + } +} + +extern stringID_table_t WPTable[]; +void SP_misc_weapon_shooter( gentity_t *self ) +{ + //alloc a client just for the weapon code to use + self->client = (gclient_s *)gi.Malloc(sizeof(gclient_s), TAG_G_ALLOC, qtrue); + + //set weapon + self->s.weapon = self->client->ps.weapon = WP_BLASTER; + if ( self->paintarget ) + {//use a different weapon + self->s.weapon = self->client->ps.weapon = GetIDForString( WPTable, self->paintarget ); + } + + //set where our muzzle is + VectorCopy( self->s.origin, self->client->renderInfo.muzzlePoint ); + //permanently updated + self->client->renderInfo.mPCalcTime = Q3_INFINITE; + + //set up to link + if ( self->target ) + { + self->e_ThinkFunc = thinkF_misc_weapon_shooter_aim; + self->nextthink = level.time + START_TIME_LINK_ENTS; + } + else + {//just set aim angles + VectorCopy( self->s.angles, self->client->ps.viewangles ); + AngleVectors( self->s.angles, self->client->renderInfo.muzzleDir, NULL, NULL ); + } + + //set up to fire when used + self->e_UseFunc = useF_misc_weapon_shooter_use; + + if ( !self->wait ) + { + self->wait = 500; + } +} diff --git a/code/game/g_weaponLoad.cpp b/code/game/g_weaponLoad.cpp new file mode 100644 index 0000000..920e7ae --- /dev/null +++ b/code/game/g_weaponLoad.cpp @@ -0,0 +1,1362 @@ +// g_weaponLoad.cpp +// fills in memory struct with ext_dat\weapons.dat + +// this is excluded from PCH usage 'cos it looks kinda scary to me, being game and ui.... -Ste + +#ifdef _USRDLL //UI dll + +#include "../ui/gameinfo.h" +#include "weapons.h" +extern gameinfo_import_t gi; +extern weaponData_t weaponData[]; +extern ammoData_t ammoData[]; + +#else //we are in the game + +// ONLY DO THIS ON THE GAME SIDE +#include "g_local.h" + +typedef struct { + char *name; + void (*func)(centity_t *cent, const struct weaponInfo_s *weapon ); +} func_t; + +// Bryar +void FX_BryarProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BryarAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Blaster +void FX_BlasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Bowcaster +void FX_BowcasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Heavy Repeater +void FX_RepeaterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_RepeaterAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// DEMP2 +void FX_DEMP2_ProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_DEMP2_AltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Golan Arms Flechette +void FX_FlechetteProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_FlechetteAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Personal Rocket Launcher +void FX_RocketProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_RocketAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Concussion Rifle +void FX_ConcProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Emplaced weapon +void FX_EmplacedProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Turret weapon +void FX_TurretProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// ATST Main weapon +void FX_ATSTMainProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// ATST Side weapons +void FX_ATSTSideMainProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_ATSTSideAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +//Tusken projectile +void FX_TuskenShotProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +//Noghri projectile +void FX_NoghriShotProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Table used to attach an extern missile function string to the actual cgame function +func_t funcs[] = { + {"bryar_func", FX_BryarProjectileThink}, + {"bryar_alt_func", FX_BryarAltProjectileThink}, + {"blaster_func", FX_BlasterProjectileThink}, + {"blaster_alt_func", FX_BlasterAltFireThink}, + {"bowcaster_func", FX_BowcasterProjectileThink}, + {"repeater_func", FX_RepeaterProjectileThink}, + {"repeater_alt_func", FX_RepeaterAltProjectileThink}, + {"demp2_func", FX_DEMP2_ProjectileThink}, + {"demp2_alt_func", FX_DEMP2_AltProjectileThink}, + {"flechette_func", FX_FlechetteProjectileThink}, + {"flechette_alt_func", FX_FlechetteAltProjectileThink}, + {"rocket_func", FX_RocketProjectileThink}, + {"rocket_alt_func", FX_RocketAltProjectileThink}, + {"conc_func", FX_ConcProjectileThink}, + {"emplaced_func", FX_EmplacedProjectileThink}, + {"turret_func", FX_TurretProjectileThink}, + {"atstmain_func", FX_ATSTMainProjectileThink}, + {"atst_side_alt_func", FX_ATSTSideAltProjectileThink}, + {"atst_side_main_func", FX_ATSTSideMainProjectileThink}, + {"tusk_shot_func", FX_TuskenShotProjectileThink}, + {"noghri_shot_func", FX_NoghriShotProjectileThink}, + {NULL, NULL} +}; +#endif + + +//qboolean COM_ParseInt( char **data, int *i ); +//qboolean COM_ParseString( char **data, char **s ); +//qboolean COM_ParseFloat( char **data, float *f ); + +struct +{ + int weaponNum; // Current weapon number + int ammoNum; +} wpnParms; + +void WPN_Ammo (const char **holdBuf); +void WPN_AmmoIcon (const char **holdBuf); +void WPN_AmmoMax (const char **holdBuf); +void WPN_AmmoLowCnt (const char **holdBuf); +void WPN_AmmoType (const char **holdBuf); +void WPN_EnergyPerShot (const char **holdBuf); +void WPN_FireTime (const char **holdBuf); +void WPN_FiringSnd (const char **holdBuf); +void WPN_AltFiringSnd(const char **holdBuf ); +void WPN_StopSnd( const char **holdBuf ); +//void WPN_FlashSnd (char **holdBuf); +//void WPN_AltFlashSnd (char **holdBuf); +void WPN_ChargeSnd (const char **holdBuf); +void WPN_AltChargeSnd (const char **holdBuf); +void WPN_SelectSnd (const char **holdBuf); +void WPN_Range (const char **holdBuf); +void WPN_WeaponClass ( const char **holdBuf); +void WPN_WeaponIcon (const char **holdBuf); +void WPN_WeaponModel (const char **holdBuf); +void WPN_WeaponType (const char **holdBuf); +void WPN_AltEnergyPerShot (const char **holdBuf); +void WPN_AltFireTime (const char **holdBuf); +void WPN_AltRange (const char **holdBuf); +void WPN_BarrelCount(const char **holdBuf); +void WPN_MissileName(const char **holdBuf); +void WPN_AltMissileName(const char **holdBuf); +void WPN_MissileSound(const char **holdBuf); +void WPN_AltMissileSound(const char **holdBuf); +void WPN_MissileLight(const char **holdBuf); +void WPN_AltMissileLight(const char **holdBuf); +void WPN_MissileLightColor(const char **holdBuf); +void WPN_AltMissileLightColor(const char **holdBuf); +void WPN_FuncName(const char **holdBuf); +void WPN_AltFuncName(const char **holdBuf); +void WPN_MissileHitSound(const char **holdBuf); +void WPN_AltMissileHitSound(const char **holdBuf); +void WPN_MuzzleEffect(const char **holdBuf); +void WPN_AltMuzzleEffect(const char **holdBuf); +//#ifdef _IMMERSION +void WPN_FiringFrc(const char **holdBuf); +void WPN_AltFiringFrc(const char **holdBuf); +void WPN_ChargeFrc(const char **holdBuf); +void WPN_AltChargeFrc(const char **holdBuf); +void WPN_StopFrc(const char **holdBuf); +void WPN_SelectFrc(const char **holdBuf); +//#endif // _IMMERSION + +typedef struct +{ + char *parmName; + void (*func)(const char **holdBuf); +} wpnParms_t; + +wpnParms_t WpnParms[] = +{ + "ammo", WPN_Ammo, //ammo + "ammoicon", WPN_AmmoIcon, + "ammomax", WPN_AmmoMax, + "ammolowcount", WPN_AmmoLowCnt, //weapons + "ammotype", WPN_AmmoType, + "energypershot", WPN_EnergyPerShot, + "fireTime", WPN_FireTime, + "firingsound", WPN_FiringSnd, + "altfiringsound", WPN_AltFiringSnd, +// "flashsound", WPN_FlashSnd, +// "altflashsound", WPN_AltFlashSnd, + "stopsound", WPN_StopSnd, + "chargesound", WPN_ChargeSnd, + "altchargesound", WPN_AltChargeSnd, + "selectsound", WPN_SelectSnd, + "range", WPN_Range, + "weaponclass", WPN_WeaponClass, + "weaponicon", WPN_WeaponIcon, + "weaponmodel", WPN_WeaponModel, + "weapontype", WPN_WeaponType, + "altenergypershot", WPN_AltEnergyPerShot, + "altfireTime", WPN_AltFireTime, + "altrange", WPN_AltRange, + "barrelcount", WPN_BarrelCount, + "missileModel", WPN_MissileName, + "altmissileModel", WPN_AltMissileName, + "missileSound", WPN_MissileSound, + "altmissileSound", WPN_AltMissileSound, + "missileLight", WPN_MissileLight, + "altmissileLight", WPN_AltMissileLight, + "missileLightColor",WPN_MissileLightColor, + "altmissileLightColor", WPN_AltMissileLightColor, + "missileFuncName", WPN_FuncName, + "altmissileFuncName", WPN_AltFuncName, + "missileHitSound", WPN_MissileHitSound, + "altmissileHitSound", WPN_AltMissileHitSound, + "muzzleEffect", WPN_MuzzleEffect, + "altmuzzleEffect", WPN_AltMuzzleEffect +//#ifdef _IMMERSION +, "firingForce", WPN_FiringFrc +, "altFiringForce", WPN_AltFiringFrc +, "chargeForce", WPN_ChargeFrc +, "altChargeForce", WPN_AltChargeFrc +, "stopForce", WPN_StopFrc +, "selectForce", WPN_SelectFrc +//#endif // _IMMERSION +}; + +const int WPN_PARM_MAX = sizeof(WpnParms) / sizeof(WpnParms[0]); + +void WPN_WeaponType( const char **holdBuf) +{ + int weaponNum; + const char *tokenStr; + + if (COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + // FIXME : put this in an array (maybe a weaponDataInternal array???) + if (!Q_stricmp(tokenStr,"WP_NONE")) + weaponNum = WP_NONE; + else if (!Q_stricmp(tokenStr,"WP_SABER")) + weaponNum = WP_SABER; + else if (!Q_stricmp(tokenStr,"WP_BLASTER_PISTOL")) + weaponNum = WP_BLASTER_PISTOL; + else if (!Q_stricmp(tokenStr,"WP_BRYAR_PISTOL")) + weaponNum = WP_BRYAR_PISTOL; + else if (!Q_stricmp(tokenStr,"WP_BLASTER")) + weaponNum = WP_BLASTER; + else if (!Q_stricmp(tokenStr,"WP_DISRUPTOR")) + weaponNum = WP_DISRUPTOR; + else if (!Q_stricmp(tokenStr,"WP_BOWCASTER")) + weaponNum = WP_BOWCASTER; + else if (!Q_stricmp(tokenStr,"WP_REPEATER")) + weaponNum = WP_REPEATER; + else if (!Q_stricmp(tokenStr,"WP_DEMP2")) + weaponNum = WP_DEMP2; + else if (!Q_stricmp(tokenStr,"WP_FLECHETTE")) + weaponNum = WP_FLECHETTE; + else if (!Q_stricmp(tokenStr,"WP_ROCKET_LAUNCHER")) + weaponNum = WP_ROCKET_LAUNCHER; + else if (!Q_stricmp(tokenStr,"WP_CONCUSSION")) + weaponNum = WP_CONCUSSION; + else if (!Q_stricmp(tokenStr,"WP_THERMAL")) + weaponNum = WP_THERMAL; + else if (!Q_stricmp(tokenStr,"WP_TRIP_MINE")) + weaponNum = WP_TRIP_MINE; + else if (!Q_stricmp(tokenStr,"WP_DET_PACK")) + weaponNum = WP_DET_PACK; + else if (!Q_stricmp(tokenStr,"WP_STUN_BATON")) + weaponNum = WP_STUN_BATON; + else if (!Q_stricmp(tokenStr,"WP_BOT_LASER")) + weaponNum = WP_BOT_LASER; + else if (!Q_stricmp(tokenStr,"WP_EMPLACED_GUN")) + weaponNum = WP_EMPLACED_GUN; + else if (!Q_stricmp(tokenStr,"WP_MELEE")) + weaponNum = WP_MELEE; + else if (!Q_stricmp(tokenStr,"WP_TURRET")) + weaponNum = WP_TURRET; + else if (!Q_stricmp(tokenStr,"WP_ATST_MAIN")) + weaponNum = WP_ATST_MAIN; + else if (!Q_stricmp(tokenStr,"WP_ATST_SIDE")) + weaponNum = WP_ATST_SIDE; + else if (!Q_stricmp(tokenStr,"WP_TIE_FIGHTER")) + weaponNum = WP_TIE_FIGHTER; + else if (!Q_stricmp(tokenStr,"WP_RAPID_FIRE_CONC")) + weaponNum = WP_RAPID_FIRE_CONC; + else if (!Q_stricmp(tokenStr,"WP_JAWA")) + weaponNum = WP_JAWA; + else if (!Q_stricmp(tokenStr,"WP_TUSKEN_RIFLE")) + weaponNum = WP_TUSKEN_RIFLE; + else if (!Q_stricmp(tokenStr,"WP_TUSKEN_STAFF")) + weaponNum = WP_TUSKEN_STAFF; + else if (!Q_stricmp(tokenStr,"WP_SCEPTER")) + weaponNum = WP_SCEPTER; + else if (!Q_stricmp(tokenStr,"WP_NOGHRI_STICK")) + weaponNum = WP_NOGHRI_STICK; + else + { + weaponNum = 0; + gi.Printf(S_COLOR_YELLOW"WARNING: bad weapontype in external weapon data '%s'\n", tokenStr); + } + + wpnParms.weaponNum = weaponNum; +} + +//-------------------------------------------- +void WPN_WeaponClass(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if (COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 32) + { + len = 32; + gi.Printf(S_COLOR_YELLOW"WARNING: weaponclass too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].classname,tokenStr,len); +} + + +//-------------------------------------------- +void WPN_WeaponModel(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: weaponMdl too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].weaponMdl,tokenStr,len); +} + +//-------------------------------------------- +void WPN_WeaponIcon(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: weaponIcon too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].weaponIcon,tokenStr,len); +} + +//-------------------------------------------- +void WPN_AmmoType(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < AMMO_NONE ) || (tokenInt >= AMMO_MAX )) + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad Ammotype in external weapon data '%d'\n", tokenInt); + return; + } + + weaponData[wpnParms.weaponNum].ammoIndex = tokenInt; +} + +//-------------------------------------------- +void WPN_AmmoLowCnt(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < 0) || (tokenInt > 200 )) // FIXME :What are the right values? + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad Ammolowcount in external weapon data '%d'\n", tokenInt); + return; + } + + weaponData[wpnParms.weaponNum].ammoLow = tokenInt; +} + +//-------------------------------------------- +void WPN_FiringSnd(const char **holdBuf) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: firingSnd too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].firingSnd,tokenStr,len); +} + +//-------------------------------------------- +void WPN_AltFiringSnd( const char **holdBuf ) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: altFiringSnd too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].altFiringSnd,tokenStr,len); +} + +//-------------------------------------------- +void WPN_StopSnd( const char **holdBuf ) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: stopSnd too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].stopSnd,tokenStr,len); +} +/* +//-------------------------------------------- +void WPN_FlashSnd(const char **holdBuf) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: flashSnd too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].flashSnd,tokenStr,len); +} + +//-------------------------------------------- +void WPN_AltFlashSnd(const char **holdBuf) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: altFlashSnd too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].altFlashSnd,tokenStr,len); +} +*/ +//-------------------------------------------- +void WPN_ChargeSnd(const char **holdBuf) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: chargeSnd too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].chargeSnd,tokenStr,len); +} + +//-------------------------------------------- +void WPN_AltChargeSnd(const char **holdBuf) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: altChargeSnd too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].altChargeSnd,tokenStr,len); +} + +//-------------------------------------------- +void WPN_SelectSnd( const char **holdBuf ) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString( holdBuf,&tokenStr )) + { + return; + } + + len = strlen( tokenStr ); + len++; + + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: selectSnd too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz( weaponData[wpnParms.weaponNum].selectSnd,tokenStr,len); +} + +//#ifdef _IMMERSION + +//-------------------------------------------- +void WPN_FiringFrc( const char **holdBuf ) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString( holdBuf,&tokenStr )) + { + return; + } + + len = strlen( tokenStr ); + len++; + + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: firingFrc too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + +#ifdef _IMMERSION + Q_strncpyz( weaponData[wpnParms.weaponNum].firingFrc,tokenStr,len); +#endif +} + +//-------------------------------------------- +void WPN_AltFiringFrc( const char **holdBuf ) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString( holdBuf,&tokenStr )) + { + return; + } + + len = strlen( tokenStr ); + len++; + + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: altFiringFrc too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + +#ifdef _IMMERSION + Q_strncpyz( weaponData[wpnParms.weaponNum].altFiringFrc,tokenStr,len); +#endif +} + +//-------------------------------------------- +void WPN_ChargeFrc( const char **holdBuf ) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString( holdBuf,&tokenStr )) + { + return; + } + + len = strlen( tokenStr ); + len++; + + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: chargeFrc too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + +#ifdef _IMMERSION + Q_strncpyz( weaponData[wpnParms.weaponNum].chargeFrc,tokenStr,len); +#endif +} + +//-------------------------------------------- +void WPN_AltChargeFrc( const char **holdBuf ) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString( holdBuf,&tokenStr )) + { + return; + } + + len = strlen( tokenStr ); + len++; + + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: altChargeFrc too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + +#ifdef _IMMERSION + Q_strncpyz( weaponData[wpnParms.weaponNum].altChargeFrc,tokenStr,len); +#endif +} + +//-------------------------------------------- +void WPN_StopFrc( const char **holdBuf ) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString( holdBuf,&tokenStr )) + { + return; + } + + len = strlen( tokenStr ); + len++; + + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: stopFrc too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + +#ifdef _IMMERSION + Q_strncpyz( weaponData[wpnParms.weaponNum].stopFrc,tokenStr,len); +#endif +} + +//-------------------------------------------- +void WPN_SelectFrc( const char **holdBuf ) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString( holdBuf,&tokenStr )) + { + return; + } + + len = strlen( tokenStr ); + len++; + + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: selectFrc too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + +#ifdef _IMMERSION + Q_strncpyz( weaponData[wpnParms.weaponNum].selectFrc,tokenStr,len); +#endif +} + +//#endif // _IMMERSION + +//-------------------------------------------- +void WPN_FireTime(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < 0) || (tokenInt > 10000 )) // FIXME :What are the right values? + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad Firetime in external weapon data '%d'\n", tokenInt); + return; + } + weaponData[wpnParms.weaponNum].fireTime = tokenInt; +} + +//-------------------------------------------- +void WPN_Range(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < 0) || (tokenInt > 10000 )) // FIXME :What are the right values? + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad Range in external weapon data '%d'\n", tokenInt); + return; + } + + weaponData[wpnParms.weaponNum].range = tokenInt; +} + +//-------------------------------------------- +void WPN_EnergyPerShot(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < 0) || (tokenInt > 1000 )) // FIXME :What are the right values? + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad EnergyPerShot in external weapon data '%d'\n", tokenInt); + return; + } + weaponData[wpnParms.weaponNum].energyPerShot = tokenInt; +} + +//-------------------------------------------- +void WPN_AltFireTime(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < 0) || (tokenInt > 10000 )) // FIXME :What are the right values? + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad altFireTime in external weapon data '%d'\n", tokenInt); + return; + } + weaponData[wpnParms.weaponNum].altFireTime = tokenInt; +} + +//-------------------------------------------- +void WPN_AltRange(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < 0) || (tokenInt > 10000 )) // FIXME :What are the right values? + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad AltRange in external weapon data '%d'\n", tokenInt); + return; + } + + weaponData[wpnParms.weaponNum].altRange = tokenInt; +} + +//-------------------------------------------- +void WPN_AltEnergyPerShot(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < 0) || (tokenInt > 1000 )) // FIXME :What are the right values? + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad AltEnergyPerShot in external weapon data '%d'\n", tokenInt); + return; + } + weaponData[wpnParms.weaponNum].altEnergyPerShot = tokenInt; +} + +//-------------------------------------------- +void WPN_Ammo(const char **holdBuf) +{ + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + if (!Q_stricmp(tokenStr,"AMMO_NONE")) + wpnParms.ammoNum = AMMO_NONE; + else if (!Q_stricmp(tokenStr,"AMMO_FORCE")) + wpnParms.ammoNum = AMMO_FORCE; + else if (!Q_stricmp(tokenStr,"AMMO_BLASTER")) + wpnParms.ammoNum = AMMO_BLASTER; + else if (!Q_stricmp(tokenStr,"AMMO_POWERCELL")) + wpnParms.ammoNum = AMMO_POWERCELL; + else if (!Q_stricmp(tokenStr,"AMMO_METAL_BOLTS")) + wpnParms.ammoNum = AMMO_METAL_BOLTS; + else if (!Q_stricmp(tokenStr,"AMMO_ROCKETS")) + wpnParms.ammoNum = AMMO_ROCKETS; + else if (!Q_stricmp(tokenStr,"AMMO_EMPLACED")) + wpnParms.ammoNum = AMMO_EMPLACED; + else if (!Q_stricmp(tokenStr,"AMMO_THERMAL")) + wpnParms.ammoNum = AMMO_THERMAL; + else if (!Q_stricmp(tokenStr,"AMMO_TRIPMINE")) + wpnParms.ammoNum = AMMO_TRIPMINE; + else if (!Q_stricmp(tokenStr,"AMMO_DETPACK")) + wpnParms.ammoNum = AMMO_DETPACK; + else + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad ammotype in external weapon data '%s'\n", tokenStr); + wpnParms.ammoNum = 0; + } +} + +//-------------------------------------------- +void WPN_AmmoIcon(const char **holdBuf) +{ + const char *tokenStr; + int len; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 32) + { + len = 32; + gi.Printf(S_COLOR_YELLOW"WARNING: ammoicon too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(ammoData[wpnParms.ammoNum].icon,tokenStr,len); + +} + +//-------------------------------------------- +void WPN_AmmoMax(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < 0) || (tokenInt > 1000 )) + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad Ammo Max in external weapon data '%d'\n", tokenInt); + return; + } + ammoData[wpnParms.ammoNum].max = tokenInt; +} + +//-------------------------------------------- +void WPN_BarrelCount(const char **holdBuf) +{ + int tokenInt; + + if ( COM_ParseInt(holdBuf,&tokenInt)) + { + SkipRestOfLine(holdBuf); + return; + } + + if ((tokenInt < 0) || (tokenInt > 4 )) + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad Range in external weapon data '%d'\n", tokenInt); + return; + } + + weaponData[wpnParms.weaponNum].numBarrels = tokenInt; +} + + +//-------------------------------------------- +static void WP_ParseWeaponParms(const char **holdBuf) +{ + const char *token; + int i; + + + while (holdBuf) + { + token = COM_ParseExt( holdBuf, qtrue ); + + if (!Q_stricmp( token, "}" )) // End of data for this weapon + break; + + // Loop through possible parameters + for (i=0;i 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: MissileName too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].missileMdl,tokenStr,len); + +} + +//-------------------------------------------- +void WPN_AltMissileName(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: AltMissileName too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].alt_missileMdl,tokenStr,len); + +} + + +//-------------------------------------------- +void WPN_MissileHitSound(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: MissileHitSound too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].missileHitSound,tokenStr,len); +} + +//-------------------------------------------- +void WPN_AltMissileHitSound(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: AltMissileHitSound too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].altmissileHitSound,tokenStr,len); +} + +//-------------------------------------------- +void WPN_MissileSound(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: MissileSound too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].missileSound,tokenStr,len); + +} + + +//-------------------------------------------- +void WPN_AltMissileSound(const char **holdBuf) +{ + int len; + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: AltMissileSound too long in external WEAPONS.DAT '%s'\n", tokenStr); + } + + Q_strncpyz(weaponData[wpnParms.weaponNum].alt_missileSound,tokenStr,len); + +} + +//-------------------------------------------- +void WPN_MissileLightColor(const char **holdBuf) +{ + int i; + float tokenFlt; + + for (i=0;i<3;++i) + { + if ( COM_ParseFloat(holdBuf,&tokenFlt)) + { + SkipRestOfLine(holdBuf); + continue; + } + + if ((tokenFlt < 0) || (tokenFlt > 1 )) + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad missilelightcolor in external weapon data '%f'\n", tokenFlt); + continue; + } + weaponData[wpnParms.weaponNum].missileDlightColor[i] = tokenFlt; + } + +} + +//-------------------------------------------- +void WPN_AltMissileLightColor(const char **holdBuf) +{ + int i; + float tokenFlt; + + for (i=0;i<3;++i) + { + if ( COM_ParseFloat(holdBuf,&tokenFlt)) + { + SkipRestOfLine(holdBuf); + continue; + } + + if ((tokenFlt < 0) || (tokenFlt > 1 )) + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad altmissilelightcolor in external weapon data '%f'\n", tokenFlt); + continue; + } + weaponData[wpnParms.weaponNum].alt_missileDlightColor[i] = tokenFlt; + } + +} + + +//-------------------------------------------- +void WPN_MissileLight(const char **holdBuf) +{ + float tokenFlt; + + if ( COM_ParseFloat(holdBuf,&tokenFlt)) + { + SkipRestOfLine(holdBuf); + } + + if ((tokenFlt < 0) || (tokenFlt > 255 )) // FIXME :What are the right values? + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad missilelight in external weapon data '%f'\n", tokenFlt); + } + weaponData[wpnParms.weaponNum].missileDlight = tokenFlt; +} + +//-------------------------------------------- +void WPN_AltMissileLight(const char **holdBuf) +{ + float tokenFlt; + + if ( COM_ParseFloat(holdBuf,&tokenFlt)) + { + SkipRestOfLine(holdBuf); + } + + if ((tokenFlt < 0) || (tokenFlt > 255 )) // FIXME :What are the right values? + { + gi.Printf(S_COLOR_YELLOW"WARNING: bad altmissilelight in external weapon data '%f'\n", tokenFlt); + } + weaponData[wpnParms.weaponNum].alt_missileDlight = tokenFlt; +} + + +//-------------------------------------------- +void WPN_FuncName(const char **holdBuf) +{ + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + // ONLY DO THIS ON THE GAME SIDE +#ifndef _USRDLL + int len = strlen(tokenStr); + + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: FuncName '%s' too long in external WEAPONS.DAT\n", tokenStr); + } + + for ( func_t* s=funcs ; s->name ; s++ ) { + if ( !Q_stricmp(s->name, tokenStr) ) { + // found it + weaponData[wpnParms.weaponNum].func = (void*)s->func; + return; + } + } + gi.Printf(S_COLOR_YELLOW"WARNING: FuncName '%s' in external WEAPONS.DAT does not exist\n", tokenStr); +#endif +} + + +//-------------------------------------------- +void WPN_AltFuncName(const char **holdBuf) +{ + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + + // ONLY DO THIS ON THE GAME SIDE +#ifndef _USRDLL + int len = strlen(tokenStr); + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: AltFuncName '%s' too long in external WEAPONS.DAT\n", tokenStr); + } + + for ( func_t* s=funcs ; s->name ; s++ ) { + if ( !Q_stricmp(s->name, tokenStr) ) { + // found it + weaponData[wpnParms.weaponNum].altfunc = (void*)s->func; + return; + } + } + gi.Printf(S_COLOR_YELLOW"WARNING: AltFuncName %s in external WEAPONS.DAT does not exist\n", tokenStr); + +#endif +} + +//-------------------------------------------- +void WPN_MuzzleEffect(const char **holdBuf) +{ + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + // ONLY DO THIS ON THE GAME SIDE +#ifndef _USRDLL + + int len = strlen(tokenStr); + + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: MuzzleEffect '%s' too long in external WEAPONS.DAT\n", tokenStr); + } + + G_EffectIndex( tokenStr ); + Q_strncpyz(weaponData[wpnParms.weaponNum].mMuzzleEffect,tokenStr,len); + +#endif +} + +//-------------------------------------------- +void WPN_AltMuzzleEffect(const char **holdBuf) +{ + const char *tokenStr; + + if ( COM_ParseString(holdBuf,&tokenStr)) + { + return; + } + // ONLY DO THIS ON THE GAME SIDE +#ifndef _USRDLL + + int len = strlen(tokenStr); + + len++; + if (len > 64) + { + len = 64; + gi.Printf(S_COLOR_YELLOW"WARNING: AltMuzzleEffect '%s' too long in external WEAPONS.DAT\n", tokenStr); + } + + G_EffectIndex( tokenStr ); + Q_strncpyz(weaponData[wpnParms.weaponNum].mAltMuzzleEffect,tokenStr,len); + +#endif +} + +//-------------------------------------------- +static void WP_ParseParms(const char *buffer) +{ + const char *holdBuf; + const char *token; + + holdBuf = buffer; + COM_BeginParseSession(); + + while ( holdBuf ) + { + token = COM_ParseExt( &holdBuf, qtrue ); + + if ( !Q_stricmp( token, "{" ) ) + { + token =token; + WP_ParseWeaponParms(&holdBuf); + } + + } + +} + +//-------------------------------------------- +void WP_LoadWeaponParms (void) +{ + char *buffer; + int len; + + len = gi.FS_ReadFile("ext_data/weapons.dat",(void **) &buffer); + + if (len == -1) + { + Com_Error(ERR_FATAL,"Cannot find ext_data/weapons.dat!\n"); + } + + // initialise the data area + memset(weaponData, 0, WP_NUM_WEAPONS * sizeof(weaponData_t)); + + WP_ParseParms(buffer); + + gi.FS_FreeFile( buffer ); //let go of the buffer +} \ No newline at end of file diff --git a/code/game/game.def b/code/game/game.def new file mode 100644 index 0000000..d52bf0a --- /dev/null +++ b/code/game/game.def @@ -0,0 +1,4 @@ +EXPORTS + GetGameAPI + vmMain + dllEntry diff --git a/code/game/game.vcproj b/code/game/game.vcproj new file mode 100644 index 0000000..525df7b --- /dev/null +++ b/code/game/game.vcproj @@ -0,0 +1,2916 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/game/game.vcproj.vspscc b/code/game/game.vcproj.vspscc new file mode 100644 index 0000000..6cb031b --- /dev/null +++ b/code/game/game.vcproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROJECT" +} diff --git a/code/game/genericparser2.cpp b/code/game/genericparser2.cpp new file mode 100644 index 0000000..dbe5178 --- /dev/null +++ b/code/game/genericparser2.cpp @@ -0,0 +1,1136 @@ +// Filename:- genericparser2.cpp + +// leave this at the top for PCH reasons... + +#include "common_headers.h" + +#ifdef _JK2EXE +#include "../qcommon/qcommon.h" +#else +#include "g_headers.h" +#endif + + + + + + +//#define _EXE + + +#define MAX_TOKEN_SIZE 1024 +static char token[MAX_TOKEN_SIZE]; + +static char *GetToken(char **text, bool allowLineBreaks, bool readUntilEOL = false) +{ + char *pointer = *text; + int length = 0; + int c = 0; + bool foundLineBreak; + + token[0] = 0; + if (!pointer) + { + return token; + } + + while(1) + { + foundLineBreak = false; + while(1) + { + c = *pointer; + if (c > ' ') + { + break; + } + if (!c) + { + *text = 0; + return token; + } + if (c == '\n') + { + foundLineBreak = true; + } + pointer++; + } + if (foundLineBreak && !allowLineBreaks) + { + *text = pointer; + return token; + } + + c = *pointer; + + // skip single line comment + if (c == '/' && pointer[1] == '/') + { + pointer += 2; + while (*pointer && *pointer != '\n') + { + pointer++; + } + } + // skip multi line comments + else if (c == '/' && pointer[1] == '*') + { + pointer += 2; + while (*pointer && (*pointer != '*' || pointer[1] != '/')) + { + pointer++; + } + if (*pointer) + { + pointer += 2; + } + } + else + { // found the start of a token + break; + } + } + + if (c == '\"') + { // handle a string + pointer++; + while (1) + { + c = *pointer++; + if (c == '\"') + { +// token[length++] = c; + break; + } + else if (!c) + { + break; + } + else if (length < MAX_TOKEN_SIZE) + { + token[length++] = c; + } + } + } + else if (readUntilEOL) + { + // absorb all characters until EOL + while(c != '\n' && c != '\r') + { + if (c == '/' && ((*(pointer+1)) == '/' || (*(pointer+1)) == '*')) + { + break; + } + + if (length < MAX_TOKEN_SIZE) + { + token[length++] = c; + } + pointer++; + c = *pointer; + } + // remove trailing white space + while(length && token[length-1] < ' ') + { + length--; + } + } + else + { + while(c > ' ') + { + if (length < MAX_TOKEN_SIZE) + { + token[length++] = c; + } + pointer++; + c = *pointer; + } + } + + if (token[0] == '\"') + { // remove start quote + length--; + memmove(token, token+1, length); + + if (length && token[length-1] == '\"') + { // remove end quote + length--; + } + } + + if (length >= MAX_TOKEN_SIZE) + { + length = 0; + } + token[length] = 0; + *text = (char *)pointer; + + return token; +} + + + + +CTextPool::CTextPool(int initSize) : + mNext(0), + mSize(initSize), + mUsed(0) +{ +#ifdef _EXE +// mPool = (char *)Z_Malloc(mSize, TAG_GP2); + mPool = (char *)Z_Malloc(mSize, TAG_TEXTPOOL, qtrue); +#else + mPool = (char *)trap_Z_Malloc(mSize, TAG_GP2); +#endif +} + +CTextPool::~CTextPool(void) +{ +#ifdef _EXE + Z_Free(mPool); +#else + trap_Z_Free(mPool); +#endif +} + +char *CTextPool::AllocText(char *text, bool addNULL, CTextPool **poolPtr) +{ + int length = strlen(text) + (addNULL ? 1 : 0); + + if (mUsed + length + 1> mSize) + { // extra 1 to put a null on the end + if (poolPtr) + { + (*poolPtr)->SetNext(new CTextPool(mSize)); + *poolPtr = (*poolPtr)->GetNext(); + + return (*poolPtr)->AllocText(text, addNULL); + } + + return 0; + } + + strcpy(mPool + mUsed, text); + mUsed += length; + mPool[mUsed] = 0; + + return mPool + mUsed - length; +} + +void CleanTextPool(CTextPool *pool) +{ + CTextPool *next; + + while(pool) + { + next = pool->GetNext(); + delete pool; + pool = next; + } +} + + + + + + + +CGPObject::CGPObject(const char *initName) : + mName(initName), + mNext(0), + mInOrderNext(0), + mInOrderPrevious(0) +{ +} + +bool CGPObject::WriteText(CTextPool **textPool, const char *text) +{ + if (strchr(text, ' ') || !text[0]) + { + (*textPool)->AllocText("\"", false, textPool); + (*textPool)->AllocText((char *)text, false, textPool); + (*textPool)->AllocText("\"", false, textPool); + } + else + { + (*textPool)->AllocText((char *)text, false, textPool); + } + + return true; +} + + + + + + + + + + + + + + +CGPValue::CGPValue(const char *initName, const char *initValue) : + CGPObject(initName), + mList(0) +{ + if (initValue) + { + AddValue(initValue); + } +} + +CGPValue::~CGPValue(void) +{ + CGPObject *next; + + while(mList) + { + next = mList->GetNext(); + delete mList; + mList = next; + } +} + +CGPValue *CGPValue::Duplicate(CTextPool **textPool) +{ + CGPValue *newValue; + CGPObject *iterator; + char *name; + + if (textPool) + { + name = (*textPool)->AllocText((char *)mName, true, textPool); + } + else + { + name = (char *)mName; + } + + newValue = new CGPValue(name); + iterator = mList; + while(iterator) + { + if (textPool) + { + name = (*textPool)->AllocText((char *)iterator->GetName(), true, textPool); + } + else + { + name = (char *)iterator->GetName(); + } + newValue->AddValue(name); + iterator = iterator->GetNext(); + } + + return newValue; +} + +bool CGPValue::IsList(void) +{ + if (!mList || !mList->GetNext()) + { + return false; + } + + return true; +} + +const char *CGPValue::GetTopValue(void) +{ + if (mList) + { + return mList->GetName(); + } + + return 0; +} + +void CGPValue::AddValue(const char *newValue, CTextPool **textPool) +{ + if (textPool) + { + newValue = (*textPool)->AllocText((char *)newValue, true, textPool); + } + + if (mList == 0) + { + mList = new CGPObject(newValue); + mList->SetInOrderNext(mList); + } + else + { + mList->GetInOrderNext()->SetNext(new CGPObject(newValue)); + mList->SetInOrderNext(mList->GetInOrderNext()->GetNext()); + } +} + +bool CGPValue::Parse(char **dataPtr, CTextPool **textPool) +{ + char *token; + char *value; + + while(1) + { + token = GetToken(dataPtr, true, true); + + if (!token[0]) + { // end of data - error! + return false; + } + else if (strcmpi(token, "]") == 0) + { // ending brace for this list + break; + } + + value = (*textPool)->AllocText(token, true, textPool); + AddValue(value); + } + + return true; +} + +bool CGPValue::Write(CTextPool **textPool, int depth) +{ + int i; + CGPObject *next; + + if (!mList) + { + return true; + } + + for(i=0;iAllocText("\t", false, textPool); + } + + WriteText(textPool, mName); + + if (!mList->GetNext()) + { + (*textPool)->AllocText("\t\t", false, textPool); + mList->WriteText(textPool, mList->GetName()); + (*textPool)->AllocText("\r\n", false, textPool); + } + else + { + (*textPool)->AllocText("\r\n", false, textPool); + + for(i=0;iAllocText("\t", false, textPool); + } + (*textPool)->AllocText("[\r\n", false, textPool); + + next = mList; + while(next) + { + for(i=0;iAllocText("\t", false, textPool); + } + mList->WriteText(textPool, next->GetName()); + (*textPool)->AllocText("\r\n", false, textPool); + + next = next->GetNext(); + } + + for(i=0;iAllocText("\t", false, textPool); + } + (*textPool)->AllocText("]\r\n", false, textPool); + } + + return true; +} + + + + + + + + + + + + + + + + +CGPGroup::CGPGroup(const char *initName, CGPGroup *initParent) : + CGPObject(initName), + mPairs(0), + mInOrderPairs(0), + mCurrentPair(0), + mSubGroups(0), + mInOrderSubGroups(0), + mCurrentSubGroup(0), + mParent(initParent), + mWriteable(false) +{ +} + +CGPGroup::~CGPGroup(void) +{ + Clean(); +} + +int CGPGroup::GetNumSubGroups(void) +{ + int count; + CGPGroup *group; + + count = 0; + group = mSubGroups; + while(group) + { + count++; + group = (CGPGroup *)group->GetNext(); + } + + return(count); +} + +int CGPGroup::GetNumPairs(void) +{ + int count; + CGPValue *pair; + + count = 0; + pair = mPairs; + while(pair) + { + count++; + pair = (CGPValue *)pair->GetNext(); + } + + return(count); +} + +void CGPGroup::Clean(void) +{ + while(mPairs) + { + mCurrentPair = (CGPValue *)mPairs->GetNext(); + delete mPairs; + mPairs = mCurrentPair; + } + + while(mSubGroups) + { + mCurrentSubGroup = (CGPGroup *)mSubGroups->GetNext(); + delete mSubGroups; + mSubGroups = mCurrentSubGroup; + } + + mPairs = mInOrderPairs = mCurrentPair = 0; + mSubGroups = mInOrderSubGroups = mCurrentSubGroup = 0; + mParent = 0; + mWriteable = false; +} + +CGPGroup *CGPGroup::Duplicate(CTextPool **textPool, CGPGroup *initParent) +{ + CGPGroup *newGroup, *subSub, *newSub; + CGPValue *newPair, *subPair; + char *name; + + if (textPool) + { + name = (*textPool)->AllocText((char *)mName, true, textPool); + } + else + { + name = (char *)mName; + } + + newGroup = new CGPGroup(name); + + subSub = mSubGroups; + while(subSub) + { + newSub = subSub->Duplicate(textPool, newGroup); + newGroup->AddGroup(newSub); + + subSub = (CGPGroup *)subSub->GetNext(); + } + + subPair = mPairs; + while(subPair) + { + newPair = subPair->Duplicate(textPool); + newGroup->AddPair(newPair); + + subPair = (CGPValue *)subPair->GetNext(); + } + + return newGroup; +} + +void CGPGroup::SortObject(CGPObject *object, CGPObject **unsortedList, CGPObject **sortedList, + CGPObject **lastObject) +{ + CGPObject *test, *last; + + if (!*unsortedList) + { + *unsortedList = *sortedList = object; + } + else + { + (*lastObject)->SetNext(object); + + test = *sortedList; + last = 0; + while(test) + { + if (strcmpi(object->GetName(), test->GetName()) < 0) + { + break; + } + + last = test; + test = test->GetInOrderNext(); + } + + if (test) + { + test->SetInOrderPrevious(object); + object->SetInOrderNext(test); + } + if (last) + { + last->SetInOrderNext(object); + object->SetInOrderPrevious(last); + } + else + { + *sortedList = object; + } + } + + *lastObject = object; +} + +CGPValue *CGPGroup::AddPair(const char *name, const char *value, CTextPool **textPool) +{ + CGPValue *newPair; + + if (textPool) + { + name = (*textPool)->AllocText((char *)name, true, textPool); + if (value) + { + value = (*textPool)->AllocText((char *)value, true, textPool); + } + } + + newPair = new CGPValue(name, value); + + AddPair(newPair); + + return newPair; +} + +void CGPGroup::AddPair(CGPValue *NewPair) +{ + SortObject(NewPair, (CGPObject **)&mPairs, (CGPObject **)&mInOrderPairs, + (CGPObject **)&mCurrentPair); +} + +CGPGroup *CGPGroup::AddGroup(const char *name, CTextPool **textPool) +{ + CGPGroup *newGroup; + + if (textPool) + { + name = (*textPool)->AllocText((char *)name, true, textPool); + } + + newGroup = new CGPGroup(name); + + AddGroup(newGroup); + + return newGroup; +} + +void CGPGroup::AddGroup(CGPGroup *NewGroup) +{ + SortObject(NewGroup, (CGPObject **)&mSubGroups, (CGPObject **)&mInOrderSubGroups, + (CGPObject **)&mCurrentSubGroup); +} + +CGPGroup *CGPGroup::FindSubGroup(const char *name) +{ + CGPGroup *group; + + group = mSubGroups; + while(group) + { + if(!stricmp(name, group->GetName())) + { + return(group); + } + group = (CGPGroup *)group->GetNext(); + } + return(NULL); +} + +bool CGPGroup::Parse(char **dataPtr, CTextPool **textPool) +{ + char *token; + char lastToken[MAX_TOKEN_SIZE]; + CGPGroup *newSubGroup; + CGPValue *newPair; + + while(1) + { + token = GetToken(dataPtr, true); + + if (!token[0]) + { // end of data - error! + if (mParent) + { + return false; + } + else + { + break; + } + } + else if (strcmpi(token, "}") == 0) + { // ending brace for this group + break; + } + + strcpy(lastToken, token); + + // read ahead to see what we are doing + token = GetToken(dataPtr, true, true); + if (strcmpi(token, "{") == 0) + { // new sub group + newSubGroup = AddGroup(lastToken, textPool); + newSubGroup->SetWriteable(mWriteable); + if (!newSubGroup->Parse(dataPtr, textPool)) + { + return false; + } + } + else if (strcmpi(token, "[") == 0) + { // new pair list + newPair = AddPair(lastToken, 0, textPool); + if (!newPair->Parse(dataPtr, textPool)) + { + return false; + } + } + else + { // new pair + AddPair(lastToken, token, textPool); + } + } + + return true; +} + +bool CGPGroup::Write(CTextPool **textPool, int depth) +{ + int i; + CGPValue *mPair = mPairs; + CGPGroup *mSubGroup = mSubGroups; + + if (depth >= 0) + { + for(i=0;iAllocText("\t", false, textPool); + } + WriteText(textPool, mName); + (*textPool)->AllocText("\r\n", false, textPool); + + for(i=0;iAllocText("\t", false, textPool); + } + (*textPool)->AllocText("{\r\n", false, textPool); + } + + while(mPair) + { + mPair->Write(textPool, depth+1); + mPair = (CGPValue *)mPair->GetNext(); + } + + while(mSubGroup) + { + mSubGroup->Write(textPool, depth+1); + mSubGroup = (CGPGroup *)mSubGroup->GetNext(); + } + + if (depth >= 0) + { + for(i=0;iAllocText("\t", false, textPool); + } + (*textPool)->AllocText("}\r\n", false, textPool); + } + + return true; +} + +CGPValue *CGPGroup::FindPair(const char *key) +{ + CGPValue *pair = mPairs; + + while(pair) + { + if (strcmpi(pair->GetName(), key) == 0) + { + return pair; + } + + pair = pair->GetNext(); + } + + return 0; +} + +const char *CGPGroup::FindPairValue(const char *key, const char *defaultVal) +{ + CGPValue *pair = FindPair(key); + + if (pair) + { + return pair->GetTopValue(); + } + + return defaultVal; +} + + + + + + + + + + + + + + + +CGenericParser2::CGenericParser2(void) : + mTextPool(0), + mWriteable(false) +{ +} + +CGenericParser2::~CGenericParser2(void) +{ + Clean(); +} + +bool CGenericParser2::Parse(char **dataPtr, bool cleanFirst, bool writeable) +{ + CTextPool *topPool; + +#ifdef _XBOX + // Parsers are temporary structures. They exist mainly at load time. + extern void Z_SetNewDeleteTemporary(bool bTemp); + Z_SetNewDeleteTemporary(true); +#endif + + if (cleanFirst) + { + Clean(); + } + + if (!mTextPool) + { + mTextPool = new CTextPool; + } + + SetWriteable(writeable); + mTopLevel.SetWriteable(writeable); + topPool = mTextPool; + bool ret = mTopLevel.Parse(dataPtr, &topPool); + +#ifdef _XBOX + Z_SetNewDeleteTemporary(false); +#endif + + return ret; +} + +void CGenericParser2::Clean(void) +{ + mTopLevel.Clean(); + + CleanTextPool(mTextPool); + mTextPool = 0; +} + +bool CGenericParser2::Write(CTextPool *textPool) +{ + return mTopLevel.Write(&textPool, -1); +} + + + + + + + + + +// The following groups of routines are used for a C interface into GP2. +// C++ users should just use the objects as normally and not call these routines below +// +// CGenericParser2 (void *) routines +TGenericParser2 GP_Parse(char **dataPtr, bool cleanFirst, bool writeable) +{ + CGenericParser2 *parse; + + parse = new CGenericParser2; + if (parse->Parse(dataPtr, cleanFirst, writeable)) + { + return parse; + } + + delete parse; + return 0; +} + +void GP_Clean(TGenericParser2 GP2) +{ + if (!GP2) + { + return; + } + + ((CGenericParser2 *)GP2)->Clean(); +} + +void GP_Delete(TGenericParser2 *GP2) +{ + if (!GP2 || !(*GP2)) + { + return; + } + + delete ((CGenericParser2 *)(*GP2)); + (*GP2) = 0; +} + +TGPGroup GP_GetBaseParseGroup(TGenericParser2 GP2) +{ + if (!GP2) + { + return 0; + } + + return ((CGenericParser2 *)GP2)->GetBaseParseGroup(); +} + + + + +// CGPGroup (void *) routines +const char *GPG_GetName(TGPGroup GPG) +{ + if (!GPG) + { + return ""; + } + + return ((CGPGroup *)GPG)->GetName(); +} + +TGPGroup GPG_GetNext(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetNext(); +} + +TGPGroup GPG_GetInOrderNext(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetInOrderNext(); +} + +TGPGroup GPG_GetInOrderPrevious(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetInOrderPrevious(); +} + +TGPGroup GPG_GetPairs(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetPairs(); +} + +TGPGroup GPG_GetInOrderPairs(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetInOrderPairs(); +} + +TGPGroup GPG_GetSubGroups(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetSubGroups(); +} + +TGPGroup GPG_GetInOrderSubGroups(TGPGroup GPG) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->GetInOrderSubGroups(); +} + +TGPGroup GPG_FindSubGroup(TGPGroup GPG, const char *name) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->FindSubGroup(name); +} + +TGPValue GPG_FindPair(TGPGroup GPG, const char *key) +{ + if (!GPG) + { + return 0; + } + + return ((CGPGroup *)GPG)->FindPair(key); +} + +const char *GPG_FindPairValue(TGPGroup GPG, const char *key, const char *defaultVal) +{ + if (!GPG) + { + return defaultVal; + } + + return ((CGPGroup *)GPG)->FindPairValue(key, defaultVal); +} + + + + +// CGPValue (void *) routines +const char *GPV_GetName(TGPValue GPV) +{ + if (!GPV) + { + return ""; + } + + return ((CGPValue *)GPV)->GetName(); +} + +TGPValue GPV_GetNext(TGPValue GPV) +{ + if (!GPV) + { + return 0; + } + + return ((CGPValue *)GPV)->GetNext(); +} + +TGPValue GPV_GetInOrderNext(TGPValue GPV) +{ + if (!GPV) + { + return 0; + } + + return ((CGPValue *)GPV)->GetInOrderNext(); +} + +TGPValue GPV_GetInOrderPrevious(TGPValue GPV) +{ + if (!GPV) + { + return 0; + } + + return ((CGPValue *)GPV)->GetInOrderPrevious(); +} + +bool GPV_IsList(TGPValue GPV) +{ + if (!GPV) + { + return 0; + } + + return ((CGPValue *)GPV)->IsList(); +} + +const char *GPV_GetTopValue(TGPValue GPV) +{ + if (!GPV) + { + return ""; + } + + return ((CGPValue *)GPV)->GetTopValue(); +} + +TGPValue GPV_GetList(TGPValue GPV) +{ + if (!GPV) + { + return 0; + } + + return ((CGPValue *)GPV)->GetList(); +} + + + +//////////////////// eof ///////////////////// + diff --git a/code/game/genericparser2.h b/code/game/genericparser2.h new file mode 100644 index 0000000..ef55f76 --- /dev/null +++ b/code/game/genericparser2.h @@ -0,0 +1,211 @@ +// Filename:- genericparser2.h + + +#ifndef GENERICPARSER2_H +#define GENERICPARSER2_H + + +// conditional expression is constant +// conversion from int to char, possible loss of data +// unreferenced inline funciton has been removed +#pragma warning( disable : 4127 4244 4514 ) + + +#ifdef DEBUG_LINKING + #pragma message("...including GenericParser2.h") +#endif + +//#include "disablewarnings.h" + +#ifdef _JK2EXE +#define trap_Z_Malloc(x, y) Z_Malloc(x,y,qtrue) +#define trap_Z_Free(x) Z_Free(x) +#else +#define trap_Z_Malloc(x, y) gi.Malloc(x,y,qtrue) +#define trap_Z_Free(x) gi.Free(x) +#endif + + +class CTextPool; +class CGPObject; + +class CTextPool +{ +private: + char *mPool; + CTextPool *mNext; + int mSize, mUsed; + +public: + CTextPool(int initSize = 10240); + ~CTextPool(void); + + CTextPool *GetNext(void) { return mNext; } + void SetNext(CTextPool *which) { mNext = which; } + char *GetPool(void) { return mPool; } + int GetUsed(void) { return mUsed; } + + char *AllocText(char *text, bool addNULL = true, CTextPool **poolPtr = 0); +}; + +void CleanTextPool(CTextPool *pool); + +class CGPObject +{ +protected: + const char *mName; + CGPObject *mNext, *mInOrderNext, *mInOrderPrevious; + +public: + CGPObject(const char *initName); + + const char *GetName(void) { return mName; } + + CGPObject *GetNext(void) { return mNext; } + void SetNext(CGPObject *which) { mNext = which; } + CGPObject *GetInOrderNext(void) { return mInOrderNext; } + void SetInOrderNext(CGPObject *which) { mInOrderNext = which; } + CGPObject *GetInOrderPrevious(void) { return mInOrderPrevious; } + void SetInOrderPrevious(CGPObject *which) { mInOrderPrevious = which; } + + bool WriteText(CTextPool **textPool, const char *text); +}; + + + +class CGPValue : public CGPObject +{ +private: + CGPObject *mList; + +public: + CGPValue(const char *initName, const char *initValue = 0); + ~CGPValue(void); + + CGPValue *GetNext(void) { return (CGPValue *)mNext; } + + CGPValue *Duplicate(CTextPool **textPool = 0); + + bool IsList(void); + const char *GetTopValue(void); + CGPObject *GetList(void) { return mList; } + void AddValue(const char *newValue, CTextPool **textPool = 0); + + bool Parse(char **dataPtr, CTextPool **textPool); + + bool Write(CTextPool **textPool, int depth); +}; + + + +class CGPGroup : public CGPObject +{ +private: + CGPValue *mPairs, *mInOrderPairs; + CGPValue *mCurrentPair; + CGPGroup *mSubGroups, *mInOrderSubGroups; + CGPGroup *mCurrentSubGroup; + CGPGroup *mParent; + bool mWriteable; + + void SortObject(CGPObject *object, CGPObject **unsortedList, CGPObject **sortedList, + CGPObject **lastObject); + +public: + CGPGroup(const char *initName = "Top Level", CGPGroup *initParent = 0); + ~CGPGroup(void); + + CGPGroup *GetParent(void) { return mParent; } + CGPGroup *GetNext(void) { return (CGPGroup *)mNext; } + int GetNumSubGroups(void); + int GetNumPairs(void); + + void Clean(void); + CGPGroup *Duplicate(CTextPool **textPool = 0, CGPGroup *initParent = 0); + + void SetWriteable(const bool writeable) { mWriteable = writeable; } + CGPValue *GetPairs(void) { return mPairs; } + CGPValue *GetInOrderPairs(void) { return mInOrderPairs; } + CGPGroup *GetSubGroups(void) { return mSubGroups; } + CGPGroup *GetInOrderSubGroups(void) { return mInOrderSubGroups; } + + CGPValue *AddPair(const char *name, const char *value, CTextPool **textPool = 0); + void AddPair(CGPValue *NewPair); + CGPGroup *AddGroup(const char *name, CTextPool **textPool = 0); + void AddGroup(CGPGroup *NewGroup); + CGPGroup *FindSubGroup(const char *name); + bool Parse(char **dataPtr, CTextPool **textPool); + bool Write(CTextPool **textPool, int depth); + + CGPValue *FindPair(const char *key); + const char *FindPairValue(const char *key, const char *defaultVal = 0); +}; + +class CGenericParser2 +{ +private: + CGPGroup mTopLevel; + CTextPool *mTextPool; + bool mWriteable; + +public: + CGenericParser2(void); + ~CGenericParser2(void); + + void SetWriteable(const bool writeable) { mWriteable = writeable; } + CGPGroup *GetBaseParseGroup(void) { return &mTopLevel; } + + bool Parse(char **dataPtr, bool cleanFirst = true, bool writeable = false); + bool Parse(char *dataPtr, bool cleanFirst = true, bool writeable = false) + { + return Parse(&dataPtr, cleanFirst, writeable); + } + void Clean(void); + + bool Write(CTextPool *textPool); +}; + + + +// The following groups of routines are used for a C interface into GP2. +// C++ users should just use the objects as normally and not call these routines below +// + +typedef void *TGenericParser2; +typedef void *TGPGroup; +typedef void *TGPValue; + +// CGenericParser2 (void *) routines +TGenericParser2 GP_Parse(char **dataPtr, bool cleanFirst, bool writeable); +void GP_Clean(TGenericParser2 GP2); +void GP_Delete(TGenericParser2 *GP2); +TGPGroup GP_GetBaseParseGroup(TGenericParser2 GP2); + +// CGPGroup (void *) routines +const char *GPG_GetName(TGPGroup GPG); +TGPGroup GPG_GetNext(TGPGroup GPG); +TGPGroup GPG_GetInOrderNext(TGPGroup GPG); +TGPGroup GPG_GetInOrderPrevious(TGPGroup GPG); +TGPGroup GPG_GetPairs(TGPGroup GPG); +TGPGroup GPG_GetInOrderPairs(TGPGroup GPG); +TGPGroup GPG_GetSubGroups(TGPGroup GPG); +TGPGroup GPG_GetInOrderSubGroups(TGPGroup GPG); +TGPGroup GPG_FindSubGroup(TGPGroup GPG, const char *name); +TGPValue GPG_FindPair(TGPGroup GPG, const char *key); +const char *GPG_FindPairValue(TGPGroup GPG, const char *key, const char *defaultVal); + +// CGPValue (void *) routines +const char *GPV_GetName(TGPValue GPV); +TGPValue GPV_GetNext(TGPValue GPV); +TGPValue GPV_GetInOrderNext(TGPValue GPV); +TGPValue GPV_GetInOrderPrevious(TGPValue GPV); +bool GPV_IsList(TGPValue GPV); +const char *GPV_GetTopValue(TGPValue GPV); +TGPValue GPV_GetList(TGPValue GPV); + + +#endif // #ifndef GENERICPARSER2_H + + +//////////////////// eof ///////////////////// + diff --git a/code/game/ghoul2_shared.h b/code/game/ghoul2_shared.h new file mode 100644 index 0000000..628e1ad --- /dev/null +++ b/code/game/ghoul2_shared.h @@ -0,0 +1,495 @@ +#pragma once +#if !defined(GHOUL2_SHARED_H_INC) +#define GHOUL2_SHARED_H_INC + +/* +Ghoul2 Insert Start +*/ +#pragma warning (push, 3) //go back down to 3 for the stl include +#pragma warning (disable:4503) // decorated name length xceeded, name was truncated +#pragma warning(disable:4702) //unreachable code +#include +#include +#pragma warning (pop) +#pragma warning (disable:4503) // decorated name length xceeded, name was truncated +using namespace std; +/* +Ghoul2 Insert End +*/ + +#define G2T_SV_TIME (0) +#define G2T_CG_TIME (1) +#define NUM_G2T_TIME (2) + +void G2API_SetTime(int currentTime,int clock); +int G2API_GetTime(int argTime); // this may or may not return arg depending on ghoul2_time cvar + + +//=================================================================== +// +// G H O U L I I D E F I N E S +// +// we save the whole surfaceInfo_t struct +struct surfaceInfo_t +{ + int offFlags; // what the flags are for this model + int surface; // index into array held inside the model definition of pointers to the actual surface data loaded in - used by both client and game + float genBarycentricJ; // point 0 barycentric coors + float genBarycentricI; // point 1 barycentric coors - point 2 is 1 - point0 - point1 + int genPolySurfaceIndex; // used to point back to the original surface and poly if this is a generated surface + int genLod; // used to determine original lod of original surface and poly hit location + +surfaceInfo_t(): + offFlags(0), + surface(0), + genBarycentricJ(0), + genBarycentricI(0), + genPolySurfaceIndex(0), + genLod(0) + {} + +}; + +#define BONE_ANGLES_PREMULT 0x0001 +#define BONE_ANGLES_POSTMULT 0x0002 +#define BONE_ANGLES_REPLACE 0x0004 +//rww - RAGDOLL_BEGIN +#define BONE_ANGLES_RAGDOLL 0x2000 // the rag flags give more details +#define BONE_ANGLES_IK 0x4000 // the rag flags give more details +//rww - RAGDOLL_END +#define BONE_ANGLES_TOTAL ( BONE_ANGLES_PREMULT | BONE_ANGLES_POSTMULT | BONE_ANGLES_REPLACE ) + +#define BONE_ANIM_OVERRIDE 0x0008 +#define BONE_ANIM_OVERRIDE_LOOP 0x0010 // Causes Last Frame To Lerp to First Frame And Start Over +#define BONE_ANIM_OVERRIDE_FREEZE (0x0040 + BONE_ANIM_OVERRIDE) // Causes Last Frame To Freeze And Not Loop To Beginning +#define BONE_ANIM_BLEND 0x0080 // Blends to and from previously played frame on same bone for given time +#define BONE_ANIM_NO_LERP 0x1000 +#define BONE_ANIM_TOTAL (BONE_ANIM_NO_LERP| BONE_ANIM_OVERRIDE | BONE_ANIM_OVERRIDE_LOOP | BONE_ANIM_OVERRIDE_FREEZE | BONE_ANIM_BLEND ) + +#define BONE_INDEX_INVALID -1 + +/*#define MDXABONEDEF // used in the mdxformat.h file to stop redefinitions of the bone struct. + +typedef struct { + float matrix[3][4]; +} mdxaBone_t; +*/ +#include "../renderer/mdx_format.h" + +// we save the whole structure here. +struct boneInfo_t +{ + int boneNumber; // what bone are we overriding? + mdxaBone_t matrix; // details of bone angle overrides - some are pre-done on the server, some in ghoul2 + int flags; // flags for override + int startFrame; // start frame for animation + int endFrame; // end frame for animation NOTE anim actually ends on endFrame+1 + int startTime; // time we started this animation + int pauseTime; // time we paused this animation - 0 if not paused + float animSpeed; // speed at which this anim runs. 1.0f means full speed of animation incoming - ie if anim is 20hrtz, we run at 20hrts. If 5hrts, we run at 5 hrts + float blendFrame; // frame PLUS LERP value to blend from + int blendLerpFrame; // frame to lerp the blend frame with. + int blendTime; // Duration time for blending - used to calc amount each frame of new anim is blended with last frame of the last anim + int blendStart; // Time when blending starts - not necessarily the same as startTime since we might start half way through an anim + int boneBlendTime; // time for duration of bone angle blend with normal animation + int boneBlendStart; // time bone angle blend with normal animation began + mdxaBone_t newMatrix; // This is the lerped matrix that Ghoul2 uses on the client side - does not go across the network + + //rww - RAGDOLL_BEGIN + int lastTimeUpdated; // if non-zero this is all intialized + int lastContents; + vec3_t lastPosition; + vec3_t velocityEffector; + vec3_t lastAngles; + vec3_t minAngles; + vec3_t maxAngles; + vec3_t currentAngles; + vec3_t anglesOffset; + vec3_t positionOffset; + float radius; + float weight; // current radius cubed + int ragIndex; + vec3_t velocityRoot; // I am really tired of recomiling the whole game to add a param here + int ragStartTime; + int firstTime; + int firstCollisionTime; + int restTime; + int RagFlags; + int DependentRagIndexMask; + mdxaBone_t originalTrueBoneMatrix; + mdxaBone_t parentTrueBoneMatrix; // figure I will need this sooner or later + mdxaBone_t parentOriginalTrueBoneMatrix; // figure I will need this sooner or later + vec3_t originalOrigin; + vec3_t originalAngles; + vec3_t lastShotDir; + mdxaBone_t *basepose; + mdxaBone_t *baseposeInv; + mdxaBone_t *baseposeParent; + mdxaBone_t *baseposeInvParent; + int parentRawBoneIndex; + mdxaBone_t ragOverrideMatrix; // figure I will need this sooner or later + + mdxaBone_t extraMatrix; // figure I will need this sooner or later + vec3_t extraVec1; // I am really tired of recomiling the whole game to add a param here + float extraFloat1; + int extraInt1; + + vec3_t ikPosition; + float ikSpeed; + + //new ragdoll stuff -rww + vec3_t epVelocity; //velocity factor, can be set, and is also maintained by physics based on gravity, mass, etc. + float epGravFactor; //gravity factor maintained by bone physics + int solidCount; //incremented every time we try to move and are in solid - if we get out of solid, it is reset to 0 + bool physicsSettled; //true when the bone is on ground and finished bouncing, etc. but may still be pushed into solid by other bones + bool snapped; //the bone is broken out of standard constraints + + int parentBoneIndex; + + float offsetRotation; + + //user api overrides + float overGradSpeed; + + vec3_t overGoalSpot; + bool hasOverGoal; + + mdxaBone_t animFrameMatrix; //matrix for the bone in the desired settling pose -rww + int hasAnimFrameMatrix; + + int airTime; //base is in air, be more quick and sensitive about collisions + //rww - RAGDOLL_END + +boneInfo_t(): + boneNumber(-1), + flags(0), + startFrame(0), + endFrame(0), + startTime(0), + pauseTime(0), + animSpeed(0), + blendFrame(0), + blendLerpFrame(0), + blendTime(0), + blendStart(0), + boneBlendTime(0), + boneBlendStart(0) + { + matrix.matrix[0][0] = matrix.matrix[0][1] = matrix.matrix[0][2] = matrix.matrix[0][3] = + matrix.matrix[1][0] = matrix.matrix[1][1] = matrix.matrix[1][2] = matrix.matrix[1][3] = + matrix.matrix[2][0] = matrix.matrix[2][1] = matrix.matrix[2][2] = matrix.matrix[2][3] = 0.0f; + } + +}; +//we save from top to boltUsed here. Don't bother saving the position, it gets rebuilt every frame anyway +struct boltInfo_t{ + int boneNumber; // bone number bolt attaches to + int surfaceNumber; // surface number bolt attaches to + int surfaceType; // if we attach to a surface, this tells us if it is an original surface or a generated one - doesn't go across the network + int boltUsed; // nor does this + boltInfo_t(): + boneNumber(-1), + surfaceNumber(-1), + surfaceType(0), + boltUsed(0) + {} +}; + + +#define MAX_GHOUL_COUNT_BITS 8 // bits required to send across the MAX_G2_MODELS inside of the networking - this is the only restriction on ghoul models possible per entity + +typedef vector surfaceInfo_v; +typedef vector boneInfo_v; +typedef vector boltInfo_v; +typedef vector mdxaBone_v; + +// defines for stuff to go into the mflags +#define GHOUL2_NOCOLLIDE 0x001 +#define GHOUL2_NORENDER 0x002 +#define GHOUL2_NOMODEL 0x004 +#define GHOUL2_NEWORIGIN 0x008 + + +// NOTE order in here matters. We save out from mModelindex to mFlags, but not the STL vectors that are at the top or the bottom. +class CBoneCache; +struct model_s; +//struct mdxaHeader_t; + +#ifdef VV_GHOUL_HACKS +class CRenderableSurface +{ +public: + int ident; // ident of this surface - required so the materials renderer knows what sort of surface this refers to + CBoneCache *boneCache; // pointer to transformed bone list for this surf + mdxmSurface_t *surfaceData; // pointer to surface data loaded into file - only used by client renderer DO NOT USE IN GAME SIDE - if there is a vid restart this will be out of wack on the game + +CRenderableSurface(): + ident(8), //SF_MDX + boneCache(0), + surfaceData(0) + {} + +CRenderableSurface(const CRenderableSurface& rs): + ident(rs.ident), + boneCache(rs.boneCache), + surfaceData(rs.surfaceData) + {} +}; +#endif + +class CGhoul2Info +{ +public: + surfaceInfo_v mSlist; + boltInfo_v mBltlist; + boneInfo_v mBlist; +// save from here (do not put any ptrs etc within this save block unless you adds special handlers to G2_SaveGhoul2Models / G2_LoadGhoul2Models!!!!!!!!!!!! +#define BSAVE_START_FIELD mModelindex // this is the start point for loadsave, keep it up to date it you change anything + int mModelindex; + int animModelIndexOffset; + qhandle_t mCustomShader; + qhandle_t mCustomSkin; + int mModelBoltLink; + int mSurfaceRoot; + int mLodBias; + int mNewOrigin; // this contains the bolt index of the new origin for this model +#ifdef _G2_GORE + int mGoreSetTag; +#endif + qhandle_t mModel; // this and the next entries do NOT go across the network. They are for gameside access ONLY + char mFileName[MAX_QPATH]; + int mAnimFrameDefault; + int mSkelFrameNum; + int mMeshFrameNum; + int mFlags; // used for determining whether to do full collision detection against this object +// to here +#define BSAVE_END_FIELD mTransformedVertsArray // this is the end point for loadsave, keep it up to date it you change anything + int *mTransformedVertsArray; // used to create an array of pointers to transformed verts per surface for collision detection + CBoneCache *mBoneCache; + int mSkin; + + // these occasionally are not valid (like after a vid_restart) + // call the questionably efficient G2_SetupModelPointers(this) to insure validity + bool mValid; // all the below are proper and valid + const model_s *currentModel; + int currentModelSize; + const model_s *animModel; + int currentAnimModelSize; + const mdxaHeader_t *aHeader; + + CGhoul2Info(): + mModelindex(-1), + mCustomShader(0), + mCustomSkin(0), + mModelBoltLink(0), + mModel(0), + mSurfaceRoot(0), + mAnimFrameDefault(0), + mSkelFrameNum(-1), + mMeshFrameNum(-1), + mFlags(0), + mTransformedVertsArray(0), + mLodBias(0), + mSkin(0), + mNewOrigin(-1), +#ifdef _G2_GORE + mGoreSetTag(0), +#endif + mBoneCache(0), + currentModel(0), + currentModelSize(0), + animModel(0), + animModelIndexOffset(0), + currentAnimModelSize(0), + aHeader(0), + mValid(false) + { + mFileName[0] = 0; + } +}; + +class CGhoul2Info_v; + +class IGhoul2InfoArray +{ +public: + virtual int New()=0; + virtual void Delete(int handle)=0; + virtual bool IsValid(int handle) const=0; + virtual vector &Get(int handle)=0; + virtual const vector &Get(int handle) const=0; +}; + +IGhoul2InfoArray &TheGhoul2InfoArray(); +IGhoul2InfoArray &TheGameGhoul2InfoArray(); + +class CGhoul2Info_v +{ + int mItem; + + IGhoul2InfoArray &InfoArray() const + { +#ifdef _JK2EXE + return TheGhoul2InfoArray(); +#else + return TheGameGhoul2InfoArray(); +#endif + } + + void Alloc() + { + assert(!mItem); //already alloced + mItem=InfoArray().New(); + assert(!Array().size()); + } + void Free() + { + if (mItem) + { + assert(InfoArray().IsValid(mItem)); + InfoArray().Delete(mItem); + mItem=0; + } + } + vector &Array() + { + assert(InfoArray().IsValid(mItem)); + return InfoArray().Get(mItem); + } + const vector &Array() const + { + assert(InfoArray().IsValid(mItem)); + return InfoArray().Get(mItem); + } +public: + CGhoul2Info_v() + { + mItem=0; + } + ~CGhoul2Info_v() + { + Free(); //this had better be taken care of via the clean ghoul2 models call + } + void operator=(const CGhoul2Info_v &other) + { + mItem=other.mItem; + } + void DeepCopy(const CGhoul2Info_v &other) + { + Free(); + if (other.mItem) + { + Alloc(); + Array()=other.Array(); + int i; + for (i=0;i=0&&idx=0&&idx=0); + if (num) + { + if (!mItem) + { + Alloc(); + } + } + if (mItem||num) + { + Array().resize(num); + } + } + void clear() + { + Free(); + } + void push_back(const CGhoul2Info &model) + { + if (!mItem) + { + Alloc(); + } + Array().push_back(model); + } + int size() const + { + if (!IsValid()) + { + return 0; + } + return Array().size(); + } + bool IsValid() const + { + return InfoArray().IsValid(mItem); + } + void kill() + { + // this scary method zeros the infovector handle without actually freeing it + // it is used for some places where a copy is made, but we don't want to go through the trouble + // of making a deep copy + mItem=0; + } +}; + + + +// collision detection stuff +#define G2_FRONTFACE 1 +#define G2_BACKFACE 0 + + +class CCollisionRecord +{ +public: + float mDistance; + int mEntityNum; + int mModelIndex; + int mPolyIndex; + int mSurfaceIndex; + vec3_t mCollisionPosition; + vec3_t mCollisionNormal; + int mFlags; + int mMaterial; + int mLocation; + float mBarycentricI; // two barycentic coodinates for the hit point + float mBarycentricJ; // K = 1-I-J + + CCollisionRecord(): + mEntityNum(-1), + mDistance(100000) + {} +}; + +// calling defines for the trace function +enum EG2_Collision +{ + G2_NOCOLLIDE, + G2_COLLIDE, + G2_RETURNONHIT +}; + + + +//==================================================================== + +#endif // GHOUL2_SHARED_H_INC \ No newline at end of file diff --git a/code/game/hitlocs.h b/code/game/hitlocs.h new file mode 100644 index 0000000..cc39a81 --- /dev/null +++ b/code/game/hitlocs.h @@ -0,0 +1,35 @@ +#ifndef HITLOCS_H +#define HITLOCS_H + +typedef enum //# hitloc_e +{ + HL_NONE = 0, + HL_FOOT_RT, + HL_FOOT_LT, + HL_LEG_RT, + HL_LEG_LT, + HL_WAIST, + HL_BACK_RT, + HL_BACK_LT, + HL_BACK, + HL_CHEST_RT, + HL_CHEST_LT, + HL_CHEST, + HL_ARM_RT, + HL_ARM_LT, + HL_HAND_RT, + HL_HAND_LT, + HL_HEAD, + HL_GENERIC1, + HL_GENERIC2, + HL_GENERIC3, + HL_GENERIC4, + HL_GENERIC5, + HL_GENERIC6, + //# #eol + HL_MAX +}; + +extern stringID_table_t HLTable[]; + +#endif // #ifndef HITLOCS_H diff --git a/code/game/npc_headers.h b/code/game/npc_headers.h new file mode 100644 index 0000000..3eb7f50 --- /dev/null +++ b/code/game/npc_headers.h @@ -0,0 +1,7 @@ +// PCH header file organiser for NPC_xxxx cpp files' most commonly used headers + +#include "b_local.h" +#include "anims.h" +#include "g_functions.h" +#include "g_nav.h" +#include "g_navigator.h" diff --git a/code/game/objectives.h b/code/game/objectives.h new file mode 100644 index 0000000..894ddd9 --- /dev/null +++ b/code/game/objectives.h @@ -0,0 +1,341 @@ +#ifndef __OBJECTIVES_H__ +#define __OBJECTIVES_H__ + +// mission Objectives + + +// DO NOT CHANGE MAX_MISSION_OBJ. IT AFFECTS THE SAVEGAME STRUCTURE + +typedef enum //# Objective_e +{ + //================================================= + // + //================================================= + + LIGHTSIDE_OBJ = 0, + HOTH2_OBJ1, + HOTH2_OBJ2, + HOTH2_OBJ3, + HOTH3_OBJ1, + HOTH3_OBJ2, + HOTH3_OBJ3, + T2_DPREDICAMENT_OBJ1, + T2_DPREDICAMENT_OBJ2, + T2_DPREDICAMENT_OBJ3, + T2_DPREDICAMENT_OBJ4, + T2_RANCOR_OBJ1, + T2_RANCOR_OBJ2, + T2_RANCOR_OBJ3, + T2_RANCOR_OBJ4, + T2_RANCOR_OBJ5, + T2_RANCOR_OBJ5_2, + T2_RANCOR_OBJ6, + T2_WEDGE_OBJ1, + T2_WEDGE_OBJ2, + T2_WEDGE_OBJ3, + T2_WEDGE_OBJ4, + T2_WEDGE_OBJ5, + T2_WEDGE_OBJ6, + T2_WEDGE_OBJ7, + T2_WEDGE_OBJ8, + T2_WEDGE_OBJ9, + T2_WEDGE_OBJ10, + T2_WEDGE_OBJ11, + T2_WEDGE_OBJ12, + T3_RIFT_OBJ1, + T3_RIFT_OBJ2, + T3_RIFT_OBJ3, + T1_DANGER_OBJ1, + T1_DANGER_OBJ2, + T1_DANGER_OBJ3, + T1_DANGER_OBJ4, + T1_DANGER_OBJ5, + T3_BOUNTY_OBJ1, + T3_BOUNTY_OBJ2, + T3_BOUNTY_OBJ3, + T3_BOUNTY_OBJ4, + T3_BOUNTY_OBJ5, + T3_BOUNTY_OBJ6, + T3_BOUNTY_OBJ7, + T3_BOUNTY_OBJ8, + T3_BOUNTY_OBJ9, + T2_ROGUE_OBJ1, + T2_ROGUE_OBJ2, + T2_TRIP_OBJ1, + T2_TRIP_OBJ2, + T3_BYSS_OBJ1, + T3_BYSS_OBJ2, + T3_BYSS_OBJ3, + T3_HEVIL_OBJ1, + T3_HEVIL_OBJ2, + T3_HEVIL_OBJ3, + T3_STAMP_OBJ1, + T3_STAMP_OBJ2, + T3_STAMP_OBJ3, + T3_STAMP_OBJ4, + TASPIR1_OBJ1, + TASPIR1_OBJ2, + TASPIR1_OBJ3, + TASPIR1_OBJ4, + TASPIR2_OBJ1, + TASPIR2_OBJ2, + VJUN1_OBJ1, + VJUN1_OBJ2, + VJUN2_OBJ1, + VJUN3_OBJ1, + YAVIN1_OBJ1, + YAVIN1_OBJ2, + YAVIN2_OBJ1, + T1_FATAL_OBJ1, + T1_FATAL_OBJ2, + T1_FATAL_OBJ3, + T1_FATAL_OBJ4, + T1_FATAL_OBJ5, + T1_FATAL_OBJ6, + KOR1_OBJ1, + KOR1_OBJ2, + KOR2_OBJ1, + KOR2_OBJ2, + KOR2_OBJ3, + KOR2_OBJ4, + T1_RAIL_OBJ1, + T1_RAIL_OBJ2, + T1_RAIL_OBJ3, + T1_SOUR_OBJ1, + T1_SOUR_OBJ2, + T1_SOUR_OBJ3, + T1_SOUR_OBJ4, + T1_SURPRISE_OBJ1, + T1_SURPRISE_OBJ2, + T1_SURPRISE_OBJ3, + T1_SURPRISE_OBJ4, + + //# #eol + MAX_OBJECTIVES, +} objectiveNumber_t; + + +typedef enum //# MissionFailed_e +{ + MISSIONFAILED_JAN=0, //# + MISSIONFAILED_LUKE, //# + MISSIONFAILED_LANDO, //# + MISSIONFAILED_R5D2, //# + MISSIONFAILED_WARDEN, //# + MISSIONFAILED_PRISONERS, //# + MISSIONFAILED_EMPLACEDGUNS, //# + MISSIONFAILED_LADYLUCK, //# + MISSIONFAILED_KYLECAPTURE, //# + MISSIONFAILED_TOOMANYALLIESDIED, //# + MISSIONFAILED_CHEWIE, //# + MISSIONFAILED_KYLE, //# + MISSIONFAILED_ROSH, //# + MISSIONFAILED_WEDGE, //# + MISSIONFAILED_TURNED, //# Turned on your friends. + + //# #eol + MAX_MISSIONFAILED, +} missionFailed_t; + + +typedef enum //# StatusText_e +{ + //================================================= + // + //================================================= + STAT_INSUBORDINATION = 0, //# Starfleet will not tolerate such insubordination + STAT_YOUCAUSEDDEATHOFTEAMMATE, //# You caused the death of a teammate. + STAT_DIDNTPROTECTTECH, //# You failed to protect Chell, your technician. + STAT_DIDNTPROTECT7OF9, //# You failed to protect 7 of 9 + STAT_NOTSTEALTHYENOUGH, //# You weren't quite stealthy enough + STAT_STEALTHTACTICSNECESSARY, //# Starfleet will not tolerate such insubordination + STAT_WATCHYOURSTEP, //# Watch your step + STAT_JUDGEMENTMUCHDESIRED, //# Your judgement leaves much to be desired + + //# #eol + MAX_STATUSTEXT, +} statusText_t; + +extern qboolean missionInfo_Updated; + +#define SET_TACTICAL_OFF 0 +#define SET_TACTICAL_ON 1 + +#define SET_OBJ_HIDE 0 +#define SET_OBJ_SHOW 1 +#define SET_OBJ_PENDING 2 +#define SET_OBJ_SUCCEEDED 3 +#define SET_OBJ_FAILED 4 + +#define OBJECTIVE_HIDE 0 +#define OBJECTIVE_SHOW 1 + +#define OBJECTIVE_STAT_PENDING 0 +#define OBJECTIVE_STAT_SUCCEEDED 1 +#define OBJECTIVE_STAT_FAILED 2 + +extern int statusTextIndex; + +void OBJ_SaveObjectiveData(void); +void OBJ_LoadObjectiveData(void); +extern void OBJ_SetPendingObjectives(gentity_t *ent); + +#ifndef G_OBJECTIVES_CPP + +extern stringID_table_t objectiveTable []; +extern stringID_table_t statusTextTable []; +extern stringID_table_t missionFailedTable []; + +#else + +stringID_table_t objectiveTable [] = +{ + //================================================= + // + //================================================= + ENUM2STRING(LIGHTSIDE_OBJ), + ENUM2STRING(HOTH2_OBJ1), + ENUM2STRING(HOTH2_OBJ2), + ENUM2STRING(HOTH2_OBJ3), + ENUM2STRING(HOTH3_OBJ1), + ENUM2STRING(HOTH3_OBJ2), + ENUM2STRING(HOTH3_OBJ3), + ENUM2STRING(T2_DPREDICAMENT_OBJ1), + ENUM2STRING(T2_DPREDICAMENT_OBJ2), + ENUM2STRING(T2_DPREDICAMENT_OBJ3), + ENUM2STRING(T2_DPREDICAMENT_OBJ4), + ENUM2STRING(T2_RANCOR_OBJ1), + ENUM2STRING(T2_RANCOR_OBJ2), + ENUM2STRING(T2_RANCOR_OBJ3), + ENUM2STRING(T2_RANCOR_OBJ4), + ENUM2STRING(T2_RANCOR_OBJ5), + ENUM2STRING(T2_RANCOR_OBJ5_2), + ENUM2STRING(T2_RANCOR_OBJ6), + ENUM2STRING(T2_WEDGE_OBJ1), + ENUM2STRING(T2_WEDGE_OBJ2), + ENUM2STRING(T2_WEDGE_OBJ3), + ENUM2STRING(T2_WEDGE_OBJ4), + ENUM2STRING(T2_WEDGE_OBJ5), + ENUM2STRING(T2_WEDGE_OBJ6), + ENUM2STRING(T2_WEDGE_OBJ7), + ENUM2STRING(T2_WEDGE_OBJ8), + ENUM2STRING(T2_WEDGE_OBJ9), + ENUM2STRING(T2_WEDGE_OBJ10), + ENUM2STRING(T2_WEDGE_OBJ11), + ENUM2STRING(T2_WEDGE_OBJ12), + ENUM2STRING(T3_RIFT_OBJ1), + ENUM2STRING(T3_RIFT_OBJ2), + ENUM2STRING(T3_RIFT_OBJ3), + ENUM2STRING(T1_DANGER_OBJ1), + ENUM2STRING(T1_DANGER_OBJ2), + ENUM2STRING(T1_DANGER_OBJ3), + ENUM2STRING(T1_DANGER_OBJ4), + ENUM2STRING(T1_DANGER_OBJ5), + ENUM2STRING(T3_BOUNTY_OBJ1), + ENUM2STRING(T3_BOUNTY_OBJ2), + ENUM2STRING(T3_BOUNTY_OBJ3), + ENUM2STRING(T3_BOUNTY_OBJ4), + ENUM2STRING(T3_BOUNTY_OBJ5), + ENUM2STRING(T3_BOUNTY_OBJ6), + ENUM2STRING(T3_BOUNTY_OBJ7), + ENUM2STRING(T3_BOUNTY_OBJ8), + ENUM2STRING(T3_BOUNTY_OBJ9), + ENUM2STRING(T2_ROGUE_OBJ1), + ENUM2STRING(T2_ROGUE_OBJ2), + ENUM2STRING(T2_TRIP_OBJ1), + ENUM2STRING(T2_TRIP_OBJ2), + ENUM2STRING(T3_BYSS_OBJ1), + ENUM2STRING(T3_BYSS_OBJ2), + ENUM2STRING(T3_BYSS_OBJ3), + ENUM2STRING(T3_HEVIL_OBJ1), + ENUM2STRING(T3_HEVIL_OBJ2), + ENUM2STRING(T3_HEVIL_OBJ3), + ENUM2STRING(T3_STAMP_OBJ1), + ENUM2STRING(T3_STAMP_OBJ2), + ENUM2STRING(T3_STAMP_OBJ3), + ENUM2STRING(T3_STAMP_OBJ4), + ENUM2STRING(TASPIR1_OBJ1), + ENUM2STRING(TASPIR1_OBJ2), + ENUM2STRING(TASPIR1_OBJ3), + ENUM2STRING(TASPIR1_OBJ4), + ENUM2STRING(TASPIR2_OBJ1), + ENUM2STRING(TASPIR2_OBJ2), + ENUM2STRING(VJUN1_OBJ1), + ENUM2STRING(VJUN1_OBJ2), + ENUM2STRING(VJUN2_OBJ1), + ENUM2STRING(VJUN3_OBJ1), + ENUM2STRING(YAVIN1_OBJ1), + ENUM2STRING(YAVIN1_OBJ2), + ENUM2STRING(YAVIN2_OBJ1), + ENUM2STRING(T1_FATAL_OBJ1), + ENUM2STRING(T1_FATAL_OBJ2), + ENUM2STRING(T1_FATAL_OBJ3), + ENUM2STRING(T1_FATAL_OBJ4), + ENUM2STRING(T1_FATAL_OBJ5), + ENUM2STRING(T1_FATAL_OBJ6), + ENUM2STRING(KOR1_OBJ1), + ENUM2STRING(KOR1_OBJ2), + ENUM2STRING(KOR2_OBJ1), + ENUM2STRING(KOR2_OBJ2), + ENUM2STRING(KOR2_OBJ3), + ENUM2STRING(KOR2_OBJ4), + ENUM2STRING(T1_RAIL_OBJ1), + ENUM2STRING(T1_RAIL_OBJ2), + ENUM2STRING(T1_RAIL_OBJ3), + ENUM2STRING(T1_SOUR_OBJ1), + ENUM2STRING(T1_SOUR_OBJ2), + ENUM2STRING(T1_SOUR_OBJ3), + ENUM2STRING(T1_SOUR_OBJ4), + ENUM2STRING(T1_SURPRISE_OBJ1), + ENUM2STRING(T1_SURPRISE_OBJ2), + ENUM2STRING(T1_SURPRISE_OBJ3), + ENUM2STRING(T1_SURPRISE_OBJ4), + + //stringID_table_t Must end with a null entry + "", NULL +}; + +stringID_table_t missionFailedTable [] = +{ + ENUM2STRING(MISSIONFAILED_JAN), //# JAN DIED + ENUM2STRING(MISSIONFAILED_LUKE), //# LUKE DIED + ENUM2STRING(MISSIONFAILED_LANDO), //# LANDO DIED + ENUM2STRING(MISSIONFAILED_R5D2), //# R5D2 DIED + ENUM2STRING(MISSIONFAILED_WARDEN), //# THE WARDEN DIED + ENUM2STRING(MISSIONFAILED_PRISONERS), //# TOO MANY PRISONERS DIED + ENUM2STRING(MISSIONFAILED_EMPLACEDGUNS),//# ALL EMPLACED GUNS GONE + ENUM2STRING(MISSIONFAILED_LADYLUCK), //# LADY LUCK DISTROYED + ENUM2STRING(MISSIONFAILED_KYLECAPTURE), //# KYLE HAS BEEN CAPTURED + ENUM2STRING(MISSIONFAILED_TOOMANYALLIESDIED), //# TOO MANY ALLIES DIED + ENUM2STRING(MISSIONFAILED_CHEWIE), + ENUM2STRING(MISSIONFAILED_KYLE), + ENUM2STRING(MISSIONFAILED_ROSH), + ENUM2STRING(MISSIONFAILED_WEDGE), + ENUM2STRING(MISSIONFAILED_TURNED), //# Turned on your friends. + + //stringID_table_t Must end with a null entry + "", NULL +}; + +stringID_table_t statusTextTable [] = +{ + //================================================= + // + //================================================= + ENUM2STRING(STAT_INSUBORDINATION), //# Starfleet will not tolerate such insubordination + ENUM2STRING(STAT_YOUCAUSEDDEATHOFTEAMMATE), //# You caused the death of a teammate. + ENUM2STRING(STAT_DIDNTPROTECTTECH), //# You failed to protect Chell, your technician. + ENUM2STRING(STAT_DIDNTPROTECT7OF9), //# You failed to protect 7 of 9 + ENUM2STRING(STAT_NOTSTEALTHYENOUGH), //# You weren't quite stealthy enough + ENUM2STRING(STAT_STEALTHTACTICSNECESSARY), //# Starfleet will not tolerate such insubordination + ENUM2STRING(STAT_WATCHYOURSTEP), //# Watch your step + ENUM2STRING(STAT_JUDGEMENTMUCHDESIRED), //# Your judgement leaves much to be desired + //stringID_table_t Must end with a null entry + "", NULL +}; + +#endif// #ifndef G_OBJECTIVES_CPP + + +#endif// #ifndef __OBJECTIVES_H__ + diff --git a/code/game/q_math.cpp b/code/game/q_math.cpp new file mode 100644 index 0000000..da02c73 --- /dev/null +++ b/code/game/q_math.cpp @@ -0,0 +1,1273 @@ +// q_math.c -- stateless support routines that are included in each code module + +// leave this at the top for PCH reasons... +#include "common_headers.h" + + +//#include "q_shared.h" + + +const vec3_t vec3_origin = {0,0,0}; +const vec3_t axisDefault[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + +vec4_t colorTable[CT_MAX] = +{ +{0, 0, 0, 0}, // CT_NONE +{0, 0, 0, 1}, // CT_BLACK +{1, 0, 0, 1}, // CT_RED +{0, 1, 0, 1}, // CT_GREEN +{0, 0, 1, 1}, // CT_BLUE +{1, 1, 0, 1}, // CT_YELLOW +{1, 0, 1, 1}, // CT_MAGENTA +{0, 1, 1, 1}, // CT_CYAN +{1, 1, 1, 1}, // CT_WHITE +{0.75f, 0.75f, 0.75f, 1}, // CT_LTGREY +{0.50f, 0.50f, 0.50f, 1}, // CT_MDGREY +{0.25f, 0.25f, 0.25f, 1}, // CT_DKGREY +{0.15f, 0.15f, 0.15f, 1}, // CT_DKGREY2 + +{0.992f, 0.652f, 0.0f, 1}, // CT_VLTORANGE -- needs values +{0.810f, 0.530f, 0.0f, 1}, // CT_LTORANGE +{0.610f, 0.330f, 0.0f, 1}, // CT_DKORANGE +{0.402f, 0.265f, 0.0f, 1}, // CT_VDKORANGE + +{0.503f, 0.375f, 0.996f, 1}, // CT_VLTBLUE1 +{0.367f, 0.261f, 0.722f, 1}, // CT_LTBLUE1 +{0.199f, 0.0f, 0.398f, 1}, // CT_DKBLUE1 +{0.160f, 0.117f, 0.324f, 1}, // CT_VDKBLUE1 + +{0.300f, 0.628f, 0.816f, 1}, // CT_VLTBLUE2 -- needs values +{0.300f, 0.628f, 0.816f, 1}, // CT_LTBLUE2 +{0.191f, 0.289f, 0.457f, 1}, // CT_DKBLUE2 +{0.125f, 0.250f, 0.324f, 1}, // CT_VDKBLUE2 + +{0.796f, 0.398f, 0.199f, 1}, // CT_VLTBROWN1 -- needs values +{0.796f, 0.398f, 0.199f, 1}, // CT_LTBROWN1 +{0.558f, 0.207f, 0.027f, 1}, // CT_DKBROWN1 +{0.328f, 0.125f, 0.035f, 1}, // CT_VDKBROWN1 + +{0.996f, 0.796f, 0.398f, 1}, // CT_VLTGOLD1 -- needs values +{0.996f, 0.796f, 0.398f, 1}, // CT_LTGOLD1 +{0.605f, 0.441f, 0.113f, 1}, // CT_DKGOLD1 +{0.386f, 0.308f, 0.148f, 1}, // CT_VDKGOLD1 + +{0.648f, 0.562f, 0.784f, 1}, // CT_VLTPURPLE1 -- needs values +{0.648f, 0.562f, 0.784f, 1}, // CT_LTPURPLE1 +{0.437f, 0.335f, 0.597f, 1}, // CT_DKPURPLE1 +{0.308f, 0.269f, 0.375f, 1}, // CT_VDKPURPLE1 + +{0.816f, 0.531f, 0.710f, 1}, // CT_VLTPURPLE2 -- needs values +{0.816f, 0.531f, 0.710f, 1}, // CT_LTPURPLE2 +{0.566f, 0.269f, 0.457f, 1}, // CT_DKPURPLE2 +{0.343f, 0.226f, 0.316f, 1}, // CT_VDKPURPLE2 + +{0.929f, 0.597f, 0.929f, 1}, // CT_VLTPURPLE3 +{0.570f, 0.371f, 0.570f, 1}, // CT_LTPURPLE3 +{0.355f, 0.199f, 0.355f, 1}, // CT_DKPURPLE3 +{0.285f, 0.136f, 0.230f, 1}, // CT_VDKPURPLE3 + +{0.953f, 0.378f, 0.250f, 1}, // CT_VLTRED1 +{0.953f, 0.378f, 0.250f, 1}, // CT_LTRED1 +{0.593f, 0.121f, 0.109f, 1}, // CT_DKRED1 +{0.429f, 0.171f, 0.113f, 1}, // CT_VDKRED1 +{.25f, 0, 0, 1}, // CT_VDKRED +{.70f, 0, 0, 1}, // CT_DKRED + +{0.717f, 0.902f, 1.0f, 1}, // CT_VLTAQUA +{0.574f, 0.722f, 0.804f, 1}, // CT_LTAQUA +{0.287f, 0.361f, 0.402f, 1}, // CT_DKAQUA +{0.143f, 0.180f, 0.201f, 1}, // CT_VDKAQUA + +{0.871f, 0.386f, 0.375f, 1}, // CT_LTPINK +{0.435f, 0.193f, 0.187f, 1}, // CT_DKPINK +{ 0, .5f, .5f, 1}, // CT_LTCYAN +{ 0, .25f, .25f, 1}, // CT_DKCYAN +{ .179f, .51f, .92f, 1}, // CT_LTBLUE3 +{ .199f, .71f, .92f, 1}, // CT_LTBLUE3 +{ .5f, .05f, .4f, 1}, // CT_DKBLUE3 + +{ 0.0f, .613f, .097f, 1}, // CT_HUD_GREEN +{ 0.835f, .015f, .015f, 1}, // CT_HUD_RED +{ .567f, .685f, 1.0f, .75f}, // CT_ICON_BLUE +{ .515f, .406f, .507f, 1}, // CT_NO_AMMO_RED + +{ 1.0f, .658f, .062f, 1}, // CT_HUD_ORANGE +{ 0.549f, .854f, 1.0f, 1.0f}, // CT_TITLE +}; + + +vec4_t g_color_table[8] = + { + {0.0, 0.0, 0.0, 1.0}, + {1.0, 0.0, 0.0, 1.0}, + {0.0, 1.0, 0.0, 1.0}, + {1.0, 1.0, 0.0, 1.0}, + {0.0, 0.0, 1.0, 1.0}, + {0.0, 1.0, 1.0, 1.0}, + {1.0, 0.0, 1.0, 1.0}, + {1.0, 1.0, 1.0, 1.0}, + }; + +#pragma warning(disable : 4305) // truncation from const double to float + +vec3_t bytedirs[NUMVERTEXNORMALS] = +{ +{-0.525731, 0.000000, 0.850651}, {-0.442863, 0.238856, 0.864188}, +{-0.295242, 0.000000, 0.955423}, {-0.309017, 0.500000, 0.809017}, +{-0.162460, 0.262866, 0.951056}, {0.000000, 0.000000, 1.000000}, +{0.000000, 0.850651, 0.525731}, {-0.147621, 0.716567, 0.681718}, +{0.147621, 0.716567, 0.681718}, {0.000000, 0.525731, 0.850651}, +{0.309017, 0.500000, 0.809017}, {0.525731, 0.000000, 0.850651}, +{0.295242, 0.000000, 0.955423}, {0.442863, 0.238856, 0.864188}, +{0.162460, 0.262866, 0.951056}, {-0.681718, 0.147621, 0.716567}, +{-0.809017, 0.309017, 0.500000},{-0.587785, 0.425325, 0.688191}, +{-0.850651, 0.525731, 0.000000},{-0.864188, 0.442863, 0.238856}, +{-0.716567, 0.681718, 0.147621},{-0.688191, 0.587785, 0.425325}, +{-0.500000, 0.809017, 0.309017}, {-0.238856, 0.864188, 0.442863}, +{-0.425325, 0.688191, 0.587785}, {-0.716567, 0.681718, -0.147621}, +{-0.500000, 0.809017, -0.309017}, {-0.525731, 0.850651, 0.000000}, +{0.000000, 0.850651, -0.525731}, {-0.238856, 0.864188, -0.442863}, +{0.000000, 0.955423, -0.295242}, {-0.262866, 0.951056, -0.162460}, +{0.000000, 1.000000, 0.000000}, {0.000000, 0.955423, 0.295242}, +{-0.262866, 0.951056, 0.162460}, {0.238856, 0.864188, 0.442863}, +{0.262866, 0.951056, 0.162460}, {0.500000, 0.809017, 0.309017}, +{0.238856, 0.864188, -0.442863},{0.262866, 0.951056, -0.162460}, +{0.500000, 0.809017, -0.309017},{0.850651, 0.525731, 0.000000}, +{0.716567, 0.681718, 0.147621}, {0.716567, 0.681718, -0.147621}, +{0.525731, 0.850651, 0.000000}, {0.425325, 0.688191, 0.587785}, +{0.864188, 0.442863, 0.238856}, {0.688191, 0.587785, 0.425325}, +{0.809017, 0.309017, 0.500000}, {0.681718, 0.147621, 0.716567}, +{0.587785, 0.425325, 0.688191}, {0.955423, 0.295242, 0.000000}, +{1.000000, 0.000000, 0.000000}, {0.951056, 0.162460, 0.262866}, +{0.850651, -0.525731, 0.000000},{0.955423, -0.295242, 0.000000}, +{0.864188, -0.442863, 0.238856}, {0.951056, -0.162460, 0.262866}, +{0.809017, -0.309017, 0.500000}, {0.681718, -0.147621, 0.716567}, +{0.850651, 0.000000, 0.525731}, {0.864188, 0.442863, -0.238856}, +{0.809017, 0.309017, -0.500000}, {0.951056, 0.162460, -0.262866}, +{0.525731, 0.000000, -0.850651}, {0.681718, 0.147621, -0.716567}, +{0.681718, -0.147621, -0.716567},{0.850651, 0.000000, -0.525731}, +{0.809017, -0.309017, -0.500000}, {0.864188, -0.442863, -0.238856}, +{0.951056, -0.162460, -0.262866}, {0.147621, 0.716567, -0.681718}, +{0.309017, 0.500000, -0.809017}, {0.425325, 0.688191, -0.587785}, +{0.442863, 0.238856, -0.864188}, {0.587785, 0.425325, -0.688191}, +{0.688191, 0.587785, -0.425325}, {-0.147621, 0.716567, -0.681718}, +{-0.309017, 0.500000, -0.809017}, {0.000000, 0.525731, -0.850651}, +{-0.525731, 0.000000, -0.850651}, {-0.442863, 0.238856, -0.864188}, +{-0.295242, 0.000000, -0.955423}, {-0.162460, 0.262866, -0.951056}, +{0.000000, 0.000000, -1.000000}, {0.295242, 0.000000, -0.955423}, +{0.162460, 0.262866, -0.951056}, {-0.442863, -0.238856, -0.864188}, +{-0.309017, -0.500000, -0.809017}, {-0.162460, -0.262866, -0.951056}, +{0.000000, -0.850651, -0.525731}, {-0.147621, -0.716567, -0.681718}, +{0.147621, -0.716567, -0.681718}, {0.000000, -0.525731, -0.850651}, +{0.309017, -0.500000, -0.809017}, {0.442863, -0.238856, -0.864188}, +{0.162460, -0.262866, -0.951056}, {0.238856, -0.864188, -0.442863}, +{0.500000, -0.809017, -0.309017}, {0.425325, -0.688191, -0.587785}, +{0.716567, -0.681718, -0.147621}, {0.688191, -0.587785, -0.425325}, +{0.587785, -0.425325, -0.688191}, {0.000000, -0.955423, -0.295242}, +{0.000000, -1.000000, 0.000000}, {0.262866, -0.951056, -0.162460}, +{0.000000, -0.850651, 0.525731}, {0.000000, -0.955423, 0.295242}, +{0.238856, -0.864188, 0.442863}, {0.262866, -0.951056, 0.162460}, +{0.500000, -0.809017, 0.309017}, {0.716567, -0.681718, 0.147621}, +{0.525731, -0.850651, 0.000000}, {-0.238856, -0.864188, -0.442863}, +{-0.500000, -0.809017, -0.309017}, {-0.262866, -0.951056, -0.162460}, +{-0.850651, -0.525731, 0.000000}, {-0.716567, -0.681718, -0.147621}, +{-0.716567, -0.681718, 0.147621}, {-0.525731, -0.850651, 0.000000}, +{-0.500000, -0.809017, 0.309017}, {-0.238856, -0.864188, 0.442863}, +{-0.262866, -0.951056, 0.162460}, {-0.864188, -0.442863, 0.238856}, +{-0.809017, -0.309017, 0.500000}, {-0.688191, -0.587785, 0.425325}, +{-0.681718, -0.147621, 0.716567}, {-0.442863, -0.238856, 0.864188}, +{-0.587785, -0.425325, 0.688191}, {-0.309017, -0.500000, 0.809017}, +{-0.147621, -0.716567, 0.681718}, {-0.425325, -0.688191, 0.587785}, +{-0.162460, -0.262866, 0.951056}, {0.442863, -0.238856, 0.864188}, +{0.162460, -0.262866, 0.951056}, {0.309017, -0.500000, 0.809017}, +{0.147621, -0.716567, 0.681718}, {0.000000, -0.525731, 0.850651}, +{0.425325, -0.688191, 0.587785}, {0.587785, -0.425325, 0.688191}, +{0.688191, -0.587785, 0.425325}, {-0.955423, 0.295242, 0.000000}, +{-0.951056, 0.162460, 0.262866}, {-1.000000, 0.000000, 0.000000}, +{-0.850651, 0.000000, 0.525731}, {-0.955423, -0.295242, 0.000000}, +{-0.951056, -0.162460, 0.262866}, {-0.864188, 0.442863, -0.238856}, +{-0.951056, 0.162460, -0.262866}, {-0.809017, 0.309017, -0.500000}, +{-0.864188, -0.442863, -0.238856}, {-0.951056, -0.162460, -0.262866}, +{-0.809017, -0.309017, -0.500000}, {-0.681718, 0.147621, -0.716567}, +{-0.681718, -0.147621, -0.716567}, {-0.850651, 0.000000, -0.525731}, +{-0.688191, 0.587785, -0.425325}, {-0.587785, 0.425325, -0.688191}, +{-0.425325, 0.688191, -0.587785}, {-0.425325, -0.688191, -0.587785}, +{-0.587785, -0.425325, -0.688191}, {-0.688191, -0.587785, -0.425325} +}; +#pragma warning(default : 4305) // truncation from const double to float + +//============================================================== + + +//======================================================= + +/* +erandom + +This function produces a random number with a exponential +distribution and the specified mean value. +*/ +float erandom( float mean ) { + float r; + + do { + r = random(); + } while ( r == 0.0 ); + + return -mean * log( r ); +} + +signed char ClampChar( int i ) { + if ( i < -128 ) { + return -128; + } + if ( i > 127 ) { + return 127; + } + return i; +} + +signed short ClampShort( int i ) { + if ( i < (short)0x8000 ) { + return (short)0x8000; + } + if ( i > 0x7fff ) { + return 0x7fff; + } + return i; +} + + +// this isn't a real cheap function to call! +int DirToByte( vec3_t dir ) { + int i, best; + float d, bestd; + + if ( !dir ) { + return 0; + } + + bestd = 0; + best = 0; + for (i=0 ; i bestd) + { + bestd = d; + best = i; + } + } + + return best; +} + +void ByteToDir( int b, vec3_t dir ) { + if ( b < 0 || b >= NUMVERTEXNORMALS ) { + VectorCopy( vec3_origin, dir ); + return; + } + VectorCopy (bytedirs[b], dir); +} + + +unsigned ColorBytes3 (float r, float g, float b) { + unsigned i; + + ( (byte *)&i )[0] = r * 255; + ( (byte *)&i )[1] = g * 255; + ( (byte *)&i )[2] = b * 255; + + return i; +} + +unsigned ColorBytes4 (float r, float g, float b, float a) { + unsigned i; + + ( (byte *)&i )[0] = r * 255; + ( (byte *)&i )[1] = g * 255; + ( (byte *)&i )[2] = b * 255; + ( (byte *)&i )[3] = a * 255; + + return i; +} + +float NormalizeColor( const vec3_t in, vec3_t out ) { + float max; + + max = in[0]; + if ( in[1] > max ) { + max = in[1]; + } + if ( in[2] > max ) { + max = in[2]; + } + + if ( !max ) { + VectorClear( out ); + } else { + out[0] = in[0] / max; + out[1] = in[1] / max; + out[2] = in[2] / max; + } + return max; +} + +void VectorAdvance( const vec3_t veca, const float scale, const vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + (scale * (vecb[0] - veca[0])); + vecc[1] = veca[1] + (scale * (vecb[1] - veca[1])); + vecc[2] = veca[2] + (scale * (vecb[2] - veca[2])); +} + +//============================================================================ + +/* +===================== +PlaneFromPoints + +Returns false if the triangle is degenrate. +The normal will point out of the clock for clockwise ordered points +===================== +*/ +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ) { + vec3_t d1, d2; + + VectorSubtract( b, a, d1 ); + VectorSubtract( c, a, d2 ); + CrossProduct( d2, d1, plane ); + if ( VectorNormalize( plane ) == 0 ) { + return qfalse; + } + + plane[3] = DotProduct( a, plane ); + return qtrue; +} + +#ifdef _XBOX +qboolean PlaneFromPoints( vec4_t plane, const short a[3], const short b[3], const short c[3] ) { + vec3_t d1, d2; + + VectorSubtract( b, a, d1 ); + VectorSubtract( c, a, d2 ); + CrossProduct( d2, d1, plane ); + if ( VectorNormalize( plane ) == 0 ) { + return qfalse; + } + + plane[3] = DotProduct( a, plane ); + return qtrue; +} +#endif + +/* +=============== +RotatePointAroundVector + +This is not implemented very well... +=============== +*/ +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; + float rad; + + 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; + + rad = DEG2RAD( degrees ); + zrot[0][0] = cos( rad ); + zrot[0][1] = sin( rad ); + zrot[1][0] = -sin( rad ); + zrot[1][1] = cos( rad ); + + MatrixMultiply( m, zrot, tmpmat ); + MatrixMultiply( 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]; + } +} + +/* +=============== +RotateAroundDirection +=============== +*/ +void RotateAroundDirection( vec3_t axis[3], float yaw ) { + + // create an arbitrary axis[1] + PerpendicularVector( axis[1], axis[0] ); + + // rotate it around axis[0] by yaw + if ( yaw ) { + vec3_t temp; + + VectorCopy( axis[1], temp ); + RotatePointAroundVector( axis[1], axis[0], temp, yaw ); + } + + // cross to get axis[2] + CrossProduct( axis[0], axis[1], axis[2] ); +} + + + +void vectoangles( const vec3_t value1, vec3_t angles ) { + float forward; + float yaw, pitch; + + if ( value1[1] == 0 && value1[0] == 0 ) { + yaw = 0; + if ( value1[2] > 0 ) { + pitch = 90; + } + else { + pitch = 270; + } + } + else { + if ( value1[0] ) { + yaw = ( atan2 ( value1[1], value1[0] ) * 180 / M_PI ); + } + else if ( value1[1] > 0 ) { + yaw = 90; + } + else { + yaw = 270; + } + if ( yaw < 0 ) { + yaw += 360; + } + + forward = sqrt ( value1[0]*value1[0] + value1[1]*value1[1] ); + pitch = ( atan2(value1[2], forward) * 180 / M_PI ); + if ( pitch < 0 ) { + pitch += 360; + } + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +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]; +} + +/* +================ +MakeNormalVectors + +Given a normalized forward vector, create two +other perpendicular vectors +================ +*/ +void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up) { + float d; + + // this rotate and negate guarantees a vector + // not colinear with the original + right[1] = -forward[0]; + right[2] = forward[1]; + right[0] = forward[2]; + + d = DotProduct (right, forward); + VectorMA (right, -d, forward, right); + VectorNormalize (right); + CrossProduct (right, forward, up); +} + +//============================================================================ + +/* +** float q_rsqrt( float number ) +*/ +float Q_rsqrt( float number ) +{ + long i; + float x2, y; + const float threehalfs = 1.5F; + + x2 = number * 0.5F; + y = number; + i = * ( long * ) &y; // evil floating point bit level hacking + i = 0x5f3759df - ( i >> 1 ); // what the fuck? + y = * ( float * ) &i; + y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration +// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed + + return y; +} + +float Q_fabs( float f ) { + int tmp = * ( int * ) &f; + tmp &= 0x7FFFFFFF; + return * ( float * ) &tmp; +} + +//============================================================ + + +//float AngleMod(float a) { +// a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); +// return a; +//} + + + +//============================================================ + + +/* +================= +SetPlaneSignbits +================= +*/ +void SetPlaneSignbits (cplane_t *out) { + int bits, j; + + // for fast box on planeside test + bits = 0; + for (j=0 ; j<3 ; j++) { + if (out->normal[j] < 0) { + bits |= 1<signbits = bits; +} + + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 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; +} + +================== +*/ + +#if !(defined __linux__ && defined __i386__) || defined __LCC__ +#if !id386 + +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 + break; + } + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + + 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,dword ptr[4+12+esp] + mov ecx,dword ptr[4+4+esp] + xor eax,eax + mov ebx,dword ptr[4+8+esp] + mov al,byte ptr[17+edx] + cmp al,8 + jge Lerror + fld dword ptr[0+edx] + fld st(0) + jmp dword ptr[Ljmptab+eax*4] +Lcase0: + fmul dword ptr[ebx] + fld dword ptr[0+4+edx] + fxch st(2) + fmul dword ptr[ecx] + fxch st(2) + fld st(0) + fmul dword ptr[4+ebx] + fld dword ptr[0+8+edx] + fxch st(2) + fmul dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase1: + fmul dword ptr[ecx] + fld dword ptr[0+4+edx] + fxch st(2) + fmul dword ptr[ebx] + fxch st(2) + fld st(0) + fmul dword ptr[4+ebx] + fld dword ptr[0+8+edx] + fxch st(2) + fmul dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase2: + fmul dword ptr[ebx] + fld dword ptr[0+4+edx] + fxch st(2) + fmul dword ptr[ecx] + fxch st(2) + fld st(0) + fmul dword ptr[4+ecx] + fld dword ptr[0+8+edx] + fxch st(2) + fmul dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase3: + fmul dword ptr[ecx] + fld dword ptr[0+4+edx] + fxch st(2) + fmul dword ptr[ebx] + fxch st(2) + fld st(0) + fmul dword ptr[4+ecx] + fld dword ptr[0+8+edx] + fxch st(2) + fmul dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase4: + fmul dword ptr[ebx] + fld dword ptr[0+4+edx] + fxch st(2) + fmul dword ptr[ecx] + fxch st(2) + fld st(0) + fmul dword ptr[4+ebx] + fld dword ptr[0+8+edx] + fxch st(2) + fmul dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase5: + fmul dword ptr[ecx] + fld dword ptr[0+4+edx] + fxch st(2) + fmul dword ptr[ebx] + fxch st(2) + fld st(0) + fmul dword ptr[4+ebx] + fld dword ptr[0+8+edx] + fxch st(2) + fmul dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase6: + fmul dword ptr[ebx] + fld dword ptr[0+4+edx] + fxch st(2) + fmul dword ptr[ecx] + fxch st(2) + fld st(0) + fmul dword ptr[4+ecx] + fld dword ptr[0+8+edx] + fxch st(2) + fmul dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase7: + fmul dword ptr[ecx] + fld dword ptr[0+4+edx] + fxch st(2) + fmul dword ptr[ebx] + fxch st(2) + fld st(0) + fmul dword ptr[4+ecx] + fld dword ptr[0+8+edx] + fxch st(2) + fmul dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul 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 dword ptr[12+edx] + xor ecx,ecx + fnstsw ax + fcomp 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 +#endif + +/* +================= +RadiusFromBounds +================= +*/ +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) { + int i; + vec3_t corner; + float a, b; + + for (i=0 ; i<3 ; i++) { + a = Q_fabs( mins[i] ); + b = Q_fabs( maxs[i] ); + corner[i] = a > b ? a : b; + } + + return VectorLength (corner); +} + + +void ClearBounds( vec3_t mins, vec3_t maxs ) { + mins[0] = mins[1] = mins[2] = WORLD_SIZE; //99999; // I used WORLD_SIZE instead of MAX_WORLD_COORD... + maxs[0] = maxs[1] = maxs[2] = -WORLD_SIZE; //-99999; // ... so it would definately be beyond furthese legal. +} + + +vec_t DistanceHorizontal( const vec3_t p1, const vec3_t p2 ) { + vec3_t v; + + VectorSubtract( p2, p1, v ); + return sqrt( v[0]*v[0] + v[1]*v[1] ); //Leave off the z component +} + +vec_t DistanceHorizontalSquared( const vec3_t p1, const vec3_t p2 ) { + vec3_t v; + + VectorSubtract( p2, p1, v ); + return v[0]*v[0] + v[1]*v[1]; //Leave off the z component +} + +int Q_log2( int val ) { + int answer; + + answer = 0; + while ( ( val>>=1 ) != 0 ) { + answer++; + } + return answer; +} + + +/* +================= +PlaneTypeForNormal +================= +*/ +int PlaneTypeForNormal (vec3_t normal) { + if ( normal[0] == 1.0 ) + return PLANE_X; + if ( normal[1] == 1.0 ) + return PLANE_Y; + if ( normal[2] == 1.0 ) + return PLANE_Z; + + return PLANE_NON_AXIAL; +} + + + +/* +================ +MatrixMultiply +================ +*/ +void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]) { + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + + in1[0][2] * in2[2][2]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + + in1[1][2] * in2[2][2]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + + in1[2][2] * in2[2][2]; +} + + +void AngleVectors( const 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.0); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360.0); + sp = sin(angle); + cp = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right || up) + { + angle = angles[ROLL] * (M_PI*2 / 360.0); + sr = sin(angle); + cr = cos(angle); + if (right) + { + right[0] = (-sr*sp*cy + cr*sy); + right[1] = (-sr*sp*sy + -cr*cy); + right[2] = -sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy + sr*sy); + up[1] = (cr*sp*sy + -sr*cy); + up[2] = cr*cp; + } + } +} + +/* +** 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 + ** bias towards using z instead of x or y + */ + for ( pos = 0, i = 2; i >= 0; i-- ) + { + if ( Q_fabs( src[i] ) < minelem ) + { + pos = i; + minelem = Q_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 ); +} + +/* +------------------------- +DotProductNormalize +------------------------- +*/ + +float DotProductNormalize( const vec3_t inVec1, const vec3_t inVec2 ) +{ + vec3_t v1, v2; + + VectorNormalize2( inVec1, v1 ); + VectorNormalize2( inVec2, v2 ); + + return DotProduct(v1, v2); +} + +/* +------------------------- +G_FindClosestPointOnLineSegment +------------------------- +*/ + +qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result ) +{ + vec3_t vecStart2From, vecStart2End, vecEnd2Start, vecEnd2From; + float distEnd2From, distEnd2Result, theta, cos_theta; + + //Find the perpendicular vector to vec from start to end + VectorSubtract( from, start, vecStart2From); + VectorSubtract( end, start, vecStart2End); + + float dot = DotProductNormalize( vecStart2From, vecStart2End ); + + if ( dot <= 0 ) + { + //The perpendicular would be beyond or through the start point + VectorCopy( start, result ); + return qfalse; + } + + if ( dot == 1 ) + { + //parallel, closer of 2 points will be the target + if( (VectorLengthSquared( vecStart2From )) < (VectorLengthSquared( vecStart2End )) ) + { + VectorCopy( from, result ); + } + else + { + VectorCopy( end, result ); + } + return qfalse; + } + + //Try other end + VectorSubtract( from, end, vecEnd2From); + VectorSubtract( start, end, vecEnd2Start); + + dot = DotProductNormalize( vecEnd2From, vecEnd2Start ); + + if ( dot <= 0 ) + {//The perpendicular would be beyond or through the start point + VectorCopy( end, result ); + return qfalse; + } + + if ( dot == 1 ) + {//parallel, closer of 2 points will be the target + if( (VectorLengthSquared( vecEnd2From )) < (VectorLengthSquared( vecEnd2Start ))) + { + VectorCopy( from, result ); + } + else + { + VectorCopy( end, result ); + } + return qfalse; + } + + // /| + // c / | + // / |a + // theta /)__| + // b + //cos(theta) = b / c + //solve for b + //b = cos(theta) * c + + //angle between vecs end2from and end2start, should be between 0 and 90 + theta = 90 * (1 - dot);//theta + + //Get length of side from End2Result using sine of theta + distEnd2From = VectorLength( vecEnd2From );//c + cos_theta = cos(DEG2RAD(theta));//cos(theta) + distEnd2Result = cos_theta * distEnd2From;//b + + //Extrapolate to find result + VectorNormalize( vecEnd2Start ); + VectorMA( end, distEnd2Result, vecEnd2Start, result ); + + //perpendicular intersection is between the 2 endpoints + return qtrue; +} + +float G_PointDistFromLineSegment( const vec3_t start, const vec3_t end, const vec3_t from ) +{ + vec3_t vecStart2From, vecStart2End, vecEnd2Start, vecEnd2From, intersection; + float distEnd2From, distStart2From, distEnd2Result, theta, cos_theta; + + //Find the perpendicular vector to vec from start to end + VectorSubtract( from, start, vecStart2From); + VectorSubtract( end, start, vecStart2End); + VectorSubtract( from, end, vecEnd2From); + VectorSubtract( start, end, vecEnd2Start); + + float dot = DotProductNormalize( vecStart2From, vecStart2End ); + + distStart2From = Distance( start, from ); + distEnd2From = Distance( end, from ); + + if ( dot <= 0 ) + { + //The perpendicular would be beyond or through the start point + return distStart2From; + } + + if ( dot == 1 ) + { + //parallel, closer of 2 points will be the target + return ((distStart2From max ) { + return max; + } + return value; +} + + +/* +============ +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( const char *in, char *out ) { + while ( *in && *in != '.' ) { + *out++ = *in++; + } + *out = 0; +} + + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension (char *path, int maxSize, const char *extension ) { + char *src; + + if (path[0]) // or the strlen()-1 stuff gets a bad ptr for blank string + { + // + // 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--; + } + } + + if (strlen(path)+strlen(extension) >= maxSize) + { + Com_Printf ("COM_DefaultExtension: overflow adding %s to %s\n", extension, path); + } + else + { + strcat(path, extension); + } +} + +/* +============================================================================ + + BYTE ORDER FUNCTIONS + +============================================================================ +*/ + +// can't just use function pointers, or dll linkage can +// mess up when qcommon is included in multiple places +static short (*_BigShort) (short l); +static short (*_LittleShort) (short l); +static int (*_BigLong) (int l); +static int (*_LittleLong) (int l); +static float (*_BigFloat) (float l); +static float (*_LittleFloat) (float l); + +#ifdef _M_IX86 +// +// optimised stuff for Intel, since most of our data is in that format anyway... +// +short BigShort(short l){return _BigShort(l);} +int BigLong (int l) {return _BigLong(l);} +float BigFloat (float l) {return _BigFloat(l);} +//short LittleShort(short l) {return _LittleShort(l);} // these are now macros in q_shared.h +//int LittleLong (int l) {return _LittleLong(l);} // +//float LittleFloat (float l) {return _LittleFloat(l);} // +// +#else +// +// standard smart-swap code... +// +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);} +// +#endif + +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) + { + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } + +} + + +/* +============================================================================ + +PARSING + +============================================================================ +*/ + +static char com_token[MAX_TOKEN_CHARS]; +//JLFCALLOUT MPNOTUSED +//#include functionality for files +int parseDataCount = -1; +parseData_t parseData[2]; + +void COM_ParseInit( void ) +{ + memset(&(parseData[0]),0,sizeof(parseData_t)); + memset(&(parseData[1]),0,sizeof(parseData_t)); + COM_BeginParseSession(); +} + +#ifdef _XBOX +void COM_BeginParseSession( bool nested ) +{ + if (nested) + parseDataCount =1; + else + parseDataCount = 0; + parseData[parseDataCount].com_lines = 1; + +} +#else +void COM_BeginParseSession( void ) +{ + parseDataCount =0; + parseData[parseDataCount].com_lines = 1; + +} + +#endif + +int COM_GetCurrentParseLine( int index ) +{ + return parseData[parseDataCount].com_lines; +} + +char *COM_Parse( const char **data_p ) +{ + return COM_ParseExt( data_p, qtrue ); +} + +/* +============== +COM_Parse + +Parse a token out of a string +Will never return NULL, just empty strings + +If "allowLineBreaks" is qtrue then an empty +string will be returned if the next token is +a newline. +============== +*/ +const char *SkipWhitespace( const char *data, qboolean *hasNewLines ) +{ + int c; + + while( (c = *data) <= ' ') + { + if( !c ) + { + return NULL; + } + if( c == '\n' ) + { + parseData[parseDataCount].com_lines++; + *hasNewLines = qtrue; + } + data++; + } + + return data; +} + +char *COM_ParseExt( const char **data_p, qboolean allowLineBreaks ) +{ + int c = 0, len; + qboolean hasNewLines = qfalse; + const char *data; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) + { + *data_p = NULL; + return com_token; + } + + while ( 1 ) + { + // skip whitespace + data = SkipWhitespace( data, &hasNewLines ); + if ( !data ) + { + *data_p = NULL; + return com_token; + } + if ( hasNewLines && !allowLineBreaks ) + { + *data_p = data; + return com_token; + } + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) + { + while (*data && *data != '\n') // Advance to the end of the line + { + data++; + } + } + // skip /* */ comments + else if ( c=='/' && data[1] == '*' ) + { + while ( *data && ( *data != '*' || data[1] != '/' ) ) // Advance to the */ characters + { + data++; + } + + if ( *data ) + { + data += 2; + } + } + else + { + break; + } + } + + // handle quoted strings + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = ( char * ) 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; + if ( c == '\n' ) + { + parseData[parseDataCount].com_lines++; + } + } 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 = ( char * ) data; + return com_token; +} + + +/* +============== +COM_Compress +remove blank space and comments from source +============== +*/ + +int COM_Compress( char *data_p ) { + char *in, *out; + int c; + qboolean newline = qfalse, whitespace = qfalse; + + in = out = data_p; + if (in) { + while ((c = *in) != 0) { + // skip double slash comments + if ( c == '/' && in[1] == '/' ) { + while (*in && *in != '\n') { + in++; + } + // skip /* */ comments + } else if ( c == '/' && in[1] == '*' ) { + while ( *in && ( *in != '*' || in[1] != '/' ) ) + in++; + if ( *in ) + in += 2; + // record when we hit a newline + } else if ( c == '\n' || c == '\r' ) { + newline = qtrue; + in++; + // record when we hit whitespace + } else if ( c == ' ' || c == '\t') { + whitespace = qtrue; + in++; + // an actual token + } else { + // if we have a pending newline, emit it (and it counts as whitespace) + if (newline) { + *out++ = '\n'; + newline = qfalse; + whitespace = qfalse; + } if (whitespace) { + *out++ = ' '; + whitespace = qfalse; + } + + // copy quoted strings unmolested + if (c == '"') { + *out++ = c; + in++; + while (1) { + c = *in; + if (c && c != '"') { + *out++ = c; + in++; + } else { + break; + } + } + if (c == '"') { + *out++ = c; + in++; + } + } else { + *out = c; + out++; + in++; + } + } + } + } + *out = 0; + return out - data_p; +} + + +/* +================== +COM_MatchToken +================== +*/ +void COM_MatchToken( const char **buf_p, const char *match ) { + const char *token; + + token = COM_Parse( buf_p ); + if ( strcmp( token, match ) ) + { + Com_Error( ERR_DROP, "MatchToken: %s != %s", token, match ); + } +} + + +/* +================= +SkipBracedSection + +The next token should be an open brace. +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +void SkipBracedSection ( const char **program) { + const char *token; + int depth=0; + + if (com_token[0]=='{') { //for tr_shader which just ate the brace + depth = 1; + } + do { + token = COM_ParseExt( program, qtrue ); + if( token[1] == 0 ) { + if( token[0] == '{' ) { + depth++; + + } + else if( token[0] == '}' ) { + depth--; + } + } + + } while (depth && *program); +} + +/* +================= +SkipRestOfLine +================= +*/ +void SkipRestOfLine ( const char **data ) { + const char *p; + int c; + + p = *data; + while ( (c = *p++) != 0 ) { + if ( c == '\n' ) { + parseData[parseDataCount].com_lines++; + break; + } + } + + *data = p; +} + + +void Parse1DMatrix ( const char **buf_p, int x, float *m) { + const char *token; + int i; + + COM_MatchToken( buf_p, "(" ); + + for (i = 0 ; i < x ; i++) { + token = COM_Parse(buf_p); + m[i] = atof(token); + } + + COM_MatchToken( buf_p, ")" ); +} + +void Parse2DMatrix ( const char **buf_p, int y, int x, float *m) { + int i; + + COM_MatchToken( buf_p, "(" ); + + for (i = 0 ; i < y ; i++) { + Parse1DMatrix (buf_p, x, m + i * x); + } + + COM_MatchToken( buf_p, ")" ); +} + +void Parse3DMatrix ( const char **buf_p, int z, int y, int x, float *m) { + int i; + + COM_MatchToken( buf_p, "(" ); + + for (i = 0 ; i < z ; i++) { + Parse2DMatrix (buf_p, y, x, m + i * x*y); + } + + COM_MatchToken( buf_p, ")" ); +} + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ + +int Q_isprint( int c ) +{ + if ( c >= 0x20 && c <= 0x7E ) + return ( 1 ); + return ( 0 ); +} + +int Q_islower( int c ) +{ + if (c >= 'a' && c <= 'z') + return ( 1 ); + return ( 0 ); +} + +int Q_isupper( int c ) +{ + if (c >= 'A' && c <= 'Z') + return ( 1 ); + return ( 0 ); +} + +int Q_isalpha( int c ) +{ + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) + return ( 1 ); + return ( 0 ); +} + +/* +char* Q_strrchr( const char* string, int c ) +{ + char cc = c; + char *s; + char *sp=(char *)0; + + s = (char*)string; + + while (*s) + { + if (*s == cc) + sp = s; + s++; + } + if (cc == 0) + sp = s; + + return sp; +} +*/ +/* +============= +Q_strncpyz + +Safe strncpy that ensures a trailing zero +============= +*/ +void Q_strncpyz( char *dest, const char *src, int destsize, qboolean bBarfIfTooLong/* = qfalse */ ) +{ + if ( !src ) { + Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" ); + } + if ( destsize < 1 ) { + Com_Error(ERR_FATAL,"Q_strncpyz: destsize < 1" ); + } + + if (bBarfIfTooLong) + { + if ( strlen(src)+1 > destsize) + { + Com_Error(ERR_FATAL,"String dest buffer too small to hold string \"%s\" %d > %d\n(source addr = %x, dest addr = %x",src, strlen(src)+1, destsize, src, dest); + } + } + strncpy( dest, src, destsize-1 ); + dest[destsize-1] = 0; +} +/* +int Q_stricmpn (const char *s1, const 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 c1 < c2 ? -1 : 1; + } + } + } while (c1); + + return 0; // strings are equal +} + +int Q_strncmp (const char *s1, const 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) { + return c1 < c2 ? -1 : 1; + } + } while (c1); + + return 0; // strings are equal +} + + + +char *Q_strlwr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = tolower(*s); + s++; + } + return s1; +} + +char *Q_strupr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = toupper(*s); + s++; + } + return s1; +} +*/ + +// never goes past bounds or leaves without a terminating 0 +void Q_strcat( char *dest, int size, const char *src ) { + int l1; + + l1 = strlen( dest ); + if ( l1 >= size ) { + Com_Error( ERR_FATAL, "Q_strcat: already overflowed" ); + } + if ( strlen(src)+1 > size - l1) + { //do the error here instead of in Q_strncpyz to get a meaningful msg + Com_Error(ERR_FATAL,"Q_strcat: cannot append \"%s\" to \"%s\"", src, dest); + } + Q_strncpyz( dest + l1, src, size - l1 ); +} + + +int Q_PrintStrlen( const char *string ) { + int len; + const char *p; + + if( !string ) { + return 0; + } + + len = 0; + p = string; + while( *p ) { + if( Q_IsColorString( p ) ) { + p += 2; + continue; + } + p++; + len++; + } + + return len; +} + + +char *Q_CleanStr( char *string ) { + char* d; + char* s; + int c; + + s = string; + d = string; + while ((c = *s) != 0 ) { + if ( Q_IsColorString( s ) ) { + s++; + } + else if ( c >= 0x20 && c <= 0x7E ) { + *d++ = c; + } + s++; + } + *d = '\0'; + + return string; +} + + +void QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) { + int len; + va_list argptr; + char bigbuffer[1024]; + + va_start (argptr,fmt); + len = vsprintf (bigbuffer,fmt,argptr); + va_end (argptr); + if ( len >= sizeof( bigbuffer ) ) { + Com_Error( ERR_FATAL, "Com_sprintf: overflowed bigbuffer" ); + } + if (len >= size) { + Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size); + } + Q_strncpyz (dest, bigbuffer, size ); +} + + +/* +============ +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 * QDECL va( const char *format, ... ) { + int len; + va_list argptr; + static char buffers[4][1024]; // in case va is called by nested functions + static int index = 0; + char *const buf = buffers[index % 4]; + index++; + + va_start (argptr, format); + len = vsprintf (buf, format,argptr); + va_end (argptr); + + assert(len= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" ); + } + + 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) + { + *o++ = *s++; + } + *o = 0; + + if (!Q_stricmp (key, pkey) ) + return value[valueindex]; + + if (!*s) + break; + s++; + } + + return ""; +} + + +/* +=================== +Info_NextPair + +Used to itterate through all the key/value pairs in an info string +=================== +*/ +void Info_NextPair( const char **head, char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ) { + char *o; + const char *s; + + s = *head; + + if ( *s == '\\' ) { + s++; + } + key[0] = 0; + value[0] = 0; + + o = key; + while ( *s != '\\' ) { + if ( !*s ) { + *o = 0; + *head = s; + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) { + *o++ = *s++; + } + *o = 0; + + *head = s; +} + + +/* +=================== +Info_RemoveKey +=================== +*/ +void Info_RemoveKey( char *s, const char *key ) { + char *start; + char pkey[MAX_INFO_KEY]; + char value[MAX_INFO_VALUE]; + char *o; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" ); + } + + if (strchr (key, '\\')) { + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + 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( const char *s ) { + if ( strchr( s, '\"' ) ) { + return qfalse; + } + if ( strchr( s, ';' ) ) { + return qfalse; + } + return qtrue; +} + +/* +================== +Info_SetValueForKey + +Changes or adds a key/value pair +================== +*/ +void Info_SetValueForKey( char *s, const char *key, const char *value ) { + char newi[MAX_INFO_STRING]; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" ); + } + + if (strchr (key, '\\') || strchr (value, '\\')) + { + Com_Printf ("Can't use keys or values with a \\(%s, %s)\n",key,value); + return; + } + + if (strchr (key, ';') || strchr (value, ';')) + { + Com_Printf ("Can't use keys or values with a semicolon(%s, %s)\n",key,value); + return; + } + + if (strchr (key, '\"') || strchr (value, '\"')) + { + Com_Printf ("Can't use keys or values with a \"(%s, %s)\n",key,value); + return; + } + + Info_RemoveKey (s, key); + if (!value || !strlen(value)) + return; + + Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); + + if (strlen(newi) + strlen(s) > MAX_INFO_STRING) + { + Com_Printf ("Info string length exceeded\n"); + return; + } + + strcat (s, newi); +} + +/* +======================================================================== + +String ID Tables + +======================================================================== +*/ + + +/* +------------------------- +GetIDForString +------------------------- +*/ + +#define VALIDSTRING( a ) ( ( a != NULL ) && ( a[0] != NULL ) ) + +int GetIDForString ( const stringID_table_t *table, const char *string ) +{ + int index = 0; + + while ( VALIDSTRING( table[index].name ) ) + { + if ( !Q_stricmp( table[index].name, string ) ) + return table[index].id; + + index++; + } + + return -1; +} + +/* +------------------------- +GetStringForID +------------------------- +*/ + +const char *GetStringForID( const stringID_table_t *table, int id ) +{ + int index = 0; + + while ( VALIDSTRING( table[index].name ) ) + { + if ( table[index].id == id ) + return table[index].name; + + index++; + } + + return NULL; +} + +/* +=============== +COM_ParseString +=============== +*/ +qboolean COM_ParseString( const char **data, const char **s ) +{ +// *s = COM_ParseExt( data, qtrue ); + *s = COM_ParseExt( data, qfalse ); + if ( s[0] == 0 ) + { + Com_Printf("unexpected EOF in COM_ParseString\n"); + return qtrue; + } + return qfalse; +} + +/* +=============== +COM_ParseInt +=============== +*/ +qboolean COM_ParseInt( const char **data, int *i ) +{ + const char *token; + + token = COM_ParseExt( data, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf( "unexpected EOF in COM_ParseInt\n" ); + return qtrue; + } + + *i = atoi( token ); + return qfalse; +} + +/* +=============== +COM_ParseFloat +=============== +*/ +qboolean COM_ParseFloat( const char **data, float *f ) +{ + const char *token; + + token = COM_ParseExt( data, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf( "unexpected EOF in COM_ParseFloat\n" ); + return qtrue; + } + + *f = atof( token ); + return qfalse; +} + +/* +=============== +COM_ParseVec4 +=============== +*/ +qboolean COM_ParseVec4( const char **buffer, vec4_t *c) +{ + int i; + float f; + + for (i = 0; i < 4; i++) + { + if (COM_ParseFloat(buffer, &f)) + { + return qtrue; + } + (*c)[i] = f; + } + return qfalse; +} + + + +// end + diff --git a/code/game/q_shared.h b/code/game/q_shared.h new file mode 100644 index 0000000..d304b8c --- /dev/null +++ b/code/game/q_shared.h @@ -0,0 +1,2425 @@ +#ifndef __Q_SHARED_H +#define __Q_SHARED_H + +// q_shared.h -- included first by ALL program modules. +// A user mod should never modify this file + +#ifdef _WIN32 + +#pragma warning(disable : 4018) // signed/unsigned mismatch +//#pragma warning(disable : 4032) //formal parameter 'number' has different type when promoted +//#pragma warning(disable : 4051) //type conversion; possible loss of data +//#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +//#pragma warning(disable : 4115) //'type' : named type definition in parentheses +#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +//#pragma warning(disable : 4136) //conversion between different floating-point types +//#pragma warning(disable : 4201) //nonstandard extension used : nameless struct/union +//#pragma warning(disable : 4214) //nonstandard extension used : bit field types other than int +//#pragma warning(disable : 4220) // varargs matches remaining parameters +#pragma warning(disable : 4244) //'conversion' conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable : 4284) // return type not UDT +//#pragma warning(disable : 4305) // truncation from const double to float +#pragma warning(disable : 4310) // cast truncates constant value +#pragma warning(disable : 4514) //unreferenced inline/local function has been removed +#pragma warning(disable : 4710) // not inlined +#pragma warning(disable : 4711) // selected for automatic inline expansion +#pragma warning(disable : 4786) // identifier was truncated + +#endif + +//rww - conveniently toggle "gore" code, for model decals and stuff. +#ifndef _XBOX +#define _G2_GORE +#endif + +#ifndef FINAL_BUILD +#define G2_PERFORMANCE_ANALYSIS +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _XBOX +#define tvector(T) std::vector< T > +#define tdeque(T) std::deque< T > + +#define tlist(T) std::list< T > +#define tslist(T) std::slist< T > + +#define tset(T) std::set< T, std::less< T > > +#define tmultiset(T) std::multiset< T, std::less< T > > + +#define tcset(T,C) std::set< T, C > +#define tcmultiset(T,C) std::multiset< T, C > + +#define tmap(K,T) std::map< K, T, std::less< K > > +#define tmultimap(K,T) std::multimap< K, T, std::less< K > > + +#define tcmap(K,T,C) std::map< K, T, C > +#define tcmultimap(K,T,C) std::multimap< K, T, C > +#endif // _XBOX + + +// this is the define for determining if we have an asm version of a C function +#if (defined _M_IX86 || defined __i386__) && !defined __sun__ && !defined __LCC__ +#define id386 1 +#else +#define id386 0 +#endif + +// for windows fastcall option + +#define QDECL + +//======================= WIN32 DEFINES ================================= + +#ifdef WIN32 + +#define MAC_STATIC + +#undef QDECL +#define QDECL __cdecl + +// buildstring will be incorporated into the version string +#ifdef NDEBUG +#ifdef _M_IX86 +#define CPUSTRING "win-x86" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP" +#endif +#else +#ifdef _M_IX86 +#define CPUSTRING "win-x86-debug" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP-debug" +#endif +#endif + + +#define PATH_SEP '\\' + +#endif + +//======================= MAC OS X SERVER DEFINES ===================== + +#if defined(__MACH__) && defined(__APPLE__) + +#define MAC_STATIC + +#ifdef __ppc__ +#define CPUSTRING "MacOSXS-ppc" +#elif defined __i386__ +#define CPUSTRING "MacOSXS-i386" +#else +#define CPUSTRING "MacOSXS-other" +#endif + +#define PATH_SEP '/' + +#define GAME_HARD_LINKED +#define CGAME_HARD_LINKED +#define UI_HARD_LINKED + +#endif + +//======================= MAC DEFINES ================================= + +#ifdef __MACOS__ + +#define MAC_STATIC static + +#define CPUSTRING "MacOS-PPC" + +#define PATH_SEP ':' + +#define GAME_HARD_LINKED +#define CGAME_HARD_LINKED +#define UI_HARD_LINKED + +void Sys_PumpEvents( void ); + +#endif + +//======================= LINUX DEFINES ================================= + +// the mac compiler can't handle >32k of locals, so we +// just waste space and make big arrays static... +#ifdef __linux__ + +#define MAC_STATIC + +#ifdef __i386__ +#define CPUSTRING "linux-i386" +#elif defined __axp__ +#define CPUSTRING "linux-alpha" +#else +#define CPUSTRING "linux-other" +#endif + +#define PATH_SEP '/' + +#endif + +//============================================================= + +typedef unsigned long ulong; +typedef unsigned short word; + +typedef unsigned char byte; + +typedef const char *LPCSTR; + +typedef enum {qfalse, qtrue} qboolean; +#define qboolean int //don't want strict type checking on the qboolean + +typedef int qhandle_t; +typedef int thandle_t; +typedef int fxHandle_t; +typedef int sfxHandle_t; +typedef int fileHandle_t; +typedef int clipHandle_t; + + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define MAX_QINT 0x7fffffff +#define MIN_QINT (-MAX_QINT-1) + + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +// the game guarantees that no string from the network will ever +// exceed MAX_STRING_CHARS +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 256 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 1024 // max length of an individual token + +#define MAX_INFO_STRING 1024 +#define MAX_INFO_KEY 1024 +#define MAX_INFO_VALUE 1024 + + +#define MAX_QPATH 64 // max length of a quake game pathname +#ifdef _XBOX +#define MAX_OSPATH 128 // max length of a filesystem pathname +#else +#define MAX_OSPATH 260 // max length of a filesystem pathname +#endif + +#define MAX_NAME_LENGTH 32 // max length of a client name + +// paramters for command buffer stuffing +typedef enum { + EXEC_NOW, // don't return until completed, a VM should NEVER use this, + // because some commands might cause the VM to be unloaded... + EXEC_INSERT, // insert at current position, but don't run yet + EXEC_APPEND // add to end of the command buffer (normal case) +} cbufExec_t; + + +// +// these aren't needed by any of the VMs. put in another header? +// +#define MAX_MAP_AREA_BYTES 32 // bit vector of area visibility + +// Light Style Constants + +#define LS_STYLES_START 0 +#define LS_NUM_STYLES 32 +#define LS_SWITCH_START (LS_STYLES_START+LS_NUM_STYLES) +#define LS_NUM_SWITCH 32 +#define MAX_LIGHT_STYLES 64 + +// print levels from renderer (FIXME: set up for game / cgame?) +typedef enum { + PRINT_ALL, + PRINT_DEVELOPER, // only print when "developer 1" + PRINT_WARNING, + PRINT_ERROR +} printParm_t; + +// parameters to the main Error routine +typedef enum { + ERR_FATAL, // exit the entire game with a popup window + ERR_DROP, // print to console and disconnect from game + ERR_DISCONNECT, // don't kill server + ERR_NEED_CD // pop up the need-cd dialog +} errorParm_t; + +// font rendering values used by ui and cgame +#define PROP_GAP_WIDTH 2 +//#define PROP_GAP_WIDTH 3 +#define PROP_SPACE_WIDTH 4 +#define PROP_HEIGHT 16 + +#define PROP_TINY_SIZE_SCALE 1 +#define PROP_SMALL_SIZE_SCALE 1 +#define PROP_BIG_SIZE_SCALE 1 +#define PROP_GIANT_SIZE_SCALE 2 + +#define PROP_TINY_HEIGHT 10 +#define PROP_GAP_TINY_WIDTH 1 +#define PROP_SPACE_TINY_WIDTH 3 + +#define PROP_BIG_HEIGHT 24 +#define PROP_GAP_BIG_WIDTH 3 +#define PROP_SPACE_BIG_WIDTH 6 + + +#define BLINK_DIVISOR 600 +#define PULSE_DIVISOR 75 + +#define UI_LEFT 0x00000000 // default +#define UI_CENTER 0x00000001 +#define UI_RIGHT 0x00000002 +#define UI_FORMATMASK 0x00000007 +#define UI_SMALLFONT 0x00000010 +#define UI_BIGFONT 0x00000020 // default +#define UI_GIANTFONT 0x00000040 +#define UI_DROPSHADOW 0x00000800 +#define UI_BLINK 0x00001000 +#define UI_INVERSE 0x00002000 +#define UI_PULSE 0x00004000 +#define UI_UNDERLINE 0x00008000 +#define UI_TINYFONT 0x00010000 + + +// stuff for TA's ROQ cinematic code... +// +#define CIN_system 1 +#define CIN_loop 2 +#define CIN_hold 4 +#define CIN_silent 8 +#define CIN_shader 16 + + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + + +typedef float vec_t; +typedef vec_t vec2_t[2]; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; +typedef vec_t vec5_t[5]; + +typedef vec3_t vec3pair_t[2]; + +typedef int ivec2_t[2]; +typedef int ivec3_t[3]; +typedef int ivec4_t[4]; +typedef int ivec5_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 + +#define NUMVERTEXNORMALS 162 +extern vec3_t bytedirs[NUMVERTEXNORMALS]; + +// all drawing is done to a 640*480 virtual screen size +// and will be automatically scaled to the real resolution +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 + +#define TINYCHAR_WIDTH (SMALLCHAR_WIDTH) +#define TINYCHAR_HEIGHT (SMALLCHAR_HEIGHT/2) + +#define SMALLCHAR_WIDTH 8 +#define SMALLCHAR_HEIGHT 16 + +#define BIGCHAR_WIDTH 16 +#define BIGCHAR_HEIGHT 16 + +#define GIANTCHAR_WIDTH 32 +#define GIANTCHAR_HEIGHT 48 + +typedef enum +{ +CT_NONE, +CT_BLACK, +CT_RED, +CT_GREEN, +CT_BLUE, +CT_YELLOW, +CT_MAGENTA, +CT_CYAN, +CT_WHITE, +CT_LTGREY, +CT_MDGREY, +CT_DKGREY, +CT_DKGREY2, + +CT_VLTORANGE, +CT_LTORANGE, +CT_DKORANGE, +CT_VDKORANGE, + +CT_VLTBLUE1, +CT_LTBLUE1, +CT_DKBLUE1, +CT_VDKBLUE1, + +CT_VLTBLUE2, +CT_LTBLUE2, +CT_DKBLUE2, +CT_VDKBLUE2, + +CT_VLTBROWN1, +CT_LTBROWN1, +CT_DKBROWN1, +CT_VDKBROWN1, + +CT_VLTGOLD1, +CT_LTGOLD1, +CT_DKGOLD1, +CT_VDKGOLD1, + +CT_VLTPURPLE1, +CT_LTPURPLE1, +CT_DKPURPLE1, +CT_VDKPURPLE1, + +CT_VLTPURPLE2, +CT_LTPURPLE2, +CT_DKPURPLE2, +CT_VDKPURPLE2, + +CT_VLTPURPLE3, +CT_LTPURPLE3, +CT_DKPURPLE3, +CT_VDKPURPLE3, + +CT_VLTRED1, +CT_LTRED1, +CT_DKRED1, +CT_VDKRED1, +CT_VDKRED, +CT_DKRED, + +CT_VLTAQUA, +CT_LTAQUA, +CT_DKAQUA, +CT_VDKAQUA, + +CT_LTPINK, +CT_DKPINK, +CT_LTCYAN, +CT_DKCYAN, +CT_LTBLUE3, +CT_BLUE3, +CT_DKBLUE3, + +CT_HUD_GREEN, +CT_HUD_RED, +CT_ICON_BLUE, +CT_NO_AMMO_RED, +CT_HUD_ORANGE, + +CT_TITLE, + +CT_MAX +} ct_table_t; + +extern vec4_t colorTable[CT_MAX]; + + +#define Q_COLOR_ESCAPE '^' +// you MUST have the last bit on here about colour strings being less than 7 or taiwanese strings register as colour!!!! +#define Q_IsColorString(p) ( p && *(p) == Q_COLOR_ESCAPE && *((p)+1) && *((p)+1) != Q_COLOR_ESCAPE && *((p)+1) <= '7' && *((p)+1) >= '0' ) + +#define COLOR_BLACK '0' +#define COLOR_RED '1' +#define COLOR_GREEN '2' +#define COLOR_YELLOW '3' +#define COLOR_BLUE '4' +#define COLOR_CYAN '5' +#define COLOR_MAGENTA '6' +#define COLOR_WHITE '7' +#define ColorIndex(c) ( ( (c) - '0' ) & 7 ) + +#define S_COLOR_BLACK "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" + +extern vec4_t g_color_table[8]; + +#define MAKERGB( v, r, g, b ) v[0]=r;v[1]=g;v[2]=b +#define MAKERGBA( v, r, g, b, a ) v[0]=r;v[1]=g;v[2]=b;v[3]=a + +// Player weapons effects +typedef enum +{ + SABER_RED, + SABER_ORANGE, + SABER_YELLOW, + SABER_GREEN, + SABER_BLUE, + SABER_PURPLE + +} saber_colors_t; + +#define MAX_BATTERIES 2500 + +#define PI_DIV_180 0.017453292519943295769236907684886 +#define INV_PI_DIV_180 57.295779513082320876798154814105 + +// Punish Aurelio if you don't like these performance enhancements. :-) +#define DEG2RAD( a ) ( ( (a) * PI_DIV_180 ) ) +#define RAD2DEG( a ) ( ( (a) * INV_PI_DIV_180 ) ) + +// A divide can be avoided by just multiplying by PI_DIV_180 which is PI divided by 180. - Aurelio +//#define DEG2RAD( a ) ( ( (a) * M_PI ) / 180.0F ) +// A divide can be avoided by just multiplying by INV_PI_DIV_180(inverse of PI/180) which is 180 divided by PI. - Aurelio +//#define RAD2DEG( a ) ( ( (a) * 180.0f ) / M_PI ) + +#define ENUM2STRING(arg) #arg,arg + +struct cplane_s; + +extern const vec3_t vec3_origin; +extern const vec3_t axisDefault[3]; + +#define nanmask (255<<23) + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +#ifdef _XBOX +inline void Q_CastShort2Float(float *f, const short *s) +{ + *f = ((float)*s); +} + +inline void Q_CastUShort2Float(float *f, const unsigned short *s) +{ + *f = ((float)*s); +} + +inline void Q_CastShort2FloatScale(float *f, const short *s, float scale) +{ + *f = ((float)*s) * scale; +} + +inline void Q_CastUShort2FloatScale(float *f, const unsigned short *s, float scale) +{ + *f = ((float)*s) * scale; +} +#endif + +float Q_fabs( float f ); +float Q_rsqrt( float f ); // reciprocal square root + +#define SQRTFAST( x ) ( 1.0f / Q_rsqrt( x ) ) + +signed char ClampChar( int i ); +signed short ClampShort( int i ); + +// this isn't a real cheap function to call! +int DirToByte( vec3_t dir ); +void ByteToDir( int b, vec3_t dir ); + +#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 _VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define _VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) + + +#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)) +#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) + +#define SnapVector(v) {v[0]=(int)v[0];v[1]=(int)v[1];v[2]=(int)v[2];} + +// just in case you do't want to use the macros +inline void VectorMA( const vec3_t veca, float scale, const 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]; +} + +#ifdef _XBOX +inline void VectorMA( const vec3_t veca, float scale, const short vecb[3], vec3_t vecc) { + // The only time this overload gets used is with normals, so + // (I think) it's safe to do this.... + vecc[0] = veca[0] + scale * ((float)vecb[0] / 32767.0f); + vecc[1] = veca[1] + scale * ((float)vecb[1] / 32767.0f); + vecc[2] = veca[2] + scale * ((float)vecb[2] / 32767.0f); +} +#endif + +inline vec_t DotProduct( const vec3_t v1, const vec3_t v2 ) { +#ifdef _XBOX1 /// use SSE + float res; + __asm { + mov edx, v1 + movss xmm1, [edx] + movhps xmm1, [edx+4] + + mov edx, v2 + movss xmm2, [edx] + movhps xmm2, [edx+4] + + mulps xmm1, xmm2 + + movaps xmm0, xmm1 + + shufps xmm0, xmm0, 32h + addps xmm1, xmm0 + + shufps xmm0, xmm0, 32h + addps xmm1, xmm0 + + movss [res], xmm1 + } + return res; +#else + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +#endif +} + +#ifdef _XBOX +inline vec_t DotProduct( const short v1[3], const vec3_t v2 ) { + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} +#endif + +inline void CrossProduct( const vec3_t v1, const 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]; +} + +inline void VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t o ) { +#ifdef _XBOX1 + __asm { + mov ecx, veca + movss xmm0, [ecx] + movhps xmm0, [ecx+4] + + mov edx, vecb + movss xmm1, [edx] + movhps xmm1, [edx+4] + + subps xmm0, xmm1 + + mov eax, o + movss [eax], xmm0 + movhps [eax+4], xmm0 + } +#else + o[0] = veca[0]-vecb[0]; + o[1] = veca[1]-vecb[1]; + o[2] = veca[2]-vecb[2]; +#endif +} + +#ifdef _XBOX +inline void VectorSubtract( const short veca[3], const vec3_t vecb, vec3_t o ) { + o[0] = veca[0]-vecb[0]; + o[1] = veca[1]-vecb[1]; + o[2] = veca[2]-vecb[2]; +} + +inline void VectorSubtract( const vec3_t veca, const short vecb[3], vec3_t o ) { + o[0] = veca[0]-vecb[0]; + o[1] = veca[1]-vecb[1]; + o[2] = veca[2]-vecb[2]; +} + +inline void VectorSubtract( const short veca[3], const short vecb[3], vec3_t o ) { + o[0] = veca[0]-vecb[0]; + o[1] = veca[1]-vecb[1]; + o[2] = veca[2]-vecb[2]; +} +#endif + +inline void VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t o ) { +#ifdef _XBOX1 + __asm { + mov ecx, veca + movss xmm0, [ecx] + movhps xmm0, [ecx+4] + + mov edx, vecb + movss xmm1, [edx] + movhps xmm1, [edx+4] + + addps xmm0, xmm1 + + mov eax, o + movss [eax], xmm0 + movhps [eax+4], xmm0 + } +#else + o[0] = veca[0]+vecb[0]; + o[1] = veca[1]+vecb[1]; + o[2] = veca[2]+vecb[2]; +#endif +} + +inline void VectorCopy( const vec3_t in, vec3_t out ) { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +#ifdef _XBOX +inline void VectorCopy( const short in[3], vec3_t out ) { + out[0] = (float)in[0]; + out[1] = (float)in[1]; + out[2] = (float)in[2]; +} +#endif + +inline void VectorScale( const vec3_t i, vec_t scale, vec3_t o ) { +#ifdef _XBOX1 +__asm { + movss xmm0, scale + shufps xmm0, xmm0, 0h + + mov edx, i + movss xmm1, [edx] + movhps xmm1, [edx+4] + + mulps xmm0, xmm1 + + mov eax, o + movss [eax], xmm0 + movhps [eax+4], xmm0 + } +#else + o[0] = i[0]*scale; + o[1] = i[1]*scale; + o[2] = i[2]*scale; +#endif +} + +float DotProductNormalize( const vec3_t inVec1, const vec3_t inVec2 ); + +unsigned ColorBytes3 (float r, float g, float b); +unsigned ColorBytes4 (float r, float g, float b, float a); + +float NormalizeColor( const vec3_t in, vec3_t out ); +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ); + +void ClearBounds( vec3_t mins, vec3_t maxs ); + +#ifdef _XBOX +inline void AddPointToBounds( const short v[3], vec3_t mins, vec3_t maxs ) { + if ( v[0] < mins[0] ) { + mins[0] = v[0]; + } + if ( v[0] > maxs[0]) { + maxs[0] = v[0]; + } + + if ( v[1] < mins[1] ) { + mins[1] = v[1]; + } + if ( v[1] > maxs[1]) { + maxs[1] = v[1]; + } + + if ( v[2] < mins[2] ) { + mins[2] = v[2]; + } + if ( v[2] > maxs[2]) { + maxs[2] = v[2]; + } +} +#endif + +inline void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) { + if ( v[0] < mins[0] ) { + mins[0] = v[0]; + } + if ( v[0] > maxs[0]) { + maxs[0] = v[0]; + } + + if ( v[1] < mins[1] ) { + mins[1] = v[1]; + } + if ( v[1] > maxs[1]) { + maxs[1] = v[1]; + } + + if ( v[2] < mins[2] ) { + mins[2] = v[2]; + } + if ( v[2] > maxs[2]) { + maxs[2] = v[2]; + } +} + +inline int VectorCompare( const vec3_t v1, const vec3_t v2 ) { + if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) { + return 0; + } + return 1; +} + +//NOTE: less precise +inline int VectorCompare2( const vec3_t v1, const vec3_t v2 ) { + if ( v1[0] > v2[0]+0.0001f || v1[0] < v2[0]-0.0001f + || v1[1] > v2[1]+0.0001f || v1[1] < v2[1]-0.0001f + || v1[2] > v2[2]+0.0001f || v1[2] < v2[2]-0.0001f ) { + return 0; + } + return 1; +} +inline vec_t VectorLength( const vec3_t v ) { +#ifdef _XBOX1 + float res; + + __asm { + mov edx, v + movss xmm1, [edx] + movhps xmm1, [edx+4] + + movaps xmm2, xmm1 + + mulps xmm1, xmm2 + + movaps xmm0, xmm1 + + shufps xmm0, xmm0, 32h + addps xmm1, xmm0 + + shufps xmm0, xmm0, 32h + addps xmm1, xmm0 + + sqrtss xmm1, xmm1 + movss [res], xmm1 + } + + return res; +#else + return (vec_t)sqrt (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); +#endif +} + +inline vec_t VectorLengthSquared( const vec3_t v ) { +#ifdef _XBOX1 + float res; + __asm { + mov edx, v + movss xmm1, [edx] + movhps xmm1, [edx+4] + + movaps xmm2, xmm1 + + mulps xmm1, xmm2 + + movaps xmm0, xmm1 + + shufps xmm0, xmm0, 32h + addps xmm1, xmm0 + + shufps xmm0, xmm0, 32h + addps xmm1, xmm0 + + movss [res], xmm1 + } + + return res; +#else + return (v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); +#endif +} + +inline vec_t Distance( const vec3_t p1, const vec3_t p2 ) { + vec3_t v; + + VectorSubtract (p2, p1, v); + return VectorLength( v ); +} + +inline vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ) { + vec3_t v; + + VectorSubtract (p2, p1, v); + return v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; +} + +// fast vector normalize routine that does not check to make sure +// that length != 0, nor does it return length, uses rsqrt approximation +inline void VectorNormalizeFast( vec3_t v ) +{ + float ilength; + + ilength = Q_rsqrt( DotProduct( v, v ) ); + + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; +} + +inline void VectorInverse( vec3_t v ){ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +inline void VectorRotate( vec3_t in, vec3_t matrix[3], vec3_t out ) +{ + out[0] = DotProduct( in, matrix[0] ); + out[1] = DotProduct( in, matrix[1] ); + out[2] = DotProduct( in, matrix[2] ); +} + +//if length is 0, v is untouched otherwise v is normalized +inline 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); + + if ( length > 0.0001f ) { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; +} + + +//if length is 0, out is cleared, otherwise out is normalized +inline vec_t VectorNormalize2( const 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); + + if (length) + { + ilength = 1/length; + out[0] = v[0]*ilength; + out[1] = v[1]*ilength; + out[2] = v[2]*ilength; + } else { + VectorClear( out ); + } + + return length; +} + +#ifdef _XBOX +inline vec_t VectorNormalize2( const vec3_t v, short out[3]) { + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); + + if (length) + { + ilength = 1/length; + out[0] = (short)(v[0]*ilength * 32767.0f); + out[1] = (short)(v[1]*ilength * 32767.0f); + out[2] = (short)(v[2]*ilength * 32767.0f); + } else { + VectorClear( out ); + } + + return length; +} +#endif + +int Q_log2(int val); + +inline int Q_rand( int *seed ) { + *seed = (69069 * *seed + 1); + return *seed; +} + +inline float Q_random( int *seed ) { + return ( Q_rand( seed ) & 0xffff ) / (float)0x10000; +} + +inline float Q_crandom( int *seed ) { + return 2.0F * ( Q_random( seed ) - 0.5f ); +} + +// Returns a float min <= x < max (exclusive; will get max - 0.00001; but never max +inline float Q_flrand(float min, float max) { + return ((rand() * (max - min)) / 32768.0F) + min; +} + +// Returns an integer min <= x <= max (ie inclusive) +inline int Q_irand(int min, int max) { + max++; //so it can round down + return ((rand() * (max - min)) >> 15) + min; +} + +//returns a float between 0 and 1.0 +inline float random() { + return (rand() / ((float)0x7fff)); +} + +//returns a float between -1 and 1.0 +inline float crandom() { + return (2.0F * (random() - 0.5F)); +} + +float erandom( float mean ); + +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); + +/* +================= +AnglesToAxis +================= +*/ +inline void AnglesToAxis( const vec3_t angles, vec3_t axis[3] ) { + vec3_t right; + + // angle vectors returns "right" instead of "y axis" + AngleVectors( angles, axis[0], right, axis[2] ); + VectorSubtract( vec3_origin, right, axis[1] ); +} + +inline void AxisClear( vec3_t axis[3] ) { + axis[0][0] = 1; + axis[0][1] = 0; + axis[0][2] = 0; + axis[1][0] = 0; + axis[1][1] = 1; + axis[1][2] = 0; + axis[2][0] = 0; + axis[2][1] = 0; + axis[2][2] = 1; +} + +inline void AxisCopy( const vec3_t in[3], vec3_t out[3] ) { + VectorCopy( in[0], out[0] ); + VectorCopy( in[1], out[1] ); + VectorCopy( in[2], out[2] ); +} + +void vectoangles( const vec3_t value1, vec3_t angles); + +vec_t DistanceHorizontal( const vec3_t p1, const vec3_t p2 ); +vec_t DistanceHorizontalSquared( const vec3_t p1, const vec3_t p2 ); + +inline vec_t GetYawForDirection( const vec3_t p1, const vec3_t p2 ) { + vec3_t v, angles; + + VectorSubtract( p2, p1, v ); + vectoangles( v, angles ); + + return angles[YAW]; +} + +inline void GetAnglesForDirection( const vec3_t p1, const vec3_t p2, vec3_t out ) { + vec3_t v; + + VectorSubtract( p2, p1, v ); + vectoangles( v, out ); +} + + +void SetPlaneSignbits( struct cplane_s *out ); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane); +//float AngleMod(float a); + +inline float LerpAngle (float from, float to, float frac) { + float a; + + if ( to - from > 180 ) { + to -= 360; + } + if ( to - from < -180 ) { + to += 360; + } + a = from + frac * (to - from); + + return a; +} + +/* +================= +AngleSubtract + +Always returns a value from -180 to 180 +================= +*/ +inline float AngleSubtract( float a1, float a2 ) { + float a; + + a = a1 - a2; + while ( a > 180 ) { + a -= 360; + } + while ( a < -180 ) { + a += 360; + } + return a; +} + +inline void AnglesSubtract( vec3_t v1, vec3_t v2, vec3_t v3 ) { + v3[0] = AngleSubtract( v1[0], v2[0] ); + v3[1] = AngleSubtract( v1[1], v2[1] ); + v3[2] = AngleSubtract( v1[2], v2[2] ); +} + +/* +================= +AngleNormalize360 + +returns angle normalized to the range [0 <= angle < 360] +================= +*/ +inline float AngleNormalize360 ( float angle ) { + return (360.0 / 65536) * ((int)(angle * (65536 / 360.0)) & 65535); +} + +/* +================= +AngleNormalize180 + +returns angle normalized to the range [-180 < angle <= 180] +================= +*/ +inline float AngleNormalize180 ( float angle ) { + angle = AngleNormalize360( angle ); + if ( angle > 180.0 ) { + angle -= 360.0; + } + return angle; +} + +/* +================= +AngleDelta + +returns the normalized delta from angle1 to angle2 +================= +*/ +inline float AngleDelta ( float angle1, float angle2 ) { + return AngleNormalize180( angle1 - angle2 ); +} + +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ); +#ifdef _XBOX +qboolean PlaneFromPoints( vec4_t plane, const short a[3], const short b[3], const short c[3] ); +#endif +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); +void RotateAroundDirection( vec3_t axis[3], float yaw ); +void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up ); +// perpendicular vector could be replaced by this + +int PlaneTypeForNormal (vec3_t normal); + +void MatrixMultiply(float in1[3][3], float in2[3][3], float out[3][3]); +void PerpendicularVector( vec3_t dst, const vec3_t src ); + + + +//============================================= + +float Com_Clamp( float min, float max, float value ); + +char *COM_SkipPath( char *pathname ); +void COM_StripExtension( const char *in, char *out ); +void COM_DefaultExtension( char *path, int maxSize, const char *extension ); + +//JLFCALLOUT include MPNOTUSED +#ifdef _XBOX +void COM_BeginParseSession( bool nested = false ); +#else +void COM_BeginParseSession( void ); +#endif + +int COM_GetCurrentParseLine( void ); +char *COM_Parse( const char **data_p ); +char *COM_ParseExt( const char **data_p, qboolean allowLineBreak ); +int COM_Compress( char *data_p ); +qboolean COM_ParseString( const char **data, const char **s ); +qboolean COM_ParseInt( const char **data, int *i ); +qboolean COM_ParseFloat( const char **data, float *f ); +qboolean COM_ParseVec4( const char **buffer, vec4_t *c); + +// data is an in/out parm, returns a parsed out token + +void COM_MatchToken( char**buf_p, char *match ); + +void SkipBracedSection (const char **program); +void SkipRestOfLine ( const char **data ); + +void Parse1DMatrix (const char **buf_p, int x, float *m); +void Parse2DMatrix (const char **buf_p, int y, int x, float *m); +void Parse3DMatrix (const char **buf_p, int z, int y, int x, float *m); + +void QDECL Com_sprintf (char *dest, int size, const char *fmt, ...); + + +// mode parm for FS_FOpenFile +typedef enum { + FS_READ, + FS_WRITE, + FS_APPEND, + FS_APPEND_SYNC +} fsMode_t; + +typedef enum { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +} fsOrigin_t; + +//============================================= + +int Q_isprint( int c ); +int Q_islower( int c ); +int Q_isupper( int c ); +int Q_isalpha( int c ); + +// portable case insensitive compare +//inline int Q_stricmp (const char *s1, const char *s2) {return Q_stricmpn (s1, s2, 99999);} +//int Q_strncmp (const char *s1, const char *s2, int n); +//int Q_stricmpn (const char *s1, const char *s2, int n); +//char *Q_strlwr( char *s1 ); +//char *Q_strupr( char *s1 ); +//char *Q_strrchr( const char* string, int c ); + +// NON-portable (but faster) versions +inline int Q_stricmp (const char *s1, const char *s2) { return stricmp(s1, s2); } +inline int Q_strncmp (const char *s1, const char *s2, int n) { return strncmp(s1, s2, n); } +inline int Q_stricmpn (const char *s1, const char *s2, int n) { return strnicmp(s1, s2, n); } +inline char *Q_strlwr( char *s1 ) { return strlwr(s1); } +inline char *Q_strupr( char *s1 ) { return strupr(s1); } +inline char *Q_strrchr( const char* str, int c ) { return strrchr(str, c); } + + +// buffer size safe library replacements +void Q_strncpyz( char *dest, const char *src, int destsize, qboolean bBarfIfTooLong=qfalse ); +void Q_strcat( char *dest, int size, const char *src ); + +// strlen that discounts Quake color sequences +int Q_PrintStrlen( const char *string ); +// removes color sequences from string +char *Q_CleanStr( char *string ); + +//============================================= + +#ifdef _M_IX86 +// +// optimised stuff for Intel, since most of our data is in that format anyway... +// +short BigShort(short l); +int BigLong (int l); +float BigFloat (float l); +#define LittleShort(l) l +#define LittleLong(l) l +#define LittleFloat(l) l +// +#else +// +// standard smart-swap code... +// +short BigShort(short l); +short LittleShort(short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); +// +#endif + + +void Swap_Init (void); +char * QDECL va(const char *format, ...); + +//============================================= + +// +// key / value info strings +// +char *Info_ValueForKey( const char *s, const char *key ); +void Info_RemoveKey( char *s, const char *key ); +void Info_SetValueForKey( char *s, const char *key, const char *value ); +qboolean Info_Validate( const char *s ); +void Info_NextPair( const char **s, char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ); + +// this is only here so the functions in q_shared.c and bg_*.c can link +void QDECL Com_Error( int level, const char *error, ... ); +void QDECL Com_Printf( const char *msg, ... ); + + +/* +========================================================== + +CVARS (console variables) + +Many variables can be used for cheating purposes, so when +cheats is zero, force all unspecified variables to their +default values. +========================================================== +*/ + +#define CVAR_TEMP 0 // can be set even when cheats are disabled, but is not archived +#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc + // used for system variables, not for player + // specific configurations +#define CVAR_USERINFO 2 // sent to server on connect or change +#define CVAR_SERVERINFO 4 // sent in response to front end requests +#define CVAR_SYSTEMINFO 8 // these cvars will be duplicated on all clients +#define CVAR_INIT 16 // don't allow change from console at all, + // but can be set from the command line +#define CVAR_LATCH 32 // will only change when C code next does + // a Cvar_Get(), so it can't be changed + // without proper initialization. modified + // will be set, even though the value hasn't + // changed yet +#define CVAR_ROM 64 // display only, cannot be set by user at all +#define CVAR_USER_CREATED 128 // created by a set command +#define CVAR_SAVEGAME 256 // store this in the savegame +#define CVAR_CHEAT 512 // can not be changed if cheats are disabled +#define CVAR_NORESTART 1024 // do not clear when a cvar_restart is issued +//JLF MPMOVED +#define CVAR_PROFILE 2048 //used to mark profile cvars. + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s { + char *name; + char *string; + char *resetString; // cvar_restart will reset to this value + char *latchedString; // for CVAR_LATCH vars + int flags; + qboolean modified; // set each time the cvar is changed + int modificationCount; // incremented each time the cvar is changed + float value; // atof( string ) + int integer; // atoi( string ) + struct cvar_s *next; +} cvar_t; + +#define MAX_CVAR_VALUE_STRING 256 + +typedef int cvarHandle_t; + +// the modules that run in the virtual machine can't access the cvar_t directly, +// so they must ask for structured updates +typedef struct { + cvarHandle_t handle; + int modificationCount; + float value; + int integer; + char string[MAX_CVAR_VALUE_STRING]; +} vmCvar_t; + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +#include "surfaceflags.h" // shared with the q3map utility + +// plane types are used to speed some tests +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 +#define PLANE_NON_AXIAL 3 + + +// 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: 0,1,2 = axial, 3 = nonaxial + byte signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision + byte pad[2]; +} cplane_t; + +/* +Ghoul2 Insert Start +*/ + +#if !defined(GHOUL2_SHARED_H_INC) + #include "..\game\ghoul2_shared.h" //for CGhoul2Info_v +#endif + +/* +Ghoul2 Insert End +*/ + +#define MAX_G2_COLLISIONS 16 +// 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, transformed to world space + int surfaceFlags; // surface hit + int contents; // contents on other side of surface hit + int entityNum; // entity the contacted sirface is a part of +/* +Ghoul2 Insert Start +*/ + CCollisionRecord G2CollisionMap[MAX_G2_COLLISIONS]; // map that describes all of the parts of ghoul2 models that got hit +/* +Ghoul2 Insert End +*/ +} trace_t; + +// trace->entityNum can also be 0 to (MAX_GENTITIES-1) +// or ENTITYNUM_NONE, ENTITYNUM_WORLD + + +// markfragments are returned by CM_MarkFragments() +typedef struct { + int firstPoint; + int numPoints; +} markFragment_t; + + + +typedef struct { + vec3_t origin; + vec3_t axis[3]; +} orientation_t; + +//===================================================================== + + +// in order from highest priority to lowest +// if none of the catchers are active, bound key strings will be executed +#define KEYCATCH_CONSOLE 1 +#define KEYCATCH_UI 2 +#define KEYCATCH_MESSAGE 4 + + +// sound channels +// channel 0 never willingly overrides +// other channels will allways override a playing sound on that channel +#include "channels.h" + +/* +======================================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +======================================================================== +*/ + +#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535) +#define SHORT2ANGLE(x) ((x)*(360.0/65536)) + +#define SNAPFLAG_RATE_DELAYED 1 +#define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies +#define SNAPFLAG_SERVERCOUNT 4 // toggled every map_restart so transitions can be detected + +// +// per-level limits +// +#define MAX_CLIENTS 1 // 128 // absolute limit +#define MAX_TERRAINS 1 //32 + +#define GENTITYNUM_BITS 10 // don't need to send any more +#define MAX_GENTITIES (1< MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +#define MAX_GAMESTATE_CHARS 16000 +typedef struct { + int stringOffsets[MAX_CONFIGSTRINGS]; + char stringData[MAX_GAMESTATE_CHARS]; + int dataCount; +} gameState_t; + +typedef enum +{ + FP_FIRST = 0,//marker + FP_HEAL = 0,//instant + FP_LEVITATION,//hold/duration + FP_SPEED,//duration + FP_PUSH,//hold/duration + FP_PULL,//hold/duration + FP_TELEPATHY,//instant + FP_GRIP,//hold/duration + FP_LIGHTNING,//hold/duration + FP_SABERTHROW, + FP_SABER_DEFENSE, + FP_SABER_OFFENSE, + //new Jedi Academy powers + FP_RAGE,//duration - speed, invincibility and extra damage for short period, drains your health and leaves you weak and slow afterwards. + FP_PROTECT,//duration - protect against physical/energy (level 1 stops blaster/energy bolts, level 2 stops projectiles, level 3 protects against explosions) + FP_ABSORB,//duration - protect against dark force powers (grip, lightning, drain - maybe push/pull, too?) + FP_DRAIN,//hold/duration - drain force power for health + FP_SEE,//duration - detect/see hidden enemies + NUM_FORCE_POWERS +} forcePowers_t; + +typedef enum +{ + SABER_NONE = 0, + SABER_SINGLE, + SABER_STAFF, + SABER_DAGGER, + SABER_BROAD, + SABER_PRONG, + SABER_ARC, + SABER_SAI, + SABER_CLAW, + SABER_LANCE, + SABER_STAR, + SABER_TRIDENT, + SABER_SITH_SWORD, + NUM_SABERS +} saberType_t; + +//========================================================= + +// bit field limits +#define MAX_STATS 16 + +// NOTE!!! be careful about altering this because although it's used to define an array size, the entry indexes come from +// the typedef'd enum "persEnum_t" in bg_public.h, and there's no compile-tie between the 2 -slc +// +#define MAX_PERSISTANT 16 + +#define MAX_POWERUPS 16 +#define MAX_WEAPONS 32 +#define MAX_AMMO 10 +#define MAX_INVENTORY 15 // See INV_MAX +#define MAX_SECURITY_KEYS 5 +#define MAX_SECURITY_KEY_MESSSAGE 24 + +#define MAX_PS_EVENTS 2 // this must be a power of 2 unless you change some &'s to %'s -ste + + +#define MAX_WORLD_COORD ( 64*1024 ) +#define MIN_WORLD_COORD ( -64*1024 ) +#define WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD ) + +typedef enum +{ + WHL_NONE, + WHL_ANKLES, + WHL_KNEES, + WHL_WAIST, + WHL_TORSO, + WHL_SHOULDERS, + WHL_HEAD, + WHL_UNDER +} waterHeightLevel_t; + +// !!!!!!! loadsave affecting struct !!!!!!! +typedef struct +{ + // Actual trail stuff + int inAction; // controls whether should we even consider starting one + int duration; // how long each trail seg stays in existence + int lastTime; // time a saber segement was last stored + vec3_t base; + vec3_t tip; + + // Marks stuff + qboolean haveOldPos[2]; + vec3_t oldPos[2]; + vec3_t oldNormal[2]; // store this in case we don't have a connect-the-dots situation + // ..then we'll need the normal to project a mark blob onto the impact point +} saberTrail_t; +#define MAX_SABER_TRAIL_SEGS 8 + +// !!!!!!!!!!!!! loadsave affecting struct !!!!!!!!!!!!!!! +typedef struct +{ + qboolean active; + saber_colors_t color; + float radius; + float length; + float lengthMax; + float lengthOld; + vec3_t muzzlePoint; + vec3_t muzzlePointOld; + vec3_t muzzleDir; + vec3_t muzzleDirOld; + saberTrail_t trail; + void ActivateTrail ( float duration ) + { + trail.inAction = qtrue; + trail.duration = duration; + }; + void DeactivateTrail ( float duration ) + { + trail.inAction = qfalse; + trail.duration = duration; + }; +} bladeInfo_t; +#define MAX_BLADES 8 + +typedef enum +{ + SS_NONE = 0, + SS_FAST, + SS_MEDIUM, + SS_STRONG, + SS_DESANN, + SS_TAVION, + SS_DUAL, + SS_STAFF, + SS_NUM_SABER_STYLES +} saber_styles_t; + +// !!!!!!!!!!!! loadsave affecting struct !!!!!!!!!!!!!!!!!!!!!!!!!! +typedef struct +{ + char *name; //entry in sabers.cfg, if any + char *fullName; //the "Proper Name" of the saber, shown in the UI + saberType_t type; //none, single or staff + char *model; //hilt model + char *skin; //hilt custom skin + int soundOn; //game soundindex for turning on sound + int soundLoop; //game soundindex for hum/loop sound + int soundOff; //game soundindex for turning off sound + int numBlades; + bladeInfo_t blade[MAX_BLADES]; //blade info - like length, trail, origin, dir, etc. + saber_styles_t style; //locked style to use, if any + int maxChain; //how many moves can be chained in a row with this weapon (-1 is infinite, 0 is use default behavior) + qboolean lockable; //can get into a saberlock + qboolean throwable; //whether or not this saber can be thrown - FIXME: maybe make this a max level of force saber throw that can be used with this saber? + qboolean disarmable; //whether or not this saber can be dropped + qboolean activeBlocking; //whether or not to try to block incoming shots with this saber + qboolean twoHanded; //uses both hands + int forceRestrictions; //force powers that cannot be used while this saber is on (bitfield) - FIXME: maybe make this a limit on the max level, per force power, that can be used with this type? + int lockBonus; //in saberlocks, this type of saber pushes harder or weaker + int parryBonus; //added to strength of parry with this saber + int breakParryBonus; //added to strength when hit a parry + int disarmBonus; //added to disarm chance when win saberlock or have a good parry (knockaway) + saber_styles_t singleBladeStyle; //makes it so that you use a different style if you only have the first blade active + qboolean singleBladeThrowable; //makes it so that you can throw this saber if only the first blade is on + char *brokenSaber1; //if saber is actually hit by another saber, it can be cut in half/broken and will be replaced with this saber in your right hand + char *brokenSaber2; //if saber is actually hit by another saber, it can be cut in half/broken and will be replaced with this saber in your left hand + qboolean returnDamage; //when returning from a saber throw, it keeps spinning and doing damage + void Activate( void ) + { + for ( int i = 0; i < numBlades; i++ ) + { + blade[i].active = qtrue; + } + }; + + void Deactivate( void ) + { + for ( int i = 0; i < numBlades; i++ ) + { + blade[i].active = qfalse; + } + }; + + // Description: Activate a specific Blade of this Saber. + // Created: 10/03/02 by Aurelio Reis, Modified: 10/03/02 by Aurelio Reis. + // [in] int iBlade Which Blade to activate. + // [in] bool bActive Whether to activate it (default true), or deactivate it (false). + // [return] void + void BladeActivate( int iBlade, qboolean bActive = qtrue ) + { + // Validate blade ID/Index. + if ( iBlade < 0 || iBlade >= numBlades ) + return; + + blade[iBlade].active = bActive; + } + + qboolean Active() + { + for ( int i = 0; i < numBlades; i++ ) + { + if ( blade[i].active ) + { + return qtrue; + } + } + return qfalse; + } + void SetLength( float length ) + { + for ( int i = 0; i < numBlades; i++ ) + { + blade[i].length = length; + } + } + float Length() + {//return largest length + float len1 = 0; + for ( int i = 0; i < numBlades; i++ ) + { + if ( blade[i].length > len1 ) + { + len1 = blade[i].length; + } + } + return len1; + }; + float LengthMax() + { + float len1 = 0; + for ( int i = 0; i < numBlades; i++ ) + { + if ( blade[i].lengthMax > len1 ) + { + len1 = blade[i].lengthMax; + } + } + return len1; + }; + void ActivateTrail ( float duration ) + { + for ( int i = 0; i < numBlades; i++ ) + { + blade[i].ActivateTrail( duration ); + } + }; + void DeactivateTrail ( float duration ) + { + for ( int i = 0; i < numBlades; i++ ) + { + blade[i].DeactivateTrail( duration ); + } + }; +} saberInfo_t; + + +#define MAX_SABERS 2 // if this ever changes then update the table "static const save_field_t savefields_gClient[]"!!!!!!!!!!!! + +// playerState_t is the information needed by both the client and server +// to predict player motion and actions +// nothing outside of pmove should modify these, or some degree of prediction error +// will occur + +// you can't add anything to this without modifying the code in msg.c + +// playerState_t is a full superset of entityState_t as it is used by players, +// so if a playerState_t is transmitted, the entityState_t can be fully derived +// from it. +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct playerState_s { + int commandTime; // cmd->serverTime of last executed command + int pm_type; + int bobCycle; // for view bobbing and footstep generation + int pm_flags; // ducked, jump_held, etc + int pm_time; + + vec3_t origin; + vec3_t velocity; + int weaponTime; + int weaponChargeTime; + int rechargeTime; // for the phaser + int gravity; + int leanofs; + int friction; + int speed; + int delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters + + int groundEntityNum;// ENTITYNUM_NONE = in air + int legsAnim; // + int legsAnimTimer; // don't change low priority animations on legs until this runs out + int torsoAnim; // + int torsoAnimTimer; // don't change low priority animations on torso until this runs out + int movementDir; // a number 0 to 7 that represents the relative angle + // of movement to the view angle (axial and diagonals) + // when at rest, the value will remain unchanged + // used to twist the legs during strafing + + int eFlags; // copied to entityState_t->eFlags + + int eventSequence; // pmove generated events + int events[MAX_PS_EVENTS]; + int eventParms[MAX_PS_EVENTS]; + + int externalEvent; // events set on player from another source + int externalEventParm; + int externalEventTime; + + int clientNum; // ranges from 0 to MAX_CLIENTS-1 + int weapon; // copied to entityState_t->weapon + int weaponstate; + + int batteryCharge; + + vec3_t viewangles; // for fixed views + float legsYaw; // actual legs forward facing + int viewheight; + + // damage feedback + int damageEvent; // when it changes, latch the other parms + int damageYaw; + int damagePitch; + int damageCount; + + int stats[MAX_STATS]; + int persistant[MAX_PERSISTANT]; // stats that aren't cleared on death + int powerups[MAX_POWERUPS]; // level.time that the powerup runs out + int ammo[MAX_AMMO]; + int inventory[MAX_INVENTORY]; // Count of each inventory item. + char security_key_message[MAX_SECURITY_KEYS][MAX_SECURITY_KEY_MESSSAGE]; // Security key types + + vec3_t serverViewOrg; + + qboolean saberInFlight; + + int viewEntity; // For overriding player movement controls and vieworg + int forcePowersActive; //prediction needs to know this + + //NEW vehicle stuff + // This has been localized to the vehicle stuff (NOTE: We can still use it later, I'm just commenting it to + // root out all the calls. We can store the data in vehicles and update by copying it here). + //int vehicleIndex; // Index into vehicleData table + //vec3_t vehicleAngles; // current angles of your vehicle + //int vehicleArmor; // current armor of your vehicle (explodes if drops to 0) + + // !! + // not communicated over the net at all + // !! + //int vehicleLastFXTime; //timer for all cgame-FX...? + //int vehicleExplodeTime; //when it will go BOOM! + + int useTime; //not sent + int lastShotTime;//last time you shot your weapon + int ping; // server to game info for scoreboard + int lastOnGround; //last time you were on the ground + int lastStationary; //last time you were on the ground + int weaponShotCount; + + //FIXME: maybe allocate all these structures (saber, force powers, vehicles) + // or descend them as classes - so not every client has all this info + saberInfo_t saber[MAX_SABERS]; + qboolean dualSabers; + qboolean SaberStaff( void ) { return ( saber[0].type == SABER_STAFF || (dualSabers && saber[1].type == SABER_STAFF) ); }; + qboolean SaberActive() { return ( saber[0].Active() || (dualSabers&&saber[1].Active()) ); }; + void SetSaberLength( float length ) + { + saber[0].SetLength( length ); + if ( dualSabers ) + { + saber[1].SetLength( length ); + } + } + float SaberLength() + {//return largest length + float len1 = saber[0].Length(); + if ( dualSabers && saber[1].Length() > len1 ) + { + return saber[1].Length(); + } + return len1; + }; + float SaberLengthMax() + { + if ( saber[0].LengthMax() > saber[1].LengthMax() ) + { + return saber[0].LengthMax(); + } + else if ( dualSabers ) + { + return saber[1].LengthMax(); + } + return 0.0f; + }; + + // Activate or deactivate a specific Blade of a Saber. + // Created: 10/03/02 by Aurelio Reis, Modified: 10/03/02 by Aurelio Reis. + // [in] int iSaber Which Saber to modify. + // [in] int iBlade Which blade to modify (0 - (NUM_BLADES - 1)). + // [in] bool bActive Whether to make it active (default true) or inactive (false). + // [return] void + void SaberBladeActivate( int iSaber, int iBlade, qboolean bActive = qtrue ) + { + // Validate saber (if it's greater than or equal to zero, OR it above the first saber but we + // are not doing duel Sabers, leave, something is not right. + if ( iSaber < 0 || ( iSaber > 0 && !dualSabers ) ) + return; + + saber[iSaber].BladeActivate( iBlade, bActive ); + } + + void SaberActivate( void ) + { + saber[0].Activate(); + if ( dualSabers ) + { + saber[1].Activate(); + } + } + void SaberDeactivate( void ) + { + saber[0].Deactivate(); + saber[1].Deactivate(); + }; + void SaberActivateTrail ( float duration ) + { + saber[0].ActivateTrail( duration ); + if ( dualSabers ) + { + saber[1].ActivateTrail( duration ); + } + }; + void SaberDeactivateTrail ( float duration ) + { + saber[0].DeactivateTrail( duration ); + if ( dualSabers ) + { + saber[1].DeactivateTrail( duration ); + } + }; + int SaberDisarmBonus( void ) + { + int disarmBonus = 0; + if ( saber[0].Active() ) + { + disarmBonus += saber[0].disarmBonus; + } + if ( dualSabers && saber[1].Active() ) + {//bonus for having 2 sabers + disarmBonus += 1 + saber[1].disarmBonus; + } + return disarmBonus; + }; + int SaberParryBonus( void ) + { + int parryBonus = 0; + if ( saber[0].Active() ) + { + parryBonus += saber[0].parryBonus; + } + if ( dualSabers && saber[1].Active() ) + {//bonus for having 2 sabers + parryBonus += 1 + saber[1].parryBonus; + } + return parryBonus; + }; + + short saberMove; + short saberMoveNext; + short saberBounceMove; + short saberBlocking; + short saberBlocked; + short leanStopDebounceTime; + + int saberEntityNum; + float saberEntityDist; + int saberThrowTime; + int saberEntityState; + int saberDamageDebounceTime; + int saberHitWallSoundDebounceTime; + int saberEventFlags; + int saberBlockingTime; + int saberAnimLevel; + int saberAttackChainCount; + int saberLockTime; + int saberLockEnemy; + int saberStylesKnown; + + int forcePowersKnown; + int forcePowerDuration[NUM_FORCE_POWERS]; //for effects that have a duration + int forcePowerDebounce[NUM_FORCE_POWERS]; //for effects that must have an interval + int forcePower; + int forcePowerMax; + int forcePowerRegenDebounceTime; + int forcePowerRegenRate; //default is 100ms + int forcePowerRegenAmount; //default is 1 + int forcePowerLevel[NUM_FORCE_POWERS]; //so we know the max forceJump power you have + float forceJumpZStart; //So when you land, you don't get hurt as much + float forceJumpCharge; //you're current forceJump charge-up level, increases the longer you hold the force jump button down + int forceGripEntityNum; //what entity I'm gripping + vec3_t forceGripOrg; //where the gripped ent should be lifted to + int forceDrainEntityNum; //what entity I'm draining + vec3_t forceDrainOrg; //where the drained ent should be lifted to + int forceHealCount; //how many points of force heal have been applied so far + + //new Jedi Academy force powers + int forceAllowDeactivateTime; + int forceRageDrainTime; + int forceRageRecoveryTime; + int forceDrainEntNum; + float forceDrainTime; + int forcePowersForced; //client is being forced to use these powers (FIXME: and only these?) + int pullAttackEntNum; + int pullAttackTime; + int lastKickedEntNum; + + int taunting; //replaced BUTTON_GESTURE + + float jumpZStart; //So when you land, you don't get hurt as much + vec3_t moveDir; + + float waterheight; //exactly what the z org of the water is (will be +4 above if under water, -4 below if not in water) + waterHeightLevel_t waterHeightLevel; //how high it really is + + //testing IK grabbing + qboolean ikStatus; //for IK + int heldClient; //for IK, who I'm grabbing, if anyone + int heldByClient; //for IK, someone is grabbing me + int heldByBolt; //for IK, what bolt I'm attached to on the holdersomeone is grabbing me by + int heldByBone; //for IK, what bone I'm being held by + + //vehicle turn-around stuff... FIXME: only vehicles need this in SP... + int vehTurnaroundIndex; + int vehTurnaroundTime; + + //NOTE: not really used in SP, just for Fighter Vehicle damage stuff + int brokenLimbs; + int electrifyTime; +} playerState_t; + + +//==================================================================== + + +// +// usercmd_t->button bits, many of which are generated by the client system, +// so they aren't game/cgame only definitions +// +#define BUTTON_ATTACK 1 +#define BUTTON_FORCE_LIGHTNING 2 // displays talk balloon and disables actions +#define BUTTON_USE_FORCE 4 +#define BUTTON_FORCE_DRAIN 8 // draining +#define BUTTON_VEH_SPEED 8 // used for some horrible vehicle hack... :) +#define BUTTON_WALKING 16 // walking can't just be infered from MOVE_RUN because a key pressed late in the frame will + // only generate a small move value for that frame walking will use different animations and + // won't generate footsteps +#define BUTTON_USE 32 // the ol' use key returns! +#define BUTTON_FORCEGRIP 64 // +#define BUTTON_ALT_ATTACK 128 + +#define BUTTON_FORCE_FOCUS 256 // any key whatsoever + +#define MOVE_RUN 120 // if forwardmove or rightmove are >= MOVE_RUN, + // then BUTTON_WALKING should be set + + +typedef enum +{ + GENCMD_FORCE_HEAL = 1, + GENCMD_FORCE_SPEED, + GENCMD_FORCE_THROW, + GENCMD_FORCE_PULL, + GENCMD_FORCE_DISTRACT, + GENCMD_FORCE_GRIP, + GENCMD_FORCE_LIGHTNING, + GENCMD_FORCE_RAGE, + GENCMD_FORCE_PROTECT, + GENCMD_FORCE_ABSORB, + GENCMD_FORCE_DRAIN, + GENCMD_FORCE_SEEING, +} genCmds_t; + + +// usercmd_t is sent to the server each client frame +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct usercmd_s { + int serverTime; + int buttons; + byte weapon; + int angles[3]; + byte generic_cmd; + signed char forwardmove, rightmove, upmove; +} usercmd_t; + +//=================================================================== + +// if entityState->solid == SOLID_BMODEL, modelindex is an inline model number +#define SOLID_BMODEL 0xffffff + +typedef enum {// !!!!!!!!!!! LOADSAVE-affecting struct !!!!!!!!!! + TR_STATIONARY, + TR_INTERPOLATE, // non-parametric, but interpolate between snapshots + TR_LINEAR, + TR_LINEAR_STOP, + TR_NONLINEAR_STOP, + TR_SINE, // value = base + sin( time / duration ) * delta + TR_GRAVITY +} trType_t; + +typedef struct {// !!!!!!!!!!! LOADSAVE-affecting struct !!!!!!!!!! + trType_t trType; + int trTime; + int trDuration; // if non 0, trTime + trDuration = stop time + vec3_t trBase; + vec3_t trDelta; // velocity, etc +} trajectory_t; + + +// entityState_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +// Different eTypes may use the information in different ways +// The messages are delta compressed, so it doesn't really matter if +// the structure size is fairly large + +typedef struct entityState_s {// !!!!!!!!!!! LOADSAVE-affecting struct !!!!!!!!!!!!! + int number; // entity index + int eType; // entityType_t + int eFlags; + + trajectory_t pos; // for calculating position + trajectory_t apos; // for calculating angles + + int time; + int time2; + + vec3_t origin; + vec3_t origin2; + + vec3_t angles; + vec3_t angles2; + + int otherEntityNum; // shotgun sources, etc + int otherEntityNum2; + + int groundEntityNum; // -1 = in air + + int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24) + int loopSound; // constantly loop this sound + + int modelindex; + int modelindex2; + int modelindex3; + int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses + int frame; + + int solid; // for client side prediction, gi.linkentity sets this properly + + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; + + // for players + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // + int legsAnimTimer; // don't change low priority animations on legs until this runs out + int torsoAnim; // + int torsoAnimTimer; // don't change low priority animations on torso until this runs out + + int scale; //Scale players + + //FIXME: why did IMMERSION dupe these 2 fields here? There's no reason for this!!! + qboolean saberInFlight; + qboolean saberActive; + + //int vehicleIndex; // What kind of vehicle you're driving + vec3_t vehicleAngles; // + int vehicleArmor; // current armor of your vehicle (explodes if drops to 0) + // 0 if not in a vehicle, otherwise the client number. + int m_iVehicleNum; + +/* +Ghoul2 Insert Start +*/ + vec3_t modelScale; // used to scale models in any axis + int radius; // used for culling all the ghoul models attached to this ent NOTE - this is automatically scaled by Ghoul2 if/when you scale the model. This is a 100% size value + int boltInfo; // info used for bolting entities to Ghoul2 models - NOT used for bolting ghoul2 models to themselves, more for stuff like bolting effects to ghoul2 models +/* +Ghoul2 Insert End +*/ + + qboolean isPortalEnt; + +} entityState_t; + +typedef enum { + CA_UNINITIALIZED, + CA_DISCONNECTED, // not talking to a server + CA_CONNECTING, // sending request packets to the server + CA_CHALLENGING, // sending challenge packets to the server + CA_CONNECTED, // netchan_t established, getting gamestate + CA_LOADING, // only during cgame initialization, never during main loop + CA_PRIMED, // got gamestate, waiting for first frame + CA_ACTIVE, // game views should be displayed + CA_CINEMATIC // playing a cinematic or a static pic, not connected to a server +} connstate_t; + +typedef struct SSkinGoreData_s +{ + vec3_t angles; + vec3_t position; + int currentTime; + int entNum; + vec3_t rayDirection; // in world space + vec3_t hitLocation; // in world space + vec3_t scale; + float SSize; // size of splotch in the S texture direction in world units + float TSize; // size of splotch in the T texture direction in world units + float theta; // angle to rotate the splotch + vec3_t uaxis; //mark direction + float depthStart; // limit marks begin depth + float depthEnd; // depth to stop making marks + + bool useTheta; + bool frontFaces; + bool backFaces; + bool fadeRGB; //specify fade method to modify RGB (by default, the alpha is set instead) + + // growing stuff + int growDuration; // time over which we want this to scale up, set to -1 for no scaling + float goreScaleStartFraction; // fraction of the final size at which we want the gore to initially appear + + //qboolean baseModelOnly; + int lifeTime; // effect expires after this amount of time + int fadeOutTime; //specify the duration of fading, from the lifeTime (e.g. 3000 will start fading 3 seconds before removal and be faded entirely by removal) + //int shrinkOutTime; // unimplemented + //float alphaModulate; // unimplemented + //vec3_t tint; // unimplemented + //float impactStrength; // unimplemented + + int shader; // shader handle + + int myIndex; // used internally + +} SSkinGoreData; + +//rww - used for my ik stuff (ported directly from mp) +typedef struct +{ + vec3_t angles; + vec3_t position; + vec3_t scale; + vec3_t velocity; + int me; +} sharedRagDollUpdateParams_t; + +//rww - update parms for ik bone stuff +typedef struct +{ + char boneName[512]; //name of bone + vec3_t desiredOrigin; //world coordinate that this bone should be attempting to reach + vec3_t origin; //world coordinate of the entity who owns the g2 instance that owns the bone + float movementSpeed; //how fast the bone should move toward the destination +} sharedIKMoveParams_t; + + +typedef struct +{ + vec3_t pcjMins; //ik joint limit + vec3_t pcjMaxs; //ik joint limit + vec3_t origin; //origin of caller + vec3_t angles; //angles of caller + vec3_t scale; //scale of caller + float radius; //bone rad + int blendTime; //bone blend time + int pcjOverrides; //override ik bone flags + int startFrame; //base pose start + int endFrame; //base pose end +} sharedSetBoneIKStateParams_t; + +enum sharedEIKMoveState +{ + IKS_NONE = 0, + IKS_DYNAMIC +}; + +/* +======================================================================== + +String ID Tables + +======================================================================== +*/ +typedef struct stringID_table_s +{ + char *name; + int id; +} stringID_table_t; + +int GetIDForString ( const stringID_table_t *table, const char *string ); +const char *GetStringForID( const stringID_table_t *table, int id ); + +// savegame screenshot size stuff... +// +//#define SG_SCR_WIDTH 512 //256 +//#define SG_SCR_HEIGHT 512 //256 +#define iSG_COMMENT_SIZE 64 + +#define sCVARNAME_PLAYERSAVE "playersave" // used for level-transition, accessed by game and server modules + + +/* +Ghoul2 Insert Start +*/ + +// For ghoul2 axis use + +enum Eorientations +{ + ORIGIN = 0, + POSITIVE_X, + POSITIVE_Z, + POSITIVE_Y, + NEGATIVE_X, + NEGATIVE_Z, + NEGATIVE_Y +}; +/* +Ghoul2 Insert End +*/ + +#define MAX_PARSEFILES 16 +typedef struct parseData_s +{ + char fileName[MAX_QPATH]; // Name of current file being read in + int com_lines; // Number of lines read in + const char *bufferStart; // Start address of buffer holding data that was read in + const char *bufferCurrent; // Where data is currently being parsed from buffer +} parseData_t; + +//JFLCALLOUT include +//changed to array +extern parseData_t parseData[]; +extern int parseDataCount; + + +// cinematic states +typedef enum { + FMV_IDLE, + FMV_PLAY, // play + FMV_EOF, // all other conditions, i.e. stop/EOF/abort + FMV_ID_BLT, + FMV_ID_IDLE, + FMV_LOOPED, + FMV_ID_WAIT +} e_status; + + +// define the new memory tags for the zone, used by all modules now +// +#define TAGDEF(blah) TAG_ ## blah +enum { + #include "../qcommon/tags.h" +}; +typedef char memtag_t; + +// stuff to help out during development process, force reloading/uncacheing of certain filetypes... +// +typedef enum +{ + eForceReload_NOTHING, + eForceReload_BSP, + eForceReload_MODELS, + eForceReload_ALL + +} ForceReload_e; + + +#include "../game/genericparser2.h" + +#ifdef _IMMERSION +#include "../ff/ff_public.h" +#endif // _IMMERSION + +#endif // __Q_SHARED_H diff --git a/code/game/say.h b/code/game/say.h new file mode 100644 index 0000000..78f61a6 --- /dev/null +++ b/code/game/say.h @@ -0,0 +1,30 @@ +#ifndef __SAY_H__ +#define __SAY_H__ + +typedef enum //# saying_e +{ + //Acknowledge command + SAY_ACKCOMM1, + SAY_ACKCOMM2, + SAY_ACKCOMM3, + SAY_ACKCOMM4, + //Refuse command + SAY_REFCOMM1, + SAY_REFCOMM2, + SAY_REFCOMM3, + SAY_REFCOMM4, + //Bad command + SAY_BADCOMM1, + SAY_BADCOMM2, + SAY_BADCOMM3, + SAY_BADCOMM4, + //Unfinished hail + SAY_BADHAIL1, + SAY_BADHAIL2, + SAY_BADHAIL3, + SAY_BADHAIL4, + //# #eol + NUM_SAYINGS +} saying_t; + +#endif //#ifndef __SAY_H__ diff --git a/code/game/statindex.h b/code/game/statindex.h new file mode 100644 index 0000000..518d85c --- /dev/null +++ b/code/game/statindex.h @@ -0,0 +1,26 @@ +// Filename: statindex.h +// +// accessed from both server and game modules + +#ifndef STATINDEX_H +#define STATINDEX_H + + +// player_state->stats[] indexes +typedef enum { + STAT_HEALTH, + STAT_ITEMS, + STAT_WEAPONS, // 16 bit fields + STAT_ARMOR, + STAT_DEAD_YAW, // look this direction when dead (FIXME: get rid of?) + STAT_CLIENTS_READY, // bit mask of clients wishing to exit the intermission (FIXME: configstring?) + STAT_MAX_HEALTH // health / armor limit, changable by handicap +} statIndex_t; + + + +#endif // #ifndef STATINDEX_H + + +/////////////////////// eof ///////////////////// + diff --git a/code/game/surfaceflags.h b/code/game/surfaceflags.h new file mode 100644 index 0000000..5f41735 --- /dev/null +++ b/code/game/surfaceflags.h @@ -0,0 +1,190 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// This file must be identical in the quake and utils directories + +// contents flags are seperate bits +// a given brush can contribute multiple content bits + +// these definitions also need to be in q_shared.h! + +#if 1 +// flags pasted from SOF2, be VERY CAREFUL when adding new ones since we share their tools!!! +#define CONTENTS_NONE 0x00000000 +#define CONTENTS_SOLID 0x00000001 // Default setting. An eye is never valid in a solid +#define CONTENTS_LAVA 0x00000002 +#define CONTENTS_WATER 0x00000004 +#define CONTENTS_FOG 0x00000008 +#define CONTENTS_PLAYERCLIP 0x00000010 // Player physically blocked +#define CONTENTS_MONSTERCLIP 0x00000020 // NPCs cannot physically pass through +#define CONTENTS_BOTCLIP 0x00000040 // do not enter - NPCs try not to enter these +#define CONTENTS_SHOTCLIP 0x00000080 // shots physically blocked +#define CONTENTS_BODY 0x00000100 // should never be on a brush, only in game +#define CONTENTS_CORPSE 0x00000200 // should never be on a brush, only in game +#define CONTENTS_TRIGGER 0x00000400 +#define CONTENTS_NODROP 0x00000800 // don't leave bodies or items (death fog, lava) +#define CONTENTS_TERRAIN 0x00001000 // volume contains terrain data +#define CONTENTS_LADDER 0x00002000 +#define CONTENTS_ABSEIL 0x00004000 // used like ladder to define where an NPC can abseil +#define CONTENTS_OPAQUE 0x00008000 // defaults to on, when off, solid can be seen through +#define CONTENTS_OUTSIDE 0x00010000 // volume is considered to be in the outside (i.e. not indoors) +// CONTENTS_INSIDE added 10/31/02 by Aurelio. +#define CONTENTS_INSIDE 0x10000000 // volume is considered to be inside (i.e. indoors) +#define CONTENTS_SLIME 0x00020000 // CHC needs this since we use same tools +#define CONTENTS_LIGHTSABER 0x00040000 // "" +#define CONTENTS_TELEPORTER 0x00080000 // "" +#define CONTENTS_ITEM 0x00100000 // "" +#define CONTENTS_DETAIL 0x08000000 // brushes not used for the bsp +#define CONTENTS_TRANSLUCENT 0x80000000 // don't consume surface fragments inside + + +// flags pasted from SOF2, be VERY CAREFUL when adding new ones since we share their tools!!! + +#define SURF_SKY 0x00002000 // lighting from environment map +#define SURF_SLICK 0x00004000 // affects game physics +#define SURF_METALSTEPS 0x00008000 // CHC needs this since we use same tools (though this flag is temp?) +#define SURF_FORCEFIELD 0x00010000 // CHC "" (but not temp) +#define SURF_NODAMAGE 0x00040000 // never give falling damage +#define SURF_NOIMPACT 0x00080000 // don't make missile explosions +#define SURF_NOMARKS 0x00100000 // don't leave missile marks +#define SURF_NODRAW 0x00200000 // don't generate a drawsurface at all +#define SURF_NOSTEPS 0x00400000 // no footstep sounds +#define SURF_NODLIGHT 0x00800000 // don't dlight even if solid (solid lava, skies) +#define SURF_NOMISCENTS 0x01000000 // no client models allowed on this surface +#define SURF_FORCESIGHT 0x02000000 // not visible without Force Sight + +#define SURF_PATCH 0x80000000 // Mark this face as a patch(editor only) + +#define MATERIAL_BITS 5 +#define MATERIAL_MASK 0x1f // mask to get the material type + +#define MATERIAL_NONE 0 // for when the artist hasn't set anything up =) +#define MATERIAL_SOLIDWOOD 1 // freshly cut timber +#define MATERIAL_HOLLOWWOOD 2 // termite infested creaky wood +#define MATERIAL_SOLIDMETAL 3 // solid girders +#define MATERIAL_HOLLOWMETAL 4 // hollow metal machines +#define MATERIAL_SHORTGRASS 5 // manicured lawn +#define MATERIAL_LONGGRASS 6 // long jungle grass +#define MATERIAL_DIRT 7 // hard mud +#define MATERIAL_SAND 8 // sandy beach +#define MATERIAL_GRAVEL 9 // lots of small stones +#define MATERIAL_GLASS 10 // +#define MATERIAL_CONCRETE 11 // hardened concrete pavement +#define MATERIAL_MARBLE 12 // marble floors +#define MATERIAL_WATER 13 // light covering of water on a surface +#define MATERIAL_SNOW 14 // freshly laid snow +#define MATERIAL_ICE 15 // packed snow/solid ice +#define MATERIAL_FLESH 16 // hung meat, corpses in the world +#define MATERIAL_MUD 17 // wet soil +#define MATERIAL_BPGLASS 18 // bulletproof glass +#define MATERIAL_DRYLEAVES 19 // dried up leaves on the floor +#define MATERIAL_GREENLEAVES 20 // fresh leaves still on a tree +#define MATERIAL_FABRIC 21 // Cotton sheets +#define MATERIAL_CANVAS 22 // tent material +#define MATERIAL_ROCK 23 // +#define MATERIAL_RUBBER 24 // hard tire like rubber +#define MATERIAL_PLASTIC 25 // +#define MATERIAL_TILES 26 // tiled floor +#define MATERIAL_CARPET 27 // lush carpet +#define MATERIAL_PLASTER 28 // drywall style plaster +#define MATERIAL_SHATTERGLASS 29 // glass with the Crisis Zone style shattering +#define MATERIAL_ARMOR 30 // body armor +#define MATERIAL_COMPUTER 31 // computers/electronic equipment +#define MATERIAL_LAST 32 // number of materials + +// Defined as a macro here so one change will affect all the relevant files + +#define MATERIALS \ + "none", \ + "solidwood", \ + "hollowwood", \ + "solidmetal", \ + "hollowmetal", \ + "shortgrass", \ + "longgrass", \ + "dirt", \ + "sand", \ + "gravel", \ + "glass", \ + "concrete", \ + "marble", \ + "water", \ + "snow", \ + "ice", \ + "flesh", \ + "mud", \ + "bpglass", \ + "dryleaves", \ + "greenleaves", \ + "fabric", \ + "canvas", \ + "rock", \ + "rubber", \ + "plastic", \ + "tiles", \ + "carpet", \ + "plaster", \ + "shatterglass", \ + "armor", \ + "computer"/* this was missing, see enums above, plus ShaderEd2 pulldown options */ +#else +/* +#define CONTENTS_NONE 0 +#define CONTENTS_SOLID 1 // an eye is never valid in a solid +#define CONTENTS_LAVA 8 +#define CONTENTS_SLIME 16 +#define CONTENTS_WATER 32 +#define CONTENTS_FOG 64 +#define CONTENTS_LADDER 128 + +#define CONTENTS_LIGHTSABER 0x1000 // Used for lightsaber buddies, blocking, deflecting. + +#define CONTENTS_AREAPORTAL 0x8000 + +Ghoul2 Insert Start + +#define CONTENTS_GHOUL2 0x4000 // used to set trace to do point to poly ghoul2 checks. Never set as a contents of an object/surface + +Ghoul2 Insert End + +#define CONTENTS_PLAYERCLIP 0x10000 +#define CONTENTS_MONSTERCLIP 0x20000 +#define CONTENTS_SHOTCLIP 0x40000 //These are not needed if CONTENTS_SOLID is included + +//q3 bot specific contents types +#define CONTENTS_TELEPORTER 0x40000 +#define CONTENTS_JUMPPAD 0x80000 //needed for bspc +#define CONTENTS_ITEM 0x80000 //Items can be touched but do not block movement (like triggers) but can be hit by the infoString trace +#define CONTENTS_CLUSTERPORTAL 0x100000 +#define CONTENTS_DONOTENTER 0x200000 +#define CONTENTS_BOTCLIP 0x400000 + +#define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity + +#define CONTENTS_BODY 0x2000000 // should never be on a brush, only in game +#define CONTENTS_CORPSE 0x4000000 +#define CONTENTS_DETAIL 0x8000000 // brushes not used for the bsp +#define CONTENTS_STRUCTURAL 0x10000000 // brushes used for the bsp +#define CONTENTS_TRANSLUCENT 0x20000000 // don't consume surface fragments inside +#define CONTENTS_TRIGGER 0x40000000 +#define CONTENTS_NODROP 0x80000000 // don't leave bodies or items (death fog, lava) + +#define SURF_NODAMAGE 0x1 // never give falling damage +#define SURF_SLICK 0x2 // effects game physics +#define SURF_SKY 0x4 // lighting from environment map +#define SURF_NOIMPACT 0x10 // don't make missile explosions +#define SURF_NOMARKS 0x20 // don't leave missile marks +#define SURF_FLESH 0x40 // make flesh sounds and effects +#define SURF_NODRAW 0x80 // don't generate a drawsurface at all +#define SURF_HINT 0x100 // make a primary bsp splitter +#define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes +#define SURF_NOLIGHTMAP 0x400 // surface doesn't need a lightmap +#define SURF_POINTLIGHT 0x800 // generate lighting info at vertexes +#define SURF_METALSTEPS 0x1000 // clanking footsteps +#define SURF_NOSTEPS 0x2000 // no footstep sounds +#define SURF_NONSOLID 0x4000 // don't collide against curves with this set +#define SURF_LIGHTFILTER 0x8000 // act as a light filter during q3map -light +#define SURF_ALPHASHADOW 0x10000 // do per-pixel light shadow casting in q3map +#define SURF_NODLIGHT 0x20000 // don't dlight even if solid (solid lava, skies) +#define SURF_FORCEFIELD 0x40000 // the surface in question is a forcefield +*/ +#endif \ No newline at end of file diff --git a/code/game/teams.h b/code/game/teams.h new file mode 100644 index 0000000..39f9e15 --- /dev/null +++ b/code/game/teams.h @@ -0,0 +1,92 @@ +#ifndef TEAMS_H +#define TEAMS_H + +typedef enum //# team_e +{ + TEAM_FREE, // caution, some code checks a team_t via "if (!team_t_varname)" so I guess this should stay as entry 0, great or what? -slc + TEAM_PLAYER, + TEAM_ENEMY, + TEAM_NEUTRAL, // most droids are team_neutral, there are some exceptions like Probe,Seeker,Interrogator + + //# #eol + TEAM_NUM_TEAMS +} team_t; + +extern stringID_table_t TeamTable[]; + +// This list is made up from the model directories, this MUST be in the same order as the ClassNames array in NPC_stats.cpp +typedef enum +{ + CLASS_NONE, // hopefully this will never be used by an npc, just covering all bases + CLASS_ATST, // technically droid... + CLASS_BARTENDER, + CLASS_BESPIN_COP, + CLASS_CLAW, + CLASS_COMMANDO, + CLASS_DESANN, + CLASS_FISH, + CLASS_FLIER2, + CLASS_GALAK, + CLASS_GLIDER, + CLASS_GONK, // droid + CLASS_GRAN, + CLASS_HOWLER, + CLASS_RANCOR, + CLASS_SAND_CREATURE, + CLASS_WAMPA, + CLASS_IMPERIAL, + CLASS_IMPWORKER, + CLASS_INTERROGATOR, // droid + CLASS_JAN, + CLASS_JEDI, + CLASS_KYLE, + CLASS_LANDO, + CLASS_LIZARD, + CLASS_LUKE, + CLASS_MARK1, // droid + CLASS_MARK2, // droid + CLASS_GALAKMECH, // droid + CLASS_MINEMONSTER, + CLASS_MONMOTHA, + CLASS_MORGANKATARN, + CLASS_MOUSE, // droid + CLASS_MURJJ, + CLASS_PRISONER, + CLASS_PROBE, // droid + CLASS_PROTOCOL, // droid + CLASS_R2D2, // droid + CLASS_R5D2, // droid + CLASS_REBEL, + CLASS_REBORN, + CLASS_REELO, + CLASS_REMOTE, + CLASS_RODIAN, + CLASS_SEEKER, // droid + CLASS_SENTRY, + CLASS_SHADOWTROOPER, + CLASS_SABOTEUR, + CLASS_STORMTROOPER, + CLASS_SWAMP, + CLASS_SWAMPTROOPER, + CLASS_NOGHRI, + CLASS_TAVION, + CLASS_ALORA, + CLASS_TRANDOSHAN, + CLASS_UGNAUGHT, + CLASS_JAWA, + CLASS_WEEQUAY, + CLASS_TUSKEN, + CLASS_BOBAFETT, + CLASS_ROCKETTROOPER, + CLASS_SABER_DROID, + CLASS_ASSASSIN_DROID, + CLASS_HAZARD_TROOPER, + CLASS_PLAYER, + CLASS_VEHICLE, + + CLASS_NUM_CLASSES +} class_t; + +extern stringID_table_t ClassTable[]; + +#endif // #ifndef TEAMS_H diff --git a/code/game/weapons.h b/code/game/weapons.h new file mode 100644 index 0000000..d295b27 --- /dev/null +++ b/code/game/weapons.h @@ -0,0 +1,149 @@ +// Filename:- weapons.h +// +// Note that this is now included from both server and game modules, so don't include any other header files +// within this one that might break stuff... + +#ifndef __WEAPONS_H__ +#define __WEAPONS_H__ + +typedef enum //# weapon_e +{ + WP_NONE, + + // Player weapons + WP_SABER, // player and NPC weapon + WP_BLASTER_PISTOL, // player and NPC weapon + WP_BLASTER, // player and NPC weapon + WP_DISRUPTOR, // player and NPC weapon + WP_BOWCASTER, // NPC weapon - player can pick this up, but never starts with them + WP_REPEATER, // NPC weapon - player can pick this up, but never starts with them + WP_DEMP2, // NPC weapon - player can pick this up, but never starts with them + WP_FLECHETTE, // NPC weapon - player can pick this up, but never starts with them + WP_ROCKET_LAUNCHER, // NPC weapon - player can pick this up, but never starts with them + WP_THERMAL, // player and NPC weapon + WP_TRIP_MINE, // NPC weapon - player can pick this up, but never starts with them + WP_DET_PACK, // NPC weapon - player can pick this up, but never starts with them + WP_CONCUSSION, // NPC weapon - player can pick this up, but never starts with them + + //extras + WP_MELEE, // player and NPC weapon - Any ol' melee attack + + //when in atst + WP_ATST_MAIN, + WP_ATST_SIDE, + + // These can never be gotten directly by the player + WP_STUN_BATON, // stupid weapon, should remove + + //NPC weapons + WP_BRYAR_PISTOL, // NPC weapon - player can pick this up, but never starts with them + + WP_EMPLACED_GUN, + + WP_BOT_LASER, // Probe droid - Laser blast + + WP_TURRET, // turret guns + + WP_TIE_FIGHTER, + + WP_RAPID_FIRE_CONC, + + WP_JAWA, + WP_TUSKEN_RIFLE, + WP_TUSKEN_STAFF, + WP_SCEPTER, + WP_NOGHRI_STICK, + + //# #eol + WP_NUM_WEAPONS +} weapon_t; + +#define FIRST_WEAPON WP_SABER // this is the first weapon for next and prev weapon switching +#define MAX_PLAYER_WEAPONS WP_STUN_BATON // this is the max you can switch to and get with the give all. - FIXME: it's actually this one *minus* one... why? + +// AMMO_NONE must be first and AMMO_MAX must be last, cause weapon load validates based off of these vals +typedef enum //# ammo_e +{ + AMMO_NONE, + AMMO_FORCE, // AMMO_PHASER + AMMO_BLASTER, // AMMO_STARFLEET, + AMMO_POWERCELL, // AMMO_ALIEN, + AMMO_METAL_BOLTS, + AMMO_ROCKETS, + AMMO_EMPLACED, + AMMO_THERMAL, + AMMO_TRIPMINE, + AMMO_DETPACK, + AMMO_MAX +} ammo_t; + + +typedef struct weaponData_s +{ + char classname[32]; // Spawning name + char weaponMdl[64]; // Weapon Model + char firingSnd[64]; // Sound made when fired + char altFiringSnd[64]; // Sound made when alt-fired +// char flashSnd[64]; // Sound made by flash +// char altFlashSnd[64]; // Sound made by an alt-flash + char stopSnd[64]; // Sound made when weapon stops firing + char chargeSnd[64]; // sound to start when the weapon initiates the charging sequence + char altChargeSnd[64]; // alt sound to start when the weapon initiates the charging sequence + char selectSnd[64]; // the sound to play when this weapon gets selected + +#ifdef _IMMERSION + char firingFrc[64]; + char altFiringFrc[64]; + char stopFrc[64]; + char chargeFrc[64]; + char altChargeFrc[64]; + char selectFrc[64]; +#endif // _IMMERSION + int ammoIndex; // Index to proper ammo slot + int ammoLow; // Count when ammo is low + + int energyPerShot; // Amount of energy used per shot + int fireTime; // Amount of time between firings + int range; // Range of weapon + + int altEnergyPerShot; // Amount of energy used for alt-fire + int altFireTime; // Amount of time between alt-firings + int altRange; // Range of alt-fire + + char weaponIcon[64]; // Name of weapon icon file + int numBarrels; // how many barrels should we expect for this weapon? + + char missileMdl[64]; // Missile Model + char missileSound[64]; // Missile flight sound + float missileDlight; // what is says + vec3_t missileDlightColor; // ditto + + char alt_missileMdl[64]; // Missile Model + char alt_missileSound[64]; // Missile sound + float alt_missileDlight; // what is says + vec3_t alt_missileDlightColor; // ditto + + char missileHitSound[64]; // Missile impact sound + char altmissileHitSound[64]; // alt Missile impact sound +#ifndef _USRDLL + void *func; + void *altfunc; + + char mMuzzleEffect[64]; + int mMuzzleEffectID; + char mAltMuzzleEffect[64]; + int mAltMuzzleEffectID; + +#endif + +} weaponData_t; + + +typedef struct ammoData_s +{ + char icon[32]; // Name of ammo icon file + int max; // Max amount player can hold of ammo +} ammoData_t; + + +#endif//#ifndef __WEAPONS_H__ diff --git a/code/game/wp_saber.cpp b/code/game/wp_saber.cpp new file mode 100644 index 0000000..f402b6c --- /dev/null +++ b/code/game/wp_saber.cpp @@ -0,0 +1,13776 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + + + +#include "g_local.h" +#include "anims.h" +#include "b_local.h" +#include "bg_local.h" +#include "g_functions.h" +#include "wp_saber.h" +#include "g_vehicles.h" +#include "../client/fffx.h" +#define JK2_RAGDOLL_GRIPNOHEALTH + +#define MAX_SABER_VICTIMS 16 +static int victimEntityNum[MAX_SABER_VICTIMS]; +static float totalDmg[MAX_SABER_VICTIMS]; +static vec3_t dmgDir[MAX_SABER_VICTIMS]; +static vec3_t dmgSpot[MAX_SABER_VICTIMS]; +static float dmgFraction[MAX_SABER_VICTIMS]; +static int hitLoc[MAX_SABER_VICTIMS]; +static qboolean hitDismember[MAX_SABER_VICTIMS]; +static int hitDismemberLoc[MAX_SABER_VICTIMS]; +static vec3_t saberHitLocation, saberHitNormal={0,0,1.0}; +static float saberHitFraction; +static float sabersCrossed; +static int saberHitEntity; +static int numVictims = 0; + +#define SABER_PITCH_HACK 90 + + +extern cvar_t *g_sex; +extern cvar_t *g_timescale; +extern cvar_t *g_dismemberment; +extern cvar_t *g_debugSaberLock; +extern cvar_t *g_saberLockRandomNess; +extern cvar_t *d_slowmodeath; +extern cvar_t *g_cheats; +extern cvar_t *g_debugMelee; +extern cvar_t *g_saberRestrictForce; + +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); +extern qboolean G_ClearViewEntity( gentity_t *ent ); +extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity ); +extern qboolean G_ControlledByPlayer( gentity_t *self ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void CG_ChangeWeapon( int num ); +extern void G_AngerAlert( gentity_t *self ); +extern void G_ReflectMissile( gentity_t *ent, gentity_t *missile, vec3_t forward ); +extern int G_CheckLedgeDive( gentity_t *self, float checkDist, const vec3_t checkVel, qboolean tryOpposite, qboolean tryPerp ); +extern void G_BounceMissile( gentity_t *ent, trace_t *trace ); +extern qboolean G_PointInBounds( const vec3_t point, const vec3_t mins, const vec3_t maxs ); +extern void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone ); +extern void WP_FireDreadnoughtBeam( gentity_t *ent ); +extern void G_MissileImpacted( gentity_t *ent, gentity_t *other, vec3_t impactPos, vec3_t normal, int hitLoc=HL_NONE ); +extern evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist = 0.0f ); +extern void Jedi_RageStop( gentity_t *self ); +extern int PM_PickAnim( gentity_t *self, int minAnim, int maxAnim ); +extern void NPC_SetPainEvent( gentity_t *self ); +extern qboolean PM_SwimmingAnim( int anim ); +extern qboolean PM_InAnimForSaberMove( int anim, int saberMove ); +extern qboolean PM_SpinningSaberAnim( int anim ); +extern qboolean PM_SaberInSpecialAttack( int anim ); +extern qboolean PM_SaberInAttack( int move ); +extern qboolean PM_SaberInTransition( int move ); +extern qboolean PM_SaberInStart( int move ); +extern qboolean PM_SaberInTransitionAny( int move ); +extern qboolean PM_SaberInReturn( int move ); +extern qboolean PM_SaberInBounce( int move ); +extern qboolean PM_SaberInParry( int move ); +extern qboolean PM_SaberInKnockaway( int move ); +extern qboolean PM_SaberInBrokenParry( int move ); +extern qboolean PM_SpinningSaberAnim( int anim ); +extern saberMoveName_t PM_SaberBounceForAttack( int move ); +extern saberMoveName_t PM_BrokenParryForAttack( int move ); +extern saberMoveName_t PM_KnockawayForParry( int move ); +extern qboolean PM_FlippingAnim( int anim ); +extern qboolean PM_RollingAnim( int anim ); +extern qboolean PM_CrouchAnim( int anim ); +extern qboolean PM_SaberInIdle( int move ); +extern qboolean PM_SaberInReflect( int move ); +extern qboolean PM_InSpecialJump( int anim ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean PM_ForceUsingSaberAnim( int anim ); +extern qboolean PM_SuperBreakLoseAnim( int anim ); +extern qboolean PM_SuperBreakWinAnim( int anim ); +extern qboolean PM_SaberLockBreakAnim( int anim ); +extern qboolean PM_InOnGroundAnim ( playerState_t *ps ); +extern qboolean PM_KnockDownAnim( int anim ); +extern qboolean PM_SaberInKata( saberMoveName_t saberMove ); +extern qboolean PM_StabDownAnim( int anim ); +extern int PM_PowerLevelForSaberAnim( playerState_t *ps, int saberNum = 0 ); +extern void PM_VelocityForSaberMove( playerState_t *ps, vec3_t throwDir ); +extern qboolean PM_VelocityForBlockedMove( playerState_t *ps, vec3_t throwDir ); +extern int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType ); +extern qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc ); +extern void Jedi_PlayDeflectSound( gentity_t *self ); +extern void Jedi_PlayBlockedPushSound( gentity_t *self ); +extern qboolean Jedi_WaitingAmbush( gentity_t *self ); +extern void Jedi_Ambush( gentity_t *self ); +extern qboolean Jedi_SaberBusy( gentity_t *self ); +extern qboolean Jedi_CultistDestroyer( gentity_t *self ); +extern qboolean Boba_Flying( gentity_t *self ); +extern void JET_FlyStart( gentity_t *self ); +extern void Boba_DoFlameThrower( gentity_t *self ); +extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent ); +extern int SaberDroid_PowerLevelForSaberAnim( gentity_t *self ); +extern qboolean G_ValidEnemy( gentity_t *self, gentity_t *enemy ); +extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 ); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_KnockOffVehicle( gentity_t *pRider, gentity_t *self, qboolean bPull ); +extern qboolean PM_LockedAnim( int anim ); +extern qboolean Rosh_BeingHealed( gentity_t *self ); +extern qboolean G_OkayToLean( playerState_t *ps, usercmd_t *cmd, qboolean interruptOkay ); + +int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacker, int atPower, int atPowerLevel, int atForceSpent); +void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower ); +qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd ); + +void WP_SaberDrop( gentity_t *self, gentity_t *saber ); +qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir ); +void WP_SaberReturn( gentity_t *self, gentity_t *saber ); +void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missleBlock ); +void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock ); +qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +void WP_DeactivateSaber( gentity_t *self, qboolean clearLength = qfalse ); +qboolean FP_ForceDrainGrippableEnt( gentity_t *victim ); + +extern cvar_t *g_saberAutoBlocking; +extern cvar_t *g_saberRealisticCombat; +extern cvar_t *g_saberDamageCapping; +extern cvar_t *g_saberNewControlScheme; +extern int g_crosshairEntNum; + +qboolean g_saberNoEffects = qfalse; +int g_saberFlashTime = 0; +vec3_t g_saberFlashPos = {0,0,0}; + +int forcePowerDarkLight[NUM_FORCE_POWERS] = //0 == neutral +{ //nothing should be usable at rank 0.. + FORCE_LIGHTSIDE,//FP_HEAL,//instant + 0,//FP_LEVITATION,//hold/duration + 0,//FP_SPEED,//duration + 0,//FP_PUSH,//hold/duration + 0,//FP_PULL,//hold/duration + FORCE_LIGHTSIDE,//FP_TELEPATHY,//instant + FORCE_DARKSIDE,//FP_GRIP,//hold/duration + FORCE_DARKSIDE,//FP_LIGHTNING,//hold/duration + 0,//FP_SABERATTACK, + 0,//FP_SABERDEFEND, + 0,//FP_SABERTHROW, + //new Jedi Academy powers + FORCE_DARKSIDE,//FP_RAGE,//duration + FORCE_LIGHTSIDE,//FP_PROTECT,//duration + FORCE_LIGHTSIDE,//FP_ABSORB,//duration + FORCE_DARKSIDE,//FP_DRAIN,//hold/duration + 0,//FP_SEE,//duration + //NUM_FORCE_POWERS +}; + +int forcePowerNeeded[NUM_FORCE_POWERS] = +{ + 0,//FP_HEAL,//instant + 10,//FP_LEVITATION,//hold/duration + 50,//FP_SPEED,//duration + 15,//FP_PUSH,//hold/duration + 15,//FP_PULL,//hold/duration + 20,//FP_TELEPATHY,//instant + 1,//FP_GRIP,//hold/duration - FIXME: 30? + 1,//FP_LIGHTNING,//hold/duration + 20,//FP_SABERTHROW, + 1,//FP_SABER_DEFENSE, + 0,//FP_SABER_OFFENSE, + //new Jedi Academy powers + 50,//FP_RAGE,//duration - speed, invincibility and extra damage for short period, drains your health and leaves you weak and slow afterwards. + 30,//FP_PROTECT,//duration - protect against physical/energy (level 1 stops blaster/energy bolts, level 2 stops projectiles, level 3 protects against explosions) + 30,//FP_ABSORB,//duration - protect against dark force powers (grip, lightning, drain) + 1,//FP_DRAIN,//hold/duration - drain force power for health + 20//FP_SEE,//duration - detect/see hidden enemies + //NUM_FORCE_POWERS +}; + +float forceJumpStrength[NUM_FORCE_POWER_LEVELS] = +{ + JUMP_VELOCITY,//normal jump + 420, + 590, + 840 +}; + +float forceJumpHeight[NUM_FORCE_POWER_LEVELS] = +{ + 32,//normal jump (+stepheight+crouchdiff = 66) + 96,//(+stepheight+crouchdiff = 130) + 192,//(+stepheight+crouchdiff = 226) + 384//(+stepheight+crouchdiff = 418) +}; + +float forceJumpHeightMax[NUM_FORCE_POWER_LEVELS] = +{ + 66,//normal jump (32+stepheight(18)+crouchdiff(24) = 74) + 130,//(96+stepheight(18)+crouchdiff(24) = 138) + 226,//(192+stepheight(18)+crouchdiff(24) = 234) + 418//(384+stepheight(18)+crouchdiff(24) = 426) +}; + +float forcePushPullRadius[NUM_FORCE_POWER_LEVELS] = +{ + 0,//none + 384,//256, + 448,//384, + 512 +}; + +float forcePushCone[NUM_FORCE_POWER_LEVELS] = +{ + 1.0f,//none + 1.0f, + 0.8f, + 0.6f +}; + +float forcePullCone[NUM_FORCE_POWER_LEVELS] = +{ + 1.0f,//none + 1.0f, + 1.0f, + 0.8f +}; + +float forceSpeedValue[NUM_FORCE_POWER_LEVELS] = +{ + 1.0f,//none + 0.75f, + 0.5f, + 0.25f +}; + +float forceSpeedRangeMod[NUM_FORCE_POWER_LEVELS] = +{ + 0.0f,//none + 30.0f, + 45.0f, + 60.0f +}; + +float forceSpeedFOVMod[NUM_FORCE_POWER_LEVELS] = +{ + 0.0f,//none + 20.0f, + 30.0f, + 40.0f +}; + +int forceGripDamage[NUM_FORCE_POWER_LEVELS] = +{ + 0,//none + 0, + 6, + 9 +}; + +int mindTrickTime[NUM_FORCE_POWER_LEVELS] = +{ + 0,//none + 10000,//5000, + 15000,//10000, + 30000//15000 +}; + +//NOTE: keep in synch with table below!!! +int saberThrowDist[NUM_FORCE_POWER_LEVELS] = +{ + 0,//none + 256, + 400, + 400 +}; + +//NOTE: keep in synch with table above!!! +int saberThrowDistSquared[NUM_FORCE_POWER_LEVELS] = +{ + 0,//none + 65536, + 160000, + 160000 +}; + +int parryDebounce[NUM_FORCE_POWER_LEVELS] = +{ + 500,//if don't even have defense, can't use defense! + 300, + 150, + 50 +}; + +float saberAnimSpeedMod[NUM_FORCE_POWER_LEVELS] = +{ + 0.0f,//if don't even have offense, can't use offense! + 0.75f, + 1.0f, + 2.0f +}; + +stringID_table_t SaberStyleTable[] = +{ + "NULL",SS_NONE, + ENUM2STRING(SS_FAST), + "fast",SS_FAST, + ENUM2STRING(SS_MEDIUM), + "medium",SS_MEDIUM, + ENUM2STRING(SS_STRONG), + "strong",SS_STRONG, + ENUM2STRING(SS_DESANN), + "desann",SS_DESANN, + ENUM2STRING(SS_TAVION), + "tavion",SS_TAVION, + ENUM2STRING(SS_DUAL), + "dual",SS_DUAL, + ENUM2STRING(SS_STAFF), + "staff",SS_STAFF, + "", NULL +}; +//SABER INITIALIZATION====================================================================== + +void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *psWeaponModel, int boltNum, int weaponNum ) +{ + if (!psWeaponModel) + { + assert (psWeaponModel); + return; + } + if ( ent->playerModel == -1 ) + { + return; + } + if ( boltNum == -1 ) + { + return; + } + + if ( ent && ent->client && ent->client->NPC_class == CLASS_GALAKMECH ) + {//hack for galakmech, no weaponmodel + ent->weaponModel[0] = ent->weaponModel[1] = -1; + return; + } + if ( weaponNum < 0 || weaponNum >= MAX_INHAND_WEAPONS ) + { + return; + } + char weaponModel[64]; + + strcpy (weaponModel, psWeaponModel); + if (char *spot = strstr(weaponModel, ".md3") ) { + *spot = 0; + spot = strstr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on + if (!spot&&!strstr(weaponModel, "noweap")) + { + strcat (weaponModel, "_w"); + } + strcat (weaponModel, ".glm"); //and change to ghoul2 + } + + // give us a saber model + int wModelIndex = G_ModelIndex( weaponModel ); + if ( wModelIndex ) + { + ent->weaponModel[weaponNum] = gi.G2API_InitGhoul2Model(ent->ghoul2, weaponModel, wModelIndex ); + if ( ent->weaponModel[weaponNum] != -1 ) + { + // attach it to the hand + gi.G2API_AttachG2Model(&ent->ghoul2[ent->weaponModel[weaponNum]], &ent->ghoul2[ent->playerModel], + boltNum, ent->playerModel); + // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0 + gi.G2API_AddBolt(&ent->ghoul2[ent->weaponModel[weaponNum]], "*flash"); + //gi.G2API_SetLodBias( &ent->ghoul2[ent->weaponModel[weaponNum]], 0 ); + } + } + +} + +void WP_SaberAddG2SaberModels( gentity_t *ent, int specificSaberNum ) +{ + int saberNum = 0, maxSaber = 1; + if ( specificSaberNum != -1 && specificSaberNum <= maxSaber ) + { + saberNum = maxSaber = specificSaberNum; + } + for ( ; saberNum <= maxSaber; saberNum++ ) + { + if ( ent->weaponModel[saberNum] > 0 ) + {//we already have a weapon model in this slot + //remove it + gi.G2API_SetSkin( &ent->ghoul2[ent->weaponModel[saberNum]], -1, 0 ); + gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[saberNum] ); + ent->weaponModel[saberNum] = -1; + } + if ( saberNum > 0 ) + {//second saber + if ( !ent->client->ps.dualSabers + || G_IsRidingVehicle( ent ) ) + {//only have one saber or riding a vehicle and can only use one saber + return; + } + } + else if ( saberNum == 0 ) + {//first saber + if ( ent->client->ps.saberInFlight ) + {//it's still out there somewhere, don't add it + //FIXME: call it back? + continue; + } + } + int handBolt = ((saberNum == 0) ? ent->handRBolt : ent->handLBolt); + G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saber[saberNum].model, handBolt, saberNum ); + + if ( ent->client->ps.saber[saberNum].skin != NULL ) + {//if this saber has a customSkin, use it + // lets see if it's out there + int saberSkin = gi.RE_RegisterSkin( ent->client->ps.saber[saberNum].skin ); + if ( saberSkin ) + { + // put it in the config strings + // and set the ghoul2 model to use it + gi.G2API_SetSkin( &ent->ghoul2[ent->weaponModel[saberNum]], G_SkinIndex( ent->client->ps.saber[saberNum].skin ), saberSkin ); + } + } + } +} + +//---------------------------------------------------------- +void G_Throw( gentity_t *targ, const vec3_t newDir, float push ) +//---------------------------------------------------------- +{ + vec3_t kvel; + float mass; + + if ( targ + && targ->client + && ( targ->client->NPC_class == CLASS_ATST + || targ->client->NPC_class == CLASS_RANCOR + || targ->client->NPC_class == CLASS_SAND_CREATURE ) ) + {//much to large to *ever* throw + return; + } + + if ( targ->physicsBounce > 0 ) //overide the mass + { + mass = targ->physicsBounce; + } + else + { + mass = 200; + } + + if ( g_gravity->value > 0 ) + { + VectorScale( newDir, g_knockback->value * (float)push / mass * 0.8, kvel ); + if ( !targ->client || targ->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//give them some z lift to get them off the ground + kvel[2] = newDir[2] * g_knockback->value * (float)push / mass * 1.5; + } + } + else + { + VectorScale( newDir, g_knockback->value * (float)push / mass, kvel ); + } + + if ( targ->client ) + { + VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity ); + } + else if ( targ->s.pos.trType != TR_STATIONARY && targ->s.pos.trType != TR_LINEAR_STOP && targ->s.pos.trType != TR_NONLINEAR_STOP ) + { + VectorAdd( targ->s.pos.trDelta, kvel, targ->s.pos.trDelta ); + VectorCopy( targ->currentOrigin, targ->s.pos.trBase ); + targ->s.pos.trTime = level.time; + } + + // set the timer so that the other client can't cancel + // out the movement immediately + if ( targ->client && !targ->client->ps.pm_time ) + { + int t; + + t = push * 2; + + if ( t < 50 ) + { + t = 50; + } + if ( t > 200 ) + { + t = 200; + } + targ->client->ps.pm_time = t; + targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } +} + +int WP_SetSaberModel( gclient_t *client, class_t npcClass ) +{//FIXME: read from NPCs.cfg + if ( client ) + { + switch ( npcClass ) + { + case CLASS_DESANN://Desann + client->ps.saber[0].model = "models/weapons2/saber_desann/saber_w.glm"; + break; + case CLASS_LUKE://Luke + client->ps.saber[0].model = "models/weapons2/saber_luke/saber_w.glm"; + break; + case CLASS_PLAYER://Kyle NPC and player + case CLASS_KYLE://Kyle NPC and player + client->ps.saber[0].model = "models/weapons2/saber/saber_w.glm"; + break; + default://reborn and tavion and everyone else + client->ps.saber[0].model = "models/weapons2/saber_reborn/saber_w.glm"; + break; + } + return ( G_ModelIndex( client->ps.saber[0].model ) ); + } + else + { + switch ( npcClass ) + { + case CLASS_DESANN://Desann + return ( G_ModelIndex( "models/weapons2/saber_desann/saber_w.glm" ) ); + break; + case CLASS_LUKE://Luke + return ( G_ModelIndex( "models/weapons2/saber_luke/saber_w.glm" ) ); + break; + case CLASS_PLAYER://Kyle NPC and player + case CLASS_KYLE://Kyle NPC and player + return ( G_ModelIndex( "models/weapons2/saber/saber_w.glm" ) ); + break; + default://reborn and tavion and everyone else + return ( G_ModelIndex( "models/weapons2/saber_reborn/saber_w.glm" ) ); + break; + } + } +} + +void WP_SetSaberEntModelSkin( gentity_t *ent, gentity_t *saberent ) +{ + int saberModel = 0; + qboolean newModel = qfalse; + //FIXME: get saberModel from NPCs.cfg + if ( !ent->client->ps.saber[0].model ) + { + saberModel = WP_SetSaberModel( ent->client, ent->client->NPC_class ); + } + else + { + //got saberModel from NPCs.cfg + saberModel = G_ModelIndex( ent->client->ps.saber[0].model ); + } + if ( saberModel && saberent->s.modelindex != saberModel ) + { + if ( saberent->playerModel >= 0 ) + {//remove the old one, if there is one + gi.G2API_RemoveGhoul2Model( saberent->ghoul2, saberent->playerModel ); + } + //add the new one + saberent->playerModel = gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saber[0].model, saberModel); + saberent->s.modelindex = saberModel; + newModel = qtrue; + } + //set skin, too + if ( ent->client->ps.saber[0].skin == NULL ) + { + gi.G2API_SetSkin( &saberent->ghoul2[0], -1, 0 ); + } + else + {//if this saber has a customSkin, use it + // lets see if it's out there + int saberSkin = gi.RE_RegisterSkin( ent->client->ps.saber[0].skin ); + if ( saberSkin && (newModel || saberent->s.modelindex2 != saberSkin) ) + { + // put it in the config strings + // and set the ghoul2 model to use it + gi.G2API_SetSkin( &saberent->ghoul2[0], G_SkinIndex( ent->client->ps.saber[0].skin ), saberSkin ); + saberent->s.modelindex2 = saberSkin; + } + } +} + +void WP_SaberSwingSound( gentity_t *ent, int saberNum, swingType_t swingType ) +{ + int index = 1; + if ( !ent || !ent->client ) + { + return; + } + if ( swingType == SWING_FAST ) + { + index = Q_irand( 1, 3 ); + } + else if ( swingType == SWING_MEDIUM ) + { + index = Q_irand( 4, 6 ); + } + else if ( swingType == SWING_STRONG ) + { + index = Q_irand( 7, 9 ); + } +#ifdef _IMMERSION + if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD ) + { + G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/sword/swing%d.wav", Q_irand( 1, 4 ) ) ); + } + else + { + G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/saber/saberhup%d.wav", index ) ); + } + G_Force( ent, G_ForceIndex( va( "fffx/weapons/saber/saberhup%d", index ), FF_CHANNEL_WEAPON ) ); +#else + if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD ) + { + G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/sword/swing%d.wav", Q_irand( 1, 4 ) ) ); + } + else + { + G_SoundOnEnt( ent, CHAN_WEAPON, va( "sound/weapons/saber/saberhup%d.wav", index ) ); + } +#endif // _IMMERSION +} + +void WP_SaberHitSound( gentity_t *ent, int saberNum ) +{ + int index = 1; + if ( !ent || !ent->client ) + { + return; + } +#ifdef _IMMERSION + index = Q_irand( 1, 3 ); + if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD ) + { + G_Sound( ent, G_SoundIndex( va( "sound/weapons/sword/stab%d.wav", Q_irand( 1, 4 ) ) ) ); + } + else + { + G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberhit%d.wav", index ) ) ); + } + G_Force( ent, G_ForceIndex( va( "fffx/weapons/saber/saberhit%d", index), FF_CHANNEL_WEAPON ) ); +#else + if ( ent->client->ps.saber[saberNum].type == SABER_SITH_SWORD ) + { + G_Sound( ent, G_SoundIndex( va( "sound/weapons/sword/stab%d.wav", Q_irand( 1, 4 ) ) ) ); + } + else + { + G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberhit%d.wav", Q_irand( 1, 3 ) ) ) ); + } +#endif // _IMMERSION + + if(ent->client && ent->client->ps.clientNum == 0) + FF_Play(fffx_Laser1); +} + +int WP_SaberInitBladeData( gentity_t *ent ) +{ + if ( !ent->client ) + { + return 0; + } + if ( 1 ) + { + VectorClear( ent->client->renderInfo.muzzlePoint ); + VectorClear( ent->client->renderInfo.muzzlePointOld ); + //VectorClear( ent->client->renderInfo.muzzlePointNext ); + VectorClear( ent->client->renderInfo.muzzleDir ); + VectorClear( ent->client->renderInfo.muzzleDirOld ); + //VectorClear( ent->client->renderInfo.muzzleDirNext ); + for ( int saberNum = 0; saberNum < MAX_SABERS; saberNum++ ) + { + for ( int bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ ) + { + VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint ); + VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld ); + VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir ); + VectorClear( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld ); + ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld = ent->client->ps.saber[saberNum].blade[bladeNum].length = 0; + if ( !ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax ) + { + if ( ent->client->NPC_class == CLASS_DESANN ) + {//longer saber + ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 48; + } + else if ( ent->client->NPC_class == CLASS_REBORN ) + {//shorter saber + ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 32; + } + else + {//standard saber length + ent->client->ps.saber[saberNum].blade[bladeNum].lengthMax = 40; + } + } + } + } + ent->client->ps.saberLockEnemy = ENTITYNUM_NONE; + ent->client->ps.saberLockTime = 0; + if ( ent->s.number ) + { + if ( !ent->client->ps.saberAnimLevel ) + { + if ( ent->client->NPC_class == CLASS_DESANN ) + { + ent->client->ps.saberAnimLevel = SS_DESANN; + } + else if ( ent->client->NPC_class == CLASS_TAVION ) + { + ent->client->ps.saberAnimLevel = SS_TAVION; + } + else if ( ent->client->NPC_class == CLASS_ALORA ) + { + ent->client->ps.saberAnimLevel = SS_DUAL; + } + //FIXME: CLASS_CULTIST instead of this Q_stricmpn? + else if ( !Q_stricmpn( "cultist", ent->NPC_type, 7 ) ) + {//should already be set in the .npc file + ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG ); + } + else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CIVILIAN || ent->NPC->rank == RANK_LT_JG) ) + {//grunt and fencer always uses quick attacks + ent->client->ps.saberAnimLevel = SS_FAST; + } + else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && (ent->NPC->rank == RANK_CREWMAN || ent->NPC->rank == RANK_ENSIGN) ) + {//acrobat & force-users always use medium attacks + ent->client->ps.saberAnimLevel = SS_MEDIUM; + } + else if ( ent->client->playerTeam == TEAM_ENEMY && ent->client->NPC_class == CLASS_SHADOWTROOPER ) + {//shadowtroopers + ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG ); + } + else if ( ent->NPC && ent->client->playerTeam == TEAM_ENEMY && ent->NPC->rank == RANK_LT ) + {//boss always starts with strong attacks + ent->client->ps.saberAnimLevel = SS_STRONG; + } + else if ( ent->client->NPC_class == CLASS_PLAYER ) + { + ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel; + } + else + {//? + ent->client->ps.saberAnimLevel = Q_irand( SS_FAST, SS_STRONG ); + } + } + } + else + { + if ( !ent->client->ps.saberAnimLevel ) + {//initialize, but don't reset + if (ent->s.number < MAX_CLIENTS) + { + if (!ent->client->ps.saberStylesKnown) + { + ent->client->ps.saberStylesKnown = (1<client->ps.saberStylesKnown & (1<client->ps.saberAnimLevel = SS_FAST; + } + else if (ent->client->ps.saberStylesKnown & (1<client->ps.saberAnimLevel = SS_STRONG; + } + else + { + ent->client->ps.saberAnimLevel = SS_MEDIUM; + } + + } + else + { + ent->client->ps.saberAnimLevel = SS_MEDIUM; + } + } + + cg.saberAnimLevelPending = ent->client->ps.saberAnimLevel; + if ( ent->client->sess.missionStats.weaponUsed[WP_SABER] <= 0 ) + {//let missionStats know that we actually do have the saber, even if we never use it + ent->client->sess.missionStats.weaponUsed[WP_SABER] = 1; + } + } + ent->client->ps.saberAttackChainCount = 0; + + if ( ent->client->ps.saberEntityNum <= 0 || ent->client->ps.saberEntityNum >= ENTITYNUM_WORLD ) + {//FIXME: if you do have a saber already, be sure to re-set the model if it's changed (say, via a script). + gentity_t *saberent = G_Spawn(); + ent->client->ps.saberEntityNum = saberent->s.number; + saberent->classname = "lightsaber"; + + saberent->s.eType = ET_GENERAL; + saberent->svFlags = SVF_USE_CURRENT_ORIGIN; + saberent->s.weapon = WP_SABER; + saberent->owner = ent; + saberent->s.otherEntityNum = ent->s.number; + //clear the enemy + saberent->enemy = NULL; + + saberent->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER; + saberent->contents = CONTENTS_LIGHTSABER;//|CONTENTS_SHOTCLIP; + + VectorSet( saberent->mins, -3.0f, -3.0f, -3.0f ); + VectorSet( saberent->maxs, 3.0f, 3.0f, 3.0f ); + saberent->mass = 10;//necc? + + saberent->s.eFlags |= EF_NODRAW; + saberent->svFlags |= SVF_NOCLIENT; +/* +Ghoul2 Insert Start +*/ + saberent->playerModel = -1; + WP_SetSaberEntModelSkin( ent, saberent ); + + // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0 + gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" ); + //gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 ); + if ( ent->client->ps.dualSabers ) + { + //int saber2 = + G_ModelIndex( ent->client->ps.saber[1].model ); + //gi.G2API_InitGhoul2Model( saberent->ghoul2, ent->client->ps.saber[1].model, saber2 ); + // set up a bolt on the end so we can get where the sabre muzzle is - we can assume this is always bolt 0 + //gi.G2API_AddBolt( &saberent->ghoul2[0], "*flash" ); + //gi.G2API_SetLodBias( &saberent->ghoul2[0], 0 ); + } + +/* +Ghoul2 Insert End +*/ + + ent->client->ps.saberInFlight = qfalse; + ent->client->ps.saberEntityDist = 0; + ent->client->ps.saberEntityState = SES_LEAVING; + + ent->client->ps.saberMove = ent->client->ps.saberMoveNext = LS_NONE; + + //FIXME: need a think function to create alerts when turned on or is on, etc. + } + else + {//already have one, might just be changing sabers, register the model and skin and use them if different from what we're using now. + WP_SetSaberEntModelSkin( ent, &g_entities[ent->client->ps.saberEntityNum] ); + } + } + else + { + ent->client->ps.saberEntityNum = ENTITYNUM_NONE; + ent->client->ps.saberInFlight = qfalse; + ent->client->ps.saberEntityDist = 0; + ent->client->ps.saberEntityState = SES_LEAVING; + } + + if ( ent->client->ps.dualSabers ) + { + return 2; + } + + return 1; +} + +void WP_SaberUpdateOldBladeData( gentity_t *ent ) +{ + if ( ent->client ) + { + qboolean didEvent = qfalse; + for ( int saberNum = 0; saberNum < 2; saberNum++ ) + { + for ( int bladeNum = 0; bladeNum < ent->client->ps.saber[saberNum].numBlades; bladeNum++ ) + { + VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld ); + VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld ); + if ( !didEvent ) + { + if ( ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld <= 0 && ent->client->ps.saber[saberNum].blade[bladeNum].length > 0 ) + {//just turned on + //do sound event + vec3_t saberOrg; + VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, saberOrg ); + if ( (!ent->client->ps.saberInFlight && ent->client->ps.groundEntityNum == ENTITYNUM_WORLD)//holding saber and on ground + || g_entities[ent->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY )//saber out there somewhere and on ground + {//a ground alert + AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS, qfalse, qtrue ); + } + else + {//an in-air alert + AddSoundEvent( ent, saberOrg, 256, AEL_SUSPICIOUS ); + } + didEvent = qtrue; + } + } + ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld = ent->client->ps.saber[saberNum].blade[bladeNum].length; + } + } + VectorCopy( ent->client->renderInfo.muzzlePoint, ent->client->renderInfo.muzzlePointOld ); + VectorCopy( ent->client->renderInfo.muzzleDir, ent->client->renderInfo.muzzleDirOld ); + } +} + + + +//SABER DAMAGE============================================================================== +//SABER DAMAGE============================================================================== +//SABER DAMAGE============================================================================== +//SABER DAMAGE============================================================================== +//SABER DAMAGE============================================================================== +//SABER DAMAGE============================================================================== +int WPDEBUG_SaberColor( saber_colors_t saberColor ) +{ + switch( (int)(saberColor) ) + { + case SABER_RED: + return 0x000000ff; + break; + case SABER_ORANGE: + return 0x000088ff; + break; + case SABER_YELLOW: + return 0x0000ffff; + break; + case SABER_GREEN: + return 0x0000ff00; + break; + case SABER_BLUE: + return 0x00ff0000; + break; + case SABER_PURPLE: + return 0x00ff00ff; + break; + default: + return 0x00ffffff;//white + break; + } +} + +qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender ) +{ + vec3_t temp, att_SaberBase, att_StartPos, saberMidNext, att_HitDir, att_HitPos, def_BladeDir; + float att_SaberHitLength, hitDot; + + if ( !attacker || !attacker->client || attacker->client->ps.saberInFlight || attacker->client->ps.SaberLength() <= 0 ) + { + return qfalse; + } + if ( !defender || !defender->client || defender->client->ps.saberInFlight || defender->client->ps.SaberLength() <= 0 ) + { + return qfalse; + } + if ( PM_SuperBreakLoseAnim( attacker->client->ps.torsoAnim ) + || PM_SuperBreakWinAnim( attacker->client->ps.torsoAnim ) ) + { + return qfalse; + } + attacker->client->ps.saberBounceMove = LS_NONE; + + //get the attacker's saber base pos at time of impact + VectorSubtract( attacker->client->renderInfo.muzzlePoint, attacker->client->renderInfo.muzzlePointOld, temp ); + VectorMA( attacker->client->renderInfo.muzzlePointOld, saberHitFraction, temp, att_SaberBase ); + + //get the position along the length of the blade where the hit occured + att_SaberHitLength = Distance( saberHitLocation, att_SaberBase )/attacker->client->ps.SaberLength(); + + //now get the start of that midpoint in the swing and the actual impact point in the swing (shouldn't the latter just be saberHitLocation?) + VectorMA( attacker->client->renderInfo.muzzlePointOld, att_SaberHitLength, attacker->client->renderInfo.muzzleDirOld, att_StartPos ); + VectorMA( attacker->client->renderInfo.muzzlePoint, att_SaberHitLength, attacker->client->renderInfo.muzzleDir, saberMidNext ); + VectorSubtract( saberMidNext, att_StartPos, att_HitDir ); + VectorMA( att_StartPos, saberHitFraction, att_HitDir, att_HitPos ); + VectorNormalize( att_HitDir ); + + //get the defender's saber dir at time of impact + VectorSubtract( defender->client->renderInfo.muzzleDirOld, defender->client->renderInfo.muzzleDir, temp ); + VectorMA( defender->client->renderInfo.muzzleDirOld, saberHitFraction, temp, def_BladeDir ); + + //now compare + hitDot = DotProduct( att_HitDir, def_BladeDir ); + if ( hitDot < 0.25f && hitDot > -0.25f ) + {//hit pretty much perpendicular, pop straight back + attacker->client->ps.saberBounceMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove ); + return qfalse; + } + else + {//a deflection + vec3_t att_Right, att_Up, att_DeflectionDir; + float swingRDot, swingUDot; + + //get the direction of the deflection + VectorScale( def_BladeDir, hitDot, att_DeflectionDir ); + //get our bounce straight back direction + VectorScale( att_HitDir, -1.0f, temp ); + //add the bounce back and deflection + VectorAdd( att_DeflectionDir, temp, att_DeflectionDir ); + //normalize the result to determine what direction our saber should bounce back toward + VectorNormalize( att_DeflectionDir ); + + //need to know the direction of the deflectoin relative to the attacker's facing + VectorSet( temp, 0, attacker->client->ps.viewangles[YAW], 0 );//presumes no pitch! + AngleVectors( temp, NULL, att_Right, att_Up ); + swingRDot = DotProduct( att_Right, att_DeflectionDir ); + swingUDot = DotProduct( att_Up, att_DeflectionDir ); + + if ( swingRDot > 0.25f ) + {//deflect to right + if ( swingUDot > 0.25f ) + {//deflect to top + attacker->client->ps.saberBounceMove = LS_D1_TR; + } + else if ( swingUDot < -0.25f ) + {//deflect to bottom + attacker->client->ps.saberBounceMove = LS_D1_BR; + } + else + {//deflect horizontally + attacker->client->ps.saberBounceMove = LS_D1__R; + } + } + else if ( swingRDot < -0.25f ) + {//deflect to left + if ( swingUDot > 0.25f ) + {//deflect to top + attacker->client->ps.saberBounceMove = LS_D1_TL; + } + else if ( swingUDot < -0.25f ) + {//deflect to bottom + attacker->client->ps.saberBounceMove = LS_D1_BL; + } + else + {//deflect horizontally + attacker->client->ps.saberBounceMove = LS_D1__L; + } + } + else + {//deflect in middle + if ( swingUDot > 0.25f ) + {//deflect to top + attacker->client->ps.saberBounceMove = LS_D1_T_; + } + else if ( swingUDot < -0.25f ) + {//deflect to bottom + attacker->client->ps.saberBounceMove = LS_D1_B_; + } + else + {//deflect horizontally? Well, no such thing as straight back in my face, so use top + if ( swingRDot > 0 ) + { + attacker->client->ps.saberBounceMove = LS_D1_TR; + } + else if ( swingRDot < 0 ) + { + attacker->client->ps.saberBounceMove = LS_D1_TL; + } + else + { + attacker->client->ps.saberBounceMove = LS_D1_T_; + } + } + } +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + gi.Printf( S_COLOR_BLUE"%s deflected from %s to %s\n", attacker->targetname, saberMoveData[attacker->client->ps.saberMove].name, saberMoveData[attacker->client->ps.saberBounceMove].name ); + } +#endif + return qtrue; + } +} + + +void WP_SaberClearDamageForEntNum( int entityNum ) +{ +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + if ( entityNum ) + { + Com_Printf( "clearing damage for entnum %d\n", entityNum ); + } + } +#endif// FINAL_BUILD + if ( g_saberRealisticCombat->integer > 1 ) + { + return; + } + for ( int i = 0; i < numVictims; i++ ) + { + if ( victimEntityNum[i] == entityNum ) + { + totalDmg[i] = 0;//no damage + hitLoc[i] = HL_NONE; + hitDismemberLoc[i] = HL_NONE; + hitDismember[i] = qfalse; + victimEntityNum[i] = ENTITYNUM_NONE;//like we never hit him + } + } +} + +extern float damageModifier[]; +extern float hitLocHealthPercentage[]; +qboolean WP_SaberApplyDamage( gentity_t *ent, float baseDamage, int baseDFlags, qboolean brokenParry, saberType_t saberType, qboolean thrownSaber ) +{ + qboolean didDamage = qfalse; + gentity_t *victim; + int dFlags = baseDFlags; + float maxDmg; + + + if ( !numVictims ) + { + return qfalse; + } + for ( int i = 0; i < numVictims; i++ ) + { + dFlags = baseDFlags|DAMAGE_DEATH_KNOCKBACK|DAMAGE_NO_HIT_LOC; + if ( victimEntityNum[i] != ENTITYNUM_NONE && &g_entities[victimEntityNum[i]] != NULL ) + { // Don't bother with this damage if the fraction is higher than the saber's fraction + if ( dmgFraction[i] < saberHitFraction || brokenParry ) + { + victim = &g_entities[victimEntityNum[i]]; + if ( !victim ) + { + continue; + } + + if ( victim->e_DieFunc == dieF_maglock_die ) + {//*sigh*, special check for maglocks + vec3_t testFrom; + if ( ent->client->ps.saberInFlight ) + { + VectorCopy( g_entities[ent->client->ps.saberEntityNum].currentOrigin, testFrom ); + } + else + { + VectorCopy( ent->currentOrigin, testFrom ); + } + testFrom[2] = victim->currentOrigin[2]; + trace_t testTrace; + gi.trace( &testTrace, testFrom, vec3_origin, vec3_origin, victim->currentOrigin, ent->s.number, MASK_SHOT ); + if ( testTrace.entityNum != victim->s.number ) + {//can only damage maglocks if have a clear trace to the thing's origin + continue; + } + } + if ( totalDmg[i] > 0 ) + {//actually want to do *some* damage here + if ( victim->client + && victim->client->NPC_class==CLASS_WAMPA + && victim->activator == ent ) + { + } + else if ( PM_SuperBreakWinAnim( ent->client->ps.torsoAnim ) + || PM_StabDownAnim( ent->client->ps.torsoAnim ) ) + {//never cap the superbreak wins + } + else + { + if ( victim->client + && (victim->s.weapon == WP_SABER || (victim->client->NPC_class==CLASS_REBORN) || (victim->client->NPC_class==CLASS_WAMPA)) + && !g_saberRealisticCombat->integer ) + {//dmg vs other saber fighters is modded by hitloc and capped + totalDmg[i] *= damageModifier[hitLoc[i]]; + if ( hitLoc[i] == HL_NONE ) + { + maxDmg = 33*baseDamage; + } + else + { + maxDmg = 50*hitLocHealthPercentage[hitLoc[i]]*baseDamage;//*victim->client->ps.stats[STAT_MAX_HEALTH]*2.0f; + } + if ( maxDmg < totalDmg[i] ) + { + totalDmg[i] = maxDmg; + } + //dFlags |= DAMAGE_NO_HIT_LOC; + } + //clamp the dmg + if ( victim->s.weapon != WP_SABER ) + {//clamp the dmg between 25 and maxhealth + /* + if ( totalDmg[i] > victim->max_health ) + { + totalDmg[i] = victim->max_health; + } + else */if ( totalDmg[i] < 25 ) + { + totalDmg[i] = 25; + } + if ( totalDmg[i] > 100 )//+(50*g_spskill->integer) ) + {//clamp using same adjustment as in NPC_Begin + totalDmg[i] = 100;//+(50*g_spskill->integer); + } + } + else + {//clamp the dmg between 5 and 100 + if ( !victim->s.number && totalDmg[i] > 50 ) + {//never do more than half full health damage to player + //prevents one-hit kills + totalDmg[i] = 50; + } + else if ( totalDmg[i] > 100 ) + { + totalDmg[i] = 100; + } + else + { + if ( totalDmg[i] < 5 ) + { + totalDmg[i] = 5; + } + } + } + } + + if ( totalDmg[i] > 0 ) + { + gentity_t *inflictor = ent; + didDamage = qtrue; + + if ( baseDamage <= 0.1f ) + {//just get their attention? + dFlags |= DAMAGE_NO_DAMAGE; + } + + if( victim->client ) + { + if ( victim->client->ps.pm_time > 0 && victim->client->ps.pm_flags & PMF_TIME_KNOCKBACK && victim->client->ps.velocity[2] > 0 ) + {//already being knocked around + dFlags |= DAMAGE_NO_KNOCKBACK; + } + if ( g_dismemberment->integer >= 11381138 || g_saberRealisticCombat->integer ) + { + dFlags |= DAMAGE_DISMEMBER; + if ( hitDismember[i] ) + { + victim->client->dismembered = false; + } + } + else if ( hitDismember[i] ) + { + dFlags |= DAMAGE_DISMEMBER; + } + if ( baseDamage <= 1.0f ) + {//very mild damage + if ( victim->s.number == 0 || victim->client->ps.weapon == WP_SABER || victim->client->NPC_class == CLASS_GALAKMECH ) + {//if it's the player or a saber-user, don't kill them with this blow + dFlags |= DAMAGE_NO_KILL; + } + } + } + else + { + if ( victim->takedamage ) + {//some other breakable thing + //create a flash here + g_saberFlashTime = level.time-50; + VectorCopy( dmgSpot[i], g_saberFlashPos ); + } + } + if ( !PM_SuperBreakWinAnim( ent->client->ps.torsoAnim ) + && !PM_StabDownAnim( ent->client->ps.torsoAnim ) + && !g_saberRealisticCombat->integer + && g_saberDamageCapping->integer ) + {//never cap the superbreak wins + if ( victim->client + && victim->s.number >= MAX_CLIENTS ) + { + if ( victim->client->NPC_class == CLASS_SHADOWTROOPER + || ( victim->NPC && (victim->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) ) + {//hit a boss character + int maxDmg = ((3-g_spskill->integer)*5)+10; + if ( totalDmg[i] > maxDmg ) + { + totalDmg[i] = maxDmg; + } + } + else if ( victim->client->ps.weapon == WP_SABER + || victim->client->NPC_class == CLASS_REBORN + || victim->client->NPC_class == CLASS_JEDI ) + {//hit a non-boss saber-user + int maxDmg = ((3-g_spskill->integer)*15)+30; + if ( totalDmg[i] > maxDmg ) + { + totalDmg[i] = maxDmg; + } + } + } + if ( victim->s.number < MAX_CLIENTS + && ent->NPC ) + { + if ( (ent->NPC->aiFlags&NPCAI_BOSS_CHARACTER) + || (ent->NPC->aiFlags&NPCAI_SUBBOSS_CHARACTER) + || ent->client->NPC_class == CLASS_SHADOWTROOPER ) + {//player hit by a boss character + int maxDmg = ((g_spskill->integer+1)*4)+3; + if ( totalDmg[i] > maxDmg ) + { + totalDmg[i] = maxDmg; + } + } + else if ( g_spskill->integer < 3 ) //was < 2 + {//player hit by any enemy //on easy or medium? + int maxDmg = ((g_spskill->integer+1)*10)+20; + if ( totalDmg[i] > maxDmg ) + { + totalDmg[i] = maxDmg; + } + } + } + } + //victim->hitLoc = hitLoc[i]; + + dFlags |= DAMAGE_NO_KNOCKBACK;//okay, let's try no knockback whatsoever... + dFlags &= ~DAMAGE_DEATH_KNOCKBACK; + if ( g_saberRealisticCombat->integer ) + { + dFlags |= DAMAGE_NO_KNOCKBACK; + dFlags &= ~DAMAGE_DEATH_KNOCKBACK; + dFlags &= ~DAMAGE_NO_KILL; + } + if ( ent->client && !ent->s.number ) + { + switch( hitLoc[i] ) + { + case HL_FOOT_RT: + case HL_FOOT_LT: + case HL_LEG_RT: + case HL_LEG_LT: + ent->client->sess.missionStats.legAttacksCnt++; + break; + case HL_WAIST: + case HL_BACK_RT: + case HL_BACK_LT: + case HL_BACK: + case HL_CHEST_RT: + case HL_CHEST_LT: + case HL_CHEST: + ent->client->sess.missionStats.torsoAttacksCnt++; + break; + case HL_ARM_RT: + case HL_ARM_LT: + case HL_HAND_RT: + case HL_HAND_LT: + ent->client->sess.missionStats.armAttacksCnt++; + break; + default: + ent->client->sess.missionStats.otherAttacksCnt++; + break; + } + } + + if ( saberType == SABER_SITH_SWORD ) + {//do knockback + dFlags &= ~(DAMAGE_NO_KNOCKBACK|DAMAGE_DEATH_KNOCKBACK); + } + if ( thrownSaber ) + { + inflictor = &g_entities[ent->client->ps.saberEntityNum]; + } + G_Damage( victim, inflictor, ent, dmgDir[i], dmgSpot[i], ceil(totalDmg[i]), dFlags, MOD_SABER, hitDismemberLoc[i] ); +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + if ( (dFlags&DAMAGE_NO_DAMAGE) ) + { + gi.Printf( S_COLOR_RED"damage: fake, hitLoc %d\n", hitLoc[i] ); + } + else + { + gi.Printf( S_COLOR_RED"damage: %4.2f, hitLoc %d\n", totalDmg[i], hitLoc[i] ); + } + } +#endif + //do the effect + //G_PlayEffect( G_EffectIndex( "blood_sparks" ), dmgSpot[i], dmgDir[i] ); + if ( ent->s.number == 0 ) + { + AddSoundEvent( victim->owner, dmgSpot[i], 256, AEL_DISCOVERED ); + AddSightEvent( victim->owner, dmgSpot[i], 512, AEL_DISCOVERED, 50 ); + } + if ( ent->client ) + { + if ( ent->enemy && ent->enemy == victim ) + {//just so Jedi knows that he hit his enemy + ent->client->ps.saberEventFlags |= SEF_HITENEMY; + } + else + { + ent->client->ps.saberEventFlags |= SEF_HITOBJECT; + } + } + } + } + } + } + } + return didDamage; +} + +void WP_SaberDamageAdd( float trDmg, int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgSpot, float dmg, float fraction, int trHitLoc, qboolean trDismember, int trDismemberLoc ) +{ + int curVictim = 0; + + if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD ) + { + return; + } + if ( trDmg * dmg < 10.0f ) + {//too piddly an amount of damage to really count? + //FIXME: but already did the effect, didn't we... sigh... + //return; + } + if ( trDmg ) + {//did some damage to something + for ( int i = 0; i < numVictims; i++ ) + { + if ( victimEntityNum[i] == trVictimEntityNum ) + {//already hit this guy before + curVictim = i; + break; + } + } + if ( i == numVictims ) + {//haven't hit his guy before + if ( numVictims + 1 >= MAX_SABER_VICTIMS ) + {//can't add another victim at this time + return; + } + //add a new victim to the list + curVictim = numVictims; + victimEntityNum[numVictims++] = trVictimEntityNum; + } + + float addDmg = trDmg*dmg; + if ( trHitLoc!=HL_NONE && (hitLoc[curVictim]==HL_NONE||hitLocHealthPercentage[trHitLoc]>hitLocHealthPercentage[hitLoc[curVictim]]) ) + {//this hitLoc is more critical than the previous one this frame + hitLoc[curVictim] = trHitLoc; + } + + totalDmg[curVictim] += addDmg; + if ( !VectorLengthSquared( dmgDir[curVictim] ) ) + { + VectorCopy( trDmgDir, dmgDir[curVictim] ); + } + if ( !VectorLengthSquared( dmgSpot[curVictim] ) ) + { + VectorCopy( trDmgSpot, dmgSpot[curVictim] ); + } + + // Make sure we keep track of the fraction. Why? + // Well, if the saber hits something that stops it, the damage isn't done past that point. + dmgFraction[curVictim] = fraction; + if ( (trDismemberLoc != HL_NONE && hitDismemberLoc[curVictim] == HL_NONE) + || (!hitDismember[curVictim] && trDismember) ) + {//either this is the first dismember loc we got or we got a loc before, but it wasn't a dismember loc, so take the new one + hitDismemberLoc[curVictim] = trDismemberLoc; + } + if ( trDismember ) + {//we scored a dismemberment hit... + hitDismember[curVictim] = trDismember; + } + } +} + +/* +WP_SabersIntersect + +Breaks the two saber paths into 2 tris each and tests each tri for the first saber path against each of the other saber path's tris + +FIXME: subdivide the arc into a consistant increment +FIXME: test the intersection to see if the sabers really did intersect (weren't going in the same direction and/or passed through same point at different times)? +*/ +extern qboolean tri_tri_intersect(vec3_t V0,vec3_t V1,vec3_t V2,vec3_t U0,vec3_t U1,vec3_t U2); +qboolean WP_SabersIntersect( gentity_t *ent1, int ent1SaberNum, int ent1BladeNum, gentity_t *ent2, qboolean checkDir ) +{ + vec3_t saberBase1, saberTip1, saberBaseNext1, saberTipNext1; + vec3_t saberBase2, saberTip2, saberBaseNext2, saberTipNext2; + int ent2SaberNum = 0, ent2BladeNum = 0; + vec3_t dir; + + /* +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" ); + } +#endif + */ + + if ( !ent1 || !ent2 ) + { + return qfalse; + } + if ( !ent1->client || !ent2->client ) + { + return qfalse; + } + if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 ) + { + return qfalse; + } + + for ( ent2SaberNum = 0; ent2SaberNum < MAX_SABERS; ent2SaberNum++ ) + { + for ( ent2BladeNum = 0; ent2BladeNum < ent2->client->ps.saber[ent2SaberNum].numBlades; ent2BladeNum++ ) + { + if ( ent2->client->ps.saber[ent2SaberNum].type != SABER_NONE + && ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length > 0 ) + {//valid saber and this blade is on + //if ( ent1->client->ps.saberInFlight ) + { + VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, saberBase1 ); + VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBaseNext1 ); + + VectorSubtract( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, dir ); + VectorNormalize( dir ); + VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 ); + + VectorMA( saberBase1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirOld, saberTip1 ); + VectorMA( saberBaseNext1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTipNext1 ); + + VectorSubtract( saberTipNext1, saberTip1, dir ); + VectorNormalize( dir ); + VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 ); + } + /* + else + { + VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBase1 ); + VectorCopy( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointNext, saberBaseNext1 ); + VectorMA( saberBase1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTip1 ); + VectorMA( saberBaseNext1, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].length, ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirNext, saberTipNext1 ); + } + */ + + //if ( ent2->client->ps.saberInFlight ) + { + VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, saberBase2 ); + VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBaseNext2 ); + + VectorSubtract( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, dir ); + VectorNormalize( dir ); + VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 ); + + VectorMA( saberBase2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirOld, saberTip2 ); + VectorMA( saberBaseNext2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTipNext2 ); + + VectorSubtract( saberTipNext2, saberTip2, dir ); + VectorNormalize( dir ); + VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 ); + } + /* + else + { + VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBase2 ); + VectorCopy( ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointNext, saberBaseNext2 ); + VectorMA( saberBase2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTip2 ); + VectorMA( saberBaseNext2, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].length, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirNext, saberTipNext2 ); + } + */ + if ( checkDir ) + {//check the direction of the two swings to make sure the sabers are swinging towards each other + vec3_t saberDir1, saberDir2; + + VectorSubtract( saberTipNext1, saberTip1, saberDir1 ); + VectorSubtract( saberTipNext2, saberTip2, saberDir2 ); + VectorNormalize( saberDir1 ); + VectorNormalize( saberDir2 ); + if ( DotProduct( saberDir1, saberDir2 ) > 0.6f ) + {//sabers moving in same dir, probably didn't actually hit + continue; + } + //now check orientation of sabers, make sure they're not parallel or close to it + float dot = DotProduct( ent1->client->ps.saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, ent2->client->ps.saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir ); + if ( dot > 0.9f || dot < -0.9f ) + {//too parallel to really block effectively? + continue; + } + } + + if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberBaseNext2 ) ) + { + return qtrue; + } + if ( tri_tri_intersect( saberBase1, saberTip1, saberBaseNext1, saberBase2, saberTip2, saberTipNext2 ) ) + { + return qtrue; + } + if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberBaseNext2 ) ) + { + return qtrue; + } + if ( tri_tri_intersect( saberBase1, saberTip1, saberTipNext1, saberBase2, saberTip2, saberTipNext2 ) ) + { + return qtrue; + } + } + } + } + return qfalse; +} + +extern float ShortestLineSegBewteen2LineSegs( vec3_t start1, vec3_t end1, vec3_t start2, vec3_t end2, vec3_t close_pnt1, vec3_t close_pnt2 ); +float WP_SabersDistance( gentity_t *ent1, gentity_t *ent2 ) +{ + vec3_t saberBaseNext1, saberTipNext1, saberPoint1; + vec3_t saberBaseNext2, saberTipNext2, saberPoint2; + + /* +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + gi.Printf( S_COLOR_GREEN"Doing precise saber intersection check\n" ); + } +#endif + */ + + if ( !ent1 || !ent2 ) + { + return qfalse; + } + if ( !ent1->client || !ent2->client ) + { + return qfalse; + } + if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 ) + { + return qfalse; + } + + //FIXME: UGH, how do we make this work for multiply-bladed sabers? + + //if ( ent1->client->ps.saberInFlight ) + { + VectorCopy( ent1->client->ps.saber[0].blade[0].muzzlePoint, saberBaseNext1 ); + VectorMA( saberBaseNext1, ent1->client->ps.saber[0].blade[0].length, ent1->client->ps.saber[0].blade[0].muzzleDir, saberTipNext1 ); + } + /* + else + { + VectorCopy( ent1->client->ps.saber[0].blade[0].muzzlePointNext, saberBaseNext1 ); + VectorMA( saberBaseNext1, ent1->client->ps.saber[0].blade[0].length, ent1->client->ps.saber[0].blade[0].muzzleDirNext, saberTipNext1 ); + } + */ + + //if ( ent2->client->ps.saberInFlight ) + { + VectorCopy( ent2->client->ps.saber[0].blade[0].muzzlePoint, saberBaseNext2 ); + VectorMA( saberBaseNext2, ent2->client->ps.saber[0].blade[0].length, ent2->client->ps.saber[0].blade[0].muzzleDir, saberTipNext2 ); + } + /* + else + { + VectorCopy( ent2->client->ps.saber[0].blade[0].muzzlePointNext, saberBaseNext2 ); + VectorMA( saberBaseNext2, ent2->client->ps.saber[0].blade[0].length, ent2->client->ps.saber[0].blade[0].muzzleDirNext, saberTipNext2 ); + } + */ + + float sabersDist = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 ); + + //okay, this is a super hack, but makes saber collisions look better from the player point of view + /* + if ( sabersDist < 16.0f ) + { + vec3_t saberDistDir, saberMidPoint, camLookDir; + + VectorSubtract( saberPoint2, saberPoint1, saberDistDir ); + VectorMA( saberPoint1, 0.5f, saberDistDir, saberMidPoint ); + VectorSubtract( saberMidPoint, cg.refdef.vieworg, camLookDir ); + VectorNormalize( saberDistDir ); + VectorNormalize( camLookDir ); + float dot = fabs(DotProduct( camLookDir, saberDistDir )); + sabersDist -= 8.0f*dot; + if ( sabersDist < 0.0f ) + { + sabersDist = 0.0f; + } + } + */ + +#ifndef FINAL_BUILD + if ( d_saberCombat->integer > 2 ) + { + G_DebugLine( saberPoint1, saberPoint2, FRAMETIME, 0x00ffffff, qtrue ); + } +#endif + return sabersDist; +} + +qboolean WP_SabersIntersection( gentity_t *ent1, gentity_t *ent2, vec3_t intersect ) +{ + vec3_t saberBaseNext1, saberTipNext1, saberPoint1; + vec3_t saberBaseNext2, saberTipNext2, saberPoint2; + int saberNum1, saberNum2, bladeNum1, bladeNum2; + float lineSegLength, bestLineSegLength = Q3_INFINITE; + + if ( !ent1 || !ent2 ) + { + return qfalse; + } + if ( !ent1->client || !ent2->client ) + { + return qfalse; + } + if ( ent1->client->ps.SaberLength() <= 0 || ent2->client->ps.SaberLength() <= 0 ) + { + return qfalse; + } + + //UGH, had to make this work for multiply-bladed sabers + for ( saberNum1 = 0; saberNum1 < MAX_SABERS; saberNum1++ ) + { + for ( bladeNum1 = 0; bladeNum1 < ent1->client->ps.saber[saberNum1].numBlades; bladeNum1++ ) + { + if ( ent1->client->ps.saber[saberNum1].type != SABER_NONE + && ent1->client->ps.saber[saberNum1].blade[bladeNum1].length > 0 ) + {//valid saber and this blade is on + for ( saberNum2 = 0; saberNum2 < MAX_SABERS; saberNum2++ ) + { + for ( bladeNum2 = 0; bladeNum2 < ent2->client->ps.saber[saberNum2].numBlades; bladeNum2++ ) + { + if ( ent2->client->ps.saber[saberNum2].type != SABER_NONE + && ent2->client->ps.saber[saberNum2].blade[bladeNum2].length > 0 ) + {//valid saber and this blade is on + VectorCopy( ent1->client->ps.saber[saberNum1].blade[bladeNum1].muzzlePoint, saberBaseNext1 ); + VectorMA( saberBaseNext1, ent1->client->ps.saber[saberNum1].blade[bladeNum1].length, ent1->client->ps.saber[saberNum1].blade[bladeNum1].muzzleDir, saberTipNext1 ); + + VectorCopy( ent2->client->ps.saber[saberNum2].blade[bladeNum2].muzzlePoint, saberBaseNext2 ); + VectorMA( saberBaseNext2, ent2->client->ps.saber[saberNum2].blade[bladeNum2].length, ent2->client->ps.saber[saberNum2].blade[bladeNum2].muzzleDir, saberTipNext2 ); + + lineSegLength = ShortestLineSegBewteen2LineSegs( saberBaseNext1, saberTipNext1, saberBaseNext2, saberTipNext2, saberPoint1, saberPoint2 ); + if ( lineSegLength < bestLineSegLength ) + { + bestLineSegLength = lineSegLength; + VectorAdd( saberPoint1, saberPoint2, intersect ); + VectorScale( intersect, 0.5, intersect ); + } + } + } + } + } + } + } + + if(ent1->client && ent1->client->ps.clientNum == 0) + FF_Play(fffx_Laser1); + return qtrue; +} + +const char *hit_blood_sparks = "sparks/blood_sparks2"; // could have changed this effect directly, but this is just safer in case anyone anywhere else is using the old one for something? +const char *hit_sparks = "saber/saber_cut"; + +//extern char *hitLocName[]; +qboolean WP_SaberDamageEffects( trace_t *tr, const vec3_t start, float length, float dmg, vec3_t dmgDir, vec3_t bladeDir, int enemyTeam, saberType_t saberType ) +{ + + int hitEntNum[MAX_G2_COLLISIONS]; + for ( int hen = 0; hen < MAX_G2_COLLISIONS; hen++ ) + { + hitEntNum[hen] = ENTITYNUM_NONE; + } + //NOTE: = {0} does NOT work on anything but bytes? + float hitEntDmgAdd[MAX_G2_COLLISIONS] = {0}; + float hitEntDmgSub[MAX_G2_COLLISIONS] = {0}; + vec3_t hitEntPoint[MAX_G2_COLLISIONS]; + vec3_t hitEntDir[MAX_G2_COLLISIONS]; + float hitEntStartFrac[MAX_G2_COLLISIONS] = {0}; + int trHitLoc[MAX_G2_COLLISIONS] = {HL_NONE};//same as 0 + int trDismemberLoc[MAX_G2_COLLISIONS] = {HL_NONE};//same as 0 + qboolean trDismember[MAX_G2_COLLISIONS] = {qfalse};//same as 0 + int i,z; + int numHitEnts = 0; + float distFromStart,doDmg; + const char *hitEffect, *trSurfName; + gentity_t *hitEnt; + + for (z=0; z < MAX_G2_COLLISIONS; z++) + { + if ( tr->G2CollisionMap[z].mEntityNum == -1 ) + {//actually, completely break out of this for loop since nothing after this in the aray should ever be valid either + continue;//break;// + } + + CCollisionRecord &coll = tr->G2CollisionMap[z]; + //distFromStart = Distance( start, coll.mCollisionPosition ); + distFromStart = coll.mDistance; + + /* + //FIXME: (distFromStart/length) is not guaranteed to be from 0 to 1... *sigh*... + if ( length && saberHitFraction < 1.0f && (distFromStart/length) < 1.0f && (distFromStart/length) > saberHitFraction ) + {//a saber was hit before this point, don't count it +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + gi.Printf( S_COLOR_MAGENTA"rejecting G2 collision- %4.2f farther than saberHitFraction %4.2f\n", (distFromStart/length), saberHitFraction ); + } +#endif + continue; + } + */ + + for ( i = 0; i < numHitEnts; i++ ) + { + if ( hitEntNum[i] == coll.mEntityNum ) + {//we hit this ent before + //we'll want to add this dist + hitEntDmgAdd[i] = distFromStart; + break; + } + } + if ( i == numHitEnts ) + {//first time we hit this ent + if ( numHitEnts == MAX_G2_COLLISIONS ) + {//hit too many damn ents! + continue; + } + hitEntNum[numHitEnts] = coll.mEntityNum; + if ( !coll.mFlags ) + {//hmm, we came out first, so we must have started inside + //we'll want to subtract this dist + hitEntDmgAdd[numHitEnts] = distFromStart; + } + else + {//we're entering the model + //we'll want to subtract this dist + hitEntDmgSub[numHitEnts] = distFromStart; + } + //keep track of how far in the damage was done + hitEntStartFrac[numHitEnts] = hitEntDmgSub[numHitEnts]/length; + //remember the entrance point + VectorCopy( coll.mCollisionPosition, hitEntPoint[numHitEnts] ); + //remember the entrance dir + VectorCopy( coll.mCollisionNormal, hitEntDir[numHitEnts] ); + VectorNormalize( hitEntDir[numHitEnts] ); + + //do the effect + + //FIXME: check material rather than team? + hitEnt = &g_entities[hitEntNum[numHitEnts]]; + hitEffect = hit_blood_sparks; + if ( hitEnt != NULL ) + { + if ( hitEnt->client ) + { + class_t npc_class = hitEnt->client->NPC_class; + if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || npc_class == CLASS_REMOTE || + npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || + npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 || + npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY ) + { + hitEffect = hit_sparks; + } + } + else + { + // So sue me, this is the easiest way to check to see if this is the turbo laser from t2_wedge, + // in which case I don't want the saber effects goin off on it. + if ( (hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY) + && hitEnt->takedamage == qfalse + && Q_stricmp( hitEnt->classname, "misc_turret" ) == 0 ) + { + continue; + } + else + { + if ( dmg ) + {//only do these effects if actually trying to damage the thing... + if ( (hitEnt->svFlags&SVF_BBRUSH)//a breakable brush + && ( (hitEnt->spawnflags&1)//INVINCIBLE + ||(hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY) )//HEAVY weapon damage only + ) + {//no hit effect (besides regular client-side one) + hitEffect = NULL; + } + else + { + hitEffect = hit_sparks; + } + } + } + } + } + + //FIXME: play less if damage is less? + if ( !g_saberNoEffects ) + { + if ( hitEffect != NULL ) + { + G_PlayEffect( hitEffect, coll.mCollisionPosition, coll.mCollisionNormal ); +// if(ent->client && ent->client->ps.clientNum == 0) +// FF_Play(fffx_Laser1); + } + /* + if ( hitEnt && hitEnt->client ) + { + CG_AddGhoul2Mark( PGORE_DECAL02, Q_flrand(3.5, 4.0), coll.mCollisionPosition, hitEntDir[numHitEnts], hitEnt->s.number, + hitEnt->client->ps.origin, hitEnt->client->renderInfo.legsYaw, hitEnt->ghoul2 ); + CG_AddGhoul2Mark( PGORE_DECAL03, Q_flrand(3.5, 4.0), coll.mCollisionPosition, hitEntDir[numHitEnts], hitEnt->s.number, + hitEnt->client->ps.origin, hitEnt->client->renderInfo.legsYaw, hitEnt->ghoul2 ); + } + */ + } + + //Get the hit location based on surface name + if ( (hitLoc[hitEntNum[numHitEnts]] == HL_NONE && trHitLoc[numHitEnts] == HL_NONE) + || (hitDismemberLoc[hitEntNum[numHitEnts]] == HL_NONE && trDismemberLoc[numHitEnts] == HL_NONE) + || (!hitDismember[hitEntNum[numHitEnts]] && !trDismember[numHitEnts]) ) + {//no hit loc set for this ent this damage cycle yet + //FIXME: find closest impact surf *first* (per ent), then call G_GetHitLocFromSurfName? + //FIXED: if hit multiple ents in this collision record, these trSurfName, trDismember and trDismemberLoc will get stomped/confused over the multiple ents I hit + trSurfName = gi.G2API_GetSurfaceName( &g_entities[coll.mEntityNum].ghoul2[coll.mModelIndex], coll.mSurfaceIndex ); + trDismember[numHitEnts] = G_GetHitLocFromSurfName( &g_entities[coll.mEntityNum], trSurfName, &trHitLoc[numHitEnts], coll.mCollisionPosition, dmgDir, bladeDir, MOD_SABER, saberType ); + if ( trDismember[numHitEnts] ) + { + trDismemberLoc[numHitEnts] = trHitLoc[numHitEnts]; + } + /* + if ( trDismember[numHitEnts] ) + { + Com_Printf( S_COLOR_RED"Okay to dismember %s on ent %d\n", hitLocName[trDismemberLoc[numHitEnts]], hitEntNum[numHitEnts] ); + } + else + { + Com_Printf( "Hit (no dismember) %s on ent %d\n", hitLocName[trHitLoc[numHitEnts]], hitEntNum[numHitEnts] ); + } + */ + } + numHitEnts++; + } + } + + //now go through all the ents we hit and do the damage + for ( i = 0; i < numHitEnts; i++ ) + { + doDmg = dmg; + if ( hitEntNum[i] != ENTITYNUM_NONE ) + { + if ( doDmg < 10 ) + {//base damage is less than 10 + if ( hitEntNum[i] != 0 ) + {//not the player + hitEnt = &g_entities[hitEntNum[i]]; + if ( !hitEnt->client || (hitEnt->client->ps.weapon!=WP_SABER&&hitEnt->client->NPC_class!=CLASS_GALAKMECH&&hitEnt->client->playerTeam==enemyTeam) ) + {//did *not* hit a jedi and did *not* hit the player + //make sure the base damage is high against non-jedi, feels better + doDmg = 10; + } + } + } + if ( !hitEntDmgAdd[i] && !hitEntDmgSub[i] ) + {//spent entire time in model + //NOTE: will we even get a collision then? + doDmg *= length; + } + else if ( hitEntDmgAdd[i] && hitEntDmgSub[i] ) + {//we did enter and exit + doDmg *= hitEntDmgAdd[i] - hitEntDmgSub[i]; + } + else if ( !hitEntDmgAdd[i] ) + {//we didn't exit, just entered + doDmg *= length - hitEntDmgSub[i]; + } + else if ( !hitEntDmgSub[i] ) + {//we didn't enter, only exited + doDmg *= hitEntDmgAdd[i]; + } + if ( doDmg > 0 ) + { + WP_SaberDamageAdd( 1.0, hitEntNum[i], hitEntDir[i], hitEntPoint[i], ceil(doDmg), hitEntStartFrac[i], trHitLoc[i], trDismember[i], trDismemberLoc[i] ); + } + } + } + return (numHitEnts>0); +} + +void WP_SaberKnockaway( gentity_t *attacker, trace_t *tr ) +{ + WP_SaberDrop( attacker, &g_entities[attacker->client->ps.saberEntityNum] ); + G_Sound( &g_entities[attacker->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) ); + G_PlayEffect( "saber/saber_block", tr->endpos ); + + saberHitFraction = tr->fraction; +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + gi.Printf( S_COLOR_MAGENTA"WP_SaberKnockaway: saberHitFraction %4.2f\n", saberHitFraction ); + } +#endif + VectorCopy( tr->endpos, saberHitLocation ); + saberHitEntity = tr->entityNum; + g_saberFlashTime = level.time-50; + VectorCopy( saberHitLocation, g_saberFlashPos ); + + //FIXME: make hitEnt play an attack anim or some other special anim when this happens + //gentity_t *hitEnt = &g_entities[tr->entityNum]; + //NPC_SetAnim( hitEnt, SETANIM_BOTH, BOTH_KNOCKSABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +} + +qboolean G_InCinematicSaberAnim( gentity_t *self ) +{ + if ( self->NPC + && self->NPC->behaviorState == BS_CINEMATIC + && (self->client->ps.torsoAnim == BOTH_CIN_16 ||self->client->ps.torsoAnim == BOTH_CIN_17) ) + { + return qtrue; + } + return qfalse; +} + +#define SABER_COLLISION_DIST 6//was 2//was 4//was 8//was 16 +extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f ); +qboolean WP_SaberDamageForTrace( int ignore, vec3_t start, vec3_t end, float dmg, + vec3_t bladeDir, qboolean noGhoul, int attackStrength, + saberType_t saberType, qboolean extrapolate, + int saberNum, int bladeNum ) +{ + trace_t tr; + vec3_t dir; + int mask = (MASK_SHOT|CONTENTS_LIGHTSABER); + gentity_t *attacker = &g_entities[ignore]; + + vec3_t end2; + VectorCopy( end, end2 ); + if ( extrapolate ) + { + //NOTE: since we can no longer use the predicted point, extrapolate the trace some. + // this may allow saber hits that aren't actually hits, but it doesn't look too bad + vec3_t diff; + VectorSubtract( end, start, diff ); + VectorNormalize( diff ); + VectorMA( end2, SABER_EXTRAPOLATE_DIST, diff, end2 ); + } + + if ( !noGhoul ) + { + if ( !attacker->s.number + || (attacker->client + && (attacker->client->playerTeam==TEAM_PLAYER + || attacker->client->NPC_class==CLASS_SHADOWTROOPER + || attacker->client->NPC_class==CLASS_ALORA + || (attacker->NPC && (attacker->NPC->aiFlags&NPCAI_BOSS_CHARACTER)) + ) + ) + )//&&attackStrength==FORCE_LEVEL_3) + {//player,. player allies, shadowtroopers, tavion and desann use larger traces + vec3_t traceMins = {-2,-2,-2}, traceMaxs = {2,2,2}; + gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX + } + /* + else if ( !attacker->s.number ) + { + vec3_t traceMins = {-1,-1,-1}, traceMaxs = {1,1,1}; + gi.trace( &tr, start, traceMins, traceMaxs, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX + } + */ + else + {//reborn use smaller traces + gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_COLLIDE, 10 );//G2_SUPERSIZEDBBOX + } + } + else + { + gi.trace( &tr, start, NULL, NULL, end2, ignore, mask, G2_NOCOLLIDE, 10 ); + } + + +#ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + if ( attacker != NULL && attacker->client != NULL ) + { + G_DebugLine(start, end2, FRAMETIME, WPDEBUG_SaberColor( attacker->client->ps.saber[0].blade[0].color ), qtrue); + } + } +#endif + + if ( tr.entityNum == ENTITYNUM_NONE ) + { + return qfalse; + } + + if ( tr.entityNum == ENTITYNUM_WORLD ) + { + return qtrue; + } + + if ( &g_entities[tr.entityNum] ) + { + gentity_t *hitEnt = &g_entities[tr.entityNum]; + gentity_t *owner = g_entities[tr.entityNum].owner; + if ( hitEnt->contents & CONTENTS_LIGHTSABER ) + { + if ( attacker && attacker->client && attacker->client->ps.saberInFlight ) + {//thrown saber hit something + if ( owner + && owner->s.number + && owner->client + && owner->NPC + && owner->health > 0 ) + { + if ( owner->client->NPC_class == CLASS_ALORA ) + {//alora takes less damage + dmg *= 0.25f; + } + else if ( owner->client->NPC_class == CLASS_TAVION + /*|| (owner->client->NPC_class == CLASS_SHADOWTROOPER && !Q_irand( 0, g_spskill->integer*3 )) + || (Q_irand( -5, owner->NPC->rank ) > RANK_CIVILIAN && !Q_irand( 0, g_spskill->integer*3 ))*/ ) + {//Tavion can toss a blocked thrown saber aside + WP_SaberKnockaway( attacker, &tr ); + Jedi_PlayDeflectSound( owner ); + return qfalse; + } + } + } + //FIXME: take target FP_SABER_DEFENSE and attacker FP_SABER_OFFENSE into account here somehow? + qboolean sabersIntersect = WP_SabersIntersect( attacker, saberNum, bladeNum, owner, qfalse );//qtrue ); + float sabersDist; + if ( attacker && attacker->client && attacker->client->ps.saberInFlight + && owner && owner->s.number == 0 && (g_saberAutoBlocking->integer||attacker->client->ps.saberBlockingTime>level.time) )//NPC flying saber hit player's saber bounding box + {//players have g_saberAutoBlocking, do the more generous check against flying sabers + //FIXME: instead of hitting the player's saber bounding box + //and picking an anim afterwards, have him use AI similar + //to the AI the jedi use for picking a saber melee block...? + sabersDist = 0; + } + else + {//sabers must actually collide with the attacking saber + sabersDist = WP_SabersDistance( attacker, owner ); + if ( attacker && attacker->client && attacker->client->ps.saberInFlight ) + { + sabersDist /= 2.0f; + if ( sabersDist <= 16.0f ) + { + sabersIntersect = qtrue; + } + } +#ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + gi.Printf( "sabersDist: %4.2f\n", sabersDist ); + } +#endif//FINAL_BUILD + } + if ( sabersCrossed == -1 || sabersCrossed > sabersDist ) + { + sabersCrossed = sabersDist; + } + float collisionDist; + if ( g_saberRealisticCombat->integer ) + { + collisionDist = SABER_COLLISION_DIST; + } + else + { + collisionDist = SABER_COLLISION_DIST+6+g_spskill->integer*4; + } + { + if ( G_InCinematicSaberAnim( owner ) + && G_InCinematicSaberAnim( attacker ) ) + { + sabersIntersect = qtrue; + } + } + if ( owner && owner->client && (attacker != NULL) + && (sabersDist > collisionDist )//|| !InFront( attacker->currentOrigin, owner->currentOrigin, owner->client->ps.viewangles, 0.35f )) + && !sabersIntersect )//was qtrue, but missed too much? + {//swing came from behind and/or was not stopped by a lightsaber + //re-try the trace without checking for lightsabers + gi.trace ( &tr, start, NULL, NULL, end2, ignore, mask&~CONTENTS_LIGHTSABER, G2_NOCOLLIDE, 10 ); + if ( tr.entityNum == ENTITYNUM_WORLD ) + { + return qtrue; + } + if ( tr.entityNum == ENTITYNUM_NONE || &g_entities[tr.entityNum] == NULL ) + {//didn't hit the owner + /* + if ( attacker + && attacker->client + && (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove )) + && DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 ) + { + if ( owner->NPC + && !owner->client->ps.saberInFlight + && owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN + && !Jedi_SaberBusy( owner ) ) + {//owner parried, just make sure they're saber is in the right spot - only does this if they're not already doing something with saber + if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 ))) + {//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat + //FIXME: also take into account the owner's FP_DEFENSE? + if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN ) + {//lower-rank Jedi aren't as good blockers + vec3_t attDir; + VectorSubtract( end2, start, attDir ); + VectorNormalize( attDir ); + Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL ); + } + } + } + } + */ + return qfalse; // Exit, but we didn't hit the wall. + } +#ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + if ( !attacker->s.number ) + { + gi.Printf( S_COLOR_MAGENTA"%d saber hit owner through saber %4.2f, dist = %4.2f\n", level.time, saberHitFraction, sabersDist ); + } + } +#endif//FINAL_BUILD + hitEnt = &g_entities[tr.entityNum]; + owner = g_entities[tr.entityNum].owner; + } + else + {//hit a lightsaber + if ( (tr.fraction < saberHitFraction || tr.startsolid) + && sabersDist < (8.0f+g_spskill->value)*4.0f// 50.0f//16.0f + && (sabersIntersect || sabersDist < (4.0f+g_spskill->value)*2.0f) )//32.0f) ) + { // This saber hit closer than the last one. + if ( (tr.allsolid || tr.startsolid) && owner && owner->client ) + {//tr.fraction will be 0, unreliable... so calculate actual + float dist = Distance( start, end2 ); + if ( dist ) + { + float hitFrac = WP_SabersDistance( attacker, owner )/dist; + if ( hitFrac > 1.0f ) + {//umm... minimum distance between sabers was longer than trace...? + hitFrac = 1.0f; + } + if ( hitFrac < saberHitFraction ) + { + saberHitFraction = hitFrac; + } + } + else + { + saberHitFraction = 0.0f; + } +#ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + if ( !attacker->s.number ) + { + gi.Printf( S_COLOR_GREEN"%d saber hit saber dist %4.2f allsolid %4.2f\n", level.time, sabersDist, saberHitFraction ); + } + } +#endif//FINAL_BUILD + } + else + { +#ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + if ( !attacker->s.number ) + { + gi.Printf( S_COLOR_BLUE"%d saber hit saber dist %4.2f, frac %4.2f\n", level.time, sabersDist, saberHitFraction ); + } + saberHitFraction = tr.fraction; + } +#endif//FINAL_BUILD + } +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + gi.Printf( S_COLOR_MAGENTA"hit saber: saberHitFraction %4.2f, allsolid %d, startsolid %d\n", saberHitFraction, tr.allsolid, tr.startsolid ); + } +#endif//FINAL_BUILD + VectorCopy(tr.endpos, saberHitLocation); + saberHitEntity = tr.entityNum; + } + /* + if ( owner + && owner->client + && attacker + && attacker->client + && (PM_SaberInAttack( attacker->client->ps.saberMove ) || PM_SaberInStart( attacker->client->ps.saberMove )) + && DistanceSquared( tr.endpos, owner->currentOrigin ) < 10000 ) + { + if ( owner->NPC + && !owner->client->ps.saberInFlight + && owner->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN + && !Jedi_SaberBusy( owner ) ) + {//owner parried, just make sure they're saber is in the right spot - only does this if they're not already doing something with saber + if ( g_spskill->integer && (g_spskill->integer > 1 || Q_irand( 0, 1 ))) + {//if on easy, they don't cheat like this, if on medium, they cheat 50% of the time, if on hard, they always cheat + //FIXME: also take into account the owner's FP_DEFENSE? + if ( Q_irand( 0, owner->NPC->rank ) >= RANK_CIVILIAN ) + {//lower-rank Jedi aren't as good blockers + vec3_t attDir; + VectorSubtract( end2, start, attDir ); + VectorNormalize( attDir ); + Jedi_SaberBlockGo( owner, owner->NPC->last_ucmd, start, attDir, NULL ); + } + } + } + } + */ + //FIXME: check to see if we broke the saber + // go through the impacted surfaces and call WP_BreakSaber + // PROBLEM: saberEnt doesn't actually have a saber g2 model + // and/or isn't in same location as saber model attached + // to the client. We'd have to fake it somehow... + return qfalse; // Exit, but we didn't hit the wall. + } + } + else + { +#ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + if ( !attacker->s.number ) + { + gi.Printf( S_COLOR_RED"%d saber hit owner directly %4.2f\n", level.time, saberHitFraction ); + } + } +#endif//FINAL_BUILD + } + + if ( attacker && attacker->client && attacker->client->ps.saberInFlight ) + {//thrown saber hit something + if ( ( hitEnt && hitEnt->client && hitEnt->health > 0 && ( hitEnt->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",hitEnt->NPC_type) || hitEnt->client->NPC_class == CLASS_LUKE || hitEnt->client->NPC_class == CLASS_BOBAFETT || (hitEnt->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) || + ( owner && owner->client && owner->health > 0 && ( owner->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",owner->NPC_type) || owner->client->NPC_class == CLASS_LUKE || owner->client->NPC_class == CLASS_BOBAFETT || (owner->client->ps.powerups[PW_GALAK_SHIELD] > 0) ) ) ) + {//Luke and Desann slap thrown sabers aside + //FIXME: control the direction of the thrown saber... if hit Galak's shield, bounce directly away from his origin? + WP_SaberKnockaway( attacker, &tr ); + if ( hitEnt->client ) + { + Jedi_PlayDeflectSound( hitEnt ); + } + else + { + Jedi_PlayDeflectSound( owner ); + } + return qfalse; // Exit, but we didn't hit the wall. + } + } + + if ( hitEnt->takedamage ) + { + //no team damage: if ( !hitEnt->client || attacker == NULL || !attacker->client || (hitEnt->client->playerTeam != attacker->client->playerTeam) ) + { + //multiply the damage by the total distance of the swipe + VectorSubtract( end2, start, dir ); + float len = VectorNormalize( dir );//VectorLength( dir ); + if ( noGhoul || !hitEnt->ghoul2.size() ) + {//we weren't doing a ghoul trace + const char *hitEffect = NULL; + if ( dmg >= 1.0 && hitEnt->bmodel ) + { + dmg = 1.0; + } + if ( len > 1 ) + { + dmg *= len; + } +#ifndef FINAL_BUILD + if ( d_saberCombat->integer > 1 ) + { + if ( !(hitEnt->contents & CONTENTS_LIGHTSABER) ) + { + gi.Printf( S_COLOR_GREEN"Hit ent, but no ghoul collisions\n" ); + } + } +#endif + float trFrac, dmgFrac; + if ( tr.allsolid ) + {//totally inside them + trFrac = 1.0; + dmgFrac = 0.0; + } + else if ( tr.startsolid ) + {//started inside them + //we don't know how much was inside, we know it's less than all, so use half? + trFrac = 0.5; + dmgFrac = 0.0; + } + else + {//started outside them and hit them + //yeah. this doesn't account for coming out the other wide, but we can worry about that later (use ghoul2) + trFrac = (1.0f - tr.fraction); + dmgFrac = tr.fraction; + } + WP_SaberDamageAdd( trFrac, tr.entityNum, dir, tr.endpos, dmg, dmgFrac, HL_NONE, qfalse, HL_NONE ); + if ( !tr.allsolid && !tr.startsolid ) + { + VectorScale( dir, -1, dir ); + } + if ( hitEnt != NULL ) + { + if ( hitEnt->client ) + { + //don't do blood sparks on non-living things + class_t npc_class = hitEnt->client->NPC_class; + if ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || + npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || npc_class == CLASS_REMOTE || + npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || npc_class == CLASS_MARK2 || + npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST || npc_class == CLASS_SENTRY ) + { + hitEffect = hit_sparks; + } + } + else + { + if ( dmg ) + {//only do these effects if actually trying to damage the thing... + if ( (hitEnt->svFlags&SVF_BBRUSH)//a breakable brush + && ( (hitEnt->spawnflags&1)//INVINCIBLE + ||(hitEnt->flags&FL_DMG_BY_HEAVY_WEAP_ONLY)//HEAVY weapon damage only + ||(hitEnt->NPC_targetname&&attacker&&attacker->targetname&&Q_stricmp(attacker->targetname,hitEnt->NPC_targetname)) ) )//only breakable by an entity who is not the attacker + {//no hit effect (besides regular client-side one) + } + else + { + hitEffect = hit_sparks; + } + } + } + } + + if ( !g_saberNoEffects && hitEffect != NULL ) + { + G_PlayEffect( hitEffect, tr.endpos, dir );//"saber_cut" +// if(ent->client && ent->client->ps.clientNum == 0) +// FF_Play(fffx_Laser1); + } + } + else + {//we were doing a ghoul trace + if ( !WP_SaberDamageEffects( &tr, start, len, dmg, dir, bladeDir, attacker->client->enemyTeam, saberType ) ) + {//didn't hit a ghoul ent + /* + if ( && hitEnt->ghoul2.size() ) + {//it was a ghoul2 model so we should have hit it + return qfalse; + } + */ + } + } + } + } + } + + return qfalse; +} + +#define LOCK_IDEAL_DIST_TOP 32.0f +#define LOCK_IDEAL_DIST_CIRCLE 48.0f +#define LOCK_IDEAL_DIST_JKA 46.0f//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN +extern void PM_SetAnimFrame( gentity_t *gent, int frame, qboolean torso, qboolean legs ); +extern qboolean ValidAnimFileIndex ( int index ); +int G_SaberLockAnim( int attackerSaberStyle, int defenderSaberStyle, int topOrSide, int lockOrBreakOrSuperBreak, int winOrLose ) +{ + int baseAnim = -1; + if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK ) + {//special case: if we're using the same style and locking + if ( attackerSaberStyle == defenderSaberStyle + || (attackerSaberStyle>=SS_FAST&&attackerSaberStyle<=SS_TAVION&&defenderSaberStyle>=SS_FAST&&defenderSaberStyle<=SS_TAVION) ) + {//using same style + if ( winOrLose == SABERLOCK_LOSE ) + {//you want the defender's stance... + switch ( defenderSaberStyle ) + { + case SS_DUAL: + if ( topOrSide == SABERLOCK_TOP ) + { + baseAnim = BOTH_LK_DL_DL_T_L_2; + } + else + { + baseAnim = BOTH_LK_DL_DL_S_L_2; + } + break; + case SS_STAFF: + if ( topOrSide == SABERLOCK_TOP ) + { + baseAnim = BOTH_LK_ST_ST_T_L_2; + } + else + { + baseAnim = BOTH_LK_ST_ST_S_L_2; + } + break; + default: + if ( topOrSide == SABERLOCK_TOP ) + { + baseAnim = BOTH_LK_S_S_T_L_2; + } + else + { + baseAnim = BOTH_LK_S_S_S_L_2; + } + break; + } + } + } + } + if ( baseAnim == -1 ) + { + switch ( attackerSaberStyle ) + { + case SS_DUAL: + switch ( defenderSaberStyle ) + { + case SS_DUAL: + baseAnim = BOTH_LK_DL_DL_S_B_1_L; + break; + case SS_STAFF: + baseAnim = BOTH_LK_DL_ST_S_B_1_L; + break; + default://single + baseAnim = BOTH_LK_DL_S_S_B_1_L; + break; + } + break; + case SS_STAFF: + switch ( defenderSaberStyle ) + { + case SS_DUAL: + baseAnim = BOTH_LK_ST_DL_S_B_1_L; + break; + case SS_STAFF: + baseAnim = BOTH_LK_ST_ST_S_B_1_L; + break; + default://single + baseAnim = BOTH_LK_ST_S_S_B_1_L; + break; + } + break; + default://single + switch ( defenderSaberStyle ) + { + case SS_DUAL: + baseAnim = BOTH_LK_S_DL_S_B_1_L; + break; + case SS_STAFF: + baseAnim = BOTH_LK_S_ST_S_B_1_L; + break; + default://single + baseAnim = BOTH_LK_S_S_S_B_1_L; + break; + } + break; + } + //side lock or top lock? + if ( topOrSide == SABERLOCK_TOP ) + { + baseAnim += 5; + } + //lock, break or superbreak? + if ( lockOrBreakOrSuperBreak == SABERLOCK_LOCK ) + { + baseAnim += 2; + } + else + {//a break or superbreak + if ( lockOrBreakOrSuperBreak == SABERLOCK_SUPERBREAK ) + { + baseAnim += 3; + } + //winner or loser? + if ( winOrLose == SABERLOCK_WIN ) + { + baseAnim += 1; + } + } + } + return baseAnim; +} + +qboolean G_CheckIncrementLockAnim( int anim, int winOrLose ) +{ + qboolean increment = qfalse;//??? + //RULE: if you are the first style in the lock anim, you advance from LOSING position to WINNING position + // if you are the second style in the lock anim, you advance from WINNING position to LOSING position + switch ( anim ) + { + //increment to win: + case BOTH_LK_DL_DL_S_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_DL_S_L_2: //lock if I'm using dual vs. dual and other initiated + case BOTH_LK_DL_DL_T_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_DL_T_L_2: //lock if I'm using dual vs. dual and other initiated + case BOTH_LK_DL_S_S_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_DL_S_T_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_DL_ST_S_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_DL_ST_T_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_S_S_S_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_S_S_T_L_2: //lock if I'm using single vs. a single and other initiated + case BOTH_LK_ST_S_S_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_ST_S_T_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_ST_ST_T_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_ST_T_L_2: //lock if I'm using staff vs. a staff and other initiated + if ( winOrLose == SABERLOCK_WIN ) + { + increment = qtrue; + } + else + { + increment = qfalse; + } + break; + + //decrement to win: + case BOTH_LK_S_DL_S_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_DL_T_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_S_S_L_2: //lock if I'm using single vs. a single and other intitiated + case BOTH_LK_S_S_T_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_S_ST_S_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_S_ST_T_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_ST_DL_S_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_DL_T_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_ST_S_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_ST_S_L_2: //lock if I'm using staff vs. a staff and other initiated + if ( winOrLose == SABERLOCK_WIN ) + { + increment = qfalse; + } + else + { + increment = qtrue; + } + break; + default: +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: unknown Saber Lock Anim: %s!!!\n", animTable[anim].name ); +#endif + break; + } + return increment; +} + +qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode ) +{ + animation_t *anim; + int attAnim, defAnim, advance = 0; + float attStart = 0.5f, defStart = 0.5f; + float idealDist = 48.0f; + //FIXME: this distances need to be modified by the lengths of the sabers involved... + //MATCH ANIMS + if ( lockMode == LOCK_KYLE_GRAB1 + || lockMode == LOCK_KYLE_GRAB2 + || lockMode == LOCK_KYLE_GRAB3 ) + { + float numSpins = 1.0f; + idealDist = 46.0f;//42.0f; + attStart = defStart = 0.0f; + + switch ( lockMode ) + { + default: + case LOCK_KYLE_GRAB1: + attAnim = BOTH_KYLE_PA_1; + defAnim = BOTH_PLAYER_PA_1; + numSpins = 2.0f; + break; + case LOCK_KYLE_GRAB2: + attAnim = BOTH_KYLE_PA_3; + defAnim = BOTH_PLAYER_PA_3; + numSpins = 1.0f; + break; + case LOCK_KYLE_GRAB3: + attAnim = BOTH_KYLE_PA_2; + defAnim = BOTH_PLAYER_PA_2; + defender->forcePushTime = level.time + PM_AnimLength( defender->client->clientInfo.animFileIndex, BOTH_PLAYER_PA_2 ); + numSpins = 3.0f; + break; + } + attacker->client->ps.SaberDeactivate(); + defender->client->ps.SaberDeactivate(); + if ( d_slowmodeath->integer > 3 + && ( defender->s.number < MAX_CLIENTS + || attacker->s.number < MAX_CLIENTS ) ) + { + if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) ) + { + int effectTime = PM_AnimLength( attacker->client->clientInfo.animFileIndex, (animNumber_t)attAnim ); + int spinTime = floor((float)effectTime/numSpins); + int meFlags = (MEF_MULTI_SPIN);//MEF_NO_TIMESCALE|MEF_NO_VERTBOB| + if ( Q_irand( 0, 1 ) ) + { + meFlags |= MEF_REVERSE_SPIN; + } + G_StartMatrixEffect( attacker, meFlags, effectTime, 0.75f, spinTime ); + } + } + } + else if ( lockMode == LOCK_FORCE_DRAIN ) + { + idealDist = 46.0f;//42.0f; + attStart = defStart = 0.0f; + + attAnim = BOTH_FORCE_DRAIN_GRAB_START; + defAnim = BOTH_FORCE_DRAIN_GRABBED; + attacker->client->ps.SaberDeactivate(); + defender->client->ps.SaberDeactivate(); + } + else + { + if ( lockMode == LOCK_RANDOM ) + { + lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 ); + } + //FIXME: attStart% and idealDist will change per saber lock anim pairing... do we need a big table like in bg_panimate.cpp? + if ( attacker->client->ps.saberAnimLevel >= SS_FAST + && attacker->client->ps.saberAnimLevel <= SS_TAVION + && defender->client->ps.saberAnimLevel >= SS_FAST + && defender->client->ps.saberAnimLevel <= SS_TAVION ) + {//2 single sabers? Just do it the old way... + switch ( lockMode ) + { + case LOCK_TOP: + attAnim = BOTH_BF2LOCK;// - starts in middle + defAnim = BOTH_BF1LOCK;// - starts in middle + attStart = defStart = 0.5f; + idealDist = LOCK_IDEAL_DIST_TOP; + break; + case LOCK_DIAG_TR: + attAnim = BOTH_CCWCIRCLELOCK; //- starts in middle + defAnim = BOTH_CWCIRCLELOCK;// - starts in middle + attStart = defStart = 0.5f; + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + case LOCK_DIAG_TL: + attAnim = BOTH_CWCIRCLELOCK;// - starts in middle + defAnim = BOTH_CCWCIRCLELOCK;// - starts in middle + attStart = defStart = 0.5f; + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + case LOCK_DIAG_BR: + attAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right + defAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left + attStart = defStart = 0.85f;//move to end of anim + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + case LOCK_DIAG_BL: + attAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left + defAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right + attStart = defStart = 0.85f;//move to end of anim + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + case LOCK_R: + attAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right + defAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left + attStart = defStart = 0.75f;//move to end of anim + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + case LOCK_L: + attAnim = BOTH_CCWCIRCLELOCK;// - starts on right, to left + defAnim = BOTH_CWCIRCLELOCK;// - starts on left, to right + attStart = defStart = 0.75f;//move to end of anim + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + default: + return qfalse; + break; + } + } + else + {//use the new system + idealDist = LOCK_IDEAL_DIST_JKA;//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN + if ( lockMode == LOCK_TOP ) + {//top lock + attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_WIN ); + defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_LOSE ); + attStart = defStart = 0.5f; + } + else + {//side lock + switch ( lockMode ) + { + case LOCK_DIAG_TR: + attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + attStart = defStart = 0.5f; + break; + case LOCK_DIAG_TL: + attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + attStart = defStart = 0.5f; + break; + case LOCK_DIAG_BR: + attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) ) + { + attStart = 0.85f;//move to end of anim + } + else + { + attStart = 0.15f;//start at beginning of anim + } + if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) ) + { + defStart = 0.85f;//start at end of anim + } + else + { + defStart = 0.15f;//start at beginning of anim + } + break; + case LOCK_DIAG_BL: + attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) ) + { + attStart = 0.85f;//move to end of anim + } + else + { + attStart = 0.15f;//start at beginning of anim + } + if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) ) + { + defStart = 0.85f;//start at end of anim + } + else + { + defStart = 0.15f;//start at beginning of anim + } + break; + case LOCK_R: + attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) ) + { + attStart = 0.75f;//move to end of anim + } + else + { + attStart = 0.25f;//start at beginning of anim + } + if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) ) + { + defStart = 0.75f;//start at end of anim + } + else + { + defStart = 0.25f;//start at beginning of anim + } + break; + case LOCK_L: + attAnim = G_SaberLockAnim( attacker->client->ps.saberAnimLevel, defender->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + defAnim = G_SaberLockAnim( defender->client->ps.saberAnimLevel, attacker->client->ps.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + //attacker starts with advantage + if ( G_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) ) + { + attStart = 0.75f;//move to end of anim + } + else + { + attStart = 0.25f;//start at beginning of anim + } + if ( G_CheckIncrementLockAnim( defAnim, SABERLOCK_LOSE ) ) + { + defStart = 0.75f;//start at end of anim + } + else + { + defStart = 0.25f;//start at beginning of anim + } + break; + default: + return qfalse; + break; + } + } + } + } + //set the proper anims + NPC_SetAnim( attacker, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC_SetAnim( defender, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + //don't let them store a kick for the whole saberlock.... + attacker->client->ps.saberMoveNext = defender->client->ps.saberMoveNext = LS_NONE; + // + if ( attStart > 0.0f ) + { + if( ValidAnimFileIndex( attacker->client->clientInfo.animFileIndex ) ) + { + anim = &level.knownAnimFileSets[attacker->client->clientInfo.animFileIndex].animations[attAnim]; + advance = floor( anim->numFrames*attStart ); + PM_SetAnimFrame( attacker, anim->firstFrame + advance, qtrue, qtrue ); +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", attacker->NPC_type, animTable[attAnim].name, anim->numFrames-advance ); + } +#endif + } + } + if ( defStart > 0.0f ) + { + if( ValidAnimFileIndex( defender->client->clientInfo.animFileIndex ) ) + { + anim = &level.knownAnimFileSets[defender->client->clientInfo.animFileIndex].animations[defAnim]; + advance = ceil( anim->numFrames*defStart ); + PM_SetAnimFrame( defender, anim->firstFrame + advance, qtrue, qtrue );//was anim->firstFrame + anim->numFrames - advance, but that's wrong since they are matched anims +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + Com_Printf( "%s starting saber lock, anim = %s, %d frames to go!\n", defender->NPC_type, animTable[defAnim].name, advance ); + } +#endif + } + } + VectorClear( attacker->client->ps.velocity ); + VectorClear( attacker->client->ps.moveDir ); + VectorClear( defender->client->ps.velocity ); + VectorClear( defender->client->ps.moveDir ); + if ( lockMode == LOCK_KYLE_GRAB1 + || lockMode == LOCK_KYLE_GRAB2 + || lockMode == LOCK_KYLE_GRAB3 + || lockMode == LOCK_FORCE_DRAIN ) + {//not a real lock, just freeze them both in place + //can't move or attack + attacker->client->ps.pm_time = attacker->client->ps.weaponTime = attacker->client->ps.legsAnimTimer; + attacker->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + attacker->painDebounceTime = level.time + attacker->client->ps.pm_time; + if ( lockMode != LOCK_FORCE_DRAIN ) + { + defender->client->ps.torsoAnimTimer += 200; + defender->client->ps.legsAnimTimer += 200; + } + defender->client->ps.pm_time = defender->client->ps.weaponTime = defender->client->ps.legsAnimTimer; + defender->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + if ( lockMode != LOCK_FORCE_DRAIN ) + { + attacker->aimDebounceTime = level.time + attacker->client->ps.pm_time; + } + } + else + { + attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + SABER_LOCK_TIME; + attacker->client->ps.legsAnimTimer = attacker->client->ps.torsoAnimTimer = defender->client->ps.legsAnimTimer = defender->client->ps.torsoAnimTimer = SABER_LOCK_TIME; + //attacker->client->ps.weaponTime = defender->client->ps.weaponTime = SABER_LOCK_TIME; + attacker->client->ps.saberLockEnemy = defender->s.number; + defender->client->ps.saberLockEnemy = attacker->s.number; + } + + //MATCH ANGLES + if ( lockMode == LOCK_KYLE_GRAB1 + || lockMode == LOCK_KYLE_GRAB2 + || lockMode == LOCK_KYLE_GRAB3 ) + {//not a real lock, just set pitch to 0 + attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH] = 0; + } + else + { + //FIXME: if zDiff in elevation, make lower look up and upper look down and move them closer? + float defPitchAdd = 0, zDiff = ((attacker->currentOrigin[2]+attacker->client->standheight)-(defender->currentOrigin[2]+defender->client->standheight)); + if ( zDiff > 24 ) + { + defPitchAdd = -30; + } + else if ( zDiff < -24 ) + { + defPitchAdd = 30; + } + else + { + defPitchAdd = zDiff/24.0f*-30.0f; + } + if ( attacker->NPC && defender->NPC ) + {//if 2 NPCs, just set pitch to 0 + attacker->client->ps.viewangles[PITCH] = -defPitchAdd; + defender->client->ps.viewangles[PITCH] = defPitchAdd; + } + else + {//if a player is involved, clamp player's pitch and match NPC's to player + if ( !attacker->s.number ) + { + //clamp to defPitch + if ( attacker->client->ps.viewangles[PITCH] > -defPitchAdd + 10 ) + { + attacker->client->ps.viewangles[PITCH] = -defPitchAdd + 10; + } + else if ( attacker->client->ps.viewangles[PITCH] < -defPitchAdd-10 ) + { + attacker->client->ps.viewangles[PITCH] = -defPitchAdd-10; + } + //clamp to sane numbers + if ( attacker->client->ps.viewangles[PITCH] > 50 ) + { + attacker->client->ps.viewangles[PITCH] = 50; + } + else if ( attacker->client->ps.viewangles[PITCH] < -50 ) + { + attacker->client->ps.viewangles[PITCH] = -50; + } + defender->client->ps.viewangles[PITCH] = attacker->client->ps.viewangles[PITCH]*-1; + defPitchAdd = defender->client->ps.viewangles[PITCH]; + } + else if ( !defender->s.number ) + { + //clamp to defPitch + if ( defender->client->ps.viewangles[PITCH] > defPitchAdd + 10 ) + { + defender->client->ps.viewangles[PITCH] = defPitchAdd + 10; + } + else if ( defender->client->ps.viewangles[PITCH] < defPitchAdd-10 ) + { + defender->client->ps.viewangles[PITCH] = defPitchAdd-10; + } + //clamp to sane numbers + if ( defender->client->ps.viewangles[PITCH] > 50 ) + { + defender->client->ps.viewangles[PITCH] = 50; + } + else if ( defender->client->ps.viewangles[PITCH] < -50 ) + { + defender->client->ps.viewangles[PITCH] = -50; + } + defPitchAdd = defender->client->ps.viewangles[PITCH]; + attacker->client->ps.viewangles[PITCH] = defender->client->ps.viewangles[PITCH]*-1; + } + } + } + vec3_t attAngles, defAngles, defDir; + VectorSubtract( defender->currentOrigin, attacker->currentOrigin, defDir ); + VectorCopy( attacker->client->ps.viewangles, attAngles ); + attAngles[YAW] = vectoyaw( defDir ); + SetClientViewAngle( attacker, attAngles ); + defAngles[PITCH] = attAngles[PITCH]*-1; + defAngles[YAW] = AngleNormalize180( attAngles[YAW] + 180); + defAngles[ROLL] = 0; + SetClientViewAngle( defender, defAngles ); + + //MATCH POSITIONS + vec3_t newOrg; + /* + idealDist -= fabs(defPitchAdd)/8.0f; + */ + float scale = (attacker->s.modelScale[0]+attacker->s.modelScale[1])*0.5f; + if ( scale && scale != 1.0f ) + { + idealDist += 8*(scale-1.0f); + } + scale = (defender->s.modelScale[0]+defender->s.modelScale[1])*0.5f; + if ( scale && scale != 1.0f ) + { + idealDist += 8*(scale-1.0f); + } + + float diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist + //try to move attacker half the diff towards the defender + VectorMA( attacker->currentOrigin, diff*0.5f, defDir, newOrg ); + trace_t trace; + gi.trace( &trace, attacker->currentOrigin, attacker->mins, attacker->maxs, newOrg, attacker->s.number, attacker->clipmask ); + if ( !trace.startsolid && !trace.allsolid ) + { + G_SetOrigin( attacker, trace.endpos ); + gi.linkentity( attacker ); + } + //now get the defender's dist and do it for him too + vec3_t attDir; + VectorSubtract( attacker->currentOrigin, defender->currentOrigin, attDir ); + diff = VectorNormalize( attDir ) - idealDist;//diff will be the total error in dist + //try to move defender all of the remaining diff towards the attacker + VectorMA( defender->currentOrigin, diff, attDir, newOrg ); + gi.trace( &trace, defender->currentOrigin, defender->mins, defender->maxs, newOrg, defender->s.number, defender->clipmask ); + if ( !trace.startsolid && !trace.allsolid ) + { + G_SetOrigin( defender, trace.endpos ); + gi.linkentity( defender ); + } + + //DONE! + + return qtrue; +} + +qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 ) +{ + if ( ent1->client->playerTeam == ent2->client->playerTeam ) + { + return qfalse; + } + if ( ent1->client->NPC_class == CLASS_SABER_DROID + || ent2->client->NPC_class == CLASS_SABER_DROID ) + {//they don't have saberlock anims + return qfalse; + } + if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE || + ent2->client->ps.groundEntityNum == ENTITYNUM_NONE ) + { + return qfalse; + } + if ( !ent1->client->ps.saber[0].lockable || !ent2->client->ps.saber[0].lockable ) + {//one of these sabers cannot lock (like a lance) + //FIXME: check the second sabers too? + return qfalse; + } + if ( ent1->painDebounceTime > level.time-1000 || ent2->painDebounceTime > level.time-1000 ) + {//can't saberlock if you're not ready + return qfalse; + } + if ( fabs( ent1->currentOrigin[2]-ent2->currentOrigin[2]) > 18 ) + { + return qfalse; + } + float dist = DistanceSquared(ent1->currentOrigin,ent2->currentOrigin); + if ( dist < 64 || dist > 6400 )//( dist < 128 || dist > 2304 ) + {//between 8 and 80 from each other//was 16 and 48 + return qfalse; + } + if ( !InFOV( ent1, ent2, 40, 180 ) || !InFOV( ent2, ent1, 40, 180 ) ) + { + return qfalse; + } + //Check for certain anims that *cannot* lock + //FIXME: there should probably be a whole *list* of these, but I'll put them in as they come up + if ( ent1->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent1->client->ps.torsoAnimTimer > 300 ) + {//can't lock when saber is behind you + return qfalse; + } + if ( ent2->client->ps.torsoAnim == BOTH_A2_STABBACK1 && ent2->client->ps.torsoAnimTimer > 300 ) + {//can't lock when saber is behind you + return qfalse; + } + if ( PM_LockedAnim( ent1->client->ps.torsoAnim ) + || PM_LockedAnim( ent2->client->ps.torsoAnim ) ) + {//stuck doing something else + return qfalse; + } + if ( PM_SaberLockBreakAnim( ent1->client->ps.torsoAnim ) + || PM_SaberLockBreakAnim( ent2->client->ps.torsoAnim ) ) + {//still finishing the last lock break! + return qfalse; + } + //BR to TL lock + if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A2_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A3_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A4_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A5_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A6_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A7_BR_TL ) + {//ent1 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR ); + } + if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A2_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A3_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A4_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A5_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A6_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A7_BR_TL ) + {//ent2 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR ); + } + //BL to TR lock + if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A2_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A3_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A4_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A5_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A6_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A7_BL_TR ) + {//ent1 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL ); + } + if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A2_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A3_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A4_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A5_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A6_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A7_BL_TR ) + {//ent2 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL ); + } + //L to R lock + if ( ent1->client->ps.torsoAnim == BOTH_A1__L__R || + ent1->client->ps.torsoAnim == BOTH_A2__L__R || + ent1->client->ps.torsoAnim == BOTH_A3__L__R || + ent1->client->ps.torsoAnim == BOTH_A4__L__R || + ent1->client->ps.torsoAnim == BOTH_A5__L__R || + ent1->client->ps.torsoAnim == BOTH_A6__L__R || + ent1->client->ps.torsoAnim == BOTH_A7__L__R ) + {//ent1 is attacking l to r + return WP_SabersCheckLock2( ent1, ent2, LOCK_L ); + /* + if ( ent2BlockingPlayer ) + {//player will block this anyway + return WP_SabersCheckLock2( ent1, ent2, LOCK_L ); + } + if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A2_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A3_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A4_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A5_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A6_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A7_TL_BR || + ent2->client->ps.torsoAnim == BOTH_P1_S1_TR || + ent2->client->ps.torsoAnim == BOTH_P1_S1_BL ) + {//ent2 is attacking or blocking on the r + return WP_SabersCheckLock2( ent1, ent2, LOCK_L ); + } + if ( ent2Boss && !Q_irand( 0, 3 ) ) + { + return WP_SabersCheckLock2( ent1, ent2, LOCK_L ); + } + */ + } + if ( ent2->client->ps.torsoAnim == BOTH_A1__L__R || + ent2->client->ps.torsoAnim == BOTH_A2__L__R || + ent2->client->ps.torsoAnim == BOTH_A3__L__R || + ent2->client->ps.torsoAnim == BOTH_A4__L__R || + ent2->client->ps.torsoAnim == BOTH_A5__L__R || + ent2->client->ps.torsoAnim == BOTH_A6__L__R || + ent2->client->ps.torsoAnim == BOTH_A7__L__R ) + {//ent2 is attacking l to r + return WP_SabersCheckLock2( ent2, ent1, LOCK_L ); + /* + if ( ent1BlockingPlayer ) + {//player will block this anyway + return WP_SabersCheckLock2( ent2, ent1, LOCK_L ); + } + if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A2_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A3_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A4_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A5_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A6_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A7_TL_BR || + ent1->client->ps.torsoAnim == BOTH_P1_S1_TR || + ent1->client->ps.torsoAnim == BOTH_P1_S1_BL ) + {//ent1 is attacking or blocking on the r + return WP_SabersCheckLock2( ent2, ent1, LOCK_L ); + } + if ( ent1Boss && !Q_irand( 0, 3 ) ) + { + return WP_SabersCheckLock2( ent2, ent1, LOCK_L ); + } + */ + } + //R to L lock + if ( ent1->client->ps.torsoAnim == BOTH_A1__R__L || + ent1->client->ps.torsoAnim == BOTH_A2__R__L || + ent1->client->ps.torsoAnim == BOTH_A3__R__L || + ent1->client->ps.torsoAnim == BOTH_A4__R__L || + ent1->client->ps.torsoAnim == BOTH_A5__R__L || + ent1->client->ps.torsoAnim == BOTH_A6__R__L || + ent1->client->ps.torsoAnim == BOTH_A7__R__L ) + {//ent1 is attacking r to l + return WP_SabersCheckLock2( ent1, ent2, LOCK_R ); + /* + if ( ent2BlockingPlayer ) + {//player will block this anyway + return WP_SabersCheckLock2( ent1, ent2, LOCK_R ); + } + if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A2_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A3_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A4_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A5_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A6_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A7_TR_BL || + ent2->client->ps.torsoAnim == BOTH_P1_S1_TL || + ent2->client->ps.torsoAnim == BOTH_P1_S1_BR ) + {//ent2 is attacking or blocking on the l + return WP_SabersCheckLock2( ent1, ent2, LOCK_R ); + } + if ( ent2Boss && !Q_irand( 0, 3 ) ) + { + return WP_SabersCheckLock2( ent1, ent2, LOCK_R ); + } + */ + } + if ( ent2->client->ps.torsoAnim == BOTH_A1__R__L || + ent2->client->ps.torsoAnim == BOTH_A2__R__L || + ent2->client->ps.torsoAnim == BOTH_A3__R__L || + ent2->client->ps.torsoAnim == BOTH_A4__R__L || + ent2->client->ps.torsoAnim == BOTH_A5__R__L || + ent2->client->ps.torsoAnim == BOTH_A6__R__L || + ent2->client->ps.torsoAnim == BOTH_A7__R__L ) + {//ent2 is attacking r to l + return WP_SabersCheckLock2( ent2, ent1, LOCK_R ); + /* + if ( ent1BlockingPlayer ) + {//player will block this anyway + return WP_SabersCheckLock2( ent2, ent1, LOCK_R ); + } + if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A2_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A3_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A4_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A5_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A6_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A7_TR_BL || + ent1->client->ps.torsoAnim == BOTH_P1_S1_TL || + ent1->client->ps.torsoAnim == BOTH_P1_S1_BR ) + {//ent1 is attacking or blocking on the l + return WP_SabersCheckLock2( ent2, ent1, LOCK_R ); + } + if ( ent1Boss && !Q_irand( 0, 3 ) ) + { + return WP_SabersCheckLock2( ent2, ent1, LOCK_R ); + } + */ + } + //TR to BL lock + if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A2_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A3_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A4_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A5_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A6_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A7_TR_BL ) + {//ent1 is attacking diagonally + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR ); + /* + if ( ent2BlockingPlayer ) + {//player will block this anyway + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR ); + } + if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A2_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A3_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A4_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A5_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A6_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A7_TR_BL || + ent2->client->ps.torsoAnim == BOTH_P1_S1_TL ) + {//ent2 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR ); + } + if ( ent2->client->ps.torsoAnim == BOTH_A1_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A2_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A3_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A4_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A5_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A6_BR_TL || + ent2->client->ps.torsoAnim == BOTH_A7_BR_TL || + ent2->client->ps.torsoAnim == BOTH_P1_S1_BL ) + {//ent2 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BL ); + } + if ( ent2Boss && !Q_irand( 0, 3 ) ) + { + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TR ); + } + */ + } + + if ( ent2->client->ps.torsoAnim == BOTH_A1_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A2_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A3_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A4_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A5_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A6_TR_BL || + ent2->client->ps.torsoAnim == BOTH_A7_TR_BL ) + {//ent2 is attacking diagonally + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR ); + /* + if ( ent1BlockingPlayer ) + {//player will block this anyway + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR ); + } + if ( ent1->client->ps.torsoAnim == BOTH_A1_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A2_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A3_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A4_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A5_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A6_TR_BL || + ent1->client->ps.torsoAnim == BOTH_A7_TR_BL || + ent1->client->ps.torsoAnim == BOTH_P1_S1_TL ) + {//ent1 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR ); + } + if ( ent1->client->ps.torsoAnim == BOTH_A1_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A2_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A3_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A4_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A5_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A6_BR_TL || + ent1->client->ps.torsoAnim == BOTH_A7_BR_TL || + ent1->client->ps.torsoAnim == BOTH_P1_S1_BL ) + {//ent1 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BL ); + } + if ( ent1Boss && !Q_irand( 0, 3 ) ) + { + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TR ); + } + */ + } + + //TL to BR lock + if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A2_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A3_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A4_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A5_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A6_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A7_TL_BR ) + {//ent1 is attacking diagonally + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL ); + /* + if ( ent2BlockingPlayer ) + {//player will block this anyway + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL ); + } + if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A2_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A3_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A4_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A5_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A6_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A7_TL_BR || + ent2->client->ps.torsoAnim == BOTH_P1_S1_TR ) + {//ent2 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL ); + } + if ( ent2->client->ps.torsoAnim == BOTH_A1_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A2_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A3_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A4_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A5_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A6_BL_TR || + ent2->client->ps.torsoAnim == BOTH_A7_BL_TR || + ent2->client->ps.torsoAnim == BOTH_P1_S1_BR ) + {//ent2 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_BR ); + } + if ( ent2Boss && !Q_irand( 0, 3 ) ) + { + return WP_SabersCheckLock2( ent1, ent2, LOCK_DIAG_TL ); + } + */ + } + + if ( ent2->client->ps.torsoAnim == BOTH_A1_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A2_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A3_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A4_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A5_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A6_TL_BR || + ent2->client->ps.torsoAnim == BOTH_A7_TL_BR ) + {//ent2 is attacking diagonally + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL ); + /* + if ( ent1BlockingPlayer ) + {//player will block this anyway + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL ); + } + if ( ent1->client->ps.torsoAnim == BOTH_A1_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A2_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A3_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A4_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A5_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A6_TL_BR || + ent1->client->ps.torsoAnim == BOTH_A7_TL_BR || + ent1->client->ps.torsoAnim == BOTH_P1_S1_TR ) + {//ent1 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL ); + } + if ( ent1->client->ps.torsoAnim == BOTH_A1_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A2_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A3_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A4_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A5_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A6_BL_TR || + ent1->client->ps.torsoAnim == BOTH_A7_BL_TR || + ent1->client->ps.torsoAnim == BOTH_P1_S1_BR ) + {//ent1 is attacking in the opposite diagonal + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_BR ); + } + if ( ent1Boss && !Q_irand( 0, 3 ) ) + { + return WP_SabersCheckLock2( ent2, ent1, LOCK_DIAG_TL ); + } + */ + } + //T to B lock + if ( ent1->client->ps.torsoAnim == BOTH_A1_T__B_ || + ent1->client->ps.torsoAnim == BOTH_A2_T__B_ || + ent1->client->ps.torsoAnim == BOTH_A3_T__B_ || + ent1->client->ps.torsoAnim == BOTH_A4_T__B_ || + ent1->client->ps.torsoAnim == BOTH_A5_T__B_ || + ent1->client->ps.torsoAnim == BOTH_A6_T__B_ || + ent1->client->ps.torsoAnim == BOTH_A7_T__B_ ) + {//ent1 is attacking top-down + /* + if ( ent2->client->ps.torsoAnim == BOTH_P1_S1_T_ || + ent2->client->ps.torsoAnim == BOTH_K1_S1_T_ ) + */ + {//ent2 is blocking at top + return WP_SabersCheckLock2( ent1, ent2, LOCK_TOP ); + } + } + + if ( ent2->client->ps.torsoAnim == BOTH_A1_T__B_ || + ent2->client->ps.torsoAnim == BOTH_A2_T__B_ || + ent2->client->ps.torsoAnim == BOTH_A3_T__B_ || + ent2->client->ps.torsoAnim == BOTH_A4_T__B_ || + ent2->client->ps.torsoAnim == BOTH_A5_T__B_ || + ent2->client->ps.torsoAnim == BOTH_A6_T__B_ || + ent2->client->ps.torsoAnim == BOTH_A7_T__B_ ) + {//ent2 is attacking top-down + /* + if ( ent1->client->ps.torsoAnim == BOTH_P1_S1_T_ || + ent1->client->ps.torsoAnim == BOTH_K1_S1_T_ ) + */ + {//ent1 is blocking at top + return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP ); + } + } + /* + if ( !Q_irand( 0, 10 ) ) + { + return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM ); + } + */ + return qfalse; +} + +qboolean WP_SaberParry( gentity_t *victim, gentity_t *attacker ) +{ + if ( !victim || !victim->client || !attacker ) + { + return qfalse; + } + if ( Rosh_BeingHealed( victim ) ) + { + return qfalse; + } + if ( G_InCinematicSaberAnim( victim ) ) + { + return qfalse; + } + if ( PM_SuperBreakLoseAnim( victim->client->ps.torsoAnim ) + || PM_SuperBreakWinAnim( victim->client->ps.torsoAnim ) ) + { + return qfalse; + } + if ( victim->s.number || g_saberAutoBlocking->integer || victim->client->ps.saberBlockingTime > level.time ) + {//either an NPC or a player who is blocking + if ( !PM_SaberInTransitionAny( victim->client->ps.saberMove ) + && !PM_SaberInBounce( victim->client->ps.saberMove ) + && !PM_SaberInKnockaway( victim->client->ps.saberMove ) ) + {//I'm not attacking, in transition or in a bounce or knockaway, so play a parry + WP_SaberBlockNonRandom( victim, saberHitLocation, qfalse ); + } + victim->client->ps.saberEventFlags |= SEF_PARRIED; + + //since it was parried, take away any damage done + //FIXME: what if the damage was done before the parry? + WP_SaberClearDamageForEntNum( victim->s.number ); + + //tell the victim to get mad at me + if ( victim->enemy != attacker && victim->client->playerTeam != attacker->client->playerTeam ) + {//they're not mad at me and they're not on my team + G_ClearEnemy( victim ); + G_SetEnemy( victim, attacker ); + } + return qtrue; + } + return qfalse; +} + +qboolean WP_BrokenParryKnockDown( gentity_t *victim ) +{ + if ( !victim || !victim->client ) + { + return qfalse; + } + if ( PM_SuperBreakLoseAnim( victim->client->ps.torsoAnim ) + || PM_SuperBreakWinAnim( victim->client->ps.torsoAnim ) ) + { + return qfalse; + } + if ( victim->client->ps.saberMove == LS_PARRY_UP + || victim->client->ps.saberMove == LS_PARRY_UR + || victim->client->ps.saberMove == LS_PARRY_UL + || victim->client->ps.saberMove == LS_H1_BR + || victim->client->ps.saberMove == LS_H1_B_ + || victim->client->ps.saberMove == LS_H1_BL ) + {//knock their asses down! + int knockAnim = BOTH_KNOCKDOWN1; + if ( PM_CrouchAnim( victim->client->ps.legsAnim ) ) + { + knockAnim = BOTH_KNOCKDOWN4; + } + NPC_SetAnim( victim, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + G_AddEvent( victim, EV_PAIN, victim->health ); + return qtrue; + } + return qfalse; +} + +qboolean G_TryingKataAttack( gentity_t *self, usercmd_t *cmd ) +{ + if ( g_saberNewControlScheme->integer ) + {//use the new control scheme: force focus button + if ( (cmd->buttons&BUTTON_FORCE_FOCUS) ) + { + return qtrue; + } + else + { + return qfalse; + } + } + else //if ( self && self->client ) + {//use the old control scheme + if ( (cmd->buttons&BUTTON_ALT_ATTACK) ) + {//pressing alt-attack + //if ( !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) ) + {//haven't been holding alt-attack + if ( (cmd->buttons&BUTTON_ATTACK) ) + {//pressing attack + return qtrue; + } + } + } + } + return qfalse; +} + +qboolean G_TryingPullAttack( gentity_t *self, usercmd_t *cmd, qboolean amPulling ) +{ + if ( g_saberNewControlScheme->integer ) + {//use the new control scheme: force focus button + if ( (cmd->buttons&BUTTON_FORCE_FOCUS) ) + { + if ( self && self->client ) + { + if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) + {//force pull 3 + if ( amPulling + || (self->client->ps.forcePowersActive&(1<client->ps.forcePowerDebounce[FP_PULL] > level.time ) //force-pulling + {//pulling + return qtrue; + } + } + } + } + else + { + return qfalse; + } + } + else + {//use the old control scheme + if ( (cmd->buttons&BUTTON_ATTACK) ) + {//pressing attack + if ( self && self->client ) + { + if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) + {//force pull 3 + if ( amPulling + || (self->client->ps.forcePowersActive&(1<client->ps.forcePowerDebounce[FP_PULL] > level.time ) //force-pulling + {//pulling + return qtrue; + } + } + } + } + } + return qfalse; +} + +qboolean G_TryingCartwheel( gentity_t *self, usercmd_t *cmd ) +{ + if ( g_saberNewControlScheme->integer ) + {//use the new control scheme: force focus button + if ( (cmd->buttons&BUTTON_FORCE_FOCUS) ) + { + return qtrue; + } + else + { + return qfalse; + } + } + else + {//use the old control scheme + if ( (cmd->buttons&BUTTON_ATTACK) ) + {//pressing attack + if ( cmd->rightmove ) + { + if ( self && self->client ) + { + if ( cmd->upmove>0 //) + && self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//on ground, pressing jump + return qtrue; + } + else + {//just jumped? + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE + && level.time - self->client->ps.lastOnGround <= 250 //50//250 + && (self->client->ps.pm_flags&PMF_JUMPING) )//jumping + {//just jumped this or last frame + return qtrue; + } + } + } + } + } + } + return qfalse; +} + +qboolean G_TryingSpecial( gentity_t *self, usercmd_t *cmd ) +{ + if ( g_saberNewControlScheme->integer ) + {//use the new control scheme: force focus button + if ( (cmd->buttons&BUTTON_FORCE_FOCUS) ) + { + return qtrue; + } + else + { + return qfalse; + } + } + else + {//use the old control scheme + return qfalse; + } +} + +qboolean G_TryingJumpAttack( gentity_t *self, usercmd_t *cmd ) +{ + if ( g_saberNewControlScheme->integer ) + {//use the new control scheme: force focus button + if ( (cmd->buttons&BUTTON_FORCE_FOCUS) ) + { + return qtrue; + } + else + { + return qfalse; + } + } + else + {//use the old control scheme + if ( (cmd->buttons&BUTTON_ATTACK) ) + {//pressing attack + if ( cmd->upmove>0 ) + {//pressing jump + return qtrue; + } + else if ( self && self->client ) + {//just jumped? + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE + && level.time - self->client->ps.lastOnGround <= 250 + && (self->client->ps.pm_flags&PMF_JUMPING) )//jumping + {//jumped within the last quarter second + return qtrue; + } + } + } + } + return qfalse; +} + +qboolean G_TryingJumpForwardAttack( gentity_t *self, usercmd_t *cmd ) +{ + if ( g_saberNewControlScheme->integer ) + {//use the new control scheme: force focus button + if ( (cmd->buttons&BUTTON_FORCE_FOCUS) ) + { + return qtrue; + } + else + { + return qfalse; + } + } + else + {//use the old control scheme + if ( (cmd->buttons&BUTTON_ATTACK) ) + {//pressing attack + if ( cmd->forwardmove > 0 ) + {//moving forward + if ( self && self->client ) + { + if ( cmd->upmove>0 + && self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//pressing jump + return qtrue; + } + else + {//no slop on forward jumps - must be precise! + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE + && level.time - self->client->ps.lastOnGround <= 250 //50 + && (self->client->ps.pm_flags&PMF_JUMPING) )//jumping + {//just jumped this or last frame + return qtrue; + } + } + } + } + } + } + return qfalse; +} + +qboolean G_TryingLungeAttack( gentity_t *self, usercmd_t *cmd ) +{ + if ( g_saberNewControlScheme->integer ) + {//use the new control scheme: force focus button + if ( (cmd->buttons&BUTTON_FORCE_FOCUS) ) + { + return qtrue; + } + else + { + return qfalse; + } + } + else + {//use the old control scheme + if ( (cmd->buttons&BUTTON_ATTACK) ) + {//pressing attack + if ( cmd->upmove<0 ) + {//pressing crouch + return qtrue; + } + else if ( self && self->client ) + {//just unducked? + if ( (self->client->ps.pm_flags&PMF_DUCKED) ) + {//just unducking + return qtrue; + } + } + } + } + return qfalse; +} + + +//FIXME: for these below funcs, maybe in the old control scheme some moves should still cost power... if so, pass in the saberMove and use a switch statement +qboolean G_EnoughPowerForSpecialMove( int forcePower, int cost, qboolean kataMove ) +{ + if ( g_saberNewControlScheme->integer || kataMove ) + {//special moves cost power + if ( forcePower >= cost ) + { + return qtrue; + } + else + { + cg.forceHUDTotalFlashTime = level.time + 1000; + return qfalse; + } + } + else + {//old control scheme: uses no power, so just do it + return qtrue; + } +} + +void G_DrainPowerForSpecialMove( gentity_t *self, forcePowers_t fp, int cost, qboolean kataMove ) +{ + if ( !self || !self->client || self->s.number >= MAX_CLIENTS ) + { + return; + } + if ( g_saberNewControlScheme->integer || kataMove ) + {//special moves cost power + WP_ForcePowerDrain( self, fp, cost );//drain the required force power + } + else + {//old control scheme: uses no power, so just do it + } +} + +int G_CostForSpecialMove( int cost, qboolean kataMove ) +{ + if ( g_saberNewControlScheme->integer || kataMove ) + {//special moves cost power + return cost; + } + else + {//old control scheme: uses no power, so just do it + return 0; + } +} + +/* +--------------------------------------------------------- +void WP_SaberDamageTrace( gentity_t *ent, int saberNum, int bladeNum ) + + Constantly trace from the old blade pos to new, down the saber beam and do damage + + FIXME: if the dot product of the old muzzle dir and the new muzzle dir is < 0.75, subdivide it and do multiple traces so we don't flatten out the arc! +--------------------------------------------------------- +*/ +#define MAX_SABER_SWING_INC 0.33f +void WP_SaberDamageTrace( gentity_t *ent, int saberNum, int bladeNum ) +{ + vec3_t mp1, mp2, md1, md2, baseOld, baseNew, baseDiff, endOld, endNew, bladePointOld, bladePointNew; + float tipDmgMod = 1.0f; + float baseDamage; + int baseDFlags = 0; + qboolean hit_wall = qfalse; + qboolean brokenParry = qfalse; + + for ( int ven = 0; ven < MAX_SABER_VICTIMS; ven++ ) + { + victimEntityNum[ven] = ENTITYNUM_NONE; + } + memset( totalDmg, 0, sizeof( totalDmg) ); + memset( dmgDir, 0, sizeof( dmgDir ) ); + memset( dmgSpot, 0, sizeof( dmgSpot ) ); + memset( dmgFraction, 0, sizeof( dmgFraction ) ); + memset( hitLoc, HL_NONE, sizeof( hitLoc ) ); + memset( hitDismemberLoc, HL_NONE, sizeof( hitDismemberLoc ) ); + memset( hitDismember, qfalse, sizeof( hitDismember ) ); + numVictims = 0; + VectorClear(saberHitLocation); + VectorClear(saberHitNormal); + saberHitFraction = 1.0; // Closest saber hit. The saber can do no damage past this point. + saberHitEntity = ENTITYNUM_NONE; + sabersCrossed = -1; + + if ( !ent->client ) + { + return; + } + + if ( !ent->s.number ) + {//player never uses these + ent->client->ps.saberEventFlags &= ~SEF_EVENTS; + } + + if ( ent->client->ps.saber[saberNum].blade[bladeNum].length <= 1 )//cen get down to 1 when in a wall + {//saber is not on + return; + } + + if ( VectorCompare( ent->client->renderInfo.muzzlePointOld, vec3_origin ) || VectorCompare( ent->client->renderInfo.muzzleDirOld, vec3_origin ) ) + { + //just started up the saber? + return; + } + + int saberContents = gi.pointcontents( ent->client->renderInfo.muzzlePoint, ent->client->ps.saberEntityNum ); + if ( (saberContents&CONTENTS_WATER)|| + (saberContents&CONTENTS_SLIME)|| + (saberContents&CONTENTS_LAVA) ) + {//um... turn off? Or just set length to 1? + //FIXME: short-out effect/sound? + ent->client->ps.saber[saberNum].blade[bladeNum].active = qfalse; + return; + } + else if (!g_saberNoEffects && gi.WE_IsOutside(ent->client->renderInfo.muzzlePoint)) + { + float chanceOfFizz = gi.WE_GetChanceOfSaberFizz(); + if (chanceOfFizz>0 && Q_flrand(0.0f, 1.0f)client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, ent->client->ps.saber[saberNum].blade[bladeNum].length*Q_flrand(0, 1), ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, end ); + G_PlayEffect( "saber/fizz", end ); +// if(ent->client && ent->client->ps.clientNum == 0) +// FF_Play(fffx_Laser1); + } + } + + //FIXMEFIXMEFIXME: When in force speed (esp. lvl 3), need to interpolate this because + // we animate so much faster that the arc is pretty much flat... + + int entPowerLevel = 0; + if ( ent->client->NPC_class == CLASS_SABER_DROID ) + { + entPowerLevel = SaberDroid_PowerLevelForSaberAnim( ent ); + } + else if ( !ent->s.number && (ent->client->ps.forcePowersActive&(1<client->ps, saberNum ); + } + + if ( entPowerLevel ) + { + if ( ent->client->ps.forceRageRecoveryTime > level.time ) + { + entPowerLevel = FORCE_LEVEL_1; + } + else if ( ent->client->ps.forcePowersActive & (1 << FP_RAGE) ) + { + entPowerLevel += ent->client->ps.forcePowerLevel[FP_RAGE]; + } + } + + if ( ent->client->ps.saberInFlight ) + {//flying sabers are much more deadly + //unless you're dead + if ( ent->health <= 0 && g_saberRealisticCombat->integer < 2 ) + {//so enemies don't keep trying to block it + //FIXME: still do damage, just not to humanoid clients who should try to avoid it + //baseDamage = 0.0f; + return; + } + //or unless returning + else if ( ent->client->ps.saberEntityState == SES_RETURNING + && ent->client->ps.saber[0].returnDamage == qfalse )//type != SABER_STAR ) + {//special case, since we're returning, chances are if we hit something + //it's going to be butt-first. So do less damage. + baseDamage = 0.1f; + } + else + { + if ( !ent->s.number ) + {//cheat for player + baseDamage = 10.0f; + } + else + { + baseDamage = 2.5f; + } + } + //Use old to current since can't predict it + VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld, mp1 ); + VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld, md1 ); + VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, mp2 ); + VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, md2 ); + } + else + { + if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) + {//no effects, no damage + return; + } + else if ( G_InCinematicSaberAnim( ent ) ) + { + baseDFlags = DAMAGE_NO_KILL; + baseDamage = 0.1f; + } + else if ( ent->client->ps.saberMove == LS_READY + && !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) ) + {//just do effects + if ( g_saberRealisticCombat->integer < 2 ) + {//don't kill with this hit + baseDFlags = DAMAGE_NO_KILL; + } + baseDamage = 0; + } + else if ( ent->client->ps.saberLockTime > level.time ) + {//just do effects + baseDamage = 0; + } + else if ( ent->client->ps.saberBlocked > BLOCKED_NONE + || ( !PM_SaberInAttack( ent->client->ps.saberMove ) + && !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) + && !PM_SaberInTransitionAny( ent->client->ps.saberMove ) + ) + ) + {//don't do damage if parrying/reflecting/bouncing/deflecting or not actually attacking or in a transition to/from/between attacks + baseDamage = 0; + } + else + {//okay, in a saberMove that does damage + //make sure we're in the right anim + if ( !PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) + && !PM_InAnimForSaberMove( ent->client->ps.torsoAnim, ent->client->ps.saberMove ) ) + {//forced into some other animation somehow, like a pain or death? + baseDamage = 0; + } + else if ( ent->client->ps.weaponstate == WEAPON_FIRING && ent->client->ps.saberBlocked == BLOCKED_NONE && + ( PM_SaberInAttack(ent->client->ps.saberMove) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || PM_SpinningSaberAnim(ent->client->ps.torsoAnim) || entPowerLevel > FORCE_LEVEL_2 ) )// || ent->client->ps.saberAnimLevel == SS_STAFF ) ) + {//normal attack swing swinging/spinning (or if using strong set), do normal damage //FIXME: or if using staff? + //FIXME: more damage for higher attack power levels? + // More damage based on length/color of saber? + //FIXME: Desann does double damage? + if ( g_saberRealisticCombat->integer ) + { + switch ( entPowerLevel ) + { + default: + case FORCE_LEVEL_3: + baseDamage = 10.0f; + break; + case FORCE_LEVEL_2: + baseDamage = 5.0f; + break; + case FORCE_LEVEL_0: + case FORCE_LEVEL_1: + baseDamage = 2.5f; + break; + } + } + else + { + if ( g_spskill->integer > 0 + && ent->s.number < MAX_CLIENTS + && ( ent->client->ps.torsoAnim == BOTH_ROLL_STAB + || ent->client->ps.torsoAnim == BOTH_SPINATTACK6 + || ent->client->ps.torsoAnim == BOTH_SPINATTACK7 + || ent->client->ps.torsoAnim == BOTH_LUNGE2_B__T_ ) ) + {//*sigh*, these anim do less damage since they're so easy to do + baseDamage = 2.5f; + } + else + { + baseDamage = 2.5f * (float)entPowerLevel; + } + } + } + else + {//saber is transitioning, defending or idle, don't do as much damage + //FIXME: strong attacks and returns should do damage and be unblockable + if ( g_timescale->value < 1.0 ) + {//in slow mo or force speed, we need to do damage during the transitions + if ( g_saberRealisticCombat->integer ) + { + switch ( entPowerLevel ) + { + case FORCE_LEVEL_3: + baseDamage = 10.0f; + break; + case FORCE_LEVEL_2: + baseDamage = 5.0f; + break; + default: + case FORCE_LEVEL_1: + baseDamage = 2.5f; + break; + } + } + else + { + baseDamage = 2.5f * (float)entPowerLevel; + } + } + else// if ( !ent->s.number ) + {//I have to do *some* damage in transitions or else you feel like a total gimp + baseDamage = 0.1f; + } + /* + else + { + baseDamage = 0;//was 1.0f;//was 0.25 + } + */ + } + } + + //Use current to next since can predict it + //FIXME: if they're closer than the saber blade start, we don't want the + // arm to pass through them without any damage... so check the radius + // and push them away (do pain & knockback) + //FIXME: if going into/coming from a parry/reflection or going into a deflection, don't use old mp & dir? Otherwise, deflections will cut through? + //VectorCopy( ent->client->renderInfo.muzzlePoint, mp1 ); + //VectorCopy( ent->client->renderInfo.muzzleDir, md1 ); + //VectorCopy( ent->client->renderInfo.muzzlePointNext, mp2 ); + //VectorCopy( ent->client->renderInfo.muzzleDirNext, md2 ); + //prediction was causing gaps in swing (G2 problem) so *don't* predict + VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePointOld, mp1 ); + VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDirOld, md1 ); + VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, mp2 ); + VectorCopy( ent->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, md2 ); + + //NOTE: this is a test, may not be necc, as I can still swing right through someone without hitting them, somehow... + //see if anyone is so close that they're within the dist from my origin to the start of the saber + if ( ent->health > 0 && !ent->client->ps.saberLockTime && saberNum == 0 && bladeNum == 0 + && !G_InCinematicSaberAnim( ent ) ) + {//only do once - for first blade + trace_t trace; + gi.trace( &trace, ent->currentOrigin, vec3_origin, vec3_origin, mp1, ent->s.number, (MASK_SHOT&~(CONTENTS_CORPSE|CONTENTS_ITEM)) ); + if ( trace.entityNum < ENTITYNUM_WORLD && (trace.entityNum > 0||ent->client->NPC_class == CLASS_DESANN) )//NPCs don't push player away, unless it's Desann + {//a valid ent + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( traceEnt + && traceEnt->client + && traceEnt->client->NPC_class != CLASS_RANCOR + && traceEnt->client->NPC_class != CLASS_ATST + && traceEnt->client->NPC_class != CLASS_WAMPA + && traceEnt->client->NPC_class != CLASS_SAND_CREATURE + && traceEnt->health > 0 + && traceEnt->client->playerTeam != ent->client->playerTeam + && !PM_SuperBreakLoseAnim( traceEnt->client->ps.legsAnim ) + && !PM_SuperBreakLoseAnim( traceEnt->client->ps.torsoAnim ) + && !PM_SuperBreakWinAnim( traceEnt->client->ps.legsAnim ) + && !PM_SuperBreakWinAnim( traceEnt->client->ps.torsoAnim ) + && !PM_InKnockDown( &traceEnt->client->ps ) + && !PM_LockedAnim( traceEnt->client->ps.legsAnim ) + && !PM_LockedAnim( traceEnt->client->ps.torsoAnim ) + && !G_InCinematicSaberAnim( traceEnt )) + {//enemy client, push them away + if ( !traceEnt->client->ps.saberLockTime + && !traceEnt->message + && !(traceEnt->flags&FL_NO_KNOCKBACK) + && (!traceEnt->NPC||traceEnt->NPC->jumpState!=JS_JUMPING) ) + {//don't push people in saberlock or with security keys or who are in BS_JUMP + vec3_t hitDir; + VectorSubtract( trace.endpos, ent->currentOrigin, hitDir ); + float totalDist = Distance( mp1, ent->currentOrigin ); + float knockback = (totalDist-VectorNormalize( hitDir ))/totalDist * 200.0f; + hitDir[2] = 0; + //FIXME: do we need to call G_Throw? Seems unfair to put actual knockback on them, stops the attack + //G_Throw( traceEnt, hitDir, knockback ); + VectorMA( traceEnt->client->ps.velocity, knockback, hitDir, traceEnt->client->ps.velocity ); + traceEnt->client->ps.pm_time = 200; + traceEnt->client->ps.pm_flags |= PMF_TIME_NOFRICTION; +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + gi.Printf( "%s pushing away %s at %s\n", ent->NPC_type, traceEnt->NPC_type, vtos( traceEnt->client->ps.velocity ) ); + } +#endif + } + } + } + } + } + + //the thicker the blade, the more damage... the thinner, the less damage + baseDamage *= ent->client->ps.saber[saberNum].blade[bladeNum].radius/SABER_RADIUS_STANDARD; + + if ( g_saberRealisticCombat->integer > 1 ) + {//always do damage, and lots of it + if ( g_saberRealisticCombat->integer > 2 ) + {//always do damage, and lots of it + baseDamage = 25.0f; + } + else if ( baseDamage > 0.1f ) + {//only do super damage if we would have done damage according to normal rules + baseDamage = 25.0f; + } + } + else if ( ((!ent->s.number&&ent->client->ps.forcePowersActive&(1<client->ps.forcePowersActive&(1<value < 1.0f ) + { + baseDamage *= (1.0f-g_timescale->value); + } + if ( baseDamage > 0.1f ) + { + if ( (ent->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_RAGE] * 5.0f; + } + else if ( ent->client->ps.forceRageRecoveryTime ) + {//halve it if recovering + baseDamage *= 0.5f; + } + } + // Get the old state of the blade + VectorCopy( mp1, baseOld ); + VectorMA( baseOld, ent->client->ps.saber[saberNum].blade[bladeNum].length, md1, endOld ); + // Get the future state of the blade + VectorCopy( mp2, baseNew ); + VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, md2, endNew ); + + sabersCrossed = -1; + if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) ) + { + hit_wall = WP_SaberDamageForTrace( ent->s.number, mp2, endNew, baseDamage*4, md2, + qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse, + saberNum, bladeNum ); + } + else + { + float aveLength, step = 8, stepsize = 8; + vec3_t ma1, ma2, md2ang, curBase1, curBase2; + int xx; + //do the trace at the base first + hit_wall = WP_SaberDamageForTrace( ent->s.number, baseOld, baseNew, baseDamage, md2, + qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue, + saberNum, bladeNum ); + + //if hit a saber, shorten rest of traces to match + if ( saberHitFraction < 1.0 ) + { + //adjust muzzleDir... + vec3_t ma1, ma2; + vectoangles( md1, ma1 ); + vectoangles( md2, ma2 ); + for ( xx = 0; xx < 3; xx++ ) + { + md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], saberHitFraction ); + } + AngleVectors( md2ang, md2, NULL, NULL ); + //shorten the base pos + VectorSubtract( mp2, mp1, baseDiff ); + VectorMA( mp1, saberHitFraction, baseDiff, baseNew ); + VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, md2, endNew ); + } + + //If the angle diff in the blade is high, need to do it in chunks of 33 to avoid flattening of the arc + float dirInc, curDirFrac; + if ( PM_SaberInAttack( ent->client->ps.saberMove ) + || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) + || PM_SpinningSaberAnim( ent->client->ps.torsoAnim ) + || PM_InSpecialJump( ent->client->ps.torsoAnim ) + || (g_timescale->value<1.0f&&PM_SaberInTransitionAny( ent->client->ps.saberMove )) ) + { + curDirFrac = DotProduct( md1, md2 ); + } + else + { + curDirFrac = 1.0f; + } + //NOTE: if saber spun at least 180 degrees since last damage trace, this is not reliable...! + if ( fabs(curDirFrac) < 1.0f - MAX_SABER_SWING_INC ) + {//the saber blade spun more than 33 degrees since the last damage trace + curDirFrac = dirInc = 1.0f/((1.0f - curDirFrac)/MAX_SABER_SWING_INC); + } + else + { + curDirFrac = 1.0f; + dirInc = 0.0f; + } + qboolean hit_saber = qfalse; + + vectoangles( md1, ma1 ); + vectoangles( md2, ma2 ); + + vec3_t curMD1, curMD2;//, mdDiff, dirDiff; + //VectorSubtract( md2, md1, mdDiff ); + VectorCopy( md1, curMD2 ); + VectorCopy( baseOld, curBase2 ); + + while ( 1 ) + { + VectorCopy( curMD2, curMD1 ); + VectorCopy( curBase2, curBase1 ); + if ( curDirFrac >= 1.0f ) + { + VectorCopy( md2, curMD2 ); + VectorCopy( baseNew, curBase2 ); + } + else + { + for ( xx = 0; xx < 3; xx++ ) + { + md2ang[xx] = LerpAngle( ma1[xx], ma2[xx], curDirFrac ); + } + AngleVectors( md2ang, curMD2, NULL, NULL ); + //VectorMA( md1, curDirFrac, mdDiff, curMD2 ); + VectorSubtract( baseNew, baseOld, baseDiff ); + VectorMA( baseOld, curDirFrac, baseDiff, curBase2 ); + } + // Move up the blade in intervals of stepsize + for ( step = stepsize; step < ent->client->ps.saber[saberNum].blade[bladeNum].length && step < ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld; step+=12 ) + { + VectorMA( curBase1, step, curMD1, bladePointOld ); + VectorMA( curBase2, step, curMD2, bladePointNew ); + if ( WP_SaberDamageForTrace( ent->s.number, bladePointOld, bladePointNew, baseDamage, curMD2, + qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qtrue, + saberNum, bladeNum ) ) + { + hit_wall = qtrue; + } + + //if hit a saber, shorten rest of traces to match + if ( saberHitFraction < 1.0 ) + { + //adjust muzzle endpoint + VectorSubtract( mp2, mp1, baseDiff ); + VectorMA( mp1, saberHitFraction, baseDiff, baseNew ); + VectorMA( baseNew, ent->client->ps.saber[saberNum].blade[bladeNum].length, curMD2, endNew ); + //adjust muzzleDir... + vec3_t curMA1, curMA2; + vectoangles( curMD1, curMA1 ); + vectoangles( curMD2, curMA2 ); + for ( xx = 0; xx < 3; xx++ ) + { + md2ang[xx] = LerpAngle( curMA1[xx], curMA2[xx], saberHitFraction ); + } + AngleVectors( md2ang, curMD2, NULL, NULL ); + /* + VectorSubtract( curMD2, curMD1, dirDiff ); + VectorMA( curMD1, saberHitFraction, dirDiff, curMD2 ); + */ + hit_saber = qtrue; + } + if (hit_wall) + { + break; + } + } + if ( hit_wall || hit_saber ) + { + break; + } + if ( curDirFrac >= 1.0f ) + { + break; + } + else + { + curDirFrac += dirInc; + if ( curDirFrac >= 1.0f ) + { + curDirFrac = 1.0f; + } + } + } + + //do the trace at the end last + //Special check- adjust for length of blade not being a multiple of 12 + aveLength = (ent->client->ps.saber[saberNum].blade[bladeNum].lengthOld + ent->client->ps.saber[saberNum].blade[bladeNum].length)/2; + if ( step > aveLength ) + {//less dmg if the last interval was not stepsize + tipDmgMod = (stepsize-(step-aveLength))/stepsize; + } + //NOTE: since this is the tip, we do not extrapolate the extra 16 + if ( WP_SaberDamageForTrace( ent->s.number, endOld, endNew, tipDmgMod*baseDamage, md2, + qfalse, entPowerLevel, ent->client->ps.saber[saberNum].type, qfalse, + saberNum, bladeNum ) ) + { + hit_wall = qtrue; + } + } + + if ( (saberHitFraction < 1.0f||(sabersCrossed>=0&&sabersCrossed<=32.0f)) && (ent->client->ps.weaponstate == WEAPON_FIRING || ent->client->ps.saberInFlight || G_InCinematicSaberAnim( ent ) ) ) + {// The saber (in-hand) hit another saber, mano. + qboolean inFlightSaberBlocked = qfalse; + qboolean collisionResolved = qfalse; + qboolean deflected = qfalse; + + gentity_t *hitEnt = &g_entities[saberHitEntity]; + gentity_t *hitOwner = NULL; + int hitOwnerPowerLevel = FORCE_LEVEL_0; + + if ( hitEnt ) + { + hitOwner = hitEnt->owner; + } + if ( hitOwner && hitOwner->client ) + { + hitOwnerPowerLevel = PM_PowerLevelForSaberAnim( &hitOwner->client->ps ); + /* + if ( entPowerLevel >= FORCE_LEVEL_3 + && PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) ) + {//a special "unblockable" attack + if ( hitOwner->client->NPC_class == CLASS_ALORA + || hitOwner->client->NPC_class == CLASS_SHADOWTROOPER + || (hitOwner->NPC&&(hitOwner->NPC->aiFlags&NPCAI_BOSS_CHARACTER)) ) + {//these masters can even block unblockables (stops cheap kills) + entPowerLevel = FORCE_LEVEL_2; + } + } + */ + } + + //FIXME: check for certain anims, facing, etc, to make them lock into a sabers-locked pose + //SEF_LOCKED + + if ( ent->client->ps.saberInFlight && saberNum == 0 && + ent->client->ps.saber[saberNum].blade[bladeNum].active && + ent->client->ps.saberEntityNum != ENTITYNUM_NONE && + ent->client->ps.saberEntityState != SES_RETURNING ) + {//saber was blocked, return it + inFlightSaberBlocked = qtrue; + } + + //FIXME: based on strength, position and angle of attack & defense, decide if: + // defender and attacker lock sabers + // *defender's parry should hold and attack bounces (or deflects, based on angle of sabers) + // *defender's parry is somewhat broken and both bounce (or deflect) + // *defender's parry is broken and they bounce while attacker's attack deflects or carries through (especially if they're dead) + // defender is knocked down and attack goes through + + //Check deflections and broken parries + if ( hitOwner && hitOwner->health > 0 && ent->health > 0 //both are alive + && !inFlightSaberBlocked && hitOwner->client && !hitOwner->client->ps.saberInFlight && !ent->client->ps.saberInFlight//both have sabers in-hand + && ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN + && ent->client->ps.saberLockTime < level.time + && hitOwner->client->ps.saberLockTime < level.time ) + {//2 in-hand sabers hit + //FIXME: defender should not parry or block at all if not in a saber anim... like, if in a roll or knockdown... + if ( baseDamage ) + {//there is damage involved, not just effects + qboolean entAttacking = qfalse; + qboolean hitOwnerAttacking = qfalse; + qboolean entDefending = qfalse; + qboolean hitOwnerDefending = qfalse; + qboolean forceLock = qfalse; + + if ( (ent->client->NPC_class == CLASS_KYLE && (ent->spawnflags&1) && hitOwner->s.number < MAX_CLIENTS ) + || (hitOwner->client->NPC_class == CLASS_KYLE && (hitOwner->spawnflags&1) && ent->s.number < MAX_CLIENTS ) ) + {//Player vs. Kyle Boss == lots of saberlocks + if ( !Q_irand( 0, 2 ) ) + { + forceLock = qtrue; + } + } + + if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) ) + { + entAttacking = qtrue; + } + else if ( entPowerLevel > FORCE_LEVEL_2 ) + {//stronger styles count as attacking even if in a transition + if ( PM_SaberInTransitionAny( ent->client->ps.saberMove ) ) + { + entAttacking = qtrue; + } + } + if ( PM_SaberInParry( ent->client->ps.saberMove ) + || ent->client->ps.saberMove == LS_READY ) + { + entDefending = qtrue; + } + + if ( ent->client->ps.torsoAnim == BOTH_A1_SPECIAL + || ent->client->ps.torsoAnim == BOTH_A2_SPECIAL + || ent->client->ps.torsoAnim == BOTH_A3_SPECIAL ) + {//parry/block/break-parry bonus for single-style kata moves + entPowerLevel++; + } + if ( entAttacking ) + {//add twoHanded bonus and breakParryBonus to entPowerLevel here + //This makes staff too powerful + if ( ent->client->ps.saber[saberNum].twoHanded ) + { + entPowerLevel++; + } + //FIXME: what if dualSabers && both sabers are hitting at same time? + entPowerLevel += ent->client->ps.saber[saberNum].breakParryBonus; + } + else if ( entDefending ) + {//add twoHanded bonus and dualSaber bonus and parryBonus to entPowerLevel here + if ( ent->client->ps.saber[saberNum].twoHanded + || (ent->client->ps.dualSabers && ent->client->ps.saber[1].Active()) ) + { + entPowerLevel++; + } + //FIXME: what about second saber if dualSabers? + entPowerLevel += ent->client->ps.saber[saberNum].parryBonus; + } + + if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) ) + { + hitOwnerAttacking = qtrue; + } + else if ( hitOwnerPowerLevel > FORCE_LEVEL_2 ) + {//stronger styles count as attacking even if in a transition + if ( PM_SaberInTransitionAny( hitOwner->client->ps.saberMove ) ) + { + hitOwnerAttacking = qtrue; + } + } + if ( PM_SaberInParry( hitOwner->client->ps.saberMove ) + || hitOwner->client->ps.saberMove == LS_READY ) + { + hitOwnerDefending = qtrue; + } + + if ( hitOwner->client->ps.torsoAnim == BOTH_A1_SPECIAL + || hitOwner->client->ps.torsoAnim == BOTH_A2_SPECIAL + || hitOwner->client->ps.torsoAnim == BOTH_A3_SPECIAL ) + {//parry/block/break-parry bonus for single-style kata moves + hitOwnerPowerLevel++; + } + if ( hitOwnerAttacking ) + {//add twoHanded bonus and breakParryBonus to entPowerLevel here + if ( hitOwner->client->ps.saber[0].twoHanded ) + { + hitOwnerPowerLevel++; + } + hitOwnerPowerLevel += hitOwner->client->ps.saber[0].breakParryBonus; + if ( hitOwner->client->ps.dualSabers && Q_irand( 0, 1 ) ) + {//FIXME: assumes both sabers are hitting at same time...? + hitOwnerPowerLevel += 1 + hitOwner->client->ps.saber[1].breakParryBonus; + } + } + else if ( hitOwnerDefending ) + {//add twoHanded bonus and dualSaber bonus and parryBonus to entPowerLevel here + if ( hitOwner->client->ps.saber[0].twoHanded || (hitOwner->client->ps.dualSabers && hitOwner->client->ps.saber[1].Active()) ) + { + hitOwnerPowerLevel++; + } + hitOwnerPowerLevel += hitOwner->client->ps.saber[0].parryBonus; + if ( hitOwner->client->ps.dualSabers && Q_irand( 0, 1 ) ) + {//FIXME: assumes both sabers are defending at same time...? + hitOwnerPowerLevel += 1 + hitOwner->client->ps.saber[1].parryBonus; + } + } + + if ( PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim ) + || PM_SuperBreakWinAnim( ent->client->ps.torsoAnim ) + || PM_SuperBreakLoseAnim( hitOwner->client->ps.torsoAnim ) + || PM_SuperBreakWinAnim( hitOwner->client->ps.torsoAnim ) ) + {//don't mess with this + collisionResolved = qtrue; + } + else if ( entAttacking + && hitOwnerAttacking + && !Q_irand( 0, g_saberLockRandomNess->integer ) + && ( g_debugSaberLock->integer || forceLock + || entPowerLevel == hitOwnerPowerLevel + || (entPowerLevel > FORCE_LEVEL_2 && hitOwnerPowerLevel > FORCE_LEVEL_2 ) + || (entPowerLevel < FORCE_LEVEL_3 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && Q_irand( 0, 3 )) + || (entPowerLevel < FORCE_LEVEL_2 && hitOwnerPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && Q_irand( 0, 2 )) + || (hitOwnerPowerLevel < FORCE_LEVEL_3 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_2 && !Q_irand( 0, 1 )) + || (hitOwnerPowerLevel < FORCE_LEVEL_2 && entPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] > FORCE_LEVEL_1 && !Q_irand( 0, 1 ))) + && WP_SabersCheckLock( ent, hitOwner ) ) + { + collisionResolved = qtrue; + } + else if ( hitOwnerAttacking + && entDefending + && !Q_irand( 0, g_saberLockRandomNess->integer*3 ) + && (g_debugSaberLock->integer || forceLock || + ((ent->client->ps.saberMove != LS_READY || (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) ) + && ((hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 )|| + (hitOwnerPowerLevel < FORCE_LEVEL_2 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 )|| + (hitOwnerPowerLevel < FORCE_LEVEL_3 && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_0 && !Q_irand( 0, (hitOwnerPowerLevel-ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE]+1)*2 ))) )) + && WP_SabersCheckLock( hitOwner, ent ) ) + { + collisionResolved = qtrue; + } + else if ( entAttacking && hitOwnerDefending ) + {//I'm attacking hit, they're parrying + qboolean activeDefense = (hitOwner->s.number||g_saberAutoBlocking->integer||hitOwner->client->ps.saberBlockingTime > level.time); + if ( !Q_irand( 0, g_saberLockRandomNess->integer*3 ) + && activeDefense + && (g_debugSaberLock->integer || forceLock || + ((hitOwner->client->ps.saberMove != LS_READY || (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]) < Q_irand( -6, 0 ) ) + && ( ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 ) + || ( entPowerLevel < FORCE_LEVEL_2 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_1 ) + || ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_0 && !Q_irand( 0, (entPowerLevel-hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]+1)*2 )) ) )) + && WP_SabersCheckLock( ent, hitOwner ) ) + { + collisionResolved = qtrue; + } + else if ( saberHitFraction < 1.0f ) + {//an actual collision + if ( entPowerLevel < FORCE_LEVEL_3 && activeDefense ) + {//strong attacks cannot be deflected + //based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back + deflected = WP_GetSaberDeflectionAngle( ent, hitOwner ); + //just so Jedi knows that he was blocked + ent->client->ps.saberEventFlags |= SEF_BLOCKED; + } + //base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE + if ( entPowerLevel < FORCE_LEVEL_3 + //&& ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] < FORCE_LEVEL_3//if you have high saber offense, you cannot have your attack knocked away, regardless of what style you're using? + //&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5 + && activeDefense + && (hitOwnerPowerLevel > FORCE_LEVEL_2||(hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&Q_irand(0,hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE]))) ) + {//knockaways can make fast-attacker go into a broken parry anim if the ent is using fast or med (but not Tavion) + //make me parry + WP_SaberParry( hitOwner, ent ); + //turn the parry into a knockaway + hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked ); + //make them go into a broken parry + ent->client->ps.saberBounceMove = PM_BrokenParryForAttack( ent->client->ps.saberMove ); + ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + if ( saberNum == 0 ) + {//FIXME: can only lose right-hand saber for now + if ( ent->client->ps.saber[saberNum].disarmable + && ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2 + //&& (ent->s.number||g_saberRealisticCombat->integer) + && Q_irand( 0, hitOwner->client->ps.SaberDisarmBonus() ) > 0 + && (hitOwner->s.number || g_saberAutoBlocking->integer || !Q_irand( 0, 2 )) )//if player defending and autoblocking is on, this is less likely to happen, so don't do the random check + {//knocked the saber right out of his hand! (never happens to player) + //Get a good velocity to send the saber in based on my parry move + vec3_t throwDir; + if ( !PM_VelocityForBlockedMove( &hitOwner->client->ps, throwDir ) ) + { + PM_VelocityForSaberMove( &ent->client->ps, throwDir ); + } + WP_SaberLose( ent, throwDir ); + } + } + //just so Jedi knows that he was blocked + ent->client->ps.saberEventFlags |= SEF_BLOCKED; +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + gi.Printf( S_COLOR_RED"%s knockaway %s's attack, new move = %s, anim = %s\n", hitOwner->NPC_type, ent->NPC_type, saberMoveData[ent->client->ps.saberBounceMove].name, animTable[saberMoveData[ent->client->ps.saberBounceMove].animToUse].name ); + } +#endif + } + else if ( !activeDefense//they're not defending + || (entPowerLevel > FORCE_LEVEL_2 //I hit hard + && hitOwnerPowerLevel < entPowerLevel)//they are defending, but their defense strength is lower than my attack... + || (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &ent->client->ps, saberNum ) - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE]/*PM_PowerLevelForSaberAnim( &hitOwner->client->ps )*/ ) > 0 ) ) + {//broke their parry altogether + if ( entPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) ) + {//chance of continuing with the attack (not bouncing back) + ent->client->ps.saberEventFlags &= ~SEF_BLOCKED; + ent->client->ps.saberBounceMove = LS_NONE; + brokenParry = qtrue; + } + //do some time-consuming saber-knocked-aside broken parry anim + hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + hitOwner->client->ps.saberBounceMove = LS_NONE; + //FIXME: for now, you always disarm the right-hand saber + if ( hitOwner->client->ps.saber[0].disarmable + && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_2 + //&& (ent->s.number||g_saberRealisticCombat->integer) + && Q_irand( 0, 2-ent->client->ps.SaberDisarmBonus() ) <= 0 ) + {//knocked the saber right out of his hand! + //get the right velocity for my attack direction + vec3_t throwDir; + PM_VelocityForSaberMove( &ent->client->ps, throwDir ); + WP_SaberLose( hitOwner, throwDir ); + if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,3) ) + || ( ent->client->ps.saberAnimLevel==SS_DESANN&&!Q_irand(0,1) ) ) + {// a strong attack + if ( WP_BrokenParryKnockDown( hitOwner ) ) + { + hitOwner->client->ps.saberBlocked = BLOCKED_NONE; + hitOwner->client->ps.saberBounceMove = LS_NONE; + } + } + } + else + { + if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,5) ) + || ( ent->client->ps.saberAnimLevel==SS_DESANN&&!Q_irand(0,3) ) ) + {// a strong attack + if ( WP_BrokenParryKnockDown( hitOwner ) ) + { + hitOwner->client->ps.saberBlocked = BLOCKED_NONE; + hitOwner->client->ps.saberBounceMove = LS_NONE; + } + } + } +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + if ( ent->client->ps.saberEventFlags&SEF_BLOCKED ) + { + gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", hitOwner->targetname ); + } + else + { + gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", hitOwner->targetname ); + } + } +#endif + } + else + {//just a parry, possibly the hitOwner can knockaway the ent + WP_SaberParry( hitOwner, ent ); + if ( PM_SaberInBounce( ent->client->ps.saberMove ) //FIXME: saberMove not set until pmove! + && activeDefense + && hitOwner->client->ps.saberAnimLevel != SS_FAST //&& hitOwner->client->ps.saberAnimLevel != FORCE_LEVEL_5 + && hitOwner->client->ps.forcePowerLevel[FP_SABER_DEFENSE] > FORCE_LEVEL_2 ) + {//attacker bounced off, and defender has ability to do knockaways, so do one unless we're using fast attacks + //turn the parry into a knockaway + hitOwner->client->ps.saberBounceMove = PM_KnockawayForParry( hitOwner->client->ps.saberBlocked ); + } + else if ( (ent->client->ps.saberAnimLevel == SS_STRONG && !Q_irand(0,6) ) + || ( ent->client->ps.saberAnimLevel==SS_DESANN && !Q_irand(0,3) ) ) + {// a strong attack can sometimes do a knockdown + //HMM... maybe only if they're moving backwards? + if ( WP_BrokenParryKnockDown( hitOwner ) ) + { + hitOwner->client->ps.saberBlocked = BLOCKED_NONE; + hitOwner->client->ps.saberBounceMove = LS_NONE; + } + } + } + collisionResolved = qtrue; + } + } + /* + else if ( entDefending && hitOwnerAttacking ) + {//I'm parrying, they're attacking + if ( hitOwnerPowerLevel < FORCE_LEVEL_3 ) + {//strong attacks cannot be deflected + //based on angle of attack & angle of defensive saber, see if I should deflect off in another dir rather than bounce back + deflected = WP_GetSaberDeflectionAngle( hitOwner, ent ); + //just so Jedi knows that he was blocked + hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED; + } + //FIXME: base parry breaks on animation (saber attack level), not FP_SABER_OFFENSE + if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || (!deflected && Q_irand( 0, PM_PowerLevelForSaberAnim( &hitOwner->client->ps ) - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) > 0 ) ) + {//broke my parry altogether + if ( hitOwnerPowerLevel > FORCE_LEVEL_2 || Q_irand( 0, hitOwner->client->ps.forcePowerLevel[FP_SABER_OFFENSE] - ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] ) ) + {//chance of continuing with the attack (not bouncing back) + hitOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED; + hitOwner->client->ps.saberBounceMove = LS_NONE; + } + //do some time-consuming saber-knocked-aside broken parry anim + ent->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + if ( hitOwner->client->ps.saberEventFlags&SEF_BLOCKED ) + { + gi.Printf( S_COLOR_RED"%s parry broken (bounce/deflect)!\n", ent->targetname ); + } + else + { + gi.Printf( S_COLOR_RED"%s parry broken (follow-through)!\n", ent->targetname ); + } + } +#endif + } + else + { + WP_SaberParry( ent, hitOwner ); + } + collisionResolved = qtrue; + } + */ + else + {//some other kind of in-hand saber collision + } + } + } + else + {//some kind of in-flight collision + } + + if ( saberHitFraction < 1.0f ) + { + if ( !collisionResolved && baseDamage ) + {//some other kind of in-hand saber collision + //handle my reaction + if ( !ent->client->ps.saberInFlight + && ent->client->ps.saberLockTime < level.time ) + {//my saber is in hand + if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + { + if ( PM_SaberInAttack( ent->client->ps.saberMove ) || PM_SaberInSpecialAttack( ent->client->ps.torsoAnim ) || + (entPowerLevel > FORCE_LEVEL_2&&!PM_SaberInIdle(ent->client->ps.saberMove)&&!PM_SaberInParry(ent->client->ps.saberMove)&&!PM_SaberInReflect(ent->client->ps.saberMove)) ) + {//in the middle of attacking + if ( entPowerLevel < FORCE_LEVEL_3 && hitOwner->health > 0 ) + {//don't deflect/bounce in strong attack or when enemy is dead + WP_GetSaberDeflectionAngle( ent, hitOwner ); + ent->client->ps.saberEventFlags |= SEF_BLOCKED; + //since it was blocked/deflected, take away any damage done + //FIXME: what if the damage was done before the parry? + WP_SaberClearDamageForEntNum( hitOwner->s.number ); + } + } + else + {//saber collided when not attacking, parry it + //since it was blocked/deflected, take away any damage done + //FIXME: what if the damage was done before the parry? + WP_SaberClearDamageForEntNum( hitOwner->s.number ); + /* + if ( ent->s.number || g_saberAutoBlocking->integer || ent->client->ps.saberBlockingTime > level.time ) + {//either an NPC or a player who has blocking + if ( !PM_SaberInTransitionAny( ent->client->ps.saberMove ) && !PM_SaberInBounce( ent->client->ps.saberMove ) ) + {//I'm not attacking, in transition or in a bounce, so play a parry + //just so Jedi knows that he parried something + WP_SaberBlockNonRandom( ent, saberHitLocation, qfalse ); + } + ent->client->ps.saberEventFlags |= SEF_PARRIED; + } + */ + } + } + else + { + //since it was blocked/deflected, take away any damage done + //FIXME: what if the damage was done before the parry? + WP_SaberClearDamageForEntNum( hitOwner->s.number ); + } + } + else + {//nothing happens to *me* when my inFlight saber hits something + } + //handle their reaction + if ( hitOwner + && hitOwner->health > 0 + && hitOwner->client + && !hitOwner->client->ps.saberInFlight + && hitOwner->client->ps.saberLockTime < level.time ) + {//their saber is in hand + if ( PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || + (hitOwner->client->ps.saberAnimLevel > SS_MEDIUM&&!PM_SaberInIdle(hitOwner->client->ps.saberMove)&&!PM_SaberInParry(hitOwner->client->ps.saberMove)&&!PM_SaberInReflect(hitOwner->client->ps.saberMove)) ) + {//in the middle of attacking + /* + if ( hitOwner->client->ps.saberAnimLevel < SS_STRONG ) + {//don't deflect/bounce in strong attack + WP_GetSaberDeflectionAngle( hitOwner, ent ); + hitOwner->client->ps.saberEventFlags |= SEF_BLOCKED; + } + */ + } + else + {//saber collided when not attacking, parry it + if ( !PM_SaberInBrokenParry( hitOwner->client->ps.saberMove ) ) + {//not currently in a broken parry + if ( !WP_SaberParry( hitOwner, ent ) ) + {//FIXME: hitOwner can't parry, do some time-consuming saber-knocked-aside broken parry anim? + //hitOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + } + } + } + } + else + {//nothing happens to *hitOwner* when their inFlight saber hits something + } + } + + //collision must have been handled by now + //Set the blocked attack bounce value in saberBlocked so we actually play our saberBounceMove anim + if ( ent->client->ps.saberEventFlags & SEF_BLOCKED ) + { + if ( ent->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + { + ent->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + } + } + /* + if ( hitOwner && hitOwner->client->ps.saberEventFlags & SEF_BLOCKED ) + { + hitOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + } + */ + } + + if ( saberHitFraction < 1.0f || collisionResolved ) + {//either actually hit or locked + if ( ent->client->ps.saberLockTime < level.time ) + { + if ( inFlightSaberBlocked ) + {//FIXME: never hear this sound + G_Sound( &g_entities[ent->client->ps.saberEntityNum], G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", Q_irand(1,3) ) ) ); + } + else + { + if ( deflected ) + { +#ifdef _IMMERSION + int index = Q_irand(1,3); + G_Sound( ent, G_SoundIndex( va("sound/weapons/saber/saberbounce%d.wav", index) ) ); + int ff = G_ForceIndex( va("fffx/weapons/saber/saberbounce%d", index), FF_CHANNEL_WEAPON ); + if ( !ent->s.saberInFlight ) + { + G_Force( ent, ff ); + } + if ( hitOwner && !hitOwner->s.saberInFlight ) + { + G_Force( hitOwner, ff ); + } +#else + G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberbounce%d.wav", Q_irand(1,3) ) ) ); +#endif // _IMMERSION + } + else + { +#ifdef _IMMERSION + int index = Q_irand(1, 9); + G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index) ) ); + int ff = G_ForceIndex( va("fffx/weapons/saber/saberblock%d", index), FF_CHANNEL_WEAPON ); + if ( !ent->s.saberInFlight ) + { + G_Force( ent, ff ); + } + if ( hitOwner && !hitOwner->s.saberInFlight ) + { + G_Force( hitOwner, ff ); + } +#else + G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) ); +#endif // _IMMERSION + } + } + if ( !g_saberNoEffects ) + { + G_PlayEffect( "saber/saber_block", saberHitLocation, saberHitNormal ); + if(ent->client && ent->client->ps.clientNum == 0) + FF_Play(fffx_Laser1); + } + } + // Set the little screen flash - only when an attack is blocked + g_saberFlashTime = level.time-50; + VectorCopy( saberHitLocation, g_saberFlashPos ); + } + + if ( saberHitFraction < 1.0f ) + { + if ( inFlightSaberBlocked ) + {//we threw a saber and it was blocked, do any effects, etc. + int knockAway = 5; + if ( hitEnt + && hitOwner + && hitOwner->client + && (PM_SaberInAttack( hitOwner->client->ps.saberMove ) || PM_SaberInSpecialAttack( hitOwner->client->ps.torsoAnim ) || PM_SpinningSaberAnim( hitOwner->client->ps.torsoAnim )) ) + {//if hit someone who was in an attack or spin anim, more likely to have in-flight saber knocked away + if ( hitOwnerPowerLevel > FORCE_LEVEL_2 ) + {//strong attacks almost always knock it aside! + knockAway = 1; + } + else + {//33% chance + knockAway = 2; + } + knockAway -= hitOwner->client->ps.SaberDisarmBonus(); + } + if ( Q_irand( 0, knockAway ) <= 0 || //random + ( hitOwner + && hitOwner->client + && hitOwner->NPC + && (hitOwner->NPC->aiFlags&NPCAI_BOSS_CHARACTER) + ) //or if blocked by a Boss character FIXME: or base on defense level? + )//FIXME: player should not auto-block a flying saber, let him override the parry with an attack to knock the saber from the air, rather than this random chance + {//knock it aside and turn it off + if ( !g_saberNoEffects ) + { + G_PlayEffect( "saber/saber_cut", saberHitLocation, saberHitNormal ); + if(ent->client && ent->client->ps.clientNum == 0) + FF_Play(fffx_Laser1); + } + if ( hitEnt ) + { + vec3_t newDir; + + VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir ); + VectorNormalize( newDir ); + G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir ); + } + Jedi_PlayDeflectSound( hitOwner ); + WP_SaberDrop( ent, &g_entities[ent->client->ps.saberEntityNum] ); + } + else + { + if ( !Q_irand( 0, 2 ) && hitEnt ) + { + vec3_t newDir; + VectorSubtract( g_entities[ent->client->ps.saberEntityNum].currentOrigin, hitEnt->currentOrigin, newDir ); + VectorNormalize( newDir ); + G_ReflectMissile( ent, &g_entities[ent->client->ps.saberEntityNum], newDir ); + } + WP_SaberReturn( ent, &g_entities[ent->client->ps.saberEntityNum] ); + } + } + } + } + + if ( ent->client->ps.saberLockTime > level.time + && ent->s.number < ent->client->ps.saberLockEnemy + && !Q_irand( 0, 3 ) ) + {//need to make some kind of effect + vec3_t hitNorm = {0,0,1}; + if ( WP_SabersIntersection( ent, &g_entities[ent->client->ps.saberLockEnemy], g_saberFlashPos ) ) + { + if ( Q_irand( 0, 10 ) ) + { + if ( !g_saberNoEffects ) + { + G_PlayEffect( "saber/saber_block", g_saberFlashPos, hitNorm ); + if(ent->client && ent->client->ps.clientNum == 0) + FF_Play(fffx_Laser1); + } + } + else + { + g_saberFlashTime = level.time-50; + if ( !g_saberNoEffects ) + { + G_PlayEffect( "saber/saber_cut", g_saberFlashPos, hitNorm ); + if(ent->client && ent->client->ps.clientNum == 0) + FF_Play(fffx_Laser1); + } + } +#ifdef _IMMERSION + int index = Q_irand(1, 9); + G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", index ) ) ); + int ff = G_ForceIndex( va("fffx/weapons/saber/saberblock%d", index), FF_CHANNEL_WEAPON ); + if ( !ent->s.saberInFlight ) + { + G_Force( ent, ff ); + } + if ( !g_entities[ent->client->ps.saberLockEnemy].s.saberInFlight ) + { + G_Force( &g_entities[ent->client->ps.saberLockEnemy], ff ); + } +#else + G_Sound( ent, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) ); +#endif // _IMMERSION + } + } + + if ( WP_SaberApplyDamage( ent, baseDamage, baseDFlags, brokenParry, ent->client->ps.saber[saberNum].type, (qboolean)(saberNum==0&&ent->client->ps.saberInFlight) ) ) + {//actually did damage to something +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + gi.Printf( "base damage was %4.2f\n", baseDamage ); + } +#endif + WP_SaberHitSound( ent, saberNum ); + } + + if ( hit_wall ) + { + //just so Jedi knows that he hit a wall + ent->client->ps.saberEventFlags |= SEF_HITWALL; + if ( ent->s.number == 0 ) + { + AddSoundEvent( ent, ent->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue );//FIXME: is this impact on ground or not? + AddSightEvent( ent, ent->currentOrigin, 256, AEL_DISCOVERED, 50 ); + } + } +} + +void WP_SabersDamageTrace( gentity_t *ent, qboolean noEffects ) +{ + if ( !ent->client ) + { + return; + } + if ( PM_SuperBreakLoseAnim( ent->client->ps.torsoAnim ) ) + { + return; + } + // Saber 1. + g_saberNoEffects = noEffects; + for ( int i = 0; i < ent->client->ps.saber[0].numBlades; i++ ) + { + // If the Blade is not active and the length is 0, don't trace it, try the next blade... + if ( !ent->client->ps.saber[0].blade[i].active && ent->client->ps.saber[0].blade[i].length == 0 ) + continue; + + if ( i != 0 ) + {//not first blade + if ( ent->client->ps.saber[0].type == SABER_BROAD || + ent->client->ps.saber[0].type == SABER_SAI || + ent->client->ps.saber[0].type == SABER_CLAW ) + { + g_saberNoEffects = qtrue; + } + } + WP_SaberDamageTrace( ent, 0, i ); + } + // Saber 2. + g_saberNoEffects = noEffects; + if ( ent->client->ps.dualSabers ) + { + for ( int i = 0; i < ent->client->ps.saber[1].numBlades; i++ ) + { + // If the Blade is not active and the length is 0, don't trace it, try the next blade... + if ( !ent->client->ps.saber[1].blade[i].active && ent->client->ps.saber[1].blade[i].length == 0 ) + continue; + + if ( i != 0 ) + {//not first blade + if ( ent->client->ps.saber[1].type == SABER_BROAD || + ent->client->ps.saber[1].type == SABER_SAI || + ent->client->ps.saber[1].type == SABER_CLAW ) + { + g_saberNoEffects = qtrue; + } + } + WP_SaberDamageTrace( ent, 1, i ); + } + } + g_saberNoEffects = qfalse; +} + +//SABER THROWING============================================================================ +//SABER THROWING============================================================================ +//SABER THROWING============================================================================ +//SABER THROWING============================================================================ +//SABER THROWING============================================================================ +//SABER THROWING============================================================================ + +/* +================ +WP_SaberImpact + +================ +*/ +void WP_SaberImpact( gentity_t *owner, gentity_t *saber, trace_t *trace ) +{ + gentity_t *other; + + other = &g_entities[trace->entityNum]; + + if ( other->takedamage && (other->svFlags&SVF_BBRUSH) ) + {//a breakable brush? break it! + if ( (other->spawnflags&1)//INVINCIBLE + ||(other->flags&FL_DMG_BY_HEAVY_WEAP_ONLY) )//HEAVY weapon damage only + {//can't actually break it + //no hit effect (besides regular client-side one) + } + else if ( other->NPC_targetname && + (!owner||!owner->targetname||Q_stricmp(owner->targetname,other->NPC_targetname)) ) + {//only breakable by an entity who is not the attacker + //no hit effect (besides regular client-side one) + } + else + { + vec3_t dir; + VectorCopy( saber->s.pos.trDelta, dir ); + VectorNormalize( dir ); + + int dmg = other->health*2; + if ( other->health > 50 && dmg > 20 && !(other->svFlags&SVF_GLASS_BRUSH) ) + { + dmg = 20; + } + G_Damage( other, saber, owner, dir, trace->endpos, dmg, 0, MOD_SABER ); + G_PlayEffect( "saber/saber_cut", trace->endpos, dir ); + if(owner->client && owner->client->ps.clientNum == 0) + FF_Play(fffx_Laser1); + if ( owner->s.number == 0 ) + { + AddSoundEvent( owner, trace->endpos, 256, AEL_DISCOVERED ); + AddSightEvent( owner, trace->endpos, 512, AEL_DISCOVERED, 50 ); + } + return; + } + } + + if ( saber->s.pos.trType == TR_LINEAR ) + { + //hit a wall? send it back + WP_SaberReturn( saber->owner, saber ); + } + + if ( other && !other->client && (other->contents&CONTENTS_LIGHTSABER) )//&& other->s.weapon == WP_SABER ) + {//2 in-flight sabers collided! + //Big flash + //FIXME: bigger effect/sound? + //FIXME: STILL DOESNT WORK!!! + G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) ); + G_PlayEffect( "saber/saber_block", trace->endpos ); + if(owner->client && owner->client->ps.clientNum == 0) + FF_Play(fffx_Laser1); + g_saberFlashTime = level.time-50; + VectorCopy( trace->endpos, g_saberFlashPos ); + } + + if ( owner && owner->s.number == 0 && owner->client ) + { + //Add the event + if ( owner->client->ps.SaberLength() > 0 ) + {//saber is on, very suspicious + if ( (!owner->client->ps.saberInFlight && owner->client->ps.groundEntityNum == ENTITYNUM_WORLD)//holding saber and on ground + || saber->s.pos.trType == TR_STATIONARY )//saber out there somewhere and on ground + {//an on-ground alert + AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED, qfalse, qtrue ); + } + else + {//an in-air alert + AddSoundEvent( owner, saber->currentOrigin, 128, AEL_DISCOVERED ); + } + AddSightEvent( owner, saber->currentOrigin, 256, AEL_DISCOVERED, 50 ); + } + else + {//saber is off, not as suspicious + AddSoundEvent( owner, saber->currentOrigin, 128, AEL_SUSPICIOUS ); + AddSightEvent( owner, saber->currentOrigin, 256, AEL_SUSPICIOUS ); + } + } + + // check for bounce + if ( !other->takedamage && ( saber->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) + { + // Check to see if there is a bounce count + if ( saber->bounceCount ) { + // decrement number of bounces and then see if it should be done bouncing + if ( --saber->bounceCount <= 0 ) { + // He (or she) will bounce no more (after this current bounce, that is). + saber->s.eFlags &= !( EF_BOUNCE | EF_BOUNCE_HALF ); + if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING ) + { + WP_SaberDrop( saber->owner, saber ); + } + return; + } + else + {//bounced and still have bounces left + if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING ) + {//under telekinetic control + if ( !gi.inPVS( saber->currentOrigin, owner->client->renderInfo.handRPoint ) ) + {//not in the PVS of my master + saber->bounceCount -= 25; + } + } + } + } + + if ( saber->s.pos.trType == TR_LINEAR && owner && owner->client && owner->client->ps.saberEntityState == SES_RETURNING ) + { + //don't home for a few frames so we can get around this thing + trace_t bounceTr; + vec3_t end; + float owner_dist = Distance( owner->client->renderInfo.handRPoint, saber->currentOrigin ); + + VectorMA( saber->currentOrigin, 10, trace->plane.normal, end ); + gi.trace( &bounceTr, saber->currentOrigin, saber->mins, saber->maxs, end, owner->s.number, saber->clipmask ); + VectorCopy( bounceTr.endpos, saber->currentOrigin ); + if ( owner_dist > 0 ) + { + if ( owner_dist > 50 ) + { + owner->client->ps.saberEntityDist = owner_dist-50; + } + else + { + owner->client->ps.saberEntityDist = 0; + } + } + return; + } + + G_BounceMissile( saber, trace ); + + if ( saber->s.pos.trType == TR_GRAVITY ) + {//bounced + //play a bounce sound + if ( owner + && owner->client + && owner->client->ps.saber[0].type == SABER_SITH_SWORD ) + { + G_Sound( saber, G_SoundIndex( va( "sound/weapons/sword/fall%d.wav", Q_irand( 1, 7 ) ) ) ); + } + else + { + G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) ); + } + //change rotation + VectorCopy( saber->currentAngles, saber->s.apos.trBase ); + saber->s.apos.trType = TR_LINEAR; + saber->s.apos.trTime = level.time; + VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), Q_irand( -300, 300 ), Q_irand( -300, 300 ) ); + } + //see if we stopped + else if ( saber->s.pos.trType == TR_STATIONARY ) + {//stopped + //play a bounce sound + if ( owner + && owner->client + && owner->client->ps.saber[0].type == SABER_SITH_SWORD ) + { + G_Sound( saber, G_SoundIndex( va( "sound/weapons/sword/fall%d.wav", Q_irand( 1, 7 ) ) ) ); + } + else + { + G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/bounce%d.wav", Q_irand( 1, 3 ) ) ) ); + } + //stop rotation + VectorClear( saber->s.apos.trDelta ); + saber->currentAngles[0] = SABER_PITCH_HACK; + VectorCopy( saber->currentAngles, saber->s.apos.trBase ); + //remember when it fell so it can return automagically + saber->aimDebounceTime = level.time; + } + } + else if ( other->client && other->health > 0 + && ( (other->NPC && (other->NPC->aiFlags&NPCAI_BOSS_CHARACTER)) + //|| other->client->NPC_class == CLASS_ALORA + || other->client->NPC_class == CLASS_BOBAFETT + || ( other->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) ) ) + {//Luke, Desann and Tavion slap thrown sabers aside + WP_SaberDrop( owner, saber ); + G_Sound( saber, G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) ); + G_PlayEffect( "saber/saber_block", trace->endpos ); + if(owner->client && owner->client->ps.clientNum == 0) + FF_Play(fffx_Laser1); + g_saberFlashTime = level.time-50; + VectorCopy( trace->endpos, g_saberFlashPos ); + //FIXME: make Luke/Desann/Tavion play an attack anim or some other special anim when this happens + Jedi_PlayDeflectSound( other ); + } +} + +extern float G_PointDistFromLineSegment( const vec3_t start, const vec3_t end, const vec3_t from ); +void WP_SaberInFlightReflectCheck( gentity_t *self, usercmd_t *ucmd ) +{ + gentity_t *ent; + gentity_t *entityList[MAX_GENTITIES]; + gentity_t *missile_list[MAX_GENTITIES]; + int numListedEntities; + vec3_t mins, maxs; + int i, e, numSabers; + int ent_count = 0; + int radius = 180; + vec3_t center; + vec3_t tip; + vec3_t up = {0,0,1}; + qboolean willHit = qfalse; + + if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) ) + {//don't react to things flying at me... + return; + } + //sanity checks: make sure we actually have a saberent + if ( self->client->ps.weapon != WP_SABER ) + { + return; + } + if ( !self->client->ps.saberInFlight ) + { + return; + } + if ( !self->client->ps.SaberLength() ) + { + return; + } + if ( self->client->ps.saberEntityNum == ENTITYNUM_NONE ) + { + return; + } + gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum]; + if ( !saberent ) + { + return; + } + //okay, enough damn sanity checks + + VectorCopy( saberent->currentOrigin, center ); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + //FIXME: check visibility? + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + + if (ent == self) + continue; + if (ent->owner == self) + continue; + if ( !(ent->inuse) ) + continue; + if ( ent->s.eType != ET_MISSILE ) + { + if ( ent->client || ent->s.weapon != WP_SABER ) + {//FIXME: wake up bad guys? + continue; + } + if ( ent->s.eFlags & EF_NODRAW ) + { + continue; + } + if ( Q_stricmp( "lightsaber", ent->classname ) != 0 ) + {//not a lightsaber + continue; + } + } + else + {//FIXME: make exploding missiles explode? + if ( ent->s.pos.trType == TR_STATIONARY ) + {//nothing you can do with a stationary missile + continue; + } + if ( ent->splashDamage || ent->splashRadius ) + {//can't deflect exploding missiles + if ( DistanceSquared( ent->currentOrigin, center ) < 256 )//16 squared + { + G_MissileImpacted( ent, saberent, ent->currentOrigin, up ); + } + continue; + } + } + + //don't deflect it if it's not within 16 units of the blade + //do this for all blades + willHit = qfalse; + numSabers = 1; + if ( self->client->ps.dualSabers ) + { + numSabers = 2; + } + for ( int saberNum = 0; saberNum < numSabers; saberNum++ ) + { + for ( int bladeNum = 0; bladeNum < self->client->ps.saber[saberNum].numBlades; bladeNum++ ) + { + VectorMA( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, self->client->ps.saber[saberNum].blade[bladeNum].length, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, tip ); + + if( G_PointDistFromLineSegment( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, tip, ent->currentOrigin ) <= 32 ) + { + willHit = qtrue; + break; + } + } + if ( willHit ) + { + break; + } + } + if ( !willHit ) + { + continue; + } + // ok, we are within the radius, add us to the incoming list + missile_list[ent_count] = ent; + ent_count++; + + } + + if ( ent_count ) + { + vec3_t fx_dir; + // we are done, do we have any to deflect? + if ( ent_count ) + { + for ( int x = 0; x < ent_count; x++ ) + { + if ( missile_list[x]->s.weapon == WP_SABER ) + {//just send it back + if ( missile_list[x]->owner && missile_list[x]->owner->client && missile_list[x]->owner->client->ps.saber[0].Active() && missile_list[x]->s.pos.trType == TR_LINEAR && missile_list[x]->owner->client->ps.saberEntityState != SES_RETURNING ) + {//it's on and being controlled + //FIXME: prevent it from damaging me? + WP_SaberReturn( missile_list[x]->owner, missile_list[x] ); + VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir ); + G_PlayEffect( "saber/saber_block", missile_list[x]->currentOrigin, fx_dir ); + if(ent->client && ent->client->ps.clientNum == 0) + FF_Play(fffx_Laser1); + if ( missile_list[x]->owner->client->ps.saberInFlight && self->client->ps.saberInFlight ) + { + G_Sound( missile_list[x], G_SoundIndex( va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) ) ) ); + g_saberFlashTime = level.time-50; + gentity_t *saber = &g_entities[self->client->ps.saberEntityNum]; + vec3_t org; + VectorSubtract( missile_list[x]->currentOrigin, saber->currentOrigin, org ); + VectorMA( saber->currentOrigin, 0.5, org, org ); + VectorCopy( org, g_saberFlashPos ); + } + } + } + else + {//bounce it + vec3_t reflectAngle, forward; + if ( self->client && !self->s.number ) + { + self->client->sess.missionStats.saberBlocksCnt++; + } + VectorCopy( saberent->s.apos.trBase, reflectAngle ); + reflectAngle[PITCH] = Q_flrand( -90, 90 ); + AngleVectors( reflectAngle, forward, NULL, NULL ); + + G_ReflectMissile( self, missile_list[x], forward ); + //do an effect + VectorNormalize2( missile_list[x]->s.pos.trDelta, fx_dir ); + G_PlayEffect( "blaster/deflect", missile_list[x]->currentOrigin, fx_dir ); + if(ent->client && ent->client->ps.clientNum == 0) + FF_Play(fffx_Laser2); + } + } + } + } +} + +qboolean WP_SaberValidateEnemy( gentity_t *self, gentity_t *enemy ) +{ + if ( !enemy ) + { + return qfalse; + } + + if ( !enemy || enemy == self || !enemy->inuse || !enemy->client ) + {//not valid + return qfalse; + } + + if ( enemy->health <= 0 ) + {//corpse + return qfalse; + } + + /* + if ( enemy->client->ps.weapon == WP_SABER + && enemy->client->ps.SaberActive() ) + {//not other saber-users? + return qfalse; + } + */ + if ( enemy->client->ps.forcePowersKnown ) + {//not other jedi? + return qfalse; + } + + if ( DistanceSquared( self->client->renderInfo.handRPoint, enemy->currentOrigin ) > saberThrowDistSquared[self->client->ps.forcePowerLevel[FP_SABERTHROW]] ) + {//too far + return qfalse; + } + + if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) ) + && ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 ) ) + {//(not in front or not clear LOS) & greater than 256 away + return qfalse; + } + + if ( enemy->client->playerTeam == self->client->playerTeam ) + {//on same team + return qfalse; + } + + //LOS? + + return qtrue; +} + +float WP_SaberRateEnemy( gentity_t *enemy, vec3_t center, vec3_t forward, float radius ) +{ + float rating; + vec3_t dir; + + VectorSubtract( enemy->currentOrigin, center, dir ); + rating = (1.0f-(VectorNormalize( dir )/radius)); + rating *= DotProduct( forward, dir ); + return rating; +} + +gentity_t *WP_SaberFindEnemy( gentity_t *self, gentity_t *saber ) +{ +//FIXME: should be a more intelligent way of doing this, like auto aim? +//closest, most in front... did damage to... took damage from? How do we know who the player is focusing on? + gentity_t *ent, *bestEnt = NULL; + gentity_t *entityList[MAX_GENTITIES]; + int numListedEntities; + vec3_t center, mins, maxs, fwdangles, forward; + int i, e; + float radius = 400; + float rating, bestRating = 0.0f; + + //FIXME: no need to do this in 1st person? + fwdangles[1] = self->client->ps.viewangles[1]; + AngleVectors( fwdangles, forward, NULL, NULL ); + + VectorCopy( saber->currentOrigin, center ); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + + //if the saber has an enemy from the last time it looked, init to that one + if ( WP_SaberValidateEnemy( self, saber->enemy ) ) + { + if ( gi.inPVS( self->currentOrigin, saber->enemy->currentOrigin ) ) + {//potentially visible + if ( G_ClearLOS( self, self->client->renderInfo.eyePoint, saber->enemy ) ) + {//can see him + bestEnt = saber->enemy; + bestRating = WP_SaberRateEnemy( bestEnt, center, forward, radius ); + } + } + } + + //If I have an enemy, see if that's even better + if ( WP_SaberValidateEnemy( self, self->enemy ) ) + { + float myEnemyRating = WP_SaberRateEnemy( self->enemy, center, forward, radius ); + if ( myEnemyRating > bestRating ) + { + if ( gi.inPVS( self->currentOrigin, self->enemy->currentOrigin ) ) + {//potentially visible + if ( G_ClearLOS( self, self->client->renderInfo.eyePoint, self->enemy ) ) + {//can see him + bestEnt = self->enemy; + bestRating = myEnemyRating; + } + } + } + } + + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + if ( !numListedEntities ) + {//should we clear the enemy? + return bestEnt; + } + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + + if ( ent == self || ent == saber || ent == bestEnt ) + { + continue; + } + if ( !WP_SaberValidateEnemy( self, ent ) ) + {//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call + continue; + } + if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) ) + {//not even potentially visible + continue; + } + if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) ) + {//can't see him + continue; + } + //rate him based on how close & how in front he is + rating = WP_SaberRateEnemy( ent, center, forward, radius ); + if ( rating > bestRating ) + { + bestEnt = ent; + bestRating = rating; + } + } + return bestEnt; +} + +void WP_RunSaber( gentity_t *self, gentity_t *saber ) +{ + vec3_t origin, oldOrg; + trace_t tr; + + VectorCopy( saber->currentOrigin, oldOrg ); + // get current position + EvaluateTrajectory( &saber->s.pos, level.time, origin ); + // get current angles + EvaluateTrajectory( &saber->s.apos, level.time, saber->currentAngles ); + + // trace a line from the previous position to the current position, + // ignoring interactions with the missile owner + int clipmask = saber->clipmask; + if ( !self || !self->client || self->client->ps.SaberLength() <= 0 ) + {//don't keep hitting other sabers when turned off + clipmask &= ~CONTENTS_LIGHTSABER; + } + gi.trace( &tr, saber->currentOrigin, saber->mins, saber->maxs, origin, + saber->owner ? saber->owner->s.number : ENTITYNUM_NONE, clipmask ); + + VectorCopy( tr.endpos, saber->currentOrigin ); + + if ( self->client->ps.SaberActive() ) + { + if ( self->client->ps.saberInFlight || (self->client->ps.weaponTime&&!Q_irand( 0, 100 )) ) + {//make enemies run from a lit saber in flight or from me when I'm attacking + if ( !Q_irand( 0, 10 ) ) + {//not so often... + AddSightEvent( self, saber->currentOrigin, self->client->ps.SaberLength()*3, AEL_DANGER, 100 ); + } + } + } + + if ( tr.startsolid ) + { + tr.fraction = 0; + } + + gi.linkentity( saber ); + + //touch push triggers? + + if ( tr.fraction != 1 ) + { + WP_SaberImpact( self, saber, &tr ); + } + + if ( saber->s.pos.trType == TR_LINEAR ) + {//home + //figure out where saber should be + vec3_t forward, saberHome, saberDest, fwdangles = {0}; + + VectorCopy( self->client->ps.viewangles, fwdangles ); + if ( self->s.number ) + { + fwdangles[0] -= 8; + } + else if ( cg.renderingThirdPerson ) + { + fwdangles[0] -= 5; + } + + if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1 + || self->client->ps.saberEntityState == SES_RETURNING + || VectorCompare( saber->s.pos.trDelta, vec3_origin ) ) + {//control if it's returning or just starting + float saberSpeed = 500;//FIXME: based on force level? + float dist; + gentity_t *enemy = NULL; + + AngleVectors( fwdangles, forward, NULL, NULL ); + + if ( self->client->ps.saberEntityDist < 100 ) + {//make the saber head to my hand- the bolt it was attached to + VectorCopy( self->client->renderInfo.handRPoint, saberHome ); + } + else + {//aim saber from eyes + VectorCopy( self->client->renderInfo.eyePoint, saberHome ); + } + VectorMA( saberHome, self->client->ps.saberEntityDist, forward, saberDest ); + + if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 + && self->client->ps.saberEntityState == SES_LEAVING ) + {//max level + if ( self->enemy && + !WP_SaberValidateEnemy( self, self->enemy ) ) + {//if my enemy isn't valid to auto-aim at, don't autoaim + } + else + { + //pick an enemy + enemy = WP_SaberFindEnemy( self, saber ); + if ( enemy ) + {//home in on enemy + float enemyDist = Distance( self->client->renderInfo.handRPoint, enemy->currentOrigin ); + VectorCopy( enemy->currentOrigin, saberDest ); + saberDest[2] += enemy->maxs[2]/2.0f;//FIXME: when in a knockdown anim, the saber float above them... do we care? + self->client->ps.saberEntityDist = enemyDist; + //once you pick an enemy, stay with it! + saber->enemy = enemy; + //FIXME: lock onto that enemy for a minimum amount of time (unless they become invalid?) + } + } + } + + + //Make the saber head there + VectorSubtract( saberDest, saber->currentOrigin, saber->s.pos.trDelta ); + dist = VectorNormalize( saber->s.pos.trDelta ); + if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 && self->client->ps.saberEntityState == SES_LEAVING && !enemy ) + { + if ( dist < 200 ) + { + saberSpeed = 400 - (dist*2); + } + } + else if ( self->client->ps.saberEntityState == SES_LEAVING && dist < 50 ) + { + saberSpeed = dist * 2 + 30; + if ( (enemy && dist > enemy->maxs[0]) || (!enemy && dist > 24) ) + {//auto-tracking an enemy and we can't hit him + if ( saberSpeed < 120 ) + {//clamp to a minimum speed + saberSpeed = 120; + } + } + } + /* + if ( self->client->ps.saberEntityState == SES_RETURNING ) + {//FIXME: if returning, move faster? + saberSpeed = 800; + if ( dist < 200 ) + { + saberSpeed -= 400 - (dist*2); + } + } + */ + VectorScale( saber->s.pos.trDelta, saberSpeed, saber->s.pos.trDelta ); + //SnapVector( saber->s.pos.trDelta ); // save net bandwidth + VectorCopy( saber->currentOrigin, saber->s.pos.trBase ); + saber->s.pos.trTime = level.time; + saber->s.pos.trType = TR_LINEAR; + } + else + { + VectorCopy( saber->currentOrigin, saber->s.pos.trBase ); + saber->s.pos.trTime = level.time; + saber->s.pos.trType = TR_LINEAR; + } + + //if it's heading back, point it's base at us + if ( self->client->ps.saberEntityState == SES_RETURNING + && self->client->ps.saber[0].returnDamage == qfalse )//type != SABER_STAR ) + { + fwdangles[0] += SABER_PITCH_HACK; + VectorCopy( fwdangles, saber->s.apos.trBase ); + saber->s.apos.trTime = level.time; + saber->s.apos.trType = TR_INTERPOLATE; + VectorClear( saber->s.apos.trDelta ); + } + } +} + + +qboolean WP_SaberLaunch( gentity_t *self, gentity_t *saber, qboolean thrown, qboolean noFail = qfalse ) +{//FIXME: probably need a debounce time + vec3_t saberMins={-3.0f,-3.0f,-3.0f}; + vec3_t saberMaxs={3.0f,3.0f,3.0f}; + trace_t trace; + + if ( self->client->NPC_class == CLASS_SABER_DROID ) + {//saber droids can't drop their saber + return qfalse; + } + if ( !noFail ) + { + if ( thrown ) + {//this is a regular throw, so see if it's legal + if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 ) + { + if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 20 ) ) + { + return qfalse; + } + } + else + { + if ( !WP_ForcePowerUsable( self, FP_SABERTHROW, 0 ) ) + { + return qfalse; + } + } + } + if ( !self->s.number && (cg.zoomMode || in_camera) ) + {//can't saber throw when zoomed in or in cinematic + return qfalse; + } + //make sure it won't start in solid + gi.trace( &trace, self->client->renderInfo.handRPoint, saberMins, saberMaxs, self->client->renderInfo.handRPoint, saber->s.number, MASK_SOLID ); + if ( trace.startsolid || trace.allsolid ) + { + return qfalse; + } + //make sure I'm not throwing it on the other side of a door or wall or whatever + gi.trace( &trace, self->currentOrigin, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID ); + if ( trace.startsolid || trace.allsolid || trace.fraction < 1.0f ) + { + return qfalse; + } + + if ( thrown ) + {//this is a regular throw, so take force power + if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 ) + {//at max skill, the cost increases as keep it out + WP_ForcePowerStart( self, FP_SABERTHROW, 10 ); + } + else + { + WP_ForcePowerStart( self, FP_SABERTHROW, 0 ); + } + } + } + //clear the enemy + saber->enemy = NULL; + //draw it + saber->s.eFlags &= ~EF_NODRAW; + saber->svFlags |= SVF_BROADCAST; + saber->svFlags &= ~SVF_NOCLIENT; + + //place it + VectorCopy( self->client->renderInfo.handRPoint, saber->currentOrigin );//muzzlePoint + VectorCopy( saber->currentOrigin, saber->s.pos.trBase ); + saber->s.pos.trTime = level.time; + saber->s.pos.trType = TR_LINEAR; + VectorClear( saber->s.pos.trDelta ); + gi.linkentity( saber ); + + //spin it + VectorClear( saber->s.apos.trBase ); + saber->s.apos.trTime = level.time; + saber->s.apos.trType = TR_LINEAR; + if ( self->health > 0 && thrown ) + {//throwing it + saber->s.apos.trBase[1] = self->client->ps.viewangles[1]; + saber->s.apos.trBase[0] = SABER_PITCH_HACK; + } + else + {//dropping it + vectoangles( self->client->renderInfo.muzzleDir, saber->s.apos.trBase ); + } + VectorClear( saber->s.apos.trDelta ); + + switch ( self->client->ps.forcePowerLevel[FP_SABERTHROW] ) + {//FIXME: make a table? + default: + case FORCE_LEVEL_1: + saber->s.apos.trDelta[1] = 600; + break; + case FORCE_LEVEL_2: + saber->s.apos.trDelta[1] = 800; + break; + case FORCE_LEVEL_3: + saber->s.apos.trDelta[1] = 1200; + break; + } + + //Take it out of my hand + self->client->ps.saberInFlight = qtrue; + self->client->ps.saberEntityState = SES_LEAVING; + self->client->ps.saberEntityDist = saberThrowDist[self->client->ps.forcePowerLevel[FP_SABERTHROW]]; + self->client->ps.saberThrowTime = level.time; + //if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 ) + { + self->client->ps.forcePowerDebounce[FP_SABERTHROW] = level.time + 1000;//so we can keep it out for a minimum amount of time + } + + if ( thrown ) + {//this is a regular throw, so turn the saber on + //turn saber on + if ( self->client->ps.saber[0].singleBladeThrowable )//SaberStaff() ) + {//only first blade can be on + if ( !self->client->ps.saber[0].blade[0].active ) + {//turn on first one + self->client->ps.SaberBladeActivate( 0, 0 ); + } + for ( int i = 1; i < self->client->ps.saber[0].numBlades; i++ ) + {//turn off all others + if ( self->client->ps.saber[0].blade[i].active ) + { + self->client->ps.SaberBladeActivate( 0, i, qfalse ); + } + } + } + else + {//turn the sabers, all blades...? + self->client->ps.saber[0].Activate(); + //self->client->ps.SaberActivate(); + } + //turn on the saber trail + self->client->ps.saber[0].ActivateTrail( 150 ); + } + + //reset the mins + VectorCopy( saberMins, saber->mins ); + VectorCopy( saberMaxs, saber->maxs ); + saber->contents = 0;//CONTENTS_LIGHTSABER; + saber->clipmask = MASK_SOLID | CONTENTS_LIGHTSABER; + + // remove the ghoul2 right-hand saber model on the player + if ( self->weaponModel[0] > 0 ) + { + gi.G2API_RemoveGhoul2Model(self->ghoul2, self->weaponModel[0]); + self->weaponModel[0] = -1; + } + + return qtrue; +} + +qboolean WP_SaberLose( gentity_t *self, vec3_t throwDir ) +{ + if ( !self || !self->client || self->client->ps.saberEntityNum <= 0 ) + {//WTF?!! We lost it already? + return qfalse; + } + if ( self->client->NPC_class == CLASS_SABER_DROID ) + {//saber droids can't drop their saber + return qfalse; + } + gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum]; + if ( !self->client->ps.saberInFlight ) + {//not alreay in air + /* + qboolean noForceThrow = qfalse; + //make it so we can throw it + self->client->ps.forcePowersKnown |= (1<client->ps.forcePowerLevel[FP_SABERTHROW] < FORCE_LEVEL_1 ) + { + noForceThrow = qtrue; + self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1; + } + */ + //throw it + if ( !WP_SaberLaunch( self, dropped, qfalse ) ) + {//couldn't throw it + return qfalse; + } + /* + if ( noForceThrow ) + { + self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_0; + } + */ + } + if ( self->client->ps.saber[0].Active() ) + {//on + //drop it instantly + WP_SaberDrop( self, dropped ); + } + //optionally give it some thrown velocity + if ( throwDir && !VectorCompare( throwDir, vec3_origin ) ) + { + VectorCopy( throwDir, dropped->s.pos.trDelta ); + } + //don't pull it back on the next frame + if ( self->NPC ) + { + self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK; + } + return qtrue; +} + +void WP_SetSaberOrigin( gentity_t *self, vec3_t newOrg ) +{ + if ( !self || !self->client ) + { + return; + } + if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD ) + {//no saber ent to reposition + return; + } + if ( self->client->NPC_class == CLASS_SABER_DROID ) + {//saber droids can't drop their saber + return; + } + gentity_t *dropped = &g_entities[self->client->ps.saberEntityNum]; + if ( !self->client->ps.saberInFlight ) + {//not already in air + qboolean noForceThrow = qfalse; + //make it so we can throw it + self->client->ps.forcePowersKnown |= (1<client->ps.forcePowerLevel[FP_SABERTHROW] < FORCE_LEVEL_1 ) + { + noForceThrow = qtrue; + self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_1; + } + //throw it + if ( !WP_SaberLaunch( self, dropped, qfalse, qtrue ) ) + {//couldn't throw it + return; + } + if ( noForceThrow ) + { + self->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_0; + } + } + VectorCopy( newOrg, dropped->s.origin ); + VectorCopy( newOrg, dropped->currentOrigin ); + VectorCopy( newOrg, dropped->s.pos.trBase ); + //drop it instantly + WP_SaberDrop( self, dropped ); + //don't pull it back on the next frame + if ( self->NPC ) + { + self->NPC->last_ucmd.buttons &= ~BUTTON_ATTACK; + } +} + +void WP_SaberCatch( gentity_t *self, gentity_t *saber, qboolean switchToSaber ) +{//FIXME: probably need a debounce time + if ( self->health > 0 && !PM_SaberInBrokenParry( self->client->ps.saberMove ) && self->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + { + //clear the enemy + saber->enemy = NULL; + //don't draw it + saber->s.eFlags |= EF_NODRAW; + saber->svFlags &= SVF_BROADCAST; + saber->svFlags |= SVF_NOCLIENT; + + //take off any gravity stuff if we'd dropped it + saber->s.pos.trType = TR_LINEAR; + saber->s.eFlags &= ~EF_BOUNCE_HALF; + + //Put it in my hand + self->client->ps.saberInFlight = qfalse; + self->client->ps.saberEntityState = SES_LEAVING; + + //turn off the saber trail + self->client->ps.saber[0].DeactivateTrail( 75 ); + + //reset its contents/clipmask + saber->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP; + saber->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + //play catch sound + G_Sound( saber, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) ); + //FIXME: if an NPC, don't turn it back on if no enemy or enemy is dead... + //if it's not our current weapon, make it our current weapon + if ( self->client->ps.weapon == WP_SABER ) + {//only do the first saber since we only throw the first one + WP_SaberAddG2SaberModels( self, qfalse ); + } + if ( switchToSaber ) + { + if ( self->client->ps.weapon != WP_SABER ) + { + CG_ChangeWeapon( WP_SABER ); + } + else + {//if it's not active, turn it on + if ( self->client->ps.saber[0].singleBladeThrowable )//SaberStaff() ) + {//only first blade can be on + if ( !self->client->ps.saber[0].blade[0].active ) + {//only turn it on if first blade is off, otherwise, leave as-is + self->client->ps.saber[0].Activate(); + } + } + else + {//turn all blades on + self->client->ps.saber[0].Activate(); + } + } + } + } +} + + +void WP_SaberReturn( gentity_t *self, gentity_t *saber ) +{ + if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN ) + { + return; + } + if ( self && self->client ) + {//still alive and stuff + //FIXME: when it's returning, flies butt first, but seems to do a lot of damage when going through people... hmm... + self->client->ps.saberEntityState = SES_RETURNING; + //turn down the saber trail + if ( self->client->ps.saber[0].returnDamage == qfalse )//type != SABER_STAR ) + { + self->client->ps.saber[0].DeactivateTrail( 75 ); + } + } + if ( !(saber->s.eFlags&EF_BOUNCE) ) + { + saber->s.eFlags |= EF_BOUNCE; + saber->bounceCount = 300; + } +} + + +void WP_SaberDrop( gentity_t *self, gentity_t *saber ) +{ + //clear the enemy + saber->enemy = NULL; + saber->s.eFlags &= ~EF_BOUNCE; + saber->bounceCount = 0; + //make it fall + saber->s.pos.trType = TR_GRAVITY; + //make it bounce some + saber->s.eFlags |= EF_BOUNCE_HALF; + //make it spin + VectorCopy( saber->currentAngles, saber->s.apos.trBase ); + saber->s.apos.trType = TR_LINEAR; + saber->s.apos.trTime = level.time; + VectorSet( saber->s.apos.trDelta, Q_irand( -300, 300 ), saber->s.apos.trDelta[1], Q_irand( -300, 300 ) ); + if ( !saber->s.apos.trDelta[1] ) + { + saber->s.apos.trDelta[1] = Q_irand( -300, 300 ); + } + //force it to be ready to return + self->client->ps.saberEntityDist = 0; + self->client->ps.saberEntityState = SES_RETURNING; + //turn it off + self->client->ps.saber[0].Deactivate(); + //turn off the saber trail + self->client->ps.saber[0].DeactivateTrail( 75 ); + //play the saber turning off sound + G_SoundIndexOnEnt( saber, CHAN_AUTO, self->client->ps.saber[0].soundOff ); +#ifdef _IMMERSION + if ( self->client->playerTeam == TEAM_PLAYER ) + { + G_Force( self, G_ForceIndex( "fffx/weapons/saber/saberoff", FF_CHANNEL_WEAPON ) ); + } + else + { + G_Force( self, G_ForceIndex( "fffx/weapons/saber/enemy_saber_off", FF_CHANNEL_WEAPON ) ); + } +#endif // _IMMERSION + + if ( self->health <= 0 ) + {//owner is dead! + saber->s.time = level.time;//will make us free ourselves after a time + } +} + + +void WP_SaberPull( gentity_t *self, gentity_t *saber ) +{ + if ( PM_SaberInBrokenParry( self->client->ps.saberMove ) || self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN ) + { + return; + } + if ( self->health > 0 ) + { + //take off gravity + saber->s.pos.trType = TR_LINEAR; + //take off bounce + saber->s.eFlags &= EF_BOUNCE_HALF; + //play sound + G_Sound( self, G_SoundIndex( "sound/weapons/force/pull.wav" ) ); +#ifdef _IMMERSION + G_Force( self, G_ForceIndex( "fffx/weapons/force/pull", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + } +} + + +// Check if we are throwing it, launch it if needed, update position if needed. +void WP_SaberThrow( gentity_t *self, usercmd_t *ucmd ) +{ + static float MAX_SABER_DIST = 400; + vec3_t saberDiff; + trace_t tr; + //static float SABER_SPEED = 10; + + gentity_t *saberent; + + if ( self->client->ps.saberEntityNum <= 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD ) + {//WTF?!! We lost it? + return; + } + + if ( self->client->ps.torsoAnim == BOTH_LOSE_SABER ) + {//can't catch it while it's being yanked from your hand! + return; + } + + if ( !g_saberNewControlScheme->integer ) + { + if ( PM_SaberInKata( (saberMoveName_t)self->client->ps.saberMove ) ) + {//don't throw saber when in special attack (alt+attack) + return; + } + if ( (ucmd->buttons&BUTTON_ATTACK) + && (ucmd->buttons&BUTTON_ALT_ATTACK) + && !self->client->ps.saberInFlight ) + {//trying to do special attack, don't throw it + return; + } + else if ( self->client->ps.torsoAnim == BOTH_A1_SPECIAL + || self->client->ps.torsoAnim == BOTH_A2_SPECIAL + || self->client->ps.torsoAnim == BOTH_A3_SPECIAL ) + {//don't throw in these anims! + return; + } + } + saberent = &g_entities[self->client->ps.saberEntityNum]; + + VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff ); + + //is our saber in flight? + if ( !self->client->ps.saberInFlight ) + {//saber is not in flight right now + if ( self->client->ps.weapon != WP_SABER ) + {//don't even have it out + return; + } + else if ( (ucmd->buttons&BUTTON_ALT_ATTACK) && !(self->client->ps.pm_flags&PMF_ALT_ATTACK_HELD) ) + {//still holding it, not still holding attack from a previous throw, so throw it. + if ( !(self->client->ps.saberEventFlags&SEF_INWATER) && WP_SaberLaunch( self, saberent, qtrue ) ) + { + if ( self->client && !self->s.number ) + { + self->client->sess.missionStats.saberThrownCnt++; + } + //need to recalc this because we just moved it + VectorSubtract( self->client->renderInfo.handRPoint, saberent->currentOrigin, saberDiff ); + } + else + {//couldn't throw it + return; + } + } + else + {//holding it, don't want to throw it, go away. + return; + } + } + else + {//inflight + //is our saber currently on it's way back to us? + if ( self->client->ps.saberEntityState == SES_RETURNING ) + {//see if we're close enough to pick it up + if ( VectorLengthSquared( saberDiff ) <= 256 )//16 squared//G_BoundsOverlap( self->absmin, self->absmax, saberent->absmin, saberent->absmax ) )// + {//caught it + vec3_t axisPoint; + trace_t trace; + VectorCopy( self->currentOrigin, axisPoint ); + axisPoint[2] = self->client->renderInfo.handRPoint[2]; + gi.trace( &trace, axisPoint, vec3_origin, vec3_origin, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID ); + if ( !trace.startsolid && trace.fraction >= 1.0f ) + {//our hand isn't through a wall + WP_SaberCatch( self, saberent, qtrue ); + //NPC_SetAnim( self, SETANIM_TORSO, TORSO_HANDRETRACT1, SETANIM_FLAG_OVERRIDE ); + } + return; + } + } + + if ( saberent->s.pos.trType != TR_STATIONARY ) + {//saber is in flight, lerp it + WP_RunSaber( self, saberent ); + } + else + {//it fell on the ground + if ( self->health <= 0 && level.time > saberent->s.time + 5000 ) + {//make us free ourselves after a time + G_FreeEntity( saberent ); + self->client->ps.saberEntityNum = ENTITYNUM_NONE; + return; + } + if ( (!self->s.number && level.time - saberent->aimDebounceTime > 15000) + || (self->s.number && level.time - saberent->aimDebounceTime > 5000) ) + {//(only for player) been missing for 15 seconds, automagicially return + WP_SaberCatch( self, saberent, qfalse ); + return; + } + } + } + + //are we still trying to use the saber? + if ( self->client->ps.weapon != WP_SABER ) + {//switched away + if ( !self->client->ps.saberInFlight ) + {//wasn't throwing saber + return; + } + else if ( saberent->s.pos.trType == TR_LINEAR ) + {//switched away while controlling it, just drop the saber + WP_SaberDrop( self, saberent ); + return; + } + else + {//it's on the ground, see if it's inside us (touching) + if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) ) + {//it's in us, pick it up automatically + WP_SaberPull( self, saberent ); + } + } + } + else if ( saberent->s.pos.trType != TR_LINEAR ) + {//weapon is saber and not flying + if ( self->client->ps.saberInFlight ) + {//we dropped it + if ( ucmd->buttons & BUTTON_ATTACK )//|| self->client->ps.weaponstate == WEAPON_RAISING )//ucmd->buttons & BUTTON_ALT_ATTACK || + {//we actively want to pick it up or we just switched to it, so pull it back + gi.trace( &tr, saberent->currentOrigin, saberent->mins, saberent->maxs, self->client->renderInfo.handRPoint, self->s.number, MASK_SOLID ); + + if ( tr.allsolid || tr.startsolid || tr.fraction < 1.0f ) + {//can't pick it up yet, no LOS + return; + } + //clear LOS, pick it up + WP_SaberPull( self, saberent ); + } + else + {//see if it's inside us (touching) + if ( G_PointInBounds( saberent->currentOrigin, self->absmin, self->absmax ) ) + {//it's in us, pick it up automatically + WP_SaberPull( self, saberent ); + } + } + } + } + else if ( self->health <= 0 && self->client->ps.saberInFlight ) + {//we died, drop it + WP_SaberDrop( self, saberent ); + return; + } + else if ( !self->client->ps.saber[0].Active() && self->client->ps.saberEntityState != SES_RETURNING ) + {//we turned it off, drop it + WP_SaberDrop( self, saberent ); + return; + } + + //TODO: if deactivate saber in flight, should it drop? + + if ( saberent->s.pos.trType != TR_LINEAR ) + {//don't home + return; + } + + float saberDist = VectorLength( saberDiff ); + if ( self->client->ps.saberEntityState == SES_LEAVING ) + {//saber still flying forward + if ( self->client->ps.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_2 ) + {//still holding it out + if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time ) + {//done throwing, return to me + if ( self->client->ps.saber[0].Active() ) + {//still on + WP_SaberReturn( self, saberent ); + } + } + else if ( level.time - self->client->ps.saberThrowTime >= 100 ) + { + if ( WP_ForcePowerAvailable( self, FP_SABERTHROW, 1 ) ) + { + WP_ForcePowerDrain( self, FP_SABERTHROW, 1 ); + self->client->ps.saberThrowTime = level.time; + } + else + {//out of force power, return to me + WP_SaberReturn( self, saberent ); + } + } + } + else + { + if ( !(ucmd->buttons&BUTTON_ALT_ATTACK) && self->client->ps.forcePowerDebounce[FP_SABERTHROW] < level.time ) + {//not holding button and has been out at least 1 second, return to me + if ( self->client->ps.saber[0].Active() ) + {//still on + WP_SaberReturn( self, saberent ); + } + } + else if ( level.time - self->client->ps.saberThrowTime > 3000 + || (self->client->ps.forcePowerLevel[FP_SABERTHROW]==FORCE_LEVEL_1&&saberDist>=self->client->ps.saberEntityDist) ) + {//been out too long, or saber throw 1 went too far, return to me + if ( self->client->ps.saber[0].Active() ) + {//still on + WP_SaberReturn( self, saberent ); + } + } + } + } + if ( self->client->ps.saberEntityState == SES_RETURNING ) + { + if ( self->client->ps.saberEntityDist > 0 ) + { + self->client->ps.saberEntityDist -= 25; + } + if ( self->client->ps.saberEntityDist < 0 ) + { + self->client->ps.saberEntityDist = 0; + } + else if ( saberDist < self->client->ps.saberEntityDist ) + {//if it's coming back to me, never push it away + self->client->ps.saberEntityDist = saberDist; + } + } +} + + +//SABER BLOCKING============================================================================ +//SABER BLOCKING============================================================================ +//SABER BLOCKING============================================================================ +//SABER BLOCKING============================================================================ +//SABER BLOCKING============================================================================ +int WP_MissileBlockForBlock( int saberBlock ) +{ + switch( saberBlock ) + { + case BLOCKED_UPPER_RIGHT: + return BLOCKED_UPPER_RIGHT_PROJ; + break; + case BLOCKED_UPPER_LEFT: + return BLOCKED_UPPER_LEFT_PROJ; + break; + case BLOCKED_LOWER_RIGHT: + return BLOCKED_LOWER_RIGHT_PROJ; + break; + case BLOCKED_LOWER_LEFT: + return BLOCKED_LOWER_LEFT_PROJ; + break; + case BLOCKED_TOP: + return BLOCKED_TOP_PROJ; + break; + } + return saberBlock; +} + +void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock ) +{ + vec3_t diff, fwdangles={0,0,0}, right; + float rightdot; + float zdiff; + + if ( self->client->ps.weaponstate == WEAPON_DROPPING || + self->client->ps.weaponstate == WEAPON_RAISING ) + {//don't block while changing weapons + return; + } + if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim ) + || PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) ) + { + return; + } + //NPCs don't auto-block + if ( !missileBlock && self->s.number != 0 && self->client->ps.saberBlocked != BLOCKED_NONE ) + { + return; + } + + VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff ); + diff[2] = 0; + VectorNormalize( diff ); + + fwdangles[1] = self->client->ps.viewangles[1]; + // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine. + AngleVectors( fwdangles, NULL, right, NULL ); + + rightdot = DotProduct(right, diff); + zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2]; + + //FIXME: take torsoAngles into account? + if ( zdiff > -5 )//0 )//40 ) + { + if ( rightdot > 0.3 ) + { + self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT; + } + else if ( rightdot < -0.3 ) + { + self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + } + else + { + self->client->ps.saberBlocked = BLOCKED_TOP; + } + } + else if ( zdiff > -22 )//-20 )//20 ) + { + if ( zdiff < -10 )//30 ) + {//hmm, pretty low, but not low enough to use the low block, so we need to duck + //NPC should duck, but NPC should never get here + } + if ( rightdot > 0.1 ) + { + self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT; + } + else if ( rightdot < -0.1 ) + { + self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + } + else + {//FIXME: this looks really weird if the shot is too low! + self->client->ps.saberBlocked = BLOCKED_TOP; + } + } + else + { + if ( rightdot >= 0 ) + { + self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + } + else + { + self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + } + } + +#ifndef FINAL_BUILD + if ( d_saberCombat->integer ) + { + if ( !self->s.number ) + { + gi.Printf( "EyeZ: %4.2f HitZ: %4.2f zdiff: %4.2f rdot: %4.2f\n", self->client->renderInfo.eyePoint[2], hitloc[2], zdiff, rightdot ); + switch ( self->client->ps.saberBlocked ) + { + case BLOCKED_TOP: + gi.Printf( "BLOCKED_TOP\n" ); + break; + case BLOCKED_UPPER_RIGHT: + gi.Printf( "BLOCKED_UPPER_RIGHT\n" ); + break; + case BLOCKED_UPPER_LEFT: + gi.Printf( "BLOCKED_UPPER_LEFT\n" ); + break; + case BLOCKED_LOWER_RIGHT: + gi.Printf( "BLOCKED_LOWER_RIGHT\n" ); + break; + case BLOCKED_LOWER_LEFT: + gi.Printf( "BLOCKED_LOWER_LEFT\n" ); + break; + default: + break; + } + } + } +#endif + + if ( missileBlock ) + { + self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked ); + } + + if ( self->client->ps.saberBlocked != BLOCKED_NONE ) + { + int parryReCalcTime = Jedi_ReCalcParryTime( self, EVASION_PARRY ); + if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime ) + { + self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime; + } + } +} + +void WP_SaberBlock( gentity_t *saber, vec3_t hitloc, qboolean missileBlock ) +{ + gentity_t *playerent; + vec3_t diff, fwdangles={0,0,0}, right; + float rightdot; + float zdiff; + + if (saber && saber->owner) + { + playerent = saber->owner; + if (!playerent->client) + { + return; + } + if ( playerent->client->ps.weaponstate == WEAPON_DROPPING || + playerent->client->ps.weaponstate == WEAPON_RAISING ) + {//don't block while changing weapons + return; + } + } + else + { // Bad entity passed. + return; + } + + //temporarily disabling auto-blocking for NPCs... + if ( !missileBlock && playerent->s.number != 0 && playerent->client->ps.saberBlocked != BLOCKED_NONE ) + { + return; + } + + if ( PM_SuperBreakLoseAnim( playerent->client->ps.torsoAnim ) ) + { + return; + } + + VectorSubtract(hitloc, playerent->currentOrigin, diff); + VectorNormalize(diff); + + fwdangles[1] = playerent->client->ps.viewangles[1]; + // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine. + AngleVectors( fwdangles, NULL, right, NULL ); + + rightdot = DotProduct(right, diff) + Q_flrand(-0.2f,0.2f); + zdiff = hitloc[2] - playerent->currentOrigin[2] + Q_irand(-8,8); + + // Figure out what quadrant the block was in. + if (zdiff > 24) + { // Attack from above + if (Q_irand(0,1)) + { + playerent->client->ps.saberBlocked = BLOCKED_TOP; + } + else + { + playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + } + } + else if (zdiff > 13) + { // The upper half has three viable blocks... + if (rightdot > 0.25) + { // In the right quadrant... + if (Q_irand(0,1)) + { + playerent->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + } + else + { + playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + } + } + else + { + switch(Q_irand(0,3)) + { + case 0: + playerent->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT; + break; + case 1: + case 2: + playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + break; + case 3: + playerent->client->ps.saberBlocked = BLOCKED_TOP; + break; + } + } + } + else + { // The lower half is a bit iffy as far as block coverage. Pick one of the "low" ones at random. + if (Q_irand(0,1)) + { + playerent->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + } + else + { + playerent->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + } + } + + if ( missileBlock ) + { + playerent->client->ps.saberBlocked = WP_MissileBlockForBlock( playerent->client->ps.saberBlocked ); + } +} + +void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd ) +{ + float dist; + gentity_t *ent, *incoming = NULL; + gentity_t *entityList[MAX_GENTITIES]; + int numListedEntities; + vec3_t mins, maxs; + int i, e; + float closestDist, radius = 256; + vec3_t forward, dir, missile_dir, fwdangles = {0}; + trace_t trace; + vec3_t traceTo, entDir; + qboolean dodgeOnlySabers = qfalse; + + + if ( self->NPC && (self->NPC->scriptFlags&SCF_IGNORE_ALERTS) ) + {//don't react to things flying at me... + return; + } + if ( self->health <= 0 ) + {//dead don't try to block (NOTE: actual deflection happens in missile code) + return; + } + + if ( PM_InKnockDown( &self->client->ps ) ) + {//can't block when knocked down + return; + } + + if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim ) + || PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) ) + {//can't block while in break anim + return; + } + + if ( Rosh_BeingHealed( self ) ) + { + return; + } + + if ( self->client->NPC_class == CLASS_ROCKETTROOPER ) + {//rockettrooper + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//must be in air + return; + } + if ( Q_irand( 0, 4-(g_spskill->integer*2) ) ) + {//easier level guys do this less + return; + } + if ( Q_irand( 0, 3 ) ) + {//base level: 25% chance of looking for something to dodge + if ( Q_irand( 0, 1 ) ) + {//dodge sabers twice as frequently as other projectiles + dodgeOnlySabers = qtrue; + } + else + { + return; + } + } + } + + if ( self->client->NPC_class == CLASS_BOBAFETT ) + {//Boba doesn't dodge quite as much + if ( Q_irand( 0, 2-g_spskill->integer) ) + {//easier level guys do this less + return; + } + } + + if ( self->client->NPC_class != CLASS_BOBAFETT + && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) + && (self->client->NPC_class != CLASS_ROCKETTROOPER||!self->NPC||self->NPC->rankinteger + && (ucmd->buttons & BUTTON_USE) + && cg.renderingThirdPerson + && G_OkayToLean( &self->client->ps, ucmd, qfalse ) + && (self->client->ps.forcePowersActive&(1<client->ps.weapon != WP_SABER ) + { + return; + } + + if ( self->client->ps.saberInFlight ) + { + return; + } + + if ( self->s.number < MAX_CLIENTS ) + { + if ( !self->client->ps.SaberLength() ) + {//player doesn't auto-activate + return; + } + + if ( !g_saberAutoBlocking->integer && self->client->ps.saberBlockingTimeclient->ps.saber[0].activeBlocking ) + {//can't actively block with this saber type + return; + } + } + + if ( !self->s.number ) + {//don't do this if already attacking! + if ( ucmd->buttons & BUTTON_ATTACK + || PM_SaberInAttack( self->client->ps.saberMove ) + || PM_SaberInSpecialAttack( self->client->ps.torsoAnim ) + || PM_SaberInTransitionAny( self->client->ps.saberMove )) + { + return; + } + } + + if ( self->client->ps.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_1 ) + {//you have not the SKILLZ + return; + } + + if ( self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] > level.time ) + {//can't block while already blocking + return; + } + + if ( self->client->ps.forcePowersActive&(1<client->ps.forcePowersActive&(1<client->ps.forcePowersActive&(1<client->ps.forcePowersActive&(1<client->ps.viewangles[1]; + AngleVectors( fwdangles, forward, NULL, NULL ); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = self->currentOrigin[i] - radius; + maxs[i] = self->currentOrigin[i] + radius; + } + + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + closestDist = radius; + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + + if (ent == self) + continue; + if (ent->owner == self) + continue; + if ( !(ent->inuse) ) + continue; + if ( dodgeOnlySabers ) + {//only care about thrown sabers + if ( ent->client + || ent->s.weapon != WP_SABER + || !ent->classname + || !ent->classname[0] + || Q_stricmp( "lightsaber", ent->classname ) ) + {//not a lightsaber, ignore it + continue; + } + } + if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) ) + {//not a normal projectile + if ( ent->client || ent->s.weapon != WP_SABER ) + {//FIXME: wake up bad guys? + continue; + } + if ( ent->s.eFlags & EF_NODRAW ) + { + continue; + } + if ( Q_stricmp( "lightsaber", ent->classname ) != 0 ) + {//not a lightsaber + //FIXME: what about general objects that are small in size- like rocks, etc... + continue; + } + //a lightsaber.. make sure it's on and inFlight + if ( !ent->owner || !ent->owner->client ) + { + continue; + } + if ( !ent->owner->client->ps.saberInFlight ) + {//not in flight + continue; + } + if ( ent->owner->client->ps.SaberLength() <= 0 ) + {//not on + continue; + } + if ( ent->owner->health <= 0 && g_saberRealisticCombat->integer < 2 ) + {//it's not doing damage, so ignore it + continue; + } + } + else + { + if ( ent->s.pos.trType == TR_STATIONARY && !self->s.number ) + {//nothing you can do with a stationary missile if you're the player + continue; + } + } + + float dot1, dot2; + //see if they're in front of me + VectorSubtract( ent->currentOrigin, self->currentOrigin, dir ); + dist = VectorNormalize( dir ); + //FIXME: handle detpacks, proximity mines and tripmines + if ( ent->s.weapon == WP_THERMAL ) + {//thermal detonator! + if ( self->NPC && dist < ent->splashRadius ) + { + if ( dist < ent->splashRadius && + ent->nextthink < level.time + 600 && + ent->count && + self->client->ps.groundEntityNum != ENTITYNUM_NONE && + (ent->s.pos.trType == TR_STATIONARY|| + ent->s.pos.trType == TR_INTERPOLATE|| + (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE|| + !WP_ForcePowerUsable( self, FP_PUSH, 0 )) ) + {//TD is close enough to hurt me, I'm on the ground and the thing is at rest or behind me and about to blow up, or I don't have force-push so force-jump! + //FIXME: sometimes this might make me just jump into it...? + self->client->ps.forceJumpCharge = 480; + } + else if ( self->client->NPC_class != CLASS_BOBAFETT + && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) + && self->client->NPC_class != CLASS_ROCKETTROOPER ) + {//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]] + if ( !ent->owner || !OnSameTeam( self, ent->owner ) ) + { + ForceThrow( self, qfalse ); + } + } + } + continue; + } + else if ( ent->splashDamage && ent->splashRadius ) + {//exploding missile + //FIXME: handle tripmines and detpacks somehow... + // maybe do a force-gesture that makes them explode? + // But what if we're within it's splashradius? + if ( !self->s.number ) + {//players don't auto-handle these at all + continue; + } + else + { + if ( self->client->NPC_class == CLASS_BOBAFETT + || self->client->NPC_class == CLASS_ROCKETTROOPER ) + { + /* + if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) ) + {//sorry, you're scrooged here + //FIXME: maybe jump or go up if on ground? + continue; + } + //else it's a rocket, try to evade it + */ + //HMM... let's see what happens if these guys try to avoid tripmines and detpacks, too...? + } + else + {//normal Jedi + if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) + && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) ) + {//a placed explosive like a tripmine or detpack + if ( InFOV( ent->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) ) + {//in front of me + if ( G_ClearLOS( self, ent ) ) + {//can see it + vec3_t throwDir; + //make the gesture + ForceThrow( self, qfalse ); + //take it off the wall and toss it + ent->s.pos.trType = TR_GRAVITY; + ent->s.eType = ET_MISSILE; + ent->s.eFlags &= ~EF_MISSILE_STICK; + ent->s.eFlags |= EF_BOUNCE_HALF; + AngleVectors( ent->currentAngles, throwDir, NULL, NULL ); + VectorMA( ent->currentOrigin, ent->maxs[0]+4, throwDir, ent->currentOrigin ); + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + VectorScale( throwDir, 300, ent->s.pos.trDelta ); + ent->s.pos.trDelta[2] += 150; + VectorMA( ent->s.pos.trDelta, 800, dir, ent->s.pos.trDelta ); + ent->s.pos.trTime = level.time; // move a bit on the very first frame + VectorCopy( ent->currentOrigin, ent->s.pos.trBase ); + ent->owner = self; + // make it explode, but with less damage + ent->splashDamage /= 3; + ent->splashRadius /= 3; + ent->e_ThinkFunc = thinkF_WP_Explode; + ent->nextthink = level.time + Q_irand( 500, 3000 ); + } + } + } + else if ( dist < ent->splashRadius + && self->client->ps.groundEntityNum != ENTITYNUM_NONE + && ( DotProduct( dir, forward ) < SABER_REFLECT_MISSILE_CONE + || !WP_ForcePowerUsable( self, FP_PUSH, 0 ) ) ) + {//NPCs try to evade it + self->client->ps.forceJumpCharge = 480; + } + else if ( (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) ) + {//else, try to force-throw it away + if ( !ent->owner || !OnSameTeam( self, ent->owner ) ) + { + //FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]] + ForceThrow( self, qfalse ); + } + } + //otherwise, can't block it, so we're screwed + continue; + } + } + } + + if ( ent->s.weapon != WP_SABER ) + {//only block shots coming from behind + if ( (dot1 = DotProduct( dir, forward )) < SABER_REFLECT_MISSILE_CONE ) + continue; + } + else if ( !self->s.number ) + {//player never auto-blocks thrown sabers + continue; + }//NPCs always try to block sabers coming from behind! + + //see if they're heading towards me + VectorCopy( ent->s.pos.trDelta, missile_dir ); + VectorNormalize( missile_dir ); + if ( (dot2 = DotProduct( dir, missile_dir )) > 0 ) + continue; + + //FIXME: must have a clear trace to me, too... + if ( dist < closestDist ) + { + VectorCopy( self->currentOrigin, traceTo ); + traceTo[2] = self->absmax[2] - 4; + gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask ); + if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) ) + {//okay, try one more check + VectorNormalize2( ent->s.pos.trDelta, entDir ); + VectorMA( ent->currentOrigin, radius, entDir, traceTo ); + gi.trace( &trace, ent->currentOrigin, ent->mins, ent->maxs, traceTo, ent->s.number, ent->clipmask ); + if ( trace.allsolid || trace.startsolid || (trace.fraction < 1.0f && trace.entityNum != self->s.number && trace.entityNum != self->client->ps.saberEntityNum) ) + {//can't hit me, ignore it + continue; + } + } + if ( self->s.number != 0 ) + {//An NPC + if ( self->NPC && !self->enemy && ent->owner ) + { + if ( ent->owner->health >= 0 && (!ent->owner->client || ent->owner->client->playerTeam != self->client->playerTeam) ) + { + G_SetEnemy( self, ent->owner ); + } + } + } + //FIXME: if NPC, predict the intersection between my current velocity/path and the missile's, see if it intersects my bounding box (+/-saberLength?), don't try to deflect unless it does? + closestDist = dist; + incoming = ent; + } + } + + if ( incoming ) + { + if ( self->NPC && !G_ControlledByPlayer( self ) ) + { + if ( Jedi_WaitingAmbush( self ) ) + { + Jedi_Ambush( self ); + } + if ( ( self->client->NPC_class == CLASS_BOBAFETT || self->client->NPC_class == CLASS_ROCKETTROOPER ) + && self->client->moveType == MT_FLYSWIM + && incoming->methodOfDeath != MOD_ROCKET_ALT ) + {//a hovering Boba Fett, not a tracking rocket + if ( !Q_irand( 0, 1 ) ) + {//strafe + self->NPC->standTime = 0; + self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 ); + } + if ( !Q_irand( 0, 1 ) ) + {//go up/down + TIMER_Set( self, "heightChange", Q_irand( 1000, 3000 ) ); + self->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 ); + } + } + else if ( self->client->NPC_class != CLASS_ROCKETTROOPER + && Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming ) != EVASION_NONE ) + {//make sure to turn on your saber if it's not on + if ( self->client->NPC_class != CLASS_BOBAFETT + && (self->client->NPC_class != CLASS_REBORN || self->s.weapon == WP_SABER) ) + { + self->client->ps.SaberActivate(); + } + } + } + else//player + { + if ( !(ucmd->buttons & BUTTON_USE) )//self->s.weapon == WP_SABER && self->client->ps.SaberActive() ) + { + WP_SaberBlockNonRandom( self, incoming->currentOrigin, qtrue ); + } + else + { + vec3_t diff, start, end; + float dist; + VectorSubtract( incoming->currentOrigin, self->currentOrigin, diff ); + dist = VectorLength( diff ); + VectorNormalize2( incoming->s.pos.trDelta, entDir ); + VectorMA( incoming->currentOrigin, dist, entDir, start ); + VectorCopy( self->currentOrigin, end ); + end[2] += self->maxs[2]*0.75f; + gi.trace( &trace, start, incoming->mins, incoming->maxs, end, incoming->s.number, MASK_SHOT, G2_COLLIDE, 10 ); + + Jedi_DodgeEvasion( self, incoming->owner, &trace, HL_NONE ); + } + if ( incoming->owner && incoming->owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters + { + self->enemy = incoming->owner; + NPC_SetLookTarget( self, incoming->owner->s.number, level.time+1000 ); + } + } + } +} + + +//GENERAL SABER============================================================================ +//GENERAL SABER============================================================================ +//GENERAL SABER============================================================================ +//GENERAL SABER============================================================================ +//GENERAL SABER============================================================================ + +void WP_SetSaberMove(gentity_t *self, short blocked) +{ + self->client->ps.saberBlocked = blocked; +} + +extern void CG_CubeOutline( vec3_t mins, vec3_t maxs, int time, unsigned int color, float alpha ); +void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd ) +{ + //float swap; + float minsize = 16; + + if(0)// if ( self->s.number != 0 ) + {//for now only the player can do this // not anymore + return; + } + + if ( !self->client ) + { + return; + } + + if ( self->client->ps.saberEntityNum < 0 || self->client->ps.saberEntityNum >= ENTITYNUM_WORLD ) + {//never got one + return; + } + + // Check if we are throwing it, launch it if needed, update position if needed. + WP_SaberThrow(self, ucmd); + + + //vec3_t saberloc; + //vec3_t sabermins={-8,-8,-8}, sabermaxs={8,8,8}; + + gentity_t *saberent; + + if (self->client->ps.saberEntityNum <= 0) + {//WTF?!! We lost it? + return; + } + + saberent = &g_entities[self->client->ps.saberEntityNum]; + + //FIXME: Based on difficulty level/jedi saber combat skill, make this bounding box fatter/smaller + if ( self->client->ps.saberBlocked != BLOCKED_NONE ) + {//we're blocking, increase min size + minsize = 32; + } + + if ( G_InCinematicSaberAnim( self ) ) + {//fake some blocking + self->client->ps.saberBlocking = BLK_TIGHT; + if ( self->client->ps.saber[0].Active() ) + { + self->client->ps.saber[0].ActivateTrail( 150 ); + } + if ( self->client->ps.saber[1].Active() ) + { + self->client->ps.saber[1].ActivateTrail( 150 ); + } + } + + //is our saber in flight? + if ( !self->client->ps.saberInFlight ) + { // It isn't, which means we can update its position as we will. + Vehicle_t *pVeh = G_IsRidingVehicle( self ); + if ( !self->client->ps.SaberActive() + || PM_InKnockDown( &self->client->ps ) + || PM_SuperBreakLoseAnim( self->client->ps.torsoAnim ) + || (pVeh && pVeh->m_pVehicleInfo && pVeh->m_pVehicleInfo->type != VH_ANIMAL && pVeh->m_pVehicleInfo->type != VH_FLIER) )//riding a vehicle that you cannot block shots on + {//can't block if saber isn't on + VectorClear(saberent->mins); + VectorClear(saberent->maxs); + G_SetOrigin(saberent, self->currentOrigin); + } + else if ( self->client->ps.saberBlocking == BLK_TIGHT || self->client->ps.saberBlocking == BLK_WIDE ) + {//FIXME: keep bbox in front of player, even when wide? + vec3_t saberOrg; + if ( ( (self->s.number&&!Jedi_SaberBusy(self)&&!g_saberRealisticCombat->integer) || (self->s.number == 0 && self->client->ps.saberBlocking == BLK_WIDE && (g_saberAutoBlocking->integer||self->client->ps.saberBlockingTime>level.time)) ) + && self->client->ps.weaponTime <= 0 + && !G_InCinematicSaberAnim( self ) ) + {//full-size blocking for non-attacking player with g_saberAutoBlocking on + vec3_t saberang={0,0,0}, fwd, sabermins={-8,-8,-8}, sabermaxs={8,8,8}; + + saberang[YAW] = self->client->ps.viewangles[YAW]; + AngleVectors( saberang, fwd, NULL, NULL ); + + VectorMA( self->currentOrigin, 12, fwd, saberOrg ); + + VectorAdd( self->mins, sabermins, saberent->mins ); + VectorAdd( self->maxs, sabermaxs, saberent->maxs ); + + saberent->contents = CONTENTS_LIGHTSABER; + + G_SetOrigin( saberent, saberOrg ); + } + else + { + vec3_t saberBase, saberTip; + int numSabers = 1; + if ( self->client->ps.dualSabers ) + { + numSabers = 2; + } + for ( int saberNum = 0; saberNum < numSabers; saberNum++ ) + { + for ( int bladeNum = 0; bladeNum < self->client->ps.saber[saberNum].numBlades; bladeNum++ ) + { + VectorCopy( self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint, saberBase ); + VectorMA( saberBase, self->client->ps.saber[saberNum].blade[bladeNum].length, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, saberTip ); + VectorMA( saberBase, self->client->ps.saber[saberNum].blade[bladeNum].length*0.5, self->client->ps.saber[saberNum].blade[bladeNum].muzzleDir, saberOrg ); + for ( int i = 0; i < 3; i++ ) + { + /* + if ( saberTip[i] > self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] ) + { + saberent->maxs[i] = saberTip[i] - saberOrg[i] + 8;//self->client->renderInfo.muzzlePoint[i]; + saberent->mins[i] = self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] - saberOrg[i] - 8; + } + else //if ( saberTip[i] < self->client->renderInfo.muzzlePoint[i] ) + { + saberent->maxs[i] = self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i] - saberOrg[i] + 8; + saberent->mins[i] = saberTip[i] - saberOrg[i] - 8;//self->client->ps.saber[saberNum].blade[bladeNum].muzzlePoint[i]; + } + */ + float newSizeTip = (saberTip[i] - saberOrg[i]); + newSizeTip += (newSizeTip>=0)?8:-8; + float newSizeBase = (saberBase[i] - saberOrg[i]); + newSizeBase += (newSizeBase>=0)?8:-8; + if ( newSizeTip > saberent->maxs[i] ) + { + saberent->maxs[i] = newSizeTip; + } + if ( newSizeBase > saberent->maxs[i] ) + { + saberent->maxs[i] = newSizeBase; + } + if ( newSizeTip < saberent->mins[i] ) + { + saberent->mins[i] = newSizeTip; + } + if ( newSizeBase < saberent->mins[i] ) + { + saberent->mins[i] = newSizeBase; + } + } + } + } + if ( self->client->ps.weaponTime > 0 + || self->s.number + || g_saberAutoBlocking->integer + || self->client->ps.saberBlockingTime > level.time ) + {//if attacking or blocking (or an NPC), inflate to a minimum size + for ( int i = 0; i < 3; i++ ) + { + if ( saberent->maxs[i] < minsize ) + { + saberent->maxs[i] = minsize; + } + if ( saberent->mins[i] > -minsize ) + { + saberent->mins[i] = -minsize; + } + } + } + saberent->contents = CONTENTS_LIGHTSABER; + G_SetOrigin( saberent, saberOrg ); + } + } + /* + else if (self->client->ps.saberBlocking == BLK_WIDE) + { // Assuming that we are not swinging, the saber's bounding box should be around the player. + vec3_t saberang={0,0,0}, fwd; + + saberang[YAW] = self->client->ps.viewangles[YAW]; + AngleVectors( saberang, fwd, NULL, NULL ); + + VectorMA(self->currentOrigin, 12, fwd, saberloc); + + VectorAdd(self->mins, sabermins, saberent->mins); + VectorAdd(self->maxs, sabermaxs, saberent->maxs); + + saberent->contents = CONTENTS_LIGHTSABER; + + G_SetOrigin( saberent, saberloc); + } + else if (self->client->ps.saberBlocking == BLK_TIGHT) + { // If the player is swinging, the bbox is around just the saber + VectorCopy(self->client->renderInfo.muzzlePoint, sabermins); + // Put the limits of the bbox around the saber size. + VectorMA(sabermins, self->client->ps.saberLength, self->client->renderInfo.muzzleDir, sabermaxs); + + // Now make the mins into mins and the maxs into maxs + if (sabermins[0] > sabermaxs[0]) + { + swap = sabermins[0]; + sabermins[0] = sabermaxs[0]; + sabermaxs[0] = swap; + } + if (sabermins[1] > sabermaxs[1]) + { + swap = sabermins[1]; + sabermins[1] = sabermaxs[1]; + sabermaxs[1] = swap; + } + if (sabermins[2] > sabermaxs[2]) + { + swap = sabermins[2]; + sabermins[2] = sabermaxs[2]; + sabermaxs[2] = swap; + } + + // Now the loc is halfway between the (absolute) mins and maxs + VectorAdd(sabermins, sabermaxs, saberloc); + VectorScale(saberloc, 0.5, saberloc); + + // Finally, turn the mins and maxs, which are absolute, into relative mins and maxs. + VectorSubtract(sabermins, saberloc, saberent->mins); + VectorSubtract(sabermaxs, saberloc, saberent->maxs); + + saberent->contents = CONTENTS_LIGHTSABER;// | CONTENTS_SHOTCLIP; + + G_SetOrigin( saberent, saberloc); + } + */ + else + { // Otherwise there is no blocking possible. + VectorClear(saberent->mins); + VectorClear(saberent->maxs); + G_SetOrigin(saberent, self->currentOrigin); + } + saberent->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + gi.linkentity(saberent); + } + else + { + WP_SaberInFlightReflectCheck( self, ucmd ); + } +#ifndef FINAL_BUILD + if ( d_saberCombat->integer > 2 ) + { + CG_CubeOutline( saberent->absmin, saberent->absmax, 50, WPDEBUG_SaberColor( self->client->ps.saber[0].blade[0].color ), 1 ); + } +#endif +} + +#define MAX_RADIUS_ENTS 256 //NOTE: This can cause entities to be lost +qboolean G_CheckEnemyPresence( gentity_t *ent, int dir, float radius, float tolerance ) +{ + gentity_t *radiusEnts[ MAX_RADIUS_ENTS ]; + vec3_t mins, maxs; + int numEnts; + vec3_t checkDir, dir2checkEnt; + float dist; + + switch( dir ) + { + case DIR_RIGHT: + AngleVectors( ent->currentAngles, NULL, checkDir, NULL ); + break; + case DIR_LEFT: + AngleVectors( ent->currentAngles, NULL, checkDir, NULL ); + VectorScale( checkDir, -1, checkDir ); + break; + case DIR_FRONT: + AngleVectors( ent->currentAngles, checkDir, NULL, NULL ); + break; + case DIR_BACK: + AngleVectors( ent->currentAngles, checkDir, NULL, NULL ); + VectorScale( checkDir, -1, checkDir ); + break; + } + //Get all ents in range, see if they're living clients and enemies, then check dot to them... + + //Setup the bbox to search in + for ( int i = 0; i < 3; i++ ) + { + mins[i] = ent->currentOrigin[i] - radius; + maxs[i] = ent->currentOrigin[i] + radius; + } + + //Get a number of entities in a given space + numEnts = gi.EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS ); + + for ( i = 0; i < numEnts; i++ ) + { + //Don't consider self + if ( radiusEnts[i] == ent ) + continue; + + //Must be valid + if ( G_ValidEnemy( ent, radiusEnts[i] ) == qfalse ) + continue; + + VectorSubtract( radiusEnts[i]->currentOrigin, ent->currentOrigin, dir2checkEnt ); + dist = VectorNormalize( dir2checkEnt ); + if ( dist <= radius + && DotProduct( dir2checkEnt, checkDir ) >= tolerance ) + { + //stop on the first one + return qtrue; + } + } + + return qfalse; +} +//OTHER JEDI POWERS========================================================================= +//OTHER JEDI POWERS========================================================================= +//OTHER JEDI POWERS========================================================================= +//OTHER JEDI POWERS========================================================================= +//OTHER JEDI POWERS========================================================================= +extern gentity_t *TossClientItems( gentity_t *self ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +void WP_DropWeapon( gentity_t *dropper, vec3_t velocity ) +{ + if ( !dropper || !dropper->client ) + { + return; + } + int replaceWeap = WP_NONE; + int oldWeap = dropper->s.weapon; + gentity_t *weapon = TossClientItems( dropper ); + if ( oldWeap == WP_THERMAL && dropper->NPC ) + {//Hmm, maybe all NPCs should go into melee? Not too many, though, or they mob you and look silly + replaceWeap = WP_MELEE; + } + if ( dropper->ghoul2.IsValid() ) + { + if ( dropper->weaponModel[0] > 0 ) + {//NOTE: guess you never drop the left-hand weapon, eh? + gi.G2API_RemoveGhoul2Model( dropper->ghoul2, dropper->weaponModel[0] ); + dropper->weaponModel[0] = -1; + } + } + //FIXME: does this work on the player? + dropper->client->ps.stats[STAT_WEAPONS] |= ( 1 << replaceWeap ); + if ( !dropper->s.number ) + { + if ( oldWeap == WP_THERMAL ) + { + dropper->client->ps.ammo[weaponData[oldWeap].ammoIndex] -= weaponData[oldWeap].energyPerShot; + } + else + { + dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap ); + } + CG_ChangeWeapon( replaceWeap ); + } + else + { + dropper->client->ps.stats[STAT_WEAPONS] &= ~( 1 << oldWeap ); + } + ChangeWeapon( dropper, replaceWeap ); + dropper->s.weapon = replaceWeap; + if ( dropper->NPC ) + { + dropper->NPC->last_ucmd.weapon = replaceWeap; + } + if ( weapon != NULL && velocity && !VectorCompare( velocity, vec3_origin ) ) + {//weapon should have a direction to it's throw + VectorScale( velocity, 3, weapon->s.pos.trDelta );//NOTE: Presumes it is moving already...? + if ( weapon->s.pos.trDelta[2] < 150 ) + {//this is presuming you don't want them to drop the weapon down on you... + weapon->s.pos.trDelta[2] = 150; + } + //FIXME: gets stuck inside it's former owner... + weapon->forcePushTime = level.time + 600; // let the push effect last for 600 ms + } +} + +void WP_KnockdownTurret( gentity_t *self, gentity_t *pas ) +{ + //knock it over + VectorCopy( pas->currentOrigin, pas->s.pos.trBase ); + pas->s.pos.trType = TR_LINEAR_STOP; + pas->s.pos.trDuration = 250; + pas->s.pos.trTime = level.time; + pas->s.pos.trDelta[2] = ( 12.0f / ( pas->s.pos.trDuration * 0.001f ) ); + + VectorCopy( pas->currentAngles, pas->s.apos.trBase ); + pas->s.apos.trType = TR_LINEAR_STOP; + pas->s.apos.trDuration = 250; + pas->s.apos.trTime = level.time; + //FIXME: pick pitch/roll that always tilts it directly away from pusher + pas->s.apos.trDelta[PITCH] = ( 100.0f / ( pas->s.apos.trDuration * 0.001f ) ); + + //kill it + pas->count = 0; + pas->nextthink = -1; + G_Sound( pas, G_SoundIndex( "sound/chars/turret/shutdown.wav" )); + //push effect? + pas->forcePushTime = level.time + 600; // let the push effect last for 600 ms +} + +void WP_ForceThrowHazardTrooper( gentity_t *self, gentity_t *trooper, qboolean pull ) +{ + if ( !self || !self->client ) + { + return; + } + if ( !trooper || !trooper->client ) + { + return; + } + + //all levels: see effect on them, they notice us + trooper->forcePushTime = level.time + 600; // let the push effect last for 600 ms + + if ( (pull&&self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_1) + || (!pull&&self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_1) ) + {//level 2: they stop for a couple seconds and make a sound + trooper->painDebounceTime = level.time + Q_irand( 1500, 2500 ); + G_AddVoiceEvent( trooper, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 1000, 3000 ) ); + GEntity_PainFunc( trooper, self, self, trooper->currentOrigin, 0, MOD_MELEE ); + + if ( (pull&&self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_2) + || (!pull&&self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_2) ) + {//level 3: they actually play a pushed anim and stumble a bit + vec3_t hazAngles = {0,trooper->currentAngles[YAW],0}; + int anim = -1; + if ( InFront( self->currentOrigin, trooper->currentOrigin, hazAngles ) ) + {//I'm on front of him + if ( pull ) + { + anim = BOTH_PAIN4; + } + else + { + anim = BOTH_PAIN1; + } + } + else + {//I'm behind him + if ( pull ) + { + anim = BOTH_PAIN1; + } + else + { + anim = BOTH_PAIN4; + } + } + if ( anim != -1 ) + { + if ( anim == BOTH_PAIN1 ) + {//make them take a couple steps back + AngleVectors( hazAngles, trooper->client->ps.velocity, NULL, NULL ); + VectorScale( trooper->client->ps.velocity, -40.0f, trooper->client->ps.velocity ); + trooper->client->ps.pm_flags |= PMF_TIME_NOFRICTION; + } + else if ( anim == BOTH_PAIN4 ) + {//make them stumble forward + AngleVectors( hazAngles, trooper->client->ps.velocity, NULL, NULL ); + VectorScale( trooper->client->ps.velocity, 80.0f, trooper->client->ps.velocity ); + trooper->client->ps.pm_flags |= PMF_TIME_NOFRICTION; + } + NPC_SetAnim( trooper, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + trooper->painDebounceTime += trooper->client->ps.torsoAnimTimer; + trooper->client->ps.pm_time = trooper->client->ps.torsoAnimTimer; + } + } + if ( trooper->NPC ) + { + if ( trooper->NPC->shotTime < trooper->painDebounceTime ) + { + trooper->NPC->shotTime = trooper->painDebounceTime; + } + } + trooper->client->ps.weaponTime = trooper->painDebounceTime-level.time; + } + else + {//level 1: no pain reaction, but they should still notice + if ( trooper->enemy == NULL//not mad at anyone + && trooper->client->playerTeam != self->client->playerTeam//not on our team + && !(trooper->svFlags&SVF_LOCKEDENEMY)//not locked on an enemy + && !(trooper->svFlags&SVF_IGNORE_ENEMIES)//not ignoring enemie + && !(self->flags&FL_NOTARGET) )//I'm not in notarget + {//not already mad at them and can get mad at them, do so + G_SetEnemy( trooper, self ); + } + } +} + +void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty ) +{ + int parts; + qboolean runningResist = qfalse; + + if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client ) + { + return; + } + if ( (!self->s.number + ||( self->NPC && (self->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) + ||( self->client && self->client->NPC_class == CLASS_SHADOWTROOPER ) + /* + || self->client->NPC_class == CLASS_DESANN + || !Q_stricmp("Yoda",self->NPC_type) + || self->client->NPC_class == CLASS_LUKE*/ + ) + && (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) ) + { + runningResist = qtrue; + } + if ( !runningResist + && self->client->ps.groundEntityNum != ENTITYNUM_NONE + && !PM_SpinningSaberAnim( self->client->ps.legsAnim ) + && !PM_FlippingAnim( self->client->ps.legsAnim ) + && !PM_RollingAnim( self->client->ps.legsAnim ) + && !PM_InKnockDown( &self->client->ps ) + && !PM_CrouchAnim( self->client->ps.legsAnim )) + {//if on a surface and not in a spin or flip, play full body resist + parts = SETANIM_BOTH; + } + else + {//play resist just in torso + parts = SETANIM_TORSO; + } + //FIXME: don't interrupt big anims with this! + NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( !noPenalty ) + { + if ( !runningResist ) + { + VectorClear( self->client->ps.velocity ); + //still stop them from attacking or moving for a bit, though + //FIXME: maybe push just a little (like, slide)? + self->client->ps.weaponTime = 1000; + if ( self->client->ps.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value ); + } + self->client->ps.pm_time = self->client->ps.weaponTime; + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + //play the full body push effect on me + self->forcePushTime = level.time + 600; // let the push effect last for 600 ms + } + else + { + self->client->ps.weaponTime = 600; + if ( self->client->ps.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value ); + } + } + } + //play my force push effect on my hand + //self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500; + + //reset to 0 in case it's still > 0 from a previous push + //self->client->pushEffectFadeTime = 0; + if ( !pusher //??? + || pusher == self->enemy//my enemy tried to push me + || (pusher->client && pusher->client->playerTeam != self->client->playerTeam) )//someone not on my team tried to push me + { + Jedi_PlayBlockedPushSound( self ); + } +} + +extern qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir, qboolean forceKnockdown ); +extern qboolean Jedi_StopKnockdown( gentity_t *self, gentity_t *pusher, const vec3_t pushDir ); +void WP_ForceKnockdown( gentity_t *self, gentity_t *pusher, qboolean pull, qboolean strongKnockdown, qboolean breakSaberLock ) +{ + if ( !self || !self->client || !pusher || !pusher->client ) + { + return; + } + + if ( self->client->NPC_class == CLASS_ROCKETTROOPER ) + { + return; + } + else if ( PM_LockedAnim( self->client->ps.legsAnim ) ) + {//stuck doing something else + return; + } + else if ( Rosh_BeingHealed( self ) ) + { + return; + } + + //break out of a saberLock? + if ( self->client->ps.saberLockTime > level.time ) + { + if ( breakSaberLock + || (pusher && self->client->ps.saberLockEnemy == pusher->s.number) ) + { + self->client->ps.saberLockTime = 0; + self->client->ps.saberLockEnemy = ENTITYNUM_NONE; + } + else + { + return; + } + } + + if ( self->health > 0 ) + { + if ( !self->s.number ) + { + NPC_SetPainEvent( self ); + } + else + { + GEntity_PainFunc( self, pusher, pusher, self->currentOrigin, 0, MOD_MELEE ); + } + + vec3_t pushDir; + if ( pull ) + { + VectorSubtract( pusher->currentOrigin, self->currentOrigin, pushDir ); + } + else + { + VectorSubtract( self->currentOrigin, pusher->currentOrigin, pushDir ); + } + + //FIXME: sometimes do this for some NPC force-users, too! + if ( Boba_StopKnockdown( self, pusher, pushDir, qtrue ) ) + {//He can backflip instead of be knocked down + return; + } + else if ( Jedi_StopKnockdown( self, pusher, pushDir ) ) + {//They can backflip instead of be knocked down + return; + } + + G_CheckLedgeDive( self, 72, pushDir, qfalse, qfalse ); + + if ( !PM_SpinningSaberAnim( self->client->ps.legsAnim ) + && !PM_FlippingAnim( self->client->ps.legsAnim ) + && !PM_RollingAnim( self->client->ps.legsAnim ) + && !PM_InKnockDown( &self->client->ps ) ) + { + int knockAnim = BOTH_KNOCKDOWN1;//default knockdown + if ( pusher->client->NPC_class == CLASS_DESANN && self->client->NPC_class != CLASS_LUKE ) + {//desann always knocks down, unless you're Luke + strongKnockdown = qtrue; + } + if ( !self->s.number + && !strongKnockdown + && ( (!pull&&(self->client->ps.forcePowerLevel[FP_PUSH]>FORCE_LEVEL_1||!g_spskill->integer)) || (pull&&(self->client->ps.forcePowerLevel[FP_PULL]>FORCE_LEVEL_1||!g_spskill->integer)) ) ) + {//player only knocked down if pushed *hard* + if ( self->s.weapon == WP_SABER ) + {//temp HACK: these are the only 2 pain anims that look good when holding a saber + knockAnim = PM_PickAnim( self, BOTH_PAIN2, BOTH_PAIN3 ); + } + else + { + knockAnim = PM_PickAnim( self, BOTH_PAIN1, BOTH_PAIN18 ); + } + } + else if ( PM_CrouchAnim( self->client->ps.legsAnim ) ) + {//crouched knockdown + knockAnim = BOTH_KNOCKDOWN4; + } + else + {//plain old knockdown + vec3_t pLFwd, pLAngles = {0,self->client->ps.viewangles[YAW],0}; + vec3_t sFwd, sAngles = {0,pusher->client->ps.viewangles[YAW],0}; + AngleVectors( pLAngles, pLFwd, NULL, NULL ); + AngleVectors( sAngles, sFwd, NULL, NULL ); + if ( DotProduct( sFwd, pLFwd ) > 0.2f ) + {//pushing him from behind + //FIXME: check to see if we're aiming below or above the waist? + if ( pull ) + { + knockAnim = BOTH_KNOCKDOWN1; + } + else + { + knockAnim = BOTH_KNOCKDOWN3; + } + } + else + {//pushing him from front + if ( pull ) + { + knockAnim = BOTH_KNOCKDOWN3; + } + else + { + knockAnim = BOTH_KNOCKDOWN1; + } + } + } + if ( knockAnim == BOTH_KNOCKDOWN1 && strongKnockdown ) + {//push *hard* + knockAnim = BOTH_KNOCKDOWN2; + } + NPC_SetAnim( self, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( self->s.number >= MAX_CLIENTS ) + {//randomize getup times - but not for boba + int addTime; + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + addTime = Q_irand( -500, 0 ); + } + else + { + addTime = Q_irand( -300, 300 ); + } + self->client->ps.legsAnimTimer += addTime; + self->client->ps.torsoAnimTimer += addTime; + } + else + {//player holds extra long so you have more time to decide to do the quick getup + if ( PM_KnockDownAnim( self->client->ps.legsAnim ) ) + { + self->client->ps.legsAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + self->client->ps.torsoAnimTimer += PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME; + } + } + // + if ( pusher->NPC && pusher->enemy == self ) + {//pushed pushed down his enemy + G_AddVoiceEvent( pusher, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 ); + pusher->NPC->blockedSpeechDebounceTime = level.time + 3000; + } + } + } + self->forcePushTime = level.time + 600; // let the push effect last for 600 ms +} + +qboolean WP_ForceThrowable( gentity_t *ent, gentity_t *forwardEnt, gentity_t *self, qboolean pull, float cone, float radius, vec3_t forward ) +{ + if (ent == self) + return qfalse; + if ( ent->owner == self && ent->s.weapon != WP_THERMAL )//can push your own thermals + return qfalse; + if ( !(ent->inuse) ) + return qfalse; + if ( ent->NPC && ent->NPC->scriptFlags & SCF_NO_FORCE ) + { + if ( ent->s.weapon == WP_SABER ) + {//Hmm, should jedi do the resist behavior? If this is on, perhaps it's because of a cinematic? + WP_ResistForcePush( ent, self, qtrue ); + } + return qfalse; + } + if ( (ent->flags&FL_FORCE_PULLABLE_ONLY) && !pull ) + {//simple HACK: cannot force-push ammo rack items (because they may start in solid) + return qfalse; + } + //FIXME: don't push it if I already pushed it a little while ago + if ( ent->s.eType != ET_MISSILE ) + { + if ( ent->client ) + { + if ( ent->client->ps.pullAttackTime > level.time ) + { + return qfalse; + } + } + if ( cone >= 1.0f ) + {//must be pointing right at them + if ( ent != forwardEnt ) + {//must be the person I'm looking right at + if ( ent->client && !pull + && ent->client->ps.forceGripEntityNum == self->s.number + && (self->s.eFlags&EF_FORCE_GRIPPED) ) + {//this is the guy that's force-gripping me, use a wider cone regardless of force power level + } + else + { + if ( ent->client && !pull + && ent->client->ps.forceDrainEntityNum == self->s.number + && (self->s.eFlags&EF_FORCE_DRAINED) ) + {//this is the guy that's force-draining me, use a wider cone regardless of force power level + } + else + { + return qfalse; + } + } + } + } + if ( ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject )//|| !(ent->flags&FL_DROPPED_ITEM) )//was only dropped items + { + //FIXME: need pushable objects + if ( ent->s.eFlags & EF_NODRAW ) + { + return qfalse; + } + if ( !ent->client ) + { + if ( Q_stricmp( "lightsaber", ent->classname ) != 0 ) + {//not a lightsaber + if ( !(ent->svFlags&SVF_GLASS_BRUSH) ) + {//and not glass + if ( Q_stricmp( "func_door", ent->classname ) != 0 || !(ent->spawnflags & 2/*MOVER_FORCE_ACTIVATE*/) ) + {//not a force-usable door + if ( Q_stricmp( "func_static", ent->classname ) != 0 || (!(ent->spawnflags&1/*F_PUSH*/)&&!(ent->spawnflags&2/*F_PULL*/)) || (ent->spawnflags&32/*SOLITARY*/) ) + {//not a force-usable func_static or, it is one, but it's solitary, so you only press it when looking right at it + if ( Q_stricmp( "limb", ent->classname ) ) + {//not a limb + if ( ent->s.weapon == WP_TURRET && !Q_stricmp( "PAS", ent->classname ) && ent->s.apos.trType == TR_STATIONARY ) + {//can knock over placed turrets + if ( !self->s.number || self->enemy != ent ) + {//only NPCs who are actively mad at this turret can push it over + return qfalse; + } + } + else + { + return qfalse; + } + } + } + } + else if ( ent->moverState != MOVER_POS1 && ent->moverState != MOVER_POS2 ) + {//not at rest + return qfalse; + } + } + } + //return qfalse; + } + else if ( ent->client->NPC_class == CLASS_MARK1 ) + {//can't push Mark1 unless push 3 + if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 ) + { + return qfalse; + } + } + else if ( ent->client->NPC_class == CLASS_GALAKMECH + || ent->client->NPC_class == CLASS_ATST + || ent->client->NPC_class == CLASS_RANCOR + || ent->client->NPC_class == CLASS_WAMPA + || ent->client->NPC_class == CLASS_SAND_CREATURE ) + {//can't push ATST or Galak or Rancor or Wampa + return qfalse; + } + else if ( ent->s.weapon == WP_EMPLACED_GUN ) + {//FIXME: maybe can pull them out? + return qfalse; + } + else if ( ent->client->playerTeam == self->client->playerTeam && self->enemy && self->enemy != ent ) + {//can't accidently push a teammate while in combat + return qfalse; + } + else if ( G_IsRidingVehicle( ent ) + && (ent->s.eFlags&EF_NODRAW) ) + {//can't push/pull anyone riding *inside* vehicle + return qfalse; + } + } + else if ( ent->s.eType == ET_ITEM + && ent->item + && ent->item->giType == IT_HOLDABLE + && ent->item->giTag == INV_SECURITY_KEY ) + //&& (ent->flags&FL_DROPPED_ITEM) ??? + {//dropped security keys can't be pushed? But placed ones can...? does this make any sense? + if ( !pull || self->s.number ) + {//can't push, NPC's can't do anything to it + return qfalse; + } + else + { + if ( g_crosshairEntNum != ent->s.number ) + {//player can pull it if looking *right* at it + if ( cone >= 1.0f ) + {//we did a forwardEnt trace + if ( forwardEnt != ent ) + {//must be pointing right at them + return qfalse; + } + } + else if ( forward ) + {//do a forwardEnt trace + trace_t tr; + vec3_t end; + VectorMA( self->client->renderInfo.eyePoint, radius, forward, end ); + gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE );//was MASK_SHOT, changed to match crosshair trace + if ( tr.entityNum != ent->s.number ) + {//last chance + return qfalse; + } + } + } + } + } + } + else + { + switch ( ent->s.weapon ) + {//only missiles with mass are force-pushable + case WP_SABER: + case WP_FLECHETTE: + case WP_ROCKET_LAUNCHER: + case WP_CONCUSSION: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + break; + //only alt-fire of this weapon is force-pushable + case WP_REPEATER: + if ( ent->methodOfDeath != MOD_REPEATER_ALT ) + {//not an alt-fire missile + return qfalse; + } + break; + //everything else cannot be pushed + case WP_ATST_SIDE: + if ( ent->methodOfDeath != MOD_EXPLOSIVE ) + {//not a rocket + return qfalse; + } + break; + default: + return qfalse; + break; + } + + if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) ) + {//can't force-push/pull stuck missiles (detpacks, tripmines) + return qfalse; + } + if ( ent->s.pos.trType == TR_STATIONARY && ent->s.weapon != WP_THERMAL ) + {//only thermal detonators can be pushed once stopped + return qfalse; + } + } + return qtrue; +} + +bool dontPillarPush = false; +void ForceThrow( gentity_t *self, qboolean pull, qboolean fake ) +{//FIXME: pass in a target ent so we (an NPC) can push/pull just one targeted ent. + //shove things in front of you away + float dist; + gentity_t *ent, *forwardEnt = NULL; + gentity_t *entityList[MAX_GENTITIES]; + gentity_t *push_list[MAX_GENTITIES]; + int numListedEntities = 0; + vec3_t mins, maxs; + vec3_t v; + int i, e; + int ent_count = 0; + int radius; + vec3_t center, ent_org, size, forward, right, end, dir, fwdangles = {0}; + float dot1, cone; + trace_t tr; + int anim, hold, soundIndex, cost, actualCost; + qboolean noResist = qfalse; + +#ifdef _IMMERSION + int forceIndex; +#endif // _IMMERSION + if ( self->health <= 0 ) + { + return; + } + if ( self->client->ps.leanofs ) + {//can't force-throw while leaning + return; + } + if ( self->client->ps.forcePowerDebounce[FP_PUSH] > level.time ) + {//already pushing- now you can't haul someone across the room, sorry + return; + } + if ( self->client->ps.forcePowerDebounce[FP_PULL] > level.time ) + {//already pulling- now you can't haul someone across the room, sorry + return; + } + if ( self->client->ps.pullAttackTime > level.time ) + {//already pull-attacking + return; + } + if ( !self->s.number && (cg.zoomMode || in_camera) ) + {//can't force throw/pull when zoomed in or in cinematic + return; + } + if ( self->client->ps.saberLockTime > level.time ) + { + if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 ) + {//this can be a way to break out + return; + } + //else, I'm breaking my half of the saberlock + self->client->ps.saberLockTime = 0; + self->client->ps.saberLockEnemy = ENTITYNUM_NONE; + } + + if ( self->client->ps.legsAnim == BOTH_KNOCKDOWN3 + || (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F1 && self->client->ps.torsoAnimTimer > 400) + || (self->client->ps.torsoAnim == BOTH_FORCE_GETUP_F2 && self->client->ps.torsoAnimTimer > 900) + || (self->client->ps.torsoAnim == BOTH_GETUP3 && self->client->ps.torsoAnimTimer > 500) + || (self->client->ps.torsoAnim == BOTH_GETUP4 && self->client->ps.torsoAnimTimer > 300) + || (self->client->ps.torsoAnim == BOTH_GETUP5 && self->client->ps.torsoAnimTimer > 500) ) + {//we're face-down, so we'd only be force-push/pulling the floor + return; + } + if ( pull ) + { + radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PULL]]; + } + else + { + radius = forcePushPullRadius[self->client->ps.forcePowerLevel[FP_PUSH]]; + } + + if ( !radius ) + {//no ability to do this yet + return; + } + + if ( pull ) + { + cost = forcePowerNeeded[FP_PULL]; + if ( !WP_ForcePowerUsable( self, FP_PULL, cost ) ) + { + return; + } + //make sure this plays and that you cannot press fire for about 200ms after this + anim = BOTH_FORCEPULL; + soundIndex = G_SoundIndex( "sound/weapons/force/pull.wav" ); +#ifdef _IMMERSION + forceIndex = G_ForceIndex( "fffx/weapons/force/pull", FF_CHANNEL_WEAPON ); +#endif // _IMMERSION + hold = 200; + } + else + { + cost = forcePowerNeeded[FP_PUSH]; + if ( !WP_ForcePowerUsable( self, FP_PUSH, cost ) ) + { + return; + } + //make sure this plays and that you cannot press fire for about 1 second after this + anim = BOTH_FORCEPUSH; + soundIndex = G_SoundIndex( "sound/weapons/force/push.wav" ); +#ifdef _IMMERSION + forceIndex = G_ForceIndex( "fffx/weapons/force/push", FF_CHANNEL_WEAPON ); +#endif // _IMMERSION + hold = 650; + } + + int parts = SETANIM_TORSO; + if ( !PM_InKnockDown( &self->client->ps ) ) + { + if ( self->client->ps.saberLockTime > level.time ) + { + self->client->ps.saberLockTime = 0; + self->painDebounceTime = level.time + 2000; + hold += 1000; + parts = SETANIM_BOTH; + } + else if ( !VectorLengthSquared( self->client->ps.velocity ) && !(self->client->ps.pm_flags&PMF_DUCKED)) + { + parts = SETANIM_BOTH; + } + } + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART ); + self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + self->client->ps.saberBlocked = BLOCKED_NONE; + if ( self->client->ps.forcePowersActive&(1<value ); + } + self->client->ps.weaponTime = hold;//was 1000, but want to swing sooner + //do effect... FIXME: build-up or delay this until in proper part of anim + self->client->ps.powerups[PW_FORCE_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500; + //reset to 0 in case it's still > 0 from a previous push + self->client->pushEffectFadeTime = 0; + + G_Sound( self, soundIndex ); +#ifdef _IMMERSION + G_Force( self, forceIndex ); +#endif // _IMMERSION + + if ( (!pull && self->client->ps.forcePowersForced&(1<client->ps.forcePowersForced&(1<client->NPC_class==CLASS_KYLE&&(self->spawnflags&1)&&TIMER_Done( self, "kyleTakesSaber" )) ) + { + noResist = qtrue; + } + + VectorCopy( self->client->ps.viewangles, fwdangles ); + //fwdangles[1] = self->client->ps.viewangles[1]; + AngleVectors( fwdangles, forward, right, NULL ); + VectorCopy( self->currentOrigin, center ); + + if ( pull ) + { + cone = forcePullCone[self->client->ps.forcePowerLevel[FP_PULL]]; + } + else + { + cone = forcePushCone[self->client->ps.forcePowerLevel[FP_PUSH]]; + } + + // if ( cone >= 1.0f ) + {//must be pointing right at them + VectorMA( self->client->renderInfo.eyePoint, radius, forward, end ); + gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE );//was MASK_SHOT, changed to match crosshair trace + if ( tr.entityNum < ENTITYNUM_WORLD ) + {//found something right in front of self, + forwardEnt = &g_entities[tr.entityNum]; + if ( !forwardEnt->client && !Q_stricmp( "func_static", forwardEnt->classname ) ) + { + if ( (forwardEnt->spawnflags&1/*F_PUSH*/)||(forwardEnt->spawnflags&2/*F_PULL*/) ) + {//push/pullable + if ( (forwardEnt->spawnflags&32/*SOLITARY*/) ) + {//can only push/pull ME, ignore all others + if ( forwardEnt->NPC_targetname == NULL + || (self->targetname&&Q_stricmp( forwardEnt->NPC_targetname, self->targetname ) == 0) ) + {//anyone can push it or only 1 person can push it and it's me + push_list[0] = forwardEnt; + ent_count = numListedEntities = 1; + } + } + } + } + } + } + + if ( forwardEnt ) + { + if ( G_TryingPullAttack( self, &self->client->usercmd, qtrue ) ) + {//we're going to try to do a pull attack on our forwardEnt + if ( WP_ForceThrowable( forwardEnt, forwardEnt, self, pull, cone, radius, forward ) ) + {//we will actually pull-attack him, so don't pull him or anything else here + //activate the power, here, though, so the later check that actually does the pull attack knows we tried to pull + self->client->ps.forcePowersActive |= (1<client->ps.forcePowerDebounce[FP_PULL] = level.time + 100; //force-pulling + return; + } + } + } + + if ( !numListedEntities ) + { + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = entityList[ e ]; + + if ( !WP_ForceThrowable( ent, forwardEnt, self, pull, cone, radius, forward ) ) + { + continue; + } + + //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter + // find the distance from the edge of the bounding box + for ( i = 0 ; i < 3 ; i++ ) + { + if ( center[i] < ent->absmin[i] ) + { + v[i] = ent->absmin[i] - center[i]; + } else if ( center[i] > ent->absmax[i] ) + { + v[i] = center[i] - ent->absmax[i]; + } else + { + v[i] = 0; + } + } + + VectorSubtract( ent->absmax, ent->absmin, size ); + VectorMA( ent->absmin, 0.5, size, ent_org ); + + //see if they're in front of me + VectorSubtract( ent_org, center, dir ); + VectorNormalize( dir ); + if ( cone < 1.0f ) + {//must be within the forward cone + if ( ent->client && !pull + && ent->client->ps.forceGripEntityNum == self->s.number + && (self->s.eFlags&EF_FORCE_GRIPPED) ) + {//this is the guy that's force-gripping me, use a wider cone regardless of force power level + if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f ) + continue; + } + else if ( ent->client && !pull + && ent->client->ps.forceDrainEntityNum == self->s.number + && (self->s.eFlags&EF_FORCE_DRAINED) ) + {//this is the guy that's force-draining me, use a wider cone regardless of force power level + if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f ) + continue; + } + else if ( ent->s.eType == ET_MISSILE )//&& ent->s.eType != ET_ITEM && ent->e_ThinkFunc != thinkF_G_RunObject ) + {//missiles are easier to force-push, never require direct trace (FIXME: maybe also items and general physics objects) + if ( (dot1 = DotProduct( dir, forward )) < cone-0.3f ) + continue; + } + else if ( (dot1 = DotProduct( dir, forward )) < cone ) + { + continue; + } + } + else if ( ent->s.eType == ET_MISSILE ) + {//a missile and we're at force level 1... just use a small cone, but not ridiculously small + if ( (dot1 = DotProduct( dir, forward )) < 0.75f ) + { + continue; + } + }//else is an NPC or brush entity that our forward trace would have to hit + + dist = VectorLength( v ); + + //Now check and see if we can actually deflect it + //method1 + //if within a certain range, deflect it + if ( ent->s.eType == ET_MISSILE && cone >= 1.0f ) + {//smaller radius on missile checks at force push 1 + if ( dist >= 192 ) + { + continue; + } + } + else if ( dist >= radius ) + { + continue; + } + + //in PVS? + if ( !ent->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.eyePoint ) ) + {//must be in PVS + continue; + } + + if ( ent != forwardEnt ) + {//don't need to trace against forwardEnt again + //really should have a clear LOS to this thing... + gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_FORCE_PUSH );//was MASK_SHOT, but changed to match above trace and crosshair trace + if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number ) + {//must have clear LOS + continue; + } + } + + // ok, we are within the radius, add us to the incoming list + push_list[ent_count] = ent; + ent_count++; + } + } + + if ( ent_count ) + { + for ( int x = 0; x < ent_count; x++ ) + { + if ( push_list[x]->client ) + { + vec3_t pushDir; + float knockback = pull?0:200; + + //SIGH band-aid... + if ( push_list[x]->s.number >= MAX_CLIENTS + && self->s.number < MAX_CLIENTS ) + { + if ( (push_list[x]->client->ps.forcePowersActive&(1<client->ps.forcePowerDebounce[FP_GRIP] < level.time + && push_list[x]->client->ps.forceGripEntityNum == self->s.number ) + { + WP_ForcePowerStop( push_list[x], FP_GRIP ); + } + if ( (push_list[x]->client->ps.forcePowersActive&(1<client->ps.forcePowerDebounce[FP_DRAIN] < level.time + && push_list[x]->client->ps.forceDrainEntityNum == self->s.number ) + { + WP_ForcePowerStop( push_list[x], FP_DRAIN ); + } + } + + if ( Rosh_BeingHealed( push_list[x] ) ) + { + continue; + } + if ( push_list[x]->client->NPC_class == CLASS_HAZARD_TROOPER + && push_list[x]->health > 0 ) + {//living hazard troopers resist push/pull + WP_ForceThrowHazardTrooper( self, push_list[x], pull ); + continue; + } + if ( fake ) + {//always resist + WP_ResistForcePush( push_list[x], self, qfalse ); + continue; + } +//FIXMEFIXMEFIXMEFIXMEFIXME: extern a lot of this common code when I have the time!!! + int powerLevel, powerUse; + if (pull) + { + powerLevel = self->client->ps.forcePowerLevel[FP_PULL]; + powerUse = FP_PULL; + } + else + { + powerLevel = self->client->ps.forcePowerLevel[FP_PUSH]; + powerUse = FP_PUSH; + } + int modPowerLevel = WP_AbsorbConversion( push_list[x], push_list[x]->client->ps.forcePowerLevel[FP_ABSORB], self, powerUse, powerLevel, forcePowerNeeded[self->client->ps.forcePowerLevel[powerUse]] ); + if (push_list[x]->client->NPC_class==CLASS_ASSASSIN_DROID || + push_list[x]->client->NPC_class==CLASS_HAZARD_TROOPER) + { + modPowerLevel = 0; // devides throw by 10 + } + + //First, if this is the player we're push/pulling, see if he can counter it + if ( modPowerLevel != -1 + && !noResist + && InFront( self->currentOrigin, push_list[x]->client->renderInfo.eyePoint, push_list[x]->client->ps.viewangles, 0.3f ) ) + {//absorbed and I'm in front of them + //counter it + if ( push_list[x]->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_2 ) + {//no reaction at all + } + else + { + WP_ResistForcePush( push_list[x], self, qfalse ); + push_list[x]->client->ps.saberMove = push_list[x]->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + push_list[x]->client->ps.saberBlocked = BLOCKED_NONE; + } + continue; + } + else if ( !push_list[x]->s.number ) + {//player + if ( !noResist + && push_list[x]->health > 0 //alive + && push_list[x]->client //client + && push_list[x]->client->ps.forceRageRecoveryTime < level.time //not recobering from rage + && push_list[x]->client->ps.torsoAnim != BOTH_FORCEGRIP_HOLD// BOTH_FORCEGRIP1//wasn't trying to grip anyone + //&& push_list[x]->client->ps.torsoAnim != BOTH_HUGGER1// wasn't trying to grip-drain anyone + && push_list[x]->client->ps.torsoAnim != BOTH_FORCE_DRAIN_GRAB_START// wasn't trying to grip-drain anyone + && push_list[x]->client->ps.torsoAnim != BOTH_FORCE_DRAIN_GRAB_HOLD// wasn't trying to grip-drain anyone + && ((self->client->NPC_class != CLASS_DESANN&&Q_stricmp("Yoda",self->NPC_type)) || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push + && push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE//on the ground + && !PM_InKnockDown( &push_list[x]->client->ps )//not knocked down already + && push_list[x]->client->ps.saberLockTime < level.time//not involved in a saberLock + && push_list[x]->client->ps.weaponTime < level.time//not attacking or otherwise busy + && (push_list[x]->client->ps.weapon == WP_SABER||push_list[x]->client->ps.weapon == WP_MELEE) )//using saber or fists + {//trying to push or pull the player! + if ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time//player was pushing/pulling too + ||( pull && Q_irand( 0, (push_list[x]->client->ps.forcePowerLevel[FP_PULL] - self->client->ps.forcePowerLevel[FP_PULL])*2+1 ) > 0 )//player's pull is high enough + ||( !pull && Q_irand( 0, (push_list[x]->client->ps.forcePowerLevel[FP_PUSH] - self->client->ps.forcePowerLevel[FP_PUSH])*2+1 ) > 0 ) )//player's push is high enough + {//player's force push/pull is high enough to try to stop me + if ( InFront( self->currentOrigin, push_list[x]->client->renderInfo.eyePoint, push_list[x]->client->ps.viewangles, 0.3f ) ) + {//I'm in front of player + WP_ResistForcePush( push_list[x], self, qfalse ); + push_list[x]->client->ps.saberMove = push_list[x]->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + push_list[x]->client->ps.saberBlocked = BLOCKED_NONE; + continue; + } + } + } + } + else if ( push_list[x]->client && Jedi_WaitingAmbush( push_list[x] ) ) + { + WP_ForceKnockdown( push_list[x], self, pull, qtrue, qfalse ); + continue; + } + + G_KnockOffVehicle( push_list[x], self, pull ); + + if ( !pull + && push_list[x]->client->ps.forceDrainEntityNum == self->s.number + && (self->s.eFlags&EF_FORCE_DRAINED) ) + {//stop them from draining me now, dammit! + WP_ForcePowerStop( push_list[x], FP_DRAIN ); + } + + //okay, everyone else (or player who couldn't resist it)... + if ( ((self->s.number == 0 && Q_irand( 0, 2 ) ) || Q_irand( 0, 2 ) ) && push_list[x]->client && push_list[x]->health > 0 //a living client + && push_list[x]->client->ps.weapon == WP_SABER //Jedi + && push_list[x]->health > 0 //alive + && push_list[x]->client->ps.forceRageRecoveryTime < level.time //not recobering from rage + && ((self->client->NPC_class != CLASS_DESANN&&Q_stricmp("Yoda",self->NPC_type)) || !Q_irand( 0, 2 ) )//only 30% chance of resisting a Desann push + && push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE //on the ground + && InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.3f ) //I'm in front of him + && ( push_list[x]->client->ps.powerups[PW_FORCE_PUSH] > level.time ||//he's pushing too + (push_list[x]->s.number != 0 && push_list[x]->client->ps.weaponTime < level.time)//not the player and not attacking (NPC jedi auto-defend against pushes) + ) + ) + {//Jedi don't get pushed, they resist as long as they aren't already attacking and are on the ground + if ( push_list[x]->client->ps.saberLockTime > level.time ) + {//they're in a lock + if ( push_list[x]->client->ps.saberLockEnemy != self->s.number ) + {//they're not in a lock with me + continue; + } + else if ( pull || self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 || + push_list[x]->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 ) + {//they're in a lock with me, but my push is too weak + continue; + } + else + {//we will knock them down + self->painDebounceTime = 0; + self->client->ps.weaponTime = 500; + if ( self->client->ps.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value ); + } + } + } + int resistChance = Q_irand(0, 2); + if ( push_list[x]->s.number >= MAX_CLIENTS ) + {//NPC + if ( g_spskill->integer == 1 ) + {//stupid tweak for graham + resistChance = Q_irand(0, 3); + } + } + if ( noResist || + ( !pull + && modPowerLevel == -1 + && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 + && !resistChance + && push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_3 ) + ) + {//a level 3 push can even knock down a jedi + if ( PM_InKnockDown( &push_list[x]->client->ps ) ) + {//can't knock them down again + continue; + } + WP_ForceKnockdown( push_list[x], self, pull, qfalse, qtrue ); + } + else + { + WP_ResistForcePush( push_list[x], self, qfalse ); + } + } + else + { + //UGH: FIXME: for enemy jedi, they should probably always do force pull 3, and not your weapon (if player?)! + //shove them + if ( push_list[x]->NPC + && push_list[x]->NPC->jumpState == JS_JUMPING ) + {//don't interrupt a scripted jump + //WP_ResistForcePush( push_list[x], self, qfalse ); + push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms + continue; + } + + if ( push_list[x]->s.number + && (push_list[x]->message || (push_list[x]->flags&FL_NO_KNOCKBACK)) ) + {//an NPC who has a key + //don't push me... FIXME: maybe can pull the key off me? + WP_ForceKnockdown( push_list[x], self, pull, qfalse, qfalse ); + continue; + } + if ( pull ) + { + VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir ); + if ( self->client->ps.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 + && self->client->NPC_class == CLASS_KYLE + && (self->spawnflags&1) + && TIMER_Done( self, "kyleTakesSaber" ) + && push_list[x]->client + && push_list[x]->client->ps.weapon == WP_SABER + && !push_list[x]->client->ps.saberInFlight + && push_list[x]->client->ps.saberEntityNum < ENTITYNUM_WORLD + && !PM_InOnGroundAnim( &push_list[x]->client->ps ) ) + { + vec3_t throwVec; + VectorScale( pushDir, 10.0f, throwVec ); + WP_SaberLose( push_list[x], throwVec ); + NPC_SetAnim( push_list[x], SETANIM_BOTH, BOTH_LOSE_SABER, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + push_list[x]->client->ps.torsoAnimTimer += 500; + push_list[x]->client->ps.pm_time = push_list[x]->client->ps.weaponTime = push_list[x]->client->ps.torsoAnimTimer; + push_list[x]->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + push_list[x]->client->ps.saberMove = LS_NONE; + push_list[x]->aimDebounceTime = level.time + push_list[x]->client->ps.torsoAnimTimer; + VectorClear( push_list[x]->client->ps.velocity ); + VectorClear( push_list[x]->client->ps.moveDir ); + //Kyle will stand around for a bit, too... + self->client->ps.pm_time = self->client->ps.weaponTime = 2000; + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + self->painDebounceTime = level.time + self->client->ps.weaponTime; + TIMER_Set( self, "kyleTakesSaber", Q_irand( 60000, 180000 ) );//don't do this again for a while + G_AddVoiceEvent( self, Q_irand(EV_TAUNT1,EV_TAUNT3), Q_irand( 4000, 6000 ) ); + VectorClear( self->client->ps.velocity ); + VectorClear( self->client->ps.moveDir ); + continue; + } + else if ( push_list[x]->NPC + && (push_list[x]->NPC->scriptFlags&SCF_DONT_FLEE) ) + {//*SIGH*... if an NPC can't flee, they can't run after and pick up their weapon, do don't drop it + } + else if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_1 + && push_list[x]->client->NPC_class != CLASS_ROCKETTROOPER//rockettroopers never drop their weapon + && push_list[x]->client->NPC_class != CLASS_VEHICLE + && push_list[x]->client->NPC_class != CLASS_BOBAFETT + && push_list[x]->client->NPC_class != CLASS_TUSKEN + && push_list[x]->client->NPC_class != CLASS_HAZARD_TROOPER + && push_list[x]->client->NPC_class != CLASS_ASSASSIN_DROID + && push_list[x]->s.weapon != WP_SABER + && push_list[x]->s.weapon != WP_MELEE + && push_list[x]->s.weapon != WP_THERMAL + && push_list[x]->s.weapon != WP_CONCUSSION // so rax can't drop his + ) + {//yank the weapon - NOTE: level 1 just knocks them down, not take weapon + //FIXME: weapon yank anim if not a knockdown? + if ( InFront( self->currentOrigin, push_list[x]->currentOrigin, push_list[x]->client->ps.viewangles, 0.0f ) ) + {//enemy has to be facing me, too... + WP_DropWeapon( push_list[x], pushDir ); + } + } + knockback += VectorNormalize( pushDir ); + if ( knockback > 200 ) + { + knockback = 200; + } + if ( self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_3 ) + {//maybe just knock them down + knockback /= 3; + } + } + else + { + VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir ); + knockback -= VectorNormalize( pushDir ); + if ( knockback < 100 ) + { + knockback = 100; + } + //scale for push level + if ( self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 ) + {//maybe just knock them down + knockback /= 3; + } + else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 ) + {//super-hard push + //Hmm, maybe in this case can even nudge/knockdown a jedi? Especially if close? + //knockback *= 5; + } + } + + if ( modPowerLevel != -1 ) + { + if ( !modPowerLevel ) + { + knockback /= 10.0f; + } + else if ( modPowerLevel == 1 ) + { + knockback /= 6.0f; + } + else// if ( modPowerLevel == 2 ) + { + knockback /= 2.0f; + } + } + //actually push/pull the enemy + G_Throw( push_list[x], pushDir, knockback ); + //make it so they don't actually hurt me when pulled at me... + push_list[x]->forcePuller = self->s.number; + + if ( push_list[x]->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//if on the ground, make sure they get shoved up some + if ( push_list[x]->client->ps.velocity[2] < knockback ) + { + push_list[x]->client->ps.velocity[2] = knockback; + } + } + + if ( push_list[x]->health > 0 ) + {//target is still alive + if ( (push_list[x]->s.number||(cg.renderingThirdPerson&&!cg.zoomMode)) //NPC or 3rd person player + && ((!pull&&self->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1) //level 1 push + || (pull && self->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_2 && push_list[x]->client->ps.forcePowerLevel[FP_PULL] < FORCE_LEVEL_1)) )//level 1 pull + {//NPC or third person player (without force push/pull skill), and force push/pull level is at 1 + WP_ForceKnockdown( push_list[x], self, pull, (!pull&&knockback>150), qfalse ); + } + else if ( !push_list[x]->s.number ) + {//player, have to force an anim on him + WP_ForceKnockdown( push_list[x], self, pull, (!pull&&knockback>150), qfalse ); + } + else + {//NPC and force-push/pull at level 2 or higher + WP_ForceKnockdown( push_list[x], self, pull, (!pull&&knockback>100), qfalse ); + } + } + push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms + } + } + else if ( !fake ) + {//not a fake push/pull + if ( push_list[x]->s.weapon == WP_SABER && (push_list[x]->contents&CONTENTS_LIGHTSABER) ) + {//a thrown saber, just send it back + /* + if ( pull ) + {//steal it? + } + else */if ( push_list[x]->owner && push_list[x]->owner->client && push_list[x]->owner->client->ps.SaberActive() && push_list[x]->s.pos.trType == TR_LINEAR && push_list[x]->owner->client->ps.saberEntityState != SES_RETURNING ) + {//it's on and being controlled + //FIXME: prevent it from damaging me? + if ( self->s.number == 0 || Q_irand( 0, 2 ) ) + {//certain chance of throwing it aside and turning it off? + //give it some velocity away from me + //FIXME: maybe actually push or pull it? + if ( Q_irand( 0, 1 ) ) + { + VectorScale( right, -1, right ); + } + G_ReflectMissile( self, push_list[x], right ); + //FIXME: isn't turning off!!! + WP_SaberDrop( push_list[x]->owner, push_list[x] ); + } + else + { + WP_SaberReturn( push_list[x]->owner, push_list[x] ); + } + //different effect? + } + } + else if ( push_list[x]->s.eType == ET_MISSILE + && push_list[x]->s.pos.trType != TR_STATIONARY + && (push_list[x]->s.pos.trType != TR_INTERPOLATE||push_list[x]->s.weapon != WP_THERMAL) )//rolling and stationary thermal detonators are dealt with below + { + vec3_t dir2Me; + VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, dir2Me ); + float dot = DotProduct( push_list[x]->s.pos.trDelta, dir2Me ); + if ( pull ) + {//deflect rather than reflect? + } + else + { + if ( push_list[x]->s.eFlags&EF_MISSILE_STICK ) + {//caught a sticky in-air + push_list[x]->s.eType = ET_MISSILE; + push_list[x]->s.eFlags &= ~EF_MISSILE_STICK; + push_list[x]->s.eFlags |= EF_BOUNCE_HALF; + push_list[x]->splashDamage /= 3; + push_list[x]->splashRadius /= 3; + push_list[x]->e_ThinkFunc = thinkF_WP_Explode; + push_list[x]->nextthink = level.time + Q_irand( 500, 3000 ); + } + if ( dot >= 0 ) + {//it's heading towards me + G_ReflectMissile( self, push_list[x], forward ); + } + else + { + VectorScale( push_list[x]->s.pos.trDelta, 1.25f, push_list[x]->s.pos.trDelta ); + } + //deflect sound + //G_Sound( push_list[x], G_SoundIndex( va("sound/weapons/blaster/reflect%d.wav", Q_irand( 1, 3 ) ) ) ); + //push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms + } + if ( push_list[x]->s.eType == ET_MISSILE + && push_list[x]->s.weapon == WP_ROCKET_LAUNCHER + && push_list[x]->damage < 60 ) + {//pushing away a rocket raises it's damage to the max for NPCs + push_list[x]->damage = 60; + } + } + else if ( push_list[x]->svFlags & SVF_GLASS_BRUSH ) + {//break the glass + trace_t tr; + vec3_t pushDir; + float damage = 800; + + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + VectorNormalize( forward ); + VectorMA( self->client->renderInfo.eyePoint, radius, forward, end ); + gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT ); + if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid ) + {//must be pointing right at it + continue; + } + + if ( pull ) + { + VectorSubtract( self->client->renderInfo.eyePoint, tr.endpos, pushDir ); + } + else + { + VectorSubtract( tr.endpos, self->client->renderInfo.eyePoint, pushDir ); + } + /* + VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size ); + VectorMA( push_list[x]->absmin, 0.5, size, center ); + if ( pull ) + { + VectorSubtract( self->client->renderInfo.eyePoint, center, pushDir ); + } + else + { + VectorSubtract( center, self->client->renderInfo.eyePoint, pushDir ); + } + */ + damage -= VectorNormalize( pushDir ); + if ( damage < 200 ) + { + damage = 200; + } + VectorScale( pushDir, damage, pushDir ); + + G_Damage( push_list[x], self, self, pushDir, tr.endpos, damage, 0, MOD_UNKNOWN ); + } + else if ( !Q_stricmp( "func_static", push_list[x]->classname ) ) + {//force-usable func_static + if ( !pull && (push_list[x]->spawnflags&1/*F_PUSH*/) ) + { + if ( push_list[x]->NPC_targetname == NULL + || (self->targetname&&Q_stricmp( push_list[x]->NPC_targetname, self->targetname ) == 0) ) + {//anyone can pull it or only 1 person can push it and it's me + if(push_list[x]->behaviorSet[BSET_USE] && + !Q_stricmp(push_list[x]->behaviorSet[BSET_USE], + "kor2/pillar_push")) { + if(!dontPillarPush) { + GEntity_UseFunc( push_list[x], self, self ); + } + dontPillarPush = true; + } else { + GEntity_UseFunc( push_list[x], self, self ); + } + } + } + else if ( pull && (push_list[x]->spawnflags&2/*F_PULL*/) ) + { + if ( push_list[x]->NPC_targetname == NULL + || (self->targetname&&Q_stricmp( push_list[x]->NPC_targetname, self->NPC_targetname ) == 0) ) + {//anyone can push it or only 1 person can push it and it's me + GEntity_UseFunc( push_list[x], self, self ); + } + } + } + else if ( !Q_stricmp( "func_door", push_list[x]->classname ) && (push_list[x]->spawnflags&2/*MOVER_FORCE_ACTIVATE*/) ) + {//push/pull the door + vec3_t pos1, pos2; + + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + VectorNormalize( forward ); + VectorMA( self->client->renderInfo.eyePoint, radius, forward, end ); + gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT ); + if ( tr.entityNum != push_list[x]->s.number || tr.fraction == 1.0 || tr.allsolid || tr.startsolid ) + {//must be pointing right at it + continue; + } + + if ( VectorCompare( vec3_origin, push_list[x]->s.origin ) ) + {//does not have an origin brush, so pos1 & pos2 are relative to world origin, need to calc center + VectorSubtract( push_list[x]->absmax, push_list[x]->absmin, size ); + VectorMA( push_list[x]->absmin, 0.5, size, center ); + if ( (push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS1 ) + {//if at pos1 and started open, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2 + VectorSubtract( center, push_list[x]->pos1, center ); + } + else if ( !(push_list[x]->spawnflags&1) && push_list[x]->moverState == MOVER_POS2 ) + {//if at pos2, make sure we get the center where it *started* because we're going to add back in the relative values pos1 and pos2 + VectorSubtract( center, push_list[x]->pos2, center ); + } + VectorAdd( center, push_list[x]->pos1, pos1 ); + VectorAdd( center, push_list[x]->pos2, pos2 ); + } + else + {//actually has an origin, pos1 and pos2 are absolute + VectorCopy( push_list[x]->currentOrigin, center ); + VectorCopy( push_list[x]->pos1, pos1 ); + VectorCopy( push_list[x]->pos2, pos2 ); + } + + if ( Distance( pos1, self->client->renderInfo.eyePoint ) < Distance( pos2, self->client->renderInfo.eyePoint ) ) + {//pos1 is closer + if ( push_list[x]->moverState == MOVER_POS1 ) + {//at the closest pos + if ( pull ) + {//trying to pull, but already at closest point, so screw it + continue; + } + } + else if ( push_list[x]->moverState == MOVER_POS2 ) + {//at farthest pos + if ( !pull ) + {//trying to push, but already at farthest point, so screw it + continue; + } + } + } + else + {//pos2 is closer + if ( push_list[x]->moverState == MOVER_POS1 ) + {//at the farthest pos + if ( !pull ) + {//trying to push, but already at farthest point, so screw it + continue; + } + } + else if ( push_list[x]->moverState == MOVER_POS2 ) + {//at closest pos + if ( pull ) + {//trying to pull, but already at closest point, so screw it + continue; + } + } + } + GEntity_UseFunc( push_list[x], self, self ); + } + else if ( push_list[x]->s.eType == ET_MISSILE/*thermal resting on ground*/ + || push_list[x]->s.eType == ET_ITEM + || push_list[x]->e_ThinkFunc == thinkF_G_RunObject || Q_stricmp( "limb", push_list[x]->classname ) == 0 ) + {//general object, toss it + vec3_t pushDir, kvel; + float knockback = pull?0:200; + float mass = 200; + + if ( pull ) + { + if ( push_list[x]->s.eType == ET_ITEM ) + {//pull it to a little higher point + vec3_t adjustedOrg; + VectorCopy( self->currentOrigin, adjustedOrg ); + adjustedOrg[2] += self->maxs[2]/3; + VectorSubtract( adjustedOrg, push_list[x]->currentOrigin, pushDir ); + } + else if ( self->enemy //I have an enemy + //&& push_list[x]->s.eType != ET_ITEM //not an item + && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 //have push 3 or greater + && InFront(push_list[x]->currentOrigin, self->currentOrigin, self->currentAngles, 0.25f)//object is generally in front of me + && InFront(self->enemy->currentOrigin, self->currentOrigin, self->currentAngles, 0.75f)//enemy is pretty much right in front of me + && !InFront(push_list[x]->currentOrigin, self->enemy->currentOrigin, self->enemy->currentAngles, -0.25f)//object is generally behind enemy + //FIXME: check dist to enemy and clear LOS to enemy and clear Path between object and enemy? + && ( (self->NPC&&(noResist||Q_irand(0,RANK_CAPTAIN)NPC->rank) )//NPC with enough skill + ||( self->s.numbermass > TARGETED_OBJECT_PUSH_MASS_MAX ) + {//already pushed too many things + //FIXME: pick closest? + continue; + } + targetedObjectMassTotal += push_list[x]->mass; + */ + VectorSubtract( self->enemy->currentOrigin, push_list[x]->currentOrigin, pushDir ); + } + else + { + VectorSubtract( self->currentOrigin, push_list[x]->currentOrigin, pushDir ); + } + knockback += VectorNormalize( pushDir ); + if ( knockback > 200 ) + { + knockback = 200; + } + if ( push_list[x]->s.eType == ET_ITEM + && push_list[x]->item + && push_list[x]->item->giType == IT_HOLDABLE + && push_list[x]->item->giTag == INV_SECURITY_KEY ) + {//security keys are pulled with less enthusiasm + if ( knockback > 100 ) + { + knockback = 100; + } + } + else if ( knockback > 200 ) + { + knockback = 200; + } + } + else + { + if ( self->enemy //I have an enemy + && push_list[x]->s.eType != ET_ITEM //not an item + && self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 //have push 3 or greater + && InFront(push_list[x]->currentOrigin, self->currentOrigin, self->currentAngles, 0.25f)//object is generally in front of me + && InFront(self->enemy->currentOrigin, self->currentOrigin, self->currentAngles, 0.75f)//enemy is pretty much right in front of me + && InFront(push_list[x]->currentOrigin, self->enemy->currentOrigin, self->enemy->currentAngles, 0.25f)//object is generally in front of enemy + //FIXME: check dist to enemy and clear LOS to enemy and clear Path between object and enemy? + && ( (self->NPC&&(noResist||Q_irand(0,RANK_CAPTAIN)NPC->rank) )//NPC with enough skill + ||( self->s.numbermass > TARGETED_OBJECT_PUSH_MASS_MAX ) + {//already pushed too many things + //FIXME: pick closest? + continue; + } + targetedObjectMassTotal += push_list[x]->mass; + */ + VectorSubtract( self->enemy->currentOrigin, push_list[x]->currentOrigin, pushDir ); + } + else + { + VectorSubtract( push_list[x]->currentOrigin, self->currentOrigin, pushDir ); + } + knockback -= VectorNormalize( pushDir ); + if ( knockback < 100 ) + { + knockback = 100; + } + } + //FIXME: if pull a FL_FORCE_PULLABLE_ONLY, clear the flag, assuming it's no longer in solid? or check? + VectorCopy( push_list[x]->currentOrigin, push_list[x]->s.pos.trBase ); + push_list[x]->s.pos.trTime = level.time; // move a bit on the very first frame + if ( push_list[x]->s.pos.trType != TR_INTERPOLATE ) + {//don't do this to rolling missiles + push_list[x]->s.pos.trType = TR_GRAVITY; + } + + if ( push_list[x]->e_ThinkFunc == thinkF_G_RunObject && push_list[x]->physicsBounce ) + {//it's a pushable misc_model_breakable, use it's mass instead of our one-size-fits-all mass + mass = push_list[x]->physicsBounce;//same as push_list[x]->mass, right? + } + if ( mass < 50 ) + {//??? + mass = 50; + } + if ( g_gravity->value > 0 ) + { + VectorScale( pushDir, g_knockback->value * knockback / mass * 0.8, kvel ); + kvel[2] = pushDir[2] * g_knockback->value * knockback / mass * 1.5; + } + else + { + VectorScale( pushDir, g_knockback->value * knockback / mass, kvel ); + } + VectorAdd( push_list[x]->s.pos.trDelta, kvel, push_list[x]->s.pos.trDelta ); + if ( g_gravity->value > 0 ) + { + if ( push_list[x]->s.pos.trDelta[2] < knockback ) + { + push_list[x]->s.pos.trDelta[2] = knockback; + } + } + //no trDuration? + if ( push_list[x]->e_ThinkFunc != thinkF_G_RunObject ) + {//objects spin themselves? + //spin it + //FIXME: messing with roll ruins the rotational center??? + push_list[x]->s.apos.trTime = level.time; + push_list[x]->s.apos.trType = TR_LINEAR; + VectorClear( push_list[x]->s.apos.trDelta ); + push_list[x]->s.apos.trDelta[1] = Q_irand( -800, 800 ); + } + + if ( Q_stricmp( "limb", push_list[x]->classname ) == 0 ) + {//make sure it runs it's physics + push_list[x]->e_ThinkFunc = thinkF_LimbThink; + push_list[x]->nextthink = level.time + FRAMETIME; + } + push_list[x]->forcePushTime = level.time + 600; // let the push effect last for 600 ms + push_list[x]->forcePuller = self->s.number;//remember this regardless + if ( push_list[x]->item && push_list[x]->item->giTag == INV_SECURITY_KEY ) + { + AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_DISCOVERED );//security keys are more important + } + else + { + AddSightEvent( player, push_list[x]->currentOrigin, 128, AEL_SUSPICIOUS );//hmm... or should this always be discovered? + } + } + else if ( push_list[x]->s.weapon == WP_TURRET + && !Q_stricmp( "PAS", push_list[x]->classname ) + && push_list[x]->s.apos.trType == TR_STATIONARY ) + {//a portable turret + WP_KnockdownTurret( self, push_list[x] ); + } + } + } + if ( pull ) + { + if ( self->client->ps.forcePowerLevel[FP_PULL] > FORCE_LEVEL_2 ) + {//at level 3, can pull multiple, so it costs more + actualCost = forcePowerNeeded[FP_PULL]*ent_count; + if ( actualCost > 50 ) + { + actualCost = 50; + } + else if ( actualCost < cost ) + { + actualCost = cost; + } + } + else + { + actualCost = cost; + } + WP_ForcePowerStart( self, FP_PULL, actualCost ); + } + else + { + if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 ) + {//at level 3, can push multiple, so costs more + actualCost = forcePowerNeeded[FP_PUSH]*ent_count; + if ( actualCost > 50 ) + { + actualCost = 50; + } + else if ( actualCost < cost ) + { + actualCost = cost; + } + } + else if ( self->client->ps.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_1 ) + {//at level 2, can push multiple, so costs more + actualCost = floor(forcePowerNeeded[FP_PUSH]*ent_count/1.5f); + if ( actualCost > 50 ) + { + actualCost = 50; + } + else if ( actualCost < cost ) + { + actualCost = cost; + } + } + else + { + actualCost = cost; + } + WP_ForcePowerStart( self, FP_PUSH, actualCost ); + } + } + else + {//didn't push or pull anything? don't penalize them too much + if ( pull ) + { + WP_ForcePowerStart( self, FP_PULL, 5 ); + } + else + { + WP_ForcePowerStart( self, FP_PUSH, 5 ); + } + } + if ( pull ) + { + if ( self->NPC ) + {//NPCs can push more often + //FIXME: vary by rank and game skill? + self->client->ps.forcePowerDebounce[FP_PULL] = level.time + 200; + } + else + { + self->client->ps.forcePowerDebounce[FP_PULL] = level.time + self->client->ps.torsoAnimTimer + 500; + } + } + else + { + if ( self->NPC ) + {//NPCs can push more often + //FIXME: vary by rank and game skill? + self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + 200; + } + else + { + self->client->ps.forcePowerDebounce[FP_PUSH] = level.time + self->client->ps.torsoAnimTimer + 500; + } + } +} + +void WP_DebounceForceDeactivateTime( gentity_t *self ) +{ + //FIXME: if these are interruptable, should they also drain power at a constant rate + // rather than just taking one lump sum of force power upfront? + if ( self && self->client ) + { + if ( self->client->ps.forcePowersActive&(1<client->ps.forcePowersActive&(1<client->ps.forcePowersActive&(1<client->ps.forcePowersActive&(1<client->ps.forcePowersActive&(1<client->ps.forceAllowDeactivateTime = level.time + 500; + } + else + {//not running one of the interruptable powers + //FIXME: this should be shorter for force speed and rage (because of timescaling) + self->client->ps.forceAllowDeactivateTime = level.time + 1500; + } + } +} + +void ForceSpeed( gentity_t *self, int duration ) +{ + if ( self->health <= 0 ) + { + return; + } + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.forcePowersActive & (1 << FP_SPEED)) ) + {//stop using it + WP_ForcePowerStop( self, FP_SPEED ); + return; + } + if ( !WP_ForcePowerUsable( self, FP_SPEED, 0 ) ) + { + return; + } + if ( self->client->ps.saberLockTime > level.time ) + {//FIXME: can this be a way to break out? + return; + } + + WP_DebounceForceDeactivateTime( self ); + + WP_ForcePowerStart( self, FP_SPEED, 0 ); + if ( duration ) + { + self->client->ps.forcePowerDuration[FP_SPEED] = level.time + duration; + } + G_Sound( self, G_SoundIndex( "sound/weapons/force/speed.wav" ) ); +#ifdef _IMMERSION + G_Force( self, G_ForceIndex( "fffx/weapons/force/speed", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION +} + +void WP_StartForceHealEffects( gentity_t *self ) +{ + if ( self->ghoul2.size() ) + { + if ( self->chestBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/heal2" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, 3000, qtrue ); + } + /* + if ( self->headBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->headBolt, self->s.number, self->currentOrigin, 3000, qtrue ); + } + if ( self->cervicalBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->cervicalBolt, self->s.number, self->currentOrigin, 3000, qtrue ); + } + if ( self->chestBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, 3000, qtrue ); + } + if ( self->gutBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->gutBolt, self->s.number, self->currentOrigin, 3000, qtrue ); + } + if ( self->kneeLBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeLBolt, self->s.number, self->currentOrigin, 3000, qtrue ); + } + if ( self->kneeRBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeRBolt, self->s.number, self->currentOrigin, 3000, qtrue ); + } + if ( self->elbowLBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowLBolt, self->s.number, self->currentOrigin, 3000, qtrue ); + } + if ( self->elbowRBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowRBolt, self->s.number, self->currentOrigin, 3000, qtrue ); + } + */ + } +} + +void WP_StopForceHealEffects( gentity_t *self ) +{ + if ( self->ghoul2.size() ) + { + if ( self->chestBolt != -1 ) + { + G_StopEffect( G_EffectIndex( "force/heal2" ), self->playerModel, self->chestBolt, self->s.number ); + } + /* + if ( self->headBolt != -1 ) + { + G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->headBolt, self->s.number ); + } + if ( self->cervicalBolt != -1 ) + { + G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->cervicalBolt, self->s.number ); + } + if ( self->chestBolt != -1 ) + { + G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->chestBolt, self->s.number ); + } + if ( self->gutBolt != -1 ) + { + G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->gutBolt, self->s.number ); + } + if ( self->kneeLBolt != -1 ) + { + G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeLBolt, self->s.number ); + } + if ( self->kneeRBolt != -1 ) + { + G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->kneeRBolt, self->s.number ); + } + if ( self->elbowLBolt != -1 ) + { + G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowLBolt, self->s.number ); + } + if ( self->elbowRBolt != -1 ) + { + G_StopEffect( G_EffectIndex( "force/heal_joint" ), self->playerModel, self->elbowRBolt, self->s.number ); + } + */ + } +} + +int FP_MaxForceHeal( gentity_t *self ) +{ + if ( self->s.number >= MAX_CLIENTS ) + { + return MAX_FORCE_HEAL_HARD; + } + switch ( g_spskill->integer ) + { + case 0://easy + return MAX_FORCE_HEAL_EASY; + break; + case 1://medium + return MAX_FORCE_HEAL_MEDIUM; + break; + case 2://hard + default: + return MAX_FORCE_HEAL_HARD; + break; + } +} + +int FP_ForceHealInterval( gentity_t *self ) +{ + return (self->client->ps.forcePowerLevel[FP_HEAL]>FORCE_LEVEL_2)?50:FORCE_HEAL_INTERVAL; +} + +void ForceHeal( gentity_t *self ) +{ + if ( self->health <= 0 || self->client->ps.stats[STAT_MAX_HEALTH] <= self->health ) + { + return; + } + + if ( !WP_ForcePowerUsable( self, FP_HEAL, 20 ) ) + {//must have enough force power for at least 5 points of health + return; + } + + if ( self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) ) + {//can't initiate a heal while taking pain or attacking + return; + } + + if ( self->client->ps.saberLockTime > level.time ) + {//FIXME: can this be a way to break out? + return; + } + /* + if ( self->client->ps.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_2 ) + {//instant heal + //no more than available force power + int max = self->client->ps.forcePower; + if ( max > MAX_FORCE_HEAL ) + {//no more than max allowed + max = MAX_FORCE_HEAL; + } + if ( max > self->client->ps.stats[STAT_MAX_HEALTH] - self->health ) + {//no more than what's missing + max = self->client->ps.stats[STAT_MAX_HEALTH] - self->health; + } + self->health += max; + WP_ForcePowerDrain( self, FP_HEAL, max ); + G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d.mp3", Q_irand( 1, 4 ) ) ); + } + else + */ + { + //start health going up + //NPC_SetAnim( self, SETANIM_TORSO, ?, SETANIM_FLAG_OVERRIDE ); + WP_ForcePowerStart( self, FP_HEAL, 0 ); + if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 ) + {//must meditate + //FIXME: holster weapon (select WP_NONE?) + //FIXME: BOTH_FORCEHEAL_START + NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + self->client->ps.saberBlocked = BLOCKED_NONE; + self->client->ps.torsoAnimTimer = self->client->ps.legsAnimTimer = FP_ForceHealInterval(self)*FP_MaxForceHeal(self) + 2000;//??? + WP_DeactivateSaber( self );//turn off saber when meditating + } + else + {//just a quick gesture + /* + //Can't get an anim that looks good... + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_QUICK, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + self->client->ps.saberBlocked = BLOCKED_NONE; + */ + } + } + + //FIXME: always play healing effect + G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/heal.mp3" ); +#ifdef _IMMERSION + G_Force( self, G_ForceIndex( "fffx/weapons/force/heal", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION +} + +extern void NPC_PlayConfusionSound( gentity_t *self ); +extern void NPC_Jedi_PlayConfusionSound( gentity_t *self ); +qboolean WP_CheckBreakControl( gentity_t *self ) +{ + if ( !self ) + { + return qfalse; + } + if ( !self->s.number ) + {//player + if ( self->client && self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 ) + {//control-level + if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD ) + {//we are in a viewentity + gentity_t *controlled = &g_entities[self->client->ps.viewEntity]; + if ( controlled->NPC && controlled->NPC->controlledTime > level.time ) + {//it is an NPC we controlled + //clear it and return + G_ClearViewEntity( self ); + return qtrue; + } + } + } + } + else + {//NPC + if ( self->NPC && self->NPC->controlledTime > level.time ) + {//being controlled + gentity_t *controller = &g_entities[0]; + if ( controller->client && controller->client->ps.viewEntity == self->s.number ) + {//we are being controlled by player + if ( controller->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 ) + {//control-level mind trick + //clear the control and return + G_ClearViewEntity( controller ); + return qtrue; + } + } + } + } + return qfalse; +} +extern bool Pilot_AnyVehiclesRegistered(); + +void ForceTelepathy( gentity_t *self ) +{ + trace_t tr; + vec3_t end, forward; + gentity_t *traceEnt; + qboolean targetLive = qfalse; + + if ( WP_CheckBreakControl( self ) ) + { + return; + } + if ( self->health <= 0 ) + { + return; + } + //FIXME: if mind trick 3 and aiming at an enemy need more force power + if ( !WP_ForcePowerUsable( self, FP_TELEPATHY, 0 ) ) + { + return; + } + + if ( self->client->ps.weaponTime >= 800 ) + {//just did one! + return; + } + if ( self->client->ps.saberLockTime > level.time ) + {//FIXME: can this be a way to break out? + return; + } + + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + VectorNormalize( forward ); + VectorMA( self->client->renderInfo.eyePoint, 2048, forward, end ); + + //Cause a distraction if enemy is not fighting + gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_OPAQUE|CONTENTS_BODY ); + if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid ) + { + return; + } + + traceEnt = &g_entities[tr.entityNum]; + + if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE ) + { + return; + } + + if ( traceEnt && traceEnt->client ) + { + switch ( traceEnt->client->NPC_class ) + { + case CLASS_GALAKMECH://cant grip him, he's in armor + case CLASS_ATST://much too big to grip! + //no droids either + case CLASS_PROBE: + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_MOUSE: + case CLASS_SEEKER: + case CLASS_REMOTE: + case CLASS_PROTOCOL: + case CLASS_ASSASSIN_DROID: + case CLASS_SABER_DROID: + case CLASS_BOBAFETT: + break; + case CLASS_RANCOR: + if ( !(traceEnt->spawnflags&1) ) + { + targetLive = qtrue; + } + break; + default: + targetLive = qtrue; + break; + } + } + if ( targetLive + && traceEnt->NPC + && traceEnt->health > 0 ) + {//hit an organic non-player + if ( G_ActivateBehavior( traceEnt, BSET_MINDTRICK ) ) + {//activated a script on him + //FIXME: do the visual sparkles effect on their heads, still? + WP_ForcePowerStart( self, FP_TELEPATHY, 0 ); + } + else if ( traceEnt->client->playerTeam != self->client->playerTeam ) + {//an enemy + int override = 0; + if ( (traceEnt->NPC->scriptFlags&SCF_NO_MIND_TRICK) ) + { + if ( traceEnt->client->NPC_class == CLASS_GALAKMECH ) + { + G_AddVoiceEvent( traceEnt, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), Q_irand( 3000, 5000 ) ); + } + } + else if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 ) + {//control them, even jedi + G_SetViewEntity( self, traceEnt ); + traceEnt->NPC->controlledTime = level.time + 30000; + } + else if ( traceEnt->s.weapon != WP_SABER + && traceEnt->client->NPC_class != CLASS_REBORN ) + {//haha! Jedi aren't easily confused! + if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_2 + && traceEnt->s.weapon != WP_NONE //don't charm people who aren't capable of fighting... like ugnaughts and droids, just confuse them + && traceEnt->client->NPC_class != CLASS_TUSKEN//don't charm them, just confuse them + && traceEnt->client->NPC_class != CLASS_NOGHRI//don't charm them, just confuse them + && !Pilot_AnyVehiclesRegistered() //also, don't charm guys when bikes are near + ) + {//turn them to our side + //if mind trick 3 and aiming at an enemy need more force power + override = 50; + if ( self->client->ps.forcePower < 50 ) + { + return; + } + if ( traceEnt->enemy ) + { + G_ClearEnemy( traceEnt ); + } + if ( traceEnt->NPC ) + { + //traceEnt->NPC->tempBehavior = BS_FOLLOW_LEADER; + traceEnt->client->leader = self; + } + //FIXME: maybe pick an enemy right here? + //FIXME: does nothing to TEAM_FREE and TEAM_NEUTRALs!!! + team_t saveTeam = traceEnt->client->enemyTeam; + traceEnt->client->enemyTeam = traceEnt->client->playerTeam; + traceEnt->client->playerTeam = saveTeam; + //FIXME: need a *charmed* timer on this...? Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done? + traceEnt->NPC->charmedTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]]; + if ( traceEnt->ghoul2.size() && traceEnt->headBolt != -1 ) + {//FIXME: what if already playing effect? + G_PlayEffect( G_EffectIndex( "force/confusion" ), traceEnt->playerModel, traceEnt->headBolt, traceEnt->s.number, traceEnt->currentOrigin, mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]], qtrue ); + } + } + else + {//just confuse them + //somehow confuse them? Set don't fire to true for a while? Drop their aggression? Maybe just take their enemy away and don't let them pick one up for a while unless shot? + traceEnt->NPC->confusionTime = level.time + mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]];//confused for about 10 seconds + if ( traceEnt->ghoul2.size() && traceEnt->headBolt != -1 ) + {//FIXME: what if already playing effect? + G_PlayEffect( G_EffectIndex( "force/confusion" ), traceEnt->playerModel, traceEnt->headBolt, traceEnt->s.number, traceEnt->currentOrigin, mindTrickTime[self->client->ps.forcePowerLevel[FP_TELEPATHY]], qtrue ); + } + NPC_PlayConfusionSound( traceEnt ); + if ( traceEnt->enemy ) + { + G_ClearEnemy( traceEnt ); + } + } + } + else + { + NPC_Jedi_PlayConfusionSound( traceEnt ); + } + WP_ForcePowerStart( self, FP_TELEPATHY, override ); + } + else if ( traceEnt->client->playerTeam == self->client->playerTeam ) + {//an ally + //maybe just have him look at you? Respond? Take your enemy? + if ( traceEnt->client->ps.pm_type < PM_DEAD && traceEnt->NPC!=NULL && !(traceEnt->NPC->scriptFlags&SCF_NO_RESPONSE) ) + { + NPC_UseResponse( traceEnt, self, qfalse ); + WP_ForcePowerStart( self, FP_TELEPATHY, 1 ); + } + }//NOTE: no effect on TEAM_NEUTRAL? + vec3_t eyeDir; + AngleVectors( traceEnt->client->renderInfo.eyeAngles, eyeDir, NULL, NULL ); + VectorNormalize( eyeDir ); + G_PlayEffect( "force/force_touch", traceEnt->client->renderInfo.eyePoint, eyeDir ); + + //make sure this plays and that you cannot press fire for about 1 second after this + //FIXME: BOTH_FORCEMINDTRICK or BOTH_FORCEDISTRACT + NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD ); + //FIXME: build-up or delay this until in proper part of anim + } + else + { + if ( self->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_1 && tr.fraction * 2048 > 64 ) + {//don't create a diversion less than 64 from you of if at power level 1 + //use distraction anim instead + G_PlayEffect( G_EffectIndex( "force/force_touch" ), tr.endpos, tr.plane.normal ); + //FIXME: these events don't seem to always be picked up...? + AddSoundEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, qtrue, qtrue ); + AddSightEvent( self, tr.endpos, 512, AEL_SUSPICIOUS, 50 ); + WP_ForcePowerStart( self, FP_TELEPATHY, 0 ); + } + NPC_SetAnim( self, SETANIM_TORSO, BOTH_MINDTRICK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD ); + } + self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + self->client->ps.saberBlocked = BLOCKED_NONE; + self->client->ps.weaponTime = 1000; + if ( self->client->ps.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value ); + } +} + +//rww - RAGDOLL_BEGIN +//#define JK2_RAGDOLL_GRIPNOHEALTH +//rww - RAGDOLL_END + +void ForceGrip( gentity_t *self ) +{//FIXME: make enemy Jedi able to use this + trace_t tr; + vec3_t end, forward; + gentity_t *traceEnt = NULL; + + if ( self->health <= 0 ) + { + return; + } + if ( !self->s.number && (cg.zoomMode || in_camera) ) + {//can't force grip when zoomed in or in cinematic + return; + } + if ( self->client->ps.leanofs ) + {//can't force-grip while leaning + return; + } + + if ( self->client->ps.forceGripEntityNum <= ENTITYNUM_WORLD ) + {//already gripping + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + { + self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 100; + self->client->ps.weaponTime = 1000; + if ( self->client->ps.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value ); + } + } + return; + } + + if ( !WP_ForcePowerUsable( self, FP_GRIP, 0 ) ) + {//can't use it right now + return; + } + + if ( self->client->ps.forcePower < 26 ) + {//need 20 to start, 6 to hold it for any decent amount of time... + return; + } + + if ( self->client->ps.weaponTime ) + {//busy + return; + } + + if ( self->client->ps.saberLockTime > level.time ) + {//FIXME: can this be a way to break out? + return; + } + //Cause choking anim + health drain in ent in front of me + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + self->client->ps.saberBlocked = BLOCKED_NONE; + + self->client->ps.weaponTime = 1000; + if ( self->client->ps.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value ); + } + + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + VectorNormalize( forward ); + VectorMA( self->client->renderInfo.handLPoint, FORCE_GRIP_DIST, forward, end ); + + if ( self->enemy ) + {//I have an enemy + if ( !self->enemy->message + && !(self->flags&FL_NO_KNOCKBACK) ) + {//don't auto-pickup guys with keys + if ( DistanceSquared( self->enemy->currentOrigin, self->currentOrigin ) < FORCE_GRIP_DIST_SQUARED ) + {//close enough to grab + float minDot = 0.5f; + if ( self->s.number < MAX_CLIENTS ) + {//player needs to be facing more directly + minDot = 0.2f; + } + if ( InFront( self->enemy->currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, minDot ) ) //self->s.number || //NPCs can always lift enemy since we assume they're looking at them...? + {//need to be facing the enemy + if ( gi.inPVS( self->enemy->currentOrigin, self->client->renderInfo.eyePoint ) ) + {//must be in PVS + gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, self->enemy->currentOrigin, self->s.number, MASK_SHOT ); + if ( tr.fraction == 1.0f || tr.entityNum == self->enemy->s.number ) + {//must have clear LOS + traceEnt = self->enemy; + } + } + } + } + } + } + if ( !traceEnt ) + {//okay, trace straight ahead and see what's there + gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT ); + if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid ) + { + return; + } + + traceEnt = &g_entities[tr.entityNum]; + } +//rww - RAGDOLL_BEGIN +#ifdef JK2_RAGDOLL_GRIPNOHEALTH + if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) ) + { + return; + } +#else +//rww - RAGDOLL_END + if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) ) + { + return; + } +//rww - RAGDOLL_BEGIN +#endif +//rww - RAGDOLL_END + + if ( traceEnt->m_pVehicle != NULL ) + {//is it a vehicle + //grab pilot if there is one + if ( traceEnt->m_pVehicle->m_pPilot != NULL + && traceEnt->m_pVehicle->m_pPilot->client != NULL ) + {//grip the pilot + traceEnt = traceEnt->m_pVehicle->m_pPilot; + } + else + {//can't grip a vehicle + return; + } + } + if ( traceEnt->client ) + { + if ( traceEnt->client->ps.forceJumpZStart ) + {//can't catch them in mid force jump - FIXME: maybe base it on velocity? + return; + } + if ( traceEnt->client->ps.pullAttackTime > level.time ) + {//can't grip someone who is being pull-attacked or is pull-attacking + return; + } + if ( !Q_stricmp("Yoda",traceEnt->NPC_type) ) + { + Jedi_PlayDeflectSound( traceEnt ); + ForceThrow( traceEnt, qfalse ); + return; + } + + if ( G_IsRidingVehicle( traceEnt ) + && (traceEnt->s.eFlags&EF_NODRAW) ) + {//riding *inside* vehicle + return; + } + + switch ( traceEnt->client->NPC_class ) + { + case CLASS_GALAKMECH://cant grip him, he's in armor + G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) ); + return; + break; + case CLASS_HAZARD_TROOPER://cant grip him, he's in armor + return; + break; + case CLASS_ATST://much too big to grip! + case CLASS_RANCOR://much too big to grip! + case CLASS_WAMPA://much too big to grip! + case CLASS_SAND_CREATURE://much too big to grip! + return; + break; + //no droids either...? + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_MOUSE://? + case CLASS_PROTOCOL: + //*sigh*... in JK3, you'll be able to grab and move *anything*... + return; + break; + //not even combat droids? (No animation for being gripped...) + case CLASS_SABER_DROID: + case CLASS_ASSASSIN_DROID: + //*sigh*... in JK3, you'll be able to grab and move *anything*... + return; + break; + case CLASS_PROBE: + case CLASS_SEEKER: + case CLASS_REMOTE: + case CLASS_SENTRY: + case CLASS_INTERROGATOR: + //*sigh*... in JK3, you'll be able to grab and move *anything*... + return; + break; + case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly + case CLASS_KYLE: + case CLASS_TAVION: + case CLASS_LUKE: + Jedi_PlayDeflectSound( traceEnt ); + ForceThrow( traceEnt, qfalse ); + return; + break; + case CLASS_REBORN: + case CLASS_SHADOWTROOPER: + case CLASS_ALORA: + case CLASS_JEDI: + if ( traceEnt->NPC && traceEnt->NPC->rank > RANK_CIVILIAN && self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 ) + { + Jedi_PlayDeflectSound( traceEnt ); + ForceThrow( traceEnt, qfalse ); + return; + } + break; + } + if ( traceEnt->s.weapon == WP_EMPLACED_GUN ) + {//FIXME: maybe can pull them out? + return; + } + if ( self->enemy && traceEnt != self->enemy && traceEnt->client->playerTeam == self->client->playerTeam ) + {//can't accidently grip your teammate in combat + return; + } +//=CHECKABSORB=== + if ( -1 != WP_AbsorbConversion( traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_GRIP, self->client->ps.forcePowerLevel[FP_GRIP], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_GRIP]]) ) + { + //WP_ForcePowerStop( self, FP_GRIP ); + return; + } +//=============== + } + else + {//can't grip non-clients... right? + //FIXME: Make it so objects flagged as "grabbable" are let through + //if ( Q_stricmp( "misc_model_breakable", traceEnt->classname ) || !(traceEnt->s.eFlags&EF_BOUNCE_HALF) || !traceEnt->physicsBounce ) + { + return; + } + } + + // Make sure to turn off Force Protection and Force Absorb. + if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) ) + { + WP_ForcePowerStop( self, FP_PROTECT ); + } + if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) ) + { + WP_ForcePowerStop( self, FP_ABSORB ); + } + + WP_ForcePowerStart( self, FP_GRIP, 20 ); + //FIXME: rule out other things? + //FIXME: Jedi resist, like the push and pull? + self->client->ps.forceGripEntityNum = traceEnt->s.number; + if ( traceEnt->client ) + { + Vehicle_t *pVeh; + if ( ( pVeh = G_IsRidingVehicle( traceEnt ) ) != NULL ) + {//riding vehicle? pull him off! + //FIXME: if in an AT-ST or X-Wing, shouldn't do this... :) + //pull him off of it + //((CVehicleNPC *)traceEnt->NPC)->Eject( traceEnt ); + pVeh->m_pVehicleInfo->Eject( pVeh, traceEnt, qtrue ); + //G_DriveVehicle( traceEnt, NULL, NULL ); + } + G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 || traceEnt->s.weapon == WP_SABER ) + {//if we pick up & carry, drop their weap + if ( traceEnt->s.weapon + && traceEnt->client->NPC_class != CLASS_ROCKETTROOPER + && traceEnt->client->NPC_class != CLASS_VEHICLE + && traceEnt->client->NPC_class != CLASS_HAZARD_TROOPER + && traceEnt->client->NPC_class != CLASS_TUSKEN + && traceEnt->client->NPC_class != CLASS_BOBAFETT + && traceEnt->client->NPC_class != CLASS_ASSASSIN_DROID + && traceEnt->s.weapon != WP_CONCUSSION // so rax can't drop his + ) + { + if ( traceEnt->client->NPC_class == CLASS_BOBAFETT ) + {//he doesn't drop them, just puts it away + ChangeWeapon( traceEnt, WP_MELEE ); + } + else if ( traceEnt->s.weapon == WP_MELEE ) + {//they can't take that away from me, oh no... + } + else if ( traceEnt->NPC + && (traceEnt->NPC->scriptFlags&SCF_DONT_FLEE) ) + {//*SIGH*... if an NPC can't flee, they can't run after and pick up their weapon, do don't drop it + } + else if ( traceEnt->s.weapon != WP_SABER ) + { + WP_DropWeapon( traceEnt, NULL ); + } + else + { + //turn it off? + traceEnt->client->ps.SaberDeactivate(); + G_SoundOnEnt( traceEnt, CHAN_WEAPON, "sound/weapons/saber/saberoffquick.wav" ); +#ifdef _IMMERSION + G_Force( traceEnt, G_ForceIndex( "fffx/weapons/saber/saberoffquick", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + } + } + } + //else FIXME: need a one-armed choke if we're not on a high enough level to make them drop their gun + VectorCopy( traceEnt->client->renderInfo.headPoint, self->client->ps.forceGripOrg ); + } + else + { + VectorCopy( traceEnt->currentOrigin, self->client->ps.forceGripOrg ); + } + self->client->ps.forceGripOrg[2] += 48;//FIXME: define? + if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 ) + {//just a duration + self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250; + self->client->ps.forcePowerDuration[FP_GRIP] = level.time + 5000; + + if ( self->m_pVehicle && self->m_pVehicle->m_pVehicleInfo->Inhabited( self->m_pVehicle ) ) + {//empty vehicles don't make gripped noise + traceEnt->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" ); + } +#ifdef _IMMERSION + G_Force( self, G_ForceIndex( "fffx/weapons/force/gripcast", FF_CHANNEL_FORCE ) ); + //G_Force( traceEnt, G_ForceIndex( "fffx/weapons/force/grip", FF_CHANNEL_DAMAGE ) ); +#endif // _IMMERSION + } + else + { + if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 ) + {//lifting sound? or always? + } + //if ( traceEnt->s.number ) + {//picks them up for a second first + self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 1000; + } + /* + else + {//player should take damage right away + self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + 250; + } + */ + // force grip sound should only play when the target is alive? + // if (traceEnt->health>0) + // { + self->s.loopSound = G_SoundIndex( "sound/weapons/force/grip.mp3" ); + // } + } +} + +qboolean ForceLightningCheck2Handed( gentity_t *self ) +{ + if ( self && self->client ) + { + if ( self->s.weapon == WP_NONE + || self->s.weapon == WP_MELEE + || (self->s.weapon == WP_SABER && !self->client->ps.SaberActive()) ) + { + return qtrue; + } + } + return qfalse; +} + +void ForceLightningAnim( gentity_t *self ) +{ + if ( !self || !self->client ) + { + return; + } + + //one-handed lightning 2 and above + int startAnim = BOTH_FORCELIGHTNING_START; + int holdAnim = BOTH_FORCELIGHTNING_HOLD; + + if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] >= FORCE_LEVEL_3 + && ForceLightningCheck2Handed( self ) ) + {//empty handed lightning 3 + startAnim = BOTH_FORCE_2HANDEDLIGHTNING_START; + holdAnim = BOTH_FORCE_2HANDEDLIGHTNING_HOLD; + } + + //FIXME: if standing still, play on whole body? Especially 2-handed version + if ( self->client->ps.torsoAnim == startAnim ) + { + if ( !self->client->ps.torsoAnimTimer ) + { + NPC_SetAnim( self, SETANIM_TORSO, holdAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( self, SETANIM_TORSO, startAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + else + { + NPC_SetAnim( self, SETANIM_TORSO, holdAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } +} + +void ForceLightning( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + if ( !self->s.number && (cg.zoomMode || in_camera) ) + {//can't force lightning when zoomed in or in cinematic + return; + } + if ( self->client->ps.leanofs ) + {//can't force-lightning while leaning + return; + } + if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_LIGHTNING, 0 ) ) + { + return; + } + if ( self->client->ps.forcePowerDebounce[FP_LIGHTNING] > level.time ) + {//stops it while using it and also after using it, up to 3 second delay + return; + } + if ( self->client->ps.saberLockTime > level.time ) + {//FIXME: can this be a way to break out? + return; + } + // Make sure to turn off Force Protection and Force Absorb. + if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) ) + { + WP_ForcePowerStop( self, FP_PROTECT ); + } + if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) ) + { + WP_ForcePowerStop( self, FP_ABSORB ); + } + //Shoot lightning from hand + //make sure this plays and that you cannot press fire for about 1 second after this + if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 ) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + { + ForceLightningAnim( self ); + /* + if ( ForceLightningCheck2Handed( self ) ) + {//empty handed lightning 3 + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_2HANDEDLIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + {//one-handed lightning 3 + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + */ + } + self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + self->client->ps.saberBlocked = BLOCKED_NONE; + + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" ); +#ifdef _IMMERSION + G_Force( self, G_ForceIndex( "fffx/weapons/force/lightning", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 ) + {//short burst + //G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/lightning.wav" ); + } + else + {//holding it + self->s.loopSound = G_SoundIndex( "sound/weapons/force/lightning2.wav" ); + } + + //FIXME: build-up or delay this until in proper part of anim + self->client->ps.weaponTime = self->client->ps.torsoAnimTimer; + WP_ForcePowerStart( self, FP_LIGHTNING, self->client->ps.torsoAnimTimer ); +} + +void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, float dist, float dot, vec3_t impactPoint ) +{ + if( traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE ) + { + return; + } + + if ( traceEnt && traceEnt->takedamage ) + { + if ( !traceEnt->client || traceEnt->client->playerTeam != self->client->playerTeam || self->enemy == traceEnt || traceEnt->enemy == self ) + {//an enemy or object + int dmg; + //FIXME: check for client using FP_ABSORB + if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 ) + {//more damage if closer and more in front + dmg = 1; + if ( self->client->NPC_class == CLASS_REBORN + && self->client->ps.weapon == WP_NONE ) + {//Cultist: looks fancy, but does less damage + } + else + { + if ( dist < 100 ) + { + dmg += 2; + } + else if ( dist < 200 ) + { + dmg += 1; + } + if ( dot > 0.9f ) + { + dmg += 2; + } + else if ( dot > 0.7f ) + { + dmg += 1; + } + } + if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING + || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START + || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD + || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_RELEASE ) + {//jackin' 'em up, Palpatine-style + dmg *= 2; + } + } + else + { + dmg = Q_irand( 1, 3 );//*self->client->ps.forcePowerLevel[FP_LIGHTNING]; + } + + if ( traceEnt->client + && traceEnt->health > 0 + && traceEnt->NPC + && (traceEnt->NPC->aiFlags&NPCAI_BOSS_CHARACTER) ) + {//Luke, Desann Tavion and Kyle can shield themselves from the attack + //FIXME: shield effect or something? + int parts; + if ( traceEnt->client->ps.groundEntityNum != ENTITYNUM_NONE && !PM_SpinningSaberAnim( traceEnt->client->ps.legsAnim ) && !PM_FlippingAnim( traceEnt->client->ps.legsAnim ) && !PM_RollingAnim( traceEnt->client->ps.legsAnim ) ) + {//if on a surface and not in a spin or flip, play full body resist + parts = SETANIM_BOTH; + } + else + {//play resist just in torso + parts = SETANIM_TORSO; + } + //FIXME: don't interrupt big anims with this! + NPC_SetAnim( traceEnt, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + Jedi_PlayDeflectSound( traceEnt ); + dmg = Q_irand(0,1); + } + else if ( traceEnt->s.weapon == WP_SABER ) + {//saber can block lightning + if ( traceEnt->client //a client + && !traceEnt->client->ps.saberInFlight//saber in hand + && ( traceEnt->client->ps.saberMove == LS_READY || PM_SaberInParry( traceEnt->client->ps.saberMove ) || PM_SaberInReturn( traceEnt->client->ps.saberMove ) )//not attacking with saber + && InFOV( self->currentOrigin, traceEnt->currentOrigin, traceEnt->client->ps.viewangles, 20, 35 ) //I'm in front of them + && !PM_InKnockDown( &traceEnt->client->ps ) //they're not in a knockdown + && !PM_SuperBreakLoseAnim( traceEnt->client->ps.torsoAnim ) + && !PM_SuperBreakWinAnim( traceEnt->client->ps.torsoAnim ) + && !PM_SaberInSpecialAttack( traceEnt->client->ps.torsoAnim ) + && !PM_InSpecialJump( traceEnt->client->ps.torsoAnim ) + && (!traceEnt->s.number||(traceEnt->NPC&&traceEnt->NPC->rank>=RANK_LT_COMM)) )//the player or a tough jedi/reborn + { + if ( Q_irand( 0, traceEnt->client->ps.forcePowerLevel[FP_SABER_DEFENSE]*3 ) > 0 )//more of a chance of defending if saber defense is high + { + dmg = 0; + } + if ( (traceEnt->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_2 ) + {//no parry, just absorb + } + else + { + //make them do a parry + traceEnt->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + int parryReCalcTime = Jedi_ReCalcParryTime( traceEnt, EVASION_PARRY ); + if ( traceEnt->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime ) + { + traceEnt->client->ps.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime; + } + traceEnt->client->ps.weaponTime = Q_irand( 100, 300 );//hold this move - can't attack! - FIXME: unless dual sabers? + } + } + else if ( Q_irand( 0, 1 ) ) + {//jedi less likely to be damaged + dmg = 0; + } + else + { + dmg = 1; + } + } + if ( traceEnt && traceEnt->client && traceEnt->client->ps.powerups[PW_GALAK_SHIELD] ) + { + //has shield up + dmg = 0; + } + int modPowerLevel = -1; + + if (traceEnt->client) + { + modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_LIGHTNING, self->client->ps.forcePowerLevel[FP_LIGHTNING], 1); + } + + if (modPowerLevel != -1) + { + if ( !modPowerLevel ) + { + dmg = 0; + } + else if ( modPowerLevel == 1 ) + { + dmg = floor((float)dmg/4.0f); + } + else if ( modPowerLevel == 2 ) + { + dmg = floor((float)dmg/2.0f); + } + } + //FIXME: if ForceDrain, sap force power and add health to self, use different sound & effects + if ( dmg ) + { + G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_FORCE_LIGHTNING ); + } + if ( traceEnt->client ) + { + if ( !Q_irand( 0, 2 ) ) + { + G_Sound( traceEnt, G_SoundIndex( va( "sound/weapons/force/lightninghit%d.wav", Q_irand( 1, 3 ) ) ) ); + } + traceEnt->s.powerups |= ( 1 << PW_SHOCKED ); + + // If we are dead or we are a bot, we can do the full version + class_t npc_class = traceEnt->client->NPC_class; + if ( traceEnt->health <= 0 || ( npc_class == CLASS_SEEKER || npc_class == CLASS_PROBE || + npc_class == CLASS_MOUSE || npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_REMOTE || + npc_class == CLASS_R5D2 || npc_class == CLASS_PROTOCOL || npc_class == CLASS_MARK1 || + npc_class == CLASS_MARK2 || npc_class == CLASS_INTERROGATOR || npc_class == CLASS_ATST ) || + npc_class == CLASS_SENTRY ) + { + traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 4000; + } + else //short version + { + traceEnt->client->ps.powerups[PW_SHOCKED] = level.time + 500; + } + } + } + } +} + +void ForceShootLightning( gentity_t *self ) +{ + trace_t tr; + vec3_t end, forward; + gentity_t *traceEnt; + + if ( self->health <= 0 ) + { + return; + } + if ( !self->s.number && cg.zoomMode ) + {//can't force lightning when zoomed in + return; + } + + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + VectorNormalize( forward ); + + //FIXME: if lightning hits water, do water-only-flagged radius damage from that point + if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 ) + {//arc + vec3_t center, mins, maxs, dir, ent_org, size, v; + float radius = 512, dot, dist; + gentity_t *entityList[MAX_GENTITIES]; + int e, numListedEntities, i; + + VectorCopy( self->currentOrigin, center ); + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + traceEnt = entityList[e]; + + if ( !traceEnt ) + continue; + if ( traceEnt == self ) + continue; + if ( traceEnt->owner == self && traceEnt->s.weapon != WP_THERMAL )//can push your own thermals + continue; + if ( !traceEnt->inuse ) + continue; + if ( !traceEnt->takedamage ) + continue; + /* + if ( traceEnt->health <= 0 )//no torturing corpses + continue; + */ + //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter + // find the distance from the edge of the bounding box + for ( i = 0 ; i < 3 ; i++ ) + { + if ( center[i] < traceEnt->absmin[i] ) + { + v[i] = traceEnt->absmin[i] - center[i]; + } else if ( center[i] > traceEnt->absmax[i] ) + { + v[i] = center[i] - traceEnt->absmax[i]; + } else + { + v[i] = 0; + } + } + + VectorSubtract( traceEnt->absmax, traceEnt->absmin, size ); + VectorMA( traceEnt->absmin, 0.5, size, ent_org ); + + //see if they're in front of me + //must be within the forward cone + VectorSubtract( ent_org, center, dir ); + VectorNormalize( dir ); + if ( (dot = DotProduct( dir, forward )) < 0.5 ) + continue; + + //must be close enough + dist = VectorLength( v ); + if ( dist >= radius ) + { + continue; + } + + //in PVS? + if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) ) + {//must be in PVS + continue; + } + + //Now check and see if we can actually hit it + gi.trace( &tr, self->client->renderInfo.handLPoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT ); + if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number ) + {//must have clear LOS + continue; + } + + // ok, we are within the radius, add us to the incoming list + //FIXME: maybe add up the ents and do more damage the less ents there are + // as if we're spreading out the damage? + ForceLightningDamage( self, traceEnt, dir, dist, dot, ent_org ); + } + + } + else + {//trace-line + int ignore = self->s.number; + int traces = 0; + vec3_t start; + + VectorCopy( self->client->renderInfo.handLPoint, start ); + VectorMA( self->client->renderInfo.handLPoint, 2048, forward, end ); + + while ( traces < 10 ) + {//need to loop this in case we hit a Jedi who dodges the shot + gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 10 ); + if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid ) + { + return; + } + + traceEnt = &g_entities[tr.entityNum]; + //NOTE: only NPCs do this auto-dodge + if ( traceEnt + && traceEnt->s.number >= MAX_CLIENTS + && traceEnt->client + && traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC + {//FIXME: need a more reliable way to know we hit a jedi? + if ( !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) ) + {//act like we didn't even hit him + VectorCopy( tr.endpos, start ); + ignore = tr.entityNum; + traces++; + continue; + } + } + //a Jedi is not dodging this shot + break; + } + + traceEnt = &g_entities[tr.entityNum]; + ForceLightningDamage( self, traceEnt, forward, 0, 0, tr.endpos ); + } +} + +void WP_DeactivateSaber( gentity_t *self, qboolean clearLength ) +{ + if ( !self || !self->client ) + { + return; + } + //keep my saber off! + if ( self->client->ps.SaberActive() ) + { + self->client->ps.SaberDeactivate(); + if ( clearLength ) + { + self->client->ps.SetSaberLength( 0 ); + } + G_SoundIndexOnEnt( self, CHAN_WEAPON, self->client->ps.saber[0].soundOff ); +#ifdef _IMMERSION + if ( !self->s.number ) + {//this is kind of silly to try to do on an NPC + if ( self->client->playerTeam == TEAM_PLAYER ) + { + G_Force( self, G_ForceIndex( "fffx/weapons/saber/saberoff", FF_CHANNEL_WEAPON ) ); + } + else + { + G_Force( self, G_ForceIndex( "fffx/weapons/saber/enemy_saber_off", FF_CHANNEL_WEAPON ) ); + } + } +#endif // _IMMERSION + } +} + +static void ForceShootDrain( gentity_t *self ); + +void ForceDrainGrabStart( gentity_t *self ) +{ + NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + self->client->ps.saberBlocked = BLOCKED_NONE; + + self->client->ps.weaponTime = 1000; + if ( self->client->ps.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value ); + } + //actually grabbing someone, so turn off the saber! + WP_DeactivateSaber( self, qtrue ); +} + +qboolean ForceDrain2( gentity_t *self ) +{//FIXME: make enemy Jedi able to use this + trace_t tr; + vec3_t end, forward; + gentity_t *traceEnt = NULL; + + if ( self->health <= 0 ) + { + return qtrue; + } + + if ( !self->s.number && (cg.zoomMode || in_camera) ) + {//can't force grip when zoomed in or in cinematic + return qtrue; + } + + if ( self->client->ps.leanofs ) + {//can't force-drain while leaning + return qtrue; + } + + /* + if ( self->client->ps.SaberLength() > 0 ) + {//can't do this if saber is on! + return qfalse; + } + */ + + if ( self->client->ps.forceDrainEntityNum <= ENTITYNUM_WORLD ) + {//already draining + //keep my saber off! + WP_DeactivateSaber( self, qtrue ); + if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 ) + { + self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 100; + self->client->ps.weaponTime = 1000; + if ( self->client->ps.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value ); + } + } + return qtrue; + } + + if ( self->client->ps.forcePowerDebounce[FP_DRAIN] > level.time ) + {//stops it while using it and also after using it, up to 3 second delay + return qtrue; + } + + if ( self->client->ps.weaponTime > 0 ) + {//busy + return qtrue; + } + + if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_DRAIN, 0 ) ) + { + return qtrue; + } + + if ( self->client->ps.saberLockTime > level.time ) + {//in saberlock + return qtrue; + } + + //NOTE: from here on, if it fails, it's okay to try a normal drain, so return qfalse + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//in air + return qfalse; + } + + //Cause choking anim + health drain in ent in front of me + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + VectorNormalize( forward ); + VectorMA( self->client->renderInfo.eyePoint, FORCE_DRAIN_DIST, forward, end ); + + //okay, trace straight ahead and see what's there + gi.trace( &tr, self->client->renderInfo.eyePoint, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT ); + if ( tr.entityNum >= ENTITYNUM_WORLD || tr.fraction == 1.0 || tr.allsolid || tr.startsolid ) + { + return qfalse; + } + traceEnt = &g_entities[tr.entityNum]; + if ( !traceEnt || traceEnt == self/*???*/ || traceEnt->bmodel || (traceEnt->health <= 0 && traceEnt->takedamage) || (traceEnt->NPC && traceEnt->NPC->scriptFlags & SCF_NO_FORCE) ) + { + return qfalse; + } + + if ( traceEnt->client ) + { + if ( traceEnt->client->ps.forceJumpZStart ) + {//can't catch them in mid force jump - FIXME: maybe base it on velocity? + return qfalse; + } + if ( traceEnt->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//can't catch them in mid air + return qfalse; + } + if ( !Q_stricmp("Yoda",traceEnt->NPC_type) ) + { + Jedi_PlayDeflectSound( traceEnt ); + ForceThrow( traceEnt, qfalse ); + return qtrue; + } + switch ( traceEnt->client->NPC_class ) + { + case CLASS_GALAKMECH://cant grab him, he's in armor + G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), Q_irand( 3000, 5000 ) ); + return qfalse; + break; + case CLASS_ROCKETTROOPER://cant grab him, he's in armor + case CLASS_HAZARD_TROOPER://cant grab him, he's in armor + return qfalse; + break; + case CLASS_ATST://much too big to grab! + return qfalse; + break; + //no droids either + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_MOUSE: + case CLASS_PROTOCOL: + case CLASS_SABER_DROID: + case CLASS_ASSASSIN_DROID: + return qfalse; + break; + case CLASS_PROBE: + case CLASS_SEEKER: + case CLASS_REMOTE: + case CLASS_SENTRY: + case CLASS_INTERROGATOR: + return qfalse; + break; + case CLASS_DESANN://Desann cannot be gripped, he just pushes you back instantly + case CLASS_KYLE: + case CLASS_TAVION: + case CLASS_LUKE: + Jedi_PlayDeflectSound( traceEnt ); + ForceThrow( traceEnt, qfalse ); + return qtrue; + break; + case CLASS_REBORN: + case CLASS_SHADOWTROOPER: + //case CLASS_ALORA: + case CLASS_JEDI: + if ( traceEnt->NPC + && traceEnt->NPC->rank > RANK_CIVILIAN + && self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 + && traceEnt->client->ps.weaponTime <= 0 ) + { + ForceDrainGrabStart( self ); + Jedi_PlayDeflectSound( traceEnt ); + ForceThrow( traceEnt, qfalse ); + return qtrue; + } + break; + } + if ( traceEnt->s.weapon == WP_EMPLACED_GUN ) + {//FIXME: maybe can pull them out? + return qfalse; + } + if ( traceEnt != self->enemy && OnSameTeam(self, traceEnt) ) + {//can't accidently grip-drain your teammate + return qfalse; + } +//=CHECKABSORB=== + /* + if ( -1 != WP_AbsorbConversion( traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]]) ) + { + //WP_ForcePowerStop( self, FP_DRAIN ); + return; + } + */ +//=============== + if ( !FP_ForceDrainGrippableEnt( traceEnt ) ) + { + return qfalse; + } + } + else + {//can't drain non-clients + return qfalse; + } + + ForceDrainGrabStart( self ); + + WP_ForcePowerStart( self, FP_DRAIN, 10 ); + self->client->ps.forceDrainEntityNum = traceEnt->s.number; + +// G_AddVoiceEvent( traceEnt, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + G_AddVoiceEvent( traceEnt, Q_irand(EV_CHOKE1, EV_CHOKE3), 2000 ); + if ( /*self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 ||*/ traceEnt->s.weapon == WP_SABER ) + {//if we pick up, turn off their weapon + WP_DeactivateSaber( traceEnt, qtrue ); + } + + /* + if ( self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 ) + {//just a duration + self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 250; + self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 5000; + } + */ + + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/drain.mp3" ); + +// NPC_SetAnim( traceEnt, SETANIM_BOTH, BOTH_HUGGEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC_SetAnim( traceEnt, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRABBED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + WP_SabersCheckLock2( self, traceEnt, LOCK_FORCE_DRAIN ); + + return qtrue; +} + +void ForceDrain( gentity_t *self, qboolean triedDrain2 ) +{ + if ( self->health <= 0 ) + { + return; + } + + if ( !triedDrain2 && self->client->ps.weaponTime > 0 ) + { + return; + } + + if ( self->client->ps.forcePower < 25 || !WP_ForcePowerUsable( self, FP_DRAIN, 0 ) ) + { + return; + } + if ( self->client->ps.forcePowerDebounce[FP_DRAIN] > level.time ) + {//stops it while using it and also after using it, up to 3 second delay + return; + } + + if ( self->client->ps.saberLockTime > level.time ) + {//FIXME: can this be a way to break out? + return; + } + + // Make sure to turn off Force Protection and Force Absorb. + if ( self->client->ps.forcePowersActive & (1 << FP_PROTECT) ) + { + WP_ForcePowerStop( self, FP_PROTECT ); + } + if ( self->client->ps.forcePowersActive & (1 << FP_ABSORB) ) + { + WP_ForcePowerStop( self, FP_ABSORB ); + } + + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/drain.mp3" ); + + WP_ForcePowerStart( self, FP_DRAIN, 0 ); +} + + +qboolean FP_ForceDrainableEnt( gentity_t *victim ) +{ + if ( !victim || !victim->client ) + { + return qfalse; + } + switch ( victim->client->NPC_class ) + { + case CLASS_SAND_CREATURE://?? + case CLASS_ATST: // technically droid... + case CLASS_GONK: // droid + case CLASS_INTERROGATOR: // droid + case CLASS_MARK1: // droid + case CLASS_MARK2: // droid + case CLASS_GALAKMECH: // droid + case CLASS_MINEMONSTER: + case CLASS_MOUSE: // droid + case CLASS_PROBE: // droid + case CLASS_PROTOCOL: // droid + case CLASS_R2D2: // droid + case CLASS_R5D2: // droid + case CLASS_REMOTE: + case CLASS_SEEKER: // droid + case CLASS_SENTRY: + case CLASS_SABER_DROID: + case CLASS_ASSASSIN_DROID: + case CLASS_VEHICLE: + return qfalse; + break; + } + return qtrue; +} + +qboolean FP_ForceDrainGrippableEnt( gentity_t *victim ) +{ + if ( !victim || !victim->client ) + { + return qfalse; + } + if ( !FP_ForceDrainableEnt( victim ) ) + { + return qfalse; + } + switch ( victim->client->NPC_class ) + { + case CLASS_RANCOR: + case CLASS_SAND_CREATURE: + case CLASS_WAMPA: + case CLASS_LIZARD: + case CLASS_MINEMONSTER: + case CLASS_MURJJ: + case CLASS_SWAMP: + case CLASS_ROCKETTROOPER: + case CLASS_HAZARD_TROOPER: + return qfalse; + } + return qtrue; +} + +void ForceDrainDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t impactPoint ) +{ + if ( traceEnt + && traceEnt->health > 0 + && traceEnt->takedamage + && FP_ForceDrainableEnt( traceEnt ) ) + { + if ( traceEnt->client + && (!OnSameTeam(self, traceEnt)||self->enemy==traceEnt)//don't drain an ally unless that is actually my current enemy + && self->client->ps.forceDrainTime < level.time ) + {//an enemy or object + int modPowerLevel = -1; + int dmg = self->client->ps.forcePowerLevel[FP_DRAIN] + 1; + int dflags = (DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC);//|DAMAGE_NO_KILL); + if ( traceEnt->s.number == self->client->ps.forceDrainEntityNum ) + {//grabbing hold of them does more damage/drains more, and can actually kill them + dmg += 3; + dflags |= DAMAGE_IGNORE_TEAM; + //dflags &= ~DAMAGE_NO_KILL; + } + + if (traceEnt->client) + { + //check for client using FP_ABSORB + modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], 0); + //Since this is drain, don't absorb any power, but nullify the affect it has + } + + if ( modPowerLevel != -1 ) + { + if ( !modPowerLevel ) + { + dmg = 0; + } + else if ( modPowerLevel == 1 ) + { + dmg = 1; + } + else if ( modPowerLevel == 2 ) + { + dmg = 2; + } + } + + if ( dmg ) + { + int drain = 0; + if ( traceEnt->client->ps.forcePower ) + { + if ( dmg > traceEnt->client->ps.forcePower ) + { + drain = traceEnt->client->ps.forcePower; + dmg -= drain; + traceEnt->client->ps.forcePower = 0; + } + else + { + drain = dmg; + traceEnt->client->ps.forcePower -= (dmg); + dmg = 0; + } + } + + /* + if ( (dflags&DAMAGE_NO_KILL) ) + {//must cap damage + if ( traceEnt->health <= 1 ) + {//can't drain more than they have + dmg = 0; + } + else if ( dmg >= traceEnt->health ) + {//no more than they have, leaving one for them + dmg = traceEnt->health-1; + } + } + */ + + int maxHealth = self->client->ps.stats[STAT_MAX_HEALTH]; + if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 ) + {//overcharge health + maxHealth = floor( (float)self->client->ps.stats[STAT_MAX_HEALTH] * 1.25f ); + } + if (self->client->ps.stats[STAT_HEALTH] < maxHealth && + self->health > 0 && self->client->ps.stats[STAT_HEALTH] > 0) + { + self->health += (drain+dmg); + if (self->health > maxHealth ) + { + self->health = maxHealth; + } + self->client->ps.stats[STAT_HEALTH] = self->health; + if ( self->health > self->client->ps.stats[STAT_MAX_HEALTH] ) + { + self->flags |= FL_OVERCHARGED_HEALTH; + } + } + + if ( dmg ) + {//do damage, too + G_Damage( traceEnt, self, self, dir, impactPoint, dmg, dflags, MOD_FORCE_DRAIN ); + } + else if ( drain ) + { + /* + if ( traceEnt->s.number == self->client->ps.forceDrainEntityNum + || traceEnt->s.number < MAX_CLIENTS ) + {//grip-draining (or player - only does sound) + */ + NPC_SetPainEvent( traceEnt ); + /* + } + else + { + GEntity_PainFunc( traceEnt, self, self, impactPoint, 0, MOD_FORCE_DRAIN ); + } + */ + } + + if ( !Q_irand( 0, 2 ) ) + { + G_Sound( traceEnt, G_SoundIndex( "sound/weapons/force/drained.mp3" ) ); + } + + traceEnt->client->ps.forcePowerRegenDebounceTime = level.time + 800; //don't let the client being drained get force power back right away + } + } + } +} + +qboolean WP_CheckForceDraineeStopMe( gentity_t *self, gentity_t *drainee ) +{ + if ( drainee->NPC + && drainee->client + && (drainee->client->ps.forcePowersKnown&(1<client->ps.forcePowerDebounce[FP_DRAIN]>self->client->ps.forcePowerLevel[FP_DRAIN]*500)//at level 1, I always get at least 500ms of drain, at level 3 I get 1500ms + && !Q_irand( 0, 100-(drainee->NPC->stats.evasion*10)-(g_spskill->integer*12) ) ) + {//a jedi who broke free + ForceThrow( drainee, qfalse ); + //FIXME: I need to go into some pushed back anim... + WP_ForcePowerStop( self, FP_DRAIN ); + //can't drain again for 2 seconds + self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 4000; + return qtrue; + } + return qfalse; +} + +void ForceShootDrain( gentity_t *self ) +{ + trace_t tr; + vec3_t end, forward; + gentity_t *traceEnt; + int numDrained = 0; + + if ( self->health <= 0 ) + { + return; + } + + if ( self->client->ps.forcePowerDebounce[FP_DRAIN] <= level.time ) + { + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + VectorNormalize( forward ); + + if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 ) + {//arc + vec3_t center, mins, maxs, dir, ent_org, size, v; + float radius = MAX_DRAIN_DISTANCE, dot, dist; + gentity_t *entityList[MAX_GENTITIES]; + int e, numListedEntities, i; + + VectorCopy( self->client->ps.origin, center ); + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + traceEnt = entityList[e]; + + if ( !traceEnt ) + continue; + if ( traceEnt == self ) + continue; + if ( !traceEnt->inuse ) + continue; + if ( !traceEnt->takedamage ) + continue; + if ( traceEnt->health <= 0 )//no torturing corpses + continue; + if ( !traceEnt->client ) + continue; + /* + if ( !traceEnt->client->ps.forcePower ) + continue; + */ + // if (traceEnt->client->ps.forceSide == FORCE_DARKSIDE) // We no longer care if the victim is dark or light + // continue; + if (self->enemy != traceEnt//not my enemy + && OnSameTeam(self, traceEnt))//on my team + continue; + //this is all to see if we need to start a saber attack, if it's in flight, this doesn't matter + // find the distance from the edge of the bounding box + for ( i = 0 ; i < 3 ; i++ ) + { + if ( center[i] < traceEnt->absmin[i] ) + { + v[i] = traceEnt->absmin[i] - center[i]; + } else if ( center[i] > traceEnt->absmax[i] ) + { + v[i] = center[i] - traceEnt->absmax[i]; + } else + { + v[i] = 0; + } + } + + VectorSubtract( traceEnt->absmax, traceEnt->absmin, size ); + VectorMA( traceEnt->absmin, 0.5, size, ent_org ); + + //see if they're in front of me + //must be within the forward cone + VectorSubtract( ent_org, center, dir ); + VectorNormalize( dir ); + if ( (dot = DotProduct( dir, forward )) < 0.5 ) + continue; + + //must be close enough + dist = VectorLength( v ); + if ( dist >= radius ) + { + continue; + } + + //in PVS? + if ( !traceEnt->bmodel && !gi.inPVS( ent_org, self->client->renderInfo.handLPoint ) ) + {//must be in PVS + continue; + } + + //Now check and see if we can actually hit it + gi.trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT, G2_RETURNONHIT, 10 ); + if ( tr.fraction < 1.0f && tr.entityNum != traceEnt->s.number ) + {//must have clear LOS + continue; + } + + if ( traceEnt + && traceEnt->s.number >= MAX_CLIENTS + && traceEnt->client + && traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC + { + if ( !Q_irand( 0, 4 ) && !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) ) + {//act like we didn't even hit him + continue; + } + } + + // ok, we are within the radius, add us to the incoming list + if ( WP_CheckForceDraineeStopMe( self, traceEnt ) ) + { + continue; + } + ForceDrainDamage( self, traceEnt, dir, ent_org ); + numDrained++; + } + + } + else + {//trace-line + int ignore = self->s.number; + int traces = 0; + vec3_t start; + + VectorCopy( self->client->renderInfo.handLPoint, start ); + VectorMA( start, MAX_DRAIN_DISTANCE, forward, end ); + + while ( traces < 10 ) + {//need to loop this in case we hit a Jedi who dodges the shot + gi.trace( &tr, start, vec3_origin, vec3_origin, end, ignore, MASK_SHOT, G2_RETURNONHIT, 10 ); + if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid ) + { + //always take 1 force point per frame that we're shooting this + WP_ForcePowerDrain( self, FP_DRAIN, 1 ); + return; + } + + traceEnt = &g_entities[tr.entityNum]; + //NOTE: only NPCs do this auto-dodge + if ( traceEnt + && traceEnt->s.number >= MAX_CLIENTS + && traceEnt->client + && traceEnt->client->ps.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 )//&& traceEnt->NPC + { + if ( !Q_irand( 0, 2 ) && !Jedi_DodgeEvasion( traceEnt, self, &tr, HL_NONE ) ) + {//act like we didn't even hit him + VectorCopy( tr.endpos, start ); + ignore = tr.entityNum; + traces++; + continue; + } + } + //a Jedi is not dodging this shot + break; + } + traceEnt = &g_entities[tr.entityNum]; + if ( !WP_CheckForceDraineeStopMe( self, traceEnt ) ) + { + ForceDrainDamage( self, traceEnt, forward, tr.endpos ); + } + numDrained = 1; + } + + self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 200;//so we don't drain so damn fast! + } + self->client->ps.forcePowerRegenDebounceTime = level.time + 500; + + if ( !numDrained ) + {//always take 1 force point per frame that we're shooting this + WP_ForcePowerDrain( self, FP_DRAIN, 1 ); + } + else + { + WP_ForcePowerDrain( self, FP_DRAIN, numDrained );//was 2, but... + } + + return; +} + +void ForceDrainEnt( gentity_t *self, gentity_t *drainEnt ) +{ + if ( self->health <= 0 ) + { + return; + } + + if ( self->client->ps.forcePowerDebounce[FP_DRAIN] <= level.time ) + { + if ( !drainEnt ) + return; + if ( drainEnt == self ) + return; + if ( !drainEnt->inuse ) + return; + if ( !drainEnt->takedamage ) + return; + if ( drainEnt->health <= 0 )//no torturing corpses + return; + if ( !drainEnt->client ) + return; + if (OnSameTeam(self, drainEnt)) + return; + + vec3_t fwd; + AngleVectors( self->client->ps.viewangles, fwd, NULL, NULL ); + + drainEnt->painDebounceTime = 0; + ForceDrainDamage( self, drainEnt, fwd, drainEnt->currentOrigin ); + drainEnt->painDebounceTime = level.time + 2000; + + if ( drainEnt->s.number ) + { + if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_2 ) + {//do damage faster at level 3 + self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 100, 500 ); + } + else + { + self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 200, 800 ); + } + } + else + {//player takes damage faster + self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + Q_irand( 100, 500 ); + } + } + + self->client->ps.forcePowerRegenDebounceTime = level.time + 500; +} + +void ForceSeeing( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.forcePowersActive & (1 << FP_SEE)) ) + { + WP_ForcePowerStop( self, FP_SEE ); + return; + } + + if ( !WP_ForcePowerUsable( self, FP_SEE, 0 ) ) + { + return; + } + + WP_DebounceForceDeactivateTime( self ); + + WP_ForcePowerStart( self, FP_SEE, 0 ); + + G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/see.wav" ); +} + +void ForceProtect( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.forcePowersActive & (1 << FP_PROTECT)) ) + { + WP_ForcePowerStop( self, FP_PROTECT ); + return; + } + + if ( !WP_ForcePowerUsable( self, FP_PROTECT, 0 ) ) + { + return; + } + + // Make sure to turn off Force Rage and Force Absorb. + if (self->client->ps.forcePowersActive & (1 << FP_RAGE) ) + { + WP_ForcePowerStop( self, FP_RAGE ); + } + + WP_DebounceForceDeactivateTime( self ); + + WP_ForcePowerStart( self, FP_PROTECT, 0 ); + + if ( self->client->ps.saberLockTime < level.time ) + { + if ( self->client->ps.forcePowerLevel[FP_PROTECT] < FORCE_LEVEL_3 ) + {//animate + int parts = SETANIM_BOTH; + int anim = BOTH_FORCE_PROTECT; + if ( self->client->ps.forcePowerLevel[FP_PROTECT] > FORCE_LEVEL_1 ) + {//level 2 only does it on torso (can keep running) + parts = SETANIM_TORSO; + anim = BOTH_FORCE_PROTECT_FAST; + } + else + { + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + VectorClear( self->client->ps.velocity ); + } + if ( self->NPC ) + { + VectorClear( self->client->ps.moveDir ); + self->client->ps.speed = 0; + } + //FIXME: what if in air? + } + NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + //don't move or attack during this anim + if ( self->client->ps.forcePowerLevel[FP_PROTECT] < FORCE_LEVEL_2 ) + { + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + self->client->ps.pm_time = self->client->ps.weaponTime = self->client->ps.torsoAnimTimer; + if ( self->s.number ) + {//NPC + self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer; + } + else + {//player + self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer; + } + } + else + { + self->client->ps.weaponTime = self->client->ps.torsoAnimTimer; + } + } + } +} + +void ForceAbsorb( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.forcePowersActive & (1 << FP_ABSORB)) ) + { + WP_ForcePowerStop( self, FP_ABSORB ); + return; + } + + if ( !WP_ForcePowerUsable( self, FP_ABSORB, 0 ) ) + { + return; + } + + // Make sure to turn off Force Rage and Force Protection. + if (self->client->ps.forcePowersActive & (1 << FP_RAGE) ) + { + WP_ForcePowerStop( self, FP_RAGE ); + } + + WP_DebounceForceDeactivateTime( self ); + + WP_ForcePowerStart( self, FP_ABSORB, 0 ); + + if ( self->client->ps.saberLockTime < level.time ) + { + if ( self->client->ps.forcePowerLevel[FP_ABSORB] < FORCE_LEVEL_3 ) + {//must animate + int parts = SETANIM_BOTH; + if ( self->client->ps.forcePowerLevel[FP_ABSORB] > FORCE_LEVEL_1 ) + {//level 2 only does it on torso (can keep running) + parts = SETANIM_TORSO; + } + else + { + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + VectorClear( self->client->ps.velocity ); + } + if ( self->NPC ) + { + VectorClear( self->client->ps.moveDir ); + self->client->ps.speed = 0; + } + //FIXME: what if in air? + } + /* + //if in air, only do on torso - NOTE: or moving? + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE )//|| !VectorCompare( self->client->ps.velocity, vec3_origin ) ) + { + parts = SETANIM_TORSO; + } + */ + NPC_SetAnim( self, parts, BOTH_FORCE_ABSORB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.weaponTime = self->client->ps.torsoAnimTimer; + if ( parts == SETANIM_BOTH ) + {//can't move + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + self->client->ps.pm_time = self->client->ps.legsAnimTimer = self->client->ps.torsoAnimTimer;// = self->client->ps.forcePowerDuration[FP_ABSORB]; + if ( self->s.number ) + {//NPC + self->painDebounceTime = level.time + self->client->ps.pm_time;//self->client->ps.forcePowerDuration[FP_ABSORB]; + } + else + {//player + self->aimDebounceTime = level.time + self->client->ps.pm_time;//self->client->ps.forcePowerDuration[FP_ABSORB]; + } + } + //stop saber + //WP_DeactivateSaber( self );//turn off saber when meditating + self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + self->client->ps.saberBlocked = BLOCKED_NONE; + } + } +} + +void ForceRage( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + + //FIXME: prevent them from using any other force powers when raging? + + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.forcePowersActive & (1 << FP_RAGE)) ) + { + WP_ForcePowerStop( self, FP_RAGE ); + return; + } + + if ( !WP_ForcePowerUsable( self, FP_RAGE, 0 ) ) + { + return; + } + + if (self->client->ps.forceRageRecoveryTime >= level.time) + { + return; + } + + if ( self->s.number < MAX_CLIENTS + && self->health < 25 ) + {//have to have at least 25 health to start it + return; + } + + if (self->health < 10) + { + return; + } + + // Make sure to turn off Force Protection and Force Absorb. + if (self->client->ps.forcePowersActive & (1 << FP_PROTECT) ) + { + WP_ForcePowerStop( self, FP_PROTECT ); + } + if (self->client->ps.forcePowersActive & (1 << FP_ABSORB) ) + { + WP_ForcePowerStop( self, FP_ABSORB ); + } + + WP_DebounceForceDeactivateTime( self ); + + WP_ForcePowerStart( self, FP_RAGE, 0 ); + + if ( self->client->ps.saberLockTime < level.time ) + { + if ( self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_3 ) + {//must animate + if ( self->client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_2 ) + {//have to stand still for whole length of anim + //FIXME: if in air, only do on torso? + NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_RAGE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + //don't attack during this anim + self->client->ps.weaponTime = self->client->ps.torsoAnimTimer; + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + self->client->ps.pm_time = self->client->ps.torsoAnimTimer; + if ( self->s.number ) + {//NPC + self->painDebounceTime = level.time + self->client->ps.torsoAnimTimer; + } + else + {//player + self->aimDebounceTime = level.time + self->client->ps.torsoAnimTimer; + } + } + else + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_RAGE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + //don't attack during this anim + self->client->ps.weaponTime = self->client->ps.torsoAnimTimer; + } + //stop saber + self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + self->client->ps.saberBlocked = BLOCKED_NONE; + } + } +} + +void ForceJumpCharge( gentity_t *self, usercmd_t *ucmd ) +{ + float forceJumpChargeInterval = forceJumpStrength[0] / (FORCE_JUMP_CHARGE_TIME/FRAMETIME); + + if ( self->health <= 0 ) + { + return; + } + if ( !self->s.number && cg.zoomMode ) + {//can't force jump when zoomed in + return; + } + + //need to play sound + if ( !self->client->ps.forceJumpCharge ) + {//FIXME: this should last only as long as the actual charge-up + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jumpbuild.wav" ); +#ifdef _IMMERSION + G_Force( self, G_ForceIndex( "fffx/weapons/force/jumpbuild", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + } + //Increment + self->client->ps.forceJumpCharge += forceJumpChargeInterval; + + //clamp to max strength for current level + if ( self->client->ps.forceJumpCharge > forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]] ) + { + self->client->ps.forceJumpCharge = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]]; + } + + //clamp to max available force power + if ( self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] > self->client->ps.forcePower ) + {//can't use more than you have + self->client->ps.forceJumpCharge = self->client->ps.forcePower*forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME); + } + //FIXME: a simple tap should always do at least a normal height's jump? +} + +int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd ) +{ + float pushFwd = 0, pushRt = 0; + vec3_t view, forward, right; + VectorCopy( self->client->ps.viewangles, view ); + view[0] = 0; + AngleVectors( view, forward, right, NULL ); + if ( ucmd->forwardmove && ucmd->rightmove ) + { + if ( ucmd->forwardmove > 0 ) + { + pushFwd = 50; + } + else + { + pushFwd = -50; + } + if ( ucmd->rightmove > 0 ) + { + pushRt = 50; + } + else + { + pushRt = -50; + } + } + else if ( ucmd->forwardmove || ucmd->rightmove ) + { + if ( ucmd->forwardmove > 0 ) + { + pushFwd = 100; + } + else if ( ucmd->forwardmove < 0 ) + { + pushFwd = -100; + } + else if ( ucmd->rightmove > 0 ) + { + pushRt = 100; + } + else if ( ucmd->rightmove < 0 ) + { + pushRt = -100; + } + } + VectorMA( self->client->ps.velocity, pushFwd, forward, jumpVel ); + VectorMA( self->client->ps.velocity, pushRt, right, jumpVel ); + jumpVel[2] += self->client->ps.forceJumpCharge;//forceJumpStrength; + if ( pushFwd > 0 && self->client->ps.forceJumpCharge > 200 ) + { + return FJ_FORWARD; + } + else if ( pushFwd < 0 && self->client->ps.forceJumpCharge > 200 ) + { + return FJ_BACKWARD; + } + else if ( pushRt > 0 && self->client->ps.forceJumpCharge > 200 ) + { + return FJ_RIGHT; + } + else if ( pushRt < 0 && self->client->ps.forceJumpCharge > 200 ) + { + return FJ_LEFT; + } + else + {//FIXME: jump straight up anim + return FJ_UP; + } +} + +void ForceJump( gentity_t *self, usercmd_t *ucmd ) +{ + if ( self->client->ps.forcePowerDuration[FP_LEVITATION] > level.time ) + { + return; + } + if ( !WP_ForcePowerUsable( self, FP_LEVITATION, 0 ) ) + { + return; + } + if ( self->s.groundEntityNum == ENTITYNUM_NONE ) + { + return; + } + if ( self->client->ps.pm_flags&PMF_JUMP_HELD ) + { + return; + } + if ( self->health <= 0 ) + { + return; + } + if ( !self->s.number && (cg.zoomMode || in_camera) ) + {//can't force jump when zoomed in or in cinematic + return; + } + if ( self->client->ps.saberLockTime > level.time ) + {//FIXME: can this be a way to break out? + return; + } + + if ( self->client->NPC_class == CLASS_BOBAFETT + || self->client->NPC_class == CLASS_ROCKETTROOPER ) + { + if ( self->client->ps.forceJumpCharge > 300 ) + { + JET_FlyStart(NPC); + } + else + { + G_AddEvent( self, EV_JUMP, 0 ); + } + } + else + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } +#ifdef _IMMERSION + G_Force( self, G_ForceIndex( "fffx/weapons/force/jump", FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + + float forceJumpChargeInterval = forceJumpStrength[self->client->ps.forcePowerLevel[FP_LEVITATION]]/(FORCE_JUMP_CHARGE_TIME/FRAMETIME); + + int anim; + vec3_t jumpVel; + + switch( WP_GetVelocityForForceJump( self, jumpVel, ucmd ) ) + { + case FJ_FORWARD: + if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 ) + || ( self->NPC && + self->NPC->rank != RANK_CREWMAN && + self->NPC->rank <= RANK_LT_JG ) ) + {//can't do acrobatics + anim = BOTH_FORCEJUMP1; + } + else + { + if ( self->client->NPC_class == CLASS_ALORA && Q_irand( 0, 3 ) ) + { + anim = Q_irand( BOTH_ALORA_FLIP_1, BOTH_ALORA_FLIP_3 ); + } + else + { + anim = BOTH_FLIP_F; + } + } + break; + case FJ_BACKWARD: + if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 ) + || ( self->NPC && + self->NPC->rank != RANK_CREWMAN && + self->NPC->rank <= RANK_LT_JG ) ) + {//can't do acrobatics + anim = BOTH_FORCEJUMPBACK1; + } + else + { + anim = BOTH_FLIP_B; + } + break; + case FJ_RIGHT: + if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 ) + || ( self->NPC && + self->NPC->rank != RANK_CREWMAN && + self->NPC->rank <= RANK_LT_JG ) ) + {//can't do acrobatics + anim = BOTH_FORCEJUMPRIGHT1; + } + else + { + anim = BOTH_FLIP_R; + } + break; + case FJ_LEFT: + if ( ((self->client->NPC_class == CLASS_BOBAFETT||self->client->NPC_class == CLASS_ROCKETTROOPER) && self->client->ps.forceJumpCharge > 300 ) + || ( self->NPC && + self->NPC->rank != RANK_CREWMAN && + self->NPC->rank <= RANK_LT_JG ) ) + {//can't do acrobatics + anim = BOTH_FORCEJUMPLEFT1; + } + else + { + anim = BOTH_FLIP_L; + } + break; + default: + case FJ_UP: + anim = BOTH_JUMP1; + break; + } + + int parts = SETANIM_BOTH; + if ( self->client->ps.weaponTime ) + {//FIXME: really only care if we're in a saber attack anim.. maybe trail length? + parts = SETANIM_LEGS; + } + + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + //FIXME: sound effect + self->client->ps.forceJumpZStart = self->currentOrigin[2];//remember this for when we land + VectorCopy( jumpVel, self->client->ps.velocity ); + //wasn't allowing them to attack when jumping, but that was annoying + //self->client->ps.weaponTime = self->client->ps.torsoAnimTimer; + + WP_ForcePowerStart( self, FP_LEVITATION, self->client->ps.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[FP_LEVITATION] ); + //self->client->ps.forcePowerDuration[FP_LEVITATION] = level.time + self->client->ps.weaponTime; + self->client->ps.forceJumpCharge = 0; +} + +int WP_AbsorbConversion(gentity_t *attacked, int atdAbsLevel, gentity_t *attacker, int atPower, int atPowerLevel, int atForceSpent) +{ + int getLevel = 0; + int addTot = 0; + + if (atPower != FP_LIGHTNING && + atPower != FP_DRAIN && + atPower != FP_GRIP && + atPower != FP_PUSH && + atPower != FP_PULL) + { //Only these powers can be absorbed + return -1; + } + + if (!atdAbsLevel) + { //looks like attacker doesn't have any absorb power + return -1; + } + + if (!(attacked->client->ps.forcePowersActive & (1 << FP_ABSORB))) + { //absorb is not active + return -1; + } + + //Subtract absorb power level from the offensive force power + getLevel = atPowerLevel; + getLevel -= atdAbsLevel; + + if (getLevel < 0) + { + getLevel = 0; + } + + //let the attacker absorb an amount of force used in this attack based on his level of absorb + addTot = (atForceSpent/3)*attacked->client->ps.forcePowerLevel[FP_ABSORB]; + + if (addTot < 1 && atForceSpent >= 1) + { + addTot = 1; + } + attacked->client->ps.forcePower += addTot; + if (attacked->client->ps.forcePower > 100) + { + attacked->client->ps.forcePower = 100; + } + + G_SoundOnEnt( attacked, CHAN_ITEM, "sound/weapons/force/absorbhit.wav" ); + + return getLevel; +} + +void WP_ForcePowerRegenerate( gentity_t *self, int overrideAmt ) +{ + if ( !self->client ) + { + return; + } + + if ( self->client->ps.forcePower < self->client->ps.forcePowerMax ) + { + if ( overrideAmt ) + { + self->client->ps.forcePower += overrideAmt; + } + else + { + self->client->ps.forcePower++; + } + if ( self->client->ps.forcePower > self->client->ps.forcePowerMax ) + { + self->client->ps.forcePower = self->client->ps.forcePowerMax; + } + } +} + +static bool infiniteForce = false; +bool Cheat_InfiniteForce( void ) +{ + infiniteForce = !infiniteForce; + return true; +} + +void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt ) +{ + // PCs have infinite force power when the cheat is turned on + if ( self->NPC || infiniteForce ) + {//For now, NPCs have infinite force power + return; + } + //take away the power + int drain = overrideAmt; + if ( !drain ) + { + drain = forcePowerNeeded[forcePower]; + } + if ( !drain ) + { + return; + } + self->client->ps.forcePower -= drain; + if ( self->client->ps.forcePower < 0 ) + { + self->client->ps.forcePower = 0; + } +} + +void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt ) +{ + int duration = 0; + + //FIXME: debounce some of these? + self->client->ps.forcePowerDebounce[forcePower] = 0; + + //and it in + //set up duration time + switch( (int)forcePower ) + { + case FP_HEAL: + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + self->client->ps.forceHealCount = 0; + WP_StartForceHealEffects( self ); + break; + case FP_LEVITATION: + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_SPEED: + //duration is always 5 seconds, player time + duration = ceil(FORCE_SPEED_DURATION*forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]]);//FIXME: because the timescale scales down (not instant), this doesn't end up being exactly right... + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + self->s.loopSound = G_SoundIndex( "sound/weapons/force/speedloop.wav" ); + if ( self->client->ps.forcePowerLevel[FP_SPEED] > FORCE_LEVEL_2 ) + {//HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else + self->client->ps.forcePowerDebounce[FP_SPEED] = level.time; + } + break; + case FP_PUSH: + break; + case FP_PULL: + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_TELEPATHY: + break; + case FP_GRIP: + duration = 1000; + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + //HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else + //self->client->ps.forcePowerDebounce[forcePower] = level.time; + break; + case FP_LIGHTNING: + duration = overrideAmt; + overrideAmt = 0; + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + break; + //new Jedi Academy force powers + case FP_RAGE: + //duration is always 5 seconds, player time + duration = ceil(FORCE_RAGE_DURATION*forceSpeedValue[self->client->ps.forcePowerLevel[FP_RAGE]-1]);//FIXME: because the timescale scales down (not instant), this doesn't end up being exactly right... + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/rage.mp3" ); + self->s.loopSound = G_SoundIndex( "sound/weapons/force/rageloop.wav" ); + if ( self->chestBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/rage2" ), self->playerModel, self->chestBolt, self->s.number, self->currentOrigin, duration, qtrue ); + } + break; + case FP_DRAIN: + if ( self->client->ps.forcePowerLevel[forcePower] > FORCE_LEVEL_1 + && self->client->ps.forceDrainEntityNum >= ENTITYNUM_WORLD ) + { + duration = overrideAmt; + overrideAmt = 0; + //HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else + self->client->ps.forcePowerDebounce[forcePower] = level.time; + } + else + { + duration = 1000; + } + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_PROTECT: + switch ( self->client->ps.forcePowerLevel[FP_PROTECT] ) + { + case FORCE_LEVEL_3: + duration = 20000; + break; + case FORCE_LEVEL_2: + duration = 15000; + break; + case FORCE_LEVEL_1: + default: + + duration = 10000; + break; + } + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/protect.mp3" ); + self->s.loopSound = G_SoundIndex( "sound/weapons/force/protectloop.wav" ); + break; + case FP_ABSORB: + duration = 20000; + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/absorb.mp3" ); + self->s.loopSound = G_SoundIndex( "sound/weapons/force/absorbloop.wav" ); + break; + case FP_SEE: + if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_1 ) + { + duration = 5000; + } + else if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_2 ) + { + duration = 10000; + } + else// if ( self->client->ps.forcePowerLevel[FP_SEE] == FORCE_LEVEL_3 ) + { + duration = 20000; + } + + self->client->ps.forcePowersActive |= ( 1 << forcePower ); + G_SoundOnEnt( self, CHAN_ITEM, "sound/weapons/force/see.mp3" ); + self->s.loopSound = G_SoundIndex( "sound/weapons/force/seeloop.wav" ); + break; + default: + break; + } + if ( duration ) + { + self->client->ps.forcePowerDuration[forcePower] = level.time + duration; + } + else + { + self->client->ps.forcePowerDuration[forcePower] = 0; + } + + WP_ForcePowerDrain( self, forcePower, overrideAmt ); + + if ( !self->s.number ) + { + self->client->sess.missionStats.forceUsed[(int)forcePower]++; + } +} + +qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ) +{ + if ( forcePower == FP_LEVITATION ) + { + return qtrue; + } + int drain = overrideAmt?overrideAmt:forcePowerNeeded[forcePower]; + if ( !drain ) + { + return qtrue; + } + if ( self->client->ps.forcePower < drain ) + { + //G_AddEvent( self, EV_NOAMMO, 0 ); + return qfalse; + } + return qtrue; +} + +extern void CG_PlayerLockedWeaponSpeech( int jumping ); +extern qboolean Rosh_TwinNearBy( gentity_t *self ); +qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ) +{ + if ( !(self->client->ps.forcePowersKnown & ( 1 << forcePower )) ) + {//don't know this power + return qfalse; + } + else if ( self->NPC && (self->NPC->aiFlags&NPCAI_ROSH) ) + { + if ( ((1<client->ps.forcePowerLevel[forcePower] <= 0 ) + {//can't use this power + return qfalse; + } + + if( (self->flags&FL_LOCK_PLAYER_WEAPONS) ) // yes this locked weapons check also includes force powers, if we need a separate check later I'll make one + { + if ( self->s.number < MAX_CLIENTS ) + { + CG_PlayerLockedWeaponSpeech( qfalse ); + } + return qfalse; + } + + if ( in_camera && self->s.number < MAX_CLIENTS ) + {//player can't turn on force powers duing cinematics + return qfalse; + } + + if ( PM_LockedAnim( self->client->ps.torsoAnim ) && self->client->ps.torsoAnimTimer ) + {//no force powers during these special anims + return qfalse; + } + if ( PM_SuperBreakLoseAnim( self->client->ps.torsoAnim ) + || PM_SuperBreakWinAnim( self->client->ps.torsoAnim ) ) + { + return qfalse; + } + + if ( (self->client->ps.forcePowersActive & ( 1 << forcePower )) ) + {//already using this power + return qfalse; + } + /* + if ( !self->client->ps.forcePowerLevel[(int)(forcePower)] ) + { + return qfalse; + } + */ + if ( self->client->NPC_class == CLASS_ATST ) + {//Doh! No force powers in an AT-ST! + return qfalse; + } + Vehicle_t *pVeh = NULL; + if ( (pVeh = G_IsRidingVehicle( self )) != NULL ) + {//Doh! No force powers when flying a vehicle! + if ( pVeh->m_pVehicleInfo->numHands > 1 ) + {//if in a two-handed vehicle + return qfalse; + } + } + if ( self->client->ps.viewEntity > 0 && self->client->ps.viewEntity < ENTITYNUM_WORLD ) + {//Doh! No force powers when controlling an NPC + return qfalse; + } + if ( self->client->ps.eFlags & EF_LOCKED_TO_WEAPON ) + {//Doh! No force powers when in an emplaced gun! + return qfalse; + } + + if ( self->client->ps.saber[0].singleBladeThrowable//SaberStaff() //using staff + && !self->client->ps.dualSabers //only 1, in right hand + && !self->client->ps.saber[0].blade[1].active )//only first blade is on + {//allow power + //FIXME: externalize this condition seperately? + } + else + { + if ( forcePower == FP_SABERTHROW && !self->client->ps.saber[0].throwable ) + {//cannot throw this kind of saber + return qfalse; + } + + if ( self->client->ps.saber[0].Active() ) + { + if ( self->client->ps.saber[0].twoHanded ) + { + if ( g_saberRestrictForce->integer ) + { + switch ( forcePower ) + { + case FP_PUSH: + case FP_PULL: + case FP_TELEPATHY: + case FP_GRIP: + case FP_LIGHTNING: + case FP_DRAIN: + return qfalse; + break; + } + } + } + if ( self->client->ps.saber[0].twoHanded || (self->client->ps.dualSabers && self->client->ps.saber[1].Active()) ) + {//this saber requires the use of two hands OR our other hand is using an active saber too + if ( (self->client->ps.saber[0].forceRestrictions&(1<client->ps.dualSabers && self->client->ps.saber[1].Active() ) + { + if ( g_saberRestrictForce->integer ) + { + switch ( forcePower ) + { + case FP_PUSH: + case FP_PULL: + case FP_TELEPATHY: + case FP_GRIP: + case FP_LIGHTNING: + case FP_DRAIN: + return qfalse; + break; + } + } + if ( (self->client->ps.saber[1].forceRestrictions&(1<client->ps.forcePowersActive&(1<client->ps.forcePowersActive &= ~( 1 << forcePower ); + + switch( (int)forcePower ) + { + case FP_HEAL: + //if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 ) + {//wasn't an instant heal and heal is now done + if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 ) + {//if in meditation pose, must come out of it + //FIXME: BOTH_FORCEHEAL_STOP + if ( self->client->ps.legsAnim == BOTH_FORCEHEAL_START ) + { + NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( self->client->ps.torsoAnim == BOTH_FORCEHEAL_START ) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + self->client->ps.saberMove = self->client->ps.saberBounceMove = LS_READY;//don't finish whatever saber anim you may have been in + self->client->ps.saberBlocked = BLOCKED_NONE; + } + } + WP_StopForceHealEffects( self ); + if (self->health >= self->client->ps.stats[STAT_MAX_HEALTH]/3) + { + gi.G2API_ClearSkinGore(self->ghoul2); + } + break; + case FP_LEVITATION: + self->client->ps.forcePowerDebounce[FP_LEVITATION] = 0; + break; + case FP_SPEED: + if ( !self->s.number ) + {//player using force speed + if ( g_timescale->value != 1.0 ) + { + if ( !(self->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_2 ) + {//not slowed down because of force rage + gi.cvar_set("timescale", "1"); + } + } + } + //FIXME: reset my current anim, keeping current frame, but with proper anim speed + // otherwise, the anim will continue playing at high speed + self->s.loopSound = 0; + break; + case FP_PUSH: + break; + case FP_PULL: + break; + case FP_TELEPATHY: + break; + case FP_GRIP: + if ( self->NPC ) + { + TIMER_Set( self, "gripping", -level.time ); + } + if ( self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD ) + { + gripEnt = &g_entities[self->client->ps.forceGripEntityNum]; + if ( gripEnt ) + { + gripEnt->s.loopSound = 0; + if ( gripEnt->client ) + { + gripEnt->client->ps.eFlags &= ~EF_FORCE_GRIPPED; + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//sanity-cap the velocity + float gripVel = VectorNormalize( gripEnt->client->ps.velocity ); + if ( gripVel > 500.0f ) + { + gripVel = 500.0f; + } + VectorScale( gripEnt->client->ps.velocity, gripVel, gripEnt->client->ps.velocity ); + } + + //FIXME: they probably dropped their weapon, should we make them flee? Or should AI handle no-weapon behavior? +//rww - RAGDOLL_BEGIN +#ifndef JK2_RAGDOLL_GRIPNOHEALTH +//rww - RAGDOLL_END + if ( gripEnt->health > 0 ) +//rww - RAGDOLL_BEGIN +#endif +//rww - RAGDOLL_END + { + int holdTime = 500; + if ( gripEnt->health > 0 ) + { + G_AddEvent( gripEnt, EV_WATER_CLEAR, 0 ); + } + if ( gripEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time ) + {//they probably pushed out of it + holdTime = 0; + } + else if ( gripEnt->s.weapon == WP_SABER ) + {//jedi recover faster + holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*200; + } + else + { + holdTime = self->client->ps.forcePowerLevel[FP_GRIP]*500; + } + //stop the anims soon, keep them locked in place for a bit + if ( gripEnt->client->ps.torsoAnim == BOTH_CHOKE1 || gripEnt->client->ps.torsoAnim == BOTH_CHOKE3 ) + {//stop choking anim on torso + if ( gripEnt->client->ps.torsoAnimTimer > holdTime ) + { + gripEnt->client->ps.torsoAnimTimer = holdTime; + } + } + if ( gripEnt->client->ps.legsAnim == BOTH_CHOKE1 || gripEnt->client->ps.legsAnim == BOTH_CHOKE3 ) + {//stop choking anim on legs + gripEnt->client->ps.legsAnimTimer = 0; + if ( holdTime ) + { + //lock them in place for a bit + gripEnt->client->ps.pm_time = gripEnt->client->ps.torsoAnimTimer; + gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + if ( gripEnt->s.number ) + {//NPC + gripEnt->painDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer; + } + else + {//player + gripEnt->aimDebounceTime = level.time + gripEnt->client->ps.torsoAnimTimer; + } + } + } + if ( gripEnt->NPC ) + { + if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + {//not falling to their death + gripEnt->NPC->nextBStateThink = level.time + holdTime; + } + //if still alive after stopped gripping, let them wake others up + if ( gripEnt->health > 0 ) + { + G_AngerAlert( gripEnt ); + } + } + } + } + else + { + gripEnt->s.eFlags &= ~EF_FORCE_GRIPPED; + if ( gripEnt->s.eType == ET_MISSILE ) + {//continue normal movement + if ( gripEnt->s.weapon == WP_THERMAL ) + { + gripEnt->s.pos.trType = TR_INTERPOLATE; + } + else + { + gripEnt->s.pos.trType = TR_LINEAR;//FIXME: what about gravity-effected projectiles? + } + VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase ); + gripEnt->s.pos.trTime = level.time; + } + else + {//drop it + gripEnt->e_ThinkFunc = thinkF_G_RunObject; + gripEnt->nextthink = level.time + FRAMETIME; + gripEnt->s.pos.trType = TR_GRAVITY; + VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase ); + gripEnt->s.pos.trTime = level.time; + } + } + } + self->s.loopSound = 0; + self->client->ps.forceGripEntityNum = ENTITYNUM_NONE; + } + if ( self->client->ps.torsoAnim == BOTH_FORCEGRIP_HOLD ) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCEGRIP_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + break; + case FP_LIGHTNING: + if ( self->NPC ) + { + TIMER_Set( self, "holdLightning", -level.time ); + } + if ( self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_HOLD + || self->client->ps.torsoAnim == BOTH_FORCELIGHTNING_START ) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD + || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START ) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_2HANDEDLIGHTNING_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] < FORCE_LEVEL_2 ) + {//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal) + self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 3000;//FIXME: define? + } + else + {//stop the looping sound + self->client->ps.forcePowerDebounce[FP_LIGHTNING] = level.time + 1000;//FIXME: define? + self->s.loopSound = 0; + } + break; + case FP_RAGE: + self->client->ps.forceRageRecoveryTime = level.time + 10000;//recover for 10 seconds + if ( self->client->ps.forcePowerDuration[FP_RAGE] > level.time ) + {//still had time left, we cut it short + self->client->ps.forceRageRecoveryTime -= (self->client->ps.forcePowerDuration[FP_RAGE] - level.time);//minus however much time you had left when you cut it short + } + if ( !self->s.number ) + {//player using force speed + if ( g_timescale->value != 1.0 ) + { + if ( !(self->client->ps.forcePowersActive&(1<s.loopSound = 0; + if ( self->NPC ) + { + Jedi_RageStop( self ); + } + if ( self->chestBolt != -1 ) + { + G_StopEffect("force/rage2", self->playerModel, self->chestBolt, self->s.number ); + } + break; + case FP_DRAIN: + if ( self->NPC ) + { + TIMER_Set( self, "draining", -level.time ); + } + if ( self->client->ps.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 ) + {//don't do it again for 3 seconds, minimum... FIXME: this should be automatic once regeneration is slower (normal) + self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 3000;//FIXME: define? + } + else + {//stop the looping sound + self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 1000;//FIXME: define? + self->s.loopSound = 0; + } + //drop them + if ( self->client->ps.forceDrainEntityNum < ENTITYNUM_WORLD ) + { + drainEnt = &g_entities[self->client->ps.forceDrainEntityNum]; + if ( drainEnt ) + { + if ( drainEnt->client ) + { + drainEnt->client->ps.eFlags &= ~EF_FORCE_DRAINED; + //VectorClear( drainEnt->client->ps.velocity ); + if ( drainEnt->health > 0 ) + { + if ( drainEnt->client->ps.forcePowerDebounce[FP_PUSH] > level.time ) + {//they probably pushed out of it + } + else + { + //NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_HUGGEESTOP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( drainEnt->client->ps.torsoAnim != BOTH_FORCEPUSH ) + {//don't stop the push + drainEnt->client->ps.torsoAnimTimer = 0; + } + drainEnt->client->ps.legsAnimTimer = 0; + } + if ( drainEnt->NPC ) + {//if still alive after stopped draining, let them wake others up + G_AngerAlert( drainEnt ); + } + } + else + {//leave the effect playing on them for a few seconds + //drainEnt->client->ps.eFlags |= EF_FORCE_DRAINED; + drainEnt->s.powerups |= ( 1 << PW_DRAINED ); + drainEnt->client->ps.powerups[PW_DRAINED] = level.time + Q_irand( 1000, 4000 ); + } + } + } + self->client->ps.forceDrainEntityNum = ENTITYNUM_NONE; + } + if ( self->client->ps.torsoAnim == BOTH_HUGGER1 ) + {//old anim + NPC_SetAnim( self, SETANIM_BOTH, BOTH_HUGGERSTOP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_START + || self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_GRAB_HOLD ) + {//new anim + NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_HOLD + || self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_START ) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_RELEASE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + break; + case FP_PROTECT: + self->s.loopSound = 0; + break; + case FP_ABSORB: + self->s.loopSound = 0; + if ( self->client->ps.legsAnim == BOTH_FORCE_ABSORB_START ) + { + NPC_SetAnim( self, SETANIM_LEGS, BOTH_FORCE_ABSORB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( self->client->ps.torsoAnim == BOTH_FORCE_ABSORB_START ) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_ABSORB_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( self->client->ps.forcePowerLevel[FP_ABSORB] < FORCE_LEVEL_2 ) + {//was stuck, free us in case we interrupted it or something + self->client->ps.weaponTime = 0; + self->client->ps.pm_flags &= ~PMF_TIME_KNOCKBACK; + self->client->ps.pm_time = 0; + if ( self->s.number ) + {//NPC + self->painDebounceTime = 0; + } + else + {//player + self->aimDebounceTime = 0; + } + } + break; + case FP_SEE: + self->s.loopSound = 0; + break; + default: + break; + } +} + +void WP_ForceForceThrow( gentity_t *thrower ) +{ + if ( !thrower || !thrower->client ) + { + return; + } + qboolean removePush = qfalse; + qboolean relock = qfalse; + if ( !(thrower->client->ps.forcePowersKnown&(1<client->ps.forcePowersKnown |= (1<client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1; + removePush = qtrue; + } + + if ( thrower->NPC + && (thrower->NPC->aiFlags&NPCAI_HEAL_ROSH) + && (thrower->flags&FL_LOCK_PLAYER_WEAPONS) ) + { + thrower->flags &= ~FL_LOCK_PLAYER_WEAPONS; + relock = qtrue; + } + + ForceThrow( thrower, qfalse ); + + if ( relock ) + { + thrower->flags |= FL_LOCK_PLAYER_WEAPONS; + } + + if ( thrower ) + {//take it back off + thrower->client->ps.forcePowersKnown &= ~(1<client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_0; + } +} + +extern qboolean PM_ForceJumpingUp( gentity_t *gent ); +static void WP_ForcePowerRun( gentity_t *self, forcePowers_t forcePower, usercmd_t *cmd ) +{ + float speed, newSpeed; + gentity_t *gripEnt, *drainEnt; + vec3_t angles, dir, gripOrg, gripEntOrg; + float dist; + extern usercmd_t ucmd; + + switch( (int)forcePower ) + { + case FP_HEAL: + if ( self->client->ps.forceHealCount >= FP_MaxForceHeal(self) || self->health >= self->client->ps.stats[STAT_MAX_HEALTH] ) + {//fully healed or used up all 25 + if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) ) + { + int index = Q_irand( 1, 4 ); + if ( self->s.number < MAX_CLIENTS ) + { + G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_%c.mp3", index, g_sex->string[0] ) ); + } + else if ( self->NPC ) + { + if ( self->NPC->blockedSpeechDebounceTime <= level.time ) + {//enough time has passed since our last speech + if ( Q3_TaskIDPending( self, TID_CHAN_VOICE ) ) + {//not playing a scripted line + //say "Ahhh...." + if ( self->NPC->stats.sex == SEX_MALE + || self->NPC->stats.sex == SEX_NEUTRAL ) + { + G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_m.mp3", index ) ); + } + else//all other sexes use female sounds + { + G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_f.mp3", index ) ); + } + } + } + } +#ifdef _IMMERSION + G_Force( self, G_ForceIndex( va( "fffx/weapons/force/heal%d", index ), FF_CHANNEL_WEAPON ) ); +#endif // _IMMERSION + } + WP_ForcePowerStop( self, forcePower ); + } + else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 && ( (cmd->buttons&BUTTON_ATTACK) || (cmd->buttons&BUTTON_ALT_ATTACK) || self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) ) ) + {//attacked or was hit while healing... + //stop healing + WP_ForcePowerStop( self, forcePower ); + } + else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 && ( cmd->rightmove || cmd->forwardmove || cmd->upmove > 0 ) ) + {//moved while healing... FIXME: also, in WP_ForcePowerStart, stop healing if any other force power is used + //stop healing + WP_ForcePowerStop( self, forcePower ); + } + else if ( self->client->ps.forcePowerDebounce[FP_HEAL] < level.time ) + {//time to heal again + if ( WP_ForcePowerAvailable( self, forcePower, 4 ) ) + {//have available power + int healInterval = FP_ForceHealInterval( self ); + int healAmount = 1;//hard, normal healing rate + if ( self->s.number < MAX_CLIENTS ) + { + if ( g_spskill->integer == 1 ) + {//medium, heal twice as fast + healAmount *= 2; + } + else if ( g_spskill->integer == 0 ) + {//easy, heal 3 times as fast... + healAmount *= 3; + } + } + if ( self->health + healAmount > self->client->ps.stats[STAT_MAX_HEALTH] ) + { + healAmount = self->client->ps.stats[STAT_MAX_HEALTH] - self->health; + } + self->health += healAmount; + self->client->ps.forceHealCount += healAmount; + self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + healInterval; + WP_ForcePowerDrain( self, forcePower, 4 ); + } + else + {//stop + WP_ForcePowerStop( self, forcePower ); + } + } + break; + case FP_LEVITATION: + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.forceJumpZStart ) + {//done with jump + WP_ForcePowerStop( self, forcePower ); + } + else + { + if ( PM_ForceJumpingUp( self ) ) + {//holding jump in air + if ( cmd->upmove > 10 ) + {//still trying to go up + if ( WP_ForcePowerAvailable( self, FP_LEVITATION, 1 ) ) + { + if ( self->client->ps.forcePowerDebounce[FP_LEVITATION] < level.time ) + { + WP_ForcePowerDrain( self, FP_LEVITATION, 5 ); + self->client->ps.forcePowerDebounce[FP_LEVITATION] = level.time + 100; + } + self->client->ps.forcePowersActive |= ( 1 << FP_LEVITATION ); + self->client->ps.forceJumpCharge = 1;//just used as a flag for the player, cleared when he lands + } + else + {//cut the jump short + WP_ForcePowerStop( self, forcePower ); + } + } + else + {//cut the jump short + WP_ForcePowerStop( self, forcePower ); + } + } + else + { + WP_ForcePowerStop( self, forcePower ); + } + } + break; + case FP_SPEED: + speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]]; + if ( !self->s.number ) + {//player using force speed + if ( !(self->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_SPEED] >= self->client->ps.forcePowerLevel[FP_RAGE] ) + {//either not using rage or rage is at a lower level than speed + gi.cvar_set("timescale", va("%4.2f", speed)); + if ( g_timescale->value > speed ) + { + newSpeed = g_timescale->value - 0.05; + if ( newSpeed < speed ) + { + newSpeed = speed; + } + gi.cvar_set("timescale", va("%4.2f", newSpeed)); + } + } + } + break; + case FP_PUSH: + break; + case FP_PULL: + break; + case FP_TELEPATHY: + break; + case FP_GRIP: + if ( !WP_ForcePowerAvailable( self, FP_GRIP, 0 ) + || (self->client->ps.forcePowerLevel[FP_GRIP]>FORCE_LEVEL_1&&!self->s.number&&!(cmd->buttons&BUTTON_FORCEGRIP)) ) + { + WP_ForcePowerStop( self, FP_GRIP ); + return; + } + else if ( self->client->ps.forceGripEntityNum >= 0 && self->client->ps.forceGripEntityNum < ENTITYNUM_WORLD ) + { + gripEnt = &g_entities[self->client->ps.forceGripEntityNum]; + + if ( !gripEnt || !gripEnt->inuse ) + {//invalid or freed ent + WP_ForcePowerStop( self, FP_GRIP ); + return; + } + else +//rww - RAGDOLL_BEGIN +#ifndef JK2_RAGDOLL_GRIPNOHEALTH +//rww - RAGDOLL_END + if ( gripEnt->health <= 0 && gripEnt->takedamage )//FIXME: what about things that never had health or lose takedamage when they die? + {//either invalid ent, or dead ent + WP_ForcePowerStop( self, FP_GRIP ); + return; + } + else +//rww - RAGDOLL_BEGIN +#endif +//rww - RAGDOLL_END + if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_1 + && gripEnt->client + && gripEnt->client->ps.groundEntityNum == ENTITYNUM_NONE + && gripEnt->client->moveType != MT_FLYSWIM ) + { + WP_ForcePowerStop( self, FP_GRIP ); + return; + } + else if ( gripEnt->client && gripEnt->client->moveType == MT_FLYSWIM && VectorLengthSquared( gripEnt->client->ps.velocity ) > (300*300) ) + {//flying creature broke free + WP_ForcePowerStop( self, FP_GRIP ); + return; + } + else if ( gripEnt->client + && gripEnt->health>0 //dead dudes don't fly + && (gripEnt->client->NPC_class == CLASS_BOBAFETT || gripEnt->client->NPC_class == CLASS_ROCKETTROOPER) + && self->client->ps.forcePowerDebounce[FP_GRIP] < level.time + && !Q_irand( 0, 3 ) + ) + {//boba fett - fly away! + gripEnt->client->ps.forceJumpCharge = 0;//so we don't play the force flip anim + gripEnt->client->ps.velocity[2] = 250; + gripEnt->client->ps.forceJumpZStart = gripEnt->currentOrigin[2];//so we don't take damage if we land at same height + gripEnt->client->ps.pm_flags |= PMF_JUMPING; + G_AddEvent( gripEnt, EV_JUMP, 0 ); + JET_FlyStart( gripEnt ); + WP_ForcePowerStop( self, FP_GRIP ); + return; + } + else if ( gripEnt->NPC + && gripEnt->client + && gripEnt->client->ps.forcePowersKnown + && (gripEnt->client->NPC_class==CLASS_REBORN||gripEnt->client->ps.weapon==WP_SABER) + && !Jedi_CultistDestroyer(gripEnt) + && !Q_irand( 0, 100-(gripEnt->NPC->stats.evasion*8)-(g_spskill->integer*20) ) ) + {//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time? + WP_ForceForceThrow( gripEnt ); + //FIXME: I need to go into some pushed back anim... + WP_ForcePowerStop( self, FP_GRIP ); + return; + } + else if ( PM_SaberInAttack( self->client->ps.saberMove ) + || PM_SaberInStart( self->client->ps.saberMove ) ) + {//started an attack + WP_ForcePowerStop( self, FP_GRIP ); + return; + } + else + { + int gripLevel = self->client->ps.forcePowerLevel[FP_GRIP]; + if ( gripEnt->client ) + { + gripLevel = WP_AbsorbConversion( gripEnt, gripEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_GRIP, self->client->ps.forcePowerLevel[FP_GRIP], forcePowerNeeded[gripLevel] ); + } + if ( !gripLevel ) + { + WP_ForcePowerStop( self, forcePower ); + return; + } + + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//holding it + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCEGRIP_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( self->client->ps.torsoAnimTimer < 100 ){//we were already playing this anim, we didn't want to restart it, but we want to hold it for at least 100ms, sooo.... + + self->client->ps.torsoAnimTimer = 100; + } + } + //get their org + VectorCopy( self->client->ps.viewangles, angles ); + angles[0] -= 10; + AngleVectors( angles, dir, NULL, NULL ); + if ( gripEnt->client ) + {//move + VectorCopy( gripEnt->client->renderInfo.headPoint, gripEntOrg ); + } + else + { + VectorCopy( gripEnt->currentOrigin, gripEntOrg ); + } + + //how far are they + dist = Distance( self->client->renderInfo.handLPoint, gripEntOrg ); + if ( self->client->ps.forcePowerLevel[FP_GRIP] == FORCE_LEVEL_2 && + (!InFront( gripEntOrg, self->client->renderInfo.handLPoint, self->client->ps.viewangles, 0.3f ) || + DistanceSquared( gripEntOrg, self->client->renderInfo.handLPoint ) > FORCE_GRIP_DIST_SQUARED)) + {//must face them + WP_ForcePowerStop( self, FP_GRIP ); + return; + } + + //check for lift or carry + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 + && (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK))) ) + {//carry + //cap dist + if ( dist > FORCE_GRIP_3_MAX_DIST ) + { + dist = FORCE_GRIP_3_MAX_DIST; + } + else if ( dist < FORCE_GRIP_3_MIN_DIST ) + { + dist = FORCE_GRIP_3_MIN_DIST; + } + VectorMA( self->client->renderInfo.handLPoint, dist, dir, gripOrg ); + } + else if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//just lift + VectorCopy( self->client->ps.forceGripOrg, gripOrg ); + } + else + { + VectorCopy( gripEnt->currentOrigin, gripOrg ); + } + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//if holding him, make sure there's a clear LOS between my hand and him + trace_t gripTrace; + gi.trace( &gripTrace, self->client->renderInfo.handLPoint, NULL, NULL, gripEntOrg, ENTITYNUM_NONE, MASK_FORCE_PUSH ); + if ( gripTrace.startsolid + || gripTrace.startsolid + || gripTrace.fraction < 1.0f ) + {//no clear trace, drop them + WP_ForcePowerStop( self, FP_GRIP ); + return; + } + } + //now move them + if ( gripEnt->client ) + { + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//level 1 just holds them + VectorSubtract( gripOrg, gripEntOrg, gripEnt->client->ps.velocity ); + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 + && (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK)) ) ) + {//level 2 just lifts them + float gripDist = VectorNormalize( gripEnt->client->ps.velocity )/3.0f; + if ( gripDist < 20.0f ) + { + if (gripDist<2.0f) + { + VectorClear(gripEnt->client->ps.velocity); + } + else + { + VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity ); + } + } + else + { + VectorScale( gripEnt->client->ps.velocity, (gripDist*gripDist), gripEnt->client->ps.velocity ); + } + } + } + //stop them from thinking + gripEnt->client->ps.pm_time = 2000; + gripEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + if ( gripEnt->NPC ) + { + if ( !(gripEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + {//not falling to their death + gripEnt->NPC->nextBStateThink = level.time + 2000; + } + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//level 1 just holds them + vectoangles( dir, angles ); + gripEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180); + gripEnt->NPC->desiredPitch = -angles[PITCH]; + SaveNPCGlobals(); + SetNPCGlobals( gripEnt ); + NPC_UpdateAngles( qtrue, qtrue ); + gripEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0]; + gripEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1]; + gripEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2]; + RestoreNPCGlobals(); + //FIXME: why does he turn back to his original angles once he dies or is let go? + } + } + else if ( !gripEnt->s.number ) + { + //vectoangles( dir, angles ); + //gripEnt->client->ps.viewangles[0] = -angles[0]; + //gripEnt->client->ps.viewangles[1] = AngleNormalize180(angles[YAW]+180); + gripEnt->enemy = self; + NPC_SetLookTarget( gripEnt, self->s.number, level.time+1000 ); + } + + gripEnt->client->ps.eFlags |= EF_FORCE_GRIPPED; + //dammit! Make sure that saber stays off! + WP_DeactivateSaber( gripEnt ); + } + else + {//move + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//level 1 just holds them + VectorCopy( gripEnt->currentOrigin, gripEnt->s.pos.trBase ); + VectorSubtract( gripOrg, gripEntOrg, gripEnt->s.pos.trDelta ); + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 + && (!gripEnt->client || (!gripEnt->message&&!(gripEnt->flags&FL_NO_KNOCKBACK))) ) + {//level 2 just lifts them + VectorScale( gripEnt->s.pos.trDelta, 10, gripEnt->s.pos.trDelta ); + } + gripEnt->s.pos.trType = TR_LINEAR; + gripEnt->s.pos.trTime = level.time; + } + + gripEnt->s.eFlags |= EF_FORCE_GRIPPED; + } + + //Shouldn't this be discovered? + //AddSightEvent( self, gripOrg, 128, AEL_DANGER, 20 ); + AddSightEvent( self, gripOrg, 128, AEL_DISCOVERED, 20 ); + + if ( self->client->ps.forcePowerDebounce[FP_GRIP] < level.time ) + { + //GEntity_PainFunc( gripEnt, self, self, gripOrg, 0, MOD_CRUSH ); + if ( !gripEnt->client + || gripEnt->client->NPC_class != CLASS_VEHICLE + || (gripEnt->m_pVehicle + && gripEnt->m_pVehicle->m_pVehicleInfo + && gripEnt->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) ) + {//we don't damage the empty vehicle + gripEnt->painDebounceTime = 0; + int gripDmg = forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]]; + if ( gripLevel != -1 ) + { + if ( gripLevel == 1 ) + { + gripDmg = floor((float)gripDmg/3.0f); + } + else //if ( gripLevel == 2 ) + { + gripDmg = floor((float)gripDmg/1.5f); + } + } + G_Damage( gripEnt, self, self, dir, gripOrg, gripDmg, DAMAGE_NO_ARMOR, MOD_CRUSH );//MOD_??? + } + if ( gripEnt->s.number ) + { + if ( self->client->ps.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_2 ) + {//do damage faster at level 3 + self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 150, 750 ); + } + else + { + self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 250, 1000 ); + } + } + else + {//player takes damage faster + self->client->ps.forcePowerDebounce[FP_GRIP] = level.time + Q_irand( 100, 600 ); + } + if ( forceGripDamage[self->client->ps.forcePowerLevel[FP_GRIP]] > 0 ) + {//no damage at level 1 + WP_ForcePowerDrain( self, FP_GRIP, 3 ); + } + if ( self->client->NPC_class == CLASS_KYLE + && (self->spawnflags&1) ) + {//"Boss" Kyle + if ( gripEnt->client ) + { + if ( !Q_irand( 0, 2 ) ) + {//toss him aside! + vec3_t vRt; + AngleVectors( self->currentAngles, NULL, vRt, NULL ); + //stop gripping + TIMER_Set( self, "gripping", -level.time ); + WP_ForcePowerStop( self, FP_GRIP ); + //now toss him + if ( Q_irand( 0, 1 ) ) + {//throw him to my left + NPC_SetAnim( self, SETANIM_BOTH, BOTH_TOSS1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + VectorScale( vRt, -1500.0f, gripEnt->client->ps.velocity ); + G_Knockdown( gripEnt, self, vRt, 500, qfalse ); + } + else + {//throw him to my right + NPC_SetAnim( self, SETANIM_BOTH, BOTH_TOSS2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + VectorScale( vRt, 1500.0f, gripEnt->client->ps.velocity ); + G_Knockdown( gripEnt, self, vRt, 500, qfalse ); + } + //don't do anything for a couple seconds + self->client->ps.weaponTime = self->client->ps.torsoAnimTimer + 2000; + self->painDebounceTime = level.time + self->client->ps.weaponTime; + //stop moving + VectorClear( self->client->ps.velocity ); + VectorClear( self->client->ps.moveDir ); + return; + } + } + } + } + else + { + //WP_ForcePowerDrain( self, FP_GRIP, 0 ); + if ( !gripEnt->enemy ) + { + if ( gripEnt->client + && gripEnt->client->playerTeam == TEAM_PLAYER + && self->s.number < MAX_CLIENTS + && self->client + && self->client->playerTeam == TEAM_PLAYER ) + {//this shouldn't make allies instantly turn on you, let the damage->pain routine determine how allies should react to this + } + else + { + G_SetEnemy( gripEnt, self ); + } + } + } + if ( gripEnt->client && gripEnt->health > 0 ) + { + int anim = BOTH_CHOKE3; //left-handed choke + if ( gripEnt->client->ps.weapon == WP_NONE || gripEnt->client->ps.weapon == WP_MELEE ) + { + anim = BOTH_CHOKE1; //two-handed choke + } + if ( self->client->ps.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 ) + {//still on ground, only set anim on torso + NPC_SetAnim( gripEnt, SETANIM_TORSO, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + {//in air, set on whole body + NPC_SetAnim( gripEnt, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + gripEnt->painDebounceTime = level.time + 2000; + } + } + } + break; + case FP_LIGHTNING: + if ( self->client->ps.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 ) + {//higher than level 1 + if ( cmd->buttons & BUTTON_FORCE_LIGHTNING ) + {//holding it keeps it going + self->client->ps.forcePowerDuration[FP_LIGHTNING] = level.time + 500; + ForceLightningAnim( self ); + } + } + if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) ) + { + WP_ForcePowerStop( self, forcePower ); + } + else + { + ForceShootLightning( self ); + if ( self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING + || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_START + || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD + || self->client->ps.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_RELEASE ) + {//jackin' 'em up, Palpatine-style + //extra cost + WP_ForcePowerDrain( self, forcePower, 0 ); + } + WP_ForcePowerDrain( self, forcePower, 0 ); + } + break; + //new Jedi Academy force powers + case FP_RAGE: + if (self->health < 1) + { + WP_ForcePowerStop(self, forcePower); + break; + } + if (self->client->ps.forceRageDrainTime < level.time) + { + int addTime = 400; + + self->health -= 2; + + if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_1) + { + addTime = 100; + } + else if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_2) + { + addTime = 250; + } + else if (self->client->ps.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_3) + { + addTime = 500; + } + self->client->ps.forceRageDrainTime = level.time + addTime; + } + + if ( self->health < 1 ) + { + self->health = 1; + //WP_ForcePowerStop( self, forcePower ); + } + else + { + self->client->ps.stats[STAT_HEALTH] = self->health; + + speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_RAGE]-1]; + if ( !self->s.number ) + {//player using force rage + if ( !(self->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_RAGE] > self->client->ps.forcePowerLevel[FP_SPEED]+1 ) + {//either not using speed or speed is at a lower level than rage + gi.cvar_set("timescale", va("%4.2f", speed)); + if ( g_timescale->value > speed ) + { + newSpeed = g_timescale->value - 0.05; + if ( newSpeed < speed ) + { + newSpeed = speed; + } + gi.cvar_set("timescale", va("%4.2f", newSpeed)); + } + } + } + } + break; + case FP_DRAIN: + if ( cmd->buttons & BUTTON_FORCE_DRAIN ) + {//holding it keeps it going + self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 500; + } + if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) ) + {//no more force power, stop + WP_ForcePowerStop( self, forcePower ); + } + else if ( self->client->ps.forceDrainEntityNum >= 0 && self->client->ps.forceDrainEntityNum < ENTITYNUM_WORLD ) + {//holding someone + if ( !WP_ForcePowerAvailable( self, FP_DRAIN, 0 ) + || (self->client->ps.forcePowerLevel[FP_DRAIN]>FORCE_LEVEL_1 + && !self->s.number + && !(cmd->buttons&BUTTON_FORCE_DRAIN) + && self->client->ps.forcePowerDuration[FP_DRAIN]client->ps.forceDrainEntityNum]; + + if ( !drainEnt ) + {//invalid ent + WP_ForcePowerStop( self, FP_DRAIN ); + return; + } + else if ( (drainEnt->health <= 0&&drainEnt->takedamage) )//FIXME: what about things that never had health or lose takedamage when they die? + {//dead ent + WP_ForcePowerStop( self, FP_DRAIN ); + return; + } + else if ( drainEnt->client && drainEnt->client->moveType == MT_FLYSWIM && VectorLengthSquared( NPC->client->ps.velocity ) > (300*300) ) + {//flying creature broke free + WP_ForcePowerStop( self, FP_DRAIN ); + return; + } + else if ( drainEnt->client + && drainEnt->health>0 //dead dudes don't fly + && (drainEnt->client->NPC_class == CLASS_BOBAFETT || drainEnt->client->NPC_class == CLASS_ROCKETTROOPER) + && self->client->ps.forcePowerDebounce[FP_DRAIN] < level.time + && !Q_irand( 0, 10 ) ) + {//boba fett - fly away! + drainEnt->client->ps.forceJumpCharge = 0;//so we don't play the force flip anim + drainEnt->client->ps.velocity[2] = 250; + drainEnt->client->ps.forceJumpZStart = drainEnt->currentOrigin[2];//so we don't take damage if we land at same height + drainEnt->client->ps.pm_flags |= PMF_JUMPING; + G_AddEvent( drainEnt, EV_JUMP, 0 ); + JET_FlyStart( drainEnt ); + WP_ForcePowerStop( self, FP_DRAIN ); + return; + } + else if ( drainEnt->NPC + && drainEnt->client + && drainEnt->client->ps.forcePowersKnown + && (drainEnt->client->NPC_class==CLASS_REBORN||drainEnt->client->ps.weapon==WP_SABER) + && !Jedi_CultistDestroyer(drainEnt) + && level.time-(self->client->ps.forcePowerDebounce[FP_DRAIN]>self->client->ps.forcePowerLevel[FP_DRAIN]*500)//at level 1, I always get at least 500ms of drain, at level 3 I get 1500ms + && !Q_irand( 0, 100-(drainEnt->NPC->stats.evasion*8)-(g_spskill->integer*15) ) ) + {//a jedi who broke free FIXME: maybe have some minimum grip length- a reaction time? + WP_ForceForceThrow( drainEnt ); + //FIXME: I need to go into some pushed back anim... + WP_ForcePowerStop( self, FP_DRAIN ); + //can't drain again for 2 seconds + self->client->ps.forcePowerDebounce[FP_DRAIN] = level.time + 4000; + return; + } + else + { + /* + int drainLevel = WP_AbsorbConversion( drainEnt, drainEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]] ); + if ( !drainLevel ) + { + WP_ForcePowerStop( self, forcePower ); + return; + } + */ + + //NPC_SetAnim( self, SETANIM_BOTH, BOTH_HUGGER1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( self->client->ps.torsoAnim != BOTH_FORCE_DRAIN_GRAB_START + || !self->client->ps.torsoAnimTimer ) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRAB_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( self->handLBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/drain_hand" ), self->playerModel, self->handLBolt, self->s.number, self->currentOrigin, 200, qtrue ); + } + if ( self->handRBolt != -1 ) + { + G_PlayEffect( G_EffectIndex( "force/drain_hand" ), self->playerModel, self->handRBolt, self->s.number, self->currentOrigin, 200, qtrue ); + } + + //how far are they + dist = Distance( self->client->renderInfo.eyePoint, drainEnt->currentOrigin ); + if ( DistanceSquared( drainEnt->currentOrigin, self->currentOrigin ) > FORCE_DRAIN_DIST_SQUARED ) + {//must be close, got away somehow! + WP_ForcePowerStop( self, FP_DRAIN ); + return; + } + + //keep my saber off! + WP_DeactivateSaber( self, qtrue ); + if ( drainEnt->client ) + { + //now move them + VectorCopy( self->client->ps.viewangles, angles ); + angles[0] = 0; + AngleVectors( angles, dir, NULL, NULL ); + /* + VectorMA( self->currentOrigin, self->maxs[0], dir, drainEnt->client->ps.forceDrainOrg ); + trace_t trace; + gi.trace( &trace, drainEnt->currentOrigin, drainEnt->mins, drainEnt->maxs, drainEnt->client->ps.forceDrainOrg, drainEnt->s.number, drainEnt->clipmask ); + if ( !trace.startsolid && !trace.allsolid ) + { + G_SetOrigin( drainEnt, trace.endpos ); + gi.linkentity( drainEnt ); + VectorClear( drainEnt->client->ps.velocity ); + } + VectorMA( self->currentOrigin, self->maxs[0]*0.5f, dir, drainEnt->client->ps.forceDrainOrg ); + */ + //stop them from thinking + drainEnt->client->ps.pm_time = 2000; + drainEnt->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + if ( drainEnt->NPC ) + { + if ( !(drainEnt->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + {//not falling to their death + drainEnt->NPC->nextBStateThink = level.time + 2000; + } + vectoangles( dir, angles ); + drainEnt->NPC->desiredYaw = AngleNormalize180(angles[YAW]+180); + drainEnt->NPC->desiredPitch = -angles[PITCH]; + SaveNPCGlobals(); + SetNPCGlobals( drainEnt ); + NPC_UpdateAngles( qtrue, qtrue ); + drainEnt->NPC->last_ucmd.angles[0] = ucmd.angles[0]; + drainEnt->NPC->last_ucmd.angles[1] = ucmd.angles[1]; + drainEnt->NPC->last_ucmd.angles[2] = ucmd.angles[2]; + RestoreNPCGlobals(); + //FIXME: why does he turn back to his original angles once he dies or is let go? + } + else if ( !drainEnt->s.number ) + { + drainEnt->enemy = self; + NPC_SetLookTarget( drainEnt, self->s.number, level.time+1000 ); + } + + drainEnt->client->ps.eFlags |= EF_FORCE_DRAINED; + //dammit! Make sure that saber stays off! + WP_DeactivateSaber( drainEnt, qtrue ); + } + //Shouldn't this be discovered? + AddSightEvent( self, drainEnt->currentOrigin, 128, AEL_DISCOVERED, 20 ); + + if ( self->client->ps.forcePowerDebounce[FP_DRAIN] < level.time ) + { + int drainLevel = WP_AbsorbConversion( drainEnt, drainEnt->client->ps.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.forcePowerLevel[FP_DRAIN], forcePowerNeeded[self->client->ps.forcePowerLevel[FP_DRAIN]] ); + if ( (drainLevel && drainLevel == -1) + || Q_irand( drainLevel, 3 ) < 3 ) + {//the drain is being absorbed + ForceDrainEnt( self, drainEnt ); + } + WP_ForcePowerDrain( self, FP_DRAIN, 3 ); + } + else + { + if ( !Q_irand( 0, 4 ) ) + { + WP_ForcePowerDrain( self, FP_DRAIN, 1 ); + } + if ( !drainEnt->enemy ) + { + G_SetEnemy( drainEnt, self ); + } + } + if ( drainEnt->health > 0 ) + {//still alive + //NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_HUGGEE1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC_SetAnim( drainEnt, SETANIM_BOTH, BOTH_FORCE_DRAIN_GRABBED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + } + else if ( self->client->ps.forcePowerLevel[forcePower] > FORCE_LEVEL_1 ) + {//regular distance-drain + if ( cmd->buttons & BUTTON_FORCE_DRAIN ) + {//holding it keeps it going + self->client->ps.forcePowerDuration[FP_DRAIN] = level.time + 500; + if ( self->client->ps.torsoAnim == BOTH_FORCE_DRAIN_START ) + { + if ( !self->client->ps.torsoAnimTimer ) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + else + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCE_DRAIN_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) ) + { + WP_ForcePowerStop( self, forcePower ); + } + else + { + ForceShootDrain( self ); + } + } + break; + case FP_PROTECT: + break; + case FP_ABSORB: + break; + case FP_SEE: + break; + default: + break; + } +} + +void WP_CheckForcedPowers( gentity_t *self, usercmd_t *ucmd ) +{ + for ( int forcePower = FP_FIRST; forcePower < NUM_FORCE_POWERS; forcePower++ ) + { + if ( (self->client->ps.forcePowersForced&(1<client->ps.forcePowersForced &= ~(1<client->ps.forcePowersForced &= ~(1<client->ps.forcePowersForced &= ~(1<client->ps.forcePowersForced &= ~(1<client->ps.forcePowersForced &= ~(1<buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCE_DRAIN|BUTTON_FORCE_LIGHTNING); + ucmd->buttons |= BUTTON_FORCEGRIP; + //holds until cleared + break; + case FP_LIGHTNING: + ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_DRAIN); + ucmd->buttons |= BUTTON_FORCE_LIGHTNING; + //holds until cleared + break; + case FP_SABERTHROW: + ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_DRAIN|BUTTON_FORCE_LIGHTNING); + ucmd->buttons |= BUTTON_ALT_ATTACK; + //holds until cleared? + break; + case FP_SABER_DEFENSE: + //nothing + break; + case FP_SABER_OFFENSE: + //nothing + break; + case FP_RAGE: + ForceRage( self ); + //do only once + self->client->ps.forcePowersForced &= ~(1<client->ps.forcePowersForced &= ~(1<client->ps.forcePowersForced &= ~(1<buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK|BUTTON_FORCE_FOCUS|BUTTON_FORCEGRIP|BUTTON_FORCE_LIGHTNING); + ucmd->buttons |= BUTTON_FORCE_DRAIN; + //holds until cleared + break; + case FP_SEE: + //nothing + break; + } + } + } +} + +void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd ) +{ + qboolean usingForce = qfalse; + int i; + //see if any force powers are running + if ( !self ) + { + return; + } + if ( !self->client ) + { + return; + } + + if ( self->health <= 0 ) + {//if dead, deactivate any active force powers + for ( i = 0; i < NUM_FORCE_POWERS; i++ ) + { + if ( self->client->ps.forcePowerDuration[i] || (self->client->ps.forcePowersActive&( 1 << i )) ) + { + WP_ForcePowerStop( self, (forcePowers_t)i ); + self->client->ps.forcePowerDuration[i] = 0; + } + } + return; + } + + WP_CheckForcedPowers( self, ucmd ); + + if ( !self->s.number ) + {//player uses different kind of force-jump + } + else + { + /* + if ( ucmd->buttons & BUTTON_FORCEJUMP ) + {//just charging up + ForceJumpCharge( self, ucmd ); + } + else */ + if ( self->client->ps.forceJumpCharge ) + {//let go of charge button, have charge + //if leave the ground by some other means, cancel the force jump so we don't suddenly jump when we land. + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE + && !PM_SwimmingAnim( self->client->ps.legsAnim ) ) + {//FIXME: stop sound? + //self->client->ps.forceJumpCharge = 0; + //FIXME: actually, we want this to still be cleared... don't clear it if the button isn't being pressed, but clear it if not holding button and not on ground. + } + else + {//still on ground, so jump + ForceJump( self, ucmd ); + return; + } + } + } + + if ( ucmd->buttons & BUTTON_FORCEGRIP ) + { + ForceGrip( self ); + } + + if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING ) + { + ForceLightning( self ); + } + + if ( ucmd->buttons & BUTTON_FORCE_DRAIN ) + { + if ( !ForceDrain2( self ) ) + {//can't drain-grip someone right in front + if ( self->client->ps.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 ) + {//try ranged + ForceDrain( self, qtrue ); + } + } + } + + for ( i = 0; i < NUM_FORCE_POWERS; i++ ) + { + if ( self->client->ps.forcePowerDuration[i] ) + { + if ( self->client->ps.forcePowerDuration[i] < level.time ) + { + if ( (self->client->ps.forcePowersActive&( 1 << i )) ) + {//turn it off + WP_ForcePowerStop( self, (forcePowers_t)i ); + } + self->client->ps.forcePowerDuration[i] = 0; + } + } + if ( (self->client->ps.forcePowersActive&( 1 << i )) ) + { + usingForce = qtrue; + WP_ForcePowerRun( self, (forcePowers_t)i, ucmd ); + } + } + if ( self->client->ps.saberInFlight ) + {//don't regen force power while throwing saber + if ( self->client->ps.saberEntityNum < ENTITYNUM_NONE && self->client->ps.saberEntityNum > 0 )//player is 0 + {// + if ( &g_entities[self->client->ps.saberEntityNum] != NULL && g_entities[self->client->ps.saberEntityNum].s.pos.trType == TR_LINEAR ) + {//fell to the ground and we're trying to pull it back + usingForce = qtrue; + } + } + } + if ( PM_ForceUsingSaberAnim( self->client->ps.torsoAnim ) ) + { + usingForce = qtrue; + } + if ( !usingForce ) + {//when not using the force, regenerate at 10 points per second + if ( self->client->ps.forcePowerRegenDebounceTime < level.time ) + { + WP_ForcePowerRegenerate( self, self->client->ps.forcePowerRegenAmount ); + self->client->ps.forcePowerRegenDebounceTime = level.time + self->client->ps.forcePowerRegenRate; + if ( self->client->ps.forceRageRecoveryTime >= level.time ) + {//regen half as fast + self->client->ps.forcePowerRegenDebounceTime += self->client->ps.forcePowerRegenRate; + } + } + } +} + +void WP_InitForcePowers( gentity_t *ent ) +{ + if ( !ent || !ent->client ) + { + return; + } + + if ( !ent->client->ps.forcePowerMax ) + { + ent->client->ps.forcePowerMax = FORCE_POWER_MAX; + } + if ( !ent->client->ps.forcePowerRegenRate ) + { + ent->client->ps.forcePowerRegenRate = 100; + } + ent->client->ps.forcePower = ent->client->ps.forcePowerMax; + ent->client->ps.forcePowerRegenDebounceTime = 0; + + ent->client->ps.forceGripEntityNum = ent->client->ps.forceDrainEntityNum = ent->client->ps.pullAttackEntNum = ENTITYNUM_NONE; + ent->client->ps.forceRageRecoveryTime = 0; + ent->client->ps.forceDrainTime = 0; + ent->client->ps.pullAttackTime = 0; + + if ( ent->s.number < MAX_CLIENTS ) + {//player + if ( !g_cheats->integer )//devmaps give you all the FP + { + ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1; + ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1; + } + else + { + ent->client->ps.forcePowersKnown = ( 1 << FP_HEAL )|( 1 << FP_LEVITATION )|( 1 << FP_SPEED )|( 1 << FP_PUSH )|( 1 << FP_PULL )|( 1 << FP_TELEPATHY )|( 1 << FP_GRIP )|( 1 << FP_LIGHTNING)|( 1 << FP_SABERTHROW)|( 1 << FP_SABER_DEFENSE )|( 1 << FP_SABER_OFFENSE )|( 1<< FP_RAGE )|( 1<< FP_DRAIN )|( 1<< FP_PROTECT )|( 1<< FP_ABSORB )|( 1<< FP_SEE ); + ent->client->ps.forcePowerLevel[FP_HEAL] = FORCE_LEVEL_2; + ent->client->ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2; + ent->client->ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1; + ent->client->ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1; + ent->client->ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2; + ent->client->ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2; + ent->client->ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1; + ent->client->ps.forcePowerLevel[FP_TELEPATHY] = FORCE_LEVEL_2; + + ent->client->ps.forcePowerLevel[FP_RAGE] = FORCE_LEVEL_1; + ent->client->ps.forcePowerLevel[FP_PROTECT] = FORCE_LEVEL_1; + ent->client->ps.forcePowerLevel[FP_ABSORB] = FORCE_LEVEL_1; + ent->client->ps.forcePowerLevel[FP_DRAIN] = FORCE_LEVEL_1; + ent->client->ps.forcePowerLevel[FP_SEE] = FORCE_LEVEL_1; + + ent->client->ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3; + ent->client->ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3; + ent->client->ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2; + } + } +} + +// Xbox Saber cycling cheat: +bool Cheat_ChangeSaber( void ) +{ + // Need to extern these. We can't #include qcommon.h because some fuckwit + // used LS_NONE to mean both a lightstyle and a sabermove. + extern char *Cvar_VariableString( const char *var_name ); + extern void Cbuf_ExecuteText( int exec_when, const char *text ); + + // First: If we have NO saber, don't do the cheat! (t2_dpred) Also, if + // client is invalid, we're probably in the UI, so don't do anything: + if( !(g_entities[0].client) || + !(g_entities[0].client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) ) + return false; + + // Saved copies of saber vars, so we use the same model when we cycle + static char singleType1[32] = { 0 }; + static char singleType2[32] = { 0 }; + static char staffType[32] = { 0 }; + + // Get current saber information + char *s1 = Cvar_VariableString( "g_saber" ); + char *s2 = Cvar_VariableString( "g_saber2" ); + + if( g_entities[0].client->ps.saber[0].type == SABER_STAFF ) + { + // We're using a staff right now. Copy type out: + Q_strncpyz( staffType, s1, sizeof(staffType), qtrue ); + + // If we don't have a remembered single type, put in default + if( !singleType1[0] ) + Q_strncpyz( singleType1, "single_1", sizeof(singleType1), qtrue ); + + // Switch to single saber, all three styles known: + g_entities[0].client->ps.saberStylesKnown = (1 << SS_FAST) | (1 << SS_MEDIUM) | (1 << SS_STRONG); + Cbuf_ExecuteText( EXEC_NOW, va("saber %s\n", singleType1) ); + } + else if( g_entities[0].client->ps.dualSabers ) + { + // We're using two sabers right now. Copy types: + Q_strncpyz( singleType1, s1, sizeof(singleType1), qtrue ); + Q_strncpyz( singleType2, s2, sizeof(singleType2), qtrue ); + + // If we don't have a remembered staff type, put in default: + if( !staffType[0] ) + Q_strncpyz( staffType, "dual_1", sizeof(staffType), qtrue ); + + // Switch to staff: + g_entities[0].client->ps.saberStylesKnown = (1 << SS_STAFF); + Cbuf_ExecuteText( EXEC_NOW, va("saber %s\n", staffType) ); + } + else + { + // We're using one saber right now. Copy type: + Q_strncpyz( singleType1, s1, sizeof(singleType1), qtrue ); + + // If we don't have a remembered second type, copy first as default: + if( !singleType2[0] ) + Q_strncpyz( singleType2, singleType1, sizeof(singleType2), qtrue ); + + // Switch to dual: + g_entities[0].client->ps.saberStylesKnown = (1 << SS_DUAL); + Cbuf_ExecuteText( EXEC_NOW, va("saber %s %s\n", singleType1, singleType2) ); + } + + return true; +} diff --git a/code/game/wp_saber.h b/code/game/wp_saber.h new file mode 100644 index 0000000..70d0e05 --- /dev/null +++ b/code/game/wp_saber.h @@ -0,0 +1,440 @@ +#ifndef __WP_SABER_H +#define __WP_SABER_H + +#define ARMOR_EFFECT_TIME 500 + +#define JSF_AMBUSH 16 //ambusher Jedi + +//saberEventFlags +#define SEF_HITENEMY 0x1 //Hit the enemy +#define SEF_HITOBJECT 0x2 //Hit some other object +#define SEF_HITWALL 0x4 //Hit a wall +#define SEF_PARRIED 0x8 //Parried a saber swipe +#define SEF_DEFLECTED 0x10 //Deflected a missile or saberInFlight +#define SEF_BLOCKED 0x20 //Was blocked by a parry +#define SEF_EVENTS (SEF_HITENEMY|SEF_HITOBJECT|SEF_HITWALL|SEF_PARRIED|SEF_DEFLECTED|SEF_BLOCKED) +#define SEF_LOCKED 0x40 //Sabers locked with someone else +#define SEF_INWATER 0x80 //Saber is in water +#define SEF_LOCK_WON 0x100 //Won a saberLock +//saberEntityState +#define SES_LEAVING 1 +#define SES_HOVERING 2 +#define SES_RETURNING 3 + +#define SABER_EXTRAPOLATE_DIST 16.0f + +#define SABER_MAX_DIST 400.0f +#define SABER_MAX_DIST_SQUARED (SABER_MAX_DIST*SABER_MAX_DIST) + +#define FORCE_POWER_MAX 100 + +#define SABER_REFLECT_MISSILE_CONE 0.2f + +#define SABER_RADIUS_STANDARD 3.0f + +#define SABER_LOCK_TIME 10000 +#define SABER_LOCK_DELAYED_TIME 9500 +typedef enum +{ + LOCK_VICTORY = 0,//one side won + LOCK_STALEMATE,//neither side won + LOCK_DRAW//both people fall back +} saberLockResult_t; + +typedef enum +{ + LOCK_FIRST = 0, + LOCK_TOP = LOCK_FIRST, + LOCK_DIAG_TR, + LOCK_DIAG_TL, + LOCK_DIAG_BR, + LOCK_DIAG_BL, + LOCK_R, + LOCK_L, + LOCK_RANDOM, + LOCK_KYLE_GRAB1, + LOCK_KYLE_GRAB2, + LOCK_KYLE_GRAB3, + LOCK_FORCE_DRAIN +} sabersLockMode_t; + +typedef enum +{ + SABERLOCK_TOP, + SABERLOCK_SIDE, + SABERLOCK_LOCK, + SABERLOCK_BREAK, + SABERLOCK_SUPERBREAK, + SABERLOCK_WIN, + SABERLOCK_LOSE +}; + +typedef enum +{ + DIR_RIGHT, + DIR_LEFT, + DIR_FRONT, + DIR_BACK +}; + +#define FORCE_LIGHTSIDE 1 +#define FORCE_DARKSIDE 2 + +#define MAX_FORCE_HEAL_HARD 25 +#define MAX_FORCE_HEAL_MEDIUM 50 +#define MAX_FORCE_HEAL_EASY 75 +#define FORCE_HEAL_INTERVAL 200//FIXME: maybe level 1 is slower or level 2 is faster? + +#define FORCE_GRIP_3_MIN_DIST 128.0f +#define FORCE_GRIP_3_MAX_DIST 256.0f +#define FORCE_GRIP_DIST 512.0f//FIXME: vary by power level? +#define FORCE_GRIP_DIST_SQUARED (FORCE_GRIP_DIST*FORCE_GRIP_DIST) + +#define FORCE_DRAIN_DIST 64.0f//FIXME: vary by power level? +#define FORCE_DRAIN_DIST_SQUARED (FORCE_DRAIN_DIST*FORCE_DRAIN_DIST) + +#define MAX_DRAIN_DISTANCE 512 + +#define MIN_SABERBLADE_DRAW_LENGTH 0.5f + +#define STAFF_KICK_RANGE 16 + +#define JUMP_OFF_WALL_SPEED 200.0f + +#define FORCE_LONG_LEAP_SPEED 475.0f//300 + + +//#define DUAL_SPIN_PROTECT_POWER 50 //power required to do the dual spin attack +//#define SINGLE_SPECIAL_POWER 20 //power required to do the single saber special attacks + +#define SABER_ALT_ATTACK_POWER 50//75? +#define SABER_ALT_ATTACK_POWER_LR 10//30? +#define SABER_ALT_ATTACK_POWER_FB 25//30/50? + +#define FORCE_LONGJUMP_POWER 20 + +#define WALL_RUN_UP_BACKFLIP_SPEED -150.0f//was -300.0f +#define MAX_WALL_RUN_Z_NORMAL 0.4f//was 0.0f + +#define PLAYER_KNOCKDOWN_HOLD_EXTRA_TIME 4000 //player stays down after a knockdown for 4 whole seconds before automatically doing one of the slow get-ups + +#define MAX_WALL_GRAB_SLOPE 0.2f + +//"Matrix" effect flags +#define MEF_NO_TIMESCALE 0x000001 //no timescale +#define MEF_NO_VERTBOB 0x000002 //no vert bob +#define MEF_NO_SPIN 0x000004 //no spin +#define MEF_NO_RANGEVAR 0x000008 //no camera range variation +#define MEF_HIT_GROUND_STOP 0x000010 //stop effect when subject hits the ground +#define MEF_REVERSE_SPIN 0x000020 //spin counter-clockwise instead of clockwise +#define MEF_MULTI_SPIN 0x000040 //spin once every second, until the effect stops +#define MEF_LOOK_AT_ENEMY 0x000200 + +extern float forceJumpStrength[]; +extern float forceJumpHeight[]; +extern float forceJumpHeightMax[]; + +extern float forcePushPullRadius[]; + +extern void ForceSpeed( gentity_t *self, int duration = 0 ); +extern float forceSpeedValue[]; +extern float forceSpeedRangeMod[]; +extern float forceSpeedFOVMod[]; +#define FORCE_SPEED_DURATION 10000.0f +#define FORCE_RAGE_DURATION 10000.0f + +#define MASK_FORCE_PUSH (MASK_OPAQUE|CONTENTS_SOLID) + +typedef enum +{ + FORCE_LEVEL_0, + FORCE_LEVEL_1, + FORCE_LEVEL_2, + FORCE_LEVEL_3, + NUM_FORCE_POWER_LEVELS +}; +#define FORCE_LEVEL_4 (FORCE_LEVEL_3+1) +#define FORCE_LEVEL_5 (FORCE_LEVEL_4+1) + +typedef enum +{ + FJ_FORWARD, + FJ_BACKWARD, + FJ_RIGHT, + FJ_LEFT, + FJ_UP +}; + +#define FORCE_JUMP_CHARGE_TIME 1000.0f //Force jump reaches maximum power in one second + +#define FORCE_POWERS_ROSH_FROM_TWINS ((1<name = NULL; + saber->fullName = NULL; + for ( int i = 0; i < MAX_BLADES; i++ ) + { + if ( setColors ) + { + saber->blade[i].color = SABER_RED; + } + saber->blade[i].radius = SABER_RADIUS_STANDARD; + saber->blade[i].lengthMax = 32; + } + saber->model = "models/weapons2/saber_reborn/saber_w.glm"; + saber->skin = NULL; + saber->soundOn = G_SoundIndex( "sound/weapons/saber/enemy_saber_on.wav" ); + saber->soundLoop = G_SoundIndex( "sound/weapons/saber/saberhum3.wav" ); + saber->soundOff = G_SoundIndex( "sound/weapons/saber/enemy_saber_off.wav" ); + saber->numBlades = 1; + saber->type = SABER_SINGLE; + saber->style = SS_NONE; + saber->maxChain = 0;//0 = use default behavior + saber->lockable = qtrue; + saber->throwable = qtrue; + saber->disarmable = qtrue; + saber->activeBlocking = qtrue; + saber->twoHanded = qfalse; + saber->forceRestrictions = 0; + saber->lockBonus = 0; + saber->parryBonus = 0; + saber->breakParryBonus = 0; + saber->disarmBonus = 0; + saber->singleBladeStyle = SS_NONE;//makes it so that you use a different style if you only have the first blade active + saber->singleBladeThrowable = qfalse;//makes it so that you can throw this saber if only the first blade is on + saber->brokenSaber1 = NULL;//if saber is actually hit by another saber, it can be cut in half/broken and will be replaced with this saber in your right hand + saber->brokenSaber2 = NULL;//if saber is actually hit by another saber, it can be cut in half/broken and will be replaced with this saber in your left hand + saber->returnDamage = qfalse;//when returning from a saber throw, it keeps spinning and doing damage +} + +qboolean WP_SaberParseParms( const char *SaberName, saberInfo_t *saber, qboolean setColors ) +{ + const char *token; + const char *value; + const char *p; + float f; + int n; + + if ( !saber ) + { + return qfalse; + } + + //Set defaults so that, if it fails, there's at least something there + WP_SaberSetDefaults( saber, setColors ); + + if ( !SaberName || !SaberName[0] ) + { + return qfalse; + } + + saber->name = G_NewString( SaberName ); + //try to parse it out + p = SaberParms; + COM_BeginParseSession(); + + // look for the right saber + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return qfalse; + } + + if ( !Q_stricmp( token, SaberName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { + return qfalse; + } + + if ( G_ParseLiteral( &p, "{" ) ) + { + return qfalse; + } + + // parse the saber info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", SaberName ); + return qfalse; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + //saber fullName + if ( !Q_stricmp( token, "name" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->fullName = G_NewString( value ); + continue; + } + + //saber type + if ( !Q_stricmp( token, "saberType" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + int saberType = GetIDForString( SaberTable, value ); + if ( saberType >= SABER_SINGLE && saberType <= NUM_SABERS ) + { + saber->type = (saberType_t)saberType; + } + continue; + } + + //saber hilt + if ( !Q_stricmp( token, "saberModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->model = G_NewString( value ); + continue; + } + + if ( !Q_stricmp( token, "customSkin" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->skin = G_NewString( value ); + continue; + } + + //on sound + if ( !Q_stricmp( token, "soundOn" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->soundOn = G_SoundIndex( G_NewString( value ) ); + continue; + } + + //loop sound + if ( !Q_stricmp( token, "soundLoop" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->soundLoop = G_SoundIndex( G_NewString( value ) ); + continue; + } + + //off sound + if ( !Q_stricmp( token, "soundOff" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->soundOff = G_SoundIndex( G_NewString( value ) ); + continue; + } + + if ( !Q_stricmp( token, "numBlades" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n >= MAX_BLADES ) + { + G_Error( "WP_SaberParseParms: saber %s has illegal number of blades (%d) max: %d", SaberName, n, MAX_BLADES ); + continue; + } + saber->numBlades = n; + continue; + } + + // saberColor + if ( !Q_stricmpn( token, "saberColor", 10 ) ) + { + if ( !setColors ) + {//don't actually want to set the colors + //read the color out anyway just to advance the *p pointer + COM_ParseString( &p, &value ); + continue; + } + else + { + if (strlen(token)==10) + { + n = -1; + } + else if (strlen(token)==11) + { + n = atoi(&token[10])-1; + if (n > 7 || n < 1 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberColor '%s' in %s\n", token, SaberName ); + //read the color out anyway just to advance the *p pointer + COM_ParseString( &p, &value ); + continue; + } + } + else + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberColor '%s' in %s\n", token, SaberName ); + //read the color out anyway just to advance the *p pointer + COM_ParseString( &p, &value ); + continue; + } + + if ( COM_ParseString( &p, &value ) ) //read the color + { + continue; + } + + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same color by default + saber_colors_t color = TranslateSaberColor( value ); + for ( n = 0; n < MAX_BLADES; n++ ) + { + saber->blade[n].color = color; + } + } else + { + saber->blade[n].color = TranslateSaberColor( value ); + } + continue; + } + } + + //saber length + if ( !Q_stricmpn( token, "saberLength", 11 ) ) + { + if (strlen(token)==11) + { + n = -1; + } + else if (strlen(token)==12) + { + n = atoi(&token[11])-1; + if (n > 7 || n < 1 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberLength '%s' in %s\n", token, SaberName ); + continue; + } + } + else + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberLength '%s' in %s\n", token, SaberName ); + continue; + } + + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same length by default + for ( n = 0; n < MAX_BLADES; n++ ) + { + saber->blade[n].lengthMax = f; + } + } + else + { + saber->blade[n].lengthMax = f; + } + continue; + } + + //blade radius + if ( !Q_stricmpn( token, "saberRadius", 11 ) ) + { + if (strlen(token)==11) + { + n = -1; + } + else if (strlen(token)==12) + { + n = atoi(&token[11])-1; + if (n > 7 || n < 1 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberRadius '%s' in %s\n", token, SaberName ); + continue; + } + } + else + { + gi.Printf( S_COLOR_YELLOW"WARNING: bad saberRadius '%s' in %s\n", token, SaberName ); + continue; + } + + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same length by default + for ( n = 0; n < MAX_BLADES; n++ ) + { + saber->blade[n].radius = f; + } + } + else + { + saber->blade[n].radius = f; + } + continue; + } + + //locked saber style + if ( !Q_stricmp( token, "saberStyle" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->style = TranslateSaberStyle( value ); + continue; + } + + //maxChain + if ( !Q_stricmp( token, "maxChain" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->maxChain = n; + continue; + } + + //lockable + if ( !Q_stricmp( token, "lockable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->lockable = ((qboolean)(n!=0)); + continue; + } + + //throwable + if ( !Q_stricmp( token, "throwable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->throwable = ((qboolean)(n!=0)); + continue; + } + + //disarmable + if ( !Q_stricmp( token, "disarmable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->disarmable = ((qboolean)(n!=0)); + continue; + } + + //active blocking + if ( !Q_stricmp( token, "blocking" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->activeBlocking = ((qboolean)(n!=0)); + continue; + } + + //twoHanded + if ( !Q_stricmp( token, "twoHanded" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->twoHanded = ((qboolean)(n!=0)); + continue; + } + + //force power restrictions + if ( !Q_stricmp( token, "forceRestrict" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + int fp = GetIDForString( FPTable, value ); + if ( fp >= FP_FIRST && fp < NUM_FORCE_POWERS ) + { + saber->forceRestrictions |= (1<lockBonus = n; + continue; + } + + //parryBonus + if ( !Q_stricmp( token, "parryBonus" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->parryBonus = n; + continue; + } + + //breakParryBonus + if ( !Q_stricmp( token, "breakParryBonus" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->breakParryBonus = n; + continue; + } + + //disarmBonus + if ( !Q_stricmp( token, "disarmBonus" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->disarmBonus = n; + continue; + } + + //single blade saber style + if ( !Q_stricmp( token, "singleBladeStyle" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->singleBladeStyle = TranslateSaberStyle( value ); + continue; + } + + //single blade throwable + if ( !Q_stricmp( token, "singleBladeThrowable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->singleBladeThrowable = ((qboolean)(n!=0)); + continue; + } + + //broken replacement saber1 (right hand) + if ( !Q_stricmp( token, "brokenSaber1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->brokenSaber1 = G_NewString( value ); + continue; + } + + //broken replacement saber2 (left hand) + if ( !Q_stricmp( token, "brokenSaber2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->brokenSaber2 = G_NewString( value ); + continue; + } + + //spins and does damage on return from saberthrow + if ( !Q_stricmp( token, "returnDamage" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->returnDamage = ((qboolean)(n!=0)); + continue; + } + + if ( !Q_stricmp( token, "notInMP" ) ) + {//ignore this + SkipRestOfLine( &p ); + continue; + } + + gi.Printf( "WARNING: unknown keyword '%s' while parsing '%s'\n", token, SaberName ); + SkipRestOfLine( &p ); + } + + //FIXME: precache the saberModel(s)? + + if ( saber->type == SABER_SITH_SWORD ) + {//precache all the sith sword sounds + Saber_SithSwordPrecache(); + } + + return qtrue; +} + +void WP_RemoveSaber( gentity_t *ent, int saberNum ) +{ + if ( !ent || !ent->client ) + { + return; + } + //reset everything for this saber just in case + WP_SaberSetDefaults( &ent->client->ps.saber[saberNum] ); + + ent->client->ps.dualSabers = qfalse; + ent->client->ps.saber[saberNum].Deactivate(); + ent->client->ps.saber[saberNum].SetLength( 0.0f ); + if ( ent->weaponModel[saberNum] > 0 ) + { + gi.G2API_SetSkin( &ent->ghoul2[ent->weaponModel[saberNum]], -1, 0 ); + gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[saberNum] ); + ent->weaponModel[saberNum] = -1; + } + if ( ent->client->ps.saberAnimLevel == SS_DUAL + || ent->client->ps.saberAnimLevel == SS_STAFF ) + {//change to the style to the default + for ( int i = SS_FAST; i < SS_NUM_SABER_STYLES; i++ ) + { + if ( (ent->client->ps.saberStylesKnown&(1<client->ps.saberAnimLevel = i; + if ( ent->s.number < MAX_CLIENTS ) + { + cg.saberAnimLevelPending = ent->client->ps.saberAnimLevel; + } + break; + } + } + } +} + +void WP_SetSaber( gentity_t *ent, int saberNum, char *saberName ) +{ + if ( !ent || !ent->client ) + { + return; + } + if ( Q_stricmp( "none", saberName ) == 0 || Q_stricmp( "remove", saberName ) == 0 ) + { + WP_RemoveSaber( ent, saberNum ); + return; + } + if ( ent->weaponModel[saberNum] > 0 ) + { + gi.G2API_RemoveGhoul2Model(ent->ghoul2, ent->weaponModel[saberNum]); + ent->weaponModel[saberNum] = -1; + } + WP_SaberParseParms( saberName, &ent->client->ps.saber[saberNum] );//get saber info + if ( ent->client->ps.saber[saberNum].style ) + { + ent->client->ps.saberStylesKnown |= (1<client->ps.saber[saberNum].style); + } + if ( saberNum == 1 && ent->client->ps.saber[1].twoHanded ) + {//not allowed to use a 2-handed saber as second saber + WP_RemoveSaber( ent, saberNum ); + return; + } + G_ModelIndex( ent->client->ps.saber[saberNum].model ); + WP_SaberInitBladeData( ent ); + //int boltNum = ent->handRBolt; + if ( saberNum == 1 ) + { + ent->client->ps.dualSabers = qtrue; + //boltNum = ent->handLBolt; + } + WP_SaberAddG2SaberModels( ent, saberNum ); + ent->client->ps.saber[saberNum].SetLength( 0.0f ); + ent->client->ps.saber[saberNum].Activate(); + + if ( ent->client->ps.saber[saberNum].style != SS_NONE ) + {//change to the style we're supposed to be using + ent->client->ps.saberAnimLevel = ent->client->ps.saber[saberNum].style; + ent->client->ps.saberStylesKnown |= (1<client->ps.saberAnimLevel); + if ( ent->s.number < MAX_CLIENTS ) + { + cg.saberAnimLevelPending = ent->client->ps.saberAnimLevel; + } + } +} + +void WP_SaberSetColor( gentity_t *ent, int saberNum, int bladeNum, char *colorName ) +{ + if ( !ent || !ent->client ) + { + return; + } + ent->client->ps.saber[saberNum].blade[bladeNum].color = TranslateSaberColor( colorName ); +} + +extern void WP_SetSaberEntModelSkin( gentity_t *ent, gentity_t *saberent ); +qboolean WP_BreakSaber( gentity_t *ent, const char *surfName, saberType_t saberType ) +{//Make sure there *is* one specified and not using dualSabers + if ( ent == NULL || ent->client == NULL ) + {//invalid ent or client + return qfalse; + } + + if ( ent->s.number < MAX_CLIENTS ) + {//player + //if ( g_spskill->integer < 3 ) + {//only on the hardest level? + //FIXME: add a cvar? + return qfalse; + } + } + + if ( ent->health <= 0 ) + {//not if they're dead + return qfalse; + } + + if ( ent->client->ps.weapon != WP_SABER ) + {//not holding saber + return qfalse; + } + + if ( ent->client->ps.dualSabers ) + {//FIXME: handle this? + return qfalse; + } + + if ( !ent->client->ps.saber[0].brokenSaber1 ) + {//not breakable into another type of saber + return qfalse; + } + + if ( PM_SaberInStart( ent->client->ps.saberMove ) //in a start + || PM_SaberInTransition( ent->client->ps.saberMove ) //in a transition + || PM_SaberInAttack( ent->client->ps.saberMove ) )//in an attack + {//don't break when in the middle of an attack + return qfalse; + } + + if ( Q_stricmpn( "w_", surfName, 2 ) + && Q_stricmpn( "saber_", surfName, 6 ) //hack because using mod-community made saber + && Q_stricmp( "cylinder01", surfName ) )//hack because using mod-community made saber + {//didn't hit my weapon + return qfalse; + } + + //Sith Sword should ALWAYS do this + if ( saberType != SABER_SITH_SWORD && Q_irand( 0, 50 ) )//&& Q_irand( 0, 10 ) ) + {//10% chance - FIXME: extern this, too? + return qfalse; + } + + //break it + char *replacementSaber1 = G_NewString( ent->client->ps.saber[0].brokenSaber1 ); + char *replacementSaber2 = G_NewString( ent->client->ps.saber[0].brokenSaber2 ); + int i, originalNumBlades = ent->client->ps.saber[0].numBlades; + qboolean broken = qfalse; + saber_colors_t colors[MAX_BLADES]; + + //store the colors + for ( i = 0; i < MAX_BLADES; i++ ) + { + colors[i] = ent->client->ps.saber[0].blade[i].color; + } + + //FIXME: chance of dropping the right-hand one? Based on damage, or...? + //FIXME: sound & effect when this happens, and send them into a broken parry? + + //remove saber[0], replace with replacementSaber1 + if ( replacementSaber1 ) + { + WP_RemoveSaber( ent, 0 ); + WP_SetSaber( ent, 0, replacementSaber1 ); + for ( i = 0; i < ent->client->ps.saber[0].numBlades; i++ ) + { + ent->client->ps.saber[0].blade[i].color = colors[i]; + } + broken = qtrue; + //change my saberent's model and skin to match my new right-hand saber + WP_SetSaberEntModelSkin( ent, &g_entities[ent->client->ps.saberEntityNum] ); + } + + if ( originalNumBlades <= 1 ) + {//nothing to split off + //FIXME: handle this? + } + else + { + //remove saber[1], replace with replacementSaber2 + if ( replacementSaber2 ) + {//FIXME: 25% chance that it just breaks - just spawn the second saber piece and toss it away immediately, can't be picked up. + //shouldn't be one in this hand, but just in case, remove it + WP_RemoveSaber( ent, 1 ); + WP_SetSaber( ent, 1, replacementSaber2 ); + + //put the remainder of the original saber's blade colors onto this saber's blade(s) + for ( i = ent->client->ps.saber[0].numBlades; i < MAX_BLADES; i++ ) + { + ent->client->ps.saber[1].blade[i-ent->client->ps.saber[0].numBlades].color = colors[i]; + } + broken = qtrue; + } + } + return broken; +} + +void WP_SaberLoadParms( void ) +{ + int len, totallen, saberExtFNLen, fileCnt, i; + char *buffer, *holdChar, *marker; + char saberExtensionListBuf[2048]; // The list of file names read in + + //gi.Printf( "Parsing *.sab saber definitions\n" ); + + //set where to store the first one + totallen = 0; + marker = SaberParms; + marker[0] = '\0'; + + //now load in the .sab definitions + fileCnt = gi.FS_GetFileList("ext_data/sabers", ".sab", saberExtensionListBuf, sizeof(saberExtensionListBuf) ); + + holdChar = saberExtensionListBuf; + for ( i = 0; i < fileCnt; i++, holdChar += saberExtFNLen + 1 ) + { + saberExtFNLen = strlen( holdChar ); + + len = gi.FS_ReadFile( va( "ext_data/sabers/%s", holdChar), (void **) &buffer ); + + if ( len == -1 ) + { + gi.Printf( "WP_SaberLoadParms: error reading %s\n", holdChar ); + } + else + { + if ( totallen && *(marker-1) == '}' ) + {//don't let it end on a } because that should be a stand-alone token + strcat( marker, " " ); + totallen++; + marker++; + } + len = COM_Compress( buffer ); + + if ( totallen + len >= MAX_SABER_DATA_SIZE ) { + G_Error( "WP_SaberLoadParms: ran out of space before reading %s\n(you must make the .npc files smaller)", holdChar ); + } + strcat( marker, buffer ); + gi.FS_FreeFile( buffer ); + + totallen += len; + marker += len; + } + } +} diff --git a/code/ghoul2/G2.h b/code/ghoul2/G2.h new file mode 100644 index 0000000..bf8c9c3 --- /dev/null +++ b/code/ghoul2/G2.h @@ -0,0 +1,201 @@ +#pragma once +#if !defined(G2_H_INC) +#define G2_H_INC + +class CMiniHeap; + +// defines to setup the +#define ENTITY_WIDTH 12 +#define MODEL_WIDTH 10 +#define BOLT_WIDTH 10 + +#define MODEL_AND ((1< +#include +#pragma warning (pop) + +#ifndef __Q_SHARED_H + #include "../game/q_shared.h" +#endif + +#if !defined(TR_LOCAL_H) + #include "../renderer/tr_local.h" +#endif + +#if !defined(G2_H_INC) + #include "G2.h" +#endif + +#if !defined(MINIHEAP_H_INC) + #include "..\qcommon\MiniHeap.h" +#endif + + +#ifdef FINAL_BUILD +#define G2API_DEBUG (0) // please don't change this +#else +#if defined(_DEBUG) && !defined(_XBOX) +#define G2API_DEBUG (1) +#else +#define G2API_DEBUG (0) // change this to test g2api in release +#endif +#endif + +//rww - RAGDOLL_BEGIN +#include "ghoul2_gore.h" +//rww - RAGDOLL_END + +using namespace std; + +extern mdxaBone_t worldMatrix; +extern mdxaBone_t worldMatrixInv; + +extern cvar_t *r_Ghoul2TimeBase; + +#define G2_MODEL_OK(g) ((g)&&(g)->mValid&&(g)->aHeader&&(g)->currentModel&&(g)->animModel) + + +#define G2_DEBUG_TIME (0) + +static int G2TimeBases[NUM_G2T_TIME]; + +bool G2_TestModelPointers(CGhoul2Info *ghlInfo); + +#if G2API_DEBUG +#include //for isnan + + +#define MAX_ERROR_PRINTS (3) +class ErrorReporter +{ + string mName; + map mErrors; +public: + ErrorReporter(const string &name) : + mName(name) + { + } + ~ErrorReporter() + { + char mess[1000]; + int total=0; + sprintf(mess,"****** %s Error Report Begin******\n",mName.c_str()); + OutputDebugString(mess); + + map::iterator i; + for (i=mErrors.begin();i!=mErrors.end();i++) + { + total+=(*i).second; + sprintf(mess,"%s (hits %d)\n",(*i).first.c_str(),(*i).second); + OutputDebugString(mess); + } + + sprintf(mess,"****** %s Error Report End %d errors of %d kinds******\n",mName.c_str(),total,mErrors.size()); + OutputDebugString(mess); + } + int AnimTest(CGhoul2Info_v &ghoul2,const char *m,const char *, int line) + { + if (G2_SetupModelPointers(ghoul2)) + { + int i; + for (i=0; ianimModel->name); + strcpy(GLAName2,ghlInfo->aHeader->name); + strcpy(GLMName1,ghlInfo->mFileName); + strcpy(GLMName2,ghlInfo->currentModel->name); + + int numFramesInFile=ghlInfo->aHeader->numFrames; + + int numActiveBones=0; + for (i=0;imBlist.size();i++) + { + if (ghlInfo->mBlist[i].boneNumber!=-1) // slot used? + { + if (ghlInfo->mBlist[i].flags&BONE_ANIM_TOTAL) // anim on this? + { + numActiveBones++; + bool loop=!!(ghlInfo->mBlist[i].flags&BONE_ANIM_OVERRIDE_LOOP); + bool not_loop=!!(ghlInfo->mBlist[i].flags&BONE_ANIM_OVERRIDE); + + if (loop==not_loop) + { + Error("Unusual animation flags, should have some sort of override, but not both",1,0,line); + } + + bool freeze=(ghlInfo->mBlist[i].flags&BONE_ANIM_OVERRIDE_FREEZE) == BONE_ANIM_OVERRIDE_FREEZE; + + if (loop&&freeze) + { + Error("Unusual animation flags, loop and freeze",1,0,line); + } + bool no_lerp=!!(ghlInfo->mBlist[i].flags&BONE_ANIM_NO_LERP); + bool blend=!!(ghlInfo->mBlist[i].flags&BONE_ANIM_BLEND); + + + //comments according to jake + int startFrame=ghlInfo->mBlist[i].startFrame; // start frame for animation + int endFrame=ghlInfo->mBlist[i].endFrame; // end frame for animation NOTE anim actually ends on endFrame+1 + int startTime=ghlInfo->mBlist[i].startTime; // time we started this animation + int pauseTime=ghlInfo->mBlist[i].pauseTime; // time we paused this animation - 0 if not paused + float animSpeed=ghlInfo->mBlist[i].animSpeed; // speed at which this anim runs. 1.0f means full speed of animation incoming - ie if anim is 20hrtz, we run at 20hrts. If 5hrts, we run at 5 hrts + + float blendFrame=0.0f; // frame PLUS LERP value to blend + int blendLerpFrame=0; // frame to lerp the blend frame with. + + if (blend) + { + blendFrame=ghlInfo->mBlist[i].blendFrame; + blendLerpFrame=ghlInfo->mBlist[i].blendLerpFrame; + if (floor(blendFrame)<0.0f) + { + Error("negative blendFrame",1,0,line); + } + if (ceil(blendFrame)>=float(numFramesInFile)) + { + Error("blendFrame >= numFramesInFile",1,0,line); + } + if (blendLerpFrame<0) + { + Error("negative blendLerpFrame",1,0,line); + } + if (blendLerpFrame>=numFramesInFile) + { + Error("blendLerpFrame >= numFramesInFile",1,0,line); + } + } + if (startFrame<0) + { + Error("negative startFrame",1,0,line); + } + if (startFrame>=numFramesInFile) + { + Error("startFrame >= numFramesInFile",1,0,line); + } + if (endFrame<0) + { + Error("negative endFrame",1,0,line); + } + if (endFrame==0&&animSpeed>0.0f) + { + Error("Zero endFrame",1,0,line); + } + if (endFrame>numFramesInFile) + { + Error("endFrame > numFramesInFile",1,0,line); + } + // mikeg call out here for further checks. + ret=(int)startTime+(int)pauseTime+(int)no_lerp; // quiet VC. + } + } + } + return ret; + } + int Error(const char *m,int kind,const char *, int line) + { + char mess[1000]; + assert(m); + string full=mName; + if (kind==2) + { + full+=":NOTE: "; + } + else if (kind==1) + { +// assert(!"G2API Warning"); + full+=":WARNING: "; + } + else + { +// assert(!"G2API Error"); + full+=":ERROR : "; + } + full+=m; + sprintf(mess," [line %d]",line); + full+=mess; + + // assert(0); + int ret=0; //place a breakpoint here + map::iterator f=mErrors.find(full); + if (f==mErrors.end()) + { + ret++; // or a breakpoint here for the first occurance + mErrors.insert(pair(full,0)); + f=mErrors.find(full); + } + assert(f!=mErrors.end()); + (*f).second++; + if ((*f).second==1000) + { + ret*=-1; // breakpoint to find a specific occurance of an error + } + if ((*f).second<=MAX_ERROR_PRINTS&&kind<2) + { + Com_Printf("%s (hit # %d)\n",full.c_str(),(*f).second); + if (1) + { + sprintf(mess,"%s (hit # %d)\n",full.c_str(),(*f).second); + OutputDebugString(mess); + } + } + return ret; + } +}; + +#include "assert.h" +ErrorReporter &G2APIError() +{ + static ErrorReporter singleton("G2API"); + return singleton; +} + +#define G2ERROR(exp,m) (void)( (exp) || (G2APIError().Error(m,0,__FILE__,__LINE__), 0) ) +#define G2WARNING(exp,m) (void)( (exp) || (G2APIError().Error(m,1,__FILE__,__LINE__), 0) ) +#define G2NOTE(exp,m) (void)( (exp) || (G2APIError().Error(m,2,__FILE__,__LINE__), 0) ) +#define G2ANIM(ghlInfo,m) (void)((G2APIError().AnimTest(ghlInfo,m,__FILE__,__LINE__), 0) ) +#else + +#define G2ERROR(exp,m) ((void)0) +#define G2WARNING(exp,m) ((void)0) +#define G2NOTE(exp,m) ((void)0) +#define G2ANIM(ghlInfo,m) ((void)0) + +#endif + +#ifdef _DEBUG +void G2_Bone_Not_Found(const char *boneName,const char *modName) +{ + G2ERROR(boneName,"NULL Bone Name"); + G2ERROR(boneName[0],"Empty Bone Name"); + if (boneName) + { + G2NOTE(0,va("Bone Not Found (%s:%s)",boneName,modName)); + } +} + +void G2_Bolt_Not_Found(const char *boneName,const char *modName) +{ + G2ERROR(boneName,"NULL Bolt/Bone Name"); + G2ERROR(boneName[0],"Empty Bolt/Bone Name"); + if (boneName) + { + G2NOTE(0,va("Bolt/Bone Not Found (%s:%s)",boneName,modName)); + } +} +#endif + +void G2API_SetTime(int currentTime,int clock) +{ + assert(clock>=0&&clockG2TimeBases[0]+200) + { + G2TimeBases[1]=0; // use server time instead + return; + } +#if G2_DEBUG_TIME + Com_Printf(" after c%6d s%6d\n",G2TimeBases[1],G2TimeBases[0]); +#endif +} + +int G2API_GetTime(int argTime) // this may or may not return arg depending on ghoul2_time cvar +{ + int ret=G2TimeBases[1]; + if ( !ret ) + { + ret = G2TimeBases[0]; + } + return ret; +} + + +// must be a power of two +#define MAX_G2_MODELS (512) +#define G2_MODEL_BITS (9) +#define G2_INDEX_MASK (MAX_G2_MODELS-1) + +void RemoveBoneCache(CBoneCache *boneCache); + +class Ghoul2InfoArray : public IGhoul2InfoArray +{ + vector mInfos[MAX_G2_MODELS]; + int mIds[MAX_G2_MODELS]; + list mFreeIndecies; + void DeleteLow(int idx) + { + { + int model; + for (model=0; model< mInfos[idx].size(); model++) + { + RemoveBoneCache(mInfos[idx][model].mBoneCache); + mInfos[idx][model].mBoneCache=0; + } + } + mInfos[idx].clear(); + + if ((mIds[idx]>>G2_MODEL_BITS)>(1<<(31-G2_MODEL_BITS))) + { + mIds[idx]=MAX_G2_MODELS+idx; //rollover reset id to minimum value + mFreeIndecies.push_back(idx); + } + else + { + mIds[idx]+=MAX_G2_MODELS; + mFreeIndecies.push_front(idx); + } + } +public: + Ghoul2InfoArray() + { + int i; + for (i=0;i::iterator j; + for (j=mFreeIndecies.begin();j!=mFreeIndecies.end();j++) + { + if (*j==i) + break; + } + if (j==mFreeIndecies.end()) + { +#if G2API_DEBUG + sprintf(mess,"Leaked Info idx=%d id=%d sz=%d\n", i, mIds[i], mInfos[i].size()); + OutputDebugString(mess); + if (mInfos[i].size()) + { + sprintf(mess,"%s\n", mInfos[i][0].mFileName); + OutputDebugString(mess); + } +#endif + DeleteLow(i); + } + } + } +#if G2API_DEBUG + else + { + OutputDebugString("No ghoul2 info slots leaked\n"); + } +#endif + } + +#ifdef _XBOX + // I'm sorry, but I really don't trust this thing any other way. + void Clear() + { + mFreeIndecies.clear(); + for (int i=0;i0); //negative handle??? + assert((handle&G2_INDEX_MASK)>=0&&(handle&G2_INDEX_MASK)0); //null handle + assert((handle&G2_INDEX_MASK)>=0&&(handle&G2_INDEX_MASK) &Get(int handle) + { + static vector null; + assert(handle>0); //null handle + assert((handle&G2_INDEX_MASK)>=0&&(handle&G2_INDEX_MASK)=MAX_G2_MODELS||mIds[handle&G2_INDEX_MASK]!=handle) + { + null.clear(); + return null; + } + return mInfos[handle&G2_INDEX_MASK]; + } + const vector &Get(int handle) const + { + assert(handle>0); + assert(mIds[handle&G2_INDEX_MASK]==handle); // not a valid handle, could be old or garbage + return mInfos[handle&G2_INDEX_MASK]; + } + +#if G2API_DEBUG + vector &GetDebug(int handle) + { + static vector null; + if (handle<=0||(handle&G2_INDEX_MASK)<0||(handle&G2_INDEX_MASK)>=MAX_G2_MODELS||mIds[handle&G2_INDEX_MASK]!=handle) + { + return *(vector *)0; // null reference, intentional + } + return mInfos[handle&G2_INDEX_MASK]; + } + void TestAllAnims() + { + int j; + for (j=0;j &ghoul2=mInfos[j]; + int i; + for (i=0; iClear(); +} + +void Ghoul2InfoArray_Reset(void) +{ + ((Ghoul2InfoArray *)(&TheGhoul2InfoArray()))->Reset(); +} +#endif + +#if G2API_DEBUG +vector &DebugG2Info(int handle) +{ + return ((Ghoul2InfoArray *)(&TheGhoul2InfoArray()))->GetDebug(handle); +} + +CGhoul2Info &DebugG2InfoI(int handle,int item) +{ + return ((Ghoul2InfoArray *)(&TheGhoul2InfoArray()))->GetDebug(handle)[item]; +} + +void TestAllGhoul2Anims() +{ + ((Ghoul2InfoArray *)(&TheGhoul2InfoArray()))->TestAllAnims(); +} + +#endif + + + + +// this is the ONLY function to read entity states directly +void G2API_CleanGhoul2Models(CGhoul2Info_v &ghoul2) +{ +#ifdef _G2_GORE + G2API_ClearSkinGore ( ghoul2 ); +#endif + ghoul2.~CGhoul2Info_v(); +} + +qhandle_t G2API_PrecacheGhoul2Model(const char *fileName) +{ + return RE_RegisterModel((char *)fileName); +} + +// initialise all that needs to be on a new Ghoul II model +int G2API_InitGhoul2Model(CGhoul2Info_v &ghoul2, const char *fileName, int, qhandle_t customSkin, + qhandle_t customShader, int modelFlags, int lodBias) +{ + int model = -1; + + G2ERROR(fileName&&fileName[0],"NULL filename"); + + if (!fileName||!fileName[0]) + { + assert(fileName[0]); + return -1; + } + + // find a free spot in the list + for (model=0; model< ghoul2.size(); model++) + { + if (ghoul2[model].mModelindex == -1) + { + ghoul2[model]=CGhoul2Info(); + break; + } + } + if (model==ghoul2.size()) + { + assert(model < 8); //arb, just catching run-away models + CGhoul2Info info; + strcpy(info.mFileName, fileName); + info.mModelindex = 0; + if(G2_TestModelPointers(&info)) { + ghoul2.push_back(CGhoul2Info()); + } else { + return -1; + } + } + + strcpy(ghoul2[model].mFileName, fileName); + ghoul2[model].mModelindex = model; + if (!G2_TestModelPointers(&ghoul2[model])) + { + ghoul2[model].mFileName[0]=0; + ghoul2[model].mModelindex = -1; + } + else + { + G2_Init_Bone_List(ghoul2[model].mBlist); + G2_Init_Bolt_List(ghoul2[model].mBltlist); + ghoul2[model].mCustomShader = customShader; + ghoul2[model].mCustomSkin = customSkin; + ghoul2[model].mLodBias = lodBias; + ghoul2[model].mAnimFrameDefault = 0; + ghoul2[model].mFlags = 0; + + ghoul2[model].mModelBoltLink = -1; + } + return ghoul2[model].mModelindex; +} + +qboolean G2API_SetLodBias(CGhoul2Info *ghlInfo, int lodBias) +{ + G2ERROR(ghlInfo,"NULL ghlInfo"); + if (ghlInfo) + { + ghlInfo->mLodBias = lodBias; + return qtrue; + } + return qfalse; +} + +qboolean G2API_SetSkin(CGhoul2Info *ghlInfo, qhandle_t customSkin, qhandle_t renderSkin) +{ + G2ERROR(ghlInfo,"NULL ghlInfo"); + if (ghlInfo) + { + ghlInfo->mCustomSkin = customSkin; + if (renderSkin) + {//this is going to set the surfs on/off matching the skin file +extern void G2API_SetSurfaceOnOffFromSkin (CGhoul2Info *ghlInfo, qhandle_t renderSkin); //tr_ghoul2.cpp + G2API_SetSurfaceOnOffFromSkin( ghlInfo, renderSkin ); + } + return qtrue; + } + return qfalse; +} + +qboolean G2API_SetShader(CGhoul2Info *ghlInfo, qhandle_t customShader) +{ + G2ERROR(ghlInfo,"NULL ghlInfo"); + if (ghlInfo) + { + ghlInfo->mCustomShader = customShader; + return qtrue; + } + return qfalse; +} + +qboolean G2API_SetSurfaceOnOff(CGhoul2Info *ghlInfo, const char *surfaceName, const int flags) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + G2ERROR(!(flags&~(G2SURFACEFLAG_OFF | G2SURFACEFLAG_NODESCENDANTS)),"G2API_SetSurfaceOnOff Illegal Flags"); + // ensure we flush the cache + ghlInfo->mMeshFrameNum = 0; + return G2_SetSurfaceOnOff(ghlInfo, surfaceName, flags); + } + return qfalse; +} + + +qboolean G2API_SetRootSurface(CGhoul2Info_v &ghlInfo, const int modelIndex, const char *surfaceName) +{ + G2ERROR(ghlInfo.IsValid(),"Invalid ghlInfo"); + G2ERROR(surfaceName,"Invalid surfaceName"); + if (G2_SetupModelPointers(ghlInfo)) + { + G2ERROR(modelIndex>=0&&modelIndex=0&&modelIndexmMeshFrameNum = 0; + return G2_AddSurface(ghlInfo, surfaceNumber, polyNumber, BarycentricI, BarycentricJ, lod); + } + return -1; +} + +qboolean G2API_RemoveSurface(CGhoul2Info *ghlInfo, const int index) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mMeshFrameNum = 0; + return G2_RemoveSurface(ghlInfo->mSlist, index); + } + return qfalse; +} + +int G2API_GetParentSurface(CGhoul2Info *ghlInfo, const int index) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_GetParentSurface(ghlInfo, index); + } + return -1; +} + +int G2API_GetSurfaceRenderStatus(CGhoul2Info *ghlInfo, const char *surfaceName) +{ + G2ERROR(surfaceName,"Invalid surfaceName"); + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_IsSurfaceRendered(ghlInfo, surfaceName, ghlInfo->mSlist); + } + return -1; +} + +qboolean G2API_RemoveGhoul2Model(CGhoul2Info_v &ghlInfo, const int modelIndex) +{ + // sanity check + if (!ghlInfo.size() || (ghlInfo.size() <= modelIndex) || modelIndex < 0 || (ghlInfo[modelIndex].mModelindex <0)) + { + // if we hit this assert then we are trying to delete a ghoul2 model on a ghoul2 instance that + // one way or another is already gone. + G2ERROR(0,"Remove Nonexistant Model"); + assert(0 && "remove non existing model"); + return qfalse; + } + +#ifdef _G2_GORE + // Cleanup the gore attached to this model + if ( ghlInfo[modelIndex].mGoreSetTag ) + { + DeleteGoreSet ( ghlInfo[modelIndex].mGoreSetTag ); + ghlInfo[modelIndex].mGoreSetTag = 0; + } +#endif + + RemoveBoneCache(ghlInfo[modelIndex].mBoneCache); + ghlInfo[modelIndex].mBoneCache=0; + + // set us to be the 'not active' state + ghlInfo[modelIndex].mModelindex = -1; + ghlInfo[modelIndex].mFileName[0]=0; + + ghlInfo[modelIndex]=CGhoul2Info(); + return qtrue; +} + +//rww - RAGDOLL_BEGIN +#define GHOUL2_RAG_STARTED 0x0010 +#define GHOUL2_RAG_FORCESOLVE 0x1000 //api-override, determine if ragdoll should be forced to continue solving even if it thinks it is settled +//rww - RAGDOLL_END + +int G2API_GetAnimIndex(CGhoul2Info *ghlInfo) +{ + if (ghlInfo) + { + return ghlInfo->animModelIndexOffset; + } + return 0; +} + +qboolean G2API_SetAnimIndex(CGhoul2Info *ghlInfo, const int index) +{ + // Is This A Valid G2 Model? + //--------------------------- + if (ghlInfo) + { + // Is This A New Anim Index? + //--------------------------- + if (ghlInfo->animModelIndexOffset != index) + { + ghlInfo->animModelIndexOffset = index; + ghlInfo->currentAnimModelSize = 0; // Clear anim size so SetupModelPointers recalcs + +// RemoveBoneCache(ghlInfo[0].mBoneCache); +// ghlInfo[0].mBoneCache=0; + + // Kill All Existing Animation, Blending, Etc. + //--------------------------------------------- + for (int index=0; indexmBlist.size(); index++) + { + ghlInfo->mBlist[index].flags &= ~(BONE_ANIM_TOTAL); + ghlInfo->mBlist[index].flags &= ~(BONE_ANGLES_TOTAL); +// G2_Remove_Bone_Index(ghlInfo->mBlist, index); + } + } + return qtrue; + } + return qfalse; +} + +qboolean G2API_SetBoneAnimIndex(CGhoul2Info *ghlInfo, const int index, const int startFrame, const int endFrame, const int flags, const float animSpeed, const int AcurrentTime, const float setFrame, const int blendTime) +{ + //rww - RAGDOLL_BEGIN + if (ghlInfo && (ghlInfo->mFlags & GHOUL2_RAG_STARTED)) + { + return qfalse; + } + //rww - RAGDOLL_END + + qboolean ret=qfalse; + if (G2_SetupModelPointers(ghlInfo)) + { + G2ERROR(startFrame>=0,"startframe<0"); + G2ERROR(startFrameaHeader->numFrames,"startframe>=numframes"); + G2ERROR(endFrame>0,"endframe<=0"); + G2ERROR(endFrame<=ghlInfo->aHeader->numFrames,"endframe>numframes"); + G2ERROR(setFrameaHeader->numFrames,"setframe>=numframes"); + G2ERROR(setFrame==-1.0f||setFrame>=0.0f,"setframe<0 but not -1"); + if (startFrame<0||startFrame>=ghlInfo->aHeader->numFrames) + { + *(int *)&startFrame=0; // cast away const + } + if (endFrame<=0||endFrame>ghlInfo->aHeader->numFrames) + { + *(int *)&endFrame=1; + } + if (setFrame!=-1.0f&&(setFrame<0.0f||setFrame>=(float)ghlInfo->aHeader->numFrames)) + { + *(float *)&setFrame=0.0f; + } + ghlInfo->mSkelFrameNum = 0; + G2ERROR(index>=0&&indexmBlist.size(),va("Out of Range Bone Index (%s)",ghlInfo->mFileName)); + if (index>=0&&indexmBlist.size()) + { + G2ERROR(ghlInfo->mBlist[index].boneNumber>=0,va("Bone Index is not Active (%s)",ghlInfo->mFileName)); + int currentTime=G2API_GetTime(AcurrentTime); +#if 0 + /* + if ( ge->ValidateAnimRange( startFrame, endFrame, animSpeed ) == -1 ) + { + int wtf = 1; + } + */ +#endif + ret=G2_Set_Bone_Anim_Index(ghlInfo->mBlist, index, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime,ghlInfo->aHeader->numFrames); + G2ANIM(ghlInfo,"G2API_SetBoneAnimIndex"); + } + } + G2WARNING(ret,va("G2API_SetBoneAnimIndex Failed (%s)",ghlInfo->mFileName)); + return ret; +} + +qboolean G2API_SetBoneAnim(CGhoul2Info *ghlInfo, const char *boneName, const int startFrame, const int endFrame, const int flags, const float animSpeed, const int AcurrentTime, const float setFrame, const int blendTime) +{ + //rww - RAGDOLL_BEGIN + if (ghlInfo && ghlInfo->mFlags & GHOUL2_RAG_STARTED) + { + return qfalse; + } + //rww - RAGDOLL_END + + qboolean ret=qfalse; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + G2ERROR(startFrame>=0,"startframe<0"); + G2ERROR(startFrameaHeader->numFrames,"startframe>=numframes"); + G2ERROR(endFrame>0,"endframe<=0"); + G2ERROR(endFrame<=ghlInfo->aHeader->numFrames,"endframe>numframes"); + G2ERROR(setFrameaHeader->numFrames,"setframe>=numframes"); + G2ERROR(setFrame==-1.0f||setFrame>=0.0f,"setframe<0 but not -1"); + if (startFrame<0||startFrame>=ghlInfo->aHeader->numFrames) + { + *(int *)&startFrame=0; // cast away const + } + if (endFrame<=0||endFrame>ghlInfo->aHeader->numFrames) + { + *(int *)&endFrame=1; + } + if (setFrame!=-1.0f&&(setFrame<0.0f||setFrame>=(float)ghlInfo->aHeader->numFrames)) + { + *(float *)&setFrame=0.0f; + } + ghlInfo->mSkelFrameNum = 0; + int currentTime=G2API_GetTime(AcurrentTime); + ret=G2_Set_Bone_Anim(ghlInfo, ghlInfo->mBlist, boneName, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime); + G2ANIM(ghlInfo,"G2API_SetBoneAnim"); + } + G2WARNING(ret,"G2API_SetBoneAnim Failed"); + return ret; +} + +qboolean G2API_GetBoneAnim(CGhoul2Info *ghlInfo, const char *boneName, const int AcurrentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *) +{ + qboolean ret=qfalse; + G2ERROR(boneName,"NULL boneName"); + if (G2_SetupModelPointers(ghlInfo)) + { + int currentTime=G2API_GetTime(AcurrentTime); + ret=G2_Get_Bone_Anim(ghlInfo, ghlInfo->mBlist, boneName, currentTime, currentFrame, + startFrame, endFrame, flags, animSpeed); + } + G2WARNING(ret,"G2API_GetBoneAnim Failed"); + return ret; +} + +qboolean G2API_GetBoneAnimIndex(CGhoul2Info *ghlInfo, const int iBoneIndex, const int AcurrentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *) +{ + qboolean ret=qfalse; + if (G2_SetupModelPointers(ghlInfo)) + { + int currentTime=G2API_GetTime(AcurrentTime); + G2NOTE(iBoneIndex>=0&&iBoneIndexmBlist.size(),va("Bad Bone Index (%d:%s)",iBoneIndex,ghlInfo->mFileName)); + if (iBoneIndex>=0&&iBoneIndexmBlist.size()) + { + G2NOTE(ghlInfo->mBlist[iBoneIndex].flags & (BONE_ANIM_OVERRIDE_LOOP | BONE_ANIM_OVERRIDE),"GetBoneAnim on non-animating bone."); + if ((ghlInfo->mBlist[iBoneIndex].flags & (BONE_ANIM_OVERRIDE_LOOP | BONE_ANIM_OVERRIDE))) + { + int sf,ef; + ret=G2_Get_Bone_Anim_Index( ghlInfo->mBlist,// boneInfo_v &blist, + iBoneIndex, // const int index, + currentTime, // const int currentTime, + currentFrame, // float *currentFrame, + &sf, // int *startFrame, + &ef, // int *endFrame, + flags, // int *flags, + animSpeed, // float *retAnimSpeed, + ghlInfo->aHeader->numFrames + ); + G2ERROR(sf>=0,"returning startframe<0"); + G2ERROR(sfaHeader->numFrames,"returning startframe>=numframes"); + G2ERROR(ef>0,"returning endframe<=0"); + G2ERROR(ef<=ghlInfo->aHeader->numFrames,"returning endframe>numframes"); + if (currentFrame) + { + G2ERROR(*currentFrame>=0.0f,"returning currentframe<0"); + G2ERROR(((int)(*currentFrame))aHeader->numFrames,"returning currentframe>=numframes"); + } + if (endFrame) + { + *endFrame=ef; + } + if (startFrame) + { + *startFrame=sf; + } + G2ANIM(ghlInfo,"G2API_GetBoneAnimIndex"); + } + } + } + if (!ret) + { + *endFrame=1; + *startFrame=0; + *flags=0; + *currentFrame=0.0f; + *animSpeed=1.0f; + } + G2NOTE(ret,"G2API_GetBoneAnimIndex Failed"); + return ret; +} + +qboolean G2API_GetAnimRange(CGhoul2Info *ghlInfo, const char *boneName, int *startFrame, int *endFrame) +{ + qboolean ret=qfalse; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + ret=G2_Get_Bone_Anim_Range(ghlInfo, ghlInfo->mBlist, boneName, startFrame, endFrame); + G2ANIM(ghlInfo,"G2API_GetAnimRange"); + } +// looks like the game checks the return value +// G2WARNING(ret,"G2API_GetAnimRange Failed"); + return ret; +} + +qboolean G2API_GetAnimRangeIndex(CGhoul2Info *ghlInfo, const int boneIndex, int *startFrame, int *endFrame) +{ + qboolean ret=qfalse; + if (G2_SetupModelPointers(ghlInfo)) + { + G2ERROR(boneIndex>=0&&boneIndexmBlist.size(),"Bad Bone Index"); + if (boneIndex>=0&&boneIndexmBlist.size()) + { + ret=G2_Get_Bone_Anim_Range_Index(ghlInfo->mBlist, boneIndex, startFrame, endFrame); + G2ANIM(ghlInfo,"G2API_GetAnimRange"); + } + } +// looks like the game checks the return value +// G2WARNING(ret,"G2API_GetAnimRangeIndex Failed"); + return ret; +} + +qboolean G2API_PauseBoneAnim(CGhoul2Info *ghlInfo, const char *boneName, const int AcurrentTime) +{ + qboolean ret=qfalse; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + int currentTime=G2API_GetTime(AcurrentTime); + ret=G2_Pause_Bone_Anim(ghlInfo, ghlInfo->mBlist, boneName, currentTime); + G2ANIM(ghlInfo,"G2API_PauseBoneAnim"); + } + G2NOTE(ret,"G2API_PauseBoneAnim Failed"); + return ret; +} + +qboolean G2API_PauseBoneAnimIndex(CGhoul2Info *ghlInfo, const int boneIndex, const int AcurrentTime) +{ + qboolean ret=qfalse; + if (G2_SetupModelPointers(ghlInfo)) + { + int currentTime=G2API_GetTime(AcurrentTime); + G2ERROR(boneIndex>=0&&boneIndexmBlist.size(),"Bad Bone Index"); + if (boneIndex>=0&&boneIndexmBlist.size()) + { + ret=G2_Pause_Bone_Anim_Index(ghlInfo->mBlist, boneIndex, currentTime,ghlInfo->aHeader->numFrames); + G2ANIM(ghlInfo,"G2API_PauseBoneAnimIndex"); + } + } + G2WARNING(ret,"G2API_PauseBoneAnimIndex Failed"); + return ret; +} + +qboolean G2API_IsPaused(CGhoul2Info *ghlInfo, const char *boneName) +{ + qboolean ret=qfalse; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + ret=G2_IsPaused(ghlInfo, ghlInfo->mBlist, boneName); + } + G2WARNING(ret,"G2API_IsPaused Failed"); + return ret; +} + +qboolean G2API_StopBoneAnimIndex(CGhoul2Info *ghlInfo, const int index) +{ + qboolean ret=qfalse; + G2ERROR(ghlInfo,"NULL ghlInfo"); + if (G2_SetupModelPointers(ghlInfo)) + { + G2ERROR(index>=0&&indexmBlist.size(),"Bad Bone Index"); + if (index>=0&&indexmBlist.size()) + { + ret=G2_Stop_Bone_Anim_Index(ghlInfo->mBlist, index); + G2ANIM(ghlInfo,"G2API_StopBoneAnimIndex"); + } + } + //G2WARNING(ret,"G2API_StopBoneAnimIndex Failed"); + return ret; +} + +qboolean G2API_StopBoneAnim(CGhoul2Info *ghlInfo, const char *boneName) +{ + qboolean ret=qfalse; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + ret=G2_Stop_Bone_Anim(ghlInfo, ghlInfo->mBlist, boneName); + G2ANIM(ghlInfo,"G2API_StopBoneAnim"); + } + G2WARNING(ret,"G2API_StopBoneAnim Failed"); + return ret; +} + +qboolean G2API_SetBoneAnglesIndex(CGhoul2Info *ghlInfo, const int index, const vec3_t angles, const int flags, + const Eorientations yaw, const Eorientations pitch, const Eorientations roll, + qhandle_t *, int blendTime, int AcurrentTime) +{ + //rww - RAGDOLL_BEGIN + if (ghlInfo && ghlInfo->mFlags & GHOUL2_RAG_STARTED) + { + return qfalse; + } + //rww - RAGDOLL_END + + qboolean ret=qfalse; + if (G2_SetupModelPointers(ghlInfo)) + { + int currentTime=G2API_GetTime(AcurrentTime); + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + G2ERROR(index>=0&&indexmBlist.size(),"G2API_SetBoneAnglesIndex:Invalid bone index"); + if (index>=0&&indexmBlist.size()) + { + ret=G2_Set_Bone_Angles_Index(ghlInfo, ghlInfo->mBlist, index, angles, flags, yaw, pitch, roll,blendTime, currentTime); + } + } + G2WARNING(ret,"G2API_SetBoneAnglesIndex Failed"); + return ret; +} + +qboolean G2API_SetBoneAngles(CGhoul2Info *ghlInfo, const char *boneName, const vec3_t angles, const int flags, + const Eorientations up, const Eorientations left, const Eorientations forward, + qhandle_t *, int blendTime, int AcurrentTime ) +{ + //rww - RAGDOLL_BEGIN + if (ghlInfo && ghlInfo->mFlags & GHOUL2_RAG_STARTED) + { + return qfalse; + } + //rww - RAGDOLL_END + + qboolean ret=qfalse; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + int currentTime=G2API_GetTime(AcurrentTime); + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + ret=G2_Set_Bone_Angles(ghlInfo, ghlInfo->mBlist, boneName, angles, flags, up, left, forward, blendTime, currentTime); + } + G2WARNING(ret,"G2API_SetBoneAngles Failed"); + return ret; +} + +qboolean G2API_SetBoneAnglesMatrixIndex(CGhoul2Info *ghlInfo, const int index, const mdxaBone_t &matrix, + const int flags, qhandle_t *, int blendTime, int AcurrentTime) +{ + qboolean ret=qfalse; + if (G2_SetupModelPointers(ghlInfo)) + { + int currentTime=G2API_GetTime(AcurrentTime); + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + G2ERROR(index>=0&&indexmBlist.size(),"Bad Bone Index"); + if (index>=0&&indexmBlist.size()) + { + ret=G2_Set_Bone_Angles_Matrix_Index(ghlInfo->mBlist, index, matrix, flags, blendTime, currentTime); + } + } + G2WARNING(ret,"G2API_SetBoneAnglesMatrixIndex Failed"); + return ret; +} + +qboolean G2API_SetBoneAnglesMatrix(CGhoul2Info *ghlInfo, const char *boneName, const mdxaBone_t &matrix, + const int flags, qhandle_t *modelList, int blendTime, int AcurrentTime) +{ + qboolean ret=qfalse; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + int currentTime=G2API_GetTime(AcurrentTime); + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + ret=G2_Set_Bone_Angles_Matrix(ghlInfo, ghlInfo->mBlist, boneName, matrix, flags, blendTime, currentTime); + } + G2WARNING(ret,"G2API_SetBoneAnglesMatrix Failed"); + return ret; +} + +qboolean G2API_StopBoneAnglesIndex(CGhoul2Info *ghlInfo, const int index) +{ + qboolean ret=qfalse; + if (G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + G2ERROR(index>=0&&indexmBlist.size(),"Bad Bone Index"); + if (index>=0&&indexmBlist.size()) + { + ret=G2_Stop_Bone_Angles_Index(ghlInfo->mBlist, index); + } + } + G2WARNING(ret,"G2API_StopBoneAnglesIndex Failed"); + return ret; +} + +qboolean G2API_StopBoneAngles(CGhoul2Info *ghlInfo, const char *boneName) +{ + qboolean ret=qfalse; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + ret=G2_Stop_Bone_Angles(ghlInfo, ghlInfo->mBlist, boneName); + } + G2WARNING(ret,"G2API_StopBoneAngles Failed"); + return ret; +} + +//rww - RAGDOLL_BEGIN +class CRagDollParams; +void G2_SetRagDoll(CGhoul2Info_v &ghoul2V,CRagDollParams *parms); +void G2API_SetRagDoll(CGhoul2Info_v &ghoul2,CRagDollParams *parms) +{ + G2_SetRagDoll(ghoul2,parms); +} +//rww - RAGDOLL_END + +qboolean G2API_RemoveBone(CGhoul2Info *ghlInfo, const char *boneName) +{ + qboolean ret=qfalse; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + ret=G2_Remove_Bone(ghlInfo, ghlInfo->mBlist, boneName); + G2ANIM(ghlInfo,"G2API_RemoveBone"); + } + G2WARNING(ret,"G2API_RemoveBone Failed"); + return ret; +} + +//rww - RAGDOLL_BEGIN +#ifdef _DEBUG +extern int ragTraceTime; +extern int ragSSCount; +extern int ragTraceCount; +#endif + +void G2API_AnimateG2Models(CGhoul2Info_v &ghoul2, int AcurrentTime,CRagDollUpdateParams *params) +{ + int model; + int currentTime=G2API_GetTime(AcurrentTime); + +#ifdef _DEBUG + ragTraceTime = 0; + ragSSCount = 0; + ragTraceCount = 0; +#endif + + // Walk the list and find all models that are active + for (model = 0; model < ghoul2.size(); model++) + { + if (ghoul2[model].mModel) + { + G2_Animate_Bone_List(ghoul2,currentTime,model,params); + } + } +#ifdef _DEBUG +// Com_Printf("Rag trace time: %i (%i STARTSOLID, %i TOTAL)\n", ragTraceTime, ragSSCount, ragTraceCount); + +// assert(ragTraceTime < 15); + //assert(ragTraceCount < 600); +#endif +} +//rww - RAGDOLL_END + +int G2_Find_Bone_Rag(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName); +#define RAG_PCJ (0x00001) +#define RAG_EFFECTOR (0x00100) + +static inline boneInfo_t *G2_GetRagBoneConveniently(CGhoul2Info_v &ghoul2, const char *boneName) +{ + assert(ghoul2.size()); + CGhoul2Info *ghlInfo = &ghoul2[0]; + + if (!(ghlInfo->mFlags & GHOUL2_RAG_STARTED)) + { //can't do this if not in ragdoll + return NULL; + } + + int boneIndex = G2_Find_Bone_Rag(ghlInfo, ghlInfo->mBlist, boneName); + + if (boneIndex < 0) + { //bad bone specification + return NULL; + } + + boneInfo_t *bone = &ghlInfo->mBlist[boneIndex]; + + if (!(bone->flags & BONE_ANGLES_RAGDOLL)) + { //only want to return rag bones + return NULL; + } + + return bone; +} + +qboolean G2API_RagPCJConstraint(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t min, vec3_t max) +{ + boneInfo_t *bone = G2_GetRagBoneConveniently(ghoul2, boneName); + + if (!bone) + { + return qfalse; + } + + if (!(bone->RagFlags & RAG_PCJ)) + { //this function is only for PCJ bones + return qfalse; + } + + VectorCopy(min, bone->minAngles); + VectorCopy(max, bone->maxAngles); + + return qtrue; +} + +qboolean G2API_RagPCJGradientSpeed(CGhoul2Info_v &ghoul2, const char *boneName, const float speed) +{ + boneInfo_t *bone = G2_GetRagBoneConveniently(ghoul2, boneName); + + if (!bone) + { + return qfalse; + } + + if (!(bone->RagFlags & RAG_PCJ)) + { //this function is only for PCJ bones + return qfalse; + } + + bone->overGradSpeed = speed; + + return qtrue; +} + +qboolean G2API_RagEffectorGoal(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t pos) +{ + boneInfo_t *bone = G2_GetRagBoneConveniently(ghoul2, boneName); + + if (!bone) + { + return qfalse; + } + + if (!(bone->RagFlags & RAG_EFFECTOR)) + { //this function is only for effectors + return qfalse; + } + + if (!pos) + { //go back to none in case we have one then + bone->hasOverGoal = false; + } + else + { + VectorCopy(pos, bone->overGoalSpot); + bone->hasOverGoal = true; + } + return qtrue; +} + +qboolean G2API_GetRagBonePos(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t pos, vec3_t entAngles, vec3_t entPos, vec3_t entScale) +{ //do something? + return qfalse; +} + +qboolean G2API_RagEffectorKick(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t velocity) +{ + boneInfo_t *bone = G2_GetRagBoneConveniently(ghoul2, boneName); + + if (!bone) + { + return qfalse; + } + + if (!(bone->RagFlags & RAG_EFFECTOR)) + { //this function is only for effectors + return qfalse; + } + + bone->epVelocity[2] = 0; + VectorAdd(bone->epVelocity, velocity, bone->epVelocity); + bone->physicsSettled = false; + + return qtrue; +} + +qboolean G2API_RagForceSolve(CGhoul2Info_v &ghoul2, qboolean force) +{ + assert(ghoul2.size()); + CGhoul2Info *ghlInfo = &ghoul2[0]; + + if (!(ghlInfo->mFlags & GHOUL2_RAG_STARTED)) + { //can't do this if not in ragdoll + return qfalse; + } + + if (force) + { + ghlInfo->mFlags |= GHOUL2_RAG_FORCESOLVE; + } + else + { + ghlInfo->mFlags &= ~GHOUL2_RAG_FORCESOLVE; + } + + return qtrue; +} + +qboolean G2_SetBoneIKState(CGhoul2Info_v &ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params); +qboolean G2API_SetBoneIKState(CGhoul2Info_v &ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params) +{ + return G2_SetBoneIKState(ghoul2, time, boneName, ikState, params); +} + +qboolean G2_IKMove(CGhoul2Info_v &ghoul2, int time, sharedIKMoveParams_t *params); +qboolean G2API_IKMove(CGhoul2Info_v &ghoul2, int time, sharedIKMoveParams_t *params) +{ + return G2_IKMove(ghoul2, time, params); +} + +qboolean G2API_RemoveBolt(CGhoul2Info *ghlInfo, const int index) +{ + qboolean ret=qfalse; + if (G2_SetupModelPointers(ghlInfo)) + { + ret=G2_Remove_Bolt( ghlInfo->mBltlist, index); + } + G2WARNING(ret,"G2API_RemoveBolt Failed"); + return ret; +} + +int G2API_AddBolt(CGhoul2Info *ghlInfo, const char *boneName) +{ + int ret=-1; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + ret=G2_Add_Bolt(ghlInfo, ghlInfo->mBltlist, ghlInfo->mSlist, boneName); + G2NOTE(ret>=0,va("G2API_AddBolt Failed (%s:%s)",boneName,ghlInfo->mFileName)); + } + return ret; +} + +int G2API_AddBoltSurfNum(CGhoul2Info *ghlInfo, const int surfIndex) +{ + int ret=-1; + if (G2_SetupModelPointers(ghlInfo)) + { + ret=G2_Add_Bolt_Surf_Num(ghlInfo, ghlInfo->mBltlist, ghlInfo->mSlist, surfIndex); + } + G2WARNING(ret>=0,"G2API_AddBoltSurfNum Failed"); + return ret; +} + + +qboolean G2API_AttachG2Model(CGhoul2Info *ghlInfo, CGhoul2Info *ghlInfoTo, int toBoltIndex, int toModel) +{ + qboolean ret=qfalse; + if (G2_SetupModelPointers(ghlInfo)&&G2_SetupModelPointers(ghlInfoTo)) + { + G2ERROR(toBoltIndex>=0&&toBoltIndexmBltlist.size(),"Invalid Bolt Index"); + G2ERROR(ghlInfoTo->mBltlist.size()>0,"Empty Bolt List"); + assert( toBoltIndex >= 0 ); + if ( toBoltIndex >= 0 && ghlInfoTo->mBltlist.size()) + { + // make sure we have a model to attach, a model to attach to, and a bolt on that model + if (((ghlInfoTo->mBltlist[toBoltIndex].boneNumber != -1) || (ghlInfoTo->mBltlist[toBoltIndex].surfaceNumber != -1))) + { + // encode the bolt address into the model bolt link + toModel &= MODEL_AND; + toBoltIndex &= BOLT_AND; + ghlInfo->mModelBoltLink = (toModel << MODEL_SHIFT) | (toBoltIndex << BOLT_SHIFT); + ret=qtrue; + } + } + } + G2WARNING(ret,"G2API_AttachG2Model Failed"); + return ret; +} + + +qboolean G2API_DetachG2Model(CGhoul2Info *ghlInfo) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + ghlInfo->mModelBoltLink = -1; + return qtrue; + } + return qfalse; +} + +qboolean G2API_AttachEnt(int *boltInfo, CGhoul2Info *ghlInfoTo, int toBoltIndex, int entNum, int toModelNum) +{ + qboolean ret=qfalse; + G2ERROR(boltInfo,"NULL boltInfo"); + if (boltInfo&&G2_SetupModelPointers(ghlInfoTo)) + { + // make sure we have a model to attach, a model to attach to, and a bolt on that model + if ( ghlInfoTo->mBltlist.size() && ((ghlInfoTo->mBltlist[toBoltIndex].boneNumber != -1) || (ghlInfoTo->mBltlist[toBoltIndex].surfaceNumber != -1))) + { + // encode the bolt address into the model bolt link + toModelNum &= MODEL_AND; + toBoltIndex &= BOLT_AND; + entNum &= ENTITY_AND; + *boltInfo = (toBoltIndex << BOLT_SHIFT) | (toModelNum << MODEL_SHIFT) | (entNum << ENTITY_SHIFT); + ret=qtrue; + } + } + G2WARNING(ret,"G2API_AttachEnt Failed"); + return ret; +} + +void G2API_DetachEnt(int *boltInfo) +{ + G2ERROR(boltInfo,"NULL boltInfo"); + if (boltInfo) + { + *boltInfo = 0; + } +} + + +bool G2_NeedsRecalc(CGhoul2Info *ghlInfo,int frameNum); + +qboolean G2API_GetBoltMatrix(CGhoul2Info_v &ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, const vec3_t angles, + const vec3_t position, const int AframeNum, qhandle_t *modelList, const vec3_t scale ) +{ + G2ERROR(ghoul2.IsValid(),"Invalid ghlInfo"); + G2ERROR(matrix,"NULL matrix"); + G2ERROR(modelIndex>=0&&modelIndex=0&&modelIndex= 0 && (boltIndex < ghlInfo->mBltlist.size()),va("Invalid Bolt Index (%d:%s)",boltIndex,ghlInfo->mFileName)); + + if (boltIndex >= 0 && ghlInfo && (boltIndex < ghlInfo->mBltlist.size()) ) + { + mdxaBone_t bolt; + + if (G2_NeedsRecalc(ghlInfo,frameNum)) + { + G2_ConstructGhoulSkeleton(ghoul2,frameNum,true,scale); + } + + G2_GetBoltMatrixLow(*ghlInfo,boltIndex,scale,bolt); + // scale the bolt position by the scale factor for this model since at this point its still in model space + if (scale[0]) + { + bolt.matrix[0][3] *= scale[0]; + } + if (scale[1]) + { + bolt.matrix[1][3] *= scale[1]; + } + if (scale[2]) + { + bolt.matrix[2][3] *= scale[2]; + } + VectorNormalize((float*)&bolt.matrix[0]); + VectorNormalize((float*)&bolt.matrix[1]); + VectorNormalize((float*)&bolt.matrix[2]); + + Multiply_3x4Matrix(matrix, &worldMatrix, &bolt); +#if G2API_DEBUG + for ( int i = 0; i < 3; i++ ) + { + for ( int j = 0; j < 4; j++ ) + { + assert( !_isnan(matrix->matrix[i][j])); + } + } +#endif// _DEBUG + G2ANIM(ghlInfo,"G2API_GetBoltMatrix"); + return qtrue; + } + } + } + else + { + G2WARNING(0,"G2API_GetBoltMatrix Failed on empty or bad model"); + } + Multiply_3x4Matrix(matrix, &worldMatrix, &identityMatrix); + return qfalse; +} + +void G2API_ListSurfaces(CGhoul2Info *ghlInfo) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + G2_List_Model_Surfaces(ghlInfo->mFileName); + } +} + +void G2API_ListBones(CGhoul2Info *ghlInfo, int frame) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + G2_List_Model_Bones(ghlInfo->mFileName, frame); + } +} + +// decide if we have Ghoul2 models associated with this ghoul list or not +qboolean G2API_HaveWeGhoul2Models(CGhoul2Info_v &ghoul2) +{ + return !!ghoul2.IsValid(); +} + +// run through the Ghoul2 models and set each of the mModel values to the correct one from the cgs.gameModel offset lsit +void G2API_SetGhoul2ModelIndexes(CGhoul2Info_v &ghoul2, qhandle_t *modelList, qhandle_t *skinList) +{ + G2ERROR(ghoul2.IsValid(),"Invalid ghlInfo"); + int i; + for (i=0; imdxm,"Bad Model"); + if (mod_m&&mod_m->mdxm) + { + return mod_m->mdxm->animName; + } + return ""; +} + +// as above, but gets the internal embedded name, not the name of the disk file. +// This is needed for some unfortunate jiggery-hackery to do with frameskipping & the animevents.cfg file +// +char *G2API_GetAnimFileInternalNameIndex(qhandle_t modelIndex) +{ + model_t *mod_a = R_GetModelByHandle(modelIndex); + G2ERROR(mod_a&&mod_a->mdxa,"Bad Model"); + if (mod_a&&mod_a->mdxa) + { + return mod_a->mdxa->name; + } + return ""; +} + +/************************************************************************************************ + * G2API_GetAnimFileName + * obtains the name of a model's .gla (animation) file + * + * Input + * pointer to list of CGhoul2Info's, WraithID of specific model in that list + * + * Output + * true if a good filename was obtained, false otherwise + * + ************************************************************************************************/ +qboolean G2API_GetAnimFileName(CGhoul2Info *ghlInfo, char **filename) +{ + qboolean ret=qfalse; + if (G2_SetupModelPointers(ghlInfo)) + { + ret=G2_GetAnimFileName(ghlInfo->mFileName, filename); + } + G2WARNING(ret,"G2API_GetAnimFileName Failed"); + return ret; +} + +/* +======================= +SV_QsortEntityNumbers +======================= +*/ +static int QDECL QsortDistance( const void *a, const void *b ) { + const float &ea = ((CCollisionRecord*)a)->mDistance; + const float &eb = ((CCollisionRecord*)b)->mDistance; + + if ( ea < eb ) { + return -1; + } + return 1; +} + + +void G2API_CollisionDetect(CCollisionRecord *collRecMap, CGhoul2Info_v &ghoul2, const vec3_t angles, const vec3_t position, + int AframeNumber, int entNum, vec3_t rayStart, vec3_t rayEnd, vec3_t scale, CMiniHeap *, + EG2_Collision eG2TraceType, int useLod, float fRadius) +{ + G2ERROR(ghoul2.IsValid(),"Invalid ghlInfo"); + G2ERROR(collRecMap,"NULL Collision Rec"); + if (G2_SetupModelPointers(ghoul2)&&collRecMap) + { + int frameNumber=G2API_GetTime(AframeNumber); + + vec3_t transRayStart, transRayEnd; + + // make sure we have transformed the whole skeletons for each model + G2_ConstructGhoulSkeleton(ghoul2, frameNumber,true, scale); + + // pre generate the world matrix - used to transform the incoming ray + G2_GenerateWorldMatrix(angles, position); + + G2VertSpaceServer->ResetHeap(); + + // now having done that, time to build the model +#ifdef _G2_GORE + G2_TransformModel(ghoul2, frameNumber, scale, G2VertSpaceServer, useLod, false); +#else + G2_TransformModel(ghoul2, frameNumber, scale,G2VertSpaceServer, useLod); +#endif + + // model is built. Lets check to see if any triangles are actually hit. + // first up, translate the ray to model space + TransformAndTranslatePoint(rayStart, transRayStart, &worldMatrixInv); + TransformAndTranslatePoint(rayEnd, transRayEnd, &worldMatrixInv); + + // now walk each model and check the ray against each poly - sigh, this is SO expensive. I wish there was a better way to do this. +#ifdef _G2_GORE + G2_TraceModels(ghoul2, transRayStart, transRayEnd, collRecMap, entNum, eG2TraceType, useLod, fRadius,0,0,0,0,0); +#else + G2_TraceModels(ghoul2, transRayStart, transRayEnd, collRecMap, entNum, eG2TraceType, useLod, fRadius); +#endif + + G2VertSpaceServer->ResetHeap(); + // now sort the resulting array of collision records so they are distance ordered + qsort( collRecMap, MAX_G2_COLLISIONS, + sizeof( CCollisionRecord ), QsortDistance ); + G2ANIM(ghoul2,"G2API_CollisionDetect"); + } +} + +qboolean G2API_SetGhoul2ModelFlags(CGhoul2Info *ghlInfo, const int flags) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + ghlInfo->mFlags &= GHOUL2_NEWORIGIN; + ghlInfo->mFlags |= flags; + return qtrue; + } + return qfalse; +} + +int G2API_GetGhoul2ModelFlags(CGhoul2Info *ghlInfo) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return (ghlInfo->mFlags & ~GHOUL2_NEWORIGIN); + } + return 0; +} + +// given a boltmatrix, return in vec a normalised vector for the axis requested in flags +void G2API_GiveMeVectorFromMatrix(mdxaBone_t &boltMatrix, Eorientations flags, vec3_t &vec) +{ + switch (flags) + { + case ORIGIN: + vec[0] = boltMatrix.matrix[0][3]; + vec[1] = boltMatrix.matrix[1][3]; + vec[2] = boltMatrix.matrix[2][3]; + break; + case POSITIVE_Y: + vec[0] = boltMatrix.matrix[0][1]; + vec[1] = boltMatrix.matrix[1][1]; + vec[2] = boltMatrix.matrix[2][1]; + break; + case POSITIVE_X: + vec[0] = boltMatrix.matrix[0][0]; + vec[1] = boltMatrix.matrix[1][0]; + vec[2] = boltMatrix.matrix[2][0]; + break; + case POSITIVE_Z: + vec[0] = boltMatrix.matrix[0][2]; + vec[1] = boltMatrix.matrix[1][2]; + vec[2] = boltMatrix.matrix[2][2]; + break; + case NEGATIVE_Y: + vec[0] = -boltMatrix.matrix[0][1]; + vec[1] = -boltMatrix.matrix[1][1]; + vec[2] = -boltMatrix.matrix[2][1]; + break; + case NEGATIVE_X: + vec[0] = -boltMatrix.matrix[0][0]; + vec[1] = -boltMatrix.matrix[1][0]; + vec[2] = -boltMatrix.matrix[2][0]; + break; + case NEGATIVE_Z: + vec[0] = -boltMatrix.matrix[0][2]; + vec[1] = -boltMatrix.matrix[1][2]; + vec[2] = -boltMatrix.matrix[2][2]; + break; + } +} + +// copy a model from one ghoul2 instance to another, and reset the root surface on the new model if need be +// NOTE if modelIndex = -1 then copy all the models +void G2API_CopyGhoul2Instance(CGhoul2Info_v &ghoul2From, CGhoul2Info_v &ghoul2To, int modelIndex) +{ + assert(modelIndex==-1); // copy individual bolted parts is not used in jk2 and I didn't want to deal with it + // if ya want it, we will add it back correctly + + G2ERROR(ghoul2From.IsValid(),"Invalid ghlInfo"); + if (ghoul2From.IsValid()) + { + ghoul2To.DeepCopy(ghoul2From); +#ifdef _G2_GORE //check through gore stuff then, as well. + int model = 0; + + //(since we are sharing this gore set with the copied instance we will have to increment + //the reference count - if the goreset is "removed" while the refcount is > 0, the refcount + //is decremented to avoid giving other instances an invalid pointer -rww) + while (model < ghoul2To.size()) + { + if ( ghoul2To[model].mGoreSetTag ) + { + CGoreSet* gore = FindGoreSet ( ghoul2To[model].mGoreSetTag ); + assert(gore); + if (gore) + { + gore->mRefCount++; + } + } + + model++; + } +#endif + G2ANIM(ghoul2From,"G2API_CopyGhoul2Instance (source)"); + G2ANIM(ghoul2To,"G2API_CopyGhoul2Instance (dest)"); + } +} + +char *G2API_GetSurfaceName(CGhoul2Info *ghlInfo, int surfNumber) +{ + static char noSurface[1] = ""; + if (G2_SetupModelPointers(ghlInfo)) + { + mdxmSurface_t *surf = 0; + mdxmSurfHierarchy_t *surfInfo = 0; + + + surf = (mdxmSurface_t *)G2_FindSurface(ghlInfo->currentModel, surfNumber, 0); + if (surf) + { + assert(G2_MODEL_OK(ghlInfo)); + mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)ghlInfo->currentModel->mdxm + sizeof(mdxmHeader_t)); + surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surf->thisSurfaceIndex]); + return surfInfo->name; + } + } + G2WARNING(0,"Surface Not Found"); + return noSurface; +} + + +int G2API_GetSurfaceIndex(CGhoul2Info *ghlInfo, const char *surfaceName) +{ + int ret=-1; + G2ERROR(surfaceName,"NULL surfaceName"); + if (surfaceName&&G2_SetupModelPointers(ghlInfo)) + { + ret=G2_GetSurfaceIndex(ghlInfo, surfaceName); + } + G2WARNING(ret>=0,"G2API_GetSurfaceIndex Failed"); + return ret; +} + +char *G2API_GetGLAName(CGhoul2Info *ghlInfo) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + assert(G2_MODEL_OK(ghlInfo)); + return (char*)ghlInfo->aHeader->name; + //return ghlInfo->currentModel->mdxm->animName; + } + return 0; +} + +qboolean G2API_SetNewOrigin(CGhoul2Info *ghlInfo, const int boltIndex) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + G2ERROR(boltIndex>=0&&boltIndexmBltlist.size(),"invalid boltIndex"); + + if (boltIndex>=0&&boltIndexmBltlist.size()) + { + ghlInfo->mNewOrigin = boltIndex; + ghlInfo->mFlags |= GHOUL2_NEWORIGIN; + } + return qtrue; + } + return qfalse; +} + +int G2API_GetBoneIndex(CGhoul2Info *ghlInfo, const char *boneName, qboolean bAddIfNotFound) +{ + int ret=-1; + G2ERROR(boneName,"NULL boneName"); + if (boneName&&G2_SetupModelPointers(ghlInfo)) + { + ret=G2_Get_Bone_Index(ghlInfo, boneName, bAddIfNotFound); + G2ANIM(ghlInfo,"G2API_GetBoneIndex"); + } + G2NOTE(ret>=0,"G2API_GetBoneIndex Failed"); + return ret; +} + +void G2API_SaveGhoul2Models(CGhoul2Info_v &ghoul2) +{ + G2ANIM(ghoul2,"G2API_SaveGhoul2Models"); + G2_SaveGhoul2Models(ghoul2); +} + +void G2API_LoadGhoul2Models(CGhoul2Info_v &ghoul2, char *buffer) +{ + G2_LoadGhoul2Model(ghoul2, buffer); + G2ANIM(ghoul2,"G2API_LoadGhoul2Models"); +// G2ERROR(ghoul2.IsValid(),"Invalid ghlInfo after load"); +} + +// this is kinda sad, but I need to call the destructor in this module (exe), not the game.dll... +// +void G2API_LoadSaveCodeDestructGhoul2Info(CGhoul2Info_v &ghoul2) +{ + ghoul2.~CGhoul2Info_v(); // so I can load junk over it then memset to 0 without orphaning +} + +#ifdef _G2_GORE +void ResetGoreTag(); // put here to reduce coupling + +void G2API_ClearSkinGore ( CGhoul2Info_v &ghoul2 ) +{ + int i; + + for (i=0; iinteger)); + const int maxLod =Com_Clamp (0,ghoul2[0].currentModel->numLods,3); //limit to the number of lods the main model has + for(lod=lodbias;lodResetHeap(); + + G2_TransformModel(ghoul2, gore.currentTime, gore.scale,G2VertSpaceServer,lod,true); + + // now walk each model and compute new texture coordinates + G2_TraceModels(ghoul2, transHitLocation, transRayDirection, 0, gore.entNum, G2_NOCOLLIDE,lod,1.0f,gore.SSize,gore.TSize,gore.theta,gore.shader,&gore); + } + } +#else +void G2API_ClearSkinGore ( CGhoul2Info_v &ghoul2 ) +{ +} + +void G2API_AddSkinGore(CGhoul2Info_v &ghoul2,SSkinGoreData &gore) +{ +} +#endif + +bool G2_TestModelPointers(CGhoul2Info *ghlInfo) // returns true if the model is properly set up +{ + G2ERROR(ghlInfo,"NULL ghlInfo"); + if (!ghlInfo) + { + return false; + } + ghlInfo->mValid=false; + if (ghlInfo->mModelindex != -1) + { + ghlInfo->mModel = RE_RegisterModel(ghlInfo->mFileName); + ghlInfo->currentModel = R_GetModelByHandle(ghlInfo->mModel); + if (ghlInfo->currentModel) + { + if (ghlInfo->currentModel->mdxm) + { + if (ghlInfo->currentModelSize) + { + if (ghlInfo->currentModelSize!=ghlInfo->currentModel->mdxm->ofsEnd) + { + Com_Error(ERR_DROP, "Ghoul2 model was reloaded and has changed, map must be restarted.\n"); + } + } + ghlInfo->currentModelSize=ghlInfo->currentModel->mdxm->ofsEnd; + ghlInfo->animModel = R_GetModelByHandle(ghlInfo->currentModel->mdxm->animIndex + ghlInfo->animModelIndexOffset); + if (ghlInfo->animModel) + { + ghlInfo->aHeader =ghlInfo->animModel->mdxa; + G2ERROR(ghlInfo->aHeader,va("Model has no mdxa (gla) %s",ghlInfo->mFileName)); + if (!ghlInfo->aHeader) + { + Com_Error(ERR_DROP, "Ghoul2 Model has no mdxa (gla) %s",ghlInfo->mFileName); + } + if (ghlInfo->currentAnimModelSize) + { + if (ghlInfo->currentAnimModelSize!=ghlInfo->aHeader->ofsEnd) + { + Com_Error(ERR_DROP, "Ghoul2 model was reloaded and has changed, map must be restarted.\n"); + } + } + ghlInfo->currentAnimModelSize=ghlInfo->aHeader->ofsEnd; + ghlInfo->mValid=true; + } + } + } + } + if (!ghlInfo->mValid) + { + ghlInfo->currentModel=0; + ghlInfo->currentModelSize=0; + ghlInfo->animModel=0; + ghlInfo->currentAnimModelSize=0; + ghlInfo->aHeader=0; + } + return ghlInfo->mValid; +} + +bool G2_SetupModelPointers(CGhoul2Info *ghlInfo) // returns true if the model is properly set up +{ + G2ERROR(ghlInfo,"NULL ghlInfo"); + if (!ghlInfo) + { + return false; + } + ghlInfo->mValid=false; +// G2WARNING(ghlInfo->mModelindex != -1,"Setup request on non-used info slot?"); + if (ghlInfo->mModelindex != -1) + { + G2ERROR(ghlInfo->mFileName[0],"empty ghlInfo->mFileName"); + ghlInfo->mModel = RE_RegisterModel(ghlInfo->mFileName); + ghlInfo->currentModel = R_GetModelByHandle(ghlInfo->mModel); + G2ERROR(ghlInfo->currentModel,va("NULL Model (glm) %s",ghlInfo->mFileName)); + if (ghlInfo->currentModel) + { + G2ERROR(ghlInfo->currentModel->mdxm,va("Model has no mdxm (glm) %s",ghlInfo->mFileName)); + if (ghlInfo->currentModel->mdxm) + { + if (ghlInfo->currentModelSize) + { + if (ghlInfo->currentModelSize!=ghlInfo->currentModel->mdxm->ofsEnd) + { + Com_Error(ERR_DROP, "Ghoul2 model was reloaded and has changed, map must be restarted.\n"); + } + } + ghlInfo->currentModelSize=ghlInfo->currentModel->mdxm->ofsEnd; + G2ERROR(ghlInfo->currentModelSize,va("Zero sized Model? (glm) %s",ghlInfo->mFileName)); + + ghlInfo->animModel = R_GetModelByHandle(ghlInfo->currentModel->mdxm->animIndex + ghlInfo->animModelIndexOffset); + G2ERROR(ghlInfo->animModel,va("NULL Model (gla) %s",ghlInfo->mFileName)); + if (ghlInfo->animModel) + { + ghlInfo->aHeader =ghlInfo->animModel->mdxa; + G2ERROR(ghlInfo->aHeader,va("Model has no mdxa (gla) %s",ghlInfo->mFileName)); + if (!ghlInfo->aHeader) + { + Com_Error(ERR_DROP, "Ghoul2 Model has no mdxa (gla) %s",ghlInfo->mFileName); + } + if (ghlInfo->currentAnimModelSize) + { + if (ghlInfo->currentAnimModelSize!=ghlInfo->aHeader->ofsEnd) + { + Com_Error(ERR_DROP, "Ghoul2 model was reloaded and has changed, map must be restarted.\n"); + } + } + ghlInfo->currentAnimModelSize=ghlInfo->aHeader->ofsEnd; + G2ERROR(ghlInfo->currentAnimModelSize,va("Zero sized Model? (gla) %s",ghlInfo->mFileName)); + ghlInfo->mValid=true; + } + } + } + } + if (!ghlInfo->mValid) + { + ghlInfo->currentModel=0; + ghlInfo->currentModelSize=0; + ghlInfo->animModel=0; + ghlInfo->currentAnimModelSize=0; + ghlInfo->aHeader=0; + } + return ghlInfo->mValid; +} + +bool G2_SetupModelPointers(CGhoul2Info_v &ghoul2) // returns true if any model is properly set up +{ + bool ret=false; + int i; + for (i=0; imValid&&(g)->aHeader&&(g)->currentModel&&(g)->animModel) + +//===================================================================================================================== +// Bolt List handling routines - so entities can attach themselves to any part of the model in question + +// Given a bone number, see if that bone is already in our bone list +int G2_Find_Bolt_Bone_Num(boltInfo_v &bltlist, const int boneNum) +{ + int i; + + // look through entire list + for(i=0; imValid); + boltInfo_t tempBolt; + int i; + + assert(surfNum>=0&&surfNum= slist.size()) + { + return -1; + } + + // look through entire list - see if it's already there first + for(i=0; imValid); + int i, x, surfNum = -1; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + mdxmHierarchyOffsets_t *surfOffsets; + boltInfo_t tempBolt; + int flags; + + assert(G2_MODEL_OK(ghlInfo)); + + surfOffsets = (mdxmHierarchyOffsets_t *)((byte*)ghlInfo->currentModel->mdxm + sizeof(mdxmHeader_t)); + // first up, we'll search for that which this bolt names in all the surfaces + surfNum = G2_IsSurfaceLegal(ghlInfo->currentModel, boneName, &flags); + + // did we find it as a surface? + if (surfNum != -1) + { + // look through entire list - see if it's already there first + for(i=0; iaHeader + sizeof(mdxaHeader_t)); + + // walk the entire list of bones in the gla file for this model and see if any match the name of the bone we want to find + for (x=0; x< ghlInfo->aHeader->numBones; x++) + { + skel = (mdxaSkel_t *)((byte *)ghlInfo->aHeader + sizeof(mdxaHeader_t) + offsets->offsets[x]); + // if name is the same, we found it + if (!stricmp(skel->name, boneName)) + { + break; + } + } + + // check to see we did actually make a match with a bone in the model + if (x == ghlInfo->aHeader->numBones) + { + // didn't find it? Error + //assert(0&&x == mod_a->mdxa->numBones); +#if _DEBUG + G2_Bolt_Not_Found(boneName,ghlInfo->mFileName); +#endif + return -1; + } + + // look through entire list - see if it's already there first + for(i=0; i=0&&index +#include "ghoul2_gore.h" +//rww - RAGDOLL_END + +extern cvar_t *r_Ghoul2BlendMultiplier; + +void G2_Bone_Not_Found(const char *boneName,const char *modName); + +//===================================================================================================================== +// Bone List handling routines - so entities can override bone info on a bone by bone level, and also interrogate this info + +// Given a bone name, see if that bone is already in our bone list - note the model_t pointer that gets passed in here MUST point at the +// gla file, not the glm file type. +int G2_Find_Bone(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName) +{ + int i; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)ghlInfo->aHeader + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)ghlInfo->aHeader + sizeof(mdxaHeader_t) + offsets->offsets[0]); + + // look through entire list + for(i=0; iaHeader + sizeof(mdxaHeader_t) + offsets->offsets[blist[i].boneNumber]); + + // if name is the same, we found it + if (!stricmp(skel->name, boneName)) + { + return i; + } + } +#if _DEBUG + G2_Bone_Not_Found(boneName,ghlInfo->mFileName); +#endif + // didn't find it + return -1; +} + +#define DEBUG_G2_BONES (0) + +// we need to add a bone to the list - find a free one and see if we can find a corresponding bone in the gla file +int G2_Add_Bone (const model_t *mod, boneInfo_v &blist, const char *boneName) +{ + int i, x; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + boneInfo_t tempBone; + + //rww - RAGDOLL_BEGIN + memset(&tempBone, 0, sizeof(tempBone)); + //rww - RAGDOLL_END + + offsets = (mdxaSkelOffsets_t *)((byte *)mod->mdxa + sizeof(mdxaHeader_t)); + + // walk the entire list of bones in the gla file for this model and see if any match the name of the bone we want to find + for (x=0; x< mod->mdxa->numBones; x++) + { + skel = (mdxaSkel_t *)((byte *)mod->mdxa + sizeof(mdxaHeader_t) + offsets->offsets[x]); + // if name is the same, we found it + if (!stricmp(skel->name, boneName)) + { + break; + } + } + + // check to see we did actually make a match with a bone in the model + if (x == mod->mdxa->numBones) + { +#if _DEBUG + G2_Bone_Not_Found(boneName,mod->name); +#endif + return -1; + } + + // look through entire list - see if it's already there first + for(i=0; imdxa + sizeof(mdxaHeader_t) + offsets->offsets[blist[i].boneNumber]); + // if name is the same, we found it + if (!stricmp(skel->name, boneName)) + { +#if DEBUG_G2_BONES + { + char mess[1000]; + sprintf(mess,"ADD BONE1 blistIndex=%3d physicalIndex=%3d %s\n", + i, + x, + boneName); + OutputDebugString(mess); + } +#endif + return i; + } + } + else + { + // if we found an entry that had a -1 for the bonenumber, then we hit a bone slot that was empty + blist[i].boneNumber = x; + blist[i].flags = 0; +#if DEBUG_G2_BONES + { + char mess[1000]; + sprintf(mess,"ADD BONE1 blistIndex=%3d physicalIndex=%3d %s\n", + i, + x, + boneName); + OutputDebugString(mess); + } +#endif + return i; + } + } + + // ok, we didn't find an existing bone of that name, or an empty slot. Lets add an entry + tempBone.boneNumber = x; + tempBone.flags = 0; + blist.push_back(tempBone); +#if DEBUG_G2_BONES + { + char mess[1000]; + sprintf(mess,"ADD BONE1 blistIndex=%3d physicalIndex=%3d %s\n", + blist.size()-1, + x, + boneName); + OutputDebugString(mess); + } +#endif + return blist.size()-1; +} + + +// Given a model handle, and a bone name, we want to remove this bone from the bone override list +qboolean G2_Remove_Bone_Index ( boneInfo_v &blist, int index) +{ + // did we find it? + if (index != -1) + { + // check the flags first - if it's still being used Do NOT remove it + if (!blist[index].flags) + { + // set this bone to not used + blist[index].boneNumber = -1; + } + return qtrue; + } + return qfalse; +} + +// given a bone number, see if there is an override bone in the bone list +int G2_Find_Bone_In_List(boneInfo_v &blist, const int boneNum) +{ + int i; + + // look through entire list + for(i=0; imdxa + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)mod->mdxa + sizeof(mdxaHeader_t) + offsets->offsets[blist[index].boneNumber]); + + Multiply_3x4Matrix(&temp1, boneOverride,&skel->BasePoseMatInv); + Multiply_3x4Matrix(boneOverride,&skel->BasePoseMat, &temp1); + + } + else + { + VectorCopy(angles, newAngles); + + // why I should need do this Fuck alone knows. But I do. + if (left == POSITIVE_Y) + { + newAngles[0] +=180; + } + + Create_Matrix(newAngles, &temp1); + + permutation.matrix[0][0] = permutation.matrix[0][1] = permutation.matrix[0][2] = permutation.matrix[0][3] = 0; + permutation.matrix[1][0] = permutation.matrix[1][1] = permutation.matrix[1][2] = permutation.matrix[1][3] = 0; + permutation.matrix[2][0] = permutation.matrix[2][1] = permutation.matrix[2][2] = permutation.matrix[2][3] = 0; + + // determine what axis newAngles Yaw should revolve around + switch (forward) + { + case NEGATIVE_X: + permutation.matrix[0][0] = -1; // works + break; + case POSITIVE_X: + permutation.matrix[0][0] = 1; // works + break; + case NEGATIVE_Y: + permutation.matrix[1][0] = -1; + break; + case POSITIVE_Y: + permutation.matrix[1][0] = 1; + break; + case NEGATIVE_Z: + permutation.matrix[2][0] = -1; + break; + case POSITIVE_Z: + permutation.matrix[2][0] = 1; + break; + } + + // determine what axis newAngles pitch should revolve around + switch (left) + { + case NEGATIVE_X: + permutation.matrix[0][1] = -1; + break; + case POSITIVE_X: + permutation.matrix[0][1] = 1; + break; + case NEGATIVE_Y: + permutation.matrix[1][1] = -1; // works + break; + case POSITIVE_Y: + permutation.matrix[1][1] = 1; // works + break; + case NEGATIVE_Z: + permutation.matrix[2][1] = -1; + break; + case POSITIVE_Z: + permutation.matrix[2][1] = 1; + break; + } + + // determine what axis newAngles Roll should revolve around + switch (up) + { + case NEGATIVE_X: + permutation.matrix[0][2] = -1; + break; + case POSITIVE_X: + permutation.matrix[0][2] = 1; + break; + case NEGATIVE_Y: + permutation.matrix[1][2] = -1; + break; + case POSITIVE_Y: + permutation.matrix[1][2] = 1; + break; + case NEGATIVE_Z: + permutation.matrix[2][2] = -1; // works + break; + case POSITIVE_Z: + permutation.matrix[2][2] = 1; // works + break; + } + + Multiply_3x4Matrix(boneOverride, &temp1,&permutation); + + } + + // keep a copy of the matrix in the newmatrix which is actually what we use + memcpy(&blist[index].newMatrix, &blist[index].matrix, sizeof(mdxaBone_t)); + +} + +//========================================================================================= +//// Public Bone Routines + + +// Given a model handle, and a bone name, we want to remove this bone from the bone override list +qboolean G2_Remove_Bone (CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName) +{ + int index = G2_Find_Bone(ghlInfo, blist, boneName); + if (index==-1) + { + return false; + } + + return G2_Remove_Bone_Index(blist, index); +} + +#define DEBUG_PCJ (0) + +// Given a model handle, and a bone name, we want to set angles specifically for overriding +qboolean G2_Set_Bone_Angles_Index(CGhoul2Info *ghlInfo, boneInfo_v &blist, const int index, + const float *angles, const int flags, const Eorientations yaw, + const Eorientations pitch, const Eorientations roll, + const int blendTime, const int currentTime) +{ + + if (index<0||(index >= blist.size()) || (blist[index].boneNumber == -1)) + { + return qfalse; + } + + // yes, so set the angles and flags correctly + blist[index].flags &= ~(BONE_ANGLES_TOTAL); + blist[index].flags |= flags; + blist[index].boneBlendStart = currentTime; + blist[index].boneBlendTime = blendTime; +#if DEBUG_PCJ + OutputDebugString(va("%8x %2d %6d (%6.2f,%6.2f,%6.2f) %d %d %d %d\n",(int)ghlInfo,index,currentTime,angles[0],angles[1],angles[2],yaw,pitch,roll,flags)); +#endif + G2_Generate_Matrix(ghlInfo->animModel, blist, index, angles, flags, yaw, pitch, roll); + return qtrue; + +} + +// Given a model handle, and a bone name, we want to set angles specifically for overriding +qboolean G2_Set_Bone_Angles(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName, const float *angles, + const int flags, const Eorientations up, const Eorientations left, const Eorientations forward, + const int blendTime, const int currentTime) +{ + int index = G2_Find_Bone(ghlInfo, blist, boneName); + if (index == -1) + { + index = G2_Add_Bone(ghlInfo->animModel, blist, boneName); + } + if (index != -1) + { + blist[index].flags &= ~(BONE_ANGLES_TOTAL); + blist[index].flags |= flags; + blist[index].boneBlendStart = currentTime; + blist[index].boneBlendTime = blendTime; + + G2_Generate_Matrix(ghlInfo->animModel, blist, index, angles, flags, up, left, forward); + return qtrue; + } + return qfalse; +} + +// Given a model handle, and a bone name, we want to set angles specifically for overriding - using a matrix directly +qboolean G2_Set_Bone_Angles_Matrix_Index(boneInfo_v &blist, const int index, + const mdxaBone_t &matrix, const int flags, + const int blendTime, const int currentTime) +{ + + if (index<0||(index >= blist.size()) || (blist[index].boneNumber == -1)) + { + // we are attempting to set a bone override that doesn't exist + assert(0); + return qfalse; + } + // yes, so set the angles and flags correctly + blist[index].flags &= ~(BONE_ANGLES_TOTAL); + blist[index].flags |= flags; + blist[index].boneBlendStart = currentTime; + blist[index].boneBlendTime = blendTime; + + memcpy(&blist[index].matrix, &matrix, sizeof(mdxaBone_t)); + memcpy(&blist[index].newMatrix, &matrix, sizeof(mdxaBone_t)); + return qtrue; + +} + +// Given a model handle, and a bone name, we want to set angles specifically for overriding - using a matrix directly +qboolean G2_Set_Bone_Angles_Matrix(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName, const mdxaBone_t &matrix, + const int flags,const int blendTime, const int currentTime) +{ + int index = G2_Find_Bone(ghlInfo, blist, boneName); + if (index == -1) + { + index = G2_Add_Bone(ghlInfo->animModel, blist, boneName); + } + if (index != -1) + { + blist[index].flags &= ~(BONE_ANGLES_TOTAL); + blist[index].flags |= flags; + memcpy(&blist[index].matrix, &matrix, sizeof(mdxaBone_t)); + memcpy(&blist[index].newMatrix, &matrix, sizeof(mdxaBone_t)); + return qtrue; + } + return qfalse; +} + +#define DEBUG_G2_TIMING (0) + +// given a model, bone name, a bonelist, a start/end frame number, a anim speed and some anim flags, set up or modify an existing bone entry for a new set of anims +qboolean G2_Set_Bone_Anim_Index(boneInfo_v &blist, const int index, const int startFrame, + const int endFrame, const int flags, const float animSpeed, const int currentTime, const float setFrame, const int AblendTime,int numFrames) +{ + int modFlags = flags; + int blendTime=AblendTime; + + if (r_Ghoul2BlendMultiplier) + { + if (r_Ghoul2BlendMultiplier->value!=1.0f) + { + if (r_Ghoul2BlendMultiplier->value<=0.0f) + { + modFlags&=~BONE_ANIM_BLEND; + } + else + { + blendTime=ceil(float(AblendTime)*r_Ghoul2BlendMultiplier->value); + } + } + } + + + if (index<0||index >= blist.size()||blist[index].boneNumber<0) + { + return qfalse; + } + + // sanity check to see if setfram is within animation bounds + assert((setFrame==-1) || ((setFrame>=startFrame) && (setFrameendFrame) && (setFrame<=(startFrame+1)))); + + + // since we already existed, we can check to see if we want to start some blending + if (modFlags & BONE_ANIM_BLEND) + { + float currentFrame, animSpeed; + int startFrame, endFrame, flags; + // figure out where we are now + if (G2_Get_Bone_Anim_Index(blist, index, currentTime, ¤tFrame, &startFrame, &endFrame, &flags, &animSpeed,numFrames)) + { + if (blist[index].blendStart == currentTime) //we're replacing a blend in progress which hasn't started + { + // set the amount of time it's going to take to blend this anim with the last frame of the last one + blist[index].blendTime = blendTime; + } + else + { + if (animSpeed<0.0f) + { + blist[index].blendFrame = floor(currentFrame); + blist[index].blendLerpFrame = floor(currentFrame); + } + else + { + blist[index].blendFrame = currentFrame; + blist[index].blendLerpFrame = currentFrame+1; + + // cope with if the lerp frame is actually off the end of the anim + if (blist[index].blendFrame >= blist[index].endFrame ) + { + // we only want to lerp with the first frame of the anim if we are looping + if (blist[index].flags & BONE_ANIM_OVERRIDE_LOOP) + { + blist[index].blendFrame = blist[index].startFrame; + } + // if we intend to end this anim or freeze after this, then just keep on the last frame + else + { + assert(blist[index].endFrame>0); + blist[index].blendFrame = blist[index].endFrame -1; + } + } + + // cope with if the lerp frame is actually off the end of the anim + if (blist[index].blendLerpFrame >= blist[index].endFrame ) + { + // we only want to lerp with the first frame of the anim if we are looping + if (blist[index].flags & BONE_ANIM_OVERRIDE_LOOP) + { + blist[index].blendLerpFrame = blist[index].startFrame; + } + // if we intend to end this anim or freeze after this, then just keep on the last frame + else + { + assert(blist[index].endFrame>0); + blist[index].blendLerpFrame = blist[index].endFrame - 1; + } + } + } + // set the amount of time it's going to take to blend this anim with the last frame of the last one + blist[index].blendTime = blendTime; + blist[index].blendStart = currentTime; + } + } + // hmm, we weren't animating on this bone. In which case disable the blend + else + { + blist[index].blendFrame = blist[index].blendLerpFrame = 0; + blist[index].blendTime = 0; + // we aren't blending, so remove the option to do so + modFlags &= ~BONE_ANIM_BLEND; + //return qfalse; + } + } + else + { + blist[index].blendFrame = blist[index].blendLerpFrame = 0; + blist[index].blendTime = blist[index].blendStart = 0; + // we aren't blending, so remove the option to do so + modFlags &= ~BONE_ANIM_BLEND; + } + + // yes, so set the anim data and flags correctly + blist[index].endFrame = endFrame; + blist[index].startFrame = startFrame; + blist[index].animSpeed = animSpeed; + blist[index].pauseTime = 0; + assert(blist[index].blendFrame>=0&&blist[index].blendFrame=0&&blist[index].blendLerpFrame-10) + { + const boneInfo_t &bone=blist[index]; + char mess[1000]; + if (bone.flags&BONE_ANIM_BLEND) + { + sprintf(mess,"sab[%2d] %5d %5d (%5d-%5d) %4.2f %4x bt(%5d-%5d) %7.2f %5d\n", + index, + currentTime, + bone.startTime, + bone.startFrame, + bone.endFrame, + bone.animSpeed, + bone.flags, + bone.blendStart, + bone.blendStart+bone.blendTime, + bone.blendFrame, + bone.blendLerpFrame + ); + } + else + { + sprintf(mess,"saa[%2d] %5d %5d (%5d-%5d) %4.2f %4x\n", + index, + currentTime, + bone.startTime, + bone.startFrame, + bone.endFrame, + bone.animSpeed, + bone.flags + ); + } + OutputDebugString(mess); + } +#endif +// assert(blist[index].startTime <= currentTime); + return qtrue; + +} + +// given a model, bone name, a bonelist, a start/end frame number, a anim speed and some anim flags, set up or modify an existing bone entry for a new set of anims +qboolean G2_Set_Bone_Anim(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName, const int startFrame, + const int endFrame, const int flags, const float animSpeed, const int currentTime, const float setFrame, const int blendTime) +{ + int modFlags = flags; + int index = G2_Find_Bone(ghlInfo, blist, boneName); + + // sanity check to see if setfram is within animation bounds + if (setFrame != -1) + { + assert((setFrame >= startFrame) && (setFrame <= endFrame)); + } + + // did we find it? + if (index != -1) + { + return G2_Set_Bone_Anim_Index(blist, index, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime,ghlInfo->aHeader->numFrames); + } + // no - lets try and add this bone in + index = G2_Add_Bone(ghlInfo->animModel, blist, boneName); + + // did we find a free one? + if (index != -1) + { + blist[index].blendFrame = blist[index].blendLerpFrame = 0; + blist[index].blendTime = 0; + // we aren't blending, so remove the option to do so + modFlags &= ~BONE_ANIM_BLEND; + // yes, so set the anim data and flags correctly + blist[index].endFrame = endFrame; + blist[index].startFrame = startFrame; + blist[index].animSpeed = animSpeed; + blist[index].pauseTime = 0; + // start up the animation:) + if (setFrame != -1) + { + blist[index].startTime = (currentTime - (((setFrame - (float)startFrame) * 50.0)/ animSpeed)); + } + else + { + blist[index].startTime = currentTime; + } + blist[index].flags &= ~BONE_ANIM_TOTAL; + blist[index].flags |= modFlags; + assert(blist[index].blendFrame>=0&&blist[index].blendFrameaHeader->numFrames); + assert(blist[index].blendLerpFrame>=0&&blist[index].blendLerpFrameaHeader->numFrames); +#if DEBUG_G2_TIMING + if (index>-10) + { + const boneInfo_t &bone=blist[index]; + char mess[1000]; + if (bone.flags&BONE_ANIM_BLEND) + { + sprintf(mess,"s2b[%2d] %5d %5d (%5d-%5d) %4.2f %4x bt(%5d-%5d) %7.2f %5d\n", + index, + currentTime, + bone.startTime, + bone.startFrame, + bone.endFrame, + bone.animSpeed, + bone.flags, + bone.blendStart, + bone.blendStart+bone.blendTime, + bone.blendFrame, + bone.blendLerpFrame + ); + } + else + { + sprintf(mess,"s2a[%2d] %5d %5d (%5d-%5d) %4.2f %4x\n", + index, + currentTime, + bone.startTime, + bone.startFrame, + bone.endFrame, + bone.animSpeed, + bone.flags + ); + } + OutputDebugString(mess); + } +#endif + return qtrue; + } + + //assert(index != -1); + // no + return qfalse; +} + +qboolean G2_Get_Bone_Anim_Range_Index(boneInfo_v &blist, const int boneIndex, int *startFrame, int *endFrame) +{ + if (boneIndex != -1) + { + assert(boneIndex>=0&&boneIndex=0&&*startFrameaHeader->numFrames); + assert(*endFrame>0&&*endFrame<=ghlInfo->aHeader->numFrames); + return qtrue; + } + return qfalse; +} + +// given a model, bonelist and bonename, return the current frame, startframe and endframe of the current animation +// NOTE if we aren't running an animation, then qfalse is returned +qboolean G2_Get_Bone_Anim_Index( boneInfo_v &blist, const int index, const int currentTime, + float *retcurrentFrame, int *startFrame, int *endFrame, int *flags, float *retAnimSpeed,int numFrames) +{ + + // did we find it? + if ((index>=0) && !((index >= blist.size()) || (blist[index].boneNumber == -1))) + { + + // are we an animating bone? + if (blist[index].flags & (BONE_ANIM_OVERRIDE_LOOP | BONE_ANIM_OVERRIDE)) + { + int currentFrame = 0, newFrame = 0; + float lerp = 0; + G2_TimingModel(blist[index],currentTime,numFrames,currentFrame,newFrame,lerp); + + if (retcurrentFrame) + { + *retcurrentFrame =float(currentFrame)+lerp; + } + if (startFrame) + { + *startFrame = blist[index].startFrame; + } + if (endFrame) + { + *endFrame = blist[index].endFrame; + } + if (flags) + { + *flags = blist[index].flags; + } + if (retAnimSpeed) + { + *retAnimSpeed = blist[index].animSpeed; + } + return qtrue; + } + } + if (startFrame) + { + *startFrame=0; + } + if (endFrame) + { + *endFrame=1; + } + if (retcurrentFrame) + { + *retcurrentFrame=0.0f; + } + if (flags) + { + *flags=0; + } + if (retAnimSpeed) + { + *retAnimSpeed=0.0f; + } + return qfalse; +} + +// given a model, bonelist and bonename, return the current frame, startframe and endframe of the current animation +// NOTE if we aren't running an animation, then qfalse is returned +qboolean G2_Get_Bone_Anim(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName, const int currentTime, + float *currentFrame, int *startFrame, int *endFrame, int *flags, float *retAnimSpeed) +{ + int index = G2_Find_Bone(ghlInfo, blist, boneName); + if (index==-1) + { + return qfalse; + } + + assert(ghlInfo->aHeader); + if (G2_Get_Bone_Anim_Index(blist, index, currentTime, currentFrame, startFrame, endFrame, flags, retAnimSpeed,ghlInfo->aHeader->numFrames)) + { + assert(*startFrame>=0&&*startFrameaHeader->numFrames); + assert(*endFrame>0&&*endFrame<=ghlInfo->aHeader->numFrames); + assert(*currentFrame>=0.0f&&((int)(*currentFrame))aHeader->numFrames); + return qtrue; + } + return qfalse; +} + + +// given a model, bonelist and bonename, lets pause an anim if it's playing. +qboolean G2_Pause_Bone_Anim_Index( boneInfo_v &blist, const int boneIndex, const int currentTime,int numFrames) +{ + if (boneIndex>=0&&boneIndexaHeader->numFrames) ); +} + +qboolean G2_IsPaused(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName) +{ + int index = G2_Find_Bone(ghlInfo, blist, boneName); + if (index != -1) + { + // are we paused? + if (blist[index].pauseTime) + { + // yup. paused. + return qtrue; + } + return qfalse; + } + + return qfalse; +} + +// given a model, bonelist and bonename, lets stop an anim if it's playing. +qboolean G2_Stop_Bone_Anim_Index(boneInfo_v &blist, const int index) +{ + + if (index<0 || (index >= blist.size()) || (blist[index].boneNumber == -1)) + { + // we are attempting to set a bone override that doesn't exist + return qfalse; + } + + blist[index].flags &= ~(BONE_ANIM_TOTAL); + return G2_Remove_Bone_Index(blist, index); +} + +// given a model, bonelist and bonename, lets stop an anim if it's playing. +qboolean G2_Stop_Bone_Anim(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName) +{ + int index = G2_Find_Bone(ghlInfo, blist, boneName); + if (index != -1) + { + blist[index].flags &= ~(BONE_ANIM_TOTAL); + return G2_Remove_Bone_Index(blist, index); + } + assert(0); + return qfalse; +} + +// given a model, bonelist and bonename, lets stop an anim if it's playing. +qboolean G2_Stop_Bone_Angles_Index(boneInfo_v &blist, const int index) +{ + + if ((index >= blist.size()) || (blist[index].boneNumber == -1)) + { + // we are attempting to set a bone override that doesn't exist + assert(0); + return qfalse; + } + blist[index].flags &= ~(BONE_ANGLES_TOTAL); + return G2_Remove_Bone_Index(blist, index); + +} + +// given a model, bonelist and bonename, lets stop an anim if it's playing. +qboolean G2_Stop_Bone_Angles(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName) +{ + int index = G2_Find_Bone(ghlInfo, blist, boneName); + if (index != -1) + { + blist[index].flags &= ~(BONE_ANGLES_TOTAL); + return G2_Remove_Bone_Index(blist, index); + } + assert(0); + return qfalse; +} + +//rww - RAGDOLL_BEGIN +/* + + + rag stuff + +*/ +static void G2_RagDollSolve(CGhoul2Info_v &ghoul2V,int g2Index,float decay,int frameNum,const vec3_t currentOrg,bool LimitAngles,CRagDollUpdateParams *params = NULL); +static void G2_RagDollCurrentPosition(CGhoul2Info_v &ghoul2V,int g2Index,int frameNum,const vec3_t angles,const vec3_t position,const vec3_t scale); +static bool G2_RagDollSettlePositionNumeroTrois(CGhoul2Info_v &ghoul2V,const vec3_t currentOrg,CRagDollUpdateParams *params, int curTime); +static bool G2_RagDollSetup(CGhoul2Info &ghoul2,int frameNum,bool resetOrigin,const vec3_t origin,bool anyRendered); + +void G2_GetBoneBasepose(CGhoul2Info &ghoul2,int boneNum,mdxaBone_t *&retBasepose,mdxaBone_t *&retBaseposeInv); +int G2_GetBoneDependents(CGhoul2Info &ghoul2,int boneNum,int *tempDependents,int maxDep); +void G2_GetBoneMatrixLow(CGhoul2Info &ghoul2,int boneNum,const vec3_t scale,mdxaBone_t &retMatrix,mdxaBone_t *&retBasepose,mdxaBone_t *&retBaseposeInv); +int G2_GetParentBoneMatrixLow(CGhoul2Info &ghoul2,int boneNum,const vec3_t scale,mdxaBone_t &retMatrix,mdxaBone_t *&retBasepose,mdxaBone_t *&retBaseposeInv); +bool G2_WasBoneRendered(CGhoul2Info &ghoul2,int boneNum); + +#define MAX_BONES_RAG (256) + +struct SRagEffector +{ + vec3_t currentOrigin; + vec3_t desiredDirection; + vec3_t desiredOrigin; + float radius; + float weight; +}; + +#define RAG_MASK (CONTENTS_SOLID|CONTENTS_TERRAIN)//|CONTENTS_SHOTCLIP|CONTENTS_TERRAIN//(/*MASK_SOLID|*/CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_SHOTCLIP|CONTENTS_TERRAIN|CONTENTS_BODY) + +extern cvar_t *broadsword; +extern cvar_t *broadsword_kickbones; +extern cvar_t *broadsword_kickorigin; +extern cvar_t *broadsword_dontstopanim; +extern cvar_t *broadsword_waitforshot; +extern cvar_t *broadsword_playflop; + +extern cvar_t *broadsword_effcorr; + +extern cvar_t *broadsword_ragtobase; + +extern cvar_t *broadsword_dircap; + +extern cvar_t *broadsword_extra1; +extern cvar_t *broadsword_extra2; + +#define RAG_PCJ (0x00001) +#define RAG_PCJ_POST_MULT (0x00002) // has the pcj flag as well +#define RAG_PCJ_MODEL_ROOT (0x00004) // has the pcj flag as well +#define RAG_PCJ_PELVIS (0x00008) // has the pcj flag and POST_MULT as well +#define RAG_EFFECTOR (0x00100) +#define RAG_WAS_NOT_RENDERED (0x01000) // not particularily reliable, more of a hint +#define RAG_WAS_EVER_RENDERED (0x02000) // not particularily reliable, more of a hint +#define RAG_BONE_LIGHTWEIGHT (0x04000) //used to indicate a bone's velocity treatment +#define RAG_PCJ_IK_CONTROLLED (0x08000) //controlled from IK move input +#define RAG_UNSNAPPABLE (0x10000) //cannot be broken out of constraints ever + +// thiese flags are on the model and correspond to... +//#define GHOUL2_RESERVED_FOR_RAGDOLL 0x0ff0 // these are not defined here for dependecies sake +#define GHOUL2_RAG_STARTED 0x0010 // we are actually a ragdoll +#define GHOUL2_RAG_PENDING 0x0100 // got start death anim but not end death anim +#define GHOUL2_RAG_DONE 0x0200 // got end death anim +#define GHOUL2_RAG_COLLISION_DURING_DEATH 0x0400 // ever have gotten a collision (da) event +#define GHOUL2_RAG_COLLISION_SLIDE 0x0800 // ever have gotten a collision (slide) event +#define GHOUL2_RAG_FORCESOLVE 0x1000 //api-override, determine if ragdoll should be forced to continue solving even if it thinks it is settled + +#define flrand Q_flrand + +static mdxaBone_t* ragBasepose[MAX_BONES_RAG]; +static mdxaBone_t* ragBaseposeInv[MAX_BONES_RAG]; +static mdxaBone_t ragBones[MAX_BONES_RAG]; +static SRagEffector ragEffectors[MAX_BONES_RAG]; +static boneInfo_t *ragBoneData[MAX_BONES_RAG]; +static int tempDependents[MAX_BONES_RAG]; +static int ragBlistIndex[MAX_BONES_RAG]; +static int numRags; +static vec3_t ragBoneMins; +static vec3_t ragBoneMaxs; +static vec3_t ragBoneCM; +static bool haveDesiredPelvisOffset=false; +static vec3_t desiredPelvisOffset; // this is for the root +static float ragOriginChange=0.0f; +static vec3_t ragOriginChangeDir; +//debug +static vec3_t handPos={0,0,0}; +static vec3_t handPos2={0,0,0}; + +enum ERagState +{ + ERS_DYNAMIC, + ERS_SETTLING, + ERS_SETTLED +}; +static int ragState; + +static vector *rag = NULL; // once we get the dependents precomputed this can be local + + +static void G2_Generate_MatrixRag( + // caution this must not be called before the whole skeleton is "remembered" + boneInfo_v &blist, + int index) +{ + + + boneInfo_t &bone=blist[index];//.sent; + + memcpy(&bone.matrix,&bone.ragOverrideMatrix, sizeof(mdxaBone_t)); +#ifdef _DEBUG + int i,j; + for (i = 0; i < 3; i++ ) + { + for (j = 0; j < 4; j++ ) + { + assert( !_isnan(bone.matrix.matrix[i][j])); + } + } +#endif// _DEBUG + memcpy(&blist[index].newMatrix,&bone.matrix, sizeof(mdxaBone_t)); +} + +int G2_Find_Bone_Rag(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName) +{ + int i; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + + offsets = (mdxaSkelOffsets_t *)((byte *)ghlInfo->aHeader + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)ghlInfo->aHeader + sizeof(mdxaHeader_t) + offsets->offsets[0]); + + /* + model_t *currentModel; + model_t *animModel; + mdxaHeader_t *aHeader; + + currentModel = R_GetModelByHandle(RE_RegisterModel(ghlInfo->mFileName)); + assert(currentModel); + animModel = R_GetModelByHandle(currentModel->mdxm->animIndex); + assert(animModel); + aHeader = animModel->mdxa; + assert(aHeader); + + offsets = (mdxaSkelOffsets_t *)((byte *)aHeader + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)aHeader + sizeof(mdxaHeader_t) + offsets->offsets[0]); + */ + + // look through entire list + for(i=0; iaHeader + sizeof(mdxaHeader_t) + offsets->offsets[blist[i].boneNumber]); + //skel = (mdxaSkel_t *)((byte *)aHeader + sizeof(mdxaHeader_t) + offsets->offsets[blist[i].boneNumber]); + + // if name is the same, we found it + if (!stricmp(skel->name, boneName)) + { + return i; + } + } +#if _DEBUG +// G2_Bone_Not_Found(boneName,ghlInfo->mFileName); +#endif + // didn't find it + return -1; +} + +static int G2_Set_Bone_Rag(const mdxaHeader_t *mod_a, + boneInfo_v &blist, + const char *boneName, + CGhoul2Info &ghoul2, + const vec3_t scale, + const vec3_t origin) +{ + // do not change the state of the skeleton here + int index = G2_Find_Bone_Rag(&ghoul2, blist, boneName); + + if (index == -1) + { + index = G2_Add_Bone(ghoul2.animModel, blist, boneName); + } + + if (index != -1) + { + boneInfo_t &bone=blist[index]; + VectorCopy(origin,bone.extraVec1); + + G2_GetBoneMatrixLow(ghoul2,bone.boneNumber,scale,bone.originalTrueBoneMatrix,bone.basepose,bone.baseposeInv); +// bone.parentRawBoneIndex=G2_GetParentBoneMatrixLow(ghoul2,bone.boneNumber,scale,bone.parentTrueBoneMatrix,bone.baseposeParent,bone.baseposeInvParent); + assert( !_isnan(bone.originalTrueBoneMatrix.matrix[1][1])); + assert( !_isnan(bone.originalTrueBoneMatrix.matrix[1][3])); + bone.originalOrigin[0]=bone.originalTrueBoneMatrix.matrix[0][3]; + bone.originalOrigin[1]=bone.originalTrueBoneMatrix.matrix[1][3]; + bone.originalOrigin[2]=bone.originalTrueBoneMatrix.matrix[2][3]; + } + return index; +} + +static int G2_Set_Bone_Angles_Rag( + CGhoul2Info &ghoul2, + const mdxaHeader_t *mod_a, + boneInfo_v &blist, + const char *boneName, + const int flags, + const float radius, + const vec3_t angleMin=0, + const vec3_t angleMax=0, + const int blendTime=500) +{ + int index = G2_Find_Bone_Rag(&ghoul2, blist, boneName); + + if (index == -1) + { + index = G2_Add_Bone(ghoul2.animModel, blist, boneName); + } + if (index != -1) + { + boneInfo_t &bone=blist[index]; + bone.flags &= ~(BONE_ANGLES_TOTAL); + bone.flags |= BONE_ANGLES_RAGDOLL; + if (flags&RAG_PCJ) + { + if (flags&RAG_PCJ_POST_MULT) + { + bone.flags |= BONE_ANGLES_POSTMULT; + } + else if (flags&RAG_PCJ_MODEL_ROOT) + { + bone.flags |= BONE_ANGLES_PREMULT; +// bone.flags |= BONE_ANGLES_POSTMULT; + } + else + { + assert(!"Invalid RAG PCJ\n"); + } + } + bone.ragStartTime=G2API_GetTime(0); + bone.boneBlendStart = bone.ragStartTime; + bone.boneBlendTime = blendTime; + bone.radius=radius; + bone.weight=1.0f; + + //init the others to valid values + bone.epGravFactor = 0; + VectorClear(bone.epVelocity); + bone.solidCount = 0; + bone.physicsSettled = false; + bone.snapped = false; + + bone.parentBoneIndex = -1; + + bone.offsetRotation = 0.0f; + + bone.overGradSpeed = 0.0f; + VectorClear(bone.overGoalSpot); + bone.hasOverGoal = false; + bone.hasAnimFrameMatrix = -1; + +// bone.weight=pow(radius,1.7f); //cubed was too harsh +// bone.weight=radius*radius*radius; + if (angleMin&&angleMax) + { + VectorCopy(angleMin,bone.minAngles); + VectorCopy(angleMax,bone.maxAngles); + } + else + { + VectorCopy(bone.currentAngles,bone.minAngles); // I guess this isn't a rag pcj then + VectorCopy(bone.currentAngles,bone.maxAngles); + } + if (!bone.lastTimeUpdated) + { + static mdxaBone_t id = + { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f + }; + memcpy(&bone.ragOverrideMatrix,&id, sizeof(mdxaBone_t)); + VectorClear(bone.anglesOffset); + VectorClear(bone.positionOffset); + VectorClear(bone.velocityEffector); // this is actually a velocity now + VectorClear(bone.velocityRoot); // this is actually a velocity now + VectorClear(bone.lastPosition); + VectorClear(bone.lastShotDir); + bone.lastContents=0; + // if this is non-zero, we are in a dynamic state + bone.firstCollisionTime=bone.ragStartTime; + // if this is non-zero, we are in a settling state + bone.restTime=0; + // if they are both zero, we are in a settled state + + bone.firstTime=0; + + bone.RagFlags=flags; + bone.DependentRagIndexMask=0; + + G2_Generate_MatrixRag(blist,index); // set everything to th id + +#if 0 + VectorClear(bone.currentAngles); +// VectorAdd(bone.minAngles,bone.maxAngles,bone.currentAngles); +// VectorScale(bone.currentAngles,0.5f,bone.currentAngles); +#else + { + if ( + (flags&RAG_PCJ_MODEL_ROOT) || + (flags&RAG_PCJ_PELVIS) || + !(flags&RAG_PCJ)) + { + VectorClear(bone.currentAngles); + } + else + { + int k; + for (k=0;k<3;k++) + { + float scalar=flrand(-1.0f,1.0f); + scalar*=flrand(-1.0f,1.0f)*flrand(-1.0f,1.0f); + // this is a heavily central distribution + // center it on .5 (and make it small) + scalar*=0.5f; + scalar+=0.5f; + + bone.currentAngles[k]=(bone.minAngles[k]-bone.maxAngles[k])*scalar+bone.maxAngles[k]; + } + } + } +// VectorClear(bone.currentAngles); +#endif + VectorCopy(bone.currentAngles,bone.lastAngles); + } + } + return index; +} + +class CRagDollParams; +const mdxaHeader_t *G2_GetModA(CGhoul2Info &ghoul2); + + +static void G2_RagDollMatchPosition() +{ + haveDesiredPelvisOffset=false; + int i; + for (i=0;iCallRagDollBegin=false; + } + if (!broadsword||!broadsword->integer||!parms) + { + return; + } + int model; + for (model = 0; model < ghoul2V.size(); model++) + { + if (ghoul2V[model].mModelindex != -1) + { + break; + } + } + if (model==ghoul2V.size()) + { + return; + } + CGhoul2Info &ghoul2=ghoul2V[model]; + const mdxaHeader_t *mod_a=G2_GetModA(ghoul2); + if (!mod_a) + { + return; + } + int curTime=G2API_GetTime(0); + boneInfo_v &blist = ghoul2.mBlist; + int index = G2_Find_Bone_Rag(&ghoul2, blist, "model_root"); + switch (parms->RagPhase) + { + case CRagDollParams::ERagPhase::RP_START_DEATH_ANIM: + ghoul2.mFlags|=GHOUL2_RAG_PENDING; + return; /// not doing anything with this yet + break; + case CRagDollParams::ERagPhase::RP_END_DEATH_ANIM: + ghoul2.mFlags|=GHOUL2_RAG_PENDING|GHOUL2_RAG_DONE; + if (broadsword_waitforshot && + broadsword_waitforshot->integer) + { + if (broadsword_waitforshot->integer==2) + { + if (!(ghoul2.mFlags&(GHOUL2_RAG_COLLISION_DURING_DEATH|GHOUL2_RAG_COLLISION_SLIDE))) + { + //nothing was encountered, lets just wait for the first shot + return; // we ain't starting yet + } + } + else + { + return; // we ain't starting yet + } + } + break; + case CRagDollParams::ERagPhase::RP_DEATH_COLLISION: + if (parms->collisionType) + { + ghoul2.mFlags|=GHOUL2_RAG_COLLISION_SLIDE; + } + else + { + ghoul2.mFlags|=GHOUL2_RAG_COLLISION_DURING_DEATH; + } + if (broadsword_dontstopanim && broadsword_waitforshot && + (broadsword_dontstopanim->integer || broadsword_waitforshot->integer) + ) + { + if (!(ghoul2.mFlags&GHOUL2_RAG_DONE)) + { + return; // we ain't starting yet + } + } + break; + case CRagDollParams::ERagPhase::RP_CORPSE_SHOT: + if (broadsword_kickorigin && + broadsword_kickorigin->integer) + { + if (index>=0&&index=0) + { + if (bone.flags & BONE_ANGLES_RAGDOLL) + { + //rww - Would need ent pointer here. But.. since this is SW, we aren't even having corpse shooting anyway I'd imagine. + /* + float magicFactor14=8.0f; //64.0f; // kick strength + + if (parms->fShotStrength) + { //if there is a shot strength, use it instead + magicFactor14 = parms->fShotStrength; + } + + parms->me->s.pos.trType = TR_GRAVITY; + parms->me->s.pos.trDelta[0] += bone.lastShotDir[0]*magicFactor14; + parms->me->s.pos.trDelta[1] += bone.lastShotDir[1]*magicFactor14; + //parms->me->s.pos.trDelta[2] = fabsf(bone.lastShotDir[2])*magicFactor14; + //rww - The vertical portion of this doesn't seem to work very well + //I am just leaving it whatever it is for now, because my velocity scaling + //only works on x and y and the gravity stuff for NPCs is a bit unpleasent + //trying to change/work with + assert( !_isnan(bone.lastShotDir[1])); + */ + } + } + } + } + break; + case CRagDollParams::ERagPhase::RP_GET_PELVIS_OFFSET: + if (parms->RagPhase==CRagDollParams::ERagPhase::RP_GET_PELVIS_OFFSET) + { + VectorClear(parms->pelvisAnglesOffset); + VectorClear(parms->pelvisPositionOffset); + } + // intentional lack of a break + case CRagDollParams::ERagPhase::RP_SET_PELVIS_OFFSET: + if (index>=0&&index=0) + { + if (bone.flags & BONE_ANGLES_RAGDOLL) + { + if (parms->RagPhase==CRagDollParams::ERagPhase::RP_GET_PELVIS_OFFSET) + { + VectorCopy(bone.anglesOffset,parms->pelvisAnglesOffset); + VectorCopy(bone.positionOffset,parms->pelvisPositionOffset); + } + else + { + VectorCopy(parms->pelvisAnglesOffset,bone.anglesOffset); + VectorCopy(parms->pelvisPositionOffset,bone.positionOffset); + } + } + } + } + return; + break; + case CRagDollParams::ERagPhase::RP_DISABLE_EFFECTORS: + // not doing anything with this yet + return; + break; + default: + assert(0); + return; + break; + } + + if (ghoul2.mFlags&GHOUL2_RAG_STARTED) + { + // only going to begin ragdoll once, everything else depends on what happens to the origin + return; + } +#if 0 +if (index>=0) +{ + OutputDebugString(va("death %d %d\n",blist[index].startFrame,blist[index].endFrame)); +} +#endif + + ghoul2.mFlags|=GHOUL2_RAG_PENDING|GHOUL2_RAG_DONE|GHOUL2_RAG_STARTED; // well anyway we are going live + parms->CallRagDollBegin=true; + + G2_GenerateWorldMatrix(parms->angles, parms->position); + G2_ConstructGhoulSkeleton(ghoul2V, curTime, false, parms->scale); +#if 1 + G2_Set_Bone_Rag(mod_a,blist,"model_root",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"pelvis",ghoul2,parms->scale,parms->position); + + G2_Set_Bone_Rag(mod_a,blist,"lower_lumbar",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"upper_lumbar",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"thoracic",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"cranium",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rhumerus",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lhumerus",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rradius",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lradius",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rfemurYZ",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lfemurYZ",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rtibia",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"ltibia",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rhand",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lhand",ghoul2,parms->scale,parms->position); + //G2_Set_Bone_Rag(mod_a,blist,"rtarsal",ghoul2,parms->scale,parms->position); + //G2_Set_Bone_Rag(mod_a,blist,"ltarsal",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rtalus",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"ltalus",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rradiusX",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lradiusX",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rfemurX",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lfemurX",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"ceyebrow",ghoul2,parms->scale,parms->position); +#else + G2_Set_Bone_Rag(mod_a,blist,"model_root",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"pelvis",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lfemurYZ",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"ltibia",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rfemurYZ",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rtibia",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lower_lumbar",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"upper_lumbar",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"thoracic",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"cervical",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"ceyebrow",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rhumerus",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rradius",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lhumerus",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lradius",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lfemurX",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"ltalus",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rfemurX",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rtalus",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rhumerusX",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rradiusX",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"rhand",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lhumerusX",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lradiusX",ghoul2,parms->scale,parms->position); + G2_Set_Bone_Rag(mod_a,blist,"lhand",ghoul2,parms->scale,parms->position); +#endif + //int startFrame = 3665, endFrame = 3665+1; + int startFrame = parms->startFrame, endFrame = parms->endFrame; + assert(startFrame < mod_a->numFrames); + assert(endFrame < mod_a->numFrames); + + G2_Set_Bone_Anim_No_BS(ghoul2, mod_a,blist,"upper_lumbar",startFrame,endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f,curTime,float(startFrame),150,0,true); + G2_Set_Bone_Anim_No_BS(ghoul2, mod_a,blist,"lower_lumbar",startFrame,endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f,curTime,float(startFrame),150,0,true); + G2_Set_Bone_Anim_No_BS(ghoul2, mod_a,blist,"Motion",startFrame,endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f,curTime,float(startFrame),150,0,true); +// G2_Set_Bone_Anim_No_BS(ghoul2, mod_a,blist,"model_root",startFrame,endFrame-1, +// BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, +// 1.0f,curTime,float(startFrame),150,0,true); + G2_Set_Bone_Anim_No_BS(ghoul2, mod_a,blist,"lfemurYZ",startFrame,endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f,curTime,float(startFrame),150,0,true); + G2_Set_Bone_Anim_No_BS(ghoul2, mod_a,blist,"rfemurYZ",startFrame,endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f,curTime,float(startFrame),150,0,true); + + G2_Set_Bone_Anim_No_BS(ghoul2, mod_a,blist,"rhumerus",startFrame,endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f,curTime,float(startFrame),150,0,true); + G2_Set_Bone_Anim_No_BS(ghoul2, mod_a,blist,"lhumerus",startFrame,endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f,curTime,float(startFrame),150,0,true); + +// should already be set G2_GenerateWorldMatrix(parms->angles, parms->position); + G2_ConstructGhoulSkeleton(ghoul2V, curTime, false, parms->scale); +#if 1 + static const float fRadScale = 0.3f;//0.5f; + + vec3_t pcjMin,pcjMax; + VectorSet(pcjMin,-90.0f,-45.0f,-45.0f); + VectorSet(pcjMax,90.0f,45.0f,45.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"model_root",RAG_PCJ_MODEL_ROOT|RAG_PCJ|RAG_UNSNAPPABLE,10.0f*fRadScale,pcjMin,pcjMax,100); + VectorSet(pcjMin,-45.0f,-45.0f,-45.0f); + VectorSet(pcjMax,45.0f,45.0f,45.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"pelvis",RAG_PCJ_PELVIS|RAG_PCJ|RAG_PCJ_POST_MULT|RAG_UNSNAPPABLE,10.0f*fRadScale,pcjMin,pcjMax,100); + + // new base anim, unconscious flop + int pcjflags=RAG_PCJ|RAG_PCJ_POST_MULT;//|RAG_EFFECTOR; + + VectorSet(pcjMin,-15.0f,-15.0f,-15.0f); + VectorSet(pcjMax,15.0f,15.0f,15.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lower_lumbar",pcjflags|RAG_UNSNAPPABLE,10.0f*fRadScale,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"upper_lumbar",pcjflags|RAG_UNSNAPPABLE,10.0f*fRadScale,pcjMin,pcjMax,500); + VectorSet(pcjMin,-25.0f,-25.0f,-25.0f); + VectorSet(pcjMax,25.0f,25.0f,25.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"thoracic",pcjflags|RAG_EFFECTOR|RAG_UNSNAPPABLE,12.0f*fRadScale,pcjMin,pcjMax,500); + + VectorSet(pcjMin,-10.0f,-10.0f,-90.0f); + VectorSet(pcjMax,10.0f,10.0f,90.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"cranium",pcjflags|RAG_BONE_LIGHTWEIGHT|RAG_UNSNAPPABLE,6.0f*fRadScale,pcjMin,pcjMax,500); + + static const float sFactLeg = 1.0f; + static const float sFactArm = 1.0f; + static const float sRadArm = 1.0f; + static const float sRadLeg = 1.0f; + + VectorSet(pcjMin,-100.0f,-40.0f,-15.0f); + VectorSet(pcjMax,-15.0f,80.0f,15.0f); + VectorScale(pcjMin, sFactArm, pcjMin); + VectorScale(pcjMax, sFactArm, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rhumerus",pcjflags|RAG_BONE_LIGHTWEIGHT|RAG_UNSNAPPABLE,(4.0f*sRadArm)*fRadScale,pcjMin,pcjMax,500); + VectorSet(pcjMin,-50.0f,-80.0f,-15.0f); + VectorSet(pcjMax,15.0f,40.0f,15.0f); + VectorScale(pcjMin, sFactArm, pcjMin); + VectorScale(pcjMax, sFactArm, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lhumerus",pcjflags|RAG_BONE_LIGHTWEIGHT|RAG_UNSNAPPABLE,(4.0f*sRadArm)*fRadScale,pcjMin,pcjMax,500); + + VectorSet(pcjMin,-25.0f,-20.0f,-20.0f); + VectorSet(pcjMax,90.0f,20.0f,-20.0f); + VectorScale(pcjMin, sFactArm, pcjMin); + VectorScale(pcjMax, sFactArm, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rradius",pcjflags|RAG_BONE_LIGHTWEIGHT,(3.0f*sRadArm)*fRadScale,pcjMin,pcjMax,500); + VectorSet(pcjMin,-90.0f,-20.0f,-20.0f); + VectorSet(pcjMax,30.0f,20.0f,-20.0f); + VectorScale(pcjMin, sFactArm, pcjMin); + VectorScale(pcjMax, sFactArm, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lradius",pcjflags|RAG_BONE_LIGHTWEIGHT,(3.0f*sRadArm)*fRadScale,pcjMin,pcjMax,500); + + + VectorSet(pcjMin,-80.0f,-50.0f,-20.0f); + VectorSet(pcjMax,30.0f,5.0f,20.0f); + VectorScale(pcjMin, sFactLeg, pcjMin); + VectorScale(pcjMax, sFactLeg, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rfemurYZ",pcjflags|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadLeg)*fRadScale,pcjMin,pcjMax,500); + VectorSet(pcjMin,-60.0f,-5.0f,-20.0f); + VectorSet(pcjMax,50.0f,50.0f,20.0f); + VectorScale(pcjMin, sFactLeg, pcjMin); + VectorScale(pcjMax, sFactLeg, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lfemurYZ",pcjflags|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadLeg)*fRadScale,pcjMin,pcjMax,500); + + VectorSet(pcjMin,-20.0f,-15.0f,-15.0f); + VectorSet(pcjMax,100.0f,15.0f,15.0f); + VectorScale(pcjMin, sFactLeg, pcjMin); + VectorScale(pcjMax, sFactLeg, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rtibia",pcjflags|RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadLeg)*fRadScale,pcjMin,pcjMax,500); + VectorSet(pcjMin,20.0f,-15.0f,-15.0f); + VectorSet(pcjMax,100.0f,15.0f,15.0f); + VectorScale(pcjMin, sFactLeg, pcjMin); + VectorScale(pcjMax, sFactLeg, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"ltibia",pcjflags|RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadLeg)*fRadScale,pcjMin,pcjMax,500); + + float sRadEArm = 1.2f; + float sRadELeg = 1.2f; + +// int rhand= + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rhand",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadEArm)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lhand",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadEArm)*fRadScale); + //G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rtarsal",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadELeg)*fRadScale); + //G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"ltarsal",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadELeg)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rtalus",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadELeg)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"ltalus",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadELeg)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rradiusX",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadEArm)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lradiusX",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadEArm)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rfemurX",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(10.0f*sRadELeg)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lfemurX",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(10.0f*sRadELeg)*fRadScale); + //G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"ceyebrow",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,10.0f*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"ceyebrow",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,5.0f); +#else + static const float fRadScale = 0.3f;//0.5f; + static int pcjflags = RAG_PCJ|RAG_PCJ_POST_MULT;//|RAG_EFFECTOR; + static const float sFactLeg = 1.0f; + static const float sFactArm = 1.0f; + static const float sRadArm = 1.0f; + static const float sRadLeg = 1.0f; + + vec3_t pcjMin,pcjMax; + VectorSet(pcjMin,-90.0f,-45.0f,-45.0f); + VectorSet(pcjMax,90.0f,45.0f,45.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"model_root",RAG_PCJ_MODEL_ROOT|RAG_PCJ|RAG_UNSNAPPABLE,10.0f*fRadScale,pcjMin,pcjMax,100); + VectorSet(pcjMin,-45.0f,-45.0f,-45.0f); + VectorSet(pcjMax,45.0f,45.0f,45.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"pelvis",RAG_PCJ_PELVIS|RAG_PCJ|RAG_PCJ_POST_MULT|RAG_UNSNAPPABLE,10.0f*fRadScale,pcjMin,pcjMax,100); + + //PCJ/EFFECTORS + VectorSet(pcjMin,-80.0f,-50.0f,-20.0f); + VectorSet(pcjMax,30.0f,5.0f,20.0f); + VectorScale(pcjMin, sFactLeg, pcjMin); + VectorScale(pcjMax, sFactLeg, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rfemurYZ",pcjflags|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadLeg)*fRadScale,pcjMin,pcjMax,500); + VectorSet(pcjMin,-60.0f,-5.0f,-20.0f); + VectorSet(pcjMax,50.0f,50.0f,20.0f); + VectorScale(pcjMin, sFactLeg, pcjMin); + VectorScale(pcjMax, sFactLeg, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lfemurYZ",pcjflags|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadLeg)*fRadScale,pcjMin,pcjMax,500); + + VectorSet(pcjMin,-20.0f,-15.0f,-15.0f); + VectorSet(pcjMax,100.0f,15.0f,15.0f); + VectorScale(pcjMin, sFactLeg, pcjMin); + VectorScale(pcjMax, sFactLeg, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rtibia",pcjflags|RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadLeg)*fRadScale,pcjMin,pcjMax,500); + VectorSet(pcjMin,20.0f,-15.0f,-15.0f); + VectorSet(pcjMax,100.0f,15.0f,15.0f); + VectorScale(pcjMin, sFactLeg, pcjMin); + VectorScale(pcjMax, sFactLeg, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"ltibia",pcjflags|RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadLeg)*fRadScale,pcjMin,pcjMax,500); + + + VectorSet(pcjMin,-15.0f,-15.0f,-15.0f); + VectorSet(pcjMax,15.0f,15.0f,15.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lower_lumbar",pcjflags|RAG_EFFECTOR|RAG_UNSNAPPABLE,10.0f*fRadScale,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"upper_lumbar",pcjflags|RAG_EFFECTOR|RAG_UNSNAPPABLE,10.0f*fRadScale,pcjMin,pcjMax,500); + VectorSet(pcjMin,-25.0f,-25.0f,-25.0f); + VectorSet(pcjMax,25.0f,25.0f,25.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"thoracic",pcjflags|RAG_EFFECTOR|RAG_UNSNAPPABLE,12.0f*fRadScale,pcjMin,pcjMax,500); + + VectorSet(pcjMin,-10.0f,-10.0f,-90.0f); + VectorSet(pcjMax,10.0f,10.0f,90.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"cranium",RAG_PCJ|RAG_PCJ_POST_MULT|RAG_BONE_LIGHTWEIGHT|RAG_UNSNAPPABLE,6.0f*fRadScale,pcjMin,pcjMax,500); + + VectorSet(pcjMin,-100.0f,-40.0f,-15.0f); + VectorSet(pcjMax,-15.0f,80.0f,15.0f); + VectorScale(pcjMin, sFactArm, pcjMin); + VectorScale(pcjMax, sFactArm, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rhumerus",pcjflags|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadArm)*fRadScale,pcjMin,pcjMax,500); + VectorSet(pcjMin,-50.0f,-80.0f,-15.0f); + VectorSet(pcjMax,15.0f,40.0f,15.0f); + VectorScale(pcjMin, sFactArm, pcjMin); + VectorScale(pcjMax, sFactArm, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lhumerus",pcjflags|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadArm)*fRadScale,pcjMin,pcjMax,500); + + VectorSet(pcjMin,-25.0f,-20.0f,-20.0f); + VectorSet(pcjMax,90.0f,20.0f,-20.0f); + VectorScale(pcjMin, sFactArm, pcjMin); + VectorScale(pcjMax, sFactArm, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rradius",pcjflags|RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(3.0f*sRadArm)*fRadScale,pcjMin,pcjMax,500); + VectorSet(pcjMin,-90.0f,-20.0f,-20.0f); + VectorSet(pcjMax,30.0f,20.0f,-20.0f); + VectorScale(pcjMin, sFactArm, pcjMin); + VectorScale(pcjMax, sFactArm, pcjMax); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lradius",pcjflags|RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(3.0f*sRadArm)*fRadScale,pcjMin,pcjMax,500); + + + //EFFECTORS + static const float sRadEArm = 1.2f; + static const float sRadELeg = 1.2f; + + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"cervical",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,10.0f*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"ceyebrow",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,10.0f*fRadScale); +// G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rfemurX",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(10.0f*sRadELeg)*fRadScale); +// G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lfemurX",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(10.0f*sRadELeg)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rtalus",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadELeg)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"ltalus",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadELeg)*fRadScale); +// G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rhumerusX",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadEArm)*fRadScale); +// G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lhumerusX",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadEArm)*fRadScale); +// G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rradiusX",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadEArm)*fRadScale); +// G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lradiusX",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadEArm)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rhand",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadEArm)*fRadScale); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lhand",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(6.0f*sRadEArm)*fRadScale); +#endif +//match the currrent animation + if (!G2_RagDollSetup(ghoul2,curTime,true,parms->position,false)) + { + assert(!"failed to add any rag bones"); + return; + } + G2_RagDollCurrentPosition(ghoul2V,model,curTime,parms->angles,parms->position,parms->scale); + + int k; + + CRagDollInitialUpdateParams fparms; + VectorCopy(parms->position, fparms.position); + VectorCopy(parms->angles, fparms.angles); + VectorCopy(parms->scale, fparms.scale); + VectorClear(fparms.velocity); + fparms.me = parms->me; + fparms.settleFrame = parms->endFrame; + fparms.groundEnt = parms->groundEnt; + + //Guess I don't need to do this, do I? + G2_ConstructGhoulSkeleton(ghoul2V, curTime, false, parms->scale); + + vec3_t dPos; + VectorCopy(parms->position, dPos); +#ifdef _OLD_STYLE_SETTLE + dPos[2] -= 6; +#endif + + for (k=0;kangles,dPos,parms->scale); + G2_RagDollMatchPosition(); + G2_RagDollSolve(ghoul2V,model,1.0f*(1.0f-k/40.0f),curTime,dPos,false); + } +} + +void G2_SetRagDollBullet(CGhoul2Info &ghoul2,const vec3_t rayStart,const vec3_t hit) +{ + if (!broadsword||!broadsword->integer) + { + return; + } + vec3_t shotDir; + VectorSubtract(hit,rayStart,shotDir); + float len=VectorLength(shotDir); + if (len<1.0f) + { + return; + } + float lenr=1.0f/len; + shotDir[0]*=lenr; + shotDir[1]*=lenr; + shotDir[2]*=lenr; + + bool firstOne=false; + if (broadsword_kickbones&&broadsword_kickbones->integer) + { + int i; + int magicFactor13=150.0f; // squared radius multiplier for shot effects + boneInfo_v &blist = ghoul2.mBlist; + for(i=blist.size()-1;i>=0;i--) + { + boneInfo_t &bone=blist[i]; + if ((bone.flags & BONE_ANGLES_TOTAL)) + { + if (bone.flags & BONE_ANGLES_RAGDOLL) + { + if (!firstOne) + { + firstOne=true; +#if 0 + int curTime=G2API_GetTime(0); + const mdxaHeader_t *mod_a=G2_GetModA(ghoul2); + int startFrame = 0, endFrame = 0; +#if 1 + TheGhoul2Wraith()->GetAnimFrames(ghoul2.mID, "unconsciousdeadflop01", startFrame, endFrame); + if (startFrame == -1 && endFrame == -1) + { //A bad thing happened! Just use the hardcoded numbers even though they could be wrong. + startFrame = 3573; + endFrame = 3583; + assert(0); + } + G2_Set_Bone_Anim_No_BS(mod_a,blist,"upper_lumbar",startFrame,endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f,curTime,float(startFrame),75,0,true); + G2_Set_Bone_Anim_No_BS(mod_a,blist,"lfemurYZ",startFrame,endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f,curTime,float(startFrame),75,0,true); + G2_Set_Bone_Anim_No_BS(mod_a,blist,"rfemurYZ",startFrame,endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f,curTime,float(startFrame),75,0,true); +#else + TheGhoul2Wraith()->GetAnimFrames(ghoul2.mID, "backdeadflop01", startFrame, endFrame); + if (startFrame == -1 && endFrame == -1) + { //A bad thing happened! Just use the hardcoded numbers even though they could be wrong. + startFrame = 3581; + endFrame = 3592; + assert(0); + } + G2_Set_Bone_Anim_No_BS(mod_a,blist,"upper_lumbar",endFrame,startFrame+1, + BONE_ANIM_OVERRIDE_FREEZE, + -1.0f,curTime,float(endFrame-1),50,0,true); + G2_Set_Bone_Anim_No_BS(mod_a,blist,"lfemurYZ",endFrame,startFrame+1, + BONE_ANIM_OVERRIDE_FREEZE, + -1.0f,curTime,float(endFrame-1),50,0,true); + G2_Set_Bone_Anim_No_BS(mod_a,blist,"rfemurYZ",endFrame,startFrame+1, + BONE_ANIM_OVERRIDE_FREEZE, + -1.0f,curTime,float(endFrame-1),50,0,true); +#endif +#endif + } + + VectorCopy(shotDir,bone.lastShotDir); + vec3_t dir; + VectorSubtract(bone.lastPosition,hit,dir); + len=VectorLength(dir); + if (len<1.0f) + { + len=1.0f; + } + lenr=1.0f/len; + float effect=lenr; + effect*=magicFactor13*effect; // this is cubed, one of them is absorbed by the next calc + bone.velocityEffector[0]=shotDir[0]*(effect+flrand(0.0f,0.05f)); + bone.velocityEffector[1]=shotDir[1]*(effect+flrand(0.0f,0.05f)); + bone.velocityEffector[2]=fabs(shotDir[2])*(effect+flrand(0.0f,0.05f)); +// bone.velocityEffector[0]=shotDir[0]*(effect+flrand(0.0f,0.05f))*flrand(-0.1f,3.0f); +// bone.velocityEffector[1]=shotDir[1]*(effect+flrand(0.0f,0.05f))*flrand(-0.1f,3.0f); +// bone.velocityEffector[2]=fabs(shotDir[2])*(effect+flrand(0.0f,0.05f))*flrand(-0.1f,3.0f); + assert( !_isnan(shotDir[2])); + // bone.currentAngles[0]+=flrand(-10.0f*lenr,10.0f*lenr); + // bone.currentAngles[1]+=flrand(-10.0f*lenr,10.0f*lenr); + // bone.currentAngles[2]+=flrand(-10.0f*lenr,10.0f*lenr); + // bone.lastAngles[0]+=flrand(-10.0f*lenr,10.0f*lenr); + // bone.lastAngles[1]+=flrand(-10.0f*lenr,10.0f*lenr); + // bone.lastAngles[2]+=flrand(-10.0f*lenr,10.0f*lenr); + + // go dynamic + bone.firstCollisionTime=G2API_GetTime(0); +// bone.firstCollisionTime=0; + bone.restTime=0; + } + } + } + } +} + + +static float G2_RagSetState(CGhoul2Info &ghoul2, boneInfo_t &bone,int frameNum,const vec3_t origin,bool &resetOrigin) +{ + ragOriginChange=DistanceSquared(origin,bone.extraVec1); + VectorSubtract(origin,bone.extraVec1,ragOriginChangeDir); + + float decay=1.0f; + + int dynamicTime=1000; + int settleTime=1000; + + if (ghoul2.mFlags & GHOUL2_RAG_FORCESOLVE) + { + ragState=ERS_DYNAMIC; + if (frameNum>bone.firstCollisionTime+dynamicTime) + { + VectorCopy(origin,bone.extraVec1); + if (ragOriginChange>15.0f) + { //if we moved, or if this bone is still in solid + bone.firstCollisionTime=frameNum; + } + else + { + // settle out + bone.firstCollisionTime=0; + bone.restTime=frameNum; + ragState=ERS_SETTLING; + } + } + } + else if (bone.firstCollisionTime>0) + { + ragState=ERS_DYNAMIC; + if (frameNum>bone.firstCollisionTime+dynamicTime) + { + VectorCopy(origin,bone.extraVec1); + if (ragOriginChange>15.0f) + { //if we moved + bone.firstCollisionTime=frameNum; + } + else + { + // settle out + bone.firstCollisionTime=0; + bone.restTime=frameNum; + ragState=ERS_SETTLING; + } + } +//decay=0.0f; + } + else if (bone.restTime>0) + { + decay=1.0f-(frameNum-bone.restTime)/float(dynamicTime); + if (decay<0.0f) + { + decay=0.0f; + } + if (decay>1.0f) + { + decay=1.0f; + } + float magicFactor8=1.0f; // Power for decay + decay=pow(decay,magicFactor8); + ragState=ERS_SETTLING; + if (frameNum>bone.restTime+settleTime) + { + VectorCopy(origin,bone.extraVec1); + if (ragOriginChange>15.0f) + { + bone.restTime=frameNum; + } + else + { + // stop + bone.restTime=0; + ragState=ERS_SETTLED; + } + } +//decay=0.0f; + } + else + { + if (bone.RagFlags & RAG_PCJ_IK_CONTROLLED) + { + bone.firstCollisionTime=frameNum; + ragState=ERS_DYNAMIC; + } + else if (ragOriginChange>15.0f) + { + bone.firstCollisionTime=frameNum; + ragState=ERS_DYNAMIC; + } + else + { + ragState=ERS_SETTLED; + } + decay=0.0f; + } +// ragState=ERS_SETTLED; +// decay=0.0f; + return decay; +} + +static bool G2_RagDollSetup(CGhoul2Info &ghoul2,int frameNum,bool resetOrigin,const vec3_t origin,bool anyRendered) +{ + int i; + int minSurvivingBone=10000; + int minSurvivingBoneAt=-1; + int minSurvivingBoneAlt=10000; + int minSurvivingBoneAtAlt=-1; + + assert(ghoul2.mFileName[0]); + boneInfo_v &blist = ghoul2.mBlist; + if(!rag) { + rag = new vector; + } + rag->clear(); + int numRendered=0; + int numNotRendered=0; + int pelvisAt=-1; + for(i=0; i=0) + { + assert(bone.boneNumberbone.boneNumber) + { + minSurvivingBone=bone.boneNumber; + minSurvivingBoneAt=i; + } + } + else if (wasRendered) + { + if (minSurvivingBoneAlt>bone.boneNumber) + { + minSurvivingBoneAlt=bone.boneNumber; + minSurvivingBoneAtAlt=i; + } + } + if ( + anyRendered && + (bone.RagFlags&RAG_WAS_EVER_RENDERED) && + !(bone.RagFlags&RAG_PCJ_MODEL_ROOT) && + !(bone.RagFlags&RAG_PCJ_PELVIS) && + !wasRendered && + (bone.RagFlags&RAG_EFFECTOR) + ) + { + // this thing was rendered in the past, but wasn't now, although other bones were, lets get rid of it +// bone.flags &= ~BONE_ANGLES_RAGDOLL; +// bone.RagFlags = 0; +//OutputDebugString(va("Deleted Effector %d\n",i)); +// continue; + } + if (rag->size()resize(bone.boneNumber+1,0); + } + (*rag)[bone.boneNumber]=&bone; + ragBlistIndex[bone.boneNumber]=i; + + bone.lastTimeUpdated=frameNum; + if (resetOrigin) + { + VectorCopy(origin,bone.extraVec1); // this is only done incase a limb is removed + } + } + } + } +#if 0 + if (numRendered<5) // I think this is a limb + { +//OutputDebugString(va("limb %3d/%3d (r,N).\n",numRendered,numNotRendered)); + if (minSurvivingBoneAt<0) + { + // pelvis is gone, but we have no remaining pcj's + // just find any remain rag effector + minSurvivingBoneAt=minSurvivingBoneAtAlt; + } + if ( + minSurvivingBoneAt>=0 && + pelvisAt>=0) + { + { + // remove the pelvis as a rag + boneInfo_t &bone=blist[minSurvivingBoneAt]; + bone.flags&=~BONE_ANGLES_RAGDOLL; + bone.RagFlags=0; + } + { + // the root-est bone is now our "pelvis + boneInfo_t &bone=blist[minSurvivingBoneAt]; + VectorSet(bone.minAngles,-14500.0f,-14500.0f,-14500.0f); + VectorSet(bone.maxAngles,14500.0f,14500.0f,14500.0f); + bone.RagFlags|=RAG_PCJ_PELVIS|RAG_PCJ; // this guy is our new "pelvis" + bone.flags |= BONE_ANGLES_POSTMULT; + bone.ragStartTime=G2API_GetTime(0); + } + } + } +#endif + numRags=0; + int ragStartTime=0; + for(i=0; isize(); i++) + { + if ((*rag)[i]) + { + boneInfo_t &bone=*(*rag)[i]; + assert(bone.boneNumber>=0); + assert(numRagsinteger) + { + return; + } + + if (!params) + { + assert(0); + return; + } + + vec3_t dPos; + VectorCopy(params->position, dPos); +#ifdef _OLD_STYLE_SETTLE + dPos[2] -= 6; +#endif + +// params->DebugLine(handPos,handPos2,false); + int frameNum=G2API_GetTime(0); + CGhoul2Info &ghoul2=ghoul2V[g2Index]; + assert(ghoul2.mFileName[0]); + boneInfo_v &blist = ghoul2.mBlist; + + // hack for freezing ragdoll (no idea if it works) +#if 0 + if (0) + { + // we gotta hack this to basically freeze the timers + for(i=0; i=0) + { + assert(bone.boneNumber=0) + { + assert(bone.boneNumber= 0 && bone2.solidCount > 8) + { + noneInSolid = false; + break; + } + } + + if (noneInSolid) + { //we're settled then + params->RagDollSettled(); + return; + } + else + { + continue; + } +#else + params->RagDollSettled(); + return; +#endif + } + if (G2_WasBoneRendered(ghoul2,bone.boneNumber)) + { + anyRendered=true; + break; + } + } + } + } + //int iters=(ragState==ERS_DYNAMIC)?2:1; + int iters=(ragState==ERS_DYNAMIC)?4:2; +/* + bool kicked=false; + if (ragOriginChangeDir[2]<-100.0f) + { + kicked=true; + //iters*=8; + iters*=5; //rww - changed to this.. it was getting up to around 600 traces at times before (which is insane) + } +*/ + if (iters) + { + if (!G2_RagDollSetup(ghoul2,frameNum,resetOrigin,dPos,anyRendered)) + { + return; + } + // ok, now our data structures are compact and set up in topological order + + for (i=0;iangles,dPos,params->scale); + + if (G2_RagDollSettlePositionNumeroTrois(ghoul2V,dPos,params,curTime)) + { +#if 0 + //effectors are start solid alot, so this was pretty extreme + if (!kicked&&iters<4) + { + kicked=true; + //iters*=4; + iters*=2; + } +#endif + } + //params->position[2] += 16; + G2_RagDollSolve(ghoul2V,g2Index,decay*2.0f,frameNum,dPos,true,params); + } + } + + if (params->me != ENTITYNUM_NONE) + { +#if 0 + vec3_t worldMins,worldMaxs; + worldMins[0]=params->position[0]-17; + worldMins[1]=params->position[1]-17; + worldMins[2]=params->position[2]; + worldMaxs[0]=params->position[0]+17; + worldMaxs[1]=params->position[1]+17; + worldMaxs[2]=params->position[2]; +//OutputDebugString(va("%f \n",worldMins[2])); +// params->DebugLine(worldMins,worldMaxs,true); +#endif + G2_RagDollCurrentPosition(ghoul2V,g2Index,frameNum,params->angles,params->position,params->scale); +// SV_UnlinkEntity(params->me); +// params->me->SetMins(BB_SHOOTING_SIZE,ragBoneMins); +// params->me->SetMaxs(BB_SHOOTING_SIZE,ragBoneMaxs); +// SV_LinkEntity(params->me); + } +} + +//#define _DEBUG_BONE_NAMES + +static inline char *G2_Get_Bone_Name(CGhoul2Info *ghlInfo, boneInfo_v &blist, int boneNum) +{ + int i; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)ghlInfo->aHeader + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)ghlInfo->aHeader + sizeof(mdxaHeader_t) + offsets->offsets[0]); + + // look through entire list + for(i=0; iaHeader + sizeof(mdxaHeader_t) + offsets->offsets[blist[i].boneNumber]); + + return skel->name; + } + + // didn't find it + return "BONE_NOT_FOUND"; +} + +char *G2_GetBoneNameFromSkel(CGhoul2Info &ghoul2, int boneNum); +static void G2_RagDollCurrentPosition(CGhoul2Info_v &ghoul2V,int g2Index,int frameNum,const vec3_t angles,const vec3_t position,const vec3_t scale) +{ + CGhoul2Info &ghoul2=ghoul2V[g2Index]; + assert(ghoul2.mFileName[0]); +//OutputDebugString(va("angles %f %f %f\n",angles[0],angles[1],angles[2])); + G2_GenerateWorldMatrix(angles,position); + G2_ConstructGhoulSkeleton(ghoul2V, frameNum, false, scale); + + float totalWt=0.0f; + int i; + for (i=0;iragBoneMaxs[k]) + { + ragBoneMaxs[k]=ragEffectors[i].currentOrigin[k]; + } + if (ragEffectors[i].currentOrigin[k]0.0f); + int k; + { + float wtInv=1.0f/totalWt; + for (k=0;k<3;k++) + { + ragBoneMaxs[k]-=position[k]; + ragBoneMins[k]-=position[k]; + ragBoneMaxs[k]+=10.0f; + ragBoneMins[k]-=10.0f; + ragBoneCM[k]*=wtInv; + + ragBoneCM[k]=ragEffectors[0].currentOrigin[k]; // use the pelvis + } + } +} + +void VectorAdvance( const vec3_t veca, const float scale, const vec3_t vecb, vec3_t vecc); + +#ifdef _DEBUG +int ragTraceTime = 0; +int ragSSCount = 0; +int ragTraceCount = 0; +#endif + +void Rag_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, const int passEntityNum, const int contentmask, const EG2_Collision eG2TraceType, const int useLod ) +{ +#ifdef _DEBUG + int ragPreTrace = Sys_Milliseconds(); +#endif + SV_Trace(results, start, mins, maxs, end, passEntityNum, contentmask, eG2TraceType, useLod); +#ifdef _DEBUG + int ragPostTrace = Sys_Milliseconds(); + + ragTraceTime += (ragPostTrace - ragPreTrace); + if (results->startsolid) + { + ragSSCount++; + } + ragTraceCount++; +#endif +} + +//run advanced physics on each bone indivudually +//an adaption of my "exphys" custom game physics model +#define MAX_GRAVITY_PULL 256//512 + +static inline bool G2_BoneOnGround(const vec3_t org, const vec3_t mins, const vec3_t maxs, const int ignoreNum) +{ + trace_t tr; + vec3_t gSpot; + + VectorCopy(org, gSpot); + gSpot[2] -= 1.0f; //seems reasonable to me + + Rag_Trace(&tr, org, mins, maxs, gSpot, ignoreNum, RAG_MASK, G2_NOCOLLIDE, 0); + + if (tr.fraction != 1.0f && !tr.startsolid && !tr.allsolid) + { //not in solid, and hit something. Guess it's ground. + return true; + } + + return false; +} + +static inline bool G2_ApplyRealBonePhysics(boneInfo_t &bone, SRagEffector &e, CRagDollUpdateParams *params, vec3_t goalSpot, const vec3_t goalBase, const vec3_t testMins, const vec3_t testMaxs, + const float gravity, const float mass, const float bounce) +{ + trace_t tr; + vec3_t projectedOrigin; + vec3_t vNorm; + vec3_t ground; + vec3_t usedOrigin; + float velScaling = 0.1f; + float vTotal = 0.0f; + bool boneOnGround = false; + + assert(mass <= 1.0f && mass >= 0.01f); + + if (bone.physicsSettled) + { //then we have no need to continue + return true; + } + + if (goalBase) + { + VectorCopy(goalBase, usedOrigin); + } + else + { + VectorCopy(e.currentOrigin, usedOrigin); + } + + if (gravity) + { //factor it in before we do anything. + VectorCopy(usedOrigin, ground); + ground[2] -= 1.0f; + + Rag_Trace(&tr, usedOrigin, testMins, testMaxs, ground, params->me, RAG_MASK, G2_NOCOLLIDE, 0); + + if (tr.entityNum == ENTITYNUM_NONE) + { + boneOnGround = false; + } + else + { + boneOnGround = true; + } + + if (!boneOnGround) + { + if (!params->velocity[2]) + { //only increase gravitational pull once the actual entity is still + bone.epGravFactor += gravity; + } + + if (bone.epGravFactor > MAX_GRAVITY_PULL) + { //cap it off if needed + bone.epGravFactor = MAX_GRAVITY_PULL; + } + + bone.epVelocity[2] -= bone.epGravFactor; + } + else + { //if we're sitting on something then reset the gravity factor. + bone.epGravFactor = 0; + } + } + else + { + boneOnGround = G2_BoneOnGround(usedOrigin, testMins, testMaxs, params->me); + } + + if (!bone.epVelocity[0] && !bone.epVelocity[1] && !bone.epVelocity[2]) + { //nothing to do if we have no velocity even after gravity. + VectorCopy(usedOrigin, goalSpot); + return true; + } + + //get the projected origin based on velocity. + VectorMA(usedOrigin, velScaling, bone.epVelocity, projectedOrigin); + + //scale it down based on mass + VectorScale(bone.epVelocity, 1.0f-mass, bone.epVelocity); + + VectorCopy(bone.epVelocity, vNorm); + vTotal = VectorNormalize(vNorm); + + if (vTotal < 1 && boneOnGround) + { //we've pretty much stopped moving anyway, just clear it out then. + VectorClear(bone.epVelocity); + bone.epGravFactor = 0; + VectorCopy(usedOrigin, goalSpot); + return true; + } + + Rag_Trace(&tr, usedOrigin, testMins, testMaxs, projectedOrigin, params->me, RAG_MASK, G2_NOCOLLIDE, 0); + + if (tr.startsolid || tr.allsolid) + { //can't go anywhere from here + return false; + } + + //Go ahead and set it to the trace endpoint regardless of what it hit + VectorCopy(tr.endpos, goalSpot); + + if (tr.fraction == 1.0f) + { //Nothing was in the way. + return true; + } + + if (bounce) + { + vTotal *= bounce; //scale it by bounce + + VectorScale(tr.plane.normal, vTotal, vNorm); //scale the trace plane normal by the bounce factor + + if (vNorm[2] > 0) + { + bone.epGravFactor -= vNorm[2]*(1.0f-mass); //The lighter it is the more gravity will be reduced by bouncing vertically. + if (bone.epGravFactor < 0) + { + bone.epGravFactor = 0; + } + } + + VectorAdd(bone.epVelocity, vNorm, bone.epVelocity); //add it into the existing velocity. + + //I suppose it could be sort of neat to make a game callback here to actual do stuff + //when bones slam into things. But it could be slow too. + /* + if (tr.entityNum != ENTITYNUM_NONE && ent->touch) + { //then call the touch function + ent->touch(ent, &g_entities[tr.entityNum], &tr); + } + */ + } + else + { //if no bounce, kill when it hits something. + bone.epVelocity[0] = 0; + bone.epVelocity[1] = 0; + + if (!gravity) + { + bone.epVelocity[2] = 0; + } + } + return true; +} + +#ifdef _DEBUG_BONE_NAMES +static inline void G2_RagDebugBox(vec3_t mins, vec3_t maxs, int duration) +{ + return; //do something +} + +static inline void G2_RagDebugLine(vec3_t start, vec3_t end, int time, int color, int radius) +{ + return; //do something +} +#endif + +#ifdef _OLD_STYLE_SETTLE +static bool G2_RagDollSettlePositionNumeroTrois(CGhoul2Info_v &ghoul2V, const vec3_t currentOrg, CRagDollUpdateParams *params, int curTime) +{ + haveDesiredPelvisOffset=false; + vec3_t desiredPos; + int i; + + assert(params); + //assert(params->me); //no longer valid, because me is an index! + int ignoreNum=params->me; + + bool anyStartSolid=false; + + vec3_t groundSpot={0,0,0}; + // lets find the floor at our quake origin + { + vec3_t testStart; + VectorCopy(currentOrg,testStart); //last arg is dest + vec3_t testEnd; + VectorCopy(testStart,testEnd); //last arg is dest + testEnd[2]-=200.0f; + + vec3_t testMins; + vec3_t testMaxs; + VectorSet(testMins,-10,-10,-10); + VectorSet(testMaxs,10,10,10); + + { + trace_t tr; + assert( !_isnan(testStart[1])); + assert( !_isnan(testEnd[1])); + assert( !_isnan(testMins[1])); + assert( !_isnan(testMaxs[1])); + Rag_Trace(&tr,testStart,testMins,testMaxs,testEnd,ignoreNum,RAG_MASK,G2_NOCOLLIDE,0/*SV_TRACE_NO_PLAYER*/); + if (tr.entityNum==0) + { + VectorAdvance(testStart,.5f,testEnd,tr.endpos); + } + if (tr.startsolid) + { + //hmmm, punt + VectorCopy(currentOrg,groundSpot); //last arg is dest + groundSpot[2]-=30.0f; + } + else + { + VectorCopy(tr.endpos,groundSpot); //last arg is dest + } + } + } + + for (i=0;imBlist, bone.boneNumber); + assert(debugBoneName); +#endif + // first we will see if we are start solid + // if so, we are gonna run some bonus iterations + bool iAmStartSolid=false; + vec3_t testStart; + VectorCopy(e.currentOrigin,testStart); //last arg is dest + testStart[2]+=12.0f; // we are no so concerned with minor penetration + + vec3_t testEnd; + VectorCopy(testStart,testEnd); //last arg is dest + testEnd[2]-=8.0f; + assert( !_isnan(testStart[1])); + assert( !_isnan(testEnd[1])); + assert( !_isnan(testMins[1])); + assert( !_isnan(testMaxs[1])); + float vertEffectorTraceFraction=0.0f; + { + trace_t tr; + Rag_Trace(&tr,testStart,testMins,testMaxs,testEnd,ignoreNum,RAG_MASK,G2_NOCOLLIDE,0); + if (tr.entityNum==0) + { + VectorAdvance(testStart,.5f,testEnd,tr.endpos); + } + if (tr.startsolid) + { + // above the origin, so lets try lower + if (e.currentOrigin[2] > groundSpot[2]) + { + testStart[2]=groundSpot[2]+(e.radius-10.0f); + } + else + { + // lets try higher + testStart[2]=groundSpot[2]+8.0f; + Rag_Trace(&tr,testStart,testMins,testMaxs,testEnd,ignoreNum,RAG_MASK,G2_NOCOLLIDE,0); + if (tr.entityNum==0) + { + VectorAdvance(testStart,.5f,testEnd,tr.endpos); + } + } + + } + if (tr.startsolid) + { + iAmStartSolid=true; + anyStartSolid=true; + // above the origin, so lets slide away + if (e.currentOrigin[2] > groundSpot[2]) + { + if (params) + { + SRagDollEffectorCollision args(e.currentOrigin,tr); + params->EffectorCollision(args); + } + } + else + { + //harumph, we are really screwed + } + } + else + { + vertEffectorTraceFraction=tr.fraction; + if (params && + vertEffectorTraceFraction < .95f && + fabsf(tr.plane.normal[2]) < .707f) + { + SRagDollEffectorCollision args(e.currentOrigin,tr); + args.useTracePlane=true; + params->EffectorCollision(args); + } + } + } + vec3_t effectorGroundSpot; + VectorAdvance(testStart,vertEffectorTraceFraction,testEnd,effectorGroundSpot);// VA(a,t,b,c)-> c := (1-t)a+tb + // trace from the quake origin horzontally to the effector + // gonna choose the maximum of the ground spot or the effector location + // and clamp it to be roughly in the bbox + VectorCopy(groundSpot,testStart); //last arg is dest + if (iAmStartSolid) + { + // we don't have a meaningful ground spot + VectorCopy(e.currentOrigin,testEnd); //last arg is dest + bone.solidCount++; + } + else + { + VectorCopy(effectorGroundSpot,testEnd); //last arg is dest + bone.solidCount = 0; + } + assert( !_isnan(testStart[1])); + assert( !_isnan(testEnd[1])); + assert( !_isnan(testMins[1])); + assert( !_isnan(testMaxs[1])); + + float ztest; + + if (testEnd[2]>testStart[2]) + { + ztest=testEnd[2]; + } + else + { + ztest=testStart[2]; + } + if (ztest c := (1-t)a+tb + + float horzontalTraceFraction=0.0f; + vec3_t HorizontalHitSpot={0,0,0}; + { + trace_t tr; + Rag_Trace(&tr,testStart,testMins,testMaxs,testEnd,ignoreNum,RAG_MASK,G2_NOCOLLIDE,0); + if (tr.entityNum==0) + { + VectorAdvance(testStart,.5f,testEnd,tr.endpos); + } + horzontalTraceFraction=tr.fraction; + if (tr.startsolid) + { + horzontalTraceFraction=1.0f; + // punt + VectorCopy(e.currentOrigin,HorizontalHitSpot); + } + else + { + VectorCopy(tr.endpos,HorizontalHitSpot); + int magicFactor46=0.98f; // shorten percetage to make sure we can go down along a wall + //float magicFactor46=0.98f; // shorten percetage to make sure we can go down along a wall + //rww - An..int? + VectorAdvance(tr.endpos,magicFactor46,testStart,HorizontalHitSpot);// VA(a,t,b,c)-> c := (1-t)a+tb + + // roughly speaking this is a wall + if (horzontalTraceFraction<0.9f) + { + + // roughly speaking this is a wall + if (fabsf(tr.plane.normal[2])<0.7f) + { + SRagDollEffectorCollision args(e.currentOrigin,tr); + args.useTracePlane=true; + params->EffectorCollision(args); + } + } + else if (!iAmStartSolid && + effectorGroundSpot[2] < groundSpot[2] - 8.0f) + { + // this is a situation where we have something dangling below the pelvis, we want to find the plane going downhill away from the origin + // for various reasons, without this correction the body will actually move away from places it can fall off. + //gotta run the trace backwards to get a plane + { + trace_t tr; + VectorCopy(effectorGroundSpot,testStart); + VectorCopy(groundSpot,testEnd); + + // this can be a line trace, we just want the plane normal + Rag_Trace(&tr,testEnd,0,0,testStart,ignoreNum,RAG_MASK,G2_NOCOLLIDE,0); + if (tr.entityNum==0) + { + VectorAdvance(testStart,.5f,testEnd,tr.endpos); + } + horzontalTraceFraction=tr.fraction; + if (!tr.startsolid && tr.fraction< 0.7f) + { + SRagDollEffectorCollision args(e.currentOrigin,tr); + args.useTracePlane=true; + params->EffectorCollision(args); + } + } + } + } + } + vec3_t goalSpot={0,0,0}; + // now lets trace down + VectorCopy(HorizontalHitSpot,testStart); + VectorCopy(testStart,testEnd); //last arg is dest + testEnd[2]=e.currentOrigin[2]-30.0f; + { + trace_t tr; + Rag_Trace(&tr,testStart,NULL,NULL,testEnd,ignoreNum,RAG_MASK,G2_NOCOLLIDE,0); + if (tr.entityNum==0) + { + VectorAdvance(testStart,.5f,testEnd,tr.endpos); + } + if (tr.startsolid) + { + // punt, go to the origin I guess + VectorCopy(currentOrg,goalSpot); + } + else + { + VectorCopy(tr.endpos,goalSpot); + int magicFactor47=0.5f; // shorten percentage to make sure we can go down along a wall + VectorAdvance(tr.endpos,magicFactor47,testStart,goalSpot);// VA(a,t,b,c)-> c := (1-t)a+tb + } + } + + // ok now as the horizontal trace fraction approaches zero, we want to head toward the horizontalHitSpot + //geeze I would like some reasonable trace fractions + assert(horzontalTraceFraction>=0.0f&&horzontalTraceFraction<=1.0f); + VectorAdvance(HorizontalHitSpot,horzontalTraceFraction*horzontalTraceFraction,goalSpot,goalSpot);// VA(a,t,b,c)-> c := (1-t)a+tb +#if 0 + if ((bone.RagFlags & RAG_EFFECTOR) && (bone.RagFlags & RAG_BONE_LIGHTWEIGHT)) + { //new rule - don't even bother unless it's a lightweight effector + //rww - Factor object velocity into the final desired spot.. + //We want the limbs with a "light" weight to drag behind the general mass. + //If we got here, we shouldn't be the pelvis or the root, so we should be + //fine to treat as lightweight. However, we can flag bones as being particularly + //light. They're given less downscale for the reduction factor. + vec3_t givenVelocity; + vec3_t vSpot; + trace_t vtr; + float vSpeed = 0; + float verticalSpeed = 0; + float vReductionFactor = 0.03f; + float verticalSpeedReductionFactor = 0.06f; //want this to be more obvious + float lwVReductionFactor = 0.1f; + float lwVerticalSpeedReductionFactor = 0.3f; //want this to be more obvious + + + VectorCopy(params->velocity, givenVelocity); + vSpeed = VectorNormalize(givenVelocity); + vSpeed = -vSpeed; //go in the opposite direction of velocity + + verticalSpeed = vSpeed; + + if (bone.RagFlags & RAG_BONE_LIGHTWEIGHT) + { + vSpeed *= lwVReductionFactor; + verticalSpeed *= lwVerticalSpeedReductionFactor; + } + else + { + vSpeed *= vReductionFactor; + verticalSpeed *= verticalSpeedReductionFactor; + } + + vSpot[0] = givenVelocity[0]*vSpeed; + vSpot[1] = givenVelocity[1]*vSpeed; + vSpot[2] = givenVelocity[2]*verticalSpeed; + VectorAdd(goalSpot, vSpot, vSpot); + + if (vSpot[0] || vSpot[1] || vSpot[2]) + { + Rag_Trace(&vtr, goalSpot, testMins, testMaxs, vSpot, ignoreNum, RAG_MASK, G2_NOCOLLIDE,0); + if (vtr.fraction == 1) + { + VectorCopy(vSpot, goalSpot); + } + } + } +#endif + + int k; + int magicFactor12=0.8f; // dampening of velocity applied + int magicFactor16=10.0f; // effect multiplier of velocity applied + + if (iAmStartSolid) + { + magicFactor16 = 30.0f; + } + + for (k=0;k<3;k++) + { + e.desiredDirection[k]=goalSpot[k]-e.currentOrigin[k]; + e.desiredDirection[k]+=magicFactor16*bone.velocityEffector[k]; + e.desiredDirection[k]+=flrand(-0.75f,0.75f)*flrand(-0.75f,0.75f); + bone.velocityEffector[k]*=magicFactor12; + } + VectorCopy(e.currentOrigin,bone.lastPosition); // last arg is dest + } + return anyStartSolid; +} +#else +#if 0 +static inline int G2_RagIndexForBoneNum(int boneNum) +{ + for (int i = 0; i < numRags; i++) + { + // these are used for affecting the end result + if (ragBoneData[i].boneNum == boneNum) + { + return i; + } + } + + return -1; +} +#endif + +extern mdxaBone_t worldMatrix; +void G2_RagGetBoneBasePoseMatrixLow(CGhoul2Info &ghoul2, int boneNum, mdxaBone_t &boneMatrix, mdxaBone_t &retMatrix, vec3_t scale); +void G2_RagGetAnimMatrix(CGhoul2Info &ghoul2, const int boneNum, mdxaBone_t &matrix, const int frame); + +static inline void G2_RagGetWorldAnimMatrix(CGhoul2Info &ghoul2, boneInfo_t &bone, CRagDollUpdateParams *params, mdxaBone_t &retMatrix) +{ + static mdxaBone_t trueBaseMatrix, baseBoneMatrix; + + //get matrix for the settleFrame to use as an ideal + G2_RagGetAnimMatrix(ghoul2, bone.boneNumber, trueBaseMatrix, params->settleFrame); + assert(bone.hasAnimFrameMatrix == params->settleFrame); + + G2_RagGetBoneBasePoseMatrixLow(ghoul2, bone.boneNumber, + trueBaseMatrix, baseBoneMatrix, params->scale); + + //Use params to multiply world coordinate/dir matrix into the + //bone matrix and give us a useable world position + Multiply_3x4Matrix(&retMatrix, &worldMatrix, &baseBoneMatrix); + + assert(!_isnan(retMatrix.matrix[2][3])); +} + +//get the current pelvis Z direction and the base anim matrix Z direction +//so they can be compared and used to offset -rww +static inline void G2_RagGetPelvisLumbarOffsets(CGhoul2Info &ghoul2, CRagDollUpdateParams *params, vec3_t &pos, vec3_t &dir, vec3_t &animPos, vec3_t &animDir) +{ + static mdxaBone_t final; + static mdxaBone_t x; +// static mdxaBone_t *unused1, *unused2; + //static vec3_t lumbarPos; + + assert(ghoul2.animModel); + int boneIndex = G2_Find_Bone(&ghoul2, ghoul2.mBlist, "pelvis"); + assert(boneIndex != -1); + + G2_RagGetWorldAnimMatrix(ghoul2, ghoul2.mBlist[boneIndex], params, final); + G2API_GiveMeVectorFromMatrix(final, ORIGIN, animPos); + G2API_GiveMeVectorFromMatrix(final, POSITIVE_X, animDir); + + //We have the anim matrix pelvis pos now, so get the normal one as well + int bolt = G2API_AddBolt(&ghoul2, "pelvis"); + G2_GetBoltMatrixLow(ghoul2, bolt, params->scale, x); + Multiply_3x4Matrix(&final, &worldMatrix, &x); + G2API_GiveMeVectorFromMatrix(final, ORIGIN, pos); + G2API_GiveMeVectorFromMatrix(final, POSITIVE_X, dir); + + /* + //now get lumbar + boneIndex = G2_Find_Bone(ghoul2.animModel, ghoul2.mBlist, "lower_lumbar"); + assert(boneIndex != -1); + + G2_RagGetWorldAnimMatrix(ghoul2, ghoul2.mBlist[boneIndex], params, final); + G2API_GiveMeVectorFromMatrix(&final, ORIGIN, lumbarPos); + + VectorSubtract(animPos, lumbarPos, animDir); + VectorNormalize(animDir); + + //We have the anim matrix lumbar dir now, so get the normal one as well + G2_GetBoneMatrixLow(ghoul2, boneIndex, params->scale, final, unused1, unused2); + G2API_GiveMeVectorFromMatrix(&final, ORIGIN, lumbarPos); + + VectorSubtract(pos, lumbarPos, dir); + VectorNormalize(dir); + */ +} + +static bool G2_RagDollSettlePositionNumeroTrois(CGhoul2Info_v &ghoul2V, const vec3_t currentOrg, CRagDollUpdateParams *params, int curTime) +{ //now returns true if any bone was in solid, otherwise false + int ignoreNum = params->me; + static int i; + static vec3_t goalSpot; + static trace_t tr; + static trace_t solidTr; + static int k; + static const float velocityDampening = 1.0f; + static const float velocityMultiplier = 60.0f; + static vec3_t testMins; + static vec3_t testMaxs; + vec3_t velDir; + static bool startSolid; + bool anySolid = false; + static mdxaBone_t worldBaseMatrix; + static vec3_t parentOrigin; + static vec3_t basePos; + static vec3_t entScale; + static bool hasDaddy; + static bool hasBasePos; + static vec3_t animPelvisDir, pelvisDir, animPelvisPos, pelvisPos; + + //Maybe customize per-bone? + static const float gravity = 3.0f; + static const float mass = 0.09f; + static const float bounce = 0.0f;//1.3f; + //Bouncing and stuff unfortunately does not work too well at the moment. + //Need to keep a seperate "physics origin" or make the filthy solve stuff + //better. + + bool inAir = false; + + if (params->velocity[0] || params->velocity[1] || params->velocity[2]) + { + inAir = true; + } + + if (!params->scale[0] && !params->scale[1] && !params->scale[2]) + { + VectorSet(entScale, 1.0f, 1.0f, 1.0f); + } + else + { + VectorCopy(params->scale, entScale); + } + + if (broadsword_ragtobase && + broadsword_ragtobase->integer > 1) + { + //grab the pelvis directions to offset base positions for bones + G2_RagGetPelvisLumbarOffsets(ghoul2V[0], params, pelvisPos, pelvisDir, animPelvisPos, + animPelvisDir); + + //don't care about the pitch offsets + pelvisDir[2] = 0; + animPelvisDir[2] = 0; + + /* + vec3_t blah; + VectorMA(pelvisPos, 32.0f, pelvisDir, blah); + //G2_RagDebugLine(pelvisPos, blah, 50, 0x00ff00, 1); + params->DebugLine(pelvisPos, blah, 0x00ff00, false); + VectorMA(animPelvisPos, 32.0f, animPelvisDir, blah); + //G2_RagDebugLine(animPelvisPos, blah, 50, 0xff0000, 1); + params->DebugLine(animPelvisPos, blah, 0xff0000, false); + */ + + //just convert to angles now, that's all we'll ever use them for + vectoangles(pelvisDir, pelvisDir); + vectoangles(animPelvisDir, animPelvisDir); + } + + for (i = 0; i < numRags; i++) + { + boneInfo_t &bone = *ragBoneData[i]; + SRagEffector &e = ragEffectors[i]; + + if (inAir) + { + bone.airTime = curTime + 30; + } + + if (bone.RagFlags & RAG_PCJ_PELVIS) + { + VectorSet(goalSpot, params->position[0], params->position[1], (params->position[2]+DEFAULT_MINS_2)+((bone.radius*entScale[2])+2)); + + VectorSubtract(goalSpot, e.currentOrigin, desiredPelvisOffset); + haveDesiredPelvisOffset = true; + VectorCopy(e.currentOrigin, bone.lastPosition); + continue; + } + + if (!(bone.RagFlags & RAG_EFFECTOR)) + { + continue; + } + + if (bone.hasOverGoal) + { //api call was made to override the goal spot + VectorCopy(bone.overGoalSpot, goalSpot); + bone.solidCount = 0; + for (k = 0; k < 3; k++) + { + e.desiredDirection[k] = (goalSpot[k] - e.currentOrigin[k]); + e.desiredDirection[k] += (velocityMultiplier * bone.velocityEffector[k]); + bone.velocityEffector[k] *= velocityDampening; + } + VectorCopy(e.currentOrigin, bone.lastPosition); + + continue; + } + + VectorSet(testMins, -e.radius*entScale[0], -e.radius*entScale[1], -e.radius*entScale[2]); + VectorSet(testMaxs, e.radius*entScale[0], e.radius*entScale[1], e.radius*entScale[2]); + + assert(ghoul2V[0].mBoneCache); + + //get the parent bone's position + hasDaddy = false; + if (bone.boneNumber) + { + assert(ghoul2V[0].animModel); + assert(ghoul2V[0].aHeader); + + if (bone.parentBoneIndex == -1) + { + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + int bParentIndex, bParentListIndex = -1; + + offsets = (mdxaSkelOffsets_t *)((byte *)ghoul2V[0].aHeader + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)ghoul2V[0].aHeader + sizeof(mdxaHeader_t) + offsets->offsets[bone.boneNumber]); + + bParentIndex = skel->parent; + + while (bParentIndex > 0) + { //go upward through hierarchy searching for the first parent that is a rag bone + skel = (mdxaSkel_t *)((byte *)ghoul2V[0].aHeader + sizeof(mdxaHeader_t) + offsets->offsets[bParentIndex]); + bParentIndex = skel->parent; + bParentListIndex = G2_Find_Bone(&ghoul2V[0], ghoul2V[0].mBlist, skel->name); + + if (bParentListIndex != -1) + { + boneInfo_t &pbone = ghoul2V[0].mBlist[bParentListIndex]; + if (pbone.flags & BONE_ANGLES_RAGDOLL) + { //valid rag bone + break; + } + } + + //didn't work out, reset to -1 again + bParentListIndex = -1; + } + + bone.parentBoneIndex = bParentListIndex; + } + + if (bone.parentBoneIndex != -1) + { + boneInfo_t &pbone = ghoul2V[0].mBlist[bone.parentBoneIndex]; + + if (pbone.flags & BONE_ANGLES_RAGDOLL) + { //has origin calculated for us already + VectorCopy(ragEffectors[pbone.ragIndex].currentOrigin, parentOrigin); + hasDaddy = true; + } + } + } + + //get the position this bone would be in if we were in the desired frame + hasBasePos = false; + if (broadsword_ragtobase && + broadsword_ragtobase->integer) + { + vec3_t v, a; + float f; + + G2_RagGetWorldAnimMatrix(ghoul2V[0], bone, params, worldBaseMatrix); + G2API_GiveMeVectorFromMatrix(worldBaseMatrix, ORIGIN, basePos); + + if (broadsword_ragtobase->integer > 1) + { + float fa = AngleNormalize180(animPelvisDir[YAW]-pelvisDir[YAW]); + float d = fa-bone.offsetRotation; + float tolerance = 16.0f; + + if (d > tolerance || + d < -tolerance) + { //don't update unless x degrees away from the ideal to avoid moving goal spots too much if pelvis rotates + bone.offsetRotation = fa; + } + else + { + fa = bone.offsetRotation; + } + //Rotate the point around the pelvis based on the offsets between pelvis positions + VectorSubtract(basePos, animPelvisPos, v); + f = VectorLength(v); + vectoangles(v, a); + a[YAW] -= fa; + AngleVectors(a, v, 0, 0); + VectorNormalize(v); + VectorMA(animPelvisPos, f, v, basePos); + + //re-orient the position of the bone to the current position of the pelvis + VectorSubtract(basePos, animPelvisPos, v); + //push the spots outward? (to stretch the skeleton more) + //v[0] *= 1.5f; + //v[1] *= 1.5f; + VectorAdd(pelvisPos, v, basePos); + } +#if 0 //for debugging frame skeleton + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + + offsets = (mdxaSkelOffsets_t *)((byte *)ghoul2V[0].aHeader + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)ghoul2V[0].aHeader + sizeof(mdxaHeader_t) + offsets->offsets[bone.boneNumber]); + + vec3_t pu; + VectorCopy(basePos, pu); + pu[2] += 32; + if (bone.boneNumber < 11) + { + params->DebugLine(basePos, pu, 0xff00ff, false); + //G2_RagDebugLine(basePos, pu, 50, 0xff00ff, 1); + } + else if (skel->name[0] == 'l') + { + params->DebugLine(basePos, pu, 0xffff00, false); + //G2_RagDebugLine(basePos, pu, 50, 0xffff00, 1); + } + else if (skel->name[0] == 'r') + { + params->DebugLine(basePos, pu, 0xffffff, false); + //G2_RagDebugLine(basePos, pu, 50, 0xffffff, 1); + } + else + { + params->DebugLine(basePos, pu, 0x00ffff, false); + //G2_RagDebugLine(basePos, pu, 50, 0x00ffff, 1); + } +#endif + hasBasePos = true; + } + + //Are we in solid? + if (hasDaddy) + { + Rag_Trace(&tr, e.currentOrigin, testMins, testMaxs, parentOrigin, ignoreNum, RAG_MASK, G2_NOCOLLIDE, 0); + //Rag_Trace(&tr, parentOrigin, testMins, testMaxs, e.currentOrigin, ignoreNum, RAG_MASK, G2_NOCOLLIDE, 0); + } + else + { + Rag_Trace(&tr, e.currentOrigin, testMins, testMaxs, params->position, ignoreNum, RAG_MASK, G2_NOCOLLIDE, 0); + } + + + if (tr.startsolid || tr.allsolid || tr.fraction != 1.0f) + { //currently in solid, see what we can do about it + vec3_t vSub; + + startSolid = true; + anySolid = true; + + if (hasBasePos)// && bone.solidCount < 32) + { //only go to the base pos for slightly in solid bones +#if 0 //over-compensation + float fl; + float floorBase; + + VectorSubtract(basePos, e.currentOrigin, vSub); + fl = VectorNormalize(vSub); + VectorMA(e.currentOrigin, /*fl*8.0f*/64.0f, vSub, goalSpot); + + floorBase = ((params->position[2]-23)-testMins[2])+8; + + if (goalSpot[2] > floorBase) + { + goalSpot[2] = floorBase; + } +#else + VectorCopy(basePos, goalSpot); + goalSpot[2] = (params->position[2]-23)-testMins[2]; +#endif + //Com_Printf("%i: %f %f %f\n", bone.boneNumber, basePos[0], basePos[1], basePos[2]); + } + else + { //if deep in solid want to try to rise up out of solid before hinting back to base + VectorSubtract(e.currentOrigin, params->position, vSub); + VectorNormalize(vSub); + VectorMA(params->position, 40.0f, vSub, goalSpot); + + //should be 1 unit above the ground taking bounding box sizes into account + goalSpot[2] = (params->position[2]-23)-testMins[2]; + } + + //Trace from the entity origin in the direction between the origin and current bone position to + //find a good eventual goal position + Rag_Trace(&tr, params->position, testMins, testMaxs, goalSpot, params->me, RAG_MASK, G2_NOCOLLIDE, 0); + VectorCopy(tr.endpos, goalSpot); + } + else + { + startSolid = false; + +#if 1 //do hinting? + //Hint the bone back to the base origin + if (hasDaddy || hasBasePos) + { + if (hasBasePos) + { + VectorSubtract(basePos, e.currentOrigin, velDir); + } + else + { + VectorSubtract(e.currentOrigin, parentOrigin, velDir); + } + } + else + { + VectorSubtract(e.currentOrigin, params->position, velDir); + } + + if (VectorLength(velDir) > 2.0f) + { //don't bother if already close + VectorNormalize(velDir); + VectorScale(velDir, 8.0f, velDir); + velDir[2] = 0; //don't want to nudge on Z, the gravity will take care of things. + VectorAdd(bone.epVelocity, velDir, bone.epVelocity); + } +#endif + + //Factor the object's velocity into the bone's velocity, by pushing the bone + //opposite the velocity to give the apperance the lighter limbs are being "dragged" + //behind those of greater mass. + if (bone.RagFlags & RAG_BONE_LIGHTWEIGHT) + { + vec3_t vel; + float vellen; + + VectorCopy(params->velocity, vel); + + //Scale down since our velocity scale is different from standard game physics + VectorScale(vel, 0.5f, vel); + + vellen = VectorLength(vel); + + if (vellen > 64.0f) + { //cap it off + VectorScale(vel, 64.0f/vellen, vel); + } + + //Invert the velocity so we go opposite the heavier parts and drag behind + VectorInverse(vel); + + if (vel[2]) + { //want to override entirely instead then + VectorCopy(vel, bone.epVelocity); + } + else + { + VectorAdd(bone.epVelocity, vel, bone.epVelocity); + } + } + + //We're not in solid so we can apply physics freely now. + if (!G2_ApplyRealBonePhysics(bone, e, params, goalSpot, NULL, testMins, testMaxs, + gravity, mass, bounce)) + { //if this is the case then somehow we failed to apply physics/get a good goal spot, just use the ent origin + VectorCopy(params->position, goalSpot); + } + } + + //Set this now so we know what to do for angle limiting + if (startSolid) + { + bone.solidCount++; + + /* + if (cgvm) + { //make a callback and see if the cgame wants to help us out + ragCallbackBoneInSolid_t *callData = (ragCallbackBoneInSolid_t *)cl.mSharedMemory; + + VectorCopy(e.currentOrigin, callData->bonePos); + callData->entNum = params->me; + callData->solidCount = bone.solidCount; + + VM_Call(cgvm, CG_RAG_CALLBACK, RAG_CALLBACK_BONEINSOLID); + } + */ + + Rag_Trace(&solidTr, params->position, testMins, testMaxs, e.currentOrigin, ignoreNum, RAG_MASK, G2_NOCOLLIDE, 0); + + if (solidTr.fraction != 1.0f && + (solidTr.plane.normal[0] || solidTr.plane.normal[1]) && + (solidTr.plane.normal[2] < 0.1f || solidTr.plane.normal[2] > -0.1f))// && //don't do anything against flat around + // e.currentOrigin[2] > pelvisPos[2]) + { //above pelvis, means not "even" with on ground level + SRagDollEffectorCollision args(e.currentOrigin,tr); + args.useTracePlane = false; + params->EffectorCollision(args); + } + +#ifdef _DEBUG_BONE_NAMES + if (bone.solidCount > 64) + { + char *debugBoneName = G2_Get_Bone_Name(&ghoul2V[0], ghoul2V[0].mBlist, bone.boneNumber); + vec3_t absmin, absmax; + + assert(debugBoneName); + + Com_Printf("High bone (%s, %i) solid count: %i\n", debugBoneName, bone.boneNumber, bone.solidCount); + + VectorAdd(e.currentOrigin, testMins, absmin); + VectorAdd(e.currentOrigin, testMaxs, absmax); + G2_RagDebugBox(absmin, absmax, 50); + + G2_RagDebugLine(e.currentOrigin, goalSpot, 50, 0x00ff00, 1); + } +#endif + } + else + { + bone.solidCount = 0; + } + +#if 0 //standard goalSpot capping? + //unless we are really in solid, we should keep adjustments minimal + if (/*bone.epGravFactor < 64 &&*/ bone.solidCount < 2 && + !inAir) + { + vec3_t moveDist; + const float extent = 32.0f; + float len; + + VectorSubtract(goalSpot, e.currentOrigin, moveDist); + len = VectorLength(moveDist); + + if (len > extent) + { //if greater than the extent then scale the vector down to the extent and factor it back into the goalspot + VectorScale(moveDist, extent/len, moveDist); + VectorAdd(e.currentOrigin, moveDist, goalSpot); + } + } +#endif + + //params->DebugLine(e.currentOrigin, goalSpot, 0xff0000, false); + + //Set the desired direction based on the goal position and other factors. + for (k = 0; k < 3; k++) + { + e.desiredDirection[k] = (goalSpot[k] - e.currentOrigin[k]); + + if (broadsword_dircap && + broadsword_dircap->value) + { + float cap = broadsword_dircap->value; + + if (bone.solidCount > 5) + { + float solidFactor = bone.solidCount*0.2f; + + if (solidFactor > 16.0f) + { //don't go too high or something ugly might happen + solidFactor = 16.0f; + } + + e.desiredDirection[k] *= solidFactor; + cap *= 8; + } + + if (e.desiredDirection[k] > cap) + { + e.desiredDirection[k] = cap; + } + else if (e.desiredDirection[k] < -cap) + { + e.desiredDirection[k] = -cap; + } + } + + e.desiredDirection[k] += (velocityMultiplier * bone.velocityEffector[k]); + e.desiredDirection[k] += (flrand(-0.75f, 0.75f) * flrand(-0.75f, 0.75f)); + + bone.velocityEffector[k] *= velocityDampening; + } + VectorCopy(e.currentOrigin, bone.lastPosition); + } + + return anySolid; +} +#endif + +static float AngleNormZero(float theta) +{ + float ret=fmodf(theta,360.0f); + if (ret<-180.0f) + { + ret+=360.0f; + } + else if (ret>180.0f) + { + ret-=360.0f; + } + assert(ret>=-180.0f&&ret<=180.0f); + return ret; +} + +static inline void G2_BoneSnap(CGhoul2Info_v &ghoul2V, boneInfo_t &bone, CRagDollUpdateParams *params) +{ + /* + if (!cgvm || !params) + { + return; + } + + ragCallbackBoneSnap_t *callData = (ragCallbackBoneSnap_t *)cl.mSharedMemory; + + callData->entNum = params->me; + strcpy(callData->boneName, G2_Get_Bone_Name(&ghoul2V[0], ghoul2V[0].mBlist, bone.boneNumber)); + + VM_Call(cgvm, CG_RAG_CALLBACK, RAG_CALLBACK_BONESNAP); + */ + //fixme: add params callback I suppose +} + +static void G2_RagDollSolve(CGhoul2Info_v &ghoul2V,int g2Index,float decay,int frameNum,const vec3_t currentOrg,bool limitAngles,CRagDollUpdateParams *params) +{ + + int i; + + CGhoul2Info &ghoul2=ghoul2V[g2Index]; + + mdxaBone_t N; + mdxaBone_t P; + mdxaBone_t temp1; + mdxaBone_t temp2; + mdxaBone_t curRot; + mdxaBone_t curRotInv; + mdxaBone_t Gs[3]; + mdxaBone_t Enew[3]; + + assert(ghoul2.mFileName[0]); + boneInfo_v &blist = ghoul2.mBlist; + + + // END this is the objective function thing + for (i=0;igroundEnt != ENTITYNUM_NONE) + { + magicFactor13 = 0.2f; + } + */ + + assert( !_isnan(bone.ragOverrideMatrix.matrix[2][3])); + vec3_t deltaInEntitySpace; + TransformPoint(desiredPelvisOffset,deltaInEntitySpace,&N); // dest middle arg + for (k=0;k<3;k++) + { + float moveTo=bone.velocityRoot[k]+deltaInEntitySpace[k]*magicFactor13; + bone.velocityRoot[k]=(bone.velocityRoot[k]-moveTo)*magicFactor12+moveTo; + /* + if (bone.velocityRoot[k]>50.0f) + { + bone.velocityRoot[k]=50.0f; + } + if (bone.velocityRoot[k]<-50.0f) + { + bone.velocityRoot[k]=-50.0f; + } + */ + //No -rww + bone.ragOverrideMatrix.matrix[k][3]=bone.velocityRoot[k]; + } + } + } + else + { + vec3_t delAngles; + VectorClear(delAngles); + + for (k=0;k<3;k++) + { + tAngles[k]+=0.5f; + Create_Matrix(tAngles,&temp2); //dest 2nd arg + tAngles[k]-=0.5f; + Multiply_3x4Matrix(&temp1,&P,&temp2); //dest first arg + Multiply_3x4Matrix(&Gs[k],&temp1,&N); //dest first arg + + } + + int allSolidCount = 0;//bone.solidCount; + + // fixme precompute this + int numDep=G2_GetBoneDependents(ghoul2,bone.boneNumber,tempDependents,MAX_BONES_RAG); + int j; + int numRagDep=0; + for (j=0;jragIndex; + assert(depIndex>i); // these are supposed to be topologically sorted + assert(ragBoneData[depIndex]); + boneInfo_t &depBone=*ragBoneData[depIndex]; + if (depBone.RagFlags & RAG_EFFECTOR) // rag doll effector + { + // this is a dependent of me, and also a rag + numRagDep++; + for (k=0;k<3;k++) + { + Multiply_3x4Matrix(&Enew[k],&Gs[k],&ragBones[depIndex]); //dest first arg + vec3_t tPosition; + tPosition[0]=Enew[k].matrix[0][3]; + tPosition[1]=Enew[k].matrix[1][3]; + tPosition[2]=Enew[k].matrix[2][3]; + + vec3_t change; + VectorSubtract(tPosition,ragEffectors[depIndex].currentOrigin,change); // dest is last arg + float goodness=DotProduct(change,ragEffectors[depIndex].desiredDirection); + assert( !_isnan(goodness)); + goodness*=depBone.weight; + delAngles[k]+=goodness; // keep bigger stuff more out of wall or something + assert( !_isnan(delAngles[k])); + } + allSolidCount += depBone.solidCount; + } + } + + allSolidCount += bone.solidCount; + + VectorCopy(bone.currentAngles,bone.lastAngles); + // Update angles + float magicFactor9=0.75f; // dampfactor for angle changes + float magicFactor1=0.40f; //controls the speed of the gradient descent + float magicFactor32 = 1.5f; + float recip=0.0f; + if (numRagDep) + { + recip=sqrt(4.0f/float(numRagDep)); + } + + if (allSolidCount > 32) + { + magicFactor1 = 0.6f; + } + else if (allSolidCount > 10) + { + magicFactor1 = 0.5f; + } + + if (bone.overGradSpeed) + { //api call was made to specify a speed for this bone + magicFactor1 = bone.overGradSpeed; + } + + float fac=decay*recip*magicFactor1; + assert(fac>=0.0f); +#if 0 + if (bone.RagFlags & RAG_PCJ_PELVIS) + { + magicFactor9=.85f; // we don't want this swinging radically, make the whole thing kindof unstable + } +#endif + if (ragState==ERS_DYNAMIC) + { + magicFactor9=.85f; // we don't want this swinging radically, make the whole thing kindof unstable + } + +#if 1 //constraint breaks? + if (bone.RagFlags & RAG_UNSNAPPABLE) + { + magicFactor32 = 1.0f; + } +#endif + + for (k=0;k<3;k++) + { + bone.currentAngles[k]+=delAngles[k]*fac; + + bone.currentAngles[k]=(bone.lastAngles[k]-bone.currentAngles[k])*magicFactor9 + bone.currentAngles[k]; + bone.currentAngles[k]=AngleNormZero(bone.currentAngles[k]); +// bone.currentAngles[k]=flrand(bone.minAngles[k],bone.maxAngles[k]); +#if 1 //constraint breaks? + if (limitAngles && ( allSolidCount < 16 || (bone.RagFlags & RAG_UNSNAPPABLE) )) //16 tries and still in solid? Then we'll let you move freely +#else + if (limitAngles) +#endif + { + if (!bone.snapped || (bone.RagFlags & RAG_UNSNAPPABLE)) + { + //magicFactor32 += (allSolidCount/32); + + if (bone.currentAngles[k]>bone.maxAngles[k]*magicFactor32) + { + bone.currentAngles[k]=bone.maxAngles[k]*magicFactor32; + } + if (bone.currentAngles[k]bone.maxAngles[k]*magicFactor32) + { + isSnapped = true; + break; + } + if (bone.currentAngles[k]ragIndex; + if (!ragBoneData[depIndex]) + { + continue; + } + boneInfo_t &depBone=*ragBoneData[depIndex]; + + if (depBone.RagFlags & RAG_EFFECTOR) + { + // this is a dependent of me, and also a rag + numRagDep++; + for (k=0;k<3;k++) + { + Multiply_3x4Matrix(&Enew[k],&Gs[k],&ragBones[depIndex]); //dest first arg + vec3_t tPosition; + tPosition[0]=Enew[k].matrix[0][3]; + tPosition[1]=Enew[k].matrix[1][3]; + tPosition[2]=Enew[k].matrix[2][3]; + + vec3_t change; + VectorSubtract(tPosition,ragEffectors[depIndex].currentOrigin,change); // dest is last arg + float goodness=DotProduct(change,ragEffectors[depIndex].desiredDirection); + assert( !_isnan(goodness)); + goodness*=depBone.weight; + delAngles[k]+=goodness; // keep bigger stuff more out of wall or something + assert( !_isnan(delAngles[k])); + } + } + } + + VectorCopy(bone.currentAngles, bone.lastAngles); + + // Update angles + float magicFactor9 = 0.75f; // dampfactor for angle changes + float magicFactor1 = bone.ikSpeed; //controls the speed of the gradient descent + float magicFactor32 = 1.0f; + float recip = 0.0f; + bool freeThisBone = false; + + if (!magicFactor1) + { + magicFactor1 = 0.40f; + } + + recip = sqrt(4.0f/1.0f); + + float fac = (decay*recip*magicFactor1); + assert(fac >= 0.0f); + + if (ragState == ERS_DYNAMIC) + { + magicFactor9 = 0.85f; // we don't want this swinging radically, make the whole thing kindof unstable + } + + + if (!bone.maxAngles[0] && !bone.maxAngles[1] && !bone.maxAngles[2] && + !bone.minAngles[0] && !bone.minAngles[1] && !bone.minAngles[2]) + { + freeThisBone = true; + } + + for (k = 0; k < 3; k++) + { + bone.currentAngles[k] += delAngles[k]*fac; + + bone.currentAngles[k] = (bone.lastAngles[k]-bone.currentAngles[k])*magicFactor9 + bone.currentAngles[k]; + bone.currentAngles[k] = AngleNormZero(bone.currentAngles[k]); + if (limitAngles && !freeThisBone) + { + if (bone.currentAngles[k] > bone.maxAngles[k]*magicFactor32) + { + bone.currentAngles[k] = bone.maxAngles[k]*magicFactor32; + } + if (bone.currentAngles[k] < bone.minAngles[k]*magicFactor32) + { + bone.currentAngles[k] = bone.minAngles[k]*magicFactor32; + } + } + } + Create_Matrix(bone.currentAngles, &temp1); + Multiply_3x4Matrix(&temp2, &temp1, bone.baseposeInv); + Multiply_3x4Matrix(&bone.ragOverrideMatrix, bone.basepose, &temp2); + assert( !_isnan(bone.ragOverrideMatrix.matrix[2][3])); + + G2_Generate_MatrixRag(blist, ragBlistIndex[bone.boneNumber]); + } +} + +static void G2_DoIK(CGhoul2Info_v &ghoul2V,int g2Index,CRagDollUpdateParams *params) +{ + int i; + + if (!params) + { + assert(0); + return; + } + + int frameNum=G2API_GetTime(0); + CGhoul2Info &ghoul2=ghoul2V[g2Index]; + assert(ghoul2.mFileName[0]); + + float decay=1.0f; + bool resetOrigin=false; + bool anyRendered=false; + + int iters = 12; //since we don't trace or anything, we can afford this. + + if (iters) + { + if (!G2_RagDollSetup(ghoul2,frameNum,resetOrigin,params->position,anyRendered)) + { + return; + } + + // ok, now our data structures are compact and set up in topological order + for (i=0;iangles,params->position,params->scale); + + G2_IKReposition(params->position, params); + + G2_IKSolve(ghoul2V,g2Index,decay*2.0f,frameNum,params->position,true); + } + } + + if (params->me != ENTITYNUM_NONE) + { + G2_RagDollCurrentPosition(ghoul2V,g2Index,frameNum,params->angles,params->position,params->scale); + } +} + +//rww - cut out the entire non-ragdoll section of this.. +void G2_Animate_Bone_List(CGhoul2Info_v &ghoul2, const int currentTime, const int index,CRagDollUpdateParams *params) +{ + int i; + bool anyRagDoll=false; + bool anyIK = false; + for(i=0; iangles, parms->position); + G2_ConstructGhoulSkeleton(ghoul2V, curTime, false, parms->scale); + + // new base anim, unconscious flop + int pcjFlags; +#if 0 + vec3_t pcjMin, pcjMax; + VectorClear(pcjMin); + VectorClear(pcjMax); + + pcjFlags=RAG_PCJ|RAG_PCJ_POST_MULT;//|RAG_EFFECTOR; + + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"model_root",RAG_PCJ_MODEL_ROOT|RAG_PCJ,10.0f,pcjMin,pcjMax,100); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"pelvis",RAG_PCJ_PELVIS|RAG_PCJ|RAG_PCJ_POST_MULT,10.0f,pcjMin,pcjMax,100); + + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"lower_lumbar",pcjFlags,10.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"upper_lumbar",pcjFlags,10.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"thoracic",pcjFlags|RAG_EFFECTOR,12.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"cranium",pcjFlags,6.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"rhumerus",pcjFlags,4.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"lhumerus",pcjFlags,4.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"rradius",pcjFlags,3.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"lradius",pcjFlags,3.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"rfemurYZ",pcjFlags,6.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"lfemurYZ",pcjFlags,6.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"rtibia",pcjFlags,4.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"ltibia",pcjFlags,4.0f,pcjMin,pcjMax,500); + + G2_ConstructGhoulSkeleton(ghoul2V, curTime, false, parms->scale); +#endif + //Only need the standard effectors for this. + pcjFlags = RAG_PCJ|RAG_PCJ_POST_MULT|RAG_EFFECTOR; + + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"rhand",pcjFlags,6.0f); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"lhand",pcjFlags,6.0f); +// G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"rtarsal",pcjFlags,4.0f); +// G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"ltarsal",pcjFlags,4.0f); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"rtibia",pcjFlags,4.0f); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"ltibia",pcjFlags,4.0f); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"rtalus",pcjFlags,4.0f); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"ltalus",pcjFlags,4.0f); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"rradiusX",pcjFlags,6.0f); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"lradiusX",pcjFlags,6.0f); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"rfemurX",pcjFlags,10.0f); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"lfemurX",pcjFlags,10.0f); + G2_Set_Bone_Angles_IK(ghoul2, mod_a,blist,"ceyebrow",pcjFlags,10.0f); +} + +qboolean G2_SetBoneIKState(CGhoul2Info_v &ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params) +{ + model_t *mod_a; + int g2index = 0; + int curTime = time; + CGhoul2Info &g2 = ghoul2[g2index]; + const mdxaHeader_t *rmod_a = G2_GetModA(g2); + + boneInfo_v &blist = g2.mBlist; + mod_a = (model_t *)g2.animModel; + + if (!boneName) + { //null bonename param means it's time to init the ik stuff on this instance + sharedRagDollUpdateParams_t sRDUP; + + if (ikState == IKS_NONE) + { //this means we want to reset the IK state completely.. run through the bone list, and reset all the appropriate flags + int i = 0; + while (i < blist.size()) + { //we can't use this method for ragdoll. However, since we expect them to set their anims/angles again on the PCJ + //limb after they reset it gameside, it's reasonable for IK bones. + boneInfo_t &bone = blist[i]; + if (bone.boneNumber != -1) + { + bone.flags &= ~BONE_ANGLES_RAGDOLL; + bone.flags &= ~BONE_ANGLES_IK; + bone.RagFlags = 0; + bone.lastTimeUpdated = 0; + } + i++; + } + return qtrue; + } + assert(params); + + if (!params) + { + return qfalse; + } + + sRDUP.me = 0; + VectorCopy(params->angles, sRDUP.angles); + VectorCopy(params->origin, sRDUP.position); + VectorCopy(params->scale, sRDUP.scale); + VectorClear(sRDUP.velocity); + G2_InitIK(ghoul2, &sRDUP, curTime, rmod_a, g2index); + return qtrue; + } + + if (!rmod_a || !mod_a) + { + return qfalse; + } + + int index = G2_Find_Bone(&g2, blist, boneName); + + if (index == -1) + { + index = G2_Add_Bone(mod_a, blist, boneName); + } + + if (index == -1) + { //couldn't find or add the bone.. + return qfalse; + } + + boneInfo_t &bone = blist[index]; + + if (ikState == IKS_NONE) + { //remove the bone from the list then, so it has to reinit. I don't think this should hurt anything since + //we don't store bone index handles gameside anywhere. + if (!(bone.flags & BONE_ANGLES_RAGDOLL)) + { //you can't set the ik state to none if it's not a rag/ik bone. + return qfalse; + } + //bone.flags = 0; + //G2_Remove_Bone_Index(blist, index); + //actually, I want to keep it on the rag list, and remove it as an IK bone instead. + bone.flags &= ~BONE_ANGLES_RAGDOLL; + bone.flags |= BONE_ANGLES_IK; + bone.RagFlags &= ~RAG_PCJ_IK_CONTROLLED; + return qtrue; + } + + //need params if we're not resetting. + if (!params) + { + assert(0); + return qfalse; + } + + /* + if (bone.flags & BONE_ANGLES_RAGDOLL) + { //otherwise if the bone is already flagged as rag, then we can't set it again. (non-active ik bones will be BONE_ANGLES_IK, active are considered rag) + return qfalse; + } + */ +#if 0 //this is wrong now.. we're only initing effectors with initik now.. which SHOULDN'T be used as pcj's + if (!(bone.flags & BONE_ANGLES_IK) && !(bone.flags & BONE_ANGLES_RAGDOLL)) + { //IK system has not been inited yet, because any bone that can be IK should be in the ragdoll list, not flagged as BONE_ANGLES_RAGDOLL but as BONE_ANGLES_IK + sharedRagDollUpdateParams_t sRDUP; + sRDUP.me = 0; + VectorCopy(params->angles, sRDUP.angles); + VectorCopy(params->origin, sRDUP.position); + VectorCopy(params->scale, sRDUP.scale); + VectorClear(sRDUP.velocity); + G2_InitIK(ghoul2, &sRDUP, curTime, rmod_a, g2index); + + G2_ConstructGhoulSkeleton(ghoul2, curTime, false, params->scale); + } + else + { + G2_GenerateWorldMatrix(params->angles, params->origin); + G2_ConstructGhoulSkeleton(ghoul2, curTime, false, params->scale); + } +#else + G2_GenerateWorldMatrix(params->angles, params->origin); + G2_ConstructGhoulSkeleton(ghoul2, curTime, false, params->scale); +#endif + + int pcjFlags = RAG_PCJ|RAG_PCJ_IK_CONTROLLED|RAG_PCJ_POST_MULT|RAG_EFFECTOR; + + if (params->pcjOverrides) + { + pcjFlags = params->pcjOverrides; + } + + bone.ikSpeed = 0.4f; + VectorClear(bone.ikPosition); + + G2_Set_Bone_Rag(rmod_a, blist, boneName, g2, params->scale, params->origin); + + int startFrame = params->startFrame, endFrame = params->endFrame; + + G2_Set_Bone_Anim_No_BS(g2, rmod_a, blist, boneName, startFrame, endFrame-1, + BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f, curTime, float(startFrame), 150, 0, true); + + G2_ConstructGhoulSkeleton(ghoul2, curTime, false, params->scale); + + bone.lastTimeUpdated = 0; + G2_Set_Bone_Angles_Rag(g2, rmod_a, blist, boneName, pcjFlags, params->radius, params->pcjMins, params->pcjMaxs, params->blendTime); + + if (!G2_RagDollSetup(g2,curTime,true,params->origin,false)) + { + assert(!"failed to add any rag bones"); + return qfalse; + } + + return qtrue; +} + +qboolean G2_IKMove(CGhoul2Info_v &ghoul2, int time, sharedIKMoveParams_t *params) +{ +#if 0 + model_t *mod_a; + int g2index = 0; + int curTime = time; + CGhoul2Info &g2 = ghoul2[g2index]; + + boneInfo_v &blist = g2.mBlist; + mod_a = (model_t *)g2.animModel; + + if (!mod_a) + { + return qfalse; + } + + int index = G2_Find_Bone(mod_a, blist, params->boneName); + + //don't add here if you can't find it.. ik bones should already be there, because they need to have special stuff done to them anyway. + if (index == -1) + { //couldn't find the bone.. + return qfalse; + } + + if (!params) + { + assert(0); + return qfalse; + } + + if (!(blist[index].flags & BONE_ANGLES_RAGDOLL) && !(blist[index].flags & BONE_ANGLES_IK)) + { //no-can-do, buddy + return qfalse; + } + + VectorCopy(params->desiredOrigin, blist[index].ikPosition); + blist[index].ikSpeed = params->movementSpeed; +#else + int g2index = 0; + int curTime = time; + CGhoul2Info &g2 = ghoul2[g2index]; + + //rwwFIXMEFIXME: Doing this on all bones at the moment, fix this later? + if (!G2_RagDollSetup(g2,curTime,true,params->origin,false)) + { //changed models, possibly. + return qfalse; + } + + for (int i=0;idesiredOrigin, bone.ikPosition); + bone.ikSpeed = params->movementSpeed; + } + } +#endif + return qtrue; +} + +// set the bone list to all unused so the bone transformation routine ignores it. +void G2_Init_Bone_List(boneInfo_v &blist) +{ + blist.clear(); +} + +int G2_Get_Bone_Index(CGhoul2Info *ghoul2, const char *boneName, qboolean bAddIfNotFound) +{ + if (bAddIfNotFound) + { + return G2_Add_Bone(ghoul2->animModel, ghoul2->mBlist, boneName); + } + else + { + return G2_Find_Bone(ghoul2, ghoul2->mBlist, boneName); + } +} + + +void G2_FreeRag(void) +{ + if(rag) { + delete rag; + rag = NULL; + } +} diff --git a/code/ghoul2/G2_misc.cpp b/code/ghoul2/G2_misc.cpp new file mode 100644 index 0000000..fac70b4 --- /dev/null +++ b/code/ghoul2/G2_misc.cpp @@ -0,0 +1,1873 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#ifndef __Q_SHARED_H + #include "../game/q_shared.h" +#endif + +#if !defined(TR_LOCAL_H) + #include "../renderer/tr_local.h" +#endif + +#include "../renderer/MatComp.h" + +#if !defined(G2_H_INC) + #include "G2.h" +#endif + +#if !defined (MINIHEAP_H_INC) + #include "../qcommon/miniheap.h" +#endif + +#define G2_MODEL_OK(g) ((g)&&(g)->mValid&&(g)->aHeader&&(g)->currentModel&&(g)->animModel) + +#include "../server/server.h" + +#include + +#ifdef _G2_GORE +#include "ghoul2_gore.h" + +#define GORE_TAG_UPPER (256) +#define GORE_TAG_MASK (~255) + +static int CurrentTag=GORE_TAG_UPPER+1; +static int CurrentTagUpper=GORE_TAG_UPPER; + +static map GoreRecords; +static map,int> GoreTagsTemp; // this is a surface index to gore tag map used only + // temporarily during the generation phase so we reuse gore tags per LOD +int goreModelIndex; + +bool AddGoreToAllModels=false; + +GoreTextureCoordinates *FindGoreRecord(int tag); +static inline void DestroyGoreTexCoordinates(int tag) +{ + GoreTextureCoordinates *gTC = FindGoreRecord(tag); + if (!gTC) + { + return; + } + (*gTC).~GoreTextureCoordinates(); + //I don't know what's going on here, it should call the destructor for + //this when it erases the record but sometimes it doesn't. -rww +} + +//TODO: This needs to be set via a scalability cvar with some reasonable minimum value if pgore is used at all +#define MAX_GORE_RECORDS (500) + +int AllocGoreRecord() +{ + while (GoreRecords.size()>MAX_GORE_RECORDS) + { + int tagHigh=(*GoreRecords.begin()).first&GORE_TAG_MASK; + map::iterator it; + GoreTextureCoordinates *gTC; + + it = GoreRecords.begin(); + gTC = &(*it).second; + + if (gTC) + { + gTC->~GoreTextureCoordinates(); + } + GoreRecords.erase(GoreRecords.begin()); + while (GoreRecords.size()) + { + if (((*GoreRecords.begin()).first&GORE_TAG_MASK)!=tagHigh) + { + break; + } + it = GoreRecords.begin(); + gTC = &(*it).second; + + if (gTC) + { + gTC->~GoreTextureCoordinates(); + } + GoreRecords.erase(GoreRecords.begin()); + } + } + int ret=CurrentTag; + GoreRecords[CurrentTag]=GoreTextureCoordinates(); + CurrentTag++; + return ret; +} + +void ResetGoreTag() +{ + GoreTagsTemp.clear(); + CurrentTag=CurrentTagUpper; + CurrentTagUpper+=GORE_TAG_UPPER; +} + +GoreTextureCoordinates *FindGoreRecord(int tag) +{ + map::iterator i=GoreRecords.find(tag); + if (i!=GoreRecords.end()) + { + return &(*i).second; + } + return 0; +} + +void *G2_GetGoreRecord(int tag) +{ + return FindGoreRecord(tag); +} + +void DeleteGoreRecord(int tag) +{ + DestroyGoreTexCoordinates(tag); + GoreRecords.erase(tag); +} + +static int CurrentGoreSet=1; // this is a UUID for gore sets +static map GoreSets; // map from uuid to goreset + +CGoreSet *FindGoreSet(int goreSetTag) +{ + map::iterator f=GoreSets.find(goreSetTag); + if (f!=GoreSets.end()) + { + return (*f).second; + } + return 0; +} + +CGoreSet *NewGoreSet() +{ + CGoreSet *ret=new CGoreSet(CurrentGoreSet++); + GoreSets[ret->mMyGoreSetTag]=ret; + ret->mRefCount = 1; + return ret; +} + +void DeleteGoreSet(int goreSetTag) +{ + map::iterator f=GoreSets.find(goreSetTag); + if (f!=GoreSets.end()) + { + if ( (*f).second->mRefCount == 0 || (*f).second->mRefCount - 1 == 0 ) + { + delete (*f).second; + GoreSets.erase(f); + } + else + { + (*f).second->mRefCount--; + } + } +} + + +CGoreSet::~CGoreSet() +{ + multimap::iterator i; + for (i=mGoreRecords.begin();i!=mGoreRecords.end();i++) + { + DeleteGoreRecord((*i).second.mGoreTag); + } +}; +#endif + +extern mdxaBone_t worldMatrix; +extern mdxaBone_t worldMatrixInv; + +const mdxaBone_t &EvalBoneCache(int index,CBoneCache *boneCache); + +#pragma warning(disable : 4512) //assignment op could not be genereated +class CTraceSurface +{ +public: + int surfaceNum; + surfaceInfo_v &rootSList; + const model_t *currentModel; + const int lod; + vec3_t rayStart; + vec3_t rayEnd; + CCollisionRecord *collRecMap; + const int entNum; + const int modelIndex; + const skin_t *skin; + const shader_t *cust_shader; + int *TransformedVertsArray; + const EG2_Collision eG2TraceType; + bool hitOne; + float m_fRadius; + +#ifdef _G2_GORE + //gore application thing + float ssize; + float tsize; + float theta; + int goreShader; + CGhoul2Info *ghoul2info; + + // Procedural-gore application things + SSkinGoreData *gore; +#endif + + CTraceSurface( + int initsurfaceNum, + surfaceInfo_v &initrootSList, + const model_t *initcurrentModel, + int initlod, + vec3_t initrayStart, + vec3_t initrayEnd, + CCollisionRecord *initcollRecMap, + int initentNum, + int initmodelIndex, + const skin_t *initskin, + const shader_t *initcust_shader, + int *initTransformedVertsArray, + const EG2_Collision einitG2TraceType, +#ifdef _G2_GORE + float fRadius, + float initssize, + float inittsize, + float inittheta, + int initgoreShader, + CGhoul2Info *initghoul2info, + SSkinGoreData *initgore): +#else + float fRadius): +#endif ): + + surfaceNum(initsurfaceNum), + rootSList(initrootSList), + currentModel(initcurrentModel), + lod(initlod), + collRecMap(initcollRecMap), + entNum(initentNum), + modelIndex(initmodelIndex), + skin(initskin), + cust_shader(initcust_shader), + eG2TraceType(einitG2TraceType), + hitOne(false), + TransformedVertsArray(initTransformedVertsArray), +#ifdef _G2_GORE + m_fRadius(fRadius), + ssize(initssize), + tsize(inittsize), + theta(inittheta), + goreShader(initgoreShader), + ghoul2info(initghoul2info), + gore(initgore) +#else + m_fRadius(fRadius) +#endif + { + VectorCopy(initrayStart, rayStart); + VectorCopy(initrayEnd, rayEnd); + } +}; + +// assorted Ghoul 2 functions. +// list all surfaces associated with a model +void G2_List_Model_Surfaces(const char *fileName) +{ + int i, x; + model_t *mod_m = R_GetModelByHandle(RE_RegisterModel(fileName)); + mdxmSurfHierarchy_t *surf; + + surf = (mdxmSurfHierarchy_t *) ( (byte *)mod_m->mdxm + mod_m->mdxm->ofsSurfHierarchy ); + mdxmSurface_t *surface = (mdxmSurface_t *)((byte *)mod_m->mdxm + mod_m->mdxm->ofsLODs + sizeof(mdxmLOD_t)); + + for ( x = 0 ; x < mod_m->mdxm->numSurfaces ; x++) + { + Com_Printf("Surface %i Name %s\n", x, surf->name); + if (r_verbose->value) + { + Com_Printf("Num Descendants %i\n", surf->numChildren); + for (i=0; inumChildren; i++) + { + Com_Printf("Descendant %i\n", surf->childIndexes[i]); + } + } + // find the next surface + surf = (mdxmSurfHierarchy_t *)( (byte *)surf + (int)( &((mdxmSurfHierarchy_t *)0)->childIndexes[ surf->numChildren ] )); + surface =(mdxmSurface_t *)( (byte *)surface + surface->ofsEnd ); + } + +} + +// list all bones associated with a model +void G2_List_Model_Bones(const char *fileName, int frame) +{ + int x, i; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + model_t *mod_m = R_GetModelByHandle(RE_RegisterModel(fileName)); + model_t *mod_a = R_GetModelByHandle(mod_m->mdxm->animIndex); +// mdxaFrame_t *aframe=0; +// int frameSize; + mdxaHeader_t *header = mod_a->mdxa; + + // figure out where the offset list is + offsets = (mdxaSkelOffsets_t *)((byte *)header + sizeof(mdxaHeader_t)); + +// frameSize = (int)( &((mdxaFrame_t *)0)->boneIndexes[ header->numBones ] ); + +// aframe = (mdxaFrame_t *)((byte *)header + header->ofsFrames + (frame * frameSize)); + // walk each bone and list it's name + for (x=0; x< mod_a->mdxa->numBones; x++) + { + skel = (mdxaSkel_t *)((byte *)header + sizeof(mdxaHeader_t) + offsets->offsets[x]); + Com_Printf("Bone %i Name %s\n", x, skel->name); + + Com_Printf("X pos %f, Y pos %f, Z pos %f\n", skel->BasePoseMat.matrix[0][3], skel->BasePoseMat.matrix[1][3], skel->BasePoseMat.matrix[2][3]); + + // if we are in verbose mode give us more details + if (r_verbose->value) + { + Com_Printf("Num Descendants %i\n", skel->numChildren); + for (i=0; inumChildren; i++) + { + Com_Printf("Num Descendants %i\n", skel->numChildren); + } + } + } +} + + +/************************************************************************************************ + * G2_GetAnimFileName + * obtain the .gla filename for a model + * + * Input + * filename of model + * + * Output + * true if we successfully obtained a filename, false otherwise + * + ************************************************************************************************/ +qboolean G2_GetAnimFileName(const char *fileName, char **filename) +{ + // find the model we want + model_t *mod = R_GetModelByHandle(RE_RegisterModel(fileName)); + + if (mod && mod->mdxm && (mod->mdxm->animName[0] != 0)) + { + *filename = mod->mdxm->animName; + return qtrue; + } + return qfalse; +} + + +///////////////////////////////////////////////////////////////////// +// +// Code for collision detection for models gameside +// +///////////////////////////////////////////////////////////////////// + +int G2_DecideTraceLod(CGhoul2Info &ghoul2, int useLod) +{ + int returnLod = useLod; + + // if we are overriding the LOD at top level, then we can afford to only check this level of model + if (ghoul2.mLodBias > returnLod) + { + returnLod = ghoul2.mLodBias; + } + assert(G2_MODEL_OK(&ghoul2)); + + assert(ghoul2.currentModel); + assert(ghoul2.currentModel->mdxm); + //what about r_lodBias? + + // now ensure that we haven't selected a lod that doesn't exist for this model + if ( returnLod >= ghoul2.currentModel->mdxm->numLODs ) + { + returnLod = ghoul2.currentModel->mdxm->numLODs - 1; + } + + return returnLod; +} + +#ifdef _XBOX +// This is in tr_ghoul2 for various reasons. +extern void R_TransformEachSurface( const mdxmSurface_t *surface, vec3_t scale, CMiniHeap *G2VertSpace, int *TransformedVertsArray,CBoneCache *boneCache); +#else +void R_TransformEachSurface( const mdxmSurface_t *surface, vec3_t scale, CMiniHeap *G2VertSpace, int *TransformedVertsArray,CBoneCache *boneCache) +{ + int j, k; + mdxmVertex_t *v; + float *TransformedVerts; + + // + // deform the vertexes by the lerped bones + // + int *piBoneReferences = (int*) ((byte*)surface + surface->ofsBoneReferences); + + // alloc some space for the transformed verts to get put in + TransformedVerts = (float *)G2VertSpace->MiniHeapAlloc(surface->numVerts * 5 * 4); + TransformedVertsArray[surface->thisSurfaceIndex] = (int)TransformedVerts; + if (!TransformedVerts) + { + assert(TransformedVerts); + Com_Error(ERR_DROP, "Ran out of transform space for Ghoul2 Models. Adjust MiniHeapSize in SV_SpawnServer.\n"); + } + + // whip through and actually transform each vertex + const int numVerts = surface->numVerts; + v = (mdxmVertex_t *) ((byte *)surface + surface->ofsVerts); + mdxmVertexTexCoord_t *pTexCoords = (mdxmVertexTexCoord_t *) &v[numVerts]; + + // optimisation issue + if ((scale[0] != 1.0) || (scale[1] != 1.0) || (scale[2] != 1.0)) + { + for ( j = 0; j < numVerts; j++ ) + { + vec3_t tempVert, tempNormal; +// mdxmWeight_t *w; + + VectorClear( tempVert ); + VectorClear( tempNormal ); +// w = v->weights; + + const int iNumWeights = G2_GetVertWeights( v ); + + float fTotalWeight = 0.0f; + for ( k = 0 ; k < iNumWeights ; k++ ) + { + int iBoneIndex = G2_GetVertBoneIndex( v, k ); + float fBoneWeight = G2_GetVertBoneWeight( v, k, fTotalWeight, iNumWeights ); + + const mdxaBone_t &bone=EvalBoneCache(piBoneReferences[iBoneIndex],boneCache); + + tempVert[0] += fBoneWeight * ( DotProduct( bone.matrix[0], v->vertCoords ) + bone.matrix[0][3] ); + tempVert[1] += fBoneWeight * ( DotProduct( bone.matrix[1], v->vertCoords ) + bone.matrix[1][3] ); + tempVert[2] += fBoneWeight * ( DotProduct( bone.matrix[2], v->vertCoords ) + bone.matrix[2][3] ); + + tempNormal[0] += fBoneWeight * DotProduct( bone.matrix[0], v->normal ); + tempNormal[1] += fBoneWeight * DotProduct( bone.matrix[1], v->normal ); + tempNormal[2] += fBoneWeight * DotProduct( bone.matrix[2], v->normal ); + } + int pos = j * 5; + + // copy tranformed verts into temp space + TransformedVerts[pos++] = tempVert[0] * scale[0]; + TransformedVerts[pos++] = tempVert[1] * scale[1]; + TransformedVerts[pos++] = tempVert[2] * scale[2]; + // we will need the S & T coors too for hitlocation and hitmaterial stuff + TransformedVerts[pos++] = pTexCoords[j].texCoords[0]; + TransformedVerts[pos] = pTexCoords[j].texCoords[1]; + + v++;// = (mdxmVertex_t *)&v->weights[/*v->numWeights*/surface->maxVertBoneWeights]; + } + } + else + { + int pos = 0; + for ( j = 0; j < numVerts; j++ ) + { + vec3_t tempVert, tempNormal; +// const mdxmWeight_t *w; + + VectorClear( tempVert ); + VectorClear( tempNormal ); +// w = v->weights; + + const int iNumWeights = G2_GetVertWeights( v ); + + float fTotalWeight = 0.0f; + for ( k = 0 ; k < iNumWeights ; k++ ) + { + int iBoneIndex = G2_GetVertBoneIndex( v, k ); + float fBoneWeight = G2_GetVertBoneWeight( v, k, fTotalWeight, iNumWeights ); + + const mdxaBone_t &bone=EvalBoneCache(piBoneReferences[iBoneIndex],boneCache); + + tempVert[0] += fBoneWeight * ( DotProduct( bone.matrix[0], v->vertCoords ) + bone.matrix[0][3] ); + tempVert[1] += fBoneWeight * ( DotProduct( bone.matrix[1], v->vertCoords ) + bone.matrix[1][3] ); + tempVert[2] += fBoneWeight * ( DotProduct( bone.matrix[2], v->vertCoords ) + bone.matrix[2][3] ); + + tempNormal[0] += fBoneWeight * DotProduct( bone.matrix[0], v->normal ); + tempNormal[1] += fBoneWeight * DotProduct( bone.matrix[1], v->normal ); + tempNormal[2] += fBoneWeight * DotProduct( bone.matrix[2], v->normal ); + } + + // copy tranformed verts into temp space + TransformedVerts[pos++] = tempVert[0]; + TransformedVerts[pos++] = tempVert[1]; + TransformedVerts[pos++] = tempVert[2]; + // we will need the S & T coors too for hitlocation and hitmaterial stuff + TransformedVerts[pos++] = pTexCoords[j].texCoords[0]; + TransformedVerts[pos++] = pTexCoords[j].texCoords[1]; + + v++;// = (mdxmVertex_t *)&v->weights[/*v->numWeights*/surface->maxVertBoneWeights]; + } + } +} + +#endif + +void G2_TransformSurfaces(int surfaceNum, surfaceInfo_v &rootSList, + CBoneCache *boneCache, const model_t *currentModel, int lod, vec3_t scale, CMiniHeap *G2VertSpace, int *TransformedVertArray, bool secondTimeAround) +{ + int i; + assert(currentModel); + assert(currentModel->mdxm); + // back track and get the surfinfo struct for this surface + const mdxmSurface_t *surface = (mdxmSurface_t *)G2_FindSurface(currentModel, surfaceNum, lod); + const mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)currentModel->mdxm + sizeof(mdxmHeader_t)); + const mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surface->thisSurfaceIndex]); + + // see if we have an override surface in the surface list + const surfaceInfo_t *surfOverride = G2_FindOverrideSurface(surfaceNum, rootSList); + + // really, we should use the default flags for this surface unless it's been overriden + int offFlags = surfInfo->flags; + + if (surfOverride) + { + offFlags = surfOverride->offFlags; + } + // if this surface is not off, add it to the shader render list + if (!offFlags) + { + + R_TransformEachSurface(surface, scale, G2VertSpace, TransformedVertArray, boneCache); + } + + // if we are turning off all descendants, then stop this recursion now + if (offFlags & G2SURFACEFLAG_NODESCENDANTS) + { + return; + } + + // now recursively call for the children + for (i=0; i< surfInfo->numChildren; i++) + { + G2_TransformSurfaces(surfInfo->childIndexes[i], rootSList, boneCache, currentModel, lod, scale, G2VertSpace, TransformedVertArray, secondTimeAround); + } +} + +// main calling point for the model transform for collision detection. At this point all of the skeleton has been transformed. +#ifdef _G2_GORE +void G2_TransformModel(CGhoul2Info_v &ghoul2, const int frameNum, vec3_t scale, CMiniHeap *G2VertSpace, int useLod, bool ApplyGore) +#else +void G2_TransformModel(CGhoul2Info_v &ghoul2, const int frameNum, vec3_t scale, CMiniHeap *G2VertSpace, int useLod) +#endif +{ + int i, lod; + vec3_t correctScale; + + + VectorCopy(scale, correctScale); + // check for scales of 0 - that's the default I believe + if (!scale[0]) + { + correctScale[0] = 1.0; + } + if (!scale[1]) + { + correctScale[1] = 1.0; + } + if (!scale[2]) + { + correctScale[2] = 1.0; + } + + // walk each possible model for this entity and try rendering it out + for (i=0; i=g.currentModel->numLods) + { + g.mTransformedVertsArray = 0; + return; + } + } + else +#endif + { + lod = G2_DecideTraceLod(g, useLod); + } + + // give us space for the transformed vertex array to be put in + g.mTransformedVertsArray = (int*)G2VertSpace->MiniHeapAlloc(g.currentModel->mdxm->numSurfaces * 4); + if (!g.mTransformedVertsArray) + { + Com_Error(ERR_DROP, "Ran out of transform space for Ghoul2 Models. Adjust MiniHeapSize in SV_SpawnServer.\n"); + } + + memset(g.mTransformedVertsArray, 0,(g.currentModel->mdxm->numSurfaces * 4)); + + G2_FindOverrideSurface(-1,g.mSlist); //reset the quick surface override lookup; + // recursively call the model surface transform + G2_TransformSurfaces(g.mSurfaceRoot, g.mSlist, g.mBoneCache, g.currentModel, lod, correctScale, G2VertSpace, g.mTransformedVertsArray, false); + +#ifdef _G2_GORE + if (ApplyGore&&!AddGoreToAllModels) + { + // we don't really need to do multiple models for gore. + break; + } +#endif + } +} + + +// work out how much space a triangle takes +static float G2_AreaOfTri(const vec3_t A, const vec3_t B, const vec3_t C) +{ + vec3_t cross, ab, cb; + VectorSubtract(A, B, ab); + VectorSubtract(C, B, cb); + + CrossProduct(ab, cb, cross); + + return VectorLength(cross); +} + +// actually determine the S and T of the coordinate we hit in a given poly +static void G2_BuildHitPointST( const vec3_t A, const float SA, const float TA, + const vec3_t B, const float SB, const float TB, + const vec3_t C, const float SC, const float TC, + const vec3_t P, float *s, float *t,float &bary_i,float &bary_j) +{ + float areaABC = G2_AreaOfTri(A, B, C); + + float i = G2_AreaOfTri(P, B, C) / areaABC; + bary_i=i; + float j = G2_AreaOfTri(A, P, C) / areaABC; + bary_j=j; + float k = G2_AreaOfTri(A, B, P) / areaABC; + + *s = SA * i + SB * j + SC * k; + *t = TA * i + TB * j + TC * k; + + *s=fmod(*s, 1); + if (*s< 0) + { + *s+= 1.0; + } + + *t=fmod(*t, 1); + if (*t< 0) + { + *t+= 1.0; + } + +} + + +// routine that works out given a ray whether or not it hits a poly +static inline qboolean G2_SegmentTriangleTest( const vec3_t start, const vec3_t end, + const vec3_t A, const vec3_t B, const vec3_t C, + qboolean backFaces,qboolean frontFaces,vec3_t returnedPoint,vec3_t returnedNormal, float *denom) +{ + static const float tiny=1E-10f; + vec3_t returnedNormalT; + vec3_t edgeAC; + + VectorSubtract(C, A, edgeAC); + VectorSubtract(B, A, returnedNormalT); + + CrossProduct(returnedNormalT, edgeAC, returnedNormal); + + vec3_t ray; + VectorSubtract(end, start, ray); + + *denom=DotProduct(ray, returnedNormal); + + if (Q_fabs(*denom)0)|| // not accepting back faces + (!frontFaces && *denom<0)) //not accepting front faces + { + return qfalse; + } + + vec3_t toPlane; + VectorSubtract(A, start, toPlane); + + float t=DotProduct(toPlane, returnedNormal)/ *denom; + + if (t<0.0f||t>1.0f) + { + return qfalse; // off segment + } + + VectorScale(ray, t, ray); + + VectorAdd(ray, start, returnedPoint); + + vec3_t edgePA; + VectorSubtract(A, returnedPoint, edgePA); + + vec3_t edgePB; + VectorSubtract(B, returnedPoint, edgePB); + + vec3_t edgePC; + VectorSubtract(C, returnedPoint, edgePC); + + vec3_t temp; + + CrossProduct(edgePA, edgePB, temp); + if (DotProduct(temp, returnedNormal)<0.0f) + { + return qfalse; // off triangle + } + + CrossProduct(edgePC, edgePA, temp); + if (DotProduct(temp,returnedNormal)<0.0f) + { + return qfalse; // off triangle + } + + CrossProduct(edgePB, edgePC, temp); + if (DotProduct(temp, returnedNormal)<0.0f) + { + return qfalse; // off triangle + } + return qtrue; +} + +#ifdef _G2_GORE +struct SVertexTemp +{ + int flags; + int touch; + int newindex; + float tex[2]; + SVertexTemp() + { + touch=0; + } +}; + +#define MAX_GORE_VERTS (3000) +static SVertexTemp GoreVerts[MAX_GORE_VERTS]; +static int GoreIndexCopy[MAX_GORE_VERTS]; +static int GoreTouch=1; + +#define MAX_GORE_INDECIES (6000) +static int GoreIndecies[MAX_GORE_INDECIES]; + +#define GORE_MARGIN (0.0f) +int G2API_GetTime(int argTime); + +// now we at poly level, check each model space transformed poly against the model world transfomed ray +static void G2_GorePolys( const mdxmSurface_t *surface, CTraceSurface &TS, const mdxmSurfHierarchy_t *surfInfo) +{ + int j; + vec3_t basis1; + vec3_t basis2; + vec3_t taxis; + vec3_t saxis; + + if (!TS.gore) + { + return; + } + + if (!TS.gore->useTheta) + { + VectorCopy(TS.gore->uaxis,basis2); + CrossProduct(TS.rayEnd,basis2,basis1); + if (DotProduct(basis1,basis1)<0.005f) + { //shot dir and slash dir are too close + return; + } + } + + if (TS.gore->useTheta) + { + basis2[0]=0.0f; + basis2[1]=0.0f; + basis2[2]=1.0f; + + CrossProduct(TS.rayEnd,basis2,basis1); + + if (DotProduct(basis1,basis1)<.1f) + { + basis2[0]=0.0f; + basis2[1]=1.0f; + basis2[2]=0.0f; + CrossProduct(TS.rayEnd,basis2,basis1); + } + CrossProduct(TS.rayEnd,basis1,basis2); + } + + // Give me a shot direction not a bunch of zeros :) -Gil + assert(DotProduct(basis1,basis1)>.0001f); + assert(DotProduct(basis2,basis2)>.0001f); + + VectorNormalize(basis1); + VectorNormalize(basis2); + + float c=cos(TS.theta); + float s=sin(TS.theta); + + VectorScale(basis1,.5f*c/TS.tsize,taxis); + VectorMA(taxis,.5f*s/TS.tsize,basis2,taxis); + + VectorScale(basis1,-.5f*s/TS.ssize,saxis); + VectorMA(saxis,.5f*c/TS.ssize,basis2,saxis); + + //fixme, everything above here should be pre-calculated in G2API_AddSkinGore + float *verts = (float *)TS.TransformedVertsArray[surface->thisSurfaceIndex]; + int numVerts = surface->numVerts; + int flags=63; + assert(numVertsGORE_MARGIN) + { + vflags|=1; + } + if (s<1.0f-GORE_MARGIN) + { + vflags|=2; + } + if (t>GORE_MARGIN) + { + vflags|=4; + } + if (t<1.0f-GORE_MARGIN) + { + vflags|=8; + } + if (depth > TS.gore->depthStart) + { + vflags|=16; + } + if (depth < TS.gore->depthEnd) + { + vflags|=32; + } + vflags=(~vflags); + flags&=vflags; + GoreVerts[j].flags=vflags; + GoreVerts[j].tex[0]=s; + GoreVerts[j].tex[1]=t; + } + if (flags) + { + return; // completely off the gore splotch. + } + int numTris,newNumTris,newNumVerts; + numTris = surface->numTriangles; + mdxmTriangle_t *tris; + tris = (mdxmTriangle_t *) ((byte *)surface + surface->ofsTriangles); + verts = (float *)TS.TransformedVertsArray[surface->thisSurfaceIndex]; + newNumTris=0; + newNumVerts=0; + GoreTouch++; + for ( j = 0; j < numTris; j++ ) + { + assert(tris[j].indexes[0]>=0&&tris[j].indexes[0]=0&&tris[j].indexes[1]=0&&tris[j].indexes[2]frontFaces || !TS.gore->backFaces) + { + // we need to back/front face cull + vec3_t e1,e2,n; + + VectorSubtract(&verts[tris[j].indexes[1]*5],&verts[tris[j].indexes[0]*5],e1); + VectorSubtract(&verts[tris[j].indexes[2]*5],&verts[tris[j].indexes[0]*5],e2); + CrossProduct(e1,e2,n); + if (DotProduct(TS.rayEnd,n)>0.0f) + { + if (!TS.gore->frontFaces) + { + continue; + } + } + else + { + if (!TS.gore->backFaces) + { + continue; + } + } + + } + + int k; + + assert(newNumTris*3+3,int>::iterator f=GoreTagsTemp.find(pair(goreModelIndex,TS.surfaceNum)); + if (f==GoreTagsTemp.end()) // need to generate a record + { + newTag=AllocGoreRecord(); + CGoreSet *goreSet=0; + if (TS.ghoul2info->mGoreSetTag) + { + goreSet=FindGoreSet(TS.ghoul2info->mGoreSetTag); + } + if (!goreSet) + { + goreSet=NewGoreSet(); + TS.ghoul2info->mGoreSetTag=goreSet->mMyGoreSetTag; + } + assert(goreSet); + SGoreSurface add; + add.shader=TS.goreShader; + add.mDeleteTime=0; + if (TS.gore->lifeTime) + { + add.mDeleteTime=G2API_GetTime(0) + TS.gore->lifeTime; + } + add.mFadeTime = TS.gore->fadeOutTime; + add.mFadeRGB = TS.gore->fadeRGB; + add.mGoreTag = newTag; + + add.mGoreGrowStartTime=G2API_GetTime(0); + if( TS.gore->growDuration == -1) + { + add.mGoreGrowEndTime = -1; // set this to -1 to disable growing + } + else + { + add.mGoreGrowEndTime = G2API_GetTime(0) + TS.gore->growDuration; + } + + assert(TS.gore->growDuration != 0); + add.mGoreGrowFactor = ( 1.0f - TS.gore->goreScaleStartFraction) / (float)(TS.gore->growDuration); //curscale = (curtime-mGoreGrowStartTime)*mGoreGrowFactor; + add.mGoreGrowOffset = TS.gore->goreScaleStartFraction; + + goreSet->mGoreRecords.insert(pair(TS.surfaceNum,add)); + GoreTagsTemp[pair(goreModelIndex,TS.surfaceNum)]=newTag; + } + else + { + newTag=(*f).second; + } + GoreTextureCoordinates *gore=FindGoreRecord(newTag); + if (gore) + { + assert(sizeof(float)==sizeof(int)); + // data block format: + unsigned int size= + sizeof(int)+ // num verts + sizeof(int)+ // num tris + sizeof(int)*newNumVerts+ // which verts to copy from original surface + sizeof(float)*4*newNumVerts+ // storgage for deformed verts + sizeof(float)*4*newNumVerts+ // storgage for deformed normal + sizeof(float)*2*newNumVerts+ // texture coordinates + sizeof(int)*newNumTris*3; // new indecies + + int *data=(int *)Z_Malloc ( sizeof(int)*size, TAG_GHOUL2, qtrue ); + + if ( gore->tex[TS.lod] ) + Z_Free(gore->tex[TS.lod]); + + gore->tex[TS.lod]=(float *)data; + *data++=newNumVerts; + *data++=newNumTris; + + memcpy(data,GoreIndexCopy,sizeof(int)*newNumVerts); + data+=newNumVerts*9; // skip verts and normals + float *fdata=(float *)data; + + for (j=0;jtex[TS.lod])*sizeof(int)==size); + fdata = (float *)data; + // build the entity to gore matrix + VectorCopy(saxis,fdata+0); + VectorCopy(taxis,fdata+4); + VectorCopy(TS.rayEnd,fdata+8); + VectorNormalize(fdata+0); + VectorNormalize(fdata+4); + VectorNormalize(fdata+8); + fdata[3]=-0.5f; // subtract texture center + fdata[7]=-0.5f; + fdata[11]=0.0f; + vec3_t shotOriginInCurrentSpace; // unknown space + TransformPoint(TS.rayStart,shotOriginInCurrentSpace,(mdxaBone_t *)fdata); // dest middle arg + // this will insure the shot origin in our unknown space is now the shot origin, making it a known space + fdata[3]-=shotOriginInCurrentSpace[0]; + fdata[7]-=shotOriginInCurrentSpace[1]; + fdata[11]-=shotOriginInCurrentSpace[2]; + Inverse_Matrix((mdxaBone_t *)fdata,(mdxaBone_t *)(fdata+12)); // dest 2nd arg + data+=24; + +// assert((data - (int *)gore->tex[TS.lod]) * sizeof(int) == size); + } +} +#else +struct SVertexTemp +{ + int flags; +// int touch; +// int newindex; +// float tex[2]; + SVertexTemp() + { +// touch=0; + } +}; + +#define MAX_GORE_VERTS (3000) +static SVertexTemp GoreVerts[MAX_GORE_VERTS]; +#endif + +// now we're at poly level, check each model space transformed poly against the model world transfomed ray +static bool G2_TracePolys(const mdxmSurface_t *surface, const mdxmSurfHierarchy_t *surfInfo, CTraceSurface &TS) +{ + int j, numTris; + + // whip through and actually transform each vertex + const mdxmTriangle_t *tris = (mdxmTriangle_t *) ((byte *)surface + surface->ofsTriangles); + const float *verts = (float *)TS.TransformedVertsArray[surface->thisSurfaceIndex]; + numTris = surface->numTriangles; + for ( j = 0; j < numTris; j++ ) + { + float face; + vec3_t hitPoint, normal; + // determine actual coords for this triangle + const float *point1 = &verts[(tris[j].indexes[0] * 5)]; + const float *point2 = &verts[(tris[j].indexes[1] * 5)]; + const float *point3 = &verts[(tris[j].indexes[2] * 5)]; + // did we hit it? + if (G2_SegmentTriangleTest(TS.rayStart, TS.rayEnd, point1, point2, point3, qtrue, qtrue, hitPoint, normal, &face)) + { // find space in the collision records for this record + for (int i=0; ithisSurfaceIndex; + newCol.mModelIndex = TS.modelIndex; + if (face>0) + { + newCol.mFlags = G2_FRONTFACE; + } + else + { + newCol.mFlags = G2_BACKFACE; + } + + VectorSubtract(hitPoint, TS.rayStart, distVect); + newCol.mDistance = VectorLength(distVect); + assert( !_isnan(newCol.mDistance) ); + + // put the hit point back into world space + TransformAndTranslatePoint(hitPoint, newCol.mCollisionPosition, &worldMatrix); + + // transform normal (but don't translate) into world angles + TransformPoint(normal, newCol.mCollisionNormal, &worldMatrix); + VectorNormalize(newCol.mCollisionNormal); + + newCol.mMaterial = newCol.mLocation = 0; + + // Determine our location within the texture, and barycentric coordinates + G2_BuildHitPointST(point1, point1[3], point1[4], + point2, point2[3], point2[4], + point3, point3[3], point3[4], + hitPoint, &x_pos, &y_pos,newCol.mBarycentricI,newCol.mBarycentricJ); + +/* + const shader_t *shader = 0; + // now, we know what surface this hit belongs to, we need to go get the shader handle so we can get the correct hit location and hit material info + if ( cust_shader ) + { + shader = cust_shader; + } + else if ( skin ) + { + int j; + + // match the surface name to something in the skin file + shader = tr.defaultShader; + for ( j = 0 ; j < skin->numSurfaces ; j++ ) + { + // the names have both been lowercased + if ( !strcmp( skin->surfaces[j]->name, surfInfo->name ) ) + { + shader = skin->surfaces[j]->shader; + break; + } + } + } + else + { + shader = R_GetShaderByHandle( surfInfo->shaderIndex ); + } + + // do we even care to decide what the hit or location area's are? If we don't have them in the shader there is little point + if ((shader->hitLocation) || (shader->hitMaterial)) + { + // ok, we have a floating point position. - determine location in data we need to look at + if (shader->hitLocation) + { + newCol.mLocation = *(hitMatReg[shader->hitLocation].loc + + ((int)(y_pos * hitMatReg[shader->hitLocation].height) * hitMatReg[shader->hitLocation].width) + + ((int)(x_pos * hitMatReg[shader->hitLocation].width))); + Com_Printf("G2_TracePolys hit location: %d\n", newCol.mLocation); + } + + if (shader->hitMaterial) + { + newCol.mMaterial = *(hitMatReg[shader->hitMaterial].loc + + ((int)(y_pos * hitMatReg[shader->hitMaterial].height) * hitMatReg[shader->hitMaterial].width) + + ((int)(x_pos * hitMatReg[shader->hitMaterial].width))); + } + } +*/ + // exit now if we should + if (TS.eG2TraceType == G2_RETURNONHIT) + { + TS.hitOne = true; + return true; + } + + break; + } + } + if (i==MAX_G2_COLLISIONS) + { + assert(i!=MAX_G2_COLLISIONS); // run out of collision record space - will probalbly never happen + TS.hitOne = true; //force stop recursion + return true; // return true to avoid wasting further time, but no hit will result without a record + } + } + } + return false; +} + + +// now we're at poly level, check each model space transformed poly against the model world transfomed ray +static bool G2_RadiusTracePolys( + const mdxmSurface_t *surface, + CTraceSurface &TS + ) +{ + int j; + vec3_t basis1; + vec3_t basis2; + vec3_t taxis; + vec3_t saxis; + + basis2[0]=0.0f; + basis2[1]=0.0f; + basis2[2]=1.0f; + + vec3_t v3RayDir; + VectorSubtract(TS.rayEnd, TS.rayStart, v3RayDir); + + CrossProduct(v3RayDir,basis2,basis1); + + if (DotProduct(basis1,basis1)<.1f) + { + basis2[0]=0.0f; + basis2[1]=1.0f; + basis2[2]=0.0f; + CrossProduct(v3RayDir,basis2,basis1); + } + + CrossProduct(v3RayDir,basis1,basis2); + // Give me a shot direction not a bunch of zeros :) -Gil +// assert(DotProduct(basis1,basis1)>.0001f); +// assert(DotProduct(basis2,basis2)>.0001f); + + VectorNormalize(basis1); + VectorNormalize(basis2); + + const float c=cos(0.0f);//theta + const float s=sin(0.0f);//theta + + VectorScale(basis1, 0.5f * c / TS.m_fRadius,taxis); + VectorMA(taxis, 0.5f * s / TS.m_fRadius,basis2,taxis); + + VectorScale(basis1,-0.5f * s /TS.m_fRadius,saxis); + VectorMA( saxis, 0.5f * c /TS.m_fRadius,basis2,saxis); + + const float * const verts = (float *)TS.TransformedVertsArray[surface->thisSurfaceIndex]; + const int numVerts = surface->numVerts; + + int flags=63; + //rayDir/=lengthSquared(raydir); + const float f = VectorLengthSquared(v3RayDir); + v3RayDir[0]/=f; + v3RayDir[1]/=f; + v3RayDir[2]/=f; + + for ( j = 0; j < numVerts; j++ ) + { + const int pos=j*5; + vec3_t delta; + delta[0]=verts[pos+0]-TS.rayStart[0]; + delta[1]=verts[pos+1]-TS.rayStart[1]; + delta[2]=verts[pos+2]-TS.rayStart[2]; + const float s=DotProduct(delta,saxis)+0.5f; + const float t=DotProduct(delta,taxis)+0.5f; + const float u=DotProduct(delta,v3RayDir); + int vflags=0; + + if (s>0) + { + vflags|=1; + } + if (s<1) + { + vflags|=2; + } + if (t>0) + { + vflags|=4; + } + if (t<1) + { + vflags|=8; + } + if (u>0) + { + vflags|=16; + } + if (u<1) + { + vflags|=32; + } + + vflags=(~vflags); + flags&=vflags; + GoreVerts[j].flags=vflags; + } + + if (flags) + { + return false; // completely off the gore splotch (so presumably hit nothing? -Ste) + } + const int numTris = surface->numTriangles; + const mdxmTriangle_t * const tris = (mdxmTriangle_t *) ((byte *)surface + surface->ofsTriangles); + + for ( j = 0; j < numTris; j++ ) + { + assert(tris[j].indexes[0]>=0&&tris[j].indexes[0]=0&&tris[j].indexes[1]=0&&tris[j].indexes[2]thisSurfaceIndex; + newCol.mModelIndex = TS.modelIndex; +// if (face>0) +// { + newCol.mFlags = G2_FRONTFACE; +// } +// else +// { +// newCol.mFlags = G2_BACKFACE; +// } + + //get normal from triangle + const float *A = &verts[(tris[j].indexes[0] * 5)]; + const float *B = &verts[(tris[j].indexes[1] * 5)]; + const float *C = &verts[(tris[j].indexes[2] * 5)]; + vec3_t normal; + vec3_t edgeAC, edgeBA; + + VectorSubtract(C, A, edgeAC); + VectorSubtract(B, A, edgeBA); + CrossProduct(edgeBA, edgeAC, normal); + + // transform normal (but don't translate) into world angles + TransformPoint(normal, newCol.mCollisionNormal, &worldMatrix); + VectorNormalize(newCol.mCollisionNormal); + + newCol.mMaterial = newCol.mLocation = 0; + // exit now if we should + if (TS.eG2TraceType == G2_RETURNONHIT) + { + TS.hitOne = true; + return true; + } + + vec3_t distVect; +#if 0 + //i don't know the hitPoint, but let's just assume it's the first vert for now... + float *hitPoint = (float *)A; +#else + //yeah, I want the collision point. Let's work out the impact point on the triangle. -rww + vec3_t hitPoint; + float side, side2; + float dist; + float third = -(A[0]*(B[1]*C[2] - C[1]*B[2]) + B[0]*(C[1]*A[2] - A[1]*C[2]) + C[0]*(A[1]*B[2] - B[1]*A[2]) ); + + VectorSubtract(TS.rayEnd, TS.rayStart, distVect); + side = normal[0]*TS.rayStart[0] + normal[1]*TS.rayStart[1] + normal[2]*TS.rayStart[2] + third; + side2 = normal[0]*distVect[0] + normal[1]*distVect[1] + normal[2]*distVect[2]; + if (fabsf(side2)<1E-8f) + { + //i don't know the hitPoint, but let's just assume it's the first vert for now... + VectorSubtract(A, TS.rayStart, distVect); + dist = VectorLength(distVect); + VectorSubtract(TS.rayEnd, TS.rayStart, distVect); + VectorMA(TS.rayStart, dist/VectorLength(distVect), distVect, hitPoint); + } + else + { + dist = side/side2; + VectorMA(TS.rayStart, -dist, distVect, hitPoint); + } +#endif + + VectorSubtract(hitPoint, TS.rayStart, distVect); + newCol.mDistance = VectorLength(distVect); + assert( !_isnan(newCol.mDistance) ); + + // put the hit point back into world space + TransformAndTranslatePoint(hitPoint, newCol.mCollisionPosition, &worldMatrix); + newCol.mBarycentricI = newCol.mBarycentricJ = 0.0f; + + break; + } + } + if (i==MAX_G2_COLLISIONS) + { + //assert(i!=MAX_G2_COLLISIONS); // run out of collision record space - happens OFTEN + TS.hitOne = true; //force stop recursion + return true; // return true to avoid wasting further time, but no hit will result without a record + } + } + } + + return false; +} + + +// look at a surface and then do the trace on each poly +static void G2_TraceSurfaces(CTraceSurface &TS) +{ + int i; + // back track and get the surfinfo struct for this surface + assert(TS.currentModel); + assert(TS.currentModel->mdxm); + const mdxmSurface_t *surface = (mdxmSurface_t *)G2_FindSurface(TS.currentModel, TS.surfaceNum, TS.lod); + const mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)TS.currentModel->mdxm + sizeof(mdxmHeader_t)); + const mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surface->thisSurfaceIndex]); + + // see if we have an override surface in the surface list + const surfaceInfo_t *surfOverride = G2_FindOverrideSurface(TS.surfaceNum, TS.rootSList); + + // don't allow recursion if we've already hit a polygon + if (TS.hitOne) + { + return; + } + + // really, we should use the default flags for this surface unless it's been overriden + int offFlags = surfInfo->flags; + + // set the off flags if we have some + if (surfOverride) + { + offFlags = surfOverride->offFlags; + } + + // if this surface is not off, try to hit it + if (!offFlags) + { +#ifdef _G2_GORE + if (TS.collRecMap) + { +#endif + if (!(Q_fabs(TS.m_fRadius) < 0.1)) // if not a point-trace + { + // .. then use radius check + // + if (G2_RadiusTracePolys(surface, // const mdxmSurface_t *surface, + TS + ) + && (TS.eG2TraceType == G2_RETURNONHIT) + ) + { + TS.hitOne = true; + return; + } + } + else + { + // go away and trace the polys in this surface + if (G2_TracePolys(surface, surfInfo, TS) + && (TS.eG2TraceType == G2_RETURNONHIT) + ) + { + // ok, we hit one, *and* we want to return instantly because the returnOnHit is set + // so indicate we've hit one, so other surfaces don't get hit and return + TS.hitOne = true; + return; + } + } +#ifdef _G2_GORE + } + else + { + G2_GorePolys(surface, TS, surfInfo); + } +#endif + } + + // if we are turning off all descendants, then stop this recursion now + if (offFlags & G2SURFACEFLAG_NODESCENDANTS) + { + return; + } + + // now recursively call for the children + for (i=0; i< surfInfo->numChildren && !TS.hitOne; i++) + { + TS.surfaceNum = surfInfo->childIndexes[i]; + G2_TraceSurfaces(TS); + } +} + +#ifdef _G2_GORE +void G2_TraceModels(CGhoul2Info_v &ghoul2, vec3_t rayStart, vec3_t rayEnd, CCollisionRecord *collRecMap, int entNum, EG2_Collision eG2TraceType, int useLod, float fRadius, float ssize,float tsize,float theta,int shader, SSkinGoreData *gore) +#else +void G2_TraceModels(CGhoul2Info_v &ghoul2, vec3_t rayStart, vec3_t rayEnd, CCollisionRecord *collRecMap, int entNum, EG2_Collision eG2TraceType, int useLod, float fRadius) +#endif +{ + int i, lod; + skin_t *skin; + shader_t *cust_shader; + + // walk each possible model for this entity and try tracing against it + for (i=0; i 0 && g.mSkin < tr.numSkins ) + { + skin = R_GetSkinByHandle( g.mSkin ); + } + else + { + skin = NULL; + } + + lod = G2_DecideTraceLod(g,useLod); + + //reset the quick surface override lookup + G2_FindOverrideSurface(-1, g.mSlist); + +#ifdef _G2_GORE + CTraceSurface TS(g.mSurfaceRoot, g.mSlist, g.currentModel, lod, rayStart, rayEnd, collRecMap, entNum, i, skin, cust_shader, g.mTransformedVertsArray, eG2TraceType, fRadius, ssize, tsize, theta, shader, &g, gore); +#else + CTraceSurface TS(g.mSurfaceRoot, g.mSlist, g.currentModel, lod, rayStart, rayEnd, collRecMap, entNum, i, skin, cust_shader, g.mTransformedVertsArray, eG2TraceType, fRadius); +#endif + // start the surface recursion loop + G2_TraceSurfaces(TS); + + // if we've hit one surface on one model, don't bother doing the rest + if (TS.hitOne) + { + break; + } +#ifdef _G2_GORE + if (!collRecMap&&!AddGoreToAllModels) + { + // we don't really need to do multiple models for gore. + break; + } +#endif + } +} + +void TransformPoint (const vec3_t in, vec3_t out, mdxaBone_t *mat) { + for (int i=0;i<3;i++) + { + out[i]= in[0]*mat->matrix[i][0] + in[1]*mat->matrix[i][1] + in[2]*mat->matrix[i][2]; + } +} + +void TransformAndTranslatePoint (const vec3_t in, vec3_t out, mdxaBone_t *mat) { + + for (int i=0;i<3;i++) + { + out[i]= in[0]*mat->matrix[i][0] + in[1]*mat->matrix[i][1] + in[2]*mat->matrix[i][2] + mat->matrix[i][3]; + } +} + + +// create a matrix using a set of angles +void Create_Matrix(const float *angle, mdxaBone_t *matrix) +{ + vec3_t axis[3]; + + // convert angles to axis + AnglesToAxis( angle, axis ); + matrix->matrix[0][0] = axis[0][0]; + matrix->matrix[1][0] = axis[0][1]; + matrix->matrix[2][0] = axis[0][2]; + + matrix->matrix[0][1] = axis[1][0]; + matrix->matrix[1][1] = axis[1][1]; + matrix->matrix[2][1] = axis[1][2]; + + matrix->matrix[0][2] = axis[2][0]; + matrix->matrix[1][2] = axis[2][1]; + matrix->matrix[2][2] = axis[2][2]; + + matrix->matrix[0][3] = 0; + matrix->matrix[1][3] = 0; + matrix->matrix[2][3] = 0; + + +} + +// given a matrix, generate the inverse of that matrix +void Inverse_Matrix(mdxaBone_t *src, mdxaBone_t *dest) +{ + int i, j; + + for (i = 0; i < 3; i++) + { + for (j = 0; j < 3; j++) + { + dest->matrix[i][j]=src->matrix[j][i]; + } + } + for (i = 0; i < 3; i++) + { + dest->matrix[i][3]=0; + for (j = 0; j < 3; j++) + { + dest->matrix[i][3]-=dest->matrix[i][j]*src->matrix[j][3]; + } + } +} + +// generate the world matrix for a given set of angles and origin - called from lots of places +void G2_GenerateWorldMatrix(const vec3_t angles, const vec3_t origin) +{ + Create_Matrix(angles, &worldMatrix); + worldMatrix.matrix[0][3] = origin[0]; + worldMatrix.matrix[1][3] = origin[1]; + worldMatrix.matrix[2][3] = origin[2]; + + Inverse_Matrix(&worldMatrix, &worldMatrixInv); +} + +// go away and determine what the pointer for a specific surface definition within the model definition is +void *G2_FindSurface(const model_s *mod, int index, int lod) +{ + assert(mod); + assert(mod->mdxm); + + // point at first lod list + byte *current = (byte*)((int)mod->mdxm + (int)mod->mdxm->ofsLODs); + int i; + + //walk the lods + assert(lod>=0&&lodmdxm->numLODs); + for (i=0; iofsEnd; + } + + // avoid the lod pointer data structure + current += sizeof(mdxmLOD_t); + + mdxmLODSurfOffset_t *indexes = (mdxmLODSurfOffset_t *)current; + // we are now looking at the offset array + assert(index>=0&&indexmdxm->numSurfaces); + current += indexes->offsets[index]; + + return (void *)current; +} + +#define SURFACE_SAVE_BLOCK_SIZE sizeof(surfaceInfo_t) +#define BOLT_SAVE_BLOCK_SIZE sizeof(boltInfo_t) +#define BONE_SAVE_BLOCK_SIZE sizeof(boneInfo_t) + +void G2_SaveGhoul2Models(CGhoul2Info_v &ghoul2) +{ + char *pGhoul2Data = NULL; + int iGhoul2Size = 0; + + // is there anything to save? + if (!ghoul2.IsValid()||!ghoul2.size()) + { + SG_Append('GHL2',&pGhoul2Data, 4); //write out a zero buffer + return; + } + + // this one isn't a define since I couldn't work out how to figure it out at compile time + const int ghoul2BlockSize = (int)&ghoul2[0].BSAVE_END_FIELD - (int)&ghoul2[0].BSAVE_START_FIELD; + + // add in count for number of ghoul2 models + iGhoul2Size += 4; + // start out working out the total size of the buffer we need to allocate + for (int i=0; imValid&&(g)->aHeader&&(g)->currentModel&&(g)->animModel) + +#pragma warning(disable : 4512) //assignment op could not be genereated + +class CQuickOverride +{ + int mOverride[512]; + int mAt[512]; + int mCurrentTouch; +public: + CQuickOverride() + { + mCurrentTouch=1; + int i; + for (i=0;i<512;i++) + { + mOverride[i]=0; + } + } + void Invalidate() + { + mCurrentTouch++; + } + void Set(int index,int pos) + { + if (index==10000) + { + return; + } + assert(index>=0&&index<512); + mOverride[index]=mCurrentTouch; + mAt[index]=pos; + } + int Test(int index) + { + assert(index>=0&&index<512); + if (mOverride[index]!=mCurrentTouch) + { + return -1; + } + else + { + return mAt[index]; + } + } +}; + +static CQuickOverride QuickOverride; + + +// find a particular surface in the surface override list +const surfaceInfo_t *G2_FindOverrideSurface(int surfaceNum,const surfaceInfo_v &surfaceList) +{ + + if (surfaceNum<0) + { + // starting a new lookup + QuickOverride.Invalidate(); + int i; + for(i=0; i=0) + { + QuickOverride.Set(surfaceList[i].surface,i); + } + } + return NULL; + } + int idx=QuickOverride.Test(surfaceNum); + if (idx<0) + { + unsigned int i; + if (surfaceNum==10000) + { + for(i=0; i=0&&idxmdxm); + // damn include file dependancies + mdxmSurfHierarchy_t *surf; + surf = (mdxmSurfHierarchy_t *) ( (byte *)mod_m->mdxm + mod_m->mdxm->ofsSurfHierarchy ); + + for ( int i = 0 ; i < mod_m->mdxm->numSurfaces ; i++) + { + if (!stricmp(surfaceName, surf->name)) + { + *flags = surf->flags; + return i; + } + // find the next surface + surf = (mdxmSurfHierarchy_t *)( (byte *)surf + (int)( &((mdxmSurfHierarchy_t *)0)->childIndexes[ surf->numChildren ] )); + } + return -1; +} + + +/************************************************************************************************ + * G2_FindSurface + * find a surface in a ghoul2 surface override list based on it's name + * + * Input + * filename of model, surface list of model instance, name of surface, int to be filled in + * with the index of this surface (defaults to NULL) + * + * Output + * pointer to surface if successful, false otherwise + * + ************************************************************************************************/ +const mdxmSurface_t *G2_FindSurface(CGhoul2Info *ghlInfo, surfaceInfo_v &slist, const char *surfaceName, + int *surfIndex/*NULL*/) +{ + int i = 0; + // find the model we want + assert(G2_MODEL_OK(ghlInfo)); + + const mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)ghlInfo->currentModel->mdxm + sizeof(mdxmHeader_t)); + + // first find if we already have this surface in the list + for (i = slist.size() - 1; i >= 0; i--) + { + if ((slist[i].surface != 10000) && (slist[i].surface != -1)) + { + const mdxmSurface_t *surf = (mdxmSurface_t *)G2_FindSurface(ghlInfo->currentModel, slist[i].surface, 0); + // back track and get the surfinfo struct for this surface + const mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surf->thisSurfaceIndex]); + + // are these the droids we're looking for? + if (!stricmp (surfInfo->name, surfaceName)) + { + // yup + if (surfIndex) + { + *surfIndex = i; + } + return surf; + } + } + } + // didn't find it + if (surfIndex) + { + *surfIndex = -1; + } + return 0; +} + +// set a named surface offFlags - if it doesn't find a surface with this name in the list then it will add one. +qboolean G2_SetSurfaceOnOff (CGhoul2Info *ghlInfo, const char *surfaceName, const int offFlags) +{ + int surfIndex = -1; + surfaceInfo_t temp_slist_entry; + + // find the model we want + // first find if we already have this surface in the list + const mdxmSurface_t *surf = G2_FindSurface(ghlInfo, ghlInfo->mSlist, surfaceName, &surfIndex); + if (surf) + { + // set descendants value + + // slist[surfIndex].offFlags = offFlags; + // seems to me that we shouldn't overwrite the other flags. + // the only bit we really care about in the incoming flags is the off bit + ghlInfo->mSlist[surfIndex].offFlags &= ~(G2SURFACEFLAG_OFF | G2SURFACEFLAG_NODESCENDANTS); + ghlInfo->mSlist[surfIndex].offFlags |= offFlags & (G2SURFACEFLAG_OFF | G2SURFACEFLAG_NODESCENDANTS); + return qtrue; + } + else + { + // ok, not in the list already - in that case, lets verify this surface exists in the model mesh + int flags; + int surfaceNum = G2_IsSurfaceLegal(ghlInfo->currentModel, surfaceName, &flags); + if (surfaceNum != -1) + { + int newflags = flags; + // the only bit we really care about in the incoming flags is the off bit + newflags &= ~(G2SURFACEFLAG_OFF | G2SURFACEFLAG_NODESCENDANTS); + newflags |= offFlags & (G2SURFACEFLAG_OFF | G2SURFACEFLAG_NODESCENDANTS); + + if (newflags != flags) + { // insert here then because it changed, no need to add an override otherwise + temp_slist_entry.offFlags = newflags; + temp_slist_entry.surface = surfaceNum; + + ghlInfo->mSlist.push_back(temp_slist_entry); + } + return qtrue; + } + } + return qfalse; +} + +void G2_FindRecursiveSurface(const model_t *currentModel, int surfaceNum, surfaceInfo_v &rootList, int *activeSurfaces) +{ + assert(currentModel); + assert(currentModel->mdxm); + int i; + const mdxmSurface_t *surface = (mdxmSurface_t *)G2_FindSurface(currentModel, surfaceNum, 0); + const mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)currentModel->mdxm + sizeof(mdxmHeader_t)); + const mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surface->thisSurfaceIndex]); + + // see if we have an override surface in the surface list + const surfaceInfo_t *surfOverride = G2_FindOverrideSurface(surfaceNum, rootList); + + // really, we should use the default flags for this surface unless it's been overriden + int offFlags = surfInfo->flags; + + // set the off flags if we have some + if (surfOverride) + { + offFlags = surfOverride->offFlags; + } + + // if this surface is not off, indicate as such in the active surface list + if (!(offFlags & G2SURFACEFLAG_OFF)) + { + activeSurfaces[surfaceNum] = 1; + } + else + // if we are turning off all descendants, then stop this recursion now + if (offFlags & G2SURFACEFLAG_NODESCENDANTS) + { + return; + } + + // now recursively call for the children + for (i=0; i< surfInfo->numChildren; i++) + { + surfaceNum = surfInfo->childIndexes[i]; + G2_FindRecursiveSurface(currentModel, surfaceNum, rootList, activeSurfaces); + } + +} + +qboolean G2_SetRootSurface( CGhoul2Info_v &ghoul2, const int modelIndex, const char *surfaceName) +{ + int surf; + int flags; + assert(modelIndex>=0&&modelIndexmdxm); + // first find if we already have this surface in the list + surf = G2_IsSurfaceLegal(ghoul2[modelIndex].currentModel, surfaceName, &flags); + if (surf != -1) + { + ghoul2[modelIndex].mSurfaceRoot = surf; + return qtrue; + } + assert(0); + return qfalse; +} + + +extern int G2_DecideTraceLod(CGhoul2Info &ghoul2, int useLod); +int G2_AddSurface(CGhoul2Info *ghoul2, int surfaceNumber, int polyNumber, float BarycentricI, float BarycentricJ, int lod ) +{ + lod = G2_DecideTraceLod(*ghoul2, lod); + + // first up, see if we have a free one already set up - look only from the end of the constant surfaces onwards + unsigned int i; + for (i=0; imSlist.size(); i++) + { + // is the surface count -1? That would indicate it's free + if (ghoul2->mSlist[i].surface == -1) + { + break; + } + } + if (i==ghoul2->mSlist.size()) + { + ghoul2->mSlist.push_back(surfaceInfo_t()); + } + ghoul2->mSlist[i].offFlags = G2SURFACEFLAG_GENERATED; + ghoul2->mSlist[i].surface = 10000; // no model will ever have 10000 surfaces + ghoul2->mSlist[i].genBarycentricI = BarycentricI; + ghoul2->mSlist[i].genBarycentricJ = BarycentricJ; + ghoul2->mSlist[i].genPolySurfaceIndex = ((polyNumber & 0xffff) << 16) | (surfaceNumber & 0xffff); + ghoul2->mSlist[i].genLod = lod; + return i; +} + +qboolean G2_RemoveSurface(surfaceInfo_v &slist, const int index) +{ + if (index != -1) + { + slist[index].surface = -1; + return qtrue; + } + assert(0); + return qfalse; +} + + +int G2_GetParentSurface(CGhoul2Info *ghlInfo, const int index) +{ + assert(ghlInfo->currentModel); + assert(ghlInfo->currentModel->mdxm); + const mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)ghlInfo->currentModel->mdxm + sizeof(mdxmHeader_t)); + + // walk each surface and see if this index is listed in it's children + const mdxmSurface_t *surf = (mdxmSurface_t *)G2_FindSurface(ghlInfo->currentModel, index, 0); + const mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surf->thisSurfaceIndex]); + + return surfInfo->parentIndex; + +} + +int G2_GetSurfaceIndex(CGhoul2Info *ghlInfo, const char *surfaceName) +{ + int flags; + assert(ghlInfo->currentModel); + return G2_IsSurfaceLegal(ghlInfo->currentModel, surfaceName, &flags); +} + +int G2_IsSurfaceRendered(CGhoul2Info *ghlInfo, const char *surfaceName, surfaceInfo_v &slist) +{ + int flags = 0;//, surfFlags = 0; + int surfIndex = 0; + assert(ghlInfo->currentModel); + assert(ghlInfo->currentModel->mdxm); + if (!ghlInfo->currentModel->mdxm) + { + return -1; + } + + // now travel up the skeleton to see if any of it's ancestors have a 'no descendants' turned on + + // find the original surface in the surface list + int surfNum = G2_IsSurfaceLegal(ghlInfo->currentModel, surfaceName, &flags); + if ( surfNum != -1 ) + {//must be legal + const mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)ghlInfo->currentModel->mdxm + sizeof(mdxmHeader_t)); + const mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surfNum]); + surfNum = surfInfo->parentIndex; + // walk the surface hierarchy up until we hit the root + while (surfNum != -1) + { + const mdxmSurface_t *parentSurf; + int parentFlags; + const mdxmSurfHierarchy_t *parentSurfInfo; + + parentSurfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surfNum]); + + // find the original surface in the surface list + //G2 was bug, above comment was accurate, but we don't want the original flags, we want the parent flags + G2_IsSurfaceLegal(ghlInfo->currentModel, parentSurfInfo->name, &parentFlags); + + // now see if we already have overriden this surface in the slist + parentSurf = G2_FindSurface(ghlInfo, slist, parentSurfInfo->name, &surfIndex); + if (parentSurf) + { + // set descendants value + parentFlags = slist[surfIndex].offFlags; + } + // now we have the parent flags, lets see if any have the 'no descendants' flag set + if (parentFlags & G2SURFACEFLAG_NODESCENDANTS) + { + flags |= G2SURFACEFLAG_OFF; + break; + } + // set up scan of next parent + surfNum = parentSurfInfo->parentIndex; + } + } + else + { + return -1; + } + if ( flags == 0 ) + {//it's not being overridden by a parent + // now see if we already have overriden this surface in the slist + const mdxmSurface_t *surf = G2_FindSurface(ghlInfo, slist, surfaceName, &surfIndex); + if (surf) + { + // set descendants value + flags = slist[surfIndex].offFlags; + } + // ok, at this point in flags we have what this surface is set to, and the index of the surface itself + } + return flags; + +} diff --git a/code/ghoul2/ghoul2_gore.h b/code/ghoul2/ghoul2_gore.h new file mode 100644 index 0000000..6f34e09 --- /dev/null +++ b/code/ghoul2/ghoul2_gore.h @@ -0,0 +1,191 @@ +#ifdef _G2_GORE + +#define MAX_LODS (8) +struct GoreTextureCoordinates +{ + float *tex[MAX_LODS]; + + GoreTextureCoordinates() + { + int i; + for (i=0;i mGoreRecords; // a map from surface index + CGoreSet(int tag) : mMyGoreSetTag(tag), mRefCount(0) {} + ~CGoreSet(); +}; + +CGoreSet *FindGoreSet(int goreSetTag); +CGoreSet *NewGoreSet(); +void DeleteGoreSet(int goreSetTag); + +#endif // _G2_GORE + +//rww - RAGDOLL_BEGIN +#pragma warning(disable: 4512) + +struct SRagDollEffectorCollision +{ + vec3_t effectorPosition; + const trace_t &tr; + bool useTracePlane; + SRagDollEffectorCollision(const vec3_t effectorPos,const trace_t &t) : + tr(t), + useTracePlane(false) + { + VectorCopy(effectorPos,effectorPosition); + } +}; + +class CRagDollUpdateParams +{ +public: + vec3_t angles; + vec3_t position; + vec3_t scale; + vec3_t velocity; + //CServerEntity *me; + int me; //index! + int settleFrame; + + int groundEnt; + + virtual void EffectorCollision(const SRagDollEffectorCollision &data) + { + assert(0); // you probably meant to override this + } + virtual void RagDollBegin() + { + assert(0); // you probably meant to override this + } + virtual void RagDollSettled() + { + assert(0); // you probably meant to override this + } + + virtual void Collision() + { + assert(0); // you probably meant to override this + // we had a collision, uhh I guess call SetRagDoll RP_DEATH_COLLISION + } + +#ifdef _DEBUG + virtual void DebugLine(vec3_t p1,vec3_t p2,int color,bool bbox) {assert(0);} +#endif +}; + + +class CRagDollParams +{ +public: + + enum ERagPhase + { + RP_START_DEATH_ANIM, + RP_END_DEATH_ANIM, + RP_DEATH_COLLISION, + RP_CORPSE_SHOT, + RP_GET_PELVIS_OFFSET, // this actually does nothing but set the pelvisAnglesOffset, and pelvisPositionOffset + RP_SET_PELVIS_OFFSET, // this actually does nothing but set the pelvisAnglesOffset, and pelvisPositionOffset + RP_DISABLE_EFFECTORS // this removes effectors given by the effectorsToTurnOff member + }; + vec3_t angles; + vec3_t position; + vec3_t scale; + vec3_t pelvisAnglesOffset; // always set on return, an argument for RP_SET_PELVIS_OFFSET + vec3_t pelvisPositionOffset; // always set on return, an argument for RP_SET_PELVIS_OFFSET + + float fImpactStrength; //should be applicable when RagPhase is RP_DEATH_COLLISION + float fShotStrength; //should be applicable for setting velocity of corpse on shot (probably only on RP_CORPSE_SHOT) + //CServerEntity *me; + int me; + + int groundEnt; + + //rww - we have convenient animation/frame access in the game, so just send this info over from there. + int startFrame; + int endFrame; + + int collisionType; // 1 = from a fall, 0 from effectors, this will be going away soon, hence no enum + + bool CallRagDollBegin; // a return value, means that we are now begininng ragdoll and the NPC stuff needs to happen + + ERagPhase RagPhase; + +// effector control, used for RP_DISABLE_EFFECTORS call + + enum ERagEffector + { + RE_MODEL_ROOT= 0x00000001, //"model_root" + RE_PELVIS= 0x00000002, //"pelvis" + RE_LOWER_LUMBAR= 0x00000004, //"lower_lumbar" + RE_UPPER_LUMBAR= 0x00000008, //"upper_lumbar" + RE_THORACIC= 0x00000010, //"thoracic" + RE_CRANIUM= 0x00000020, //"cranium" + RE_RHUMEROUS= 0x00000040, //"rhumerus" + RE_LHUMEROUS= 0x00000080, //"lhumerus" + RE_RRADIUS= 0x00000100, //"rradius" + RE_LRADIUS= 0x00000200, //"lradius" + RE_RFEMURYZ= 0x00000400, //"rfemurYZ" + RE_LFEMURYZ= 0x00000800, //"lfemurYZ" + RE_RTIBIA= 0x00001000, //"rtibia" + RE_LTIBIA= 0x00002000, //"ltibia" + RE_RHAND= 0x00004000, //"rhand" + RE_LHAND= 0x00008000, //"lhand" + RE_RTARSAL= 0x00010000, //"rtarsal" + RE_LTARSAL= 0x00020000, //"ltarsal" + RE_RTALUS= 0x00040000, //"rtalus" + RE_LTALUS= 0x00080000, //"ltalus" + RE_RRADIUSX= 0x00100000, //"rradiusX" + RE_LRADIUSX= 0x00200000, //"lradiusX" + RE_RFEMURX= 0x00400000, //"rfemurX" + RE_LFEMURX= 0x00800000, //"lfemurX" + RE_CEYEBROW= 0x01000000 //"ceyebrow" + }; + + ERagEffector effectorsToTurnOff; // set this to an | of the above flags for a RP_DISABLE_EFFECTORS + +}; +//rww - RAGDOLL_END diff --git a/code/goblib/Copy of goblib.vcproj b/code/goblib/Copy of goblib.vcproj new file mode 100644 index 0000000..f432ee3 --- /dev/null +++ b/code/goblib/Copy of goblib.vcproj @@ -0,0 +1,824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/goblib/debug/vc70.idb b/code/goblib/debug/vc70.idb new file mode 100644 index 0000000000000000000000000000000000000000..62acb179b6b121eb2989bb12b85266a87c1ef157 GIT binary patch literal 68608 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 JB5(!-{ujbzyIlYP literal 0 HcmV?d00001 diff --git a/code/goblib/goblib.cpp b/code/goblib/goblib.cpp new file mode 100644 index 0000000..ddef2cd --- /dev/null +++ b/code/goblib/goblib.cpp @@ -0,0 +1,1876 @@ +/***************************************** + * + * GOB File System + * + * Here's what Merriam-Webster says about "gob": --Chuck + * Entry: gob + * Function: noun + * Etymology: Middle English gobbe, from Middle French gobe large piece of food, + * back-formation from gobet + * Date: 14th century + * 1 : LUMP + * 2 : a large amount -- usually used in plural + * + * Purpose: Provide fast, efficient disk access on a variety of platforms. + * + * Implementation: + * The GOB system maintains two files -- GOB and GFC. The GOB file is actually + * an archive of many files split into variable size, compressed blocks. The GFC, + * GOB File Control, contains 3 tables -- a block table, basic file table, and + * extended file table. The block table is analogous to a DOS FAT. The basic + * file table contains a minimal set of file information to handle basic reading + * tasks. The extended file table is optionally loaded and contains additional + * file information. File names are case insensitive. + * + * Files can be read in a normal manner. Open, read, seek and close + * operations are all provided. Files can only be written in a single + * contiguous chunk of blocks at the end of an archive. Reads are processed + * through a configurable number of read ahead buffers to in an effort to + * minimize both reads and seeks. Other operations including delete, verify, + * access, and get size are also supported on files inside an archive. + * + * The system supports read profiling. By supplying a file read callback + * function, the library will output the block number of each read. This can + * be used rearrange block in the archive to minimize seek times. The + * GOBRearrange sorts files in an archive. + * + * Supports block based caching. Primarily aimed at caching files off a DVD/CD + * to a faster hard disk. + * + * Future Work: + * + * Dependencies: vvInt, snprintf, zlib + * Owner: Chris McEvoy + * History: + * 09/23/2001 Original version + * 10/28/2002 Merged into vvtech + * + * Copyright (C) 2002, Vicarious Visions, Inc. All Rights Reserved. + * + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + * + *****************************************/ + +/* + This is an unofficial branch of GOB, for Jedi Academy + Maintainer: Brian Osman +*/ + +#include "goblib.h" +#include "../zlib/zlib.h" + +#include +#include +#include +#include +#include + +#if (VV_PLATFORM == VV_PLATFORM_WIN) || (VV_PLATFORM == VV_PLATFORM_XBOX) +# define CDECL __cdecl +#else +# define CDECL +#endif + +// Profiling data +static GOBProfileReadFunc ProfileReadCallback = NULL; +static GOBBool ProfileEnabled = GOB_FALSE; + +// Indicates whether or not the library has been initialized +static GOBBool LibraryInit = GOB_FALSE; + +// Callbacks for handling low-level compression/decompression +static struct GOBCodecFuncSet CodecFuncs; + +// Callbacks for handling low-level memory alloc and free +static struct GOBMemoryFuncSet MemFuncs; + +// Callbacks for handling low-level file access +static struct GOBFileSysFuncSet FSFuncs; + +// Callbacks for handling block caching (ie Xbox temp space) +static struct GOBCacheFileFuncSet CacheFileFuncs; +static GOBBool CacheFileActive = GOB_FALSE; + +// Name of the GFC file +static GOBChar ControlFileName[GOB_MAX_FILE_NAME_LEN]; + +// Handle to the GOB archive +static GOBFSHandle ArchiveHandle = (GOBFSHandle*)0xFFFFFFFF; + +// Size of the active GOB archive +static GOBUInt32 ArchiveSize = 0; +static GOBUInt32 ArchiveNumBlocks = 0; +static GOBUInt32 ArchiveNumFiles = 0; + +// Cached blocks +struct GOBBlockCache +{ + GOBChar* data; + GOBUInt32 block; + GOBUInt32 time; + GOBUInt32 size; +}; +static struct GOBBlockCache* CacheBlocks = NULL; +static GOBUInt32 NumCacheBlocks = 0; +static GOBUInt32 CacheBlockCounter = 0; + +// Read ahead buffer +struct GOBReadBuffer +{ + GOBChar* data; + GOBChar* dataStart; + GOBUInt32 pos; + GOBUInt32 size; +}; +static struct GOBReadBuffer ReadBuffer; + +// Decompression buffer +static GOBChar* DecompBuffer = NULL; + +// Stats gathering +static struct GOBReadStats ReadStats; +static GOBUInt32 CurrentArchivePos = 0; + +// File tables (from the GFC) +static struct GOBFileTableBasicEntry* FileTableBasic = NULL; +static struct GOBFileTableExtEntry* FileTableExt = NULL; + +// Block tables (from the GFC) +static struct GOBBlockTableEntry* BlockTable = NULL; +static GOBUInt32* BlockCRC = NULL; +static GOBUInt32* CacheFileTable = NULL; + +// Do the tables need to be written? +static GOBBool FileTableDirty = GOB_FALSE; + +// Information about open files +struct OpenFileInfo +{ + GOBBool valid; + GOBUInt32 startBlock; + GOBUInt32 block; + GOBUInt32 offset; + + GOBUInt32 pos; + GOBUInt32 size; +}; + +// Open file table -- indices in this array are passed +// back to the caller as pseudo file handles. +static struct OpenFileInfo OpenFiles[GOB_MAX_OPEN_FILES]; + +// Converting text to lower case -- this isn't very +// clean. A common buffer is used to store lower case +// text. So its not thread safe... among other things. ;) +static GOBChar LowerCaseBuffer[GOB_MAX_FILE_NAME_LEN]; +static GOBChar* LowerCase(const GOBChar* name) +{ + GOBInt32 i; + for (i = 0; name[i]; ++i) { + LowerCaseBuffer[i] = (GOBChar)tolower(name[i]); + } + LowerCaseBuffer[i] = 0; + + return LowerCaseBuffer; +} + +// Checks if a file handle is invalid +static GOBBool InvalidHandle(GOBFSHandle h) +{ + return (GOBUInt32)h == 0xFFFFFFFF ? GOB_TRUE : GOB_FALSE; +} + +// Endian conversion +#if VV_ENDIAN == VV_ENDIAN_LITTLE +static GOBUInt32 SwapBytes(GOBUInt32 x) +{ + return + (x >> 24) | + ((x >> 8) & 0xFF00) | + ((x << 8) & 0xFF0000) | + (x << 24); + +} +#else +static GOBUInt32 SwapBytes(GOBUInt32 x) +{ + return x; +} +#endif + + +// Given a file name, get its index in the FileTable +static GOBInt32 GetFileTableEntry(const GOBChar* file) +{ + GOBUInt32 entry; + GOBUInt32 hash; + + // hash the file name + hash = crc32(0L, Z_NULL, 0); + hash = crc32(hash, (const unsigned char*)file, strlen(file)); + + // linear search for matching a matching hash + for (entry = 0; entry < ArchiveNumFiles; ++entry) { + if (FileTableBasic[entry].block != GOB_INVALID_BLOCK && + FileTableBasic[entry].hash == hash) + { + return entry; + } + } + + return -1; +} + +// Mark the contents of cache and read buffer invalid +static GOBVoid InvalidateCache(GOBVoid) +{ + GOBUInt32 i; + for (i = 0; i < NumCacheBlocks; ++i) { + CacheBlocks[i].block = 0xFFFFFFFF; + } + ReadBuffer.pos = 0xFFFFFFFF; +} + +// Deallocate memory used by cache and read buffer +static GOBVoid FreeCache(GOBVoid) +{ + GOBUInt32 i; + + if (CacheBlocks) { + for (i = 0; i < NumCacheBlocks; ++i) { + if (CacheBlocks[i].data) MemFuncs.free(CacheBlocks[i].data); + CacheBlocks[i].data = NULL; + } + + MemFuncs.free(CacheBlocks); + NumCacheBlocks = 0; + CacheBlocks = NULL; + } +} + +// Write the file table to disk if the form of a GFC +static GOBError CommitFileTable(GOBVoid) +{ + GOBUInt32 num; + struct GOBFileTableBasicEntry basic; + struct GOBFileTableExtEntry ext; + struct GOBBlockTableEntry block; + + // open the GFC + GOBFSHandle handle = FSFuncs.open(ControlFileName, GOBACCESS_WRITE); + if (InvalidHandle(handle)) return GOBERR_FILE_WRITE; + + // write the magic identifier + num = SwapBytes(GOB_MAGIC_IDENTIFIER); + if (!FSFuncs.write(handle, &num, sizeof(num))) return GOBERR_FILE_WRITE; + + // write the size of the GOB + num = SwapBytes(ArchiveSize); + if (!FSFuncs.write(handle, &num, sizeof(num))) return GOBERR_FILE_WRITE; + + // write number of blocks in archive + num = SwapBytes(ArchiveNumBlocks); + if (!FSFuncs.write(handle, &num, sizeof(num))) return GOBERR_FILE_WRITE; + + // write number of file in archive + num = SwapBytes(ArchiveNumFiles); + if (!FSFuncs.write(handle, &num, sizeof(num))) return GOBERR_FILE_WRITE; + + // write block table -- with endian conversion + for (num = 0; num < ArchiveNumBlocks; ++num) { + block.next = SwapBytes(BlockTable[num].next); + block.offset = SwapBytes(BlockTable[num].offset); + block.size = SwapBytes(BlockTable[num].size); + + if (!FSFuncs.write(handle, &block, sizeof(block))) return GOBERR_FILE_WRITE; + } + + // write block CRCs -- with endian conversion + for (num = 0; num < ArchiveNumBlocks; ++num) { + BlockCRC[num] = SwapBytes(BlockCRC[num]); + if (!FSFuncs.write(handle, &BlockCRC[num], sizeof(BlockCRC[num]))) { + return GOBERR_FILE_WRITE; + } + } + + // write each basic table entry -- with endian conversion + for (num = 0; num < ArchiveNumFiles; ++num) { + basic.hash = SwapBytes(FileTableBasic[num].hash); + basic.block = SwapBytes(FileTableBasic[num].block); + basic.size = SwapBytes(FileTableBasic[num].size); + + if (!FSFuncs.write(handle, &basic, sizeof(basic))) return GOBERR_FILE_WRITE; + } + + // write each extended table entry -- with endian conversion + for (num = 0; num < ArchiveNumFiles; ++num) { + strcpy(ext.name, FileTableExt[num].name); + ext.crc = SwapBytes(FileTableExt[num].crc); + ext.time = SwapBytes(FileTableExt[num].time); + + if (!FSFuncs.write(handle, &ext, sizeof(ext))) return GOBERR_FILE_WRITE; + } + + // all done + FSFuncs.close(&handle); + FileTableDirty = GOB_FALSE; + + return GOBERR_OK; +} + + +static GOBVoid DeallocTables(GOBVoid) +{ + if (BlockTable) { + // free the block table + MemFuncs.free(BlockTable); + BlockTable = NULL; + } + + if (BlockCRC) { + // free the block crc table + MemFuncs.free(BlockCRC); + BlockCRC = NULL; + } + + if (CacheFileTable) + { + // free the block cache table + MemFuncs.free(CacheFileTable); + CacheFileTable = NULL; + } + + if (FileTableBasic) { + // free the basic file table + MemFuncs.free(FileTableBasic); + FileTableBasic = NULL; + } + + if (FileTableExt) { + // free the extended file table + MemFuncs.free(FileTableExt); + FileTableExt = NULL; + } +} + +static GOBError AllocTables(GOBUInt32 num_blocks, GOBUInt32 num_files, + GOBBool extended, GOBBool safe) +{ + GOBUInt32 num; + + // dump any old tables + DeallocTables(); + + // allocate the block table + BlockTable = (struct GOBBlockTableEntry*) + MemFuncs.alloc(num_blocks * sizeof(struct GOBBlockTableEntry)); + if (!BlockTable) return GOBERR_NO_MEMORY; + + if (safe) { + // allocate the block crc table for verifying data validity + BlockCRC = (GOBUInt32*)MemFuncs.alloc(num_blocks * sizeof(GOBUInt32)); + if (!BlockCRC) return GOBERR_NO_MEMORY; + } + else { + BlockCRC = NULL; + } + + if (CacheFileActive) + { + // allocate the block cache bitfield + CacheFileTable = (GOBUInt32*) + MemFuncs.alloc((num_blocks / 32 + 1) * 4); + if (!CacheFileTable) return GOBERR_NO_MEMORY; + } + + // allocate the basic file table + FileTableBasic = (struct GOBFileTableBasicEntry*) + MemFuncs.alloc(num_files * sizeof(struct GOBFileTableBasicEntry)); + if (!FileTableBasic) return GOBERR_NO_MEMORY; + + if (extended) { + // allocate the extended file table + FileTableExt = (struct GOBFileTableExtEntry*) + MemFuncs.alloc(num_files * sizeof(struct GOBFileTableExtEntry)); + if (!FileTableExt) return GOBERR_NO_MEMORY; + } + else { + FileTableExt = NULL; + } + + // clear the tables + for (num = 0; num < num_files; ++num) { + FileTableBasic[num].block = GOB_INVALID_BLOCK; + if (FileTableExt) FileTableExt[num].name[0] = 0; + } + + for (num = 0; num < num_blocks; ++num) { + BlockTable[num].next = GOB_INVALID_BLOCK; + BlockTable[num].size = GOB_INVALID_SIZE; + } + + return GOBERR_OK; +} + + +// GOBInit +// Public function. Initialize the library. +GOBError GOBInit(struct GOBMemoryFuncSet* mem, + struct GOBFileSysFuncSet* file, + struct GOBCodecFuncSet* codec, + struct GOBCacheFileFuncSet* cache) +{ + GOBInt32 i; + GOBError err; + + if (LibraryInit) return GOBERR_ALREADY_INIT; + + // setup the callbacks + MemFuncs = *mem; + FSFuncs = *file; + CodecFuncs = *codec; + if (cache) { + CacheFileFuncs = *cache; + CacheFileActive = GOB_TRUE; + } else { + CacheFileActive = GOB_FALSE; + } + + // allocate decompression buffer + DecompBuffer = (GOBChar*)MemFuncs.alloc(GOB_BLOCK_SIZE + GOB_COMPRESS_OVERHEAD); + if (!DecompBuffer) return GOBERR_NO_MEMORY; + + // clear open table + for (i = 0; i < GOB_MAX_OPEN_FILES; ++i) { + OpenFiles[i].valid = GOB_FALSE; + } + + LibraryInit = GOB_TRUE; + + err = GOBSetCacheSize(1); + if (err != GOBERR_OK) { + LibraryInit = GOB_FALSE; + return err; + } + + ReadBuffer.data = NULL; + err = GOBSetReadBufferSize(128*1024); + if (err != GOBERR_OK) { + LibraryInit = GOB_FALSE; + return err; + } + + return GOBERR_OK; +} + +// GOBShutdown +// Public function. Close the library. +GOBError GOBShutdown(GOBVoid) +{ + if (!LibraryInit) return GOBERR_NOT_INIT; + + // if we have an open archive, close it + if (!InvalidHandle(ArchiveHandle)) GOBArchiveClose(); + + FreeCache(); + + // free read ahead buffer + if (ReadBuffer.data) { + MemFuncs.free(ReadBuffer.data); + ReadBuffer.data = NULL; + } + + // free decompression buffer + MemFuncs.free(DecompBuffer); + + // free the file and block tables + DeallocTables(); + + LibraryInit = GOB_FALSE; + return GOBERR_OK; +} + + +// GOBArchiveCreate +// Public function. Create an empty GFC and GOB. +GOBError GOBArchiveCreate(const GOBChar* file) +{ + GOBChar fname[GOB_MAX_FILE_NAME_LEN]; + GOBFSHandle handle; + GOBError error; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (!InvalidHandle(ArchiveHandle)) return GOBERR_ALREADY_OPEN; + + // Allocate the max space for tables + error = AllocTables(GOB_MAX_BLOCKS, GOB_MAX_FILES, GOB_TRUE, GOB_TRUE); + if (GOBERR_OK != error) { + return error; + } + + // create an empty GFC + _snprintf(ControlFileName, GOB_MAX_FILE_NAME_LEN, "%s.gfc", file); + + ArchiveSize = 0; + ArchiveNumBlocks = 0; + ArchiveNumFiles = 0; + CacheFileActive = GOB_FALSE; + + CommitFileTable(); + + // create an empty GOB + _snprintf(fname, GOB_MAX_FILE_NAME_LEN, "%s.gob", file); + handle = FSFuncs.open(fname, GOBACCESS_WRITE); + if (InvalidHandle(handle)) return GOBERR_CANNOT_CREATE; + + FSFuncs.close(&handle); + + return GOBERR_OK; +} + +// GOBArchiveOpen +// Public function. Open a GOB file and cache file tables. +GOBError GOBArchiveOpen(const GOBChar* file, GOBAccessType atype, + GOBBool extended, GOBBool safe) +{ + GOBChar fname[GOB_MAX_FILE_NAME_LEN]; + GOBFSHandle handle; + GOBUInt32 magic; + GOBUInt32 i; + GOBError error; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (!InvalidHandle(ArchiveHandle)) return GOBERR_ALREADY_OPEN; + + // open the GFC + _snprintf(ControlFileName, GOB_MAX_FILE_NAME_LEN, "%s.gfc", file); + handle = FSFuncs.open(ControlFileName, atype); + if (InvalidHandle(handle)) return GOBERR_FILE_NOT_FOUND; + + // read and check the magic + if (!FSFuncs.read(handle, &magic, sizeof(magic))) return GOBERR_FILE_READ; + if (SwapBytes(magic) != GOB_MAGIC_IDENTIFIER) return GOBERR_NOT_GOB_FILE; + + // read the GOB archive size + if (!FSFuncs.read(handle, &ArchiveSize, sizeof(ArchiveSize))) return GOBERR_FILE_READ; + ArchiveSize = SwapBytes(ArchiveSize); + + // read the number of blocks + if (!FSFuncs.read(handle, &ArchiveNumBlocks, sizeof(ArchiveNumBlocks))) return GOBERR_FILE_READ; + ArchiveNumBlocks = SwapBytes(ArchiveNumBlocks); + + // read the number of files + if (!FSFuncs.read(handle, &ArchiveNumFiles, sizeof(ArchiveNumFiles))) return GOBERR_FILE_READ; + ArchiveNumFiles = SwapBytes(ArchiveNumFiles); + + // Allocate the space for tables + if (atype == GOBACCESS_READ) { + error = AllocTables(ArchiveNumBlocks, ArchiveNumFiles, extended, safe); + } + else { + error = AllocTables(GOB_MAX_BLOCKS, GOB_MAX_FILES, extended, safe); + } + if (GOBERR_OK != error) { + return error; + } + + // read the block table + if (ArchiveNumBlocks && + !FSFuncs.read(handle, BlockTable, + sizeof(struct GOBBlockTableEntry) * ArchiveNumBlocks)) + { + return GOBERR_FILE_READ; + } + + if (BlockCRC) { + // read the block CRCs + if (ArchiveNumBlocks && + !FSFuncs.read(handle, BlockCRC, + sizeof(GOBUInt32) * ArchiveNumBlocks)) + { + return GOBERR_FILE_READ; + } + } + else { + // skip block CRCs + FSFuncs.seek(handle, sizeof(GOBUInt32) * ArchiveNumBlocks, + GOBSEEK_CURRENT); + } + + if (CacheFileActive) + { + // clear the block cache table + for (i = 0; i < ArchiveNumBlocks / 32; ++i) { + CacheFileTable[i] = 0; + } + } + + // open the cache file + if (CacheFileActive && !CacheFileFuncs.open(ArchiveSize)) { + CacheFileActive = GOB_FALSE; + } + + // endian convert the table + for (i = 0; i < ArchiveNumBlocks; ++i) { + BlockTable[i].next = SwapBytes(BlockTable[i].next); + BlockTable[i].offset = SwapBytes(BlockTable[i].offset); + BlockTable[i].size = SwapBytes(BlockTable[i].size); + + if (BlockCRC) { + BlockCRC[i] = SwapBytes(BlockCRC[i]); + } + } + + // read the basic file table + if (ArchiveNumFiles && + !FSFuncs.read(handle, FileTableBasic, + sizeof(struct GOBFileTableBasicEntry) * ArchiveNumFiles)) + { + return GOBERR_FILE_READ; + } + + // endian convert the table + for (i = 0; i < ArchiveNumFiles; ++i) { + FileTableBasic[i].hash = SwapBytes(FileTableBasic[i].hash); + FileTableBasic[i].block = SwapBytes(FileTableBasic[i].block); + FileTableBasic[i].size = SwapBytes(FileTableBasic[i].size); + } + + // if we have memory for the extended file table + if (FileTableExt) { + // read the table + if (ArchiveNumFiles && + !FSFuncs.read(handle, FileTableExt, + sizeof(struct GOBFileTableExtEntry) * ArchiveNumFiles)) + { + return GOBERR_FILE_READ; + } + + // endian convert the table + for (i = 0; i < ArchiveNumFiles; ++i) { + FileTableExt[i].crc = SwapBytes(FileTableExt[i].crc); + FileTableExt[i].time = SwapBytes(FileTableExt[i].time); + } + } + + FSFuncs.close(&handle); + + // open the GOB + _snprintf(fname, GOB_MAX_FILE_NAME_LEN, "%s.gob", file); + ArchiveHandle = FSFuncs.open(fname, atype); + if (InvalidHandle(ArchiveHandle)) return GOBERR_FILE_NOT_FOUND; + + // initialize stats gathering + CurrentArchivePos = 0; + ReadStats.bufferUsed = 0; + ReadStats.bytesRead = 0; + ReadStats.cacheBytesRead = 0; + ReadStats.cacheBytesWrite = 0; + ReadStats.totalSeeks = 0; + ReadStats.farSeeks = 0; + ReadStats.filesOpened = 0; + + return GOBERR_OK; +} + +// GOBArchiveClose +// Public function. Close an open GOB archive. +GOBError GOBArchiveClose(GOBVoid) +{ + GOBInt32 i; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + + // close any open files + for (i = 0; i < GOB_MAX_OPEN_FILES; ++i) { + GOBClose(i); + } + + // close the GOB + FSFuncs.close(&ArchiveHandle); + ArchiveHandle = (GOBFSHandle*)0xFFFFFFFF; + + // commit the file table if we're updated it + if (FileTableDirty) { + CommitFileTable(); + } + + // close the cache file + if (CacheFileActive) { + CacheFileFuncs.close(); + CacheFileActive = GOB_FALSE; + } + + return GOBERR_OK; +} + +static int CDECL SortBlockDescsCallback(const void* elem1, const void* elem2) +{ + return (int)((struct GOBBlockTableEntry *)elem1)->offset - + (int)((struct GOBBlockTableEntry *)elem2)->offset; +} + +// GOBArchiveCheckMarkers +// Public function. Check start/end markers to check approximate validity of GOB file +GOBError GOBArchiveCheckMarkers(GOBVoid) +{ + GOBUInt32 i; + GOBUInt32 valid_blocks; + struct GOBBlockTableEntry *blocks; + GOBUInt32 block; + GOBUInt32 start_marker; + GOBUInt32 end_marker; + GOBBool ok; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + + // count valid blocks + valid_blocks = 0; + for (i = 0; i < ArchiveNumBlocks; i++) + { + if (BlockTable[i].size != GOB_INVALID_SIZE && + BlockTable[i].next != GOB_INVALID_BLOCK) + { + valid_blocks++; + } + } + + // arcvive is empty + if (valid_blocks == 0) + { + return GOBERR_OK; + } + + // alloc mem for valid block list + blocks = (GOBBlockTableEntry *) MemFuncs.alloc(sizeof(*blocks) * valid_blocks); + if (blocks == NULL) + { + return GOBERR_NO_MEMORY; + } + + // copy valid blocks descriptions + block = 0; + for (i = 0; i < ArchiveNumBlocks; ++i) + { + if (BlockTable[i].size != GOB_INVALID_SIZE && + BlockTable[i].next != GOB_INVALID_BLOCK) + { + blocks[block++] = BlockTable[i]; + } + } + assert(block == valid_blocks); + + // and sort 'em + qsort(blocks, valid_blocks, sizeof(*blocks), SortBlockDescsCallback); + + // suppress some warnings + start_marker = 0; + end_marker = 0; + + // now scan entire archive for start-of-block and end-of-block markers + for (i = 0; i < valid_blocks; i++) + { + ok = GOB_TRUE; + ok = ok && !FSFuncs.seek(ArchiveHandle, blocks[i].offset, GOBSEEK_START); + ok = ok && FSFuncs.read(ArchiveHandle, &start_marker, sizeof(GOBUInt32)) == sizeof(GOBUInt32); + ok = ok && !FSFuncs.seek(ArchiveHandle, blocks[i].offset + blocks[i].size - sizeof(GOBUInt32), GOBSEEK_START); + ok = ok && FSFuncs.read(ArchiveHandle, &end_marker, sizeof(GOBUInt32)) == sizeof(GOBUInt32); + if (!ok || + SwapBytes(start_marker) != GOBMARKER_STARTBLOCK || + SwapBytes(end_marker) != GOBMARKER_ENDBLOCK) + { + MemFuncs.free(blocks); + + return GOBERR_NOT_GOB_FILE; + } + } + + MemFuncs.free(blocks); + + return GOBERR_OK; +} + +// GOBArchiveCreate +// Public function. Create an empty GFC and GOB. +GOBUInt32 GOBGetSlack(GOBUInt32 x) +{ + GOBUInt32 align = x % GOB_BLOCK_ALIGNMENT; + if (align) return GOB_BLOCK_ALIGNMENT - align; + return 0; +} + +// GOBOpen +// Public function. Open a file inside a GOB. +GOBError GOBOpen(GOBChar* file, GOBHandle* handle) +{ + GOBInt32 entry; + GOBChar* lfile; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + + // find a free handle + for (*handle = 0; *handle < GOB_MAX_OPEN_FILES; (*handle) += 1) { + if (!OpenFiles[*handle].valid) break; + } + + if (*handle >= GOB_MAX_OPEN_FILES) return GOBERR_TOO_MANY_OPEN; + + // find the file in the table + lfile = LowerCase(file); + + entry = GetFileTableEntry(lfile); + + if (entry == -1) return GOBERR_FILE_NOT_FOUND; + + // setup the open file + OpenFiles[*handle].startBlock = OpenFiles[*handle].block = + FileTableBasic[entry].block; + OpenFiles[*handle].size = FileTableBasic[entry].size; + OpenFiles[*handle].offset = 0; + OpenFiles[*handle].pos = 0; + + OpenFiles[*handle].valid = GOB_TRUE; + + ++ReadStats.filesOpened; + + return GOBERR_OK; +} + +// GOBOpenCode +// Public function. Open file with a code inside a GOB. +GOBError GOBOpenCode(GOBInt32 code, GOBHandle* handle) +{ + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + + // find a free handle + for (*handle = 0; *handle < GOB_MAX_OPEN_FILES; (*handle) += 1) { + if (!OpenFiles[*handle].valid) break; + } + + if (*handle >= GOB_MAX_OPEN_FILES) return GOBERR_TOO_MANY_OPEN; + + // setup the open file + OpenFiles[*handle].startBlock = OpenFiles[*handle].block = + FileTableBasic[code].block; + OpenFiles[*handle].size = FileTableBasic[code].size; + OpenFiles[*handle].offset = 0; + OpenFiles[*handle].pos = 0; + + OpenFiles[*handle].valid = GOB_TRUE; + + ++ReadStats.filesOpened; + + return GOBERR_OK; +} + +// GOBClose +// Public function. Close a file. +GOBError GOBClose(GOBHandle handle) +{ + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + if (!OpenFiles[handle].valid) return GOBERR_NOT_OPEN; + + // close the file by simply invalidating the open + // file table entry + OpenFiles[handle].valid = GOB_FALSE; + + return GOBERR_OK; +} + +static GOBUInt32 RawRead(GOBVoid* buffer, GOBUInt32 size, GOBUInt32 pos) +{ + GOBUInt32 bytes; + + // Reads _must_ be aligned otherwise things get very slow + if (pos % GOB_BLOCK_ALIGNMENT) { + return 0; + } + if ((GOBUInt32)buffer % GOB_MEM_ALIGNMENT) { + return 0; + } + + // seek + if (FSFuncs.seek(ArchiveHandle, pos, GOBSEEK_START)) return 0; + + if (CurrentArchivePos != pos) ++ReadStats.totalSeeks; + if (pos > CurrentArchivePos + GOB_BLOCK_ALIGNMENT || + CurrentArchivePos > pos + GOB_BLOCK_ALIGNMENT) + { + ++ReadStats.farSeeks; + } + + // read + bytes = FSFuncs.read(ArchiveHandle, buffer, size); + + ReadStats.bytesRead += bytes; + CurrentArchivePos = pos + bytes; + + return bytes; +} + +static GOBUInt32 CacheRawRead(GOBVoid* buffer, GOBUInt32 size, GOBUInt32 pos) +{ + GOBUInt32 bytes; + + // Reads _must_ be aligned otherwise things get very slow + if (pos % GOB_BLOCK_ALIGNMENT) { + return 0; + } + if ((GOBUInt32)buffer % GOB_MEM_ALIGNMENT) { + return 0; + } + + // seek + if (CacheFileFuncs.seek(pos)) return 0; + + // read + bytes = CacheFileFuncs.read(buffer, size); + ReadStats.cacheBytesRead += bytes; + + return bytes; +} + +static GOBUInt32 CacheRawWrite(GOBVoid* buffer, GOBUInt32 size, GOBUInt32 pos) +{ + GOBUInt32 bytes; + + // Writes _must_ be aligned otherwise things get very slow + if (pos % GOB_BLOCK_ALIGNMENT) { + return 0; + } + if ((GOBUInt32)buffer % GOB_MEM_ALIGNMENT) { + return 0; + } + + // seek + if (CacheFileFuncs.seek(pos)) return 0; + + // write + bytes = CacheFileFuncs.write(buffer, size); + ReadStats.cacheBytesWrite += bytes; + + return bytes; +} + +static GOBInt32 BlockReadLow(GOBUInt32 block) +{ + GOBUInt32 pos; + GOBUInt32 bytes; + GOBBool cache_read; + GOBBool cache_write; + GOBBool cache_fail; + + pos = 0; + cache_read = GOB_FALSE; + cache_write = GOB_FALSE; + cache_fail = GOB_FALSE; + + for (;;) { + // is the block in the read ahead buffer? + if (ReadBuffer.pos <= BlockTable[block].offset + pos && + ReadBuffer.pos + ReadBuffer.size > BlockTable[block].offset + pos) + { + GOBUInt32 buffer_offset; + GOBUInt32 buffer_size; + + // use data in the read buffer + buffer_offset = BlockTable[block].offset + pos - ReadBuffer.pos; + buffer_size = ReadBuffer.size - buffer_offset; + + // clamp size + if (buffer_size > BlockTable[block].size - pos) { + buffer_size = BlockTable[block].size - pos; + } + + memcpy(&DecompBuffer[pos], &ReadBuffer.dataStart[buffer_offset], buffer_size); + + pos += buffer_size; + } + + // got enough data + if (pos == BlockTable[block].size) break; + + // refill read buffer + ReadBuffer.pos = BlockTable[block].offset + pos; + ReadBuffer.pos -= ReadBuffer.pos % GOB_BLOCK_ALIGNMENT; + + // check if block is in the external cache system + if (CacheFileActive && + CacheFileTable[block / 32] & (1 << (block % 32))) + { + if (CacheRawRead(ReadBuffer.dataStart, + ReadBuffer.size, ReadBuffer.pos)) + { + cache_read = GOB_TRUE; + continue; + } + } + + // read block from archive + bytes = RawRead(ReadBuffer.dataStart, ReadBuffer.size, ReadBuffer.pos); + if (bytes != ReadBuffer.size && + bytes != ArchiveSize - ReadBuffer.pos) + { + return -1; // Main read fail error code + } + + // write block to cache file + if (CacheFileActive) + { + if (CacheRawWrite(ReadBuffer.dataStart, bytes, + ReadBuffer.pos) == bytes) + { + cache_write = GOB_TRUE; + } + else + { + cache_fail = GOB_TRUE; + } + } + } + + if (cache_write) { + if (!cache_fail) return 2; + return 0; + } + + if (cache_read) return 1; + return 0; +} + +static GOBBool BlockReadWithCache(GOBUInt32 block) +{ + GOBInt32 i; + + for (i = 0; i < GOB_READ_RETRYS; ++i) { + GOBInt32 result; + + // read the data + result = BlockReadLow(block); + if (result >= 0) + { + if (BlockCRC) { + // crc check + GOBUInt32 crc; + + crc = adler32(0L, Z_NULL, 0); + crc = adler32(crc, (const unsigned char*)DecompBuffer, + BlockTable[block].size); + + if (BlockCRC[block] != crc) { + // crc mismatch, we must have got bad data -- + // try invalidating the cache and retrying... + if (CacheFileActive) { + CacheFileTable[block / 32] &= ~(1 << (block % 32)); + } + ReadBuffer.pos = 0xFFFFFFFF; + continue; + } + } + + // if cache write occurred -- mark block as cached + if (result == 2) { + CacheFileTable[block / 32] |= (1 << (block % 32)); + } + + // read success, crc success (or no check performed) + return GOB_TRUE; + } + } + + // multiple read/crc failures + return GOB_FALSE; +} + +static GOBUInt32 BlockRead(GOBVoid* buffer, GOBUInt32 block) +{ + GOBUInt32 size; + GOBInt32 codec_index; + GOBChar *compressed_data; + + // read block from cache or archive + if (!BlockReadWithCache(block)) + { + return GOB_INVALID_SIZE; + } + + // decompress + codec_index = 0; + size = 0; // Initialize to satisfy compiler + compressed_data = DecompBuffer + sizeof(GOBUInt32); // skip start-of-block marker + while (codec_index < CodecFuncs.codecs) { + // Check if codec matches + if (*compressed_data == CodecFuncs.codec[codec_index].tag) { + size = GOB_BLOCK_SIZE; + if (CodecFuncs.codec[codec_index].decompress(compressed_data + 1, + BlockTable[block].size - 1 - sizeof(GOBUInt32) * 2, buffer, &size)) { + return GOB_INVALID_SIZE; + } + break; + } + codec_index++; + } + + // If no suitable codecs were found, we're screwed + if (codec_index == CodecFuncs.codecs) { + return GOB_INVALID_SIZE; + } + + if (ProfileReadCallback && ProfileEnabled) { + // register current read command + ProfileReadCallback(block); + } + + return size; +} + +static GOBVoid FillCacheBlock(GOBUInt32 block, GOBUInt32 index) +{ + CacheBlocks[index].time = CacheBlockCounter++; + CacheBlocks[index].block = block; + CacheBlocks[index].size = BlockRead(CacheBlocks[index].data, block); +} + +static GOBInt32 FindBestCacheBlock(GOBUInt32 block) +{ + GOBInt32 i; + GOBUInt32 oldest_time; + GOBInt32 oldest_index; + + oldest_time = 0xFFFFFFFF; + oldest_index = -1; + + for (i = 0; i < (signed)NumCacheBlocks; ++i) { + if (CacheBlocks[i].block == block) { + // if block is in this read buffer, use it + return i; + } + + // find the buffer that hasn't been accessed + // for the longest time + if (CacheBlocks[i].time < oldest_time) { + oldest_time = CacheBlocks[i].time; + oldest_index = i; + } + } + + // use the buffer that hasn't been accessed + // in the longest time + return oldest_index; +} + +// GOBRead +// Public function. Read from an open file using +// a funky read-ahead buffer system. +GOBUInt32 GOBRead(GOBVoid* buffer, GOBUInt32 size, GOBHandle handle) +{ + GOBUInt32 pos; + GOBInt32 cache_id; + + if (!LibraryInit) return 0; + if (InvalidHandle(ArchiveHandle)) return 0; + if (!OpenFiles[handle].valid) return 0; + + // make sure we're reading within the file + if (OpenFiles[handle].pos + size > OpenFiles[handle].size) { + size = OpenFiles[handle].size - OpenFiles[handle].pos; + if (!size) return 0; + } + + cache_id = FindBestCacheBlock(OpenFiles[handle].block); + if (cache_id < 0) return GOB_INVALID_SIZE; + + pos = OpenFiles[handle].pos; + + for (;;) { + // are looking for data inside the read buffer? + if (CacheBlocks[cache_id].block == OpenFiles[handle].block) { + // move any relevant data from the read buffer to the target buffer + GOBUInt32 buffer_size; + + // calc size of data we want from current buffer + buffer_size = CacheBlocks[cache_id].size - OpenFiles[handle].offset; + if (buffer_size > size) buffer_size = size; + + // move from read buffer into output buffer + memcpy(&((char*)buffer)[OpenFiles[handle].pos - pos], + &CacheBlocks[cache_id].data[OpenFiles[handle].offset], + buffer_size); + + // update file position + OpenFiles[handle].pos += buffer_size; + OpenFiles[handle].offset += buffer_size; + + // if we've completed this block -- move to next + if (OpenFiles[handle].offset == CacheBlocks[cache_id].size) { + OpenFiles[handle].block = BlockTable[OpenFiles[handle].block].next; + OpenFiles[handle].offset = 0; + } + + CacheBlocks[cache_id].time = CacheBlockCounter++; + + ReadStats.bufferUsed += buffer_size; + size -= buffer_size; + if (size == 0) break; + } + + // refill the buffer + FillCacheBlock(OpenFiles[handle].block, cache_id); + if (CacheBlocks[cache_id].size == GOB_INVALID_SIZE) { + CacheBlocks[cache_id].block = GOB_INVALID_BLOCK; + return GOB_INVALID_SIZE; + } + + // reading off the end of the archive + if (CacheBlocks[cache_id].block != OpenFiles[handle].block) break; + } + + return OpenFiles[handle].pos - pos; +} + +// GOBSeek +// Public function. Seek to a position in an open file. +GOBError GOBSeek(GOBHandle handle, GOBUInt32 offset, GOBSeekType type, GOBUInt32* pos) +{ + GOBUInt32 blocks; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + if (!OpenFiles[handle].valid) return GOBERR_NOT_OPEN; + + // find a new position based on the seek type + switch (type) { + case GOBSEEK_START: + *pos = offset; + break; + + case GOBSEEK_CURRENT: + *pos = OpenFiles[handle].pos + offset; + break; + + case GOBSEEK_END: + *pos = OpenFiles[handle].size + offset; + break; + + default: + return GOBERR_INVALID_SEEK; + } + + // check to make sure we're still in the file + if (*pos > OpenFiles[handle].size) { + return GOBERR_INVALID_SEEK; + } + + // update the file position + OpenFiles[handle].pos = *pos; + + // update block + blocks = *pos / GOB_BLOCK_SIZE; + OpenFiles[handle].block = OpenFiles[handle].startBlock; + while (blocks--) { + OpenFiles[handle].block = BlockTable[OpenFiles[handle].block].next; + } + + // update position inside block + OpenFiles[handle].offset = *pos % GOB_BLOCK_SIZE; + + return GOBERR_OK; +} + + +static GOBUInt32 FindFreeBlock(GOBVoid) +{ + GOBInt32 i; + for (i = 0; i < GOB_MAX_BLOCKS; ++i) { + if (BlockTable[i].next == GOB_INVALID_BLOCK) return i; + } + return GOB_MAX_BLOCKS; +} + +// GOBWrite +// Public function. Write an entire file. The file should not be open! +GOBError GOBWrite(GOBVoid* buffer, GOBUInt32 size, GOBUInt32 mtime, const GOBChar* file, GOBUInt32 codec_mask) +{ + GOBHandle handle; + GOBInt32 slack; + GOBChar* lfile; + GOBUInt32 hash; + GOBUInt32 crc; + GOBInt32 i; + GOBChar* out; + GOBUInt32 pos; + GOBUInt32 last_block; + GOBInt32 codec_index; + GOBInt32 compression_ratio; + GOBChar* out_data; + GOBUInt32 compressed_size; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + if (!FileTableExt) return GOBERR_NO_EXTENDED; + if (!BlockCRC) return GOBERR_NO_EXTENDED; + + InvalidateCache(); + + // delete the file if it exists + GOBDelete(file); + + // find a free entry in the file table + for (handle = 0; handle < GOB_MAX_FILES; ++handle) { + if (FileTableBasic[handle].block == GOB_INVALID_BLOCK) break; + } + + if (handle >= GOB_MAX_FILES) return GOBERR_TOO_MANY_FILES; + if (handle >= (GOBInt32)ArchiveNumFiles) ArchiveNumFiles = handle + 1; + + // move to the end of the GOB + if (FSFuncs.seek(ArchiveHandle, 0, GOBSEEK_END)) { + return GOBERR_FILE_WRITE; + } + + // alloc compression buffer + out = (GOBChar*)MemFuncs.alloc(GOB_BLOCK_SIZE + GOB_COMPRESS_OVERHEAD); + + last_block = GOB_MAX_BLOCKS - 1; + + for (pos = 0; pos < size; pos += GOB_BLOCK_SIZE) { + GOBUInt32 block; + GOBUInt32 in_size; + + // get a free block + block = FindFreeBlock(); + if (block >= GOB_MAX_BLOCKS) return GOBERR_TOO_MANY_BLOCKS; + if (block >= ArchiveNumBlocks) ArchiveNumBlocks = block + 1; + + // if this is not the first block, mark next block for the last block + // else assign the first block in file table + if (pos != 0) BlockTable[last_block].next = block; + else FileTableBasic[handle].block = block; + + // invalidate the next block + BlockTable[block].next = GOB_MAX_BLOCKS; + + // compute the decompressed block size + in_size = size - pos; + if (in_size > GOB_BLOCK_SIZE) in_size = GOB_BLOCK_SIZE; + + // compress block + + for ( + codec_index = 0; + codec_index < CodecFuncs.codecs; + codec_index++) + { + if ( ! (GOB_CODEC_MASK(codec_index) & codec_mask) ) + { + // skip if this codec is not listed as one of the allowed ones + continue; + } + BlockTable[block].size = GOB_BLOCK_SIZE + GOB_COMPRESS_OVERHEAD; + out_data = out; + *(GOBUInt32*)out_data = SwapBytes(GOBMARKER_STARTBLOCK); + out_data += sizeof(GOBUInt32); + *out_data = CodecFuncs.codec[codec_index].tag; + out_data++; + if (CodecFuncs.codec[codec_index].compress(&((GOBChar*)buffer)[pos], + in_size, out_data, &BlockTable[block].size)) + { + return GOBERR_COMPRESS_FAIL; + } + out_data += BlockTable[block].size; + *(GOBUInt32*)out_data = SwapBytes(GOBMARKER_ENDBLOCK); + out_data += sizeof(GOBUInt32); + + // Adjust for the prefixed start-of-block marker and codec tag and trailing end-of-block marker + compressed_size = BlockTable[block].size; + BlockTable[block].size += 1 + sizeof(GOBUInt32) * 2; + + // Check compression result + compression_ratio = compressed_size * 100 / in_size; + if (compression_ratio <= CodecFuncs.codec[codec_index].max_ratio) + { + // Compressed result is under par. Let's go with it + break; + } + + // Otherwise, try the next compressor + } + + // If no suitable codecs were found, take our ball and go home + if (codec_index == CodecFuncs.codecs) return GOBERR_NO_SUITABLE_CODEC; + + // compute and store the CRC + BlockCRC[block] = adler32(0L, Z_NULL, 0); + BlockCRC[block] = adler32(BlockCRC[block], (const unsigned char*)out, + BlockTable[block].size); + + // write block + if (FSFuncs.write(ArchiveHandle, out, BlockTable[block].size) != + (signed)BlockTable[block].size) + { + return GOBERR_FILE_WRITE; + } + + // compute the slack (to keep alignment) + slack = GOBGetSlack(BlockTable[block].size); + + // write the slack space + memset(out, 0, slack); + if (FSFuncs.write(ArchiveHandle, out, slack) != slack) { + return GOBERR_FILE_WRITE; + } + + BlockTable[block].offset = ArchiveSize; + ArchiveSize += BlockTable[block].size + slack; + + last_block = block; + } + + MemFuncs.free(out); + + lfile = LowerCase(file); + + // calculate file name hash + hash = crc32(0L, Z_NULL, 0); + hash = crc32(hash, (const unsigned char*)lfile, strlen(lfile)); + + // make sure hash is unique + for (i = 0; i < GOB_MAX_FILES; ++i) { + if (i != handle && + FileTableBasic[i].block != GOB_INVALID_BLOCK && + FileTableBasic[i].hash == hash) + { + return GOBERR_DUP_HASH; + } + } + + // update the file tables + FileTableBasic[handle].hash = hash; + FileTableBasic[handle].size = size; + + strcpy(FileTableExt[handle].name, lfile); + + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (const unsigned char*)buffer, size); + FileTableExt[handle].crc = crc; + + FileTableExt[handle].time = mtime; + + FileTableDirty = GOB_TRUE; + return GOBERR_OK; +} + +// GOBDelete +// Public function. Delete a file from a GOB. The file should not be open! +GOBError GOBDelete(const GOBChar* file) +{ + GOBInt32 entry; + GOBChar* lfile; + GOBUInt32 block; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + if (!FileTableExt) return GOBERR_NO_EXTENDED; + if (!BlockCRC) return GOBERR_NO_EXTENDED; + + // find the file in the table + lfile = LowerCase(file); + + entry = GetFileTableEntry(lfile); + if (entry == -1) return GOBERR_FILE_NOT_FOUND; + + // invalidate blocks + block = FileTableBasic[entry].block; + do { + GOBUInt32 next; + next = BlockTable[block].next; + BlockTable[block].next = GOB_INVALID_BLOCK; + block = next; + } while(block != GOB_MAX_BLOCKS); + + // invalidate the file + FileTableBasic[entry].block = GOB_INVALID_BLOCK; + + FileTableDirty = GOB_TRUE; + + return GOBERR_OK; +} + +// GOBRearrange +// Public function. Sorts the blocks in an archive. +GOBError GOBRearrange(const GOBChar* file, const GOBUInt32* xlat, GOBFileSysRenameFunc _rename) +{ + GOBError err; + GOBVoid* buffer; + GOBInt32 slack; + GOBVoid* slack_buf; + GOBUInt32 i; + GOBUInt32 size; + GOBFSHandle temp_handle; + GOBChar full_name[GOB_MAX_FILE_NAME_LEN]; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (!InvalidHandle(ArchiveHandle)) return GOBERR_ALREADY_OPEN; + if (!FileTableExt) return GOBERR_NO_EXTENDED; + if (!BlockCRC) return GOBERR_NO_EXTENDED; + + // start things up + err = GOBArchiveOpen(file, GOBACCESS_READ, GOB_TRUE, GOB_TRUE); + if (err != GOBERR_OK) return err; + + // create temporary file + temp_handle = FSFuncs.open("~temp.tmp", GOBACCESS_WRITE); + if (InvalidHandle(temp_handle)) return GOBERR_FILE_WRITE; + + size = 0; + + // create an empty buffer for slack + slack_buf = MemFuncs.alloc(GOB_BLOCK_ALIGNMENT); + if (!slack_buf) return GOBERR_NO_MEMORY; + memset(slack_buf, 0, GOB_BLOCK_ALIGNMENT); + + // get memory for block + buffer = MemFuncs.alloc(GOB_BLOCK_SIZE + GOB_COMPRESS_OVERHEAD); + if (!buffer) return GOBERR_NO_MEMORY; + + // copy files in new order to end of archive + for (i = 0; i < ArchiveNumBlocks; ++i) { + if (BlockTable[xlat[i]].next != GOB_INVALID_BLOCK) { + // seek to the block + if (FSFuncs.seek(ArchiveHandle, + BlockTable[xlat[i]].offset, GOBSEEK_START)) + { + return GOBERR_FILE_READ; + } + + // read the block + if (FSFuncs.read(ArchiveHandle, buffer, BlockTable[xlat[i]].size) != + (signed)BlockTable[xlat[i]].size) + { + return GOBERR_FILE_READ; + } + + // write block + if (FSFuncs.write(temp_handle, buffer, BlockTable[xlat[i]].size) != + (signed)BlockTable[xlat[i]].size) + { + return GOBERR_FILE_WRITE; + } + + // write the slack + slack = GOBGetSlack(BlockTable[xlat[i]].size); + if (FSFuncs.write(temp_handle, slack_buf, slack) != slack) { + return GOBERR_FILE_WRITE; + } + + // update block pos + BlockTable[xlat[i]].offset = size; + size += BlockTable[xlat[i]].size + slack; + } + } + + MemFuncs.free(buffer); + MemFuncs.free(slack_buf); + + // close the archive + err = GOBArchiveClose(); + if (err != GOBERR_OK) return err; + + // close temp file + FSFuncs.close(&temp_handle); + + // overrwrite archive with temp file + _snprintf(full_name, GOB_MAX_FILE_NAME_LEN, "%s.gob", file); + if (_rename("~temp.tmp", full_name)) return GOBERR_FILE_RENAME; + + ArchiveSize = size; + + CommitFileTable(); + + return GOBERR_OK; +} + + +// GOBVerify +// Public function. Verifies the integrity of a file. +GOBError GOBVerify(const GOBChar* file, GOBBool* status) +{ + GOBHandle handle; + GOBError err; + GOBVoid* buffer; + GOBUInt32 size, junk; + GOBUInt32 crc; + GOBInt32 entry; + GOBChar* lfile; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + if (!FileTableExt) return GOBERR_NO_EXTENDED; + if (!BlockCRC) return GOBERR_NO_EXTENDED; + + // get the file size + size = 0; // assign to avoid compiler warning + err = GOBGetSize(file, &size, &junk, &junk); + if (err != GOBERR_OK) return err; + + // open the file + err = GOBOpen((GOBChar*)file, &handle); + if (err != GOBERR_OK) return err; + + lfile = LowerCase(file); + entry = GetFileTableEntry(lfile); + + // alloc space for the file + buffer = MemFuncs.alloc(size); + if (!buffer) return GOBERR_NO_MEMORY; + + // read it into the buffer + crc = GOBRead(buffer, size, handle); + if (crc != size) return GOBERR_FILE_READ; + + // calc the crc + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (const unsigned char*)buffer, size); + + MemFuncs.free(buffer); + + // verify the crc matches + if (crc != FileTableExt[entry].crc) *status = GOB_FALSE; + else *status = GOB_TRUE; + + err = GOBClose(handle); + if (err != GOBERR_OK) return err; + + return GOBERR_OK; +} + +// GOBGetSize +// Public function. Get a file compressed, decompressed, slack sizes. +GOBError GOBGetSize(const GOBChar* file, + GOBUInt32* decomp, GOBUInt32* comp, GOBUInt32* slack) +{ + GOBInt32 entry; + GOBChar* lfile; + GOBUInt32 block; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + + // get file table entry + lfile = LowerCase(file); + entry = GetFileTableEntry(lfile); + if (entry == -1) return GOBERR_FILE_NOT_FOUND; + + // decompressed size from file table + *decomp = FileTableBasic[entry].size; + + // compressed size is sum of block sizes + *comp = 0; + *slack = 0; + block = FileTableBasic[entry].block; + while (block != GOB_MAX_BLOCKS) { + *comp += BlockTable[block].size; + *slack += GOBGetSlack(BlockTable[block].size); + block = BlockTable[block].next; + } + + return GOBERR_OK; +} + +// GOBGetTime +// Public function. Get a file modification time. +GOBError GOBGetTime(const GOBChar* file, GOBUInt32* time) +{ + GOBInt32 entry; + GOBChar* lfile; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + if (!FileTableExt) return GOBERR_NO_EXTENDED; + if (!BlockCRC) return GOBERR_NO_EXTENDED; + + lfile = LowerCase(file); + entry = GetFileTableEntry(lfile); + if (entry == -1) return GOBERR_FILE_NOT_FOUND; + + *time = FileTableExt[entry].time; + return GOBERR_OK; +} + +// GOBGetCRC +// Public function. Get a file CRC. +GOBError GOBGetCRC(const GOBChar* file, GOBUInt32* crc) +{ + GOBInt32 entry; + GOBChar* lfile; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + if (!FileTableExt) return GOBERR_NO_EXTENDED; + if (!BlockCRC) return GOBERR_NO_EXTENDED; + + lfile = LowerCase(file); + entry = GetFileTableEntry(lfile); + if (entry == -1) return GOBERR_FILE_NOT_FOUND; + + *crc = FileTableExt[entry].crc; + return GOBERR_OK; +} + +// GOBAccess +// Public function. Determine if a file exists in the archive. +GOBError GOBAccess(const GOBChar* file, GOBBool* status) +{ + GOBInt32 entry; + GOBChar* lfile; + + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + + lfile = LowerCase(file); + entry = GetFileTableEntry(lfile); + if (entry == -1) *status = GOB_FALSE; + else *status = GOB_TRUE; + + return GOBERR_OK; +} + +// GOBGetFileCode +// Public function. Find the index into the file table of a file. +GOBInt32 GOBGetFileCode(const GOBChar* file) +{ + GOBInt32 entry; + GOBChar* lfile; + + if (!LibraryInit) return -1; + if (InvalidHandle(ArchiveHandle)) return -1; + + lfile = LowerCase(file); + entry = GetFileTableEntry(lfile); + + return entry; +} + +// GOBGetFileTables +// Public function. Return the active file tables. +GOBError GOBGetFileTables(struct GOBFileTableBasicEntry** basic, + struct GOBFileTableExtEntry** ext) +{ + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + *basic = FileTableBasic; + *ext = FileTableExt; + return GOBERR_OK; +} + +// GOBGetBlockTable +// Public function. Return the active block table. +GOBError GOBGetBlockTable(struct GOBBlockTableEntry** table, GOBUInt32* num) +{ + if (!LibraryInit) return GOBERR_NOT_INIT; + if (InvalidHandle(ArchiveHandle)) return GOBERR_NOT_OPEN; + *table = BlockTable; + *num = ArchiveNumBlocks; + return GOBERR_OK; +} + +// GOBSetCacheSize +// Public function. Allocates buffers to cache blocks. +GOBError GOBSetCacheSize(GOBUInt32 num) +{ + GOBUInt32 i; + + if (!LibraryInit) return GOBERR_NOT_INIT; + + // only continue if we actually need to resize + if (num == NumCacheBlocks) return GOBERR_OK; + + // free old cache buffers + FreeCache(); + + NumCacheBlocks = 0; + + CacheBlocks = (struct GOBBlockCache*)MemFuncs.alloc( + sizeof(struct GOBBlockCache) * num); + if (!CacheBlocks) return GOBERR_NO_MEMORY; + + // allocate cache blocks and initialize + for (i = 0; i < num; ++i) { + CacheBlocks[i].data = (GOBChar*)MemFuncs.alloc(GOB_BLOCK_SIZE); + if (!CacheBlocks[i].data) return GOBERR_NO_MEMORY; + + CacheBlocks[i].size = 0; + CacheBlocks[i].time = 0; + CacheBlocks[i].block = 0xFFFFFFFF; + + ++NumCacheBlocks; + } + + return GOBERR_OK; +} + +// GOBSetReadBufferSize +// Public function. Allocate a read ahead buffer. +GOBError GOBSetReadBufferSize(GOBUInt32 size) +{ + if (!LibraryInit) return GOBERR_NOT_INIT; + + // only continue if we actually need to resize + if (size == ReadBuffer.size) return GOBERR_OK; + + // remove old buffer + if (ReadBuffer.data) MemFuncs.free(ReadBuffer.data); + + // allocate new buffer + ReadBuffer.data = (GOBChar*)MemFuncs.alloc(size + GOB_MEM_ALIGNMENT); + if (!ReadBuffer.data) return GOB_INVALID_SIZE; + + // set aligned pointer + ReadBuffer.dataStart = + &ReadBuffer.data[GOB_MEM_ALIGNMENT - + ((GOBUInt32)(ReadBuffer.data) % GOB_MEM_ALIGNMENT)]; + + ReadBuffer.pos = 0xFFFFFFFF; + ReadBuffer.size = size; + + return GOBERR_OK; +} + +// GOBGetReadStats +// Public function. Get file read statistics (seeks, sizes). +struct GOBReadStats GOBGetReadStats(GOBVoid) +{ + return ReadStats; +} + + +GOBVoid GOBSetProfileFuncs(struct GOBProfileFuncSet* fset) +{ + ProfileReadCallback = fset->read; +} + +GOBError GOBStartProfile(GOBVoid) +{ + if (ProfileEnabled) return GOBERR_PROFILE_ON; + ProfileEnabled = GOB_TRUE; + return GOBERR_OK; +} + +GOBError GOBStopProfile(GOBVoid) +{ + if (!ProfileEnabled) return GOBERR_PROFILE_OFF; + ProfileEnabled = GOB_FALSE; + return GOBERR_OK; +} diff --git a/code/goblib/goblib.h b/code/goblib/goblib.h new file mode 100644 index 0000000..7316028 --- /dev/null +++ b/code/goblib/goblib.h @@ -0,0 +1,299 @@ +/***************************************** + * + * GOB File System + * + * Here's what Merriam-Webster says about "gob": --Chuck + * Entry: gob + * Function: noun + * Etymology: Middle English gobbe, from Middle French gobe large piece of food, + * back-formation from gobet + * Date: 14th century + * 1 : LUMP + * 2 : a large amount -- usually used in plural + * + * Purpose: Provide fast, efficient disk access on a variety of platforms. + * + * Implementation: + * The GOB system maintains two files -- GOB and GFC. The GOB file is actually + * an archive of many files split into variable size, compressed blocks. The GFC, + * GOB File Control, contains 3 tables -- a block table, basic file table, and + * extended file table. The block table is analogous to a DOS FAT. The basic + * file table contains a minimal set of file information to handle basic reading + * tasks. The extended file table is optionally loaded and contains additional + * file information. File names are case insensitive. + * + * Files can be read in a normal manner. Open, read, seek and close + * operations are all provided. Files can only be written in a single + * contiguous chunk of blocks at the end of an archive. Reads are processed + * through a configurable number of read ahead buffers to in an effort to + * minimize both reads and seeks. Other operations including delete, verify, + * access, and get size are also supported on files inside an archive. + * + * The system supports read profiling. By supplying a file read callback + * function, the library will output the block number of each read. This can + * be used rearrange block in the archive to minimize seek times. The + * GOBRearrange sorts files in an archive. + * + * Supports block based caching. Primarily aimed at caching files off a DVD/CD + * to a faster hard disk. + * + * Future Work: + * + * Dependencies: vvInt, snprintf, zlib + * Owner: Chris McEvoy + * History: + * 09/23/2001 Original version + * 10/28/2002 Merged into vvtech + * + * Copyright (C) 2002, Vicarious Visions, Inc. All Rights Reserved. + * + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + * + *****************************************/ + +/* + This is an unofficial branch of GOB, for Jedi Academy + Maintainer: Brian Osman +*/ + +#ifndef GOBLIB_H__ +#define GOBLIB_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define GOB_MAGIC_IDENTIFIER 0x8008 +#define GOB_MAX_FILE_NAME_LEN 96 +#define GOB_MAX_OPEN_FILES 16 +#define GOB_MAX_CODECS 2 +#define GOB_INFINITE_RATIO 1000 +#define GOB_READ_RETRYS 3 + +#define GOB_MAX_FILES (16*1024) +#define GOB_MAX_BLOCKS 32767 + +#define GOB_BLOCK_SIZE (64*1024) +#define GOB_BLOCK_ALIGNMENT 2048 +#define GOB_MEM_ALIGNMENT 64 +#define GOB_COMPRESS_OVERHEAD 1024 + +#define GOB_INVALID_SIZE 0xFFFFFFFF +#define GOB_INVALID_BLOCK 0xFFFFFFFF + +#define GOB_TRUE 1 +#define GOB_FALSE 0 + +#define GOBERR_OK 0 +#define GOBERR_NOT_INIT 1 +#define GOBERR_FILE_NOT_FOUND 2 +#define GOBERR_FILE_READ 3 +#define GOBERR_FILE_WRITE 4 +#define GOBERR_NO_MEMORY 5 +#define GOBERR_ALREADY_INIT 6 +#define GOBERR_ALREADY_OPEN 7 +#define GOBERR_INVALID_ACCESS 8 +#define GOBERR_NOT_GOB_FILE 9 +#define GOBERR_NOT_OPEN 10 +#define GOBERR_CANNOT_CREATE 11 +#define GOBERR_TOO_MANY_OPEN 12 +#define GOBERR_INVALID_SEEK 13 +#define GOBERR_TOO_MANY_FILES 14 +#define GOBERR_FILE_RENAME 15 +#define GOBERR_PROFILE_OFF 16 +#define GOBERR_PROFILE_ON 17 +#define GOBERR_NO_EXTENDED 18 +#define GOBERR_DUP_HASH 19 +#define GOBERR_TOO_MANY_BLOCKS 20 +#define GOBERR_COMPRESS_FAIL 21 +#define GOBERR_NO_SUITABLE_CODEC 22 + +#define GOBACCESS_READ 0 +#define GOBACCESS_WRITE 1 +#define GOBACCESS_RW 2 + +#define GOBSEEK_START 0 +#define GOBSEEK_CURRENT 1 +#define GOBSEEK_END 2 + +#define GOB_CODEC_MASK(n) ((GOBUInt32)(1u<<(n))) +#define GOB_CODEC_MASK_ANY ((GOBUInt32)(-1)) + +#define GOBMARKER_STARTBLOCK ('L' | 'B' << 8 | 'T' << 16 | 'S' << 24) +#define GOBMARKER_ENDBLOCK ('L' | 'B' << 8 | 'N' << 16 | 'E' << 24) + +typedef int int32; +typedef unsigned int uint32; +//#define bool int +//#define false 0 +//#define true 1 +typedef unsigned long ulong; +typedef unsigned char byte; + +typedef int32 GOBInt32; +typedef uint32 GOBUInt32; +typedef char GOBChar; +typedef bool GOBBool; +typedef int32 GOBError; +typedef int32 GOBSeekType; +typedef int32 GOBHandle; +typedef int32 GOBAccessType; +typedef void* GOBFSHandle; +typedef void GOBVoid; + +typedef GOBFSHandle (*GOBFileSysOpenFunc)(GOBChar*, GOBAccessType); +typedef GOBBool (*GOBFileSysCloseFunc)(GOBFSHandle*); +typedef GOBInt32 (*GOBFileSysReadFunc)(GOBFSHandle, GOBVoid*, GOBInt32); +typedef GOBInt32 (*GOBFileSysWriteFunc)(GOBFSHandle, GOBVoid*, GOBInt32); +typedef GOBInt32 (*GOBFileSysSeekFunc)(GOBFSHandle, GOBInt32, GOBSeekType); +typedef GOBInt32 (*GOBFileSysRenameFunc)(GOBChar*, GOBChar*); + +typedef GOBVoid* (*GOBMemAllocFunc)(GOBUInt32); +typedef GOBVoid (*GOBMemFreeFunc)(GOBVoid*); + +typedef GOBInt32 (*GOBCompressFunc)(GOBVoid*, GOBUInt32, GOBVoid*, GOBUInt32*); +typedef GOBInt32 (*GOBDecompressFunc)(GOBVoid*, GOBUInt32, GOBVoid*, GOBUInt32*); + +typedef GOBBool (*GOBCacheFileOpenFunc)(GOBUInt32); +typedef GOBBool (*GOBCacheFileCloseFunc)(GOBVoid); +typedef GOBInt32 (*GOBCacheFileReadFunc)(GOBVoid*, GOBInt32); +typedef GOBInt32 (*GOBCacheFileWriteFunc)(GOBVoid*, GOBInt32); +typedef GOBInt32 (*GOBCacheFileSeekFunc)(GOBInt32); + +struct GOBBlockTableEntry +{ + GOBUInt32 size; // compressed size + GOBUInt32 offset; + GOBUInt32 next; +}; + +struct GOBFileTableBasicEntry +{ + GOBUInt32 hash; + GOBUInt32 size; // decompressed size + GOBUInt32 block; +}; + +struct GOBFileTableExtEntry +{ + GOBChar name[GOB_MAX_FILE_NAME_LEN]; + GOBUInt32 crc; + GOBUInt32 time; +}; + +struct GOBMemoryFuncSet +{ + GOBMemAllocFunc alloc; + GOBMemFreeFunc free; +}; + +struct GOBSingleCodecDesc +{ + GOBChar tag; + GOBInt32 max_ratio; + GOBCompressFunc compress; + GOBDecompressFunc decompress; +}; + +struct GOBCodecFuncSet +{ + GOBInt32 codecs; + struct GOBSingleCodecDesc codec[GOB_MAX_CODECS]; +}; + +struct GOBFileSysFuncSet +{ + GOBFileSysOpenFunc open; + GOBFileSysCloseFunc close; + GOBFileSysReadFunc read; + GOBFileSysWriteFunc write; + GOBFileSysSeekFunc seek; +}; + +struct GOBCacheFileFuncSet +{ + GOBCacheFileOpenFunc open; + GOBCacheFileCloseFunc close; + GOBCacheFileReadFunc read; + GOBCacheFileWriteFunc write; + GOBCacheFileSeekFunc seek; +}; + +struct GOBReadStats +{ + GOBUInt32 bufferUsed; + GOBUInt32 bytesRead; + GOBUInt32 cacheBytesRead; + GOBUInt32 cacheBytesWrite; + GOBUInt32 totalSeeks; + GOBUInt32 farSeeks; + GOBUInt32 filesOpened; +}; + +extern GOBError GOBInit(struct GOBMemoryFuncSet* mem, + struct GOBFileSysFuncSet* file, + struct GOBCodecFuncSet* codec, + struct GOBCacheFileFuncSet* cache); +extern GOBError GOBShutdown(GOBVoid); + +extern GOBError GOBArchiveCreate(const GOBChar* file); +extern GOBError GOBArchiveOpen(const GOBChar* file, GOBAccessType atype, + GOBBool extended, GOBBool safe); +extern GOBError GOBArchiveClose(GOBVoid); +extern GOBError GOBArchiveCheckMarkers(GOBVoid); + +extern GOBError GOBOpen(GOBChar* file, GOBHandle* handle); +extern GOBError GOBOpenCode(GOBInt32 code, GOBHandle* handle); +extern GOBError GOBClose(GOBHandle handle); + +extern GOBUInt32 GOBRead(GOBVoid* buffer, GOBUInt32 size, GOBHandle handle); +extern GOBError GOBSeek(GOBHandle handle, GOBUInt32 offset, GOBSeekType type, GOBUInt32* pos); + +extern GOBError GOBWrite(GOBVoid* buffer, GOBUInt32 size, GOBUInt32 mtime, const GOBChar* file, GOBUInt32 codec_mask); +extern GOBError GOBDelete(const GOBChar* file); + +extern GOBError GOBRearrange(const GOBChar* file, const GOBUInt32* xlat, GOBFileSysRenameFunc _rename); + +extern GOBError GOBVerify(const GOBChar* file, GOBBool* status); + +extern GOBError GOBGetSize(const GOBChar* file, GOBUInt32* decomp, GOBUInt32* comp, GOBUInt32* slack); +extern GOBError GOBGetTime(const GOBChar* file, GOBUInt32* time); +extern GOBError GOBGetCRC(const GOBChar* file, GOBUInt32* crc); + +extern GOBError GOBAccess(const GOBChar* file, GOBBool* status); +extern GOBInt32 GOBGetFileCode(const GOBChar* file); + +extern GOBError GOBGetFileTables(struct GOBFileTableBasicEntry** basic, struct GOBFileTableExtEntry** ext); +extern GOBError GOBGetBlockTable(struct GOBBlockTableEntry** table, GOBUInt32* num); +extern GOBUInt32 GOBGetSlack(GOBUInt32 x); + +extern GOBError GOBSetCacheSize(GOBUInt32 num); +extern GOBError GOBSetReadBufferSize(GOBUInt32 size); + +extern struct GOBReadStats GOBGetReadStats(GOBVoid); + + +typedef GOBVoid (*GOBProfileReadFunc)(GOBUInt32); +struct GOBProfileFuncSet +{ + GOBProfileReadFunc read; +}; +extern GOBVoid GOBSetProfileFuncs(struct GOBProfileFuncSet* fset); + +extern GOBError GOBStartProfile(GOBVoid); +extern GOBError GOBStopProfile(GOBVoid); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +#endif /* GOBLIB_H__ */ diff --git a/code/goblib/goblib.vcproj b/code/goblib/goblib.vcproj new file mode 100644 index 0000000..0f7bbdc --- /dev/null +++ b/code/goblib/goblib.vcproj @@ -0,0 +1,824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/goblib/goblib.vcproj.old b/code/goblib/goblib.vcproj.old new file mode 100644 index 0000000..978693d --- /dev/null +++ b/code/goblib/goblib.vcproj.old @@ -0,0 +1,822 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/icarus/BlockStream.cpp b/code/icarus/BlockStream.cpp new file mode 100644 index 0000000..552796d --- /dev/null +++ b/code/icarus/BlockStream.cpp @@ -0,0 +1,586 @@ +// Interpreted Block Stream Functions +// +// -- jweier + +// this include must remain at the top of every Icarus CPP file +#include "stdafx.h" + +#include "IcarusInterface.h" +#include "IcarusImplementation.h" + +#include "BlockStream.h" + +/* +=================================================================================================== + + CBlockMember + +=================================================================================================== +*/ + +inline CBlockMember::CBlockMember( void ) +{ + m_id = -1; + m_size = -1; + m_data = NULL; +} + +inline CBlockMember::~CBlockMember( void ) +{ +} + +/* +------------------------- +Free +------------------------- +*/ + +void CBlockMember::Free(IGameInterface* game) +{ + if ( m_data != NULL ) + { + game->Free ( m_data ); + m_data = NULL; + + m_id = m_size = -1; + } + delete this; +} + +/* +------------------------- +GetInfo +------------------------- +*/ + +void CBlockMember::GetInfo( int *id, int *size, void **data ) +{ + *id = m_id; + *size = m_size; + *data = m_data; +} + +/* +------------------------- +SetData overloads +------------------------- +*/ + +void CBlockMember::SetData( const char *data , CIcarus* icarus) +{ + WriteDataPointer( data, strlen(data)+1, icarus ); +} + +void CBlockMember::SetData( vec3_t data , CIcarus* icarus) +{ + WriteDataPointer( data, 3 , icarus); +} + +void CBlockMember::SetData( void *data, int size, CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + if ( m_data ) + game->Free( m_data ); + + m_data = game->Malloc( size ); + memcpy( m_data, data, size ); + m_size = size; +} + +// Member I/O functions + +/* +------------------------- +ReadMember +------------------------- +*/ + +int CBlockMember::ReadMember( char **stream, long *streamPos, CIcarus* icarus ) +{ + IGameInterface* game = icarus->GetGame(); + m_id = *(int *) (*stream + *streamPos); + *streamPos += sizeof( int ); + + if ( m_id == CIcarus::ID_RANDOM ) + {//special case, need to initialize this member's data to Q3_INFINITE so we can randomize the number only the first time random is checked when inside a wait + m_size = sizeof( float ); + *streamPos += sizeof( long ); + m_data = game->Malloc( m_size ); + float infinite = game->MaxFloat(); + memcpy( m_data, &infinite, m_size ); + } + else + { + m_size = *(long *) (*stream + *streamPos); + *streamPos += sizeof( long ); + m_data = game->Malloc( m_size ); + memcpy( m_data, (*stream + *streamPos), m_size ); + } + *streamPos += m_size; + + return true; +} + +/* +------------------------- +WriteMember +------------------------- +*/ + +int CBlockMember::WriteMember( FILE *m_fileHandle ) +{ + fwrite( &m_id, sizeof(m_id), 1, m_fileHandle ); + fwrite( &m_size, sizeof(m_size), 1, m_fileHandle ); + fwrite( m_data, m_size, 1, m_fileHandle ); + + return true; +} + +/* +------------------------- +Duplicate +------------------------- +*/ + +CBlockMember *CBlockMember::Duplicate( CIcarus* icarus ) +{ + CBlockMember *newblock = new CBlockMember; + + if ( newblock == NULL ) + return NULL; + + newblock->SetData( m_data, m_size, icarus ); + newblock->SetSize( m_size ); + newblock->SetID( m_id ); + + return newblock; +} + +/* +=================================================================================================== + + CBlock + +=================================================================================================== +*/ + + +/* +------------------------- +Init +------------------------- +*/ + +int CBlock::Init( void ) +{ + m_flags = 0; + m_id = 0; + + return true; +} + +/* +------------------------- +Create +------------------------- +*/ + +int CBlock::Create( int block_id ) +{ + Init(); + + m_id = block_id; + + return true; +} + +/* +------------------------- +Free +------------------------- +*/ + +int CBlock::Free( CIcarus* icarus ) +{ + IGameInterface* game = icarus->GetGame(); + int numMembers = GetNumMembers(); + CBlockMember *bMember; + + while ( numMembers-- ) + { + bMember = GetMember( numMembers ); + + if (!bMember) + return false; + + bMember->Free(game); + } + + m_members.clear(); //List of all CBlockMembers owned by this list + + return true; +} + +// Write overloads + +/* +------------------------- +Write +------------------------- +*/ + +int CBlock::Write( int member_id, const char *member_data, CIcarus* icarus ) +{ + CBlockMember *bMember = new CBlockMember; + + bMember->SetID( member_id ); + + bMember->SetData( member_data, icarus ); + bMember->SetSize( strlen(member_data) + 1 ); + + AddMember( bMember ); + + return true; +} + +int CBlock::Write( int member_id, vec3_t member_data, CIcarus* icarus ) +{ + CBlockMember *bMember; + + bMember = new CBlockMember; + + bMember->SetID( member_id ); + bMember->SetData( member_data, icarus ); + bMember->SetSize( sizeof(vec3_t) ); + + AddMember( bMember ); + + return true; +} + +int CBlock::Write( int member_id, float member_data, CIcarus* icarus ) +{ + CBlockMember *bMember = new CBlockMember; + + bMember->SetID( member_id ); + bMember->WriteData( member_data, icarus ); + bMember->SetSize( sizeof(member_data) ); + + AddMember( bMember ); + + return true; +} + +int CBlock::Write( int member_id, int member_data, CIcarus* icarus ) +{ + CBlockMember *bMember = new CBlockMember; + + bMember->SetID( member_id ); + bMember->WriteData( member_data , icarus); + bMember->SetSize( sizeof(member_data) ); + + AddMember( bMember ); + + return true; +} + + +int CBlock::Write( CBlockMember *bMember, CIcarus* ) +{ +// findme: this is wrong: bMember->SetSize( sizeof(bMember->GetData()) ); + + AddMember( bMember ); + + return true; +} + +// Member list functions + +/* +------------------------- +AddMember +------------------------- +*/ + +int CBlock::AddMember( CBlockMember *member ) +{ + m_members.insert( m_members.end(), member ); + return true; +} + +/* +------------------------- +GetMember +------------------------- +*/ + +CBlockMember *CBlock::GetMember( int memberNum ) +{ + if ( memberNum > GetNumMembers()-1 ) + { + return false; + } + return m_members[ memberNum ]; +} + +/* +------------------------- +GetMemberData +------------------------- +*/ + +void *CBlock::GetMemberData( int memberNum ) +{ + if ( memberNum > GetNumMembers()-1 ) + { + return NULL; + } + return (void *) ((GetMember( memberNum ))->GetData()); +} + +/* +------------------------- +Duplicate +------------------------- +*/ + +CBlock *CBlock::Duplicate( CIcarus* icarus ) +{ + blockMember_v::iterator mi; + CBlock *newblock; + + newblock = new CBlock; + + if ( newblock == NULL ) + return false; + + newblock->Create( m_id ); + + //Duplicate entire block and return the cc + for ( mi = m_members.begin(); mi != m_members.end(); mi++ ) + { + newblock->AddMember( (*mi)->Duplicate(icarus) ); + } + + return newblock; +} + +/* +=================================================================================================== + + CBlockStream + +=================================================================================================== +*/ + +char* CBlockStream::s_IBI_EXT = ".IBI"; //(I)nterpreted (B)lock (I)nstructions +char* CBlockStream::s_IBI_HEADER_ID = "IBI"; +const float CBlockStream::s_IBI_VERSION = 1.57f; + +/* +------------------------- +Free +------------------------- +*/ + +int CBlockStream::Free( void ) +{ + //NOTENOTE: It is assumed that the user will free the passed memory block (m_stream) immediately after the run call + // That's why this doesn't free the memory, it only clears its internal pointer + + m_stream = NULL; + m_streamPos = 0; + + return true; +} + +/* +------------------------- +Create +------------------------- +*/ + +int CBlockStream::Create( char *filename ) +{ + // strip extension + int extensionloc = strlen(filename); + while ( (filename[extensionloc] != '.') && (extensionloc >= 0) ) + { + extensionloc--; + } + if ( extensionloc < 0 ) + { + strcpy(m_fileName, filename); + } + else + { + strncpy(m_fileName, filename, extensionloc); + m_fileName[extensionloc] = '\0'; + } + // add extension + strcat((char *) m_fileName, s_IBI_EXT); + + if ( ((m_fileHandle = fopen(m_fileName, "wb")) == NULL) ) + { + return false; + } + + fwrite( s_IBI_HEADER_ID, 1, sizeof(s_IBI_HEADER_ID), m_fileHandle ); + fwrite( &s_IBI_VERSION, 1, sizeof(s_IBI_VERSION), m_fileHandle ); + + return true; +} + +/* +------------------------- +Init +------------------------- +*/ + +int CBlockStream::Init( void ) +{ + m_fileHandle = NULL; + memset(m_fileName, 0, sizeof(m_fileName)); + + m_stream = NULL; + m_streamPos = 0; + + return true; +} + +// Block I/O functions + +/* +------------------------- +WriteBlock +------------------------- +*/ + +int CBlockStream::WriteBlock( CBlock *block, CIcarus* icarus ) +{ + CBlockMember *bMember; + int id = block->GetBlockID(); + int numMembers = block->GetNumMembers(); + unsigned char flags = block->GetFlags(); + + fwrite ( &id, sizeof(id), 1, m_fileHandle ); + fwrite ( &numMembers, sizeof(numMembers), 1, m_fileHandle ); + fwrite ( &flags, sizeof( flags ), 1, m_fileHandle ); + + for ( int i = 0; i < numMembers; i++ ) + { + bMember = block->GetMember( i ); + bMember->WriteMember( m_fileHandle ); + } + + block->Free(icarus); + + return true; +} + +/* +------------------------- +BlockAvailable +------------------------- +*/ + +int CBlockStream::BlockAvailable( void ) +{ + if ( m_streamPos >= m_fileSize ) + return false; + + return true; +} + +/* +------------------------- +ReadBlock +------------------------- +*/ + +int CBlockStream::ReadBlock( CBlock *get, CIcarus* icarus ) +{ + CBlockMember *bMember; + int b_id, numMembers; + unsigned char flags; + + if (!BlockAvailable()) + return false; + + b_id = *(int *) (m_stream + m_streamPos); + m_streamPos += sizeof( b_id ); + + numMembers = *(int *) (m_stream + m_streamPos); + m_streamPos += sizeof( numMembers ); + + flags = *(unsigned char*) (m_stream + m_streamPos); + m_streamPos += sizeof( flags ); + + if (numMembers < 0) + return false; + + get->Create( b_id ); + get->SetFlags( flags ); + + // Stream blocks are generally temporary as they + // are just used in an initial parsing phase... +#ifdef _XBOX + extern void Z_SetNewDeleteTemporary(bool bTemp); + Z_SetNewDeleteTemporary(true); +#endif + + while ( numMembers-- > 0) + { + bMember = new CBlockMember; + bMember->ReadMember( &m_stream, &m_streamPos, icarus ); + get->AddMember( bMember ); + } + +#ifdef _XBOX + Z_SetNewDeleteTemporary(false); +#endif + + return true; +} + +/* +------------------------- +Open +------------------------- +*/ + +int CBlockStream::Open( char *buffer, long size ) +{ + char id_header[sizeof(s_IBI_HEADER_ID)]; + float version; + + Init(); + + m_fileSize = size; + + m_stream = buffer; + + for ( int i = 0; i < sizeof( id_header ); i++ ) + { + id_header[i] = *(m_stream + m_streamPos++); + } + + version = *(float *) (m_stream + m_streamPos); + m_streamPos += sizeof( version ); + + //Check for valid header + if ( strcmp( id_header, s_IBI_HEADER_ID ) ) + { + Free(); + return false; + } + + //Check for valid version + if ( version != s_IBI_VERSION ) + { + Free(); + return false; + } + + return true; +} diff --git a/code/icarus/IcarusImplementation.cpp b/code/icarus/IcarusImplementation.cpp new file mode 100644 index 0000000..7dea9a3 --- /dev/null +++ b/code/icarus/IcarusImplementation.cpp @@ -0,0 +1,809 @@ +// IcarusImplementation.cpp + +#include "stdafx.h" +#include "IcarusImplementation.h" + +#include "BlockStream.h" +#include "Sequence.h" +#include "TaskManager.h" +#include "Sequencer.h" + +#define STL_ITERATE( a, b ) for ( a = b.begin(); a != b.end(); a++ ) +#define STL_INSERT( a, b ) a.insert( a.end(), b ); + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// required implementation of CIcarusInterface + +IIcarusInterface* IIcarusInterface::GetIcarus(int flavor,bool constructIfNecessary) +{ + if(!CIcarus::s_instances && constructIfNecessary) + { + CIcarus::s_flavorsAvailable = IGameInterface::s_IcarusFlavorsNeeded; + if (!CIcarus::s_flavorsAvailable) + { + return NULL; + } + CIcarus::s_instances = new CIcarus*[CIcarus::s_flavorsAvailable]; + for (int index = 0; index < CIcarus::s_flavorsAvailable; index++) + { + CIcarus::s_instances[index] = new CIcarus(index); + //OutputDebugString( "ICARUS flavor successfully created\n" ); + } + } + + if(flavor >= CIcarus::s_flavorsAvailable || !CIcarus::s_instances ) + { + return NULL; + } + return CIcarus::s_instances[flavor]; +} + +void IIcarusInterface::DestroyIcarus() +{ + for(int index = 0; index < CIcarus::s_flavorsAvailable; index++) + { + delete CIcarus::s_instances[index]; + } + delete[] CIcarus::s_instances; + CIcarus::s_instances = NULL; + CIcarus::s_flavorsAvailable = 0; +} + +IIcarusInterface::~IIcarusInterface() +{ +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CIcarus + +double CIcarus::ICARUS_VERSION = 1.40; + +int CIcarus::s_flavorsAvailable = 0; + +CIcarus** CIcarus::s_instances = NULL; + +CIcarus::CIcarus(int flavor) : + m_flavor(flavor), m_nextSequencerID(0) +{ + + m_GUID = 0; + +#ifdef _DEBUG + + m_DEBUG_NumSequencerAlloc = 0; + m_DEBUG_NumSequencerFreed = 0; + m_DEBUG_NumSequencerResidual = 0; + + m_DEBUG_NumSequenceAlloc = 0; + m_DEBUG_NumSequenceFreed = 0; + m_DEBUG_NumSequenceResidual = 0; + +#endif + + m_ulBufferCurPos = 0; + m_ulBytesRead = 0; + m_byBuffer = NULL; +} + +CIcarus::~CIcarus() +{ + Delete(); +} + +#if defined (_DEBUG) && defined (_WIN32) +#include "../qcommon/platform.h" // for OutputDebugString +#endif + +void CIcarus::Delete( void ) +{ + + Free(); + +#ifdef _DEBUG + + char buffer[1024]; + + OutputDebugString( "\nICARUS Instance Debug Info:\n---------------------------\n" ); + + sprintf( (char *) buffer, "Sequencers Allocated:\t%d\n", m_DEBUG_NumSequencerAlloc ); + OutputDebugString( (const char *) &buffer ); + + sprintf( (char *) buffer, "Sequencers Freed:\t\t%d\n", m_DEBUG_NumSequencerFreed ); + OutputDebugString( (const char *) &buffer ); + + sprintf( (char *) buffer, "Sequencers Residual:\t%d\n\n", m_DEBUG_NumSequencerResidual ); + OutputDebugString( (const char *) &buffer ); + + sprintf( (char *) buffer, "Sequences Allocated:\t%d\n", m_DEBUG_NumSequenceAlloc ); + OutputDebugString( (const char *) &buffer ); + + sprintf( (char *) buffer, "Sequences Freed:\t\t%d\n", m_DEBUG_NumSequenceFreed ); + OutputDebugString( (const char *) &buffer ); + + sprintf( (char *) buffer, "Sequences Residual:\t\t%d\n\n", m_DEBUG_NumSequenceResidual ); + OutputDebugString( (const char *) &buffer ); + + OutputDebugString( "\n" ); + +#endif +} + +void CIcarus::Signal( const char *identifier ) +{ + m_signals[ identifier ] = 1; +} + +bool CIcarus::CheckSignal( const char *identifier ) +{ + signal_m::iterator smi; + + smi = m_signals.find( identifier ); + + if ( smi == m_signals.end() ) + return false; + + return true; +} + +void CIcarus::ClearSignal( const char *identifier ) +{ + m_signals.erase( identifier ); +} + +void CIcarus::Free( void ) +{ + sequencer_l::iterator sri; + + //Delete any residual sequencers + STL_ITERATE( sri, m_sequencers ) + { + (*sri)->Free(this); + +#ifdef _DEBUG + + m_DEBUG_NumSequencerResidual++; + +#endif + + } + + m_sequencers.clear(); + m_signals.clear(); + + sequence_l::iterator si; + + //Delete any residual sequences + STL_ITERATE( si, m_sequences ) + { + (*si)->Delete(this); + delete (*si); + +#ifdef _DEBUG + + m_DEBUG_NumSequenceResidual++; + +#endif + + } + + m_sequences.clear(); + + m_sequencerMap.clear(); +} + +int CIcarus::GetIcarusID( int gameID ) +{ + CSequencer *sequencer = CSequencer::Create(); + CTaskManager *taskManager = CTaskManager::Create(); + + sequencer->Init( gameID, taskManager ); + + taskManager->Init( sequencer ); + + STL_INSERT( m_sequencers, sequencer ); + + m_sequencerMap[sequencer->GetID()] = sequencer; + +#ifdef _DEBUG + + m_DEBUG_NumSequencerAlloc++; + +#endif + + return sequencer->GetID(); +} + +void CIcarus::DeleteIcarusID( int& icarusID ) +{ + CSequencer* sequencer = FindSequencer(icarusID); + if(!sequencer) + { + icarusID = -1; + return; + } + + CTaskManager *taskManager = sequencer->GetTaskManager(); + if (taskManager->IsResident()) + { + IGameInterface::GetGame()->DebugPrint( IGameInterface::WL_ERROR, "Refusing DeleteIcarusID(%d) because it is running!\n", icarusID); + assert(0); + return; + } + + m_sequencerMap.erase(icarusID); + + // added 2/12/2 to properly delete blocks that were passed to the task manager + sequencer->Recall(this); + + + if ( taskManager ) + { + taskManager->Free(); + delete taskManager; + } + + m_sequencers.remove( sequencer ); + + sequencer->Free(this); + +#ifdef _DEBUG + + m_DEBUG_NumSequencerFreed++; + +#endif + icarusID = -1; +} + +CSequence *CIcarus::GetSequence( void ) +{ + CSequence *sequence = CSequence::Create(); + + //Assign the GUID + sequence->SetID( m_GUID++ ); + + STL_INSERT( m_sequences, sequence ); + +#ifdef _DEBUG + + m_DEBUG_NumSequenceAlloc++; + +#endif + + return sequence; +} + +CSequence *CIcarus::GetSequence( int id ) +{ + sequence_l::iterator si; + STL_ITERATE( si, m_sequences ) + { + if ( (*si)->GetID() == id ) + return (*si); + } + + return NULL; +} + +void CIcarus::DeleteSequence( CSequence *sequence ) +{ + m_sequences.remove( sequence ); + + sequence->Delete(this); + delete sequence; + +#ifdef _DEBUG + + m_DEBUG_NumSequenceFreed++; + +#endif +} + +int CIcarus::AllocateSequences( int numSequences, int *idTable ) +{ + CSequence *sequence; + + for ( int i = 0; i < numSequences; i++ ) + { + //If the GUID of this sequence is higher than the current, take this a the "current" GUID + if ( idTable[i] > m_GUID ) + m_GUID = idTable[i]; + + //Allocate the container sequence + if ( ( sequence = GetSequence() ) == NULL ) + return false; + + //Override the given GUID with the real one + sequence->SetID( idTable[i] ); + } + + return true; +} + +void CIcarus::Precache(char* buffer, long length) +{ + IGameInterface* game = IGameInterface::GetGame(m_flavor); + CBlockStream stream; + CBlockMember *blockMember; + CBlock block; + + if ( stream.Open( buffer, length ) == 0 ) + return; + + const char *sVal1, *sVal2; + + //Now iterate through all blocks of the script, searching for keywords + while ( stream.BlockAvailable() ) + { + //Get a block + if ( stream.ReadBlock( &block, this ) == 0 ) + return; + + //Determine what type of block this is + switch( block.GetBlockID() ) + { + case ID_CAMERA: // to cache ROFF files + { + float f = *(float *) block.GetMemberData( 0 ); + + if (f == TYPE_PATH) + { + sVal1 = (const char *) block.GetMemberData( 1 ); + + game->PrecacheRoff(sVal1); + } + } + break; + + case ID_PLAY: // to cache ROFF files + + sVal1 = (const char *) block.GetMemberData( 0 ); + + if (!stricmp(sVal1,"PLAY_ROFF")) + { + sVal1 = (const char *) block.GetMemberData( 1 ); + + game->PrecacheRoff(sVal1); + } + break; + + //Run commands + case ID_RUN: + sVal1 = (const char *) block.GetMemberData( 0 ); + game->PrecacheScript( sVal1 ); + break; + + case ID_SOUND: + sVal1 = (const char *) block.GetMemberData( 1 ); //0 is channel, 1 is filename + game->PrecacheSound(sVal1); + break; + + case ID_SET: + blockMember = block.GetMember( 0 ); + + //NOTENOTE: This will not catch special case get() inlines! (There's not really a good way to do that) + + //Make sure we're testing against strings + if ( blockMember->GetID() == TK_STRING ) + { + sVal1 = (const char *) block.GetMemberData( 0 ); + sVal2 = (const char *) block.GetMemberData( 1 ); + + game->PrecacheFromSet( sVal1 , sVal2); + } + break; + + default: + break; + } + + //Clean out the block for the next pass + block.Free(this); + } + + //All done + stream.Free(); +} + +CSequencer* CIcarus::FindSequencer(int sequencerID) +{ + sequencer_m::iterator mi = m_sequencerMap.find( sequencerID ); + + if ( mi == m_sequencerMap.end() ) + return NULL; + + return (*mi).second; +} + +int CIcarus::Run(int icarusID, char* buffer, long length) +{ + CSequencer* sequencer = FindSequencer(icarusID); + if(sequencer) + { + return sequencer->Run(buffer, length, this); + } + return ICARUS_INVALID; +} + +int CIcarus::SaveSequenceIDTable() +{ + //Save out the number of sequences to follow + int numSequences = m_sequences.size(); + + BufferWrite( &numSequences, sizeof( numSequences ) ); + + //Sequences are saved first, by ID and information + sequence_l::iterator sqi; + + //First pass, save all sequences ID for reconstruction + int *idTable = new int[ numSequences ]; + int itr = 0; + + if ( idTable == NULL ) + return false; + + STL_ITERATE( sqi, m_sequences ) + { + idTable[itr++] = (*sqi)->GetID(); + } + + //game->WriteSaveData( 'SQTB', idTable, sizeof( int ) * numSequences ); + BufferWrite( idTable, sizeof( int ) * numSequences ); + + delete[] idTable; + + return true; +} + +int CIcarus::SaveSequences() +{ + //Save out a listing of all the used sequences by ID + SaveSequenceIDTable(); + + //Save all the information in order + sequence_l::iterator sqi; + STL_ITERATE( sqi, m_sequences ) + { + (*sqi)->Save(); + } + + return true; +} + +int CIcarus::SaveSequencers() +{ + //Save out the number of sequences to follow + int numSequencers = m_sequencers.size(); + BufferWrite( &numSequencers, sizeof( numSequencers ) ); + + //The sequencers are then saved + int sequencessaved = 0; + sequencer_l::iterator si; + STL_ITERATE( si, m_sequencers ) + { + (*si)->Save(); + sequencessaved++; + } + + assert( sequencessaved == numSequencers ); + + return true; +} + +int CIcarus::SaveSignals() +{ + int numSignals = m_signals.size(); + + //game->WriteSaveData( 'ISIG', &numSignals, sizeof( numSignals ) ); + BufferWrite( &numSignals, sizeof( numSignals ) ); + + signal_m::iterator si; + STL_ITERATE( si, m_signals ) + { + //game->WriteSaveData( 'ISIG', &numSignals, sizeof( numSignals ) ); + const char *name = ((*si).first).c_str(); + + int length = strlen( name ) + 1; + + //Save out the string size + BufferWrite( &length, sizeof( length ) ); + + //Write out the string + BufferWrite( (void *) name, length ); + } + + return true; +} + +// Get the current Game flavor. +int CIcarus::GetFlavor() +{ + return m_flavor; +} + +int CIcarus::Save() +{ + // Allocate the temporary buffer. + CreateBuffer(); + + IGameInterface* game = IGameInterface::GetGame(m_flavor); + + //Save out a ICARUS save block header with the ICARUS version + double version = ICARUS_VERSION; + game->WriteSaveData( 'ICAR', &version, sizeof( version ) ); + + //Save out the signals + if ( SaveSignals() == false ) + { + DestroyBuffer(); + return false; + } + + //Save out the sequences + if ( SaveSequences() == false ) + { + DestroyBuffer(); + return false; + } + + //Save out the sequencers + if ( SaveSequencers() == false ) + { + DestroyBuffer(); + return false; + } + + // Write out the buffer with all our collected data. + game->WriteSaveData( 'ISEQ', m_byBuffer, m_ulBufferCurPos ); + + // De-allocate the temporary buffer. + DestroyBuffer(); + + return true; +} + +int CIcarus::LoadSignals() +{ + int numSignals; + + BufferRead( &numSignals, sizeof( numSignals ) ); + + for ( int i = 0; i < numSignals; i++ ) + { + char buffer[1024]; + int length; + + //Get the size of the string + BufferRead( &length, sizeof( length ) ); + + //Get the string + BufferRead( &buffer, length ); + + //Turn it on and add it to the system + Signal( (const char *) &buffer ); + } + + return true; +} + +int CIcarus::LoadSequence() +{ + CSequence *sequence = GetSequence(); + + //Load the sequence back in + sequence->Load(this); + + //If this sequence had a higher GUID than the current, save it + if ( sequence->GetID() > m_GUID ) + m_GUID = sequence->GetID(); + + return true; +} + +int CIcarus::LoadSequences() +{ + CSequence *sequence; + int numSequences; + + //Get the number of sequences to read in + BufferRead( &numSequences, sizeof( numSequences ) ); + + int *idTable = new int[ numSequences ]; + + if ( idTable == NULL ) + return false; + + //Load the sequencer ID table + BufferRead( idTable, sizeof( int ) * numSequences ); + + //First pass, allocate all container sequences and give them their proper IDs + if ( AllocateSequences( numSequences, idTable ) == false ) + return false; + + //Second pass, load all sequences + for ( int i = 0; i < numSequences; i++ ) + { + //Get the proper sequence for this load + if ( ( sequence = GetSequence( idTable[i] ) ) == NULL ) + return false; + + //Load the sequence + if ( ( sequence->Load(this) ) == false ) + return false; + } + + //Free the idTable + delete[] idTable; + + return true; +} + +int CIcarus::LoadSequencers() +{ + CSequencer *sequencer; + int numSequencers; + IGameInterface* game = IGameInterface::GetGame(m_flavor); + + //Get the number of sequencers to load + BufferRead( &numSequencers, sizeof( numSequencers ) ); + + //Load all sequencers + for ( int i = 0; i < numSequencers; i++ ) + { + //NOTENOTE: The ownerID will be replaced in the loading process + int sequencerID = GetIcarusID(-1); + if ( ( sequencer = FindSequencer(sequencerID) ) == NULL ) + return false; + + if ( sequencer->Load(this, game) == false ) + return false; + } + + return true; +} + +int CIcarus::Load() +{ + CreateBuffer(); + + IGameInterface* game = IGameInterface::GetGame(m_flavor); + + //Clear out any old information + Free(); + + //Check to make sure we're at the ICARUS save block + double version; + game->ReadSaveData( 'ICAR', &version, sizeof( version ) ); + + //Versions must match! + if ( version != ICARUS_VERSION ) + { + DestroyBuffer(); + game->DebugPrint( IGameInterface::WL_ERROR, "save game data contains outdated ICARUS version information!\n"); + return false; + } + + // Read into the buffer all our data. + /*m_ulBytesAvailable = */game->ReadSaveData( 'ISEQ', m_byBuffer, 0 ); //fixme, use real buff size + + //Load all signals + if ( LoadSignals() == false ) + { + DestroyBuffer(); + game->DebugPrint( IGameInterface::WL_ERROR, "failed to load signals from save game!\n"); + return false; + } + + //Load in all sequences + if ( LoadSequences() == false ) + { + DestroyBuffer(); + game->DebugPrint( IGameInterface::WL_ERROR, "failed to load sequences from save game!\n"); + return false; + } + + //Load in all sequencers + if ( LoadSequencers() == false ) + { + DestroyBuffer(); + game->DebugPrint( IGameInterface::WL_ERROR, "failed to load sequencers from save game!\n"); + return false; + } + + DestroyBuffer(); + + return true; +} + +int CIcarus::Update(int icarusID) +{ + CSequencer* sequencer = FindSequencer(icarusID); + if(sequencer) + { + return sequencer->GetTaskManager()->Update(this); + } + return -1; +} + +int CIcarus::IsRunning(int icarusID) +{ + CSequencer* sequencer = FindSequencer(icarusID); + if(sequencer) + { + return sequencer->GetTaskManager()->IsRunning(); + } + return false; +} + +void CIcarus::Completed( int icarusID, int taskID ) +{ + CSequencer* sequencer = FindSequencer(icarusID); + if(sequencer) + { + sequencer->GetTaskManager()->Completed(taskID); + } +} + +// Destroy the File Buffer. +void CIcarus::DestroyBuffer() +{ + if ( m_byBuffer ) + { + IGameInterface::GetGame()->Free( m_byBuffer ); + m_byBuffer = NULL; + } +} + +// Create the File Buffer. +void CIcarus::CreateBuffer() +{ + DestroyBuffer(); + m_byBuffer = (unsigned char *)IGameInterface::GetGame()->Malloc( MAX_BUFFER_SIZE ); + m_ulBufferCurPos = 0; +} + +// Write to a buffer. +void CIcarus::BufferWrite( void *pSrcData, unsigned long ulNumBytesToWrite ) +{ + if ( !pSrcData ) + return; + + // Make sure we have enough space in the buffer to write to. + if ( MAX_BUFFER_SIZE - m_ulBufferCurPos < ulNumBytesToWrite ) + { // Write out the buffer with all our collected data so far... + IGameInterface::GetGame()->DebugPrint( IGameInterface::WL_ERROR, "BufferWrite: Out of buffer space, Flushing." ); + IGameInterface::GetGame()->WriteSaveData( 'ISEQ', m_byBuffer, m_ulBufferCurPos ); + m_ulBufferCurPos = 0; //reset buffer + } + + assert( MAX_BUFFER_SIZE - m_ulBufferCurPos >= ulNumBytesToWrite ); + { + memcpy( m_byBuffer + m_ulBufferCurPos, pSrcData, ulNumBytesToWrite ); + m_ulBufferCurPos += ulNumBytesToWrite; + } +} + +// Read from a buffer. +void CIcarus::BufferRead( void *pDstBuff, unsigned long ulNumBytesToRead ) +{ + if ( !pDstBuff ) + return; + + // If we can read this data... + if ( m_ulBytesRead + ulNumBytesToRead > MAX_BUFFER_SIZE ) + {// We've tried to read past the buffer... + IGameInterface::GetGame()->DebugPrint( IGameInterface::WL_ERROR, "BufferRead: Buffer underflow, Looking for new block." ); + // Read in the next block. + /*m_ulBytesAvailable = */IGameInterface::GetGame()->ReadSaveData( 'ISEQ', m_byBuffer, 0 ); //FIXME, to actually check underflows, use real buff size + m_ulBytesRead = 0; //reset buffer + } + + assert(m_ulBytesRead + ulNumBytesToRead <= MAX_BUFFER_SIZE); + { + memcpy( pDstBuff, m_byBuffer + m_ulBytesRead, ulNumBytesToRead ); + m_ulBytesRead += ulNumBytesToRead; + } +} \ No newline at end of file diff --git a/code/icarus/IcarusImplementation.h b/code/icarus/IcarusImplementation.h new file mode 100644 index 0000000..fd4e719 --- /dev/null +++ b/code/icarus/IcarusImplementation.h @@ -0,0 +1,253 @@ +// IcarusImplementation.h +#ifndef ICARUSIMPLEMENTATION_DEFINED +#define ICARUSIMPLEMENTATION_DEFINED + +#ifndef ICARUSINTERFACE_DEFINED +#include "IcarusInterface.h" +#endif + +#pragma warning( disable : 4786 ) // identifier was truncated +#pragma warning (push, 3) // go back down to 3 for the stl include +#pragma warning (disable:4503) // decorated name length xceeded, name was truncated +#include +#include +#include +#include +#include +#pragma warning (pop) +#pragma warning (disable:4503) // decorated name length xceeded, name was truncated +using namespace std; + + +class CSequence; +class CSequencer; + +class CIcarusSequencer; +class CIcarusSequence; + +class CIcarus : public IIcarusInterface +{ +public: + CIcarus(int flavor); + virtual ~CIcarus(); + + inline IGameInterface* GetGame() {return IGameInterface::GetGame(m_flavor);}; + + enum + { + MAX_STRING_SIZE = 256, + MAX_FILENAME_LENGTH = 1024, + }; + +protected: + int m_flavor; + int m_nextSequencerID; + + int m_GUID; + + typedef list< CSequence * > sequence_l; + typedef list< CSequencer * > sequencer_l; + typedef map < int, CSequencer* > sequencer_m; + + sequence_l m_sequences; + sequencer_l m_sequencers; + sequencer_m m_sequencerMap; + + typedef map < string, unsigned char > signal_m; + signal_m m_signals; + + static double ICARUS_VERSION; + +#ifdef _DEBUG + + int m_DEBUG_NumSequencerAlloc; + int m_DEBUG_NumSequencerFreed; + int m_DEBUG_NumSequencerResidual; + + int m_DEBUG_NumSequenceAlloc; + int m_DEBUG_NumSequenceFreed; + int m_DEBUG_NumSequenceResidual; + +#endif + +public: + static int s_flavorsAvailable; + static CIcarus** s_instances; + + // mandatory overrides + // Get the current Game flavor. + int GetFlavor(); + + int Save(); + int Load(); + int Run(int icarusID, char* buffer, long length); + void DeleteIcarusID(int& icarusID); + int GetIcarusID(int ownerID); + int Update(int icarusID); + + int IsRunning(int icarusID); + void Completed( int icarusID, int taskID ); + void Precache(char* buffer, long length); + +protected: + void Delete(); + void Free(); + +public: + CSequence* GetSequence(int id); + void DeleteSequence( CSequence *sequence ); + int AllocateSequences( int numSequences, int *idTable ); + CSequencer* FindSequencer(int sequencerID); + CSequence* GetSequence(); + +protected: + int SaveSequenceIDTable(); + int SaveSequences(); + int SaveSequencers(); + int SaveSignals(); + + int LoadSignals(); + int LoadSequence(); + int LoadSequences(); + int LoadSequencers(); + +public: + void Signal( const char *identifier ); + bool CheckSignal( const char *identifier ); + void ClearSignal( const char *identifier ); + + // Overloaded new operator. + inline void *operator new( size_t size ) + { + return IGameInterface::GetGame()->Malloc( size ); + } + + // Overloaded delete operator. + inline void operator delete( void *pRawData ) + { + // Free the Memory. + IGameInterface::GetGame()->Free( pRawData ); + } + +public: + enum + { + TK_EOF = -1, + TK_UNDEFINED, + TK_COMMENT, + TK_EOL, + TK_CHAR, + TK_STRING, + TK_INT, + TK_INTEGER = TK_INT, + TK_FLOAT, + TK_IDENTIFIER, + TK_USERDEF, + TK_BLOCK_START = TK_USERDEF, + TK_BLOCK_END, + TK_VECTOR_START, + TK_VECTOR_END, + TK_OPEN_PARENTHESIS, + TK_CLOSED_PARENTHESIS, + TK_VECTOR, + TK_GREATER_THAN, + TK_LESS_THAN, + TK_EQUALS, + TK_NOT, + + NUM_USER_TOKENS + }; + + //ID defines + enum + { + ID_AFFECT = NUM_USER_TOKENS, + ID_SOUND, + ID_MOVE, + ID_ROTATE, + ID_WAIT, + ID_BLOCK_START, + ID_BLOCK_END, + ID_SET, + ID_LOOP, + ID_LOOPEND, + ID_PRINT, + ID_USE, + ID_FLUSH, + ID_RUN, + ID_KILL, + ID_REMOVE, + ID_CAMERA, + ID_GET, + ID_RANDOM, + ID_IF, + ID_ELSE, + ID_REM, + ID_TASK, + ID_DO, + ID_DECLARE, + ID_FREE, + ID_DOWAIT, + ID_SIGNAL, + ID_WAITSIGNAL, + ID_PLAY, + + ID_TAG, + ID_EOF, + NUM_IDS + }; + + //Type defines + enum + { + //Wait types + TYPE_WAIT_COMPLETE = NUM_IDS, + TYPE_WAIT_TRIGGERED, + + //Set types + TYPE_ANGLES, + TYPE_ORIGIN, + + //Affect types + TYPE_INSERT, + TYPE_FLUSH, + + //Camera types + TYPE_PAN, + TYPE_ZOOM, + TYPE_MOVE, + TYPE_FADE, + TYPE_PATH, + TYPE_ENABLE, + TYPE_DISABLE, + TYPE_SHAKE, + TYPE_ROLL, + TYPE_TRACK, + TYPE_DISTANCE, + TYPE_FOLLOW, + + //Variable type + TYPE_VARIABLE, + + TYPE_EOF, + NUM_TYPES + }; + + // Used by the new Icarus Save code. + enum { MAX_BUFFER_SIZE = 100000 }; + unsigned long m_ulBufferCurPos; + unsigned long m_ulBytesRead; + unsigned char *m_byBuffer; + // Destroy the File Buffer. + void DestroyBuffer(); + // Create the File Buffer. + void CreateBuffer(); + // Reset the buffer completely. + void ResetBuffer(); + // Write to a buffer. + void BufferWrite( void *pSrcData, unsigned long ulNumBytesToWrite ); + // Read from a buffer. + void BufferRead( void *pDstBuff, unsigned long ulNumBytesToRead ); +}; + +#endif \ No newline at end of file diff --git a/code/icarus/IcarusInterface.h b/code/icarus/IcarusInterface.h new file mode 100644 index 0000000..725350a --- /dev/null +++ b/code/icarus/IcarusInterface.h @@ -0,0 +1,143 @@ +#pragma once +#ifndef ICARUSINTERFACE_DEFINED +#define ICARUSINTERFACE_DEFINED + +// IcarusInterface.h: ICARUS Interface header file. +// -Date: ~October, 2002 +// -Created by: Mike Crowns and Aurelio Reis. +// -Description: The new interface between a Game Engine and the Icarus Scripting Language. +// An Interface is an Abstract Base Class with pure virtual members that MUST be implemented +// in order for the compile to succeed. Because of this, all needed functionality can be +// added without compromising other core systems. +// -Usage: To use the new Icarus Interface, two classes must be derived. The first is the +// actual Icarus Interface class which contains all relevent functionality to the scripting +// system. The second is the Game Interface which is very much more broad and thus implemented +// by the user. Icarus functions by calling the Game Interface to do certain tasks for it. This +// is why the Game Interface is required to have certain functions implemented. + + +// The basic Icarus Interface ABC. +class IIcarusInterface +{ +public: + enum { ICARUS_INVALID = 0 }; + virtual ~IIcarusInterface(); + + // Get a static singleton instance (of a specific flavor). + static IIcarusInterface* GetIcarus(int flavor = 0,bool constructIfNecessary=true); // must be implemented along with concrete class + static void DestroyIcarus(); // Destroy the static singleton instance. + + virtual int GetFlavor() = 0; + + virtual int Save() = 0; // Save all Icarus states. + virtual int Load() = 0; // Load all Icarus states. + + virtual int Run(int icarusID, char* buffer, long length) = 0; // Execute a script. + virtual void DeleteIcarusID(int &icarusID) = 0; // Delete an Icarus ID from the list (making the ID Invalid on the other end through reference). + virtual int GetIcarusID(int gameID) = 0; // Get an Icarus ID. + virtual int Update( int icarusID ) = 0; // Update all internal Icarus structures. + virtual int IsRunning( int icarusID ) = 0; // Whether a Icarus is running or not. + virtual void Completed( int icarusID, int taskID ) = 0; // Tells Icarus a task is completed. + virtual void Precache( char* buffer, long length ) = 0; // Precache a Script in memory. +}; + +// Description: The Game Interface is used by the Icarus Interface to access specific +// data or initiate certain things within the engine being used. It is made to be +// as generic as possible to allow any engine to derive it's own interface for use. +// Created: 10/08/02 by Aurelio Reis. +class IGameInterface +{ +protected: + // Pure Virtual Destructor. + virtual ~IGameInterface(); + +public: + //For system-wide prints + enum e_DebugPrintLevel { WL_ERROR = 1, WL_WARNING, WL_VERBOSE, WL_DEBUG }; + + // How many flavors are needed. + static int s_IcarusFlavorsNeeded; + + // Get a static singleton instance (of a specific flavor). + static IGameInterface *GetGame( int flavor = 0 ); + + // Destroy the static singleton instance (NOTE: Destroy the Game Interface BEFORE the Icarus Interface). + static void Destroy(); + + // General + // Load a script File into the destination buffer. If the script has already been loaded + // NOTE: This is what was called before: + /* + // Description : Reads in a file and attaches the script directory properly + extern int ICARUS_GetScript( const char *name, char **buf ); //g_icarus.cpp + static int Q3_ReadScript( const char *name, void **buf ) + { + return ICARUS_GetScript( va( "%s/%s", Q3_SCRIPT_DIR, name ), (char**)buf ); //get a (hopefully) cached file + } + */ + virtual int GetFlavor() = 0; + + virtual int LoadFile( const char *name, void **buf ) = 0; + virtual void CenterPrint( const char *format, ... ) = 0; + virtual void DebugPrint( e_DebugPrintLevel, const char *, ... ) = 0; + virtual unsigned int GetTime( void ) = 0; //Gets the current time + virtual int PlaySound( int taskID, int gameID, const char *name, const char *channel ) = 0; + virtual void Lerp2Pos( int taskID, int gameID, float origin[3], float angles[3], float duration ) = 0; + virtual void Lerp2Angles( int taskID, int gameID, float angles[3], float duration ) = 0; + virtual int GetTag( int gameID, const char *name, int lookup, float info[3] ) = 0; + virtual void Set( int taskID, int gameID, const char *type_name, const char *data ) = 0; + virtual void Use( int gameID, const char *name ) = 0; + virtual void Activate( int gameID, const char *name ) = 0; + virtual void Deactivate( int gameID, const char *name ) = 0; + virtual void Kill( int gameID, const char *name ) = 0; + virtual void Remove( int gameID, const char *name ) = 0; + virtual float Random( float min, float max ) = 0; + virtual void Play( int taskID, int gameID, const char *type, const char *name ) = 0; + + // Camera functions + virtual void CameraPan( float angles[3], float dir[3], float duration ) = 0; + virtual void CameraMove( float origin[3], float duration ) = 0; + virtual void CameraZoom( float fov, float duration ) = 0; + virtual void CameraRoll( float angle, float duration ) = 0; + virtual void CameraFollow( const char *name, float speed, float initLerp ) = 0; + virtual void CameraTrack( const char *name, float speed, float initLerp ) = 0; + virtual void CameraDistance( float dist, float initLerp ) = 0; + virtual void CameraFade( float sr, float sg, float sb, float sa, float dr, float dg, float db, float da, float duration ) = 0; + virtual void CameraPath( const char *name ) = 0; + virtual void CameraEnable( void ) = 0; + virtual void CameraDisable( void ) = 0; + virtual void CameraShake( float intensity, int duration ) = 0; + + virtual int GetFloat( int gameID, const char *name, float *value ) = 0; + // Should be float return type? + virtual int GetVector( int gameID, const char *name, float value[3] ) = 0; + virtual int GetString( int gameID, const char *name, char **value ) = 0; + + virtual int Evaluate( int p1Type, const char *p1, int p2Type, const char *p2, int operatorType ) = 0; + + virtual void DeclareVariable( int type, const char *name ) = 0; + virtual void FreeVariable( const char *name ) = 0; + + // Save / Load functions + + virtual int WriteSaveData( unsigned long chid, void *data, int length ) = 0; + virtual int ReadSaveData( unsigned long chid, void *address, int length, void **addressptr = NULL ) = 0; + virtual int LinkGame( int gameID, int icarusID ) = 0; + + // Access functions + + virtual int CreateIcarus( int gameID) = 0; + virtual int GetByName( const char *name ) = 0; //Polls the engine for the sequencer of the entity matching the name passed + virtual int IsFrozen(int gameID) = 0; // (g_entities[m_ownerID].svFlags&SVF_ICARUS_FREEZE) // return -1 indicates invalid + virtual void Free(void* data) = 0; + virtual void* Malloc( int size ) = 0; + virtual float MaxFloat(void) = 0; + + // Script precache functions. + virtual void PrecacheRoff(const char* name) = 0; // G_LoadRoff + virtual void PrecacheScript(const char* name) = 0; // must strip extension COM_StripExtension() + virtual void PrecacheSound(const char* name) = 0; // G_SoundIndex + virtual void PrecacheFromSet(const char* setname, const char* filename) = 0; +}; + +#endif \ No newline at end of file diff --git a/code/icarus/Sequence.cpp b/code/icarus/Sequence.cpp new file mode 100644 index 0000000..1f75f51 --- /dev/null +++ b/code/icarus/Sequence.cpp @@ -0,0 +1,674 @@ +// Script Command Sequences +// +// -- jweier + +// this include must remain at the top of every Icarus CPP file +#include "stdafx.h" +#include "IcarusImplementation.h" + +#include "BlockStream.h" +#include "Sequence.h" + +#define STL_ITERATE( a, b ) for ( a = b.begin(); a != b.end(); a++ ) +#define STL_INSERT( a, b ) a.insert( a.end(), b ); + + +inline CSequence::CSequence( void ) +{ + m_numCommands = 0; +// m_numChildren = 0; + m_flags = 0; + m_iterations = 1; + + m_parent = NULL; + m_return = NULL; +} + +CSequence::~CSequence( void ) +{ + assert(!m_commands.size()); + //assert(!m_numChildren); +} + +/* +------------------------- +Create +------------------------- +*/ + +CSequence *CSequence::Create( void ) +{ + CSequence *seq = new CSequence; + + //TODO: Emit warning + assert(seq); + if ( seq == NULL ) + return NULL; + + seq->SetFlag( SQ_COMMON ); + + return seq; +} + +/* +------------------------- +Delete +------------------------- +*/ + +void CSequence::Delete( CIcarus* icarus ) +{ + block_l::iterator bi; + sequence_l::iterator si; + + //Notify the parent of the deletion + if ( m_parent ) + { + m_parent->RemoveChild( this ); + } + + //Clear all children + if ( m_children.size() > 0 ) + { + /*for ( iterSeq = m_childrenMap.begin(); iterSeq != m_childrenMap.end(); iterSeq++ ) + { + (*iterSeq).second->SetParent( NULL ); + }*/ + + for ( si = m_children.begin(); si != m_children.end(); si++ ) + { + (*si)->SetParent( NULL ); + } + } + m_children.clear(); + //m_childrenMap.clear(); + + //Clear all held commands + for ( bi = m_commands.begin(); bi != m_commands.end(); bi++ ) + { + (*bi)->Free(icarus); + delete (*bi); //Free() handled internally -- not any more!! + } + + m_commands.clear(); + +} + + +/* +------------------------- +AddChild +------------------------- +*/ + +void CSequence::AddChild( CSequence *child ) +{ + assert( child ); + if ( child == NULL ) + return; + + m_children.insert( m_children.end(), child ); +// m_childrenMap[ m_numChildren ] = child; +// m_numChildren++; +} + +/* +------------------------- +RemoveChild +------------------------- +*/ + +void CSequence::RemoveChild( CSequence *child ) +{ + assert( child ); + if ( child == NULL ) + return; + + m_children.remove( child ); + + //Remove the child +/* sequenceID_m::iterator iterSeq = m_childrenMap.find( child->GetID() ); + if ( iterSeq != m_childrenMap.end() ) + { + m_childrenMap.erase( iterSeq ); + } + + m_numChildren--;*/ +} + +/* +------------------------- +HasChild +------------------------- +*/ + +bool CSequence::HasChild( CSequence *sequence ) +{ + sequence_l::iterator ci; + + for ( ci = m_children.begin(); ci != m_children.end(); ci++ ) + { + if ( (*ci) == sequence ) + return true; + + if ( (*ci)->HasChild( sequence ) ) + return true; + } + +/* sequenceID_m::iterator iterSeq = NULL; + for ( iterSeq = m_childrenMap.begin(); iterSeq != m_childrenMap.end(); iterSeq++ ) + { + if ( ((*iterSeq).second) == sequence ) + return true; + + if ( (*iterSeq).second->HasChild( sequence ) ) + return true; + }*/ + + return false; +} + +/* +------------------------- +SetParent +------------------------- +*/ + +void CSequence::SetParent( CSequence *parent ) +{ + m_parent = parent; + + if ( parent == NULL ) + return; + + //Inherit the parent's properties (this avoids messy tree walks later on) + if ( parent->m_flags & SQ_RETAIN ) + m_flags |= SQ_RETAIN; + + if ( parent->m_flags & SQ_PENDING ) + m_flags |= SQ_PENDING; +} + +/* +------------------------- +PopCommand +------------------------- +*/ + +CBlock *CSequence::PopCommand( int type ) +{ + CBlock *command = NULL; + + //Make sure everything is ok + assert( (type == POP_FRONT) || (type == POP_BACK) ); + + if ( m_commands.empty() ) + return NULL; + + switch ( type ) + { + case POP_FRONT: + + command = m_commands.front(); + m_commands.pop_front(); + m_numCommands--; + + return command; + break; + + case POP_BACK: + + command = m_commands.back(); + m_commands.pop_back(); + m_numCommands--; + + return command; + break; + } + + //Invalid flag + return NULL; +} + +/* +------------------------- +PushCommand +------------------------- +*/ + +int CSequence::PushCommand( CBlock *block, int type ) +{ + //Make sure everything is ok + assert( (type == PUSH_FRONT) || (type == PUSH_BACK) ); + assert( block ); + + switch ( type ) + { + case PUSH_FRONT: + + m_commands.push_front( block ); + m_numCommands++; + + return true; + break; + + case PUSH_BACK: + + m_commands.push_back( block ); + m_numCommands++; + + return true; + break; + } + + //Invalid flag + return false; +} + +/* +------------------------- +SetFlag +------------------------- +*/ + +void CSequence::SetFlag( int flag ) +{ + m_flags |= flag; +} + +/* +------------------------- +RemoveFlag +------------------------- +*/ + +void CSequence::RemoveFlag( int flag, bool children ) +{ + m_flags &= ~flag; + + if ( children ) + { +/* sequenceID_m::iterator iterSeq = NULL; + for ( iterSeq = m_childrenMap.begin(); iterSeq != m_childrenMap.end(); iterSeq++ ) + { + (*iterSeq).second->RemoveFlag( flag, true ); + }*/ + + sequence_l::iterator si; + for ( si = m_children.begin(); si != m_children.end(); si++ ) + { + (*si)->RemoveFlag( flag, true ); + } + } +} + +/* +------------------------- +HasFlag +------------------------- +*/ + +int CSequence::HasFlag( int flag ) +{ + return (m_flags & flag); +} + +/* +------------------------- +SetReturn +------------------------- +*/ + +void CSequence::SetReturn ( CSequence *sequence ) +{ + assert( sequence != this ); + m_return = sequence; +} + +/* +------------------------- +GetChildByID +------------------------- +*/ + +CSequence *CSequence::GetChildByID( int id ) +{ + if ( id < 0 ) + return NULL; + + //NOTENOTE: Done for safety reasons, I don't know what this template will return on underflow ( sigh... ) +/* sequenceID_m::iterator mi = m_childrenMap.find( id ); + + if ( mi == m_childrenMap.end() ) + return NULL; + + return (*mi).second;*/ + + sequence_l::iterator iterSeq; + STL_ITERATE( iterSeq, m_children ) + { + if ( (*iterSeq)->GetID() == id ) + return (*iterSeq); + } + + return NULL; +} + +/* +------------------------- +GetChildByIndex +------------------------- +*/ + +CSequence *CSequence::GetChildByIndex( int iIndex ) +{ + if ( iIndex < 0 || iIndex >= (int)m_children.size() ) + return NULL; + + sequence_l::iterator iterSeq = m_children.begin(); + for ( int i = 0; i < iIndex; i++ ) + { + ++iterSeq; + } + return (*iterSeq); +} + +/* +------------------------- +SaveCommand +------------------------- +*/ + +int CSequence::SaveCommand( CBlock *block ) +{ + CIcarus *pIcarus = (CIcarus *)IIcarusInterface::GetIcarus(); + + unsigned char flags; + int numMembers, bID, size; + CBlockMember *bm; + + // Data saved here (IBLK): + // Block ID. + // Block Flags. + // Number of Block Members. + // Block Members: + // - Block Member ID. + // - Block Data Size. + // - Block (Raw) Data. + + //Save out the block ID + bID = block->GetBlockID(); + pIcarus->BufferWrite( &bID, sizeof ( bID ) ); + + //Save out the block's flags + flags = block->GetFlags(); + pIcarus->BufferWrite( &flags, sizeof ( flags ) ); + + //Save out the number of members to read + numMembers = block->GetNumMembers(); + pIcarus->BufferWrite( &numMembers, sizeof ( numMembers ) ); + + for ( int i = 0; i < numMembers; i++ ) + { + bm = block->GetMember( i ); + + //Save the block id + bID = bm->GetID(); + pIcarus->BufferWrite( &bID, sizeof ( bID ) ); + + //Save out the data size + size = bm->GetSize(); + pIcarus->BufferWrite( &size, sizeof ( size ) ); + + //Save out the raw data + pIcarus->BufferWrite( bm->GetData(), size ); + } + + return true; +} + +int CSequence::LoadCommand( CBlock *block, CIcarus *icarus ) +{ + IGameInterface* game = icarus->GetGame(); + int bID, bSize; + void *bData; + unsigned char flags; + int id, numMembers; + + // Data expected/loaded here (IBLK) (with the size as : 'IBSZ' ). + // Block ID. + // Block Flags. + // Number of Block Members. + // Block Members: + // - Block Member ID. + // - Block Data Size. + // - Block (Raw) Data. + + //Get the block ID. + icarus->BufferRead( &id, sizeof( id ) ); + block->Create( id ); + + //Read the block's flags + icarus->BufferRead( &flags, sizeof( flags ) ); + block->SetFlags( flags ); + + //Get the number of block members + icarus->BufferRead( &numMembers, sizeof( numMembers ) ); + + for ( int j = 0; j < numMembers; j++ ) + { + //Get the member ID + icarus->BufferRead( &bID, sizeof( bID ) ); + + //Get the member size + icarus->BufferRead( &bSize, sizeof( bSize ) ); + + //Get the member's data + if ( ( bData = game->Malloc( bSize ) ) == NULL ) + return false; + + //Get the actual raw data + icarus->BufferRead( bData, bSize ); + + //Write out the correct type + switch ( bID ) + { + case CIcarus::TK_INT: + { + assert(0); + int data = *(int *) bData; + block->Write( CIcarus::TK_FLOAT, (float) data, icarus ); + } + break; + + case CIcarus::TK_FLOAT: + block->Write( CIcarus::TK_FLOAT, *(float *) bData, icarus ); + break; + + case CIcarus::TK_STRING: + case CIcarus::TK_IDENTIFIER: + case CIcarus::TK_CHAR: + block->Write( CIcarus::TK_STRING, (char *) bData, icarus ); + break; + + case CIcarus::TK_VECTOR: + case CIcarus::TK_VECTOR_START: + block->Write( CIcarus::TK_VECTOR, *(vec3_t *) bData, icarus ); + break; + + case CIcarus::ID_TAG: + block->Write( CIcarus::ID_TAG, (float) CIcarus::ID_TAG, icarus ); + break; + + case CIcarus::ID_GET: + block->Write( CIcarus::ID_GET, (float) CIcarus::ID_GET, icarus ); + break; + + case CIcarus::ID_RANDOM: + block->Write( CIcarus::ID_RANDOM, *(float *) bData, icarus );//(float) ID_RANDOM ); + break; + + case CIcarus::TK_EQUALS: + case CIcarus::TK_GREATER_THAN: + case CIcarus::TK_LESS_THAN: + case CIcarus::TK_NOT: + block->Write( bID, 0, icarus ); + break; + + default: + assert(0); + return false; + break; + } + + //Get rid of the temp memory + game->Free( bData ); + } + + return true; +} + +/* +------------------------- +Save +------------------------- +*/ + +int CSequence::Save() +{ + // Data saved here. + // Parent ID. + // Return ID. + // Number of Children. + // Children. + // - Child ID + // Save Flags. + // Save Iterations. + // Number of Commands + // - Commands (raw) data. + + CIcarus *pIcarus = (CIcarus *)IIcarusInterface::GetIcarus(); + + block_l::iterator bi; + int id; + + // Save the parent (by GUID). + id = ( m_parent != NULL ) ? m_parent->GetID() : -1; + pIcarus->BufferWrite( &id, sizeof( id ) ); + + //Save the return (by GUID) + id = ( m_return != NULL ) ? m_return->GetID() : -1; + pIcarus->BufferWrite( &id, sizeof( id ) ); + + //Save the number of children + int iNumChildren = m_children.size(); + pIcarus->BufferWrite( &iNumChildren, sizeof( iNumChildren ) ); + + //Save out the children (only by GUID) + /*STL_ITERATE( iterSeq, m_childrenMap ) + { + id = (*iterSeq).second->GetID(); + pIcarus->BufferWrite( &id, sizeof( id ) ); + }*/ + sequence_l::iterator iterSeq; + STL_ITERATE( iterSeq, m_children ) + { + id = (*iterSeq)->GetID(); + pIcarus->BufferWrite( &id, sizeof( id ) ); + } + + //Save flags + pIcarus->BufferWrite( &m_flags, sizeof( m_flags ) ); + + //Save iterations + pIcarus->BufferWrite( &m_iterations, sizeof( m_iterations ) ); + + //Save the number of commands + pIcarus->BufferWrite( &m_numCommands, sizeof( m_numCommands ) ); + + //Save the commands + STL_ITERATE( bi, m_commands ) + { + SaveCommand( (*bi) ); + } + + return true; +} + +/* +------------------------- +Load +------------------------- +*/ + +int CSequence::Load( CIcarus* icarus ) +{ + CSequence *sequence; + CBlock *block; + int id; + + // Data expected/loaded here (ISEQ) (with the size as : 'ISSZ' ). + // Parent ID. + // Return ID. + // Number of Children. + // Children. + // - Child ID + // Save Flags. + // Save Iterations. + // Number of Commands + // - Commands (raw) data. + + //Get the parent sequence + icarus->BufferRead( &id, sizeof( id ) ); + m_parent = ( id != -1 ) ? icarus->GetSequence( id ) : NULL; + + //Get the return sequence + icarus->BufferRead( &id, sizeof( id ) ); + m_return = ( id != -1 ) ? icarus->GetSequence( id ) : NULL; + + //Get the number of children + int iNumChildren = 0; + icarus->BufferRead( &iNumChildren, sizeof( iNumChildren ) ); + + //Reload all children + for ( int i = 0; i < iNumChildren; i++ ) + { + //Get the child sequence ID + icarus->BufferRead( &id, sizeof( id ) ); + + //Get the desired sequence + if ( ( sequence = icarus->GetSequence( id ) ) == NULL ) + return false; + + //Insert this into the list + STL_INSERT( m_children, sequence ); + + //Restore the connection in the child / ID map +// m_childrenMap[ i ] = sequence; + } + + + //Get the sequence flags + icarus->BufferRead( &m_flags, sizeof( m_flags ) ); + + //Get the number of iterations + icarus->BufferRead( &m_iterations, sizeof( m_iterations ) ); + + int numCommands; + + //Get the number of commands + icarus->BufferRead( &numCommands, sizeof( numCommands ) ); + + //Get all the commands + for ( i = 0; i < numCommands; i++ ) + { + block = new CBlock; + LoadCommand( block, icarus ); + + //Save the block + //STL_INSERT( m_commands, block ); + PushCommand( block, PUSH_BACK ); + } + + return true; +} \ No newline at end of file diff --git a/code/icarus/Sequencer.cpp b/code/icarus/Sequencer.cpp new file mode 100644 index 0000000..63c34bd --- /dev/null +++ b/code/icarus/Sequencer.cpp @@ -0,0 +1,2614 @@ +// Script Command Sequencer +// +// -- jweier + +// this include must remain at the top of every Icarus CPP file +#include "stdafx.h" +#include "IcarusImplementation.h" + +#include "BlockStream.h" +#include "Sequence.h" +#include "TaskManager.h" +#include "Sequencer.h" + +#define S_FAILED(a) (a!=SEQ_OK) + +#define STL_ITERATE( a, b ) for ( a = b.begin(); a != b.end(); a++ ) +#define STL_INSERT( a, b ) a.insert( a.end(), b ); + + +// Save/Load restructuring. +// Date: 10/29/02 +// By: Aurelio Reis +// Purpose: In an effort to reduce file size and overhead, it was decided that +// using a chunks for EVERYTHING is vastly inefficient and wasteful. Thus, the saving +// is now primary focused on saving large chunks with *expected* data read from there. + + +// Sequencer + +CSequencer::CSequencer( void ) +{ + static int uniqueID = 1; + m_id = uniqueID++; + + m_numCommands = 0; + + m_curStream = NULL; + m_curSequence = NULL; + + m_elseValid = 0; + m_elseOwner = NULL; + + m_curGroup = NULL; +} + +CSequencer::~CSequencer( void ) +{ +} + +/* +======================== +Create + +Static creation function +======================== +*/ + +CSequencer *CSequencer::Create ( void ) +{ + CSequencer *sequencer = new CSequencer; + + return sequencer; +} + +/* +======================== +Init + +Initializes the sequencer +======================== +*/ +int CSequencer::Init( int ownerID, CTaskManager *taskManager ) +{ + m_ownerID = ownerID; + m_taskManager = taskManager; + + return SEQ_OK; +} + +/* +======================== +Free + +Releases all resources and re-inits the sequencer +======================== +*/ +void CSequencer::Free( CIcarus* icarus ) +{ + //Flush the sequences +/* sequenceID_m::iterator iterSeq = NULL; + for ( iterSeq = m_sequenceMap.begin(); iterSeq != m_sequenceMap.end(); iterSeq++ ) + { + icarus->DeleteSequence( (*iterSeq).second ); + } + m_sequenceMap.clear();*/ + + // OLD STUFF! + sequence_l::iterator sli; + for ( sli = m_sequences.begin(); sli != m_sequences.end(); sli++ ) + { + icarus->DeleteSequence( (*sli) ); + } + m_sequences.clear(); + + m_taskSequences.clear(); + + //Clean up any other info + m_numCommands = 0; + m_curSequence = NULL; + + bstream_t *streamToDel; + while(!m_streamsCreated.empty()) + { + streamToDel = m_streamsCreated.back(); + DeleteStream(streamToDel); + } + + delete this; +} + +/* +------------------------- +Flush +------------------------- +*/ + +int CSequencer::Flush( CSequence *owner, CIcarus* icarus ) +{ + if ( owner == NULL ) + return SEQ_FAILED; + + Recall(icarus); + + + //Flush the sequences +/* sequenceID_m::iterator iterSeq = NULL; + for ( iterSeq = m_sequenceMap.begin(); iterSeq != m_sequenceMap.end(); ) + { + if ( ( (*iterSeq).second == owner ) || ( owner->HasChild( (*iterSeq).second ) ) || ( (*iterSeq).second->HasFlag( CSequence::SQ_PENDING ) ) || ( (*iterSeq).second->HasFlag( CSequence::SQ_TASK ) ) ) + { + iterSeq++; + continue; + } + + //Delete it, and remove all references + RemoveSequence( (*iterSeq).second, icarus ); + icarus->DeleteSequence( (*iterSeq).second ); + + //Remove it from the map + //Delete from the sequence list and move on + iterSeq = m_sequenceMap.erase( iterSeq ); + }*/ + + + // OLD STUFF! + //Flush the sequences + sequence_l::iterator sli; + for ( sli = m_sequences.begin(); sli != m_sequences.end(); ) + { + if ( ( (*sli) == owner ) || ( owner->HasChild( (*sli) ) ) || ( (*sli)->HasFlag( CSequence::SQ_PENDING ) ) || ( (*sli)->HasFlag( CSequence::SQ_TASK ) ) ) + { + sli++; + continue; + } + + //Remove it from the map + //m_sequenceMap.erase( (*sli)->GetID() ); + + //Delete it, and remove all references + RemoveSequence( (*sli), icarus ); + icarus->DeleteSequence( (*sli) ); + + //Delete from the sequence list and move on + sli = m_sequences.erase( sli ); + } + + //Make sure this owner knows it's now the root sequence + owner->SetParent( NULL ); + owner->SetReturn( NULL ); + + return SEQ_OK; +} + +/* +======================== +AddStream + +Creates a stream for parsing +======================== +*/ + +bstream_t *CSequencer::AddStream( void ) +{ + bstream_t *stream; + + stream = new bstream_t; //deleted in Route() + stream->stream = new CBlockStream; //deleted in Route() + stream->last = m_curStream; + + m_streamsCreated.push_back(stream); + + return stream; +} + +/* +======================== +DeleteStream + +Deletes parsing stream +======================== +*/ +void CSequencer::DeleteStream( bstream_t *bstream ) +{ + vector::iterator finder = find(m_streamsCreated.begin(), m_streamsCreated.end(), bstream); + if(finder != m_streamsCreated.end()) + { + m_streamsCreated.erase(finder); + } + + bstream->stream->Free(); + + delete bstream->stream; + delete bstream; + + bstream = NULL; +} + +/* +------------------------- +AddTaskSequence +------------------------- +*/ + +void CSequencer::AddTaskSequence( CSequence *sequence, CTaskGroup *group ) +{ + m_taskSequences[ group ] = sequence; +} + +/* +------------------------- +GetTaskSequence +------------------------- +*/ + +CSequence *CSequencer::GetTaskSequence( CTaskGroup *group ) +{ + taskSequence_m::iterator tsi; + + tsi = m_taskSequences.find( group ); + + if ( tsi == m_taskSequences.end() ) + return NULL; + + return (*tsi).second; +} + +/* +======================== +AddSequence + +Creates and adds a sequence to the sequencer +======================== +*/ + +CSequence *CSequencer::AddSequence( CIcarus* icarus ) +{ + CSequence *sequence = (CSequence*)icarus->GetSequence(); + + assert( sequence ); + if ( sequence == NULL ) + return NULL; + + //The rest is handled internally to the class + //m_sequenceMap[ sequence->GetID() ] = sequence; + + // OLD STUFF! + //Add it to the list + m_sequences.insert( m_sequences.end(), sequence ); + + //FIXME: Temp fix + sequence->SetFlag( CSequence::SQ_PENDING ); + + return sequence; +} + +CSequence *CSequencer::AddSequence( CSequence *parent, CSequence *returnSeq, int flags, CIcarus* icarus ) +{ + CSequence *sequence = (CSequence*)icarus->GetSequence(); + + assert( sequence ); + if ( sequence == NULL ) + return NULL; + + //The rest is handled internally to the class +// m_sequenceMap[ sequence->GetID() ] = sequence; + + // OLD STUFF! + //Add it to the list + m_sequences.insert( m_sequences.end(), sequence ); + + sequence->SetFlags( flags ); + sequence->SetParent( parent ); + sequence->SetReturn( returnSeq ); + + return sequence; +} + +/* +======================== +GetSequence + +Retrieves a sequence by its ID +======================== +*/ + +CSequence *CSequencer::GetSequence( int id ) +{ +/* sequenceID_m::iterator mi; + + mi = m_sequenceMap.find( id ); + + if ( mi == m_sequenceMap.end() ) + return NULL; + + return (*mi).second;*/ + + sequence_l::iterator iterSeq; + STL_ITERATE( iterSeq, m_sequences ) + { + if ( (*iterSeq)->GetID() == id ) + return (*iterSeq); + } + + return NULL; +} + +/* +------------------------- +Interrupt +------------------------- +*/ + +void CSequencer::Interrupt( void ) +{ + CBlock *command = m_taskManager->GetCurrentTask(); + + if ( command == NULL ) + return; + + //Save it + PushCommand( command, CSequence::PUSH_BACK ); +} + +/* +======================== +Run + +Runs a script +======================== +*/ +int CSequencer::Run( char *buffer, long size, CIcarus* icarus ) +{ + bstream_t *blockStream; + + IGameInterface* game = icarus->GetGame(); + + Recall(icarus); + + //Create a new stream + blockStream = AddStream(); + + //Open the stream as an IBI stream + if (!blockStream->stream->Open( buffer, size )) + { + game->DebugPrint(IGameInterface::WL_ERROR, "invalid stream" ); + return SEQ_FAILED; + } + + CSequence *sequence = AddSequence( NULL, m_curSequence, CSequence::SQ_COMMON, icarus ); + + // Interpret the command blocks and route them properly + if ( S_FAILED( Route( sequence, blockStream, icarus )) ) + { + //Error code is set inside of Route() + return SEQ_FAILED; + } + + return SEQ_OK; +} + +/* +======================== +ParseRun + +Parses a user triggered run command +======================== +*/ + +int CSequencer::ParseRun( CBlock *block , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CSequence *new_sequence; + bstream_t *new_stream; + char *buffer; + char newname[ CIcarus::MAX_STRING_SIZE ]; + int buffer_size; + + //Get the name and format it + StripExtension( (char*) block->GetMemberData( 0 ), (char *) newname ); + + //Get the file from the game engine + buffer_size = game->LoadFile( newname, (void **) &buffer ); + + if ( buffer_size <= 0 ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "'%s' : could not open file\n", (char*) block->GetMemberData( 0 )); + block->Free(icarus); + delete block; + block = NULL; + return SEQ_FAILED; + } + + //Create a new stream for this file + new_stream = AddStream(); + + //Begin streaming the file + if (!new_stream->stream->Open( buffer, buffer_size )) + { + game->DebugPrint(IGameInterface::WL_ERROR, "invalid stream" ); + block->Free(icarus); + delete block; + block = NULL; + return SEQ_FAILED; + } + + //Create a new sequence + new_sequence = AddSequence( m_curSequence, m_curSequence, ( CSequence::SQ_RUN | CSequence::SQ_PENDING ), icarus ); + + m_curSequence->AddChild( new_sequence ); + + // Interpret the command blocks and route them properly + if ( S_FAILED( Route( new_sequence, new_stream, icarus )) ) + { + //Error code is set inside of Route() + block->Free(icarus); + delete block; + block = NULL; + return SEQ_FAILED; + } + + m_curSequence = m_curSequence->GetReturn(); + + assert( m_curSequence ); + + block->Write( CIcarus::TK_FLOAT, (float) new_sequence->GetID() , icarus); + PushCommand( block, CSequence::PUSH_FRONT ); + + return SEQ_OK; +} + +/* +======================== +ParseIf + +Parses an if statement +======================== +*/ + +int CSequencer::ParseIf( CBlock *block, bstream_t *bstream , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CSequence *sequence; + + //Create the container sequence + sequence = AddSequence( m_curSequence, m_curSequence, CSequence::SQ_CONDITIONAL, icarus); + + assert( sequence ); + if ( sequence == NULL ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "ParseIf: failed to allocate container sequence" ); + block->Free(icarus); + delete block; + block = NULL; + return SEQ_FAILED; + } + + m_curSequence->AddChild( sequence ); + + //Add a unique conditional identifier to the block for reference later + block->Write( CIcarus::TK_FLOAT, (float) sequence->GetID(), icarus ); + + //Push this onto the stack to mark the conditional entrance + PushCommand( block, CSequence::PUSH_FRONT ); + + //Recursively obtain the conditional body + Route( sequence, bstream, icarus ); + + m_elseValid = 2; + m_elseOwner = block; + + return SEQ_OK; +} + +/* +======================== +ParseElse + +Parses an else statement +======================== +*/ + +int CSequencer::ParseElse( CBlock *block, bstream_t *bstream , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + //The else is not retained + block->Free(icarus); + delete block; + block = NULL; + + CSequence *sequence; + + //Create the container sequence + sequence = AddSequence( m_curSequence, m_curSequence, CSequence::SQ_CONDITIONAL, icarus ); + + assert( sequence ); + if ( sequence == NULL ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "ParseIf: failed to allocate container sequence" ); + return SEQ_FAILED; + } + + m_curSequence->AddChild( sequence ); + + //Add a unique conditional identifier to the block for reference later + //TODO: Emit warning + if ( m_elseOwner == NULL ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "Invalid 'else' found!\n" ); + return SEQ_FAILED; + } + + m_elseOwner->Write( CIcarus::TK_FLOAT, (float) sequence->GetID(), icarus ); + + m_elseOwner->SetFlag( BF_ELSE ); + + //Recursively obtain the conditional body + Route( sequence, bstream, icarus ); + + m_elseValid = 0; + m_elseOwner = NULL; + + return SEQ_OK; +} + +/* +======================== +ParseLoop + +Parses a loop command +======================== +*/ + +int CSequencer::ParseLoop( CBlock *block, bstream_t *bstream , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CSequence *sequence; + CBlockMember *bm; + float min, max; + int rIter; + int memberNum = 0; + + //Set the parent + sequence = AddSequence( m_curSequence, m_curSequence, ( CSequence::SQ_LOOP | CSequence::SQ_RETAIN ), icarus ); + + assert( sequence ); + if ( sequence == NULL ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "ParseLoop : failed to allocate container sequence" ); + block->Free(icarus); + delete block; + block = NULL; + return SEQ_FAILED; + } + + m_curSequence->AddChild( sequence ); + + //Set the number of iterations of this sequence + + bm = block->GetMember( memberNum++ ); + + if ( bm->GetID() == CIcarus::ID_RANDOM ) + { + //Parse out the random number + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + rIter = (int) game->Random( min, max ); + sequence->SetIterations( rIter ); + } + else + { + sequence->SetIterations ( (int) (*(float *) bm->GetData()) ); + } + + //Add a unique loop identifier to the block for reference later + block->Write( CIcarus::TK_FLOAT, (float) sequence->GetID(), icarus ); + + //Push this onto the stack to mark the loop entrance + PushCommand( block, CSequence::PUSH_FRONT ); + + //Recursively obtain the loop + Route( sequence, bstream , icarus); + + return SEQ_OK; +} + +/* +======================== +AddAffect + +Adds a sequence that is saved until the affect is called by the parent +======================== +*/ + +int CSequencer::AddAffect( bstream_t *bstream, int retain, int *id, CIcarus* icarus ) +{ + CSequence *sequence = AddSequence(icarus); + bstream_t new_stream; + + sequence->SetFlag( CSequence::SQ_AFFECT | CSequence::SQ_PENDING ); + + if ( retain ) + sequence->SetFlag( CSequence::SQ_RETAIN ); + + //This will be replaced once it's actually used, but this will restore the route state properly + sequence->SetReturn( m_curSequence ); + + //We need this as a temp holder + new_stream.last = m_curStream; + new_stream.stream = bstream->stream; + + if S_FAILED( Route( sequence, &new_stream , icarus) ) + { + return SEQ_FAILED; + } + + *id = sequence->GetID(); + + sequence->SetReturn( NULL ); + + return SEQ_OK; +} + +/* +======================== +ParseAffect + +Parses an affect command +======================== +*/ + +int CSequencer::ParseAffect( CBlock *block, bstream_t *bstream, CIcarus* icarus ) +{ + IGameInterface* game = icarus->GetGame(); + CSequencer *stream_sequencer = NULL; + char *entname = NULL; + int ret; + int ent = -1; + + entname = (char*) block->GetMemberData( 0 ); + ent = game->GetByName( entname ); + + if( ent < 0 ) // if there wasn't a valid entname in the affect, we need to check if it's a get command + { + //try to parse a 'get' command that is embeded in this 'affect' + + int id; + char *p1 = NULL; + char *name = 0; + CBlockMember *bm = NULL; + // + // Get the first parameter (this should be the get) + // + bm = block->GetMember( 0 ); + id = bm->GetID(); + + switch ( id ) + { + // these 3 cases probably aren't necessary + case CIcarus::TK_STRING: + case CIcarus::TK_IDENTIFIER: + case CIcarus::TK_CHAR: + p1 = (char *) bm->GetData(); + break; + + case CIcarus::ID_GET: + { + int type; + + //get( TYPE, NAME ) + type = (int) (*(float *) block->GetMemberData( 1 )); + name = (char *) block->GetMemberData( 2 ); + + switch ( type ) // what type are they attempting to get + { + + case CIcarus::TK_STRING: + //only string is acceptable for affect, store result in p1 + if ( game->GetString( m_ownerID, name, &p1 ) == false) + { + block->Free(icarus); + delete block; + block = NULL; + return false; + } + break; + default: + //FIXME: Make an enum id for the error... + game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on affect _1" ); + block->Free(icarus); + delete block; + block = NULL; + return false; + break; + } + + break; + } + + default: + //FIXME: Make an enum id for the error... + game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on affect _2" ); + block->Free(icarus); + delete block; + block = NULL; + return false; + break; + }//end id switch + + if(p1) + { + ent = game->GetByName( p1 ); + } + if(ent < 0) + { // a valid entity name was not returned from the get command + game->DebugPrint(IGameInterface::WL_WARNING, "'%s' : invalid affect() target\n"); + } + + } // end if(!ent) + + if( ent >= 0 ) + { + int sequencerID = game->CreateIcarus(ent); + stream_sequencer = icarus->FindSequencer(sequencerID); + } + + if (stream_sequencer == NULL) + { + game->DebugPrint(IGameInterface::WL_WARNING, "'%s' : invalid affect() target\n", entname ); + + //Fast-forward out of this affect block onto the next valid code + CSequence *backSeq = m_curSequence; + + CSequence *trashSeq = (CSequence*)icarus->GetSequence(); + Route( trashSeq, bstream , icarus); + Recall(icarus); + DestroySequence( trashSeq, icarus ); + m_curSequence = backSeq; + block->Free(icarus); + delete block; + block = NULL; + return SEQ_OK; + } + + if S_FAILED ( stream_sequencer->AddAffect( bstream, (int) m_curSequence->HasFlag( CSequence::SQ_RETAIN ), &ret, icarus) ) + { + block->Free(icarus); + delete block; + block = NULL; + return SEQ_FAILED; + } + + //Hold onto the id for later use + //FIXME: If the target sequence is freed, what then? (!suspect!) + + block->Write( CIcarus::TK_FLOAT, (float) ret, icarus ); + + PushCommand( block, CSequence::PUSH_FRONT ); + /* + //Don't actually do these right now, we're just pre-processing (parsing) the affect + if( ent ) + { // ents need to update upon being affected + ent->taskManager->Update(); + } + */ + + return SEQ_OK; +} + +/* +------------------------- +ParseTask +------------------------- +*/ + +int CSequencer::ParseTask( CBlock *block, bstream_t *bstream , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CSequence *sequence; + CTaskGroup *group; + const char *taskName; + + //Setup the container sequence + sequence = AddSequence( m_curSequence, m_curSequence, CSequence::SQ_TASK | CSequence::SQ_RETAIN, icarus); + m_curSequence->AddChild( sequence ); + + //Get the name of this task for reference later + taskName = (const char *) block->GetMemberData( 0 ); + + //Get a new task group from the task manager + group = m_taskManager->AddTaskGroup( taskName, icarus ); + + if ( group == NULL ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "error : unable to allocate a new task group" ); + block->Free(icarus); + delete block; + block = NULL; + return SEQ_FAILED; + } + + //The current group is set to this group, all subsequent commands (until a block end) will fall into this task group + group->SetParent( m_curGroup ); + m_curGroup = group; + + //Keep an association between this task and the container sequence + AddTaskSequence( sequence, group ); + + //PushCommand( block, PUSH_FRONT ); + block->Free(icarus); + delete block; + block = NULL; + + //Recursively obtain the loop + Route( sequence, bstream, icarus ); + + return SEQ_OK; +} + +/* +======================== +Route + +Properly handles and routes commands to the sequencer +======================== +*/ + +//FIXME: Re-entering this code will produce unpredictable results if a script has already been routed and is running currently + +//FIXME: A sequencer cannot properly affect itself + +int CSequencer::Route( CSequence *sequence, bstream_t *bstream , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CBlockStream *stream; + CBlock *block; + + //Take the stream as the current stream + m_curStream = bstream; + stream = bstream->stream; + + m_curSequence = sequence; + + //Obtain all blocks + while ( stream->BlockAvailable() ) + { + block = new CBlock; //deleted in Free() + stream->ReadBlock( block , icarus); + + //TEMP: HACK! + if ( m_elseValid ) + m_elseValid--; + + switch( block->GetBlockID() ) + { + //Marks the end of a blocked section + case CIcarus::ID_BLOCK_END: + + //Save this as a pre-process marker + PushCommand( block, CSequence::PUSH_FRONT ); + + if ( m_curSequence->HasFlag( CSequence::SQ_RUN ) || m_curSequence->HasFlag( CSequence::SQ_AFFECT ) ) + { + //Go back to the last stream + m_curStream = bstream->last; + } + + if ( m_curSequence->HasFlag( CSequence::SQ_TASK ) ) + { + //Go back to the last stream + m_curStream = bstream->last; + m_curGroup = m_curGroup->GetParent(); + } + + m_curSequence = m_curSequence->GetReturn(); + + return SEQ_OK; + break; + + //Affect pre-processor + case CIcarus::ID_AFFECT: + + if S_FAILED( ParseAffect( block, bstream, icarus ) ) + return SEQ_FAILED; + + break; + + //Run pre-processor + case CIcarus::ID_RUN: + + if S_FAILED( ParseRun( block, icarus ) ) + return SEQ_FAILED; + + break; + + //Loop pre-processor + case CIcarus::ID_LOOP: + + if S_FAILED( ParseLoop( block, bstream, icarus ) ) + return SEQ_FAILED; + + break; + + //Conditional pre-processor + case CIcarus::ID_IF: + + if S_FAILED( ParseIf( block, bstream, icarus ) ) + return SEQ_FAILED; + + break; + + case CIcarus::ID_ELSE: + + //TODO: Emit warning + if ( m_elseValid == 0 ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "Invalid 'else' found!\n" ); + return SEQ_FAILED; + } + + if S_FAILED( ParseElse( block, bstream, icarus ) ) + return SEQ_FAILED; + + break; + + case CIcarus::ID_TASK: + + if S_FAILED( ParseTask( block, bstream, icarus ) ) + return SEQ_FAILED; + + break; + + //FIXME: For now this is to catch problems, but can ultimately be removed + case CIcarus::ID_WAIT: + case CIcarus::ID_PRINT: + case CIcarus::ID_SOUND: + case CIcarus::ID_MOVE: + case CIcarus::ID_ROTATE: + case CIcarus::ID_SET: + case CIcarus::ID_USE: + case CIcarus::ID_REMOVE: + case CIcarus::ID_KILL: + case CIcarus::ID_FLUSH: + case CIcarus::ID_CAMERA: + case CIcarus::ID_DO: + case CIcarus::ID_DECLARE: + case CIcarus::ID_FREE: + case CIcarus::ID_SIGNAL: + case CIcarus::ID_WAITSIGNAL: + case CIcarus::ID_PLAY: + + //Commands go directly into the sequence without pre-process + PushCommand( block, CSequence::PUSH_FRONT ); + break; + + //Error + default: + + game->DebugPrint(IGameInterface::WL_ERROR, "'%d' : invalid block ID", block->GetBlockID() ); + + return SEQ_FAILED; + break; + } + } + + //Check for a run sequence, it must be marked + if ( m_curSequence->HasFlag( CSequence::SQ_RUN ) ) + { + block = new CBlock; + block->Create( CIcarus::ID_BLOCK_END ); + PushCommand( block, CSequence::PUSH_FRONT ); //mark the end of the run + + /* + //Free the stream + m_curStream = bstream->last; + DeleteStream( bstream ); + */ + + return SEQ_OK; + } + + //Check to start the communication + if ( ( bstream->last == NULL ) && ( m_numCommands > 0 ) ) + { + //Everything is routed, so get it all rolling + Prime( m_taskManager, PopCommand( CSequence::POP_BACK ), icarus ); + } + + m_curStream = bstream->last; + + //Free the stream + DeleteStream( bstream ); + + return SEQ_OK; +} + +/* +======================== +CheckRun + +Checks for run command pre-processing +======================== +*/ + +//Directly changes the parameter to avoid excess push/pop + +void CSequencer::CheckRun( CBlock **command , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CBlock *block = *command; + + if ( block == NULL ) + return; + + //Check for a run command + if ( block->GetBlockID() == CIcarus::ID_RUN ) + { + int id = (int) (*(float *) block->GetMemberData( 1 )); + + game->DebugPrint(IGameInterface::WL_DEBUG, "%4d run( \"%s\" ); [%d]", m_ownerID, (char *) block->GetMemberData(0), game->GetTime() ); + + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + + *command = NULL; + } + + m_curSequence = GetSequence( id ); + + //TODO: Emit warning + assert( m_curSequence ); + if ( m_curSequence == NULL ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "Unable to find 'run' sequence!\n" ); + *command = NULL; + return; + } + + if ( m_curSequence->GetNumCommands() > 0 ) + { + *command = PopCommand( CSequence::POP_BACK ); + + Prep( command , icarus); //Account for any other pre-processes + return; + } + + return; + } + + //Check for the end of a run + if ( ( block->GetBlockID() == CIcarus::ID_BLOCK_END ) && ( m_curSequence->HasFlag( CSequence::SQ_RUN ) ) ) + { + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + m_curSequence = ReturnSequence( m_curSequence ); + + if ( m_curSequence && m_curSequence->GetNumCommands() > 0 ) + { + *command = PopCommand( CSequence::POP_BACK ); + + Prep( command, icarus ); //Account for any other pre-processes + return; + } + + //FIXME: Check this... + } +} + +/* +------------------------- +EvaluateConditional +------------------------- +*/ + +//FIXME: This function will be written better later once the functionality of the ideas here are tested + +int CSequencer::EvaluateConditional( CBlock *block , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CBlockMember *bm; + char tempString1[128], tempString2[128]; + vec3_t vec; + int id, i, oper, memberNum = 0; + char *p1 = NULL, *p2 = NULL; + int t1, t2; + + // + // Get the first parameter + // + + bm = block->GetMember( memberNum++ ); + id = bm->GetID(); + + t1 = id; + + switch ( id ) + { + case CIcarus::TK_FLOAT: + sprintf( (char *) tempString1, "%.3f", *(float *) bm->GetData() ); + p1 = (char *) tempString1; + break; + + case CIcarus::TK_VECTOR: + + tempString1[0] = NULL; + + for ( i = 0; i < 3; i++ ) + { + bm = block->GetMember( memberNum++ ); + vec[i] = *(float *) bm->GetData(); + } + + sprintf( (char *) tempString1, "%.3f %.3f %.3f", vec[0], vec[1], vec[2] ); + p1 = (char *) tempString1; + + break; + + case CIcarus::TK_STRING: + case CIcarus::TK_IDENTIFIER: + case CIcarus::TK_CHAR: + + p1 = (char *) bm->GetData(); + break; + + case CIcarus::ID_GET: + { + int type; + char *name; + + //get( TYPE, NAME ) + type = (int) (*(float *) block->GetMemberData( memberNum++ )); + name = (char *) block->GetMemberData( memberNum++ ); + + //Get the type returned and hold onto it + t1 = type; + + switch ( type ) + { + case CIcarus::TK_FLOAT: + { + float fVal; + + if ( game->GetFloat( m_ownerID, name, &fVal ) == false) + return false; + + sprintf( (char *) tempString1, "%.3f", fVal ); + p1 = (char *) tempString1; + } + + break; + + case CIcarus::TK_INT: + { + float fVal; + + if ( game->GetFloat( m_ownerID, name, &fVal ) == false) + return false; + + sprintf( (char *) tempString1, "%d", (int) fVal ); + p1 = (char *) tempString1; + } + break; + + case CIcarus::TK_STRING: + + if ( game->GetString( m_ownerID, name, &p1 ) == false) + return false; + + break; + + case CIcarus::TK_VECTOR: + { + vec3_t vVal; + + if ( game->GetVector( m_ownerID, name, vVal ) == false) + return false; + + sprintf( (char *) tempString1, "%.3f %.3f %.3f", vVal[0], vVal[1], vVal[2] ); + p1 = (char *) tempString1; + } + + break; + } + + break; + } + + case CIcarus::ID_RANDOM: + { + float min, max; + //FIXME: This will not account for nested random() statements + + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + //A float value is returned from the function + t1 = CIcarus::TK_FLOAT; + + sprintf( (char *) tempString1, "%.3f", game->Random( min, max ) ); + p1 = (char *) tempString1; + } + + break; + + case CIcarus::ID_TAG: + { + char *name; + float type; + + name = (char *) block->GetMemberData( memberNum++ ); + type = *(float *) block->GetMemberData( memberNum++ ); + + t1 = CIcarus::TK_VECTOR; + + //TODO: Emit warning + if ( game->GetTag( m_ownerID, name, (int) type, vec ) == false) + { + game->DebugPrint(IGameInterface::WL_ERROR, "Unable to find tag \"%s\"!\n", name ); + return false; + } + + sprintf( (char *) tempString1, "%.3f %.3f %.3f", vec[0], vec[1], vec[2] ); + p1 = (char *) tempString1; + + break; + } + + default: + //FIXME: Make an enum id for the error... + game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on conditional" ); + return false; + break; + } + + // + // Get the comparison operator + // + + bm = block->GetMember( memberNum++ ); + id = bm->GetID(); + + switch ( id ) + { + case CIcarus::TK_EQUALS: + case CIcarus::TK_GREATER_THAN: + case CIcarus::TK_LESS_THAN: + case CIcarus::TK_NOT: + oper = id; + break; + + default: + game->DebugPrint(IGameInterface::WL_ERROR, "Invalid operator type found on conditional!\n" ); + return false; //FIXME: Emit warning + break; + } + + // + // Get the second parameter + // + + bm = block->GetMember( memberNum++ ); + id = bm->GetID(); + + t2 = id; + + switch ( id ) + { + case CIcarus::TK_FLOAT: + sprintf( (char *) tempString2, "%.3f", *(float *) bm->GetData() ); + p2 = (char *) tempString2; + break; + + case CIcarus::TK_VECTOR: + + tempString2[0] = NULL; + + for ( i = 0; i < 3; i++ ) + { + bm = block->GetMember( memberNum++ ); + vec[i] = *(float *) bm->GetData(); + } + + sprintf( (char *) tempString2, "%.3f %.3f %.3f", vec[0], vec[1], vec[2] ); + p2 = (char *) tempString2; + + break; + + case CIcarus::TK_STRING: + case CIcarus::TK_IDENTIFIER: + case CIcarus::TK_CHAR: + + p2 = (char *) bm->GetData(); + break; + + case CIcarus::ID_GET: + { + int type; + char *name; + + //get( TYPE, NAME ) + type = (int) (*(float *) block->GetMemberData( memberNum++ )); + name = (char *) block->GetMemberData( memberNum++ ); + + //Get the type returned and hold onto it + t2 = type; + + switch ( type ) + { + case CIcarus::TK_FLOAT: + { + float fVal; + + if ( game->GetFloat( m_ownerID, name, &fVal ) == false) + return false; + + sprintf( (char *) tempString2, "%.3f", fVal ); + p2 = (char *) tempString2; + } + + break; + + case CIcarus::TK_INT: + { + float fVal; + + if ( game->GetFloat( m_ownerID, name, &fVal ) == false) + return false; + + sprintf( (char *) tempString2, "%d", (int) fVal ); + p2 = (char *) tempString2; + } + break; + + case CIcarus::TK_STRING: + + if ( game->GetString( m_ownerID, name, &p2 ) == false) + return false; + + break; + + case CIcarus::TK_VECTOR: + { + vec3_t vVal; + + if ( game->GetVector( m_ownerID, name, vVal ) == false) + return false; + + sprintf( (char *) tempString2, "%.3f %.3f %.3f", vVal[0], vVal[1], vVal[2] ); + p2 = (char *) tempString2; + } + + break; + } + + break; + } + + case CIcarus::ID_RANDOM: + + { + float min, max; + //FIXME: This will not account for nested random() statements + + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + //A float value is returned from the function + t2 = CIcarus::TK_FLOAT; + + sprintf( (char *) tempString2, "%.3f", game->Random( min, max ) ); + p2 = (char *) tempString2; + } + + break; + + case CIcarus::ID_TAG: + + { + char *name; + float type; + + name = (char *) block->GetMemberData( memberNum++ ); + type = *(float *) block->GetMemberData( memberNum++ ); + + t2 = CIcarus::TK_VECTOR; + + //TODO: Emit warning + if ( game->GetTag( m_ownerID, name, (int) type, vec ) == false) + { + game->DebugPrint(IGameInterface::WL_ERROR, "Unable to find tag \"%s\"!\n", name ); + return false; + } + + sprintf( (char *) tempString2, "%.3f %.3f %.3f", vec[0], vec[1], vec[2] ); + p2 = (char *) tempString2; + + break; + } + + default: + //FIXME: Make an enum id for the error... + game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on conditional" ); + return false; + break; + } + + return game->Evaluate( t1, p1, t2, p2, oper ); +} + +/* +======================== +CheckIf + +Checks for if statement pre-processing +======================== +*/ + +void CSequencer::CheckIf( CBlock **command , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CBlock *block = *command; + int successID, failureID; + CSequence *successSeq, *failureSeq; + + if ( block == NULL ) + return; + + if ( block->GetBlockID() == CIcarus::ID_IF ) + { + int ret = EvaluateConditional( block, icarus ); + + if ( ret /*TRUE*/ ) + { + if ( block->HasFlag( BF_ELSE ) ) + { + successID = (int) (*(float *) block->GetMemberData( block->GetNumMembers() - 2 )); + } + else + { + successID = (int) (*(float *) block->GetMemberData( block->GetNumMembers() - 1 )); + } + + successSeq = GetSequence( successID ); + + //TODO: Emit warning + assert( successSeq ); + if ( successSeq == NULL ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "Unable to find conditional success sequence!\n" ); + *command = NULL; + return; + } + + //Only save the conditional statement if the calling sequence is retained + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + m_curSequence = successSeq; + + //Recursively work out any other pre-processors + *command = PopCommand( CSequence::POP_BACK ); + Prep( command , icarus); + + return; + } + + if ( ( ret == false ) && ( block->HasFlag( BF_ELSE ) ) ) + { + failureID = (int) (*(float *) block->GetMemberData( block->GetNumMembers() - 1 )); + failureSeq = GetSequence( failureID ); + + //TODO: Emit warning + assert( failureSeq ); + if ( failureSeq == NULL ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "Unable to find conditional failure sequence!\n" ); + *command = NULL; + return; + } + + //Only save the conditional statement if the calling sequence is retained + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + m_curSequence = failureSeq; + + //Recursively work out any other pre-processors + *command = PopCommand( CSequence::POP_BACK ); + Prep( command , icarus); + + return; + } + + //Only save the conditional statement if the calling sequence is retained + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + //Conditional failed, just move on to the next command + *command = PopCommand( CSequence::POP_BACK ); + Prep( command , icarus); + + return; + } + + if ( ( block->GetBlockID() == CIcarus::ID_BLOCK_END ) && ( m_curSequence->HasFlag( CSequence::SQ_CONDITIONAL ) ) ) + { + assert( m_curSequence->GetReturn() ); + if ( m_curSequence->GetReturn() == NULL ) + { + *command = NULL; + return; + } + + //Check to retain it + if ( m_curSequence->GetParent()->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + //Back out of the conditional and resume the previous sequence + m_curSequence = ReturnSequence( m_curSequence ); + + //This can safely happen + if ( m_curSequence == NULL ) + { + *command = NULL; + return; + } + + *command = PopCommand( CSequence::POP_BACK ); + Prep( command , icarus); + } +} + +/* +======================== +CheckLoop + +Checks for loop command pre-processing +======================== +*/ + +void CSequencer::CheckLoop( CBlock **command , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CBlockMember *bm; + CBlock *block = *command; + float min, max; + int iterations; + int loopID; + int memberNum = 0; + + if ( block == NULL ) + return; + + //Check for a loop + if ( block->GetBlockID() == CIcarus::ID_LOOP ) + { + //Get the loop ID + bm = block->GetMember( memberNum++ ); + + if ( bm->GetID() == CIcarus::ID_RANDOM ) + { + //Parse out the random number + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + iterations = (int) game->Random( min, max ); + } + else + { + iterations = (int) (*(float *) bm->GetData()); + } + + loopID = (int) (*(float *) block->GetMemberData( memberNum++ )); + + CSequence *loop = GetSequence( loopID ); + + //TODO: Emit warning + assert( loop ); + if ( loop == NULL ) + { + game->DebugPrint(IGameInterface::WL_ERROR, "Unable to find 'loop' sequence!\n" ); + *command = NULL; + return; + } + + assert( loop->GetParent() ); + if ( loop->GetParent() == NULL ) + { + *command = NULL; + return; + } + + //Restore the count if it has been lost + loop->SetIterations( iterations ); + + //Only save the loop command if the calling sequence is retained + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + m_curSequence = loop; + + //Recursively work out any other pre-processors + *command = PopCommand( CSequence::POP_BACK ); + Prep( command , icarus); + + return; + } + + //Check for the end of the loop + if ( ( block->GetBlockID() == CIcarus::ID_BLOCK_END ) && ( m_curSequence->HasFlag( CSequence::SQ_LOOP ) ) ) + { + //We don't want to decrement -1 + if ( m_curSequence->GetIterations() > 0 ) + m_curSequence->SetIterations( m_curSequence->GetIterations()-1 ); //Nice, eh? + + //Either there's another iteration, or it's infinite + if ( m_curSequence->GetIterations() != 0 ) + { + //Another iteration is going to happen, so this will need to be considered again + PushCommand( block, CSequence::PUSH_FRONT ); + + *command = PopCommand( CSequence::POP_BACK ); + Prep( command, icarus ); + + return; + } + else + { + assert( m_curSequence->GetReturn() ); + if ( m_curSequence->GetReturn() == NULL ) + { + *command = NULL; + return; + } + + //Check to retain it + if ( m_curSequence->GetParent()->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + //Back out of the loop and resume the previous sequence + m_curSequence = ReturnSequence( m_curSequence ); + + //This can safely happen + if ( m_curSequence == NULL ) + { + *command = NULL; + return; + } + + *command = PopCommand( CSequence::POP_BACK ); + Prep( command, icarus); + } + } +} + +/* +======================== +CheckFlush + +Checks for flush command pre-processing +======================== +*/ + +void CSequencer::CheckFlush( CBlock **command, CIcarus* icarus) +{ + CBlock *block = *command; + + if ( block == NULL ) + return; + + if ( block->GetBlockID() == CIcarus::ID_FLUSH ) + { + //Flush the sequence + Flush( m_curSequence, icarus ); + + //Check to retain it + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + *command = PopCommand( CSequence::POP_BACK ); + Prep( command , icarus); + + return; + } +} + +/* +======================== +CheckAffect + +Checks for affect command pre-processing +======================== +*/ + +void CSequencer::CheckAffect( CBlock **command , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CBlock *block = *command; + int ent = -1; + char *entname = NULL; + int memberNum = 0; + + if ( block == NULL ) + { + return; + } + + if ( block->GetBlockID() == CIcarus::ID_AFFECT ) + { + CSequencer *sequencer = NULL; + entname = (char*) block->GetMemberData( memberNum++ ); + ent = game->GetByName( entname ); + + if( ent < 0 ) // if there wasn't a valid entname in the affect, we need to check if it's a get command + { + //try to parse a 'get' command that is embeded in this 'affect' + + int id; + char *p1 = NULL; + char *name = 0; + CBlockMember *bm = NULL; + // + // Get the first parameter (this should be the get) + // + bm = block->GetMember( 0 ); + id = bm->GetID(); + + switch ( id ) + { + // these 3 cases probably aren't necessary + case CIcarus::TK_STRING: + case CIcarus::TK_IDENTIFIER: + case CIcarus::TK_CHAR: + p1 = (char *) bm->GetData(); + break; + + case CIcarus::ID_GET: + { + int type; + + //get( TYPE, NAME ) + type = (int) (*(float *) block->GetMemberData( memberNum++ )); + name = (char *) block->GetMemberData( memberNum++ ); + + switch ( type ) // what type are they attempting to get + { + + case CIcarus::TK_STRING: + //only string is acceptable for affect, store result in p1 + if ( game->GetString( m_ownerID, name, &p1 ) == false) + { + return; + } + break; + default: + //FIXME: Make an enum id for the error... + game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on affect _1" ); + return; + break; + } + + break; + } + + default: + //FIXME: Make an enum id for the error... + game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on affect _2" ); + return; + break; + }//end id switch + + if(p1) + { + ent = game->GetByName( p1 ); + } + if(ent < 0) + { // a valid entity name was not returned from the get command + game->DebugPrint(IGameInterface::WL_WARNING, "'%s' : invalid affect() target\n"); + } + + } // end if(!ent) + + if( ent >= 0) + { + int sequencerID = game->CreateIcarus(ent); + sequencer = icarus->FindSequencer(sequencerID); + } + if(memberNum == 0) + { //there was no get, increment manually before next step + memberNum++; + } + int type = (int) (*(float *) block->GetMemberData( memberNum )); + int id = (int) (*(float *) block->GetMemberData( memberNum+1 )); + + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + //NOTENOTE: If this isn't found, continue on to the next command + if ( sequencer == NULL ) + { + *command = PopCommand( CSequence::POP_BACK ); + Prep( command , icarus); + return; + } + + sequencer->Affect( id, type , icarus); + + *command = PopCommand( CSequence::POP_BACK ); + Prep( command, icarus ); + if( ent >= 0 ) + { // ents need to update upon being affected + int sequencerID = game->CreateIcarus(ent); + CSequencer* entsequencer = icarus->FindSequencer(sequencerID); + CTaskManager* taskmanager = entsequencer->GetTaskManager(); + if(taskmanager) + { + taskmanager->Update(icarus); + } + } + + return; + } + + if ( ( block->GetBlockID() == CIcarus::ID_BLOCK_END ) && ( m_curSequence->HasFlag( CSequence::SQ_AFFECT ) ) ) + { + if ( m_curSequence->HasFlag(CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + m_curSequence = ReturnSequence( m_curSequence ); + + if ( m_curSequence == NULL ) + { + *command = NULL; + return; + } + + *command = PopCommand( CSequence::POP_BACK ); + Prep( command , icarus); + if( ent >= 0) + { // ents need to update upon being affected + int sequencerID = game->CreateIcarus(ent); + CSequencer* entsequencer = icarus->FindSequencer(sequencerID); + CTaskManager* taskmanager = entsequencer->GetTaskManager(); + if(taskmanager) + { + taskmanager->Update(icarus); + } + } + + } +} + +/* +------------------------- +CheckDo +------------------------- +*/ + +void CSequencer::CheckDo( CBlock **command , CIcarus* icarus) +{ + IGameInterface* game = icarus->GetGame(); + CBlock *block = *command; + + if ( block == NULL ) + return; + + if ( block->GetBlockID() == CIcarus::ID_DO ) + { + //Get the sequence + const char *groupName = (const char *) block->GetMemberData( 0 ); + CTaskGroup *group = m_taskManager->GetTaskGroup( groupName, icarus ); + CSequence *sequence = GetTaskSequence( group ); + + //TODO: Emit warning + assert( group ); + if ( group == NULL ) + { + //TODO: Give name/number of entity trying to execute, too + game->DebugPrint(IGameInterface::WL_ERROR, "ICARUS Unable to find task group \"%s\"!\n", groupName ); + *command = NULL; + return; + } + + //TODO: Emit warning + assert( sequence ); + if ( sequence == NULL ) + { + //TODO: Give name/number of entity trying to execute, too + game->DebugPrint(IGameInterface::WL_ERROR, "ICARUS Unable to find task 'group' sequence!\n", groupName ); + *command = NULL; + return; + } + + //Only save the loop command if the calling sequence is retained + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + //Set this to our current sequence + sequence->SetReturn( m_curSequence ); + m_curSequence = sequence; + + group->SetParent( m_curGroup ); + m_curGroup = group; + + //Mark all the following commands as being in the task + m_taskManager->MarkTask( group->GetGUID(), TASK_START, icarus ); + + //Recursively work out any other pre-processors + *command = PopCommand( CSequence::POP_BACK ); + Prep( command , icarus); + + return; + } + + if ( ( block->GetBlockID() == CIcarus::ID_BLOCK_END ) && ( m_curSequence->HasFlag( CSequence::SQ_TASK ) ) ) + { + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + *command = NULL; + } + + m_taskManager->MarkTask( m_curGroup->GetGUID(), TASK_END, icarus ); + m_curGroup = m_curGroup->GetParent(); + + CSequence *returnSeq = ReturnSequence( m_curSequence ); + m_curSequence->SetReturn( NULL ); + m_curSequence = returnSeq; + + if ( m_curSequence == NULL ) + { + *command = NULL; + return; + } + + *command = PopCommand( CSequence::POP_BACK ); + Prep( command , icarus); + } +} + +/* +======================== +Prep + +Handles internal sequencer maintenance +======================== +*/ + +void CSequencer::Prep( CBlock **command , CIcarus* icarus) +{ + //Check all pre-processes + CheckAffect( command , icarus); + CheckFlush( command , icarus); + CheckLoop( command , icarus); + CheckRun( command , icarus); + CheckIf( command , icarus); + CheckDo( command , icarus); +} + +/* +======================== +Prime + +Starts communication between the task manager and this sequencer +======================== +*/ + +int CSequencer::Prime( CTaskManager *taskManager, CBlock *command , CIcarus* icarus) +{ + Prep( &command , icarus); + + if ( command ) + { + taskManager->SetCommand( command, CSequence::PUSH_BACK, icarus ); + } + + return SEQ_OK; +} + +/* +======================== +Callback + +Handles a completed task and returns a new task to be completed +======================== +*/ + +int CSequencer::Callback( CTaskManager *taskManager, CBlock *block, int returnCode, CIcarus* icarus ) +{ + IGameInterface* game = icarus->GetGame(); + CBlock *command; + + if (returnCode == TASK_RETURN_COMPLETE) + { + //There are no more pending commands + if ( m_curSequence == NULL ) + { + block->Free(icarus); + delete block; + block = NULL; + return SEQ_OK; + } + + //Check to retain the command + if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) //This isn't true for affect sequences...? + { + PushCommand( block, CSequence::PUSH_FRONT ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + } + + //Check for pending commands + if ( m_curSequence->GetNumCommands() <= 0 ) + { + if ( m_curSequence->GetReturn() == NULL) + return SEQ_OK; + + m_curSequence = m_curSequence->GetReturn(); + } + + command = PopCommand( CSequence::POP_BACK ); + Prep( &command , icarus); + + if ( command ) + taskManager->SetCommand( command, CSequence::PUSH_FRONT, icarus ); + + return SEQ_OK; + } + + //FIXME: This could be more descriptive + game->DebugPrint(IGameInterface::WL_ERROR, "command could not be called back\n" ); + assert(0); + + return SEQ_FAILED; +} + +/* +------------------------- +Recall +------------------------- +*/ + +int CSequencer::Recall( CIcarus* icarus ) +{ + CBlock *block = NULL; + + while ( ( block = m_taskManager->RecallTask() ) != NULL ) + { + if (m_curSequence) + { + PushCommand( block, CSequence::PUSH_BACK ); + } + else + { + block->Free(icarus); + delete block; + block = NULL; + } + } + + return true; +} + +/* +------------------------- +Affect +------------------------- +*/ + +int CSequencer::Affect( int id, int type, CIcarus* icarus ) +{ + IGameInterface* game = icarus->GetGame(); + CSequence *sequence = GetSequence( id ); + + if ( sequence == NULL ) + { + return SEQ_FAILED; + } + + switch ( type ) + { + + case CIcarus::TYPE_FLUSH: + + //Get rid of all old code + Flush( sequence, icarus ); + + sequence->RemoveFlag( CSequence::SQ_PENDING, true ); + + m_curSequence = sequence; + + Prime( m_taskManager, PopCommand( CSequence::POP_BACK ), icarus ); + + break; + + case CIcarus::TYPE_INSERT: + + Recall(icarus); + + sequence->SetReturn( m_curSequence ); + + sequence->RemoveFlag( CSequence::SQ_PENDING, true ); + + m_curSequence = sequence; + + Prime( m_taskManager, PopCommand( CSequence::POP_BACK ), icarus ); + + break; + + + default: + game->DebugPrint(IGameInterface::WL_ERROR, "unknown affect type found" ); + break; + } + + return SEQ_OK; +} + +/* +======================== +PushCommand + +Pushes a commands onto the current sequence +======================== +*/ + +int CSequencer::PushCommand( CBlock *command, int flag ) +{ + //Make sure everything is ok + assert( m_curSequence ); + if ( m_curSequence == NULL ) + return SEQ_FAILED; + + m_curSequence->PushCommand( command, flag ); + m_numCommands++; + + //Invalid flag + return SEQ_OK; +} + +/* +======================== +PopCommand + +Pops a command off the current sequence +======================== +*/ + +CBlock *CSequencer::PopCommand( int flag ) +{ + //Make sure everything is ok + assert( m_curSequence ); + if ( m_curSequence == NULL ) + return NULL; + + CBlock *block = m_curSequence->PopCommand( flag ); + + if ( block != NULL ) + m_numCommands--; + + return block; +} + +/* +======================== +StripExtension + +Filename ultility. Probably get rid of this if I decided to use CStrings... +======================== +*/ + +void CSequencer::StripExtension( const char *in, char *out ) +{ + int i = strlen(in) + 1; + + while ( (in[i] != '.') && (i >= 0) ) + i--; + + if ( i < 0 ) + { + strcpy(out, in); + return; + } + + strncpy(out, in, i); +} + + +/* +------------------------- +RemoveSequence +------------------------- +*/ + +//NOTENOTE: This only removes references to the sequence, IT DOES NOT FREE THE ALLOCATED MEMORY! You've be warned! =) + +int CSequencer::RemoveSequence( CSequence *sequence, CIcarus* icarus ) +{ + IGameInterface* game = icarus->GetGame(); + CSequence *temp; + + int numChildren = sequence->GetNumChildren(); + + //Add all the children + for ( int i = 0; i < numChildren; i++ ) + { + temp = sequence->GetChildByIndex( i ); + + //TODO: Emit warning + assert( temp ); + if ( temp == NULL ) + { + game->DebugPrint(IGameInterface::WL_WARNING, "Unable to find child sequence on RemoveSequence call!\n" ); + continue; + } + + //Remove the references to this sequence + temp->SetParent( NULL ); + temp->SetReturn( NULL ); + + } + + return SEQ_OK; +} + +int CSequencer::DestroySequence( CSequence *sequence, CIcarus* icarus ) +{ + if ( !sequence || !icarus ) + return SEQ_FAILED; + + //m_sequenceMap.erase( sequence->GetID() ); + m_sequences.remove( sequence ); + + taskSequence_m::iterator tsi; + for ( tsi = m_taskSequences.begin(); tsi != m_taskSequences.end(); ) + { + if((*tsi).second == sequence) + { + tsi = m_taskSequences.erase(tsi); + } + else + { + ++tsi; + } + } + + // Remove this guy from his parents list. + CSequence* parent = sequence->GetParent(); + if ( parent ) + { + parent->RemoveChild( sequence ); + parent = NULL; + } + + int curChild = sequence->GetNumChildren(); + while( curChild ) + { + // Stop if we're about to go negative (invalid index!). + if ( curChild > 0 ) + { + DestroySequence( sequence->GetChildByIndex( --curChild ), icarus); + } + else + break; + } + + icarus->DeleteSequence( sequence ); + + return SEQ_OK; +} + +/* +------------------------- +ReturnSequence +------------------------- +*/ + +inline CSequence *CSequencer::ReturnSequence( CSequence *sequence ) +{ + while ( sequence->GetReturn() ) + { + assert(sequence != sequence->GetReturn() ); + if ( sequence == sequence->GetReturn() ) + return NULL; + + sequence = sequence->GetReturn(); + + if ( sequence->GetNumCommands() > 0 ) + return sequence; + } + return NULL; +} + +//Save / Load + +/* +------------------------- +Save +------------------------- +*/ + +int CSequencer::Save() +{ + taskSequence_m::iterator ti; + int numSequences = 0, id, numTasks; + + // Data saved here. + // Owner Sequence. + // Number of Sequences. + // Sequences (data). + // Taskmanager. + // Number of Task Sequences. + // Task Sequences (data): + // -Task group ID. + // -Sequence ID. + // Group ID. + // Number of Commands. + // ID of current Sequence. + + CIcarus *pIcarus = (CIcarus *)IIcarusInterface::GetIcarus(); + + //Get the number of sequences to save out + numSequences = /*m_sequenceMap.size();*/ m_sequences.size(); + + //Save out the owner sequence + pIcarus->BufferWrite( &m_ownerID, sizeof( m_ownerID ) ); + + //Write out the number of sequences we need to read + pIcarus->BufferWrite( &numSequences, sizeof( numSequences ) ); + + //Second pass, save out all sequences, in order + sequence_l::iterator iterSeq = NULL; + STL_ITERATE( iterSeq, m_sequences ) + { + id = (*iterSeq)->GetID(); + pIcarus->BufferWrite( &id, sizeof( id ) ); + } + + //Save out the taskManager + m_taskManager->Save(); + + //Save out the task sequences mapping the name to the GUIDs + numTasks = m_taskSequences.size(); + pIcarus->BufferWrite( &numTasks, sizeof( numTasks ) ); + + STL_ITERATE( ti, m_taskSequences ) + { + //Save the task group's ID + id = ((*ti).first)->GetGUID(); + pIcarus->BufferWrite( &id, sizeof( id ) ); + + //Save the sequence's ID + id = ((*ti).second)->GetID(); + pIcarus->BufferWrite( &id, sizeof( id ) ); + } + + int curGroupID = ( m_curGroup == NULL ) ? -1 : m_curGroup->GetGUID(); + + // Right the group ID. + pIcarus->BufferWrite( &curGroupID, sizeof( curGroupID ) ); + + //Output the number of commands + pIcarus->BufferWrite( &m_numCommands, sizeof( m_numCommands ) ); + + //Output the ID of the current sequence + id = ( m_curSequence != NULL ) ? m_curSequence->GetID() : -1; + pIcarus->BufferWrite( &id, sizeof( id ) ); + + return true; +} + +/* +------------------------- +Load +------------------------- +*/ + +int CSequencer::Load( CIcarus* icarus, IGameInterface* game ) +{ + // Data expected/loaded here. + // Owner Sequence. + // Number of Sequences. + // Sequences (data). + // Taskmanager. + // Number of Task Sequences. + // Task Sequences (data): + // -Task group ID. + // -Sequence ID. + // Group ID. + // Number of Commands. + // ID of current Sequence. + + CIcarus *pIcarus = (CIcarus *)IIcarusInterface::GetIcarus(); + + //Get the owner of this sequencer + pIcarus->BufferRead( &m_ownerID, sizeof( m_ownerID ) ); + + //Link the entity back to the sequencer + game->LinkGame( m_ownerID, m_id ); + + CTaskGroup *taskGroup; + CSequence *seq; + int numSequences, seqID, taskID, numTasks; + + //Get the number of sequences to read + pIcarus->BufferRead( &numSequences, sizeof( numSequences ) ); + + //Read in all the sequences + for ( int i = 0; i < numSequences; i++ ) + { + pIcarus->BufferRead( &seqID, sizeof( seqID ) ); + + seq = (CSequence*)icarus->GetSequence( seqID ); + + assert( seq ); + + STL_INSERT( m_sequences, seq ); + //m_sequenceMap[ seqID ] = seq; + } + + //Setup the task manager + m_taskManager->Init( this ); + + //Load the task manager + m_taskManager->Load(icarus); + + //Get the number of tasks in the map + pIcarus->BufferRead( &numTasks, sizeof( numTasks ) ); + + //Read in, and reassociate the tasks to the sequences + for ( i = 0; i < numTasks; i++ ) + { + //Read in the task's ID + pIcarus->BufferRead( &taskID, sizeof( taskID ) ); + + //Read in the sequence's ID + pIcarus->BufferRead( &seqID, sizeof( seqID ) ); + + taskGroup = m_taskManager->GetTaskGroup( taskID , icarus); + + assert( taskGroup ); + + seq = icarus->GetSequence( seqID ); + + assert( seq ); + + //Associate the values + m_taskSequences[ taskGroup ] = seq; + } + + int curGroupID; + + //Get the current task group + pIcarus->BufferRead( &curGroupID, sizeof( curGroupID ) ); + + m_curGroup = ( curGroupID == -1 ) ? NULL : m_taskManager->GetTaskGroup( curGroupID , icarus); + + //Get the number of commands + pIcarus->BufferRead( &m_numCommands, sizeof( m_numCommands ) ); + + //Get the current sequence + pIcarus->BufferRead( &seqID, sizeof( seqID ) ); + + m_curSequence = ( seqID != -1 ) ? (CSequence*)icarus->GetSequence( seqID ) : NULL; + + return true; +} diff --git a/code/icarus/StdAfx.h b/code/icarus/StdAfx.h new file mode 100644 index 0000000..72cbf81 --- /dev/null +++ b/code/icarus/StdAfx.h @@ -0,0 +1,19 @@ +#ifndef __ICR_STDAFX__ +#define __ICR_STDAFX__ + +#pragma warning( disable : 4786 ) // identifier was truncated + +#pragma warning (push, 3) +#include +#include +#include +#include +#include +#pragma warning (pop) + +using namespace std; + +#define STL_ITERATE( a, b ) for ( a = b.begin(); a != b.end(); a++ ) +#define STL_INSERT( a, b ) a.insert( a.end(), b ); + +#endif \ No newline at end of file diff --git a/code/icarus/TaskManager.cpp b/code/icarus/TaskManager.cpp new file mode 100644 index 0000000..1be3566 --- /dev/null +++ b/code/icarus/TaskManager.cpp @@ -0,0 +1,2036 @@ +// Task Manager +// +// -- jweier + + +// this include must remain at the top of every Icarus CPP file +#include "stdafx.h" +#include "IcarusImplementation.h" + +#include "BlockStream.h" +#include "Sequence.h" +#include "TaskManager.h" +#include "Sequencer.h" + +#define ICARUS_VALIDATE(a) if ( a == false ) return TASK_FAILED; + +#define STL_ITERATE( a, b ) for ( a = b.begin(); a != b.end(); a++ ) +#define STL_INSERT( a, b ) a.insert( a.end(), b ); + +/* +================================================= + +CTask + +================================================= +*/ + +CTask::CTask( void ) +{ +} + +CTask::~CTask( void ) +{ +} + +CTask *CTask::Create( int GUID, CBlock *block ) +{ + CTask *task = new CTask; + + //TODO: Emit warning + if ( task == NULL ) + return NULL; + + task->SetTimeStamp( 0 ); + task->SetBlock( block ); + task->SetGUID( GUID ); + + return task; +} + +/* +------------------------- +Free +------------------------- +*/ + +void CTask::Free( void ) +{ + //NOTENOTE: The block is not consumed by the task, it is the sequencer's job to clean blocks up + delete this; +} + +/* +================================================= + +CTaskGroup + +================================================= +*/ + +CTaskGroup::CTaskGroup( void ) +{ + Init(); + + m_GUID = 0; + m_parent = NULL; +} + +CTaskGroup::~CTaskGroup( void ) +{ + m_completedTasks.clear(); +} + +/* +------------------------- +SetGUID +------------------------- +*/ + +void CTaskGroup::SetGUID( int GUID ) +{ + m_GUID = GUID; +} + +/* +------------------------- +Init +------------------------- +*/ + +void CTaskGroup::Init( void ) +{ + m_completedTasks.clear(); + + m_numCompleted = 0; + m_parent = NULL; +} + +/* +------------------------- +Add +------------------------- +*/ + +int CTaskGroup::Add( CTask *task ) +{ + m_completedTasks[ task->GetGUID() ] = false; + return TASK_OK; +} + +/* +------------------------- +MarkTaskComplete +------------------------- +*/ + +bool CTaskGroup::MarkTaskComplete( int id ) +{ + if ( (m_completedTasks.find( id )) != m_completedTasks.end() ) + { + m_completedTasks[ id ] = true; + m_numCompleted++; + + return true; + } + + return false; +} + +/* +================================================= + +CTaskManager + +================================================= +*/ + +CTaskManager::CTaskManager( void ) +{ + static int uniqueID = 0; + m_id = uniqueID++; +} + +CTaskManager::~CTaskManager( void ) +{ +} + +/* +------------------------- +Create +------------------------- +*/ + +CTaskManager *CTaskManager::Create( void ) +{ + return new CTaskManager; +} + +/* +------------------------- +Init +------------------------- +*/ + +int CTaskManager::Init( CSequencer *owner ) +{ + //TODO: Emit warning + if ( owner == NULL ) + return TASK_FAILED; + + m_tasks.clear(); + m_owner = owner; + m_ownerID = owner->GetOwnerID(); + m_curGroup = NULL; + m_GUID = 0; + m_resident = false; + + return TASK_OK; +} + +/* +------------------------- +Free +------------------------- +*/ +int CTaskManager::Free( void ) +{ + taskGroup_v::iterator gi; + tasks_l::iterator ti; + + assert(!m_resident); //don't free me, i'm currently running! + //Clear out all pending tasks + for ( ti = m_tasks.begin(); ti != m_tasks.end(); ti++ ) + { + (*ti)->Free(); + } + + m_tasks.clear(); + + //Clear out all taskGroups + for ( gi = m_taskGroups.begin(); gi != m_taskGroups.end(); gi++ ) + { + delete (*gi); + } + + m_taskGroups.clear(); + m_taskGroupNameMap.clear(); + m_taskGroupIDMap.clear(); + + return TASK_OK; +} + +/* +------------------------- +Flush +------------------------- +*/ + +int CTaskManager::Flush( void ) +{ + //FIXME: Rewrite + + return true; +} + +/* +------------------------- +AddTaskGroup +------------------------- +*/ + +CTaskGroup *CTaskManager::AddTaskGroup( const char *name, CIcarus* icarus ) +{ + CTaskGroup *group; + + //Collect any garbage + taskGroupName_m::iterator tgni; + tgni = m_taskGroupNameMap.find( name ); + + if ( tgni != m_taskGroupNameMap.end() ) + { + group = (*tgni).second; + + //Clear it and just move on + group->Init(); + + return group; + } + + //Allocate a new one + group = new CTaskGroup; + + //TODO: Emit warning + assert( group ); + if ( group == NULL ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Unable to allocate task group \"%s\"\n", name ); + return NULL; + } + + //Setup the internal information + group->SetGUID( m_GUID++ ); + + //Add it to the list and associate it for retrieval later + m_taskGroups.insert( m_taskGroups.end(), group ); + m_taskGroupNameMap[ name ] = group; + m_taskGroupIDMap[ group->GetGUID() ] = group; + + return group; +} + +/* +------------------------- +GetTaskGroup +------------------------- +*/ + +CTaskGroup *CTaskManager::GetTaskGroup( const char *name, CIcarus* icarus ) +{ + taskGroupName_m::iterator tgi; + + tgi = m_taskGroupNameMap.find( name ); + + if ( tgi == m_taskGroupNameMap.end() ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_WARNING, "Could not find task group \"%s\"\n", name ); + return NULL; + } + + return (*tgi).second; +} + +CTaskGroup *CTaskManager::GetTaskGroup( int id, CIcarus* icarus ) +{ + taskGroupID_m::iterator tgi; + + tgi = m_taskGroupIDMap.find( id ); + + if ( tgi == m_taskGroupIDMap.end() ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_WARNING, "Could not find task group \"%d\"\n", id ); + return NULL; + } + + return (*tgi).second; +} + +/* +------------------------- +Update +------------------------- +*/ + +int CTaskManager::Update( CIcarus* icarus ) +{ + if ( icarus->GetGame()->IsFrozen(m_ownerID) ) + { + return TASK_FAILED; + } + m_count = 0; //Needed for runaway init + m_resident = true; + + int returnVal = Go(icarus); + + m_resident = false; + + return returnVal; +} + +/* +------------------------- +Check +------------------------- +*/ + +inline bool CTaskManager::Check( int targetID, CBlock *block, int memberNum ) const +{ + if ( (block->GetMember( memberNum ))->GetID() == targetID ) + return true; + + return false; +} + +/* +------------------------- +GetFloat +------------------------- +*/ + +int CTaskManager::GetFloat( int entID, CBlock *block, int &memberNum, float &value, CIcarus* icarus ) +{ + char *name; + int type; + + //See if this is a get() command replacement + if ( Check( CIcarus::ID_GET, block, memberNum ) ) + { + //Update the member past the header id + memberNum++; + + //get( TYPE, NAME ) + type = (int) (*(float *) block->GetMemberData( memberNum++ )); + name = (char *) block->GetMemberData( memberNum++ ); + + //TODO: Emit warning + if ( type != CIcarus::TK_FLOAT ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Get() call tried to return a non-FLOAT parameter!\n" ); + return false; + } + + return icarus->GetGame()->GetFloat( entID, name, &value ); + } + + //Look for a random() inline call + if ( Check( CIcarus::ID_RANDOM, block, memberNum ) ) + { + float min, max; + + memberNum++; + + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + value = icarus->GetGame()->Random( min, max ); + + return true; + } + + //Look for a tag() inline call + if ( Check( CIcarus::ID_TAG, block, memberNum ) ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_WARNING, "Invalid use of \"tag\" inline. Not a valid replacement for type FLOAT\n" ); + return false; + } + + CBlockMember *bm = block->GetMember( memberNum ); + + if ( bm->GetID() == CIcarus::TK_INT ) + { + value = (float) (*(int *) block->GetMemberData( memberNum++ )); + } + else if ( bm->GetID() == CIcarus::TK_FLOAT ) + { + value = *(float *) block->GetMemberData( memberNum++ ); + } + else + { + assert(0); + icarus->GetGame()->DebugPrint(IGameInterface::WL_WARNING, "Unexpected value; expected type FLOAT\n" ); + return false; + } + + return true; +} + +/* +------------------------- +GetVector +------------------------- +*/ + +int CTaskManager::GetVector( int entID, CBlock *block, int &memberNum, vec3_t &value, CIcarus* icarus ) +{ + char *name; + int type, i; + + //See if this is a get() command replacement + if ( Check( CIcarus::ID_GET, block, memberNum ) ) + { + //Update the member past the header id + memberNum++; + + //get( TYPE, NAME ) + type = (int) (*(float *) block->GetMemberData( memberNum++ )); + name = (char *) block->GetMemberData( memberNum++ ); + + //TODO: Emit warning + if ( type != CIcarus::TK_VECTOR ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Get() call tried to return a non-VECTOR parameter!\n" ); + } + + return icarus->GetGame()->GetVector( entID, name, value ); + } + + //Look for a random() inline call + if ( Check( CIcarus::ID_RANDOM, block, memberNum ) ) + { + float min, max; + + memberNum++; + + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + for ( i = 0; i < 3; i++ ) + { + value[i] = (float) icarus->GetGame()->Random( min, max ); //FIXME: Just truncating it for now.. should be fine though + } + + return true; + } + + //Look for a tag() inline call + if ( Check( CIcarus::ID_TAG, block, memberNum ) ) + { + char *tagName; + float tagLookup; + + memberNum++; + ICARUS_VALIDATE ( Get( entID, block, memberNum, &tagName, icarus ) ); + ICARUS_VALIDATE ( GetFloat( entID, block, memberNum, tagLookup, icarus ) ); + + if ( icarus->GetGame()->GetTag( entID, tagName, (int) tagLookup, value ) == false) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Unable to find tag \"%s\"!\n", tagName ); + assert(0&&"Unable to find tag"); + return TASK_FAILED; + } + + return true; + } + + //Check for a real vector here + type = (int) (*(float *) block->GetMemberData( memberNum )); + + if ( type != CIcarus::TK_VECTOR ) + { +// icarus->GetGame()->DPrintf( WL_WARNING, "Unexpected value; expected type VECTOR\n" ); + return false; + } + + memberNum++; + + for ( i = 0; i < 3; i++ ) + { + if ( GetFloat( entID, block, memberNum, value[i], icarus ) == false ) + return false; + } + + return true; +} + +/* +------------------------- +Get +------------------------- +*/ + +int CTaskManager::GetID() +{ + return m_id; +} + +int CTaskManager::Get( int entID, CBlock *block, int &memberNum, char **value, CIcarus* icarus ) +{ + static char tempBuffer[128]; //FIXME: EEEK! + vec3_t vector; + char *name, *tagName; + float tagLookup; + int type; + + //Look for a get() inline call + if ( Check( CIcarus::ID_GET, block, memberNum ) ) + { + //Update the member past the header id + memberNum++; + + //get( TYPE, NAME ) + type = (int) (*(float *) block->GetMemberData( memberNum++ )); + name = (char *) block->GetMemberData( memberNum++ ); + + //Format the return properly + //FIXME: This is probably doing double formatting in certain cases... + //FIXME: STRING MANAGEMENT NEEDS TO BE IMPLEMENTED, MY CURRENT SOLUTION IS NOT ACCEPTABLE!! + switch ( type ) + { + case CIcarus::TK_STRING: + if ( icarus->GetGame()->GetString( entID, name, value ) == false ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Get() parameter \"%s\" could not be found!\n", name ); + return false; + } + + return true; + break; + + case CIcarus::TK_FLOAT: + { + float temp; + + if ( icarus->GetGame()->GetFloat( entID, name, &temp ) == false ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Get() parameter \"%s\" could not be found!\n", name ); + return false; + } + + sprintf( (char *) tempBuffer, "%f", temp ); + *value = (char *) tempBuffer; + } + + return true; + break; + + case CIcarus::TK_VECTOR: + { + vec3_t vval; + + if ( icarus->GetGame()->GetVector( entID, name, vval ) == false ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Get() parameter \"%s\" could not be found!\n", name ); + return false; + } + + sprintf( (char *) tempBuffer, "%f %f %f", vval[0], vval[1], vval[2] ); + *value = (char *) tempBuffer; + } + + return true; + break; + + default: + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Get() call tried to return an unknown type!\n" ); + return false; + break; + } + } + + //Look for a random() inline call + if ( Check( CIcarus::ID_RANDOM, block, memberNum ) ) + { + float min, max, ret; + + memberNum++; + + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + ret = icarus->GetGame()->Random( min, max ); + + sprintf( (char *) tempBuffer, "%f", ret ); + *value = (char *) tempBuffer; + + return true; + } + + //Look for a tag() inline call + if ( Check( CIcarus::ID_TAG, block, memberNum ) ) + { + memberNum++; + ICARUS_VALIDATE ( Get( entID, block, memberNum, &tagName, icarus ) ); + ICARUS_VALIDATE ( GetFloat( entID, block, memberNum, tagLookup, icarus ) ); + + if (icarus->GetGame()->GetTag( entID, tagName, (int) tagLookup, vector ) == false) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Unable to find tag \"%s\"!\n", tagName ); + assert(0 && "Unable to find tag"); + return false; + } + + sprintf( (char *) tempBuffer, "%f %f %f", vector[0], vector[1], vector[2] ); + *value = (char *) tempBuffer; + + return true; + } + + //Get an actual piece of data + + CBlockMember *bm = block->GetMember( memberNum ); + + if ( bm->GetID() == CIcarus::TK_INT ) + { + float fval = (float) (*(int *) block->GetMemberData( memberNum++ )); + sprintf( (char *) tempBuffer, "%f", fval ); + *value = (char *) tempBuffer; + + return true; + } + else if ( bm->GetID() == CIcarus::TK_FLOAT ) + { + float fval = *(float *) block->GetMemberData( memberNum++ ); + sprintf( (char *) tempBuffer, "%f", fval ); + *value = (char *) tempBuffer; + + return true; + } + else if ( bm->GetID() == CIcarus::TK_VECTOR ) + { + vec3_t vval; + + memberNum++; + + for ( int i = 0; i < 3; i++ ) + { + if ( GetFloat( entID, block, memberNum, vval[i], icarus ) == false ) + return false; + + sprintf( (char *) tempBuffer, "%f %f %f", vval[0], vval[1], vval[2] ); + *value = (char *) tempBuffer; + } + + return true; + } + else if ( ( bm->GetID() == CIcarus::TK_STRING ) || ( bm->GetID() == CIcarus::TK_IDENTIFIER ) ) + { + *value = (char *) block->GetMemberData( memberNum++ ); + + return true; + } + + //TODO: Emit warning + assert( 0 ); + icarus->GetGame()->DebugPrint(IGameInterface::WL_WARNING, "Unexpected value; expected type STRING\n" ); + + return false; +} + +/* +------------------------- +Go +------------------------- +*/ + +int CTaskManager::Go( CIcarus* icarus ) +{ + CTask *task = NULL; + bool completed = false; + + //Check for run away scripts + if ( m_count++ > RUNAWAY_LIMIT ) + { + assert(0); + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Runaway loop detected!\n" ); + return TASK_FAILED; + } + + //If there are tasks to complete, do so + if ( m_tasks.empty() == false ) + { + //Get the next task + task = PopTask( CSequence::POP_BACK ); + + assert( task ); + if ( task == NULL ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Invalid task found in Go()!\n" ); + return TASK_FAILED; + } + + //If this hasn't been stamped, do so + if ( task->GetTimeStamp() == 0 ) + task->SetTimeStamp( icarus->GetGame()->GetTime() ); + + //Switch and call the proper function + switch( task->GetID() ) + { + case CIcarus::ID_WAIT: + + Wait( task, completed, icarus ); + + //Push it to consider it again on the next frame if not complete + if ( completed == false ) + { + PushTask( task, CSequence::PUSH_BACK ); + return TASK_OK; + } + + Completed( task->GetGUID() ); + + break; + + case CIcarus::ID_WAITSIGNAL: + + WaitSignal( task, completed , icarus); + + //Push it to consider it again on the next frame if not complete + if ( completed == false ) + { + PushTask( task, CSequence::PUSH_BACK ); + return TASK_OK; + } + + Completed( task->GetGUID() ); + + break; + + case CIcarus::ID_PRINT: //print( STRING ) + Print( task, icarus ); + break; + + case CIcarus::ID_SOUND: //sound( name ) + Sound( task, icarus ); + break; + + case CIcarus::ID_MOVE: //move ( ORIGIN, ANGLES, DURATION ) + Move( task, icarus ); + break; + + case CIcarus::ID_ROTATE: //rotate( ANGLES, DURATION ) + Rotate( task, icarus ); + break; + + case CIcarus::ID_KILL: //kill( NAME ) + Kill( task, icarus ); + break; + + case CIcarus::ID_REMOVE: //remove( NAME ) + Remove( task, icarus ); + break; + + case CIcarus::ID_CAMERA: //camera( ? ) + Camera( task, icarus ); + break; + + case CIcarus::ID_SET: //set( NAME, ? ) + Set( task, icarus ); + break; + + case CIcarus::ID_USE: //use( NAME ) + Use( task, icarus ); + break; + + case CIcarus::ID_DECLARE://declare( TYPE, NAME ) + DeclareVariable( task, icarus ); + break; + + case CIcarus::ID_FREE: //free( NAME ) + FreeVariable( task, icarus ); + break; + + case CIcarus::ID_SIGNAL: //signal( NAME ) + Signal( task, icarus ); + break; + + case CIcarus::ID_PLAY: //play ( NAME ) + Play( task, icarus ); + break; + + default: + assert(0); + task->Free(); + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Found unknown task type!\n" ); + return TASK_FAILED; + break; + } + + //Pump the sequencer for another task + CallbackCommand( task, TASK_RETURN_COMPLETE , icarus); + + task->Free(); + } + + //FIXME: A command surge limiter could be implemented at this point to be sure a script doesn't + // execute too many commands in one cycle. This may, however, cause timing errors to surface. + return TASK_OK; +} + +/* +------------------------- +SetCommand +------------------------- +*/ + +int CTaskManager::SetCommand( CBlock *command, int type, CIcarus* icarus ) +{ + CTask *task = CTask::Create( m_GUID++, command ); + + //If this is part of a task group, add it in + if ( m_curGroup ) + { + m_curGroup->Add( task ); + } + + //TODO: Emit warning + assert( task ); + if ( task == NULL ) + { + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Unable to allocate new task!\n" ); + return TASK_FAILED; + } + + PushTask( task, type ); + + return TASK_OK; +} + +/* +------------------------- +MarkTask +------------------------- +*/ + +int CTaskManager::MarkTask( int id, int operation, CIcarus* icarus ) +{ + CTaskGroup *group = GetTaskGroup( id, icarus ); + + assert( group ); + + if ( group == NULL ) + return TASK_FAILED; + + if ( operation == TASK_START ) + { + //Reset all the completion information + group->Init(); + + group->SetParent( m_curGroup ); + m_curGroup = group; + } + else if ( operation == TASK_END ) + { + assert( m_curGroup ); + if ( m_curGroup == NULL ) + return TASK_FAILED; + + m_curGroup = m_curGroup->GetParent(); + } + +#ifdef _DEBUG + else + { + assert(0); + } +#endif + + return TASK_OK; +} + +/* +------------------------- +Completed +------------------------- +*/ + +int CTaskManager::Completed( int id ) +{ + taskGroup_v::iterator tgi; + + //Mark the task as completed + for ( tgi = m_taskGroups.begin(); tgi != m_taskGroups.end(); tgi++ ) + { + //If this returns true, then the task was marked properly + if ( (*tgi)->MarkTaskComplete( id ) ) + break; + } + + return TASK_OK; +} + +/* +------------------------- +CallbackCommand +------------------------- +*/ + +int CTaskManager::CallbackCommand( CTask *task, int returnCode, CIcarus* icarus ) +{ + if ( m_owner->Callback( this, task->GetBlock(), returnCode, icarus ) == CSequencer::SEQ_OK, icarus ) + return Go(icarus); + + assert(0); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Command callback failure!\n" ); + return TASK_FAILED; +} + +/* +------------------------- +RecallTask +------------------------- +*/ + +CBlock *CTaskManager::RecallTask( void ) +{ + CTask *task; + + task = PopTask( CSequence::POP_BACK ); + + if ( task ) + { + // fixed 2/12/2 to free the task that has been popped (called from sequencer Recall) + CBlock* retBlock = task->GetBlock(); + task->Free(); + + return retBlock; + // return task->GetBlock(); + } + + return NULL; +} + +/* +------------------------- +PushTask +------------------------- +*/ + +int CTaskManager::PushTask( CTask *task, int flag ) +{ + assert( (flag == CSequence::PUSH_FRONT) || (flag == CSequence::PUSH_BACK) ); + + switch ( flag ) + { + case CSequence::PUSH_FRONT: + m_tasks.insert(m_tasks.begin(), task); + + return TASK_OK; + break; + + case CSequence::PUSH_BACK: + m_tasks.insert(m_tasks.end(), task); + + return TASK_OK; + break; + } + + //Invalid flag + return CSequencer::SEQ_FAILED; +} + +/* +------------------------- +PopTask +------------------------- +*/ + +CTask *CTaskManager::PopTask( int flag ) +{ + CTask *task; + + assert( (flag == CSequence::POP_FRONT) || (flag == CSequence::POP_BACK) ); + + if ( m_tasks.empty() ) + return NULL; + + switch ( flag ) + { + case CSequence::POP_FRONT: + task = m_tasks.front(); + m_tasks.pop_front(); + + return task; + break; + + case CSequence::POP_BACK: + task = m_tasks.back(); + m_tasks.pop_back(); + + return task; + break; + } + + //Invalid flag + return NULL; +} + +/* +------------------------- +GetCurrentTask +------------------------- +*/ + +CBlock *CTaskManager::GetCurrentTask( void ) +{ + CTask *task = PopTask( CSequence::POP_BACK ); + + if ( task == NULL ) + return NULL; +// fixed 2/12/2 to free the task that has been popped (called from sequencer Interrupt) + CBlock* retBlock = task->GetBlock(); + task->Free(); + + return retBlock; +// return task->GetBlock(); +} + +/* +================================================= + + Task Functions + +================================================= +*/ + +int CTaskManager::Wait( CTask *task, bool &completed , CIcarus* icarus ) +{ + CBlockMember *bm; + CBlock *block = task->GetBlock(); + char *sVal; + float dwtime; + int memberNum = 0; + + completed = false; + + bm = block->GetMember( 0 ); + + //Check if this is a task completion wait + if ( bm->GetID() == CIcarus::TK_STRING ) + { + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + + if ( task->GetTimeStamp() == icarus->GetGame()->GetTime() ) + { + //Print out the debug info + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d wait(\"%s\"); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + } + + CTaskGroup *group = GetTaskGroup( sVal , icarus); + + if ( group == NULL ) + { + //TODO: Emit warning + completed = false; + return TASK_FAILED; + } + + completed = group->Complete(); + } + else //Otherwise it's a time completion wait + { + if ( Check( CIcarus::ID_RANDOM, block, memberNum ) ) + {//get it random only the first time + float min, max; + + dwtime = *(float *) block->GetMemberData( memberNum++ ); + if ( dwtime == icarus->GetGame()->MaxFloat() ) + {//we have not evaluated this random yet + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + dwtime = icarus->GetGame()->Random( min, max ); + + //store the result in the first member + bm->SetData( &dwtime, sizeof( dwtime ) , icarus); + } + } + else + { + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, dwtime, icarus ) ); + } + + if ( task->GetTimeStamp() == icarus->GetGame()->GetTime() ) + { + //Print out the debug info + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d wait( %d ); [%d]", m_ownerID, (int) dwtime, task->GetTimeStamp() ); + } + + if ( (task->GetTimeStamp() + dwtime) < (icarus->GetGame()->GetTime()) ) + { + completed = true; + memberNum = 0; + if ( Check( CIcarus::ID_RANDOM, block, memberNum ) ) + {//set the data back to 0 so it will be re-randomized next time + dwtime = icarus->GetGame()->MaxFloat(); + bm->SetData( &dwtime, sizeof( dwtime ), icarus ); + } + } + } + + return TASK_OK; +} + +/* +------------------------- +WaitSignal +------------------------- +*/ + +int CTaskManager::WaitSignal( CTask *task, bool &completed , CIcarus* icarus ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + completed = false; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal , icarus) ); + + if ( task->GetTimeStamp() == icarus->GetGame()->GetTime() ) + { + //Print out the debug info + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d waitsignal(\"%s\"); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + } + + if ( icarus->CheckSignal( sVal ) ) + { + completed = true; + icarus->ClearSignal( sVal ); + } + + return TASK_OK; +} + +/* +------------------------- +Print +------------------------- +*/ + +int CTaskManager::Print( CTask *task , CIcarus* icarus) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d print(\"%s\"); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + + icarus->GetGame()->CenterPrint( sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Sound +------------------------- +*/ + +int CTaskManager::Sound( CTask *task, CIcarus* icarus ) +{ + CBlock *block = task->GetBlock(); + char *sVal, *sVal2; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal2, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d sound(\"%s\", \"%s\"); [%d]", m_ownerID, sVal, sVal2, task->GetTimeStamp() ); + + //Only instantly complete if the user has requested it + if( icarus->GetGame()->PlaySound( task->GetGUID(), m_ownerID, sVal2, sVal ) ) + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Rotate +------------------------- +*/ + +int CTaskManager::Rotate( CTask *task , CIcarus* icarus) +{ + vec3_t vector; + CBlock *block = task->GetBlock(); + char *tagName; + float tagLookup, duration; + int memberNum = 0; + + //Check for a tag reference + if ( Check( CIcarus::ID_TAG, block, memberNum ) ) + { + memberNum++; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &tagName, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, tagLookup, icarus ) ); + + if ( icarus->GetGame()->GetTag( m_ownerID, tagName, (int) tagLookup, vector ) == false ) + { + //TODO: Emit warning + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Unable to find tag \"%s\"!\n", tagName ); + assert(0); + return TASK_FAILED; + } + } + else + { + //Get a normal vector + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector, icarus ) ); + } + + //Find the duration + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, duration, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d rotate( <%f,%f,%f>, %d); [%d]", m_ownerID, vector[0], vector[1], vector[2], (int) duration, task->GetTimeStamp() ); + icarus->GetGame()->Lerp2Angles( task->GetGUID(), m_ownerID, vector, duration ); + + return TASK_OK; +} + +/* +------------------------- +Remove +------------------------- +*/ + +int CTaskManager::Remove( CTask *task, CIcarus* icarus ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d remove(\"%s\"); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + icarus->GetGame()->Remove( m_ownerID, sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Camera +------------------------- +*/ + +int CTaskManager::Camera( CTask *task , CIcarus* icarus) +{ + CBlock *block = task->GetBlock(); + vec3_t vector, vector2; + float type, fVal, fVal2, fVal3; + char *sVal; + int memberNum = 0; + + //Get the camera function type + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, type, icarus ) ); + + switch ( (int) type ) + { + case CIcarus::TYPE_PAN: + + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector, icarus ) ); + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector2, icarus ) ); + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( PAN, <%f %f %f>, <%f %f %f>, %f); [%d]", m_ownerID, vector[0], vector[1], vector[2], vector2[0], vector2[1], vector2[2], fVal, task->GetTimeStamp() ); + icarus->GetGame()->CameraPan( vector, vector2, fVal ); + break; + + case CIcarus::TYPE_ZOOM: + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( ZOOM, %f, %f); [%d]", m_ownerID, fVal, fVal2, task->GetTimeStamp() ); + icarus->GetGame()->CameraZoom( fVal, fVal2 ); + break; + + case CIcarus::TYPE_MOVE: + + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( MOVE, <%f %f %f>, %f); [%d]", m_ownerID, vector[0], vector[1], vector[2], fVal, task->GetTimeStamp() ); + icarus->GetGame()->CameraMove( vector, fVal ); + break; + + case CIcarus::TYPE_ROLL: + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( ROLL, %f, %f); [%d]", m_ownerID, fVal, fVal2, task->GetTimeStamp() ); + icarus->GetGame()->CameraRoll( fVal, fVal2 ); + + break; + + case CIcarus::TYPE_FOLLOW: + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( FOLLOW, \"%s\", %f, %f); [%d]", m_ownerID, sVal, fVal, fVal2, task->GetTimeStamp() ); + icarus->GetGame()->CameraFollow( (const char *) sVal, fVal, fVal2 ); + + break; + + case CIcarus::TYPE_TRACK: + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( TRACK, \"%s\", %f, %f); [%d]", m_ownerID, sVal, fVal, fVal2, task->GetTimeStamp() ); + icarus->GetGame()->CameraTrack( (const char *) sVal, fVal, fVal2 ); + break; + + case CIcarus::TYPE_DISTANCE: + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( DISTANCE, %f, %f); [%d]", m_ownerID, fVal, fVal2, task->GetTimeStamp() ); + icarus->GetGame()->CameraDistance( fVal, fVal2 ); + break; + + case CIcarus::TYPE_FADE: + + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal, icarus ) ); + + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector2, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2, icarus ) ); + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal3, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( FADE, <%f %f %f>, %f, <%f %f %f>, %f, %f); [%d]", m_ownerID, vector[0], vector[1], vector[2], fVal, vector2[0], vector2[1], vector2[2], fVal2, fVal3, task->GetTimeStamp() ); + icarus->GetGame()->CameraFade( vector[0], vector[1], vector[2], fVal, vector2[0], vector2[1], vector2[2], fVal2, fVal3 ); + break; + + case CIcarus::TYPE_PATH: + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( PATH, \"%s\"); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + icarus->GetGame()->CameraPath( sVal ); + break; + + case CIcarus::TYPE_ENABLE: + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( ENABLE ); [%d]", m_ownerID, task->GetTimeStamp() ); + icarus->GetGame()->CameraEnable(); + break; + + case CIcarus::TYPE_DISABLE: + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( DISABLE ); [%d]", m_ownerID, task->GetTimeStamp() ); + icarus->GetGame()->CameraDisable(); + break; + + case CIcarus::TYPE_SHAKE: + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal, icarus ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d camera( SHAKE, %f, %f ); [%d]", m_ownerID, fVal, fVal2, task->GetTimeStamp() ); + icarus->GetGame()->CameraShake( fVal, (int) fVal2 ); + break; + } + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Move +------------------------- +*/ + +int CTaskManager::Move( CTask *task, CIcarus* icarus ) +{ + vec3_t vector, vector2; + CBlock *block = task->GetBlock(); + float duration; + int memberNum = 0; + + //Get the goal position + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector, icarus ) ); + + //Check for possible angles field + if ( GetVector( m_ownerID, block, memberNum, vector2, icarus ) == false ) + { + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, duration, icarus ) ); + + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d move( <%f %f %f>, %f ); [%d]", m_ownerID, vector[0], vector[1], vector[2], duration, task->GetTimeStamp() ); + icarus->GetGame()->Lerp2Pos( task->GetGUID(), m_ownerID, vector, NULL, duration ); + + return TASK_OK; + } + + //Get the duration and make the call + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, duration, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d move( <%f %f %f>, <%f %f %f>, %f ); [%d]", m_ownerID, vector[0], vector[1], vector[2], vector2[0], vector2[1], vector2[2], duration, task->GetTimeStamp() ); + icarus->GetGame()->Lerp2Pos( task->GetGUID(), m_ownerID, vector, vector2, duration ); + + return TASK_OK; +} + +/* +------------------------- +Kill +------------------------- +*/ + +int CTaskManager::Kill( CTask *task, CIcarus* icarus ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d kill( \"%s\" ); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + icarus->GetGame()->Kill( m_ownerID, sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Set +------------------------- +*/ + +int CTaskManager::Set( CTask *task, CIcarus* icarus ) +{ + CBlock *block = task->GetBlock(); + char *sVal, *sVal2; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal2, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d set( \"%s\", \"%s\" ); [%d]", m_ownerID, sVal, sVal2, task->GetTimeStamp() ); + icarus->GetGame()->Set( task->GetGUID(), m_ownerID, sVal, sVal2 ); + + return TASK_OK; +} + +/* +------------------------- +Use +------------------------- +*/ + +int CTaskManager::Use( CTask *task , CIcarus* icarus) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d use( \"%s\" ); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + icarus->GetGame()->Use( m_ownerID, sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +DeclareVariable +------------------------- +*/ + +int CTaskManager::DeclareVariable( CTask *task , CIcarus* icarus) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + float fVal; + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal, icarus ) ); + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d declare( %d, \"%s\" ); [%d]", m_ownerID, (int) fVal, sVal, task->GetTimeStamp() ); + icarus->GetGame()->DeclareVariable( (int) fVal, sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; + +} + +/* +------------------------- +FreeVariable +------------------------- +*/ + +int CTaskManager::FreeVariable( CTask *task, CIcarus* icarus ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d free( \"%s\" ); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + icarus->GetGame()->FreeVariable( sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; + +} + +/* +------------------------- +Signal +------------------------- +*/ + +int CTaskManager::Signal( CTask *task, CIcarus* icarus ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d signal( \"%s\" ); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + icarus->Signal( (const char *) sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Play +------------------------- +*/ + +int CTaskManager::Play( CTask *task, CIcarus* icarus ) +{ + CBlock *block = task->GetBlock(); + char *sVal, *sVal2; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal, icarus ) ); + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal2, icarus ) ); + + icarus->GetGame()->DebugPrint(IGameInterface::WL_DEBUG, "%4d play( \"%s\", \"%s\" ); [%d]", m_ownerID, sVal, sVal2, task->GetTimeStamp() ); + icarus->GetGame()->Play( task->GetGUID(), m_ownerID, (const char *) sVal, (const char *) sVal2 ); + + return TASK_OK; +} + +/* +------------------------- +SaveCommand +------------------------- +*/ + +//FIXME: ARGH! This is duplicated from CSequence because I can't directly link it any other way... + +int CTaskManager::SaveCommand( CBlock *block ) +{ + CIcarus *pIcarus = (CIcarus *)IIcarusInterface::GetIcarus(); + + unsigned char flags; + int numMembers, bID, size; + CBlockMember *bm; + + //Save out the block ID + bID = block->GetBlockID(); + pIcarus->BufferWrite( &bID, sizeof( bID ) ); + + //Save out the block's flags + flags = block->GetFlags(); + pIcarus->BufferWrite( &flags, sizeof( flags ) ); + + //Save out the number of members to read + numMembers = block->GetNumMembers(); + pIcarus->BufferWrite( &numMembers, sizeof( numMembers ) ); + + for ( int i = 0; i < numMembers; i++ ) + { + bm = block->GetMember( i ); + + //Save the block id + bID = bm->GetID(); + pIcarus->BufferWrite( &bID, sizeof( bID ) ); + + //Save out the data size + size = bm->GetSize(); + pIcarus->BufferWrite( &size, sizeof( size ) ); + + //Save out the raw data + pIcarus->BufferWrite( bm->GetData(), size ); + } + + return true; +} + +/* +------------------------- +Save +------------------------- +*/ + +void CTaskManager::Save() +{ + CTaskGroup *taskGroup; + const char *name; + CBlock *block; + DWORD timeStamp; + bool completed; + int id, numCommands; + int numWritten; + + // Data saved here. + // Taskmanager GUID. + // Number of Tasks. + // Tasks: + // - GUID. + // - Timestamp. + // - Block/Command. + // Number of task groups. + // Task groups ID's. + // Task groups (data). + // - Parent. + // - Number of Commands. + // - Commands: + // + ID. + // + State of Completion. + // - Number of Completed Commands. + // Currently active group. + // Task group names: + // - String Size. + // - String. + // - ID. + + CIcarus *pIcarus = (CIcarus *)IIcarusInterface::GetIcarus(); + + //Save the taskmanager's GUID + pIcarus->BufferWrite( &m_GUID, sizeof( m_GUID ) ); + + //Save out the number of tasks that will follow + int iNumTasks = m_tasks.size(); + pIcarus->BufferWrite( &iNumTasks, sizeof( iNumTasks ) ); + + //Save out all the tasks + tasks_l::iterator ti; + + STL_ITERATE( ti, m_tasks ) + { + //Save the GUID + id = (*ti)->GetGUID(); + pIcarus->BufferWrite( &id, sizeof( id ) ); + + //Save the timeStamp (FIXME: Although, this is going to be worthless if time is not consistent...) + timeStamp = (*ti)->GetTimeStamp(); + pIcarus->BufferWrite( &timeStamp, sizeof( timeStamp ) ); + + //Save out the block + block = (*ti)->GetBlock(); + SaveCommand( block ); + } + + //Save out the number of task groups + int numTaskGroups = m_taskGroups.size(); + pIcarus->BufferWrite( &numTaskGroups, sizeof( numTaskGroups ) ); + + //Save out the IDs of all the task groups + numWritten = 0; + taskGroup_v::iterator tgi; + STL_ITERATE( tgi, m_taskGroups ) + { + id = (*tgi)->GetGUID(); + pIcarus->BufferWrite( &id, sizeof( id ) ); + numWritten++; + } + assert (numWritten == numTaskGroups); + + //Save out the task groups + numWritten = 0; + STL_ITERATE( tgi, m_taskGroups ) + { + //Save out the parent + id = ( (*tgi)->GetParent() == NULL ) ? -1 : ((*tgi)->GetParent())->GetGUID(); + pIcarus->BufferWrite( &id, sizeof( id ) ); + + //Save out the number of commands + numCommands = (*tgi)->m_completedTasks.size(); + pIcarus->BufferWrite( &numCommands, sizeof( numCommands ) ); + + //Save out the command map + CTaskGroup::taskCallback_m::iterator tci; + + STL_ITERATE( tci, (*tgi)->m_completedTasks ) + { + //Write out the ID + id = (*tci).first; + pIcarus->BufferWrite( &id, sizeof( id ) ); + + //Write out the state of completion + completed = (*tci).second; + pIcarus->BufferWrite( &completed, sizeof( completed ) ); + } + + //Save out the number of completed commands + id = (*tgi)->m_numCompleted; + pIcarus->BufferWrite( &id, sizeof( id ) ); + numWritten++; + } + assert (numWritten == numTaskGroups); + + //Only bother if we've got tasks present + if ( m_taskGroups.size() ) + { + //Save out the currently active group + int curGroupID = ( m_curGroup == NULL ) ? -1 : m_curGroup->GetGUID(); + pIcarus->BufferWrite( &curGroupID, sizeof( curGroupID ) ); + } + + //Save out the task group name maps + taskGroupName_m::iterator tmi; + numWritten = 0; + STL_ITERATE( tmi, m_taskGroupNameMap ) + { + name = ((*tmi).first).c_str(); + + //Make sure this is a valid string + assert( ( name != NULL ) && ( name[0] != NULL ) ); + + int length = strlen( name ) + 1; + + //Save out the string size + //icarus->GetGame()->WriteSaveData( 'TGNL', &length, sizeof ( length ) ); + pIcarus->BufferWrite( &length, sizeof( length ) ); + + //Write out the string + pIcarus->BufferWrite( (void *) name, length ); + + taskGroup = (*tmi).second; + + id = taskGroup->GetGUID(); + + //Write out the ID + pIcarus->BufferWrite( &id, sizeof( id ) ); + numWritten++; + } + assert (numWritten == numTaskGroups); +} + +/* +------------------------- +Load +------------------------- +*/ + +void CTaskManager::Load( CIcarus* icarus ) +{ + unsigned char flags; + CTaskGroup *taskGroup; + CBlock *block; + CTask *task; + DWORD timeStamp; + bool completed; + void *bData; + int id, numTasks, numMembers; + int bID, bSize; + + // Data expected/loaded here. + // Taskmanager GUID. + // Number of Tasks. + // Tasks: + // - GUID. + // - Timestamp. + // - Block/Command. + // Number of task groups. + // Task groups ID's. + // Task groups (data). + // - Parent. + // - Number of Commands. + // - Commands: + // + ID. + // + State of Completion. + // - Number of Completed Commands. + // Currently active group. + // Task group names: + // - String Size. + // - String. + // - ID. + + CIcarus *pIcarus = (CIcarus *)IIcarusInterface::GetIcarus(); + + //Get the GUID + pIcarus->BufferRead( &m_GUID, sizeof( m_GUID ) ); + + //Get the number of tasks to follow + pIcarus->BufferRead( &numTasks, sizeof( numTasks ) ); + + //Reload all the tasks + for ( int i = 0; i < numTasks; i++ ) + { + task = new CTask; + + assert( task ); + + //Get the GUID + pIcarus->BufferRead( &id, sizeof( id ) ); + task->SetGUID( id ); + + //Get the time stamp + pIcarus->BufferRead( &timeStamp, sizeof( timeStamp ) ); + task->SetTimeStamp( timeStamp ); + + // + // BLOCK LOADING + // + + //Get the block ID and create a new container + pIcarus->BufferRead( &id, sizeof( id ) ); + block = new CBlock; + + block->Create( id ); + + //Read the block's flags + pIcarus->BufferRead( &flags, sizeof( flags ) ); + block->SetFlags( flags ); + + //Get the number of block members + pIcarus->BufferRead( &numMembers, sizeof( numMembers ) ); + + for ( int j = 0; j < numMembers; j++ ) + { + //Get the member ID + pIcarus->BufferRead( &bID, sizeof( bID ) ); + + //Get the member size + pIcarus->BufferRead( &bSize, sizeof( bSize ) ); + + //Get the member's data + if ( ( bData = icarus->GetGame()->Malloc( bSize ) ) == NULL ) + { + assert( 0 ); + return; + } + + //Get the actual raw data + pIcarus->BufferRead( bData, bSize ); + + //Write out the correct type + switch ( bID ) + { + case CIcarus::TK_FLOAT: + block->Write( CIcarus::TK_FLOAT, *(float *) bData, icarus ); + break; + + case CIcarus::TK_IDENTIFIER: + block->Write( CIcarus::TK_IDENTIFIER, (char *) bData , icarus); + break; + + case CIcarus::TK_STRING: + block->Write( CIcarus::TK_STRING, (char *) bData , icarus); + break; + + case CIcarus::TK_VECTOR: + block->Write( CIcarus::TK_VECTOR, *(vec3_t *) bData, icarus ); + break; + + case CIcarus::ID_RANDOM: + block->Write( CIcarus::ID_RANDOM, *(float *) bData, icarus );//ID_RANDOM ); + break; + + case CIcarus::ID_TAG: + block->Write( CIcarus::ID_TAG, (float) CIcarus::ID_TAG , icarus); + break; + + case CIcarus::ID_GET: + block->Write( CIcarus::ID_GET, (float) CIcarus::ID_GET , icarus); + break; + + default: + icarus->GetGame()->DebugPrint(IGameInterface::WL_ERROR, "Invalid Block id %d\n", bID); + assert( 0 ); + break; + } + + //Get rid of the temp memory + icarus->GetGame()->Free( bData ); + } + + task->SetBlock( block ); + + STL_INSERT( m_tasks, task ); + } + + //Load the task groups + int numTaskGroups; + + //icarus->GetGame()->ReadSaveData( 'TG#G', &numTaskGroups, sizeof( numTaskGroups ) ); + pIcarus->BufferRead( &numTaskGroups, sizeof( numTaskGroups ) ); + + if ( numTaskGroups == 0 ) + return; + + int *taskIDs = new int[ numTaskGroups ]; + + //Get the task group IDs + for ( i = 0; i < numTaskGroups; i++ ) + { + //Creat a new task group + taskGroup = new CTaskGroup; + assert( taskGroup ); + + //Get this task group's ID + pIcarus->BufferRead( &taskIDs[i], sizeof( taskIDs[i] ) ); + taskGroup->m_GUID = taskIDs[i]; + + m_taskGroupIDMap[ taskIDs[i] ] = taskGroup; + + STL_INSERT( m_taskGroups, taskGroup ); + } + + //Recreate and load the task groups + for ( i = 0; i < numTaskGroups; i++ ) + { + taskGroup = GetTaskGroup( taskIDs[i], icarus ); + assert( taskGroup ); + + //Load the parent ID + pIcarus->BufferRead( &id, sizeof( id ) ); + + if ( id != -1 ) + taskGroup->m_parent = ( GetTaskGroup( id, icarus ) != NULL ) ? GetTaskGroup( id, icarus ) : NULL; + + //Get the number of commands in this group + pIcarus->BufferRead( &numMembers, sizeof( numMembers ) ); + + //Get each command and its completion state + for ( int j = 0; j < numMembers; j++ ) + { + //Get the ID + pIcarus->BufferRead( &id, sizeof( id ) ); + + //Write out the state of completion + pIcarus->BufferRead( &completed, sizeof( completed ) ); + + //Save it out + taskGroup->m_completedTasks[ id ] = completed; + } + + //Get the number of completed tasks + pIcarus->BufferRead( &taskGroup->m_numCompleted, sizeof( taskGroup->m_numCompleted ) ); + } + + //Reload the currently active group + int curGroupID; + + pIcarus->BufferRead( &curGroupID, sizeof( curGroupID ) ); + + //Reload the map entries + for ( i = 0; i < numTaskGroups; i++ ) + { + char name[1024]; + int length; + + //Get the size of the string + pIcarus->BufferRead( &length, sizeof( length ) ); + + //Get the string + pIcarus->BufferRead( &name, length ); + + //Get the id + pIcarus->BufferRead( &id, sizeof( id ) ); + + taskGroup = GetTaskGroup( id, icarus ); + assert( taskGroup ); + + m_taskGroupNameMap[ name ] = taskGroup; + m_taskGroupIDMap[ taskGroup->GetGUID() ] = taskGroup; + } + + m_curGroup = ( curGroupID == -1 ) ? NULL : m_taskGroupIDMap[curGroupID]; + + delete[] taskIDs; +} diff --git a/code/icarus/blockstream.h b/code/icarus/blockstream.h new file mode 100644 index 0000000..f604bd5 --- /dev/null +++ b/code/icarus/blockstream.h @@ -0,0 +1,213 @@ +// BlockStream.h + +#ifndef __INTERPRETED_BLOCK_STREAM__ +#define __INTERPRETED_BLOCK_STREAM__ + +#include + +typedef float vec3_t[3]; + + +// Templates + +// CBlockMember + +class CBlockMember +{ +public: + CBlockMember(); + +protected: + ~CBlockMember(); + +public: + void Free(IGameInterface* game); + + int WriteMember ( FILE * ); //Writes the member's data, in block format, to FILE * + int ReadMember( char **, long *, CIcarus* icarus ); //Reads the member's data, in block format, from FILE * + + void SetID( int id ) { m_id = id; } //Set the ID member variable + void SetSize( int size ) { m_size = size; } //Set the size member variable + + void GetInfo( int *, int *, void **); + + //SetData overloads + void SetData( const char * ,CIcarus* icarus); + void SetData( vec3_t , CIcarus* icarus); + void SetData( void *data, int size, CIcarus* icarus); + + int GetID( void ) const { return m_id; } //Get ID member variables + void *GetData( void ) const { return m_data; } //Get data member variable + int GetSize( void ) const { return m_size; } //Get size member variable + + // Overloaded new operator. + inline void *operator new( size_t size ) + { // Allocate the memory. + return IGameInterface::GetGame()->Malloc( size ); + } + + // Overloaded delete operator. + inline void operator delete( void *pRawData ) + { // Free the Memory. + IGameInterface::GetGame()->Free( pRawData ); + } + + CBlockMember *Duplicate( CIcarus* icarus ); + + template void WriteData(T &data, CIcarus* icarus) + { + IGameInterface* game = icarus->GetGame(); + if ( m_data ) + { + game->Free( m_data ); + } + + m_data = game->Malloc( sizeof(T) ); + *((T *) m_data) = data; + m_size = sizeof(T); + } + + template void WriteDataPointer(const T *data, int num, CIcarus* icarus) + { + IGameInterface* game =icarus->GetGame(); + if ( m_data ) + { + game->Free( m_data ); + } + + m_data = game->Malloc( num*sizeof(T) ); + memcpy( m_data, data, num*sizeof(T) ); + m_size = num*sizeof(T); + } + +protected: + + int m_id; //ID of the value contained in data + int m_size; //Size of the data member variable + void *m_data; //Data for this member + +}; + +//CBlock + +class CBlock +{ + typedef vector< CBlockMember * > blockMember_v; + +public: + + CBlock() + { + m_flags = 0; + m_id = 0; + } + ~CBlock() { assert(!GetNumMembers()); } + + int Init( void ); + + int Create( int ); + int Free(CIcarus* icarus); + + //Write Overloads + + int Write( int, vec3_t, CIcarus* icaru ); + int Write( int, float, CIcarus* icaru ); + int Write( int, const char *, CIcarus* icaru ); + int Write( int, int, CIcarus* icaru ); + int Write( CBlockMember *, CIcarus* icaru ); + + //Member push / pop functions + + int AddMember( CBlockMember * ); + CBlockMember *GetMember( int memberNum ); + + void *GetMemberData( int memberNum ); + + CBlock *Duplicate( CIcarus* icarus ); + + int GetBlockID( void ) const { return m_id; } //Get the ID for the block + int GetNumMembers( void ) const { return m_members.size();} //Get the number of member in the block's list + + void SetFlags( unsigned char flags ) { m_flags = flags; } + void SetFlag( unsigned char flag ) { m_flags |= flag; } + + int HasFlag( unsigned char flag ) const { return ( m_flags & flag ); } + unsigned char GetFlags( void ) const { return m_flags; } + + // Overloaded new operator. + inline void *operator new( size_t size ) + { // Allocate the memory. + return IGameInterface::GetGame()->Malloc( size ); + } + + // Overloaded delete operator. + inline void operator delete( void *pRawData ) + { // Validate data. + if ( pRawData == 0 ) + return; + + // Free the Memory. + IGameInterface::GetGame()->Free( pRawData ); + } + + +protected: + + blockMember_v m_members; //List of all CBlockMembers owned by this list + int m_id; //ID of the block + unsigned char m_flags; +}; + +// CBlockStream + +class CBlockStream +{ +public: + + CBlockStream() + { + m_stream = NULL; + m_streamPos = 0; + } + ~CBlockStream() {}; + + int Init( void ); + + int Create( char * ); + int Free( void ); + + // Stream I/O functions + + int BlockAvailable( void ); + + int WriteBlock( CBlock *, CIcarus* icarus ); //Write the block out + int ReadBlock( CBlock *, CIcarus* icarus ); //Read the block in + + int Open( char *, long ); //Open a stream for reading / writing + + // Overloaded new operator. + static void *operator new( size_t size ) + { // Allocate the memory. + return IGameInterface::GetGame()->Malloc( size ); + } + + // Overloaded delete operator. + static void operator delete( void *pRawData ) + { // Free the Memory. + IGameInterface::GetGame()->Free( pRawData ); + } + +protected: + long m_fileSize; //Size of the file + FILE *m_fileHandle; //Global file handle of current I/O source + char m_fileName[CIcarus::MAX_FILENAME_LENGTH]; //Name of the current file + + char *m_stream; //Stream of data to be parsed + long m_streamPos; + + static char* s_IBI_EXT; + static char* s_IBI_HEADER_ID; + static const float s_IBI_VERSION; +}; + +#endif //__INTERPRETED_BLOCK_STREAM__ \ No newline at end of file diff --git a/code/icarus/sequence.h b/code/icarus/sequence.h new file mode 100644 index 0000000..b193ad7 --- /dev/null +++ b/code/icarus/sequence.h @@ -0,0 +1,115 @@ +// Sequence Header File + +#ifndef __SEQUENCE__ +#define __SEQUENCE__ + +class CSequence +{ + + typedef list < CSequence * > sequence_l; +// typedef map < int, CSequence *> sequenceID_m; + typedef list < CBlock * > block_l; + +public: + + //Constructors / Destructors + CSequence( void ); + ~CSequence( void ); + + //Creation and deletion + static CSequence *Create( void ); + void Delete( CIcarus* icarus ); + + //Organization functions + void AddChild( CSequence * ); + void RemoveChild( CSequence * ); + + void SetParent( CSequence * ); + CSequence *GetParent( void ) const { return m_parent; } + + //Block manipulation + CBlock *PopCommand( int ); + int PushCommand( CBlock *, int ); + + //Flag utilties + void SetFlag( int ); + void RemoveFlag( int, bool = false ); + int HasFlag( int ); + int GetFlags( void ) const { return m_flags; } + void SetFlags( int flags ) { m_flags = flags; } + + //Various encapsulation utilities + int GetIterations( void ) const { return m_iterations; } + void SetIterations( int it ) { m_iterations = it; } + + int GetID( void ) const { return m_id; } + void SetID( int id ) { m_id = id; } + + CSequence *GetReturn( void ) const { return m_return; } + + void SetReturn ( CSequence *sequence ); + + int GetNumCommands( void ) const { return m_numCommands; } + int GetNumChildren( void ) const { return m_children.size(); } + + CSequence *GetChildByID( int id ); + CSequence *GetChildByIndex( int iIndex ); + bool HasChild( CSequence *sequence ); + + int Save(); + int Load( CIcarus* icarus ); + + // Overloaded new operator. + inline void *operator new( size_t size ) + { // Allocate the memory. + return IGameInterface::GetGame()->Malloc( size ); + } + + // Overloaded delete operator. + inline void operator delete( void *pRawData ) + { // Free the Memory. + IGameInterface::GetGame()->Free( pRawData ); + } + + enum + { + SQ_COMMON = 0x00000000, //Common one-pass sequence + SQ_LOOP = 0x00000001, //Looping sequence + SQ_RETAIN = 0x00000002, //Inside a looping sequence list, retain the information + SQ_AFFECT = 0x00000004, //Affect sequence + SQ_RUN = 0x00000008, //A run block + SQ_PENDING = 0x00000010, //Pending use, don't free when flushing the sequences + SQ_CONDITIONAL = 0x00000020, //Conditional statement + SQ_TASK = 0x00000040, //Task block + }; + + enum + { + POP_FRONT, + POP_BACK, + PUSH_FRONT, + PUSH_BACK + }; + +protected: + + int SaveCommand( CBlock *block ); + int LoadCommand( CBlock *block, CIcarus *icarus ); + + //Organization information + sequence_l m_children; + //sequenceID_m m_childrenMap; + + //int m_numChildren; + CSequence *m_parent; + CSequence *m_return; + + //Data information + block_l m_commands; + int m_flags; + int m_iterations; + int m_id; + int m_numCommands; +}; + +#endif //__SEQUENCE__ \ No newline at end of file diff --git a/code/icarus/sequencer.h b/code/icarus/sequencer.h new file mode 100644 index 0000000..808a25b --- /dev/null +++ b/code/icarus/sequencer.h @@ -0,0 +1,163 @@ +// Sequencer Header File + +#ifndef __SEQUENCER__ +#define __SEQUENCER__ + +//Defines + + +//const int MAX_ERROR_LENGTH = 256; + +//Typedefs + +typedef struct bstream_s +{ + CBlockStream *stream; + bstream_s *last; +} bstream_t; + +// Sequencer + +/* +================================================================================================== + + CSequencer + +================================================================================================== +*/ + +class CSequencer +{ + //typedef map < int, CSequence * > sequenceID_m; + typedef list < CSequence * > sequence_l; + typedef map < CTaskGroup *, CSequence * > taskSequence_m; + +public: + enum + { + BF_ELSE = 0x00000001, //Block has an else id //FIXME: This was a sloppy fix for a problem that arose from conditionals + }; + + enum + { + SEQ_OK, //Command was successfully added + SEQ_FAILED, //An error occured while trying to insert the command + }; + + CSequencer(); + +protected: + ~CSequencer(); + +public: + int GetID() { return m_id;}; + + int Init( int ownerID, CTaskManager *taskManager); + static CSequencer *Create ( void ); + void Free( CIcarus* icarus ); + + int Run( char *buffer, long size, CIcarus* icarus); + int Callback( CTaskManager *taskManager, CBlock *block, int returnCode, CIcarus* icarus ); + + void SetOwnerID( int owner ) { m_ownerID = owner;} + + int GetOwnerID( void ) const { return m_ownerID; } + + CTaskManager *GetTaskManager( void ) const { return m_taskManager; } + + void SetTaskManager( CTaskManager *tm) { if ( tm ) m_taskManager = tm; } + + int Save(); + int Load( CIcarus* icarus, IGameInterface* game ); + + // Overloaded new operator. + inline void *operator new( size_t size ) + { // Allocate the memory. + return IGameInterface::GetGame()->Malloc( size ); + } + + // Overloaded delete operator. + inline void operator delete( void *pRawData ) + { // Free the Memory. + IGameInterface::GetGame()->Free( pRawData ); + } + +// moved to public on 2/12/2 to allow calling during shutdown + int Recall( CIcarus* icarus ); +protected: + + int EvaluateConditional( CBlock *block, CIcarus* icarus ); + + int Route( CSequence *sequence, bstream_t *bstream , CIcarus* icarus); + int Flush( CSequence *owner, CIcarus* icarus ); + void Interrupt( void ); + + bstream_t *AddStream( void ); + void DeleteStream( bstream_t *bstream ); + + int AddAffect( bstream_t *bstream, int retain, int *id, CIcarus* icarus ); + + CSequence *AddSequence( CIcarus* icarus ); + CSequence *AddSequence( CSequence *parent, CSequence *returnSeq, int flags, CIcarus* icarus ); + + CSequence *GetSequence( int id ); + + //NOTENOTE: This only removes references to the sequence, IT DOES NOT FREE THE ALLOCATED MEMORY! + int RemoveSequence( CSequence *sequence, CIcarus* icarus); + int DestroySequence( CSequence *sequence, CIcarus* icarus); + + int PushCommand( CBlock *command, int flag ); + CBlock *PopCommand( int flag ); + + inline CSequence *ReturnSequence( CSequence *sequence ); + + void CheckRun( CBlock ** , CIcarus* icarus); + void CheckLoop( CBlock ** , CIcarus* icarus); + void CheckAffect( CBlock ** , CIcarus* icarus); + void CheckIf( CBlock ** , CIcarus* icarus); + void CheckDo( CBlock ** , CIcarus* icarus); + void CheckFlush( CBlock ** , CIcarus* icarus); + + void Prep( CBlock ** , CIcarus* icarus); + + int Prime( CTaskManager *taskManager, CBlock *command, CIcarus* icarus); + + void StripExtension( const char *in, char *out ); + + int ParseRun( CBlock *block , CIcarus* icarus); + int ParseLoop( CBlock *block, bstream_t *bstream , CIcarus* icarus); + int ParseAffect( CBlock *block, bstream_t *bstream, CIcarus* icarus ); + int ParseIf( CBlock *block, bstream_t *bstream, CIcarus* icarus ); + int ParseElse( CBlock *block, bstream_t *bstream, CIcarus* icarus ); + int ParseTask( CBlock *block, bstream_t *bstream , CIcarus* icarus); + + int Affect( int id, int type , CIcarus* icarus); + + void AddTaskSequence( CSequence *sequence, CTaskGroup *group ); + CSequence *GetTaskSequence( CTaskGroup *group ); + + //Member variables + + int m_ownerID; + + CTaskManager *m_taskManager; + + int m_numCommands; //Total number of commands for the sequencer (including all child sequences) + + //sequenceID_m m_sequenceMap; + sequence_l m_sequences; + taskSequence_m m_taskSequences; + + CSequence *m_curSequence; + CTaskGroup *m_curGroup; + + bstream_t *m_curStream; + + int m_elseValid; + CBlock *m_elseOwner; + vector m_streamsCreated; + + int m_id; +}; + +#endif //__SEQUENCER__ \ No newline at end of file diff --git a/code/icarus/taskmanager.h b/code/icarus/taskmanager.h new file mode 100644 index 0000000..b5014ba --- /dev/null +++ b/code/icarus/taskmanager.h @@ -0,0 +1,227 @@ +// Task Manager header file + +#ifndef __TASK_MANAGER__ +#define __TASK_MANAGER__ + +typedef unsigned long DWORD; + +#define MAX_TASK_NAME 64 +#define TASKFLAG_NORMAL 0x00000000 +const int RUNAWAY_LIMIT = 256; + +enum +{ + TASK_RETURN_COMPLETE, + TASK_RETURN_FAILED, +}; + +enum +{ + TASK_OK, + TASK_FAILED, + TASK_START, + TASK_END, +}; + +// CTask + +class CTask +{ +public: + + CTask(); + ~CTask(); + + static CTask *Create( int GUID, CBlock *block ); + + void Free( void ); + + DWORD GetTimeStamp( void ) const { return m_timeStamp; } + CBlock *GetBlock( void ) const { return m_block; } + int GetGUID( void) const { return m_id; } + int GetID( void ) const { return m_block->GetBlockID(); } + + void SetTimeStamp( DWORD timeStamp ) { m_timeStamp = timeStamp; } + void SetBlock( CBlock *block ) { m_block = block; } + void SetGUID( int id ) { m_id = id; } + + // Overloaded new operator. + inline void *operator new( size_t size ) + { // Allocate the memory. + return IGameInterface::GetGame()->Malloc( size ); + } + + // Overloaded delete operator. + inline void operator delete( void *pRawData ) + { // Free the Memory. + IGameInterface::GetGame()->Free( pRawData ); + } + +protected: + + int m_id; + DWORD m_timeStamp; + CBlock *m_block; +}; + +// CTaskGroup + +class CTaskGroup +{ +public: + + typedef map < int, bool > taskCallback_m; + + CTaskGroup( void ); + ~CTaskGroup( void ); + + void Init( void ); + + int Add( CTask *task ); + + void SetGUID( int GUID ); + void SetParent( CTaskGroup *group ) { m_parent = group; } + + bool Complete(void) const { return ( m_numCompleted == m_completedTasks.size() ); } + + bool MarkTaskComplete( int id ); + + CTaskGroup *GetParent( void ) const { return m_parent; } + int GetGUID( void ) const { return m_GUID; } + + // Overloaded new operator. + static void *operator new( size_t size ) + { // Allocate the memory. + return IGameInterface::GetGame()->Malloc( size ); + } + // Overloaded delete operator. + static void operator delete( void *pRawData ) + { // Free the Memory. + IGameInterface::GetGame()->Free( pRawData ); + } + +//protected: + + taskCallback_m m_completedTasks; + + CTaskGroup *m_parent; + + unsigned int m_numCompleted; + int m_GUID; +}; + +// CTaskManager +class CSequencer; + +class CTaskManager +{ + + typedef map < int, CTask * > taskID_m; + typedef map < string, CTaskGroup * > taskGroupName_m; + typedef map < int, CTaskGroup * > taskGroupID_m; + typedef vector < CTaskGroup * > taskGroup_v; + typedef list < CTask *> tasks_l; + +public: + + CTaskManager(); + ~CTaskManager(); + + int GetID(); + + static CTaskManager *Create( void ); + + CBlock *GetCurrentTask( void ); + + int Init( CSequencer *owner ); + int Free( void ); + + int Flush( void ); + + int SetCommand( CBlock *block, int type, CIcarus* icarus ); + int Completed( int id ); + + int Update( CIcarus* icarus ); + int IsRunning( void ) const { return(!m_tasks.empty()); }; + bool IsResident( void ) const { return m_resident;}; + + CTaskGroup *AddTaskGroup( const char *name , CIcarus* icarus); + CTaskGroup *GetTaskGroup( const char *name, CIcarus* icarus); + CTaskGroup *GetTaskGroup( int id, CIcarus* icarus ); + + int MarkTask( int id, int operation, CIcarus* icarus ); + CBlock *RecallTask( void ); + + void Save(); + void Load( CIcarus* icarus ); + + // Overloaded new operator. + inline void* operator new( size_t size ) + { // Allocate the memory. + return IGameInterface::GetGame()->Malloc( size ); + } + + // Overloaded delete operator. + inline void operator delete( void *pRawData ) + { // Free the Memory. + IGameInterface::GetGame()->Free( pRawData ); + } + +protected: + + int Go( CIcarus* icarus ); //Heartbeat function called once per game frame + int CallbackCommand( CTask *task, int returnCode, CIcarus* icarus ); + + inline bool Check( int targetID, CBlock *block, int memberNum ) const; + + int GetVector( int entID, CBlock *block, int &memberNum, vec3_t &value, CIcarus* icarus ); + int GetFloat( int entID, CBlock *block, int &memberNum, float &value, CIcarus* icarus ); + int Get( int entID, CBlock *block, int &memberNum, char **value, CIcarus* icarus ); + + int PushTask( CTask *task, int flag ); + CTask *PopTask( int flag ); + + // Task functions + int Rotate( CTask *task, CIcarus* icarus ); + int Remove( CTask *task , CIcarus* icarus); + int Camera( CTask *task, CIcarus* icarus ); + int Print( CTask *task , CIcarus* icarus); + int Sound( CTask *task, CIcarus* icarus ); + int Move( CTask *task , CIcarus* icarus); + int Kill( CTask *task , CIcarus* icarus); + int Set( CTask *task, CIcarus* icarus ); + int Use( CTask *task , CIcarus* icarus); + int DeclareVariable( CTask *task , CIcarus* icarus); + int FreeVariable( CTask *task, CIcarus* icarus ); + int Signal( CTask *task , CIcarus* icarus); + int Play( CTask *task , CIcarus* icarus); + + int Wait( CTask *task, bool &completed, CIcarus* icarus ); + int WaitSignal( CTask *task, bool &completed, CIcarus* icarus); + + int SaveCommand( CBlock *block ); + + // Variables + + CSequencer *m_owner; + int m_ownerID; + + CTaskGroup *m_curGroup; + + taskGroup_v m_taskGroups; + tasks_l m_tasks; + + int m_GUID; + int m_count; + + taskGroupName_m m_taskGroupNameMap; + taskGroupID_m m_taskGroupIDMap; + + bool m_resident; + + int m_id; + + //CTask *m_waitTask; //Global pointer to the current task that is waiting for callback completion +}; + +#endif //__TASK_MANAGER__ diff --git a/code/jpeg-6/jcapimin.cpp b/code/jpeg-6/jcapimin.cpp new file mode 100644 index 0000000..3d917d7 --- /dev/null +++ b/code/jpeg-6/jcapimin.cpp @@ -0,0 +1,234 @@ +/* + * jcapimin.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the compression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-compression case or the transcoding-only + * case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jcapistd.c. But also see jcparam.c for + * parameter-setup helper routines, jcomapi.c for routines shared by + * compression and decompression, and jctrans.c for the transcoding case. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG compression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL void +jpeg_create_compress (j_compress_ptr cinfo) +{ + int i; + + /* For debugging purposes, zero the whole master structure. + * But error manager pointer is already there, so save and restore it. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + MEMZERO(cinfo, SIZEOF(struct jpeg_compress_struct)); + cinfo->err = err; + } + cinfo->is_decompressor = FALSE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->dest = NULL; + + cinfo->comp_info = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) + cinfo->quant_tbl_ptrs[i] = NULL; + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + cinfo->input_gamma = 1.0; /* in case application forgets */ + + /* OK, I'm ready */ + cinfo->global_state = CSTATE_START; +} + + +/* + * Destruction of a JPEG compression object + */ + +GLOBAL void +jpeg_destroy_compress (j_compress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG compression operation, + * but don't destroy the object itself. + */ + +GLOBAL void +jpeg_abort_compress (j_compress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Forcibly suppress or un-suppress all quantization and Huffman tables. + * Marks all currently defined tables as already written (if suppress) + * or not written (if !suppress). This will control whether they get emitted + * by a subsequent jpeg_start_compress call. + * + * This routine is exported for use by applications that want to produce + * abbreviated JPEG datastreams. It logically belongs in jcparam.c, but + * since it is called by jpeg_start_compress, we put it here --- otherwise + * jcparam.o would be linked whether the application used it or not. + */ + +GLOBAL void +jpeg_suppress_tables (j_compress_ptr cinfo, boolean suppress) +{ + int i; + JQUANT_TBL * qtbl; + JHUFF_TBL * htbl; + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + if ((qtbl = cinfo->quant_tbl_ptrs[i]) != NULL) + qtbl->sent_table = suppress; + } + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + if ((htbl = cinfo->dc_huff_tbl_ptrs[i]) != NULL) + htbl->sent_table = suppress; + if ((htbl = cinfo->ac_huff_tbl_ptrs[i]) != NULL) + htbl->sent_table = suppress; + } +} + + +/* + * Finish JPEG compression. + * + * If a multipass operating mode was selected, this may do a great deal of + * work including most of the actual output. + */ + +GLOBAL void +jpeg_finish_compress (j_compress_ptr cinfo) +{ + JDIMENSION iMCU_row; + + if (cinfo->global_state == CSTATE_SCANNING || + cinfo->global_state == CSTATE_RAW_OK) { + /* Terminate first pass */ + if (cinfo->next_scanline < cinfo->image_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_pass) (cinfo); + } else if (cinfo->global_state != CSTATE_WRCOEFS) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any remaining passes */ + while (! cinfo->master->is_last_pass) { + (*cinfo->master->prepare_for_pass) (cinfo); + for (iMCU_row = 0; iMCU_row < cinfo->total_iMCU_rows; iMCU_row++) { + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) iMCU_row; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* We bypass the main controller and invoke coef controller directly; + * all work is being done from the coefficient buffer. + */ + if (! (*cinfo->coef->compress_data) (cinfo, (JSAMPIMAGE) NULL)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } + (*cinfo->master->finish_pass) (cinfo); + } + /* Write EOI, do final cleanup */ + (*cinfo->marker->write_file_trailer) (cinfo); + (*cinfo->dest->term_destination) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); +} + + +/* + * Write a special marker. + * This is only recommended for writing COM or APPn markers. + * Must be called after jpeg_start_compress() and before + * first call to jpeg_write_scanlines() or jpeg_write_raw_data(). + */ + +GLOBAL void +jpeg_write_marker (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen) +{ + if (cinfo->next_scanline != 0 || + (cinfo->global_state != CSTATE_SCANNING && + cinfo->global_state != CSTATE_RAW_OK && + cinfo->global_state != CSTATE_WRCOEFS)) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + (*cinfo->marker->write_any_marker) (cinfo, marker, dataptr, datalen); +} + + +/* + * Alternate compression function: just write an abbreviated table file. + * Before calling this, all parameters and a data destination must be set up. + * + * To produce a pair of files containing abbreviated tables and abbreviated + * image data, one would proceed as follows: + * + * initialize JPEG object + * set JPEG parameters + * set destination to table file + * jpeg_write_tables(cinfo); + * set destination to image file + * jpeg_start_compress(cinfo, FALSE); + * write data... + * jpeg_finish_compress(cinfo); + * + * jpeg_write_tables has the side effect of marking all tables written + * (same as jpeg_suppress_tables(..., TRUE)). Thus a subsequent start_compress + * will not re-emit the tables unless it is passed write_all_tables=TRUE. + */ + +GLOBAL void +jpeg_write_tables (j_compress_ptr cinfo) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Initialize the marker writer ... bit of a crock to do it here. */ + jinit_marker_writer(cinfo); + /* Write them tables! */ + (*cinfo->marker->write_tables_only) (cinfo); + /* And clean up. */ + (*cinfo->dest->term_destination) (cinfo); + /* We can use jpeg_abort to release memory. */ + jpeg_abort((j_common_ptr) cinfo); +} diff --git a/code/jpeg-6/jccoefct.cpp b/code/jpeg-6/jccoefct.cpp new file mode 100644 index 0000000..74e98bc --- /dev/null +++ b/code/jpeg-6/jccoefct.cpp @@ -0,0 +1,454 @@ +/* + * jccoefct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for compression. + * This controller is the top level of the JPEG compressor proper. + * The coefficient buffer lies between forward-DCT and entropy encoding steps. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* We use a full-image coefficient buffer when doing Huffman optimization, + * and also for writing multiple-scan JPEG files. In all cases, the DCT + * step is run during the first pass, and subsequent passes need only read + * the buffered coefficients. + */ +#ifdef ENTROPY_OPT_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#else +#ifdef C_MULTISCAN_FILES_SUPPORTED +#define FULL_COEF_BUFFER_SUPPORTED +#endif +#endif + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* For single-pass compression, it's sufficient to buffer just one MCU + * (although this may prove a bit slow in practice). We allocate a + * workspace of C_MAX_BLOCKS_IN_MCU coefficient blocks, and reuse it for each + * MCU constructed and sent. (On 80x86, the workspace is FAR even though + * it's not really very big; this is to keep the module interfaces unchanged + * when a large coefficient buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays. + */ + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +/* Forward declarations */ +METHODDEF boolean compress_data + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +#ifdef FULL_COEF_BUFFER_SUPPORTED +METHODDEF boolean compress_first_pass + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +METHODDEF boolean compress_output + JPP((j_compress_ptr cinfo, JSAMPIMAGE input_buf)); +#endif + + +LOCAL void +start_iMCU_row (j_compress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (coef->iMCU_row_num < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_coef (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + coef->iMCU_row_num = 0; + start_iMCU_row(cinfo); + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (coef->whole_image[0] != NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_data; + break; +#ifdef FULL_COEF_BUFFER_SUPPORTED + case JBUF_SAVE_AND_PASS: + if (coef->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_first_pass; + break; + case JBUF_CRANK_DEST: + if (coef->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + coef->pub.compress_data = compress_output; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data in the single-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf contains a plane for each component in image. + * For single pass, this is the same as the components in the scan. + */ + +METHODDEF boolean +compress_data (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, bi, ci, yindex, yoffset, blockcnt; + JDIMENSION ypos, xpos; + jpeg_component_info *compptr; + + /* Loop to write as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Determine where data comes from in input_buf and do the DCT thing. + * Each call on forward_DCT processes a horizontal row of DCT blocks + * as wide as an MCU; we rely on having allocated the MCU_buffer[] blocks + * sequentially. Dummy blocks at the right or bottom edge are filled in + * specially. The data in them does not matter for image reconstruction, + * so we fill them with values that will encode to the smallest amount of + * data, viz: all zeroes in the AC entries, DC entries equal to previous + * block's DC value. (Thanks to Thomas Kinsman for this idea.) + */ + blkn = 0; + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + xpos = MCU_col_num * compptr->MCU_sample_width; + ypos = yoffset * DCTSIZE; /* ypos == (yoffset+yindex) * DCTSIZE */ + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (coef->iMCU_row_num < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + (*cinfo->fdct->forward_DCT) (cinfo, compptr, + input_buf[ci], coef->MCU_buffer[blkn], + ypos, xpos, (JDIMENSION) blockcnt); + if (blockcnt < compptr->MCU_width) { + /* Create some dummy blocks at the right edge of the image. */ + jzero_far((void FAR *) coef->MCU_buffer[blkn + blockcnt], + (compptr->MCU_width - blockcnt) * SIZEOF(JBLOCK)); + for (bi = blockcnt; bi < compptr->MCU_width; bi++) { + coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn+bi-1][0][0]; + } + } + } else { + /* Create a row of dummy blocks at the bottom of the image. */ + jzero_far((void FAR *) coef->MCU_buffer[blkn], + compptr->MCU_width * SIZEOF(JBLOCK)); + for (bi = 0; bi < compptr->MCU_width; bi++) { + coef->MCU_buffer[blkn+bi][0][0] = coef->MCU_buffer[blkn-1][0][0]; + } + } + blkn += compptr->MCU_width; + ypos += DCTSIZE; + } + } + /* Try to write the MCU. In event of a suspension failure, we will + * re-DCT the MCU on restart (a bit inefficient, could be fixed...) + */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + + +#ifdef FULL_COEF_BUFFER_SUPPORTED + +/* + * Process some data in the first pass of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the image. + * This amount of data is read from the source buffer, DCT'd and quantized, + * and saved into the virtual arrays. We also generate suitable dummy blocks + * as needed at the right and lower edges. (The dummy blocks are constructed + * in the virtual arrays, which have been padded appropriately.) This makes + * it possible for subsequent passes not to worry about real vs. dummy blocks. + * + * We must also emit the data to the entropy encoder. This is conveniently + * done by calling compress_output() after we've loaded the current strip + * of the virtual arrays. + * + * NB: input_buf contains a plane for each component in image. All + * components are DCT'd and loaded into the virtual arrays in this pass. + * However, it may be that only a subset of the components are emitted to + * the entropy encoder during this first pass; be careful about looking + * at the scan-dependent variables (MCU dimensions, etc). + */ + +METHODDEF boolean +compress_first_pass (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION blocks_across, MCUs_across, MCUindex; + int bi, ci, h_samp_factor, block_row, block_rows, ndummy; + JCOEF lastDC; + jpeg_component_info *compptr; + JBLOCKARRAY buffer; + JBLOCKROW thisblockrow, lastblockrow; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (coef->iMCU_row_num < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here, since may not be set! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + blocks_across = compptr->width_in_blocks; + h_samp_factor = compptr->h_samp_factor; + /* Count number of dummy blocks to be added at the right margin. */ + ndummy = (int) (blocks_across % h_samp_factor); + if (ndummy > 0) + ndummy = h_samp_factor - ndummy; + /* Perform DCT for all non-dummy blocks in this iMCU row. Each call + * on forward_DCT processes a complete horizontal row of DCT blocks. + */ + for (block_row = 0; block_row < block_rows; block_row++) { + thisblockrow = buffer[block_row]; + (*cinfo->fdct->forward_DCT) (cinfo, compptr, + input_buf[ci], thisblockrow, + (JDIMENSION) (block_row * DCTSIZE), + (JDIMENSION) 0, blocks_across); + if (ndummy > 0) { + /* Create dummy blocks at the right edge of the image. */ + thisblockrow += blocks_across; /* => first dummy block */ + jzero_far((void FAR *) thisblockrow, ndummy * SIZEOF(JBLOCK)); + lastDC = thisblockrow[-1][0]; + for (bi = 0; bi < ndummy; bi++) { + thisblockrow[bi][0] = lastDC; + } + } + } + /* If at end of image, create dummy block rows as needed. + * The tricky part here is that within each MCU, we want the DC values + * of the dummy blocks to match the last real block's DC value. + * This squeezes a few more bytes out of the resulting file... + */ + if (coef->iMCU_row_num == last_iMCU_row) { + blocks_across += ndummy; /* include lower right corner */ + MCUs_across = blocks_across / h_samp_factor; + for (block_row = block_rows; block_row < compptr->v_samp_factor; + block_row++) { + thisblockrow = buffer[block_row]; + lastblockrow = buffer[block_row-1]; + jzero_far((void FAR *) thisblockrow, + (size_t) (blocks_across * SIZEOF(JBLOCK))); + for (MCUindex = 0; MCUindex < MCUs_across; MCUindex++) { + lastDC = lastblockrow[h_samp_factor-1][0]; + for (bi = 0; bi < h_samp_factor; bi++) { + thisblockrow[bi][0] = lastDC; + } + thisblockrow += h_samp_factor; /* advance to next MCU in row */ + lastblockrow += h_samp_factor; + } + } + } + } + /* NB: compress_output will increment iMCU_row_num if successful. + * A suspension return will result in redoing all the work above next time. + */ + + /* Emit data to the entropy encoder, sharing code with subsequent passes */ + return compress_output(cinfo, input_buf); +} + + +/* + * Process some data in subsequent passes of a multi-pass case. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF boolean +compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. + * NB: during first pass, this is safe only because the buffers will + * already be aligned properly, so jmemmgr.c won't need to do any I/O. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to write the MCU. */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + +#endif /* FULL_COEF_BUFFER_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL void +jinit_c_coef_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr coef; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef FULL_COEF_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + int ci; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) compptr->v_samp_factor); + } +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->whole_image[0] = NULL; /* flag for no virtual arrays */ + } +} diff --git a/code/jpeg-6/jccolor.cpp b/code/jpeg-6/jccolor.cpp new file mode 100644 index 0000000..a1b5293 --- /dev/null +++ b/code/jpeg-6/jccolor.cpp @@ -0,0 +1,465 @@ +/* + * jccolor.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input colorspace conversion routines. + */ + + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_converter pub; /* public fields */ + + /* Private state for RGB->YCC conversion */ + INT32 * rgb_ycc_tab; /* => table for RGB to YCbCr conversion */ +} my_color_converter; + +typedef my_color_converter * my_cconvert_ptr; + + +/**************** RGB -> YCbCr conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * Y = 0.29900 * R + 0.58700 * G + 0.11400 * B + * Cb = -0.16874 * R - 0.33126 * G + 0.50000 * B + CENTERJSAMPLE + * Cr = 0.50000 * R - 0.41869 * G - 0.08131 * B + CENTERJSAMPLE + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * Note: older versions of the IJG code used a zero offset of MAXJSAMPLE/2, + * rather than CENTERJSAMPLE, for Cb and Cr. This gave equal positive and + * negative swings for Cb/Cr, but meant that grayscale values (Cb=Cr=0) + * were not represented exactly. Now we sacrifice exact representation of + * maximum red and maximum blue in order to get exact grayscales. + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times R,G,B for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The CENTERJSAMPLE offsets and the rounding fudge-factor of 0.5 are included + * in the tables to save adding them separately in the inner loop. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define CBCR_OFFSET ((INT32) CENTERJSAMPLE << SCALEBITS) +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L< Y section */ +#define G_Y_OFF (1*(MAXJSAMPLE+1)) /* offset to G => Y section */ +#define B_Y_OFF (2*(MAXJSAMPLE+1)) /* etc. */ +#define R_CB_OFF (3*(MAXJSAMPLE+1)) +#define G_CB_OFF (4*(MAXJSAMPLE+1)) +#define B_CB_OFF (5*(MAXJSAMPLE+1)) +#define R_CR_OFF B_CB_OFF /* B=>Cb, R=>Cr are the same */ +#define G_CR_OFF (6*(MAXJSAMPLE+1)) +#define B_CR_OFF (7*(MAXJSAMPLE+1)) +#define TABLE_SIZE (8*(MAXJSAMPLE+1)) + + +/* + * Initialize for RGB->YCC colorspace conversion. + */ + +METHODDEF void +rgb_ycc_start (j_compress_ptr cinfo) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + INT32 * rgb_ycc_tab; + INT32 i; + + /* Allocate and fill in the conversion tables. */ + cconvert->rgb_ycc_tab = rgb_ycc_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (TABLE_SIZE * SIZEOF(INT32))); + + for (i = 0; i <= MAXJSAMPLE; i++) { + rgb_ycc_tab[i+R_Y_OFF] = FIX(0.29900) * i; + rgb_ycc_tab[i+G_Y_OFF] = FIX(0.58700) * i; + rgb_ycc_tab[i+B_Y_OFF] = FIX(0.11400) * i + ONE_HALF; + rgb_ycc_tab[i+R_CB_OFF] = (-FIX(0.16874)) * i; + rgb_ycc_tab[i+G_CB_OFF] = (-FIX(0.33126)) * i; + /* We use a rounding fudge-factor of 0.5-epsilon for Cb and Cr. + * This ensures that the maximum output will round to MAXJSAMPLE + * not MAXJSAMPLE+1, and thus that we don't have to range-limit. + */ + rgb_ycc_tab[i+B_CB_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +/* B=>Cb and R=>Cr tables are the same + rgb_ycc_tab[i+R_CR_OFF] = FIX(0.50000) * i + CBCR_OFFSET + ONE_HALF-1; +*/ + rgb_ycc_tab[i+G_CR_OFF] = (-FIX(0.41869)) * i; + rgb_ycc_tab[i+B_CR_OFF] = (-FIX(0.08131)) * i; + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * + * Note that we change from the application's interleaved-pixel format + * to our internal noninterleaved, one-plane-per-component format. + * The input buffer is therefore three times as wide as the output buffer. + * + * A starting row offset is provided only for the output buffer. The caller + * can easily adjust the passed input_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF void +rgb_ycc_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = GETJSAMPLE(inptr[RGB_RED]); + g = GETJSAMPLE(inptr[RGB_GREEN]); + b = GETJSAMPLE(inptr[RGB_BLUE]); + inptr += RGB_PIXELSIZE; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + /* Cb */ + outptr1[col] = (JSAMPLE) + ((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF]) + >> SCALEBITS); + /* Cr */ + outptr2[col] = (JSAMPLE) + ((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF]) + >> SCALEBITS); + } + } +} + + +/**************** Cases other than RGB -> YCbCr **************/ + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles RGB->grayscale conversion, which is the same + * as the RGB->Y portion of RGB->YCbCr. + * We assume rgb_ycc_start has been called (we only use the Y tables). + */ + +METHODDEF void +rgb_gray_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = GETJSAMPLE(inptr[RGB_RED]); + g = GETJSAMPLE(inptr[RGB_GREEN]); + b = GETJSAMPLE(inptr[RGB_BLUE]); + inptr += RGB_PIXELSIZE; + /* Y */ + outptr[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles Adobe-style CMYK->YCCK conversion, + * where we convert R=1-C, G=1-M, and B=1-Y to YCbCr using the same + * conversion as above, while passing K (black) unchanged. + * We assume rgb_ycc_start has been called. + */ + +METHODDEF void +cmyk_ycck_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int r, g, b; + register INT32 * ctab = cconvert->rgb_ycc_tab; + register JSAMPROW inptr; + register JSAMPROW outptr0, outptr1, outptr2, outptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr0 = output_buf[0][output_row]; + outptr1 = output_buf[1][output_row]; + outptr2 = output_buf[2][output_row]; + outptr3 = output_buf[3][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + r = MAXJSAMPLE - GETJSAMPLE(inptr[0]); + g = MAXJSAMPLE - GETJSAMPLE(inptr[1]); + b = MAXJSAMPLE - GETJSAMPLE(inptr[2]); + /* K passes through as-is */ + outptr3[col] = inptr[3]; /* don't need GETJSAMPLE here */ + inptr += 4; + /* If the inputs are 0..MAXJSAMPLE, the outputs of these equations + * must be too; we do not need an explicit range-limiting operation. + * Hence the value being shifted is never negative, and we don't + * need the general RIGHT_SHIFT macro. + */ + /* Y */ + outptr0[col] = (JSAMPLE) + ((ctab[r+R_Y_OFF] + ctab[g+G_Y_OFF] + ctab[b+B_Y_OFF]) + >> SCALEBITS); + /* Cb */ + outptr1[col] = (JSAMPLE) + ((ctab[r+R_CB_OFF] + ctab[g+G_CB_OFF] + ctab[b+B_CB_OFF]) + >> SCALEBITS); + /* Cr */ + outptr2[col] = (JSAMPLE) + ((ctab[r+R_CR_OFF] + ctab[g+G_CR_OFF] + ctab[b+B_CR_OFF]) + >> SCALEBITS); + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles grayscale output with no conversion. + * The source can be either plain grayscale or YCbCr (since Y == gray). + */ + +METHODDEF void +grayscale_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->image_width; + int instride = cinfo->input_components; + + while (--num_rows >= 0) { + inptr = *input_buf++; + outptr = output_buf[0][output_row]; + output_row++; + for (col = 0; col < num_cols; col++) { + outptr[col] = inptr[0]; /* don't need GETJSAMPLE() here */ + inptr += instride; + } + } +} + + +/* + * Convert some rows of samples to the JPEG colorspace. + * This version handles multi-component colorspaces without conversion. + * We assume input_components == num_components. + */ + +METHODDEF void +null_convert (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows) +{ + register JSAMPROW inptr; + register JSAMPROW outptr; + register JDIMENSION col; + register int ci; + int nc = cinfo->num_components; + JDIMENSION num_cols = cinfo->image_width; + + while (--num_rows >= 0) { + /* It seems fastest to make a separate pass for each component. */ + for (ci = 0; ci < nc; ci++) { + inptr = *input_buf; + outptr = output_buf[ci][output_row]; + for (col = 0; col < num_cols; col++) { + outptr[col] = inptr[ci]; /* don't need GETJSAMPLE() here */ + inptr += nc; + } + } + input_buf++; + output_row++; + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF void +null_method (j_compress_ptr cinfo) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for input colorspace conversion. + */ + +GLOBAL void +jinit_color_converter (j_compress_ptr cinfo) +{ + my_cconvert_ptr cconvert; + + cconvert = (my_cconvert_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_converter)); + cinfo->cconvert = (struct jpeg_color_converter *) cconvert; + /* set start_pass to null method until we find out differently */ + cconvert->pub.start_pass = null_method; + + /* Make sure input_components agrees with in_color_space */ + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + if (cinfo->input_components != 1) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + if (cinfo->input_components != RGB_PIXELSIZE) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; +#endif /* else share code with YCbCr */ + + case JCS_YCbCr: + if (cinfo->input_components != 3) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->input_components != 4) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->input_components < 1) + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + break; + } + + /* Check num_components, set conversion method based on requested space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_GRAYSCALE) + cconvert->pub.color_convert = grayscale_convert; + else if (cinfo->in_color_space == JCS_RGB) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_gray_convert; + } else if (cinfo->in_color_space == JCS_YCbCr) + cconvert->pub.color_convert = grayscale_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_RGB && RGB_PIXELSIZE == 3) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_RGB) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = rgb_ycc_convert; + } else if (cinfo->in_color_space == JCS_YCbCr) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_CMYK) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + if (cinfo->in_color_space == JCS_CMYK) { + cconvert->pub.start_pass = rgb_ycc_start; + cconvert->pub.color_convert = cmyk_ycck_convert; + } else if (cinfo->in_color_space == JCS_YCCK) + cconvert->pub.color_convert = null_convert; + else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: /* allow null conversion of JCS_UNKNOWN */ + if (cinfo->jpeg_color_space != cinfo->in_color_space || + cinfo->num_components != cinfo->input_components) + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + cconvert->pub.color_convert = null_convert; + break; + } +} diff --git a/code/jpeg-6/jcdctmgr.cpp b/code/jpeg-6/jcdctmgr.cpp new file mode 100644 index 0000000..de98cb1 --- /dev/null +++ b/code/jpeg-6/jcdctmgr.cpp @@ -0,0 +1,397 @@ +/* + * jcdctmgr.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the forward-DCT management logic. + * This code selects a particular DCT implementation to be used, + * and it performs related housekeeping chores including coefficient + * quantization. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_forward_dct pub; /* public fields */ + + /* Pointer to the DCT routine actually in use */ + forward_DCT_method_ptr do_dct; + + /* The actual post-DCT divisors --- not identical to the quant table + * entries, because of scaling (especially for an unnormalized DCT). + * Each table is given in normal array order; note that this must + * be converted from the zigzag order of the quantization tables. + */ + DCTELEM * divisors[NUM_QUANT_TBLS]; + +#ifdef DCT_FLOAT_SUPPORTED + /* Same as above for the floating-point case. */ + float_DCT_method_ptr do_float_dct; + FAST_FLOAT * float_divisors[NUM_QUANT_TBLS]; +#endif +} my_fdct_controller; + +typedef my_fdct_controller * my_fdct_ptr; + + +/* + * Initialize for a processing pass. + * Verify that all referenced Q-tables are present, and set up + * the divisor table for each one. + * In the current implementation, DCT of all components is done during + * the first pass, even if only some components will be output in the + * first scan. Hence all components should be examined here. + */ + +METHODDEF void +start_pass_fdctmgr (j_compress_ptr cinfo) +{ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + int ci, qtblno, i; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; +#ifdef DCT_ISLOW_SUPPORTED + DCTELEM * dtbl; +#endif + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + qtblno = compptr->quant_tbl_no; + /* Make sure specified quantization table is present */ + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + qtbl = cinfo->quant_tbl_ptrs[qtblno]; + /* Compute divisors for this quant table */ + /* We may do this more than once for same table, but it's not a big deal */ + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + /* For LL&M IDCT method, divisors are equal to raw quantization + * coefficients multiplied by 8 (to counteract scaling). + */ + if (fdct->divisors[qtblno] == NULL) { + fdct->divisors[qtblno] = (DCTELEM *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(DCTELEM)); + } + dtbl = fdct->divisors[qtblno]; + for (i = 0; i < DCTSIZE2; i++) { + dtbl[i] = ((DCTELEM) qtbl->quantval[jpeg_zigzag_order[i]]) << 3; + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + */ +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits: in natural order */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + if (fdct->divisors[qtblno] == NULL) { + fdct->divisors[qtblno] = (DCTELEM *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(DCTELEM)); + } + dtbl = fdct->divisors[qtblno]; + for (i = 0; i < DCTSIZE2; i++) { + dtbl[i] = (DCTELEM) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[jpeg_zigzag_order[i]], + (INT32) aanscales[i]), + CONST_BITS-3); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, divisors are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * We apply a further scale factor of 8. + * What's actually stored is 1/divisor so that the inner loop can + * use a multiplication rather than a division. + */ + FAST_FLOAT * fdtbl; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + if (fdct->float_divisors[qtblno] == NULL) { + fdct->float_divisors[qtblno] = (FAST_FLOAT *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + DCTSIZE2 * SIZEOF(FAST_FLOAT)); + } + fdtbl = fdct->float_divisors[qtblno]; + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fdtbl[i] = (FAST_FLOAT) + (1.0 / (((double) qtbl->quantval[jpeg_zigzag_order[i]] * + aanscalefactor[row] * aanscalefactor[col] * 8.0))); + i++; + } + } + } + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Perform forward DCT on one or more blocks of a component. + * + * The input samples are taken from the sample_data[] array starting at + * position start_row/start_col, and moving to the right for any additional + * blocks. The quantized coefficients are returned in coef_blocks[]. + */ + +#if 0 // bk001204 +METHODDEF void +forward_DCT (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks) +/* This version is used for integer DCT implementations. */ +{ + /* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + forward_DCT_method_ptr do_dct = fdct->do_dct; + DCTELEM * divisors = fdct->divisors[compptr->quant_tbl_no]; + DCTELEM workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register DCTELEM *workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for (elemr = 0; elemr < DCTSIZE; elemr++) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; +#else + { register int elemc; + for (elemc = DCTSIZE; elemc > 0; elemc--) { + *workspaceptr++ = GETJSAMPLE(*elemptr++) - CENTERJSAMPLE; + } + } +#endif + } + } + + /* Perform the DCT */ + (*do_dct) (workspace); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register DCTELEM temp, qval; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for (i = 0; i < DCTSIZE2; i++) { + qval = divisors[i]; + temp = workspace[i]; + /* Divide the coefficient value by qval, ensuring proper rounding. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * + * In most files, at least half of the output values will be zero + * (at default quantization settings, more like three-quarters...) + * so we should ensure that this case is fast. On many machines, + * a comparison is enough cheaper than a divide to make a special test + * a win. Since both inputs will be nonnegative, we need only test + * for a < b to discover whether a/b is 0. + * If your machine's division is fast enough, define FAST_DIVIDE. + */ +#ifdef FAST_DIVIDE +#define DIVIDE_BY(a,b) a /= b +#else +#define DIVIDE_BY(a,b) if (a >= b) a /= b; else a = 0 +#endif + if (temp < 0) { + temp = -temp; + temp += qval>>1; /* for rounding */ + DIVIDE_BY(temp, qval); + temp = -temp; + } else { + temp += qval>>1; /* for rounding */ + DIVIDE_BY(temp, qval); + } + output_ptr[i] = (JCOEF) temp; + } + } + } +} +#endif // 0 + +#ifdef DCT_FLOAT_SUPPORTED + +METHODDEF void +forward_DCT_float (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks) +/* This version is used for floating-point DCT implementations. */ +{ + /* This routine is heavily used, so it's worth coding it tightly. */ + my_fdct_ptr fdct = (my_fdct_ptr) cinfo->fdct; + float_DCT_method_ptr do_dct = fdct->do_float_dct; + FAST_FLOAT * divisors = fdct->float_divisors[compptr->quant_tbl_no]; + FAST_FLOAT workspace[DCTSIZE2]; /* work area for FDCT subroutine */ + JDIMENSION bi; + + sample_data += start_row; /* fold in the vertical offset once */ + + for (bi = 0; bi < num_blocks; bi++, start_col += DCTSIZE) { + /* Load data into workspace, applying unsigned->signed conversion */ + { register FAST_FLOAT *workspaceptr; + register JSAMPROW elemptr; + register int elemr; + + workspaceptr = workspace; + for (elemr = 0; elemr < DCTSIZE; elemr++) { + elemptr = sample_data[elemr] + start_col; +#if DCTSIZE == 8 /* unroll the inner loop */ + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + *workspaceptr++ = (FAST_FLOAT)(GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); +#else + { register int elemc; + for (elemc = DCTSIZE; elemc > 0; elemc--) { + *workspaceptr++ = (FAST_FLOAT) + (GETJSAMPLE(*elemptr++) - CENTERJSAMPLE); + } + } +#endif + } + } + + /* Perform the DCT */ + (*do_dct) (workspace); + + /* Quantize/descale the coefficients, and store into coef_blocks[] */ + { register FAST_FLOAT temp; + register int i; + register JCOEFPTR output_ptr = coef_blocks[bi]; + + for (i = 0; i < DCTSIZE2; i++) { + /* Apply the quantization and scaling factor */ + temp = workspace[i] * divisors[i]; + /* Round to nearest integer. + * Since C does not specify the direction of rounding for negative + * quotients, we have to force the dividend positive for portability. + * The maximum coefficient size is +-16K (for 12-bit data), so this + * code should work for either 16-bit or 32-bit ints. + */ + output_ptr[i] = (JCOEF) ((int) (temp + (FAST_FLOAT) 16384.5) - 16384); + } + } + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ + + +/* + * Initialize FDCT manager. + */ + +GLOBAL void +jinit_forward_dct (j_compress_ptr cinfo) +{ + my_fdct_ptr fdct; + int i; + + fdct = (my_fdct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_fdct_controller)); + cinfo->fdct = (struct jpeg_forward_dct *) fdct; + fdct->pub.start_pass = start_pass_fdctmgr; + + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_islow; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + fdct->pub.forward_DCT = forward_DCT; + fdct->do_dct = jpeg_fdct_ifast; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + fdct->pub.forward_DCT = forward_DCT_float; + fdct->do_float_dct = jpeg_fdct_float; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + + /* Mark divisor tables unallocated */ + for (i = 0; i < NUM_QUANT_TBLS; i++) { + fdct->divisors[i] = NULL; +#ifdef DCT_FLOAT_SUPPORTED + fdct->float_divisors[i] = NULL; +#endif + } +} diff --git a/code/jpeg-6/jchuff.cpp b/code/jpeg-6/jchuff.cpp new file mode 100644 index 0000000..4dcb183 --- /dev/null +++ b/code/jpeg-6/jchuff.cpp @@ -0,0 +1,853 @@ +/* + * jchuff.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines. + * + * Much of the complexity here has to do with supporting output suspension. + * If the data destination module demands suspension, we want to be able to + * back up to the start of the current MCU. To do this, we copy state + * variables into local working storage, and update them back to the + * permanent JPEG objects only upon successful completion of an MCU. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jcphuff.c */ + + +/* Expanded entropy encoder object for Huffman encoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).put_buffer = (src).put_buffer, \ + (dest).put_bits = (src).put_bits, \ + (dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + savable_state saved; /* Bit buffer & DC state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + c_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + c_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; + +#ifdef ENTROPY_OPT_SUPPORTED /* Statistics tables for optimization */ + long * dc_count_ptrs[NUM_HUFF_TBLS]; + long * ac_count_ptrs[NUM_HUFF_TBLS]; +#endif +} huff_entropy_encoder; + +typedef huff_entropy_encoder * huff_entropy_ptr; + +/* Working state while writing an MCU. + * This struct contains all the fields that are needed by subroutines. + */ + +typedef struct { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + savable_state cur; /* Current bit buffer & DC state */ + j_compress_ptr cinfo; /* dump_buffer needs access to this */ +} working_state; + + +/* Forward declarations */ +METHODDEF boolean encode_mcu_huff JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF void finish_pass_huff JPP((j_compress_ptr cinfo)); +#ifdef ENTROPY_OPT_SUPPORTED +METHODDEF boolean encode_mcu_gather JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF void finish_pass_gather JPP((j_compress_ptr cinfo)); +#endif + + +/* + * Initialize for a Huffman-compressed scan. + * If gather_statistics is TRUE, we do not output anything during the scan, + * just count the Huffman symbols used and generate Huffman code tables. + */ + +METHODDEF void +start_pass_huff (j_compress_ptr cinfo, boolean gather_statistics) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + if (gather_statistics) { +#ifdef ENTROPY_OPT_SUPPORTED + entropy->pub.encode_mcu = encode_mcu_gather; + entropy->pub.finish_pass = finish_pass_gather; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + entropy->pub.encode_mcu = encode_mcu_huff; + entropy->pub.finish_pass = finish_pass_huff; + } + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Make sure requested tables are present */ + /* (In gather mode, tables need not be allocated yet) */ + if (dctbl < 0 || dctbl >= NUM_HUFF_TBLS || + (cinfo->dc_huff_tbl_ptrs[dctbl] == NULL && !gather_statistics)) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, dctbl); + if (actbl < 0 || actbl >= NUM_HUFF_TBLS || + (cinfo->ac_huff_tbl_ptrs[actbl] == NULL && !gather_statistics)) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, actbl); + if (gather_statistics) { +#ifdef ENTROPY_OPT_SUPPORTED + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if (entropy->dc_count_ptrs[dctbl] == NULL) + entropy->dc_count_ptrs[dctbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->dc_count_ptrs[dctbl], 257 * SIZEOF(long)); + if (entropy->ac_count_ptrs[actbl] == NULL) + entropy->ac_count_ptrs[actbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->ac_count_ptrs[actbl], 257 * SIZEOF(long)); +#endif + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_c_derived_tbl(cinfo, cinfo->dc_huff_tbl_ptrs[dctbl], + & entropy->dc_derived_tbls[dctbl]); + jpeg_make_c_derived_tbl(cinfo, cinfo->ac_huff_tbl_ptrs[actbl], + & entropy->ac_derived_tbls[actbl]); + } + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bit buffer to empty */ + entropy->saved.put_buffer = 0; + entropy->saved.put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* + * Compute the derived values for a Huffman table. + * Note this is also used by jcphuff.c. + */ + +GLOBAL void +jpeg_make_c_derived_tbl (j_compress_ptr cinfo, JHUFF_TBL * htbl, + c_derived_tbl ** pdtbl) +{ + c_derived_tbl *dtbl; + int p, i, l, lastp, si; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (c_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(c_derived_tbl)); + dtbl = *pdtbl; + + /* Figure C.1: make table of Huffman code length for each symbol */ + /* Note that this is in code-length order. */ + + p = 0; + for (l = 1; l <= 16; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + lastp = p; + + /* Figure C.2: generate the codes themselves */ + /* Note that this is in code-length order. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + /* Figure C.3: generate encoding tables */ + /* These are code and size indexed by symbol value */ + + /* Set any codeless symbols to have code length 0; + * this allows emit_bits to detect any attempt to emit such symbols. + */ + MEMZERO(dtbl->ehufsi, SIZEOF(dtbl->ehufsi)); + + for (p = 0; p < lastp; p++) { + dtbl->ehufco[htbl->huffval[p]] = huffcode[p]; + dtbl->ehufsi[htbl->huffval[p]] = huffsize[p]; + } +} + + +/* Outputting bytes to the file */ + +/* Emit a byte, taking 'action' if must suspend. */ +#define emit_byte(state,val,action) \ + { *(state)->next_output_byte++ = (JOCTET) (val); \ + if (--(state)->free_in_buffer == 0) \ + if (! dump_buffer(state)) \ + { action; } } + + +LOCAL boolean +dump_buffer (working_state * state) +/* Empty the output buffer; return TRUE if successful, FALSE if must suspend */ +{ + struct jpeg_destination_mgr * dest = state->cinfo->dest; + + if (! (*dest->empty_output_buffer) (state->cinfo)) + return FALSE; + /* After a successful buffer dump, must reset buffer pointers */ + state->next_output_byte = dest->next_output_byte; + state->free_in_buffer = dest->free_in_buffer; + return TRUE; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL boolean +emit_bits (working_state * state, unsigned int code, int size) +/* Emit some bits; return TRUE if successful, FALSE if must suspend */ +{ + /* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = state->cur.put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if (size == 0) + ERREXIT(state->cinfo, JERR_HUFF_MISSING_CODE); + + put_buffer &= (((INT32) 1)<cur.put_buffer; /* and merge with old buffer contents */ + + while (put_bits >= 8) { + int c = (int) ((put_buffer >> 16) & 0xFF); + + emit_byte(state, c, return FALSE); + if (c == 0xFF) { /* need to stuff a zero byte? */ + emit_byte(state, 0, return FALSE); + } + put_buffer <<= 8; + put_bits -= 8; + } + + state->cur.put_buffer = put_buffer; /* update state variables */ + state->cur.put_bits = put_bits; + + return TRUE; +} + + +LOCAL boolean +flush_bits (working_state * state) +{ + if (! emit_bits(state, 0x7F, 7)) /* fill any partial byte with ones */ + return FALSE; + state->cur.put_buffer = 0; /* and reset bit-buffer to empty */ + state->cur.put_bits = 0; + return TRUE; +} + + +/* Encode a single block's worth of coefficients */ + +LOCAL boolean +encode_one_block (working_state * state, JCOEFPTR block, int last_dc_val, + c_derived_tbl *dctbl, c_derived_tbl *actbl) +{ + register int temp, temp2; + register int nbits; + register int k, r, i; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = temp2 = block[0] - last_dc_val; + + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + + /* Emit the Huffman-coded symbol for the number of bits */ + if (! emit_bits(state, dctbl->ehufco[nbits], dctbl->ehufsi[nbits])) + return FALSE; + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (nbits) /* emit_bits rejects calls with size 0 */ + if (! emit_bits(state, (unsigned int) temp2, nbits)) + return FALSE; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for (k = 1; k < DCTSIZE2; k++) { + if ((temp = block[jpeg_natural_order[k]]) == 0) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + if (! emit_bits(state, actbl->ehufco[0xF0], actbl->ehufsi[0xF0])) + return FALSE; + r -= 16; + } + + temp2 = temp; + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + + /* Emit Huffman symbol for run length / number of bits */ + i = (r << 4) + nbits; + if (! emit_bits(state, actbl->ehufco[i], actbl->ehufsi[i])) + return FALSE; + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (! emit_bits(state, (unsigned int) temp2, nbits)) + return FALSE; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if (r > 0) + if (! emit_bits(state, actbl->ehufco[0], actbl->ehufsi[0])) + return FALSE; + + return TRUE; +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL boolean +emit_restart (working_state * state, int restart_num) +{ + int ci; + + if (! flush_bits(state)) + return FALSE; + + emit_byte(state, 0xFF, return FALSE); + emit_byte(state, JPEG_RST0 + restart_num, return FALSE); + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < state->cinfo->comps_in_scan; ci++) + state->cur.last_dc_val[ci] = 0; + + /* The restart counter is not updated until we successfully write the MCU. */ + + return TRUE; +} + + +/* + * Encode and output one MCU's worth of Huffman-compressed coefficients. + */ + +METHODDEF boolean +encode_mcu_huff (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + int blkn, ci; + jpeg_component_info * compptr; + + /* Load up working state */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE(state.cur, entropy->saved); + state.cinfo = cinfo; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! emit_restart(&state, entropy->next_restart_num)) + return FALSE; + } + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + if (! encode_one_block(&state, + MCU_data[blkn][0], state.cur.last_dc_val[ci], + entropy->dc_derived_tbls[compptr->dc_tbl_no], + entropy->ac_derived_tbls[compptr->ac_tbl_no])) + return FALSE; + /* Update last_dc_val */ + state.cur.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + /* Completed MCU, so update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE(entropy->saved, state.cur); + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed scan. + */ + +METHODDEF void +finish_pass_huff (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + working_state state; + + /* Load up working state ... flush_bits needs it */ + state.next_output_byte = cinfo->dest->next_output_byte; + state.free_in_buffer = cinfo->dest->free_in_buffer; + ASSIGN_STATE(state.cur, entropy->saved); + state.cinfo = cinfo; + + /* Flush out the last data */ + if (! flush_bits(&state)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + + /* Update state */ + cinfo->dest->next_output_byte = state.next_output_byte; + cinfo->dest->free_in_buffer = state.free_in_buffer; + ASSIGN_STATE(entropy->saved, state.cur); +} + + +/* + * Huffman coding optimization. + * + * This actually is optimization, in the sense that we find the best possible + * Huffman table(s) for the given data. We first scan the supplied data and + * count the number of uses of each symbol that is to be Huffman-coded. + * (This process must agree with the code above.) Then we build an + * optimal Huffman coding tree for the observed counts. + * + * The JPEG standard requires Huffman codes to be no more than 16 bits long. + * If some symbols have a very small but nonzero probability, the Huffman tree + * must be adjusted to meet the code length restriction. We currently use + * the adjustment method suggested in the JPEG spec. This method is *not* + * optimal; it may not choose the best possible limited-length code. But + * since the symbols involved are infrequently used, it's not clear that + * going to extra trouble is worthwhile. + */ + +#ifdef ENTROPY_OPT_SUPPORTED + + +/* Process a single block's worth of coefficients */ + +LOCAL void +htest_one_block (JCOEFPTR block, int last_dc_val, + long dc_counts[], long ac_counts[]) +{ + register int temp; + register int nbits; + register int k, r; + + /* Encode the DC coefficient difference per section F.1.2.1 */ + + temp = block[0] - last_dc_val; + if (temp < 0) + temp = -temp; + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + + /* Count the Huffman symbol for the number of bits */ + dc_counts[nbits]++; + + /* Encode the AC coefficients per section F.1.2.2 */ + + r = 0; /* r = run length of zeros */ + + for (k = 1; k < DCTSIZE2; k++) { + if ((temp = block[jpeg_natural_order[k]]) == 0) { + r++; + } else { + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + ac_counts[0xF0]++; + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + if (temp < 0) + temp = -temp; + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + + /* Count Huffman symbol for run length / number of bits */ + ac_counts[(r << 4) + nbits]++; + + r = 0; + } + } + + /* If the last coef(s) were zero, emit an end-of-block code */ + if (r > 0) + ac_counts[0]++; +} + + +/* + * Trial-encode one MCU's worth of Huffman-compressed coefficients. + * No data is actually output, so no suspension return is possible. + */ + +METHODDEF boolean +encode_mcu_gather (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int blkn, ci; + jpeg_component_info * compptr; + + /* Take care of restart intervals if needed */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + /* Update restart state */ + entropy->restarts_to_go = cinfo->restart_interval; + } + entropy->restarts_to_go--; + } + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + htest_one_block(MCU_data[blkn][0], entropy->saved.last_dc_val[ci], + entropy->dc_count_ptrs[compptr->dc_tbl_no], + entropy->ac_count_ptrs[compptr->ac_tbl_no]); + entropy->saved.last_dc_val[ci] = MCU_data[blkn][0][0]; + } + + return TRUE; +} + + +/* + * Generate the optimal coding for the given counts, fill htbl. + * Note this is also used by jcphuff.c. + */ + +GLOBAL void +jpeg_gen_optimal_table (j_compress_ptr cinfo, JHUFF_TBL * htbl, long freq[]) +{ +#define MAX_CLEN 32 /* assumed maximum initial code length */ + UINT8 bits[MAX_CLEN+1]; /* bits[k] = # of symbols with code length k */ + int codesize[257]; /* codesize[k] = code length of symbol k */ + int others[257]; /* next symbol in current branch of tree */ + int c1, c2; + int p, i, j; + long v; + + /* This algorithm is explained in section K.2 of the JPEG standard */ + + MEMZERO(bits, SIZEOF(bits)); + MEMZERO(codesize, SIZEOF(codesize)); + for (i = 0; i < 257; i++) + others[i] = -1; /* init links to empty */ + + freq[256] = 1; /* make sure there is a nonzero count */ + /* Including the pseudo-symbol 256 in the Huffman procedure guarantees + * that no real symbol is given code-value of all ones, because 256 + * will be placed in the largest codeword category. + */ + + /* Huffman's basic algorithm to assign optimal code lengths to symbols */ + + for (;;) { + /* Find the smallest nonzero frequency, set c1 = its symbol */ + /* In case of ties, take the larger symbol number */ + c1 = -1; + v = 1000000000L; + for (i = 0; i <= 256; i++) { + if (freq[i] && freq[i] <= v) { + v = freq[i]; + c1 = i; + } + } + + /* Find the next smallest nonzero frequency, set c2 = its symbol */ + /* In case of ties, take the larger symbol number */ + c2 = -1; + v = 1000000000L; + for (i = 0; i <= 256; i++) { + if (freq[i] && freq[i] <= v && i != c1) { + v = freq[i]; + c2 = i; + } + } + + /* Done if we've merged everything into one frequency */ + if (c2 < 0) + break; + + /* Else merge the two counts/trees */ + freq[c1] += freq[c2]; + freq[c2] = 0; + + /* Increment the codesize of everything in c1's tree branch */ + codesize[c1]++; + while (others[c1] >= 0) { + c1 = others[c1]; + codesize[c1]++; + } + + others[c1] = c2; /* chain c2 onto c1's tree branch */ + + /* Increment the codesize of everything in c2's tree branch */ + codesize[c2]++; + while (others[c2] >= 0) { + c2 = others[c2]; + codesize[c2]++; + } + } + + /* Now count the number of symbols of each code length */ + for (i = 0; i <= 256; i++) { + if (codesize[i]) { + /* The JPEG standard seems to think that this can't happen, */ + /* but I'm paranoid... */ + if (codesize[i] > MAX_CLEN) + ERREXIT(cinfo, JERR_HUFF_CLEN_OVERFLOW); + + bits[codesize[i]]++; + } + } + + /* JPEG doesn't allow symbols with code lengths over 16 bits, so if the pure + * Huffman procedure assigned any such lengths, we must adjust the coding. + * Here is what the JPEG spec says about how this next bit works: + * Since symbols are paired for the longest Huffman code, the symbols are + * removed from this length category two at a time. The prefix for the pair + * (which is one bit shorter) is allocated to one of the pair; then, + * skipping the BITS entry for that prefix length, a code word from the next + * shortest nonzero BITS entry is converted into a prefix for two code words + * one bit longer. + */ + + for (i = MAX_CLEN; i > 16; i--) { + while (bits[i] > 0) { + j = i - 2; /* find length of new prefix to be used */ + while (bits[j] == 0) + j--; + + bits[i] -= 2; /* remove two symbols */ + bits[i-1]++; /* one goes in this length */ + bits[j+1] += 2; /* two new symbols in this length */ + bits[j]--; /* symbol of this length is now a prefix */ + } + } + + /* Remove the count for the pseudo-symbol 256 from the largest codelength */ + while (bits[i] == 0) /* find largest codelength still in use */ + i--; + bits[i]--; + + /* Return final symbol counts (only for lengths 0..16) */ + MEMCOPY(htbl->bits, bits, SIZEOF(htbl->bits)); + + /* Return a list of the symbols sorted by code length */ + /* It's not real clear to me why we don't need to consider the codelength + * changes made above, but the JPEG spec seems to think this works. + */ + p = 0; + for (i = 1; i <= MAX_CLEN; i++) { + for (j = 0; j <= 255; j++) { + if (codesize[j] == i) { + htbl->huffval[p] = (UINT8) j; + p++; + } + } + } + + /* Set sent_table FALSE so updated table will be written to JPEG file. */ + htbl->sent_table = FALSE; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF void +finish_pass_gather (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did_dc[NUM_HUFF_TBLS]; + boolean did_ac[NUM_HUFF_TBLS]; + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO(did_dc, SIZEOF(did_dc)); + MEMZERO(did_ac, SIZEOF(did_ac)); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + if (! did_dc[dctbl]) { + htblptr = & cinfo->dc_huff_tbl_ptrs[dctbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->dc_count_ptrs[dctbl]); + did_dc[dctbl] = TRUE; + } + if (! did_ac[actbl]) { + htblptr = & cinfo->ac_huff_tbl_ptrs[actbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->ac_count_ptrs[actbl]); + did_ac[actbl] = TRUE; + } + } +} + + +#endif /* ENTROPY_OPT_SUPPORTED */ + + +/* + * Module initialization routine for Huffman entropy encoding. + */ + +GLOBAL void +jinit_huff_encoder (j_compress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_encoder)); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_huff; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; +#ifdef ENTROPY_OPT_SUPPORTED + entropy->dc_count_ptrs[i] = entropy->ac_count_ptrs[i] = NULL; +#endif + } +} diff --git a/code/jpeg-6/jchuff.h b/code/jpeg-6/jchuff.h new file mode 100644 index 0000000..f43d571 --- /dev/null +++ b/code/jpeg-6/jchuff.h @@ -0,0 +1,34 @@ +/* + * jchuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy encoding routines + * that are shared between the sequential encoder (jchuff.c) and the + * progressive encoder (jcphuff.c). No other modules need to see these. + */ + +/* Derived data constructed for each Huffman table */ + +typedef struct { + unsigned int ehufco[256]; /* code for each symbol */ + char ehufsi[256]; /* length of code for each symbol */ + /* If no code has been allocated for a symbol S, ehufsi[S] contains 0 */ +} c_derived_tbl; + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_c_derived_tbl jMkCDerived +#define jpeg_gen_optimal_table jGenOptTbl +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Expand a Huffman table definition into the derived format */ +EXTERN void jpeg_make_c_derived_tbl JPP((j_compress_ptr cinfo, + JHUFF_TBL * htbl, c_derived_tbl ** pdtbl)); + +/* Generate an optimal table definition given the specified counts */ +EXTERN void jpeg_gen_optimal_table JPP((j_compress_ptr cinfo, + JHUFF_TBL * htbl, long freq[])); diff --git a/code/jpeg-6/jcinit.cpp b/code/jpeg-6/jcinit.cpp new file mode 100644 index 0000000..4bc8f1c --- /dev/null +++ b/code/jpeg-6/jcinit.cpp @@ -0,0 +1,79 @@ +/* + * jcinit.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains initialization logic for the JPEG compressor. + * This routine is in charge of selecting the modules to be executed and + * making an initialization call to each one. + * + * Logically, this code belongs in jcmaster.c. It's split out because + * linking this routine implies linking the entire compression library. + * For a transcoding-only application, we want to be able to use jcmaster.c + * without linking in the whole library. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Master selection of compression modules. + * This is done once at the start of processing an image. We determine + * which modules will be used and give them appropriate initialization calls. + */ + +GLOBAL void +jinit_compress_master (j_compress_ptr cinfo) +{ + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control(cinfo, FALSE /* full compression */); + + /* Preprocessing */ + if (! cinfo->raw_data_in) { + jinit_color_converter(cinfo); + jinit_downsampler(cinfo); + jinit_c_prep_controller(cinfo, FALSE /* never need full buffer here */); + } + /* Forward DCT */ + jinit_forward_dct(cinfo); + /* Entropy encoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_encoder(cinfo); + } + + /* Need a full-image coefficient buffer in any multi-pass mode. */ + jinit_c_coef_controller(cinfo, + (cinfo->num_scans > 1 || cinfo->optimize_coding)); + jinit_c_main_controller(cinfo, FALSE /* never need full buffer here */); + + jinit_marker_writer(cinfo); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Write the datastream header (SOI) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + (*cinfo->marker->write_file_header) (cinfo); +} diff --git a/code/jpeg-6/jcmainct.cpp b/code/jpeg-6/jcmainct.cpp new file mode 100644 index 0000000..8e3dc46 --- /dev/null +++ b/code/jpeg-6/jcmainct.cpp @@ -0,0 +1,302 @@ +/* + * jcmainct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for compression. + * The main buffer lies between the pre-processor and the JPEG + * compressor proper; it holds downsampled data in the JPEG colorspace. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Note: currently, there is no operating mode in which a full-image buffer + * is needed at this step. If there were, that mode could not be used with + * "raw data" input, since this module is bypassed in that case. However, + * we've left the code here for possible use in special applications. + */ +#undef FULL_MAIN_BUFFER_SUPPORTED + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_main_controller pub; /* public fields */ + + JDIMENSION cur_iMCU_row; /* number of current iMCU row */ + JDIMENSION rowgroup_ctr; /* counts row groups received in iMCU row */ + boolean suspended; /* remember if we suspended output */ + J_BUF_MODE pass_mode; /* current operating mode */ + + /* If using just a strip buffer, this points to the entire set of buffers + * (we allocate one for each component). In the full-image case, this + * points to the currently accessible strips of the virtual arrays. + */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* If using full-image storage, this array holds pointers to virtual-array + * control blocks for each component. Unused if not full-image storage. + */ + jvirt_sarray_ptr whole_image[MAX_COMPONENTS]; +#endif +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + + +/* Forward declarations */ +METHODDEF void process_data_simple_main + JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)); +#ifdef FULL_MAIN_BUFFER_SUPPORTED +METHODDEF void process_data_buffer_main + JPP((j_compress_ptr cinfo, JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, JDIMENSION in_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_main (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + // bk001204 - don't use main... + my_main_ptr jmain = (my_main_ptr) cinfo->main; + + /* Do nothing in raw-data mode. */ + if (cinfo->raw_data_in) + return; + + jmain->cur_iMCU_row = 0; /* initialize counters */ + jmain->rowgroup_ctr = 0; + jmain->suspended = FALSE; + jmain->pass_mode = pass_mode; /* save mode for use by process_data */ + + switch (pass_mode) { + case JBUF_PASS_THRU: +#ifdef FULL_MAIN_BUFFER_SUPPORTED + if (jmain->whole_image[0] != NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + jmain->pub.process_data = process_data_simple_main; + break; +#ifdef FULL_MAIN_BUFFER_SUPPORTED + case JBUF_SAVE_SOURCE: + case JBUF_CRANK_DEST: + case JBUF_SAVE_AND_PASS: + if (jmain->whole_image[0] == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + jmain->pub.process_data = process_data_buffer_main; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This routine handles the simple pass-through mode, + * where we have only a strip buffer. + */ + +METHODDEF void +process_data_simple_main (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail) +{ + // bk001204 - don't use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + + while (jmain->cur_iMCU_row < cinfo->total_iMCU_rows) { + /* Read input data if we haven't filled the main buffer yet */ + if (jmain->rowgroup_ctr < DCTSIZE) + (*cinfo->prep->pre_process_data) (cinfo, + input_buf, in_row_ctr, in_rows_avail, + jmain->buffer, &jmain->rowgroup_ctr, + (JDIMENSION) DCTSIZE); + + /* If we don't have a full iMCU row buffered, return to application for + * more data. Note that preprocessor will always pad to fill the iMCU row + * at the bottom of the image. + */ + if (jmain->rowgroup_ctr != DCTSIZE) + return; + + /* Send the completed row to the compressor */ + if (! (*cinfo->coef->compress_data) (cinfo, jmain->buffer)) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if (! jmain->suspended) { + (*in_row_ctr)--; + jmain->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if (jmain->suspended) { + (*in_row_ctr)++; + jmain->suspended = FALSE; + } + jmain->rowgroup_ctr = 0; + jmain->cur_iMCU_row++; + } +} + + +#ifdef FULL_MAIN_BUFFER_SUPPORTED + +/* + * Process some data. + * This routine handles all of the modes that use a full-size buffer. + */ + +METHODDEF void +process_data_buffer_main (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail) +{ + my_main_ptr main = (my_main_ptr) cinfo->main; + int ci; + jpeg_component_info *compptr; + boolean writing = (main->pass_mode != JBUF_CRANK_DEST); + + while (main->cur_iMCU_row < cinfo->total_iMCU_rows) { + /* Realign the virtual buffers if at the start of an iMCU row. */ + if (main->rowgroup_ctr == 0) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + main->buffer[ci] = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, main->whole_image[ci], + main->cur_iMCU_row * (compptr->v_samp_factor * DCTSIZE), + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE), writing); + } + /* In a read pass, pretend we just read some source data. */ + if (! writing) { + *in_row_ctr += cinfo->max_v_samp_factor * DCTSIZE; + main->rowgroup_ctr = DCTSIZE; + } + } + + /* If a write pass, read input data until the current iMCU row is full. */ + /* Note: preprocessor will pad if necessary to fill the last iMCU row. */ + if (writing) { + (*cinfo->prep->pre_process_data) (cinfo, + input_buf, in_row_ctr, in_rows_avail, + main->buffer, &main->rowgroup_ctr, + (JDIMENSION) DCTSIZE); + /* Return to application if we need more data to fill the iMCU row. */ + if (main->rowgroup_ctr < DCTSIZE) + return; + } + + /* Emit data, unless this is a sink-only pass. */ + if (main->pass_mode != JBUF_SAVE_SOURCE) { + if (! (*cinfo->coef->compress_data) (cinfo, main->buffer)) { + /* If compressor did not consume the whole row, then we must need to + * suspend processing and return to the application. In this situation + * we pretend we didn't yet consume the last input row; otherwise, if + * it happened to be the last row of the image, the application would + * think we were done. + */ + if (! main->suspended) { + (*in_row_ctr)--; + main->suspended = TRUE; + } + return; + } + /* We did finish the row. Undo our little suspension hack if a previous + * call suspended; then mark the main buffer empty. + */ + if (main->suspended) { + (*in_row_ctr)++; + main->suspended = FALSE; + } + } + + /* If get here, we are done with this iMCU row. Mark buffer empty. */ + main->rowgroup_ctr = 0; + main->cur_iMCU_row++; + } +} + +#endif /* FULL_MAIN_BUFFER_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL void +jinit_c_main_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + // bk001204 - don't use main + my_main_ptr jmain; + int ci; + jpeg_component_info *compptr; + + jmain = (my_main_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller)); + cinfo->main = (struct jpeg_c_main_controller *) jmain; + jmain->pub.start_pass = start_pass_main; + + /* We don't need to create a buffer in raw-data mode. */ + if (cinfo->raw_data_in) + return; + + /* Create the buffer. It holds downsampled data, so each component + * may be of a different size. + */ + if (need_full_buffer) { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + /* Allocate a full-image virtual array for each component */ + /* Note we pad the bottom to a multiple of the iMCU height */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + jmain->whole_image[ci] = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor) * DCTSIZE, + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE)); + } +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif + } else { +#ifdef FULL_MAIN_BUFFER_SUPPORTED + jmain->whole_image[0] = NULL; /* flag for no virtual arrays */ +#endif + /* Allocate a strip buffer for each component */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + jmain->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * DCTSIZE, + (JDIMENSION) (compptr->v_samp_factor * DCTSIZE)); + } + } +} diff --git a/code/jpeg-6/jcmarker.cpp b/code/jpeg-6/jcmarker.cpp new file mode 100644 index 0000000..5ac98a8 --- /dev/null +++ b/code/jpeg-6/jcmarker.cpp @@ -0,0 +1,645 @@ +/* + * jcmarker.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to write JPEG datastream markers. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Basic output routines. + * + * Note that we do not support suspension while writing a marker. + * Therefore, an application using suspension must ensure that there is + * enough buffer space for the initial markers (typ. 600-700 bytes) before + * calling jpeg_start_compress, and enough space to write the trailing EOI + * (a few bytes) before calling jpeg_finish_compress. Multipass compression + * modes are not supported at all with suspension, so those two are the only + * points where markers will be written. + */ + +LOCAL void +emit_byte (j_compress_ptr cinfo, int val) +/* Emit a byte */ +{ + struct jpeg_destination_mgr * dest = cinfo->dest; + + *(dest->next_output_byte)++ = (JOCTET) val; + if (--dest->free_in_buffer == 0) { + if (! (*dest->empty_output_buffer) (cinfo)) + ERREXIT(cinfo, JERR_CANT_SUSPEND); + } +} + + +LOCAL void +emit_marker (j_compress_ptr cinfo, JPEG_MARKER mark) +/* Emit a marker code */ +{ + emit_byte(cinfo, 0xFF); + emit_byte(cinfo, (int) mark); +} + + +LOCAL void +emit_2bytes (j_compress_ptr cinfo, int value) +/* Emit a 2-byte integer; these are always MSB first in JPEG files */ +{ + emit_byte(cinfo, (value >> 8) & 0xFF); + emit_byte(cinfo, value & 0xFF); +} + + +/* + * Routines to write specific marker types. + */ + +LOCAL int +emit_dqt (j_compress_ptr cinfo, int index) +/* Emit a DQT marker */ +/* Returns the precision used (0 = 8bits, 1 = 16bits) for baseline checking */ +{ + JQUANT_TBL * qtbl = cinfo->quant_tbl_ptrs[index]; + int prec; + int i; + + if (qtbl == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, index); + + prec = 0; + for (i = 0; i < DCTSIZE2; i++) { + if (qtbl->quantval[i] > 255) + prec = 1; + } + + if (! qtbl->sent_table) { + emit_marker(cinfo, M_DQT); + + emit_2bytes(cinfo, prec ? DCTSIZE2*2 + 1 + 2 : DCTSIZE2 + 1 + 2); + + emit_byte(cinfo, index + (prec<<4)); + + for (i = 0; i < DCTSIZE2; i++) { + if (prec) + emit_byte(cinfo, qtbl->quantval[i] >> 8); + emit_byte(cinfo, qtbl->quantval[i] & 0xFF); + } + + qtbl->sent_table = TRUE; + } + + return prec; +} + + +LOCAL void +emit_dht (j_compress_ptr cinfo, int index, boolean is_ac) +/* Emit a DHT marker */ +{ + JHUFF_TBL * htbl; + int length, i; + + if (is_ac) { + htbl = cinfo->ac_huff_tbl_ptrs[index]; + index += 0x10; /* output index has AC bit set */ + } else { + htbl = cinfo->dc_huff_tbl_ptrs[index]; + } + + if (htbl == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, index); + + if (! htbl->sent_table) { + emit_marker(cinfo, M_DHT); + + length = 0; + for (i = 1; i <= 16; i++) + length += htbl->bits[i]; + + emit_2bytes(cinfo, length + 2 + 1 + 16); + emit_byte(cinfo, index); + + for (i = 1; i <= 16; i++) + emit_byte(cinfo, htbl->bits[i]); + + for (i = 0; i < length; i++) + emit_byte(cinfo, htbl->huffval[i]); + + htbl->sent_table = TRUE; + } +} + + +LOCAL void +emit_dac (j_compress_ptr cinfo) +/* Emit a DAC marker */ +/* Since the useful info is so small, we want to emit all the tables in */ +/* one DAC marker. Therefore this routine does its own scan of the table. */ +{ +#ifdef C_ARITH_CODING_SUPPORTED + char dc_in_use[NUM_ARITH_TBLS]; + char ac_in_use[NUM_ARITH_TBLS]; + int length, i; + jpeg_component_info *compptr; + + for (i = 0; i < NUM_ARITH_TBLS; i++) + dc_in_use[i] = ac_in_use[i] = 0; + + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + dc_in_use[compptr->dc_tbl_no] = 1; + ac_in_use[compptr->ac_tbl_no] = 1; + } + + length = 0; + for (i = 0; i < NUM_ARITH_TBLS; i++) + length += dc_in_use[i] + ac_in_use[i]; + + emit_marker(cinfo, M_DAC); + + emit_2bytes(cinfo, length*2 + 2); + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + if (dc_in_use[i]) { + emit_byte(cinfo, i); + emit_byte(cinfo, cinfo->arith_dc_L[i] + (cinfo->arith_dc_U[i]<<4)); + } + if (ac_in_use[i]) { + emit_byte(cinfo, i + 0x10); + emit_byte(cinfo, cinfo->arith_ac_K[i]); + } + } +#endif /* C_ARITH_CODING_SUPPORTED */ +} + + +LOCAL void +emit_dri (j_compress_ptr cinfo) +/* Emit a DRI marker */ +{ + emit_marker(cinfo, M_DRI); + + emit_2bytes(cinfo, 4); /* fixed length */ + + emit_2bytes(cinfo, (int) cinfo->restart_interval); +} + + +LOCAL void +emit_sof (j_compress_ptr cinfo, JPEG_MARKER code) +/* Emit a SOF marker */ +{ + int ci; + jpeg_component_info *compptr; + + emit_marker(cinfo, code); + + emit_2bytes(cinfo, 3 * cinfo->num_components + 2 + 5 + 1); /* length */ + + /* Make sure image isn't bigger than SOF field can handle */ + if ((long) cinfo->image_height > 65535L || + (long) cinfo->image_width > 65535L) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) 65535); + + emit_byte(cinfo, cinfo->data_precision); + emit_2bytes(cinfo, (int) cinfo->image_height); + emit_2bytes(cinfo, (int) cinfo->image_width); + + emit_byte(cinfo, cinfo->num_components); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + emit_byte(cinfo, compptr->component_id); + emit_byte(cinfo, (compptr->h_samp_factor << 4) + compptr->v_samp_factor); + emit_byte(cinfo, compptr->quant_tbl_no); + } +} + + +LOCAL void +emit_sos (j_compress_ptr cinfo) +/* Emit a SOS marker */ +{ + int i, td, ta; + jpeg_component_info *compptr; + + emit_marker(cinfo, M_SOS); + + emit_2bytes(cinfo, 2 * cinfo->comps_in_scan + 2 + 1 + 3); /* length */ + + emit_byte(cinfo, cinfo->comps_in_scan); + + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + emit_byte(cinfo, compptr->component_id); + td = compptr->dc_tbl_no; + ta = compptr->ac_tbl_no; + if (cinfo->progressive_mode) { + /* Progressive mode: only DC or only AC tables are used in one scan; + * furthermore, Huffman coding of DC refinement uses no table at all. + * We emit 0 for unused field(s); this is recommended by the P&M text + * but does not seem to be specified in the standard. + */ + if (cinfo->Ss == 0) { + ta = 0; /* DC scan */ + if (cinfo->Ah != 0 && !cinfo->arith_code) + td = 0; /* no DC table either */ + } else { + td = 0; /* AC scan */ + } + } + emit_byte(cinfo, (td << 4) + ta); + } + + emit_byte(cinfo, cinfo->Ss); + emit_byte(cinfo, cinfo->Se); + emit_byte(cinfo, (cinfo->Ah << 4) + cinfo->Al); +} + + +LOCAL void +emit_jfif_app0 (j_compress_ptr cinfo) +/* Emit a JFIF-compliant APP0 marker */ +{ + /* + * Length of APP0 block (2 bytes) + * Block ID (4 bytes - ASCII "JFIF") + * Zero byte (1 byte to terminate the ID string) + * Version Major, Minor (2 bytes - 0x01, 0x01) + * Units (1 byte - 0x00 = none, 0x01 = inch, 0x02 = cm) + * Xdpu (2 bytes - dots per unit horizontal) + * Ydpu (2 bytes - dots per unit vertical) + * Thumbnail X size (1 byte) + * Thumbnail Y size (1 byte) + */ + + emit_marker(cinfo, M_APP0); + + emit_2bytes(cinfo, 2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1); /* length */ + + emit_byte(cinfo, 0x4A); /* Identifier: ASCII "JFIF" */ + emit_byte(cinfo, 0x46); + emit_byte(cinfo, 0x49); + emit_byte(cinfo, 0x46); + emit_byte(cinfo, 0); + /* We currently emit version code 1.01 since we use no 1.02 features. + * This may avoid complaints from some older decoders. + */ + emit_byte(cinfo, 1); /* Major version */ + emit_byte(cinfo, 1); /* Minor version */ + emit_byte(cinfo, cinfo->density_unit); /* Pixel size information */ + emit_2bytes(cinfo, (int) cinfo->X_density); + emit_2bytes(cinfo, (int) cinfo->Y_density); + emit_byte(cinfo, 0); /* No thumbnail image */ + emit_byte(cinfo, 0); +} + + +LOCAL void +emit_adobe_app14 (j_compress_ptr cinfo) +/* Emit an Adobe APP14 marker */ +{ + /* + * Length of APP14 block (2 bytes) + * Block ID (5 bytes - ASCII "Adobe") + * Version Number (2 bytes - currently 100) + * Flags0 (2 bytes - currently 0) + * Flags1 (2 bytes - currently 0) + * Color transform (1 byte) + * + * Although Adobe TN 5116 mentions Version = 101, all the Adobe files + * now in circulation seem to use Version = 100, so that's what we write. + * + * We write the color transform byte as 1 if the JPEG color space is + * YCbCr, 2 if it's YCCK, 0 otherwise. Adobe's definition has to do with + * whether the encoder performed a transformation, which is pretty useless. + */ + + emit_marker(cinfo, M_APP14); + + emit_2bytes(cinfo, 2 + 5 + 2 + 2 + 2 + 1); /* length */ + + emit_byte(cinfo, 0x41); /* Identifier: ASCII "Adobe" */ + emit_byte(cinfo, 0x64); + emit_byte(cinfo, 0x6F); + emit_byte(cinfo, 0x62); + emit_byte(cinfo, 0x65); + emit_2bytes(cinfo, 100); /* Version */ + emit_2bytes(cinfo, 0); /* Flags0 */ + emit_2bytes(cinfo, 0); /* Flags1 */ + switch (cinfo->jpeg_color_space) { + case JCS_YCbCr: + emit_byte(cinfo, 1); /* Color transform = 1 */ + break; + case JCS_YCCK: + emit_byte(cinfo, 2); /* Color transform = 2 */ + break; + default: + emit_byte(cinfo, 0); /* Color transform = 0 */ + break; + } +} + + +/* + * This routine is exported for possible use by applications. + * The intended use is to emit COM or APPn markers after calling + * jpeg_start_compress() and before the first jpeg_write_scanlines() call + * (hence, after write_file_header but before write_frame_header). + * Other uses are not guaranteed to produce desirable results. + */ + +METHODDEF void +write_any_marker (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen) +/* Emit an arbitrary marker with parameters */ +{ + if (datalen <= (unsigned int) 65533) { /* safety check */ + emit_marker(cinfo, (JPEG_MARKER) marker); + + emit_2bytes(cinfo, (int) (datalen + 2)); /* total length */ + + while (datalen--) { + emit_byte(cinfo, *dataptr); + dataptr++; + } + } +} + + +/* + * Write datastream header. + * This consists of an SOI and optional APPn markers. + * We recommend use of the JFIF marker, but not the Adobe marker, + * when using YCbCr or grayscale data. The JFIF marker should NOT + * be used for any other JPEG colorspace. The Adobe marker is helpful + * to distinguish RGB, CMYK, and YCCK colorspaces. + * Note that an application can write additional header markers after + * jpeg_start_compress returns. + */ + +METHODDEF void +write_file_header (j_compress_ptr cinfo) +{ + emit_marker(cinfo, M_SOI); /* first the SOI */ + + if (cinfo->write_JFIF_header) /* next an optional JFIF APP0 */ + emit_jfif_app0(cinfo); + if (cinfo->write_Adobe_marker) /* next an optional Adobe APP14 */ + emit_adobe_app14(cinfo); +} + + +/* + * Write frame header. + * This consists of DQT and SOFn markers. + * Note that we do not emit the SOF until we have emitted the DQT(s). + * This avoids compatibility problems with incorrect implementations that + * try to error-check the quant table numbers as soon as they see the SOF. + */ + +METHODDEF void +write_frame_header (j_compress_ptr cinfo) +{ + int ci, prec; + boolean is_baseline; + jpeg_component_info *compptr; + + /* Emit DQT for each quantization table. + * Note that emit_dqt() suppresses any duplicate tables. + */ + prec = 0; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + prec += emit_dqt(cinfo, compptr->quant_tbl_no); + } + /* now prec is nonzero iff there are any 16-bit quant tables. */ + + /* Check for a non-baseline specification. + * Note we assume that Huffman table numbers won't be changed later. + */ + if (cinfo->arith_code || cinfo->progressive_mode || + cinfo->data_precision != 8) { + is_baseline = FALSE; + } else { + is_baseline = TRUE; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->dc_tbl_no > 1 || compptr->ac_tbl_no > 1) + is_baseline = FALSE; + } + if (prec && is_baseline) { + is_baseline = FALSE; + /* If it's baseline except for quantizer size, warn the user */ + TRACEMS(cinfo, 0, JTRC_16BIT_TABLES); + } + } + + /* Emit the proper SOF marker */ + if (cinfo->arith_code) { + emit_sof(cinfo, M_SOF9); /* SOF code for arithmetic coding */ + } else { + if (cinfo->progressive_mode) + emit_sof(cinfo, M_SOF2); /* SOF code for progressive Huffman */ + else if (is_baseline) + emit_sof(cinfo, M_SOF0); /* SOF code for baseline implementation */ + else + emit_sof(cinfo, M_SOF1); /* SOF code for non-baseline Huffman file */ + } +} + + +/* + * Write scan header. + * This consists of DHT or DAC markers, optional DRI, and SOS. + * Compressed data will be written following the SOS. + */ + +METHODDEF void +write_scan_header (j_compress_ptr cinfo) +{ + int i; + jpeg_component_info *compptr; + + if (cinfo->arith_code) { + /* Emit arith conditioning info. We may have some duplication + * if the file has multiple scans, but it's so small it's hardly + * worth worrying about. + */ + emit_dac(cinfo); + } else { + /* Emit Huffman tables. + * Note that emit_dht() suppresses any duplicate tables. + */ + for (i = 0; i < cinfo->comps_in_scan; i++) { + compptr = cinfo->cur_comp_info[i]; + if (cinfo->progressive_mode) { + /* Progressive mode: only DC or only AC tables are used in one scan */ + if (cinfo->Ss == 0) { + if (cinfo->Ah == 0) /* DC needs no table for refinement scan */ + emit_dht(cinfo, compptr->dc_tbl_no, FALSE); + } else { + emit_dht(cinfo, compptr->ac_tbl_no, TRUE); + } + } else { + /* Sequential mode: need both DC and AC tables */ + emit_dht(cinfo, compptr->dc_tbl_no, FALSE); + emit_dht(cinfo, compptr->ac_tbl_no, TRUE); + } + } + } + + /* Emit DRI if required --- note that DRI value could change for each scan. + * If it doesn't, a tiny amount of space is wasted in multiple-scan files. + * We assume DRI will never be nonzero for one scan and zero for a later one. + */ + if (cinfo->restart_interval) + emit_dri(cinfo); + + emit_sos(cinfo); +} + + +/* + * Write datastream trailer. + */ + +METHODDEF void +write_file_trailer (j_compress_ptr cinfo) +{ + emit_marker(cinfo, M_EOI); +} + + +/* + * Write an abbreviated table-specification datastream. + * This consists of SOI, DQT and DHT tables, and EOI. + * Any table that is defined and not marked sent_table = TRUE will be + * emitted. Note that all tables will be marked sent_table = TRUE at exit. + */ + +METHODDEF void +write_tables_only (j_compress_ptr cinfo) +{ + int i; + + emit_marker(cinfo, M_SOI); + + for (i = 0; i < NUM_QUANT_TBLS; i++) { + if (cinfo->quant_tbl_ptrs[i] != NULL) + (void) emit_dqt(cinfo, i); + } + + if (! cinfo->arith_code) { + for (i = 0; i < NUM_HUFF_TBLS; i++) { + if (cinfo->dc_huff_tbl_ptrs[i] != NULL) + emit_dht(cinfo, i, FALSE); + if (cinfo->ac_huff_tbl_ptrs[i] != NULL) + emit_dht(cinfo, i, TRUE); + } + } + + emit_marker(cinfo, M_EOI); +} + + +/* + * Initialize the marker writer module. + */ + +GLOBAL void +jinit_marker_writer (j_compress_ptr cinfo) +{ + /* Create the subobject */ + cinfo->marker = (struct jpeg_marker_writer *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(struct jpeg_marker_writer)); + /* Initialize method pointers */ + cinfo->marker->write_any_marker = write_any_marker; + cinfo->marker->write_file_header = write_file_header; + cinfo->marker->write_frame_header = write_frame_header; + cinfo->marker->write_scan_header = write_scan_header; + cinfo->marker->write_file_trailer = write_file_trailer; + cinfo->marker->write_tables_only = write_tables_only; +} diff --git a/code/jpeg-6/jcmaster.cpp b/code/jpeg-6/jcmaster.cpp new file mode 100644 index 0000000..c896134 --- /dev/null +++ b/code/jpeg-6/jcmaster.cpp @@ -0,0 +1,584 @@ +/* + * jcmaster.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG compressor. + * These routines are concerned with parameter validation, initial setup, + * and inter-pass control (determining the number of passes and the work + * to be done in each pass). + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef enum { + main_pass, /* input data, also do first output step */ + huff_opt_pass, /* Huffman code optimization pass */ + output_pass /* data output pass */ +} c_pass_type; + +typedef struct { + struct jpeg_comp_master pub; /* public fields */ + + c_pass_type pass_type; /* the type of the current pass */ + + int pass_number; /* # of passes completed */ + int total_passes; /* total # of passes needed */ + + int scan_number; /* current index in scan_info[] */ +} my_comp_master; + +typedef my_comp_master * my_master_ptr; + + +/* + * Support routines that do various essential calculations. + */ + +LOCAL void +initial_setup (j_compress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ + int ci; + jpeg_component_info *compptr; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Sanity check on image dimensions */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0 || cinfo->input_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* Width of an input scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->image_width * (long) cinfo->input_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Fill in the correct component_index value; don't rely on application */ + compptr->component_index = ci; + /* For compression, we never do DCT scaling. */ + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor); + /* Mark component needed (this flag isn't actually used for compression) */ + compptr->component_needed = TRUE; + } + + /* Compute number of fully interleaved MCU rows (number of times that + * main controller will call coefficient controller). + */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); +} + + +#ifdef C_MULTISCAN_FILES_SUPPORTED + +LOCAL void +validate_script (j_compress_ptr cinfo) +/* Verify that the scan script in cinfo->scan_info[] is valid; also + * determine whether it uses progressive JPEG, and set cinfo->progressive_mode. + */ +{ + const jpeg_scan_info * scanptr; + int scanno, ncomps, ci, coefi, thisi; + int Ss, Se, Ah, Al; + boolean component_sent[MAX_COMPONENTS]; +#ifdef C_PROGRESSIVE_SUPPORTED + int * last_bitpos_ptr; + int last_bitpos[MAX_COMPONENTS][DCTSIZE2]; + /* -1 until that coefficient has been seen; then last Al for it */ +#endif + + if (cinfo->num_scans <= 0) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, 0); + + /* For sequential JPEG, all scans must have Ss=0, Se=DCTSIZE2-1; + * for progressive JPEG, no scan can have this. + */ + scanptr = cinfo->scan_info; + if (scanptr->Ss != 0 || scanptr->Se != DCTSIZE2-1) { +#ifdef C_PROGRESSIVE_SUPPORTED + cinfo->progressive_mode = TRUE; + last_bitpos_ptr = & last_bitpos[0][0]; + for (ci = 0; ci < cinfo->num_components; ci++) + for (coefi = 0; coefi < DCTSIZE2; coefi++) + *last_bitpos_ptr++ = -1; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + cinfo->progressive_mode = FALSE; + for (ci = 0; ci < cinfo->num_components; ci++) + component_sent[ci] = FALSE; + } + + for (scanno = 1; scanno <= cinfo->num_scans; scanptr++, scanno++) { + /* Validate component indexes */ + ncomps = scanptr->comps_in_scan; + if (ncomps <= 0 || ncomps > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, ncomps, MAX_COMPS_IN_SCAN); + for (ci = 0; ci < ncomps; ci++) { + thisi = scanptr->component_index[ci]; + if (thisi < 0 || thisi >= cinfo->num_components) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + /* Components must appear in SOF order within each scan */ + if (ci > 0 && thisi <= scanptr->component_index[ci-1]) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + } + /* Validate progression parameters */ + Ss = scanptr->Ss; + Se = scanptr->Se; + Ah = scanptr->Ah; + Al = scanptr->Al; + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + if (Ss < 0 || Ss >= DCTSIZE2 || Se < Ss || Se >= DCTSIZE2 || + Ah < 0 || Ah > 13 || Al < 0 || Al > 13) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + if (Ss == 0) { + if (Se != 0) /* DC and AC together not OK */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } else { + if (ncomps != 1) /* AC scans must be for only one component */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } + for (ci = 0; ci < ncomps; ci++) { + last_bitpos_ptr = & last_bitpos[scanptr->component_index[ci]][0]; + if (Ss != 0 && last_bitpos_ptr[0] < 0) /* AC without prior DC scan */ + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + for (coefi = Ss; coefi <= Se; coefi++) { + if (last_bitpos_ptr[coefi] < 0) { + /* first scan of this coefficient */ + if (Ah != 0) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } else { + /* not first scan */ + if (Ah != last_bitpos_ptr[coefi] || Al != Ah-1) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + } + last_bitpos_ptr[coefi] = Al; + } + } +#endif + } else { + /* For sequential JPEG, all progression parameters must be these: */ + if (Ss != 0 || Se != DCTSIZE2-1 || Ah != 0 || Al != 0) + ERREXIT1(cinfo, JERR_BAD_PROG_SCRIPT, scanno); + /* Make sure components are not sent twice */ + for (ci = 0; ci < ncomps; ci++) { + thisi = scanptr->component_index[ci]; + if (component_sent[thisi]) + ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, scanno); + component_sent[thisi] = TRUE; + } + } + } + + /* Now verify that everything got sent. */ + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + /* For progressive mode, we only check that at least some DC data + * got sent for each component; the spec does not require that all bits + * of all coefficients be transmitted. Would it be wiser to enforce + * transmission of all coefficient bits?? + */ + for (ci = 0; ci < cinfo->num_components; ci++) { + if (last_bitpos[ci][0] < 0) + ERREXIT(cinfo, JERR_MISSING_DATA); + } +#endif + } else { + for (ci = 0; ci < cinfo->num_components; ci++) { + if (! component_sent[ci]) + ERREXIT(cinfo, JERR_MISSING_DATA); + } + } +} + +#endif /* C_MULTISCAN_FILES_SUPPORTED */ + + +LOCAL void +select_scan_parameters (j_compress_ptr cinfo) +/* Set up the scan parameters for the current scan */ +{ + int ci; + +#ifdef C_MULTISCAN_FILES_SUPPORTED + if (cinfo->scan_info != NULL) { + /* Prepare for current scan --- the script is already validated */ + my_master_ptr master = (my_master_ptr) cinfo->master; + const jpeg_scan_info * scanptr = cinfo->scan_info + master->scan_number; + + cinfo->comps_in_scan = scanptr->comps_in_scan; + for (ci = 0; ci < scanptr->comps_in_scan; ci++) { + cinfo->cur_comp_info[ci] = + &cinfo->comp_info[scanptr->component_index[ci]]; + } + cinfo->Ss = scanptr->Ss; + cinfo->Se = scanptr->Se; + cinfo->Ah = scanptr->Ah; + cinfo->Al = scanptr->Al; + } + else +#endif + { + /* Prepare for single sequential-JPEG scan containing all components */ + if (cinfo->num_components > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPS_IN_SCAN); + cinfo->comps_in_scan = cinfo->num_components; + for (ci = 0; ci < cinfo->num_components; ci++) { + cinfo->cur_comp_info[ci] = &cinfo->comp_info[ci]; + } + cinfo->Ss = 0; + cinfo->Se = DCTSIZE2-1; + cinfo->Ah = 0; + cinfo->Al = 0; + } +} + + +LOCAL void +per_scan_setup (j_compress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] are already set */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = DCTSIZE; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, + (long) (cinfo->max_h_samp_factor*DCTSIZE)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * DCTSIZE; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > C_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } + + /* Convert restart specified in rows to actual MCU count. */ + /* Note that count must fit in 16 bits, so we provide limiting. */ + if (cinfo->restart_in_rows > 0) { + long nominal = (long) cinfo->restart_in_rows * (long) cinfo->MCUs_per_row; + cinfo->restart_interval = (unsigned int) MIN(nominal, 65535L); + } +} + + +/* + * Per-pass setup. + * This is called at the beginning of each pass. We determine which modules + * will be active during this pass and give them appropriate start_pass calls. + * We also set is_last_pass to indicate whether any more passes will be + * required. + */ + +METHODDEF void +prepare_for_pass (j_compress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + switch (master->pass_type) { + case main_pass: + /* Initial pass: will collect input data, and do either Huffman + * optimization or data output for the first scan. + */ + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + if (! cinfo->raw_data_in) { + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->downsample->start_pass) (cinfo); + (*cinfo->prep->start_pass) (cinfo, JBUF_PASS_THRU); + } + (*cinfo->fdct->start_pass) (cinfo); + (*cinfo->entropy->start_pass) (cinfo, cinfo->optimize_coding); + (*cinfo->coef->start_pass) (cinfo, + (master->total_passes > 1 ? + JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + if (cinfo->optimize_coding) { + /* No immediate data output; postpone writing frame/scan headers */ + master->pub.call_pass_startup = FALSE; + } else { + /* Will write frame/scan headers at first jpeg_write_scanlines call */ + master->pub.call_pass_startup = TRUE; + } + break; +#ifdef ENTROPY_OPT_SUPPORTED + case huff_opt_pass: + /* Do Huffman optimization for a scan after the first one. */ + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + if (cinfo->Ss != 0 || cinfo->Ah == 0 || cinfo->arith_code) { + (*cinfo->entropy->start_pass) (cinfo, TRUE); + (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); + master->pub.call_pass_startup = FALSE; + break; + } + /* Special case: Huffman DC refinement scans need no Huffman table + * and therefore we can skip the optimization pass for them. + */ + master->pass_type = output_pass; + master->pass_number++; + /*FALLTHROUGH*/ +#endif + case output_pass: + /* Do a data-output pass. */ + /* We need not repeat per-scan setup if prior optimization pass did it. */ + if (! cinfo->optimize_coding) { + select_scan_parameters(cinfo); + per_scan_setup(cinfo); + } + (*cinfo->entropy->start_pass) (cinfo, FALSE); + (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); + /* We emit frame/scan headers now */ + if (master->scan_number == 0) + (*cinfo->marker->write_frame_header) (cinfo); + (*cinfo->marker->write_scan_header) (cinfo); + master->pub.call_pass_startup = FALSE; + break; + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + } + + master->pub.is_last_pass = (master->pass_number == master->total_passes-1); + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->total_passes; + } +} + + +/* + * Special start-of-pass hook. + * This is called by jpeg_write_scanlines if call_pass_startup is TRUE. + * In single-pass processing, we need this hook because we don't want to + * write frame/scan headers during jpeg_start_compress; we want to let the + * application write COM markers etc. between jpeg_start_compress and the + * jpeg_write_scanlines loop. + * In multi-pass processing, this routine is not used. + */ + +METHODDEF void +pass_startup (j_compress_ptr cinfo) +{ + cinfo->master->call_pass_startup = FALSE; /* reset flag so call only once */ + + (*cinfo->marker->write_frame_header) (cinfo); + (*cinfo->marker->write_scan_header) (cinfo); +} + + +/* + * Finish up at end of pass. + */ + +METHODDEF void +finish_pass_master (j_compress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* The entropy coder always needs an end-of-pass call, + * either to analyze statistics or to flush its output buffer. + */ + (*cinfo->entropy->finish_pass) (cinfo); + + /* Update state for next pass */ + switch (master->pass_type) { + case main_pass: + /* next pass is either output of scan 0 (after optimization) + * or output of scan 1 (if no optimization). + */ + master->pass_type = output_pass; + if (! cinfo->optimize_coding) + master->scan_number++; + break; + case huff_opt_pass: + /* next pass is always output of current scan */ + master->pass_type = output_pass; + break; + case output_pass: + /* next pass is either optimization or output of next scan */ + if (cinfo->optimize_coding) + master->pass_type = huff_opt_pass; + master->scan_number++; + break; + } + + master->pass_number++; +} + + +/* + * Initialize master compression control. + */ + +GLOBAL void +jinit_c_master_control (j_compress_ptr cinfo, boolean transcode_only) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_comp_master)); + cinfo->master = (struct jpeg_comp_master *) master; + master->pub.prepare_for_pass = prepare_for_pass; + master->pub.pass_startup = pass_startup; + master->pub.finish_pass = finish_pass_master; + master->pub.is_last_pass = FALSE; + + /* Validate parameters, determine derived values */ + initial_setup(cinfo); + + if (cinfo->scan_info != NULL) { +#ifdef C_MULTISCAN_FILES_SUPPORTED + validate_script(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + cinfo->progressive_mode = FALSE; + cinfo->num_scans = 1; + } + + if (cinfo->progressive_mode) /* TEMPORARY HACK ??? */ + cinfo->optimize_coding = TRUE; /* assume default tables no good for progressive mode */ + + /* Initialize my private state */ + if (transcode_only) { + /* no main pass in transcoding */ + if (cinfo->optimize_coding) + master->pass_type = huff_opt_pass; + else + master->pass_type = output_pass; + } else { + /* for normal compression, first pass is always this type: */ + master->pass_type = main_pass; + } + master->scan_number = 0; + master->pass_number = 0; + if (cinfo->optimize_coding) + master->total_passes = cinfo->num_scans * 2; + else + master->total_passes = cinfo->num_scans; +} diff --git a/code/jpeg-6/jcomapi.cpp b/code/jpeg-6/jcomapi.cpp new file mode 100644 index 0000000..faa70c7 --- /dev/null +++ b/code/jpeg-6/jcomapi.cpp @@ -0,0 +1,100 @@ +/* + * jcomapi.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface routines that are used for both + * compression and decompression. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Abort processing of a JPEG compression or decompression operation, + * but don't destroy the object itself. + * + * For this, we merely clean up all the nonpermanent memory pools. + * Note that temp files (virtual arrays) are not allowed to belong to + * the permanent pool, so we will be able to close all temp files here. + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL void +jpeg_abort (j_common_ptr cinfo) +{ + int pool; + + /* Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool > JPOOL_PERMANENT; pool--) { + (*cinfo->mem->free_pool) (cinfo, pool); + } + + /* Reset overall state for possible reuse of object */ + cinfo->global_state = (cinfo->is_decompressor ? DSTATE_START : CSTATE_START); +} + + +/* + * Destruction of a JPEG object. + * + * Everything gets deallocated except the master jpeg_compress_struct itself + * and the error manager struct. Both of these are supplied by the application + * and must be freed, if necessary, by the application. (Often they are on + * the stack and so don't need to be freed anyway.) + * Closing a data source or destination, if necessary, is the application's + * responsibility. + */ + +GLOBAL void +jpeg_destroy (j_common_ptr cinfo) +{ + /* We need only tell the memory manager to release everything. */ + /* NB: mem pointer is NULL if memory mgr failed to initialize. */ + if (cinfo->mem != NULL) + (*cinfo->mem->self_destruct) (cinfo); + cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ + cinfo->global_state = 0; /* mark it destroyed */ +} + + +/* + * Convenience routines for allocating quantization and Huffman tables. + * (Would jutils.c be a more reasonable place to put these?) + */ + +GLOBAL JQUANT_TBL * +jpeg_alloc_quant_table (j_common_ptr cinfo) +{ + JQUANT_TBL *tbl; + + tbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JQUANT_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} + + +GLOBAL JHUFF_TBL * +jpeg_alloc_huff_table (j_common_ptr cinfo) +{ + JHUFF_TBL *tbl; + + tbl = (JHUFF_TBL *) + (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JHUFF_TBL)); + tbl->sent_table = FALSE; /* make sure this is false in any new table */ + return tbl; +} diff --git a/code/jpeg-6/jconfig.h b/code/jpeg-6/jconfig.h new file mode 100644 index 0000000..7d2f733 --- /dev/null +++ b/code/jpeg-6/jconfig.h @@ -0,0 +1,41 @@ +/* jconfig.wat --- jconfig.h for Watcom C/C++ on MS-DOS or OS/2. */ +/* see jconfig.doc for explanations */ + +#define HAVE_PROTOTYPES +#define HAVE_UNSIGNED_CHAR +#define HAVE_UNSIGNED_SHORT +/* #define void char */ +/* #define const */ +#define CHAR_IS_UNSIGNED +#define HAVE_STDDEF_H +#define HAVE_STDLIB_H +#undef NEED_BSD_STRINGS +#undef NEED_SYS_TYPES_H +#undef NEED_FAR_POINTERS /* Watcom uses flat 32-bit addressing */ +#undef NEED_SHORT_EXTERNAL_NAMES +#undef INCOMPLETE_TYPES_BROKEN + +#define JDCT_DEFAULT JDCT_FLOAT +#define JDCT_FASTEST JDCT_FLOAT + +#ifdef JPEG_INTERNALS + +#undef RIGHT_SHIFT_IS_UNSIGNED + +#endif /* JPEG_INTERNALS */ + +#ifdef JPEG_CJPEG_DJPEG + +#define BMP_SUPPORTED /* BMP image file format */ +#define GIF_SUPPORTED /* GIF image file format */ +#define PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ +#undef RLE_SUPPORTED /* Utah RLE image file format */ +#define TARGA_SUPPORTED /* Targa image file format */ + +#undef TWO_FILE_COMMANDLINE /* optional */ +#define USE_SETMODE /* Needed to make one-file style work in Watcom */ +#undef NEED_SIGNAL_CATCHER /* Define this if you use jmemname.c */ +#undef DONT_USE_B_MODE +#undef PROGRESS_REPORT /* optional */ + +#endif /* JPEG_CJPEG_DJPEG */ diff --git a/code/jpeg-6/jcparam.cpp b/code/jpeg-6/jcparam.cpp new file mode 100644 index 0000000..a60e665 --- /dev/null +++ b/code/jpeg-6/jcparam.cpp @@ -0,0 +1,580 @@ +/* + * jcparam.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains optional default-setting code for the JPEG compressor. + * Applications do not have to use this file, but those that don't use it + * must know a lot more about the innards of the JPEG code. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Quantization table setup routines + */ + +GLOBAL void +jpeg_add_quant_table (j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, boolean force_baseline) +/* Define a quantization table equal to the basic_table times + * a scale factor (given as a percentage). + * If force_baseline is TRUE, the computed quantization table entries + * are limited to 1..255 for JPEG baseline compatibility. + */ +{ + JQUANT_TBL ** qtblptr = & cinfo->quant_tbl_ptrs[which_tbl]; + int i; + long temp; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (*qtblptr == NULL) + *qtblptr = jpeg_alloc_quant_table((j_common_ptr) cinfo); + + for (i = 0; i < DCTSIZE2; i++) { + temp = ((long) basic_table[i] * scale_factor + 50L) / 100L; + /* limit the values to the valid range */ + if (temp <= 0L) temp = 1L; + if (temp > 32767L) temp = 32767L; /* max quantizer needed for 12 bits */ + if (force_baseline && temp > 255L) + temp = 255L; /* limit to baseline range if requested */ + (*qtblptr)->quantval[i] = (UINT16) temp; + } + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*qtblptr)->sent_table = FALSE; +} + + +GLOBAL void +jpeg_set_linear_quality (j_compress_ptr cinfo, int scale_factor, + boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables + * and a straight percentage-scaling quality scale. In most cases it's better + * to use jpeg_set_quality (below); this entry point is provided for + * applications that insist on a linear percentage scaling. + */ +{ + /* This is the sample quantization table given in the JPEG spec section K.1, + * but expressed in zigzag order (as are all of our quant. tables). + * The spec says that the values given produce "good" quality, and + * when divided by 2, "very good" quality. + */ + static const unsigned int std_luminance_quant_tbl[DCTSIZE2] = { + 16, 11, 12, 14, 12, 10, 16, 14, + 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, + 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, + 87, 69, 55, 56, 80, 109, 81, 87, + 95, 98, 103, 104, 103, 62, 77, 113, + 121, 112, 100, 120, 92, 101, 103, 99 + }; + static const unsigned int std_chrominance_quant_tbl[DCTSIZE2] = { + 17, 18, 18, 24, 21, 24, 47, 26, + 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + }; + + /* Set up two quantization tables using the specified scaling */ + jpeg_add_quant_table(cinfo, 0, std_luminance_quant_tbl, + scale_factor, force_baseline); + jpeg_add_quant_table(cinfo, 1, std_chrominance_quant_tbl, + scale_factor, force_baseline); +} + + +GLOBAL int +jpeg_quality_scaling (int quality) +/* Convert a user-specified quality rating to a percentage scaling factor + * for an underlying quantization table, using our recommended scaling curve. + * The input 'quality' factor should be 0 (terrible) to 100 (very good). + */ +{ + /* Safety limit on quality factor. Convert 0 to 1 to avoid zero divide. */ + if (quality <= 0) quality = 1; + if (quality > 100) quality = 100; + + /* The basic table is used as-is (scaling 100) for a quality of 50. + * Qualities 50..100 are converted to scaling percentage 200 - 2*Q; + * note that at Q=100 the scaling is 0, which will cause j_add_quant_table + * to make all the table entries 1 (hence, no quantization loss). + * Qualities 1..50 are converted to scaling percentage 5000/Q. + */ + if (quality < 50) + quality = 5000 / quality; + else + quality = 200 - quality*2; + + return quality; +} + + +GLOBAL void +jpeg_set_quality (j_compress_ptr cinfo, int quality, boolean force_baseline) +/* Set or change the 'quality' (quantization) setting, using default tables. + * This is the standard quality-adjusting entry point for typical user + * interfaces; only those who want detailed control over quantization tables + * would use the preceding three routines directly. + */ +{ + /* Convert user 0-100 rating to percentage scaling */ + quality = jpeg_quality_scaling(quality); + + /* Set up standard quality tables */ + jpeg_set_linear_quality(cinfo, quality, force_baseline); +} + + +/* + * Huffman table setup routines + */ + +LOCAL void +add_huff_table (j_compress_ptr cinfo, + JHUFF_TBL **htblptr, const UINT8 *bits, const UINT8 *val) +/* Define a Huffman table */ +{ + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + MEMCOPY((*htblptr)->huffval, val, SIZEOF((*htblptr)->huffval)); + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*htblptr)->sent_table = FALSE; +} + + +LOCAL void +std_huff_tables (j_compress_ptr cinfo) +/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */ +/* IMPORTANT: these are only valid for 8-bit data precision! */ +{ + static const UINT8 bits_dc_luminance[17] = + { /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_luminance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_dc_chrominance[17] = + { /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_chrominance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_ac_luminance[17] = + { /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d }; + static const UINT8 val_ac_luminance[] = + { 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + static const UINT8 bits_ac_chrominance[17] = + { /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 }; + static const UINT8 val_ac_chrominance[] = + { 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[0], + bits_dc_luminance, val_dc_luminance); + add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[0], + bits_ac_luminance, val_ac_luminance); + add_huff_table(cinfo, &cinfo->dc_huff_tbl_ptrs[1], + bits_dc_chrominance, val_dc_chrominance); + add_huff_table(cinfo, &cinfo->ac_huff_tbl_ptrs[1], + bits_ac_chrominance, val_ac_chrominance); +} + + +/* + * Default parameter setup for compression. + * + * Applications that don't choose to use this routine must do their + * own setup of all these parameters. Alternately, you can call this + * to establish defaults and then alter parameters selectively. This + * is the recommended approach since, if we add any new parameters, + * your code will still work (they'll be set to reasonable defaults). + */ + +GLOBAL void +jpeg_set_defaults (j_compress_ptr cinfo) +{ + int i; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Allocate comp_info array large enough for maximum component count. + * Array is made permanent in case application wants to compress + * multiple images at same param settings. + */ + if (cinfo->comp_info == NULL) + cinfo->comp_info = (jpeg_component_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + MAX_COMPONENTS * SIZEOF(jpeg_component_info)); + + /* Initialize everything not dependent on the color space */ + + cinfo->data_precision = BITS_IN_JSAMPLE; + /* Set up two quantization tables using default quality of 75 */ + jpeg_set_quality(cinfo, 75, TRUE); + /* Set up two Huffman tables */ + std_huff_tables(cinfo); + + /* Initialize default arithmetic coding conditioning */ + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + + /* Default is no multiple-scan output */ + cinfo->scan_info = NULL; + cinfo->num_scans = 0; + + /* Expect normal source image, not raw downsampled data */ + cinfo->raw_data_in = FALSE; + + /* Use Huffman coding, not arithmetic coding, by default */ + cinfo->arith_code = FALSE; + + /* By default, do extra passes to optimize entropy coding */ + cinfo->optimize_coding = TRUE; + /* The standard Huffman tables are only valid for 8-bit data precision. + * If the precision is higher, force optimization on so that usable + * tables will be computed. This test can be removed if default tables + * are supplied that are valid for the desired precision. + */ + if (cinfo->data_precision > 8) + cinfo->optimize_coding = TRUE; + + /* By default, use the simpler non-cosited sampling alignment */ + cinfo->CCIR601_sampling = FALSE; + + /* No input smoothing */ + cinfo->smoothing_factor = 0; + + /* DCT algorithm preference */ + cinfo->dct_method = JDCT_DEFAULT; + + /* No restart markers */ + cinfo->restart_interval = 0; + cinfo->restart_in_rows = 0; + + /* Fill in default JFIF marker parameters. Note that whether the marker + * will actually be written is determined by jpeg_set_colorspace. + */ + cinfo->density_unit = 0; /* Pixel size is unknown by default */ + cinfo->X_density = 1; /* Pixel aspect ratio is square by default */ + cinfo->Y_density = 1; + + /* Choose JPEG colorspace based on input space, set defaults accordingly */ + + jpeg_default_colorspace(cinfo); +} + + +/* + * Select an appropriate JPEG colorspace for in_color_space. + */ + +GLOBAL void +jpeg_default_colorspace (j_compress_ptr cinfo) +{ + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + jpeg_set_colorspace(cinfo, JCS_GRAYSCALE); + break; + case JCS_RGB: + jpeg_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_YCbCr: + jpeg_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_CMYK: + jpeg_set_colorspace(cinfo, JCS_CMYK); /* By default, no translation */ + break; + case JCS_YCCK: + jpeg_set_colorspace(cinfo, JCS_YCCK); + break; + case JCS_UNKNOWN: + jpeg_set_colorspace(cinfo, JCS_UNKNOWN); + break; + default: + ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); + } +} + + +/* + * Set the JPEG colorspace, and choose colorspace-dependent default values. + */ + +GLOBAL void +jpeg_set_colorspace (j_compress_ptr cinfo, J_COLOR_SPACE colorspace) +{ + jpeg_component_info * compptr; + int ci; + +#define SET_COMP(index,id,hsamp,vsamp,quant,dctbl,actbl) \ + (compptr = &cinfo->comp_info[index], \ + compptr->component_id = (id), \ + compptr->h_samp_factor = (hsamp), \ + compptr->v_samp_factor = (vsamp), \ + compptr->quant_tbl_no = (quant), \ + compptr->dc_tbl_no = (dctbl), \ + compptr->ac_tbl_no = (actbl) ) + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* For all colorspaces, we use Q and Huff tables 0 for luminance components, + * tables 1 for chrominance components. + */ + + cinfo->jpeg_color_space = colorspace; + + cinfo->write_JFIF_header = FALSE; /* No marker for non-JFIF colorspaces */ + cinfo->write_Adobe_marker = FALSE; /* write no Adobe marker by default */ + + switch (colorspace) { + case JCS_GRAYSCALE: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 1; + /* JFIF specifies component ID 1 */ + SET_COMP(0, 1, 1,1, 0, 0,0); + break; + case JCS_RGB: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag RGB */ + cinfo->num_components = 3; + SET_COMP(0, 0x52 /* 'R' */, 1,1, 0, 0,0); + SET_COMP(1, 0x47 /* 'G' */, 1,1, 0, 0,0); + SET_COMP(2, 0x42 /* 'B' */, 1,1, 0, 0,0); + break; + case JCS_YCbCr: + cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */ + cinfo->num_components = 3; + /* JFIF specifies component IDs 1,2,3 */ + /* We default to 2x2 subsamples of chrominance */ + SET_COMP(0, 1, 2,2, 0, 0,0); + SET_COMP(1, 2, 1,1, 1, 1,1); + SET_COMP(2, 3, 1,1, 1, 1,1); + break; + case JCS_CMYK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag CMYK */ + cinfo->num_components = 4; + SET_COMP(0, 0x43 /* 'C' */, 1,1, 0, 0,0); + SET_COMP(1, 0x4D /* 'M' */, 1,1, 0, 0,0); + SET_COMP(2, 0x59 /* 'Y' */, 1,1, 0, 0,0); + SET_COMP(3, 0x4B /* 'K' */, 1,1, 0, 0,0); + break; + case JCS_YCCK: + cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag YCCK */ + cinfo->num_components = 4; + SET_COMP(0, 1, 2,2, 0, 0,0); + SET_COMP(1, 2, 1,1, 1, 1,1); + SET_COMP(2, 3, 1,1, 1, 1,1); + SET_COMP(3, 4, 2,2, 0, 0,0); + break; + case JCS_UNKNOWN: + cinfo->num_components = cinfo->input_components; + if (cinfo->num_components < 1 || cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + for (ci = 0; ci < cinfo->num_components; ci++) { + SET_COMP(ci, ci, 1,1, 0, 0,0); + } + break; + default: + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + } +} + + +#ifdef C_PROGRESSIVE_SUPPORTED + +LOCAL jpeg_scan_info * +fill_a_scan (jpeg_scan_info * scanptr, int ci, + int Ss, int Se, int Ah, int Al) +/* Support routine: generate one scan for specified component */ +{ + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + return scanptr; +} + +LOCAL jpeg_scan_info * +fill_scans (jpeg_scan_info * scanptr, int ncomps, + int Ss, int Se, int Ah, int Al) +/* Support routine: generate one scan for each component */ +{ + int ci; + + for (ci = 0; ci < ncomps; ci++) { + scanptr->comps_in_scan = 1; + scanptr->component_index[0] = ci; + scanptr->Ss = Ss; + scanptr->Se = Se; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } + return scanptr; +} + +LOCAL jpeg_scan_info * +fill_dc_scans (jpeg_scan_info * scanptr, int ncomps, int Ah, int Al) +/* Support routine: generate interleaved DC scan if possible, else N scans */ +{ + int ci; + + if (ncomps <= MAX_COMPS_IN_SCAN) { + /* Single interleaved DC scan */ + scanptr->comps_in_scan = ncomps; + for (ci = 0; ci < ncomps; ci++) + scanptr->component_index[ci] = ci; + scanptr->Ss = scanptr->Se = 0; + scanptr->Ah = Ah; + scanptr->Al = Al; + scanptr++; + } else { + /* Noninterleaved DC scan for each component */ + scanptr = fill_scans(scanptr, ncomps, 0, 0, Ah, Al); + } + return scanptr; +} + + +/* + * Create a recommended progressive-JPEG script. + * cinfo->num_components and cinfo->jpeg_color_space must be correct. + */ + +GLOBAL void +jpeg_simple_progression (j_compress_ptr cinfo) +{ + int ncomps = cinfo->num_components; + int nscans; + jpeg_scan_info * scanptr; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Figure space needed for script. Calculation must match code below! */ + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + nscans = 10; + } else { + /* All-purpose script for other color spaces. */ + if (ncomps > MAX_COMPS_IN_SCAN) + nscans = 6 * ncomps; /* 2 DC + 4 AC scans per component */ + else + nscans = 2 + 4 * ncomps; /* 2 DC scans; 4 AC scans per component */ + } + + /* Allocate space for script. */ + /* We use permanent pool just in case application re-uses script. */ + scanptr = (jpeg_scan_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + nscans * SIZEOF(jpeg_scan_info)); + cinfo->scan_info = scanptr; + cinfo->num_scans = nscans; + + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + /* Initial DC scan */ + scanptr = fill_dc_scans(scanptr, ncomps, 0, 1); + /* Initial AC scan: get some luma data out in a hurry */ + scanptr = fill_a_scan(scanptr, 0, 1, 5, 0, 2); + /* Chroma data is too small to be worth expending many scans on */ + scanptr = fill_a_scan(scanptr, 2, 1, 63, 0, 1); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 0, 1); + /* Complete spectral selection for luma AC */ + scanptr = fill_a_scan(scanptr, 0, 6, 63, 0, 2); + /* Refine next bit of luma AC */ + scanptr = fill_a_scan(scanptr, 0, 1, 63, 2, 1); + /* Finish DC successive approximation */ + scanptr = fill_dc_scans(scanptr, ncomps, 1, 0); + /* Finish AC successive approximation */ + scanptr = fill_a_scan(scanptr, 2, 1, 63, 1, 0); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 1, 0); + /* Luma bottom bit comes last since it's usually largest scan */ + scanptr = fill_a_scan(scanptr, 0, 1, 63, 1, 0); + } else { + /* All-purpose script for other color spaces. */ + /* Successive approximation first pass */ + scanptr = fill_dc_scans(scanptr, ncomps, 0, 1); + scanptr = fill_scans(scanptr, ncomps, 1, 5, 0, 2); + scanptr = fill_scans(scanptr, ncomps, 6, 63, 0, 2); + /* Successive approximation second pass */ + scanptr = fill_scans(scanptr, ncomps, 1, 63, 2, 1); + /* Successive approximation final pass */ + scanptr = fill_dc_scans(scanptr, ncomps, 1, 0); + scanptr = fill_scans(scanptr, ncomps, 1, 63, 1, 0); + } +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/code/jpeg-6/jcphuff.cpp b/code/jpeg-6/jcphuff.cpp new file mode 100644 index 0000000..aa0df9b --- /dev/null +++ b/code/jpeg-6/jcphuff.cpp @@ -0,0 +1,835 @@ +/* + * jcphuff.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy encoding routines for progressive JPEG. + * + * We do not support output suspension in this module, since the library + * currently does not allow multiple-scan files to be written with output + * suspension. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jchuff.h" /* Declarations shared with jchuff.c */ + +#ifdef C_PROGRESSIVE_SUPPORTED + +/* Expanded entropy encoder object for progressive Huffman encoding. */ + +typedef struct { + struct jpeg_entropy_encoder pub; /* public fields */ + + /* Mode flag: TRUE for optimization, FALSE for actual data output */ + boolean gather_statistics; + + /* Bit-level coding status. + * next_output_byte/free_in_buffer are local copies of cinfo->dest fields. + */ + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + INT32 put_buffer; /* current bit-accumulation buffer */ + int put_bits; /* # of bits now in it */ + j_compress_ptr cinfo; /* link to cinfo (needed for dump_buffer) */ + + /* Coding status for DC components */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ + + /* Coding status for AC components */ + int ac_tbl_no; /* the table number of the single component */ + unsigned int EOBRUN; /* run length of EOBs */ + unsigned int BE; /* # of buffered correction bits before MCU */ + char * bit_buffer; /* buffer for correction bits (1 per char) */ + /* packing correction bits tightly would save some space but cost time... */ + + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + int next_restart_num; /* next restart number to write (0-7) */ + + /* Pointers to derived tables (these workspaces have image lifespan). + * Since any one scan codes only DC or only AC, we only need one set + * of tables, not one for DC and one for AC. + */ + c_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; + + /* Statistics tables for optimization; again, one set is enough */ + long * count_ptrs[NUM_HUFF_TBLS]; +} phuff_entropy_encoder; + +typedef phuff_entropy_encoder * phuff_entropy_ptr; + +/* MAX_CORR_BITS is the number of bits the AC refinement correction-bit + * buffer can hold. Larger sizes may slightly improve compression, but + * 1000 is already well into the realm of overkill. + * The minimum safe size is 64 bits. + */ + +#define MAX_CORR_BITS 1000 /* Max # of correction bits I can buffer */ + +/* IRIGHT_SHIFT is like RIGHT_SHIFT, but works on int rather than INT32. + * We assume that int right shift is unsigned if INT32 right shift is, + * which should be safe. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define ISHIFT_TEMPS int ishift_temp; +#define IRIGHT_SHIFT(x,shft) \ + ((ishift_temp = (x)) < 0 ? \ + (ishift_temp >> (shft)) | ((~0) << (16-(shft))) : \ + (ishift_temp >> (shft))) +#else +#define ISHIFT_TEMPS +#define IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + +/* Forward declarations */ +METHODDEF boolean encode_mcu_DC_first JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF boolean encode_mcu_AC_first JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF boolean encode_mcu_DC_refine JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF boolean encode_mcu_AC_refine JPP((j_compress_ptr cinfo, + JBLOCKROW *MCU_data)); +METHODDEF void finish_pass_phuff JPP((j_compress_ptr cinfo)); +METHODDEF void finish_pass_gather_phuff JPP((j_compress_ptr cinfo)); + + +/* + * Initialize for a Huffman-compressed scan using progressive JPEG. + */ + +METHODDEF void +start_pass_phuff (j_compress_ptr cinfo, boolean gather_statistics) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + + entropy->cinfo = cinfo; + entropy->gather_statistics = gather_statistics; + + is_DC_band = (cinfo->Ss == 0); + + /* We assume jcmaster.c already validated the scan parameters. */ + + /* Select execution routines */ + if (cinfo->Ah == 0) { + if (is_DC_band) + entropy->pub.encode_mcu = encode_mcu_DC_first; + else + entropy->pub.encode_mcu = encode_mcu_AC_first; + } else { + if (is_DC_band) + entropy->pub.encode_mcu = encode_mcu_DC_refine; + else { + entropy->pub.encode_mcu = encode_mcu_AC_refine; + /* AC refinement needs a correction bit buffer */ + if (entropy->bit_buffer == NULL) + entropy->bit_buffer = (char *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + MAX_CORR_BITS * SIZEOF(char)); + } + } + if (gather_statistics) + entropy->pub.finish_pass = finish_pass_gather_phuff; + else + entropy->pub.finish_pass = finish_pass_phuff; + + /* Only DC coefficients may be interleaved, so cinfo->comps_in_scan = 1 + * for AC coefficients. + */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Initialize DC predictions to 0 */ + entropy->last_dc_val[ci] = 0; + /* Make sure requested tables are present */ + /* (In gather mode, tables need not be allocated yet) */ + if (is_DC_band) { + if (cinfo->Ah != 0) /* DC refinement needs no table */ + continue; + tbl = compptr->dc_tbl_no; + if (tbl < 0 || tbl >= NUM_HUFF_TBLS || + (cinfo->dc_huff_tbl_ptrs[tbl] == NULL && !gather_statistics)) + ERREXIT1(cinfo,JERR_NO_HUFF_TABLE, tbl); + } else { + entropy->ac_tbl_no = tbl = compptr->ac_tbl_no; + if (tbl < 0 || tbl >= NUM_HUFF_TBLS || + (cinfo->ac_huff_tbl_ptrs[tbl] == NULL && !gather_statistics)) + ERREXIT1(cinfo,JERR_NO_HUFF_TABLE, tbl); + } + if (gather_statistics) { + /* Allocate and zero the statistics tables */ + /* Note that jpeg_gen_optimal_table expects 257 entries in each table! */ + if (entropy->count_ptrs[tbl] == NULL) + entropy->count_ptrs[tbl] = (long *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 257 * SIZEOF(long)); + MEMZERO(entropy->count_ptrs[tbl], 257 * SIZEOF(long)); + } else { + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + if (is_DC_band) + jpeg_make_c_derived_tbl(cinfo, cinfo->dc_huff_tbl_ptrs[tbl], + & entropy->derived_tbls[tbl]); + else + jpeg_make_c_derived_tbl(cinfo, cinfo->ac_huff_tbl_ptrs[tbl], + & entropy->derived_tbls[tbl]); + } + } + + /* Initialize AC stuff */ + entropy->EOBRUN = 0; + entropy->BE = 0; + + /* Initialize bit buffer to empty */ + entropy->put_buffer = 0; + entropy->put_bits = 0; + + /* Initialize restart stuff */ + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num = 0; +} + + +/* Outputting bytes to the file. + * NB: these must be called only when actually outputting, + * that is, entropy->gather_statistics == FALSE. + */ + +/* Emit a byte */ +#define emit_byte(entropy,val) \ + { *(entropy)->next_output_byte++ = (JOCTET) (val); \ + if (--(entropy)->free_in_buffer == 0) \ + dump_buffer(entropy); } + + +LOCAL void +dump_buffer (phuff_entropy_ptr entropy) +/* Empty the output buffer; we do not support suspension in this module. */ +{ + struct jpeg_destination_mgr * dest = entropy->cinfo->dest; + + if (! (*dest->empty_output_buffer) (entropy->cinfo)) + ERREXIT(entropy->cinfo, JERR_CANT_SUSPEND); + /* After a successful buffer dump, must reset buffer pointers */ + entropy->next_output_byte = dest->next_output_byte; + entropy->free_in_buffer = dest->free_in_buffer; +} + + +/* Outputting bits to the file */ + +/* Only the right 24 bits of put_buffer are used; the valid bits are + * left-justified in this part. At most 16 bits can be passed to emit_bits + * in one call, and we never retain more than 7 bits in put_buffer + * between calls, so 24 bits are sufficient. + */ + +INLINE +LOCAL void +emit_bits (phuff_entropy_ptr entropy, unsigned int code, int size) +/* Emit some bits, unless we are in gather mode */ +{ + /* This routine is heavily used, so it's worth coding tightly. */ + register INT32 put_buffer = (INT32) code; + register int put_bits = entropy->put_bits; + + /* if size is 0, caller used an invalid Huffman table entry */ + if (size == 0) + ERREXIT(entropy->cinfo, JERR_HUFF_MISSING_CODE); + + if (entropy->gather_statistics) + return; /* do nothing if we're only getting stats */ + + put_buffer &= (((INT32) 1)<put_buffer; /* and merge with old buffer contents */ + + while (put_bits >= 8) { + int c = (int) ((put_buffer >> 16) & 0xFF); + + emit_byte(entropy, c); + if (c == 0xFF) { /* need to stuff a zero byte? */ + emit_byte(entropy, 0); + } + put_buffer <<= 8; + put_bits -= 8; + } + + entropy->put_buffer = put_buffer; /* update variables */ + entropy->put_bits = put_bits; +} + + +LOCAL void +flush_bits (phuff_entropy_ptr entropy) +{ + emit_bits(entropy, 0x7F, 7); /* fill any partial byte with ones */ + entropy->put_buffer = 0; /* and reset bit-buffer to empty */ + entropy->put_bits = 0; +} + + +/* + * Emit (or just count) a Huffman symbol. + */ + +INLINE +LOCAL void +emit_symbol (phuff_entropy_ptr entropy, int tbl_no, int symbol) +{ + if (entropy->gather_statistics) + entropy->count_ptrs[tbl_no][symbol]++; + else { + c_derived_tbl * tbl = entropy->derived_tbls[tbl_no]; + emit_bits(entropy, tbl->ehufco[symbol], tbl->ehufsi[symbol]); + } +} + + +/* + * Emit bits from a correction bit buffer. + */ + +LOCAL void +emit_buffered_bits (phuff_entropy_ptr entropy, char * bufstart, + unsigned int nbits) +{ + if (entropy->gather_statistics) + return; /* no real work */ + + while (nbits > 0) { + emit_bits(entropy, (unsigned int) (*bufstart), 1); + bufstart++; + nbits--; + } +} + + +/* + * Emit any pending EOBRUN symbol. + */ + +LOCAL void +emit_eobrun (phuff_entropy_ptr entropy) +{ + register int temp, nbits; + + if (entropy->EOBRUN > 0) { /* if there is any pending EOBRUN */ + temp = entropy->EOBRUN; + nbits = 0; + while ((temp >>= 1)) + nbits++; + + emit_symbol(entropy, entropy->ac_tbl_no, nbits << 4); + if (nbits) + emit_bits(entropy, entropy->EOBRUN, nbits); + + entropy->EOBRUN = 0; + + /* Emit any buffered correction bits */ + emit_buffered_bits(entropy, entropy->bit_buffer, entropy->BE); + entropy->BE = 0; + } +} + + +/* + * Emit a restart marker & resynchronize predictions. + */ + +LOCAL void +emit_restart (phuff_entropy_ptr entropy, int restart_num) +{ + int ci; + + emit_eobrun(entropy); + + if (! entropy->gather_statistics) { + flush_bits(entropy); + emit_byte(entropy, 0xFF); + emit_byte(entropy, JPEG_RST0 + restart_num); + } + + if (entropy->cinfo->Ss == 0) { + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < entropy->cinfo->comps_in_scan; ci++) + entropy->last_dc_val[ci] = 0; + } else { + /* Re-initialize all AC-related fields to 0 */ + entropy->EOBRUN = 0; + entropy->BE = 0; + } +} + + +/* + * MCU encoding for DC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +encode_mcu_DC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + int blkn, ci; + int Al = cinfo->Al; + JBLOCKROW block; + jpeg_component_info * compptr; + ISHIFT_TEMPS + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + + /* Compute the DC value after the required point transform by Al. + * This is simply an arithmetic right shift. + */ + temp2 = IRIGHT_SHIFT((int) ((*block)[0]), Al); + + /* DC differences are figured on the point-transformed values. */ + temp = temp2 - entropy->last_dc_val[ci]; + entropy->last_dc_val[ci] = temp2; + + /* Encode the DC coefficient difference per section G.1.2.1 */ + temp2 = temp; + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + /* For a negative input, want temp2 = bitwise complement of abs(input) */ + /* This code assumes we are on a two's complement machine */ + temp2--; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 0; + while (temp) { + nbits++; + temp >>= 1; + } + + /* Count/emit the Huffman-coded symbol for the number of bits */ + emit_symbol(entropy, compptr->dc_tbl_no, nbits); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + if (nbits) /* emit_bits rejects calls with size 0 */ + emit_bits(entropy, (unsigned int) temp2, nbits); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC initial scan (either spectral selection, + * or first pass of successive approximation). + */ + +METHODDEF boolean +encode_mcu_AC_first (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp, temp2; + register int nbits; + register int r, k; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* Encode the AC coefficients per section G.1.2.2, fig. G.3 */ + + r = 0; /* r = run length of zeros */ + + for (k = cinfo->Ss; k <= Se; k++) { + if ((temp = (*block)[jpeg_natural_order[k]]) == 0) { + r++; + continue; + } + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value; so the code is + * interwoven with finding the abs value (temp) and output bits (temp2). + */ + if (temp < 0) { + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + /* For a negative coef, want temp2 = bitwise complement of abs(coef) */ + temp2 = ~temp; + } else { + temp >>= Al; /* apply the point transform */ + temp2 = temp; + } + /* Watch out for case that nonzero coef is zero after point transform */ + if (temp == 0) { + r++; + continue; + } + + /* Emit any pending EOBRUN */ + if (entropy->EOBRUN > 0) + emit_eobrun(entropy); + /* if run length > 15, must emit special run-length-16 codes (0xF0) */ + while (r > 15) { + emit_symbol(entropy, entropy->ac_tbl_no, 0xF0); + r -= 16; + } + + /* Find the number of bits needed for the magnitude of the coefficient */ + nbits = 1; /* there must be at least one 1 bit */ + while ((temp >>= 1)) + nbits++; + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol(entropy, entropy->ac_tbl_no, (r << 4) + nbits); + + /* Emit that number of bits of the value, if positive, */ + /* or the complement of its magnitude, if negative. */ + emit_bits(entropy, (unsigned int) temp2, nbits); + + r = 0; /* reset zero run length */ + } + + if (r > 0) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + if (entropy->EOBRUN == 0x7FFF) + emit_eobrun(entropy); /* force it out to avoid overflow */ + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for DC successive approximation refinement scan. + * Note: we assume such scans can be multi-component, although the spec + * is not very clear on the point. + */ + +METHODDEF boolean +encode_mcu_DC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + int blkn; + int Al = cinfo->Al; + JBLOCKROW block; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data blocks */ + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + + /* We simply emit the Al'th bit of the DC coefficient value. */ + temp = (*block)[0]; + emit_bits(entropy, (unsigned int) (temp >> Al), 1); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * MCU encoding for AC successive approximation refinement scan. + */ + +METHODDEF boolean +encode_mcu_AC_refine (j_compress_ptr cinfo, JBLOCKROW *MCU_data) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + register int temp; + register int r, k; + int EOB; + char *BR_buffer; + unsigned int BR; + int Se = cinfo->Se; + int Al = cinfo->Al; + JBLOCKROW block; + int absvalues[DCTSIZE2]; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Emit restart marker if needed */ + if (cinfo->restart_interval) + if (entropy->restarts_to_go == 0) + emit_restart(entropy, entropy->next_restart_num); + + /* Encode the MCU data block */ + block = MCU_data[0]; + + /* It is convenient to make a pre-pass to determine the transformed + * coefficients' absolute values and the EOB position. + */ + EOB = 0; + for (k = cinfo->Ss; k <= Se; k++) { + temp = (*block)[jpeg_natural_order[k]]; + /* We must apply the point transform by Al. For AC coefficients this + * is an integer division with rounding towards 0. To do this portably + * in C, we shift after obtaining the absolute value. + */ + if (temp < 0) + temp = -temp; /* temp is abs value of input */ + temp >>= Al; /* apply the point transform */ + absvalues[k] = temp; /* save abs value for main pass */ + if (temp == 1) + EOB = k; /* EOB = index of last newly-nonzero coef */ + } + + /* Encode the AC coefficients per section G.1.2.3, fig. G.7 */ + + r = 0; /* r = run length of zeros */ + BR = 0; /* BR = count of buffered bits added now */ + BR_buffer = entropy->bit_buffer + entropy->BE; /* Append bits to buffer */ + + for (k = cinfo->Ss; k <= Se; k++) { + if ((temp = absvalues[k]) == 0) { + r++; + continue; + } + + /* Emit any required ZRLs, but not if they can be folded into EOB */ + while (r > 15 && k <= EOB) { + /* emit any pending EOBRUN and the BE correction bits */ + emit_eobrun(entropy); + /* Emit ZRL */ + emit_symbol(entropy, entropy->ac_tbl_no, 0xF0); + r -= 16; + /* Emit buffered correction bits that must be associated with ZRL */ + emit_buffered_bits(entropy, BR_buffer, BR); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + } + + /* If the coef was previously nonzero, it only needs a correction bit. + * NOTE: a straight translation of the spec's figure G.7 would suggest + * that we also need to test r > 15. But if r > 15, we can only get here + * if k > EOB, which implies that this coefficient is not 1. + */ + if (temp > 1) { + /* The correction bit is the next bit of the absolute value. */ + BR_buffer[BR++] = (char) (temp & 1); + continue; + } + + /* Emit any pending EOBRUN and the BE correction bits */ + emit_eobrun(entropy); + + /* Count/emit Huffman symbol for run length / number of bits */ + emit_symbol(entropy, entropy->ac_tbl_no, (r << 4) + 1); + + /* Emit output bit for newly-nonzero coef */ + temp = ((*block)[jpeg_natural_order[k]] < 0) ? 0 : 1; + emit_bits(entropy, (unsigned int) temp, 1); + + /* Emit buffered correction bits that must be associated with this code */ + emit_buffered_bits(entropy, BR_buffer, BR); + BR_buffer = entropy->bit_buffer; /* BE bits are gone now */ + BR = 0; + r = 0; /* reset zero run length */ + } + + if (r > 0 || BR > 0) { /* If there are trailing zeroes, */ + entropy->EOBRUN++; /* count an EOB */ + entropy->BE += BR; /* concat my correction bits to older ones */ + /* We force out the EOB if we risk either: + * 1. overflow of the EOB counter; + * 2. overflow of the correction bit buffer during the next MCU. + */ + if (entropy->EOBRUN == 0x7FFF || entropy->BE > (MAX_CORR_BITS-DCTSIZE2+1)) + emit_eobrun(entropy); + } + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; + + /* Update restart-interval state too */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) { + entropy->restarts_to_go = cinfo->restart_interval; + entropy->next_restart_num++; + entropy->next_restart_num &= 7; + } + entropy->restarts_to_go--; + } + + return TRUE; +} + + +/* + * Finish up at the end of a Huffman-compressed progressive scan. + */ + +METHODDEF void +finish_pass_phuff (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + + entropy->next_output_byte = cinfo->dest->next_output_byte; + entropy->free_in_buffer = cinfo->dest->free_in_buffer; + + /* Flush out any buffered data */ + emit_eobrun(entropy); + flush_bits(entropy); + + cinfo->dest->next_output_byte = entropy->next_output_byte; + cinfo->dest->free_in_buffer = entropy->free_in_buffer; +} + + +/* + * Finish up a statistics-gathering pass and create the new Huffman tables. + */ + +METHODDEF void +finish_pass_gather_phuff (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; + boolean is_DC_band; + int ci, tbl; + jpeg_component_info * compptr; + JHUFF_TBL **htblptr; + boolean did[NUM_HUFF_TBLS]; + + /* Flush out buffered data (all we care about is counting the EOB symbol) */ + emit_eobrun(entropy); + + is_DC_band = (cinfo->Ss == 0); + + /* It's important not to apply jpeg_gen_optimal_table more than once + * per table, because it clobbers the input frequency counts! + */ + MEMZERO(did, SIZEOF(did)); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + if (is_DC_band) { + if (cinfo->Ah != 0) /* DC refinement needs no table */ + continue; + tbl = compptr->dc_tbl_no; + } else { + tbl = compptr->ac_tbl_no; + } + if (! did[tbl]) { + if (is_DC_band) + htblptr = & cinfo->dc_huff_tbl_ptrs[tbl]; + else + htblptr = & cinfo->ac_huff_tbl_ptrs[tbl]; + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + jpeg_gen_optimal_table(cinfo, *htblptr, entropy->count_ptrs[tbl]); + did[tbl] = TRUE; + } + } +} + + +/* + * Module initialization routine for progressive Huffman entropy encoding. + */ + +GLOBAL void +jinit_phuff_encoder (j_compress_ptr cinfo) +{ + phuff_entropy_ptr entropy; + int i; + + entropy = (phuff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(phuff_entropy_encoder)); + cinfo->entropy = (struct jpeg_entropy_encoder *) entropy; + entropy->pub.start_pass = start_pass_phuff; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->derived_tbls[i] = NULL; + entropy->count_ptrs[i] = NULL; + } + entropy->bit_buffer = NULL; /* needed only in AC refinement scan */ +} + +#endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/code/jpeg-6/jcprepct.cpp b/code/jpeg-6/jcprepct.cpp new file mode 100644 index 0000000..1d7e68c --- /dev/null +++ b/code/jpeg-6/jcprepct.cpp @@ -0,0 +1,377 @@ +/* + * jcprepct.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the compression preprocessing controller. + * This controller manages the color conversion, downsampling, + * and edge expansion steps. + * + * Most of the complexity here is associated with buffering input rows + * as required by the downsampler. See the comments at the head of + * jcsample.c for the downsampler's needs. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* At present, jcsample.c can request context rows only for smoothing. + * In the future, we might also need context rows for CCIR601 sampling + * or other more-complex downsampling procedures. The code to support + * context rows should be compiled only if needed. + */ +#ifdef INPUT_SMOOTHING_SUPPORTED +#define CONTEXT_ROWS_SUPPORTED +#endif + + +/* + * For the simple (no-context-row) case, we just need to buffer one + * row group's worth of pixels for the downsampling step. At the bottom of + * the image, we pad to a full row group by replicating the last pixel row. + * The downsampler's last output row is then replicated if needed to pad + * out to a full iMCU row. + * + * When providing context rows, we must buffer three row groups' worth of + * pixels. Three row groups are physically allocated, but the row pointer + * arrays are made five row groups high, with the extra pointers above and + * below "wrapping around" to point to the last and first real row groups. + * This allows the downsampler to access the proper context rows. + * At the top and bottom of the image, we create dummy context rows by + * copying the first or last real pixel row. This copying could be avoided + * by pointer hacking as is done in jdmainct.c, but it doesn't seem worth the + * trouble on the compression side. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_prep_controller pub; /* public fields */ + + /* Downsampling input buffer. This buffer holds color-converted data + * until we have enough to do a downsample step. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + JDIMENSION rows_to_go; /* counts rows remaining in source image */ + int next_buf_row; /* index of next row to store in color_buf */ + +#ifdef CONTEXT_ROWS_SUPPORTED /* only needed for context case */ + int this_row_group; /* starting row index of group to process */ + int next_buf_stop; /* downsample when we reach this index */ +#endif +} my_prep_controller; + +typedef my_prep_controller * my_prep_ptr; + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_prep (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + + if (pass_mode != JBUF_PASS_THRU) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Initialize total-height counter for detecting bottom of image */ + prep->rows_to_go = cinfo->image_height; + /* Mark the conversion buffer empty */ + prep->next_buf_row = 0; +#ifdef CONTEXT_ROWS_SUPPORTED + /* Preset additional state variables for context mode. + * These aren't used in non-context mode, so we needn't test which mode. + */ + prep->this_row_group = 0; + /* Set next_buf_stop to stop after two row groups have been read in. */ + prep->next_buf_stop = 2 * cinfo->max_v_samp_factor; +#endif +} + + +/* + * Expand an image vertically from height input_rows to height output_rows, + * by duplicating the bottom row. + */ + +LOCAL void +expand_bottom_edge (JSAMPARRAY image_data, JDIMENSION num_cols, + int input_rows, int output_rows) +{ + register int row; + + for (row = input_rows; row < output_rows; row++) { + jcopy_sample_rows(image_data, input_rows-1, image_data, row, + 1, num_cols); + } +} + + +/* + * Process some data in the simple no-context case. + * + * Preprocessor output data is counted in "row groups". A row group + * is defined to be v_samp_factor sample rows of each component. + * Downsampling will produce this much data from each max_v_samp_factor + * input rows. + */ + +METHODDEF void +pre_process_data (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while (*in_row_ctr < in_rows_avail && + *out_row_group_ctr < out_row_groups_avail) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = cinfo->max_v_samp_factor - prep->next_buf_row; + numrows = (int) MIN((JDIMENSION) numrows, inrows); + (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows); + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + /* If at bottom of image, pad to fill the conversion buffer. */ + if (prep->rows_to_go == 0 && + prep->next_buf_row < cinfo->max_v_samp_factor) { + for (ci = 0; ci < cinfo->num_components; ci++) { + expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, cinfo->max_v_samp_factor); + } + prep->next_buf_row = cinfo->max_v_samp_factor; + } + /* If we've filled the conversion buffer, empty it. */ + if (prep->next_buf_row == cinfo->max_v_samp_factor) { + (*cinfo->downsample->downsample) (cinfo, + prep->color_buf, (JDIMENSION) 0, + output_buf, *out_row_group_ctr); + prep->next_buf_row = 0; + (*out_row_group_ctr)++; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if (prep->rows_to_go == 0 && + *out_row_group_ctr < out_row_groups_avail) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + expand_bottom_edge(output_buf[ci], + compptr->width_in_blocks * DCTSIZE, + (int) (*out_row_group_ctr * compptr->v_samp_factor), + (int) (out_row_groups_avail * compptr->v_samp_factor)); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +#ifdef CONTEXT_ROWS_SUPPORTED + +/* + * Process some data in the context case. + */ + +METHODDEF void +pre_process_context (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int numrows, ci; + int buf_height = cinfo->max_v_samp_factor * 3; + JDIMENSION inrows; + jpeg_component_info * compptr; + + while (*out_row_group_ctr < out_row_groups_avail) { + if (*in_row_ctr < in_rows_avail) { + /* Do color conversion to fill the conversion buffer. */ + inrows = in_rows_avail - *in_row_ctr; + numrows = prep->next_buf_stop - prep->next_buf_row; + numrows = (int) MIN((JDIMENSION) numrows, inrows); + (*cinfo->cconvert->color_convert) (cinfo, input_buf + *in_row_ctr, + prep->color_buf, + (JDIMENSION) prep->next_buf_row, + numrows); + /* Pad at top of image, if first time through */ + if (prep->rows_to_go == cinfo->image_height) { + for (ci = 0; ci < cinfo->num_components; ci++) { + int row; + for (row = 1; row <= cinfo->max_v_samp_factor; row++) { + jcopy_sample_rows(prep->color_buf[ci], 0, + prep->color_buf[ci], -row, + 1, cinfo->image_width); + } + } + } + *in_row_ctr += numrows; + prep->next_buf_row += numrows; + prep->rows_to_go -= numrows; + } else { + /* Return for more data, unless we are at the bottom of the image. */ + if (prep->rows_to_go != 0) + break; + } + /* If at bottom of image, pad to fill the conversion buffer. */ + if (prep->rows_to_go == 0 && + prep->next_buf_row < prep->next_buf_stop) { + for (ci = 0; ci < cinfo->num_components; ci++) { + expand_bottom_edge(prep->color_buf[ci], cinfo->image_width, + prep->next_buf_row, prep->next_buf_stop); + } + prep->next_buf_row = prep->next_buf_stop; + } + /* If we've gotten enough data, downsample a row group. */ + if (prep->next_buf_row == prep->next_buf_stop) { + (*cinfo->downsample->downsample) (cinfo, + prep->color_buf, + (JDIMENSION) prep->this_row_group, + output_buf, *out_row_group_ctr); + (*out_row_group_ctr)++; + /* Advance pointers with wraparound as necessary. */ + prep->this_row_group += cinfo->max_v_samp_factor; + if (prep->this_row_group >= buf_height) + prep->this_row_group = 0; + if (prep->next_buf_row >= buf_height) + prep->next_buf_row = 0; + prep->next_buf_stop = prep->next_buf_row + cinfo->max_v_samp_factor; + } + /* If at bottom of image, pad the output to a full iMCU height. + * Note we assume the caller is providing a one-iMCU-height output buffer! + */ + if (prep->rows_to_go == 0 && + *out_row_group_ctr < out_row_groups_avail) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + expand_bottom_edge(output_buf[ci], + compptr->width_in_blocks * DCTSIZE, + (int) (*out_row_group_ctr * compptr->v_samp_factor), + (int) (out_row_groups_avail * compptr->v_samp_factor)); + } + *out_row_group_ctr = out_row_groups_avail; + break; /* can exit outer loop without test */ + } + } +} + + +/* + * Create the wrapped-around downsampling input buffer needed for context mode. + */ + +LOCAL void +create_context_buffer (j_compress_ptr cinfo) +{ + my_prep_ptr prep = (my_prep_ptr) cinfo->prep; + int rgroup_height = cinfo->max_v_samp_factor; + int ci, i; + jpeg_component_info * compptr; + JSAMPARRAY true_buffer, fake_buffer; + + /* Grab enough space for fake row pointers for all the components; + * we need five row groups' worth of pointers for each component. + */ + fake_buffer = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (cinfo->num_components * 5 * rgroup_height) * + SIZEOF(JSAMPROW)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate the actual buffer space (3 row groups) for this component. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + true_buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (((long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor) / compptr->h_samp_factor), + (JDIMENSION) (3 * rgroup_height)); + /* Copy true buffer row pointers into the middle of the fake row array */ + MEMCOPY(fake_buffer + rgroup_height, true_buffer, + 3 * rgroup_height * SIZEOF(JSAMPROW)); + /* Fill in the above and below wraparound pointers */ + for (i = 0; i < rgroup_height; i++) { + fake_buffer[i] = true_buffer[2 * rgroup_height + i]; + fake_buffer[4 * rgroup_height + i] = true_buffer[i]; + } + prep->color_buf[ci] = fake_buffer + rgroup_height; + fake_buffer += 5 * rgroup_height; /* point to space for next component */ + } +} + +#endif /* CONTEXT_ROWS_SUPPORTED */ + + +/* + * Initialize preprocessing controller. + */ + +GLOBAL void +jinit_c_prep_controller (j_compress_ptr cinfo, boolean need_full_buffer) +{ + my_prep_ptr prep; + int ci; + jpeg_component_info * compptr; + + if (need_full_buffer) /* safety check */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + prep = (my_prep_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_prep_controller)); + cinfo->prep = (struct jpeg_c_prep_controller *) prep; + prep->pub.start_pass = start_pass_prep; + + /* Allocate the color conversion buffer. + * We make the buffer wide enough to allow the downsampler to edge-expand + * horizontally within the buffer, if it so chooses. + */ + if (cinfo->downsample->need_context_rows) { + /* Set up to provide context rows */ +#ifdef CONTEXT_ROWS_SUPPORTED + prep->pub.pre_process_data = pre_process_context; + create_context_buffer(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* No context, just make it tall enough for one row group */ + prep->pub.pre_process_data = pre_process_data; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + prep->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) (((long) compptr->width_in_blocks * DCTSIZE * + cinfo->max_h_samp_factor) / compptr->h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/code/jpeg-6/jcsample.cpp b/code/jpeg-6/jcsample.cpp new file mode 100644 index 0000000..fcdc543 --- /dev/null +++ b/code/jpeg-6/jcsample.cpp @@ -0,0 +1,525 @@ +/* + * jcsample.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains downsampling routines. + * + * Downsampling input data is counted in "row groups". A row group + * is defined to be max_v_samp_factor pixel rows of each component, + * from which the downsampler produces v_samp_factor sample rows. + * A single row group is processed in each call to the downsampler module. + * + * The downsampler is responsible for edge-expansion of its output data + * to fill an integral number of DCT blocks horizontally. The source buffer + * may be modified if it is helpful for this purpose (the source buffer is + * allocated wide enough to correspond to the desired output width). + * The caller (the prep controller) is responsible for vertical padding. + * + * The downsampler may request "context rows" by setting need_context_rows + * during startup. In this case, the input arrays will contain at least + * one row group's worth of pixels above and below the passed-in data; + * the caller will create dummy rows at image top and bottom by replicating + * the first or last real pixel row. + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + * + * The downsampling algorithm used here is a simple average of the source + * pixels covered by the output pixel. The hi-falutin sampling literature + * refers to this as a "box filter". In general the characteristics of a box + * filter are not very good, but for the specific cases we normally use (1:1 + * and 2:1 ratios) the box is equivalent to a "triangle filter" which is not + * nearly so bad. If you intend to use other sampling ratios, you'd be well + * advised to improve this code. + * + * A simple input-smoothing capability is provided. This is mainly intended + * for cleaning up color-dithered GIF input files (if you find it inadequate, + * we suggest using an external filtering program such as pnmconvol). When + * enabled, each input pixel P is replaced by a weighted sum of itself and its + * eight neighbors. P's weight is 1-8*SF and each neighbor's weight is SF, + * where SF = (smoothing_factor / 1024). + * Currently, smoothing is only supported for 2h2v sampling factors. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to downsample a single component */ +typedef JMETHOD(void, downsample1_ptr, + (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data)); + +/* Private subobject */ + +typedef struct { + struct jpeg_downsampler pub; /* public fields */ + + /* Downsampling method pointers, one per component */ + downsample1_ptr methods[MAX_COMPONENTS]; +} my_downsampler; + +typedef my_downsampler * my_downsample_ptr; + + +/* + * Initialize for a downsampling pass. + */ + +METHODDEF void +start_pass_downsample (j_compress_ptr cinfo) +{ + /* no work for now */ +} + + +/* + * Expand a component horizontally from width input_cols to width output_cols, + * by duplicating the rightmost samples. + */ + +LOCAL void +expand_right_edge (JSAMPARRAY image_data, int num_rows, + JDIMENSION input_cols, JDIMENSION output_cols) +{ + register JSAMPROW ptr; + register JSAMPLE pixval; + register int count; + int row; + int numcols = (int) (output_cols - input_cols); + + if (numcols > 0) { + for (row = 0; row < num_rows; row++) { + ptr = image_data[row] + input_cols; + pixval = ptr[-1]; /* don't need GETJSAMPLE() here */ + for (count = numcols; count > 0; count--) + *ptr++ = pixval; + } + } +} + + +/* + * Do downsampling for a whole row group (all components). + * + * In this version we simply downsample each component independently. + */ + +METHODDEF void +sep_downsample (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, JDIMENSION out_row_group_index) +{ + my_downsample_ptr downsample = (my_downsample_ptr) cinfo->downsample; + int ci; + jpeg_component_info * compptr; + JSAMPARRAY in_ptr, out_ptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + in_ptr = input_buf[ci] + in_row_index; + out_ptr = output_buf[ci] + (out_row_group_index * compptr->v_samp_factor); + (*downsample->methods[ci]) (cinfo, compptr, in_ptr, out_ptr); + } +} + + +/* + * Downsample pixel values of a single component. + * One row group is processed per call. + * This version handles arbitrary integral sampling ratios, without smoothing. + * Note that this version is not actually used for customary sampling ratios. + */ + +METHODDEF void +int_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow, h_expand, v_expand, numpix, numpix2, h, v; + JDIMENSION outcol, outcol_h; /* outcol_h == outcol*h_expand */ + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + JSAMPROW inptr, outptr; + INT32 outvalue; + + h_expand = cinfo->max_h_samp_factor / compptr->h_samp_factor; + v_expand = cinfo->max_v_samp_factor / compptr->v_samp_factor; + numpix = h_expand * v_expand; + numpix2 = numpix/2; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * h_expand); + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + for (outcol = 0, outcol_h = 0; outcol < output_cols; + outcol++, outcol_h += h_expand) { + outvalue = 0; + for (v = 0; v < v_expand; v++) { + inptr = input_data[inrow+v] + outcol_h; + for (h = 0; h < h_expand; h++) { + outvalue += (INT32) GETJSAMPLE(*inptr++); + } + } + *outptr++ = (JSAMPLE) ((outvalue + numpix2) / numpix); + } + inrow += v_expand; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * without smoothing. + */ + +METHODDEF void +fullsize_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + /* Copy the data */ + jcopy_sample_rows(input_data, 0, output_data, 0, + cinfo->max_v_samp_factor, cinfo->image_width); + /* Edge-expand */ + expand_right_edge(output_data, cinfo->max_v_samp_factor, + cinfo->image_width, compptr->width_in_blocks * DCTSIZE); +} + + +/* + * Downsample pixel values of a single component. + * This version handles the common case of 2:1 horizontal and 1:1 vertical, + * without smoothing. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF void +h2v1_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2); + + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + bias = 0; /* bias = 0,1,0,1,... for successive samples */ + for (outcol = 0; outcol < output_cols; outcol++) { + *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr) + GETJSAMPLE(inptr[1]) + + bias) >> 1); + bias ^= 1; /* 0=>1, 1=>0 */ + inptr += 2; + } + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * without smoothing. + */ + +METHODDEF void +h2v2_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow; + JDIMENSION outcol; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, outptr; + register int bias; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data, cinfo->max_v_samp_factor, + cinfo->image_width, output_cols * 2); + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow+1]; + bias = 1; /* bias = 1,2,1,2,... for successive samples */ + for (outcol = 0; outcol < output_cols; outcol++) { + *outptr++ = (JSAMPLE) ((GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]) + + bias) >> 2); + bias ^= 3; /* 1=>2, 2=>1 */ + inptr0 += 2; inptr1 += 2; + } + inrow += 2; + } +} + + +#ifdef INPUT_SMOOTHING_SUPPORTED + +/* + * Downsample pixel values of a single component. + * This version handles the standard case of 2:1 horizontal and 2:1 vertical, + * with smoothing. One row of context is required. + */ + +METHODDEF void +h2v2_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int inrow, outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr0, inptr1, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols * 2); + + /* We don't bother to form the individual "smoothed" input pixel values; + * we can directly compute the output which is the average of the four + * smoothed values. Each of the four member pixels contributes a fraction + * (1-8*SF) to its own smoothed image and a fraction SF to each of the three + * other smoothed pixels, therefore a total fraction (1-5*SF)/4 to the final + * output. The four corner-adjacent neighbor pixels contribute a fraction + * SF to just one smoothed pixel, or SF/4 to the final output; while the + * eight edge-adjacent neighbors contribute SF to each of two smoothed + * pixels, or SF/2 overall. In order to use integer arithmetic, these + * factors are scaled by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 16384 - cinfo->smoothing_factor * 80; /* scaled (1-5*SF)/4 */ + neighscale = cinfo->smoothing_factor * 16; /* scaled SF/4 */ + + inrow = 0; + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr0 = input_data[inrow]; + inptr1 = input_data[inrow+1]; + above_ptr = input_data[inrow-1]; + below_ptr = input_data[inrow+2]; + + /* Special case for first column: pretend column -1 is same as column 0 */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[2]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[2]); + neighsum += neighsum; + neighsum += GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[2]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[2]); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + + for (colctr = output_cols - 2; colctr > 0; colctr--) { + /* sum of pixels directly mapped to this output element */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + /* sum of edge-neighbor pixels */ + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[2]) + + GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[2]); + /* The edge-neighbors count twice as much as corner-neighbors */ + neighsum += neighsum; + /* Add in the corner-neighbors */ + neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[2]) + + GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[2]); + /* form final output scaled up by 2^16 */ + membersum = membersum * memberscale + neighsum * neighscale; + /* round, descale and output it */ + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + inptr0 += 2; inptr1 += 2; above_ptr += 2; below_ptr += 2; + } + + /* Special case for last column */ + membersum = GETJSAMPLE(*inptr0) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(*inptr1) + GETJSAMPLE(inptr1[1]); + neighsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(*below_ptr) + GETJSAMPLE(below_ptr[1]) + + GETJSAMPLE(inptr0[-1]) + GETJSAMPLE(inptr0[1]) + + GETJSAMPLE(inptr1[-1]) + GETJSAMPLE(inptr1[1]); + neighsum += neighsum; + neighsum += GETJSAMPLE(above_ptr[-1]) + GETJSAMPLE(above_ptr[1]) + + GETJSAMPLE(below_ptr[-1]) + GETJSAMPLE(below_ptr[1]); + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ((membersum + 32768) >> 16); + + inrow += 2; + } +} + + +/* + * Downsample pixel values of a single component. + * This version handles the special case of a full-size component, + * with smoothing. One row of context is required. + */ + +METHODDEF void +fullsize_smooth_downsample (j_compress_ptr cinfo, jpeg_component_info *compptr, + JSAMPARRAY input_data, JSAMPARRAY output_data) +{ + int outrow; + JDIMENSION colctr; + JDIMENSION output_cols = compptr->width_in_blocks * DCTSIZE; + register JSAMPROW inptr, above_ptr, below_ptr, outptr; + INT32 membersum, neighsum, memberscale, neighscale; + int colsum, lastcolsum, nextcolsum; + + /* Expand input data enough to let all the output samples be generated + * by the standard loop. Special-casing padded output would be more + * efficient. + */ + expand_right_edge(input_data - 1, cinfo->max_v_samp_factor + 2, + cinfo->image_width, output_cols); + + /* Each of the eight neighbor pixels contributes a fraction SF to the + * smoothed pixel, while the main pixel contributes (1-8*SF). In order + * to use integer arithmetic, these factors are multiplied by 2^16 = 65536. + * Also recall that SF = smoothing_factor / 1024. + */ + + memberscale = 65536L - cinfo->smoothing_factor * 512L; /* scaled 1-8*SF */ + neighscale = cinfo->smoothing_factor * 64; /* scaled SF */ + + for (outrow = 0; outrow < compptr->v_samp_factor; outrow++) { + outptr = output_data[outrow]; + inptr = input_data[outrow]; + above_ptr = input_data[outrow-1]; + below_ptr = input_data[outrow+1]; + + /* Special case for first column */ + colsum = GETJSAMPLE(*above_ptr++) + GETJSAMPLE(*below_ptr++) + + GETJSAMPLE(*inptr); + membersum = GETJSAMPLE(*inptr++); + nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) + + GETJSAMPLE(*inptr); + neighsum = colsum + (colsum - membersum) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + lastcolsum = colsum; colsum = nextcolsum; + + for (colctr = output_cols - 2; colctr > 0; colctr--) { + membersum = GETJSAMPLE(*inptr++); + above_ptr++; below_ptr++; + nextcolsum = GETJSAMPLE(*above_ptr) + GETJSAMPLE(*below_ptr) + + GETJSAMPLE(*inptr); + neighsum = lastcolsum + (colsum - membersum) + nextcolsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr++ = (JSAMPLE) ((membersum + 32768) >> 16); + lastcolsum = colsum; colsum = nextcolsum; + } + + /* Special case for last column */ + membersum = GETJSAMPLE(*inptr); + neighsum = lastcolsum + (colsum - membersum) + colsum; + membersum = membersum * memberscale + neighsum * neighscale; + *outptr = (JSAMPLE) ((membersum + 32768) >> 16); + + } +} + +#endif /* INPUT_SMOOTHING_SUPPORTED */ + + +/* + * Module initialization routine for downsampling. + * Note that we must select a routine for each component. + */ + +GLOBAL void +jinit_downsampler (j_compress_ptr cinfo) +{ + my_downsample_ptr downsample; + int ci; + jpeg_component_info * compptr; + boolean smoothok = TRUE; + + downsample = (my_downsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_downsampler)); + cinfo->downsample = (struct jpeg_downsampler *) downsample; + downsample->pub.start_pass = start_pass_downsample; + downsample->pub.downsample = sep_downsample; + downsample->pub.need_context_rows = FALSE; + + if (cinfo->CCIR601_sampling) + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* Verify we can handle the sampling factors, and set up method pointers */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor) { + downsample->methods[ci] = fullsize_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = fullsize_downsample; + } else if (compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor == cinfo->max_v_samp_factor) { + smoothok = FALSE; + downsample->methods[ci] = h2v1_downsample; + } else if (compptr->h_samp_factor * 2 == cinfo->max_h_samp_factor && + compptr->v_samp_factor * 2 == cinfo->max_v_samp_factor) { +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor) { + downsample->methods[ci] = h2v2_smooth_downsample; + downsample->pub.need_context_rows = TRUE; + } else +#endif + downsample->methods[ci] = h2v2_downsample; + } else if ((cinfo->max_h_samp_factor % compptr->h_samp_factor) == 0 && + (cinfo->max_v_samp_factor % compptr->v_samp_factor) == 0) { + smoothok = FALSE; + downsample->methods[ci] = int_downsample; + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + } + +#ifdef INPUT_SMOOTHING_SUPPORTED + if (cinfo->smoothing_factor && !smoothok) + TRACEMS(cinfo, 0, JTRC_SMOOTH_NOTIMPL); +#endif +} diff --git a/code/jpeg-6/jctrans.cpp b/code/jpeg-6/jctrans.cpp new file mode 100644 index 0000000..c7ed174 --- /dev/null +++ b/code/jpeg-6/jctrans.cpp @@ -0,0 +1,378 @@ +/* + * jctrans.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding compression, + * that is, writing raw DCT coefficient arrays to an output JPEG file. + * The routines in jcapimin.c will also be needed by a transcoder. + */ + + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL void transencode_master_selection + JPP((j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays)); +LOCAL void transencode_coef_controller + JPP((j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays)); + + +/* + * Compression initialization for writing raw-coefficient data. + * Before calling this, all parameters and a data destination must be set up. + * Call jpeg_finish_compress() to actually write the data. + * + * The number of passed virtual arrays must match cinfo->num_components. + * Note that the virtual arrays need not be filled or even realized at + * the time write_coefficients is called; indeed, if the virtual arrays + * were requested from this compression object's memory manager, they + * typically will be realized during this routine and filled afterwards. + */ + +GLOBAL void +jpeg_write_coefficients (j_compress_ptr cinfo, jvirt_barray_ptr * coef_arrays) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Mark all tables to be written */ + jpeg_suppress_tables(cinfo, FALSE); + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Perform master selection of active modules */ + transencode_master_selection(cinfo, coef_arrays); + /* Wait for jpeg_finish_compress() call */ + cinfo->next_scanline = 0; /* so jpeg_write_marker works */ + cinfo->global_state = CSTATE_WRCOEFS; +} + + +/* + * Initialize the compression object with default parameters, + * then copy from the source object all parameters needed for lossless + * transcoding. Parameters that can be varied without loss (such as + * scan script and Huffman optimization) are left in their default states. + */ + +GLOBAL void +jpeg_copy_critical_parameters (j_decompress_ptr srcinfo, + j_compress_ptr dstinfo) +{ + JQUANT_TBL ** qtblptr; + jpeg_component_info *incomp, *outcomp; + JQUANT_TBL *c_quant, *slot_quant; + int tblno, ci, coefi; + + /* Safety check to ensure start_compress not called yet. */ + if (dstinfo->global_state != CSTATE_START) + ERREXIT1(dstinfo, JERR_BAD_STATE, dstinfo->global_state); + /* Copy fundamental image dimensions */ + dstinfo->image_width = srcinfo->image_width; + dstinfo->image_height = srcinfo->image_height; + dstinfo->input_components = srcinfo->num_components; + dstinfo->in_color_space = srcinfo->jpeg_color_space; + /* Initialize all parameters to default values */ + jpeg_set_defaults(dstinfo); + /* jpeg_set_defaults may choose wrong colorspace, eg YCbCr if input is RGB. + * Fix it to get the right header markers for the image colorspace. + */ + jpeg_set_colorspace(dstinfo, srcinfo->jpeg_color_space); + dstinfo->data_precision = srcinfo->data_precision; + dstinfo->CCIR601_sampling = srcinfo->CCIR601_sampling; + /* Copy the source's quantization tables. */ + for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) { + if (srcinfo->quant_tbl_ptrs[tblno] != NULL) { + qtblptr = & dstinfo->quant_tbl_ptrs[tblno]; + if (*qtblptr == NULL) + *qtblptr = jpeg_alloc_quant_table((j_common_ptr) dstinfo); + MEMCOPY((*qtblptr)->quantval, + srcinfo->quant_tbl_ptrs[tblno]->quantval, + SIZEOF((*qtblptr)->quantval)); + (*qtblptr)->sent_table = FALSE; + } + } + /* Copy the source's per-component info. + * Note we assume jpeg_set_defaults has allocated the dest comp_info array. + */ + dstinfo->num_components = srcinfo->num_components; + if (dstinfo->num_components < 1 || dstinfo->num_components > MAX_COMPONENTS) + ERREXIT2(dstinfo, JERR_COMPONENT_COUNT, dstinfo->num_components, + MAX_COMPONENTS); + for (ci = 0, incomp = srcinfo->comp_info, outcomp = dstinfo->comp_info; + ci < dstinfo->num_components; ci++, incomp++, outcomp++) { + outcomp->component_id = incomp->component_id; + outcomp->h_samp_factor = incomp->h_samp_factor; + outcomp->v_samp_factor = incomp->v_samp_factor; + outcomp->quant_tbl_no = incomp->quant_tbl_no; + /* Make sure saved quantization table for component matches the qtable + * slot. If not, the input file re-used this qtable slot. + * IJG encoder currently cannot duplicate this. + */ + tblno = outcomp->quant_tbl_no; + if (tblno < 0 || tblno >= NUM_QUANT_TBLS || + srcinfo->quant_tbl_ptrs[tblno] == NULL) + ERREXIT1(dstinfo, JERR_NO_QUANT_TABLE, tblno); + slot_quant = srcinfo->quant_tbl_ptrs[tblno]; + c_quant = incomp->quant_table; + if (c_quant != NULL) { + for (coefi = 0; coefi < DCTSIZE2; coefi++) { + if (c_quant->quantval[coefi] != slot_quant->quantval[coefi]) + ERREXIT1(dstinfo, JERR_MISMATCHED_QUANT_TABLE, tblno); + } + } + /* Note: we do not copy the source's Huffman table assignments; + * instead we rely on jpeg_set_colorspace to have made a suitable choice. + */ + } +} + + +/* + * Master selection of compression modules for transcoding. + * This substitutes for jcinit.c's initialization of the full compressor. + */ + +LOCAL void +transencode_master_selection (j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays) +{ + /* Although we don't actually use input_components for transcoding, + * jcmaster.c's initial_setup will complain if input_components is 0. + */ + cinfo->input_components = 1; + /* Initialize master control (includes parameter checking/processing) */ + jinit_c_master_control(cinfo, TRUE /* transcode only */); + + /* Entropy encoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef C_PROGRESSIVE_SUPPORTED + jinit_phuff_encoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_encoder(cinfo); + } + + /* We need a special coefficient buffer controller. */ + transencode_coef_controller(cinfo, coef_arrays); + + jinit_marker_writer(cinfo); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Write the datastream header (SOI) immediately. + * Frame and scan headers are postponed till later. + * This lets application insert special markers after the SOI. + */ + (*cinfo->marker->write_file_header) (cinfo); +} + + +/* + * The rest of this file is a special implementation of the coefficient + * buffer controller. This is similar to jccoefct.c, but it handles only + * output from presupplied virtual arrays. Furthermore, we generate any + * dummy padding blocks on-the-fly rather than expecting them to be present + * in the arrays. + */ + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_c_coef_controller pub; /* public fields */ + + JDIMENSION iMCU_row_num; /* iMCU row # within image */ + JDIMENSION mcu_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* Virtual block array for each component. */ + jvirt_barray_ptr * whole_image; + + /* Workspace for constructing dummy blocks at right/bottom edges. */ + JBLOCKROW dummy_buffer[C_MAX_BLOCKS_IN_MCU]; +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + + +LOCAL void +start_iMCU_row (j_compress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (coef->iMCU_row_num < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->mcu_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_coef (j_compress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + if (pass_mode != JBUF_CRANK_DEST) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + coef->iMCU_row_num = 0; + start_iMCU_row(cinfo); +} + + +/* + * Process some data. + * We process the equivalent of one fully interleaved MCU row ("iMCU" row) + * per call, ie, v_samp_factor block rows for each component in the scan. + * The data is obtained from the virtual arrays and fed to the entropy coder. + * Returns TRUE if the iMCU row is completed, FALSE if suspended. + * + * NB: input_buf is ignored; it is likely to be a NULL pointer. + */ + +METHODDEF boolean +compress_output (j_compress_ptr cinfo, JSAMPIMAGE input_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, blockcnt; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW MCU_buffer[C_MAX_BLOCKS_IN_MCU]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + coef->iMCU_row_num * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->mcu_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + blockcnt = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (coef->iMCU_row_num < last_iMCU_row || + yindex+yoffset < compptr->last_row_height) { + /* Fill in pointers to real blocks in this row */ + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < blockcnt; xindex++) + MCU_buffer[blkn++] = buffer_ptr++; + } else { + /* At bottom of image, need a whole row of dummy blocks */ + xindex = 0; + } + /* Fill in any dummy blocks needed in this row. + * Dummy blocks are filled in the same way as in jccoefct.c: + * all zeroes in the AC entries, DC entries equal to previous + * block's DC value. The init routine has already zeroed the + * AC entries, so we need only set the DC entries correctly. + */ + for (; xindex < compptr->MCU_width; xindex++) { + MCU_buffer[blkn] = coef->dummy_buffer[blkn]; + MCU_buffer[blkn][0][0] = MCU_buffer[blkn-1][0][0]; + blkn++; + } + } + } + /* Try to write the MCU. */ + if (! (*cinfo->entropy->encode_mcu) (cinfo, MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->mcu_ctr = MCU_col_num; + return FALSE; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->mcu_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + coef->iMCU_row_num++; + start_iMCU_row(cinfo); + return TRUE; +} + + +/* + * Initialize coefficient buffer controller. + * + * Each passed coefficient array must be the right size for that + * coefficient: width_in_blocks wide and height_in_blocks high, + * with unitheight at least v_samp_factor. + */ + +LOCAL void +transencode_coef_controller (j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays) +{ + my_coef_ptr coef; + JBLOCKROW buffer; + int i; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_c_coef_controller *) coef; + coef->pub.start_pass = start_pass_coef; + coef->pub.compress_data = compress_output; + + /* Save pointer to virtual arrays */ + coef->whole_image = coef_arrays; + + /* Allocate and pre-zero space for dummy DCT blocks. */ + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + jzero_far((void FAR *) buffer, C_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < C_MAX_BLOCKS_IN_MCU; i++) { + coef->dummy_buffer[i] = buffer + i; + } +} diff --git a/code/jpeg-6/jdapimin.cpp b/code/jpeg-6/jdapimin.cpp new file mode 100644 index 0000000..77c8c27 --- /dev/null +++ b/code/jpeg-6/jdapimin.cpp @@ -0,0 +1,404 @@ +/* + * jdapimin.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "minimum" API routines that may be + * needed in either the normal full-decompression case or the + * transcoding-only case. + * + * Most of the routines intended to be called directly by an application + * are in this file or in jdapistd.c. But also see jcomapi.c for routines + * shared by compression and decompression, and jdtrans.c for the transcoding + * case. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * Initialization of a JPEG decompression object. + * The error manager must already be set up (in case memory manager fails). + */ + +GLOBAL void +jpeg_create_decompress (j_decompress_ptr cinfo) +{ + int i; + + /* For debugging purposes, zero the whole master structure. + * But error manager pointer is already there, so save and restore it. + */ + { + struct jpeg_error_mgr * err = cinfo->err; + MEMZERO(cinfo, SIZEOF(struct jpeg_decompress_struct)); + cinfo->err = err; + } + cinfo->is_decompressor = TRUE; + + /* Initialize a memory manager instance for this object */ + jinit_memory_mgr((j_common_ptr) cinfo); + + /* Zero out pointers to permanent structures. */ + cinfo->progress = NULL; + cinfo->src = NULL; + + for (i = 0; i < NUM_QUANT_TBLS; i++) + cinfo->quant_tbl_ptrs[i] = NULL; + + for (i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = NULL; + cinfo->ac_huff_tbl_ptrs[i] = NULL; + } + + /* Initialize marker processor so application can override methods + * for COM, APPn markers before calling jpeg_read_header. + */ + jinit_marker_reader(cinfo); + + /* And initialize the overall input controller. */ + jinit_input_controller(cinfo); + + /* OK, I'm ready */ + cinfo->global_state = DSTATE_START; +} + + +/* + * Destruction of a JPEG decompression object + */ + +GLOBAL void +jpeg_destroy_decompress (j_decompress_ptr cinfo) +{ + jpeg_destroy((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Abort processing of a JPEG decompression operation, + * but don't destroy the object itself. + */ + +GLOBAL void +jpeg_abort_decompress (j_decompress_ptr cinfo) +{ + jpeg_abort((j_common_ptr) cinfo); /* use common routine */ +} + + +/* + * Install a special processing method for COM or APPn markers. + */ + +GLOBAL void +jpeg_set_marker_processor (j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine) +{ + if (marker_code == JPEG_COM) + cinfo->marker->process_COM = routine; + else if (marker_code >= JPEG_APP0 && marker_code <= JPEG_APP0+15) + cinfo->marker->process_APPn[marker_code-JPEG_APP0] = routine; + else + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, marker_code); +} + + +/* + * Set default decompression parameters. + */ + +LOCAL void +default_decompress_parms (j_decompress_ptr cinfo) +{ + /* Guess the input colorspace, and set output colorspace accordingly. */ + /* (Wish JPEG committee had provided a real way to specify this...) */ + /* Note application may override our guesses. */ + switch (cinfo->num_components) { + case 1: + cinfo->jpeg_color_space = JCS_GRAYSCALE; + cinfo->out_color_space = JCS_GRAYSCALE; + break; + + case 3: + if (cinfo->saw_JFIF_marker) { + cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */ + } else if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_RGB; + break; + case 1: + cinfo->jpeg_color_space = JCS_YCbCr; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + break; + } + } else { + /* Saw no special markers, try to guess from the component IDs */ + int cid0 = cinfo->comp_info[0].component_id; + int cid1 = cinfo->comp_info[1].component_id; + int cid2 = cinfo->comp_info[2].component_id; + + if (cid0 == 1 && cid1 == 2 && cid2 == 3) + cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */ + else if (cid0 == 82 && cid1 == 71 && cid2 == 66) + cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */ + else { + TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + } + } + /* Always guess RGB is proper output colorspace. */ + cinfo->out_color_space = JCS_RGB; + break; + + case 4: + if (cinfo->saw_Adobe_marker) { + switch (cinfo->Adobe_transform) { + case 0: + cinfo->jpeg_color_space = JCS_CMYK; + break; + case 2: + cinfo->jpeg_color_space = JCS_YCCK; + break; + default: + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */ + break; + } + } else { + /* No special markers, assume straight CMYK. */ + cinfo->jpeg_color_space = JCS_CMYK; + } + cinfo->out_color_space = JCS_CMYK; + break; + + default: + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->out_color_space = JCS_UNKNOWN; + break; + } + + /* Set defaults for other decompression parameters. */ + cinfo->scale_num = 1; /* 1:1 scaling */ + cinfo->scale_denom = 1; + cinfo->output_gamma = 1.0; + cinfo->buffered_image = FALSE; + cinfo->raw_data_out = FALSE; + cinfo->dct_method = JDCT_DEFAULT; + cinfo->do_fancy_upsampling = TRUE; + cinfo->do_block_smoothing = TRUE; + cinfo->quantize_colors = FALSE; + /* We set these in case application only sets quantize_colors. */ + cinfo->dither_mode = JDITHER_FS; +#ifdef QUANT_2PASS_SUPPORTED + cinfo->two_pass_quantize = TRUE; +#else + cinfo->two_pass_quantize = FALSE; +#endif + cinfo->desired_number_of_colors = 256; + cinfo->colormap = NULL; + /* Initialize for no mode change in buffered-image mode. */ + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; +} + + +/* + * Decompression startup: read start of JPEG datastream to see what's there. + * Need only initialize JPEG object and supply a data source before calling. + * + * This routine will read as far as the first SOS marker (ie, actual start of + * compressed data), and will save all tables and parameters in the JPEG + * object. It will also initialize the decompression parameters to default + * values, and finally return JPEG_HEADER_OK. On return, the application may + * adjust the decompression parameters and then call jpeg_start_decompress. + * (Or, if the application only wanted to determine the image parameters, + * the data need not be decompressed. In that case, call jpeg_abort or + * jpeg_destroy to release any temporary space.) + * If an abbreviated (tables only) datastream is presented, the routine will + * return JPEG_HEADER_TABLES_ONLY upon reaching EOI. The application may then + * re-use the JPEG object to read the abbreviated image datastream(s). + * It is unnecessary (but OK) to call jpeg_abort in this case. + * The JPEG_SUSPENDED return code only occurs if the data source module + * requests suspension of the decompressor. In this case the application + * should load more source data and then re-call jpeg_read_header to resume + * processing. + * If a non-suspending data source is used and require_image is TRUE, then the + * return code need not be inspected since only JPEG_HEADER_OK is possible. + * + * This routine is now just a front end to jpeg_consume_input, with some + * extra error checking. + */ + +GLOBAL int +jpeg_read_header (j_decompress_ptr cinfo, boolean require_image) +{ + int retcode; + + if (cinfo->global_state != DSTATE_START && + cinfo->global_state != DSTATE_INHEADER) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + retcode = jpeg_consume_input(cinfo); + + switch (retcode) { + case JPEG_REACHED_SOS: + retcode = JPEG_HEADER_OK; + break; + case JPEG_REACHED_EOI: + if (require_image) /* Complain if application wanted an image */ + ERREXIT(cinfo, JERR_NO_IMAGE); + /* Reset to start state; it would be safer to require the application to + * call jpeg_abort, but we can't change it now for compatibility reasons. + * A side effect is to free any temporary memory (there shouldn't be any). + */ + jpeg_abort((j_common_ptr) cinfo); /* sets state = DSTATE_START */ + retcode = JPEG_HEADER_TABLES_ONLY; + break; + case JPEG_SUSPENDED: + /* no work */ + break; + } + + return retcode; +} + + +/* + * Consume data in advance of what the decompressor requires. + * This can be called at any time once the decompressor object has + * been created and a data source has been set up. + * + * This routine is essentially a state machine that handles a couple + * of critical state-transition actions, namely initial setup and + * transition from header scanning to ready-for-start_decompress. + * All the actual input is done via the input controller's consume_input + * method. + */ + +GLOBAL int +jpeg_consume_input (j_decompress_ptr cinfo) +{ + int retcode = JPEG_SUSPENDED; + + /* NB: every possible DSTATE value should be listed in this switch */ + switch (cinfo->global_state) { + case DSTATE_START: + /* Start-of-datastream actions: reset appropriate modules */ + (*cinfo->inputctl->reset_input_controller) (cinfo); + /* Initialize application's data source module */ + (*cinfo->src->init_source) (cinfo); + cinfo->global_state = DSTATE_INHEADER; + /*FALLTHROUGH*/ + case DSTATE_INHEADER: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_REACHED_SOS) { /* Found SOS, prepare to decompress */ + /* Set up default parameters based on header data */ + default_decompress_parms(cinfo); + /* Set global state: ready for start_decompress */ + cinfo->global_state = DSTATE_READY; + } + break; + case DSTATE_READY: + /* Can't advance past first SOS until start_decompress is called */ + retcode = JPEG_REACHED_SOS; + break; + case DSTATE_PRELOAD: + case DSTATE_PRESCAN: + case DSTATE_SCANNING: + case DSTATE_RAW_OK: + case DSTATE_BUFIMAGE: + case DSTATE_BUFPOST: + case DSTATE_STOPPING: + retcode = (*cinfo->inputctl->consume_input) (cinfo); + break; + default: + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + return retcode; +} + + +/* + * Have we finished reading the input file? + */ + +GLOBAL boolean +jpeg_input_complete (j_decompress_ptr cinfo) +{ + /* Check for valid jpeg object */ + if (cinfo->global_state < DSTATE_START || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->eoi_reached; +} + + +/* + * Is there more than one scan? + */ + +GLOBAL boolean +jpeg_has_multiple_scans (j_decompress_ptr cinfo) +{ + /* Only valid after jpeg_read_header completes */ + if (cinfo->global_state < DSTATE_READY || + cinfo->global_state > DSTATE_STOPPING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + return cinfo->inputctl->has_multiple_scans; +} + + +/* + * Finish JPEG decompression. + * + * This will normally just verify the file trailer and release temp storage. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_finish_decompress (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && ! cinfo->buffered_image) { + /* Terminate final pass of non-buffered mode */ + if (cinfo->output_scanline < cinfo->output_height) + ERREXIT(cinfo, JERR_TOO_LITTLE_DATA); + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state == DSTATE_BUFIMAGE) { + /* Finishing after a buffered-image operation */ + cinfo->global_state = DSTATE_STOPPING; + } else if (cinfo->global_state != DSTATE_STOPPING) { + /* STOPPING = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read until EOI */ + while (! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + /* Do final cleanup */ + (*cinfo->src->term_source) (cinfo); + /* We can use jpeg_abort to release memory and reset global_state */ + jpeg_abort((j_common_ptr) cinfo); + return TRUE; +} diff --git a/code/jpeg-6/jdapistd.cpp b/code/jpeg-6/jdapistd.cpp new file mode 100644 index 0000000..c18d584 --- /dev/null +++ b/code/jpeg-6/jdapistd.cpp @@ -0,0 +1,281 @@ +/* + * jdapistd.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains application interface code for the decompression half + * of the JPEG library. These are the "standard" API routines that are + * used in the normal full-decompression case. They are not used by a + * transcoding-only application. Note that if an application links in + * jpeg_start_decompress, it will end up linking in the entire decompressor. + * We thus must separate this file from jdapimin.c to avoid linking the + * whole decompression library into a transcoder. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL boolean output_pass_setup JPP((j_decompress_ptr cinfo)); + + +/* + * Decompression initialization. + * jpeg_read_header must be completed before calling this. + * + * If a multipass operating mode was selected, this will do all but the + * last pass, and thus may take a great deal of time. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_start_decompress (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize master control, select active modules */ + jinit_master_decompress(cinfo); + if (cinfo->buffered_image) { + /* No more work here; expecting jpeg_start_output next */ + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; + } + cinfo->global_state = DSTATE_PRELOAD; + } + if (cinfo->global_state == DSTATE_PRELOAD) { + /* If file has multiple scans, absorb them all into the coef buffer */ + if (cinfo->inputctl->has_multiple_scans) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return FALSE; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* jdmaster underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + } + cinfo->output_scan_number = cinfo->input_scan_number; + } else if (cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Perform any dummy output passes, and set up for the final pass */ + return output_pass_setup(cinfo); +} + + +/* + * Set up for an output pass, and perform any dummy pass(es) needed. + * Common subroutine for jpeg_start_decompress and jpeg_start_output. + * Entry: global_state = DSTATE_PRESCAN only if previously suspended. + * Exit: If done, returns TRUE and sets global_state for proper output mode. + * If suspended, returns FALSE and sets global_state = DSTATE_PRESCAN. + */ + +LOCAL boolean +output_pass_setup (j_decompress_ptr cinfo) +{ + if (cinfo->global_state != DSTATE_PRESCAN) { + /* First call: do pass setup */ + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; + cinfo->global_state = DSTATE_PRESCAN; + } + /* Loop over any required dummy passes */ + while (cinfo->master->is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Crank through the dummy pass */ + while (cinfo->output_scanline < cinfo->output_height) { + JDIMENSION last_scanline; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + /* Process some data */ + last_scanline = cinfo->output_scanline; + (*cinfo->main->process_data) (cinfo, (JSAMPARRAY) NULL, + &cinfo->output_scanline, (JDIMENSION) 0); + if (cinfo->output_scanline == last_scanline) + return FALSE; /* No progress made, must suspend */ + } + /* Finish up dummy pass, and set up for another one */ + (*cinfo->master->finish_output_pass) (cinfo); + (*cinfo->master->prepare_for_output_pass) (cinfo); + cinfo->output_scanline = 0; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } + /* Ready for application to drive output pass through + * jpeg_read_scanlines or jpeg_read_raw_data. + */ + cinfo->global_state = cinfo->raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING; + return TRUE; +} + + +/* + * Read some scanlines of data from the JPEG decompressor. + * + * The return value will be the number of lines actually read. + * This may be less than the number requested in several cases, + * including bottom of image, data source suspension, and operating + * modes that emit multiple scanlines at a time. + * + * Note: we warn about excess calls to jpeg_read_scanlines() since + * this likely signals an application programmer error. However, + * an oversize buffer (max_lines > scanlines remaining) is not an error. + */ + +GLOBAL JDIMENSION +jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines) +{ + JDIMENSION row_ctr; + + if (cinfo->global_state != DSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Process some data */ + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines); + cinfo->output_scanline += row_ctr; + return row_ctr; +} + + +/* + * Alternate entry point to read raw data. + * Processes exactly one iMCU row per call, unless suspended. + */ + +GLOBAL JDIMENSION +jpeg_read_raw_data (j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines) +{ + JDIMENSION lines_per_iMCU_row; + + if (cinfo->global_state != DSTATE_RAW_OK) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->output_scanline >= cinfo->output_height) { + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + return 0; + } + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Verify that at least one iMCU row can be returned. */ + lines_per_iMCU_row = cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size; + if (max_lines < lines_per_iMCU_row) + ERREXIT(cinfo, JERR_BUFFER_SIZE); + + /* Decompress directly into user's buffer. */ + if (! (*cinfo->coef->decompress_data) (cinfo, data)) + return 0; /* suspension forced, can do nothing more */ + + /* OK, we processed one iMCU row. */ + cinfo->output_scanline += lines_per_iMCU_row; + return lines_per_iMCU_row; +} + + +/* Additional entry points for buffered-image mode. */ + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Initialize for an output pass in buffered-image mode. + */ + +GLOBAL boolean +jpeg_start_output (j_decompress_ptr cinfo, int scan_number) +{ + if (cinfo->global_state != DSTATE_BUFIMAGE && + cinfo->global_state != DSTATE_PRESCAN) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Limit scan number to valid range */ + if (scan_number <= 0) + scan_number = 1; + if (cinfo->inputctl->eoi_reached && + scan_number > cinfo->input_scan_number) + scan_number = cinfo->input_scan_number; + cinfo->output_scan_number = scan_number; + /* Perform any dummy output passes, and set up for the real pass */ + return output_pass_setup(cinfo); +} + + +/* + * Finish up after an output pass in buffered-image mode. + * + * Returns FALSE if suspended. The return value need be inspected only if + * a suspending data source is used. + */ + +GLOBAL boolean +jpeg_finish_output (j_decompress_ptr cinfo) +{ + if ((cinfo->global_state == DSTATE_SCANNING || + cinfo->global_state == DSTATE_RAW_OK) && cinfo->buffered_image) { + /* Terminate this pass. */ + /* We do not require the whole pass to have been completed. */ + (*cinfo->master->finish_output_pass) (cinfo); + cinfo->global_state = DSTATE_BUFPOST; + } else if (cinfo->global_state != DSTATE_BUFPOST) { + /* BUFPOST = repeat call after a suspension, anything else is error */ + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + } + /* Read markers looking for SOS or EOI */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) + return FALSE; /* Suspend, come back later */ + } + cinfo->global_state = DSTATE_BUFIMAGE; + return TRUE; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ diff --git a/code/jpeg-6/jdatadst.cpp b/code/jpeg-6/jdatadst.cpp new file mode 100644 index 0000000..1107784 --- /dev/null +++ b/code/jpeg-6/jdatadst.cpp @@ -0,0 +1,158 @@ +/* + * jdatadst.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains compression data destination routines for the case of + * emitting JPEG data to a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * destination manager. + * IMPORTANT: we assume that fwrite() will correctly transcribe an array of + * JOCTETs into 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data destination object for stdio output */ + +typedef struct { + struct jpeg_destination_mgr pub; /* public fields */ + + FILE * outfile; /* target stream */ + JOCTET * buffer; /* start of buffer */ +} my_destination_mgr; + +typedef my_destination_mgr * my_dest_ptr; + +#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite'able size */ + + +/* + * Initialize destination --- called by jpeg_start_compress + * before any data is actually written. + */ + +METHODDEF void +init_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + /* Allocate the output buffer --- it will be released when done with image */ + dest->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + OUTPUT_BUF_SIZE * SIZEOF(JOCTET)); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +METHODDEF boolean +empty_output_buffer (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + + if (JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE) != + (size_t) OUTPUT_BUF_SIZE) + ERREXIT(cinfo, JERR_FILE_WRITE); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; + + return TRUE; +} + + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF void +term_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; + + /* Write any data remaining in the buffer */ + if (datacount > 0) { + if (JFWRITE(dest->outfile, dest->buffer, datacount) != datacount) + ERREXIT(cinfo, JERR_FILE_WRITE); + } + fflush(dest->outfile); + /* Make sure we wrote the output file OK */ + if (ferror(dest->outfile)) + ERREXIT(cinfo, JERR_FILE_WRITE); +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +GLOBAL void +jpeg_stdio_dest (j_compress_ptr cinfo, FILE * outfile) +{ + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_destination_mgr)); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; +} diff --git a/code/jpeg-6/jdatasrc.cpp b/code/jpeg-6/jdatasrc.cpp new file mode 100644 index 0000000..88bde93 --- /dev/null +++ b/code/jpeg-6/jdatasrc.cpp @@ -0,0 +1,210 @@ +/* + * jdatasrc.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains decompression data source routines for the case of + * reading JPEG data from a file (or any stdio stream). While these routines + * are sufficient for most applications, some will want to use a different + * source manager. + * IMPORTANT: we assume that fread() will correctly transcribe an array of + * JOCTETs from 8-bit-wide elements on external storage. If char is wider + * than 8 bits on your machine, you may need to do some tweaking. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h" + + +/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + unsigned char *infile; /* source stream */ + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + +#define INPUT_BUF_SIZE 4096 /* choose an efficiently fread'able size */ + + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +METHODDEF void +init_source (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; +} + + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + +METHODDEF boolean +fill_input_buffer (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + memcpy( src->buffer, src->infile, INPUT_BUF_SIZE ); + + src->infile += INPUT_BUF_SIZE; + + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = INPUT_BUF_SIZE; + src->start_of_file = FALSE; + + return TRUE; +} + + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +METHODDEF void +skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->pub.bytes_in_buffer) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) fill_input_buffer(cinfo); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF void +term_source (j_decompress_ptr cinfo) +{ + /* no work necessary here */ +} + + +/* + * Prepare for input from a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing decompression. + */ + +GLOBAL void +jpeg_stdio_src (j_decompress_ptr cinfo, unsigned char *infile) +{ + my_src_ptr src; + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_source_mgr)); + src = (my_src_ptr) cinfo->src; + src->buffer = (JOCTET *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + INPUT_BUF_SIZE * SIZEOF(JOCTET)); + } + + src = (my_src_ptr) cinfo->src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = term_source; + src->infile = infile; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ +} diff --git a/code/jpeg-6/jdcoefct.cpp b/code/jpeg-6/jdcoefct.cpp new file mode 100644 index 0000000..6b31c07 --- /dev/null +++ b/code/jpeg-6/jdcoefct.cpp @@ -0,0 +1,731 @@ +/* + * jdcoefct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the coefficient buffer controller for decompression. + * This controller is the top level of the JPEG decompressor proper. + * The coefficient buffer lies between entropy decoding and inverse-DCT steps. + * + * In buffered-image mode, this controller is the interface between + * input-oriented processing and output-oriented processing. + * Also, the input side (only) is used when reading a file for transcoding. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + +/* Block smoothing is only applicable for progressive JPEG, so: */ +#ifndef D_PROGRESSIVE_SUPPORTED +#undef BLOCK_SMOOTHING_SUPPORTED +#endif + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_coef_controller pub; /* public fields */ + + /* These variables keep track of the current location of the input side. */ + /* cinfo->input_iMCU_row is also used for this. */ + JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ + + /* The output side's location is represented by cinfo->output_iMCU_row. */ + + /* In single-pass modes, it's sufficient to buffer just one MCU. + * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks, + * and let the entropy decoder write into that workspace each time. + * (On 80x86, the workspace is FAR even though it's not really very big; + * this is to keep the module interfaces unchanged when a large coefficient + * buffer is necessary.) + * In multi-pass modes, this array points to the current MCU's blocks + * within the virtual arrays; it is used only by the input side. + */ + JBLOCKROW MCU_buffer[D_MAX_BLOCKS_IN_MCU]; + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* In multi-pass modes, we need a virtual block array for each component. */ + jvirt_barray_ptr whole_image[MAX_COMPONENTS]; +#endif + +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* When doing block smoothing, we latch coefficient Al values here */ + int * coef_bits_latch; +#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ +#endif +} my_coef_controller; + +typedef my_coef_controller * my_coef_ptr; + +/* Forward declarations */ +METHODDEF int decompress_onepass + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#ifdef D_MULTISCAN_FILES_SUPPORTED +METHODDEF int decompress_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif +#ifdef BLOCK_SMOOTHING_SUPPORTED +LOCAL boolean smoothing_ok JPP((j_decompress_ptr cinfo)); +METHODDEF int decompress_smooth_data + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); +#endif + + +LOCAL void +start_iMCU_row (j_decompress_ptr cinfo) +/* Reset within-iMCU-row counters for a new row (input side) */ +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* In an interleaved scan, an MCU row is the same as an iMCU row. + * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows. + * But at the bottom of the image, process only what's left. + */ + if (cinfo->comps_in_scan > 1) { + coef->MCU_rows_per_iMCU_row = 1; + } else { + if (cinfo->input_iMCU_row < (cinfo->total_iMCU_rows-1)) + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->v_samp_factor; + else + coef->MCU_rows_per_iMCU_row = cinfo->cur_comp_info[0]->last_row_height; + } + + coef->MCU_ctr = 0; + coef->MCU_vert_offset = 0; +} + + +/* + * Initialize for an input processing pass. + */ + +METHODDEF void +start_input_pass (j_decompress_ptr cinfo) +{ + cinfo->input_iMCU_row = 0; + start_iMCU_row(cinfo); +} + + +/* + * Initialize for an output processing pass. + */ + +METHODDEF void +start_output_pass (j_decompress_ptr cinfo) +{ +#ifdef BLOCK_SMOOTHING_SUPPORTED + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + + /* If multipass, check to see whether to use block smoothing on this pass */ + if (coef->pub.coef_arrays != NULL) { + if (cinfo->do_block_smoothing && smoothing_ok(cinfo)) + coef->pub.decompress_data = decompress_smooth_data; + else + coef->pub.decompress_data = decompress_data; + } +#endif + cinfo->output_iMCU_row = 0; +} + + +/* + * Decompress and return some data in the single-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Input and output must run in lockstep since we have only a one-MCU buffer. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + * For single pass, this is the same as the components in the scan. + */ + +METHODDEF int +decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + int blkn, ci, xindex, yindex, yoffset, useful_width; + JSAMPARRAY output_ptr; + JDIMENSION start_col, output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Loop to process as much as one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col; + MCU_col_num++) { + /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ + jzero_far((void FAR *) coef->MCU_buffer[0], + (size_t) (cinfo->blocks_in_MCU * SIZEOF(JBLOCK))); + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + /* Determine where data should go in output_buf and do the IDCT thing. + * We skip dummy blocks at the right and bottom edges (but blkn gets + * incremented past them!). Note the inner loop relies on having + * allocated the MCU_buffer[] blocks sequentially. + */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) { + blkn += compptr->MCU_blocks; + continue; + } + inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; + useful_width = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + output_ptr = output_buf[ci] + yoffset * compptr->DCT_scaled_size; + start_col = MCU_col_num * compptr->MCU_sample_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (cinfo->input_iMCU_row < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + output_col = start_col; + for (xindex = 0; xindex < useful_width; xindex++) { + (*inverse_DCT) (cinfo, compptr, + (JCOEFPTR) coef->MCU_buffer[blkn+xindex], + output_ptr, output_col); + output_col += compptr->DCT_scaled_size; + } + } + blkn += compptr->MCU_width; + output_ptr += compptr->DCT_scaled_size; + } + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + cinfo->output_iMCU_row++; + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Dummy consume-input routine for single-pass operation. + */ + +METHODDEF int +dummy_consume_data (j_decompress_ptr cinfo) +{ + return JPEG_SUSPENDED; /* Always indicate nothing was done */ +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Consume input data and store it in the full-image coefficient buffer. + * We read as much as one fully interleaved MCU row ("iMCU" row) per call, + * ie, v_samp_factor block rows for each component in the scan. + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + */ + +METHODDEF int +consume_data (j_decompress_ptr cinfo) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION MCU_col_num; /* index of current MCU within row */ + int blkn, ci, xindex, yindex, yoffset; + JDIMENSION start_col; + JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; + JBLOCKROW buffer_ptr; + jpeg_component_info *compptr; + + /* Align the virtual buffers for the components used in this scan. */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + buffer[ci] = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[compptr->component_index], + cinfo->input_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, TRUE); + /* Note: entropy decoder expects buffer to be zeroed, + * but this is handled automatically by the memory manager + * because we requested a pre-zeroed array. + */ + } + + /* Loop to process one whole iMCU row */ + for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; + yoffset++) { + for (MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row; + MCU_col_num++) { + /* Construct list of pointers to DCT blocks belonging to this MCU */ + blkn = 0; /* index of current DCT block within MCU */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } + } + /* Try to fetch the MCU. */ + if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; + } + } + /* Completed an MCU row, but perhaps not an iMCU row */ + coef->MCU_ctr = 0; + } + /* Completed the iMCU row, advance counters for next one */ + if (++(cinfo->input_iMCU_row) < cinfo->total_iMCU_rows) { + start_iMCU_row(cinfo); + return JPEG_ROW_COMPLETED; + } + /* Completed the scan */ + (*cinfo->inputctl->finish_input_pass) (cinfo); + return JPEG_SCAN_COMPLETED; +} + + +/* + * Decompress and return some data in the multi-pass case. + * Always attempts to emit one fully interleaved MCU row ("iMCU" row). + * Return value is JPEG_ROW_COMPLETED, JPEG_SCAN_COMPLETED, or JPEG_SUSPENDED. + * + * NB: output_buf contains a plane for each component in image. + */ + +METHODDEF int +decompress_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num; + int ci, block_row, block_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number < cinfo->output_scan_number || + (cinfo->input_scan_number == cinfo->output_scan_number && + cinfo->input_iMCU_row <= cinfo->output_iMCU_row)) { + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Align the virtual buffer for this component. */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + cinfo->output_iMCU_row * compptr->v_samp_factor, + (JDIMENSION) compptr->v_samp_factor, FALSE); + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) + block_rows = compptr->v_samp_factor; + else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + } + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + output_col = 0; + for (block_num = 0; block_num < compptr->width_in_blocks; block_num++) { + (*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr, + output_ptr, output_col); + buffer_ptr++; + output_col += compptr->DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +#ifdef BLOCK_SMOOTHING_SUPPORTED + +/* + * This code applies interblock smoothing as described by section K.8 + * of the JPEG standard: the first 5 AC coefficients are estimated from + * the DC values of a DCT block and its 8 neighboring blocks. + * We apply smoothing only for progressive JPEG decoding, and only if + * the coefficients it can estimate are not yet known to full precision. + */ + +/* + * Determine whether block smoothing is applicable and safe. + * We also latch the current states of the coef_bits[] entries for the + * AC coefficients; otherwise, if the input side of the decompressor + * advances into a new scan, we might think the coefficients are known + * more accurately than they really are. + */ + +LOCAL boolean +smoothing_ok (j_decompress_ptr cinfo) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + boolean smoothing_useful = FALSE; + int ci, coefi; + jpeg_component_info *compptr; + JQUANT_TBL * qtable; + int * coef_bits; + int * coef_bits_latch; + + if (! cinfo->progressive_mode || cinfo->coef_bits == NULL) + return FALSE; + + /* Allocate latch area if not already done */ + if (coef->coef_bits_latch == NULL) + coef->coef_bits_latch = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * + (SAVED_COEFS * SIZEOF(int))); + coef_bits_latch = coef->coef_bits_latch; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* All components' quantization values must already be latched. */ + if ((qtable = compptr->quant_table) == NULL) + return FALSE; + /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ + for (coefi = 0; coefi <= 5; coefi++) { + if (qtable->quantval[coefi] == 0) + return FALSE; + } + /* DC values must be at least partly known for all components. */ + coef_bits = cinfo->coef_bits[ci]; + if (coef_bits[0] < 0) + return FALSE; + /* Block smoothing is helpful if some AC coefficients remain inaccurate. */ + for (coefi = 1; coefi <= 5; coefi++) { + coef_bits_latch[coefi] = coef_bits[coefi]; + if (coef_bits[coefi] != 0) + smoothing_useful = TRUE; + } + coef_bits_latch += SAVED_COEFS; + } + + return smoothing_useful; +} + + +/* + * Variant of decompress_data for use when doing block smoothing. + */ + +METHODDEF int +decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) +{ + my_coef_ptr coef = (my_coef_ptr) cinfo->coef; + JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; + JDIMENSION block_num, last_block_column; + int ci, block_row, block_rows, access_rows; + JBLOCKARRAY buffer; + JBLOCKROW buffer_ptr, prev_block_row, next_block_row; + JSAMPARRAY output_ptr; + JDIMENSION output_col; + jpeg_component_info *compptr; + inverse_DCT_method_ptr inverse_DCT; + boolean first_row, last_row; + JBLOCK workspace; + int *coef_bits; + JQUANT_TBL *quanttbl; + INT32 Q00,Q01,Q02,Q10,Q11,Q20, num; + int DC1,DC2,DC3,DC4,DC5,DC6,DC7,DC8,DC9; + int Al, pred; + + /* Force some input to be done if we are getting ahead of the input. */ + while (cinfo->input_scan_number <= cinfo->output_scan_number && + ! cinfo->inputctl->eoi_reached) { + if (cinfo->input_scan_number == cinfo->output_scan_number) { + /* If input is working on current scan, we ordinarily want it to + * have completed the current row. But if input scan is DC, + * we want it to keep one row ahead so that next block row's DC + * values are up to date. + */ + JDIMENSION delta = (cinfo->Ss == 0) ? 1 : 0; + if (cinfo->input_iMCU_row > cinfo->output_iMCU_row+delta) + break; + } + if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) + return JPEG_SUSPENDED; + } + + /* OK, output from the virtual arrays. */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) + continue; + /* Count non-dummy DCT block rows in this iMCU row. */ + if (cinfo->output_iMCU_row < last_iMCU_row) { + block_rows = compptr->v_samp_factor; + access_rows = block_rows * 2; /* this and next iMCU row */ + last_row = FALSE; + } else { + /* NB: can't use last_row_height here; it is input-side-dependent! */ + block_rows = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (block_rows == 0) block_rows = compptr->v_samp_factor; + access_rows = block_rows; /* this iMCU row only */ + last_row = TRUE; + } + /* Align the virtual buffer for this component. */ + if (cinfo->output_iMCU_row > 0) { + access_rows += compptr->v_samp_factor; /* prior iMCU row too */ + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (cinfo->output_iMCU_row - 1) * compptr->v_samp_factor, + (JDIMENSION) access_rows, FALSE); + buffer += compptr->v_samp_factor; /* point to current iMCU row */ + first_row = FALSE; + } else { + buffer = (*cinfo->mem->access_virt_barray) + ((j_common_ptr) cinfo, coef->whole_image[ci], + (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE); + first_row = TRUE; + } + /* Fetch component-dependent info */ + coef_bits = coef->coef_bits_latch + (ci * SAVED_COEFS); + quanttbl = compptr->quant_table; + Q00 = quanttbl->quantval[0]; + Q01 = quanttbl->quantval[1]; + Q10 = quanttbl->quantval[2]; + Q20 = quanttbl->quantval[3]; + Q11 = quanttbl->quantval[4]; + Q02 = quanttbl->quantval[5]; + inverse_DCT = cinfo->idct->inverse_DCT[ci]; + output_ptr = output_buf[ci]; + /* Loop over all DCT blocks to be processed. */ + for (block_row = 0; block_row < block_rows; block_row++) { + buffer_ptr = buffer[block_row]; + if (first_row && block_row == 0) + prev_block_row = buffer_ptr; + else + prev_block_row = buffer[block_row-1]; + if (last_row && block_row == block_rows-1) + next_block_row = buffer_ptr; + else + next_block_row = buffer[block_row+1]; + /* We fetch the surrounding DC values using a sliding-register approach. + * Initialize all nine here so as to do the right thing on narrow pics. + */ + DC1 = DC2 = DC3 = (int) prev_block_row[0][0]; + DC4 = DC5 = DC6 = (int) buffer_ptr[0][0]; + DC7 = DC8 = DC9 = (int) next_block_row[0][0]; + output_col = 0; + last_block_column = compptr->width_in_blocks - 1; + for (block_num = 0; block_num <= last_block_column; block_num++) { + /* Fetch current DCT block into workspace so we can modify it. */ + jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1); + /* Update DC values */ + if (block_num < last_block_column) { + DC3 = (int) prev_block_row[1][0]; + DC6 = (int) buffer_ptr[1][0]; + DC9 = (int) next_block_row[1][0]; + } + /* Compute coefficient estimates per K.8. + * An estimate is applied only if coefficient is still zero, + * and is not known to be fully accurate. + */ + /* AC01 */ + if ((Al=coef_bits[1]) != 0 && workspace[1] == 0) { + num = 36 * Q00 * (DC4 - DC6); + if (num >= 0) { + pred = (int) (((Q01<<7) + num) / (Q01<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q10<<7) + num) / (Q10<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q20<<7) + num) / (Q20<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q11<<7) + num) / (Q11<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q02<<7) + num) / (Q02<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<DCT_scaled_size; + } + output_ptr += compptr->DCT_scaled_size; + } + } + + if (++(cinfo->output_iMCU_row) < cinfo->total_iMCU_rows) + return JPEG_ROW_COMPLETED; + return JPEG_SCAN_COMPLETED; +} + +#endif /* BLOCK_SMOOTHING_SUPPORTED */ + + +/* + * Initialize coefficient buffer controller. + */ + +GLOBAL void +jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_coef_ptr coef; + + coef = (my_coef_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_coef_controller)); + cinfo->coef = (struct jpeg_d_coef_controller *) coef; + coef->pub.start_input_pass = start_input_pass; + coef->pub.start_output_pass = start_output_pass; +#ifdef BLOCK_SMOOTHING_SUPPORTED + coef->coef_bits_latch = NULL; +#endif + + /* Create the coefficient buffer. */ + if (need_full_buffer) { +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* Allocate a full-image virtual array for each component, */ + /* padded to a multiple of samp_factor DCT blocks in each direction. */ + /* Note we ask for a pre-zeroed array. */ + int ci, access_rows; + jpeg_component_info *compptr; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + access_rows = compptr->v_samp_factor; +#ifdef BLOCK_SMOOTHING_SUPPORTED + /* If block smoothing could be used, need a bigger window */ + if (cinfo->progressive_mode) + access_rows *= 3; +#endif + coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) access_rows); + } + coef->pub.consume_data = consume_data; + coef->pub.decompress_data = decompress_data; + coef->pub.coef_arrays = coef->whole_image; /* link to virtual arrays */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + /* We only need a single-MCU buffer. */ + JBLOCKROW buffer; + int i; + + buffer = (JBLOCKROW) + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + D_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + for (i = 0; i < D_MAX_BLOCKS_IN_MCU; i++) { + coef->MCU_buffer[i] = buffer + i; + } + coef->pub.consume_data = dummy_consume_data; + coef->pub.decompress_data = decompress_onepass; + coef->pub.coef_arrays = NULL; /* flag for no virtual arrays */ + } +} diff --git a/code/jpeg-6/jdcolor.cpp b/code/jpeg-6/jdcolor.cpp new file mode 100644 index 0000000..ff09875 --- /dev/null +++ b/code/jpeg-6/jdcolor.cpp @@ -0,0 +1,373 @@ +/* + * jdcolor.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains output colorspace conversion routines. + */ + + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private subobject */ + +typedef struct { + struct jpeg_color_deconverter pub; /* public fields */ + + /* Private state for YCC->RGB conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ +} my_color_deconverter; + +typedef my_color_deconverter * my_cconvert_ptr; + + +/**************** YCbCr -> RGB conversion: most common case **************/ + +/* + * YCbCr is defined per CCIR 601-1, except that Cb and Cr are + * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. + * The conversion equations to be implemented are therefore + * R = Y + 1.40200 * Cr + * G = Y - 0.34414 * Cb - 0.71414 * Cr + * B = Y + 1.77200 * Cb + * where Cb and Cr represent the incoming values less CENTERJSAMPLE. + * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) + * + * To avoid floating-point arithmetic, we represent the fractional constants + * as integers scaled up by 2^16 (about 4 digits precision); we have to divide + * the products by 2^16, with appropriate rounding, to get the correct answer. + * Notice that Y, being an integral input, does not contribute any fraction + * so it need not participate in the rounding. + * + * For even more speed, we avoid doing any multiplications in the inner loop + * by precalculating the constants times Cb and Cr for all possible values. + * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table); + * for 12-bit samples it is still acceptable. It's not very reasonable for + * 16-bit samples, but if you want lossless storage you shouldn't be changing + * colorspace anyway. + * The Cr=>R and Cb=>B values can be rounded to integers in advance; the + * values for the G calculation are left scaled up, since we must add them + * together before rounding. + */ + +#define SCALEBITS 16 /* speediest right-shift on some machines */ +#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) +#define FIX(x) ((INT32) ((x) * (1L<RGB colorspace conversion. + */ + +LOCAL void +build_ycc_rgb_table (j_decompress_ptr cinfo) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + int i; + INT32 x; + SHIFT_TEMPS + + cconvert->Cr_r_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cb_b_tab = (int *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(int)); + cconvert->Cr_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + cconvert->Cb_g_tab = (INT32 *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (MAXJSAMPLE+1) * SIZEOF(INT32)); + + for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + cconvert->Cr_r_tab[i] = (int) + RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF, SCALEBITS); + /* Cb=>B value is nearest int to 1.77200 * x */ + cconvert->Cb_b_tab[i] = (int) + RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF, SCALEBITS); + /* Cr=>G value is scaled-up -0.71414 * x */ + cconvert->Cr_g_tab[i] = (- FIX(0.71414)) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + cconvert->Cb_g_tab[i] = (- FIX(0.34414)) * x + ONE_HALF; + } +} + + +/* + * Convert some rows of samples to the output colorspace. + * + * Note that we change from noninterleaved, one-plane-per-component format + * to interleaved-pixel format. The output buffer is therefore three times + * as wide as the input buffer. + * A starting row offset is provided only for the input buffer. The caller + * can easily adjust the passed output_buf value to accommodate any row + * offset required on that side. + */ + +METHODDEF void +ycc_rgb_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[RGB_RED] = range_limit[y + Crrtab[cr]]; + outptr[RGB_GREEN] = range_limit[y + + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS))]; + outptr[RGB_BLUE] = range_limit[y + Cbbtab[cb]]; + outptr += RGB_PIXELSIZE; + } + } +} + + +/**************** Cases other than YCbCr -> RGB **************/ + + +/* + * Color conversion for no colorspace change: just copy the data, + * converting from separate-planes to interleaved representation. + */ + +METHODDEF void +null_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + register JSAMPROW inptr, outptr; + register JDIMENSION count; + register int num_components = cinfo->num_components; + JDIMENSION num_cols = cinfo->output_width; + int ci; + + while (--num_rows >= 0) { + for (ci = 0; ci < num_components; ci++) { + inptr = input_buf[ci][input_row]; + outptr = output_buf[0] + ci; + for (count = num_cols; count > 0; count--) { + *outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */ + outptr += num_components; + } + } + input_row++; + output_buf++; + } +} + + +/* + * Color conversion for grayscale: just copy the data. + * This also works for YCbCr -> grayscale conversion, in which + * we just copy the Y (luminance) component and ignore chrominance. + */ + +METHODDEF void +grayscale_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + jcopy_sample_rows(input_buf[0], (int) input_row, output_buf, 0, + num_rows, cinfo->output_width); +} + + +/* + * Adobe-style YCCK->CMYK conversion. + * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same + * conversion as above, while passing K (black) unchanged. + * We assume build_ycc_rgb_table has been called. + */ + +METHODDEF void +ycck_cmyk_convert (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) +{ + my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; + register int y, cb, cr; + register JSAMPROW outptr; + register JSAMPROW inptr0, inptr1, inptr2, inptr3; + register JDIMENSION col; + JDIMENSION num_cols = cinfo->output_width; + /* copy these pointers into registers if possible */ + register JSAMPLE * range_limit = cinfo->sample_range_limit; + register int * Crrtab = cconvert->Cr_r_tab; + register int * Cbbtab = cconvert->Cb_b_tab; + register INT32 * Crgtab = cconvert->Cr_g_tab; + register INT32 * Cbgtab = cconvert->Cb_g_tab; + SHIFT_TEMPS + + while (--num_rows >= 0) { + inptr0 = input_buf[0][input_row]; + inptr1 = input_buf[1][input_row]; + inptr2 = input_buf[2][input_row]; + inptr3 = input_buf[3][input_row]; + input_row++; + outptr = *output_buf++; + for (col = 0; col < num_cols; col++) { + y = GETJSAMPLE(inptr0[col]); + cb = GETJSAMPLE(inptr1[col]); + cr = GETJSAMPLE(inptr2[col]); + /* Range-limiting is essential due to noise introduced by DCT losses. */ + outptr[0] = range_limit[MAXJSAMPLE - (y + Crrtab[cr])]; /* red */ + outptr[1] = range_limit[MAXJSAMPLE - (y + /* green */ + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS)))]; + outptr[2] = range_limit[MAXJSAMPLE - (y + Cbbtab[cb])]; /* blue */ + /* K passes through unchanged */ + outptr[3] = inptr3[col]; /* don't need GETJSAMPLE here */ + outptr += 4; + } + } +} + + +/* + * Empty method for start_pass. + */ + +METHODDEF void +start_pass_dcolor (j_decompress_ptr cinfo) +{ + /* no work needed */ +} + + +/* + * Module initialization routine for output colorspace conversion. + */ + +GLOBAL void +jinit_color_deconverter (j_decompress_ptr cinfo) +{ + my_cconvert_ptr cconvert; + int ci; + + cconvert = (my_cconvert_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_color_deconverter)); + cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert; + cconvert->pub.start_pass = start_pass_dcolor; + + /* Make sure num_components agrees with jpeg_color_space */ + switch (cinfo->jpeg_color_space) { + case JCS_GRAYSCALE: + if (cinfo->num_components != 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_RGB: + case JCS_YCbCr: + if (cinfo->num_components != 3) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + case JCS_CMYK: + case JCS_YCCK: + if (cinfo->num_components != 4) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + + default: /* JCS_UNKNOWN can be anything */ + if (cinfo->num_components < 1) + ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); + break; + } + + /* Set out_color_components and conversion method based on requested space. + * Also clear the component_needed flags for any unused components, + * so that earlier pipeline stages can avoid useless computation. + */ + + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + if (cinfo->jpeg_color_space == JCS_GRAYSCALE || + cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = grayscale_convert; + /* For color->grayscale conversion, only the Y (0) component is needed */ + for (ci = 1; ci < cinfo->num_components; ci++) + cinfo->comp_info[ci].component_needed = FALSE; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_RGB: + cinfo->out_color_components = RGB_PIXELSIZE; + if (cinfo->jpeg_color_space == JCS_YCbCr) { + cconvert->pub.color_convert = ycc_rgb_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_RGB && RGB_PIXELSIZE == 3) { + cconvert->pub.color_convert = null_convert; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + case JCS_CMYK: + cinfo->out_color_components = 4; + if (cinfo->jpeg_color_space == JCS_YCCK) { + cconvert->pub.color_convert = ycck_cmyk_convert; + build_ycc_rgb_table(cinfo); + } else if (cinfo->jpeg_color_space == JCS_CMYK) { + cconvert->pub.color_convert = null_convert; + } else + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + + default: + /* Permit null conversion to same output space */ + if (cinfo->out_color_space == cinfo->jpeg_color_space) { + cinfo->out_color_components = cinfo->num_components; + cconvert->pub.color_convert = null_convert; + } else /* unsupported non-null conversion */ + ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); + break; + } + + if (cinfo->quantize_colors) + cinfo->output_components = 1; /* single colormapped output component */ + else + cinfo->output_components = cinfo->out_color_components; +} diff --git a/code/jpeg-6/jdct.h b/code/jpeg-6/jdct.h new file mode 100644 index 0000000..3ce790b --- /dev/null +++ b/code/jpeg-6/jdct.h @@ -0,0 +1,176 @@ +/* + * jdct.h + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file contains common declarations for the forward and + * inverse DCT modules. These declarations are private to the DCT managers + * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms. + * The individual DCT algorithms are kept in separate files to ease + * machine-dependent tuning (e.g., assembly coding). + */ + + +/* + * A forward DCT routine is given a pointer to a work area of type DCTELEM[]; + * the DCT is to be performed in-place in that buffer. Type DCTELEM is int + * for 8-bit samples, INT32 for 12-bit samples. (NOTE: Floating-point DCT + * implementations use an array of type FAST_FLOAT, instead.) + * The DCT inputs are expected to be signed (range +-CENTERJSAMPLE). + * The DCT outputs are returned scaled up by a factor of 8; they therefore + * have a range of +-8K for 8-bit data, +-128K for 12-bit data. This + * convention improves accuracy in integer implementations and saves some + * work in floating-point ones. + * Quantization of the output coefficients is done by jcdctmgr.c. + */ + +#if BITS_IN_JSAMPLE == 8 +typedef int DCTELEM; /* 16 or 32 bits is fine */ +#else +typedef INT32 DCTELEM; /* must have 32 bits */ +#endif + +typedef JMETHOD(void, forward_DCT_method_ptr, (DCTELEM * data)); +typedef JMETHOD(void, float_DCT_method_ptr, (FAST_FLOAT * data)); + + +/* + * An inverse DCT routine is given a pointer to the input JBLOCK and a pointer + * to an output sample array. The routine must dequantize the input data as + * well as perform the IDCT; for dequantization, it uses the multiplier table + * pointed to by compptr->dct_table. The output data is to be placed into the + * sample array starting at a specified column. (Any row offset needed will + * be applied to the array pointer before it is passed to the IDCT code.) + * Note that the number of samples emitted by the IDCT routine is + * DCT_scaled_size * DCT_scaled_size. + */ + +/* typedef inverse_DCT_method_ptr is declared in jpegint.h */ + +/* + * Each IDCT routine has its own ideas about the best dct_table element type. + */ + +typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */ +#if BITS_IN_JSAMPLE == 8 +typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ +#else +typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ +#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ +#endif +typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ + + +/* + * Each IDCT routine is responsible for range-limiting its results and + * converting them to unsigned form (0..MAXJSAMPLE). The raw outputs could + * be quite far out of range if the input data is corrupt, so a bulletproof + * range-limiting step is required. We use a mask-and-table-lookup method + * to do the combined operations quickly. See the comments with + * prepare_range_limit_table (in jdmaster.c) for more info. + */ + +#define IDCT_range_limit(cinfo) ((cinfo)->sample_range_limit + CENTERJSAMPLE) + +#define RANGE_MASK (MAXJSAMPLE * 4 + 3) /* 2 bits wider than legal samples */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_fdct_islow jFDislow +#define jpeg_fdct_ifast jFDifast +#define jpeg_fdct_float jFDfloat +#define jpeg_idct_islow jRDislow +#define jpeg_idct_ifast jRDifast +#define jpeg_idct_float jRDfloat +#define jpeg_idct_4x4 jRD4x4 +#define jpeg_idct_2x2 jRD2x2 +#define jpeg_idct_1x1 jRD1x1 +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + +/* Extern declarations for the forward and inverse DCT routines. */ + +EXTERN void jpeg_fdct_islow JPP((DCTELEM * data)); +EXTERN void jpeg_fdct_ifast JPP((DCTELEM * data)); +EXTERN void jpeg_fdct_float JPP((FAST_FLOAT * data)); + +EXTERN void jpeg_idct_islow + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_ifast + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_float + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_4x4 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_2x2 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); +EXTERN void jpeg_idct_1x1 + JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + + +/* + * Macros for handling fixed-point arithmetic; these are used by many + * but not all of the DCT/IDCT modules. + * + * All values are expected to be of type INT32. + * Fractional constants are scaled left by CONST_BITS bits. + * CONST_BITS is defined within each module using these macros, + * and may differ from one module to the next. + */ + +#define ONE ((INT32) 1) +#define CONST_SCALE (ONE << CONST_BITS) + +/* Convert a positive real constant to an integer scaled by CONST_SCALE. + * Caution: some C compilers fail to reduce "FIX(constant)" at compile time, + * thus causing a lot of useless floating-point operations at run time. + */ + +#define FIX(x) ((INT32) ((x) * CONST_SCALE + 0.5)) + +/* Descale and correctly round an INT32 value that's scaled by N bits. + * We assume RIGHT_SHIFT rounds towards minus infinity, so adding + * the fudge factor is correct for either sign of X. + */ + +#define DESCALE(x,n) RIGHT_SHIFT((x) + (ONE << ((n)-1)), n) + +/* Multiply an INT32 variable by an INT32 constant to yield an INT32 result. + * This macro is used only when the two inputs will actually be no more than + * 16 bits wide, so that a 16x16->32 bit multiply can be used instead of a + * full 32x32 multiply. This provides a useful speedup on many machines. + * Unfortunately there is no way to specify a 16x16->32 multiply portably + * in C, but some C compilers will do the right thing if you provide the + * correct combination of casts. + */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT16) (const))) +#endif +#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ +#define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT32) (const))) +#endif + +#ifndef MULTIPLY16C16 /* default definition */ +#define MULTIPLY16C16(var,const) ((var) * (const)) +#endif + +/* Same except both inputs are variables. */ + +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#define MULTIPLY16V16(var1,var2) (((INT16) (var1)) * ((INT16) (var2))) +#endif + +#ifndef MULTIPLY16V16 /* default definition */ +#define MULTIPLY16V16(var1,var2) ((var1) * (var2)) +#endif diff --git a/code/jpeg-6/jddctmgr.cpp b/code/jpeg-6/jddctmgr.cpp new file mode 100644 index 0000000..c3103c6 --- /dev/null +++ b/code/jpeg-6/jddctmgr.cpp @@ -0,0 +1,276 @@ +/* + * jddctmgr.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the inverse-DCT management logic. + * This code selects a particular IDCT implementation to be used, + * and it performs related housekeeping chores. No code in this file + * is executed per IDCT step, only during output pass setup. + * + * Note that the IDCT routines are responsible for performing coefficient + * dequantization as well as the IDCT proper. This module sets up the + * dequantization multiplier table needed by the IDCT routine. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + + +/* + * The decompressor input side (jdinput.c) saves away the appropriate + * quantization table for each component at the start of the first scan + * involving that component. (This is necessary in order to correctly + * decode files that reuse Q-table slots.) + * When we are ready to make an output pass, the saved Q-table is converted + * to a multiplier table that will actually be used by the IDCT routine. + * The multiplier table contents are IDCT-method-dependent. To support + * application changes in IDCT method between scans, we can remake the + * multiplier tables if necessary. + * In buffered-image mode, the first output pass may occur before any data + * has been seen for some components, and thus before their Q-tables have + * been saved away. To handle this case, multiplier tables are preset + * to zeroes; the result of the IDCT will be a neutral gray level. + */ + + +/* Private subobject for this module */ + +typedef struct { + struct jpeg_inverse_dct pub; /* public fields */ + + /* This array contains the IDCT method code that each multiplier table + * is currently set up for, or -1 if it's not yet set up. + * The actual multiplier tables are pointed to by dct_table in the + * per-component comp_info structures. + */ + int cur_method[MAX_COMPONENTS]; +} my_idct_controller; + +typedef my_idct_controller * my_idct_ptr; + + +/* Allocated multiplier tables: big enough for any supported variant */ + +typedef union { + ISLOW_MULT_TYPE islow_array[DCTSIZE2]; +#ifdef DCT_IFAST_SUPPORTED + IFAST_MULT_TYPE ifast_array[DCTSIZE2]; +#endif +#ifdef DCT_FLOAT_SUPPORTED + FLOAT_MULT_TYPE float_array[DCTSIZE2]; +#endif +} multiplier_table; + + +/* The current scaled-IDCT routines require ISLOW-style multiplier tables, + * so be sure to compile that code if either ISLOW or SCALING is requested. + */ +#ifdef DCT_ISLOW_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#else +#ifdef IDCT_SCALING_SUPPORTED +#define PROVIDE_ISLOW_TABLES +#endif +#endif + + +/* + * Prepare for an output pass. + * Here we select the proper IDCT routine for each component and build + * a matching multiplier table. + */ + +METHODDEF void +start_pass (j_decompress_ptr cinfo) +{ + my_idct_ptr idct = (my_idct_ptr) cinfo->idct; + int ci, i; + jpeg_component_info *compptr; + int method = 0; + inverse_DCT_method_ptr method_ptr = NULL; + JQUANT_TBL * qtbl; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Select the proper IDCT routine for this component's scaling */ + switch (compptr->DCT_scaled_size) { +#ifdef IDCT_SCALING_SUPPORTED + case 1: + method_ptr = jpeg_idct_1x1; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 2: + method_ptr = jpeg_idct_2x2; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; + case 4: + method_ptr = jpeg_idct_4x4; + method = JDCT_ISLOW; /* jidctred uses islow-style table */ + break; +#endif + case DCTSIZE: + switch (cinfo->dct_method) { +#ifdef DCT_ISLOW_SUPPORTED + case JDCT_ISLOW: + method_ptr = jpeg_idct_islow; + method = JDCT_ISLOW; + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + method_ptr = jpeg_idct_ifast; + method = JDCT_IFAST; + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + method_ptr = jpeg_idct_float; + method = JDCT_FLOAT; + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + break; + default: + ERREXIT1(cinfo, JERR_BAD_DCTSIZE, compptr->DCT_scaled_size); + break; + } + idct->pub.inverse_DCT[ci] = method_ptr; + /* Create multiplier table from quant table. + * However, we can skip this if the component is uninteresting + * or if we already built the table. Also, if no quant table + * has yet been saved for the component, we leave the + * multiplier table all-zero; we'll be reading zeroes from the + * coefficient controller's buffer anyway. + */ + if (! compptr->component_needed || idct->cur_method[ci] == method) + continue; + qtbl = compptr->quant_table; + if (qtbl == NULL) /* happens if no data yet for component */ + continue; + idct->cur_method[ci] = method; + switch (method) { +#ifdef PROVIDE_ISLOW_TABLES + case JDCT_ISLOW: + { + /* For LL&M IDCT method, multipliers are equal to raw quantization + * coefficients, but are stored in natural order as ints. + */ + ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; + for (i = 0; i < DCTSIZE2; i++) { + ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[jpeg_zigzag_order[i]]; + } + } + break; +#endif +#ifdef DCT_IFAST_SUPPORTED + case JDCT_IFAST: + { + /* For AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * For integer operation, the multiplier table is to be scaled by + * IFAST_SCALE_BITS. The multipliers are stored in natural order. + */ + IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; +#define CONST_BITS 14 + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + for (i = 0; i < DCTSIZE2; i++) { + ifmtbl[i] = (IFAST_MULT_TYPE) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[jpeg_zigzag_order[i]], + (INT32) aanscales[i]), + CONST_BITS-IFAST_SCALE_BITS); + } + } + break; +#endif +#ifdef DCT_FLOAT_SUPPORTED + case JDCT_FLOAT: + { + /* For float AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * The multipliers are stored in natural order. + */ + FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fmtbl[i] = (FLOAT_MULT_TYPE) + ((double) qtbl->quantval[jpeg_zigzag_order[i]] * + aanscalefactor[row] * aanscalefactor[col]); + i++; + } + } + } + break; +#endif + default: + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; + } + } +} + + +/* + * Initialize IDCT manager. + */ + +GLOBAL void +jinit_inverse_dct (j_decompress_ptr cinfo) +{ + my_idct_ptr idct; + int ci; + jpeg_component_info *compptr; + + idct = (my_idct_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_idct_controller)); + cinfo->idct = (struct jpeg_inverse_dct *) idct; + idct->pub.start_pass = start_pass; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Allocate and pre-zero a multiplier table for each component */ + compptr->dct_table = + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(multiplier_table)); + MEMZERO(compptr->dct_table, SIZEOF(multiplier_table)); + /* Mark multiplier table not yet set up for any method */ + idct->cur_method[ci] = -1; + } +} diff --git a/code/jpeg-6/jdhuff.cpp b/code/jpeg-6/jdhuff.cpp new file mode 100644 index 0000000..faafde7 --- /dev/null +++ b/code/jpeg-6/jdhuff.cpp @@ -0,0 +1,580 @@ +/* + * jdhuff.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains Huffman entropy decoding routines. + * + * Much of the complexity here has to do with supporting input suspension. + * If the data source module demands suspension, we want to be able to back + * up to the start of the current MCU. To do this, we copy state variables + * into local working storage, and update them back to the permanent + * storage only upon successful completion of an MCU. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdhuff.h" /* Declarations shared with jdphuff.c */ + + +/* + * Expanded entropy decoder object for Huffman decoding. + * + * The savable_state subrecord contains fields that change within an MCU, + * but must not be updated permanently until we complete the MCU. + */ + +typedef struct { + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ +} savable_state; + +/* This macro is to work around compilers with missing or broken + * structure assignment. You'll need to fix this code if you have + * such a compiler and you change MAX_COMPS_IN_SCAN. + */ + +#ifndef NO_STRUCT_ASSIGN +#define ASSIGN_STATE(dest,src) ((dest) = (src)) +#else +#if MAX_COMPS_IN_SCAN == 4 +#define ASSIGN_STATE(dest,src) \ + ((dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) +#endif +#endif + + +typedef struct { + struct jpeg_entropy_decoder pub; /* public fields */ + + /* These fields are loaded into local variables at start of each MCU. + * In case of suspension, we exit WITHOUT updating them. + */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ + + /* These fields are NOT loaded into local working state. */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ + + /* Pointers to derived tables (these workspaces have image lifespan) */ + d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; + d_derived_tbl * ac_derived_tbls[NUM_HUFF_TBLS]; +} huff_entropy_decoder; + +typedef huff_entropy_decoder * huff_entropy_ptr; + + +/* + * Initialize for a Huffman-compressed scan. + */ + +METHODDEF void +start_pass_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci, dctbl, actbl; + jpeg_component_info * compptr; + + /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG. + * This ought to be an error condition, but we make it a warning because + * there are some baseline files out there with all zeroes in these bytes. + */ + if (cinfo->Ss != 0 || cinfo->Se != DCTSIZE2-1 || + cinfo->Ah != 0 || cinfo->Al != 0) + WARNMS(cinfo, JWRN_NOT_SEQUENTIAL); + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + dctbl = compptr->dc_tbl_no; + actbl = compptr->ac_tbl_no; + /* Make sure requested tables are present */ + if (dctbl < 0 || dctbl >= NUM_HUFF_TBLS || + cinfo->dc_huff_tbl_ptrs[dctbl] == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, dctbl); + if (actbl < 0 || actbl >= NUM_HUFF_TBLS || + cinfo->ac_huff_tbl_ptrs[actbl] == NULL) + ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, actbl); + /* Compute derived values for Huffman tables */ + /* We may do this more than once for a table, but it's not expensive */ + jpeg_make_d_derived_tbl(cinfo, cinfo->dc_huff_tbl_ptrs[dctbl], + & entropy->dc_derived_tbls[dctbl]); + jpeg_make_d_derived_tbl(cinfo, cinfo->ac_huff_tbl_ptrs[actbl], + & entropy->ac_derived_tbls[actbl]); + /* Initialize DC predictions to 0 */ + entropy->saved.last_dc_val[ci] = 0; + } + + /* Initialize bitread state variables */ + entropy->bitstate.bits_left = 0; + entropy->bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */ + entropy->bitstate.printed_eod = FALSE; + + /* Initialize restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; +} + + +/* + * Compute the derived values for a Huffman table. + * Note this is also used by jdphuff.c. + */ + +GLOBAL void +jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, JHUFF_TBL * htbl, + d_derived_tbl ** pdtbl) +{ + d_derived_tbl *dtbl; + int p, i, l, si; + int lookbits, ctr; + char huffsize[257]; + unsigned int huffcode[257]; + unsigned int code; + + /* Allocate a workspace if we haven't already done so. */ + if (*pdtbl == NULL) + *pdtbl = (d_derived_tbl *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(d_derived_tbl)); + dtbl = *pdtbl; + dtbl->pub = htbl; /* fill in back link */ + + /* Figure C.1: make table of Huffman code length for each symbol */ + /* Note that this is in code-length order. */ + + p = 0; + for (l = 1; l <= 16; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++) + huffsize[p++] = (char) l; + } + huffsize[p] = 0; + + /* Figure C.2: generate the codes themselves */ + /* Note that this is in code-length order. */ + + code = 0; + si = huffsize[0]; + p = 0; + while (huffsize[p]) { + while (((int) huffsize[p]) == si) { + huffcode[p++] = code; + code++; + } + code <<= 1; + si++; + } + + /* Figure F.15: generate decoding tables for bit-sequential decoding */ + + p = 0; + for (l = 1; l <= 16; l++) { + if (htbl->bits[l]) { + dtbl->valptr[l] = p; /* huffval[] index of 1st symbol of code length l */ + dtbl->mincode[l] = huffcode[p]; /* minimum code of length l */ + p += htbl->bits[l]; + dtbl->maxcode[l] = huffcode[p-1]; /* maximum code of length l */ + } else { + dtbl->maxcode[l] = -1; /* -1 if no codes of this length */ + } + } + dtbl->maxcode[17] = 0xFFFFFL; /* ensures jpeg_huff_decode terminates */ + + /* Compute lookahead tables to speed up decoding. + * First we set all the table entries to 0, indicating "too long"; + * then we iterate through the Huffman codes that are short enough and + * fill in all the entries that correspond to bit sequences starting + * with that code. + */ + + MEMZERO(dtbl->look_nbits, SIZEOF(dtbl->look_nbits)); + + p = 0; + for (l = 1; l <= HUFF_LOOKAHEAD; l++) { + for (i = 1; i <= (int) htbl->bits[l]; i++, p++) { + /* l = current code's length, p = its index in huffcode[] & huffval[]. */ + /* Generate left-justified code followed by all possible bit sequences */ + lookbits = huffcode[p] << (HUFF_LOOKAHEAD-l); + for (ctr = 1 << (HUFF_LOOKAHEAD-l); ctr > 0; ctr--) { + dtbl->look_nbits[lookbits] = l; + dtbl->look_sym[lookbits] = htbl->huffval[p]; + lookbits++; + } + } + } +} + + +/* + * Out-of-line code for bit fetching (shared with jdphuff.c). + * See jdhuff.h for info about usage. + * Note: current values of get_buffer and bits_left are passed as parameters, + * but are returned in the corresponding fields of the state struct. + * + * On most machines MIN_GET_BITS should be 25 to allow the full 32-bit width + * of get_buffer to be used. (On machines with wider words, an even larger + * buffer could be used.) However, on some machines 32-bit shifts are + * quite slow and take time proportional to the number of places shifted. + * (This is true with most PC compilers, for instance.) In this case it may + * be a win to set MIN_GET_BITS to the minimum value of 15. This reduces the + * average shift distance at the cost of more calls to jpeg_fill_bit_buffer. + */ + +#ifdef SLOW_SHIFT_32 +#define MIN_GET_BITS 15 /* minimum allowable value */ +#else +#define MIN_GET_BITS (BIT_BUF_SIZE-7) +#endif + + +GLOBAL boolean +jpeg_fill_bit_buffer (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits) +/* Load up the bit buffer to a depth of at least nbits */ +{ + /* Copy heavily used state fields into locals (hopefully registers) */ + register const JOCTET * next_input_byte = state->next_input_byte; + register size_t bytes_in_buffer = state->bytes_in_buffer; + register int c; + + /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ + /* (It is assumed that no request will be for more than that many bits.) */ + + while (bits_left < MIN_GET_BITS) { + /* Attempt to read a byte */ + if (state->unread_marker != 0) + goto no_more_data; /* can't advance past a marker */ + + if (bytes_in_buffer == 0) { + if (! (*state->cinfo->src->fill_input_buffer) (state->cinfo)) + return FALSE; + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + + /* If it's 0xFF, check and discard stuffed zero byte */ + if (c == 0xFF) { + do { + if (bytes_in_buffer == 0) { + if (! (*state->cinfo->src->fill_input_buffer) (state->cinfo)) + return FALSE; + next_input_byte = state->cinfo->src->next_input_byte; + bytes_in_buffer = state->cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + } while (c == 0xFF); + + if (c == 0) { + /* Found FF/00, which represents an FF data byte */ + c = 0xFF; + } else { + /* Oops, it's actually a marker indicating end of compressed data. */ + /* Better put it back for use later */ + state->unread_marker = c; + + no_more_data: + /* There should be enough bits still left in the data segment; */ + /* if so, just break out of the outer while loop. */ + if (bits_left >= nbits) + break; + /* Uh-oh. Report corrupted data to user and stuff zeroes into + * the data stream, so that we can produce some kind of image. + * Note that this code will be repeated for each byte demanded + * for the rest of the segment. We use a nonvolatile flag to ensure + * that only one warning message appears. + */ + if (! *(state->printed_eod_ptr)) { + WARNMS(state->cinfo, JWRN_HIT_MARKER); + *(state->printed_eod_ptr) = TRUE; + } + c = 0; /* insert a zero byte into bit buffer */ + } + } + + /* OK, load c into get_buffer */ + get_buffer = (get_buffer << 8) | c; + bits_left += 8; + } + + /* Unload the local registers */ + state->next_input_byte = next_input_byte; + state->bytes_in_buffer = bytes_in_buffer; + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + return TRUE; +} + + +/* + * Out-of-line code for Huffman code decoding. + * See jdhuff.h for info about usage. + */ + +GLOBAL int +jpeg_huff_decode (bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits) +{ + register int l = min_bits; + register INT32 code; + + /* HUFF_DECODE has determined that the code is at least min_bits */ + /* bits long, so fetch that many bits in one swoop. */ + + CHECK_BIT_BUFFER(*state, l, return -1); + code = GET_BITS(l); + + /* Collect the rest of the Huffman code one bit at a time. */ + /* This is per Figure F.16 in the JPEG spec. */ + + while (code > htbl->maxcode[l]) { + code <<= 1; + CHECK_BIT_BUFFER(*state, 1, return -1); + code |= GET_BITS(1); + l++; + } + + /* Unload the local registers */ + state->get_buffer = get_buffer; + state->bits_left = bits_left; + + /* With garbage input we may reach the sentinel value l = 17. */ + + if (l > 16) { + WARNMS(state->cinfo, JWRN_HUFF_BAD_CODE); + return 0; /* fake a zero as the safest result */ + } + + return htbl->pub->huffval[ htbl->valptr[l] + + ((int) (code - htbl->mincode[l])) ]; +} + + +/* + * Figure F.12: extend sign bit. + * On some machines, a shift and add will be faster than a table lookup. + */ + +#ifdef AVOID_TABLES + +#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x)) + +#else + +#define HUFF_EXTEND(x,s) ((x) < extend_test[s] ? (x) + extend_offset[s] : (x)) + +static const int extend_test[16] = /* entry n is 2**(n-1) */ + { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, + 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; + +static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ + { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, + ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, + ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, + ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; + +#endif /* AVOID_TABLES */ + + +/* + * Check for a restart marker & resynchronize decoder. + * Returns FALSE if must suspend. + */ + +LOCAL boolean +process_restart (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + int ci; + + /* Throw away any unused bits remaining in bit buffer; */ + /* include any full bytes in next_marker's count of discarded bytes */ + cinfo->marker->discarded_bytes += entropy->bitstate.bits_left / 8; + entropy->bitstate.bits_left = 0; + + /* Advance past the RSTn marker */ + if (! (*cinfo->marker->read_restart_marker) (cinfo)) + return FALSE; + + /* Re-initialize DC predictions to 0 */ + for (ci = 0; ci < cinfo->comps_in_scan; ci++) + entropy->saved.last_dc_val[ci] = 0; + + /* Reset restart counter */ + entropy->restarts_to_go = cinfo->restart_interval; + + /* Next segment can get another out-of-data warning */ + entropy->bitstate.printed_eod = FALSE; + + return TRUE; +} + + +/* + * Decode and return one MCU's worth of Huffman-compressed coefficients. + * The coefficients are reordered from zigzag order into natural array order, + * but are not dequantized. + * + * The i'th block of the MCU is stored into the block pointed to by + * MCU_data[i]. WE ASSUME THIS AREA HAS BEEN ZEROED BY THE CALLER. + * (Wholesale zeroing is usually a little faster than retail...) + * + * Returns FALSE if data source requested suspension. In that case no + * changes have been made to permanent state. (Exception: some output + * coefficients may already have been assigned. This is harmless for + * this module, since we'll just re-assign them on the next call.) + */ + +METHODDEF boolean +decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) +{ + huff_entropy_ptr entropy = (huff_entropy_ptr) cinfo->entropy; + register int s, k, r; + int blkn, ci; + JBLOCKROW block; + BITREAD_STATE_VARS; + savable_state state; + d_derived_tbl * dctbl; + d_derived_tbl * actbl; + jpeg_component_info * compptr; + + /* Process restart marker if needed; may have to suspend */ + if (cinfo->restart_interval) { + if (entropy->restarts_to_go == 0) + if (! process_restart(cinfo)) + return FALSE; + } + + /* Load up working state */ + BITREAD_LOAD_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(state, entropy->saved); + + /* Outer loop handles each block in the MCU */ + + for (blkn = 0; blkn < cinfo->blocks_in_MCU; blkn++) { + block = MCU_data[blkn]; + ci = cinfo->MCU_membership[blkn]; + compptr = cinfo->cur_comp_info[ci]; + dctbl = entropy->dc_derived_tbls[compptr->dc_tbl_no]; + actbl = entropy->ac_derived_tbls[compptr->ac_tbl_no]; + + /* Decode a single block's worth of coefficients */ + + /* Section F.2.2.1: decode the DC coefficient difference */ + HUFF_DECODE(s, br_state, dctbl, return FALSE, label1); + if (s) { + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + } + + /* Shortcut if component's values are not interesting */ + if (! compptr->component_needed) + goto skip_ACs; + + /* Convert DC difference to actual value, update last_dc_val */ + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */ + (*block)[0] = (JCOEF) s; + + /* Do we need to decode the AC coefficients for this component? */ + if (compptr->DCT_scaled_size > 1) { + + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label2); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in jpeg_natural_order[] will save us + * if k >= DCTSIZE2, which could happen if the data is corrupted. + */ + (*block)[jpeg_natural_order[k]] = (JCOEF) s; + } else { + if (r != 15) + break; + k += 15; + } + } + + } else { +skip_ACs: + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label3); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } else { + if (r != 15) + break; + k += 15; + } + } + + } + } + + /* Completed MCU, so update state */ + BITREAD_SAVE_STATE(cinfo,entropy->bitstate); + ASSIGN_STATE(entropy->saved, state); + + /* Account for restart interval (no-op if not using restarts) */ + entropy->restarts_to_go--; + + return TRUE; +} + + +/* + * Module initialization routine for Huffman entropy decoding. + */ + +GLOBAL void +jinit_huff_decoder (j_decompress_ptr cinfo) +{ + huff_entropy_ptr entropy; + int i; + + entropy = (huff_entropy_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(huff_entropy_decoder)); + cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; + entropy->pub.start_pass = start_pass_huff_decoder; + entropy->pub.decode_mcu = decode_mcu; + + /* Mark tables unallocated */ + for (i = 0; i < NUM_HUFF_TBLS; i++) { + entropy->dc_derived_tbls[i] = entropy->ac_derived_tbls[i] = NULL; + } +} diff --git a/code/jpeg-6/jdhuff.h b/code/jpeg-6/jdhuff.h new file mode 100644 index 0000000..d375c78 --- /dev/null +++ b/code/jpeg-6/jdhuff.h @@ -0,0 +1,202 @@ +/* + * jdhuff.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains declarations for Huffman entropy decoding routines + * that are shared between the sequential decoder (jdhuff.c) and the + * progressive decoder (jdphuff.c). No other modules need to see these. + */ + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_make_d_derived_tbl jMkDDerived +#define jpeg_fill_bit_buffer jFilBitBuf +#define jpeg_huff_decode jHufDecode +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Derived data constructed for each Huffman table */ + +#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ + +typedef struct { + /* Basic tables: (element [0] of each array is unused) */ + INT32 mincode[17]; /* smallest code of length k */ + INT32 maxcode[18]; /* largest code of length k (-1 if none) */ + /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */ + int valptr[17]; /* huffval[] index of 1st symbol of length k */ + + /* Link to public Huffman table (needed only in jpeg_huff_decode) */ + JHUFF_TBL *pub; + + /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of + * the input data stream. If the next Huffman code is no more + * than HUFF_LOOKAHEAD bits long, we can obtain its length and + * the corresponding symbol directly from these tables. + */ + int look_nbits[1< 32 bits on your machine, and shifting/masking longs is + * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE + * appropriately should be a win. Unfortunately we can't do this with + * something like #define BIT_BUF_SIZE (sizeof(bit_buf_type)*8) + * because not all machines measure sizeof in 8-bit bytes. + */ + +typedef struct { /* Bitreading state saved across MCUs */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + boolean printed_eod; /* flag to suppress multiple warning msgs */ +} bitread_perm_state; + +typedef struct { /* Bitreading working state within an MCU */ + /* current data source state */ + const JOCTET * next_input_byte; /* => next byte to read from source */ + size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ + int unread_marker; /* nonzero if we have hit a marker */ + /* bit input buffer --- note these values are kept in register variables, + * not in this struct, inside the inner loops. + */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ + /* pointers needed by jpeg_fill_bit_buffer */ + j_decompress_ptr cinfo; /* back link to decompress master record */ + boolean * printed_eod_ptr; /* => flag in permanent state */ +} bitread_working_state; + +/* Macros to declare and load/save bitread local variables. */ +#define BITREAD_STATE_VARS \ + register bit_buf_type get_buffer; \ + register int bits_left; \ + bitread_working_state br_state + +#define BITREAD_LOAD_STATE(cinfop,permstate) \ + br_state.cinfo = cinfop; \ + br_state.next_input_byte = cinfop->src->next_input_byte; \ + br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ + br_state.unread_marker = cinfop->unread_marker; \ + get_buffer = permstate.get_buffer; \ + bits_left = permstate.bits_left; \ + br_state.printed_eod_ptr = & permstate.printed_eod + +#define BITREAD_SAVE_STATE(cinfop,permstate) \ + cinfop->src->next_input_byte = br_state.next_input_byte; \ + cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ + cinfop->unread_marker = br_state.unread_marker; \ + permstate.get_buffer = get_buffer; \ + permstate.bits_left = bits_left + +/* + * These macros provide the in-line portion of bit fetching. + * Use CHECK_BIT_BUFFER to ensure there are N bits in get_buffer + * before using GET_BITS, PEEK_BITS, or DROP_BITS. + * The variables get_buffer and bits_left are assumed to be locals, + * but the state struct might not be (jpeg_huff_decode needs this). + * CHECK_BIT_BUFFER(state,n,action); + * Ensure there are N bits in get_buffer; if suspend, take action. + * val = GET_BITS(n); + * Fetch next N bits. + * val = PEEK_BITS(n); + * Fetch next N bits without removing them from the buffer. + * DROP_BITS(n); + * Discard next N bits. + * The value N should be a simple variable, not an expression, because it + * is evaluated multiple times. + */ + +#define CHECK_BIT_BUFFER(state,nbits,action) \ + { if (bits_left < (nbits)) { \ + if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits)) \ + { action; } \ + get_buffer = (state).get_buffer; bits_left = (state).bits_left; } } + +#define GET_BITS(nbits) \ + (((int) (get_buffer >> (bits_left -= (nbits)))) & ((1<<(nbits))-1)) + +#define PEEK_BITS(nbits) \ + (((int) (get_buffer >> (bits_left - (nbits)))) & ((1<<(nbits))-1)) + +#define DROP_BITS(nbits) \ + (bits_left -= (nbits)) + +/* Load up the bit buffer to a depth of at least nbits */ +EXTERN boolean jpeg_fill_bit_buffer JPP((bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + int nbits)); + + +/* + * Code for extracting next Huffman-coded symbol from input bit stream. + * Again, this is time-critical and we make the main paths be macros. + * + * We use a lookahead table to process codes of up to HUFF_LOOKAHEAD bits + * without looping. Usually, more than 95% of the Huffman codes will be 8 + * or fewer bits long. The few overlength codes are handled with a loop, + * which need not be inline code. + * + * Notes about the HUFF_DECODE macro: + * 1. Near the end of the data segment, we may fail to get enough bits + * for a lookahead. In that case, we do it the hard way. + * 2. If the lookahead table contains no entry, the next code must be + * more than HUFF_LOOKAHEAD bits long. + * 3. jpeg_huff_decode returns -1 if forced to suspend. + */ + +#define HUFF_DECODE(result,state,htbl,failaction,slowlabel) \ +{ register int nb, look; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + if (! jpeg_fill_bit_buffer(&state,get_buffer,bits_left, 0)) {failaction;} \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + if (bits_left < HUFF_LOOKAHEAD) { \ + nb = 1; goto slowlabel; \ + } \ + } \ + look = PEEK_BITS(HUFF_LOOKAHEAD); \ + if ((nb = htbl->look_nbits[look]) != 0) { \ + DROP_BITS(nb); \ + result = htbl->look_sym[look]; \ + } else { \ + nb = HUFF_LOOKAHEAD+1; \ +slowlabel: \ + if ((result=jpeg_huff_decode(&state,get_buffer,bits_left,htbl,nb)) < 0) \ + { failaction; } \ + get_buffer = state.get_buffer; bits_left = state.bits_left; \ + } \ +} + +/* Out-of-line case for Huffman code fetching */ +EXTERN int jpeg_huff_decode JPP((bitread_working_state * state, + register bit_buf_type get_buffer, register int bits_left, + d_derived_tbl * htbl, int min_bits)); diff --git a/code/jpeg-6/jdinput.cpp b/code/jpeg-6/jdinput.cpp new file mode 100644 index 0000000..f9acbec --- /dev/null +++ b/code/jpeg-6/jdinput.cpp @@ -0,0 +1,386 @@ +/* + * jdinput.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains input control logic for the JPEG decompressor. + * These routines are concerned with controlling the decompressor's input + * processing (marker reading and coefficient decoding). The actual input + * reading is done in jdmarker.c, jdhuff.c, and jdphuff.c. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_input_controller pub; /* public fields */ + + boolean inheaders; /* TRUE until first SOS is reached */ +} my_input_controller; + +typedef my_input_controller * my_inputctl_ptr; + + +/* Forward declarations */ +METHODDEF int consume_markers JPP((j_decompress_ptr cinfo)); + + +/* + * Routines to calculate various quantities related to the size of the image. + */ + +LOCAL void +initial_setup (j_decompress_ptr cinfo) +/* Called once, when first SOS marker is reached */ +{ + int ci; + jpeg_component_info *compptr; + + /* Make sure image isn't bigger than I can handle */ + if ((long) cinfo->image_height > (long) JPEG_MAX_DIMENSION || + (long) cinfo->image_width > (long) JPEG_MAX_DIMENSION) + ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION); + + /* For now, precision must match compiled-in value... */ + if (cinfo->data_precision != BITS_IN_JSAMPLE) + ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo->data_precision); + + /* Check that number of components won't exceed internal array sizes */ + if (cinfo->num_components > MAX_COMPONENTS) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, + MAX_COMPONENTS); + + /* Compute maximum sampling factors; check factor validity */ + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + ERREXIT(cinfo, JERR_BAD_SAMPLING); + cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, + compptr->h_samp_factor); + cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, + compptr->v_samp_factor); + } + + /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE. + * In the full decompressor, this will be overridden by jdmaster.c; + * but in the transcoder, jdmaster.c is not used, so we must do it here. + */ + cinfo->min_DCT_scaled_size = DCTSIZE; + + /* Compute dimensions of components */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->DCT_scaled_size = DCTSIZE; + /* Size in DCT blocks */ + compptr->width_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->height_in_blocks = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + /* downsampled_width and downsampled_height will also be overridden by + * jdmaster.c if we are doing full decompression. The transcoder library + * doesn't use these values, but the calling application might. + */ + /* Size in samples */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, + (long) cinfo->max_h_samp_factor); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, + (long) cinfo->max_v_samp_factor); + /* Mark component needed, until color conversion says otherwise */ + compptr->component_needed = TRUE; + /* Mark no quantization table yet saved for component */ + compptr->quant_table = NULL; + } + + /* Compute number of fully interleaved MCU rows. */ + cinfo->total_iMCU_rows = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + /* Decide whether file contains multiple scans */ + if (cinfo->comps_in_scan < cinfo->num_components || cinfo->progressive_mode) + cinfo->inputctl->has_multiple_scans = TRUE; + else + cinfo->inputctl->has_multiple_scans = FALSE; +} + + +LOCAL void +per_scan_setup (j_decompress_ptr cinfo) +/* Do computations that are needed before processing a JPEG scan */ +/* cinfo->comps_in_scan and cinfo->cur_comp_info[] were set from SOS marker */ +{ + int ci, mcublks, tmp; + jpeg_component_info *compptr; + + if (cinfo->comps_in_scan == 1) { + + /* Noninterleaved (single-component) scan */ + compptr = cinfo->cur_comp_info[0]; + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = compptr->width_in_blocks; + cinfo->MCU_rows_in_scan = compptr->height_in_blocks; + + /* For noninterleaved scan, always one block per MCU */ + compptr->MCU_width = 1; + compptr->MCU_height = 1; + compptr->MCU_blocks = 1; + compptr->MCU_sample_width = compptr->DCT_scaled_size; + compptr->last_col_width = 1; + /* For noninterleaved scans, it is convenient to define last_row_height + * as the number of block rows present in the last iMCU row. + */ + tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); + if (tmp == 0) tmp = compptr->v_samp_factor; + compptr->last_row_height = tmp; + + /* Prepare array describing MCU composition */ + cinfo->blocks_in_MCU = 1; + cinfo->MCU_membership[0] = 0; + + } else { + + /* Interleaved (multi-component) scan */ + if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) + ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, + MAX_COMPS_IN_SCAN); + + /* Overall image size in MCUs */ + cinfo->MCUs_per_row = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, + (long) (cinfo->max_h_samp_factor*DCTSIZE)); + cinfo->MCU_rows_in_scan = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + + cinfo->blocks_in_MCU = 0; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* Sampling factors give # of blocks of component in each MCU */ + compptr->MCU_width = compptr->h_samp_factor; + compptr->MCU_height = compptr->v_samp_factor; + compptr->MCU_blocks = compptr->MCU_width * compptr->MCU_height; + compptr->MCU_sample_width = compptr->MCU_width * compptr->DCT_scaled_size; + /* Figure number of non-dummy blocks in last MCU column & row */ + tmp = (int) (compptr->width_in_blocks % compptr->MCU_width); + if (tmp == 0) tmp = compptr->MCU_width; + compptr->last_col_width = tmp; + tmp = (int) (compptr->height_in_blocks % compptr->MCU_height); + if (tmp == 0) tmp = compptr->MCU_height; + compptr->last_row_height = tmp; + /* Prepare array describing MCU composition */ + mcublks = compptr->MCU_blocks; + if (cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU) + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + while (mcublks-- > 0) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + } + } + + } +} + + +/* + * Save away a copy of the Q-table referenced by each component present + * in the current scan, unless already saved during a prior scan. + * + * In a multiple-scan JPEG file, the encoder could assign different components + * the same Q-table slot number, but change table definitions between scans + * so that each component uses a different Q-table. (The IJG encoder is not + * currently capable of doing this, but other encoders might.) Since we want + * to be able to dequantize all the components at the end of the file, this + * means that we have to save away the table actually used for each component. + * We do this by copying the table at the start of the first scan containing + * the component. + * The JPEG spec prohibits the encoder from changing the contents of a Q-table + * slot between scans of a component using that slot. If the encoder does so + * anyway, this decoder will simply use the Q-table values that were current + * at the start of the first scan for the component. + * + * The decompressor output side looks only at the saved quant tables, + * not at the current Q-table slots. + */ + +LOCAL void +latch_quant_tables (j_decompress_ptr cinfo) +{ + int ci, qtblno; + jpeg_component_info *compptr; + JQUANT_TBL * qtbl; + + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { + compptr = cinfo->cur_comp_info[ci]; + /* No work if we already saved Q-table for this component */ + if (compptr->quant_table != NULL) + continue; + /* Make sure specified quantization table is present */ + qtblno = compptr->quant_tbl_no; + if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || + cinfo->quant_tbl_ptrs[qtblno] == NULL) + ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); + /* OK, save away the quantization table */ + qtbl = (JQUANT_TBL *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(JQUANT_TBL)); + MEMCOPY(qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF(JQUANT_TBL)); + compptr->quant_table = qtbl; + } +} + + +/* + * Initialize the input modules to read a scan of compressed data. + * The first call to this is done by jdmaster.c after initializing + * the entire decompressor (during jpeg_start_decompress). + * Subsequent calls come from consume_markers, below. + */ + +METHODDEF void +start_input_pass (j_decompress_ptr cinfo) +{ + per_scan_setup(cinfo); + latch_quant_tables(cinfo); + (*cinfo->entropy->start_pass) (cinfo); + (*cinfo->coef->start_input_pass) (cinfo); + cinfo->inputctl->consume_input = cinfo->coef->consume_data; +} + + +/* + * Finish up after inputting a compressed-data scan. + * This is called by the coefficient controller after it's read all + * the expected data of the scan. + */ + +METHODDEF void +finish_input_pass (j_decompress_ptr cinfo) +{ + cinfo->inputctl->consume_input = consume_markers; +} + + +/* + * Read JPEG markers before, between, or after compressed-data scans. + * Change state as necessary when a new scan is reached. + * Return value is JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + * + * The consume_input method pointer points either here or to the + * coefficient controller's consume_data routine, depending on whether + * we are reading a compressed data segment or inter-segment markers. + */ + +METHODDEF int +consume_markers (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + int val; + + if (inputctl->pub.eoi_reached) /* After hitting EOI, read no further */ + return JPEG_REACHED_EOI; + + val = (*cinfo->marker->read_markers) (cinfo); + + switch (val) { + case JPEG_REACHED_SOS: /* Found SOS */ + if (inputctl->inheaders) { /* 1st SOS */ + initial_setup(cinfo); + inputctl->inheaders = FALSE; + /* Note: start_input_pass must be called by jdmaster.c + * before any more input can be consumed. jdapi.c is + * responsible for enforcing this sequencing. + */ + } else { /* 2nd or later SOS marker */ + if (! inputctl->pub.has_multiple_scans) + ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */ + start_input_pass(cinfo); + } + break; + case JPEG_REACHED_EOI: /* Found EOI */ + inputctl->pub.eoi_reached = TRUE; + if (inputctl->inheaders) { /* Tables-only datastream, apparently */ + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_NO_SOS); + } else { + /* Prevent infinite loop in coef ctlr's decompress_data routine + * if user set output_scan_number larger than number of scans. + */ + if (cinfo->output_scan_number > cinfo->input_scan_number) + cinfo->output_scan_number = cinfo->input_scan_number; + } + break; + case JPEG_SUSPENDED: + break; + } + + return val; +} + + +/* + * Reset state to begin a fresh datastream. + */ + +METHODDEF void +reset_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl = (my_inputctl_ptr) cinfo->inputctl; + + inputctl->pub.consume_input = consume_markers; + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; + /* Reset other modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->marker->reset_marker_reader) (cinfo); + /* Reset progression state -- would be cleaner if entropy decoder did this */ + cinfo->coef_bits = NULL; +} + + +/* + * Initialize the input controller module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_input_controller (j_decompress_ptr cinfo) +{ + my_inputctl_ptr inputctl; + + /* Create subobject in permanent pool */ + inputctl = (my_inputctl_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_input_controller)); + cinfo->inputctl = (struct jpeg_input_controller *) inputctl; + /* Initialize method pointers */ + inputctl->pub.consume_input = consume_markers; + inputctl->pub.reset_input_controller = reset_input_controller; + inputctl->pub.start_input_pass = start_input_pass; + inputctl->pub.finish_input_pass = finish_input_pass; + /* Initialize state: can't use reset_input_controller since we don't + * want to try to reset other modules yet. + */ + inputctl->pub.has_multiple_scans = FALSE; /* "unknown" would be better */ + inputctl->pub.eoi_reached = FALSE; + inputctl->inheaders = TRUE; +} diff --git a/code/jpeg-6/jdmainct.cpp b/code/jpeg-6/jdmainct.cpp new file mode 100644 index 0000000..852f8ac --- /dev/null +++ b/code/jpeg-6/jdmainct.cpp @@ -0,0 +1,525 @@ +/* + * jdmainct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the main buffer controller for decompression. + * The main buffer lies between the JPEG decompressor proper and the + * post-processor; it holds downsampled data in the JPEG colorspace. + * + * Note that this code is bypassed in raw-data mode, since the application + * supplies the equivalent of the main buffer in that case. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * In the current system design, the main buffer need never be a full-image + * buffer; any full-height buffers will be found inside the coefficient or + * postprocessing controllers. Nonetheless, the main controller is not + * trivial. Its responsibility is to provide context rows for upsampling/ + * rescaling, and doing this in an efficient fashion is a bit tricky. + * + * Postprocessor input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. (We require DCT_scaled_size values to be + * chosen such that these numbers are integers. In practice DCT_scaled_size + * values will likely be powers of two, so we actually have the stronger + * condition that DCT_scaled_size / min_DCT_scaled_size is an integer.) + * Upsampling will typically produce max_v_samp_factor pixel rows from each + * row group (times any additional scale factor that the upsampler is + * applying). + * + * The coefficient controller will deliver data to us one iMCU row at a time; + * each iMCU row contains v_samp_factor * DCT_scaled_size sample rows, or + * exactly min_DCT_scaled_size row groups. (This amount of data corresponds + * to one row of MCUs when the image is fully interleaved.) Note that the + * number of sample rows varies across components, but the number of row + * groups does not. Some garbage sample rows may be included in the last iMCU + * row at the bottom of the image. + * + * Depending on the vertical scaling algorithm used, the upsampler may need + * access to the sample row(s) above and below its current input row group. + * The upsampler is required to set need_context_rows TRUE at global selection + * time if so. When need_context_rows is FALSE, this controller can simply + * obtain one iMCU row at a time from the coefficient controller and dole it + * out as row groups to the postprocessor. + * + * When need_context_rows is TRUE, this controller guarantees that the buffer + * passed to postprocessing contains at least one row group's worth of samples + * above and below the row group(s) being processed. Note that the context + * rows "above" the first passed row group appear at negative row offsets in + * the passed buffer. At the top and bottom of the image, the required + * context rows are manufactured by duplicating the first or last real sample + * row; this avoids having special cases in the upsampling inner loops. + * + * The amount of context is fixed at one row group just because that's a + * convenient number for this controller to work with. The existing + * upsamplers really only need one sample row of context. An upsampler + * supporting arbitrary output rescaling might wish for more than one row + * group of context when shrinking the image; tough, we don't handle that. + * (This is justified by the assumption that downsizing will be handled mostly + * by adjusting the DCT_scaled_size values, so that the actual scale factor at + * the upsample step needn't be much less than one.) + * + * To provide the desired context, we have to retain the last two row groups + * of one iMCU row while reading in the next iMCU row. (The last row group + * can't be processed until we have another row group for its below-context, + * and so we have to save the next-to-last group too for its above-context.) + * We could do this most simply by copying data around in our buffer, but + * that'd be very slow. We can avoid copying any data by creating a rather + * strange pointer structure. Here's how it works. We allocate a workspace + * consisting of M+2 row groups (where M = min_DCT_scaled_size is the number + * of row groups per iMCU row). We create two sets of redundant pointers to + * the workspace. Labeling the physical row groups 0 to M+1, the synthesized + * pointer lists look like this: + * M+1 M-1 + * master pointer --> 0 master pointer --> 0 + * 1 1 + * ... ... + * M-3 M-3 + * M-2 M + * M-1 M+1 + * M M-2 + * M+1 M-1 + * 0 0 + * We read alternate iMCU rows using each master pointer; thus the last two + * row groups of the previous iMCU row remain un-overwritten in the workspace. + * The pointer lists are set up so that the required context rows appear to + * be adjacent to the proper places when we pass the pointer lists to the + * upsampler. + * + * The above pictures describe the normal state of the pointer lists. + * At top and bottom of the image, we diddle the pointer lists to duplicate + * the first or last sample row as necessary (this is cheaper than copying + * sample rows around). + * + * This scheme breaks down if M < 2, ie, min_DCT_scaled_size is 1. In that + * situation each iMCU row provides only one row group so the buffering logic + * must be different (eg, we must read two iMCU rows before we can emit the + * first row group). For now, we simply do not support providing context + * rows when min_DCT_scaled_size is 1. That combination seems unlikely to + * be worth providing --- if someone wants a 1/8th-size preview, they probably + * want it quick and dirty, so a context-free upsampler is sufficient. + */ + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_main_controller pub; /* public fields */ + + /* Pointer to allocated workspace (M or M+2 row groups). */ + JSAMPARRAY buffer[MAX_COMPONENTS]; + + boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ + JDIMENSION rowgroup_ctr; /* counts row groups output to postprocessor */ + + /* Remaining fields are only used in the context case. */ + + /* These are the master pointers to the funny-order pointer lists. */ + JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ + + int whichptr; /* indicates which pointer set is now in use */ + int context_state; /* process_data state machine status */ + JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ + JDIMENSION iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ +} my_main_controller; + +typedef my_main_controller * my_main_ptr; + +/* context_state values: */ +#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ +#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ +#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ + + +/* Forward declarations */ +METHODDEF void process_data_simple_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +METHODDEF void process_data_context_main + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF void process_data_crank_post + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); +#endif + + +LOCAL void +alloc_funny_pointers (j_decompress_ptr cinfo) +/* Allocate space for the funny pointer lists. + * This is done only once, not once per pass. + */ +{ + // bk001204 - no use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + int ci, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + /* Get top-level space for component array pointers. + * We alloc both arrays with one call to save a few cycles. + */ + jmain->xbuffer[0] = (JSAMPIMAGE) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * 2 * SIZEOF(JSAMPARRAY)); + jmain->xbuffer[1] = jmain->xbuffer[0] + cinfo->num_components; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + /* Get space for pointer lists --- M+4 row groups in each list. + * We alloc both pointer lists with one call to save a few cycles. + */ + xbuf = (JSAMPARRAY) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + 2 * (rgroup * (M + 4)) * SIZEOF(JSAMPROW)); + xbuf += rgroup; /* want one row group at negative offsets */ + jmain->xbuffer[0][ci] = xbuf; + xbuf += rgroup * (M + 4); + jmain->xbuffer[1][ci] = xbuf; + } +} + + +LOCAL void +make_funny_pointers (j_decompress_ptr cinfo) +/* Create the funny pointer lists discussed in the comments above. + * The actual workspace is already allocated (in main->buffer), + * and the space for the pointer lists is allocated too. + * This routine just fills in the curiously ordered lists. + * This will be repeated at the beginning of each pass. + */ +{ + // bk001204 - no use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY buf, xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = jmain->xbuffer[0][ci]; + xbuf1 = jmain->xbuffer[1][ci]; + /* First copy the workspace pointers as-is */ + buf = jmain->buffer[ci]; + for (i = 0; i < rgroup * (M + 2); i++) { + xbuf0[i] = xbuf1[i] = buf[i]; + } + /* In the second list, put the last four row groups in swapped order */ + for (i = 0; i < rgroup * 2; i++) { + xbuf1[rgroup*(M-2) + i] = buf[rgroup*M + i]; + xbuf1[rgroup*M + i] = buf[rgroup*(M-2) + i]; + } + /* The wraparound pointers at top and bottom will be filled later + * (see set_wraparound_pointers, below). Initially we want the "above" + * pointers to duplicate the first actual data line. This only needs + * to happen in xbuffer[0]. + */ + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[0]; + } + } +} + + +LOCAL void +set_wraparound_pointers (j_decompress_ptr cinfo) +/* Set up the "wraparound" pointers at top and bottom of the pointer lists. + * This changes the pointer list state from top-of-image to the normal state. + */ +{ + // bk001204 - no use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + int ci, i, rgroup; + int M = cinfo->min_DCT_scaled_size; + jpeg_component_info *compptr; + JSAMPARRAY xbuf0, xbuf1; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + xbuf0 = jmain->xbuffer[0][ci]; + xbuf1 = jmain->xbuffer[1][ci]; + for (i = 0; i < rgroup; i++) { + xbuf0[i - rgroup] = xbuf0[rgroup*(M+1) + i]; + xbuf1[i - rgroup] = xbuf1[rgroup*(M+1) + i]; + xbuf0[rgroup*(M+2) + i] = xbuf0[i]; + xbuf1[rgroup*(M+2) + i] = xbuf1[i]; + } + } +} + + +LOCAL void +set_bottom_pointers (j_decompress_ptr cinfo) +/* Change the pointer lists to duplicate the last sample row at the bottom + * of the image. whichptr indicates which xbuffer holds the final iMCU row. + * Also sets rowgroups_avail to indicate number of nondummy row groups in row. + */ +{ + // bk001204 - no use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + int ci, i, rgroup, iMCUheight, rows_left; + jpeg_component_info *compptr; + JSAMPARRAY xbuf; + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Count sample rows in one iMCU row and in one row group */ + iMCUheight = compptr->v_samp_factor * compptr->DCT_scaled_size; + rgroup = iMCUheight / cinfo->min_DCT_scaled_size; + /* Count nondummy sample rows remaining for this component */ + rows_left = (int) (compptr->downsampled_height % (JDIMENSION) iMCUheight); + if (rows_left == 0) rows_left = iMCUheight; + /* Count nondummy row groups. Should get same answer for each component, + * so we need only do it once. + */ + if (ci == 0) { + jmain->rowgroups_avail = (JDIMENSION) ((rows_left-1) / rgroup + 1); + } + /* Duplicate the last real sample row rgroup*2 times; this pads out the + * last partial rowgroup and ensures at least one full rowgroup of context. + */ + xbuf = jmain->xbuffer[jmain->whichptr][ci]; + for (i = 0; i < rgroup * 2; i++) { + xbuf[rows_left + i] = xbuf[rows_left-1]; + } + } +} + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_main (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + // bk001204 - no use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->upsample->need_context_rows) { + jmain->pub.process_data = process_data_context_main; + make_funny_pointers(cinfo); /* Create the xbuffer[] lists */ + jmain->whichptr = 0; /* Read first iMCU row into xbuffer[0] */ + jmain->context_state = CTX_PREPARE_FOR_IMCU; + jmain->iMCU_row_ctr = 0; + } else { + /* Simple case with no context needed */ + jmain->pub.process_data = process_data_simple_main; + } + jmain->buffer_full = FALSE; /* Mark buffer empty */ + jmain->rowgroup_ctr = 0; + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_CRANK_DEST: + /* For last pass of 2-pass quantization, just crank the postprocessor */ + jmain->pub.process_data = process_data_crank_post; + break; +#endif + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } +} + + +/* + * Process some data. + * This handles the simple case where no context is required. + */ + +METHODDEF void +process_data_simple_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + // bk001204 - no use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + JDIMENSION rowgroups_avail; + + /* Read input data if we haven't filled the main buffer yet */ + if (! jmain->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, jmain->buffer)) + return; /* suspension forced, can do nothing more */ + jmain->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + } + + /* There are always min_DCT_scaled_size row groups in an iMCU row. */ + rowgroups_avail = (JDIMENSION) cinfo->min_DCT_scaled_size; + /* Note: at the bottom of the image, we may pass extra garbage row groups + * to the postprocessor. The postprocessor has to check for bottom + * of image anyway (at row resolution), so no point in us doing it too. + */ + + /* Feed the postprocessor */ + (*cinfo->post->post_process_data) (cinfo, jmain->buffer, + &jmain->rowgroup_ctr, rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + + /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ + if (jmain->rowgroup_ctr >= rowgroups_avail) { + jmain->buffer_full = FALSE; + jmain->rowgroup_ctr = 0; + } +} + + +/* + * Process some data. + * This handles the case where context rows must be provided. + */ + +METHODDEF void +process_data_context_main (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + // bk001204 - no use main + my_main_ptr jmain = (my_main_ptr) cinfo->main; + + /* Read input data if we haven't filled the main buffer yet */ + if (! jmain->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, + jmain->xbuffer[jmain->whichptr])) + return; /* suspension forced, can do nothing more */ + jmain->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + jmain->iMCU_row_ctr++; /* count rows received */ + } + + /* Postprocessor typically will not swallow all the input data it is handed + * in one call (due to filling the output buffer first). Must be prepared + * to exit and restart. This switch lets us keep track of how far we got. + * Note that each case falls through to the next on successful completion. + */ + switch (jmain->context_state) { + case CTX_POSTPONED_ROW: + /* Call postprocessor using previously set pointers for postponed row */ + (*cinfo->post->post_process_data) (cinfo, jmain->xbuffer[jmain->whichptr], + &jmain->rowgroup_ctr, jmain->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (jmain->rowgroup_ctr < jmain->rowgroups_avail) + return; /* Need to suspend */ + jmain->context_state = CTX_PREPARE_FOR_IMCU; + if (*out_row_ctr >= out_rows_avail) + return; /* Postprocessor exactly filled output buf */ + /*FALLTHROUGH*/ + case CTX_PREPARE_FOR_IMCU: + /* Prepare to process first M-1 row groups of this iMCU row */ + jmain->rowgroup_ctr = 0; + jmain->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size - 1); + /* Check for bottom of image: if so, tweak pointers to "duplicate" + * the last sample row, and adjust rowgroups_avail to ignore padding rows. + */ + if (jmain->iMCU_row_ctr == cinfo->total_iMCU_rows) + set_bottom_pointers(cinfo); + jmain->context_state = CTX_PROCESS_IMCU; + /*FALLTHROUGH*/ + case CTX_PROCESS_IMCU: + /* Call postprocessor using previously set pointers */ + (*cinfo->post->post_process_data) (cinfo, jmain->xbuffer[jmain->whichptr], + &jmain->rowgroup_ctr, jmain->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (jmain->rowgroup_ctr < jmain->rowgroups_avail) + return; /* Need to suspend */ + /* After the first iMCU, change wraparound pointers to normal state */ + if (jmain->iMCU_row_ctr == 1) + set_wraparound_pointers(cinfo); + /* Prepare to load new iMCU row using other xbuffer list */ + jmain->whichptr ^= 1; /* 0=>1 or 1=>0 */ + jmain->buffer_full = FALSE; + /* Still need to process last row group of this iMCU row, */ + /* which is saved at index M+1 of the other xbuffer */ + jmain->rowgroup_ctr = (JDIMENSION) (cinfo->min_DCT_scaled_size + 1); + jmain->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size + 2); + jmain->context_state = CTX_POSTPONED_ROW; + } +} + + +/* + * Process some data. + * Final pass of two-pass quantization: just call the postprocessor. + * Source data will be the postprocessor controller's internal buffer. + */ + +#ifdef QUANT_2PASS_SUPPORTED + +METHODDEF void +process_data_crank_post (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + (*cinfo->post->post_process_data) (cinfo, (JSAMPIMAGE) NULL, + (JDIMENSION *) NULL, (JDIMENSION) 0, + output_buf, out_row_ctr, out_rows_avail); +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize main buffer controller. + */ + +GLOBAL void +jinit_d_main_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + // bk001204 - no use main + my_main_ptr jmain; + int ci, rgroup, ngroups; + jpeg_component_info *compptr; + + jmain = (my_main_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_main_controller)); + cinfo->main = (struct jpeg_d_main_controller *) jmain; + jmain->pub.start_pass = start_pass_main; + + if (need_full_buffer) /* shouldn't happen */ + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + + /* Allocate the workspace. + * ngroups is the number of row groups we need. + */ + if (cinfo->upsample->need_context_rows) { + if (cinfo->min_DCT_scaled_size < 2) /* unsupported, see comments above */ + ERREXIT(cinfo, JERR_NOTIMPL); + alloc_funny_pointers(cinfo); /* Alloc space for xbuffer[] lists */ + ngroups = cinfo->min_DCT_scaled_size + 2; + } else { + ngroups = cinfo->min_DCT_scaled_size; + } + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; /* height of a row group of component */ + jmain->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * compptr->DCT_scaled_size, + (JDIMENSION) (rgroup * ngroups)); + } +} diff --git a/code/jpeg-6/jdmarker.cpp b/code/jpeg-6/jdmarker.cpp new file mode 100644 index 0000000..7ef879a --- /dev/null +++ b/code/jpeg-6/jdmarker.cpp @@ -0,0 +1,1058 @@ +/* + * jdmarker.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains routines to decode JPEG datastream markers. + * Most of the complexity arises from our desire to support input + * suspension: if not all of the data for a marker is available, + * we must exit back to the application. On resumption, we reprocess + * the marker. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +typedef enum { /* JPEG marker codes */ + M_SOF0 = 0xc0, + M_SOF1 = 0xc1, + M_SOF2 = 0xc2, + M_SOF3 = 0xc3, + + M_SOF5 = 0xc5, + M_SOF6 = 0xc6, + M_SOF7 = 0xc7, + + M_JPG = 0xc8, + M_SOF9 = 0xc9, + M_SOF10 = 0xca, + M_SOF11 = 0xcb, + + M_SOF13 = 0xcd, + M_SOF14 = 0xce, + M_SOF15 = 0xcf, + + M_DHT = 0xc4, + + M_DAC = 0xcc, + + M_RST0 = 0xd0, + M_RST1 = 0xd1, + M_RST2 = 0xd2, + M_RST3 = 0xd3, + M_RST4 = 0xd4, + M_RST5 = 0xd5, + M_RST6 = 0xd6, + M_RST7 = 0xd7, + + M_SOI = 0xd8, + M_EOI = 0xd9, + M_SOS = 0xda, + M_DQT = 0xdb, + M_DNL = 0xdc, + M_DRI = 0xdd, + M_DHP = 0xde, + M_EXP = 0xdf, + + M_APP0 = 0xe0, + M_APP1 = 0xe1, + M_APP2 = 0xe2, + M_APP3 = 0xe3, + M_APP4 = 0xe4, + M_APP5 = 0xe5, + M_APP6 = 0xe6, + M_APP7 = 0xe7, + M_APP8 = 0xe8, + M_APP9 = 0xe9, + M_APP10 = 0xea, + M_APP11 = 0xeb, + M_APP12 = 0xec, + M_APP13 = 0xed, + M_APP14 = 0xee, + M_APP15 = 0xef, + + M_JPG0 = 0xf0, + M_JPG13 = 0xfd, + M_COM = 0xfe, + + M_TEM = 0x01, + + M_ERROR = 0x100 +} JPEG_MARKER; + + +/* + * Macros for fetching data from the data source module. + * + * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect + * the current restart point; we update them only when we have reached a + * suitable place to restart if a suspension occurs. + */ + +/* Declare and initialize local copies of input pointer/count */ +#define INPUT_VARS(cinfo) \ + struct jpeg_source_mgr * datasrc = (cinfo)->src; \ + const JOCTET * next_input_byte = datasrc->next_input_byte; \ + size_t bytes_in_buffer = datasrc->bytes_in_buffer + +/* Unload the local copies --- do this only at a restart boundary */ +#define INPUT_SYNC(cinfo) \ + ( datasrc->next_input_byte = next_input_byte, \ + datasrc->bytes_in_buffer = bytes_in_buffer ) + +/* Reload the local copies --- seldom used except in MAKE_BYTE_AVAIL */ +#define INPUT_RELOAD(cinfo) \ + ( next_input_byte = datasrc->next_input_byte, \ + bytes_in_buffer = datasrc->bytes_in_buffer ) + +/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available. + * Note we do *not* do INPUT_SYNC before calling fill_input_buffer, + * but we must reload the local copies after a successful fill. + */ +#define MAKE_BYTE_AVAIL(cinfo,action) \ + if (bytes_in_buffer == 0) { \ + if (! (*datasrc->fill_input_buffer) (cinfo)) \ + { action; } \ + INPUT_RELOAD(cinfo); \ + } \ + bytes_in_buffer-- + +/* Read a byte into variable V. + * If must suspend, take the specified action (typically "return FALSE"). + */ +#define INPUT_BYTE(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + V = GETJOCTET(*next_input_byte++); ) + +/* As above, but read two bytes interpreted as an unsigned 16-bit integer. + * V should be declared unsigned int or perhaps INT32. + */ +#define INPUT_2BYTES(cinfo,V,action) \ + MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \ + V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \ + MAKE_BYTE_AVAIL(cinfo,action); \ + V += GETJOCTET(*next_input_byte++); ) + + +/* + * Routines to process JPEG markers. + * + * Entry condition: JPEG marker itself has been read and its code saved + * in cinfo->unread_marker; input restart point is just after the marker. + * + * Exit: if return TRUE, have read and processed any parameters, and have + * updated the restart point to point after the parameters. + * If return FALSE, was forced to suspend before reaching end of + * marker parameters; restart point has not been moved. Same routine + * will be called again after application supplies more input data. + * + * This approach to suspension assumes that all of a marker's parameters can + * fit into a single input bufferload. This should hold for "normal" + * markers. Some COM/APPn markers might have large parameter segments, + * but we use skip_input_data to get past those, and thereby put the problem + * on the source manager's shoulders. + * + * Note that we don't bother to avoid duplicate trace messages if a + * suspension occurs within marker parameters. Other side effects + * require more care. + */ + + +LOCAL boolean +get_soi (j_decompress_ptr cinfo) +/* Process an SOI marker */ +{ + int i; + + TRACEMS(cinfo, 1, JTRC_SOI); + + if (cinfo->marker->saw_SOI) + ERREXIT(cinfo, JERR_SOI_DUPLICATE); + + /* Reset all parameters that are defined to be reset by SOI */ + + for (i = 0; i < NUM_ARITH_TBLS; i++) { + cinfo->arith_dc_L[i] = 0; + cinfo->arith_dc_U[i] = 1; + cinfo->arith_ac_K[i] = 5; + } + cinfo->restart_interval = 0; + + /* Set initial assumptions for colorspace etc */ + + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->CCIR601_sampling = FALSE; /* Assume non-CCIR sampling??? */ + + cinfo->saw_JFIF_marker = FALSE; + cinfo->density_unit = 0; /* set default JFIF APP0 values */ + cinfo->X_density = 1; + cinfo->Y_density = 1; + cinfo->saw_Adobe_marker = FALSE; + cinfo->Adobe_transform = 0; + + cinfo->marker->saw_SOI = TRUE; + + return TRUE; +} + + +LOCAL boolean +get_sof (j_decompress_ptr cinfo, boolean is_prog, boolean is_arith) +/* Process a SOFn marker */ +{ + INT32 length; + int c, ci; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + cinfo->progressive_mode = is_prog; + cinfo->arith_code = is_arith; + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, cinfo->data_precision, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_height, return FALSE); + INPUT_2BYTES(cinfo, cinfo->image_width, return FALSE); + INPUT_BYTE(cinfo, cinfo->num_components, return FALSE); + + length -= 8; + + TRACEMS4(cinfo, 1, JTRC_SOF, cinfo->unread_marker, + (int) cinfo->image_width, (int) cinfo->image_height, + cinfo->num_components); + + if (cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOF_DUPLICATE); + + /* We don't support files in which the image height is initially specified */ + /* as 0 and is later redefined by DNL. As long as we have to check that, */ + /* might as well have a general sanity check. */ + if (cinfo->image_height <= 0 || cinfo->image_width <= 0 + || cinfo->num_components <= 0) + ERREXIT(cinfo, JERR_EMPTY_IMAGE); + + if (length != (cinfo->num_components * 3)) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + if (cinfo->comp_info == NULL) /* do only once, even if suspend */ + cinfo->comp_info = (jpeg_component_info *) (*cinfo->mem->alloc_small) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * SIZEOF(jpeg_component_info)); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + compptr->component_index = ci; + INPUT_BYTE(cinfo, compptr->component_id, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + compptr->h_samp_factor = (c >> 4) & 15; + compptr->v_samp_factor = (c ) & 15; + INPUT_BYTE(cinfo, compptr->quant_tbl_no, return FALSE); + + TRACEMS4(cinfo, 1, JTRC_SOF_COMPONENT, + compptr->component_id, compptr->h_samp_factor, + compptr->v_samp_factor, compptr->quant_tbl_no); + } + + cinfo->marker->saw_SOF = TRUE; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_sos (j_decompress_ptr cinfo) +/* Process a SOS marker */ +{ + INT32 length; + int i, ci, n, c, cc; + jpeg_component_info * compptr; + INPUT_VARS(cinfo); + + if (! cinfo->marker->saw_SOF) + ERREXIT(cinfo, JERR_SOS_NO_SOF); + + INPUT_2BYTES(cinfo, length, return FALSE); + + INPUT_BYTE(cinfo, n, return FALSE); /* Number of components */ + + if (length != (n * 2 + 6) || n < 1 || n > MAX_COMPS_IN_SCAN) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + TRACEMS1(cinfo, 1, JTRC_SOS, n); + + cinfo->comps_in_scan = n; + + /* Collect the component-spec parameters */ + + for (i = 0; i < n; i++) { + INPUT_BYTE(cinfo, cc, return FALSE); + INPUT_BYTE(cinfo, c, return FALSE); + + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + if (cc == compptr->component_id) + goto id_found; + } + + ERREXIT1(cinfo, JERR_BAD_COMPONENT_ID, cc); + + id_found: + + cinfo->cur_comp_info[i] = compptr; + compptr->dc_tbl_no = (c >> 4) & 15; + compptr->ac_tbl_no = (c ) & 15; + + TRACEMS3(cinfo, 1, JTRC_SOS_COMPONENT, cc, + compptr->dc_tbl_no, compptr->ac_tbl_no); + } + + /* Collect the additional scan parameters Ss, Se, Ah/Al. */ + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ss = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Se = c; + INPUT_BYTE(cinfo, c, return FALSE); + cinfo->Ah = (c >> 4) & 15; + cinfo->Al = (c ) & 15; + + TRACEMS4(cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se, + cinfo->Ah, cinfo->Al); + + /* Prepare to scan data & restart markers */ + cinfo->marker->next_restart_num = 0; + + /* Count another SOS marker */ + cinfo->input_scan_number++; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +METHODDEF boolean +get_app0 (j_decompress_ptr cinfo) +/* Process an APP0 marker */ +{ +#define JFIF_LEN 14 + INT32 length; + UINT8 b[JFIF_LEN]; + int buffp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* See if a JFIF APP0 marker is present */ + + if (length >= JFIF_LEN) { + for (buffp = 0; buffp < JFIF_LEN; buffp++) + INPUT_BYTE(cinfo, b[buffp], return FALSE); + length -= JFIF_LEN; + + if (b[0]==0x4A && b[1]==0x46 && b[2]==0x49 && b[3]==0x46 && b[4]==0) { + /* Found JFIF APP0 marker: check version */ + /* Major version must be 1, anything else signals an incompatible change. + * We used to treat this as an error, but now it's a nonfatal warning, + * because some bozo at Hijaak couldn't read the spec. + * Minor version should be 0..2, but process anyway if newer. + */ + if (b[5] != 1) + WARNMS2(cinfo, JWRN_JFIF_MAJOR, b[5], b[6]); + else if (b[6] > 2) + TRACEMS2(cinfo, 1, JTRC_JFIF_MINOR, b[5], b[6]); + /* Save info */ + cinfo->saw_JFIF_marker = TRUE; + cinfo->density_unit = b[7]; + cinfo->X_density = (b[8] << 8) + b[9]; + cinfo->Y_density = (b[10] << 8) + b[11]; + TRACEMS3(cinfo, 1, JTRC_JFIF, + cinfo->X_density, cinfo->Y_density, cinfo->density_unit); + if (b[12] | b[13]) + TRACEMS2(cinfo, 1, JTRC_JFIF_THUMBNAIL, b[12], b[13]); + if (length != ((INT32) b[12] * (INT32) b[13] * (INT32) 3)) + TRACEMS1(cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) length); + } else { + /* Start of APP0 does not match "JFIF" */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) length + JFIF_LEN); + } + } else { + /* Too short to be JFIF marker */ + TRACEMS1(cinfo, 1, JTRC_APP0, (int) length); + } + + INPUT_SYNC(cinfo); + if (length > 0) /* skip any remaining data -- could be lots */ + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +METHODDEF boolean +get_app14 (j_decompress_ptr cinfo) +/* Process an APP14 marker */ +{ +#define ADOBE_LEN 12 + INT32 length; + UINT8 b[ADOBE_LEN]; + int buffp; + unsigned int version, flags0, flags1, transform; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + /* See if an Adobe APP14 marker is present */ + + if (length >= ADOBE_LEN) { + for (buffp = 0; buffp < ADOBE_LEN; buffp++) + INPUT_BYTE(cinfo, b[buffp], return FALSE); + length -= ADOBE_LEN; + + if (b[0]==0x41 && b[1]==0x64 && b[2]==0x6F && b[3]==0x62 && b[4]==0x65) { + /* Found Adobe APP14 marker */ + version = (b[5] << 8) + b[6]; + flags0 = (b[7] << 8) + b[8]; + flags1 = (b[9] << 8) + b[10]; + transform = b[11]; + TRACEMS4(cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform); + cinfo->saw_Adobe_marker = TRUE; + cinfo->Adobe_transform = (UINT8) transform; + } else { + /* Start of APP14 does not match "Adobe" */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) length + ADOBE_LEN); + } + } else { + /* Too short to be Adobe marker */ + TRACEMS1(cinfo, 1, JTRC_APP14, (int) length); + } + + INPUT_SYNC(cinfo); + if (length > 0) /* skip any remaining data -- could be lots */ + (*cinfo->src->skip_input_data) (cinfo, (long) length); + + return TRUE; +} + + +LOCAL boolean +get_dac (j_decompress_ptr cinfo) +/* Process a DAC marker */ +{ + INT32 length; + int index, val; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + INPUT_BYTE(cinfo, val, return FALSE); + + length -= 2; + + TRACEMS2(cinfo, 1, JTRC_DAC, index, val); + + if (index < 0 || index >= (2*NUM_ARITH_TBLS)) + ERREXIT1(cinfo, JERR_DAC_INDEX, index); + + if (index >= NUM_ARITH_TBLS) { /* define AC table */ + cinfo->arith_ac_K[index-NUM_ARITH_TBLS] = (UINT8) val; + } else { /* define DC table */ + cinfo->arith_dc_L[index] = (UINT8) (val & 0x0F); + cinfo->arith_dc_U[index] = (UINT8) (val >> 4); + if (cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index]) + ERREXIT1(cinfo, JERR_DAC_VALUE, val); + } + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dht (j_decompress_ptr cinfo) +/* Process a DHT marker */ +{ + INT32 length; + UINT8 bits[17]; + UINT8 huffval[256]; + int i, index, count; + JHUFF_TBL **htblptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, index, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DHT, index); + + bits[0] = 0; + count = 0; + for (i = 1; i <= 16; i++) { + INPUT_BYTE(cinfo, bits[i], return FALSE); + count += bits[i]; + } + + length -= 1 + 16; + + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[1], bits[2], bits[3], bits[4], + bits[5], bits[6], bits[7], bits[8]); + TRACEMS8(cinfo, 2, JTRC_HUFFBITS, + bits[9], bits[10], bits[11], bits[12], + bits[13], bits[14], bits[15], bits[16]); + + if (count > 256 || ((INT32) count) > length) + ERREXIT(cinfo, JERR_DHT_COUNTS); + + for (i = 0; i < count; i++) + INPUT_BYTE(cinfo, huffval[i], return FALSE); + + length -= count; + + if (index & 0x10) { /* AC table definition */ + index -= 0x10; + htblptr = &cinfo->ac_huff_tbl_ptrs[index]; + } else { /* DC table definition */ + htblptr = &cinfo->dc_huff_tbl_ptrs[index]; + } + + if (index < 0 || index >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_DHT_INDEX, index); + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); + + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + MEMCOPY((*htblptr)->huffval, huffval, SIZEOF((*htblptr)->huffval)); + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dqt (j_decompress_ptr cinfo) +/* Process a DQT marker */ +{ + INT32 length; + int n, i, prec; + unsigned int tmp; + JQUANT_TBL *quant_ptr; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + length -= 2; + + while (length > 0) { + INPUT_BYTE(cinfo, n, return FALSE); + prec = n >> 4; + n &= 0x0F; + + TRACEMS2(cinfo, 1, JTRC_DQT, n, prec); + + if (n >= NUM_QUANT_TBLS) + ERREXIT1(cinfo, JERR_DQT_INDEX, n); + + if (cinfo->quant_tbl_ptrs[n] == NULL) + cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table((j_common_ptr) cinfo); + quant_ptr = cinfo->quant_tbl_ptrs[n]; + + for (i = 0; i < DCTSIZE2; i++) { + if (prec) + INPUT_2BYTES(cinfo, tmp, return FALSE); + else + INPUT_BYTE(cinfo, tmp, return FALSE); + quant_ptr->quantval[i] = (UINT16) tmp; + } + + for (i = 0; i < DCTSIZE2; i += 8) { + TRACEMS8(cinfo, 2, JTRC_QUANTVALS, + quant_ptr->quantval[i ], quant_ptr->quantval[i+1], + quant_ptr->quantval[i+2], quant_ptr->quantval[i+3], + quant_ptr->quantval[i+4], quant_ptr->quantval[i+5], + quant_ptr->quantval[i+6], quant_ptr->quantval[i+7]); + } + + length -= DCTSIZE2+1; + if (prec) length -= DCTSIZE2; + } + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +get_dri (j_decompress_ptr cinfo) +/* Process a DRI marker */ +{ + INT32 length; + unsigned int tmp; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + if (length != 4) + ERREXIT(cinfo, JERR_BAD_LENGTH); + + INPUT_2BYTES(cinfo, tmp, return FALSE); + + TRACEMS1(cinfo, 1, JTRC_DRI, tmp); + + cinfo->restart_interval = tmp; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +METHODDEF boolean +skip_variable (j_decompress_ptr cinfo) +/* Skip over an unknown or uninteresting variable-length marker */ +{ + INT32 length; + INPUT_VARS(cinfo); + + INPUT_2BYTES(cinfo, length, return FALSE); + + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length); + + INPUT_SYNC(cinfo); /* do before skip_input_data */ + (*cinfo->src->skip_input_data) (cinfo, (long) length - 2L); + + return TRUE; +} + + +/* + * Find the next JPEG marker, save it in cinfo->unread_marker. + * Returns FALSE if had to suspend before reaching a marker; + * in that case cinfo->unread_marker is unchanged. + * + * Note that the result might not be a valid marker code, + * but it will never be 0 or FF. + */ + +LOCAL boolean +next_marker (j_decompress_ptr cinfo) +{ + int c; + INPUT_VARS(cinfo); + + for (;;) { + INPUT_BYTE(cinfo, c, return FALSE); + /* Skip any non-FF bytes. + * This may look a bit inefficient, but it will not occur in a valid file. + * We sync after each discarded byte so that a suspending data source + * can discard the byte from its buffer. + */ + while (c != 0xFF) { + cinfo->marker->discarded_bytes++; + INPUT_SYNC(cinfo); + INPUT_BYTE(cinfo, c, return FALSE); + } + /* This loop swallows any duplicate FF bytes. Extra FFs are legal as + * pad bytes, so don't count them in discarded_bytes. We assume there + * will not be so many consecutive FF bytes as to overflow a suspending + * data source's input buffer. + */ + do { + INPUT_BYTE(cinfo, c, return FALSE); + } while (c == 0xFF); + if (c != 0) + break; /* found a valid marker, exit loop */ + /* Reach here if we found a stuffed-zero data sequence (FF/00). + * Discard it and loop back to try again. + */ + cinfo->marker->discarded_bytes += 2; + INPUT_SYNC(cinfo); + } + + if (cinfo->marker->discarded_bytes != 0) { + WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c); + cinfo->marker->discarded_bytes = 0; + } + + cinfo->unread_marker = c; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +LOCAL boolean +first_marker (j_decompress_ptr cinfo) +/* Like next_marker, but used to obtain the initial SOI marker. */ +/* For this marker, we do not allow preceding garbage or fill; otherwise, + * we might well scan an entire input file before realizing it ain't JPEG. + * If an application wants to process non-JFIF files, it must seek to the + * SOI before calling the JPEG library. + */ +{ + int c, c2; + INPUT_VARS(cinfo); + + INPUT_BYTE(cinfo, c, return FALSE); + INPUT_BYTE(cinfo, c2, return FALSE); + if (c != 0xFF || c2 != (int) M_SOI) + ERREXIT2(cinfo, JERR_NO_SOI, c, c2); + + cinfo->unread_marker = c2; + + INPUT_SYNC(cinfo); + return TRUE; +} + + +/* + * Read markers until SOS or EOI. + * + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + +METHODDEF int +read_markers (j_decompress_ptr cinfo) +{ + /* Outer loop repeats once for each marker. */ + for (;;) { + /* Collect the marker proper, unless we already did. */ + /* NB: first_marker() enforces the requirement that SOI appear first. */ + if (cinfo->unread_marker == 0) { + if (! cinfo->marker->saw_SOI) { + if (! first_marker(cinfo)) + return JPEG_SUSPENDED; + } else { + if (! next_marker(cinfo)) + return JPEG_SUSPENDED; + } + } + /* At this point cinfo->unread_marker contains the marker code and the + * input point is just past the marker proper, but before any parameters. + * A suspension will cause us to return with this state still true. + */ + switch (cinfo->unread_marker) { + case M_SOI: + if (! get_soi(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_SOF0: /* Baseline */ + case M_SOF1: /* Extended sequential, Huffman */ + if (! get_sof(cinfo, FALSE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF2: /* Progressive, Huffman */ + if (! get_sof(cinfo, TRUE, FALSE)) + return JPEG_SUSPENDED; + break; + + case M_SOF9: /* Extended sequential, arithmetic */ + if (! get_sof(cinfo, FALSE, TRUE)) + return JPEG_SUSPENDED; + break; + + case M_SOF10: /* Progressive, arithmetic */ + if (! get_sof(cinfo, TRUE, TRUE)) + return JPEG_SUSPENDED; + break; + + /* Currently unsupported SOFn types */ + case M_SOF3: /* Lossless, Huffman */ + case M_SOF5: /* Differential sequential, Huffman */ + case M_SOF6: /* Differential progressive, Huffman */ + case M_SOF7: /* Differential lossless, Huffman */ + case M_JPG: /* Reserved for JPEG extensions */ + case M_SOF11: /* Lossless, arithmetic */ + case M_SOF13: /* Differential sequential, arithmetic */ + case M_SOF14: /* Differential progressive, arithmetic */ + case M_SOF15: /* Differential lossless, arithmetic */ + ERREXIT1(cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker); + break; + + case M_SOS: + if (! get_sos(cinfo)) + return JPEG_SUSPENDED; + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_SOS; + + case M_EOI: + TRACEMS(cinfo, 1, JTRC_EOI); + cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_REACHED_EOI; + + case M_DAC: + if (! get_dac(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DHT: + if (! get_dht(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DQT: + if (! get_dqt(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_DRI: + if (! get_dri(cinfo)) + return JPEG_SUSPENDED; + break; + + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + if (! (*cinfo->marker->process_APPn[cinfo->unread_marker - (int) M_APP0]) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_COM: + if (! (*cinfo->marker->process_COM) (cinfo)) + return JPEG_SUSPENDED; + break; + + case M_RST0: /* these are all parameterless */ + case M_RST1: + case M_RST2: + case M_RST3: + case M_RST4: + case M_RST5: + case M_RST6: + case M_RST7: + case M_TEM: + TRACEMS1(cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker); + break; + + case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ + if (! skip_variable(cinfo)) + return JPEG_SUSPENDED; + break; + + default: /* must be DHP, EXP, JPGn, or RESn */ + /* For now, we treat the reserved markers as fatal errors since they are + * likely to be used to signal incompatible JPEG Part 3 extensions. + * Once the JPEG 3 version-number marker is well defined, this code + * ought to change! + */ + ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo->unread_marker); + break; + } + /* Successfully processed marker, so reset state variable */ + cinfo->unread_marker = 0; + } /* end loop */ +} + + +/* + * Read a restart marker, which is expected to appear next in the datastream; + * if the marker is not there, take appropriate recovery action. + * Returns FALSE if suspension is required. + * + * This is called by the entropy decoder after it has read an appropriate + * number of MCUs. cinfo->unread_marker may be nonzero if the entropy decoder + * has already read a marker from the data source. Under normal conditions + * cinfo->unread_marker will be reset to 0 before returning; if not reset, + * it holds a marker which the decoder will be unable to read past. + */ + +METHODDEF boolean +read_restart_marker (j_decompress_ptr cinfo) +{ + /* Obtain a marker unless we already did. */ + /* Note that next_marker will complain if it skips any data. */ + if (cinfo->unread_marker == 0) { + if (! next_marker(cinfo)) + return FALSE; + } + + if (cinfo->unread_marker == + ((int) M_RST0 + cinfo->marker->next_restart_num)) { + /* Normal case --- swallow the marker and let entropy decoder continue */ + TRACEMS1(cinfo, 2, JTRC_RST, cinfo->marker->next_restart_num); + cinfo->unread_marker = 0; + } else { + /* Uh-oh, the restart markers have been messed up. */ + /* Let the data source manager determine how to resync. */ + if (! (*cinfo->src->resync_to_restart) (cinfo, + cinfo->marker->next_restart_num)) + return FALSE; + } + + /* Update next-restart state */ + cinfo->marker->next_restart_num = (cinfo->marker->next_restart_num + 1) & 7; + + return TRUE; +} + + +/* + * This is the default resync_to_restart method for data source managers + * to use if they don't have any better approach. Some data source managers + * may be able to back up, or may have additional knowledge about the data + * which permits a more intelligent recovery strategy; such managers would + * presumably supply their own resync method. + * + * read_restart_marker calls resync_to_restart if it finds a marker other than + * the restart marker it was expecting. (This code is *not* used unless + * a nonzero restart interval has been declared.) cinfo->unread_marker is + * the marker code actually found (might be anything, except 0 or FF). + * The desired restart marker number (0..7) is passed as a parameter. + * This routine is supposed to apply whatever error recovery strategy seems + * appropriate in order to position the input stream to the next data segment. + * Note that cinfo->unread_marker is treated as a marker appearing before + * the current data-source input point; usually it should be reset to zero + * before returning. + * Returns FALSE if suspension is required. + * + * This implementation is substantially constrained by wanting to treat the + * input as a data stream; this means we can't back up. Therefore, we have + * only the following actions to work with: + * 1. Simply discard the marker and let the entropy decoder resume at next + * byte of file. + * 2. Read forward until we find another marker, discarding intervening + * data. (In theory we could look ahead within the current bufferload, + * without having to discard data if we don't find the desired marker. + * This idea is not implemented here, in part because it makes behavior + * dependent on buffer size and chance buffer-boundary positions.) + * 3. Leave the marker unread (by failing to zero cinfo->unread_marker). + * This will cause the entropy decoder to process an empty data segment, + * inserting dummy zeroes, and then we will reprocess the marker. + * + * #2 is appropriate if we think the desired marker lies ahead, while #3 is + * appropriate if the found marker is a future restart marker (indicating + * that we have missed the desired restart marker, probably because it got + * corrupted). + * We apply #2 or #3 if the found marker is a restart marker no more than + * two counts behind or ahead of the expected one. We also apply #2 if the + * found marker is not a legal JPEG marker code (it's certainly bogus data). + * If the found marker is a restart marker more than 2 counts away, we do #1 + * (too much risk that the marker is erroneous; with luck we will be able to + * resync at some future point). + * For any valid non-restart JPEG marker, we apply #3. This keeps us from + * overrunning the end of a scan. An implementation limited to single-scan + * files might find it better to apply #2 for markers other than EOI, since + * any other marker would have to be bogus data in that case. + */ + +GLOBAL boolean +jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired) +{ + int marker = cinfo->unread_marker; + int action = 1; + + /* Always put up a warning. */ + WARNMS2(cinfo, JWRN_MUST_RESYNC, marker, desired); + + /* Outer loop handles repeated decision after scanning forward. */ + for (;;) { + if (marker < (int) M_SOF0) + action = 2; /* invalid marker */ + else if (marker < (int) M_RST0 || marker > (int) M_RST7) + action = 3; /* valid non-restart marker */ + else { + if (marker == ((int) M_RST0 + ((desired+1) & 7)) || + marker == ((int) M_RST0 + ((desired+2) & 7))) + action = 3; /* one of the next two expected restarts */ + else if (marker == ((int) M_RST0 + ((desired-1) & 7)) || + marker == ((int) M_RST0 + ((desired-2) & 7))) + action = 2; /* a prior restart, so advance */ + else + action = 1; /* desired restart or too far away */ + } + TRACEMS2(cinfo, 4, JTRC_RECOVERY_ACTION, marker, action); + switch (action) { + case 1: + /* Discard marker and let entropy decoder resume processing. */ + cinfo->unread_marker = 0; + return TRUE; + case 2: + /* Scan to the next marker, and repeat the decision loop. */ + if (! next_marker(cinfo)) + return FALSE; + marker = cinfo->unread_marker; + break; + case 3: + /* Return without advancing past this marker. */ + /* Entropy decoder will be forced to process an empty segment. */ + return TRUE; + } + } /* end loop */ +} + + +/* + * Reset marker processing state to begin a fresh datastream. + */ + +METHODDEF void +reset_marker_reader (j_decompress_ptr cinfo) +{ + cinfo->comp_info = NULL; /* until allocated by get_sof */ + cinfo->input_scan_number = 0; /* no SOS seen yet */ + cinfo->unread_marker = 0; /* no pending marker */ + cinfo->marker->saw_SOI = FALSE; /* set internal state too */ + cinfo->marker->saw_SOF = FALSE; + cinfo->marker->discarded_bytes = 0; +} + + +/* + * Initialize the marker reader module. + * This is called only once, when the decompression object is created. + */ + +GLOBAL void +jinit_marker_reader (j_decompress_ptr cinfo) +{ + int i; + + /* Create subobject in permanent pool */ + cinfo->marker = (struct jpeg_marker_reader *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(struct jpeg_marker_reader)); + /* Initialize method pointers */ + cinfo->marker->reset_marker_reader = reset_marker_reader; + cinfo->marker->read_markers = read_markers; + cinfo->marker->read_restart_marker = read_restart_marker; + cinfo->marker->process_COM = skip_variable; + for (i = 0; i < 16; i++) + cinfo->marker->process_APPn[i] = skip_variable; + cinfo->marker->process_APPn[0] = get_app0; + cinfo->marker->process_APPn[14] = get_app14; + /* Reset marker processing state */ + reset_marker_reader(cinfo); +} diff --git a/code/jpeg-6/jdmaster.cpp b/code/jpeg-6/jdmaster.cpp new file mode 100644 index 0000000..72b1d9f --- /dev/null +++ b/code/jpeg-6/jdmaster.cpp @@ -0,0 +1,562 @@ +/* + * jdmaster.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains master control logic for the JPEG decompressor. + * These routines are concerned with selecting the modules to be executed + * and with determining the number of passes and the work to be done in each + * pass. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private state */ + +typedef struct { + struct jpeg_decomp_master pub; /* public fields */ + + int pass_number; /* # of passes completed */ + + boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */ + + /* Saved references to initialized quantizer modules, + * in case we need to switch modes. + */ + struct jpeg_color_quantizer * quantizer_1pass; + struct jpeg_color_quantizer * quantizer_2pass; +} my_decomp_master; + +typedef my_decomp_master * my_master_ptr; + + +/* + * Determine whether merged upsample/color conversion should be used. + * CRUCIAL: this must match the actual capabilities of jdmerge.c! + */ + +LOCAL boolean +use_merged_upsample (j_decompress_ptr cinfo) +{ +#ifdef UPSAMPLE_MERGING_SUPPORTED + /* Merging is the equivalent of plain box-filter upsampling */ + if (cinfo->do_fancy_upsampling || cinfo->CCIR601_sampling) + return FALSE; + /* jdmerge.c only supports YCC=>RGB color conversion */ + if (cinfo->jpeg_color_space != JCS_YCbCr || cinfo->num_components != 3 || + cinfo->out_color_space != JCS_RGB || + cinfo->out_color_components != RGB_PIXELSIZE) + return FALSE; + /* and it only handles 2h1v or 2h2v sampling ratios */ + if (cinfo->comp_info[0].h_samp_factor != 2 || + cinfo->comp_info[1].h_samp_factor != 1 || + cinfo->comp_info[2].h_samp_factor != 1 || + cinfo->comp_info[0].v_samp_factor > 2 || + cinfo->comp_info[1].v_samp_factor != 1 || + cinfo->comp_info[2].v_samp_factor != 1) + return FALSE; + /* furthermore, it doesn't work if we've scaled the IDCTs differently */ + if (cinfo->comp_info[0].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[1].DCT_scaled_size != cinfo->min_DCT_scaled_size || + cinfo->comp_info[2].DCT_scaled_size != cinfo->min_DCT_scaled_size) + return FALSE; + /* ??? also need to test for upsample-time rescaling, when & if supported */ + return TRUE; /* by golly, it'll work... */ +#else + return FALSE; +#endif +} + + +/* + * Compute output image dimensions and related values. + * NOTE: this is exported for possible use by application. + * Hence it mustn't do anything that can't be done twice. + * Also note that it may be called before the master module is initialized! + */ + +GLOBAL void +jpeg_calc_output_dimensions (j_decompress_ptr cinfo) +/* Do computations that are needed before master selection phase */ +{ +#if 0 // JDC: commented out to remove warning + int ci; + jpeg_component_info *compptr; +#endif + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_READY) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + +#ifdef IDCT_SCALING_SUPPORTED + + /* Compute actual output image dimensions and DCT scaling choices. */ + if (cinfo->scale_num * 8 <= cinfo->scale_denom) { + /* Provide 1/8 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 8L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 8L); + cinfo->min_DCT_scaled_size = 1; + } else if (cinfo->scale_num * 4 <= cinfo->scale_denom) { + /* Provide 1/4 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 4L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 4L); + cinfo->min_DCT_scaled_size = 2; + } else if (cinfo->scale_num * 2 <= cinfo->scale_denom) { + /* Provide 1/2 scaling */ + cinfo->output_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width, 2L); + cinfo->output_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height, 2L); + cinfo->min_DCT_scaled_size = 4; + } else { + /* Provide 1/1 scaling */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + cinfo->min_DCT_scaled_size = DCTSIZE; + } + /* In selecting the actual DCT scaling for each component, we try to + * scale up the chroma components via IDCT scaling rather than upsampling. + * This saves time if the upsampler gets to use 1:1 scaling. + * Note this code assumes that the supported DCT scalings are powers of 2. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + int ssize = cinfo->min_DCT_scaled_size; + while (ssize < DCTSIZE && + (compptr->h_samp_factor * ssize * 2 <= + cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size) && + (compptr->v_samp_factor * ssize * 2 <= + cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size)) { + ssize = ssize * 2; + } + compptr->DCT_scaled_size = ssize; + } + + /* Recompute downsampled dimensions of components; + * application needs to know these if using raw downsampled data. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Size in samples, after IDCT scaling */ + compptr->downsampled_width = (JDIMENSION) + jdiv_round_up((long) cinfo->image_width * + (long) (compptr->h_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_h_samp_factor * DCTSIZE)); + compptr->downsampled_height = (JDIMENSION) + jdiv_round_up((long) cinfo->image_height * + (long) (compptr->v_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_v_samp_factor * DCTSIZE)); + } + +#else /* !IDCT_SCALING_SUPPORTED */ + + /* Hardwire it to "no scaling" */ + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + /* jdinput.c has already initialized DCT_scaled_size to DCTSIZE, + * and has computed unscaled downsampled_width and downsampled_height. + */ + +#endif /* IDCT_SCALING_SUPPORTED */ + + /* Report number of components in selected colorspace. */ + /* Probably this should be in the color conversion module... */ + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: +#if RGB_PIXELSIZE != 3 + cinfo->out_color_components = RGB_PIXELSIZE; + break; +#endif /* else share code with YCbCr */ + case JCS_YCbCr: + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo->out_color_components = 4; + break; + default: /* else must be same colorspace as in file */ + cinfo->out_color_components = cinfo->num_components; + break; + } + cinfo->output_components = (cinfo->quantize_colors ? 1 : + cinfo->out_color_components); + + /* See if upsampler will want to emit more than one row at a time */ + if (use_merged_upsample(cinfo)) + cinfo->rec_outbuf_height = cinfo->max_v_samp_factor; + else + cinfo->rec_outbuf_height = 1; +} + + +/* + * Several decompression processes need to range-limit values to the range + * 0..MAXJSAMPLE; the input value may fall somewhat outside this range + * due to noise introduced by quantization, roundoff error, etc. These + * processes are inner loops and need to be as fast as possible. On most + * machines, particularly CPUs with pipelines or instruction prefetch, + * a (subscript-check-less) C table lookup + * x = sample_range_limit[x]; + * is faster than explicit tests + * if (x < 0) x = 0; + * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; + * These processes all use a common table prepared by the routine below. + * + * For most steps we can mathematically guarantee that the initial value + * of x is within MAXJSAMPLE+1 of the legal range, so a table running from + * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial + * limiting step (just after the IDCT), a wildly out-of-range value is + * possible if the input data is corrupt. To avoid any chance of indexing + * off the end of memory and getting a bad-pointer trap, we perform the + * post-IDCT limiting thus: + * x = range_limit[x & MASK]; + * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit + * samples. Under normal circumstances this is more than enough range and + * a correct output will be generated; with bogus input data the mask will + * cause wraparound, and we will safely generate a bogus-but-in-range output. + * For the post-IDCT step, we want to convert the data from signed to unsigned + * representation by adding CENTERJSAMPLE at the same time that we limit it. + * So the post-IDCT limiting table ends up looking like this: + * CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE, + * MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0 (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times), + * 0,1,...,CENTERJSAMPLE-1 + * Negative inputs select values from the upper half of the table after + * masking. + * + * We can save some space by overlapping the start of the post-IDCT table + * with the simpler range limiting table. The post-IDCT table begins at + * sample_range_limit + CENTERJSAMPLE. + * + * Note that the table is allocated in near data space on PCs; it's small + * enough and used often enough to justify this. + */ + +LOCAL void +prepare_range_limit_table (j_decompress_ptr cinfo) +/* Allocate and fill in the sample_range_limit table */ +{ + JSAMPLE * table; + int i; + + table = (JSAMPLE *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + (5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + table += (MAXJSAMPLE+1); /* allow negative subscripts of simple table */ + cinfo->sample_range_limit = table; + /* First segment of "simple" table: limit[x] = 0 for x < 0 */ + MEMZERO(table - (MAXJSAMPLE+1), (MAXJSAMPLE+1) * SIZEOF(JSAMPLE)); + /* Main part of "simple" table: limit[x] = x */ + for (i = 0; i <= MAXJSAMPLE; i++) + table[i] = (JSAMPLE) i; + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + /* End of simple table, rest of first half of post-IDCT table */ + for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++) + table[i] = MAXJSAMPLE; + /* Second half of post-IDCT table */ + MEMZERO(table + (2 * (MAXJSAMPLE+1)), + (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + MEMCOPY(table + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE), + cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE)); +} + + +/* + * Master selection of decompression modules. + * This is done once at jpeg_start_decompress time. We determine + * which modules will be used and give them appropriate initialization calls. + * We also initialize the decompressor input side to begin consuming data. + * + * Since jpeg_read_header has finished, we know what is in the SOF + * and (first) SOS markers. We also have all the application parameter + * settings. + */ + +LOCAL void +master_selection (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + boolean use_c_buffer; + long samplesperrow; + JDIMENSION jd_samplesperrow; + + /* Initialize dimensions and other stuff */ + jpeg_calc_output_dimensions(cinfo); + prepare_range_limit_table(cinfo); + + /* Width of an output scanline must be representable as JDIMENSION. */ + samplesperrow = (long) cinfo->output_width * (long) cinfo->out_color_components; + jd_samplesperrow = (JDIMENSION) samplesperrow; + if ((long) jd_samplesperrow != samplesperrow) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + + /* Initialize my private state */ + master->pass_number = 0; + master->using_merged_upsample = use_merged_upsample(cinfo); + + /* Color quantizer selection */ + master->quantizer_1pass = NULL; + master->quantizer_2pass = NULL; + /* No mode changes if not using buffered-image mode. */ + if (! cinfo->quantize_colors || ! cinfo->buffered_image) { + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + } + if (cinfo->quantize_colors) { + if (cinfo->raw_data_out) + ERREXIT(cinfo, JERR_NOTIMPL); + /* 2-pass quantizer only works in 3-component color space. */ + if (cinfo->out_color_components != 3) { + cinfo->enable_1pass_quant = TRUE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + cinfo->colormap = NULL; + } else if (cinfo->colormap != NULL) { + cinfo->enable_external_quant = TRUE; + } else if (cinfo->two_pass_quantize) { + cinfo->enable_2pass_quant = TRUE; + } else { + cinfo->enable_1pass_quant = TRUE; + } + + if (cinfo->enable_1pass_quant) { +#ifdef QUANT_1PASS_SUPPORTED + jinit_1pass_quantizer(cinfo); + master->quantizer_1pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + + /* We use the 2-pass code to map to external colormaps. */ + if (cinfo->enable_2pass_quant || cinfo->enable_external_quant) { +#ifdef QUANT_2PASS_SUPPORTED + jinit_2pass_quantizer(cinfo); + master->quantizer_2pass = cinfo->cquantize; +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } + /* If both quantizers are initialized, the 2-pass one is left active; + * this is necessary for starting with quantization to an external map. + */ + } + + /* Post-processing: in particular, color conversion first */ + if (! cinfo->raw_data_out) { + if (master->using_merged_upsample) { +#ifdef UPSAMPLE_MERGING_SUPPORTED + jinit_merged_upsampler(cinfo); /* does color conversion too */ +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else { + jinit_color_deconverter(cinfo); + jinit_upsampler(cinfo); + } + jinit_d_post_controller(cinfo, cinfo->enable_2pass_quant); + } + /* Inverse DCT */ + jinit_inverse_dct(cinfo); + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Initialize principal buffer controllers. */ + use_c_buffer = cinfo->inputctl->has_multiple_scans || cinfo->buffered_image; + jinit_d_coef_controller(cinfo, use_c_buffer); + + if (! cinfo->raw_data_out) + jinit_d_main_controller(cinfo, FALSE /* never need full buffer here */); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + +#ifdef D_MULTISCAN_FILES_SUPPORTED + /* If jpeg_start_decompress will read the whole file, initialize + * progress monitoring appropriately. The input step is counted + * as one pass. + */ + if (cinfo->progress != NULL && ! cinfo->buffered_image && + cinfo->inputctl->has_multiple_scans) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = (cinfo->enable_2pass_quant ? 3 : 2); + /* Count the input pass as done */ + master->pass_number++; + } +#endif /* D_MULTISCAN_FILES_SUPPORTED */ +} + + +/* + * Per-pass setup. + * This is called at the beginning of each output pass. We determine which + * modules will be active during this pass and give them appropriate + * start_pass calls. We also set is_dummy_pass to indicate whether this + * is a "real" output pass or a dummy pass for color quantization. + * (In the latter case, jdapi.c will crank the pass to completion.) + */ + +METHODDEF void +prepare_for_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (master->pub.is_dummy_pass) { +#ifdef QUANT_2PASS_SUPPORTED + /* Final pass of 2-pass quantization */ + master->pub.is_dummy_pass = FALSE; + (*cinfo->cquantize->start_pass) (cinfo, FALSE); + (*cinfo->post->start_pass) (cinfo, JBUF_CRANK_DEST); + (*cinfo->main->start_pass) (cinfo, JBUF_CRANK_DEST); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + if (cinfo->quantize_colors && cinfo->colormap == NULL) { + /* Select new quantization method */ + if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) { + cinfo->cquantize = master->quantizer_2pass; + master->pub.is_dummy_pass = TRUE; + } else if (cinfo->enable_1pass_quant) { + cinfo->cquantize = master->quantizer_1pass; + } else { + ERREXIT(cinfo, JERR_MODE_CHANGE); + } + } + (*cinfo->idct->start_pass) (cinfo); + (*cinfo->coef->start_output_pass) (cinfo); + if (! cinfo->raw_data_out) { + if (! master->using_merged_upsample) + (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->upsample->start_pass) (cinfo); + if (cinfo->quantize_colors) + (*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass); + (*cinfo->post->start_pass) (cinfo, + (master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + } + } + + /* Set up progress monitor's pass info if present */ + if (cinfo->progress != NULL) { + cinfo->progress->completed_passes = master->pass_number; + cinfo->progress->total_passes = master->pass_number + + (master->pub.is_dummy_pass ? 2 : 1); + /* In buffered-image mode, we assume one more output pass if EOI not + * yet reached, but no more passes if EOI has been reached. + */ + if (cinfo->buffered_image && ! cinfo->inputctl->eoi_reached) { + cinfo->progress->total_passes += (cinfo->enable_2pass_quant ? 2 : 1); + } + } +} + + +/* + * Finish up at end of an output pass. + */ + +METHODDEF void +finish_output_pass (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + if (cinfo->quantize_colors) + (*cinfo->cquantize->finish_pass) (cinfo); + master->pass_number++; +} + + +#ifdef D_MULTISCAN_FILES_SUPPORTED + +/* + * Switch to a new external colormap between output passes. + */ + +GLOBAL void +jpeg_new_colormap (j_decompress_ptr cinfo) +{ + my_master_ptr master = (my_master_ptr) cinfo->master; + + /* Prevent application from calling me at wrong times */ + if (cinfo->global_state != DSTATE_BUFIMAGE) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (cinfo->quantize_colors && cinfo->enable_external_quant && + cinfo->colormap != NULL) { + /* Select 2-pass quantizer for external colormap use */ + cinfo->cquantize = master->quantizer_2pass; + /* Notify quantizer of colormap change */ + (*cinfo->cquantize->new_color_map) (cinfo); + master->pub.is_dummy_pass = FALSE; /* just in case */ + } else + ERREXIT(cinfo, JERR_MODE_CHANGE); +} + +#endif /* D_MULTISCAN_FILES_SUPPORTED */ + + +/* + * Initialize master decompression control and select active modules. + * This is performed at the start of jpeg_start_decompress. + */ + +GLOBAL void +jinit_master_decompress (j_decompress_ptr cinfo) +{ + my_master_ptr master; + + master = (my_master_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_decomp_master)); + cinfo->master = (struct jpeg_decomp_master *) master; + master->pub.prepare_for_output_pass = prepare_for_output_pass; + master->pub.finish_output_pass = finish_output_pass; + + master->pub.is_dummy_pass = FALSE; + + master_selection(cinfo); +} diff --git a/code/jpeg-6/jdpostct.cpp b/code/jpeg-6/jdpostct.cpp new file mode 100644 index 0000000..49a6b8f --- /dev/null +++ b/code/jpeg-6/jdpostct.cpp @@ -0,0 +1,296 @@ +/* + * jdpostct.c + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the decompression postprocessing controller. + * This controller manages the upsampling, color conversion, and color + * quantization/reduction steps; specifically, it controls the buffering + * between upsample/color conversion and color quantization/reduction. + * + * If no color quantization/reduction is required, then this module has no + * work to do, and it just hands off to the upsample/color conversion code. + * An integrated upsample/convert/quantize process would replace this module + * entirely. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Private buffer controller object */ + +typedef struct { + struct jpeg_d_post_controller pub; /* public fields */ + + /* Color quantization source buffer: this holds output data from + * the upsample/color conversion step to be passed to the quantizer. + * For two-pass color quantization, we need a full-image buffer; + * for one-pass operation, a strip buffer is sufficient. + */ + jvirt_sarray_ptr whole_image; /* virtual array, or NULL if one-pass */ + JSAMPARRAY buffer; /* strip buffer, or current strip of virtual */ + JDIMENSION strip_height; /* buffer size in rows */ + /* for two-pass mode only: */ + JDIMENSION starting_row; /* row # of first row in current strip */ + JDIMENSION next_row; /* index of next row to fill/empty in strip */ +} my_post_controller; + +typedef my_post_controller * my_post_ptr; + + +/* Forward declarations */ +METHODDEF void post_process_1pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#ifdef QUANT_2PASS_SUPPORTED +METHODDEF void post_process_prepass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +METHODDEF void post_process_2pass + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +#endif + + +/* + * Initialize for a processing pass. + */ + +METHODDEF void +start_pass_dpost (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + + switch (pass_mode) { + case JBUF_PASS_THRU: + if (cinfo->quantize_colors) { + /* Single-pass processing with color quantization. */ + post->pub.post_process_data = post_process_1pass; + /* We could be doing buffered-image output before starting a 2-pass + * color quantization; in that case, jinit_d_post_controller did not + * allocate a strip buffer. Use the virtual-array buffer as workspace. + */ + if (post->buffer == NULL) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + (JDIMENSION) 0, post->strip_height, TRUE); + } + } else { + /* For single-pass processing without color quantization, + * I have no work to do; just call the upsampler directly. + */ + post->pub.post_process_data = cinfo->upsample->upsample; + } + break; +#ifdef QUANT_2PASS_SUPPORTED + case JBUF_SAVE_AND_PASS: + /* First pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_prepass; + break; + case JBUF_CRANK_DEST: + /* Second pass of 2-pass quantization */ + if (post->whole_image == NULL) + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + post->pub.post_process_data = post_process_2pass; + break; +#endif /* QUANT_2PASS_SUPPORTED */ + default: + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); + break; + } + post->starting_row = post->next_row = 0; +} + + +/* + * Process some data in the one-pass (strip buffer) case. + * This is used for color precision reduction as well as one-pass quantization. + */ + +METHODDEF void +post_process_1pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Fill the buffer, but not more than what we can dump out in one go. */ + /* Note we rely on the upsampler to detect bottom of image. */ + max_rows = out_rows_avail - *out_row_ctr; + if (max_rows > post->strip_height) + max_rows = post->strip_height; + num_rows = 0; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &num_rows, max_rows); + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer, output_buf + *out_row_ctr, (int) num_rows); + *out_row_ctr += num_rows; +} + + +#ifdef QUANT_2PASS_SUPPORTED + +/* + * Process some data in the first pass of 2-pass quantization. + */ + +METHODDEF void +post_process_prepass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION old_next_row, num_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, TRUE); + } + + /* Upsample some data (up to a strip height's worth). */ + old_next_row = post->next_row; + (*cinfo->upsample->upsample) (cinfo, + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &post->next_row, post->strip_height); + + /* Allow quantizer to scan new data. No data is emitted, */ + /* but we advance out_row_ctr so outer loop can tell when we're done. */ + if (post->next_row > old_next_row) { + num_rows = post->next_row - old_next_row; + (*cinfo->cquantize->color_quantize) (cinfo, post->buffer + old_next_row, + (JSAMPARRAY) NULL, (int) num_rows); + *out_row_ctr += num_rows; + } + + /* Advance if we filled the strip. */ + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + + +/* + * Process some data in the second pass of 2-pass quantization. + */ + +METHODDEF void +post_process_2pass (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_post_ptr post = (my_post_ptr) cinfo->post; + JDIMENSION num_rows, max_rows; + + /* Reposition virtual buffer if at start of strip. */ + if (post->next_row == 0) { + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, FALSE); + } + + /* Determine number of rows to emit. */ + num_rows = post->strip_height - post->next_row; /* available in strip */ + max_rows = out_rows_avail - *out_row_ctr; /* available in output area */ + if (num_rows > max_rows) + num_rows = max_rows; + /* We have to check bottom of image here, can't depend on upsampler. */ + max_rows = cinfo->output_height - post->starting_row; + if (num_rows > max_rows) + num_rows = max_rows; + + /* Quantize and emit data. */ + (*cinfo->cquantize->color_quantize) (cinfo, + post->buffer + post->next_row, output_buf + *out_row_ctr, + (int) num_rows); + *out_row_ctr += num_rows; + + /* Advance if we filled the strip. */ + post->next_row += num_rows; + if (post->next_row >= post->strip_height) { + post->starting_row += post->strip_height; + post->next_row = 0; + } +} + +#endif /* QUANT_2PASS_SUPPORTED */ + + +/* + * Initialize postprocessing controller. + */ + +GLOBAL void +jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) +{ + my_post_ptr post; + + post = (my_post_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_post_controller)); + cinfo->post = (struct jpeg_d_post_controller *) post; + post->pub.start_pass = start_pass_dpost; + post->whole_image = NULL; /* flag for no virtual arrays */ + post->buffer = NULL; /* flag for no strip buffer */ + + /* Create the quantization buffer, if needed */ + if (cinfo->quantize_colors) { + /* The buffer strip height is max_v_samp_factor, which is typically + * an efficient number of rows for upsampling to return. + * (In the presence of output rescaling, we might want to be smarter?) + */ + post->strip_height = (JDIMENSION) cinfo->max_v_samp_factor; + if (need_full_buffer) { + /* Two-pass color quantization: need full-image storage. */ + /* We round up the number of rows to a multiple of the strip height. */ +#ifdef QUANT_2PASS_SUPPORTED + post->whole_image = (*cinfo->mem->request_virt_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + cinfo->output_width * cinfo->out_color_components, + (JDIMENSION) jround_up((long) cinfo->output_height, + (long) post->strip_height), + post->strip_height); +#else + ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); +#endif /* QUANT_2PASS_SUPPORTED */ + } else { + /* One-pass color quantization: just make a strip buffer. */ + post->buffer = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->output_width * cinfo->out_color_components, + post->strip_height); + } + } +} diff --git a/code/jpeg-6/jdsample.cpp b/code/jpeg-6/jdsample.cpp new file mode 100644 index 0000000..4629286 --- /dev/null +++ b/code/jpeg-6/jdsample.cpp @@ -0,0 +1,484 @@ +/* + * jdsample.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains upsampling routines. + * + * Upsampling input data is counted in "row groups". A row group + * is defined to be (v_samp_factor * DCT_scaled_size / min_DCT_scaled_size) + * sample rows of each component. Upsampling will normally produce + * max_v_samp_factor pixel rows from each row group (but this could vary + * if the upsampler is applying a scale factor of its own). + * + * An excellent reference for image resampling is + * Digital Image Warping, George Wolberg, 1990. + * Pub. by IEEE Computer Society Press, Los Alamitos, CA. ISBN 0-8186-8944-7. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Pointer to routine to upsample a single component */ +typedef JMETHOD(void, upsample1_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)); + +/* Private subobject */ + +typedef struct { + struct jpeg_upsampler pub; /* public fields */ + + /* Color conversion buffer. When using separate upsampling and color + * conversion steps, this buffer holds one upsampled row group until it + * has been color converted and output. + * Note: we do not allocate any storage for component(s) which are full-size, + * ie do not need rescaling. The corresponding entry of color_buf[] is + * simply set to point to the input data array, thereby avoiding copying. + */ + JSAMPARRAY color_buf[MAX_COMPONENTS]; + + /* Per-component upsampling method pointers */ + upsample1_ptr methods[MAX_COMPONENTS]; + + int next_row_out; /* counts rows emitted from color_buf */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ + + /* Height of an input row group for each component. */ + int rowgroup_height[MAX_COMPONENTS]; + + /* These arrays save pixel expansion factors so that int_expand need not + * recompute them each time. They are unused for other upsampling methods. + */ + UINT8 h_expand[MAX_COMPONENTS]; + UINT8 v_expand[MAX_COMPONENTS]; +} my_upsampler; + +typedef my_upsampler * my_upsample_ptr; + + +/* + * Initialize for an upsampling pass. + */ + +METHODDEF void +start_pass_upsample (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + + /* Mark the conversion buffer empty */ + upsample->next_row_out = cinfo->max_v_samp_factor; + /* Initialize total-height counter for detecting bottom of image */ + upsample->rows_to_go = cinfo->output_height; +} + + +/* + * Control routine to do upsampling (and color conversion). + * + * In this version we upsample each component independently. + * We upsample one row group into the conversion buffer, then apply + * color conversion a row at a time. + */ + +METHODDEF void +sep_upsample (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + int ci; + jpeg_component_info * compptr; + JDIMENSION num_rows; + + /* Fill the conversion buffer, if it's empty */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) { + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Invoke per-component upsample method. Notice we pass a POINTER + * to color_buf[ci], so that fullsize_upsample can change it. + */ + (*upsample->methods[ci]) (cinfo, compptr, + input_buf[ci] + (*in_row_group_ctr * upsample->rowgroup_height[ci]), + upsample->color_buf + ci); + } + upsample->next_row_out = 0; + } + + /* Color-convert and emit rows */ + + /* How many we have in the buffer: */ + num_rows = (JDIMENSION) (cinfo->max_v_samp_factor - upsample->next_row_out); + /* Not more than the distance to the end of the image. Need this test + * in case the image height is not a multiple of max_v_samp_factor: + */ + if (num_rows > upsample->rows_to_go) + num_rows = upsample->rows_to_go; + /* And not more than what the client can accept: */ + out_rows_avail -= *out_row_ctr; + if (num_rows > out_rows_avail) + num_rows = out_rows_avail; + + (*cinfo->cconvert->color_convert) (cinfo, upsample->color_buf, + (JDIMENSION) upsample->next_row_out, + output_buf + *out_row_ctr, + (int) num_rows); + + /* Adjust counts */ + *out_row_ctr += num_rows; + upsample->rows_to_go -= num_rows; + upsample->next_row_out += num_rows; + /* When the buffer is emptied, declare this input row group consumed */ + if (upsample->next_row_out >= cinfo->max_v_samp_factor) + (*in_row_group_ctr)++; +} + + +/* + * These are the routines invoked by sep_upsample to upsample pixel values + * of a single component. One row group is processed per call. + */ + + +/* + * For full-size components, we just make color_buf[ci] point at the + * input buffer, and thus avoid copying any data. Note that this is + * safe only because sep_upsample doesn't declare the input row group + * "consumed" until we are done color converting and emitting it. + */ + +METHODDEF void +fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = input_data; +} + + +/* + * This is a no-op version used for "uninteresting" components. + * These components will not be referenced by color conversion. + */ + +METHODDEF void +noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + *output_data_ptr = NULL; /* safety check */ +} + + +/* + * This version handles any integral sampling ratios. + * This is not used for typical JPEG files, so it need not be fast. + * Nor, for that matter, is it particularly accurate: the algorithm is + * simple replication of the input pixel onto the corresponding output + * pixels. The hi-falutin sampling literature refers to this as a + * "box filter". A box filter tends to introduce visible artifacts, + * so if you are actually going to use 3:1 or 4:1 sampling ratios + * you would be well advised to improve this code. + */ + +METHODDEF void +int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + register int h; + JSAMPROW outend; + int h_expand, v_expand; + int inrow, outrow; + + h_expand = upsample->h_expand[compptr->component_index]; + v_expand = upsample->v_expand[compptr->component_index]; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + /* Generate one output row with proper horizontal expansion */ + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + for (h = h_expand; h > 0; h--) { + *outptr++ = invalue; + } + } + /* Generate any additional output rows by duplicating the first one */ + if (v_expand > 1) { + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + v_expand-1, cinfo->output_width); + } + inrow++; + outrow += v_expand; + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 1:1 vertical. + * It's still a box filter. + */ + +METHODDEF void +h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow; + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + } +} + + +/* + * Fast processing for the common case of 2:1 horizontal and 2:1 vertical. + * It's still a box filter. + */ + +METHODDEF void +h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register JSAMPLE invalue; + JSAMPROW outend; + int inrow, outrow; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + inptr = input_data[inrow]; + outptr = output_data[outrow]; + outend = outptr + cinfo->output_width; + while (outptr < outend) { + invalue = *inptr++; /* don't need GETJSAMPLE() here */ + *outptr++ = invalue; + *outptr++ = invalue; + } + jcopy_sample_rows(output_data, outrow, output_data, outrow+1, + 1, cinfo->output_width); + inrow++; + outrow += 2; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 1:1 vertical. + * + * The upsampling algorithm is linear interpolation between pixel centers, + * also known as a "triangle filter". This is a good compromise between + * speed and visual quality. The centers of the output pixels are 1/4 and 3/4 + * of the way between input pixel centers. + * + * A note about the "bias" calculations: when rounding fractional values to + * integer, we do not want to always round 0.5 up to the next integer. + * If we did that, we'd introduce a noticeable bias towards larger values. + * Instead, this code is arranged so that 0.5 will be rounded up or down at + * alternate pixel locations (a simple ordered dither pattern). + */ + +METHODDEF void +h2v1_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr, outptr; + register int invalue; + register JDIMENSION colctr; + int inrow; + + for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { + inptr = input_data[inrow]; + outptr = output_data[inrow]; + /* Special case for first column */ + invalue = GETJSAMPLE(*inptr++); + *outptr++ = (JSAMPLE) invalue; + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(*inptr) + 2) >> 2); + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel */ + invalue = GETJSAMPLE(*inptr++) * 3; + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(inptr[-2]) + 1) >> 2); + *outptr++ = (JSAMPLE) ((invalue + GETJSAMPLE(*inptr) + 2) >> 2); + } + + /* Special case for last column */ + invalue = GETJSAMPLE(*inptr); + *outptr++ = (JSAMPLE) ((invalue * 3 + GETJSAMPLE(inptr[-1]) + 1) >> 2); + *outptr++ = (JSAMPLE) invalue; + } +} + + +/* + * Fancy processing for the common case of 2:1 horizontal and 2:1 vertical. + * Again a triangle filter; see comments for h2v1 case, above. + * + * It is OK for us to reference the adjacent input rows because we demanded + * context from the main buffer controller (see initialization code). + */ + +METHODDEF void +h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) +{ + JSAMPARRAY output_data = *output_data_ptr; + register JSAMPROW inptr0, inptr1, outptr; +#if BITS_IN_JSAMPLE == 8 + register int thiscolsum, lastcolsum, nextcolsum; +#else + register INT32 thiscolsum, lastcolsum, nextcolsum; +#endif + register JDIMENSION colctr; + int inrow, outrow, v; + + inrow = outrow = 0; + while (outrow < cinfo->max_v_samp_factor) { + for (v = 0; v < 2; v++) { + /* inptr0 points to nearest input row, inptr1 points to next nearest */ + inptr0 = input_data[inrow]; + if (v == 0) /* next nearest is row above */ + inptr1 = input_data[inrow-1]; + else /* next nearest is row below */ + inptr1 = input_data[inrow+1]; + outptr = output_data[outrow++]; + + /* Special case for first column */ + thiscolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + + for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { + /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ + /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; + } + + /* Special case for last column */ + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 4 + 7) >> 4); + } + inrow++; + } +} + + +/* + * Module initialization routine for upsampling. + */ + +GLOBAL void +jinit_upsampler (j_decompress_ptr cinfo) +{ + my_upsample_ptr upsample; + int ci; + jpeg_component_info * compptr; + boolean need_buffer, do_fancy; + int h_in_group, v_in_group, h_out_group, v_out_group; + + upsample = (my_upsample_ptr) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(my_upsampler)); + cinfo->upsample = (struct jpeg_upsampler *) upsample; + upsample->pub.start_pass = start_pass_upsample; + upsample->pub.upsample = sep_upsample; + upsample->pub.need_context_rows = FALSE; /* until we find out differently */ + + if (cinfo->CCIR601_sampling) /* this isn't supported */ + ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); + + /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1, + * so don't ask for it. + */ + do_fancy = cinfo->do_fancy_upsampling && cinfo->min_DCT_scaled_size > 1; + + /* Verify we can handle the sampling factors, select per-component methods, + * and create storage as needed. + */ + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; + ci++, compptr++) { + /* Compute size of an "input group" after IDCT scaling. This many samples + * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. + */ + h_in_group = (compptr->h_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; + v_in_group = (compptr->v_samp_factor * compptr->DCT_scaled_size) / + cinfo->min_DCT_scaled_size; + h_out_group = cinfo->max_h_samp_factor; + v_out_group = cinfo->max_v_samp_factor; + upsample->rowgroup_height[ci] = v_in_group; /* save for use later */ + need_buffer = TRUE; + if (! compptr->component_needed) { + /* Don't bother to upsample an uninteresting component. */ + upsample->methods[ci] = noop_upsample; + need_buffer = FALSE; + } else if (h_in_group == h_out_group && v_in_group == v_out_group) { + /* Fullsize components can be processed without any work. */ + upsample->methods[ci] = fullsize_upsample; + need_buffer = FALSE; + } else if (h_in_group * 2 == h_out_group && + v_in_group == v_out_group) { + /* Special cases for 2h1v upsampling */ + if (do_fancy && compptr->downsampled_width > 2) + upsample->methods[ci] = h2v1_fancy_upsample; + else + upsample->methods[ci] = h2v1_upsample; + } else if (h_in_group * 2 == h_out_group && + v_in_group * 2 == v_out_group) { + /* Special cases for 2h2v upsampling */ + if (do_fancy && compptr->downsampled_width > 2) { + upsample->methods[ci] = h2v2_fancy_upsample; + upsample->pub.need_context_rows = TRUE; + } else + upsample->methods[ci] = h2v2_upsample; + } else if ((h_out_group % h_in_group) == 0 && + (v_out_group % v_in_group) == 0) { + /* Generic integral-factors upsampling method */ + upsample->methods[ci] = int_upsample; + upsample->h_expand[ci] = (UINT8) (h_out_group / h_in_group); + upsample->v_expand[ci] = (UINT8) (v_out_group / v_in_group); + } else + ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); + if (need_buffer) { + upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) jround_up((long) cinfo->output_width, + (long) cinfo->max_h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); + } + } +} diff --git a/code/jpeg-6/jdtrans.cpp b/code/jpeg-6/jdtrans.cpp new file mode 100644 index 0000000..6c5a878 --- /dev/null +++ b/code/jpeg-6/jdtrans.cpp @@ -0,0 +1,129 @@ +/* + * jdtrans.c + * + * Copyright (C) 1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains library routines for transcoding decompression, + * that is, reading raw DCT coefficient arrays from an input JPEG file. + * The routines in jdapimin.c will also be needed by a transcoder. + */ + + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* Forward declarations */ +LOCAL void transdecode_master_selection JPP((j_decompress_ptr cinfo)); + + +/* + * Read the coefficient arrays from a JPEG file. + * jpeg_read_header must be completed before calling this. + * + * The entire image is read into a set of virtual coefficient-block arrays, + * one per component. The return value is a pointer to the array of + * virtual-array descriptors. These can be manipulated directly via the + * JPEG memory manager, or handed off to jpeg_write_coefficients(). + * To release the memory occupied by the virtual arrays, call + * jpeg_finish_decompress() when done with the data. + * + * Returns NULL if suspended. This case need be checked only if + * a suspending data source is used. + */ + +GLOBAL jvirt_barray_ptr * +jpeg_read_coefficients (j_decompress_ptr cinfo) +{ + if (cinfo->global_state == DSTATE_READY) { + /* First call: initialize active modules */ + transdecode_master_selection(cinfo); + cinfo->global_state = DSTATE_RDCOEFS; + } else if (cinfo->global_state != DSTATE_RDCOEFS) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + /* Absorb whole file into the coef buffer */ + for (;;) { + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return NULL; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* startup underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } + } + /* Set state so that jpeg_finish_decompress does the right thing */ + cinfo->global_state = DSTATE_STOPPING; + return cinfo->coef->coef_arrays; +} + + +/* + * Master selection of decompression modules for transcoding. + * This substitutes for jdmaster.c's initialization of the full decompressor. + */ + +LOCAL void +transdecode_master_selection (j_decompress_ptr cinfo) +{ + /* Entropy decoding: either Huffman or arithmetic coding. */ + if (cinfo->arith_code) { + ERREXIT(cinfo, JERR_ARITH_NOTIMPL); + } else { + if (cinfo->progressive_mode) { +#ifdef D_PROGRESSIVE_SUPPORTED + jinit_phuff_decoder(cinfo); +#else + ERREXIT(cinfo, JERR_NOT_COMPILED); +#endif + } else + jinit_huff_decoder(cinfo); + } + + /* Always get a full-image coefficient buffer. */ + jinit_d_coef_controller(cinfo, TRUE); + + /* We can now tell the memory manager to allocate virtual arrays. */ + (*cinfo->mem->realize_virt_arrays) ((j_common_ptr) cinfo); + + /* Initialize input side of decompressor to consume first scan. */ + (*cinfo->inputctl->start_input_pass) (cinfo); + + /* Initialize progress monitoring. */ + if (cinfo->progress != NULL) { + int nscans; + /* Estimate number of scans to set pass_limit. */ + if (cinfo->progressive_mode) { + /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */ + nscans = 2 + 3 * cinfo->num_components; + } else if (cinfo->inputctl->has_multiple_scans) { + /* For a nonprogressive multiscan file, estimate 1 scan per component. */ + nscans = cinfo->num_components; + } else { + nscans = 1; + } + cinfo->progress->pass_counter = 0L; + cinfo->progress->pass_limit = (long) cinfo->total_iMCU_rows * nscans; + cinfo->progress->completed_passes = 0; + cinfo->progress->total_passes = 1; + } +} diff --git a/code/jpeg-6/jerror.cpp b/code/jpeg-6/jerror.cpp new file mode 100644 index 0000000..fc34fc9 --- /dev/null +++ b/code/jpeg-6/jerror.cpp @@ -0,0 +1,239 @@ +/* + * jerror.c + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains simple error-reporting and trace-message routines. + * These are suitable for Unix-like systems and others where writing to + * stderr is the right thing to do. Many applications will want to replace + * some or all of these routines. + * + * These routines are used by both the compression and decompression code. + */ + + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +/* this is not a core library module, so it doesn't define JPEG_INTERNALS */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jversion.h" +#include "jerror.h" + +#include "../renderer/tr_local.h" + +#ifndef EXIT_FAILURE /* define exit() codes if not provided */ +#define EXIT_FAILURE 1 +#endif + + +/* + * Create the message string table. + * We do this from the master message list in jerror.h by re-reading + * jerror.h with a suitable definition for macro JMESSAGE. + * The message table is made an external symbol just in case any applications + * want to refer to it directly. + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_message_table jMsgTable +#endif + +#define JMESSAGE(code,string) string , + +const char * const jpeg_std_message_table[] = { +#include "jerror.h" + NULL +}; + + +/* + * Error exit handler: must not return to caller. + * + * Applications may override this if they want to get control back after + * an error. Typically one would longjmp somewhere instead of exiting. + * The setjmp buffer can be made a private field within an expanded error + * handler object. Note that the info needed to generate an error message + * is stored in the error object, so you can generate the message now or + * later, at your convenience. + * You should make sure that the JPEG object is cleaned up (with jpeg_abort + * or jpeg_destroy) at some point. + */ + +METHODDEF void +error_exit (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + /* Let the memory manager delete any temp files before we die */ + jpeg_destroy(cinfo); + + Com_Error( ERR_FATAL, "%s\n", buffer ); +} + + +/* + * Actual output of an error or trace message. + * Applications may override this method to send JPEG messages somewhere + * other than stderr. + */ + +METHODDEF void +output_message (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message) (cinfo, buffer); + + /* Send it to stderr, adding a newline */ + VID_Printf(PRINT_ALL, "%s\n", buffer); +} + + +/* + * Decide whether to emit a trace or warning message. + * msg_level is one of: + * -1: recoverable corrupt-data warning, may want to abort. + * 0: important advisory messages (always display to user). + * 1: first level of tracing detail. + * 2,3,...: successively more detailed tracing messages. + * An application might override this method if it wanted to abort on warnings + * or change the policy about which messages to display. + */ + +METHODDEF void +emit_message (j_common_ptr cinfo, int msg_level) +{ + struct jpeg_error_mgr * err = cinfo->err; + + if (msg_level < 0) { + /* It's a warning message. Since corrupt files may generate many warnings, + * the policy implemented here is to show only the first warning, + * unless trace_level >= 3. + */ + if (err->num_warnings == 0 || err->trace_level >= 3) + (*err->output_message) (cinfo); + /* Always count warnings in num_warnings. */ + err->num_warnings++; + } else { + /* It's a trace message. Show it if trace_level >= msg_level. */ + if (err->trace_level >= msg_level) + (*err->output_message) (cinfo); + } +} + + +/* + * Format a message string for the most recent JPEG error or message. + * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX + * characters. Note that no '\n' character is added to the string. + * Few applications should need to override this method. + */ + +METHODDEF void +format_message (j_common_ptr cinfo, char * buffer) +{ + struct jpeg_error_mgr * err = cinfo->err; + int msg_code = err->msg_code; + const char * msgtext = NULL; + const char * msgptr; + char ch; + boolean isstring; + + /* Look up message string in proper table */ + if (msg_code > 0 && msg_code <= err->last_jpeg_message) { + msgtext = err->jpeg_message_table[msg_code]; + } else if (err->addon_message_table != NULL && + msg_code >= err->first_addon_message && + msg_code <= err->last_addon_message) { + msgtext = err->addon_message_table[msg_code - err->first_addon_message]; + } + + /* Defend against bogus message number */ + if (msgtext == NULL) { + err->msg_parm.i[0] = msg_code; + msgtext = err->jpeg_message_table[0]; + } + + /* Check for string parameter, as indicated by %s in the message text */ + isstring = FALSE; + msgptr = msgtext; + while ((ch = *msgptr++) != '\0') { + if (ch == '%') { + if (*msgptr == 's') isstring = TRUE; + break; + } + } + + /* Format the message into the passed buffer */ + if (isstring) + sprintf(buffer, msgtext, err->msg_parm.s); + else + sprintf(buffer, msgtext, + err->msg_parm.i[0], err->msg_parm.i[1], + err->msg_parm.i[2], err->msg_parm.i[3], + err->msg_parm.i[4], err->msg_parm.i[5], + err->msg_parm.i[6], err->msg_parm.i[7]); +} + + +/* + * Reset error state variables at start of a new image. + * This is called during compression startup to reset trace/error + * processing to default state, without losing any application-specific + * method pointers. An application might possibly want to override + * this method if it has additional error processing state. + */ + +METHODDEF void +reset_error_mgr (j_common_ptr cinfo) +{ + cinfo->err->num_warnings = 0; + /* trace_level is not reset since it is an application-supplied parameter */ + cinfo->err->msg_code = 0; /* may be useful as a flag for "no error" */ +} + + +/* + * Fill in the standard error-handling methods in a jpeg_error_mgr object. + * Typical call is: + * struct jpeg_compress_struct cinfo; + * struct jpeg_error_mgr err; + * + * cinfo.err = jpeg_std_error(&err); + * after which the application may override some of the methods. + */ + +GLOBAL struct jpeg_error_mgr * +jpeg_std_error (struct jpeg_error_mgr * err) +{ + err->error_exit = error_exit; + err->emit_message = emit_message; + err->output_message = output_message; + err->format_message = format_message; + err->reset_error_mgr = reset_error_mgr; + + err->trace_level = 0; /* default = no tracing */ + err->num_warnings = 0; /* no warnings emitted yet */ + err->msg_code = 0; /* may be useful as a flag for "no error" */ + + /* Initialize message table pointers */ + err->jpeg_message_table = jpeg_std_message_table; + err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1; + + err->addon_message_table = NULL; + err->first_addon_message = 0; /* for safety */ + err->last_addon_message = 0; + + return err; +} diff --git a/code/jpeg-6/jerror.h b/code/jpeg-6/jerror.h new file mode 100644 index 0000000..bf60e7e --- /dev/null +++ b/code/jpeg-6/jerror.h @@ -0,0 +1,273 @@ +/* + * jerror.h + * + * Copyright (C) 1994-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the error and message codes for the JPEG library. + * Edit this file to add new codes, or to translate the message strings to + * some other language. + * A set of error-reporting macros are defined too. Some applications using + * the JPEG library may wish to include this file to get the error codes + * and/or the macros. + */ + +/* + * To define the enum list of message codes, include this file without + * defining macro JMESSAGE. To create a message string table, include it + * again with a suitable JMESSAGE definition (see jerror.c for an example). + */ +#ifndef JMESSAGE +#ifndef JERROR_H +/* First time through, define the enum list */ +#define JMAKE_ENUM_LIST +#else +/* Repeated inclusions of this file are no-ops unless JMESSAGE is defined */ +#define JMESSAGE(code,string) +#endif /* JERROR_H */ +#endif /* JMESSAGE */ + +#ifdef JMAKE_ENUM_LIST + +typedef enum { + +#define JMESSAGE(code,string) code , + +#endif /* JMAKE_ENUM_LIST */ + +JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */ + +/* For maintenance convenience, list is alphabetical by message code name */ +JMESSAGE(JERR_ARITH_NOTIMPL, + "Sorry, there are legal restrictions on arithmetic coding") +JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix") +JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix") +JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode") +JMESSAGE(JERR_BAD_COMPONENT_ID, "Invalid component ID %d in SOS") +JMESSAGE(JERR_BAD_DCTSIZE, "IDCT output block size %d not supported") +JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace") +JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace") +JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length") +JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan") +JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d") +JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d") +JMESSAGE(JERR_BAD_PROGRESSION, + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") +JMESSAGE(JERR_BAD_PROG_SCRIPT, + "Invalid progressive parameters at scan script entry %d") +JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors") +JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d") +JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d") +JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access") +JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small") +JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here") +JMESSAGE(JERR_CCIR601_NOTIMPL, "CCIR601 sampling not implemented yet") +JMESSAGE(JERR_COMPONENT_COUNT, "Too many color components: %d, max %d") +JMESSAGE(JERR_CONVERSION_NOTIMPL, "Unsupported color conversion request") +JMESSAGE(JERR_DAC_INDEX, "Bogus DAC index %d") +JMESSAGE(JERR_DAC_VALUE, "Bogus DAC value 0x%x") +JMESSAGE(JERR_DHT_COUNTS, "Bogus DHT counts") +JMESSAGE(JERR_DHT_INDEX, "Bogus DHT index %d") +JMESSAGE(JERR_DQT_INDEX, "Bogus DQT index %d") +JMESSAGE(JERR_EMPTY_IMAGE, "Empty JPEG image (DNL not supported)") +JMESSAGE(JERR_EMS_READ, "Read from EMS failed") +JMESSAGE(JERR_EMS_WRITE, "Write to EMS failed") +JMESSAGE(JERR_EOI_EXPECTED, "Didn't expect more than one scan") +JMESSAGE(JERR_FILE_READ, "Input file read error") +JMESSAGE(JERR_FILE_WRITE, "Output file write error --- out of disk space?") +JMESSAGE(JERR_FRACT_SAMPLE_NOTIMPL, "Fractional sampling not implemented yet") +JMESSAGE(JERR_HUFF_CLEN_OVERFLOW, "Huffman code size table overflow") +JMESSAGE(JERR_HUFF_MISSING_CODE, "Missing Huffman code table entry") +JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels") +JMESSAGE(JERR_INPUT_EMPTY, "Empty input file") +JMESSAGE(JERR_INPUT_EOF, "Premature end of input file") +JMESSAGE(JERR_MISMATCHED_QUANT_TABLE, + "Cannot transcode due to multiple use of quantization table %d") +JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data") +JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change") +JMESSAGE(JERR_NOTIMPL, "Not implemented yet") +JMESSAGE(JERR_NOT_COMPILED, "Requested feature was omitted at compile time") +JMESSAGE(JERR_NO_BACKING_STORE, "Backing store not supported") +JMESSAGE(JERR_NO_HUFF_TABLE, "Huffman table 0x%02x was not defined") +JMESSAGE(JERR_NO_IMAGE, "JPEG datastream contains no image") +JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined") +JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x") +JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)") +JMESSAGE(JERR_QUANT_COMPONENTS, + "Cannot quantize more than %d color components") +JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors") +JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors") +JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers") +JMESSAGE(JERR_SOF_NO_SOS, "Invalid JPEG file structure: missing SOS marker") +JMESSAGE(JERR_SOF_UNSUPPORTED, "Unsupported JPEG process: SOF type 0x%02x") +JMESSAGE(JERR_SOI_DUPLICATE, "Invalid JPEG file structure: two SOI markers") +JMESSAGE(JERR_SOS_NO_SOF, "Invalid JPEG file structure: SOS before SOF") +JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s") +JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file") +JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file") +JMESSAGE(JERR_TFILE_WRITE, + "Write failed on temporary file --- out of disk space?") +JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines") +JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x") +JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up") +JMESSAGE(JERR_WIDTH_OVERFLOW, "Image too wide for this implementation") +JMESSAGE(JERR_XMS_READ, "Read from XMS failed") +JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed") +JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT) +JMESSAGE(JMSG_VERSION, JVERSION) +JMESSAGE(JTRC_16BIT_TABLES, + "Caution: quantization tables are too coarse for baseline JPEG") +JMESSAGE(JTRC_ADOBE, + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") +JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u") +JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u") +JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x") +JMESSAGE(JTRC_DHT, "Define Huffman Table 0x%02x") +JMESSAGE(JTRC_DQT, "Define Quantization Table %d precision %d") +JMESSAGE(JTRC_DRI, "Define Restart Interval %u") +JMESSAGE(JTRC_EMS_CLOSE, "Freed EMS handle %u") +JMESSAGE(JTRC_EMS_OPEN, "Obtained EMS handle %u") +JMESSAGE(JTRC_EOI, "End Of Image") +JMESSAGE(JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d") +JMESSAGE(JTRC_JFIF, "JFIF APP0 marker, density %dx%d %d") +JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE, + "Warning: thumbnail image size does not match data length %u") +JMESSAGE(JTRC_JFIF_MINOR, "Unknown JFIF minor revision number %d.%02d") +JMESSAGE(JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image") +JMESSAGE(JTRC_MISC_MARKER, "Skipping marker 0x%02x, length %u") +JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x") +JMESSAGE(JTRC_QUANTVALS, " %4u %4u %4u %4u %4u %4u %4u %4u") +JMESSAGE(JTRC_QUANT_3_NCOLORS, "Quantizing to %d = %d*%d*%d colors") +JMESSAGE(JTRC_QUANT_NCOLORS, "Quantizing to %d colors") +JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization") +JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d") +JMESSAGE(JTRC_RST, "RST%d") +JMESSAGE(JTRC_SMOOTH_NOTIMPL, + "Smoothing not supported with nonstandard sampling ratios") +JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d") +JMESSAGE(JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d") +JMESSAGE(JTRC_SOI, "Start of Image") +JMESSAGE(JTRC_SOS, "Start Of Scan: %d components") +JMESSAGE(JTRC_SOS_COMPONENT, " Component %d: dc=%d ac=%d") +JMESSAGE(JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d") +JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s") +JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s") +JMESSAGE(JTRC_UNKNOWN_IDS, + "Unrecognized component IDs %d %d %d, assuming YCbCr") +JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u") +JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u") +JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d") +JMESSAGE(JWRN_BOGUS_PROGRESSION, + "Inconsistent progression sequence for component %d coefficient %d") +JMESSAGE(JWRN_EXTRANEOUS_DATA, + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") +JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment") +JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code") +JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d") +JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file") +JMESSAGE(JWRN_MUST_RESYNC, + "Corrupt JPEG data: found marker 0x%02x instead of RST%d") +JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG") +JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") + +#ifdef JMAKE_ENUM_LIST + + JMSG_LASTMSGCODE +} J_MESSAGE_CODE; + +#undef JMAKE_ENUM_LIST +#endif /* JMAKE_ENUM_LIST */ + +/* Zap JMESSAGE macro so that future re-inclusions do nothing by default */ +#undef JMESSAGE + + +#ifndef JERROR_H +#define JERROR_H + +/* Macros to simplify using the error and trace message stuff */ +/* The first parameter is either type of cinfo pointer */ + +/* Fatal errors (print message and exit) */ +#define ERREXIT(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT3(cinfo,code,p1,p2,p3) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXIT4(cinfo,code,p1,p2,p3,p4) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (cinfo)->err->msg_parm.i[2] = (p3), \ + (cinfo)->err->msg_parm.i[3] = (p4), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) +#define ERREXITS(cinfo,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) + +#define MAKESTMT(stuff) do { stuff } while (0) + +/* Nonfatal errors (we can keep going, but the data is probably corrupt) */ +#define WARNMS(cinfo,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS1(cinfo,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) +#define WARNMS2(cinfo,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), -1)) + +/* Informational/debugging messages */ +#define TRACEMS(cinfo,lvl,code) \ + ((cinfo)->err->msg_code = (code), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS1(cinfo,lvl,code,p1) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS2(cinfo,lvl,code,p1,p2) \ + ((cinfo)->err->msg_code = (code), \ + (cinfo)->err->msg_parm.i[0] = (p1), \ + (cinfo)->err->msg_parm.i[1] = (p2), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) +#define TRACEMS3(cinfo,lvl,code,p1,p2,p3) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS4(cinfo,lvl,code,p1,p2,p3,p4) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMS8(cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8) \ + MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) +#define TRACEMSS(cinfo,lvl,code,str) \ + ((cinfo)->err->msg_code = (code), \ + strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) + +#endif /* JERROR_H */ diff --git a/code/jpeg-6/jfdctflt.cpp b/code/jpeg-6/jfdctflt.cpp new file mode 100644 index 0000000..e15e190 --- /dev/null +++ b/code/jpeg-6/jfdctflt.cpp @@ -0,0 +1,174 @@ +/* + * jfdctflt.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * forward DCT (Discrete Cosine Transform). + * + * This implementation should be more accurate than either of the integer + * DCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D DCT can be done by 1-D DCT on each row followed by 1-D DCT + * on each column. Direct algorithms are also available, but they are + * much more complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* + * Perform the forward DCT on one block of samples. + */ + +GLOBAL void +jpeg_fdct_float (FAST_FLOAT * data) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z1, z2, z3, z4, z5, z11, z13; + FAST_FLOAT *dataptr; + int ctr; + + /* Pass 1: process rows. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[0] + dataptr[7]; + tmp7 = dataptr[0] - dataptr[7]; + tmp1 = dataptr[1] + dataptr[6]; + tmp6 = dataptr[1] - dataptr[6]; + tmp2 = dataptr[2] + dataptr[5]; + tmp5 = dataptr[2] - dataptr[5]; + tmp3 = dataptr[3] + dataptr[4]; + tmp4 = dataptr[3] - dataptr[4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[0] = tmp10 + tmp11; /* phase 3 */ + dataptr[4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[2] = tmp13 + z1; /* phase 5 */ + dataptr[6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[5] = z13 + z2; /* phase 6 */ + dataptr[3] = z13 - z2; + dataptr[1] = z11 + z4; + dataptr[7] = z11 - z4; + + dataptr += DCTSIZE; /* advance pointer to next row */ + } + + /* Pass 2: process columns. */ + + dataptr = data; + for (ctr = DCTSIZE-1; ctr >= 0; ctr--) { + tmp0 = dataptr[DCTSIZE*0] + dataptr[DCTSIZE*7]; + tmp7 = dataptr[DCTSIZE*0] - dataptr[DCTSIZE*7]; + tmp1 = dataptr[DCTSIZE*1] + dataptr[DCTSIZE*6]; + tmp6 = dataptr[DCTSIZE*1] - dataptr[DCTSIZE*6]; + tmp2 = dataptr[DCTSIZE*2] + dataptr[DCTSIZE*5]; + tmp5 = dataptr[DCTSIZE*2] - dataptr[DCTSIZE*5]; + tmp3 = dataptr[DCTSIZE*3] + dataptr[DCTSIZE*4]; + tmp4 = dataptr[DCTSIZE*3] - dataptr[DCTSIZE*4]; + + /* Even part */ + + tmp10 = tmp0 + tmp3; /* phase 2 */ + tmp13 = tmp0 - tmp3; + tmp11 = tmp1 + tmp2; + tmp12 = tmp1 - tmp2; + + dataptr[DCTSIZE*0] = tmp10 + tmp11; /* phase 3 */ + dataptr[DCTSIZE*4] = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * ((FAST_FLOAT) 0.707106781); /* c4 */ + dataptr[DCTSIZE*2] = tmp13 + z1; /* phase 5 */ + dataptr[DCTSIZE*6] = tmp13 - z1; + + /* Odd part */ + + tmp10 = tmp4 + tmp5; /* phase 2 */ + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + /* The rotator is modified from fig 4-8 to avoid extra negations. */ + z5 = (tmp10 - tmp12) * ((FAST_FLOAT) 0.382683433); /* c6 */ + z2 = ((FAST_FLOAT) 0.541196100) * tmp10 + z5; /* c2-c6 */ + z4 = ((FAST_FLOAT) 1.306562965) * tmp12 + z5; /* c2+c6 */ + z3 = tmp11 * ((FAST_FLOAT) 0.707106781); /* c4 */ + + z11 = tmp7 + z3; /* phase 5 */ + z13 = tmp7 - z3; + + dataptr[DCTSIZE*5] = z13 + z2; /* phase 6 */ + dataptr[DCTSIZE*3] = z13 - z2; + dataptr[DCTSIZE*1] = z11 + z4; + dataptr[DCTSIZE*7] = z11 - z4; + + dataptr++; /* advance pointer to next column */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/code/jpeg-6/jidctflt.cpp b/code/jpeg-6/jidctflt.cpp new file mode 100644 index 0000000..f2a04ce --- /dev/null +++ b/code/jpeg-6/jidctflt.cpp @@ -0,0 +1,246 @@ +/* + * jidctflt.c + * + * Copyright (C) 1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains a floating-point implementation of the + * inverse DCT (Discrete Cosine Transform). In the IJG code, this routine + * must also perform dequantization of the input coefficients. + * + * This implementation should be more accurate than either of the integer + * IDCT implementations. However, it may not give the same results on all + * machines because of differences in roundoff behavior. Speed will depend + * on the hardware's floating point capacity. + * + * A 2-D IDCT can be done by 1-D IDCT on each column followed by 1-D IDCT + * on each row (or vice versa, but it's more convenient to emit a row at + * a time). Direct algorithms are also available, but they are much more + * complex and seem not to be any faster when reduced to code. + * + * This implementation is based on Arai, Agui, and Nakajima's algorithm for + * scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in + * Japanese, but the algorithm is described in the Pennebaker & Mitchell + * JPEG textbook (see REFERENCES section in file README). The following code + * is based directly on figure 4-8 in P&M. + * While an 8-point DCT cannot be done in less than 11 multiplies, it is + * possible to arrange the computation so that many of the multiplies are + * simple scalings of the final outputs. These multiplies can then be + * folded into the multiplications or divisions by the JPEG quantization + * table entries. The AA&N method leaves only 5 multiplies and 29 adds + * to be done in the DCT itself. + * The primary disadvantage of this method is that with a fixed-point + * implementation, accuracy is lost due to imprecise representation of the + * scaled quantization values. However, that problem does not arise if + * we use floating point arithmetic. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jdct.h" /* Private declarations for DCT subsystem */ + +#ifdef DCT_FLOAT_SUPPORTED + + +/* + * This module is specialized to the case DCTSIZE = 8. + */ + +#if DCTSIZE != 8 + Sorry, this code only copes with 8x8 DCTs. /* deliberate syntax err */ +#endif + + +/* Dequantize a coefficient by multiplying it by the multiplier-table + * entry; produce a float result. + */ + +#define DEQUANTIZE(coef,quantval) (((FAST_FLOAT) (coef)) * (quantval)) + + +/* + * Perform dequantization and inverse DCT on one block of coefficients. + */ + +GLOBAL void +jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) +{ + FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + FAST_FLOAT tmp10, tmp11, tmp12, tmp13; + FAST_FLOAT z5, z10, z11, z12, z13; + JCOEFPTR inptr; + FLOAT_MULT_TYPE * quantptr; + FAST_FLOAT * wsptr; + JSAMPROW outptr; + JSAMPLE *range_limit = IDCT_range_limit(cinfo); + int ctr; + FAST_FLOAT workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS + + /* Pass 1: process columns from input, store into work array. */ + + inptr = coef_block; + quantptr = (FLOAT_MULT_TYPE *) compptr->dct_table; + wsptr = workspace; + for (ctr = DCTSIZE; ctr > 0; ctr--) { + /* Due to quantization, we will usually find that many of the input + * coefficients are zero, especially the AC terms. We can exploit this + * by short-circuiting the IDCT calculation for any column in which all + * the AC terms are zero. In that case each output is equal to the + * DC coefficient (with scale factor as needed). + * With typical images and quantization tables, half or more of the + * column DCT calculations can be simplified this way. + */ + + if ((inptr[DCTSIZE*1] | inptr[DCTSIZE*2] | inptr[DCTSIZE*3] | + inptr[DCTSIZE*4] | inptr[DCTSIZE*5] | inptr[DCTSIZE*6] | + inptr[DCTSIZE*7]) == 0) { + /* AC terms all zero */ + FAST_FLOAT dcval = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + + wsptr[DCTSIZE*0] = dcval; + wsptr[DCTSIZE*1] = dcval; + wsptr[DCTSIZE*2] = dcval; + wsptr[DCTSIZE*3] = dcval; + wsptr[DCTSIZE*4] = dcval; + wsptr[DCTSIZE*5] = dcval; + wsptr[DCTSIZE*6] = dcval; + wsptr[DCTSIZE*7] = dcval; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + continue; + } + + /* Even part */ + + tmp0 = DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); + tmp1 = DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); + tmp2 = DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); + tmp3 = DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); + + tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp11 = tmp0 - tmp2; + + tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp12 = (tmp1 - tmp3) * ((FAST_FLOAT) 1.414213562) - tmp13; /* 2*c4 */ + + tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + tmp4 = DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); + tmp5 = DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); + tmp6 = DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); + tmp7 = DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); + + z13 = tmp6 + tmp5; /* phase 6 */ + z10 = tmp6 - tmp5; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; /* phase 5 */ + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); /* 2*c4 */ + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + wsptr[DCTSIZE*0] = tmp0 + tmp7; + wsptr[DCTSIZE*7] = tmp0 - tmp7; + wsptr[DCTSIZE*1] = tmp1 + tmp6; + wsptr[DCTSIZE*6] = tmp1 - tmp6; + wsptr[DCTSIZE*2] = tmp2 + tmp5; + wsptr[DCTSIZE*5] = tmp2 - tmp5; + wsptr[DCTSIZE*4] = tmp3 + tmp4; + wsptr[DCTSIZE*3] = tmp3 - tmp4; + + inptr++; /* advance pointers to next column */ + quantptr++; + wsptr++; + } + + /* Pass 2: process rows from work array, store into output array. */ + /* Note that we must descale the results by a factor of 8 == 2**3. */ + + wsptr = workspace; + for (ctr = 0; ctr < DCTSIZE; ctr++) { + outptr = output_buf[ctr] + output_col; + /* Rows of zeroes can be exploited in the same way as we did with columns. + * However, the column calculation has created many nonzero AC terms, so + * the simplification applies less often (typically 5% to 10% of the time). + * And testing floats for zero is relatively expensive, so we don't bother. + */ + + /* Even part */ + + tmp10 = wsptr[0] + wsptr[4]; + tmp11 = wsptr[0] - wsptr[4]; + + tmp13 = wsptr[2] + wsptr[6]; + tmp12 = (wsptr[2] - wsptr[6]) * ((FAST_FLOAT) 1.414213562) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + /* Odd part */ + + z13 = wsptr[5] + wsptr[3]; + z10 = wsptr[5] - wsptr[3]; + z11 = wsptr[1] + wsptr[7]; + z12 = wsptr[1] - wsptr[7]; + + tmp7 = z11 + z13; + tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); + + z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ + tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ + tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ + + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + + /* Final output stage: scale down by a factor of 8 and range-limit */ + + outptr[0] = range_limit[(int) DESCALE((INT32) (tmp0 + tmp7), 3) + & RANGE_MASK]; + outptr[7] = range_limit[(int) DESCALE((INT32) (tmp0 - tmp7), 3) + & RANGE_MASK]; + outptr[1] = range_limit[(int) DESCALE((INT32) (tmp1 + tmp6), 3) + & RANGE_MASK]; + outptr[6] = range_limit[(int) DESCALE((INT32) (tmp1 - tmp6), 3) + & RANGE_MASK]; + outptr[2] = range_limit[(int) DESCALE((INT32) (tmp2 + tmp5), 3) + & RANGE_MASK]; + outptr[5] = range_limit[(int) DESCALE((INT32) (tmp2 - tmp5), 3) + & RANGE_MASK]; + outptr[4] = range_limit[(int) DESCALE((INT32) (tmp3 + tmp4), 3) + & RANGE_MASK]; + outptr[3] = range_limit[(int) DESCALE((INT32) (tmp3 - tmp4), 3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ + } +} + +#endif /* DCT_FLOAT_SUPPORTED */ diff --git a/code/jpeg-6/jinclude.h b/code/jpeg-6/jinclude.h new file mode 100644 index 0000000..eadcd19 --- /dev/null +++ b/code/jpeg-6/jinclude.h @@ -0,0 +1,116 @@ +/* + * jinclude.h + * + * Copyright (C) 1991-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file exists to provide a single place to fix any problems with + * including the wrong system include files. (Common problems are taken + * care of by the standard jconfig symbols, but on really weird systems + * you may have to edit this file.) + * + * NOTE: this file is NOT intended to be included by applications using the + * JPEG library. Most applications need only include jpeglib.h. + */ + + +#ifdef _WIN32 + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4032) +#pragma warning(disable : 4051) +#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4115) +#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4136) +#pragma warning(disable : 4152) // nonstandard extension, function/data pointer conversion in expression +#pragma warning(disable : 4201) +#pragma warning(disable : 4214) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) // truncation from const double to float +#pragma warning(disable : 4310) // cast truncates constant value +#pragma warning(disable: 4505) // unreferenced local function has been removed +#pragma warning(disable : 4514) +#pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4711) // selected for automatic inline expansion +#pragma warning(disable : 4220) // varargs matches remaining parameters +#pragma warning(disable : 4761) // integral size mismatch +#endif + +/* Include auto-config file to find out which system include files we need. */ + +#include "../jpeg-6/jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ + +/* + * We need the NULL macro and size_t typedef. + * On an ANSI-conforming system it is sufficient to include . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * only the default error handler and data source/destination modules do. + * But we must pull it in because of the references to FILE in jpeglib.h. + * You can remove those references if you want to compile without . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * NOTE: we assume the size parameters to these functions are of type size_t. + * Change the casts in these macros if not! + */ + +#ifdef NEED_BSD_STRINGS + +#include +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) + +#else /* not BSD, assume ANSI/SysV string lib */ + +#include +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) + +#endif + +/* + * In ANSI C, and indeed any rational implementation, size_t is also the + * type returned by sizeof(). However, it seems there are some irrational + * implementations out there, in which sizeof() returns an int even though + * size_t is defined as long or unsigned long. To ensure consistent results + * we always use this SIZEOF() macro in place of using sizeof() directly. + */ + +#define SIZEOF(object) ((size_t) sizeof(object)) + +/* + * The modules that use fread() and fwrite() always invoke them through + * these macros. On some systems you may need to twiddle the argument casts. + * CAUTION: argument order is different from underlying functions! + */ + +#define JFREAD(file,buf,sizeofbuf) \ + ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) +#define JFWRITE(file,buf,sizeofbuf) \ + ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file))) diff --git a/code/jpeg-6/jmemmgr.cpp b/code/jpeg-6/jmemmgr.cpp new file mode 100644 index 0000000..01c7fca --- /dev/null +++ b/code/jpeg-6/jmemmgr.cpp @@ -0,0 +1,1120 @@ +/* + * jmemmgr.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains the JPEG system-independent memory management + * routines. This code is usable across a wide variety of machines; most + * of the system dependencies have been isolated in a separate file. + * The major functions provided here are: + * * pool-based allocation and freeing of memory; + * * policy decisions about how to divide available memory among the + * virtual arrays; + * * control logic for swapping virtual arrays between main memory and + * backing storage. + * The separate system-dependent file provides the actual backing-storage + * access code, and it contains the policy decision about how much total + * main memory to use. + * This file is system-dependent in the sense that some of its functions + * are unnecessary in some systems. For example, if there is enough virtual + * memory so that backing storage will never be used, much of the virtual + * array control logic could be removed. (Of course, if you have that much + * memory then you shouldn't care about a little bit of unused code...) + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#define JPEG_INTERNALS +#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#ifndef NO_GETENV +#ifndef HAVE_STDLIB_H /* should declare getenv() */ +extern char * getenv JPP((const char * name)); +#endif +#endif + + +/* + * Some important notes: + * The allocation routines provided here must never return NULL. + * They should exit to error_exit if unsuccessful. + * + * It's not a good idea to try to merge the sarray and barray routines, + * even though they are textually almost the same, because samples are + * usually stored as bytes while coefficients are shorts or ints. Thus, + * in machines where byte pointers have a different representation from + * word pointers, the resulting machine code could not be the same. + */ + + +/* + * Many machines require storage alignment: longs must start on 4-byte + * boundaries, doubles on 8-byte boundaries, etc. On such machines, malloc() + * always returns pointers that are multiples of the worst-case alignment + * requirement, and we had better do so too. + * There isn't any really portable way to determine the worst-case alignment + * requirement. This module assumes that the alignment requirement is + * multiples of sizeof(ALIGN_TYPE). + * By default, we define ALIGN_TYPE as double. This is necessary on some + * workstations (where doubles really do need 8-byte alignment) and will work + * fine on nearly everything. If your machine has lesser alignment needs, + * you can save a few bytes by making ALIGN_TYPE smaller. + * The only place I know of where this will NOT work is certain Macintosh + * 680x0 compilers that define double as a 10-byte IEEE extended float. + * Doing 10-byte alignment is counterproductive because longwords won't be + * aligned well. Put "#define ALIGN_TYPE long" in jconfig.h if you have + * such a compiler. + */ + +#ifndef ALIGN_TYPE /* so can override from jconfig.h */ +#define ALIGN_TYPE double +#endif + + +/* + * We allocate objects from "pools", where each pool is gotten with a single + * request to jpeg_get_small() or jpeg_get_large(). There is no per-object + * overhead within a pool, except for alignment padding. Each pool has a + * header with a link to the next pool of the same class. + * Small and large pool headers are identical except that the latter's + * link pointer must be FAR on 80x86 machines. + * Notice that the "real" header fields are union'ed with a dummy ALIGN_TYPE + * field. This forces the compiler to make SIZEOF(small_pool_hdr) a multiple + * of the alignment requirement of ALIGN_TYPE. + */ + +typedef union small_pool_struct * small_pool_ptr; + +typedef union small_pool_struct { + struct { + small_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} small_pool_hdr; + +typedef union large_pool_struct FAR * large_pool_ptr; + +typedef union large_pool_struct { + struct { + large_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ + } hdr; + ALIGN_TYPE dummy; /* included in union to ensure alignment */ +} large_pool_hdr; + + +/* + * Here is the full definition of a memory manager object. + */ + +typedef struct { + struct jpeg_memory_mgr pub; /* public fields */ + + /* Each pool identifier (lifetime class) names a linked list of pools. */ + small_pool_ptr small_list[JPOOL_NUMPOOLS]; + large_pool_ptr large_list[JPOOL_NUMPOOLS]; + + /* Since we only have one lifetime class of virtual arrays, only one + * linked list is necessary (for each datatype). Note that the virtual + * array control blocks being linked together are actually stored somewhere + * in the small-pool list. + */ + jvirt_sarray_ptr virt_sarray_list; + jvirt_barray_ptr virt_barray_list; + + /* This counts total space obtained from jpeg_get_small/large */ + long total_space_allocated; + + /* alloc_sarray and alloc_barray set this value for use by virtual + * array routines. + */ + JDIMENSION last_rowsperchunk; /* from most recent alloc_sarray/barray */ +} my_memory_mgr; + +typedef my_memory_mgr * my_mem_ptr; + + +/* + * The control blocks for virtual arrays. + * Note that these blocks are allocated in the "small" pool area. + * System-dependent info for the associated backing store (if any) is hidden + * inside the backing_store_info struct. + */ + +struct jvirt_sarray_control { + JSAMPARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_sarray_ptr next; /* link to next virtual sarray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + +struct jvirt_barray_control { + JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_barray_ptr next; /* link to next virtual barray control block */ + backing_store_info b_s_info; /* System-dependent control info */ +}; + + +#ifdef MEM_STATS /* optional extra stuff for statistics */ + +LOCAL void +print_mem_stats (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + + /* Since this is only a debugging stub, we can cheat a little by using + * fprintf directly rather than going through the trace message code. + * This is helpful because message parm array can't handle longs. + */ + fprintf(stderr, "Freeing pool %d, total space = %ld\n", + pool_id, mem->total_space_allocated); + + for (lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL; + lhdr_ptr = lhdr_ptr->hdr.next) { + fprintf(stderr, " Large chunk used %ld\n", + (long) lhdr_ptr->hdr.bytes_used); + } + + for (shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL; + shdr_ptr = shdr_ptr->hdr.next) { + fprintf(stderr, " Small chunk used %ld free %ld\n", + (long) shdr_ptr->hdr.bytes_used, + (long) shdr_ptr->hdr.bytes_left); + } +} + +#endif /* MEM_STATS */ + + +LOCAL void +out_of_memory (j_common_ptr cinfo, int which) +/* Report an out-of-memory error and stop execution */ +/* If we compiled MEM_STATS support, report alloc requests before dying */ +{ +#ifdef MEM_STATS + cinfo->err->trace_level = 2; /* force self_destruct to report stats */ +#endif + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, which); +} + + +/* + * Allocation of "small" objects. + * + * For these, we use pooled storage. When a new pool must be created, + * we try to get enough space for the current request plus a "slop" factor, + * where the slop will be the amount of leftover space in the new pool. + * The speed vs. space tradeoff is largely determined by the slop values. + * A different slop value is provided for each pool class (lifetime), + * and we also distinguish the first pool of a class from later ones. + * NOTE: the values given work fairly well on both 16- and 32-bit-int + * machines, but may be too small if longs are 64 bits or more. + */ + +static const size_t first_pool_slop[JPOOL_NUMPOOLS] = +{ + 1600, /* first PERMANENT pool */ + 16000 /* first IMAGE pool */ +}; + +static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = +{ + 0, /* additional PERMANENT pools */ + 5000 /* additional IMAGE pools */ +}; + +#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ + + +METHODDEF void * +alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "small" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr hdr_ptr, prev_hdr_ptr; + char * data_ptr; + size_t odd_bytes, min_request, slop; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(small_pool_hdr))) + out_of_memory(cinfo, 1); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* See if space is available in any existing pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + prev_hdr_ptr = NULL; + hdr_ptr = mem->small_list[pool_id]; + while (hdr_ptr != NULL) { + if (hdr_ptr->hdr.bytes_left >= sizeofobject) + break; /* found pool with enough space */ + prev_hdr_ptr = hdr_ptr; + hdr_ptr = hdr_ptr->hdr.next; + } + + /* Time to make a new pool? */ + if (hdr_ptr == NULL) { + /* min_request is what we need now, slop is what will be leftover */ + min_request = sizeofobject + SIZEOF(small_pool_hdr); + if (prev_hdr_ptr == NULL) /* first pool in class? */ + slop = first_pool_slop[pool_id]; + else + slop = extra_pool_slop[pool_id]; + /* Don't ask for more than MAX_ALLOC_CHUNK */ + if (slop > (size_t) (MAX_ALLOC_CHUNK-min_request)) + slop = (size_t) (MAX_ALLOC_CHUNK-min_request); + /* Try to get space, if fail reduce slop and try again */ + for (;;) { + hdr_ptr = (small_pool_ptr) jpeg_get_small(cinfo, min_request + slop); + if (hdr_ptr != NULL) + break; + slop /= 2; + if (slop < MIN_SLOP) /* give up when it gets real small */ + out_of_memory(cinfo, 2); /* jpeg_get_small failed */ + } + mem->total_space_allocated += min_request + slop; + /* Success, initialize the new pool header and add to end of list */ + hdr_ptr->hdr.next = NULL; + hdr_ptr->hdr.bytes_used = 0; + hdr_ptr->hdr.bytes_left = sizeofobject + slop; + if (prev_hdr_ptr == NULL) /* first pool in class? */ + mem->small_list[pool_id] = hdr_ptr; + else + prev_hdr_ptr->hdr.next = hdr_ptr; + } + + /* OK, allocate the object from the current pool */ + data_ptr = (char *) (hdr_ptr + 1); /* point to first data byte in pool */ + data_ptr += hdr_ptr->hdr.bytes_used; /* point to place for object */ + hdr_ptr->hdr.bytes_used += sizeofobject; + hdr_ptr->hdr.bytes_left -= sizeofobject; + + return (void *) data_ptr; +} + + +/* + * Allocation of "large" objects. + * + * The external semantics of these are the same as "small" objects, + * except that FAR pointers are used on 80x86. However the pool + * management heuristics are quite different. We assume that each + * request is large enough that it may as well be passed directly to + * jpeg_get_large; the pool management just links everything together + * so that we can free it all on demand. + * Note: the major use of "large" objects is in JSAMPARRAY and JBLOCKARRAY + * structures. The routines that create these structures (see below) + * deliberately bunch rows together to ensure a large request size. + */ + +METHODDEF void FAR * +alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject) +/* Allocate a "large" object */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + large_pool_ptr hdr_ptr; + size_t odd_bytes; + + /* Check for unsatisfiable request (do now to ensure no overflow below) */ + if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr))) + out_of_memory(cinfo, 3); /* request exceeds malloc's ability */ + + /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ + odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); + if (odd_bytes > 0) + sizeofobject += SIZEOF(ALIGN_TYPE) - odd_bytes; + + /* Always make a new pool */ + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + hdr_ptr = (large_pool_ptr) jpeg_get_large(cinfo, sizeofobject + + SIZEOF(large_pool_hdr)); + if (hdr_ptr == NULL) + out_of_memory(cinfo, 4); /* jpeg_get_large failed */ + mem->total_space_allocated += sizeofobject + SIZEOF(large_pool_hdr); + + /* Success, initialize the new pool header and add to list */ + hdr_ptr->hdr.next = mem->large_list[pool_id]; + /* We maintain space counts in each pool header for statistical purposes, + * even though they are not needed for allocation. + */ + hdr_ptr->hdr.bytes_used = sizeofobject; + hdr_ptr->hdr.bytes_left = 0; + mem->large_list[pool_id] = hdr_ptr; + + return (void FAR *) (hdr_ptr + 1); /* point to first data byte in pool */ +} + + +/* + * Creation of 2-D sample arrays. + * The pointers are in near heap, the samples themselves in FAR heap. + * + * To minimize allocation overhead and to allow I/O of large contiguous + * blocks, we allocate the sample rows in groups of as many rows as possible + * without exceeding MAX_ALLOC_CHUNK total bytes per allocation request. + * NB: the virtual array control routines, later in this file, know about + * this chunking of rows. The rowsperchunk value is left in the mem manager + * object so that it can be saved away if this sarray is the workspace for + * a virtual array. + */ + +METHODDEF JSAMPARRAY +alloc_sarray (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, JDIMENSION numrows) +/* Allocate a 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JSAMPARRAY result; + JSAMPROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) samplesperrow * SIZEOF(JSAMPLE)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JSAMPARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JSAMPROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JSAMPROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) samplesperrow + * SIZEOF(JSAMPLE))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += samplesperrow; + } + } + + return result; +} + + +/* + * Creation of 2-D coefficient-block arrays. + * This is essentially the same as the code for sample arrays, above. + */ + +METHODDEF JBLOCKARRAY +alloc_barray (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, JDIMENSION numrows) +/* Allocate a 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + JBLOCKARRAY result; + JBLOCKROW workspace; + JDIMENSION rowsperchunk, currow, i; + long ltemp; + + /* Calculate max # of rows allowed in one allocation chunk */ + ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / + ((long) blocksperrow * SIZEOF(JBLOCK)); + if (ltemp <= 0) + ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); + if (ltemp < (long) numrows) + rowsperchunk = (JDIMENSION) ltemp; + else + rowsperchunk = numrows; + mem->last_rowsperchunk = rowsperchunk; + + /* Get space for row pointers (small object) */ + result = (JBLOCKARRAY) alloc_small(cinfo, pool_id, + (size_t) (numrows * SIZEOF(JBLOCKROW))); + + /* Get the rows themselves (large objects) */ + currow = 0; + while (currow < numrows) { + rowsperchunk = MIN(rowsperchunk, numrows - currow); + workspace = (JBLOCKROW) alloc_large(cinfo, pool_id, + (size_t) ((size_t) rowsperchunk * (size_t) blocksperrow + * SIZEOF(JBLOCK))); + for (i = rowsperchunk; i > 0; i--) { + result[currow++] = workspace; + workspace += blocksperrow; + } + } + + return result; +} + + +/* + * About virtual array management: + * + * The above "normal" array routines are only used to allocate strip buffers + * (as wide as the image, but just a few rows high). Full-image-sized buffers + * are handled as "virtual" arrays. The array is still accessed a strip at a + * time, but the memory manager must save the whole array for repeated + * accesses. The intended implementation is that there is a strip buffer in + * memory (as high as is possible given the desired memory limit), plus a + * backing file that holds the rest of the array. + * + * The request_virt_array routines are told the total size of the image and + * the maximum number of rows that will be accessed at once. The in-memory + * buffer must be at least as large as the maxaccess value. + * + * The request routines create control blocks but not the in-memory buffers. + * That is postponed until realize_virt_arrays is called. At that time the + * total amount of space needed is known (approximately, anyway), so free + * memory can be divided up fairly. + * + * The access_virt_array routines are responsible for making a specific strip + * area accessible (after reading or writing the backing file, if necessary). + * Note that the access routines are told whether the caller intends to modify + * the accessed strip; during a read-only pass this saves having to rewrite + * data to disk. The access routines are also responsible for pre-zeroing + * any newly accessed rows, if pre-zeroing was requested. + * + * In current usage, the access requests are usually for nonoverlapping + * strips; that is, successive access start_row numbers differ by exactly + * num_rows = maxaccess. This means we can get good performance with simple + * buffer dump/reload logic, by making the in-memory buffer be a multiple + * of the access height; then there will never be accesses across bufferload + * boundaries. The code will still work with overlapping access requests, + * but it doesn't handle bufferload overlaps very efficiently. + */ + + +METHODDEF jvirt_sarray_ptr +request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION samplesperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D sample array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_sarray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_sarray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_sarray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->samplesperrow = samplesperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_sarray_list; /* add to list of virtual arrays */ + mem->virt_sarray_list = result; + + return result; +} + + +METHODDEF jvirt_barray_ptr +request_virt_barray (j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION blocksperrow, JDIMENSION numrows, + JDIMENSION maxaccess) +/* Request a virtual 2-D coefficient-block array */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + jvirt_barray_ptr result; + + /* Only IMAGE-lifetime virtual arrays are currently supported */ + if (pool_id != JPOOL_IMAGE) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + + /* get control block */ + result = (jvirt_barray_ptr) alloc_small(cinfo, pool_id, + SIZEOF(struct jvirt_barray_control)); + + result->mem_buffer = NULL; /* marks array not yet realized */ + result->rows_in_array = numrows; + result->blocksperrow = blocksperrow; + result->maxaccess = maxaccess; + result->pre_zero = pre_zero; + result->b_s_open = FALSE; /* no associated backing-store object */ + result->next = mem->virt_barray_list; /* add to list of virtual arrays */ + mem->virt_barray_list = result; + + return result; +} + + +METHODDEF void +realize_virt_arrays (j_common_ptr cinfo) +/* Allocate the in-memory buffers for any unrealized virtual arrays */ +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + long space_per_minheight, maximum_space, avail_mem; + long minheights, max_minheights; + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + /* Compute the minimum space needed (maxaccess rows in each buffer) + * and the maximum space needed (full image height in each buffer). + * These may be of use to the system-dependent jpeg_mem_available routine. + */ + space_per_minheight = 0; + maximum_space = 0; + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) sptr->maxaccess * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + maximum_space += (long) sptr->rows_in_array * + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + } + } + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + space_per_minheight += (long) bptr->maxaccess * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + maximum_space += (long) bptr->rows_in_array * + (long) bptr->blocksperrow * SIZEOF(JBLOCK); + } + } + + if (space_per_minheight <= 0) + return; /* no unrealized arrays, no work */ + + /* Determine amount of memory to actually use; this is system-dependent. */ + avail_mem = jpeg_mem_available(cinfo, space_per_minheight, maximum_space, + mem->total_space_allocated); + + /* If the maximum space needed is available, make all the buffers full + * height; otherwise parcel it out with the same number of minheights + * in each buffer. + */ + if (avail_mem >= maximum_space) + max_minheights = 1000000000L; + else { + max_minheights = avail_mem / space_per_minheight; + /* If there doesn't seem to be enough space, try to get the minimum + * anyway. This allows a "stub" implementation of jpeg_mem_available(). + */ + if (max_minheights <= 0) + max_minheights = 1; + } + + /* Allocate the in-memory buffers and initialize backing store as needed. */ + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) sptr->rows_in_array - 1L) / sptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + sptr->rows_in_mem = sptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + sptr->rows_in_mem = (JDIMENSION) (max_minheights * sptr->maxaccess); + jpeg_open_backing_store(cinfo, & sptr->b_s_info, + (long) sptr->rows_in_array * + (long) sptr->samplesperrow * + (long) SIZEOF(JSAMPLE)); + sptr->b_s_open = TRUE; + } + sptr->mem_buffer = alloc_sarray(cinfo, JPOOL_IMAGE, + sptr->samplesperrow, sptr->rows_in_mem); + sptr->rowsperchunk = mem->last_rowsperchunk; + sptr->cur_start_row = 0; + sptr->first_undef_row = 0; + sptr->dirty = FALSE; + } + } + + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->mem_buffer == NULL) { /* if not realized yet */ + minheights = ((long) bptr->rows_in_array - 1L) / bptr->maxaccess + 1L; + if (minheights <= max_minheights) { + /* This buffer fits in memory */ + bptr->rows_in_mem = bptr->rows_in_array; + } else { + /* It doesn't fit in memory, create backing store. */ + bptr->rows_in_mem = (JDIMENSION) (max_minheights * bptr->maxaccess); + jpeg_open_backing_store(cinfo, & bptr->b_s_info, + (long) bptr->rows_in_array * + (long) bptr->blocksperrow * + (long) SIZEOF(JBLOCK)); + bptr->b_s_open = TRUE; + } + bptr->mem_buffer = alloc_barray(cinfo, JPOOL_IMAGE, + bptr->blocksperrow, bptr->rows_in_mem); + bptr->rowsperchunk = mem->last_rowsperchunk; + bptr->cur_start_row = 0; + bptr->first_undef_row = 0; + bptr->dirty = FALSE; + } + } +} + + +LOCAL void +do_sarray_io (j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual sample array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->samplesperrow * SIZEOF(JSAMPLE); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +LOCAL void +do_barray_io (j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing) +/* Do backing store read or write of a virtual coefficient-block array */ +{ + long bytesperrow, file_offset, byte_count, rows, thisrow, i; + + bytesperrow = (long) ptr->blocksperrow * SIZEOF(JBLOCK); + file_offset = ptr->cur_start_row * bytesperrow; + /* Loop to read or write each allocation chunk in mem_buffer */ + for (i = 0; i < (long) ptr->rows_in_mem; i += ptr->rowsperchunk) { + /* One chunk, but check for short chunk at end of buffer */ + rows = MIN((long) ptr->rowsperchunk, (long) ptr->rows_in_mem - i); + /* Transfer no more than is currently defined */ + thisrow = (long) ptr->cur_start_row + i; + rows = MIN(rows, (long) ptr->first_undef_row - thisrow); + /* Transfer no more than fits in file */ + rows = MIN(rows, (long) ptr->rows_in_array - thisrow); + if (rows <= 0) /* this chunk might be past end of file! */ + break; + byte_count = rows * bytesperrow; + if (writing) + (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + else + (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); + file_offset += byte_count; + } +} + + +METHODDEF JSAMPARRAY +access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual sample array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_sarray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_sarray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->samplesperrow * SIZEOF(JSAMPLE); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +METHODDEF JBLOCKARRAY +access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) +/* Access the part of a virtual block array starting at start_row */ +/* and extending for num_rows rows. writable is true if */ +/* caller intends to modify the accessed area. */ +{ + JDIMENSION end_row = start_row + num_rows; + JDIMENSION undef_row; + + /* debugging check */ + if (end_row > ptr->rows_in_array || num_rows > ptr->maxaccess || + ptr->mem_buffer == NULL) + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + + /* Make the desired part of the virtual array accessible */ + if (start_row < ptr->cur_start_row || + end_row > ptr->cur_start_row+ptr->rows_in_mem) { + if (! ptr->b_s_open) + ERREXIT(cinfo, JERR_VIRTUAL_BUG); + /* Flush old buffer contents if necessary */ + if (ptr->dirty) { + do_barray_io(cinfo, ptr, TRUE); + ptr->dirty = FALSE; + } + /* Decide what part of virtual array to access. + * Algorithm: if target address > current window, assume forward scan, + * load starting at target address. If target address < current window, + * assume backward scan, load so that target area is top of window. + * Note that when switching from forward write to forward read, will have + * start_row = 0, so the limiting case applies and we load from 0 anyway. + */ + if (start_row > ptr->cur_start_row) { + ptr->cur_start_row = start_row; + } else { + /* use long arithmetic here to avoid overflow & unsigned problems */ + long ltemp; + + ltemp = (long) end_row - (long) ptr->rows_in_mem; + if (ltemp < 0) + ltemp = 0; /* don't fall off front end of file */ + ptr->cur_start_row = (JDIMENSION) ltemp; + } + /* Read in the selected part of the array. + * During the initial write pass, we will do no actual read + * because the selected part is all undefined. + */ + do_barray_io(cinfo, ptr, FALSE); + } + /* Ensure the accessed part of the array is defined; prezero if needed. + * To improve locality of access, we only prezero the part of the array + * that the caller is about to access, not the entire in-memory array. + */ + if (ptr->first_undef_row < end_row) { + if (ptr->first_undef_row < start_row) { + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ + } else { + undef_row = ptr->first_undef_row; + } + if (writable) + ptr->first_undef_row = end_row; + if (ptr->pre_zero) { + size_t bytesperrow = (size_t) ptr->blocksperrow * SIZEOF(JBLOCK); + undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ + end_row -= ptr->cur_start_row; + while (undef_row < end_row) { + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; + } + } else { + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + } + } + /* Flag the buffer dirty if caller will write in it */ + if (writable) + ptr->dirty = TRUE; + /* Return address of proper part of the buffer */ + return ptr->mem_buffer + (start_row - ptr->cur_start_row); +} + + +/* + * Release all objects belonging to a specified pool. + */ + +METHODDEF void +free_pool (j_common_ptr cinfo, int pool_id) +{ + my_mem_ptr mem = (my_mem_ptr) cinfo->mem; + small_pool_ptr shdr_ptr; + large_pool_ptr lhdr_ptr; + size_t space_freed; + + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + +#ifdef MEM_STATS + if (cinfo->err->trace_level > 1) + print_mem_stats(cinfo, pool_id); /* print pool's memory usage statistics */ +#endif + + /* If freeing IMAGE pool, close any virtual arrays first */ + if (pool_id == JPOOL_IMAGE) { + jvirt_sarray_ptr sptr; + jvirt_barray_ptr bptr; + + for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { + if (sptr->b_s_open) { /* there may be no backing store */ + sptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*sptr->b_s_info.close_backing_store) (cinfo, & sptr->b_s_info); + } + } + mem->virt_sarray_list = NULL; + for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { + if (bptr->b_s_open) { /* there may be no backing store */ + bptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*bptr->b_s_info.close_backing_store) (cinfo, & bptr->b_s_info); + } + } + mem->virt_barray_list = NULL; + } + + /* Release large objects */ + lhdr_ptr = mem->large_list[pool_id]; + mem->large_list[pool_id] = NULL; + + while (lhdr_ptr != NULL) { + large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next; + space_freed = lhdr_ptr->hdr.bytes_used + + lhdr_ptr->hdr.bytes_left + + SIZEOF(large_pool_hdr); + jpeg_free_large(cinfo, (void FAR *) lhdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + lhdr_ptr = next_lhdr_ptr; + } + + /* Release small objects */ + shdr_ptr = mem->small_list[pool_id]; + mem->small_list[pool_id] = NULL; + + while (shdr_ptr != NULL) { + small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next; + space_freed = shdr_ptr->hdr.bytes_used + + shdr_ptr->hdr.bytes_left + + SIZEOF(small_pool_hdr); + jpeg_free_small(cinfo, (void *) shdr_ptr, space_freed); + mem->total_space_allocated -= space_freed; + shdr_ptr = next_shdr_ptr; + } +} + + +/* + * Close up shop entirely. + * Note that this cannot be called unless cinfo->mem is non-NULL. + */ + +METHODDEF void +self_destruct (j_common_ptr cinfo) +{ + int pool; + + /* Close all backing store, release all memory. + * Releasing pools in reverse order might help avoid fragmentation + * with some (brain-damaged) malloc libraries. + */ + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + free_pool(cinfo, pool); + } + + /* Release the memory manager control block too. */ + jpeg_free_small(cinfo, (void *) cinfo->mem, SIZEOF(my_memory_mgr)); + cinfo->mem = NULL; /* ensures I will be called only once */ + + jpeg_mem_term(cinfo); /* system-dependent cleanup */ +} + + +/* + * Memory manager initialization. + * When this is called, only the error manager pointer is valid in cinfo! + */ + +GLOBAL void +jinit_memory_mgr (j_common_ptr cinfo) +{ + my_mem_ptr mem; + long max_to_use; + int pool; + size_t test_mac; + + cinfo->mem = NULL; /* for safety if init fails */ + + /* Check for configuration errors. + * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably + * doesn't reflect any real hardware alignment requirement. + * The test is a little tricky: for X>0, X and X-1 have no one-bits + * in common if and only if X is a power of 2, ie has only one one-bit. + * Some compilers may give an "unreachable code" warning here; ignore it. + */ + if ((SIZEOF(ALIGN_TYPE) & (SIZEOF(ALIGN_TYPE)-1)) != 0) + ERREXIT(cinfo, JERR_BAD_ALIGN_TYPE); + /* MAX_ALLOC_CHUNK must be representable as type size_t, and must be + * a multiple of SIZEOF(ALIGN_TYPE). + * Again, an "unreachable code" warning may be ignored here. + * But a "constant too large" warning means you need to fix MAX_ALLOC_CHUNK. + */ + test_mac = (size_t) MAX_ALLOC_CHUNK; + if ((long) test_mac != MAX_ALLOC_CHUNK || + (MAX_ALLOC_CHUNK % SIZEOF(ALIGN_TYPE)) != 0) + ERREXIT(cinfo, JERR_BAD_ALLOC_CHUNK); + + max_to_use = jpeg_mem_init(cinfo); /* system-dependent initialization */ + + /* Attempt to allocate memory manager's control block */ + mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr)); + + if (mem == NULL) { + jpeg_mem_term(cinfo); /* system-dependent cleanup */ + ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0); + } + + /* OK, fill in the method pointers */ + mem->pub.alloc_small = alloc_small; + mem->pub.alloc_large = alloc_large; + mem->pub.alloc_sarray = alloc_sarray; + mem->pub.alloc_barray = alloc_barray; + mem->pub.request_virt_sarray = request_virt_sarray; + mem->pub.request_virt_barray = request_virt_barray; + mem->pub.realize_virt_arrays = realize_virt_arrays; + mem->pub.access_virt_sarray = access_virt_sarray; + mem->pub.access_virt_barray = access_virt_barray; + mem->pub.free_pool = free_pool; + mem->pub.self_destruct = self_destruct; + + /* Initialize working state */ + mem->pub.max_memory_to_use = max_to_use; + + for (pool = JPOOL_NUMPOOLS-1; pool >= JPOOL_PERMANENT; pool--) { + mem->small_list[pool] = NULL; + mem->large_list[pool] = NULL; + } + mem->virt_sarray_list = NULL; + mem->virt_barray_list = NULL; + + mem->total_space_allocated = SIZEOF(my_memory_mgr); + + /* Declare ourselves open for business */ + cinfo->mem = & mem->pub; + + /* Check for an environment variable JPEGMEM; if found, override the + * default max_memory setting from jpeg_mem_init. Note that the + * surrounding application may again override this value. + * If your system doesn't support getenv(), define NO_GETENV to disable + * this feature. + */ +#ifndef NO_GETENV + { char * memenv; + + if ((memenv = getenv("JPEGMEM")) != NULL) { + char ch = 'x'; + + if (sscanf(memenv, "%ld%c", &max_to_use, &ch) > 0) { + if (ch == 'm' || ch == 'M') + max_to_use *= 1000L; + mem->pub.max_memory_to_use = max_to_use * 1000L; + } + } + } +#endif + +} diff --git a/code/jpeg-6/jmemnobs.cpp b/code/jpeg-6/jmemnobs.cpp new file mode 100644 index 0000000..a65575d --- /dev/null +++ b/code/jpeg-6/jmemnobs.cpp @@ -0,0 +1,111 @@ +/* + * jmemnobs.c + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides a really simple implementation of the system- + * dependent portion of the JPEG memory manager. This implementation + * assumes that no backing-store files are needed: all required space + * can be obtained from ri.Malloc(). + * This is very portable in the sense that it'll compile on almost anything, + * but you'd better have lots of main memory (or virtual memory) if you want + * to process big images. + * Note that the max_memory_to_use option is ignored by this implementation. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" +#include "jmemsys.h" /* import the system-dependent declarations */ + +#include "../renderer/tr_local.h" + +/* + * Memory allocation and ri.Freeing are controlled by the regular library + * routines ri.Malloc() and ri.Free(). + */ + +GLOBAL void * +jpeg_get_small (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void *) Z_Malloc(sizeofobject, TAG_TEMP_JPG, qfalse); +} + +GLOBAL void +jpeg_free_small (j_common_ptr cinfo, void * object, size_t sizeofobject) +{ + Z_Free(object); +} + + +/* + * "Large" objects are treated the same as "small" ones. + * NB: although we include FAR keywords in the routine declarations, + * this file won't actually work in 80x86 small/medium model; at least, + * you probably won't be able to process useful-size images in only 64KB. + */ + +GLOBAL void FAR * +jpeg_get_large (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void FAR *) Z_Malloc(sizeofobject, TAG_TEMP_JPG, qfalse); +} + +GLOBAL void +jpeg_free_large (j_common_ptr cinfo, void FAR * object, size_t sizeofobject) +{ + Z_Free(object); +} + + +/* + * This routine computes the total memory space available for allocation. + * Here we always say, "we got all you want bud!" + */ + +GLOBAL long +jpeg_mem_available (j_common_ptr cinfo, long min_bytes_needed, + long max_bytes_needed, long already_allocated) +{ + return max_bytes_needed; +} + + +/* + * Backing store (temporary file) management. + * Since jpeg_mem_available always promised the moon, + * this should never be called and we can just error out. + */ + +GLOBAL void +jpeg_open_backing_store (j_common_ptr cinfo, backing_store_ptr info, + long total_bytes_needed) +{ + ERREXIT(cinfo, JERR_NO_BACKING_STORE); +} + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. Here, there isn't any. + */ + +GLOBAL long +jpeg_mem_init (j_common_ptr cinfo) +{ + return 0; /* just set max_memory_to_use to 0 */ +} + +GLOBAL void +jpeg_mem_term (j_common_ptr cinfo) +{ + /* no work */ +} diff --git a/code/jpeg-6/jmemsys.h b/code/jpeg-6/jmemsys.h new file mode 100644 index 0000000..033d29a --- /dev/null +++ b/code/jpeg-6/jmemsys.h @@ -0,0 +1,182 @@ +/* + * jmemsys.h + * + * Copyright (C) 1992-1994, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This include file defines the interface between the system-independent + * and system-dependent portions of the JPEG memory manager. No other + * modules need include it. (The system-independent portion is jmemmgr.c; + * there are several different versions of the system-dependent portion.) + * + * This file works as-is for the system-dependent memory managers supplied + * in the IJG distribution. You may need to modify it if you write a + * custom memory manager. If system-dependent changes are needed in + * this file, the best method is to #ifdef them based on a configuration + * symbol supplied in jconfig.h, as we have done with USE_MSDOS_MEMMGR. + */ + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_get_small jGetSmall +#define jpeg_free_small jFreeSmall +#define jpeg_get_large jGetLarge +#define jpeg_free_large jFreeLarge +#define jpeg_mem_available jMemAvail +#define jpeg_open_backing_store jOpenBackStore +#define jpeg_mem_init jMemInit +#define jpeg_mem_term jMemTerm +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* + * These two functions are used to allocate and release small chunks of + * memory. (Typically the total amount requested through jpeg_get_small is + * no more than 20K or so; this will be requested in chunks of a few K each.) + * Behavior should be the same as for the standard library functions malloc + * and free; in particular, jpeg_get_small must return NULL on failure. + * On most systems, these ARE malloc and free. jpeg_free_small is passed the + * size of the object being freed, just in case it's needed. + * On an 80x86 machine using small-data memory model, these manage near heap. + */ + +EXTERN void * jpeg_get_small JPP((j_common_ptr cinfo, size_t sizeofobject)); +EXTERN void jpeg_free_small JPP((j_common_ptr cinfo, void * object, + size_t sizeofobject)); + +/* + * These two functions are used to allocate and release large chunks of + * memory (up to the total free space designated by jpeg_mem_available). + * The interface is the same as above, except that on an 80x86 machine, + * far pointers are used. On most other machines these are identical to + * the jpeg_get/free_small routines; but we keep them separate anyway, + * in case a different allocation strategy is desirable for large chunks. + */ + +EXTERN void FAR * jpeg_get_large JPP((j_common_ptr cinfo,size_t sizeofobject)); +EXTERN void jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, + size_t sizeofobject)); + +/* + * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may + * be requested in a single call to jpeg_get_large (and jpeg_get_small for that + * matter, but that case should never come into play). This macro is needed + * to model the 64Kb-segment-size limit of far addressing on 80x86 machines. + * On those machines, we expect that jconfig.h will provide a proper value. + * On machines with 32-bit flat address spaces, any large constant may be used. + * + * NB: jmemmgr.c expects that MAX_ALLOC_CHUNK will be representable as type + * size_t and will be a multiple of sizeof(align_type). + */ + +#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ +#define MAX_ALLOC_CHUNK 1000000000L +#endif + +/* + * This routine computes the total space still available for allocation by + * jpeg_get_large. If more space than this is needed, backing store will be + * used. NOTE: any memory already allocated must not be counted. + * + * There is a minimum space requirement, corresponding to the minimum + * feasible buffer sizes; jmemmgr.c will request that much space even if + * jpeg_mem_available returns zero. The maximum space needed, enough to hold + * all working storage in memory, is also passed in case it is useful. + * Finally, the total space already allocated is passed. If no better + * method is available, cinfo->mem->max_memory_to_use - already_allocated + * is often a suitable calculation. + * + * It is OK for jpeg_mem_available to underestimate the space available + * (that'll just lead to more backing-store access than is really necessary). + * However, an overestimate will lead to failure. Hence it's wise to subtract + * a slop factor from the true available space. 5% should be enough. + * + * On machines with lots of virtual memory, any large constant may be returned. + * Conversely, zero may be returned to always use the minimum amount of memory. + */ + +EXTERN long jpeg_mem_available JPP((j_common_ptr cinfo, + long min_bytes_needed, + long max_bytes_needed, + long already_allocated)); + + +/* + * This structure holds whatever state is needed to access a single + * backing-store object. The read/write/close method pointers are called + * by jmemmgr.c to manipulate the backing-store object; all other fields + * are private to the system-dependent backing store routines. + */ + +#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ + +#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ + +typedef unsigned short XMSH; /* type of extended-memory handles */ +typedef unsigned short EMSH; /* type of expanded-memory handles */ + +typedef union { + short file_handle; /* DOS file handle if it's a temp file */ + XMSH xms_handle; /* handle if it's a chunk of XMS */ + EMSH ems_handle; /* handle if it's a chunk of EMS */ +} handle_union; + +#endif /* USE_MSDOS_MEMMGR */ + +typedef struct backing_store_struct * backing_store_ptr; + +typedef struct backing_store_struct { + /* Methods for reading/writing/closing this backing-store object */ + JMETHOD(void, read_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, write_backing_store, (j_common_ptr cinfo, + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); + JMETHOD(void, close_backing_store, (j_common_ptr cinfo, + backing_store_ptr info)); + + /* Private fields for system-dependent backing-store management */ +#ifdef USE_MSDOS_MEMMGR + /* For the MS-DOS manager (jmemdos.c), we need: */ + handle_union handle; /* reference to backing-store storage object */ + char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ +#else + /* For a typical implementation with temp files, we need: */ + FILE * temp_file; /* stdio reference to temp file */ + char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */ +#endif +} backing_store_info; + +/* + * Initial opening of a backing-store object. This must fill in the + * read/write/close pointers in the object. The read/write routines + * may take an error exit if the specified maximum file size is exceeded. + * (If jpeg_mem_available always returns a large value, this routine can + * just take an error exit.) + */ + +EXTERN void jpeg_open_backing_store JPP((j_common_ptr cinfo, + backing_store_ptr info, + long total_bytes_needed)); + + +/* + * These routines take care of any system-dependent initialization and + * cleanup required. jpeg_mem_init will be called before anything is + * allocated (and, therefore, nothing in cinfo is of use except the error + * manager pointer). It should return a suitable default value for + * max_memory_to_use; this may subsequently be overridden by the surrounding + * application. (Note that max_memory_to_use is only important if + * jpeg_mem_available chooses to consult it ... no one else will.) + * jpeg_mem_term may assume that all requested memory has been freed and that + * all opened backing-store objects have been closed. + */ + +EXTERN long jpeg_mem_init JPP((j_common_ptr cinfo)); +EXTERN void jpeg_mem_term JPP((j_common_ptr cinfo)); diff --git a/code/jpeg-6/jmorecfg.h b/code/jpeg-6/jmorecfg.h new file mode 100644 index 0000000..d293284 --- /dev/null +++ b/code/jpeg-6/jmorecfg.h @@ -0,0 +1,349 @@ +/* + * jmorecfg.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains additional configuration options that customize the + * JPEG software for special applications or support machine-dependent + * optimizations. Most users will not need to touch this file. + */ + + +/* + * Define BITS_IN_JSAMPLE as either + * 8 for 8-bit sample values (the usual setting) + * 12 for 12-bit sample values + * Only 8 and 12 are legal data precisions for lossy JPEG according to the + * JPEG standard, and the IJG code does not support anything else! + * We do not support run-time selection of data precision, sorry. + */ + +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ + + +/* + * Maximum number of components (color channels) allowed in JPEG image. + * To meet the letter of the JPEG spec, set this to 255. However, darn + * few applications need more than 4 channels (maybe 5 for CMYK + alpha + * mask). We recommend 10 as a reasonable compromise; use 4 if you are + * really short on memory. (Each allowed component costs a hundred or so + * bytes of storage, whether actually used in an image or not.) + */ + +#define MAX_COMPONENTS 10 /* maximum number of image components */ + + +/* + * Basic data types. + * You may need to change these if you have a machine with unusual data + * type sizes; for example, "char" not 8 bits, "short" not 16 bits, + * or "long" not 32 bits. We don't care whether "int" is 16 or 32 bits, + * but it had better be at least 16. + */ + +/* Representation of a single sample (pixel element value). + * We frequently allocate large arrays of these, so it's important to keep + * them small. But if you have memory to burn and access to char or short + * arrays is very slow on your hardware, you might want to change these. + */ + +#if BITS_IN_JSAMPLE == 8 +/* JSAMPLE should be the smallest type that will hold the values 0..255. + * You can use a signed char by having GETJSAMPLE mask it with 0xFF. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JSAMPLE; +#ifdef CHAR_IS_UNSIGNED +#define GETJSAMPLE(value) ((int) (value)) +#else +#define GETJSAMPLE(value) ((int) (value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 + +#endif /* BITS_IN_JSAMPLE == 8 */ + + +#if BITS_IN_JSAMPLE == 12 +/* JSAMPLE should be the smallest type that will hold the values 0..4095. + * On nearly all machines "short" will do nicely. + */ + +typedef short JSAMPLE; +#define GETJSAMPLE(value) ((int) (value)) + +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 + +#endif /* BITS_IN_JSAMPLE == 12 */ + + +/* Representation of a DCT frequency coefficient. + * This should be a signed value of at least 16 bits; "short" is usually OK. + * Again, we allocate large arrays of these, but you can change to int + * if you have memory to burn and "short" is really slow. + */ + +typedef short JCOEF; + + +/* Compressed datastreams are represented as arrays of JOCTET. + * These must be EXACTLY 8 bits wide, at least once they are written to + * external storage. Note that when using the stdio data source/destination + * managers, this is also the data type passed to fread/fwrite. + */ + +#ifdef HAVE_UNSIGNED_CHAR + +typedef unsigned char JOCTET; +#define GETJOCTET(value) (value) + +#else /* not HAVE_UNSIGNED_CHAR */ + +typedef char JOCTET; +#ifdef CHAR_IS_UNSIGNED +#define GETJOCTET(value) (value) +#else +#define GETJOCTET(value) ((value) & 0xFF) +#endif /* CHAR_IS_UNSIGNED */ + +#endif /* HAVE_UNSIGNED_CHAR */ + + +/* These typedefs are used for various table entries and so forth. + * They must be at least as wide as specified; but making them too big + * won't cost a huge amount of memory, so we don't provide special + * extraction code like we did for JSAMPLE. (In other words, these + * typedefs live at a different point on the speed/space tradeoff curve.) + */ + +/* UINT8 must hold at least the values 0..255. */ + +#ifdef HAVE_UNSIGNED_CHAR +typedef unsigned char UINT8; +#else /* not HAVE_UNSIGNED_CHAR */ +#ifdef CHAR_IS_UNSIGNED +typedef char UINT8; +#else /* not CHAR_IS_UNSIGNED */ +typedef short UINT8; +#endif /* CHAR_IS_UNSIGNED */ +#endif /* HAVE_UNSIGNED_CHAR */ + +/* UINT16 must hold at least the values 0..65535. */ + +#ifdef HAVE_UNSIGNED_SHORT +typedef unsigned short UINT16; +#else /* not HAVE_UNSIGNED_SHORT */ +typedef unsigned int UINT16; +#endif /* HAVE_UNSIGNED_SHORT */ + +// compile warning for VC6 with CPP being defined +// typedef long INT32; + +/* INT16 must hold at least the values -32768..32767. */ + +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +typedef short INT16; +#endif + +/* INT32 must hold at least signed 32-bit values. */ + +//#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +//typedef long INT32; +//#endif + +/* Datatype used for image dimensions. The JPEG standard only supports + * images up to 64K*64K due to 16-bit fields in SOF markers. Therefore + * "unsigned int" is sufficient on all machines. However, if you need to + * handle larger images and you don't mind deviating from the spec, you + * can change this datatype. + */ + +typedef unsigned int JDIMENSION; + +#define JPEG_MAX_DIMENSION 65500L /* a tad under 64K to prevent overflows */ + + +/* These defines are used in all function definitions and extern declarations. + * You could modify them if you need to change function linkage conventions. + * Another application is to make all functions global for use with debuggers + * or code profilers that require it. + */ + +#define METHODDEF static /* a function called through method pointers */ +#define LOCAL static /* a function used only in its module */ +#define GLOBAL /* a function referenced thru EXTERNs */ +#define EXTERN extern /* a reference to a GLOBAL function */ + + +/* Here is the pseudo-keyword for declaring pointers that must be "far" + * on 80x86 machines. Most of the specialized coding for 80x86 is handled + * by just saying "FAR *" where such a pointer is needed. In a few places + * explicit coding is needed; see uses of the NEED_FAR_POINTERS symbol. + */ + +#ifdef NEED_FAR_POINTERS +#undef FAR +#define FAR far +#else +#undef FAR +#define FAR +#endif + + +/* + * On a few systems, type boolean and/or its values FALSE, TRUE may appear + * in standard header files. Or you may have conflicts with application- + * specific header files that you want to include together with these files. + * Defining HAVE_BOOLEAN before including jpeglib.h should make it work. + */ + +//#ifndef HAVE_BOOLEAN +//typedef int boolean; +//#endif +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ +#endif +#ifndef TRUE +#define TRUE 1 +#endif + + +/* + * The remaining options affect code selection within the JPEG library, + * but they don't need to be visible to most applications using the library. + * To minimize application namespace pollution, the symbols won't be + * defined unless JPEG_INTERNALS or JPEG_INTERNAL_OPTIONS has been defined. + */ + +#ifdef JPEG_INTERNALS +#define JPEG_INTERNAL_OPTIONS +#endif + +#ifdef JPEG_INTERNAL_OPTIONS + + +/* + * These defines indicate whether to include various optional functions. + * Undefining some of these symbols will produce a smaller but less capable + * library. Note that you can leave certain source files out of the + * compilation/linking process if you've #undef'd the corresponding symbols. + * (You may HAVE to do that if your compiler doesn't like null source files.) + */ + +/* Arithmetic coding is unsupported for legal reasons. Complaints to IBM. */ + +/* Capability options common to encoder and decoder: */ + +#undef DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ +#undef DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ + +/* Encoder capability options: */ + +#undef C_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#define C_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#define C_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#define ENTROPY_OPT_SUPPORTED /* Optimization of entropy coding parms? */ +/* Note: if you selected 12-bit data precision, it is dangerous to turn off + * ENTROPY_OPT_SUPPORTED. The standard Huffman tables are only good for 8-bit + * precision, so jchuff.c normally uses entropy optimization to compute + * usable tables for higher precision. If you don't want to do optimization, + * you'll have to supply different default Huffman tables. + * The exact same statements apply for progressive JPEG: the default tables + * don't work for progressive mode. (This may get fixed, however.) + */ +#define INPUT_SMOOTHING_SUPPORTED /* Input image smoothing option? */ + +/* Decoder capability options: */ + +#undef D_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ +#undef D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ +#undef D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +#undef BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ +#undef IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ +#undef UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ +#undef QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +#undef QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ + +/* more capability options later, no doubt */ + + +/* + * Ordering of RGB data in scanlines passed to or from the application. + * If your application wants to deal with data in the order B,G,R, just + * change these macros. You can also deal with formats such as R,G,B,X + * (one extra byte per pixel) by changing RGB_PIXELSIZE. Note that changing + * the offsets will also change the order in which colormap data is organized. + * RESTRICTIONS: + * 1. The sample applications cjpeg,djpeg do NOT support modified RGB formats. + * 2. These macros only affect RGB<=>YCbCr color conversion, so they are not + * useful if you are using JPEG color spaces other than YCbCr or grayscale. + * 3. The color quantizer modules will not behave desirably if RGB_PIXELSIZE + * is not 3 (they don't understand about dummy color components!). So you + * can't use color quantization if you change that value. + */ + +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +#define RGB_PIXELSIZE 4 /* JSAMPLEs per RGB scanline element */ + + +/* Definitions for speed-related optimizations. */ + + +/* If your compiler supports inline functions, define INLINE + * as the inline keyword; otherwise define it as empty. + */ + +#ifndef INLINE +#ifdef __GNUC__ /* for instance, GNU C knows about inline */ +#define INLINE __inline__ +#endif +#ifndef INLINE +#define INLINE /* default is to define it as empty */ +#endif +#endif + + +/* On some machines (notably 68000 series) "int" is 32 bits, but multiplying + * two 16-bit shorts is faster than multiplying two ints. Define MULTIPLIER + * as short on such a machine. MULTIPLIER must be at least 16 bits wide. + */ + +#ifndef MULTIPLIER +#define MULTIPLIER int /* type for fastest integer multiply */ +#endif + + +/* FAST_FLOAT should be either float or double, whichever is done faster + * by your compiler. (Note that this type is only used in the floating point + * DCT routines, so it only matters if you've defined DCT_FLOAT_SUPPORTED.) + * Typically, float is faster in ANSI C compilers, while double is faster in + * pre-ANSI compilers (because they insist on converting to double anyway). + * The code below therefore chooses float if we have ANSI-style prototypes. + */ + +#ifndef FAST_FLOAT +#ifdef HAVE_PROTOTYPES +#define FAST_FLOAT float +#else +#define FAST_FLOAT double +#endif +#endif + +#endif /* JPEG_INTERNAL_OPTIONS */ diff --git a/code/jpeg-6/jpegint.h b/code/jpeg-6/jpegint.h new file mode 100644 index 0000000..ab5bee2 --- /dev/null +++ b/code/jpeg-6/jpegint.h @@ -0,0 +1,388 @@ +/* + * jpegint.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file provides common declarations for the various JPEG modules. + * These declarations are considered internal to the JPEG library; most + * applications using the library shouldn't need to include this file. + */ + + +/* Declarations for both compression & decompression */ + +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +} J_BUF_MODE; + +/* Values of global_state field (jdapi.c has some dependencies on ordering!) */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ + + +/* Declarations for compression modules */ + +/* Master control module */ +struct jpeg_comp_master { + JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo)); + JMETHOD(void, pass_startup, (j_compress_ptr cinfo)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_c_main_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); +}; + +/* Compression preprocessing (downsampling input buffer control) */ +struct jpeg_c_prep_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_c_coef_controller { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf)); +}; + +/* Colorspace conversion */ +struct jpeg_color_converter { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, color_convert, (j_compress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); +}; + +/* Downsampling */ +struct jpeg_downsampler { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + JMETHOD(void, downsample, (j_compress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Forward DCT (also controls coefficient quantization) */ +struct jpeg_forward_dct { + JMETHOD(void, start_pass, (j_compress_ptr cinfo)); + /* perhaps this should be an array??? */ + JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); +}; + +/* Entropy encoding */ +struct jpeg_entropy_encoder { + JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics)); + JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data)); + JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); +}; + +/* Marker writing */ +struct jpeg_marker_writer { + /* write_any_marker is exported for use by applications */ + /* Probably only COM and APPn markers should be written */ + JMETHOD(void, write_any_marker, (j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen)); + JMETHOD(void, write_file_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_frame_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_scan_header, (j_compress_ptr cinfo)); + JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo)); + JMETHOD(void, write_tables_only, (j_compress_ptr cinfo)); +}; + + +/* Declarations for decompression modules */ + +/* Master control module */ +struct jpeg_decomp_master { + JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ +}; + +/* Input control module */ +struct jpeg_input_controller { + JMETHOD(int, consume_input, (j_decompress_ptr cinfo)); + JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo)); + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); + + /* State variables made visible to other modules */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ +}; + +/* Main buffer control (downsampled-data buffer) */ +struct jpeg_d_main_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, process_data, (j_decompress_ptr cinfo, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Coefficient buffer control */ +struct jpeg_d_coef_controller { + JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); + JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); + JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, + JSAMPIMAGE output_buf)); + /* Pointer to array of coefficient virtual arrays, or NULL if none */ + jvirt_barray_ptr *coef_arrays; +}; + +/* Decompression postprocessing (color quantization buffer control) */ +struct jpeg_d_post_controller { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); + JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); +}; + +/* Marker reading & parsing */ +struct jpeg_marker_reader { + JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo)); + /* Read markers until SOS or EOI. + * Returns same codes as are defined for jpeg_consume_input: + * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI. + */ + JMETHOD(int, read_markers, (j_decompress_ptr cinfo)); + /* Read a restart marker --- exported for use by entropy decoder only */ + jpeg_marker_parser_method read_restart_marker; + /* Application-overridable marker processing methods */ + jpeg_marker_parser_method process_COM; + jpeg_marker_parser_method process_APPn[16]; + + /* State of marker reader --- nominally internal, but applications + * supplying COM or APPn handlers might like to know the state. + */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ +}; + +/* Entropy decoding */ +struct jpeg_entropy_decoder { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, + JBLOCKROW *MCU_data)); +}; + +/* Inverse DCT (also performs dequantization) */ +typedef JMETHOD(void, inverse_DCT_method_ptr, + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); + +struct jpeg_inverse_dct { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + /* It is useful to allow each component to have a separate IDCT method. */ + inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS]; +}; + +/* Upsampling (note that upsampler must also call color converter) */ +struct jpeg_upsampler { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, upsample, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ +}; + +/* Colorspace conversion */ +struct jpeg_color_deconverter { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, color_convert, (j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); +}; + +/* Color quantization or color precision reduction */ +struct jpeg_color_quantizer { + JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); + JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); + JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); + JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); +}; + + +/* Miscellaneous useful macros */ + +#undef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#undef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + + +/* We assume that right shift corresponds to signed division by 2 with + * rounding towards minus infinity. This is correct for typical "arithmetic + * shift" instructions that shift in copies of the sign bit. But some + * C compilers implement >> with an unsigned shift. For these machines you + * must define RIGHT_SHIFT_IS_UNSIGNED. + * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity. + * It is only applied with constant shift counts. SHIFT_TEMPS must be + * included in the variables of any routine using RIGHT_SHIFT. + */ + +#ifdef RIGHT_SHIFT_IS_UNSIGNED +#define SHIFT_TEMPS INT32 shift_temp; +#define RIGHT_SHIFT(x,shft) \ + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) +#else +#define SHIFT_TEMPS +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Compression module initialization routines */ +EXTERN void jinit_compress_master JPP((j_compress_ptr cinfo)); +EXTERN void jinit_c_master_control JPP((j_compress_ptr cinfo, + boolean transcode_only)); +EXTERN void jinit_c_main_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_c_prep_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_c_coef_controller JPP((j_compress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_color_converter JPP((j_compress_ptr cinfo)); +EXTERN void jinit_downsampler JPP((j_compress_ptr cinfo)); +EXTERN void jinit_forward_dct JPP((j_compress_ptr cinfo)); +EXTERN void jinit_huff_encoder JPP((j_compress_ptr cinfo)); +EXTERN void jinit_phuff_encoder JPP((j_compress_ptr cinfo)); +EXTERN void jinit_marker_writer JPP((j_compress_ptr cinfo)); +/* Decompression module initialization routines */ +EXTERN void jinit_master_decompress JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_d_main_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_d_coef_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_d_post_controller JPP((j_decompress_ptr cinfo, + boolean need_full_buffer)); +EXTERN void jinit_input_controller JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_marker_reader JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_huff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_phuff_decoder JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_inverse_dct JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_upsampler JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_color_deconverter JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_1pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_2pass_quantizer JPP((j_decompress_ptr cinfo)); +EXTERN void jinit_merged_upsampler JPP((j_decompress_ptr cinfo)); +/* Memory manager initialization */ +EXTERN void jinit_memory_mgr JPP((j_common_ptr cinfo)); + +/* Utility routines in jutils.c */ +EXTERN long jdiv_round_up JPP((long a, long b)); +EXTERN long jround_up JPP((long a, long b)); +EXTERN void jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); +EXTERN void jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks)); +EXTERN void jzero_far JPP((void FAR * target, size_t bytestozero)); +/* Constant tables in jutils.c */ +extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ +extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ + +/* Suppress undefined-structure complaints if necessary. */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +#endif +#endif /* INCOMPLETE_TYPES_BROKEN */ diff --git a/code/jpeg-6/jpeglib.h b/code/jpeg-6/jpeglib.h new file mode 100644 index 0000000..1c82e36 --- /dev/null +++ b/code/jpeg-6/jpeglib.h @@ -0,0 +1,1065 @@ +/* + * jpeglib.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file defines the application interface for the JPEG library. + * Most applications using the library need only include this file, + * and perhaps jerror.h if they want to know the exact error codes. + */ + +#ifndef JPEGLIB_H +#define JPEGLIB_H + +#if !defined(TR_LOCAL_H) + #include "../renderer/tr_local.h" +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + +typedef unsigned char boolean; +/* + * First we include the configuration files that record how this + * installation of the JPEG library is set up. jconfig.h can be + * generated automatically for many systems. jmorecfg.h contains + * manual configuration options that most people need not worry about. + */ + +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +#include "../jpeg-6/jconfig.h" /* widely used configuration options */ +#endif +#include "../jpeg-6/jmorecfg.h" /* seldom changed options */ + + +/* Version ID for the JPEG library. + * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". + */ + +#define JPEG_LIB_VERSION 60 /* Version 6 */ + + +/* Various constants determining the sizes of things. + * All of these are specified by the JPEG standard, so don't change them + * if you want to be compatible. + */ + +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; + * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. + * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU + * to handle it. We even let you do this from the jconfig.h file. However, + * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe + * sometimes emits noncompliant files doesn't mean you should too. + */ +#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */ +#ifndef D_MAX_BLOCKS_IN_MCU +#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */ +#endif + + +/* This macro is used to declare a "method", that is, a function pointer. + * We want to supply prototype parameters if the compiler can cope. + * Note that the arglist parameter must be parenthesized! + */ + +#ifdef HAVE_PROTOTYPES +#define JMETHOD(type,methodname,arglist) type (*methodname) arglist +#else +#define JMETHOD(type,methodname,arglist) type (*methodname) () +#endif + + +/* Data structures for images (arrays of samples and of DCT coefficients). + * On 80x86 machines, the image arrays are too big for near pointers, + * but the pointer arrays can fit in near memory. + */ + +typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ + +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ + +typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ + + +/* Types for JPEG compression parameters and working tables. */ + + +/* DCT coefficient quantization tables. */ + +typedef struct { + /* This field directly represents the contents of a JPEG DQT marker. + * Note: the values are always given in zigzag order. + */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JQUANT_TBL; + + +/* Huffman coding tables. */ + +typedef struct { + /* These two fields directly represent the contents of a JPEG DHT marker */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ + /* This field is used only during compression. It's initialized FALSE when + * the table is created, and set TRUE when it's been output to the file. + * You could suppress output of a table by setting this to TRUE. + * (See jpeg_suppress_tables for an example.) + */ + boolean sent_table; /* TRUE when table has been output */ +} JHUFF_TBL; + + +/* Basic info about one component (color channel). */ + +typedef struct { + /* These values are fixed over the whole image. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOF marker. */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ + /* These values may vary between scans. */ + /* For compression, they must be supplied by parameter setup; */ + /* for decompression, they are read from the SOS marker. */ + /* The decompressor output side may not use these variables. */ + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + + /* Remaining fields should be treated as private by applications. */ + + /* These values are computed during compression or decompression startup: */ + /* Component's size in DCT blocks. + * Any dummy blocks added to complete an MCU are not counted; therefore + * these values do not depend on whether a scan is interleaved or not. + */ + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + /* Size of a DCT block in samples. Always DCTSIZE for compression. + * For decompression this is the size of the output from one DCT block, + * reflecting any scaling we choose to apply during the IDCT step. + * Values of 1,2,4,8 are likely to be supported. Note that different + * components may receive different IDCT scalings. + */ + int DCT_scaled_size; + /* The downsampled dimensions are the component's actual, unpadded number + * of samples at the main buffer (preprocessing/compression interface), thus + * downsampled_width = ceil(image_width * Hi/Hmax) + * and similarly for height. For decompression, IDCT scaling is included, so + * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + */ + JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_height; /* actual height in samples */ + /* This flag is used only for decompression. In cases where some of the + * components will be ignored (eg grayscale output from YCbCr image), + * we can skip most computations for the unused components. + */ + boolean component_needed; /* do we need the value of this component? */ + + /* These values are computed before starting a scan of the component. */ + /* The decompressor output side may not use these variables. */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ + + /* Saved quantization table for component; NULL if none yet saved. + * See jdinput.c comments about the need for this information. + * This field is not currently used by the compressor. + */ + JQUANT_TBL * quant_table; + + /* Private per-component storage for DCT or IDCT subsystem. */ + void * dct_table; +} jpeg_component_info; + + +/* The script for encoding a multiple-scan file is an array of these: */ + +typedef struct { + int comps_in_scan; /* number of components encoded in this scan */ + int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ +} jpeg_scan_info; + + +/* Known color spaces. */ + +typedef enum { + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK /* Y/Cb/Cr/K */ +} J_COLOR_SPACE; + +/* DCT/IDCT algorithm options. */ + +typedef enum { + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ +} J_DCT_METHOD; + +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#define JDCT_DEFAULT JDCT_ISLOW +#endif +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#define JDCT_FASTEST JDCT_IFAST +#endif + +/* Dithering options for decompression. */ + +typedef enum { + JDITHER_NONE, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ +} J_DITHER_MODE; + + +/* Common fields between JPEG compression and decompression master structs. */ + +#define jpeg_common_fields \ + struct jpeg_error_mgr * err; /* Error handler module */\ + struct jpeg_memory_mgr * mem; /* Memory manager module */\ + struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\ + boolean is_decompressor; /* so common code can tell which is which */\ + int global_state /* for checking call sequence validity */ + +/* Routines that are to be used by both halves of the library are declared + * to receive a pointer to this structure. There are no actual instances of + * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. + */ +struct jpeg_common_struct { + jpeg_common_fields; /* Fields common to both master struct types */ + /* Additional fields follow in an actual jpeg_compress_struct or + * jpeg_decompress_struct. All three structs must agree on these + * initial fields! (This would be a lot cleaner in C++.) + */ +}; + +typedef struct jpeg_common_struct * j_common_ptr; +typedef struct jpeg_compress_struct * j_compress_ptr; +typedef struct jpeg_decompress_struct * j_decompress_ptr; + + +/* Master record for a compression instance */ + +struct jpeg_compress_struct { + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + + /* Destination for compressed data */ + struct jpeg_destination_mgr * dest; + + /* Description of source image --- these fields must be filled in by + * outer application before starting compression. in_color_space must + * be correct before you can even call jpeg_set_defaults(). + */ + + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ + + double input_gamma; /* image gamma of input image */ + + /* Compression parameters --- these fields must be set before calling + * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to + * initialize everything to reasonable defaults, then changing anything + * the application specifically wants to change. That way you won't get + * burnt when new parameters are added. Also note that there are several + * helper routines to simplify changing parameters. + */ + + int data_precision; /* bits of precision in image data */ + + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + int num_scans; /* # of entries in scan_info array */ + const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ + /* The default value of scan_info is NULL, which causes a single-scan + * sequential JPEG file to be emitted. To create a multi-scan file, + * set num_scans and scan_info to point to an array of scan definitions. + */ + + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ + + /* The restart interval can be specified in absolute MCUs by setting + * restart_interval, or in MCU rows by setting restart_in_rows + * (in which case the correct restart_interval will be figured + * for each scan). + */ + unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ + + /* Parameters controlling emission of special markers. */ + + boolean write_JFIF_header; /* should a JFIF marker be written? */ + /* These three values are not used by the JPEG code, merely copied */ + /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ + /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ + /* ratio is defined by X_density/Y_density even when density_unit=0. */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + + /* State variable: index of next scanline to be written to + * jpeg_write_scanlines(). Application may use this to control its + * processing loop, e.g., "while (next_scanline < image_height)". + */ + + JDIMENSION next_scanline; /* 0 .. image_height-1 */ + + /* Remaining fields are known throughout compressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during compression startup + */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + /* The coefficient controller receives data in units of MCU rows as defined + * for fully interleaved scans (whether the JPEG file is interleaved or not). + * There are v_samp_factor * DCTSIZE sample rows of each component in an + * "iMCU" (interleaved MCU) row. + */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* + * Links to compression subobjects (methods and private variables of modules) + */ + struct jpeg_comp_master * master; + struct jpeg_c_main_controller * main; + struct jpeg_c_prep_controller * prep; + struct jpeg_c_coef_controller * coef; + struct jpeg_marker_writer * marker; + struct jpeg_color_converter * cconvert; + struct jpeg_downsampler * downsample; + struct jpeg_forward_dct * fdct; + struct jpeg_entropy_encoder * entropy; +}; + + +/* Master record for a decompression instance */ + +struct jpeg_decompress_struct { + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + + /* Source of compressed data */ + struct jpeg_source_mgr * src; + + /* Basic description of image --- filled in by jpeg_read_header(). */ + /* Application may inspect these values to decide how to process image. */ + + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ + J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ + + /* Decompression processing parameters --- these fields must be set before + * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes + * them to default values. + */ + + J_COLOR_SPACE out_color_space; /* colorspace for output */ + + unsigned int scale_num, scale_denom; /* fraction by which to scale image */ + + double output_gamma; /* image gamma wanted in output */ + + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ + + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + + boolean quantize_colors; /* TRUE=colormapped output wanted */ + /* the following are ignored if not quantize_colors: */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ + /* these are significant only in buffered-image mode: */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_external_quant;/* enable future use of external colormap */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + + /* Description of actual output image that will be returned to application. + * These fields are computed by jpeg_start_decompress(). + * You can also use jpeg_calc_output_dimensions() to determine these values + * in advance of calling jpeg_start_decompress(). + */ + + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ + /* output_components is 1 (a colormap index) when quantizing colors; + * otherwise it equals out_color_components. + */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ + /* If the buffer passed to jpeg_read_scanlines() is less than this many rows + * high, space and time will be wasted due to unnecessary data copying. + * Usually rec_outbuf_height will be 1 or 2, at most 4. + */ + + /* When quantizing colors, the output colormap is described by these fields. + * The application can supply a colormap by setting colormap non-NULL before + * calling jpeg_start_decompress; otherwise a colormap is created during + * jpeg_start_decompress or jpeg_start_output. + * The map has out_color_components rows and actual_number_of_colors columns. + */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + + /* State variables: these variables indicate the progress of decompression. + * The application may examine these but must not modify them. + */ + + /* Row index of next scanline to be read from jpeg_read_scanlines(). + * Application may use this to control its processing loop, e.g., + * "while (output_scanline < output_height)". + */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ + + /* Current input scan number and number of iMCU rows completed in scan. + * These indicate the progress of the decompressor input side. + */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + + /* The "output scan number" is the notional scan being displayed by the + * output side. The decompressor will not allow output scan/row number + * to get ahead of input scan/row, but it can fall arbitrarily far behind. + */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + + /* Current progression status. coef_bits[c][i] indicates the precision + * with which component c's DCT coefficient i (in zigzag order) is known. + * It is -1 when no data has yet been received, otherwise it is the point + * transform (shift) value for the most recent scan of the coefficient + * (thus, 0 at completion of the progression). + * This pointer is NULL when reading a non-progressive file. + */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + + /* Internal JPEG parameters --- the application usually need not look at + * these fields. Note that the decompressor output side may not use + * any parameters that can change between scans. + */ + + /* Quantization and Huffman tables are carried forward across input + * datastreams when processing abbreviated JPEG datastreams. + */ + + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; + /* ptrs to coefficient quantization tables, or NULL if not defined */ + + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + /* ptrs to Huffman coding tables, or NULL if not defined */ + + /* These parameters are never carried across datastreams, since they + * are given in SOF/SOS markers or defined to be reset by SOI. + */ + + int data_precision; /* bits of precision in image data */ + + jpeg_component_info * comp_info; + /* comp_info[i] describes component that appears i'th in SOF */ + + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ + UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ + UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ + + unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */ + + /* These fields record data obtained from optional markers recognized by + * the JPEG library. + */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + /* Data copied from JFIF marker: */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + + /* Remaining fields are known throughout decompressor, but generally + * should not be touched by a surrounding application. + */ + + /* + * These fields are computed during decompression startup + */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ + + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ + + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + /* The coefficient controller's input and output progress is measured in + * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows + * in fully interleaved JPEG scans, but are used whether the scan is + * interleaved or not. We define an iMCU row as v_samp_factor DCT block + * rows of each component. Therefore, the IDCT output contains + * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row. + */ + + JSAMPLE * sample_range_limit; /* table for fast range-limiting */ + + /* + * These fields are valid during any one scan. + * They describe the components and MCUs actually appearing in the scan. + * Note that the decompressor output side must not use these fields. + */ + int comps_in_scan; /* # of JPEG components in this scan */ + jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; + /* *cur_comp_info[i] describes component that appears i'th in SOS */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + /* MCU_membership[i] is index in cur_comp_info of component owning */ + /* i'th block in an MCU */ + + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + + /* This field is shared between entropy decoder and marker parser. + * It is either zero or the code of a JPEG marker that has been + * read from the data source, but has not yet been processed. + */ + int unread_marker; + + /* + * Links to decompression subobjects (methods, private variables of modules) + */ + struct jpeg_decomp_master * master; + struct jpeg_d_main_controller * main; + struct jpeg_d_coef_controller * coef; + struct jpeg_d_post_controller * post; + struct jpeg_input_controller * inputctl; + struct jpeg_marker_reader * marker; + struct jpeg_entropy_decoder * entropy; + struct jpeg_inverse_dct * idct; + struct jpeg_upsampler * upsample; + struct jpeg_color_deconverter * cconvert; + struct jpeg_color_quantizer * cquantize; +}; + + +/* "Object" declarations for JPEG modules that may be supplied or called + * directly by the surrounding application. + * As with all objects in the JPEG library, these structs only define the + * publicly visible methods and state variables of a module. Additional + * private fields may exist after the public ones. + */ + + +/* Error handler object */ + +struct jpeg_error_mgr { + /* Error exit handler: does not return to caller */ + JMETHOD(void, error_exit, (j_common_ptr cinfo)); + /* Conditionally emit a trace or warning message */ + JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level)); + /* Routine that actually outputs a trace or error message */ + JMETHOD(void, output_message, (j_common_ptr cinfo)); + /* Format a message string for the most recent JPEG error or message */ + JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer)); +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ + /* Reset error state variables at start of a new image */ + JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo)); + + /* The message ID code and any parameters are saved here. + * A message can have one string parameter or up to 8 int parameters. + */ + int msg_code; +#define JMSG_STR_PARM_MAX 80 + union { + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + /* Standard state variables for error facility */ + + int trace_level; /* max msg_level that will be displayed */ + + /* For recoverable corrupt-data errors, we emit a warning message, + * but keep going unless emit_message chooses to abort. emit_message + * should count warnings in num_warnings. The surrounding application + * can check for bad data by seeing if num_warnings is nonzero at the + * end of processing. + */ + long num_warnings; /* number of corrupt-data warnings */ + + /* These fields point to the table(s) of error message strings. + * An application can change the table pointer to switch to a different + * message list (typically, to change the language in which errors are + * reported). Some applications may wish to add additional error codes + * that will be handled by the JPEG library error mechanism; the second + * table pointer is used for this purpose. + * + * First table includes all errors generated by JPEG library itself. + * Error code 0 is reserved for a "no such error string" message. + */ + const char * const * jpeg_message_table; /* Library errors */ + int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */ + /* Second table can be added by application (see cjpeg/djpeg for example). + * It contains strings numbered first_addon_message..last_addon_message. + */ + const char * const * addon_message_table; /* Non-library errors */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ +}; + + +/* Progress monitor object */ + +struct jpeg_progress_mgr { + JMETHOD(void, progress_monitor, (j_common_ptr cinfo)); + + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ +}; + + +/* Data destination object for compression */ + +struct jpeg_destination_mgr { + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + + JMETHOD(void, init_destination, (j_compress_ptr cinfo)); + JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo)); + JMETHOD(void, term_destination, (j_compress_ptr cinfo)); +}; + + +/* Data source object for decompression */ + +struct jpeg_source_mgr { + const JOCTET * next_input_byte; /* => next byte to read from buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + + JMETHOD(void, init_source, (j_decompress_ptr cinfo)); + JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo)); + JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes)); + JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired)); + JMETHOD(void, term_source, (j_decompress_ptr cinfo)); +}; + + +/* Memory manager object. + * Allocates "small" objects (a few K total), "large" objects (tens of K), + * and "really big" objects (virtual arrays with backing store if needed). + * The memory manager does not allow individual objects to be freed; rather, + * each created object is assigned to a pool, and whole pools can be freed + * at once. This is faster and more convenient than remembering exactly what + * to free, especially where malloc()/free() are not too speedy. + * NB: alloc routines never return NULL. They exit to error_exit if not + * successful. + */ + +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 + +typedef struct jvirt_sarray_control * jvirt_sarray_ptr; +typedef struct jvirt_barray_control * jvirt_barray_ptr; + + +struct jpeg_memory_mgr { + /* Method pointers */ + JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id, + size_t sizeofobject)); + JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id, + JDIMENSION samplesperrow, + JDIMENSION numrows)); + JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id, + JDIMENSION blocksperrow, + JDIMENSION numrows)); + JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo, + int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); + JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo)); + JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo, + jvirt_sarray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo, + jvirt_barray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); + JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id)); + JMETHOD(void, self_destruct, (j_common_ptr cinfo)); + + /* Limit on memory allocation for this JPEG object. (Note that this is + * merely advisory, not a guaranteed maximum; it only affects the space + * used for virtual-array buffers.) May be changed by outer application + * after creating the JPEG object. + */ + long max_memory_to_use; +}; + + +/* Routine signature for application-supplied marker processing methods. + * Need not pass marker code since it is stored in cinfo->unread_marker. + */ +typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo)); + + +/* Declarations for routines called by application. + * The JPP macro hides prototype parameters from compilers that can't cope. + * Note JPP requires double parentheses. + */ + +#ifdef HAVE_PROTOTYPES +#define JPP(arglist) arglist +#else +#define JPP(arglist) () +#endif + + +/* Short forms of external names for systems with brain-damaged linkers. + * We shorten external names to be unique in the first six letters, which + * is good enough for all known systems. + * (If your compiler itself needs names to be unique in less than 15 + * characters, you are out of luck. Get a better compiler.) + */ + +#ifdef NEED_SHORT_EXTERNAL_NAMES +#define jpeg_std_error jStdError +#define jpeg_create_compress jCreaCompress +#define jpeg_create_decompress jCreaDecompress +#define jpeg_destroy_compress jDestCompress +#define jpeg_destroy_decompress jDestDecompress +#define jpeg_stdio_dest jStdDest +#define jpeg_stdio_src jStdSrc +#define jpeg_set_defaults jSetDefaults +#define jpeg_set_colorspace jSetColorspace +#define jpeg_default_colorspace jDefColorspace +#define jpeg_set_quality jSetQuality +#define jpeg_set_linear_quality jSetLQuality +#define jpeg_add_quant_table jAddQuantTable +#define jpeg_quality_scaling jQualityScaling +#define jpeg_simple_progression jSimProgress +#define jpeg_suppress_tables jSuppressTables +#define jpeg_alloc_quant_table jAlcQTable +#define jpeg_alloc_huff_table jAlcHTable +#define jpeg_start_compress jStrtCompress +#define jpeg_write_scanlines jWrtScanlines +#define jpeg_finish_compress jFinCompress +#define jpeg_write_raw_data jWrtRawData +#define jpeg_write_marker jWrtMarker +#define jpeg_write_tables jWrtTables +#define jpeg_read_header jReadHeader +#define jpeg_start_decompress jStrtDecompress +#define jpeg_read_scanlines jReadScanlines +#define jpeg_finish_decompress jFinDecompress +#define jpeg_read_raw_data jReadRawData +#define jpeg_has_multiple_scans jHasMultScn +#define jpeg_start_output jStrtOutput +#define jpeg_finish_output jFinOutput +#define jpeg_input_complete jInComplete +#define jpeg_new_colormap jNewCMap +#define jpeg_consume_input jConsumeInput +#define jpeg_calc_output_dimensions jCalcDimensions +#define jpeg_set_marker_processor jSetMarker +#define jpeg_read_coefficients jReadCoefs +#define jpeg_write_coefficients jWrtCoefs +#define jpeg_copy_critical_parameters jCopyCrit +#define jpeg_abort_compress jAbrtCompress +#define jpeg_abort_decompress jAbrtDecompress +#define jpeg_abort jAbort +#define jpeg_destroy jDestroy +#define jpeg_resync_to_restart jResyncRestart +#endif /* NEED_SHORT_EXTERNAL_NAMES */ + + +/* Default error-management setup */ +EXTERN struct jpeg_error_mgr *jpeg_std_error JPP((struct jpeg_error_mgr *err)); + +/* Initialization and destruction of JPEG compression objects */ +/* NB: you must set up the error-manager BEFORE calling jpeg_create_xxx */ +EXTERN void jpeg_create_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_create_decompress JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_destroy_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_destroy_decompress JPP((j_decompress_ptr cinfo)); + +/* Standard data source and destination managers: stdio streams. */ +/* Caller is responsible for opening the file before and closing after. */ +EXTERN void jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile)); +EXTERN void jpeg_stdio_src JPP((j_decompress_ptr cinfo, unsigned char *infile)); + +/* Default parameter setup for compression */ +EXTERN void jpeg_set_defaults JPP((j_compress_ptr cinfo)); +/* Compression parameter setup aids */ +EXTERN void jpeg_set_colorspace JPP((j_compress_ptr cinfo, + J_COLOR_SPACE colorspace)); +EXTERN void jpeg_default_colorspace JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_set_quality JPP((j_compress_ptr cinfo, int quality, + boolean force_baseline)); +EXTERN void jpeg_set_linear_quality JPP((j_compress_ptr cinfo, + int scale_factor, + boolean force_baseline)); +EXTERN void jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, + int scale_factor, + boolean force_baseline)); +EXTERN int jpeg_quality_scaling JPP((int quality)); +EXTERN void jpeg_simple_progression JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_suppress_tables JPP((j_compress_ptr cinfo, + boolean suppress)); +EXTERN JQUANT_TBL * jpeg_alloc_quant_table JPP((j_common_ptr cinfo)); +EXTERN JHUFF_TBL * jpeg_alloc_huff_table JPP((j_common_ptr cinfo)); + +/* Main entry points for compression */ +EXTERN void jpeg_start_compress JPP((j_compress_ptr cinfo, + boolean write_all_tables)); +EXTERN JDIMENSION jpeg_write_scanlines JPP((j_compress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION num_lines)); +EXTERN void jpeg_finish_compress JPP((j_compress_ptr cinfo)); + +/* Replaces jpeg_write_scanlines when writing raw downsampled data. */ +EXTERN JDIMENSION jpeg_write_raw_data JPP((j_compress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION num_lines)); + +/* Write a special marker. See libjpeg.doc concerning safe usage. */ +EXTERN void jpeg_write_marker JPP((j_compress_ptr cinfo, int marker, + const JOCTET *dataptr, unsigned int datalen)); + +/* Alternate compression function: just write an abbreviated table file */ +EXTERN void jpeg_write_tables JPP((j_compress_ptr cinfo)); + +/* Decompression startup: read start of JPEG datastream to see what's there */ +EXTERN int jpeg_read_header JPP((j_decompress_ptr cinfo, + boolean require_image)); +/* Return value is one of: */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +/* If you pass require_image = TRUE (normal case), you need not check for + * a TABLES_ONLY return code; an abbreviated file will cause an error exit. + * JPEG_SUSPENDED is only possible if you use a data source module that can + * give a suspension return (the stdio source module doesn't). + */ + +/* Main entry points for decompression */ +EXTERN boolean jpeg_start_decompress JPP((j_decompress_ptr cinfo)); +EXTERN JDIMENSION jpeg_read_scanlines JPP((j_decompress_ptr cinfo, + JSAMPARRAY scanlines, + JDIMENSION max_lines)); +EXTERN boolean jpeg_finish_decompress JPP((j_decompress_ptr cinfo)); + +/* Replaces jpeg_read_scanlines when reading raw downsampled data. */ +EXTERN JDIMENSION jpeg_read_raw_data JPP((j_decompress_ptr cinfo, + JSAMPIMAGE data, + JDIMENSION max_lines)); + +/* Additional entry points for buffered-image mode. */ +EXTERN boolean jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo)); +EXTERN boolean jpeg_start_output JPP((j_decompress_ptr cinfo, + int scan_number)); +EXTERN boolean jpeg_finish_output JPP((j_decompress_ptr cinfo)); +EXTERN boolean jpeg_input_complete JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_new_colormap JPP((j_decompress_ptr cinfo)); +EXTERN int jpeg_consume_input JPP((j_decompress_ptr cinfo)); +/* Return value is one of: */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ + +/* Precalculate output dimensions for current decompression parameters. */ +EXTERN void jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo)); + +/* Install a special processing method for COM or APPn markers. */ +EXTERN void jpeg_set_marker_processor JPP((j_decompress_ptr cinfo, + int marker_code, + jpeg_marker_parser_method routine)); + +/* Read or write raw DCT coefficients --- useful for lossless transcoding. */ +EXTERN jvirt_barray_ptr * jpeg_read_coefficients JPP((j_decompress_ptr cinfo)); +EXTERN void jpeg_write_coefficients JPP((j_compress_ptr cinfo, + jvirt_barray_ptr * coef_arrays)); +EXTERN void jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo, + j_compress_ptr dstinfo)); + +/* If you choose to abort compression or decompression before completing + * jpeg_finish_(de)compress, then you need to clean up to release memory, + * temporary files, etc. You can just call jpeg_destroy_(de)compress + * if you're done with the JPEG object, but if you want to clean it up and + * reuse it, call this: + */ +EXTERN void jpeg_abort_compress JPP((j_compress_ptr cinfo)); +EXTERN void jpeg_abort_decompress JPP((j_decompress_ptr cinfo)); + +/* Generic versions of jpeg_abort and jpeg_destroy that work on either + * flavor of JPEG object. These may be more convenient in some places. + */ +EXTERN void jpeg_abort JPP((j_common_ptr cinfo)); +EXTERN void jpeg_destroy JPP((j_common_ptr cinfo)); + +/* Default restart-marker-resync procedure for use by data source modules */ +EXTERN boolean jpeg_resync_to_restart JPP((j_decompress_ptr cinfo, + int desired)); + + +/* These marker codes are exported since applications and data source modules + * are likely to want to use them. + */ + +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ + + +/* If we have a brain-damaged compiler that emits warnings (or worse, errors) + * for structure definitions that are never filled in, keep it quiet by + * supplying dummy definitions for the various substructures. + */ + +#ifdef INCOMPLETE_TYPES_BROKEN +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +struct jvirt_sarray_control { long dummy; }; +struct jvirt_barray_control { long dummy; }; +struct jpeg_comp_master { long dummy; }; +struct jpeg_c_main_controller { long dummy; }; +struct jpeg_c_prep_controller { long dummy; }; +struct jpeg_c_coef_controller { long dummy; }; +struct jpeg_marker_writer { long dummy; }; +struct jpeg_color_converter { long dummy; }; +struct jpeg_downsampler { long dummy; }; +struct jpeg_forward_dct { long dummy; }; +struct jpeg_entropy_encoder { long dummy; }; +struct jpeg_decomp_master { long dummy; }; +struct jpeg_d_main_controller { long dummy; }; +struct jpeg_d_coef_controller { long dummy; }; +struct jpeg_d_post_controller { long dummy; }; +struct jpeg_input_controller { long dummy; }; +struct jpeg_marker_reader { long dummy; }; +struct jpeg_entropy_decoder { long dummy; }; +struct jpeg_inverse_dct { long dummy; }; +struct jpeg_upsampler { long dummy; }; +struct jpeg_color_deconverter { long dummy; }; +struct jpeg_color_quantizer { long dummy; }; +#endif /* JPEG_INTERNALS */ +#endif /* INCOMPLETE_TYPES_BROKEN */ + + +/* + * The JPEG library modules define JPEG_INTERNALS before including this file. + * The internal structure declarations are read only when that is true. + * Applications using the library should not include jpegint.h, but may wish + * to include jerror.h. + */ + +#ifdef JPEG_INTERNALS +#include "../jpeg-6/jpegint.h" /* fetch private declarations */ +#include "../jpeg-6/jerror.h" /* fetch error codes too */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* JPEGLIB_H */ diff --git a/code/jpeg-6/jutils.cpp b/code/jpeg-6/jutils.cpp new file mode 100644 index 0000000..054c919 --- /dev/null +++ b/code/jpeg-6/jutils.cpp @@ -0,0 +1,179 @@ +/* + * jutils.c + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains tables and miscellaneous utility routines needed + * for both compression and decompression. + * Note we prefix all global names with "j" to minimize conflicts with + * a surrounding application. + */ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#define JPEG_INTERNALS +#include "jinclude.h" +#include "jpeglib.h" + + +/* + * jpeg_zigzag_order[i] is the zigzag-order position of the i'th element + * of a DCT block read in natural order (left to right, top to bottom). + */ + +const int jpeg_zigzag_order[DCTSIZE2] = { + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63 +}; + +/* + * jpeg_natural_order[i] is the natural-order position of the i'th element + * of zigzag order. + * + * When reading corrupted data, the Huffman decoders could attempt + * to reference an entry beyond the end of this array (if the decoded + * zero run length reaches past the end of the block). To prevent + * wild stores without adding an inner-loop test, we put some extra + * "63"s after the real entries. This will cause the extra coefficient + * to be stored in location 63 of the block, not somewhere random. + * The worst case would be a run-length of 15, which means we need 16 + * fake entries. + */ + +const int jpeg_natural_order[DCTSIZE2+16] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + 63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */ + 63, 63, 63, 63, 63, 63, 63, 63 +}; + + +/* + * Arithmetic utilities + */ + +GLOBAL long +jdiv_round_up (long a, long b) +/* Compute a/b rounded up to next integer, ie, ceil(a/b) */ +/* Assumes a >= 0, b > 0 */ +{ + return (a + b - 1L) / b; +} + + +GLOBAL long +jround_up (long a, long b) +/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */ +/* Assumes a >= 0, b > 0 */ +{ + a += b - 1L; + return a - (a % b); +} + + +/* On normal machines we can apply MEMCOPY() and MEMZERO() to sample arrays + * and coefficient-block arrays. This won't work on 80x86 because the arrays + * are FAR and we're assuming a small-pointer memory model. However, some + * DOS compilers provide far-pointer versions of memcpy() and memset() even + * in the small-model libraries. These will be used if USE_FMEM is defined. + * Otherwise, the routines below do it the hard way. (The performance cost + * is not all that great, because these routines aren't very heavily used.) + */ + +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ +#define FMEMCOPY(dest,src,size) MEMCOPY(dest,src,size) +#define FMEMZERO(target,size) MEMZERO(target,size) +#else /* 80x86 case, define if we can */ +#ifdef USE_FMEM +#define FMEMCOPY(dest,src,size) _fmemcpy((void FAR *)(dest), (const void FAR *)(src), (size_t)(size)) +#define FMEMZERO(target,size) _fmemset((void FAR *)(target), 0, (size_t)(size)) +#endif +#endif + + +GLOBAL void +jcopy_sample_rows (JSAMPARRAY input_array, int source_row, + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols) +/* Copy some rows of samples from one place to another. + * num_rows rows are copied from input_array[source_row++] + * to output_array[dest_row++]; these areas may overlap for duplication. + * The source and destination arrays must be at least as wide as num_cols. + */ +{ + register JSAMPROW inptr, outptr; +#ifdef FMEMCOPY + register size_t count = (size_t) (num_cols * SIZEOF(JSAMPLE)); +#else + register JDIMENSION count; +#endif + register int row; + + input_array += source_row; + output_array += dest_row; + + for (row = num_rows; row > 0; row--) { + inptr = *input_array++; + outptr = *output_array++; +#ifdef FMEMCOPY + FMEMCOPY(outptr, inptr, count); +#else + for (count = num_cols; count > 0; count--) + *outptr++ = *inptr++; /* needn't bother with GETJSAMPLE() here */ +#endif + } +} + + +GLOBAL void +jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, + JDIMENSION num_blocks) +/* Copy a row of coefficient blocks from one place to another. */ +{ +#ifdef FMEMCOPY + FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF))); +#else + register JCOEFPTR inptr, outptr; + register long count; + + inptr = (JCOEFPTR) input_row; + outptr = (JCOEFPTR) output_row; + for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) { + *outptr++ = *inptr++; + } +#endif +} + + +GLOBAL void +jzero_far (void FAR * target, size_t bytestozero) +/* Zero out a chunk of FAR memory. */ +/* This might be sample-array data, block-array data, or alloc_large data. */ +{ +#ifdef FMEMZERO + FMEMZERO(target, bytestozero); +#else + register char FAR * ptr = (char FAR *) target; + register size_t count; + + for (count = bytestozero; count > 0; count--) { + *ptr++ = 0; + } +#endif +} diff --git a/code/jpeg-6/jversion.h b/code/jpeg-6/jversion.h new file mode 100644 index 0000000..f2f1b8d --- /dev/null +++ b/code/jpeg-6/jversion.h @@ -0,0 +1,14 @@ +/* + * jversion.h + * + * Copyright (C) 1991-1995, Thomas G. Lane. + * This file is part of the Independent JPEG Group's software. + * For conditions of distribution and use, see the accompanying README file. + * + * This file contains software version identification. + */ + + +#define JVERSION "6 2-Aug-95" + +#define JCOPYRIGHT "Copyright (C) 1995, Thomas G. Lane" diff --git a/code/mac/MacGamma.c b/code/mac/MacGamma.c new file mode 100644 index 0000000..59313ae --- /dev/null +++ b/code/mac/MacGamma.c @@ -0,0 +1,487 @@ +/* + File: MacGamma.cpp + + Contains: Functions to enable Mac OS device gamma adjustments using Windows common 3 channel 256 element 8 bit gamma ramps + + Written by: Geoff Stahl + + Copyright: Copyright © 1999 Apple Computer, Inc., All Rights Reserved + + Change History (most recent first): + + <4> 5/20/99 GGS Added handling for gamma tables with different data widths, + number of entries, and channels. Forced updates to 3 channels + (poss. could break on rare card, but very unlikely). Added + quick update with BlockMove for 3x256x8 tables. Updated function + names. + <3> 5/20/99 GGS Cleaned up and commented + <2> 5/20/99 GGS Added system wide get and restore gamma functions to enable + restoration of original for all devices. Modified functionality + to return pointers vice squirreling away the memory. + <1> 5/20/99 GGS Initial Add +*/ + + + +// system includes ---------------------------------------------------------- + +#include +#include +#include +#include +#include +#include + + + +// project includes --------------------------------------------------------- + +#include "MacGamma.h" + + + +// functions (external/public) ---------------------------------------------- + +// GetRawDeviceGamma + +// Returns the device gamma table pointer in ppDeviceTable + +OSErr GetGammaTable (GDHandle hGD, GammaTblPtr * ppTableGammaOut) +{ + VDGammaRecord DeviceGammaRec; + CntrlParam cParam; + OSErr err; + + cParam.ioCompletion = NULL; // set up control params + cParam.ioNamePtr = NULL; + cParam.ioVRefNum = 0; + cParam.ioCRefNum = (**hGD).gdRefNum; + cParam.csCode = cscGetGamma; // Get Gamma commnd to device + *(Ptr *)cParam.csParam = (Ptr) &DeviceGammaRec; // record for gamma + + err = PBStatus( (ParmBlkPtr)&cParam, 0 ); // get gamma + + *ppTableGammaOut = (GammaTblPtr)(DeviceGammaRec.csGTable); // pull table out of record + + return err; +} + +// -------------------------------------------------------------------------- + +// CreateEmptyGammaTable + +// creates an empty gamma table of a given size, assume no formula data will be used + +Ptr CreateEmptyGammaTable (short channels, short entries, short bits) +{ + GammaTblPtr pTableGammaOut = NULL; + short tableSize, dataWidth; + + dataWidth = (bits + 7) / 8; // number of bytes per entry + tableSize = sizeof (GammaTbl) + (channels * entries * dataWidth); + pTableGammaOut = (GammaTblPtr) NewPtrClear (tableSize); // allocate new tabel + + if (pTableGammaOut) // if we successfully allocated + { + pTableGammaOut->gVersion = 0; // set parameters based on input + pTableGammaOut->gType = 0; + pTableGammaOut->gFormulaSize = 0; + pTableGammaOut->gChanCnt = channels; + pTableGammaOut->gDataCnt = entries; + pTableGammaOut->gDataWidth = bits; + } + return (Ptr)pTableGammaOut; // return whatever we allocated +} + +// -------------------------------------------------------------------------- + +// CopyGammaTable + +// given a pointer toa device gamma table properly iterates and copies + +Ptr CopyGammaTable (GammaTblPtr pTableGammaIn) +{ + GammaTblPtr pTableGammaOut = NULL; + short tableSize, dataWidth; + + if (pTableGammaIn) // if there is a table to copy + { + dataWidth = (pTableGammaIn->gDataWidth + 7) / 8; // number of bytes per entry + tableSize = sizeof (GammaTbl) + pTableGammaIn->gFormulaSize + + (pTableGammaIn->gChanCnt * pTableGammaIn->gDataCnt * dataWidth); + pTableGammaOut = (GammaTblPtr) NewPtr (tableSize); // allocate new table + if (pTableGammaOut) + BlockMove( (Ptr)pTableGammaIn, (Ptr)pTableGammaOut, tableSize); // move everything + } + return (Ptr)pTableGammaOut; // return whatever we allocated, could be NULL +} + +// -------------------------------------------------------------------------- + +// DisposeGammaTable + +// disposes gamma table returned from GetGammaTable, GetDeviceGamma, or CopyGammaTable +// 5/20/99: (GGS) added + +void DisposeGammaTable (Ptr pGamma) +{ + if (pGamma) + DisposePtr((Ptr) pGamma); // get rid of it +} + +// -------------------------------------------------------------------------- + +// GetDeviceGamma + +// returns pointer to copy of orginal device gamma table in native format (allocates memory for gamma table, call DisposeDeviceGamma to delete) +// 5/20/99: (GGS) change spec to return the allocated pointer vice storing internally + +Ptr GetDeviceGamma (GDHandle hGD) +{ + GammaTblPtr pTableGammaDevice = NULL; + GammaTblPtr pTableGammaReturn = NULL; + OSErr err; + + err = GetGammaTable (hGD, &pTableGammaDevice); // get a pointer to the devices table + if ((err == noErr) && pTableGammaDevice) // if succesful + pTableGammaReturn = (GammaTblPtr) CopyGammaTable (pTableGammaDevice); // copy to global + + return (Ptr) pTableGammaReturn; +} + +// -------------------------------------------------------------------------- + +// RestoreDeviceGamma + +// sets device to saved table +// 5/20/99: (GGS) now does not delete table, avoids confusion + +void RestoreDeviceGamma (GDHandle hGD, Ptr pGammaTable) +{ + VDSetEntryRecord setEntriesRec; + VDGammaRecord gameRecRestore; + CTabHandle hCTabDeviceColors; + Ptr csPtr; + OSErr err = noErr; + + if (pGammaTable) // if we have a table to restore + { + gameRecRestore.csGTable = pGammaTable; // setup restore record + csPtr = (Ptr) &gameRecRestore; + err = Control((**hGD).gdRefNum, cscSetGamma, (Ptr) &csPtr); // restore gamma + + if ((err == noErr) && ((**(**hGD).gdPMap).pixelSize == 8)) // if successful and on an 8 bit device + { + hCTabDeviceColors = (**(**hGD).gdPMap).pmTable; // do SetEntries to force CLUT update + setEntriesRec.csTable = (ColorSpec *) &(**hCTabDeviceColors).ctTable; + setEntriesRec.csStart = 0; + setEntriesRec.csCount = (**hCTabDeviceColors).ctSize; + csPtr = (Ptr) &setEntriesRec; + + err = Control((**hGD).gdRefNum, cscSetEntries, (Ptr) &csPtr); // SetEntries in CLUT + } + } +} + +// -------------------------------------------------------------------------- + +// GetSystemGammas + +// returns a pointer to a set of all current device gammas in native format (returns NULL on failure, which means reseting gamma will not be possible) +// 5/20/99: (GGS) added + +Ptr GetSystemGammas (void) +{ + precSystemGamma pSysGammaOut; // return pointer to system device gamma info + short devCount = 0; // number of devices attached + Boolean fail = false; + GDHandle hGDevice; + + pSysGammaOut = (precSystemGamma) NewPtr (sizeof (recSystemGamma)); // allocate for structure + + hGDevice = GetDeviceList (); // top of device list + do // iterate + { + devCount++; // count devices + hGDevice = GetNextDevice (hGDevice); // next device + } while (hGDevice); + + pSysGammaOut->devGamma = (precDeviceGamma *) NewPtr (sizeof (precDeviceGamma) * devCount); // allocate for array of pointers to device records + if (pSysGammaOut) + { + pSysGammaOut->numDevices = devCount; // stuff count + + devCount = 0; // reset iteration + hGDevice = GetDeviceList (); + do + { + pSysGammaOut->devGamma [devCount] = (precDeviceGamma) NewPtr (sizeof (recDeviceGamma)); // new device record + if (pSysGammaOut->devGamma [devCount]) // if we actually allocated memory + { + pSysGammaOut->devGamma [devCount]->hGD = hGDevice; // stuff handle + pSysGammaOut->devGamma [devCount]->pDeviceGamma = (GammaTblPtr)GetDeviceGamma (hGDevice); // copy gamma table + } + else // otherwise dump record on exit + fail = true; + devCount++; // next device + hGDevice = GetNextDevice (hGDevice); + } while (hGDevice); + } + if (!fail) // if we did not fail + return (Ptr) pSysGammaOut; // return pointer to structure + else + { + DisposeSystemGammas (&(Ptr)pSysGammaOut); // otherwise dump the current structures (dispose does error checking) + return NULL; // could not complete + } +} + +// -------------------------------------------------------------------------- + +// RestoreSystemGammas + +// restores all system devices to saved gamma setting +// 5/20/99: (GGS) added + +void RestoreSystemGammas (Ptr pSystemGammas) +{ + short i; + precSystemGamma pSysGammaIn = (precSystemGamma) pSystemGammas; + if (pSysGammaIn) + for ( i = 0; i < pSysGammaIn->numDevices; i++) // for all devices + RestoreDeviceGamma (pSysGammaIn->devGamma [i]->hGD, (Ptr) pSysGammaIn->devGamma [i]->pDeviceGamma); // restore gamma +} + +// -------------------------------------------------------------------------- + +// DisposeSystemGammas + +// iterates through and deletes stored gamma settings +// 5/20/99: (GGS) added + +void DisposeSystemGammas (Ptr* ppSystemGammas) +{ + precSystemGamma pSysGammaIn; + if (ppSystemGammas) + { + pSysGammaIn = (precSystemGamma) *ppSystemGammas; + if (pSysGammaIn) + { + short i; + for (i = 0; i < pSysGammaIn->numDevices; i++) // for all devices + if (pSysGammaIn->devGamma [i]) // if pointer is valid + { + DisposeGammaTable ((Ptr) pSysGammaIn->devGamma [i]->pDeviceGamma); // dump gamma table + DisposePtr ((Ptr) pSysGammaIn->devGamma [i]); // dump device info + } + DisposePtr ((Ptr) pSysGammaIn->devGamma); // dump device pointer array + DisposePtr ((Ptr) pSysGammaIn); // dump system structure + *ppSystemGammas = NULL; + } + } +} + +// -------------------------------------------------------------------------- + +// GetDeviceGammaRampGD + +// retrieves the gamma ramp from a graphics device (pRamp: 3 arrays of 256 elements each) + +Boolean GetDeviceGammaRampGD (GDHandle hGD, Ptr pRamp) +{ + GammaTblPtr pTableGammaTemp = NULL; + long indexChan, indexEntry; + OSErr err; + + if (pRamp) // ensure pRamp is allocated + { + err = GetGammaTable (hGD, &pTableGammaTemp); // get a pointer to the current gamma + if ((err == noErr) && pTableGammaTemp) // if successful + { + // fill ramp + unsigned char * pEntry = (unsigned char *)&pTableGammaTemp->gFormulaData + pTableGammaTemp->gFormulaSize; // base of table + short bytesPerEntry = (pTableGammaTemp->gDataWidth + 7) / 8; // size, in bytes, of the device table entries + short shiftRightValue = pTableGammaTemp->gDataWidth - 8; // number of right shifts device -> ramp + short channels = pTableGammaTemp->gChanCnt; + short entries = pTableGammaTemp->gDataCnt; + if (channels == 3) // RGB format + { // note, this will create runs of entries if dest. is bigger (not linear interpolate) + for (indexChan = 0; indexChan < channels; indexChan++) + for (indexEntry = 0; indexEntry < 256; indexEntry++) + *((unsigned char *)pRamp + (indexChan << 8) + indexEntry) = + *(pEntry + (indexChan * entries * bytesPerEntry) + indexEntry * ((entries * bytesPerEntry) >> 8)) >> shiftRightValue; + } + else // single channel format + { + for (indexEntry = 0; indexEntry < 256; indexEntry++) // for all entries set vramp value + for (indexChan = 0; indexChan < channels; indexChan++) // repeat for all channels + *((unsigned char *)pRamp + (indexChan << 8) + indexEntry) = + *(pEntry + ((indexEntry * entries * bytesPerEntry) >> 8)) >> shiftRightValue; + } + return true; + } + } + return false; +} + +// -------------------------------------------------------------------------- + +// GetDeviceGammaRampGW + +// retrieves the gamma ramp from a graphics device associated with a GWorld pointer (pRamp: 3 arrays of 256 elements each) + +Boolean GetDeviceGammaRampGW (GWorldPtr pGW, Ptr pRamp) +{ + GDHandle hGD = GetGWorldDevice (pGW); + return GetDeviceGammaRampGD (hGD, pRamp); +} + +// -------------------------------------------------------------------------- + +// GetDeviceGammaRampCGP + +// retrieves the gamma ramp from a graphics device associated with a CGraf pointer (pRamp: 3 arrays of 256 elements each) + +Boolean GetDeviceGammaRampCGP (CGrafPtr pGraf, Ptr pRamp) +{ + CGrafPtr pGrafSave; + GDHandle hGDSave; + GDHandle hGD; + Boolean fResult; + + GetGWorld (&pGrafSave, &hGDSave); + SetGWorld (pGraf, NULL); + hGD = GetGDevice (); + fResult = GetDeviceGammaRampGD (hGD, pRamp); + SetGWorld (pGrafSave, hGDSave); + return fResult; +} + +// -------------------------------------------------------------------------- + +// SetDeviceGammaRampGD + +// sets the gamma ramp for a graphics device (pRamp: 3 arrays of 256 elements each (R,G,B)) + +Boolean SetDeviceGammaRampGD (GDHandle hGD, Ptr pRamp) +{ + VDSetEntryRecord setEntriesRec; + VDGammaRecord gameRecRestore; + GammaTblPtr pTableGammaNew; + GammaTblPtr pTableGammaCurrent = NULL; + CTabHandle hCTabDeviceColors; + Ptr csPtr; + OSErr err; + short dataBits, entries, channels = 3; // force three channels in the gamma table + + if (pRamp) // ensure pRamp is allocated + { + err= GetGammaTable (hGD, &pTableGammaCurrent); // get pointer to current table + if ((err == noErr) && pTableGammaCurrent) + { + dataBits = pTableGammaCurrent->gDataWidth; // table must have same data width + entries = pTableGammaCurrent->gDataCnt; // table must be same size + pTableGammaNew = (GammaTblPtr) CreateEmptyGammaTable (channels, entries, dataBits); // our new table + if (pTableGammaNew) // if successful fill table + { + unsigned char * pGammaBase = (unsigned char *)&pTableGammaNew->gFormulaData + pTableGammaNew->gFormulaSize; // base of table + if (entries == 256 && dataBits == 8) // simple case: direct mapping + BlockMove ((Ptr)pRamp, (Ptr)pGammaBase, channels * entries); // move everything + else // tough case handle entry, channel and data size disparities + { + short bytesPerEntry = (dataBits + 7) / 8; // size, in bytes, of the device table entries + short shiftRightValue = 8 - dataBits; // number of right shifts ramp -> device + short indexChan; + short indexEntry; + short indexByte; + + shiftRightValue += ((bytesPerEntry - 1) * 8); // multibyte entries and the need to map a byte at a time most sig. to least sig. + for ( indexChan = 0; indexChan < channels; indexChan++) // for all the channels + for ( indexEntry = 0; indexEntry < entries; indexEntry++) // for all the entries + { + short currentShift = shiftRightValue; // reset current bit shift + long temp = *((unsigned char *)pRamp + (indexChan << 8) + (indexEntry << 8) / entries); // get data from ramp + for ( indexByte = 0; indexByte < bytesPerEntry; indexByte++) // for all bytes + { + if (currentShift < 0) // shift data correctly for current byte + *(pGammaBase++) = temp << -currentShift; + else + *(pGammaBase++) = temp >> currentShift; + currentShift -= 8; // increment shift to align to next less sig. byte + } + } + } + + // set gamma + gameRecRestore.csGTable = (Ptr) pTableGammaNew; // setup restore record + csPtr = (Ptr) &gameRecRestore; + err = Control((**hGD).gdRefNum, cscSetGamma, (Ptr) &csPtr); // restore gamma + + if (((**(**hGD).gdPMap).pixelSize == 8) && (err == noErr)) // if successful and on an 8 bit device + { + hCTabDeviceColors = (**(**hGD).gdPMap).pmTable; // do SetEntries to force CLUT update + setEntriesRec.csTable = (ColorSpec *) &(**hCTabDeviceColors).ctTable; + setEntriesRec.csStart = 0; + setEntriesRec.csCount = (**hCTabDeviceColors).ctSize; + csPtr = (Ptr) &setEntriesRec; + err = Control((**hGD).gdRefNum, cscSetEntries, (Ptr) &csPtr); // SetEntries in CLUT + } + DisposeGammaTable ((Ptr) pTableGammaNew); // dump table + if (err == noErr) + return true; + } + } + } + else // set NULL gamma -> results in linear map + { + gameRecRestore.csGTable = (Ptr) NULL; // setup restore record + csPtr = (Ptr) &gameRecRestore; + err = Control((**hGD).gdRefNum, cscSetGamma, (Ptr) &csPtr); // restore gamma + + if (((**(**hGD).gdPMap).pixelSize == 8) && (err == noErr)) // if successful and on an 8 bit device + { + hCTabDeviceColors = (**(**hGD).gdPMap).pmTable; // do SetEntries to force CLUT update + setEntriesRec.csTable = (ColorSpec *) &(**hCTabDeviceColors).ctTable; + setEntriesRec.csStart = 0; + setEntriesRec.csCount = (**hCTabDeviceColors).ctSize; + csPtr = (Ptr) &setEntriesRec; + err = Control((**hGD).gdRefNum, cscSetEntries, (Ptr) &csPtr); // SetEntries in CLUT + } + if (err == noErr) + return true; + } + return false; // memory allocation or device control failed if we get here +} + +// -------------------------------------------------------------------------- + +// SetDeviceGammaRampGW + +// sets the gamma ramp for a graphics device associated with a GWorld pointer (pRamp: 3 arrays of 256 elements each (R,G,B)) + +Boolean SetDeviceGammaRampGW (GWorldPtr pGW, Ptr pRamp) +{ + GDHandle hGD = GetGWorldDevice (pGW); + return SetDeviceGammaRampGD (hGD, pRamp); +} + +// -------------------------------------------------------------------------- + +// SetDeviceGammaRampCGP + +// sets the gamma ramp for a graphics device associated with a CGraf pointer (pRamp: 3 arrays of 256 elements each (R,G,B)) + +Boolean SetDeviceGammaRampCGP (CGrafPtr pGraf, Ptr pRamp) +{ + CGrafPtr pGrafSave; + GDHandle hGDSave; + GDHandle hGD; + Boolean fResult; + + GetGWorld (&pGrafSave, &hGDSave); + SetGWorld (pGraf, NULL); + hGD = GetGDevice (); + fResult = SetDeviceGammaRampGD (hGD, pRamp); + SetGWorld (pGrafSave, hGDSave); + return fResult; +} \ No newline at end of file diff --git a/code/mac/MacGamma.cpp b/code/mac/MacGamma.cpp new file mode 100644 index 0000000..eb7c9e8 --- /dev/null +++ b/code/mac/MacGamma.cpp @@ -0,0 +1,474 @@ +/* + File: MacGamma.cpp + + Contains: Functions to enable Mac OS device gamma adjustments using Windows common 3 channel 256 element 8 bit gamma ramps + + Written by: Geoff Stahl + + Copyright: Copyright © 1999 Apple Computer, Inc., All Rights Reserved + + Change History (most recent first): + + <4> 5/20/99 GGS Added handling for gamma tables with different data widths, + number of entries, and channels. Forced updates to 3 channels + (poss. could break on rare card, but very unlikely). Added + quick update with BlockMove for 3x256x8 tables. Updated function + names. + <3> 5/20/99 GGS Cleaned up and commented + <2> 5/20/99 GGS Added system wide get and restore gamma functions to enable + restoration of original for all devices. Modified functionality + to return pointers vice squirreling away the memory. + <1> 5/20/99 GGS Initial Add +*/ + + + +// system includes ---------------------------------------------------------- + +#include +#include +#include +#include +#include +#include + + + +// project includes --------------------------------------------------------- + +#include "MacGamma.h" + + + +// functions (external/public) ---------------------------------------------- + +// GetRawDeviceGamma + +// Returns the device gamma table pointer in ppDeviceTable + +OSErr GetGammaTable (GDHandle hGD, GammaTblPtr * ppTableGammaOut) +{ + VDGammaRecord DeviceGammaRec; + CntrlParam cParam; + OSErr err; + + cParam.ioCompletion = NULL; // set up control params + cParam.ioNamePtr = NULL; + cParam.ioVRefNum = 0; + cParam.ioCRefNum = (**hGD).gdRefNum; + cParam.csCode = cscGetGamma; // Get Gamma commnd to device + *(Ptr *)cParam.csParam = (Ptr) &DeviceGammaRec; // record for gamma + + err = PBStatus( (ParmBlkPtr)&cParam, 0 ); // get gamma + + *ppTableGammaOut = (GammaTblPtr)(DeviceGammaRec.csGTable); // pull table out of record + + return err; +} + +// -------------------------------------------------------------------------- + +// CreateEmptyGammaTable + +// creates an empty gamma table of a given size, assume no formula data will be used + +Ptr CreateEmptyGammaTable (short channels, short entries, short bits) +{ + GammaTblPtr pTableGammaOut = NULL; + short tableSize, dataWidth; + + dataWidth = (bits + 7) / 8; // number of bytes per entry + tableSize = sizeof (GammaTbl) + (channels * entries * dataWidth); + pTableGammaOut = (GammaTblPtr) NewPtrClear (tableSize); // allocate new tabel + + if (pTableGammaOut) // if we successfully allocated + { + pTableGammaOut->gVersion = 0; // set parameters based on input + pTableGammaOut->gType = 0; + pTableGammaOut->gFormulaSize = 0; + pTableGammaOut->gChanCnt = channels; + pTableGammaOut->gDataCnt = entries; + pTableGammaOut->gDataWidth = bits; + } + return (Ptr)pTableGammaOut; // return whatever we allocated +} + +// -------------------------------------------------------------------------- + +// CopyGammaTable + +// given a pointer toa device gamma table properly iterates and copies + +Ptr CopyGammaTable (GammaTblPtr pTableGammaIn) +{ + GammaTblPtr pTableGammaOut = NULL; + short tableSize, dataWidth; + + if (pTableGammaIn) // if there is a table to copy + { + dataWidth = (pTableGammaIn->gDataWidth + 7) / 8; // number of bytes per entry + tableSize = sizeof (GammaTbl) + pTableGammaIn->gFormulaSize + + (pTableGammaIn->gChanCnt * pTableGammaIn->gDataCnt * dataWidth); + pTableGammaOut = (GammaTblPtr) NewPtr (tableSize); // allocate new table + if (pTableGammaOut) + BlockMove( (Ptr)pTableGammaIn, (Ptr)pTableGammaOut, tableSize); // move everything + } + return (Ptr)pTableGammaOut; // return whatever we allocated, could be NULL +} + +// -------------------------------------------------------------------------- + +// DisposeGammaTable + +// disposes gamma table returned from GetGammaTable, GetDeviceGamma, or CopyGammaTable +// 5/20/99: (GGS) added + +void DisposeGammaTable (Ptr pGamma) +{ + if (pGamma) + DisposePtr((Ptr) pGamma); // get rid of it +} + +// -------------------------------------------------------------------------- + +// GetDeviceGamma + +// returns pointer to copy of orginal device gamma table in native format (allocates memory for gamma table, call DisposeDeviceGamma to delete) +// 5/20/99: (GGS) change spec to return the allocated pointer vice storing internally + +Ptr GetDeviceGamma (GDHandle hGD) +{ + GammaTblPtr pTableGammaDevice = NULL; + GammaTblPtr pTableGammaReturn = NULL; + OSErr err; + + err = GetGammaTable (hGD, &pTableGammaDevice); // get a pointer to the devices table + if ((err == noErr) && pTableGammaDevice) // if succesful + pTableGammaReturn = (GammaTblPtr) CopyGammaTable (pTableGammaDevice); // copy to global + + return (Ptr) pTableGammaReturn; +} + +// -------------------------------------------------------------------------- + +// RestoreDeviceGamma + +// sets device to saved table +// 5/20/99: (GGS) now does not delete table, avoids confusion + +void RestoreDeviceGamma (GDHandle hGD, Ptr pGammaTable) +{ + VDSetEntryRecord setEntriesRec; + VDGammaRecord gameRecRestore; + CTabHandle hCTabDeviceColors; + Ptr csPtr; + OSErr err = noErr; + + if (pGammaTable) // if we have a table to restore + { + gameRecRestore.csGTable = pGammaTable; // setup restore record + csPtr = (Ptr) &gameRecRestore; + err = Control((**hGD).gdRefNum, cscSetGamma, (Ptr) &csPtr); // restore gamma + + if ((err == noErr) && ((**(**hGD).gdPMap).pixelSize == 8)) // if successful and on an 8 bit device + { + hCTabDeviceColors = (**(**hGD).gdPMap).pmTable; // do SetEntries to force CLUT update + setEntriesRec.csTable = (ColorSpec *) &(**hCTabDeviceColors).ctTable; + setEntriesRec.csStart = 0; + setEntriesRec.csCount = (**hCTabDeviceColors).ctSize; + csPtr = (Ptr) &setEntriesRec; + + err = Control((**hGD).gdRefNum, cscSetEntries, (Ptr) &csPtr); // SetEntries in CLUT + } + } +} + +// -------------------------------------------------------------------------- + +// GetSystemGammas + +// returns a pointer to a set of all current device gammas in native format (returns NULL on failure, which means reseting gamma will not be possible) +// 5/20/99: (GGS) added + +Ptr GetSystemGammas (void) +{ + precSystemGamma pSysGammaOut; // return pointer to system device gamma info + short devCount = 0; // number of devices attached + Boolean fail = false; + + pSysGammaOut = (precSystemGamma) NewPtr (sizeof (recSystemGamma)); // allocate for structure + + GDHandle hGDevice = GetDeviceList (); // top of device list + do // iterate + { + devCount++; // count devices + hGDevice = GetNextDevice (hGDevice); // next device + } while (hGDevice); + + pSysGammaOut->devGamma = (precDeviceGamma *) NewPtr (sizeof (precDeviceGamma) * devCount); // allocate for array of pointers to device records + if (pSysGammaOut) + { + pSysGammaOut->numDevices = devCount; // stuff count + + devCount = 0; // reset iteration + hGDevice = GetDeviceList (); + do + { + pSysGammaOut->devGamma [devCount] = (precDeviceGamma) NewPtr (sizeof (recDeviceGamma)); // new device record + if (pSysGammaOut->devGamma [devCount]) // if we actually allocated memory + { + pSysGammaOut->devGamma [devCount]->hGD = hGDevice; // stuff handle + pSysGammaOut->devGamma [devCount]->pDeviceGamma = (GammaTblPtr)GetDeviceGamma (hGDevice); // copy gamma table + } + else // otherwise dump record on exit + fail = true; + devCount++; // next device + hGDevice = GetNextDevice (hGDevice); + } while (hGDevice); + } + if (!fail) // if we did not fail + return (Ptr) pSysGammaOut; // return pointer to structure + else + { + DisposeSystemGammas (&(Ptr)pSysGammaOut); // otherwise dump the current structures (dispose does error checking) + return NULL; // could not complete + } +} + +// -------------------------------------------------------------------------- + +// RestoreSystemGammas + +// restores all system devices to saved gamma setting +// 5/20/99: (GGS) added + +void RestoreSystemGammas (Ptr pSystemGammas) +{ + precSystemGamma pSysGammaIn = (precSystemGamma) pSystemGammas; + if (pSysGammaIn) + for (short i = 0; i < pSysGammaIn->numDevices; i++) // for all devices + RestoreDeviceGamma (pSysGammaIn->devGamma [i]->hGD, (Ptr) pSysGammaIn->devGamma [i]->pDeviceGamma); // restore gamma +} + +// -------------------------------------------------------------------------- + +// DisposeSystemGammas + +// iterates through and deletes stored gamma settings +// 5/20/99: (GGS) added + +void DisposeSystemGammas (Ptr* ppSystemGammas) +{ + precSystemGamma pSysGammaIn; + if (ppSystemGammas) + { + pSysGammaIn = (precSystemGamma) *ppSystemGammas; + if (pSysGammaIn) + { + for (short i = 0; i < pSysGammaIn->numDevices; i++) // for all devices + if (pSysGammaIn->devGamma [i]) // if pointer is valid + { + DisposeGammaTable ((Ptr) pSysGammaIn->devGamma [i]->pDeviceGamma); // dump gamma table + DisposePtr ((Ptr) pSysGammaIn->devGamma [i]); // dump device info + } + DisposePtr ((Ptr) pSysGammaIn->devGamma); // dump device pointer array + DisposePtr ((Ptr) pSysGammaIn); // dump system structure + *ppSystemGammas = NULL; + } + } +} + +// -------------------------------------------------------------------------- + +// GetDeviceGammaRampGD + +// retrieves the gamma ramp from a graphics device (pRamp: 3 arrays of 256 elements each) + +Boolean GetDeviceGammaRampGD (GDHandle hGD, Ptr pRamp) +{ + GammaTblPtr pTableGammaTemp = NULL; + long indexChan, indexEntry; + OSErr err; + + if (pRamp) // ensure pRamp is allocated + { + err = GetGammaTable (hGD, &pTableGammaTemp); // get a pointer to the current gamma + if ((err == noErr) && pTableGammaTemp) // if successful + { + // fill ramp + unsigned char * pEntry = (unsigned char *)&pTableGammaTemp->gFormulaData + pTableGammaTemp->gFormulaSize; // base of table + short bytesPerEntry = (pTableGammaTemp->gDataWidth + 7) / 8; // size, in bytes, of the device table entries + short shiftRightValue = pTableGammaTemp->gDataWidth - 8; // number of right shifts device -> ramp + short channels = pTableGammaTemp->gChanCnt; + short entries = pTableGammaTemp->gDataCnt; + if (channels == 3) // RGB format + { // note, this will create runs of entries if dest. is bigger (not linear interpolate) + for (indexChan = 0; indexChan < channels; indexChan++) + for (indexEntry = 0; indexEntry < 256; indexEntry++) + *((unsigned char *)pRamp + (indexChan << 8) + indexEntry) = + *(pEntry + (indexChan * entries * bytesPerEntry) + indexEntry * ((entries * bytesPerEntry) >> 8)) >> shiftRightValue; + } + else // single channel format + { + for (indexEntry = 0; indexEntry < 256; indexEntry++) // for all entries set vramp value + for (indexChan = 0; indexChan < channels; indexChan++) // repeat for all channels + *((unsigned char *)pRamp + (indexChan << 8) + indexEntry) = + *(pEntry + ((indexEntry * entries * bytesPerEntry) >> 8)) >> shiftRightValue; + } + return true; + } + } + return false; +} + +// -------------------------------------------------------------------------- + +// GetDeviceGammaRampGW + +// retrieves the gamma ramp from a graphics device associated with a GWorld pointer (pRamp: 3 arrays of 256 elements each) + +Boolean GetDeviceGammaRampGW (GWorldPtr pGW, Ptr pRamp) +{ + GDHandle hGD = GetGWorldDevice (pGW); + return GetDeviceGammaRampGD (hGD, pRamp); +} + +// -------------------------------------------------------------------------- + +// GetDeviceGammaRampCGP + +// retrieves the gamma ramp from a graphics device associated with a CGraf pointer (pRamp: 3 arrays of 256 elements each) + +Boolean GetDeviceGammaRampCGP (CGrafPtr pGraf, Ptr pRamp) +{ + CGrafPtr pGrafSave; + GDHandle hGDSave; + GetGWorld (&pGrafSave, &hGDSave); + SetGWorld (pGraf, NULL); + GDHandle hGD = GetGDevice (); + Boolean fResult = GetDeviceGammaRampGD (hGD, pRamp); + SetGWorld (pGrafSave, hGDSave); + return fResult; +} + +// -------------------------------------------------------------------------- + +// SetDeviceGammaRampGD + +// sets the gamma ramp for a graphics device (pRamp: 3 arrays of 256 elements each (R,G,B)) + +Boolean SetDeviceGammaRampGD (GDHandle hGD, Ptr pRamp) +{ + VDSetEntryRecord setEntriesRec; + VDGammaRecord gameRecRestore; + GammaTblPtr pTableGammaNew; + GammaTblPtr pTableGammaCurrent = NULL; + CTabHandle hCTabDeviceColors; + Ptr csPtr; + OSErr err; + short dataBits, entries, channels = 3; // force three channels in the gamma table + + if (pRamp) // ensure pRamp is allocated + { + err= GetGammaTable (hGD, &pTableGammaCurrent); // get pointer to current table + if ((err == noErr) && pTableGammaCurrent) + { + dataBits = pTableGammaCurrent->gDataWidth; // table must have same data width + entries = pTableGammaCurrent->gDataCnt; // table must be same size + pTableGammaNew = (GammaTblPtr) CreateEmptyGammaTable (channels, entries, dataBits); // our new table + if (pTableGammaNew) // if successful fill table + { + unsigned char * pGammaBase = (unsigned char *)&pTableGammaNew->gFormulaData + pTableGammaNew->gFormulaSize; // base of table + if (entries == 256 && dataBits == 8) // simple case: direct mapping + BlockMove ((Ptr)pRamp, (Ptr)pGammaBase, channels * entries); // move everything + else // tough case handle entry, channel and data size disparities + { + short bytesPerEntry = (dataBits + 7) / 8; // size, in bytes, of the device table entries + short shiftRightValue = 8 - dataBits; // number of right shifts ramp -> device + shiftRightValue += ((bytesPerEntry - 1) * 8); // multibyte entries and the need to map a byte at a time most sig. to least sig. + for (short indexChan = 0; indexChan < channels; indexChan++) // for all the channels + for (short indexEntry = 0; indexEntry < entries; indexEntry++) // for all the entries + { + short currentShift = shiftRightValue; // reset current bit shift + long temp = *((unsigned char *)pRamp + (indexChan << 8) + (indexEntry << 8) / entries); // get data from ramp + for (short indexByte = 0; indexByte < bytesPerEntry; indexByte++) // for all bytes + { + if (currentShift < 0) // shift data correctly for current byte + *(pGammaBase++) = temp << -currentShift; + else + *(pGammaBase++) = temp >> currentShift; + currentShift -= 8; // increment shift to align to next less sig. byte + } + } + } + + // set gamma + gameRecRestore.csGTable = (Ptr) pTableGammaNew; // setup restore record + csPtr = (Ptr) &gameRecRestore; + err = Control((**hGD).gdRefNum, cscSetGamma, (Ptr) &csPtr); // restore gamma + + if (((**(**hGD).gdPMap).pixelSize == 8) && (err == noErr)) // if successful and on an 8 bit device + { + hCTabDeviceColors = (**(**hGD).gdPMap).pmTable; // do SetEntries to force CLUT update + setEntriesRec.csTable = (ColorSpec *) &(**hCTabDeviceColors).ctTable; + setEntriesRec.csStart = 0; + setEntriesRec.csCount = (**hCTabDeviceColors).ctSize; + csPtr = (Ptr) &setEntriesRec; + err = Control((**hGD).gdRefNum, cscSetEntries, (Ptr) &csPtr); // SetEntries in CLUT + } + DisposeGammaTable ((Ptr) pTableGammaNew); // dump table + if (err == noErr) + return true; + } + } + } + else // set NULL gamma -> results in linear map + { + gameRecRestore.csGTable = (Ptr) NULL; // setup restore record + csPtr = (Ptr) &gameRecRestore; + err = Control((**hGD).gdRefNum, cscSetGamma, (Ptr) &csPtr); // restore gamma + + if (((**(**hGD).gdPMap).pixelSize == 8) && (err == noErr)) // if successful and on an 8 bit device + { + hCTabDeviceColors = (**(**hGD).gdPMap).pmTable; // do SetEntries to force CLUT update + setEntriesRec.csTable = (ColorSpec *) &(**hCTabDeviceColors).ctTable; + setEntriesRec.csStart = 0; + setEntriesRec.csCount = (**hCTabDeviceColors).ctSize; + csPtr = (Ptr) &setEntriesRec; + err = Control((**hGD).gdRefNum, cscSetEntries, (Ptr) &csPtr); // SetEntries in CLUT + } + if (err == noErr) + return true; + } + return false; // memory allocation or device control failed if we get here +} + +// -------------------------------------------------------------------------- + +// SetDeviceGammaRampGW + +// sets the gamma ramp for a graphics device associated with a GWorld pointer (pRamp: 3 arrays of 256 elements each (R,G,B)) + +Boolean SetDeviceGammaRampGW (GWorldPtr pGW, Ptr pRamp) +{ + GDHandle hGD = GetGWorldDevice (pGW); + return SetDeviceGammaRampGD (hGD, pRamp); +} + +// -------------------------------------------------------------------------- + +// SetDeviceGammaRampCGP + +// sets the gamma ramp for a graphics device associated with a CGraf pointer (pRamp: 3 arrays of 256 elements each (R,G,B)) + +Boolean SetDeviceGammaRampCGP (CGrafPtr pGraf, Ptr pRamp) +{ + CGrafPtr pGrafSave; + GDHandle hGDSave; + GetGWorld (&pGrafSave, &hGDSave); + SetGWorld (pGraf, NULL); + GDHandle hGD = GetGDevice (); + Boolean fResult = SetDeviceGammaRampGD (hGD, pRamp); + SetGWorld (pGrafSave, hGDSave); + return fResult; +} \ No newline at end of file diff --git a/code/mac/MacGamma.h b/code/mac/MacGamma.h new file mode 100644 index 0000000..d1c5a6e --- /dev/null +++ b/code/mac/MacGamma.h @@ -0,0 +1,82 @@ +/* + File: MacGamma.h + + Contains: Functions to enable Mac OS device gamma adjustments using Windows common 3 channel 256 element 8 bit gamma ramps + + Written by: Geoff Stahl + + Copyright: Copyright © 1999 Apple Computer, Inc., All Rights Reserved + + Change History (most recent first): + + <4> 5/20/99 GGS Updated function names. + <3> 5/20/99 GGS Cleaned up and commented + <2> 5/20/99 GGS Added system wide get and restore gamma functions to enable + restoration of original for all devices. Modified functionality + to return pointers vice squirreling away the memory. + <1> 5/20/99 GGS Initial Add +*/ + + + +// include control -------------------------------------------------- + +#ifndef MacGamma_h +#define MacGamma_h + + + +// includes --------------------------------------------------------- + +#include +#include + + + +// structures/classes ----------------------------------------------- + +typedef struct // storage for device handle and gamma table +{ + GDHandle hGD; // handle to device + GammaTblPtr pDeviceGamma; // pointer to device gamma table +} recDeviceGamma; +typedef recDeviceGamma * precDeviceGamma; + +typedef struct // storage for system devices and gamma tables +{ + short numDevices; // number of devices + precDeviceGamma * devGamma; // array of pointers to device gamma records +} recSystemGamma; +typedef recSystemGamma * precSystemGamma; + + + +// function declarations -------------------------------------------- + +// 5/20/99: (GGS) changed functional specification +OSErr GetGammaTable(GDHandle gd, GammaTblPtr * ppTableGammaOut); // Returns the device gamma table pointer in ppDeviceTable +Ptr CreateEmptyGammaTable (short channels, short entries, short bits); // creates an empty gamma table of a given size, assume no formula data will be used +Ptr CopyGammaTable (GammaTblPtr pTableGammaIn); // given a pointer toa device gamma table properly iterates and copies +void DisposeGammaTable (Ptr pGamma); // disposes gamma table returned from GetGammaTable, GetDeviceGamma, or CopyGammaTable + +Ptr GetDeviceGamma (GDHandle hGD); // returns pointer to copy of orginal device gamma table in native format +void RestoreDeviceGamma (GDHandle hGD, Ptr pGammaTable); // sets device to saved table + +// 5/20/99: (GGS) added system wide gamma get and restore +Ptr GetSystemGammas (void); // returns a pointer to a set of all current device gammas in native format + // (returns NULL on failure, which means reseting gamma will not be possible) +void RestoreSystemGammas (Ptr pSystemGammas); // restores all system devices to saved gamma setting +void DisposeSystemGammas (Ptr* ppSystemGammas); // iterates through and deletes stored gamma settings + +Boolean GetDeviceGammaRampGD (GDHandle hGD, Ptr pRamp); // retrieves the gamma ramp from a graphics device (pRamp: 3 arrays of 256 elements each) +Boolean GetDeviceGammaRampGW (GWorldPtr pGW, Ptr pRamp); // retrieves the gamma ramp from a graphics device associated with a GWorld pointer (pRamp: 3 arrays of 256 elements each) +Boolean GetDeviceGammaRampCGP (CGrafPtr pGraf, Ptr pRamp); // retrieves the gamma ramp from a graphics device associated with a CGraf pointer (pRamp: 3 arrays of 256 elements each) + + +Boolean SetDeviceGammaRampGD (GDHandle hGD, Ptr pRamp); // sets the gamma ramp for a graphics device (pRamp: 3 arrays of 256 elements each (R,G,B)) +Boolean SetDeviceGammaRampGW (GWorldPtr pGW, Ptr pRamp); // sets the gamma ramp for a graphics device associated with a GWorld pointer (pRamp: 3 arrays of 256 elements each (R,G,B)) +Boolean SetDeviceGammaRampCGP (CGrafPtr pGraf, Ptr pRamp); // sets the gamma ramp for a graphics device associated with a CGraf pointer (pRamp: 3 arrays of 256 elements each (R,G,B)) + + + +#endif // MacGamma_h \ No newline at end of file diff --git a/code/mac/MacQuake3 b/code/mac/MacQuake3 new file mode 100644 index 0000000000000000000000000000000000000000..f6d71f869ab6e29f7ef191ad5c82aeff1c96c004 GIT binary patch literal 141015 zcmeI52VfLc{>NvMkc7}e6$Nx@(j*{Fz(ODtK?0E?oaMrjEU=R1CZXA|WACS)y`6e@ z_TD+g-p{i)>}Na6*~{_&{NB9TmYvy$=6|PTAAI-s-8bKP^WN|M-kaH($xKCUZIxwN z*_LGww6eOFTUlElV_EIW$UDi-vN297sjUnx2u32|+DM=*QoDbsqQPC%LTpx{Wn~2{ zYg(3N?M|EJyY+jEZ=`aom9mTQ<5Ym{IN!3mkjZy@?ftfO<6{@!V6WW(Irzd`WG#Gd z6=<7(<700&^0f!w6H;Enx6GFo>VfNM_dOCcS(ZHt+Xm!<0+0_{gSMbO=l}{qC(s3S z1zUmcpcmK{YzG3MH|PWUf_|Vs*d7c31Hm9L7z_bBfT5rW3ewF6FUHi6Pq}(`+|O;KOjzQ;>0FS>_LDyv56CV2QU;6CpK|n6DKxtVvhhL!6?F5 z#d9ts*V%SA=wHFTPM-C>%#Md{BB$wRJITcbR%x(e_fW7h6sa#-rt*?MWcL-@g*i)G z**00uf6K~YxXcKy2$oa@>+3`HMf(RM$%X7ay)00=VDB=OFOH8(lx5QqQKL1MHAi)j z-V(WNj|L@f`)=}aht)H#z(gu_ZzVfn*-TuDuYO|wW83fkiRmsbj$QW{NB;4g|VZYoMzaQWwB$G(+9siN!>xK zU{S5_@m8^HO6~di(00Ck3O_aulqEXFT4~~w%mnQEt;O2R1l(e~z8M?7MNg?)@a`hB z18BxU@Y^gZvjg~T_S>(l)^?D?*u|g%RD%7$BEU~FdkI(y_66mDQxSF;><^X$E`r+C zpa#?e&Q{t70DcJC^?>tmHW%pZ--8ukC0GSkgY&@!;6iW_I0u{uE(Vu?OTpRTTyPn< z9Q+Af0j>mBfj@)408WD2SA%Q7wSW`l_VwV8;0ACbxCz{h^Cy84Fd0k%yMc*dDwqa# z2fKn|FdfVQdw`js6wCs%ft=dj6U+f~!92jlOKUG6r*js7h2VE!Z_p3)2it=IAOQM; zfnX3A47LM(z!0zl7z&EOFfbhK2u6UBU=$b)#(AT-Eqra^YYSgn z_|(Fu)TK{A%G>OZ<8Z z!1osZwr&Ntf!hInV$m1Y9pFxI7q}bT1MUUu!Qa4r;C`?H(0|q;;83s@90t~agTdk8 z2yi4=4GscFfuq4OU>!IX90!gECx8>dN#JB~3OE&<1`Y(LgEPRH;18e`$OU;IAJ`xV zv<3yB4Um)#+JbhVJ?H=mK}XODbOv2OSI`Y?1-gT+K^E8s^Z-3UFR%y%!D3JW_66mj z5`@4Kun+h>SPGVbFxVd~2UVaN)PP!02Mz!cP!AfwexMPo04u>Nn)ecT8N32s1>003U*nz{lVd@I3ez_!N8wK4;sHg2%w) z;0f>``7^MOfG5FI;A!vxc$j5-V4uOx1f^gW-~SGt1=P=?-WJ=jsHa8!E!t(#E{k?q z522q8%D|pr4tcc0q8%3PuxN)R?H!BV2xyZsv2L%s3936275(2MV+FAfIcx1-6ku=Jz4sCW!~9XJ+9zZ?gS2Pc3N!AXF=vrh)6fK$O~ z;B;^hI0Kvs=;tiyw?BZh;ItzPUu5B4d4OrJ$Mj21Re&DfJcGE)??st@C0}gJOyq8PlIQ`50uvm z`*-jxPN5%uarze}dHv!2tH5fo1{??&e`?-kY`e!7GACPW!C~NV@Gdw490`sB#J<{h?}N<-Qtzph zKO7Kep7Y>XmP!0g!*&Lp;ClkrQ^#SAz}U0q`~W20R9y0FQ$w!MGMVb^I|ATMkM<2rL3Yuoy@jR)CMeBj9M-E_FW!JO}bYC3psO1>L~O;1qBwI1ii; z-T@bY3&BO;VsHt#6wCwtK!3{4!d?Te1=sOC8+$#t0elH=1UG@3!7boca2vQC+yU+c zKY_cz-K?L3{RrFx-Uj!A_26$TE5Y6ePJ{1?JsrFP7k`|A#lN!Om19ZS_sa2tI;K2} z?~{SF_iS(udItu&ClL4bMjXZzMZeRi^1`;QefW#5;p!O+C zuv0-0ECv;z5=;ZTgXv%f*aOT2e+H!>1TFxxz-&+k_5^dmpTO^cjFb6bFF*{c-+fnM zmx5&=4E6`hK^3S5HJ}#MfdfDU)Pn}l2v&fVU=>&m)_?=SLEvC;2sjk11&4vd!4cp{ za1=Ni90S&YW5IFYcyIzZ5u60#=L5!Cliz56CC?r6(4U=tX+6ZJ@o{r5-`&`ezVVA6 z>7y%v#E|rf^sB^@v`ym4Z69-5bUyQ%%aEi`iMQnGd%A439-rCHEMR_rS613B=DBl< zvk}ox3f6}!0`s_iesS%pKxt!DLwI~iV|_zybzqOs>Xo&T%KDU=i4_&0`uadwuwhwh z!Mq@MzBL5qh8h~eHA_=#riQCRfzn`Iou^__W4Nl4yBQiH!PG^J8M{Y7Zt9;Fs!3T- zSw1;jFSkWjFRtQ-h*S+0(adnoa-YTS9}A>fy<}L)kRjBfx{mgw-hw0HFAr)wn_4op zlzODB-}E_w*>w%!>hKzm;^p2?xy!JQ3*V_~sFwRRK?(LMO}R3+Y0aJ+aPDL*3)Y0H zQmV>=^%cP?e=lf_uN%tBN_;z1mZ$Ad5&3keEH0ZeHFY~=nf}X=RIX&*iS^Y4zLz8B zgx2_bVL`ZNj@$32vG3p& zf%njLmwJX_{IDeqE5gzJGLA?!NRt%FgEVPzib0yBXfjBXmM0I=q{T^tG{w^7{!Sr@ zc_+ofxM)vW+H{bn6vRhsvLHH0lNKZo(xk;ngEZAr?`p>l(xeqr4$>q|ib0xM;nVD- zL7KdNib0w};@N3Zx*nt{R`d1?>m>}*q?MBgY0~1RgEUEyG)Pk{^=whnAdNeC_86o| zisV6>v^d2eO;R)&q)E$@2Wiscq(PcuY4RXVA&Ge>#lpB=Oo%z#cJMuNgkw0 zD<==qq{U4KX_6pmkfvDb*&^>jI(6c_RD066!bRaTNM*7AAQgq*AeH4lgH#rK4^nrj z&meUrDZS$^Ox>%p)OV1^1Rl|<1gQq8EbtkmveuQV2>^K z8S*hvx)B;v`j68ly0o?T9?vKC8Bhr%hjYEU9Qll_gslz?})}=bj4)8@u_6qqe~X29bF4zeY{VA( zjF6bf2@daZ5>xt*mL|Hiwf7!5ab@|BpG0Ar{z*NS5{pRJe?H?XR#4i}MJZ|a8%|-O zOFOzmWHP!$mwt4KD(&bJQM%FPYSNA_SLoAwuEei*L}M_zVlvW6{t~g!V+N1T@FU4X@G)Da+!ZiIO5;-1A+}R{f7)=@VCyvNzm?sr@WVeMd-4;1OL)kZM%O0-y09i_?t+ z(fE&nIE7FBPQev4Q0x20v@7xSu9V(1rrp)N{q7Ufva(N1#}1Vyh3ji;^bJ;tQ61AvtqQG5 zBZ-FRCXEM0lFoxs zjh_djBJM8qs;_|urI>UcbTvL6bS0^4%#7vZJ6Y=KHCgEGujo?m?WT@0S<%-!BJlQ$ zyELUo+=Z#@B%bn)BU$R{3t8yxi|A7C?WXpHtmx|t5qSH;U7FGt?n1Xt_$Hv9O33ZW za`&q~yVqo|>!nNB_j-wv_rPAd#Qm_BDq(NzC6au1ui>Xj*faYHwKx4FX?>b_`?Ob4 z&MrN=NA#3A`)#kq343oZQGD?FYSJ~$4P>uEoZyN-(c+~{K4eIvOY0^AK5Za~#wZX( zm?jWJA_o+n^_J!Oo=qQ3?10%zlyHdQB}$q`ykzl{iJvA-+oc${ZOW(exYINfi&E|4j$dJE0zp=liN&voblzm$ z*u>&hP{PFGB}$rDykzkci=QS<+v6t|8Guv6HT(%q`XiQ9x`c_vtFYvW#Y>ksv3RKx zCKfM|eK#;QBBbHSCqL#-Uv3O`=6N^_} z5+)WeQPRZXC5xX}{4{CW9zU^2J5#kS`2?hwE@5Kv5+zS8Ub@7I#Y>eiv3QBZJ#IYt zX%Z$DKVjTtoyuRbJ$LkZ6_Yfv_{*Xr##0tMVo6n@go(wgjQEMgSCg)3?!@9%NYjbM zOPM^eq|v2yldPXE5JY1X2qH`q2qJNhSW@+iTCOJ+;U1dU#Ns7Nm{`0-NfV2gEPi6~ z)1+y8{KUcu;WWo8O%(}~Nz=v2(@0ZA;snxkdBW7ubaDKok$P!Dl%$quAEjRCj-#ea zlO_i*K{Vb|2%HI_>4Jo*py}fHNx);NceUMt-*m;ML%yjdd9Zsbe3~us>JIP}>$5aX zr%3^xohA!qIHxYz+b^sa8^TRjP8hsR7bgwdrh@nZn|i5di{b~Vv@X?&P}Qq5gEdPV zgG)o|$5*Ops;MefS^D)ak$^LNJ%7qgomZx>`S{gpa&1Mzem2z>rG-;gRfKq`LT!ya z#X|m=UkXjth*Z@}rAw?{DuuWsxq7K|p4Cg$7MYT!Rn;!$mtX855nc|G{FLHU)$lAi zmDsc7RCQKy!4nXwgEf^hvYZI=R5}wy^id*SjT5&SH!gJ*QngGeQ&b_9*0Tz!+NScB zh9ZGEjWs;o!+RK|6s9V1$ACK|V-H^O^4177CkA+MN+=SPao{=bMh2pjt5;#80uw7M z!%~V4Lxz>-fbyzkDu<@7Wh!M-EmKKS)-vr=b6SGHwjv>U230@cK1XN@JUd)fH5n_< zrOL?~eOmx4&xUH1H!UR3pOR-In6g_hj$H4tm1iHS@*%a)z(a;Zdq@_ zx2kmVe}Z@CVLog(yBB=xRSgCBBU_#wwoPUJG}g^7hxZ6IOs1aM@=UFs^Fpgce+j%- zLz`0CnEf~SwoBVj7QK|eU1Ov|xYR$;$Psf5Ee-a?XVJ2c#T%{?zu0S~<^S2WT$k4|I5|j@UsUK(~#+4lb|YVMh~e3BCn`SRSj z5+@$=7s4ky@sKaiR^y3O5)b)O{!}L(@^67NDkL8AUx4rK#6$kK@aaxGw3fb@;lx90 z84G(j@z8n}e5Ml*t>u|-rQj5X%`sS6hL2mzv&d!x8~y~g3>3gW#qJ4sU|2yr>>SV@ zF6B!fv&{l2Zyx9ZmvKHHYz03GD{+ex#!^lp!xf#iw>&Ql zz8!oYo)?B`vnwC_F0JMvTvi(y7yDT=!;9ha?781_93h&E_$ZGw1jfME!xwRv&+hOy z;6ca#ZDianmScXVg8;lDv_{(5R?4qr)v@q>;GwME*ZvbO&$wFx{|tUUe5vNr7t1t% z4j#_xeI2H)jEnu@FEYh+f-i?Z4Br7>r8(Q08Sq-oZ-CcnF6AGf`4{kr z!`ruo*K0l)-T-I2?THUw`0Max@D*_U(EeEX%B(Sm)WNTSud;GOufjLLSMzKw#%TMu z;A_l?tq_k|xcYvXL;#HDrcyp8jPhv0|oy>TDmN5T(Xv@t0B68PFB z8nzMk8|dq!mr@RJM&LRVv*+!s`;nmIQWUq{L@k5@gzX%DCM8* z%s(B^f}i5dKOL`!pX$s%9iM`q=FC4G{{=tYnSVOTxH!X^e>xSx&vfR5PBYI{fYgQfP|xZWUd_klfYe3&@FzvBF85+3 zZb)6;=i^F6t*&jbS7|;9E6+6~brs*roI>ill8?VCYIQvqd$s24vDaw+E>?U&>L&h_ z0Y&OIf)D8fQn$VNkg-APww8|@g~6r1Hz{g$djxy4=3iiCtdh3s%7^%cw3WobZHii3 zEyCWex%A5&noGOy)ci5*U7AZ=-RaYe0d zw#Po9xs?B;=0U8)H))&0`FL7UYnw~4GOkG5h!6g*sI`s6)w7y!!v4eIJ$hrMZKNJj z{_~1jJ*4~>G?)4_S4rw|F&{4}YV{C*zpT0B$u>wmzUAXpMXjD&VPDgH0`_&yE3t2A zegbx*<~Ly9)LgdzmgYZT-*$Mfe%N<3pN19Rk$NrX<2^;KUeb^6YcBEfPtBzrA87s) z_CtqnI{^EU=CiRMYrYcuiRR~GWga1IyMd2S6}7gN?R@6&?eegnYd!?~h2~{g+24@1 zYvkigMXl}5$9|=`#M;-IOTE5vc%Tp~J|zW4^C9s^3jB_b?-jKI5(DBRQs8nvWIiGV zB-W&zq=3}%KZ;ttrC%ftNWI6X!w84(1Gg2mdLISP(wsR!J-b=-#EW_kvvBI7o<|Pv z(+8gG==)58i!VrhLhyXekA=6^oVi5aF(9cAv8kSWE*yWzdjll(?E;tiMYzOyT1El_=96qoWTwq}cA)O^eh(S&87Z9&0l~o*8B%JaVDv#ADs0S$H#U^^X2d*nx6(=s`>5kWtzVT4{QDt ze1C@zW6asCFKL+gp-OYgv*}Ao!%l(MXfFL&tNBKFox_Lczz@)TC_JJ${bSc_j@E9_ zTw;UyOVV(uC$Xsb>u~y5(vC7lR%tH%wOVsxMBa%aX-6sZK+VsFAEf!+@Pjpf7k-Gt zM@S4Ds`+sETFr?I`!LN}Pu|5MX~acv;y}`f4e%p1{|J7R!$)?4AFVlafPIYS#FV{G zbLuPaW|1`VLOAo0;`hRj*Zc!GF(GMGNBD`FkAt72`F?QbVo9Uero78V@vGpcYW@hE zI8gZ#|ED{AwA2&7OB%fs{7lV>O?lsoq|vln-ut5X<#2qexRiO0=AXm==dPntgqze4kG;a56*Y)9JM z{}TSD!;3q^=}Sq)JHqJ$#mnIDXfAR4uI9`Q_IsLN2Y+94d?4>WlT`c}93M%VDCK{s z`EWS>A!(w-+sB$OgEMzXns^NSUz-03{;B5lv%EJ=(nRW_-k%1alm-7nWm}U1@PBJQ z6;3-PO{#)_rTH1~uQg{J$UCScO%nfq>+q5`aOMa}B_rYAYd#{WeMwWOkGykA@txqgnpeW} zG(QoZuQ~0KcYjHmLf^_ez$8s=3#Tl_8K+r%SA1W1JI$p&?KQsv&X|!jmH5pnbog)k zb5hgyFIZEVsY1z&Ss)dOq;Uc;qq41__H_dT+E@3svnii}M$?Zm5U$0rgB}CrY;ryv& zBvcovmDi@!@{$nekDTMPN{Lq|kOqzj$HL8xp;)F9-_qyNJo%F1h5)Zi`s&ZHQd}YT zx@6z<<|b=mU0qeU!ntvPp%n>6!l8QUA@1jJzfGJqcY55{@o~ep!ixW<1~GqbS~K2p z0=`x;8_S9trn;x(_L16}@d-meagjR_jMpR9wE>D(8`Akrww5?doWMXtQ`9?RlJ9&@ zxGAy8Er^NlxQTzCP4u=7ecw~k4Svqo=&cvA+lvzJ)=R!YPJfTCCiQl2eiNQ>t6N;7 zgt~X7x;L$n7rR9*;f6F(H@VfOVrhA0B)F3K85UZyBvjELleK)Qi88r(;$&6_!!-`8 z3q~5k6;&avuB!^J4n^EAkx-?hS`iNEvR8(Jb=+V=_Tr`G%-0U74%b&WtggCtMM!9Q zFs#fHIzh?H^+p$$*92FvXlZ${q9LrrOUo;&!cOI&)s>=$)K)JJN`;q}hq;x&$<&1` zE!Ue=q@Hakooqa!Sbbe^r8IGAdHsrLi4E$-NGi7>5?;#vKZ-Xtgw?KXX}QxnG7q4( z4a*d%Ul!DUs4A~;+y=o1wQ8dkRpsFtUjBm?x;(U6>IrjQTvd*X>!o^R)>lNNQuQ^J z<(1Vz&8kDyE(@=MHHORCVvQ_@vZWe&)A>>vs;+gI-Vg$gR2rSUnot98jau$}sf&b` zgeZnd&&fC-Cae!d@RQD85v~kLq4g`u7uU*mAQjqW(4|bNPG-0!tQYIHv9!J>SXaNy zU9FLV9WC{!Qn6F*d@?6jv81B9oYzTJC~v4aMDkVyWhFOGWP9P7B~qj6%2D!Jzm!kf zR$j3zDBf#`lrOHYa|qXFCGf}+R_^jf+UI2JQGp;_&Fzv-rfvzDRpF(}q)|}aC}E*` z>3U&KTnp1JfY#K8RewV3*vB+RWDxL$K&jLlP*hqa*~?dJLd?~Lm$=^=7gvVG_j+Mt zWQoIE*Hs5A+&a}XRuMZjl}^met_#&no9RsTt1Xd*^vD*zv?^RJ&Jm8&U+=!*n#vC`GkSMkH8MF9*8pV&ZZ|#=aCOTTK*7hHT0m1Jh+J z%$1$(az`Brt*O9wMG={ds%q&bJtK`DKYnqrK6JoH*>l9|MRvmXIiUj@L-h@N=_q9m zl{w1YY}6@}nf>B7J2}GC5}8Y&hZzAPk1ts5f5i*V0BeZUstJJbtf~z<5zDY`sE{y_ zm<>t@3ZW*#n30?ig~{!M&MMWx$Z{Edi=z{pjAKR; zd9ZrssRn0^lEFL~mi2Um)vgp3y9pUE5$Pn=xTw#fO-psFU(;Lq;%?nbd5d0Yq(mBC zS6j8ZO3n6EV@VbBlhlHy)K}C-LW^sI5%F3=WX^49bYf-N%u=SU$ZC~A6^q}kN=*~b85$}d{M#OlWcQ^_9^pSWmwIVQmfds#H?GP=P;_iI7nR9NL~4&Cpqod zkeWiAR>h`DnLiqMMW61PCE<{o*HnM0odGP?vARXNU@8*7y3|Weyzb(v@^SQ9sTutu{h)W;4Z#L+3YCiO$t9xHUWq++eHG)# z-9?AWgTZ>4OP9&qyg2&xfDwsbN5;NZRPlmBHOXsvrTjsCY&l~)+#oZYUMxWx`=+N@ zy)3aBwfZf4F?A#qR0B}UBm5qYD@%P1Rw@5DHLnj=*G0D$9^p=&vO2p__Tsu|^~GH; zT)wn6=r&&lq3Tp;owy2y)uBPOfR!P=-#{3W9W^%W{t)IXF&B}2QI#AbIr}$gtvHG! z12q~HC1^Ryk?C3;8-(kmTF{0_qY7mrq-IUnS?(UCRFyk9G^(=Nnc)>>ny;{6x(W1p zkXK1cC`sJa*K>m~+&Qpu;$f-Y;Ugt*)g&lAFLAnBPFtv;m(jt#$=PW~;|l$furx=4 zO9i!_5P#dJgfg{nc^?zLopFTtle^`uwYUZ?Hc9s@?&m zS^E5|x}T{7`n4?Qys}kj<$k&m`wr-M=%(B{tepGq#7T8IS1jkOIp3Zw=jdgdU7WV4 zdnvl|ouuwd=%)D?>{i+TdUzM|IbUvdXIoFh=fbxJyTkXvZeyvl&+58t55AMswa=cK zpMdRU<@PEj{}1qO0q2d?wX5y8Riq>QKJo&6NN~BH(HpxKd|D(^VCQKr{WxFqH?ezZ{wa2W z=09R3hDf<>`S_ipR<6YR-kRfU_3m^`uJkQ&At_gUxu2p|uGCBVo0J>mL;Oq1t>$Bq zF!&*S$TCu{#K2-jt=v=Ml%$1c_UdF(RH-^PYD{{*|g z<`O5%9iEqktq)<2Oob>)XLw0JxlW^v9dlXU;J~9qE`OL*gtCi4fb4zx6Z+yr+Ejg#4D+F zcRr+_NUi(waiOAC>!H|-G~Wq(vE~!8muNm4EA=L|mhpF)qE_ol?B$wEEdNP!iM1;< zmp;EzbLkU_4^nIK&z}{wT1&hCqPcAUubN94S8Fcz8qIIRUaR@vu;NEj>&N)GUQw&{ z3)mYpe+w(INNO#9yGc>2wfOC3hZoq`TQqNry;bvWSlJ$_Ai&4%!r+7XkUk?7jOOD` zMXiEjti(D={jJH}nlHrOqq+F)Ud^kq>oq?REA1f_NNnDxs8t~Ga=+%%ZyPih|2?3& z)bT;h#Xh9@-?0yC{yO#%&HssgRP)cU;yY5ofB1M@QLBx_&l8&W$4V@c+KlDnDMhU| z)3Hx$J|Fvx=1Z`$ZBm;$KAu(7Y9r(RADT3Y;^T8gt#*ajFEp3F`?uzUuwQCE2K$xfldxZF zJ_jpfgVe5)k8c&V+BIUo)BIrU_nIG%{Xz5dus>>k74|31Wqe3%k=jXoZc@~0N8HKK zlhlrWcJIG#hcDgxuiMck_x|g4l;Pfg-98^KZ6>wv4$sxR4?Iuv9pUICwciEaTJx#! z0?o_dZ8Q(U+iG41Z>RZMczey)!8>SvI$V5BYJUN|qvo{Tz5lxX4e-u7|6X_(%^830 z{nzb@U-$m&_QZ>O|8;w5J8hFBn{@BL?!XuK{_76g!h0x*)nO>Sr{=rCdud(@M<=Pn ze(>!yUk(pwz8czY>boc)2Lj3C9e_cpB-21N!SC>Gxmp&!VBo+Px zzN_Z1!|4x6g&)8tXwLYviyhukd@@mU;?%wWx}(H)iO$Ez?)}#tX_I^Zb;l`iVmgz4 zfd{f!cM>1;gHAb$|8(PHx7Kgk^fzKBlUk?;Cg(PdiytK}I3;jx3-w8+*)mgfoils( zym(i%n>J@YhZHJx&gCjTR~`7$W9syoQ;4GHwS7U`7qxv!+n2R{McY@keNEffwS7a| zjoQAc?c3VEqwTxezOU^+wf#Wb54HVB+mE&VMB9I9`>D2{Y5TdhU&J-HODmci4Aa_d zz4U8sztQ&FICaOYMv->bi^gg@PTTR??xO8(+D_26Slfx(9;NLS+Fq&c25leI_8D!z z)3!|8pB!s%rET}Ps^!?qluZEKsQtsd-lj{e?C+gxq)wAG<%x7Obav~8oUc8J|h zf7gz&wPS4U7`vlht{r4+huYdvwsw@=Ev`X1ZMj4rm|GXB2rmiq>mWLA!cJjUm9}fN zt=6_i+XiiK)Ap&j!n0e?3DwuiBTP6R;6U7&>1*Q@S?y%=+#I6s59Q~sR%s_h>!Y1x z)#>F2XdBVCUfV`(57hP=ZEw={W^LEU)xvI{bSElTP_#_Z(?CdQ09i9*PJtNO(vE+BQ{Jw4n_}!;|)XMK?`Q6+JbOv2OSI`Y? z1?2bd)?gbTzjNjHt^BT)-?Q>NHUN5qKA34sbbBDbL*@79ATSsV0Xu-9Kz>gS z1M>TEcQ75y0DAy_SE_3tv#|1;QhrPFdrMtAlHZK-TTy-^%5OvYO?Wmq2mBG72hIl< zf{Vb#;1X~txC~qlt^ikpzW{z$s%uH|8&ZBd%5O&btth_{i@`)N36ubS`>Ja`Q?b*) z5-=Oc0mq(T4wwt%h-5z43oHN&!SBGI!QO!1%IbXZzS#Z1??E{TfmA`-A163RDBxLDqsgZ~&0$L&ih{Xaw>bY9&|&R)aO*KyVN^7#so)1#7`! z;BasRI1(HMjt0kob>LWV95^1F08Ruafs?^0;8buLI31h;&IEq|e+AcoYr)S-xx_J$ z2R;>me9#)S1?@oxPzXAKE}$#e3Umj(z_wsJ5CFYFAJ7-{1O37FU;r2h27$p~2-pD( z1w~*O7!Gy>Bfv;73XBG0z)oN+7zf6KUBGT&0w@L(!3;1Hl!94cHYfvof;nI=2!kq6 z4QfCwr~?Op2&e}Qpb@MDYruivU~mXH6s!e@fy2R(;3#l3I0mc($AaU)$>0=lD!2k% z39bRxgB!t3;AU_OxE0(6?f`d!yTILGJ=g#q1P_6S!6V>N@ECX;JO!Qx&wyva^WX*W zB6tbB3|;}Rg4e+7;0>@5yb0b0?|^r~`{1A81MngE2z(4a0sjJ@g3rL`;0y3I_y&9n zz60NbAHa{`C-9$Krc_`9F1*{>AP2Mp#G9Q5@siar?19N)_nVP}r-_2I#mbbRkL zsklRjfmOR(rBl{Uw7RV0=&M89YYN9^^I(kgvbv1zX=TsrX^lVhh__d*X`p+V z=4Tt|UO|^*pnH|=z8uqK^EH}mm*MBvD6dsa7hNQh=ofSw`I>K_dkbBGf$nW|Z47kp zplfTOdly|h1KoS*+8gNJN7unX_fK?%2D%T>bu`d@gszK$?qhUa4RoKN>t>+)6kT@% z-52P380h|uuBUT;QeJq&+MeW8Oo@o`WaZr`L5rx4(g|5Z!VE9eX04h@XDD%xF9zKfR6_ zm#62a*KLI^Vxa4euHHb`3*9OMom^pCZJ^r@-5LX30NsHGy58sxGSKxwcd&u3FSG=!_XaPpc{_vcmthW@jbynHv-*>2D*{xPBPGqLU*!(ZZx`640L1Aoob-l z3EgQ1y0Pd^H_(kkcZPv(Ji0Rtbi1HC%Rnbr&CfB=O+fcY16?t?a}9J8(Vb_Yn}qIs z16>Ka3k-CV(Oqbuk%4Y1x{D2T)6iXFpqq~FG6P*0-39|)6}pEEbk*n{Hqh0e zd&EFji|)~wjuQ#7k!sUF`egj-|69)a#n1KoM(-Zjvj zkM2DK-3930H_+XL?gIne&FDTf(A|mdlbFuaf9uhG9n*RG?{Db7iRnDcyAR#B2D@-!&^?3hF9y25qx-9Y?pbtK8|eOl?ivH#bLd_((EZx4_`OOyUXHcH zb6jpDPV-|r&++yax&i~;+vr--mA8**Z{~^`!A6P(563xQ@ilWrt*Kvr#4njEYEAqi zf2EzdqUN|IejLePnrE)4rSOaC6*bO_+QsSjTk;q5_Cy2S*61b~=(a&uVxa4Z&fv}* z^;i1W8k8sJW3MyN$@$pp4RitOmt&yoO?fvM==z|$(LmQ1-B$)WIUjqIflkiH-fWEqKKGvRMpyPb3JvE(7t90m!Cqhi*dHtha$QBPnaK4Jxz-`qG2|MBTz`;j3yoj}kn{bkz-q7t z$a(#Pz#-sJuofH!4hKhoBf(MNXmAWz2aW~Df#bmm;6!i|I2oJ*P6eld)4>_wOz;PA z7C0N61O5ok1?K@dCw~FB5L^T<2A6Mn% z1Go{~1a1bmfLp6uXzycOy#c#-^QZ=j5pSP<5y} zOH&n4s522VoosfZV78N!rA*qj-Hjn+4bRjyuDV;HnyZgZ=joYgzB(qABO<4i1y(Cn z)P+@5N5zInX@)<%>9{!`Ja#9~{?cpZ-*t_2R1A_Li$=J?$A*o6;$><};Tqo)?TP zCGY6D^XByB`)OrmwTt=nRM)SE&IQn9^Tmck5gA03dbx_hgkl8OQSICFjm-3-(H22p}I88#G-P&_f zuw%k+#lV=cdu(1Kwt$m@?Np2L39i^Z6y(A0^+j=mE6yLOdb^c6JWo1PKfyS%a=aB98JHYw2#)vgiQK%FNn5H1GB>b9 zdp2_eOVkw^(vm&k{sJ)df%99!`7~YUzo&C<-0|C9o_jhVt_fT6$>g|ZCb`9IN&IbL z2|Vs6VtjtmMmFzj^PZ7+?tqg|o}$P{TXo-7w_zFeU(Xt}JX{{R%#r&BjCmom{ zX4i#krp*k@oxI0*eZe*p87(I=+<2hHU7Ey0^ey9=h-fJh5f=|#o&7~)IMe$rrT3e* z(~XB>%j)dJ!{XY8s_^1WOf>aXOUQ|fhc3~02xg{;meBX9+87rPUAZII9`D=&Ik&N{ zt~SyTJ))dn6P9~!gH?gdF=i?^ZZUar@sRDrL#QEAyD}76ULTm-5Ui;TMk@J};>D3* zWcB#cxibSLnLWuC)A=bH5*H7=I6>xzbn!4`NG2juIC2Zgii?M=6#I+VUE`S`*g|^T zzZr4ykdq=FGG`$C-M7Upjf;oYDdJ&^Yj37O{vL>nhg=m8{_@PyE!+ccJZyU6<&?}mL7oxpn@Sf1 zq+!0AlB#-nmauO!DQCw1ktIdIx1cl&<>`T?3rZ%Zk)>U{2*sSab4t<*(+H#-6{(9; z|J9|t`lMi0RV^{Ld#H-PfKHuMIZG>2FVwlyxH++;wlWkbsjcA*Pebza0E*G6a+Wnz z|I#2*s?T)(ao0)pd}Mc- zi7nQmqBUX9aV!#(`Z%X<`KxuW-Adrswnr%~ojFN6z|-&(GBCYb&PIFxK|YU#NE@j} z@$PlKXQ~)7 z#Lofd<@i^~yd2+wEF;;lU3I<%8JEKod;I)kvP|unn(au!e~anbRmkeidIXThJJhEv zSYN@VFYo7psQT0eefB461~)9H!uQZGoqyK4{;8{2<~K6T%c=9-d#CeHv_8#sgj64r zX94}9)LchezqTWr`ejw}JSE>$Hf3ts{Z4&DW3!Ya6)D0=z7|PrcQGU)vGVuX4}bTzi7%`VH4yGfOl4_WFfEs-jXN)np{64&lUHC*pmc1HSFA zE#f!xHvvscY_a=oGk+6!m(xtY34WC$InB%*Jx1{K&SoBir!B|7Lgw}HKd9S`a(oLi zF6Xy;4DQiig$%O)%`vz~h1~iWAA_eW$M6{3qnNeP`WPOAr!B|y7(8`B(fTykp3?W1 z4zC+5E16g??-BG#&2Q#3I;#5kyIK5fc-CtRJ$u&R8_q_KUJ$MsIU=xX>=>`>I$btvMxY?C z%{t{6&IoDCF`N<7mg65{(U~QEIlcuMm(yG$xw+1wq#4QS+7+DFg@o$$ zDPxa`LzUAEXV0Yg<~YVwaj>*}oboaC@qDCq*n(JPsUw98vbfz=A2g%nz&dBlZ z2U(KNKcd-;dv!ePQ!M3}->c(Uj?~BG{B^o^Es}yrhWFU`)<>0Nc5jS-Ik9Io78YB) z2_*AuK5=b}PVT+P#k|@@#Y2nsMyBy@Jm86T&is&hR%1(!3O62#EuQk5c~)af4h7Zr zxOmX>L+06hEjcLS;-S4WKV+WWnTZGW?C%avJY=5Ln285@(ZqKG4rg(mJtq23DI{=WuDd8 zLi*gVnQ`%uc~+yJqgu$axOm7stFeXjxnDEm;vw^_Mn6ZjkY#c4ka<>P3+Z#eX2!)s zLFQSFex7O>%iMTSt<5~Eab~z?d6U04(vgsPR%4oHK4zZPn0Z#C*Q}X&R->0^oP7P) z1D>K{mTboT-=5{jg38RZ8a@3}te0e-)#&A-xWB3K{5#*~`fKXudTMWT{Z)46S&f-z zHF}MU=-*&^jtc#kQoq&b#CWtT`WILK^}j9ks1O~b{&LIsIWg(VG5p(7k7CwF>tp!0 zrD@AC{oB&i1x4%AY|n|wJgbo|Zju&}vzgC#N!?*m&gMPe#j_l#WV8M4t;wGe$_72s zBJwlOYV>f9%KooE|G&9r@aCFXn&Gd_e_@cSeptk($w*e>G#{B~HTpRy?ze^KGpd^F zH-X7tzx&lymMsF!`q%IL)qlZxJFJs4^Zec)Uhw|y-Q~Nz=H0cH^}A8gV@&0p?7i50 zPo-~X&F{SG>zRCa>SOx6bWbn4<@h{?fNdq#N4DNHDgRXXKQLr7%JD78xSZeWF}O#6 z{qK&!Jt{;PY~C?=x^fJU!99vu8?BGwF?iZ?Opn1+7Zj~ebL}a8pYD=*RwLd2i_#+2 zHaP;wJgd>m&BZLpJgf1yc=jywtVXYxR(m)urg$wk$&c>2(>&qLq%3ET>#yVpDf6sG ze;1`)nt4{EpNpc$;GR!PbAMYHK6N!+IhkiQ`oG82_ZL&n0mTLEik+{ z#=o3=|7~QJZuuT4ojZL>$LTe^kGdw*5SX&6uBtZTb@Q#4H>PtYS5_+ut@3}zyjKC4 z{NJ_*a)%W(&X_*WI%9=C(BL6{XB={!rT%huJa4re*Jz(nzsP!V)ouH9qNzRY8CILp zDYNEtjb-t?U_{aw$>h+dw7fg=S*0JS2IdQ+{bHY4n(z42dm9xLK73NamyvCvjpWh0# zcaSFt*#(LhSpx%>y{F0ok-bs6RWr-CY!57JXs8=MY?wSdzNo&jrl_K}dRSegwz9FJp?=u@mCJ|iShV9X z&iK|33)Y2)ZGpas4vj6aZJ8#Sdms}B=Dx}(Te1f-M-wf%_kG)*IhycwPiA4p0~rrw zJh0_^AbK?2dd8&M#+u5BUUxoBAX$YtgsUT)R9Y~7*5sPTsx*I@vE{ok)544g@BnA) zI9(SVPZ^(NJdp7~#se+X1JQXfb6n9vBOuf4=J7!0xT1Od_}ecfI;Z~jo04h4&-8#B z16cX*ewEUqw+sBtqPNJ^;vKX_HgGc=CH~OBq>Z08)sYIhvn;%-DDJ+o&D`d1S)Lp} zLn}+BO)Q;KzWc;ElgnpLpS8!7$$_Dj$yvYU7W>vc*RuF;^~iGk!ugKulzK#*C$zd7 z=vt%O+CV3FAZ}xzYlCi(hc4Qi)UPeNr7>M}5!tb`IOy8*J#3)sfNp;Sot#5mZlIGp zLaPmQUC`AU=(?h-GtkLhpb-OIcXagzx?bp38R)h}x7t9r9lA9Jx&XQZ4RpQH9b};E zgYIAhU0-yE80h+;JJdkeAKh95-S+4XGtdn{cesIWAi5(Abc4_xX`ma7?kEG@5OhZy z=ypJNjDc<_x^)J+B6P@M1GSJON_h$oL8M?n1=;W^w{%W9`gYIer z-CT6n80f<2HW=vSI_X0Ox@vR}8|Z4#Jz}7%MfYe-m&4bliQ}9)avzWBJdf)RK=*`! zE`sh!1D!k{=qUqT1G=YUI?v-Z{;tM)CZ_Xj$4Ye1#&n+LtwHyqf$l(bFU548+dUZF z`7xd6b`L>!fr0K&bQc=v)}p(}KzA6riw$&#qr1dFcO<&Y40K1Kd(}X9G`iOebjP53 z-9Wbv-5UnFW6^Ci&>e^FO#|J@=-xHZor3N?1Kp|U-Z#))f$jqX-IeG*G|*jx?vt1< zA?~BW&HQpbx-Vn8=pwQcesA5#_pc3fH=+9`rt{qH&FH>0(A|RWyO_?iez&4qW}v$b z-S-B%JJ4-1(A_C+GSJ;6r*#Z;ccaTP(5&_MSTx-JH~r_psa&^?2$n}P0GbaCftJp1i= zbaCf>JasRi>uIpv7t!@H(7l9iTLaz8=(aP^y@D=apnDZvZv)+H==vDwUPss0K=%f^ zeg?XY==vMz-bA;(f$nW|0}XWVpc`bMdl%hc1Ks=Rb}-QW6Wve)-3RE340Io&8)l&U z2;Fc4-N)#5G|+v5ZiIpEU+6{}=sra^%0TxSy3q!@&(V!B(0ze!Cj;Hr=yo>HeS>Zn z1Kqdib~Vs_hi*3m-S_Aw80daLS8SmB5#2-s-B0Ky8R-6lt|Yxq=3_hCKqta$4Rl%P zt~1cd0m1bKI(~TCIR?5`ly`%Hjzdc8Mgv_Qx~~j$`RHyk(6vT)vw^Mv-7N+>xn6Or zfvzpO+YEH=(A{C6Yme?u16>Dn|1r=NqPyEb*Ad-42D(nfODh9iXUhB7KquEp?lsVH zoy4AOpzB6?_Z#T8qP!^vy6)(vrqi*_Ov-p*GdwVi^W(c))%6W>J}xe;B!BC*hk0B`%ky5R=WK7e7lS_ zb@>VTt1K&Phw8d|wi};+Gxg6IRo$?Z`p4(r&iT_kt88X{gO$Cfv_Y;HlG}{UAO9)eZI1FR~BT>7{>LRc<$P%W94Gs1X+NpcK745Zywd}|LUPI9lMn5mmpR6?R!XjAZ6)>8{@F_U zg#MP)eGDItf3p7&g0GfbVsil>x}LAfxxiy2m-0Ce>h|M<7m~3~Wn1}kvAUjj9}5?d z3J&ALsb@~N?crxgF6AG^hpy+N&%q^DNZlUhL)Y`;$KZdETPgij(XgUWn_*SiZHMxQ+H>Tz+l?E4uv{mw!J#iS}b$e%?a#`A%HTx`BNP z<3v*4B0k!xeEYdm$k%aOApN5ClV*}XiSML>-|?aKpA%bliORMLq%XAo)~V$0q4TM~ z)@SuuL_TFnDv*BD`m5Ln+w)YmRrCl}m;co{Hm8;1=Q03c0DlBV2 zv6KJle&owI!L46E~9b zIPYV1amv4on6cMNF8Ta!*YQw#`sL(H+>r8Qu50gK@qs!S<+I5u5xAS|$ou_)0W`6=d+R?xCK32DP{B-hl{O2-{ zStFeC-XYQ3p!0`g+d1VQw~_o4b^b+I?ax2oLH*u9+>&y zBJw4UN%=*5IPqM1-YoKORoV8z^ox$?(uMfUzRk(scHdY$=gcMlb|?R`*|B&oUqC)S zmGmP1(D6L;?Na!iPX0>|Vs-sT60`PQPJY1@tmFUEllqWU|K!L zrxVYm*Plzq<4%5I9^8rN(m~YSe!|J`xE=BW zv3MT9oN7Pq!0_e{d(Pj_1c1 zTlPPk{Ekv)p&BpNTA6?T?c{G=9?Sno`u$5MfBOdN>CFGu1=8NHocw>Z;ka#-Q%>pk zPX5b#y5%3b>E(CH|IW#Om9g)Xf9R$! zUn8GwknA@YYi{`m;#>O%Cx7q?tTRrn2I^w}sPe76GHiZzFzN4{?N(cpe^+%-9gjIu z_4(4v7-ya-Idv%I{NyYw7!=Dtdky(AUt^h$@;_z! zZaJ;rB%eMI{owU{I_)~=u*Kvvhm!1nGw!mS{YdT$=fjC_$^VMEOXue;-T|KP zw}-3#wI8QXvsyd(Id@>^sr;-Q{F7DS +#include + +#define CONSOLE_MASK 1023 +static char consoleChars[CONSOLE_MASK+1]; +static int consoleHead, consoleTail; +static qboolean consoleDisplayed; + +/* +================== +Sys_InitConsole +================== +*/ +void Sys_InitConsole( void ) { + SIOUXSettings.initializeTB = 0; + SIOUXSettings.standalone = 0; + SIOUXSettings.setupmenus = 0; + SIOUXSettings.autocloseonquit = 1; + SIOUXSettings.asktosaveonclose = 0; + SIOUXSettings.toppixel = 40; + SIOUXSettings.leftpixel = 10; + +// Sys_ShowConsole( 1, qfalse ); +} + +/* +================== +Sys_ShowConsole +================== +*/ +void Sys_ShowConsole( int level, qboolean quitOnClose ) { + + if ( level ) { + consoleDisplayed = qtrue; + printf( "\n" ); + } else { + // FIXME: I don't know how to hide this window... + consoleDisplayed = qfalse; + } +} + + +/* +================ +Sys_Print + +This is called for all console output, even if the game is running +full screen and the dedicated console window is hidden. +================ +*/ +void Sys_Print( const char *text ) { + if ( !consoleDisplayed ) { + return; + } + printf( "%s", text ); +} + + +/* +================== +Sys_ConsoleEvent +================== +*/ +qboolean Sys_ConsoleEvent( EventRecord *event ) { + qboolean flag; + + flag = SIOUXHandleOneEvent(event); + + // track keyboard events so we can do console input, + // because SIOUX doesn't offer a polled read as far + // as I can tell... + if ( flag && event->what == keyDown ) { + int myCharCode; + + myCharCode = BitAnd( event->message, charCodeMask ); + if ( myCharCode == 8 || myCharCode == 28 ) { + if ( consoleHead > consoleTail ) { + consoleHead--; + } + } else if ( myCharCode >= 32 || myCharCode == 13 ) { + consoleChars[ consoleHead & CONSOLE_MASK ] = myCharCode; + consoleHead++; + } + } + + return flag; +} + + +/* +================ +Sys_ConsoleInput + +Checks for a complete line of text typed in at the console. +Return NULL if a complete line is not ready. +================ +*/ +char *Sys_ConsoleInput( void ) { + static char string[1024]; + int i; + + if ( consoleTail == consoleHead ) { + return NULL; + } + + for ( i = 0 ; i + consoleTail < consoleHead ; i++ ) { + string[i] = consoleChars[ ( consoleTail + i ) & CONSOLE_MASK ]; + if ( string[i] == 13 ) { + consoleTail += i + 1; + string[i] = 0; + return string; + } + } + + return NULL; +} + diff --git a/code/mac/mac_event.c b/code/mac/mac_event.c new file mode 100644 index 0000000..dd9fd83 --- /dev/null +++ b/code/mac/mac_event.c @@ -0,0 +1,357 @@ +#include "../client/client.h" +#include "mac_local.h" + +void DoMenuCommand(long menuAndItem); +void DoDrag(WindowPtr myWindow,Point mouseloc); +void DoGoAwayBox(WindowPtr myWindow, Point mouseloc); +void DoCloseWindow(WindowPtr myWindow); +void DoKeyDown(EventRecord *event); +void DoDiskEvent(EventRecord *event); +void DoOSEvent(EventRecord *event); +void DoUpdate(WindowPtr myWindow); +void DoActivate(WindowPtr myWindow, int myModifiers); +void DoAboutBox(void); +void DoMenuCommand(long menuAndItem); +void DoMouseDown(EventRecord *event); +void DoMouseUp(EventRecord *event); +void DoMenuAdjust(void); +void DoKeyUp(EventRecord *event); + +/* +================ +Sys_MsecForMacEvent + +Q3 event records take time in msec, +so convert the mac event record when +(60ths) to msec. The base values +are updated ever frame, so this +is guaranteed to not drift. +================= +*/ +int Sys_MsecForMacEvent( void ) { + int tics; + + tics = sys_lastEventTic - sys_ticBase; + + return sys_msecBase + tics * 16; +} + + + + +void DoMouseDown(EventRecord *event) +{ + int myPart; + WindowPtr myWindow; + Point point; + + myPart = FindWindow(event->where, &myWindow); + + switch(myPart) + { + case inMenuBar: + DrawMenuBar(); + DoMenuCommand(MenuSelect(event->where)); + break; + case inSysWindow: + SystemClick(event, myWindow); + break; + case inDrag: + DoDrag(myWindow, event->where); + + // update the vid_xpos / vid_ypos cvars + point.h = 0; + point.v = 0; + LocalToGlobal( &point ); + Cvar_SetValue( "vid_xpos", point.h ); + Cvar_SetValue( "vid_ypos", point.v ); + return; + break; + case inGoAway: + DoGoAwayBox(myWindow, event->where); + break; + + case inContent: + if (myWindow != FrontWindow()) + { + SelectWindow(myWindow); + } + break; + } +} + +void DoMouseUp(EventRecord *event) +{ +} + +void DoDrag(WindowPtr myWindow, Point mouseloc) +{ + Rect dragBounds; + + dragBounds = (**GetGrayRgn()).rgnBBox; + DragWindow(myWindow,mouseloc,&dragBounds); + + aglUpdateContext(aglGetCurrentContext()); +} + + +void DoGoAwayBox(WindowPtr myWindow, Point mouseloc) +{ + if(TrackGoAway(myWindow,mouseloc)) + { + DoCloseWindow(myWindow); + } +} + +void DoCloseWindow(WindowPtr myWindow) +{ +} + +void DoMenuAdjust(void) +{ +} + +int vkeyToQuakeKey[256] = { +/*0x00*/ 'a', 's', 'd', 'f', 'h', 'g', 'z', 'x', +/*0x08*/ 'c', 'v', '?', 'b', 'q', 'w', 'e', 'r', +/*0x10*/ 'y', 't', '1', '2', '3', '4', '6', '5', +/*0x18*/ '=', '9', '7', '-', '8', '0', ']', 'o', +/*0x20*/ 'u', '[', 'i', 'p', K_ENTER, 'l', 'j', '\'', +/*0x28*/ 'k', ';', '\\', ',', '/', 'n', 'm', '.', +/*0x30*/ K_TAB, K_SPACE, '`', K_BACKSPACE, '?', K_ESCAPE, '?', K_COMMAND, +/*0x38*/ K_SHIFT, K_CAPSLOCK, K_ALT, K_CTRL, '?', '?', '?', '?', +/*0x40*/ '?', K_KP_DEL, '?', K_KP_STAR, '?', K_KP_PLUS, '?', K_KP_NUMLOCK, +/*0x48*/ '?', '?', '?', K_KP_SLASH, K_KP_ENTER, '?', K_KP_MINUS, '?', +/*0x50*/ '?', K_KP_EQUALS, K_KP_INS, K_KP_END, K_KP_DOWNARROW, K_KP_PGDN, K_KP_LEFTARROW, K_KP_5, +/*0x58*/ K_KP_RIGHTARROW, K_KP_HOME, '?', K_KP_UPARROW, K_KP_PGUP, '?', '?', '?', +/*0x60*/ K_F5, K_F6, K_F7, K_F3, K_F8, K_F9, '?', K_F11, +/*0x68*/ '?', K_F13, '?', K_F14, '?', K_F10, '?', K_F12, +/*0x70*/ '?', K_F15, K_INS, K_HOME, K_PGUP, K_DEL, K_F4, K_END, +/*0x78*/ K_F2, K_PGDN, K_F1, K_LEFTARROW, K_RIGHTARROW, K_DOWNARROW, K_UPARROW, K_POWER +}; + +void DoKeyDown(EventRecord *event) +{ + int myCharCode; + int myKeyCode; + + myCharCode = BitAnd(event->message,charCodeMask); + myKeyCode = ( event->message & keyCodeMask ) >> 8; + + Sys_QueEvent( Sys_MsecForMacEvent(), SE_KEY, vkeyToQuakeKey[ myKeyCode ], 1, 0, NULL ); + Sys_QueEvent( Sys_MsecForMacEvent(), SE_CHAR, myCharCode, 0, 0, NULL ); +} + +void DoKeyUp(EventRecord *event) +{ + int myCharCode; + int myKeyCode; + + myCharCode = BitAnd(event->message,charCodeMask); + myKeyCode = ( event->message & keyCodeMask ) >> 8; + + Sys_QueEvent( Sys_MsecForMacEvent(), SE_KEY, vkeyToQuakeKey[ myKeyCode ], 0, 0, NULL ); +} + +/* +================== +Sys_ModifierEvents +================== +*/ +static void Sys_ModifierEvents( int modifiers ) { + static int oldModifiers; + int changed; + int i; + + typedef struct { + int bit; + int keyCode; + } modifierKey_t; + + static modifierKey_t keys[] = { + { 128, K_MOUSE1 }, + { 256, K_COMMAND }, + { 512, K_SHIFT }, + {1024, K_CAPSLOCK }, + {2048, K_ALT }, + {4096, K_CTRL }, + {-1, -1 } + }; + + changed = modifiers ^ oldModifiers; + + for ( i = 0 ; keys[i].bit != -1 ; i++ ) { + // if we have input sprockets running, ignore mouse events we + // get from the debug passthrough driver + if ( inputActive && keys[i].keyCode == K_MOUSE1 ) { + continue; + } + + if ( changed & keys[i].bit ) { + Sys_QueEvent( Sys_MsecForMacEvent(), + SE_KEY, keys[i].keyCode, !!( modifiers & keys[i].bit ), 0, NULL ); + } + } + + oldModifiers = modifiers; +} + + +static void DoDiskEvent(EventRecord *event) +{ + +} + +static void DoOSEvent(EventRecord *event) +{ + +} + +static void DoUpdate(WindowPtr myWindow) +{ + GrafPtr origPort; + + GetPort(&origPort); + SetPort(myWindow); + + BeginUpdate(myWindow); + EndUpdate(myWindow); + + aglUpdateContext(aglGetCurrentContext()); + + SetPort(origPort); +} + +static void DoActivate( WindowPtr myWindow, int myModifiers) { + +} + +static void DoAboutBox( void ) { + DialogPtr myDialog; + short itemHit; + + myDialog = GetNewDialog(kAboutDialog, nil, (WindowPtr) -1); + ModalDialog(nil, &itemHit); + DisposeDialog(myDialog); +} + +static void DoMenuCommand( long menuAndItem ) { + int myMenuNum; + int myItemNum; + int myResult; + Str255 myDAName; + WindowPtr myWindow; + + myMenuNum = HiWord(menuAndItem); + myItemNum = LoWord(menuAndItem); + + GetPort(&myWindow); + + switch (myMenuNum) { + case mApple: + switch( myItemNum ) { + case iAbout: + DoAboutBox(); + break; + default: + GetMenuItemText(GetMenuHandle(mApple), myItemNum, myDAName); + myResult = OpenDeskAcc(myDAName); + break; + } + break; + case mFile: + switch (myItemNum) { + case iQuit: + Com_Quit_f(); + break; + } + break; + } + + HiliteMenu(0); +} + +void TestTime( EventRecord *ev ) { + int msec; + int tics; + static int startTics, startMsec; + + msec = Sys_Milliseconds(); + tics = ev->when; + + if ( !startTics || ev->what == mouseDown ) { + startTics = tics; + startMsec = msec; + } + + msec -= startMsec; + tics -= startTics; + + if ( !tics ) { + return; + } + Com_Printf( "%i msec to tic\n", msec / tics ); +} + +/* +================== +Sys_SendKeyEvents +================== +*/ +void Sys_SendKeyEvents (void) { + Boolean gotEvent; + EventRecord event; + + if ( !glConfig.isFullscreen || sys_waitNextEvent->value ) { + // this call involves 68k code and task switching. + // do it on the desktop, or if they explicitly ask for + // it when fullscreen + gotEvent = WaitNextEvent(everyEvent, &event, 0, nil); + } else { + gotEvent = GetOSEvent( everyEvent, &event ); + } + + // generate faked events from modifer changes + Sys_ModifierEvents( event.modifiers ); + + sys_lastEventTic = event.when; + + if ( !gotEvent ) { + return; + } + if ( Sys_ConsoleEvent(&event) ) { + return; + } + switch(event.what) + { + case mouseDown: + DoMouseDown(&event); + break; + case mouseUp: + DoMouseUp(&event); + break; + case keyDown: + DoKeyDown(&event); + break; + case keyUp: + DoKeyUp(&event); + break; + case autoKey: + DoKeyDown(&event); + break; + case updateEvt: + DoUpdate((WindowPtr) event.message); + break; + case diskEvt: + DoDiskEvent(&event); + break; + case activateEvt: + DoActivate((WindowPtr) event.message, event.modifiers); + break; + case osEvt: + DoOSEvent(&event); + break; + default: + break; + } +} diff --git a/code/mac/mac_glimp.c b/code/mac/mac_glimp.c new file mode 100644 index 0000000..86343f9 --- /dev/null +++ b/code/mac/mac_glimp.c @@ -0,0 +1,829 @@ + +typedef int sysEventType_t; // FIXME... +#include "../renderer/tr_local.h" +#include "mac_local.h" +#include +#include +#include "MacGamma.h" + +#define MAX_DEVICES 32 + +typedef struct { + GDHandle devices[MAX_DEVICES]; + int numDevices; + + Ptr systemGammas; + + GDHandle device; + + AGLContext context; + AGLDrawable drawable; + AGLPixelFormat fmt; + + GLint textureMemory; + GLint videoMemory; + + DSpContextReference DSpContext; +} macGlInfo; + + +cvar_t *r_device; +cvar_t *r_ext_transform_hint; +glHardwareType_t sys_hardwareType; +macGlInfo sys_gl; + +void GLimp_EndFrame( void ); +static void GLimp_Extensions( void ); + + +void CToPStr(char *cs, Str255 ps) +{ + GLint i, l; + + l = strlen(cs); + if(l > 255) l = 255; + ps[0] = l; + for(i = 0; i < l; i++) ps[i + 1] = cs[i]; +} + +/* +============ +CheckErrors +============ +*/ +void CheckErrors( void ) { + GLenum err; + + err = aglGetError(); + if( err != AGL_NO_ERROR ) { + ri.Error( ERR_FATAL, "aglGetError: %s", + aglErrorString( err ) ); + } +} + +//======================================================================= + +/* +===================== +GLimp_ResetDisplay +===================== +*/ +void GLimp_ResetDisplay( void ) { + if ( !glConfig.isFullscreen ) { + return; + } + glConfig.isFullscreen = qfalse; + + // put the context into the inactive state + DSpContext_SetState( sys_gl.DSpContext, kDSpContextState_Inactive ); + + // release the context + DSpContext_Release( sys_gl.DSpContext ); + + // shutdown draw sprockets + DSpShutdown(); +} + +/* +===================== +GLimp_ChangeDisplay +===================== +*/ +void GLimp_ChangeDisplay( int *actualWidth, int *actualHeight ) { + OSStatus theError; + DSpContextAttributes inAttributes; + int colorBits; + + // startup DrawSprocket + theError = DSpStartup(); + if( theError ) { + ri.Printf( PRINT_ALL, "DSpStartup() failed: %i\n", theError ); + *actualWidth = 640; + *actualHeight = 480; + return; + } + + if ( r_colorbits->integer == 24 || r_colorbits->integer == 32 ) { + colorBits = kDSpDepthMask_32; + } else { + colorBits = kDSpDepthMask_16; + } + + memset( &inAttributes, 0, sizeof( inAttributes ) ); + inAttributes.frequency = 0; + inAttributes.displayWidth = glConfig.vidWidth; + inAttributes.displayHeight = glConfig.vidHeight; + inAttributes.reserved1 = 0; + inAttributes.reserved2 = 0; + inAttributes.colorNeeds = kDSpColorNeeds_Require; + inAttributes.colorTable = NULL; + inAttributes.contextOptions = 0; + inAttributes.backBufferDepthMask = colorBits; + inAttributes.displayDepthMask = colorBits; + inAttributes.backBufferBestDepth = colorBits; + inAttributes.displayBestDepth = colorBits; + inAttributes.pageCount = 1; + inAttributes.gameMustConfirmSwitch = false; + inAttributes.reserved3[0] = 0; + inAttributes.reserved3[1] = 0; + inAttributes.reserved3[2] = 0; + inAttributes.reserved3[3] = 0; + + theError = DSpFindBestContext( &inAttributes, &sys_gl.DSpContext ); + + inAttributes.displayWidth = glConfig.vidWidth; + inAttributes.displayHeight = glConfig.vidHeight; + inAttributes.backBufferDepthMask = colorBits; + inAttributes.displayDepthMask = colorBits; + inAttributes.backBufferBestDepth = colorBits; + inAttributes.displayBestDepth = colorBits; + inAttributes.pageCount = 1; + + theError = DSpContext_Reserve( sys_gl.DSpContext, &inAttributes ); + + // find out what res we actually got + theError = DSpContext_GetAttributes( sys_gl.DSpContext, &inAttributes ); + + *actualWidth = inAttributes.displayWidth; + *actualHeight = inAttributes.displayHeight; + + // put the context into the active state + theError = DSpContext_SetState( sys_gl.DSpContext, kDSpContextState_Active ); + + // fade back in + theError = DSpContext_FadeGammaIn( NULL, NULL ); + + glConfig.isFullscreen = qtrue; +} + +//======================================================================= + + +/* +=================== +GLimp_AglDescribe_f + +=================== +*/ +void GLimp_AglDescribe_f( void ) { + long value; + long r,g,b,a; + long stencil, depth; + + ri.Printf( PRINT_ALL, "Selected pixel format 0x%x\n", (int)sys_gl.fmt ); + + ri.Printf( PRINT_ALL, "TEXTURE_MEMORY: %i\n", sys_gl.textureMemory ); + ri.Printf( PRINT_ALL, "VIDEO_MEMORY: %i\n", sys_gl.videoMemory ); + + aglDescribePixelFormat(sys_gl.fmt, AGL_RED_SIZE, &r); + aglDescribePixelFormat(sys_gl.fmt, AGL_GREEN_SIZE, &g); + aglDescribePixelFormat(sys_gl.fmt, AGL_BLUE_SIZE, &b); + aglDescribePixelFormat(sys_gl.fmt, AGL_ALPHA_SIZE, &a); + aglDescribePixelFormat(sys_gl.fmt, AGL_STENCIL_SIZE, &stencil); + aglDescribePixelFormat(sys_gl.fmt, AGL_DEPTH_SIZE, &depth); + ri.Printf( PRINT_ALL, "red:%i green:%i blue:%i alpha:%i depth:%i stencil:%i\n", + r, g, b, a, depth, stencil ); + + aglDescribePixelFormat(sys_gl.fmt, AGL_BUFFER_SIZE, &value); + ri.Printf( PRINT_ALL, "BUFFER_SIZE: %i\n", value ); + + aglDescribePixelFormat(sys_gl.fmt, AGL_PIXEL_SIZE, &value); + ri.Printf( PRINT_ALL, "PIXEL_SIZE: %i\n", value ); + + aglDescribePixelFormat(sys_gl.fmt, AGL_RENDERER_ID, &value); + ri.Printf( PRINT_ALL, "RENDERER_ID: %i\n", value ); + + // memory functions + value = glmGetInteger( GLM_PAGE_SIZE ); + ri.Printf( PRINT_ALL, "GLM_PAGE_SIZE: %i\n", value ); + + value = glmGetInteger( GLM_NUMBER_PAGES ); + ri.Printf( PRINT_ALL, "GLM_NUMBER_PAGES: %i\n", value ); + + value = glmGetInteger( GLM_CURRENT_MEMORY ); + ri.Printf( PRINT_ALL, "GLM_CURRENT_MEMORY: %i\n", value ); + + value = glmGetInteger( GLM_MAXIMUM_MEMORY ); + ri.Printf( PRINT_ALL, "GLM_MAXIMUM_MEMORY: %i\n", value ); + +} + +/* +=================== +GLimp_AglState_f + +=================== +*/ +void GLimp_AglState_f( void ) { + char *cmd; + int state, value; + + if ( ri.Cmd_Argc() != 3 ) { + ri.Printf( PRINT_ALL, "Usage: aglstate <0/1>\n" ); + return; + } + + cmd = ri.Cmd_Argv( 1 ); + if ( !Q_stricmp( cmd, "rasterization" ) ) { + state = AGL_RASTERIZATION; + } else { + ri.Printf( PRINT_ALL, "Unknown agl state: %s\n", cmd ); + return; + } + + cmd = ri.Cmd_Argv( 2 ); + value = atoi( cmd ); + + if ( value ) { + aglEnable( sys_gl.context, state ); + } else { + aglDisable( sys_gl.context, state ); + } +} + +/* +=================== +GLimp_Extensions + +=================== +*/ +static void GLimp_Extensions( void ) { + const char *extensions; + + // get our config strings + Q_strncpyz( glConfig.vendor_string, (const char *)qglGetString (GL_VENDOR), sizeof( glConfig.vendor_string ) ); + Q_strncpyz( glConfig.renderer_string, (const char *)qglGetString (GL_RENDERER), sizeof( glConfig.renderer_string ) ); + Q_strncpyz( glConfig.version_string, (const char *)qglGetString (GL_VERSION), sizeof( glConfig.version_string ) ); + Q_strncpyz( glConfig.extensions_string, (const char *)qglGetString (GL_EXTENSIONS), sizeof( glConfig.extensions_string ) ); + + extensions = glConfig.extensions_string; + + // GL_ARB_multitexture + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + + if ( strstr( extensions, "GL_ARB_multitexture" ) ) { + if ( r_ext_multitexture->integer && r_allowExtensions->integer ) { + qglMultiTexCoord2fARB = glMultiTexCoord2fARB; + qglActiveTextureARB = glActiveTextureARB; + qglClientActiveTextureARB = glClientActiveTextureARB; + + ri.Printf( PRINT_ALL, "...using GL_ARB_multitexture\n" ); + } else { + ri.Printf( PRINT_ALL, "...ignoring GL_ARB_multitexture\n" ); + } + } else { + ri.Printf( PRINT_ALL, "...GL_ARB_multitexture not found\n" ); + } + + // GL_EXT_compiled_vertex_array + qglLockArraysEXT = NULL; + qglUnlockArraysEXT = NULL; + + if ( strstr( extensions, "GL_EXT_compiled_vertex_array" ) ) { + if ( r_ext_compiled_vertex_array->integer && r_allowExtensions->integer ) { + qglLockArraysEXT = glLockArraysEXT; + qglUnlockArraysEXT = glUnlockArraysEXT; + + ri.Printf( PRINT_ALL, "...using GL_EXT_compiled_vertex_array\n" ); + } else { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_compiled_vertex_array\n" ); + } + } else { + ri.Printf( PRINT_ALL, "...GL_EXT_compiled_vertex_array not found\n" ); + } + + // GL_EXT_texture_env_add + glConfig.textureEnvAddAvailable = qfalse; + if ( strstr( glConfig.extensions_string, "EXT_texture_env_add" ) ) { + if ( r_ext_texture_env_add->integer ) { + glConfig.textureEnvAddAvailable = qtrue; + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_env_add\n" ); + } else { + glConfig.textureEnvAddAvailable = qfalse; + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_env_add\n" ); + } + } else { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_env_add not found\n" ); + } + + // GL_EXT_texture_filter_anisotropic + glConfig.textureFilterAnisotropicAvailable = qfalse; + if ( strstr( glConfig.extensions_string, "EXT_texture_filter_anisotropic" ) ) + { + glConfig.textureFilterAnisotropicAvailable = qtrue; + ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic available\n" ); + + if ( r_ext_texture_filter_anisotropic->integer ) + { + ri.Printf( PRINT_ALL, "...using GL_EXT_texture_filter_anisotropic\n" ); + } + else + { + ri.Printf( PRINT_ALL, "...ignoring GL_EXT_texture_filter_anisotropic\n" ); + } + ri.Cvar_Set( "r_ext_texture_filter_anisotropic_avail", "1" ); + } + else + { + ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic not found\n" ); + ri.Cvar_Set( "r_ext_texture_filter_anisotropic_avail", "0" ); + } + + // apple transform hint + if ( strstr( extensions, "GL_APPLE_transform_hint" ) ) { + if ( r_ext_compiled_vertex_array->integer && r_allowExtensions->integer ) { + glHint( GL_TRANSFORM_HINT_APPLE, GL_FASTEST ); + ri.Printf( PRINT_ALL, "...using GL_APPLE_transform_hint\n" ); + } else { + ri.Printf( PRINT_ALL, "...ignoring GL_APPLE_transform_hint\n" ); + } + } else { + ri.Printf( PRINT_ALL, "...GL_APPLE_transform_hint not found\n" ); + } +} + +/* +============================ +GLimp_SufficientVideoMemory +============================ +*/ +#if 0 + +qboolean GLimp_SufficientVideoMemory( void ) { + AGLRendererInfo head_info, info; + GLint accelerated; + AGLDevice *device; + GLint i, ndevs; + + device = aglDevicesOfPixelFormat( sys_gl.fmt, &ndevs); + if (!device || ndevs < 1) { + ri.Printf( PRINT_ALL, "aglDevicesOfPixelFormat failed.\n" ); + return 0; + } + + ri.Printf( PRINT_ALL, "%i rendering devices\n", ndevs ); + + head_info = aglQueryRendererInfo( device, 1 ); + info = head_info; + if (!info) { + ri.Printf( PRINT_ALL, "aglQueryRendererInfo failed.\n" ); + return 0; + } + + for ( i = 0 ; i < ndevs ; i++ ) { + // ignore the software renderer listing + aglDescribeRenderer( info, AGL_ACCELERATED, &accelerated ); + if ( accelerated ) { + aglDescribeRenderer( info, AGL_TEXTURE_MEMORY, &sys_gl.textureMemory ); + aglDescribeRenderer( info, AGL_VIDEO_MEMORY, &sys_gl.videoMemory ); + } + info = aglNextRendererInfo(info); + } + + aglDestroyRendererInfo(head_info); +#if 0 + if ( sys_gl.videoMemory < 16000000 ) { + glConfig.hardwareType = GLHW_RAGEPRO; // FIXME when voodoo is available + } else { + glConfig.isRagePro = GLHW_GENERIC; // FIXME when voodoo is available + } +#endif + ri.Printf( PRINT_ALL, "%i texture memory, %i video memory\n", sys_gl.textureMemory, sys_gl.videoMemory ); + + return qtrue; +} + +#endif + +/* +======================= +CheckDeviceRenderers + +======================== +*/ +static void CheckDeviceRenderers( GDHandle device ) { + AGLRendererInfo info, head_info; + GLint inum; + GLint accelerated; + GLint textureMemory, videoMemory; + + head_info = aglQueryRendererInfo(&device, 1); + if( !head_info ) { + ri.Printf( PRINT_ALL, "aglQueryRendererInfo : Info Error\n"); + return; + } + + info = head_info; + inum = 0; + while( info ) { + ri.Printf( PRINT_ALL, " Renderer : %d\n", inum); + + aglDescribeRenderer( info, AGL_ACCELERATED, &accelerated ); + + if ( accelerated ) { + aglDescribeRenderer( info, AGL_TEXTURE_MEMORY, &textureMemory ); + aglDescribeRenderer( info, AGL_VIDEO_MEMORY, &videoMemory ); + ri.Printf( PRINT_ALL, " AGL_VIDEO_MEMORY: %i\n", textureMemory ); + ri.Printf( PRINT_ALL, " AGL_TEXTURE_MEMORY: %i\n", videoMemory ); + + // save the device with the most texture memory + if ( sys_gl.textureMemory < textureMemory ) { + sys_gl.textureMemory = textureMemory; + sys_gl.device = device; + } + } else { + ri.Printf( PRINT_ALL, " Not accelerated.\n" ); + } + + info = aglNextRendererInfo(info); + inum++; + } + + aglDestroyRendererInfo(head_info); +} + + +/* +======================= +CheckDevices + +Make sure there is a device with enough video memory to play +======================= +*/ +static void CheckDevices( void ) { + GDHandle device; + static qboolean checkedFullscreen; + + if ( checkedFullscreen ) { + return; + } + if ( glConfig.isFullscreen ) { + checkedFullscreen = qtrue; + } + + device = GetDeviceList(); + sys_gl.numDevices = 0; + while( device && sys_gl.numDevices < MAX_DEVICES ) { + sys_gl.devices[ sys_gl.numDevices ] = device; + + ri.Printf( PRINT_ALL, "Device : %d\n", sys_gl.numDevices); + CheckDeviceRenderers(device); + + device = GetNextDevice(device); + + sys_gl.numDevices++; + } + + CheckErrors(); + + if ( sys_gl.textureMemory < 4000000 ) { + ri.Error( ERR_FATAL, "You must have at least four megs of video memory to play" ); + } + + if ( sys_gl.textureMemory < 16000000 ) { + sys_hardwareType = GLHW_RAGEPRO; // this will have to change with voodoo + } else { + sys_hardwareType = GLHW_GENERIC; + } +} + +/* +================= +CreateGameWindow +================= +*/ +static qboolean CreateGameWindow( void ) { + cvar_t *vid_xpos; + cvar_t *vid_ypos; + int mode; + int x, y; + Str255 pstr; + + + vid_xpos = ri.Cvar_Get( "vid_xpos", "30", 0 ); + vid_ypos = ri.Cvar_Get( "vid_ypos", "30", 0 ); + + // get mode info + mode = r_mode->integer; + ri.Printf( PRINT_ALL, "...setting mode %d:", mode ); + + if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, &glConfig.windowAspect, mode ) ) { + ri.Printf( PRINT_ALL, " invalid mode\n" ); + return false; + } + ri.Printf( PRINT_ALL, " %d %d\n", glConfig.vidWidth, glConfig.vidHeight ); + + /* Create window */ + if ( r_fullscreen->integer ) { + int actualWidth, actualHeight; + + // change display resolution + GLimp_ChangeDisplay( &actualWidth, &actualHeight ); + + x = ( actualWidth - glConfig.vidWidth ) / 2; + y = ( actualHeight - glConfig.vidHeight ) / 2; + sys_gl.drawable = (AGLDrawable) GetNewCWindow(kFullScreenWindow,nil,(WindowPtr)-1L); + } else { + x = vid_xpos->integer; + y = vid_ypos->integer; + sys_gl.drawable = (AGLDrawable) GetNewCWindow(kMainWindow,nil,(WindowPtr)-1L); + } + if( !sys_gl.drawable ) { + return qfalse; + } + + SizeWindow((GrafPort *) sys_gl.drawable, glConfig.vidWidth, glConfig.vidHeight,GL_FALSE); + MoveWindow((GrafPort *) sys_gl.drawable,x, y, GL_FALSE); + ShowWindow((GrafPort *) sys_gl.drawable); + SetPort((GrafPort *) sys_gl.drawable); + CToPStr("Quake3: Arena", pstr); + SetWTitle((GrafPort *) sys_gl.drawable, pstr); + HiliteWindow((GrafPort *) sys_gl.drawable, 1); + + return qtrue; +} + + +/* +=================== +GLimp_SetMode + +Returns false if the mode / fullscrenn / options combination failed, +so another fallback can be tried +=================== +*/ +qboolean GLimp_SetMode( void ) { + GLint value; + GLint attrib[64]; + int i; + + if ( !CreateGameWindow() ) { + ri.Printf( PRINT_ALL, "GLimp_Init: window could not be created" ); + return qfalse; + } + + // check devices now that the game has set the display mode, + // because RAVE devices don't get reported if in an 8 bit desktop + CheckDevices(); + + // set up the attribute list + i = 0; + attrib[i++] = AGL_RGBA; + attrib[i++] = AGL_DOUBLEBUFFER; + attrib[i++] = AGL_NO_RECOVERY; + attrib[i++] = AGL_ACCELERATED; + + if ( r_colorbits->integer >= 16 ) { + attrib[i++] = AGL_RED_SIZE; + attrib[i++] = 8; + attrib[i++] = AGL_GREEN_SIZE; + attrib[i++] = 8; + attrib[i++] = AGL_BLUE_SIZE; + attrib[i++] = 8; + attrib[i++] = AGL_ALPHA_SIZE; + attrib[i++] = 0; + } else { + attrib[i++] = AGL_RED_SIZE; + attrib[i++] = 5; + attrib[i++] = AGL_GREEN_SIZE; + attrib[i++] = 5; + attrib[i++] = AGL_BLUE_SIZE; + attrib[i++] = 5; + attrib[i++] = AGL_ALPHA_SIZE; + attrib[i++] = 0; + } + + attrib[i++] = AGL_STENCIL_SIZE; + if ( r_stencilbits->integer ) { + attrib[i++] = r_stencilbits->integer; + } else { + attrib[i++] = 0; + } + + attrib[i++] = AGL_DEPTH_SIZE; + if ( r_depthbits->integer ) { + attrib[i++] = r_depthbits->integer; + } else { + attrib[i++] = 16; + } + + attrib[i++] = 0; + + /* Choose pixel format */ + ri.Printf( PRINT_ALL, "aglChoosePixelFormat\n" ); + if ( r_device->integer < 0 || r_device->integer >= sys_gl.numDevices ) { + ri.Cvar_Set( "r_device", "0" ); + } + sys_gl.fmt = aglChoosePixelFormat( &sys_gl.devices[ r_device->integer ], 1, attrib); + if(!sys_gl.fmt) { + ri.Printf( PRINT_ALL, "GLimp_Init: Pixel format could not be achieved\n"); + return qfalse; + } + ri.Printf( PRINT_ALL, "Selected pixel format 0x%x\n", (int)sys_gl.fmt ); + + aglDescribePixelFormat(sys_gl.fmt, AGL_RED_SIZE, &value); + glConfig.colorBits = value * 3; + aglDescribePixelFormat(sys_gl.fmt, AGL_STENCIL_SIZE, &value); + glConfig.stencilBits = value; + aglDescribePixelFormat(sys_gl.fmt, AGL_DEPTH_SIZE, &value); + glConfig.depthBits = value; + + CheckErrors(); + + /* Create context */ + sys_gl.context = aglCreateContext(sys_gl.fmt, NULL); + if(!sys_gl.context) { + ri.Printf( PRINT_ALL, "GLimp_init: Context could not be created\n"); + return qfalse; + } + + CheckErrors(); + + /* Make context current */ + + if(!aglSetDrawable(sys_gl.context, sys_gl.drawable)) { + ri.Printf( PRINT_ALL, "GLimp_Init: Could not attach to context\n" ); + return qfalse; + } + + CheckErrors(); + + if( !aglSetCurrentContext(sys_gl.context) ) { + ri.Printf( PRINT_ALL, "GLimp_Init: Could not attach to context"); + return qfalse; + } + + CheckErrors(); + + // check video memory and determine ragePro status +#if 0 + if ( !GLimp_SufficientVideoMemory() ) { + return qfalse; + } +#endif + glConfig.hardwareType = sys_hardwareType; // FIXME: this isn't really right + + // draw something to show that GL is alive + qglClearColor( 1, 0.5, 0.2, 0 ); + qglClear( GL_COLOR_BUFFER_BIT ); + GLimp_EndFrame(); + + CheckErrors(); + + // get the extensions + GLimp_Extensions(); + + CheckErrors(); + + return qtrue; +} + + + +/* +=================== +GLimp_Init + +Don't return unless OpenGL has been properly initialized +=================== +*/ +void GLimp_Init( void ) { + GLint major, minor; + static qboolean registered; + + ri.Printf( PRINT_ALL, "--- GLimp_Init ---\n" ); + + aglGetVersion( &major, &minor ); + ri.Printf( PRINT_ALL, "aglVersion: %i.%i\n", (int)major, (int)minor ); + + r_device = ri.Cvar_Get( "r_device", "0", CVAR_LATCH | CVAR_ARCHIVE ); + r_ext_transform_hint = ri.Cvar_Get( "r_ext_transform_hint", "1", CVAR_LATCH | CVAR_ARCHIVE ); + + if ( !registered ) { + ri.Cmd_AddCommand( "aglDescribe", GLimp_AglDescribe_f ); + ri.Cmd_AddCommand( "aglState", GLimp_AglState_f ); + } + + memset( &glConfig, 0, sizeof( glConfig ) ); + + + r_swapInterval->modified = qtrue; // force a set next frame + + + glConfig.deviceSupportsGamma = qtrue; + + // FIXME: try for a voodoo first + sys_gl.systemGammas = GetSystemGammas(); + + if ( GLimp_SetMode() ) { + ri.Printf( PRINT_ALL, "------------------\n" ); + return; + } + + // fall back to the known-good mode + ri.Cvar_Set( "r_fullscreen", "1" ); + ri.Cvar_Set( "r_mode", "3" ); + ri.Cvar_Set( "r_stereo", "0" ); + ri.Cvar_Set( "r_depthBits", "16" ); + ri.Cvar_Set( "r_colorBits", "16" ); + ri.Cvar_Set( "r_stencilBits", "0" ); + if ( GLimp_SetMode() ) { + ri.Printf( PRINT_ALL, "------------------\n" ); + return; + } + + ri.Error( ERR_FATAL, "Could not initialize OpenGL" ); +} + + +/* +=============== +GLimp_EndFrame + +=============== +*/ +void GLimp_EndFrame( void ) { + // check for variable changes + if ( r_swapInterval->modified ) { + r_swapInterval->modified = qfalse; + ri.Printf( PRINT_ALL, "Changing AGL_SWAP_INTERVAL\n" ); + aglSetInteger( sys_gl.context, AGL_SWAP_INTERVAL, (long *)&r_swapInterval->integer ); + } + + // make sure the event loop is pumped + Sys_SendKeyEvents(); + + aglSwapBuffers( sys_gl.context ); +} + +/* +=============== +GLimp_Shutdown + +=============== +*/ +void GLimp_Shutdown( void ) { + if ( sys_gl.systemGammas ) { + RestoreSystemGammas( sys_gl.systemGammas ); + DisposeSystemGammas( &sys_gl.systemGammas ); + sys_gl.systemGammas = 0; + } + + if ( sys_gl.context ) { + aglDestroyContext(sys_gl.context); + sys_gl.context = 0; + } + if ( sys_gl.fmt ) { + aglDestroyPixelFormat(sys_gl.fmt); + sys_gl.fmt = 0; + } + if ( sys_gl.drawable ) { + DisposeWindow((GrafPort *) sys_gl.drawable); + sys_gl.drawable = 0; + } + GLimp_ResetDisplay(); + + memset( &glConfig, 0, sizeof( glConfig ) ); +} + + +void GLimp_LogComment( char *comment ) { +} +qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) { +} +void *GLimp_RendererSleep( void ) { + +} + +void GLimp_FrontEndSleep( void ) { + +} + +void GLimp_WakeRenderer( void * data ) { + +} + + + +/* +=============== +GLimp_SetGamma + +=============== +*/ +void GLimp_SetGamma( unsigned char red[256], + unsigned char green[256], + unsigned char blue[256] ) { + char color[3][256]; + int i; + + for ( i = 0 ; i < 256 ; i++ ) { + color[0][i] = red[i]; + color[1][i] = green[i]; + color[2][i] = blue[i]; + } + SetDeviceGammaRampGD( sys_gl.device, color[0] ); +} + diff --git a/code/mac/mac_input.c b/code/mac/mac_input.c new file mode 100644 index 0000000..d0fe62a --- /dev/null +++ b/code/mac/mac_input.c @@ -0,0 +1,212 @@ + +#include "../client/client.h" +#include "mac_local.h" +#include "InputSprocket.h" + +qboolean inputActive; +qboolean inputSuspended; + +#define MAX_DEVICES 100 +ISpDeviceReference devices[MAX_DEVICES]; +ISpElementListReference elementList; + +#define MAX_ELEMENTS 512 +#define MAX_MOUSE_DEVICES 2 +UInt32 numDevices; +UInt32 numElements[MAX_MOUSE_DEVICES]; +ISpElementReference elements[MAX_MOUSE_DEVICES][MAX_ELEMENTS]; + +cvar_t *in_nomouse; + +void Input_Init(void); +void Input_GetState( void ); + +/* +================= +Sys_InitInput +================= +*/ +void Sys_InitInput( void ) { + NumVersion ver; + ISpElementInfo info; + int i, j; + OSStatus err; + + // no input with dedicated servers + if ( com_dedicated->integer ) { + return; + } + + Com_Printf( "------- Input Initialization -------\n" ); + in_nomouse = Cvar_Get( "in_nomouse", "0", 0 ); + if ( in_nomouse->integer != 0 ) { + Com_Printf( "in_nomouse is set, skipping.\n" ); + Com_Printf( "------------------------------------\n" ); + return; + } + + ver = ISpGetVersion(); + Com_Printf( "InputSprocket version: 0x%x\n", ver ); + + err = ISpStartup(); + if ( err ) { + Com_Printf( "ISpStartup failed: %i\n", err ); + Com_Printf( "------------------------------------\n" ); + return; + } + + // disable everything + ISpDevices_Extract( MAX_DEVICES, &numDevices, devices ); + Com_Printf("%i total devices\n", numDevices); + if (numDevices > MAX_DEVICES) { + numDevices = MAX_DEVICES; + } + err = ISpDevices_Deactivate( + numDevices, + devices); + + // enable mouse + err = ISpDevices_ExtractByClass( + kISpDeviceClass_Mouse, + MAX_DEVICES, + &numDevices, + devices); + Com_Printf("%i mouse devices\n", numDevices); + if (numDevices > MAX_MOUSE_DEVICES) { + numDevices = MAX_MOUSE_DEVICES; + } + + err = ISpDevices_Activate( numDevices, devices); + for ( i = 0 ; i < numDevices ; i++ ) { + ISpDevice_GetElementList( devices[i], &elementList ); + +// ISpGetGlobalElementList( &elementList ); + + // go through all the elements and asign them Quake key codes + ISpElementList_Extract( elementList, MAX_ELEMENTS, &numElements[i], elements[i] ); + Com_Printf("%i elements in list\n", numElements[i] ); + + for ( j = 0 ; j < numElements[i] ; j++ ) { + ISpElement_GetInfo( elements[i][j], &info ); + PStringToCString( (char *)info.theString ); + Com_Printf( "%i : %s\n", i, info.theString ); + } + } + + inputActive = true; + + HideCursor(); + + Com_Printf( "------------------------------------\n" ); +} + +/* +================= +Sys_ShutdownInput +================= +*/ +void Sys_ShutdownInput( void ) { + if ( !inputActive ) { + return; + } + ShowCursor(); + ISpShutdown(); + inputActive = qfalse; +} + +void Sys_SuspendInput( void ) { + if ( inputSuspended ) { + return; + } + inputSuspended = true; + ShowCursor(); + ISpSuspend(); +} + +void Sys_ResumeInput( void ) { + if ( !inputSuspended ) { + return; + } + inputSuspended = false; + HideCursor(); + ISpResume(); +} + +/* +================= +Sys_Input +================= +*/ +void Sys_Input( void ) { + ISpElementEvent event; + Boolean wasEvent; + UInt32 state, state2; + int xmove, ymove; + int button; + static int xtotal, ytotal; + int device; + + if ( !inputActive ) { + return; + } + + // during debugging it is sometimes usefull to be able to kill mouse support + if ( in_nomouse->integer ) { + Com_Printf( "Shutting down input.\n"); + Sys_ShutdownInput(); + return; + } + + // always suspend for dedicated + if ( com_dedicated->integer ) { + Sys_SuspendInput(); + return; + } + + // temporarily deactivate if not in the game and + if ( cls.keyCatchers || cls.state != CA_ACTIVE ) { + if ( !glConfig.isFullscreen ) { + Sys_SuspendInput(); + return; + } + } + + Sys_ResumeInput(); + + // send all button events + for ( device = 0 ; device < numDevices ; device++ ) { + // mouse buttons + + for ( button = 2 ; button < numElements[device] ; button++ ) { + while ( 1 ) { + ISpElement_GetNextEvent( elements[device][button], sizeof( event ), &event, &wasEvent ); + if ( !wasEvent ) { + break; + } + if ( event.data ) { + Sys_QueEvent( 0, SE_KEY, K_MOUSE1 + button - 2, 1, 0, NULL ); + } else { + Sys_QueEvent( 0, SE_KEY, K_MOUSE1 + button - 2, 0, 0, NULL ); + } + } + } + + // mouse movement + +#define MAC_MOUSE_SCALE 163 // why this constant? + // send mouse event + ISpElement_GetSimpleState( elements[device][0], &state ); + xmove = (int)state / MAC_MOUSE_SCALE; + + ISpElement_GetSimpleState( elements[device][1], &state2 ); + ymove = (int)state2 / -MAC_MOUSE_SCALE; + + if ( xmove || ymove ) { + xtotal += xmove; + ytotal += ymove; + //Com_Printf("%i %i = %i %i\n", state, state2, xtotal, ytotal ); + Sys_QueEvent( 0, SE_MOUSE, xmove, ymove, 0, NULL ); + } + } + +} diff --git a/code/mac/mac_local.h b/code/mac/mac_local.h new file mode 100644 index 0000000..dbfacf2 --- /dev/null +++ b/code/mac/mac_local.h @@ -0,0 +1,321 @@ + +/* + * Apple Universal Headers 3.1 + * + * Uncomment any additional #includes you want to add to your MacHeaders. + */ +//#include + + +// #include +// #include + #include + #include + #include + #include + #include +// #include + #include + #include + #include + #include + #include + #include + #include + #include +// #include +// #include + #include +// #include +// #include + #include +// #include +// #include +// #include + #include +// #include +// #include +// #include + #include +// #include + #include +// #include +// #include + #include + #include +// #include +// #include + #include +// #include +// #include +// #include +// #include +// #include + #include +// #include +// #include + #include + #include +// #include +// #include + #include +// #include + #include + #include +// #include +// #include + #include +// #include +// #include +// #include +// #include +// #include +// #include + #include + #include + #include +// #include + #include +// #include +// #include + #include + #include + #include + #include + #include + #include +// #include +// #include +// #include + #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + #include +// #include + #include +// #include + #include +// #include +// #include + #include +// #include +// #include +// #include + #include +// #include +// #include + #include +// #include + #include +// #include + #include + #include +// #include +// #include + #include // Start using MacMemory.h + #include +// #include + #include + #include +// #include +// #include +// #include + #include +// #include + #include + #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + #include + #include + #include + #include +// #include + #include + #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + #include +// #include + #include + #include + #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + #include +// #include +// #include + #include + #include +// #include +// #include +// #include +// #include +// #include + #include +// #include +// #include +// #include +// #include + #include + #include +// #include + #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + #include +// #include +// #include +// #include +// #include +// #include +// #include + #include + #include +// #include + #include // Start using TextUtils.h +// #include +// #include +// #include + #include + #include +// #include +// #include + #include + #include + #include + #include +// #include +// #include + #include +// #include + #include // Start using MacTypes.h +// #include +// #include + #include +// #include + #include // Start using MacWindows.h +// #include +// #include + + + + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Menus: */ + #define rMenuBar 128 + /* Apple menu: */ + #define mApple 128 + #define iAbout 1 + /* File menu: */ + #define mFile 129 + #define iQuit 1 + /* Edit menu: */ + #define mEdit 130 + #define iUndo 1 + #define iCut 3 + #define iCopy 4 + #define iPaste 5 + #define iClear 6 +/* Windows: */ + #define kMainWindow 128 + #define kFullScreenWindow 129 +/* Dilogs: */ + #define kAboutDialog 128 + + +// mac_main.c +extern int sys_ticBase; +extern int sys_msecBase; +extern int sys_lastEventTic; + +extern cvar_t *sys_waitNextEvent; + +void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ); +int PStringToCString( char *s ); +int CStringToPString( char *s ); + +// mac_event.c +extern int vkeyToQuakeKey[256]; +void Sys_SendKeyEvents (void); + +// mac_net.c +void Sys_InitNetworking( void ); +void Sys_ShutdownNetworking( void ); + +// mac_input.c +void Sys_InitInput( void ); +void Sys_ShutdownInput( void ); +void Sys_Input( void ); + +extern qboolean inputActive; + +// mac_glimp.c +extern glconfig_t glConfig; + +// mac_console.c + +void Sys_InitConsole( void ); +void Sys_ShowConsole( int level, qboolean quitOnClose ); +void Sys_Print( const char *text ); +char *Sys_ConsoleInput( void ); +qboolean Sys_ConsoleEvent( EventRecord *event ); + + diff --git a/code/mac/mac_main.c b/code/mac/mac_main.c new file mode 100644 index 0000000..b532326 --- /dev/null +++ b/code/mac/mac_main.c @@ -0,0 +1,693 @@ +#include "../client/client.h" +#include "mac_local.h" +#include +#include + +/* + +TODO: + +about box +dir with no extension gives strange results +console input? +dedicated servers +icons +dynamic loading of server game +clipboard pasting + +quit from menu + +*/ + +int sys_ticBase; +int sys_msecBase; +int sys_lastEventTic; + +cvar_t *sys_profile; +cvar_t *sys_waitNextEvent; + +void putenv( char *buffer ) { + // the mac doesn't seem to have the concept of environment vars, so nop this +} + +//=========================================================================== + +void Sys_UnloadGame (void) { +} +void *Sys_GetGameAPI (void *parms) { + void *GetGameAPI (void *import); + // we are hard-linked in, so no need to load anything + return GetGameAPI (parms); +} + +void Sys_UnloadUI (void) { +} +void *Sys_GetUIAPI (void) { + void *GetUIAPI (void); + // we are hard-linked in, so no need to load anything + return GetUIAPI (); +} + +void Sys_UnloadBotLib( void ) { +} + +void *Sys_GetBotLibAPI (void *parms) { + return NULL; +} + +void *Sys_GetBotAIAPI (void *parms) { + return NULL; +} + +void dllEntry( int (*syscallptr)( int arg,... ) ); +int vmMain( int command, ... ); + +void *Sys_LoadDll( const char *name, int (**entryPoint)(int, ...), + int (*systemCalls)(int, ...) ) { + + dllEntry( systemCalls ); + + *entryPoint = vmMain; + + return (void *)1; +} + +void Sys_UnloadDll( void *dllHandle ) { +} + +//=========================================================================== + +char *Sys_GetClipboardData( void ) { // FIXME + return NULL; +} + +int Sys_GetProcessorId( void ) { + return CPUID_GENERIC; +} + +void Sys_Mkdir( const char *path ) { + char ospath[MAX_OSPATH]; + int err; + + Com_sprintf( ospath, sizeof(ospath), "%s:", path ); + + err = mkdir( ospath, 0777 ); +} + +char *Sys_Cwd( void ) { + static char dir[MAX_OSPATH]; + int l; + + getcwd( dir, sizeof( dir ) ); + dir[MAX_OSPATH-1] = 0; + + // strip off the last colon + l = strlen( dir ); + if ( l > 0 ) { + dir[ l - 1 ] = 0; + } + return dir; +} + +char *Sys_DefaultCDPath( void ) { + return ""; +} + +char *Sys_DefaultBasePath( void ) { + return Sys_Cwd(); +} + +/* + ================================================================================= + + FILE FINDING + + ================================================================================= +*/ + +int PStringToCString( char *s ) { + int l; + int i; + + l = ((unsigned char *)s)[0]; + for ( i = 0 ; i < l ; i++ ) { + s[i] = s[i+1]; + } + s[l] = 0; + return l; +} + + +int CStringToPString( char *s ) { + int l; + int i; + + l = strlen( s ); + for ( i = 0 ; i < l ; i++ ) { + s[l-i] = s[l-i-1]; + } + s[0] = l; + return l; +} + +#define MAX_FOUND_FILES 0x1000 + +char **Sys_ListFiles( const char *directory, const char *extension, int *numfiles, qboolean wantsubs ) { + int nfiles; + char **listCopy; + char pdirectory[MAX_OSPATH]; + char *list[MAX_FOUND_FILES]; + int findhandle; + int directoryFlag; + int i; + int extensionLength; + int VRefNum; + int DrDirId; + int index; + FSSpec fsspec; + + // get the volume and directory numbers + // there has to be a better way than this... + { + CInfoPBRec paramBlock; + + Q_strncpyz( pdirectory, directory, sizeof(pdirectory) ); + CStringToPString( pdirectory ); + FSMakeFSSpec( 0, 0, (unsigned char *)pdirectory, &fsspec ); + + VRefNum = fsspec.vRefNum; + + memset( ¶mBlock, 0, sizeof( paramBlock ) ); + paramBlock.hFileInfo.ioNamePtr = (unsigned char *)pdirectory; + PBGetCatInfoSync( ¶mBlock ); + + DrDirId = paramBlock.hFileInfo.ioDirID; + } + + if ( !extension) { + extension = ""; + } + extensionLength = strlen( extension ); + + if ( wantsubs || (extension[0] == '/' && extension[1] == 0) ) { + directoryFlag = 16; + } else { + directoryFlag = 0; + } + + nfiles = 0; + + for ( index = 1 ; ; index++ ) { + CInfoPBRec paramBlock; + char fileName[MAX_OSPATH]; + int length; + OSErr err; + + memset( ¶mBlock, 0, sizeof( paramBlock ) ); + paramBlock.hFileInfo.ioNamePtr = (unsigned char *)fileName; + paramBlock.hFileInfo.ioVRefNum = VRefNum; + paramBlock.hFileInfo.ioFDirIndex = index; + paramBlock.hFileInfo.ioDirID = DrDirId; + + err = PBGetCatInfoSync( ¶mBlock ); + + if ( err != noErr ) { + break; + } + + if ( directoryFlag ^ ( paramBlock.hFileInfo.ioFlAttrib & 16 ) ) { + continue; + } + + // convert filename to C string + length = PStringToCString( fileName ); + + // check the extension + if ( !directoryFlag ) { + if ( length < extensionLength ) { + continue; + } + if ( Q_stricmp( fileName + length - extensionLength, extension ) ) { + continue; + } + } + + // add this file + if ( nfiles == MAX_FOUND_FILES - 1 ) { + break; + } + list[ nfiles ] = CopyString( fileName ); + nfiles++; + } + + list[ nfiles ] = 0; + + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ) ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + +void Sys_FreeFileList( char **list ) { + int i; + + if ( !list ) { + return; + } + + for ( i = 0 ; list[i] ; i++ ) { + Z_Free( list[i] ); + } + + Z_Free( list ); +} + + +//=================================================================== + + +/* +================ +Sys_Init + +The cvar and file system has been setup, so configurations are loaded +================ +*/ +void Sys_Init(void) { + Sys_InitNetworking(); + Sys_InitInput(); +} + +/* +================= +Sys_Shutdown +================= +*/ +void Sys_Shutdown( void ) { + Sys_EndProfiling(); + Sys_ShutdownInput(); + Sys_ShutdownNetworking(); +} + + +/* +================= +Sys_BeginProfiling +================= +*/ +static qboolean sys_profiling; +void Sys_BeginProfiling( void ) { + if ( !sys_profile->integer ) { + return; + } + ProfilerInit(collectDetailed, bestTimeBase, 16384, 64); + sys_profiling = qtrue; +} + +/* +================= +Sys_EndProfiling +================= +*/ +void Sys_EndProfiling( void ) { + unsigned char pstring[1024]; + + if ( !sys_profiling ) { + return; + } + sys_profiling = qfalse; + + sprintf( (char *)pstring + 1, "%s:profile.txt", Cvar_VariableString( "fs_basepath" ) ); + pstring[0] = strlen( (char *)pstring + 1 ); + ProfilerDump( pstring ); + ProfilerTerm(); +} + +//================================================================================ + + +/* +================ +Sys_Milliseconds +================ +*/ +int Sys_Milliseconds (void) { +#if 0 + int c; + + c = clock(); // FIXME, make more accurate + + return c*1000/60; +#else + AbsoluteTime t; + Nanoseconds nano; + double doub; + +#define kTwoPower32 (4294967296.0) /* 2^32 */ + + t = UpTime(); + nano = AbsoluteToNanoseconds( t ); + doub = (((double) nano.hi) * kTwoPower32) + nano.lo; + + return doub * 0.000001; +#endif +} + +/* +================ +Sys_Error +================ +*/ +void Sys_Error( const char *error, ... ) { + va_list argptr; + char string[1024]; + char string2[1024]; + + Sys_Shutdown(); + + va_start (argptr,error); + vsprintf (string2+1,error,argptr); + va_end (argptr); + string2[0] = strlen( string2 + 1 ); + + strcpy( string+1, "Quake 3 Error:" ); + string[0] = strlen( string + 1 ); + + // set the dialog box strings + ParamText( (unsigned char *)string, (unsigned char *)string2, + (unsigned char *)string2, (unsigned char *)string2 ); + + // run a dialog + StopAlert( 128, NULL ); + + exit(0); +} + + +/* +================ +Sys_Quit +================ +*/ +void Sys_Quit( void ) { + Sys_Shutdown(); + exit (0); +} + + +//=================================================================== + +void Sys_BeginStreamedFile( fileHandle_t f, int readAhead ) { +} + +void Sys_EndStreamedFile( fileHandle_t f ) { +} + +int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ) { + return FS_Read( buffer, size, count, f ); +} + +void Sys_StreamSeek( fileHandle_t f, int offset, int origin ) { + FS_Seek( f, offset, origin ); +} + +//================================================================================= + + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +qboolean Sys_GetPacket ( netadr_t *net_from, msg_t *net_message ); + +#define MAX_QUED_EVENTS 256 +#define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 ) + +sysEvent_t eventQue[MAX_QUED_EVENTS]; +int eventHead, eventTail; +byte sys_packetReceived[MAX_MSGLEN]; + +/* +================ +Sys_QueEvent + +A time of 0 will get the current time +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) { + sysEvent_t *ev; + + ev = &eventQue[ eventHead & MASK_QUED_EVENTS ]; + if ( eventHead - eventTail >= MAX_QUED_EVENTS ) { + Com_Printf("Sys_QueEvent: overflow\n"); + // we are discarding an event, but don't leak memory + if ( ev->evPtr ) { + free( ev->evPtr ); + } + eventTail++; + } + eventHead++; + + if ( time == 0 ) { + time = Sys_Milliseconds(); + } + + ev->evTime = time; + ev->evType = type; + ev->evValue = value; + ev->evValue2 = value2; + ev->evPtrLength = ptrLength; + ev->evPtr = ptr; +} + +/* +================= +Sys_PumpEvents +================= +*/ +void Sys_PumpEvents( void ) { + char *s; + msg_t netmsg; + netadr_t adr; + + // pump the message loop + Sys_SendKeyEvents(); + + // check for console commands + s = Sys_ConsoleInput(); + if ( s ) { + char *b; + int len; + + len = strlen( s ) + 1; + b = malloc( len ); + if ( !b ) { + Com_Error( ERR_FATAL, "malloc failed in Sys_PumpEvents" ); + } + strcpy( b, s ); + Sys_QueEvent( 0, SE_CONSOLE, 0, 0, len, b ); + } + + // check for other input devices + Sys_Input(); + + // check for network packets + MSG_Init( &netmsg, sys_packetReceived, sizeof( sys_packetReceived ) ); + if ( Sys_GetPacket ( &adr, &netmsg ) ) { + netadr_t *buf; + int len; + + // copy out to a seperate buffer for qeueing + len = sizeof( netadr_t ) + netmsg.cursize; + buf = malloc( len ); + if ( !buf ) { + Com_Error( ERR_FATAL, "malloc failed in Sys_PumpEvents" ); + } + *buf = adr; + memcpy( buf+1, netmsg.data, netmsg.cursize ); + Sys_QueEvent( 0, SE_PACKET, 0, 0, len, buf ); + } +} + +/* +================ +Sys_GetEvent + +================ +*/ +sysEvent_t Sys_GetEvent( void ) { + sysEvent_t ev; + + if ( eventHead == eventTail ) { + Sys_PumpEvents(); + } + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } + + // create an empty event to return + memset( &ev, 0, sizeof( ev ) ); + ev.evTime = Sys_Milliseconds(); + + // track the mac event "when" to milliseconds rate + sys_ticBase = sys_lastEventTic; + sys_msecBase = ev.evTime; + + return ev; +} + + +/* +============= +InitMacStuff +============= +*/ +void InitMacStuff( void ) { + Handle menuBar; + char dir[MAX_OSPATH]; + + // init toolbox + MaxApplZone(); + MoreMasters(); + + InitGraf(&qd.thePort); + InitFonts(); + FlushEvents(everyEvent, 0); + SetEventMask( -1 ); + InitWindows(); + InitMenus(); + TEInit(); + InitDialogs(nil); + InitCursor(); + + // init menu + menuBar = GetNewMBar(rMenuBar); + if(!menuBar) { + Com_Error( ERR_FATAL, "MenuBar not found."); + } + + SetMenuBar(menuBar); + DisposeHandle(menuBar); + AppendResMenu(GetMenuHandle(mApple),'DRVR'); + DrawMenuBar(); + + Sys_InitConsole(); + + SetEventMask( -1 ); +} + +//================================================================================== + +/* +============= +ReadCommandLineParms + +Read startup options from a text file or dialog box +============= +*/ +char *ReadCommandLineParms( void ) { + FILE *f; + int len; + char *buf; + EventRecord event; + + // flush out all the events and see if shift is held down + // to bring up the args window + while ( WaitNextEvent(everyEvent, &event, 0, nil) ) { + } + if ( event.modifiers & 512 ) { + static char text[1024]; + int argc; + char **argv; + int i; + + argc = ccommand( &argv ); + text[0] = 0; + // concat all the args into a string + // quote each arg seperately, because metrowerks does + // its own quote combining from the dialog + for ( i = 1 ; i < argc ; i++ ) { + if ( argv[i][0] != '+' ) { + Q_strcat( text, sizeof(text), "\"" ); + } + Q_strcat( text, sizeof(text), argv[i] ); + if ( argv[i][0] != '+' ) { + Q_strcat( text, sizeof(text), "\"" ); + } + Q_strcat( text, sizeof(text), " " ); + } + return text; + } + + // otherwise check for a parms file + f = fopen( "MacQuake3Parms.txt", "r" ); + if ( !f ) { + return ""; + } + len = FS_filelength( f ); + buf = malloc( len + 1 ); + if ( !buf ) { + exit( 1 ); + } + buf[len] = 0; + fread( buf, len, 1, f ); + fclose( f ); + + return buf; +} + +/* +============= +main +============= +*/ +void main( void ) { + char *commandLine; + + InitMacStuff(); + + commandLine = ReadCommandLineParms( ); + + Com_Init ( commandLine ); + + sys_profile = Cvar_Get( "sys_profile", "0", 0 ); + sys_profile->modified = qfalse; + + sys_waitNextEvent = Cvar_Get( "sys_waitNextEvent", "0", 0 ); + + while( 1 ) { + // run the frame + Com_Frame(); + + if ( sys_profile->modified ) { + sys_profile->modified = qfalse; + if ( sys_profile->integer ) { + Com_Printf( "Beginning profile.\n" ); + Sys_BeginProfiling() ; + } else { + Com_Printf( "Ending profile.\n" ); + Sys_EndProfiling(); + } + } + } +} + diff --git a/code/mac/mac_net.c b/code/mac/mac_net.c new file mode 100644 index 0000000..d99c165 --- /dev/null +++ b/code/mac/mac_net.c @@ -0,0 +1,527 @@ +#include "../client/client.h" +#include "mac_local.h" +#include +#include + +static qboolean gOTInited; +static EndpointRef endpoint = kOTInvalidEndpointRef; +static EndpointRef resolverEndpoint = kOTInvalidEndpointRef; + +#define MAX_IPS 16 +static int numIP; +static InetInterfaceInfo sys_inetInfo[MAX_IPS]; + +static TUDErr uderr; + +void RcvUDErr( void ) { + memset( &uderr, 0, sizeof( uderr ) ); + uderr.addr.maxlen = 0; + uderr.opt.maxlen = 0; + OTRcvUDErr( endpoint, &uderr ); +} + +void HandleOTError( int err, const char *func ) { + int r; + static int lastErr; + + if ( err != lastErr ) { + Com_Printf( "%s: error %i\n", func, err ); + } + + // if we don't call OTLook, things wedge + r = OTLook( endpoint ); + if ( err != lastErr ) { + Com_DPrintf( "%s: OTLook %i\n", func, r ); + } + + switch( r ) { + case T_UDERR: + RcvUDErr(); + if ( err != lastErr ) { + Com_DPrintf( "%s: OTRcvUDErr %i\n", func, uderr.error ); + } + break; + default: +// Com_Printf( "%s: Unknown OTLook error %i\n", func, r ); + break; + } + lastErr = err; // don't spew tons of messages +} + +/* +================= +NotifyProc +================= +*/ +pascal void NotifyProc(void* contextPtr, OTEventCode code, + OTResult result, void* cookie) { + switch( code ) { + case T_OPENCOMPLETE: + endpoint = cookie; + break; + case T_UDERR: + RcvUDErr(); + break; + } +} + + +/* +================= +GetFourByteOption +================= +*/ +static OTResult GetFourByteOption(EndpointRef ep, + OTXTILevel level, + OTXTIName name, + UInt32 *value) +{ + OTResult err; + TOption option; + TOptMgmt request; + TOptMgmt result; + + /* Set up the option buffer */ + option.len = kOTFourByteOptionSize; + option.level= level; + option.name = name; + option.status = 0; + option.value[0] = 0;// Ignored because we're getting the value. + + /* Set up the request parameter for OTOptionManagement to point + to the option buffer we just filled out */ + + request.opt.buf= (UInt8 *) &option; + request.opt.len= sizeof(option); + request.flags= T_CURRENT; + + /* Set up the reply parameter for OTOptionManagement. */ + result.opt.buf = (UInt8 *) &option; + result.opt.maxlen = sizeof(option); + + err = OTOptionManagement(ep, &request, &result); + + if (err == noErr) { + switch (option.status) + { + case T_SUCCESS: + case T_READONLY: + *value = option.value[0]; + break; + default: + err = option.status; + break; + } + } + + return (err); +} + + +/* +================= +SetFourByteOption +================= +*/ +static OTResult SetFourByteOption(EndpointRef ep, + OTXTILevel level, + OTXTIName name, + UInt32 value) +{ + OTResult err; + TOption option; + TOptMgmt request; + TOptMgmt result; + + /* Set up the option buffer to specify the option and value to + set. */ + option.len = kOTFourByteOptionSize; + option.level= level; + option.name = name; + option.status = 0; + option.value[0] = value; + + /* Set up request parameter for OTOptionManagement */ + request.opt.buf= (UInt8 *) &option; + request.opt.len= sizeof(option); + request.flags = T_NEGOTIATE; + + /* Set up reply parameter for OTOptionManagement. */ + result.opt.buf = (UInt8 *) &option; + result.opt.maxlen = sizeof(option); + + + err = OTOptionManagement(ep, &request, &result); + + if (err == noErr) { + if (option.status != T_SUCCESS) + err = option.status; + } + + return (err); +} + + +/* +===================== +NET_GetLocalAddress +===================== +*/ +void NET_GetLocalAddress( void ) { + OSStatus err; + + for ( numIP = 0 ; numIP < MAX_IPS ; numIP++ ) { + err = OTInetGetInterfaceInfo( &sys_inetInfo[ numIP ], numIP ); + if ( err ) { + break; + } + Com_Printf( "LocalAddress: %i.%i.%i.%i\n", + ((byte *)&sys_inetInfo[numIP].fAddress)[0], + ((byte *)&sys_inetInfo[numIP].fAddress)[1], + ((byte *)&sys_inetInfo[numIP].fAddress)[2], + ((byte *)&sys_inetInfo[numIP].fAddress)[3] ); + + Com_Printf( "Netmask: %i.%i.%i.%i\n", + ((byte *)&sys_inetInfo[numIP].fNetmask)[0], + ((byte *)&sys_inetInfo[numIP].fNetmask)[1], + ((byte *)&sys_inetInfo[numIP].fNetmask)[2], + ((byte *)&sys_inetInfo[numIP].fNetmask)[3] ); + } +} + + +/* +================== +Sys_InitNetworking + + +struct InetAddress +{ + OTAddressType fAddressType; // always AF_INET + InetPort fPort; // Port number + InetHost fHost; // Host address in net byte order + UInt8 fUnused[8]; // Traditional unused bytes +}; +typedef struct InetAddress InetAddress; + +================== +*/ +void Sys_InitNetworking( void ) { + OSStatus err; + OTConfiguration *config; + TBind bind, bindOut; + InetAddress in, out; + int i; + + Com_Printf( "----- Sys_InitNetworking -----\n" ); + // init OpenTransport + Com_Printf( "... InitOpenTransport()\n" ); + err = InitOpenTransport(); + if ( err != noErr ) { + Com_Printf( "InitOpenTransport() failed\n" ); + Com_Printf( "------------------------------\n" ); + return; + } + + gOTInited = true; + + // get an endpoint + Com_Printf( "... OTOpenEndpoint()\n" ); + config = OTCreateConfiguration( kUDPName ); + +#if 1 + endpoint = OTOpenEndpoint( config, 0, nil, &err); +#else + err = OTAsyncOpenEndpoint( config, 0, 0, NotifyProc, 0 ); + if ( !endpoint ) { + err = 1; + } +#endif + + if ( err != noErr ) { + endpoint = 0; + Com_Printf( "OTOpenEndpoint() failed\n" ); + Com_Printf( "------------------------------\n" ); + return; + } + + // set non-blocking + err = OTSetNonBlocking( endpoint ); + + // scan for a valid port in our range + Com_Printf( "... OTBind()\n" ); + for ( i = 0 ; i < 10 ; i++ ) { + in.fAddressType = AF_INET; + in.fPort = PORT_SERVER + i; + in.fHost = 0; + + bind.addr.maxlen = sizeof( in ); + bind.addr.len = sizeof( in ); + bind.addr.buf = (unsigned char *)∈ + bind.qlen = 0; + + bindOut.addr.maxlen = sizeof( out ); + bindOut.addr.len = sizeof( out ); + bindOut.addr.buf = (unsigned char *)&out; + bindOut.qlen = 0; + + err = OTBind( endpoint, &bind, &bindOut ); + if ( err == noErr ) { + Com_Printf( "Opened UDP endpoint at port %i\n", + out.fPort ); + break; + } + } + + if ( err != noErr ) { + Com_Printf( "Couldn't bind a local port\n" ); + } + + // get the local address for LAN client detection + NET_GetLocalAddress(); + + + // set to allow broadcasts + err = SetFourByteOption( endpoint, INET_IP, IP_BROADCAST, T_YES ); + + if ( err != noErr ) { + Com_Printf( "IP_BROADCAST failed\n" ); + } + + // get an endpoint just for resolving addresses, because + // I was having crashing problems doing it on the same endpoint + config = OTCreateConfiguration( kUDPName ); + resolverEndpoint = OTOpenEndpoint( config, 0, nil, &err); + if ( err != noErr ) { + resolverEndpoint = 0; + Com_Printf( "OTOpenEndpoint() for resolver failed\n" ); + Com_Printf( "------------------------------\n" ); + return; + } + + in.fAddressType = AF_INET; + in.fPort = 0; + in.fHost = 0; + + bind.addr.maxlen = sizeof( in ); + bind.addr.len = sizeof( in ); + bind.addr.buf = (unsigned char *)∈ + bind.qlen = 0; + + bindOut.addr.maxlen = sizeof( out ); + bindOut.addr.len = sizeof( out ); + bindOut.addr.buf = (unsigned char *)&out; + bindOut.qlen = 0; + + err = OTBind( resolverEndpoint, &bind, &bindOut ); + + Com_Printf( "------------------------------\n" ); +} + + +/* +================== +Sys_ShutdownNetworking +================== +*/ +void Sys_ShutdownNetworking( void ) { + Com_Printf( "Sys_ShutdownNetworking();\n" ); + + if ( endpoint != kOTInvalidEndpointRef ) { + OTUnbind( endpoint ); + OTCloseProvider( endpoint ); + endpoint = kOTInvalidEndpointRef; + } + if ( resolverEndpoint != kOTInvalidEndpointRef ) { + OTUnbind( resolverEndpoint ); + OTCloseProvider( resolverEndpoint ); + resolverEndpoint = kOTInvalidEndpointRef; + } + if (gOTInited) { + CloseOpenTransport(); + gOTInited = false; + } +} + +/* +============= +Sys_StringToAdr + + +Does NOT parse port numbers + + +idnewt +192.246.40.70 +============= +*/ +qboolean Sys_StringToAdr( const char *s, netadr_t *a ) { + OSStatus err; + TBind in, out; + InetAddress inAddr; + DNSAddress dnsAddr; + + if ( !resolverEndpoint ) { + return qfalse; + } + + memset( &in, 0, sizeof( in ) ); + in.addr.buf = (UInt8 *) &dnsAddr; + in.addr.len = OTInitDNSAddress(&dnsAddr, (char *)s ); + in.qlen = 0; + + memset( &out, 0, sizeof( out ) ); + out.addr.buf = (byte *)&inAddr; + out.addr.maxlen = sizeof( inAddr ); + out.qlen = 0; + + err = OTResolveAddress( resolverEndpoint, &in, &out, 10000 ); + if ( err ) { + HandleOTError( err, "Sys_StringToAdr" ); + return qfalse; + } + + a->type = NA_IP; + *(int *)a->ip = inAddr.fHost; + + return qtrue; +} + +/* +================== +Sys_SendPacket +================== +*/ +#define MAX_PACKETLEN 1400 +void Sys_SendPacket( int length, const void *data, netadr_t to ) { + TUnitData d; + InetAddress inAddr; + OSStatus err; + + if ( !endpoint ) { + return; + } + + if ( length > MAX_PACKETLEN ) { + Com_Error( ERR_DROP, "Sys_SendPacket: length > MAX_PACKETLEN" ); + } + + inAddr.fAddressType = AF_INET; + inAddr.fPort = to.port; + if ( to.type == NA_BROADCAST ) { + inAddr.fHost = -1; + } else { + inAddr.fHost = *(int *)&to.ip; + } + + memset( &d, 0, sizeof( d ) ); + + d.addr.len = sizeof( inAddr ); + d.addr.maxlen = sizeof( inAddr ); + d.addr.buf = (unsigned char *)&inAddr; + + d.opt.len = 0; + d.opt.maxlen = 0; + d.opt.buf = NULL; + + d.udata.len = length; + d.udata.maxlen = length; + d.udata.buf = (unsigned char *)data; + + err = OTSndUData( endpoint, &d ); + if ( err ) { + HandleOTError( err, "Sys_SendPacket" ); + } +} + +/* +================== +Sys_GetPacket + +Never called by the game logic, just the system event queing +================== +*/ +qboolean Sys_GetPacket ( netadr_t *net_from, msg_t *net_message ) { + TUnitData d; + InetAddress inAddr; + OSStatus err; + OTFlags flags; + + if ( !endpoint ) { + return qfalse; + } + + inAddr.fAddressType = AF_INET; + inAddr.fPort = 0; + inAddr.fHost = 0; + + memset( &d, 0, sizeof( d ) ); + + d.addr.len = sizeof( inAddr ); + d.addr.maxlen = sizeof( inAddr ); + d.addr.buf = (unsigned char *)&inAddr; + + d.opt.len = 0; + d.opt.maxlen = 0; + d.opt.buf = 0; + + d.udata.len = net_message->maxsize; + d.udata.maxlen = net_message->maxsize; + d.udata.buf = net_message->data; + + err = OTRcvUData( endpoint, &d, &flags ); + if ( err ) { + if ( err == kOTNoDataErr ) { + return false; + } + HandleOTError( err, "Sys_GetPacket" ); + return qfalse; + } + + net_from->type = NA_IP; + net_from->port = inAddr.fPort; + *(int *)net_from->ip = inAddr.fHost; + + net_message->cursize = d.udata.len; + + return qtrue; +} + + +/* +================== +Sys_IsLANAddress + +LAN clients will have their rate var ignored +================== +*/ +qboolean Sys_IsLANAddress (netadr_t adr) { + int i; + int ip; + + if ( adr.type == NA_LOOPBACK ) { + return qtrue; + } + + if ( adr.type != NA_IP ) { + return qfalse; + } + + for ( ip = 0 ; ip < numIP ; ip++ ) { + for ( i = 0 ; i < 4 ; i++ ) { + if ( ( adr.ip[i] & ((byte *)&sys_inetInfo[ip].fNetmask)[i] ) + != ( ((byte *)&sys_inetInfo[ip].fAddress)[i] & ((byte *)&sys_inetInfo[ip].fNetmask)[i] ) ) { + break; + } + } + if ( i == 4 ) { + return qtrue; // matches this subnet + } + } + + return qfalse; +} + + +void NET_Sleep( int i ) { +} diff --git a/code/mac/mac_snddma.c b/code/mac/mac_snddma.c new file mode 100644 index 0000000..334f897 --- /dev/null +++ b/code/mac/mac_snddma.c @@ -0,0 +1,140 @@ + +// mac_snddma.c +// all other sound mixing is portable + +#include "../client/snd_local.h" +#include + +#define MAX_MIXED_SAMPLES 0x8000 +#define SUBMISSION_CHUNK 0x100 + +static short s_mixedSamples[MAX_MIXED_SAMPLES]; +static int s_chunkCount; // number of chunks submitted +static SndChannel *s_sndChan; +static ExtSoundHeader s_sndHeader; + +/* +=============== +S_Callback +=============== +*/ +void S_Callback( SndChannel *sc, SndCommand *cmd ) { + SndCommand mySndCmd; + SndCommand mySndCmd2; + int offset; + + offset = ( s_chunkCount * SUBMISSION_CHUNK ) & (MAX_MIXED_SAMPLES-1); + + // queue up another sound buffer + memset( &s_sndHeader, 0, sizeof( s_sndHeader ) ); + s_sndHeader.samplePtr = (void *)(s_mixedSamples + offset); + s_sndHeader.numChannels = 2; + s_sndHeader.sampleRate = rate22khz; + s_sndHeader.loopStart = 0; + s_sndHeader.loopEnd = 0; + s_sndHeader.encode = extSH; + s_sndHeader.baseFrequency = 1; + s_sndHeader.numFrames = SUBMISSION_CHUNK / 2; + s_sndHeader.markerChunk = NULL; + s_sndHeader.instrumentChunks = NULL; + s_sndHeader.AESRecording = NULL; + s_sndHeader.sampleSize = 16; + + mySndCmd.cmd = bufferCmd; + mySndCmd.param1 = 0; + mySndCmd.param2 = (int)&s_sndHeader; + SndDoCommand( sc, &mySndCmd, true ); + + // and another callback + mySndCmd2.cmd = callBackCmd; + mySndCmd2.param1 = 0; + mySndCmd2.param2 = 0; + SndDoCommand( sc, &mySndCmd2, true ); + + s_chunkCount++; // this is the next buffer we will submit +} + +/* +=============== +S_MakeTestPattern +=============== +*/ +void S_MakeTestPattern( void ) { + int i; + float v; + int sample; + + for ( i = 0 ; i < dma.samples / 2 ; i ++ ) { + v = sin( M_PI * 2 * i / 64 ); + sample = v * 0x4000; + ((short *)dma.buffer)[i*2] = sample; + ((short *)dma.buffer)[i*2+1] = sample; + } +} + +/* +=============== +SNDDMA_Init +=============== +*/ +qboolean SNDDMA_Init(void) { + int err; + + // create a sound channel + s_sndChan = NULL; + err = SndNewChannel( &s_sndChan, sampledSynth, initStereo, NewSndCallBackProc(S_Callback) ); + if ( err ) { + return false; + } + + dma.channels = 2; + dma.samples = MAX_MIXED_SAMPLES; + dma.submission_chunk = SUBMISSION_CHUNK; + dma.samplebits = 16; + dma.speed = 22050; + dma.buffer = (byte *)s_mixedSamples; + + // que up the first submission-chunk sized buffer + s_chunkCount = 0; + + S_Callback( s_sndChan, NULL ); + + return qtrue; +} + +/* +=============== +SNDDMA_GetDMAPos +=============== +*/ +int SNDDMA_GetDMAPos(void) { + return s_chunkCount * SUBMISSION_CHUNK; +} + +/* +=============== +SNDDMA_Shutdown +=============== +*/ +void SNDDMA_Shutdown(void) { + if ( s_sndChan ) { + SndDisposeChannel( s_sndChan, true ); + s_sndChan = NULL; + } +} + +/* +=============== +SNDDMA_BeginPainting +=============== +*/ +void SNDDMA_BeginPainting(void) { +} + +/* +=============== +SNDDMA_Submit +=============== +*/ +void SNDDMA_Submit(void) { +} diff --git a/code/mac/macprefix.h b/code/mac/macprefix.h new file mode 100644 index 0000000..5c8866a --- /dev/null +++ b/code/mac/macprefix.h @@ -0,0 +1,3 @@ +//#define __MACOS__ // needed for MrC +#define BOTLIB + diff --git a/code/mac/q3.rsrc b/code/mac/q3.rsrc new file mode 100644 index 0000000..e69de29 diff --git a/code/mp3code/cdct.c b/code/mp3code/cdct.c new file mode 100644 index 0000000..53db9cb --- /dev/null +++ b/code/mp3code/cdct.c @@ -0,0 +1,320 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: cdct.c,v 1.11 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** cdct.c *************************************************** + +mod 5/16/95 first stage in 8 pt dct does not drop last sb mono + + +MPEG audio decoder, dct +portable C + +******************************************************************/ + +#include "config.h" +#include +#include +#include +#include + +#pragma warning ( disable : 4711 ) // function 'xxxx' selected for automatic inline expansion + +float coef32[31]; /* 32 pt dct coefs */ // !!!!!!!!!!!!!!!!!! (only generated once (always to same value) + +/*------------------------------------------------------------*/ +float *dct_coef_addr() +{ + return coef32; +} +/*------------------------------------------------------------*/ +static void forward_bf(int m, int n, float x[], float f[], float coef[]) +{ + int i, j, n2; + int p, q, p0, k; + + p0 = 0; + n2 = n >> 1; + for (i = 0; i < m; i++, p0 += n) + { + k = 0; + p = p0; + q = p + n - 1; + for (j = 0; j < n2; j++, p++, q--, k++) + { + f[p] = x[p] + x[q]; + f[n2 + p] = coef[k] * (x[p] - x[q]); + } + } +} +/*------------------------------------------------------------*/ +static void back_bf(int m, int n, float x[], float f[]) +{ + int i, j, n2, n21; + int p, q, p0; + + p0 = 0; + n2 = n >> 1; + n21 = n2 - 1; + for (i = 0; i < m; i++, p0 += n) + { + p = p0; + q = p0; + for (j = 0; j < n2; j++, p += 2, q++) + f[p] = x[q]; + p = p0 + 1; + for (j = 0; j < n21; j++, p += 2, q++) + f[p] = x[q] + x[q + 1]; + f[p] = x[q]; + } +} +/*------------------------------------------------------------*/ + + +void fdct32(float x[], float c[]) +{ + float a[32]; /* ping pong buffers */ + float b[32]; + int p, q; + + float *src = x; + +/* special first stage */ + for (p = 0, q = 31; p < 16; p++, q--) + { + a[p] = src[p] + src[q]; + a[16 + p] = coef32[p] * (src[p] - src[q]); + } + forward_bf(2, 16, a, b, coef32 + 16); + forward_bf(4, 8, b, a, coef32 + 16 + 8); + forward_bf(8, 4, a, b, coef32 + 16 + 8 + 4); + forward_bf(16, 2, b, a, coef32 + 16 + 8 + 4 + 2); + back_bf(8, 4, a, b); + back_bf(4, 8, b, a); + back_bf(2, 16, a, b); + back_bf(1, 32, b, c); +} +/*------------------------------------------------------------*/ +void fdct32_dual(float x[], float c[]) +{ + float a[32]; /* ping pong buffers */ + float b[32]; + int p, pp, qq; + +/* special first stage for dual chan (interleaved x) */ + pp = 0; + qq = 2 * 31; + for (p = 0; p < 16; p++, pp += 2, qq -= 2) + { + a[p] = x[pp] + x[qq]; + a[16 + p] = coef32[p] * (x[pp] - x[qq]); + } + forward_bf(2, 16, a, b, coef32 + 16); + forward_bf(4, 8, b, a, coef32 + 16 + 8); + forward_bf(8, 4, a, b, coef32 + 16 + 8 + 4); + forward_bf(16, 2, b, a, coef32 + 16 + 8 + 4 + 2); + back_bf(8, 4, a, b); + back_bf(4, 8, b, a); + back_bf(2, 16, a, b); + back_bf(1, 32, b, c); +} +/*---------------convert dual to mono------------------------------*/ +void fdct32_dual_mono(float x[], float c[]) +{ + float a[32]; /* ping pong buffers */ + float b[32]; + float t1, t2; + int p, pp, qq; + +/* special first stage */ + pp = 0; + qq = 2 * 31; + for (p = 0; p < 16; p++, pp += 2, qq -= 2) + { + t1 = 0.5F * (x[pp] + x[pp + 1]); + t2 = 0.5F * (x[qq] + x[qq + 1]); + a[p] = t1 + t2; + a[16 + p] = coef32[p] * (t1 - t2); + } + forward_bf(2, 16, a, b, coef32 + 16); + forward_bf(4, 8, b, a, coef32 + 16 + 8); + forward_bf(8, 4, a, b, coef32 + 16 + 8 + 4); + forward_bf(16, 2, b, a, coef32 + 16 + 8 + 4 + 2); + back_bf(8, 4, a, b); + back_bf(4, 8, b, a); + back_bf(2, 16, a, b); + back_bf(1, 32, b, c); +} +/*------------------------------------------------------------*/ +/*---------------- 16 pt fdct -------------------------------*/ +void fdct16(float x[], float c[]) +{ + float a[16]; /* ping pong buffers */ + float b[16]; + int p, q; + +/* special first stage (drop highest sb) */ + a[0] = x[0]; + a[8] = coef32[16] * x[0]; + for (p = 1, q = 14; p < 8; p++, q--) + { + a[p] = x[p] + x[q]; + a[8 + p] = coef32[16 + p] * (x[p] - x[q]); + } + forward_bf(2, 8, a, b, coef32 + 16 + 8); + forward_bf(4, 4, b, a, coef32 + 16 + 8 + 4); + forward_bf(8, 2, a, b, coef32 + 16 + 8 + 4 + 2); + back_bf(4, 4, b, a); + back_bf(2, 8, a, b); + back_bf(1, 16, b, c); +} +/*------------------------------------------------------------*/ +/*---------------- 16 pt fdct dual chan---------------------*/ +void fdct16_dual(float x[], float c[]) +{ + float a[16]; /* ping pong buffers */ + float b[16]; + int p, pp, qq; + +/* special first stage for interleaved input */ + a[0] = x[0]; + a[8] = coef32[16] * x[0]; + pp = 2; + qq = 2 * 14; + for (p = 1; p < 8; p++, pp += 2, qq -= 2) + { + a[p] = x[pp] + x[qq]; + a[8 + p] = coef32[16 + p] * (x[pp] - x[qq]); + } + forward_bf(2, 8, a, b, coef32 + 16 + 8); + forward_bf(4, 4, b, a, coef32 + 16 + 8 + 4); + forward_bf(8, 2, a, b, coef32 + 16 + 8 + 4 + 2); + back_bf(4, 4, b, a); + back_bf(2, 8, a, b); + back_bf(1, 16, b, c); +} +/*------------------------------------------------------------*/ +/*---------------- 16 pt fdct dual to mono-------------------*/ +void fdct16_dual_mono(float x[], float c[]) +{ + float a[16]; /* ping pong buffers */ + float b[16]; + float t1, t2; + int p, pp, qq; + +/* special first stage */ + a[0] = 0.5F * (x[0] + x[1]); + a[8] = coef32[16] * a[0]; + pp = 2; + qq = 2 * 14; + for (p = 1; p < 8; p++, pp += 2, qq -= 2) + { + t1 = 0.5F * (x[pp] + x[pp + 1]); + t2 = 0.5F * (x[qq] + x[qq + 1]); + a[p] = t1 + t2; + a[8 + p] = coef32[16 + p] * (t1 - t2); + } + forward_bf(2, 8, a, b, coef32 + 16 + 8); + forward_bf(4, 4, b, a, coef32 + 16 + 8 + 4); + forward_bf(8, 2, a, b, coef32 + 16 + 8 + 4 + 2); + back_bf(4, 4, b, a); + back_bf(2, 8, a, b); + back_bf(1, 16, b, c); +} +/*------------------------------------------------------------*/ +/*---------------- 8 pt fdct -------------------------------*/ +void fdct8(float x[], float c[]) +{ + float a[8]; /* ping pong buffers */ + float b[8]; + int p, q; + +/* special first stage */ + + b[0] = x[0] + x[7]; + b[4] = coef32[16 + 8] * (x[0] - x[7]); + for (p = 1, q = 6; p < 4; p++, q--) + { + b[p] = x[p] + x[q]; + b[4 + p] = coef32[16 + 8 + p] * (x[p] - x[q]); + } + + forward_bf(2, 4, b, a, coef32 + 16 + 8 + 4); + forward_bf(4, 2, a, b, coef32 + 16 + 8 + 4 + 2); + back_bf(2, 4, b, a); + back_bf(1, 8, a, c); +} +/*------------------------------------------------------------*/ +/*---------------- 8 pt fdct dual chan---------------------*/ +void fdct8_dual(float x[], float c[]) +{ + float a[8]; /* ping pong buffers */ + float b[8]; + int p, pp, qq; + +/* special first stage for interleaved input */ + b[0] = x[0] + x[14]; + b[4] = coef32[16 + 8] * (x[0] - x[14]); + pp = 2; + qq = 2 * 6; + for (p = 1; p < 4; p++, pp += 2, qq -= 2) + { + b[p] = x[pp] + x[qq]; + b[4 + p] = coef32[16 + 8 + p] * (x[pp] - x[qq]); + } + forward_bf(2, 4, b, a, coef32 + 16 + 8 + 4); + forward_bf(4, 2, a, b, coef32 + 16 + 8 + 4 + 2); + back_bf(2, 4, b, a); + back_bf(1, 8, a, c); +} +/*------------------------------------------------------------*/ +/*---------------- 8 pt fdct dual to mono---------------------*/ +void fdct8_dual_mono(float x[], float c[]) +{ + float a[8]; /* ping pong buffers */ + float b[8]; + float t1, t2; + int p, pp, qq; + +/* special first stage */ + t1 = 0.5F * (x[0] + x[1]); + t2 = 0.5F * (x[14] + x[15]); + b[0] = t1 + t2; + b[4] = coef32[16 + 8] * (t1 - t2); + pp = 2; + qq = 2 * 6; + for (p = 1; p < 4; p++, pp += 2, qq -= 2) + { + t1 = 0.5F * (x[pp] + x[pp + 1]); + t2 = 0.5F * (x[qq] + x[qq + 1]); + b[p] = t1 + t2; + b[4 + p] = coef32[16 + 8 + p] * (t1 - t2); + } + forward_bf(2, 4, b, a, coef32 + 16 + 8 + 4); + forward_bf(4, 2, a, b, coef32 + 16 + 8 + 4 + 2); + back_bf(2, 4, b, a); + back_bf(1, 8, a, c); +} +/*------------------------------------------------------------*/ diff --git a/code/mp3code/config.h b/code/mp3code/config.h new file mode 100644 index 0000000..50f7fc8 --- /dev/null +++ b/code/mp3code/config.h @@ -0,0 +1,136 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: config.win32,v 1.16 1999/12/09 08:44:07 elrod Exp $ +____________________________________________________________________________*/ + +#ifndef CONFIG_H +#define CONFIG_H + +#if !defined(RC_INVOKED) + +#include + +#define HAVE_IO_H 1 +#define HAVE_ERRNO_H 1 + +#if HAVE_UNISTD_H +#define RD_BNRY_FLAGS O_RDONLY +#elif HAVE_IO_H +#define RD_BNRY_FLAGS O_RDONLY | O_BINARY +#endif + +/* Endian Issues */ +#ifdef LINUX +#include +#endif + +#ifdef WIN32 +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __PDP_ENDIAN 3412 +#define __BYTE_ORDER __LITTLE_ENDIAN +#define usleep(x) ::Sleep(x/1000) +#define strcasecmp(a,b) stricmp(a,b) +#define strncasecmp(a,b,c) strnicmp(a,b,c) +typedef int socklen_t; +#endif + +#ifndef _MAX_PATH +#define _MAX_PATH 260 +#endif + +/* define our datatypes */ +// real number +typedef double real; + +#if UCHAR_MAX == 0xff + +typedef unsigned char uint8; +typedef signed char int8; + +#else +#error This machine has no 8-bit type +#endif + +#if UINT_MAX == 0xffff + +typedef unsigned int uint16; +typedef int int16; + +#elif USHRT_MAX == 0xffff + +typedef unsigned short uint16; +typedef short int16; + +#else +#error This machine has no 16-bit type +#endif + + +#if UINT_MAX == 0xfffffffful + +typedef unsigned int uint32; +typedef int int32; + +#elif ULONG_MAX == 0xfffffffful + +typedef unsigned long uint32; +typedef long int32; + +#elif USHRT_MAX == 0xfffffffful + +typedef unsigned short uint32; +typedef short int32; + +#else +#error This machine has no 32-bit type +#endif + + +// What character marks the end of a directory entry? For DOS and +// Windows, it is "\"; in UNIX it is "/". +#if defined(WIN32) || defined(OS2) || defined(__DOS__) +#define DIR_MARKER '\\' +#define DIR_MARKER_STR "\\" +#else +#define DIR_MARKER '/' +#define DIR_MARKER_STR "/" +#endif /* WIN32 */ + +// What character(s) marks the end of a line in a text file? +// For DOS and Windows, it is "\r\n"; in UNIX it is "\r". +#if defined(WIN32) || defined(OS2) || defined(__DOS__) +#define LINE_END_MARKER_STR "\r\n" +#else +#define LINE_END_MARKER_STR "\n" +#endif /* WIN32 */ + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif /* NULL */ + +#endif /* RC_INVOKED */ + +#endif /* CONFIG_H */ diff --git a/code/mp3code/copyright.h b/code/mp3code/copyright.h new file mode 100644 index 0000000..7d3fa68 --- /dev/null +++ b/code/mp3code/copyright.h @@ -0,0 +1,19 @@ +//############################################################################ +//## ## +//## MSS 4.0 Miles Sound Studio ## +//## ## +//## V1.00 of 18-Mar-96: Initial version ## +//## ## +//## C source compatible with Microsoft C v9.0 or later ## +//## ## +//## Author: Jeff Roberts ## +//## ## +//############################################################################ +//## ## +//## Copyright (C) RAD Game Tools, Inc. ## +//## ## +//## For technical support, contact RAD Game Tools at 425-893-4300. ## +//## ## +//############################################################################ + +#define MSS_COPYRIGHT "Copyright (C) 1991-2000, RAD Game Tools, Inc." diff --git a/code/mp3code/csbt.c b/code/mp3code/csbt.c new file mode 100644 index 0000000..7ec7c36 --- /dev/null +++ b/code/mp3code/csbt.c @@ -0,0 +1,355 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: csbt.c,v 1.2 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** csbt.c *************************************************** + +MPEG audio decoder, dct and window +portable C + +1/7/96 mod for Layer III + +******************************************************************/ + +#include +#include +#include +#include + +void fdct32(float *, float *); +void fdct32_dual(float *, float *); +void fdct32_dual_mono(float *, float *); +void fdct16(float *, float *); +void fdct16_dual(float *, float *); +void fdct16_dual_mono(float *, float *); +void fdct8(float *, float *); +void fdct8_dual(float *, float *); +void fdct8_dual_mono(float *, float *); + +void window(float *vbuf, int vb_ptr, short *pcm); +void window_dual(float *vbuf, int vb_ptr, short *pcm); +void window16(float *vbuf, int vb_ptr, short *pcm); +void window16_dual(float *vbuf, int vb_ptr, short *pcm); +void window8(float *vbuf, int vb_ptr, short *pcm); +void window8_dual(float *vbuf, int vb_ptr, short *pcm); + +/*-------------------------------------------------------------------------*/ +/* circular window buffers */ +#include "mp3struct.h" +////static signed int vb_ptr; // !!!!!!!!!!!!! +////static signed int vb2_ptr; // !!!!!!!!!!!!! +////static float pMP3Stream->vbuf[512]; // !!!!!!!!!!!!! +////static float vbuf2[512]; // !!!!!!!!!!!!! + +float *dct_coef_addr(); + +/*======================================================================*/ +static void gencoef() /* gen coef for N=32 (31 coefs) */ +{ + static int iOnceOnly = 0; + int p, n, i, k; + double t, pi; + float *coef32; + + if (!iOnceOnly++) + { + coef32 = dct_coef_addr(); + + pi = 4.0 * atan(1.0); + n = 16; + k = 0; + for (i = 0; i < 5; i++, n = n / 2) + { + + for (p = 0; p < n; p++, k++) + { + t = (pi / (4 * n)) * (2 * p + 1); + coef32[k] = (float) (0.50 / cos(t)); + } + } + } +} +/*------------------------------------------------------------*/ +void sbt_init() +{ + int i; + static int first_pass = 1; + + if (first_pass) + { + gencoef(); + first_pass = 0; + } + +/* clear window pMP3Stream->vbuf */ + for (i = 0; i < 512; i++) + { + pMP3Stream->vbuf[i] = 0.0F; + pMP3Stream->vbuf2[i] = 0.0F; + } + pMP3Stream->vb2_ptr = pMP3Stream->vb_ptr = 0; + +} +/*============================================================*/ +/*============================================================*/ +/*============================================================*/ +void sbt_mono(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct32(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 32; + } + +} +/*------------------------------------------------------------*/ +void sbt_dual(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct32_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + fdct32_dual(sample + 1, pMP3Stream->vbuf2 + pMP3Stream->vb_ptr); + window_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + window_dual(pMP3Stream->vbuf2, pMP3Stream->vb_ptr, pcm + 1); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 64; + } + + +} +/*------------------------------------------------------------*/ +/* convert dual to mono */ +void sbt_dual_mono(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct32_dual_mono(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 32; + } + +} +/*------------------------------------------------------------*/ +/* convert dual to left */ +void sbt_dual_left(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct32_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 32; + } +} +/*------------------------------------------------------------*/ +/* convert dual to right */ +void sbt_dual_right(float *sample, short *pcm, int n) +{ + int i; + + sample++; /* point to right chan */ + for (i = 0; i < n; i++) + { + fdct32_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 32; + } +} +/*------------------------------------------------------------*/ +/*---------------- 16 pt sbt's -------------------------------*/ +/*------------------------------------------------------------*/ +void sbt16_mono(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct16(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window16(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 16; + } + + +} +/*------------------------------------------------------------*/ +void sbt16_dual(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct16_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + fdct16_dual(sample + 1, pMP3Stream->vbuf2 + pMP3Stream->vb_ptr); + window16_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + window16_dual(pMP3Stream->vbuf2, pMP3Stream->vb_ptr, pcm + 1); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 32; + } +} +/*------------------------------------------------------------*/ +void sbt16_dual_mono(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct16_dual_mono(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window16(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 16; + } +} +/*------------------------------------------------------------*/ +void sbt16_dual_left(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct16_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window16(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 16; + } +} +/*------------------------------------------------------------*/ +void sbt16_dual_right(float *sample, short *pcm, int n) +{ + int i; + + sample++; + for (i = 0; i < n; i++) + { + fdct16_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window16(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 16; + } +} +/*------------------------------------------------------------*/ +/*---------------- 8 pt sbt's -------------------------------*/ +/*------------------------------------------------------------*/ +void sbt8_mono(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct8(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window8(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 8; + } + +} +/*------------------------------------------------------------*/ +void sbt8_dual(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct8_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + fdct8_dual(sample + 1, pMP3Stream->vbuf2 + pMP3Stream->vb_ptr); + window8_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + window8_dual(pMP3Stream->vbuf2, pMP3Stream->vb_ptr, pcm + 1); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 16; + } +} +/*------------------------------------------------------------*/ +void sbt8_dual_mono(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct8_dual_mono(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window8(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 8; + } +} +/*------------------------------------------------------------*/ +void sbt8_dual_left(float *sample, short *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct8_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window8(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 8; + } +} +/*------------------------------------------------------------*/ +void sbt8_dual_right(float *sample, short *pcm, int n) +{ + int i; + + sample++; + for (i = 0; i < n; i++) + { + fdct8_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window8(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 8; + } +} +/*------------------------------------------------------------*/ +/*------------------------------------------------------------*/ +#define COMPILE_ME +#include "csbtb.c" /* 8 bit output */ +#include "csbtL3.c" /* Layer III */ +/*------------------------------------------------------------*/ diff --git a/code/mp3code/csbtb.c b/code/mp3code/csbtb.c new file mode 100644 index 0000000..a92bf4d --- /dev/null +++ b/code/mp3code/csbtb.c @@ -0,0 +1,279 @@ +#pragma warning(disable:4206) // nonstandard extension used : translation unit is empty +#ifdef COMPILE_ME +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: csbtb.c,v 1.2 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** csbtb.c *************************************************** +include to csbt.c + +MPEG audio decoder, dct and window - byte (8 pcm bit output) +portable C + +******************************************************************/ +/*============================================================*/ +/*============================================================*/ +void windowB(float *vbuf, int vb_ptr, unsigned char *pcm); +void windowB_dual(float *vbuf, int vb_ptr, unsigned char *pcm); +void windowB16(float *vbuf, int vb_ptr, unsigned char *pcm); +void windowB16_dual(float *vbuf, int vb_ptr, unsigned char *pcm); +void windowB8(float *vbuf, int vb_ptr, unsigned char *pcm); +void windowB8_dual(float *vbuf, int vb_ptr, unsigned char *pcm); + +/*============================================================*/ +void sbtB_mono(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct32(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 32; + } + +} +/*------------------------------------------------------------*/ +void sbtB_dual(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct32_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + fdct32_dual(sample + 1, pMP3Stream->vbuf2 + pMP3Stream->vb_ptr); + windowB_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + windowB_dual(pMP3Stream->vbuf2, pMP3Stream->vb_ptr, pcm + 1); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 64; + } + + +} +/*------------------------------------------------------------*/ +/* convert dual to mono */ +void sbtB_dual_mono(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct32_dual_mono(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 32; + } + +} +/*------------------------------------------------------------*/ +/* convert dual to left */ +void sbtB_dual_left(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct32_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 32; + } +} +/*------------------------------------------------------------*/ +/* convert dual to right */ +void sbtB_dual_right(float *sample, unsigned char *pcm, int n) +{ + int i; + + sample++; /* point to right chan */ + for (i = 0; i < n; i++) + { + fdct32_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 32; + } +} +/*------------------------------------------------------------*/ +/*---------------- 16 pt sbt's -------------------------------*/ +/*------------------------------------------------------------*/ +void sbtB16_mono(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct16(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB16(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 16; + } + + +} +/*------------------------------------------------------------*/ +void sbtB16_dual(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct16_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + fdct16_dual(sample + 1, pMP3Stream->vbuf2 + pMP3Stream->vb_ptr); + windowB16_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + windowB16_dual(pMP3Stream->vbuf2, pMP3Stream->vb_ptr, pcm + 1); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 32; + } +} +/*------------------------------------------------------------*/ +void sbtB16_dual_mono(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct16_dual_mono(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB16(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 16; + } +} +/*------------------------------------------------------------*/ +void sbtB16_dual_left(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct16_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB16(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 16; + } +} +/*------------------------------------------------------------*/ +void sbtB16_dual_right(float *sample, unsigned char *pcm, int n) +{ + int i; + + sample++; + for (i = 0; i < n; i++) + { + fdct16_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB16(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 16; + } +} +/*------------------------------------------------------------*/ +/*---------------- 8 pt sbt's -------------------------------*/ +/*------------------------------------------------------------*/ +void sbtB8_mono(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct8(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB8(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 8; + } + +} +/*------------------------------------------------------------*/ +void sbtB8_dual(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct8_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + fdct8_dual(sample + 1, pMP3Stream->vbuf2 + pMP3Stream->vb_ptr); + windowB8_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + windowB8_dual(pMP3Stream->vbuf2, pMP3Stream->vb_ptr, pcm + 1); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 16; + } +} +/*------------------------------------------------------------*/ +void sbtB8_dual_mono(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct8_dual_mono(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB8(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 8; + } +} +/*------------------------------------------------------------*/ +void sbtB8_dual_left(float *sample, unsigned char *pcm, int n) +{ + int i; + + for (i = 0; i < n; i++) + { + fdct8_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB8(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 8; + } +} +/*------------------------------------------------------------*/ +void sbtB8_dual_right(float *sample, unsigned char *pcm, int n) +{ + int i; + + sample++; + for (i = 0; i < n; i++) + { + fdct8_dual(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB8(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 64; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 8; + } +} +/*------------------------------------------------------------*/ +#endif // #ifdef COMPILE_ME diff --git a/code/mp3code/csbtl3.c b/code/mp3code/csbtl3.c new file mode 100644 index 0000000..683af0d --- /dev/null +++ b/code/mp3code/csbtl3.c @@ -0,0 +1,309 @@ +#pragma warning(disable:4206) // nonstandard extension used : translation unit is empty +#ifdef COMPILE_ME +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: csbtL3.c,v 1.2 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** csbtL3.c *************************************************** + +layer III + + include to csbt.c + +******************************************************************/ +/*============================================================*/ +/*============ Layer III =====================================*/ +/*============================================================*/ +void sbt_mono_L3(float *sample, short *pcm, int ch) +{ + int i; + + ch = 0; + for (i = 0; i < 18; i++) + { + fdct32(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 32; + } +} +/*------------------------------------------------------------*/ +void sbt_dual_L3(float *sample, short *pcm, int ch) +{ + int i; + + if (ch == 0) + { + for (i = 0; i < 18; i++) + { + fdct32(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 64; + } + } + else + { + for (i = 0; i < 18; i++) + { + fdct32(sample, pMP3Stream->vbuf2 + pMP3Stream->vb2_ptr); + window_dual(pMP3Stream->vbuf2, pMP3Stream->vb2_ptr, pcm + 1); + sample += 32; + pMP3Stream->vb2_ptr = (pMP3Stream->vb2_ptr - 32) & 511; + pcm += 64; + } + } +} +/*------------------------------------------------------------*/ +/*------------------------------------------------------------*/ +/*---------------- 16 pt sbt's -------------------------------*/ +/*------------------------------------------------------------*/ +void sbt16_mono_L3(float *sample, short *pcm, int ch) +{ + int i; + + ch = 0; + for (i = 0; i < 18; i++) + { + fdct16(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window16(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 16; + } +} +/*------------------------------------------------------------*/ +void sbt16_dual_L3(float *sample, short *pcm, int ch) +{ + int i; + + if (ch == 0) + { + for (i = 0; i < 18; i++) + { + fdct16(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window16_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 32; + } + } + else + { + for (i = 0; i < 18; i++) + { + fdct16(sample, pMP3Stream->vbuf2 + pMP3Stream->vb2_ptr); + window16_dual(pMP3Stream->vbuf2, pMP3Stream->vb2_ptr, pcm + 1); + sample += 32; + pMP3Stream->vb2_ptr = (pMP3Stream->vb2_ptr - 16) & 255; + pcm += 32; + } + } +} +/*------------------------------------------------------------*/ +/*---------------- 8 pt sbt's -------------------------------*/ +/*------------------------------------------------------------*/ +void sbt8_mono_L3(float *sample, short *pcm, int ch) +{ + int i; + + ch = 0; + for (i = 0; i < 18; i++) + { + fdct8(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window8(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 8; + } +} +/*------------------------------------------------------------*/ +void sbt8_dual_L3(float *sample, short *pcm, int ch) +{ + int i; + + if (ch == 0) + { + for (i = 0; i < 18; i++) + { + fdct8(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + window8_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 16; + } + } + else + { + for (i = 0; i < 18; i++) + { + fdct8(sample, pMP3Stream->vbuf2 + pMP3Stream->vb2_ptr); + window8_dual(pMP3Stream->vbuf2, pMP3Stream->vb2_ptr, pcm + 1); + sample += 32; + pMP3Stream->vb2_ptr = (pMP3Stream->vb2_ptr - 8) & 127; + pcm += 16; + } + } +} +/*------------------------------------------------------------*/ +/*------- 8 bit output ---------------------------------------*/ +/*------------------------------------------------------------*/ +void sbtB_mono_L3(float *sample, unsigned char *pcm, int ch) +{ + int i; + + ch = 0; + for (i = 0; i < 18; i++) + { + fdct32(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 32; + } +} +/*------------------------------------------------------------*/ +void sbtB_dual_L3(float *sample, unsigned char *pcm, int ch) +{ + int i; + + if (ch == 0) + { + for (i = 0; i < 18; i++) + { + fdct32(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 32) & 511; + pcm += 64; + } + } + else + { + for (i = 0; i < 18; i++) + { + fdct32(sample, pMP3Stream->vbuf2 + pMP3Stream->vb2_ptr); + windowB_dual(pMP3Stream->vbuf2, pMP3Stream->vb2_ptr, pcm + 1); + sample += 32; + pMP3Stream->vb2_ptr = (pMP3Stream->vb2_ptr - 32) & 511; + pcm += 64; + } + } +} +/*------------------------------------------------------------*/ +/*------------------------------------------------------------*/ +/*---------------- 16 pt sbtB's -------------------------------*/ +/*------------------------------------------------------------*/ +void sbtB16_mono_L3(float *sample, unsigned char *pcm, int ch) +{ + int i; + + ch = 0; + for (i = 0; i < 18; i++) + { + fdct16(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB16(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 16; + } +} +/*------------------------------------------------------------*/ +void sbtB16_dual_L3(float *sample, unsigned char *pcm, int ch) +{ + int i; + + if (ch == 0) + { + for (i = 0; i < 18; i++) + { + fdct16(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB16_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 16) & 255; + pcm += 32; + } + } + else + { + for (i = 0; i < 18; i++) + { + fdct16(sample, pMP3Stream->vbuf2 + pMP3Stream->vb2_ptr); + windowB16_dual(pMP3Stream->vbuf2, pMP3Stream->vb2_ptr, pcm + 1); + sample += 32; + pMP3Stream->vb2_ptr = (pMP3Stream->vb2_ptr - 16) & 255; + pcm += 32; + } + } +} +/*------------------------------------------------------------*/ +/*---------------- 8 pt sbtB's -------------------------------*/ +/*------------------------------------------------------------*/ +void sbtB8_mono_L3(float *sample, unsigned char *pcm, int ch) +{ + int i; + + ch = 0; + for (i = 0; i < 18; i++) + { + fdct8(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB8(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 8; + } +} +/*------------------------------------------------------------*/ +void sbtB8_dual_L3(float *sample, unsigned char *pcm, int ch) +{ + int i; + + if (ch == 0) + { + for (i = 0; i < 18; i++) + { + fdct8(sample, pMP3Stream->vbuf + pMP3Stream->vb_ptr); + windowB8_dual(pMP3Stream->vbuf, pMP3Stream->vb_ptr, pcm); + sample += 32; + pMP3Stream->vb_ptr = (pMP3Stream->vb_ptr - 8) & 127; + pcm += 16; + } + } + else + { + for (i = 0; i < 18; i++) + { + fdct8(sample, pMP3Stream->vbuf2 + pMP3Stream->vb2_ptr); + windowB8_dual(pMP3Stream->vbuf2, pMP3Stream->vb2_ptr, pcm + 1); + sample += 32; + pMP3Stream->vb2_ptr = (pMP3Stream->vb2_ptr - 8) & 127; + pcm += 16; + } + } +} +/*------------------------------------------------------------*/ +#endif // #ifdef COMPILE_ME diff --git a/code/mp3code/cup.c b/code/mp3code/cup.c new file mode 100644 index 0000000..848680c --- /dev/null +++ b/code/mp3code/cup.c @@ -0,0 +1,546 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: cup.c,v 1.3 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** cup.c *************************************************** + +MPEG audio decoder Layer I/II mpeg1 and mpeg2 +should be portable ANSI C, should be endian independent + + +mod 2/21/95 2/21/95 add bit skip, sb limiting + +mods 11/15/95 for Layer I + +******************************************************************/ +/****************************************************************** + + MPEG audio software decoder portable ANSI c. + Decodes all Layer I/II to 16 bit linear pcm. + Optional stereo to mono conversion. Optional + output sample rate conversion to half or quarter of + native mpeg rate. dec8.c adds oupuut conversion features. + +------------------------------------- +int audio_decode_init(MPEG_HEAD *h, int framebytes_arg, + int reduction_code, int transform_code, int convert_code, + int freq_limit) + +initilize decoder: + return 0 = fail, not 0 = success + +MPEG_HEAD *h input, mpeg header info (returned by call to head_info) +pMP3Stream->framebytes input, mpeg frame size (returned by call to head_info) +reduction_code input, sample rate reduction code + 0 = full rate + 1 = half rate + 2 = quarter rate + +transform_code input, ignored +convert_code input, channel conversion + convert_code: 0 = two chan output + 1 = convert two chan to mono + 2 = convert two chan to left chan + 3 = convert two chan to right chan +freq_limit input, limits bandwidth of pcm output to specified + frequency. Special use. Set to 24000 for normal use. + + +--------------------------------- +void audio_decode_info( DEC_INFO *info) + +information return: + Call after audio_decode_init. See mhead.h for + information returned in DEC_INFO structure. + + +--------------------------------- +IN_OUT audio_decode(unsigned char *bs, void *pcmbuf) + +decode one mpeg audio frame: +bs input, mpeg bitstream, must start with + sync word. Caution: may read up to 3 bytes + beyond end of frame. +pcmbuf output, pcm samples. + +IN_OUT structure returns: + Number bytes conceptually removed from mpeg bitstream. + Returns 0 if sync loss. + Number bytes of pcm output. + +*******************************************************************/ + + +#include +#include +#include +#include +#include "mhead.h" /* mpeg header structure */ + + +#ifdef _MSC_VER +#pragma warning(disable: 4709) +#endif + +#include "mp3struct.h" + + +/*------------------------------------------------------- +NOTE: Decoder may read up to three bytes beyond end of +frame. Calling application must ensure that this does +not cause a memory access violation (protection fault) +---------------------------------------------------------*/ + +/*====================================================================*/ +/*----------------*/ +//@@@@ This next one (decinfo) is ok: +DEC_INFO decinfo; /* global for Layer III */ // only written into by decode init funcs, then copied to stack struct higher up + +/*----------------*/ +static float look_c_value[18]; /* built by init */ // effectively constant + +/*----------------*/ +////@@@@static int pMP3Stream->outbytes; // !!!!!!!!!!!!!!? +////@@@@static int pMP3Stream->framebytes; // !!!!!!!!!!!!!!!! +////@@@@static int pMP3Stream->outvalues; // !!!!!!!!!!!!? +////@@@@static int pad; +static const int look_joint[16] = +{ /* lookup stereo sb's by mode+ext */ + 64, 64, 64, 64, /* stereo */ + 2 * 4, 2 * 8, 2 * 12, 2 * 16, /* joint */ + 64, 64, 64, 64, /* dual */ + 32, 32, 32, 32, /* mono */ +}; + +/*----------------*/ +////@@@@static int max_sb; // !!!!!!!!! L1, 2 3 +////@@@@static int stereo_sb; + +/*----------------*/ +////@@@@static int pMP3Stream->nsb_limit = 6; +////@@@@static int bit_skip; +static const int bat_bit_master[] = +{ + 0, 5, 7, 9, 10, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48}; + +/*----------------*/ +////@@@@static int nbat[4] = {3, 8, 12, 7}; // !!!!!!!!!!!!! not constant!!!! +////@@@@static int bat[4][16]; // built as constant, but built according to header type (sigh) +static int ballo[64]; /* set by unpack_ba */ // scratchpad +static unsigned int samp_dispatch[66]; /* set by unpack_ba */ // scratchpad? +static float c_value[64]; /* set by unpack_ba */ // scratchpad + +/*----------------*/ +static unsigned int sf_dispatch[66]; /* set by unpack_ba */ // scratchpad? +static float sf_table[64]; // effectively constant +////@@@@ static float cs_factor[3][64]; + +/*----------------*/ +////@@@@FINDME - groan.... (I shoved a *2 in just in case it needed it for stereo. This whole thing is crap now +float sample[2304*2]; /* global for use by Later 3 */ // !!!!!!!!!!!!!!!!!!!!!! // scratchpad? +static signed char group3_table[32][3]; // effectively constant +static signed char group5_table[128][3]; // effectively constant +static signed short group9_table[1024][3]; // effectively constant + +/*----------------*/ + +////@@@@typedef void (*SBT_FUNCTION) (float *sample, short *pcm, int n); +void sbt_mono(float *sample, short *pcm, int n); +void sbt_dual(float *sample, short *pcm, int n); +////@@@@static SBT_FUNCTION sbt = sbt_mono; + + +typedef IN_OUT(*AUDIO_DECODE_ROUTINE) (unsigned char *bs, signed short *pcm); +IN_OUT L2audio_decode(unsigned char *bs, signed short *pcm); +static AUDIO_DECODE_ROUTINE audio_decode_routine = L2audio_decode; + +/*======================================================================*/ +/*======================================================================*/ +/* get bits from bitstream in endian independent way */ +////@@@@ FINDME - this stuff doesn't appear to be used by any of our samples (phew) +static unsigned char *bs_ptr; +static unsigned long bitbuf; +static int bits; +static long bitval; + +/*------------- initialize bit getter -------------*/ +static void load_init(unsigned char *buf) +{ + bs_ptr = buf; + bits = 0; + bitbuf = 0; +} +/*------------- get n bits from bitstream -------------*/ +static long load(int n) +{ + unsigned long x; + + if (bits < n) + { /* refill bit buf if necessary */ + while (bits <= 24) + { + bitbuf = (bitbuf << 8) | *bs_ptr++; + bits += 8; + } + } + bits -= n; + x = bitbuf >> bits; + bitbuf -= x << bits; + return x; +} +/*------------- skip over n bits in bitstream -------------*/ +static void skip(int n) +{ + int k; + + if (bits < n) + { + n -= bits; + k = n >> 3; +/*--- bytes = n/8 --*/ + bs_ptr += k; + n -= k << 3; + bitbuf = *bs_ptr++; + bits = 8; + } + bits -= n; + bitbuf -= (bitbuf >> bits) << bits; +} +/*--------------------------------------------------------------*/ +#define mac_load_check(n) if( bits < (n) ) { \ + while( bits <= 24 ) { \ + bitbuf = (bitbuf << 8) | *bs_ptr++; \ + bits += 8; \ + } \ + } +/*--------------------------------------------------------------*/ +#define mac_load(n) ( bits -= n, \ + bitval = bitbuf >> bits, \ + bitbuf -= bitval << bits, \ + bitval ) +/*======================================================================*/ +static void unpack_ba() +{ + int i, j, k; + static int nbit[4] = + {4, 4, 3, 2}; + int nstereo; + + pMP3Stream->bit_skip = 0; + nstereo = pMP3Stream->stereo_sb; + k = 0; + for (i = 0; i < 4; i++) + { + for (j = 0; j < pMP3Stream->nbat[i]; j++, k++) + { + mac_load_check(4); + ballo[k] = samp_dispatch[k] = pMP3Stream->bat[i][mac_load(nbit[i])]; + if (k >= pMP3Stream->nsb_limit) + pMP3Stream->bit_skip += bat_bit_master[samp_dispatch[k]]; + c_value[k] = look_c_value[samp_dispatch[k]]; + if (--nstereo < 0) + { + ballo[k + 1] = ballo[k]; + samp_dispatch[k] += 18; /* flag as joint */ + samp_dispatch[k + 1] = samp_dispatch[k]; /* flag for sf */ + c_value[k + 1] = c_value[k]; + k++; + j++; + } + } + } + samp_dispatch[pMP3Stream->nsb_limit] = 37; /* terminate the dispatcher with skip */ + samp_dispatch[k] = 36; /* terminate the dispatcher */ + +} +/*-------------------------------------------------------------------------*/ +static void unpack_sfs() /* unpack scale factor selectors */ +{ + int i; + + for (i = 0; i < pMP3Stream->max_sb; i++) + { + mac_load_check(2); + if (ballo[i]) + sf_dispatch[i] = mac_load(2); + else + sf_dispatch[i] = 4; /* no allo */ + } + sf_dispatch[i] = 5; /* terminate dispatcher */ +} +/*-------------------------------------------------------------------------*/ +static void unpack_sf() /* unpack scale factor */ +{ /* combine dequant and scale factors */ + int i; + + i = -1; + dispatch:switch (sf_dispatch[++i]) + { + case 0: /* 3 factors 012 */ + mac_load_check(18); + pMP3Stream->cs_factor[0][i] = c_value[i] * sf_table[mac_load(6)]; + pMP3Stream->cs_factor[1][i] = c_value[i] * sf_table[mac_load(6)]; + pMP3Stream->cs_factor[2][i] = c_value[i] * sf_table[mac_load(6)]; + goto dispatch; + case 1: /* 2 factors 002 */ + mac_load_check(12); + pMP3Stream->cs_factor[1][i] = pMP3Stream->cs_factor[0][i] = c_value[i] * sf_table[mac_load(6)]; + pMP3Stream->cs_factor[2][i] = c_value[i] * sf_table[mac_load(6)]; + goto dispatch; + case 2: /* 1 factor 000 */ + mac_load_check(6); + pMP3Stream->cs_factor[2][i] = pMP3Stream->cs_factor[1][i] = pMP3Stream->cs_factor[0][i] = + c_value[i] * sf_table[mac_load(6)]; + goto dispatch; + case 3: /* 2 factors 022 */ + mac_load_check(12); + pMP3Stream->cs_factor[0][i] = c_value[i] * sf_table[mac_load(6)]; + pMP3Stream->cs_factor[2][i] = pMP3Stream->cs_factor[1][i] = c_value[i] * sf_table[mac_load(6)]; + goto dispatch; + case 4: /* no allo */ +/*-- pMP3Stream->cs_factor[2][i] = pMP3Stream->cs_factor[1][i] = pMP3Stream->cs_factor[0][i] = 0.0; --*/ + goto dispatch; + case 5: /* all done */ + ; + } /* end switch */ +} +/*-------------------------------------------------------------------------*/ +#define UNPACK_N(n) s[k] = pMP3Stream->cs_factor[i][k]*(load(n)-((1 << (n-1)) -1)); \ + s[k+64] = pMP3Stream->cs_factor[i][k]*(load(n)-((1 << (n-1)) -1)); \ + s[k+128] = pMP3Stream->cs_factor[i][k]*(load(n)-((1 << (n-1)) -1)); \ + goto dispatch; +#define UNPACK_N2(n) mac_load_check(3*n); \ + s[k] = pMP3Stream->cs_factor[i][k]*(mac_load(n)-((1 << (n-1)) -1)); \ + s[k+64] = pMP3Stream->cs_factor[i][k]*(mac_load(n)-((1 << (n-1)) -1)); \ + s[k+128] = pMP3Stream->cs_factor[i][k]*(mac_load(n)-((1 << (n-1)) -1)); \ + goto dispatch; +#define UNPACK_N3(n) mac_load_check(2*n); \ + s[k] = pMP3Stream->cs_factor[i][k]*(mac_load(n)-((1 << (n-1)) -1)); \ + s[k+64] = pMP3Stream->cs_factor[i][k]*(mac_load(n)-((1 << (n-1)) -1)); \ + mac_load_check(n); \ + s[k+128] = pMP3Stream->cs_factor[i][k]*(mac_load(n)-((1 << (n-1)) -1)); \ + goto dispatch; +#define UNPACKJ_N(n) tmp = (load(n)-((1 << (n-1)) -1)); \ + s[k] = pMP3Stream->cs_factor[i][k]*tmp; \ + s[k+1] = pMP3Stream->cs_factor[i][k+1]*tmp; \ + tmp = (load(n)-((1 << (n-1)) -1)); \ + s[k+64] = pMP3Stream->cs_factor[i][k]*tmp; \ + s[k+64+1] = pMP3Stream->cs_factor[i][k+1]*tmp; \ + tmp = (load(n)-((1 << (n-1)) -1)); \ + s[k+128] = pMP3Stream->cs_factor[i][k]*tmp; \ + s[k+128+1] = pMP3Stream->cs_factor[i][k+1]*tmp; \ + k++; /* skip right chan dispatch */ \ + goto dispatch; +/*-------------------------------------------------------------------------*/ +static void unpack_samp() /* unpack samples */ +{ + int i, j, k; + float *s; + int n; + long tmp; + + s = sample; + for (i = 0; i < 3; i++) + { /* 3 groups of scale factors */ + for (j = 0; j < 4; j++) + { + k = -1; + dispatch:switch (samp_dispatch[++k]) + { + case 0: + s[k + 128] = s[k + 64] = s[k] = 0.0F; + goto dispatch; + case 1: /* 3 levels grouped 5 bits */ + mac_load_check(5); + n = mac_load(5); + s[k] = pMP3Stream->cs_factor[i][k] * group3_table[n][0]; + s[k + 64] = pMP3Stream->cs_factor[i][k] * group3_table[n][1]; + s[k + 128] = pMP3Stream->cs_factor[i][k] * group3_table[n][2]; + goto dispatch; + case 2: /* 5 levels grouped 7 bits */ + mac_load_check(7); + n = mac_load(7); + s[k] = pMP3Stream->cs_factor[i][k] * group5_table[n][0]; + s[k + 64] = pMP3Stream->cs_factor[i][k] * group5_table[n][1]; + s[k + 128] = pMP3Stream->cs_factor[i][k] * group5_table[n][2]; + goto dispatch; + case 3: + UNPACK_N2(3) /* 7 levels */ + case 4: /* 9 levels grouped 10 bits */ + mac_load_check(10); + n = mac_load(10); + s[k] = pMP3Stream->cs_factor[i][k] * group9_table[n][0]; + s[k + 64] = pMP3Stream->cs_factor[i][k] * group9_table[n][1]; + s[k + 128] = pMP3Stream->cs_factor[i][k] * group9_table[n][2]; + goto dispatch; + case 5: + UNPACK_N2(4) /* 15 levels */ + case 6: + UNPACK_N2(5) /* 31 levels */ + case 7: + UNPACK_N2(6) /* 63 levels */ + case 8: + UNPACK_N2(7) /* 127 levels */ + case 9: + UNPACK_N2(8) /* 255 levels */ + case 10: + UNPACK_N3(9) /* 511 levels */ + case 11: + UNPACK_N3(10) /* 1023 levels */ + case 12: + UNPACK_N3(11) /* 2047 levels */ + case 13: + UNPACK_N3(12) /* 4095 levels */ + case 14: + UNPACK_N(13) /* 8191 levels */ + case 15: + UNPACK_N(14) /* 16383 levels */ + case 16: + UNPACK_N(15) /* 32767 levels */ + case 17: + UNPACK_N(16) /* 65535 levels */ +/* -- joint ---- */ + case 18 + 0: + s[k + 128 + 1] = s[k + 128] = s[k + 64 + 1] = s[k + 64] = s[k + 1] = s[k] = 0.0F; + k++; /* skip right chan dispatch */ + goto dispatch; + case 18 + 1: /* 3 levels grouped 5 bits */ + n = load(5); + s[k] = pMP3Stream->cs_factor[i][k] * group3_table[n][0]; + s[k + 1] = pMP3Stream->cs_factor[i][k + 1] * group3_table[n][0]; + s[k + 64] = pMP3Stream->cs_factor[i][k] * group3_table[n][1]; + s[k + 64 + 1] = pMP3Stream->cs_factor[i][k + 1] * group3_table[n][1]; + s[k + 128] = pMP3Stream->cs_factor[i][k] * group3_table[n][2]; + s[k + 128 + 1] = pMP3Stream->cs_factor[i][k + 1] * group3_table[n][2]; + k++; /* skip right chan dispatch */ + goto dispatch; + case 18 + 2: /* 5 levels grouped 7 bits */ + n = load(7); + s[k] = pMP3Stream->cs_factor[i][k] * group5_table[n][0]; + s[k + 1] = pMP3Stream->cs_factor[i][k + 1] * group5_table[n][0]; + s[k + 64] = pMP3Stream->cs_factor[i][k] * group5_table[n][1]; + s[k + 64 + 1] = pMP3Stream->cs_factor[i][k + 1] * group5_table[n][1]; + s[k + 128] = pMP3Stream->cs_factor[i][k] * group5_table[n][2]; + s[k + 128 + 1] = pMP3Stream->cs_factor[i][k + 1] * group5_table[n][2]; + k++; /* skip right chan dispatch */ + goto dispatch; + case 18 + 3: + UNPACKJ_N(3) /* 7 levels */ + case 18 + 4: /* 9 levels grouped 10 bits */ + n = load(10); + s[k] = pMP3Stream->cs_factor[i][k] * group9_table[n][0]; + s[k + 1] = pMP3Stream->cs_factor[i][k + 1] * group9_table[n][0]; + s[k + 64] = pMP3Stream->cs_factor[i][k] * group9_table[n][1]; + s[k + 64 + 1] = pMP3Stream->cs_factor[i][k + 1] * group9_table[n][1]; + s[k + 128] = pMP3Stream->cs_factor[i][k] * group9_table[n][2]; + s[k + 128 + 1] = pMP3Stream->cs_factor[i][k + 1] * group9_table[n][2]; + k++; /* skip right chan dispatch */ + goto dispatch; + case 18 + 5: + UNPACKJ_N(4) /* 15 levels */ + case 18 + 6: + UNPACKJ_N(5) /* 31 levels */ + case 18 + 7: + UNPACKJ_N(6) /* 63 levels */ + case 18 + 8: + UNPACKJ_N(7) /* 127 levels */ + case 18 + 9: + UNPACKJ_N(8) /* 255 levels */ + case 18 + 10: + UNPACKJ_N(9) /* 511 levels */ + case 18 + 11: + UNPACKJ_N(10) /* 1023 levels */ + case 18 + 12: + UNPACKJ_N(11) /* 2047 levels */ + case 18 + 13: + UNPACKJ_N(12) /* 4095 levels */ + case 18 + 14: + UNPACKJ_N(13) /* 8191 levels */ + case 18 + 15: + UNPACKJ_N(14) /* 16383 levels */ + case 18 + 16: + UNPACKJ_N(15) /* 32767 levels */ + case 18 + 17: + UNPACKJ_N(16) /* 65535 levels */ +/* -- end of dispatch -- */ + case 37: + skip(pMP3Stream->bit_skip); + case 36: + s += 3 * 64; + } /* end switch */ + } /* end j loop */ + } /* end i loop */ + + +} +/*-------------------------------------------------------------------------*/ +unsigned char *gpNextByteAfterData = NULL; +IN_OUT audio_decode(unsigned char *bs, signed short *pcm, unsigned char *pNextByteAfterData) +{ + gpNextByteAfterData = pNextByteAfterData; // sigh.... + return audio_decode_routine(bs, pcm); +} +/*-------------------------------------------------------------------------*/ +IN_OUT L2audio_decode(unsigned char *bs, signed short *pcm) +{ + int sync, prot; + IN_OUT in_out; + + load_init(bs); /* initialize bit getter */ +/* test sync */ + in_out.in_bytes = 0; /* assume fail */ + in_out.out_bytes = 0; + sync = load(12); + if (sync != 0xFFF) + return in_out; /* sync fail */ + + load(3); /* skip id and option (checked by init) */ + prot = load(1); /* load prot bit */ + load(6); /* skip to pad */ + pMP3Stream->pad = load(1); + load(1); /* skip to mode */ + pMP3Stream->stereo_sb = look_joint[load(4)]; + if (prot) + load(4); /* skip to data */ + else + load(20); /* skip crc */ + + unpack_ba(); /* unpack bit allocation */ + unpack_sfs(); /* unpack scale factor selectors */ + unpack_sf(); /* unpack scale factor */ + unpack_samp(); /* unpack samples */ + + pMP3Stream->sbt(sample, pcm, 36); +/*-----------*/ + in_out.in_bytes = pMP3Stream->framebytes + pMP3Stream->pad; + in_out.out_bytes = pMP3Stream->outbytes; + + return in_out; +} +/*-------------------------------------------------------------------------*/ +#define COMPILE_ME +#include "cupini.c" /* initialization */ +#include "cupL1.c" /* Layer I */ +/*-------------------------------------------------------------------------*/ diff --git a/code/mp3code/cupini.c b/code/mp3code/cupini.c new file mode 100644 index 0000000..3e333b9 --- /dev/null +++ b/code/mp3code/cupini.c @@ -0,0 +1,401 @@ +#pragma warning(disable:4206) // nonstandard extension used : translation unit is empty +#ifdef COMPILE_ME +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: cupini.c,v 1.3 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/*========================================================= + initialization for cup.c - include to cup.c + mpeg audio decoder portable "c" + +mod 8/6/96 add 8 bit output + +mod 5/10/95 add quick (low precision) window + +mod 5/16/95 sb limit for reduced samprate output + changed from 94% to 100% of Nyquist sb + +mod 11/15/95 for Layer I + + +=========================================================*/ +/*-- compiler bug, floating constant overflow w/ansi --*/ +#ifdef _MSC_VER +#pragma warning(disable:4056) +#endif + + + + +static const long steps[18] = +{ + 0, 3, 5, 7, 9, 15, 31, 63, 127, + 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535}; + + +/* ABCD_INDEX = lookqt[mode][sr_index][br_index] */ +/* -1 = invalid */ +static const signed char lookqt[4][3][16] = +{ + {{1, -1, -1, -1, 2, -1, 2, 0, 0, 0, 1, 1, 1, 1, 1, -1}, /* 44ks stereo */ + {0, -1, -1, -1, 2, -1, 2, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /* 48ks */ + {1, -1, -1, -1, 3, -1, 3, 0, 0, 0, 1, 1, 1, 1, 1, -1}}, /* 32ks */ + {{1, -1, -1, -1, 2, -1, 2, 0, 0, 0, 1, 1, 1, 1, 1, -1}, /* 44ks joint stereo */ + {0, -1, -1, -1, 2, -1, 2, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /* 48ks */ + {1, -1, -1, -1, 3, -1, 3, 0, 0, 0, 1, 1, 1, 1, 1, -1}}, /* 32ks */ + {{1, -1, -1, -1, 2, -1, 2, 0, 0, 0, 1, 1, 1, 1, 1, -1}, /* 44ks dual chan */ + {0, -1, -1, -1, 2, -1, 2, 0, 0, 0, 0, 0, 0, 0, 0, -1}, /* 48ks */ + {1, -1, -1, -1, 3, -1, 3, 0, 0, 0, 1, 1, 1, 1, 1, -1}}, /* 32ks */ +// mono extended beyond legal br index +// 1,2,2,0,0,0,1,1,1,1,1,1,1,1,1,-1, /* 44ks single chan */ +// 0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,-1, /* 48ks */ +// 1,3,3,0,0,0,1,1,1,1,1,1,1,1,1,-1, /* 32ks */ +// legal mono + {{1, 2, 2, 0, 0, 0, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1}, /* 44ks single chan */ + {0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1}, /* 48ks */ + {1, 3, 3, 0, 0, 0, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1}}, /* 32ks */ +}; + +static const long sr_table[8] = +{22050L, 24000L, 16000L, 1L, + 44100L, 48000L, 32000L, 1L}; + +/* bit allocation table look up */ +/* table per mpeg spec tables 3b2a/b/c/d /e is mpeg2 */ +/* look_bat[abcd_index][4][16] */ +static const unsigned char look_bat[5][4][16] = +{ +/* LOOK_BATA */ + {{0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17}, + {0, 1, 2, 3, 4, 5, 6, 17, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 2, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, +/* LOOK_BATB */ + {{0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17}, + {0, 1, 2, 3, 4, 5, 6, 17, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 2, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, +/* LOOK_BATC */ + {{0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 2, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, +/* LOOK_BATD */ + {{0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 2, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, +/* LOOK_BATE */ + {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 2, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, +}; + +/* look_nbat[abcd_index]][4] */ +static const unsigned char look_nbat[5][4] = +{ + {3, 8, 12, 4}, + {3, 8, 12, 7}, + {2, 0, 6, 0}, + {2, 0, 10, 0}, + {4, 0, 7, 19}, +}; + + +void sbt_mono(float *sample, short *pcm, int n); +void sbt_dual(float *sample, short *pcm, int n); +void sbt_dual_mono(float *sample, short *pcm, int n); +void sbt_dual_left(float *sample, short *pcm, int n); +void sbt_dual_right(float *sample, short *pcm, int n); +void sbt16_mono(float *sample, short *pcm, int n); +void sbt16_dual(float *sample, short *pcm, int n); +void sbt16_dual_mono(float *sample, short *pcm, int n); +void sbt16_dual_left(float *sample, short *pcm, int n); +void sbt16_dual_right(float *sample, short *pcm, int n); +void sbt8_mono(float *sample, short *pcm, int n); +void sbt8_dual(float *sample, short *pcm, int n); +void sbt8_dual_mono(float *sample, short *pcm, int n); +void sbt8_dual_left(float *sample, short *pcm, int n); +void sbt8_dual_right(float *sample, short *pcm, int n); + +/*--- 8 bit output ---*/ +void sbtB_mono(float *sample, unsigned char *pcm, int n); +void sbtB_dual(float *sample, unsigned char *pcm, int n); +void sbtB_dual_mono(float *sample, unsigned char *pcm, int n); +void sbtB_dual_left(float *sample, unsigned char *pcm, int n); +void sbtB_dual_right(float *sample, unsigned char *pcm, int n); +void sbtB16_mono(float *sample, unsigned char *pcm, int n); +void sbtB16_dual(float *sample, unsigned char *pcm, int n); +void sbtB16_dual_mono(float *sample, unsigned char *pcm, int n); +void sbtB16_dual_left(float *sample, unsigned char *pcm, int n); +void sbtB16_dual_right(float *sample, unsigned char *pcm, int n); +void sbtB8_mono(float *sample, unsigned char *pcm, int n); +void sbtB8_dual(float *sample, unsigned char *pcm, int n); +void sbtB8_dual_mono(float *sample, unsigned char *pcm, int n); +void sbtB8_dual_left(float *sample, unsigned char *pcm, int n); +void sbtB8_dual_right(float *sample, unsigned char *pcm, int n); + + +static const SBT_FUNCTION sbt_table[2][3][5] = +{ + {{sbt_mono, sbt_dual, sbt_dual_mono, sbt_dual_left, sbt_dual_right}, + {sbt16_mono, sbt16_dual, sbt16_dual_mono, sbt16_dual_left, sbt16_dual_right}, + {sbt8_mono, sbt8_dual, sbt8_dual_mono, sbt8_dual_left, sbt8_dual_right}}, + {{(SBT_FUNCTION) sbtB_mono, + (SBT_FUNCTION) sbtB_dual, + (SBT_FUNCTION) sbtB_dual_mono, + (SBT_FUNCTION) sbtB_dual_left, + (SBT_FUNCTION) sbtB_dual_right}, + {(SBT_FUNCTION) sbtB16_mono, + (SBT_FUNCTION) sbtB16_dual, + (SBT_FUNCTION) sbtB16_dual_mono, + (SBT_FUNCTION) sbtB16_dual_left, + (SBT_FUNCTION) sbtB16_dual_right}, + {(SBT_FUNCTION) sbtB8_mono, + (SBT_FUNCTION) sbtB8_dual, + (SBT_FUNCTION) sbtB8_dual_mono, + (SBT_FUNCTION) sbtB8_dual_left, + (SBT_FUNCTION) sbtB8_dual_right}}, +}; + +static const int out_chans[5] = +{1, 2, 1, 1, 1}; + + +int audio_decode_initL1(MPEG_HEAD * h, int framebytes_arg, + int reduction_code, int transform_code, int convert_code, + int freq_limit); +void sbt_init(); + + +IN_OUT L1audio_decode(unsigned char *bs, signed short *pcm); +IN_OUT L2audio_decode(unsigned char *bs, signed short *pcm); +IN_OUT L3audio_decode(unsigned char *bs, unsigned char *pcm); +static const AUDIO_DECODE_ROUTINE decode_routine_table[4] = +{ + L2audio_decode, + (AUDIO_DECODE_ROUTINE)L3audio_decode, + L2audio_decode, + L1audio_decode,}; + +/*---------------------------------------------------------*/ +static void table_init() +{ + int i, j; + int code; + static int iOnceOnly=0; + + if (!iOnceOnly++) + { + /*-- c_values (dequant) --*/ + for (i = 1; i < 18; i++) + look_c_value[i] = 2.0F / steps[i]; + + /*-- scale factor table, scale by 32768 for 16 pcm output --*/ + for (i = 0; i < 64; i++) + sf_table[i] = (float) (32768.0 * 2.0 * pow(2.0, -i / 3.0)); + + /*-- grouped 3 level lookup table 5 bit token --*/ + for (i = 0; i < 32; i++) + { + code = i; + for (j = 0; j < 3; j++) + { + group3_table[i][j] = (char) ((code % 3) - 1); + code /= 3; + } + } + + /*-- grouped 5 level lookup table 7 bit token --*/ + for (i = 0; i < 128; i++) + { + code = i; + for (j = 0; j < 3; j++) + { + group5_table[i][j] = (char) ((code % 5) - 2); + code /= 5; + } + } + + /*-- grouped 9 level lookup table 10 bit token --*/ + for (i = 0; i < 1024; i++) + { + code = i; + for (j = 0; j < 3; j++) + { + group9_table[i][j] = (short) ((code % 9) - 4); + code /= 9; + } + } + } +} +/*---------------------------------------------------------*/ +int L1audio_decode_init(MPEG_HEAD * h, int framebytes_arg, + int reduction_code, int transform_code, int convert_code, + int freq_limit); +int L3audio_decode_init(MPEG_HEAD * h, int framebytes_arg, + int reduction_code, int transform_code, int convert_code, + int freq_limit); + +/*---------------------------------------------------------*/ +/* mpeg_head defined in mhead.h frame bytes is without pad */ +int audio_decode_init(MPEG_HEAD * h, int framebytes_arg, + int reduction_code, int transform_code, int convert_code, + int freq_limit) +{ + int i, j, k; + static int first_pass = 1; + int abcd_index; + long samprate; + int limit; + int bit_code; + + if (first_pass) + { + table_init(); + first_pass = 0; + } + +/* select decoder routine Layer I,II,III */ + audio_decode_routine = decode_routine_table[h->option & 3]; + + + if (h->option == 3) /* layer I */ + return L1audio_decode_init(h, framebytes_arg, + reduction_code, transform_code, convert_code, freq_limit); + + if (h->option == 1) /* layer III */ + return L3audio_decode_init(h, framebytes_arg, + reduction_code, transform_code, convert_code, freq_limit); + + + + transform_code = transform_code; /* not used, asm compatability */ + bit_code = 0; + if (convert_code & 8) + bit_code = 1; + convert_code = convert_code & 3; /* higher bits used by dec8 freq cvt */ + if (reduction_code < 0) + reduction_code = 0; + if (reduction_code > 2) + reduction_code = 2; + if (freq_limit < 1000) + freq_limit = 1000; + + + pMP3Stream->framebytes = framebytes_arg; +/* check if code handles */ + if (h->option != 2) + return 0; /* layer II only */ + if (h->sr_index == 3) + return 0; /* reserved */ + +/* compute abcd index for bit allo table selection */ + if (h->id) /* mpeg 1 */ + abcd_index = lookqt[h->mode][h->sr_index][h->br_index]; + else + abcd_index = 4; /* mpeg 2 */ + + if (abcd_index < 0) + return 0; // fail invalid Layer II bit rate index + + for (i = 0; i < 4; i++) + for (j = 0; j < 16; j++) + pMP3Stream->bat[i][j] = look_bat[abcd_index][i][j]; + for (i = 0; i < 4; i++) + pMP3Stream->nbat[i] = look_nbat[abcd_index][i]; + pMP3Stream->max_sb = pMP3Stream->nbat[0] + pMP3Stream->nbat[1] + pMP3Stream->nbat[2] + pMP3Stream->nbat[3]; +/*----- compute pMP3Stream->nsb_limit --------*/ + samprate = sr_table[4 * h->id + h->sr_index]; + pMP3Stream->nsb_limit = (freq_limit * 64L + samprate / 2) / samprate; +/*- caller limit -*/ +/*---- limit = 0.94*(32>>reduction_code); ----*/ + limit = (32 >> reduction_code); + if (limit > 8) + limit--; + if (pMP3Stream->nsb_limit > limit) + pMP3Stream->nsb_limit = limit; + if (pMP3Stream->nsb_limit > pMP3Stream->max_sb) + pMP3Stream->nsb_limit = pMP3Stream->max_sb; + + pMP3Stream->outvalues = 1152 >> reduction_code; + if (h->mode != 3) + { /* adjust for 2 channel modes */ + for (i = 0; i < 4; i++) + pMP3Stream->nbat[i] *= 2; + pMP3Stream->max_sb *= 2; + pMP3Stream->nsb_limit *= 2; + } + +/* set sbt function */ + k = 1 + convert_code; + if (h->mode == 3) + { + k = 0; + } + pMP3Stream->sbt = sbt_table[bit_code][reduction_code][k]; + pMP3Stream->outvalues *= out_chans[k]; + if (bit_code) + pMP3Stream->outbytes = pMP3Stream->outvalues; + else + pMP3Stream->outbytes = sizeof(short) * pMP3Stream->outvalues; + + decinfo.channels = out_chans[k]; + decinfo.outvalues = pMP3Stream->outvalues; + decinfo.samprate = samprate >> reduction_code; + if (bit_code) + decinfo.bits = 8; + else + decinfo.bits = sizeof(short) * 8; + + decinfo.framebytes = pMP3Stream->framebytes; + decinfo.type = 0; + + + +/* clear sample buffer, unused sub bands must be 0 */ + for (i = 0; i < 2304*2; i++) // the *2 here was inserted by me just in case, since the array is now *2, because of stereo files unpacking at 4608 bytes per frame (which may or may not be relevant, but in any case I don't think we use the L1 versions of MP3 now anyway + sample[i] = 0.0F; + + +/* init sub-band transform */ + sbt_init(); + + return 1; +} +/*---------------------------------------------------------*/ +void audio_decode_info(DEC_INFO * info) +{ + *info = decinfo; /* info return, call after init */ +} +/*---------------------------------------------------------*/ +void decode_table_init() +{ +/* dummy for asm version compatability */ +} +/*---------------------------------------------------------*/ +#endif // #ifdef COMPILE_ME + diff --git a/code/mp3code/cupl1.c b/code/mp3code/cupl1.c new file mode 100644 index 0000000..3174208 --- /dev/null +++ b/code/mp3code/cupl1.c @@ -0,0 +1,325 @@ +#pragma warning(disable:4206) // nonstandard extension used : translation unit is empty +#pragma warning(disable:4711) // function 'xxxx' selected for automatic inline expansion +#ifdef COMPILE_ME +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: cupL1.c,v 1.3 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** cupL1.c *************************************************** + +MPEG audio decoder Layer I mpeg1 and mpeg2 + +include to clup.c + + +******************************************************************/ +/*======================================================================*/ +static const int bat_bit_masterL1[] = +{ + 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 +}; +////@@@@static float *pMP3Stream->cs_factorL1 = &pMP3Stream->cs_factor[0]; // !!!!!!!!!!!!!!!! +static float look_c_valueL1[16]; // effectively constant +////@@@@static int nbatL1 = 32; + +/*======================================================================*/ +static void unpack_baL1() +{ + int j; + int nstereo; + + pMP3Stream->bit_skip = 0; + nstereo = pMP3Stream->stereo_sb; + + for (j = 0; j < pMP3Stream->nbatL1; j++) + { + mac_load_check(4); + ballo[j] = samp_dispatch[j] = mac_load(4); + if (j >= pMP3Stream->nsb_limit) + pMP3Stream->bit_skip += bat_bit_masterL1[samp_dispatch[j]]; + c_value[j] = look_c_valueL1[samp_dispatch[j]]; + if (--nstereo < 0) + { + ballo[j + 1] = ballo[j]; + samp_dispatch[j] += 15; /* flag as joint */ + samp_dispatch[j + 1] = samp_dispatch[j]; /* flag for sf */ + c_value[j + 1] = c_value[j]; + j++; + } + } +/*-- terminate with bit skip and end --*/ + samp_dispatch[pMP3Stream->nsb_limit] = 31; + samp_dispatch[j] = 30; +} +/*-------------------------------------------------------------------------*/ +static void unpack_sfL1(void) /* unpack scale factor */ +{ /* combine dequant and scale factors */ + int i; + + for (i = 0; i < pMP3Stream->nbatL1; i++) + { + if (ballo[i]) + { + mac_load_check(6); + pMP3Stream->cs_factorL1[i] = c_value[i] * sf_table[mac_load(6)]; + } + } +/*-- done --*/ +} +/*-------------------------------------------------------------------------*/ +#define UNPACKL1_N(n) s[k] = pMP3Stream->cs_factorL1[k]*(load(n)-((1 << (n-1)) -1)); \ + goto dispatch; +#define UNPACKL1J_N(n) tmp = (load(n)-((1 << (n-1)) -1)); \ + s[k] = pMP3Stream->cs_factorL1[k]*tmp; \ + s[k+1] = pMP3Stream->cs_factorL1[k+1]*tmp; \ + k++; \ + goto dispatch; +/*-------------------------------------------------------------------------*/ +static void unpack_sampL1() /* unpack samples */ +{ + int j, k; + float *s; + long tmp; + + s = sample; + for (j = 0; j < 12; j++) + { + k = -1; + dispatch:switch (samp_dispatch[++k]) + { + case 0: + s[k] = 0.0F; + goto dispatch; + case 1: + UNPACKL1_N(2) /* 3 levels */ + case 2: + UNPACKL1_N(3) /* 7 levels */ + case 3: + UNPACKL1_N(4) /* 15 levels */ + case 4: + UNPACKL1_N(5) /* 31 levels */ + case 5: + UNPACKL1_N(6) /* 63 levels */ + case 6: + UNPACKL1_N(7) /* 127 levels */ + case 7: + UNPACKL1_N(8) /* 255 levels */ + case 8: + UNPACKL1_N(9) /* 511 levels */ + case 9: + UNPACKL1_N(10) /* 1023 levels */ + case 10: + UNPACKL1_N(11) /* 2047 levels */ + case 11: + UNPACKL1_N(12) /* 4095 levels */ + case 12: + UNPACKL1_N(13) /* 8191 levels */ + case 13: + UNPACKL1_N(14) /* 16383 levels */ + case 14: + UNPACKL1_N(15) /* 32767 levels */ +/* -- joint ---- */ + case 15 + 0: + s[k + 1] = s[k] = 0.0F; + k++; /* skip right chan dispatch */ + goto dispatch; +/* -- joint ---- */ + case 15 + 1: + UNPACKL1J_N(2) /* 3 levels */ + case 15 + 2: + UNPACKL1J_N(3) /* 7 levels */ + case 15 + 3: + UNPACKL1J_N(4) /* 15 levels */ + case 15 + 4: + UNPACKL1J_N(5) /* 31 levels */ + case 15 + 5: + UNPACKL1J_N(6) /* 63 levels */ + case 15 + 6: + UNPACKL1J_N(7) /* 127 levels */ + case 15 + 7: + UNPACKL1J_N(8) /* 255 levels */ + case 15 + 8: + UNPACKL1J_N(9) /* 511 levels */ + case 15 + 9: + UNPACKL1J_N(10) /* 1023 levels */ + case 15 + 10: + UNPACKL1J_N(11) /* 2047 levels */ + case 15 + 11: + UNPACKL1J_N(12) /* 4095 levels */ + case 15 + 12: + UNPACKL1J_N(13) /* 8191 levels */ + case 15 + 13: + UNPACKL1J_N(14) /* 16383 levels */ + case 15 + 14: + UNPACKL1J_N(15) /* 32767 levels */ + +/* -- end of dispatch -- */ + case 31: + skip(pMP3Stream->bit_skip); + case 30: + s += 64; + } /* end switch */ + } /* end j loop */ + +/*-- done --*/ +} +/*-------------------------------------------------------------------*/ +IN_OUT L1audio_decode(unsigned char *bs, signed short *pcm) +{ + int sync, prot; + IN_OUT in_out; + + load_init(bs); /* initialize bit getter */ +/* test sync */ + in_out.in_bytes = 0; /* assume fail */ + in_out.out_bytes = 0; + sync = load(12); + if (sync != 0xFFF) + return in_out; /* sync fail */ + + + load(3); /* skip id and option (checked by init) */ + prot = load(1); /* load prot bit */ + load(6); /* skip to pad */ + pMP3Stream->pad = (load(1)) << 2; + load(1); /* skip to mode */ + pMP3Stream->stereo_sb = look_joint[load(4)]; + if (prot) + load(4); /* skip to data */ + else + load(20); /* skip crc */ + + unpack_baL1(); /* unpack bit allocation */ + unpack_sfL1(); /* unpack scale factor */ + unpack_sampL1(); /* unpack samples */ + + pMP3Stream->sbt(sample, pcm, 12); +/*-----------*/ + in_out.in_bytes = pMP3Stream->framebytes + pMP3Stream->pad; + in_out.out_bytes = pMP3Stream->outbytes; + + return in_out; +} +/*-------------------------------------------------------------------------*/ +int L1audio_decode_init(MPEG_HEAD * h, int framebytes_arg, + int reduction_code, int transform_code, int convert_code, + int freq_limit) +{ + int i, k; + static int first_pass = 1; + long samprate; + int limit; + long step; + int bit_code; + +/*--- sf init done by layer II init ---*/ + if (first_pass) + { + for (step = 4, i = 1; i < 16; i++, step <<= 1) + look_c_valueL1[i] = (float) (2.0 / (step - 1)); + first_pass = 0; + } + pMP3Stream->cs_factorL1 = pMP3Stream->cs_factor[0]; + + transform_code = transform_code; /* not used, asm compatability */ + + bit_code = 0; + if (convert_code & 8) + bit_code = 1; + convert_code = convert_code & 3; /* higher bits used by dec8 freq cvt */ + if (reduction_code < 0) + reduction_code = 0; + if (reduction_code > 2) + reduction_code = 2; + if (freq_limit < 1000) + freq_limit = 1000; + + + pMP3Stream->framebytes = framebytes_arg; +/* check if code handles */ + if (h->option != 3) + return 0; /* layer I only */ + + pMP3Stream->nbatL1 = 32; + pMP3Stream->max_sb = pMP3Stream->nbatL1; +/*----- compute pMP3Stream->nsb_limit --------*/ + samprate = sr_table[4 * h->id + h->sr_index]; + pMP3Stream->nsb_limit = (freq_limit * 64L + samprate / 2) / samprate; +/*- caller limit -*/ +/*---- limit = 0.94*(32>>reduction_code); ----*/ + limit = (32 >> reduction_code); + if (limit > 8) + limit--; + if (pMP3Stream->nsb_limit > limit) + pMP3Stream->nsb_limit = limit; + if (pMP3Stream->nsb_limit > pMP3Stream->max_sb) + pMP3Stream->nsb_limit = pMP3Stream->max_sb; + + pMP3Stream->outvalues = 384 >> reduction_code; + if (h->mode != 3) + { /* adjust for 2 channel modes */ + pMP3Stream->nbatL1 *= 2; + pMP3Stream->max_sb *= 2; + pMP3Stream->nsb_limit *= 2; + } + +/* set sbt function */ + k = 1 + convert_code; + if (h->mode == 3) + { + k = 0; + } + pMP3Stream->sbt = sbt_table[bit_code][reduction_code][k]; + pMP3Stream->outvalues *= out_chans[k]; + + if (bit_code) + pMP3Stream->outbytes = pMP3Stream->outvalues; + else + pMP3Stream->outbytes = sizeof(short) * pMP3Stream->outvalues; + + decinfo.channels = out_chans[k]; + decinfo.outvalues = pMP3Stream->outvalues; + decinfo.samprate = samprate >> reduction_code; + if (bit_code) + decinfo.bits = 8; + else + decinfo.bits = sizeof(short) * 8; + + decinfo.framebytes = pMP3Stream->framebytes; + decinfo.type = 0; + + +/* clear sample buffer, unused sub bands must be 0 */ + for (i = 0; i < 768; i++) + sample[i] = 0.0F; + + +/* init sub-band transform */ + sbt_init(); + + return 1; +} +/*---------------------------------------------------------*/ +#endif // #ifdef COMPILE_ME diff --git a/code/mp3code/cupl3.c b/code/mp3code/cupl3.c new file mode 100644 index 0000000..7b1b608 --- /dev/null +++ b/code/mp3code/cupl3.c @@ -0,0 +1,1287 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: cupl3.c,v 1.8 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** cupL3.c *************************************************** +unpack Layer III + + +mod 8/18/97 bugfix crc problem + +mod 10/9/97 add pMP3Stream->band_limit12 for short blocks + +mod 10/22/97 zero buf_ptrs in init + +mod 5/15/98 mpeg 2.5 + +mod 8/19/98 decode 22 sf bands + +******************************************************************/ + +/*--------------------------------------- +TO DO: Test mixed blocks (mixed long/short) + No mixed blocks in mpeg-1 test stream being used for development + +-----------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include +#include "mhead.h" /* mpeg header structure */ +#include "L3.h" +#include "jdw.h" + +#include "mp3struct.h" + +/*====================================================================*/ +static const int mp_sr20_table[2][4] = +{{441, 480, 320, -999}, {882, 960, 640, -999}}; +static const int mp_br_tableL3[2][16] = +{{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, /* mpeg 2 */ + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}}; + +/*====================================================================*/ + +/*-- global band tables */ +/*-- short portion is 3*x !! --*/ +////@@@@int nBand[2][22]; /* [long/short][cb] */ +////@@@@int sfBandIndex[2][22]; /* [long/short][cb] */ + +/*====================================================================*/ + +/*----------------*/ +extern DEC_INFO decinfo; ////@@@@ this is ok, only written to during init, then chucked. + +/*----------------*/ +////@@@@static int pMP3Stream->mpeg25_flag; // L3 only + +//int iframe; + +/*-------*/ +////@@@@static int pMP3Stream->band_limit = (576); // L3 only +////@@@@static int pMP3Stream->band_limit21 = (576); // limit for sf band 21 // L3 only +////@@@@static int pMP3Stream->band_limit12 = (576); // limit for sf band 12 short //L3 only + +////@@@@int band_limit_nsb = 32; /* global for hybrid */ +////@@@@static int pMP3Stream->nsb_limit = 32; +////@@@@static int pMP3Stream->gain_adjust = 0; /* adjust gain e.g. cvt to mono */ // L3 only +////@@@@static int id; // L3 only +////@@@@static int pMP3Stream->ncbl_mixed; /* number of long cb's in mixed block 8 or 6 */ // L3 only +////@@@@static int pMP3Stream->sr_index; // L3 only (99%) + +//@@@@ +////@@@@static int pMP3Stream->outvalues; // +////@@@@static int pMP3Stream->outbytes; // +////@@@@static int pMP3Stream->half_outbytes; // L3 only +////@@@@static int pMP3Stream->framebytes; // + +//static int padframebytes; +////@@@@static int pMP3Stream->crcbytes; // L3 only +////@@@@static int pMP3Stream->pad; // +//static int stereo_flag; // only written to +////@@@@static int pMP3Stream->nchan; // L3 only +////@@@@static int pMP3Stream->ms_mode; // L3 only (99%) +////@@@@static int pMP3Stream->is_mode; // L3 only +////@@@@static unsigned int pMP3Stream->zero_level_pcm = 0; // L3 only + +/* cb_info[igr][ch], compute by dequant, used by joint */ +static CB_INFO cb_info[2][2]; // L3 only ############ I think this is ok, only a scratchpad? +static IS_SF_INFO is_sf_info; /* MPEG-2 intensity stereo */ // L3 only ############## scratchpad? + +/*---------------------------------*/ +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +/* main data bit buffer */ +/*@@@@ +#define NBUF (8*1024) +#define BUF_TRIGGER (NBUF-1500) +static unsigned char buf[NBUF]; +static int buf_ptr0 = 0; // !!!!!!!!!!! +static int buf_ptr1 = 0; // !!!!!!!!!!! +static int main_pos_bit; +*/ +/*---------------------------------*/ +static SIDE_INFO side_info; // ####### scratchpad? + +static SCALEFACT sf[2][2]; /* [gr][ch] */ // ########## scratchpad? + +static int nsamp[2][2]; /* must start = 0, for nsamp[igr_prev] */ // ########## scratchpad? + +/*- sample union of int/float sample[ch][gr][576] */ +/* static SAMPLE sample[2][2][576]; */ +// @@@@FINDME +////@@@@extern SAMPLE sample[2][2][576]; ////////////????? suspicious, mainly used in decode loop, but zeroed init code +static float yout[576]; /* hybrid out, sbt in */ //////////// scratchpad + +////@@@@typedef void (*SBT_FUNCTION) (float *sample, short *pcm, int ch); +void sbt_dual_L3(float *sample, short *pcm, int n); +////@@@@static SBT_FUNCTION sbt_L3 = sbt_dual_L3; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +////@@@@typedef void (*XFORM_FUNCTION) (void *pcm, int igr); +static void Xform_dual(void *pcm, int igr); +////@@@@static XFORM_FUNCTION Xform = Xform_dual; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +IN_OUT L3audio_decode_MPEG1(unsigned char *bs, unsigned char *pcm); +IN_OUT L3audio_decode_MPEG2(unsigned char *bs, unsigned char *pcm); +////@@@@typedef IN_OUT(*DECODE_FUNCTION) (unsigned char *bs, unsigned char *pcm); +////@@@@static DECODE_FUNCTION decode_function = L3audio_decode_MPEG1; <------------------ needs streaming, ditto above!!! + + +/*====================================================================*/ +int hybrid(void *xin, void *xprev, float *y, + int btype, int nlong, int ntot, int nprev); +int hybrid_sum(void *xin, void *xin_left, float *y, + int btype, int nlong, int ntot); +void sum_f_bands(void *a, void *b, int n); +void FreqInvert(float *y, int n); +void antialias(void *x, int n); +void ms_process(void *x, int n); /* sum-difference stereo */ +void is_process_MPEG1(void *x, /* intensity stereo */ + SCALEFACT * sf, + CB_INFO cb_info[2], /* [ch] */ + int nsamp, int ms_mode); +void is_process_MPEG2(void *x, /* intensity stereo */ + SCALEFACT * sf, + CB_INFO cb_info[2], /* [ch] */ + IS_SF_INFO * is_sf_info, + int nsamp, int ms_mode); + +void unpack_huff(void *xy, int n, int ntable); +int unpack_huff_quad(void *vwxy, int n, int nbits, int ntable); +void dequant(SAMPLE sample[], int *nsamp, + SCALEFACT * sf, + GR * gr, + CB_INFO * cb_info, int ncbl_mixed); +void unpack_sf_sub_MPEG1(SCALEFACT * scalefac, GR * gr, + int scfsi, /* bit flag */ + int igr); +void unpack_sf_sub_MPEG2(SCALEFACT sf[], /* return intensity scale */ + GR * grdat, + int is_and_ch, IS_SF_INFO * is_sf_info); + +/*====================================================================*/ +/* get bits from bitstream in endian independent way */ + +BITDAT bitdat; /* global for inline use by Huff */ // !!!!!!!!!!!!!!!!!!! + +/*------------- initialize bit getter -------------*/ +static void bitget_init(unsigned char *buf) +{ + bitdat.bs_ptr0 = bitdat.bs_ptr = buf; + bitdat.bits = 0; + bitdat.bitbuf = 0; +} +/*------------- initialize bit getter -------------*/ +static void bitget_init_end(unsigned char *buf_end) +{ + bitdat.bs_ptr_end = buf_end; +} +/*------------- get n bits from bitstream -------------*/ +int bitget_bits_used() +{ + int n; /* compute bits used from last init call */ + + n = ((bitdat.bs_ptr - bitdat.bs_ptr0) << 3) - bitdat.bits; + return n; +} +/*------------- check for n bits in bitbuf -------------*/ +void bitget_check(int n) +{ + if (bitdat.bits < n) + { + while (bitdat.bits <= 24) + { + bitdat.bitbuf = (bitdat.bitbuf << 8) | *bitdat.bs_ptr++; + bitdat.bits += 8; + } + } +} +/*------------- get n bits from bitstream -------------*/ +unsigned int bitget(int n) +{ + unsigned int x; + + if (bitdat.bits < n) + { /* refill bit buf if necessary */ + while (bitdat.bits <= 24) + { + bitdat.bitbuf = (bitdat.bitbuf << 8) | *bitdat.bs_ptr++; + bitdat.bits += 8; + } + } + bitdat.bits -= n; + x = bitdat.bitbuf >> bitdat.bits; + bitdat.bitbuf -= x << bitdat.bits; + return x; +} +/*------------- get 1 bit from bitstream -------------*/ +unsigned int bitget_1bit() +{ + unsigned int x; + + if (bitdat.bits <= 0) + { /* refill bit buf if necessary */ + while (bitdat.bits <= 24) + { + bitdat.bitbuf = (bitdat.bitbuf << 8) | *bitdat.bs_ptr++; + bitdat.bits += 8; + } + } + bitdat.bits--; + x = bitdat.bitbuf >> bitdat.bits; + bitdat.bitbuf -= x << bitdat.bits; + return x; +} +/*====================================================================*/ +static void Xform_mono(void *pcm, int igr) +{ + int igr_prev, n1, n2; + +/*--- hybrid + sbt ---*/ + n1 = n2 = nsamp[igr][0]; /* total number bands */ + if (side_info.gr[igr][0].block_type == 2) + { /* long bands */ + n1 = 0; + if (side_info.gr[igr][0].mixed_block_flag) + n1 = pMP3Stream->sfBandIndex[0][pMP3Stream->ncbl_mixed - 1]; + } + if (n1 > pMP3Stream->band_limit) + n1 = pMP3Stream->band_limit; + if (n2 > pMP3Stream->band_limit) + n2 = pMP3Stream->band_limit; + igr_prev = igr ^ 1; + + nsamp[igr][0] = hybrid(pMP3Stream->sample[0][igr], pMP3Stream->sample[0][igr_prev], + yout, side_info.gr[igr][0].block_type, n1, n2, nsamp[igr_prev][0]); + FreqInvert(yout, nsamp[igr][0]); + pMP3Stream->sbt_L3(yout, pcm, 0); + +} +/*--------------------------------------------------------------------*/ +static void Xform_dual_right(void *pcm, int igr) +{ + int igr_prev, n1, n2; + +/*--- hybrid + sbt ---*/ + n1 = n2 = nsamp[igr][1]; /* total number bands */ + if (side_info.gr[igr][1].block_type == 2) + { /* long bands */ + n1 = 0; + if (side_info.gr[igr][1].mixed_block_flag) + n1 = pMP3Stream->sfBandIndex[0][pMP3Stream->ncbl_mixed - 1]; + } + if (n1 > pMP3Stream->band_limit) + n1 = pMP3Stream->band_limit; + if (n2 > pMP3Stream->band_limit) + n2 = pMP3Stream->band_limit; + igr_prev = igr ^ 1; + nsamp[igr][1] = hybrid(pMP3Stream->sample[1][igr], pMP3Stream->sample[1][igr_prev], + yout, side_info.gr[igr][1].block_type, n1, n2, nsamp[igr_prev][1]); + FreqInvert(yout, nsamp[igr][1]); + pMP3Stream->sbt_L3(yout, pcm, 0); + +} +/*--------------------------------------------------------------------*/ +static void Xform_dual(void *pcm, int igr) +{ + int ch; + int igr_prev, n1, n2; + +/*--- hybrid + sbt ---*/ + igr_prev = igr ^ 1; + for (ch = 0; ch < pMP3Stream->nchan; ch++) + { + n1 = n2 = nsamp[igr][ch]; /* total number bands */ + if (side_info.gr[igr][ch].block_type == 2) + { /* long bands */ + n1 = 0; + if (side_info.gr[igr][ch].mixed_block_flag) + n1 = pMP3Stream->sfBandIndex[0][pMP3Stream->ncbl_mixed - 1]; + } + if (n1 > pMP3Stream->band_limit) + n1 = pMP3Stream->band_limit; + if (n2 > pMP3Stream->band_limit) + n2 = pMP3Stream->band_limit; + nsamp[igr][ch] = hybrid(pMP3Stream->sample[ch][igr], pMP3Stream->sample[ch][igr_prev], + yout, side_info.gr[igr][ch].block_type, n1, n2, nsamp[igr_prev][ch]); + FreqInvert(yout, nsamp[igr][ch]); + pMP3Stream->sbt_L3(yout, pcm, ch); + } + +} +/*--------------------------------------------------------------------*/ +static void Xform_dual_mono(void *pcm, int igr) +{ + int igr_prev, n1, n2, n3; + +/*--- hybrid + sbt ---*/ + igr_prev = igr ^ 1; + if ((side_info.gr[igr][0].block_type == side_info.gr[igr][1].block_type) + && (side_info.gr[igr][0].mixed_block_flag == 0) + && (side_info.gr[igr][1].mixed_block_flag == 0)) + { + + n2 = nsamp[igr][0]; /* total number bands max of L R */ + if (n2 < nsamp[igr][1]) + n2 = nsamp[igr][1]; + if (n2 > pMP3Stream->band_limit) + n2 = pMP3Stream->band_limit; + n1 = n2; /* n1 = number long bands */ + if (side_info.gr[igr][0].block_type == 2) + n1 = 0; + sum_f_bands(pMP3Stream->sample[0][igr], pMP3Stream->sample[1][igr], n2); + n3 = nsamp[igr][0] = hybrid(pMP3Stream->sample[0][igr], pMP3Stream->sample[0][igr_prev], + yout, side_info.gr[igr][0].block_type, n1, n2, nsamp[igr_prev][0]); + } + else + { /* transform and then sum (not tested - never happens in test) */ +/*-- left chan --*/ + n1 = n2 = nsamp[igr][0]; /* total number bands */ + if (side_info.gr[igr][0].block_type == 2) + { + n1 = 0; /* long bands */ + if (side_info.gr[igr][0].mixed_block_flag) + n1 = pMP3Stream->sfBandIndex[0][pMP3Stream->ncbl_mixed - 1]; + } + n3 = nsamp[igr][0] = hybrid(pMP3Stream->sample[0][igr], pMP3Stream->sample[0][igr_prev], + yout, side_info.gr[igr][0].block_type, n1, n2, nsamp[igr_prev][0]); +/*-- right chan --*/ + n1 = n2 = nsamp[igr][1]; /* total number bands */ + if (side_info.gr[igr][1].block_type == 2) + { + n1 = 0; /* long bands */ + if (side_info.gr[igr][1].mixed_block_flag) + n1 = pMP3Stream->sfBandIndex[0][pMP3Stream->ncbl_mixed - 1]; + } + nsamp[igr][1] = hybrid_sum(pMP3Stream->sample[1][igr], pMP3Stream->sample[0][igr], + yout, side_info.gr[igr][1].block_type, n1, n2); + if (n3 < nsamp[igr][1]) + n1 = nsamp[igr][1]; + } + +/*--------*/ + FreqInvert(yout, n3); + pMP3Stream->sbt_L3(yout, pcm, 0); + +} +/*--------------------------------------------------------------------*/ +/*====================================================================*/ +static int unpack_side_MPEG1() +{ + int prot; + int br_index; + int igr, ch; + int side_bytes; + +/* decode partial header plus initial side info */ +/* at entry bit getter points at id, sync skipped by caller */ + + pMP3Stream->id = bitget(1); /* id */ + bitget(2); /* skip layer */ + prot = bitget(1); /* bitget prot bit */ + br_index = bitget(4); + pMP3Stream->sr_index = bitget(2); + pMP3Stream->pad = bitget(1); + bitget(1); /* skip to mode */ + side_info.mode = bitget(2); /* mode */ + side_info.mode_ext = bitget(2); /* mode ext */ + + if (side_info.mode != 1) + side_info.mode_ext = 0; + +/* adjust global gain in ms mode to avoid having to mult by 1/sqrt(2) */ + pMP3Stream->ms_mode = side_info.mode_ext >> 1; + pMP3Stream->is_mode = side_info.mode_ext & 1; + + + pMP3Stream->crcbytes = 0; + if (prot) + bitget(4); /* skip to data */ + else + { + bitget(20); /* skip crc */ + pMP3Stream->crcbytes = 2; + } + + if (br_index > 0) /* pMP3Stream->framebytes fixed for free format */ + { + pMP3Stream->framebytes = + 2880 * mp_br_tableL3[pMP3Stream->id][br_index] / mp_sr20_table[pMP3Stream->id][pMP3Stream->sr_index]; + } + + side_info.main_data_begin = bitget(9); + if (side_info.mode == 3) + { + side_info.private_bits = bitget(5); + pMP3Stream->nchan = 1; +// stereo_flag = 0; + side_bytes = (4 + 17); +/*-- with header --*/ + } + else + { + side_info.private_bits = bitget(3); + pMP3Stream->nchan = 2; +// stereo_flag = 1; + side_bytes = (4 + 32); +/*-- with header --*/ + } + for (ch = 0; ch < pMP3Stream->nchan; ch++) + side_info.scfsi[ch] = bitget(4); +/* this always 0 (both igr) for short blocks */ + + for (igr = 0; igr < 2; igr++) + { + for (ch = 0; ch < pMP3Stream->nchan; ch++) + { + side_info.gr[igr][ch].part2_3_length = bitget(12); + side_info.gr[igr][ch].big_values = bitget(9); + side_info.gr[igr][ch].global_gain = bitget(8) + pMP3Stream->gain_adjust; + if (pMP3Stream->ms_mode) + side_info.gr[igr][ch].global_gain -= 2; + side_info.gr[igr][ch].scalefac_compress = bitget(4); + side_info.gr[igr][ch].window_switching_flag = bitget(1); + if (side_info.gr[igr][ch].window_switching_flag) + { + side_info.gr[igr][ch].block_type = bitget(2); + side_info.gr[igr][ch].mixed_block_flag = bitget(1); + side_info.gr[igr][ch].table_select[0] = bitget(5); + side_info.gr[igr][ch].table_select[1] = bitget(5); + side_info.gr[igr][ch].subblock_gain[0] = bitget(3); + side_info.gr[igr][ch].subblock_gain[1] = bitget(3); + side_info.gr[igr][ch].subblock_gain[2] = bitget(3); + /* region count set in terms of long block cb's/bands */ + /* r1 set so r0+r1+1 = 21 (lookup produces 576 bands ) */ + /* if(window_switching_flag) always 36 samples in region0 */ + side_info.gr[igr][ch].region0_count = (8 - 1); /* 36 samples */ + side_info.gr[igr][ch].region1_count = 20 - (8 - 1); + } + else + { + side_info.gr[igr][ch].mixed_block_flag = 0; + side_info.gr[igr][ch].block_type = 0; + side_info.gr[igr][ch].table_select[0] = bitget(5); + side_info.gr[igr][ch].table_select[1] = bitget(5); + side_info.gr[igr][ch].table_select[2] = bitget(5); + side_info.gr[igr][ch].region0_count = bitget(4); + side_info.gr[igr][ch].region1_count = bitget(3); + } + side_info.gr[igr][ch].preflag = bitget(1); + side_info.gr[igr][ch].scalefac_scale = bitget(1); + side_info.gr[igr][ch].count1table_select = bitget(1); + } + } + + + +/* return bytes in header + side info */ + return side_bytes; +} +/*====================================================================*/ +static int unpack_side_MPEG2(int igr) +{ + int prot; + int br_index; + int ch; + int side_bytes; + +/* decode partial header plus initial side info */ +/* at entry bit getter points at id, sync skipped by caller */ + + pMP3Stream->id = bitget(1); /* id */ + bitget(2); /* skip layer */ + prot = bitget(1); /* bitget prot bit */ + br_index = bitget(4); + pMP3Stream->sr_index = bitget(2); + pMP3Stream->pad = bitget(1); + bitget(1); /* skip to mode */ + side_info.mode = bitget(2); /* mode */ + side_info.mode_ext = bitget(2); /* mode ext */ + + if (side_info.mode != 1) + side_info.mode_ext = 0; + +/* adjust global gain in ms mode to avoid having to mult by 1/sqrt(2) */ + pMP3Stream->ms_mode = side_info.mode_ext >> 1; + pMP3Stream->is_mode = side_info.mode_ext & 1; + + pMP3Stream->crcbytes = 0; + if (prot) + bitget(4); /* skip to data */ + else + { + bitget(20); /* skip crc */ + pMP3Stream->crcbytes = 2; + } + + if (br_index > 0) + { /* pMP3Stream->framebytes fixed for free format */ + if (pMP3Stream->mpeg25_flag == 0) + { + pMP3Stream->framebytes = + 1440 * mp_br_tableL3[pMP3Stream->id][br_index] / mp_sr20_table[pMP3Stream->id][pMP3Stream->sr_index]; + } + else + { + pMP3Stream->framebytes = + 2880 * mp_br_tableL3[pMP3Stream->id][br_index] / mp_sr20_table[pMP3Stream->id][pMP3Stream->sr_index]; + //if( pMP3Stream->sr_index == 2 ) return 0; // fail mpeg25 8khz + } + } + side_info.main_data_begin = bitget(8); + if (side_info.mode == 3) + { + side_info.private_bits = bitget(1); + pMP3Stream->nchan = 1; +// stereo_flag = 0; + side_bytes = (4 + 9); +/*-- with header --*/ + } + else + { + side_info.private_bits = bitget(2); + pMP3Stream->nchan = 2; +// stereo_flag = 1; + side_bytes = (4 + 17); +/*-- with header --*/ + } + side_info.scfsi[1] = side_info.scfsi[0] = 0; + + + for (ch = 0; ch < pMP3Stream->nchan; ch++) + { + side_info.gr[igr][ch].part2_3_length = bitget(12); + side_info.gr[igr][ch].big_values = bitget(9); + side_info.gr[igr][ch].global_gain = bitget(8) + pMP3Stream->gain_adjust; + if (pMP3Stream->ms_mode) + side_info.gr[igr][ch].global_gain -= 2; + side_info.gr[igr][ch].scalefac_compress = bitget(9); + side_info.gr[igr][ch].window_switching_flag = bitget(1); + if (side_info.gr[igr][ch].window_switching_flag) + { + side_info.gr[igr][ch].block_type = bitget(2); + side_info.gr[igr][ch].mixed_block_flag = bitget(1); + side_info.gr[igr][ch].table_select[0] = bitget(5); + side_info.gr[igr][ch].table_select[1] = bitget(5); + side_info.gr[igr][ch].subblock_gain[0] = bitget(3); + side_info.gr[igr][ch].subblock_gain[1] = bitget(3); + side_info.gr[igr][ch].subblock_gain[2] = bitget(3); + /* region count set in terms of long block cb's/bands */ + /* r1 set so r0+r1+1 = 21 (lookup produces 576 bands ) */ + /* bt=1 or 3 54 samples */ + /* bt=2 mixed=0 36 samples */ + /* bt=2 mixed=1 54 (8 long sf) samples? or maybe 36 */ + /* region0 discussion says 54 but this would mix long */ + /* and short in region0 if scale factors switch */ + /* at band 36 (6 long scale factors) */ + if ((side_info.gr[igr][ch].block_type == 2)) + { + side_info.gr[igr][ch].region0_count = (6 - 1); /* 36 samples */ + side_info.gr[igr][ch].region1_count = 20 - (6 - 1); + } + else + { /* long block type 1 or 3 */ + side_info.gr[igr][ch].region0_count = (8 - 1); /* 54 samples */ + side_info.gr[igr][ch].region1_count = 20 - (8 - 1); + } + } + else + { + side_info.gr[igr][ch].mixed_block_flag = 0; + side_info.gr[igr][ch].block_type = 0; + side_info.gr[igr][ch].table_select[0] = bitget(5); + side_info.gr[igr][ch].table_select[1] = bitget(5); + side_info.gr[igr][ch].table_select[2] = bitget(5); + side_info.gr[igr][ch].region0_count = bitget(4); + side_info.gr[igr][ch].region1_count = bitget(3); + } + side_info.gr[igr][ch].preflag = 0; + side_info.gr[igr][ch].scalefac_scale = bitget(1); + side_info.gr[igr][ch].count1table_select = bitget(1); + } + +/* return bytes in header + side info */ + return side_bytes; +} +/*-----------------------------------------------------------------*/ +static void unpack_main(unsigned char *pcm, int igr) +{ + int ch; + int bit0; + int n1, n2, n3, n4, nn2, nn3; + int nn4; + int qbits; + int m0; + + + for (ch = 0; ch < pMP3Stream->nchan; ch++) + { + bitget_init(pMP3Stream->buf + (pMP3Stream->main_pos_bit >> 3)); + bit0 = (pMP3Stream->main_pos_bit & 7); + if (bit0) + bitget(bit0); + pMP3Stream->main_pos_bit += side_info.gr[igr][ch].part2_3_length; + bitget_init_end(pMP3Stream->buf + ((pMP3Stream->main_pos_bit + 39) >> 3)); +/*-- scale factors --*/ + if (pMP3Stream->id) + unpack_sf_sub_MPEG1(&sf[igr][ch], + &side_info.gr[igr][ch], side_info.scfsi[ch], igr); + else + unpack_sf_sub_MPEG2(&sf[igr][ch], + &side_info.gr[igr][ch], pMP3Stream->is_mode & ch, &is_sf_info); +/*--- huff data ---*/ + n1 = pMP3Stream->sfBandIndex[0][side_info.gr[igr][ch].region0_count]; + n2 = pMP3Stream->sfBandIndex[0][side_info.gr[igr][ch].region0_count + + side_info.gr[igr][ch].region1_count + 1]; + n3 = side_info.gr[igr][ch].big_values; + n3 = n3 + n3; + + + if (n3 > pMP3Stream->band_limit) + n3 = pMP3Stream->band_limit; + if (n2 > n3) + n2 = n3; + if (n1 > n3) + n1 = n3; + nn3 = n3 - n2; + nn2 = n2 - n1; + unpack_huff(pMP3Stream->sample[ch][igr], n1, side_info.gr[igr][ch].table_select[0]); + unpack_huff(pMP3Stream->sample[ch][igr] + n1, nn2, side_info.gr[igr][ch].table_select[1]); + unpack_huff(pMP3Stream->sample[ch][igr] + n2, nn3, side_info.gr[igr][ch].table_select[2]); + qbits = side_info.gr[igr][ch].part2_3_length - (bitget_bits_used() - bit0); + nn4 = unpack_huff_quad(pMP3Stream->sample[ch][igr] + n3, pMP3Stream->band_limit - n3, qbits, + side_info.gr[igr][ch].count1table_select); + n4 = n3 + nn4; + nsamp[igr][ch] = n4; + //limit n4 or allow deqaunt to sf band 22 + if (side_info.gr[igr][ch].block_type == 2) + n4 = min(n4, pMP3Stream->band_limit12); + else + n4 = min(n4, pMP3Stream->band_limit21); + if (n4 < 576) + memset(pMP3Stream->sample[ch][igr] + n4, 0, sizeof(SAMPLE) * (576 - n4)); + if (bitdat.bs_ptr > bitdat.bs_ptr_end) + { // bad data overrun + + memset(pMP3Stream->sample[ch][igr], 0, sizeof(SAMPLE) * (576)); + } + } + + + +/*--- dequant ---*/ + for (ch = 0; ch < pMP3Stream->nchan; ch++) + { + dequant(pMP3Stream->sample[ch][igr], + &nsamp[igr][ch], /* nsamp updated for shorts */ + &sf[igr][ch], &side_info.gr[igr][ch], + &cb_info[igr][ch], pMP3Stream->ncbl_mixed); + } + +/*--- ms stereo processing ---*/ + if (pMP3Stream->ms_mode) + { + if (pMP3Stream->is_mode == 0) + { + m0 = nsamp[igr][0]; /* process to longer of left/right */ + if (m0 < nsamp[igr][1]) + m0 = nsamp[igr][1]; + } + else + { /* process to last cb in right */ + m0 = pMP3Stream->sfBandIndex[cb_info[igr][1].cbtype][cb_info[igr][1].cbmax]; + } + ms_process(pMP3Stream->sample[0][igr], m0); + } + +/*--- is stereo processing ---*/ + if (pMP3Stream->is_mode) + { + if (pMP3Stream->id) + is_process_MPEG1(pMP3Stream->sample[0][igr], &sf[igr][1], + cb_info[igr], nsamp[igr][0], pMP3Stream->ms_mode); + else + is_process_MPEG2(pMP3Stream->sample[0][igr], &sf[igr][1], + cb_info[igr], &is_sf_info, + nsamp[igr][0], pMP3Stream->ms_mode); + } + +/*-- adjust ms and is modes to max of left/right */ + if (side_info.mode_ext) + { + if (nsamp[igr][0] < nsamp[igr][1]) + nsamp[igr][0] = nsamp[igr][1]; + else + nsamp[igr][1] = nsamp[igr][0]; + } + +/*--- antialias ---*/ + for (ch = 0; ch < pMP3Stream->nchan; ch++) + { + if (cb_info[igr][ch].ncbl == 0) + continue; /* have no long blocks */ + if (side_info.gr[igr][ch].mixed_block_flag) + n1 = 1; /* 1 -> 36 samples */ + else + n1 = (nsamp[igr][ch] + 7) / 18; + if (n1 > 31) + n1 = 31; + antialias(pMP3Stream->sample[ch][igr], n1); + n1 = 18 * n1 + 8; /* update number of samples */ + if (n1 > nsamp[igr][ch]) + nsamp[igr][ch] = n1; + } + + + +/*--- hybrid + sbt ---*/ + pMP3Stream->Xform(pcm, igr); + + +/*-- done --*/ +} +/*--------------------------------------------------------------------*/ +/*-----------------------------------------------------------------*/ +IN_OUT L3audio_decode(unsigned char *bs, unsigned char *pcm) +{ + return pMP3Stream->decode_function(bs, pcm); +} + +/*--------------------------------------------------------------------*/ +extern unsigned char *gpNextByteAfterData; +IN_OUT L3audio_decode_MPEG1(unsigned char *bs, unsigned char *pcm) +{ + int sync; + IN_OUT in_out; + int side_bytes; + int nbytes; + + int padframebytes; ////@@@@ + +// iframe++; + + bitget_init(bs); /* initialize bit getter */ +/* test sync */ + in_out.in_bytes = 0; /* assume fail */ + in_out.out_bytes = 0; + sync = bitget(12); + + if (sync != 0xFFF) + return in_out; /* sync fail */ +/*-----------*/ + +/*-- unpack side info --*/ + side_bytes = unpack_side_MPEG1(); + padframebytes = pMP3Stream->framebytes + pMP3Stream->pad; + + if (bs + padframebytes > gpNextByteAfterData) + return in_out; // error check if we're about to read off the end of the legal memory (caused by certain MP3 writers' goofy comment formats) -ste. + in_out.in_bytes = padframebytes; + +/*-- load main data and update buf pointer --*/ +/*------------------------------------------- + if start point < 0, must just cycle decoder + if jumping into middle of stream, +w---------------------------------------------*/ + pMP3Stream->buf_ptr0 = pMP3Stream->buf_ptr1 - side_info.main_data_begin; /* decode start point */ + if (pMP3Stream->buf_ptr1 > BUF_TRIGGER) + { /* shift buffer */ + memmove(pMP3Stream->buf, pMP3Stream->buf + pMP3Stream->buf_ptr0, side_info.main_data_begin); + pMP3Stream->buf_ptr0 = 0; + pMP3Stream->buf_ptr1 = side_info.main_data_begin; + } + nbytes = padframebytes - side_bytes - pMP3Stream->crcbytes; + + // RAK: This is no bueno. :-( + if (nbytes < 0 || nbytes > NBUF) + { + in_out.in_bytes = 0; + return in_out; + } + + if (bFastEstimateOnly) + { + in_out.out_bytes = pMP3Stream->outbytes; + return in_out; + } + + memmove(pMP3Stream->buf + pMP3Stream->buf_ptr1, bs + side_bytes + pMP3Stream->crcbytes, nbytes); + pMP3Stream->buf_ptr1 += nbytes; +/*-----------------------*/ + + if (pMP3Stream->buf_ptr0 >= 0) + { +// dump_frame(buf+buf_ptr0, 64); + pMP3Stream->main_pos_bit = pMP3Stream->buf_ptr0 << 3; + unpack_main(pcm, 0); + unpack_main(pcm + pMP3Stream->half_outbytes, 1); + in_out.out_bytes = pMP3Stream->outbytes; + } + else + { + memset(pcm, pMP3Stream->zero_level_pcm, pMP3Stream->outbytes); /* fill out skipped frames */ + in_out.out_bytes = pMP3Stream->outbytes; +/* iframe--; in_out.out_bytes = 0; // test test */ + } + + return in_out; +} +/*--------------------------------------------------------------------*/ +/*--------------------------------------------------------------------*/ +IN_OUT L3audio_decode_MPEG2(unsigned char *bs, unsigned char *pcm) +{ + int sync; + IN_OUT in_out; + int side_bytes; + int nbytes; + static int igr = 0; + + int padframebytes; ////@@@@ + +// iframe++; + + + bitget_init(bs); /* initialize bit getter */ +/* test sync */ + in_out.in_bytes = 0; /* assume fail */ + in_out.out_bytes = 0; + sync = bitget(12); + +// if( sync != 0xFFF ) return in_out; /* sync fail */ + + pMP3Stream->mpeg25_flag = 0; + if (sync != 0xFFF) + { + pMP3Stream->mpeg25_flag = 1; /* mpeg 2.5 sync */ + if (sync != 0xFFE) + return in_out; /* sync fail */ + } +/*-----------*/ + + +/*-- unpack side info --*/ + side_bytes = unpack_side_MPEG2(igr); + padframebytes = pMP3Stream->framebytes + pMP3Stream->pad; + in_out.in_bytes = padframebytes; + + pMP3Stream->buf_ptr0 = pMP3Stream->buf_ptr1 - side_info.main_data_begin; /* decode start point */ + if (pMP3Stream->buf_ptr1 > BUF_TRIGGER) + { /* shift buffer */ + memmove(pMP3Stream->buf, pMP3Stream->buf + pMP3Stream->buf_ptr0, side_info.main_data_begin); + pMP3Stream->buf_ptr0 = 0; + pMP3Stream->buf_ptr1 = side_info.main_data_begin; + } + nbytes = padframebytes - side_bytes - pMP3Stream->crcbytes; + // RAK: This is no bueno. :-( + if (nbytes < 0 || nbytes > NBUF) + { + in_out.in_bytes = 0; + return in_out; + } + + if (bFastEstimateOnly) + { + in_out.out_bytes = pMP3Stream->outbytes; + return in_out; + } + + memmove(pMP3Stream->buf + pMP3Stream->buf_ptr1, bs + side_bytes + pMP3Stream->crcbytes, nbytes); + pMP3Stream->buf_ptr1 += nbytes; +/*-----------------------*/ + + if (pMP3Stream->buf_ptr0 >= 0) + { + pMP3Stream->main_pos_bit = pMP3Stream->buf_ptr0 << 3; + unpack_main(pcm, igr); + in_out.out_bytes = pMP3Stream->outbytes; + } + else + { + memset(pcm, pMP3Stream->zero_level_pcm, pMP3Stream->outbytes); /* fill out skipped frames */ + in_out.out_bytes = pMP3Stream->outbytes; +// iframe--; in_out.out_bytes = 0; return in_out;// test test */ + } + + + + igr = igr ^ 1; + return in_out; +} +/*--------------------------------------------------------------------*/ +/*--------------------------------------------------------------------*/ +/*--------------------------------------------------------------------*/ +static const int sr_table[8] = +{22050, 24000, 16000, 1, + 44100, 48000, 32000, 1}; + +static const struct +{ + int l[23]; + int s[14]; +} +sfBandIndexTable[3][3] = +{ +/* mpeg-2 */ + { + { + { + 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 + } + , + { + 0, 4, 8, 12, 18, 24, 32, 42, 56, 74, 100, 132, 174, 192 + } + } + , + { + { + 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, 278, 332, 394, 464, 540, 576 + } + , + { + 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 136, 180, 192 + } + } + , + { + { + 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 + } + , + { + 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 + } + } + , + } + , +/* mpeg-1 */ + { + { + { + 0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, 238, 288, 342, 418, 576 + } + , + { + 0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192 + } + } + , + { + { + 0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, 230, 276, 330, 384, 576 + } + , + { + 0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192 + } + } + , + { + { + 0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, 240, 296, 364, 448, 550, 576 + } + , + { + 0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192 + } + } + } + , + +/* mpeg-2.5, 11 & 12 KHz seem ok, 8 ok */ + { + { + { + 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 + } + , + { + 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 + } + } + , + { + { + 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576 + } + , + { + 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192 + } + } + , +// this 8khz table, and only 8khz, from mpeg123) + { + { + 0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, 476, 566, 568, 570, 572, 574, 576 + } + , + { + 0, 8, 16, 24, 36, 52, 72, 96, 124, 160, 162, 164, 166, 192 + } + } + , + } + , +}; + + +void sbt_mono_L3(float *sample, signed short *pcm, int ch); +void sbt_dual_L3(float *sample, signed short *pcm, int ch); +void sbt16_mono_L3(float *sample, signed short *pcm, int ch); +void sbt16_dual_L3(float *sample, signed short *pcm, int ch); +void sbt8_mono_L3(float *sample, signed short *pcm, int ch); +void sbt8_dual_L3(float *sample, signed short *pcm, int ch); + +void sbtB_mono_L3(float *sample, unsigned char *pcm, int ch); +void sbtB_dual_L3(float *sample, unsigned char *pcm, int ch); +void sbtB16_mono_L3(float *sample, unsigned char *pcm, int ch); +void sbtB16_dual_L3(float *sample, unsigned char *pcm, int ch); +void sbtB8_mono_L3(float *sample, unsigned char *pcm, int ch); +void sbtB8_dual_L3(float *sample, unsigned char *pcm, int ch); + + + +static const SBT_FUNCTION sbt_table[2][3][2] = +{ +{{ (SBT_FUNCTION) sbt_mono_L3, + (SBT_FUNCTION) sbt_dual_L3 } , + { (SBT_FUNCTION) sbt16_mono_L3, + (SBT_FUNCTION) sbt16_dual_L3 } , + { (SBT_FUNCTION) sbt8_mono_L3, + (SBT_FUNCTION) sbt8_dual_L3 }} , +/*-- 8 bit output -*/ +{{ (SBT_FUNCTION) sbtB_mono_L3, + (SBT_FUNCTION) sbtB_dual_L3 }, + { (SBT_FUNCTION) sbtB16_mono_L3, + (SBT_FUNCTION) sbtB16_dual_L3 }, + { (SBT_FUNCTION) sbtB8_mono_L3, + (SBT_FUNCTION) sbtB8_dual_L3 }} +}; + + +void Xform_mono(void *pcm, int igr); +void Xform_dual(void *pcm, int igr); +void Xform_dual_mono(void *pcm, int igr); +void Xform_dual_right(void *pcm, int igr); + +static XFORM_FUNCTION xform_table[5] = +{ + Xform_mono, + Xform_dual, + Xform_dual_mono, + Xform_mono, /* left */ + Xform_dual_right, +}; +int L3table_init(); +void msis_init(); +void sbt_init(); +typedef int iARRAY22[22]; +iARRAY22 *quant_init_band_addr(); +iARRAY22 *msis_init_band_addr(); + +/*---------------------------------------------------------*/ +/* mpeg_head defined in mhead.h frame bytes is without pMP3Stream->pad */ +////@@@@INIT +int L3audio_decode_init(MPEG_HEAD * h, int framebytes_arg, + int reduction_code, int transform_code, int convert_code, + int freq_limit) +{ + int i, j, k; + // static int first_pass = 1; + int samprate; + int limit; + int bit_code; + int out_chans; + + pMP3Stream->buf_ptr0 = 0; + pMP3Stream->buf_ptr1 = 0; + +/* check if code handles */ + if (h->option != 1) + return 0; /* layer III only */ + + if (h->id) + pMP3Stream->ncbl_mixed = 8; /* mpeg-1 */ + else + pMP3Stream->ncbl_mixed = 6; /* mpeg-2 */ + + pMP3Stream->framebytes = framebytes_arg; + + transform_code = transform_code; /* not used, asm compatability */ + bit_code = 0; + if (convert_code & 8) + bit_code = 1; + convert_code = convert_code & 3; /* higher bits used by dec8 freq cvt */ + if (reduction_code < 0) + reduction_code = 0; + if (reduction_code > 2) + reduction_code = 2; + if (freq_limit < 1000) + freq_limit = 1000; + + + samprate = sr_table[4 * h->id + h->sr_index]; + if ((h->sync & 1) == 0) + samprate = samprate / 2; // mpeg 2.5 +/*----- compute pMP3Stream->nsb_limit --------*/ + pMP3Stream->nsb_limit = (freq_limit * 64L + samprate / 2) / samprate; +/*- caller limit -*/ + limit = (32 >> reduction_code); + if (limit > 8) + limit--; + if (pMP3Stream->nsb_limit > limit) + pMP3Stream->nsb_limit = limit; + limit = 18 * pMP3Stream->nsb_limit; + + k = h->id; + if ((h->sync & 1) == 0) + k = 2; // mpeg 2.5 + + if (k == 1) + { + pMP3Stream->band_limit12 = 3 * sfBandIndexTable[k][h->sr_index].s[13]; + pMP3Stream->band_limit = pMP3Stream->band_limit21 = sfBandIndexTable[k][h->sr_index].l[22]; + } + else + { + pMP3Stream->band_limit12 = 3 * sfBandIndexTable[k][h->sr_index].s[12]; + pMP3Stream->band_limit = pMP3Stream->band_limit21 = sfBandIndexTable[k][h->sr_index].l[21]; + } + pMP3Stream->band_limit += 8; /* allow for antialias */ + if (pMP3Stream->band_limit > limit) + pMP3Stream->band_limit = limit; + + if (pMP3Stream->band_limit21 > pMP3Stream->band_limit) + pMP3Stream->band_limit21 = pMP3Stream->band_limit; + if (pMP3Stream->band_limit12 > pMP3Stream->band_limit) + pMP3Stream->band_limit12 = pMP3Stream->band_limit; + + + pMP3Stream->band_limit_nsb = (pMP3Stream->band_limit + 17) / 18; /* limit nsb's rounded up */ +/*----------------------------------------------*/ + pMP3Stream->gain_adjust = 0; /* adjust gain e.g. cvt to mono sum channel */ + if ((h->mode != 3) && (convert_code == 1)) + pMP3Stream->gain_adjust = -4; + + pMP3Stream->outvalues = 1152 >> reduction_code; + if (h->id == 0) + pMP3Stream->outvalues /= 2; + + out_chans = 2; + if (h->mode == 3) + out_chans = 1; + if (convert_code) + out_chans = 1; + + pMP3Stream->sbt_L3 = sbt_table[bit_code][reduction_code][out_chans - 1]; + k = 1 + convert_code; + if (h->mode == 3) + k = 0; + pMP3Stream->Xform = xform_table[k]; + + + pMP3Stream->outvalues *= out_chans; + + if (bit_code) + pMP3Stream->outbytes = pMP3Stream->outvalues; + else + pMP3Stream->outbytes = sizeof(short) * pMP3Stream->outvalues; + + if (bit_code) + pMP3Stream->zero_level_pcm = 128; /* 8 bit output */ + else + pMP3Stream->zero_level_pcm = 0; + + + decinfo.channels = out_chans; + decinfo.outvalues = pMP3Stream->outvalues; + decinfo.samprate = samprate >> reduction_code; + if (bit_code) + decinfo.bits = 8; + else + decinfo.bits = sizeof(short) * 8; + + decinfo.framebytes = pMP3Stream->framebytes; + decinfo.type = 0; + + pMP3Stream->half_outbytes = pMP3Stream->outbytes / 2; +/*------------------------------------------*/ + +/*- init band tables --*/ + + + k = h->id; + if ((h->sync & 1) == 0) + k = 2; // mpeg 2.5 + + for (i = 0; i < 22; i++) + pMP3Stream->sfBandIndex[0][i] = sfBandIndexTable[k][h->sr_index].l[i + 1]; + for (i = 0; i < 13; i++) + pMP3Stream->sfBandIndex[1][i] = 3 * sfBandIndexTable[k][h->sr_index].s[i + 1]; + for (i = 0; i < 22; i++) + pMP3Stream->nBand[0][i] = sfBandIndexTable[k][h->sr_index].l[i + 1] - sfBandIndexTable[k][h->sr_index].l[i]; + for (i = 0; i < 13; i++) + pMP3Stream->nBand[1][i] = sfBandIndexTable[k][h->sr_index].s[i + 1] - sfBandIndexTable[k][h->sr_index].s[i]; + + +/* init tables */ + L3table_init(); +/* init ms and is stereo modes */ + msis_init(); + +/*----- init sbt ---*/ + sbt_init(); + + + +/*--- clear buffers --*/ + for (i = 0; i < 576; i++) + yout[i] = 0.0f; + for (j = 0; j < 2; j++) + { + for (k = 0; k < 2; k++) + { + for (i = 0; i < 576; i++) + { + pMP3Stream->sample[j][k][i].x = 0.0f; + pMP3Stream->sample[j][k][i].s = 0; + } + } + } + + if (h->id == 1) + pMP3Stream->decode_function = L3audio_decode_MPEG1; + else + pMP3Stream->decode_function = L3audio_decode_MPEG2; + + return 1; +} +/*---------------------------------------------------------*/ +/*==========================================================*/ diff --git a/code/mp3code/cwin.c b/code/mp3code/cwin.c new file mode 100644 index 0000000..44393f1 --- /dev/null +++ b/code/mp3code/cwin.c @@ -0,0 +1,470 @@ +#pragma warning(disable:4206) // nonstandard extension used : translation unit is empty +#ifdef COMPILE_ME +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: cwin.c,v 1.7 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** cwin.c *************************************************** + +include to cwinm.c + +MPEG audio decoder, float window routines +portable C + +******************************************************************/ + +#include "config.h" + +/*-------------------------------------------------------------------------*/ +void window(float *vbuf, int vb_ptr, short *pcm) +{ + int i, j; + int si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 16; + bx = (si + 32) & 511; + coef = wincoef; + +/*-- first 16 --*/ + for (i = 0; i < 16; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si = (si + 64) & 511; + sum -= (*coef++) * vbuf[bx]; + bx = (bx + 64) & 511; + } + si++; + bx--; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = tmp; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx = (bx + 64) & 511; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = tmp; +/*-- last 15 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 15; i++) + { + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si = (si + 64) & 511; + sum += (*coef--) * vbuf[bx]; + bx = (bx + 64) & 511; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = tmp; + } +} + + + +/*------------------------------------------------------------*/ +void window_dual(float *vbuf, int vb_ptr, short *pcm) +{ + int i, j; /* dual window interleaves output */ + int si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 16; + bx = (si + 32) & 511; + coef = wincoef; + +/*-- first 16 --*/ + for (i = 0; i < 16; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si = (si + 64) & 511; + sum -= (*coef++) * vbuf[bx]; + bx = (bx + 64) & 511; + } + si++; + bx--; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = tmp; + pcm += 2; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx = (bx + 64) & 511; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = tmp; + pcm += 2; +/*-- last 15 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 15; i++) + { + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si = (si + 64) & 511; + sum += (*coef--) * vbuf[bx]; + bx = (bx + 64) & 511; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = tmp; + pcm += 2; + } +} +/*------------------------------------------------------------*/ +/*------------------- 16 pt window ------------------------------*/ +void window16(float *vbuf, int vb_ptr, short *pcm) +{ + int i, j; + unsigned char si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 8; + bx = si + 16; + coef = wincoef; + +/*-- first 8 --*/ + for (i = 0; i < 8; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si += 32; + sum -= (*coef++) * vbuf[bx]; + bx += 32; + } + si++; + bx--; + coef += 16; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = tmp; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx += 32; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = tmp; +/*-- last 7 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 7; i++) + { + coef -= 16; + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si += 32; + sum += (*coef--) * vbuf[bx]; + bx += 32; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = tmp; + } +} +/*--------------- 16 pt dual window (interleaved output) -----------------*/ +void window16_dual(float *vbuf, int vb_ptr, short *pcm) +{ + int i, j; + unsigned char si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 8; + bx = si + 16; + coef = wincoef; + +/*-- first 8 --*/ + for (i = 0; i < 8; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si += 32; + sum -= (*coef++) * vbuf[bx]; + bx += 32; + } + si++; + bx--; + coef += 16; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = tmp; + pcm += 2; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx += 32; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = tmp; + pcm += 2; +/*-- last 7 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 7; i++) + { + coef -= 16; + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si += 32; + sum += (*coef--) * vbuf[bx]; + bx += 32; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = tmp; + pcm += 2; + } +} +/*------------------- 8 pt window ------------------------------*/ +void window8(float *vbuf, int vb_ptr, short *pcm) +{ + int i, j; + int si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 4; + bx = (si + 8) & 127; + coef = wincoef; + +/*-- first 4 --*/ + for (i = 0; i < 4; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si = (si + 16) & 127; + sum -= (*coef++) * vbuf[bx]; + bx = (bx + 16) & 127; + } + si++; + bx--; + coef += 48; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = tmp; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx = (bx + 16) & 127; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = tmp; +/*-- last 3 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 3; i++) + { + coef -= 48; + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si = (si + 16) & 127; + sum += (*coef--) * vbuf[bx]; + bx = (bx + 16) & 127; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = tmp; + } +} +/*--------------- 8 pt dual window (interleaved output) -----------------*/ +void window8_dual(float *vbuf, int vb_ptr, short *pcm) +{ + int i, j; + int si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 4; + bx = (si + 8) & 127; + coef = wincoef; + +/*-- first 4 --*/ + for (i = 0; i < 4; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si = (si + 16) & 127; + sum -= (*coef++) * vbuf[bx]; + bx = (bx + 16) & 127; + } + si++; + bx--; + coef += 48; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = tmp; + pcm += 2; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx = (bx + 16) & 127; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = tmp; + pcm += 2; +/*-- last 3 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 3; i++) + { + coef -= 48; + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si = (si + 16) & 127; + sum += (*coef--) * vbuf[bx]; + bx = (bx + 16) & 127; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = tmp; + pcm += 2; + } +} +/*------------------------------------------------------------*/ +#endif // #ifdef COMPILE_ME diff --git a/code/mp3code/cwinb.c b/code/mp3code/cwinb.c new file mode 100644 index 0000000..dceefd8 --- /dev/null +++ b/code/mp3code/cwinb.c @@ -0,0 +1,465 @@ +#pragma warning(disable:4206) // nonstandard extension used : translation unit is empty +#ifdef COMPILE_ME +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: cwinb.c,v 1.4 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** cwin.c *************************************************** + +include to cwinm.c + +MPEG audio decoder, float window routines - 8 bit output +portable C + +******************************************************************/ +/*-------------------------------------------------------------------------*/ + +void windowB(float *vbuf, int vb_ptr, unsigned char *pcm) +{ + int i, j; + int si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 16; + bx = (si + 32) & 511; + coef = wincoef; + +/*-- first 16 --*/ + for (i = 0; i < 16; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si = (si + 64) & 511; + sum -= (*coef++) * vbuf[bx]; + bx = (bx + 64) & 511; + } + si++; + bx--; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = ((unsigned char) (tmp >> 8)) ^ 0x80; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx = (bx + 64) & 511; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = ((unsigned char) (tmp >> 8)) ^ 0x80; +/*-- last 15 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 15; i++) + { + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si = (si + 64) & 511; + sum += (*coef--) * vbuf[bx]; + bx = (bx + 64) & 511; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = ((unsigned char) (tmp >> 8)) ^ 0x80; + } +} +/*------------------------------------------------------------*/ +void windowB_dual(float *vbuf, int vb_ptr, unsigned char *pcm) +{ + int i, j; /* dual window interleaves output */ + int si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 16; + bx = (si + 32) & 511; + coef = wincoef; + +/*-- first 16 --*/ + for (i = 0; i < 16; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si = (si + 64) & 511; + sum -= (*coef++) * vbuf[bx]; + bx = (bx + 64) & 511; + } + si++; + bx--; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = ((unsigned char) (tmp >> 8)) ^ 0x80; + pcm += 2; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx = (bx + 64) & 511; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = ((unsigned char) (tmp >> 8)) ^ 0x80; + pcm += 2; +/*-- last 15 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 15; i++) + { + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si = (si + 64) & 511; + sum += (*coef--) * vbuf[bx]; + bx = (bx + 64) & 511; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = ((unsigned char) (tmp >> 8)) ^ 0x80; + pcm += 2; + } +} +/*------------------------------------------------------------*/ +/*------------------- 16 pt window ------------------------------*/ +void windowB16(float *vbuf, int vb_ptr, unsigned char *pcm) +{ + int i, j; + unsigned char si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 8; + bx = si + 16; + coef = wincoef; + +/*-- first 8 --*/ + for (i = 0; i < 8; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si += 32; + sum -= (*coef++) * vbuf[bx]; + bx += 32; + } + si++; + bx--; + coef += 16; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = ((unsigned char) (tmp >> 8)) ^ 0x80; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx += 32; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = ((unsigned char) (tmp >> 8)) ^ 0x80; +/*-- last 7 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 7; i++) + { + coef -= 16; + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si += 32; + sum += (*coef--) * vbuf[bx]; + bx += 32; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = ((unsigned char) (tmp >> 8)) ^ 0x80; + } +} +/*--------------- 16 pt dual window (interleaved output) -----------------*/ +void windowB16_dual(float *vbuf, int vb_ptr, unsigned char *pcm) +{ + int i, j; + unsigned char si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 8; + bx = si + 16; + coef = wincoef; + +/*-- first 8 --*/ + for (i = 0; i < 8; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si += 32; + sum -= (*coef++) * vbuf[bx]; + bx += 32; + } + si++; + bx--; + coef += 16; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = ((unsigned char) (tmp >> 8)) ^ 0x80; + pcm += 2; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx += 32; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = ((unsigned char) (tmp >> 8)) ^ 0x80; + pcm += 2; +/*-- last 7 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 7; i++) + { + coef -= 16; + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si += 32; + sum += (*coef--) * vbuf[bx]; + bx += 32; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = ((unsigned char) (tmp >> 8)) ^ 0x80; + pcm += 2; + } +} +/*------------------- 8 pt window ------------------------------*/ +void windowB8(float *vbuf, int vb_ptr, unsigned char *pcm) +{ + int i, j; + int si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 4; + bx = (si + 8) & 127; + coef = wincoef; + +/*-- first 4 --*/ + for (i = 0; i < 4; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si = (si + 16) & 127; + sum -= (*coef++) * vbuf[bx]; + bx = (bx + 16) & 127; + } + si++; + bx--; + coef += 48; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = ((unsigned char) (tmp >> 8)) ^ 0x80; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx = (bx + 16) & 127; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = ((unsigned char) (tmp >> 8)) ^ 0x80; +/*-- last 3 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 3; i++) + { + coef -= 48; + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si = (si + 16) & 127; + sum += (*coef--) * vbuf[bx]; + bx = (bx + 16) & 127; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm++ = ((unsigned char) (tmp >> 8)) ^ 0x80; + } +} +/*--------------- 8 pt dual window (interleaved output) -----------------*/ +void windowB8_dual(float *vbuf, int vb_ptr, unsigned char *pcm) +{ + int i, j; + int si, bx; + const float *coef; + float sum; + long tmp; + + si = vb_ptr + 4; + bx = (si + 8) & 127; + coef = wincoef; + +/*-- first 4 --*/ + for (i = 0; i < 4; i++) + { + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[si]; + si = (si + 16) & 127; + sum -= (*coef++) * vbuf[bx]; + bx = (bx + 16) & 127; + } + si++; + bx--; + coef += 48; + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = ((unsigned char) (tmp >> 8)) ^ 0x80; + pcm += 2; + } +/*-- special case --*/ + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef++) * vbuf[bx]; + bx = (bx + 16) & 127; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = ((unsigned char) (tmp >> 8)) ^ 0x80; + pcm += 2; +/*-- last 3 --*/ + coef = wincoef + 255; /* back pass through coefs */ + for (i = 0; i < 3; i++) + { + coef -= 48; + si--; + bx++; + sum = 0.0F; + for (j = 0; j < 8; j++) + { + sum += (*coef--) * vbuf[si]; + si = (si + 16) & 127; + sum += (*coef--) * vbuf[bx]; + bx = (bx + 16) & 127; + } + tmp = (long) sum; + if (tmp > 32767) + tmp = 32767; + else if (tmp < -32768) + tmp = -32768; + *pcm = ((unsigned char) (tmp >> 8)) ^ 0x80; + pcm += 2; + } +} +/*------------------------------------------------------------*/ +#endif // #ifdef COMPILE_ME diff --git a/code/mp3code/cwinm.c b/code/mp3code/cwinm.c new file mode 100644 index 0000000..0446b4e --- /dev/null +++ b/code/mp3code/cwinm.c @@ -0,0 +1,55 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: cwinm.c,v 1.3 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** cwinm.c *************************************************** + +MPEG audio decoder, window master routine +portable C + + +******************************************************************/ + +#include +#include +#include +#include + + +/* disable precision loss warning on type conversion */ +#ifdef _MSC_VER +#pragma warning(disable:4244 4056) +#endif + +const float wincoef[264] = +{ /* window coefs */ +#include "tableawd.h" +}; + +/*--------------------------------------------------------*/ +#define COMPILE_ME +#include "cwin.c" +#include "cwinb.c" +/*--------------------------------------------------------*/ diff --git a/code/mp3code/htable.h b/code/mp3code/htable.h new file mode 100644 index 0000000..7d3536e --- /dev/null +++ b/code/mp3code/htable.h @@ -0,0 +1,999 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License}, {or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not}, {write to the Free Software + Foundation}, {Inc.}, {675 Mass Ave}, {Cambridge}, {MA 02139}, {USA. + + $Id: htable.h,v 1.2 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/* TABLE 1 4 entries maxbits 3 linbits 0 */ +static const HUFF_ELEMENT huff_table_1[] = +{ + {0xFF000003}, {0x03010102}, {0x03010001}, {0x02000101}, {0x02000101}, /* 4 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, +}; + +/* max table bits 3 */ + +/* TABLE 2 9 entries maxbits 6 linbits 0 */ +static const HUFF_ELEMENT huff_table_2[] = +{ + {0xFF000006}, {0x06020202}, {0x06020001}, {0x05020102}, {0x05020102}, /* 4 */ + {0x05010202}, {0x05010202}, {0x05000201}, {0x05000201}, {0x03010102}, /* 9 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 14 */ + {0x03010102}, {0x03010102}, {0x03010001}, {0x03010001}, {0x03010001}, /* 19 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 24 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 29 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x01000000}, {0x01000000}, /* 34 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 39 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 44 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 49 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 54 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 59 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 64 */ +}; + +/* max table bits 6 */ + +/* TABLE 3 9 entries maxbits 6 linbits 0 */ +static const HUFF_ELEMENT huff_table_3[] = +{ + {0xFF000006}, {0x06020202}, {0x06020001}, {0x05020102}, {0x05020102}, /* 4 */ + {0x05010202}, {0x05010202}, {0x05000201}, {0x05000201}, {0x03000101}, /* 9 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 14 */ + {0x03000101}, {0x03000101}, {0x02010102}, {0x02010102}, {0x02010102}, /* 19 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 24 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 29 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010001}, {0x02010001}, /* 34 */ + {0x02010001}, {0x02010001}, {0x02010001}, {0x02010001}, {0x02010001}, /* 39 */ + {0x02010001}, {0x02010001}, {0x02010001}, {0x02010001}, {0x02010001}, /* 44 */ + {0x02010001}, {0x02010001}, {0x02010001}, {0x02010001}, {0x02000000}, /* 49 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 54 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 59 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 64 */ +}; + +/* max table bits 6 */ +/* NO XING TABLE 4 */ + +/* TABLE 5 16 entries maxbits 8 linbits 0 */ +static const HUFF_ELEMENT huff_table_5[] = +{ + {0xFF000008}, {0x08030302}, {0x08030202}, {0x07020302}, {0x07020302}, /* 4 */ + {0x06010302}, {0x06010302}, {0x06010302}, {0x06010302}, {0x07030102}, /* 9 */ + {0x07030102}, {0x07030001}, {0x07030001}, {0x07000301}, {0x07000301}, /* 14 */ + {0x07020202}, {0x07020202}, {0x06020102}, {0x06020102}, {0x06020102}, /* 19 */ + {0x06020102}, {0x06010202}, {0x06010202}, {0x06010202}, {0x06010202}, /* 24 */ + {0x06020001}, {0x06020001}, {0x06020001}, {0x06020001}, {0x06000201}, /* 29 */ + {0x06000201}, {0x06000201}, {0x06000201}, {0x03010102}, {0x03010102}, /* 34 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 39 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 44 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 49 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 54 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 59 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 64 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 69 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 74 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 79 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 84 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 89 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 94 */ + {0x03010001}, {0x03010001}, {0x03000101}, {0x03000101}, {0x03000101}, /* 99 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 104 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 109 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 114 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 119 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 124 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x01000000}, /* 129 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 134 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 139 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 144 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 149 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 154 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 159 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 164 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 169 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 174 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 179 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 184 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 189 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 194 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 199 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 204 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 209 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 214 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 219 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 224 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 229 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 234 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 239 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 244 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 249 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 254 */ + {0x01000000}, {0x01000000}, +}; + +/* max table bits 8 */ + +/* TABLE 6 16 entries maxbits 7 linbits 0 */ +static const HUFF_ELEMENT huff_table_6[] = +{ + {0xFF000007}, {0x07030302}, {0x07030001}, {0x06030202}, {0x06030202}, /* 4 */ + {0x06020302}, {0x06020302}, {0x06000301}, {0x06000301}, {0x05030102}, /* 9 */ + {0x05030102}, {0x05030102}, {0x05030102}, {0x05010302}, {0x05010302}, /* 14 */ + {0x05010302}, {0x05010302}, {0x05020202}, {0x05020202}, {0x05020202}, /* 19 */ + {0x05020202}, {0x05020001}, {0x05020001}, {0x05020001}, {0x05020001}, /* 24 */ + {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, /* 29 */ + {0x04020102}, {0x04020102}, {0x04020102}, {0x04010202}, {0x04010202}, /* 34 */ + {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, /* 39 */ + {0x04010202}, {0x04000201}, {0x04000201}, {0x04000201}, {0x04000201}, /* 44 */ + {0x04000201}, {0x04000201}, {0x04000201}, {0x04000201}, {0x03010001}, /* 49 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 54 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 59 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 64 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 69 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 74 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 79 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 84 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 89 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 94 */ + {0x02010102}, {0x02010102}, {0x03000101}, {0x03000101}, {0x03000101}, /* 99 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 104 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 109 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000000}, {0x03000000}, /* 114 */ + {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, /* 119 */ + {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, /* 124 */ + {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, +}; + +/* max table bits 7 */ + +/* TABLE 7 36 entries maxbits 10 linbits 0 */ +static const HUFF_ELEMENT huff_table_7[] = +{ + {0xFF000006}, {0x00000041}, {0x00000052}, {0x0000005B}, {0x00000060}, /* 4 */ + {0x00000063}, {0x00000068}, {0x0000006B}, {0x06020102}, {0x05010202}, /* 9 */ + {0x05010202}, {0x06020001}, {0x06000201}, {0x04010102}, {0x04010102}, /* 14 */ + {0x04010102}, {0x04010102}, {0x03010001}, {0x03010001}, {0x03010001}, /* 19 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 24 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 29 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x01000000}, {0x01000000}, /* 34 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 39 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 44 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 49 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 54 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 59 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 64 */ + {0xFF000004}, {0x04050502}, {0x04050402}, {0x04040502}, {0x04030502}, /* 69 */ + {0x03050302}, {0x03050302}, {0x03040402}, {0x03040402}, {0x03050202}, /* 74 */ + {0x03050202}, {0x03020502}, {0x03020502}, {0x02050102}, {0x02050102}, /* 79 */ + {0x02050102}, {0x02050102}, {0xFF000003}, {0x02010502}, {0x02010502}, /* 84 */ + {0x03050001}, {0x03040302}, {0x02000501}, {0x02000501}, {0x03030402}, /* 89 */ + {0x03030302}, {0xFF000002}, {0x02040202}, {0x02020402}, {0x01040102}, /* 94 */ + {0x01040102}, {0xFF000001}, {0x01010402}, {0x01000401}, {0xFF000002}, /* 99 */ + {0x02040001}, {0x02030202}, {0x02020302}, {0x02030001}, {0xFF000001}, /* 104 */ + {0x01030102}, {0x01010302}, {0xFF000001}, {0x01000301}, {0x01020202}, /* 109 */ +}; + +/* max table bits 6 */ + +/* TABLE 8 36 entries maxbits 11 linbits 0 */ +static const HUFF_ELEMENT huff_table_8[] = +{ + {0xFF000008}, {0x00000101}, {0x0000010A}, {0x0000010F}, {0x08050102}, /* 4 */ + {0x08010502}, {0x00000112}, {0x00000115}, {0x08040202}, {0x08020402}, /* 9 */ + {0x08040102}, {0x07010402}, {0x07010402}, {0x08040001}, {0x08000401}, /* 14 */ + {0x08030202}, {0x08020302}, {0x08030102}, {0x08010302}, {0x08030001}, /* 19 */ + {0x08000301}, {0x06020202}, {0x06020202}, {0x06020202}, {0x06020202}, /* 24 */ + {0x06020001}, {0x06020001}, {0x06020001}, {0x06020001}, {0x06000201}, /* 29 */ + {0x06000201}, {0x06000201}, {0x06000201}, {0x04020102}, {0x04020102}, /* 34 */ + {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, /* 39 */ + {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, /* 44 */ + {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, {0x04010202}, /* 49 */ + {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, /* 54 */ + {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, /* 59 */ + {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, /* 64 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 69 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 74 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 79 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 84 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 89 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 94 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 99 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 104 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 109 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 114 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 119 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, /* 124 */ + {0x02010102}, {0x02010102}, {0x02010102}, {0x02010102}, {0x03010001}, /* 129 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 134 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 139 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 144 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 149 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 154 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 159 */ + {0x03010001}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 164 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 169 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 174 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 179 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 184 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 189 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x02000000}, {0x02000000}, /* 194 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 199 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 204 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 209 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 214 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 219 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 224 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 229 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 234 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 239 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 244 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 249 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 254 */ + {0x02000000}, {0x02000000}, {0xFF000003}, {0x03050502}, {0x03040502}, /* 259 */ + {0x02050402}, {0x02050402}, {0x01030502}, {0x01030502}, {0x01030502}, /* 264 */ + {0x01030502}, {0xFF000002}, {0x02050302}, {0x02040402}, {0x01050202}, /* 269 */ + {0x01050202}, {0xFF000001}, {0x01020502}, {0x01050001}, {0xFF000001}, /* 274 */ + {0x01040302}, {0x01030402}, {0xFF000001}, {0x01000501}, {0x01030302}, /* 279 */ +}; + +/* max table bits 8 */ + +/* TABLE 9 36 entries maxbits 9 linbits 0 */ +static const HUFF_ELEMENT huff_table_9[] = +{ + {0xFF000006}, {0x00000041}, {0x0000004A}, {0x0000004F}, {0x00000052}, /* 4 */ + {0x00000057}, {0x0000005A}, {0x06040102}, {0x06010402}, {0x06030202}, /* 9 */ + {0x06020302}, {0x05030102}, {0x05030102}, {0x05010302}, {0x05010302}, /* 14 */ + {0x06030001}, {0x06000301}, {0x05020202}, {0x05020202}, {0x05020001}, /* 19 */ + {0x05020001}, {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, /* 24 */ + {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, {0x04000201}, /* 29 */ + {0x04000201}, {0x04000201}, {0x04000201}, {0x03010102}, {0x03010102}, /* 34 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 39 */ + {0x03010102}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 44 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03000101}, /* 49 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 54 */ + {0x03000101}, {0x03000101}, {0x03000000}, {0x03000000}, {0x03000000}, /* 59 */ + {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, /* 64 */ + {0xFF000003}, {0x03050502}, {0x03050402}, {0x02050302}, {0x02050302}, /* 69 */ + {0x02030502}, {0x02030502}, {0x03040502}, {0x03050001}, {0xFF000002}, /* 74 */ + {0x02040402}, {0x02050202}, {0x02020502}, {0x02050102}, {0xFF000001}, /* 79 */ + {0x01010502}, {0x01040302}, {0xFF000002}, {0x01030402}, {0x01030402}, /* 84 */ + {0x02000501}, {0x02040001}, {0xFF000001}, {0x01040202}, {0x01020402}, /* 89 */ + {0xFF000001}, {0x01030302}, {0x01000401}, +}; + +/* max table bits 6 */ + +/* TABLE 10 64 entries maxbits 11 linbits 0 */ +static const HUFF_ELEMENT huff_table_10[] = +{ + {0xFF000008}, {0x00000101}, {0x0000010A}, {0x0000010F}, {0x00000118}, /* 4 */ + {0x0000011B}, {0x00000120}, {0x00000125}, {0x08070102}, {0x08010702}, /* 9 */ + {0x0000012A}, {0x0000012D}, {0x00000132}, {0x08060102}, {0x08010602}, /* 14 */ + {0x08000601}, {0x00000137}, {0x0000013A}, {0x0000013D}, {0x08040102}, /* 19 */ + {0x08010402}, {0x08000401}, {0x08030202}, {0x08020302}, {0x08030001}, /* 24 */ + {0x07030102}, {0x07030102}, {0x07010302}, {0x07010302}, {0x07000301}, /* 29 */ + {0x07000301}, {0x07020202}, {0x07020202}, {0x06020102}, {0x06020102}, /* 34 */ + {0x06020102}, {0x06020102}, {0x06010202}, {0x06010202}, {0x06010202}, /* 39 */ + {0x06010202}, {0x06020001}, {0x06020001}, {0x06020001}, {0x06020001}, /* 44 */ + {0x06000201}, {0x06000201}, {0x06000201}, {0x06000201}, {0x04010102}, /* 49 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 54 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 59 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 64 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 69 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 74 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 79 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 84 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 89 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 94 */ + {0x03010001}, {0x03010001}, {0x03000101}, {0x03000101}, {0x03000101}, /* 99 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 104 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 109 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 114 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 119 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 124 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x01000000}, /* 129 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 134 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 139 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 144 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 149 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 154 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 159 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 164 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 169 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 174 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 179 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 184 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 189 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 194 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 199 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 204 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 209 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 214 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 219 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 224 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 229 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 234 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 239 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 244 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 249 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 254 */ + {0x01000000}, {0x01000000}, {0xFF000003}, {0x03070702}, {0x03070602}, /* 259 */ + {0x03060702}, {0x03070502}, {0x03050702}, {0x03060602}, {0x02070402}, /* 264 */ + {0x02070402}, {0xFF000002}, {0x02040702}, {0x02060502}, {0x02050602}, /* 269 */ + {0x02070302}, {0xFF000003}, {0x02030702}, {0x02030702}, {0x02060402}, /* 274 */ + {0x02060402}, {0x03050502}, {0x03040502}, {0x02030602}, {0x02030602}, /* 279 */ + {0xFF000001}, {0x01070202}, {0x01020702}, {0xFF000002}, {0x02040602}, /* 284 */ + {0x02070001}, {0x01000701}, {0x01000701}, {0xFF000002}, {0x01020602}, /* 289 */ + {0x01020602}, {0x02050402}, {0x02050302}, {0xFF000002}, {0x01060001}, /* 294 */ + {0x01060001}, {0x02030502}, {0x02040402}, {0xFF000001}, {0x01060302}, /* 299 */ + {0x01060202}, {0xFF000002}, {0x02050202}, {0x02020502}, {0x01050102}, /* 304 */ + {0x01050102}, {0xFF000002}, {0x01010502}, {0x01010502}, {0x02040302}, /* 309 */ + {0x02030402}, {0xFF000001}, {0x01050001}, {0x01000501}, {0xFF000001}, /* 314 */ + {0x01040202}, {0x01020402}, {0xFF000001}, {0x01030302}, {0x01040001}, /* 319 */ +}; + +/* max table bits 8 */ + +/* TABLE 11 64 entries maxbits 11 linbits 0 */ +static const HUFF_ELEMENT huff_table_11[] = +{ + {0xFF000008}, {0x00000101}, {0x00000106}, {0x0000010F}, {0x00000114}, /* 4 */ + {0x00000117}, {0x08070202}, {0x08020702}, {0x0000011C}, {0x07010702}, /* 9 */ + {0x07010702}, {0x08070102}, {0x08000701}, {0x08060302}, {0x08030602}, /* 14 */ + {0x08000601}, {0x0000011F}, {0x00000122}, {0x08050102}, {0x07020602}, /* 19 */ + {0x07020602}, {0x08060202}, {0x08060001}, {0x07060102}, {0x07060102}, /* 24 */ + {0x07010602}, {0x07010602}, {0x08010502}, {0x08040302}, {0x08000501}, /* 29 */ + {0x00000125}, {0x08040202}, {0x08020402}, {0x08040102}, {0x08010402}, /* 34 */ + {0x08040001}, {0x08000401}, {0x07030202}, {0x07030202}, {0x07020302}, /* 39 */ + {0x07020302}, {0x06030102}, {0x06030102}, {0x06030102}, {0x06030102}, /* 44 */ + {0x06010302}, {0x06010302}, {0x06010302}, {0x06010302}, {0x07030001}, /* 49 */ + {0x07030001}, {0x07000301}, {0x07000301}, {0x06020202}, {0x06020202}, /* 54 */ + {0x06020202}, {0x06020202}, {0x05010202}, {0x05010202}, {0x05010202}, /* 59 */ + {0x05010202}, {0x05010202}, {0x05010202}, {0x05010202}, {0x05010202}, /* 64 */ + {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, /* 69 */ + {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, /* 74 */ + {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, /* 79 */ + {0x04020102}, {0x05020001}, {0x05020001}, {0x05020001}, {0x05020001}, /* 84 */ + {0x05020001}, {0x05020001}, {0x05020001}, {0x05020001}, {0x05000201}, /* 89 */ + {0x05000201}, {0x05000201}, {0x05000201}, {0x05000201}, {0x05000201}, /* 94 */ + {0x05000201}, {0x05000201}, {0x03010102}, {0x03010102}, {0x03010102}, /* 99 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 104 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 109 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 114 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 119 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 124 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010001}, /* 129 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 134 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 139 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 144 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 149 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 154 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 159 */ + {0x03010001}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 164 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 169 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 174 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 179 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 184 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 189 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x02000000}, {0x02000000}, /* 194 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 199 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 204 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 209 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 214 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 219 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 224 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 229 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 234 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 239 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 244 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 249 */ + {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, {0x02000000}, /* 254 */ + {0x02000000}, {0x02000000}, {0xFF000002}, {0x02070702}, {0x02070602}, /* 259 */ + {0x02060702}, {0x02050702}, {0xFF000003}, {0x02060602}, {0x02060602}, /* 264 */ + {0x02070402}, {0x02070402}, {0x02040702}, {0x02040702}, {0x03070502}, /* 269 */ + {0x03050502}, {0xFF000002}, {0x02060502}, {0x02050602}, {0x01070302}, /* 274 */ + {0x01070302}, {0xFF000001}, {0x01030702}, {0x01060402}, {0xFF000002}, /* 279 */ + {0x02050402}, {0x02040502}, {0x02050302}, {0x02030502}, {0xFF000001}, /* 284 */ + {0x01040602}, {0x01070001}, {0xFF000001}, {0x01040402}, {0x01050202}, /* 289 */ + {0xFF000001}, {0x01020502}, {0x01050001}, {0xFF000001}, {0x01030402}, /* 294 */ + {0x01030302}, +}; + +/* max table bits 8 */ + +/* TABLE 12 64 entries maxbits 10 linbits 0 */ +static const HUFF_ELEMENT huff_table_12[] = +{ + {0xFF000007}, {0x00000081}, {0x0000008A}, {0x0000008F}, {0x00000092}, /* 4 */ + {0x00000097}, {0x0000009A}, {0x0000009D}, {0x000000A2}, {0x000000A5}, /* 9 */ + {0x000000A8}, {0x07060202}, {0x07020602}, {0x07010602}, {0x000000AD}, /* 14 */ + {0x000000B0}, {0x000000B3}, {0x07050102}, {0x07010502}, {0x07040302}, /* 19 */ + {0x07030402}, {0x000000B6}, {0x07040202}, {0x07020402}, {0x07040102}, /* 24 */ + {0x06030302}, {0x06030302}, {0x06010402}, {0x06010402}, {0x06030202}, /* 29 */ + {0x06030202}, {0x06020302}, {0x06020302}, {0x07000401}, {0x07030001}, /* 34 */ + {0x06000301}, {0x06000301}, {0x05030102}, {0x05030102}, {0x05030102}, /* 39 */ + {0x05030102}, {0x05010302}, {0x05010302}, {0x05010302}, {0x05010302}, /* 44 */ + {0x05020202}, {0x05020202}, {0x05020202}, {0x05020202}, {0x04020102}, /* 49 */ + {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, {0x04020102}, /* 54 */ + {0x04020102}, {0x04020102}, {0x04010202}, {0x04010202}, {0x04010202}, /* 59 */ + {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, {0x04010202}, /* 64 */ + {0x05020001}, {0x05020001}, {0x05020001}, {0x05020001}, {0x05000201}, /* 69 */ + {0x05000201}, {0x05000201}, {0x05000201}, {0x04000000}, {0x04000000}, /* 74 */ + {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, /* 79 */ + {0x04000000}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 84 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 89 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 94 */ + {0x03010102}, {0x03010102}, {0x03010001}, {0x03010001}, {0x03010001}, /* 99 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 104 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, {0x03010001}, /* 109 */ + {0x03010001}, {0x03010001}, {0x03010001}, {0x03000101}, {0x03000101}, /* 114 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 119 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 124 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0xFF000003}, /* 129 */ + {0x03070702}, {0x03070602}, {0x02060702}, {0x02060702}, {0x02070502}, /* 134 */ + {0x02070502}, {0x02050702}, {0x02050702}, {0xFF000002}, {0x02060602}, /* 139 */ + {0x02070402}, {0x02040702}, {0x02050602}, {0xFF000001}, {0x01060502}, /* 144 */ + {0x01070302}, {0xFF000002}, {0x02030702}, {0x02050502}, {0x01070202}, /* 149 */ + {0x01070202}, {0xFF000001}, {0x01020702}, {0x01060402}, {0xFF000001}, /* 154 */ + {0x01040602}, {0x01070102}, {0xFF000002}, {0x01010702}, {0x01010702}, /* 159 */ + {0x02070001}, {0x02000701}, {0xFF000001}, {0x01060302}, {0x01030602}, /* 164 */ + {0xFF000001}, {0x01050402}, {0x01040502}, {0xFF000002}, {0x01040402}, /* 169 */ + {0x01040402}, {0x02060001}, {0x02050001}, {0xFF000001}, {0x01060102}, /* 174 */ + {0x01000601}, {0xFF000001}, {0x01050302}, {0x01030502}, {0xFF000001}, /* 179 */ + {0x01050202}, {0x01020502}, {0xFF000001}, {0x01000501}, {0x01040001}, /* 184 */ +}; + +/* max table bits 7 */ + +/* TABLE 13 256 entries maxbits 19 linbits 0 */ +static const HUFF_ELEMENT huff_table_13[] = +{ + {0xFF000006}, {0x00000041}, {0x00000082}, {0x000000C3}, {0x000000E4}, /* 4 */ + {0x00000105}, {0x00000116}, {0x0000011F}, {0x00000130}, {0x00000139}, /* 9 */ + {0x0000013E}, {0x00000143}, {0x00000146}, {0x06020102}, {0x06010202}, /* 14 */ + {0x06020001}, {0x06000201}, {0x04010102}, {0x04010102}, {0x04010102}, /* 19 */ + {0x04010102}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 24 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 29 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x01000000}, {0x01000000}, /* 34 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 39 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 44 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 49 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 54 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 59 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 64 */ + {0xFF000006}, {0x00000108}, {0x00000111}, {0x0000011A}, {0x00000123}, /* 69 */ + {0x0000012C}, {0x00000131}, {0x00000136}, {0x0000013F}, {0x00000144}, /* 74 */ + {0x00000147}, {0x0000014C}, {0x00000151}, {0x00000156}, {0x0000015B}, /* 79 */ + {0x060F0102}, {0x06010F02}, {0x06000F01}, {0x00000160}, {0x00000163}, /* 84 */ + {0x00000166}, {0x06020E02}, {0x00000169}, {0x060E0102}, {0x06010E02}, /* 89 */ + {0x0000016C}, {0x0000016F}, {0x00000172}, {0x00000175}, {0x00000178}, /* 94 */ + {0x0000017B}, {0x06060C02}, {0x060D0302}, {0x0000017E}, {0x060D0202}, /* 99 */ + {0x06020D02}, {0x060D0102}, {0x06070B02}, {0x00000181}, {0x00000184}, /* 104 */ + {0x06030C02}, {0x00000187}, {0x060B0402}, {0x05010D02}, {0x05010D02}, /* 109 */ + {0x060D0001}, {0x06000D01}, {0x060A0802}, {0x06080A02}, {0x060C0402}, /* 114 */ + {0x06040C02}, {0x060B0602}, {0x06060B02}, {0x050C0302}, {0x050C0302}, /* 119 */ + {0x050C0202}, {0x050C0202}, {0x05020C02}, {0x05020C02}, {0x050B0502}, /* 124 */ + {0x050B0502}, {0x06050B02}, {0x06090802}, {0x050C0102}, {0x050C0102}, /* 129 */ + {0xFF000006}, {0x05010C02}, {0x05010C02}, {0x06080902}, {0x060C0001}, /* 134 */ + {0x05000C01}, {0x05000C01}, {0x06040B02}, {0x060A0602}, {0x06060A02}, /* 139 */ + {0x06090702}, {0x050B0302}, {0x050B0302}, {0x05030B02}, {0x05030B02}, /* 144 */ + {0x06080802}, {0x060A0502}, {0x050B0202}, {0x050B0202}, {0x06050A02}, /* 149 */ + {0x06090602}, {0x05040A02}, {0x05040A02}, {0x06080702}, {0x06070802}, /* 154 */ + {0x05040902}, {0x05040902}, {0x06070702}, {0x06060702}, {0x04020B02}, /* 159 */ + {0x04020B02}, {0x04020B02}, {0x04020B02}, {0x040B0102}, {0x040B0102}, /* 164 */ + {0x040B0102}, {0x040B0102}, {0x04010B02}, {0x04010B02}, {0x04010B02}, /* 169 */ + {0x04010B02}, {0x050B0001}, {0x050B0001}, {0x05000B01}, {0x05000B01}, /* 174 */ + {0x05060902}, {0x05060902}, {0x050A0402}, {0x050A0402}, {0x050A0302}, /* 179 */ + {0x050A0302}, {0x05030A02}, {0x05030A02}, {0x05090502}, {0x05090502}, /* 184 */ + {0x05050902}, {0x05050902}, {0x040A0202}, {0x040A0202}, {0x040A0202}, /* 189 */ + {0x040A0202}, {0x04020A02}, {0x04020A02}, {0x04020A02}, {0x04020A02}, /* 194 */ + {0xFF000005}, {0x040A0102}, {0x040A0102}, {0x04010A02}, {0x04010A02}, /* 199 */ + {0x050A0001}, {0x05080602}, {0x04000A01}, {0x04000A01}, {0x05060802}, /* 204 */ + {0x05090402}, {0x04030902}, {0x04030902}, {0x05090302}, {0x05080502}, /* 209 */ + {0x05050802}, {0x05070602}, {0x04090202}, {0x04090202}, {0x04020902}, /* 214 */ + {0x04020902}, {0x05070502}, {0x05050702}, {0x04080302}, {0x04080302}, /* 219 */ + {0x04030802}, {0x04030802}, {0x05060602}, {0x05070402}, {0x05040702}, /* 224 */ + {0x05060502}, {0x05050602}, {0x05030702}, {0xFF000005}, {0x03090102}, /* 229 */ + {0x03090102}, {0x03090102}, {0x03090102}, {0x03010902}, {0x03010902}, /* 234 */ + {0x03010902}, {0x03010902}, {0x04090001}, {0x04090001}, {0x04000901}, /* 239 */ + {0x04000901}, {0x04080402}, {0x04080402}, {0x04040802}, {0x04040802}, /* 244 */ + {0x04020702}, {0x04020702}, {0x05060402}, {0x05040602}, {0x03080202}, /* 249 */ + {0x03080202}, {0x03080202}, {0x03080202}, {0x03020802}, {0x03020802}, /* 254 */ + {0x03020802}, {0x03020802}, {0x03080102}, {0x03080102}, {0x03080102}, /* 259 */ + {0x03080102}, {0xFF000004}, {0x04070302}, {0x04070202}, {0x03070102}, /* 264 */ + {0x03070102}, {0x03010702}, {0x03010702}, {0x04050502}, {0x04070001}, /* 269 */ + {0x04000701}, {0x04060302}, {0x04030602}, {0x04050402}, {0x04040502}, /* 274 */ + {0x04060202}, {0x04020602}, {0x04050302}, {0xFF000003}, {0x02010802}, /* 279 */ + {0x02010802}, {0x03080001}, {0x03000801}, {0x03060102}, {0x03010602}, /* 284 */ + {0x03060001}, {0x03000601}, {0xFF000004}, {0x04030502}, {0x04040402}, /* 289 */ + {0x03050202}, {0x03050202}, {0x03020502}, {0x03020502}, {0x03050001}, /* 294 */ + {0x03050001}, {0x02050102}, {0x02050102}, {0x02050102}, {0x02050102}, /* 299 */ + {0x02010502}, {0x02010502}, {0x02010502}, {0x02010502}, {0xFF000003}, /* 304 */ + {0x03040302}, {0x03030402}, {0x03000501}, {0x03040202}, {0x03020402}, /* 309 */ + {0x03030302}, {0x02040102}, {0x02040102}, {0xFF000002}, {0x01010402}, /* 314 */ + {0x01010402}, {0x02040001}, {0x02000401}, {0xFF000002}, {0x02030202}, /* 319 */ + {0x02020302}, {0x01030102}, {0x01030102}, {0xFF000001}, {0x01010302}, /* 324 */ + {0x01030001}, {0xFF000001}, {0x01000301}, {0x01020202}, {0xFF000003}, /* 329 */ + {0x00000082}, {0x0000008B}, {0x0000008E}, {0x00000091}, {0x00000094}, /* 334 */ + {0x00000097}, {0x030C0E02}, {0x030D0D02}, {0xFF000003}, {0x00000093}, /* 339 */ + {0x030E0B02}, {0x030B0E02}, {0x030F0902}, {0x03090F02}, {0x030A0E02}, /* 344 */ + {0x030D0B02}, {0x030B0D02}, {0xFF000003}, {0x030F0802}, {0x03080F02}, /* 349 */ + {0x030C0C02}, {0x0000008D}, {0x030E0802}, {0x00000090}, {0x02070F02}, /* 354 */ + {0x02070F02}, {0xFF000003}, {0x020A0D02}, {0x020A0D02}, {0x030D0A02}, /* 359 */ + {0x030C0B02}, {0x030B0C02}, {0x03060F02}, {0x020F0602}, {0x020F0602}, /* 364 */ + {0xFF000002}, {0x02080E02}, {0x020F0502}, {0x020D0902}, {0x02090D02}, /* 369 */ + {0xFF000002}, {0x02050F02}, {0x02070E02}, {0x020C0A02}, {0x020B0B02}, /* 374 */ + {0xFF000003}, {0x020F0402}, {0x020F0402}, {0x02040F02}, {0x02040F02}, /* 379 */ + {0x030A0C02}, {0x03060E02}, {0x02030F02}, {0x02030F02}, {0xFF000002}, /* 384 */ + {0x010F0302}, {0x010F0302}, {0x020D0802}, {0x02080D02}, {0xFF000001}, /* 389 */ + {0x010F0202}, {0x01020F02}, {0xFF000002}, {0x020E0602}, {0x020C0902}, /* 394 */ + {0x010F0001}, {0x010F0001}, {0xFF000002}, {0x02090C02}, {0x020E0502}, /* 399 */ + {0x010B0A02}, {0x010B0A02}, {0xFF000002}, {0x020D0702}, {0x02070D02}, /* 404 */ + {0x010E0402}, {0x010E0402}, {0xFF000002}, {0x02080C02}, {0x02060D02}, /* 409 */ + {0x010E0302}, {0x010E0302}, {0xFF000002}, {0x01090B02}, {0x01090B02}, /* 414 */ + {0x020B0902}, {0x020A0A02}, {0xFF000001}, {0x010A0B02}, {0x01050E02}, /* 419 */ + {0xFF000001}, {0x01040E02}, {0x010C0802}, {0xFF000001}, {0x010D0602}, /* 424 */ + {0x01030E02}, {0xFF000001}, {0x010E0202}, {0x010E0001}, {0xFF000001}, /* 429 */ + {0x01000E01}, {0x010D0502}, {0xFF000001}, {0x01050D02}, {0x010C0702}, /* 434 */ + {0xFF000001}, {0x01070C02}, {0x010D0402}, {0xFF000001}, {0x010B0802}, /* 439 */ + {0x01080B02}, {0xFF000001}, {0x01040D02}, {0x010A0902}, {0xFF000001}, /* 444 */ + {0x01090A02}, {0x010C0602}, {0xFF000001}, {0x01030D02}, {0x010B0702}, /* 449 */ + {0xFF000001}, {0x010C0502}, {0x01050C02}, {0xFF000001}, {0x01090902}, /* 454 */ + {0x010A0702}, {0xFF000001}, {0x01070A02}, {0x01070902}, {0xFF000003}, /* 459 */ + {0x00000023}, {0x030D0F02}, {0x020D0E02}, {0x020D0E02}, {0x010F0F02}, /* 464 */ + {0x010F0F02}, {0x010F0F02}, {0x010F0F02}, {0xFF000001}, {0x010F0E02}, /* 469 */ + {0x010F0D02}, {0xFF000001}, {0x010E0E02}, {0x010F0C02}, {0xFF000001}, /* 474 */ + {0x010E0D02}, {0x010F0B02}, {0xFF000001}, {0x010B0F02}, {0x010E0C02}, /* 479 */ + {0xFF000002}, {0x010C0D02}, {0x010C0D02}, {0x020F0A02}, {0x02090E02}, /* 484 */ + {0xFF000001}, {0x010A0F02}, {0x010D0C02}, {0xFF000001}, {0x010E0A02}, /* 489 */ + {0x010E0902}, {0xFF000001}, {0x010F0702}, {0x010E0702}, {0xFF000001}, /* 494 */ + {0x010E0F02}, {0x010C0F02}, +}; + +/* max table bits 6 */ +/* NO XING TABLE 14 */ + +/* TABLE 15 256 entries maxbits 13 linbits 0 */ +static const HUFF_ELEMENT huff_table_15[] = +{ + {0xFF000008}, {0x00000101}, {0x00000122}, {0x00000143}, {0x00000154}, /* 4 */ + {0x00000165}, {0x00000176}, {0x0000017F}, {0x00000188}, {0x00000199}, /* 9 */ + {0x000001A2}, {0x000001AB}, {0x000001B4}, {0x000001BD}, {0x000001C2}, /* 14 */ + {0x000001CB}, {0x000001D4}, {0x000001D9}, {0x000001DE}, {0x000001E3}, /* 19 */ + {0x000001E8}, {0x000001ED}, {0x000001F2}, {0x000001F7}, {0x000001FC}, /* 24 */ + {0x00000201}, {0x00000204}, {0x00000207}, {0x0000020A}, {0x0000020F}, /* 29 */ + {0x00000212}, {0x00000215}, {0x0000021A}, {0x0000021D}, {0x00000220}, /* 34 */ + {0x08010902}, {0x00000223}, {0x00000226}, {0x00000229}, {0x0000022C}, /* 39 */ + {0x0000022F}, {0x08080202}, {0x08020802}, {0x08080102}, {0x08010802}, /* 44 */ + {0x00000232}, {0x00000235}, {0x00000238}, {0x0000023B}, {0x08070202}, /* 49 */ + {0x08020702}, {0x08040602}, {0x08070102}, {0x08050502}, {0x08010702}, /* 54 */ + {0x0000023E}, {0x08060302}, {0x08030602}, {0x08050402}, {0x08040502}, /* 59 */ + {0x08060202}, {0x08020602}, {0x08060102}, {0x00000241}, {0x08050302}, /* 64 */ + {0x07010602}, {0x07010602}, {0x08030502}, {0x08040402}, {0x07050202}, /* 69 */ + {0x07050202}, {0x07020502}, {0x07020502}, {0x07050102}, {0x07050102}, /* 74 */ + {0x07010502}, {0x07010502}, {0x08050001}, {0x08000501}, {0x07040302}, /* 79 */ + {0x07040302}, {0x07030402}, {0x07030402}, {0x07040202}, {0x07040202}, /* 84 */ + {0x07020402}, {0x07020402}, {0x07030302}, {0x07030302}, {0x06010402}, /* 89 */ + {0x06010402}, {0x06010402}, {0x06010402}, {0x07040102}, {0x07040102}, /* 94 */ + {0x07040001}, {0x07040001}, {0x06030202}, {0x06030202}, {0x06030202}, /* 99 */ + {0x06030202}, {0x06020302}, {0x06020302}, {0x06020302}, {0x06020302}, /* 104 */ + {0x07000401}, {0x07000401}, {0x07030001}, {0x07030001}, {0x06030102}, /* 109 */ + {0x06030102}, {0x06030102}, {0x06030102}, {0x06010302}, {0x06010302}, /* 114 */ + {0x06010302}, {0x06010302}, {0x06000301}, {0x06000301}, {0x06000301}, /* 119 */ + {0x06000301}, {0x05020202}, {0x05020202}, {0x05020202}, {0x05020202}, /* 124 */ + {0x05020202}, {0x05020202}, {0x05020202}, {0x05020202}, {0x05020102}, /* 129 */ + {0x05020102}, {0x05020102}, {0x05020102}, {0x05020102}, {0x05020102}, /* 134 */ + {0x05020102}, {0x05020102}, {0x05010202}, {0x05010202}, {0x05010202}, /* 139 */ + {0x05010202}, {0x05010202}, {0x05010202}, {0x05010202}, {0x05010202}, /* 144 */ + {0x05020001}, {0x05020001}, {0x05020001}, {0x05020001}, {0x05020001}, /* 149 */ + {0x05020001}, {0x05020001}, {0x05020001}, {0x05000201}, {0x05000201}, /* 154 */ + {0x05000201}, {0x05000201}, {0x05000201}, {0x05000201}, {0x05000201}, /* 159 */ + {0x05000201}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 164 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 169 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 174 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 179 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 184 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, {0x03010102}, /* 189 */ + {0x03010102}, {0x03010102}, {0x03010102}, {0x04010001}, {0x04010001}, /* 194 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 199 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 204 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04000101}, /* 209 */ + {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, /* 214 */ + {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, /* 219 */ + {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, /* 224 */ + {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, /* 229 */ + {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, /* 234 */ + {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, /* 239 */ + {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, /* 244 */ + {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, /* 249 */ + {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, {0x03000000}, /* 254 */ + {0x03000000}, {0x03000000}, {0xFF000005}, {0x050F0F02}, {0x050F0E02}, /* 259 */ + {0x050E0F02}, {0x050F0D02}, {0x040E0E02}, {0x040E0E02}, {0x050D0F02}, /* 264 */ + {0x050F0C02}, {0x050C0F02}, {0x050E0D02}, {0x050D0E02}, {0x050F0B02}, /* 269 */ + {0x040B0F02}, {0x040B0F02}, {0x050E0C02}, {0x050C0E02}, {0x040D0D02}, /* 274 */ + {0x040D0D02}, {0x040F0A02}, {0x040F0A02}, {0x040A0F02}, {0x040A0F02}, /* 279 */ + {0x040E0B02}, {0x040E0B02}, {0x040B0E02}, {0x040B0E02}, {0x040D0C02}, /* 284 */ + {0x040D0C02}, {0x040C0D02}, {0x040C0D02}, {0x040F0902}, {0x040F0902}, /* 289 */ + {0xFF000005}, {0x04090F02}, {0x04090F02}, {0x040A0E02}, {0x040A0E02}, /* 294 */ + {0x040D0B02}, {0x040D0B02}, {0x040B0D02}, {0x040B0D02}, {0x040F0802}, /* 299 */ + {0x040F0802}, {0x04080F02}, {0x04080F02}, {0x040C0C02}, {0x040C0C02}, /* 304 */ + {0x040E0902}, {0x040E0902}, {0x04090E02}, {0x04090E02}, {0x040F0702}, /* 309 */ + {0x040F0702}, {0x04070F02}, {0x04070F02}, {0x040D0A02}, {0x040D0A02}, /* 314 */ + {0x040A0D02}, {0x040A0D02}, {0x040C0B02}, {0x040C0B02}, {0x040F0602}, /* 319 */ + {0x040F0602}, {0x050E0A02}, {0x050F0001}, {0xFF000004}, {0x030B0C02}, /* 324 */ + {0x030B0C02}, {0x03060F02}, {0x03060F02}, {0x040E0802}, {0x04080E02}, /* 329 */ + {0x040F0502}, {0x040D0902}, {0x03050F02}, {0x03050F02}, {0x030E0702}, /* 334 */ + {0x030E0702}, {0x03070E02}, {0x03070E02}, {0x030C0A02}, {0x030C0A02}, /* 339 */ + {0xFF000004}, {0x030A0C02}, {0x030A0C02}, {0x030B0B02}, {0x030B0B02}, /* 344 */ + {0x04090D02}, {0x040D0802}, {0x030F0402}, {0x030F0402}, {0x03040F02}, /* 349 */ + {0x03040F02}, {0x030F0302}, {0x030F0302}, {0x03030F02}, {0x03030F02}, /* 354 */ + {0x03080D02}, {0x03080D02}, {0xFF000004}, {0x03060E02}, {0x03060E02}, /* 359 */ + {0x030F0202}, {0x030F0202}, {0x03020F02}, {0x03020F02}, {0x040E0602}, /* 364 */ + {0x04000F01}, {0x030F0102}, {0x030F0102}, {0x03010F02}, {0x03010F02}, /* 369 */ + {0x030C0902}, {0x030C0902}, {0x03090C02}, {0x03090C02}, {0xFF000003}, /* 374 */ + {0x030E0502}, {0x030B0A02}, {0x030A0B02}, {0x03050E02}, {0x030D0702}, /* 379 */ + {0x03070D02}, {0x030E0402}, {0x03040E02}, {0xFF000003}, {0x030C0802}, /* 384 */ + {0x03080C02}, {0x030E0302}, {0x030D0602}, {0x03060D02}, {0x03030E02}, /* 389 */ + {0x030B0902}, {0x03090B02}, {0xFF000004}, {0x030E0202}, {0x030E0202}, /* 394 */ + {0x030A0A02}, {0x030A0A02}, {0x03020E02}, {0x03020E02}, {0x030E0102}, /* 399 */ + {0x030E0102}, {0x03010E02}, {0x03010E02}, {0x040E0001}, {0x04000E01}, /* 404 */ + {0x030D0502}, {0x030D0502}, {0x03050D02}, {0x03050D02}, {0xFF000003}, /* 409 */ + {0x030C0702}, {0x03070C02}, {0x030D0402}, {0x030B0802}, {0x02040D02}, /* 414 */ + {0x02040D02}, {0x03080B02}, {0x030A0902}, {0xFF000003}, {0x03090A02}, /* 419 */ + {0x030C0602}, {0x03060C02}, {0x030D0302}, {0x02030D02}, {0x02030D02}, /* 424 */ + {0x02020D02}, {0x02020D02}, {0xFF000003}, {0x030D0202}, {0x030D0001}, /* 429 */ + {0x020D0102}, {0x020D0102}, {0x020B0702}, {0x020B0702}, {0x02070B02}, /* 434 */ + {0x02070B02}, {0xFF000003}, {0x02010D02}, {0x02010D02}, {0x030C0502}, /* 439 */ + {0x03000D01}, {0x02050C02}, {0x02050C02}, {0x020A0802}, {0x020A0802}, /* 444 */ + {0xFF000002}, {0x02080A02}, {0x020C0402}, {0x02040C02}, {0x020B0602}, /* 449 */ + {0xFF000003}, {0x02060B02}, {0x02060B02}, {0x03090902}, {0x030C0001}, /* 454 */ + {0x020C0302}, {0x020C0302}, {0x02030C02}, {0x02030C02}, {0xFF000003}, /* 459 */ + {0x020A0702}, {0x020A0702}, {0x02070A02}, {0x02070A02}, {0x02060A02}, /* 464 */ + {0x02060A02}, {0x03000C01}, {0x030B0001}, {0xFF000002}, {0x01020C02}, /* 469 */ + {0x01020C02}, {0x020C0202}, {0x020B0502}, {0xFF000002}, {0x02050B02}, /* 474 */ + {0x020C0102}, {0x02090802}, {0x02080902}, {0xFF000002}, {0x02010C02}, /* 479 */ + {0x020B0402}, {0x02040B02}, {0x020A0602}, {0xFF000002}, {0x020B0302}, /* 484 */ + {0x02090702}, {0x01030B02}, {0x01030B02}, {0xFF000002}, {0x02070902}, /* 489 */ + {0x02080802}, {0x020B0202}, {0x020A0502}, {0xFF000002}, {0x01020B02}, /* 494 */ + {0x01020B02}, {0x02050A02}, {0x020B0102}, {0xFF000002}, {0x01010B02}, /* 499 */ + {0x01010B02}, {0x02000B01}, {0x02090602}, {0xFF000002}, {0x02060902}, /* 504 */ + {0x020A0402}, {0x02040A02}, {0x02080702}, {0xFF000002}, {0x02070802}, /* 509 */ + {0x020A0302}, {0x01030A02}, {0x01030A02}, {0xFF000001}, {0x01090502}, /* 514 */ + {0x01050902}, {0xFF000001}, {0x010A0202}, {0x01020A02}, {0xFF000001}, /* 519 */ + {0x010A0102}, {0x01010A02}, {0xFF000002}, {0x020A0001}, {0x02000A01}, /* 524 */ + {0x01080602}, {0x01080602}, {0xFF000001}, {0x01060802}, {0x01090402}, /* 529 */ + {0xFF000001}, {0x01040902}, {0x01090302}, {0xFF000002}, {0x01030902}, /* 534 */ + {0x01030902}, {0x02070702}, {0x02090001}, {0xFF000001}, {0x01080502}, /* 539 */ + {0x01050802}, {0xFF000001}, {0x01090202}, {0x01070602}, {0xFF000001}, /* 544 */ + {0x01060702}, {0x01020902}, {0xFF000001}, {0x01090102}, {0x01000901}, /* 549 */ + {0xFF000001}, {0x01080402}, {0x01040802}, {0xFF000001}, {0x01070502}, /* 554 */ + {0x01050702}, {0xFF000001}, {0x01080302}, {0x01030802}, {0xFF000001}, /* 559 */ + {0x01060602}, {0x01070402}, {0xFF000001}, {0x01040702}, {0x01080001}, /* 564 */ + {0xFF000001}, {0x01000801}, {0x01060502}, {0xFF000001}, {0x01050602}, /* 569 */ + {0x01070302}, {0xFF000001}, {0x01030702}, {0x01060402}, {0xFF000001}, /* 574 */ + {0x01070001}, {0x01000701}, {0xFF000001}, {0x01060001}, {0x01000601}, /* 579 */ +}; + +/* max table bits 8 */ + +/* TABLE 16 256 entries maxbits 17 linbits 0 */ +static const HUFF_ELEMENT huff_table_16[] = +{ + {0xFF000008}, {0x00000101}, {0x0000010A}, {0x00000113}, {0x080F0F02}, /* 4 */ + {0x00000118}, {0x0000011D}, {0x00000120}, {0x08020F02}, {0x00000131}, /* 9 */ + {0x080F0102}, {0x08010F02}, {0x00000134}, {0x00000145}, {0x00000156}, /* 14 */ + {0x00000167}, {0x00000178}, {0x00000189}, {0x0000019A}, {0x000001A3}, /* 19 */ + {0x000001AC}, {0x000001B5}, {0x000001BE}, {0x000001C7}, {0x000001D0}, /* 24 */ + {0x000001D9}, {0x000001DE}, {0x000001E3}, {0x000001E6}, {0x000001EB}, /* 29 */ + {0x000001F0}, {0x08010502}, {0x000001F3}, {0x000001F6}, {0x000001F9}, /* 34 */ + {0x000001FC}, {0x08040102}, {0x08010402}, {0x000001FF}, {0x08030202}, /* 39 */ + {0x08020302}, {0x07030102}, {0x07030102}, {0x07010302}, {0x07010302}, /* 44 */ + {0x08030001}, {0x08000301}, {0x07020202}, {0x07020202}, {0x06020102}, /* 49 */ + {0x06020102}, {0x06020102}, {0x06020102}, {0x06010202}, {0x06010202}, /* 54 */ + {0x06010202}, {0x06010202}, {0x06020001}, {0x06020001}, {0x06020001}, /* 59 */ + {0x06020001}, {0x06000201}, {0x06000201}, {0x06000201}, {0x06000201}, /* 64 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 69 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 74 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 79 */ + {0x04010102}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 84 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 89 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 94 */ + {0x04010001}, {0x04010001}, {0x03000101}, {0x03000101}, {0x03000101}, /* 99 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 104 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 109 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 114 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 119 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, /* 124 */ + {0x03000101}, {0x03000101}, {0x03000101}, {0x03000101}, {0x01000000}, /* 129 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 134 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 139 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 144 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 149 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 154 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 159 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 164 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 169 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 174 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 179 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 184 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 189 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 194 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 199 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 204 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 209 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 214 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 219 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 224 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 229 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 234 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 239 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 244 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 249 */ + {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, {0x01000000}, /* 254 */ + {0x01000000}, {0x01000000}, {0xFF000003}, {0x030F0E02}, {0x030E0F02}, /* 259 */ + {0x030F0D02}, {0x030D0F02}, {0x030F0C02}, {0x030C0F02}, {0x030F0B02}, /* 264 */ + {0x030B0F02}, {0xFF000003}, {0x020F0A02}, {0x020F0A02}, {0x030A0F02}, /* 269 */ + {0x030F0902}, {0x03090F02}, {0x03080F02}, {0x020F0802}, {0x020F0802}, /* 274 */ + {0xFF000002}, {0x020F0702}, {0x02070F02}, {0x020F0602}, {0x02060F02}, /* 279 */ + {0xFF000002}, {0x020F0502}, {0x02050F02}, {0x010F0402}, {0x010F0402}, /* 284 */ + {0xFF000001}, {0x01040F02}, {0x01030F02}, {0xFF000004}, {0x01000F01}, /* 289 */ + {0x01000F01}, {0x01000F01}, {0x01000F01}, {0x01000F01}, {0x01000F01}, /* 294 */ + {0x01000F01}, {0x01000F01}, {0x020F0302}, {0x020F0302}, {0x020F0302}, /* 299 */ + {0x020F0302}, {0x000000E2}, {0x000000F3}, {0x000000FC}, {0x00000105}, /* 304 */ + {0xFF000001}, {0x010F0202}, {0x010F0001}, {0xFF000004}, {0x000000FA}, /* 309 */ + {0x000000FF}, {0x00000104}, {0x00000109}, {0x0000010C}, {0x00000111}, /* 314 */ + {0x00000116}, {0x00000119}, {0x0000011E}, {0x00000123}, {0x00000128}, /* 319 */ + {0x04030E02}, {0x0000012D}, {0x00000130}, {0x00000133}, {0x00000136}, /* 324 */ + {0xFF000004}, {0x00000128}, {0x0000012B}, {0x0000012E}, {0x040D0001}, /* 329 */ + {0x00000131}, {0x00000134}, {0x00000137}, {0x040C0302}, {0x0000013A}, /* 334 */ + {0x040C0102}, {0x04000C01}, {0x0000013D}, {0x03020E02}, {0x03020E02}, /* 339 */ + {0x040E0202}, {0x040E0102}, {0xFF000004}, {0x04030D02}, {0x040D0202}, /* 344 */ + {0x04020D02}, {0x04010D02}, {0x040B0302}, {0x0000012F}, {0x030D0102}, /* 349 */ + {0x030D0102}, {0x04040C02}, {0x040B0602}, {0x04030C02}, {0x04070A02}, /* 354 */ + {0x030C0202}, {0x030C0202}, {0x04020C02}, {0x04050B02}, {0xFF000004}, /* 359 */ + {0x04010C02}, {0x040C0001}, {0x040B0402}, {0x04040B02}, {0x040A0602}, /* 364 */ + {0x04060A02}, {0x03030B02}, {0x03030B02}, {0x040A0502}, {0x04050A02}, /* 369 */ + {0x030B0202}, {0x030B0202}, {0x03020B02}, {0x03020B02}, {0x030B0102}, /* 374 */ + {0x030B0102}, {0xFF000004}, {0x03010B02}, {0x03010B02}, {0x040B0001}, /* 379 */ + {0x04000B01}, {0x04090602}, {0x04060902}, {0x040A0402}, {0x04040A02}, /* 384 */ + {0x04080702}, {0x04070802}, {0x03030A02}, {0x03030A02}, {0x040A0302}, /* 389 */ + {0x04090502}, {0x030A0202}, {0x030A0202}, {0xFF000004}, {0x04050902}, /* 394 */ + {0x04080602}, {0x03010A02}, {0x03010A02}, {0x04060802}, {0x04070702}, /* 399 */ + {0x03040902}, {0x03040902}, {0x04090402}, {0x04070502}, {0x03070602}, /* 404 */ + {0x03070602}, {0x02020A02}, {0x02020A02}, {0x02020A02}, {0x02020A02}, /* 409 */ + {0xFF000003}, {0x020A0102}, {0x020A0102}, {0x030A0001}, {0x03000A01}, /* 414 */ + {0x03090302}, {0x03030902}, {0x03080502}, {0x03050802}, {0xFF000003}, /* 419 */ + {0x02090202}, {0x02090202}, {0x02020902}, {0x02020902}, {0x03060702}, /* 424 */ + {0x03090001}, {0x02090102}, {0x02090102}, {0xFF000003}, {0x02010902}, /* 429 */ + {0x02010902}, {0x03000901}, {0x03080402}, {0x03040802}, {0x03050702}, /* 434 */ + {0x03080302}, {0x03030802}, {0xFF000003}, {0x03060602}, {0x03080202}, /* 439 */ + {0x02020802}, {0x02020802}, {0x03070402}, {0x03040702}, {0x02080102}, /* 444 */ + {0x02080102}, {0xFF000003}, {0x02010802}, {0x02010802}, {0x02000801}, /* 449 */ + {0x02000801}, {0x03080001}, {0x03060502}, {0x02070302}, {0x02070302}, /* 454 */ + {0xFF000003}, {0x02030702}, {0x02030702}, {0x03050602}, {0x03060402}, /* 459 */ + {0x02070202}, {0x02070202}, {0x02020702}, {0x02020702}, {0xFF000003}, /* 464 */ + {0x03040602}, {0x03050502}, {0x02070001}, {0x02070001}, {0x01070102}, /* 469 */ + {0x01070102}, {0x01070102}, {0x01070102}, {0xFF000002}, {0x01010702}, /* 474 */ + {0x01010702}, {0x02000701}, {0x02060302}, {0xFF000002}, {0x02030602}, /* 479 */ + {0x02050402}, {0x02040502}, {0x02060202}, {0xFF000001}, {0x01020602}, /* 484 */ + {0x01060102}, {0xFF000002}, {0x01010602}, {0x01010602}, {0x02060001}, /* 489 */ + {0x02000601}, {0xFF000002}, {0x01030502}, {0x01030502}, {0x02050302}, /* 494 */ + {0x02040402}, {0xFF000001}, {0x01050202}, {0x01020502}, {0xFF000001}, /* 499 */ + {0x01050102}, {0x01050001}, {0xFF000001}, {0x01040302}, {0x01030402}, /* 504 */ + {0xFF000001}, {0x01000501}, {0x01040202}, {0xFF000001}, {0x01020402}, /* 509 */ + {0x01030302}, {0xFF000001}, {0x01040001}, {0x01000401}, {0xFF000004}, /* 514 */ + {0x040E0C02}, {0x00000086}, {0x030E0D02}, {0x030E0D02}, {0x03090E02}, /* 519 */ + {0x03090E02}, {0x040A0E02}, {0x04090D02}, {0x020E0E02}, {0x020E0E02}, /* 524 */ + {0x020E0E02}, {0x020E0E02}, {0x030D0E02}, {0x030D0E02}, {0x030B0E02}, /* 529 */ + {0x030B0E02}, {0xFF000003}, {0x020E0B02}, {0x020E0B02}, {0x020D0C02}, /* 534 */ + {0x020D0C02}, {0x030C0D02}, {0x030B0D02}, {0x020E0A02}, {0x020E0A02}, /* 539 */ + {0xFF000003}, {0x020C0C02}, {0x020C0C02}, {0x030D0A02}, {0x030A0D02}, /* 544 */ + {0x030E0702}, {0x030C0A02}, {0x020A0C02}, {0x020A0C02}, {0xFF000003}, /* 549 */ + {0x03090C02}, {0x030D0702}, {0x020E0502}, {0x020E0502}, {0x010D0B02}, /* 554 */ + {0x010D0B02}, {0x010D0B02}, {0x010D0B02}, {0xFF000002}, {0x010E0902}, /* 559 */ + {0x010E0902}, {0x020C0B02}, {0x020B0C02}, {0xFF000002}, {0x020E0802}, /* 564 */ + {0x02080E02}, {0x020D0902}, {0x02070E02}, {0xFF000002}, {0x020B0B02}, /* 569 */ + {0x020D0802}, {0x02080D02}, {0x020E0602}, {0xFF000001}, {0x01060E02}, /* 574 */ + {0x010C0902}, {0xFF000002}, {0x020B0A02}, {0x020A0B02}, {0x02050E02}, /* 579 */ + {0x02070D02}, {0xFF000002}, {0x010E0402}, {0x010E0402}, {0x02040E02}, /* 584 */ + {0x020C0802}, {0xFF000001}, {0x01080C02}, {0x010E0302}, {0xFF000002}, /* 589 */ + {0x010D0602}, {0x010D0602}, {0x02060D02}, {0x020B0902}, {0xFF000002}, /* 594 */ + {0x02090B02}, {0x020A0A02}, {0x01010E02}, {0x01010E02}, {0xFF000002}, /* 599 */ + {0x01040D02}, {0x01040D02}, {0x02080B02}, {0x02090A02}, {0xFF000002}, /* 604 */ + {0x010B0702}, {0x010B0702}, {0x02070B02}, {0x02000D01}, {0xFF000001}, /* 609 */ + {0x010E0001}, {0x01000E01}, {0xFF000001}, {0x010D0502}, {0x01050D02}, /* 614 */ + {0xFF000001}, {0x010C0702}, {0x01070C02}, {0xFF000001}, {0x010D0402}, /* 619 */ + {0x010B0802}, {0xFF000001}, {0x010A0902}, {0x010C0602}, {0xFF000001}, /* 624 */ + {0x01060C02}, {0x010D0302}, {0xFF000001}, {0x010C0502}, {0x01050C02}, /* 629 */ + {0xFF000001}, {0x010A0802}, {0x01080A02}, {0xFF000001}, {0x01090902}, /* 634 */ + {0x010C0402}, {0xFF000001}, {0x01060B02}, {0x010A0702}, {0xFF000001}, /* 639 */ + {0x010B0502}, {0x01090802}, {0xFF000001}, {0x01080902}, {0x01090702}, /* 644 */ + {0xFF000001}, {0x01070902}, {0x01080802}, {0xFF000001}, {0x010C0E02}, /* 649 */ + {0x010D0D02}, +}; + +/* max table bits 8 */ +/* NO XING TABLE 17 */ +/* NO XING TABLE 18 */ +/* NO XING TABLE 19 */ +/* NO XING TABLE 20 */ +/* NO XING TABLE 21 */ +/* NO XING TABLE 22 */ +/* NO XING TABLE 23 */ + +/* TABLE 24 256 entries maxbits 12 linbits 0 */ +static const HUFF_ELEMENT huff_table_24[] = +{ + {0xFF000009}, {0x080F0E02}, {0x080F0E02}, {0x080E0F02}, {0x080E0F02}, /* 4 */ + {0x080F0D02}, {0x080F0D02}, {0x080D0F02}, {0x080D0F02}, {0x080F0C02}, /* 9 */ + {0x080F0C02}, {0x080C0F02}, {0x080C0F02}, {0x080F0B02}, {0x080F0B02}, /* 14 */ + {0x080B0F02}, {0x080B0F02}, {0x070A0F02}, {0x070A0F02}, {0x070A0F02}, /* 19 */ + {0x070A0F02}, {0x080F0A02}, {0x080F0A02}, {0x080F0902}, {0x080F0902}, /* 24 */ + {0x07090F02}, {0x07090F02}, {0x07090F02}, {0x07090F02}, {0x07080F02}, /* 29 */ + {0x07080F02}, {0x07080F02}, {0x07080F02}, {0x080F0802}, {0x080F0802}, /* 34 */ + {0x080F0702}, {0x080F0702}, {0x07070F02}, {0x07070F02}, {0x07070F02}, /* 39 */ + {0x07070F02}, {0x070F0602}, {0x070F0602}, {0x070F0602}, {0x070F0602}, /* 44 */ + {0x07060F02}, {0x07060F02}, {0x07060F02}, {0x07060F02}, {0x070F0502}, /* 49 */ + {0x070F0502}, {0x070F0502}, {0x070F0502}, {0x07050F02}, {0x07050F02}, /* 54 */ + {0x07050F02}, {0x07050F02}, {0x070F0402}, {0x070F0402}, {0x070F0402}, /* 59 */ + {0x070F0402}, {0x07040F02}, {0x07040F02}, {0x07040F02}, {0x07040F02}, /* 64 */ + {0x070F0302}, {0x070F0302}, {0x070F0302}, {0x070F0302}, {0x07030F02}, /* 69 */ + {0x07030F02}, {0x07030F02}, {0x07030F02}, {0x070F0202}, {0x070F0202}, /* 74 */ + {0x070F0202}, {0x070F0202}, {0x07020F02}, {0x07020F02}, {0x07020F02}, /* 79 */ + {0x07020F02}, {0x07010F02}, {0x07010F02}, {0x07010F02}, {0x07010F02}, /* 84 */ + {0x080F0102}, {0x080F0102}, {0x08000F01}, {0x08000F01}, {0x090F0001}, /* 89 */ + {0x00000201}, {0x00000206}, {0x0000020B}, {0x00000210}, {0x00000215}, /* 94 */ + {0x0000021A}, {0x0000021F}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, /* 99 */ + {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, /* 104 */ + {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, /* 109 */ + {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, /* 114 */ + {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, /* 119 */ + {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, /* 124 */ + {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x040F0F02}, {0x00000224}, /* 129 */ + {0x00000229}, {0x00000232}, {0x00000237}, {0x0000023A}, {0x0000023F}, /* 134 */ + {0x00000242}, {0x00000245}, {0x0000024A}, {0x0000024D}, {0x00000250}, /* 139 */ + {0x00000253}, {0x00000256}, {0x00000259}, {0x0000025C}, {0x0000025F}, /* 144 */ + {0x00000262}, {0x00000265}, {0x00000268}, {0x0000026B}, {0x0000026E}, /* 149 */ + {0x00000271}, {0x00000274}, {0x00000277}, {0x0000027A}, {0x0000027D}, /* 154 */ + {0x00000280}, {0x00000283}, {0x00000288}, {0x0000028B}, {0x0000028E}, /* 159 */ + {0x00000291}, {0x00000294}, {0x00000297}, {0x0000029A}, {0x0000029F}, /* 164 */ + {0x09040B02}, {0x000002A4}, {0x000002A7}, {0x000002AA}, {0x09030B02}, /* 169 */ + {0x09080802}, {0x000002AF}, {0x09020B02}, {0x000002B2}, {0x000002B5}, /* 174 */ + {0x09060902}, {0x09040A02}, {0x000002B8}, {0x09070802}, {0x090A0302}, /* 179 */ + {0x09030A02}, {0x09090502}, {0x09050902}, {0x090A0202}, {0x09020A02}, /* 184 */ + {0x09010A02}, {0x09080602}, {0x09060802}, {0x09070702}, {0x09090402}, /* 189 */ + {0x09040902}, {0x09090302}, {0x09030902}, {0x09080502}, {0x09050802}, /* 194 */ + {0x09090202}, {0x09070602}, {0x09060702}, {0x09020902}, {0x09090102}, /* 199 */ + {0x09010902}, {0x09080402}, {0x09040802}, {0x09070502}, {0x09050702}, /* 204 */ + {0x09080302}, {0x09030802}, {0x09060602}, {0x09080202}, {0x09020802}, /* 209 */ + {0x09080102}, {0x09070402}, {0x09040702}, {0x09010802}, {0x000002BB}, /* 214 */ + {0x09060502}, {0x09050602}, {0x09070102}, {0x000002BE}, {0x08030702}, /* 219 */ + {0x08030702}, {0x09070302}, {0x09070202}, {0x08020702}, {0x08020702}, /* 224 */ + {0x08060402}, {0x08060402}, {0x08040602}, {0x08040602}, {0x08050502}, /* 229 */ + {0x08050502}, {0x08010702}, {0x08010702}, {0x08060302}, {0x08060302}, /* 234 */ + {0x08030602}, {0x08030602}, {0x08050402}, {0x08050402}, {0x08040502}, /* 239 */ + {0x08040502}, {0x08060202}, {0x08060202}, {0x08020602}, {0x08020602}, /* 244 */ + {0x08060102}, {0x08060102}, {0x08010602}, {0x08010602}, {0x09060001}, /* 249 */ + {0x09000601}, {0x08050302}, {0x08050302}, {0x08030502}, {0x08030502}, /* 254 */ + {0x08040402}, {0x08040402}, {0x08050202}, {0x08050202}, {0x08020502}, /* 259 */ + {0x08020502}, {0x08050102}, {0x08050102}, {0x09050001}, {0x09000501}, /* 264 */ + {0x07010502}, {0x07010502}, {0x07010502}, {0x07010502}, {0x08040302}, /* 269 */ + {0x08040302}, {0x08030402}, {0x08030402}, {0x07040202}, {0x07040202}, /* 274 */ + {0x07040202}, {0x07040202}, {0x07020402}, {0x07020402}, {0x07020402}, /* 279 */ + {0x07020402}, {0x07030302}, {0x07030302}, {0x07030302}, {0x07030302}, /* 284 */ + {0x07040102}, {0x07040102}, {0x07040102}, {0x07040102}, {0x07010402}, /* 289 */ + {0x07010402}, {0x07010402}, {0x07010402}, {0x08040001}, {0x08040001}, /* 294 */ + {0x08000401}, {0x08000401}, {0x07030202}, {0x07030202}, {0x07030202}, /* 299 */ + {0x07030202}, {0x07020302}, {0x07020302}, {0x07020302}, {0x07020302}, /* 304 */ + {0x06030102}, {0x06030102}, {0x06030102}, {0x06030102}, {0x06030102}, /* 309 */ + {0x06030102}, {0x06030102}, {0x06030102}, {0x06010302}, {0x06010302}, /* 314 */ + {0x06010302}, {0x06010302}, {0x06010302}, {0x06010302}, {0x06010302}, /* 319 */ + {0x06010302}, {0x07030001}, {0x07030001}, {0x07030001}, {0x07030001}, /* 324 */ + {0x07000301}, {0x07000301}, {0x07000301}, {0x07000301}, {0x06020202}, /* 329 */ + {0x06020202}, {0x06020202}, {0x06020202}, {0x06020202}, {0x06020202}, /* 334 */ + {0x06020202}, {0x06020202}, {0x05020102}, {0x05020102}, {0x05020102}, /* 339 */ + {0x05020102}, {0x05020102}, {0x05020102}, {0x05020102}, {0x05020102}, /* 344 */ + {0x05020102}, {0x05020102}, {0x05020102}, {0x05020102}, {0x05020102}, /* 349 */ + {0x05020102}, {0x05020102}, {0x05020102}, {0x05010202}, {0x05010202}, /* 354 */ + {0x05010202}, {0x05010202}, {0x05010202}, {0x05010202}, {0x05010202}, /* 359 */ + {0x05010202}, {0x05010202}, {0x05010202}, {0x05010202}, {0x05010202}, /* 364 */ + {0x05010202}, {0x05010202}, {0x05010202}, {0x05010202}, {0x06020001}, /* 369 */ + {0x06020001}, {0x06020001}, {0x06020001}, {0x06020001}, {0x06020001}, /* 374 */ + {0x06020001}, {0x06020001}, {0x06000201}, {0x06000201}, {0x06000201}, /* 379 */ + {0x06000201}, {0x06000201}, {0x06000201}, {0x06000201}, {0x06000201}, /* 384 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 389 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 394 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 399 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 404 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 409 */ + {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, {0x04010102}, /* 414 */ + {0x04010102}, {0x04010102}, {0x04010001}, {0x04010001}, {0x04010001}, /* 419 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 424 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 429 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 434 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 439 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, /* 444 */ + {0x04010001}, {0x04010001}, {0x04010001}, {0x04010001}, {0x04000101}, /* 449 */ + {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, /* 454 */ + {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, /* 459 */ + {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, /* 464 */ + {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, /* 469 */ + {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, /* 474 */ + {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, {0x04000101}, /* 479 */ + {0x04000101}, {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, /* 484 */ + {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, /* 489 */ + {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, /* 494 */ + {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, /* 499 */ + {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, /* 504 */ + {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, {0x04000000}, /* 509 */ + {0x04000000}, {0x04000000}, {0x04000000}, {0xFF000002}, {0x020E0E02}, /* 514 */ + {0x020E0D02}, {0x020D0E02}, {0x020E0C02}, {0xFF000002}, {0x020C0E02}, /* 519 */ + {0x020D0D02}, {0x020E0B02}, {0x020B0E02}, {0xFF000002}, {0x020D0C02}, /* 524 */ + {0x020C0D02}, {0x020E0A02}, {0x020A0E02}, {0xFF000002}, {0x020D0B02}, /* 529 */ + {0x020B0D02}, {0x020C0C02}, {0x020E0902}, {0xFF000002}, {0x02090E02}, /* 534 */ + {0x020D0A02}, {0x020A0D02}, {0x020C0B02}, {0xFF000002}, {0x020B0C02}, /* 539 */ + {0x020E0802}, {0x02080E02}, {0x020D0902}, {0xFF000002}, {0x02090D02}, /* 544 */ + {0x020E0702}, {0x02070E02}, {0x020C0A02}, {0xFF000002}, {0x020A0C02}, /* 549 */ + {0x020B0B02}, {0x020D0802}, {0x02080D02}, {0xFF000003}, {0x030E0001}, /* 554 */ + {0x03000E01}, {0x020D0001}, {0x020D0001}, {0x01060E02}, {0x01060E02}, /* 559 */ + {0x01060E02}, {0x01060E02}, {0xFF000002}, {0x020E0602}, {0x020C0902}, /* 564 */ + {0x01090C02}, {0x01090C02}, {0xFF000001}, {0x010E0502}, {0x010A0B02}, /* 569 */ + {0xFF000002}, {0x01050E02}, {0x01050E02}, {0x020B0A02}, {0x020D0702}, /* 574 */ + {0xFF000001}, {0x01070D02}, {0x01040E02}, {0xFF000001}, {0x010C0802}, /* 579 */ + {0x01080C02}, {0xFF000002}, {0x020E0402}, {0x020E0202}, {0x010E0302}, /* 584 */ + {0x010E0302}, {0xFF000001}, {0x010D0602}, {0x01060D02}, {0xFF000001}, /* 589 */ + {0x01030E02}, {0x010B0902}, {0xFF000001}, {0x01090B02}, {0x010A0A02}, /* 594 */ + {0xFF000001}, {0x01020E02}, {0x010E0102}, {0xFF000001}, {0x01010E02}, /* 599 */ + {0x010D0502}, {0xFF000001}, {0x01050D02}, {0x010C0702}, {0xFF000001}, /* 604 */ + {0x01070C02}, {0x010D0402}, {0xFF000001}, {0x010B0802}, {0x01080B02}, /* 609 */ + {0xFF000001}, {0x01040D02}, {0x010A0902}, {0xFF000001}, {0x01090A02}, /* 614 */ + {0x010C0602}, {0xFF000001}, {0x01060C02}, {0x010D0302}, {0xFF000001}, /* 619 */ + {0x01030D02}, {0x010D0202}, {0xFF000001}, {0x01020D02}, {0x010D0102}, /* 624 */ + {0xFF000001}, {0x010B0702}, {0x01070B02}, {0xFF000001}, {0x01010D02}, /* 629 */ + {0x010C0502}, {0xFF000001}, {0x01050C02}, {0x010A0802}, {0xFF000001}, /* 634 */ + {0x01080A02}, {0x01090902}, {0xFF000001}, {0x010C0402}, {0x01040C02}, /* 639 */ + {0xFF000001}, {0x010B0602}, {0x01060B02}, {0xFF000002}, {0x02000D01}, /* 644 */ + {0x020C0001}, {0x010C0302}, {0x010C0302}, {0xFF000001}, {0x01030C02}, /* 649 */ + {0x010A0702}, {0xFF000001}, {0x01070A02}, {0x010C0202}, {0xFF000001}, /* 654 */ + {0x01020C02}, {0x010B0502}, {0xFF000001}, {0x01050B02}, {0x010C0102}, /* 659 */ + {0xFF000001}, {0x01090802}, {0x01080902}, {0xFF000001}, {0x01010C02}, /* 664 */ + {0x010B0402}, {0xFF000002}, {0x02000C01}, {0x020B0001}, {0x010B0302}, /* 669 */ + {0x010B0302}, {0xFF000002}, {0x02000B01}, {0x020A0001}, {0x010A0102}, /* 674 */ + {0x010A0102}, {0xFF000001}, {0x010A0602}, {0x01060A02}, {0xFF000001}, /* 679 */ + {0x01090702}, {0x01070902}, {0xFF000002}, {0x02000A01}, {0x02090001}, /* 684 */ + {0x01000901}, {0x01000901}, {0xFF000001}, {0x010B0202}, {0x010A0502}, /* 689 */ + {0xFF000001}, {0x01050A02}, {0x010B0102}, {0xFF000001}, {0x01010B02}, /* 694 */ + {0x01090602}, {0xFF000001}, {0x010A0402}, {0x01080702}, {0xFF000001}, /* 699 */ + {0x01080001}, {0x01000801}, {0xFF000001}, {0x01070001}, {0x01000701}, /* 704 */ +}; + +/* max table bits 9 */ +/* NO XING TABLE 25 */ +/* NO XING TABLE 26 */ +/* NO XING TABLE 27 */ +/* NO XING TABLE 28 */ +/* NO XING TABLE 29 */ +/* NO XING TABLE 30 */ +/* NO XING TABLE 31 */ +/* done */ diff --git a/code/mp3code/hwin.c b/code/mp3code/hwin.c new file mode 100644 index 0000000..53c8a87 --- /dev/null +++ b/code/mp3code/hwin.c @@ -0,0 +1,264 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: hwin.c,v 1.5 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/**** hwin.c *************************************************** + +Layer III + +hybrid window/filter + +******************************************************************/ +#include +#include +#include +#include + +#include "mp3struct.h" +////@@@@extern int band_limit_nsb; + +typedef float ARRAY36[36]; + +/*-- windows by block type --*/ +static float win[4][36]; // effectively a constant + + +/*====================================================================*/ +void imdct18(float f[]); /* 18 point */ +void imdct6_3(float f[]); /* 6 point */ + +/*====================================================================*/ +ARRAY36 *hwin_init_addr() +{ + return win; +} + + +/*====================================================================*/ +int hybrid(float xin[], float xprev[], float y[18][32], + int btype, int nlong, int ntot, int nprev) +{ + int i, j; + float *x, *x0; + float xa, xb; + int n; + int nout; + + + + if (btype == 2) + btype = 0; + x = xin; + x0 = xprev; + +/*-- do long blocks (if any) --*/ + n = (nlong + 17) / 18; /* number of dct's to do */ + for (i = 0; i < n; i++) + { + imdct18(x); + for (j = 0; j < 9; j++) + { + y[j][i] = x0[j] + win[btype][j] * x[9 + j]; + y[9 + j][i] = x0[9 + j] + win[btype][9 + j] * x[17 - j]; + } + /* window x for next time x0 */ + for (j = 0; j < 4; j++) + { + xa = x[j]; + xb = x[8 - j]; + x[j] = win[btype][18 + j] * xb; + x[8 - j] = win[btype][(18 + 8) - j] * xa; + x[9 + j] = win[btype][(18 + 9) + j] * xa; + x[17 - j] = win[btype][(18 + 17) - j] * xb; + } + xa = x[j]; + x[j] = win[btype][18 + j] * xa; + x[9 + j] = win[btype][(18 + 9) + j] * xa; + + x += 18; + x0 += 18; + } + +/*-- do short blocks (if any) --*/ + n = (ntot + 17) / 18; /* number of 6 pt dct's triples to do */ + for (; i < n; i++) + { + imdct6_3(x); + for (j = 0; j < 3; j++) + { + y[j][i] = x0[j]; + y[3 + j][i] = x0[3 + j]; + + y[6 + j][i] = x0[6 + j] + win[2][j] * x[3 + j]; + y[9 + j][i] = x0[9 + j] + win[2][3 + j] * x[5 - j]; + + y[12 + j][i] = x0[12 + j] + win[2][6 + j] * x[2 - j] + win[2][j] * x[(6 + 3) + j]; + y[15 + j][i] = x0[15 + j] + win[2][9 + j] * x[j] + win[2][3 + j] * x[(6 + 5) - j]; + } + /* window x for next time x0 */ + for (j = 0; j < 3; j++) + { + x[j] = win[2][6 + j] * x[(6 + 2) - j] + win[2][j] * x[(12 + 3) + j]; + x[3 + j] = win[2][9 + j] * x[6 + j] + win[2][3 + j] * x[(12 + 5) - j]; + } + for (j = 0; j < 3; j++) + { + x[6 + j] = win[2][6 + j] * x[(12 + 2) - j]; + x[9 + j] = win[2][9 + j] * x[12 + j]; + } + for (j = 0; j < 3; j++) + { + x[12 + j] = 0.0f; + x[15 + j] = 0.0f; + } + x += 18; + x0 += 18; + } + +/*--- overlap prev if prev longer that current --*/ + n = (nprev + 17) / 18; + for (; i < n; i++) + { + for (j = 0; j < 18; j++) + y[j][i] = x0[j]; + x0 += 18; + } + nout = 18 * i; + +/*--- clear remaining only to band limit --*/ + for (; i < pMP3Stream->band_limit_nsb; i++) + { + for (j = 0; j < 18; j++) + y[j][i] = 0.0f; + } + + return nout; +} + + +/*--------------------------------------------------------------------*/ +/*--------------------------------------------------------------------*/ +/*-- convert to mono, add curr result to y, + window and add next time to current left */ +int hybrid_sum(float xin[], float xin_left[], float y[18][32], + int btype, int nlong, int ntot) +{ + int i, j; + float *x, *x0; + float xa, xb; + int n; + int nout; + + + + if (btype == 2) + btype = 0; + x = xin; + x0 = xin_left; + +/*-- do long blocks (if any) --*/ + n = (nlong + 17) / 18; /* number of dct's to do */ + for (i = 0; i < n; i++) + { + imdct18(x); + for (j = 0; j < 9; j++) + { + y[j][i] += win[btype][j] * x[9 + j]; + y[9 + j][i] += win[btype][9 + j] * x[17 - j]; + } + /* window x for next time x0 */ + for (j = 0; j < 4; j++) + { + xa = x[j]; + xb = x[8 - j]; + x0[j] += win[btype][18 + j] * xb; + x0[8 - j] += win[btype][(18 + 8) - j] * xa; + x0[9 + j] += win[btype][(18 + 9) + j] * xa; + x0[17 - j] += win[btype][(18 + 17) - j] * xb; + } + xa = x[j]; + x0[j] += win[btype][18 + j] * xa; + x0[9 + j] += win[btype][(18 + 9) + j] * xa; + + x += 18; + x0 += 18; + } + +/*-- do short blocks (if any) --*/ + n = (ntot + 17) / 18; /* number of 6 pt dct's triples to do */ + for (; i < n; i++) + { + imdct6_3(x); + for (j = 0; j < 3; j++) + { + y[6 + j][i] += win[2][j] * x[3 + j]; + y[9 + j][i] += win[2][3 + j] * x[5 - j]; + + y[12 + j][i] += win[2][6 + j] * x[2 - j] + win[2][j] * x[(6 + 3) + j]; + y[15 + j][i] += win[2][9 + j] * x[j] + win[2][3 + j] * x[(6 + 5) - j]; + } + /* window x for next time */ + for (j = 0; j < 3; j++) + { + x0[j] += win[2][6 + j] * x[(6 + 2) - j] + win[2][j] * x[(12 + 3) + j]; + x0[3 + j] += win[2][9 + j] * x[6 + j] + win[2][3 + j] * x[(12 + 5) - j]; + } + for (j = 0; j < 3; j++) + { + x0[6 + j] += win[2][6 + j] * x[(12 + 2) - j]; + x0[9 + j] += win[2][9 + j] * x[12 + j]; + } + x += 18; + x0 += 18; + } + + nout = 18 * i; + + return nout; +} +/*--------------------------------------------------------------------*/ +void sum_f_bands(float a[], float b[], int n) +{ + int i; + + for (i = 0; i < n; i++) + a[i] += b[i]; +} +/*--------------------------------------------------------------------*/ +/*--------------------------------------------------------------------*/ +void FreqInvert(float y[18][32], int n) +{ + int i, j; + + n = (n + 17) / 18; + for (j = 0; j < 18; j += 2) + { + for (i = 0; i < n; i += 2) + { + y[1 + j][1 + i] = -y[1 + j][1 + i]; + } + } +} +/*--------------------------------------------------------------------*/ diff --git a/code/mp3code/jdw.h b/code/mp3code/jdw.h new file mode 100644 index 0000000..16cab6c --- /dev/null +++ b/code/mp3code/jdw.h @@ -0,0 +1,28 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + Copyright (C) 1998 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: jdw.h,v 1.2 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/* LOL */ + +#ifndef min +#define min(a,b) ((a>b)?b:a) +#endif diff --git a/code/mp3code/l3.h b/code/mp3code/l3.h new file mode 100644 index 0000000..b5b1801 --- /dev/null +++ b/code/mp3code/l3.h @@ -0,0 +1,187 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1996-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 Emusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: L3.h,v 1.7 1999/12/10 07:16:42 elrod Exp $ +____________________________________________________________________________*/ + +/**** L3.h *************************************************** + + Layer III structures + + *** Layer III is 32 bit only *** + *** Layer III code assumes 32 bit int *** + +******************************************************************/ +#ifndef L3_H +#define L3_H + +#include "config.h" + +#define GLOBAL_GAIN_SCALE (4*15) +/* #define GLOBAL_GAIN_SCALE 0 */ + + +#ifdef _M_IX86 +#define LITTLE_ENDIAN 1 +#endif + +#ifdef _M_ALPHA +#define LITTLE_ENDIAN 1 +#endif + +#ifdef sparc +#define LITTLE_ENDIAN 0 +#endif + +#if defined(__POWERPC__) +#define LITTLE_ENDIAN 0 +#elif defined(__INTEL__) +#define LITTLE_ENDIAN 1 +#endif + +#ifndef LITTLE_ENDIAN +#error Layer III LITTLE_ENDIAN must be defined 0 or 1 +#endif + +/*-----------------------------------------------------------*/ +/*---- huffman lookup tables ---*/ +/* endian dependent !!! */ +#if LITTLE_ENDIAN +typedef union +{ + int ptr; + struct + { + unsigned char signbits; + unsigned char x; + unsigned char y; + unsigned char purgebits; // 0 = esc + + } + b; +} +HUFF_ELEMENT; + +#else /* big endian machines */ +typedef union +{ + int ptr; /* int must be 32 bits or more */ + struct + { + unsigned char purgebits; // 0 = esc + + unsigned char y; + unsigned char x; + unsigned char signbits; + } + b; +} +HUFF_ELEMENT; + +#endif +/*--------------------------------------------------------------*/ +typedef struct +{ + unsigned int bitbuf; + int bits; + unsigned char *bs_ptr; + unsigned char *bs_ptr0; + unsigned char *bs_ptr_end; // optional for overrun test + +} +BITDAT; + +/*-- side info ---*/ +typedef struct +{ + int part2_3_length; + int big_values; + int global_gain; + int scalefac_compress; + int window_switching_flag; + int block_type; + int mixed_block_flag; + int table_select[3]; + int subblock_gain[3]; + int region0_count; + int region1_count; + int preflag; + int scalefac_scale; + int count1table_select; +} +GR; +typedef struct +{ + int mode; + int mode_ext; +/*---------------*/ + int main_data_begin; /* beginning, not end, my spec wrong */ + int private_bits; +/*---------------*/ + int scfsi[2]; /* 4 bit flags [ch] */ + GR gr[2][2]; /* [gran][ch] */ +} +SIDE_INFO; + +/*-----------------------------------------------------------*/ +/*-- scale factors ---*/ +// check dimensions - need 21 long, 3*12 short +// plus extra for implicit sf=0 above highest cb +typedef struct +{ + int l[23]; /* [cb] */ + int s[3][13]; /* [window][cb] */ +} +SCALEFACT; + +/*-----------------------------------------------------------*/ +typedef struct +{ + int cbtype; /* long=0 short=1 */ + int cbmax; /* max crit band */ +// int lb_type; /* long block type 0 1 3 */ + int cbs0; /* short band start index 0 3 12 (12=no shorts */ + int ncbl; /* number long cb's 0 8 21 */ + int cbmax_s[3]; /* cbmax by individual short blocks */ +} +CB_INFO; + +/*-----------------------------------------------------------*/ +/* scale factor infor for MPEG2 intensity stereo */ +typedef struct +{ + int nr[3]; + int slen[3]; + int intensity_scale; +} +IS_SF_INFO; + + +#ifndef SAMPLE +#include "small_header.h" +#endif + +/*-----------------------------------------------------------*/ + +#endif // #ifndef L3_H + diff --git a/code/mp3code/l3dq.c b/code/mp3code/l3dq.c new file mode 100644 index 0000000..e9646b2 --- /dev/null +++ b/code/mp3code/l3dq.c @@ -0,0 +1,262 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: l3dq.c,v 1.6 1999/10/19 07:13:09 elrod Exp $ +____________________________________________________________________________*/ + +/**** quant.c *************************************************** + Layer III dequant + + does reordering of short blocks + + mod 8/19/98 decode 22 sf bands + +******************************************************************/ + +#include +#include +#include +#include +#include +#include "L3.h" + +#include "mp3struct.h" + +/*---------- +static struct { +int l[23]; +int s[14];} sfBandTable[3] = +{{{0,4,8,12,16,20,24,30,36,44,52,62,74,90,110,134,162,196,238,288,342,418,576}, + {0,4,8,12,16,22,30,40,52,66,84,106,136,192}}, +{{0,4,8,12,16,20,24,30,36,42,50,60,72,88,106,128,156,190,230,276,330,384,576}, + {0,4,8,12,16,22,28,38,50,64,80,100,126,192}}, +{{0,4,8,12,16,20,24,30,36,44,54,66,82,102,126,156,194,240,296,364,448,550,576}, + {0,4,8,12,16,22,30,42,58,78,104,138,180,192}}}; +----------*/ + +/*--------------------------------*/ +static const int pretab[2][22] = +{ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 2, 0}, +}; + + +////@@@@extern int nBand[2][22]; /* long = nBand[0][i], short = nBand[1][i] */ + +/* 8 bit plus 2 lookup x = pow(2.0, 0.25*(global_gain-210)) */ +/* two extra slots to do 1/sqrt(2) scaling for ms */ +/* 4 extra slots to do 1/2 scaling for cvt to mono */ +static float look_global[256 + 2 + 4]; // effectively constant + +/*-------- scaling lookup +x = pow(2.0, -0.5*(1+scalefact_scale)*scalefac + preemp) +look_scale[scalefact_scale][preemp][scalefac] +-----------------------*/ +static float look_scale[2][4][32]; // effectively constant +typedef float LS[4][32]; + + +/*--- iSample**(4/3) lookup, -32<=i<=31 ---*/ +#define ISMAX 32 +static float look_pow[2 * ISMAX]; // effectively constant + +/*-- pow(2.0, -0.25*8.0*subblock_gain) --*/ +static float look_subblock[8]; // effectively constant + +/*-- reorder buffer ---*/ +static float re_buf[192][3]; // used by dequant() below, but only during func (as workspace) +typedef float ARRAY3[3]; + + +/*=============================================================*/ +float *quant_init_global_addr() +{ + return look_global; +} +/*-------------------------------------------------------------*/ +LS *quant_init_scale_addr() +{ + return look_scale; +} +/*-------------------------------------------------------------*/ +float *quant_init_pow_addr() +{ + return look_pow; +} +/*-------------------------------------------------------------*/ +float *quant_init_subblock_addr() +{ + return look_subblock; +} +/*=============================================================*/ + +#ifdef _MSC_VER +#pragma warning(disable: 4056) +#endif + +void dequant(SAMPLE Sample[], int *nsamp, + SCALEFACT * sf, + GR * gr, + CB_INFO * cb_info, int ncbl_mixed) +{ + int i, j; + int cb, n, w; + float x0, xs; + float xsb[3]; + double tmp; + int ncbl; + int cbs0; + ARRAY3 *buf; /* short block reorder */ + int nbands; + int i0; + int non_zero; + int cbmax[3]; + + nbands = *nsamp; + + + ncbl = 22; /* long block cb end */ + cbs0 = 12; /* short block cb start */ +/* ncbl_mixed = 8 or 6 mpeg1 or 2 */ + if (gr->block_type == 2) + { + ncbl = 0; + cbs0 = 0; + if (gr->mixed_block_flag) + { + ncbl = ncbl_mixed; + cbs0 = 3; + } + } +/* fill in cb_info -- */ + /* This doesn't seem used anywhere... + cb_info->lb_type = gr->block_type; + if (gr->block_type == 2) + cb_info->lb_type; + */ + cb_info->cbs0 = cbs0; + cb_info->ncbl = ncbl; + + cbmax[2] = cbmax[1] = cbmax[0] = 0; +/* global gain pre-adjusted by 2 if ms_mode, 0 otherwise */ + x0 = look_global[(2 + 4) + gr->global_gain]; + i = 0; +/*----- long blocks ---*/ + for (cb = 0; cb < ncbl; cb++) + { + non_zero = 0; + xs = x0 * look_scale[gr->scalefac_scale][pretab[gr->preflag][cb]][sf->l[cb]]; + n = pMP3Stream->nBand[0][cb]; + for (j = 0; j < n; j++, i++) + { + if (Sample[i].s == 0) + Sample[i].x = 0.0F; + else + { + non_zero = 1; + if ((Sample[i].s >= (-ISMAX)) && (Sample[i].s < ISMAX)) + Sample[i].x = xs * look_pow[ISMAX + Sample[i].s]; + else + { + float tmpConst = (float)(1.0/3.0); + tmp = (double) Sample[i].s; + Sample[i].x = (float) (xs * tmp * pow(fabs(tmp), tmpConst)); + } + } + } + if (non_zero) + cbmax[0] = cb; + if (i >= nbands) + break; + } + + cb_info->cbmax = cbmax[0]; + cb_info->cbtype = 0; // type = long + + if (cbs0 >= 12) + return; +/*--------------------------- +block type = 2 short blocks +----------------------------*/ + cbmax[2] = cbmax[1] = cbmax[0] = cbs0; + i0 = i; /* save for reorder */ + buf = re_buf; + for (w = 0; w < 3; w++) + xsb[w] = x0 * look_subblock[gr->subblock_gain[w]]; + for (cb = cbs0; cb < 13; cb++) + { + n = pMP3Stream->nBand[1][cb]; + for (w = 0; w < 3; w++) + { + non_zero = 0; + xs = xsb[w] * look_scale[gr->scalefac_scale][0][sf->s[w][cb]]; + for (j = 0; j < n; j++, i++) + { + if (Sample[i].s == 0) + buf[j][w] = 0.0F; + else + { + non_zero = 1; + if ((Sample[i].s >= (-ISMAX)) && (Sample[i].s < ISMAX)) + buf[j][w] = xs * look_pow[ISMAX + Sample[i].s]; + else + { + float tmpConst = (float)(1.0/3.0); + tmp = (double) Sample[i].s; + buf[j][w] = (float) (xs * tmp * pow(fabs(tmp), tmpConst)); + } + } + } + if (non_zero) + cbmax[w] = cb; + } + if (i >= nbands) + break; + buf += n; + } + + + memmove(&Sample[i0].x, &re_buf[0][0], sizeof(float) * (i - i0)); + + *nsamp = i; /* update nsamp */ + cb_info->cbmax_s[0] = cbmax[0]; + cb_info->cbmax_s[1] = cbmax[1]; + cb_info->cbmax_s[2] = cbmax[2]; + if (cbmax[1] > cbmax[0]) + cbmax[0] = cbmax[1]; + if (cbmax[2] > cbmax[0]) + cbmax[0] = cbmax[2]; + + cb_info->cbmax = cbmax[0]; + cb_info->cbtype = 1; /* type = short */ + + + return; +} + +#ifdef _MSC_VER +#pragma warning(default: 4056) +#endif + +/*-------------------------------------------------------------*/ diff --git a/code/mp3code/l3init.c b/code/mp3code/l3init.c new file mode 100644 index 0000000..9dabd38 --- /dev/null +++ b/code/mp3code/l3init.c @@ -0,0 +1,422 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: l3init.c,v 1.2 1999/10/19 07:13:09 elrod Exp $ +____________________________________________________________________________*/ + +/**** tinit.c *************************************************** + Layer III init tables + + +******************************************************************/ + +#include +#include +#include +#include +#include "L3.h" + +/* get rid of precision loss warnings on conversion */ +#ifdef _MSC_VER +#pragma warning(disable:4244 4056) +#endif + + +/*---------- quant ---------------------------------*/ +/* 8 bit lookup x = pow(2.0, 0.25*(global_gain-210)) */ +float *quant_init_global_addr(); + + +/* x = pow(2.0, -0.5*(1+scalefact_scale)*scalefac + preemp) */ +typedef float LS[4][32]; +LS *quant_init_scale_addr(); + + +float *quant_init_pow_addr(); +float *quant_init_subblock_addr(); + +typedef int iARRAY22[22]; +iARRAY22 *quant_init_band_addr(); + +/*---------- antialias ---------------------------------*/ +typedef float PAIR[2]; +PAIR *alias_init_addr(); + +static const float Ci[8] = +{ + -0.6f, -0.535f, -0.33f, -0.185f, -0.095f, -0.041f, -0.0142f, -0.0037f}; + + +void hwin_init(); /* hybrid windows -- */ +void imdct_init(); +typedef struct +{ + float *w; + float *w2; + void *coef; +} +IMDCT_INIT_BLOCK; + +void msis_init(); +void msis_init_MPEG2(); + +/*=============================================================*/ +int L3table_init() +{ + int i; + float *x; + LS *ls; + int scalefact_scale, preemp, scalefac; + double tmp; + PAIR *csa; + + static int iOnceOnly = 0; + + if (!iOnceOnly++) + { +/*================ quant ===============================*/ + + /* 8 bit plus 2 lookup x = pow(2.0, 0.25*(global_gain-210)) */ + /* extra 2 for ms scaling by 1/sqrt(2) */ + /* extra 4 for cvt to mono scaling by 1/2 */ + x = quant_init_global_addr(); + for (i = 0; i < 256 + 2 + 4; i++) + x[i] = (float) pow(2.0, 0.25 * ((i - (2 + 4)) - 210 + GLOBAL_GAIN_SCALE)); + + + + /* x = pow(2.0, -0.5*(1+scalefact_scale)*scalefac + preemp) */ + ls = quant_init_scale_addr(); + for (scalefact_scale = 0; scalefact_scale < 2; scalefact_scale++) + { + for (preemp = 0; preemp < 4; preemp++) + { + for (scalefac = 0; scalefac < 32; scalefac++) + { + ls[scalefact_scale][preemp][scalefac] = + (float) pow(2.0, -0.5 * (1 + scalefact_scale) * (scalefac + preemp)); + } + } + } + + /*--- iSample**(4/3) lookup, -32<=i<=31 ---*/ + x = quant_init_pow_addr(); + for (i = 0; i < 64; i++) + { + tmp = i - 32; + x[i] = (float) (tmp * pow(fabs(tmp), (1.0 / 3.0))); + } + + + /*-- pow(2.0, -0.25*8.0*subblock_gain) 3 bits --*/ + x = quant_init_subblock_addr(); + for (i = 0; i < 8; i++) + { + x[i] = (float) pow(2.0, 0.25 * -8.0 * i); + } + + /*-------------------------*/ + // quant_init_sf_band(sr_index); replaced by code in sup.c + + +/*================ antialias ===============================*/ + // onceonly!!!!!!!!!!!!!!!!!!!!! + csa = alias_init_addr(); + for (i = 0; i < 8; i++) + { + csa[i][0] = (float) (1.0 / sqrt(1.0 + Ci[i] * Ci[i])); + csa[i][1] = (float) (Ci[i] / sqrt(1.0 + Ci[i] * Ci[i])); + } + } + + // these 4 are iOnceOnly-protected inside... + +/*================ msis ===============================*/ + msis_init(); + msis_init_MPEG2(); + +/*================ imdct ===============================*/ + imdct_init(); + +/*--- hybrid windows ------------*/ + hwin_init(); + + return 0; +} +/*====================================================================*/ +typedef float ARRAY36[36]; +ARRAY36 *hwin_init_addr(); + +/*--------------------------------------------------------------------*/ +void hwin_init() +{ + int i, j; + double pi; + ARRAY36 *win; + + static int iOnceOnly = 0; + + if (!iOnceOnly++) + { + win = hwin_init_addr(); + + pi = 4.0 * atan(1.0); + + /* type 0 */ + for (i = 0; i < 36; i++) + win[0][i] = (float) sin(pi / 36 * (i + 0.5)); + + /* type 1 */ + for (i = 0; i < 18; i++) + win[1][i] = (float) sin(pi / 36 * (i + 0.5)); + for (i = 18; i < 24; i++) + win[1][i] = 1.0F; + for (i = 24; i < 30; i++) + win[1][i] = (float) sin(pi / 12 * (i + 0.5 - 18)); + for (i = 30; i < 36; i++) + win[1][i] = 0.0F; + + /* type 3 */ + for (i = 0; i < 6; i++) + win[3][i] = 0.0F; + for (i = 6; i < 12; i++) + win[3][i] = (float) sin(pi / 12 * (i + 0.5 - 6)); + for (i = 12; i < 18; i++) + win[3][i] = 1.0F; + for (i = 18; i < 36; i++) + win[3][i] = (float) sin(pi / 36 * (i + 0.5)); + + /* type 2 */ + for (i = 0; i < 12; i++) + win[2][i] = (float) sin(pi / 12 * (i + 0.5)); + for (i = 12; i < 36; i++) + win[2][i] = 0.0F; + + /*--- invert signs by region to match mdct 18pt --> 36pt mapping */ + for (j = 0; j < 4; j++) + { + if (j == 2) + continue; + for (i = 9; i < 36; i++) + win[j][i] = -win[j][i]; + } + + /*-- invert signs for short blocks --*/ + for (i = 3; i < 12; i++) + win[2][i] = -win[2][i]; + } +} +/*=============================================================*/ +typedef float ARRAY4[4]; +const IMDCT_INIT_BLOCK *imdct_init_addr_18(); +const IMDCT_INIT_BLOCK *imdct_init_addr_6(); + +/*-------------------------------------------------------------*/ +void imdct_init() +{ + int k, p, n; + double t, pi; + const IMDCT_INIT_BLOCK *addr; + float *w, *w2; + float *v, *v2, *coef87; + ARRAY4 *coef; + + static int iOnceOnly = 0; + + if (!iOnceOnly++) + { + /*--- 18 point --*/ + addr = imdct_init_addr_18(); + w = addr->w; + w2 = addr->w2; + coef = addr->coef; + /*----*/ + n = 18; + pi = 4.0 * atan(1.0); + t = pi / (4 * n); + for (p = 0; p < n; p++) + w[p] = (float) (2.0 * cos(t * (2 * p + 1))); + for (p = 0; p < 9; p++) + w2[p] = (float) 2.0 *cos(2 * t * (2 * p + 1)); + + t = pi / (2 * n); + for (k = 0; k < 9; k++) + { + for (p = 0; p < 4; p++) + coef[k][p] = (float) cos(t * (2 * k) * (2 * p + 1)); + } + + /*--- 6 point */ + addr = imdct_init_addr_6(); + v = addr->w; + v2 = addr->w2; + coef87 = addr->coef; + /*----*/ + n = 6; + pi = 4.0 * atan(1.0); + t = pi / (4 * n); + for (p = 0; p < n; p++) + v[p] = (float) 2.0 *cos(t * (2 * p + 1)); + + for (p = 0; p < 3; p++) + v2[p] = (float) 2.0 *cos(2 * t * (2 * p + 1)); + + t = pi / (2 * n); + k = 1; + p = 0; + *coef87 = (float) cos(t * (2 * k) * (2 * p + 1)); + /* adjust scaling to save a few mults */ + for (p = 0; p < 6; p++) + v[p] = v[p] / 2.0f; + *coef87 = (float) 2.0 *(*coef87); + + } +} +/*===============================================================*/ +typedef float ARRAY8_2[8][2]; +ARRAY8_2 *msis_init_addr(); + +/*-------------------------------------------------------------*/ +void msis_init() +{ + int i; + double s, c; + double pi; + double t; + ARRAY8_2 *lr; + + static int iOnceOnly = 0; + + if (!iOnceOnly++) + { + lr = msis_init_addr(); + + + pi = 4.0 * atan(1.0); + t = pi / 12.0; + for (i = 0; i < 7; i++) + { + s = sin(i * t); + c = cos(i * t); + /* ms_mode = 0 */ + lr[0][i][0] = (float) (s / (s + c)); + lr[0][i][1] = (float) (c / (s + c)); + /* ms_mode = 1 */ + lr[1][i][0] = (float) (sqrt(2.0) * (s / (s + c))); + lr[1][i][1] = (float) (sqrt(2.0) * (c / (s + c))); + } + /* sf = 7 */ + /* ms_mode = 0 */ + lr[0][i][0] = 1.0f; + lr[0][i][1] = 0.0f; + /* ms_mode = 1, in is bands is routine does ms processing */ + lr[1][i][0] = 1.0f; + lr[1][i][1] = 1.0f; + + + /*------- + for(i=0;i<21;i++) nBand[0][i] = + sfBandTable[sr_index].l[i+1] - sfBandTable[sr_index].l[i]; + for(i=0;i<12;i++) nBand[1][i] = + sfBandTable[sr_index].s[i+1] - sfBandTable[sr_index].s[i]; + -------------*/ + } +} +/*-------------------------------------------------------------*/ +/*===============================================================*/ +typedef float ARRAY2_64_2[2][64][2]; +ARRAY2_64_2 *msis_init_addr_MPEG2(); + +/*-------------------------------------------------------------*/ +void msis_init_MPEG2() +{ + int k, n; + double t; + ARRAY2_64_2 *lr2; + int intensity_scale, ms_mode, sf, sflen; + float ms_factor[2]; + + static int iOnceOnly = 0; + + if (!iOnceOnly++) + { + ms_factor[0] = 1.0; + ms_factor[1] = (float) sqrt(2.0); + + lr2 = msis_init_addr_MPEG2(); + + /* intensity stereo MPEG2 */ + /* lr2[intensity_scale][ms_mode][sflen_offset+sf][left/right] */ + + for (intensity_scale = 0; intensity_scale < 2; intensity_scale++) + { + t = pow(2.0, -0.25 * (1 + intensity_scale)); + for (ms_mode = 0; ms_mode < 2; ms_mode++) + { + + n = 1; + k = 0; + for (sflen = 0; sflen < 6; sflen++) + { + for (sf = 0; sf < (n - 1); sf++, k++) + { + if (sf == 0) + { + lr2[intensity_scale][ms_mode][k][0] = ms_factor[ms_mode] * 1.0f; + lr2[intensity_scale][ms_mode][k][1] = ms_factor[ms_mode] * 1.0f; + } + else if ((sf & 1)) + { + lr2[intensity_scale][ms_mode][k][0] = + (float) (ms_factor[ms_mode] * pow(t, (sf + 1) / 2)); + lr2[intensity_scale][ms_mode][k][1] = ms_factor[ms_mode] * 1.0f; + } + else + { + lr2[intensity_scale][ms_mode][k][0] = ms_factor[ms_mode] * 1.0f; + lr2[intensity_scale][ms_mode][k][1] = + (float) (ms_factor[ms_mode] * pow(t, sf / 2)); + } + } + + /* illegal is_pos used to do ms processing */ + if (ms_mode == 0) + { /* ms_mode = 0 */ + lr2[intensity_scale][ms_mode][k][0] = 1.0f; + lr2[intensity_scale][ms_mode][k][1] = 0.0f; + } + else + { + /* ms_mode = 1, in is bands is routine does ms processing */ + lr2[intensity_scale][ms_mode][k][0] = 1.0f; + lr2[intensity_scale][ms_mode][k][1] = 1.0f; + } + k++; + n = n + n; + } + } + } + } + +} +/*-------------------------------------------------------------*/ diff --git a/code/mp3code/mdct.c b/code/mp3code/mdct.c new file mode 100644 index 0000000..0f1f6c9 --- /dev/null +++ b/code/mp3code/mdct.c @@ -0,0 +1,229 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: mdct.c,v 1.4 1999/10/19 07:13:09 elrod Exp $ +____________________________________________________________________________*/ + +/**** mdct.c *************************************************** + +Layer III + + cos transform for n=18, n=6 + +computes c[k] = Sum( cos((pi/4*n)*(2*k+1)*(2*p+1))*f[p] ) + k = 0, ...n-1, p = 0...n-1 + + +inplace ok. + +******************************************************************/ + +#include +#include +#include +#include + + +/*------ 18 point xform -------*/ +float mdct18w[18]; // effectively constant +float mdct18w2[9]; // " " +float coef[9][4]; // " " + +float mdct6_3v[6]; // " " +float mdct6_3v2[3]; // " " +float coef87; // " " + +typedef struct +{ + float *w; + float *w2; + void *coef; +} +IMDCT_INIT_BLOCK; + +static const IMDCT_INIT_BLOCK imdct_info_18 = +{mdct18w, mdct18w2, coef}; +static const IMDCT_INIT_BLOCK imdct_info_6 = +{mdct6_3v, mdct6_3v2, &coef87}; + + + +/*====================================================================*/ +const IMDCT_INIT_BLOCK *imdct_init_addr_18() +{ + return &imdct_info_18; +} +const IMDCT_INIT_BLOCK *imdct_init_addr_6() +{ + return &imdct_info_6; +} +/*--------------------------------------------------------------------*/ +void imdct18(float f[18]) /* 18 point */ +{ + int p; + float a[9], b[9]; + float ap, bp, a8p, b8p; + float g1, g2; + + + for (p = 0; p < 4; p++) + { + g1 = mdct18w[p] * f[p]; + g2 = mdct18w[17 - p] * f[17 - p]; + ap = g1 + g2; // a[p] + + bp = mdct18w2[p] * (g1 - g2); // b[p] + + g1 = mdct18w[8 - p] * f[8 - p]; + g2 = mdct18w[9 + p] * f[9 + p]; + a8p = g1 + g2; // a[8-p] + + b8p = mdct18w2[8 - p] * (g1 - g2); // b[8-p] + + a[p] = ap + a8p; + a[5 + p] = ap - a8p; + b[p] = bp + b8p; + b[5 + p] = bp - b8p; + } + g1 = mdct18w[p] * f[p]; + g2 = mdct18w[17 - p] * f[17 - p]; + a[p] = g1 + g2; + b[p] = mdct18w2[p] * (g1 - g2); + + + f[0] = 0.5f * (a[0] + a[1] + a[2] + a[3] + a[4]); + f[1] = 0.5f * (b[0] + b[1] + b[2] + b[3] + b[4]); + + f[2] = coef[1][0] * a[5] + coef[1][1] * a[6] + coef[1][2] * a[7] + + coef[1][3] * a[8]; + f[3] = coef[1][0] * b[5] + coef[1][1] * b[6] + coef[1][2] * b[7] + + coef[1][3] * b[8] - f[1]; + f[1] = f[1] - f[0]; + f[2] = f[2] - f[1]; + + f[4] = coef[2][0] * a[0] + coef[2][1] * a[1] + coef[2][2] * a[2] + + coef[2][3] * a[3] - a[4]; + f[5] = coef[2][0] * b[0] + coef[2][1] * b[1] + coef[2][2] * b[2] + + coef[2][3] * b[3] - b[4] - f[3]; + f[3] = f[3] - f[2]; + f[4] = f[4] - f[3]; + + f[6] = coef[3][0] * (a[5] - a[7] - a[8]); + f[7] = coef[3][0] * (b[5] - b[7] - b[8]) - f[5]; + f[5] = f[5] - f[4]; + f[6] = f[6] - f[5]; + + f[8] = coef[4][0] * a[0] + coef[4][1] * a[1] + coef[4][2] * a[2] + + coef[4][3] * a[3] + a[4]; + f[9] = coef[4][0] * b[0] + coef[4][1] * b[1] + coef[4][2] * b[2] + + coef[4][3] * b[3] + b[4] - f[7]; + f[7] = f[7] - f[6]; + f[8] = f[8] - f[7]; + + f[10] = coef[5][0] * a[5] + coef[5][1] * a[6] + coef[5][2] * a[7] + + coef[5][3] * a[8]; + f[11] = coef[5][0] * b[5] + coef[5][1] * b[6] + coef[5][2] * b[7] + + coef[5][3] * b[8] - f[9]; + f[9] = f[9] - f[8]; + f[10] = f[10] - f[9]; + + f[12] = 0.5f * (a[0] + a[2] + a[3]) - a[1] - a[4]; + f[13] = 0.5f * (b[0] + b[2] + b[3]) - b[1] - b[4] - f[11]; + f[11] = f[11] - f[10]; + f[12] = f[12] - f[11]; + + f[14] = coef[7][0] * a[5] + coef[7][1] * a[6] + coef[7][2] * a[7] + + coef[7][3] * a[8]; + f[15] = coef[7][0] * b[5] + coef[7][1] * b[6] + coef[7][2] * b[7] + + coef[7][3] * b[8] - f[13]; + f[13] = f[13] - f[12]; + f[14] = f[14] - f[13]; + + f[16] = coef[8][0] * a[0] + coef[8][1] * a[1] + coef[8][2] * a[2] + + coef[8][3] * a[3] + a[4]; + f[17] = coef[8][0] * b[0] + coef[8][1] * b[1] + coef[8][2] * b[2] + + coef[8][3] * b[3] + b[4] - f[15]; + f[15] = f[15] - f[14]; + f[16] = f[16] - f[15]; + f[17] = f[17] - f[16]; + + + return; +} +/*--------------------------------------------------------------------*/ +/* does 3, 6 pt dct. changes order from f[i][window] c[window][i] */ +void imdct6_3(float f[]) /* 6 point */ +{ + int w; + float buf[18]; + float *a, *c; // b[i] = a[3+i] + + float g1, g2; + float a02, b02; + + c = f; + a = buf; + for (w = 0; w < 3; w++) + { + g1 = mdct6_3v[0] * f[3 * 0]; + g2 = mdct6_3v[5] * f[3 * 5]; + a[0] = g1 + g2; + a[3 + 0] = mdct6_3v2[0] * (g1 - g2); + + g1 = mdct6_3v[1] * f[3 * 1]; + g2 = mdct6_3v[4] * f[3 * 4]; + a[1] = g1 + g2; + a[3 + 1] = mdct6_3v2[1] * (g1 - g2); + + g1 = mdct6_3v[2] * f[3 * 2]; + g2 = mdct6_3v[3] * f[3 * 3]; + a[2] = g1 + g2; + a[3 + 2] = mdct6_3v2[2] * (g1 - g2); + + a += 6; + f++; + } + + a = buf; + for (w = 0; w < 3; w++) + { + a02 = (a[0] + a[2]); + b02 = (a[3 + 0] + a[3 + 2]); + c[0] = a02 + a[1]; + c[1] = b02 + a[3 + 1]; + c[2] = coef87 * (a[0] - a[2]); + c[3] = coef87 * (a[3 + 0] - a[3 + 2]) - c[1]; + c[1] = c[1] - c[0]; + c[2] = c[2] - c[1]; + c[4] = a02 - a[1] - a[1]; + c[5] = b02 - a[3 + 1] - a[3 + 1] - c[3]; + c[3] = c[3] - c[2]; + c[4] = c[4] - c[3]; + c[5] = c[5] - c[4]; + a += 6; + c += 6; + } + + return; +} +/*--------------------------------------------------------------------*/ diff --git a/code/mp3code/mhead.c b/code/mp3code/mhead.c new file mode 100644 index 0000000..f4c0961 --- /dev/null +++ b/code/mp3code/mhead.c @@ -0,0 +1,328 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: mhead.c,v 1.7 1999/10/19 07:13:09 elrod Exp $ +____________________________________________________________________________*/ + +/*------------ mhead.c ---------------------------------------------- + mpeg audio + extract info from mpeg header + portable version (adapted from c:\eco\mhead.c + + add Layer III + + mods 6/18/97 re mux restart, 32 bit ints + + mod 5/7/98 parse mpeg 2.5 + +---------------------------------------------------------------------*/ +#include +#include +#include +#include +#include "mhead.h" /* mpeg header structure */ + +static const int mp_br_table[2][16] = +{{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0}}; +static const int mp_sr20_table[2][4] = +{{441, 480, 320, -999}, {882, 960, 640, -999}}; + +static const int mp_br_tableL1[2][16] = +{{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0},/* mpeg2 */ + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0}}; + +static const int mp_br_tableL3[2][16] = +{{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, /* mpeg 2 */ + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}}; + + + +static int find_sync(unsigned char *buf, int n); +static int sync_scan(unsigned char *buf, int n, int i0); +static int sync_test(unsigned char *buf, int n, int isync, int padbytes); + + +/*--------------------------------------------------------------*/ +int head_info(unsigned char *buf, unsigned int n, MPEG_HEAD * h) +{ + int framebytes; + int mpeg25_flag; + + if (n > 10000) + n = 10000; /* limit scan for free format */ + + + + h->sync = 0; + //if ((buf[0] == 0xFF) && ((buf[1] & 0xF0) == 0xF0)) + if ((buf[0] == 0xFF) && ((buf[0+1] & 0xF0) == 0xF0)) + { + mpeg25_flag = 0; // mpeg 1 & 2 + + } + else if ((buf[0] == 0xFF) && ((buf[0+1] & 0xF0) == 0xE0)) + { + mpeg25_flag = 1; // mpeg 2.5 + + } + else + return 0; // sync fail + + h->sync = 1; + if (mpeg25_flag) + h->sync = 2; //low bit clear signals mpeg25 (as in 0xFFE) + + h->id = (buf[0+1] & 0x08) >> 3; + h->option = (buf[0+1] & 0x06) >> 1; + h->prot = (buf[0+1] & 0x01); + + h->br_index = (buf[0+2] & 0xf0) >> 4; + h->sr_index = (buf[0+2] & 0x0c) >> 2; + h->pad = (buf[0+2] & 0x02) >> 1; + h->private_bit = (buf[0+2] & 0x01); + h->mode = (buf[0+3] & 0xc0) >> 6; + h->mode_ext = (buf[0+3] & 0x30) >> 4; + h->cr = (buf[0+3] & 0x08) >> 3; + h->original = (buf[0+3] & 0x04) >> 2; + h->emphasis = (buf[0+3] & 0x03); + + +// if( mpeg25_flag ) { + // if( h->sr_index == 2 ) return 0; // fail 8khz + //} + + +/* compute framebytes for Layer I, II, III */ + if (h->option < 1) + return 0; + if (h->option > 3) + return 0; + + framebytes = 0; + + if (h->br_index > 0) + { + if (h->option == 3) + { /* layer I */ + framebytes = + 240 * mp_br_tableL1[h->id][h->br_index] + / mp_sr20_table[h->id][h->sr_index]; + framebytes = 4 * framebytes; + } + else if (h->option == 2) + { /* layer II */ + framebytes = + 2880 * mp_br_table[h->id][h->br_index] + / mp_sr20_table[h->id][h->sr_index]; + } + else if (h->option == 1) + { /* layer III */ + if (h->id) + { // mpeg1 + + framebytes = + 2880 * mp_br_tableL3[h->id][h->br_index] + / mp_sr20_table[h->id][h->sr_index]; + } + else + { // mpeg2 + + if (mpeg25_flag) + { // mpeg2.2 + + framebytes = + 2880 * mp_br_tableL3[h->id][h->br_index] + / mp_sr20_table[h->id][h->sr_index]; + } + else + { + framebytes = + 1440 * mp_br_tableL3[h->id][h->br_index] + / mp_sr20_table[h->id][h->sr_index]; + } + } + } + } + else + framebytes = find_sync(buf, n); /* free format */ + + return framebytes; +} + +int head_info3(unsigned char *buf, unsigned int n, MPEG_HEAD *h, int *br, unsigned int *searchForward) { + unsigned int pBuf = 0; + + // jdw insertion... + while ((pBuf < n) && !((buf[pBuf] == 0xFF) && + ((buf[pBuf+1] & 0xF0) == 0xF0 || (buf[pBuf+1] & 0xF0) == 0xE0))) + { + pBuf++; + } + + if (pBuf == n) return 0; + + *searchForward = pBuf; + return head_info2(&(buf[pBuf]),n,h,br); +} + +/*--------------------------------------------------------------*/ +int head_info2(unsigned char *buf, unsigned int n, MPEG_HEAD * h, int *br) +{ + int framebytes; + + /*--- return br (in bits/sec) in addition to frame bytes ---*/ + + *br = 0; + /*-- assume fail --*/ + framebytes = head_info(buf, n, h); + + if (framebytes == 0) + return 0; + + switch (h->option) + { + case 1: /* layer III */ + { + if (h->br_index > 0) + *br = 1000 * mp_br_tableL3[h->id][h->br_index]; + else + { + if (h->id) // mpeg1 + + *br = 1000 * framebytes * mp_sr20_table[h->id][h->sr_index] / (144 * 20); + else + { // mpeg2 + + if ((h->sync & 1) == 0) // flags mpeg25 + + *br = 500 * framebytes * mp_sr20_table[h->id][h->sr_index] / (72 * 20); + else + *br = 1000 * framebytes * mp_sr20_table[h->id][h->sr_index] / (72 * 20); + } + } + } + break; + + case 2: /* layer II */ + { + if (h->br_index > 0) + *br = 1000 * mp_br_table[h->id][h->br_index]; + else + *br = 1000 * framebytes * mp_sr20_table[h->id][h->sr_index] / (144 * 20); + } + break; + + case 3: /* layer I */ + { + if (h->br_index > 0) + *br = 1000 * mp_br_tableL1[h->id][h->br_index]; + else + *br = 1000 * framebytes * mp_sr20_table[h->id][h->sr_index] / (48 * 20); + } + break; + + default: + + return 0; // fuck knows what this is, but it ain't one of ours... + } + + + return framebytes; +} +/*--------------------------------------------------------------*/ +static int compare(unsigned char *buf, unsigned char *buf2) +{ + if (buf[0] != buf2[0]) + return 0; + if (buf[1] != buf2[1]) + return 0; + return 1; +} +/*----------------------------------------------------------*/ +/*-- does not scan for initial sync, initial sync assumed --*/ +static int find_sync(unsigned char *buf, int n) +{ + int i0, isync, nmatch, pad; + int padbytes, option; + +/* mod 4/12/95 i0 change from 72, allows as low as 8kbits for mpeg1 */ + i0 = 24; + padbytes = 1; + option = (buf[1] & 0x06) >> 1; + if (option == 3) + { + padbytes = 4; + i0 = 24; /* for shorter layer I frames */ + } + + pad = (buf[2] & 0x02) >> 1; + + n -= 3; /* need 3 bytes of header */ + + while (i0 < 2000) + { + isync = sync_scan(buf, n, i0); + i0 = isync + 1; + isync -= pad; + if (isync <= 0) + return 0; + nmatch = sync_test(buf, n, isync, padbytes); + if (nmatch > 0) + return isync; + } + + return 0; +} +/*------------------------------------------------------*/ +/*---- scan for next sync, assume start is valid -------*/ +/*---- return number bytes to next sync ----------------*/ +static int sync_scan(unsigned char *buf, int n, int i0) +{ + int i; + + for (i = i0; i < n; i++) + if (compare(buf, buf + i)) + return i; + + return 0; +} +/*------------------------------------------------------*/ +/*- test consecutative syncs, input isync without pad --*/ +static int sync_test(unsigned char *buf, int n, int isync, int padbytes) +{ + int i, nmatch, pad; + + nmatch = 0; + for (i = 0;;) + { + pad = padbytes * ((buf[i + 2] & 0x02) >> 1); + i += (pad + isync); + if (i > n) + break; + if (!compare(buf, buf + i)) + return -nmatch; + nmatch++; + } + return nmatch; +} diff --git a/code/mp3code/mhead.h b/code/mp3code/mhead.h new file mode 100644 index 0000000..944bfd8 --- /dev/null +++ b/code/mp3code/mhead.h @@ -0,0 +1,102 @@ +#ifndef MHEAD_H +#define MHEAD_H + + +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: mhead.h,v 1.3 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/* portable copy of eco\mhead.h */ +/* mpeg audio header */ +typedef struct +{ + int sync; /* 1 if valid sync */ + int id; + int option; + int prot; + int br_index; + int sr_index; + int pad; + int private_bit; + int mode; + int mode_ext; + int cr; + int original; + int emphasis; +} +MPEG_HEAD; + +/* portable mpeg audio decoder, decoder functions */ + +#ifndef IN_OUT +#include "small_header.h" +#endif + +typedef struct +{ + int channels; + int outvalues; + long samprate; + int bits; + int framebytes; + int type; +} +DEC_INFO; + + +#ifdef __cplusplus +extern "C" +{ +#endif + + int head_info(unsigned char *buf, unsigned int n, MPEG_HEAD * h); + int head_info2(unsigned char *buf, + unsigned int n, MPEG_HEAD * h, int *br); + int head_info3(unsigned char *buf, unsigned int n, MPEG_HEAD *h, int*br, unsigned int *searchForward); +/* head_info returns framebytes > 0 for success */ +/* audio_decode_init returns 1 for success, 0 for fail */ +/* audio_decode returns in_bytes = 0 on sync loss */ + + int audio_decode_init(MPEG_HEAD * h, int framebytes_arg, + int reduction_code, int transform_code, int convert_code, + int freq_limit); + void audio_decode_info(DEC_INFO * info); + IN_OUT audio_decode(unsigned char *bs, short *pcm, unsigned char *pNextByteAfterData); + + int audio_decode8_init(MPEG_HEAD * h, int framebytes_arg, + int reduction_code, int transform_code, int convert_code, + int freq_limit); + void audio_decode8_info(DEC_INFO * info); + IN_OUT audio_decode8(unsigned char *bs, short *pcmbuf); + + +#ifdef __cplusplus +} +#endif + +#pragma warning(disable:4711) // function 'xxxx' selected for automatic inline expansion + +#endif // #ifndef MHEAD_H + diff --git a/code/mp3code/mp3struct.h b/code/mp3code/mp3struct.h new file mode 100644 index 0000000..33fca50 --- /dev/null +++ b/code/mp3code/mp3struct.h @@ -0,0 +1,141 @@ +// Filename: mp3struct.h +// +// this file is my struct to gather all loose MP3 global vars into one struct so we can do multiple-stream decompression +// + +#ifndef MP3STRUCT_H +#define MP3STRUCT_H + +#pragma warning (disable : 4201 ) // nonstandard extension used : nameless struct/union + +#include "small_header.h" // for SAMPLE and IN_OUT + +typedef void (*SBT_FUNCTION) (float *sample, short *pcm, int n); +typedef void (*XFORM_FUNCTION) (void *pcm, int igr); +typedef IN_OUT(*DECODE_FUNCTION) (unsigned char *bs, unsigned char *pcm); + +typedef struct +{ + union + { + struct + { + SBT_FUNCTION sbt; + + float cs_factor[3][64]; // 768 bytes + + int nbat[4]; + int bat[4][16]; + int max_sb; + int stereo_sb; + int bit_skip; + + float* cs_factorL1; + int nbatL1; + + };//L1_2; + + struct + { + SBT_FUNCTION sbt_L3; + XFORM_FUNCTION Xform; + DECODE_FUNCTION decode_function; + + SAMPLE sample[2][2][576]; // if this isn't kept per stream then the decode breaks up + + // the 4k version of these 2 seems to work for everything, but I'm reverting to original 8k for safety jic. + // + #define NBUF (8*1024) + #define BUF_TRIGGER (NBUF-1500) +// #define NBUF (4096) // 2048 works for all except 133+ kbps VBR files, 4096 copes with these +// #define BUF_TRIGGER ((NBUF/4)*3) + + unsigned char buf[NBUF]; + int buf_ptr0; + int buf_ptr1; + int main_pos_bit; + + + int band_limit_nsb; + int nBand[2][22]; /* [long/short][cb] */ + int sfBandIndex[2][22]; /* [long/short][cb] */ + int half_outbytes; + int crcbytes; + int nchan; + int ms_mode; + int is_mode; + unsigned int zero_level_pcm; + int mpeg25_flag; + int band_limit; + int band_limit21; + int band_limit12; + int gain_adjust; + int ncbl_mixed; + };//L3; + }; + // from csbt.c... + // + // if this isn't kept per stream then the decode breaks up + signed int vb_ptr; // + signed int vb2_ptr; // + float vbuf[512]; // + float vbuf2[512]; // this can be lost if we stick to mono samples + + // L3 only... + // + int sr_index; // L3 only (99%) + int id; + + // any type... + // + int outvalues; + int outbytes; + int framebytes; + int pad; + int nsb_limit; + + // stuff added now that the game uses streaming MP3s... + // + byte *pbSourceData; // a useful dup ptr only, this whole struct will be owned by an sfx_t struct that has the actual data ptr field + int iSourceBytesRemaining; + int iSourceReadIndex; + int iSourceFrameBytes; +#ifdef _DEBUG + int iSourceFrameCounter; // not really important +#endif + int iBytesDecodedTotal; + int iBytesDecodedThisPacket;// not sure how useful this will be, it's only per-frame, so will always be full frame size (eg 2304 or below for mono) except possibly for the last frame? + + int iRewind_FinalReductionCode; + int iRewind_FinalConvertCode; + int iRewind_SourceBytesRemaining; + int iRewind_SourceReadIndex; + byte bDecodeBuffer[2304*2]; // *2 to allow for stereo now + int iCopyOffset; // used for painting to DMA-feeder, since 2304 won't match the size it wants + + // some new stuff added for dynamic music, to allow "how many seconds left to play" queries... + // + // ( m_lengthInSeconds = ((iUnpackedDataLength / iRate) / iChannels) / iWidth; ) + // + // Note that these fields are only valid/initialised if MP3Stream_InitPlayingTimeFields() was called. + // If not, this->iTimeQuery_UnpackedLength will be zero. + // + int iTimeQuery_UnpackedLength; + int iTimeQuery_SampleRate; + int iTimeQuery_Channels; + int iTimeQuery_Width; + +} MP3STREAM, *LP_MP3STREAM; + +#define MP3STUFF_KNOWN + +extern LP_MP3STREAM pMP3Stream; +extern int bFastEstimateOnly; + +#pragma warning (default : 4201 ) // nonstandard extension used : nameless struct/union +#pragma warning (disable : 4711 ) // function 'xxxx' selected for automatic inline expansion + +#endif // #ifndef MP3STRUCT_H + +////////////////// eof ///////////////////// + diff --git a/code/mp3code/msis.c b/code/mp3code/msis.c new file mode 100644 index 0000000..733b209 --- /dev/null +++ b/code/mp3code/msis.c @@ -0,0 +1,296 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: msis.c,v 1.4 1999/10/19 07:13:09 elrod Exp $ +____________________________________________________________________________*/ + +/**** msis.c *************************************************** + Layer III + antialias, ms and is stereo precessing + +**** is_process assumes never switch + from short to long in is region ***** + +is_process does ms or stereo in "forbidded sf regions" + //ms_mode = 0 + lr[0][i][0] = 1.0f; + lr[0][i][1] = 0.0f; + // ms_mode = 1, in is bands is routine does ms processing + lr[1][i][0] = 1.0f; + lr[1][i][1] = 1.0f; + +******************************************************************/ + +#include +#include +#include +#include +#include "L3.h" + +#include "mp3struct.h" + +typedef float ARRAY2[2]; +typedef float ARRAY8_2[8][2]; + +float csa[8][2]; /* antialias */ // effectively constant + +/* pMP3Stream->nBand[0] = long, pMP3Stream->nBand[1] = short */ +////@@@@extern int pMP3Stream->nBand[2][22]; +////@@@@extern int pMP3Stream->sfBandIndex[2][22]; /* [long/short][cb] */ + +/* intensity stereo */ +/* if ms mode quant pre-scales all values by 1.0/sqrt(2.0) ms_mode in table + compensates */ +static float lr[2][8][2]; /* [ms_mode 0/1][sf][left/right] */ // effectively constant + + +/* intensity stereo MPEG2 */ +/* lr2[intensity_scale][ms_mode][sflen_offset+sf][left/right] */ +typedef float ARRAY2_64_2[2][64][2]; +typedef float ARRAY64_2[64][2]; +static float lr2[2][2][64][2]; // effectively constant + + +/*===============================================================*/ +ARRAY2 *alias_init_addr() +{ + return csa; +} +/*-----------------------------------------------------------*/ +ARRAY8_2 *msis_init_addr() +{ +/*------- +pi = 4.0*atan(1.0); +t = pi/12.0; +for(i=0;i<7;i++) { + s = sin(i*t); + c = cos(i*t); + // ms_mode = 0 + lr[0][i][0] = (float)(s/(s+c)); + lr[0][i][1] = (float)(c/(s+c)); + // ms_mode = 1 + lr[1][i][0] = (float)(sqrt(2.0)*(s/(s+c))); + lr[1][i][1] = (float)(sqrt(2.0)*(c/(s+c))); +} +//sf = 7 +//ms_mode = 0 +lr[0][i][0] = 1.0f; +lr[0][i][1] = 0.0f; +// ms_mode = 1, in is bands is routine does ms processing +lr[1][i][0] = 1.0f; +lr[1][i][1] = 1.0f; +------------*/ + + return lr; +} +/*-------------------------------------------------------------*/ +ARRAY2_64_2 *msis_init_addr_MPEG2() +{ + return lr2; +} +/*===============================================================*/ +void antialias(float x[], int n) +{ + int i, k; + float a, b; + + for (k = 0; k < n; k++) + { + for (i = 0; i < 8; i++) + { + a = x[17 - i]; + b = x[18 + i]; + x[17 - i] = a * csa[i][0] - b * csa[i][1]; + x[18 + i] = b * csa[i][0] + a * csa[i][1]; + } + x += 18; + } +} +/*===============================================================*/ +void ms_process(float x[][1152], int n) /* sum-difference stereo */ +{ + int i; + float xl, xr; + +/*-- note: sqrt(2) done scaling by dequant ---*/ + for (i = 0; i < n; i++) + { + xl = x[0][i] + x[1][i]; + xr = x[0][i] - x[1][i]; + x[0][i] = xl; + x[1][i] = xr; + } + return; +} +/*===============================================================*/ +void is_process_MPEG1(float x[][1152], /* intensity stereo */ + SCALEFACT * sf, + CB_INFO cb_info[2], /* [ch] */ + int nsamp, int ms_mode) +{ + int i, j, n, cb, w; + float fl, fr; + int m; + int isf; + float fls[3], frs[3]; + int cb0; + + + cb0 = cb_info[1].cbmax; /* start at end of right */ + i = pMP3Stream->sfBandIndex[cb_info[1].cbtype][cb0]; + cb0++; + m = nsamp - i; /* process to len of left */ + + if (cb_info[1].cbtype) + goto short_blocks; +/*------------------------*/ +/* long_blocks: */ + for (cb = cb0; cb < 21; cb++) + { + isf = sf->l[cb]; + n = pMP3Stream->nBand[0][cb]; + fl = lr[ms_mode][isf][0]; + fr = lr[ms_mode][isf][1]; + for (j = 0; j < n; j++, i++) + { + if (--m < 0) + goto exit; + x[1][i] = fr * x[0][i]; + x[0][i] = fl * x[0][i]; + } + } + return; +/*------------------------*/ + short_blocks: + for (cb = cb0; cb < 12; cb++) + { + for (w = 0; w < 3; w++) + { + isf = sf->s[w][cb]; + fls[w] = lr[ms_mode][isf][0]; + frs[w] = lr[ms_mode][isf][1]; + } + n = pMP3Stream->nBand[1][cb]; + for (j = 0; j < n; j++) + { + m -= 3; + if (m < 0) + goto exit; + x[1][i] = frs[0] * x[0][i]; + x[0][i] = fls[0] * x[0][i]; + x[1][1 + i] = frs[1] * x[0][1 + i]; + x[0][1 + i] = fls[1] * x[0][1 + i]; + x[1][2 + i] = frs[2] * x[0][2 + i]; + x[0][2 + i] = fls[2] * x[0][2 + i]; + i += 3; + } + } + + exit: + return; +} +/*===============================================================*/ +void is_process_MPEG2(float x[][1152], /* intensity stereo */ + SCALEFACT * sf, + CB_INFO cb_info[2], /* [ch] */ + IS_SF_INFO * is_sf_info, + int nsamp, int ms_mode) +{ + int i, j, k, n, cb, w; + float fl, fr; + int m; + int isf; + int il[21]; + int tmp; + int r; + ARRAY2 *lr; + int cb0, cb1; + + lr = lr2[is_sf_info->intensity_scale][ms_mode]; + + if (cb_info[1].cbtype) + goto short_blocks; + +/*------------------------*/ +/* long_blocks: */ + cb0 = cb_info[1].cbmax; /* start at end of right */ + i = pMP3Stream->sfBandIndex[0][cb0]; + m = nsamp - i; /* process to len of left */ +/* gen sf info */ + for (k = r = 0; r < 3; r++) + { + tmp = (1 << is_sf_info->slen[r]) - 1; + for (j = 0; j < is_sf_info->nr[r]; j++, k++) + il[k] = tmp; + } + for (cb = cb0 + 1; cb < 21; cb++) + { + isf = il[cb] + sf->l[cb]; + fl = lr[isf][0]; + fr = lr[isf][1]; + n = pMP3Stream->nBand[0][cb]; + for (j = 0; j < n; j++, i++) + { + if (--m < 0) + goto exit; + x[1][i] = fr * x[0][i]; + x[0][i] = fl * x[0][i]; + } + } + return; +/*------------------------*/ + short_blocks: + + for (k = r = 0; r < 3; r++) + { + tmp = (1 << is_sf_info->slen[r]) - 1; + for (j = 0; j < is_sf_info->nr[r]; j++, k++) + il[k] = tmp; + } + + for (w = 0; w < 3; w++) + { + cb0 = cb_info[1].cbmax_s[w]; /* start at end of right */ + i = pMP3Stream->sfBandIndex[1][cb0] + w; + cb1 = cb_info[0].cbmax_s[w]; /* process to end of left */ + + for (cb = cb0 + 1; cb <= cb1; cb++) + { + isf = il[cb] + sf->s[w][cb]; + fl = lr[isf][0]; + fr = lr[isf][1]; + n = pMP3Stream->nBand[1][cb]; + for (j = 0; j < n; j++) + { + x[1][i] = fr * x[0][i]; + x[0][i] = fl * x[0][i]; + i += 3; + } + } + + } + + exit: + return; +} +/*===============================================================*/ diff --git a/code/mp3code/port.h b/code/mp3code/port.h new file mode 100644 index 0000000..b233d0d --- /dev/null +++ b/code/mp3code/port.h @@ -0,0 +1,80 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: port.h,v 1.2 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + + +/*--- no kb function unless DOS ---*/ + +#ifndef KB_OK +#ifdef __MSDOS__ +#define KB_OK +#endif +#ifdef _CONSOLE +#define KB_OK +#endif +#endif + +/*-- no pcm conversion to wave required + if short = 16 bits and little endian ---*/ + +/* mods 1/9/97 LITTLE_SHORT16 detect */ + +#ifndef LITTLE_SHORT16 + #ifdef __MSDOS__ + #undef LITTLE_SHORT16 + #define LITTLE_SHORT16 + #endif + #ifdef WIN32 + #undef LITTLE_SHORT16 + #define LITTLE_SHORT16 + #endif + #ifdef _M_IX86 + #undef LITTLE_SHORT16 + #define LITTLE_SHORT16 + #endif +#endif + + +// JDW // +//#ifdef LITTLE_SHORT16 +//#define cvt_to_wave_init(a) +//#define cvt_to_wave(a, b) b +//#else +//void cvt_to_wave_init(int bits); +//unsigned int cvt_to_wave(void *a, unsigned int b); +// +//#endif +#ifdef LITTLE_SHORT16 +#define cvt_to_wave_init(a) +#define cvt_to_wave(a, b) b +#else +void cvt_to_wave_init(int); +unsigned int cvt_to_wave(unsigned char *,unsigned int); +#endif + diff --git a/code/mp3code/small_header.h b/code/mp3code/small_header.h new file mode 100644 index 0000000..044942c --- /dev/null +++ b/code/mp3code/small_header.h @@ -0,0 +1,34 @@ +// Filename:- small_header.h +// +// This file is just used so I can isolate a few small structs from various horrible MP3 header files without +// externalising code protos etc. This can now be included by both main game sound code (through sfx_t) and MP3 C code. +// + +#ifndef SMALL_HEADER_H +#define SMALL_HEADER_H + + +typedef union +{ + int s; + float x; +} +SAMPLE; + +typedef struct +{ + int in_bytes; + int out_bytes; +} +IN_OUT; + +#ifdef WIN32 // Damn linux gcc isn't detecting byte as defined +#ifndef byte +typedef unsigned char byte; +#endif +#endif + +#endif // #ifndef SMALL_HEADER_H + +/////////////// eof //////////// + diff --git a/code/mp3code/tableawd.h b/code/mp3code/tableawd.h new file mode 100644 index 0000000..ff7f627 --- /dev/null +++ b/code/mp3code/tableawd.h @@ -0,0 +1,93 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: tableawd.h,v 1.2 1999/10/19 07:13:08 elrod Exp $ +____________________________________________________________________________*/ + +/* decoder analysis window gen by dinit.c (asm version table gen) */ +0.000000000f, 0.000442505f, -0.003250122f, 0.007003784f, +-0.031082151f, 0.078628540f, -0.100311279f, 0.572036743f, +-1.144989014f, -0.572036743f, -0.100311279f, -0.078628540f, +-0.031082151f, -0.007003784f, -0.003250122f, -0.000442505f, +0.000015259f, 0.000473022f, -0.003326416f, 0.007919312f, +-0.030517576f, 0.084182739f, -0.090927124f, 0.600219727f, +-1.144287109f, -0.543823242f, -0.108856201f, -0.073059082f, +-0.031478882f, -0.006118774f, -0.003173828f, -0.000396729f, +0.000015259f, 0.000534058f, -0.003387451f, 0.008865356f, +-0.029785154f, 0.089706421f, -0.080688477f, 0.628295898f, +-1.142211914f, -0.515609741f, -0.116577141f, -0.067520142f, +-0.031738281f, -0.005294800f, -0.003082275f, -0.000366211f, +0.000015259f, 0.000579834f, -0.003433228f, 0.009841919f, +-0.028884888f, 0.095169067f, -0.069595337f, 0.656219482f, +-1.138763428f, -0.487472534f, -0.123474121f, -0.061996460f, +-0.031845093f, -0.004486084f, -0.002990723f, -0.000320435f, +0.000015259f, 0.000625610f, -0.003463745f, 0.010848999f, +-0.027801514f, 0.100540161f, -0.057617184f, 0.683914185f, +-1.133926392f, -0.459472656f, -0.129577637f, -0.056533810f, +-0.031814575f, -0.003723145f, -0.002899170f, -0.000289917f, +0.000015259f, 0.000686646f, -0.003479004f, 0.011886597f, +-0.026535034f, 0.105819702f, -0.044784546f, 0.711318970f, +-1.127746582f, -0.431655884f, -0.134887695f, -0.051132202f, +-0.031661987f, -0.003005981f, -0.002792358f, -0.000259399f, +0.000015259f, 0.000747681f, -0.003479004f, 0.012939452f, +-0.025085449f, 0.110946655f, -0.031082151f, 0.738372803f, +-1.120223999f, -0.404083252f, -0.139450073f, -0.045837402f, +-0.031387329f, -0.002334595f, -0.002685547f, -0.000244141f, +0.000030518f, 0.000808716f, -0.003463745f, 0.014022826f, +-0.023422241f, 0.115921021f, -0.016510010f, 0.765029907f, +-1.111373901f, -0.376800537f, -0.143264771f, -0.040634155f, +-0.031005858f, -0.001693726f, -0.002578735f, -0.000213623f, +0.000030518f, 0.000885010f, -0.003417969f, 0.015121460f, +-0.021575928f, 0.120697014f, -0.001068115f, 0.791213989f, +-1.101211548f, -0.349868774f, -0.146362305f, -0.035552979f, +-0.030532837f, -0.001098633f, -0.002456665f, -0.000198364f, +0.000030518f, 0.000961304f, -0.003372192f, 0.016235352f, +-0.019531250f, 0.125259399f, 0.015228271f, 0.816864014f, +-1.089782715f, -0.323318481f, -0.148773193f, -0.030609131f, +-0.029937742f, -0.000549316f, -0.002349854f, -0.000167847f, +0.000030518f, 0.001037598f, -0.003280640f, 0.017349243f, +-0.017257690f, 0.129562378f, 0.032379150f, 0.841949463f, +-1.077117920f, -0.297210693f, -0.150497437f, -0.025817871f, +-0.029281614f, -0.000030518f, -0.002243042f, -0.000152588f, +0.000045776f, 0.001113892f, -0.003173828f, 0.018463135f, +-0.014801024f, 0.133590698f, 0.050354004f, 0.866363525f, +-1.063217163f, -0.271591187f, -0.151596069f, -0.021179199f, +-0.028533936f, 0.000442505f, -0.002120972f, -0.000137329f, +0.000045776f, 0.001205444f, -0.003051758f, 0.019577026f, +-0.012115479f, 0.137298584f, 0.069168091f, 0.890090942f, +-1.048156738f, -0.246505737f, -0.152069092f, -0.016708374f, +-0.027725220f, 0.000869751f, -0.002014160f, -0.000122070f, +0.000061035f, 0.001296997f, -0.002883911f, 0.020690918f, +-0.009231566f, 0.140670776f, 0.088775635f, 0.913055420f, +-1.031936646f, -0.221984863f, -0.151962280f, -0.012420653f, +-0.026840210f, 0.001266479f, -0.001907349f, -0.000106812f, +0.000061035f, 0.001388550f, -0.002700806f, 0.021789551f, +-0.006134033f, 0.143676758f, 0.109161377f, 0.935195923f, +-1.014617920f, -0.198059082f, -0.151306152f, -0.008316040f, +-0.025909424f, 0.001617432f, -0.001785278f, -0.000106812f, +0.000076294f, 0.001480103f, -0.002487183f, 0.022857666f, +-0.002822876f, 0.146255493f, 0.130310059f, 0.956481934f, +-0.996246338f, -0.174789429f, -0.150115967f, -0.004394531f, +-0.024932859f, 0.001937866f, -0.001693726f, -0.000091553f, +-0.001586914f, -0.023910521f, -0.148422241f, -0.976852417f, +0.152206421f, 0.000686646f, -0.002227783f, 0.000076294f, diff --git a/code/mp3code/towave.c b/code/mp3code/towave.c new file mode 100644 index 0000000..5d19d00 --- /dev/null +++ b/code/mp3code/towave.c @@ -0,0 +1,766 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: towave.c,v 1.3 1999/10/19 07:13:09 elrod Exp $ +____________________________________________________________________________*/ + +/* ------------------------------------------------------------------------ + + NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE + + This file exists for reference only. It is not actually used + in the FreeAmp project. There is no need to mess with this + file. There is no need to flatten the beavers, either. + + NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE + +/*---- towave.c -------------------------------------------- + 32 bit version only + +decode mpeg Layer I/II/III file using portable ANSI C decoder, +output to pcm wave file. + +mod 8/19/98 decode 22 sf bands + +mod 5/14/98 allow mpeg25 (dec8 not supported for mpeg25 samp rate) + +mod 3/4/98 bs_trigger bs_bufbytes made signed, unsigned may + not terminate properly. Also extra test in bs_fill. + +mod 8/6/96 add 8 bit output to standard decoder + +ver 1.4 mods 7/18/96 32 bit and add asm option + +mods 6/29/95 allow MS wave file for u-law. bugfix u-law table dec8.c + +mods 2/95 add sample rate reduction, freq_limit and conversions. + add _decode8 for 8Ks output, 16bit 8bit, u-law output. + add additional control parameters to init. + add _info function + +mod 5/12/95 add quick window cwinq.c + +mod 5/19/95 change from stream io to handle io + +mod 11/16/95 add Layer I + +mod 1/5/95 integer overflow mod iup.c + +ver 1.3 +mod 2/5/96 portability mods + drop Tom and Gloria pcm file types + +ver 2.0 +mod 1/7/97 Layer 3 (float mpeg-1 only) + 2/6/97 Layer 3 MPEG-2 + +ver 3.01 Layer III bugfix crc problem 8/18/97 +ver 3.02 Layer III fix wannabe.mp3 problem 10/9/97 +ver 3.03 allow mpeg 2.5 5/14/98 + +Decoder functions for _decode8 are defined in dec8.c. Useage +is same as regular decoder. + +Towave illustrates use of decoder. Towave converts +mpeg audio files to 16 bit (short) pcm. Default pcm file +format is wave. Other formats can be accommodated by +adding alternative write_pcm_header and write_pcm_tailer +functions. The functions kbhit and getch used in towave.c +may not port to other systems. + +The decoder handles all mpeg1 and mpeg2 Layer I/II bitstreams. + +For compatability with the asm decoder and future C versions, +source code users are discouraged from making modifications +to the decoder proper. MS Windows applications can use wrapper +functions in a separate module if decoder functions need to +be exported. + +NOTE additional control parameters. + +mod 8/6/96 standard decoder adds 8 bit output + +decode8 (8Ks output) convert_code: + convert_code = 4*bit_code + chan_code + bit_code: 1 = 16 bit linear pcm + 2 = 8 bit (unsigned) linear pcm + 3 = u-law (8 bits unsigned) + chan_code: 0 = convert two chan to mono + 1 = convert two chan to mono + 2 = convert two chan to left chan + 3 = convert two chan to right chan + +decode (standard decoder) convert_code: + 0 = two chan output + 1 = convert two chan to mono + 2 = convert two chan to left chan + 3 = convert two chan to right chan + or with 8 = 8 bit output + (other bits ignored) + +decode (standard decoder) reduction_code: + 0 = full sample rate output + 1 = half rate + 2 = quarter rate + +-----------------------------------------------------------*/ +#include +#include +#include +#include +#include +#ifdef WIN32 +#include +#endif +#include /* file open flags */ +#include /* someone wants for port */ +#include /* forward slash for portability */ +#include "mhead.h" /* mpeg header structure, decode protos */ + +#include "port.h" + +// JDW +#ifdef __linux__ +#include +#include +#include +#include +#endif +// JDW + +#include "mp3struct.h" +#include + + +#ifndef byte +typedef unsigned char byte; +#endif + + + +typedef struct id3v1_1 { + char id[3]; + char title[30]; // + char artist[30]; // "Raven Software" + char album[30]; // "#UNCOMP %d" // needed + char year[4]; // "2000" + char comment[28]; // "#MAXVOL %g" // needed + char zero; + char track; + char genre; +} id3v1_1; // 128 bytes in size + +id3v1_1 *gpTAG; +#define BYTESREMAINING_ACCOUNT_FOR_REAR_TAG(_pvData, _iBytesRemaining) \ + \ + /* account for trailing ID3 tag in _iBytesRemaining */ \ + gpTAG = (id3v1_1*) (((byte *)_pvData + _iBytesRemaining)-sizeof(id3v1_1)); /* sizeof = 128 */ \ + if (!strncmp(gpTAG->id, "TAG", 3)) \ + { \ + _iBytesRemaining -= sizeof(id3v1_1); \ + } + + + + + +/******** pcm buffer ********/ + +#define PCM_BUFBYTES 60000U // more than enough to cover the largest that one packet will ever expand to +char PCM_Buffer[PCM_BUFBYTES]; // better off being declared, so we don't do mallocs in this module (MAC reasons) + + typedef struct + { + int (*decode_init) (MPEG_HEAD * h, int framebytes_arg, + int reduction_code, int transform_code, + int convert_code, int freq_limit); + void (*decode_info) (DEC_INFO * info); + IN_OUT(*decode) (unsigned char *bs, short *pcm, unsigned char *pNextByteAfterData); + } + AUDIO; + +#if 0 + // fuck this... + static AUDIO audio_table[2][2] = + { + { + {audio_decode_init, audio_decode_info, audio_decode}, + {audio_decode8_init, audio_decode8_info, audio_decode8}, + }, + { + {i_audio_decode_init, i_audio_decode_info, i_audio_decode}, + {audio_decode8_init, audio_decode8_info, audio_decode8}, + } + }; +#else + static AUDIO audio_table[2][2] = + { + { + {audio_decode_init, audio_decode_info, audio_decode}, + {audio_decode_init, audio_decode_info, audio_decode}, + }, + { + {audio_decode_init, audio_decode_info, audio_decode}, + {audio_decode_init, audio_decode_info, audio_decode}, + } + }; +#endif + + static const AUDIO audio = {audio_decode_init, audio_decode_info, audio_decode}; //audio_table[0][0]; + + +// Do NOT change these, ever!!!!!!!!!!!!!!!!!! +// +const int reduction_code = 0; // unpack at full sample rate output +const int convert_code_mono = 1; +const int convert_code_stereo = 0; +const int freq_limit = 24000; // no idea what this is about, but it's always this value so... + +// the entire decode mechanism uses this now... +// +MP3STREAM _MP3Stream; +LP_MP3STREAM pMP3Stream = &_MP3Stream; +int bFastEstimateOnly = 0; // MUST DEFAULT TO THIS VALUE!!!!!!!!! + + +// char *return is NZ for any errors (no trailing CR!) +// +char *C_MP3_IsValid(void *pvData, int iDataLen, int bStereoDesired) +{ +// char sTemp[1024]; ///////////////////////////////////////////////// + unsigned int iRealDataStart; + MPEG_HEAD head; + DEC_INFO decinfo; + + int iBitRate; + int iFrameBytes; + +#ifdef _DEBUG +// int iIgnoreThisForNowIJustNeedItToBreakpointOnToReadAValue = sizeof(MP3STREAM); +// iBitRate = iIgnoreThisForNowIJustNeedItToBreakpointOnToReadAValue; // get rid of unused-variable warning +#endif + + memset(pMP3Stream,0,sizeof(*pMP3Stream)); + + iFrameBytes = head_info3( pvData, iDataLen/2, &head, &iBitRate, &iRealDataStart); + if (iFrameBytes == 0) + { + return "MP3ERR: Bad or unsupported file!"; + } + + // check for files with bad frame unpack sizes (that would crash the game), or stereo files. + // + // although the decoder can convert stereo to mono (apparently), we want to know about stereo files + // because they're a waste of source space... (all FX are mono, and moved via panning) + // + if (head.mode != 3 && !bStereoDesired && iDataLen > 98000) //3 seems to mean mono + {// we'll allow it for small files even if stereo + if ( iDataLen != 1050024 ) //fixme, make cinematic_1 play as music instead + { + return "MP3ERR: Sound file is stereo!"; + } + } + if (audio.decode_init(&head, iFrameBytes, reduction_code, iRealDataStart, bStereoDesired?convert_code_stereo:convert_code_mono, freq_limit)) + { + if (bStereoDesired) + { + if (pMP3Stream->outbytes > 4608) + { + return "MP3ERR: Source file has output packet size > 2304 (*2 for stereo) bytes!"; + } + } + else + { + if (pMP3Stream->outbytes > 2304) + { + return "MP3ERR: Source file has output packet size > 2304 bytes!"; + } + } + + audio.decode_info(&decinfo); + + if (decinfo.bits != 16) + { + return "MP3ERR: Source file is not 16bit!"; // will this ever happen? oh well... + } + + if (decinfo.samprate != 44100) + { + return "MP3ERR: Source file is not sampled @ 44100!"; + } + if (bStereoDesired && decinfo.channels != 2) + { + return "MP3ERR: Source file is not stereo!"; // sod it, I'm going to count this as an error now + } + + } + else + { + return "MP3ERR: Decoder failed to initialise"; + } + + // file seems to be valid... + // + return NULL; +} + + + +// char *return is NZ for any errors (no trailing CR!) +// +char* C_MP3_GetHeaderData(void *pvData, int iDataLen, int *piRate, int *piWidth, int *piChannels, int bStereoDesired) +{ + unsigned int iRealDataStart; + MPEG_HEAD head; + DEC_INFO decinfo; + + int iBitRate; + int iFrameBytes; + + memset(pMP3Stream,0,sizeof(*pMP3Stream)); + + iFrameBytes = head_info3( pvData, iDataLen/2, &head, &iBitRate, &iRealDataStart); + if (iFrameBytes == 0) + { + return "MP3ERR: Bad or unsupported file!"; + } + + if (audio.decode_init(&head, iFrameBytes, reduction_code, iRealDataStart, bStereoDesired?convert_code_stereo:convert_code_mono, freq_limit)) + { + audio.decode_info(&decinfo); + + *piRate = decinfo.samprate; // rate (eg 22050, 44100 etc) + *piWidth = decinfo.bits/8; // 1 for 8bit, 2 for 16 bit + *piChannels = decinfo.channels; // 1 for mono, 2 for stereo + } + else + { + return "MP3ERR: Decoder failed to initialise"; + } + + // everything ok... + // + return NULL; +} + + + + +// this duplicates work done in C_MP3_IsValid(), but it avoids global structs, and means that you can call this anytime +// if you just want info for some reason +// +// ( size is now workd out just by decompressing each packet header, not the whole stream. MUCH faster :-) +// +// char *return is NZ for any errors (no trailing CR!) +// +char *C_MP3_GetUnpackedSize(void *pvData, int iSourceBytesRemaining, int *piUnpackedSize, int bStereoDesired ) +{ + int iReadLimit; + unsigned int iRealDataStart; + MPEG_HEAD head; + int iBitRate; + + char *pPCM_Buffer = PCM_Buffer; + char *psReturn = NULL; +// int iSourceReadIndex = 0; + int iDestWriteIndex = 0; + + int iFrameBytes; + int iFrameCounter; + + DEC_INFO decinfo; + IN_OUT x; + + memset(pMP3Stream,0,sizeof(*pMP3Stream)); + +#define iSourceReadIndex iRealDataStart + +// iFrameBytes = head_info2( pvData, 0, &head, &iBitRate); + iFrameBytes = head_info3( pvData, iSourceBytesRemaining/2, &head, &iBitRate, &iRealDataStart); + + BYTESREMAINING_ACCOUNT_FOR_REAR_TAG(pvData, iSourceBytesRemaining) + iSourceBytesRemaining -= iRealDataStart; + + iReadLimit = iSourceReadIndex + iSourceBytesRemaining; + + if (iFrameBytes) + { + //pPCM_Buffer = Z_Malloc(PCM_BUFBYTES); + + //if (pPCM_Buffer) + { + // init decoder... + + if (audio.decode_init(&head, iFrameBytes, reduction_code, iRealDataStart, bStereoDesired?convert_code_stereo:convert_code_mono, freq_limit)) + { + audio.decode_info(&decinfo); + + // decode... + // + for (iFrameCounter = 0;;iFrameCounter++) + { + if ( iSourceBytesRemaining == 0 || iSourceBytesRemaining < iFrameBytes) + break; // end of file + + bFastEstimateOnly = 1; /////////////////////////////// + + x = audio.decode((unsigned char *)pvData + iSourceReadIndex, (short *) ((char *)pPCM_Buffer + //+ iDestWriteIndex // keep decoding over the same spot since we're only counting bytes in this function + ), + (unsigned char *)pvData + iReadLimit + ); + + bFastEstimateOnly = 0; /////////////////////////////// + + iSourceReadIndex += x.in_bytes; + iSourceBytesRemaining -= x.in_bytes; + iDestWriteIndex += x.out_bytes; + + if (x.in_bytes <= 0) + { + //psReturn = "MP3ERR: Bad sync in file"; + break; + } + } + + *piUnpackedSize = iDestWriteIndex; // yeeehaaa! + } + else + { + psReturn = "MP3ERR: Decoder failed to initialise"; + } + } +// else +// { +// psReturn = "MP3ERR: Unable to alloc temp decomp buffer"; +// } + } + else + { + psReturn = "MP3ERR: Bad or Unsupported MP3 file!"; + } + + +// if (pPCM_Buffer) +// { +// Z_Free(pPCM_Buffer); +// pPCM_Buffer = NULL; // I know, I know... +// } + + return psReturn; + +#undef iSourceReadIndex +} + + + + +char *C_MP3_UnpackRawPCM( void *pvData, int iSourceBytesRemaining, int *piUnpackedSize, void *pbUnpackBuffer, int bStereoDesired) +{ + int iReadLimit; + unsigned int iRealDataStart; + MPEG_HEAD head; + int iBitRate; + + char *psReturn = NULL; +// int iSourceReadIndex = 0; + int iDestWriteIndex = 0; + + int iFrameBytes; + int iFrameCounter; + + DEC_INFO decinfo; + IN_OUT x; + + memset(pMP3Stream,0,sizeof(*pMP3Stream)); + +#define iSourceReadIndex iRealDataStart + +// iFrameBytes = head_info2( pvData, 0, &head, &iBitRate); + iFrameBytes = head_info3( pvData, iSourceBytesRemaining/2, &head, &iBitRate, &iRealDataStart); + + BYTESREMAINING_ACCOUNT_FOR_REAR_TAG(pvData, iSourceBytesRemaining) + iSourceBytesRemaining -= iRealDataStart; + + iReadLimit = iSourceReadIndex + iSourceBytesRemaining; + + if (iFrameBytes) + { +// if (1)////////////////////////pPCM_Buffer) + { + // init decoder... + + if (audio.decode_init(&head, iFrameBytes, reduction_code, iRealDataStart, bStereoDesired?convert_code_stereo:convert_code_mono, freq_limit)) + { + audio.decode_info(&decinfo); + +// printf("\n output samprate = %6ld",decinfo.samprate); +// printf("\n output channels = %6d", decinfo.channels); +// printf("\n output bits = %6d", decinfo.bits); +// printf("\n output type = %6d", decinfo.type); + +//=============== + + // decode... + // + for (iFrameCounter = 0;;iFrameCounter++) + { + if ( iSourceBytesRemaining == 0 || iSourceBytesRemaining < iFrameBytes) + break; // end of file + + x = audio.decode((unsigned char *)pvData + iSourceReadIndex, (short *) ((char *)pbUnpackBuffer + iDestWriteIndex), + (unsigned char *)pvData + iReadLimit + ); + + iSourceReadIndex += x.in_bytes; + iSourceBytesRemaining -= x.in_bytes; + iDestWriteIndex += x.out_bytes; + + if (x.in_bytes <= 0) + { + //psReturn = "MP3ERR: Bad sync in file"; + break; + } + } + + *piUnpackedSize = iDestWriteIndex; // yeeehaaa! + } + else + { + psReturn = "MP3ERR: Decoder failed to initialise"; + } + } + } + else + { + psReturn = "MP3ERR: Bad or Unsupported MP3 file!"; + } + + return psReturn; + +#undef iSourceReadIndex +} + + +// called once, after we've decided to keep something as MP3. This just sets up the decoder for subsequent stream-calls. +// +// (the struct pSFX_MP3Stream is cleared internally, so pass as args anything you want stored in it) +// +// char * return is NULL for ok, else error string +// +char *C_MP3Stream_DecodeInit( LP_MP3STREAM pSFX_MP3Stream, void *pvSourceData, int iSourceBytesRemaining, + int iGameAudioSampleRate, int iGameAudioSampleBits, int bStereoDesired ) +{ + char *psReturn = NULL; + MPEG_HEAD head; // only relevant within this function during init + DEC_INFO decinfo; // " " + int iBitRate; // not used after being filled in by head_info3() + + pMP3Stream = pSFX_MP3Stream; + + memset(pMP3Stream,0,sizeof(*pMP3Stream)); + + pMP3Stream->pbSourceData = (byte *) pvSourceData; + pMP3Stream->iSourceBytesRemaining = iSourceBytesRemaining; + pMP3Stream->iSourceFrameBytes = head_info3( (byte *) pvSourceData, iSourceBytesRemaining/2, &head, &iBitRate, (unsigned int*)&pMP3Stream->iSourceReadIndex ); + + // hack, do NOT do this for stereo, since music files are now streamed and therefore the data isn't actually fully + // loaded at this point, only about 4k or so for the header is actually in memory!!!... + // + if (!bStereoDesired) + { + BYTESREMAINING_ACCOUNT_FOR_REAR_TAG(pvSourceData, pMP3Stream->iSourceBytesRemaining); + pMP3Stream->iSourceBytesRemaining -= pMP3Stream->iSourceReadIndex; + } + + // backup a couple of fields so we can play this again later... + // + pMP3Stream->iRewind_SourceReadIndex = pMP3Stream->iSourceReadIndex; + pMP3Stream->iRewind_SourceBytesRemaining= pMP3Stream->iSourceBytesRemaining; + + assert(pMP3Stream->iSourceFrameBytes); + if (pMP3Stream->iSourceFrameBytes) + { + if (audio.decode_init(&head, pMP3Stream->iSourceFrameBytes, reduction_code, pMP3Stream->iSourceReadIndex, bStereoDesired?convert_code_stereo:convert_code_mono, freq_limit)) + { + pMP3Stream->iRewind_FinalReductionCode = reduction_code; // default = 0 (no reduction), 1=half, 2 = quarter + + pMP3Stream->iRewind_FinalConvertCode = bStereoDesired?convert_code_stereo:convert_code_mono; + // default = 1 (mono), OR with 8 for 8-bit output + + // only now can we ask what kind of properties this file has, and then adjust to fit what the game wants... + // + audio.decode_info(&decinfo); + +// printf("\n output samprate = %6ld",decinfo.samprate); +// printf("\n output channels = %6d", decinfo.channels); +// printf("\n output bits = %6d", decinfo.bits); +// printf("\n output type = %6d", decinfo.type); + + // decoder offers half or quarter rate adjustement only... + // + if (iGameAudioSampleRate == decinfo.samprate>>1) + pMP3Stream->iRewind_FinalReductionCode = 1; + else + if (iGameAudioSampleRate == decinfo.samprate>>2) + pMP3Stream->iRewind_FinalReductionCode = 2; + + if (iGameAudioSampleBits == decinfo.bits>>1) // if game wants 8 bit sounds, then setup for that + pMP3Stream->iRewind_FinalConvertCode |= 8; + + if (audio.decode_init(&head, pMP3Stream->iSourceFrameBytes, pMP3Stream->iRewind_FinalReductionCode, pMP3Stream->iSourceReadIndex, pMP3Stream->iRewind_FinalConvertCode, freq_limit)) + { + audio.decode_info(&decinfo); +#ifdef _DEBUG + assert( iGameAudioSampleRate == decinfo.samprate ); + assert( iGameAudioSampleBits == decinfo.bits ); +#endif + + // sod it, no harm in one last check... (should never happen) + // + if ( iGameAudioSampleRate != decinfo.samprate || iGameAudioSampleBits != decinfo.bits ) + { + psReturn = "MP3ERR: Decoder unable to convert to current game audio settings"; + } + } + else + { + psReturn = "MP3ERR: Decoder failed to initialise for pass 2 sample adjust"; + } + } + else + { + psReturn = "MP3ERR: Decoder failed to initialise"; + } + } + else + { + psReturn = "MP3ERR: Errr.... something's broken with this MP3 file"; // should never happen by this point + } + + // restore global stream ptr before returning to normal functions (so the rest of the MP3 code still works)... + // + pMP3Stream = &_MP3Stream; + + return psReturn; +} + +// return value is decoded bytes for this packet, which is effectively a BOOL, NZ for not finished decoding yet... +// +unsigned int C_MP3Stream_Decode( LP_MP3STREAM pSFX_MP3Stream, int bFastForwarding ) +{ + unsigned int uiDecoded = 0; // default to "finished" + IN_OUT x; + + pMP3Stream = pSFX_MP3Stream; + + do + { + if ( pSFX_MP3Stream->iSourceBytesRemaining == 0 )//|| pSFX_MP3Stream->iSourceBytesRemaining < pSFX_MP3Stream->iSourceFrameBytes) + { + uiDecoded = 0; // finished + break; + } + + + + bFastEstimateOnly = bFastForwarding; /////////////////////////////// + + x = audio.decode(pSFX_MP3Stream->pbSourceData + pSFX_MP3Stream->iSourceReadIndex, (short *) (pSFX_MP3Stream->bDecodeBuffer), + pSFX_MP3Stream->pbSourceData + pSFX_MP3Stream->iRewind_SourceReadIndex + pSFX_MP3Stream->iRewind_SourceBytesRemaining + ); + + bFastEstimateOnly = 0; /////////////////////////////// + + + +#ifdef _DEBUG + pSFX_MP3Stream->iSourceFrameCounter++; +#endif + + pSFX_MP3Stream->iSourceReadIndex += x.in_bytes; + pSFX_MP3Stream->iSourceBytesRemaining -= x.in_bytes; + pSFX_MP3Stream->iBytesDecodedTotal += x.out_bytes; + pSFX_MP3Stream->iBytesDecodedThisPacket = x.out_bytes; + + uiDecoded = x.out_bytes; + + if (x.in_bytes <= 0) + { + //psReturn = "MP3ERR: Bad sync in file"; + uiDecoded= 0; // finished + break; + } + } + #pragma warning (disable : 4127 ) // conditional expression is constant + while (0); // + #pragma warning (default : 4127 ) // conditional expression is constant + + // restore global stream ptr before returning to normal functions (so the rest of the MP3 code still works)... + // + pMP3Stream = &_MP3Stream; + + return uiDecoded; +} + + +// ret is char* errstring, else NULL for ok +// +char *C_MP3Stream_Rewind( LP_MP3STREAM pSFX_MP3Stream ) +{ + char* psReturn = NULL; + MPEG_HEAD head; // only relevant within this function during init + int iBitRate; // ditto + int iNULL; + + pMP3Stream = pSFX_MP3Stream; + + pMP3Stream->iSourceReadIndex = pMP3Stream->iRewind_SourceReadIndex; + pMP3Stream->iSourceBytesRemaining = pMP3Stream->iRewind_SourceBytesRemaining; // already adjusted for tags etc + + // I'm not sure that this is needed, but where else does decode_init get passed useful data ptrs?... + // + if (pMP3Stream->iSourceFrameBytes == head_info3( pMP3Stream->pbSourceData, pMP3Stream->iSourceBytesRemaining/2, &head, &iBitRate, (unsigned int*)&iNULL ) ) + { + if (audio.decode_init(&head, pMP3Stream->iSourceFrameBytes, pMP3Stream->iRewind_FinalReductionCode, pMP3Stream->iSourceReadIndex, pMP3Stream->iRewind_FinalConvertCode, freq_limit)) + { + // we should always get here... + // + } + else + { + psReturn = "MP3ERR: Failed to re-init decoder for rewind!"; + } + } + else + { + psReturn = "MP3ERR: Frame bytes mismatch during rewind header-read!"; + } + + // restore global stream ptr before returning to normal functions (so the rest of the MP3 code still works)... + // + pMP3Stream = &_MP3Stream; + + return psReturn; +} + diff --git a/code/mp3code/uph.c b/code/mp3code/uph.c new file mode 100644 index 0000000..48d9369 --- /dev/null +++ b/code/mp3code/uph.c @@ -0,0 +1,507 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: uph.c,v 1.3 1999/10/19 07:13:09 elrod Exp $ +____________________________________________________________________________*/ + +/**** uph.c *************************************************** + +Layer 3 audio + huffman decode + + +******************************************************************/ +#include +#include +#include +#include + +#include "L3.h" + +#pragma warning ( disable : 4711 ) // function 'xxxx' selected for automatic inline expansion + + +#ifdef _MSC_VER +#pragma warning(disable: 4505) +#endif + +/*===============================================================*/ + +/* max bits required for any lookup - change if htable changes */ +/* quad required 10 bit w/signs must have (MAXBITS+2) >= 10 */ +#define MAXBITS 9 + +static const HUFF_ELEMENT huff_table_0[4] = +{{0}, {0}, {0}, {64}}; /* dummy must not use */ + +#include "htable.h" + +/*-- 6 bit lookup (purgebits, value) --*/ +static const unsigned char quad_table_a[][2] = +{ + {6, 11}, {6, 15}, {6, 13}, {6, 14}, {6, 7}, {6, 5}, {5, 9}, + {5, 9}, {5, 6}, {5, 6}, {5, 3}, {5, 3}, {5, 10}, {5, 10}, + {5, 12}, {5, 12}, {4, 2}, {4, 2}, {4, 2}, {4, 2}, {4, 1}, + {4, 1}, {4, 1}, {4, 1}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, + {4, 8}, {4, 8}, {4, 8}, {4, 8}, {1, 0}, {1, 0}, {1, 0}, + {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, + {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, + {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, + {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}, + {1, 0}, +}; + + +typedef struct +{ + const HUFF_ELEMENT *table; + int linbits; + int ncase; +} +HUFF_SETUP; + +#define no_bits 0 +#define one_shot 1 +#define no_linbits 2 +#define have_linbits 3 +#define quad_a 4 +#define quad_b 5 + + +static const HUFF_SETUP table_look[] = +{ + {huff_table_0, 0, no_bits}, + {huff_table_1, 0, one_shot}, + {huff_table_2, 0, one_shot}, + {huff_table_3, 0, one_shot}, + {huff_table_0, 0, no_bits}, + {huff_table_5, 0, one_shot}, + {huff_table_6, 0, one_shot}, + {huff_table_7, 0, no_linbits}, + {huff_table_8, 0, no_linbits}, + {huff_table_9, 0, no_linbits}, + {huff_table_10, 0, no_linbits}, + {huff_table_11, 0, no_linbits}, + {huff_table_12, 0, no_linbits}, + {huff_table_13, 0, no_linbits}, + {huff_table_0, 0, no_bits}, + {huff_table_15, 0, no_linbits}, + {huff_table_16, 1, have_linbits}, + {huff_table_16, 2, have_linbits}, + {huff_table_16, 3, have_linbits}, + {huff_table_16, 4, have_linbits}, + {huff_table_16, 6, have_linbits}, + {huff_table_16, 8, have_linbits}, + {huff_table_16, 10, have_linbits}, + {huff_table_16, 13, have_linbits}, + {huff_table_24, 4, have_linbits}, + {huff_table_24, 5, have_linbits}, + {huff_table_24, 6, have_linbits}, + {huff_table_24, 7, have_linbits}, + {huff_table_24, 8, have_linbits}, + {huff_table_24, 9, have_linbits}, + {huff_table_24, 11, have_linbits}, + {huff_table_24, 13, have_linbits}, + {huff_table_0, 0, quad_a}, + {huff_table_0, 0, quad_b}, +}; + +/*========================================================*/ +extern BITDAT bitdat; + +/*------------- get n bits from bitstream -------------*/ +/* unused +static unsigned int bitget(int n) +{ + unsigned int x; + + if (bitdat.bits < n) + { */ /* refill bit buf if necessary */ +/* while (bitdat.bits <= 24) + { + bitdat.bitbuf = (bitdat.bitbuf << 8) | *bitdat.bs_ptr++; + bitdat.bits += 8; + } + } + bitdat.bits -= n; + x = bitdat.bitbuf >> bitdat.bits; + bitdat.bitbuf -= x << bitdat.bits; + return x; +} +*/ +/*----- get n bits - checks for n+2 avail bits (linbits+sign) -----*/ +static unsigned int bitget_lb(int n) +{ + unsigned int x; + + if (bitdat.bits < (n + 2)) + { /* refill bit buf if necessary */ + while (bitdat.bits <= 24) + { + bitdat.bitbuf = (bitdat.bitbuf << 8) | *bitdat.bs_ptr++; + bitdat.bits += 8; + } + } + bitdat.bits -= n; + x = bitdat.bitbuf >> bitdat.bits; + bitdat.bitbuf -= x << bitdat.bits; + return x; +} + + + + +/*------------- get n bits but DO NOT remove from bitstream --*/ +static unsigned int bitget2(int n) +{ + unsigned int x; + + if (bitdat.bits < (MAXBITS + 2)) + { /* refill bit buf if necessary */ + while (bitdat.bits <= 24) + { + bitdat.bitbuf = (bitdat.bitbuf << 8) | *bitdat.bs_ptr++; + bitdat.bits += 8; + } + } + x = bitdat.bitbuf >> (bitdat.bits - n); + return x; +} +/*------------- remove n bits from bitstream ---------*/ +/* unused +static void bitget_purge(int n) +{ + bitdat.bits -= n; + bitdat.bitbuf -= (bitdat.bitbuf >> bitdat.bits) << bitdat.bits; +} +*/ +/*------------- get 1 bit from bitstream NO CHECK -------------*/ +/* unused +static unsigned int bitget_1bit() +{ + unsigned int x; + + bitdat.bits--; + x = bitdat.bitbuf >> bitdat.bits; + bitdat.bitbuf -= x << bitdat.bits; + return x; +} +*/ +/*========================================================*/ +/*========================================================*/ +#define mac_bitget_check(n) if( bitdat.bits < (n) ) { \ + while( bitdat.bits <= 24 ) { \ + bitdat.bitbuf = (bitdat.bitbuf << 8) | *bitdat.bs_ptr++; \ + bitdat.bits += 8; \ + } \ +} +/*---------------------------------------------------------*/ +#define mac_bitget2(n) (bitdat.bitbuf >> (bitdat.bits-n)); +/*---------------------------------------------------------*/ +#define mac_bitget(n) ( bitdat.bits -= n, \ + code = bitdat.bitbuf >> bitdat.bits, \ + bitdat.bitbuf -= code << bitdat.bits, \ + code ) +/*---------------------------------------------------------*/ +#define mac_bitget_purge(n) bitdat.bits -= n, \ + bitdat.bitbuf -= (bitdat.bitbuf >> bitdat.bits) << bitdat.bits; +/*---------------------------------------------------------*/ +#define mac_bitget_1bit() ( bitdat.bits--, \ + code = bitdat.bitbuf >> bitdat.bits, \ + bitdat.bitbuf -= code << bitdat.bits, \ + code ) +/*========================================================*/ +/*========================================================*/ +void unpack_huff(int xy[][2], int n, int ntable) +{ + int i; + const HUFF_ELEMENT *t; + const HUFF_ELEMENT *t0; + int linbits; + int bits; + int code; + int x, y; + + if (n <= 0) + return; + n = n >> 1; /* huff in pairs */ +/*-------------*/ + t0 = table_look[ntable].table; + linbits = table_look[ntable].linbits; + switch (table_look[ntable].ncase) + { + default: +/*------------------------------------------*/ + case no_bits: +/*- table 0, no data, x=y=0--*/ + for (i = 0; i < n; i++) + { + xy[i][0] = 0; + xy[i][1] = 0; + } + return; +/*------------------------------------------*/ + case one_shot: +/*- single lookup, no escapes -*/ + for (i = 0; i < n; i++) + { + mac_bitget_check((MAXBITS + 2)); + bits = t0[0].b.signbits; + code = mac_bitget2(bits); + mac_bitget_purge(t0[1 + code].b.purgebits); + x = t0[1 + code].b.x; + y = t0[1 + code].b.y; + if (x) + if (mac_bitget_1bit()) + x = -x; + if (y) + if (mac_bitget_1bit()) + y = -y; + xy[i][0] = x; + xy[i][1] = y; + if (bitdat.bs_ptr > bitdat.bs_ptr_end) + break; // bad data protect + + } + return; +/*------------------------------------------*/ + case no_linbits: + for (i = 0; i < n; i++) + { + t = t0; + for (;;) + { + mac_bitget_check((MAXBITS + 2)); + bits = t[0].b.signbits; + code = mac_bitget2(bits); + if (t[1 + code].b.purgebits) + break; + t += t[1 + code].ptr; /* ptr include 1+code */ + mac_bitget_purge(bits); + } + mac_bitget_purge(t[1 + code].b.purgebits); + x = t[1 + code].b.x; + y = t[1 + code].b.y; + if (x) + if (mac_bitget_1bit()) + x = -x; + if (y) + if (mac_bitget_1bit()) + y = -y; + xy[i][0] = x; + xy[i][1] = y; + if (bitdat.bs_ptr > bitdat.bs_ptr_end) + break; // bad data protect + + } + return; +/*------------------------------------------*/ + case have_linbits: + for (i = 0; i < n; i++) + { + t = t0; + for (;;) + { + bits = t[0].b.signbits; + code = bitget2(bits); + if (t[1 + code].b.purgebits) + break; + t += t[1 + code].ptr; /* ptr includes 1+code */ + mac_bitget_purge(bits); + } + mac_bitget_purge(t[1 + code].b.purgebits); + x = t[1 + code].b.x; + y = t[1 + code].b.y; + if (x == 15) + x += bitget_lb(linbits); + if (x) + if (mac_bitget_1bit()) + x = -x; + if (y == 15) + y += bitget_lb(linbits); + if (y) + if (mac_bitget_1bit()) + y = -y; + xy[i][0] = x; + xy[i][1] = y; + if (bitdat.bs_ptr > bitdat.bs_ptr_end) + break; // bad data protect + + } + return; + } +/*--- end switch ---*/ + +} +/*==========================================================*/ +int unpack_huff_quad(int vwxy[][4], int n, int nbits, int ntable) +{ + int i; + int code; + int x, y, v, w; + int tmp; + int i_non_zero, tmp_nz; + + tmp_nz = 15; + i_non_zero = -1; + + n = n >> 2; /* huff in quads */ + + if (ntable) + goto case_quad_b; + +/* case_quad_a: */ + for (i = 0; i < n; i++) + { + if (nbits <= 0) + break; + mac_bitget_check(10); + code = mac_bitget2(6); + nbits -= quad_table_a[code][0]; + mac_bitget_purge(quad_table_a[code][0]); + tmp = quad_table_a[code][1]; + if (tmp) + { + i_non_zero = i; + tmp_nz = tmp; + } + v = (tmp >> 3) & 1; + w = (tmp >> 2) & 1; + x = (tmp >> 1) & 1; + y = tmp & 1; + if (v) + { + if (mac_bitget_1bit()) + v = -v; + nbits--; + } + if (w) + { + if (mac_bitget_1bit()) + w = -w; + nbits--; + } + if (x) + { + if (mac_bitget_1bit()) + x = -x; + nbits--; + } + if (y) + { + if (mac_bitget_1bit()) + y = -y; + nbits--; + } + vwxy[i][0] = v; + vwxy[i][1] = w; + vwxy[i][2] = x; + vwxy[i][3] = y; + if (bitdat.bs_ptr > bitdat.bs_ptr_end) + break; // bad data protect + + } + if (i && nbits < 0) + { + i--; + vwxy[i][0] = 0; + vwxy[i][1] = 0; + vwxy[i][2] = 0; + vwxy[i][3] = 0; + } + + i_non_zero = (i_non_zero + 1) << 2; + + if ((tmp_nz & 3) == 0) + i_non_zero -= 2; + + return i_non_zero; + +/*--------------------*/ + case_quad_b: + for (i = 0; i < n; i++) + { + if (nbits < 4) + break; + nbits -= 4; + mac_bitget_check(8); + tmp = mac_bitget(4) ^ 15; /* one's complement of bitstream */ + if (tmp) + { + i_non_zero = i; + tmp_nz = tmp; + } + v = (tmp >> 3) & 1; + w = (tmp >> 2) & 1; + x = (tmp >> 1) & 1; + y = tmp & 1; + if (v) + { + if (mac_bitget_1bit()) + v = -v; + nbits--; + } + if (w) + { + if (mac_bitget_1bit()) + w = -w; + nbits--; + } + if (x) + { + if (mac_bitget_1bit()) + x = -x; + nbits--; + } + if (y) + { + if (mac_bitget_1bit()) + y = -y; + nbits--; + } + vwxy[i][0] = v; + vwxy[i][1] = w; + vwxy[i][2] = x; + vwxy[i][3] = y; + if (bitdat.bs_ptr > bitdat.bs_ptr_end) + break; // bad data protect + + } + if (nbits < 0) + { + i--; + vwxy[i][0] = 0; + vwxy[i][1] = 0; + vwxy[i][2] = 0; + vwxy[i][3] = 0; + } + + i_non_zero = (i_non_zero + 1) << 2; + + if ((tmp_nz & 3) == 0) + i_non_zero -= 2; + + return i_non_zero; /* return non-zero sample (to nearest pair) */ + +} +/*-----------------------------------------------------*/ diff --git a/code/mp3code/upsf.c b/code/mp3code/upsf.c new file mode 100644 index 0000000..6a9f922 --- /dev/null +++ b/code/mp3code/upsf.c @@ -0,0 +1,404 @@ +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: upsf.c,v 1.3 1999/10/19 07:13:09 elrod Exp $ +____________________________________________________________________________*/ + +/**** upsf.c *************************************************** + +Layer III + unpack scale factors + + + +******************************************************************/ + +#include +#include +#include +#include +#include "L3.h" + +//extern int iframe; + +unsigned int bitget(int n); + +/*------------------------------------------------------------*/ +static const int slen_table[16][2] = +{ + {0, 0}, {0, 1}, + {0, 2}, {0, 3}, + {3, 0}, {1, 1}, + {1, 2}, {1, 3}, + {2, 1}, {2, 2}, + {2, 3}, {3, 1}, + {3, 2}, {3, 3}, + {4, 2}, {4, 3}, +}; + +/* nr_table[size+3*is_right][block type 0,1,3 2, 2+mixed][4] */ +/* for bt=2 nr is count for group of 3 */ +static const int nr_table[6][3][4] = +{ + {{6, 5, 5, 5}, + {3, 3, 3, 3}, + {6, 3, 3, 3}}, + + {{6, 5, 7, 3}, + {3, 3, 4, 2}, + {6, 3, 4, 2}}, + + {{11, 10, 0, 0}, + {6, 6, 0, 0}, + {6, 3, 6, 0}}, /* adjusted *//* 15, 18, 0, 0, */ +/*-intensity stereo right chan--*/ + {{7, 7, 7, 0}, + {4, 4, 4, 0}, + {6, 5, 4, 0}}, + + {{6, 6, 6, 3}, + {4, 3, 3, 2}, + {6, 4, 3, 2}}, + + {{8, 8, 5, 0}, + {5, 4, 3, 0}, + {6, 6, 3, 0}}, +}; + +/*=============================================================*/ +void unpack_sf_sub_MPEG1(SCALEFACT sf[], + GR * grdat, + int scfsi, /* bit flag */ + int gr) +{ + int sfb; + int slen0, slen1; + int block_type, mixed_block_flag, scalefac_compress; + + + block_type = grdat->block_type; + mixed_block_flag = grdat->mixed_block_flag; + scalefac_compress = grdat->scalefac_compress; + + slen0 = slen_table[scalefac_compress][0]; + slen1 = slen_table[scalefac_compress][1]; + + + if (block_type == 2) + { + if (mixed_block_flag) + { /* mixed */ + for (sfb = 0; sfb < 8; sfb++) + sf[0].l[sfb] = bitget(slen0); + for (sfb = 3; sfb < 6; sfb++) + { + sf[0].s[0][sfb] = bitget(slen0); + sf[0].s[1][sfb] = bitget(slen0); + sf[0].s[2][sfb] = bitget(slen0); + } + for (sfb = 6; sfb < 12; sfb++) + { + sf[0].s[0][sfb] = bitget(slen1); + sf[0].s[1][sfb] = bitget(slen1); + sf[0].s[2][sfb] = bitget(slen1); + } + return; + } + for (sfb = 0; sfb < 6; sfb++) + { + sf[0].s[0][sfb] = bitget(slen0); + sf[0].s[1][sfb] = bitget(slen0); + sf[0].s[2][sfb] = bitget(slen0); + } + for (; sfb < 12; sfb++) + { + sf[0].s[0][sfb] = bitget(slen1); + sf[0].s[1][sfb] = bitget(slen1); + sf[0].s[2][sfb] = bitget(slen1); + } + return; + } + +/* long blocks types 0 1 3, first granule */ + if (gr == 0) + { + for (sfb = 0; sfb < 11; sfb++) + sf[0].l[sfb] = bitget(slen0); + for (; sfb < 21; sfb++) + sf[0].l[sfb] = bitget(slen1); + return; + } + +/* long blocks 0, 1, 3, second granule */ + sfb = 0; + if (scfsi & 8) + for (; sfb < 6; sfb++) + sf[0].l[sfb] = sf[-2].l[sfb]; + else + for (; sfb < 6; sfb++) + sf[0].l[sfb] = bitget(slen0); + if (scfsi & 4) + for (; sfb < 11; sfb++) + sf[0].l[sfb] = sf[-2].l[sfb]; + else + for (; sfb < 11; sfb++) + sf[0].l[sfb] = bitget(slen0); + if (scfsi & 2) + for (; sfb < 16; sfb++) + sf[0].l[sfb] = sf[-2].l[sfb]; + else + for (; sfb < 16; sfb++) + sf[0].l[sfb] = bitget(slen1); + if (scfsi & 1) + for (; sfb < 21; sfb++) + sf[0].l[sfb] = sf[-2].l[sfb]; + else + for (; sfb < 21; sfb++) + sf[0].l[sfb] = bitget(slen1); + + + + return; +} +/*=============================================================*/ +void unpack_sf_sub_MPEG2(SCALEFACT sf[], + GR * grdat, + int is_and_ch, IS_SF_INFO * sf_info) +{ + int sfb; + int slen1, slen2, slen3, slen4; + int nr1, nr2, nr3, nr4; + int i, k; + int preflag, intensity_scale; + int block_type, mixed_block_flag, scalefac_compress; + + + block_type = grdat->block_type; + mixed_block_flag = grdat->mixed_block_flag; + scalefac_compress = grdat->scalefac_compress; + + preflag = 0; + intensity_scale = 0; /* to avoid compiler warning */ + if (is_and_ch == 0) + { + if (scalefac_compress < 400) + { + slen2 = scalefac_compress >> 4; + slen1 = slen2 / 5; + slen2 = slen2 % 5; + slen4 = scalefac_compress & 15; + slen3 = slen4 >> 2; + slen4 = slen4 & 3; + k = 0; + } + else if (scalefac_compress < 500) + { + scalefac_compress -= 400; + slen2 = scalefac_compress >> 2; + slen1 = slen2 / 5; + slen2 = slen2 % 5; + slen3 = scalefac_compress & 3; + slen4 = 0; + k = 1; + } + else + { + scalefac_compress -= 500; + slen1 = scalefac_compress / 3; + slen2 = scalefac_compress % 3; + slen3 = slen4 = 0; + if (mixed_block_flag) + { + slen3 = slen2; /* adjust for long/short mix logic */ + slen2 = slen1; + } + preflag = 1; + k = 2; + } + } + else + { /* intensity stereo ch = 1 (right) */ + intensity_scale = scalefac_compress & 1; + scalefac_compress >>= 1; + if (scalefac_compress < 180) + { + slen1 = scalefac_compress / 36; + slen2 = scalefac_compress % 36; + slen3 = slen2 % 6; + slen2 = slen2 / 6; + slen4 = 0; + k = 3 + 0; + } + else if (scalefac_compress < 244) + { + scalefac_compress -= 180; + slen3 = scalefac_compress & 3; + scalefac_compress >>= 2; + slen2 = scalefac_compress & 3; + slen1 = scalefac_compress >> 2; + slen4 = 0; + k = 3 + 1; + } + else + { + scalefac_compress -= 244; + slen1 = scalefac_compress / 3; + slen2 = scalefac_compress % 3; + slen3 = slen4 = 0; + k = 3 + 2; + } + } + + i = 0; + if (block_type == 2) + i = (mixed_block_flag & 1) + 1; + nr1 = nr_table[k][i][0]; + nr2 = nr_table[k][i][1]; + nr3 = nr_table[k][i][2]; + nr4 = nr_table[k][i][3]; + + +/* return is scale factor info (for right chan is mode) */ + if (is_and_ch) + { + sf_info->nr[0] = nr1; + sf_info->nr[1] = nr2; + sf_info->nr[2] = nr3; + sf_info->slen[0] = slen1; + sf_info->slen[1] = slen2; + sf_info->slen[2] = slen3; + sf_info->intensity_scale = intensity_scale; + } + grdat->preflag = preflag; /* return preflag */ + +/*--------------------------------------*/ + if (block_type == 2) + { + if (mixed_block_flag) + { /* mixed */ + if (slen1 != 0) /* long block portion */ + for (sfb = 0; sfb < 6; sfb++) + sf[0].l[sfb] = bitget(slen1); + else + for (sfb = 0; sfb < 6; sfb++) + sf[0].l[sfb] = 0; + sfb = 3; /* start sfb for short */ + } + else + { /* all short, initial short blocks */ + sfb = 0; + if (slen1 != 0) + for (i = 0; i < nr1; i++, sfb++) + { + sf[0].s[0][sfb] = bitget(slen1); + sf[0].s[1][sfb] = bitget(slen1); + sf[0].s[2][sfb] = bitget(slen1); + } + else + for (i = 0; i < nr1; i++, sfb++) + { + sf[0].s[0][sfb] = 0; + sf[0].s[1][sfb] = 0; + sf[0].s[2][sfb] = 0; + } + } +/* remaining short blocks */ + if (slen2 != 0) + for (i = 0; i < nr2; i++, sfb++) + { + sf[0].s[0][sfb] = bitget(slen2); + sf[0].s[1][sfb] = bitget(slen2); + sf[0].s[2][sfb] = bitget(slen2); + } + else + for (i = 0; i < nr2; i++, sfb++) + { + sf[0].s[0][sfb] = 0; + sf[0].s[1][sfb] = 0; + sf[0].s[2][sfb] = 0; + } + if (slen3 != 0) + for (i = 0; i < nr3; i++, sfb++) + { + sf[0].s[0][sfb] = bitget(slen3); + sf[0].s[1][sfb] = bitget(slen3); + sf[0].s[2][sfb] = bitget(slen3); + } + else + for (i = 0; i < nr3; i++, sfb++) + { + sf[0].s[0][sfb] = 0; + sf[0].s[1][sfb] = 0; + sf[0].s[2][sfb] = 0; + } + if (slen4 != 0) + for (i = 0; i < nr4; i++, sfb++) + { + sf[0].s[0][sfb] = bitget(slen4); + sf[0].s[1][sfb] = bitget(slen4); + sf[0].s[2][sfb] = bitget(slen4); + } + else + for (i = 0; i < nr4; i++, sfb++) + { + sf[0].s[0][sfb] = 0; + sf[0].s[1][sfb] = 0; + sf[0].s[2][sfb] = 0; + } + return; + } + + +/* long blocks types 0 1 3 */ + sfb = 0; + if (slen1 != 0) + for (i = 0; i < nr1; i++, sfb++) + sf[0].l[sfb] = bitget(slen1); + else + for (i = 0; i < nr1; i++, sfb++) + sf[0].l[sfb] = 0; + + if (slen2 != 0) + for (i = 0; i < nr2; i++, sfb++) + sf[0].l[sfb] = bitget(slen2); + else + for (i = 0; i < nr2; i++, sfb++) + sf[0].l[sfb] = 0; + + if (slen3 != 0) + for (i = 0; i < nr3; i++, sfb++) + sf[0].l[sfb] = bitget(slen3); + else + for (i = 0; i < nr3; i++, sfb++) + sf[0].l[sfb] = 0; + + if (slen4 != 0) + for (i = 0; i < nr4; i++, sfb++) + sf[0].l[sfb] = bitget(slen4); + else + for (i = 0; i < nr4; i++, sfb++) + sf[0].l[sfb] = 0; + + +} +/*-------------------------------------------------*/ diff --git a/code/mp3code/wavep.c b/code/mp3code/wavep.c new file mode 100644 index 0000000..55578a4 --- /dev/null +++ b/code/mp3code/wavep.c @@ -0,0 +1,96 @@ +#pragma warning(disable:4206) // nonstandard extension used : translation unit is empty +#if 0 +/*____________________________________________________________________________ + + FreeAmp - The Free MP3 Player + + MP3 Decoder originally Copyright (C) 1995-1997 Xing Technology + Corp. http://www.xingtech.com + + Portions Copyright (C) 1998-1999 EMusic.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id: wavep.c,v 1.3 1999/10/19 07:13:09 elrod Exp $ +____________________________________________________________________________*/ + +/*---- wavep.c -------------------------------------------- + +WAVE FILE HEADER ROUTINES +with conditional pcm conversion to MS wave format +portable version + +-----------------------------------------------------------*/ +#include +#include +#include +#include +#ifdef WIN32 +#include +#else +#include +#endif +#include "port.h" + +typedef struct +{ + unsigned char riff[4]; + unsigned char size[4]; + unsigned char wave[4]; + unsigned char fmt[4]; + unsigned char fmtsize[4]; + unsigned char tag[2]; + unsigned char nChannels[2]; + unsigned char nSamplesPerSec[4]; + unsigned char nAvgBytesPerSec[4]; + unsigned char nBlockAlign[2]; + unsigned char nBitsPerSample[2]; + unsigned char data[4]; + unsigned char pcm_bytes[4]; +} +BYTE_WAVE; + +static const BYTE_WAVE wave = +{ + "RIFF", + {(sizeof(BYTE_WAVE) - 8), 0, 0, 0}, + "WAVE", + "fmt ", + {16, 0, 0, 0}, + {1, 0}, + {1, 0}, + {34, 86, 0, 0}, /* 86 * 256 + 34 = 22050 */ + {172, 68, 0, 0}, /* 172 * 256 + 68 = 44100 */ + {2, 0}, + {16, 0}, + "data", + {0, 0, 0, 0} +}; + +/*---------------------------------------------------------------- + pcm conversion to wave format + + This conversion code required for big endian machine, or, + if sizeof(short) != 16 bits. + Conversion routines may be used on any machine, but if + not required, the do nothing macros in port.h can be used instead + to reduce overhead. + +-----------------------------------------------------------------*/ +#ifndef LITTLE_SHORT16 +#include "wcvt.c" +#endif +/*-----------------------------------------------*/ +#endif diff --git a/code/null/mac_net.c b/code/null/mac_net.c new file mode 100644 index 0000000..14f1d4f --- /dev/null +++ b/code/null/mac_net.c @@ -0,0 +1,44 @@ + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" + +/* +============= +NET_StringToAdr + +localhost +idnewt +idnewt:28000 +192.246.40.70 +192.246.40.70:28000 +============= +*/ +qboolean NET_StringToAdr (char *s, netadr_t *a) +{ + if (!strcmp (s, "localhost")) { + memset (a, 0, sizeof(*a)); + a->type = NA_LOOPBACK; + return true; + } + + return false; +} + +/* +================== +Sys_SendPacket +================== +*/ +void Sys_SendPacket( int length, void *data, netadr_t to ) { +} + +/* +================== +Sys_GetPacket + +Never called by the game logic, just the system event queing +================== +*/ +qboolean Sys_GetPacket ( netadr_t *net_from, msg_t *net_message ) { + return false; +} diff --git a/code/null/null_glimp.c b/code/null/null_glimp.c new file mode 100644 index 0000000..e891599 --- /dev/null +++ b/code/null/null_glimp.c @@ -0,0 +1,39 @@ +#include "../renderer/tr_local.h" + + +qboolean ( * qwglSwapIntervalEXT)( int interval ); +void ( * qglMultiTexCoord2fARB )( enum texture, float s, float t ); +void ( * qglActiveTextureARB )( enum texture ); +void ( * qglClientActiveTextureARB )( enum texture ); + + +void ( * qglLockArraysEXT)( int, int); +void ( * qglUnlockArraysEXT) ( void ); + + +void GLimp_EndFrame( void ) { +} + +int GLimp_Init( void ) +{ +} + +void GLimp_Shutdown( void ) { +} + +rserr_t GLimp_SetMode( const char *drivername, int *pWidth, int *pHeight, int mode, qboolean fullscreen ) { +} + + +void GLimp_EnableLogging( qboolean enable ) { +} + +void GLimp_LogComment( char *comment ) { +} + +qboolean QGL_Init( const char *dllname ) { + return true; +} + +void QGL_Shutdown( void ) { +} diff --git a/code/null/null_main.c b/code/null/null_main.c new file mode 100644 index 0000000..63ec2f5 --- /dev/null +++ b/code/null/null_main.c @@ -0,0 +1,94 @@ +// sys_null.h -- null system driver to aid porting efforts + +#include "../qcommon/qcommon.h" +#include "errno.h" + +int sys_curtime; + + +//=================================================================== + +void Sys_BeginStreamedFile( fileHandle_t f, int readAhead ) { +} + +void Sys_EndStreamedFile( fileHandle_t f ) { +} + +int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ) { + return FS_Read( buffer, size, count, f ); +} + +void Sys_StreamSeek( fileHandle_t f, int offset, int origin ) { + FS_Seek( f, offset, origin ); +} + + +//=================================================================== + + +void Sys_mkdir (char *path) { +} + +void Sys_Error (char *error, ...) { + va_list argptr; + + printf ("Sys_Error: "); + va_start (argptr,error); + vprintf (error,argptr); + va_end (argptr); + printf ("\n"); + + exit (1); +} + +void Sys_Quit (void) { + exit (0); +} + +void Sys_UnloadGame (void) { +} + +void *Sys_GetGameAPI (void *parms) { + return NULL; +} + +char *Sys_GetClipboardData( void ) { + return NULL; +} + +int Sys_Milliseconds (void) { + return 0; +} + +void Sys_Mkdir (char *path) { +} + +char *Sys_FindFirst (char *path, unsigned musthave, unsigned canthave) { + return NULL; +} + +char *Sys_FindNext (unsigned musthave, unsigned canthave) { + return NULL; +} + +void Sys_FindClose (void) { +} + +void Sys_Init (void) { +} + + +void Sys_EarlyOutput( char *string ) { + printf( "%s", string ); +} + + +void main (int argc, char **argv) { + Com_Init (argc, argv); + + while (1) { + Com_Frame( ); + } +} + + diff --git a/code/null/null_net.c b/code/null/null_net.c new file mode 100644 index 0000000..0df5d9c --- /dev/null +++ b/code/null/null_net.c @@ -0,0 +1,43 @@ + +#include "../qcommon/qcommon.h" + +/* +============= +NET_StringToAdr + +localhost +idnewt +idnewt:28000 +192.246.40.70 +192.246.40.70:28000 +============= +*/ +qboolean NET_StringToAdr (char *s, netadr_t *a) +{ + if (!strcmp (s, "localhost")) { + memset (a, 0, sizeof(*a)); + a->type = NA_LOOPBACK; + return true; + } + + return false; +} + +/* +================== +Sys_SendPacket +================== +*/ +void Sys_SendPacket( int length, void *data, netadr_t to ) { +} + +/* +================== +Sys_GetPacket + +Never called by the game logic, just the system event queing +================== +*/ +qboolean Sys_GetPacket ( netadr_t *net_from, msg_t *net_message ) { + return false; +} diff --git a/code/null/null_snddma.c b/code/null/null_snddma.c new file mode 100644 index 0000000..ec7fd0a --- /dev/null +++ b/code/null/null_snddma.c @@ -0,0 +1,27 @@ + +// snddma_null.c +// all other sound mixing is portable + +#include "../client/client.h" + +qboolean SNDDMA_Init(void) +{ + return qfalse; +} + +int SNDDMA_GetDMAPos(void) +{ + return 0; +} + +void SNDDMA_Shutdown(void) +{ +} + +void SNDDMA_BeginPainting (void) +{ +} + +void SNDDMA_Submit(void) +{ +} diff --git a/code/png/png.cpp b/code/png/png.cpp new file mode 100644 index 0000000..11b489d --- /dev/null +++ b/code/png/png.cpp @@ -0,0 +1,783 @@ +// Generic PNG file loading code + +// leave this as first line for PCH reasons... +// +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" + +#include "../zlib32/zip.h" +#include "png.h" +//#include "../qcommon/memory.h" + +// Error returns + +#define PNG_ERROR_OK 0 +#define PNG_ERROR_DECOMP 1 +#define PNG_ERROR_COMP 2 +#define PNG_ERROR_MEMORY 3 +#define PNG_ERROR_NOSIG 4 +#define PNG_ERROR_TOO_SMALL 5 +#define PNG_ERROR_WNP2 6 +#define PNG_ERROR_HNP2 7 +#define PNG_ERROR_NOT_TC 8 +#define PNG_ERROR_INV_FIL 9 +#define PNG_ERROR_FAILED_CRC 10 +#define PNG_ERROR_CREATE_FAIL 11 +#define PNG_ERROR_WRITE 12 +#define PNG_ERROR_NOT_PALETTE 13 +#define PNG_ERROR_NOT8BIT 14 +#define PNG_ERROR_TOO_LARGE 15 + +static int png_error = PNG_ERROR_OK; + +static const byte png_signature[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; +static const char png_copyright[] = "Copyright\0Raven Software Inc. 2001"; +static const char *png_errors[] = +{ + "OK.", + "Error decompressing image data.", + "Error compressing image data.", + "Error allocating memory.", + "PNG signature not found.", + "Image is too small to load.", + "Width is not a power of two.", + "Height is not a power of two.", + "Image is not 24 or 32 bit.", + "Invalid filter or compression type.", + "Failed CRC check.", + "Could not create file.", + "Error writing to file.", + "Image is not indexed colour.", + "Image does not have 8 bits per sample.", + "Image is too large", +}; + +// Gets the error string for a failed PNG operation + +const char *PNG_GetError(void) +{ + return(png_errors[png_error]); +} + +// Create a header chunk + +void PNG_CreateHeader(png_ihdr_t *header, int width, int height, int bytedepth) +{ + header->width = BigLong(width); + header->height = BigLong(height); + header->bitdepth = 8; + + if(bytedepth == 3) + { + header->colortype = 2; + } + if(bytedepth == 4) + { + header->colortype = 6; + } + header->compression = 0; + header->filter = 0; + header->interlace = 0; +} + +// Processes the header chunk and checks to see if all the data is valid + +bool PNG_HandleIHDR(const byte *data, png_image_t *image) +{ + png_ihdr_t *ihdr = (png_ihdr_t *)data; + + image->width = BigLong(ihdr->width); + image->height = BigLong(ihdr->height); + + // Make sure image is a reasonable size + if((image->width < 2) || (image->height < 2)) + { + png_error = PNG_ERROR_TOO_SMALL; + return(false); + } + if(image->width > MAX_PNG_WIDTH) + { + png_error = PNG_ERROR_TOO_LARGE; + return(false); + } + if(ihdr->bitdepth != 8) + { + png_error = PNG_ERROR_NOT8BIT; + return(false); + } + // Check for non power of two size (but not for data files) + if(image->isimage) + { + if(image->width & (image->width - 1)) + { + png_error = PNG_ERROR_WNP2; + return(false); + } + if(image->height & (image->height - 1)) + { + png_error = PNG_ERROR_HNP2; + return(false); + } + } + // Make sure we have a 24 or 32 bit image (for images) + if(image->isimage) + { + if((ihdr->colortype != 2) && (ihdr->colortype != 6)) + { + png_error = PNG_ERROR_NOT_TC; + return(false); + } + } + // Make sure we have an 8 bit grayscale image for data files + if(!image->isimage) + { + if(ihdr->colortype && (ihdr->colortype != 3)) + { + png_error = PNG_ERROR_NOT_PALETTE; + return(false); + } + } + // Make sure we aren't using any wacky compression or filter algos + if(ihdr->compression || ihdr->filter) + { + png_error = PNG_ERROR_INV_FIL; + return(false); + } + // Extract the data we need + if(!ihdr->colortype || (ihdr->colortype == 3)) + { + image->bytedepth = 1; + } + if(ihdr->colortype == 2) + { + image->bytedepth = 3; + } + if(ihdr->colortype == 6) + { + image->bytedepth = 4; + } + return(true); +} + +// Filter a row of data + +void PNG_Filter(byte *out, byte filter, const byte *in, const byte *lastline, ulong rowbytes, ulong bpp) +{ + ulong i; + + switch(filter) + { + case PNG_FILTER_VALUE_NONE: + memcpy(out, in, rowbytes); + break; + case PNG_FILTER_VALUE_SUB: + for(i = 0; i < bpp; i++) + { + *out++ = *in++; + } + for(i = bpp; i < rowbytes; i++) + { + *out++ = *in - *(in - bpp); + in++; + } + break; + case PNG_FILTER_VALUE_UP: + for(i = 0; i < rowbytes; i++) + { + if(lastline) + { + *out++ = *in++ - *lastline++; + } + else + { + *out++ = *in++; + } + } + break; + case PNG_FILTER_VALUE_AVG: + for(i = 0; i < bpp; i++) + { + if(lastline) + { + *out++ = *in++ - (*lastline++ >> 1); + } + else + { + *out++ = *in++; + } + } + for(i = bpp; i < rowbytes; i++) + { + if(lastline) + { + *out++ = *in - ((*lastline++ + *(in - bpp)) >> 1); + } + else + { + *out++ = *in - (*(in - bpp) >> 1); + } + in++; + } + break; + case PNG_FILTER_VALUE_PAETH: + int a, b, c; + int pa, pb, pc, p; + + for(i = 0; i < bpp; i++) + { + if(lastline) + { + *out++ = *in++ - *lastline++; + } + else + { + *out++ = *in++; + } + } + for(i = bpp; i < rowbytes; i++) + { + a = *(in - bpp); + c = 0; + b = 0; + if(lastline) + { + c = *(lastline - bpp); + b = *lastline++; + } + + p = b - c; + pc = a - c; + + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; + + p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; + + *out++ = *in++ - p; + } + break; + } +} + +// Unfilters a row of data + +void PNG_Unfilter(byte *out, byte filter, const byte *lastline, ulong rowbytes, ulong bpp) +{ + ulong i; + + switch(filter) + { + case PNG_FILTER_VALUE_NONE: + break; + case PNG_FILTER_VALUE_SUB: + out += bpp; + for(i = bpp; i < rowbytes; i++) + { + *out += *(out - bpp); + out++; + } + break; + case PNG_FILTER_VALUE_UP: + for(i = 0; i < rowbytes; i++) + { + if(lastline) + { + *out += *lastline++; + } + out++; + } + break; + case PNG_FILTER_VALUE_AVG: + for(i = 0; i < bpp; i++) + { + if(lastline) + { + *out += *lastline++ >> 1; + } + out++; + } + for(i = bpp; i < rowbytes; i++) + { + if(lastline) + { + *out += (*lastline++ + *(out - bpp)) >> 1; + } + else + { + *out += *(out - bpp) >> 1; + } + out++; + } + break; + case PNG_FILTER_VALUE_PAETH: + int a, b, c; + int pa, pb, pc, p; + + for(i = 0; i < bpp; i++) + { + if(lastline) + { + *out += *lastline++; + } + out++; + } + for(i = bpp; i < rowbytes; i++) + { + a = *(out - bpp); + c = 0; + b = 0; + if(lastline) + { + c = *(lastline - bpp); + b = *lastline++; + } + p = b - c; + pc = a - c; + + pa = p < 0 ? -p : p; + pb = pc < 0 ? -pc : pc; + pc = (p + pc) < 0 ? -(p + pc) : p + pc; + + p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c; + + *out++ += p; + } + break; + default: + break; + } +} + +// Pack up the image data line by line + +bool PNG_Pack(byte *out, ulong *size, ulong maxsize, byte *data, int width, int height, int bytedepth) +{ + z_stream zdata; + ulong rowbytes; + ulong y; + const byte *lastline, *source; + // Storage for filter type and filtered row + byte workline[(MAX_PNG_WIDTH * MAX_PNG_DEPTH) + 1]; + + // Number of bytes per row + rowbytes = width * bytedepth; + + memset(&zdata, 0, sizeof(z_stream)); + if(deflateInit(&zdata, Z_FAST_COMPRESSION_HIGH) != Z_OK) + { + png_error = PNG_ERROR_COMP; + return(false); + } + + zdata.next_out = out; + zdata.avail_out = maxsize; + + lastline = NULL; + source = data + ((height - 1) * rowbytes); + for(y = 0; y < height; y++) + { + // Refilter using the most compressable filter algo + // Assume paeth to speed things up + workline[0] = (byte)PNG_FILTER_VALUE_PAETH; + PNG_Filter(workline + 1, (byte)PNG_FILTER_VALUE_PAETH, source, lastline, rowbytes, bytedepth); + + zdata.next_in = workline; + zdata.avail_in = rowbytes + 1; + if(deflate(&zdata, Z_SYNC_FLUSH) != Z_OK) + { + deflateEnd(&zdata); + png_error = PNG_ERROR_COMP; + return(false); + } + lastline = source; + source -= rowbytes; + } + if(deflate(&zdata, Z_FINISH) != Z_STREAM_END) + { + png_error = PNG_ERROR_COMP; + return(false); + } + *size = zdata.total_out; + deflateEnd(&zdata); + return(true); +} + +// Unpack the image data, line by line + +bool PNG_Unpack(const byte *data, const ulong datasize, png_image_t *image) +{ + ulong rowbytes, zerror, y; + byte filter; + z_stream zdata; + byte *lastline, *out; + +// MD_PushTag(TAG_ZIP_TEMP); + + memset(&zdata, 0, sizeof(z_stream)); + if(inflateInit(&zdata, Z_SYNC_FLUSH) != Z_OK) + { + png_error = PNG_ERROR_DECOMP; +// MD_PopTag(); + return(false); + } + zdata.next_in = (byte *)data; + zdata.avail_in = datasize; + + rowbytes = image->width * image->bytedepth; + + lastline = NULL; + out = image->data; + for(y = 0; y < image->height; y++) + { + // Inflate a row of data + zdata.next_out = &filter; + zdata.avail_out = 1; + if(inflate(&zdata) != Z_OK) + { + inflateEnd(&zdata); + png_error = PNG_ERROR_DECOMP; +// MD_PopTag(); + return(false); + } + zdata.next_out = out; + zdata.avail_out = rowbytes; + zerror = inflate(&zdata); + if((zerror != Z_OK) && (zerror != Z_STREAM_END)) + { + inflateEnd(&zdata); + png_error = PNG_ERROR_DECOMP; +// MD_PopTag(); + return(false); + } + + // Unfilter a row of data + PNG_Unfilter(out, filter, lastline, rowbytes, image->bytedepth); + + lastline = out; + out += rowbytes; + } + inflateEnd(&zdata); +// MD_PopTag(); + return(true); +} + +// Scan through all chunks and process each one + +bool PNG_Load(const byte *data, ulong datasize, png_image_t *image) +{ + bool moredata; + const byte *next; + byte *workspace, *work; + ulong length, type, crc, totallength; + + png_error = PNG_ERROR_OK; + + if(memcmp(data, png_signature, sizeof(png_signature))) + { + png_error = PNG_ERROR_NOSIG; + return(false); + } + data += sizeof(png_signature); + + workspace = (byte *)Z_Malloc(datasize, TAG_TEMP_PNG, qfalse); + work = workspace; + totallength = 0; + + moredata = true; + while(moredata) + { + length = BigLong(*(ulong *)data); + data += sizeof(ulong); + + type = BigLong(*(ulong *)data); + const byte *crcbase = data; + data += sizeof(ulong); + + // CRC checksum location + next = data + length + sizeof(ulong); + + // CRC checksum includes header field + crc = crc32(0, crcbase, length + sizeof(ulong)); + if(crc != (ulong)BigLong(*(ulong *)(next - 4))) + { + if(image->data) + { + Z_Free(image->data); + image->data = NULL; + } + Z_Free(workspace); + png_error = PNG_ERROR_FAILED_CRC; + return(false); + } + switch(type) + { + case PNG_IHDR: + if(!PNG_HandleIHDR(data, image)) + { + Z_Free(workspace); + return(false); + } + image->data = (byte *)Z_Malloc(image->width * image->height * image->bytedepth, TAG_TEMP_PNG, qfalse); + break; + case PNG_IDAT: + // Need to copy all the various IDAT chunks into one big one + // Everything but 3dsmax has one IDAT chunk + memcpy(work, data, length); + work += length; + totallength += length; + break; + case PNG_IEND: + if(!PNG_Unpack(workspace, totallength, image)) + { + Z_Free(workspace); + Z_Free(image->data); + image->data = NULL; + return(false); + } + moredata = false; + break; + default: + break; + } + data = next; + } + Z_Free(workspace); + return(true); +} + +// Outputs a crc'd chunk of PNG data + +bool PNG_OutputChunk(fileHandle_t fp, ulong type, byte *data, ulong size) +{ + ulong crc, little, outcount; + + // Output a standard PNG chunk - length, type, data, crc + little = BigLong(size); + outcount = FS_Write(&little, sizeof(little), fp); + + little = BigLong(type); + crc = crc32(0, (byte *)&little, sizeof(little)); + outcount += FS_Write(&little, sizeof(little), fp); + + if(size) + { + crc = crc32(crc, data, size); + outcount += FS_Write(data, size, fp); + } + + little = BigLong(crc); + outcount += FS_Write(&little, sizeof(little), fp); + + if(outcount != (size + 12)) + { + png_error = PNG_ERROR_WRITE; + return(false); + } + return(true); +} + +// Saves a PNG format compressed image + +bool PNG_Save(const char *name, byte *data, int width, int height, int bytedepth) +{ + byte *work; + fileHandle_t fp; + int maxsize; + ulong size, outcount; + png_ihdr_t png_header; + + png_error = PNG_ERROR_OK; + + // Create the file + fp = FS_FOpenFileWrite(name); + if(!fp) + { + png_error = PNG_ERROR_CREATE_FAIL; + return(false); + } + // Write out the PNG signature + outcount = FS_Write(png_signature, sizeof(png_signature), fp); + if(outcount != sizeof(png_signature)) + { + FS_FCloseFile(fp); + png_error = PNG_ERROR_WRITE; + return(false); + } + // Create and output a valid header + PNG_CreateHeader(&png_header, width, height, bytedepth); + if(!PNG_OutputChunk(fp, PNG_IHDR, (byte *)&png_header, sizeof(png_header))) + { + FS_FCloseFile(fp); + return(false); + } + // Create and output the copyright info + if(!PNG_OutputChunk(fp, PNG_tEXt, (byte *)png_copyright, sizeof(png_copyright))) + { + FS_FCloseFile(fp); + return(false); + } + // Max size of compressed image (source size + 0.1% + 12) + maxsize = (width * height * bytedepth) + 4096; + work = (byte *)Z_Malloc(maxsize, TAG_TEMP_PNG, qtrue); // fixme: optimise to qfalse sometime - ok? + + // Pack up the image data + if(!PNG_Pack(work, &size, maxsize, data, width, height, bytedepth)) + { + Z_Free(work); + FS_FCloseFile(fp); + return(false); + } + // Write out the compressed image data + if(!PNG_OutputChunk(fp, PNG_IDAT, (byte *)work, size)) + { + Z_Free(work); + FS_FCloseFile(fp); + return(false); + } + Z_Free(work); + // Output terminating chunk + if(!PNG_OutputChunk(fp, PNG_IEND, NULL, 0)) + { + FS_FCloseFile(fp); + return(false); + } + FS_FCloseFile(fp); + return(true); +} + +/* +============= +PNG_ConvertTo32 +============= +*/ + +void PNG_ConvertTo32(png_image_t *image) +{ + byte *temp; + byte *old, *old2; + ulong i; + + temp = (byte *)Z_Malloc(image->width * image->height * 4, TAG_TEMP_PNG, qtrue); + old = image->data; + old2 = old; + image->data = temp; + image->bytedepth = 4; + + for(i = 0; i < image->width * image->height; i++) + { + *temp++ = *old++; + *temp++ = *old++; + *temp++ = *old++; + *temp++ = 0xff; + } + Z_Free(old2); +} + +/* +============= +LoadPNG32 +============= +*/ +bool LoadPNG32 (char *name, byte **pixels, int *width, int *height, int *bytedepth) +{ + byte *buffer; + byte **bufferptr = &buffer; + int nLen; + png_image_t png_image; + + if(!pixels) + { + bufferptr = NULL; + } + nLen = FS_ReadFile ( ( char * ) name, (void **)bufferptr); + if (nLen == -1) + { + if(pixels) + { + *pixels = NULL; + } + return(false); + } + if(!pixels) + { + return(true); + } + *pixels = NULL; + png_image.isimage = true; + if(!PNG_Load(buffer, nLen, &png_image)) + { + Com_Printf ("Error parsing %s: %s\n", name, PNG_GetError()); + return(false); + } + if(png_image.bytedepth != 4) + { + PNG_ConvertTo32(&png_image); + } + *pixels = png_image.data; + if(width) + { + *width = png_image.width; + } + if(height) + { + *height = png_image.height; + } + if(bytedepth) + { + *bytedepth = png_image.bytedepth; + } + FS_FreeFile(buffer); + return(true); +} + +/* +============= +LoadPNG8 +============= +*/ +bool LoadPNG8 (char *name, byte **pixels, int *width, int *height) +{ + byte *buffer; + byte **bufferptr = &buffer; + int nLen; + png_image_t png_image; + + if(!pixels) + { + bufferptr = NULL; + } + nLen = FS_ReadFile ( ( char * ) name, (void **)bufferptr); + if (nLen == -1) + { + if(pixels) + { + *pixels = NULL; + } + return(false); + } + if(!pixels) + { + return(true); + } + *pixels = NULL; + png_image.isimage = false; + if(!PNG_Load(buffer, nLen, &png_image)) + { + Com_Printf ("Error parsing %s: %s\n", name, PNG_GetError()); + return(false); + } + *pixels = png_image.data; + if(width) + { + *width = png_image.width; + } + if(height) + { + *height = png_image.height; + } + FS_FreeFile(buffer); + return(true); +} + +// end \ No newline at end of file diff --git a/code/png/png.h b/code/png/png.h new file mode 100644 index 0000000..3ab0ec2 --- /dev/null +++ b/code/png/png.h @@ -0,0 +1,73 @@ +// Known chunk types + +#define PNG_IHDR 'IHDR' +#define PNG_IDAT 'IDAT' +#define PNG_IEND 'IEND' +#define PNG_tEXt 'tEXt' + +#define PNG_PLTE 'PLTE' +#define PNG_bKGD 'bKGD' +#define PNG_cHRM 'cHRM' +#define PNG_gAMA 'gAMA' +#define PNG_hIST 'hIST' +#define PNG_iCCP 'iCCP' +#define PNG_iTXt 'iTXt' +#define PNG_oFFs 'oFFs' +#define PNG_pCAL 'pCAL' +#define PNG_sCAL 'sCAL' +#define PNG_pHYs 'pHYs' +#define PNG_sBIT 'sBIT' +#define PNG_sPLT 'sPLT' +#define PNG_sRGB 'sRGB' +#define PNG_tIME 'tIME' +#define PNG_tRNS 'tRNS' +#define PNG_zTXt 'zTXt' + +// Filter values + +#define PNG_FILTER_VALUE_NONE 0 +#define PNG_FILTER_VALUE_SUB 1 +#define PNG_FILTER_VALUE_UP 2 +#define PNG_FILTER_VALUE_AVG 3 +#define PNG_FILTER_VALUE_PAETH 4 +#define PNG_FILTER_NUM 5 + +// Common defines and typedefs + +#define MAX_PNG_WIDTH 4096 +#define MAX_PNG_DEPTH 4 + +typedef unsigned char byte; +typedef unsigned short word; +typedef unsigned long ulong; + +#pragma pack(push) +#pragma pack(1) + +typedef struct png_ihdr_s +{ + ulong width; + ulong height; + byte bitdepth; // Bits per sample (not per pixel) + byte colortype; // bit 0 - palette; bit 1 - RGB; bit 2 - alpha channel + byte compression; // 0 for zip - error otherwise + byte filter; // 0 for adaptive with the 5 basic types - error otherwise + byte interlace; // 0 for no interlace - 1 for Adam7 interlace +} png_ihdr_t; + +#pragma pack(pop) + +typedef struct png_image_s +{ + byte *data; + ulong width; + ulong height; + ulong bytedepth; + bool isimage; +} png_image_t; + +bool LoadPNG32 (char *name, byte **pixels, int *width, int *height, int *bytedepth); +bool LoadPNG8 (char *name, byte **pixels, int *width, int *height); +bool PNG_Save(const char *name, byte *data, int width, int height, int bytedepth); + +// end \ No newline at end of file diff --git a/code/qcommon/MiniHeap.h b/code/qcommon/MiniHeap.h new file mode 100644 index 0000000..4bea603 --- /dev/null +++ b/code/qcommon/MiniHeap.h @@ -0,0 +1,67 @@ +#if !defined(MINIHEAP_H_INC) +#define MINIHEAP_H_INC + + +class CMiniHeap +{ + char *mHeap; + char *mCurrentHeap; + int mSize; +#if _DEBUG + int mMaxAlloc; +#endif +public: + +// reset the heap back to the start +void ResetHeap() +{ +#if _DEBUG + if ((int)mCurrentHeap - (int)mHeap>mMaxAlloc) + { + mMaxAlloc=(int)mCurrentHeap - (int)mHeap; + } +#endif + mCurrentHeap = mHeap; +} + +// initialise the heap +CMiniHeap(int size) +{ + mHeap = (char *)Z_Malloc(size, TAG_GHOUL2, qtrue); + mSize = size; +#if _DEBUG + mMaxAlloc=0; +#endif + if (mHeap) + { + ResetHeap(); + } +} + +// free up the heap +~CMiniHeap() +{ + if (mHeap) + { + // the quake heap will be long gone, no need to free it Z_Free(mHeap); + } +} + +// give me some space from the heap please +char *MiniHeapAlloc(int size) +{ + if (size < (mSize - ((int)mCurrentHeap - (int)mHeap))) + { + char *tempAddress = mCurrentHeap; + mCurrentHeap += size; + return tempAddress; + } + return NULL; +} + +}; + +extern CMiniHeap *G2VertSpaceServer; + + +#endif //MINIHEAP_H_INC diff --git a/code/qcommon/chash.h b/code/qcommon/chash.h new file mode 100644 index 0000000..20076d8 --- /dev/null +++ b/code/qcommon/chash.h @@ -0,0 +1,162 @@ +// Notes +// Make sure extension is stripped if it needs to be + +// Template class must have +// 1. A GetName() accessor - a null terminated string case insensitive +// 2. A Destroy() function - normally "delete this" +// 3. SetNext(T *) and T *GetNext() functions + +#define HASH_SIZE 1024 + +template + +class CHash +{ +private: + T *mHashTable[TSize]; + T *mNext; + int mCount; + T *mPrevious; // Internal work variable + long mHash; // Internal work variable + + // Creates the hash value and sets the mHash member + void CreateHash(const char *key) + { + int i = 0; + char letter; + + mHash = 0; + letter = *key++; + while (letter) + { + mHash += (long)(letter) * (i + 119); + + i++; + letter = *key++; + } + mHash &= TSize - 1; + } +public: + // Constructor + CHash(void) + { + memset(mHashTable, NULL, sizeof(mHashTable)); + mNext = NULL; + mCount = 0; + mPrevious = NULL; + mHash = 0; + } + // Destructor + ~CHash(void) + { +#ifdef _DEBUG +// Com_OPrintf("Shutting down %s hash table .....", typeid(T).name()); +#endif + clear(); +#ifdef _DEBUG +// Com_OPrintf(" done\n"); +#endif + } + // Returns the total number of entries in the hash table + int count(void) const { return(mCount); } + + // Inserts an item into the hash table + void insert(T *item) + { + CreateHash(item->GetName()); + item->SetNext(mHashTable[mHash]); + mHashTable[mHash] = item; + mCount++; + } + // Finds an item in the hash table (sets the mPrevious member) + T *find(const char *key) + { + CreateHash(key); + T *item = mHashTable[mHash]; + mPrevious = NULL; + while(item) + { + mNext = item->GetNext(); + if(!TCompare(item->GetName(), key)) + { + return(item); + } + mPrevious = item; + item = mNext; + } + return(NULL); + } + // Remove item from the hash table referenced by key + bool remove(const char *key) + { + T *item = find(key); + if(item) + { + T *next = item->GetNext(); + if(mPrevious) + { + mPrevious->SetNext(next); + } + else + { + mHashTable[mHash] = next; + } + item->Destroy(); + mCount--; + return(true); + } + return(false); + } + // Remove item from hash referenced by item + bool remove(T *item) + { + return(remove(item->GetName())); + } + // Returns the first valid entry + T *head(void) + { + mHash = -1; + mNext = NULL; + return(next()); + } + // Returns the next entry in the hash table + T *next(void) + { + T *item; + + assert(mHash < TSize); + + if(mNext) + { + item = mNext; + mNext = item->GetNext(); + return(item); + } + mHash++; + + for( ; mHash < TSize; mHash++) + { + item = mHashTable[mHash]; + if(item) + { + mNext = item->GetNext(); + return(item); + } + } + return(NULL); + } + // Destroy all entries in the hash table + void clear(void) + { + T *item = head(); + while(item) + { + remove(item); + item = next(); + } + } + // Override the [] operator + T *operator[](const char *key) { return(find(key)); } +}; + +// end \ No newline at end of file diff --git a/code/qcommon/cm_draw.cpp b/code/qcommon/cm_draw.cpp new file mode 100644 index 0000000..f291207 --- /dev/null +++ b/code/qcommon/cm_draw.cpp @@ -0,0 +1,1488 @@ +/////////////////////////////////////////////////////////////////////////////// +// CDraw32 Class Implementation +// +// Basic drawing routines for 32-bit buffer +/////////////////////////////////////////////////////////////////////////////// +#include "../server/exe_headers.h" + +#include "cm_local.h" +#include "cm_draw.h" + + +///////////// statics for CDraw32 ////////////////////////////////// +// Used by all drawing routines as the "current" drawing context +CPixel32* CDraw32::buffer = NULL; // pointer to 32-bit deep pixel buffer +long CDraw32::buf_width=0; // width of buffer in pixels +long CDraw32::buf_height=0; // height of buffer in pixels +long CDraw32::stride = 0; // stride in pixels +long CDraw32::clip_min_x=0; // clip bounds +long CDraw32::clip_min_y=0; // clip bounds +long CDraw32::clip_max_x=0; // clip bounds +long CDraw32::clip_max_y=0; // clip bounds +long* CDraw32::row_off = NULL; // Table for quick Y calculations + +CDraw32::CDraw32() +//USE: constructor +{ +} + +CDraw32::~CDraw32() +//USE: Destructor +{ +} + +int imgKernel[5][5] = +{ + {-1,-1,-1,-1, 0}, + {-1,-1,-1, 0, 1}, + {-1,-1, 0, 1, 1}, + {-1, 0, 1, 1, 1}, + { 0, 1, 1, 1, 1} +}; + +const int KWIDTH = 2; + +void CDraw32::Emboss(long dstX, long dstY, long width, long height, + CPixel32* clrImage, long clrX, long clrY, long clrStride) +{ + CPixel32 *dst; + CPixel32 *clr; + int x,y,i,j; + int dstNextLine; + int clrNextLine; + + assert(buffer != NULL); + + BlitClip(dstX, dstY, width, height, clrX, clrY); + + if (width < 1 || height < 1) + return; + + dst = &buffer[PIXPOS(dstX,dstY,stride)]; + clr = &clrImage[PIXPOS(clrX,clrY,clrStride)]; + + dstNextLine = (stride - width); + clrNextLine = (clrStride - width); + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + int accum = 0; + for (j = -KWIDTH; j<=KWIDTH; j++) + for (i = -KWIDTH; i<=KWIDTH; i++) + { + int xk = CLAMP(x + i, clrX, clrX+width-1); + int yk = CLAMP(y + j, clrY, clrY+height-1); + accum += clrImage[PIXPOS(xk,yk,clrStride)].a * imgKernel[j+KWIDTH][i+KWIDTH]; + } + *dst = LIGHT_PIX(*clr, accum); + dst->a = 255; + ++dst; + ++clr; + } + dst += dstNextLine; + clr += clrNextLine; + } + + +} + +bool CDraw32::SetBufferSize(long width,long height,long stride_len) +//USE: setup for a particular size drawing buffer +// (do not re-setup if buffer size has not changed) +//IN: width,height - size of buffer +// stride_len - distance to next line +//OUT: true if everything goes OK, otherwise false +{ + long i; + + assert(width!=0); + assert(height!=0); + assert(stride_len!=0); + + if (buf_width != width || buf_height != height || + stride_len != stride) + { // need to re-create row_off table + buf_width = width; + buf_height = height; + stride = stride_len; + + if (row_off) + delete [] row_off; + + // row offsets used for quick pixel address calcs + row_off = new long[height]; + + assert(row_off != NULL); + if (row_off == NULL) + return false; + + // table for quick pixel lookups + for (i=0; i=0); + assert(end=0); + assert(enda = alpha; + ++dest; + } + dest += next_line; + } +} + +#define LEFT 1 // code bits +#define RIGHT 2 +#define TOP 4 +#define BOTTOM 8 + +static long code(long x,long y) +//USE: determines where a point is in relation to a bounding box +//IN: x,y - coordinate pair +//OUT: clipping code compaired to global clip context +{ + long c; + + c = 0; + if (x < CDraw32::clip_min_x) c |= LEFT; + if (x > CDraw32::clip_max_x) c |= RIGHT; + if (y < CDraw32::clip_min_y) c |= BOTTOM; + if (y > CDraw32::clip_max_y) c |= TOP; + + return c; +} + +bool CDraw32::ClipLine(long& x1, long& y1, long& x2, long& y2) +//USE: clip a line from (x1,y1) to (x2,y2) to clip bounds +//IN: (x1,y1)-(x2,y2) line +//OUT: return true if something left to draw, otherwise false +{ + long c1,c2,c,x,y,f; + x = x1; + y = y1; + + c1 = code(x1,y1); // find where first pt. is + c2 = code(x2,y2); // find where second pt. is + + if ((c1 & c2) == 0) + { // the line may be visible + while (c1 | c2) + { // where there is 2D clipping to be done + if (c1 & c2) + { + return false; // if both on same side, quit + } + + c = c1; + if (c==0) + { + c = c2; // pick a point + } + + if (c & TOP) + { + f = ((clip_max_y-y1) << 15)/(y2-y1); + x = x1 + (((x2-x1)*f + 16384) >> 15); + y = clip_max_y; + } + else if (c & BOTTOM) + { + f = ((clip_min_y-y1) << 15)/(y2-y1); + x = x1 + (((x2-x1)*f + 16384) >> 15); + y = clip_min_y; + } + else if (c & LEFT) + { + f = ((clip_min_x-x1) << 15)/(x2-x1); + y = y1 + (((y2-y1)*f + 16384) >> 15); + x = clip_min_x; + } + else if (c & RIGHT) + { + f = ((clip_max_x-x1) << 15)/(x2-x1); + y = y1 + (((y2-y1)*f + 16384) >> 15); + x = clip_max_x; + } + if (c==c1) + { + x1=x; y1=y; c1=code(x1,y1); + } + else + { + x2=x; y2=y; c2=code(x2,y2); + } + } // while still needs clipping + } + else + { // line not visible + return false; + } + return true; +} + +void CDraw32::DrawLineNC(long x1, long y1, long x2, long y2, CPixel32 color) +//USE: draw a line from (x1,y1) to (x2,y2) in color (no clip) +//IN: (x1,y1) - starting coordinate +// (x2,y2) - ending coordinate +// color - 32-bit color value +//OUT: none +{ + long d, ax, ay, sx, sy, dx, dy; + CPixel32* dest; + + assert(buffer != NULL); + + dx = x2-x1; + ax = ABS(dx) << 1; + sx = SIGN(dx); + dy = y2-y1; + ay = ABS(dy) << 1; + sy = SIGN(dy); + + if (255 == color.a) + { + if (dy == 0) + { // horz line + if (dx >= 0) + { + dest = &buffer[row_off[y1] + x1]; + int i = dx+1; + while (i--) + *dest++ = color; + } + else + { + dest = &buffer[row_off[y1] + x1 + dx]; + int i = -dx+1; + while (i--) + *dest++ = color; + } + return; + } + + if (dx == 0) + { // vert line + if (dy >= 0) + { + dest = &buffer[row_off[y1] + x1]; + dy++; + } + else + { + dest = &buffer[row_off[y2] + x1]; + dy = -dy + 1; + } + + while (dy-- != 0) + { + *dest = color; + dest += stride; + } + return; + } + } + + // bressenham's algorithm + if (ax > ay) + { + d = ay - (ax >> 1); + while(x1 != x2) + { + PutPixAlphaNC(x1,y1,color); + + if (d >= 0) + { + y1 += sy; + d -= ax; + } + x1 += sx; + d += ay; + } + } + else + { + d = ax - (ay >> 1); + while(y1 != y2) + { + PutPixAlphaNC(x1,y1,color); + if (d >= 0) + { + x1 += sx; + d -= ay; + } + y1 += sy; + d += ax; + } + } + PutPixAlphaNC(x1,y1,color); +} + +void CDraw32::DrawLineAveNC(long x1, long y1, long x2, long y2, CPixel32 color) +//USE: draw a translucent line from (x1,y1) to (x2,y2) in color (no clip) +//IN: (x1,y1) - starting coordinate +// (x2,y2) - ending coordinate +// color - 32-bit color value +//OUT: none +{ + long d, ax, ay, sx, sy, dx, dy; + CPixel32* dest; + + assert(buffer != NULL); + + dx = x2-x1; + ax = ABS(dx) << 1; + sx = SIGN(dx); + dy = y2-y1; + ay = ABS(dy) << 1; + sy = SIGN(dy); + + if (dy == 0) + { // horz line + if (dx >= 0) + { + dest = &buffer[row_off[y1] + x1]; + int i = dx+1; + while (i--) + *dest++ = AVE_PIX(*dest, color); + } + else + { + dest = &buffer[row_off[y1] + x1 + dx]; + int i = -dx+1; + while (i--) + *dest++ = AVE_PIX(*dest, color); + } + return; + } + + if (dx == 0) + { // vert line + if (dy >= 0) + { + dest = &buffer[row_off[y1] + x1]; + dy++; + } + else + { + dest = &buffer[row_off[y2] + x1]; + dy = -dy + 1; + } + + while (dy-- != 0) + { + *dest = AVE_PIX(*dest, color); + dest += stride; + } + return; + } + + // bressenham's algorithm + if (ax > ay) + { + d = ay - (ax >> 1); + while(x1 != x2) + { + PutPixAveNC(x1,y1,color); + + if (d >= 0) + { + y1 += sy; + d -= ax; + } + x1 += sx; + d += ay; + } + } + else + { + d = ax - (ay >> 1); + while(y1 != y2) + { + PutPixAveNC(x1,y1,color); + if (d >= 0) + { + x1 += sx; + d -= ay; + } + y1 += sy; + d += ax; + } + } + PutPixAveNC(x1,y1,color); +} + +void CDraw32::DrawLineAANC(long x0, long y0, long x1, long y1, CPixel32 color) +// Wu antialiased line drawer. +//USE: Function to draw an antialiased line from (x0,y0) to (x1,y1), using an +// antialiasing approach published by Xiaolin Wu in the July 1991 issue of +// Computer Graphics (SIGGRAPH proceedings). +// +//IN: (x0,y0),(x1,y1) = line to draw +// color = 32-bit color +//OUT: none +{ + assert(buffer != NULL); + + // Make sure the line runs top to bottom + if (y0 > y1) + { + SWAP(y0,y1); + SWAP(x0,x1); + } + + long DeltaX = x1 - x0; + long DeltaY = y1 - y0; + long XDir; + + // Draw the initial pixel, which is always exactly intersected by + // the line and so needs no Alpha + PutPixAlphaNC(x0, y0, color); + + if (DeltaX >= 0) + { + XDir = 1; + } + else + { + XDir = -1; + DeltaX = -DeltaX; // make DeltaX positive + } + + // Special-case horizontal, vertical, and diagonal lines, which + // require no Alpha because they go right through the center of + // every pixel + if (DeltaY == 0) + { // Horizontal line + while (DeltaX-- != 0) + { + x0 += XDir; + PutPixAlphaNC(x0, y0, color); + } + return; + } + if (DeltaX == 0) + { // Vertical line + do + { + y0++; + PutPixAlphaNC(x0, y0, color); + } + while (--DeltaY != 0); + return; + } + if (DeltaX == DeltaY) + { // Diagonal line + do + { + x0 += XDir; + y0++; + PutPixAlphaNC(x0, y0, color); + } + while (--DeltaY != 0); + return; + } + + // Line is not horizontal, diagonal, or vertical + unsigned short ErrorAcc = 0; // initialize the line error accumulator to 0 + + // # of bits by which to shift ErrorAcc to get intensity level + const unsigned long IntensityShift = 16 - 8; + + // Is this an X-major or Y-major line? + if (DeltaY > DeltaX) + { + // Y-major line; calculate 16-bit fixed-point fractional part of a + // pixel that X advances each time Y advances 1 pixel, truncating the + // result so that we won't overrun the endpoint along the X axis + unsigned short ErrorAdj = unsigned short + (((unsigned long) DeltaX << 16) / (unsigned long) DeltaY); + + // Draw all pixels other than the first and last + while (--DeltaY) + { + unsigned short ErrorAccTemp = ErrorAcc; // remember currrent accumulated error + ErrorAcc += ErrorAdj; // calculate error for next pixel + if (ErrorAcc <= ErrorAccTemp) + { // The error accumulator turned over, so advance the X coord + x0 += XDir; + } + y0++; // Y-major, so always advance Y + // The IntensityBits most significant bits of ErrorAcc give us the + // intensity Alpha for this pixel, and the complement of the + // Alpha for the paired pixel + unsigned long Alpha = ErrorAcc >> IntensityShift; + unsigned long InvAlpha = 256-Alpha; + + PutPixAlphaNC(x0, y0, + ALPHA_PIX(GetPix(x0, y0), color, Alpha, InvAlpha)); + PutPixAlphaNC(x0+XDir, y0, + ALPHA_PIX(GetPix(x0+XDir, y0), color, InvAlpha, Alpha)); + } + // Draw the final pixel, which is always exactly intersected by the line + // and so needs no Alpha + PutPixAlphaNC(x1, y1, color); + return; + } + // It's an X-major line; calculate 16-bit fixed-point fractional part of a + // pixel that Y advances each time X advances 1 pixel, truncating the + // result to avoid overrunning the endpoint along the X axis + unsigned short ErrorAdj = unsigned short + (((unsigned long) DeltaY << 16) / (unsigned long) DeltaX); + // Draw all pixels other than the first and last + while (--DeltaX) + { + unsigned short ErrorAccTemp = ErrorAcc; // remember currrent accumulated error + ErrorAcc += ErrorAdj; // calculate error for next pixel + if (ErrorAcc <= ErrorAccTemp) + { // The error accumulator turned over, so advance the Y coord + y0++; + } + x0 += XDir; // X-major, so always advance X + // The IntensityBits most significant bits of ErrorAcc give us the + // intensity Alpha for this pixel, and the complement of the + // Alpha for the paired pixel + unsigned long Alpha = ErrorAcc >> IntensityShift; + unsigned long InvAlpha = 256-Alpha; + PutPixAlphaNC(x0, y0, + ALPHA_PIX(GetPix(x0, y0), color, Alpha, InvAlpha)); + PutPixAlphaNC(x0, y0+1, + ALPHA_PIX(GetPix(x0, y0+1), color, InvAlpha, Alpha)); + } + // Draw the final pixel, which is always exactly intersected by the line + // and so needs no Alpha + PutPixAlphaNC(x1, y1, color); +} + +void CDraw32::DrawRectNC(long ulx, long uly, long width, long height, CPixel32 color) +//USE: draw rectangle in solid color, no clipping +//IN: (ulx,uly) - coordinates of upper-left corner of rect +// width, height - dimensions of rectangle +// color - color value +//OUT: none +{ + + assert(buffer != NULL); + assert(ulx>=0); + assert(uly>=0); + assert(ulx+width= limit) + { + if (di < 0) + { + delta = 2*di + 2*y - 1; + if (delta <= 0) + { // move horizontal + last_x = x; + x++; + di += (2*x + 1); + } + else + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + } + else + { + if (di > 0) + { + delta = 2*di - 2*x -1; + if (delta <= 0) + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + else + { // move vertical + y--; + di += (1 - 2*y); + } + } + else /* di = 0 */ + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + } + } + + if (y != last_y) + { // circle fill + DrawLine(xc-last_x,yc+last_y,xc+last_x,yc+last_y,fill); + if (last_y > limit) + { + DrawLine(xc-last_x,yc-last_y,xc+last_x,yc-last_y,fill); + } + last_y = y; + } + } while (y >= limit); + } + + // draw edge + if (edge.a != 0) + { + x = 0; + y = r; + limit = 0; + di = 2*(1-r); + + do + { + // circle edge + PutPix(xc+x, yc+y, edge); + PutPix(xc-x, yc+y, edge); + if (y > limit) + { + PutPix(xc+x, yc-y, edge); + PutPix(xc-x, yc-y, edge); + } + + if (y >= limit) + { + if (di < 0) + { + delta = 2*di + 2*y - 1; + if (delta <= 0) + { // move horizontal + x++; + di += (2*x + 1); + } + else + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + } + else + { + if (di > 0) + { + delta = 2*di - 2*x -1; + if (delta <= 0) + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + else + { // move vertical + y--; + di += (1 - 2*y); + } + } + else /* di = 0 */ + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + } + } + } while (y >= limit); + } +} // DrawCircle + +void CDraw32::DrawCircleAve(long xc, long yc, long r, CPixel32 edge, CPixel32 fill) +//USE: Draw a simple circle in current color with Bresenham's +// circle algorithm. +// a circle with fill and edge colors averaged with dest (-1 = no color) +// See PROCEDURAL ELEMENTS FOR COMPUTER GRAPHICS +// David F. Rogers Pg. 48. +// +//IN: xc,yc - center +// r - radius +// edge - edge color +// fill - fill color +// +//OUT: none (a circle on in the off-screen buffer) +{ + long x,y; + long limit,di,delta; + long last_x,last_y; + long f; + + assert(buffer != NULL); + + if (r < 1) + return; + + // draw fill + if (fill.a != 0) + { + x = 0; last_x = x; + y = r; last_y = y; + di = 2*(1-r); + limit = 0; + + do + { + if (y >= limit) + { + if (di < 0) + { + delta = 2*di + 2*y - 1; + if (delta <= 0) + { // move horizontal + last_x = x; + x++; + di += (2*x + 1); + } + else + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + } + else + { + if (di > 0) + { + delta = 2*di - 2*x -1; + if (delta <= 0) + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + else + { // move vertical + y--; + di += (1 - 2*y); + } + } + else /* di = 0 */ + { // move diagonal + last_x = x; + x++; + y--; + di += (2*x - 2*y + 2); + } + } + } + + if (y != last_y) + { // circle fill + for (f=xc-last_x; f<=xc+last_x; f++) + PutPixAve(f, yc+last_y, fill); + if (last_y > limit) + { + for (f=xc-last_x; f<=xc+last_x; f++) + PutPixAve(f, yc-last_y, fill); + } + last_y = y; + } + } while (y >= limit); + } + + // draw edge + if (edge.a != 0) + { + x = 0; + y = r; + limit = 0; + di = 2*(1-r); + + do + { + // circle edge + PutPixAve(xc+x, yc+y, edge); + PutPixAve(xc-x, yc+y, edge); + if (y > limit) + { + PutPixAve(xc+x, yc-y, edge); + PutPixAve(xc-x, yc-y, edge); + } + if (y >= limit) + { + if (di < 0) + { + delta = 2*di + 2*y - 1; + if (delta <= 0) + { // move horizontal + x++; + di += (2*x + 1); + } + else + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + } + else + { + if (di > 0) + { + delta = 2*di - 2*x -1; + if (delta <= 0) + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + else + { // move vertical + y--; + di += (1 - 2*y); + } + } + else /* di = 0 */ + { // move diagonal + x++; + y--; + di += (2*x - 2*y + 2); + } + } + } + } while (y >= limit); + } +} // DrawCircleAve + +//////////////////////////////////////////////////////////////////////////// +//Concave Polygon Scan Conversion +//////////////////////////////////////////////////////////////////////////// + +// concave: scan convert nvert-sided concave non-simple polygon +// with vertices at (point[i].x, point[i].y) for i in +// [0..nvert-1] within the window win by +// calling spanproc for each visible span of pixels. +// +// Polygon can be clockwise or counterclockwise. +// +// Algorithm does uniform point sampling at pixel centers. +// Inside-outside test done by even-odd rule: a point is +// considered inside if an emanating ray intersects the polygon +// an odd number of times. +// +// spanproc should fill in pixels from xl to xr inclusive on scanline y, +// +// e.g: +// spanproc(short y, short xl, short xr) +// { +// short x; +// for (x=xl; x<=xr; x++) +// pixel_write(x, y, pixelvalue); +// } + +typedef struct +{ // a polygon edge + // these are fixed point long ints for some accuracy & speed + long x; // x coordinate of edge's intersection with current scanline + long dx; // change in x with respect to y + long i; // edge number: edge i goes from pt[i] to + // pt[i+1] +} POLYEDGE; + +#define INT_SHIFT 13 + +// global for speed +static long n; // number of vertices +static POINT *pt; // vertices + +static long nact; // number of active edges +static POLYEDGE active[256]; // active edge list:edges crossing scanline y +static long ind[256]; // list of vertex indices, sorted by + // pt[ind[j]].y + +static void del_edge(long i) +// remove edge i from active list +{ + int j; + + for (j = 0; j < nact && active[j].i != i; j++) + ; + + // edge not in active list; happens at cliprect->top + if (j >= nact) + { + return; + } + + nact--; + memcpy(&active[j], &active[j + 1], (nact - j) * sizeof(active[0])); +} + +static void ins_edge(long i, long y) +// append edge i to end of active list +{ + int j; + long dx; + POINT *p; + POINT *q; + + j = i < n - 1 ? i + 1 : 0; + if (pt[i].y < pt[j].y) + { + p = &pt[i]; + q = &pt[j]; + } + else + { + p = &pt[j]; + q = &pt[i]; + } + + // initialize x position at intersection of edge with scanline y + if ((q->y - p->y) != 0) + { + dx = (((long)q->x - (long)p->x) * (long)(1<y - (long)p->y)); + } + else + { + // horizontal line + dx = 0; + } + + active[nact].dx = dx; + active[nact].x = (dx * (long)(y - p->y)) + ((long)p->x << INT_SHIFT); + active[nact].i = i; + nact++; +} + +// comparison routines for shellsort +int compare_ind(long *u, long *v) +{ + return (pt[*u].y <= pt[*v].y ? -1 : 1); +} + +int compare_active(POLYEDGE *u, POLYEDGE *v) +{ + return (u->x <= v->x ? -1 : 1); +} + +void shell_sort(void *vec, long n, long siz, + int (*compare)(void*,void*)) +// USE: shell sort aka heap sort. Best sort algorithm for almost sorted list. +{ + byte *a; + byte v[128]; // temp object + long i,j,h; + + a = (byte *)vec; + + // choose size of "heap" + for (h = 1; h <= n/9; h = 3*h+1) + ; + + // divide and conq. + for ( ; h > 0; h /= 3) + { + for (i = h; i < n; i++) + { + // v = a[i]; + memcpy(v,(a+i*siz),siz); + j = i; + // j >= h && a[j-h] > v + while ((j >= h) && compare((void*)(a+(j-h)*siz),(void*)v) > 0) + { + // a[j] = a[j-h] + memcpy((a+j*siz),(a+(j-h)*siz),siz); + j -= h; + } + // a[j] = v; + memcpy((a+j*siz),v,siz); + } + } +} + +void CDraw32::DrawPolygon(long nvert, POINT *point, CPixel32 edge, CPixel32 fill) +//USE: Scan convert a polygon +//IN: nvert: Number of vertices +// point: Vertices of polygon +// edge: edge color +// fill: fill color +//OUT: none +{ + long k, + y0, + y1, + y, + i, + j, + xl, + xr; + + assert(buffer != NULL); + + n = nvert; + + if (n <= 0) + { // nothing to do + return; + } + + pt = point; + + if (fill.a != 0) + { // draw fill + + // create y-sorted array of indices ind[k] into vertex list + for (k = 0; k < n; k++) + { + ind[k] = k; + } + + // sort ind by pt[ind[k]].y + shell_sort(ind, n, sizeof(long), (int (*)(void*,void*)) compare_ind); + + nact = 0; // start with empty active list + k = 0; // ind[k] is next vertex to process + + // ymin of polygon + y0 = MAX(clip_min_y-1, pt[ind[0]].y); + + // ymax of polygon + y1 = MIN(clip_max_y+1, pt[ind[n-1]].y); + + // step through scanlines + for (y = y0; y < y1; y++) + { + // Check vertices between previous scanline + // and current one, if any + for (; (k < n) && (pt[ind[k]].y <= y); k++) + { + i = ind[k]; + // insert or delete edges before and after vertex i + // (i-1 to i, and i to i+1) + // from active list if they cross scanline y + j = i > 0 ? i - 1 : n - 1; // vertex previous to i + if (pt[j].y < y) + { + // old edge, remove from active list + del_edge(j); + } + else + { + if (pt[j].y > y) + { + // new edge, add to active list + ins_edge(j, y); + } + } + j = i < n - 1 ? i + 1 : 0; // vertex next after i + if (pt[j].y < y) + { + // old edge, remove from active list + del_edge(i); + } + else + { + if (pt[j].y > y) + { + // new edge, add to active list + ins_edge(i, y); + } + } + } + + // sort active edge list by active[j].x + shell_sort(active, nact, sizeof(POLYEDGE), (int (*)(void*,void*))compare_active); + + // draw horizontal segments for scanline y + for (j = 0; j < nact; j += 2) + { // draw horizontal segments + // span 'tween j & j+1 is inside, span tween + // j+1 & j+2 is outside + + // left end of span + // convert back from fixed point - round down + xl = (long) (active[j].x >> INT_SHIFT); + if (xl < clip_min_x-1) + { + xl = clip_min_x-1; + } + + // right end of span + // convert back from fixed point - round down + xr = (long) (active[j + 1].x >> INT_SHIFT); + if (xr > clip_max_x) + { + xr = clip_max_x; + } + + if (xl < xr) + { + // draw pixels in span + DrawLine(xl+1,y,xr,y,fill); + } + + // increment edge coords + active[j].x += active[j].dx; + active[j + 1].x += active[j + 1].dx; + } + } + + // sort active edge list by active[j].x + shell_sort(active, nact, sizeof(POLYEDGE), (int (*)(void*,void*))compare_active); + + // draw horizontal segments for scanline y + for (j = 0; j < nact; j += 2) + { // draw horizontal segments + // span 'tween j & j+1 is inside, span tween + // j+1 & j+2 is outside + + // left end of span + // convert back from fixed point - round down + xl = (long) (active[j].x >> INT_SHIFT); + if (xl < clip_min_x-1) + { + xl = clip_min_x-1; + } + + // right end of span + // convert back from fixed point - round up + xr = (long) (active[j + 1].x >> INT_SHIFT); + if (xr > clip_max_x) + { + xr = clip_max_x; + } + + if (xl < xr) + { + // draw pixels in span + DrawLine(xl+1,y,xr,y,fill); + } + + // increment edge coords + active[j].x += active[j].dx; + active[j + 1].x += active[j + 1].dx; + } + } + + if (edge.a != 0) + { // draw edges + for (k = 0; k < n-1; k++) + { + DrawLineAA(pt[k].x,pt[k].y,pt[k+1].x,pt[k+1].y,edge); + } + + DrawLineAA(pt[n-1].x,pt[n-1].y,pt[0].x,pt[0].y,edge); + } + + return; +} + +void CDraw32::Blit(long dstX, long dstY, long width, long height, + CPixel32* srcImage, long srcX, long srcY, long srcStride) +//USE: simple blit +//IN: dstX, dstY - upper left corner of where image will land in buffer +// width, height - width and height of image +// srcImage - src image buffer +// srcX, srcY - upper left corner in src image +// srcStride - number of pixels per line in src image +//OUT: none +{ + CPixel32 *dst; + CPixel32 *src; + int x,y; + + assert(buffer != NULL); + + BlitClip(dstX, dstY, width, height, srcX, srcY); + + if (width < 1 || height < 1) + return; + + dst = &buffer[PIXPOS(dstX,dstY,stride)]; + src = &srcImage[PIXPOS(srcX,srcY,srcStride)]; + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { +// *dst++ = *src++; + byte alpha = src->a; + byte dst_alpha = dst->a; + *dst = ALPHA_PIX(*src, *dst, alpha, 256-alpha); + dst->a = dst_alpha; + ++dst; + ++src; + } + dst += (stride - width); + src += (srcStride - width); + } +} + +void CDraw32::BlitClip(long& dstX, long& dstY, + long& width, long& height, + long& srcX, long& srcY) +//USE: simple blit clip +//IN: dstX, dstY - upper left corner of where image will land in buffer +// width, height - width and height of image +// srcX, srcY - upper left corner in src image +//OUT: none +{ + + // clip to our buffer size + if (dstX < clip_min_x) + { + int dif = (clip_min_x - dstX); + dstX += dif; + srcX += dif; + width -= dif; + } + + if (dstY < clip_min_y) + { + int dif = (clip_min_y - dstY); + dstY += dif; + srcY += dif; + height -= dif; + } + + if (dstX+width-1 > clip_max_x) + { + width -= (dstX+width-1 - clip_max_x); + } + + if (dstY+height-1 > clip_max_y) + { + height -= (dstY+height-1 - clip_max_y); + } +} + +void CDraw32::BlitColor(long dstX, long dstY, long width, long height, + CPixel32* srcImage, long srcX, long srcY, long srcStride, CPixel32 color) +//USE: blit using image alpha as mask +//IN: dstX, dstY - upper left corner of where image will land in buffer +// width, height - width and height of image +// srcImage - src image buffer +// srcX, srcY - upper left corner in src image +// srcStride - number of pixels per line in src image +// color - color to apply to srcImage +//OUT: none +{ + CPixel32 *dst; + CPixel32 *src; + int x,y; + int dstNextLine; + int srcNextLine; + + assert(buffer != NULL); + + BlitClip(dstX, dstY, width, height, srcX, srcY); + + if (width < 1 || height < 1) + return; + + dst = &buffer[PIXPOS(dstX,dstY,stride)]; + src = &srcImage[PIXPOS(srcX,srcY,srcStride)]; + + dstNextLine = (stride - width); + srcNextLine = (srcStride - width); + + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + byte alpha = src->a; + *dst = ALPHA_PIX(color, *dst, alpha, 256-alpha); + ++dst; + ++src; + } + dst += dstNextLine; + src += srcNextLine; + } +} + diff --git a/code/qcommon/cm_draw.h b/code/qcommon/cm_draw.h new file mode 100644 index 0000000..1058efa --- /dev/null +++ b/code/qcommon/cm_draw.h @@ -0,0 +1,245 @@ +/////////////////////////////////////////////////////////////////////////////// +// CDraw32 Class Interface +// +// Basic drawing routines for 32-bit per pixel buffer +/////////////////////////////////////////////////////////////////////////////// + +#if !defined(CM_DRAW_H_INC) +#define CM_DRAW_H_INC + +// calc offset into image array for a pixel at (x,y) +#define PIXPOS(x,y,stride) (((y)*(stride))+(x)) + +#ifndef MIN +// handy macros +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define ABS(x) ((x)<0 ? -(x):(x)) +#define SIGN(x) (((x) < 0) ? -1 : (((x) > 0) ? 1 : 0)) +#endif + +#ifndef CLAMP +#define SWAP(a,b) { a^=b; b^=a; a^=b; } +#define SQR(a) ((a)*(a)) +#define CLAMP(v,l,h) ((v)<(l) ? (l) : (v) > (h) ? (h) : (v)) +#define LERP(t, a, b) (((b)-(a))*(t) + (a)) + +// round a to nearest integer towards 0 +#define FLOOR(a) ((a)>0 ? (int)(a) : -(int)(-a)) + +// round a to nearest integer away from 0 +#define CEILING(a) \ +((a)==(int)(a) ? (a) : (a)>0 ? 1+(int)(a) : -(1+(int)(-a))) + +#include +#endif + +class CPixel32 +{ +public: + byte r; + byte g; + byte b; + byte a; + + CPixel32(byte R = 0, byte G = 0, byte B = 0, byte A = 255) : r(R), g(G), b(B), a(A) {} + CPixel32(long l) {r = (l >> 24) & 0xff; g = (l >> 16) & 0xff; b = (l >> 8) & 0xff; a = l & 0xff;}; + + ~CPixel32() + {} +}; + +#define PIX32_SIZE sizeof(CPixel32) + +// standard image operator macros +#define IMAGE_SIZE(width,height) ((width)*(height)*(PIX32_SIZE)) + + +inline CPixel32 AVE_PIX (CPixel32 x, CPixel32 y) + { CPixel32 t; t.r = (byte)(((int)x.r + (int)y.r)>>1); + t.g = (byte)(((int)x.g + (int)y.g)>>1); + t.b = (byte)(((int)x.b + (int)y.b)>>1); + t.a = (byte)(((int)x.a + (int)y.a)>>1); return t;} + +inline CPixel32 ALPHA_PIX (CPixel32 x, CPixel32 y, long alpha, long inv_alpha) + { CPixel32 t; t.r = (byte)((x.r*alpha + y.r*inv_alpha)>>8); + t.g = (byte)((x.g*alpha + y.g*inv_alpha)>>8); + t.b = (byte)((x.b*alpha + y.b*inv_alpha)>>8); +// t.a = (byte)((x.a*alpha + y.a*inv_alpha)>>8); return t;} + t.a = y.a; return t;} + +inline CPixel32 LIGHT_PIX (CPixel32 p, long light) +{ CPixel32 t; + t.r = (byte)CLAMP(((p.r * light)>>10) + p.r, 0, 255); + t.g = (byte)CLAMP(((p.g * light)>>10) + p.g, 0, 255); + t.b = (byte)CLAMP(((p.b * light)>>10) + p.b, 0, 255); + t.a = p.a; return t;} + +// Colors are 32-bit RGBA + +// draw class +class CDraw32 +{ +public: // static drawing context - static so we set only ONCE for many draw calls + static CPixel32* buffer; // pointer to pixel buffer (one active) + static long buf_width; // size of buffer + static long buf_height; // size of buffer + static long stride; // stride of buffer in pixels + static long clip_min_x; // clip bounds + static long clip_min_y; // clip bounds + static long clip_max_x; // clip bounds + static long clip_max_y; // clip bounds + static long* row_off; // Table for quick Y calculations + +private: + void BlitClip(long& dstX, long& dstY, + long& width, long& height, + long& srcX, long& srcY); + +protected: +public: + CDraw32(); // constructor + ~CDraw32(); // destructor + + // set the rect to clip drawing functions to + static void SetClip(long min_x, long min_y,long max_x, long max_y) + {clip_min_x = MAX(min_x,0); clip_max_x = MIN(max_x,buf_width-1); + clip_min_y = MAX(min_y,0); clip_max_y = MIN(max_y,buf_height-1);} + + static void GetClip(long& min_x, long& min_y,long& max_x, long& max_y) + {min_x = clip_min_x; min_y = clip_min_y; + max_x = clip_max_x; max_y = clip_max_y; } + + // set the buffer to use for drawing off-screen + static void SetBuffer(CPixel32* buf) {buffer = buf;}; + + // set the dimensions of the off-screen buffer + static bool SetBufferSize(long width,long height,long stride_len); + + // call this to free the table for quick y calcs before the program ends + static void CleanUp(void) + {if (row_off) delete [] row_off; row_off=NULL; buf_width=0; buf_height=0;} + + // set a pixel at (x,y) to color (no clipping) + void PutPixNC(long x, long y, CPixel32 color) + {buffer[row_off[y] + x] = color;} + + // set a pixel at (x,y) to color + void PutPix(long x, long y, CPixel32 color) + { // clipping check + if (x < clip_min_x || x > clip_max_x || + y < clip_min_y || y > clip_max_y) + return; + PutPixNC(x,y,color); + } + + // get the color of a pixel at (x,y) + CPixel32 GetPix(long x, long y) + {return buffer[row_off[y] + x];} + + // set a pixel at (x,y) with 50% translucency (no clip) + void PutPixAveNC(long x, long y, CPixel32 color) + { PutPixNC(x,y,AVE_PIX(GetPix(x, y), color)); } + + // set a pixel at (x,y) with 50% translucency + void PutPixAve(long x, long y, CPixel32 color) + { // clipping check + if (x < clip_min_x || x > clip_max_x || + y < clip_min_y || y > clip_max_y) + return; + PutPixNC(x,y,AVE_PIX(GetPix(x, y), color)); + } + + // set a pixel at (x,y) with translucency level (no clip) + void PutPixAlphaNC(long x, long y, CPixel32 color) + { PutPixNC(x,y,ALPHA_PIX(color, GetPix(x, y), color.a, 256-color.a));} + + // set a pixel at (x,y) with translucency level + void PutPixAlpha(long x, long y, CPixel32 color) + { // clipping check + if (x < clip_min_x || x > clip_max_x || + y < clip_min_y || y > clip_max_y) + return; + PutPixNC(x,y,ALPHA_PIX(color, GetPix(x, y), color.a, 256-color.a));} + + // clear screen buffer to color from start to end line + void ClearLines(CPixel32 color,long start,long end); + + // clear screen buffer to color provided + void ClearBuffer(CPixel32 color) + {ClearLines(color,0,buf_height-1);}; + + // fill buffer alpha from start to end line + void SetAlphaLines(byte alpha,long start,long end); + + // clear screen buffer to color provided + void SetAlphaBuffer(byte alpha) + {SetAlphaLines(alpha,0,buf_height-1);}; + + // clip a line segment to the clip rect + bool ClipLine(long& x1, long& y1, long& x2, long& y2); + + // draw a solid colored line, no clipping + void DrawLineNC(long x1, long y1, long x2, long y2, CPixel32 color); + + // draw a solid color line + void DrawLine(long x1, long y1, long x2, long y2, CPixel32 color) + { if (ClipLine(x1,y1,x2,y2)) DrawLineNC(x1,y1,x2,y2,color);} + + void DrawLineAveNC(long x1, long y1, long x2, long y2, CPixel32 color); + + // draw a translucent solid color line + void DrawLineAve(long x1, long y1, long x2, long y2, CPixel32 color) + { if (ClipLine(x1,y1,x2,y2)) DrawLineAveNC(x1,y1,x2,y2,color);} + + // draw an anti-aliased line, no clipping + void DrawLineAANC(long x0, long y0, long x1, long y1, CPixel32 color); + + // draw an anti-aliased line + void DrawLineAA(long x1, long y1, long x2, long y2, CPixel32 color) + { if (ClipLine(x1,y1,x2,y2)) DrawLineAANC(x1,y1,x2,y2,color);} + + // draw a filled rectangle, no clipping + void DrawRectNC(long ulx, long uly, long width, long height,CPixel32 color); + + // draw a filled rectangle + void DrawRect(long ulx, long uly, long width, long height, CPixel32 color); + + // draw a filled rectangle + void DrawRectAve(long ulx, long uly, long width, long height,CPixel32 color); + + // draw a box (unfilled rectangle) no clip + void DrawBoxNC(long ulx, long uly, long width, long height, CPixel32 color); + + // draw a box (unfilled rectangle) + void DrawBox(long ulx, long uly, long width, long height, CPixel32 color); + + // draw a box (unfilled rectangle) + void DrawBoxAve(long ulx, long uly, long width, long height, CPixel32 color); + + // draw a circle with fill and edge colors + void DrawCircle(long xc, long yc, long r, CPixel32 edge, CPixel32 fill); + + // draw a circle with fill and edge colors averaged with dest + void DrawCircleAve(long xc, long yc, long r, CPixel32 edge, CPixel32 fill); + + // draw a polygon (complex) with fill and edge colors + void DrawPolygon(long nvert, POINT *point, CPixel32 edge, CPixel32 fill); + + // simple blit function + void BlitNC(long dstX, long dstY, long dstWidth, long dstHeight, + CPixel32* srcImage, long srcX, long srcY, long srcStride); + + void Blit(long dstX, long dstY, long dstWidth, long dstHeight, + CPixel32* srcImage, long srcX, long srcY, long srcStride); + + // blit image times color + void BlitColor(long dstX, long dstY, long dstWidth, long dstHeight, + CPixel32* srcImage, long srcX, long srcY, long srcStride, CPixel32 color); + + void Emboss(long dstX, long dstY, long width, long height, + CPixel32* clrImage, long clrX, long clrY, long clrStride); +}; + +/////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/code/qcommon/cm_landscape.h b/code/qcommon/cm_landscape.h new file mode 100644 index 0000000..15f40fb --- /dev/null +++ b/code/qcommon/cm_landscape.h @@ -0,0 +1,271 @@ +#if !defined(CM_LANDSCAPE_H_INC) +#define CM_LANDSCAPE_H_INC + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include +#pragma warning (pop) + +using namespace std; + +// These are the root classes using data shared in both the server and the renderer. +// This common data is also available to physics + +#define HEIGHT_RESOLUTION 256 + +// Trying to make a guess at the optimal step through the patches +// This is the average of 1 side and the diagonal presuming a square patch +#define TERRAIN_STEP_MAGIC (1.0f / 1.2071f) + +#define MIN_TERXELS 2 +#define MAX_TERXELS 8 +// Defined as 1 << (sqrt(MAX_TERXELS) + 1) +#define MAX_VARIANCE_SIZE 16 + +// Maximum number of instances to pick from an instance file +#define MAX_INSTANCE_TYPES 16 + +// Types of areas + +typedef enum +{ + AT_NONE, + AT_FLAT, + AT_BSP, + AT_NPC, + AT_GROUP, + AT_RIVER, + AT_OBJECTIVE, + AT_PLAYER, + +} areaType_t; + +class CArea +{ +private: + vec3_t mPosition; + float mRadius; + float mAngle; + float mAngleDiff; + int mType; + int mVillageID; +public: + CArea(void) {} + ~CArea(void) {} + + void Init(vec3_t pos, float radius, float angle = 0.0f, int type = AT_NONE, float angleDiff = 0.0f, int villageID = 0) + { + VectorCopy(pos, mPosition); + mRadius = radius; + mAngle = angle; + mAngleDiff = angleDiff; + mType = type; + mVillageID = villageID; + } + float GetRadius(void) const { return(mRadius); } + float GetAngle(void) const { return(mAngle); } + float GetAngleDiff(void) const { return(mAngleDiff); } + vec3_t &GetPosition(void) { return(mPosition); } + int GetType(void) const { return(mType); } + int GetVillageID(void) const { return(mVillageID); } +}; + +typedef list areaList_t; +typedef list::iterator areaIter_t; + +class CCMHeightDetails +{ +private: + int mContents; + int mSurfaceFlags; +public: + CCMHeightDetails(void) {} + ~CCMHeightDetails(void) {} + + // Accessors + const int GetSurfaceFlags(void) const { return(mSurfaceFlags); } + const int GetContents(void) const { return(mContents); } + void SetFlags(const int con, const int sf) { mContents = con; mSurfaceFlags = sf; } +}; + +class CCMPatch +{ +protected: + class CCMLandScape *owner; // Owning landscape + int mHx, mHy; // Terxel coords of patch + byte *mHeightMap; // Pointer to height map to use + byte mCornerHeights[4]; // Heights at the corners of the patch + vec3_t mWorldCoords; // World coordinate offset of this patch. + vec3pair_t mBounds; // mins and maxs of the patch for culling + int mNumBrushes; // number of brushes to collide with in the patch + struct cbrush_s *mPatchBrushData; // List of brushes that make up the patch + int mSurfaceFlags; // surfaceflag of the heightshader + int mContentFlags; // contents of the heightshader +public: + // Constructors + CCMPatch(void) {} + ~CCMPatch(void); + + // Accessors + const vec3_t &GetWorld(void) const { return(mWorldCoords); } + const vec3_t &GetMins(void) const { return(mBounds[0]); } + const vec3_t &GetMaxs(void) const { return(mBounds[1]); } + const vec3pair_t &GetBounds(void) const { return(mBounds); } + const int GetHeightMapX(void) const { return(mHx); } + const int GetHeightMapY(void) const { return(mHy); } + const int GetHeight(int corner) const { return(mCornerHeights[corner]); } + const int GetNumBrushes(void) const { return(mNumBrushes); } + struct cbrush_s *GetCollisionData(void) const { return(mPatchBrushData); } + + void SetSurfaceFlags(const int in) { mSurfaceFlags = in; } + const int GetSurfaceFlags(void) const { return(mSurfaceFlags); } + void SetContents(const int in) { mContentFlags = in; } + const int GetContents(void) const { return(mContentFlags); } + + // Prototypes + void Init(CCMLandScape *ls, int heightX, int heightY, vec3_t world, byte *hMap, byte *patchBrushData); + void InitPlane(struct cbrushside_s *side, cplane_t *plane, vec3_t p0, vec3_t p1, vec3_t p2); + void CreatePatchPlaneData(void); + + void* GetAdjacentBrushX ( int x, int y ); + void* GetAdjacentBrushY ( int x, int y ); +}; + +class CRandomTerrain; + +class CCMLandScape +{ +private: + int mRefCount; // Number of times this class is referenced + thandle_t mTerrainHandle; + byte *mHeightMap; // Pointer to byte array of height samples + byte *mFlattenMap; // Pointer to byte array of flatten samples + int mWidth, mHeight; // Width and height of heightMap excluding the 1 pixel edge + int mTerxels; // Number of terxels per patch side + vec3_t mTerxelSize; // Vector to scale heightMap samples to real world coords + vec3pair_t mBounds; // Real world bounds of terrain brush + vec3_t mSize; // Size of terrain brush in real world coords excluding 1 patch edge + vec3_t mPatchSize; // Size of each patch in the x and y directions only + float mPatchScalarSize; // Horizontal size of the patch + int mBlockWidth, mBlockHeight; // Width and height of heightfield on blocks + CCMPatch *mPatches; + byte *mPatchBrushData; // Base memory from which the patch brush data is taken + bool mHasPhysics; // Set to true unless disabled + CRandomTerrain *mRandomTerrain; + + int mBaseWaterHeight; // Base water height in terxels + float mWaterHeight; // Real world height of the water + int mWaterContents; // Contents of the water shader + int mWaterSurfaceFlags; // Surface flags of the water shader + + unsigned long holdrand; + + list mAreas; // List of flattened areas on this landscape + list::iterator mAreasIt; + + CCMHeightDetails mHeightDetails[HEIGHT_RESOLUTION]; // Surfaceflags per height + vec3_t *mCoords; // Temp storage for real world coords + +public: + CCMLandScape(const char *configstring, bool server); + ~CCMLandScape(void); + + CCMPatch *GetPatch(int x, int y); + + // Prototypes + void PatchCollide(struct traceWork_s *tw, trace_t &trace, const vec3_t start, const vec3_t end, int checkcount); + void TerrainPatchIterate(void (*IterateFunc)( CCMPatch *, void * ), void *userdata) const; + float GetWorldHeight(vec3_t origin, const vec3pair_t bounds, bool aboveGround) const; + float WaterCollide(const vec3_t begin, const vec3_t end, float fraction) const; + void UpdatePatches(void); + void GetTerxelLocalCoords ( int x, int y, vec3_t coords[8] ); + void LoadTerrainDef(const char *td); + void SetShaders(int height, class CCMShader *shader); + void FlattenArea(CArea *area, int height, bool save, bool forceHeight, bool smooth); + void CarveLine ( vec3_t start, vec3_t end, int depth, int width ); + void CarveBezierCurve ( int numCtlPoints, vec3_t* ctlPoints, int steps, int depth, int size ); + void SaveArea(CArea *area); + float FractionBelowLevel(CArea *area, int height); + bool AreaCollision(CArea *area, int *areaTypes, int areaTypeCount); + CArea *GetFirstArea(void); + CArea *GetFirstObjectiveArea(void); + CArea *GetPlayerArea(void); + CArea *GetNextArea(void); + CArea *GetNextObjectiveArea(void); + + // Accessors + const int GetRefCount(void) const { return(mRefCount); } + void IncreaseRefCount(void) { mRefCount++; } + void DecreaseRefCount(void) { mRefCount--; } + const vec3pair_t &GetBounds(void) const { return(mBounds); } + const vec3_t &GetMins(void) const { return(mBounds[0]); } + const vec3_t &GetMaxs(void) const { return(mBounds[1]); } + const vec3_t &GetSize(void) const { return(mSize); } + const vec3_t &GetTerxelSize(void) const { return(mTerxelSize); } + const vec3_t &GetPatchSize(void) const { return(mPatchSize); } + const float GetPatchWidth(void) const { return(mPatchSize[0]); } + const float GetPatchHeight(void) const { return(mPatchSize[1]); } + const float GetPatchScalarSize(void) const { return(mPatchScalarSize); } + const int GetTerxels(void) const { return(mTerxels); } + const int GetRealWidth(void) const { return(mWidth + 1); } + const int GetRealHeight(void) const { return(mHeight + 1); } + const int GetRealArea(void) const { return((mWidth + 1) * (mHeight + 1)); } + const int GetWidth(void) const { return(mWidth); } + const int GetHeight(void) const { return(mHeight); } + const int GetArea(void) const { return(mWidth * mHeight); } + const int GetBlockWidth(void) const { return(mBlockWidth); } + const int GetBlockHeight(void) const { return(mBlockHeight); } + const int GetBlockCount(void) const { return(mBlockWidth * mBlockHeight); } + byte *GetHeightMap(void) const { return(mHeightMap); } + byte *GetFlattenMap(void) const { return(mFlattenMap); } + const thandle_t GetTerrainId(void) const { return(mTerrainHandle); } + void SetTerrainId(const thandle_t terrainId) { mTerrainHandle = terrainId; } + const float CalcWorldHeight(int height) const { return((height * mTerxelSize[2]) + mBounds[0][2]); } + const bool GetHasPhysics(void) const { return(mHasPhysics); } + const bool GetIsRandom(void) const { return(mRandomTerrain != 0); } + const int GetSurfaceFlags(int height) const { return(mHeightDetails[height].GetSurfaceFlags()); } + const int GetContentFlags(int height) const { return(mHeightDetails[height].GetContents()); } + void CalcRealCoords(void); + vec3_t *GetCoords(void) const { return(mCoords); } + + int GetBaseWaterHeight(void) const { return(mBaseWaterHeight); } + void SetRealWaterHeight(int height) { mWaterHeight = height * mTerxelSize[2]; } + float GetWaterHeight(void) const { return(mWaterHeight); } + int GetWaterContents(void) const { return(mWaterContents); } + int GetWaterSurfaceFlags(void) const { return(mWaterSurfaceFlags); } + + CRandomTerrain *GetRandomTerrain(void) { return mRandomTerrain; } + + void rand_seed(int seed); + unsigned long get_rand_seed(void) { return holdrand; } + + float flrand(float min, float max); + int irand(int min, int max); +}; + +void CM_TerrainPatchIterate(const class CCMLandScape *ls, void (*IterateFunc)( CCMPatch *, void * ), void *userdata); +class CCMLandScape *CM_InitTerrain(const char *configstring, thandle_t terrainId, bool server); +float CM_GetWorldHeight(const CCMLandScape *landscape, vec3_t origin, const vec3pair_t bounds, bool aboveGround); +void CM_FlattenArea(CCMLandScape *landscape, CArea *area, int height, bool save, bool forceHeight, bool smooth); +void CM_CarveBezierCurve (CCMLandScape *landscape, int numCtls, vec3_t* ctls, int steps, int depth, int size ); +void CM_SaveArea(CCMLandScape *landscape, CArea *area); +float CM_FractionBelowLevel(CCMLandScape *landscape, CArea *area, int height); +bool CM_AreaCollision(class CCMLandScape *landscape, class CArea *area, int *areaTypes, int areaTypeCount); +CArea *CM_GetFirstArea(CCMLandScape *landscape); +CArea *CM_GetFirstObjectiveArea(CCMLandScape *landscape); +CArea *CM_GetPlayerArea(class CCMLandScape *common); +CArea *CM_GetNextArea(CCMLandScape *landscape); +CArea *CM_GetNextObjectiveArea(CCMLandScape *landscape); +void CM_CircularIterate(byte *data, int width, int height, int xo, int yo, int insideRadius, int outsideRadius, int *user, void (*callback)(byte *, float, int *)); + +CRandomTerrain *CreateRandomTerrain(const char *config, CCMLandScape *landscape, byte *heightmap, int width, int height); + +void SV_LoadMissionDef(const char *configstring, class CCMLandScape *landscape); +void CL_CreateRandomTerrain(const char *config, class CCMLandScape *landscape, byte *image, int width, int height); +void CL_LoadInstanceDef(const char *configstring, class CCMLandScape *landscape); +void CL_LoadMissionDef(const char *configstring, class CCMLandScape *landscape); + +extern cvar_t *com_terrainPhysics; + +#endif + +// end diff --git a/code/qcommon/cm_load.cpp b/code/qcommon/cm_load.cpp new file mode 100644 index 0000000..7e717ec --- /dev/null +++ b/code/qcommon/cm_load.cpp @@ -0,0 +1,1298 @@ +// cmodel.c -- model loading + +#include "cm_local.h" +#include "../RMG/RM_Headers.h" + +void CM_LoadShaderText(bool forceReload); + +#ifdef BSPC +void SetPlaneSignbits (cplane_t *out) { + int bits, j; + + // for fast box on planeside test + bits = 0; + for (j=0 ; j<3 ; j++) { + if (out->normal[j] < 0) { + bits |= 1<signbits = bits; +} +#endif //BSPC + +// to allow boxes to be treated as brush models, we allocate +// some extra indexes along with those needed by the map +#define BOX_BRUSHES 1 +#define BOX_SIDES 6 +#define BOX_LEAFS 2 +#define BOX_PLANES 12 + +#define LL(x) x=LittleLong(x) + + +clipMap_t cmg; +int c_pointcontents; +int c_traces, c_brush_traces, c_patch_traces; + + +byte *cmod_base; + +#ifndef BSPC +cvar_t *cm_noAreas; +cvar_t *cm_noCurves; +cvar_t *cm_playerCurveClip; +#endif + +cmodel_t box_model; +cplane_t *box_planes; +cbrush_t *box_brush; + +int CM_OrOfAllContentsFlagsInMap; + + +void CM_InitBoxHull (void); +void CM_FloodAreaConnections (clipMap_t &cm); + +clipMap_t SubBSP[MAX_SUB_BSP]; +int NumSubBSP = 0, TotalSubModels = 0; + +/* +=============================================================================== + + MAP LOADING + +=============================================================================== +*/ + +/* +================= +CMod_LoadShaders +================= +*/ +void CMod_LoadShaders( lump_t *l, clipMap_t &cm ) +{ + dshader_t *in; + int i, count; + CCMShader *out; + + in = (dshader_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) { + Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size"); + } + count = l->filelen / sizeof(*in); + + if (count < 1) { + Com_Error (ERR_DROP, "Map with no shaders"); + } + cm.shaders = (CCMShader *)Z_Malloc( (1 + count) * sizeof( *cm.shaders ), TAG_BSP, qtrue ); //+1 for the BOX_SIDES to point at + cm.numShaders = count; + + out = cm.shaders; + for ( i = 0; i < count; i++, in++, out++ ) + { + Q_strncpyz(out->shader, in->shader, MAX_QPATH); + out->contentFlags = LittleLong( in->contentFlags ); + out->surfaceFlags = LittleLong( in->surfaceFlags ); + } +} + + +/* +================= +CMod_LoadSubmodels +================= +*/ +void CMod_LoadSubmodels( lump_t *l, clipMap_t &cm ) { + dmodel_t *in; + cmodel_t *out; + int i, j, count; + int *indexes; + + in = (dmodel_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "CMod_LoadSubmodels: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) { + Com_Error (ERR_DROP, "Map with no models"); + } + + //FIXME: note that MAX_SUBMODELS - 1 is used for BOX_MODEL_HANDLE, if that slot gets used, that would be bad, no? + if ( count > MAX_SUBMODELS ) { + Com_Error( ERR_DROP, "MAX_SUBMODELS (%d) exceeded by %d", MAX_SUBMODELS, count-MAX_SUBMODELS ); + } + + cm.cmodels = (struct cmodel_s *) Z_Malloc( count * sizeof( *cm.cmodels ), TAG_BSP, qtrue ); + cm.numSubModels = count; + + for ( i=0 ; imins[j] = LittleFloat (in->mins[j]) - 1; + out->maxs[j] = LittleFloat (in->maxs[j]) + 1; + } + + //rww - I changed this to do the &cm == &cmg check. sof2 does not have to do this, + //but I think they have a different tracing system that allows it to catch subbsp + //stuff without extra leaf data. The reason we have to do this for subbsp instances + //is that they often are compiled in a sort of "prefab" form, so the first model isn't + //necessarily the world model. + if ( i == 0 && &cm == &cmg ) { + continue; // world model doesn't need other info + } + + // make a "leaf" just to hold the model's brushes and surfaces + out->leaf.numLeafBrushes = LittleLong( in->numBrushes ); + indexes = (int *) Z_Malloc( out->leaf.numLeafBrushes * 4, TAG_BSP, qfalse); + out->leaf.firstLeafBrush = indexes - cm.leafbrushes; + for ( j = 0 ; j < out->leaf.numLeafBrushes ; j++ ) { + indexes[j] = LittleLong( in->firstBrush ) + j; + } + + out->leaf.numLeafSurfaces = LittleLong( in->numSurfaces ); + indexes = (int *) Z_Malloc( out->leaf.numLeafSurfaces * 4, TAG_BSP, qfalse); + out->leaf.firstLeafSurface = indexes - cm.leafsurfaces; + for ( j = 0 ; j < out->leaf.numLeafSurfaces ; j++ ) { + indexes[j] = LittleLong( in->firstSurface ) + j; + } + } +} + + +/* +================= +CMod_LoadNodes + +================= +*/ +void CMod_LoadNodes( lump_t *l, clipMap_t &cm ) { + dnode_t *in; + int child; + cNode_t *out; + int i, j, count; + + in = (dnode_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map has no nodes"); + cm.nodes = (cNode_t *) Z_Malloc( count * sizeof( *cm.nodes ), TAG_BSP, qfalse); + cm.numNodes = count; + + out = cm.nodes; + + for (i=0 ; iplane = cm.planes + LittleLong( in->planeNum ); + for (j=0 ; j<2 ; j++) + { + child = LittleLong (in->children[j]); + out->children[j] = child; + } + } + +} + +/* +================= +CM_BoundBrush + +================= +*/ +void CM_BoundBrush( cbrush_t *b ) { + b->bounds[0][0] = -b->sides[0].plane->dist; + b->bounds[1][0] = b->sides[1].plane->dist; + + b->bounds[0][1] = -b->sides[2].plane->dist; + b->bounds[1][1] = b->sides[3].plane->dist; + + b->bounds[0][2] = -b->sides[4].plane->dist; + b->bounds[1][2] = b->sides[5].plane->dist; +} + + +/* +================= +CMod_LoadBrushes + +================= +*/ +void CMod_LoadBrushes( lump_t *l, clipMap_t &cm ) { + dbrush_t *in; + cbrush_t *out; + int i, count; + + in = (dbrush_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = l->filelen / sizeof(*in); + + cm.brushes = (cbrush_t *) Z_Malloc( ( BOX_BRUSHES + count ) * sizeof( *cm.brushes ), TAG_BSP, qfalse); + cm.numBrushes = count; + + out = cm.brushes; + + for ( i=0 ; isides = cm.brushsides + LittleLong(in->firstSide); + out->numsides = LittleLong(in->numSides); + + out->shaderNum = LittleLong( in->shaderNum ); + if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum ); + } + out->contents = cm.shaders[out->shaderNum].contentFlags; + CM_OrOfAllContentsFlagsInMap |= out->contents; + out->checkcount=0; + + CM_BoundBrush( out ); + } + +} + +/* +================= +CMod_LoadLeafs +================= +*/ +void CMod_LoadLeafs (lump_t *l, clipMap_t &cm) +{ + int i; + cLeaf_t *out; + dleaf_t *in; + int count; + + in = (dleaf_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no leafs"); + + cm.leafs = (cLeaf_t *) Z_Malloc( ( BOX_LEAFS + count ) * sizeof( *cm.leafs ), TAG_BSP, qfalse); + cm.numLeafs = count; + out = cm.leafs; + + for ( i=0 ; icluster = LittleLong (in->cluster); + out->area = LittleLong (in->area); + out->firstLeafBrush = LittleLong (in->firstLeafBrush); + out->numLeafBrushes = LittleLong (in->numLeafBrushes); + out->firstLeafSurface = LittleLong (in->firstLeafSurface); + out->numLeafSurfaces = LittleLong (in->numLeafSurfaces); + + if (out->cluster >= cm.numClusters) + cm.numClusters = out->cluster + 1; + if (out->area >= cm.numAreas) + cm.numAreas = out->area + 1; + } + + cm.areas = (cArea_t *) Z_Malloc( cm.numAreas * sizeof( *cm.areas ), TAG_BSP, qtrue ); + cm.areaPortals = (int *) Z_Malloc( cm.numAreas * cm.numAreas * sizeof( *cm.areaPortals ), TAG_BSP, qtrue ); +} + +/* +================= +CMod_LoadPlanes +================= +*/ +void CMod_LoadPlanes (lump_t *l, clipMap_t &cm) +{ + int i, j; + cplane_t *out; + dplane_t *in; + int count; + int bits; + + in = (dplane_t *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no planes"); + cm.planes = (struct cplane_s *) Z_Malloc( ( BOX_PLANES + count ) * sizeof( *cm.planes ), TAG_BSP, qfalse); + cm.numPlanes = count; + + out = cm.planes; + + for ( i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) + bits |= 1<dist = LittleFloat (in->dist); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +CMod_LoadLeafBrushes +================= +*/ +void CMod_LoadLeafBrushes (lump_t *l, clipMap_t &cm) +{ + int i; + int *out; + int *in; + int count; + + in = (int *)(cmod_base + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + cm.leafbrushes = (int *) Z_Malloc( ( BOX_BRUSHES + count ) * sizeof( *cm.leafbrushes ), TAG_BSP, qfalse); + cm.numLeafBrushes = count; + + out = cm.leafbrushes; + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = l->filelen / sizeof(*in); + + cm.leafsurfaces = (int *) Z_Malloc( count * sizeof( *cm.leafsurfaces ), TAG_BSP, qfalse); + cm.numLeafSurfaces = count; + + out = cm.leafsurfaces; + + for ( i=0 ; ifileofs); + if ( l->filelen % sizeof(*in) ) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = l->filelen / sizeof(*in); + + cm.brushsides = (cbrushside_t *) Z_Malloc( ( BOX_SIDES + count ) * sizeof( *cm.brushsides ), TAG_BSP, qfalse); + cm.numBrushSides = count; + + out = cm.brushsides; + + for ( i=0 ; iplaneNum ); + out->plane = &cm.planes[num]; + out->shaderNum = LittleLong( in->shaderNum ); + if ( out->shaderNum < 0 || out->shaderNum >= cm.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushSides: bad shaderNum: %i", out->shaderNum ); + } +// out->surfaceFlags = cm.shaders[out->shaderNum].surfaceFlags; + } +} + + +/* +================= +CMod_LoadEntityString +================= +*/ +void CMod_LoadEntityString( lump_t *l, clipMap_t &cm ) { + cm.entityString = (char *) Z_Malloc( l->filelen, TAG_BSP, qfalse); + cm.numEntityChars = l->filelen; + memcpy (cm.entityString, cmod_base + l->fileofs, l->filelen); +} + +/* +================= +CMod_LoadVisibility +================= +*/ +#define VIS_HEADER 8 +void CMod_LoadVisibility( lump_t *l, clipMap_t &cm ) { + int len; + byte *buf; + + len = l->filelen; + if ( !len ) { + cm.clusterBytes = ( cm.numClusters + 31 ) & ~31; + cm.visibility = (unsigned char *) Z_Malloc( cm.clusterBytes, TAG_BSP, qfalse); + memset( cm.visibility, 255, cm.clusterBytes ); + return; + } + buf = cmod_base + l->fileofs; + + cm.vised = qtrue; + cm.visibility = (unsigned char *) Z_Malloc( len, TAG_BSP, qtrue ); + cm.numClusters = LittleLong( ((int *)buf)[0] ); + cm.clusterBytes = LittleLong( ((int *)buf)[1] ); + memcpy (cm.visibility, buf + VIS_HEADER, len - VIS_HEADER ); +} + +//================================================================== + + +/* +================= +CMod_LoadPatches +================= +*/ +#define MAX_PATCH_VERTS 1024 +void CMod_LoadPatches( lump_t *surfs, lump_t *verts, clipMap_t &cm ) { + mapVert_t *dv, *dv_p; + dsurface_t *in; + int count; + int i, j; + int c; + cPatch_t *patch; + vec3_t points[MAX_PATCH_VERTS]; + int width, height; + int shaderNum; + + in = (dsurface_t *)(cmod_base + surfs->fileofs); + if (surfs->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + cm.numSurfaces = count = surfs->filelen / sizeof(*in); + cm.surfaces = (cPatch_t **) Z_Malloc( cm.numSurfaces * sizeof( cm.surfaces[0] ), TAG_BSP, qtrue ); + + dv = (mapVert_t *)(cmod_base + verts->fileofs); + if (verts->filelen % sizeof(*dv)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + + // scan through all the surfaces, but only load patches, + // not planar faces + for ( i = 0 ; i < count ; i++, in++ ) { + if ( LittleLong( in->surfaceType ) != MST_PATCH ) { + continue; // ignore other surfaces + } + // FIXME: check for non-colliding patches + + cm.surfaces[ i ] = patch = (cPatch_t *) Z_Malloc( sizeof( *patch ), TAG_BSP, qtrue ); + + // load the full drawverts onto the stack + width = LittleLong( in->patchWidth ); + height = LittleLong( in->patchHeight ); + c = width * height; + if ( c > MAX_PATCH_VERTS ) { + Com_Error( ERR_DROP, "ParseMesh: MAX_PATCH_VERTS" ); + } + + dv_p = dv + LittleLong( in->firstVert ); + for ( j = 0 ; j < c ; j++, dv_p++ ) { + points[j][0] = LittleFloat( dv_p->xyz[0] ); + points[j][1] = LittleFloat( dv_p->xyz[1] ); + points[j][2] = LittleFloat( dv_p->xyz[2] ); + } + + shaderNum = LittleLong( in->shaderNum ); + patch->contents = cm.shaders[shaderNum].contentFlags; + CM_OrOfAllContentsFlagsInMap |= patch->contents; + + patch->surfaceFlags = cm.shaders[shaderNum].surfaceFlags; + + // create the internal facet structure + patch->pc = CM_GeneratePatchCollide( width, height, points ); + } +} + +//================================================================== + +#ifdef BSPC +/* +================== +CM_FreeMap + +Free any loaded map and all submodels +================== +*/ +void CM_FreeMap(void) { + memset( &cm, 0, sizeof( cm ) ); + Hunk_ClearHigh(); + CM_ClearLevelPatches(); +} +#endif //BSPC + +unsigned CM_LumpChecksum(lump_t *lump) { + return LittleLong (Com_BlockChecksum (cmod_base + lump->fileofs, lump->filelen)); +} + +unsigned CM_Checksum(dheader_t *header) { + unsigned checksums[16]; + checksums[0] = CM_LumpChecksum(&header->lumps[LUMP_SHADERS]); + checksums[1] = CM_LumpChecksum(&header->lumps[LUMP_LEAFS]); + checksums[2] = CM_LumpChecksum(&header->lumps[LUMP_LEAFBRUSHES]); + checksums[3] = CM_LumpChecksum(&header->lumps[LUMP_LEAFSURFACES]); + checksums[4] = CM_LumpChecksum(&header->lumps[LUMP_PLANES]); + checksums[5] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHSIDES]); + checksums[6] = CM_LumpChecksum(&header->lumps[LUMP_BRUSHES]); + checksums[7] = CM_LumpChecksum(&header->lumps[LUMP_MODELS]); + checksums[8] = CM_LumpChecksum(&header->lumps[LUMP_NODES]); + checksums[9] = CM_LumpChecksum(&header->lumps[LUMP_SURFACES]); + checksums[10] = CM_LumpChecksum(&header->lumps[LUMP_DRAWVERTS]); + + return LittleLong(Com_BlockChecksum(checksums, 11 * 4)); +} + +/* +================== +CM_LoadMap + +Loads in the map and all submodels +================== +*/ +void *gpvCachedMapDiskImage = NULL; +char gsCachedMapDiskImage[MAX_QPATH]; +qboolean gbUsingCachedMapDataRightNow = qfalse; // if true, signifies that you can't delete this at the moment!! (used during z_malloc()-fail recovery attempt) + +// called in response to a "devmapbsp blah" or "devmapall blah" command, do NOT use inside CM_Load unless you pass in qtrue +// +// new bool return used to see if anything was freed, used during z_malloc failure re-try +// +qboolean CM_DeleteCachedMap(qboolean bGuaranteedOkToDelete) +{ + qboolean bActuallyFreedSomething = qfalse; + + if (bGuaranteedOkToDelete || !gbUsingCachedMapDataRightNow) + { + // dump cached disk image... + // + if (gpvCachedMapDiskImage) + { + Z_Free( gpvCachedMapDiskImage ); + gpvCachedMapDiskImage = NULL; + + bActuallyFreedSomething = qtrue; + } + gsCachedMapDiskImage[0] = '\0'; + + // force map loader to ignore cached internal BSP structures for next level CM_LoadMap() call... + // + cmg.name[0] = '\0'; + } + + return bActuallyFreedSomething; +} + +static void CM_LoadMap_Actual( const char *name, qboolean clientload, int *checksum, clipMap_t &cm ) { + const int *buf; + int i; + dheader_t header; + static unsigned last_checksum; + void *subBSPData = NULL; + + if ( !name || !name[0] ) { + Com_Error( ERR_DROP, "CM_LoadMap: NULL name" ); + } + +#ifndef BSPC + cm_noAreas = Cvar_Get ("cm_noAreas", "0", CVAR_CHEAT); + cm_noCurves = Cvar_Get ("cm_noCurves", "0", CVAR_CHEAT); + cm_playerCurveClip = Cvar_Get ("cm_playerCurveClip", "1", CVAR_ARCHIVE|CVAR_CHEAT ); +#endif + Com_DPrintf( "CM_LoadMap( %s, %i )\n", name, clientload ); + + if ( !strcmp( cm.name, name ) && clientload ) { + *checksum = last_checksum; + return; + } + + if (&cm == &cmg) + { + // if there was a cached disk image but the name was empty (ie ERR_DROP happened) or just doesn't match + // the current name, then ditch it... + // + if (gpvCachedMapDiskImage && + (gsCachedMapDiskImage[0] == '\0' || strcmp( gsCachedMapDiskImage, name )) + ) + { + Z_Free(gpvCachedMapDiskImage); + gpvCachedMapDiskImage = NULL; + gsCachedMapDiskImage[0] = '\0'; + + CM_ClearMap(); + } + } + + // if there's a valid map name, and it's the same as last time (respawn?), and it's the server-load, + // then keep the data from last time... + // + if (name[0] && !strcmp( cm.name, name ) && !clientload && &cm == &cmg ) + { + // clear some stuff that needs zeroing... + // + cm.floodvalid = 0; + //NO... don't reset this because the brush checkcounts are cached, + //so when you load up, brush checkcounts equal the cm.checkcount + //and the trace will be skipped (because everything loads and + //traces in the same exact order ever time you load the map) + cm.checkcount++;// = 0; + memset(cm.areas, 0, cm.numAreas * sizeof( *cm.areas )); + memset(cm.areaPortals, 0, cm.numAreas * cm.numAreas * sizeof( *cm.areaPortals )); + } + else + { + // ... else load map from scratch... + // + if (&cm == &cmg) + { + assert(!clientload); // logic check. I'm assuming that a client load doesn't get this far? + + // free old stuff + memset( &cm, 0, sizeof( cm ) ); + CM_ClearLevelPatches(); + Z_TagFree(TAG_BSP); + + if ( !name[0] ) { + cm.numLeafs = 1; + cm.numClusters = 1; + cm.numAreas = 1; + cm.cmodels = (struct cmodel_s *) Z_Malloc( sizeof( *cm.cmodels ), TAG_BSP, qtrue ); + *checksum = 0; + return; + } + } + + // load the file into a buffer that we either discard as usual at the bottom, or if we've got enough memory + // then keep it long enough to save the renderer re-loading it, then discard it after that. + // + fileHandle_t h; + const int iBSPLen = FS_FOpenFileRead( name, &h, qfalse ); + if(!h) + { + Com_Error (ERR_DROP, "Couldn't load %s", name); + return; + } + //rww - only do this when not loading a sub-bsp! + if (&cm == &cmg) + { + if (gpvCachedMapDiskImage && gsCachedMapDiskImage[0]) + { //didn't get cleared elsewhere so free it before we allocate the pointer again + //Maps with terrain will allow this to happen because they want everything to be cleared out (going between terrain and no-terrain is messy) + Z_Free(gpvCachedMapDiskImage); + } + gsCachedMapDiskImage[0] = '\0'; // flag that map isn't valid, until name is filled in + gpvCachedMapDiskImage = Z_Malloc( iBSPLen, TAG_BSP_DISKIMAGE, qfalse); + FS_Read(gpvCachedMapDiskImage, iBSPLen, h); + FS_FCloseFile( h ); + + buf = (int*) gpvCachedMapDiskImage; // so the rest of the code works as normal + } + else + { //otherwise, read straight in.. + subBSPData = Z_Malloc( iBSPLen, TAG_BSP_DISKIMAGE, qfalse); + FS_Read(subBSPData, iBSPLen, h); + FS_FCloseFile( h ); + + buf = (int*)subBSPData; + } + + // carry on as before... + + last_checksum = LittleLong (Com_BlockChecksum (buf, iBSPLen)); + + header = *(dheader_t *)buf; + for (i=0 ; i, if we've got enough ram to keep it for the renderer's disk-load... + // + extern qboolean Sys_LowPhysicalMemory(); + if (Sys_LowPhysicalMemory() //|| com_dedicated->integer // no need to check for dedicated in single-player codebase + ) + { + Z_Free( gpvCachedMapDiskImage ); + gpvCachedMapDiskImage = NULL; + } + else + { + // ... do nothing, and let the renderer free it after it's finished playing with it... + // + } + + if (subBSPData) + { + Z_Free(subBSPData); + } + + if (&cm == &cmg) + { +#if !defined(BSPC) + CM_LoadShaderText(false); +// MAT_Init((bool)(!clientload)); +#endif + CM_InitBoxHull (); +#if !defined(BSPC) + CM_SetupShaderProperties(); +#endif + + Q_strncpyz( gsCachedMapDiskImage, name, sizeof(gsCachedMapDiskImage) ); // so the renderer can check it + } + } + + *checksum = last_checksum; + + // do this whether or not the map was cached from last load... + // + CM_FloodAreaConnections (cm); + + // allow this to be cached if it is loaded by the server + if ( !clientload ) { + Q_strncpyz( cm.name, name, sizeof( cm.name ) ); + } + CM_CleanLeafCache(); +} + +// need a wrapper function around this because of multiple returns, need to ensure bool is correct... +// +void CM_LoadMap( const char *name, qboolean clientload, int *checksum, qboolean subBSP ) +{ + if (subBSP) + { + CM_LoadSubBSP(va("maps/%s.bsp", ((const char *)name) + 1), qfalse); + //CM_LoadMap_Actual( name, clientload, checksum, cmg ); + } + else + { + gbUsingCachedMapDataRightNow = qtrue; // !!!!!!!!!!!!!!!!!! + + CM_LoadMap_Actual( name, clientload, checksum, cmg ); + + gbUsingCachedMapDataRightNow = qfalse; // !!!!!!!!!!!!!!!!!! + } + /* + gbUsingCachedMapDataRightNow = qtrue; // !!!!!!!!!!!!!!!!!! + + CM_LoadMap_Actual( name, clientload, checksum, cmg ); + + gbUsingCachedMapDataRightNow = qfalse; // !!!!!!!!!!!!!!!!!! + */ +} + +qboolean CM_SameMap(char *server) +{ + if (!cmg.name[0] || !server || !server[0]) + { + return qfalse; + } + + if (Q_stricmp(cmg.name, va("maps/%s.bsp", server))) + { + return qfalse; + } + + return qtrue; +} + +qboolean CM_HasTerrain(void) +{ + if (cmg.landScape) + { + return qtrue; + } + + return qfalse; +} + +/* +================== +CM_ClearMap +================== +*/ +void CM_ClearMap( void ) +{ + int i; + + CM_OrOfAllContentsFlagsInMap = CONTENTS_BODY; + +#if !defined(BSPC) + CM_ShutdownShaderProperties(); +// MAT_Shutdown(); +#endif + + if (TheRandomMissionManager) + { + delete TheRandomMissionManager; + TheRandomMissionManager = 0; + } + + if (cmg.landScape) + { + delete cmg.landScape; + cmg.landScape = 0; + } + + memset( &cmg, 0, sizeof( cmg ) ); + CM_ClearLevelPatches(); + + for(i = 0; i < NumSubBSP; i++) + { + memset(&SubBSP[i], 0, sizeof(SubBSP[0])); + } + NumSubBSP = 0; + TotalSubModels = 0; +} + +int CM_TotalMapContents() +{ + return CM_OrOfAllContentsFlagsInMap; +} + +/* +================== +CM_ClipHandleToModel +================== +*/ +cmodel_t *CM_ClipHandleToModel( clipHandle_t handle, clipMap_t **clipMap ) +{ + int i; + int count; + + if ( handle < 0 ) + { + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle ); + } + if ( handle < cmg.numSubModels ) + { + if (clipMap) + { + *clipMap = &cmg; + } + return &cmg.cmodels[handle]; + } + if ( handle == BOX_MODEL_HANDLE ) + { + if (clipMap) + { + *clipMap = &cmg; + } + return &box_model; + } + + count = cmg.numSubModels; + for(i = 0; i < NumSubBSP; i++) + { + if (handle < count + SubBSP[i].numSubModels) + { + if (clipMap) + { + *clipMap = &SubBSP[i]; + } + return &SubBSP[i].cmodels[handle - count]; + } + count += SubBSP[i].numSubModels; + } + + if ( handle < MAX_SUBMODELS ) + { + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i < %i < %i", + cmg.numSubModels, handle, MAX_SUBMODELS ); + } + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle + MAX_SUBMODELS ); + + return NULL; +} +/* +================== +CM_InlineModel +================== +*/ +clipHandle_t CM_InlineModel( int index ) { + if ( index < 0 || index >= TotalSubModels ) { + Com_Error (ERR_DROP, "CM_InlineModel: bad number (may need to re-BSP map?)"); + } + return index; +} + +int CM_NumClusters( void ) { + return cmg.numClusters; +} + +int CM_NumInlineModels( void ) { + return cmg.numSubModels; +} + +char *CM_EntityString( void ) { + return cmg.entityString; +} + +char *CM_SubBSPEntityString( int index ) +{ + return SubBSP[index].entityString; +} + +int CM_LeafCluster( int leafnum ) { + if (leafnum < 0 || leafnum >= cmg.numLeafs) { + Com_Error (ERR_DROP, "CM_LeafCluster: bad number"); + } + return cmg.leafs[leafnum].cluster; +} + +int CM_LeafArea( int leafnum ) { + if ( leafnum < 0 || leafnum >= cmg.numLeafs ) { + Com_Error (ERR_DROP, "CM_LeafArea: bad number"); + } + return cmg.leafs[leafnum].area; +} + +//======================================================================= + + +/* +=================== +CM_InitBoxHull + +Set up the planes and nodes so that the six floats of a bounding box +can just be stored out and get a proper clipping hull structure. +=================== +*/ +void CM_InitBoxHull (void) +{ + int i; + int side; + cplane_t *p; + cbrushside_t *s; + + box_planes = &cmg.planes[cmg.numPlanes]; + + box_brush = &cmg.brushes[cmg.numBrushes]; + box_brush->numsides = 6; + box_brush->sides = cmg.brushsides + cmg.numBrushSides; + box_brush->contents = CONTENTS_BODY; + + box_model.leaf.numLeafBrushes = 1; +// box_model.leaf.firstLeafBrush = cmg.numBrushes; + box_model.leaf.firstLeafBrush = cmg.numLeafBrushes; + cmg.leafbrushes[cmg.numLeafBrushes] = cmg.numBrushes; + + for (i=0 ; i<6 ; i++) + { + side = i&1; + + // brush sides + s = &cmg.brushsides[cmg.numBrushSides+i]; + s->plane = cmg.planes + (cmg.numPlanes+i*2+side); + s->shaderNum = cmg.numShaders; //not storing flags directly anymore, so be sure to point @ a valid shader + + // planes + p = &box_planes[i*2]; + p->type = i>>1; + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = 1; + + p = &box_planes[i*2+1]; + p->type = 3 + (i>>1); + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = -1; + + SetPlaneSignbits( p ); + } +} + + + +/* +=================== +CM_HeadnodeForBox + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +=================== +*/ +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs) {//, const int contents ) { + box_planes[0].dist = maxs[0]; + box_planes[1].dist = -maxs[0]; + box_planes[2].dist = mins[0]; + box_planes[3].dist = -mins[0]; + box_planes[4].dist = maxs[1]; + box_planes[5].dist = -maxs[1]; + box_planes[6].dist = mins[1]; + box_planes[7].dist = -mins[1]; + box_planes[8].dist = maxs[2]; + box_planes[9].dist = -maxs[2]; + box_planes[10].dist = mins[2]; + box_planes[11].dist = -mins[2]; + + VectorCopy( mins, box_brush->bounds[0] ); + VectorCopy( maxs, box_brush->bounds[1] ); + + //FIXME: this is the "correct" way, but not the way JK2 was designed around... fix for further projects + //box_brush->contents = contents; + + return BOX_MODEL_HANDLE; +} + + +/* +=================== +CM_ModelBounds +=================== +*/ +void CM_ModelBounds( clipMap_t &cm, clipHandle_t model, vec3_t mins, vec3_t maxs ) +{ + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + VectorCopy( cmod->mins, mins ); + VectorCopy( cmod->maxs, maxs ); +} + +/* +=================== +CM_RegisterTerrain + +Allows physics to examine the terrain data. +=================== +*/ +#if !defined(BSPC) +CCMLandScape *CM_RegisterTerrain(const char *config, bool server) +{ + thandle_t terrainId; + CCMLandScape *ls; + + terrainId = atol(Info_ValueForKey(config, "terrainId")); + if(terrainId && cmg.landScape) + { + // Already spawned so just return + ls = cmg.landScape; + ls->IncreaseRefCount(); + return(ls); + } + // Doesn't exist so create and link in + //cmg.numTerrains++; + ls = CM_InitTerrain(config, 1, server); + + // Increment for the next instance + if (cmg.landScape) + { + Com_Error(ERR_DROP, "You can't have more than one terrain brush."); + } + cmg.landScape = ls; + return(ls); +} + +/* +=================== +CM_ShutdownTerrain +=================== +*/ + +void CM_ShutdownTerrain( thandle_t terrainId) +{ + CCMLandScape *landscape; + + landscape = cmg.landScape; + if (landscape) + { + landscape->DecreaseRefCount(); + if(landscape->GetRefCount() <= 0) + { + delete landscape; + cmg.landScape = NULL; + } + } +} +#endif + +int CM_LoadSubBSP(const char *name, qboolean clientload) +{ + int i; + int checksum; + int count; + + count = cmg.numSubModels; + for(i = 0; i < NumSubBSP; i++) + { + if (!stricmp(name, SubBSP[i].name)) + { + return count; + } + count += SubBSP[i].numSubModels; + } + + if (NumSubBSP == MAX_SUB_BSP) + { + Com_Error (ERR_DROP, "CM_LoadSubBSP: too many unique sub BSPs"); + } + + CM_LoadMap_Actual( name, clientload, &checksum, SubBSP[NumSubBSP] ); + NumSubBSP++; + + return count; +} + +int CM_FindSubBSP(int modelIndex) +{ + int i; + int count; + + count = cmg.numSubModels; + if (modelIndex < count) + { // belongs to the main bsp + return -1; + } + + for(i = 0; i < NumSubBSP; i++) + { + count += SubBSP[i].numSubModels; + if (modelIndex < count) + { + return i; + } + } + return -1; +} + +void CM_GetWorldBounds ( vec3_t mins, vec3_t maxs ) +{ + VectorCopy ( cmg.cmodels[0].mins, mins ); + VectorCopy ( cmg.cmodels[0].maxs, maxs ); +} + +int CM_ModelContents_Actual( clipHandle_t model, clipMap_t *cm ) +{ + cmodel_t *cmod; + int contents = 0; + int i; + + if (!cm) + { + cm = &cmg; + } + + cmod = CM_ClipHandleToModel( model, &cm ); + + //MCG ADDED - return the contents, too + if( cmod->leaf.numLeafBrushes ) // check for brush + { + int brushNum; + for ( i = cmod->leaf.firstLeafBrush; i < cmod->leaf.firstLeafBrush+cmod->leaf.numLeafBrushes; i++ ) + { + brushNum = cm->leafbrushes[i]; + contents |= cm->brushes[brushNum].contents; + } + } + if( cmod->leaf.numLeafSurfaces ) // if not brush, check for patch + { + int surfaceNum; + for ( i = cmod->leaf.firstLeafSurface; i < cmod->leaf.firstLeafSurface+cmod->leaf.numLeafSurfaces; i++ ) + { + surfaceNum = cm->leafsurfaces[i]; + if ( cm->surfaces[surfaceNum] != NULL ) + {//HERNH? How could we have a null surf within our cmod->leaf.numLeafSurfaces? + contents |= cm->surfaces[surfaceNum]->contents; + } + } + } + return contents; +} + +int CM_ModelContents( clipHandle_t model, int subBSPIndex ) +{ + if (subBSPIndex < 0) + { + return CM_ModelContents_Actual(model, NULL); + } + + return CM_ModelContents_Actual(model, &SubBSP[subBSPIndex]); +} + +//support for save/load games +/* +=================== +CM_WritePortalState + +Writes the portal state to a savegame file +=================== +*/ +// +qboolean SG_Append(unsigned long chid, const void *data, int length); +int SG_Read(unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr = NULL); + +void CM_WritePortalState () +{ + SG_Append('PRTS', (void *)cmg.areaPortals, cmg.numAreas * cmg.numAreas * sizeof( *cmg.areaPortals )); +} + +/* +=================== +CM_ReadPortalState + +Reads the portal state from a savegame file +and recalculates the area connections +=================== +*/ +void CM_ReadPortalState () +{ + SG_Read('PRTS', (void *)cmg.areaPortals, cmg.numAreas * cmg.numAreas * sizeof( *cmg.areaPortals )); + CM_FloodAreaConnections (cmg); +} + diff --git a/code/qcommon/cm_load_xbox.cpp b/code/qcommon/cm_load_xbox.cpp new file mode 100644 index 0000000..32f03ba --- /dev/null +++ b/code/qcommon/cm_load_xbox.cpp @@ -0,0 +1,1280 @@ +// cmodel.c -- model loading + +#include "cm_local.h" +#include "cm_patch.h" +#include "../renderer/tr_local.h" +#include "../RMG/RM_Headers.h" + +#include "sparc.h" +#include "../zlib/zlib.h" + +static SPARC visData; + +void *SparcAllocator(unsigned int size) +{ + return Z_Malloc(size, TAG_BSP, false); +} + +void SparcDeallocator(void *ptr) +{ + Z_Free(ptr); +} + +extern world_t s_worldData; + + +void CM_LoadShaderText(bool forceReload); + +#ifdef BSPC +void SetPlaneSignbits (cplane_t *out) { + int bits, j; + + // for fast box on planeside test + bits = 0; + for (j=0 ; j<3 ; j++) { + if (out->normal[j] < 0) { + bits |= 1<signbits = bits; +} +#endif //BSPC + +// to allow boxes to be treated as brush models, we allocate +// some extra indexes along with those needed by the map +#define BOX_BRUSHES 1 +#define BOX_SIDES 6 +#define BOX_LEAFS 2 +#define BOX_PLANES 12 + +#define LL(x) x=LittleLong(x) + +clipMap_t cmg; +int c_pointcontents; +int c_traces, c_brush_traces, c_patch_traces; + + +byte *cmod_base; + +#ifndef BSPC +cvar_t *cm_noAreas; +cvar_t *cm_noCurves; +cvar_t *cm_playerCurveClip; +#endif + +cmodel_t box_model; +cplane_t *box_planes; +cbrush_t *box_brush; + +int CM_OrOfAllContentsFlagsInMap; + + +void CM_InitBoxHull (void); +void CM_FloodAreaConnections (void); + +clipMap_t SubBSP[MAX_SUB_BSP]; +int NumSubBSP = 0, TotalSubModels = 0; + +/* +=============================================================================== + + MAP LOADING + +=============================================================================== +*/ + +/* +================= +CMod_LoadShaders +================= +*/ +void CMod_LoadShaders( void *data, int len ) { + dshader_t *in; + int i, count; + CCMShader *out; + + in = (dshader_t *)(data); + if (len % sizeof(*in)) { + Com_Error (ERR_DROP, "CMod_LoadShaders: funny lump size"); + } + count = len / sizeof(*in); + + if (count < 1) { + Com_Error (ERR_DROP, "Map with no shaders"); + } + cmg.shaders = (CCMShader *) Z_Malloc( count * sizeof( *cmg.shaders ), TAG_BSP, qfalse); + cmg.numShaders = count; + + s_worldData.shaders = (dshader_t *) Z_Malloc ( count*sizeof(dshader_t), TAG_BSP, qfalse ); + s_worldData.numShaders = count; + + out = cmg.shaders; + for ( i = 0; i < count; i++, in++, out++ ) + { + Q_strncpyz(out->shader, in->shader, MAX_QPATH); + out->contentFlags = in->contentFlags; + out->surfaceFlags = in->surfaceFlags; + + Q_strncpyz(s_worldData.shaders[i].shader, in->shader, MAX_QPATH); + s_worldData.shaders[i].contentFlags = in->contentFlags; + s_worldData.shaders[i].surfaceFlags = in->surfaceFlags; + } +} + + +/* +================= +CMod_LoadSubmodels +================= +*/ +void CMod_LoadSubmodels( void *data, int len ) { + dmodel_t *in; + cmodel_t *out; + int i, j, count; + int *indexes; + + in = (dmodel_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "CMod_LoadSubmodels: funny lump size"); + count = len / sizeof(*in); + + if (count < 1) { + Com_Error (ERR_DROP, "Map with no models"); + } + + if ( count > MAX_SUBMODELS ) { + Com_Error( ERR_DROP, "MAX_SUBMODELS (%d) exceeded by %d", MAX_SUBMODELS, count-MAX_SUBMODELS ); + } + + cmg.cmodels = (struct cmodel_s *) Z_Malloc( count * sizeof( *cmg.cmodels ), TAG_BSP, qtrue ); + cmg.numSubModels = count; + + for ( i=0 ; imins[j] = in->mins[j] - 1; + out->maxs[j] = in->maxs[j] + 1; + } + + if ( i == 0 ) { + continue; // world model doesn't need other info + } + + // make a "leaf" just to hold the model's brushes and surfaces + out->leaf.numLeafBrushes = in->numBrushes; + indexes = (int *) Z_Malloc( out->leaf.numLeafBrushes * 4, TAG_BSP, qfalse); + out->leaf.firstLeafBrush = indexes - cmg.leafbrushes; + for ( j = 0 ; j < out->leaf.numLeafBrushes ; j++ ) { + indexes[j] = in->firstBrush + j; + } + + out->leaf.numLeafSurfaces = in->numSurfaces; + indexes = (int *) Z_Malloc( out->leaf.numLeafSurfaces * 4, TAG_BSP, qfalse); + out->leaf.firstLeafSurface = indexes - cmg.leafsurfaces; + for ( j = 0 ; j < out->leaf.numLeafSurfaces ; j++ ) { + indexes[j] = in->firstSurface + j; + } + } +} + +/* +================= +CMod_LoadNodes + +================= +*/ +void CMod_LoadNodes( void *data, int len ) { + dnode_t *in; + cNode_t *out; + int i, count; + + in = (dnode_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = len / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map has no nodes"); + cmg.nodes = (cNode_t *) Z_Malloc( count * sizeof( *cmg.nodes ), TAG_BSP, qfalse); + cmg.numNodes = count; + + out = cmg.nodes; + + for (i=0 ; ichildren[0] = in->children[0]; + out->children[1] = in->children[1]; + } +} + +/* +================= +CM_BoundBrush + +================= +*/ +void CM_BoundBrush( cbrush_t *b ) { + b->bounds[0][0] = -cmg.planes[b->sides[0].planeNum.GetValue()].dist; + b->bounds[1][0] = cmg.planes[b->sides[1].planeNum.GetValue()].dist; + + b->bounds[0][1] = -cmg.planes[b->sides[2].planeNum.GetValue()].dist; + b->bounds[1][1] = cmg.planes[b->sides[3].planeNum.GetValue()].dist; + + b->bounds[0][2] = -cmg.planes[b->sides[4].planeNum.GetValue()].dist; + b->bounds[1][2] = cmg.planes[b->sides[5].planeNum.GetValue()].dist; +} + + +/* +================= +CMod_LoadBrushes + +================= +*/ +void CMod_LoadBrushes( void *data, int len ) { + dbrush_t *in; + cbrush_t *out; + int i, count; + + in = (dbrush_t *)(data); + if (len % sizeof(*in)) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = len / sizeof(*in); + + cmg.brushes = (cbrush_t *) Z_Malloc( ( BOX_BRUSHES + count ) * sizeof( *cmg.brushes ), TAG_BSP, qfalse); + cmg.numBrushes = count; + + out = cmg.brushes; + + for ( i=0 ; isides = cmg.brushsides + in->firstSide; + out->numsides = in->numSides; + + out->shaderNum = in->shaderNum; + if ( out->shaderNum < 0 || out->shaderNum >= cmg.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushes: bad shaderNum: %i", out->shaderNum ); + } + out->contents = cmg.shaders[out->shaderNum].contentFlags; + //TEMP HACK: for water that cuts vis but is not solid!!! + if ( cmg.shaders[out->shaderNum].surfaceFlags & SURF_SLICK ) + { + out->contents &= ~CONTENTS_SOLID; + } + + CM_OrOfAllContentsFlagsInMap |= out->contents; + + CM_BoundBrush( out ); + } + +} + +/* +================= +CMod_LoadLeafs +================= +*/ +void CMod_LoadLeafs (void *data, int len) +{ + int i; + cLeaf_t *out; + dleaf_t *in; + int count; + + in = (dleaf_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = len / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no leafs"); + + cmg.leafs = (cLeaf_t *) Z_Malloc( ( BOX_LEAFS + count ) * sizeof( *cmg.leafs ), TAG_BSP, qfalse); + cmg.numLeafs = count; + out = cmg.leafs; + + for ( i=0 ; icluster = in->cluster; + out->area = in->area; + out->firstLeafBrush = in->firstLeafBrush; + out->numLeafBrushes = in->numLeafBrushes; + out->firstLeafSurface = in->firstLeafSurface; + out->numLeafSurfaces = in->numLeafSurfaces; + + if (out->cluster >= cmg.numClusters) + cmg.numClusters = out->cluster + 1; + if (out->area >= cmg.numAreas) + cmg.numAreas = out->area + 1; + } + + cmg.areas = (cArea_t *) Z_Malloc( cmg.numAreas * sizeof( *cmg.areas ), TAG_BSP, qtrue ); + + extern qboolean vidRestartReloadMap; + if (!vidRestartReloadMap) + { + cmg.areaPortals = (int *) Z_Malloc( cmg.numAreas * cmg.numAreas * sizeof( *cmg.areaPortals ), TAG_BSP, qtrue ); + } +} + +/* +================= +CMod_LoadPlanes +================= +*/ +void CMod_LoadPlanes (void *data, int len) +{ + int i, j; + cplane_t *out; + dplane_t *in; + int count; + int bits; + + in = (dplane_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = len / sizeof(*in); + + if (count < 1) + Com_Error (ERR_DROP, "Map with no planes"); + cmg.planes = (struct cplane_s *) Z_Malloc( ( BOX_PLANES + count ) * sizeof( *cmg.planes ), TAG_BSP, qfalse); + cmg.numPlanes = count; + + out = cmg.planes; + + for ( i=0 ; inormal[j] = in->normal[j]; + if (out->normal[j] < 0) + bits |= 1<dist = in->dist; + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +CMod_LoadLeafBrushes +================= +*/ +void CMod_LoadLeafBrushes (void *data, int len) +{ + int *out; + int *in; + int count; + + in = (int *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + count = len / sizeof(*in); + + cmg.leafbrushes = (int *) Z_Malloc( ( BOX_BRUSHES + count ) * sizeof( *cmg.leafbrushes ), TAG_BSP, qfalse); + cmg.numLeafBrushes = count; + + out = cmg.leafbrushes; + + memcpy(out, in, len); +} + +/* +================= +CMod_LoadBrushSides +================= +*/ +void CMod_LoadBrushSides (void *data, int len) +{ + int i; + cbrushside_t *out; + dbrushside_t *in; + int count; + + in = (dbrushside_t *)(data); + if ( len % sizeof(*in) ) { + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + } + count = len / sizeof(*in); + + cmg.brushsides = (cbrushside_t *) Z_Malloc( ( BOX_SIDES + count ) * sizeof( *cmg.brushsides ), TAG_BSP, qfalse); + cmg.numBrushSides = count; + + out = cmg.brushsides; + + for ( i=0 ; iplaneNum = in->planeNum; + assert(in->planeNum == out->planeNum.GetValue()); + + out->shaderNum = in->shaderNum; + if ( out->shaderNum < 0 || out->shaderNum >= cmg.numShaders ) { + Com_Error( ERR_DROP, "CMod_LoadBrushSides: bad shaderNum: %i", out->shaderNum ); + } + } +} + + +/* +================= +CMod_LoadEntityString +================= +*/ +void CMod_LoadEntityString( void *data, int len ) { + cmg.entityString = (char *) Z_Malloc( len, TAG_BSP, qfalse); + cmg.numEntityChars = len; + memcpy (cmg.entityString, data, len); +} + +/* +================= +CMod_LoadVisibility +================= +*/ +#define VIS_HEADER 8 +void CMod_LoadVisibility( void *data, int len ) { + char *buf; + + if ( !len ) { + cmg.visibility = NULL; + return; + } + buf = (char*)data; + + visData.SetAllocator(SparcAllocator, SparcDeallocator); + + cmg.vised = qtrue; + cmg.numClusters = ((int *)buf)[0]; + cmg.clusterBytes = ((int *)buf)[1]; + visData.Load(buf + VIS_HEADER, len - VIS_HEADER); + cmg.visibility = &visData; + RE_SetWorldVisData(&visData); +} + +//================================================================== + + +/* +================= +CMod_LoadPatches +================= +*/ +#define MAX_PATCH_VERTS 1024 + +void CMod_LoadPatches( void *verts, int vertlen, void *surfaces, int surfacelen, int numsurfs ) { + mapVert_t *dv, *dv_p; + dpatch_t *in; + int count; + int i, j; + int c; + cPatch_t *patch; + vec3_t points[MAX_PATCH_VERTS]; + int width, height; + int shaderNum; + + count = surfacelen / sizeof(*in); + + cmg.numSurfaces = numsurfs; + cmg.surfaces = (cPatch_t **) Z_Malloc( cmg.numSurfaces * sizeof( cmg.surfaces[0] ), TAG_BSP, qtrue ); + + dv = (mapVert_t *)(verts); + if (vertlen % sizeof(*dv)) + Com_Error (ERR_DROP, "MOD_LoadBmodel: funny lump size"); + + unsigned char* patchScratch = (unsigned char*)Z_Malloc( sizeof( *patch ) * count, TAG_BSP, qtrue); + + extern void CM_GridAlloc(); + extern void CM_PatchCollideFromGridTempAlloc(); + extern void CM_PreparePatchCollide(int num); + extern void CM_TempPatchPlanesAlloc(); + CM_GridAlloc(); + CM_PatchCollideFromGridTempAlloc(); + CM_PreparePatchCollide(count); + CM_TempPatchPlanesAlloc(); + + facetLoad_t *facetbuf = (facetLoad_t*)Z_Malloc( + MAX_PATCH_PLANES*sizeof(facetLoad_t), TAG_TEMP_WORKSPACE, qfalse); + + int *gridbuf = (int*)Z_Malloc( + CM_MAX_GRID_SIZE*CM_MAX_GRID_SIZE*2*sizeof(int), TAG_TEMP_WORKSPACE, qfalse); + + for ( i = 0 ; i < count ; i++) { + in = (dpatch_t *)surfaces + i; + + cmg.surfaces[ in->code ] = patch = (cPatch_t *) patchScratch; + patchScratch += sizeof( *patch ); + + // load the full drawverts onto the stack + width = in->patchWidth; + height = in->patchHeight; + c = width * height; + if ( c > MAX_PATCH_VERTS ) { + Com_Error( ERR_DROP, "ParseMesh: MAX_PATCH_VERTS" ); + } + + dv_p = dv + (in->verts >> 12); + for ( j = 0 ; j < c ; j++, dv_p++ ) { + points[j][0] = dv_p->xyz[0]; + points[j][1] = dv_p->xyz[1]; + points[j][2] = dv_p->xyz[2]; + } + + shaderNum = in->shaderNum; + patch->contents = cmg.shaders[shaderNum].contentFlags; + CM_OrOfAllContentsFlagsInMap |= patch->contents; + + patch->surfaceFlags = cmg.shaders[shaderNum].surfaceFlags; + + // create the internal facet structure + patch->pc = CM_GeneratePatchCollide( width, height, points, facetbuf, gridbuf ); + } + + extern void CM_GridDealloc(); + extern void CM_PatchCollideFromGridTempDealloc(); + extern void CM_TempPatchPlanesDealloc(); + CM_PatchCollideFromGridTempDealloc(); + CM_GridDealloc(); + CM_TempPatchPlanesDealloc(); + + Z_Free(gridbuf); + Z_Free(facetbuf); +} + +//================================================================== + +#ifdef BSPC +/* +================== +CM_FreeMap + +Free any loaded map and all submodels +================== +*/ +void CM_FreeMap(void) { + memset( &cmg, 0, sizeof( cmg ) ); + Hunk_ClearHigh(); + CM_ClearLevelPatches(); +} +#endif //BSPC + + +/* +================== +CM_LoadMap + +Loads in the map and all submodels +================== +*/ +void *gpvCachedMapDiskImage = NULL; +char gsCachedMapDiskImage[MAX_QPATH]; +qboolean gbUsingCachedMapDataRightNow = qfalse; // if true, signifies that you can't delete this at the moment!! (used during z_malloc()-fail recovery attempt) + +// called in response to a "devmapbsp blah" or "devmapall blah" command, do NOT use inside CM_Load unless you pass in qtrue +// +// new bool return used to see if anything was freed, used during z_malloc failure re-try +// +qboolean CM_DeleteCachedMap(qboolean bGuaranteedOkToDelete) +{ + qboolean bActuallyFreedSomething = qfalse; + + if (bGuaranteedOkToDelete || !gbUsingCachedMapDataRightNow) + { + // dump cached disk image... + // + if (gpvCachedMapDiskImage) + { + Z_Free( gpvCachedMapDiskImage ); + gpvCachedMapDiskImage = NULL; + + bActuallyFreedSomething = qtrue; + } + gsCachedMapDiskImage[0] = '\0'; + + // force map loader to ignore cached internal BSP structures for next level CM_LoadMap() call... + // + cmg.name[0] = '\0'; + } + + return bActuallyFreedSomething; +} + +void CM_Free(void) +{ + CM_ClearLevelPatches(); + visData.Release(); + Z_TagFree(TAG_BSP); +} + +void R_LoadSurfaces( int count ); +void R_LoadPatches( void *verts, int vertlen, + void *surfaces, int surfacelen ); +void R_LoadTriSurfs( void *indexdata, int indexlen, + void *verts, int vertlen, + void *surfaces, int surfacelen ); +void R_LoadFaces( void *indexdata, int indexlen, + void *verts, int vertlen, + void *surfaces, int surfacelen ); +void R_LoadFlares( void *surfaces, int surfacelen ); +extern void R_LoadShaders( void ); +extern void R_LoadLightmaps( void *data, int len, const char *psMapName ); +extern byte *fileBase; +extern void UpdateLoadingAnimation(); +static void CM_LoadMap_Actual( const char *name, qboolean clientload, int *checksum ) { + const int *buf = NULL; + const int *surfBuf = NULL; + static unsigned last_checksum; + char lmName[MAX_QPATH]; + char stripName[MAX_QPATH]; + Lump outputLump; + + if ( !name || !name[0] ) { + Com_Error( ERR_DROP, "CM_LoadMap: NULL name" ); + } + +#ifndef BSPC + cm_noAreas = Cvar_Get ("cm_noAreas", "0", CVAR_CHEAT); + cm_noCurves = Cvar_Get ("cm_noCurves", "0", CVAR_CHEAT); + cm_playerCurveClip = Cvar_Get ("cm_playerCurveClip", "1", CVAR_ARCHIVE|CVAR_CHEAT ); +#endif + Com_DPrintf( "CM_LoadMap( %s, %i )\n", name, clientload ); + + if ( !strcmp( cmg.name, name ) && clientload ) { + *checksum = last_checksum; + return; + } + + // free old stuff + extern qboolean vidRestartReloadMap; + int* ap; + if (vidRestartReloadMap) ap = cmg.areaPortals; + memset( &cmg, 0, sizeof( cmg ) ); + if (vidRestartReloadMap) cmg.areaPortals = ap; + + if ( !name[0] ) { + cmg.numLeafs = 1; + cmg.numClusters = 1; + cmg.numAreas = 1; + cmg.cmodels = (struct cmodel_s *) Z_Malloc( sizeof( *cmg.cmodels ), TAG_BSP, qtrue ); + *checksum = 0; + return; + } + + last_checksum = crc32(0, (const Bytef *)name, strlen(name)); + COM_StripExtension(name, stripName); + + UpdateLoadingAnimation(); + + // load into heap + outputLump.load(stripName, "shaders"); + CMod_LoadShaders( outputLump.data, outputLump.len ); + R_LoadShaders(); + + UpdateLoadingAnimation(); + + strcpy(lmName, name); + outputLump.load(stripName, "lightmaps"); + R_LoadLightmaps( outputLump.data, outputLump.len, lmName); + + UpdateLoadingAnimation(); + + { + fileBase = NULL; + outputLump.clear(); + + Lump misc; + misc.load(stripName, "misc"); + + int num_surfs = *(int*)misc.data; + misc.clear(); + + R_LoadSurfaces(num_surfs); + + UpdateLoadingAnimation(); + + Lump verts; + verts.load(stripName, "verts"); + + Lump patches; + patches.load(stripName, "patches"); + + UpdateLoadingAnimation(); + + CMod_LoadPatches(verts.data, verts.len, + patches.data, patches.len, + num_surfs ); + R_LoadPatches(verts.data, verts.len, + patches.data, patches.len); + + UpdateLoadingAnimation(); + + patches.clear(); + + Lump indexes; + indexes.load(stripName, "indexes"); + + Lump trisurfs; + trisurfs.load(stripName, "trisurfs"); + + UpdateLoadingAnimation(); + + R_LoadTriSurfs(indexes.data, indexes.len, + verts.data, verts.len, + trisurfs.data, trisurfs.len); + + trisurfs.clear(); + + UpdateLoadingAnimation(); + + Lump faces; + faces.load(stripName, "faces"); + + R_LoadFaces(indexes.data, indexes.len, + verts.data, verts.len, + faces.data, faces.len); + + UpdateLoadingAnimation(); + + Lump flares; + flares.load(stripName, "flares"); + + R_LoadFlares(flares.data, flares.len); + } + + UpdateLoadingAnimation(); + + outputLump.load(stripName, "leafs"); + CMod_LoadLeafs (outputLump.data, outputLump.len); + + outputLump.load(stripName, "leafbrushes"); + CMod_LoadLeafBrushes (outputLump.data, outputLump.len); + + UpdateLoadingAnimation(); + + cmg.leafsurfaces = NULL; + outputLump.load(stripName, "planes"); + CMod_LoadPlanes (outputLump.data, outputLump.len); + + outputLump.load(stripName, "brushsides"); + CMod_LoadBrushSides (outputLump.data, outputLump.len); + outputLump.load(stripName, "brushes"); + CMod_LoadBrushes (outputLump.data, outputLump.len); + + UpdateLoadingAnimation(); + + outputLump.load(stripName, "models"); + CMod_LoadSubmodels (outputLump.data, outputLump.len); + + outputLump.load(stripName, "nodes"); + CMod_LoadNodes (outputLump.data, outputLump.len); + + UpdateLoadingAnimation(); + + outputLump.load(stripName, "entities"); + CMod_LoadEntityString (outputLump.data, outputLump.len); + + outputLump.load(stripName, "visibility"); + CMod_LoadVisibility( outputLump.data, outputLump.len); + + UpdateLoadingAnimation(); + + TotalSubModels += cmg.numSubModels; + + CM_InitBoxHull (); + + *checksum = last_checksum; + + // do this whether or not the map was cached from last load... + // + CM_FloodAreaConnections (); + + UpdateLoadingAnimation(); + + // allow this to be cached if it is loaded by the server + if ( !clientload ) { + Q_strncpyz( cmg.name, name, sizeof( cmg.name ) ); + } + CM_CleanLeafCache(); +} + +// need a wrapper function around this because of multiple returns, need to ensure bool is correct... +// +void CM_LoadMap( const char *name, qboolean clientload, int *checksum ) +{ + CM_LoadMap_Actual( name, clientload, checksum ); +} + +qboolean CM_SameMap(char *server) +{ + if (!cmg.name[0] || !server || !server[0]) + { + return qfalse; + } + + if (Q_stricmp(cmg.name, va("maps/%s.bsp", server))) + { + return qfalse; + } + + return qtrue; +} + +#ifndef _XBOX +qboolean CM_HasTerrain(void) +{ + if (cmg.landScape) + return qtrue; + return qfalse; +} +#endif + +/* +================== +CM_ClearMap +================== +*/ +void CM_ClearMap( void ) +{ + int i; + + CM_OrOfAllContentsFlagsInMap = CONTENTS_BODY; + +#if !defined(BSPC) +// CM_ShutdownShaderProperties(); +// MAT_Shutdown(); +#endif + +#ifndef _XBOX + if (TheRandomMissionManager) + { + delete TheRandomMissionManager; + TheRandomMissionManager = 0; + } + + if (cmg.landScape) + { + delete cmg.landScape; + cmg.landScape = 0; + } +#endif + + memset( &cmg, 0, sizeof( cmg ) ); + CM_ClearLevelPatches(); + + for(i = 0; i < NumSubBSP; i++) + { + memset(&SubBSP[i], 0, sizeof(SubBSP[0])); + } + NumSubBSP = 0; + TotalSubModels = 0; +} + +int CM_TotalMapContents() +{ + return CM_OrOfAllContentsFlagsInMap; +} + +/* +================== +CM_ClipHandleToModel +================== +*/ +cmodel_t *CM_ClipHandleToModel( clipHandle_t handle, clipMap_t **clipMap ) +{ + int i; + int count; + + if ( handle < 0 ) + { + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle ); + } + if ( handle < cmg.numSubModels ) + { + if (clipMap) + { + *clipMap = &cmg; + } + return &cmg.cmodels[handle]; + } + if ( handle == BOX_MODEL_HANDLE ) + { + if (clipMap) + { + *clipMap = &cmg; + } + return &box_model; + } + + count = cmg.numSubModels; + for(i = 0; i < NumSubBSP; i++) + { + if (handle < count + SubBSP[i].numSubModels) + { + if (clipMap) + { + *clipMap = &SubBSP[i]; + } + return &SubBSP[i].cmodels[handle - count]; + } + count += SubBSP[i].numSubModels; + } + + if ( handle < MAX_SUBMODELS ) + { + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i < %i < %i", + cmg.numSubModels, handle, MAX_SUBMODELS ); + } + Com_Error( ERR_DROP, "CM_ClipHandleToModel: bad handle %i", handle + MAX_SUBMODELS ); + + return NULL; +} +/* +================== +CM_InlineModel +================== +*/ +clipHandle_t CM_InlineModel( int index ) { + if ( index < 0 || index >= TotalSubModels ) { + Com_Error (ERR_DROP, "CM_InlineModel: bad number (may need to re-BSP map?)"); + } + return index; +} + +int CM_NumClusters( void ) { + return cmg.numClusters; +} + +int CM_NumInlineModels( void ) { + return cmg.numSubModels; +} + +char *CM_EntityString( void ) { + return cmg.entityString; +} + +char *CM_SubBSPEntityString( int index ) +{ + return SubBSP[index].entityString; +} + +int CM_LeafCluster( int leafnum ) { + if (leafnum < 0 || leafnum >= cmg.numLeafs) { + Com_Error (ERR_DROP, "CM_LeafCluster: bad number"); + } + return cmg.leafs[leafnum].cluster; +} + +int CM_LeafArea( int leafnum ) { + if ( leafnum < 0 || leafnum >= cmg.numLeafs ) { + Com_Error (ERR_DROP, "CM_LeafArea: bad number"); + } + return cmg.leafs[leafnum].area; +} + +//======================================================================= + + +/* +=================== +CM_InitBoxHull + +Set up the planes and nodes so that the six floats of a bounding box +can just be stored out and get a proper clipping hull structure. +=================== +*/ +void CM_InitBoxHull (void) +{ + int i; + int side; + cplane_t *p; + cbrushside_t *s; + + box_planes = &cmg.planes[cmg.numPlanes]; + + box_brush = &cmg.brushes[cmg.numBrushes]; + box_brush->numsides = 6; + box_brush->sides = cmg.brushsides + cmg.numBrushSides; + box_brush->contents = CONTENTS_BODY; + + box_model.leaf.numLeafBrushes = 1; +// box_model.leaf.firstLeafBrush = cmg.numBrushes; + box_model.leaf.firstLeafBrush = cmg.numLeafBrushes; + cmg.leafbrushes[cmg.numLeafBrushes] = cmg.numBrushes; + + for (i=0 ; i<6 ; i++) + { + side = i&1; + + // brush sides + s = &cmg.brushsides[cmg.numBrushSides+i]; + s->planeNum = cmg.numPlanes+i*2+side; + s->shaderNum = cmg.numShaders; + + // planes + p = &box_planes[i*2]; + p->type = i>>1; + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = 1; + + p = &box_planes[i*2+1]; + p->type = 3 + (i>>1); + p->signbits = 0; + VectorClear (p->normal); + p->normal[i>>1] = -1; + + SetPlaneSignbits( p ); + } +} + + + +/* +=================== +CM_HeadnodeForBox + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +=================== +*/ +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs) {//, const int contents ) { + box_planes[0].dist = maxs[0]; + box_planes[1].dist = -maxs[0]; + box_planes[2].dist = mins[0]; + box_planes[3].dist = -mins[0]; + box_planes[4].dist = maxs[1]; + box_planes[5].dist = -maxs[1]; + box_planes[6].dist = mins[1]; + box_planes[7].dist = -mins[1]; + box_planes[8].dist = maxs[2]; + box_planes[9].dist = -maxs[2]; + box_planes[10].dist = mins[2]; + box_planes[11].dist = -mins[2]; + + VectorCopy( mins, box_brush->bounds[0] ); + VectorCopy( maxs, box_brush->bounds[1] ); + + //FIXME: this is the "correct" way, but not the way JK2 was designed around... fix for further projects + //box_brush->contents = contents; + + return BOX_MODEL_HANDLE; +} + + +/* +=================== +CM_ModelBounds +=================== +*/ +void CM_ModelBounds( clipMap_t &cmg, clipHandle_t model, vec3_t mins, vec3_t maxs ) +{ + cmodel_t *cmod; + + cmod = CM_ClipHandleToModel( model ); + VectorCopy( cmod->mins, mins ); + VectorCopy( cmod->maxs, maxs ); +} + +/* +=================== +CM_RegisterTerrain + +Allows physics to examine the terrain data. +=================== +*/ +#if !defined(BSPC) +#if 0 // Removing terrain on Xbox +CCMLandScape *CM_RegisterTerrain(const char *config, bool server) +{ + thandle_t terrainId; + CCMLandScape *ls; + + terrainId = atol(Info_ValueForKey(config, "terrainId")); + if(terrainId && cmg.landScape) + { + // Already spawned so just return + ls = cmg.landScape; + ls->IncreaseRefCount(); + return(ls); + } + // Doesn't exist so create and link in + //cmg.numTerrains++; + ls = CM_InitTerrain(config, 1, server); + + // Increment for the next instance + if (cmg.landScape) + { + Com_Error(ERR_DROP, "You can't have more than one terrain brush."); + } + cmg.landScape = ls; + return(ls); +} + +/* +=================== +CM_ShutdownTerrain +=================== +*/ + +void CM_ShutdownTerrain( thandle_t terrainId) +{ + CCMLandScape *landscape; + + landscape = cmg.landScape; + if (landscape) + { + landscape->DecreaseRefCount(); + if(landscape->GetRefCount() <= 0) + { + delete landscape; + cmg.landScape = NULL; + } + } +} +#endif // No terrain on Xbox +#endif + +int CM_LoadSubBSP(const char *name, qboolean clientload) +{ + int i; +// int checksum; + int count; + + count = cmg.numSubModels; + for(i = 0; i < NumSubBSP; i++) + { + if (!stricmp(name, SubBSP[i].name)) + { + return count; + } + count += SubBSP[i].numSubModels; + } + + if (NumSubBSP == MAX_SUB_BSP) + { + Com_Error (ERR_DROP, "CM_LoadSubBSP: too many unique sub BSPs"); + } + +#ifdef _XBOX + assert(0); // MATT! - testing now - fix this later! +#else + CM_LoadMap_Actual( name, clientload, &checksum, SubBSP[NumSubBSP] ); +#endif + NumSubBSP++; + + return count; +} + +int CM_FindSubBSP(int modelIndex) +{ + int i; + int count; + + count = cmg.numSubModels; + if (modelIndex < count) + { // belongs to the main bsp + return -1; + } + + for(i = 0; i < NumSubBSP; i++) + { + count += SubBSP[i].numSubModels; + if (modelIndex < count) + { + return i; + } + } + return -1; +} + +void CM_GetWorldBounds ( vec3_t mins, vec3_t maxs ) +{ + VectorCopy ( cmg.cmodels[0].mins, mins ); + VectorCopy ( cmg.cmodels[0].maxs, maxs ); +} + +int CM_ModelContents_Actual( clipHandle_t model, clipMap_t *cm ) +{ + cmodel_t *cmod; + int contents = 0; + int i; + + if (!cm) + { + cm = &cmg; + } + + cmod = CM_ClipHandleToModel( model, &cm ); + + //MCG ADDED - return the contents, too + if( cmod->leaf.numLeafBrushes ) // check for brush + { + int brushNum; + for ( i = cmod->leaf.firstLeafBrush; i < cmod->leaf.firstLeafBrush+cmod->leaf.numLeafBrushes; i++ ) + { + brushNum = cm->leafbrushes[i]; + contents |= cm->brushes[brushNum].contents; + } + } + if( cmod->leaf.numLeafSurfaces ) // if not brush, check for patch + { + int surfaceNum; + for ( i = cmod->leaf.firstLeafSurface; i < cmod->leaf.firstLeafSurface+cmod->leaf.numLeafSurfaces; i++ ) + { + surfaceNum = cm->leafsurfaces[i]; + if ( cm->surfaces[surfaceNum] != NULL ) + {//HERNH? How could we have a null surf within our cmod->leaf.numLeafSurfaces? + contents |= cm->surfaces[surfaceNum]->contents; + } + } + } + return contents; +} + +int CM_ModelContents( clipHandle_t model, int subBSPIndex ) +{ + if (subBSPIndex < 0) + { + return CM_ModelContents_Actual(model, NULL); + } + + return CM_ModelContents_Actual(model, &SubBSP[subBSPIndex]); +} + +//support for save/load games +/* +=================== +CM_WritePortalState + +Writes the portal state to a savegame file +=================== +*/ +// having to proto this stuff again here is crap, but wtf?... +// +qboolean SG_Append(unsigned long chid, const void *data, int length); +int SG_Read(unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr = NULL); + +void CM_WritePortalState () +{ + SG_Append('PRTS', (void *)cmg.areaPortals, cmg.numAreas * cmg.numAreas * sizeof( *cmg.areaPortals )); +} + +/* +=================== +CM_ReadPortalState + +Reads the portal state from a savegame file +and recalculates the area connections +=================== +*/ +void CM_ReadPortalState () +{ + SG_Read('PRTS', (void *)cmg.areaPortals, cmg.numAreas * cmg.numAreas * sizeof( *cmg.areaPortals )); + CM_FloodAreaConnections (); + +} + diff --git a/code/qcommon/cm_local.h b/code/qcommon/cm_local.h new file mode 100644 index 0000000..ee3e3fb --- /dev/null +++ b/code/qcommon/cm_local.h @@ -0,0 +1,321 @@ +#include "../game/q_shared.h" +#include "qcommon.h" +#include "cm_polylib.h" +#include "cm_landscape.h" + +#ifdef _XBOX +#include "sparc.h" +#endif + +#ifndef CM_LOCAL_H +#define CM_LOCAL_H + +#define BOX_MODEL_HANDLE (MAX_SUBMODELS-1) + +#ifdef _XBOX +#pragma pack(push, 1) +typedef struct { + short children[2]; // negative numbers are leafs +} cNode_t; +#pragma pack(pop) + +#else // _XBOX + +typedef struct { + cplane_t *plane; + int children[2]; // negative numbers are leafs +} cNode_t; + +#endif // _XBOX + +typedef struct { + int cluster; + int area; + + int firstLeafBrush; + int numLeafBrushes; + + int firstLeafSurface; + int numLeafSurfaces; +} cLeaf_t; + +typedef struct cmodel_s { + vec3_t mins, maxs; + cLeaf_t leaf; // submodels don't reference the main tree +} cmodel_t; + +#ifdef _XBOX +#pragma pack (push, 1) +typedef struct cbrushside_s { + NotSoShort planeNum; + unsigned char shaderNum; +} cbrushside_t; +#pragma pack(pop) + +#else // _XBOX + +typedef struct cbrushside_s { + cplane_t *plane; + int shaderNum; +} cbrushside_t; + +#endif // _XBOX + +typedef struct cbrush_s { + int shaderNum; // the shader that determined the contents + int contents; + vec3_t bounds[2]; + cbrushside_t *sides; + unsigned short numsides; + unsigned short checkcount; // to avoid repeated testings +} cbrush_t; + +class CCMShader +{ +public: + char shader[MAX_QPATH]; + class CCMShader *mNext; + int surfaceFlags; + int contentFlags; + + const char *GetName(void) const { return(shader); } + class CCMShader *GetNext(void) const { return(mNext); } + void SetNext(class CCMShader *next) { mNext = next; } + void Destroy(void) { } +}; + +typedef struct { + int checkcount; // to avoid repeated testings + int surfaceFlags; + int contents; + struct patchCollide_s *pc; +} cPatch_t; + + +typedef struct { + int floodnum; + int floodvalid; +} cArea_t; + +#ifdef _XBOX +template +class SPARC; +typedef struct { + char name[MAX_QPATH]; + + int numShaders; + CCMShader *shaders; + + int numBrushSides; + cbrushside_t *brushsides; + + int numPlanes; + cplane_t *planes; + + int numNodes; + cNode_t *nodes; + + int numLeafs; + cLeaf_t *leafs; + + int numLeafBrushes; + int *leafbrushes; + + int numLeafSurfaces; + int *leafsurfaces; + + int numSubModels; + cmodel_t *cmodels; + + int numBrushes; + cbrush_t *brushes; + + int numClusters; + int clusterBytes; + SPARC *visibility; + qboolean vised; // if false, visibility is just a single cluster of ffs + + int numEntityChars; + char *entityString; + + int numAreas; + cArea_t *areas; + int *areaPortals; // [ numAreas*numAreas ] reference counts + + int numSurfaces; + cPatch_t **surfaces; // non-patches will be NULL + + int floodvalid; + int checkcount; // incremented on each trace + +// CCMLandScape *landScape; // Removing terrain from Xbox +} clipMap_t; + +#else // _XBOX + +typedef struct { + char name[MAX_QPATH]; + + int numShaders; + //dshader_t *shaders; + CCMShader *shaders; + + int numBrushSides; + cbrushside_t *brushsides; + + int numPlanes; + cplane_t *planes; + + int numNodes; + cNode_t *nodes; + + int numLeafs; + cLeaf_t *leafs; + + int numLeafBrushes; + int *leafbrushes; + + int numLeafSurfaces; + int *leafsurfaces; + + int numSubModels; + cmodel_t *cmodels; + + int numBrushes; + cbrush_t *brushes; + + int numClusters; + int clusterBytes; + byte *visibility; + qboolean vised; // if false, visibility is just a single cluster of ffs + + int numEntityChars; + char *entityString; + + int numAreas; + cArea_t *areas; + int *areaPortals; // [ numAreas*numAreas ] reference counts + + int numSurfaces; + cPatch_t **surfaces; // non-patches will be NULL + + int floodvalid; + int checkcount; // incremented on each trace + + CCMLandScape *landScape; +} clipMap_t; + +#endif // _XBOX + + +// keep 1/8 unit away to keep the position valid before network snapping +// and to avoid various numeric issues +#define SURFACE_CLIP_EPSILON (0.125) + +extern clipMap_t cmg; +extern int c_pointcontents; +extern int c_traces, c_brush_traces, c_patch_traces; +extern cvar_t *cm_noAreas; +extern cvar_t *cm_noCurves; +extern cvar_t *cm_playerCurveClip; + +extern clipMap_t SubBSP[MAX_SUB_BSP]; +extern int NumSubBSP; + +// cm_test.c + +// Used for oriented capsule collision detection +typedef struct +{ + qboolean use; + float radius; + float halfheight; + vec3_t offset; +} sphere_t; + +typedef struct traceWork_s { + vec3_t start; + vec3_t end; + vec3_t size[2]; // size of the box being swept through the model + vec3_t offsets[8]; // [signbits][x] = either size[0][x] or size[1][x] + float maxOffset; // longest corner length from origin + vec3_t extents; // greatest of abs(size[0]) and abs(size[1]) + + vec3_t bounds[2]; // enclosing box of start and end surrounding by size + vec3pair_t localBounds; // enclosing box of start and end surrounding by size for a segment + + vec3_t modelOrigin;// origin of the model tracing through + int contents; // ored contents of the model tracing through + qboolean isPoint; // optimized case + sphere_t sphere; // sphere for oriendted capsule collision + + float baseEnterFrac; // global enter fraction (before processing subsections of the brush) + float baseLeaveFrac; // global leave fraction (before processing subsections of the brush) + float enterFrac; // fraction where the ray enters the brush + float leaveFrac; // fraction where the ray leaves the brush + cbrushside_t *leadside; + cplane_t *clipplane; + bool startout; + bool getout; + + trace_t trace; // returned from trace call + // make sure nothing goes under here for Ghoul2 collision purposes +} traceWork_t; + +typedef struct leafList_s { + int count; + int maxcount; + qboolean overflowed; + int *list; + vec3_t bounds[2]; + int lastLeaf; // for overflows where each leaf can't be stored individually + void (*storeLeafs)( struct leafList_s *ll, int nodenum ); +} leafList_t; + + +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **boxlist, int listsize ); + +void CM_StoreLeafs( leafList_t *ll, int nodenum ); +void CM_StoreBrushes( leafList_t *ll, int nodenum ); + +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ); + +cmodel_t *CM_ClipHandleToModel( clipHandle_t handle, clipMap_t **clipMap = 0 ); +void CM_CleanLeafCache(void); + +// cm_load.c +void CM_ModelBounds( clipMap_t &cm, clipHandle_t model, vec3_t mins, vec3_t maxs ); + +// cm_patch.c + +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_ClearLevelPatches( void ); + +// cm_shader.cpp +void CM_SetupShaderProperties( void ); +void CM_ShutdownShaderProperties(void); +CCMShader *CM_GetShaderInfo( const char *name ); +CCMShader *CM_GetShaderInfo( int shaderNum ); +void CM_GetModelFormalName ( const char* model, const char* skin, char* name, int size ); + +//cm_trace.cpp +void CM_CalcExtents(const vec3_t start, const vec3_t end, const traceWork_t *tw, vec3pair_t bounds); +void CM_HandlePatchCollision(struct traceWork_s *tw, trace_t &trace, const vec3_t tStart, const vec3_t tEnd, CCMPatch *patch, int checkcount); +bool CM_GenericBoxCollide(const vec3pair_t abounds, const vec3pair_t bbounds); + + +//RM_Terrain.cpp +int Round(float value); + +//random utils for cm_terrain (and others?) +#define VectorInc(v) ((v)[0] += 1.0f,(v)[1] += 1.0f,(v)[2] +=1.0f) +#define VectorDec(v) ((v)[0] -= 1.0f,(v)[1] -= 1.0f,(v)[2] -=1.0f) +#define VectorInverseScaleVector(a,b,c) ((c)[0]=(a)[0]/(b)[0],(c)[1]=(a)[1]/(b)[1],(c)[2]=(a)[2]/(b)[2]) +#define VectorScaleVectorAdd(c,a,b,o) ((o)[0]=(c)[0]+((a)[0]*(b)[0]),(o)[1]=(c)[1]+((a)[1]*(b)[1]),(o)[2]=(c)[2]+((a)[2]*(b)[2])) + +#define minimum(x,y) ((x)<(y)?(x):(y)) +#define maximum(x,y) ((x)>(y)?(x):(y)) + +#endif diff --git a/code/qcommon/cm_patch.cpp b/code/qcommon/cm_patch.cpp new file mode 100644 index 0000000..442abd1 --- /dev/null +++ b/code/qcommon/cm_patch.cpp @@ -0,0 +1,2930 @@ + +#include "cm_local.h" +#include "cm_patch.h" + +//#define CULL_BBOX + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +Issues for collision against curved surfaces: + +Surface edges need to be handled differently than surface planes + +Plane expansion causes raw surfaces to expand past expanded bounding box + +Position test of a volume against a surface is tricky. + +Position test of a point against a surface is not well defined, because the surface has no volume. + + +Tracing leading edge points instead of volumes? +Position test by tracing corner to corner? (8*7 traces -- ouch) + +coplanar edges +triangulated patches +degenerate patches + + endcaps + degenerate + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + +/* +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4+6+16]; + int borderInward[4+6+16]; + qboolean borderNoAdjust[4+6+16]; +} facet_t; + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + + +#define MAX_GRID_SIZE 129 + +typedef struct { + int width; + int height; + qboolean wrapWidth; + qboolean wrapHeight; + vec3_t points[MAX_GRID_SIZE][MAX_GRID_SIZE]; // [width][height] +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 +*/ + +#define ADDBEVELS + +int c_totalPatchBlocks; +int c_totalPatchSurfaces; +int c_totalPatchEdges; + +static const patchCollide_t *debugPatchCollide; +static const facet_t *debugFacet; +static qboolean debugBlock; +static vec3_t debugBlockPoints[4]; + +/* +================= +CM_ClearLevelPatches +================= +*/ +void CM_ClearLevelPatches( void ) { + debugPatchCollide = NULL; + debugFacet = NULL; +} + +/* +================= +CM_SignbitsForNormal +================= +*/ +static int CM_SignbitsForNormal( vec3_t normal ) { + int bits, j; + + bits = 0; + for (j=0 ; j<3 ; j++) { + if ( normal[j] < 0 ) { + bits |= 1<= SUBDIVIDE_DISTANCE * SUBDIVIDE_DISTANCE; +} + +/* +=============== +CM_Subdivide + +a, b, and c are control points. +the subdivided sequence will be: a, out1, out2, out3, c +=============== +*/ +static void CM_Subdivide( vec3_t a, vec3_t b, vec3_t c, vec3_t out1, vec3_t out2, vec3_t out3 ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + out1[i] = 0.5 * (a[i] + b[i]); + out3[i] = 0.5 * (b[i] + c[i]); + out2[i] = 0.5 * (out1[i] + out3[i]); + } +} + +/* +================= +CM_TransposeGrid + +Swaps the rows and columns in place +================= +*/ +static void CM_TransposeGrid( cGrid_t *grid ) { + int i, j, l; + vec3_t temp; + qboolean tempWrap; + + if ( grid->width > grid->height ) { + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = i + 1 ; j < grid->width ; j++ ) { + if ( j < grid->height ) { + // swap the value + VectorCopy( grid->points[i][j], temp ); + VectorCopy( grid->points[j][i], grid->points[i][j] ); + VectorCopy( temp, grid->points[j][i] ); + } else { + // just copy + VectorCopy( grid->points[j][i], grid->points[i][j] ); + } + } + } + } else { + for ( i = 0 ; i < grid->width ; i++ ) { + for ( j = i + 1 ; j < grid->height ; j++ ) { + if ( j < grid->width ) { + // swap the value + VectorCopy( grid->points[j][i], temp ); + VectorCopy( grid->points[i][j], grid->points[j][i] ); + VectorCopy( temp, grid->points[i][j] ); + } else { + // just copy + VectorCopy( grid->points[i][j], grid->points[j][i] ); + } + } + } + } + + l = grid->width; + grid->width = grid->height; + grid->height = l; + + tempWrap = grid->wrapWidth; + grid->wrapWidth = grid->wrapHeight; + grid->wrapHeight = tempWrap; +} + +/* +=================== +CM_SetGridWrapWidth + +If the left and right columns are exactly equal, set grid->wrapWidth qtrue +=================== +*/ +static void CM_SetGridWrapWidth( cGrid_t *grid ) { + int i, j; + float d; + + for ( i = 0 ; i < grid->height ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + d = grid->points[0][i][j] - grid->points[grid->width-1][i][j]; + if ( d < -WRAP_POINT_EPSILON || d > WRAP_POINT_EPSILON ) { + break; + } + } + if ( j != 3 ) { + break; + } + } + if ( i == grid->height ) { + grid->wrapWidth = qtrue; + } else { + grid->wrapWidth = qfalse; + } +} + + +/* +================= +CM_SubdivideGridColumns + +Adds columns as necessary to the grid until +all the aproximating points are within SUBDIVIDE_DISTANCE +from the true curve +================= +*/ +static void CM_SubdivideGridColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 2 ; ) { + // grid->points[i][x] is an interpolating control point + // grid->points[i+1][x] is an aproximating control point + // grid->points[i+2][x] is an interpolating control point + + // + // first see if we can collapse the aproximating collumn away + // + for ( j = 0 ; j < grid->height ; j++ ) { + if ( CM_NeedsSubdivision( grid->points[i][j], grid->points[i+1][j], grid->points[i+2][j] ) ) { + break; + } + } + if ( j == grid->height ) { + // all of the points were close enough to the linear midpoints + // that we can collapse the entire column away + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k-1][j] ); + } + } + + grid->width--; + + // go to the next curve segment + i++; + continue; + } + + // + // we need to subdivide the curve + // + for ( j = 0 ; j < grid->height ; j++ ) { + vec3_t prev, mid, next; + + // save the control points now + VectorCopy( grid->points[i][j], prev ); + VectorCopy( grid->points[i+1][j], mid ); + VectorCopy( grid->points[i+2][j], next ); + + // make room for two additional columns in the grid + // columns i+1 will be replaced, column i+2 will become i+4 + // i+1, i+2, and i+3 will be generated + for ( k = grid->width - 1 ; k > i + 1 ; k-- ) { + VectorCopy( grid->points[k][j], grid->points[k+2][j] ); + } + + // generate the subdivided points + CM_Subdivide( prev, mid, next, grid->points[i+1][j], grid->points[i+2][j], grid->points[i+3][j] ); + } + + grid->width += 2; + + // the new aproximating point at i+1 may need to be removed + // or subdivided farther, so don't advance i + } +} + + +#define POINT_EPSILON 0.1 +/* +====================== +CM_ComparePoints +====================== +*/ +static qboolean CM_ComparePoints( float *a, float *b ) { + float d; + + d = a[0] - b[0]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + d = a[1] - b[1]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + d = a[2] - b[2]; + if ( d < -POINT_EPSILON || d > POINT_EPSILON ) { + return qfalse; + } + return qtrue; +} + +/* +================= +CM_RemoveDegenerateColumns + +If there are any identical columns, remove them +================= +*/ +static void CM_RemoveDegenerateColumns( cGrid_t *grid ) { + int i, j, k; + + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height ; j++ ) { + if ( !CM_ComparePoints( grid->points[i][j], grid->points[i+1][j] ) ) { + break; + } + } + + if ( j != grid->height ) { + continue; // not degenerate + } + + for ( j = 0 ; j < grid->height ; j++ ) { + // remove the column + for ( k = i + 2 ; k < grid->width ; k++ ) { + VectorCopy( grid->points[k][j], grid->points[k-1][j] ); + } + } + grid->width--; + + // check against the next column + i--; + } +} + +/* +================================================================================ + +PATCH COLLIDE GENERATION + +================================================================================ +*/ + +static int numPlanes; + +#ifdef _XBOX +static patchPlane_t *planes = NULL; +void CM_TempPatchPlanesAlloc(void) +{ + if(!planes) { + planes = (patchPlane_t*)Z_Malloc(MAX_PATCH_PLANES*sizeof(patchPlane_t), + TAG_TEMP_WORKSPACE, qfalse); + } +} + +void CM_TempPatchPlanesDealloc(void) +{ + if(planes) { + Z_Free(planes); + planes = NULL; + } +} +#else +static patchPlane_t planes[MAX_PATCH_PLANES]; +#endif + +//static int numFacets; +// static facet_t facets[MAX_PATCH_PLANES]; //maybe MAX_FACETS ?? +//static facet_t facets[MAX_FACETS]; // Switched to MAX_FACETS = VV_FIXME, allocate these only during use +static facet_t *facets = NULL; + +#define NORMAL_EPSILON 0.0001 +#define DIST_EPSILON 0.02 + +int CM_PlaneEqual(patchPlane_t *p, float plane[4], int *flipped) { + float invplane[4]; + + if ( + Q_fabs(p->plane[0] - plane[0]) < NORMAL_EPSILON + && Q_fabs(p->plane[1] - plane[1]) < NORMAL_EPSILON + && Q_fabs(p->plane[2] - plane[2]) < NORMAL_EPSILON + && Q_fabs(p->plane[3] - plane[3]) < DIST_EPSILON ) + { + *flipped = qfalse; + return qtrue; + } + + VectorNegate(plane, invplane); + invplane[3] = -plane[3]; + + if ( + Q_fabs(p->plane[0] - invplane[0]) < NORMAL_EPSILON + && Q_fabs(p->plane[1] - invplane[1]) < NORMAL_EPSILON + && Q_fabs(p->plane[2] - invplane[2]) < NORMAL_EPSILON + && Q_fabs(p->plane[3] - invplane[3]) < DIST_EPSILON ) + { + *flipped = qtrue; + return qtrue; + } + + return qfalse; +} + +void CM_SnapVector(vec3_t normal) { + int i; + + for (i=0 ; i<3 ; i++) + { + if ( Q_fabs(normal[i] - 1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + break; + } + if ( Q_fabs(normal[i] - -1) < NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + break; + } + } +} + +int CM_FindPlane2(float plane[4], int *flipped) { + int i; + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if (CM_PlaneEqual(&planes[i], plane, flipped)) return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES reached (%d)", MAX_PATCH_PLANES ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + *flipped = qfalse; + + return numPlanes-1; +} + +/* +================== +CM_FindPlane +================== +*/ +static int CM_FindPlane( float *p1, float *p2, float *p3 ) { + float plane[4]; + int i; + float d; + + if ( !CM_PlaneFromPoints( plane, p1, p2, p3 ) ) { + return -1; + } + + // see if the points are close enough to an existing plane + for ( i = 0 ; i < numPlanes ; i++ ) { + if ( DotProduct( plane, planes[i].plane ) < 0 ) { + continue; // allow backwards planes? + } + + d = DotProduct( p1, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p2, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + d = DotProduct( p3, planes[i].plane ) - planes[i].plane[3]; + if ( d < -PLANE_TRI_EPSILON || d > PLANE_TRI_EPSILON ) { + continue; + } + + // found it + return i; + } + + // add a new plane + if ( numPlanes == MAX_PATCH_PLANES ) { + Com_Error( ERR_DROP, "MAX_PATCH_PLANES" ); + } + + Vector4Copy( plane, planes[numPlanes].plane ); + planes[numPlanes].signbits = CM_SignbitsForNormal( plane ); + + numPlanes++; + + return numPlanes-1; +} + + +/* +================== +CM_PointOnPlaneSide +================== +*/ +static int CM_PointOnPlaneSide( float *p, int planeNum ) { + float *plane; + float d; + + if ( planeNum == -1 ) { + return SIDE_ON; + } + plane = planes[ planeNum ].plane; + + d = DotProduct( p, plane ) - plane[3]; + + if ( d > PLANE_TRI_EPSILON ) { + return SIDE_FRONT; + } + + if ( d < -PLANE_TRI_EPSILON ) { + return SIDE_BACK; + } + + return SIDE_ON; +} + + +#ifdef _XBOX +static int CM_GridPlane( int* gridPlanes, int i, int j, int tri ) { + int p; + + p = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+tri]; + if ( p != -1 ) { + return p; + } + p = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+(!tri)]; + if ( p != -1 ) { + return p; + } + + // should never happen + Com_Printf( "WARNING: CM_GridPlane unresolvable\n" ); + return -1; +} + +#else // _XBOX + +static int CM_GridPlane( int gridPlanes[CM_MAX_GRID_SIZE][CM_MAX_GRID_SIZE][2], int i, int j, int tri ) { + int p; + + p = gridPlanes[i][j][tri]; + if ( p != -1 ) { + return p; + } + p = gridPlanes[i][j][!tri]; + if ( p != -1 ) { + return p; + } + + // should never happen + Com_Printf( "WARNING: CM_GridPlane unresolvable\n" ); + return -1; +} + +#endif // _XBOX + +/* +================== +CM_EdgePlaneNum +================== +*/ +#ifdef _XBOX +static int CM_EdgePlaneNum( cGrid_t *grid, int* gridPlanes/*[PATCH_MAX_GRID_SIZE][PATCH_MAX_GRID_SIZE][2]*/, int i, int j, int k ) { + float *p1, *p2; + vec3_t up; + int p; + + switch ( k ) { + case 0: // top border + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 2: // bottom border + p1 = grid->points[i][j+1]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 3: // left border + p1 = grid->points[i][j]; + p2 = grid->points[i][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 1: // right border + p1 = grid->points[i+1][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 4: // diagonal out of triangle 0 + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 5: // diagonal out of triangle 1 + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + } + + Com_Error( ERR_DROP, "CM_EdgePlaneNum: bad k" ); + return -1; +} + +#else + +static int CM_EdgePlaneNum( cGrid_t *grid, int gridPlanes[CM_MAX_GRID_SIZE][CM_MAX_GRID_SIZE][2], int i, int j, int k ) { + float *p1, *p2; + vec3_t up; + int p; + + switch ( k ) { + case 0: // top border + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 2: // bottom border + p1 = grid->points[i][j+1]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 3: // left border + p1 = grid->points[i][j]; + p2 = grid->points[i][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p2, p1, up ); + + case 1: // right border + p1 = grid->points[i+1][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 4: // diagonal out of triangle 0 + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j]; + p = CM_GridPlane( gridPlanes, i, j, 0 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + case 5: // diagonal out of triangle 1 + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j+1]; + p = CM_GridPlane( gridPlanes, i, j, 1 ); + VectorMA( p1, 4, planes[ p ].plane, up ); + return CM_FindPlane( p1, p2, up ); + + } + + Com_Error( ERR_DROP, "CM_EdgePlaneNum: bad k" ); + return -1; +} + +#endif + +/* +=================== +CM_SetBorderInward +=================== +*/ +#ifdef _XBOX +static void CM_SetBorderInward( facetLoad_t *facet, cGrid_t *grid, + int i, int j, int which ) { + int k, l; + float *points[4]; + int numPoints; + + switch ( which ) { + case -1: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + points[3] = grid->points[i][j+1]; + numPoints = 4; + break; + case 0: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + numPoints = 3; + break; + case 1: + points[0] = grid->points[i+1][j+1]; + points[1] = grid->points[i][j+1]; + points[2] = grid->points[i][j]; + numPoints = 3; + break; + default: + Com_Error( ERR_FATAL, "CM_SetBorderInward: bad parameter" ); + numPoints = 0; + break; + } + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + int front, back; + + front = 0; + back = 0; + + for ( l = 0 ; l < numPoints ; l++ ) { + int side; + + side = CM_PointOnPlaneSide( points[l], facet->borderPlanes[k] ); + if ( side == SIDE_FRONT ) { + front++; + } if ( side == SIDE_BACK ) { + back++; + } + } + + if ( front && !back ) { + facet->borderInward[k] = qtrue; + } else if ( back && !front ) { + facet->borderInward[k] = qfalse; + } else if ( !front && !back ) { + // flat side border + facet->borderPlanes[k] = -1; + } else { + // bisecting side border + Com_DPrintf( "WARNING: CM_SetBorderInward: mixed plane sides\n" ); + facet->borderInward[k] = qfalse; + if ( !debugBlock ) { + debugBlock = qtrue; + VectorCopy( grid->points[i][j], debugBlockPoints[0] ); + VectorCopy( grid->points[i+1][j], debugBlockPoints[1] ); + VectorCopy( grid->points[i+1][j+1], debugBlockPoints[2] ); + VectorCopy( grid->points[i][j+1], debugBlockPoints[3] ); + } + } + } +} + +#else // _XBOX + +static void CM_SetBorderInward( facet_t *facet, cGrid_t *grid, int gridPlanes[CM_MAX_GRID_SIZE][CM_MAX_GRID_SIZE][2], + int i, int j, int which ) { + int k, l; + float *points[4]; + int numPoints; + + switch ( which ) { + case -1: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + points[3] = grid->points[i][j+1]; + numPoints = 4; + break; + case 0: + points[0] = grid->points[i][j]; + points[1] = grid->points[i+1][j]; + points[2] = grid->points[i+1][j+1]; + numPoints = 3; + break; + case 1: + points[0] = grid->points[i+1][j+1]; + points[1] = grid->points[i][j+1]; + points[2] = grid->points[i][j]; + numPoints = 3; + break; + default: + Com_Error( ERR_FATAL, "CM_SetBorderInward: bad parameter" ); + numPoints = 0; + break; + } + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + int front, back; + + front = 0; + back = 0; + + for ( l = 0 ; l < numPoints ; l++ ) { + int side; + + side = CM_PointOnPlaneSide( points[l], facet->borderPlanes[k] ); + if ( side == SIDE_FRONT ) { + front++; + } if ( side == SIDE_BACK ) { + back++; + } + } + + if ( front && !back ) { + facet->borderInward[k] = qtrue; + } else if ( back && !front ) { + facet->borderInward[k] = qfalse; + } else if ( !front && !back ) { + // flat side border + facet->borderPlanes[k] = -1; + } else { + // bisecting side border + Com_DPrintf( "WARNING: CM_SetBorderInward: mixed plane sides\n" ); + facet->borderInward[k] = qfalse; + if ( !debugBlock ) { + debugBlock = qtrue; + VectorCopy( grid->points[i][j], debugBlockPoints[0] ); + VectorCopy( grid->points[i+1][j], debugBlockPoints[1] ); + VectorCopy( grid->points[i+1][j+1], debugBlockPoints[2] ); + VectorCopy( grid->points[i][j+1], debugBlockPoints[3] ); + } + } + } +} + +#endif + +/* +================== +CM_ValidateFacet + +If the facet isn't bounded by its borders, we screwed up. +================== +*/ +#ifdef _XBOX +static qboolean CM_ValidateFacet( facetLoad_t *facet ) { + float plane[4]; + int j; + winding_t *w; + vec3_t bounds[2]; + + if ( facet->surfacePlane == -1 ) { + return qfalse; + } + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if ( facet->borderPlanes[j] == -1 ) { + FreeWinding(w); + return qfalse; + } + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + + if ( !w ) { + return qfalse; // winding was completely chopped away + } + + // see if the facet is unreasonably large + WindingBounds( w, bounds[0], bounds[1] ); + FreeWinding( w ); + + for ( j = 0 ; j < 3 ; j++ ) { + if ( bounds[1][j] - bounds[0][j] > MAX_MAP_BOUNDS ) { + return qfalse; // we must be missing a plane + } + if ( bounds[0][j] >= MAX_MAP_BOUNDS ) { + return qfalse; + } + if ( bounds[1][j] <= -MAX_MAP_BOUNDS ) { + return qfalse; + } + } + return qtrue; // winding is fine +} + +#else // _XBOX + +static qboolean CM_ValidateFacet( facet_t *facet ) { + float plane[4]; + int j; + winding_t *w; + vec3_t bounds[2]; + + if ( facet->surfacePlane == -1 ) { + return qfalse; + } + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if ( facet->borderPlanes[j] == -1 ) { + FreeWinding(w); + return qfalse; + } + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + + if ( !w ) { + return qfalse; // winding was completely chopped away + } + + // see if the facet is unreasonably large + WindingBounds( w, bounds[0], bounds[1] ); + FreeWinding( w ); + + for ( j = 0 ; j < 3 ; j++ ) { + if ( bounds[1][j] - bounds[0][j] > MAX_MAP_BOUNDS ) { + return qfalse; // we must be missing a plane + } + if ( bounds[0][j] >= MAX_MAP_BOUNDS ) { + return qfalse; + } + if ( bounds[1][j] <= -MAX_MAP_BOUNDS ) { + return qfalse; + } + } + return qtrue; // winding is fine +} + +#endif // _XBOX + +/* +================== +CM_AddFacetBevels +================== +*/ +#ifdef _XBOX +void CM_AddFacetBevels( facetLoad_t *facet ) { + + int i, j, k, l; + int axis, dir, order, flipped; + float plane[4], d, newplane[4]; + winding_t *w, *w2; + vec3_t mins, maxs, vec, vec2; + +#ifndef ADDBEVELS + return; +#endif + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if (facet->borderPlanes[j] == facet->surfacePlane) continue; + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( !w ) { + return; + } + + WindingBounds(w, mins, maxs); + + // add the axial planes + order = 0; + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2, order++ ) + { + VectorClear(plane); + plane[axis] = dir; + if (dir == 1) { + plane[3] = maxs[axis]; + } + else { + plane[3] = -mins[axis]; + } + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) + break; + } + + if ( i == facet->numBorders ) { + if (facet->numBorders > 4 + 6 + 16) Com_Printf(S_COLOR_RED"ERROR: too many bevels\n"); + int num = CM_FindPlane2(plane, &flipped); + assert(num > -32768 && num < 32768); + facet->borderPlanes[facet->numBorders] = num; + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = flipped; + facet->numBorders++; + } + } + } + // + // add the edge bevels + // + // test the non-axial plane edges + for ( j = 0 ; j < w->numpoints ; j++ ) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + //if it's a degenerate edge + if (VectorNormalize (vec) < 0.5) + continue; + CM_SnapVector(vec); + for ( k = 0; k < 3 ; k++ ) + if ( vec[k] == -1 || vec[k] == 1 ) + break; // axial + if ( k < 3 ) + continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2 ) + { + // construct a plane + VectorClear (vec2); + vec2[axis] = dir; + CrossProduct (vec, vec2, plane); + if (VectorNormalize (plane) < 0.5) + continue; + plane[3] = DotProduct (w->p[j], plane); + + // if all the points of the facet winding are + // behind this plane, it is a proper edge bevel + for ( l = 0 ; l < w->numpoints ; l++ ) + { + d = DotProduct (w->p[l], plane) - plane[3]; + if (d > 0.1) + break; // point in front + } + if ( l < w->numpoints ) + continue; + + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) { + break; + } + } + + if ( i == facet->numBorders ) { + if (facet->numBorders > 4 + 6 + 16) Com_Printf(S_COLOR_RED"ERROR: too many bevels\n"); + int num = CM_FindPlane2(plane, &flipped); + assert(num > -32768 && num < 32768); + facet->borderPlanes[facet->numBorders] = num; + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + if (facet->borderPlanes[facet->numBorders] == + facet->borderPlanes[k]) Com_Printf("WARNING: bevel plane already used\n"); + } + + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = flipped; + // + w2 = CopyWinding(w); + Vector4Copy(planes[facet->borderPlanes[facet->numBorders]].plane, newplane); + if (!facet->borderInward[facet->numBorders]) + { + VectorNegate(newplane, newplane); + newplane[3] = -newplane[3]; + } //end if + ChopWindingInPlace( &w2, newplane, newplane[3], 0.1f ); + if (!w2) { + Com_DPrintf("WARNING: CM_AddFacetBevels... invalid bevel\n"); + continue; + } + else { + FreeWinding(w2); + } + // + facet->numBorders++; + //already got a bevel +// break; + } + } + } + } + FreeWinding( w ); + +#ifndef BSPC + //add opposite plane + assert(facet->surfacePlane > -32768 && facet->surfacePlane < 32768); + facet->borderPlanes[facet->numBorders] = facet->surfacePlane; + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = qtrue; + facet->numBorders++; +#endif //BSPC + +} + +#else // _XBOX + +void CM_AddFacetBevels( facet_t *facet ) { + + int i, j, k, l; + int axis, dir, order, flipped; + float plane[4], d, newplane[4]; + winding_t *w, *w2; + vec3_t mins, maxs, vec, vec2; + +#ifndef ADDBEVELS + return; +#endif + + Vector4Copy( planes[ facet->surfacePlane ].plane, plane ); + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders && w ; j++ ) { + if (facet->borderPlanes[j] == facet->surfacePlane) continue; + Vector4Copy( planes[ facet->borderPlanes[j] ].plane, plane ); + + if ( !facet->borderInward[j] ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( !w ) { + return; + } + + WindingBounds(w, mins, maxs); + + // add the axial planes + order = 0; + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2, order++ ) + { + VectorClear(plane); + plane[axis] = dir; + if (dir == 1) { + plane[3] = maxs[axis]; + } + else { + plane[3] = -mins[axis]; + } + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) + break; + } + + if ( i == facet->numBorders ) { + if (facet->numBorders > 4 + 6 + 16) Com_Printf(S_COLOR_RED"ERROR: too many bevels\n"); + facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = flipped; + facet->numBorders++; + } + } + } + // + // add the edge bevels + // + // test the non-axial plane edges + for ( j = 0 ; j < w->numpoints ; j++ ) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + //if it's a degenerate edge + if (VectorNormalize (vec) < 0.5) + continue; + CM_SnapVector(vec); + for ( k = 0; k < 3 ; k++ ) + if ( vec[k] == -1 || vec[k] == 1 ) + break; // axial + if ( k < 3 ) + continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for ( axis = 0 ; axis < 3 ; axis++ ) + { + for ( dir = -1 ; dir <= 1 ; dir += 2 ) + { + // construct a plane + VectorClear (vec2); + vec2[axis] = dir; + CrossProduct (vec, vec2, plane); + if (VectorNormalize (plane) < 0.5) + continue; + plane[3] = DotProduct (w->p[j], plane); + + // if all the points of the facet winding are + // behind this plane, it is a proper edge bevel + for ( l = 0 ; l < w->numpoints ; l++ ) + { + d = DotProduct (w->p[l], plane) - plane[3]; + if (d > 0.1) + break; // point in front + } + if ( l < w->numpoints ) + continue; + + //if it's the surface plane + if (CM_PlaneEqual(&planes[facet->surfacePlane], plane, &flipped)) { + continue; + } + // see if the plane is allready present + for ( i = 0 ; i < facet->numBorders ; i++ ) { + if (CM_PlaneEqual(&planes[facet->borderPlanes[i]], plane, &flipped)) { + break; + } + } + + if ( i == facet->numBorders ) { + if (facet->numBorders > 4 + 6 + 16) Com_Printf(S_COLOR_RED"ERROR: too many bevels\n"); + facet->borderPlanes[facet->numBorders] = CM_FindPlane2(plane, &flipped); + + for ( k = 0 ; k < facet->numBorders ; k++ ) { + if (facet->borderPlanes[facet->numBorders] == + facet->borderPlanes[k]) Com_Printf("WARNING: bevel plane already used\n"); + } + + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = flipped; + // + w2 = CopyWinding(w); + Vector4Copy(planes[facet->borderPlanes[facet->numBorders]].plane, newplane); + if (!facet->borderInward[facet->numBorders]) + { + VectorNegate(newplane, newplane); + newplane[3] = -newplane[3]; + } //end if + ChopWindingInPlace( &w2, newplane, newplane[3], 0.1f ); + if (!w2) { + Com_DPrintf("WARNING: CM_AddFacetBevels... invalid bevel\n"); + continue; + } + else { + FreeWinding(w2); + } + // + facet->numBorders++; + //already got a bevel +// break; + } + } + } + } + FreeWinding( w ); + +#ifndef BSPC + //add opposite plane + facet->borderPlanes[facet->numBorders] = facet->surfacePlane; + facet->borderNoAdjust[facet->numBorders] = 0; + facet->borderInward[facet->numBorders] = qtrue; + facet->numBorders++; +#endif //BSPC + +} + +#endif // _XBOX + +typedef enum { + EN_TOP, + EN_RIGHT, + EN_BOTTOM, + EN_LEFT +} edgeName_t; + +#ifdef _XBOX +facetLoad_t* cm_facets = 0; +int* cm_gridPlanes = 0; +void CM_PatchCollideFromGridTempAlloc() +{ + if (!cm_facets) + { + cm_facets = (facetLoad_t*)Z_Malloc(MAX_PATCH_PLANES*sizeof(facetLoad_t), TAG_TEMP_WORKSPACE, qfalse); + } + if (!cm_gridPlanes) + { + cm_gridPlanes = (int*)Z_Malloc(CM_MAX_GRID_SIZE*CM_MAX_GRID_SIZE*2*sizeof(int), TAG_TEMP_WORKSPACE, qfalse); + } +} + +void CM_PatchCollideFromGridTempDealloc() +{ + Z_Free(cm_gridPlanes); + Z_Free(cm_facets); + cm_gridPlanes = 0; + cm_facets = 0; +} +#endif + +/* +================== +CM_PatchCollideFromGrid +================== +*/ +#ifdef _XBOX +int min1 = 0, max1 = 0, min2 = 0, max2 = 0; +static void CM_PatchCollideFromGrid( cGrid_t *grid, patchCollide_t *pf, + facetLoad_t *facetbuf, int *gridbuf ) { + int i, j; + float *p1, *p2, *p3; + int *gridPlanes; + facetLoad_t *facet; + int borders[4]; + int noAdjust[4]; + facetLoad_t *facets; + int numFacets; + + facets = cm_facets; + if (facets == 0) + { + facets = facetbuf; + } + gridPlanes = cm_gridPlanes; + if (gridPlanes == 0) + { + gridPlanes = gridbuf; + } + + numPlanes = 0; + numFacets = 0; + + // find the planes for each triangle of the grid + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p3 = grid->points[i+1][j+1]; + gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] = CM_FindPlane( p1, p2, p3 ); + + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j+1]; + p3 = grid->points[i][j]; + gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] = CM_FindPlane( p1, p2, p3 ); + } + } + + // create the borders for each facet + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + + borders[EN_TOP] = -1; + if ( j > 0 ) { + borders[EN_TOP] = gridPlanes[i*CM_MAX_GRID_SIZE*2+(j-1)*2+1]; + } else if ( grid->wrapHeight ) { + borders[EN_TOP] = gridPlanes[i*CM_MAX_GRID_SIZE*2+(grid->height-2)*2+1]; + } + noAdjust[EN_TOP] = ( borders[EN_TOP] == gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] ); + if ( borders[EN_TOP] == -1 || noAdjust[EN_TOP] ) { + borders[EN_TOP] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 0 ); + } + + borders[EN_BOTTOM] = -1; + if ( j < grid->height - 2 ) { + borders[EN_BOTTOM] = gridPlanes[i*CM_MAX_GRID_SIZE*2+(j+1)*2+0]; + } else if ( grid->wrapHeight ) { + borders[EN_BOTTOM] = gridPlanes[i*CM_MAX_GRID_SIZE*2+0*2+0]; + } + noAdjust[EN_BOTTOM] = ( borders[EN_BOTTOM] == gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] ); + if ( borders[EN_BOTTOM] == -1 || noAdjust[EN_BOTTOM] ) { + borders[EN_BOTTOM] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 2 ); + } + + borders[EN_LEFT] = -1; + if ( i > 0 ) { + borders[EN_LEFT] = gridPlanes[(i-1)*CM_MAX_GRID_SIZE*2+j*2+0]; + } else if ( grid->wrapWidth ) { + borders[EN_LEFT] = gridPlanes[(grid->width-2)*CM_MAX_GRID_SIZE*2+j*2+0]; + } + noAdjust[EN_LEFT] = ( borders[EN_LEFT] == gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] ); + if ( borders[EN_LEFT] == -1 || noAdjust[EN_LEFT] ) { + borders[EN_LEFT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 3 ); + } + + borders[EN_RIGHT] = -1; + if ( i < grid->width - 2 ) { + borders[EN_RIGHT] = gridPlanes[(i+1)*CM_MAX_GRID_SIZE*2+j*2+1]; + } else if ( grid->wrapWidth ) { + borders[EN_RIGHT] = gridPlanes[0*CM_MAX_GRID_SIZE*2+j*2+1]; + } + noAdjust[EN_RIGHT] = ( borders[EN_RIGHT] == gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] ); + if ( borders[EN_RIGHT] == -1 || noAdjust[EN_RIGHT] ) { + borders[EN_RIGHT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 1 ); + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + memset( facet, 0, sizeof( *facet ) ); + + if ( gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] == gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] ) { + if ( gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] == -1 ) { + continue; // degenrate + } + facet->surfacePlane = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0]; + facet->numBorders = 4; + facet->borderPlanes[0] = borders[EN_TOP]; + assert(borders[EN_TOP] > -32768 && borders[EN_TOP] < 32768); + facet->borderNoAdjust[0] = noAdjust[EN_TOP]; + assert(noAdjust[EN_TOP] >= 0 && noAdjust[EN_TOP] < 256); + facet->borderPlanes[1] = borders[EN_RIGHT]; + assert(borders[EN_RIGHT] > -32768 && borders[EN_RIGHT] < 32768); + facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; + assert(noAdjust[EN_RIGHT] >= 0 && noAdjust[EN_RIGHT] < 256); + facet->borderPlanes[2] = borders[EN_BOTTOM]; + assert(borders[EN_BOTTOM] > -32768 && + borders[EN_BOTTOM] < 32768); + facet->borderNoAdjust[2] = noAdjust[EN_BOTTOM]; + assert(noAdjust[EN_BOTTOM] >= 0 && noAdjust[EN_BOTTOM] < 256); + facet->borderPlanes[3] = borders[EN_LEFT]; + assert(borders[EN_LEFT] > -32768 && borders[EN_LEFT] < 32768); + facet->borderNoAdjust[3] = noAdjust[EN_LEFT]; + assert(noAdjust[EN_LEFT] >= 0 && noAdjust[EN_LEFT] < 256); + CM_SetBorderInward( facet, grid, i, j, -1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } else { + // two seperate triangles + facet->surfacePlane = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_TOP]; + assert(borders[EN_TOP] > -32768 && borders[EN_TOP] < 32768); + assert(noAdjust[EN_TOP] >= 0 && noAdjust[EN_TOP] < 256); + facet->borderNoAdjust[0] = noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + assert(borders[EN_RIGHT] > -32768 && borders[EN_RIGHT] < 32768); + facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; + assert(noAdjust[EN_RIGHT] >= 0 && noAdjust[EN_RIGHT] < 256); + facet->borderPlanes[2] = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1]; + assert(gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] > -32768 && + gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1] < 32768); + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_BOTTOM]; + assert(borders[EN_BOTTOM] > -32768 && + borders[EN_BOTTOM] < 32768); + if ( facet->borderPlanes[2] == -1 ) { + int num = CM_EdgePlaneNum( grid, gridPlanes, i, j, 4 ); + assert(num > -32768 && num < 32768); + facet->borderPlanes[2] = num; + } + } + CM_SetBorderInward( facet, grid, i, j, 0 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + memset( facet, 0, sizeof( *facet ) ); + + facet->surfacePlane = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+1]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_BOTTOM]; + assert(borders[EN_BOTTOM] > -32768 && + borders[EN_BOTTOM] < 32768); + facet->borderNoAdjust[0] = noAdjust[EN_BOTTOM]; + assert(noAdjust[EN_BOTTOM] >= 0 && noAdjust[EN_BOTTOM] < 256); + facet->borderPlanes[1] = borders[EN_LEFT]; + assert(borders[EN_LEFT] > -32768 && borders[EN_LEFT] < 32768); + facet->borderNoAdjust[1] = noAdjust[EN_LEFT]; + assert(noAdjust[EN_LEFT] >= 0 && noAdjust[EN_LEFT] < 256); + facet->borderPlanes[2] = gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0]; + assert(gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] > -32768 && + gridPlanes[i*CM_MAX_GRID_SIZE*2+j*2+0] < 32768); + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_TOP]; + assert(borders[EN_TOP] > -32768 && + borders[EN_TOP] < 32768); + if ( facet->borderPlanes[2] == -1 ) { + int num = CM_EdgePlaneNum( grid, gridPlanes, i, j, 5 ); + assert(num > -32768 && num < 32768); + facet->borderPlanes[2] = num; + } + } + CM_SetBorderInward( facet, grid, i, j, 1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } + } + } + + // copy the results out + pf->numPlanes = numPlanes; + pf->numFacets = numFacets; + if (numFacets) + { + pf->facets = (facet_t *) Z_Malloc( numFacets * sizeof( *pf->facets ), TAG_BSP, qfalse); + for(i=0; ifacets[i].data = (char*)Z_Malloc(facets[i].numBorders * 4, + TAG_BSP, qfalse); + pf->facets[i].surfacePlane = facets[i].surfacePlane; + pf->facets[i].numBorders = facets[i].numBorders; + short *bp = pf->facets[i].GetBorderPlanes(); + char *bi = pf->facets[i].GetBorderInward(); + char *bna = pf->facets[i].GetBorderNoAdjust(); + for(j=0; jfacets = 0; + } + pf->planes = (patchPlane_t *) Z_Malloc( numPlanes * sizeof( *pf->planes ), TAG_BSP, qfalse); + memcpy( pf->planes, planes, numPlanes * sizeof( *pf->planes ) ); +} + +#else + +static void CM_PatchCollideFromGrid( cGrid_t *grid, patchCollide_t *pf ) { + int i, j; + float *p1, *p2, *p3; + MAC_STATIC int gridPlanes[CM_MAX_GRID_SIZE][CM_MAX_GRID_SIZE][2]; + facet_t *facet; + int borders[4]; + int noAdjust[4]; + + int numFacets; + facets = (facet_t*) Z_Malloc(MAX_FACETS*sizeof(facet_t), TAG_TEMP_WORKSPACE, qfalse, 4); + + numPlanes = 0; + numFacets = 0; + + // find the planes for each triangle of the grid + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + p1 = grid->points[i][j]; + p2 = grid->points[i+1][j]; + p3 = grid->points[i+1][j+1]; + gridPlanes[i][j][0] = CM_FindPlane( p1, p2, p3 ); + + p1 = grid->points[i+1][j+1]; + p2 = grid->points[i][j+1]; + p3 = grid->points[i][j]; + gridPlanes[i][j][1] = CM_FindPlane( p1, p2, p3 ); + } + } + + // create the borders for each facet + for ( i = 0 ; i < grid->width - 1 ; i++ ) { + for ( j = 0 ; j < grid->height - 1 ; j++ ) { + + borders[EN_TOP] = -1; + if ( j > 0 ) { + borders[EN_TOP] = gridPlanes[i][j-1][1]; + } else if ( grid->wrapHeight ) { + borders[EN_TOP] = gridPlanes[i][grid->height-2][1]; + } + noAdjust[EN_TOP] = ( borders[EN_TOP] == gridPlanes[i][j][0] ); + if ( borders[EN_TOP] == -1 || noAdjust[EN_TOP] ) { + borders[EN_TOP] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 0 ); + } + + borders[EN_BOTTOM] = -1; + if ( j < grid->height - 2 ) { + borders[EN_BOTTOM] = gridPlanes[i][j+1][0]; + } else if ( grid->wrapHeight ) { + borders[EN_BOTTOM] = gridPlanes[i][0][0]; + } + noAdjust[EN_BOTTOM] = ( borders[EN_BOTTOM] == gridPlanes[i][j][1] ); + if ( borders[EN_BOTTOM] == -1 || noAdjust[EN_BOTTOM] ) { + borders[EN_BOTTOM] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 2 ); + } + + borders[EN_LEFT] = -1; + if ( i > 0 ) { + borders[EN_LEFT] = gridPlanes[i-1][j][0]; + } else if ( grid->wrapWidth ) { + borders[EN_LEFT] = gridPlanes[grid->width-2][j][0]; + } + noAdjust[EN_LEFT] = ( borders[EN_LEFT] == gridPlanes[i][j][1] ); + if ( borders[EN_LEFT] == -1 || noAdjust[EN_LEFT] ) { + borders[EN_LEFT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 3 ); + } + + borders[EN_RIGHT] = -1; + if ( i < grid->width - 2 ) { + borders[EN_RIGHT] = gridPlanes[i+1][j][1]; + } else if ( grid->wrapWidth ) { + borders[EN_RIGHT] = gridPlanes[0][j][1]; + } + noAdjust[EN_RIGHT] = ( borders[EN_RIGHT] == gridPlanes[i][j][0] ); + if ( borders[EN_RIGHT] == -1 || noAdjust[EN_RIGHT] ) { + borders[EN_RIGHT] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 1 ); + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + memset( facet, 0, sizeof( *facet ) ); + + if ( gridPlanes[i][j][0] == gridPlanes[i][j][1] ) { + if ( gridPlanes[i][j][0] == -1 ) { + continue; // degenrate + } + facet->surfacePlane = gridPlanes[i][j][0]; + facet->numBorders = 4; + facet->borderPlanes[0] = borders[EN_TOP]; + facet->borderNoAdjust[0] = noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; + facet->borderPlanes[2] = borders[EN_BOTTOM]; + facet->borderNoAdjust[2] = noAdjust[EN_BOTTOM]; + facet->borderPlanes[3] = borders[EN_LEFT]; + facet->borderNoAdjust[3] = noAdjust[EN_LEFT]; + CM_SetBorderInward( facet, grid, gridPlanes, i, j, -1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } else { + // two seperate triangles + facet->surfacePlane = gridPlanes[i][j][0]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_TOP]; + facet->borderNoAdjust[0] = noAdjust[EN_TOP]; + facet->borderPlanes[1] = borders[EN_RIGHT]; + facet->borderNoAdjust[1] = noAdjust[EN_RIGHT]; + facet->borderPlanes[2] = gridPlanes[i][j][1]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_BOTTOM]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 4 ); + } + } + CM_SetBorderInward( facet, grid, gridPlanes, i, j, 0 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + + if ( numFacets == MAX_FACETS ) { + Com_Error( ERR_DROP, "MAX_FACETS" ); + } + facet = &facets[numFacets]; + memset( facet, 0, sizeof( *facet ) ); + + facet->surfacePlane = gridPlanes[i][j][1]; + facet->numBorders = 3; + facet->borderPlanes[0] = borders[EN_BOTTOM]; + facet->borderNoAdjust[0] = noAdjust[EN_BOTTOM]; + facet->borderPlanes[1] = borders[EN_LEFT]; + facet->borderNoAdjust[1] = noAdjust[EN_LEFT]; + facet->borderPlanes[2] = gridPlanes[i][j][0]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = borders[EN_TOP]; + if ( facet->borderPlanes[2] == -1 ) { + facet->borderPlanes[2] = CM_EdgePlaneNum( grid, gridPlanes, i, j, 5 ); + } + } + CM_SetBorderInward( facet, grid, gridPlanes, i, j, 1 ); + if ( CM_ValidateFacet( facet ) ) { + CM_AddFacetBevels( facet ); + numFacets++; + } + } + } + } + + // copy the results out + pf->numPlanes = numPlanes; + pf->numFacets = numFacets; + if (numFacets) + { + pf->facets = (facet_t *) Z_Malloc( numFacets * sizeof( *pf->facets ), TAG_BSP, qfalse); + memcpy( pf->facets, facets, numFacets * sizeof( *pf->facets ) ); + } + else + { + pf->facets = 0; + } + pf->planes = (patchPlane_t *) Z_Malloc( numPlanes * sizeof( *pf->planes ), TAG_BSP, qfalse); + memcpy( pf->planes, planes, numPlanes * sizeof( *pf->planes ) ); + + Z_Free(facets); +} + +#endif // _XBOX + +static patchCollide_t *pfScratch = 0; +void CM_PreparePatchCollide(int num) +{ + pfScratch = (patchCollide_t *) Z_Malloc( sizeof( *pfScratch ) * num, TAG_BSP, qfalse ); +} + +cGrid_t *cm_grid = 0; +void CM_GridAlloc() +{ + if (cm_grid) return; + cm_grid = (cGrid_t*)Z_Malloc(sizeof(cGrid_t), TAG_TEMP_WORKSPACE, qfalse); +} + +void CM_GridDealloc() +{ + Z_Free(cm_grid); + cm_grid = 0; +} + +/* +=================== +CM_GeneratePatchCollide + +Creates an internal structure that will be used to perform +collision detection with a patch mesh. + +Points is packed as concatenated rows. +=================== +*/ +#ifdef _XBOX +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points, + facetLoad_t *facetbuf, int *gridbuf ) { + patchCollide_t *pf; +// --AAA--AAA-- +// cGrid_t *grid = new cGrid_t; + cGrid_t *grid = cm_grid; +// --AAA--AAA-- + int i, j; + + memset(grid, 0, sizeof(cGrid_t)); + if ( width <= 2 || height <= 2 || !points ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: bad parameters: (%i, %i, %p)", + width, height, points ); + } + + if ( !(width & 1) || !(height & 1) ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: even sizes are invalid for quadratic meshes" ); + } + + if ( width > CM_MAX_GRID_SIZE || height > CM_MAX_GRID_SIZE ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: source is > CM_MAX_GRID_SIZE" ); + } + + // build a grid + grid->width = width; + grid->height = height; + grid->wrapWidth = qfalse; + grid->wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + VectorCopy( points[j*width + i], grid->points[i][j] ); + } + } + + // subdivide the grid + CM_SetGridWrapWidth( grid ); + CM_SubdivideGridColumns( grid ); + CM_RemoveDegenerateColumns( grid ); + + CM_TransposeGrid( grid ); + + CM_SetGridWrapWidth( grid ); + CM_SubdivideGridColumns( grid ); + CM_RemoveDegenerateColumns( grid ); + + // we now have a grid of points exactly on the curve + // the aproximate surface defined by these points will be + // collided against + + // --AAA--AAA-- +// pf = (patchCollide_t *) Z_Malloc( sizeof( *pf ), TAG_BSP, qfalse ); + pf = pfScratch++; + // --AAA--AAA-- + + ClearBounds( pf->bounds[0], pf->bounds[1] ); + for ( i = 0 ; i < grid->width ; i++ ) { + for ( j = 0 ; j < grid->height ; j++ ) { + AddPointToBounds( grid->points[i][j], pf->bounds[0], pf->bounds[1] ); + } + } + + c_totalPatchBlocks += ( grid->width - 1 ) * ( grid->height - 1 ); + + // generate a bsp tree for the surface + CM_PatchCollideFromGrid( grid, pf, facetbuf, gridbuf ); + + // expand by one unit for epsilon purposes + pf->bounds[0][0] -= 1; + pf->bounds[0][1] -= 1; + pf->bounds[0][2] -= 1; + + pf->bounds[1][0] += 1; + pf->bounds[1][1] += 1; + pf->bounds[1][2] += 1; + +// --AAA--AAA-- +// delete grid; +// --AAA--AAA-- + + return pf; +} + +#else + +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ) { + patchCollide_t *pf; + MAC_STATIC cGrid_t grid; + int i, j; + + if ( width <= 2 || height <= 2 || !points ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: bad parameters: (%i, %i, %p)", + width, height, points ); + } + + if ( !(width & 1) || !(height & 1) ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: even sizes are invalid for quadratic meshes" ); + } + + if ( width > CM_MAX_GRID_SIZE || height > CM_MAX_GRID_SIZE ) { + Com_Error( ERR_DROP, "CM_GeneratePatchFacets: source is > CM_MAX_GRID_SIZE" ); + } + + // build a grid + grid.width = width; + grid.height = height; + grid.wrapWidth = qfalse; + grid.wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + VectorCopy( points[j*width + i], grid.points[i][j] ); + } + } + + // subdivide the grid + CM_SetGridWrapWidth( &grid ); + CM_SubdivideGridColumns( &grid ); + CM_RemoveDegenerateColumns( &grid ); + + CM_TransposeGrid( &grid ); + + CM_SetGridWrapWidth( &grid ); + CM_SubdivideGridColumns( &grid ); + CM_RemoveDegenerateColumns( &grid ); + + // we now have a grid of points exactly on the curve + // the aproximate surface defined by these points will be + // collided against + pf = (patchCollide_t *) Z_Malloc( sizeof( *pf ), TAG_BSP, qfalse ); + ClearBounds( pf->bounds[0], pf->bounds[1] ); + for ( i = 0 ; i < grid.width ; i++ ) { + for ( j = 0 ; j < grid.height ; j++ ) { + AddPointToBounds( grid.points[i][j], pf->bounds[0], pf->bounds[1] ); + } + } + + c_totalPatchBlocks += ( grid.width - 1 ) * ( grid.height - 1 ); + + // generate a bsp tree for the surface + CM_PatchCollideFromGrid( &grid, pf ); + + // expand by one unit for epsilon purposes + pf->bounds[0][0] -= 1; + pf->bounds[0][1] -= 1; + pf->bounds[0][2] -= 1; + + pf->bounds[1][0] += 1; + pf->bounds[1][1] += 1; + pf->bounds[1][2] += 1; + + return pf; +} + +#endif // _XBOX + +/* +================================================================================ + +TRACE TESTING + +================================================================================ +*/ + +/* +==================== +CM_TraceThroughPatchCollide + +Modifies tr->tr if any of the facets effect the trace +==================== +*/ + +/* +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) +{ + int i, j, n; + float d1, d2, offset, planedist, fraction; + vec3_t v1, v2, normal, point; + patchPlane_t *planes; + facet_t *facet; + // + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) + { + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, normal); + for (n = 0; n < 3; n++) + { + if (normal[n] > 0) v1[n] = tw->size[0][n]; + else v1[n] = tw->size[1][n]; + } //end for + VectorNegate(normal, v2); + offset = DotProduct(v1, v2); + //offset = 0; + // + planedist = planes->plane[3] + offset; + // + d1 = DotProduct( tw->start, normal ) - planedist; + d2 = DotProduct( tw->end, normal ) - planedist; + + // if completely in front of face, no intersection with the entire patch + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + continue; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + continue; + } + + // crosses face + if (d1 > d2) { // enter + fraction = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( fraction < 0 ) { + fraction = 0; + } + for (j = 0; j < 3; j++) + point[j] = tw->start[j] + (tw->end[j] - tw->start[j]) * fraction; + } + else { + continue; + } + // + for (j = 0; j < facet->numBorders; j++) + { + planes = &pc->planes[ facet->borderPlanes[j] ]; + if (!facet->borderInward[j]) + { + VectorNegate(planes->plane, normal); + planedist = -planes->plane[3]; + } //end if + else + { + VectorCopy(planes->plane, normal); + planedist = planes->plane[3]; + } //end else + for (n = 0; n < 3; n++) + { + if (normal[n] > 0) v1[n] = tw->size[0][n]; + else v1[n] = tw->size[1][n]; + } //end for + VectorNegate(normal, v2); + offset = DotProduct(v1, v2); + //offset = 0; + planedist -= offset; + //the hit point should be in front of the (inward facing) border plane + if (DotProduct(point, normal) - planedist < -ON_EPSILON) break; + } //end for + if (j < facet->numBorders) continue; + // + if (fraction < tw->trace.fraction) + { + debugPatchCollide = pc; + debugFacet = facet; + + tw->trace.fraction = fraction; + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy( planes->plane, tw->trace.plane.normal ); + tw->trace.plane.dist = planes->plane[3]; + } //end if + } //end for +} //end of the function CM_TraceThroughPatchCollide*/ + +/* +==================== +CM_TracePointThroughPatchCollide + + special case for point traces because the patch collide "brushes" have no volume +==================== +*/ +#ifdef _XBOX +void CM_TracePointThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + qboolean frontFacing[MAX_PATCH_PLANES]; + float intersection[MAX_PATCH_PLANES]; + float intersect; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d1, d2; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + +#ifndef BSPC + if ( !cm_playerCurveClip->integer && !tw->isPoint ) { + return; // FIXME: until I get player sized clipping working right + } +#endif + + if (!pc->numFacets) + { //not gonna do anything anyhow? + return; + } + // determine the trace's relationship to all planes + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + if ( d1 <= 0 ) { + frontFacing[i] = qfalse; + } else { + frontFacing[i] = qtrue; + } + if ( d1 == d2 ) { + intersection[i] = WORLD_SIZE; + } else { + intersection[i] = d1 / ( d1 - d2 ); + if ( intersection[i] <= 0 ) { + intersection[i] = WORLD_SIZE; + } + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + if ( !frontFacing[facet->surfacePlane] ) { + continue; + } + intersect = intersection[facet->surfacePlane]; + if ( intersect < 0 ) { + continue; // surface is behind the starting point + } + if ( intersect > tw->trace.fraction ) { + continue; // already hit something closer + } + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->GetBorderPlanes()[j]; + if ( frontFacing[k] ^ facet->GetBorderInward()[j] ) { + if ( intersection[k] > intersect ) { + break; + } + } else { + if ( intersection[k] < intersect ) { + break; + } + } + } + if ( j == facet->numBorders ) { + // we hit this facet +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif //BSPC + planes = &pc->planes[facet->surfacePlane]; + + // calculate intersection with a slight pushoff + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + tw->trace.fraction = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if ( tw->trace.fraction < 0 ) { + tw->trace.fraction = 0; + } + + VectorCopy( planes->plane, tw->trace.plane.normal ); + tw->trace.plane.dist = planes->plane[3]; + } + } +} + +#else // _XBOX + +void CM_TracePointThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + qboolean frontFacing[MAX_PATCH_PLANES]; + float intersection[MAX_PATCH_PLANES]; + float intersect; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d1, d2; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + +#ifndef BSPC + if ( !cm_playerCurveClip->integer && !tw->isPoint ) { + return; // FIXME: until I get player sized clipping working right + } +#endif + + if (!pc->numFacets) + { //not gonna do anything anyhow? + return; + } + // determine the trace's relationship to all planes + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + if ( d1 <= 0 ) { + frontFacing[i] = qfalse; + } else { + frontFacing[i] = qtrue; + } + if ( d1 == d2 ) { + intersection[i] = WORLD_SIZE; + } else { + intersection[i] = d1 / ( d1 - d2 ); + if ( intersection[i] <= 0 ) { + intersection[i] = WORLD_SIZE; + } + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + if ( !frontFacing[facet->surfacePlane] ) { + continue; + } + intersect = intersection[facet->surfacePlane]; + if ( intersect < 0 ) { + continue; // surface is behind the starting point + } + if ( intersect > tw->trace.fraction ) { + continue; // already hit something closer + } + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->borderPlanes[j]; + if ( frontFacing[k] ^ facet->borderInward[j] ) { + if ( intersection[k] > intersect ) { + break; + } + } else { + if ( intersection[k] < intersect ) { + break; + } + } + } + if ( j == facet->numBorders ) { + // we hit this facet +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif //BSPC + planes = &pc->planes[facet->surfacePlane]; + + // calculate intersection with a slight pushoff + offset = DotProduct( tw->offsets[ planes->signbits ], planes->plane ); + d1 = DotProduct( tw->start, planes->plane ) - planes->plane[3] + offset; + d2 = DotProduct( tw->end, planes->plane ) - planes->plane[3] + offset; + tw->trace.fraction = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 ); + + if ( tw->trace.fraction < 0 ) { + tw->trace.fraction = 0; + } + + VectorCopy( planes->plane, tw->trace.plane.normal ); + tw->trace.plane.dist = planes->plane[3]; + } + } +} + +#endif + +/* +==================== +CM_CheckFacetPlane +==================== +*/ +int CM_CheckFacetPlane(float *plane, vec3_t start, vec3_t end, float *enterFrac, float *leaveFrac, int *hit) { + float d1, d2, f; + + *hit = qfalse; + + d1 = DotProduct( start, plane ) - plane[3]; + d2 = DotProduct( end, plane ) - plane[3]; + + // if completely in front of face, no intersection with the entire facet + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return qfalse; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + return qtrue; + } + + // crosses face + if (d1 > d2) { // enter + f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f < 0 ) { + f = 0; + } + //always favor previous plane hits and thus also the surface plane hit + if (f > *enterFrac) { + *enterFrac = f; + *hit = qtrue; + } + } else { // leave + f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f > 1 ) { + f = 1; + } + if (f < *leaveFrac) { + *leaveFrac = f; + } + } + return qtrue; +} + +/* +==================== +CM_TraceThroughPatchCollide +==================== +*/ +#ifdef _XBOX +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) +{ + int i, j, hit, hitnum; + float offset, enterFrac, leaveFrac, t; + patchPlane_t *planes; + facet_t *facet; + float plane[4], bestplane[4]; + vec3_t startp, endp; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + +#ifndef CULL_BBOX + // I'm not sure if test is strictly correct. Are all + // bboxes axis aligned? Do I care? It seems to work + // good enough... + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw->bounds[0][i] > pc->bounds[1][i] + || tw->bounds[1][i] < pc->bounds[0][i] ) { + return; + } + } +#endif + + if (tw->isPoint) { + + CM_TracePointThroughPatchCollide( tw, pc ); + return; + } +#ifndef ADDBEVELS + CM_TracePointThroughPatchCollide( tw, pc ); + return; +#endif + // + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + enterFrac = -1.0; + leaveFrac = 1.0; + hitnum = -1; + // + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] -= offset; + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { + continue; + } + if (hit) { + Vector4Copy(plane, bestplane); + } + for ( j = 0 ; j < facet->numBorders ; j++ ) { + planes = &pc->planes[ facet->GetBorderPlanes()[j] ]; + if (facet->GetBorderInward()[j]) { + VectorNegate(planes->plane, plane); + plane[3] = -planes->plane[3]; + } + else { + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + } + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + // NOTE: this works even though the plane might be flipped because the bbox is centered + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] += Q_fabs(offset); + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { + break; + } + if (hit) { + hitnum = j; + Vector4Copy(plane, bestplane); + } + } + if (j < facet->numBorders) continue; + //never clip against the back side + if (hitnum == facet->numBorders - 1) continue; + if (enterFrac < leaveFrac && enterFrac >= 0) { + if (enterFrac < tw->trace.fraction) { + if (enterFrac < 0) { + enterFrac = 0; + } +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv && cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif // BSPC + + tw->trace.fraction = enterFrac; + VectorCopy( bestplane, tw->trace.plane.normal ); + tw->trace.plane.dist = bestplane[3]; + } + } + } +} + +#else // _XBOX + +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) +{ + int i, j, hit, hitnum; + float offset, enterFrac, leaveFrac, t; + patchPlane_t *planes; + facet_t *facet; + float plane[4], bestplane[4]; + vec3_t startp, endp; +#ifndef BSPC + static cvar_t *cv; +#endif //BSPC + + if (tw->isPoint) { + CM_TracePointThroughPatchCollide( tw, pc ); + return; + } +#ifndef ADDBEVELS + CM_TracePointThroughPatchCollide( tw, pc ); + return; +#endif + // + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + enterFrac = -1.0; + leaveFrac = 1.0; + hitnum = -1; + // + planes = &pc->planes[ facet->surfacePlane ]; + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] -= offset; + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { + continue; + } + if (hit) { + Vector4Copy(plane, bestplane); + } + for ( j = 0 ; j < facet->numBorders ; j++ ) { + planes = &pc->planes[ facet->borderPlanes[j] ]; + if (facet->borderInward[j]) { + VectorNegate(planes->plane, plane); + plane[3] = -planes->plane[3]; + } + else { + VectorCopy(planes->plane, plane); + plane[3] = planes->plane[3]; + } + if ( tw->sphere.use ) { + // adjust the plane distance apropriately for radius + plane[3] += tw->sphere.radius; + + // find the closest point on the capsule to the plane + t = DotProduct( plane, tw->sphere.offset ); + if ( t > 0.0f ) + { + VectorSubtract( tw->start, tw->sphere.offset, startp ); + VectorSubtract( tw->end, tw->sphere.offset, endp ); + } + else + { + VectorAdd( tw->start, tw->sphere.offset, startp ); + VectorAdd( tw->end, tw->sphere.offset, endp ); + } + } + else { + // NOTE: this works even though the plane might be flipped because the bbox is centered + offset = DotProduct( tw->offsets[ planes->signbits ], plane); + plane[3] += fabs(offset); + VectorCopy( tw->start, startp ); + VectorCopy( tw->end, endp ); + } + if (!CM_CheckFacetPlane(plane, startp, endp, &enterFrac, &leaveFrac, &hit)) { + break; + } + if (hit) { + hitnum = j; + Vector4Copy(plane, bestplane); + } + } + if (j < facet->numBorders) continue; + //never clip against the back side + if (hitnum == facet->numBorders - 1) continue; + if (enterFrac < leaveFrac && enterFrac >= 0) { + if (enterFrac < tw->trace.fraction) { + if (enterFrac < 0) { + enterFrac = 0; + } +#ifndef BSPC + if (!cv) { + cv = Cvar_Get( "r_debugSurfaceUpdate", "1", 0 ); + } + if (cv && cv->integer) { + debugPatchCollide = pc; + debugFacet = facet; + } +#endif // BSPC + + tw->trace.fraction = enterFrac; + VectorCopy( bestplane, tw->trace.plane.normal ); + tw->trace.plane.dist = bestplane[3]; + } + } + } +} +#endif // _XBOX + +/* +======================================================================= + +POSITION TEST + +======================================================================= +*/ + +#define BOX_FRONT 0 +#define BOX_BACK 1 +#define BOX_CROSS 2 + +/* +==================== +CM_PositionTestInPatchCollide + +Modifies tr->tr if any of the facets effect the trace +==================== +*/ +#ifdef _XBOX +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + int cross[MAX_PATCH_PLANES]; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d; + +//return qfalse; + +#ifndef CULL_BBOX + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw->bounds[0][i] > pc->bounds[1][i] + || tw->bounds[1][i] < pc->bounds[0][i] ) { + return qfalse; + } + } +#endif + + // determine if the box is in front, behind, or crossing each plane + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + d = DotProduct( tw->start, planes->plane ) - planes->plane[3]; + offset = Q_fabs( DotProduct( tw->offsets[ planes->signbits ], planes->plane ) ); + if ( d < -offset ) { + cross[i] = BOX_FRONT; + } else if ( d > offset ) { + cross[i] = BOX_BACK; + } else { + cross[i] = BOX_CROSS; + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + // the facet plane must be in a cross state + if ( cross[facet->surfacePlane] != BOX_CROSS ) { + continue; + } + // all of the boundaries must be either cross or back + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->GetBorderPlanes()[j]; + if ( cross[ k ] == BOX_CROSS ) { + continue; + } + if ( cross[k] ^ facet->GetBorderInward()[j] ) { + break; + } + } + // if we passed all borders, we are definately in this facet + if ( j == facet->numBorders ) { + return qtrue; + } + } + + return qfalse; +} + +#else // _XBOX + +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ) { + int cross[MAX_PATCH_PLANES]; + const patchPlane_t *planes; + const facet_t *facet; + int i, j, k; + float offset; + float d; + +//return qfalse; + +#ifndef CULL_BBOX + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw->bounds[0][i] > pc->bounds[1][i] + || tw->bounds[1][i] < pc->bounds[0][i] ) { + return qfalse; + } + } +#endif + + // determine if the box is in front, behind, or crossing each plane + planes = pc->planes; + for ( i = 0 ; i < pc->numPlanes ; i++, planes++ ) { + d = DotProduct( tw->start, planes->plane ) - planes->plane[3]; + offset = fabs( DotProduct( tw->offsets[ planes->signbits ], planes->plane ) ); + if ( d < -offset ) { + cross[i] = BOX_FRONT; + } else if ( d > offset ) { + cross[i] = BOX_BACK; + } else { + cross[i] = BOX_CROSS; + } + } + + + // see if any of the surface planes are intersected + facet = pc->facets; + for ( i = 0 ; i < pc->numFacets ; i++, facet++ ) { + // the facet plane must be in a cross state + if ( cross[facet->surfacePlane] != BOX_CROSS ) { + continue; + } + // all of the boundaries must be either cross or back + for ( j = 0 ; j < facet->numBorders ; j++ ) { + k = facet->borderPlanes[j]; + if ( cross[ k ] == BOX_CROSS ) { + continue; + } + if ( cross[k] ^ facet->borderInward[j] ) { + break; + } + } + // if we passed all borders, we are definately in this facet + if ( j == facet->numBorders ) { + return qtrue; + } + } + + return qfalse; +} + +#endif // _XBOX + + +/* +======================================================================= + +DEBUGGING + +======================================================================= +*/ + + +/* +================== +CM_DrawDebugSurface + +Called from the renderer +================== +*/ +#ifndef BSPC +void BotDrawDebugPolygons(void (*drawPoly)(int color, int numPoints, float *points), int value); +#endif + +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ) { +#ifndef _XBOX + static cvar_t *cv; + const patchCollide_t *pc; + facet_t *facet; + winding_t *w; + int i, j, k, n; + int curplanenum, planenum, curinward, inward; + float plane[4]; + vec3_t mins = {-15, -15, -28}, maxs = {15, 15, 28}; + //vec3_t mins = {0, 0, 0}, maxs = {0, 0, 0}; + vec3_t v1, v2; + + if ( !debugPatchCollide ) { + return; + } + +#ifndef BSPC + if ( !cv ) { + cv = Cvar_Get( "cm_debugSize", "2", 0 ); + } +#endif + pc = debugPatchCollide; + + for ( i = 0, facet = pc->facets ; i < pc->numFacets ; i++, facet++ ) { + + for ( k = 0 ; k < facet->numBorders + 1; k++ ) { + // + if (k < facet->numBorders) { + planenum = facet->borderPlanes[k]; + inward = facet->borderInward[k]; + } + else { + planenum = facet->surfacePlane; + inward = qfalse; + //continue; + } + + Vector4Copy( pc->planes[ planenum ].plane, plane ); + + //planenum = facet->surfacePlane; + if ( inward ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + + plane[3] += cv->value; + //* + for (n = 0; n < 3; n++) + { + if (plane[n] > 0) v1[n] = maxs[n]; + else v1[n] = mins[n]; + } //end for + VectorNegate(plane, v2); + plane[3] += fabs(DotProduct(v1, v2)); + //*/ + + w = BaseWindingForPlane( plane, plane[3] ); + for ( j = 0 ; j < facet->numBorders + 1 && w; j++ ) { + // + if (j < facet->numBorders) { + curplanenum = facet->borderPlanes[j]; + curinward = facet->borderInward[j]; + } + else { + curplanenum = facet->surfacePlane; + curinward = qfalse; + //continue; + } + // + if (curplanenum == planenum) continue; + + Vector4Copy( pc->planes[ curplanenum ].plane, plane ); + if ( !curinward ) { + VectorSubtract( vec3_origin, plane, plane ); + plane[3] = -plane[3]; + } + // if ( !facet->borderNoAdjust[j] ) { + plane[3] -= cv->value; + // } + for (n = 0; n < 3; n++) + { + if (plane[n] > 0) v1[n] = maxs[n]; + else v1[n] = mins[n]; + } //end for + VectorNegate(plane, v2); + plane[3] -= fabs(DotProduct(v1, v2)); + + ChopWindingInPlace( &w, plane, plane[3], 0.1f ); + } + if ( w ) { + if ( facet == debugFacet ) { + drawPoly( 4, w->numpoints, w->p[0] ); + //Com_Printf("blue facet has %d border planes\n", facet->numBorders); + } else { + drawPoly( 1, w->numpoints, w->p[0] ); + } + FreeWinding( w ); + } + else + Com_Printf("winding chopped away by border planes\n"); + } + } + + // draw the debug block + { + vec3_t v[3]; + + VectorCopy( debugBlockPoints[0], v[0] ); + VectorCopy( debugBlockPoints[1], v[1] ); + VectorCopy( debugBlockPoints[2], v[2] ); + drawPoly( 2, 3, v[0] ); + + VectorCopy( debugBlockPoints[2], v[0] ); + VectorCopy( debugBlockPoints[3], v[1] ); + VectorCopy( debugBlockPoints[0], v[2] ); + drawPoly( 2, 3, v[0] ); + } + +#if 0 + vec3_t v[4]; + + v[0][0] = pc->bounds[1][0]; + v[0][1] = pc->bounds[1][1]; + v[0][2] = pc->bounds[1][2]; + + v[1][0] = pc->bounds[1][0]; + v[1][1] = pc->bounds[0][1]; + v[1][2] = pc->bounds[1][2]; + + v[2][0] = pc->bounds[0][0]; + v[2][1] = pc->bounds[0][1]; + v[2][2] = pc->bounds[1][2]; + + v[3][0] = pc->bounds[0][0]; + v[3][1] = pc->bounds[1][1]; + v[3][2] = pc->bounds[1][2]; + + drawPoly( 4, v[0] ); +#endif +#endif // _XBOX +} + + diff --git a/code/qcommon/cm_patch.h b/code/qcommon/cm_patch.h new file mode 100644 index 0000000..42538d8 --- /dev/null +++ b/code/qcommon/cm_patch.h @@ -0,0 +1,121 @@ + +//#define CULL_BBOX + +/* + +This file does not reference any globals, and has these entry points: + +void CM_ClearLevelPatches( void ); +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, const vec3_t *points ); +void CM_TraceThroughPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +qboolean CM_PositionTestInPatchCollide( traceWork_t *tw, const struct patchCollide_s *pc ); +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, flaot *points) ); + + +Issues for collision against curved surfaces: + +Surface edges need to be handled differently than surface planes + +Plane expansion causes raw surfaces to expand past expanded bounding box + +Position test of a volume against a surface is tricky. + +Position test of a point against a surface is not well defined, because the surface has no volume. + + +Tracing leading edge points instead of volumes? +Position test by tracing corner to corner? (8*7 traces -- ouch) + +coplanar edges +triangulated patches +degenerate patches + + endcaps + degenerate + +WARNING: this may misbehave with meshes that have rows or columns that only +degenerate a few triangles. Completely degenerate rows and columns are handled +properly. +*/ + + +#define MAX_FACETS 1024 +#define MAX_PATCH_PLANES 2048 + +typedef struct { + float plane[4]; + int signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision +} patchPlane_t; + +#ifdef _XBOX +//Facets are now two structures - a maximum sized version that's used +//temporarily during load time, and smaller version that only allocates +//as much memory as needed. The load version is copied into the small +//version after it's been assembled. +#pragma pack(push, 1) +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + short borderPlanes[4+6+16]; + unsigned char borderInward[4+6+16]; + unsigned char borderNoAdjust[4+6+16]; +} facetLoad_t; + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + char *data; + + short *GetBorderPlanes(void) { return (short*)data; } + char *GetBorderInward(void) { return data + (numBorders * 2); } + char *GetBorderNoAdjust(void) + { return data + (numBorders * 2) + numBorders; } + + const short *GetBorderPlanes(void) const { return (short*)data; } + const char *GetBorderInward(void) const { return data + (numBorders * 2); } + const char *GetBorderNoAdjust(void) const + { return data + (numBorders * 2) + numBorders; } +} facet_t; +#pragma pack(pop) + +#else // _XBOX + +typedef struct { + int surfacePlane; + int numBorders; // 3 or four + 6 axial bevels + 4 or 3 * 4 edge bevels + int borderPlanes[4+6+16]; + int borderInward[4+6+16]; + qboolean borderNoAdjust[4+6+16]; +} facet_t; + +#endif // _XBOX + +typedef struct patchCollide_s { + vec3_t bounds[2]; + int numPlanes; // surface planes plus edge planes + patchPlane_t *planes; + int numFacets; + facet_t *facets; +} patchCollide_t; + + +#define CM_MAX_GRID_SIZE 129 + +typedef struct { + int width; + int height; + qboolean wrapWidth; + qboolean wrapHeight; + vec3_t points[CM_MAX_GRID_SIZE][CM_MAX_GRID_SIZE]; // [width][height] +} cGrid_t; + +#define SUBDIVIDE_DISTANCE 16 //4 // never more than this units away from curve +#define PLANE_TRI_EPSILON 0.1 +#define WRAP_POINT_EPSILON 0.1 + +#ifdef _XBOX +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points, + facetLoad_t *facetbuf, int *gridbuf ); +#else +struct patchCollide_s *CM_GeneratePatchCollide( int width, int height, vec3_t *points ); +#endif // _XBOX diff --git a/code/qcommon/cm_polylib.cpp b/code/qcommon/cm_polylib.cpp new file mode 100644 index 0000000..bf21aa8 --- /dev/null +++ b/code/qcommon/cm_polylib.cpp @@ -0,0 +1,711 @@ + +// this is only used for visualization tools in cm_ debug functions + + +#include "cm_local.h" + + +// counters are only bumped when running single threaded, +// because they are an awefull coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + + s = sizeof(vec_t)*3*points + sizeof(int); + w = (winding_t *) Z_Malloc (s,TAG_BSP, qtrue);//TAG_WINDING); +// memset (w, 0, s); // qtrue above does this + return w; +} + +void FreeWinding (winding_t *w) +{ + if (*(unsigned *)w == 0xdeaddead) + Com_Error (ERR_FATAL, "FreeWinding: freed a freed winding"); + *(unsigned *)w = 0xdeaddead; + + c_active_windings--; + Z_Free (w); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize2(v1,v1); + VectorNormalize2(v2,v2); + if (DotProduct(v1, v2) < 0.999) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + c_removed += w->numpoints - nump; + w->numpoints = nump; + memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ + vec3_t v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + VectorSubtract (w->p[2], w->p[0], v2); + CrossProduct (v2, v1, normal); + VectorNormalize2(normal, normal); + *dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea (winding_t *w) +{ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += 0.5 * VectorLength ( cross ); + } + return total; +} + +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = WORLD_SIZE; // 99999; // WORLD_SIZE instead of MAX_WORLD_COORD so that... + maxs[0] = maxs[1] = maxs[2] = -WORLD_SIZE; //-99999; // ... it's guaranteed to be outide of legal + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, vec3_t center) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist) +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + +// find the major axis + + max = -MAX_MAP_BOUNDS; + x = -1; + for (i=0 ; i<3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + x = i; + max = v; + } + } + if (x==-1) + Com_Error (ERR_DROP, "BaseWindingForPlane: no axis found"); + + VectorCopy (vec3_origin, vup); + switch (x) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct (vup, normal); + VectorMA (vup, -v, normal, vup); + VectorNormalize2(vup, vup); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, MAX_MAP_BOUNDS, vup); + VectorScale (vright, MAX_MAP_BOUNDS, vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + c = AllocWinding (w->numpoints); + size = (int)((winding_t *)0)->p[w->numpoints]; + memcpy (c, w, size); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding (winding_t *w) +{ + int i; + winding_t *c; + + c = AllocWinding (w->numpoints); + for (i=0 ; inumpoints ; i++) + { + VectorCopy (w->p[w->numpoints-1-i], c->p[i]); + } + c->numpoints = w->numpoints; + return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + FreeWinding (in); + *inout = NULL; + return; + } + if (!counts[1]) + return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Com_Error (ERR_DROP, "ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Com_Error (ERR_DROP, "ClipWinding: MAX_POINTS_ON_WINDING"); + + FreeWinding (in); + *inout = f; +} + + +/* +================= +ChopWinding + +Returns the fragment of in that is on the front side +of the cliping plane. The original is freed. +================= +*/ +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist) +{ + winding_t *f, *b; + + ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); + FreeWinding (in); + if (b) + FreeWinding (b); + return f; +} + + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Com_Error (ERR_DROP, "CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Com_Error (ERR_DROP, "CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > MAX_MAP_BOUNDS || p1[j] < -MAX_MAP_BOUNDS) + Com_Error (ERR_DROP, "CheckFace: BOGUS_RANGE: %f",p1[j]); + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Com_Error (ERR_DROP, "CheckWinding: point off plane"); + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Com_Error (ERR_DROP, "CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize2 (edgenormal, edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Com_Error (ERR_DROP, "CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = qfalse; + back = qfalse; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = qtrue; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = qtrue; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + + +/* +================= +AddWindingToConvexHull + +Both w and *hull are on the same plane +================= +*/ +#define MAX_HULL_POINTS 128 +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ) { + int i, j, k; + float *p, *copy; + vec3_t dir; + float d; + int numHullPoints, numNew; + vec3_t hullPoints[MAX_HULL_POINTS]; + vec3_t newHullPoints[MAX_HULL_POINTS]; + vec3_t hullDirs[MAX_HULL_POINTS]; + qboolean hullSide[MAX_HULL_POINTS]; + qboolean outside; + + if ( !*hull ) { + *hull = CopyWinding( w ); + return; + } + + numHullPoints = (*hull)->numpoints; + memcpy( hullPoints, (*hull)->p, numHullPoints * sizeof(vec3_t) ); + + for ( i = 0 ; i < w->numpoints ; i++ ) { + p = w->p[i]; + + // calculate hull side vectors + for ( j = 0 ; j < numHullPoints ; j++ ) { + k = ( j + 1 ) % numHullPoints; + + VectorSubtract( hullPoints[k], hullPoints[j], dir ); + VectorNormalize2( dir, dir ); + CrossProduct( normal, dir, hullDirs[j] ); + } + + outside = qfalse; + for ( j = 0 ; j < numHullPoints ; j++ ) { + VectorSubtract( p, hullPoints[j], dir ); + d = DotProduct( dir, hullDirs[j] ); + if ( d >= ON_EPSILON ) { + outside = qtrue; + } + if ( d >= -ON_EPSILON ) { + hullSide[j] = qtrue; + } else { + hullSide[j] = qfalse; + } + } + + // if the point is effectively inside, do nothing + if ( !outside ) { + continue; + } + + // find the back side to front side transition + for ( j = 0 ; j < numHullPoints ; j++ ) { + if ( !hullSide[ j % numHullPoints ] && hullSide[ (j + 1) % numHullPoints ] ) { + break; + } + } + if ( j == numHullPoints ) { + continue; + } + + // insert the point here + VectorCopy( p, newHullPoints[0] ); + numNew = 1; + + // copy over all points that aren't double fronts + j = (j+1)%numHullPoints; + for ( k = 0 ; k < numHullPoints ; k++ ) { + if ( hullSide[ (j+k) % numHullPoints ] && hullSide[ (j+k+1) % numHullPoints ] ) { + continue; + } + copy = hullPoints[ (j+k+1) % numHullPoints ]; + VectorCopy( copy, newHullPoints[numNew] ); + numNew++; + } + + numHullPoints = numNew; + memcpy( hullPoints, newHullPoints, numHullPoints * sizeof(vec3_t) ); + } + + FreeWinding( *hull ); + w = AllocWinding( numHullPoints ); + w->numpoints = numHullPoints; + *hull = w; + memcpy( w->p, hullPoints, numHullPoints * sizeof(vec3_t) ); +} + + diff --git a/code/qcommon/cm_polylib.h b/code/qcommon/cm_polylib.h new file mode 100644 index 0000000..81ed41c --- /dev/null +++ b/code/qcommon/cm_polylib.h @@ -0,0 +1,51 @@ + +// this is only used for visualization tools in cm_ debug functions + +#ifndef CM_POLYLIB_H +#define CM_POLYLIB_H + +typedef struct +{ + int numpoints; + vec3_t p[4]; // variable sized +} winding_t; + +#define MAX_POINTS_ON_WINDING 64 + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS 3 + +#define CLIP_EPSILON 0.1 + +#define MAX_MAP_BOUNDS 65535 + +// you can define on_epsilon in the makefile as tighter +#ifndef ON_EPSILON +#define ON_EPSILON 0.1f +#endif + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, vec3_t center); +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back); +winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *ReverseWinding (winding_t *w); +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); + +void AddWindingToConvexHull( winding_t *w, winding_t **hull, vec3_t normal ); + +void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); +// frees the original if clipped + +void pw(winding_t *w); +#endif diff --git a/code/qcommon/cm_public.h b/code/qcommon/cm_public.h new file mode 100644 index 0000000..0369a32 --- /dev/null +++ b/code/qcommon/cm_public.h @@ -0,0 +1,72 @@ +#ifndef __CM_PUBLIC_H__ +#define __CM_PUBLIC_H__ + +#include "qfiles.h" + +qboolean CM_DeleteCachedMap(qboolean bGuaranteedOkToDelete); +#ifdef _XBOX +void CM_LoadMap( const char *name, qboolean clientload, int *checksum); +#else +void CM_LoadMap( const char *name, qboolean clientload, int *checksum, qboolean subBSP); +#endif +void CM_ClearMap( void ); +int CM_TotalMapContents(); + +clipHandle_t CM_InlineModel( int index ); // 0 = world, 1 + are bmodels +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs );//, const int contents ); + +int CM_ModelContents( clipHandle_t model, int subBSPIndex ); + + +int CM_NumClusters (void); +int CM_NumInlineModels( void ); +char *CM_EntityString (void); +char *CM_SubBSPEntityString (int index); +int CM_LoadSubBSP(const char *name, qboolean clientload); +int CM_FindSubBSP(int modelIndex); + +// returns an ORed contents mask +int CM_PointContents( const vec3_t p, clipHandle_t model ); +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); + +void CM_BoxTrace ( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask); +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles); + +#ifdef _XBOX +const byte *CM_ClusterPVS (int cluster); +#else +byte *CM_ClusterPVS (int cluster); +#endif + +int CM_PointLeafnum( const vec3_t p ); + +// only returns non-solid leafs +// overflow if return listsize and if *lastLeaf != list[listsize-1] +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *boxList, + int listsize, int *lastLeaf ); + +int CM_LeafCluster (int leafnum); +int CM_LeafArea (int leafnum); + +void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ); +qboolean CM_AreasConnected( int area1, int area2 ); + +int CM_WriteAreaBits( byte *buffer, int area ); + +//for savegames +void CM_WritePortalState (); +void CM_ReadPortalState (); + +// cm_marks.c +int CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + +// cm_patch.c +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ); + +#endif //__CM_PUBLIC_H__ diff --git a/code/qcommon/cm_randomterrain.cpp b/code/qcommon/cm_randomterrain.cpp new file mode 100644 index 0000000..12b01b4 --- /dev/null +++ b/code/qcommon/cm_randomterrain.cpp @@ -0,0 +1,1086 @@ +#include "../server/exe_headers.h" + +#include "cm_local.h" +#include "cm_patch.h" +#include "cm_landscape.h" +#include "../game/genericparser2.h" +#include "cm_randomterrain.h" + + +#define NOISE_SIZE 256 +#define NOISE_MASK (NOISE_SIZE - 1) + +static float noiseTable[NOISE_SIZE]; +static int noisePerm[NOISE_SIZE]; + +static void CM_NoiseInit( CCMLandScape *landscape ) +{ + int i; + + for ( i = 0; i < NOISE_SIZE; i++ ) + { + noiseTable[i] = landscape->flrand(-1.0f, 1.0f); + noisePerm[i] = (byte)landscape->irand(0, 255); + } +} + +#define VAL( a ) noisePerm[ ( a ) & ( NOISE_MASK )] +#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) ) + +#define LERP( a, b, w ) ( a * ( 1.0f - w ) + b * w ) + +static float GetNoiseValue( int x, int y, int z, int t ) +{ + int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t ); + + return noiseTable[index]; +} + +#if 0 +static float GetNoiseTime( int t ) +{ + int index = VAL( t ); + + return (1 + noiseTable[index]); +} +#endif + +static float CM_NoiseGet4f( float x, float y, float z, float t ) +{ + int i; + int ix, iy, iz, it; + float fx, fy, fz, ft; + float front[4]; + float back[4]; + float fvalue, bvalue, value[2], finalvalue; + + ix = ( int ) floor( x ); + fx = x - ix; + iy = ( int ) floor( y ); + fy = y - iy; + iz = ( int ) floor( z ); + fz = z - iz; + it = ( int ) floor( t ); + ft = t - it; + + for ( i = 0; i < 2; i++ ) + { + front[0] = GetNoiseValue( ix, iy, iz, it + i ); + front[1] = GetNoiseValue( ix+1, iy, iz, it + i ); + front[2] = GetNoiseValue( ix, iy+1, iz, it + i ); + front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i ); + + back[0] = GetNoiseValue( ix, iy, iz + 1, it + i ); + back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i ); + back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i ); + back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i ); + + fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy ); + bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy ); + + value[i] = LERP( fvalue, bvalue, fz ); + } + + finalvalue = LERP( value[0], value[1], ft ); + return finalvalue; +} + + + + + + + + +/****** lincrv.c ******/ +/* Ken Shoemake, 1994 */ + +/* Perform a generic vector unary operation. */ +#define V_Op(vdst,gets,vsrc,n) {register int V_i;\ + for(V_i=(n)-1;V_i>=0;V_i--) (vdst)[V_i] gets ((vsrc)[V_i]);} + +static void lerp(float t, float a0, float a1, vec4_t p0, vec4_t p1, int m, vec4_t p) +{ + register float t0=(a1-t)/(a1-a0), t1=1-t0; + register int i; + for (i=m-1; i>=0; i--) p[i] = t0*p0[i] + t1*p1[i]; +} + +/* DialASpline(t,a,p,m,n,work,Cn,interp,val) computes a point val at parameter + t on a spline with knot values a and control points p. The curve will have + Cn continuity, and if interp is TRUE it will interpolate the control points. + Possibilities include Langrange interpolants, Bezier curves, Catmull-Rom + interpolating splines, and B-spline curves. Points have m coordinates, and + n+1 of them are provided. The work array must have room for n+1 points. + */ +static int DialASpline(float t, float a[], vec4_t p[], int m, int n, vec4_t work[], + unsigned int Cn, bool interp, vec4_t val) +{ + register int i, j, k, h, lo, hi; + + if (Cn>n-1) Cn = n-1; /* Anything greater gives one polynomial */ + for (k=0; t> a[k]; k++); /* Find enclosing knot interval */ + for (h=k; t==a[k]; k++); /* May want to use fewer legs */ + if (k>n) {k = n; if (h>k) h = k;} + h = 1+Cn - (k-h); k--; + lo = k-Cn; hi = k+1+Cn; + + if (interp) { /* Lagrange interpolation steps */ + int drop=0; + if (lo<0) {lo = 0; drop += Cn-k; + if (hi-lon) {hi = n; drop += k+1+Cn-n; + if (hi-lo=j; i--) { + lerp(t,a[lo+i],a[lo+i+tmp],work[lo+i],work[lo+i+1],m,work[lo+i+1]); + } + } + V_Op(val,=,work[lo+h],m); + return (k); +} + +#define BIG (1.0e12) + +static vec_t Vector2Normalize( vec2_t v ) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1]; + length = sqrt (length); + + if ( length ) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + } + + return length; +} + +CPathInfo::CPathInfo(CCMLandScape *landscape, int numPoints, float bx, float by, float ex, float ey, + float minWidth, float maxWidth, float depth, float deviation, float breadth, + CPathInfo *Connected, unsigned CreationFlags) : + mNumPoints(numPoints), + mMinWidth(minWidth), + mMaxWidth(maxWidth), + mDepth(depth), + mDeviation(deviation), + mBreadth(breadth) +{ + int i, numConnected, index; + float position, goal, deltaGoal; +// float random, delta; + bool horizontal; + float *point; + float currentWidth; + float currentPosition; + vec2_t testPoint, percPoint, diffPoint, normalizedPath; + float distance, length; + + CreateCircle(); + + numConnected = -1; + if (Connected) + { // we are connecting to an existing spline + numConnected = Connected->GetNumPoints(); + if (numConnected >= SPLINE_MERGE_SIZE) + { // plenty of points to choose from + mNumPoints += SPLINE_MERGE_SIZE; + } + else + { // the existing spline doesn't have enough points + mNumPoints += numConnected; + } + } + + mPoints = (vec4_t *)malloc(sizeof(vec4_t) * mNumPoints); + mWork = (vec4_t *)malloc(sizeof(vec4_t) * (mNumPoints+1)); + mWeights = (vec_t *)malloc(sizeof(vec_t) * (mNumPoints+1)); + + length = sqrt((ex-bx)*(ex-bx) + (ey-by)*(ey-by)); + if (fabs(ex - bx) >= fabs(ey - by)) + { // this appears to be a horizontal path + mInc = 1.0 / fabs(ex - bx); + horizontal = true; + position = by; + goal = ey; + deltaGoal = (ey-by) / (numPoints-1); + } + else + { // this appears to be a vertical path + mInc = 1.0 / fabs(ey - by); + horizontal = false; + position = bx; + goal = ex; + deltaGoal = (ex-bx) / (numPoints-1); + } + normalizedPath[0] = (ex-bx); + normalizedPath[1] = (ey-by); + Vector2Normalize(normalizedPath); + // approx calculate how much we need to iterate through the spline to hit every point + mInc /= 16; + + currentWidth = landscape->flrand(minWidth, maxWidth); + currentPosition = 0.0; + + for(i=0;iGetPoint(index); + mPoints[i][0] = point[0]; + mPoints[i][1] = point[1]; + mPoints[i][3] = point[3]; + } + else + { + if (horizontal) + { // we appear to be going horizontal, so spread the randomness across the vertical + mPoints[i][0] = ((ex - bx) * currentPosition) + bx; + mPoints[i][1] = position; + } + else + { // we appear to be going vertical, so spread the randomness across the horizontal + mPoints[i][0] = position; + mPoints[i][1] = ((ey - by) * currentPosition) + by; + } + currentPosition += 1.0 / (numPoints-1); + + // set the width of the spline + mPoints[i][3] = currentWidth; + currentWidth += landscape->flrand(-0.1f, 0.1f); + if (currentWidth < minWidth) + { + currentWidth = minWidth; + } + else if (currentWidth > maxWidth) + { + currentWidth = maxWidth; + } + + // see how far we are from the goal +/* delta = (goal - position) * currentPosition; + // calculate the randomness we are allowed at this place + random = landscape->flrand(-mDeviation/1.0, mDeviation/1.0) * (1.0 - currentPosition); + position += delta + random;*/ + + if (i == mNumPoints-2) + { // -2 because we are calculating for the next point + position = goal; + } + else + { + if (i == 0) + { + position += deltaGoal + landscape->flrand(-mDeviation/10.0, mDeviation/10.0); + } + else + { + position += deltaGoal + landscape->flrand(-mDeviation*1.5, mDeviation*1.5); + } + } + + + if (position > 0.9) + { // too far over, so move back a bit + position = 0.9 - landscape->flrand(0.02f, 0.1f); + } + if (position < 0.1) + { // too near, so move bakc a bit + position = 0.1 + landscape->flrand(0.02f, 0.1f); + } + + // check our deviation from the straight line to the end + if (horizontal) + { + testPoint[0] = ((ex - bx) * currentPosition) + bx; + testPoint[1] = position; + } + else + { + testPoint[0] = position; + testPoint[1] = ((ey - by) * currentPosition) + by; + } + // dot product of the normal of the path to the point we are at + distance = ((testPoint[0]-bx)*normalizedPath[0]) + ((testPoint[1]-by)*normalizedPath[1]); + // find the perpendicular place that is intersected by the point and the path + percPoint[0] = (distance * normalizedPath[0]) + bx; + percPoint[1] = (distance * normalizedPath[1]) + by; + // calculate the difference between the perpendicular point and the test point + diffPoint[0] = testPoint[0] - percPoint[0]; + diffPoint[1] = testPoint[1] - percPoint[1]; + // calculate the distance + distance = sqrt((diffPoint[0]*diffPoint[0]) + (diffPoint[1]*diffPoint[1])); + if (distance > mDeviation) + { // we are beyond our allowed deviation, so head back + if (horizontal) + { + position = (ey-by) * currentPosition + by; + } + else + { + position = (ex-bx) * currentPosition + bx; + } + position += landscape->flrand(-mDeviation/2.0, mDeviation/2.0); + } + } + } + mWeights[mNumPoints] = (float)BIG; +} + +CPathInfo::~CPathInfo(void) +{ + free(mWeights); + free(mWork); + free(mPoints); +} + +void CPathInfo::CreateCircle(void) +{ + int x, y; + float r, d; + + memset(mCircleStamp, 0, sizeof(mCircleStamp)); + r = CIRCLE_STAMP_SIZE; + for(x=0;x r) + { + mCircleStamp[y][x] = 255; + } + else + { + mCircleStamp[y][x] = pow(sin((float)(d / r * M_PI / 2)), mBreadth) * 255; + } + } + } +} + +void CPathInfo::Stamp(int x, int y, int size, int depth, unsigned char *Data, int DataWidth, int DataHeight) +{ +// int xPos; +// float yPos; + int dx, dy, fx, fy; + float offset; + byte value; + byte invDepth; + + offset = (float)(CIRCLE_STAMP_SIZE-1) / size; + invDepth = 255-depth; + + for(dx = -size; dx <= size; dx++) + { + for ( dy = -size; dy <= size; dy ++ ) + { + float d; + + d = dx * dx + dy * dy ; + if ( d > size * size ) + { + continue; + } + + fx = x + dx; + if (fx < 2 || fx > DataWidth-2) + { + continue; + } + + fy = y + dy; + if (fy < 2 || fy > DataHeight-2) + { + continue; + } + + value = pow ( sin ( (float)(d / (size * size) * M_PI / 2)), mBreadth ) * invDepth + depth; + if (value < Data[(fy * DataWidth) + fx]) + { + Data[(fy * DataWidth) + fx] = value; + } + } + } +/* + + fx = x + dx; + if (fx < 2 || fx > DataWidth-2) + { + continue; + } + xPos = abs((int)(dx*offset)); + yPos = offset*size + offset; + for(dy = -size; dy < 0; dy++) + { + yPos -= offset; + fy = y + dy; + if (fy < 2 || fy > DataHeight-2) + { + continue; + } + + value = (invDepth * mCircleStamp[(int)yPos][xPos] / 256) + depth; + if (value < Data[(fy * DataWidth) + fx]) + { + Data[(fy * DataWidth) + fx] = value; + } + } + + yPos = -offset; + for(; dy <= size; dy++) + { + yPos += offset; + + fy = y + dy; + if (fy < 2 || fy > DataHeight-2) + { + continue; + } + + value = (invDepth * mCircleStamp[(int)yPos][xPos] / 256) + depth; + if (value < Data[(fy * DataWidth) + fx]) + { + Data[(fy * DataWidth) + fx] = value; + } + } + } +*/ +} + +void CPathInfo::GetInfo(float PercentInto, vec4_t Coord, vec4_t Vector) +{ + vec4_t before, after; + float testPercent; + + DialASpline(PercentInto, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, Coord); + + testPercent = PercentInto - 0.01; + if (testPercent < 0) + { + testPercent = 0; + } + DialASpline(testPercent, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, before); + + testPercent = PercentInto + 0.01; + if (testPercent > 1.0) + { + testPercent = 1.0; + } + DialASpline(testPercent, mWeights, mPoints, sizeof(vec4_t) / sizeof(vec_t), mNumPoints-1, mWork, 2, true, after); + + Coord[2] = mDepth; + + Vector[0] = after[0] - before[0]; + Vector[1] = after[1] - before[1]; +} + +void CPathInfo::DrawPath(unsigned char *Data, int DataWidth, int DataHeight ) +{ + float t; + vec4_t val, vector;//, perp; + int size; + float inc; + int x, y, lastX, lastY; + float depth; + + inc = mInc / DataWidth; + + lastX = lastY = -999; + + for (t=0.0; t<=1.0; t+=inc) + { + GetInfo(t, val, vector); + +/* perp[0] = -vector[1]; + perp[1] = vector[0]; + + if (fabs(perp[0]) > fabs(perp[1])) + { + perp[1] /= fabs(perp[0]); + perp[0] /= fabs(perp[0]); + } + else + { + perp[0] /= fabs(perp[1]); + perp[1] /= fabs(perp[1]); + } +*/ + x = val[0] * DataWidth; + y = val[1] * DataHeight; + + if (x == lastX && y == lastY) + { + continue; + } + + lastX = x; + lastY = y; + + size = val[3] * DataWidth; + + depth = mDepth * 255.0f; + + Stamp(x, y, size, (int)depth, Data, DataWidth, DataHeight); + } +} + + + + + + + +CRandomTerrain::CRandomTerrain(void) +{ + memset(mPaths, 0, sizeof(mPaths)); +} + +CRandomTerrain::~CRandomTerrain(void) +{ + Shutdown(); +} + +void CRandomTerrain::Init(CCMLandScape *landscape, byte *grid, int width, int height) +{ + Shutdown(); + mLandScape = landscape; + mWidth = width; + mHeight = height; + mArea = mWidth * mHeight; + mBorder = (width + height) >> 6; + mGrid = grid; +} + +void CRandomTerrain::ClearPaths(void) +{ + int i; + + for(i=0;i= MAX_RANDOM_PATHS || mPaths[PathID]) + { + return false; + } + + if (ConnectedID >= 0 && ConnectedID < MAX_RANDOM_PATHS) + { + connected = mPaths[ConnectedID]; + } + + mPaths[PathID] = new CPathInfo(mLandScape, numPoints, bx, by, ex, ey, + minWidth, maxWidth, depth, deviation, breadth, + connected, CreationFlags ); + + return true; +} + +bool CRandomTerrain::GetPathInfo(int PathNum, float PercentInto, vec4_t Coord, vec4_t Vector) +{ + if (PathNum < 0 || PathNum >= MAX_RANDOM_PATHS || !mPaths[PathNum]) + { + return false; + } + + mPaths[PathNum]->GetInfo(PercentInto, Coord, Vector); + + return true; +} + +void CRandomTerrain::ParseGenerate(const char *GenerateFile) +{ +} + +void CRandomTerrain::Smooth ( void ) +{ + // Scale down to 1/4 size then back up to smooth out the terrain + byte *temp; + int x, y, o; + + temp = mLandScape->GetFlattenMap ( ); + + // Copy over anything in the flatten map + if (temp) + { + for ( o = 0; o < mHeight * mWidth; o++) + { + if ( temp[o] > 0 ) + { + mGrid[o] = (byte)temp[o] & 0x7F; + } + } + } + temp = (byte *)Z_Malloc(mWidth * mHeight, TAG_TEMP_WORKSPACE, qfalse); +#if 1 + unsigned total, count; + for(x=1;x> 1, mHeight >> 1, 1); + R_Resample(temp, mWidth >> 1, mHeight >> 1, mGrid, mWidth, mHeight, 1); + + // now lets filter it. + memcpy(temp, mGrid, mWidth * mHeight); + + for (dy = -KERNEL_SIZE; dy <= KERNEL_SIZE; dy++) + { + for (dx = -KERNEL_SIZE; dx <= KERNEL_SIZE; dx++) + { + smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE] = + 1.0f / (1.0f + fabs(float(dx) * float(dx) * float(dx)) + fabs(float(dy) * float(dy) * float(dy))); + } + } + + for (y = 0; y < mHeight; y++) + { + for (x = 0; x < mWidth; x++) + { + total = 0.0f; + num = 0.0f; + for (dy = -KERNEL_SIZE; dy <= KERNEL_SIZE; dy++) + { + for (dx = -KERNEL_SIZE; dx <= KERNEL_SIZE; dx++) + { + xx = x + dx; + if (xx >= 0 && xx < mWidth) + { + yy = y + dy; + if (yy >= 0 && yy < mHeight) + { + total += smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE] * (float)temp[yy * mWidth + xx]; + num += smoothKernel[dy + KERNEL_SIZE][dx + KERNEL_SIZE]; + } + } + } + } + total /= num; + mGrid[y * mWidth + x] = (byte)Com_Clamp(0, 255, (int)Round(total)); + } + } +#endif + + Z_Free(temp); + +/* Uncomment to see the symmetry line on the map + + for ( x = 0; x < mWidth; x ++ ) + { + mGrid[x * mWidth + x] = 255; + } +*/ +} + +void CRandomTerrain::Generate(int symmetric) +{ + int i,j; + + // Clear out all existing data + memset(mGrid, 255, mArea); + + // make landscape a little bumpy + float t1 = mLandScape->flrand(0, 2); + float t2 = mLandScape->flrand(0, 2); + float t3 = mLandScape->flrand(0, 2); + + CM_NoiseInit(mLandScape); + + int x, y; + for (y = 0; y < mHeight; y++) + for (x = 0; x < mWidth; x++) + { + i = x + y*mWidth; + byte val = (byte)Com_Clamp(0, 255, (int)(220 + (CM_NoiseGet4f( x*0.25, y*0.25, 0, t3 ) * 20)) + (CM_NoiseGet4f( x*0.5, y*0.5, 0, t2 ) * 15)); + mGrid[i] = val; + } + + for ( i = 0; mPaths[i] != 0; i ++ ) + { + mPaths[i]->DrawPath(mGrid, mWidth, mHeight); + } + + for (y = 0; y < mHeight; y++) + for (x = 0; x < mWidth; x++) + { + i = x + y*mWidth; + byte val = (byte)Com_Clamp(0, 255, (int)(mGrid[i] + (CM_NoiseGet4f( x, y, 0, t1 ) * 5))); + mGrid[i] = val; + } + + // if symmetric, do this now + if (symmetric) + { + assert (mWidth == mHeight); // must be square + + for (y = 0; y < mHeight; y++) + for (x = 0; x < (mWidth-y); x++) + { + i = x + y*mWidth; + j = (mWidth-1 - x) + (mHeight-1 - y)*mWidth; + byte val = mGrid[i] < mGrid[j] ? mGrid[i] : mGrid[j]; + mGrid[i] = mGrid[j] = val; + } + } +} + + + + +typedef enum +{ + RMG_CP_NONE = -1, + RMG_CP_CONSONANT, + RMG_CP_COMPLEX_CONSONANT, + RMG_CP_VOWEL, + RMG_CP_COMPLEX_VOWEL, + RMG_CP_ENDING, + + RMG_CP_NUM_PIECES, +} ECPType; + +typedef struct SCharacterPiece +{ + char *mPiece; + int mCommonality; +} TCharacterPiece; + +static TCharacterPiece Consonants[] = +{ + { "b", 6 }, + { "c", 8 }, + { "d", 6 }, + { "f", 5 }, + { "g", 4 }, + { "h", 5 }, + { "j", 2 }, + { "k", 4 }, + { "l", 4 }, + { "m", 7 }, + { "n", 7 }, + { "r", 6 }, + { "s", 10 }, + { "t", 10 }, + { "v", 1 }, + { "w", 2 }, + { "x", 1 }, + { "z", 1 }, + + { 0, 0 } +}; + +static TCharacterPiece ComplexConsonants[] = +{ + { "st", 10 }, + { "ck", 10 }, + { "ss", 10 }, + { "tt", 7 }, + { "ll", 8 }, + { "nd", 10 }, + { "rn", 6 }, + { "nc", 6 }, + { "mp", 4 }, + { "sc", 10 }, + { "sl", 10 }, + { "tch", 6 }, + { "th", 4 }, + { "rn", 5 }, + { "cl", 10 }, + { "sp", 10 }, + { "st", 10 }, + { "fl", 4 }, + { "sh", 7 }, + { "ng", 4 }, +// { "" }, + + { 0, 0 } +}; + +static TCharacterPiece Vowels[] = +{ + { "a", 10 }, + { "e", 10 }, + { "i", 10 }, + { "o", 10 }, + { "u", 2 }, +// { "" }, + + { 0, 0 } +}; + +static TCharacterPiece ComplexVowels[] = +{ + { "ea", 10 }, + { "ue", 3 }, + { "oi", 10 }, + { "ai", 8 }, + { "oo", 10 }, + { "io", 10 }, + { "oe", 10 }, + { "au", 3 }, + { "ee", 7 }, + { "ei", 7 }, + { "ou", 7 }, + { "ia", 4 }, +// { "" }, + + { 0, 0 } +}; + +static TCharacterPiece Endings[] = +{ + { "ing", 10 }, + { "ed", 10 }, + { "ute", 10 }, + { "ance", 10 }, + { "ey", 10 }, + { "ation", 10 }, + { "ous", 10 }, + { "ent", 10 }, + { "ate", 10 }, + { "ible", 10 }, + { "age", 10 }, + { "ity", 10 }, + { "ist", 10 }, + { "ism", 10 }, + { "ime", 10 }, + { "ic", 10 }, + { "ant", 10 }, + { "etry", 10 }, + { "ious", 10 }, + { "ative", 10 }, + { "er", 10 }, + { "ize", 10 }, + { "able", 10 }, + { "itude", 10 }, +// { "" }, + + { 0, 0 } +}; + +static void FindPiece(ECPType type, char *&pos) +{ + TCharacterPiece *search, *start; + int count = 0; + + switch(type) + { + case RMG_CP_CONSONANT: + default: + start = Consonants; + break; + + case RMG_CP_COMPLEX_CONSONANT: + start = ComplexConsonants; + break; + + case RMG_CP_VOWEL: + start = Vowels; + break; + + case RMG_CP_COMPLEX_VOWEL: + start = ComplexVowels; + break; + + case RMG_CP_ENDING: + start = Endings; + break; + } + + search = start; + while(search->mPiece) + { + count += search->mCommonality; + search++; + } + + count = Q_irand(0, count-1); + search = start; + while(count > search->mCommonality) + { + count -= search->mCommonality; + search++; + } + + strcpy(pos, search->mPiece); + pos += strlen(search->mPiece); +} + +unsigned RMG_CreateSeed(char *TextSeed) +{ + int Length; + char Ending[256], *pos; + int ComplexVowelChance, ComplexConsonantChance; + ECPType LookingFor; + unsigned SeedValue = 0, high; + + Length = Q_irand(4, 9); + + if (Q_irand(0, 100) < 20) + { + LookingFor = RMG_CP_VOWEL; + } + else + { + LookingFor = RMG_CP_CONSONANT; + } + + Ending[0] = 0; + + if (Q_irand(0, 100) < 55) + { + pos = Ending; + FindPiece(RMG_CP_ENDING, pos); + Length -= (pos - Ending); + } + + pos = TextSeed; + *pos = 0; + + ComplexVowelChance = -1; + ComplexConsonantChance = -1; + + while((pos - TextSeed) < Length || LookingFor == RMG_CP_CONSONANT) + { + if (LookingFor == RMG_CP_VOWEL) + { + if (Q_irand(0, 100) < ComplexVowelChance) + { + ComplexVowelChance = -1; + LookingFor = RMG_CP_COMPLEX_VOWEL; + } + else + { + ComplexVowelChance += 10; + } + + FindPiece(LookingFor, pos); + LookingFor = RMG_CP_CONSONANT; + } + else + { + if (Q_irand(0, 100) < ComplexConsonantChance) + { + ComplexConsonantChance = -1; + LookingFor = RMG_CP_COMPLEX_CONSONANT; + } + else + { + ComplexConsonantChance += 45; + } + + FindPiece(LookingFor, pos); + LookingFor = RMG_CP_VOWEL; + } + } + + if (Ending[0]) + { + strcpy(pos, Ending); + } + + pos = TextSeed; + while(*pos) + { + high = SeedValue >> 28; + SeedValue ^= (SeedValue << 4) + ((*pos)-'a'); + SeedValue ^= high; + pos++; + } + + return SeedValue; +} diff --git a/code/qcommon/cm_randomterrain.h b/code/qcommon/cm_randomterrain.h new file mode 100644 index 0000000..f50a9db --- /dev/null +++ b/code/qcommon/cm_randomterrain.h @@ -0,0 +1,89 @@ +#pragma once +#if !defined(CM_RANDOMTERRAIN_H_INC) +#define CM_RANDOMTERRAIN_H_INC + +#ifdef DEBUG_LINKING + #pragma message("...including cm_randomterrain.h") +#endif + +//class CPathInfo; + +#define SPLINE_MERGE_SIZE 3 +#define CIRCLE_STAMP_SIZE 128 + + +class CPathInfo +{ +private: + vec4_t *mPoints, *mWork; + vec_t *mWeights; + int mNumPoints; + float mMinWidth, mMaxWidth; + float mInc; + float mDepth, mBreadth; + float mDeviation; + byte mCircleStamp[CIRCLE_STAMP_SIZE][CIRCLE_STAMP_SIZE]; + + void CreateCircle(void); + void Stamp(int x, int y, int size, int depth, unsigned char *Data, int DataWidth, int DataHeight); + +public: + CPathInfo(CCMLandScape *landscape, int numPoints, float bx, float by, float ex, float ey, + float minWidth, float maxWidth, float depth, float deviation, float breadth, + CPathInfo *Connected, unsigned CreationFlags); + ~CPathInfo(void); + + int GetNumPoints(void) { return mNumPoints; } + float *GetPoint(int index) { return mPoints[index]; } + float GetWidth(int index) { return mPoints[index][3]; } + + void GetInfo(float PercentInto, vec4_t Coord, vec4_t Vector); + void DrawPath(unsigned char *Data, int DataWidth, int DataHeight ); +}; + + +const int MAX_RANDOM_PATHS = 30; + +// Path Creation Flags +#define PATH_CREATION_CONNECT_FRONT 0x00000001 + + + +class CRandomTerrain +{ +private: + + class CCMLandScape *mLandScape; + int mWidth; + int mHeight; + int mArea; + int mBorder; + byte *mGrid; + CPathInfo *mPaths[MAX_RANDOM_PATHS]; + +public: + CRandomTerrain(void); + ~CRandomTerrain(void); + + CCMLandScape *GetLandScape(void) { return mLandScape; } + const vec3pair_t &GetBounds(void) const { return mLandScape->GetBounds(); } + void rand_seed(int seed) { mLandScape->rand_seed(seed); } + int get_rand_seed(void) { return mLandScape->get_rand_seed();} + float flrand(float min, float max) { return mLandScape->flrand(min, max); } + int irand(int min, int max) { return mLandScape->irand(min, max); } + + void Init(class CCMLandScape *landscape, byte *data, int width, int height); + void Shutdown(void); + bool CreatePath(int PathID, int ConnectedID, unsigned CreationFlags, int numPoints, + float bx, float by, float ex, float ey, + float minWidth, float maxWidth, float depth, float deviation, float breadth ); + bool GetPathInfo(int PathNum, float PercentInto, vec2_t Coord, vec2_t Vector); + void ParseGenerate(const char *GenerateFile); + void Smooth ( void ); + void Generate(int symmetric); + void ClearPaths(void); +}; + +unsigned RMG_CreateSeed(char *TextSeed); + +#endif // CM_RANDOM_H_INC diff --git a/code/qcommon/cm_shader.cpp b/code/qcommon/cm_shader.cpp new file mode 100644 index 0000000..65fab16 --- /dev/null +++ b/code/qcommon/cm_shader.cpp @@ -0,0 +1,529 @@ +#include "../server/exe_headers.h" + +#include "../game/q_shared.h" + +#include "cm_local.h" +#include "memory.h" +#include "chash.h" + +class CCMShaderText +{ +private: + char mName[MAX_QPATH]; + class CCMShaderText *mNext; + const char *mData; +public: + // Constructors + CCMShaderText(const char *name, const char *data) { Q_strncpyz(mName, name, MAX_QPATH); mNext = NULL; mData = data; } + ~CCMShaderText(void) {} + + // Accessors + const char *GetName(void) const { return(mName); } + class CCMShaderText *GetNext(void) const { return(mNext); } + void SetNext(class CCMShaderText *next) { mNext = next; } + void Destroy(void) { delete this; } + + const char *GetData(void) const { return(mData); } +}; + +char *shaderText = NULL; +CHash shaderTextTable; +CHash cmShaderTable; + +const char *SkipWhitespace( const char *data, qboolean *hasNewLines ); + +//rwwFIXMEFIXME: Called at RE_BeginRegistration because Hunk_Clear +//destroys the memory cmShaderTable is on. This is a temp solution +//I guess. +void ShaderTableCleanup() +{ + cmShaderTable.clear(); +} + +/* +==================== +CM_CreateShaderTextHash +===================== +*/ +void CM_CreateShaderTextHash(void) +{ + const char *p; + qboolean hasNewLines; + char *token; + CCMShaderText *shader; + + p = shaderText; + // look for label + while (p) + { + p = SkipWhitespace(p, &hasNewLines); + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + break; + } + shader = new CCMShaderText(token, p); + shaderTextTable.insert(shader); + + SkipBracedSection(&p); + } +} + +/* +==================== +CM_LoadShaderFiles + +Finds and loads all .shader files, combining them into +a single large text block that can be scanned for shader names +===================== +*/ +#define MAX_SHADER_FILES 1024 + +void CM_LoadShaderFiles( void ) +{ + char **shaderFiles1; + int numShaders1; + char *buffers[MAX_SHADER_FILES]; + int numShaders; + int i; + int sum = 0; + + // scan for shader files + shaderFiles1 = FS_ListFiles( "shaders", ".shader", &numShaders1 ); + + if ( !shaderFiles1 || !numShaders1 ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: no shader files found\n" ); + return; + } + + numShaders = numShaders1; + if ( numShaders > MAX_SHADER_FILES ) + { + numShaders = MAX_SHADER_FILES; + } + + // load and parse shader files + for ( i = 0; i < numShaders1; i++ ) + { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "shaders/%s", shaderFiles1[i] ); + Com_DPrintf( "...loading '%s'\n", filename ); + FS_ReadFile( filename, (void **)&buffers[i] ); + if ( !buffers[i] ) + { + Com_Error( ERR_DROP, "Couldn't load %s", filename ); + } + sum += COM_Compress( buffers[i] ); + } + + // build single large buffer + shaderText = (char *)Z_Malloc( sum + numShaders * 2, TAG_SHADERTEXT, qtrue); + + // free in reverse order, so the temp files are all dumped + for ( i = numShaders - 1; i >= 0 ; i-- ) + { + strcat( shaderText, "\n" ); + strcat( shaderText, buffers[i] ); + FS_FreeFile( buffers[i] ); + } + + // free up memory + FS_FreeFileList( shaderFiles1 ); +} + +/* +================== +CM_GetShaderText +================== +*/ + +const char *CM_GetShaderText(const char *key) +{ + CCMShaderText *st; + + st = shaderTextTable[key]; + if(st) + { + return(st->GetData()); + } + return(NULL); +} + +/* +================== +CM_FreeShaderText +================== +*/ + +void CM_FreeShaderText(void) +{ + shaderTextTable.clear(); + if(shaderText) + { + Z_Free(shaderText); + shaderText = NULL; + } +} + +/* +================== +CM_LoadShaderText + + Loads in all the .shader files so it can be accessed by the server and the renderer + Creates a hash table to quickly access the shader text +================== +*/ + +void CM_LoadShaderText(bool forceReload) +{ + if(forceReload) + { + CM_FreeShaderText(); + } + if(shaderText) + { + return; + } + Com_Printf("Loading shader text .....\n"); + CM_LoadShaderFiles(); + CM_CreateShaderTextHash(); + + Com_Printf("..... %d shader definitions loaded\n", shaderTextTable.count()); +} + +/* +=============== +ParseSurfaceParm + +surfaceparm +=============== +*/ + +typedef struct +{ + char *name; + int clearSolid, surfaceFlags, contents; +} infoParm_t; + +infoParm_t svInfoParms[] = +{ + // Game content Flags + {"nonsolid", ~CONTENTS_SOLID, 0, 0 }, // special hack to clear solid flag + {"nonopaque", ~CONTENTS_OPAQUE, 0, 0 }, // special hack to clear opaque flag + {"lava", ~CONTENTS_SOLID, 0, CONTENTS_LAVA }, // very damaging + {"slime", ~CONTENTS_SOLID, 0, CONTENTS_SLIME }, // mildly damaging + {"water", ~CONTENTS_SOLID, 0, CONTENTS_WATER }, + {"fog", ~CONTENTS_SOLID, 0, CONTENTS_FOG}, // carves surfaces entering + {"shotclip", ~CONTENTS_SOLID, 0, CONTENTS_SHOTCLIP }, /* block shots, but not people */ + {"playerclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_PLAYERCLIP }, /* block only the player */ + {"monsterclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_MONSTERCLIP }, + {"botclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_BOTCLIP }, /* NPC do not enter */ + {"trigger", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_TRIGGER }, + {"nodrop", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc) + {"terrain", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_TERRAIN }, /* use special terrain collsion */ + {"ladder", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_LADDER }, // climb up in it like water + {"abseil", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_ABSEIL }, // can abseil down this brush + {"outside", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_OUTSIDE }, // volume is considered to be in the outside (i.e. not indoors) + {"inside", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_INSIDE }, // volume is considered to be inside (i.e. indoors) + + {"detail", -1, 0, CONTENTS_DETAIL }, // don't include in structural bsp + {"trans", -1, 0, CONTENTS_TRANSLUCENT }, // surface has an alpha component + + /* Game surface flags */ + {"sky", -1, SURF_SKY, 0 }, /* emit light from an environment map */ + {"slick", -1, SURF_SLICK, 0 }, + + {"nodamage", -1, SURF_NODAMAGE, 0 }, + {"noimpact", -1, SURF_NOIMPACT, 0 }, /* don't make impact explosions or marks */ + {"nomarks", -1, SURF_NOMARKS, 0 }, /* don't make impact marks, but still explode */ + {"nodraw", -1, SURF_NODRAW, 0 }, /* don't generate a drawsurface (or a lightmap) */ + {"nosteps", -1, SURF_NOSTEPS, 0 }, + {"nodlight", -1, SURF_NODLIGHT, 0 }, /* don't ever add dynamic lights */ + {"metalsteps", -1, SURF_METALSTEPS,0 }, + {"nomiscents", -1, SURF_NOMISCENTS,0 }, /* No misc ents on this surface */ + {"forcefield", -1, SURF_FORCEFIELD,0 }, + {"forcesight", -1, SURF_FORCESIGHT,0 }, // only visible with force sight +}; + +void SV_ParseSurfaceParm( CCMShader * shader, const char **text ) +{ + char *token; + int numsvInfoParms = sizeof(svInfoParms) / sizeof(svInfoParms[0]); + int i; + + token = COM_ParseExt( text, qfalse ); + for ( i = 0 ; i < numsvInfoParms ; i++ ) + { + if ( !Q_stricmp( token, svInfoParms[i].name ) ) + { + shader->surfaceFlags |= svInfoParms[i].surfaceFlags; + shader->contentFlags |= svInfoParms[i].contents; + shader->contentFlags &= svInfoParms[i].clearSolid; + break; + } + } +} + +/* +================= +ParseMaterial +================= +*/ +const char *svMaterialNames[MATERIAL_LAST] = +{ + MATERIALS +}; + +void SV_ParseMaterial( CCMShader *shader, const char **text ) +{ + char *token; + int i; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: missing material in shader '%s'\n", shader->shader ); + return; + } + for(i = 0; i < MATERIAL_LAST; i++) + { + if ( !Q_stricmp( token, svMaterialNames[i] ) ) + { + shader->surfaceFlags &= ~MATERIAL_MASK;//safety, clear it first + shader->surfaceFlags |= i; + break; + } + } +} + +/* +=============== +ParseVector +=============== +*/ +qboolean CM_ParseVector( CCMShader *shader, const char **text, int count, float *v ) +{ + char *token; + int i; + + // FIXME: spaces are currently required after parens, should change parseext... + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "(" ) ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: missing parenthesis in shader '%s'\n", shader->shader ); + return qfalse; + } + + for ( i = 0 ; i < count ; i++ ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: missing vector element in shader '%s'\n", shader->shader ); + return qfalse; + } + v[i] = atof( token ); + } + + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, ")" ) ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: missing parenthesis in shader '%s'\n", shader->shader ); + return qfalse; + } + return qtrue; +} + +/* +================= +CM_ParseShader + +The current text pointer is at the explicit text definition of the +shader. Parse it into the global shader variable. + +This extracts all the info from the shader required for physics and collision +It is designed to *NOT* load any image files and not require any of the renderer to +be initialised. +================= +*/ +void CM_ParseShader( CCMShader *shader, const char **text ) +{ + char *token; + + token = COM_ParseExt( text, qtrue ); + if ( token[0] != '{' ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader->shader ); + return; + } + + while ( true ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: no concluding '}' in shader %s\n", shader->shader ); + return; + } + + // end of shader definition + if ( token[0] == '}' ) + { + break; + } + // stage definition + else if ( token[0] == '{' ) + { + SkipBracedSection( text ); + continue; + } + // material deprecated as of 11 Jan 01 + // material undeprecated as of 7 May 01 - q3map_material deprecated + else if ( !Q_stricmp( token, "material" ) || !Q_stricmp( token, "q3map_material" ) ) + { + SV_ParseMaterial( shader, text ); + } + // sun parms + // q3map_sun deprecated as of 11 Jan 01 + else if ( !Q_stricmp( token, "sun" ) || !Q_stricmp( token, "q3map_sun" ) ) + { +// float a, b; + + token = COM_ParseExt( text, qfalse ); +// shader->sunLight[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); +// shader->sunLight[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); +// shader->sunLight[2] = atof( token ); + +// VectorNormalize( shader->sunLight ); + + token = COM_ParseExt( text, qfalse ); +// a = atof( token ); +// VectorScale( shader->sunLight, a, shader->sunLight); + + token = COM_ParseExt( text, qfalse ); +// a = DEG2RAD(atof( token )); + + token = COM_ParseExt( text, qfalse ); +// b = DEG2RAD(atof( token )); + +// shader->sunDirection[0] = cos( a ) * cos( b ); +// shader->sunDirection[1] = sin( a ) * cos( b ); +// shader->sunDirection[2] = sin( b ); + } + else if ( !Q_stricmp( token, "surfaceParm" ) ) + { + SV_ParseSurfaceParm( shader, text ); + continue; + } + else if ( !Q_stricmp( token, "fogParms" ) ) + { + vec3_t fogColor; + if ( !CM_ParseVector( shader, text, 3, fogColor ) ) + { + return; + } + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader->shader ); + continue; + } +// shader->depthForOpaque = atof( token ); + + // skip any old gradient directions + SkipRestOfLine( (const char **)text ); + continue; + } + } + return; +} + +/* +================= +CM_SetupShaderProperties + + Scans thru the shaders loaded for the map, parses the text of that shader and + extracts the interesting info *WITHOUT* loading up any images or requiring + the renderer to be active. +================= +*/ + +void CM_SetupShaderProperties(void) +{ + int i; + const char *def; + CCMShader *shader; + + // Add all basic shaders to the cmShaderTable + for(i = 0; i < cmg.numShaders; i++) + { + cmShaderTable.insert(CM_GetShaderInfo(i)); + } + // Go through and parse evaluate shader names to shadernums + for(i = 0; i < cmg.numShaders; i++) + { + shader = CM_GetShaderInfo(i); + def = CM_GetShaderText(shader->shader); + if(def) + { + CM_ParseShader(shader, &def); + } + } +} + +void CM_ShutdownShaderProperties(void) +{ + if(cmShaderTable.count()) + { + Com_Printf("Shutting down cmShaderTable .....\n"); + cmShaderTable.clear(); + } +} + +CCMShader *CM_GetShaderInfo( const char *name ) +{ + CCMShader *out; + const char *def; + + out = cmShaderTable[name]; + if(out) + { + return(out); + } + + // Create a new CCMShader class + //out = (CCMShader *)Hunk_Alloc( sizeof( CCMShader ), h_high ); + out = (CCMShader *)Hunk_Alloc( sizeof( CCMShader ), qtrue ); + // Set defaults + Q_strncpyz(out->shader, name, MAX_QPATH); + out->contentFlags = CONTENTS_SOLID | CONTENTS_OPAQUE; + + // Parse in any text if it exists + def = CM_GetShaderText(name); + if(def) + { + CM_ParseShader(out, &def); + } + + cmShaderTable.insert(out); + return(out); +} + +CCMShader *CM_GetShaderInfo( int shaderNum ) +{ + CCMShader *out; + + if((shaderNum < 0) || (shaderNum >= cmg.numShaders)) + { + return(NULL); + } + out = cmg.shaders + shaderNum; + return(out); +} + +// end diff --git a/code/qcommon/cm_terrain.cpp b/code/qcommon/cm_terrain.cpp new file mode 100644 index 0000000..d8ce9c2 --- /dev/null +++ b/code/qcommon/cm_terrain.cpp @@ -0,0 +1,1714 @@ +#include "../server/exe_headers.h" + +#include "cm_local.h" +#include "cm_patch.h" +#include "cm_landscape.h" +#include "../game/genericparser2.h" +#include "cm_randomterrain.h" + +#ifdef _WIN32 +#pragma optimize("p", on) +#endif + +void R_LoadDataImage ( const char *name, byte **pic, int *width, int *height); +void R_InvertImage ( byte *data, int width, int height, int depth); +void R_Resample ( byte *source, int swidth, int sheight, byte *dest, int dwidth, int dheight, int components); + +//#define _SMOOTH_TERXEL_BRUSH + +#ifdef _SMOOTH_TERXEL_BRUSH +#define BRUSH_SIDES_PER_TERXEL 8 +#else +#define BRUSH_SIDES_PER_TERXEL 5 +#endif + +void CCMLandScape::SetShaders(int height, CCMShader *shader) +{ + int i; + + for(i = height; shader && (i < HEIGHT_RESOLUTION); i++) + { + if(!mHeightDetails[i].GetSurfaceFlags()) + { + mHeightDetails[i].SetFlags(shader->contentFlags, shader->surfaceFlags); + } + } +} + +void CCMLandScape::LoadTerrainDef(const char *td) +{ + char terrainDef[MAX_QPATH]; + CGenericParser2 parse; + CGPGroup *basegroup, *classes, *items; + + Com_sprintf(terrainDef, MAX_QPATH, "ext_data/RMG/%s.terrain", Info_ValueForKey(td, "terrainDef")); + Com_DPrintf("CM_Terrain: Loading and parsing terrainDef %s.....\n", Info_ValueForKey(td, "terrainDef")); + + if(!Com_ParseTextFile(terrainDef, parse)) + { + Com_sprintf(terrainDef, MAX_QPATH, "ext_data/arioche/%s.terrain", Info_ValueForKey(td, "terrainDef")); + if(!Com_ParseTextFile(terrainDef, parse)) + { + Com_Printf("Could not open %s\n", terrainDef); + return; + } + } + // The whole file.... + basegroup = parse.GetBaseParseGroup(); + + // The root { } struct + classes = basegroup->GetSubGroups(); + while(classes) + { + items = classes->GetSubGroups(); + while(items) + { + if(!stricmp(items->GetName(), "altitudetexture")) + { + int height; + const char *shaderName; + CCMShader *shader; + + // Height must exist - the rest are optional + height = atol(items->FindPairValue("height", "0")); + + // Shader for this height + shaderName = items->FindPairValue("shader", ""); + if(strlen(shaderName)) + { + shader = CM_GetShaderInfo(shaderName); + if(shader) + { + SetShaders(height, shader); + } + } + } + else if(!stricmp(items->GetName(), "water")) + { + const char *shaderName; + CCMShader *shader; + + // Grab the height of the water + mBaseWaterHeight = atol(items->FindPairValue("height", "0")); + SetRealWaterHeight(mBaseWaterHeight); + + // Grab the material of the water + shaderName = items->FindPairValue("shader", ""); + shader = CM_GetShaderInfo(shaderName); + if(shader) + { + mWaterContents = shader->contentFlags; + mWaterSurfaceFlags = shader->surfaceFlags; + } + } + items = (CGPGroup *)items->GetNext(); + } + classes = (CGPGroup *)classes->GetNext(); + } + Com_ParseTextFileDestroy(parse); +} + +CCMPatch::~CCMPatch(void) +{ +} + +CCMLandScape::CCMLandScape(const char *configstring, bool server) +{ + int numPatches, numBrushesPerPatch, size;// seed; + char heightMap[MAX_QPATH]; +// char *ptr; + + holdrand = 0x89abcdef; + + // Clear out the height details + memset(mHeightDetails, 0, sizeof(CCMHeightDetails) * HEIGHT_RESOLUTION); + mBaseWaterHeight = 0; + mWaterHeight = 0.0f; + + // When constructed, referenced once + mRefCount = 1; + + // Extract the relevant data from the config string + Com_sprintf(heightMap, MAX_QPATH, "%s", Info_ValueForKey(configstring, "heightMap")); + numPatches = atol(Info_ValueForKey(configstring, "numPatches")); + mTerxels = atol(Info_ValueForKey(configstring, "terxels")); + mHasPhysics = !!atol(Info_ValueForKey(configstring, "physics")); + //seed = strtoul(Info_ValueForKey(configstring, "seed"), &ptr, 10); + + mBounds[0][0] = (float)atof(Info_ValueForKey(configstring, "minx")); + mBounds[0][1] = (float)atof(Info_ValueForKey(configstring, "miny")); + mBounds[0][2] = (float)atof(Info_ValueForKey(configstring, "minz")); + mBounds[1][0] = (float)atof(Info_ValueForKey(configstring, "maxx")); + mBounds[1][1] = (float)atof(Info_ValueForKey(configstring, "maxy")); + mBounds[1][2] = (float)atof(Info_ValueForKey(configstring, "maxz")); + + // Calculate size of the brush + VectorSubtract(mBounds[1], mBounds[0], mSize); + + // Work out the dimensions of the brush in blocks - the object is to make the blocks as square as possible + mBlockWidth = Round(sqrtf(numPatches * mSize[0] / mSize[1])); + mBlockHeight = Round(sqrtf(numPatches * mSize[1] / mSize[0])); + + // ...which lets us get the size of the heightmap + mWidth = mBlockWidth * mTerxels; + mHeight = mBlockHeight * mTerxels; + + mHeightMap = (byte *)Z_Malloc(GetRealArea(), TAG_CM_TERRAIN, qfalse); + mFlattenMap = 0; //only needed on random terrains + + if(strlen(heightMap)) + { + byte *imageData; + int iWidth, iHeight; + + Com_DPrintf("CM_Terrain: Loading heightmap %s.....\n", heightMap); + R_LoadDataImage(heightMap, &imageData, &iWidth, &iHeight); + + mRandomTerrain = 0; + + if(imageData) + { + if(strstr(heightMap, "random_")) + { + mFlattenMap = (byte *)Z_Malloc(GetRealArea(), TAG_CM_TERRAIN, qfalse); + memset ( mFlattenMap, 0, GetRealArea() );// Zero means unused. + mRandomTerrain = CreateRandomTerrain ( configstring, this, mHeightMap, GetRealWidth(), GetRealHeight()); + } + else + { + // Flip to make the same as GenSurf + R_InvertImage(imageData, iWidth, iHeight, 1); + R_Resample(imageData, iWidth, iHeight, mHeightMap, GetRealWidth(), GetRealHeight(), 1); + } + Z_Free(imageData); + } + } + else + { + Com_Error(ERR_FATAL, "Terrain has no heightmap specified\n"); + } + + // Work out the dimensions of the terxel - should be almost square + mTerxelSize[0] = mSize[0] / mWidth; + mTerxelSize[1] = mSize[1] / mHeight; + mTerxelSize[2] = mSize[2] / 255.0f; + + // Work out the patchsize + mPatchSize[0] = mSize[0] / mBlockWidth; + mPatchSize[1] = mSize[1] / mBlockHeight; + mPatchSize[2] = 1.0f; + mPatchScalarSize = VectorLength(mPatchSize); + + // Loads in the water height and properties + // Gets the shader properties for the blended shaders + LoadTerrainDef(configstring); + + Com_DPrintf("CM_Terrain: Creating patches.....\n"); + mPatches = (CCMPatch *)Z_Malloc(sizeof(CCMPatch) * GetBlockCount(), TAG_CM_TERRAIN, qfalse); + + numBrushesPerPatch = mTerxels * mTerxels * 2; + size = (numBrushesPerPatch * sizeof(cbrush_t)) + (numBrushesPerPatch * BRUSH_SIDES_PER_TERXEL * 2 * (sizeof(cbrushside_t) + sizeof(cplane_t))); + mPatchBrushData = (byte *)Z_Malloc(size * GetBlockCount(), TAG_CM_TERRAIN, qfalse); + + // Initialize all terrain patches + UpdatePatches(); +} + +// Initialise a plane from 3 coords + +void CCMPatch::InitPlane(struct cbrushside_s *side, cplane_t *plane, vec3_t p0, vec3_t p1, vec3_t p2) +{ + vec3_t dx, dy; + + VectorSubtract(p1, p0, dx); + VectorSubtract(p2, p0, dy); + CrossProduct(dx, dy, plane->normal); + VectorNormalize(plane->normal); + + plane->dist = DotProduct(p0, plane->normal); + plane->type = PlaneTypeForNormal(plane->normal); + SetPlaneSignbits(plane); + +#ifdef _XBOX + // MATT! - does this work? + cmg.planes[side->planeNum.GetValue()] = *plane; +#else + side->plane = plane; +#endif +} + +// Create the planes required for collision detection +// 2 brushes per terxel - each brush has 5 sides and 5 planes + +void* CCMPatch::GetAdjacentBrushY ( int x, int y ) +{ + int yo1 = y % owner->GetTerxels(); + int yo2 = (y-1) % owner->GetTerxels(); + int xo = x % owner->GetTerxels(); + CCMPatch* patch; + + // Different patch + if ( yo2 > yo1 ) + { + patch = owner->GetPatch ( x / owner->GetTerxels(), (y-1) / owner->GetTerxels() ); + } + else + { + patch = this; + } + + cbrush_t *brush; + + brush = patch->mPatchBrushData; + brush += ((yo2 * owner->GetTerxels ( ) + xo) * 2); + brush ++; + + return brush; +} + +void* CCMPatch::GetAdjacentBrushX ( int x, int y ) +{ + int xo1 = x % owner->GetTerxels(); + int xo2 = (x-1) % owner->GetTerxels(); + int yo = y % owner->GetTerxels(); + CCMPatch* patch; + + // Different patch + if ( xo2 > xo1 ) + { + patch = owner->GetPatch ( (x-1) / owner->GetTerxels(), y / owner->GetTerxels() ); + } + else + { + patch = this; + } + + cbrush_t *brush; + + brush = patch->mPatchBrushData; + brush += ((yo * owner->GetTerxels ( ) + xo2) * 2); + + if ( ! ((x+y) & 1) ) + { + brush ++; + } + + return brush; +} + +void CCMPatch::CreatePatchPlaneData(void) +{ +#ifndef PRE_RELEASE_DEMO + int realWidth; + int x, y, i, j; +#if 0 + int n; +#endif + cbrush_t *brush; + cbrushside_t *side; + cplane_t *plane; + vec3_t *coords; + vec3_t localCoords[8]; + + mNumBrushes = owner->GetTerxels() * owner->GetTerxels() * 2; + realWidth = owner->GetRealWidth(); + coords = owner->GetCoords(); + + brush = mPatchBrushData; + side = (cbrushside_t *)(mPatchBrushData + mNumBrushes); + plane = (cplane_t *)(side + (mNumBrushes * BRUSH_SIDES_PER_TERXEL * 2)); + for(y = mHy; y < mHy + owner->GetTerxels(); y++) + { + for(x = mHx; x < mHx + owner->GetTerxels(); x++) + { + int offsets[4]; + + if ( (x+y)&1 ) + { + offsets[0] = (y * realWidth) + x; // TL + offsets[1] = (y * realWidth) + x + 1; // TR + offsets[2] = ((y + 1) * realWidth) + x; // BL + offsets[3] = ((y + 1) * realWidth) + x + 1; // BR + } + else + { + offsets[2] = (y * realWidth) + x; // TL + offsets[0] = (y * realWidth) + x + 1; // TR + offsets[3] = ((y + 1) * realWidth) + x; // BL + offsets[1] = ((y + 1) * realWidth) + x + 1; // BR + } + + for(i = 0; i < 4; i++) + { + VectorCopy(coords[offsets[i]], localCoords[i]); + VectorCopy(coords[offsets[i]], localCoords[i + 4]); + // Set z of base of brush to bottom of landscape brush + localCoords[i + 4][2] = owner->GetMins()[2]; + } + + // Set the bounds of the terxel + VectorSet(brush[0].bounds[0], MAX_WORLD_COORD, MAX_WORLD_COORD, MAX_WORLD_COORD); + VectorSet(brush[0].bounds[1], MIN_WORLD_COORD, MIN_WORLD_COORD, MIN_WORLD_COORD); + + for(i = 0; i < 8; i++) + { + for(j = 0; j < 3; j++) + { + // mins + if(localCoords[i][j] < brush[0].bounds[0][j]) + { + brush[0].bounds[0][j] = localCoords[i][j]; + } + // maxs + if(localCoords[i][j] > brush[0].bounds[1][j]) + { + brush[0].bounds[1][j] = localCoords[i][j]; + } + } + } + VectorDec(brush[0].bounds[0]); + VectorInc(brush[0].bounds[1]); + VectorCopy(brush[0].bounds[0], brush[1].bounds[0]); + VectorCopy(brush[0].bounds[1], brush[1].bounds[1]); + + brush[0].contents = mContentFlags; + brush[1].contents = mContentFlags; + +#ifndef _SMOOTH_TERXEL_BRUSH + + // Set up sides of the brushes + brush[0].numsides = 5; + brush[0].sides = side; + brush[1].numsides = 5; + brush[1].sides = side + 5; + + for ( i = 0; i < 8 ; i ++ ) + { + localCoords[i][0] = (int)localCoords[i][0]; + localCoords[i][1] = (int)localCoords[i][1]; + localCoords[i][2] = (int)localCoords[i][2]; + } + + // Create the planes of the 2 triangles that make up the tops of the brushes + InitPlane(side + 0, plane + 0, localCoords[0], localCoords[1], localCoords[2]); + InitPlane(side + 5, plane + 5, localCoords[3], localCoords[2], localCoords[1]); + + // Create the bottom face of the brushes + InitPlane(side + 1, plane + 1, localCoords[6], localCoords[5], localCoords[4]); + InitPlane(side + 6, plane + 6, localCoords[5], localCoords[6], localCoords[7]); + + // Create the 3 vertical faces + InitPlane(side + 2, plane + 2, localCoords[0], localCoords[2], localCoords[4]); + InitPlane(side + 7, plane + 7, localCoords[3], localCoords[1], localCoords[7]); + + InitPlane(side + 3, plane + 3, localCoords[0], localCoords[4], localCoords[1]); + InitPlane(side + 8, plane + 8, localCoords[3], localCoords[7], localCoords[2]); + + InitPlane(side + 4, plane + 4, localCoords[2], localCoords[1], localCoords[6]); + InitPlane(side + 9, plane + 9, localCoords[5], localCoords[1], localCoords[6]); + + // Increment to next terxel + brush += 2; + side += 10; + plane += 10; + + + +#else + + // Set up sides of the brushes + brush[0].numsides = 5; + brush[0].sides = side; + brush[1].numsides = 5; + brush[1].sides = side + 8; + + // Create the planes of the 2 triangles that make up the tops of the brushes + InitPlane(side + 0, plane + 0, localCoords[0], localCoords[1], localCoords[2]); + InitPlane(side + 8, plane + 8, localCoords[3], localCoords[2], localCoords[1]); + + // Create the bottom face of the brushes + InitPlane(side + 1, plane + 1, localCoords[4], localCoords[6], localCoords[5]); + InitPlane(side + 9, plane + 9, localCoords[7], localCoords[5], localCoords[6]); + + // Create the 3 vertical faces + InitPlane(side + 2, plane + 2, localCoords[0], localCoords[2], localCoords[4]); + InitPlane(side + 10, plane + 10, localCoords[3], localCoords[1], localCoords[7]); + + InitPlane(side + 3, plane + 3, localCoords[0], localCoords[4], localCoords[1]); + InitPlane(side + 11, plane + 11, localCoords[3], localCoords[7], localCoords[2]); + + InitPlane(side + 4, plane + 4, localCoords[2], localCoords[1], localCoords[6]); + InitPlane(side + 12, plane + 12, localCoords[5], localCoords[1], localCoords[6]); + + float V = DotProduct ( (plane + 8)->normal, localCoords[0] ) - (plane + 8)->dist; + + if ( V < 0 ) + { + InitPlane ( brush[0].sides + brush[0].numsides, plane + brush[0].numsides, localCoords[3], localCoords[2], localCoords[1]); + brush[0].numsides++; + + InitPlane ( brush[1].sides + brush[1].numsides, plane + 8 + brush[1].numsides, localCoords[0], localCoords[1], localCoords[2]); + brush[1].numsides++; + } + + // Determine if we need to smooth the brush transition from the brush above us + if ( y > 0 && y < owner->GetPatchHeight ( ) - 1 ) + { + cbrush_t* abovebrush = (cbrush_t*)GetAdjacentBrushY ( x, y ); +#ifdef _XBOX + cplane_t* aboveplane = &cmg.planes[abovebrush->sides->planeNum.GetValue()]; +#else + cplane_t* aboveplane = abovebrush->sides->plane; +#endif + + V = DotProduct ( aboveplane->normal, ((y+x)&1)?(localCoords[2]):(localCoords[1]) ) - aboveplane->dist; + + if ( V < 0 ) + { + memcpy ( brush[0].sides + brush[0].numsides, abovebrush->sides, sizeof(cbrushside_t) ); + brush[0].numsides++; + + memcpy ( abovebrush->sides + abovebrush->numsides, side + 0, sizeof(cbrushside_t) ); + abovebrush->numsides++; + } + } + + // Determine if we need to smooth the brush transition from the brush to the left of us + if ( x > 0 && x < owner->GetPatchWidth ( ) - 1 ) + { + cbrush_t* abovebrush = (cbrush_t*)GetAdjacentBrushX ( x, y ); +#ifdef _XBOX + cplane_t* aboveplane = &cmg.planes[abovebrush->sides->planeNum.GetValue()]; +#else + cplane_t* aboveplane = abovebrush->sides->plane; +#endif + + V = DotProduct ( aboveplane->normal, localCoords[1] ) - aboveplane->dist; + + if ( V < 0 ) + { + if ( (x+y)&1 ) + { + memcpy ( brush[0].sides + brush[0].numsides, abovebrush->sides, sizeof(cbrushside_t) ); + brush[0].numsides++; + + memcpy ( abovebrush->sides + abovebrush->numsides, side + 0, sizeof(cbrushside_t) ); + abovebrush->numsides++; + } + else + { + memcpy ( brush[1].sides + brush[1].numsides, abovebrush->sides, sizeof(cbrushside_t) ); + brush[1].numsides++; + + memcpy ( abovebrush->sides + abovebrush->numsides, side + 8, sizeof(cbrushside_t) ); + abovebrush->numsides++; + } + } + } + + // Increment to next terxel + brush += 2; + side += 16; + plane += 16; +#endif + } + } +#endif // PRE_RELEASE_DEMO +} + +void CCMPatch::Init(CCMLandScape *ls, int heightX, int heightY, vec3_t world, byte *hMap, byte *patchBrushData) +{ +#ifndef PRE_RELEASE_DEMO + int min, max, x, y, height; + + // Set owning landscape + owner = ls; + + // Store the base of the top left corner + VectorCopy(world, mWorldCoords); + + // Store pointer to first byte of the height data for this patch. + mHx = heightX; + mHy = heightY; + mHeightMap = hMap + ((heightY * owner->GetRealWidth()) + heightX); + + // Calculate the bounds for culling + // Use the dimensions 1 terxel outside the patch to allow for sloping of edge terxels + min = 256; + max = -1; + for(y = heightY - 1; y < heightY + owner->GetTerxels() + 1; y++) + { + if(y >= 0) + { + for(x = heightX - 1; x < heightX + owner->GetTerxels() + 1; x++) + { + if(x >= 0) + { + height = hMap[(y * owner->GetRealWidth()) + x]; + + if(height > max) + { + max = height; + } + if(height < min) + { + min = height; + } + } + } + } + } + + // Mins + mBounds[0][0] = world[0]; + mBounds[0][1] = world[1]; + mBounds[0][2] = world[2] + (min * owner->GetTerxelSize()[2]); + + // Maxs + mBounds[1][0] = world[0] + (owner->GetPatchSize()[0]); + mBounds[1][1] = world[1] + (owner->GetPatchSize()[1]); + mBounds[1][2] = world[2] + (max * owner->GetTerxelSize()[2]); + + // Corner heights + mCornerHeights[0] = mHeightMap[0]; + mCornerHeights[1] = mHeightMap[owner->GetTerxels()]; + mCornerHeights[2] = mHeightMap[owner->GetTerxels() * owner->GetRealWidth()]; + mCornerHeights[3] = mHeightMap[(owner->GetTerxels() * owner->GetRealWidth()) + owner->GetTerxels()]; + + // Set the surfaceFlags using average height (may want a more complex algo here) + mSurfaceFlags = owner->GetSurfaceFlags((min + max) >> 1); + mContentFlags = owner->GetContentFlags((min + max) >> 1); + + // Set base of brush data from big array + mPatchBrushData = (cbrush_t *)patchBrushData; + CreatePatchPlaneData(); +#endif // PRE_RELEASE_DEMO +} + +CCMPatch *CCMLandScape::GetPatch(int x, int y) +{ + return(mPatches + ((y * mBlockWidth) + x)); +} + +void CCMLandScape::PatchCollide(struct traceWork_s *tw, trace_t &trace, const vec3_t start, const vec3_t end, int checkcount) +{ + vec3pair_t tBounds; + + // Convert to valid bounding box + CM_CalcExtents(start, end, tw, tBounds); + +// if (com_newtrace->integer) + if (1) + { + float slope, offset; + float startPatchLoc, endPatchLoc, startPos, endPos; + float patchDirection = 1; + float checkDirection = 1; + int countPatches, count; + CCMPatch *patch; + float fraction = trace.fraction; + + if (fabs(end[0]-start[0]) >= fabs(fabs(end[1]-start[1]))) + { // x travels more than y + // calculate line slope and offset + if (end[0] - start[0]) + { + slope = (end[1] - start[1]) / (end[0] - start[0]); + } + else + { + slope = 0; + } + offset = start[1] - (start[0] * slope); + + // find the starting + startPatchLoc = floor((start[0] - mBounds[0][0]) / mPatchSize[0]); + endPatchLoc = floor((end[0] - mBounds[0][0]) / mPatchSize[0]); + + if (startPatchLoc <= endPatchLoc) + { // moving along slope in a positive direction + endPatchLoc++; + startPatchLoc--; + countPatches = endPatchLoc - startPatchLoc + 1; + } + else + { // moving along slope in a negative direction + endPatchLoc--; + startPatchLoc++; + patchDirection = -1; + countPatches = startPatchLoc - endPatchLoc + 1; + } + if (slope < 0.0) + { + checkDirection = -1; + } + + // first calculate the real world location + startPos = ((startPatchLoc * mPatchSize[0] + mBounds[0][0]) * slope) + offset; + // calculate it back into patch coords + startPos = floor((startPos - mBounds[0][1] + tw->size[0][1]) / mPatchSize[1]); + do + { + if (startPatchLoc >= 0 && startPatchLoc < mBlockWidth) + { // valid location + // first calculate the real world location + endPos = (((startPatchLoc+patchDirection) * mPatchSize[0] + mBounds[0][0]) * slope) + offset; + // calculate it back into patch coords + endPos = floor((endPos - mBounds[0][1] + tw->size[1][1]) / mPatchSize[1]); + + if (checkDirection < 0) + { + startPos++; + endPos--; + } + else + { + startPos--; + endPos++; + } + count = fabs(endPos - startPos) + 1; + while(count) + { + if (startPos >= 0 && startPos < mBlockHeight) + { // valid location + patch = GetPatch(startPatchLoc, startPos); + // Collide with every patch to find the minimum fraction + CM_HandlePatchCollision(tw, trace, tBounds[0], tBounds[1], patch, checkcount); + + if (trace.fraction <= 0.0) + { + return; + } + } + startPos += checkDirection; + count--; + } + + if (trace.fraction < fraction) + { + return; + } + } + // move to the next spot + // we still stay one behind, to get the opposite edge of the terrain patch + startPos = ((startPatchLoc * mPatchSize[0] + mBounds[0][0]) * slope) + offset; + startPatchLoc += patchDirection; + // first calculate the real world location + // calculate it back into patch coords + startPos = floor((startPos - mBounds[0][1] + tw->size[0][1]) / mPatchSize[1]); + + countPatches--; + } + while (countPatches); + } + else + { + // calculate line slope and offset + slope = (end[0] - start[0]) / (end[1] - start[1]); + offset = start[0] - (start[1] * slope); + + // find the starting + startPatchLoc = floor((start[1] - mBounds[0][1]) / mPatchSize[1]); + endPatchLoc = floor((end[1] - mBounds[0][1]) / mPatchSize[1]); + + if (startPatchLoc <= endPatchLoc) + { // moving along slope in a positive direction + endPatchLoc++; + startPatchLoc--; + countPatches = endPatchLoc - startPatchLoc + 1; + } + else + { // moving along slope in a negative direction + endPatchLoc--; + startPatchLoc++; + patchDirection = -1; + countPatches = startPatchLoc - endPatchLoc + 1; + } + if (slope < 0.0) + { + checkDirection = -1; + } + + // first calculate the real world location + startPos = ((startPatchLoc * mPatchSize[1] + mBounds[0][1]) * slope) + offset; + // calculate it back into patch coords + startPos = floor((startPos - mBounds[0][0] + tw->size[0][0]) / mPatchSize[0]); + do + { + if (startPatchLoc >= 0 && startPatchLoc < mBlockHeight) + { // valid location + // first calculate the real world location + endPos = (((startPatchLoc+patchDirection) * mPatchSize[1] + mBounds[0][1]) * slope) + offset; + // calculate it back into patch coords + endPos = floor((endPos - mBounds[0][0] + tw->size[1][0]) / mPatchSize[0]); + + if (checkDirection < 0) + { + startPos++; + endPos--; + } + else + { + startPos--; + endPos++; + } + + count = fabs(endPos - startPos) + 1; + while(count) + { + if (startPos >= 0 && startPos < mBlockWidth) + { // valid location + patch = GetPatch(startPos, startPatchLoc); + // Collide with every patch to find the minimum fraction + CM_HandlePatchCollision(tw, trace, tBounds[0], tBounds[1], patch, checkcount); + + if (trace.fraction <= 0.0) + { + return; + } + } + startPos += checkDirection; + count--; + } + + if (trace.fraction < fraction) + { + return; + } + } + + // move to the next spot + // we still stay one behind, to get the opposite edge of the terrain patch + startPos = ((startPatchLoc * mPatchSize[1] + mBounds[0][1]) * slope) + offset; + startPatchLoc += patchDirection; + // first calculate the real world location + // calculate it back into patch coords + startPos = floor((startPos - mBounds[0][0] + tw->size[0][0]) / mPatchSize[0]); + countPatches--; + } + while (countPatches); + } + } + else + { + int x, y; + vec3_t tWork; + vec3_t pStart, pEnd; + int minx, maxx, miny, maxy; + CCMPatch *patch; + + // Work out and grab the relevant patches + VectorSubtract(tBounds[0], mBounds[0], tWork); + VectorInverseScaleVector(tWork, mPatchSize, pStart); + VectorSubtract(tBounds[1], mBounds[0], tWork); + VectorInverseScaleVector(tWork, mPatchSize, pEnd); + + minx = Com_Clamp(0, mBlockWidth - 1, floorf(pStart[0])); + maxx = Com_Clamp(0, mBlockWidth - 1, ceilf(pEnd[0])); + miny = Com_Clamp(0, mBlockHeight - 1, floorf(pStart[1])); + maxy = Com_Clamp(0, mBlockHeight - 1, ceilf(pEnd[1])); + + // generic box collide with each one + for(y = miny; y <= maxy; y++) + { + for(x = minx; x <= maxx; x++) + { + patch = GetPatch(x, y); + // Collide with every patch to find the minimum fraction + CM_HandlePatchCollision(tw, trace, tBounds[0], tBounds[1], patch, checkcount); + + if (trace.fraction <= 0.0) + { + break; + } + } + } + } +} + +float CCMLandScape::WaterCollide(const vec3_t begin, const vec3_t end, float fraction) const +{ + // Check for completely above water + if((begin[2] > mWaterHeight) && (end[2] > mWaterHeight)) + { + return(fraction); + } + // Check for completely below water + if((begin[2] < mWaterHeight) && (end[2] < mWaterHeight)) + { + return(fraction); + } + // Check for starting in water and leaving + if(begin[2] < mWaterHeight - SURFACE_CLIP_EPSILON) + { + fraction = ((mWaterHeight - SURFACE_CLIP_EPSILON) - begin[2]) / (end[2] - begin[2]); + return(fraction); + } + // Now the trace must be entering the water + if(begin[2] > mWaterHeight + SURFACE_CLIP_EPSILON) + { + fraction = (begin[2] - (mWaterHeight + SURFACE_CLIP_EPSILON)) / (begin[2] - end[2]); + } + return(fraction); +} + +void CCMLandScape::GetTerxelLocalCoords ( int x, int y, vec3_t localCoords[8] ) +{ + int realWidth; + vec3_t* coords; + int offsets[4]; + int i; + + coords = GetCoords ( ); + realWidth = GetRealWidth ( ); + + if ( (x+y)&1 ) + { + offsets[0] = (y * realWidth) + x; // TL + offsets[1] = (y * realWidth) + x + 1; // TR + offsets[2] = ((y + 1) * realWidth) + x; // BL + offsets[3] = ((y + 1) * realWidth) + x + 1; // BR + } + else + { + offsets[2] = (y * realWidth) + x; // TL + offsets[0] = (y * realWidth) + x + 1; // TR + offsets[3] = ((y + 1) * realWidth) + x; // BL + offsets[1] = ((y + 1) * realWidth) + x + 1; // BR + } + + for( i = 0; i < 4; i++ ) + { + VectorCopy(coords[offsets[i]], localCoords[i]); + VectorCopy(coords[offsets[i]], localCoords[i + 4]); + + // Set z of base of brush to bottom of landscape brush + localCoords[i + 4][2] = GetMins()[2]; + } +} + + +void CCMLandScape::UpdatePatches(void) +{ + CCMPatch *patch; + int x, y, ix, iy, numBrushesPerPatch; + vec3_t world; + int size; + +/* for(y=0;yInit(this, x, y, world, mHeightMap, mPatchBrushData + (size * (ix + (iy * mBlockWidth)))); + } + } + +/* + for ( y = mTerxels; y < mHeight - mTerxels; y ++ ) + { + for ( x = mTerxels; x < mWidth - mTerxels; x ++ ) + { + int xo = x % mTerxels; + int yo = y % mTerxels; + int xor = (x + 1) % mTerxels; + int yob = (y + 1) % mTerxels; + + CCMPatch* patch = mPatches + (mWidth / mTerxels) * y + (x / mTerxels); + CCMPatch* rpatch = mPatches + (mWidth / mTerxels) * y + ((x+1) / mTerxels); + CCMPatch* bpatch = mPatches + (mWidth / mTerxels) * (y + 1) + (x / mTerxels); + + int offsets[4]; + vec3_t localCoords[8]; + vec3_t localCoordsR[8]; + vec3_t localCoordsL[8]; + + GetTerxelLocalCoords ( x, y, localCoords ); + GetTerxelLocalCoords ( x + 1, y, localCoordsR ); + GetTerxelLocalCoords ( x, y + 1, localCoordsB ); + + brush = patch->GetCollisionData ( );; + side = (cbrushside_t *)(mPatchBrushData + patch->GetNumBrushes ( ) ); + plane = (cplane_t *)(side + (mNumBrushes * BRUSH_SIDES_PER_TERXEL * 2)); + + + float V = DotProduct ( (plane + 8)->normal, localCoords[0] ) + plane->dist; + + if ( V < 0 ) + { + InitPlane ( brush[0].sides + brush[0].numsides, plane + brush[0].numsides, localCoords[3], localCoords[2], localCoords[1]); + brush[0].numsides++; + + InitPlane ( brush[1].sides + brush[1].numsides, plane + 8 + brush[1].numsides, localCoords[0], localCoords[1], localCoords[2]); + brush[1].numsides++; + } + } + } +*/ + + // Cleanup coord array + Z_Free(mCoords); +} + +void CCMLandScape::CalcRealCoords(void) +{ + int x, y; + + mCoords = (vec3_t *)Z_Malloc(sizeof(vec3_t) * GetRealWidth() * GetRealHeight(), TAG_CM_TERRAIN, qfalse); + + // Work out the real world coordinates of each heightmap entry + for(y = 0; y < GetRealHeight(); y++) + { + for(x = 0; x < GetRealWidth(); x++) + { + ivec3_t icoords; + int offset; + + offset = (y * GetRealWidth()) + x; + + VectorSet(icoords, x, y, mHeightMap[offset]); + VectorScaleVectorAdd(GetMins(), icoords, GetTerxelSize(), mCoords[offset]); + } + } +} + +void CCMLandScape::TerrainPatchIterate(void (*IterateFunc)( CCMPatch *, void * ), void *userdata) const +{ + int i; + CCMPatch *patch; + + patch = mPatches; + for(i = 0; i < GetBlockCount(); i++, patch++) + { + IterateFunc(patch, userdata); + } +} + +#define LERP(t, a, b) (((b)-(a))*(t) + (a)) + +float CCMLandScape::GetWorldHeight(vec3_t origin, const vec3pair_t bounds, bool aboveGround) const +{ + vec3_t work; + int minx, maxx, miny, maxy; + int TL, TR, BL, BR; + int final; + + VectorSubtract(origin, mBounds[0], work); + VectorInverseScaleVector(work, mTerxelSize, work); + + // Presume the bases of all misc models are less than 1 terxel square + minx = Com_Clamp(0, GetWidth(), (int)floorf(work[0])); + maxx = Com_Clamp(0, GetWidth(), (int)ceilf(work[0])); + miny = Com_Clamp(0, GetHeight(), (int)floorf(work[1])); + maxy = Com_Clamp(0, GetHeight(), (int)ceilf(work[1])); + + TL = mHeightMap[(miny * GetRealWidth()) + minx]; + TR = mHeightMap[(miny * GetRealWidth()) + maxx]; + BL = mHeightMap[(maxy * GetRealWidth()) + minx]; + BR = mHeightMap[(maxy * GetRealWidth()) + maxx]; + + if(aboveGround) + { +// int max1, max2; +// max1 = maximum(TL, TR); +// max2 = maximum(BL, BR); +// final = maximum(max1, max2); + float h1, h2; + float tx, ty; + tx = (work[0] - minx)/((float)(maxx-minx)); + ty = (work[1] - miny)/((float)(maxy-miny)); + h1 = LERP(tx, TL, TR); + h2 = LERP(tx, BL, BR); + final = LERP(ty, h1, h2); + } + else + { + int min1, min2; + + min1 = minimum(TL, TR); + min2 = minimum(BL, BR); + final = minimum(min1, min2); + } + origin[2] = (final * mTerxelSize[2]) + mBounds[0][2]; + + // compute slope at this spot + if (maxx == minx) + maxx = Com_Clamp(0, GetWidth(), minx+1); + if (maxy == miny) + maxy = Com_Clamp(0, GetHeight(), miny+1); + BR = mHeightMap[(maxy * GetRealWidth()) + maxx]; + + // rise over run + return (fabs((float)(BR - TL)) * mTerxelSize[2]) / mTerxelSize[0]; +} + +void CM_CircularIterate(byte *data, int width, int height, int xo, int yo, int insideRadius, int outsideRadius, int *user, void (*callback)(byte *, float, int *)) +{ + int x, y, offset; + byte *work; + + for(y = -outsideRadius; y < outsideRadius + 1; y++) + { + if(y + yo >= 0 && y + yo < height) + { + offset = sqrtf((outsideRadius * outsideRadius) - (y * y)); + for(x = -offset; x < offset + 1; x++) + { + if(x + xo >= 0 && x + xo < width) + { + float radius = sqrt((float)(x*x+y*y)); + + if ( radius >= insideRadius ) + { + work = data + (x + xo) + ((y + yo) * width); + callback( work, (radius - (float)insideRadius) / (float)(outsideRadius - insideRadius), user); + } + } + } + } + } +} + +void CM_ForceHeight( byte *work, float lerp, int *user) +{ + *work = (byte)Com_Clamp(0, 255, (int)*user); +} + + +void CM_GetAverage( byte *work, float lerp, int *user) +{ + user[0] += *work; + user[1]++; +} + +void CM_Smooth ( byte* work, float lerp, int *user ) +{ + float smooth = sin ( M_PI/2*3 + (1.0f-lerp) * (M_PI / 2) ) + 1.0f; +// float smooth = (1.0f - lerp); + + *work = *work + (int)((float)(*user - *work) * smooth); +} + +void CM_MakeAverage( byte *work, float lerp, int *user) +{ + int height, diff; + + height = (int)*work; + diff = *user - height; + if(abs(diff) > 3) + { + diff >>= 2; + } + height += diff; + *work = (byte)Com_Clamp(0, 255, height); +} + +void CCMLandScape::SaveArea(CArea *area) +{ + mAreas.push_back(area); +} + +void CCMLandScape::CarveLine ( vec3_t start, vec3_t end, int depth, int width ) +{ + int x, x1, x2, deltax; + int y, y1, y2, deltay; + int xinc1, xinc2; + int yinc1, yinc2; + int den, num; + int count, add; + int i; + float heightStart; + float heightEnd; + float heightStep; + + x1 = (int) start[0]; + y1 = (int) start[1]; + x2 = (int) end[0]; + y2 = (int) end[1]; + + deltax = abs(x2 - x1); + deltay = abs(y2 - y1); + x = x1; + y = y1; + + // The x-values are increasing + if (x2 >= x1) + { + xinc1 = 1; + xinc2 = 1; + } + // The x-values are decreasing + else + { + xinc1 = -1; + xinc2 = -1; + } + + // The y-values are increasing + if (y2 >= y1) + { + yinc1 = 1; + yinc2 = 1; + } + // The y-values are decreasing + else + { + yinc1 = -1; + yinc2 = -1; + } + + if (deltax >= deltay) // There is at least one x-value for every y-value + { + xinc1 = 0; // Don't change the x when numerator >= denominator + yinc2 = 0; // Don't change the y for every iteration + den = deltax; + num = deltax / 2; + add = deltay; + count = deltax; // There are more x-values than y-values + } + else // There is at least one y-value for every x-value + { + xinc2 = 0; // Don't change the x for every iteration + yinc1 = 0; // Don't change the y when numerator >= denominator + den = deltay; + num = deltay / 2; + add = deltax; + count = deltay; // There are more y-values than x-values + } + + vec3_t pt; + vec3_t bounds[2] = {{-1,-1,-1},{1,1,1}}; + + pt[0] = start[0]; + pt[1] = start[1]; + GetWorldHeight ( pt, bounds, false ); + heightStart = pt[2]; + + pt[0] = end[0]; + pt[1] = end[1]; + GetWorldHeight ( pt, bounds, false ); + heightEnd = pt[2]; + + heightStep = (heightEnd-heightStart) / count; + + for ( i = 0; i <= count; i++ ) + { + // Flatten the current location + CArea area; + + pt[0] = x; + pt[1] = y; + area.Init ( pt, width / 2 + (irand(0, width/2)) ); + FlattenArea ( &area, heightStart + (heightStep * i) - (depth/2 - (irand(0, depth/2))), false, true, true ); + + // Increase the numerator by the top of the fraction + num += add; + + if (num >= den) + { + // Calculate the new numerator value + num -= den; + + // Change the x and y as appropriate + x += xinc1; + y += yinc1; + } + + // Change the x and y as appropriate + x += xinc2; + y += yinc2; + } +} + +void CCMLandScape::CarveBezierCurve ( int numCtlPoints, vec3_t* ctlPoints, int steps, int depth, int size ) +{ + int i; + int choose; + int n; + float u; + float t; + float tt; + float t1; + float step; + vec3_t pt; + vec3_t lastpt; + vec3_t b[10]; + + n = numCtlPoints - 1; + choose = 1; + + for ( i = 1; i <= n; i ++ ) + { + if ( i == 1 ) + choose = n; + else + choose = choose * (n-i+1) / i; + + (*(ctlPoints+i))[0] *= choose; + (*(ctlPoints+i))[1] *= choose; + } + + step = 1.0f / (float)steps; + for ( choose = 0, t = step; t < 1; t += step, choose++ ) + { + b[0][0] = (*(ctlPoints+0))[0]; + b[0][1] = (*(ctlPoints+0))[1]; + + for ( u = t, i = 1; i <= n; i ++ ) + { + b[i][0] = (*(ctlPoints+i))[0] * u; + b[i][1] = (*(ctlPoints+i))[1] * u; + + u = u * t; + } + + pt[0] = b[n][0]; + pt[1] = b[n][1]; + + t1 = 1 - t; + tt = t1; + + for ( i = n - 1; i >= 0; i -- ) + { + pt[0] += b[i][0] * tt; + pt[1] += b[i][1] * tt; + + tt = tt * t1; + } + + if ( choose != 0 ) + { + CarveLine ( lastpt, pt, depth, size ); + } + + // Save this point for next time around + lastpt[0] = pt[0]; + lastpt[1] = pt[1]; + } +} + +void CCMLandScape::FlattenArea(CArea *area, int height, bool save, bool forceHeight, bool smooth ) +{ + vec3_t temp; + ivec3_t icoords; + int radius; + int height2; + + if(save) + { + SaveArea(area); + // mAreas.push_back(*area); + } + + // Work out coords in the heightmap + VectorSubtract(area->GetPosition(), mBounds[0], temp); + icoords[0] = temp[0] / (mBounds[1][0] - mBounds[0][0]) * (float)GetRealWidth ( ); + icoords[1] = temp[1] / (mBounds[1][1] - mBounds[0][1]) * (float)GetRealHeight ( ); + +// VectorInverseScaleVector(temp, mTerxelSize, icoords); + + // round up, we'd rather have a little more area flattened than have less then what was requested + radius = (int)ceilf( (area->GetRadius() / mTerxelSize[1]) ); + + // Work out the average height of the surrounding terrain + height2 = height; + if(height < 0) + { + ivec3_t info; + + info[0] = 0; + info[1] = 0; + CM_CircularIterate(mHeightMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], 0, radius, info, CM_GetAverage); + if(info[1]) + { + height = info[0] / info[1]; + } + } + else + { + height = height & 0x7F; + } + + if ( smooth ) + { + CM_CircularIterate(mHeightMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], radius, radius * 3, &height, CM_Smooth); + } + + if ( forceHeight ) + { + CM_CircularIterate(mHeightMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], 0, radius + 1, &height, CM_ForceHeight ); + assert (mFlattenMap); + CM_CircularIterate(mFlattenMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], 0, radius + 1, &height2, CM_ForceHeight ); + } + else if ( smooth ) + { + CM_CircularIterate(mHeightMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], 0, radius, &height, CM_Smooth); + } +} + +void CM_BelowLevel(byte *data, float lerp, int *info) +{ + info[1]++; + if(*data < info[2]) + { + info[0]++; + } +} + +float CCMLandScape::FractionBelowLevel(CArea *area, int height) +{ + vec3_t temp; + ivec3_t icoords, info; + int count; + float level; + + // Work out coords in the heightmap + VectorSubtract(area->GetPosition(), mBounds[0], temp); + VectorInverseScaleVector(temp, mTerxelSize, icoords); + + // Work out radius of area in heightmap entries + count = area->GetRadius() / mTerxelSize[1]; + + info[0] = 0; + info[1] = 0; + + info[2] = height; + if(height < 0) + { + info[2] = mBaseWaterHeight; + } + CM_CircularIterate(mHeightMap, GetRealWidth(), GetRealHeight(), icoords[0], icoords[1], 0, count, info, CM_BelowLevel); + + level = 0.0f; + if(info[1]) + { + level = (float)info[0] / info[1]; + } + + return(level); +} + +CArea *CCMLandScape::GetFirstArea(void) +{ + if(!mAreas.size()) + { + return(NULL); + } + mAreasIt = mAreas.begin(); + return (*mAreasIt); +} + +CArea *CCMLandScape::GetFirstObjectiveArea(void) +{ + if(!mAreas.size()) + { + return(NULL); + } + mAreasIt = mAreas.begin(); + + while (mAreasIt != mAreas.end()) + { + // run through the areas to find the player area + if((*mAreasIt)->GetType() == AT_OBJECTIVE) + { + return (*mAreasIt); + } + mAreasIt++; + } + return(NULL); +} + +CArea *CCMLandScape::GetPlayerArea(void) +{ // do me + if(!mAreas.size()) + { + return(NULL); + } + mAreasIt = mAreas.begin(); + + while (mAreasIt != mAreas.end()) + { + // run through the areas to find the player area + if((*mAreasIt)->GetType() == AT_PLAYER) + { + return (*mAreasIt); + } + mAreasIt++; + } + return(NULL); +} + +CArea *CCMLandScape::GetNextArea(void) +{ + mAreasIt++; + if(mAreasIt == mAreas.end()) + { + return(NULL); + } + return (*mAreasIt); +} + +CArea *CCMLandScape::GetNextObjectiveArea(void) +{ + mAreasIt++; + + while (mAreasIt != mAreas.end()) + { + // run through the areas to find the player area + if((*mAreasIt)->GetType() == AT_OBJECTIVE) + { + return (*mAreasIt); + } + mAreasIt++; + } + return(NULL); +} + +bool CCMLandScape::AreaCollision(CArea *area, int *areaTypes, int areaTypeCount) +{ + CArea *areas; + int i; + float segment; + bool collision; + + areas = GetFirstArea(); + while(areas) + { + collision = false; + + if(area->GetVillageID() == areas->GetVillageID()) + { + // Check for being too close angularly + if(area->GetAngleDiff() && areas->GetAngleDiff()) + { + segment = areas->GetAngle() - area->GetAngle(); + if(segment < M_PI) + { + segment += (float)(2 * M_PI); + } + if(segment > M_PI) + { + segment -= (float)(2 * M_PI); + } + if(fabsf(segment) < areas->GetAngleDiff() + area->GetAngleDiff()) + { + collision = true; + } + } + } + + // Check for buildings being too close together + if(Distance(areas->GetPosition(), area->GetPosition()) < areas->GetRadius() + area->GetRadius()) + { + collision = true; + } + + if(collision) + { + // If no area type list was specified then all areas are fair game + if ( !areaTypes ) + { + return true; + } + + for(i = 0; i < areaTypeCount; i++) + { + if(areas->GetType() == areaTypes[i]) + { + return(true); + } + } + } + areas = GetNextArea(); + } + return(false); +} + +void CCMLandScape::rand_seed(int seed) +{ + holdrand = seed; + Com_Printf("rand_seed = %d\n", holdrand); +} + +float CCMLandScape::flrand(float min, float max) +{ + float result; + + assert((max - min) < 32768); + + holdrand = (holdrand * 214013L) + 2531011L; + result = (float)(holdrand >> 17); // 0 - 32767 range + result = ((result * (max - min)) / 32768.0F) + min; +// Com_Printf("flrand: Seed = %d\n", holdrand); + + return(result); +} + +int CCMLandScape::irand(int min, int max) +{ + int result; + + assert((max - min) < 32768); + + max++; + holdrand = (holdrand * 214013L) + 2531011L; + result = holdrand >> 17; + result = ((result * (max - min)) >> 15) + min; +// Com_Printf("irand: Seed = %d\n", holdrand); + + return(result); +} + +CCMLandScape::~CCMLandScape(void) +{ + if(mHeightMap) + { + Z_Free(mHeightMap); + mHeightMap = NULL; + } + if(mFlattenMap) + { + Z_Free(mFlattenMap); + mFlattenMap = NULL; + } + if(mPatchBrushData) + { + Z_Free(mPatchBrushData); + mPatchBrushData = NULL; + } + if(mPatches) + { + Z_Free(mPatches); + mPatches = NULL; + } + if (mRandomTerrain) + { + delete mRandomTerrain; + } + + for(mAreasIt=mAreas.begin(); mAreasIt != mAreas.end(); mAreasIt++) + { + delete (*mAreasIt); + } + + mAreas.clear(); +} + +class CCMLandScape *CM_InitTerrain(const char *configstring, thandle_t terrainId, bool server) +{ + CCMLandScape *ls; + + ls = new CCMLandScape(configstring, server); + ls->SetTerrainId(terrainId); + + return(ls); +} + +void CM_TerrainPatchIterate(const class CCMLandScape *landscape, void (*IterateFunc)( CCMPatch *, void * ), void *userdata) +{ + landscape->TerrainPatchIterate(IterateFunc, userdata); +} + +float CM_GetWorldHeight(const CCMLandScape *landscape, vec3_t origin, const vec3pair_t bounds, bool aboveGround) +{ + return landscape->GetWorldHeight(origin, bounds, aboveGround); +} + +void CM_FlattenArea(CCMLandScape *landscape, CArea *area, int height, bool save, bool forceHeight, bool smooth ) +{ + landscape->FlattenArea(area, height, save, forceHeight, smooth ); +} + +void CM_CarveBezierCurve(CCMLandScape *landscape, int numCtls, vec3_t* ctls, int steps, int depth, int size ) +{ + landscape->CarveBezierCurve(numCtls, ctls, steps, depth, size ); +} + +void CM_SaveArea(CCMLandScape *landscape, CArea *area) +{ + landscape->SaveArea(area); +} + +float CM_FractionBelowLevel(CCMLandScape *landscape, CArea *area, int height) +{ + return(landscape->FractionBelowLevel(area, height)); +} + +bool CM_AreaCollision(class CCMLandScape *landscape, class CArea *area, int *areaTypes, int areaTypeCount) +{ + return(landscape->AreaCollision(area, areaTypes, areaTypeCount)); +} + +CArea *CM_GetFirstArea(CCMLandScape *landscape) +{ + return(landscape->GetFirstArea()); +} + +CArea *CM_GetFirstObjectiveArea(CCMLandScape *landscape) +{ + return(landscape->GetFirstObjectiveArea()); +} + +CArea *CM_GetPlayerArea(CCMLandScape *landscape) +{ + return(landscape->GetPlayerArea()); +} + +CArea *CM_GetNextArea(CCMLandScape *landscape) +{ + return(landscape->GetNextArea()); +} + +CArea *CM_GetNextObjectiveArea(CCMLandScape *landscape) +{ + return(landscape->GetNextObjectiveArea()); +} + +CRandomTerrain *CreateRandomTerrain(const char *config, CCMLandScape *landscape, byte *heightmap, int width, int height) +{ + CRandomTerrain *RandomTerrain = 0; + +#ifndef PRE_RELEASE_DEMO + char *ptr; + unsigned long seed; + + seed = strtoul(Info_ValueForKey(config, "seed"), &ptr, 10); + + landscape->rand_seed(seed); + + RandomTerrain = new CRandomTerrain; + RandomTerrain->Init(landscape, heightmap, width, height); +#endif // #ifndef PRE_RELEASE_DEMO + +/* + RandomTerrain->CreatePath(0, -1, 0, 9, 0.1, 0.5, 0.5, 0.5, 0.05, 0.08, 0.31, 0.1, 3); + RandomTerrain->CreatePath(1, 0, 0, 6, 0.5, 0.5, 0.9, 0.1, 0.08, 0.1, 0.31, 0.1, 0.9); + RandomTerrain->CreatePath(2, 0, 0, 6, 0.5, 0.5, 0.9, 0.9, 0.08, 0.1, 0.31, 0.1, 0.9); + + RandomTerrain->Generate(); +*/ + + return RandomTerrain; +} + + +// end + +#ifdef _WIN32 +#pragma optimize("p", off) +#endif diff --git a/code/qcommon/cm_terrainmap.cpp b/code/qcommon/cm_terrainmap.cpp new file mode 100644 index 0000000..3361500 --- /dev/null +++ b/code/qcommon/cm_terrainmap.cpp @@ -0,0 +1,489 @@ +#include "../server/exe_headers.h" + +#include "cm_local.h" +#include "cm_patch.h" +#include "cm_landscape.h" +#include "../game/genericparser2.h" +//#include "image.h" +//#include "../qcommon/q_imath.h" +#include "cm_terrainmap.h" +#include "cm_draw.h" +#include "../png/png.h" + +static CTerrainMap *TerrainMap = 0; + +void R_CreateAutomapImage( const char *name, const byte *pic, int width, int height, + qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ); + +// simple function for getting a proper color for a side +inline CPixel32 SideColor(int side) +{ + CPixel32 col(255,255,255); + switch (side) + { + default: + break; + case SIDE_BLUE: + col = CPixel32(0,0,192); + break; + case SIDE_RED: + col = CPixel32(192,0,0); + break; + } + return col; +} + +CTerrainMap::CTerrainMap(CCMLandScape *landscape) : + mLandscape(landscape) +{ + ApplyBackground(); + ApplyHeightmap(); + + CDraw32 draw; + draw.SetBuffer((CPixel32*) mImage); + draw.SetBufferSize(TM_WIDTH,TM_HEIGHT,TM_WIDTH); + + // create version with paths and water shown + int x,y; + int water; + int land; + + for (y=0; yGetBaseWaterHeight() - cp.a)*4, 0, 255); + cp.a = 255; + + if (x > TM_BORDER && x < (TM_WIDTH-TM_BORDER) && + y > TM_BORDER && y < (TM_WIDTH-TM_BORDER)) + { + cp = ALPHA_PIX (CPixel32(0,0,0), cp, land, 256-land); + if (water > 0) + cp = ALPHA_PIX (CPixel32(0,0,255), cp, water, 256-water); + } + + draw.PutPix(x, y, cp); + } + + // Load icons for symbols on map + GLenum format; +#ifdef _XBOX + int mipcount; + + R_LoadImage("gfx/menus/rmg/start", (byte**)&mSymStart, &mSymStartWidth, &mSymStartHeight, &mipcount, &format); + R_LoadImage("gfx/menus/rmg/end", (byte**)&mSymEnd, &mSymEndWidth, &mSymEndHeight, &mipcount, &format); + R_LoadImage("gfx/menus/rmg/objective", (byte**)&mSymObjective, &mSymObjectiveWidth, &mSymObjectiveHeight, &mipcount, &format); + + R_LoadImage("gfx/menus/rmg/building", (byte**)&mSymBld, &mSymBldWidth, &mSymBldHeight, &mipcount, &format); +#else + R_LoadImage("gfx/menus/rmg/start", (byte**)&mSymStart, &mSymStartWidth, &mSymStartHeight, &format); + R_LoadImage("gfx/menus/rmg/end", (byte**)&mSymEnd, &mSymEndWidth, &mSymEndHeight, &format); + R_LoadImage("gfx/menus/rmg/objective", (byte**)&mSymObjective, &mSymObjectiveWidth, &mSymObjectiveHeight, &format); + + R_LoadImage("gfx/menus/rmg/building", (byte**)&mSymBld, &mSymBldWidth, &mSymBldHeight, &format); +#endif +} + +CTerrainMap::~CTerrainMap() +{ + if (mSymStart) + { + Z_Free(mSymStart); + mSymStart = NULL; + } + + if (mSymEnd) + { + Z_Free(mSymEnd); + mSymEnd = NULL; + } + + if (mSymBld) + { + Z_Free(mSymBld); + mSymBld = NULL; + } + + if (mSymObjective) + { + Z_Free(mSymObjective); + mSymObjective = NULL; + } + + CDraw32::CleanUp(); +} + +void CTerrainMap::ApplyBackground(void) +{ + int x, y; + byte *outPos; + float xRel, yRel, xInc, yInc; + byte *backgroundImage; + int backgroundWidth, backgroundHeight, backgroundDepth; + int pos; + GLenum format; + + memset(mImage, 255, sizeof(mBufImage)); +// R_LoadImage("textures\\kamchatka\\ice", &backgroundImage, &backgroundWidth, &backgroundHeight, &format);0 + backgroundDepth = 4; + +#ifdef _XBOX + int mipcount; + + R_LoadImage("gfx\\menus\\rmg\\01_bg", &backgroundImage, &backgroundWidth, &backgroundHeight, &mipcount, &format); +#else + R_LoadImage("gfx\\menus\\rmg\\01_bg", &backgroundImage, &backgroundWidth, &backgroundHeight, &format); +#endif + if (backgroundImage) + { + outPos = (byte *)mBufImage; + xInc = (float)backgroundWidth / (float)TM_WIDTH; + yInc = (float)backgroundHeight / (float)TM_HEIGHT; + + yRel = 0.0; + for(y=0;yGetHeightMap(); + int width = mLandscape->GetRealWidth(); + int height = mLandscape->GetRealHeight(); + byte *outPos; + unsigned tempColor; + float xRel, yRel, xInc, yInc; + int count; + + outPos = (byte *)mBufImage; + outPos += (((TM_BORDER * TM_WIDTH) + TM_BORDER) * 4); + xInc = (float)width / (float)(TM_REAL_WIDTH); + yInc = (float)height / (float)(TM_REAL_HEIGHT); + + // add in height map as alpha + yRel = 0.0; + for(y=0;y= 1.0) + { + tempColor += inPos[(((int)(yRel-0.5))*width) + ((int)xRel)]; + count++; + } + if (yRel <= height-2) + { + tempColor += inPos[(((int)(yRel+0.5))*width) + ((int)xRel)]; + count++; + } + if (xRel >= 1.0) + { + tempColor += inPos[(((int)(yRel))*width) + ((int)(xRel-0.5))]; + count++; + } + if (xRel <= width-2) + { + tempColor += inPos[(((int)(yRel))*width) + ((int)(xRel+0.5))]; + count++; + } + tempColor /= count; + + outPos[3] = tempColor; + outPos += 4; + + // x is flipped! + xRel -= xInc; + } + outPos += TM_BORDER * 4 * 2; + + yRel += yInc; + } +} + +// Convert position in game coords to automap coords +void CTerrainMap::ConvertPos(int& x, int& y) +{ + x = ((x - mLandscape->GetMins()[0]) / mLandscape->GetSize()[0]) * TM_REAL_WIDTH; + y = ((y - mLandscape->GetMins()[1]) / mLandscape->GetSize()[1]) * TM_REAL_HEIGHT; + + // x is flipped! + x = TM_REAL_WIDTH - x - 1; + + // border + x += TM_BORDER; + y += TM_BORDER; +} + +void CTerrainMap::AddStart(int x, int y, int side) +{ + ConvertPos(x, y); + + CDraw32 draw; + draw.BlitColor(x-mSymStartWidth/2, y-mSymStartHeight/2, mSymStartWidth, mSymStartHeight, + (CPixel32*)mSymStart, 0, 0, mSymStartWidth, SideColor(side)); +} + +void CTerrainMap::AddEnd(int x, int y, int side) +{ + ConvertPos(x, y); + + CDraw32 draw; + draw.BlitColor(x-mSymEndWidth/2, y-mSymEndHeight/2, mSymEndWidth, mSymEndHeight, + (CPixel32*)mSymEnd, 0, 0, mSymEndWidth, SideColor(side)); +} + +void CTerrainMap::AddObjective(int x, int y, int side) +{ + ConvertPos(x, y); + + CDraw32 draw; + draw.BlitColor(x-mSymObjectiveWidth/2, y-mSymObjectiveHeight/2, mSymObjectiveWidth, mSymObjectiveHeight, + (CPixel32*)mSymObjective, 0, 0, mSymObjectiveWidth, SideColor(side)); +} + +void CTerrainMap::AddBuilding(int x, int y, int side) +{ + ConvertPos(x, y); + + CDraw32 draw; + draw.BlitColor(x-mSymBldWidth/2, y-mSymBldHeight/2, mSymBldWidth, mSymBldHeight, + (CPixel32*)mSymBld, 0, 0, mSymBldWidth, SideColor(side)); +} + +void CTerrainMap::AddNPC(int x, int y, bool friendly) +{ + ConvertPos(x, y); + + CDraw32 draw; + if (friendly) + draw.DrawCircle(x,y,3, CPixel32(0,192,0), CPixel32(0,0,0,0)); + else + draw.DrawCircle(x,y,3, CPixel32(192,0,0), CPixel32(0,0,0,0)); +} + +void CTerrainMap::AddNode(int x, int y) +{ + ConvertPos(x, y); + + CDraw32 draw; + draw.DrawCircle(x,y,20, CPixel32(255,255,255), CPixel32(0,0,0,0)); +} + +void CTerrainMap::AddWallRect(int x, int y, int side) +{ + ConvertPos(x, y); + + CDraw32 draw; + switch (side) + { + default: + draw.DrawBox(x-1,y-1,3,3,CPixel32(192,192,192,128)); + break; + case SIDE_BLUE: + draw.DrawBox(x-1,y-1,3,3,CPixel32(0,0,192,128)); + break; + case SIDE_RED: + draw.DrawBox(x-1,y-1,3,3,CPixel32(192,0,0,128)); + break; + } +} + +void CTerrainMap::AddPlayer(vec3_t origin, vec3_t angles) +{ + // draw player start on automap + CDraw32 draw; + + vec3_t up; + vec3_t pt[4] = {{0,0,0},{-5,-5,0},{10,0,0},{-5,5,0}}; + vec3_t p; + int x,y,i; + float facing; + POINT poly[4]; + + facing = angles[1]; + + up[0] = 0; + up[1] = 0; + up[2] = 1; + + x = (int)origin[0]; + y = (int)origin[1]; + ConvertPos(x, y); + x++; y++; + + for (i=0; i<4; i++) + { + RotatePointAroundVector( p, up, pt[i], facing ); + poly[i].x = (int)(-p[0] + x); + poly[i].y = (int)(p[1] + y); + } + + // draw arrowhead shadow + draw.DrawPolygon(4, poly, CPixel32(0,0,0,128), CPixel32(0,0,0,128)); + + // draw arrowhead + for (i=0; i<4; i++) + { + poly[i].x--; + poly[i].y--; + } + draw.DrawPolygon(4, poly, CPixel32(255,255,255), CPixel32(255,255,255)); +} + +void CTerrainMap::Upload(vec3_t player_origin, vec3_t player_angles) +{ + CDraw32 draw; + + // copy completed map to mBufImage + draw.SetBuffer((CPixel32*) mBufImage); + draw.SetBufferSize(TM_WIDTH,TM_HEIGHT,TM_WIDTH); + + draw.Blit(0, 0, TM_WIDTH, TM_HEIGHT, + (CPixel32*)mImage, 0, 0, TM_WIDTH); + + // now draw player's location on map + if (player_origin) + { + AddPlayer(player_origin, player_angles); + } + + draw.SetAlphaBuffer(255); + + R_CreateAutomapImage("*automap", (unsigned char *)draw.buffer, TM_WIDTH, TM_HEIGHT, qfalse, qfalse, qtrue, qfalse); + + draw.SetBuffer((CPixel32*) mImage); +} + +void CTerrainMap::SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed) +{ + //ri.COM_SavePNG(va("save/%s_%s_%s.png", terrainName, missionName, seed), + // (unsigned char *)mImage, TM_WIDTH, TM_HEIGHT, 4); + //rww - Use JPG here? This function seems to be only for debugging anyway. +// PNG_Save(va("save/%s_%s_%s.png", terrainName, missionName, seed), +// (unsigned char *)mImage, TM_WIDTH, TM_HEIGHT, 4); +} + +void CM_TM_Create(CCMLandScape *landscape) +{ + if (TerrainMap) + { + CM_TM_Free(); + } + + TerrainMap = new CTerrainMap(landscape); +} + +void CM_TM_Free(void) +{ + if (TerrainMap) + { + delete TerrainMap; + TerrainMap = 0; + } +} + +void CM_TM_AddStart(int x, int y, int side) +{ + if (TerrainMap) + { + TerrainMap->AddStart(x, y, side); + } +} + +void CM_TM_AddEnd(int x, int y, int side) +{ + if (TerrainMap) + { + TerrainMap->AddEnd(x, y, side); + } +} + +void CM_TM_AddObjective(int x, int y, int side) +{ + if (TerrainMap) + { + TerrainMap->AddObjective(x, y, side); + } +} + +void CM_TM_AddNPC(int x, int y, bool friendly) +{ + if (TerrainMap) + { + TerrainMap->AddNPC(x, y, friendly); + } +} + +void CM_TM_AddNode(int x, int y) +{ + if (TerrainMap) + { + TerrainMap->AddNode(x, y); + } +} + +void CM_TM_AddBuilding(int x, int y, int side) +{ + if (TerrainMap) + { + TerrainMap->AddBuilding(x, y, side); + } +} + +void CM_TM_AddWallRect(int x, int y, int side) +{ + if (TerrainMap) + { + TerrainMap->AddWallRect(x, y, side); + } +} + +void CM_TM_Upload(vec3_t player_origin, vec3_t player_angles) +{ + if (TerrainMap) + { + TerrainMap->Upload(player_origin, player_angles); + } +} + +void CM_TM_SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed) +{ + if (TerrainMap) + { // write out automap + TerrainMap->SaveImageToDisk(terrainName, missionName, seed); + } +} + +void CM_TM_ConvertPosition(int &x, int &y, int Width, int Height) +{ + if (TerrainMap) + { + TerrainMap->ConvertPos(x, y); + x = x * Width / TM_WIDTH; + y = y * Height / TM_HEIGHT; + } +} + diff --git a/code/qcommon/cm_terrainmap.h b/code/qcommon/cm_terrainmap.h new file mode 100644 index 0000000..33cd53c --- /dev/null +++ b/code/qcommon/cm_terrainmap.h @@ -0,0 +1,77 @@ +#pragma once +#if !defined(CM_TERRAINMAP_H_INC) +#define CM_TERRAINMAP_H_INC + +#define TM_WIDTH 512 +#define TM_HEIGHT 512 +#define TM_BORDER 16 +#define TM_REAL_WIDTH (TM_WIDTH-TM_BORDER-TM_BORDER) +#define TM_REAL_HEIGHT (TM_HEIGHT-TM_BORDER-TM_BORDER) + +class CTerrainMap +{ +private: + byte mImage[TM_HEIGHT][TM_WIDTH][4]; // image to output + byte mBufImage[TM_HEIGHT][TM_WIDTH][4]; // src data for image, color and bump + + byte* mSymBld; + int mSymBldWidth; + int mSymBldHeight; + + byte* mSymStart; + int mSymStartWidth; + int mSymStartHeight; + + byte* mSymEnd; + int mSymEndWidth; + int mSymEndHeight; + + byte* mSymObjective; + int mSymObjectiveWidth; + int mSymObjectiveHeight; + + CCMLandScape *mLandscape; + + void ApplyBackground(void); + void ApplyHeightmap(void); + +public: + CTerrainMap(CCMLandScape *landscape); + ~CTerrainMap(); + + void ConvertPos(int& x, int& y); + void AddBuilding(int x, int y, int side); + void AddStart(int x, int y, int side); + void AddEnd(int x, int y, int side); + void AddObjective(int x, int y, int side); + void AddNPC(int x, int y, bool friendly); + void AddWallRect(int x, int y, int side); + void AddNode(int x, int y); + void AddPlayer(vec3_t origin, vec3_t angles); + + void Upload(vec3_t player_origin, vec3_t player_angles); + void SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed); +}; + +enum +{ + SIDE_NONE =0, + SIDE_BLUE =1, + SIDE_RED = 2 +}; + +void CM_TM_Create(CCMLandScape *landscape); +void CM_TM_Free(void); +void CM_TM_AddStart(int x, int y, int side = SIDE_NONE); +void CM_TM_AddEnd(int x, int y, int side = SIDE_NONE); +void CM_TM_AddObjective(int x, int y, int side = SIDE_NONE); +void CM_TM_AddNPC(int x, int y, bool friendly); +void CM_TM_AddWallRect(int x, int y, int side = SIDE_NONE); +void CM_TM_AddNode(int x, int y); +void CM_TM_AddBuilding(int x, int y, int side = SIDE_NONE); +void CM_TM_Upload(vec3_t player_origin, vec3_t player_angles); +void CM_TM_SaveImageToDisk(const char * terrainName, const char * missionName, const char * seed); +void CM_TM_ConvertPosition(int &x, int &y, int Width, int Height); + +#endif CM_TERRAINMAP_H_INC + diff --git a/code/qcommon/cm_test.cpp b/code/qcommon/cm_test.cpp new file mode 100644 index 0000000..e9125f9 --- /dev/null +++ b/code/qcommon/cm_test.cpp @@ -0,0 +1,793 @@ +#include "cm_local.h" +#pragma warning (push, 3) //go back down to 3 for the stl include +#pragma warning (pop) +using namespace std; + +#ifdef _XBOX +#include "../renderer/tr_local.h" +#endif + +class CPoint +{ +public: + float x,y,z; + CPoint(float _x,float _y,float _z): + x(_x), + y(_y), + z(_z) + { + } + bool operator== (const CPoint& _P) const {return((x==_P.x)&&(y==_P.y)&&(z==_P.z));} +}; +/* +class CPointComparator +{ +public: + bool operator()(const CPoint& _A,const CPoint& _B) const {return((_A.x==_B.x)&&(_A.y==_B.y)&&(_A.z==_B.z));} +}; +*/ + +// Fixed memory version of pointToLeaf that doesn't use STL +// cuts down on memory fragmentation +struct PointAndLeaf +{ + // Default constructor for array construction below + PointAndLeaf() : point(0, 0, 0), leaf(0) { } + + CPoint point; + int leaf; +}; + +// I think it is a patholoically bad idea to do a 64 item linear search for a cache, +// so I reduced this to something more manageable. +// hopefully getting rid of water checks on maps with no water will leave us less +// reliant on this cache. -gwg + +#define MAX_POINTS_TO_LEAVES 16 + +static PointAndLeaf pointToLeaf[MAX_POINTS_TO_LEAVES]; +static int oldestPointToLeaf = 0, sizePointToLeaf = 0; + +//static hlist > pointToLeaf; +//static hlist > pointToContents; + +void CM_CleanLeafCache(void) +{ + oldestPointToLeaf = sizePointToLeaf = 0; +// pointToLeaf.clear(); +#if 0 // VVFIXME + hlist >::iterator l; + for(l=pointToLeaf.begin();l!=pointToLeaf.end();l++) + { + pointToLeaf.erase(l); + } +#endif +/* + for(l=pointToContents.begin();l!=pointToContents.end();l++) + { + pointToContents.erase(l); + } +*/ +} + +/* +================== +CM_PointLeafnum_r + +================== +*/ +int CM_PointLeafnum_r( const vec3_t p, int num, clipMap_t *local ) { + float d; + cNode_t *node; + cplane_t *plane; + +#ifdef _XBOX + if(!tr.world) { + return 0; + } +#endif + + while (num >= 0) + { + node = local->nodes + num; +#ifdef _XBOX + plane = cmg.planes + tr.world->nodes[num].planeNum; +#else + plane = node->plane; +#endif + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->children[1]; + else + num = node->children[0]; + } + + c_pointcontents++; // optimize counter + + return -1 - num; +} + +int CM_PointLeafnum( const vec3_t p ) { + if ( !cmg.numNodes ) { // map not loaded + return 0; + } + return CM_PointLeafnum_r (p, 0, &cmg); +} + + +/* +====================================================================== + +LEAF LISTING + +====================================================================== +*/ + + +void CM_StoreLeafs( leafList_t *ll, int nodenum ) { + int leafNum; + + leafNum = -1 - nodenum; + + // store the lastLeaf even if the list is overflowed + if ( cmg.leafs[ leafNum ].cluster != -1 ) { + ll->lastLeaf = leafNum; + } + + if ( ll->count >= ll->maxcount) { + ll->overflowed = qtrue; + return; + } + ll->list[ ll->count++ ] = leafNum; +} + +void CM_StoreBrushes( leafList_t *ll, int nodenum ) { + int i, k; + int leafnum; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + + leafnum = -1 - nodenum; + + leaf = &cmg.leafs[leafnum]; + + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = cmg.leafbrushes[leaf->firstLeafBrush+k]; + b = &cmg.brushes[brushnum]; + if ( b->checkcount == cmg.checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = cmg.checkcount; + for ( i = 0 ; i < 3 ; i++ ) { + if ( b->bounds[0][i] >= ll->bounds[1][i] || b->bounds[1][i] <= ll->bounds[0][i] ) { + break; + } + } + if ( i != 3 ) { + continue; + } + if ( ll->count >= ll->maxcount) { + ll->overflowed = qtrue; + return; + } + ((cbrush_t **)ll->list)[ ll->count++ ] = b; + } +#if 0 + // store patches? + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { + patch = cmg.surfaces[ cmg.leafsurfaces[ leaf->firstleafsurface + k ] ]; + if ( !patch ) { + continue; + } + } +#endif +} + +/* +============= +CM_BoxLeafnums + +Fills in a list of all the leafs touched +============= +*/ +void CM_BoxLeafnums_r( leafList_t *ll, int nodenum ) { + cplane_t *plane; + cNode_t *node; + int s; + +#ifdef _XBOX + if(!tr.world) { + return; + } +#endif + + while (1) { + if (nodenum < 0) { + ll->storeLeafs( ll, nodenum ); + return; + } + + node = &cmg.nodes[nodenum]; + +#ifdef _XBOX + plane = cmg.planes + tr.world->nodes[nodenum].planeNum; +#else + plane = node->plane; +#endif + s = BoxOnPlaneSide( ll->bounds[0], ll->bounds[1], plane ); + if (s == 1) { + nodenum = node->children[0]; + } else if (s == 2) { + nodenum = node->children[1]; + } else { + // go down both + CM_BoxLeafnums_r( ll, node->children[0] ); + nodenum = node->children[1]; + } + + } +} + +/* +================== +CM_BoxLeafnums +================== +*/ +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *boxList, int listsize, int *lastLeaf) { + leafList_t ll; + + cmg.checkcount++; + + VectorCopy( mins, ll.bounds[0] ); + VectorCopy( maxs, ll.bounds[1] ); + ll.count = 0; + ll.maxcount = listsize; + ll.list = boxList; + ll.storeLeafs = CM_StoreLeafs; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + CM_BoxLeafnums_r( &ll, 0 ); + + *lastLeaf = ll.lastLeaf; + return ll.count; +} + +/* +================== +CM_BoxBrushes +================== +*/ +int CM_BoxBrushes( const vec3_t mins, const vec3_t maxs, cbrush_t **boxlist, int listsize ) { + leafList_t ll; + + cmg.checkcount++; + + VectorCopy( mins, ll.bounds[0] ); + VectorCopy( maxs, ll.bounds[1] ); + ll.count = 0; + ll.maxcount = listsize; + ll.list = (int *)boxlist; + ll.storeLeafs = CM_StoreBrushes; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + CM_BoxLeafnums_r( &ll, 0 ); + + return ll.count; +} + + +//==================================================================== + + +/* +================== +CM_PointContents + +================== +*/ + +#if 1 + +int CM_PointContents( const vec3_t p, clipHandle_t model ) { + int leafnum=0; + int i, k; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + int contents; + float d; + cmodel_t *clipm; + clipMap_t *local; + + if (!cmg.numNodes) { // map not loaded + return 0; + } + + if ( model ) { + clipm = CM_ClipHandleToModel( model, &local ); + leaf = &clipm->leaf; + } + else + { + local = &cmg; + CPoint pt(p[0],p[1],p[2]); +/* map::iterator l=pointToLeaf.find(pt); + if(l!=pointToLeaf.end()) + { + leafnum=(*l).second; + } + else + { + if(pointToLeaf.size()>=64) + { + pointToLeaf.clear(); + Com_Printf("Cleared cache\n"); + } + leafnum=CM_PointLeafnum_r(p, 0); + pointToLeaf[pt]=leafnum; + }*/ + + int l = 0; + for ( ; l < sizePointToLeaf; ++l) + { + if (pointToLeaf[l].point == pt) + { + leafnum = pointToLeaf[l].leaf; + break; + } + } + + if (l == sizePointToLeaf) + { // Didn't find it + if (sizePointToLeaf < MAX_POINTS_TO_LEAVES) + { // We're adding a new one, rather than replacing + sizePointToLeaf++; + } + else + { // Put it in the "oldest" slot + l = oldestPointToLeaf++; + oldestPointToLeaf &= (MAX_POINTS_TO_LEAVES-1); + } + + leafnum = CM_PointLeafnum_r(p, 0, local); + pointToLeaf[l].leaf = leafnum; + pointToLeaf[l].point = pt; + } + + /* + hlist >::iterator l; + for(l=pointToLeaf.begin();l!=pointToLeaf.end();l++) + { + if((*l).first==pt) + { + leafnum=(*l).second; + break; + } + } + if(l==pointToLeaf.end()) + { + if(pointToLeaf.size()>=64) + { + pointToLeaf.pop_back(); + } + leafnum=CM_PointLeafnum_r(p, 0, local); + pointToLeaf.push_front(pair(pt,leafnum)); + } + */ + leaf = &local->leafs[leafnum]; + } + + contents = 0; + for (k=0 ; knumLeafBrushes ; k++) { + brushnum = local->leafbrushes[leaf->firstLeafBrush+k]; + b = &local->brushes[brushnum]; + + // see if the point is in the brush + for ( i = 0 ; i < b->numsides ; i++ ) { +#ifdef _XBOX + d = DotProduct( p, + cmg.planes[b->sides[i].planeNum.GetValue()].normal ); +#else + d = DotProduct( p, b->sides[i].plane->normal ); +#endif +// FIXME test for Cash +// if ( d >= b->sides[i].plane->dist ) { +#ifdef _XBOX + if ( d > cmg.planes[b->sides[i].planeNum.GetValue()].dist ) { +#else + if ( d > b->sides[i].plane->dist ) { +#endif + break; + } + } + + if ( i == b->numsides ) { + contents |= b->contents; +#ifndef _XBOX // Removing terrain from Xbox + if(cmg.landScape && (contents & CONTENTS_TERRAIN) ) + { + if(p[2] < cmg.landScape->GetWaterHeight()) + { + contents |= cmg.landScape->GetWaterContents(); + } + } +#endif + } + } + + return contents; +} + +#else + +int CM_PointContents( const vec3_t p, clipHandle_t model ) { + int leafnum=0; + int i, k; + int brushnum; + cLeaf_t *leaf; + cbrush_t *b; + int contents; + float d; + cmodel_t *clipm; + + if (!cmg.numNodes) { // map not loaded + return 0; + } + + CPoint pt(p[0],p[1],p[2]); + if ( model ) + { + clipm = CM_ClipHandleToModel( model ); + leaf = &clipm->leaf; + } + else + { + hlist >::iterator l; + for(l=pointToContents.begin();l!=pointToContents.end();l++) + { + if((*l).first==pt) + { + // Breakout early. + return((*l).second); + } + } + + leafnum=CM_PointLeafnum_r(p, 0); + leaf = &cmg.leafs[leafnum]; + } + + contents = 0; + for (k=0 ; knumLeafBrushes ; k++) + { + brushnum = cmg.leafbrushes[leaf->firstLeafBrush+k]; + b = &cmg.brushes[brushnum]; + + // see if the point is in the brush + for ( i = 0 ; i < b->numsides ; i++ ) + { + d = DotProduct( p, b->sides[i].plane->normal ); + // FIXME test for Cash +// if ( d >= b->sides[i].plane->dist ) { + if ( d > b->sides[i].plane->dist ) + { + break; + } + } + + if ( i == b->numsides ) + { + contents |= b->contents; + } + } + + // Cache the result for next time. + if(!model) + { + if(pointToContents.size()>=64) + { + pointToContents.pop_back(); + } + pointToContents.push_front(pair(pt,contents)); + } + + return contents; +} + +#endif +/* +================== +CM_TransformedPointContents + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles) { + vec3_t p_l; + vec3_t temp; + vec3_t forward, right, up; + + // subtract origin offset + VectorSubtract (p, origin, p_l); + + // rotate start and end into the models frame of reference + if ( model != BOX_MODEL_HANDLE && + (angles[0] || angles[1] || angles[2]) ) + { + AngleVectors (angles, forward, right, up); + + VectorCopy (p_l, temp); + p_l[0] = DotProduct (temp, forward); + p_l[1] = -DotProduct (temp, right); + p_l[2] = DotProduct (temp, up); + } + + return CM_PointContents( p_l, model ); +} + + + +/* +=============================================================================== + +PVS + +=============================================================================== +*/ + +#ifdef _XBOX +extern trGlobals_t tr; +const byte *CM_ClusterPVS (int cluster) { + if (cluster < 0 || cluster >= cmg.numClusters || !cmg.vised ) { + return NULL; + } + + return cmg.visibility->Decompress(cluster * cmg.clusterBytes, + cmg.numClusters); +} +#else +byte *CM_ClusterPVS (int cluster) { + if (cluster < 0 || cluster >= cmg.numClusters || !cmg.vised ) { + return cmg.visibility; + } + + return cmg.visibility + cluster * cmg.clusterBytes; +} +#endif + + +/* +=============================================================================== + +AREAPORTALS + +=============================================================================== +*/ + +#ifdef _XBOX +void CM_FloodArea_r( int areaNum, int floodnum) { + int i; + cArea_t *area; + int *con; + + area = &cmg.areas[ areaNum ]; + + if ( area->floodvalid == cmg.floodvalid ) { + if (area->floodnum == floodnum) + return; + Com_Error (ERR_DROP, "FloodArea_r: reflooded"); + } + + area->floodnum = floodnum; + area->floodvalid = cmg.floodvalid; + con = cmg.areaPortals + areaNum * cmg.numAreas; + for ( i=0 ; i < cmg.numAreas ; i++ ) { + if ( con[i] > 0 ) { + CM_FloodArea_r( i, floodnum ); + } + } +} +#else // _XBOX + +void CM_FloodArea_r( int areaNum, int floodnum, clipMap_t &cm) { + int i; + cArea_t *area; + int *con; + + area = &cmg.areas[ areaNum ]; + + if ( area->floodvalid == cmg.floodvalid ) { + if (area->floodnum == floodnum) + return; + Com_Error (ERR_DROP, "FloodArea_r: reflooded"); + } + + area->floodnum = floodnum; + area->floodvalid = cmg.floodvalid; + con = cmg.areaPortals + areaNum * cmg.numAreas; + for ( i=0 ; i < cmg.numAreas ; i++ ) { + if ( con[i] > 0 ) { + CM_FloodArea_r( i, floodnum, cm ); + } + } +} +#endif // XBOX + +/* +==================== +CM_FloodAreaConnections + +==================== +*/ +#ifdef _XBOX +void CM_FloodAreaConnections( void ) { + int i; + cArea_t *area; + int floodnum; + + // all current floods are now invalid + cmg.floodvalid++; + floodnum = 0; + + for (i = 0 ; i < cmg.numAreas ; i++) { + area = &cmg.areas[i]; + if (area->floodvalid == cmg.floodvalid) { + continue; // already flooded into + } + floodnum++; + CM_FloodArea_r (i, floodnum); + } + +} +#else // _XBOX +void CM_FloodAreaConnections( clipMap_t &cm ) { + int i; + cArea_t *area; + int floodnum; + + // all current floods are now invalid + cmg.floodvalid++; + floodnum = 0; + + for (i = 0 ; i < cmg.numAreas ; i++) { + area = &cmg.areas[i]; + if (area->floodvalid == cmg.floodvalid) { + continue; // already flooded into + } + floodnum++; + CM_FloodArea_r (i, floodnum, cm); + } + +} +#endif // _XBOX + +/* +==================== +CM_AdjustAreaPortalState + +==================== +*/ +void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ) { + if ( area1 < 0 || area2 < 0 ) { + return; + } + + if ( area1 >= cmg.numAreas || area2 >= cmg.numAreas ) { + Com_Error (ERR_DROP, "CM_ChangeAreaPortalState: bad area number"); + } + + if ( open ) { + cmg.areaPortals[ area1 * cmg.numAreas + area2 ]++; + cmg.areaPortals[ area2 * cmg.numAreas + area1 ]++; + } else { + cmg.areaPortals[ area1 * cmg.numAreas + area2 ]--; + cmg.areaPortals[ area2 * cmg.numAreas + area1 ]--; + if ( cmg.areaPortals[ area2 * cmg.numAreas + area1 ] < 0 ) { + Com_Error (ERR_DROP, "CM_AdjustAreaPortalState: negative reference count"); + } + } + +#ifdef _XBOX + CM_FloodAreaConnections (); +#else + CM_FloodAreaConnections (cmg); +#endif +} + +/* +==================== +CM_AreasConnected + +==================== +*/ +qboolean CM_AreasConnected( int area1, int area2 ) { +#ifndef BSPC + if ( cm_noAreas->integer ) { + return qtrue; + } +#endif + + if ( area1 < 0 || area2 < 0 ) { + return qfalse; + } + + if (area1 >= cmg.numAreas || area2 >= cmg.numAreas) { + Com_Error (ERR_DROP, "area >= cmg.numAreas"); + } + + if (cmg.areas[area1].floodnum == cmg.areas[area2].floodnum) { + return qtrue; + } + return qfalse; +} + + +/* +================= +CM_WriteAreaBits + +Writes a bit vector of all the areas +that are in the same flood as the area parameter +Returns the number of bytes needed to hold all the bits. + +The bits are OR'd in, so you can CM_WriteAreaBits from multiple +viewpoints and get the union of all visible areas. + +This is used to cull non-visible entities from snapshots +================= +*/ +int CM_WriteAreaBits (byte *buffer, int area) +{ + int i; + int floodnum; + int bytes; + + bytes = (cmg.numAreas+7)>>3; + +#ifndef BSPC + if (cm_noAreas->integer || area == -1) +#else + if ( area == -1) +#endif + { // for debugging, send everything + memset (buffer, 255, bytes); + } + else + { + floodnum = cmg.areas[area].floodnum; + for (i=0 ; i>3] |= 1<<(i&7); + } + } + + return bytes; +} + +void CM_SnapPVS(vec3_t origin,byte *buffer) +{ + int clientarea; + int leafnum; + int i; + + leafnum = CM_PointLeafnum (origin); + clientarea = CM_LeafArea (leafnum); + + // calculate the visible areas + memset(buffer,0,MAX_MAP_AREA_BYTES); + CM_WriteAreaBits(buffer,clientarea); + for ( i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++ ) { + ((int *)buffer)[i] = ((int *)buffer)[i] ^ -1; + } + +} + + diff --git a/code/qcommon/cm_trace.cpp b/code/qcommon/cm_trace.cpp new file mode 100644 index 0000000..9e83c92 --- /dev/null +++ b/code/qcommon/cm_trace.cpp @@ -0,0 +1,1223 @@ + +#include "cm_local.h" + +#ifdef _XBOX +#include "../renderer/tr_local.h" +#endif + +/* +=============================================================================== + +POSITION TESTING + +=============================================================================== +*/ + +extern cvar_t *com_terrainPhysics; +void VectorAdvance( const vec3_t veca, const float scale, const vec3_t vecb, vec3_t vecc); + +/* +================ +CM_TestBoxInBrush +================ +*/ +void CM_TestBoxInBrush( traceWork_t *tw, cbrush_t *brush ) { + int i; + cplane_t *plane; + float dist; + float d1; + cbrushside_t *side; + + if (!brush->numsides) { + return; + } + + // special test for axial + if ( tw->bounds[0][0] > brush->bounds[1][0] + || tw->bounds[0][1] > brush->bounds[1][1] + || tw->bounds[0][2] > brush->bounds[1][2] + || tw->bounds[1][0] < brush->bounds[0][0] + || tw->bounds[1][1] < brush->bounds[0][1] + || tw->bounds[1][2] < brush->bounds[0][2] + ) { + return; + } + + // the first six planes are the axial planes, so we only + // need to test the remainder + for ( i = 6 ; i < brush->numsides ; i++ ) { + side = brush->sides + i; +#ifdef _XBOX + plane = &cmg.planes[side->planeNum.GetValue()]; +#else + plane = side->plane; +#endif + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + + // if completely in front of face, no intersection + if ( d1 > 0 ) { + return; + } + } + + // inside this brush + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + tw->trace.contents = brush->contents; +} + +/* +================ +CM_PlaneCollision + + Returns false for a quick getout +================ +*/ + +bool CM_PlaneCollision(traceWork_t *tw, cbrushside_t *side) +{ + float dist, f; + float d1, d2; +#ifdef _XBOX + cplane_t *plane = &cmg.planes[side->planeNum.GetValue()]; +#else + cplane_t *plane = side->plane; +#endif + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + d2 = DotProduct( tw->end, plane->normal ) - dist; + + if (d2 > 0.0f) + { + // endpoint is not in solid + tw->getout = true; + } + if (d1 > 0.0f) + { + // startpoint is not in solid + tw->startout = true; + } + + // if completely in front of face, no intersection with the entire brush + if ((d1 > 0.0f) && ( (d2 >= SURFACE_CLIP_EPSILON) || (d2 >= d1) ) ) + { + return(false); + } + + // if it doesn't cross the plane, the plane isn't relevent + if ((d1 <= 0.0f) && (d2 <= 0.0f)) + { + return(true); + } + // crosses face + if (d1 > d2) + { // enter + f = (d1 - SURFACE_CLIP_EPSILON); + if ( f < 0.0f ) + { + f = 0.0f; + if (f > tw->enterFrac) + { + tw->enterFrac = f; + tw->clipplane = plane; + tw->leadside = side; + } + } + else if (f > tw->enterFrac * (d1 - d2) ) + { + tw->enterFrac = f / (d1 - d2); + tw->clipplane = plane; + tw->leadside = side; + } + } + else + { // leave + f = (d1 + SURFACE_CLIP_EPSILON); + if ( f < (d1 - d2) ) + { + f = 1.0f; + if (f < tw->leaveFrac) + { + tw->leaveFrac = f; + } + } + else if (f > tw->leaveFrac * (d1 - d2) ) + { + tw->leaveFrac = f / (d1 - d2); + } + } + return(true); +} + +/* +================ +CM_TraceThroughBrush +================ +*/ +void CM_TraceThroughBrush( traceWork_t *tw, trace_t &trace, cbrush_t *brush, bool infoOnly ) +{ + int i; + cbrushside_t *side; + + tw->enterFrac = -1.0f; + tw->leaveFrac = 1.0f; + tw->clipplane = NULL; + + if ( !brush->numsides ) + { + return; + } + + tw->getout = false; + tw->startout = false; + tw->leadside = NULL; + + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for (i = 0; i < brush->numsides; i++) + { + side = brush->sides + i; + + if(!CM_PlaneCollision(tw, side)) + { + return; + } + } + + // + // all planes have been checked, and the trace was not + // completely outside the brush + // + if (!tw->startout) + { + if(!infoOnly) + { + // original point was inside brush + trace.startsolid = qtrue; + if (!tw->getout) + { + trace.allsolid = qtrue; + trace.fraction = 0.0f; + } + } + tw->enterFrac = 0.0f; + return; + } + + if (tw->enterFrac < tw->leaveFrac) + { + if ((tw->enterFrac > -1.0f) && (tw->enterFrac < trace.fraction)) + { + if (tw->enterFrac < 0.0f) + { + tw->enterFrac = 0.0f; + } + if(!infoOnly) + { + trace.fraction = tw->enterFrac; + trace.plane = *tw->clipplane; + trace.surfaceFlags = cmg.shaders[tw->leadside->shaderNum].surfaceFlags; +// tw->trace.sideNum = tw->leadside - cmg.brushsides; + trace.contents = brush->contents; + } + } + } +} + +#ifndef BSPC +#ifndef _XBOX // Removing terrain from Xbox +void CM_TraceThroughTerrain( traceWork_t *tw, trace_t &trace, cbrush_t *brush ) +{ + CCMLandScape *landscape; + vec3_t tBegin, tEnd, tDistance, tStep; + vec3_t baseStart; + vec3_t baseEnd; + int count; + int i; + float fraction; + + // At this point we know we may be colliding with a terrain brush (and we know we have a valid terrain structure) + landscape = cmg.landScape; + + if (!landscape) + { + assert(landscape); + Com_Error(ERR_FATAL,"Brush had surfaceparm terrain, but there is no Terrain entity on this map!"); + } + // Check for absolutely no connection + if(!CM_GenericBoxCollide(tw->bounds, landscape->GetBounds())) + { + return; + } + // Now we know that at least some part of the trace needs to collide with the terrain + // The regular brush collision is handled elsewhere, so advance the ray to an edge in the terrain brush + CM_TraceThroughBrush( tw, trace, brush, true ); + + // Remember the base entering and leaving fractions + tw->baseEnterFrac = tw->enterFrac; + tw->baseLeaveFrac = tw->leaveFrac; + // Reset to full spread within the brush + tw->enterFrac = -1.0f; + tw->leaveFrac = 1.0f; + + // Work out the corners of the AABB when the trace first hits the terrain brush and when it leaves + VectorAdvance(tw->start, tw->baseEnterFrac, tw->end, tBegin); + VectorAdvance(tw->start, tw->baseLeaveFrac, tw->end, tEnd); + VectorSubtract(tEnd, tBegin, tDistance); + + // Calculate number of iterations to process + count = ceilf(VectorLength(tDistance) / (landscape->GetPatchScalarSize() * TERRAIN_STEP_MAGIC)); + count = 1; + fraction = trace.fraction; + VectorScale(tDistance, 1.0f / count, tStep); + + // Save the base start and end vectors + VectorCopy ( tw->start, baseStart ); + VectorCopy ( tw->end, baseEnd ); + + // Use the terrain vectors. Start both at the beginning since the + // step will be added to the end as the first step of the loop + VectorCopy ( tBegin, tw->start ); + VectorCopy ( tBegin, tw->end ); + + // Step thru terrain patches moving on about 1 patch at a time + for ( i = 0; i < count; i ++ ) + { + // Add the step to the end + VectorAdd(tw->end, tStep, tw->end); + + CM_CalcExtents(tBegin, tw->end, tw, tw->localBounds); + + landscape->PatchCollide(tw, trace, tw->start, tw->end, brush->checkcount); + + // If collision with something closer than water then just stop here + if ( trace.fraction < fraction ) + { + // Convert the fraction of this sub tract into the full trace's fraction + trace.fraction = i * (1.0f / count) + (1.0f / count) * trace.fraction; + break; + } + + // Move the end to the start so the next trace starts + // where this one left off + VectorCopy(tw->end, tw->start); + } + + // Put the original start and end back + VectorCopy ( baseStart, tw->start ); + VectorCopy ( baseEnd, tw->end ); + + // Convert to global fraction only if something was hit along the way + if ( trace.fraction != 1.0 ) + { + trace.fraction = tw->baseEnterFrac + ((tw->baseLeaveFrac - tw->baseEnterFrac) * trace.fraction); + trace.contents = brush->contents; + } + + // Collide with any water + if ( tw->contents & CONTENTS_WATER ) + { + fraction = landscape->WaterCollide(tw->start, tw->end, trace.fraction); + if( fraction < trace.fraction ) + { + VectorSet(trace.plane.normal, 0.0f, 0.0f, 1.0f); + trace.contents = landscape->GetWaterContents(); + trace.fraction = fraction; + trace.surfaceFlags = landscape->GetWaterSurfaceFlags(); + } + } +} +#endif // _XBOX +#endif + +#ifdef _XBOX +static int CM_GetSurfaceIndex(int firstLeafSurface) +{ + if(!tr.world || + firstLeafSurface > tr.world->nummarksurfaces || + firstLeafSurface < 0) { + return cmg.leafsurfaces[ firstLeafSurface ] ; + } else { + return tr.world->marksurfaces[firstLeafSurface] - tr.world->surfaces; + } +} +#endif + +/* +================ +CM_TestInLeaf +================ +*/ +void CM_TestInLeaf( traceWork_t *tw, cLeaf_t *leaf, clipMap_t *local ) { + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // test box position against all brushes in the leaf + for (k=0 ; knumLeafBrushes ; k++) { + brushnum = local->leafbrushes[leaf->firstLeafBrush+k]; + b = &local->brushes[brushnum]; + if (b->checkcount == local->checkcount) { + continue; // already checked this brush in another leaf + } + b->checkcount = local->checkcount; + + if ( !(b->contents & tw->contents)) { + continue; + } + +#ifndef BSPC +#ifndef _XBOX // Removing terrain from Xbox + if (com_terrainPhysics->integer && cmg.landScape && (b->contents & CONTENTS_TERRAIN) ) + { + // Invalidate the checkcount for terrain as the terrain brush has to be processed + // many times. + b->checkcount--; + + CM_TraceThroughTerrain( tw, tw->trace, b ); + // If inside a terrain brush don't bother with regular brush collision + continue; + } +#endif +#endif + + CM_TestBoxInBrush( tw, b ); + if ( tw->trace.allsolid ) { + return; + } + } + + // test against all patches +#ifdef BSPC + if (1) { +#else + if ( !cm_noCurves->integer ) { +#endif //BSPC + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { +#ifdef _XBOX + int index = CM_GetSurfaceIndex(leaf->firstLeafSurface + k); + patch = cmg.surfaces[ index ]; +#else + patch = local->surfaces[ local->leafsurfaces[ leaf->firstLeafSurface + k ] ]; +#endif + if ( !patch ) { + continue; + } + if ( patch->checkcount == local->checkcount ) { + continue; // already checked this brush in another leaf + } + patch->checkcount = local->checkcount; + + if ( !(patch->contents & tw->contents)) { + continue; + } + + if ( CM_PositionTestInPatchCollide( tw, patch->pc ) ) { + tw->trace.startsolid = tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + tw->trace.contents = patch->contents; + return; + } + } + } +} + +/* +================== +CM_PositionTest +================== +*/ +#define MAX_POSITION_LEAFS 1024 +void CM_PositionTest( traceWork_t *tw ) { + int leafs[MAX_POSITION_LEAFS]; + int i; + leafList_t ll; + + // identify the leafs we are touching + VectorAdd( tw->start, tw->size[0], ll.bounds[0] ); + VectorAdd( tw->start, tw->size[1], ll.bounds[1] ); + + for (i=0 ; i<3 ; i++) { + ll.bounds[0][i] -= 1; + ll.bounds[1][i] += 1; + } + + ll.count = 0; + ll.maxcount = MAX_POSITION_LEAFS; + ll.list = leafs; + ll.storeLeafs = CM_StoreLeafs; + ll.lastLeaf = 0; + ll.overflowed = qfalse; + + cmg.checkcount++; + + CM_BoxLeafnums_r( &ll, 0 ); + + + cmg.checkcount++; + + // test the contents of the leafs + for (i=0 ; i < ll.count ; i++) { + CM_TestInLeaf( tw, &cmg.leafs[leafs[i]], &cmg ); + if ( tw->trace.allsolid ) { + break; + } + } +} + +/* +=============================================================================== + +BOX TRACING + +=============================================================================== +*/ + + +/* +================ +CM_TraceThroughPatch +================ +*/ + +void CM_TraceThroughPatch( traceWork_t *tw, cPatch_t *patch ) { + float oldFrac; + + c_patch_traces++; + + oldFrac = tw->trace.fraction; + + CM_TraceThroughPatchCollide( tw, patch->pc ); + + if ( tw->trace.fraction < oldFrac ) { + tw->trace.surfaceFlags = patch->surfaceFlags; + tw->trace.contents = patch->contents; + } +} + + +/* +================ +CM_TraceThroughBrush +================ +*/ +void CM_TraceThroughBrush( traceWork_t *tw, cbrush_t *brush ) { + int i; + cplane_t *plane, *clipplane; + float dist; + float enterFrac, leaveFrac; + float d1, d2; + qboolean getout, startout; + float f; + cbrushside_t *side, *leadside; + + enterFrac = -1.0; + leaveFrac = 1.0; + clipplane = NULL; + + if ( !brush->numsides ) { + return; + } + + // I'm not sure if test is strictly correct. Are all + // bboxes axis aligned? Do I care? It seems to work + // good enough... + if ( tw->bounds[0][0] > brush->bounds[1][0] + || tw->bounds[0][1] > brush->bounds[1][1] + || tw->bounds[0][2] > brush->bounds[1][2] + || tw->bounds[1][0] < brush->bounds[0][0] + || tw->bounds[1][1] < brush->bounds[0][1] + || tw->bounds[1][2] < brush->bounds[0][2] + ) { + return; + } + + c_brush_traces++; + + getout = qfalse; + startout = qfalse; + + leadside = NULL; + + // + // compare the trace against all planes of the brush + // find the latest time the trace crosses a plane towards the interior + // and the earliest time the trace crosses a plane towards the exterior + // + for (i=0 ; inumsides ; i++) { + side = brush->sides + i; +#ifdef _XBOX + plane = &cmg.planes[side->planeNum.GetValue()]; +#else + plane = side->plane; +#endif + + // adjust the plane distance apropriately for mins/maxs + dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal ); + + d1 = DotProduct( tw->start, plane->normal ) - dist; + d2 = DotProduct( tw->end, plane->normal ) - dist; + + if (d2 > 0) { + getout = qtrue; // endpoint is not in solid + } + if (d1 > 0) { + startout = qtrue; + } + + // if completely in front of face, no intersection with the entire brush + if (d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) { + return; + } + + // if it doesn't cross the plane, the plane isn't relevent + if (d1 <= 0 && d2 <= 0 ) { + continue; + } + + // crosses face + if (d1 > d2) { // enter + f = (d1-SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f < 0 ) { + f = 0; + } + if (f > enterFrac) { + enterFrac = f; + clipplane = plane; + leadside = side; + } + } else { // leave + f = (d1+SURFACE_CLIP_EPSILON) / (d1-d2); + if ( f > 1 ) { + f = 1; + } + if (f < leaveFrac) { + leaveFrac = f; + } + } + } + + // + // all planes have been checked, and the trace was not + // completely outside the brush + // + if (!startout) { // original point was inside brush + tw->trace.startsolid = qtrue; + tw->trace.contents |= brush->contents; //note, we always want to know the contents of something we're inside of + if (!getout) + { //endpoint was inside brush + tw->trace.allsolid = qtrue; + tw->trace.fraction = 0; + } + return; + } + + if (enterFrac < leaveFrac) { + if (enterFrac > -1 && enterFrac < tw->trace.fraction) { + if (enterFrac < 0) { + enterFrac = 0; + } + tw->trace.fraction = enterFrac; + tw->trace.plane = *clipplane; + tw->trace.surfaceFlags = cmg.shaders[leadside->shaderNum].surfaceFlags; + tw->trace.contents = brush->contents; + } + } +} + +/* +================ +CM_PatchCollide + + By the time we get here we know the AABB is within the patch AABB ie there is a chance of collision + The collision data is made up of bounds, 2 triangle planes + There is an BB check for the terxel check to see if it is worth checking the planes. + Collide with both triangles to find the shortest fraction +================ +*/ + +void CM_HandlePatchCollision(struct traceWork_s *tw, trace_t &trace, const vec3_t tStart, const vec3_t tEnd, CCMPatch *patch, int checkcount) +{ + int numBrushes, i; + cbrush_t *brush; + + // Get the collision data + brush = patch->GetCollisionData(); + numBrushes = patch->GetNumBrushes(); + + for(i = 0; i < numBrushes; i++, brush++) + { + if(brush->checkcount == checkcount) + { + return; + } + + // Generic collision of terxel bounds to line segment bounds + if(!CM_GenericBoxCollide(brush->bounds, tw->localBounds)) + { + continue; + } + + brush->checkcount = checkcount; + + //CM_TraceThroughBrush(tw, trace, brush, false ); + CM_TraceThroughBrush(tw, brush); + if (trace.fraction <= 0.0) + { + break; + } + } +} + +/* +================ +CM_GenericBoxCollide +================ +*/ + +bool CM_GenericBoxCollide(const vec3pair_t abounds, const vec3pair_t bbounds) +{ + int i; + + // Check for completely no intersection + for(i = 0; i < 3; i++) + { + if(abounds[1][i] < bbounds[0][i]) + { + return(false); + } + if(abounds[0][i] > bbounds[1][i]) + { + return(false); + } + } + return(true); +} + +/* +================ +CM_TraceToLeaf +================ +*/ +void CM_TraceToLeaf( traceWork_t *tw, cLeaf_t *leaf, clipMap_t *local ) { + int k; + int brushnum; + cbrush_t *b; + cPatch_t *patch; + + // trace line against all brushes in the leaf + for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) { + brushnum = local->leafbrushes[leaf->firstLeafBrush+k]; + + b = &local->brushes[brushnum]; + if ( b->checkcount == local->checkcount ) { + continue; // already checked this brush in another leaf + } + b->checkcount = local->checkcount; + + if ( !(b->contents & tw->contents) ) { + continue; + } + +#ifndef BSPC +#ifndef _XBOX // Removing terrain from Xbox + if ( com_terrainPhysics->integer && cmg.landScape && (b->contents & CONTENTS_TERRAIN) ) + { + // Invalidate the checkcount for terrain as the terrain brush has to be processed + // many times. + b->checkcount--; + + CM_TraceThroughTerrain( tw, tw->trace, b ); + // If inside a terrain brush don't bother with regular brush collision + continue; + } +#endif +#endif + + //if (b->contents & CONTENTS_PLAYERCLIP) continue; + + CM_TraceThroughBrush( tw, b ); + if ( !tw->trace.fraction ) { + return; + } + } + + // trace line against all patches in the leaf +#ifdef BSPC + if (1) { +#else + if ( !cm_noCurves->integer ) { +#endif + for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) { +#ifdef _XBOX + int index = CM_GetSurfaceIndex(leaf->firstLeafSurface + k); + patch = cmg.surfaces[ index ]; +#else + patch = local->surfaces[ local->leafsurfaces[ leaf->firstLeafSurface + k ] ]; +#endif + if ( !patch ) { + continue; + } + if ( patch->checkcount == local->checkcount ) { + continue; // already checked this patch in another leaf + } + patch->checkcount = local->checkcount; + + if ( !(patch->contents & tw->contents) ) { + continue; + } + + CM_TraceThroughPatch( tw, patch ); + if ( !tw->trace.fraction ) { + return; + } + } + } +} + +//========================================================================================= + +/* +================== +CM_TraceThroughTree + +Traverse all the contacted leafs from the start to the end position. +If the trace is a point, they will be exactly in order, but for larger +trace volumes it is possible to hit something in a later leaf with +a smaller intercept fraction. +================== +*/ +void CM_TraceThroughTree( traceWork_t *tw, clipMap_t *local, int num, float p1f, float p2f, vec3_t p1, vec3_t p2) { + cNode_t *node; + cplane_t *plane; + float t1, t2, offset; + float frac, frac2; + float idist; + vec3_t mid; + int side; + float midf; + +#ifdef _XBOX + if(!tr.world) { + return; + } +#endif + + if (tw->trace.fraction <= p1f) { + return; // already hit something nearer + } + + // if < 0, we are in a leaf node + if (num < 0) { + CM_TraceToLeaf( tw, &local->leafs[-1-num], local ); + return; + } + + // + // find the point distances to the seperating plane + // and the offset for the size of the box + // + node = local->nodes + num; +#ifdef _XBOX + plane = cmg.planes + tr.world->nodes[num].planeNum; +#else mnode_s + plane = node->plane; +#endif + + // adjust the plane distance apropriately for mins/maxs + if ( plane->type < 3 ) { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + offset = tw->extents[plane->type]; + } else { + t1 = DotProduct (plane->normal, p1) - plane->dist; + t2 = DotProduct (plane->normal, p2) - plane->dist; + if ( tw->isPoint ) { + offset = 0; + } else { + // this is silly + offset = 2048; + } + } + + // see which sides we need to consider + if ( t1 >= offset + 1 && t2 >= offset + 1 ) { + CM_TraceThroughTree( tw, local, node->children[0], p1f, p2f, p1, p2 ); + return; + } + if ( t1 < -offset - 1 && t2 < -offset - 1 ) { + CM_TraceThroughTree( tw, local, node->children[1], p1f, p2f, p1, p2 ); + return; + } + + // put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side + if ( t1 < t2 ) { + idist = 1.0/(t1-t2); + side = 1; + frac2 = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; + frac = (t1 - offset + SURFACE_CLIP_EPSILON)*idist; + } else if (t1 > t2) { + idist = 1.0/(t1-t2); + side = 0; + frac2 = (t1 - offset - SURFACE_CLIP_EPSILON)*idist; + frac = (t1 + offset + SURFACE_CLIP_EPSILON)*idist; + } else { + side = 0; + frac = 1; + frac2 = 0; + } + + // move up to the node + if ( frac < 0 ) { + frac = 0; + } + if ( frac > 1 ) { + frac = 1; + } + + midf = p1f + (p2f - p1f)*frac; + + mid[0] = p1[0] + frac*(p2[0] - p1[0]); + mid[1] = p1[1] + frac*(p2[1] - p1[1]); + mid[2] = p1[2] + frac*(p2[2] - p1[2]); + + CM_TraceThroughTree( tw, local, node->children[side], p1f, midf, p1, mid ); + + + // go past the node + if ( frac2 < 0 ) { + frac2 = 0; + } + if ( frac2 > 1 ) { + frac2 = 1; + } + + midf = p1f + (p2f - p1f)*frac2; + + mid[0] = p1[0] + frac2*(p2[0] - p1[0]); + mid[1] = p1[1] + frac2*(p2[1] - p1[1]); + mid[2] = p1[2] + frac2*(p2[2] - p1[2]); + + CM_TraceThroughTree( tw, local, node->children[side^1], midf, p2f, mid, p2 ); +} + +void CM_CalcExtents(const vec3_t start, const vec3_t end, const traceWork_t *tw, vec3pair_t bounds) +{ + int i; + + for ( i = 0 ; i < 3 ; i++ ) + { + if ( start[i] < end[i] ) + { + bounds[0][i] = start[i] + tw->size[0][i]; + bounds[1][i] = end[i] + tw->size[1][i]; + } + else + { + bounds[0][i] = end[i] + tw->size[0][i]; + bounds[1][i] = start[i] + tw->size[1][i]; + } + } +} + +//====================================================================== + +/* +================== +CM_BoxTrace +================== +*/ +void CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask) { + int i; + traceWork_t tw; + vec3_t offset; + cmodel_t *cmod; + clipMap_t *local = 0; + + cmod = CM_ClipHandleToModel( model, &local ); + + local->checkcount++; // for multi-check avoidance + + c_traces++; // for statistics, may be zeroed + + // fill in a default trace + memset( &tw, 0, sizeof(tw) - sizeof(tw.trace.G2CollisionMap)); + tw.trace.fraction = 1; // assume it goes the entire distance until shown otherwise + + if (!local->numNodes) { + *results = tw.trace; + return; // map not loaded, shouldn't happen + } + + // allow NULL to be passed in for 0,0,0 + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + // set basic parms + tw.contents = brushmask; + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + tw.size[0][i] = mins[i] - offset[i]; + tw.size[1][i] = maxs[i] - offset[i]; + tw.start[i] = start[i] + offset[i]; + tw.end[i] = end[i] + offset[i]; + } + + tw.maxOffset = tw.size[1][0] + tw.size[1][1] + tw.size[1][2]; + + // tw.offsets[signbits] = vector to apropriate corner from origin + tw.offsets[0][0] = tw.size[0][0]; + tw.offsets[0][1] = tw.size[0][1]; + tw.offsets[0][2] = tw.size[0][2]; + + tw.offsets[1][0] = tw.size[1][0]; + tw.offsets[1][1] = tw.size[0][1]; + tw.offsets[1][2] = tw.size[0][2]; + + tw.offsets[2][0] = tw.size[0][0]; + tw.offsets[2][1] = tw.size[1][1]; + tw.offsets[2][2] = tw.size[0][2]; + + tw.offsets[3][0] = tw.size[1][0]; + tw.offsets[3][1] = tw.size[1][1]; + tw.offsets[3][2] = tw.size[0][2]; + + tw.offsets[4][0] = tw.size[0][0]; + tw.offsets[4][1] = tw.size[0][1]; + tw.offsets[4][2] = tw.size[1][2]; + + tw.offsets[5][0] = tw.size[1][0]; + tw.offsets[5][1] = tw.size[0][1]; + tw.offsets[5][2] = tw.size[1][2]; + + tw.offsets[6][0] = tw.size[0][0]; + tw.offsets[6][1] = tw.size[1][1]; + tw.offsets[6][2] = tw.size[1][2]; + + tw.offsets[7][0] = tw.size[1][0]; + tw.offsets[7][1] = tw.size[1][1]; + tw.offsets[7][2] = tw.size[1][2]; + + + // + // calculate bounds + // + for ( i = 0 ; i < 3 ; i++ ) { + if ( tw.start[i] < tw.end[i] ) { + tw.bounds[0][i] = tw.start[i] + tw.size[0][i]; + tw.bounds[1][i] = tw.end[i] + tw.size[1][i]; + } else { + tw.bounds[0][i] = tw.end[i] + tw.size[0][i]; + tw.bounds[1][i] = tw.start[i] + tw.size[1][i]; + } + } + + // + // check for position test special case + // + if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2]) { + if ( model ) { + CM_TestInLeaf( &tw, &cmod->leaf, local ); + } else { + CM_PositionTest( &tw ); + } + } else { + // + // check for point special case + // + if ( tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0 ) { + tw.isPoint = qtrue; + VectorClear( tw.extents ); + } else { + tw.isPoint = qfalse; + tw.extents[0] = tw.size[1][0]; + tw.extents[1] = tw.size[1][1]; + tw.extents[2] = tw.size[1][2]; + } + + // + // general sweeping through world + // + if ( model ) { + CM_TraceToLeaf( &tw, &cmod->leaf, local ); + } else { + CM_TraceThroughTree( &tw, local, 0, 0, 1, tw.start, tw.end ); + } + } + + // generate endpos from the original, unmodified start/end + if ( tw.trace.fraction == 1 ) { + VectorCopy (end, tw.trace.endpos); + } else { + for ( i=0 ; i<3 ; i++ ) { + tw.trace.endpos[i] = start[i] + tw.trace.fraction * (end[i] - start[i]); + } + } + + *results = tw.trace; +} + + +/* +================== +CM_TransformedBoxTrace + +Handles offseting and rotation of the end points for moving and +rotating entities +================== +*/ +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles) { + trace_t trace; + vec3_t start_l, end_l; + vec3_t a; + vec3_t forward, right, up; + vec3_t temp; + qboolean rotated; + vec3_t offset; + vec3_t symetricSize[2]; + int i; + + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for ( i = 0 ; i < 3 ; i++ ) { + offset[i] = ( mins[i] + maxs[i] ) * 0.5; + symetricSize[0][i] = mins[i] - offset[i]; + symetricSize[1][i] = maxs[i] - offset[i]; + start_l[i] = start[i] + offset[i]; + end_l[i] = end[i] + offset[i]; + } + + // subtract origin offset + VectorSubtract( start_l, origin, start_l ); + VectorSubtract( end_l, origin, end_l ); + + // rotate start and end into the models frame of reference + if ( model != BOX_MODEL_HANDLE && + (angles[0] || angles[1] || angles[2]) ) { + rotated = qtrue; + } else { + rotated = qfalse; + } + + if (rotated) { + AngleVectors (angles, forward, right, up); + + VectorCopy (start_l, temp); + start_l[0] = DotProduct (temp, forward); + start_l[1] = -DotProduct (temp, right); + start_l[2] = DotProduct (temp, up); + + VectorCopy (end_l, temp); + end_l[0] = DotProduct (temp, forward); + end_l[1] = -DotProduct (temp, right); + end_l[2] = DotProduct (temp, up); + } + + // sweep the box through the model + CM_BoxTrace( &trace, start_l, end_l, symetricSize[0], symetricSize[1], model, brushmask); + + if ( rotated && trace.fraction != 1.0 ) { + // FIXME: figure out how to do this with existing angles + VectorNegate (angles, a); + AngleVectors (a, forward, right, up); + + VectorCopy (trace.plane.normal, temp); + trace.plane.normal[0] = DotProduct (temp, forward); + trace.plane.normal[1] = -DotProduct (temp, right); + trace.plane.normal[2] = DotProduct (temp, up); + } + + trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]); + trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]); + trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]); + + *results = trace; +} + +/* +================= +CM_CullBox + +Returns true if culled out +================= +*/ + +bool CM_CullBox(const cplane_t *frustum, const vec3_t transformed[8]) +{ + int i, j; + const cplane_t *frust; + + // check against frustum planes + for (i=0, frust=frustum; i<4 ; i++, frust++) + { + for (j=0 ; j<8 ; j++) + { + if (DotProduct(transformed[j], frust->normal) > frust->dist) + { // a point is in front + break; + } + } + + if (j == 8) + { // all points were behind one of the planes + return true; + } + } + return false; +} + +/* +================= +CM_CullWorldBox + +Returns true if culled out +================= +*/ + +bool CM_CullWorldBox (const cplane_t *frustum, const vec3pair_t bounds) +{ + int i; + vec3_t transformed[8]; + + for (i = 0 ; i < 8 ; i++) + { + transformed[i][0] = bounds[i & 1][0]; + transformed[i][1] = bounds[(i >> 1) & 1][1]; + transformed[i][2] = bounds[(i >> 2) & 1][2]; + } + + //rwwFIXMEFIXME: Was not ! before. But that seems the way it should be and it works that way. Why? + return(!CM_CullBox(frustum, transformed)); +} diff --git a/code/qcommon/cmd.cpp b/code/qcommon/cmd.cpp new file mode 100644 index 0000000..b89bf9c --- /dev/null +++ b/code/qcommon/cmd.cpp @@ -0,0 +1,722 @@ +// cmd.c -- Quake script command processing module + +#include "../game/q_shared.h" +#include "qcommon.h" + +#define MAX_CMD_BUFFER 8192 +int cmd_wait; +msg_t cmd_text; +byte cmd_text_buf[MAX_CMD_BUFFER]; +char cmd_defer_text_buf[MAX_CMD_BUFFER]; + + +//============================================================================= + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame. This allows commands like: +bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster" +============ +*/ +void Cmd_Wait_f( void ) { + if ( Cmd_Argc() == 2 ) { + cmd_wait = atoi( Cmd_Argv( 1 ) ); + } else { + cmd_wait = 1; + } +} + + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init (void) +{ + MSG_Init (&cmd_text, cmd_text_buf, sizeof(cmd_text_buf)); +} + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer, does NOT add a final \n +============ +*/ +void Cbuf_AddText( const char *text ) { + int l; + + l = strlen (text); + + if (cmd_text.cursize + l >= cmd_text.maxsize) + { + Com_Printf ("Cbuf_AddText: overflow\n"); + return; + } + MSG_WriteData (&cmd_text, text, strlen (text)); +} + + +/* +============ +Cbuf_InsertText + +Adds command text immediately after the current command +Adds a \n to the text +============ +*/ +void Cbuf_InsertText( const char *text ) { + int len; + int i; + + len = strlen( text ) + 1; + if ( len + cmd_text.cursize > cmd_text.maxsize ) { + Com_Printf( "Cbuf_InsertText overflowed\n" ); + return; + } + + // move the existing command text + for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) { + cmd_text.data[ i + len ] = cmd_text.data[ i ]; + } + + // copy the new text in + memcpy( cmd_text.data, text, len - 1 ); + + // add a \n + cmd_text.data[ len - 1 ] = '\n'; + + cmd_text.cursize += len; +} + + +/* +============ +Cbuf_ExecuteText +============ +*/ +void Cbuf_ExecuteText (int exec_when, const char *text) +{ + switch (exec_when) + { + case EXEC_NOW: + Cmd_ExecuteString (text); + break; + case EXEC_INSERT: + Cbuf_InsertText (text); + break; + case EXEC_APPEND: + Cbuf_AddText (text); + break; + default: + Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when"); + } +} + +/* +============ +Cbuf_Execute +============ +*/ +void Cbuf_Execute (void) +{ + int i; + char *text; + char line[MAX_CMD_BUFFER]; + int quotes; + + while (cmd_text.cursize) + { + if ( cmd_wait ) { + // skip out while text still remains in buffer, leaving it + // for next frame + cmd_wait--; + break; + } + + // find a \n or ; line break + text = (char *)cmd_text.data; + + quotes = 0; + for (i=0 ; i< cmd_text.cursize ; i++) + { + if (text[i] == '"') + quotes++; + if ( !(quotes&1) && text[i] == ';') + break; // don't break if inside a quoted string + if (text[i] == '\n' || text[i] == '\r' ) + break; + } + + + memcpy (line, text, i); + line[i] = 0; + +// delete the text from the command buffer and move remaining commands down +// this is necessary because commands (exec) can insert data at the +// beginning of the text buffer + + if (i == cmd_text.cursize) + cmd_text.cursize = 0; + else + { + i++; + cmd_text.cursize -= i; + memmove (text, text+i, cmd_text.cursize); + } + +// execute the command line + Cmd_ExecuteString (line); + } +} + + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ + + +/* +=============== +Cmd_Exec_f +=============== +*/ +void Cmd_Exec_f( void ) { + char *f; + int len; + char filename[MAX_QPATH]; + + if (Cmd_Argc () != 2) { + Com_Printf ("exec : execute a script file\n"); + return; + } + + Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + len = FS_ReadFile( filename, (void **)&f); + if (!f) { + Com_Printf ("couldn't exec %s\n",Cmd_Argv(1)); + return; + } + Com_Printf ("execing %s\n",Cmd_Argv(1)); + + Cbuf_InsertText (f); + + FS_FreeFile (f); +} + + +/* +=============== +Cmd_Vstr_f + +Inserts the current value of a variable as command text +=============== +*/ +void Cmd_Vstr_f( void ) { + char *v; + + if (Cmd_Argc () != 2) { + Com_Printf ("vstr : execute a variable command\n"); + return; + } + + v = Cvar_VariableString( Cmd_Argv( 1 ) ); + Cbuf_InsertText( va("%s\n", v ) ); +} + + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void Cmd_Echo_f (void) +{ + int i; + + for (i=1 ; i= cmd_argc ) { + return ""; + } + return cmd_argv[arg]; +} + +/* +============ +Cmd_ArgvBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) { + Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength ); +} + + +/* +============ +Cmd_Args + +Returns a single string containing argv(1) to argv(argc()-1) +============ +*/ +char *Cmd_Args( void ) { + static char cmd_args[MAX_STRING_CHARS]; + int i; + + cmd_args[0] = 0; + for ( i = 1 ; i < cmd_argc ; i++ ) { + strcat( cmd_args, cmd_argv[i] ); + if ( i != cmd_argc ) { + strcat( cmd_args, " " ); + } + } + + return cmd_args; +} + + +/* +============ +Cmd_ArgsBuffer + +The interpreted versions use this because +they can't have pointers returned to them +============ +*/ +void Cmd_ArgsBuffer( char *buffer, int bufferLength ) { + Q_strncpyz( buffer, Cmd_Args(), bufferLength ); +} + + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +The text is copied to a seperate buffer and 0 characters +are inserted in the apropriate place, The argv array +will point into this temporary buffer. +============ +*/ +void Cmd_TokenizeString( const char *text_in ) { + const unsigned char *text; + char *textOut; + + // clear previous args + cmd_argc = 0; + + if ( !text_in ) { + return; + } + + text = (const unsigned char *)text_in; + textOut = cmd_tokenized; + + while ( 1 ) { + if ( cmd_argc == MAX_STRING_TOKENS ) { + return; // this is usually something malicious + } + + while ( 1 ) { + // skip whitespace + while ( *text && *text <= ' ' ) { + text++; + } + if ( !*text ) { + return; // all tokens parsed + } + + // skip // comments + if ( text[0] == '/' && text[1] == '/' ) { + return; // all tokens parsed + } + + // skip /* */ comments + if ( text[0] == '/' && text[1] =='*' ) { + while ( *text && ( text[0] != '*' || text[1] != '/' ) ) { + text++; + } + if ( !*text ) { + return; // all tokens parsed + } + text += 2; + } else { + break; // we are ready to parse a token + } + } + + // handle quoted strings + if ( *text == '"' ) { + cmd_argv[cmd_argc] = textOut; + cmd_argc++; + text++; + while ( *text && *text != '"' ) { + *textOut++ = *text++; + } + *textOut++ = 0; + if ( !*text ) { + return; // all tokens parsed + } + text++; + continue; + } + + // regular token + cmd_argv[cmd_argc] = textOut; + cmd_argc++; + + // skip until whitespace, quote, or command + while ( *text > ' ' ) { + if ( text[0] == '"' ) { + break; + } + + if ( text[0] == '/' && text[1] == '/' ) { + break; + } + + // skip /* */ comments + if ( text[0] == '/' && text[1] =='*' ) { + break; + } + + *textOut++ = *text++; + } + + *textOut++ = 0; + + if ( !*text ) { + return; // all tokens parsed + } + } + +} + + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) { + cmd_function_t *cmd; + cmd_function_t *add = NULL; + int c; + + // fail if the command already exists + for ( c = 0; c < CMD_MAX_NUM; ++c ) + { + cmd = cmd_functions + c; + if ( !strcmp( cmd_name, cmd->name ) ) { + // allow completion-only commands to be silently doubled + if ( function != NULL ) { + Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); + } + return; + } + + if ( add == NULL && cmd->name[0] == '\0') + { + add = cmd; + } + } + + if ( add == NULL ) + { + Com_Printf ("Cmd_AddCommand: Too many commands registered\n", cmd_name); + return; + } + + Q_strncpyz(add->name, cmd_name, CMD_MAX_NAME, qtrue); + add->function = function; +} + +/* +============ +Cmd_RemoveCommand +============ +*/ +void Cmd_RemoveCommand( const char *cmd_name ) { + cmd_function_t *cmd; + + for ( int c = 0; c < CMD_MAX_NUM; ++c ) + { + cmd = cmd_functions + c; + if ( !strcmp( cmd_name, cmd->name ) ) { + cmd->name[0] = '\0'; + return; + } + } +} + +char *Cmd_CompleteCommandNext (char *partial, char *last) +{ + cmd_function_t *cmd, *base; + int len, c; + + len = strlen(partial); + + if (!len) + return NULL; + + // start past last match + base = NULL; + if(last) + { + for (c = 0; c < CMD_MAX_NUM; ++c) + { + cmd = cmd_functions + c; + if(!strcmp(last, cmd->name)) + { + base = cmd + 1; + break; + } + } + if(base == NULL) + { //not found, either error or at end of list + return NULL; + } + } + else + { + base = cmd_functions; + } + + + for (c = base - cmd_functions; c < CMD_MAX_NUM; ++c) + { + cmd = cmd_functions + c; + if (!strcmp (partial,cmd->name)) + return cmd->name; + } + +// check for partial match + for (c = base - cmd_functions; c < CMD_MAX_NUM; ++c) + { + cmd = cmd_functions + c; + if (!strncmp (partial,cmd->name, len)) + return cmd->name; + } + + return NULL; +} + + +/* +============ +Cmd_CompleteCommand +============ +*/ +char *Cmd_CompleteCommand( const char *partial ) { + cmd_function_t *cmd; + int len; + + len = strlen(partial); + + if (!len) + return NULL; + + // check for exact match + for (int c = 0; c < CMD_MAX_NUM; ++c) + { + cmd = cmd_functions + c; + if (!Q_stricmp( partial, cmd->name)) + return cmd->name; + } + + // check for partial match + for (int c = 0; c < CMD_MAX_NUM; ++c) + { + cmd = cmd_functions + c; + if (!Q_stricmpn (partial,cmd->name, len)) + return cmd->name; + } + + return NULL; +} + + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +============ +*/ +extern void Key_SetCatcher( int catcher ); +extern void Menus_CloseAll(void); + +void Cmd_ExecuteString( const char *text ) { + + // execute the command line + Cmd_TokenizeString( text ); + if ( !Cmd_Argc() ) { + return; // no tokens + } + + cvar_t* levelSelectCheat = Cvar_Get("levelSelectCheat", "-1", CVAR_SAVEGAME); + if( (!strcmp(text,"use end_level") || strstr(text, "maptransition") || !strcmp(text, "uimenu ingameMissionSelect") || !strcmp(text, "uimenu ingameGotoTier")) && // level end + levelSelectCheat->integer != -1 ) // was cheating + { + Cbuf_ExecuteText( EXEC_APPEND, "disconnect\n" ); // disconnect the player + Key_SetCatcher( KEYCATCH_UI ); // set them in the ui + Menus_CloseAll(); // close all the menus + Cvar_Set("levelSelectCheat", "-1"); + return; + } + + // check registered command functions + for ( int c = 0; c < CMD_MAX_NUM; ++c ) + { + if ( !Q_stricmp( cmd_argv[0],cmd_functions[c].name ) ) { + // rearrange the links so that the command will be + // near the head of the list next time it is used + cmd_function_t temp = cmd_functions[c]; + cmd_functions[c] = cmd_functions[0]; + cmd_functions[0] = temp; + + // perform the action + if ( !temp.function ) { + // let the cgame or game handle it + break; + } else { + temp.function (); + } + return; + } + } + + // check cvars + if ( Cvar_Command() ) { + return; + } + + // check client game commands + if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) { + return; + } + + // check server game commands + if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) { + return; + } + + // check ui commands + if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) { + return; + } + + // send it as a server command if we are connected + // this will usually result in a chat message + CL_ForwardCommandToServer (); +} + +/* +============ +Cmd_List_f +============ +*/ +void Cmd_List_f (void) +{ + cmd_function_t *cmd; + int i; + char *match; + + if ( Cmd_Argc() > 1 ) { + match = Cmd_Argv( 1 ); + } else { + match = NULL; + } + + i = 0; + for ( int c = 0; c < CMD_MAX_NUM; ++c ) + { + cmd = cmd_functions + c; + if (match && !Com_Filter(match, cmd->name, qfalse)) continue; + Com_Printf ("%s\n", cmd->name); + i++; + } + Com_Printf ("%i commands\n", i); +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init (void) +{ +// +// register our commands +// + Cmd_AddCommand ("cmdlist",Cmd_List_f); + Cmd_AddCommand ("exec",Cmd_Exec_f); + Cmd_AddCommand ("vstr",Cmd_Vstr_f); + Cmd_AddCommand ("echo",Cmd_Echo_f); + Cmd_AddCommand ("wait", Cmd_Wait_f); +} + diff --git a/code/qcommon/common.cpp b/code/qcommon/common.cpp new file mode 100644 index 0000000..005fb25 --- /dev/null +++ b/code/qcommon/common.cpp @@ -0,0 +1,1662 @@ +// common.c -- misc functions used in client and server + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "../qcommon/sstring.h" // to get Gil's string class, because MS's doesn't compile properly in here +#include "stv_version.h" + +#ifdef _XBOX +#include "../win32/win_file.h" +#include "../ui/ui_splash.h" +#endif + +#include "platform.h" + +#define MAXPRINTMSG 4096 + +#define MAX_NUM_ARGVS 50 + +int com_argc; +char *com_argv[MAX_NUM_ARGVS+1]; + +#ifndef _XBOX +static fileHandle_t logfile; +static fileHandle_t speedslog; +static fileHandle_t camerafile; +fileHandle_t com_journalFile; +fileHandle_t com_journalDataFile; // config files are written here +#endif + +// Global language setting - this should be used instead of the myriad language +// cvars. Will be one of the Xbox values: XC_LANGUAGE_(ENGLISH|FRENCH|GERMAN) +DWORD g_dwLanguage; + +cvar_t *com_viewlog; +cvar_t *com_speeds; +cvar_t *com_developer; +cvar_t *com_timescale; +cvar_t *com_fixedtime; +cvar_t *com_maxfps; +cvar_t *com_sv_running; +cvar_t *com_cl_running; +cvar_t *com_logfile; // 1 = buffer log, 2 = flush after each print +cvar_t *com_showtrace; +cvar_t *com_terrainPhysics; +cvar_t *com_version; +cvar_t *com_buildScript; // for automated data building scripts +cvar_t *cl_paused; +cvar_t *sv_paused; +cvar_t *com_skippingcin; +cvar_t *com_speedslog; // 1 = buffer log, 2 = flush after each print +extern cvar_t *inSplashMenu; +extern cvar_t *controllerOut; + +#ifdef G2_PERFORMANCE_ANALYSIS +cvar_t *com_G2Report; +#endif + + +// com_speeds times +int time_game; +int time_frontend; // renderer frontend time +int time_backend; // renderer backend time + +int timeInTrace; +int timeInPVSCheck; +int numTraces; + +int com_frameTime; +int com_frameMsec; +int com_frameNumber = 0; + +qboolean com_errorEntered; +qboolean com_fullyInitialized = qfalse; + +char com_errorMessage[MAXPRINTMSG]; + +void Com_WriteConfig_f( void ); + +//============================================================================ + +#ifndef _XBOX +static char *rd_buffer; +static int rd_buffersize; +static void (*rd_flush)( char *buffer ); + +void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)( char *) ) +{ + if (!buffer || !buffersize || !flush) + return; + rd_buffer = buffer; + rd_buffersize = buffersize; + rd_flush = flush; + + *rd_buffer = 0; +} + +void Com_EndRedirect (void) +{ + if ( rd_flush ) { + rd_flush(rd_buffer); + } + + rd_buffer = NULL; + rd_buffersize = 0; + rd_flush = NULL; +} +#ifndef FINAL_BUILD +#define OUTPUT_TO_BUILD_WINDOW +#endif +#endif //not xbox + +/* +============= +Com_Printf + +Both client and server can use this, and it will output +to the apropriate place. + +A raw string should NEVER be passed as fmt, because of "%f" type crashers. +============= +*/ +void QDECL Com_Printf( const char *fmt, ... ) { +#ifndef FINAL_BUILD + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + +#ifndef _XBOX + if ( rd_buffer ) { + if ((strlen (msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) { + rd_flush(rd_buffer); + *rd_buffer = 0; + } + strcat (rd_buffer, msg); + return; + } +#endif + + CL_ConsolePrint( msg ); + + // echo to dedicated console and early console + Sys_Print( msg ); + +#ifdef OUTPUT_TO_BUILD_WINDOW + OutputDebugString(msg); +#endif + +#ifndef _XBOX + // logfile + if ( com_logfile && com_logfile->integer ) { + if ( !logfile ) { + logfile = FS_FOpenFileWrite( "qconsole.log" ); + if ( com_logfile->integer > 1 ) { + // force it to not buffer so we get valid + // data even if we are crashing + FS_ForceFlush(logfile); + } + } + if ( logfile ) { + FS_Write(msg, strlen(msg), logfile); + } + } +#endif +#endif +} + +void QDECL Com_PrintfAlways( const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + +#ifndef _XBOX + if ( rd_buffer ) { + if ((strlen (msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) { + rd_flush(rd_buffer); + *rd_buffer = 0; + } + strcat (rd_buffer, msg); + return; + } +#endif + + CL_ConsolePrint( msg ); + + // echo to dedicated console and early console +#ifndef FINAL_BUILD + Sys_Print( msg ); + +#ifdef OUTPUT_TO_BUILD_WINDOW + OutputDebugString(msg); +#endif +#endif + +#ifndef _XBOX + // logfile + if ( com_logfile && com_logfile->integer ) { + if ( !logfile ) { + logfile = FS_FOpenFileWrite( "qconsole.log" ); + if ( com_logfile->integer > 1 ) { + // force it to not buffer so we get valid + // data even if we are crashing + FS_ForceFlush(logfile); + } + } + if ( logfile ) { + FS_Write(msg, strlen(msg), logfile); + } + } +#endif +} + + + +/* +================ +Com_DPrintf + +A Com_Printf that only shows up if the "developer" cvar is set +================ +*/ +void QDECL Com_DPrintf( const char *fmt, ...) { + va_list argptr; + char msg[MAXPRINTMSG]; + + if ( !com_developer || !com_developer->integer ) { + return; // don't confuse non-developers with techie stuff... + } + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + Com_Printf ("%s", msg); +} + +void Com_WriteCam ( const char *text ) +{ +#ifndef _XBOX + static char mapname[MAX_QPATH]; + // camerafile + if ( !camerafile ) + { + extern cvar_t *sv_mapname; + + //NOTE: always saves in working dir if using one... + sprintf( mapname, "maps/%s_cam.map", sv_mapname->string ); + camerafile = FS_FOpenFileWrite( mapname ); + } + + if ( camerafile ) + { + FS_Printf( camerafile, "%s", text ); + } + + Com_Printf( "%s\n", mapname ); +#endif +} + +void Com_FlushCamFile() +{ +#ifndef _XBOX + if (!camerafile) + { + // nothing to flush, right? + Com_Printf("No cam file available\n"); + return; + } + FS_ForceFlush(camerafile); + FS_FCloseFile (camerafile); + camerafile = 0; + + static char flushedMapname[MAX_QPATH]; + extern cvar_t *sv_mapname; + sprintf( flushedMapname, "maps/%s_cam.map", sv_mapname->string ); + Com_Printf("flushed all cams to %s\n", flushedMapname); +#endif +} + +/* +============= +Com_Error + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ + +void SG_WipeSavegame(const char *name); // pretty sucky, but that's how SoF did it... +void SG_Shutdown(); +//void SCR_UnprecacheScreenshot(); + +void QDECL Com_Error( int code, const char *fmt, ... ) { + va_list argptr; + +#if defined(_WIN32) && defined(_DEBUG) + if ( code != ERR_DISCONNECT && code != ERR_NEED_CD ) { +// if (com_noErrorInterrupt && !com_noErrorInterrupt->integer) + { + __asm { + int 0x03 + } + } + } +#endif + + // when we are running automated scripts, make sure we + // know if anything failed + if ( com_buildScript && com_buildScript->integer ) { + code = ERR_FATAL; + } + + if ( com_errorEntered ) { + Sys_Error( "recursive error after: %s", com_errorMessage ); + } + + com_errorEntered = qtrue; + + //reset some game stuff here +// SCR_UnprecacheScreenshot(); + + va_start (argptr,fmt); + vsprintf (com_errorMessage,fmt,argptr); + va_end (argptr); + + if ( code != ERR_DISCONNECT ) { + Cvar_Get("com_errorMessage", "", CVAR_ROM); //give com_errorMessage a default so it won't come back to life after a resetDefaults + Cvar_Set("com_errorMessage", com_errorMessage); + } + + SG_Shutdown(); // close any file pointers + if ( code == ERR_DISCONNECT ) { + SV_Shutdown("Disconnect"); + CL_Disconnect(); + CL_FlushMemory(); + CL_StartHunkUsers(); + com_errorEntered = qfalse; + throw ("DISCONNECTED\n"); + } else if ( code == ERR_DROP ) { + // If loading/saving caused the crash/error - delete the temp file + // SG_WipeSavegame("current"); // delete file + + SV_Shutdown (va("Server crashed: %s\n", com_errorMessage)); + CL_Disconnect(); + CL_FlushMemory(); + CL_StartHunkUsers(); + Com_Printf (S_COLOR_RED"********************\n"S_COLOR_MAGENTA"ERROR: %s\n"S_COLOR_RED"********************\n", com_errorMessage); + com_errorEntered = qfalse; + throw ("DROPPED\n"); + } else if ( code == ERR_NEED_CD ) { + SV_Shutdown( "Server didn't have CD\n" ); + if ( com_cl_running && com_cl_running->integer ) { + CL_Disconnect(); + CL_FlushMemory(); + CL_StartHunkUsers(); + com_errorEntered = qfalse; + } else { + Com_Printf("Server didn't have CD\n" ); + } + throw ("NEED CD\n"); + } else { + CL_Shutdown (); + SV_Shutdown (va(S_COLOR_RED"Server fatal crashed: %s\n", com_errorMessage)); + } + + Com_Shutdown (); + + Sys_Error ("%s", com_errorMessage); +} + + +/* +============= +Com_Quit_f + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ +void Com_Quit_f( void ) { + // don't try to shutdown if we are in a recursive error + if ( !com_errorEntered ) { + SV_Shutdown ("Server quit\n"); + CL_Shutdown (); + Com_Shutdown (); + } + Sys_Quit (); +} + + + +/* +============================================================================ + +COMMAND LINE FUNCTIONS + ++ characters seperate the commandLine string into multiple console +command lines. + +All of these are valid: + +quake3 +set test blah +map test +quake3 set test blah+map test +quake3 set test blah + map test + +============================================================================ +*/ + +#define MAX_CONSOLE_LINES 32 +int com_numConsoleLines; +char *com_consoleLines[MAX_CONSOLE_LINES]; + +/* +================== +Com_ParseCommandLine + +Break it up into multiple console lines +================== +*/ +void Com_ParseCommandLine( char *commandLine ) { + com_consoleLines[0] = commandLine; + com_numConsoleLines = 1; + + while ( *commandLine ) { + // look for a + seperating character + // if commandLine came from a file, we might have real line seperators + if ( *commandLine == '+' || *commandLine == '\n' ) { + if ( com_numConsoleLines == MAX_CONSOLE_LINES ) { + return; + } + com_consoleLines[com_numConsoleLines] = commandLine + 1; + com_numConsoleLines++; + *commandLine = 0; + } + commandLine++; + } +} + + +/* +=================== +Com_SafeMode + +Check for "safe" on the command line, which will +skip loading of jaconfig.cfg +=================== +*/ +qboolean Com_SafeMode( void ) { + int i; + + for ( i = 0 ; i < com_numConsoleLines ; i++ ) { + Cmd_TokenizeString( com_consoleLines[i] ); + if ( !Q_stricmp( Cmd_Argv(0), "safe" ) + || !Q_stricmp( Cmd_Argv(0), "cvar_restart" ) ) { + com_consoleLines[i][0] = 0; + return qtrue; + } + } + return qfalse; +} + + +/* +=============== +Com_StartupVariable + +Searches for command line parameters that are set commands. +If match is not NULL, only that cvar will be looked for. +That is necessary because cddir and basedir need to be set +before the filesystem is started, but all other sets should +be after execing the config and default. +=============== +*/ +void Com_StartupVariable( const char *match ) { + int i; + char *s; + cvar_t *cv; + + for (i=0 ; i < com_numConsoleLines ; i++) { + Cmd_TokenizeString( com_consoleLines[i] ); + if ( strcmp( Cmd_Argv(0), "set" ) ) { + continue; + } + + s = Cmd_Argv(1); + if ( !match || !stricmp( s, match ) ) { + Cvar_Set( s, Cmd_Argv(2) ); + cv = Cvar_Get( s, "", 0 ); + cv->flags |= CVAR_USER_CREATED; +// com_consoleLines[i] = 0; + } + } +} + + +/* +================= +Com_AddStartupCommands + +Adds command line parameters as script statements +Commands are seperated by + signs + +Returns qtrue if any late commands were added, which +will keep the demoloop from immediately starting +================= +*/ +qboolean Com_AddStartupCommands( void ) { + int i; + qboolean added; + + added = qfalse; + // quote every token, so args with semicolons can work + for (i=0 ; i < com_numConsoleLines ; i++) { + if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) { + continue; + } + + // set commands won't override menu startup + if ( Q_stricmpn( com_consoleLines[i], "set", 3 ) ) { + added = qtrue; + } + Cbuf_AddText( com_consoleLines[i] ); + Cbuf_AddText( "\n" ); + } + + return added; +} + + +//============================================================================ + + +void Info_Print( const char *s ) { + char key[512]; + char value[512]; + char *o; + int l; + + if (*s == '\\') + s++; + while (*s) + { + o = key; + while (*s && *s != '\\') + *o++ = *s++; + + l = o - key; + if (l < 20) + { + memset (o, ' ', 20-l); + key[20] = 0; + } + else + *o = 0; + Com_Printf ("%s", key); + + if (!*s) + { + Com_Printf ("MISSING VALUE\n"); + return; + } + + o = value; + s++; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (*s) + s++; + Com_Printf ("%s\n", value); + } +} + +/* +============ +Com_StringContains +============ +*/ +char *Com_StringContains(char *str1, char *str2, int casesensitive) { + int len, i, j; + + len = strlen(str1) - strlen(str2); + for (i = 0; i <= len; i++, str1++) { + for (j = 0; str2[j]; j++) { + if (casesensitive) { + if (str1[j] != str2[j]) { + break; + } + } + else { + if (toupper(str1[j]) != toupper(str2[j])) { + break; + } + } + } + if (!str2[j]) { + return str1; + } + } + return NULL; +} + +/* +============ +Com_Filter +============ +*/ +int Com_Filter(char *filter, char *name, int casesensitive) { + char buf[MAX_TOKEN_CHARS]; + char *ptr; + int i; + + while(*filter) { + if (*filter == '*') { + filter++; + for (i = 0; *filter; i++) { + if (*filter == '*' || *filter == '?') { + break; + } + buf[i] = *filter; + filter++; + } + buf[i] = '\0'; + if (strlen(buf)) { + ptr = Com_StringContains(name, buf, casesensitive); + if (!ptr) { + return qfalse; + } + name = ptr + strlen(buf); + } + } + else if (*filter == '?') { + filter++; + name++; + } + else { + if (casesensitive) { + if (*filter != *name) { + return qfalse; + } + } + else { + if (toupper(*filter) != toupper(*name)) { + return qfalse; + } + } + filter++; + name++; + } + } + return qtrue; +} + + + +/* +================= +Com_InitHunkMemory +================= +*/ +void Com_InitHunkMemory( void ) +{ + Hunk_Clear(); + +// Cmd_AddCommand( "meminfo", Z_Details_f ); +} + +// I'm leaving this in just in case we ever need to remember where's a good place to hook something like this in. +// +void Com_ShutdownHunkMemory(void) +{ +} + + +/* +=================== +Hunk_SetMark + +The server calls this after the level and game VM have been loaded +=================== +*/ +void Hunk_SetMark( void ) +{ +} + + + +/* +================= +Hunk_ClearToMark + +The client calls this before starting a vid_restart or snd_restart +================= +*/ +void Hunk_ClearToMark( void ) +{ + Z_TagFree(TAG_HUNKALLOC); +// Z_TagFree(TAG_HUNKMISCMODELS); +} + + + +/* +================= +Hunk_Clear + +The server calls this before shutting down or loading a new map +================= +*/ +void Hunk_Clear( void ) +{ + Z_TagFree(TAG_HUNKALLOC); +// Z_TagFree(TAG_HUNKMISCMODELS); + + extern void CIN_CloseAllVideos(); + CIN_CloseAllVideos(); + + extern void R_ClearStuffToStopGhoul2CrashingThings(void); + R_ClearStuffToStopGhoul2CrashingThings(); +} + + + + + + +/* +=================================================================== + +EVENTS AND JOURNALING + +In addition to these events, .cfg files are also copied to the +journaled file +=================================================================== +*/ + +#define MAX_PUSHED_EVENTS 64 +int com_pushedEventsHead, com_pushedEventsTail; +sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS]; + +/* +================= +Com_GetRealEvent +================= +*/ +sysEvent_t Com_GetRealEvent( void ) { + sysEvent_t ev; + + // get an event from the system + ev = Sys_GetEvent(); + + return ev; +} + +/* +================= +Com_PushEvent +================= +*/ +void Com_PushEvent( sysEvent_t *event ) { + sysEvent_t *ev; + static int printedWarning; + + ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ]; + + if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) { + + // don't print the warning constantly, or it can give time for more... + if ( !printedWarning ) { + printedWarning = qtrue; + Com_Printf( "WARNING: Com_PushEvent overflow\n" ); + } + + if ( ev->evPtr ) { + Z_Free( ev->evPtr ); + } + com_pushedEventsTail++; + } else { + printedWarning = qfalse; + } + + *ev = *event; + com_pushedEventsHead++; +} + +/* +================= +Com_GetEvent +================= +*/ +sysEvent_t Com_GetEvent( void ) { + if ( com_pushedEventsHead > com_pushedEventsTail ) { + com_pushedEventsTail++; + return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ]; + } + return Com_GetRealEvent(); +} + +/* +================= +Com_RunAndTimeServerPacket +================= +*/ +void Com_RunAndTimeServerPacket( netadr_t *evFrom, msg_t *buf ) { + int t1, t2, msec; + + t1 = 0; + + if ( com_speeds->integer ) { + t1 = Sys_Milliseconds (); + } + + SV_PacketEvent( *evFrom, buf ); + + if ( com_speeds->integer ) { + t2 = Sys_Milliseconds (); + msec = t2 - t1; + if ( com_speeds->integer == 3 ) { + Com_Printf( "SV_PacketEvent time: %i\n", msec ); + } + } +} + +/* +================= +Com_EventLoop + +Returns last event time +================= +*/ +int Com_EventLoop( void ) { + sysEvent_t ev; + netadr_t evFrom; + byte bufData[MAX_MSGLEN]; + msg_t buf; + + MSG_Init( &buf, bufData, sizeof( bufData ) ); + + while ( 1 ) { + ev = Com_GetEvent(); + + // if no more events are available + if ( ev.evType == SE_NONE ) { + // manually send packet events for the loopback channel + while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) { + CL_PacketEvent( evFrom, &buf ); + } + + while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) { + // if the server just shut down, flush the events + if ( com_sv_running->integer ) { + Com_RunAndTimeServerPacket( &evFrom, &buf ); + } + } + + return ev.evTime; + } + + + switch ( ev.evType ) { + default: + Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evTime ); + break; + case SE_NONE: + break; + case SE_KEY: + CL_KeyEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_CHAR: + CL_CharEvent( ev.evValue ); + break; + case SE_MOUSE: + CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_JOYSTICK_AXIS: + CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_CONSOLE: + Cbuf_AddText( (char *)ev.evPtr ); + Cbuf_AddText( "\n" ); + break; + case SE_PACKET: + evFrom = *(netadr_t *)ev.evPtr; + buf.cursize = ev.evPtrLength - sizeof( evFrom ); + + // we must copy the contents of the message out, because + // the event buffers are only large enough to hold the + // exact payload, but channel messages need to be large + // enough to hold fragment reassembly + if ( (unsigned)buf.cursize > buf.maxsize ) { + Com_Printf("Com_EventLoop: oversize packet\n"); + continue; + } + memcpy( buf.data, (byte *)((netadr_t *)ev.evPtr + 1), buf.cursize ); + if ( com_sv_running->integer ) { + Com_RunAndTimeServerPacket( &evFrom, &buf ); + } else { + CL_PacketEvent( evFrom, &buf ); + } + break; + } + + // free any block data + if ( ev.evPtr ) { + Z_Free( ev.evPtr ); + } + } +} + +/* +================ +Com_Milliseconds + +Can be used for profiling, but will be journaled accurately +================ +*/ +int Com_Milliseconds (void) { + sysEvent_t ev; + + // get events and push them until we get a null event with the current time + do { + + ev = Com_GetRealEvent(); + if ( ev.evType != SE_NONE ) { + Com_PushEvent( &ev ); + } + } while ( ev.evType != SE_NONE ); + + return ev.evTime; +} + +//============================================================================ + +/* +============= +Com_Error_f + +Just throw a fatal error to +test error shutdown procedures +============= +*/ +static void Com_Error_f (void) { + if ( Cmd_Argc() > 1 ) { + Com_Error( ERR_DROP, "Testing drop error" ); + } else { + Com_Error( ERR_FATAL, "Testing fatal error" ); + } +} + + +/* +============= +Com_Freeze_f + +Just freeze in place for a given number of seconds to test +error recovery +============= +*/ +static void Com_Freeze_f (void) { + float s; + int start, now; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "freeze \n" ); + return; + } + s = atof( Cmd_Argv(1) ); + + start = Com_Milliseconds(); + + while ( 1 ) { + now = Com_Milliseconds(); + if ( ( now - start ) * 0.001 > s ) { + break; + } + } +} + +/* +================= +Com_Crash_f + +A way to force a bus error for development reasons +================= +*/ +static void Com_Crash_f( void ) { + * ( int * ) 0 = 0x12345678; +} + +/* +================= +Com_Init +================= +*/ +extern void Com_InitZoneMemory(); +extern void R_InitWorldEffects(); +void Com_Init( char *commandLine ) { + char *s; + + Com_Printf( "%s %s %s\n", Q3_VERSION, CPUSTRING, __DATE__ ); + + try { + // Grab the user's langauge preference from the dashboard right away! + // We only support french/german/english (with english as default) + g_dwLanguage = XGetLanguage(); + if( g_dwLanguage != XC_LANGUAGE_FRENCH && g_dwLanguage != XC_LANGUAGE_GERMAN ) + g_dwLanguage = XC_LANGUAGE_ENGLISH; + + // prepare enough of the subsystems to handle + // cvar and command buffer management + Com_ParseCommandLine( commandLine ); + + Swap_Init (); + Cbuf_Init (); + + Com_InitZoneMemory(); + +#ifdef _XBOX + WF_Init(); + // set up ri + extern void CL_InitRef( void ); + CL_InitRef(); + + // register renderer cvars + extern void R_Register(void); + R_Register(); + + // start the gl render layer + extern void GLimp_Init(void); + GLimp_Init(); + + // put up the license screen + SP_DoLicense(); +#endif + + Cmd_Init (); + Cvar_Init (); + + // get the commandline cvars set + Com_StartupVariable( NULL ); + + // done early so bind command exists + CL_InitKeyCommands(); + +#ifdef _XBOX + extern void Sys_FilecodeScan_f(); + Sys_InitFileCodes(); + Cmd_AddCommand("filecodes", Sys_FilecodeScan_f); + + extern void Sys_StreamInit(); + Sys_StreamInit(); + + // This just forces the static singleton in the function to call + // its constructor, which allocates a stupid 12 byte block of + // memory that never gets freed. Otherwise, it ends up stranded in + // the middle of the zone: + TheGhoul2InfoArray(); +#endif + + FS_InitFilesystem (); //uses z_malloc + R_InitWorldEffects(); // this doesn't do much but I want to be sure certain variables are intialized. + + Cbuf_AddText ("exec default.cfg\n"); + + // skip the jaconfig.cfg if "safe" is on the command line + if ( !Com_SafeMode() ) { + Cbuf_AddText ("exec jaconfig.cfg\n"); + } + + Cbuf_AddText ("exec autoexec.cfg\n"); + + Cbuf_Execute (); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + // allocate the stack based hunk allocator + Com_InitHunkMemory(); + + // if any archived cvars are modified after this, we will trigger a writing + // of the config file + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + // + // init commands and vars + // + Cmd_AddCommand ("quit", Com_Quit_f); + Cmd_AddCommand ("writeconfig", Com_WriteConfig_f ); + + com_maxfps = Cvar_Get ("com_maxfps", "85", CVAR_ARCHIVE); + + com_developer = Cvar_Get ("developer", "0", CVAR_TEMP ); + com_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP ); + com_speedslog = Cvar_Get ("speedslog", "0", CVAR_TEMP ); + + com_timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT ); + com_fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT); + com_showtrace = Cvar_Get ("com_showtrace", "0", CVAR_CHEAT); + com_terrainPhysics = Cvar_Get ("com_terrainPhysics", "1", CVAR_CHEAT); + com_viewlog = Cvar_Get( "viewlog", "0", CVAR_TEMP ); + com_speeds = Cvar_Get ("com_speeds", "0", 0); + +#ifdef G2_PERFORMANCE_ANALYSIS + com_G2Report = Cvar_Get("com_G2Report", "0", 0); +#endif + + cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM); + sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM); + com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM); + com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM); + com_skippingcin = Cvar_Get ("skippingCinematic", "0", CVAR_ROM); + com_buildScript = Cvar_Get( "com_buildScript", "0", 0 ); + + if ( com_developer && com_developer->integer ) { + Cmd_AddCommand ("error", Com_Error_f); + Cmd_AddCommand ("crash", Com_Crash_f ); + Cmd_AddCommand ("freeze", Com_Freeze_f); + } + + s = va("%s %s %s", Q3_VERSION, CPUSTRING, __DATE__ ); + com_version = Cvar_Get ("version", s, CVAR_ROM | CVAR_SERVERINFO ); + + + // So any controller can skip the logo movies: + inSplashMenu = Cvar_Get( "inSplashMenu", "1", 0 ); + controllerOut= Cvar_Get( "ControllerOutNum", "-1", 0); + +#ifdef XBOX_DEMO + // Cvar used to hide "QUIT TO DEMOS MENU" options if we weren't started by CDX + extern bool demoLaunchDataValid; + if( demoLaunchDataValid ) + Cvar_SetValue( "ui_allowDemoQuit", 1 ); + else + Cvar_SetValue( "ui_allowDemoQuit", 0 ); +#endif + + SE_Init(); // Initialize StringEd + + Sys_Init(); // this also detects CPU type, so I can now do this CPU check below... + + Netchan_Init( Com_Milliseconds() & 0xffff ); // pick a port value that should be nice and random +// VM_Init(); + SV_Init(); + + CL_Init(); + +#ifdef _XBOX + // Experiment. Sound memory never gets freed, move it earlier. This + // will also let us play movies sooner, if we need to. + extern void CL_StartSound(void); + CL_StartSound(); +#endif + + Sys_ShowConsole( com_viewlog->integer, qfalse ); + + // set com_frameTime so that if a map is started on the + // command line it will still be able to count on com_frameTime + // being random enough for a serverid + com_frameTime = Com_Milliseconds(); + + // add + commands from command line +#ifndef _XBOX + if ( !Com_AddStartupCommands() ) { +#ifdef NDEBUG + // if the user didn't give any commands, run default action +// if ( !com_dedicated->integer ) + { + Cbuf_AddText ("cinematic openinglogos\n"); +// if( !com_introPlayed->integer ) { +// Cvar_Set( com_introPlayed->name, "1" ); +// Cvar_Set( "nextmap", "cinematic intro" ); +// } + } +#endif + } +#endif + com_fullyInitialized = qtrue; + Com_Printf ("--- Common Initialization Complete ---\n"); + +//HACKERY FOR THE DEUTSCH + //if ( (Cvar_VariableIntegerValue("ui_iscensored") == 1) //if this was on before, set it again so it gets its flags + // ) + //{ + // Cvar_Get( "ui_iscensored", "1", CVAR_ARCHIVE|CVAR_ROM|CVAR_INIT|CVAR_CHEAT|CVAR_NORESTART); + // Cvar_Set( "ui_iscensored", "1"); //just in case it was archived + // // NOTE : I also create this in UI_Init() + // Cvar_Get( "g_dismemberment", "0", CVAR_ARCHIVE|CVAR_ROM|CVAR_INIT|CVAR_CHEAT); + // Cvar_Set( "g_dismemberment", "0"); //just in case it was archived + //} + } + + catch (const char* reason) { + Sys_Error ("Error during initialization %s", reason); + } + +#ifdef _XBOX + //Load these early to keep them at the beginning of memory. Perhaps + //here is too early though. After the license screen would be better. + extern void SE_CheckForLanguageUpdates(void); + SE_CheckForLanguageUpdates(); +#endif + +} + +//================================================================== + +void Com_WriteConfigToFile( const char *filename ) { +#ifndef _XBOX + fileHandle_t f; + + f = FS_FOpenFileWrite( filename ); + if ( !f ) { + Com_Printf ("Couldn't write %s.\n", filename ); + return; + } + + FS_Printf (f, "// generated by Star Wars Jedi Academy, do not modify\n"); + Key_WriteBindings (f); + Cvar_WriteVariables (f); + FS_FCloseFile( f ); +#endif +} + + +/* +=============== +Com_WriteConfiguration + +Writes key bindings and archived cvars to config file if modified +=============== +*/ +void Com_WriteConfiguration( void ) { + // if we are quiting without fully initializing, make sure + // we don't write out anything + if ( !com_fullyInitialized ) { + return; + } + + if ( !(cvar_modifiedFlags & CVAR_ARCHIVE ) ) { + return; + } + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + Com_WriteConfigToFile( "jaconfig.cfg" ); +} + + +/* +=============== +Com_WriteConfig_f + +Write the config file to a specific name +=============== +*/ +void Com_WriteConfig_f( void ) { + char filename[MAX_QPATH]; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: writeconfig \n" ); + return; + } + + Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + Com_Printf( "Writing %s.\n", filename ); + Com_WriteConfigToFile( filename ); +} + +/* +================ +Com_ModifyMsec +================ +*/ + + +int Com_ModifyMsec( int msec, float &fraction ) +{ + int clampTime; + + fraction=0.0f; + + // + // modify time for debugging values + // + if ( com_fixedtime->integer ) + { + msec = com_fixedtime->integer; + } + else if ( com_timescale->value ) + { + fraction=(float)msec; + fraction*=com_timescale->value; + msec=(int)floor(fraction); + fraction-=(float)msec; + } + + // don't let it scale below 1 msec + if ( msec < 1 ) + { + msec = 1; + fraction=0.0f; + } + + if ( com_skippingcin->integer ) { + // we're skipping ahead so let it go a bit faster + clampTime = 500; + } else { + // for local single player gaming + // we may want to clamp the time to prevent players from + // flying off edges when something hitches. + clampTime = 200; + } + + if ( msec > clampTime ) { + msec = clampTime; + fraction=0.0f; + } + + return msec; +} + +/* +================= +Com_Frame +================= +*/ +static vec3_t corg; +static vec3_t cangles; +static bool bComma; +void Com_SetOrgAngles(vec3_t org,vec3_t angles) +{ + VectorCopy(org,corg); + VectorCopy(angles,cangles); +} + +#ifdef G2_PERFORMANCE_ANALYSIS +void G2Time_ResetTimers(void); +void G2Time_ReportTimers(void); +#endif + +#pragma warning (disable: 4701) //local may have been used without init (timing info vars) +void Com_Frame( void ) { +try +{ + int timeBeforeFirstEvents, timeBeforeServer, timeBeforeEvents, timeBeforeClient, timeAfter; + int msec, minMsec; + static int lastTime; + + // write config file if anything changed +#ifndef _XBOX + Com_WriteConfiguration(); + + // if "viewlog" has been modified, show or hide the log console + if ( com_viewlog->modified ) { + Sys_ShowConsole( com_viewlog->integer, qfalse ); + com_viewlog->modified = qfalse; + } +#endif + + // + // main event loop + // + if ( com_speeds->integer ) { + timeBeforeFirstEvents = Sys_Milliseconds (); + } + + // we may want to spin here if things are going too fast + if ( com_maxfps->integer > 0 ) { + minMsec = 1000 / com_maxfps->integer; + } else { + minMsec = 1; + } + do { + com_frameTime = Com_EventLoop(); + if ( lastTime > com_frameTime ) { + lastTime = com_frameTime; // possible on first frame + } + msec = com_frameTime - lastTime; + } while ( msec < minMsec ); + Cbuf_Execute (); + + lastTime = com_frameTime; + + // mess with msec if needed + com_frameMsec = msec; + float fractionMsec=0.0f; + msec = Com_ModifyMsec( msec, fractionMsec); + + // + // server side + // + if ( com_speeds->integer ) { + timeBeforeServer = Sys_Milliseconds (); + } + + SV_Frame (msec, fractionMsec); + + + // + // client system + // +#ifdef _XBOX + extern bool TestDemoTimer(); + extern void PlayDemo(); + if ( TestDemoTimer()) + { + PlayDemo(); + } +#endif +// if ( !com_dedicated->integer ) + { + // + // run event loop a second time to get server to client packets + // without a frame of latency + // + if ( com_speeds->integer ) { + timeBeforeEvents = Sys_Milliseconds (); + } + Com_EventLoop(); + Cbuf_Execute (); + + + // + // client side + // + if ( com_speeds->integer ) { + timeBeforeClient = Sys_Milliseconds (); + } + + CL_Frame (msec, fractionMsec); + + if ( com_speeds->integer ) { + timeAfter = Sys_Milliseconds (); + } + } + + + // + // report timing information + // + if ( com_speeds->integer ) { + int all, sv, ev, cl; + + all = timeAfter - timeBeforeServer; + sv = timeBeforeEvents - timeBeforeServer; + ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents; + cl = timeAfter - timeBeforeClient; + sv -= time_game; + cl -= time_frontend + time_backend; + + Com_Printf("fr:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i tr:%3i pvs:%3i rf:%3i bk:%3i\n", + com_frameNumber, all, sv, ev, cl, time_game, timeInTrace, timeInPVSCheck, time_frontend, time_backend); + +#ifndef _XBOX + // speedslog + if ( com_speedslog && com_speedslog->integer ) + { + if(!speedslog) + { + speedslog = FS_FOpenFileWrite("speeds.log"); + FS_Write("data={\n", strlen("data={\n"), speedslog); + bComma=false; + if ( com_speedslog->integer > 1 ) + { + // force it to not buffer so we get valid + // data even if we are crashing + FS_ForceFlush(logfile); + } + } + if (speedslog) + { + char msg[MAXPRINTMSG]; + + if(bComma) + { + FS_Write(",\n", strlen(",\n"), speedslog); + bComma=false; + } + FS_Write("{", strlen("{"), speedslog); + Com_sprintf(msg,sizeof(msg), + "%8.4f,%8.4f,%8.4f,%8.4f,%8.4f,%8.4f,",corg[0],corg[1],corg[2],cangles[0],cangles[1],cangles[2]); + FS_Write(msg, strlen(msg), speedslog); + Com_sprintf(msg,sizeof(msg), + "%i,%3i,%3i,%3i,%3i,%3i,%3i,%3i,%3i,%3i}", + com_frameNumber, all, sv, ev, cl, time_game, timeInTrace, timeInPVSCheck, time_frontend, time_backend); + FS_Write(msg, strlen(msg), speedslog); + bComma=true; + } + } +#endif + + timeInTrace = timeInPVSCheck = 0; + } + + // + // trace optimization tracking + // + if ( com_showtrace->integer ) { + extern int c_traces, c_brush_traces, c_patch_traces; + extern int c_pointcontents; + + /* + Com_Printf( "%4i non-sv_traces, %4i sv_traces, %4i ms, ave %4.2f ms\n", c_traces - numTraces, numTraces, timeInTrace, (float)timeInTrace/(float)numTraces ); + timeInTrace = numTraces = 0; + c_traces = 0; + */ + + Com_Printf ("%4i traces (%ib %ip) %4i points\n", c_traces, + c_brush_traces, c_patch_traces, c_pointcontents); + c_traces = 0; + c_brush_traces = 0; + c_patch_traces = 0; + c_pointcontents = 0; + } + + com_frameNumber++; +}//try + catch (const char* reason) { + Com_Printf (reason); + return; // an ERR_DROP was thrown + } + +#ifdef G2_PERFORMANCE_ANALYSIS + if (com_G2Report && com_G2Report->integer) + { + G2Time_ReportTimers(); + } + + G2Time_ResetTimers(); +#endif + + Cvar_Get("levelSelectCheat", "-1", CVAR_SAVEGAME | CVAR_ARCHIVE); + +#ifdef XBOX_DEMO + // This is for the code that auto-reboots back to CDX after a timeout: + extern void Demo_TimerUpdate( void ); + Demo_TimerUpdate(); +#endif +} + +#pragma warning (default: 4701) //local may have been used without init + +/* +================= +Com_Shutdown +================= +*/ +extern void CM_FreeShaderText(void); +void Com_Shutdown (void) { + CM_ClearMap(); + +#ifndef _XBOX + CM_FreeShaderText(); + + if (logfile) { + FS_FCloseFile (logfile); + logfile = 0; + } + + if (speedslog) { + FS_Write("\n};", strlen("\n};"), speedslog); + FS_FCloseFile (speedslog); + speedslog = 0; + } + + if (camerafile) { + FS_FCloseFile (camerafile); + camerafile = 0; + } + + if ( com_journalFile ) { + FS_FCloseFile( com_journalFile ); + com_journalFile = 0; + } +#endif + +#ifdef _XBOX + extern void Sys_StreamShutdown(); + Sys_StreamShutdown(); + Sys_ShutdownFileCodes(); +#endif + +// SE_ShutDown();//close the string packages + + extern void Netchan_Shutdown(); + Netchan_Shutdown(); +} + +/* +============ +ParseTextFile +============ +*/ + +bool Com_ParseTextFile(const char *file, class CGenericParser2 &parser, bool cleanFirst) +{ + fileHandle_t f; + int length = 0; + char *buf = 0, *bufParse = 0; + + length = FS_FOpenFileByMode( file, &f, FS_READ ); + if (!f || !length) + { + return false; + } + + buf = new char [length + 1]; + FS_Read( buf, length, f ); + buf[length] = 0; + + bufParse = buf; + parser.Parse(&bufParse, cleanFirst); + delete buf; + + FS_FCloseFile( f ); + + return true; +} + +void Com_ParseTextFileDestroy(class CGenericParser2 &parser) +{ + parser.Clean(); +} + +CGenericParser2 *Com_ParseTextFile(const char *file, bool cleanFirst, bool writeable) +{ + fileHandle_t f; + int length = 0; + char *buf = 0, *bufParse = 0; + CGenericParser2 *parse; + + length = FS_FOpenFileByMode( file, &f, FS_READ ); + if (!f || !length) + { + return 0; + } + + buf = new char [length + 1]; + FS_Read( buf, length, f ); + FS_FCloseFile( f ); + buf[length] = 0; + + bufParse = buf; + + parse = new CGenericParser2; + if (!parse->Parse(&bufParse, cleanFirst, writeable)) + { + delete parse; + parse = 0; + } + + delete buf; + + return parse; +} + diff --git a/code/qcommon/cvar.cpp b/code/qcommon/cvar.cpp new file mode 100644 index 0000000..b9c1a97 --- /dev/null +++ b/code/qcommon/cvar.cpp @@ -0,0 +1,951 @@ +// cvar.c -- dynamic variable tracking + +#include "../game/q_shared.h" +#include "qcommon.h" + +cvar_t *cvar_vars; +cvar_t *cvar_cheats; +int cvar_modifiedFlags; + +#define MAX_CVARS 1024 +cvar_t cvar_indexes[MAX_CVARS]; +int cvar_numIndexes; + + +cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force); + +static char *lastMemPool = NULL; +static int memPoolSize; + + +//If the string came from the memory pool, don't really free it. The entire +//memory pool will be wiped during the next level load. +static void Cvar_FreeString(char *string) +{ + if(!lastMemPool || string < lastMemPool || + string >= lastMemPool + memPoolSize) { + Z_Free(string); + } +} + +/* +============ +Cvar_ValidateString +============ +*/ +static qboolean Cvar_ValidateString( const char *s ) { + if ( !s ) { + return qfalse; + } + if ( strchr( s, '\\' ) ) { + return qfalse; + } + if ( strchr( s, '\"' ) ) { + return qfalse; + } + if ( strchr( s, ';' ) ) { + return qfalse; + } + return qtrue; +} + +/* +============ +Cvar_FindVar +============ +*/ +static cvar_t *Cvar_FindVar( const char *var_name ) { + cvar_t *var; + + for (var=cvar_vars ; var ; var=var->next) { + if (!Q_stricmp(var_name, var->name)) { + return var; + } + } + + return NULL; +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValue( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return 0; + return var->value; +} + + +/* +============ +Cvar_VariableIntegerValue +============ +*/ +int Cvar_VariableIntegerValue( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return 0; + return var->integer; +} + + +/* +============ +Cvar_VariableString +============ +*/ +char *Cvar_VariableString( const char *var_name ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) + return ""; + return var->string; +} + + +/* +============ +Cvar_VariableStringBuffer +============ +*/ +void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + cvar_t *var; + + var = Cvar_FindVar (var_name); + if (!var) { + *buffer = 0; + } + else { + Q_strncpyz( buffer, var->string, bufsize ); + } +} + + +/* +============ +Cvar_CompleteVariable +============ +*/ +char *Cvar_CompleteVariable( const char *partial ) { + cvar_t *cvar; + int len; + + len = strlen(partial); + + if ( !len ) { + return NULL; + } + + // check partial match + for ( cvar=cvar_vars ; cvar ; cvar=cvar->next ) { + if ( !Q_stricmpn (partial,cvar->name, len) ) { + if ( (cvar->flags & CVAR_CHEAT) && !cvar_cheats->integer ) { + continue; + } + else { + return cvar->name; + } + } + } + + return NULL; +} + +/* +============ +Cvar_CompleteVariableNext - get the next cvar in alphabetical order. + +============ +*/ +char *Cvar_CompleteVariableNext (char *partial, char *last) +{ + cvar_t *cvar, *base; + int len; + + len = strlen(partial); + + if (!len) + return NULL; + + // this check needed since cvars may be resetting from cmd searches + base = NULL; + if(last) + { + for (cvar=cvar_vars; cvar; cvar = cvar->next) + { + if(!Q_stricmp(last, cvar->name)) + { + base = cvar->next; + break; + } + } + if(base == NULL) + { //not found, either error or at end of list + return NULL; + } + } + else + { + base = cvar_vars; + } + + // check partial match + for (cvar=base ; cvar ; cvar=cvar->next) + { + if (!Q_stricmpn (partial,cvar->name, len)) { + if ( (cvar->flags & CVAR_CHEAT) && !cvar_cheats->integer ) { + continue; + } + else { + return cvar->name; + } + } + } + + return NULL; +} + +/* +============ +Cvar_Get + +If the variable already exists, the value will not be set unless CVAR_ROM +The flags will be or'ed in if the variable exists. +============ +*/ +cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) { + cvar_t *var; + + if ( !var_name || ! var_value ) { + Com_Error( ERR_FATAL, "Cvar_Get: NULL parameter" ); + } + + if ( !Cvar_ValidateString( var_name ) ) { + Com_Printf("invalid cvar name string: %s\n", var_name ); + var_name = "BADNAME"; + } + +#if 0 // FIXME: values with backslash happen + if ( !Cvar_ValidateString( var_value ) ) { + Com_Printf("invalid cvar value string: %s\n", var_value ); + var_value = "BADVALUE"; + } +#endif + + var = Cvar_FindVar (var_name); + if ( var ) { + // if the C code is now specifying a variable that the user already + // set a value for, take the new value as the reset value + if ( ( var->flags & CVAR_USER_CREATED ) && !( flags & CVAR_USER_CREATED ) + && var_value[0] ) { + var->flags &= ~CVAR_USER_CREATED; + Cvar_FreeString( var->resetString ); + var->resetString = CopyString( var_value ); + + // ZOID--needs to be set so that cvars the game sets as + // SERVERINFO get sent to clients + cvar_modifiedFlags |= flags; + } + + var->flags |= flags; + // only allow one non-empty reset string without a warning + if ( !var->resetString[0] ) { + // we don't have a reset string yet + Cvar_FreeString( var->resetString ); + var->resetString = CopyString( var_value ); + } else if ( var_value[0] && strcmp( var->resetString, var_value ) ) { + Com_Printf( "Warning: cvar \"%s\" given initial values: \"%s\" and \"%s\"\n", + var_name, var->resetString, var_value ); + } + // if we have a latched string, take that value now + if ( var->latchedString ) { + char *s; + + s = var->latchedString; + var->latchedString = NULL; // otherwise cvar_set2 would free it + Cvar_Set2( var_name, s, qtrue ); + Cvar_FreeString( s ); + } + +// use a CVAR_SET for rom sets, get won't override +#if 0 + // CVAR_ROM always overrides + if ( flags & CVAR_ROM ) { + Cvar_Set2( var_name, var_value, qtrue ); + } +#endif + return var; + } + + // + // allocate a new cvar + // + if ( cvar_numIndexes == MAX_CVARS ) { + Com_Error( ERR_FATAL, "MAX_CVARS" ); + } + var = &cvar_indexes[cvar_numIndexes]; + cvar_numIndexes++; + var->name = CopyString (var_name); + var->string = CopyString (var_value); + var->modified = qtrue; + var->modificationCount = 1; + var->value = atof (var->string); + var->integer = atoi(var->string); + var->resetString = CopyString( var_value ); + + // link the variable in + var->next = cvar_vars; + cvar_vars = var; + + var->flags = flags; + + return var; +} + +/* +============ +Cvar_Set2 +============ +*/ +cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force ) { + cvar_t *var; + + Com_DPrintf( "Cvar_Set2: %s %s\n", var_name, value ); + + if ( !Cvar_ValidateString( var_name ) ) { + Com_Printf("invalid cvar name string: %s\n", var_name ); + var_name = "BADNAME"; + } + +#if 0 // FIXME + if ( value && !Cvar_ValidateString( value ) ) { + Com_Printf("invalid cvar value string: %s\n", value ); + var_value = "BADVALUE"; + } +#endif + + var = Cvar_FindVar (var_name); + if (!var) { + if ( !value ) { + return NULL; + } + // create it + if ( !force ) { + return Cvar_Get( var_name, value, CVAR_USER_CREATED ); + } else { + return Cvar_Get (var_name, value, 0); + } + } + + if (!value ) { + value = var->resetString; + } + + // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) + cvar_modifiedFlags |= var->flags; + + if (!force) + { + if (var->flags & CVAR_ROM) + { + Com_Printf ("%s is read only.\n", var_name); + return var; + } + + if (var->flags & CVAR_INIT) + { + Com_Printf ("%s is write protected.\n", var_name); + return var; + } + + if (var->flags & CVAR_LATCH) + { + if (var->latchedString) + { + if (strcmp(value, var->latchedString) == 0) + return var; + Cvar_FreeString (var->latchedString); + } + else + { + if (strcmp(value, var->string) == 0) + return var; + } + + Com_Printf ("%s will be changed upon restarting.\n", var_name); + var->latchedString = CopyString(value); + var->modified = qtrue; + var->modificationCount++; + return var; + } + + if ( (var->flags & CVAR_CHEAT) && !cvar_cheats->integer ) + { + Com_Printf ("%s is cheat protected.\n", var_name); + return var; + } + + } + else + { + if (var->latchedString) + { + Cvar_FreeString (var->latchedString); + var->latchedString = NULL; + } + } + + if (!strcmp(value, var->string)) + return var; // not changed + + var->modified = qtrue; + var->modificationCount++; + + Cvar_FreeString (var->string); // free the old value string + + var->string = CopyString(value); + var->value = atof (var->string); + var->integer = atoi (var->string); + + return var; +} + +/* +============ +Cvar_Set +============ +*/ +void Cvar_Set( const char *var_name, const char *value) { + Cvar_Set2 (var_name, value, qtrue); +} + + +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValue( const char *var_name, float value) { + char val[32]; + + if ( value == (int)value ) { + Com_sprintf (val, sizeof(val), "%i",(int)value); + } else { + Com_sprintf (val, sizeof(val), "%f",value); + } + Cvar_Set (var_name, val); +} + + +/* +============ +Cvar_Reset +============ +*/ +void Cvar_Reset( const char *var_name ) { + Cvar_Set2( var_name, NULL, qfalse ); +} + + +/* +============ +Cvar_SetCheatState + +Any testing variables will be reset to the safe values +============ +*/ +void Cvar_SetCheatState( void ) { + cvar_t *var; + + // set all default vars to the safe value + for ( var = cvar_vars ; var ; var = var->next ) { + if ( var->flags & CVAR_CHEAT) { + Cvar_Set( var->name, var->resetString ); + } + } +} + +/* +============ +Cvar_Command + +Handles variable inspection and changing from the console +============ +*/ +qboolean Cvar_Command( void ) { + cvar_t *v; + + // check variables + v = Cvar_FindVar (Cmd_Argv(0)); + if (!v) { + return qfalse; + } + + // perform a variable print or set + if ( Cmd_Argc() == 1 ) { + Com_Printf ("\"%s\" is:\"%s" S_COLOR_WHITE "\" default:\"%s" S_COLOR_WHITE "\"\n", v->name, v->string, v->resetString ); + if ( v->latchedString ) { + Com_Printf( "latched: \"%s\"\n", v->latchedString ); + } + return qtrue; + } + +//JFM toggle test + char *value; + value = Cmd_Argv(1); + if (value[0] =='!') //toggle + { + char buff[5]; + sprintf(buff,"%i",!v->value); + Cvar_Set2 (v->name, buff, qfalse);// toggle the value + } + else + Cvar_Set2 (v->name, value, qfalse);// set the value if forcing isn't required + + return qtrue; +} + + +/* +============ +Cvar_Toggle_f + +Toggles a cvar for easy single key binding +============ +*/ +void Cvar_Toggle_f( void ) { + int v; + + if ( Cmd_Argc() != 2 ) { + Com_Printf ("usage: toggle \n"); + return; + } + + v = Cvar_VariableIntegerValue( Cmd_Argv( 1 ) ); + v = !v; + + Cvar_Set2 (Cmd_Argv(1), va("%i", v), qfalse); +} + +/* +============ +Cvar_Set_f + +Allows setting and defining of arbitrary cvars from console, even if they +weren't declared in C code. +============ +*/ +void Cvar_Set_f( void ) { + int i, c, l, len; + char combined[MAX_STRING_TOKENS]; + + c = Cmd_Argc(); + if ( c < 3 ) { + Com_Printf ("usage: set \n"); + return; + } + + combined[0] = 0; + l = 0; + for ( i = 2 ; i < c ; i++ ) { + len = strlen ( Cmd_Argv( i ) + 1 ); + if ( l + len >= MAX_STRING_TOKENS - 2 ) { + break; + } + strcat( combined, Cmd_Argv( i ) ); + if ( i != c-1 ) { + strcat( combined, " " ); + } + l += len; + } + Cvar_Set2 (Cmd_Argv(1), combined, qfalse); +} + +/* +============ +Cvar_SetU_f + +As Cvar_Set, but also flags it as userinfo +============ +*/ +void Cvar_SetU_f( void ) { + cvar_t *v; + + if ( Cmd_Argc() != 3 ) { + Com_Printf ("usage: setu \n"); + return; + } + Cvar_Set_f(); + v = Cvar_FindVar( Cmd_Argv( 1 ) ); + if ( !v ) { + return; + } + v->flags |= CVAR_USERINFO; +} + +/* +============ +Cvar_SetS_f + +As Cvar_Set, but also flags it as userinfo +============ +*/ +void Cvar_SetS_f( void ) { + cvar_t *v; + + if ( Cmd_Argc() != 3 ) { + Com_Printf ("usage: sets \n"); + return; + } + Cvar_Set_f(); + v = Cvar_FindVar( Cmd_Argv( 1 ) ); + if ( !v ) { + return; + } + v->flags |= CVAR_SERVERINFO; +} + +/* +============ +Cvar_SetA_f + +As Cvar_Set, but also flags it as archived +============ +*/ +void Cvar_SetA_f( void ) { + cvar_t *v; + + if ( Cmd_Argc() != 3 ) { + Com_Printf ("usage: seta \n"); + return; + } + Cvar_Set_f(); + v = Cvar_FindVar( Cmd_Argv( 1 ) ); + if ( !v ) { + return; + } + v->flags |= CVAR_ARCHIVE; +} + +/* +============ +Cvar_Reset_f +============ +*/ +void Cvar_Reset_f( void ) { + if ( Cmd_Argc() != 2 ) { + Com_Printf ("usage: reset \n"); + return; + } + Cvar_Reset( Cmd_Argv( 1 ) ); +} + +/* +============ +Cvar_WriteVariables + +Appends lines containing "set variable value" for all variables +with the archive flag set to qtrue. +============ +*/ +void Cvar_WriteVariables( fileHandle_t f ) { +#ifndef _XBOX + cvar_t *var; + char buffer[1024]; + + for (var = cvar_vars ; var ; var = var->next) { + if (var->flags & CVAR_ARCHIVE ) { + // write the latched value, even if it hasn't taken effect yet + if ( var->latchedString ) { + Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->latchedString); + } else { + Com_sprintf (buffer, sizeof(buffer), "seta %s \"%s\"\n", var->name, var->string); + } + FS_Printf (f, "%s", buffer); + } + } +#endif +} + +/* +============ +Cvar_List_f + +============ +*/ +void Cvar_List_f( void ) { + cvar_t *var; + int i; + char *match; + + if ( Cmd_Argc() > 1 ) { + match = Cmd_Argv( 1 ); + } else { + match = NULL; + } + + i = 0; + for (var = cvar_vars ; var ; var = var->next, i++) + { + if (match && !Com_Filter(match, var->name, qfalse)) continue; + + if (var->flags & CVAR_SERVERINFO) { + Com_Printf("S"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_USERINFO) { + Com_Printf("U"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_ROM) { + Com_Printf("R"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_INIT) { + Com_Printf("I"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_ARCHIVE) { + Com_Printf("A"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_LATCH) { + Com_Printf("L"); + } else { + Com_Printf(" "); + } + if (var->flags & CVAR_CHEAT) { + if (!cvar_cheats->integer) + { + i--; + continue; + } + Com_Printf("C"); + } else { + Com_Printf(" "); + } + + Com_Printf (" %s \"%s\"\n", var->name, var->string); + } + + Com_Printf ("\n%i total cvars\n", i); +} + +/* +============ +Cvar_Restart_f + +Resets all cvars to their hardcoded values +============ +*/ +void Cvar_Restart_f( void ) { + cvar_t *var; + cvar_t **prev; + + prev = &cvar_vars; + while ( 1 ) { + var = *prev; + if ( !var ) { + break; + } + + // don't mess with rom values, or some inter-module + // communication will get broken (com_cl_running, etc) + if ( var->flags & ( CVAR_ROM | CVAR_INIT | CVAR_NORESTART ) ) { + prev = &var->next; + continue; + } + + // throw out any variables the user created + if ( var->flags & CVAR_USER_CREATED ) { + *prev = var->next; + if ( var->name ) { + Cvar_FreeString( var->name ); + } + if ( var->string ) { + Cvar_FreeString( var->string ); + } + if ( var->latchedString ) { + Cvar_FreeString( var->latchedString ); + } + if ( var->resetString ) { + Cvar_FreeString( var->resetString ); + } + // clear the var completely, since we + // can't remove the index from the list + memset( var, 0, sizeof( var ) ); + continue; + } + Cvar_Set( var->name, var->resetString ); + prev = &var->next; + } +} + + + +/* +===================== +Cvar_InfoString +===================== +*/ +char *Cvar_InfoString( int bit ) { + static char info[MAX_INFO_STRING]; + cvar_t *var; + + info[0] = 0; + + for (var = cvar_vars ; var ; var = var->next) { + if (var->flags & bit) { + Info_SetValueForKey (info, var->name, var->string); + } + } + return info; +} + +/* +===================== +Cvar_InfoStringBuffer +===================== +*/ +void Cvar_InfoStringBuffer( int bit, char* buff, int buffsize ) { + Q_strncpyz(buff,Cvar_InfoString(bit),buffsize); +} + +/* +===================== +Cvar_Register + +basically a slightly modified Cvar_Get for the interpreted modules +===================== +*/ +void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { + cvar_t *cv; + + cv = Cvar_Get( varName, defaultValue, flags ); + if ( !vmCvar ) { + return; + } + vmCvar->handle = cv - cvar_indexes; + vmCvar->modificationCount = -1; + Cvar_Update( vmCvar ); +} + + +/* +===================== +Cvar_Register + +updates an interpreted modules' version of a cvar +===================== +*/ +void Cvar_Update( vmCvar_t *vmCvar ) { + cvar_t *cv; + + if ( (unsigned)vmCvar->handle >= cvar_numIndexes ) { + Com_Error( ERR_DROP, "Cvar_Update: handle out of range" ); + } + + cv = cvar_indexes + vmCvar->handle; + + if ( cv->modificationCount == vmCvar->modificationCount ) { + return; + } + if ( !cv->string ) { + return; // variable might have been cleared by a cvar_restart + } + vmCvar->modificationCount = cv->modificationCount; + Q_strncpyz( vmCvar->string, cv->string, sizeof( vmCvar->string ) ); + vmCvar->value = cv->value; + vmCvar->integer = cv->integer; +} + + +/* +============ +Cvar_Init + +Reads in all archived cvars +============ +*/ +void Cvar_Init (void) { + cvar_cheats = Cvar_Get("helpUsObi", "0", CVAR_SYSTEMINFO ); + + Cmd_AddCommand ("toggle", Cvar_Toggle_f); + Cmd_AddCommand ("set", Cvar_Set_f); + Cmd_AddCommand ("sets", Cvar_SetS_f); + Cmd_AddCommand ("setu", Cvar_SetU_f); + Cmd_AddCommand ("seta", Cvar_SetA_f); + Cmd_AddCommand ("reset", Cvar_Reset_f); + Cmd_AddCommand ("cvarlist", Cvar_List_f); + Cmd_AddCommand ("cvar_restart", Cvar_Restart_f); +} + + +static void Cvar_Realloc(char **string, char *memPool, int &memPoolUsed) +{ + if(string && *string) + { + char *temp = memPool + memPoolUsed; + strcpy(temp, *string); + memPoolUsed += strlen(*string) + 1; + Cvar_FreeString(*string); + *string = temp; + } +} + + +//Turns many small allocation blocks into one big one. +void Cvar_Defrag(void) +{ + cvar_t *var; + int totalMem = 0; + int nextMemPoolSize; + + for (var = cvar_vars; var; var = var->next) + { + if (var->name) { + totalMem += strlen(var->name) + 1; + } + if (var->string) { + totalMem += strlen(var->string) + 1; + } + if (var->resetString) { + totalMem += strlen(var->resetString) + 1; + } + if (var->latchedString) { + totalMem += strlen(var->latchedString) + 1; + } + } + + char *mem = (char*)Z_Malloc(totalMem, TAG_SMALL, qfalse); + nextMemPoolSize = totalMem; + totalMem = 0; + + for (var = cvar_vars; var; var = var->next) + { + Cvar_Realloc(&var->name, mem, totalMem); + Cvar_Realloc(&var->string, mem, totalMem); + Cvar_Realloc(&var->resetString, mem, totalMem); + Cvar_Realloc(&var->latchedString, mem, totalMem); + } + + if(lastMemPool) { + Z_Free(lastMemPool); + } + lastMemPool = mem; + memPoolSize = nextMemPoolSize; +} + diff --git a/code/qcommon/files.h b/code/qcommon/files.h new file mode 100644 index 0000000..af62610 --- /dev/null +++ b/code/qcommon/files.h @@ -0,0 +1,119 @@ +#ifndef __FILES_H +#define __FILES_H + + + +/* + Structures local to the files_* modules. +*/ + + + +#ifdef _XBOX +#include "../goblib/goblib.h" + +typedef int wfhandle_t; +#else +#include "../zlib32/zip.h" +#include "unzip.h" +#endif + + +#define MAX_ZPATH 256 +#define BASEGAME "base" + + + +typedef struct fileInPack_s { + char *name; // name of the file + unsigned long pos; // file info position in zip + struct fileInPack_s* next; // next file in the hash +} fileInPack_t; + +typedef struct { + char pakFilename[MAX_OSPATH]; // c:\quake3\base\asset0.pk3 +#ifndef _XBOX + unzFile handle; +#endif + int checksum; + int numfiles; + int hashSize; // hash table size (power of 2) + fileInPack_t* *hashTable; // hash table + fileInPack_t* buildBuffer; // buffer with the filenames etc. +} pack_t; + +typedef struct { + char path[MAX_OSPATH]; // c:\stvoy + char gamedir[MAX_OSPATH]; // base +} directory_t; + +typedef struct searchpath_s { + struct searchpath_s *next; + + pack_t *pack; // only one of pack / dir will be non NULL + directory_t *dir; +} searchpath_t; + + +#define MAX_FILE_HANDLES 16 + +typedef union qfile_gus { + FILE* o; +#ifndef _XBOX + unzFile z; +#endif +} qfile_gut; + +typedef struct qfile_us { + qfile_gut file; + qboolean unique; +} qfile_ut; + +typedef struct { + qfile_ut handleFiles; + qboolean handleSync; + int baseOffset; + int fileSize; + int zipFilePos; + qboolean zipFile; + char name[MAX_QPATH]; + +#ifdef _XBOX + GOBHandle ghandle; + qboolean gob; + qboolean used; + wfhandle_t whandle; +#endif +} fileHandleData_t; + + +extern fileHandleData_t fsh[MAX_FILE_HANDLES]; + +extern searchpath_t *fs_searchpaths; +extern char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators +extern cvar_t *fs_debug; +extern cvar_t *fs_basepath; +extern cvar_t *fs_cdpath; +extern cvar_t *fs_copyfiles; +extern cvar_t *fs_gamedirvar; +extern cvar_t *fs_restrict; +extern int fs_readCount; // total bytes read +extern int fs_loadCount; // total files read +extern int fs_packFiles; // total number of files in packs + + +void FS_Startup( const char *gameName ); +void FS_CreatePath(char *OSPath); +char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ); +char *FS_BuildOSPath( const char *qpath ); +char *FS_BuildOSPathUnMapped( const char *qpath ); +fileHandle_t FS_HandleForFile(void); +qboolean FS_FilenameCompare( const char *s1, const char *s2 ); +int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ); +void FS_Shutdown( void ); +void FS_SetRestrictions(void); +void FS_CheckInit(void); +void FS_ReplaceSeparators( char *path ); + + +#endif diff --git a/code/qcommon/files_common.cpp b/code/qcommon/files_common.cpp new file mode 100644 index 0000000..75560cd --- /dev/null +++ b/code/qcommon/files_common.cpp @@ -0,0 +1,605 @@ + +/***************************************************************************** + * name: files.c + * + * desc: handle based filesystem for Quake III Arena + * + * + *****************************************************************************/ + + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "files.h" + +/* +============================================================================= + +QUAKE3 FILESYSTEM + +All of Quake's data access is through a hierarchical file system, but the contents of +the file system can be transparently merged from several sources. + +A "qpath" is a reference to game file data. MAX_QPATH is 64 characters, which must include +a terminating zero. "..", "\\", and ":" are explicitly illegal in qpaths to prevent any +references outside the quake directory system. + +The "base path" is the path to the directory holding all the game directories and usually +the executable. It defaults to ".", but can be overridden with a "+set fs_basepath c:\quake3" +command line to allow code debugging in a different directory. Basepath cannot +be modified at all after startup. Any files that are created (demos, screenshots, +etc) will be created relative to the base path, so base path should usually be writable. + +The "cd path" is the path to an alternate hierarchy that will be searched if a file +is not located in the base path. A user can do a partial install that copies some +data to a base path created on their hard drive and leave the rest on the cd. Files +are never writen to the cd path. It defaults to a value set by the installer, like +"e:\quake3", but it can be overridden with "+set ds_cdpath g:\quake3". + +If a user runs the game directly from a CD, the base path would be on the CD. This +should still function correctly, but all file writes will fail (harmlessly). + + +The "base game" is the directory under the paths where data comes from by default, and +can be either "base" or "demo". + +The "current game" may be the same as the base game, or it may be the name of another +directory under the paths that should be searched for files before looking in the base game. +This is the basis for addons. + +Clients automatically set the game directory after receiving a gamestate from a server, +so only servers need to worry about +set fs_game. + +No other directories outside of the base game and current game will ever be referenced by +filesystem functions. + +To save disk space and speed loading, directory trees can be collapsed into zip files. +The files use a ".pk3" extension to prevent users from unzipping them accidentally, but +otherwise the are simply normal uncompressed zip files. A game directory can have multiple +zip files of the form "asset0.pk3", "pak1.pk3", etc. Zip files are searched in decending order +from the highest number to the lowest, and will always take precedence over the filesystem. +This allows a pk3 distributed as a patch to override all existing data. + +Because we will have updated executables freely available online, there is no point to +trying to restrict demo / oem versions of the game with code changes. Demo / oem versions +should be exactly the same executables as release versions, but with different data that +automatically restricts where game media can come from to prevent add-ons from working. + +After the paths are initialized, quake will look for the product.txt file. If not +found and verified, the game will run in restricted mode. In restricted mode, only +files contained in demo/asset0.pk3 will be available for loading, and only if the zip header is +verified to not have been modified. A single exception is made for jaconfig.cfg. Files +can still be written out in restricted mode, so screenshots and demos are allowed. +Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even +if there is a valid product.txt under the basepath or cdpath. + +If not running in restricted mode, and a file is not found in any local filesystem, +an attempt will be made to download it and save it under the base path. + +If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd +path, it will be copied over to the base path. This is a development aid to help build +test releases and to copy working sets over slow network links. +(If set to 2, copying will only take place if the two filetimes are NOT EQUAL) + + +The qpath "sound/newstuff/test.wav" would be searched for in the following places: + +base path + current game's zip files +base path + current game's directory +cd path + current game's zip files +cd path + current game's directory +base path + base game's zip files +base path + base game's directory +cd path + base game's zip files +cd path + base game's directory +server download, to be written to base path + current game's directory + + +The filesystem can be safely shutdown and reinitialized with different +basedir / cddir / game combinations, but all other subsystems that rely on it +(sound, video) must also be forced to restart. + +Because the same files are loaded by both the clip model (CM_) and renderer (TR_) +subsystems, a simple single-file caching scheme is used. The CM_ subsystems will +load the file with a request to cache. Only one file will be kept cached at a time, +so any models that are going to be referenced by both subsystems should alternate +between the CM_ load function and the ref load function. + + + + +TODO: A qpath that starts with a leading slash will always refer to the base game, even if another +game is currently active. This allows character models, skins, and sounds to be downloaded +to a common directory no matter which game is active. + + +How to prevent downloading zip files? +Pass pk3 file names in systeminfo, and download before FS_Restart()? + + + +Aborting a download disconnects the client from the server. + +How to mark files as downloadable? Commercial add-ons won't be downloadable. + +Non-commercial downloads will want to download the entire zip file. +the game would have to be reset to actually read the zip in + +Auto-update information + +Path separators + +Casing + + separate server gamedir and client gamedir, so if the user starts + a local game after having connected to a network game, it won't stick + with the network game. + + allow menu options for game selection? + +Read / write config to floppy option. + +Different version coexistance? + +When building a pak file, make sure a jaconfig.cfg isn't present in it, +or configs will never get loaded from disk! + + todo: + + downloading (outside fs?) + game directory passing and restarting + +============================================================================= + +*/ + +// if this is defined, the executable positively won't work with any paks other +// than the demo pak, even if productid is present. This is only used for our +// last demo release to prevent the mac and linux users from using the demo +// executable with the production windows pak before the mac/linux products +// hit the shelves a little later +//#define PRE_RELEASE_DEMO + + +char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators +cvar_t *fs_debug; +cvar_t *fs_basepath; +cvar_t *fs_cdpath; +cvar_t *fs_copyfiles; +cvar_t *fs_gamedirvar; +cvar_t *fs_restrict; +searchpath_t *fs_searchpaths; +int fs_readCount; // total bytes read +int fs_loadCount; // total files read +int fs_packFiles; // total number of files in packs + +qboolean initialized = qfalse; + + + + + +fileHandleData_t fsh[MAX_FILE_HANDLES]; + +void FS_CheckInit(void) +{ + if (!initialized) + { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } +} + + +/* +============== +FS_Initialized +============== +*/ + +qboolean FS_Initialized() { + return (qboolean)(fs_searchpaths != NULL); +} + + + +fileHandle_t FS_HandleForFile(void) { + int i; + + for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { +#ifdef _XBOX + if ( !fsh[i].used ) { +#else + if ( fsh[i].handleFiles.file.o == NULL ) { +#endif + return i; + } + } + + Com_Printf( "FS_HandleForFile: all handles taken:\n" ); + for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { + Com_Printf( "%d. %s\n", i, fsh[i].name); + } + Com_Error( ERR_DROP, "FS_HandleForFile: none free" ); + return 0; +} + + +/* +==================== +FS_ReplaceSeparators + +Fix things up differently for win/unix/mac +==================== +*/ +void FS_ReplaceSeparators( char *path ) { + char *s; + + for ( s = path ; *s ; s++ ) { + if ( *s == '/' || *s == '\\' ) { + *s = PATH_SEP; + } + } +} + +/* +=================== +FS_BuildOSPath + +Qpath may have either forward or backwards slashes +=================== +*/ + +char *FS_BuildOSPath( const char *qpath ) +{ + char temp[MAX_OSPATH]; + static char ospath[2][MAX_OSPATH]; + static int toggle; + + toggle ^= 1; // flip-flop to allow two returns without clash + + Com_sprintf( temp, sizeof(temp), "/%s/%s", fs_gamedirvar->string, qpath ); + + FS_ReplaceSeparators( temp ); + Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", + fs_basepath->string, temp ); + + return ospath[toggle]; +} + +char *FS_BuildOSPathUnMapped( const char *qpath ) +{ + char temp[MAX_OSPATH]; + static char ospath[2][MAX_OSPATH]; + static int toggle; + + toggle ^= 1; // flip-flop to allow two returns without clash + + Com_sprintf( temp, sizeof(temp), "/%s/%s", fs_gamedirvar->string, qpath ); + + FS_ReplaceSeparators( temp ); + Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", + "d:", temp ); + + return ospath[toggle]; +} + +#ifndef _XBOX +char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ) { + char temp[MAX_OSPATH]; + static char ospath[4][MAX_OSPATH]; + static int toggle; + + toggle = (++toggle)&3; // allows four returns without clash (increased from 2 during fs_copyfiles 2 enhancement) + + Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath ); + FS_ReplaceSeparators( temp ); + Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp ); + + return ospath[toggle]; +} +#endif + + +/* +============ +FS_CreatePath + +Creates any directories needed to store the given filename +============ +*/ +void FS_CreatePath (char *OSPath) { + char *ofs; + + // make absolutely sure that it can't back up the path + // FIXME: is c: allowed??? + if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) { + Com_Printf( "WARNING: refusing to create relative path \"%s\"\n", OSPath ); + return; + } + + strlwr(OSPath); + + for (ofs = OSPath+1 ; *ofs ; ofs++) { + if (*ofs == PATH_SEP) { + // create the directory + *ofs = 0; + Sys_Mkdir (OSPath); + *ofs = PATH_SEP; + } + } +} + + + +/* +=========== +FS_SV_FOpenFileRead + +=========== +*/ +int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + // don't let sound stutter + S_ClearSoundBuffer(); + +#ifdef _XBOX + ospath = FS_BuildOSPath( filename ); +#else + ospath = FS_BuildOSPath( fs_basepath->string, filename, "" ); +#endif + // remove trailing slash + ospath[strlen(ospath)-1] = '\0'; + + if ( fs_debug->integer ) { + Com_Printf( "FS_SV_FOpenFileRead: %s\n", ospath ); + } + + fsh[f].handleFiles.file.o = fopen( ospath, "rb" ); + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) { + f = 0; + } + + *fp = f; + if (f) { + return FS_filelength(f); + } + return 0; +} + + + + +/* +=========== +FS_FOpenFileAppend + +=========== +*/ +fileHandle_t FS_FOpenFileAppend( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + // don't let sound stutter + S_ClearSoundBuffer(); + +#ifdef _XBOX + ospath = FS_BuildOSPath( filename ); +#else + ospath = FS_BuildOSPath( fs_basepath->string, fs_gamedir, filename ); +#endif + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileAppend: %s\n", ospath ); + } + + FS_CreatePath( ospath ); + fsh[f].handleFiles.file.o = fopen( ospath, "ab" ); + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) { + f = 0; + } + return f; +} + + +/* +=========== +FS_FilenameCompare + +Ignore case and seprator char distinctions +=========== +*/ +qboolean FS_FilenameCompare( const char *s1, const char *s2 ) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if ( Q_islower(c1) ) { + c1 -= ('a' - 'A'); + } + if ( Q_islower(c2) ) { + c2 -= ('a' - 'A'); + } + + if ( c1 == '\\' || c1 == ':' ) { + c1 = '/'; + } + if ( c2 == '\\' || c2 == ':' ) { + c2 = '/'; + } + + if (c1 != c2) { + return -1; // strings not equal + } + } while (c1); + + return 0; // strings are equal +} + + +#define MAXPRINTMSG 4096 +void QDECL FS_Printf( fileHandle_t h, const char *fmt, ... ) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + vsprintf (msg,fmt,argptr); + va_end (argptr); + + FS_Write(msg, strlen(msg), h); +} + + + + +/* +============ +FS_WriteFile + +Filename are relative to the quake search path +============ +*/ +void FS_WriteFile( const char *qpath, const void *buffer, int size ) { + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !qpath || !buffer ) { + Com_Error( ERR_FATAL, "FS_WriteFile: NULL parameter" ); + } + + f = FS_FOpenFileWrite( qpath ); + if ( !f ) { + Com_Printf( "Failed to open %s\n", qpath ); + return; + } + + FS_Write( buffer, size, f ); + + FS_FCloseFile( f ); +} + + + + + + + + +/* +================ +FS_Shutdown + +Frees all resources and closes all files +================ +*/ +void FS_Shutdown( void ) { + searchpath_t *p, *next; + int i; + + for(i = 0; i < MAX_FILE_HANDLES; i++) { + if (fsh[i].fileSize) { + FS_FCloseFile(i); + } + } + + // free everything + for ( p = fs_searchpaths ; p ; p = next ) { + next = p->next; + + if ( p->pack ) { +#ifndef _XBOX + unzClose(p->pack->handle); +#endif + Z_Free( p->pack->buildBuffer ); + Z_Free( p->pack ); + } + if ( p->dir ) { + Z_Free( p->dir ); + } + Z_Free( p ); + } + + // any FS_ calls will now be an error until reinitialized + fs_searchpaths = NULL; + + Cmd_RemoveCommand( "path" ); + Cmd_RemoveCommand( "dir" ); + Cmd_RemoveCommand( "touchFile" ); + + initialized = qfalse; +} + + + +/* +================ +FS_InitFilesystem + +Called only at inital startup, not when the filesystem +is resetting due to a game change +================ +*/ +void FS_InitFilesystem( void ) { + // allow command line parms to override our defaults + // we don't have to specially handle this, because normal command + // line variable sets happen before the filesystem + // has been initialized + // + // UPDATE: BTO (VV) + // we have to specially handle this, because normal command + // line variable sets don't happen until after the filesystem + // has already been initialized + Com_StartupVariable( "fs_cdpath" ); + Com_StartupVariable( "fs_basepath" ); + Com_StartupVariable( "fs_game" ); + Com_StartupVariable( "fs_copyfiles" ); + Com_StartupVariable( "fs_restrict" ); + + // try to start up normally + FS_Startup( BASEGAME ); + initialized = qtrue; + + // see if we are going to allow add-ons + FS_SetRestrictions(); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) { + Com_Error( ERR_FATAL, "Couldn't load default.cfg" ); + } +} + + + + +void FS_Flush( fileHandle_t f ) { + fflush(fsh[f].handleFiles.file.o); +} + diff --git a/code/qcommon/files_console.cpp b/code/qcommon/files_console.cpp new file mode 100644 index 0000000..9c97df4 --- /dev/null +++ b/code/qcommon/files_console.cpp @@ -0,0 +1,1031 @@ + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "files.h" +#include "../win32/win_file.h" +#include "../zlib/zlib.h" + + +//#define GOB_PROFILE + + +static cvar_t *fs_openorder; + + +// Zlib Tech Ref says decompression should use about 44kb. I'll +// go with 64kb as a safety factor... +#define ZI_STACKSIZE (64*1024) + +static char* zi_stackTop = NULL; +static char* zi_stackBase = NULL; + + + +//GOB stuff +//=========================================================================== + +struct gi_handleTable +{ + wfhandle_t file; + bool used; +}; + +static gi_handleTable *gi_handles = NULL; +static int gi_cacheHandle = 0; + +static GOBFSHandle gi_open(GOBChar* name, GOBAccessType type) +{ + if (type != GOBACCESS_READ) return (GOBFSHandle)0xFFFFFFFF; + + int f; + for (f = 0; f < MAX_FILE_HANDLES; ++f) + { + if (!gi_handles[f].used) break; + } + + if (f == MAX_FILE_HANDLES) return (GOBFSHandle)0xFFFFFFFF; + + gi_handles[f].file = WF_Open(name, true, strstr(name, "assets.gob") ? true : false); + if (gi_handles[f].file < 0) return (GOBFSHandle)0xFFFFFFFF; + gi_handles[f].used = true; + + return (GOBFSHandle)f; +} + +static GOBBool gi_close(GOBFSHandle* handle) +{ + WF_Close(gi_handles[(int)*handle].file); + gi_handles[(int)*handle].used = false; + return GOB_TRUE; +} + +static GOBInt32 gi_read(GOBFSHandle handle, GOBVoid* buffer, GOBInt32 size) +{ + return WF_Read(buffer, size, gi_handles[(int)handle].file); +} + +static GOBInt32 gi_seek(GOBFSHandle handle, GOBInt32 offset, GOBSeekType type) +{ + int _type; + switch (type) { + case GOBSEEK_START: _type = SEEK_SET; break; + case GOBSEEK_CURRENT: _type = SEEK_CUR; break; + case GOBSEEK_END: _type = SEEK_END; break; + default: assert(0); _type = SEEK_SET; break; + } + + return WF_Seek(offset, _type, gi_handles[(int)handle].file); +} + +static GOBVoid* gi_alloc(GOBUInt32 size) +{ + return Z_Malloc(size, TAG_FILESYS, qfalse, 32); +} + +static GOBVoid gi_free(GOBVoid* ptr) +{ + Z_Free(ptr); +} + +static GOBBool cache_open(GOBUInt32 size) +{ + for (gi_cacheHandle = 0; gi_cacheHandle < MAX_FILE_HANDLES; ++gi_cacheHandle) + { + if (!gi_handles[gi_cacheHandle].used) break; + } + + if (gi_cacheHandle == MAX_FILE_HANDLES) return GOB_FALSE; + + gi_handles[gi_cacheHandle].file = WF_Open("z:\\jedi.swap", false, true); + if (gi_handles[gi_cacheHandle].file < 0) return GOB_FALSE; + + if (!WF_Resize(size, gi_handles[gi_cacheHandle].file)) + { + WF_Close(gi_handles[gi_cacheHandle].file); + return GOB_FALSE; + } + + gi_handles[gi_cacheHandle].used = true; + + return GOB_TRUE; +} + +static GOBBool cache_close(GOBVoid) +{ + WF_Close(gi_handles[gi_cacheHandle].file); + gi_handles[gi_cacheHandle].used = false; + return GOB_TRUE; +} + +static GOBInt32 cache_read(GOBVoid* buffer, GOBInt32 size) +{ + return WF_Read(buffer, size, gi_handles[gi_cacheHandle].file); +} + +static GOBInt32 cache_write(GOBVoid* buffer, GOBInt32 size) +{ + return WF_Write(buffer, size, gi_handles[gi_cacheHandle].file); +} + +static GOBInt32 cache_seek(GOBInt32 offset) +{ + return WF_Seek(offset, SEEK_SET, gi_handles[gi_cacheHandle].file); +} + +static voidpf zi_alloc(voidpf opaque, uInt items, uInt size) +{ + voidpf ret = zi_stackTop; + + zi_stackTop += items * size; + assert(zi_stackTop < zi_stackBase + ZI_STACKSIZE); + + return ret; +} + +static void zi_free(voidpf opaque, voidpf address) +{ +} + +static GOBInt32 gi_decompress_zlib(GOBVoid* source, GOBUInt32 sourceLen, + GOBVoid* dest, GOBUInt32* destLen) +{ + // Copied and modified version of zlib's uncompress()... + + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + + stream.next_out = (Bytef*)dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = zi_alloc; + stream.zfree = zi_free; + zi_stackTop = zi_stackBase; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} + +GOBInt32 gi_decompress_null(GOBVoid* source, GOBUInt32 sourceLen, + GOBVoid* dest, GOBUInt32* destLen) +{ + if (sourceLen > *destLen) return -1; + *destLen = sourceLen; + + memcpy(dest, source, sourceLen); + return 0; +} + +#ifdef GOB_PROFILE +static GOBVoid gi_profileread(GOBUInt32 code) +{ + code = LittleLong(code); + Sys_Log("gob-prof.dat", &code, sizeof(code), true); +} +#endif + +//=========================================================================== + + + + +static void FS_CheckUsed(fileHandle_t f) +{ + if (!fsh[f].used) + { + Com_Error( ERR_FATAL, "Filesystem call attempting to use invalid handle\n" ); + } +} + + +int FS_filelength( fileHandle_t f ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if (fsh[f].gob) + { + GOBUInt32 cur, end, crap; + GOBSeek(fsh[f].ghandle, 0, GOBSEEK_CURRENT, &cur); + GOBSeek(fsh[f].ghandle, 0, GOBSEEK_END, &end); + GOBSeek(fsh[f].ghandle, cur, GOBSEEK_START, &crap); + + return end; + } + else + { + int pos = WF_Tell(fsh[f].whandle); + WF_Seek(0, SEEK_END, fsh[f].whandle); + int end = WF_Tell(fsh[f].whandle); + WF_Seek(pos, SEEK_SET, fsh[f].whandle); + + return end; + } +} + + +void FS_FCloseFile( fileHandle_t f ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if (fsh[f].gob) + GOBClose(fsh[f].ghandle); + else + WF_Close(fsh[f].whandle); + + fsh[f].used = qfalse; +} + + +fileHandle_t FS_FOpenFileWrite( const char *filename ) +{ + FS_CheckInit(); + + fileHandle_t f = FS_HandleForFile(); + + char* osname = FS_BuildOSPath( filename ); + fsh[f].whandle = WF_Open(osname, false, false); + if (fsh[f].whandle >= 0) + { + fsh[f].used = qtrue; + fsh[f].gob = qfalse; + return f; + } + + return 0; +} + + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ + +static int FS_FOpenFileReadOS( const char *filename, fileHandle_t f ) +{ + if (Sys_GetFileCode(filename) != -1) + { + char* osname = FS_BuildOSPath( filename ); + fsh[f].whandle = WF_Open(osname, true, false); + if (fsh[f].whandle >= 0) + { + fsh[f].used = qtrue; + fsh[f].gob = qfalse; + return FS_filelength(f); + } + } + return -1; +} + + +/* +=================== +FS_BuildGOBPath + +Qpath may have either forward or backwards slashes +=================== +*/ +static char *FS_BuildGOBPath(const char *qpath ) +{ + static char path[2][MAX_OSPATH]; + static int toggle; + + toggle ^= 1; // flip-flop to allow two returns without clash + + if (qpath[0] == '\\' || qpath[0] == '/') + { + Com_sprintf( path[toggle], sizeof( path[0] ), ".%s", qpath ); + } + else + { + Com_sprintf( path[toggle], sizeof( path[0] ), ".\\%s", qpath ); + } + +// FS_ReplaceSeparators( path[toggle], '\\' ); + FS_ReplaceSeparators( path[toggle] ); + + return path[toggle]; +} + + +static int FS_FOpenFileReadGOB( const char *filename, fileHandle_t f ) +{ + char* gobname = FS_BuildGOBPath( filename ); + if (GOBOpen(gobname, &fsh[f].ghandle) == GOBERR_OK) + { + fsh[f].used = qtrue; + fsh[f].gob = qtrue; + return FS_filelength(f); + } + return -1; +} + + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ +int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) +{ + FS_CheckInit(); + + if ( file == NULL ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'file' parameter passed\n" ); + } + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + *file = FS_HandleForFile(); + + int len; + + if (fs_openorder->integer == 0) + { + // Release mode -- read from GOB first + len = FS_FOpenFileReadGOB(filename, *file); + if (len < 0) len = FS_FOpenFileReadOS(filename, *file); + } + else + { + // Debug mode -- external files override GOB + len = FS_FOpenFileReadOS(filename, *file); + if (len < 0) len = FS_FOpenFileReadGOB(filename, *file); + } + + if (len >= 0) return len; + + Com_DPrintf ("Can't find %s\n", filename); + + *file = 0; + return -1; +} + + +/* +================= +FS_Read + +Properly handles partial reads +================= +*/ +int FS_Read( void *buffer, int len, fileHandle_t f ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if ( !f ) + { + return 0; + } + + if ( f <= 0 || f >= MAX_FILE_HANDLES ) + { + Com_Error( ERR_FATAL, "FS_Read: Invalid handle %d\n", f ); + } + + if (fsh[f].gob) + { + GOBUInt32 size = GOBRead(buffer, len, fsh[f].ghandle); + if (size == GOB_INVALID_SIZE) + { +#if defined(FINAL_BUILD) + extern void ERR_DiscFail(bool); + ERR_DiscFail(false); +#else + Com_Error( ERR_FATAL, "Failed to read from GOB" ); +#endif + } + return size; + } + else + { + return WF_Read(buffer, len, fsh[f].whandle); + } +} + +/* + MP has FS_Read2 which is supposed to do some extra logic. + We don't care, and just call FS_Read() +*/ +int FS_Read2( void *buffer, int len, fileHandle_t f ) +{ + return FS_Read(buffer, len, f); +} + +/* +================= +FS_Write +================= +*/ +int FS_Write( const void *buffer, int len, fileHandle_t f ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if ( !f ) + { + return 0; + } + + if ( f <= 0 || f >= MAX_FILE_HANDLES ) + { + Com_Error( ERR_FATAL, "FS_Read: Invalid handle %d\n", f ); + } + + if (fsh[f].gob) + { + Com_Error( ERR_FATAL, "FS_Write: Cannot write to GOB files %d\n", f ); + } + else + { + return WF_Write(buffer, len, fsh[f].whandle); + } + + return 0; +} + + +/* +================= +FS_Seek + +================= +*/ +int FS_Seek( fileHandle_t f, long offset, int origin ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if (fsh[f].gob) + { + int _origin; + switch( origin ) { + case FS_SEEK_CUR: _origin = GOBSEEK_CURRENT; break; + case FS_SEEK_END: _origin = GOBSEEK_END; break; + case FS_SEEK_SET: _origin = GOBSEEK_START; break; + default: + _origin = GOBSEEK_CURRENT; + Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); + break; + } + + GOBUInt32 pos; + GOBSeek(fsh[f].ghandle, offset, _origin, &pos); + return pos; + } + else + { + int _origin; + switch( origin ) { + case FS_SEEK_CUR: _origin = SEEK_CUR; break; + case FS_SEEK_END: _origin = SEEK_END; break; + case FS_SEEK_SET: _origin = SEEK_SET; break; + default: + _origin = SEEK_CUR; + Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); + break; + } + + return WF_Seek(offset, _origin, fsh[f].whandle); + } +} + + +/* +================= +FS_Access +================= +*/ +qboolean FS_Access( const char *filename ) +{ + GOBBool status; + + FS_CheckInit(); + + char* gobname = FS_BuildGOBPath( filename ); + if (GOBAccess(gobname, &status) != GOBERR_OK || status != GOB_TRUE) + { + return Sys_GetFileCode( filename ) != -1; + } + + return qtrue; +} + + +/* +====================================================================================== + +CONVENIENCE FUNCTIONS FOR ENTIRE FILES + +====================================================================================== +*/ + +#ifdef _JK2MP +int FS_FileIsInPAK(const char *filename, int *pChecksum) +#else +int FS_FileIsInPAK(const char *filename) +#endif +{ + FS_CheckInit(); + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + GOBBool exists; + GOBAccess(const_cast(filename), &exists); + +#ifdef _JK2MP + *pChecksum = 0; +#endif + + return exists ? 1 : -1; +} + +/* +============ +FS_ReadFile + +Filename are relative to the quake search path +a null buffer will just return the file length without loading +============ +*/ +int FS_ReadFile( const char *qpath, void **buffer ) +{ + FS_CheckInit(); + + if ( !qpath || !qpath[0] ) { + Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" ); + } + + // stop sounds from repeating + S_ClearSoundBuffer(); + + fileHandle_t h; + int len = FS_FOpenFileRead( qpath, &h, qfalse ); + if ( h == 0 ) + { + if ( buffer ) *buffer = NULL; + return -1; + } + + if ( !buffer ) + { + FS_FCloseFile(h); + return len; + } + + // assume temporary.... + byte* buf = (byte*)Z_Malloc( len+1, TAG_TEMP_WORKSPACE, qfalse, 32); + buf[len]='\0'; + +// Z_Label(buf, qpath); + + FS_Read(buf, len, h); + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + FS_FCloseFile( h ); + + *buffer = buf; + return len; +} + + +/* +============= +FS_FreeFile +============= +*/ +void FS_FreeFile( void *buffer ) +{ + FS_CheckInit(); + + if ( !buffer ) { + Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" ); + } + + Z_Free( buffer ); +} + + +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) +{ + FS_CheckInit(); + + if (mode != FS_READ) + { + Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" ); + return -1; + } + + return FS_FOpenFileRead( qpath, f, qtrue ); +} + + +int FS_FTell( fileHandle_t f ) +{ + FS_CheckInit(); + FS_CheckUsed(f); + + if (fsh[f].gob) + { + GOBUInt32 pos; + GOBSeek(fsh[f].ghandle, 0, GOBSEEK_CURRENT, &pos); + return pos; + } + else + { + return WF_Tell(fsh[f].whandle); + } +} + +/* +================ +FS_Startup +================ +*/ +void FS_Startup( const char *gameName ) +{ + Com_Printf( "----- FS_Startup -----\n" ); + + fs_openorder = Cvar_Get( "fs_openorder", "0", 0 ); + fs_debug = Cvar_Get( "fs_debug", "0", 0 ); + fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT ); + fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT ); + fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultBasePath(), CVAR_INIT ); + fs_gamedirvar = Cvar_Get ("fs_game", "base", CVAR_INIT|CVAR_SERVERINFO ); + fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT ); + + gi_handles = new gi_handleTable[MAX_FILE_HANDLES]; + for (int f = 0; f < MAX_FILE_HANDLES; ++f) + { + fsh[f].used = false; + gi_handles[f].used = false; + } + + zi_stackBase = (char*)Z_Malloc(ZI_STACKSIZE, TAG_FILESYS, qfalse); + + GOBMemoryFuncSet mem; + mem.alloc = gi_alloc; + mem.free = gi_free; + + GOBFileSysFuncSet file; + file.close = gi_close; + file.open = gi_open; + file.read = gi_read; + file.seek = gi_seek; + file.write = NULL; + + GOBCacheFileFuncSet cache; + cache.close = cache_close; + cache.open = cache_open; + cache.read = cache_read; + cache.seek = cache_seek; + cache.write = cache_write; + + GOBCodecFuncSet codec = { + 2, // codecs + { + { // Codec 0 - zlib + 'z', GOB_INFINITE_RATIO, // tag, ratio (ratio is meaningless for decomp) + NULL, + gi_decompress_zlib, + }, + { // Codec 1 - null + '0', GOB_INFINITE_RATIO, // tag, ratio (ratio is meaningless for decomp) + NULL, + gi_decompress_null, + }, + } + }; + + if ( +#ifdef _XBOX + GOBInit(&mem, &file, &codec, &cache) +#else + GOBInit(&mem, &file, &codec, NULL) +#endif + != GOBERR_OK) + { + Com_Error( ERR_FATAL, "Could not initialize GOB" ); + } + + char* archive = FS_BuildOSPath( "assets" ); + if (GOBArchiveOpen(archive, GOBACCESS_READ, GOB_FALSE, GOB_TRUE) != GOBERR_OK) + { +#if defined(FINAL_BUILD) + extern void ERR_DiscFail(bool); + ERR_DiscFail(false); +#else + //Com_Error( ERR_FATAL, "Could not initialize GOB" ); + Cvar_Set("fs_openorder", "1"); +#endif + } + + GOBSetCacheSize(1); + GOBSetReadBufferSize(32 * 1024); + +#ifdef GOB_PROFILE + GOBProfileFuncSet profile = { + gi_profileread + }; + GOBSetProfileFuncs(&profile); + GOBStartProfile(); +#endif + + Com_Printf( "----------------------\n" ); +} + +/* +============================ + +DIRECTORY SCANNING FUCNTIONS + +============================ +*/ + +#define MAX_FOUND_FILES 0x1000 + +/* +================== +FS_AddFileToList +================== +*/ +static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) { + int i; + + if ( nfiles == MAX_FOUND_FILES - 1 ) { + return nfiles; + } + for ( i = 0 ; i < nfiles ; i++ ) { + if ( !stricmp( name, list[i] ) ) { + return nfiles; // allready in list + } + } +// list[nfiles] = CopyString( name ); + list[nfiles] = (char *) Z_Malloc( strlen(name) + 1, TAG_LISTFILES, qfalse ); + strcpy(list[nfiles], name); + nfiles++; + + return nfiles; +} + +/* +=============== +FS_ListFiles + +Returns a uniqued list of files that match the given criteria +from all search paths +=============== +*/ +char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) +{ + char *netpath; + int numSysFiles; + char **sysFiles; + char *name; + int nfiles = 0; + char **listCopy; + char *list[MAX_FOUND_FILES]; + int i; + + FS_CheckInit(); + + if ( !path ) { + *numfiles = 0; + return NULL; + } + + // We don't do any fancy searchpath magic here, it's all in the meta-file + // that Sys_ListFiles will return + netpath = FS_BuildOSPath( path ); +#ifdef _JK2MP + sysFiles = Sys_ListFiles( netpath, extension, NULL, &numSysFiles, qfalse ); +#else + sysFiles = Sys_ListFiles( netpath, extension, &numSysFiles, qfalse ); +#endif + for ( i = 0 ; i < numSysFiles ; i++ ) { + // unique the match + name = sysFiles[i]; + nfiles = FS_AddFileToList( name, list, nfiles ); + } + Sys_FreeFileList( sysFiles ); + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ), TAG_LISTFILES, qfalse); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + + +/* +================= +FS_FreeFileList +================= +*/ +void FS_FreeFileList( char **filelist ) +{ + int i; + + FS_CheckInit(); + + if ( !filelist ) { + return; + } + + for ( i = 0 ; filelist[i] ; i++ ) { + Z_Free( filelist[i] ); + } + + Z_Free( filelist ); +} + +/* +=============== +FS_AddFileToListBuf +=============== +*/ +static int FS_AddFileToListBuf( char *name, char *listbuf, int bufsize, int nfiles ) +{ + char *p; + + if ( nfiles == MAX_FOUND_FILES - 1 ) { + return nfiles; + } + + if (name[0] == '/' || name[0] == '\\') { + name++; + } + + p = listbuf; + while ( *p ) { + if ( !stricmp( name, p ) ) { + return nfiles; // already in list + } + p += strlen( p ) + 1; + } + + if ( ( p + strlen( name ) + 2 - listbuf ) > bufsize ) { + return nfiles; // list is full + } + + strcpy( p, name ); + p += strlen( p ) + 1; + *p = 0; + + return nfiles + 1; +} + +/* +================ +FS_GetFileList + +Returns a uniqued list of files that match the given criteria +from all search paths +================ +*/ +int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) +{ + int nfiles = 0; + int i; + char *netpath; + int numSysFiles; + char **sysFiles; + char *name; + + FS_CheckInit(); + + if ( !path ) { + return 0; + } + if ( !extension ) { + extension = ""; + } + + // Prime the file list buffer + listbuf[0] = '\0'; + netpath = FS_BuildOSPath( path ); +#ifdef _JK2MP + sysFiles = Sys_ListFiles( netpath, extension, NULL, &numSysFiles, qfalse ); +#else + sysFiles = Sys_ListFiles( netpath, extension, &numSysFiles, qfalse ); +#endif + for ( i = 0 ; i < numSysFiles ; i++ ) { + // unique the match + name = sysFiles[i]; + nfiles = FS_AddFileToListBuf( name, listbuf, bufsize, nfiles ); + } + Sys_FreeFileList( sysFiles ); + + return nfiles; +} + +/* +================= + Filesytem STUBS +================= +*/ + +qboolean FS_ConditionalRestart(int checksumFeed) +{ + return qfalse; +} + +void FS_ClearPakReferences(int flags) +{ + return; +} + +const char *FS_LoadedPakNames(void) +{ + return ""; +} + +const char *FS_ReferencedPakNames(void) +{ + return ""; +} + +void FS_SetRestrictions(void) +{ + return; +} + +#ifdef _JK2MP +void FS_Restart(int checksumFeed) +#else +void FS_Restart(void) +#endif +{ + return; +} + +qboolean FS_FileExists(const char *file) +{ + assert(!"FS_FileExists not implemented on Xbox"); + return qfalse; +} + +void FS_UpdateGamedir(void) +{ + return; +} + +void FS_PureServerSetReferencedPaks(const char *pakSums, const char *pakNames) +{ + return; +} + +void FS_PureServerSetLoadedPaks(const char *pakSums, const char *pakNames) +{ + return; +} + +const char *FS_ReferencedPakChecksums(void) +{ + return ""; +} + +const char *FS_LoadedPakChecksums(void) +{ + return ""; +} diff --git a/code/qcommon/files_pc.cpp b/code/qcommon/files_pc.cpp new file mode 100644 index 0000000..f91ef5c --- /dev/null +++ b/code/qcommon/files_pc.cpp @@ -0,0 +1,1741 @@ + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "files.h" + + +#define MAX_SEARCH_PATHS 2048 +#define MAX_FILEHASH_SIZE 1024 + + +#define DEMOGAME "demo" + +// every time a new demo pk3 file is built, this checksum must be updated. +// the easiest way to get it is to just run the game and see what it spits out +#define DEMO_PAK_CHECKSUM 1431467275 +#define DEMO_PAK_MAXFILES 5174u + + +//static int fs_numServerPaks; +//static int fs_serverPaks[MAX_SEARCH_PATHS]; + +// productId: This file is copyright 2003 Raven Software, and may not be duplicated except during a licensed installation of the full commercial version of Star Wars: Jedi Academy +static const byte fs_scrambledProductId[] = { +42, 143, 149, 190, 10, 197, 225, 133, 243, 63, 189, 182, 226, 56, 143, 17, 215, 37, 197, 218, 50, 103, 24, 235, 246, 191, 183, 149, 160, 170, +230, 52, 176, 231, 15, 194, 236, 247, 159, 168, 132, 154, 24, 133, 67, 85, 36, 97, 99, 86, 117, 189, 212, 156, 236, 153, 68, 10, 196, 241, +39, 219, 156, 88, 93, 198, 200, 232, 142, 67, 45, 209, 53, 186, 228, 241, 162, 127, 213, 83, 7, 121, 11, 93, 123, 243, 148, 240, 229, 42, +42, 6, 215, 239, 112, 120, 240, 244, 104, 12, 38, 47, 201, 253, 223, 208, 154, 69, 141, 157, 32, 117, 166, 146, 236, 59, 15, 223, 52, 89, +133, 64, 201, 56, 119, 25, 211, 152, 159, 11, 92, 59, 207, 81, 123, 0, 121, 241, 116, 42, 36, 251, 51, 149, 79, 165, 12, 106, 187, 225, +203, 99, 102, 69, 97, 81, 27, 107, 81, 178, 63, 35, 185, 64, 115 +}; + + +/* +================ +return a hash value for the filename +================ +*/ +long FS_HashFileName( const char *fname, int hashSize ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + if (letter == PATH_SEP) letter = '/'; // damn path names //mac and unix are different + hash+=(long)(letter)*(i+119); + i++; + } + hash = (hash ^ (hash >> 10) ^ (hash >> 20)); + hash &= (hashSize-1); + return hash; +} + + +static FILE *FS_FileForHandle( fileHandle_t f ) { + if ( f < 0 || f > MAX_FILE_HANDLES ) { + Com_Error( ERR_DROP, "FS_FileForHandle: out of reange" ); + } + if (fsh[f].zipFile == (int)qtrue) { + Com_Error( ERR_DROP, "FS_FileForHandle: can't get FILE on zip file" ); + } + if ( ! fsh[f].handleFiles.file.o ) { + Com_Error( ERR_DROP, "FS_FileForHandle: NULL" ); + } + + return fsh[f].handleFiles.file.o; +} + +void FS_ForceFlush( fileHandle_t f ) { + FILE *file; + + file = FS_FileForHandle(f); + setvbuf( file, NULL, _IONBF, 0 ); +} + + +/* +================ +FS_filelength + +If this is called on a non-unique FILE (from a pak file), +it will return the size of the pak file, not the expected +size of the file. +================ +*/ +int FS_filelength( fileHandle_t f ) { + int pos; + int end; + FILE* h; + + h = FS_FileForHandle(f); + pos = ftell (h); + fseek (h, 0, SEEK_END); + end = ftell (h); + fseek (h, pos, SEEK_SET); + + return end; +} + +/* +================= +FS_CopyFile + +Copy a fully specified file from one place to another +================= +*/ +// added extra param so behind-the-scenes copying in savegames doesn't clutter up the screen -slc +qboolean FS_CopyFile( char *fromOSPath, char *toOSPath, qboolean qbSilent = qfalse ); +qboolean FS_CopyFile( char *fromOSPath, char *toOSPath, qboolean qbSilent ) { + FILE *f; + int len; + byte *buf; + + if (!qbSilent) + { + Com_Printf( "copy %s to %s\n", fromOSPath, toOSPath ); + } + f = fopen( fromOSPath, "rb" ); + if ( !f ) { + return qfalse; + } + fseek (f, 0, SEEK_END); + len = ftell (f); + fseek (f, 0, SEEK_SET); + + buf = (unsigned char *) Z_Malloc( len, TAG_FILESYS, qfalse); + if (fread( buf, 1, len, f ) != (size_t) len) + { + Z_Free( buf ); + fclose(f); + if (qbSilent){ + return qfalse; + } + Com_Error( ERR_FATAL, "Short read in FS_Copyfiles()\n" ); + } + fclose( f ); + + FS_CreatePath( toOSPath ); + f = fopen( toOSPath, "wb" ); + if ( !f ) { + Z_Free( buf ); + return qfalse; + } + if (fwrite( buf, 1, len, f ) != (size_t)len) + { + Z_Free( buf ); + fclose(f); + if (qbSilent){ + return qfalse; + } + Com_Error( ERR_FATAL, "Short write in FS_Copyfiles()\n" ); + } + fclose( f ); + Z_Free( buf ); + + return qtrue; +} + +/* +============== +FS_FCloseFile + +If the FILE pointer is an open pak file, leave it open. + +For some reason, other dll's can't just call fclose() +on files returned by FS_FOpenFile... +============== +*/ +void FS_FCloseFile( fileHandle_t f ) { + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if (fsh[f].zipFile == qtrue) { + unzCloseCurrentFile( fsh[f].handleFiles.file.z ); + if ( fsh[f].handleFiles.unique ) { + unzClose( fsh[f].handleFiles.file.z ); + } + memset( &fsh[f], 0, sizeof( fsh[f] ) ); + return; + } + + // we didn't find it as a pak, so close it as a unique file + fclose (fsh[f].handleFiles.file.o); + memset( &fsh[f], 0, sizeof( fsh[f] ) ); +} + + + + +// The following functions with "UserGen" in them were added for savegame handling, +// since outside functions aren't supposed to know about full paths/dirs + +// "filename" is local to the current gamedir (eg "saves/blah.sav") +// +void FS_DeleteUserGenFile( const char *filename ) +{ + char *ospath; + + if ( !fs_searchpaths ) + { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + ospath = FS_BuildOSPath( fs_basepath->string, fs_gamedir, filename ); + + if ( fs_debug->integer ) + { + Com_Printf( "FS_DeleteUserGenFile: %s\n", ospath ); + } + + remove ( ospath ); +} + +// filenames are local (eg "saves/blah.sav") +// +// return: qtrue = OK +// +qboolean FS_MoveUserGenFile( const char *filename_src, const char *filename_dst ) +{ + char *ospath_src, + *ospath_dst; + + if ( !fs_searchpaths ) + { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + ospath_src = FS_BuildOSPath( fs_basepath->string, fs_gamedir, filename_src ); + ospath_dst = FS_BuildOSPath( fs_basepath->string, fs_gamedir, filename_dst ); + + if ( fs_debug->integer ) + { + Com_Printf( "FS_MoveUserGenFile: %s to %s\n", ospath_src, ospath_dst ); + } + +/* int iSlashes1=0; + int iSlashes2=0; + char *p; + for (p = strchr(filename_src,'/'); p; iSlashes1++) + { + p = strchr(p+1,'/'); + } + for (p = strchr(filename_dst,'/'); p; iSlashes2++) + { + p = strchr(p+1,'/'); + } + + if (iSlashes1 != iSlashes2) + { + int ret = FS_CopyFile( ospath_src, ospath_dst, qtrue ); + remove(ospath_src); + return ret; + } + else +*/ + { + remove(ospath_dst); + return (0 == rename (ospath_src, ospath_dst )); + } +} + +/* +=========== +FS_FOpenFileWrite + +=========== +*/ +fileHandle_t FS_FOpenFileWrite( const char *filename ) { + char *ospath; + fileHandle_t f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + f = FS_HandleForFile(); + fsh[f].zipFile = qfalse; + + ospath = FS_BuildOSPath( fs_basepath->string, fs_gamedir, filename ); + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileWrite: %s\n", ospath ); + } + + //Com_DPrintf( "writing to: %s\n", ospath ); + FS_CreatePath( ospath ); + fsh[f].handleFiles.file.o = fopen( ospath, "wb" ); + + Q_strncpyz( fsh[f].name, filename, sizeof( fsh[f].name ) ); + + fsh[f].handleSync = qfalse; + if (!fsh[f].handleFiles.file.o) { + f = 0; + } + return f; +} + +static bool FS_FileCacheable(const char* const filename) +{ + extern cvar_t *com_buildScript; + if (com_buildScript && com_buildScript->integer) + { + return true; + } + return( strchr(filename, '/') != 0 ); +} + +/* +=========== +FS_FOpenFileRead + +Finds the file in the search path. +Returns filesize and an open FILE pointer. +Used for streaming data out of either a +separate file or a ZIP file. +=========== +*/ +int FS_FOpenFileRead( const char *filename, fileHandle_t *file, qboolean uniqueFILE ) { + searchpath_t *search; + char *netpath; + pack_t *pak; + fileInPack_t *pakFile; + directory_t *dir; + long hash=0; + unz_s *zfi; + ZIP_FILE *temp; +// int i; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( file == NULL ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'file' parameter passed\n" ); + } + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + // qpaths are not supposed to have a leading slash + if ( filename[0] == '/' || filename[0] == '\\' ) { + filename++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { + *file = 0; + return -1; + } + + // + // search through the path, one element at a time + // + + *file = FS_HandleForFile(); + fsh[*file].handleFiles.unique = uniqueFILE; + + // this new bool is in for an optimisation, if you (eg) opened a BSP file under fs_copyfiles==2, + // then it triggered a copy operation to update your local HD version, then this will re-open the + // file handle on your local version, not the net build. This uses a bit more CPU to re-do the loop + // logic, but should read faster than accessing the net version a second time. + // + qboolean bFasterToReOpenUsingNewLocalFile = qfalse; + + do + { + bFasterToReOpenUsingNewLocalFile = qfalse; + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // + if ( search->pack ) { + hash = FS_HashFileName(filename, search->pack->hashSize); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + // disregard if it doesn't match one of the allowed pure pak files + /* if ( !FS_PakIsPure(search->pack) ) { + continue; + } + */ + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + // found it! + if ( uniqueFILE ) { + // open a new file on the pakfile + fsh[*file].handleFiles.file.z = unzReOpen (pak->pakFilename, pak->handle); + if (fsh[*file].handleFiles.file.z == NULL) { + Com_Error (ERR_FATAL, "Couldn't reopen %s", pak->pakFilename); + } + } else { + fsh[*file].handleFiles.file.z = pak->handle; + } + Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); + fsh[*file].zipFile = qtrue; + zfi = (unz_s *)fsh[*file].handleFiles.file.z; + // in case the file was new + temp = zfi->file; + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition(pak->handle, pakFile->pos); + // copy the file info into the unzip structure + memcpy( zfi, pak->handle, sizeof(unz_s)); + // we copy this back into the structure + zfi->file = temp; + // open the file in the zip + unzOpenCurrentFile( fsh[*file].handleFiles.file.z ); + fsh[*file].zipFilePos = pakFile->pos; + + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", + filename, pak->pakFilename ); + } + return zfi->cur_file_info.uncompressed_size; + } + pakFile = pakFile->next; + } while(pakFile != NULL); + } else if ( search->dir ) { + // check a file in the directory tree + + // if we are running restricted, the only files we + // will allow to come from the directory are .cfg files + if ( fs_restrict->integer /*|| fs_numServerPaks*/ ) { + int l; + + l = strlen( filename ); + + if ( stricmp( filename + l - 4, ".cfg" ) // for config files + && stricmp( filename + l - 4, ".sav" ) // for save games + && stricmp( filename + l - 4, ".dat" ) ) { // for journal files + continue; + } + } + + dir = search->dir; + + netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); + + fsh[*file].handleFiles.file.o = fopen (netpath, "rb"); + if ( !fsh[*file].handleFiles.file.o ) { + continue; + } + + // if running with fs_copyfiles 2, and search path == local, then we need to fail to open + // if the time/date stamp != the network version (so it'll loop round again and use the network path, + // which comes later in the search order) + // + if ( fs_copyfiles->integer == 2 && fs_cdpath->string[0] && !Q_stricmp( dir->path, fs_basepath->string ) + && FS_FileCacheable(filename) ) + { + if ( Sys_FileOutOfDate( netpath, FS_BuildOSPath( fs_cdpath->string, dir->gamedir, filename ) )) + { + fclose(fsh[*file].handleFiles.file.o); + fsh[*file].handleFiles.file.o = 0; + continue; //carry on to find the cdpath version. + } + } + + Q_strncpyz( fsh[*file].name, filename, sizeof( fsh[*file].name ) ); + fsh[*file].zipFile = qfalse; + if ( fs_debug->integer ) { + Com_Printf( "FS_FOpenFileRead: %s (found in '%s/%s')\n", filename, + dir->path, dir->gamedir ); + } + + // if we are getting it from the cdpath, optionally copy it + // to the basepath + if ( fs_copyfiles->integer && !stricmp( dir->path, fs_cdpath->string ) ) { + char *copypath; + + copypath = FS_BuildOSPath( fs_basepath->string, dir->gamedir, filename ); + + switch ( fs_copyfiles->integer ) + { + default: + case 1: + { + FS_CopyFile( netpath, copypath ); + } + break; + + case 2: + { + + if (FS_FileCacheable(filename) ) + { + // maybe change this to Com_DPrintf? On the other hand... + // + Com_Printf( S_COLOR_CYAN"fs_copyfiles(2), Copying: %s to %s\n", netpath, copypath ); + + FS_CreatePath( copypath ); + + if (Sys_CopyFile( netpath, copypath, qtrue )) + { + // clear this handle and setup for re-opening of the new local copy... + // + bFasterToReOpenUsingNewLocalFile = qtrue; + fclose(fsh[*file].handleFiles.file.o); + fsh[*file].handleFiles.file.o = NULL; + } + } + } + break; + } + } + + if (bFasterToReOpenUsingNewLocalFile) + { + break; // and re-read the local copy, not the net version + } + + return FS_filelength (*file); + } + } + } + while ( bFasterToReOpenUsingNewLocalFile ); + + Com_DPrintf ("Can't find %s\n", filename); + + *file = 0; + return -1; +} + + + +/* +================= +FS_Read + +Properly handles partial reads +================= +*/ +int FS_Read( void *buffer, int len, fileHandle_t f ) { + int block, remaining; + int read; + byte *buf; + int tries; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !f ) { + return 0; + } + if ( f <= 0 || f >= MAX_FILE_HANDLES ) + { + Com_Error( ERR_FATAL, "FS_Read: Invalid handle %d\n", f ); + } + + buf = (byte *)buffer; + fs_readCount += len; + + if (fsh[f].zipFile == qfalse) { + remaining = len; + tries = 0; + while (remaining) { + block = remaining; + read = fread (buf, 1, block, fsh[f].handleFiles.file.o); + if (read == 0) { + // we might have been trying to read from a CD, which + // sometimes returns a 0 read on windows + if (!tries) { + tries = 1; + } else { + return len-remaining; //Com_Error (ERR_FATAL, "FS_Read: 0 bytes read"); + } + } + + if (read == -1) { + Com_Error (ERR_FATAL, "FS_Read: -1 bytes read"); + } + + remaining -= read; + buf += read; + } + return len; + } else { + return unzReadCurrentFile(fsh[f].handleFiles.file.z, buffer, len); + } +} + + +/* +================= +FS_Write + +Properly handles partial writes +================= +*/ +int FS_Write( const void *buffer, int len, fileHandle_t h ) { + int block, remaining; + int written; + byte *buf; + int tries; + FILE *f; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !h ) { + return 0; + } + + f = FS_FileForHandle(h); + buf = (byte *)buffer; + + remaining = len; + tries = 0; + while (remaining) { + block = remaining; + written = fwrite (buf, 1, block, f); + if (written == 0) { + if (!tries) { + tries = 1; + } else { + Com_Printf( "FS_Write: 0 bytes written\n" ); + return 0; + } + } + + if (written == -1) { + Com_Printf( "FS_Write: -1 bytes written\n" ); + return 0; + } + + remaining -= written; + buf += written; + } + if ( fsh[h].handleSync ) { + fflush( f ); + } + return len; +} + + +/* +================= +FS_Seek + +================= +*/ +int FS_Seek( fileHandle_t f, long offset, int origin ) { + int _origin; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + return -1; + } + + if (fsh[f].zipFile == qtrue) { + char foo[65536]; + if (offset == 0 && origin == FS_SEEK_SET) { + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos); + return unzOpenCurrentFile(fsh[f].handleFiles.file.z); + } else if (offset<65536) { + // set the file position in the zip file (also sets the current file info) + unzSetCurrentFileInfoPosition(fsh[f].handleFiles.file.z, fsh[f].zipFilePos); + unzOpenCurrentFile(fsh[f].handleFiles.file.z); + return FS_Read(foo, offset, f); + } else { + Com_Error( ERR_FATAL, "ZIP FILE FSEEK NOT YET IMPLEMENTED for big offsets(%s)\n", fsh[f].name); + return -1; + } + } else { + FILE *file; + file = FS_FileForHandle(f); + switch( origin ) { + case FS_SEEK_CUR: + _origin = SEEK_CUR; + break; + case FS_SEEK_END: + _origin = SEEK_END; + break; + case FS_SEEK_SET: + _origin = SEEK_SET; + break; + default: + _origin = SEEK_CUR; + Com_Error( ERR_FATAL, "Bad origin in FS_Seek\n" ); + break; + } + + return fseek( file, offset, _origin ); + } +} + + +/* +====================================================================================== + +CONVENIENCE FUNCTIONS FOR ENTIRE FILES + +====================================================================================== +*/ + +int FS_FileIsInPAK(const char *filename ) { + searchpath_t *search; + pack_t *pak; + fileInPack_t *pakFile; + long hash = 0; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !filename ) { + Com_Error( ERR_FATAL, "FS_FOpenFileRead: NULL 'filename' parameter passed\n" ); + } + + // qpaths are not supposed to have a leading slash + if ( filename[0] == '/' || filename[0] == '\\' ) { + filename++; + } + + // make absolutely sure that it can't back up the path. + // The searchpaths do guarantee that something will always + // be prepended, so we don't need to worry about "c:" or "//limbo" + if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { + return -1; + } + + // + // search through the path, one element at a time + // + + for ( search = fs_searchpaths ; search ; search = search->next ) { + // + if (search->pack) { + hash = FS_HashFileName(filename, search->pack->hashSize); + } + // is the element a pak file? + if ( search->pack && search->pack->hashTable[hash] ) { + // disregard if it doesn't match one of the allowed pure pak files +/* if ( !FS_PakIsPure(search->pack) ) { + continue; + } +*/ + // look through all the pak file elements + pak = search->pack; + pakFile = pak->hashTable[hash]; + do { + // case and separator insensitive comparisons + if ( !FS_FilenameCompare( pakFile->name, filename ) ) { + return 1; + } + pakFile = pakFile->next; + } while(pakFile != NULL); + } + } + return -1; +} + +/* +============ +FS_ReadFile + +Filename are relative to the quake search path +a null buffer will just return the file length without loading +============ +*/ +#include "..\client\client.h" +int FS_ReadFile( const char *qpath, void **buffer ) { + fileHandle_t h; + byte* buf; + int len; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !qpath || !qpath[0] ) { + Com_Error( ERR_FATAL, "FS_ReadFile with empty name\n" ); + } + + // stop sounds from repeating + S_ClearSoundBuffer(); + + // look for it in the filesystem or pack files + len = FS_FOpenFileRead( qpath, &h, qfalse ); + if ( h == 0 ) { + if ( buffer ) { + *buffer = NULL; + } + return -1; + } + + if ( !buffer ) { + FS_FCloseFile( h); + return len; + } + + fs_loadCount++; + + buf = (byte*)Z_Malloc( len+1, TAG_FILESYS, qfalse); + *buffer = buf; + + Z_Label(buf, qpath); + + // PRECACE CHECKER! +#ifndef FINAL_BUILD + if (com_sv_running && com_sv_running->integer && cls.state >= CA_ACTIVE) { //com_cl_running + if (strncmp(qpath,"menu/",5) ) { + Com_Printf( S_COLOR_MAGENTA"FS_ReadFile: %s NOT PRECACHED!\n", qpath ); + } + } +#endif + + FS_Read (buf, len, h); + + // guarantee that it will have a trailing 0 for string operations + buf[len] = 0; + FS_FCloseFile( h ); + return len; +} + + +/* +============= +FS_FreeFile +============= +*/ +void FS_FreeFile( void *buffer ) { + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + if ( !buffer ) { + Com_Error( ERR_FATAL, "FS_FreeFile( NULL )" ); + } + + Z_Free( buffer ); +} + + + +/* +========================================================================== + +ZIP FILE LOADING + +========================================================================== +*/ + +/* +================= +FS_LoadZipFile + +Creates a new pak_t in the search chain for the contents +of a zip file. +================= +*/ +static pack_t *FS_LoadZipFile( char *zipfile ) +{ + fileInPack_t *buildBuffer; + pack_t *pack; + unzFile uf; + int err; + unz_global_info gi; + char filename_inzip[MAX_ZPATH]; + unz_file_info file_info; + int i, len; + long hash; + int fs_numHeaderLongs; + int *fs_headerLongs; + char *namePtr; + + fs_numHeaderLongs = 0; + + uf = unzOpen(zipfile); + err = unzGetGlobalInfo (uf,&gi); + + if (err != UNZ_OK) + return NULL; + + fs_packFiles += gi.number_entry; + + len = 0; //find the length of all filenames + unzGoToFirstFile(uf); + for (i = 0; i < gi.number_entry; i++) + { + err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); + if (err != UNZ_OK) { + break; + } + if ( file_info.size_filename > MAX_QPATH) + { + Com_Error(ERR_FATAL, "ERROR: filename length > MAX_QPATH ( strlen(%s) = %d) \n", filename_inzip, file_info.size_filename ); + } + len += strlen(filename_inzip) + 1; + unzGoToNextFile(uf); + } + + buildBuffer = (fileInPack_t *)Z_Malloc( gi.number_entry * sizeof( fileInPack_t ) + len , TAG_FILESYS, qtrue ); + namePtr = ((char *) buildBuffer) + gi.number_entry * sizeof( fileInPack_t ); + fs_headerLongs = (int*)Z_Malloc( gi.number_entry * sizeof(int), TAG_FILESYS, qtrue ); + + // get the hash table size from the number of files in the zip + // because lots of custom pk3 files have less than 32 or 64 files + for (i = 1; i <= MAX_FILEHASH_SIZE; i <<= 1) { + if (i > gi.number_entry) { + break; + } + } + + pack = (pack_t*)Z_Malloc( sizeof( pack_t ) + i * sizeof(fileInPack_t *), TAG_FILESYS, qtrue ); + memset (pack, 0, sizeof( pack_t ) + i * sizeof(fileInPack_t *)); + pack->hashSize = i; + pack->hashTable = (fileInPack_t **) (((char *) pack) + sizeof( pack_t )); + + Q_strncpyz( pack->pakFilename, zipfile, sizeof( pack->pakFilename ) ); + + pack->handle = uf; + pack->numfiles = gi.number_entry; + unzGoToFirstFile(uf); + + for (i = 0; i < gi.number_entry; i++) + { + err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); + if (err != UNZ_OK) { + break; + } + if (file_info.uncompressed_size > 0) { + fs_headerLongs[fs_numHeaderLongs++] = LittleLong(file_info.crc); + } + Q_strlwr( filename_inzip ); + hash = FS_HashFileName(filename_inzip, pack->hashSize); + buildBuffer[i].name = namePtr; + strcpy( buildBuffer[i].name, filename_inzip ); + namePtr += strlen(filename_inzip) + 1; + // store the file position in the zip + unzGetCurrentFileInfoPosition(uf, &buildBuffer[i].pos); + // + buildBuffer[i].next = pack->hashTable[hash]; + pack->hashTable[hash] = &buildBuffer[i]; + unzGoToNextFile(uf); + } + + pack->checksum = Com_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs ); + pack->checksum = LittleLong( pack->checksum ); + + Z_Free(fs_headerLongs); + + pack->buildBuffer = buildBuffer; + return pack; +} + +/* +================================================================================= + +DIRECTORY SCANNING FUNCTIONS + +================================================================================= +*/ + +#define MAX_FOUND_FILES 0x1000 + +static int FS_ReturnPath( const char *zname, char *zpath, int *depth ) { + int len, at, newdep; + + newdep = 0; + zpath[0] = 0; + len = 0; + at = 0; + + while(zname[at] != 0) + { + if (zname[at]=='/' || zname[at]=='\\') { + len = at; + newdep++; + } + at++; + } + strcpy(zpath, zname); + zpath[len] = 0; + *depth = newdep; + + return len; +} + +/* +================== +FS_AddFileToList +================== +*/ +static int FS_AddFileToList( char *name, char *list[MAX_FOUND_FILES], int nfiles ) { + int i; + + if ( nfiles == MAX_FOUND_FILES - 1 ) { + return nfiles; + } + for ( i = 0 ; i < nfiles ; i++ ) { + if ( !stricmp( name, list[i] ) ) { + return nfiles; // allready in list + } + } + list[nfiles] = CopyString( name ); + nfiles++; + + return nfiles; +} + +/* +=============== +FS_ListFiles + +Returns a uniqued list of files that match the given criteria +from all search paths +=============== +*/ +char **FS_ListFiles( const char *path, const char *extension, int *numfiles ) { + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + searchpath_t *search; + int i; + int pathLength; + int extensionLength; + int length, pathDepth; + pack_t *pak; + fileInPack_t *buildBuffer; + char zpath[MAX_QPATH]; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !path ) { + *numfiles = 0; + return NULL; + } + if ( !extension ) { + extension = ""; + } + + pathLength = strlen( path ); + extensionLength = strlen( extension ); + nfiles = 0; + FS_ReturnPath(path, zpath, &pathDepth); + + // + // search through the path, one element at a time, adding to list + // + for (search = fs_searchpaths ; search ; search = search->next) { + // is the element a pak file? + if (search->pack) { + // look through all the pak file elements + pak = search->pack; + buildBuffer = pak->buildBuffer; + for (i=0 ; inumfiles ; i++) { + char *name; + int zpathLen, depth; + + // check for directory match + name = buildBuffer[i].name; + zpathLen = FS_ReturnPath(name, zpath, &depth); + + if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) { + continue; + } + + // check for extension match + length = strlen( name ); + if ( length < extensionLength ) { + continue; + } + + if ( stricmp( name + length - extensionLength, extension ) ) { + continue; + } + + // unique the match + nfiles = FS_AddFileToList( name + pathLength + 1, list, nfiles ); + } + } else if (search->dir) { // scan for files in the filesystem + char *netpath; + int numSysFiles; + char **sysFiles; + char *name; + + netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path ); + sysFiles = Sys_ListFiles( netpath, extension, &numSysFiles, qfalse ); + for ( i = 0 ; i < numSysFiles ; i++ ) { + // unique the match + name = sysFiles[i]; + nfiles = FS_AddFileToList( name, list, nfiles ); + } + Sys_FreeFileList( sysFiles ); + } + } + + // return a copy of the list + *numfiles = nfiles; + + if ( !nfiles ) { + return NULL; + } + + listCopy = (char**)Z_Malloc( ( nfiles + 1 ) * sizeof( *listCopy ), TAG_FILESYS, qfalse); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + return listCopy; +} + + +/* +================= +FS_FreeFileList +================= +*/ +void FS_FreeFileList( char **filelist ) { + int i; + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !filelist ) { + return; + } + + for ( i = 0 ; filelist[i] ; i++ ) { + Z_Free( filelist[i] ); + } + + Z_Free( filelist ); +} + +/* +=============== +FS_AddFileToListBuf +=============== +*/ +static int FS_AddFileToListBuf( char *name, char *listbuf, int bufsize, int nfiles ) { + char *p; + + if ( nfiles == MAX_FOUND_FILES - 1 ) { + return nfiles; + } + + if (name[0] == '/' || name[0] == '\\') { + name++; + } + + p = listbuf; + while ( *p ) { + if ( !stricmp( name, p ) ) { + return nfiles; // already in list + } + p += strlen( p ) + 1; + } + + if ( ( p + strlen( name ) + 2 - listbuf ) > bufsize ) { + return nfiles; // list is full + } + + strcpy( p, name ); + p += strlen( p ) + 1; + *p = 0; + + return nfiles + 1; +} + +/* +================ +FS_GetFileList + +Returns a uniqued list of files that match the given criteria +from all search paths +================ +*/ +int FS_GetModList( char *listbuf, int bufsize ); +int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { + int nfiles; + searchpath_t *search; + int i; + int pathLength; + int extensionLength; + int length, pathDepth; + pack_t *pak; + fileInPack_t *buildBuffer; + char zpath[MAX_QPATH]; + + if (Q_stricmp(path, "$modlist") == 0) + { + return FS_GetModList(listbuf, bufsize); + } + + if ( !fs_searchpaths ) { + Com_Error( ERR_FATAL, "Filesystem call made without initialization\n" ); + } + + if ( !path ) { + return 0; + } + if ( !extension ) { + extension = ""; + } + + pathLength = strlen( path ); + extensionLength = strlen( extension ); + nfiles = 0; + *listbuf = 0; + FS_ReturnPath(path, zpath, &pathDepth); + // + // search through the path, one element at a time, adding to list + // + for (search = fs_searchpaths ; search ; search = search->next) { + // is the element a pak file? + if (search->pack) { + // look through all the pak file elements + pak = search->pack; + buildBuffer = pak->buildBuffer; + for (i=0 ; inumfiles ; i++) { + char *name; + int zpathLen, depth; + + // check for directory match + name = buildBuffer[i].name; + zpathLen = FS_ReturnPath(name, zpath, &depth); + + if ( (depth-pathDepth)>2 || pathLength > zpathLen || Q_stricmpn( name, path, pathLength ) ) { + continue; + } + + // check for extension match + length = strlen( name ); + if ( length < extensionLength || (length == (extensionLength + pathLength))) { + continue; + } + + if ( stricmp( name + length - extensionLength, extension ) ) { + continue; + } + + // unique the match + nfiles = FS_AddFileToListBuf( name + pathLength, listbuf, bufsize, nfiles ); + } + } else if (search->dir) { // scan for files in the filesystem + char *netpath; + int numSysFiles; + char **sysFiles; + char *name; + + netpath = FS_BuildOSPath( search->dir->path, search->dir->gamedir, path ); + sysFiles = Sys_ListFiles( netpath, extension, &numSysFiles, qfalse ); + for ( i = 0 ; i < numSysFiles ; i++ ) { + // unique the match + name = sysFiles[i]; + nfiles = FS_AddFileToListBuf( name, listbuf, bufsize, nfiles ); + } + Sys_FreeFileList( sysFiles ); + } + } + + return nfiles; +} + +/* +================ +FS_GetModList + +Returns a list of mod directory names +A mod directory is a peer to base with a pk3 in it + +================ +*/ +int FS_GetModList( char *listbuf, int bufsize ) { + int nMods, i, nTotal, nLen, nPaks, nPotential, nDescLen; + char **pFiles = NULL; + char **pPaks = NULL; + char *name, *path; + char descPath[MAX_OSPATH]; + fileHandle_t descHandle; + + *listbuf = 0; + nMods = nPotential = nTotal = 0; + + pFiles = Sys_ListFiles( fs_basepath->string, ".*", &nPotential, qtrue ); + for ( i = 0 ; i < nPotential ; i++ ) { + name = pFiles[i]; + if (Q_stricmp(name, BASEGAME) && Q_stricmpn(name, ".", 1)) { + // ignore base + path = FS_BuildOSPath( fs_basepath->string, name, "" ); + nPaks = 0; + pPaks = Sys_ListFiles(path, ".pk3", &nPaks, qfalse); + if (nPaks > 0) { + nLen = strlen(name) + 1; + // nLen is the length of the mod path + // we need to see if there is a description available + descPath[0] = '\0'; + strcpy(descPath, name); + strcat(descPath, "/description.txt"); + nDescLen = FS_SV_FOpenFileRead( descPath, &descHandle); + if ( nDescLen > 0 && descHandle) { + FILE *file; + file = FS_FileForHandle(descHandle); + memset( descPath, 0, sizeof( descPath ) ); + nDescLen = fread(descPath, 1, 48, file); + if (nDescLen >= 0) { + descPath[nDescLen] = '\0'; + } + FS_FCloseFile(descHandle); + } else { + strcpy(descPath, name); + } + nDescLen = strlen(descPath) + 1; + + if (nTotal + nLen + 1 + nDescLen + 1 < bufsize) { + strcpy(listbuf, name); + listbuf += nLen; + strcpy(listbuf, descPath); + listbuf += nDescLen; + nTotal += nLen + nDescLen; + nMods++; + } + else { + break; + } + } + Sys_FreeFileList( pPaks ); + } + } + Sys_FreeFileList( pFiles ); + + return nMods; +} + +//============================================================================ + +/* +================ +FS_Dir_f +================ +*/ +void FS_Dir_f( void ) { + char *path; + char *extension; + char **dirnames; + int ndirs; + int i; + + if ( Cmd_Argc() < 2 || Cmd_Argc() > 3 ) { + Com_Printf( "usage: dir [extension]\n" ); + return; + } + + if ( Cmd_Argc() == 2 ) { + path = Cmd_Argv( 1 ); + extension = ""; + } else { + path = Cmd_Argv( 1 ); + extension = Cmd_Argv( 2 ); + } + + Com_Printf( "Directory of %s %s\n", path, extension ); + Com_Printf( "---------------\n" ); + + dirnames = FS_ListFiles( path, extension, &ndirs ); + + for ( i = 0; i < ndirs; i++ ) { + Com_Printf( "%s\n", dirnames[i] ); + } + FS_FreeFileList( dirnames ); +} + +/* +============ +FS_Path_f + +============ +*/ +void FS_Path_f( void ) { + searchpath_t *s; + int i; + + Com_Printf ("Current search path:\n"); + for (s=fs_searchpaths ; s ; s=s->next) { + if (s->pack) { + Com_Printf ("%s (%i files)\n", s->pack->pakFilename, s->pack->numfiles); +/* if ( fs_numServerPaks ) { + for ( i = 0 ; i < fs_numServerPaks ; i++ ) { + if ( s->pack->checksum == fs_serverPaks[i] ) { + break; // on the aproved list + } + } + if ( i == fs_numServerPaks ) { + Com_Printf( " not on the pure list\n" ); + } else { + Com_Printf( " on the pure list\n" ); + } + } +*/ + } else { + Com_Printf ("%s/%s\n", s->dir->path, s->dir->gamedir ); + } + } + + + Com_Printf( "\n" ); + for ( i = 1 ; i < MAX_FILE_HANDLES ; i++ ) { + if ( fsh[i].handleFiles.file.o ) { + Com_Printf( "handle %i: %s\n", i, fsh[i].name ); + } + } +} + +/* +============ +FS_TouchFile_f + +The only purpose of this function is to allow game script files to copy +arbitrary files during an "fs_copyfiles 1" run. +============ +*/ +void FS_TouchFile_f( void ) { + fileHandle_t f; + int count = Cmd_Argc(); + + if ( (count == 2) || (count == 3) ) { + FS_FOpenFileRead( Cmd_Argv( 1 ), &f, qfalse ); + if ( f ) { + FS_FCloseFile( f ); + } + if ( count == 3 ) { + FS_FOpenFileRead( Cmd_Argv( 2 ), &f, qfalse ); + if ( f ) { + FS_FCloseFile( f ); + } + } + } + else { + Com_Printf( "Usage: touchFile [file2] -- You gave %d args!\n", Cmd_Argc() ); + } +} + +//=========================================================================== + + + +static int QDECL paksort( const void *a, const void *b ) { + char *aa, *bb; + + aa = *(char **)a; + bb = *(char **)b; + + return stricmp( aa, bb ); +} + + +/* +================ +FS_AddGameDirectory + +Sets fs_gamedir, adds the directory to the head of the path, +then loads the zip headers +================ +*/ +#define MAX_PAKFILES 1024 +static void FS_AddGameDirectory( const char *path, const char *dir ) { + int i; + searchpath_t *search; + pack_t *pak; + char *pakfile; + int numfiles; + char **pakfiles; + char *sorted[MAX_PAKFILES]; + + Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) ); + + // + // add the directory to the search path + // + search = (searchpath_t *)Z_Malloc (sizeof(searchpath_t), TAG_FILESYS, qtrue ); + search->dir = (directory_t*)Z_Malloc( sizeof( *search->dir ), TAG_FILESYS, qtrue ); + search->pack = 0; + Q_strncpyz( search->dir->path, path, sizeof( search->dir->path ) ); + Q_strncpyz( search->dir->gamedir, dir, sizeof( search->dir->gamedir ) ); + search->next = fs_searchpaths; + fs_searchpaths = search; + + Z_Label(search, path); + Z_Label(search->dir, dir); + + // find all pak files in this directory + pakfile = FS_BuildOSPath( path, dir, "" ); + pakfile[ strlen(pakfile) - 1 ] = 0; // strip the trailing slash + +#ifdef PRE_RELEASE_DEMO + pakfile = FS_BuildOSPath( path, dir, "asset0.pksp" ); + if ( ( pak = FS_LoadZipFile( pakfile ) ) == 0 ) + return; + if ( (pak->numfiles^ 0x84268436u) != (DEMO_PAK_MAXFILES^ 0x84268436u)) //don't let them use the full version, even if renamed! + return; + search = (searchpath_t*)Z_Malloc(sizeof(searchpath_t), TAG_FILESYS, qtrue ); + search->pack = pak; + search->dir = 0; + search->next = fs_searchpaths; + fs_searchpaths = search; +#else + pakfiles = Sys_ListFiles( pakfile, ".pk3", &numfiles, qfalse ); + + // sort them so that later alphabetic matches override + // earlier ones. This makes pak1.pk3 override asset0.pk3 + if ( numfiles > MAX_PAKFILES ) { + numfiles = MAX_PAKFILES; + } + for ( i = 0 ; i < numfiles ; i++ ) { + sorted[i] = pakfiles[i]; + } + + qsort( sorted, numfiles, 4, paksort ); + + for ( i = 0 ; i < numfiles ; i++ ) { + pakfile = FS_BuildOSPath( path, dir, sorted[i] ); + if ( ( pak = FS_LoadZipFile( pakfile ) ) == 0 ) + continue; + search = (searchpath_t*)Z_Malloc(sizeof(searchpath_t), TAG_FILESYS, qtrue ); + search->pack = pak; + search->dir = 0; + search->next = fs_searchpaths; + fs_searchpaths = search; + } + + // done + Sys_FreeFileList( pakfiles ); +#endif +} + + + +/* +================ +FS_Startup +================ +*/ +void FS_Startup( const char *gameName ) { + Com_Printf( "----- FS_Startup -----\n" ); + + fs_debug = Cvar_Get( "fs_debug", "0", 0 ); + fs_copyfiles = Cvar_Get( "fs_copyfiles", "0", CVAR_INIT ); + fs_cdpath = Cvar_Get ("fs_cdpath", Sys_DefaultCDPath(), CVAR_INIT ); + fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultBasePath(), CVAR_INIT ); + + fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SERVERINFO ); + fs_restrict = Cvar_Get ("fs_restrict", "", CVAR_INIT ); + Cvar_Get( "com_demo", "", CVAR_INIT ); + + // set up cdpath + if (fs_cdpath->string[0]) { + FS_AddGameDirectory ( fs_cdpath->string, gameName ); + } + + // set up basepath + FS_AddGameDirectory ( fs_basepath->string, gameName ); + + // check for game override + if ( fs_gamedirvar->string[0] && + !Q_stricmp( gameName, BASEGAME ) && + Q_stricmp( fs_gamedirvar->string, gameName ) ) { + if ( fs_cdpath->string[0] ) { + FS_AddGameDirectory( fs_cdpath->string, fs_gamedirvar->string ); + } + FS_AddGameDirectory( fs_basepath->string, fs_gamedirvar->string ); + } + + // add our commands + Cmd_AddCommand ("path", FS_Path_f); + Cmd_AddCommand ("dir", FS_Dir_f ); + Cmd_AddCommand ("touchFile", FS_TouchFile_f ); + + // print the current search paths + FS_Path_f(); + + Com_Printf( "----------------------\n" ); + Com_Printf( "%d files in pk3 files\n", fs_packFiles ); +} + + +/* +=================== +FS_SetRestrictions + +Looks for product keys and restricts media add on ability +if the full version is not found +=================== +*/ +void FS_SetRestrictions( void ) { + searchpath_t *path; + +#ifndef PRE_RELEASE_DEMO + byte *productId; + + // if fs_restrict is set, don't even look for the id file, + // which allows the demo release to be tested even if + // the full game is present + if ( !fs_restrict->integer ) { + // look for the full game id + FS_ReadFile( "productid.txt", (void **)&productId ); + if ( productId ) { + // check against the hardcoded string + unsigned int seed, i; + + seed = 102270; + for ( i = 0 ; i < sizeof( fs_scrambledProductId ) ; i++ ) { +#if 0 + fs_scrambledProductId[i] = productId[i] ^ (seed&255); + Com_Printf("%3i, ", fs_scrambledProductId[i]); +#endif + if ( ( fs_scrambledProductId[i] ^ (seed&255) ) != productId[i] ) { + break; + } + seed = (69069 * seed + 1); + } + + FS_FreeFile( productId ); + + if ( i == sizeof( fs_scrambledProductId ) ) { + return; // no restrictions + } + Com_Error( ERR_FATAL, "Invalid product identification" ); + } + } +#endif + Cvar_Set( "fs_restrict", "1" ); + Cvar_Set( "com_demo", "1" ); + + Com_Printf( "\nRunning in restricted demo mode.\n\n" ); + + // restart the filesystem with just the demo directory + FS_Shutdown(); + FS_Startup( DEMOGAME ); + + // make sure that the pak file has the header checksum we expect + for ( path = fs_searchpaths ; path ; path = path->next ) { + if ( path->pack ) { + // a tiny attempt to keep the checksum from being scannable from the exe + if ( (path->pack->checksum ^ 0x84268436u) != (DEMO_PAK_CHECKSUM ^ 0x84268436u) ) { + Com_Error( ERR_FATAL, "Corrupted pk3: %u", path->pack->checksum ); + } + } + } +} + + +/* +================ +FS_Restart +================ +*/ + +void FS_Restart( void ) { + // free anything we currently have loaded + FS_Shutdown(); + + // try to start up normally + FS_Startup( BASEGAME ); + + // see if we are going to allow add-ons + FS_SetRestrictions(); + + // if we can't find default.cfg, assume that the paths are + // busted and error out now, rather than getting an unreadable + // graphics screen when the font fails to load + if ( FS_ReadFile( "default.cfg", NULL ) <= 0 ) { + Com_Error( ERR_FATAL, "Couldn't load default.cfg" ); + } +} + +/* +======================================================================================== + +Handle based file calls for virtual machines + +======================================================================================== +*/ + +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + int r; + qboolean sync; + + sync = qfalse; + + switch( mode ) { + case FS_READ: + r = FS_FOpenFileRead( qpath, f, qtrue ); + break; + case FS_WRITE: + *f = FS_FOpenFileWrite( qpath ); + r = 0; + break; + case FS_APPEND_SYNC: + sync = qtrue; + case FS_APPEND: + *f = FS_FOpenFileAppend( qpath ); + r = 0; + break; + default: + Com_Error( ERR_FATAL, "FSH_FOpenFile: bad mode" ); + return -1; + } + + if ( *f ) { + if (fsh[*f].zipFile == (int)qtrue) { + fsh[*f].baseOffset = unztell(fsh[*f].handleFiles.file.z); + } else { + fsh[*f].baseOffset = ftell(fsh[*f].handleFiles.file.o); + } + fsh[*f].fileSize = r; + } + fsh[*f].handleSync = sync; + + return r; +} + +int FS_FTell( fileHandle_t f ) { + int pos; + if (fsh[f].zipFile == (int)qtrue) { + pos = unztell(fsh[f].handleFiles.file.z); + } else { + pos = ftell(fsh[f].handleFiles.file.o); + } + return pos; +} + + diff --git a/code/qcommon/fixedmap.h b/code/qcommon/fixedmap.h new file mode 100644 index 0000000..50101d2 --- /dev/null +++ b/code/qcommon/fixedmap.h @@ -0,0 +1,169 @@ +#ifndef __FIXEDMAP_H +#define __FIXEDMAP_H + + +/* + An STL map-like container. Quickly thrown together to replace STL maps + in specific instances. Many gotchas. Use with caution. +*/ + + +#include + + +template < class T, class U > +class VVFixedMap +{ +private: + struct Data { + T data; + U key; + }; + + Data *items; + unsigned int numItems; + unsigned int maxItems; + + VVFixedMap(void) {} + +public: + VVFixedMap(unsigned int maxItems) + { + items = new Data[maxItems]; + numItems = 0; + this->maxItems = maxItems; + } + + + ~VVFixedMap(void) + { + items -= ( maxItems - numItems ); + delete [] items; + numItems = 0; + } + + + bool Insert(const T &newItem, const U &key) + { + Data *storage = NULL; + + //Check for fullness. + if(numItems >= maxItems) { + assert( 0 ); + return false; + } + + //Check for reuse. + if(!FindUnsorted(key, storage)) { + storage = items + numItems; + numItems++; + } + else + assert( 0 ); + + storage->data = newItem; + storage->key = key; + + return true; + } + + // Faster version of Insert(), but it doesn't check for dupes. Used + // by the filecode cache (when we know the data we're inserting is good). + bool InsertUnsafe(const T &newItem, const U &key) + { + //Check for fullness. + if(numItems >= maxItems) { + return false; + } + + Data *storage = items + numItems; + numItems++; + + storage->data = newItem; + storage->key = key; + + return true; + } + + + void Sort(void) + { + qsort(items, numItems, sizeof(Data), + VVFixedMap< T, U >::FixedMapSorter); + } + + + //Binary search, items must have been sorted! + T *Find(const U &key) + { + int i; + int high; + int low; + + for(low = -1, high = numItems; high - low > 1; ) { + i = (high + low) / 2; + if(key < items[i].key) { + high = i; + } else if(key > items[i].key) { + low = i; + } else { + return &items[i].data; + } + } + + if(items[i+1].key == key) { + return &items[i+1].data; + } else if(items[i-1].key == key) { + return &items[i-1].data; + } + + return NULL; + } + + + //Slower, but don't need to call sort first. + T *FindUnsorted(const U &key, Data *&storage) + { + int i; + + for(i=0; ikey > ((Data*)b)->key) { + return 1; + } else if(((Data*)a)->key == ((Data*)b)->key) { + return 0; + } else { + return -1; + } + } +}; + + +#endif diff --git a/code/qcommon/hstring.cpp b/code/qcommon/hstring.cpp new file mode 100644 index 0000000..ab74cfa --- /dev/null +++ b/code/qcommon/hstring.cpp @@ -0,0 +1,525 @@ +#include "cm_local.h" +#include "hstring.h" + +#if defined (_DEBUG) && defined (_WIN32) +#define WIN32_LEAN_AND_MEAN 1 +//#include // for Sleep for Z_Malloc recovery attempy +#include "platform.h" +#endif + +// mapPoolBlockCount is defined differently in the executable (sv_main.cpp) and the game dll (g_main.cpp) cuz +//we likely don't need as many blocks in the executable as we do in the game +extern int mapPoolBlockCount; + +// Used to fool optimizer during compilation of mem touch routines. +int HaHaOptimizer2=0; + +#ifndef _XBOX +CMapPoolLow &GetMapPool() +{ + // this may need to be ifdefed to be different for different modules + static CMapPoolLow thePool; + return thePool; +} +#endif + +#define MAPBLOCK_SIZE_NODES (1024) +#define MAPNODE_FREE (0xa1) +#define MAPNODE_INUSE (0x94) + +struct SMapNode +{ + unsigned char mData[MAP_NODE_SIZE-2]; + unsigned char mMapBlockNum; + unsigned char mTag; +}; + +class CMapBlock +{ + int mId; + char mRaw[(MAPBLOCK_SIZE_NODES+1)*MAP_NODE_SIZE]; + SMapNode *mNodes; + int mLastNode; + +public: + CMapBlock(int id,vector &freeList) : + mLastNode(0) + { + // Alloc node storage for MAPBLOCK_SIZE_NODES worth of nodes. + mNodes=(SMapNode *)((((unsigned long)mRaw)+MAP_NODE_SIZE)&~(unsigned long)0x1f); + // Set all nodes to initially be free. + int i; + for(i=0;i=&mNodes[0])&&(((SMapNode *)node)<&mNodes[MAPBLOCK_SIZE_NODES])); + } +}; + +#ifndef _XBOX +CMapPoolLow::CMapPoolLow() +{ + mLastBlockNum=-1; +} + +CMapPoolLow::~CMapPoolLow() +{ +#if _DEBUG + char mess[1000]; +#if _GAME + if(mFreeList.size()mTag==MAPNODE_FREE); + assert((((SMapNode *)node)->mMapBlockNum)>=0); + assert((((SMapNode *)node)->mMapBlockNum)<256); + assert((((SMapNode *)node)->mMapBlockNum)<=mLastBlockNum); + assert(mMapBlocks[((SMapNode *)node)->mMapBlockNum]->bOwnsNode(node)); + + // Ok, mark the node as in use. + ((SMapNode *)node)->mTag=MAPNODE_INUSE; + + return(node); +} + +void CMapPoolLow::Free(void *p) +{ + // Validate that someone isn't trying to double free this node and also + // that the end marker is intact. + assert(((SMapNode *)p)->mTag==MAPNODE_INUSE); + assert((((SMapNode *)p)->mMapBlockNum)>=0); + assert((((SMapNode *)p)->mMapBlockNum)<256); + assert((((SMapNode *)p)->mMapBlockNum)<=mLastBlockNum); + assert(mMapBlocks[((SMapNode *)p)->mMapBlockNum]->bOwnsNode(p)); + + // Ok, mark the the node as free. + ((SMapNode *)p)->mTag=MAPNODE_FREE; + + // Add a new freelist entry to point at this node. + mFreeList.push_back(p); +} + +void CMapPoolLow::TouchMem() +{ + int i,j; + unsigned char *memory; + int totSize=0; + for(i=0;i=0&&hash=0&&mFindPtr(BLOCK_SIZE-mBytesUsed)) + { + return(0); + } + + // Return the pointer to the start of allocated space. + char *ret=&mRaw[mBytesUsed]; + mBytesUsed+=sizeBytes; + return ret; + } + + bool operator== (const CHSBlock &block) const + { + if(!memcmp(mRaw,block.mRaw,BLOCK_SIZE)) + { + return(true); + } + return(false); + } +}; + +class CPool +{ + vector mBlockVec; + +public: + int mNextStringId; + int mLastBlockNum; + + CPool(void) : + mNextStringId(1), + mLastBlockNum(-1) + { + memset(gCharPtrs,0,MAX_HSTRINGS*4); + } + + ~CPool(void) + { + int i; + for (i=0;i=0) + { + // Get the pointer to the start of allocated space in the current block. + raw=mBlockVec[mLastBlockNum]->Alloc(sizeBytes); + } + if(!raw) + { + // Ok, make a new empty block and append it. + CHSBlock *block=new(CHSBlock); + mBlockVec.push_back(block); + mLastBlockNum++; + raw=mBlockVec[mLastBlockNum]->Alloc(sizeBytes); + } + // Should never really happen!! + assert(raw); + + id=mNextStringId; + gCharPtrs[mNextStringId]=raw; + mNextStringId++; + + return(raw); + } + + bool operator== (const CPool &pool) const + { + int i; + for(i=0;i0&&id0&&mId0&&mId +#include +#include +#include +#pragma warning (pop) + +using namespace std; + +class hstring +{ + int mId; + + void Init(const char *str); + +public: + hstring() + { + mId=0; + } + hstring(const char *str) + { + Init(str); + } + hstring(const string &str) + { + Init(str.c_str()); + } + hstring(const hstring &str) + { + mId=str.mId; + } + + operator string () const + { + return str(); + } + + const char *c_str(void) const; + string str(void) const; + + hstring& operator= (const char *str) + { + Init(str); + return *this; + } + hstring& operator= (const string &str) + { + Init(str.c_str()); + return *this; + } + hstring& operator= (const hstring &str) + { + mId=str.mId; + return *this; + } + + bool operator== (const hstring &str) const + { + return((mId==str.mId)?true:false); + } + + int compare(const hstring &str) const + { + return strcmp(c_str(),str.c_str()); + } + + bool operator< (const hstring &str) const + { + return((mId mMapBlocks; + vector mFreeList; + int mLastBlockNum; + +public: + CMapPoolLow(); + ~CMapPoolLow(); + void *Alloc(); + void Free(void *p); + void TouchMem(); +}; + +CMapPoolLow &GetMapPool(); + +template +class CMapPool +{ + CMapPoolLow &mPool; +public: + CMapPool() : mPool(GetMapPool()) + { + + } + template + CMapPool(const U&) : mPool(GetMapPool()) + { + } + ~CMapPool() + { + } + + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + template + struct rebind + { + typedef CMapPool other; + }; + + // return address of values + pointer address (reference value) const + { + return &value; + } + const_pointer address (const_reference value) const + { + return &value; + } + + // return maximum number of elements that can be allocated + size_type max_size () const + { +// return mMaxSize; + return 0xfffffff; //uh, take a guess + } + + // allocate but don't initialize num elements of type T + pointer allocate (size_type num, const void* = 0) + { + assert(sizeof(T)<=(MAP_NODE_SIZE-2)); // to big for this pool + assert(num==1); //allocator not design for this + return (T*)mPool.Alloc(); + } + void *_Charalloc(size_type size) + { + assert(size<=(MAP_NODE_SIZE-2)); // to big for this pool + return mPool.Alloc(); + } + + // initialize elements of allocated storage p with value value + void construct (pointer p, const T& value) + { + // initialize memory with placement new + new((void*)p)T(value); + } + + // destroy elements of initialized storage p + void destroy (pointer p) + { + // destroy objects by calling their destructor + p->~T(); + } + + // deallocate storage p of deleted elements + template + void deallocate (U *p, size_type num) + { + assert(num==1); //allocator not design for this + mPool.Free(p); + } +}; + +template +bool operator== (const CMapPool&, + const CMapPool&) +{ + return false; +} +template +bool operator!= (const CMapPool&, + const CMapPool&) +{ + return true; +} + + +template > +class hmap : public map >{}; + +template > +class hmultimap : public multimap >{}; + +template > +class hset : public set >{}; + +template > +class hmultiset : public multiset >{}; + +template +class hlist : public list >{}; + +#endif // hString_H \ No newline at end of file diff --git a/code/qcommon/md4.cpp b/code/qcommon/md4.cpp new file mode 100644 index 0000000..c8070df --- /dev/null +++ b/code/qcommon/md4.cpp @@ -0,0 +1,274 @@ +/* GLOBAL.H - RSAREF types and constants */ + +#include + +/* POINTER defines a generic pointer type */ +typedef unsigned char *POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + + +/* MD4.H - header file for MD4C.C */ + +/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. + +All rights reserved. + +License to copy and use this software is granted provided that it is identified as the “RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as “derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm” in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided “as is” without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* MD4 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD4_CTX; + +void MD4Init (MD4_CTX *); +void MD4Update (MD4_CTX *, const unsigned char *, unsigned int); +void MD4Final (unsigned char [16], MD4_CTX *); + + + +/* MD4C.C - RSA Data Security, Inc., MD4 message-digest algorithm */ +/* Copyright (C) 1990-2, RSA Data Security, Inc. All rights reserved. + +License to copy and use this software is granted provided that it is identified as the +RSA Data Security, Inc. MD4 Message-Digest Algorithm + in all material mentioning or referencing this software or this function. +License is also granted to make and use derivative works provided that such works are identified as +derived from the RSA Data Security, Inc. MD4 Message-Digest Algorithm +in all material mentioning or referencing the derived work. +RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided +as is without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this documentation and/or software. */ + +/* Constants for MD4Transform routine. */ +#define S11 3 +#define S12 7 +#define S13 11 +#define S14 19 +#define S21 3 +#define S22 5 +#define S23 9 +#define S24 13 +#define S31 3 +#define S32 9 +#define S33 11 +#define S34 15 + +static void MD4Transform (UINT4 [4], const unsigned char [64]); +static void Encode (unsigned char *, UINT4 *, unsigned int); +static void Decode (UINT4 *, const unsigned char *, unsigned int); + +static unsigned char PADDING[64] = { +0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G and H are basic MD4 functions. */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG and HH are transformations for rounds 1, 2 and 3 */ +/* Rotation is separate from addition to prevent recomputation */ +#define FF(a, b, c, d, x, s) {(a) += F ((b), (c), (d)) + (x); (a) = ROTATE_LEFT ((a), (s));} + +#define GG(a, b, c, d, x, s) {(a) += G ((b), (c), (d)) + (x) + (UINT4)0x5a827999; (a) = ROTATE_LEFT ((a), (s));} + +#define HH(a, b, c, d, x, s) {(a) += H ((b), (c), (d)) + (x) + (UINT4)0x6ed9eba1; (a) = ROTATE_LEFT ((a), (s));} + + +/* MD4 initialization. Begins an MD4 operation, writing a new context. */ +void MD4Init (MD4_CTX *context) +{ + context->count[0] = context->count[1] = 0; + +/* Load magic initialization constants.*/ +context->state[0] = 0x67452301; +context->state[1] = 0xefcdab89; +context->state[2] = 0x98badcfe; +context->state[3] = 0x10325476; +} + +/* MD4 block update operation. Continues an MD4 message-digest operation, processing another message block, and updating the context. */ +void MD4Update (MD4_CTX *context, const unsigned char *input, unsigned int inputLen) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3))< ((UINT4)inputLen << 3)) + context->count[1]++; + + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible.*/ + if (inputLen >= partLen) + { + memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD4Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD4Transform (context->state, &input[i]); + + index = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy ((POINTER)&context->buffer[index], (POINTER)&input[i], inputLen-i); +} + + +/* MD4 finalization. Ends an MD4 message-digest operation, writing the the message digest and zeroizing the context. */ +void MD4Final (unsigned char digest[16], MD4_CTX *context) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64.*/ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD4Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD4Update (context, bits, 8); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information.*/ + memset ((POINTER)context, 0, sizeof (*context)); +} + + +/* MD4 basic transformation. Transforms state based on block. */ +static void MD4Transform (UINT4 state[4], const unsigned char block[64]) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + +/* Round 1 */ +FF (a, b, c, d, x[ 0], S11); /* 1 */ +FF (d, a, b, c, x[ 1], S12); /* 2 */ +FF (c, d, a, b, x[ 2], S13); /* 3 */ +FF (b, c, d, a, x[ 3], S14); /* 4 */ +FF (a, b, c, d, x[ 4], S11); /* 5 */ +FF (d, a, b, c, x[ 5], S12); /* 6 */ +FF (c, d, a, b, x[ 6], S13); /* 7 */ +FF (b, c, d, a, x[ 7], S14); /* 8 */ +FF (a, b, c, d, x[ 8], S11); /* 9 */ +FF (d, a, b, c, x[ 9], S12); /* 10 */ +FF (c, d, a, b, x[10], S13); /* 11 */ +FF (b, c, d, a, x[11], S14); /* 12 */ +FF (a, b, c, d, x[12], S11); /* 13 */ +FF (d, a, b, c, x[13], S12); /* 14 */ +FF (c, d, a, b, x[14], S13); /* 15 */ +FF (b, c, d, a, x[15], S14); /* 16 */ + +/* Round 2 */ +GG (a, b, c, d, x[ 0], S21); /* 17 */ +GG (d, a, b, c, x[ 4], S22); /* 18 */ +GG (c, d, a, b, x[ 8], S23); /* 19 */ +GG (b, c, d, a, x[12], S24); /* 20 */ +GG (a, b, c, d, x[ 1], S21); /* 21 */ +GG (d, a, b, c, x[ 5], S22); /* 22 */ +GG (c, d, a, b, x[ 9], S23); /* 23 */ +GG (b, c, d, a, x[13], S24); /* 24 */ +GG (a, b, c, d, x[ 2], S21); /* 25 */ +GG (d, a, b, c, x[ 6], S22); /* 26 */ +GG (c, d, a, b, x[10], S23); /* 27 */ +GG (b, c, d, a, x[14], S24); /* 28 */ +GG (a, b, c, d, x[ 3], S21); /* 29 */ +GG (d, a, b, c, x[ 7], S22); /* 30 */ +GG (c, d, a, b, x[11], S23); /* 31 */ +GG (b, c, d, a, x[15], S24); /* 32 */ + +/* Round 3 */ +HH (a, b, c, d, x[ 0], S31); /* 33 */ +HH (d, a, b, c, x[ 8], S32); /* 34 */ +HH (c, d, a, b, x[ 4], S33); /* 35 */ +HH (b, c, d, a, x[12], S34); /* 36 */ +HH (a, b, c, d, x[ 2], S31); /* 37 */ +HH (d, a, b, c, x[10], S32); /* 38 */ +HH (c, d, a, b, x[ 6], S33); /* 39 */ +HH (b, c, d, a, x[14], S34); /* 40 */ +HH (a, b, c, d, x[ 1], S31); /* 41 */ +HH (d, a, b, c, x[ 9], S32); /* 42 */ +HH (c, d, a, b, x[ 5], S33); /* 43 */ +HH (b, c, d, a, x[13], S34); /* 44 */ +HH (a, b, c, d, x[ 3], S31); /* 45 */ +HH (d, a, b, c, x[11], S32); /* 46 */ +HH (c, d, a, b, x[ 7], S33); /* 47 */ +HH (b, c, d, a, x[15], S34); /* 48 */ + +state[0] += a; +state[1] += b; +state[2] += c; +state[3] += d; + + /* Zeroize sensitive information.*/ + memset ((POINTER)x, 0, sizeof (x)); +} + + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4. */ +static void Encode (unsigned char *output, UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is a multiple of 4. */ +static void Decode (UINT4 *output, const unsigned char *input, unsigned int len) +{ +unsigned int i, j; + +for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j+1]) << 8) | (((UINT4)input[j+2]) << 16) | (((UINT4)input[j+3]) << 24); +} + +//=================================================================== + +unsigned Com_BlockChecksum (void const *buffer, int length) +{ + int digest[4]; + unsigned val; + MD4_CTX ctx; + + MD4Init (&ctx); + MD4Update (&ctx, (unsigned char *)buffer, length); + MD4Final ( (unsigned char *)digest, &ctx); + + val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; + + return val; +} diff --git a/code/qcommon/msg.cpp b/code/qcommon/msg.cpp new file mode 100644 index 0000000..3b84762 --- /dev/null +++ b/code/qcommon/msg.cpp @@ -0,0 +1,1248 @@ + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "../server/server.h" + + + +/* +============================================================================== + + MESSAGE IO FUNCTIONS + +Handles byte ordering and avoids alignment errors +============================================================================== +*/ + + +void MSG_Init( msg_t *buf, byte *data, int length ) { + memset (buf, 0, sizeof(*buf)); + buf->data = data; + buf->maxsize = length; +} + +void MSG_Clear( msg_t *buf ) { + buf->cursize = 0; + buf->overflowed = qfalse; + buf->bit = 0; +} + + +void MSG_BeginReading( msg_t *msg ) { + msg->readcount = 0; + msg->bit = 0; +} + + +void MSG_ReadByteAlign( msg_t *buf ) { + // round up to the next byte + if ( buf->bit ) { + buf->bit = 0; + buf->readcount++; + } +} + +void *MSG_GetSpace( msg_t *buf, int length ) { + void *data; + + // round up to the next byte + if ( buf->bit ) { + buf->bit = 0; + buf->cursize++; + } + + if ( buf->cursize + length > buf->maxsize ) { + if ( !buf->allowoverflow ) { + Com_Error (ERR_FATAL, "MSG_GetSpace: overflow without allowoverflow set"); + } + if ( length > buf->maxsize ) { + Com_Error (ERR_FATAL, "MSG_GetSpace: %i is > full buffer size", length); + } + Com_Printf ("MSG_GetSpace: overflow\n"); + MSG_Clear (buf); + buf->overflowed = qtrue; + } + + data = buf->data + buf->cursize; + buf->cursize += length; + + return data; +} + +void MSG_WriteData( msg_t *buf, const void *data, int length ) { + memcpy (MSG_GetSpace(buf,length),data,length); +} + + +/* +============================================================================= + +bit functions + +============================================================================= +*/ + +int overflows; + +// negative bit values include signs +void MSG_WriteBits( msg_t *msg, int value, int bits ) { + int put; + int fraction; + + // this isn't an exact overflow check, but close enough + if ( msg->maxsize - msg->cursize < 4 ) { + msg->overflowed = qtrue; +#ifndef FINAL_BUILD + Com_Printf (S_COLOR_RED"MSG_WriteBits: buffer Full writing %d in %d bits\n", value, bits); +#endif + return; + } + + if ( bits == 0 || bits < -31 || bits > 32 ) { + Com_Error( ERR_DROP, "MSG_WriteBits: bad bits %i", bits ); + } + + // check for overflows + if ( bits != 32 ) { + if ( bits > 0 ) { + if ( value > ( ( 1 << bits ) - 1 ) || value < 0 ) { + overflows++; +#ifndef FINAL_BUILD +#ifdef _DEBUG + Com_Printf (S_COLOR_RED"MSG_WriteBits: overflow writing %d in %d bits\n", value, bits); +#endif +#endif + } + } else { + int r; + + r = 1 << (bits-1); + + if ( value > r - 1 || value < -r ) { + overflows++; +#ifndef FINAL_BUILD +#ifdef _DEBUG + Com_Printf (S_COLOR_RED"MSG_WriteBits: overflow writing %d in %d bits\n", value, bits); +#endif +#endif + } + } + } + if ( bits < 0 ) { + bits = -bits; + } + + while ( bits ) { + if ( msg->bit == 0 ) { + msg->data[msg->cursize] = 0; + msg->cursize++; + } + put = 8 - msg->bit; + if ( put > bits ) { + put = bits; + } + fraction = value & ( ( 1 << put ) - 1 ); + msg->data[msg->cursize - 1] |= fraction << msg->bit; + bits -= put; + value >>= put; + msg->bit = ( msg->bit + put ) & 7; + } +} + +int MSG_ReadBits( msg_t *msg, int bits ) { + int value; + int valueBits; + int get; + int fraction; + qboolean sgn; + + value = 0; + valueBits = 0; + + if ( bits < 0 ) { + bits = -bits; + sgn = qtrue; + } else { + sgn = qfalse; + } + + while ( valueBits < bits ) { + if ( msg->bit == 0 ) { + msg->readcount++; + assert (msg->readcount <= msg->cursize); + } + get = 8 - msg->bit; + if ( get > (bits - valueBits) ) { + get = (bits - valueBits); + } + fraction = msg->data[msg->readcount - 1]; + fraction >>= msg->bit; + fraction &= ( 1 << get ) - 1; + value |= fraction << valueBits; + + valueBits += get; + msg->bit = ( msg->bit + get ) & 7; + } + + if ( sgn ) { + if ( value & ( 1 << ( bits - 1 ) ) ) { + value |= -1 ^ ( ( 1 << bits ) - 1 ); + } + } + + return value; +} + + + +//================================================================================ + +// +// writing functions +// + +void MSG_WriteByte( msg_t *sb, int c ) { +#ifdef PARANOID + if (c < 0 || c > 255) + Com_Error (ERR_FATAL, "MSG_WriteByte: range error"); +#endif + + MSG_WriteBits( sb, c, 8 ); +} + +void MSG_WriteShort( msg_t *sb, int c ) { +#ifdef PARANOID + if (c < ((short)0x8000) || c > (short)0x7fff) + Com_Error (ERR_FATAL, "MSG_WriteShort: range error"); +#endif + + MSG_WriteBits( sb, c, 16 ); +} + +static void MSG_WriteSShort( msg_t *sb, int c ) { + MSG_WriteBits( sb, c, -16 ); +} + +void MSG_WriteLong( msg_t *sb, int c ) { + MSG_WriteBits( sb, c, 32 ); +} + +void MSG_WriteString( msg_t *sb, const char *s ) { + if ( !s ) { + MSG_WriteData (sb, "", 1); + } else { + int l, i; + char string[MAX_STRING_CHARS]; + + l = strlen( s ); + if ( l >= MAX_STRING_CHARS ) { + Com_Printf( "MSG_WriteString: MAX_STRING_CHARS" ); + MSG_WriteData (sb, "", 1); + return; + } + Q_strncpyz( string, s, sizeof( string ) ); + + // get rid of 0xff chars, because old clients don't like them + for ( i = 0 ; i < l ; i++ ) { + if ( ((byte *)string)[i] > 127 ) { + string[i] = '.'; + } + } + + MSG_WriteData (sb, string, l+1); + } +} + + + +//============================================================ + +// +// reading functions +// + +// returns -1 if no more characters are available +int MSG_ReadByte( msg_t *msg ) { + int c; + + if ( msg->readcount+1 > msg->cursize ) { + c = -1; + } else { + c = (unsigned char)MSG_ReadBits( msg, 8 ); + } + + return c; +} + +int MSG_ReadShort( msg_t *msg ) { + int c; + + if ( msg->readcount+2 > msg->cursize ) { + c = -1; + } else { + c = MSG_ReadBits( msg, 16 ); + } + + return c; +} + +static int MSG_ReadSShort( msg_t *msg ) { + int c; + + if ( msg->readcount+2 > msg->cursize ) { + c = -1; + } else { + c = MSG_ReadBits( msg, -16 ); + } + + return c; +} + +int MSG_ReadLong( msg_t *msg ) { + int c; + + if ( msg->readcount+4 > msg->cursize ) { + c = -1; + } else { + c = MSG_ReadBits( msg, 32 ); + } + + return c; +} + +char *MSG_ReadString( msg_t *msg ) { + static char string[MAX_STRING_CHARS]; + int l,c; + + MSG_ReadByteAlign( msg ); + l = 0; + do { + c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if ( c == -1 || c == 0 ) { + break; + } + // translate all fmt spec to avoid crash bugs + if ( c == '%' ) { + c = '.'; + } + + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +char *MSG_ReadStringLine( msg_t *msg ) { + static char string[MAX_STRING_CHARS]; + int l,c; + + MSG_ReadByteAlign( msg ); + l = 0; + do { + c = MSG_ReadByte(msg); // use ReadByte so -1 is out of bounds + if (c == -1 || c == 0 || c == '\n') { + break; + } + // translate all fmt spec to avoid crash bugs + if ( c == '%' ) { + c = '.'; + } + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + + +void MSG_ReadData( msg_t *msg, void *data, int len ) { + int i; + + MSG_ReadByteAlign( msg ); + for (i=0 ; iinteger == 4 ) { Com_Printf("%s ", x ); }; + +void MSG_WriteDelta( msg_t *msg, int oldV, int newV, int bits ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, newV, bits ); +} + +int MSG_ReadDelta( msg_t *msg, int oldV, int bits ) { + if ( MSG_ReadBits( msg, 1 ) ) { + return MSG_ReadBits( msg, bits ); + } + return oldV; +} + +void MSG_WriteDeltaFloat( msg_t *msg, float oldV, float newV ) { + if ( oldV == newV ) { + MSG_WriteBits( msg, 0, 1 ); + return; + } + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *(int *)&newV, 32 ); +} + +float MSG_ReadDeltaFloat( msg_t *msg, float oldV ) { + if ( MSG_ReadBits( msg, 1 ) ) { + float newV; + + *(int *)&newV = MSG_ReadBits( msg, 32 ); + return newV; + } + return oldV; +} + + +/* +============================================================================ + +usercmd_t communication + +============================================================================ +*/ + +// ms is allways sent, the others are optional +#define CM_ANGLE1 (1<<0) +#define CM_ANGLE2 (1<<1) +#define CM_ANGLE3 (1<<2) +#define CM_FORWARD (1<<3) +#define CM_SIDE (1<<4) +#define CM_UP (1<<5) +#define CM_BUTTONS (1<<6) +#define CM_WEAPON (1<<7) + +/* +===================== +MSG_WriteDeltaUsercmd +===================== +*/ +void MSG_WriteDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) { + MSG_WriteDelta( msg, from->serverTime, to->serverTime, 32 ); + MSG_WriteDelta( msg, from->angles[0], to->angles[0], 16 ); + MSG_WriteDelta( msg, from->angles[1], to->angles[1], 16 ); + MSG_WriteDelta( msg, from->angles[2], to->angles[2], 16 ); + MSG_WriteDelta( msg, from->forwardmove, to->forwardmove, -8 ); + MSG_WriteDelta( msg, from->rightmove, to->rightmove, -8 ); + MSG_WriteDelta( msg, from->upmove, to->upmove, -8 ); + MSG_WriteDelta( msg, from->buttons, to->buttons, 16 );//FIXME: We're only really using 9 bits...can this be changed to that? + MSG_WriteDelta( msg, from->weapon, to->weapon, 8 ); + MSG_WriteDelta( msg, from->generic_cmd, to->generic_cmd, 8 ); +} + + +/* +===================== +MSG_ReadDeltaUsercmd +===================== +*/ +void MSG_ReadDeltaUsercmd( msg_t *msg, usercmd_t *from, usercmd_t *to ) { + to->serverTime = MSG_ReadDelta( msg, from->serverTime, 32); + to->angles[0] = MSG_ReadDelta( msg, from->angles[0], 16); + to->angles[1] = MSG_ReadDelta( msg, from->angles[1], 16); + to->angles[2] = MSG_ReadDelta( msg, from->angles[2], 16); + to->forwardmove = MSG_ReadDelta( msg, from->forwardmove, -8); + to->rightmove = MSG_ReadDelta( msg, from->rightmove, -8); + to->upmove = MSG_ReadDelta( msg, from->upmove, -8); + to->buttons = MSG_ReadDelta( msg, from->buttons, 16);//FIXME: We're only really using 9 bits...can this be changed to that? + to->weapon = MSG_ReadDelta( msg, from->weapon, 8); + to->generic_cmd = MSG_ReadDelta( msg, from->generic_cmd, 8); +} + +/* +============================================================================= + +entityState_t communication + +============================================================================= +*/ + +typedef struct { + char *name; + int offset; + int bits; // 0 = float +} netField_t; + +// using the stringizing operator to save typing... +#define NETF(x) #x,(int)&((entityState_t*)0)->x + +#if 0 // Removed by BTO (VV) +const netField_t entityStateFields[] = +{ +{ NETF(eType), 8 }, +{ NETF(eFlags), 32 }, + +{ NETF(pos.trType), 8 }, +{ NETF(pos.trTime), 32 }, +{ NETF(pos.trDuration), 32 }, +{ NETF(pos.trBase[0]), 0 }, +{ NETF(pos.trBase[1]), 0 }, +{ NETF(pos.trBase[2]), 0 }, +{ NETF(pos.trDelta[0]), 0 }, +{ NETF(pos.trDelta[1]), 0 }, +{ NETF(pos.trDelta[2]), 0 }, + +{ NETF(apos.trType), 8 }, +{ NETF(apos.trTime), 32 }, +{ NETF(apos.trDuration), 32 }, +{ NETF(apos.trBase[0]), 0 }, +{ NETF(apos.trBase[1]), 0 }, +{ NETF(apos.trBase[2]), 0 }, +{ NETF(apos.trDelta[0]), 0 }, +{ NETF(apos.trDelta[1]), 0 }, +{ NETF(apos.trDelta[2]), 0 }, + +{ NETF(time), 32 }, +{ NETF(time2), 32 }, + +{ NETF(origin[0]), 0 }, +{ NETF(origin[1]), 0 }, +{ NETF(origin[2]), 0 }, + +{ NETF(origin2[0]), 0 }, +{ NETF(origin2[1]), 0 }, +{ NETF(origin2[2]), 0 }, + +{ NETF(angles[0]), 0 }, +{ NETF(angles[1]), 0 }, +{ NETF(angles[2]), 0 }, + +{ NETF(angles2[0]), 0 }, +{ NETF(angles2[1]), 0 }, +{ NETF(angles2[2]), 0 }, + +{ NETF(otherEntityNum), GENTITYNUM_BITS }, +//{ NETF(otherEntityNum2), GENTITYNUM_BITS }, +{ NETF(groundEntityNum), GENTITYNUM_BITS }, + +{ NETF(constantLight), 32 }, +{ NETF(loopSound), 16 }, +{ NETF(modelindex), 9 }, //0 to 511 +{ NETF(modelindex2), 8 }, +{ NETF(modelindex3), 8 }, +{ NETF(clientNum), 32 }, +{ NETF(frame), 16 }, + +{ NETF(solid), 24 }, + +{ NETF(event), 10 }, +{ NETF(eventParm), 16 }, + +{ NETF(powerups), 16 }, +{ NETF(weapon), 8 }, +{ NETF(legsAnim), 16 }, +{ NETF(legsAnimTimer), 8 }, +{ NETF(torsoAnim), 16 }, +{ NETF(torsoAnimTimer), 8 }, +{ NETF(scale), 8 }, + +{ NETF(saberInFlight), 4 }, +{ NETF(saberActive), 4 }, +{ NETF(vehicleArmor), 32 }, +{ NETF(vehicleAngles[0]), 0 }, +{ NETF(vehicleAngles[1]), 0 }, +{ NETF(vehicleAngles[2]), 0 }, +{ NETF(m_iVehicleNum), 32 }, + +/* +Ghoul2 Insert Start +*/ +{ NETF(modelScale[0]), 0 }, +{ NETF(modelScale[1]), 0 }, +{ NETF(modelScale[2]), 0 }, +{ NETF(radius), 16 }, +{ NETF(boltInfo), 32 }, +//{ NETF(ghoul2), 32 }, + +{ NETF(isPortalEnt), 1 }, + +}; +#endif + + +// if (int)f == f and (int)f + ( 1<<(FLOAT_INT_BITS-1) ) < ( 1 << FLOAT_INT_BITS ) +// the float will be sent with FLOAT_INT_BITS, otherwise all 32 bits will be sent +#define FLOAT_INT_BITS 13 +#define FLOAT_INT_BIAS (1<<(FLOAT_INT_BITS-1)) + +void MSG_WriteField (msg_t *msg, const int *toF, const netField_t *field) +{ + int trunc; + float fullFloat; + + if ( field->bits == -1) + { // a -1 in the bits field means it's a float that's always between -1 and 1 + int temp = *(float *)toF * 32767; + MSG_WriteBits( msg, temp, -16 ); + } + else + if ( field->bits == 0 ) { + // float + fullFloat = *(float *)toF; + trunc = (int)fullFloat; + + if (fullFloat == 0.0f) { + MSG_WriteBits( msg, 0, 1 ); //it's a zero + } else { + MSG_WriteBits( msg, 1, 1 ); //not a zero + if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && + trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) { + // send as small integer + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS ); + } else { + // send as full floating point value + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *toF, 32 ); + } + } + } else { + if (*toF == 0) { + MSG_WriteBits( msg, 0, 1 ); //it's a zero + } else { + MSG_WriteBits( msg, 1, 1 ); //not a zero + // integer + MSG_WriteBits( msg, *toF, field->bits ); + } + } +} + +void MSG_ReadField (msg_t *msg, int *toF, const netField_t *field, int print) +{ + int trunc; + + if ( field->bits == -1) + { // a -1 in the bits field means it's a float that's always between -1 and 1 + int temp = MSG_ReadBits( msg, -16); + *(float *)toF = (float)temp / 32767; + } + else + if ( field->bits == 0 ) { + // float + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *(float *)toF = 0.0f; + } else { + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + // integral float + trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + if ( print ) { + Com_Printf( "%s:%i ", field->name, trunc ); + } + } else { + // full floating point value + *toF = MSG_ReadBits( msg, 32 ); + if ( print ) { + Com_Printf( "%s:%f ", field->name, *(float *)toF ); + } + } + } + } else { + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *toF = 0; + } else { + // integer + *toF = MSG_ReadBits( msg, field->bits ); + if ( print ) { + Com_Printf( "%s:%i ", field->name, *toF ); + } + } + } +} + + +/* +================== +MSG_WriteDeltaEntity + + +GENTITYNUM_BITS 1 : remove this entity +GENTITYNUM_BITS 0 1 SMALL_VECTOR_BITS +GENTITYNUM_BITS 0 0 LARGE_VECTOR_BITS >data> + +Writes part of a packetentities message, including the entity number. +Can delta from either a baseline or a previous packet_entity +If to is NULL, a remove entity update will be sent +If force is not set, then nothing at all will be generated if the entity is +identical, under the assumption that the in-order delta code will catch it. +================== +*/ +#if 0 // Removed by BTO (VV) +void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to, + qboolean force ) { + int c; + int i; + const netField_t *field; + int *fromF, *toF; + int blah; + bool stuffChanged = false; + const int numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]); + byte changeVector[(numFields/8) + 1]; + + + // all fields should be 32 bits to avoid any compiler packing issues + // the "number" field is not part of the field list + // if this assert fails, someone added a field to the entityState_t + // struct without updating the message fields + blah = sizeof( *from ); + assert( numFields + 1 == blah/4); + + c = msg->cursize; + + // a NULL to is a delta remove message + if ( to == NULL ) { + if ( from == NULL ) { + return; + } + MSG_WriteBits( msg, from->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 1, 1 ); + return; + } + + if ( to->number < 0 || to->number >= MAX_GENTITIES ) { + Com_Error (ERR_FATAL, "MSG_WriteDeltaEntity: Bad entity number: %i", to->number ); + } + + memset(changeVector, 0, sizeof(changeVector)); + + // build the change vector as bytes so it is endien independent + for ( i = 0, field = entityStateFields ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + if ( *fromF != *toF ) { + changeVector[ i>>3 ] |= 1 << ( i & 7 ); + stuffChanged = true; + } + } + + if ( stuffChanged ) + { + MSG_WriteBits( msg, to->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 0, 1 ); // not removed + MSG_WriteBits( msg, 1, 1 ); // we have a delta + + // we need to write the entire delta + for ( i = 0 ; i + 8 <= numFields ; i += 8 ) { + MSG_WriteByte( msg, changeVector[i>>3] ); + } + if ( numFields & 7 ) { + MSG_WriteBits( msg, changeVector[i>>3], numFields & 7 ); + } + + for ( i = 0, field = entityStateFields ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + + if ( *fromF == *toF ) { + continue; + } + + MSG_WriteField(msg, toF, field); + } + } + else + { + // nothing at all changed + // write two bits for no change + MSG_WriteBits( msg, to->number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 0, 1 ); // not removed + MSG_WriteBits( msg, 0, 1 ); // no delta + } + + c = msg->cursize - c; +} +#endif + + +extern serverStatic_t svs; +void MSG_WriteEntity( msg_t *msg, struct entityState_s *to, int removeNum) +{ + + if ( to == NULL ) { + MSG_WriteBits(msg, removeNum, GENTITYNUM_BITS); + MSG_WriteBits(msg, 1, 1); //removed + return; + } else { + MSG_WriteBits(msg, to->number, GENTITYNUM_BITS); + MSG_WriteBits(msg, 0, 1); //not removed + } + assert(( to - svs.snapshotEntities ) >= 0 && ( to - svs.snapshotEntities ) < 512); + MSG_WriteLong(msg, to - svs.snapshotEntities); +} + +void MSG_ReadEntity( msg_t *msg, entityState_t *to) +{ + // check for a remove + if ( MSG_ReadBits( msg, 1 ) == 1 ) { + memset( to, 0, sizeof( *to ) ); + to->number = MAX_GENTITIES - 1; + return; + } + + //No remove, read data + int index; + index = MSG_ReadLong(msg); + assert(index >= 0 && index < svs.numSnapshotEntities); + *to = svs.snapshotEntities[index]; +} + +/* +================== +MSG_ReadDeltaEntity + +The entity number has already been read from the message, which +is how the from state is identified. + +If the delta removes the entity, entityState_t->number will be set to MAX_GENTITIES-1 + +Can go from either a baseline or a previous packet_entity +================== +*/ +extern cvar_t *cl_shownet; + +#if 0 // Removed by BTO (VV) +void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, int number) +{ + int i; + const netField_t *field; + int *fromF, *toF; + int print = 0; + int startBit, endBit; + const int numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]); + byte expandedVector[(numFields/8) + 1]; + + if ( number < 0 || number >= MAX_GENTITIES) { + Com_Error( ERR_DROP, "Bad delta entity number: %i", number ); + } + + if ( msg->bit == 0 ) { + startBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // check for a remove + if ( MSG_ReadBits( msg, 1 ) == 1 ) { + memset( to, 0, sizeof( *to ) ); + to->number = MAX_GENTITIES - 1; + if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) { + Com_Printf( "%3i: #%-3i remove\n", msg->readcount, number ); + } + return; + } + + // check for no delta + if ( MSG_ReadBits( msg, 1 ) != 0 ) + { + const int numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]); + + // shownet 2/3 will interleave with other printed info, -1 will + // just print the delta records` + if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) { + print = 1; + Com_Printf( "%3i: #%-3i ", msg->readcount, to->number ); + } else { + print = 0; + } + + // we need to write the entire delta + for ( i = 0 ; i + 8 <= numFields ; i += 8 ) { + expandedVector[i>>3] = MSG_ReadByte( msg ); + } + if ( numFields & 7 ) { + expandedVector[i>>3] = MSG_ReadBits( msg, numFields & 7 ); + } + + to->number = number; + + for ( i = 0, field = entityStateFields ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + + if ( ! ( expandedVector[ i >> 3 ] & ( 1 << ( i & 7 ) ) ) ) { + // no change + *toF = *fromF; + } else { + MSG_ReadField(msg, toF, field, print); + } + } + } + else + { + memcpy(to, from,sizeof(entityState_t)); + to->number = number; + } + if ( print ) { + if ( msg->bit == 0 ) { + endBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf( " (%i bits)\n", endBit - startBit ); + } +} +#endif + +/* +Ghoul2 Insert End +*/ + +/* +============================================================================ + +plyer_state_t communication + +============================================================================ +*/ + +// using the stringizing operator to save typing... +#define PSF(x) #x,(int)&((playerState_t*)0)->x + +static const netField_t playerStateFields[] = +{ +{ PSF(commandTime), 32 }, +{ PSF(pm_type), 8 }, +{ PSF(bobCycle), 8 }, +{ PSF(pm_flags), 32 }, +{ PSF(pm_time), -16 }, +{ PSF(origin[0]), 0 }, +{ PSF(origin[1]), 0 }, +{ PSF(origin[2]), 0 }, +{ PSF(velocity[0]), 0 }, +{ PSF(velocity[1]), 0 }, +{ PSF(velocity[2]), 0 }, +{ PSF(weaponTime), -16 }, +{ PSF(weaponChargeTime), 32 }, //? really need 32 bits?? +{ PSF(gravity), 16 }, +{ PSF(leanofs), -8 }, +{ PSF(friction), 16 }, +{ PSF(speed), 16 }, +{ PSF(delta_angles[0]), 16 }, +{ PSF(delta_angles[1]), 16 }, +{ PSF(delta_angles[2]), 16 }, +{ PSF(groundEntityNum), GENTITYNUM_BITS }, +//{ PSF(animationTimer), 16 }, +{ PSF(legsAnim), 16 }, +{ PSF(torsoAnim), 16 }, +{ PSF(movementDir), 4 }, +{ PSF(eFlags), 32 }, +{ PSF(eventSequence), 16 }, +{ PSF(events[0]), 8 }, +{ PSF(events[1]), 8 }, +{ PSF(eventParms[0]), -9 }, +{ PSF(eventParms[1]), -9 }, +{ PSF(externalEvent), 8 }, +{ PSF(externalEventParm), 8 }, +{ PSF(clientNum), 32 }, +{ PSF(weapon), 5 }, +{ PSF(weaponstate), 4 }, +{ PSF(batteryCharge), 16 }, +{ PSF(viewangles[0]), 0 }, +{ PSF(viewangles[1]), 0 }, +{ PSF(viewangles[2]), 0 }, +{ PSF(viewheight), -8 }, +{ PSF(damageEvent), 8 }, +{ PSF(damageYaw), 8 }, +{ PSF(damagePitch), -8 }, +{ PSF(damageCount), 8 }, +//{ PSF(saberColor), 8 }, +//{ PSF(saberActive), 8 }, +//{ PSF(saberLength), 32 }, +//{ PSF(saberLengthMax), 32 }, +{ PSF(forcePowersActive), 32}, +{ PSF(saberInFlight), 8 }, + +/*{ PSF(vehicleIndex), 32 }, // WOAH, what do we do with this stuff??? +{ PSF(vehicleArmor), 32 }, +{ PSF(vehicleAngles[0]), 0 }, +{ PSF(vehicleAngles[1]), 0 }, +{ PSF(vehicleAngles[2]), 0 },*/ + +{ PSF(viewEntity), 32 }, +{ PSF(serverViewOrg[0]), 0 }, +{ PSF(serverViewOrg[1]), 0 }, +{ PSF(serverViewOrg[2]), 0 }, +}; + +/* +============= +MSG_WriteDeltaPlayerstate + +============= +*/ +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ) { + int i; + playerState_t dummy; + int statsbits; + int persistantbits; + int ammobits; + int powerupbits; + int numFields; + int c; + const netField_t *field; + int *fromF, *toF; + + if (!from) { + from = &dummy; + memset (&dummy, 0, sizeof(dummy)); + } + + c = msg->cursize; + + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); + for ( i = 0, field = playerStateFields ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + + if ( *fromF == *toF ) { + MSG_WriteBits( msg, 0, 1 ); // no change + continue; + } + + MSG_WriteBits( msg, 1, 1 ); // changed + MSG_WriteField (msg, toF, field); + } + c = msg->cursize - c; + + + // + // send the arrays + // + statsbits = 0; + for (i=0 ; istats[i] != from->stats[i]) { + statsbits |= 1<stats[i], 32); + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + + + persistantbits = 0; + for (i=0 ; ipersistant[i] != from->persistant[i]) { + persistantbits |= 1<persistant[i]); + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + + + ammobits = 0; + for (i=0 ; iammo[i] != from->ammo[i]) { + ammobits |= 1<ammo[i]); + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + + powerupbits = 0; + for (i=0 ; ipowerups[i] != from->powerups[i]) { + powerupbits |= 1<powerups[i] ); + } else { + MSG_WriteBits( msg, 0, 1 ); // no change + } + + + statsbits = 0; + for (i=0 ; iinventory[i] != from->inventory[i]) + { + statsbits |= 1<inventory[i]); + } + } + } + else + { + MSG_WriteBits( msg, 0, 1 ); // no change + } +} + + +/* +=================== +MSG_ReadDeltaPlayerstate +=================== +*/ +void MSG_ReadDeltaPlayerstate (msg_t *msg, playerState_t *from, playerState_t *to ) { + int i; + int bits; + const netField_t *field; + int numFields; + int startBit, endBit; + int print; + int *fromF, *toF; + playerState_t dummy; + + if ( !from ) { + from = &dummy; + memset( &dummy, 0, sizeof( dummy ) ); + } + *to = *from; + + if ( msg->bit == 0 ) { + startBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + startBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + + // shownet 2/3 will interleave with other printed info, -2 will + // just print the delta records + if ( cl_shownet->integer >= 2 || cl_shownet->integer == -2 ) { + print = 1; + Com_Printf( "%3i: playerstate ", msg->readcount ); + } else { + print = 0; + } + + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); + for ( i = 0, field = playerStateFields ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + + if ( ! MSG_ReadBits( msg, 1 ) ) { + // no change + *toF = *fromF; + } else { + MSG_ReadField( msg, toF, field, print); + } + } + + // read the arrays + + // parse stats + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_STATS"); + bits = MSG_ReadShort (msg); + for (i=0 ; istats[i] = MSG_ReadBits(msg,32); + } + } + } + + // parse persistant stats + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_PERSISTANT"); + bits = MSG_ReadShort (msg); + for (i=0 ; ipersistant[i] = MSG_ReadSShort(msg); + } + } + } + + // parse ammo + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_AMMO"); + bits = MSG_ReadShort (msg); + for (i=0 ; iammo[i] = MSG_ReadSShort(msg); + } + } + } + + // parse powerups + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_POWERUPS"); + bits = MSG_ReadShort (msg); + for (i=0 ; ipowerups[i] = MSG_ReadLong(msg); + } + } + } + + // parse inventory + if ( MSG_ReadBits( msg, 1 ) ) { + LOG("PS_INVENTORY"); + bits = MSG_ReadShort (msg); + for (i=0 ; iinventory[i] = MSG_ReadShort(msg); + } + } + } + + if ( print ) { + if ( msg->bit == 0 ) { + endBit = msg->readcount * 8 - GENTITYNUM_BITS; + } else { + endBit = ( msg->readcount - 1 ) * 8 + msg->bit - GENTITYNUM_BITS; + } + Com_Printf( " (%i bits)\n", endBit - startBit ); + } +} + + +//=========================================================================== diff --git a/code/qcommon/net_chan.cpp b/code/qcommon/net_chan.cpp new file mode 100644 index 0000000..5409510 --- /dev/null +++ b/code/qcommon/net_chan.cpp @@ -0,0 +1,566 @@ + +#include "../game/q_shared.h" +#include "qcommon.h" + +/* + +packet header +------------- +4 outgoing sequence. high bit will be set if this is a fragmented message +4 acknowledge sequence +[2 qport (only for client to server)] +[2 fragment start byte] +[2 fragment length. if < FRAGMENT_SIZE, this is the last fragment] + +if the sequence number is -1, the packet should be handled as an out-of-band +message instead of as part of a netcon. + +All fragments will have the same sequence numbers. + +The qport field is a workaround for bad address translating routers that +sometimes remap the client's source port on a packet during gameplay. + +If the base part of the net address matches and the qport matches, then the +channel matches even if the IP port differs. The IP port should be updated +to the new value before sending out any replies. + +*/ + + +#define MAX_PACKETLEN (MAX_MSGLEN) //(1400) // max size of a network packet +#define MAX_LOOPDATA 16 * 1024 + +#if (MAX_PACKETLEN > MAX_MSGLEN) +#error MAX_PACKETLEN must be <= MAX_MSGLEN +#endif +#if (MAX_LOOPDATA > MAX_MSGLEN) +#error MAX_LOOPDATA must be <= MAX_MSGLEN +#endif + +#define FRAGMENT_SIZE (MAX_PACKETLEN - 100) +#define PACKET_HEADER 10 // two ints and a short + +#define FRAGMENT_BIT (1<<31) + +cvar_t *showpackets; +cvar_t *showdrop; +cvar_t *qport; + +static char *netsrcString[2] = { + "client", + "server" +}; + +typedef struct { + char loopData[MAX_LOOPDATA]; + int get, send; +} loopback_t; + +static loopback_t *loopbacks = NULL; + + +/* +=============== +Netchan_Init + +=============== +*/ +void Netchan_Init( int port ) { + if (!loopbacks) + { + loopbacks = (loopback_t*) Z_Malloc(sizeof(loopback_t) * 2, TAG_NEWDEL, qtrue); + } + + port &= 0xffff; + showpackets = Cvar_Get ("showpackets", "0", CVAR_TEMP ); + showdrop = Cvar_Get ("showdrop", "0", CVAR_TEMP ); + qport = Cvar_Get ("qport", va("%i", port), CVAR_INIT ); +} + +void Netchan_Shutdown() +{ + if (loopbacks) + { + Z_Free(loopbacks); + loopbacks = 0; + } +} + +/* +============== +Netchan_Setup + +called to open a channel to a remote system +============== +*/ +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ) { + memset (chan, 0, sizeof(*chan)); + + chan->sock = sock; + chan->remoteAddress = adr; + chan->qport = qport; + chan->incomingSequence = 0; + chan->outgoingSequence = 1; +} + +/* +=============== +Netchan_Transmit + +Sends a message to a connection, fragmenting if necessary +A 0 length will still generate a packet. +================ +*/ +void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { + msg_t send; + byte send_buf[MAX_PACKETLEN]; + int fragmentStart, fragmentLength; + + fragmentStart = 0; // stop warning message + fragmentLength = 0; + + // fragment large reliable messages + if ( length >= FRAGMENT_SIZE ) { + fragmentStart = 0; + do { + // write the packet header + MSG_Init (&send, send_buf, sizeof(send_buf)); + + MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT ); + MSG_WriteLong( &send, chan->incomingSequence ); + + // send the qport if we are a client + if ( chan->sock == NS_CLIENT ) { + MSG_WriteShort( &send, qport->integer ); + } + + // copy the reliable message to the packet first + fragmentLength = FRAGMENT_SIZE; + if ( fragmentStart + fragmentLength > length ) { + fragmentLength = length - fragmentStart; + } + + MSG_WriteShort( &send, fragmentStart ); + MSG_WriteShort( &send, fragmentLength ); + MSG_WriteData( &send, data + fragmentStart, fragmentLength ); + + // send the datagram + NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + + if ( showpackets->integer ) { + Com_Printf ("%s send %4i : s=%i ack=%i fragment=%i,%i\n" + , netsrcString[ chan->sock ] + , send.cursize + , chan->outgoingSequence - 1 + , chan->incomingSequence + , fragmentStart, fragmentLength); + } + + fragmentStart += fragmentLength; + // this exit condition is a little tricky, because a packet + // that is exactly the fragment length still needs to send + // a second packet of zero length so that the other side + // can tell there aren't more to follow + } while ( fragmentStart != length || fragmentLength == FRAGMENT_SIZE ); + + chan->outgoingSequence++; + return; + } + + // write the packet header + MSG_Init (&send, send_buf, sizeof(send_buf)); + + MSG_WriteLong( &send, chan->outgoingSequence ); + MSG_WriteLong( &send, chan->incomingSequence ); + chan->outgoingSequence++; + + // send the qport if we are a client + if ( chan->sock == NS_CLIENT ) { + MSG_WriteShort( &send, qport->integer ); + } + + MSG_WriteData( &send, data, length ); + + // send the datagram + NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + + if ( showpackets->integer ) { + Com_Printf( "%s send %4i : s=%i ack=%i\n" + , netsrcString[ chan->sock ] + , send.cursize + , chan->outgoingSequence - 1 + , chan->incomingSequence ); + } +} + +/* +================= +Netchan_Process + +Returns qfalse if the message should not be processed due to being +out of order or a fragment. + +Msg must be large enough to hold MAX_MSGLEN, because if this is the +final fragment of a multi-part message, the entire thing will be +copied out. +================= +*/ +qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { + int sequence, sequence_ack; + int qport; + int fragmentStart, fragmentLength; + qboolean fragmented; + + // get sequence numbers + MSG_BeginReading( msg ); + sequence = MSG_ReadLong( msg ); + sequence_ack = MSG_ReadLong( msg ); + + // check for fragment information + if ( sequence & FRAGMENT_BIT ) { + sequence &= ~FRAGMENT_BIT; + fragmented = qtrue; + } else { + fragmented = qfalse; + } + + // read the qport if we are a server + if ( chan->sock == NS_SERVER ) { + qport = MSG_ReadShort( msg ); + } + + // read the fragment information + if ( fragmented ) { + fragmentStart = MSG_ReadShort( msg ); + fragmentLength = MSG_ReadShort( msg ); + } else { + fragmentStart = 0; // stop warning message + fragmentLength = 0; + } + + if ( showpackets->integer ) { + if ( fragmented ) { + Com_Printf( "%s recv %4i : s=%i ack=%i fragment=%i,%i\n" + , netsrcString[ chan->sock ] + , msg->cursize + , sequence + , sequence_ack + , fragmentStart, fragmentLength ); + } else { + Com_Printf( "%s recv %4i : s=%i ack=%i\n" + , netsrcString[ chan->sock ] + , msg->cursize + , sequence + , sequence_ack ); + } + } + + // + // discard out of order or duplicated packets + // + if ( sequence <= chan->incomingSequence ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Out of order packet %i at %i\n" + , NET_AdrToString( chan->remoteAddress ) + , sequence + , chan->incomingSequence ); + } + return qfalse; + } + + // + // dropped packets don't keep the message from being used + // + chan->dropped = sequence - (chan->incomingSequence+1); + if ( chan->dropped > 0 ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Dropped %i packets at %i\n" + , NET_AdrToString( chan->remoteAddress ) + , chan->dropped + , sequence ); + } + } + + + // + // if this is the final framgent of a reliable message, + // bump incoming_reliable_sequence + // + if ( fragmented ) { + // make sure we + if ( sequence != chan->fragmentSequence ) { + chan->fragmentSequence = sequence; + chan->fragmentLength = 0; + } + + // if we missed a fragment, dump the message + if ( fragmentStart != chan->fragmentLength ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf( "%s:Dropped a message fragment\n" + , NET_AdrToString( chan->remoteAddress ) + , sequence); + } + // we can still keep the part that we have so far, + // so we don't need to clear chan->fragmentLength + return qfalse; + } + + // copy the fragment to the fragment buffer + if ( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize || + chan->fragmentLength + fragmentLength > sizeof( chan->fragmentBuffer ) ) { + if ( showdrop->integer || showpackets->integer ) { + Com_Printf ("%s:illegal fragment length\n" + , NET_AdrToString (chan->remoteAddress ) ); + } + return qfalse; + } + + memcpy( chan->fragmentBuffer + chan->fragmentLength, + msg->data + msg->readcount, fragmentLength ); + + chan->fragmentLength += fragmentLength; + + // if this wasn't the last fragment, don't process anything + if ( fragmentLength == FRAGMENT_SIZE ) { + return qfalse; + } + + if ( chan->fragmentLength > msg->maxsize ) { + Com_Printf( "%s:fragmentLength %i > msg->maxsize\n" + , NET_AdrToString (chan->remoteAddress ), + chan->fragmentLength ); + return qfalse; + } + + // copy the full message over the partial fragment + + // make sure the sequence number is still there + *(int *)msg->data = LittleLong( sequence ); + + memcpy( msg->data + 4, chan->fragmentBuffer, chan->fragmentLength ); + msg->cursize = chan->fragmentLength + 4; + chan->fragmentLength = 0; + msg->readcount = 4; // past the sequence number + + return qtrue; + } + + // + // the message can now be read from the current message pointer + // + chan->incomingSequence = sequence; + chan->incomingAcknowledged = sequence_ack; + + return qtrue; +} + + +//============================================================================== + +/* +=================== +NET_CompareBaseAdr + +Compares without the port +=================== +*/ +qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b) +{ + if (a.type != b.type) + return qfalse; + + if (a.type == NA_LOOPBACK) + return qtrue; + + Com_Printf ("NET_CompareBaseAdr: bad address type\n"); + return qfalse; +} + +const char *NET_AdrToString (netadr_t a) +{ + static char s[64]; + + if (a.type == NA_LOOPBACK) { + Com_sprintf (s, sizeof(s), "loopback"); + } + + return s; +} + + +qboolean NET_CompareAdr (netadr_t a, netadr_t b) +{ + if (a.type != b.type) + return qfalse; + + if (a.type == NA_LOOPBACK) + return qtrue; + + Com_Printf ("NET_CompareAdr: bad address type\n"); + return qfalse; +} + + +qboolean NET_IsLocalAddress( netadr_t adr ) { + return adr.type == NA_LOOPBACK; +} + + + +/* +============================================================================= + +LOOPBACK BUFFERS FOR LOCAL PLAYER + +============================================================================= +*/ + +qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message) +{ + int i; + loopback_t *loop; + + loop = &loopbacks[sock]; + + //If read and write positions are the same, nothing left to read. + if (loop->get == loop->send) + return qfalse; + + //Get read position. Wrap if too close to end. + i = loop->get; + if(i > MAX_LOOPDATA - 4) { + i = 0; + } + + //Get length of packet. + int length = *(int*)(loop->loopData + i); + i += 4; + + //See if entire packet is at end of buffer or part is at the beginning. + if(i + length <= MAX_LOOPDATA) { + //Everything fits, full copy. + memcpy (net_message->data, loop->loopData + i, length); + net_message->cursize = length; + i += length; + loop->get = i; + } else { + //Doesn't all fit, partial copy + const int copyToEnd = MAX_LOOPDATA - i; + memcpy (net_message->data, loop->loopData + i, copyToEnd); + memcpy ((char*)net_message->data + copyToEnd, + loop->loopData, length - copyToEnd); + net_message->cursize = length; + loop->get = length - copyToEnd; + } + + memset (net_from, 0, sizeof(*net_from)); + net_from->type = NA_LOOPBACK; + + return qtrue; +} + + +void NET_SendLoopPacket (netsrc_t sock, int length, const void *data, netadr_t to) +{ + int i; + loopback_t *loop; + + loop = &loopbacks[sock^1]; + + //Make sure there is enough free space in the buffer. + int freeSpace; + if(loop->send >= loop->get) { + freeSpace = MAX_LOOPDATA - (loop->send - loop->get); + } else { + freeSpace = loop->get - loop->send; + } + + assert(freeSpace > length); + + //Get write position. Wrap around if too close to end. + i = loop->send; + if(i > MAX_LOOPDATA - 4) { + i = 0; + } + + //Write length of packet. + *(int*)(loop->loopData + i) = length; + i += 4; + + //See if the whole packet will fit on the end of the buffer or if we + //need to write part of it back at the beginning. + if(i + length <= MAX_LOOPDATA) { + //Everything fits, full copy. + memcpy (loop->loopData + i, data, length); + i += length; + loop->send = i; + } else { + //Doesn't all fit, partial copy + int copyToEnd = MAX_LOOPDATA - i; + memcpy(loop->loopData + i, data, copyToEnd); + memcpy(loop->loopData, (char*)data + copyToEnd, length - copyToEnd); + loop->send = length - copyToEnd; + } +} + +//============================================================================= + + +void NET_SendPacket( netsrc_t sock, int length, const void *data, netadr_t to ) { + + // sequenced packets are shown in netchan, so just show oob + if ( showpackets->integer && *(int *)data == -1 ) { + Com_Printf ("send packet %4i\n", length); + } + + if ( to.type == NA_LOOPBACK ) { + NET_SendLoopPacket (sock, length, data, to); + return; + } +} + +/* +=============== +NET_OutOfBandPrint + +Sends a text message in an out-of-band datagram +================ +*/ +void QDECL NET_OutOfBandPrint( netsrc_t sock, netadr_t adr, const char *format, ... ) { + va_list argptr; + char string[MAX_MSGLEN]; + + // set the header + string[0] = (char) 0xff; + string[1] = (char) 0xff; + string[2] = (char) 0xff; + string[3] = (char) 0xff; + + va_start( argptr, format ); + vsprintf( string+4, format, argptr ); + va_end( argptr ); + + // send the datagram + NET_SendPacket( sock, strlen( string ), string, adr ); +} + + + +/* +============= +NET_StringToAdr + +Traps "localhost" for loopback, passes everything else to system +============= +*/ +qboolean NET_StringToAdr( const char *s, netadr_t *a ) { + if (!strcmp (s, "localhost")) { + memset (a, 0, sizeof(*a)); + a->type = NA_LOOPBACK; + return qtrue; + } + + a->type = NA_BAD; + return qfalse; +} + diff --git a/code/qcommon/platform.h b/code/qcommon/platform.h new file mode 100644 index 0000000..be17950 --- /dev/null +++ b/code/qcommon/platform.h @@ -0,0 +1,17 @@ +// Simple header file to dispatch to the relevant platform API headers +#ifndef _PLATFORM_H +#define _PLATFORM_H + +#if defined(_XBOX) +#include +#endif + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN 1 +#endif + +#if defined(_WINDOWS) +#include +#endif + +#endif diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h new file mode 100644 index 0000000..f96edc0 --- /dev/null +++ b/code/qcommon/qcommon.h @@ -0,0 +1,855 @@ +// qcommon.h -- definitions common between client and server, but not game.or ref modules +#ifndef __QCOMMON_H__ +#define __QCOMMON_H__ + +#include "stringed_ingame.h" +#include "../qcommon/cm_public.h" + + +// some zone mem debugging stuff +#ifndef FINAL_BUILD + #ifdef _DEBUG + // + // both of these should be REM'd unless you specifically need them... + // + //#define DEBUG_ZONE_ALLOCS // adds __FILE__ and __LINE__ info to zone blocks, to see who's leaking + //#define DETAILED_ZONE_DEBUG_CODE // this slows things down a LOT, and is only for tracking nasty double-freeing Z_Malloc bugs + #endif +#endif + + +//============================================================================ + +// +// msg.c +// +typedef struct { + qboolean allowoverflow; // if false, do a Com_Error + qboolean overflowed; // set to true if the buffer size failed (with allowoverflow set) + byte *data; + int maxsize; + int cursize; + int readcount; + int bit; // for bitwise reads and writes +} msg_t; + +void MSG_Init (msg_t *buf, byte *data, int length); +void MSG_Clear (msg_t *buf); +void *MSG_GetSpace (msg_t *buf, int length); +void MSG_WriteData (msg_t *buf, const void *data, int length); + + +struct usercmd_s; +struct entityState_s; +struct playerState_s; + +void MSG_WriteBits( msg_t *msg, int value, int bits ); + +void MSG_WriteByte (msg_t *sb, int c); +void MSG_WriteShort (msg_t *sb, int c); +void MSG_WriteLong (msg_t *sb, int c); +void MSG_WriteString (msg_t *sb, const char *s); + +void MSG_BeginReading (msg_t *sb); + +int MSG_ReadBits( msg_t *msg, int bits ); + +int MSG_ReadByte (msg_t *sb); +int MSG_ReadShort (msg_t *sb); +int MSG_ReadLong (msg_t *sb); +char *MSG_ReadString (msg_t *sb); +char *MSG_ReadStringLine (msg_t *sb); +void MSG_ReadData (msg_t *sb, void *buffer, int size); + + +void MSG_WriteDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to ); +void MSG_ReadDeltaUsercmd( msg_t *msg, struct usercmd_s *from, struct usercmd_s *to ); + +void MSG_WriteDeltaEntity( msg_t *msg, struct entityState_s *from, struct entityState_s *to + , qboolean force ); +void MSG_ReadDeltaEntity( msg_t *msg, entityState_t *from, entityState_t *to, + int number ); +void MSG_ReadEntity( msg_t *msg, entityState_t *to); +void MSG_WriteEntity( msg_t *msg, entityState_t *to, int removeNum); + +void MSG_WriteDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ); +void MSG_ReadDeltaPlayerstate( msg_t *msg, struct playerState_s *from, struct playerState_s *to ); + + +//============================================================================ + +#ifdef _M_IX86 +// +// optimised stuff for Intel, since most of our data is in that format anyway... +// +extern short BigShort (short l); +extern int BigLong (int l); +extern float BigFloat (float l); +#define LittleShort(l) l +#define LittleLong(l) l +#define LittleFloat(l) l +// +#else +// +// standard smart-swap code... +// +extern short BigShort (short l); +extern short LittleShort (short l); +extern int BigLong (int l); +extern int LittleLong (int l); +extern float BigFloat (float l); +extern float LittleFloat (float l); +// +#endif + + +/* +============================================================== + +NET + +============================================================== +*/ + +#ifdef _XBOX +#define PACKET_BACKUP 2 +#else +#define PACKET_BACKUP 16 // number of old messages that must be kept on client and +#endif // server for delta comrpession and ping estimation +#define PACKET_MASK (PACKET_BACKUP-1) + +#define MAX_PACKET_USERCMDS 32 // max number of usercmd_t in a packet + +#define PORT_ANY -1 + +#define MAX_RELIABLE_COMMANDS 64 // max string commands buffered for restransmit + +typedef enum { + NA_BAD, // an address lookup failed + NA_LOOPBACK, +} netadrtype_t; + +typedef enum { + NS_CLIENT, + NS_SERVER +} netsrc_t; + +typedef struct { + netadrtype_t type; + + unsigned short port; +} netadr_t; + +void NET_SendPacket (netsrc_t sock, int length, const void *data, netadr_t to); +void NET_OutOfBandPrint( netsrc_t net_socket, netadr_t adr, const char *format, ...); + +qboolean NET_CompareAdr (netadr_t a, netadr_t b); +qboolean NET_CompareBaseAdr (netadr_t a, netadr_t b); +qboolean NET_IsLocalAddress (netadr_t adr); +qboolean NET_IsLANAddress (netadr_t adr); +const char *NET_AdrToString (netadr_t a); +qboolean NET_StringToAdr ( const char *s, netadr_t *a); +qboolean NET_GetLoopPacket (netsrc_t sock, netadr_t *net_from, msg_t *net_message); + + +#define MAX_MSGLEN (1*17408) // max length of a message, which may +//#define MAX_MSGLEN (3*16384) // max length of a message, which may + // be fragmented into multiple packets + + +/* +Netchan handles packet fragmentation and out of order / duplicate suppression +*/ + +typedef struct { + netsrc_t sock; + + int dropped; // between last packet and previous + + netadr_t remoteAddress; + int qport; // qport value to write when transmitting + + // sequencing variables + int incomingSequence; + int incomingAcknowledged; + + int outgoingSequence; + + // incoming fragment assembly buffer + int fragmentSequence; + int fragmentLength; + byte fragmentBuffer[MAX_MSGLEN]; +} netchan_t; + +void Netchan_Init( int qport ); +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport ); + +void Netchan_Transmit( netchan_t *chan, int length, const byte *data ); +qboolean Netchan_Process( netchan_t *chan, msg_t *msg ); + + +/* +============================================================== + +PROTOCOL + +============================================================== +*/ + +#define PROTOCOL_VERSION 40 + +#define PORT_SERVER 27960 + +// the svc_strings[] array in cl_parse.c should mirror this +// +// server to client +// +enum svc_ops_e { + svc_bad, + svc_nop, + svc_gamestate, + svc_configstring, // [short] [string] only in gamestate messages + svc_baseline, // only in gamestate messages + svc_serverCommand, // [string] to be executed by client game module + svc_download, // [short] size [size bytes] + svc_snapshot +}; + + +// +// client to server +// +enum clc_ops_e { + clc_bad, + clc_nop, + clc_move, // [[usercmd_t] + clc_clientCommand // [string] message +}; + + +/* +============================================================== + +CMD + +Command text buffering and command execution + +============================================================== +*/ + +/* + +Any number of commands can be added in a frame, from several different sources. +Most commands come from either keybindings or console line input, but entire text +files can be execed. + +*/ + +void Cbuf_Init (void); +// allocates an initial text buffer that will grow as needed + +void Cbuf_AddText( const char *text ); +// Adds command text at the end of the buffer, does NOT add a final \n + +void Cbuf_ExecuteText( int exec_when, const char *text ); +// this can be used in place of either Cbuf_AddText or Cbuf_InsertText + +void Cbuf_Execute (void); +// Pulls off \n terminated lines of text from the command buffer and sends +// them through Cmd_ExecuteString. Stops when the buffer is empty. +// Normally called once per frame, but may be explicitly invoked. +// Do not call inside a command function, or current args will be destroyed. + +//=========================================================================== + +/* + +Command execution takes a null terminated string, breaks it into tokens, +then searches for a command or variable that matches the first token. + +*/ + +typedef void (*xcommand_t) (void); + +void Cmd_Init (void); + +void Cmd_AddCommand( const char *cmd_name, xcommand_t function ); +// called by the init functions of other parts of the program to +// register commands and functions to call for them. +// The cmd_name is referenced later, so it should not be in temp memory +// if function is NULL, the command will be forwarded to the server +// as a clc_clientCommand instead of executed locally + +void Cmd_RemoveCommand( const char *cmd_name ); + +char *Cmd_CompleteCommand( const char *partial ); +// attempts to match a partial command for automatic command line completion +// returns NULL if nothing fits + +int Cmd_Argc (void); +char *Cmd_Argv (int arg); +void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ); +char *Cmd_Args (void); +void Cmd_ArgsBuffer( char *buffer, int bufferLength ); +// The functions that execute commands get their parameters with these +// functions. Cmd_Argv () will return an empty string, not a NULL +// if arg > argc, so string operations are allways safe. + +void Cmd_TokenizeString( const char *text ); +// Takes a null terminated string. Does not need to be /n terminated. +// breaks the string up into arg tokens. + +void Cmd_ExecuteString( const char *text ); +// Parses a single line of text into arguments and tries to execute it +// as if it was typed at the console + + +/* +============================================================== + +CVAR + +============================================================== +*/ + +/* + +cvar_t variables are used to hold scalar or string variables that can be changed +or displayed at the console or prog code as well as accessed directly +in C code. + +The user can access cvars from the console in three ways: +r_draworder prints the current value +r_draworder 0 sets the current value to 0 +set r_draworder 0 as above, but creates the cvar if not present + +Cvars are restricted from having the same names as commands to keep this +interface from being ambiguous. + +The are also occasionally used to communicated information between different +modules of the program. + +*/ + +cvar_t *Cvar_Get( const char *var_name, const char *value, int flags ); +// creates the variable if it doesn't exist, or returns the existing one +// if it exists, the value will not be changed, but flags will be ORed in +// that allows variables to be unarchived without needing bitflags +// if value is "", the value will not override a previously set value. + +void Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +// basically a slightly modified Cvar_Get for the interpreted modules + +void Cvar_Update( vmCvar_t *vmCvar ); +// updates an interpreted modules' version of a cvar + +void Cvar_Set( const char *var_name, const char *value ); +// will create the variable with no flags if it doesn't exist + +void Cvar_SetValue( const char *var_name, float value ); +// expands value to a string and calls Cvar_Set + +float Cvar_VariableValue( const char *var_name ); +int Cvar_VariableIntegerValue( const char *var_name ); +// returns 0 if not defined or non numeric + +char *Cvar_VariableString( const char *var_name ); +void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +// returns an empty string if not defined + +char *Cvar_CompleteVariable( const char *partial ); +// attempts to match a partial variable name for command line completion +// returns NULL if nothing fits + +void Cvar_Reset( const char *var_name ); + +void Cvar_SetCheatState( void ); +// reset all testing vars to a safe value + +qboolean Cvar_Command( void ); +// called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known +// command. Returns true if the command was a variable reference that +// was handled. (print or change) + +void Cvar_WriteVariables( fileHandle_t f ); +// writes lines containing "set variable value" for all variables +// with the archive flag set to true. + +void Cvar_Init( void ); + +char *Cvar_InfoString( int bit ); +// returns an info string containing all the cvars that have the given bit set +// in their flags ( CVAR_USERINFO, CVAR_SERVERINFO, CVAR_SYSTEMINFO, etc ) +void Cvar_InfoStringBuffer( int bit, char *buff, int buffsize ); + +void Cvar_Restart_f( void ); + +extern int cvar_modifiedFlags; +// whenever a cvar is modifed, its flags will be OR'd into this, so +// a single check can determine if any CVAR_USERINFO, CVAR_SERVERINFO, +// etc, variables have been modified since the last check. The bit +// can then be cleared to allow another change detection. + +/* +============================================================== + +FILESYSTEM + +No stdio calls should be used by any part of the game, because +we need to deal with all sorts of directory and seperator char +issues. +============================================================== +*/ + +qboolean FS_Initialized(); + +void FS_InitFilesystem (void); + +char **FS_ListFiles( const char *directory, const char *extension, int *numfiles ); +// directory should not have either a leading or trailing / +// if extension is "/", only subdirectories will be returned +// the returned files will not include any directories or / + +void FS_FreeFileList( char **filelist ); + +int FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); + +fileHandle_t FS_FOpenFileWrite( const char *qpath ); +// will properly create any needed paths and deal with seperater character issues + +fileHandle_t FS_FOpenFileAppend( const char *filename ); // this was present already, but no public proto + +qboolean FS_GetExtendedInfo_FOpenFileRead(const char *filename, char **ppsFilename, int *piOffset); +//return value is success of opening file, then ppsFilename and piOffset are valid + +int FS_FOpenFileRead( const char *qpath, fileHandle_t *file, qboolean uniqueFILE ); +// if uniqueFILE is true, then a new FILE will be fopened even if the file +// is found in an already open pak file. If uniqueFILE is false, you must call +// FS_FCloseFile instead of fclose, otherwise the pak FILE would be improperly closed +// It is generally safe to always set uniqueFILE to true, because the majority of +// file IO goes through FS_ReadFile, which Does The Right Thing already. + +int FS_FileIsInPAK(const char *filename ); +// returns 1 if a file is in the PAK file, otherwise -1 + +int FS_Write( const void *buffer, int len, fileHandle_t f ); + +int FS_Read( void *buffer, int len, fileHandle_t f ); +// properly handles partial reads and reads from other dlls + +void FS_FCloseFile( fileHandle_t f ); +// note: you can't just fclose from another DLL, due to MS libc issues + +int FS_ReadFile( const char *qpath, void **buffer ); +// returns the length of the file +// a null buffer will just return the file length without loading +// as a quick check for existance. -1 length == not present +// A 0 byte will always be appended at the end, so string ops are safe. +// the buffer should be considered read-only, because it may be cached +// for other uses. + +void FS_ForceFlush( fileHandle_t f ); +// forces flush on files we're writing to. + +void FS_FreeFile( void *buffer ); +// frees the memory returned by FS_ReadFile + +void FS_WriteFile( const char *qpath, const void *buffer, int size ); +// writes a complete file, creating any subdirectories needed + +int FS_filelength( fileHandle_t f ); +// doesn't work for files that are opened from a pack file + +int FS_FTell( fileHandle_t f ); +// where are we? + +void FS_Flush( fileHandle_t f ); + +void QDECL FS_Printf( fileHandle_t f, const char *fmt, ... ); +// like fprintf + +int FS_FOpenFileByMode( const char *qpath, fileHandle_t *f, fsMode_t mode ); +// opens a file for reading, writing, or appending depending on the value of mode + +int FS_Seek( fileHandle_t f, long offset, int origin ); +// seek on a file (doesn't work for zip files!!!!!!!!) + + +// These 2 are generally only used by the save games, filenames are local (eg "saves/blah.sav") +// +void FS_DeleteUserGenFile( const char *filename ); +qboolean FS_MoveUserGenFile ( const char *filename_src, const char *filename_dst ); + +/* +============================================================== + +MISC + +============================================================== +*/ + +//========================================================== +// +// NOTE NOTE NOTE!!!!!!!!!!!!! +// +// Any CPUID_XXXX defined as higher than CPUID_INTEL_MMX *must* have MMX support (eg like CPUID_AMD_3DNOW (0x30) has), +// this allows convenient MMX capability checking. If you for some reason want to support some new processor that does +// *NOT* have MMX (yeah, right), then define it as a lower number. -slc +// +// ( These values are returned by Sys_GetProcessorId ) +// +#define CPUID_GENERIC 0 // any unrecognized processor + +#define CPUID_AXP 0x10 + +#define CPUID_INTEL_UNSUPPORTED 0x20 // Intel 386/486 +#define CPUID_INTEL_PENTIUM 0x21 // Intel Pentium or PPro +#define CPUID_INTEL_MMX 0x22 // Intel Pentium/MMX or P2/MMX +#define CPUID_INTEL_KATMAI 0x23 // Intel Katmai +#define CPUID_INTEL_WILLIAMETTE 0x24 // Intel Williamette + +#define CPUID_AMD_3DNOW 0x30 // AMD K6 3DNOW! +// +//========================================================== + +#define RoundUp(N, M) ((N) + ((unsigned int)(M)) - (((unsigned int)(N)) % ((unsigned int)(M)))) +#define RoundDown(N, M) ((N) - (((unsigned int)(N)) % ((unsigned int)(M)))) + +char *CopyString( const char *in ); +void Info_Print( const char *s ); + +void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)(char *)); +void Com_EndRedirect( void ); +void QDECL Com_Printf( const char *fmt, ... ); +void QDECL Com_PrintfAlways( const char *fmt, ... ); +void QDECL Com_DPrintf( const char *fmt, ... ); +void QDECL Com_Error( int code, const char *fmt, ... ); +void Com_Quit_f( void ); +int Com_EventLoop( void ); +int Com_Milliseconds( void ); // will be journaled properly +unsigned Com_BlockChecksum( const void *buffer, int length ); +int Com_Filter(char *filter, char *name, int casesensitive); + +void Com_StartupVariable( const char *match ); +// checks for and removes command line "+set var arg" constructs +// if match is NULL, all set commands will be executed, otherwise +// only a set with the exact name. Only used during startup. + + +extern cvar_t *com_developer; +extern cvar_t *com_speeds; +extern cvar_t *com_timescale; +extern cvar_t *com_sv_running; +extern cvar_t *com_cl_running; +extern cvar_t *com_viewlog; // 0 = hidden, 1 = visible, 2 = minimized +extern cvar_t *com_version; + +// both client and server must agree to pause +extern cvar_t *cl_paused; +extern cvar_t *sv_paused; + +// com_speeds times +extern int time_game; +extern int time_frontend; +extern int time_backend; // renderer backend time + +extern int timeInTrace; +extern int timeInPVSCheck; +extern int numTraces; + +extern int com_frameTime; +extern int com_frameMsec; + +extern qboolean com_errorEntered; + + +#ifndef _XBOX +extern fileHandle_t com_journalFile; +extern fileHandle_t com_journalDataFile; +#endif + +/* + +--- low memory ---- +server vm +server clipmap +---mark--- +renderer initialization (shaders, etc) +UI vm +cgame vm +renderer map +renderer models + +---free--- + +temp file loading +--- high memory --- + +*/ +int Z_Validate( void ); // also used to insure all of these are paged in +int Z_MemSize ( memtag_t eTag ); +void Z_TagFree ( memtag_t eTag ); +int Z_Free ( void *ptr ); //returns bytes freed +int Z_Size ( void *pvAddress); +void Z_MorphMallocTag( void *pvAddress, memtag_t eDesiredTag ); +qboolean Z_IsFromZone(void *pvAddress, memtag_t eTag); +qboolean Z_IsFromTempPool(void *pvAddress); + +#ifdef DEBUG_ZONE_ALLOCS + + void *_D_Z_Malloc ( int iSize, memtag_t eTag, qboolean bZeroit, const char *psFile, int iLine ); + void *_D_S_Malloc ( int iSize, const char *psFile, int iLine ); + void _D_Z_Label ( const void *pvAddress, const char *pslabel ); + + #define Z_Malloc(_iSize, _eTag, _bZeroit) _D_Z_Malloc (_iSize, _eTag, _bZeroit, __FILE__, __LINE__) + #define S_Malloc(_iSize) _D_S_Malloc (_iSize, __FILE__, __LINE__) // NOT 0 filled memory only for small allocations + + #define Z_Label(_ptr, _label) _D_Z_Label (_ptr, _label) + +#else + + void *Z_Malloc ( int iSize, memtag_t eTag, qboolean bZeroit, int iAlign = 4); // return memory NOT zero-filled by default + void *S_Malloc ( int iSize ); // NOT 0 filled memory only for small allocations + + #define Z_Label(_ptr, _label) /* */ + +#endif + + +void Hunk_Clear( void ); +void Hunk_ClearToMark( void ); +void Hunk_SetMark( void ); +// note the opposite default for 'bZeroIt' in Hunk_Alloc to Z_Malloc, since Hunk_Alloc always used to memset(0)... +// +inline void *Hunk_Alloc( int size, qboolean bZeroIt = qtrue) +{ + return Z_Malloc(size, TAG_HUNKALLOC, bZeroIt); +} + +// Used to re-tag new/delete allocations: +void Z_PushNewDeleteTag( memtag_t eTag ); +void Z_PopNewDeleteTag( void ); + +void Com_TouchMemory( void ); + +// commandLine should not include the executable name (argv[0]) +void Com_SetOrgAngles(vec3_t org,vec3_t angles); +void Com_Init( char *commandLine ); +void Com_Frame( void ); +void Com_Shutdown( void ); +void Com_ShutdownZoneMemory(void); +void Com_ShutdownHunkMemory(void); + +bool Com_ParseTextFile(const char *file, class CGenericParser2 &parser, bool cleanFirst = true); +CGenericParser2 *Com_ParseTextFile(const char *file, bool cleanFirst, bool writeable); +void Com_ParseTextFileDestroy(class CGenericParser2 &parser); + +/* +============================================================== + +CLIENT / SERVER SYSTEMS + +============================================================== +*/ + +// +// client interface +// +void CL_InitKeyCommands( void ); +// the keyboard binding interface must be setup before execing +// config files, but the rest of client startup will happen later + +void CL_Init( void ); +void CL_Disconnect( void ); +void CL_Shutdown( void ); +void CL_Frame( int msec,float fractionMsec ); +qboolean CL_GameCommand( void ); +void CL_KeyEvent (int key, qboolean down, unsigned time); + +void CL_CharEvent( int key ); +// char events are for field typing, not game control + +void CL_MouseEvent( int dx, int dy, int time ); + +void CL_JoystickEvent( int axis, int value, int time ); + +void CL_PacketEvent( netadr_t from, msg_t *msg ); + +void CL_ConsolePrint( char *text ); + +void CL_MapLoading( void ); +// do a screen update before starting to load a map +// when the server is going to load a new map, the entire hunk +// will be cleared, so the client must shutdown cgame, ui, and +// the renderer + +void CL_ForwardCommandToServer( void ); +// adds the current command line as a clc_clientCommand to the client message. +// things like godmode, noclip, etc, are commands directed to the server, +// so when they are typed in at the console, they will need to be forwarded. + +void CL_FlushMemory( void ); +// dump all memory on an error + +void CL_StartHunkUsers( void ); + +void Key_WriteBindings( fileHandle_t f ); +// for writing the config files + +void S_ClearSoundBuffer( void ); +// call before filesystem access + +void SCR_DebugGraph (float value, int color); // FIXME: move logging to common? + + +// +// server interface +// +void SV_Init( void ); +void SV_Shutdown( char *finalmsg ); +void SV_Frame( int msec,float fractionMsec); +void SV_PacketEvent( netadr_t from, msg_t *msg ); +qboolean SV_GameCommand( void ); + + +// +// UI interface +// +qboolean UI_GameCommand( void ); + + +/* +============================================================== + +NON-PORTABLE SYSTEM SERVICES + +============================================================== +*/ + +typedef enum { + AXIS_SIDE, + AXIS_FORWARD, + AXIS_UP, + AXIS_ROLL, + AXIS_YAW, + AXIS_PITCH, + MAX_JOYSTICK_AXIS +} joystickAxis_t; + +typedef enum { + SE_NONE, // evTime is still valid + SE_KEY, // evValue is a key code, evValue2 is the down flag + SE_CHAR, // evValue is an ascii char + SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves + SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127) + SE_CONSOLE, // evPtr is a char* + SE_PACKET // evPtr is a netadr_t followed by data bytes to evPtrLength +} sysEventType_t; + +typedef struct { + int evTime; + sysEventType_t evType; + int evValue, evValue2; + int evPtrLength; // bytes of data pointed to by evPtr, for journaling + void *evPtr; // this must be manually freed if not NULL +} sysEvent_t; + +sysEvent_t Sys_GetEvent( void ); + +void Sys_Init (void); + +char *Sys_GetCurrentUser( void ); + +void QDECL Sys_Error( const char *error, ...); +void Sys_Quit (void); +char *Sys_GetClipboardData( void ); // note that this isn't journaled... + +void Sys_Print( const char *msg ); +#ifdef _XBOX +void Sys_Log( const char *file, const char *msg ); +void Sys_Log( const char *file, const void *buffer, int size, bool flush ); +#endif + +// Sys_Milliseconds should only be used for profiling purposes, +// any game related timing information should come from event timestamps +int Sys_Milliseconds (void); + + +// the system console is shown when a dedicated server is running +void Sys_DisplaySystemConsole( qboolean show ); + +int Sys_GetProcessorId( void ); + +void Sys_BeginStreamedFile( fileHandle_t f, int readahead ); +void Sys_EndStreamedFile( fileHandle_t f ); +int Sys_StreamedRead( void *buffer, int size, int count, fileHandle_t f ); +void Sys_StreamSeek( fileHandle_t f, int offset, int origin ); + +void Sys_ShowConsole( int level, qboolean quitOnClose ); +void Sys_SetErrorText( const char *text ); + +qboolean Sys_CheckCD( void ); + +void Sys_Mkdir( const char *path ); +char *Sys_Cwd( void ); +char *Sys_DefaultCDPath(void); +char *Sys_DefaultBasePath(void); + +char **Sys_ListFiles( const char *directory, const char *extension, int *numfiles, qboolean wantsubs ); +void Sys_FreeFileList( char **filelist ); + +void Sys_BeginProfiling( void ); +void Sys_EndProfiling( void ); + +qboolean Sys_LowPhysicalMemory(); +qboolean Sys_FileOutOfDate( LPCSTR psFinalFileName /* dest */, LPCSTR psDataFileName /* src */ ); +qboolean Sys_CopyFile(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, qboolean bOverwrite); + + +//byte* SCR_GetScreenshot(qboolean *qValid); +//void SCR_SetScreenshot(const byte *pbData, int w, int h); +//byte* SCR_TempRawImage_ReadFromFile(const char *psLocalFilename, int *piWidth, int *piHeight, byte *pbReSampleBuffer, qboolean qbVertFlip); +//void SCR_TempRawImage_CleanUp(); + +inline int Round(float value) +{ + return((int)floorf(value + 0.5f)); +} + + +#ifdef _XBOX +////////////////////////////// +// +// Map Lump Loader +// +struct Lump +{ + void* data; + int len; + + Lump() : data(NULL), len(0) {} + ~Lump() { clear(); } + + void load(const char* map, const char* lump) + { + clear(); + + char path[MAX_QPATH]; + Com_sprintf(path, MAX_QPATH, "%s/%s.mle", map, lump); + + len = FS_ReadFile(path, &data); + if (len < 0) len = 0; + } + + void clear(void) + { + if (data) + { + FS_FreeFile(data); + data = NULL; + } + } +}; +#endif _XBOX + +#endif //__QCOMMON_H__ diff --git a/code/qcommon/qfiles.h b/code/qcommon/qfiles.h new file mode 100644 index 0000000..604ac0e --- /dev/null +++ b/code/qcommon/qfiles.h @@ -0,0 +1,634 @@ +#ifndef __QFILES_H__ +#define __QFILES_H__ + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 1000 +#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) + + +// the maximum size of game reletive pathnames +#define MAX_QPATH 64 + +/* +======================================================================== + +QVM files + +======================================================================== +*/ + +#define VM_MAGIC 0x12721444 +typedef struct { + int vmMagic; + + int instructionCount; + + int codeOffset; + int codeLength; + + int dataOffset; + int dataLength; + int litLength; // ( dataLength - litLength ) should be byteswapped on load + int bssLength; // zero filled memory appended to datalength +} vmHeader_t; + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + + + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD3_VERSION 15 + +// limits +#define MD3_MAX_LODS 3 +#define MD3_MAX_TRIANGLES 8192 // per surface +#define MD3_MAX_VERTS 4096 // per surface +#define MD3_MAX_SHADERS 256 // per surface +#define MD3_MAX_FRAMES 1024 // per model +#define MD3_MAX_SURFACES 32 + 32 // per model +#define MD3_MAX_TAGS 16 // per frame + +// vertex scales +#define MD3_XYZ_SCALE (1.0/64) + +typedef struct md3Frame_s { + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_QPATH]; // tag name + vec3_t origin; + vec3_t axis[3]; +} md3Tag_t; + +/* +** md3Surface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** shaders sizeof( md3Shader_t ) * numShaders +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ +typedef struct { + int ident; // + + char name[MAX_QPATH]; // polyset name + + int flags; + int numFrames; // all surfaces in a model should have the same + + int numShaders; // all surfaces in a model should have the same + int numVerts; + + int numTriangles; + int ofsTriangles; + + int ofsShaders; // offset from start of md3Surface_t + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows +} md3Surface_t; + +typedef struct { + char name[MAX_QPATH]; + int shaderIndex; // for in-game use +} md3Shader_t; + +typedef struct { + int indexes[3]; +} md3Triangle_t; + +typedef struct { + float st[2]; +} md3St_t; + +typedef struct { + short xyz[3]; + short normal; +} md3XyzNormal_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + int flags; + + int numFrames; + int numTags; + int numSurfaces; + + int numSkins; + + int ofsFrames; // offset for first frame + int ofsTags; // numFrames * numTags + int ofsSurfaces; // first surface, others follow + + int ofsEnd; // end of file +} md3Header_t; + + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + + +#define BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'R') + // little-endian "IBSP" + +#define BSP_VERSION 1 + + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x800 +#define MAX_MAP_ENTSTRING 0x40000 +#define MAX_MAP_SHADERS 0x400 + +#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define MAX_MAP_FOGS 0x100 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x20000 +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x20000 +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x800000 +#define MAX_MAP_LIGHTGRID 65535 +#define MAX_MAP_LIGHTGRID_ARRAY 0x100000 + +#define MAX_MAP_VISIBILITY 0x400000 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 + + +// key / value pair sizes in the entities lump +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + +//============================================================================= + +#ifdef _XBOX + +#pragma pack(push, 1) + +typedef struct { + float mins[3], maxs[3]; + int firstSurface; + unsigned short numSurfaces; + int firstBrush; + unsigned short numBrushes; +} dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for frustom culling + short maxs[3]; +} dnode_t; + +typedef struct { + short cluster; // -1 = opaque cluster (do I still store these?) + signed char area; + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstLeafSurface; + unsigned short numLeafSurfaces; + + unsigned short firstLeafBrush; + unsigned short numLeafBrushes; +} dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + byte shaderNum; +} dbrushside_t; + +typedef struct { + int firstSide; + byte numSides; + unsigned short shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +// Light Style Constants +#define MAXLIGHTMAPS 4 +#define LS_NORMAL 0x00 +#define LS_UNUSED 0xfe +#define LS_NONE 0xff +#define MAX_LIGHT_STYLES 64 + +typedef struct { + float lightmap[MAXLIGHTMAPS][2]; + float st[2]; + short xyz[3]; + short normal[3]; + byte color[MAXLIGHTMAPS][4]; +} mapVert_t; + +#define DRAWVERT_LIGHTMAP_SCALE 32768.0f +// Change texture coordinates for TriSurfs to be even more fine grain. +// See below for note about keeping MIN_ST and MAX_ST up to date with +// ST_SCALE. These are in 4.12. Okay, how about 5.11? Gah. 9.7! +//#define DRAWVERT_ST_SCALE 4096.0f +//#define DRAWVERT_ST_SCALE 2048.0f +#define DRAWVERT_ST_SCALE 128.0f + +// We use a slightly different format for the fixed point texture +// coords in Grid/Mesh drawverts: 10.6 rather than 12.4 +// To be sure that this is ok, keep the max and min values equal to +// the largest and smallest whole numbers that can be stored using the +// format. (ie: Don't change GRID_DRAWVERT_ST_SCALE without changing +// the other two!) (And don't forget that we're using a bit for sign.) +#define GRID_DRAWVERT_ST_SCALE 64.0f + +// This master switch controls whether we use compressed (4-bit per channel) +// vertex colors in draw and surface verts. It saves memory, but I'm switching +// it off, because we end up with that nasty green/purple streaking effect. +// If we ever figure out how to do it better... (1555? 565?) +//#define COMPRESS_VERTEX_COLORS + +typedef struct { + short xyz[3]; + short dvst[2]; + short dvlightmap[MAXLIGHTMAPS][2]; + short normal[3]; +#ifdef _XBOX + vec3_t tangent; +#endif +#ifdef COMPRESS_VERTEX_COLORS + byte dvcolor[MAXLIGHTMAPS][1]; +#else + byte dvcolor[MAXLIGHTMAPS][4]; +#endif +} drawVert_t; + +typedef struct { + byte flags; + byte latLong[2]; +} dgrid_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + unsigned int verts; // high 20 bits are first vert, low 12 are num verts + unsigned int indexes; // high 20 bits are first index, low 12 are num indices + + byte lightmapStyles[MAXLIGHTMAPS]; + byte lightmapNum[MAXLIGHTMAPS]; + + short lightmapVecs[3]; +} dface_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + unsigned int verts; // high 20 bits are first vert, low 12 are num verts + + byte lightmapStyles[MAXLIGHTMAPS]; + byte lightmapNum[MAXLIGHTMAPS]; + + short lightmapVecs[2][3]; // for patches, [0] and [1] are lodbounds + + byte patchWidth; + byte patchHeight; +} dpatch_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + unsigned int verts; // high 20 bits are first vert, low 12 are num verts + unsigned int indexes; // high 20 bits are first index, low 12 are num indices + + byte lightmapStyles[MAXLIGHTMAPS]; +} dtrisurf_t; + +typedef struct { + int code; + byte shaderNum; + signed char fogNum; + + short origin[3]; + short normal[3]; + byte color[3]; +} dflare_t; + +#pragma pack(pop) + +#else // _XBOX + +typedef struct { + int fileofs, filelen; +} lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_SHADERS 1 +#define LUMP_PLANES 2 +#define LUMP_NODES 3 +#define LUMP_LEAFS 4 +#define LUMP_LEAFSURFACES 5 +#define LUMP_LEAFBRUSHES 6 +#define LUMP_MODELS 7 +#define LUMP_BRUSHES 8 +#define LUMP_BRUSHSIDES 9 +#define LUMP_DRAWVERTS 10 +#define LUMP_DRAWINDEXES 11 +#define LUMP_FOGS 12 +#define LUMP_SURFACES 13 +#define LUMP_LIGHTMAPS 14 +#define LUMP_LIGHTGRID 15 +#define LUMP_VISIBILITY 16 +#define LUMP_LIGHTARRAY 17 +#define HEADER_LUMPS 18 + +typedef struct { + int ident; + int version; + + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} dmodel_t; + +typedef struct dshader_s { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (do I still store these?) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int firstLeafSurface; + int numLeafSurfaces; + + int firstLeafBrush; + int numLeafBrushes; +} dleaf_t; + +typedef struct { + int planeNum; // positive plane side faces out of the leaf + int shaderNum; + int drawSurfNum; +} dbrushside_t; + +typedef struct { + int firstSide; + int numSides; + int shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +// Light Style Constants +#define MAXLIGHTMAPS 4 +#define LS_NORMAL 0x00 +#define LS_UNUSED 0xfe +#define LS_NONE 0xff +#define MAX_LIGHT_STYLES 64 + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[MAXLIGHTMAPS][2]; + vec3_t normal; + byte color[MAXLIGHTMAPS][4]; +} mapVert_t; + +typedef struct { + vec3_t xyz; + float st[2]; + float lightmap[MAXLIGHTMAPS][2]; + vec3_t normal; + byte color[MAXLIGHTMAPS][4]; +} drawVert_t; + +typedef struct +{ + byte ambientLight[MAXLIGHTMAPS][3]; + byte directLight[MAXLIGHTMAPS][3]; + byte styles[MAXLIGHTMAPS]; + byte latLong[2]; +} dgrid_t; + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE +} mapSurfaceType_t; + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + byte lightmapStyles[MAXLIGHTMAPS], vertexStyles[MAXLIGHTMAPS]; + int lightmapNum[MAXLIGHTMAPS]; + int lightmapX[MAXLIGHTMAPS], lightmapY[MAXLIGHTMAPS]; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; +} dsurface_t; + +#endif _XBOX + +typedef enum //# hunkAllocType_e +{ + HA_MISC, + HA_MAP, + HA_SHADERS, + HA_LIGHTING, + HA_FOG, + HA_PATCHES, + HA_VIS, + HA_SUBMODELS, + HA_MODELS, + MAX_HA_TYPES +} hunkAllocType_t; + + + +///////////////////////////////////////////////////////////// +// +// Defines and structures required for fonts + +#define GLYPH_COUNT 256 + +// Must match define in stmparse.h +#define STYLE_DROPSHADOW 0x80000000 +#define STYLE_BLINK 0x40000000 +#define SET_MASK 0x00ffffff + +typedef struct +{ + short width; // number of pixels wide + short height; // number of scan lines + short horizAdvance; // number of pixels to advance to the next char + short horizOffset; // x offset into space to render glyph + int baseline; // y offset + float s; // x start tex coord + float t; // y start tex coord + float s2; // x end tex coord + float t2; // y end tex coord +} glyphInfo_t; + + +// this file corresponds 1:1 with the "*.fontdat" files, so don't change it unless you're going to +// recompile the fontgen util and regenerate all the fonts! +// +typedef struct dfontdat_s +{ + glyphInfo_t mGlyphs[GLYPH_COUNT]; + + short mPointSize; + short mHeight; // max height of font + short mAscender; + short mDescender; + + short mKoreanHack; // unused field, written out by John's fontgen program but we have to leave it there for disk structs +} dfontdat_t; + +/////////////////// fonts end //////////////////////////////////// + + + +#endif diff --git a/code/qcommon/sparc.h b/code/qcommon/sparc.h new file mode 100644 index 0000000..5b22c18 --- /dev/null +++ b/code/qcommon/sparc.h @@ -0,0 +1,725 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +/* + +AUTHOR: Dave Calvin +CREATED: 2002-05-07 + +SParse ARray Compressor. Given an array, this class reduces the memory +needed to store the array by eliminating the most-frequently used element. +The remaining elements are increased in size by one integer. + +If the compressed data would be larger than the original data, the +original data is stored as is. + +Compression is O(2N) where N is the number of elements to compress. + +Decompression is O(log M + N) where M is the number of elements after +compression (CompressedLength()) and N is the number of elements to decompress. +Decompression is O(1) when the same or smaller amount of data is requested as +the last decompression. + +The pointer returned by Decompress() is valid until the class is destroyed +or a new call is made to Compress() or Decompress(). + +Elements must define operator==, operator!=, and sizeof. + +*/ + +#ifndef __SPARC_H +#define __SPARC_H + +#ifdef _GAMECUBE +#define SPARC_BIG_ENDIAN +#endif + +//Bigger than a short, smaller than an int. +#pragma pack(push, 1) +struct NotSoShort +{ + unsigned char bytes[3]; + + NotSoShort(void) {} + + NotSoShort(unsigned int source) { +#ifdef SPARC_BIG_ENDIAN + bytes[2] = source & 0xFF; + bytes[1] = (source >> 8) & 0xFF; + bytes[0] = (source >> 16) & 0xFF; +#else + bytes[0] = source & 0xFF; + bytes[1] = (source >> 8) & 0xFF; + bytes[2] = (source >> 16) & 0xFF; +#endif + } + + inline unsigned int GetValue(void) { +#ifdef SPARC_BIG_ENDIAN + return (bytes[0] << 16) | (bytes[1] << 8) | bytes[2]; +#else + return (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]; +#endif + } + + inline bool operator==(unsigned int cmp) { +#ifdef SPARC_BIG_ENDIAN + return cmp == ((*(unsigned int*)bytes) >> 8); +#else + return cmp == ((*(unsigned int*)bytes) & 0x00FFFFFF); +#endif + } + + bool operator<(unsigned int cmp) { + unsigned int tmp = *(unsigned int*)bytes; +#ifdef SPARC_BIG_ENDIAN + tmp >>= 8; +#else + tmp &= 0x00FFFFFF; +#endif + return tmp < cmp; + } + + bool operator<=(unsigned int cmp) { + unsigned int tmp = *(unsigned int*)bytes; +#ifdef SPARC_BIG_ENDIAN + tmp >>= 8; +#else + tmp &= 0x00FFFFFF; +#endif + return tmp <= cmp; + } + + bool operator>(unsigned int cmp) { + unsigned int tmp = *(unsigned int*)bytes; +#ifdef SPARC_BIG_ENDIAN + tmp >>= 8; +#else + tmp &= 0x00FFFFFF; +#endif + return tmp > cmp; + } +}; + +//Compressed data is made up of these elements. +template +struct SPARCElement +{ + T data; + U offset; +}; +#pragma pack(pop) + + +inline unsigned int SPARC_SWAP32(unsigned int x, bool doSwap) { + if (doSwap) { + return ((unsigned int)( ( (x & 0xff000000) >> 24) + + ( (x & 0x00ff0000) >> 8 ) + + ( (x & 0x0000ff00) << 8 ) + + ( (x & 0x000000ff) << 24 ) )); + } + return x; +} + +inline NotSoShort SPARC_SWAP24(NotSoShort x, bool doSwap) { + if (doSwap) { + x.bytes[0] ^= x.bytes[2]; + x.bytes[2] ^= x.bytes[0]; + x.bytes[0] ^= x.bytes[2]; + } + return x; +} + +inline unsigned short SPARC_SWAP16(unsigned short x, bool doSwap) { + if (doSwap) { + return ((unsigned short)( ( (x & 0xff00) >> 8) + + ( (x & 0x00ff) << 8 ) )); + } + return x; +} + + +//The core of the SPARC system. T is the data type to be compressed. +//U is the data type needed to store offsets information in the compressed +//data. Smaller U makes for better compression but bigger data requires +//larger U. +template +class SPARCCore +{ +private: + //Using compression or just storing clear data? + bool compressionUsed; + + //Compressed data and its length. + SPARCElement *compressedData; + unsigned int compressedLength; + + //Decompression cache. + T *decompressedData; + unsigned int decompressedOffset; + unsigned int decompressedLength; + + //Element which was removed to compress. + T removedElement; + + //Length of original data before compression. + unsigned int originalLength; + + //Memory allocators. + void* (*Allocator)(unsigned int size); + void (*Deallocator)(void *ptr); + + //Destroy all allocated memory. + void Cleanup(void) { + if(compressedData) { + if(Deallocator) { + Deallocator(compressedData); + } else { + delete [] compressedData; + } + compressedData = NULL; + } + + if(decompressedData) { + if(Deallocator) { + Deallocator(decompressedData); + } else { + delete [] decompressedData; + } + decompressedData = NULL; + } + } + + void Init(void) { + compressionUsed = false; + compressedData = NULL; + originalLength = 0; + compressedLength = 0; + decompressedData = NULL; + decompressedOffset = 0; + decompressedLength = 0; + } + + + //Binary search for the compressed element most closely matching 'offset'. + SPARCElement *FindDecompStart(unsigned int offset) + { + unsigned int startPoint = compressedLength / 2; + unsigned int divisor = 4; + unsigned int leap; + while(1) { + if(compressedData[startPoint].offset <= offset && + compressedData[startPoint+1].offset > offset) { + if(compressedData[startPoint].offset == offset) { + return &compressedData[startPoint]; + } else { + return &compressedData[startPoint+1]; + } + } + + leap = compressedLength / divisor; + if(leap < 1) { + leap = 1; + } else { + divisor *= 2; + } + if(compressedData[startPoint].offset > offset) { + startPoint -= leap; + } else { + startPoint += leap; + } + } + } + +public: + SPARCCore(void) { + Init(); + Allocator = NULL; + Deallocator = NULL; + } + + ~SPARCCore(void) { + Cleanup(); + } + + void SetAllocator(void* (*alloc)(unsigned int size), + void (*dealloc)(void *ptr)) { + Allocator = alloc; + Deallocator = dealloc; + } + + //Just store the array without compression. + unsigned int Store(const T *array, unsigned int length) { + //Destroy old data. + Cleanup(); + Init(); + + //Allocate memory and copy array. + if(Allocator) { + decompressedData = (T*)Allocator(length * sizeof(T)); + } else { + decompressedData = new T[length]; + } + compressedLength = length; + memcpy(decompressedData, array, sizeof(T) * length); + + //Set length. + originalLength = length; + + return CompressedSize(); + } + + //Load compressed data directly. + unsigned int Load(const char *array, unsigned int length) { + //Destroy old data. + Cleanup(); + Init(); + + //Restore some attributes. + compressionUsed = (bool)*array++; + + assert(sizeof(T) == 1); //For now only support characters. + removedElement = *(T*)array; + array += sizeof(T); + + originalLength = *(unsigned int*)array; + array += sizeof(unsigned int); + + compressedLength = *(unsigned int*)array; + array += sizeof(unsigned int); + + //Allocate memory and copy array. + if (compressionUsed) { + if(Allocator) { + compressedData = (SPARCElement*) + Allocator(compressedLength * sizeof(SPARCElement)); + } else { + compressedData = new SPARCElement[compressedLength]; + } + memcpy(compressedData, array, + compressedLength * sizeof(SPARCElement)); + } + else { + if(Allocator) { + decompressedData = (T*)Allocator( + compressedLength * sizeof(T)); + } else { + decompressedData = new T[compressedLength]; + } + memcpy(decompressedData, array, compressedLength * sizeof(T)); + } + + return CompressedSize(); + } + + //Save state for later restoration. + unsigned int Save(char *array, unsigned int length, bool doSwap) { + //Figure out how much space is needed. + unsigned int size = sizeof(char) + sizeof(T) + + sizeof(unsigned int) + sizeof(unsigned int); + + if (compressionUsed) { + size += compressedLength * sizeof(SPARCElement); + } + else { + size += compressedLength * sizeof(T); + } + + assert(length >= size); + + //Save some attributes. + *array++ = (char)compressionUsed; + + assert(sizeof(T) == 1); //For now only support characters. + *(T*)array = removedElement; + array += sizeof(T); + + *(unsigned int*)array = SPARC_SWAP32(originalLength, doSwap); + array += sizeof(unsigned int); + + *(unsigned int*)array = SPARC_SWAP32(compressedLength, doSwap); + array += sizeof(unsigned int); + + //Store compressed data (or uncompressed data if none exists) + if (compressionUsed) { + for (unsigned int i = 0; i < compressedLength; ++i) { + //Copy the data element. For now only support characters. + ((SPARCElement *)array)[i].data = compressedData[i].data; + + //Copy the offset to the next unique element. + if (sizeof(U) == 1) { + ((SPARCElement *)array)[i].offset = + compressedData[i].offset; + } + else if (sizeof(U) == 2) { + ((SPARCElement *)array)[i].offset = + SPARC_SWAP16(*(unsigned short*)&compressedData[i].offset, + doSwap); + } + else if (sizeof(U) == 3) { + ((SPARCElement *)array)[i].offset = + SPARC_SWAP24(*(NotSoShort*)&compressedData[i].offset, + doSwap); + } + else if (sizeof(U) == 4) { + ((SPARCElement *)array)[i].offset = + SPARC_SWAP32(*(unsigned int*)&compressedData[i].offset, + doSwap); + } + } + } + else { + memcpy(array, decompressedData, compressedLength * sizeof(T)); + } + + return size; + } + + //Compresses this array, returns the compressed size. Compresses + //by eliminating the given element. + unsigned int Compress(const T *array, unsigned int length, T removal) { + + unsigned int i; + unsigned int numRemove = 0; + SPARCElement *compress; + + //Destroy old data. + Cleanup(); + Init(); + + //Count number of elements to remove. Can't remove first or + //last element (prevents boundary conditions). + for(i=1; i) * compressedLength >= + sizeof(T) * length) { + Store(array, length); + return CompressedSize(); + } + + //Allocate memory for compressed elements. + if(Allocator) { + compressedData = (SPARCElement*) + Allocator(compressedLength * sizeof(SPARCElement)); + } else { + compressedData = new SPARCElement[compressedLength]; + } + compressionUsed = true; + + //Fill compressed array. First and last elements go in no matter + //what. + compressedData[0].data = array[0]; + compressedData[0].offset = 0; + compress = &compressedData[1]; + for(i=1; idata = array[i]; + compress->offset = i; + compress++; + } + } + compress->data = array[i]; + compress->offset = i; + + //Store removal value for decompression purposes. + removedElement = removal; + + //Store original length for bounds checking. + originalLength = length; + + //Return the compressed size. + return CompressedSize(); + } + + + //Get the compressed data size in bytes, or 0 if nothing stored. + unsigned int CompressedSize(void) { + return compressedLength * sizeof(SPARCElement); + } + + //Get the decompressed data starting at offset and ending at + //offset + length. Returns NULL on error. + const T *Decompress(unsigned int offset, unsigned int length) { + + SPARCElement *decomp = NULL; + unsigned int i; + + //If data isn't compressed, just return a pointers. + if(!compressionUsed) { + return decompressedData + offset; + } + + //If last decompression falls within offset and length, just return + //a pointer. + if(decompressedData && decompressedOffset <= offset && + decompressedOffset + decompressedLength >= offset + length) { + return decompressedData + offset - decompressedOffset; + } + + + + //Allocate new space for decompression if length has changed. + if(length != decompressedLength) { + //Destroy old data first. + if(decompressedData) { + if(Deallocator) { + Deallocator(decompressedData); + } else { + delete [] decompressedData; + } + } + + if(Allocator) { + decompressedData = (T*)Allocator(length * sizeof(T)); + } else { + decompressedData = new T[length]; + } + } + decompressedOffset = offset; + decompressedLength = length; + + //Find position to start decompressing from. + decomp = FindDecompStart(offset); + + if(!decomp) { //should never happen + assert(0); + return NULL; + } + + //Decompress the data. + for(i=0; i < length; i++) { + if(decomp->offset == i + offset) { + decompressedData[i] = decomp->data; + decomp++; + } else { + decompressedData[i] = removedElement; + } + } + + return decompressedData; + } +}; + + +//The user-interface to SPARC. Automatically selects the best core based +//on data size. +template +class SPARC +{ +private: + void *core; + unsigned char offsetBytes; + + //Memory allocators. + void* (*Allocator)(unsigned int size); + void (*Deallocator)(void *ptr); + +public: + SPARC(void) { + core = NULL; + offsetBytes = 0; + Allocator = NULL; + Deallocator = NULL; + } + + ~SPARC(void) { + Release(); + }; + + void SetAllocator(void* (*alloc)(unsigned int size), + void (*dealloc)(void *ptr)) { + Allocator = alloc; + Deallocator = dealloc; + } + + //Select a core, cast it to the right type and return the size. + unsigned int CompressedSize(void) { + if(!core) { + return 0; + } + + switch(offsetBytes) { + case 1: + return ((SPARCCore*)core)->CompressedSize(); + case 2: + return ((SPARCCore*)core)->CompressedSize(); + case 3: + return ((SPARCCore*)core)->CompressedSize(); + case 4: + return ((SPARCCore*)core)->CompressedSize(); + } + + return 0; + } + + //Always use the same core type since we won't be compressing. + unsigned int Store(const T *array, unsigned int length) + { + Release(); + offsetBytes = 1; + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> Store(array, length); + } + + //Load compressed data directly. + unsigned int Load(const char *array, unsigned int length) { + Release(); + + offsetBytes = *array++; + + switch (offsetBytes) { + case 1: + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Load(array, length-1); + case 2: + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Load(array, length-1); + case 3: + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Load(array, length-1); + case 4: + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Load(array, length-1); + default: + assert(false); + return 0; + } + } + + //Save compressed data into array. + unsigned int Save(char *array, unsigned int length, bool doSwap) { + *array++ = offsetBytes; + + switch (offsetBytes) { + case 1: + return ((SPARCCore*)core)-> + Save(array, length-1, doSwap); + case 2: + return ((SPARCCore*)core)-> + Save(array, length-1, doSwap); + case 3: + return ((SPARCCore*)core)-> + Save(array, length-1, doSwap); + case 4: + return ((SPARCCore*)core)-> + Save(array, length-1, doSwap); + default: + assert(false); + return 0; + } + } + + //Create the smallest core possible for the given data. + unsigned int Compress(const T *array, unsigned int length, T removal) { + Release(); + + if(length < 256) { + offsetBytes = 1; + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Compress(array, length, removal); + } else if(length < 65536) { + offsetBytes = 2; + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Compress(array, length, removal); + } else if(length < 16777216) { + offsetBytes = 3; + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Compress(array, length, removal); + } else { + offsetBytes = 4; + core = new SPARCCore; + ((SPARCCore*)core)-> + SetAllocator(Allocator, Deallocator); + return ((SPARCCore*)core)-> + Compress(array, length, removal); + } + } + + //Cast to the correct core type and decompress. + const T *Decompress(unsigned int offset, unsigned int length) { + if(!core) { + return NULL; + } + + switch(offsetBytes) { + case 1: + return ((SPARCCore*)core)-> + Decompress(offset, length); + case 2: + return ((SPARCCore*)core)-> + Decompress(offset, length); + case 3: + return ((SPARCCore*)core)-> + Decompress(offset, length); + case 4: + return ((SPARCCore*)core)-> + Decompress(offset, length); + } + + return NULL; + } + + //Destroy all compressed data and the current decompressed buffer. + void Release(void) { + if(core) { + switch(offsetBytes) { + case 1: + delete (SPARCCore*)core; + break; + case 2: + delete (SPARCCore*)core; + break; + case 3: + delete (SPARCCore*)core; + break; + case 4: + delete (SPARCCore*)core; + break; + } + core = NULL; + } + } +}; + +#endif diff --git a/code/qcommon/sstring.h b/code/qcommon/sstring.h new file mode 100644 index 0000000..c0a2484 --- /dev/null +++ b/code/qcommon/sstring.h @@ -0,0 +1,120 @@ +// Filename:- sstring.h +// +// Gil's string template, used to replace Microsoft's vrsion which doesn't compile under certain stl map<> +// conditions... + + +#ifndef SSTRING_H +#define SSTRING_H + + +template +class sstring +{ + struct SStorage + { + char data[MaxSize]; + }; + SStorage mStorage; +public: +/* don't figure we need this + template + sstring(const sstring &o) + { + assert(strlen(o.mStorage.data) &o) + { + //strcpy(mStorage.data,o.mStorage.data); + Q_strncpyz(mStorage.data,o.mStorage.data,sizeof(mStorage.data),qtrue); + } + sstring(const char *s) + { + //assert(strlen(s) + sstring & operator =(const sstring &o) + { + assert(strlen(o.mStorage.data) & operator=(const sstring &o) + { + //strcpy(mStorage.data,o.mStorage.data); + Q_strncpyz(mStorage.data,o.mStorage.data,sizeof(mStorage.data),qtrue); + return *this; + } + sstring & operator=(const char *s) + { + assert(strlen(s) &o) const + { + if (!strcmpi(mStorage.data,o.mStorage.data)) + { + return true; + } + return false; + } + bool operator!=(const sstring &o) const + { + if (strcmpi(mStorage.data,o.mStorage.data)!=0) + { + return true; + } + return false; + } + bool operator<(const sstring &o) const + { + if (strcmpi(mStorage.data,o.mStorage.data)<0) + { + return true; + } + return false; + } + bool operator>(const sstring &o) const + { + if (strcmpi(mStorage.data,o.mStorage.data)>0) + { + return true; + } + return false; + } +}; + +typedef sstring sstring_t; + +#endif // #ifndef SSTRING_H + +/////////////////// eof //////////////////// + diff --git a/code/qcommon/stringed_ingame.cpp b/code/qcommon/stringed_ingame.cpp new file mode 100644 index 0000000..a3048d4 --- /dev/null +++ b/code/qcommon/stringed_ingame.cpp @@ -0,0 +1,985 @@ +// Filename:- stringed_ingame.cpp +// +// This file is designed to be pasted into each game project that uses the StringEd package's files. +// You can alter the way it does things by (eg) replacing STL with RATL, BUT LEAVE THE OVERALL +// FUNCTIONALITY THE SAME, or if I ever make any funadamental changes to the way the package works +// then you're going to be SOOL (shit out of luck ;-)... +// + +////////////////////////////////////////////////// +// +// stuff common to all qcommon files... +#include "../server/server.h" +#include "../game/q_shared.h" +#include "qcommon.h" +#include "../qcommon/fixedmap.h" +#include "../zlib/zlib.h" + +// +////////////////////////////////////////////////// + + +#pragma warning ( disable : 4511 ) // copy constructor could not be generated +#pragma warning ( disable : 4512 ) // assignment operator could not be generated +#pragma warning ( disable : 4663 ) // C++ language change: blah blah template crap blah blah +#include "stringed_ingame.h" +#include "stringed_interface.h" + +// Needed for DWORD and XC_LANGUAGE defines: +#include + +/////////////////////////////////////////////// + +// some STL stuff... +#include +using namespace std; + +/////////////////////////////////////////////// + +cvar_t *se_language = NULL; + +// Yeah, it's hardcoded. I don't give a shit. +#define MAX_STRING_ENTRIES 4096 + + +typedef struct SE_Entry_s +{ + string m_strString; +} SE_Entry_t; + + +//typedef map mapStringEntries_t; + +class CStringEdPackage +{ +private: + + SE_BOOL m_bEndMarkerFound_ParseOnly; + string m_strCurrentEntryRef_ParseOnly; + string m_strCurrentEntryEnglish_ParseOnly; + string m_strCurrentFileRef_ParseOnly; + string m_strLoadingLanguage_ParseOnly; // eg "german" + SE_BOOL m_bLoadingEnglish_ParseOnly; + +public: + + CStringEdPackage() + { + Z_PushNewDeleteTag( TAG_STRINGED ); + m_Strings = new VVFixedMap< char *, unsigned long >( MAX_STRING_ENTRIES ); + Z_PopNewDeleteTag(); + + Clear( SE_FALSE ); + } + + ~CStringEdPackage() + { + Clear( SE_FALSE ); + } + + // Text entries, indexed by crc32 of reference: + VVFixedMap< char *, unsigned long > *m_Strings; + +// mapStringEntries_t m_StringEntries; // needs to be in public space now + + void Clear( SE_BOOL bChangingLanguages ); + void SetupNewFileParse( LPCSTR psFileName ); + SE_BOOL ReadLine( LPCSTR &psParsePos, char *psDest ); + LPCSTR ParseLine( LPCSTR psLine ); + LPCSTR ExtractLanguageFromPath( LPCSTR psFileName ); + SE_BOOL EndMarkerFoundDuringParse( void ) + { + return m_bEndMarkerFound_ParseOnly; + } + +private: + + void AddEntry( LPCSTR psLocalReference ); + int GetNumStrings(void); + void SetString( LPCSTR psLocalReference, LPCSTR psNewString, SE_BOOL bEnglishDebug ); + SE_BOOL SetReference( int iIndex, LPCSTR psNewString ); + LPCSTR GetCurrentFileName(void); + LPCSTR GetCurrentReference_ParseOnly( void ); + SE_BOOL CheckLineForKeyword( LPCSTR psKeyword, LPCSTR &psLine); + LPCSTR InsideQuotes( LPCSTR psLine ); + LPCSTR ConvertCRLiterals_Read( LPCSTR psString ); + void REMKill( char *psBuffer ); + char *Filename_PathOnly( LPCSTR psFilename ); + char *Filename_WithoutPath(LPCSTR psFilename); + char *Filename_WithoutExt(LPCSTR psFilename); +}; + +CStringEdPackage TheStringPackage; + + +void CStringEdPackage::Clear( SE_BOOL bChangingLanguages ) +{ +// m_StringEntries.clear(); + + m_bEndMarkerFound_ParseOnly = SE_FALSE; + m_strCurrentEntryRef_ParseOnly = ""; + m_strCurrentEntryEnglish_ParseOnly = ""; + // + // the other vars are cleared in SetupNewFileParse(), and are ok to not do here. + // +} + + + +// loses anything after the path (if any), (eg) "dir/name.bmp" becomes "dir" +// (copes with either slash-scheme for names) +// +// (normally I'd call another function for this, but this is supposed to be engine-independant, +// so a certain amount of re-invention of the wheel is to be expected...) +// +char *CStringEdPackage::Filename_PathOnly(LPCSTR psFilename) +{ + static char sString[ iSE_MAX_FILENAME_LENGTH ]; + + strcpy(sString,psFilename); + + char *p1= strrchr(sString,'\\'); + char *p2= strrchr(sString,'/'); + char *p = (p1>p2)?p1:p2; + if (p) + *p=0; + + return sString; +} + + +// returns (eg) "dir/name" for "dir/name.bmp" +// (copes with either slash-scheme for names) +// +// (normally I'd call another function for this, but this is supposed to be engine-independant, +// so a certain amount of re-invention of the wheel is to be expected...) +// +char *CStringEdPackage::Filename_WithoutExt(LPCSTR psFilename) +{ + static char sString[ iSE_MAX_FILENAME_LENGTH ]; + + strcpy(sString,psFilename); + + char *p = strrchr(sString,'.'); + char *p2= strrchr(sString,'\\'); + char *p3= strrchr(sString,'/'); + + // special check, make sure the first suffix we found from the end wasn't just a directory suffix (eg on a path'd filename with no extension anyway) + // + if (p && + (p2==0 || (p2 && p>p2)) && + (p3==0 || (p3 && p>p3)) + ) + *p=0; + + return sString; +} + +// returns actual filename only, no path +// (copes with either slash-scheme for names) +// +// (normally I'd call another function for this, but this is supposed to be engine-independant, +// so a certain amount of re-invention of the wheel is to be expected...) +// +char *CStringEdPackage::Filename_WithoutPath(LPCSTR psFilename) +{ + static char sString[ iSE_MAX_FILENAME_LENGTH ]; + + LPCSTR psCopyPos = psFilename; + + while (*psFilename) + { + if (*psFilename == '/' || *psFilename == '\\') + psCopyPos = psFilename+1; + psFilename++; + } + + strcpy(sString,psCopyPos); + + return sString; +} + + +LPCSTR CStringEdPackage::ExtractLanguageFromPath( LPCSTR psFileName ) +{ + return Filename_WithoutPath( Filename_PathOnly( psFileName ) ); +} + + +void CStringEdPackage::SetupNewFileParse( LPCSTR psFileName ) +{ + char sString[ iSE_MAX_FILENAME_LENGTH ]; + + strcpy(sString, Filename_WithoutPath( Filename_WithoutExt( psFileName ) )); + Q_strupr(sString); + + m_strCurrentFileRef_ParseOnly = sString; // eg "OBJECTIVES" + m_strLoadingLanguage_ParseOnly = ExtractLanguageFromPath( psFileName ); + m_bLoadingEnglish_ParseOnly = (!stricmp( m_strLoadingLanguage_ParseOnly.c_str(), "english" )) ? SE_TRUE : SE_FALSE; +} + + +// returns SE_TRUE if supplied keyword found at line start (and advances supplied ptr past any whitespace to next arg (or line end if none), +// +// else returns SE_FALSE... +// +SE_BOOL CStringEdPackage::CheckLineForKeyword( LPCSTR psKeyword, LPCSTR &psLine) +{ + if (!Q_stricmpn(psKeyword, psLine, strlen(psKeyword)) ) + { + psLine += strlen(psKeyword); + + // skip whitespace to arrive at next item... + // + while ( *psLine == '\t' || *psLine == ' ' ) + { + psLine++; + } + return SE_TRUE; + } + + return SE_FALSE; +} + +// change "\n" to '\n' (i.e. 2-byte char-string to 1-byte ctrl-code)... +// (or "\r\n" in editor) +// +LPCSTR CStringEdPackage::ConvertCRLiterals_Read( LPCSTR psString ) +{ + static string str; + str = psString; + int iLoc; + while ( (iLoc = str.find("\\n")) != -1 ) + { + str[iLoc ] = '\n'; + str.erase( iLoc+1,1 ); + } + + return str.c_str(); +} + + +// kill off any "//" onwards part in the line, but NOT if it's inside a quoted string... +// +void CStringEdPackage::REMKill( char *psBuffer ) +{ + char *psScanPos = psBuffer; + char *p; + int iDoubleQuotesSoFar = 0; + + // scan forwards in case there are more than one (and the first is inside quotes)... + // + while ( (p=strstr(psScanPos,"//")) != NULL) + { + // count the number of double quotes before this point, if odd number, then we're inside quotes... + // + int iDoubleQuoteCount = iDoubleQuotesSoFar; + + for (int i=0; i=0 && isspace(psScanPos[iWhiteSpaceScanPos])) + { + psScanPos[iWhiteSpaceScanPos--] = '\0'; + } + } + + return; + } + else + { + // inside quotes (blast), oh well, skip past and keep scanning... + // + psScanPos = p+1; + iDoubleQuotesSoFar = iDoubleQuoteCount; + } + } +} + +// returns true while new lines available to be read... +// +SE_BOOL CStringEdPackage::ReadLine( LPCSTR &psParsePos, char *psDest ) +{ + if (psParsePos[0]) + { + LPCSTR psLineEnd = strchr(psParsePos, '\n'); + if (psLineEnd) + { + int iCharsToCopy = (psLineEnd - psParsePos); + strncpy(psDest, psParsePos, iCharsToCopy); + psDest[iCharsToCopy] = '\0'; + psParsePos += iCharsToCopy; + while (*psParsePos && strchr("\r\n",*psParsePos)) + { + psParsePos++; // skip over CR or CR/LF pairs + } + } + else + { + // last line... + // + strcpy(psDest, psParsePos); + psParsePos += strlen(psParsePos); + } + + // clean up the line... + // + if (psDest[0]) + { + int iWhiteSpaceScanPos = strlen(psDest)-1; + while (iWhiteSpaceScanPos>=0 && isspace(psDest[iWhiteSpaceScanPos])) + { + psDest[iWhiteSpaceScanPos--] = '\0'; + } + + REMKill( psDest ); + } + return SE_TRUE; + } + + return SE_FALSE; +} + +// remove any outside quotes from this supplied line, plus any leading or trailing whitespace... +// +LPCSTR CStringEdPackage::InsideQuotes( LPCSTR psLine ) +{ + // I *could* replace this string object with a declared array, but wasn't sure how big to leave it, and it'd have to + // be static as well, hence permanent. (problem on consoles?) + // + static string str; + str = ""; // do NOT join to above line + + // skip any leading whitespace... + // + while (*psLine == ' ' || *psLine == '\t') + { + psLine++; + } + + // skip any leading quote... + // + if (*psLine == '"') + { + psLine++; + } + + // assign it... + // + str = psLine; + + if (psLine[0]) + { + // lose any trailing whitespace... + // + while ( str.c_str()[ strlen(str.c_str()) -1 ] == ' ' || + str.c_str()[ strlen(str.c_str()) -1 ] == '\t' + ) + { + str.erase( strlen(str.c_str()) -1, 1); + } + + // lose any trailing quote... + // + if (str.c_str()[ strlen(str.c_str()) -1 ] == '"') + { + str.erase( strlen(str.c_str()) -1, 1); + } + } + + // and return it... + // + return str.c_str(); +} + + + +// this copes with both foreigners using hi-char values (eg the french using 0x92 instead of 0x27 +// for a "'" char), as well as the fact that our buggy fontgen program writes out zeroed glyph info for +// some fonts anyway (though not all, just as a gotcha). +// +// New bit, instead of static buffer (since XBox guys are desperately short of mem) I return a malloc'd buffer now, +// so remember to free it! +// +static char *CopeWithDumbStringData( LPCSTR psSentence, LPCSTR psThisLanguage ) +{ + const int iBufferSize = strlen(psSentence)*3; // *3 to allow for expansion of anything even stupid string consisting entirely of elipsis chars + char *psNewString = (char *) Z_Malloc(iBufferSize, TAG_TEMP_WORKSPACE, qfalse); + Q_strncpyz(psNewString, psSentence, iBufferSize); + + // this is annoying, I have to just guess at which languages to do it for (ie NOT ASIAN/MBCS!!!) since the + // string system was deliberately (and correctly) designed to not know or care whether it was doing SBCS + // or MBCS languages, because it was never envisioned that I'd have to clean up other people's mess. + // + // Ok, bollocks to it, this will have to do. Any other languages that come later and have bugs in their text can + // get fixed by them typing it in properly in the first place... + // + if (!stricmp(psThisLanguage,"ENGLISH") || + !stricmp(psThisLanguage,"FRENCH") || + !stricmp(psThisLanguage,"GERMAN") || + !stricmp(psThisLanguage,"ITALIAN") || + !stricmp(psThisLanguage,"SPANISH") || + !stricmp(psThisLanguage,"POLISH") || + !stricmp(psThisLanguage,"RUSSIAN") + ) + { + char *p; + + // strXLS_Speech.Replace(va("%c",0x92),va("%c",0x27)); // "'" + while ((p=strchr(psNewString,0x92))!=NULL) // "rich" (and illegal) apostrophe + { + *p = 0x27; + } + + // strXLS_Speech.Replace(va("%c",0x93),"\""); // smart quotes -> '"' + while ((p=strchr(psNewString,0x93))!=NULL) + { + *p = '"'; + } + + // strXLS_Speech.Replace(va("%c",0x94),"\""); // smart quotes -> '"' + while ((p=strchr(psNewString,0x94))!=NULL) + { + *p = '"'; + } + + // strXLS_Speech.Replace(va("%c",0x0B),"."); // full stop + while ((p=strchr(psNewString,0x0B))!=NULL) + { + *p = '.'; + } + + // strXLS_Speech.Replace(va("%c",0x85),"..."); // "..."-char -> 3-char "..." + while ((p=strchr(psNewString,0x85))!=NULL) // "rich" (and illegal) apostrophe + { + memmove(p+2,p,strlen(p)); + *p++ = '.'; + *p++ = '.'; + *p = '.'; + } + + // strXLS_Speech.Replace(va("%c",0x91),va("%c",0x27)); // "'" + while ((p=strchr(psNewString,0x91))!=NULL) + { + *p = 0x27; + } + + // strXLS_Speech.Replace(va("%c",0x96),va("%c",0x2D)); // "-" + while ((p=strchr(psNewString,0x96))!=NULL) + { + *p = 0x2D; + } + + // strXLS_Speech.Replace(va("%c",0x97),va("%c",0x2D)); // "-" + while ((p=strchr(psNewString,0x97))!=NULL) + { + *p = 0x2D; + } + + // bug fix for picky grammatical errors, replace "?." with "? " + // + while ((p=strstr(psNewString,"?."))!=NULL) + { + p[1] = ' '; + } + + // StripEd and our print code don't support tabs... + // + while ((p=strchr(psNewString,0x09))!=NULL) + { + *p = ' '; + } + } + + return psNewString; +} + +// return is either NULL for good else error message to display... +// +LPCSTR CStringEdPackage::ParseLine( LPCSTR psLine ) +{ + LPCSTR psErrorMessage = NULL; + + if (psLine) + { + if (CheckLineForKeyword( sSE_KEYWORD_VERSION, psLine )) + { + // VERSION "1" + // + LPCSTR psVersionNumber = InsideQuotes( psLine ); + int iVersionNumber = atoi( psVersionNumber ); + + if (iVersionNumber != iSE_VERSION) + { + psErrorMessage = va("Unexpected version number %d, expecting %d!\n", iVersionNumber, iSE_VERSION); + } + } + else + if ( CheckLineForKeyword(sSE_KEYWORD_CONFIG, psLine) + || CheckLineForKeyword(sSE_KEYWORD_FILENOTES, psLine) + || CheckLineForKeyword(sSE_KEYWORD_NOTES, psLine) + ) + { + // not used ingame, but need to absorb the token + } + else + if (CheckLineForKeyword(sSE_KEYWORD_REFERENCE, psLine)) + { + // REFERENCE GUARD_GOOD_TO_SEE_YOU + // + LPCSTR psLocalReference = InsideQuotes( psLine ); + AddEntry( psLocalReference ); + } + else + if (CheckLineForKeyword(sSE_KEYWORD_ENDMARKER, psLine)) + { + // ENDMARKER + // + m_bEndMarkerFound_ParseOnly = SE_TRUE; // the only major error checking I bother to do (for file truncation) + } + else + if (!Q_stricmpn(sSE_KEYWORD_LANG, psLine, strlen(sSE_KEYWORD_LANG))) + { + // LANG_ENGLISH "GUARD: Good to see you, sir. Taylor is waiting for you in the clean tent. We need to get you suited up. " + // + LPCSTR psReference = GetCurrentReference_ParseOnly(); + if ( psReference[0] ) + { + psLine += strlen(sSE_KEYWORD_LANG); + + // what language is this?... + // + LPCSTR psWordEnd = psLine; + while (*psWordEnd && *psWordEnd != ' ' && *psWordEnd != '\t') + { + psWordEnd++; + } + char sThisLanguage[1024]={0}; + int iCharsToCopy = psWordEnd - psLine; + if (iCharsToCopy > sizeof(sThisLanguage)-1) + { + iCharsToCopy = sizeof(sThisLanguage)-1; + } + strncpy(sThisLanguage, psLine, iCharsToCopy); // already declared as {0} so no need to zero-cap dest buffer + + psLine += strlen(sThisLanguage); + LPCSTR _psSentence = ConvertCRLiterals_Read( InsideQuotes( psLine ) ); + + // Dammit, I hate having to do crap like this just because other people mess up and put + // stupid data in their text, so I have to cope with it. + // + // note hackery with _psSentence and psSentence because of const-ness. bleurgh. Just don't ask. + // + char *psSentence = CopeWithDumbStringData( _psSentence, sThisLanguage ); + + if ( m_bLoadingEnglish_ParseOnly ) + { + // if loading just "english", then go ahead and store it... + // + SetString( psReference, psSentence, SE_FALSE ); + } + else + { + // if loading a foreign language... + // + SE_BOOL bSentenceIsEnglish = (!stricmp(sThisLanguage,"english")) ? SE_TRUE: SE_FALSE; // see whether this is the english master or not + + // this check can be omitted, I'm just being extra careful here... + // + if ( !bSentenceIsEnglish ) + { + // basically this is just checking that an .STE file override is the same language as the .STR... + // + if (stricmp( m_strLoadingLanguage_ParseOnly.c_str(), sThisLanguage )) + { + psErrorMessage = va("Language \"%s\" found when expecting \"%s\"!\n", sThisLanguage, m_strLoadingLanguage_ParseOnly.c_str()); + } + } + + if (!psErrorMessage) + { + SetString( psReference, psSentence, bSentenceIsEnglish ); + } + } + + Z_Free( psSentence ); + } + else + { + psErrorMessage = "Error parsing file: Unexpected \"" sSE_KEYWORD_LANG "\"\n"; + } + } + else + { + psErrorMessage = va("Unknown keyword at linestart: \"%s\"\n", psLine); + } + } + + return psErrorMessage; +} + +// returns reference of string being parsed, else "" for none. +// +LPCSTR CStringEdPackage::GetCurrentReference_ParseOnly( void ) +{ + return m_strCurrentEntryRef_ParseOnly.c_str(); +} + +// add new string entry (during parse) +// +void CStringEdPackage::AddEntry( LPCSTR psLocalReference ) +{ + // the reason I don't just assign it anyway is because the optional .STE override files don't contain flags, + // and therefore would wipe out the parsed flags of the .STR file... + // +/* + mapStringEntries_t::iterator itEntry = m_StringEntries.find( va("%s_%s",m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference) ); + if (itEntry == m_StringEntries.end()) + { + SE_Entry_t SE_Entry; + m_StringEntries[ va("%s_%s", m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference) ] = SE_Entry; + } +*/ + m_strCurrentEntryRef_ParseOnly = psLocalReference; + +} + + +void CStringEdPackage::SetString( LPCSTR psLocalReference, LPCSTR psNewString, SE_BOOL bEnglishDebug ) +{ + const char *ref = va("%s_%s", m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference); + unsigned long refCrc = crc32( 0, (const Bytef *)ref, strlen(ref) ); + + if ( bEnglishDebug ) + { + // This is the leading english text of a foreign sentence pair (so it's the debug-key text): + // don't store, just make a note in-case #same shows up: + m_strCurrentEntryEnglish_ParseOnly = psNewString; + } + else if ( m_bLoadingEnglish_ParseOnly ) + { + // It's the english text, and we're loading english. Add it! + int len = strlen( psNewString ); + char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse ); + strcpy( strData, psNewString ); + + m_Strings->Insert( strData, refCrc ); + } + else + { + // It's foreign text, we're going to add it, but we need to check for #same + if (!stricmp(psNewString, sSE_EXPORT_SAME)) + { + // If it's #same, then copy the stored english version: + int len = m_strCurrentEntryEnglish_ParseOnly.length(); + char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse ); + strcpy( strData, m_strCurrentEntryEnglish_ParseOnly.c_str() ); + + m_Strings->Insert( strData, refCrc ); + } + else + { + // Explicit foreign text. Add it! + int len = strlen( psNewString ); + char *strData = (char *) Z_Malloc( len + 1, TAG_STRINGED, qfalse ); + strcpy( strData, psNewString ); + + m_Strings->Insert( strData, refCrc ); + } + } +} + + + +// filename is local here, eg: "strings/german/obj.str" +// +// return is either NULL for good else error message to display... +// +static LPCSTR SE_Load_Actual( LPCSTR psFileName, SE_BOOL bSpeculativeLoad ) +{ + LPCSTR psErrorMessage = NULL; + + unsigned char *psLoadedData = SE_LoadFileData( psFileName ); + if ( psLoadedData ) + { + // now parse the data... + // + char *psParsePos = (char *) psLoadedData; + + TheStringPackage.SetupNewFileParse( psFileName ); + + char sLineBuffer[16384]; // should be enough for one line of text (some of them can be BIG though) + while ( !psErrorMessage && TheStringPackage.ReadLine((LPCSTR &) psParsePos, sLineBuffer ) ) + { + if (strlen(sLineBuffer)) + { + psErrorMessage = TheStringPackage.ParseLine( sLineBuffer ); + } + } + + SE_FreeFileDataAfterLoad( psLoadedData); + + if (!psErrorMessage && !TheStringPackage.EndMarkerFoundDuringParse()) + { + psErrorMessage = va("Truncated file, failed to find \"%s\" at file end!", sSE_KEYWORD_ENDMARKER); + } + } + else + { + if ( bSpeculativeLoad ) + { + // then it's ok to not find the file, so do nothing... + } + else + { + psErrorMessage = va("Unable to load \"%s\"!", psFileName); + } + } + + return psErrorMessage; +} + +static LPCSTR SE_GetFoundFile( string &strResult ) +{ + static char sTemp[1024/*MAX_PATH*/]; + + if (!strlen(strResult.c_str())) + return NULL; + + strncpy(sTemp,strResult.c_str(),sizeof(sTemp)-1); + sTemp[sizeof(sTemp)-1]='\0'; + + char *psSemiColon = strchr(sTemp,';'); + if ( psSemiColon) + { + *psSemiColon = '\0'; + + strResult.erase(0,(psSemiColon-sTemp)+1); + } + else + { + // no semicolon found, probably last entry? (though i think even those have them on, oh well) + // + strResult.erase(); + } + +// strlwr(sTemp); // just for consistancy and set<> -> set<> erasure checking etc + + return sTemp; +} + +//////////// API entry points from rest of game.... ////////////////////////////// + +// filename is local here, eg: "strings/german/obj.str" +// +// return is either NULL for good else error message to display... +// +LPCSTR SE_Load( LPCSTR psFileName, SE_BOOL bFailIsCritical = SE_TRUE ) +{ + //////////////////////////////////////////////////// + // + // ingame here tends to pass in names without paths, but I expect them when doing a language load, so... + // + char sTemp[1000]={0}; + if (!strchr(psFileName,'/')) + { + strcpy(sTemp,sSE_STRINGS_DIR); + strcat(sTemp,"/"); + if (se_language) + { + strcat(sTemp,se_language->string); + strcat(sTemp,"/"); + } + } + strcat(sTemp,psFileName); + COM_DefaultExtension( sTemp, sizeof(sTemp), sSE_INGAME_FILE_EXTENSION); + psFileName = &sTemp[0]; + // + //////////////////////////////////////////////////// + + + LPCSTR psErrorMessage = SE_Load_Actual( psFileName, SE_FALSE ); + + // check for any corresponding / overriding .STE files and load them afterwards... + // + if ( !psErrorMessage ) + { + char sFileName[ iSE_MAX_FILENAME_LENGTH ]; + strncpy( sFileName, psFileName, sizeof(sFileName)-1 ); + sFileName[ sizeof(sFileName)-1 ] = '\0'; + char *p = strrchr( sFileName, '.' ); + if (p && strlen(p) == strlen(sSE_EXPORT_FILE_EXTENSION)) + { + strcpy( p, sSE_EXPORT_FILE_EXTENSION ); + + psErrorMessage = SE_Load_Actual( sFileName, SE_TRUE ); + } + } + + if (psErrorMessage) + { + if (bFailIsCritical) + { + // TheStringPackage.Clear(TRUE); // Will we want to do this? Any errors that arise should be fixed immediately + Com_Error( ERR_DROP, "SE_Load(): Couldn't load \"%s\"!\n\nError: \"%s\"\n", psFileName, psErrorMessage ); + } + else + { + Com_DPrintf(S_COLOR_YELLOW "SE_Load(): Couldn't load \"%s\"!\n", psFileName ); + } + } + + return psErrorMessage; +} + + +// convenience-function for the main GetString call... +// +LPCSTR SE_GetString( LPCSTR psPackageReference, LPCSTR psStringReference) +{ + char sReference[256]; // will always be enough, I've never seen one more than about 30 chars long + + sprintf(sReference,"%s_%s", psPackageReference, psStringReference); + + return SE_GetString( Q_strupr(sReference) ); +} + + +LPCSTR SE_GetString( LPCSTR psPackageAndStringReference ) +{ + int len = strlen( psPackageAndStringReference ); + char sReference[256]; // will always be enough, I've never seen one more than about 30 chars long + assert( len < sizeof(sReference) ); + + Q_strncpyz( sReference, psPackageAndStringReference, sizeof(sReference) ); + Q_strupr( sReference ); + + unsigned long refCrc = crc32( 0, (const Bytef *)sReference, len ); + char **strData = TheStringPackage.m_Strings->Find( refCrc ); + + if( !strData ) + return ""; + else + return *strData; +} + + +void SE_NewLanguage(void) +{ + TheStringPackage.Clear( SE_TRUE ); +} + + + +// these two functions aren't needed other than to make Quake-type games happy and/or stop memory managers +// complaining about leaks if they report them before the global StringEd package object calls it's own dtor. +// +// but here they are for completeness's sake I guess... +// +void SE_Init(void) +{ + Z_PushNewDeleteTag( TAG_STRINGED ); + + TheStringPackage.Clear( SE_FALSE ); + +// se_language = Cvar_Get("se_language", "english", CVAR_ARCHIVE | CVAR_NORESTART); + extern DWORD g_dwLanguage; + switch( g_dwLanguage ) + { + case XC_LANGUAGE_FRENCH: + se_language = Cvar_Get("se_language", "french", CVAR_NORESTART); + break; + case XC_LANGUAGE_GERMAN: + se_language = Cvar_Get("se_language", "german", CVAR_NORESTART); + break; + case XC_LANGUAGE_ENGLISH: + default: + se_language = Cvar_Get("se_language", "english", CVAR_NORESTART); + break; + } + + // Rather than calling SE_LoadLanguage directly, do this. Otherwise, + // se_langauge->modified doesn't get cleared, and we parse the string files + // twice. Gah. + SE_CheckForLanguageUpdates(); + + Z_PopNewDeleteTag(); +} + +// returns error message else NULL for ok. +// +// Any errors that result from this should probably be treated as game-fatal, since an asset file is fuxored. +// +LPCSTR SE_LoadLanguage( LPCSTR psLanguage ) +{ + LPCSTR psErrorMessage = NULL; + + if (psLanguage && psLanguage[0]) + { + SE_NewLanguage(); + + string strResults; + /*int iFilesFound = */SE_BuildFileList( + #ifdef _STRINGED + va("C:\\Source\\Tools\\StringEd\\test_data\\%s",sSE_STRINGS_DIR) + #else + sSE_STRINGS_DIR + #endif + , strResults + ); + + LPCSTR p; + while ( (p=SE_GetFoundFile (strResults)) != NULL && !psErrorMessage ) + { + LPCSTR psThisLang = TheStringPackage.ExtractLanguageFromPath( p ); + + if ( !stricmp( psLanguage, psThisLang ) ) + { + psErrorMessage = SE_Load( p ); + } + } + } + else + { + assert( 0 && "SE_LoadLanguage(): Bad language name!" ); + } + + return psErrorMessage; +} + + +// called in Com_Frame, so don't take up any time! (can also be called during dedicated) +// +// instead of re-loading just the files we've already loaded I'm going to load the whole language (simpler) +// +void SE_CheckForLanguageUpdates(void) +{ + if (se_language && se_language->modified) + { + LPCSTR psErrorMessage = SE_LoadLanguage( se_language->string ); + if ( psErrorMessage ) + { + Com_Error( ERR_DROP, psErrorMessage ); + } + TheStringPackage.m_Strings->Sort(); + se_language->modified = SE_FALSE; + } +} + + +///////////////////////// eof ////////////////////////// diff --git a/code/qcommon/stringed_ingame.h b/code/qcommon/stringed_ingame.h new file mode 100644 index 0000000..05d1124 --- /dev/null +++ b/code/qcommon/stringed_ingame.h @@ -0,0 +1,53 @@ +// Filename:- stringed_ingame.h +// + +#ifndef STRINGED_INGAME_H +#define STRINGED_INGAME_H + + +// alter these to suit your own game... +// +#define SE_BOOL qboolean +#define SE_TRUE qtrue +#define SE_FALSE qfalse +#define iSE_MAX_FILENAME_LENGTH MAX_QPATH +#define sSE_STRINGS_DIR "strings" + +extern cvar_t *se_language; + +// some needed text-equates, do not alter these under any circumstances !!!! (unless you're me. Which you're not) +// +#define iSE_VERSION 1 +#define sSE_KEYWORD_VERSION "VERSION" +#define sSE_KEYWORD_CONFIG "CONFIG" +#define sSE_KEYWORD_FILENOTES "FILENOTES" +#define sSE_KEYWORD_REFERENCE "REFERENCE" +#define sSE_KEYWORD_NOTES "NOTES" +#define sSE_KEYWORD_LANG "LANG_" +#define sSE_KEYWORD_ENDMARKER "ENDMARKER" +#define sSE_FILE_EXTENSION ".st" // editor-only NEVER used ingame, but I wanted all extensions together +#define sSE_EXPORT_FILE_EXTENSION ".ste" +#define sSE_INGAME_FILE_EXTENSION ".str" +#define sSE_EXPORT_SAME "#same" + + + +// available API calls... +// +typedef const char *LPCSTR; + +void SE_Init ( void ); +void SE_CheckForLanguageUpdates(void); +LPCSTR SE_LoadLanguage ( LPCSTR psLanguage ); +void SE_NewLanguage ( void ); +// +// for convenience, two ways of getting at the same data... +// +LPCSTR SE_GetString ( LPCSTR psPackageReference, LPCSTR psStringReference); +LPCSTR SE_GetString ( LPCSTR psPackageAndStringReference); + + +#endif // #ifndef STRINGED_INGAME_H + +/////////////////// eof //////////////// + diff --git a/code/qcommon/stringed_interface.cpp b/code/qcommon/stringed_interface.cpp new file mode 100644 index 0000000..079ca56 --- /dev/null +++ b/code/qcommon/stringed_interface.cpp @@ -0,0 +1,215 @@ +// Filename:- stringed_interface.cpp +// +// This file contains functions that StringEd wants to call to do things like load/save, they can be modified +// for use ingame, but must remain functionally the same... +// +// Please try and put modifications for whichever games this is used for inside #defines, so I can copy the same file +// into each project. +// + + +////////////////////////////////////////////////// +// +// stuff common to all qcommon files... +#include "../server/server.h" +#include "../game/q_shared.h" +#include "qcommon.h" +// +////////////////////////////////////////////////// + + +#pragma warning ( disable : 4511 ) // copy constructor could not be generated +#pragma warning ( disable : 4512 ) // assignment operator could not be generated +#pragma warning ( disable : 4663 ) // C++ language change: blah blah template crap blah blah +#include "stringed_interface.h" +#include "stringed_ingame.h" + +#include +using namespace std; + +#ifdef _STRINGED +#include +#include +#include "generic.h" +#endif + + +// this just gets the binary of the file into memory, so I can parse it. Called by main SGE loader +// +// returns either char * of loaded file, else NULL for failed-to-open... +// +unsigned char *SE_LoadFileData( const char *psFileName, int *piLoadedLength /* = 0 */) +{ + unsigned char *psReturn = NULL; + if ( piLoadedLength ) + { + *piLoadedLength = 0; + } + +#ifdef _STRINGED + if (psFileName[1] == ':') + { + // full-path filename... + // + FILE *fh = fopen( psFileName, "rb" ); + if (fh) + { + long lLength = filesize(fh); + + if (lLength > 0) + { + psReturn = (unsigned char *) malloc( lLength + 1); + if (psReturn) + { + int iBytesRead = fread( psReturn, 1, lLength, fh ); + if (iBytesRead != lLength) + { + // error reading file!!!... + // + free(psReturn); + psReturn = NULL; + } + else + { + psReturn[ lLength ] = '\0'; + if ( piLoadedLength ) + { + *piLoadedLength = iBytesRead; + } + } + fclose(fh); + } + } + } + } + else +#endif + { + // local filename, so prepend the base dir etc according to game and load it however (from PAK?) + // + unsigned char *pvLoadedData; + int iLen = FS_ReadFile( psFileName, (void **)&pvLoadedData ); + + if (iLen>0) + { + psReturn = pvLoadedData; + if ( piLoadedLength ) + { + *piLoadedLength = iLen; + } + } + } + + return psReturn; +} + + +// called by main SGE code after loaded data has been parsedinto internal structures... +// +void SE_FreeFileDataAfterLoad( unsigned char *psLoadedFile ) +{ +#ifdef _STRINGED + if ( psLoadedFile ) + { + free( psLoadedFile ); + } +#else + if ( psLoadedFile ) + { + FS_FreeFile( psLoadedFile ); + } +#endif +} + + + + + +#ifndef _STRINGED +// quake-style method of doing things since their file-list code doesn't have a 'recursive' flag... +// +int giFilesFound; +static void SE_R_ListFiles( const char *psExtension, const char *psDir, string &strResults ) +{ +// Com_Printf(va("Scanning Dir: %s\n",psDir)); + + char **sysFiles, **dirFiles; + int numSysFiles, i, numdirs; + + dirFiles = FS_ListFiles( psDir, "/", &numdirs); + for (i=0;i +using namespace std; + +unsigned char * SE_LoadFileData ( const char *psFileName, int *piLoadedLength = 0); +void SE_FreeFileDataAfterLoad( unsigned char *psLoadedFile ); +int SE_BuildFileList ( const char *psStartDir, string &strResults ); + +#endif // #ifndef STRINGED_INTERFACE_H + + +////////////////// eof /////////////////// + diff --git a/code/qcommon/stv_version.h b/code/qcommon/stv_version.h new file mode 100644 index 0000000..f9d9b53 --- /dev/null +++ b/code/qcommon/stv_version.h @@ -0,0 +1,13 @@ +// Current version of the single player game +#include "../win32/autoversion.h" + +#ifdef _DEBUG + #define Q3_VERSION "(debug)JA: v"VERSION_STRING_DOTTED +#elif defined FINAL_BUILD + #define Q3_VERSION "JA: v"VERSION_STRING_DOTTED +#else + #define Q3_VERSION "(internal)JA: v"VERSION_STRING_DOTTED +#endif +// end + + diff --git a/code/qcommon/tags.h b/code/qcommon/tags.h new file mode 100644 index 0000000..d630113 --- /dev/null +++ b/code/qcommon/tags.h @@ -0,0 +1,42 @@ +// Filename:- tags.h + +// do NOT include-protect this file, or add any fields or labels, because it's included within enums and tables +// +// these macro args get "TAG_" prepended on them for enum purposes, and appear as literal strings for "meminfo" command + + TAGDEF(ALL), + TAGDEF(HUNKALLOC), // mem that was formerly from the hunk AFTER the SetMark (ie discarded during vid_reset) + TAGDEF(HUNKMISCMODELS), // sub-hunk alloc to track misc models + TAGDEF(FILESYS), // general filesystem usage + TAGDEF(LISTFILES), // for "*.blah" lists + TAGDEF(AMBIENTSET), + TAGDEF(G_ALLOC), // used by G_Alloc() + TAGDEF(CLIENTS), // Memory used for client info + TAGDEF(STATIC), // special usage for 1-byte allocations from 0..9 to avoid CopyString() slowdowns during cvar value copies + TAGDEF(SMALL), // used by S_Malloc, but probably more of a hint now. Will be dumped later + TAGDEF(MODEL_MD3), // specific model types' disk images + TAGDEF(MODEL_GLM), // " + TAGDEF(MODEL_GLA), // " + TAGDEF(ICARUS), // Memory used internally by the Icarus scripting system + TAGDEF(IMAGE_T), // an image_t struct (no longer on the hunk because of cached texture stuff) + TAGDEF(TEMP_WORKSPACE), // anything like file loading or image workspace that's only temporary + TAGDEF(SND_RAWDATA), // raw sound data, either MP3 or WAV + TAGDEF(GHOUL2), // Ghoul2 stuff + TAGDEF(BSP), // guess. + TAGDEF(GP2), // generic parser 2 + TAGDEF(ANIMATION_CFG), // may as well keep this seperate / readable + TAGDEF(SAVEGAME), // used for allocating chunks during savegame file read +// TAGDEF(INFLATE), // Temp memory used by zlib32 +// TAGDEF(DEFLATE), // Temp memory used by zlib32 + TAGDEF(POINTCACHE), // weather effects + TAGDEF(NEWDEL), + TAGDEF(UI_ALLOC), + TAGDEF(LIPSYNC), + TAGDEF(FILELIST), + TAGDEF(BINK), + TAGDEF(STRINGED), + + TAGDEF(COUNT) + +//////////////// eof ////////////// + diff --git a/code/qcommon/timing.h b/code/qcommon/timing.h new file mode 100644 index 0000000..6f57d9a --- /dev/null +++ b/code/qcommon/timing.h @@ -0,0 +1,62 @@ + +class timing_c +{ +private: + __int64 start; + __int64 end; + + int reset; +public: + timing_c(void) + { + } + void Start() + { + const __int64 *s = &start; + + __asm + { + push eax + push ebx + push edx + + rdtsc + mov ebx, s + mov [ebx], eax + mov [ebx + 4], edx + + pop edx + pop ebx + pop eax + } + } + int End() + { + const __int64 *e = &end; + __int64 time; + + __asm + { + push eax + push ebx + push edx + + rdtsc + mov ebx, e + mov [ebx], eax + mov [ebx + 4], edx + + pop edx + pop ebx + pop eax + } + time = end - start; + if (time < 0) + { + time = 0; + } + return((int)time); + } +}; + +// end \ No newline at end of file diff --git a/code/qcommon/tri_coll_test.cpp b/code/qcommon/tri_coll_test.cpp new file mode 100644 index 0000000..afe0b7f --- /dev/null +++ b/code/qcommon/tri_coll_test.cpp @@ -0,0 +1,506 @@ +/* Triangle/triangle intersection test routine, + * by Tomas Moller, 1997. + * See article "A Fast Triangle-Triangle Intersection Test", + * Journal of Graphics Tools, 2(2), 1997 + * + * int tri_tri_intersect(float V0[3],float V1[3],float V2[3], + * float U0[3],float U1[3],float U2[3]) + * + * parameters: vertices of triangle 1: V0,V1,V2 + * vertices of triangle 2: U0,U1,U2 + * result : returns 1 if the triangles intersect, otherwise 0 + * + */ + +// leave this at the top for PCH reasons... +#include "common_headers.h" + + + + +#include +#include "../game/q_shared.h" +#include "../game/g_local.h" + +/* if USE_EPSILON_TEST is true then we do a check: + if |dv|b) \ + { \ + float c; \ + c=a; \ + a=b; \ + b=c; \ + } + +#define ISECT(VV0,VV1,VV2,D0,D1,D2,isect0,isect1) \ + isect0=VV0+(VV1-VV0)*D0/(D0-D1); \ + isect1=VV0+(VV2-VV0)*D0/(D0-D2); + + +#define COMPUTE_INTERVALS(VV0,VV1,VV2,D0,D1,D2,D0D1,D0D2,isect0,isect1) \ + if(D0D1>0.0f) \ + { \ + /* here we know that D0D2<=0.0 */ \ + /* that is D0, D1 are on the same side, D2 on the other or on the plane */ \ + ISECT(VV2,VV0,VV1,D2,D0,D1,isect0,isect1); \ + } \ + else if(D0D2>0.0f) \ + { \ + /* here we know that d0d1<=0.0 */ \ + ISECT(VV1,VV0,VV2,D1,D0,D2,isect0,isect1); \ + } \ + else if(D1*D2>0.0f || D0!=0.0f) \ + { \ + /* here we know that d0d1<=0.0 or that D0!=0.0 */ \ + ISECT(VV0,VV1,VV2,D0,D1,D2,isect0,isect1); \ + } \ + else if(D1!=0.0f) \ + { \ + ISECT(VV1,VV0,VV2,D1,D0,D2,isect0,isect1); \ + } \ + else if(D2!=0.0f) \ + { \ + ISECT(VV2,VV0,VV1,D2,D0,D1,isect0,isect1); \ + } \ + else \ + { \ + /* triangles are coplanar */ \ + return coplanar_tri_tri(N1,V0,V1,V2,U0,U1,U2); \ + } + + + +/* this edge to edge test is based on Franlin Antonio's gem: + "Faster Line Segment Intersection", in Graphics Gems III, + pp. 199-202 */ +#define EDGE_EDGE_TEST(V0,U0,U1) \ + Bx=U0[i0]-U1[i0]; \ + By=U0[i1]-U1[i1]; \ + Cx=V0[i0]-U0[i0]; \ + Cy=V0[i1]-U0[i1]; \ + f=Ay*Bx-Ax*By; \ + d=By*Cx-Bx*Cy; \ + if((f>0 && d>=0 && d<=f) || (f<0 && d<=0 && d>=f)) \ + { \ + e=Ax*Cy-Ay*Cx; \ + if(f>0) \ + { \ + if(e>=0 && e<=f) return 1; \ + } \ + else \ + { \ + if(e<=0 && e>=f) return 1; \ + } \ + } + +#define EDGE_AGAINST_TRI_EDGES(V0,V1,U0,U1,U2) \ +{ \ + float Ax,Ay,Bx,By,Cx,Cy,e,d,f; \ + Ax=V1[i0]-V0[i0]; \ + Ay=V1[i1]-V0[i1]; \ + /* test edge U0,U1 against V0,V1 */ \ + EDGE_EDGE_TEST(V0,U0,U1); \ + /* test edge U1,U2 against V0,V1 */ \ + EDGE_EDGE_TEST(V0,U1,U2); \ + /* test edge U2,U1 against V0,V1 */ \ + EDGE_EDGE_TEST(V0,U2,U0); \ +} + +#define POINT_IN_TRI(V0,U0,U1,U2) \ +{ \ + float a,b,c,d0,d1,d2; \ + /* is T1 completly inside T2? */ \ + /* check if V0 is inside tri(U0,U1,U2) */ \ + a=U1[i1]-U0[i1]; \ + b=-(U1[i0]-U0[i0]); \ + c=-a*U0[i0]-b*U0[i1]; \ + d0=a*V0[i0]+b*V0[i1]+c; \ + \ + a=U2[i1]-U1[i1]; \ + b=-(U2[i0]-U1[i0]); \ + c=-a*U1[i0]-b*U1[i1]; \ + d1=a*V0[i0]+b*V0[i1]+c; \ + \ + a=U0[i1]-U2[i1]; \ + b=-(U0[i0]-U2[i0]); \ + c=-a*U2[i0]-b*U2[i1]; \ + d2=a*V0[i0]+b*V0[i1]+c; \ + if(d0*d1>0.0) \ + { \ + if(d0*d2>0.0) return 1; \ + } \ +} + +qboolean coplanar_tri_tri(vec3_t N,vec3_t V0,vec3_t V1,vec3_t V2, + vec3_t U0,vec3_t U1,vec3_t U2) +{ + vec3_t A; + short i0,i1; + /* first project onto an axis-aligned plane, that maximizes the area */ + /* of the triangles, compute indices: i0,i1. */ + A[0]=fabs(N[0]); + A[1]=fabs(N[1]); + A[2]=fabs(N[2]); + if(A[0]>A[1]) + { + if(A[0]>A[2]) + { + i0=1; /* A[0] is greatest */ + i1=2; + } + else + { + i0=0; /* A[2] is greatest */ + i1=1; + } + } + else /* A[0]<=A[1] */ + { + if(A[2]>A[1]) + { + i0=0; /* A[2] is greatest */ + i1=1; + } + else + { + i0=0; /* A[1] is greatest */ + i1=2; + } + } + + /* test all edges of triangle 1 against the edges of triangle 2 */ + EDGE_AGAINST_TRI_EDGES(V0,V1,U0,U1,U2); + EDGE_AGAINST_TRI_EDGES(V1,V2,U0,U1,U2); + EDGE_AGAINST_TRI_EDGES(V2,V0,U0,U1,U2); + + /* finally, test if tri1 is totally contained in tri2 or vice versa */ + POINT_IN_TRI(V0,U0,U1,U2); + POINT_IN_TRI(U0,V0,V1,V2); + + return qfalse; +} + +qboolean tri_tri_intersect(vec3_t V0,vec3_t V1,vec3_t V2, + vec3_t U0,vec3_t U1,vec3_t U2) +{ + vec3_t E1,E2; + vec3_t N1,N2; + float d1,d2; + float du0,du1,du2,dv0,dv1,dv2; + vec3_t D; + float isect1[2], isect2[2]; + float du0du1,du0du2,dv0dv1,dv0dv2; + short index; + float vp0,vp1,vp2; + float up0,up1,up2; + float b,c,max; + + /* compute plane equation of triangle(V0,V1,V2) */ + SUB(E1,V1,V0); + SUB(E2,V2,V0); + CROSS(N1,E1,E2); + d1=-DOT(N1,V0); + /* plane equation 1: N1.X+d1=0 */ + + /* put U0,U1,U2 into plane equation 1 to compute signed distances to the plane*/ + du0=DOT(N1,U0)+d1; + du1=DOT(N1,U1)+d1; + du2=DOT(N1,U2)+d1; + + /* coplanarity robustness check */ +#if USE_EPSILON_TEST + if(fabs(du0)0.0f && du0du2>0.0f) /* same sign on all of them + not equal 0 ? */ + return 0; /* no intersection occurs */ + + /* compute plane of triangle (U0,U1,U2) */ + SUB(E1,U1,U0); + SUB(E2,U2,U0); + CROSS(N2,E1,E2); + d2=-DOT(N2,U0); + /* plane equation 2: N2.X+d2=0 */ + + /* put V0,V1,V2 into plane equation 2 */ + dv0=DOT(N2,V0)+d2; + dv1=DOT(N2,V1)+d2; + dv2=DOT(N2,V2)+d2; + +#if USE_EPSILON_TEST + if(fabs(dv0)0.0f && dv0dv2>0.0f) /* same sign on all of them + not equal 0 ? */ + return 0; /* no intersection occurs */ + + /* compute direction of intersection line */ + CROSS(D,N1,N2); + + /* compute and index to the largest component of D */ + max=fabs(D[0]); + index=0; + b=fabs(D[1]); + c=fabs(D[2]); + if(b>max) max=b,index=1; + if(c>max) max=c,index=2; + + /* this is the simplified projection onto L*/ + vp0=V0[index]; + vp1=V1[index]; + vp2=V2[index]; + + up0=U0[index]; + up1=U1[index]; + up2=U2[index]; + + /* compute interval for triangle 1 */ + COMPUTE_INTERVALS(vp0,vp1,vp2,dv0,dv1,dv2,dv0dv1,dv0dv2,isect1[0],isect1[1]); + + /* compute interval for triangle 2 */ + COMPUTE_INTERVALS(up0,up1,up2,du0,du1,du2,du0du1,du0du2,isect2[0],isect2[1]); + + SORT(isect1[0],isect1[1]); + SORT(isect2[0],isect2[1]); + + if(isect1[1] 0.001f ) + { + float s = -( (v2v2*DotProduct( v1, start_dif )) - (v1v2*DotProduct( v2, start_dif )) ) / denom; + float t = ( (v1v1*DotProduct( v2, start_dif )) - (v1v2*DotProduct( v1, start_dif )) ) / denom; + qboolean done = qtrue; + + if ( s < 0 ) + { + done = qfalse; + s = 0;// and see note below + } + + if ( s > 1 ) + { + done = qfalse; + s = 1;// and see note below + } + + if ( t < 0 ) + { + done = qfalse; + t = 0;// and see note below + } + + if ( t > 1 ) + { + done = qfalse; + t = 1;// and see note below + } + + //vec close_pnt1 = start1 + s * v1 + VectorMA( start1, s, v1, close_pnt1 ); + //vec close_pnt2 = start2 + t * v2 + VectorMA( start2, t, v2, close_pnt2 ); + + current_dist = Distance( close_pnt1, close_pnt2 ); + //now, if none of those if's fired, you are done. + if ( done ) + { + return current_dist; + } + //If they did fire, then we need to do some additional tests. + + //What we are gonna do is see if we can find a shorter distance than the above + //involving the endpoints. + } + else + { + //******start here for paralell lines with current_dist = infinity**** + current_dist = Q3_INFINITE; + } + + //test 2 close_pnts first + /* + G_FindClosestPointOnLineSegment( start1, end1, close_pnt2, new_pnt ); + new_dist = Distance( close_pnt2, new_pnt ); + if ( new_dist < current_dist ) + {//then update close_pnt1 close_pnt2 and current_dist + VectorCopy( new_pnt, close_pnt1 ); + VectorCopy( close_pnt2, close_pnt2 ); + current_dist = new_dist; + } + + G_FindClosestPointOnLineSegment( start2, end2, close_pnt1, new_pnt ); + new_dist = Distance( close_pnt1, new_pnt ); + if ( new_dist < current_dist ) + {//then update close_pnt1 close_pnt2 and current_dist + VectorCopy( close_pnt1, close_pnt1 ); + VectorCopy( new_pnt, close_pnt2 ); + current_dist = new_dist; + } + */ + //test all the endpoints + new_dist = Distance( start1, start2 ); + if ( new_dist < current_dist ) + {//then update close_pnt1 close_pnt2 and current_dist + VectorCopy( start1, close_pnt1 ); + VectorCopy( start2, close_pnt2 ); + current_dist = new_dist; + } + + new_dist = Distance( start1, end2 ); + if ( new_dist < current_dist ) + {//then update close_pnt1 close_pnt2 and current_dist + VectorCopy( start1, close_pnt1 ); + VectorCopy( end2, close_pnt2 ); + current_dist = new_dist; + } + + new_dist = Distance( end1, start2 ); + if ( new_dist < current_dist ) + {//then update close_pnt1 close_pnt2 and current_dist + VectorCopy( end1, close_pnt1 ); + VectorCopy( start2, close_pnt2 ); + current_dist = new_dist; + } + + new_dist = Distance( end1, end2 ); + if ( new_dist < current_dist ) + {//then update close_pnt1 close_pnt2 and current_dist + VectorCopy( end1, close_pnt1 ); + VectorCopy( end2, close_pnt2 ); + current_dist = new_dist; + } + + //Then we have 4 more point / segment tests + + G_FindClosestPointOnLineSegment( start2, end2, start1, new_pnt ); + new_dist = Distance( start1, new_pnt ); + if ( new_dist < current_dist ) + {//then update close_pnt1 close_pnt2 and current_dist + VectorCopy( start1, close_pnt1 ); + VectorCopy( new_pnt, close_pnt2 ); + current_dist = new_dist; + } + + G_FindClosestPointOnLineSegment( start2, end2, end1, new_pnt ); + new_dist = Distance( end1, new_pnt ); + if ( new_dist < current_dist ) + {//then update close_pnt1 close_pnt2 and current_dist + VectorCopy( end1, close_pnt1 ); + VectorCopy( new_pnt, close_pnt2 ); + current_dist = new_dist; + } + + G_FindClosestPointOnLineSegment( start1, end1, start2, new_pnt ); + new_dist = Distance( start2, new_pnt ); + if ( new_dist < current_dist ) + {//then update close_pnt1 close_pnt2 and current_dist + VectorCopy( new_pnt, close_pnt1 ); + VectorCopy( start2, close_pnt2 ); + current_dist = new_dist; + } + + G_FindClosestPointOnLineSegment( start1, end1, end2, new_pnt ); + new_dist = Distance( end2, new_pnt ); + if ( new_dist < current_dist ) + {//then update close_pnt1 close_pnt2 and current_dist + VectorCopy( new_pnt, close_pnt1 ); + VectorCopy( end2, close_pnt2 ); + current_dist = new_dist; + } + + return current_dist; +} \ No newline at end of file diff --git a/code/qcommon/unzip.cpp b/code/qcommon/unzip.cpp new file mode 100644 index 0000000..397b699 --- /dev/null +++ b/code/qcommon/unzip.cpp @@ -0,0 +1,1348 @@ +/***************************************************************************** + * name: unzip.c + * + * desc: IO on .zip files using portions of zlib + * + * $Archive: /StarTrek/Code-Single/qcommon/unzip.cpp $ + * $Author: osman $ + * $Revision: 1.19 $ + * $Modtime: 4/10/01 6:47p $ + * $Date: 2003/07/27 20:28:47 $ + * + *****************************************************************************/ + +#include "../client/client.h" + +#define ZIP_fopen fopen +#define ZIP_fclose fclose +#define ZIP_fseek fseek +#define ZIP_fread fread +#define ZIP_ftell ftell + +#include "../zlib32/zip.h" +#include "unzip.h" + +/* unzip.h -- IO for uncompress .zip files using zlib + Version 0.15 beta, Mar 19th, 1998, + + Copyright (C) 1998 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + Encryption and multi volume ZipFile (span) are not supported. + Old compressions used by old PKZip 1.x are not supported + + THIS IS AN ALPHA VERSION. AT THIS STAGE OF DEVELOPPEMENT, SOMES API OR STRUCTURE + CAN CHANGE IN FUTURE VERSION !! + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +#define OF(args) args +#endif + +typedef unsigned char Byte; /* 8 bits */ +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ +typedef Byte *voidp; + +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +typedef voidp gzFile; + +gzFile gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h". (See the description + of deflateInit2 for more information about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +gzFile gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +int gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +int gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +int gzwrite OF((gzFile file, + const voidp buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +int QDECL gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). +*/ + +int gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +char * gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +int gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +int gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +int gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +long gzseek OF((gzFile file, + long offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +int gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +long gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +int gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +int gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +const char * gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + +#if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) && \ + !defined(CASESENSITIVITYDEFAULT_NO) +#define CASESENSITIVITYDEFAULT_NO +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (65536) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (UNZ_Malloc (size)) //Quake hookups +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) Z_Free(p);}//Quake hookups +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + +static void *UNZ_Malloc( int size ) { + void *buf; + + buf = Z_Malloc( size, TAG_FILESYS, qfalse); + return buf; +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +static int unzlocal_getShort (ZIP_FILE* fin, uLong *pX) +{ + short v; + + ZIP_fread( &v, sizeof(v), 1, fin ); + + *pX = LittleShort( v); + return UNZ_OK; +} + +static int unzlocal_getLong (ZIP_FILE *fin, uLong *pX) +{ + int v; + + ZIP_fread( &v, sizeof(v), 1, fin ); + + *pX = LittleLong( v); + return UNZ_OK; +} + + +/* My own strcmpi / strcasecmp */ +static int strcmpcasenosensitive_internal (const char* fileName1,const char* fileName2) +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int unzStringFileNameCompare (const char* fileName1,const char* fileName2,int iCaseSensitivity) +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#define BUFREADCOMMENT (0x400) + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +static uLong unzlocal_SearchCentralDir(ZIP_FILE *fin) +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (ZIP_fseek(fin,0,SEEK_END) != 0) + return 0; + + + uSizeFile = ZIP_ftell( fin ); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (ZIP_fseek(fin,uReadPos,SEEK_SET)!=0) + break; + + if (ZIP_fread(buf,(uInt)uReadSize,1,fin)!=1) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +extern unzFile unzReOpen (const char* path, unzFile file) +{ + unz_s *s; + ZIP_FILE * fin; + + fin=ZIP_fopen(path,"rb"); + if (fin==NULL) + return NULL; + + s=(unz_s*)ALLOC(sizeof(unz_s)); + memcpy(s, (unz_s*)file, sizeof(unz_s)); + + s->file = fin; + return (unzFile)s; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib109.zip" or on an Unix computer + "zlib/zlib109.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +extern unzFile unzOpen (const char* path) +{ + unz_s us; + unz_s *s; + uLong central_pos,uL; + ZIP_FILE * fin ; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + fin=ZIP_fopen(path,"rb"); + if (fin==NULL) + return NULL; + + central_pos = unzlocal_SearchCentralDir(fin); + if (central_pos==0) + err=UNZ_ERRNO; + + if (ZIP_fseek(fin,central_pos,SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unzlocal_getLong(fin,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unzlocal_getShort(fin,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unzlocal_getShort(fin,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unzlocal_getShort(fin,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir */ + if (unzlocal_getShort(fin,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unzlocal_getLong(fin,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unzlocal_getLong(fin,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* zipfile comment length */ + if (unzlocal_getShort(fin,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + ZIP_fclose(s->file); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int unzGetGlobalInfo (unzFile file,unz_global_info *pglobal_info) +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + + +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +static void unzlocal_DosDateToTmuDate (uLong ulDosDate, tm_unz* ptm) +{ + uLong uDate; + uDate = (uLong)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +static int unzlocal_GetCurrentFileInfoInternal (unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize) +{ + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (ZIP_fseek(s->file,s->pos_in_central_dir+s->byte_before_the_zipfile,SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + + if (unzlocal_getShort(s->file,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unzlocal_getLong(s->file,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (ZIP_fread(szFileName,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + + if ((err==UNZ_OK) && (extraField!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_extrafile,lSeek,SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if (ZIP_fread(extraField,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek += file_info.size_file_extra - uSizeRead; + } + else + lSeek+=file_info.size_file_extra; + + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentfile,lSeek,SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if (ZIP_fread(szComment,(uInt)uSizeRead,1,s->file)!=1) + err=UNZ_ERRNO; + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int unzGetCurrentFileInfo ( unzFile file, unz_file_info *pfile_info, + char *szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char *szComment, uLong commentBufferSize) +{ + return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int unzGoToFirstFile (unzFile file) +{ + int err=UNZ_OK; + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int unzGoToNextFile (unzFile file) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Get the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ) +{ + unz_s* s; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + *pos = s->pos_in_central_dir; + return UNZ_OK; +} + +/* + Set the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ) +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + s->pos_in_central_dir = pos; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return UNZ_OK; +} + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) +{ + unz_s* s; + int err; + + + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + unzGetCurrentFileInfo(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + return err; +} + + +/* + Read the static header of the current zipfile + Check the coherency of the static header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in static header + (filename and size of extra field data) +*/ +static int unzlocal_CheckCurrentFileCoherencyHeader (unz_s* s, uInt* piSizeVar, + uLong *poffset_local_extrafield, + uInt *psize_local_extrafield) +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (ZIP_fseek(s->file,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) + if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unzlocal_getShort(s->file,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(s->file,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=ZF_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(s->file,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + + if (unzlocal_getShort(s->file,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unzlocal_getShort(s->file,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int unzOpenCurrentFile (unzFile file) +{ + int err=UNZ_OK; + int Store; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; /* offset of the static extra field */ + uInt size_local_extrafield; /* size of the static extra field */ + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, + &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) ALLOC(sizeof(file_in_zip_read_info_s)); + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + + pfile_in_zip_read_info->stream_initialised=0; + + if ((s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=ZF_DEFLATED)) + err=UNZ_BADZIPFILE; + Store = s->cur_file_info.compression_method==0; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->compression_method = + s->cur_file_info.compression_method; + pfile_in_zip_read_info->file=s->file; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if (!Store) + { + err=inflateInit(&pfile_in_zip_read_info->stream, Z_SYNC_FLUSH, 1); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=1; + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + + s->pfile_in_zip_read = pfile_in_zip_read_info; + return UNZ_OK; +} + + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int unzReadCurrentFile (unzFile file, void *buf, unsigned len) +{ + int err=UNZ_OK; + uInt iRead = 0; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->read_buffer == NULL)) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Byte*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if (len>pfile_in_zip_read_info->rest_read_uncompressed) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (s->cur_file_info.compressed_size == pfile_in_zip_read_info->rest_read_compressed) + if (ZIP_fseek(pfile_in_zip_read_info->file, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile,SEEK_SET)!=0) + return UNZ_ERRNO; + if (ZIP_fread(pfile_in_zip_read_info->read_buffer,uReadThis,1, + pfile_in_zip_read_info->file)!=1) + return UNZ_ERRNO; + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Byte*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if (pfile_in_zip_read_info->compression_method==0) + { + uInt uDoCopy,i ; + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else + { + uLong uTotalOutBefore,uTotalOutAfter; + const Byte *bufBefore; + uLong uOutThis; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream); + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern long unztell (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (long)pfile_in_zip_read_info->stream.total_out; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int unzeof (unzFile file) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the static-header version of the extra field (sometimes, there is + more info in the static-header version than in the central-header) + + if buf==NULL, it return the size of the static extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int unzGetLocalExtrafield (unzFile file,void *buf,unsigned len) +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (ZIP_fseek(pfile_in_zip_read_info->file, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield,SEEK_SET)!=0) + return UNZ_ERRNO; + + if (ZIP_fread(buf,(uInt)size_to_read,1,pfile_in_zip_read_info->file)!=1) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int unzCloseCurrentFile (unzFile file) +{ + int err=UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd(&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int unzGetGlobalComment (unzFile file, char *szComment, uLong uSizeBuf) +{ + unz_s* s; + uLong uReadThis ; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (ZIP_fseek(s->file,s->central_pos+22,SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (ZIP_fread(szComment,(uInt)uReadThis,1,s->file)!=1) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +// end diff --git a/code/qcommon/unzip.h b/code/qcommon/unzip.h new file mode 100644 index 0000000..3fe3bae --- /dev/null +++ b/code/qcommon/unzip.h @@ -0,0 +1,286 @@ + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef void* unzFile; +#endif + +#define ZIP_FILE FILE + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + unsigned int tm_sec; /* seconds after the minute - [0,59] */ + unsigned int tm_min; /* minutes after the hour - [0,59] */ + unsigned int tm_hour; /* hours since midnight - [0,23] */ + unsigned int tm_mday; /* day of the month - [1,31] */ + unsigned int tm_mon; /* months since January - [0,11] */ + unsigned int tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + unsigned long number_entry; /* total number of entries in the central dir on this disk */ + unsigned long size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + unsigned long version; /* version made by 2 unsigned chars */ + unsigned long version_needed; /* version needed to extract 2 unsigned chars */ + unsigned long flag; /* general purpose bit flag 2 unsigned chars */ + unsigned long compression_method; /* compression method 2 unsigned chars */ + unsigned long dosDate; /* last mod file date in Dos fmt 4 unsigned chars */ + unsigned long crc; /* crc-32 4 unsigned chars */ + unsigned long compressed_size; /* compressed size 4 unsigned chars */ + unsigned long uncompressed_size; /* uncompressed size 4 unsigned chars */ + unsigned long size_filename; /* filename length 2 unsigned chars */ + unsigned long size_file_extra; /* extra field length 2 unsigned chars */ + unsigned long size_file_comment; /* file comment length 2 unsigned chars */ + + unsigned long disk_num_start; /* disk number start 2 unsigned chars */ + unsigned long internal_fa; /* internal file attributes 2 unsigned chars */ + unsigned long external_fa; /* external file attributes 4 unsigned chars */ + + tm_unz tmu_date; +} unz_file_info; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ + unsigned long offset_curfile;/* relative offset of static header 4 unsigned chars */ +} unz_file_info_internal; + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + + unsigned long pos_in_zipfile; /* position in unsigned char on the zipfile, for fseek*/ + unsigned long stream_initialised; /* flag set if stream structure is initialised*/ + + unsigned long offset_local_extrafield;/* offset of the static extra field */ + unsigned int size_local_extrafield;/* size of the static extra field */ + unsigned long pos_local_extrafield; /* position in the static extra field in read*/ + + unsigned long crc32; /* crc32 of all data uncompressed */ + unsigned long crc32_wait; /* crc32 we must obtain after decompress all */ + unsigned long rest_read_compressed; /* number of unsigned char to be decompressed */ + unsigned long rest_read_uncompressed;/*number of unsigned char to be obtained after decomp*/ + ZIP_FILE *file; /* io structore of the zipfile */ + unsigned long compression_method; /* compression method (0==store) */ + unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ + ZIP_FILE* file; /* io structore of the zipfile */ + unz_global_info gi; /* public global information */ + unsigned long byte_before_the_zipfile;/* unsigned char before the zipfile, (>0 for sfx)*/ + unsigned long num_file; /* number of the current file in the zipfile*/ + unsigned long pos_in_central_dir; /* pos of the current file in the central dir*/ + unsigned long current_file_ok; /* flag about the usability of the current file*/ + unsigned long central_pos; /* position of the beginning of the central dir*/ + + unsigned long size_central_dir; /* size of the central directory */ + unsigned long offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info cur_file_info; /* public info about the current file in zip*/ + unz_file_info_internal cur_file_info_internal; /* private info about it*/ + file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ +} unz_s; + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_DATA_ERROR) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +#define UNZ_CASESENSITIVE 1 +#define UNZ_NOTCASESENSITIVE 2 +#define UNZ_OSDEFAULTCASE 0 + +extern int unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity); + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + +extern unzFile unzOpen (const char *path); +extern unzFile unzReOpen (const char* path, unzFile file); + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\zlib\\zlib111.zip" or on an Unix computer + "zlib/zlib111.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern int unzClose (unzFile file); + +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int unzGetGlobalInfo (unzFile file, unz_global_info *pglobal_info); + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int unzGetGlobalComment (unzFile file, char *szComment, unsigned long uSizeBuf); + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of unsigned char copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int unzGoToFirstFile (unzFile file); + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int unzGoToNextFile (unzFile file); + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int unzGetCurrentFileInfoPosition (unzFile file, unsigned long *pos ); + +/* + Get the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ + +extern int unzSetCurrentFileInfoPosition (unzFile file, unsigned long pos ); + +/* + Set the position of the info of the current file in the zip. + return UNZ_OK if there is no problem +*/ + +extern int unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity); + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +extern int unzGetCurrentFileInfo (unzFile file, unz_file_info *pfile_info, char *szFileName, unsigned long fileNameBufferSize, void *extraField, unsigned long extraFieldBufferSize, char *szComment, unsigned long commentBufferSize); + +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int unzOpenCurrentFile (unzFile file); + +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int unzCloseCurrentFile (unzFile file); + +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + + +extern int unzReadCurrentFile (unzFile file, void* buf, unsigned len); + +/* + Read unsigned chars from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of unsigned char copied if somes unsigned chars are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern long unztell(unzFile file); + +/* + Give the current position in uncompressed data +*/ + +extern int unzeof (unzFile file); + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int unzGetLocalExtrafield (unzFile file, void* buf, unsigned len); + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of unsigned chars copied in buf, or (if <0) + the error code +*/ diff --git a/code/qcommon/xb_settings.cpp b/code/qcommon/xb_settings.cpp new file mode 100644 index 0000000..aef24f6 --- /dev/null +++ b/code/qcommon/xb_settings.cpp @@ -0,0 +1,382 @@ + +#include "xb_settings.h" +#include +#include "../game/q_shared.h" +#include "qcommon.h" + +#define SETTINGS_VERSION 0x00082877 +#define SETTINGS_DIRNAME "Settings" +#define SETTINGS_FILENAME "settings.dat" +#define SETTINGS_IMAGE "saveimage.xbx" +#define SETTINGS_IMAGE_SRC "d:\\base\\media\\settings.xbx" + +// The one copy of Settings: +XBSettings Settings; +const DWORD settingsSize = sizeof(Settings); +const DWORD sigSize = sizeof(XCALCSIG_SIGNATURE); + +// This isn't user data, don't put it in XBSettings! +enum XBSettingsStatus +{ + SETTINGS_OK, // Everything is ok + SETTINGS_MISSING, // File is not on disk + SETTINGS_CORRUPT, // File on disk is corrupt + SETTINGS_FAILED, // General error +}; +XBSettingsStatus SettingsStatus; + +bool settingsDisabled = false; + +const char *buttonConfigStrings[3] = { + "weaponsbias", + "forcebias", + "southpaw", +}; + +const char *triggerConfigStrings[2] = { + "default", + "southpaw", +}; + +XBSettings::XBSettings( void ) +{ + version = SETTINGS_VERSION; + + // Defaults: + invertAim[0] = invertAim[1] = false; + + thumbstickMode[0] = thumbstickMode[1] = 0; + buttonMode[0] = buttonMode[1] = 0; + triggerMode[0] = triggerMode[1] = 0; + + rumble[0] = rumble[1] = 1; + autolevel[0] = autolevel[0] = 0; + autoswitch[0] = autoswitch[1] = 1; + sensitivityX[0] = sensitivityX[1] = 2.0f; + sensitivityY[0] = sensitivityY[1] = 2.0f; + + hotswapSP[0] = hotswapSP[1] = hotswapSP[2] = -1; + hotswapMP[0] = hotswapMP[1] = -1; + hotswapMP[2] = hotswapMP[3] = -1; + + effectsVolume = 1.0f; + musicVolume = 0.25f; + voiceVolume = 1.0f; + brightness = 6.0f; + + subtitles = 0; + +#ifdef XBOX_DEMO + // Demo has no foreign audio, so we turn subtitles on if Dash language is FR/GE + DWORD dwLang = XGetLanguage(); + if( dwLang == XC_LANGUAGE_FRENCH || dwLang == XC_LANGUAGE_GERMAN ) + subtitles = 1; +#endif + + voiceMode = 2; + voiceMask = 0; + appearOffline = 0; + +#ifdef XBOX_DEMO + Disable(); // Ensure that we never try to load/save settings in the demo +#endif +} + +// Write the current stored settings to the HD: +bool XBSettings::Save( void ) +{ + // Do nothing if user chose "Continue Without Saving" + if( settingsDisabled ) + return true; + + char settingsPath[128]; + char *pathEnd; + DWORD dwWritten; + + // Build the settings directory: + unsigned short wideName[128]; + mbstowcs( wideName, SETTINGS_DIRNAME, sizeof(wideName) ); + + // Open/create the settings directory: + if (XCreateSaveGame( "U:\\", wideName, OPEN_ALWAYS, 0, settingsPath, sizeof(settingsPath) ) != ERROR_SUCCESS ) + { + SettingsStatus = SETTINGS_FAILED; + return false; + } + + // Build path to settings file: + pathEnd = settingsPath + strlen( settingsPath ); + strcpy( pathEnd, SETTINGS_FILENAME ); + + // Open/create the settings file: + HANDLE hFile = CreateFile( settingsPath, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + if( hFile == INVALID_HANDLE_VALUE ) + { + SettingsStatus = SETTINGS_FAILED; + return false; + } + + // Write the data: + if( !WriteFile( hFile, this, settingsSize, &dwWritten, NULL ) || (dwWritten != settingsSize) ) + { + SettingsStatus = SETTINGS_FAILED; + CloseHandle( hFile ); + return false; + } + + // Sign the data: + XCALCSIG_SIGNATURE xsig; + if( !Sign( &xsig ) ) + { + SettingsStatus = SETTINGS_FAILED; + CloseHandle( hFile ); + return false; + } + + // Write signature: + if( !WriteFile( hFile, &xsig, sigSize, &dwWritten, NULL ) || (dwWritten != sigSize) ) + { + SettingsStatus = SETTINGS_FAILED; + CloseHandle( hFile ); + return false; + } + + // Truncate and close file: + SetEndOfFile( hFile ); + CloseHandle( hFile ); + + // Copy the save image over: + strcpy( pathEnd, SETTINGS_IMAGE ); + CopyFile( SETTINGS_IMAGE_SRC, settingsPath, FALSE ); + + return true; +} + +// Read saved settings from the HD: +bool XBSettings::Load( void ) +{ + // Do nothing if user chose "Continue Without Saving" + if( settingsDisabled ) + return true; + + char settingsPath[128]; + char *pathEnd; + DWORD dwRead; + + // Build the settings directory: + unsigned short wideName[128]; + mbstowcs( wideName, SETTINGS_DIRNAME, sizeof(wideName) ); + + // Open the settings directory: + if( XCreateSaveGame( "U:\\", wideName, OPEN_EXISTING, 0, settingsPath, sizeof(settingsPath) ) != ERROR_SUCCESS ) + { + SettingsStatus = SETTINGS_MISSING; + return false; + } + + // Build path to settings file: + pathEnd = settingsPath + strlen( settingsPath ); + strcpy( pathEnd, SETTINGS_FILENAME ); + + HANDLE hFile = CreateFile( settingsPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); + if( hFile == INVALID_HANDLE_VALUE ) + { + SettingsStatus = SETTINGS_CORRUPT; + return false; + } + + // Verify file size: + if( GetFileSize( hFile, NULL ) != (settingsSize + sigSize) ) + { + SettingsStatus = SETTINGS_CORRUPT; + CloseHandle( hFile ); + return false; + } + + // Temp struct to read data into: + XBSettings temp; + if( !ReadFile( hFile, &temp, settingsSize, &dwRead, NULL ) || (dwRead != settingsSize) ) + { + SettingsStatus = SETTINGS_CORRUPT; + CloseHandle( hFile ); + return false; + } + + // Calculate signature over the read-in data: + XCALCSIG_SIGNATURE xsig; + if( !temp.Sign( &xsig ) ) + { + SettingsStatus = SETTINGS_CORRUPT; + CloseHandle( hFile ); + return false; + } + + // Read in stored signature: + XCALCSIG_SIGNATURE storedSig; + if( !ReadFile( hFile, &storedSig, sigSize, &dwRead, NULL ) || (dwRead != sigSize) ) + { + SettingsStatus = SETTINGS_CORRUPT; + CloseHandle( hFile ); + return false; + } + + // We're done with the file: + CloseHandle( hFile ); + + // Compare signatures: + if( memcmp( &xsig, &storedSig, sigSize ) != 0 ) + { + SettingsStatus = SETTINGS_CORRUPT; + return false; + } + + // Lastly, verify that the version number is right: + if( temp.version != SETTINGS_VERSION ) + { + SettingsStatus = SETTINGS_CORRUPT; + return false; + } + + // OK. The data checks out! + *this = temp; + + // TODO: Range-check all the values? + + return true; +} + +void XBSettings::Delete( void ) +{ + // Build the settings directory: + unsigned short wideName[128]; + mbstowcs( wideName, SETTINGS_DIRNAME, sizeof(wideName) ); + + // Delete the game: + XDeleteSaveGame( "U:\\", wideName ); +} + +bool XBSettings::Corrupt( void ) +{ + return (SettingsStatus == SETTINGS_CORRUPT); +} + +bool XBSettings::Missing( void ) +{ + return (SettingsStatus == SETTINGS_MISSING); +} + +// Copy all stored settings into cvars +void XBSettings::SetAll( void ) +{ + Cvar_SetValue( "m_pitch", invertAim[0] ? 0.022f : -0.022f ); + Cvar_SetValue( "ui_thumbStickMode", thumbstickMode[0] ); + + Cvar_Set( "ui_buttonconfig", buttonConfigStrings[buttonMode[0]] ); + Cbuf_ExecuteText( EXEC_APPEND, va("exec cfg/spbuttonConfig%d.cfg\n", buttonMode[0]) ); + + Cvar_Set( "ui_triggerconfig", triggerConfigStrings[triggerMode[0]] ); + Cbuf_ExecuteText( EXEC_APPEND, va("exec cfg/triggersConfig%d.cfg\n", triggerMode[0]) ); + + Cvar_SetValue( "in_useRumble", rumble[0] ); + Cvar_SetValue( "cl_autolevel", autolevel[0] ); + Cvar_SetValue( "cg_autoswitch", autoswitch[0] ); + + Cvar_SetValue( "sensitivity", sensitivityX[0] ); + Cvar_SetValue( "sensitivityY", sensitivityY[0] ); + + if( hotswapSP[0] >= 0 ) + Cvar_SetValue( "hotswap0", hotswapSP[0] ); + else + Cvar_Set( "hotswap0", "" ); + + if( hotswapSP[1] >= 0 ) + Cvar_SetValue( "hotswap1", hotswapSP[1] ); + else + Cvar_Set( "hotswap1", "" ); + + if( hotswapSP[2] >= 0 ) + Cvar_SetValue( "hotswap2", hotswapSP[2] ); + else + Cvar_Set( "hotswap2", "" ); + + Cvar_SetValue( "s_effects_volume", effectsVolume ); + Cvar_SetValue( "s_music_volume", musicVolume ); + Cvar_SetValue( "s_voice_volume", voiceVolume ); + Cvar_SetValue( "s_brightness_volume", brightness ); + extern void GLimp_SetGamma(float); + GLimp_SetGamma(Cvar_VariableValue( "s_brightness_volume" ) / 5.0f); + + Cvar_SetValue( "g_subtitles", subtitles ); +} + +#ifdef XBOX_DEMO +void XBSettings::RestoreDefaults( void ) +{ + version = SETTINGS_VERSION; + + // Defaults: + invertAim[0] = invertAim[1] = false; + + thumbstickMode[0] = thumbstickMode[1] = 0; + buttonMode[0] = buttonMode[1] = 0; + triggerMode[0] = triggerMode[1] = 0; + + rumble[0] = rumble[1] = 1; + autolevel[0] = autolevel[0] = 0; + autoswitch[0] = autoswitch[1] = 1; + sensitivityX[0] = sensitivityX[1] = 2.0f; + sensitivityY[0] = sensitivityY[1] = 2.0f; + + hotswapSP[0] = hotswapSP[1] = hotswapSP[2] = -1; + hotswapMP[0] = hotswapMP[1] = -1; + hotswapMP[2] = hotswapMP[3] = -1; + + effectsVolume = 1.0f; + musicVolume = 0.25f; + voiceVolume = 1.0f; + brightness = 6.0f; + + subtitles = 0; + + // Demo has no foreign audio, so we turn subtitles on if Dash language is FR/GE + DWORD dwLang = XGetLanguage(); + if( dwLang == XC_LANGUAGE_FRENCH || dwLang == XC_LANGUAGE_GERMAN ) + subtitles = 1; + + voiceMode = 2; + voiceMask = 0; + appearOffline = 0; +} +#endif + +// Utility - signs the current contents of this XBSettings into the supplied struct: +bool XBSettings::Sign( XCALCSIG_SIGNATURE *pSig ) +{ + // Start the signature: + HANDLE hSig = XCalculateSignatureBegin( 0 ); + if( hSig == INVALID_HANDLE_VALUE ) + return false; + + // Build the signature + if( XCalculateSignatureUpdate( hSig, (BYTE *) this, sizeof(*this) ) != ERROR_SUCCESS ) + return false; + + // Finish the signature: + if( XCalculateSignatureEnd( hSig, pSig ) != ERROR_SUCCESS ) + return false; + + // Done! + return true; +} + +// Master switch for turning off settings when user picks +// "Continue Without Saving" +void XBSettings::Disable( void ) +{ + settingsDisabled = true; +} + +bool XBSettings::IsDisabled( void ) +{ + return settingsDisabled; +} diff --git a/code/qcommon/xb_settings.h b/code/qcommon/xb_settings.h new file mode 100644 index 0000000..6c363de --- /dev/null +++ b/code/qcommon/xb_settings.h @@ -0,0 +1,87 @@ + +#ifndef __XB_SETTINGS_H +#define __XB_SETTINGS_H + +#include + +enum XBStartupState +{ + STARTUP_LOAD_SETTINGS, + STARTUP_COMBINED_SPACE_CHECK, + STARTUP_GAME_SPACE_CHECK, + STARTUP_INVITE_CHECK, + STARTUP_FINISH, +}; + +// Minimum save size on Xbox. Bleh: +#define SETTINGS_NUM_BLOCKS 4 + +struct XBSettings +{ + // Magic number/revision stamp: + unsigned long version; + + // Controls, etc... One for SP/P1 in MP, other for P2 in MP: + bool invertAim[2]; + int thumbstickMode[2]; + int buttonMode[2]; + int triggerMode[2]; + int rumble[2]; + int autolevel[2]; + int autoswitch[2]; + float sensitivityX[2]; + float sensitivityY[2]; + + // Black/White/X assignments, SP: + int hotswapSP[3]; + + // Black/White for players one & two, MP: + int hotswapMP[4]; + + // A/V settings, Global: + float effectsVolume; + float musicVolume; + float voiceVolume; + float brightness; + + // Subtitles, only used in SP: + int subtitles; + + // Voice/Live options, only used in MP: + int voiceMode; + int voiceMask; + int appearOffline; + +// INTERFACE: + + XBSettings( void ); + + bool Save( void ); + bool Load( void ); + void Delete( void ); + + // For determining why a Save/Load failed: + bool Missing( void ); + bool Corrupt( void ); + + // This copies all settings from the Settings struct to their various cvars + void SetAll( void ); + + // Turn off the settings file completely: + void Disable( void ); + + // Has the user turned off saving (by choosing "Continue Without Saving")? + bool IsDisabled( void ); + +#ifdef XBOX_DEMO + void RestoreDefaults( void ); +#endif + +private: + bool Sign( XCALCSIG_SIGNATURE *pSig ); +}; + +// One global copy (declared in xb_settings.cpp) +extern XBSettings Settings; + +#endif diff --git a/code/qcommon/z_memman_console.cpp b/code/qcommon/z_memman_console.cpp new file mode 100644 index 0000000..cb9a8fc --- /dev/null +++ b/code/qcommon/z_memman_console.cpp @@ -0,0 +1,1882 @@ + +/* + * UNPUBLISHED -- Rights reserved under the copyright laws of the + * United States. Use of a copyright notice is precautionary only and + * does not imply publication or disclosure. + * + * THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION + * OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION, + * DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR + * EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC. + */ + +/* + * ZONE MEMORY MANAGER + * + * Goals: + * 1. Minimize overhead + * 2. Minimize fragmentation + * + * Constraints: + * 1. Maximum allocated block size is 32MB + * 2. Maximum 64 different memory tags supported + * 3. Maximum 256 byte alignment + * + * All memory required by the manager is allocated at startup in + * the form of one large pool. + * + * Allocated blocks require a 4 byte header to store size, tag, and + * alignment information. Blocks that need to support the Z_TagFree() + * feature require an additional 8 byte link list structure. + * + * Free blocks require a 16 bytes of tracking information. If possible + * this information is stored directly in the block (which is in the + * pool.) If the free block is not large enough, its information is + * stored in an overflow buffer. + * + * In an effort to reduce fragmentation, blocks allocated for a short + * period of time at the end of the pool. All other blocks are allocated + * at the start. Allocation is first fit. + * + */ + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "../renderer/qgl_console.h" + +#ifdef _GAMECUBE +#include +#endif + +#ifdef _WINDOWS +#include +#endif + +#ifdef _XBOX +#include +#include "../win32/xbox_texture_man.h" +#endif + +// Used to mark the start and end of blocks in debug mode +#define ZONE_MAGIC 0xfe + +// Size of the free block overflow buffer +#define ZONE_FREE_OVERFLOW 4096 + +// Indicates whether or not special (slow) debug code should be enabled +#define ZONE_DEBUG 0 + +// Allocate all available memory minus this amount - texture pools are +// allocated before this, so just leave enough for framebuffer, etc... +// Gah! I hate Bink! Stupid thing allocates physical memory (probably via +// DSound) when starting. Need to leave just a little more. +#ifdef FINAL_BUILD +# define ZONE_HEAP_FREE (1024*1024*6 + 512*1024 + 256*1024 + 64*1024) +#else +# define ZONE_HEAP_FREE (1024*1024*16 + 16*1024*1024) +#endif + +#ifdef FINAL_BUILD +#define TEXTURE_POOL_SIZE 16*1024*1024 +#else +#define TEXTURE_POOL_SIZE 20*1024*1024 +#endif + +// Two systems (Bink, and Savegames) need large, contiguous allocations once things +// are running and fragmented. They get their own sandbox: +#define TEMP_ALLOC_POOL_SIZE (2*1024*1024 + 512*1024) + +__declspec (align(32)) char s_TempAllocPool[TEMP_ALLOC_POOL_SIZE]; +int s_TempAllocPoint = 0; + +void *TempAlloc( unsigned long size ) +{ + if( s_TempAllocPoint + size > TEMP_ALLOC_POOL_SIZE ) + { + Com_Printf( "WARNING: TempAlloc pool full!\n" ); + return NULL; + } + + void *retVal = &s_TempAllocPool[s_TempAllocPoint]; + + s_TempAllocPoint = (s_TempAllocPoint + size + 31) & ~31; + + return retVal; +} + +void TempFree( void ) +{ + s_TempAllocPoint = 0; +} + +// Should we emulate the smaller memory footprint of actual release systems? +#define ZONE_EMULATE_SPACE 0 + +// All standard header data is crammed into 4 bytes +typedef unsigned int ZoneHeader; + +// Debug markers to check for overflow/underflow +typedef unsigned int ZoneDebugHeader; +typedef unsigned char ZoneDebugFooter; + +// Extended header information for memory freed with TagFree() +struct ZoneLinkHeader +{ + ZoneLinkHeader* m_Next; + ZoneLinkHeader* m_Prev; +}; + +static ZoneLinkHeader* s_LinkBase; + +// Free memory block tracking information +struct ZoneFreeBlock +{ + unsigned int m_Address; + unsigned int m_Size; + ZoneFreeBlock* m_Next; + ZoneFreeBlock* m_Prev; +}; + +// Buffer to hold free memory information that we can't +// fit directly in the pool +static ZoneFreeBlock s_FreeOverflow[ZONE_FREE_OVERFLOW]; +static int s_LastOverflowIndex; + +static ZoneFreeBlock s_FreeStart; +static ZoneFreeBlock s_FreeEnd; + +// Various stats collected at runtime +struct ZoneStats +{ + int m_CountAlloc; + int m_SizeAlloc; + int m_OverheadAlloc; + int m_PeakAlloc; + int m_CountFree; + int m_SizeFree; + int m_SizesPerTag[TAG_COUNT]; + int m_CountsPerTag[TAG_COUNT]; +}; + +static ZoneStats s_Stats; + +// Special empty block for zero size allocations +struct ZoneEmptyBlock +{ + ZoneHeader header; +#ifdef _DEBUG + ZoneDebugHeader start; + ZoneDebugFooter end; +#endif +}; + +#ifdef _DEBUG +static ZoneEmptyBlock s_EmptyBlock = {TAG_STATIC << 25, ZONE_MAGIC, ZONE_MAGIC}; +#else +static ZoneEmptyBlock s_EmptyBlock = {TAG_STATIC << 25}; +#endif + +// Free block jump table for fast memory deallocation +#define Z_JUMP_TABLE_SIZE 64 +static ZoneFreeBlock* s_FreeJumpTable[Z_JUMP_TABLE_SIZE]; +static unsigned int s_FreeJumpResolution; + +static void* s_PoolBase; +static int s_PoolSize; +static bool s_Initialized = false; + +static memtag_t s_newDeleteTagStack[32] = { TAG_NEWDEL }; +static int s_newDeleteTagStackTop = 0; + +#ifndef _GAMECUBE +static HANDLE s_Mutex = INVALID_HANDLE_VALUE; +#endif + +static void Z_Stats_f(void); +void Z_Details_f(void); +void Z_DumpMemMap_f(void); +void Z_CompactStats(void); + +void Z_PushNewDeleteTag( memtag_t eTag ) +{ + assert( s_newDeleteTagStackTop < 31 ); + s_newDeleteTagStack[++s_newDeleteTagStackTop] = eTag; +} + +void Z_PopNewDeleteTag( void ) +{ + assert( s_newDeleteTagStackTop ); + --s_newDeleteTagStackTop; +} + +#ifdef _XBOX +void ShowOSMemory(void) +{ + MEMORYSTATUS stat; + GlobalMemoryStatus(&stat); + Com_Printf(" total mem: %d, free mem: %d\n", stat.dwTotalPhys / 1024, + stat.dwAvailPhys / 1024); + FILE *out = fopen("d:\\osmem.txt", "a"); + if(out) { + fprintf(out, "total mem: %d, free mem: %d\n", stat.dwTotalPhys / 1024, + stat.dwAvailPhys / 1024); + fclose(out); + } +} +#endif + + +int Z_MemFree(void) +{ + return s_Stats.m_SizeFree; +} + +void Com_InitZoneMemory(void) +{ +// assert(!s_Initialized); + // Zone now initializes on first use, can't reliably assume anything here + if (s_Initialized) + return; + + Com_Printf("Initialising zone memory .....\n"); + + // Clear some globals + memset(&s_Stats, 0, sizeof(s_Stats)); + memset(s_FreeOverflow, 0, sizeof(s_FreeOverflow)); + s_LastOverflowIndex = 0; + s_LinkBase = NULL; + + // Alloc the pool + MEMORYSTATUS status; + GlobalMemoryStatus(&status); + + // BTO : VVFIXME - Extra little note to see how much memory + // is being used by globals/statics + Com_Printf("*** PhysRAM: %d used, %d free\n", + status.dwTotalPhys-status.dwAvailPhys, + status.dwAvailPhys); + + // Allocate the texture pool: + gTextures.Initialize( TEXTURE_POOL_SIZE ); + + GlobalMemoryStatus(&status); + + // BTO : VVFIXME - Extra little note to see how much memory + // is being used by globals/statics + Com_Printf("*** PhysRAM: %d used, %d free\n", + status.dwTotalPhys-status.dwAvailPhys, + status.dwAvailPhys); + SIZE_T size; +# if ZONE_EMULATE_SPACE +#ifdef _DEBUG + //Emulated space is always about 6 megs off from release build. Try + //to compensate. This number may need tweaking in the future. + SIZE_T exe = 6500 * 1024; +#else + SIZE_T exe = 0; //Exe size is already reflected in GlobalMemoryStatus(). +#endif + size = 0x4000000 - (exe + ZONE_HEAP_FREE); +# else + size = status.dwAvailPhys - ZONE_HEAP_FREE; +# endif + + s_PoolBase = GlobalAlloc(0, size); + s_PoolSize = size; + + // Setup the initial free block + ZoneFreeBlock* base = (ZoneFreeBlock*)s_PoolBase; + base->m_Address = (unsigned int)s_PoolBase; + base->m_Size = size; + base->m_Next = &s_FreeEnd; + base->m_Prev = &s_FreeStart; + + // Init the free block jump table + memset(s_FreeJumpTable, 0, Z_JUMP_TABLE_SIZE * sizeof(ZoneFreeBlock*)); + s_FreeJumpResolution = (size / Z_JUMP_TABLE_SIZE) + 1; + s_FreeJumpTable[0] = base; + + // Setup free block dummies + s_FreeStart.m_Address = 0; + s_FreeStart.m_Size = 0; + s_FreeStart.m_Next = base; + s_FreeStart.m_Prev = NULL; + + s_FreeEnd.m_Address = 0xFFFFFFFF; + s_FreeEnd.m_Size = 0; + s_FreeEnd.m_Next = NULL; + s_FreeEnd.m_Prev = base; + + s_Stats.m_CountFree = 1; + s_Stats.m_SizeFree = size; + + s_Initialized = true; + + // Add some commands + Cmd_AddCommand("zone_stats", Z_Stats_f); + Cmd_AddCommand("zone_details", Z_Details_f); + Cmd_AddCommand("zone_memmap", Z_DumpMemMap_f); + Cmd_AddCommand("zone_cstats", Z_CompactStats); + +#ifndef _GAMECUBE + s_Mutex = CreateMutex(NULL, FALSE, NULL); +#endif + + // Super-size my hack. With fries. We allocate enough space for g_entities at the + // end of the zone now. If it turns out (somehow) that there is no persisted surface, + // then we need to take g_entities from the zone, but we'd normally allocate it so late + // that we'll have a big chunk in the middle. That's bad. And if we reserve space at the + // front, then we might not need it, and we've got a 1.2MB chunk of empty space there. + // This works perfectly, though: + extern void G_ReserveZoneGentities( void ); + G_ReserveZoneGentities(); +} + +void Com_ShutdownZoneMemory(void) +{ + assert(s_Initialized); + + // Remove commands + Cmd_RemoveCommand("zone_stats"); + Cmd_RemoveCommand("zone_details"); + Cmd_RemoveCommand("zone_memmap"); + + if (s_Stats.m_CountAlloc) + { + // Free all memory +// CM_ReleaseVisData(); + Z_TagFree(TAG_ALL); + } + + // Clear some globals + memset(&s_Stats, 0, sizeof(s_Stats)); + memset(s_FreeOverflow, 0, sizeof(s_FreeOverflow)); + s_LastOverflowIndex = 0; + s_LinkBase = NULL; + + // Free the pool +#ifndef _GAMECUBE + GlobalFree(s_PoolBase); + CloseHandle(s_Mutex); +#endif + + s_PoolBase = NULL; + s_Initialized = false; +} + + +// Determine if a tag should only be allocated for a very +// short period of time. +static bool Z_IsTagTemp(memtag_t eTag) +{ + return + eTag == TAG_TEMP_WORKSPACE || + eTag == TAG_SND_RAWDATA || + eTag == TAG_ICARUS || + eTag == TAG_LISTFILES || + eTag == TAG_GP2; +} + +// Determine if a tag needs TagFree() support. +static bool Z_IsTagLinked(memtag_t eTag) +{ + return + eTag == TAG_BSP || + eTag == TAG_HUNKALLOC || +// eTag == TAG_HUNKMISCMODELS || + eTag == TAG_G_ALLOC || + eTag == TAG_UI_ALLOC; +} + +static int Z_CalcAlignmentPad(int iAlign, unsigned int iAddress, unsigned int iOffset, + unsigned int iSize, unsigned int iHeaderSize, unsigned int iFooterSize) +{ + int align_size; + + if (iAlign == 0) return 0; + + if (iOffset == 0) + { + // Align data at low end of block + align_size = iAlign - + ((iAddress + iHeaderSize) % iAlign); + } + else + { + // Align data at high end of block + unsigned int block_start = iAddress + iOffset - + iSize + iHeaderSize; + align_size = block_start % iAlign; + } + + if (align_size == iAlign) + { + return 0; + } + + return align_size; +} + +static ZoneFreeBlock* Z_GetOverflowBlock(void) +{ + for (int i = s_LastOverflowIndex; i < ZONE_FREE_OVERFLOW; ++i) + { + if (s_FreeOverflow[i].m_Address == 0) + { + s_LastOverflowIndex = i; + return &s_FreeOverflow[i]; + } + } + + for (int j = 0; j < s_LastOverflowIndex; ++j) + { + if (s_FreeOverflow[j].m_Address == 0) + { + s_LastOverflowIndex = j; + return &s_FreeOverflow[j]; + } + } + + return NULL; +} + +static inline bool Z_IsFreeBlockLargeEnough(ZoneFreeBlock* pBlock, int iSize, + int iHeaderSize, int iFooterSize, int iAlign, bool bLow, int& iAlignPad) +{ + // Is the block large enough? + if (pBlock->m_Size >= iSize) + { + if (iAlign > 0) + { + // If we need some aligment, we need to check size + // against that as well. + iAlignPad = Z_CalcAlignmentPad(iAlign, + pBlock->m_Address, !bLow ? pBlock->m_Size : 0, + iSize, iHeaderSize, iFooterSize); + + if (pBlock->m_Size < iAlignPad + iSize) + { + return false; + } + } + return true; + } + return false; +} + +static ZoneFreeBlock* Z_FindFirstFree(int iSize, int iHeaderSize, + int iFooterSize, int iAlign, int& iAlignPad) +{ + for (ZoneFreeBlock* block = s_FreeStart.m_Next; block; block = block->m_Next) + { + if (Z_IsFreeBlockLargeEnough(block, iSize, iHeaderSize, iFooterSize, + iAlign, true, iAlignPad)) + { + return block; + } + } + return NULL; +} + +static ZoneFreeBlock* Z_FindLastFree(int iSize, int iHeaderSize, + int iFooterSize, int iAlign, int& iAlignPad) +{ + for (ZoneFreeBlock* block = s_FreeEnd.m_Prev; block; block = block->m_Prev) + { + if (Z_IsFreeBlockLargeEnough(block, iSize, iHeaderSize, iFooterSize, + iAlign, false, iAlignPad)) + { + return block; + } + } + return NULL; +} + +static bool Z_ValidateFree(void) +{ +#if ZONE_DEBUG + // Make sure no free blocks are overlapping + for (ZoneFreeBlock* a = &s_FreeStart; a; a = a->m_Next) + { + if (a->m_Address == 0 && a->m_Size != 0) + { + return false; + } + + for (ZoneFreeBlock* b = &s_FreeStart; b; b = b->m_Next) + { + if (a != b && + a->m_Address >= b->m_Address && + a->m_Address < b->m_Address + b->m_Size) + { + return false; + } + } + } +#endif + + return true; +} + +static bool Z_ValidateLinks(void) +{ +#if ZONE_DEBUG + // Make sure links are sane + for (ZoneLinkHeader* a = s_LinkBase; a; a = a->m_Next) + { + if ((a->m_Next && a != a->m_Next->m_Prev) || + (a->m_Prev && a != a->m_Prev->m_Next)) + { + return false; + } + } +#endif + + return true; +} + +static int Z_GetJumpTableIndex(unsigned int iAddress) +{ + int index = (iAddress - (unsigned int)s_PoolBase) / s_FreeJumpResolution; + if (index < 0) return 0; + if (index >= Z_JUMP_TABLE_SIZE) return Z_JUMP_TABLE_SIZE - 1; + return index; +} + +static ZoneFreeBlock* Z_GetFreeBlockBefore(unsigned int iAddress) +{ + // Find this block's position in the jump table + int index = Z_GetJumpTableIndex(iAddress) - 1; + + // Find a valid jump table entry + while (index >= 0 && !s_FreeJumpTable[index]) --index; + + if (index < 0) return &s_FreeStart; + return s_FreeJumpTable[index]; +} + +static void Z_RemoveFromJumpTable(ZoneFreeBlock* pBlock) +{ + // Is this block in the jump table? + int index = Z_GetJumpTableIndex(pBlock->m_Address); + if (s_FreeJumpTable[index] == pBlock) + { + // See if the next block will fit in our slot + if (pBlock->m_Next != &s_FreeEnd) + { + int nindex = Z_GetJumpTableIndex(pBlock->m_Next->m_Address); + if (nindex == index) + { + s_FreeJumpTable[index] = pBlock->m_Next; + return; + } + } + + // See if the previous block will fit in our slot + if (pBlock->m_Prev != &s_FreeStart) + { + int pindex = Z_GetJumpTableIndex(pBlock->m_Prev->m_Address); + if (pindex == index) + { + s_FreeJumpTable[index] = pBlock->m_Prev; + return; + } + } + + // No other free blocks fit here, give up + s_FreeJumpTable[index] = NULL; + } +} + +static void Z_LinkFreeBlock(ZoneFreeBlock* pBlock) +{ + ZoneFreeBlock* cur = Z_GetFreeBlockBefore(pBlock->m_Address); + for (; cur; cur = cur->m_Next) + { + // Find the correct position, ordered by address + if (cur->m_Address > pBlock->m_Address) + { + // Link up the block + pBlock->m_Next = cur; + pBlock->m_Prev = cur->m_Prev; + cur->m_Prev->m_Next = pBlock; + cur->m_Prev = pBlock; + + // Update the jump table if necessary + int index = Z_GetJumpTableIndex(pBlock->m_Address); + if (!s_FreeJumpTable[index]) + { + s_FreeJumpTable[index] = pBlock; + } + + s_Stats.m_CountFree++; + s_Stats.m_SizeFree += pBlock->m_Size; + + assert(Z_ValidateFree()); + break; + } + } +} + +static void* Z_SplitFree(ZoneFreeBlock* pBlock, int iSize, bool bLow) +{ + assert(pBlock->m_Size >= iSize); + + Z_RemoveFromJumpTable(pBlock); + + // Delink the free block + ZoneFreeBlock fblock = *pBlock; + pBlock->m_Prev->m_Next = pBlock->m_Next; + pBlock->m_Next->m_Prev = pBlock->m_Prev; + pBlock->m_Address = 0; + + s_Stats.m_CountFree--; + s_Stats.m_SizeFree -= pBlock->m_Size; + assert(Z_ValidateFree()); + + if (fblock.m_Size > iSize) + { + // Split the block into an allocated and free portion + int remainder = fblock.m_Size - iSize; + + if (remainder < sizeof(ZoneFreeBlock)) + { + // Free portion is not large to hold free info -- + // we're going to have to use the overflow buffer. + ZoneFreeBlock* nblock = Z_GetOverflowBlock(); + + if (nblock == NULL) + { + Z_Details_f(); + Com_Error(ERR_FATAL, "Zone free overflow buffer overflowed!"); + } + + // Split the block + void* ret; + if (bLow) + { + ret = (void*)fblock.m_Address; + nblock->m_Address = fblock.m_Address + iSize; + } + else + { + ret = (void*)(fblock.m_Address + remainder); + nblock->m_Address = fblock.m_Address; + } + + nblock->m_Size = remainder; + Z_LinkFreeBlock(nblock); + + return ret; + } + else + { + // Free portion is large enough -- split it + void* ret; + ZoneFreeBlock* nblock; + if (bLow) + { + ret = (void*)fblock.m_Address; + nblock = (ZoneFreeBlock*)(fblock.m_Address + iSize); + } + else + { + ret = (void*)(fblock.m_Address + remainder); + nblock = (ZoneFreeBlock*)fblock.m_Address; + } + + nblock->m_Address = (unsigned int)nblock; + nblock->m_Size = remainder; + + Z_LinkFreeBlock(nblock); + + return ret; + } + } + else + { + // No need to split, just return block. + return (void*)fblock.m_Address; + } +} + +static void Z_SetupAlignmentPad(void* pBlock, int iAlignPad, bool bLow) +{ + // Clear alignment bytes + memset(pBlock, 0, iAlignPad); + + // If we have more than 1 alignment byte, the first align byte + // tells us how many additional bytes we have. + if (iAlignPad > 1) + { + assert(iAlignPad < 256); + unsigned char* ptr; + if (bLow) + { + ptr = (unsigned char*)pBlock + (iAlignPad - 1); + } + else + { + ptr = (unsigned char*)pBlock; + } + *ptr = iAlignPad - 1; + } +} + +void Z_MallocFail(const char* pMessage, int iSize, memtag_t eTag) +{ + // Report the error +// Com_Printf("Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); + Com_Printf("Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); + Z_Details_f(); + Z_DumpMemMap_f(); +// Com_Printf("(Repeat): Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); + Com_Printf("(Repeat): Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag); + + // Clear the screen blue to indicate out of memory + for (;;) + { + qglBeginFrame(); + qglClearColor(0, 0, 1, 1); + qglClear(GL_COLOR_BUFFER_BIT); + qglEndFrame(); + } +} + +void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int iAlign) +{ +// assert(s_Initialized); + // Zone now initializes on first use. (During static constructors) + if (!s_Initialized) + Com_InitZoneMemory(); + + if (iSize == 0) + { +#ifdef _DEBUG + return (void*)(&s_EmptyBlock.start + 1); +#else + return (void*)(&s_EmptyBlock.header + 1); +#endif + } + + if (iSize < 0) + { + Z_MallocFail("Negative size", iSize, eTag); + return NULL; + } + +#ifndef _GAMECUBE + WaitForSingleObject(s_Mutex, INFINITE); +#endif + + // Make new/delete memory temporary if requested + if (eTag == TAG_NEWDEL ) + { + eTag = s_newDeleteTagStack[s_newDeleteTagStackTop]; + } + + // Determine how much space we need with headers and footers + int header_size = sizeof(ZoneHeader); + int footer_size = 0; + if (Z_IsTagLinked(eTag)) + { + header_size += sizeof(ZoneLinkHeader); + } +#ifdef _DEBUG + header_size += sizeof(ZoneDebugHeader); + footer_size += sizeof(ZoneDebugFooter); +#endif + int real_size = iSize + header_size + footer_size; + int align_pad = 0; + + // Get a bit of free memory. Temporary memory is allocated + // from the end. More permanent allocations are done at the + // begining of the pool. + ZoneFreeBlock* fblock; + if (Z_IsTagTemp(eTag)) + { + fblock = Z_FindLastFree(real_size, header_size, footer_size, + iAlign, align_pad); + } + else + { + fblock = Z_FindFirstFree(real_size, header_size, footer_size, + iAlign, align_pad); + } + + // Did we actually find some memory? + if (!fblock) + { +#ifndef _GAMECUBE + ReleaseMutex(s_Mutex); +#endif +// if(eTag == TAG_TEMP_SND_RAWDATA) { + if(eTag == TAG_SND_RAWDATA) { + return NULL; + } + + Z_MallocFail("Out of memory", iSize, eTag); + return NULL; + } + + // Add any alignment bytes + real_size += align_pad; + + // Split the free block and get a pointer to the start + // allocated space. + void* ablock; + if (Z_IsTagTemp(eTag)) + { + ablock = Z_SplitFree(fblock, real_size, false); + + // Append align pad to end of block + Z_SetupAlignmentPad( + (void*)((char*)ablock + real_size - align_pad), + align_pad, false); + } + else + { + ablock = Z_SplitFree(fblock, real_size, true); + + // Insert align pad at block start + Z_SetupAlignmentPad(ablock, align_pad, true); + ablock = (void*)((char*)ablock + align_pad); + } + + if (!ablock) + { + Z_MallocFail("Failed to split", iSize, eTag); + } + + // Add linking header if necessary + if (Z_IsTagLinked(eTag)) + { + ZoneLinkHeader* linked = (ZoneLinkHeader*)ablock; + linked->m_Next = s_LinkBase; + linked->m_Prev = NULL; + if (s_LinkBase) + { + s_LinkBase->m_Prev = linked; + } + s_LinkBase = linked; + + assert(Z_ValidateLinks()); + + // Next... + ablock = (void*)((char*)ablock + sizeof(ZoneLinkHeader)); + } + + // Setup the header: + // 31 - alignment flag + // 25-30 - tag + // 0-24 - size without headers/footers + assert(iSize >= 0 && iSize < (1 << 25)); + assert(eTag >= 0 && eTag < 64); + ZoneHeader* header = (ZoneHeader*)ablock; + *header = + (((unsigned int)eTag) << 25) | + ((unsigned int)iSize); + + if (align_pad) + { + *header |= (1 << 31); + } + + // Next... + ablock = (void*)((char*)ablock + sizeof(ZoneHeader)); + +#ifdef _DEBUG + { + // Setup the debug markers + ZoneDebugHeader* debug_header = (ZoneDebugHeader*)ablock; + + ZoneDebugFooter* debug_footer = (ZoneDebugFooter*)((char*)debug_header + + (sizeof(ZoneDebugHeader) + iSize)); + + *debug_header = ZONE_MAGIC; + *debug_footer = ZONE_MAGIC; + + // Next... + ablock = (void*)((char*)ablock + sizeof(ZoneDebugHeader)); + } +#endif + + // Update the stats + s_Stats.m_SizeAlloc += iSize; + s_Stats.m_OverheadAlloc += header_size + footer_size + align_pad; + s_Stats.m_SizesPerTag[eTag] += iSize; + s_Stats.m_CountAlloc++; + s_Stats.m_CountsPerTag[eTag]++; + + if (s_Stats.m_SizeAlloc + s_Stats.m_OverheadAlloc > s_Stats.m_PeakAlloc) + { + s_Stats.m_PeakAlloc = s_Stats.m_SizeAlloc + s_Stats.m_OverheadAlloc; + } + + // Return a pointer to data memory + if (bZeroit) + { + memset(ablock, 0, iSize); + } + + assert(iAlign == 0 || (unsigned int)ablock % iAlign == 0); + + /* + This is useful for figuring out who's allocating a certain block of + memory. Please don't remove it. + if(eTag == TAG_NEWDEL && (unsigned int)ablock >= 0x806c0000 && + (unsigned int)ablock <= 0x806c1000 && iSize == 24) { + int suck = 0; + } + if(eTag == TAG_SMALL && (iSize == 7 || iSize == 96)) { + int suck = 0; + } + if(eTag == TAG_CLIENTS) { + int suck = 0; + } + + if ((unsigned)ablock >= 0x169b000 && (unsigned)ablock <= 0x169c000 && iSize == 20) + { + int suck = 0; + } + */ + +#ifndef _GAMECUBE + ReleaseMutex(s_Mutex); +#endif + + return ablock; +} + +static memtag_t Z_GetTag(const ZoneHeader* header) +{ + return (*header & 0x7E000000) >> 25; +} + +static unsigned int Z_GetSize(const ZoneHeader* header) +{ + return *header & 0x1FFFFFF; +} + +static int Z_GetAlign(const ZoneHeader* header) +{ + if (*header & (1 << 31)) + { + unsigned char* ptr = (unsigned char*)header; + memtag_t tag = Z_GetTag(header); + + // point to the first alignment block + if (Z_IsTagTemp(tag)) + { + ptr += sizeof(ZoneHeader) + Z_GetSize(header); +#ifdef _DEBUG + ptr += sizeof(ZoneDebugHeader) + sizeof(ZoneDebugFooter); +#endif + } + else + { + if (Z_IsTagLinked(tag)) + { + // skip the link header + ptr -= sizeof(ZoneLinkHeader); + } + ptr -= 1; + } + + return *ptr + 1; + } + return 0; +} + +int Z_Size(void *pvAddress) +{ + assert(s_Initialized); + +#ifdef _DEBUG + ZoneDebugHeader* debug = (ZoneDebugHeader*)pvAddress - 1; + + if (*debug != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Size(): Not a valid zone header!"); + return 0; // won't get here + } + + pvAddress = (void*)debug; +#endif + + ZoneHeader* header = (ZoneHeader*)pvAddress - 1; + + if (Z_GetTag(header) == TAG_STATIC) + { + return 0; // kind of + } + + return Z_GetSize(header); +} + +static void Z_Coalasce(ZoneFreeBlock* pBlock) +{ + unsigned int size = 0; + + // Find later free blocks adjacent to us + ZoneFreeBlock* end; + for (end = pBlock->m_Next; + end->m_Next; + end = end->m_Next) + { + if (end->m_Address != + end->m_Prev->m_Address + end->m_Prev->m_Size) + { + break; + } + + size += end->m_Size; + + Z_RemoveFromJumpTable(end); + + end->m_Address = 0; // invalidate block + s_Stats.m_CountFree--; + } + + // Find previous free blocks adjacent to us + ZoneFreeBlock* start; + for (start = pBlock; + start->m_Prev; + start = start->m_Prev) + { + if (start->m_Prev->m_Address + start->m_Prev->m_Size != + start->m_Address) + { + break; + } + + size += start->m_Size; + + Z_RemoveFromJumpTable(start); + + start->m_Address = 0; // invalidate block + s_Stats.m_CountFree--; + } + + // Do we need to coalesce some blocks? + if (start->m_Next != end) + { + start->m_Next = end; + end->m_Prev = start; + start->m_Size += size; + } +} + +// Return type of Z_Free differs in SP/MP. Macro hack to wrap it up +#ifdef _JK2MP + void Z_Free(void *pvAddress) + #define Z_FREE_RETURN(x) return +#else +int Z_Free(void *pvAddress) + #define Z_FREE_RETURN(x) return (x) +#endif +{ +#ifdef _WINDOWS + if (!s_Initialized) return; +#endif + + assert(s_Initialized); + +#ifdef _DEBUG + // check the header magic + ZoneDebugHeader* debug_header = (ZoneDebugHeader*)pvAddress - 1; + + if (*debug_header != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone header!"); + Z_FREE_RETURN( 0 ); + } + + ZoneHeader* header = (ZoneHeader*)debug_header - 1; + + // check the footer magic + ZoneDebugFooter* debug_footer = (ZoneDebugFooter*)((char*)pvAddress + + Z_GetSize(header)); + + if (*debug_footer != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone footer!"); + Z_FREE_RETURN( 0 ); + } +#else + ZoneHeader* header = (ZoneHeader*)pvAddress - 1; +#endif + + memtag_t tag = Z_GetTag(header); + + if (tag != TAG_STATIC) + { +#ifndef _GAMECUBE + WaitForSingleObject(s_Mutex, INFINITE); +#endif + + // Determine size of header and footer + int header_size = sizeof(ZoneHeader); + int align_size = Z_GetAlign(header); + int footer_size = 0; + int data_size = Z_GetSize(header); + if (Z_IsTagLinked(tag)) + { + header_size += sizeof(ZoneLinkHeader); + } + if (Z_IsTagTemp(tag)) + { + footer_size += align_size; + } + else + { + header_size += align_size; + } +#ifdef _DEBUG + header_size += sizeof(ZoneDebugHeader); + footer_size += sizeof(ZoneDebugFooter); +#endif + int real_size = data_size + header_size + footer_size; + + // Update the stats + s_Stats.m_SizeAlloc -= data_size; + s_Stats.m_OverheadAlloc -= header_size + footer_size; + s_Stats.m_SizesPerTag[tag] -= data_size; + s_Stats.m_CountAlloc--; + s_Stats.m_CountsPerTag[tag]--; + + // Delink block + if (Z_IsTagLinked(tag)) + { + ZoneLinkHeader* linked = (ZoneLinkHeader*)header - 1; + + if (linked == s_LinkBase) + { + s_LinkBase = linked->m_Next; + if (s_LinkBase) + { + s_LinkBase->m_Prev = NULL; + } + } + else + { + if (linked->m_Next) + { + linked->m_Next->m_Prev = linked->m_Prev; + } + linked->m_Prev->m_Next = linked->m_Next; + } + + assert(Z_ValidateLinks()); + } + + // Clear the block header for safety + *header = 0; + + // Add block to free list + ZoneFreeBlock* nblock = NULL; + if (real_size < sizeof(ZoneFreeBlock)) + { + // Not enough space in block to put free information -- + // use overflow buffer. + nblock = Z_GetOverflowBlock(); + + if (nblock == NULL) + { + Z_Details_f(); + Com_Error(ERR_FATAL, "Zone free overflow buffer overflowed!"); + } + } + else + { + // Place free information in block + nblock = (ZoneFreeBlock*)((char*)pvAddress - header_size); + } + + nblock->m_Address = (unsigned int)pvAddress - header_size; + nblock->m_Size = real_size; + Z_LinkFreeBlock(nblock); + + // Coalesce any adjacent free blocks + Z_Coalasce(nblock); +#ifndef _GAMECUBE + ReleaseMutex(s_Mutex); +#endif + } + + Z_FREE_RETURN( 0 ); +} + + +int Z_MemSize(memtag_t eTag) +{ + return s_Stats.m_SizesPerTag[eTag]; +} + +#if ZONE_DEBUG +void Z_FindLeak(void) +{ + assert(s_Initialized); + + static int cycle_count = 0; + const memtag_t tag = TAG_NEWDEL; + + struct PointerInfo + { + void* data; + int counter; + bool mark; + }; + + const int max_pointers = 32768; + static PointerInfo pointers[max_pointers]; + static int num_pointers = 0; + + // Clear pointer existance + for (int i = 0; i < num_pointers; ++i) + { + pointers[i].mark = false; + } + + // Add all known pointers + int start_num = num_pointers; + for (ZoneLinkHeader* link = s_LinkBase; link;) + { + ZoneHeader* header = (ZoneHeader*)(link + 1); + link = link->m_Next; + + if (Z_GetTag(header) == tag) + { + // See if the pointer already is in the array + bool found = false; + for (int k = start_num; k < num_pointers; ++k) + { + if (pointers[k].data == header) + { + ++pointers[k].counter; + pointers[k].mark = true; + found = true; + break; + } + } + + // If the pointer is not in the array, add it + if (!found) + { + assert(num_pointers < max_pointers); + pointers[num_pointers].data = header; + pointers[num_pointers].counter = 0; + pointers[num_pointers].mark = true; + ++num_pointers; + } + } + } + + // Remove pointers that are no longer used + for (int j = 0; j < num_pointers; ++j) + { + if (pointers[j].mark) + { + if (pointers[j].counter != cycle_count && + pointers[j].counter != cycle_count - 1 && + pointers[j].counter != 0) + { + Com_Printf("Memory leak: %p\n", pointers[j].data); + } + } + else + { + int k; + for (k = j; k < num_pointers; ++k) + { + if (pointers[k].mark) break; + } + + if (k == num_pointers) break; + + memmove(pointers + j, pointers + k, (num_pointers - k) * sizeof(PointerInfo)); + num_pointers -= k - j; + } + } + + ++cycle_count; +} +#endif + +void Z_TagPointers(memtag_t eTag) +{ + assert(s_Initialized); + +#ifndef _GAMECUBE + WaitForSingleObject(s_Mutex, INFINITE); +#endif + + Sys_Log( "pointers.txt", va("Pointers for tag %d:\n", eTag) ); + + for (ZoneLinkHeader* link = s_LinkBase; link;) + { + ZoneHeader* header = (ZoneHeader*)(link + 1); + link = link->m_Next; + + if (eTag == TAG_ALL || Z_GetTag(header) == eTag) + { +#ifdef _DEBUG + Sys_Log( "pointers.txt", + va("%x - %d\n", ((void*)((char*)header + + sizeof(ZoneHeader) + sizeof(ZoneDebugHeader))), + Z_Size(((void*)((char*)header + + sizeof(ZoneHeader) + sizeof(ZoneDebugHeader)))))); +#else + Sys_Log( "pointers.txt", + va("%x - %d\n", (void*)(header + 1), + Z_Size((void*)(header + 1)))); +#endif + } + } + +#ifndef _GAMECUBE + ReleaseMutex(s_Mutex); +#endif +} + +void Z_TagFree(memtag_t eTag) +{ + assert(s_Initialized); + + for (ZoneLinkHeader* link = s_LinkBase; link;) + { + ZoneHeader* header = (ZoneHeader*)(link + 1); + link = link->m_Next; + + if (eTag == TAG_ALL || Z_GetTag(header) == eTag) + { +#ifdef _DEBUG + Z_Free((void*)((char*)header + sizeof(ZoneHeader) + + sizeof(ZoneDebugHeader))); +#else + Z_Free((void*)(header + 1)); +#endif + } + } +} + +void Z_SetNewDeleteTemporary(bool bTemp) +{ + if( bTemp ) + Z_PushNewDeleteTag( TAG_TEMP_WORKSPACE ); + else + Z_PopNewDeleteTag(); +} + +void *S_Malloc( int iSize ) +{ + return Z_Malloc(iSize, TAG_SMALL, qfalse, 0); +} + +int Z_GetLevelMemory(void) +{ +#ifdef _JK2MP + return s_Stats.m_SizesPerTag[TAG_BSP]; +#else + return s_Stats.m_SizesPerTag[TAG_HUNKALLOC] + +// s_Stats.m_SizesPerTag[TAG_HUNKMISCMODELS] + + s_Stats.m_SizesPerTag[TAG_BSP]; +#endif +} + +#ifdef _JK2MP +int Z_GetHunkMemory(void) +{ + return s_Stats.m_SizesPerTag[TAG_HUNKALLOC] + + s_Stats.m_SizesPerTag[TAG_TEMP_HUNKALLOC]; +} +#endif + +int Z_GetMiscMemory(void) +{ + return s_Stats.m_SizeAlloc - + (Z_GetLevelMemory() + +#ifdef _JK2MP + Z_GetHunkMemory() + +#endif + s_Stats.m_SizesPerTag[TAG_MODEL_GLM] + + s_Stats.m_SizesPerTag[TAG_MODEL_GLA] + + s_Stats.m_SizesPerTag[TAG_MODEL_MD3] + + s_Stats.m_SizesPerTag[TAG_BINK] + + s_Stats.m_SizesPerTag[TAG_SND_RAWDATA]); +} + +#ifdef _GAMECUBE +static int texMemSize = 0; +#else +extern int texMemSize; +//extern unsigned long texturePoint; +#endif +void Z_CompactStats(void) +{ + // New and improved, super version of CompactStats: + assert(s_Initialized); + + static int printHeader = 1; + if( printHeader ) + { + printHeader = 0; + Sys_Log("memory-map.txt", "Level:\tTextures:\tFreeZone:\tOverhead:\tTags...\n"); + } + + // No more being conservative and doing strange math. I want real numbers: + Sys_Log("memory-map.txt", va("%s\t%d\t%d\t%d", + Cvar_VariableString( "mapname" ), + gTextures.Size(), + s_Stats.m_SizeFree, + s_Stats.m_OverheadAlloc)); + for( int t = 0; t < TAG_COUNT; ++t ) + Sys_Log("memory-map.txt", va("\t%d", s_Stats.m_SizesPerTag[t])); + Sys_Log("memory-map.txt", "\n"); +} + + +static void Z_Stats_f(void) +{ + assert(s_Initialized); + // Display some memory usage summary information... + + Com_Printf("\nThe zone is using %d bytes (%.2fMB) in %d memory blocks\n", + s_Stats.m_SizeAlloc, + (float)s_Stats.m_SizeAlloc / 1024.0f / 1024.0f, + s_Stats.m_CountAlloc); + + Com_Printf("Free memory is %d bytes (%.2fMB) in %d memory blocks\n", + s_Stats.m_SizeFree, + (float)s_Stats.m_SizeFree / 1024.0f / 1024.0f, + s_Stats.m_CountFree); + + Com_Printf("The zone peaked at %d bytes (%.2fMB)\n", + s_Stats.m_PeakAlloc, + (float)s_Stats.m_PeakAlloc / 1024.0f / 1024.0f); + + Com_Printf("The zone overhead is %d bytes (%.2fMB)\n", + s_Stats.m_OverheadAlloc, + (float)s_Stats.m_OverheadAlloc / 1024.0f / 1024.0f); +} + +void Z_Details_f(void) +{ + assert(s_Initialized); + // Display some tag specific information... + + Com_Printf("---------------------------------------------------------------------------\n"); + Com_Printf("%20s %9s\n","Zone Tag","Bytes"); + Com_Printf("%20s %9s\n","--------","-----"); + for (int i=0; im_Next) + { + while (fblock->m_Address > cur + 1024) + { + WRITECHAR("*"); + } + + if (fblock->m_Address > cur && fblock->m_Address < cur + 1024) + { + WRITECHAR("+"); + } + + while (fblock->m_Address + fblock->m_Size > cur + 1024) + { + WRITECHAR("-"); + } + + if (fblock->m_Address + fblock->m_Size > cur && + fblock->m_Address + fblock->m_Size < cur + 1024) + { + WRITECHAR("+"); + } + } + + Sys_Log("memmap.txt", "\n"); +} + +void Z_DisplayLevelMemory(int size, int surf, int block) +{ + Z_DumpMemMap_f(); + + //Yes, it should be divided by 1024, but I'm going for a safety margin + //by rounding down. + //Com_Printf("level memory used: %d KB\n", size / 1000); + //Z_CompactStats(size, surf, block); + Z_CompactStats(); +} + +void Z_DisplayLevelMemory(void) +{ +#ifdef _GAMECUBE + extern void R_SurfMramUsed(int &surface, int &block); + int surface, block; + R_SurfMramUsed(surface, block); + Z_DisplayLevelMemory(Z_GetLevelMemory(), surface, block); +#else + Z_DisplayLevelMemory(Z_GetLevelMemory(), 0, 0); +#endif +} + + +/* +======================== +CopyString + + NOTE: never write over the memory CopyString returns because + memory from a memstatic_t might be returned +======================== +*/ +char *CopyString( const char *in ) +{ + struct ZoneSingleChar + { + ZoneHeader header; +#ifdef _DEBUG + ZoneDebugHeader start; +#endif + char data[2]; +#ifdef _DEBUG + ZoneDebugFooter end; +#endif + }; + +#ifdef _DEBUG + static ZoneSingleChar empty = {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "\0", ZONE_MAGIC}; + static ZoneSingleChar numbers[10] = + { + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "0", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "1", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "2", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "3", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "4", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "5", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "6", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "7", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "8", ZONE_MAGIC}, + {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "9", ZONE_MAGIC}, + }; +#else + static ZoneSingleChar empty = {(TAG_STATIC << 25) | 2, "\0"}; + static ZoneSingleChar numbers[10] = + { + {(TAG_STATIC << 25) | 2, "0"}, + {(TAG_STATIC << 25) | 2, "1"}, + {(TAG_STATIC << 25) | 2, "2"}, + {(TAG_STATIC << 25) | 2, "3"}, + {(TAG_STATIC << 25) | 2, "4"}, + {(TAG_STATIC << 25) | 2, "5"}, + {(TAG_STATIC << 25) | 2, "6"}, + {(TAG_STATIC << 25) | 2, "7"}, + {(TAG_STATIC << 25) | 2, "8"}, + {(TAG_STATIC << 25) | 2, "9"}, + }; +#endif + + char *out; + + if (!in[0]) + { + return empty.data; + } + else if (!in[1]) + { + if (in[0] >= '0' && in[0] <= '9') + { + return numbers[in[0]-'0'].data; + } + } + + out = (char *) S_Malloc (strlen(in)+1); + strcpy (out, in); + +// Z_Label(out,in); + + return out; +} + +void Com_TouchMemory(void) +{ + // Stub function. Do nothing. + return; +} + + +qboolean Z_IsFromZone(void *pvAddress, memtag_t eTag) +{ + if(pvAddress >= s_PoolBase && pvAddress < (char*)s_PoolBase + s_PoolSize) { + return qtrue; + } + + return qfalse; +} + + +qboolean Z_IsFromTempPool(void *pvAddress) +{ + if(pvAddress >= s_TempAllocPool && pvAddress < s_TempAllocPool + + s_TempAllocPoint) { + return qtrue; + } + + return qfalse; +} + + +/* + Hunk emulation + + The emulation is pretty bad right now, we just use two tags: + TAG_HUNKALLOC and TAG_TEMP_HUNKALLOC, to represent the permanent and + temporary sides of the hunk respectively. We should make the + Hunk allocations tagged so we can do this better. +*/ +#ifdef _JK2MP + +void Hunk_Clear(void) +{ + Z_TagFree(TAG_TEMP_HUNKALLOC); + Z_TagFree(TAG_HUNKALLOC); +/* + Z_TagFree(TAG_HUNKALLOC); + Z_TagFree(TAG_BSP_HUNK); + Z_TagFree(TAG_BOT_HUNK); + Z_TagFree(TAG_RENDERER_HUNK); + Z_TagFree(TAG_SKELETON); + Z_TagFree(TAG_MODEL_OTHER); + Z_TagFree(TAG_MODEL_CHAR); + VM_Clear(); +*/ +} + + +//void *Hunk_Alloc( int size, ha_pref preference, memtag_t eTag ) +void *Hunk_Alloc(int size, ha_pref preference) +{ + return Z_Malloc(size, TAG_HUNKALLOC, qtrue); +/* + assert(eTag == TAG_HUNKALLOC || + eTag == TAG_BSP_HUNK || + eTag == TAG_BOT_HUNK || + eTag == TAG_RENDERER_HUNK || + eTag == TAG_SKELETON || + eTag == TAG_MODEL_OTHER || + eTag == TAG_MODEL_CHAR); + return Z_Malloc(size, eTag, qtrue); +*/ +} + + +void *Hunk_AllocateTempMemory(int size) +{ + return Z_Malloc(size, TAG_TEMP_HUNKALLOC, qtrue); +/* + return Z_Malloc(size, TAG_TEMP_HUNK, qtrue); +*/ +} + + +void Hunk_FreeTempMemory(void *buf) +{ + Z_Free(buf); +} + + +void Hunk_ClearTempMemory(void) +{ + Z_TagFree(TAG_TEMP_HUNKALLOC); +// Z_TagFree(TAG_TEMP_HUNK); +} + + +void Com_InitHunkMemory(void) +{ +} + + +int Hunk_MemoryRemaining(void) +{ + return 0; +} + + +void Hunk_ClearToMark(void) +{ +} + + +qboolean Hunk_CheckMark(void) +{ + return qfalse; +} + + +void Hunk_SetMark(void) +{ +} +#endif // _JK2MP + +/* +XBOXAPI +LPVOID +WINAPI +XMemAlloc(SIZE_T dwSize, DWORD dwAllocAttributes) +{ + return XMemAllocDefault(dwSize, dwAllocAttributes); +} +*/ + +/* + XTL Replacement functions + XMemAlloc + XMemFree + XMemSize + + Replacing these lets us intercept ALL memory allocation done by the XTL, and lets the + Zone take pretty much all available memory at startup +*/ +/* This still doesn't work. Numrous allocations still use internal functions, so there's + little benefit right now. + +XBOXAPI +LPVOID +WINAPI +XMemAlloc(SIZE_T dwSize, DWORD dwAllocAttributes) +{ + PXALLOC_ATTRIBUTES pAllocAttributes = (PXALLOC_ATTRIBUTES)&dwAllocAttributes; + LPVOID ptr = NULL; + + if (pAllocAttributes->dwMemoryType == XALLOC_MEMTYPE_HEAP) + { // Heap allocation + ptr = HeapAlloc(GetProcessHeap(), + pAllocAttributes->dwZeroInitialize ? HEAP_ZERO_MEMORY : 0, + dwSize); + if (pAllocAttributes->dwHeapTracksAttributes) + XSetAttributesOnHeapAlloc(ptr, dwAllocAttributes); + } + else + { // Physical allocation + // Map requested alignment to real alignment + ULONG_PTR ulAlign = 0; + DWORD dwProtect = 0; + + switch(pAllocAttributes->dwAlignment) + { + case XALLOC_PHYSICAL_ALIGNMENT_8K: + ulAlign = 8*1024; + break; + + case XALLOC_PHYSICAL_ALIGNMENT_16K: + ulAlign = 16*1024; + break; + + case XALLOC_PHYSICAL_ALIGNMENT_32K: + ulAlign = 32*1024; + break; + + default: + ulAlign = 4*1024; + break; + } + + if (pAllocAttributes->dwMemoryProtect & XALLOC_MEMPROTECT_READONLY) + dwProtect = PAGE_READONLY; + else + dwProtect = PAGE_READWRITE; + + if (pAllocAttributes->dwMemoryProtect & XALLOC_MEMPROTECT_NOCACHE) + dwProtect |= PAGE_NOCACHE; + if (pAllocAttributes->dwMemoryProtect & XALLOC_MEMPROTECT_WRITECOMBINE) + dwProtect |= PAGE_WRITECOMBINE; + + ptr = XPhysicalAlloc(dwSize, MAXULONG_PTR, ulAlign, dwProtect); + } + + return ptr; +} +*/ + +/* +XBOXAPI +VOID +WINAPI +XMemFree(PVOID pAddress, DWORD dwAllocAttributes) +{ + XMemFreeDefault(pAddress, dwAllocAttributes); +} +*/ + +/* +XBOXAPI +VOID +WINAPI +XMemFree(PVOID pAddress, DWORD dwAllocAttributes) +{ + PXALLOC_ATTRIBUTES pAllocAttributes = (PXALLOC_ATTRIBUTES)&dwAllocAttributes; + + if (pAllocAttributes->dwMemoryType == XALLOC_MEMTYPE_HEAP) + { // Heap pointer + HeapFree(GetProcessHeap(), 0, pAddress); + } + else + { // Physical pointer + XPhysicalFree(pAddress); + } +} +*/ + +/* +XBOXAPI +SIZE_T +WINAPI +XMemSize(PVOID pAddress, DWORD dwAllocAttributes) +{ + return XMemSizeDefault(pAddress, dwAllocAttributes); +} +*/ + +/* +XBOXAPI +SIZE_T +WINAPI +XMemSize(PVOID pAddress, DWORD dwAllocAttributes) +{ + PXALLOC_ATTRIBUTES pAllocAttributes = (PXALLOC_ATTRIBUTES)&dwAllocAttributes; + + if (pAllocAttributes->dwMemoryType == XALLOC_MEMTYPE_HEAP) + { // Heap pointer + return HeapSize(GetProcessHeap(), 0, pAddress); + } + else + { // Physical pointer + return XPhysicalSize(pAddress); + } +} +*/ diff --git a/code/qcommon/z_memman_pc.cpp b/code/qcommon/z_memman_pc.cpp new file mode 100644 index 0000000..54e2874 --- /dev/null +++ b/code/qcommon/z_memman_pc.cpp @@ -0,0 +1,958 @@ +// Created 2/3/03 by Brian Osman - split Zone code from common.cpp + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "../qcommon/sstring.h" + +#include "platform.h" + +#ifdef DEBUG_ZONE_ALLOCS +int giZoneSnaphotNum=0; +#define DEBUG_ZONE_ALLOC_OPTIONAL_LABEL_SIZE 256 +typedef sstring sDebugString_t; +#endif + +static void Z_Details_f(void); + +// define a string table of all mem tags... +// +#ifdef TAGDEF // itu? +#undef TAGDEF +#endif +#define TAGDEF(blah) #blah +static const char *psTagStrings[TAG_COUNT+1]= // +1 because TAG_COUNT will itself become a string here. Oh well. +{ + #include "../qcommon/tags.h" +}; + +// This handles zone memory allocation. +// It is a wrapper around malloc with a tag id and a magic number at the start + +#define ZONE_MAGIC 0x21436587 + +// if you change ANYTHING in this structure, be sure to update the tables below using DEF_STATIC... +// +typedef struct zoneHeader_s +{ + int iMagic; + memtag_t eTag; + int iSize; +struct zoneHeader_s *pNext; +struct zoneHeader_s *pPrev; + +#ifdef DEBUG_ZONE_ALLOCS + char sSrcFileBaseName[MAX_QPATH]; + int iSrcFileLineNum; + char sOptionalLabel[DEBUG_ZONE_ALLOC_OPTIONAL_LABEL_SIZE]; + int iSnapshotNumber; +#endif + +} zoneHeader_t; + +typedef struct +{ + int iMagic; + +} zoneTail_t; + +static inline zoneTail_t *ZoneTailFromHeader(zoneHeader_t *pHeader) +{ + return (zoneTail_t*) ( (char*)pHeader + sizeof(*pHeader) + pHeader->iSize ); +} + +#ifdef DETAILED_ZONE_DEBUG_CODE +map mapAllocatedZones; +#endif + + +typedef struct zoneStats_s +{ + int iCount; + int iCurrent; + int iPeak; + + // I'm keeping these updated on the fly, since it's quicker for cache-pool + // purposes rather than recalculating each time... + // + int iSizesPerTag [TAG_COUNT]; + int iCountsPerTag[TAG_COUNT]; + +} zoneStats_t; + +typedef struct zone_s +{ + zoneStats_t Stats; + zoneHeader_t Header; +} zone_t; + +cvar_t *com_validateZone; + +zone_t TheZone = {0}; + + + + +// Scans through the linked list of mallocs and makes sure no data has been overwritten + +int Z_Validate(void) +{ + int ret=0; + if(!com_validateZone || !com_validateZone->integer) + { + return ret; + } + + zoneHeader_t *pMemory = TheZone.Header.pNext; + while (pMemory) + { + #ifdef DETAILED_ZONE_DEBUG_CODE + // this won't happen here, but wtf? + int& iAllocCount = mapAllocatedZones[pMemory]; + if (iAllocCount <= 0) + { + Com_Error(ERR_FATAL, "Z_Validate(): Bad block allocation count!"); + return ret; + } + #endif + + if(pMemory->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone header!"); + return ret; + } + + // this block of code is intended to make sure all of the data is paged in + if (pMemory->eTag != TAG_IMAGE_T + && pMemory->eTag != TAG_MODEL_MD3 + && pMemory->eTag != TAG_MODEL_GLM + && pMemory->eTag != TAG_MODEL_GLA ) //don't bother with disk caches as they've already been hit or will be thrown out next + { + unsigned char *memstart = (unsigned char *)pMemory; + int totalSize = pMemory->iSize; + while (totalSize > 4096) + { + memstart += 4096; + ret += (int)(*memstart); // this fools the optimizer + totalSize -= 4096; + } + } + + + if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone tail!"); + return ret; + } + + pMemory = pMemory->pNext; + } + return ret; +} + + + +// static mem blocks to reduce a lot of small zone overhead +// +#pragma pack(push) +#pragma pack(1) +typedef struct +{ + zoneHeader_t Header; +// byte mem[0]; + zoneTail_t Tail; +} StaticZeroMem_t; + +typedef struct +{ + zoneHeader_t Header; + byte mem[2]; + zoneTail_t Tail; +} StaticMem_t; +#pragma pack(pop) + +const static StaticZeroMem_t gZeroMalloc = + { {ZONE_MAGIC, TAG_STATIC,0,NULL,NULL},{ZONE_MAGIC}}; + +#ifdef DEBUG_ZONE_ALLOCS +#define DEF_STATIC(_char) {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL, "",0,"",0},_char,'\0',{ZONE_MAGIC} +#else +#define DEF_STATIC(_char) {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL },_char,'\0',{ZONE_MAGIC} +#endif + +const static StaticMem_t gEmptyString = + { DEF_STATIC('\0') }; + +const static StaticMem_t gNumberString[] = { + { DEF_STATIC('0') }, + { DEF_STATIC('1') }, + { DEF_STATIC('2') }, + { DEF_STATIC('3') }, + { DEF_STATIC('4') }, + { DEF_STATIC('5') }, + { DEF_STATIC('6') }, + { DEF_STATIC('7') }, + { DEF_STATIC('8') }, + { DEF_STATIC('9') }, +}; + +qboolean gbMemFreeupOccured = qfalse; + +#ifdef DEBUG_ZONE_ALLOCS +void *_D_Z_Malloc ( int iSize, memtag_t eTag, qboolean bZeroit, const char *psFile, int iLine) +#else +void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int unusedAlign) +#endif +{ + gbMemFreeupOccured = qfalse; + + if (iSize == 0) + { + zoneHeader_t *pMemory = (zoneHeader_t *) &gZeroMalloc; + return &pMemory[1]; + } + + // Add in tracking info and round to a longword... (ignore longword aligning now we're not using contiguous blocks) + // +// int iRealSize = (iSize + sizeof(zoneHeader_t) + sizeof(zoneTail_t) + 3) & 0xfffffffc; + int iRealSize = (iSize + sizeof(zoneHeader_t) + sizeof(zoneTail_t)); + + // Allocate a chunk... + // + zoneHeader_t *pMemory = NULL; + while (pMemory == NULL) + { + #ifdef _WIN32 + if (gbMemFreeupOccured) + { + Sleep(1000); // sleep for a second, so Windows has a chance to shuffle mem to de-swiss-cheese it + } + #endif + + if (bZeroit) { + pMemory = (zoneHeader_t *) calloc ( iRealSize, 1 ); + } else { + pMemory = (zoneHeader_t *) malloc ( iRealSize ); + } + if (!pMemory) + { + // new bit, if we fail to malloc memory, try dumping some of the cached stuff that's non-vital and try again... + // + + // ditch the BSP cache... + // + if (CM_DeleteCachedMap(qfalse)) + { + gbMemFreeupOccured = qtrue; + continue; // we've just ditched a whole load of memory, so try again with the malloc + } + + + // ditch any sounds not used on this level... + // + extern qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel); + if (SND_RegisterAudio_LevelLoadEnd(qtrue)) + { + gbMemFreeupOccured = qtrue; + continue; // we've dropped at least one sound, so try again with the malloc + } + + + // ditch any image_t's (and associated GL texture mem) not used on this level... + // + extern qboolean RE_RegisterImages_LevelLoadEnd(void); + if (RE_RegisterImages_LevelLoadEnd()) + { + gbMemFreeupOccured = qtrue; + continue; // we've dropped at least one image, so try again with the malloc + } + + + // ditch the model-binaries cache... (must be getting desperate here!) + // + extern qboolean RE_RegisterModels_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel); + if (RE_RegisterModels_LevelLoadEnd(qtrue)) + { + gbMemFreeupOccured = qtrue; + continue; + } + + // as a last panic measure, dump all the audio memory, but not if we're in the audio loader + // (which is annoying, but I'm not sure how to ensure we're not dumping any memory needed by the sound + // currently being loaded if that was the case)... + // + // note that this keeps querying until it's freed up as many bytes as the requested size, but freeing + // several small blocks might not mean that one larger one is satisfiable after freeup, however that'll + // just make it go round again and try for freeing up another bunch of blocks until the total is satisfied + // again (though this will have freed twice the requested amount in that case), so it'll either work + // eventually or not free up enough and drop through to the final ERR_DROP. No worries... + // + extern qboolean gbInsideLoadSound; + extern int SND_FreeOldestSound(void); // I had to add a void-arg version of this because of link issues, sigh + if (!gbInsideLoadSound) + { + int iBytesFreed = SND_FreeOldestSound(); + if (iBytesFreed) + { + int iTheseBytesFreed = 0; + while ( (iTheseBytesFreed = SND_FreeOldestSound()) != 0) + { + iBytesFreed += iTheseBytesFreed; + if (iBytesFreed >= iRealSize) + break; // early opt-out since we've managed to recover enough (mem-contiguity issues aside) + } + gbMemFreeupOccured = qtrue; + continue; + } + } + + // sigh, dunno what else to try, I guess we'll have to give up and report this as an out-of-mem error... + // + // findlabel: "recovermem" + + Com_Printf(S_COLOR_RED"Z_Malloc(): Failed to alloc %d bytes (TAG_%s) !!!!!\n", iSize, psTagStrings[eTag]); + Z_Details_f(); + Com_Error(ERR_FATAL,"(Repeat): Z_Malloc(): Failed to alloc %d bytes (TAG_%s) !!!!!\n", iSize, psTagStrings[eTag]); + return NULL; + } + } + + +#ifdef DEBUG_ZONE_ALLOCS + extern char *Filename_WithoutPath(const char *psFilename); + + Q_strncpyz(pMemory->sSrcFileBaseName, Filename_WithoutPath(psFile), sizeof(pMemory->sSrcFileBaseName)); + pMemory->iSrcFileLineNum = iLine; + pMemory->sOptionalLabel[0] = '\0'; + pMemory->iSnapshotNumber = giZoneSnaphotNum; +#endif + + // Link in + pMemory->iMagic = ZONE_MAGIC; + pMemory->eTag = eTag; + pMemory->iSize = iSize; + pMemory->pNext = TheZone.Header.pNext; + TheZone.Header.pNext = pMemory; + if (pMemory->pNext) + { + pMemory->pNext->pPrev = pMemory; + } + pMemory->pPrev = &TheZone.Header; + // + // add tail... + // + ZoneTailFromHeader(pMemory)->iMagic = ZONE_MAGIC; + + // Update stats... + // + TheZone.Stats.iCurrent += iSize; + TheZone.Stats.iCount++; + TheZone.Stats.iSizesPerTag [eTag] += iSize; + TheZone.Stats.iCountsPerTag [eTag]++; + + if (TheZone.Stats.iCurrent > TheZone.Stats.iPeak) + { + TheZone.Stats.iPeak = TheZone.Stats.iCurrent; + } + +#ifdef DETAILED_ZONE_DEBUG_CODE + mapAllocatedZones[pMemory]++; +#endif + + Z_Validate(); // check for corruption + + void *pvReturnMem = &pMemory[1]; + return pvReturnMem; +} + +// used during model cacheing to save an extra malloc, lets us morph the disk-load buffer then +// just not fs_freefile() it afterwards. +// +void Z_MorphMallocTag( void *pvAddress, memtag_t eDesiredTag ) +{ + zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; + + if (pMemory->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_MorphMallocTag(): Not a valid zone header!"); + return; // won't get here + } + + // DEC existing tag stats... + // +// TheZone.Stats.iCurrent - unchanged +// TheZone.Stats.iCount - unchanged + TheZone.Stats.iSizesPerTag [pMemory->eTag] -= pMemory->iSize; + TheZone.Stats.iCountsPerTag [pMemory->eTag]--; + + // morph... + // + pMemory->eTag = eDesiredTag; + + // INC new tag stats... + // +// TheZone.Stats.iCurrent - unchanged +// TheZone.Stats.iCount - unchanged + TheZone.Stats.iSizesPerTag [pMemory->eTag] += pMemory->iSize; + TheZone.Stats.iCountsPerTag [pMemory->eTag]++; +} + + +static int Zone_FreeBlock(zoneHeader_t *pMemory) +{ + const int iSize = pMemory->iSize; + if (pMemory->eTag != TAG_STATIC) // belt and braces, should never hit this though + { + // Update stats... + // + TheZone.Stats.iCount--; + TheZone.Stats.iCurrent -= pMemory->iSize; + TheZone.Stats.iSizesPerTag [pMemory->eTag] -= pMemory->iSize; + TheZone.Stats.iCountsPerTag [pMemory->eTag]--; + + // Sanity checks... + // + assert(pMemory->pPrev->pNext == pMemory); + assert(!pMemory->pNext || (pMemory->pNext->pPrev == pMemory)); + + // Unlink and free... + // + pMemory->pPrev->pNext = pMemory->pNext; + if(pMemory->pNext) + { + pMemory->pNext->pPrev = pMemory->pPrev; + } + + //debugging double frees + pMemory->iMagic = 'FREE'; + free (pMemory); + + + #ifdef DETAILED_ZONE_DEBUG_CODE + // this has already been checked for in execution order, but wtf? + int& iAllocCount = mapAllocatedZones[pMemory]; + if (iAllocCount == 0) + { + Com_Error(ERR_FATAL, "Zone_FreeBlock(): Double-freeing block!"); + return -1; + } + iAllocCount--; + #endif + } + return iSize; +} + +// stats-query function to to see if it's our malloc +// returns block size if so +qboolean Z_IsFromZone(void *pvAddress, memtag_t eTag) +{ + zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; +#if 1 //debugging double free + if (pMemory->iMagic == 'FREE') + { + Com_Printf("Z_IsFromZone(%x): Ptr has been freed already!(%9s)\n",pvAddress,pvAddress); + return qfalse; + } +#endif + if (pMemory->iMagic != ZONE_MAGIC) + { + return qfalse; + } + + //looks like it is from our zone, let's double check the tag + + if (pMemory->eTag != eTag) + { + return qfalse; + } + + return pMemory->iSize; +} + +// stats-query function to ask how big a malloc is... +// +int Z_Size(void *pvAddress) +{ + zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; + + if (pMemory->eTag == TAG_STATIC) + { + return 0; // kind of + } + + if (pMemory->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Size(): Not a valid zone header!"); + return 0; // won't get here + } + + return pMemory->iSize; +} + + +#ifdef DEBUG_ZONE_ALLOCS +void _D_Z_Label(const void *pvAddress, const char *psLabel) +{ + zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; + + if (pMemory->eTag == TAG_STATIC) + { + return; + } + + if (pMemory->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "_D_Z_Label(): Not a valid zone header!"); + } + + Q_strncpyz( pMemory->sOptionalLabel, psLabel, sizeof(pMemory->sOptionalLabel)); + pMemory->sOptionalLabel[ sizeof(pMemory->sOptionalLabel)-1 ] = '\0'; +} +#endif + + + +// Frees a block of memory... +// +int Z_Free(void *pvAddress) +{ + if (!TheZone.Stats.iCount) + { + //Com_Error(ERR_FATAL, "Z_Free(): Zone has been cleard already!"); + Com_Printf("Z_Free(%x): Zone has been cleard already!\n",pvAddress); + return -1; + } + + zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; + +#if 1 //debugging double free + if (pMemory->iMagic == 'FREE') + { + Com_Error(ERR_FATAL, "Z_Free(%s): Block already-freed, or not allocated through Z_Malloc!",pvAddress); + return -1; + } +#endif + + if (pMemory->eTag == TAG_STATIC) + { + return 0; + } + + #ifdef DETAILED_ZONE_DEBUG_CODE + // + // check this error *before* barfing on bad magics... + // + int& iAllocCount = mapAllocatedZones[pMemory]; + if (iAllocCount <= 0) + { + Com_Error(ERR_FATAL, "Z_Free(): Block already-freed, or not allocated through Z_Malloc!"); + return -1; + } + #endif + + if (pMemory->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone header!"); + return -1; + } + if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC) + { + Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone tail!"); + return -1; + } + + return Zone_FreeBlock(pMemory); +} + + +int Z_MemSize(memtag_t eTag) +{ + return TheZone.Stats.iSizesPerTag[eTag]; +} + +// Frees all blocks with the specified tag... +// +void Z_TagFree(memtag_t eTag) +{ +//#ifdef _DEBUG +// int iZoneBlocks = TheZone.Stats.iCount; +//#endif + + zoneHeader_t *pMemory = TheZone.Header.pNext; + while (pMemory) + { + zoneHeader_t *pNext = pMemory->pNext; + if ( (eTag == TAG_ALL) || (pMemory->eTag == eTag)) + { + Zone_FreeBlock(pMemory); + } + pMemory = pNext; + } + +// these stupid pragmas don't work here???!?!?! +// +//#ifdef _DEBUG +//#pragma warning( disable : 4189) +// int iBlocksFreed = iZoneBlocks - TheZone.Stats.iCount; +//#pragma warning( default : 4189) +//#endif +} + + +#ifdef DEBUG_ZONE_ALLOCS +void *_D_S_Malloc ( int iSize, const char *psFile, int iLine) +{ + return _D_Z_Malloc( iSize, TAG_SMALL, qfalse, psFile, iLine ); +} +#else +void *S_Malloc( int iSize ) +{ + return Z_Malloc( iSize, TAG_SMALL, qfalse); +} +#endif + + +#ifdef _DEBUG +static void Z_MemRecoverTest_f(void) +{ + // needs to be in _DEBUG only, not good for final game! + // + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: zone_memrecovertest max2alloc\n" ); + return; + } + + int iMaxAlloc = 1024*1024*atoi( Cmd_Argv(1) ); + int iTotalMalloc = 0; + while (1) + { + const int iThisMalloc = 5* (1024 * 1024); + Z_Malloc(iThisMalloc, TAG_SPECIAL_MEM_TEST, qfalse); // and lose, just to consume memory + iTotalMalloc += iThisMalloc; + + if (gbMemFreeupOccured || (iTotalMalloc >= iMaxAlloc) ) + break; + } + + Z_TagFree(TAG_SPECIAL_MEM_TEST); +} +#endif + +// Gives a summary of the zone memory usage + +static void Z_Stats_f(void) +{ + Com_Printf("\nThe zone is using %d bytes (%.2fMB) in %d memory blocks\n", + TheZone.Stats.iCurrent, + (float)TheZone.Stats.iCurrent / 1024.0f / 1024.0f, + TheZone.Stats.iCount + ); + + Com_Printf("The zone peaked at %d bytes (%.2fMB)\n", + TheZone.Stats.iPeak, + (float)TheZone.Stats.iPeak / 1024.0f / 1024.0f + ); +} + +// Gives a detailed breakdown of the memory blocks in the zone +// +static void Z_Details_f(void) +{ + + Com_Printf("---------------------------------------------------------------------------\n"); + Com_Printf("%20s %9s\n","Zone Tag","Bytes"); + Com_Printf("%20s %9s\n","--------","-----"); + for (int i=0; i LabelRefCount_t; // yet another place where Gil's tring class works and MS's doesn't +typedef map TagBlockLabels_t; + TagBlockLabels_t AllTagBlockLabels; +#pragma warning (disable:4503) // decorated name length xceeded, name was truncated +static void Z_Snapshot_f(void) +{ + AllTagBlockLabels.clear(); + + zoneHeader_t *pMemory = TheZone.Header.pNext; + while (pMemory) + { + AllTagBlockLabels[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]++; + pMemory = pMemory->pNext; + } + + giZoneSnaphotNum++; + Com_Printf("Ok. ( Current snapshot num is now %d )\n",giZoneSnaphotNum); +} + +static void Z_TagDebug_f(void) +{ + TagBlockLabels_t AllTagBlockLabels_Local; + qboolean bSnapShotTestActive = qfalse; + + memtag_t eTag = TAG_ALL; + + const char *psTAGName = Cmd_Argv(1); + if (psTAGName[0]) + { + // check optional arg... + // + if (!Q_stricmp(psTAGName,"#snap")) + { + bSnapShotTestActive = qtrue; + + AllTagBlockLabels_Local = AllTagBlockLabels; // horrible great STL copy + + psTAGName = Cmd_Argv(2); + } + + if (psTAGName[0]) + { + // skip over "tag_" if user supplied it... + // + if (!Q_stricmpn(psTAGName,"TAG_",4)) + { + psTAGName += 4; + } + + // see if the user specified a valid tag... + // + for (int i=0; i', e.g. TAG_GHOUL2, TAG_ALL (careful!)\n"); + return; + } + + Com_Printf("Dumping debug data for tag \"%s\"...%s\n\n",psTagStrings[eTag], bSnapShotTestActive?"( since snapshot only )":""); + + Com_Printf("%8s"," "); // to compensate for code further down: Com_Printf("(%5d) ",iBlocksListed); + if (eTag == TAG_ALL) + { + Com_Printf("%20s ","Zone Tag"); + } + Com_Printf("%9s\n","Bytes"); + Com_Printf("%8s"," "); + if (eTag == TAG_ALL) + { + Com_Printf("%20s ","--------"); + } + Com_Printf("%9s\n","-----"); + + + if (bSnapShotTestActive) + { + // dec ref counts in last snapshot for all current blocks (which will make new stuff go negative) + // + zoneHeader_t *pMemory = TheZone.Header.pNext; + while (pMemory) + { + if (pMemory->eTag == eTag || eTag == TAG_ALL) + { + AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]--; + } + pMemory = pMemory->pNext; + } + } + + // now dump them out... + // + int iBlocksListed = 0; + int iTotalSize = 0; + zoneHeader_t *pMemory = TheZone.Header.pNext; + while (pMemory) + { + if ( (pMemory->eTag == eTag || eTag == TAG_ALL) + && (!bSnapShotTestActive || (pMemory->iSnapshotNumber == giZoneSnaphotNum && AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel] <0) ) + ) + { + float fSize = (float)(pMemory->iSize) / 1024.0f / 1024.0f; + int iSize = fSize; + int iRemainder = 100.0f * (fSize - floor(fSize)); + + Com_Printf("(%5d) ",iBlocksListed); + + if (eTag == TAG_ALL) + { + Com_Printf("%20s",psTagStrings[pMemory->eTag]); + } + + Com_Printf(" %9d (%2d.%02dMB) File: \"%s\", Line: %d\n", + pMemory->iSize, + iSize,iRemainder, + pMemory->sSrcFileBaseName, + pMemory->iSrcFileLineNum + ); + if (pMemory->sOptionalLabel[0]) + { + Com_Printf("( Label: \"%s\" )\n",pMemory->sOptionalLabel); + } + iBlocksListed++; + iTotalSize += pMemory->iSize; + + if (bSnapShotTestActive) + { + // bump ref count so we only 1 warning per new string, not for every one sharing that label... + // + AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]++; + } + } + pMemory = pMemory->pNext; + } + + Com_Printf("( %d blocks listed, %d bytes (%.2fMB) total )\n",iBlocksListed, iTotalSize, (float)iTotalSize / 1024.0f / 1024.0f); +} +#endif + +// Shuts down the zone memory system and frees up all memory +void Com_ShutdownZoneMemory(void) +{ + Cmd_RemoveCommand("zone_stats"); + Cmd_RemoveCommand("zone_details"); + +#ifdef _DEBUG + Cmd_RemoveCommand("zone_memrecovertest"); +#endif + +#ifdef DEBUG_ZONE_ALLOCS + Cmd_RemoveCommand("zone_tagdebug"); + Cmd_RemoveCommand("zone_snapshot"); +#endif + + if(TheZone.Stats.iCount) + { + //Com_Printf("Automatically freeing %d blocks making up %d bytes\n", TheZone.Stats.iCount, TheZone.Stats.iCurrent); + Z_TagFree(TAG_ALL); + + assert(!TheZone.Stats.iCount); + assert(!TheZone.Stats.iCurrent); + } +} + +// Initialises the zone memory system + +void Com_InitZoneMemory( void ) +{ + Com_Printf("Initialising zone memory .....\n"); + + memset(&TheZone, 0, sizeof(TheZone)); + TheZone.Header.iMagic = ZONE_MAGIC; + + com_validateZone = Cvar_Get("com_validateZone", "0", 0); + + Cmd_AddCommand("zone_stats", Z_Stats_f); + Cmd_AddCommand("zone_details", Z_Details_f); + +#ifdef _DEBUG + Cmd_AddCommand("zone_memrecovertest", Z_MemRecoverTest_f); +#endif + + +#ifdef DEBUG_ZONE_ALLOCS + Cmd_AddCommand("zone_tagdebug", Z_TagDebug_f); + Cmd_AddCommand("zone_snapshot", Z_Snapshot_f); +#endif +} + + + + +/* +======================== +CopyString + + NOTE: never write over the memory CopyString returns because + memory from a memstatic_t might be returned +======================== +*/ +char *CopyString( const char *in ) { + char *out; + + if (!in[0]) { + return ((char *)&gEmptyString) + sizeof(zoneHeader_t); + } + else if (!in[1]) { + if (in[0] >= '0' && in[0] <= '9') { + return ((char *)&gNumberString[in[0]-'0']) + sizeof(zoneHeader_t); + } + } + + out = (char *) S_Malloc (strlen(in)+1); + strcpy (out, in); + + Z_Label(out,in); + + return out; +} + + +/* +=============== +Com_TouchMemory + +Touch all known used data to make sure it is paged in +=============== +*/ +void Com_TouchMemory( void ) { + int start, end; + int i, j; + int sum; + int totalTouched; + + Z_Validate(); + + start = Sys_Milliseconds(); + + sum = 0; + totalTouched=0; + + zoneHeader_t *pMemory = TheZone.Header.pNext; + while (pMemory) + { + byte *pMem = (byte *) &pMemory[1]; + j = pMemory->iSize >> 2; + for (i=0; iiSize; + pMemory = pMemory->pNext; + } + + end = Sys_Milliseconds(); + + //Com_Printf( "Com_TouchMemory: %i bytes, %i msec\n", totalTouched, end - start ); +} + + diff --git a/code/ragl/graph_region.h b/code/ragl/graph_region.h new file mode 100644 index 0000000..a35c460 --- /dev/null +++ b/code/ragl/graph_region.h @@ -0,0 +1,419 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Graph Region +// ------------ +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_GRAPH_REGION_INC) +#define RATL_GRAPH_REGION_INC + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if defined(RA_DEBUG_LINKING) + #pragma message("...including graph_region.h") +#endif +#if !defined(RAGL_COMMON_INC) + #include "ragl_common.h" +#endif +#if !defined(RAGL_GRAPH_VS_INC) + #include "graph_vs.h" +#endif + +namespace ragl +{ + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Graph Region Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class graph_region : public ratl::ratl_base +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + NULL_REGION = -1, + NULL_EDGE = -1, + CAPACITY = MAXREGIONS + }; + + + //////////////////////////////////////////////////////////////////////////////////// + // Some Public Type Defines + //////////////////////////////////////////////////////////////////////////////////// + typedef ragl::graph_vs TGraph; + typedef ratl::vector_vs TRegions; + typedef ratl::vector_vs TRegionEdge; // List Of All Edges Which Connect RegionA<->RegionB + typedef ratl::pool_vs TEdges; // Pool Of All RegionEdges + typedef ratl::grid2_vs TLinks; // Graph Of Links From Region To Region, Each Points To A RegionEdge + typedef ratl::bits_vs TClosed; + + + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + graph_region(TGraph& Graph) : mGraph(Graph) + { + clear(); + } + ~graph_region() + { + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Clear Out All Temp Data So We Can Recalculate Regions + //////////////////////////////////////////////////////////////////////////////////// + void clear() + { + mRegions.resize(0, (int)NULL_REGION); + mRegions.resize(MAXNODES, (int)NULL_REGION); + mRegionCount = 0; + mReservedRegionCount = 0; + + mLinks.init(NULL_EDGE); + + for (int i=0; i= (MAXREGIONS-1) ) + {//stop adding points, we're full, you MUST increase MAXREGIONS for this to work + return NULL_REGION; + } + mReservedRegionCount ++; + mRegionCount ++; + return (mRegionCount); + } + + //////////////////////////////////////////////////////////////////////////////////// + // assign_region + // + // Allows a user to pre-allocate a special region for a group of points + //////////////////////////////////////////////////////////////////////////////////// + void assign_region(int NodeIndex, int RegionIndex) + { + mRegions[NodeIndex] = RegionIndex; + } + + + + + + + //////////////////////////////////////////////////////////////////////////////////// + // Define Regions + // + // Scan through all the nodes (calling the depth first recursive traversal below), + // and mark regions of nodes which can traverse to one another without needing to check + // for valid edges. + // + //////////////////////////////////////////////////////////////////////////////////// + bool find_regions(const typename TGraph::user& user) + { + int CurNodeIndex; + for (TGraph::TNodes::iterator i=mGraph.nodes_begin(); i!=mGraph.nodes_end(); i++) + { + CurNodeIndex = i.index(); + if (mRegions[CurNodeIndex] == NULL_REGION) + { + assert(mRegionCount < (MAXREGIONS-1)); + if (mRegionCount >= (MAXREGIONS-1) ) + {//stop adding points, we're full, you MUST increase MAXREGIONS for this to work + return false; + } + mRegionCount ++; // Allocate The New Region + assign(CurNodeIndex, user); // Assign All Points To It + } + } + mRegionCount ++; // Size is actually 1 greater than the number of regions + return true; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Search For All Possible Edges Which Connect Regions + // + // Once called, this class will have reference data for how to get from one region + // to another. + //////////////////////////////////////////////////////////////////////////////////// + bool find_region_edges() + { + int RegionA; + int RegionB; + int RegionLink; + bool Success = true; + bool ReservedRegionLink; + + for (int indexA=0; indexA0); + for (int j=0; j TTriangulation +// TTriangulation MyTriangulation(mMyGraph); +// +// Next, you are free to call any of the public functions in any order, but the best use +// is to call them in this order: +// +// MyTriangulation.insertion_hull(); +// MyTriangulation.delaunay_edge_flip(); +// MyTriangulation.alpha_shape(MyGraphUser, , ); +// +// For documentation on the above functions, look at their def below. Also, the doc on +// the Graph User class is in graph_vs.h +// +// +// Finally, when you are ready, call the finish() function. That will populate your +// graph (which has not been altered in any way up until now). After calling finish() +// you can dump the triangulation class, as it has done it's job and all the data is +// now stored in the class. +// +// MyTriangulation.finish(); +// +// +// +// +// How Does It Work? (Overview) +// ----------------------------- +// The details of how each step works are outlined below, however, here is the general +// idea: +// +// - Call insertion hull to generate a "rough and dirty" triangulation of the point set. +// The algorithm is relativly fast, and as a handy bi-product, generates the convex +// hull of the points. The resulting mesh is ugly though. You probably won't want +// to use it in the rough state. The basic idea of this algorithm is to iterativly +// add points which have been presorted along the x-axis into the triangulation. It +// is easy to do so, because you always know it will be on the right side of any edge +// it needs to connect with. +// +// - Now that you have a functional triangulation with edges and faces, there is fairly +// simple and fast algorithm to "clean it up" called EdgeFlipping. The idea is simple. +// Just scan through the edges, if you find one that is "bad", flip it! Continue until +// you find no "bad" edges. NOTE: This algorithm can lock up if any four points are +// colinear! +// +// - Finally, Alpha Shape is just a simple prune scan of the edges for anything that is +// too big or too small. This step is totally optional. +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_GRAPH_TRIANGULATE_INC) +#define RATL_GRAPH_TRIANGULATE_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if defined(RA_DEBUG_LINKING) + #pragma message("...including graph_triangulate.h") +#endif +#if !defined(RAGL_COMMON_INC) + #include "ragl_common.h" +#endif +#if !defined(RAGL_GRAPH_VS_INC) + #include "graph_vs.h" +#endif +#if !defined(RATL_LIST_VS_INC) + #include "..\Ratl\list_vs.h" +#endif +namespace ragl +{ + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Graph Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class graph_triangulate : public ratl::ratl_base +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = MAXNODES, + MAXFACES = MAXEDGES*2, + NULLEDGE = -1 + }; + + typedef graph_vs TGraph; + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + graph_triangulate(TGraph& Graph) : mGraph(Graph), mHull(), mHullIter(mHull.begin()) + { + mLinks.init(0); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Clear Out All Temp Data So We Can Triangulate Again + //////////////////////////////////////////////////////////////////////////////////// + void clear() + { + mLinks.init(0); + mEdges.clear(); + mFaces.clear(); + + mHull.clear(); + mHullIter = mHull.begin(); + + mSortNodes.clear(); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Insertion Hull + // + // This is a "quick and dirty" triangulation technique. It does not give you a very + // nice looking or terribly useful mesh, but it is a good place to start. Once + // you have an insertion hull triangulation, you can call delauny_edge_flip() to + // clean it up some. + // + // This algorithm's complexity isbounded in the worst case where all the points in + // the mesh are on the "hull", in which case it is O(n^2). However the number of + // points on the hull for most common point clouds is more likely to be log n. + // + //////////////////////////////////////////////////////////////////////////////////// + void insertion_hull() + { + assert(mGraph.size_nodes()>3); // We Need More Than 3 Points To Triangulate + + // STEP ONE: Sort all points along the x axis in increasing order + //---------------------------------------------------------------- + // COMPLEXITY: O(n log n) Heapsort + + sort_points(); + + + + // STEP TWO: Manually constructe the first face of the triangulation out of the 3 rightmost points + //-------------------------------------------------------------------------------------------------- + // COMPLEXITY: O(1) + + add_face(mSortNodes[0].mNodeHandle, mSortNodes[1].mNodeHandle, mSortNodes[2].mNodeHandle); + + + + // STEP THREE: Add each remaining point to the hull, constructing new faces as we go + //----------------------------------------------------------------------------------- + // COMPLEXITY: O(n*c) (n = num nodes, c = num nodes on hull) + + for (int i=3; i CullEdges; + int nEdge; + TEdges::iterator stop=mEdges.end(); + for (TEdges::iterator it=mEdges.begin(); it!=mEdges.end(); it++) + { + if (!(*it).mOnHull) + { + edge& EdgeAt = *it; + face& FaceR = mFaces[EdgeAt.mRight]; + face& FaceL = mFaces[EdgeAt.mLeft]; + +// int Edge = mEdges.index_to_handle(it.index()); + int R = FaceR.opposing_node(EdgeAt.mA, EdgeAt.mB); + int L = FaceL.opposing_node(EdgeAt.mA, EdgeAt.mB); + int RInd = mGraph.node_index(R); + int LInd = mGraph.node_index(L); + + TNODE& PtA = mGraph.get_node(EdgeAt.mA); + TNODE& PtB = mGraph.get_node(EdgeAt.mB); + TNODE& PtR = mGraph.get_node(R); + TNODE& PtL = mGraph.get_node(L); + + + if ( + (user.on_same_floor(PtR, PtL)) && + (mLinks.get(RInd, LInd)==0) && + (mLinks.get(LInd, RInd)==0) && + (!user.on_same_floor(PtL, PtA) || !user.on_same_floor(PtL, PtB)) + ) + { + nEdge= mEdges.alloc(); + + mEdges[nEdge].mA = R; + mEdges[nEdge].mB = L; + mEdges[nEdge].mHullLoc = mHullIter; + mEdges[nEdge].mOnHull = true; + mEdges[nEdge].mFlips = 0; + mEdges[nEdge].mLeft = 0; + mEdges[nEdge].mRight = 0; + + + mLinks.get(RInd, LInd) = nEdge; + mLinks.get(LInd, RInd) = nEdge; + } + + if (!user.on_same_floor(PtA, PtB)) + { + mLinks.get(mGraph.node_index(EdgeAt.mA), mGraph.node_index(EdgeAt.mB)) = 0; + mLinks.get(mGraph.node_index(EdgeAt.mB), mGraph.node_index(EdgeAt.mA)) = 0; + + CullEdges.push_back(it.index()); + } + } + } + + for (int i=0; i CullEdges; + float cost; + for (TEdges::iterator it=mEdges.begin(); it!=mEdges.end(); it++) + { + cost = user.cost(mGraph.get_node((*it).mA), mGraph.get_node((*it).mB)); + if (costmax) + { + mLinks.get(mGraph.node_index((*it).mA), mGraph.node_index((*it).mB)) = 0; + mLinks.get(mGraph.node_index((*it).mB), mGraph.node_index((*it).mA)) = 0; + + CullEdges.push_back(it.index()); + } + } + + for (int i=0; i THull; + typedef typename ratl::list_vs::iterator THullIter; + typedef typename ratl::grid2_vs TLinks; + + + //////////////////////////////////////////////////////////////////////////////////// + // The Local Edge Class + // + // RIGHT + // B<-<-<-<-<-<-A + // LEFT + // + //////////////////////////////////////////////////////////////////////////////////// + class edge + { + public: + int mA; + int mB; + + int mLeft; + int mRight; + int mFlips; + + THullIter mHullLoc; + bool mOnHull; + + void flip_face(int OldFace, int NewFace) + { + assert(mRight!=mLeft); + assert(mLeft!=NewFace && mRight!=NewFace); + if (mLeft==OldFace) + { + mLeft=NewFace; + } + else + { + assert(mRight==OldFace); + mRight = NewFace; + } + assert(mRight!=mLeft); + } + void verify(int PtA, int PtB, int Edge) + { + assert(PtA==mA || PtA==mB); + assert(PtB==mA || PtB==mB); + assert(mRight==Edge || mLeft==Edge); + assert(mRight!=mLeft); + assert(mA!=mB); + } + void verify(int PtA, int PtB, int PtC, int Edge) + { + assert((PtC==mA && (PtA==mB || PtB==mB)) || (PtC==mB && (PtA==mA || PtB==mA))); + + assert(mRight==Edge || mLeft==Edge); + assert(mRight!=mLeft); + assert(mA!=mB); + } + }; + + //////////////////////////////////////////////////////////////////////////////////// + // The Local Face Class + // + // _ C + // /| \ + // LEFT/ \RIGHT + // / \ + // B-<-<-<-<-A + // BOTTOM + // + //////////////////////////////////////////////////////////////////////////////////// + class face + { + public: + int mA; + int mB; + int mC; + + int mLeft; + int mRight; + int mBottom; + + int mFlips; + + int& opposing_node(int A, int B) + { + if (mA!=A && mA!=B) + { + return mA; + } + if (mB!=A && mB!=B) + { + return mB; + } + assert(mC!=A && mC!=B); + return mC; + } + + int& relative_left(int edge) + { + if (edge==mLeft) + { + return mRight; + } + if (edge==mRight) + { + return mBottom; + } + assert(edge==mBottom); // If you hit this assert, then the edge is not in this face + return mLeft; + } + int& relative_right(int edge) + { + if (edge==mLeft) + { + return mBottom; + } + if (edge==mRight) + { + return mLeft; + } + assert(edge==mBottom); // If you hit this assert, then the edge is not in this face + return mRight; + } + }; + + //////////////////////////////////////////////////////////////////////////////////// + // The Sort Node Class + // + // Used To Sort Nodes In Increasing X Order + //////////////////////////////////////////////////////////////////////////////////// + class sort_node + { + public: + bool operator<(const sort_node& r) const + { + return ((*r.mNodePointer)[0] < (*mNodePointer)[0]); + } + + int mNodeHandle; + TNODE* mNodePointer; + }; + + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + typedef typename ratl::handle_pool_vs TEdges; + typedef typename ratl::handle_pool_vs::iterator TEdgesIter; + typedef typename ratl::handle_pool_vs TFaces; + typedef typename ratl::vector_vs TSortNodes; + + + TGraph& mGraph; // A Reference To The Graph Points To Triangulate + + TLinks mLinks; + TEdges mEdges; + TFaces mFaces; + + THull mHull; // The Convex Hull + THullIter mHullIter; + + TSortNodes mSortNodes; // Need To Presort Nodes On (x-Axis) For Insertion Hull + sort_node mSortNode; + + + + +private: + //////////////////////////////////////////////////////////////////////////////////// + // Copy All The Graph Nodes To Our Sort Node Class And Run Heap Sort + //////////////////////////////////////////////////////////////////////////////////// + void sort_points() + { + mSortNodes.clear(); + for (TGraph::TNodes::iterator i=mGraph.nodes_begin(); i!=mGraph.nodes_end(); i++) + { + mSortNode.mNodeHandle = mGraph.node_handle(i); + mSortNode.mNodePointer = &(*i); + mSortNodes.push_back(mSortNode); + } + mSortNodes.sort(); + + } + + //////////////////////////////////////////////////////////////////////////////////// + // Create A New Edge A->B, And Fix Up The Face + //////////////////////////////////////////////////////////////////////////////////// + int add_edge(int A, int B, int Face=0, bool OnHull=true) + { + assert(A!=B ); + + int nEdge = mLinks.get(mGraph.node_index(A), mGraph.node_index(B)); + + // Apparently This Edge Does Not Exist, So Make A New One + //-------------------------------------------------------- + if (nEdge==0) + { + nEdge= mEdges.alloc(); + + mHull.insert_after(mHullIter, nEdge); + assert(mHullIter!=mHull.end()); + + mEdges[nEdge].mA = A; + mEdges[nEdge].mB = B; + mEdges[nEdge].mHullLoc = mHullIter; + mEdges[nEdge].mOnHull = true; + mEdges[nEdge].mFlips = 0; + mEdges[nEdge].mLeft = 0; + mEdges[nEdge].mRight = 0; + + + mLinks.get(mGraph.node_index(A), mGraph.node_index(B)) = nEdge; + mLinks.get(mGraph.node_index(B), mGraph.node_index(A)) = nEdge; + } + + // If This Edge DOES Already Exist, Then We Need To Remove It From The Hull + //-------------------------------------------------------------------------- + else if (mEdges[nEdge].mOnHull) + { + assert(mEdges[nEdge].mHullLoc!=mHull.end()); + + if (mHullIter==mEdges[nEdge].mHullLoc) + { + mHull.erase(mHullIter); // Make Sure To Fix Up The Hull Iter If That Is What We Are Removing + } + else + { + mHull.erase(mEdges[nEdge].mHullLoc); + } + mEdges[nEdge].mOnHull = false; + } + + + // If The Edge Was Made With The Same Orientation Currently Asked For (A->B), Then Mark Face As Right + //---------------------------------------------------------------------------------------------------- + if (mEdges[nEdge].mA==A) + { + mEdges[nEdge].mRight = Face; + } + else + { + mEdges[nEdge].mLeft = Face; + } + return nEdge; + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Create A New Face A->B->C, And Fix Up The Edges & Neighboring Faces + //////////////////////////////////////////////////////////////////////////////////// + int add_face(int A, int B, int C) + { + int Temp = 0; + int nFace = mFaces.alloc(); + + // First, Make Sure Node A.x Is Greater Than B and C. If Not, Swap With B or C + //------------------------------------------------------------------------------ + if (mGraph.get_node(B)[0]>mGraph.get_node(A)[0]) + { + Temp = A; + A = B; + B = Temp; + } + if (mGraph.get_node(C)[0]>mGraph.get_node(A)[0]) + { + Temp = A; + A = C; + C = Temp; + } + + // Similarly, Make Sure Node B.y Is Greater Than Node C.y + //-------------------------------------------------------- + if (mGraph.get_node(C).LRTest(mGraph.get_node(A), mGraph.get_node(B))==Side_Left) + { + Temp = C; + C = B; + B = Temp; + } + + // DEBUG ASSERTS + //==================================================================================== + // IF YOU HIT THESE ASSERTS, CHANCES ARE THAT YOU ARE TRYING TO TRIANGULATE OVER A SET + // WITH MORE THAN 2 COLINEAR POINTS ON THE SAME FACE. INSERT HULL WILL FAIL IN THIS + // FACE. INSERT HULL WILL FAIL IN THIS SITUATION + + assert(mGraph.get_node(C).LRTest(mGraph.get_node(A), mGraph.get_node(B))==Side_Right); + assert(mGraph.get_node(A).LRTest(mGraph.get_node(B), mGraph.get_node(C))==Side_Right); + assert(mGraph.get_node(B).LRTest(mGraph.get_node(C), mGraph.get_node(A))==Side_Right); + //==================================================================================== + + mFaces[nFace].mA = A; + mFaces[nFace].mB = B; + mFaces[nFace].mC = C; + + mFaces[nFace].mRight = add_edge(C, A, nFace); + mFaces[nFace].mBottom = add_edge(A, B, nFace); + mFaces[nFace].mLeft = add_edge(B, C, nFace); + + mFaces[nFace].mFlips = 0; + + return nFace; + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Insertion Hull Triangulation + // + // This algorithm works by scanning the outer convex hull of the set of points that + // have already been triangulated. When encountering a hull edge which evaluates + // LEFT in a left right test (remember, the triangles always have clockwise orientation) + // it adds a face to the triangulation including the edge as one side of the triangle + // and two new edges to the node handle. It's very important to traverse the convex + // hull in counter clockwise order (backwards). + // + // In the example below, we assume the convex hull starts at the edge (CA). (nodeHandle) is + // RIGHT of (C->A), so it skips that edge and moves on to (D->C). (nodeHandle) is in fact + // LEFT of (D->C), so we would add a new face that would go (D->nodeHandle->C), and we remove + // (D->C) from the hull. + // + // + // + // (C)-------------(A) + // / \ __/ \ + // (nodeHandle) / \ __/ \ + // / \ / \ + // (D)----____(B)_ \ + // \ | \ __ + // \ | \__ + // \ | \ + // + //////////////////////////////////////////////////////////////////////////////////// + void insert_point(int nodeHandle) + { + // Iterate Over The Existing Convex Hull + //--------------------------------------- + for (mHullIter = mHull.begin(); mHullIter!=mHull.end(); mHullIter++) + { + edge& curEdge = mEdges[*mHullIter]; + + // Can This Edge "See" The node Handle We Have Passed In? + //--------------------------------------------------------- + if ( mGraph.get_node(nodeHandle).LRTest(mGraph.get_node(curEdge.mA), mGraph.get_node(curEdge.mB))==Side_Left ) + { + // Then Add The Face And Remove This Edge From The Hull + //------------------------------------------------------ + add_face(curEdge.mA, curEdge.mB, nodeHandle); + } + } + } + + + + //////////////////////////////////////////////////////////////////////////////////// + // Edge Flip Function + // + // This function scans the edge list for any edge that is "bad" (defined as not + // fitting within the circumscribed circle of either adjoining face). When it + // encounters one, it "flips" the edge in question and fixes up the adjoining faces + // which were altered. + // + // + // The Flip Edge (PtA->PtB): + // + // + // + // BEFORE AFTER + // + // (PtR) (PtA) + // / \ / | \ + // / \ / | \ + // / (FaceR) \ / V \ + // / \ / | \ + // (PtB)-<---------<-(PtA) (PtR) | (PtL) + // \ / \ | / + // \ (FaceL) / \ V / + // \ / \ | / + // \ / \ | / + // (PtL) (PtB) + // + //////////////////////////////////////////////////////////////////////////////////// + int flip() + { + int Flipped = 0; + + int EdgeHandle; + int PtR, PtL, PtA, PtB; + int EdgeRL, EdgeRR, EdgeLL, EdgeLR; + + + // Iterate Through All The Edges Looking For Potential NON-Delauney Edges + //------------------------------------------------------------------------ + for (TEdgesIter CurEdge=mEdges.begin(); CurEdge!=mEdges.end(); CurEdge++) + { + // If It Is On The Hull, We Don't Even Need To Look At It + //-------------------------------------------------------- + if (!(*CurEdge).mOnHull) + { + edge& EdgeAt = *CurEdge; + face& FaceR = mFaces[EdgeAt.mRight]; + face& FaceL = mFaces[EdgeAt.mLeft]; + + EdgeHandle = mEdges.index_to_handle(CurEdge.index()); + PtA = EdgeAt.mA; + PtB = EdgeAt.mB; + PtR = FaceR.opposing_node(PtA, PtB); + PtL = FaceL.opposing_node(PtA, PtB); + + assert(EdgeAt.mRight!=EdgeAt.mLeft); + assert(PtA!=PtB); + assert(PtR!=PtL); + assert(PtA!=PtR && PtA!=PtL); + assert(PtB!=PtR && PtB!=PtL); + + // Is This Edge Invalid For Delaunay? + //------------------------------------- + if (!mGraph.get_node(PtB).InCircle(mGraph.get_node(PtR), mGraph.get_node(PtL), mGraph.get_node(PtA)) && + !mGraph.get_node(PtA).InCircle(mGraph.get_node(PtR), mGraph.get_node(PtB), mGraph.get_node(PtL)) + ) + { + // Change The Link: Remove The Old, Add The New + //---------------------------------------------- + mLinks.get(mGraph.node_index(PtA), mGraph.node_index(PtB)) = 0; + mLinks.get(mGraph.node_index(PtB), mGraph.node_index(PtA)) = 0; + + mLinks.get(mGraph.node_index(PtR), mGraph.node_index(PtL)) = EdgeHandle; + mLinks.get(mGraph.node_index(PtL), mGraph.node_index(PtR)) = EdgeHandle; + + + Flipped++; + EdgeAt.mFlips++; + FaceL.mFlips++; + FaceR.mFlips++; + + // Flip The Edge We Found + //------------------------ + EdgeAt.mA = PtR; + EdgeAt.mB = PtL; + + // Calculate Relatave Edges And Points Assuming (EdgeAt) Were mBottom For The Two Faces + //-------------------------------------------------------------------------------------- + EdgeRL = FaceR.relative_left(EdgeHandle); + EdgeRR = FaceR.relative_right(EdgeHandle); + + EdgeLL = FaceL.relative_left(EdgeHandle); + EdgeLR = FaceL.relative_right(EdgeHandle); + + + // Fix Edges Which Had Been Rotated To New Faces + //----------------------------------------------- + mEdges[EdgeLR].flip_face(EdgeAt.mLeft, EdgeAt.mRight); + mEdges[EdgeRR].flip_face(EdgeAt.mRight, EdgeAt.mLeft); + + // Rotate The Edges Clockwise + //---------------------------- + FaceR.mLeft = EdgeLR; + FaceR.mRight = EdgeRL; + FaceR.mBottom = EdgeHandle; + + FaceL.mLeft = EdgeRR; + FaceL.mRight = EdgeLL; + FaceL.mBottom = EdgeHandle; + + FaceR.mA = PtR; + FaceR.mB = PtL; + FaceR.mC = PtB; + + FaceL.mA = PtR; + FaceL.mB = PtL; + FaceL.mC = PtA; + + + // DEBUG VERIFICATION + //======================================================================== + #ifdef _DEBUG + mEdges[FaceR.mLeft ].verify(FaceR.mA, FaceR.mB, FaceR.mC, EdgeAt.mRight); + mEdges[FaceR.mRight ].verify(FaceR.mA, FaceR.mB, FaceR.mC, EdgeAt.mRight); + mEdges[FaceR.mBottom].verify(FaceR.mA, FaceR.mB, EdgeAt.mRight); + + mEdges[FaceL.mLeft ].verify(FaceL.mA, FaceL.mB, FaceL.mC, EdgeAt.mLeft); + mEdges[FaceL.mRight ].verify(FaceL.mA, FaceL.mB, FaceL.mC, EdgeAt.mLeft); + mEdges[FaceL.mBottom].verify(FaceL.mA, FaceL.mB, EdgeAt.mLeft); + #endif + + assert(EdgeAt.mRight!=EdgeAt.mLeft); + assert(PtA!=PtB); + assert(PtR!=PtL); + //======================================================================== + } + } + } + return Flipped; + } + +}; + + + + +} +#endif \ No newline at end of file diff --git a/code/ragl/graph_vs.h b/code/ragl/graph_vs.h new file mode 100644 index 0000000..bb7fbdc --- /dev/null +++ b/code/ragl/graph_vs.h @@ -0,0 +1,1776 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Graph +// ----- +// A Graph object is one of the most generic data structures. Theoretically, a graph +// could be used to make a tree, list, or any other standard structure. A graph has +// two data types: NODE and EDGE, and maintains connection information how edges can +// link two nodes. +// +// One of the most intuitive uses of a graph class is a navigation bouy system. In such +// a use, the nodes would probably be vector based objects with positional information +// and the edges might contain portal information (door is open / closed / locked). +// +// Another example might be a web page, or decision tree, or any other problem space +// which requires object connection data. +// +// +// +// +// Implimentation +// -------------- +// This template allocates a pool for NODES, a pool for EDGES, and a grid2_vs to serve +// as an Adjacency Matrix (called Links). The Adj. Matrix stores indicies to EDGE objects +// in the EDGE pool. +// +// +// +// +// What If You Do Not Need Any Edge Objects? +// ----------------------------------------- +// It's fairly common to have a graph with no connection information other than the +// existance of the link. For this case, you should be able to create a graph with a +// MAXEDGES of 1. You will want to call the version of connect_node() which does not +// take an edge object, and uses 1 as the "index" in the Adj. Matrix. +// +// +// +// +// How Do You Search? +// ------------------- +// This graph supports 3 search methods: +// Breadth First - Exausts as many links close to start as possible +// Depth First - Gets as far from start as quickly as possible +// A* - Uses a distance heuristic toward end point +// +// First, create a (graph_vs::search) object with the start and end points that you want +// to search for. Then, call either bfs(), dfs(), or astar(). When you get the +// object back, it will have a vector of all the nodes that were visited and methods +// for iterating over that vector to get the path. +// +// for (TestSearch.path_begin(); !TestSearch.path_end(); TestSearch.path_inc()) +// { +// sprintf(Buf, "(%d)", TestSearch.path_at()); +// OutputDebugString(Buf); +// } +// +// +// +// +// Complexity Analisis +// ------------------- +// All data operations except remove_node() are O(1) constant time. +// remove_node() can be O(n) where n is the number of NODES in the graph. +// +// Search routines: +// BFS - +// DFS - +// A* - +// +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RAGL_GRAPH_VS_INC) +#define RAGL_GRAPH_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if defined(RA_DEBUG_LINKING) + #pragma message("...including graph_vs.h") +#endif +#if !defined(RAGL_COMMON_INC) + #include "ragl_common.h" +#endif +#if !defined(RATL_ARRAY_VS_INC) + #include "..\Ratl\array_vs.h" +#endif +#if !defined(RATL_VECTOR_VS_INC) + #include "..\Ratl\vector_vs.h" +#endif +#if !defined(RATL_BITS_INC) + #include "..\Ratl\bits_vs.h" +#endif +#if !defined(RATL_QUEUE_VS_INC) + #include "..\Ratl\queue_vs.h" +#endif +#if !defined(RATL_STACK_VS_INC) + #include "..\Ratl\stack_vs.h" +#endif +#if !defined(RBTREE_MAP_VS_INC) + #include "..\Ratl\map_vs.h" +#endif +#if !defined(RATL_POOL_VS_INC) + #include "..\Ratl\pool_vs.h" +#endif +#if !defined(RATL_GRID_VS_INC) + #include "..\Ratl\grid_vs.h" +#endif +namespace ragl +{ + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Graph Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class graph_vs : public ratl::ratl_base +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // The Graph User Class + // + // When executing a search (in particular an A* search), you may want to derive your + // own user class so that you can provide specific functionality to the search. For + // example, you might want characters on one team to bias the cost of going to nodes + // which are occupied by the other team. Or you might want to allow specific + // characters to access some edges where others cannot. Perhaps one can fly or has + // a special key to a door... + // + //////////////////////////////////////////////////////////////////////////////////// + class user + { + public: + //////////////////////////////////////////////////////////////////////////////////// + // can be invalid edge (For Region) + //////////////////////////////////////////////////////////////////////////////////// + virtual bool can_be_invalid(const TEDGE& Edge) const = 0; + + //////////////////////////////////////////////////////////////////////////////////// + // valid edge (For A* and Region) + //////////////////////////////////////////////////////////////////////////////////// + virtual bool is_valid(TEDGE& Edge, int EndPoint=0) const = 0; + + //////////////////////////////////////////////////////////////////////////////////// + // cost estimate from A to B (For A*) + //////////////////////////////////////////////////////////////////////////////////// + virtual float cost(const TNODE& A, const TNODE& B) const = 0; + + //////////////////////////////////////////////////////////////////////////////////// + // cost estimate of Edge (For A*) + //////////////////////////////////////////////////////////////////////////////////// + virtual float cost(const TEDGE& Edge, const TNODE& B) const = 0; + + //////////////////////////////////////////////////////////////////////////////////// + // same floor check (For Triangulation) + //////////////////////////////////////////////////////////////////////////////////// + virtual bool on_same_floor(const TNODE& A, const TNODE& B) const = 0; + + //////////////////////////////////////////////////////////////////////////////////// + // setup the edge (For Triangulation) + //////////////////////////////////////////////////////////////////////////////////// + virtual void setup_edge(TEDGE& Edge, int A, int B, bool OnHull, const TNODE& NodeA, const TNODE& NodeB, bool CanBeInvalid=false) = 0; + }; + + + + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = MAXNODES, + NULLEDGE = -1, + }; + + + typedef typename ratl::pool_vs TNodes; + typedef typename ratl::pool_vs TEdges; + struct SNodeNeighbor + { + short mEdge; + short mNode; + }; + typedef typename ratl::vector_vs TNodeNeighbors; + typedef typename ratl::array_vs< TNodeNeighbors, MAXNODES> TLinks; + + + typedef typename ragl::graph_vs TGraph; + + + //////////////////////////////////////////////////////////////////////////////////// + // cells class + //////////////////////////////////////////////////////////////////////////////////// + template + class cells : public ratl::ratl_base + { + public: + enum + { + SIZEX = CELLSX, + SIZEY = CELLSY, + SIZENODES = NODESPERCELL + }; + + + struct SSortNode + { + float mCost; + short mHandle; + bool operator<(const SSortNode& t) const + { + return (mCost TSortNodes; + typedef typename ratl::vector_vs TCellNodes; + struct SCell + { + TCellNodes mNodes; + TCellNodes mEdges; + }; + typedef typename ratl::grid2_vs TCells; + + + //////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////// + cells(TGraph& g) : mGraph(g) + { + } + + void clear() + { + mCells.clear(); + SCell EmptyCell; + mCells.init(EmptyCell); + } + + void get_cell_upperleft(int x, int y, float& xReal, float& yReal) + { + mCells.get_cell_upperleft(x,y,xReal,yReal); + } + void get_cell_lowerright(int x, int y, float& xReal, float& yReal) + { + mCells.get_cell_lowerright(x,y,xReal,yReal); + } + + //////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////// + void expand_bounds(int nodeHandle) + { + TNODE& node = mGraph.get_node(nodeHandle); + mCells.expand_bounds(node[0], node[1]); + } + + //////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////// + SCell& get_cell(float x, float y) + { + return mCells.get(x,y); + } + SCell& get_cell(int x, int y) + { + return mCells.get(x,y); + } + void convert_to_cell_coords(float x, float y, int& xint, int& yint) + { + mCells.get_cell_coords(x, y, xint, yint); + } + + //////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////// + void fill_cells_nodes(float range) + { + // I. Fill All The Cells With The Points Contained By Those Cells + //---------------------------------------------------------------- + bool full = false; + for (TNodes::iterator it=mGraph.nodes_begin(); it!=mGraph.nodes_end() && !full; it++) + { + TNODE& node = (*it); + SCell& cell = mCells.get(node[0], node[1]); + + cell.mNodes.push_back(it.index()); + full = cell.mNodes.full(); + assert(!full || "Cell Filled On Inital Containment"==0); + } + + mCells.scale_by_largest_axis(range); + + + // II. Go To All Neighboring Cells And Get Them + //============================================== + int iRange = (int)(range) + 1; + TCells::riterator rcell; + TCells::riterator rcellend; + CVec3 cellCenter(0,0,0); + CVec3 nodeCenter(0,0,0); + + TSortNodes *sortnodes = new TSortNodes; + SSortNode sortnode; + + TCells *sortcells = new TCells; + sortcells->copy_bounds(mCells); + + + + // For Every Cell + //---------------- + for (int x=0; xclear(); + + mCells.get_cell_position(x,y, cellCenter.v[0], cellCenter.v[1]); + + for (rcell = mCells.rangeBegin(iRange,x,y); !rcell.at_end(); rcell++) + { + SCell& cell = (*rcell); + + // Add The Nodes To The Sort List + //-------------------------------- + for (int i=0; ifull(); i++) + { + int nodeHandle = cell.mNodes[i]; + + TNODE& node = mGraph.get_node(nodeHandle); + nodeCenter[0] = node[0]; + nodeCenter[1] = node[1]; + + sortnode.mHandle = nodeHandle; + sortnode.mCost = cellCenter.Dist2(nodeCenter); + sortnodes->push_back(sortnode); + } + } + + // Sort The Results + //------------------ + sortnodes->sort(); + + + // Copy The Sorted Nodes Vector Into The Sorted Cell (Of The Sorted Cell Grid) + //---------------------------------------------------------------------------- + SCell& cell = sortcells->get(x,y); + cell.mNodes.clear(); + for (int i=0; isize() && iget(xb,yb); + SCell& mcell = mCells.get(xb,yb); + mcell.mNodes = scell.mNodes; + } + } + + delete sortnodes; + delete sortcells; + } + + + //////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////// + void fill_cells_edges(float range) + { + // I. Fill All The Cells With The Points Contained By Those Cells + //---------------------------------------------------------------- + bool full = false; + for (TEdges::iterator eit=mGraph.edges_begin(); eit!=mGraph.edges_end() && !full; eit++) + { + TEDGE& edge = (*eit); + SCell& cell = mCells.get(edge[0], edge[1]); + + cell.mEdges.push_back(eit.index()); + full = cell.mEdges.full(); + assert(!full || "Cell Filled On Inital Containment"==0); + } + + + mCells.scale_by_largest_axis(range); + + + // II. Go To All Neighboring Cells And Get Them + //============================================== + int iRange = (int)(range) + 1; + TCells::riterator rcell; + TCells::riterator rcellend; + CVec3 cellCenter(0,0,0); + CVec3 nodeCenter(0,0,0); + + TSortNodes *sortedges = new TSortNodes; + SSortNode sortnode; + + TCells *sortcells = new TCells; + sortcells->copy_bounds(mCells); + + + // For Every Cell + //---------------- + for (int x=0; xclear(); + + mCells.get_cell_position(x,y, cellCenter.v[0], cellCenter.v[1]); + + for (rcell = mCells.rangeBegin(iRange,x,y); !rcell.at_end(); rcell++) + { + SCell& cell = (*rcell); + + // Add The Edges To The Sort List + //-------------------------------- + for (int e=0; efull(); e++) + { + int edgeHandle = cell.mEdges[e]; + + TEDGE& edge = mGraph.get_edge(edgeHandle); + nodeCenter[0] = edge[0]; + nodeCenter[1] = edge[1]; + + sortnode.mHandle = edgeHandle; + sortnode.mCost = cellCenter.Dist2(nodeCenter); + sortedges->push_back(sortnode); + } + } + + // Sort The Results + //------------------ + sortedges->sort(); + + + // Copy The Sorted Nodes Vector Into The Sorted Cell (Of The Sorted Cell Grid) + //---------------------------------------------------------------------------- + SCell& cell = sortcells->get(x,y); + cell.mEdges.clear(); + for (int j=0; jsize() && jget(xb,yb); + SCell& mcell = mCells.get(xb,yb); + mcell.mEdges = scell.mEdges; + } + } + + delete sortedges; + delete sortcells; + } + + + private: + TGraph& mGraph; + TCells mCells; + }; + + + + + + + //////////////////////////////////////////////////////////////////////////////////// + // Remove All Edges + //////////////////////////////////////////////////////////////////////////////////// + void clear_edges() + { + mEdges.clear(); + mEdges.alloc(); // Alloc a dummy edge at location 0 + for (int i=0; iB) if reflexive, also (B->A) + //////////////////////////////////////////////////////////////////////////////////// + int connect_node(const TEDGE& t, int nodeA, int nodeB, bool reflexive=true) + { + if (nodeA==nodeB || !nodeA || !nodeB || !mNodes.is_used(nodeA) || !mNodes.is_used(nodeB)) + { + assert("ERROR: Cannot Connect A and B!"==0); + return 0; + } + + if (mLinks[nodeA].full() || (reflexive && mLinks[nodeB].full())) + { + assert("ERROR: Max edges per node exceeded!"==0); + return 0; + } + + if (mEdges.full()) + { + assert("ERROR: Max edges exceeded!"==0); + return 0; + } + + SNodeNeighbor nNbr; + + nNbr.mNode = nodeB; + nNbr.mEdge = mEdges.alloc(); + mEdges[nNbr.mEdge] = t; + + + mLinks[nodeA].push_back(nNbr); + if (reflexive) + { + nNbr.mNode = nodeA; + mLinks[nodeB].push_back(nNbr); + } + + return nNbr.mEdge; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Connect Node Without Allocating An Edge Object (A->B) if reflexive, also (B->A) + //////////////////////////////////////////////////////////////////////////////////// + void connect_node(int nodeA, int nodeB, bool reflexive=true) + { + if (nodeA==nodeB || !nodeA || !nodeB || !mNodes.is_used(nodeA) || !mNodes.is_used(nodeB)) + { + assert("ERROR: Cannot Connect A and B!"==0); + return 0; + } + + if (mLinks[nodeA].full() || (reflexive && mLinks[nodeB].full())) + { + assert("ERROR: Max edges per node exceeded!"==0); + return 0; + } + + + SNodeNeighbor nNbr; + + nNbr.mNode = nodeB; + nNbr.mEdge = 0; + + + mLinks[nodeA].push_back(nNbr); + if (reflexive) + { + nNbr.mNode = nodeA; + mLinks[nodeB].push_back(nNbr); + } + + return nNbr.mEdge; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Remove Edge + //////////////////////////////////////////////////////////////////////////////////// + void remove_edge(int nodeA, int nodeB, bool reflexive=true) + { + if (!mNodes.is_used(nodeA) || !mNodes.is_used(nodeB) && nodeA==nodeB) + { + assert("Unable To Remove Edge"==0); + return; + } + + int linkNum=0; + + for (linkNum=0; linkNumindex. + // + // The handle heap is used by A* to sort the open list by cost. + // + //////////////////////////////////////////////////////////////////////////////////////// + template + class handle_heap + { + public: + + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + handle_heap(TNodes& Nodes) : mNodes(Nodes) + { + clear(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get The Size (The Difference Between The Push And Pop "Pointers") + //////////////////////////////////////////////////////////////////////////////////// + int size() const + { + return (mPush); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Check To See If The Size Is Zero + //////////////////////////////////////////////////////////////////////////////////// + bool empty() const + { + return (!size()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Check To See If The Size Is Full + //////////////////////////////////////////////////////////////////////////////////// + bool full() const + { + return (size()==MAXNODES); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Empty Out The Entire Heap + //////////////////////////////////////////////////////////////////////////////////// + void clear() + { + mPush = 0; + for (int i=0; i0); // Don't Try To Look At This If There Is Nothing In Here + return (mData[0]); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Accessor + //////////////////////////////////////////////////////////////////////////////////// + T& operator[](int handle) + { + // If You Hit This Assert, Then You Are Asking For Unallocated Data + //------------------------------------------------------------------ + assert(used(handle)); + return mData[mHandleToPos[handle]]; + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Add A Value To The Queue + //////////////////////////////////////////////////////////////////////////////////// + void push(const T& nValue) + { + assert(size()0); + + mPush--; + + assert(mHandleToPos[mData[0].handle()]==0); + + + // Swap The Lowest Element Up To The Spot We Just "Erased" + //--------------------------------------------------------- + swap(0, mPush); + + mHandleToPos[mData[mPush].handle()] = -1; // Erase This Handles Marker + + + // Fix Possible Heap Inconsistancies + //----------------------------------- + reheapify_downward(0); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Call This Func If The Value At The Given Handle Has Changed & Needs Adjustment + //////////////////////////////////////////////////////////////////////////////////// + void reheapify(int handle) + { + assert(used(handle)); + + int Pos = mHandleToPos[handle]; + reheapify_upward(Pos); + reheapify_downward(Pos); + } + + private: + //////////////////////////////////////////////////////////////////////////////////// + // Returns The Location Of Node (i)'s Parent Node (The Parent Node Of Zero Is Zero) + //////////////////////////////////////////////////////////////////////////////////// + int parent(int i) + { + return ((i-1)/2); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Returns The Location Of Node (i)'s Left Child (The Child Of A Leaf Is The Leaf) + //////////////////////////////////////////////////////////////////////////////////// + int left(int i) + { + return (2*i)+1; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Returns The Location Of Node (i)'s Right Child (The Child Of A Leaf Is The Leaf) + //////////////////////////////////////////////////////////////////////////////////// + int right(int i) + { + return (2*i)+2; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Returns The Location Of Largest Child Of Node (i) + //////////////////////////////////////////////////////////////////////////////////// + int largest_child(int i) + { + if (left(i)=0 && b>=0 && aTEMP + mData[a] = mData[b]; // b->a + mData[b] = mData[MAXNODES]; // TEMP->B + + assert(mHandleToPos[mData[a].handle()]==a); + assert(mHandleToPos[mData[b].handle()]==b); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Swaps The Data Up The Heap Until It Reaches A Valid Location + //////////////////////////////////////////////////////////////////////////////////// + void reheapify_upward(int Pos) + { + while (Pos && mData[parent(Pos)] mData; // The Memory (Plus One For Temp Sort) + ratl::array_vs mHandleToPos; // + + int mPush; // Address Of Next Add Location + + TNodes& mNodes; + }; + + + + + + + + //////////////////////////////////////////////////////////////////////////////////// + // A Search Node + //////////////////////////////////////////////////////////////////////////////////// + class search_node + { + public: + //////////////////////////////////////////////////////////////////////////////// + // Constructors + //////////////////////////////////////////////////////////////////////////////// + search_node(int Node=-1, int Parent=-1) : + mNode(Node), + mParentVisit(Parent), + mCostToGoal(-1), + mCostFromStart(0) + {} + search_node(const search_node& t) : + mNode(t.mNode), + mParentVisit(t.mParentVisit) + mCostToGoal(t.mCostToGoal), + mCostFromStart(t.mCostFromStart) + {} + + //////////////////////////////////////////////////////////////////////////////// + // Assignment Operator + //////////////////////////////////////////////////////////////////////////////// + void operator=(const search_node& t) + { + mNode = (t.mNode); + mParentVisit = (t.mParentVisit); + mCostToGoal = (t.mCostToGoal); + mCostFromStart = (t.mCostFromStart); + } + + //////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////// + int handle() const + { + return mNode; + } + + //////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////// + float cost_estimate() const + { + return mCostFromStart+mCostToGoal; + } + + //////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////// + bool operator< (const search_node& t) const + { + return (cost_estimate() > t.cost_estimate()); + } + + //////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////// + int mNode; // Which Node Is This (Index To Pool mNodes) + int mParentVisit; // Which Search Node (In Visited) + + float mCostToGoal; // How Far From The Start Of The Search Are We? + float mCostFromStart; // How Far From The End Of The Search Are We? + }; + + + +public: + typedef ratl::vector_vs TVisited; + typedef ratl::array_vs TVisitedHandles; + typedef ratl::bits_vs TNodeState; + + //////////////////////////////////////////////////////////////////////////////////// + // The Search Data Object + //////////////////////////////////////////////////////////////////////////////////// + class search + { + public: + //////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////// + search(int nodeStart=0, int nodeEnd=0) : + mStart(nodeStart), + mEnd(nodeEnd) + { + clear(true, false); + } + + enum + { + NULL_NODE = 0, + NULL_NODE_INDEX = -1, + NULL_VISIT_INDEX = -1, + NULL_COST = -1, + }; + + + //////////////////////////////////////////////////////////////////////////////// + // Reset All The Search Parameters + //////////////////////////////////////////////////////////////////////////////// + void clear(bool clearNodesPtr=true, bool clearStartAndEnd=true) + { + // Reset All Data + //---------------- + mClosed.clear(); + mVisited.clear(); + mNodeIndexToVisited.fill(NULL_VISIT_INDEX); + + + mNext.mNode = NULL_NODE; + mNext.mParentVisit = NULL_VISIT_INDEX; + mNext.mCostToGoal = NULL_COST; + mNext.mCostFromStart= NULL_COST; + + mPrevIndex = NULL_NODE_INDEX; + mNextIndex = NULL_NODE_INDEX; + + mPathVisit = NULL_VISIT_INDEX; + + if (clearNodesPtr) + { + mNodesPtr = 0; + } + + // Clear Out The Start And End Handles + //------------------------------------- + if (clearStartAndEnd) + { + mStart = NULL_NODE; + mEnd = NULL_NODE; + } + + // Otherwise, We Can Start The Next Index + //---------------------------------------- + else if (mNodesPtr && mStart!=NULL_NODE && mEnd!=NULL_NODE) + { + mNextIndex = mStart; + } + + } + + //////////////////////////////////////////////////////////////////////////////// + // Call This Function To Clear Out Everything EXCEPT Start And End Points + //////////////////////////////////////////////////////////////////////////////// + void reset() + { + clear(false, false); + } + + //////////////////////////////////////////////////////////////////////////////// + // Mark This Node As Closed + //////////////////////////////////////////////////////////////////////////////// + void close(int node) + { + mClosed.set_bit(node); + } + //////////////////////////////////////////////////////////////////////////////// + // Mark This Node As Closed + //////////////////////////////////////////////////////////////////////////////// + void close(const TNodeState& AllNodesToClose) + { + mClosed |= AllNodesToClose; + } + + + //////////////////////////////////////////////////////////////////////////////// + // Return True If We Have Found The Node We Were Searching For + //////////////////////////////////////////////////////////////////////////////// + bool success() + { + if (mEnd && mPrevIndex!=NULL_NODE_INDEX) + { + return (mPrevIndex==mEnd); + } + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + // Get An Index To The Beginning Of The Path + //////////////////////////////////////////////////////////////////////////////// + void path_begin() + { + if (success()) + { + mPathVisit = (mVisited.size()-1); + } + else + { + mPathVisit = NULL_VISIT_INDEX; + } + } + + //////////////////////////////////////////////////////////////////////////////// + // Check ForThe End Of The Path (Use In A for() Loop) + //////////////////////////////////////////////////////////////////////////////// + bool path_end() + { + return (mPathVisit==NULL_VISIT_INDEX); + } + + //////////////////////////////////////////////////////////////////////////////// + // Index Path Inc (Get The Old Path Node's Parent) + //////////////////////////////////////////////////////////////////////////////// + void path_inc() + { + assert(mPathVisit!=NULL_VISIT_INDEX); + mPathVisit = mVisited[mPathVisit].mParentVisit; + } + + //////////////////////////////////////////////////////////////////////////////// + // Get the node handle of the current node + //////////////////////////////////////////////////////////////////////////////// + int path_at() + { + assert(mPathVisit!=NULL_VISIT_INDEX); + return mVisited[mPathVisit].mNode; + } + + //////////////////////////////////////////////////////////////////////////////// + // The Total Cost Of The Path + //////////////////////////////////////////////////////////////////////////////// + float path_cost() + { + if (success()) + { + return (mVisited[(mVisited.size()-1)].mCostFromStart); + } + return NULL_COST; + } + + + //////////////////////////////////////////////////////////////////////////////// + // How Many Nodes Were Looked At In This Search + //////////////////////////////////////////////////////////////////////////////// + int num_visited() + { + return mVisited.size(); + } + + //////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The Beginning Of The Visited Pool + //////////////////////////////////////////////////////////////////////////////// + typename TVisited::iterator visited_begin() + { + return mVisited.begin(); + } + + //////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The End Of The Visited Pool + //////////////////////////////////////////////////////////////////////////////// + typename TVisited::iterator visited_end() + { + return mVisited.end(); + } + + + + + + private: + //////////////////////////////////////////////////////////////////////////////// + // Start At The First NON Closed Node + //////////////////////////////////////////////////////////////////////////////// + void setup(TNodes* nodesPtr) + { + mNodesPtr = nodesPtr; + clear(false, false); + } + + + //////////////////////////////////////////////////////////////////////////////// + // Pretend the next index is open, probably because we found a shorter route + // than the first time it was closed, and want it back on the open list + //////////////////////////////////////////////////////////////////////////////// + void reopen_next_index() + { + assert(mNextIndex!=NULL_NODE_INDEX); + + mNodeIndexToVisited[mNextIndex] = NULL_VISIT_INDEX; + mClosed.clear_bit(mNextIndex); + } + + //////////////////////////////////////////////////////////////////////////////// + // The current estimated cost of reaching a give node, if the node was never + // visited, but IS closed, it returns NULL cost + //////////////////////////////////////////////////////////////////////////////// + float visited_cost(int NodeIndex) + { + int VisitedIndex = mNodeIndexToVisited[NodeIndex]; + if (VisitedIndex!=NULL_VISIT_INDEX) + { + return mVisited[VisitedIndex].cost_estimate(); + } + return NULL_COST; + } + + + //////////////////////////////////////////////////////////////////////////////// + // Add This Search Node To The Visited List And Keep Track Of It For Later + //////////////////////////////////////////////////////////////////////////////// + void visit(search_node& t) + { + assert(mNodesPtr!=0); + mPrevIndex = t.mNode; + + // Add It To The Visited List, And Mark The Location In The Node Index Array + //--------------------------------------------------------------------------- + mVisited.push_back(t); + mNodeIndexToVisited[mPrevIndex] = (mVisited.size()-1); + mClosed.set_bit(mPrevIndex); + + // Setup Our Next Node To Know It Came From The Last Location In The Visited Vector + //---------------------------------------------------------------------------------- + mNext.mParentVisit = (mVisited.size()-1); + } + + //////////////////////////////////////////////////////////////////////////////// + // Check To See If The Next Index Has Already Been Closed + //////////////////////////////////////////////////////////////////////////////// + bool next_index_closed() + { + assert(mNextIndex!=NULL_NODE_INDEX); + + return (mClosed.get_bit(mNextIndex)); + } + + //////////////////////////////////////////////////////////////////////////////// + // The Simple "Get Next" Just Converts The Next Index To A Handle In mNext + //////////////////////////////////////////////////////////////////////////////// + search_node& get_next() + { + assert(mNodesPtr); + assert(mNextIndex!=NULL_NODE_INDEX); + + mClosed.set_bit(mNextIndex); + + mNext.mNode = mNextIndex; + mNext.mCostToGoal = 0; + mNext.mCostFromStart = 0; + + return mNext; + } + + //////////////////////////////////////////////////////////////////////////////// + // This "Get Next" Function Is For A* and Sets Up THe Costs Of The Search Node + //////////////////////////////////////////////////////////////////////////////// + search_node& get_next(const user& suser, TEDGE& edge_parent_to_next) + { + assert(mNodesPtr); + assert(mNextIndex!=NULL_NODE_INDEX); + + //NOTE: we do not do a "mClosed.set_bit(mNextIndex);" here because A* only closes nodes that are visited + + mNext.mNode = mNextIndex; + mNext.mCostToGoal = suser.cost((*mNodesPtr)[mNext.mNode], (*mNodesPtr)[mEnd]); + mNext.mCostFromStart = suser.cost(edge_parent_to_next, (*mNodesPtr)[mNext.mNode]); + + if (mNext.mParentVisit!=NULL_VISIT_INDEX) + { + mNext.mCostFromStart += mVisited[mNext.mParentVisit].mCostFromStart; + } + return mNext; + } + + //////////////////////////////////////////////////////////////////////////////// + // Parameters (Start And End Are External Handles) + //////////////////////////////////////////////////////////////////////////////// + public: + int mStart; + int mEnd; + + + //////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////// + private: + TNodes* mNodesPtr; + + int mPathVisit; + int mPrevIndex; + int mNextIndex; + search_node mNext; + + ratl::bits_vs mClosed; + TVisited mVisited; + TVisitedHandles mNodeIndexToVisited; + + friend class graph_vs; + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Setup The Search Data + //////////////////////////////////////////////////////////////////////////////////// + void setup_search(search& sdata) + { + sdata.setup(&mNodes); + } + + + + + + + + //////////////////////////////////////////////////////////////////////////////////// + // A* Search + //////////////////////////////////////////////////////////////////////////////////// + void astar(search& sdata, const user& suser) + { + // Make Sure The Nodes We Are Searching For Exist + //------------------------------------------------ + assert(MAXEDGES>1); + sdata.setup(&mNodes); + + // Allocate Our Data Structures + //------------------------------ + handle_heap open(mNodes); + int curNeighbor; + int curEdge; + float curCost; + + // Run Through The Open List + //--------------------------- + open.push(sdata.get_next()); + while (!open.empty() && !sdata.success()) + { + sdata.visit(open.top()); + open.pop(); + + // Search Through The Non Closed Nodes Edges + //------------------------------------------- + TNodeNeighbors& curNeighbors = get_node_neighbors(sdata.mPrevIndex); + for (curNeighbor=0; curNeighbor open; + + // Run Through The Open List + //--------------------------- + open.push(sdata.get_next()); + while (open.size()>0 && !sdata.success()) + { + sdata.visit(open.top()); + open.pop(); + + + // Search Through The Non Closed Nodes Edges + //------------------------------------------- + for (sdata.mNextIndex=0; sdata.mNextIndex open; + + // Run Through The Open List + //--------------------------- + open.push(sdata.get_next()); + while (open.size()>0 && !sdata.success()) + { + sdata.visit(open.top()); + open.pop(); + + + // Search Through The Non Closed Nodes Edges + //------------------------------------------- + for (sdata.mNextIndex=0; sdata.mNextIndex +class kdtree_vs : public ratl::ratl_base +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = SIZE, + NULL_NODE = SIZE+2, // Invalid Node ID + TARG_NODE = SIZE+3 // Used To Mark Nodes Add Location + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + kdtree_vs() : mRoot(NULL_NODE) + { + } + + //////////////////////////////////////////////////////////////////////////////////// + // How Many Objects Are In This Tree + //////////////////////////////////////////////////////////////////////////////////// + int size() const + { + return (mPool.size()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Are There Any Objects In This Tree? + //////////////////////////////////////////////////////////////////////////////////// + bool empty() const + { + return (mRoot==NULL_NODE); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Is This List Filled? + //////////////////////////////////////////////////////////////////////////////////// + bool full() const + { + return (mPool.full()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Clear All Elements + //////////////////////////////////////////////////////////////////////////////////// + void clear() + { + mRoot = NULL_NODE; + mPool.clear(); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Add A New Element To The Tree + //////////////////////////////////////////////////////////////////////////////////// + void add(const T& data) + { + // CREATE: New + //-------------------------------------------- + int nNew = mPool.alloc(); + mPool[nNew].mData = data; + mPool[nNew].mLeft = NULL_NODE; + mPool[nNew].mRight = NULL_NODE; + + // LINK: (nNew)->(Parent) + //-------------------------------------------- + if (mRoot==NULL_NODE) + { + mRoot = nNew; + mPool[nNew].mParent = NULL_NODE; + return; + } + + // LINK: (nNew)->(Parent) + //-------------------------------------------- + mPool[nNew].mParent = find_index(data, mRoot, 0, true, true); + + + // LINK: (Parent)->(nNew) + //-------------------------------------------- + if (mPool[mPool[nNew].mParent].mLeft==TARG_NODE) + { + mPool[mPool[nNew].mParent].mLeft = nNew; + } + else if (mPool[mPool[nNew].mParent].mRight==TARG_NODE) + { + mPool[mPool[nNew].mParent].mRight = nNew; + } + + // Hey! It Didn't Mark Any Targets, Which Means We Found An Exact match To This Data + //------------------------------------------------------------------------------------ + else + { + mPool.free(nNew); + } + } + + + + + + + //////////////////////////////////////////////////////////////////////////////////// + // Does (data) Exist In The Tree? + //////////////////////////////////////////////////////////////////////////////////// + bool find(const T& data) + { + assert(mRoot!=NULL_NODE); // If You Hit This Assert, You Are Asking For Data On An Empty Tree + + int node = find_index(data, mRoot, 0, true, true); + + // Exact Find, Or Found Root? + //---------------------------- + if (mPool[node].mData==data || mPool[node].mParent==NULL_NODE) + { + return true; + } + return false; + } + + + + + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + class range_query + { + public: + range_query() {} + + public: + ratl::vector_vs mReported; + T mMins; + T mMaxs; + }; + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + void find(range_query& query) + { + if (mRoot!=NULL_NODE) + { + query.mReported.clear(); + tree_search(query); + } + } + + + + + + + +private: + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + class node + { + public: + int mParent; + int mLeft; + int mRight; + + T mData; + }; + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + class range_bounds + { + public: + int mMins[DIMENSION]; + int mMaxs[DIMENSION]; + }; + + + + //////////////////////////////////////////////////////////////////////////////////// + // This Private Function Of The Class Does A Standard Binary Tree Search + //////////////////////////////////////////////////////////////////////////////////// + int find_index(const T& data, int curNode, int curDimension, bool returnClosest, bool markTarget) + { + // Did We Just Go Off The End Of The Tree Or Find The Data We Were Looking For? + //------------------------------------------------------------------------------ + if (curNode==NULL_NODE || mPool[curNode].mData==data) + { + return curNode; + } + + + // Calculate The Next Dimension For Searching + //-------------------------------------------- + int nextDimension = curDimension+1; + if (nextDimension>=DIMENSION) + { + nextDimension = 0; + } + + + // Search Recursivly Down The Tree Either Left (For Data > Current Node), Or Right + //--------------------------------------------------------------------------------- + int findRecursive; + bool goLeft = (data[curDimension] < mPool[curNode].mData[curDimension]); + if (goLeft) + { + findRecursive = find_index(data, mPool[curNode].mLeft, nextDimension, returnClosest, markTarget); + } + else + { + findRecursive = find_index(data, mPool[curNode].mRight, nextDimension, returnClosest, markTarget); + } + + // Success! + //---------- + if (findRecursive!=NULL_NODE) + { + return findRecursive; + } + + // If We Want To Return The CLOSEST Node, And We Went Off The End, Then Return This One + //-------------------------------------------------------------------------------------- + if (returnClosest) + { + // If We Are Asked To Mark The Target, We Mark (TARG_NODE) At Either mLeft or mRight, + // Depending On Where The Node Should Have Been + //---------------------------------------------------------------------------------- + if (markTarget) + { + if (goLeft) + { + mPool[curNode].mLeft = TARG_NODE; + } + else + { + mPool[curNode].mRight = TARG_NODE; + } + } + + // Go Ahead And Return This Node, It's The One We Would Have Put As The Child + return curNode; + } + + // Return The Results Of The Recursive Call + //------------------------------------------ + return NULL_NODE; + } + + + + //////////////////////////////////////////////////////////////////////////////////// + // This function just sets up the range bounds and starts the recursive tree search + //////////////////////////////////////////////////////////////////////////////////// + void tree_search(range_query& query) + { + range_bounds bounds; + for (int i=0; i=DIMENSION) + { + nextDimension = 0; + } + + + // Test To See If Our Subtree Is In Range + //---------------------------------------- + ESide Side = tree_search_bounds_in_range(query, bounds); + + // If The Bounds Are Contained Entirely Within The Query Range, We Report The Sub Tree + //------------------------------------------------------------------------------------- + if (Side==Side_AllIn) + { + tree_search_report_sub_tree(query, curNode); + } + + // Otherwise, If Our Bounds Intersect The Query Range, We Need To Look Further + //----------------------------------------------------------------------------- + else if (Side==Side_In) + { + // Test The Left Child + //--------------------- + if (mPool[curNode].mLeft!=NULL_NODE) + { + int OldMaxs = bounds.mMaxs[curDimension]; + if ( !bounds.mMins[curDimension] || ((mPool[curNode].mData[curDimension]) < (mPool[bounds.mMins[curDimension]].mData[curDimension])) ) + { + bounds.mMins[curDimension] = curNode; + } + tree_search(query, mPool[curNode].mLeft, nextDimension, bounds); + bounds.mMaxs[curDimension] = OldMaxs; // Restore Old Maxs For The Right Child Search + } + + // Test The Right Child + //---------------------- + if (mPool[curNode].mRight!=NULL_NODE) + { + if ( !bounds.mMaxs[curDimension] || ((mPool[bounds.mMaxs[curDimension]].mData[curDimension]) < (mPool[curNode].mData[curDimension])) ) + { + bounds.mMaxs[curDimension] = curNode; + } + tree_search(query, mPool[curNode].mRight, nextDimension, bounds); + } + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // This Function Returns True If The Node Is Within The Query Range + //////////////////////////////////////////////////////////////////////////////////// + bool tree_search_node_in_range(range_query& query, node& n) + { + for (int dim=0; dim mPool; // The Allocation Data Pool + int mRoot; // The Beginning Of The Tree +}; + +} +#endif \ No newline at end of file diff --git a/code/ragl/ragl_common.h b/code/ragl/ragl_common.h new file mode 100644 index 0000000..63a11fd --- /dev/null +++ b/code/ragl/ragl_common.h @@ -0,0 +1,232 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Common +// ------ +// The raven libraries contain a number of common defines, enums, and typedefs which +// need to be accessed by all templates. Each of these is included here. +// +// Also included is a safeguarded assert file for all the asserts in RTL. +// +// This file is included in EVERY TEMPLATE, so it should be very light in order to +// reduce compile times. +// +// +// Format +// ------ +// In order to simplify code and provide readability, the template library has some +// standard formats. Any new templates or functions should adhere to these formats: +// +// - All memory is statically allocated, usually by parameter SIZE +// - All classes provide an enum which defines constant variables, including CAPACITY +// - All classes which moniter the number of items allocated provide the following functions: +// size() - the number of objects +// empty() - does the container have zero objects +// full() - does the container have any room left for more objects +// clear() - remove all objects +// +// +// - Functions are defined in the following order: +// Capacity +// Constructors (copy, from string, etc...) +// Range (size(), empty(), full(), clear(), etc...) +// Access (operator[], front(), back(), etc...) +// Modification (add(), remove(), push(), pop(), etc...) +// Iteration (begin(), end(), insert(), erase(), find(), etc...) +// +// +// NOTES: +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RAGL_COMMON_INC) +#define RAGL_COMMON_INC + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if defined(RA_DEBUG_LINKING) + #pragma message("...including ragl_common.h") +#endif +#if !defined(RAGL_ASSERT_INC) + #define RAGL_ASSERT_INC + #include +#endif +#if !defined(FINAL_BUILD) + #if !defined(RAGL_PROFILE_INC) && !defined(_XBOX) + #define RAGL_PROFILE_INC + #include "Windows.h" + #endif +#endif +#if !defined(RAVL_VEC_INC) + #include "..\Ravl\CVec.h" +#endif +#if !defined(RATL_COMMON_INC) + #include "..\Ratl\ratl_common.h" +#endif +namespace ragl +{ + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Enums +//////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////// +// Typedefs +//////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////// +// Defines +//////////////////////////////////////////////////////////////////////////////////////// + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Graph Node Class +//////////////////////////////////////////////////////////////////////////////////////// +class CNode +{ +public: + CNode() {} + CNode(const CVec3& Pt) : mPoint(Pt) {} + + + //////////////////////////////////////////////////////////////////////////////////// + // Access Operator (For Triangulation) + //////////////////////////////////////////////////////////////////////////////////// + float operator[](int dimension) + { + return mPoint[dimension]; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Equality Operator (For KDTree) + //////////////////////////////////////////////////////////////////////////////////// + bool operator==(const CNode& t) const + { + return (t.mPoint==mPoint); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Left Right Test (For Triangulation) + //////////////////////////////////////////////////////////////////////////////////// + virtual ESide LRTest(const CNode& A, const CNode& B) const + { + return (mPoint.LRTest(A.mPoint, B.mPoint)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Point In Circle (For Triangulation) + //////////////////////////////////////////////////////////////////////////////////// + virtual bool InCircle(const CNode& A, const CNode& B, const CNode& C) const + { + return (mPoint.PtInCircle(A.mPoint, B.mPoint, C.mPoint)); + } + + + +public: + CVec3 mPoint; +}; + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Graph Edge Class +//////////////////////////////////////////////////////////////////////////////////////// +class CEdge +{ +public: + int mNodeA; + int mNodeB; + bool mOnHull; + float mDistance; + bool mCanBeInval; + bool mValid; +}; + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Geometric Reference Class +// +// This adds one additional function to the common ratl_ref class to allow access for +// various dimensions. It is used in both Triangulation and KDTree +//////////////////////////////////////////////////////////////////////////////////////// +template +class ragl_ref +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructors + //////////////////////////////////////////////////////////////////////////////////// + ragl_ref() {} + ragl_ref(const ragl_ref & r) {mDataRef = (TDATAREF)(r.mDataRef);} + ragl_ref(const TDATA & r) {mDataRef = (TDATAREF)(& r);} + ragl_ref(const TDATAREF r) {mDataRef = (TDATAREF)(r);} + + + //////////////////////////////////////////////////////////////////////////////////// + // Assignment Operators + //////////////////////////////////////////////////////////////////////////////////// + void operator=(const ragl_ref & r) {mDataRef = (TDATAREF)(r.mDataRef);} + void operator=(const TDATA & r) {mDataRef = (TDATAREF)(& r);} + void operator=(const TDATAREF r) {mDataRef = (TDATAREF)(r);} + + //////////////////////////////////////////////////////////////////////////////////// + // Access Operator (For Triangulation) + //////////////////////////////////////////////////////////////////////////////////// + float operator[](int dimension) const {return (*mDataRef)[dimension];} + + + //////////////////////////////////////////////////////////////////////////////////// + // Dereference Operator + //////////////////////////////////////////////////////////////////////////////////// + TDATA & operator*() {return (*mDataRef);} + const TDATA & operator*() const {return (*mDataRef);} + + TDATAREF handle() const {return mDataRef;} + + + + //////////////////////////////////////////////////////////////////////////////////// + // Equality / Inequality Operators + //////////////////////////////////////////////////////////////////////////////////// + bool operator== (const ragl_ref& t) const {return (*mDataRef)==(*(t.mDataRef));} + bool operator!= (const ragl_ref& t) const {return (*mDataRef)!=(*(t.mDataRef));} + bool operator< (const ragl_ref& t) const {return (*mDataRef)< (*(t.mDataRef));} + bool operator> (const ragl_ref& t) const {return (*mDataRef)> (*(t.mDataRef));} + bool operator<= (const ragl_ref& t) const {return (*mDataRef)<=(*(t.mDataRef));} + bool operator>= (const ragl_ref& t) const {return (*mDataRef)>=(*(t.mDataRef));} + + //////////////////////////////////////////////////////////////////////////////////// + // Equality / Inequality Operators + //////////////////////////////////////////////////////////////////////////////////// + bool operator== (const TDATA& t) const {return (*mDataRef)==t;} + bool operator!= (const TDATA& t) const {return (*mDataRef)!=t;} + bool operator< (const TDATA& t) const {return (*mDataRef)< t;} + bool operator> (const TDATA& t) const {return (*mDataRef)> t;} + bool operator<= (const TDATA& t) const {return (*mDataRef)<=t;} + bool operator>= (const TDATA& t) const {return (*mDataRef)>=t;} + + + //////////////////////////////////////////////////////////////////////////////////// + // The Data Reference + //////////////////////////////////////////////////////////////////////////////////// +private: + TDATAREF mDataRef; +}; + +} +#endif \ No newline at end of file diff --git a/code/ratl/array_vs.h b/code/ratl/array_vs.h new file mode 100644 index 0000000..891a4e3 --- /dev/null +++ b/code/ratl/array_vs.h @@ -0,0 +1,73 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Array +// ----- +// This array class is little more than an assert loaded wrapper around a standard +// array. +// +// +// +// NOTES: +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_ARRAY_VS) +#define RATL_ARRAY_VS + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif + +namespace ratl +{ + +template +class array_vs : public array_base > +{ +public: + typedef typename storage::value_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + array_vs() {} +}; + +template +class array_os : public array_base > +{ +public: + typedef typename storage::object_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + array_os() {} +}; + +template +class array_is : public array_base > +{ +public: + typedef typename storage::virtual_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + array_is() {} +}; + +} +#endif \ No newline at end of file diff --git a/code/ratl/bits_vs.h b/code/ratl/bits_vs.h new file mode 100644 index 0000000..d0bde48 --- /dev/null +++ b/code/ratl/bits_vs.h @@ -0,0 +1,218 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Bit Field +// --------- +// The bits class is a bit field of any length which supports all the +// standard bitwize operations in addition to some operators for adding & removing +// individual bits by their integer indicies and a string conversion method. +// +// +// +// NOTES: +// - The SIZE template variable determines how many BITS are available in this template, +// not how much memory (number of ints) were used to store it. +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_BITS_INC) +#define RATL_BITS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +namespace ratl +{ + +//////////////////////////////////////////////////////////////////////////////////////// +// The Bit Field Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class bits_vs : public bits_base +{ + //////////////////////////////////////////////////////////////////////////////////// + // Call This Function To Set All Bits Beyond SIZE to Zero + //////////////////////////////////////////////////////////////////////////////////// + void clear_trailing_bits() + { + for (int i=SIZE; i>BITS_SHIFT] &= ~(1<<(i&BITS_AND)); + } + } + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + SIZE = SZ, + CAPACITY = SZ, + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Standard Constructor + //////////////////////////////////////////////////////////////////////////////////// + bits_vs(bool init=true,bool initValue=false) : bits_base(init,initValue) + { + } + //////////////////////////////////////////////////////////////////////////////////// + // Copy Constructor + //////////////////////////////////////////////////////////////////////////////////// + bits_vs(const bits_vs &B) + { + mem::cpy(mV, B.mV,BYTE_SIZE); + } + + //////////////////////////////////////////////////////////////////////////////////// + // String Constructor (Format: "100010100101") + //////////////////////////////////////////////////////////////////////////////////// + bits_vs(const char* Str) + { + clear(); + + for (int b=0; b=0 && i < SIZE); + return ( (mV[i>>BITS_SHIFT] & (1<<(i&BITS_AND)))!=0 ); + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Checks If There Are Any Values At All In This Bit Field + //////////////////////////////////////////////////////////////////////////////////////// + bool operator!() const + { + return empty(); + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Equality Operator + //////////////////////////////////////////////////////////////////////////////////////// + bool operator==(const bits_vs &B) const + { + return (mem::eql(mV, B.mV,BYTE_SIZE)); + } + + //////////////////////////////////////////////////////////////////////////////////////// + // InEquality Operator + //////////////////////////////////////////////////////////////////////////////////////// + bool operator!=(const bits_vs &B) const + { + return !(operator==(B)); + } + + //////////////////////////////////////////////////////////////////////////////////////// + // Or In From Another Bits Object + //////////////////////////////////////////////////////////////////////////////////////// + void operator|=(const bits_vs &B) + { + for (int i=0; i +class grid2_vs : public ratl_base +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + grid2_vs() + { + clear(); + } + + enum + { + RANGE_NULL = 12345, + }; + + + //////////////////////////////////////////////////////////////////////////////////// + // Assignment Operator + //////////////////////////////////////////////////////////////////////////////////// + grid2_vs& operator=(const grid2_vs& other) + { + mData = other.mData; + for (int i=0; i<2; i++) + { + mSize[i] = other.mSize[i]; + mMins[i] = other.mMins[i]; + mMaxs[i] = other.mMaxs[i]; + mScale[i] = other.mScale[i]; + } + return (*this); + } + + void set_size(int xSize, int ySize) + { + if (xSize=0 && y>=0 && x=0 && yint>=0 && xint=0 && yint>=0 && xintmMaxs[i] || mMaxs[i]==RANGE_NULL) + { + mMaxs[i] = point[i]; + } + } + assert(mSize[0]>0 && mSize[1]>0); + + mScale[0] = ((mMaxs[0] - mMins[0])/mSize[0]); + mScale[1] = ((mMaxs[1] - mMins[1])/mSize[1]); + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + void truncate_position_to_bounds(float& xReal, float& yReal) + { + if (xReal(mMaxs[0]-1.0f)) + { + xReal = mMaxs[0]-1.0f; + } + if (yReal(mMaxs[1]-1.0f)) + { + yReal = mMaxs[1]-1.0f; + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + void get_cell_position(int x, int y, float& xReal, float& yReal) + { + // assert(mScale[0]!=0.0f && mScale[1]!=0.0f); + xReal = (x * mScale[0]) + mMins[0] + (mScale[0] * 0.5f); + yReal = (y * mScale[1]) + mMins[1] + (mScale[1] * 0.5f); + } + void get_cell_upperleft(int x, int y, float& xReal, float& yReal) + { + // assert(mScale[0]!=0.0f && mScale[1]!=0.0f); + xReal = (x * mScale[0]) + mMins[0]; + yReal = (y * mScale[1]) + mMins[1]; + } + void get_cell_lowerright(int x, int y, float& xReal, float& yReal) + { + // assert(mScale[0]!=0.0f && mScale[1]!=0.0f); + xReal = (x * mScale[0]) + mMins[0] + (mScale[0]); + yReal = (y * mScale[1]) + mMins[1] + (mScale[1]); + } + void scale_by_largest_axis(float& dist) + { + assert(mScale[0]!=0.0f && mScale[1]!=0.0f); + if (mScale[0]>mScale[1]) + { + dist /= mScale[0]; + } + else + { + dist /= mScale[1]; + } + } + + + + + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// +private: + array_vs mData; + + int mSize[2]; + float mMins[2]; + float mMaxs[2]; + float mScale[2]; + + + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Raw Get - For The Iterator Dereference Function + //////////////////////////////////////////////////////////////////////////////////// + T& rawGet(int Loc) + { + assert(Loc>=0 && LocrawGet(mLoc));} + + // Inc Operator + //-------------- + void operator++(int) {mLoc++;} + + + // Row & Col Offsets + //------------------- + void offsetRows(int num) {mLoc += (YSIZE_MAX*num);} + void offsetCols(int num) {mLoc += (num);} + + + // Return True If On Frist Column Of A Row + //----------------------------------------- + bool onColZero() + { + return (mLoc%XSIZE_MAX)==0; + } + + // Evaluate The XY Position Of This Iterator + //------------------------------------------- + void position(int& X, int& Y) + { + Y = mLoc / XSIZE_MAX; + X = mLoc - (Y*XSIZE_MAX); + } + + private: + int mLoc; + grid2_vs* mOwner; + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Begin + //////////////////////////////////////////////////////////////////////////////////// + iterator begin(int x=0, int y=0) + { + assert(x>=0 && y>=0 && x Bounds[i]) + { + mMaxs[i] = Bounds[i]; + } + + mLoc[i] = mMins[i]; + } + } + + // Assignment Operator + //--------------------- + void operator= (const riterator &t) + { + mOwner = t.mOwner; + for (int i=0; i<2; i++) + { + mMins[i] = t.mMins[i]; + mMaxs[i] = t.mMaxs[i]; + mLoc[i] = t.mLoc[i]; + } + } + + // Equality & Inequality Operators + //--------------------------------- + bool operator!=(const riterator &t) + { + return (mLoc[0]!=t.mLoc[0] || mLoc[1]!=t.mLoc[1]); + } + bool operator==(const riterator &t) + { + return (mLoc[0]==t.mLoc[0] && mLoc[1]==t.mLoc[1]); + } + + // Dereference Operator + //---------------------- + T& operator* () + { + return (mOwner->get(mLoc[0], mLoc[1])); + } + + // Inc Operator + //-------------- + void operator++(int) + { + if (mLoc[1] <= mMaxs[1]) + { + mLoc[0]++; + if (mLoc[0]>(mMaxs[0])) + { + mLoc[0] = mMins[0]; + mLoc[1]++; + } + } + } + + bool at_end() + { + return (mLoc[1]>mMaxs[1]); + } + + + // Return True If On Frist Column Of A Row + //----------------------------------------- + bool onColZero() + { + return (mLoc[0]==mMins[0]); + } + + // Evaluate The XY Position Of This Iterator + //------------------------------------------- + void position(int& X, int& Y) + { + Y = mLoc[1]; + X = mLoc[0]; + } + + private: + int mMins[2]; + int mMaxs[2]; + int mLoc[2]; + grid2_vs* mOwner; + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Ranged Iterator Begin (x and y are the center of the range) + //////////////////////////////////////////////////////////////////////////////////// + riterator rangeBegin(int range, int x, int y) + { + assert(x>=0 && y>=0 && x=0 && y>=0 && x +class handle_pool_base : public pool_root +{ +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY + }; + +private: + int mHandles[CAPACITY]; + int mMASK_HANDLE_TO_INDEX; + int mMASK_NUM_BITS; + + void IncHandle(int index) + { + mHandles[index] += (1<>= 1; + + mMASK_HANDLE_TO_INDEX <<= 1; + mMASK_HANDLE_TO_INDEX |= 1; + mMASK_NUM_BITS++; + } + for (int i=0; i::clear(); + // note that we do not refill the handles cause we want old handles to still be stale + for (int i=0; i::free_index(index); + IncHandle(index); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Deallocator, by handle + //////////////////////////////////////////////////////////////////////////////////// + void free(int handle) + { + assert(is_used(handle)); + free_index(handle&mMASK_HANDLE_TO_INDEX); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Deallocator, by pointer + //////////////////////////////////////////////////////////////////////////////////// + void free(TTValue *me) + { + free_index(pointer_to_index(me)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Convert a handle to a raw index, not generally something you should use + //////////////////////////////////////////////////////////////////////////////////// + int handle_to_index(int handle) const + { + assert(is_used(handle)); + return (handle&mMASK_HANDLE_TO_INDEX); + } + + //////////////////////////////////////////////////////////////////////////////////// + // FInd the handle for a given index, this cannot check for stale handles, so it is ill advised + //////////////////////////////////////////////////////////////////////////////////// + int index_to_handle(int index) const + { + assert(index>=0 && index::iterator at(int handle) + { + assert(is_used(handle)); + return at_index(handle&mMASK_HANDLE_TO_INDEX); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The Object At handle + //////////////////////////////////////////////////////////////////////////////////// + pool_root::const_iterator at(int handle) const + { + assert(is_used(handle)); + return at_index(handle&mMASK_HANDLE_TO_INDEX); + } +}; + +template +class handle_pool_vs : public handle_pool_base > +{ +public: + typedef typename storage::value_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + handle_pool_vs() {} +}; + +template +class handle_pool_os : public handle_pool_base > +{ +public: + typedef typename storage::object_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + handle_pool_os() {} +}; + +template +class handle_pool_is : public handle_pool_base > +{ +public: + typedef typename storage::virtual_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + handle_pool_is() {} +}; + +} +#endif \ No newline at end of file diff --git a/code/ratl/hash_pool_vs.h b/code/ratl/hash_pool_vs.h new file mode 100644 index 0000000..9cb5749 --- /dev/null +++ b/code/ratl/hash_pool_vs.h @@ -0,0 +1,200 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Hash Pool +// --------- +// The hash pool stores raw data of variable size. It uses a hash table to check for +// redundant data, and upon finding any, will return the existing handle. Otherwise +// it copies the data to memory and returns a new handle. +// +// +// NOTES: +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_HASH_POOL_VS_INC) +#define RATL_HASH_POOL_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +namespace ratl +{ + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Hash Pool +//////////////////////////////////////////////////////////////////////////////////////// +template +class hash_pool +{ + int mHandles[SIZE_HANDLES]; // each handle holds the start index of it's data + + int mDataAlloc; // where the next chunck of data will go + char mData[SIZE]; + + #ifdef _DEBUG + int mFinds; // counts how many total finds have run + int mCurrentCollisions; // counts how many collisions on the last find + int mTotalCollisions; // counts the total number of collisions + int mTotalAllocs; + #endif + + + //////////////////////////////////////////////////////////////////////////////////// + // This function searches for a handle which already stores the data (assuming the + // handle is a hash within range SIZE_HANDLES). + // + // If it failes, it returns false, and the handle passed in points to the next + // free slot. + //////////////////////////////////////////////////////////////////////////////////// + bool find_existing(int& handle, const void* data, int datasize) + { + #ifdef _DEBUG + mFinds++; + mCurrentCollisions = 0; + #endif + + while (mHandles[handle]) // So long as a handle exists there + { + if (mem::eql((void*)(&mData[mHandles[handle]]), data, datasize)) + { + return true; // found + } + handle=(handle+1)&(SIZE_HANDLES-1); // incriment the handle + + #ifdef _DEBUG + mCurrentCollisions ++; + mTotalCollisions ++; + + //assert(mCurrentCollisions < 16); // If We Had 16+ Collisions, Hash May Be Inefficient. + // Evaluate SIZE and SIZEHANDLES + #endif + } + return false; // failed to find + } + + + + //////////////////////////////////////////////////////////////////////////////////// + // A simple hash function for the range of [0, SIZE_HANDLES] + //////////////////////////////////////////////////////////////////////////////////// + int hash(const void* data, int datasize) + { + int h=0; + for (int i=0; i=0 && handle +class heap_base : public ratl_base +{ +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY + }; + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// +private: + array_base mData; // The Memory + int mPush; // Address Of Next Add Location + + + //////////////////////////////////////////////////////////////////////////////////// + // Returns The Location Of Node (i)'s Parent Node (The Parent Node Of Zero Is Zero) + //////////////////////////////////////////////////////////////////////////////////// + static int parent(int i) + { + return ((i-1)/2); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Returns The Location Of Node (i)'s Left Child (The Child Of A Leaf Is The Leaf) + //////////////////////////////////////////////////////////////////////////////////// + static int left(int i) + { + return (2*i)+1; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Returns The Location Of Node (i)'s Right Child (The Child Of A Leaf Is The Leaf) + //////////////////////////////////////////////////////////////////////////////////// + static int right(int i) + { + return (2*i)+2; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Returns The Location Of Largest Child Of Node (i) + //////////////////////////////////////////////////////////////////////////////////// + int largest_child(int i) const + { + if (left(i)=0 && b>=0 && a0); // Don't Try To Look At This If There Is Nothing In Here + return mData[0]; + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Add A Value To The Queue + //////////////////////////////////////////////////////////////////////////////////// + void push(const TTValue& nValue) + { + assert(size()0); + + mPush--; + + // Swap The Lowest Element Up To The Spot We Just "Erased" + //--------------------------------------------------------- + swap(0, mPush); + mData.destruct(mPush); + + // Fix Possible Heap Inconsistancies + //----------------------------------- + reheapify_downward(0); + assert(valid()); + } + + + +}; +template +class heap_vs : public heap_base > +{ +public: + typedef typename storage::value_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + heap_vs() {} +}; + +template +class heap_os : public heap_base > +{ +public: + typedef typename storage::object_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + heap_os() {} +}; + +template +class heap_is : public heap_base > +{ +public: + typedef typename storage::virtual_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + heap_is() {} +}; + +} +#endif diff --git a/code/ratl/list_vs.h b/code/ratl/list_vs.h new file mode 100644 index 0000000..c5f6b45 --- /dev/null +++ b/code/ratl/list_vs.h @@ -0,0 +1,751 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// List +// ---- +// The list class supports ordered insertion and deletion in O(1) constant time. +// It simulates a linked list of pointers by allocating free spots in a pool and +// maintaining "links" as indicies to the pool array objects. +// +// +// +// NOTES: +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_LIST_VS_INC) +#define RATL_LIST_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if defined(RA_DEBUG_LINKING) + #pragma message("...including list_vs.h") +#endif +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +#if !defined(RATL_POOL_VS_INC) + #include "pool_vs.h" +#endif +namespace ratl +{ + + +// this is private to the list, but you have no access to it, soooo.. +class list_node +{ +public: + int mNext; + int mPrev; +}; + +//////////////////////////////////////////////////////////////////////////////////////// +// The List Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class list_base : public ratl_base +{ +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY + }; +private: + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + pool_base mPool; // The Allocation Data Pool + + int mFront; // The Beginning Of The List + int mBack; // The End Of The List + enum + { + NULL_NODE = -1 + }; + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + list_base() : mFront(NULL_NODE), mBack(NULL_NODE) + { + } + + //////////////////////////////////////////////////////////////////////////////////// + // How Many Objects Are In This List + //////////////////////////////////////////////////////////////////////////////////// + int size() const + { + return (mPool.size()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Are There Any Objects In This List? + //////////////////////////////////////////////////////////////////////////////////// + bool empty() const + { + assert(mFront!=NULL_NODE || size()==0); + return (mFront==NULL_NODE); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Is This List Filled? + //////////////////////////////////////////////////////////////////////////////////// + bool full() const + { + return (mPool.full()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Clear All Elements + //////////////////////////////////////////////////////////////////////////////////// + void clear() + { + mFront = NULL_NODE; + mBack = NULL_NODE; + mPool.clear(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get The First Object In The List + //////////////////////////////////////////////////////////////////////////////////// + TTValue & front() + { + assert(mFront!=NULL_NODE); // this is empty + return mPool[mFront]; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get The Last Object In The List + //////////////////////////////////////////////////////////////////////////////////// + TTValue & back() + { + assert(mBack!=NULL_NODE); + return mPool[mBack]; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get The First Object In The List + //////////////////////////////////////////////////////////////////////////////////// + const TTValue & front() const + { + assert(mFront!=NULL_NODE); // this is empty + return mPool[mFront]; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get The Last Object In The List + //////////////////////////////////////////////////////////////////////////////////// + const TTValue & back() const + { + assert(mBack!=NULL_NODE); + return mPool[mBack]; + } + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Iterator + //////////////////////////////////////////////////////////////////////////////////// + class const_iterator; + class iterator + { + friend class list_base; + friend class const_iterator; + + int mLoc; + list_base* mOwner; + + + public: + // Constructors + //-------------- + iterator() : mOwner(0), mLoc(0) + {} + iterator(list_base* p, int t) : mOwner(p), mLoc(t) + {} + iterator(const iterator &t) : mOwner(t.mOwner), mLoc(t.mLoc) + {} + + // Assignment Operator + //--------------------- + void operator= (const iterator &t) + { + mOwner = t.mOwner; + mLoc = t.mLoc; + } + + + // Equality Operators + //-------------------- + bool operator!=(const iterator &t) const + { + return (mLoc!=t.mLoc || mOwner!=t.mOwner); + } + bool operator==(const iterator &t) const + { + return (mLoc==t.mLoc && mOwner==t.mOwner); + } + + // DeReference Operator + //---------------------- + TTValue& operator* () const + { + assert(mLoc>=0 && mLocmPool[mLoc]); + } + + // DeReference Operator + //---------------------- + TTValue& value() const + { + assert(mLoc>=0 && mLocmPool[mLoc]); + } + + // DeReference Operator + //---------------------- + TTValue* operator-> () const + { + assert(mLoc>=0 && mLocmPool[mLoc]); + } + + // prefix Inc Operator + //-------------- + iterator operator++(int) + { + assert(mLoc>=0 && mLocmPool[mLoc]).mNext; + return old; + } + + // postfix Inc Operator + //-------------- + iterator operator++() + { + assert(mLoc>=0 && mLocmPool[mLoc]).mNext; + return *this; + } + // prefix Inc Operator + //-------------- + iterator operator--(int) + { + assert(mLoc>=0 && mLocmPool[mLoc]).mPrev; + return old; + } + + //-------------- + // postfix Dec Operator + //-------------- + iterator operator--() + { + assert(mLoc>=0 && mLocmPool[mLoc]).mPrev; + return *this; + } + + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Constant Iterator + //////////////////////////////////////////////////////////////////////////////////// + class const_iterator + { + friend class list_base; + + int mLoc; + const list_base* mOwner; + + public: + // Constructors + //-------------- + const_iterator() : mOwner(0), mLoc(0) + {} + const_iterator(const list_base* p, int t) : mOwner(p), mLoc(t) + {} + const_iterator(const const_iterator &t) : mOwner(t.mOwner), mLoc(t.mLoc) + {} + const_iterator(const iterator &t) : mOwner(t.mOwner), mLoc(t.mLoc) + {} + + // Assignment Operator + //--------------------- + void operator= (const const_iterator &t) + { + mOwner = t.mOwner; + mLoc = t.mLoc; + } + // Assignment Operator + //--------------------- + void operator= (const iterator &t) + { + mOwner = t.mOwner; + mLoc = t.mLoc; + } + + + + // Equality Operators + //-------------------- + bool operator!=(const iterator &t) const + { + return (mLoc!=t.mLoc || mOwner!=t.mOwner); + } + bool operator==(const iterator &t) const + { + return (mLoc==t.mLoc && mOwner==t.mOwner); + } + + // Equality Operators + //-------------------- + bool operator!=(const const_iterator &t) const + { + return (mLoc!=t.mLoc || mOwner!=t.mOwner); + } + bool operator==(const const_iterator &t) const + { + return (mLoc==t.mLoc && mOwner==t.mOwner); + } + + // DeReference Operator + //---------------------- + const TTValue& operator* () const + { + assert(mLoc>=0 && mLocmPool[mLoc]); + } + + // DeReference Operator + //---------------------- + const TTValue* operator-> () const + { + assert(mLoc>=0 && mLocmPool[mLoc]); + } + + // DeReference Operator + //---------------------- + const TTValue& value() const + { + assert(mLoc>=0 && mLocmPool[mLoc]); + } + + // prefix Inc Operator + //-------------- + const_iterator operator++(int) + { + assert(mLoc>=0 && mLocmPool[mLoc]).mNext; + return old; + } + + // postfix Inc Operator + //-------------- + const_iterator operator++() + { + assert(mLoc>=0 && mLocmPool[mLoc]).mNext; + return *this; + } + // prefix Inc Operator + //-------------- + const_iterator operator--(int) + { + assert(mLoc>=0 && mLocmPool[mLoc]).mPrev; + return old; + } + + //-------------- + // postfix Dec Operator + //-------------- + const_iterator operator--() + { + assert(mLoc>=0 && mLocmPool[mLoc]).mPrev; + return *this; + } + }; + friend class iterator; + friend class const_iterator; + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Begin (Starts At Address mFront) + //////////////////////////////////////////////////////////////////////////////////// + iterator begin() + { + return iterator(this, mFront); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Begin (Starts At Address mFront) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator begin() const + { + return const_iterator(this, mFront); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Reverse Iterator Begin (Starts At Address mBack) + //////////////////////////////////////////////////////////////////////////////////// + iterator rbegin() + { + return iterator(this, mBack); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Reverse Iterator Begin (Starts At Address mBack) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator rbegin() const + { + return const_iterator(this, mBack); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator End (Set To Address NULL_NODE) Should Work For Forward & Backward Iteration + //////////////////////////////////////////////////////////////////////////////////// + iterator end() + { + return iterator(this, NULL_NODE); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator End (Set To Address NULL_NODE) Should Work For Forward & Backward Iteration + //////////////////////////////////////////////////////////////////////////////////// + const_iterator end() const + { + return const_iterator(this, NULL_NODE); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Insert (BEFORE POINTED ELEMENT) + //////////////////////////////////////////////////////////////////////////////////// + T& insert(const iterator& it) + { + int nNew= mPool.alloc(); + insert_low(it,nNew); + return mPool[mNew]; + } + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Insert Value(BEFORE POINTED ELEMENT) + //////////////////////////////////////////////////////////////////////////////////// + void insert(const iterator& it, const TTValue& val) + { + int nNew= mPool.alloc(val); + insert_low(it,nNew); + } + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Insert Raw(BEFORE POINTED ELEMENT) + //////////////////////////////////////////////////////////////////////////////////// + TRatlNew* insert_raw(const iterator& it) + { + TRatlNew *ret = mPool.alloc_raw(); + insert_low(it,mPool.pointer_to_index(ret)); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Insert (AFTER POINTED ELEMENT) (ALSO - NOT CONSTANT, WILL CHANGE it) + //////////////////////////////////////////////////////////////////////////////////// + T& insert_after(iterator& it) + { + int nNew= mPool.alloc(); + insert_low_after(it,nNew); + it = iterator(this, nNew); + return mPool[mNew]; + } + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Insert Value(AFTER POINTED ELEMENT) (ALSO - NOT CONSTANT, WILL CHANGE it) + //////////////////////////////////////////////////////////////////////////////////// + void insert_after(iterator& it, const TTValue& val) + { + int nNew= mPool.alloc(val); + insert_low_after(it,nNew); + it = iterator(this, nNew); + } + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Insert Raw(AFTER POINTED ELEMENT) (ALSO - NOT CONSTANT, WILL CHANGE it) + //////////////////////////////////////////////////////////////////////////////////// + TRatlNew* insert_raw_after(iterator& it) + { + TRatlNew *ret = mPool.alloc_raw(); + insert_low_after(it,mPool.pointer_to_index(ret)); + it = iterator(this, mPool.pointer_to_index(ret)); + return ret; + } + + + + + //////////////////////////////////////////////////////////////////////////////////// + // Front Insert (BEFORE POINTED ELEMENT) + //////////////////////////////////////////////////////////////////////////////////// + T& push_front() + { + int nNew= mPool.alloc(); + insert_low(begin(),nNew); + return mPool[mNew]; + } + //////////////////////////////////////////////////////////////////////////////////// + // Front Insert Value(BEFORE POINTED ELEMENT) + //////////////////////////////////////////////////////////////////////////////////// + void push_front(const TTValue& val) + { + int nNew= mPool.alloc(val); + insert_low(begin(),nNew); + } + //////////////////////////////////////////////////////////////////////////////////// + // Front Insert Raw(BEFORE POINTED ELEMENT) + //////////////////////////////////////////////////////////////////////////////////// + TRatlNew * push_front_raw() + { + TRatlNew *ret = mPool.alloc_raw(); + insert_low(begin(),mPool.pointer_to_index(ret)); + return ret; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Front Insert (BEFORE POINTED ELEMENT) + //////////////////////////////////////////////////////////////////////////////////// + T& push_back() + { + int nNew= mPool.alloc(); + insert_low(end(),nNew); + return mPool[mNew]; + } + //////////////////////////////////////////////////////////////////////////////////// + // Front Insert Value(BEFORE POINTED ELEMENT) + //////////////////////////////////////////////////////////////////////////////////// + void push_back(const TTValue& val) + { + int nNew= mPool.alloc(val); + insert_low(end(),nNew); + } + //////////////////////////////////////////////////////////////////////////////////// + // Front Insert Raw(BEFORE POINTED ELEMENT) + //////////////////////////////////////////////////////////////////////////////////// + TRatlNew * push_back_raw() + { + TRatlNew *ret = mPool.alloc_raw(); + insert_low(end(),mPool.pointer_to_index(ret)); + return ret; + } + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Erase + //////////////////////////////////////////////////////////////////////////////////// + void erase(iterator& it) + { + assert(it.mOwner==this); // Iterators must be mixed up, this is from a different list. + assert(it.mLoc!=NULL_NODE); + + int At = it.mLoc; + int Prev = T::node(mPool[At]).mPrev; + int Next = T::node(mPool[At]).mNext; + + // LINK: (Prev)<-(At)--(Next) + //-------------------------------------------- + if (Next!=NULL_NODE) + { + T::node(mPool[Next]).mPrev = Prev; + } + + // LINK: (Prev)--(At)->(Next) + //-------------------------------------------- + if (Prev!=NULL_NODE) + { + T::node(mPool[Prev]).mNext = Next; + } + + // UPDATE: Front & Back + //---------------------- + if (At==mBack) + { + mBack = Prev; + } + if (At==mFront) + { + mFront = Next; + } + + // ERASE At + //-------------------------------------------- + mPool.free(At); + it.mLoc = Prev; + } + template + CAST_TO *verify_alloc(CAST_TO *p) const + { + return mPool.verify_alloc(p); + } +private: + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Insert, returns pool index + //////////////////////////////////////////////////////////////////////////////////// + void insert_low(const iterator& it,int nNew) + { + assert(it.mOwner==this); // Iterators must be mixed up, this is from a different list. + + int Next = it.mLoc; + int Prev = NULL_NODE; + if (Next!=NULL_NODE) + { + Prev = T::node(mPool[Next]).mPrev; + } + else + { + Prev = mBack; + } + + + assert(nNew!=Next && nNew!=Prev); + + + // LINK: (Prev)<-(New)->(Next) + //-------------------------------------------- + T::node(mPool[nNew]).mPrev = Prev; + T::node(mPool[nNew]).mNext = Next; + + + // LINK: (New)<-(Next) + //-------------------------------------------- + if (Next!=NULL_NODE) + { + T::node(mPool[Next]).mPrev = nNew; + assert(T::node(mPool[Next]).mPrev!=T::node(mPool[Next]).mNext); + } + else + { + mBack = nNew; + } + + + // LINK: (Prev)->(New) + //-------------------------------------------- + if (Prev!=NULL_NODE) + { + T::node(mPool[Prev]).mNext = nNew; + assert(T::node(mPool[Prev]).mPrev!=T::node(mPool[Prev]).mNext); + } + else + { + mFront = nNew; + } + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Insert, returns pool index (AFTER POINTED ELEMENT) + //////////////////////////////////////////////////////////////////////////////////// + void insert_low_after(const iterator& it,int nNew) + { + assert(it.mOwner==this); // Iterators must be mixed up, this is from a different list. + + int Next = NULL_NODE;//it.mLoc; + int Prev = it.mLoc;//NULL_NODE; + if (Prev!=NULL_NODE) + { + Next = T::node(mPool[Prev]).mNext; + } + else + { + Prev = mFront; + } + + + assert(nNew!=Next && nNew!=Prev); + + + // LINK: (Prev)<-(New)->(Next) + //-------------------------------------------- + T::node(mPool[nNew]).mPrev = Prev; + T::node(mPool[nNew]).mNext = Next; + + + // LINK: (New)<-(Next) + //-------------------------------------------- + if (Next!=NULL_NODE) + { + T::node(mPool[Next]).mPrev = nNew; + assert(T::node(mPool[Next]).mPrev!=T::node(mPool[Next]).mNext); + } + else + { + mBack = nNew; + } + + + // LINK: (Prev)->(New) + //-------------------------------------------- + if (Prev!=NULL_NODE) + { + T::node(mPool[Prev]).mNext = nNew; + assert(T::node(mPool[Prev]).mPrev!=T::node(mPool[Prev]).mNext); + } + else + { + mFront = nNew; + } + } + +}; + +template +class list_vs : public list_base > +{ +public: + typedef typename storage::value_semantics_node TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + list_vs() {} +}; + +template +class list_os : public list_base > +{ +public: + typedef typename storage::object_semantics_node TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + list_os() {} +}; + +template +class list_is : public list_base > +{ +public: + typedef typename storage::virtual_semantics_node TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + list_is() {} +}; + +} +#endif \ No newline at end of file diff --git a/code/ratl/map_vs.h b/code/ratl/map_vs.h new file mode 100644 index 0000000..ded931e --- /dev/null +++ b/code/ratl/map_vs.h @@ -0,0 +1,1629 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Map +// --- +// This map is based on a red black tree, which guarentees balanced data, no mater what +// order elements are added. The map uses a memory pool for storage of node data. +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_MAP_VS_INC) +#define RATL_MAP_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if defined(RA_DEBUG_LINKING) + #pragma message("...including map_vs.h") +#endif +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +#if !defined(RATL_POOL_VS_INC) + #include "pool_vs.h" +#endif +namespace ratl +{ + + +// this is private to the set, but you have no access to it, soooo.. + +class tree_node +{ + int mParent; + int mLeft; + int mRight; +public: + enum + { + RED_BIT = 0x40000000, // to save space we are putting the red bool in a high bit + // this is in the parent only + NULL_NODE = 0x3fffffff, // this must not have the red bit set + }; + void init() + { + mLeft = NULL_NODE; + mRight = NULL_NODE; + mParent = NULL_NODE | RED_BIT; + } + int left() const + { + return mLeft; + } + int right() const + { + return mRight; + } + int parent() const + { + return mParent & (~RED_BIT); + } + bool red() const + { + return !!(mParent & RED_BIT); + } + void set_left(int l) + { + mLeft = l; + } + void set_right(int r) + { + mRight = r; + } + void set_parent(int p) + { + mParent &= RED_BIT; + mParent |= p; + } + void set_red(bool isRed) + { + if (isRed) + { + mParent |= RED_BIT; + } + else + { + mParent &= ~RED_BIT; + } + } +}; + +//fixme void *, comparison function pointer-ize this for code bloat. + +template +class tree_base +{ +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY, + }; +private: + pool_base mPool; // The Allocation Data Pool + int mRoot; + int mLastAdd; + + + void link_left(int node,int left) + { + T::node(mPool[node]).set_left(left); + if (left!=tree_node::NULL_NODE) + { + T::node(mPool[left]).set_parent(node); + } + } + + void link_right(int node,int right) + { + T::node(mPool[node]).set_right(right); + if (right!=tree_node::NULL_NODE) + { + T::node(mPool[right]).set_parent(node); + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // This is the map internal recursive find function - do not use externally + //////////////////////////////////////////////////////////////////////////////////// + int find_internal(const TTValue &key, int at) const + { + + // FAIL TO FIND? + //--------------- + if (at==tree_node::NULL_NODE) + { + return tree_node::NULL_NODE; + } + + // Should We Search Left? + //------------------------ + if (key + CAST_TO *verify_alloc_key(CAST_TO *p) const + { + return mPool.verify_alloc(p); + } + + void insert_alloced_key() + { + assert(mLastAdd>=0&&mLastAdd=0&&mLastAdd=0&&i=0&&i +class set_base : public tree_base +{ + +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Adds Element Value At Location Key - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + void insert(const TTValue &key) + { + + assert(!IS_MULTI || find_index(key)==tree_node::NULL_NODE); //fixme handle duplicates more sensibly? + + alloc_key(key); + insert_alloced_key(); + + } + + //////////////////////////////////////////////////////////////////////////////////// + // Allocs an item, when filled, call insert_alloced + //////////////////////////////////////////////////////////////////////////////////// + TTValue & alloc() + { + return alloc_key(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Allocs an item (raw), when constucted, call insert_alloced + //////////////////////////////////////////////////////////////////////////////////// + TRatlNew *alloc_raw() + { + return alloc_key_raw(); + } + template + CAST_TO *verify_alloc(CAST_TO *p) const + { + return verify_alloc_key(p); + } + + void insert_alloced() + { + insert_alloced_key(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Removes The First Element With Key (key) - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + void erase(const TTValue &key) + { + //fixme this is a double search currently + int i=find_index(key); + if (i!=tree_node::NULL_NODE) + { + erase_index(i); + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator + // + // A map is sorted in ascending order on the KEY type. ++ and -- are both + // O((log n)^2) operations + // + //////////////////////////////////////////////////////////////////////////////////// + class iterator + { + friend class set_base; + friend class const_iterator; + + int mLoc; + set_base* mOwner; + + public: + iterator(set_base *owner=0, int loc=tree_node::NULL_NODE) : + mOwner(owner), + mLoc(loc) + { + } + iterator(const iterator &o) : + mOwner(o.mOwner), + mLoc(o.mLoc) + { + } + + void operator=(const iterator &o) + { + mOwner=o.mOwner; + mLoc=o.mLoc; + } + + iterator operator++() //prefix + { + assert(mOwner); + mLoc=mOwner->next(mLoc); + return *this; + } + iterator operator++(int) //postfix + { + assert(mOwner); + iterator old(*this); + mLoc=mOwner->next(mLoc); + return old; + } + + iterator operator--() //prefix + { + assert(mOwner); + mLoc=mOwner->previous(mLoc); + return *this; + } + iterator operator--(int) //postfix + { + assert(mOwner); + iterator old(*this); + mLoc=mOwner->previous(mLoc); + return old; + } + + bool operator!=(const iterator p) const {return (mLoc!=p.mLoc || mOwner!=p.mOwner);} + bool operator==(const iterator p) const {return (mLoc==p.mLoc && mOwner==p.mOwner);} + + const TTValue & operator*() const + { + assert(mOwner); + assert(mLoc>=0&&mLocindex_to_key(mLoc); + } + const TTValue * operator->() const + { + assert(mOwner); + assert(mLoc>=0&&mLocindex_to_key(mLoc); + } + }; + class const_iterator + { + friend class set_base; + + int mLoc; + + const set_base* mOwner; + + public: + const_iterator(const set_base *owner=0, int loc=tree_node::NULL_NODE) : + mOwner(owner), + mLoc(loc) + { + } + const_iterator(const const_iterator &o) : + mOwner(o.mOwner), + mLoc(o.mLoc) + { + } + const_iterator(const iterator &o) : + mOwner(o.mOwner), + mLoc(o.mLoc) + { + } + void operator=(const const_iterator &o) + { + mOwner=o.mOwner; + mLoc=o.mLoc; + } + void operator=(const iterator &o) + { + mOwner=o.mOwner; + mLoc=o.mLoc; + } + + + const_iterator operator++() //prefix + { + assert(mOwner); + mLoc=mOwner->next(mLoc); + return *this; + } + const_iterator operator++(int) //postfix + { + assert(mOwner); + const_iterator old(*this); + mLoc=mOwner->next(mLoc); + return old; + } + + const_iterator operator--() //prefix + { + assert(mOwner); + mLoc=mOwner->previous(mLoc); + return *this; + } + const_iterator operator--(int) //postfix + { + assert(mOwner); + const_iterator old(*this); + mLoc=mOwner->previous(mLoc); + return old; + } + + bool operator!=(const const_iterator p) const {return (mLoc!=p.mLoc || mOwner!=p.mOwner);} + bool operator==(const const_iterator p) const {return (mLoc==p.mLoc && mOwner==p.mOwner);} + bool operator!=(const iterator p) const {return (mLoc!=p.mLoc || mOwner!=p.mOwner);} + bool operator==(const iterator p) const {return (mLoc==p.mLoc && mOwner==p.mOwner);} + + const TTValue & operator*() const + { + assert(mOwner); + assert(mLoc>=0&&mLocindex_to_key(mLoc); + } + const TTValue * operator->() const + { + assert(mOwner); + assert(mLoc>=0&&mLocindex_to_key(mLoc); + } + }; + friend class iterator; + friend class const_iterator; + + //////////////////////////////////////////////////////////////////////////////////// + // Seach For A Given Key. Will Return end() if Failed - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + iterator find(const TTValue &key) + { + return iterator(this,find_index(key)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The Smallest Element - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + iterator begin() + { + return iterator(this, front()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The Largest Element - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + iterator rbegin() + { + return iterator(this, back()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Invalid Iterator, Use As A Stop Condition In Your For Loops - O(1) + //////////////////////////////////////////////////////////////////////////////////// + iterator end() + { + return iterator(this); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Seach For A Given Key. Will Return end() if Failed - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator find(const TTValue &key) const + { + return const_iterator(this, find_index(key)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An const_iterator To The Smallest Element - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator begin() const + { + return const_iterator(this, front()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An const_iterator To The Largest Element - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator rbegin() const + { + return const_iterator(this, back()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Invalid const_iterator, Use As A Stop Condition In Your For Loops - O(1) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator end() const + { + return const_iterator(this); + } + //////////////////////////////////////////////////////////////////////////////////// + // Removes The Element Pointed To At (it) And Decrements (it) - O((log n)^2) + //////////////////////////////////////////////////////////////////////////////////// + void erase(const iterator &it) + { + assert(it.mOwner==this && it.mLoc>=0&&it.mLoc +class set_vs : public set_base,0 > +{ +public: + typedef typename storage::value_semantics_node TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + set_vs() {} +}; + +template +class set_os : public set_base,0 > +{ +public: + typedef typename storage::object_semantics_node TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + set_os() {} +}; + +template +class set_is : public set_base,0 > +{ +public: + typedef typename storage::virtual_semantics_node TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + set_is() {} +}; + + +template +class map_base : public tree_base +{ +public: + typedef typename K TKeyStorageTraits; + typedef typename K::TValue TKTValue; + typedef typename V TValueStorageTraits; + typedef typename V::TValue TVTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = K::CAPACITY + }; +private: + array_base mValues; +public: + map_base() + { + compile_assert(); + } + + void clear() + { + tree_base::clear(); + mValues.clear(); + } + //////////////////////////////////////////////////////////////////////////////////// + // Adds Element Value At Location Key - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + void insert(const TKTValue &key,const TVTValue &value) + { + assert(!IS_MULTI || find_index(key)==tree_node::NULL_NODE); //fixme handle duplicates more sensibly? + + alloc_key(key); + insert_alloced_key(); + assert(check_validity()); + mValues.construct(index_of_alloced_key(),value); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Adds Element Value At Location Key returns a reference + //////////////////////////////////////////////////////////////////////////////////// + TVTValue &insert(const TKTValue &key) + { + + assert(!IS_MULTI || find_index(key)==tree_node::NULL_NODE); //fixme handle duplicates more sensibly? + + alloc_key(key); + insert_alloced_key(); + + int idx=index_of_alloced_key(); + assert(check_validity()); + mValues.construct(idx); + return mValues[idx]; + } + //////////////////////////////////////////////////////////////////////////////////// + // Adds Element Value At Location Key returns a rew pointer for placement new + //////////////////////////////////////////////////////////////////////////////////// + TRatlNew *insert_raw(const TKTValue &key) + { + + assert(!IS_MULTI || find_index(key)==tree_node::NULL_NODE); //fixme handle duplicates more sensibly? + + alloc_key(key); + insert_alloced_key(); + assert(check_validity()); + return mValues.alloc_raw(index_of_alloced_key()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // After calling alloc_key*, you may call this to alloc the value + //////////////////////////////////////////////////////////////////////////////////// + TVTValue &alloc_value() + { + mValues.construct(index_of_alloced_key()); + return mValues[index_of_alloced_key()]; + } + + //////////////////////////////////////////////////////////////////////////////////// + // After calling alloc_key*, you may call this to alloc_raw the value + //////////////////////////////////////////////////////////////////////////////////// + TRatlNew *alloc_value_raw() + { + return mValues.alloc_raw(index_of_alloced_key()); + } + + template + CAST_TO *verify_alloc(CAST_TO *p) const + { + return mValues.verify_alloc(p); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Removes The First Element With Key (key) - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + void erase(const TKTValue &key) + { + //fixme this is a double search currently + int i=find_index(key); + if (i!=tree_node::NULL_NODE) + { + erase_index(i); + mValues.destruct(i); + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator + // + // A map is sorted in ascending order on the KEY type. ++ and -- are both + // O((log n)^2) operations + // + //////////////////////////////////////////////////////////////////////////////////// + class const_iterator; + class iterator + { + friend class map_base; + friend class const_iterator; + + int mLoc; + map_base* mOwner; + + public: + iterator(map_base *owner=0, int loc=tree_node::NULL_NODE) : + mOwner(owner), + mLoc(loc) + { + } + iterator(const iterator &o) : + mOwner(o.mOwner), + mLoc(o.mLoc) + { + } + + void operator=(const iterator &o) + { + mOwner=o.mOwner; + mLoc=o.mLoc; + } + + iterator operator++() //prefix + { + assert(mOwner); + mLoc=mOwner->next(mLoc); + return *this; + } + iterator operator++(int) //postfix + { + assert(mOwner); + iterator old(*this); + mLoc=mOwner->next(mLoc); + return old; + } + + iterator operator--() //prefix + { + assert(mOwner); + mLoc=mOwner->previous(mLoc); + return *this; + } + iterator operator--(int) //postfix + { + assert(mOwner); + iterator old(*this); + mLoc=mOwner->previous(mLoc); + return old; + } + + bool operator!=(const iterator &p) const {return (mLoc!=p.mLoc || mOwner!=p.mOwner);} + bool operator==(const iterator &p) const {return (mLoc==p.mLoc && mOwner==p.mOwner);} + + TVTValue & operator*() const + { + assert(mOwner); + assert(mLoc>=0&&mLocmValues[mLoc]; + } + const TKTValue & key() const + { + assert(mOwner); + assert(mLoc>=0&&mLocindex_to_key(mLoc); + } + TVTValue & value() const + { + assert(mOwner); + assert(mLoc>=0&&mLocmValues[mLoc]; + } + TVTValue * operator->() const + { + assert(mOwner); + assert(mLoc>=0&&mLocmValues[mLoc]; + } + }; + class const_iterator + { + friend class map_base; + + int mLoc; + const map_base* mOwner; + + public: + const_iterator(const map_base *owner=0, int loc=tree_node::NULL_NODE) : + mOwner(owner), + mLoc(loc) + { + } + const_iterator(const const_iterator &o) : + mOwner(o.mOwner), + mLoc(o.mLoc) + { + } + const_iterator(const iterator &o) : + mOwner(o.mOwner), + mLoc(o.mLoc) + { + } + void operator=(const const_iterator &o) + { + mOwner=o.mOwner; + mLoc=o.mLoc; + } + void operator=(const iterator &o) + { + mOwner=o.mOwner; + mLoc=o.mLoc; + } + + + const_iterator operator++() //prefix + { + assert(mOwner); + mLoc=mOwner->next(mLoc); + return *this; + } + const_iterator operator++(int) //postfix + { + assert(mOwner); + const_iterator old(*this); + mLoc=mOwner->next(mLoc); + return old; + } + + const_iterator operator--() //prefix + { + assert(mOwner); + mLoc=mOwner->previous(mLoc); + return *this; + } + const_iterator operator--(int) //postfix + { + assert(mOwner); + const_iterator old(*this); + mLoc=mOwner->previous(mLoc); + return old; + } + + bool operator!=(const const_iterator &p) const {return (mLoc!=p.mLoc || mOwner!=p.mOwner);} + bool operator==(const const_iterator &p) const {return (mLoc==p.mLoc && mOwner==p.mOwner);} + bool operator!=(const iterator &p) const {return (mLoc!=p.mLoc || mOwner!=p.mOwner);} + bool operator==(const iterator &p) const {return (mLoc==p.mLoc && mOwner==p.mOwner);} + + const TVTValue & operator*() const + { + assert(mOwner); + assert(mLoc>=0&&mLocmValues[mLoc]; + } + const TKTValue & key() const + { + assert(mOwner); + assert(mLoc>=0&&mLocindex_to_key(mLoc); + } + const TVTValue & value() const + { + assert(mOwner); + assert(mLoc>=0&&mLocmValues[mLoc]; + } + const TVTValue * operator->() const + { + assert(mOwner); + assert(mLoc>=0&&mLocmValues[mLoc]; + } + }; + friend class iterator; + friend class const_iterator; + + //////////////////////////////////////////////////////////////////////////////////// + // Seach For A Given Key. Will Return end() if Failed - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + iterator find(const TKTValue &key) + { + return iterator(this,find_index(key)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The Smallest Element - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + iterator begin() + { + return iterator(this, front()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The Largest Element - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + iterator rbegin() + { + return iterator(this, back()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Invalid Iterator, Use As A Stop Condition In Your For Loops - O(1) + //////////////////////////////////////////////////////////////////////////////////// + iterator end() + { + return iterator(this); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Seach For A Given Key. Will Return end() if Failed - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator find(const TKTValue &key) const + { + return const_iterator(this, find_index(key)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An const_iterator To The Smallest Element - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator begin() const + { + return const_iterator(this, front()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An const_iterator To The Largest Element - O(log n) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator rbegin() const + { + return const_iterator(this, back()); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Invalid const_iterator, Use As A Stop Condition In Your For Loops - O(1) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator end() const + { + return const_iterator(this); + } + //////////////////////////////////////////////////////////////////////////////////// + // Removes The Element Pointed To At (it) And Decrements (it) - O((log n)^2) + //////////////////////////////////////////////////////////////////////////////////// + void erase(const iterator &it) + { + assert(it.mOwner==this && it.mLoc>=0&&it.mLoc +class map_vs : public map_base< + storage::value_semantics_node, + storage::value_semantics, + 0 > +{ +public: + typedef typename storage::value_semantics VStorageTraits; + typedef typename VStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + map_vs() {} +}; + +template +class map_os : public map_base< + storage::value_semantics_node, + storage::object_semantics, + 0 > +{ +public: + typedef typename storage::object_semantics VStorageTraits; + typedef typename VStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + map_os() {} +}; + +template +class map_is : public map_base< + storage::value_semantics_node, + storage::virtual_semantics, + 0 > +{ +public: + typedef typename storage::virtual_semantics VStorageTraits; + typedef typename VStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + map_is() {} +}; + +} + +#endif diff --git a/code/ratl/pool_vs.h b/code/ratl/pool_vs.h new file mode 100644 index 0000000..5cd87bd --- /dev/null +++ b/code/ratl/pool_vs.h @@ -0,0 +1,570 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Memory Pool +// ----------- +// The memory pool class is a simple routine for constant time allocation and deallocation +// from a fixed size pool of objects. The class uses a simple array to hold the actual +// data, a queue for the free list, and a bit field to mark which spots in the array +// are allocated. +// +// +// +// NOTES: +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_POOL_VS_INC) +#define RATL_POOL_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +#if !defined(RATL_QUEUE_VS_INC) + #include "queue_vs.h" +#endif + +namespace ratl +{ + +// fixme, this could be made more efficient by keepingtrack of the highest slot ever used +// then there is no need to fill the free list +template +class pool_root : public ratl_base +{ +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY + }; +private: + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + array_base mData; + queue_vs mFree; + bits_base mUsed; + int mSize; + + + void FillFreeList() + { + mFree.clear(); + int i; + for (i=0;i0); + + int index=mData.pointer_to_index(me); + + assert(index>=0 && index0); + + int index=mData.pointer_to_index(me); + + assert(index>=0 && index=0 && i=0 && j0); + assert(i>=0 && i; + friend class const_iterator; + int mIndex; + pool_root* mOwner; + public: + // Constructors + //-------------- + iterator() : mOwner(0) + {} + iterator(pool_root* p, int index) : mOwner(p), mIndex(index) + {} + iterator(const iterator &t) : mOwner(t.mOwner), mIndex(t.mIndex) + {} + + // Assignment Operator + //--------------------- + void operator= (const iterator &t) + { + mOwner = t.mOwner; + mIndex = t.mIndex; + } + + // Equality Operators + //-------------------- + bool operator!=(const iterator& t) {assert(mOwner && mOwner==t.mOwner); return (mIndex!=t.mIndex);} + bool operator==(const iterator& t) {assert(mOwner && mOwner==t.mOwner); return (mIndex==t.mIndex);} + + // Dereference Operators + //---------------------- + TTValue& operator* () const {assert(mOwner && mOwner->is_used_index(mIndex)); return (mOwner->mData[mIndex]);} + TTValue* operator->() const {assert(mOwner && mOwner->is_used_index(mIndex)); return (&mOwner->mData[mIndex]);} + + // Handle & Index Access + //----------------------- + int index() {assert(mOwner && mOwner->is_used_index(mIndex)); return mIndex;} + + // Inc Operator + //------------- + iterator operator++(int) // postfix + { + assert(mIndex>=0&&mIndexis_used_index(mIndex)); + + iterator ret(*this); + mIndex = mOwner->mUsed.next_bit(mIndex+1); + return ret; + } + // Inc Operator + //------------- + iterator operator++() // prefix + { + assert(mIndex>=0&&mIndexis_used_index(mIndex)); + mIndex = mOwner->mUsed.next_bit(mIndex+1); + return *this; + } + }; + friend class iterator; + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator + //////////////////////////////////////////////////////////////////////////////////// + class const_iterator + { + int mIndex; + const pool_root* mOwner; + public: + // Constructors + //-------------- + const_iterator() : mOwner(0) + {} + const_iterator(const pool_root* p, int index) : mOwner(p), mIndex(index) + {} + const_iterator(const iterator &t) : mOwner(t.mOwner), mIndex(t.mIndex) + {} + const_iterator(const const_iterator &t) : mOwner(t.mOwner), mIndex(t.mIndex) + {} + + // Equality Operators + //-------------------- + bool operator!=(const const_iterator& t) const {assert(mOwner && mOwner==t.mOwner); return (mIndex!=t.mIndex);} + bool operator==(const const_iterator& t) const {assert(mOwner && mOwner==t.mOwner); return (mIndex==t.mIndex);} + bool operator!=(const iterator& t) const {assert(mOwner && mOwner==t.mOwner); return (mIndex!=t.mIndex);} + bool operator==(const iterator& t) const {assert(mOwner && mOwner==t.mOwner); return (mIndex==t.mIndex);} + + // Dereference Operators + //---------------------- + const TTValue& operator* () const {assert(mOwner && mOwner->is_used_index(mIndex)); return (mOwner->mData[mIndex]);} + const TTValue* operator->() const {assert(mOwner && mOwner->is_used_index(mIndex)); return (&mOwner->mData[mIndex]);} + + // Handle & Index Access + //----------------------- + int index() const {assert(mOwner && mOwner->is_used_index(mIndex)); return mIndex;} + + // Inc Operator + //------------- + const_iterator operator++(int) // postfix + { + assert(mIndex>=0&&mIndexis_used_index(mIndex)); + + const_iterator ret(*this); + mIndex = mOwner->mUsed.next_bit(mIndex+1); + return ret; + } + // Inc Operator + //------------- + const_iterator operator++() // prefix + { + assert(mIndex>=0&&mIndexis_used_index(mIndex)); + mIndex = mOwner->mUsed.next_bit(mIndex+1); + return *this; + } + }; + friend class const_iterator; + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The First Allocated Memory Block + //////////////////////////////////////////////////////////////////////////////////// + iterator begin() + { + int idx=mUsed.next_bit(0); + return iterator(this,idx); // Find The First Allocated + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The Object At index + //////////////////////////////////////////////////////////////////////////////////// + iterator at_index(int index) + { + assert(mUsed[index]); // disallow iterators to non alloced things + return iterator(this, index); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The End Of The Memroy (One Step Beyond) + //////////////////////////////////////////////////////////////////////////////////// + iterator end() + { + return iterator(this, CAPACITY); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The First Allocated Memory Block + //////////////////////////////////////////////////////////////////////////////////// + const_iterator begin() const + { + int idx=mUsed.next_bit(0); + return iterator(this,idx); // Find The First Allocated + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The Object At index + //////////////////////////////////////////////////////////////////////////////////// + const_iterator at_index(int index) const + { + assert(mUsed[index]); // disallow iterators to non alloced things + return iterator(this, index); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The End Of The Memroy (One Step Beyond) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator end() const + { + return iterator(this, CAPACITY); + } + + template + CAST_TO *verify_alloc(CAST_TO *p) const + { + return mData.verify_alloc(p); + } +}; + +/* +pool_base, base class for the pools + +operations: + +size() +empty() +full() +clear() op[] +at_index() op[] +at_index() const +index pointer_to_index(ptr) +index alloc_index() alloc() +index alloc_index(ref) alloc() +ptr alloc_raw() +free_index(index) +free(ptr) +is_used_index(index) + +*/ + +template +class pool_base : public pool_root +{ +public: + typedef typename T::TValue TTValue; + + //////////////////////////////////////////////////////////////////////////////////// + // Constant Accessor + //////////////////////////////////////////////////////////////////////////////////// + const TTValue& operator[](int i) const + { + return value_at_index(i); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Accessor + //////////////////////////////////////////////////////////////////////////////////// + TTValue& operator[](int i) + { + return value_at_index(i); + } + + bool is_used(int i) const + { + return is_used_index(i); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Swap two items based on index + //////////////////////////////////////////////////////////////////////////////////// + void swap(int i,int j) + { + swap_index(i,j); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Allocator returns an index + //////////////////////////////////////////////////////////////////////////////////// + int alloc() + { + return alloc_index(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Allocator returns an index + //////////////////////////////////////////////////////////////////////////////////// + int alloc(const TTValue &v) + { + return alloc_index(v); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Deallocator + //////////////////////////////////////////////////////////////////////////////////// + void free(int i) + { + free_index(i); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The Object At index + //////////////////////////////////////////////////////////////////////////////////// + pool_root::iterator at(int index) + { + return at_index(index); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The Object At index + //////////////////////////////////////////////////////////////////////////////////// + pool_root::const_iterator at(int index) const + { + return at_index(index); + } +}; + +template +class pool_vs : public pool_base > +{ +public: + typedef typename storage::value_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + pool_vs() {} +}; + +template +class pool_os : public pool_base > +{ +public: + typedef typename storage::object_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + pool_os() {} +}; + +template +class pool_is : public pool_base > +{ +public: + typedef typename storage::virtual_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + pool_is() {} +}; + +} +#endif \ No newline at end of file diff --git a/code/ratl/queue_vs.h b/code/ratl/queue_vs.h new file mode 100644 index 0000000..31e6e0a --- /dev/null +++ b/code/ratl/queue_vs.h @@ -0,0 +1,231 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Queue Template +// -------------- +// The queue is a circular buffer of objects which supports a push at the "front" and a +// pop at the "back". Therefore it is: +// +// First In, First Out +// +// As the pointers to the push and pop locations are changed it wrapps around the end +// of the array and back to the front. There are asserts to make sure it never goes +// beyond the capacity of the object. +// +// +// +// NOTES: +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_QUEUE_VS_INC) +#define RATL_QUEUE_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +namespace ratl +{ + +//////////////////////////////////////////////////////////////////////////////////////// +// The Queue Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class queue_base : public ratl_base +{ +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY + }; +private: + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + array_base mData; // The Memory + int mPush; // Address Of Next Add Location + int mPop; // Address Of Next Remove Location + int mSize; + + + int push_low() + { + assert(size()=CAPACITY) + { + mPush=0; + return CAPACITY-1; + } + return mPush-1; + } + + +public: + typedef T TStorageTraits; + + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + queue_base() : mPush(0), mPop(0), mSize(0) + { + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get The Size (The Difference Between The Push And Pop "Pointers") + //////////////////////////////////////////////////////////////////////////////////// + int size() const + { + return mSize; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Check To See If The Size Is Zero + //////////////////////////////////////////////////////////////////////////////////// + bool empty() const + { + return !mSize; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Check To See If The Size Is Full + //////////////////////////////////////////////////////////////////////////////////// + bool full() const + { + return mSize>=CAPACITY; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Empty Out The Entire Queue + //////////////////////////////////////////////////////////////////////////////////// + void clear() + { + mPush = 0; + mPop = 0; + mSize = 0; + mData.clear(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Add A Value, returns a reference to the value in place + //////////////////////////////////////////////////////////////////////////////////// + TTValue & push() + { + int idx=push_low(); + mData.construct(idx); + return mData[idx]; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Add A Value to the Queue + //////////////////////////////////////////////////////////////////////////////////// + void push(const TTValue& v) + { + mData.construct(push_low(),v); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Add A Value to the Queue, returning a void * to the memory + //////////////////////////////////////////////////////////////////////////////////// + TRatlNew *push_raw() + { + return mData.alloc_raw(push_low()); + } + //////////////////////////////////////////////////////////////////////////////////// + // Remove A Value From The Queue + //////////////////////////////////////////////////////////////////////////////////// + void pop() + { + assert(size()>0); + + mData.destruct(mPop); + // Update Pop Location + //--------------------- + mPop++; + if (mPop>=CAPACITY) + { + mPop=0; + } + + mSize--; + } + + TTValue & top() + { + assert(size()>0); + return mData[mPop]; + } + + const TTValue & top() const + { + assert(size()>0); + return mData[mPop]; + } + template + CAST_TO *verify_alloc(CAST_TO *p) const + { + return mData.verify_alloc(p); + } +}; + +template +class queue_vs : public queue_base > +{ +public: + typedef typename storage::value_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + queue_vs() {} +}; + +template +class queue_os : public queue_base > +{ +public: + typedef typename storage::object_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + queue_os() {} +}; + +template +class queue_is : public queue_base > +{ +public: + typedef typename storage::virtual_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + queue_is() {} +}; + +} +#endif \ No newline at end of file diff --git a/code/ratl/ratl.cpp b/code/ratl/ratl.cpp new file mode 100644 index 0000000..6fa94ba --- /dev/null +++ b/code/ratl/ratl.cpp @@ -0,0 +1,130 @@ +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif + +#if 0 +#include "array_vs.h" +#include "bits_vs.h" +#include "heap_vs.h" +#include "pool_vs.h" +#include "list_vs.h" +#include "queue_vs.h" +#include "stack_vs.h" +#include "string_vs.h" +#include "vector_vs.h" +#include "handle_pool_vs.h" +#include "hash_pool_vs.h" +#include "map_vs.h" +#include "scheduler_vs.h" +#endif + +#if !defined(CTYPE_H_INC) + #include + #define CTYPE_H_INC +#endif + +#if !defined(STDARG_H_INC) + #include + #define STDARG_H_INC +#endif + +#if !defined(STDIO_H_INC) + #include + #define STDIO_H_INC +#endif + + +#if !defined(RUFL_HFILE_INC) + #include "..\Rufl\hfile.h" +#endif + + +void* ratl::ratl_base::OutputPrint = 0; + + + +namespace ratl +{ + +#ifdef _DEBUG + int HandleSaltValue=1027; //this is used in debug for global uniqueness of handles + int FoolTheOptimizer=5; //this is used to make sure certain things aren't optimized out +#endif + + +#ifndef _XBOX +void ratl_base::save(hfile& file) +{ +} + +void ratl_base::load(hfile& file) +{ +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// A Profile Print Function +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(FINAL_BUILD) +void ratl_base::ProfilePrint(const char * format, ...) +{ + static char string[2][1024]; // in case this is called by nested functions + static int index = 0; + static char nFormat[300]; + char* buf; + + // Tack On The Standard Format Around The Given Format + //----------------------------------------------------- + sprintf(nFormat, "[PROFILE] %s\n", format); + + + // Resolve Remaining Elipsis Parameters Into Newly Formated String + //----------------------------------------------------------------- + buf = string[index & 1]; + index++; + + va_list argptr; + va_start (argptr, format); + vsprintf (buf, nFormat, argptr); + va_end (argptr); + + // Print It To Debug Output Console + //---------------------------------- + if (OutputPrint!=0) + { + void (*OutputPrintFcn)(const char* text) = (void (__cdecl*)(const char*))OutputPrint; + OutputPrintFcn(buf); + } +} +#else +void ratl_base::ProfilePrint(const char * format, ...) +{ +} +#endif + +namespace str +{ + void to_upper(char *dest) + { + for (int i=0; i +#define ASSERT_H_INC +#endif + +#if !defined(STRING_H_INC) +#include +#define STRING_H_INC +#endif + + +//////////////////////////////////////////////////////////////////////////////////////// +// Forward Dec. +//////////////////////////////////////////////////////////////////////////////////////// +class hfile; + + + +// I don't know why this needs to be in the global namespace, but it does +class TRatlNew; +inline void *operator new(size_t,TRatlNew *where) +{ + return where; +} + +inline void operator delete(void *, TRatlNew *) +{ + return; +} + +namespace ratl +{ + + + +#ifdef _DEBUG +extern int HandleSaltValue; //this is used in debug for global uniqueness of handles +extern int FoolTheOptimizer; //this is used to make sure certain things aren't optimized out +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// All Raven Template Library Internal Memory Operations +// +// This is mostly for future use. For now, they only provide a simple interface with +// a couple extra functions (eql and clr). +//////////////////////////////////////////////////////////////////////////////////////// +namespace mem +{ +//////////////////////////////////////////////////////////////////////////////////////// +// The Align Struct Is The Root Memory Structure for Inheritance and Object Semantics +// +// In most cases, we just want a simple int. However, sometimes we need to use an +// unsigned character array +// +//////////////////////////////////////////////////////////////////////////////////////// +#if defined(_MSC_VER) && !defined(__MWERKS__) + struct alignStruct + { + int space; + }; +#else + struct alignStruct + { + unsigned char space[16]; + } __attribute__ ((aligned(16))); +#endif + + inline void* cpy( void *dest, const void *src, size_t count ) + { + return memcpy(dest, src, count); + } + inline void* set( void *dest, int c, size_t count ) + { + return memset(dest, c, count); + } + inline int cmp( const void *buf1, const void *buf2, size_t count ) + { + return memcmp( buf1, buf2, count ); + } + inline bool eql( const void *buf1, const void *buf2, size_t count ) + { + return (memcmp( buf1, buf2, count )==0); + } + inline void* zero( void *dest, size_t count ) + { + return memset(dest, 0, count); + } + + template + inline void cpy( T *dest, const T *src) + { + cpy(dest, src, sizeof(T)); + } + template + inline void set(T *dest, int c) + { + set(dest, c, sizeof(T)); + } + + template + inline void swap(T *s1, T *s2) + { + unsigned char temp[sizeof(T)]; + cpy((T *)temp,s1); + cpy(s1,s2); + cpy(s2,(T *)temp); + } + + template + inline int cmp( const T *buf1, const T *buf2) + { + return cmp( buf1, buf2, sizeof(T) ); + } + + template + inline bool eql( const T *buf1, const T *buf2) + { + return cmp( buf1, buf2,sizeof(T))==0; + } + + template + inline void zero( T *dest ) + { + return set(dest, 0, sizeof(T)); + } +} + +namespace str +{ + inline int len(const char *src) + { + return strlen(src); + } + + inline void cpy(char *dest,const char *src) + { + strcpy(dest,src); + } + + inline void ncpy(char *dest,const char *src,int destBufferLen) + { + strncpy(dest,src,destBufferLen); + } + + inline void cat(char *dest,const char *src) + { + strcat(dest,src); + } + + inline void ncat(char *dest,const char *src,int destBufferLen) + { + ncpy(dest+len(dest),src,destBufferLen-len(dest)); + } + + inline int cmp(const char *s1,const char *s2) + { + return strcmp(s1,s2); + } + inline bool eql(const char *s1,const char *s2) + { + return !strcmp(s1,s2); + } + inline int icmp(const char *s1,const char *s2) + { + return stricmp(s1,s2); + } + inline int cmpi(const char *s1,const char *s2) + { + return stricmp(s1,s2); + } + inline bool ieql(const char *s1,const char *s2) + { + return !stricmp(s1,s2); + } + inline bool eqli(const char *s1,const char *s2) + { + return !stricmp(s1,s2); + } + + inline char *tok(char *s,const char *gap) + { + return strtok(s,gap); + } + + void to_upper(char *dest); + void to_lower(char *dest); + void printf(char *dest,const char *formatS, ...); +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Raven Template Library Compile Assert +// +// If, during compile time the stuff under (condition) is zero, this code will not +// compile. +//////////////////////////////////////////////////////////////////////////////////////// +template +class compile_assert +{ +#ifdef _DEBUG + int junk[(1 - (2 * !condition))]; // Look At Where This Was Being Compiled +public: + compile_assert() + { + assert(condition); + junk[0]=FoolTheOptimizer++; + } + int operator()() + { + assert(condition); + FoolTheOptimizer++; + return junk[0]; + } +#else +public: + int operator()() + { + return 1; + } +#endif; +}; + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Raven Template Library Base Class +// +// This is the base class for all the Raven Template Library container classes like +// vector_vs and pool_vs. +// +// This class might be a good place to put memory profile code in the future. +// +//////////////////////////////////////////////////////////////////////////////////////// +class ratl_base +{ +public: +#ifndef _XBOX + void save(hfile& file); + void load(hfile& file); +#endif + + void ProfilePrint(const char * format, ...); + +public: + static void* OutputPrint; +}; + + +//////////////////////////////////////////////////////////////////////////////////////// +// this is a simplified version of bits_vs +//////////////////////////////////////////////////////////////////////////////////////// +template +class bits_base +{ +protected: + enum + { + BITS_SHIFT = 5, // 5. Such A Nice Number + BITS_INT_SIZE = 32, // Size Of A Single Word + BITS_AND = (BITS_INT_SIZE - 1), // Used For And Operation + ARRAY_SIZE = ((SZ + BITS_AND)/(BITS_INT_SIZE)), // Num Words Used + BYTE_SIZE = (ARRAY_SIZE*sizeof(unsigned int)), // Num Bytes Used + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + unsigned int mV[ARRAY_SIZE]; +public: + enum + { + SIZE = SZ, + CAPACITY = SZ, + }; + + bits_base(bool init=true,bool initValue=false) + { + if (init) + { + if (initValue) + { + set(); + } + else + { + clear(); + } + } + } + void clear() + { + mem::zero(&mV,BYTE_SIZE); + } + void set() + { + mem::set(&mV, 0xff,BYTE_SIZE); + } + + void set_bit(const int i) + { + assert(i>=0 && i < SIZE); + mV[i>>BITS_SHIFT] |= (1<<(i&BITS_AND)); + } + void clear_bit(const int i) + { + assert(i>=0 && i < SIZE); + mV[i>>BITS_SHIFT] &= ~(1<<(i&BITS_AND)); + } + void mark_bit(const int i, bool set) + { + assert(i>=0 && i < SIZE); + if (set) + { + mV[i>>BITS_SHIFT] |= (1<<(i&BITS_AND)); + } + else + { + mV[i>>BITS_SHIFT] &= ~(1<<(i&BITS_AND)); + } + } + bool operator[](const int i) const + { + assert(i>=0 && i < SIZE); + return (mV[i>>BITS_SHIFT] & (1<<(i&BITS_AND)))!=0; + } + int next_bit(int start=0,bool onBit=true) const + { + assert(start>=0&&start<=SIZE); //we have to accept start==size for the way the loops are done + if (start>=SIZE) + { + return SIZE; // Did Not Find + } + // Get The Word Which Contains The Start Bit & Mask Out Everything Before The Start Bit + //-------------------------------------------------------------------------------------- + unsigned int v = mV[start>>BITS_SHIFT]; + if (!onBit) + { + v= (~v); + } + v >>= (start&31); + + + // Search For The First Non Zero Word In The Array + //------------------------------------------------- + while(!v) + { + start = (start & (~(BITS_INT_SIZE-1))) + BITS_INT_SIZE; + if (start>=SIZE) + { + return SIZE; // Did Not Find + } + v = mV[start>>BITS_SHIFT]; + if (!onBit) + { + v= (~v); + } + } + + + // So, We've Found A Non Zero Word, So Start Masking Against Parts To Skip Over Bits + //----------------------------------------------------------------------------------- + if (!(v&0xffff)) + { + start+=16; + v>>=16; + } + if (!(v&0xff)) + { + start+=8; + v>>=8; + } + if (!(v&0xf)) + { + start+=4; + v>>=4; + } + + // Time To Search Each Bit + //------------------------- + while(!(v&1)) + { + start++; + v>>=1; + } + if (start>=SIZE) + { + return SIZE; + } + return start; + } +}; + + +//////////////////////////////////////////////////////////////////////////////////////// +// Raven Standard Compare Class +//////////////////////////////////////////////////////////////////////////////////////// +struct ratl_compare +{ + float mCost; + int mHandle; + + bool operator<(const ratl_compare& t) const + { + return (mCost + struct value_semantics + { + enum + { + CAPACITY = SIZE, + }; + typedef T TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + typedef T TStorage; // this is what we make our array of + + typedef bits_true TConstructed; + typedef TStorage TArray[SIZE]; + + + enum + { + NEEDS_CONSTRUCT=0, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=sizeof(TStorage), + }; + static void construct(TStorage *) + { + + } + static void construct(TStorage *me,const TValue &v) + { + *me=v; + } + static void destruct(TStorage *) + { + + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)me; + } + static T * ptr(TStorage *me) + { + return me; + } + static const T * ptr(const TStorage *me) + { + return me; + } + static T & ref(TStorage *me) + { + return *me; + } + static const T & ref(const TStorage *me) + { + return *me; + } + static T *raw_array(TStorage *me) + { + return me; + } + static const T *raw_array(const TStorage *me) + { + return me; + } + static void swap(TStorage *s1,TStorage *s2) + { + mem::swap(ptr(s1),ptr(s2)); + } + static int pointer_to_index(const void *s1,const void *s2) + { + return ((TStorage *)s1)-((TStorage *)s2); + } + }; + template + struct object_semantics + { + enum + { + CAPACITY = SIZE, + }; + typedef mem::alignStruct TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + + typedef bits_base TConstructed; + + struct TStorage + { + TAlign mMemory[((sizeof(T) + sizeof(TAlign) -1 )/sizeof(TAlign))]; + }; + typedef TStorage TArray[SIZE]; + + enum + { + NEEDS_CONSTRUCT=1, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=sizeof(TStorage), + }; + + static void construct(TStorage *me) + { + new(raw(me)) TValue(); + } + static void construct(TStorage *me,const TValue &v) + { + new(raw(me)) TValue(v); + } + static void destruct(TStorage *me) + { + ptr(me)->~T(); + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)me; + } + static T * ptr(TStorage *me) + { + return (T *)me; + } + static const T * ptr(const TStorage *me) + { + return (const T *)me; + } + static T & ref(TStorage *me) + { + return *(T *)me; + } + static const T & ref(const TStorage *me) + { + return *(const T *)me; + } + static void swap(TStorage *s1,TStorage *s2) + { + TValue temp(ref(s1)); + ref(s1)=ref(s2); + ref(s2)=temp; + } + static int pointer_to_index(const void *s1,const void *s2) + { + return ((TStorage *)s1)-((TStorage *)s2); + } + }; + template + struct virtual_semantics + { + enum + { + CAPACITY = SIZE, + }; + typedef mem::alignStruct TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + + typedef bits_base TConstructed; + + struct TStorage + { + TAlign mMemory[((MAX_CLASS_SIZE + sizeof(TAlign) -1 )/sizeof(TAlign))]; + }; + typedef TStorage TArray[SIZE]; + + enum + { + NEEDS_CONSTRUCT=1, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=MAX_CLASS_SIZE, + }; + + static void construct(TStorage *me) + { + new(raw(me)) TValue(); + } + static void destruct(TStorage *me) + { + ptr(me)->~T(); + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)me; + } + static T * ptr(TStorage *me) + { + return (T *)me; + } + static const T * ptr(const TStorage *me) + { + return (const T *)me; + } + static T & ref(TStorage *me) + { + return *(T *)me; + } + static const T & ref(const TStorage *me) + { + return *(const T *)me; + } + // this is a bit suspicious, we are forced to do a memory swap, and for a class, that, say + // stores a pointer to itself, it won't work right + static void swap(TStorage *s1,TStorage *s2) + { + mem::swap(s1,s2); + } + static int pointer_to_index(const void *s1,const void *s2) + { + return ((TStorage *)s1)-((TStorage *)s2); + } + template + static CAST_TO *verify_alloc(CAST_TO *p) + { +#ifdef _DEBUG + assert(p); + assert(dynamic_cast(p)); + T *ptr=p; // if this doesn't compile, you are trying to alloc something that is not derived from base + assert(dynamic_cast(ptr)); + int i=VALUE_SIZE; + int k=MAX_CLASS_SIZE; + int j=sizeof(CAST_TO); + compile_assert(); + assert(sizeof(CAST_TO)<=MAX_CLASS_SIZE); +#endif + return p; + } + }; + + // The below versions are for nodes + + template + struct value_semantics_node + { + enum + { + CAPACITY = SIZE, + }; + struct SNode + { + NODE nodeData; + T value; + }; + typedef SNode TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + typedef SNode TStorage; // this is what we make our array of + + typedef bits_true TConstructed; + typedef TStorage TArray[SIZE]; + + enum + { + NEEDS_CONSTRUCT=0, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=sizeof(TValue), + }; + static void construct(TStorage *) + { + + } + static void construct(TStorage *me,const TValue &v) + { + me->value=v; + } + static void destruct(TStorage *) + { + + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)&me->value; + } + static T * ptr(TStorage *me) + { + return &me->value; + } + static const T * ptr(const TStorage *me) + { + return &me->value; + } + static T & ref(TStorage *me) + { + return me->value; + } + static const T & ref(const TStorage *me) + { + return me->value; + } + // this ugly unsafe cast-hack is a backhanded way of getting the node data from the value data + // this is so node support does not need to be added to the primitive containers + static NODE & node(TValue &v) + { + return *(NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + static const NODE & node(const TValue &v) + { + return *(const NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + static void swap(TStorage *s1,TStorage *s2) + { + mem::swap(&s1->value,&s2->value); + } + // this is hideous + static int pointer_to_index(const void *s1,const void *s2) + { + return + ((TStorage *)(((unsigned char *)s1)-int(&((TStorage *)0)->value))) - + ((TStorage *)(((unsigned char *)s2)-int(&((TStorage *)0)->value))); + } + }; + + template + struct object_semantics_node + { + enum + { + CAPACITY = SIZE, + }; + typedef mem::alignStruct TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + + typedef bits_base TConstructed; + + struct TValueStorage + { + TAlign mMemory[((sizeof(T) + sizeof(TAlign) -1 )/sizeof(TAlign))]; + }; + struct SNode + { + NODE nodeData; + TValueStorage value; + }; + typedef SNode TStorage; // this is what we make our array of + typedef TStorage TArray[SIZE]; + + + enum + { + NEEDS_CONSTRUCT=0, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=sizeof(TValueStorage), + }; + + static void construct(TStorage *me) + { + new(raw(me)) TValue(); + } + static void construct(TStorage *me,const TValue &v) + { + new(raw(me)) TValue(v); + } + static void destruct(TStorage *me) + { + ptr(me)->~T(); + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)&me->value; + } + static T * ptr(TStorage *me) + { + return (T *)&me->value; + } + static const T * ptr(const TStorage *me) + { + return (const T *)&me->value; + } + static T & ref(TStorage *me) + { + return *(T *)&me->value; + } + static const T & ref(const TStorage *me) + { + return *(const T *)&me->value; + } + static NODE & node(TStorage *me) + { + return me->nodeData; + } + static const NODE & node(const TStorage *me) + { + return me->nodeData; + } + // this ugly unsafe cast-hack is a backhanded way of getting the node data from the value data + // this is so node support does not need to be added to the primitive containers + static NODE & node(TValue &v) + { + return *(NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + static const NODE & node(const TValue &v) + { + return *(const NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + static void swap(TStorage *s1,TStorage *s2) + { + TValue temp(ref(s1)); + ref(s1)=ref(s2); + ref(s2)=temp; + } + // this is hideous + static int pointer_to_index(const void *s1,const void *s2) + { + return + ((TStorage *)(((unsigned char *)s1)-int(&((TStorage *)0)->value))) - + ((TStorage *)(((unsigned char *)s2)-int(&((TStorage *)0)->value))); + } + }; + template + struct virtual_semantics_node + { + enum + { + CAPACITY = SIZE, + }; + typedef mem::alignStruct TAlign; // this is any type that has the right alignment + typedef T TValue; // this is the actual thing the user uses + + typedef bits_base TConstructed; + + struct TValueStorage + { + TAlign mMemory[((MAX_CLASS_SIZE + sizeof(TAlign) -1 )/sizeof(TAlign))]; + }; + struct SNode + { + NODE nodeData; + TValueStorage value; + }; + typedef SNode TStorage; // this is what we make our array of + typedef TStorage TArray[SIZE]; + + enum + { + NEEDS_CONSTRUCT=1, + TOTAL_SIZE=sizeof(TStorage), + VALUE_SIZE=sizeof(TValueStorage), + }; + + static void construct(TStorage *me) + { + new(raw(me)) TValue(); + } + static void destruct(TStorage *me) + { + ptr(me)->~T(); + } + static TRatlNew *raw(TStorage *me) + { + return (TRatlNew *)&me->value; + } + static T * ptr(TStorage *me) + { + return (T *)&me->value; + } + static const T * ptr(const TStorage *me) + { + return (const T *)&me->value; + } + static T & ref(TStorage *me) + { + return *(T *)&me->value; + } + static const T & ref(const TStorage *me) + { + return *(const T *)&me->value; + } + static NODE & node(TStorage *me) + { + return me->nodeData; + } + static const NODE & node(const TStorage *me) + { + return me->nodeData; + } + // this ugly unsafe cast-hack is a backhanded way of getting the node data from the value data + // this is so node support does not need to be added to the primitive containers + static NODE & node(TValue &v) + { + return *(NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + static const NODE & node(const TValue &v) + { + return *(const NODE *)((unsigned char *)(&v)+int(&((TStorage *)0)->nodeData)-int(&((TStorage *)0)->value)); + } + // this is a bit suspicious, we are forced to do a memory swap, and for a class, that, say + // stores a pointer to itself, it won't work right + static void swap(TStorage *s1,TStorage *s2) + { + mem::swap(&s1->value,&s2->value); + } + // this is hideous + static int pointer_to_index(const void *s1,const void *s2) + { + return + ((TStorage *)(((unsigned char *)s1)-int(&((TStorage *)0)->value))) - + ((TStorage *)(((unsigned char *)s2)-int(&((TStorage *)0)->value))); + } + template + static CAST_TO *verify_alloc(CAST_TO *p) + { +#ifdef _DEBUG + assert(p); + assert(dynamic_cast(p)); + T *ptr=p; // if this doesn't compile, you are trying to alloc something that is not derived from base + assert(dynamic_cast(ptr)); + int i=VALUE_SIZE; + int k=MAX_CLASS_SIZE; + int j=sizeof(CAST_TO); + compile_assert(); + assert(sizeof(CAST_TO)<=MAX_CLASS_SIZE); +#endif + return p; + } + }; + +} + +//////////////////////////////////////////////////////////////////////////////////////// +// The Array Base Class, used for most containers +//////////////////////////////////////////////////////////////////////////////////////// +template +class array_base : public ratl_base +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY, + SIZE = T::CAPACITY, + }; + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + typedef typename T TStorageTraits; + typedef typename T::TArray TTArray; + typedef typename T::TValue TTValue; + typedef typename T::TConstructed TTConstructed; + +private: + TTArray mArray; + TTConstructed mConstructed; + +public: + + array_base() + { + } + + ~array_base() + { + clear(); + } + + void clear() + { + if (T::NEEDS_CONSTRUCT) + { + int i=mConstructed.next_bit(); + while (i=0 && index=0 && index=0 && i=0 && i=0 && j=0 && i=0 && i=0 && index=0 && index + CAST_TO *verify_alloc(CAST_TO *p) const + { + return T::verify_alloc(p); + } + +}; + +} +#endif \ No newline at end of file diff --git a/code/ratl/scheduler_vs.h b/code/ratl/scheduler_vs.h new file mode 100644 index 0000000..f085c5e --- /dev/null +++ b/code/ratl/scheduler_vs.h @@ -0,0 +1,218 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Scheduler +// --------- +// The scheduler is a common piece of game functionality. To use it, simply add events +// at the given time, and call update() with the current time as frequently as you wish. +// +// Your event class MUST define a Fire() function which accepts a TCALLBACKPARAMS +// parameter. +// +// NOTES: +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_SCHEDULER_VS_INC) +#define RATL_SCHEDULER_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +#if !defined(RATL_POOL_VS_INC) + #include "pool_vs.h" +#endif +#if !defined(RATL_HEAP_VS_INC) + #include "heap_vs.h" +#endif +namespace ratl +{ + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Scheduler Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class scheduler_base : public ratl_base +{ +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY + }; +private: + //////////////////////////////////////////////////////////////////////////////////// + // The Timed Event Class + // + // This class stores two numbers, a timer and an iterator to the events list. We + // don't store the event directly in the heap to make the swap operation in the + // heap faster. We define a less than operator so we can sort in the heap. + // + //////////////////////////////////////////////////////////////////////////////////// + struct timed_event + { + float mTime; + int mEvent; + + timed_event() {} + timed_event(float time, int event) : mTime(time), mEvent(event) {} + bool operator< (const timed_event& t) const + { + return (mTime > t.mTime); + } + }; + + pool_base mEvents; + heap_vs mHeap; + +public: + //////////////////////////////////////////////////////////////////////////////////// + // How Many Objects Are In This List + //////////////////////////////////////////////////////////////////////////////////// + int size() const + { + // warning during a fire call, there will be one extra event + return mEvents.size(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Are There Any Objects In This List? + //////////////////////////////////////////////////////////////////////////////////// + bool empty() const + { + return !size(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Is This List Filled? + //////////////////////////////////////////////////////////////////////////////////// + bool full() const + { + return mEvents.full(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Clear All Elements + //////////////////////////////////////////////////////////////////////////////////// + void clear() + { + mEvents.clear(); + mHeap.clear(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Add An Event + //////////////////////////////////////////////////////////////////////////////////// + void add(float time, const TTValue& e) + { + int nLoc = mEvents.alloc(e); + mHeap.push(timed_event(time, nLoc)); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Add An Event + //////////////////////////////////////////////////////////////////////////////////// + TTValue & add(float time) + { + int nLoc = mEvents.alloc(); + mHeap.push(timed_event(time, nLoc)); + return mEvents[nLoc]; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Add A Raw Event for placement new + //////////////////////////////////////////////////////////////////////////////////// + TRatlNew * add_raw(float time) + { + TRatlNew *ret = mEvents.alloc_raw(); + mHeap.push(timed_event(time, mEvents.pointer_to_index(ret))); + return ret; + } + + template + void update(float time, TCALLBACKPARAMS& Params) + { + while (!mHeap.empty()) + { + timed_event Next = mHeap.top(); + if (Next.mTime>=time) + { + break; + } + mHeap.pop(); + mEvents[Next.mEvent].Fire(Params); + mEvents.free(Next.mEvent); + } + } + + void update(float time) + { + while (!mHeap.empty()) + { + timed_event Next = mHeap.top(); + if (Next.mTime>=time) + { + break; + } + mHeap.pop(); + mEvents[Next.mEvent].Fire(); + mEvents.free(Next.mEvent); + } + } +}; + + +template +class scheduler_vs : public scheduler_base > +{ +public: + typedef typename storage::value_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + scheduler_vs() {} +}; + +template +class scheduler_os : public scheduler_base > +{ +public: + typedef typename storage::object_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + scheduler_os() {} +}; + +template +class scheduler_is : public scheduler_base > +{ +public: + typedef typename storage::virtual_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + scheduler_is() {} +}; + +} +#endif \ No newline at end of file diff --git a/code/ratl/stack_vs.h b/code/ratl/stack_vs.h new file mode 100644 index 0000000..215a2a3 --- /dev/null +++ b/code/ratl/stack_vs.h @@ -0,0 +1,197 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Stack +// ----- +// This is a very simple wrapper around a stack object. +// +// First In, Last Out +// +// +// +// NOTES: +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_STACK_VS_INC) +#define RATL_STACK_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +namespace ratl +{ + +//////////////////////////////////////////////////////////////////////////////////////// +// The stack Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class stack_base : public ratl_base +{ +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY + }; +private: + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + array_base mData; // The Memory + int mSize; + +public: + + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + stack_base() : mSize(0) + { + } + + //////////////////////////////////////////////////////////////////////////////////// + // Get The Size (The Difference Between The Push And Pop "Pointers") + //////////////////////////////////////////////////////////////////////////////////// + int size() const + { + return mSize; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Check To See If The Size Is Zero + //////////////////////////////////////////////////////////////////////////////////// + bool empty() const + { + return !mSize; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Check To See If The Size Is Full + //////////////////////////////////////////////////////////////////////////////////// + bool full() const + { + return mSize>=CAPACITY; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Empty Out The Entire stack + //////////////////////////////////////////////////////////////////////////////////// + void clear() + { + mSize = 0; + mData.clear(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Add A Value, returns a reference to the value in place + //////////////////////////////////////////////////////////////////////////////////// + TTValue & push() + { + assert(!full()); + mData.construct(mSize); + mSize++; + return mData[mSize-1]; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Add A Value to the stack + //////////////////////////////////////////////////////////////////////////////////// + void push(const TTValue& v) + { + assert(!full()); + mData.construct(mSize,v); + mSize++; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Add A Value to the stack, returning a void * to the memory + //////////////////////////////////////////////////////////////////////////////////// + TRatlNew *push_raw() + { + assert(!full()); + mSize++; + return mData.alloc_raw(mSize-1); + } + //////////////////////////////////////////////////////////////////////////////////// + // Remove A Value From The stack + //////////////////////////////////////////////////////////////////////////////////// + void pop() + { + assert(!empty()); + mSize--; + mData.destruct(mSize); + } + + TTValue & top() + { + assert(!empty()); + return mData[mSize-1]; + } + + const TTValue & top() const + { + assert(!empty()); + return mData[mSize-1]; + } + template + CAST_TO *verify_alloc(CAST_TO *p) const + { + return mData.verify_alloc(p); + } +}; + +template +class stack_vs : public stack_base > +{ +public: + typedef typename storage::value_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + stack_vs() {} +}; + +template +class stack_os : public stack_base > +{ +public: + typedef typename storage::object_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + stack_os() {} +}; + +template +class stack_is : public stack_base > +{ +public: + typedef typename storage::virtual_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + stack_is() {} +}; + + +} +#endif \ No newline at end of file diff --git a/code/ratl/string_vs.h b/code/ratl/string_vs.h new file mode 100644 index 0000000..2adf9d0 --- /dev/null +++ b/code/ratl/string_vs.h @@ -0,0 +1,366 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// String +// ------ +// Simple wrapper around a char[SIZE] array. +// +// +// +// NOTES: +// +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_STRING_VS_INC) +#define RATL_STRING_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif + +namespace ratl +{ + +//////////////////////////////////////////////////////////////////////////////////////// +// The String Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class string_vs : public ratl_base +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = ARG_CAPACITY, + }; +private: + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// +#ifdef _DEBUG + char mData[CAPACITY+4]; +#else + char mData[CAPACITY]; +#endif + + void FillTerminator() + { +#ifdef _DEBUG + mData[CAPACITY]='e'; + mData[CAPACITY+1]='n'; + mData[CAPACITY+2]='d'; + mData[CAPACITY+3]=0; +#endif + } + +public: + + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + string_vs() + { + mData[0]=0; + FillTerminator(); + } +#ifdef _DEBUG + ~string_vs() + { + //if you hit the below asserts, the end of the string was overwritten + assert(mData[CAPACITY]=='e'); + assert(mData[CAPACITY+1]=='n'); + assert(mData[CAPACITY+2]=='d'); + assert(mData[CAPACITY+3]==0); + } +#endif + //////////////////////////////////////////////////////////////////////////////////// + // Copy Constructor + //////////////////////////////////////////////////////////////////////////////////// + string_vs(const string_vs &o) + { + assert(str::len(o.mData)(const string_vs &o) const + { + if (str::icmp(mData,o.mData)>0) + { + return true; + } + return false; + } + + //////////////////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////////////////// + void operator+=(const string_vs &o) + { + if ( (str::len(mData)+o.length())::tokenizer it=MyString.begin(" ,\t\n"); it!=MyString.end(); it++) + // { + // const char* token = *it; + // } + // + // + // NOTE: This class is built upon the c library function strtok() which uses a + // static working area, so having multiple tokenizers in multiple threads or just + // plain at the same time is not safe. + // + //////////////////////////////////////////////////////////////////////////////////// + class tokenizer + { + enum + { + TOKEN_GAP_LEN = 15, + }; + + public: + // Constructors + //-------------- + tokenizer() : mLoc(0) + {} + tokenizer(const char* t, const char* gap) + { + strncpy(mGap, gap, TOKEN_GAP_LEN); // Safe String Copy + mGap[TOKEN_GAP_LEN-1] = 0; // Make Sure We Have A Null Terminated Str + + char* temp = (char*)t; + mLoc = str::tok(temp, mGap); + } + + // Assignment Operator + //--------------------- + void operator= (const tokenizer &t) + { + mLoc = t.mLoc; + str::cpy(mGap, t.mGap); + } + + // Equality Operators + //-------------------- + bool operator==(const tokenizer &t) {return (mLoc==t.mLoc);} + bool operator!=(const tokenizer &t) {return !(operator==(t));} + + + + // DeReference Operator + //---------------------- + const char* operator*() + { + assert(mLoc); + return mLoc; + } + + // Inc & Dec Operators + //-------------------- + void operator++(int) + { + assert(mLoc && mGap[0]); + mLoc = str::tok(NULL, mGap); + } + + // Data + //------ + private: + char* mLoc; + char mGap[TOKEN_GAP_LEN]; + }; + + + //////////////////////////////////////////////////////////////////////////////////// + // Get An Iterator To The First Token Seperated By Gap + //////////////////////////////////////////////////////////////////////////////////// + tokenizer begin(const char* gap) + { + return tokenizer(mData, gap); + } + + //////////////////////////////////////////////////////////////////////////////////// + // The Invalid Iterator, Use As A Stop Condition In Your For Loops + //////////////////////////////////////////////////////////////////////////////////// + tokenizer end() + { + return tokenizer(); + } + +}; + + +} + +//fixme get rid of these +typedef ratl::string_vs<256> TString_vs; +typedef ratl::string_vs<128> TUIString_vs; + +#endif diff --git a/code/ratl/vector_vs.h b/code/ratl/vector_vs.h new file mode 100644 index 0000000..5c33175 --- /dev/null +++ b/code/ratl/vector_vs.h @@ -0,0 +1,757 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Vector +// ------ +// The vector class is a simple addition to the array. It supports some useful additions +// like sort and binary search, as well as keeping track of the number of objects +// contained within. +// +// +// +// +// +// NOTES: +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_VECTOR_VS_INC) +#define RATL_VECTOR_VS_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RATL_COMMON_INC) + #include "ratl_common.h" +#endif +namespace ratl +{ + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Vector Class +//////////////////////////////////////////////////////////////////////////////////////// +template +class vector_base : public ratl_base +{ +public: + typedef typename T TStorageTraits; + typedef typename T::TValue TTValue; + //////////////////////////////////////////////////////////////////////////////////// + // Capacity Enum + //////////////////////////////////////////////////////////////////////////////////// + enum + { + CAPACITY = T::CAPACITY + }; +private: + array_base mArray; // The Memory + int mSize; +public: + + //////////////////////////////////////////////////////////////////////////////////// + // Constructor + //////////////////////////////////////////////////////////////////////////////////// + vector_base() + { + mSize = 0; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Copy Constructor + //////////////////////////////////////////////////////////////////////////////////// + vector_base(const vector_base &B) + { + for (int i=0; i=0&&mSize<=CAPACITY); + return (CAPACITY); + } + + //////////////////////////////////////////////////////////////////////////////////// + // How Many Objects Have Been Added To This Vector? + //////////////////////////////////////////////////////////////////////////////////// + int size() const + { + assert(mSize>=0&&mSize<=CAPACITY); + return (mSize); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Have Any Objects Have Been Added To This Vector? + //////////////////////////////////////////////////////////////////////////////////// + bool empty() const + { + assert(mSize>=0&&mSize<=CAPACITY); + return (!mSize); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Have Any Objects Have Been Added To This Vector? + //////////////////////////////////////////////////////////////////////////////////// + bool full() const + { + assert(mSize>=0&&mSize<=CAPACITY); + return (mSize==CAPACITY); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Clear Out Entire Array + //////////////////////////////////////////////////////////////////////////////////// + void clear() + { + mArray.clear(); + mSize = 0; + } + // Constant Access Operator + //////////////////////////////////////////////////////////////////////////////////// + const TTValue& operator[](int index) const + { + assert(index>=0&&index=0&&index=0&&mSize=0&&mSize=0&&mSize0); + mSize--; + mArray.destruct(mSize); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Resizes The Array. If New Elements Are Needed, It Uses The (value) Param + //////////////////////////////////////////////////////////////////////////////////// + void resize(int nSize, const TTValue& value) + { + int i; + for (i=(mSize-1); i>=nSize; i--) + { + mArray.destruct(i); + mSize--; + } + for (i=mSize; i=nSize; i--) + { + mArray.destruct(i); + mSize--; + } + for (i=mSize; i=0 && Index0; HeapSize--) + { + // Swap The End And Front Of The "Heap" Half Of The Array + //-------------------------------------------------------- + mArray.swap(0, HeapSize); + + // We Now Have A Bogus Element At The Root, So Fix The Heap + //---------------------------------------------------------- + Pos = 0; + Compare = largest_child(Pos, HeapSize); + while (mArray[Pos]; + friend class const_iterator; + // Data + //------ + int mLoc; + vector_base* mOwner; + + public: + // Constructors + //-------------- + iterator() : mOwner(0), mLoc(0) + {} + iterator(vector_base* p, int t) : mOwner(p), mLoc(t) + {} + + iterator(const iterator &t) : mOwner(t.mOwner), mLoc(t.mLoc) + {} + + // Assignment Operator + //--------------------- + void operator= (const iterator &t) + { + mOwner = t.mOwner; + mLoc = t.mLoc; + } + + + // Equality Operators + //-------------------- + bool operator!=(const iterator &t) const + { + return (mLoc!=t.mLoc || mOwner!=t.mOwner); + } + bool operator==(const iterator &t) const + { + return (mLoc==t.mLoc && mOwner==t.mOwner); + } + + // DeReference Operator + //---------------------- + TTValue& operator* () const + { + assert(mLoc>=0 && mLocmSize); + return (mOwner->mArray[mLoc]); + } + // DeReference Operator + //---------------------- + TTValue& value() const + { + assert(mLoc>=0 && mLocmSize); + return (mOwner->mArray[mLoc]); + } + + // DeReference Operator + //---------------------- + TTValue* operator-> () const + { + assert(mLoc>=0 && mLocmSize); + return (&mOwner->mArray[mLoc]); + } + + // Inc Operator + //-------------- + iterator operator++(int) //postfix + { + assert(mLoc>=0 && mLocmSize); + iterator old(*this); + mLoc ++; + return old; + } + + // Inc Operator + //-------------- + iterator operator++() + { + assert(mLoc>=0 && mLocmSize); + mLoc ++; + return *this; + } + + }; + + //////////////////////////////////////////////////////////////////////////////////// + // Constant Iterator + //////////////////////////////////////////////////////////////////////////////////// + class const_iterator + { + friend class vector_base; + + int mLoc; + const vector_base* mOwner; + + public: + // Constructors + //-------------- + const_iterator() : mOwner(0), mLoc(0) + {} + const_iterator(const vector_base* p, int t) : mOwner(p), mLoc(t) + {} + const_iterator(const const_iterator &t) : mOwner(t.mOwner), mLoc(t.mLoc) + {} + const_iterator(const iterator &t) : mOwner(t.mOwner), mLoc(t.mLoc) + {} + + // Assignment Operator + //--------------------- + void operator= (const const_iterator &t) + { + mOwner = t.mOwner; + mLoc = t.mLoc; + } + // Assignment Operator + //--------------------- + void operator= (const iterator &t) + { + mOwner = t.mOwner; + mLoc = t.mLoc; + } + + + + // Equality Operators + //-------------------- + bool operator!=(const iterator &t) const + { + return (mLoc!=t.mLoc || mOwner!=t.mOwner); + } + bool operator==(const iterator &t) const + { + return (mLoc==t.mLoc && mOwner==t.mOwner); + } + + // Equality Operators + //-------------------- + bool operator!=(const const_iterator &t) const + { + return (mLoc!=t.mLoc || mOwner!=t.mOwner); + } + bool operator==(const const_iterator &t) const + { + return (mLoc==t.mLoc && mOwner==t.mOwner); + } + + // DeReference Operator + //---------------------- + const TTValue& operator* () const + { + assert(mLoc>=0 && mLocmSize); + return (mOwner->mArray[mLoc]); + } + + // DeReference Operator + //---------------------- + const TTValue& value() const + { + assert(mLoc>=0 && mLocmSize); + return (mOwner->mArray[mLoc]); + } + + // DeReference Operator + //---------------------- + const TTValue* operator-> () const + { + assert(mLoc>=0 && mLocmSize); + return (&mOwner->mArray[mLoc]); + } + + // Inc Operator + //-------------- + const_iterator operator++(int) + { + assert(mLoc>=0 && mLocmSize); + const_iterator old(*this); + mLoc ++; + return old; + } + + // Inc Operator + //-------------- + const_iterator operator++() + { + assert(mLoc>=0 && mLocmSize); + mLoc ++; + return *this; + } + + + }; + friend class iterator; + friend class const_iterator; + + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Begin (Starts At Address 0) + //////////////////////////////////////////////////////////////////////////////////// + iterator begin() + { + return iterator(this, 0); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator End (Set To Address mSize) + //////////////////////////////////////////////////////////////////////////////////// + iterator end() + { + return iterator(this, mSize); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Begin (Starts At Address 0) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator begin() const + { + return const_iterator(this, 0); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator End (Set To Address mSize) + //////////////////////////////////////////////////////////////////////////////////// + const_iterator end() const + { + return const_iterator(this, mSize); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Iterator Find (If Fails To Find, Returns iterator end() + //////////////////////////////////////////////////////////////////////////////////// + iterator find(const TTValue& value) + { + int index = find_index(value); // Call Find By Index + if (index=0 && it.mLocmSize); + if (it.mLoc != mSize - 1) + { + mArray.swap(it.mLoc, mSize - 1); + } + pop_back(); + return it; + } + template + CAST_TO *verify_alloc(CAST_TO *p) const + { + return mArray.verify_alloc(p); + } +}; + +template +class vector_vs : public vector_base > +{ +public: + typedef typename storage::value_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + vector_vs() {} +}; + +template +class vector_os : public vector_base > +{ +public: + typedef typename storage::object_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY + }; + vector_os() {} +}; + +template +class vector_is : public vector_base > +{ +public: + typedef typename storage::virtual_semantics TStorageTraits; + typedef typename TStorageTraits::TValue TTValue; + enum + { + CAPACITY = ARG_CAPACITY, + MAX_CLASS_SIZE = ARG_MAX_CLASS_SIZE + }; + vector_is() {} +}; + +} +#endif diff --git a/code/ravl/CBounds.cpp b/code/ravl/CBounds.cpp new file mode 100644 index 0000000..aa9bdc1 --- /dev/null +++ b/code/ravl/CBounds.cpp @@ -0,0 +1,366 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Vector Library +// -------------- +// +// +// +// +// NOTES: +// 05/31/02 - CREATED +// +// +//////////////////////////////////////////////////////////////////////////////////////// + +#if !defined(ASSERT_H_INC) +#include +#define ASSERT_H_INC +#endif + +#include +#include +#include +#include "CBounds.h" + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +/*void CBBox::ThroughMatrix(const CBBox &from, const CMatrix4 &mat) +{ + Clear(); + CVec3 bb,t; + int i; + const CVec3 &xmn=from.GetMin(); + const CVec3 &xmx=from.GetMax(); + for ( i = 0; i < 8; i++ ) + { + if ( i & 1 ) + bb[0] = xmn[0]; + else + bb[0] = xmx[0]; + if ( i & 2 ) + bb[1] = xmn[1]; + else + bb[1] = xmx[1]; + if ( i & 4 ) + bb[2] = xmn[2]; + else + bb[2] = xmx[2]; + mat.XFormPoint(t,bb); + AddPoint(t); + } +}*/ + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +float CBBox::LargestAxisSize() const +{ + CVec3 Work(mMax); + Work-=mMin; + return Work.MaxElement(); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +float CBBox::DistanceEstimate(const CVec3 &p) const +{ + float ret=0.0f; + + // X Axis + //-------- + if (p[0]>mMax[0]) + { + ret=p[0]-mMax[0]; + } + else if (p[0]mMax[1]) + { + ret+=p[1]-mMax[1]; + } + else if (p[1]mMax[2]) + { + ret+=p[2]-mMax[2]; + } + else if (p[2]mMin && vmMax[0]+tolout|| + v[1]mMax[1]+tolout|| + v[2]mMax[2]+tolout) + { + return Side_Out; + } + if (v[0]>mMin[0]+tolin&&v[0]mMin[1]+tolin&&v[1]mMin[2]+tolin&&v[2]b2.mMax[0] || + mMin[1]-tolout>b2.mMax[1] || + mMin[2]-tolout>b2.mMax[2] || + b2.mMin[0]-tolout>mMax[0] || + b2.mMin[1]-tolout>mMax[1] || + b2.mMin[2]-tolout>mMax[2]) + { + return false; + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool CBBox::SphereTouchTest(const CVec3 &v,float rad) const +{ + if (v[0]mMax[0]+rad|| + v[1]mMax[1]+rad|| + v[2]mMax[2]+rad) + return false; + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +TPlanes CBBox::PlaneFlags(const CVec3 &p) +{ + TPlanes ret=0; + if (p[0]mMax[0]) + { + ret|=2; + } + if (p[1]mMax[1]) + { + ret|=8; + } + if (p[2]mMax[2]) + { + ret|=32; + } + return ret; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +// return true if the segment intersect the box, in that case, return the first contact. +//////////////////////////////////////////////////////////////////////////////////////// +bool CBBox::HitTest(CBTrace& Tr) const +{ + // Quick Box Cull + //---------------- + CBBox tmp; + tmp.AddPoint(Tr.mStart); + tmp.AddPoint(Tr.mStop); + if (!BoxTouchTest(tmp)) + { + return false; + } + + + // Initialize Our Ranges + //----------------------- + Tr.mRange =-1E30f; + Tr.mRangeMax = 1E30f; + + + // For Each Non Zero Axis Of The Aim Vector + //------------------------------------------ + float tmax,tmin,temp; + for (int axis=0; axis<3; axis++) + { + if (fabs(Tr.mAim[axis])>1E-6f) + { + // Find Mins And Maxs From The Start Along The Axis Of Aim + //--------------------------------------------------------- + tmax = ((mMax[axis]-Tr.mStart[axis])/Tr.mAim[axis]); + tmin = ((mMin[axis]-Tr.mStart[axis])/Tr.mAim[axis]); + if (tmaxTr.mRange) + { + Tr.mRange=tmin; + Tr.mNormal.Clear(); + Tr.mNormal[axis]=-1.0f; + } + } + } + + + // Missed? + //--------- + if (Tr.mRangeMaxTr.mLength) + { + return false; + } + + + // Start Solid Conditions + //------------------------ + if (Tr.mRange<0.0f) + { + Tr.mRange = 0.0f; + Tr.mPoint = Tr.mStart; + return true; + } + + + // Calculate The End Point + //------------------------- + Tr.mPoint = Tr.mAim; + Tr.mPoint *= Tr.mRange; + Tr.mPoint += Tr.mStart; + return true; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void CBBox::FromStr(const char *s) +{ + assert(s && s[0]); + + char MinS[256]; + char MaxS[266]; + sscanf(s, "(%s|%s)", MinS, MaxS); + + mMin.FromStr(MinS); + mMax.FromStr(MaxS); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void CBBox::ToStr(char* s) +{ + assert(s && s[0]); + + char MinS[256]; + char MaxS[266]; + + mMin.ToStr(MinS); + mMax.ToStr(MaxS); + sprintf(s, "(%s|%s)", MinS, MaxS); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +void CBBox::Validate() +{ + assert(mMax>=mMin); +} + diff --git a/code/ravl/CBounds.h b/code/ravl/CBounds.h new file mode 100644 index 0000000..e43692f --- /dev/null +++ b/code/ravl/CBounds.h @@ -0,0 +1,188 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Vector Library +// -------------- +// +// +// +// +// NOTES: +// 05/31/02 - CREATED +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RAVL_BOUNDS_INC) +#define RAVL_BOUNDS_INC +//namespace ravl +//{ + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#include "CVec.h" + + +//////////////////////////////////////////////////////////////////////////////////////// +// Defines +//////////////////////////////////////////////////////////////////////////////////////// +#define RAVL_BB_EMPTY_MIN ( 1.234567E30f) // Empty Value +#define RAVL_BB_EMPTY_MAX (-1.234567E30f) // Empty Value + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Enums And Typedefs +//////////////////////////////////////////////////////////////////////////////////////// +typedef unsigned char TPlanes; + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Bounds Trace +//////////////////////////////////////////////////////////////////////////////////////// +class CBTrace +{ +public: + CBTrace(const CVec3& Start, const CVec3& Stop) : + mStart(Start), + mStop(Stop), + mAim(Stop), + mRange(0), + mRangeMax(0), + mPoint(Stop) + { + mAim-=Start; + mLength = mAim.Norm(); + } + + CBTrace& operator =(const CBTrace& T) + { + mStart = (T.mStart); + mStop = (T.mStop); + mAim = (T.mAim); + mRange = (T.mRange); + mRangeMax = (T.mRangeMax); + mPoint = (T.mPoint); + return (*this); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Setup Values, Do Not Change + //////////////////////////////////////////////////////////////////////////////////// +public: + CVec3 mStart; + CVec3 mStop; + CVec3 mAim; + float mLength; + + //////////////////////////////////////////////////////////////////////////////////// + // Results + //////////////////////////////////////////////////////////////////////////////////// +public: + float mRange; + float mRangeMax; + CVec3 mPoint; + CVec3 mNormal; +}; + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Bounding Box Class +//////////////////////////////////////////////////////////////////////////////////////// +class CBBox +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructors + //////////////////////////////////////////////////////////////////////////////////// + CBBox() {mMin.Set(RAVL_BB_EMPTY_MIN); mMax.Set(RAVL_BB_EMPTY_MAX);} + CBBox(float Radius) {mMin.Set(-Radius); mMax.Set(Radius);} + CBBox(const CVec3& t) {mMin=t; mMax=t;} + CBBox(const CVec3& min, const CVec3& max) {mMin=min; mMax=max;} + CBBox(const CBBox& t) {mMin=t.mMin; mMax=t.mMax;} + + //////////////////////////////////////////////////////////////////////////////////// + // Initializers + //////////////////////////////////////////////////////////////////////////////////// + void Set(const CVec3& min, const CVec3& max) {mMin=min; mMax=max; Validate();} + void Clear() {mMin.Set(RAVL_BB_EMPTY_MIN); mMax.Set(RAVL_BB_EMPTY_MAX);} + void AddPoint(const CVec3 &p) {mMin.Min(p); mMax.Max(p); Validate();} + + //////////////////////////////////////////////////////////////////////////////////// + // Accessors + //////////////////////////////////////////////////////////////////////////////////// + bool IsEmpty() const {return (mMin[0]==RAVL_BB_EMPTY_MIN);} + CVec3 Center() const {return (mMin+mMax)*0.5;} + + //////////////////////////////////////////////////////////////////////////////////// + // Translation, Rotation, Expansion + //////////////////////////////////////////////////////////////////////////////////// + void Translate(const CVec3 &f) {mMin+=f; mMax+=f;} + void Expand(float x) {mMin-=x; mMax+=x;} + void Expand(const CVec3 &f) {mMin-=f; mMax+=f;} +// void ThroughMatrix(const CBBox &from, const CMatrix4 &mat); + + + //////////////////////////////////////////////////////////////////////////////////// + // Volumetric & Area Operations + //////////////////////////////////////////////////////////////////////////////////// + float Volume() const {return (mMax[0]-mMin[0])*(mMax[1]-mMin[1])*(mMax[2]-mMin[2]);} + float AxisSize(int axis) const {return (mMax[axis]-mMin[axis]);} + float LargestAxisSize() const; + float DistanceEstimate(const CVec3 &p) const; // Manhattan Distance + float AreaEstimate(const CVec3 &p) const; // Manhattan Distance * LargestAxisSize() + + + //////////////////////////////////////////////////////////////////////////////////// + // Set Operations + //////////////////////////////////////////////////////////////////////////////////// + void Intersect(const CBBox &b2); + void Union(const CBBox &b2); + + //////////////////////////////////////////////////////////////////////////////////// + // Tests + //////////////////////////////////////////////////////////////////////////////////// + ESide InOutTest(const CVec3 &p) const; + ESide InOutTest(const CVec3 &p, float tolout, float tolin) const; + bool BoxTouchTest(const CBBox &b2, float tolout=0.0f) const; + bool SphereTouchTest(const CVec3 &c, float rad) const; + TPlanes PlaneFlags(const CVec3 &p); + + + //////////////////////////////////////////////////////////////////////////////////// + // Hit Tests + //////////////////////////////////////////////////////////////////////////////////// + bool HitTest(CBTrace& Tr) const; + + + //////////////////////////////////////////////////////////////////////////////////// + // String Operations + //////////////////////////////////////////////////////////////////////////////////// + void FromStr(const char *s); + void ToStr(char* s); + + //////////////////////////////////////////////////////////////////////////////////// + // Debug Operations + //////////////////////////////////////////////////////////////////////////////////// + void Validate(); + + + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// +public: + CVec3 mMin; + CVec3 mMax; +}; + +//}; +#endif \ No newline at end of file diff --git a/code/ravl/CMatrix.h b/code/ravl/CMatrix.h new file mode 100644 index 0000000..c3ba849 --- /dev/null +++ b/code/ravl/CMatrix.h @@ -0,0 +1,165 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Matrix Library +// -------------- +// +// +// +// NOTES: +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RAVL_MATRIX_INC) +#define RAVL_MATRIX_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#if defined(RA_DEBUG_LINKING) + #pragma message("...including CMatrix.h") +#endif +#if !defined(RAVL_VEC_INC) + #include "CVec.h" +#endif +//namespace ravl +//{ + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Matrix +//////////////////////////////////////////////////////////////////////////////////////// +class CMatrix +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructors + //////////////////////////////////////////////////////////////////////////////////// + CMatrix() {} + CMatrix(const CVec4& x,const CVec4& y,const CVec4& z, const CVec4& w) {v[0]=x; v[1]=y; v[2]=z; v[3]=w;} + CMatrix(const CMatrix& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2]; v[3]=t.v[3];} + CMatrix(const float t[16]) {v[0]=t[0]; v[1]=t[4]; v[2]=t[8]; v[3]=t[12];} + + //////////////////////////////////////////////////////////////////////////////////// + // Initializers + //////////////////////////////////////////////////////////////////////////////////// + void Set(const CVec4& x,const CVec4& y,const CVec4& z, const CVec4& w) {v[0]=x; v[1]=y; v[2]=z; v[3]=w;} + void Set(const CMatrix& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2]; v[3]=t.v[3];} + void Set(const float t[16]) {v[0]=t[0]; v[1]=t[4]; v[2]=t[8]; v[3]=t[12];} + + void Clear() {v[0].Set(0,0,0,0); v[1].Set(0,0,0,0); v[2].Set(0,0,0,0); v[3].Set(0,0,0,0);} + void Itentity() {v[0].Set(1,0,0,0); v[1].Set(0,1,0,0); v[2].Set(0,0,1,0); v[3].Set(0,0,0,1);} + void Translate(const float x, const float y, const float z) {v[0].Set(1,0,0,0); v[1].Set(0,1,0,0); v[2].Set(0,0,1,0); v[3].Set(x,y,z,1);} + void Scale(const float x, const float y, const float z) {v[0].Set(x,0,0,0); v[1].Set(0,y,0,0); v[2].Set(0,0,z,0); v[3].Set(0,0,0,1);} + void Rotate(int axis, const float s/*sin(angle)*/, const float c/*cos(angle)*/) + { + switch(axis) + { + case 0: + v[0].Set( 1, 0, 0, 0); + v[1].Set( 0, c,-s, 0); + v[2].Set( 0, s, c, 0); + break; + case 1: + v[0].Set( c, 0, s, 0); + v[1].Set( 0, 1, 0, 0); + v[2].Set(-s, 0, c, 0); + break; + case 2: + v[0].Set( c,-s, 0, 0); + v[1].Set( s, c, 0, 0); + v[2].Set( 0, 0, 1, 0); + break; + } + v[3].Set( 0, 0, 0, 1); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Member Accessors + //////////////////////////////////////////////////////////////////////////////////// + const CVec4& operator[](int i) const {return v[i];} + CVec4& operator[](int i) {return v[i];} + + CVec4& up() {return v[0];} + CVec4& left() {return v[1];} + CVec4& fwd() {return v[2];} + CVec4& origin() {return v[3];} + + //////////////////////////////////////////////////////////////////////////////////// + // Equality / Inequality Operators + //////////////////////////////////////////////////////////////////////////////////// + bool operator== (const CMatrix& t) const {return (v[0]==t.v[0] && v[1]==t.v[1] && v[2]==t.v[2] && v[3]==t.v[3]);} + bool operator!= (const CMatrix& t) const {return !(v[0]==t.v[0] && v[1]==t.v[1] && v[2]==t.v[2] && v[3]==t.v[3]);} + + //////////////////////////////////////////////////////////////////////////////////// + // Basic Arithimitic Operators + //////////////////////////////////////////////////////////////////////////////////// + const CMatrix &operator= (const CMatrix& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2]; v[3]=t.v[3]; return *this;} + const CMatrix &operator+= (const CMatrix& t) {v[0]+=t.v[0]; v[1]+=t.v[1]; v[2]+=t.v[2]; v[3]+=t.v[3];return *this;} + const CMatrix &operator-= (const CMatrix& t) {v[0]-=t.v[0]; v[1]-=t.v[1]; v[2]-=t.v[2]; v[3]-=t.v[3];return *this;} + + CMatrix operator+ (const CMatrix &t) const {return CMatrix(v[0]+t.v[0], v[1]+t.v[1], v[2]+t.v[2], v[3]+t.v[3]);} + CMatrix operator- (const CMatrix &t) const {return CMatrix(v[0]-t.v[0], v[1]-t.v[1], v[2]-t.v[2], v[3]-t.v[3]);} + + //////////////////////////////////////////////////////////////////////////////////// + // Matrix Scale + //////////////////////////////////////////////////////////////////////////////////// + const CMatrix &operator*= (const float d) {v[0]*=d; v[1]*=d; v[2]*=d; v[3]*=d; return *this;} + + + //////////////////////////////////////////////////////////////////////////////////// + // Matrix To Matrix Multiply + //////////////////////////////////////////////////////////////////////////////////// + CMatrix operator* (const CMatrix &t) const + { + // assert(this!=&t); // Don't Multiply With Self + + CMatrix Result; // The Resulting Matrix + int i,j,k; // Counters + float Accumulator; // Current Value Of The Dot Product + for (i=0; i<4; i++) + { + for (j=0; j<4; j++) + { + Accumulator = 0.0f; // Reset The Accumulator + for(k=0; k<4; k++) + { + Accumulator += v[i][k]*t[k][j]; // Calculate Dot Product Of The Two Vectors + } + Result[i][j]=Accumulator; // Place In Result + } + } + + return Result; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Vector To Matrix Multiply + //////////////////////////////////////////////////////////////////////////////////// + CVec4 operator* (const CVec4 &t) const + { + CVec4 Result; + + Result[0] = v[0][0]*t[0] + v[1][0]*t[1] + v[2][0]*t[2] + v[3][0]; + Result[1] = v[0][1]*t[0] + v[1][1]*t[1] + v[2][1]*t[2] + v[3][1]; + Result[2] = v[0][2]*t[0] + v[1][2]*t[1] + v[2][2]*t[2] + v[3][2]; + + return Result; + } + +public: + CVec4 v[4]; +}; + + + +//} +#endif \ No newline at end of file diff --git a/code/ravl/CVec.cpp b/code/ravl/CVec.cpp new file mode 100644 index 0000000..9bf051f --- /dev/null +++ b/code/ravl/CVec.cpp @@ -0,0 +1,1154 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Vector Library +//////////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include "CVec.h" + +//using namespace ravl; + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Static Class Member Initialization +//////////////////////////////////////////////////////////////////////////////////////// +const CVec4 CVec4::mX(1.f, 0.f, 0.f, 0.f); +const CVec4 CVec4::mY(0.f, 1.f, 0.f, 0.f); +const CVec4 CVec4::mZ(0.f, 0.f, 1.f, 0.f); +const CVec4 CVec4::mW(0.f, 0.f, 0.f, 1.f); +const CVec4 CVec4::mZero(0.f, 0.f, 0.f, 0.f); + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Length +//////////////////////////////////////////////////////////////////////////////////////// +float CVec4::Len() const +{ + return sqrtf(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]+v[3]*v[3]); +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Distance To Other Point +//////////////////////////////////////////////////////////////////////////////////////// +float CVec4::Dist(const CVec4& t) const +{ + return sqrtf( + (t.v[0]-v[0])*(t.v[0]-v[0]) + + (t.v[1]-v[1])*(t.v[1]-v[1]) + + (t.v[2]-v[2])*(t.v[2]-v[2]) + + (t.v[3]-v[3])*(t.v[3]-v[3])); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Normalize +//////////////////////////////////////////////////////////////////////////////////////// +float CVec4::Norm() +{ + float L = Len(); + (*this)/=L; + return L; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Safe Normalize +// Do Not Normalize If Length Is Too Small +//////////////////////////////////////////////////////////////////////////////////////// +float CVec4::SafeNorm() +{ + float d=this->Len(); + if (d>1E-10) + { + (*this)/=d; + return d; + } + (*this)=0.0f; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Angular Normalize +// All floats Exist(-180, +180) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::AngleNorm() +{ + v[0]= fmodf(v[0],360.0f); + if (v[0]<-180.f) v[0]+=360.0f; + if (v[0]>180.f) v[0]-=360.0f; + + v[1]= fmodf(v[1],360.0f); + if (v[1]<-180.f) v[1]+=360.0f; + if (v[1]>180.f) v[1]-=360.0f; + + v[2]= fmodf(v[2],360.0f); + if (v[2]<-180.f) v[2]+=360.0f; + if (v[2]>180.f) v[2]-=360.0f; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Perpendicular Vector +// +// This implimentation is a bit slow, needs some optimization work... +// +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::Perp() +{ + float rlen,tlen; + CVec4 r,t; + r = (*this); + r.Cross(mX); + rlen=r.Len(); + t = (*this); + t.Cross(mY); + tlen=t.Len(); + if (tlen>rlen) + { + r=t; + rlen=tlen; + } + t = (*this); + t.Cross(mZ); + tlen=t.Len(); + if (tlen>rlen) + { + r=t; + rlen=tlen; + } + (*this) = r; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Find Largest Element (Ignores 4th component for now) +//////////////////////////////////////////////////////////////////////////////////////// +int CVec4::MaxElementIndex() const +{ + if (fabs(v[0])>fabs(v[1]) && fabs(v[0])>fabs(v[2])) + { + return 0; + } + if (fabs(v[1])>fabs(v[2])) + { + return 1; + } + return 2; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert To {Pitch, Yaw} (DEGREES) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::VecToAng() +{ + float yaw; + float pitch; + + if (!v[1] && !v[0]) + { + yaw = 0; + pitch = (v[2]>0) ? (90.0f):(270.0f); + } + else + { + // Calculate Yaw + //--------------- + if (v[0]) + { + yaw = (RAVL_VEC_RADTODEG(atan2f(v[1], v[0]))); + if (yaw<0) + { + yaw += 360; + } + } + else + { + yaw = (v[1]>0) ? (90.0f):(270.0f); + } + + // Calculate Pitch + //----------------- + float forward = (sqrtf(v[0]*v[0] + v[1]*v[1])); + pitch = (RAVL_VEC_RADTODEG(atan2f(v[2], forward))); + if (pitch<0) + { + pitch += 360; + } + } + + // Copy Over Current Vector + //-------------------------- + v[0] = -pitch; + v[1] = yaw; + v[2] = 0; + v[3] = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert From {Picth, Yaw} (DEGREES) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::AngToVec() +{ + float angle; + float sp, sy, cp, cy; + + angle = yaw() * (RAVL_VEC_DEGTORADCONST); + sy = sinf(angle); + cy = cosf(angle); + angle = pitch() * (RAVL_VEC_DEGTORADCONST); + sp = sinf(angle); + cp = cosf(angle); + + v[0] = cp * cy; + v[1] = cp * sy; + v[2] = -sp; + v[3] = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert From {Picth, Yaw, Roll} (DEGREES) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::AngToVec(CVec4& Right, CVec4& Up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = yaw() * (RAVL_VEC_DEGTORADCONST); + sy = sinf(angle); + cy = cosf(angle); + angle = pitch() * (RAVL_VEC_DEGTORADCONST); + sp = sinf(angle); + cp = cosf(angle); + angle = roll() * (RAVL_VEC_DEGTORADCONST); + sr = sinf(angle); + cr = cosf(angle); + + // Forward Vector Is Stored Here + v[0] = cp * cy; + v[1] = cp * sy; + v[2] = -sp; + v[3] = 0; + + // Calculate Right + Right.v[0] = (-1 * sr * sp * cy + -1 * cr * -sy); + Right.v[1] = (-1 * sr * sp * sy + -1 * cr * cy); + Right.v[2] = -1 * sr * cp; + Right.v[3] = 0; + + // Calculate Up + Up.v[0] = (cr * sp * cy + -sr * -sy); + Up.v[1] = (cr * sp * sy + -sr * cy); + Up.v[2] = cr * cp; + Up.v[3] = 0; +} + + + + +/////////////////////////////////////////////////////////////////////////////////////// +// Convert To {Pitch, Yaw} (RADIANS) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::VecToAngRad() +{ + float yaw; + float pitch; + + if (!v[1] && !v[0]) + { + yaw = 0; + pitch = (v[2]>0) ? (RAVL_VEC_PI*0.5f):(RAVL_VEC_PI*1.5f); + } + else + { + // Calculate Yaw + //--------------- + if (v[0]) + { + yaw = (atan2f(v[1], v[0])); + } + else + { + yaw = (v[1]>0) ? (RAVL_VEC_PI*0.5f):(RAVL_VEC_PI*1.5f); + } + + // Calculate Pitch + //----------------- + float forward = (sqrtf(v[0]*v[0] + v[1]*v[1])); + pitch = (atan2f(v[2], forward)); + } + + // Copy Over Current Vector + //-------------------------- + v[0] = -pitch; + v[1] = yaw; + v[2] = 0; + v[3] = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert From {Picth, Yaw} (RADIANS) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::AngToVecRad() +{ + float sp, sy, cp, cy; + + sy = sinf(yaw()); + cy = cosf(yaw()); + sp = sinf(pitch()); + cp = cosf(pitch()); + + v[0] = cp * cy; + v[1] = cp * sy; + v[2] = -sp; + v[3] = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert From {Picth, Yaw, Roll} (RADIANS) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::AngToVecRad(CVec4& Right, CVec4& Up) +{ + float sr, sp, sy, cr, cp, cy; + + sy = sinf(yaw()); + cy = cosf(yaw()); + sp = sinf(pitch()); + cp = cosf(pitch()); + sr = sinf(roll()); + cr = cosf(roll()); + + // Forward Vector Is Stored Here + v[0] = cp * cy; + v[1] = cp * sy; + v[2] = -sp; + v[3] = 0; + + // Calculate Right + Right.v[0] = (-1 * sr * sp * cy + -1 * cr * -sy); + Right.v[1] = (-1 * sr * sp * sy + -1 * cr * cy); + Right.v[2] = -1 * sr * cp; + Right.v[3] = 0; + + // Calculate Up + Up.v[0] = (cr * sp * cy + -sr * -sy); + Up.v[1] = (cr * sp * sy + -sr * cy); + Up.v[2] = cr * cp; + Up.v[3] = 0; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert To Radians +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::ToRadians() +{ + v[0] = RAVL_VEC_DEGTORAD(v[0]); + v[1] = RAVL_VEC_DEGTORAD(v[1]); + v[2] = RAVL_VEC_DEGTORAD(v[2]); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert To Degrees +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::ToDegrees() +{ + v[0] = RAVL_VEC_RADTODEG(v[0]); + v[1] = RAVL_VEC_RADTODEG(v[1]); + v[2] = RAVL_VEC_RADTODEG(v[2]); +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Copy Values From String +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::FromStr(const char *s) +{ +// assert(s && s[0]); + sscanf(s, "(%f %f %f %f)", &v[0], &v[1], &v[2], &v[3]); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Write Values To A String +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::ToStr(char* s) const +{ +// assert(s); + sprintf(s, "(%3.3f %3.3f %3.3f %3.3f)", v[0], v[1], v[2], v[3]); +} + + + +#ifdef _DEBUG + +//////////////////////////////////////////////////////////////////////////////////////// +// Make Sure Entire Vector Has Valid Numbers +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec4::IsFinite() +{ +#if defined(_MSC_VER) + return (_finite(v[0]) && _finite(v[1]) && _finite(v[2]) && _finite(v[3])); +#endif +#if defined(__MWERKS__) + //FIXME: _finite won't compile on the PS2, so commented it out for now, false seemed like the best option + return true; +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Make Sure Vector Has Been Initialized +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec4::IsInitialized() +{ + return (v[0]!=RAVL_VEC_UDF && v[1]!=RAVL_VEC_UDF && v[2]!=RAVL_VEC_UDF && v[3]!=RAVL_VEC_UDF); +} + +#endif + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Point In Circumscribed Circle (True/False) +// +// Returns true if the given point is within the circumscribed +// circle of the given ABC Triangle: +// _____ +// / B \ +// / / \ \ +// | / \ | +// |A---------C| +// \ Pt / +// \_______/ +// +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec4::PtInCircle(const CVec4 &A, const CVec4 &B, const CVec4 &C) const +{ + float vol; + float ax, ay, az, bx, by, bz, cx, cy, cz, dx, dy, dz; + float bxdx, bydy, bzdz, cxdx, cydy, czdz; + + float tolerance=0.00000005f; + + ax = A.v[0]; + ay = A.v[1]; + az = ax*ax + ay*ay; + bx = B.v[0]; + by = B.v[1]; + bz = bx*bx + by*by; + cx = C.v[0]; + cy = C.v[1]; + cz = cx*cx + cy*cy; + dx = v[0]; + dy = v[1]; + dz = dx*dx + dy*dy; + + bxdx=bx-dx; + bydy=by-dy; + bzdz=bz-dz; + cxdx=cx-dx; + cydy=cy-dy; + czdz=cz-dz; + vol = (az-dz)*(bxdx*cydy-bydy*cxdx) + (ay-dy)*(bzdz*cxdx-bxdx*czdz) + (ax-dx)*(bydy*czdz-bzdz*cydy); + + if ( vol > tolerance) return true; + else if ( vol < -1*tolerance) return false; + else return false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Point In Standard Circle (True/False) +// +// Returns true if the given point is within the Circle +// _____ +// / \ +// / \ +// | Circle | +// | | +// \ Pt / +// \_______/ +// +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec4::PtInCircle(const CVec4 &Circle, float Radius) const +{ + return (Dist2(Circle)<(Radius*Radius)); +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Line Intersects Circle (True/False) +// +// r - Radius Of The Circle +// A - Start Of Line Segment +// B - End Of Line Segment +// +// P - Projected Position Of Origin Onto Line AB +// +// +// B +// / +// / +// P +// / \ \ +// / this-r->| +// / / +// A +// +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec4::LineInCircle(const CVec4 &A, const CVec4 &B, float r, CVec4 &P) +{ + P = (*this); + float Scale = P.ProjectToLine(A, B); + + // If The Projected Position Is Not On The Line Segment, + // Check If It Is Within Radius Of Endpoints A and B. + //------------------------------------------------------- + if (Scale<0 || Scale>1) + { + return (PtInCircle(A, r) || PtInCircle(B, r)); + } + + // Otherwise, Check To See If P Is Within The Radius Of This Circle + //------------------------------------------------------------------ + return (PtInCircle(P, r)); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Same As Test Above, Just Don't Bother Returning P +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec4::LineInCircle(const CVec4 &A, const CVec4 &B, float r) +{ + CVec4 P(*this); + float Scale = P.ProjectToLine(A, B); + + // If The Projected Position Is Not On The Line Segment, + // Check If It Is Within Radius Of Endpoints A and B. + //------------------------------------------------------- + if (Scale<0 || Scale>1) + { + return (PtInCircle(A, r) || PtInCircle(B, r)); + } + + // Otherwise, Check To See If P Is Within The Radius Of This Circle + //------------------------------------------------------------------ + return (PtInCircle(P, r)); +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Rotate +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::RotatePoint(const CVec4 &, const CVec4 &) +{ + // TO DO: Use Matrix Code To Rotate +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Reposition +//////////////////////////////////////////////////////////////////////////////////////// +void CVec4::Reposition(const CVec4 &Translation, float RotationDegrees) +{ + // Apply Any Rotation First + //-------------------------- + if (RotationDegrees) + { + CVec4 Old(*this); + float Rotation = RAVL_VEC_DEGTORAD(RotationDegrees); + v[0] = Old.v[0]*cosf(Rotation) - Old.v[1]*sinf(Rotation); + v[1] = Old.v[0]*sinf(Rotation) + Old.v[1]*cosf(Rotation); + } + + // Now Apply Translation + //----------------------- + (*this) += Translation; +} + + + + + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Static Class Member Initialization +//////////////////////////////////////////////////////////////////////////////////////// +const CVec3 CVec3::mX(1.f, 0.f, 0.f); +const CVec3 CVec3::mY(0.f, 1.f, 0.f); +const CVec3 CVec3::mZ(0.f, 0.f, 1.f); +const CVec3 CVec3::mZero(0.f, 0.f, 0.f); + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Length +//////////////////////////////////////////////////////////////////////////////////////// +float CVec3::Len() const +{ + return sqrtf(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Distance To Other Point +//////////////////////////////////////////////////////////////////////////////////////// +float CVec3::Dist(const CVec3& t) const +{ + return sqrtf( + (t.v[0]-v[0])*(t.v[0]-v[0]) + + (t.v[1]-v[1])*(t.v[1]-v[1]) + + (t.v[2]-v[2])*(t.v[2]-v[2])); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Normalize +//////////////////////////////////////////////////////////////////////////////////////// +float CVec3::Norm() +{ + float L = Len(); + (*this)/=L; + return L; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Safe Normalize +// Do Not Normalize If Length Is Too Small +//////////////////////////////////////////////////////////////////////////////////////// +float CVec3::SafeNorm() +{ + float d=this->Len(); + if (d>1E-10) + { + (*this)/=d; + return d; + } + (*this)=0.0f; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Angular Normalize +// All floats Exist(-180, +180) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::AngleNorm() +{ + v[0]= fmodf(v[0],360.0f); + if (v[0]<-180.f) v[0]+=360.0f; + if (v[0]>180.f) v[0]-=360.0f; + + v[1]= fmodf(v[1],360.0f); + if (v[1]<-180.f) v[1]+=360.0f; + if (v[1]>180.f) v[1]-=360.0f; + + v[2]= fmodf(v[2],360.0f); + if (v[2]<-180.f) v[2]+=360.0f; + if (v[2]>180.f) v[2]-=360.0f; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Angular Normalize +// All floats Exist(-180, +180) +//////////////////////////////////////////////////////////////////////////////////////// +float CVec3::Truncate(float maxlen) +{ + float len=this->Len(); + if (len>maxlen && len>1E-10) + { + len = maxlen / len; + v[0] *= len; + v[1] *= len; + v[2] *= len; + + return maxlen; + } + return len; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Perpendicular Vector +// +// This implimentation is a bit slow, needs some optimization work... +// +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::Perp() +{ + float rlen,tlen; + CVec3 r,t; + r = (*this); + r.Cross(mX); + rlen=r.Len(); + t = (*this); + t.Cross(mY); + tlen=t.Len(); + if (tlen>rlen) + { + r=t; + rlen=tlen; + } + t = (*this); + t.Cross(mZ); + tlen=t.Len(); + if (tlen>rlen) + { + r=t; + rlen=tlen; + } + (*this) = r; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Find Largest Element (Ignores 4th component for now) +//////////////////////////////////////////////////////////////////////////////////////// +int CVec3::MaxElementIndex() const +{ + if (fabs(v[0])>fabs(v[1]) && fabs(v[0])>fabs(v[2])) + { + return 0; + } + if (fabs(v[1])>fabs(v[2])) + { + return 1; + } + return 2; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert To {Pitch, Yaw} (DEGREES) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::VecToAng() +{ + float yaw; + float pitch; + + if (!v[1] && !v[0]) + { + yaw = 0; + pitch = (v[2]>0) ? (90.0f):(270.0f); + } + else + { + // Calculate Yaw + //--------------- + if (v[0]) + { + yaw = (RAVL_VEC_RADTODEG(atan2f(v[1], v[0]))); + if (yaw<0) + { + yaw += 360; + } + } + else + { + yaw = (v[1]>0) ? (90.0f):(270.0f); + } + + // Calculate Pitch + //----------------- + float forward = (sqrtf(v[0]*v[0] + v[1]*v[1])); + pitch = (RAVL_VEC_RADTODEG(atan2f(v[2], forward))); + if (pitch<0) + { + pitch += 360; + } + } + + // Copy Over Current Vector + //-------------------------- + v[0] = -pitch; + v[1] = yaw; + v[2] = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert From {Picth, Yaw} (DEGREES) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::AngToVec() +{ + float angle; + float sp, sy, cp, cy; + + angle = yaw() * (RAVL_VEC_DEGTORADCONST); + sy = sinf(angle); + cy = cosf(angle); + angle = pitch() * (RAVL_VEC_DEGTORADCONST); + sp = sinf(angle); + cp = cosf(angle); + + v[0] = cp * cy; + v[1] = cp * sy; + v[2] = -sp; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert From {Picth, Yaw, Roll} (DEGREES) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::AngToVec(CVec3& Right, CVec3& Up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = yaw() * (RAVL_VEC_DEGTORADCONST); + sy = sinf(angle); + cy = cosf(angle); + angle = pitch() * (RAVL_VEC_DEGTORADCONST); + sp = sinf(angle); + cp = cosf(angle); + angle = roll() * (RAVL_VEC_DEGTORADCONST); + sr = sinf(angle); + cr = cosf(angle); + + // Forward Vector Is Stored Here + v[0] = cp * cy; + v[1] = cp * sy; + v[2] = -sp; + + // Calculate Right + Right.v[0] = (-1 * sr * sp * cy + -1 * cr * -sy); + Right.v[1] = (-1 * sr * sp * sy + -1 * cr * cy); + Right.v[2] = -1 * sr * cp; + + // Calculate Up + Up.v[0] = (cr * sp * cy + -sr * -sy); + Up.v[1] = (cr * sp * sy + -sr * cy); + Up.v[2] = cr * cp; +} + + + + +/////////////////////////////////////////////////////////////////////////////////////// +// Convert To {Pitch, Yaw} (RADIANS) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::VecToAngRad() +{ + float yaw; + float pitch; + + if (!v[1] && !v[0]) + { + yaw = 0; + pitch = (v[2]>0) ? (RAVL_VEC_PI*0.5f):(RAVL_VEC_PI*1.5f); + } + else + { + // Calculate Yaw + //--------------- + if (v[0]) + { + yaw = (atan2f(v[1], v[0])); + } + else + { + yaw = (v[1]>0) ? (RAVL_VEC_PI*0.5f):(RAVL_VEC_PI*1.5f); + } + + // Calculate Pitch + //----------------- + float forward = (sqrtf(v[0]*v[0] + v[1]*v[1])); + pitch = (atan2f(v[2], forward)); + } + + // Copy Over Current Vector + //-------------------------- + v[0] = -pitch; + v[1] = yaw; + v[2] = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert From {Picth, Yaw} (RADIANS) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::AngToVecRad() +{ + float sp, sy, cp, cy; + + sy = sinf(yaw()); + cy = cosf(yaw()); + sp = sinf(pitch()); + cp = cosf(pitch()); + + v[0] = cp * cy; + v[1] = cp * sy; + v[2] = -sp; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert From {Picth, Yaw, Roll} (RADIANS) +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::AngToVecRad(CVec3& Right, CVec3& Up) +{ + float sr, sp, sy, cr, cp, cy; + + sy = sinf(yaw()); + cy = cosf(yaw()); + sp = sinf(pitch()); + cp = cosf(pitch()); + sr = sinf(roll()); + cr = cosf(roll()); + + // Forward Vector Is Stored Here + v[0] = cp * cy; + v[1] = cp * sy; + v[2] = -sp; + + // Calculate Right + Right.v[0] = (-1 * sr * sp * cy + -1 * cr * -sy); + Right.v[1] = (-1 * sr * sp * sy + -1 * cr * cy); + Right.v[2] = -1 * sr * cp; + + // Calculate Up + Up.v[0] = (cr * sp * cy + -sr * -sy); + Up.v[1] = (cr * sp * sy + -sr * cy); + Up.v[2] = cr * cp; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert To Radians +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::ToRadians() +{ + v[0] = RAVL_VEC_DEGTORAD(v[0]); + v[1] = RAVL_VEC_DEGTORAD(v[1]); + v[2] = RAVL_VEC_DEGTORAD(v[2]); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Convert To Degrees +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::ToDegrees() +{ + v[0] = RAVL_VEC_RADTODEG(v[0]); + v[1] = RAVL_VEC_RADTODEG(v[1]); + v[2] = RAVL_VEC_RADTODEG(v[2]); +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Copy Values From String +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::FromStr(const char *s) +{ + assert(s && s[0]); + sscanf(s, "(%f %f %f)", &v[0], &v[1], &v[2]); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Write Values To A String +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::ToStr(char* s) const +{ + assert(s); + sprintf(s, "(%3.3f %3.3f %3.3f)", v[0], v[1], v[2]); +} + + + +#ifdef _DEBUG + +//////////////////////////////////////////////////////////////////////////////////////// +// Make Sure Entire Vector Has Valid Numbers +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec3::IsFinite() +{ +#if defined(_MSC_VER) + return (_finite(v[0]) && _finite(v[1]) && _finite(v[2])); +#endif +#if defined(__MWERKS__) + //FIXME: _finite won't compile on the PS2, so commented it out for now, false seemed like the best option + return true; +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Make Sure Vector Has Been Initialized +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec3::IsInitialized() +{ + return (v[0]!=RAVL_VEC_UDF && v[1]!=RAVL_VEC_UDF && v[2]!=RAVL_VEC_UDF); +} + +#endif + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Point In Circumscribed Circle (True/False) +// +// Returns true if the given point is within the circumscribed +// circle of the given ABC Triangle: +// _____ +// / B \ +// / / \ \ +// | / \ | +// |A---------C| +// \ Pt / +// \_______/ +// +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec3::PtInCircle(const CVec3 &A, const CVec3 &B, const CVec3 &C) const +{ + float vol; + float ax, ay, az, bx, by, bz, cx, cy, cz, dx, dy, dz; + float bxdx, bydy, bzdz, cxdx, cydy, czdz; + + float tolerance=0.00000005f; + + ax = A.v[0]; + ay = A.v[1]; + az = ax*ax + ay*ay; + bx = B.v[0]; + by = B.v[1]; + bz = bx*bx + by*by; + cx = C.v[0]; + cy = C.v[1]; + cz = cx*cx + cy*cy; + dx = v[0]; + dy = v[1]; + dz = dx*dx + dy*dy; + + bxdx=bx-dx; + bydy=by-dy; + bzdz=bz-dz; + cxdx=cx-dx; + cydy=cy-dy; + czdz=cz-dz; + vol = (az-dz)*(bxdx*cydy-bydy*cxdx) + (ay-dy)*(bzdz*cxdx-bxdx*czdz) + (ax-dx)*(bydy*czdz-bzdz*cydy); + + if ( vol > tolerance) return true; + else if ( vol < -1*tolerance) return false; + else return false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Point In Standard Circle (True/False) +// +// Returns true if the given point is within the Circle +// _____ +// / \ +// / \ +// | Circle | +// | | +// \ Pt / +// \_______/ +// +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec3::PtInCircle(const CVec3 &Circle, float Radius) const +{ + return (Dist2(Circle)<(Radius*Radius)); +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Line Intersects Circle (True/False) +// +// r - Radius Of The Circle +// A - Start Of Line Segment +// B - End Of Line Segment +// +// P - Projected Position Of Origin Onto Line AB +// +// +// B +// / +// / +// P +// / \ \ +// / this-r->| +// / / +// A +// +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec3::LineInCircle(const CVec3 &A, const CVec3 &B, float r, CVec3 &P) +{ + P = (*this); + float Scale = P.ProjectToLine(A, B); + + // If The Projected Position Is Not On The Line Segment, + // Check If It Is Within Radius Of Endpoints A and B. + //------------------------------------------------------- + if (Scale<0 || Scale>1) + { + return (PtInCircle(A, r) || PtInCircle(B, r)); + } + + // Otherwise, Check To See If P Is Within The Radius Of This Circle + //------------------------------------------------------------------ + return (PtInCircle(P, r)); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Same As Test Above, Just Don't Bother Returning P +//////////////////////////////////////////////////////////////////////////////////////// +bool CVec3::LineInCircle(const CVec3 &A, const CVec3 &B, float r) +{ + CVec3 P(*this); + float Scale = P.ProjectToLine(A, B); + + // If The Projected Position Is Not On The Line Segment, + // Check If It Is Within Radius Of Endpoints A and B. + //------------------------------------------------------- + if (Scale<0 || Scale>1) + { + return (PtInCircle(A, r) || PtInCircle(B, r)); + } + + // Otherwise, Check To See If P Is Within The Radius Of This Circle + //------------------------------------------------------------------ + return (PtInCircle(P, r)); +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Rotate +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::RotatePoint(const CVec3 &, const CVec3 &) +{ + // TO DO: Use Matrix Code To Rotate +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Reposition +//////////////////////////////////////////////////////////////////////////////////////// +void CVec3::Reposition(const CVec3 &Translation, float RotationDegrees) +{ + // Apply Any Rotation First + //-------------------------- + if (RotationDegrees) + { + CVec3 Old(*this); + float Rotation = RAVL_VEC_DEGTORAD(RotationDegrees); + v[0] = Old.v[0]*cosf(Rotation) - Old.v[1]*sinf(Rotation); + v[1] = Old.v[0]*sinf(Rotation) + Old.v[1]*cosf(Rotation); + } + + // Now Apply Translation + //----------------------- + (*this) += Translation; +} + diff --git a/code/ravl/CVec.h b/code/ravl/CVec.h new file mode 100644 index 0000000..2e35a9d --- /dev/null +++ b/code/ravl/CVec.h @@ -0,0 +1,1002 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD TEMPLATE LIBRARY +// (c) 2002 Activision +// +// +// Vector Library +// -------------- +// The base implimention of the Raven Vector object attempts to solve a number of +// high level problems as efficiently as possible. Where ever feasible, functions have +// been included in the .h file so the compiler can inline them. +// +// The vectors define the following operations: +// - Construction +// - Initialization +// - Member Access +// - Equality / Inequality Operators +// - Arithimitic Operators +// - Length & Distance +// - Normalization (Standard, Safe, Angular) +// - Dot & Cross Product +// - Perpendicular Vector +// - Truncation +// - Min & Max Element Analisis +// - Interpolation +// - Angle / Vector Conversion +// - Translation & Rotation +// - Point and Line Intersection Tests +// - Left / Right Line Test +// - String Operations +// - Debug Routines +// - "Standard" Vectors As Static Memebers +// +// As necessary, some projects may #define special faster versions of these routines to +// make better use of native hardware / software implimentations. +// +// +// +// +// NOTES: +// 05/29/02 - CREATED +// 05/30/02 - RotatePoint() is currently unimplimented. Waiting for Matrix Library +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RAVL_VEC_INC) +#define RAVL_VEC_INC +//namespace ravl +//{ + + +template T Min(const T& a, const T& b) {return (a T Max(const T& a, const T& b) {return (b Radians +#define RAVL_VEC_RADTODEG( a ) ( (a) * RAVL_VEC_RADTODEGCONST ) // Quick Macro For Radians -> Degrees + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Enums And Typedefs +//////////////////////////////////////////////////////////////////////////////////////// +enum ESide +{ + Side_None = 0, + Side_Left = 1, + Side_Right = 2, + Side_In = 3, + Side_Out = 4, + Side_AllIn = 5 +}; + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The 4 Dimensional Vector +//////////////////////////////////////////////////////////////////////////////////////// +class CVec4 +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructors + //////////////////////////////////////////////////////////////////////////////////// +#ifndef _DEBUG + CVec4() {} +#else + CVec4() {v[0]=v[1]=v[2]=v[3]=RAVL_VEC_UDF;} // DEBUG INITIALIZATION +#endif + CVec4(const float val) {v[0]=val; v[1]=val; v[2]=val; v[3]=val;} + CVec4(const float x,const float y,const float z, const float r) {v[0]=x; v[1]=y; v[2]=z; v[3]=r;} + CVec4(const CVec4& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2]; v[3]=t.v[3];} + CVec4(const float *t) {v[0]=t[0]; v[1]=t[1]; v[2]=t[2]; v[3]=t[3];} + + //////////////////////////////////////////////////////////////////////////////////// + // Initializers + //////////////////////////////////////////////////////////////////////////////////// + void Set(const float t) {v[0]=t; v[1]=t; v[2]=t; v[3]=t;} + void Set(const float *t) {v[0]=t[0]; v[1]=t[1]; v[2]=t[2]; v[3]=t[3];} + void Set(const float x,const float y,const float z, const float r) {v[0]=x; v[1]=y; v[2]=z; v[3]=r;} + void Clear() {v[0]=0; v[1]=0; v[2]=0; v[3]=0;} + + //////////////////////////////////////////////////////////////////////////////////// + // Member Accessors + //////////////////////////////////////////////////////////////////////////////////// + const float& operator[](int i) const {return v[i];} + float& operator[](int i) {return v[i];} + float& pitch() {return v[0];} + float& yaw() {return v[1];} + float& roll() {return v[2];} + float& radius() {return v[3];} + + //////////////////////////////////////////////////////////////////////////////////// + // Equality / Inequality Operators + //////////////////////////////////////////////////////////////////////////////////// + bool operator! () const {return !(v[0] && v[1] && v[2] && v[3] );} + bool operator== (const CVec4& t) const {return (v[0]==t.v[0] && v[1]==t.v[1] && v[2]==t.v[2] && v[3]==t.v[3]);} + bool operator!= (const CVec4& t) const {return !(v[0]==t.v[0] && v[1]==t.v[1] && v[2]==t.v[2] && v[3]==t.v[3]);} + bool operator< (const CVec4& t) const {return (v[0]< t.v[0] && v[1]< t.v[1] && v[2]< t.v[2] && v[3]< t.v[3]);} + bool operator> (const CVec4& t) const {return (v[0]> t.v[0] && v[1]> t.v[1] && v[2]> t.v[2] && v[3]> t.v[3]);} + bool operator<= (const CVec4& t) const {return (v[0]<=t.v[0] && v[1]<=t.v[1] && v[2]<=t.v[2] && v[3]<=t.v[3]);} + bool operator>= (const CVec4& t) const {return (v[0]>=t.v[0] && v[1]>=t.v[1] && v[2]>=t.v[2] && v[3]>=t.v[3]);} + + //////////////////////////////////////////////////////////////////////////////////// + // Basic Arithimitic Operators + //////////////////////////////////////////////////////////////////////////////////// + const CVec4 &operator= (const float d) {v[0]=d; v[1]=d; v[2]=d; v[3]=d; return *this;} + const CVec4 &operator= (const CVec4& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2]; v[3]=t.v[3]; return *this;} + + const CVec4 &operator+= (const float d) {v[0]+=d; v[1]+=d; v[2]+=d; v[3]+=d; return *this;} + const CVec4 &operator+= (const CVec4& t) {v[0]+=t.v[0]; v[1]+=t.v[1]; v[2]+=t.v[2]; v[3]+=t.v[3];return *this;} + + const CVec4 &operator-= (const float d) {v[0]-=d; v[1]-=d; v[2]-=d; v[3]-=d; return *this;} + const CVec4 &operator-= (const CVec4& t) {v[0]-=t.v[0]; v[1]-=t.v[1]; v[2]-=t.v[2]; v[3]-=t.v[3];return *this;} + + const CVec4 &operator*= (const float d) {v[0]*=d; v[1]*=d; v[2]*=d; v[3]*=d; return *this;} + const CVec4 &operator*= (const CVec4& t) {v[0]*=t.v[0]; v[1]*=t.v[1]; v[2]*=t.v[2]; v[3]*=t.v[3];return *this;} + + const CVec4 &operator/= (const float d) {v[0]/=d; v[1]/=d; v[2]/=d; v[3]/=d; return *this;} + const CVec4 &operator/= (const CVec4& t) {v[0]/=t.v[0]; v[1]/=t.v[1]; v[2]/=t.v[2]; v[3]/=t.v[3];return *this;} + + inline CVec4 operator+ (const CVec4 &t) const {return CVec4(v[0]+t.v[0], v[1]+t.v[1], v[2]+t.v[2], v[3]+t.v[3]);} + inline CVec4 operator- (const CVec4 &t) const {return CVec4(v[0]-t.v[0], v[1]-t.v[1], v[2]-t.v[2], v[3]-t.v[3]);} + inline CVec4 operator* (const CVec4 &t) const {return CVec4(v[0]*t.v[0], v[1]*t.v[1], v[2]*t.v[2], v[3]*t.v[3]);} + inline CVec4 operator/ (const CVec4 &t) const {return CVec4(v[0]/t.v[0], v[1]/t.v[1], v[2]/t.v[2], v[3]/t.v[3]);} + + + //////////////////////////////////////////////////////////////////////////////////// + // Length And Distance Calculations + //////////////////////////////////////////////////////////////////////////////////// + float Len() const; + float Len2() const {return (v[0]*v[0]+v[1]*v[1]+v[2]*v[2]+v[3]*v[3]);} + + float Dist(const CVec4& t) const; + float Dist2(const CVec4& t) const {return ((t.v[0]-v[0])*(t.v[0]-v[0]) + (t.v[1]-v[1])*(t.v[1]-v[1]) + (t.v[2]-v[2])*(t.v[2]-v[2]) + (t.v[3]-v[3])*(t.v[3]-v[3]) );} + + + //////////////////////////////////////////////////////////////////////////////////// + // Normalization + //////////////////////////////////////////////////////////////////////////////////// + float Norm(); + float SafeNorm(); + void AngleNorm(); + + + //////////////////////////////////////////////////////////////////////////////////// + // Dot, Cross & Perpendicular Vector + //////////////////////////////////////////////////////////////////////////////////// + float Dot(const CVec4& t) const {return (v[0]*t.v[0] + v[1]*t.v[1] + v[2]*t.v[2] + v[3]*t.v[3]);} + void Cross(const CVec4& t) + { + CVec4 temp(*this); + v[0] = (temp.v[1]*t.v[2]) - (temp.v[2]*t.v[1]); + v[1] = (temp.v[2]*t.v[0]) - (temp.v[0]*t.v[2]); + v[2] = (temp.v[0]*t.v[1]) - (temp.v[1]*t.v[0]); + v[3] = 0; + } + void Perp(); + + + //////////////////////////////////////////////////////////////////////////////////// + // Truncation & Element Analysis + //////////////////////////////////////////////////////////////////////////////////// + void Min(const CVec4& t) + { + if (t.v[0]v[0]) v[0]=t.v[0]; + if (t.v[1]>v[1]) v[1]=t.v[1]; + if (t.v[2]>v[2]) v[2]=t.v[2]; + if (t.v[3]>v[3]) v[3]=t.v[3]; + } + float MaxElement() const + { + return v[MaxElementIndex()]; + } + int MaxElementIndex() const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Interpolation + //////////////////////////////////////////////////////////////////////////////////// + void Interp(const CVec4 &v1, const CVec4 &v2, const float t) + { + (*this)=v1; + (*this)-=v2; + (*this)*=t; + (*this)+=v2; + } + void ScaleAdd(const CVec4& t, const float scale) + { + v[0] += (scale * t.v[0]); + v[1] += (scale * t.v[1]); + v[2] += (scale * t.v[2]); + v[3] += (scale * t.v[3]); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Angle To Vector (Angle In Degrees) + //////////////////////////////////////////////////////////////////////////////////// + void VecToAng(); + void AngToVec(); + void AngToVec(CVec4& Right, CVec4& Up); + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Angle To Vector (Angle In Radians) + //////////////////////////////////////////////////////////////////////////////////// + void VecToAngRad(); + void AngToVecRad(); + void AngToVecRad(CVec4& Right, CVec4& Up); + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Between Radians And Degrees + //////////////////////////////////////////////////////////////////////////////////// + void ToRadians(); + void ToDegrees(); + + + + //////////////////////////////////////////////////////////////////////////////////// + // Project + // + // Standard projection function. Take the (this) and project it onto the vector + // (U). Imagine drawing a line perpendicular to U from the endpoint of the (this) + // Vector. That then becomes the new vector. + // + // The value returned is the scale of the new vector with respect to the one passed + // to the function. If the scale is less than (1.0) then the new vector is shorter + // than (U). If the scale is negative, then the vector is going in the opposite + // direction of (U). + // + // _ (U) + // /| + // / _ (this) + // / RESULTS-> /| + // / / + // / __\ (this) / + // /___--- / / + // + //////////////////////////////////////////////////////////////////////////////////// + float Project(const CVec4 &U) + { + float Scale = (Dot(U) / U.Len2()); // Find the scale of this vector on U + (*this)=U; // Copy U onto this vector + (*this)*=Scale; // Use the previously calculated scale to get the right length. + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Project To Line + // + // This function takes two other points in space as the start and end of a line + // segment and projects the (this) point onto the line defined by (Start)->(Stop) + // + // RETURN VALUES: + // (-INF, 0.0) : (this) landed on the line before (Start) + // (0.0, 1.0) : (this) landed in the line segment between (Start) and (Stop) + // (1.0, INF) : (this) landed on the line beyond (End) + // + // (Stop) + // / + // / + // o _ + // / |\ + // / (this) + // / + // (Start) + // + //////////////////////////////////////////////////////////////////////////////////// + float ProjectToLine(const CVec4 &Start, const CVec4 &Stop) + { + (*this) -= Start; + float Scale = Project(Stop - Start); + (*this) += Start; + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Project To Line Seg + // + // Same As Project To Line, Except It Will Clamp To Start And Stop + //////////////////////////////////////////////////////////////////////////////////// + float ProjectToLineSeg(const CVec4 &Start, const CVec4 &Stop) + { + float Scale = ProjectToLine(Start, Stop); + if (Scale<0.0f) + { + (*this) = Start; + } + else if (Scale>1.0f) + { + (*this) = Stop; + } + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance To Line + // + // Uses project to line and than calculates distance to the new point + //////////////////////////////////////////////////////////////////////////////////// + float DistToLine(const CVec4 &Start, const CVec4 &Stop) const + { + CVec4 P(*this); + P.ProjectToLineSeg(Start, Stop); + + return Dist(P); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance To Line + // + // Uses project to line and than calculates distance to the new point + //////////////////////////////////////////////////////////////////////////////////// + float DistToLine2(const CVec4 &Start, const CVec4 &Stop) const + { + CVec4 P(*this); + P.ProjectToLineSeg(Start, Stop); + + return Dist2(P); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Translation & Rotation (2D) + //////////////////////////////////////////////////////////////////////////////////// + void RotatePoint(const CVec4 &Angle, const CVec4 &Origin); + void Reposition(const CVec4 &Translation, float RotationDegrees=0.0); + + + + //////////////////////////////////////////////////////////////////////////////////// + // Area Of The Parallel Pipid (2D) + // + // Given two more points, this function calculates the area of the parallel pipid + // formed. + // + // Note: This function CAN return a negative "area" if (this) is above or right of + // (A) and (B)... We do not take the abs because the sign of the "area" is needed + // for the left right test (see below) + // + // + // ___---( ... ) + // (A)---/ / + // / / + // / / + // / / + // / ___---(B) + // (this)---/ + // + //////////////////////////////////////////////////////////////////////////////////// + float AreaParallelPipid(const CVec4 &A, const CVec4 &B) const + { + return ((A.v[0]*B.v[1] - A.v[1]*B.v[0]) + + (B.v[0]* v[1] - v[0]*B.v[1]) + + ( v[0]*A.v[1] - A.v[0]* v[1])); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Area Of The Triangle (2D) + // + // Given two more points, this function calculates the area of the triangle formed. + // + // (A) + // / \__ + // / \__ + // / \_ + // / ___---(B) + // (this)---/ + // + //////////////////////////////////////////////////////////////////////////////////// + float AreaTriange(const CVec4 &A, const CVec4 &B) const + { + return (AreaParallelPipid(A, B) * 0.5f); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // The Left Right Test (2D) + // + // Given a line segment (Start->End) and a tolerance for *right on*, this function + // evaluates which side the point is of the line. (Side_Left in this example) + // + // + // + // (this) ___---/(End) + // ___---/ + // ___---/ + // (Start)/ + // + //////////////////////////////////////////////////////////////////////////////////// + ESide LRTest(const CVec4 &Start, const CVec4 &End, float Tolerance=0.0) const + { + float Area = AreaParallelPipid(Start, End); + if (Area>Tolerance) + { + return Side_Left; + } + if (Area<(Tolerance*-1)) + { + return Side_Right; + } + return Side_None; + + } + + //////////////////////////////////////////////////////////////////////////////////// + // Point In Circumscribed Circle (True/False) + // + // Returns true if the given point is within the circumscribed + // circle of the given ABC Triangle: + // _____ + // / B \ + // / / \ \ + // | / \ | + // |A---------C| + // \ Pt / + // \_______/ + // + //////////////////////////////////////////////////////////////////////////////////// + bool PtInCircle(const CVec4 &A, const CVec4 &B, const CVec4 &C) const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Point In Standard Circle (True/False) + // + // Returns true if the given point is within the Circle + // _____ + // / \ + // / \ + // | Circle | + // | | + // \ Pt / + // \_______/ + // + //////////////////////////////////////////////////////////////////////////////////// + bool PtInCircle(const CVec4 &Circle, float Radius) const; + + //////////////////////////////////////////////////////////////////////////////////// + // Line Intersects Circle (True/False) + // + // r - Radius Of The Circle + // A - Start Of Line Segment + // B - End Of Line Segment + // + // P - Projected Position Of Origin Onto Line AB + // + // + // (Stop) + // / + // / + // (P) + // / \ \ + // / (this)-r->| + // / / + // (Start) + // + //////////////////////////////////////////////////////////////////////////////////// + bool LineInCircle(const CVec4 &Start, const CVec4 &Stop, float Radius); + bool LineInCircle(const CVec4 &Start, const CVec4 &Stop, float Radius, CVec4 &PointOnLine); + + + + //////////////////////////////////////////////////////////////////////////////////// + // String Operations + //////////////////////////////////////////////////////////////////////////////////// + void FromStr(const char *s); + void ToStr(char* s) const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Debug Routines + //////////////////////////////////////////////////////////////////////////////////// +#ifdef _DEBUG + bool IsFinite(); + bool IsInitialized(); +#endif + + + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// +private: + float v[4]; + + +public: + static const CVec4 mX; + static const CVec4 mY; + static const CVec4 mZ; + static const CVec4 mW; + static const CVec4 mZero; +}; + + + + + + + + + + + + + + + + + + + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The 3 Dimensional Vector +//////////////////////////////////////////////////////////////////////////////////////// +class CVec3 +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructors + //////////////////////////////////////////////////////////////////////////////////// +#ifndef _DEBUG + CVec3() {} +#else + CVec3() {v[0]=v[1]=v[2]=RAVL_VEC_UDF;} // DEBUG INITIALIZATION +#endif + CVec3(const float val) {v[0]=val; v[1]=val; v[2]=val; } + CVec3(const float x,const float y,const float z) {v[0]=x; v[1]=y; v[2]=z; } + CVec3(const CVec3& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2];} + CVec3(const float *t) {v[0]=t[0]; v[1]=t[1]; v[2]=t[2]; } + + float x() const {return v[0];} + float y() const {return v[1];} + float z() const {return v[2];} + + //////////////////////////////////////////////////////////////////////////////////// + // Initializers + //////////////////////////////////////////////////////////////////////////////////// + void Set(const float t) {v[0]=t; v[1]=t; v[2]=t; } + void Set(const float *t) {v[0]=t[0]; v[1]=t[1]; v[2]=t[2]; } + void Set(const float x,const float y,const float z) {v[0]=x; v[1]=y; v[2]=z; } + void Clear() {v[0]=0; v[1]=0; v[2]=0; } + + //////////////////////////////////////////////////////////////////////////////////// + // Member Accessors + //////////////////////////////////////////////////////////////////////////////////// + const float& operator[](int i) const {return v[i];} + float& operator[](int i) {return v[i];} + float& pitch() {return v[0];} + float& yaw() {return v[1];} + float& roll() {return v[2];} + float& radius() {return v[3];} + + //////////////////////////////////////////////////////////////////////////////////// + // Equality / Inequality Operators + //////////////////////////////////////////////////////////////////////////////////// + bool operator! () const {return !(v[0] && v[1] && v[2] );} + bool operator== (const CVec3& t) const {return (v[0]==t.v[0] && v[1]==t.v[1] && v[2]==t.v[2]);} + bool operator!= (const CVec3& t) const {return !(v[0]==t.v[0] && v[1]==t.v[1] && v[2]==t.v[2]);} + bool operator< (const CVec3& t) const {return (v[0]< t.v[0] && v[1]< t.v[1] && v[2]< t.v[2]);} + bool operator> (const CVec3& t) const {return (v[0]> t.v[0] && v[1]> t.v[1] && v[2]> t.v[2]);} + bool operator<= (const CVec3& t) const {return (v[0]<=t.v[0] && v[1]<=t.v[1] && v[2]<=t.v[2]);} + bool operator>= (const CVec3& t) const {return (v[0]>=t.v[0] && v[1]>=t.v[1] && v[2]>=t.v[2]);} + + //////////////////////////////////////////////////////////////////////////////////// + // Basic Arithimitic Operators + //////////////////////////////////////////////////////////////////////////////////// + const CVec3 &operator= (const float d) {v[0]=d; v[1]=d; v[2]=d; return *this;} + const CVec3 &operator= (const CVec3& t) {v[0]=t.v[0]; v[1]=t.v[1]; v[2]=t.v[2]; return *this;} + + const CVec3 &operator+= (const float d) {v[0]+=d; v[1]+=d; v[2]+=d; return *this;} + const CVec3 &operator+= (const CVec3& t) {v[0]+=t.v[0]; v[1]+=t.v[1]; v[2]+=t.v[2];return *this;} + + const CVec3 &operator-= (const float d) {v[0]-=d; v[1]-=d; v[2]-=d; return *this;} + const CVec3 &operator-= (const CVec3& t) {v[0]-=t.v[0]; v[1]-=t.v[1]; v[2]-=t.v[2];return *this;} + + const CVec3 &operator*= (const float d) {v[0]*=d; v[1]*=d; v[2]*=d; return *this;} + const CVec3 &operator*= (const CVec3& t) {v[0]*=t.v[0]; v[1]*=t.v[1]; v[2]*=t.v[2];return *this;} + + const CVec3 &operator/= (const float d) {v[0]/=d; v[1]/=d; v[2]/=d; return *this;} + const CVec3 &operator/= (const CVec3& t) {v[0]/=t.v[0]; v[1]/=t.v[1]; v[2]/=t.v[2];return *this;} + + inline CVec3 operator+ (const CVec3 &t) const {return CVec3(v[0]+t.v[0], v[1]+t.v[1], v[2]+t.v[2]);} + inline CVec3 operator- (const CVec3 &t) const {return CVec3(v[0]-t.v[0], v[1]-t.v[1], v[2]-t.v[2]);} + inline CVec3 operator* (const CVec3 &t) const {return CVec3(v[0]*t.v[0], v[1]*t.v[1], v[2]*t.v[2]);} + inline CVec3 operator/ (const CVec3 &t) const {return CVec3(v[0]/t.v[0], v[1]/t.v[1], v[2]/t.v[2]);} + + + //////////////////////////////////////////////////////////////////////////////////// + // Length And Distance Calculations + //////////////////////////////////////////////////////////////////////////////////// + float Len() const; + float Len2() const {return (v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);} + + float Dist(const CVec3& t) const; + float Dist2(const CVec3& t) const {return ((t.v[0]-v[0])*(t.v[0]-v[0]) + (t.v[1]-v[1])*(t.v[1]-v[1]) + (t.v[2]-v[2])*(t.v[2]-v[2]));} + + + //////////////////////////////////////////////////////////////////////////////////// + // Normalization + //////////////////////////////////////////////////////////////////////////////////// + float Norm(); + float SafeNorm(); + void AngleNorm(); + float Truncate(float maxlen); + + + //////////////////////////////////////////////////////////////////////////////////// + // Dot, Cross & Perpendicular Vector + //////////////////////////////////////////////////////////////////////////////////// + float Dot(const CVec3& t) const {return (v[0]*t.v[0] + v[1]*t.v[1] + v[2]*t.v[2]);} + void Cross(const CVec3& t) + { + CVec3 temp(*this); + v[0] = (temp.v[1]*t.v[2]) - (temp.v[2]*t.v[1]); + v[1] = (temp.v[2]*t.v[0]) - (temp.v[0]*t.v[2]); + v[2] = (temp.v[0]*t.v[1]) - (temp.v[1]*t.v[0]); + } + void Perp(); + + + //////////////////////////////////////////////////////////////////////////////////// + // Truncation & Element Analysis + //////////////////////////////////////////////////////////////////////////////////// + void Min(const CVec3& t) + { + if (t.v[0]v[0]) v[0]=t.v[0]; + if (t.v[1]>v[1]) v[1]=t.v[1]; + if (t.v[2]>v[2]) v[2]=t.v[2]; + } + float MaxElement() const + { + return v[MaxElementIndex()]; + } + int MaxElementIndex() const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Interpolation + //////////////////////////////////////////////////////////////////////////////////// + void Interp(const CVec3 &v1, const CVec3 &v2, const float t) + { + (*this)=v1; + (*this)-=v2; + (*this)*=t; + (*this)+=v2; + } + void ScaleAdd(const CVec3& t, const float scale) + { + v[0] += (scale * t.v[0]); + v[1] += (scale * t.v[1]); + v[2] += (scale * t.v[2]); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Angle To Vector (Angle In Degrees) + //////////////////////////////////////////////////////////////////////////////////// + void VecToAng(); + void AngToVec(); + void AngToVec(CVec3& Right, CVec3& Up); + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Angle To Vector (Angle In Radians) + //////////////////////////////////////////////////////////////////////////////////// + void VecToAngRad(); + void AngToVecRad(); + void AngToVecRad(CVec3& Right, CVec3& Up); + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion Between Radians And Degrees + //////////////////////////////////////////////////////////////////////////////////// + void ToRadians(); + void ToDegrees(); + + + + //////////////////////////////////////////////////////////////////////////////////// + // Project + // + // Standard projection function. Take the (this) and project it onto the vector + // (U). Imagine drawing a line perpendicular to U from the endpoint of the (this) + // Vector. That then becomes the new vector. + // + // The value returned is the scale of the new vector with respect to the one passed + // to the function. If the scale is less than (1.0) then the new vector is shorter + // than (U). If the scale is negative, then the vector is going in the opposite + // direction of (U). + // + // _ (U) + // /| + // / _ (this) + // / RESULTS-> /| + // / / + // / __\ (this) / + // /___--- / / + // + //////////////////////////////////////////////////////////////////////////////////// + float Project(const CVec3 &U) + { + float Scale = (Dot(U) / U.Len2()); // Find the scale of this vector on U + (*this)=U; // Copy U onto this vector + (*this)*=Scale; // Use the previously calculated scale to get the right length. + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Project To Line + // + // This function takes two other points in space as the start and end of a line + // segment and projects the (this) point onto the line defined by (Start)->(Stop) + // + // RETURN VALUES: + // (-INF, 0.0) : (this) landed on the line before (Start) + // (0.0, 1.0) : (this) landed in the line segment between (Start) and (Stop) + // (1.0, INF) : (this) landed on the line beyond (End) + // + // (Stop) + // / + // / + // o _ + // / |\ + // / (this) + // / + // (Start) + // + //////////////////////////////////////////////////////////////////////////////////// + float ProjectToLine(const CVec3 &Start, const CVec3 &Stop) + { + (*this) -= Start; + float Scale = Project(Stop - Start); + (*this) += Start; + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Project To Line Seg + // + // Same As Project To Line, Except It Will Clamp To Start And Stop + //////////////////////////////////////////////////////////////////////////////////// + float ProjectToLineSeg(const CVec3 &Start, const CVec3 &Stop) + { + float Scale = ProjectToLine(Start, Stop); + if (Scale<0.0f) + { + (*this) = Start; + } + else if (Scale>1.0f) + { + (*this) = Stop; + } + return Scale; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance To Line + // + // Uses project to line and than calculates distance to the new point + //////////////////////////////////////////////////////////////////////////////////// + float DistToLine(const CVec3 &Start, const CVec3 &Stop) const + { + CVec3 P(*this); + P.ProjectToLineSeg(Start, Stop); + + return Dist(P); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Distance To Line + // + // Uses project to line and than calculates distance to the new point + //////////////////////////////////////////////////////////////////////////////////// + float DistToLine2(const CVec3 &Start, const CVec3 &Stop) const + { + CVec3 P(*this); + P.ProjectToLineSeg(Start, Stop); + + return Dist2(P); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Translation & Rotation (2D) + //////////////////////////////////////////////////////////////////////////////////// + void RotatePoint(const CVec3 &Angle, const CVec3 &Origin); + void Reposition(const CVec3 &Translation, float RotationDegrees=0.0); + + + + //////////////////////////////////////////////////////////////////////////////////// + // Area Of The Parallel Pipid (2D) + // + // Given two more points, this function calculates the area of the parallel pipid + // formed. + // + // Note: This function CAN return a negative "area" if (this) is above or right of + // (A) and (B)... We do not take the abs because the sign of the "area" is needed + // for the left right test (see below) + // + // + // ___---( ... ) + // (A)---/ / + // / / + // / / + // / / + // / ___---(B) + // (this)---/ + // + //////////////////////////////////////////////////////////////////////////////////// + float AreaParallelPipid(const CVec3 &A, const CVec3 &B) const + { + return ((A.v[0]*B.v[1] - A.v[1]*B.v[0]) + + (B.v[0]* v[1] - v[0]*B.v[1]) + + ( v[0]*A.v[1] - A.v[0]* v[1])); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Area Of The Triangle (2D) + // + // Given two more points, this function calculates the area of the triangle formed. + // + // (A) + // / \__ + // / \__ + // / \_ + // / ___---(B) + // (this)---/ + // + //////////////////////////////////////////////////////////////////////////////////// + float AreaTriange(const CVec3 &A, const CVec3 &B) const + { + return (AreaParallelPipid(A, B) * 0.5f); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // The Left Right Test (2D) + // + // Given a line segment (Start->End) and a tolerance for *right on*, this function + // evaluates which side the point is of the line. (Side_Left in this example) + // + // + // + // (this) ___---/(End) + // ___---/ + // ___---/ + // (Start)/ + // + //////////////////////////////////////////////////////////////////////////////////// + ESide LRTest(const CVec3 &Start, const CVec3 &End, float Tolerance=0.0) const + { + float Area = AreaParallelPipid(Start, End); + if (Area>Tolerance) + { + return Side_Left; + } + if (Area<(Tolerance*-1)) + { + return Side_Right; + } + return Side_None; + + } + + //////////////////////////////////////////////////////////////////////////////////// + // Point In Circumscribed Circle (True/False) + // + // Returns true if the given point is within the circumscribed + // circle of the given ABC Triangle: + // _____ + // / B \ + // / / \ \ + // | / \ | + // |A---------C| + // \ Pt / + // \_______/ + // + //////////////////////////////////////////////////////////////////////////////////// + bool PtInCircle(const CVec3 &A, const CVec3 &B, const CVec3 &C) const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Point In Standard Circle (True/False) + // + // Returns true if the given point is within the Circle + // _____ + // / \ + // / \ + // | Circle | + // | | + // \ Pt / + // \_______/ + // + //////////////////////////////////////////////////////////////////////////////////// + bool PtInCircle(const CVec3 &Circle, float Radius) const; + + //////////////////////////////////////////////////////////////////////////////////// + // Line Intersects Circle (True/False) + // + // r - Radius Of The Circle + // A - Start Of Line Segment + // B - End Of Line Segment + // + // P - Projected Position Of Origin Onto Line AB + // + // + // (Stop) + // / + // / + // (P) + // / \ \ + // / (this)-r->| + // / / + // (Start) + // + //////////////////////////////////////////////////////////////////////////////////// + bool LineInCircle(const CVec3 &Start, const CVec3 &Stop, float Radius); + bool LineInCircle(const CVec3 &Start, const CVec3 &Stop, float Radius, CVec3 &PointOnLine); + + + + //////////////////////////////////////////////////////////////////////////////////// + // String Operations + //////////////////////////////////////////////////////////////////////////////////// + void FromStr(const char *s); + void ToStr(char* s) const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Debug Routines + //////////////////////////////////////////////////////////////////////////////////// +#ifdef _DEBUG + bool IsFinite(); + bool IsInitialized(); +#endif + + + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + +public: + float v[3]; + static const CVec3 mX; + static const CVec3 mY; + static const CVec3 mZ; + static const CVec3 mZero; +}; + + + +//}; +#endif \ No newline at end of file diff --git a/code/renderer/amd3d.h b/code/renderer/amd3d.h new file mode 100644 index 0000000..9763a26 --- /dev/null +++ b/code/renderer/amd3d.h @@ -0,0 +1,471 @@ +/****************************************************************** +; * +; * Copyright (c) 1996-1998 ADVANCED MICRO DEVICES, INC. +; * All Rights reserved. +; * +; * This software is unpublished and contains the trade secrets +; * and confidential proprietary information of AMD. Unless +; * otherwise provided in the Software Agreement associated +; * herewith, it is licensed in confidence "AS IS" and +; * is not to be reproduced in whole or part by any means except +; * for backup. Use, duplication, or disclosure by the Government +; * is subject to the restrictions in paragraph(b)(3)(B)of the +; * Rights in Technical Data and Computer Software clause in +; * DFAR 52.227-7013(a)(Oct 1988). Software owned by Advanced +; * Micro Devices Inc., One AMD Place, P.O. Box 3453, Sunnyvale, +; * CA 94088-3453. +; * +; ****************************************************************** + * + * AMD3D.H + * + * MACRO FORMAT + * ============ + * This file contains inline assembly macros that + * generate AMD-3D instructions in binary format. + * Therefore, C or C++ programmer can use AMD-3D instructions + * without any penalty in their C or C++ source code. + * + * The macro's name and format conventions are as follow: + * + * + * 1. First argument of macro is a destination and + * second argument is a source operand. + * ex) _asm PFCMPEQ (m3, m4) + * | | + * dst src + * + * 2. The destination operand can be m0 to m7 only. + * The source operand can be any one of the register + * m0 to m7 or _eax, _ecx, _edx, _ebx, _esi, or _edi + * that contains effective address. + * ex) _asm PFRCP (M7, M6) + * ex) _asm PFRCPIT2 (m0, m4) + * ex) _asm PFMUL (m3, _edi) + * + * 3. The prefetch(w) takes one src operand _eax, ecx, _edx, + * _ebx, _esi, or _edi that contains effective address. + * ex) _asm PREFETCH (_edi) + * + * EXAMPLE + * ======= + * Following program doesn't do anything but it shows you + * how to use inline assembly AMD-3D instructions in C. + * Note that this will only work in flat memory model which + * segment registers cs, ds, ss and es point to the same + * linear address space total less than 4GB. + * + * Used Microsoft VC++ 5.0 + * + * #include + * #include "amd3d.h" + * + * void main () + * { + * float x = (float)1.25; + * float y = (float)1.25; + * float z, zz; + * + * _asm { + * movd mm1, x + * movd mm2, y + * pfmul (m1, m2) + * movd z, mm1 + * femms + * } + * + * printf ("value of z = %f\n", z); + * + * // + * // Demonstration of using the memory instead of + * // multimedia register + * // + * _asm { + * movd mm3, x + * lea esi, y // load effective address of y + * pfmul (m3, _esi) + * movd zz, mm3 + * femms + * } + * + * printf ("value of zz = %f\n", zz); + * } + ******************************************************************/ + +#define M0 0xc0 +#define M1 0xc1 +#define M2 0xc2 +#define M3 0xc3 +#define M4 0xc4 +#define M5 0xc5 +#define M6 0xc6 +#define M7 0xc7 + +#define m0 0xc0 +#define m1 0xc1 +#define m2 0xc2 +#define m3 0xc3 +#define m4 0xc4 +#define m5 0xc5 +#define m6 0xc6 +#define m7 0xc7 +#define _EAX 0x00 +#define _ECX 0x01 +#define _EDX 0x02 +#define _EBX 0x03 +#define _ESI 0x06 +#define _EDI 0x07 +#define _eax 0x00 +#define _ecx 0x01 +#define _edx 0x02 +#define _ebx 0x03 +#define _esi 0x06 +#define _edi 0x07 + +#define PF2ID(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x1d \ +} + +#define PFACC(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xae \ +} + +#define PFADD(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x9e \ +} + +#define PFCMPEQ(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xb0 \ +} + +#define PFCMPGE(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x90 \ +} + +#define PFCMPGT(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xa0 \ +} + +#define PFMAX(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xa4 \ +} + +#define PFMIN(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x94 \ +} + +#define PFMUL(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xb4 \ +} + +#define PFRCP(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x96 \ +} + +#define PFRCPIT1(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xa6 \ +} + +#define PFRCPIT2(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xb6 \ +} + +#define PFRSQRT(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x97 \ +} + +#define PFRSQIT1(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xa7 \ +} + +#define PFSUB(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x9a \ +} + +#define PFSUBR(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xaa \ +} + +#define PI2FD(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x0d \ +} + +#define FEMMS \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0e \ +} + +#define PAVGUSB(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xbf \ +} + +#define PMULHRW(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xb7 \ +} + +#define PREFETCH(src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0d \ + _asm _emit 0x00 | src \ +} + +#define PREFETCHW(src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0d \ + _asm _emit 0x08 | src \ +} + +// +// Exactly same as above except macro names are all +// lower case latter. +// +#define pf2id(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x1d \ +} + +#define pfacc(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xae \ +} + +#define pfadd(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x9e \ +} + +#define pfcmpeq(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xb0 \ +} + +#define pfcmpge(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x90 \ +} + +#define pfcmpgt(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xa0 \ +} + +#define pfmax(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xa4 \ +} + +#define pfmin(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x94 \ +} + +#define pfmul(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xb4 \ +} + +#define pfrcp(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x96 \ +} + +#define pfrcpit1(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xa6 \ +} + +#define pfrcpit2(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xb6 \ +} + +#define pfrsqrt(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x97 \ +} + +#define pfrsqit1(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xa7 \ +} + +#define pfsub(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x9a \ +} + +#define pfsubr(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xaa \ +} + +#define pi2fd(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0x0d \ +} + +#define femms \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0e \ +} + +#define pavgusb(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xbf \ +} + +#define pmulhrw(dst, src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0f \ + _asm _emit ((dst & 0x3f) << 3) | src \ + _asm _emit 0xb7 \ +} + +#define prefetch(src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0d \ + _asm _emit 0x00 | src \ +} + +#define prefetchw(src) \ +{\ + _asm _emit 0x0f \ + _asm _emit 0x0d \ + _asm _emit 0x08 | src \ +} diff --git a/code/renderer/glext.h b/code/renderer/glext.h new file mode 100644 index 0000000..fb01726 --- /dev/null +++ b/code/renderer/glext.h @@ -0,0 +1,2920 @@ +#ifndef __glext_h_ +#define __glext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif + +/*************************************************************/ + +/* Header file version number, required by OpenGL ABI for Linux */ +#define GL_GLEXT_VERSION 6 + +#ifndef GL_VERSION_1_2 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#endif + +#ifndef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +#endif + +#ifndef GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif + +#ifndef GL_ARB_texture_compression +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef GL_EXT_blend_color +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef GL_EXT_texture +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef GL_EXT_histogram +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef GL_EXT_convolution +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +#endif + +#ifndef GL_SGI_color_matrix +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif + +#ifndef GL_SGI_color_table +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef GL_EXT_cmyka +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif + +#ifndef GL_EXT_texture_object +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif + +#ifndef GL_SGIS_multisample +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef GL_SGIX_shadow +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef GL_EXT_light_texture +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif + +#ifndef GL_EXT_secondary_color +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef GL_EXT_fog_coord +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE3_RGB_EXT 0x8583 +#define GL_SOURCE4_RGB_EXT 0x8584 +#define GL_SOURCE5_RGB_EXT 0x8585 +#define GL_SOURCE6_RGB_EXT 0x8586 +#define GL_SOURCE7_RGB_EXT 0x8587 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_SOURCE3_ALPHA_EXT 0x858B +#define GL_SOURCE4_ALPHA_EXT 0x858C +#define GL_SOURCE5_ALPHA_EXT 0x858D +#define GL_SOURCE6_ALPHA_EXT 0x858E +#define GL_SOURCE7_ALPHA_EXT 0x858F +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND3_RGB_EXT 0x8593 +#define GL_OPERAND4_RGB_EXT 0x8594 +#define GL_OPERAND5_RGB_EXT 0x8595 +#define GL_OPERAND6_RGB_EXT 0x8596 +#define GL_OPERAND7_RGB_EXT 0x8597 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#define GL_OPERAND3_ALPHA_EXT 0x859B +#define GL_OPERAND4_ALPHA_EXT 0x859C +#define GL_OPERAND5_ALPHA_EXT 0x859D +#define GL_OPERAND6_ALPHA_EXT 0x859E +#define GL_OPERAND7_ALPHA_EXT 0x859F +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef GL_SUN_triangle_list +#define GL_RESTART_SUN 0x01 +#define GL_REPLACE_MIDDLE_SUN 0x02 +#define GL_REPLACE_OLDEST_SUN 0x03 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +#endif + +#ifndef GL_INGR_color_clamp +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef GL_EXT_texture_cube_map +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW_MATRIX1_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +#endif + +#ifndef GL_NV_register_combiners +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef GL_EXT_multisample +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + +/* NV_point_sprite */ +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +#define GL_NV_point_sprite 1 + +/*************************************************************/ + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glBlendColor (GLclampf, GLclampf, GLclampf, GLclampf); +extern void APIENTRY glBlendEquation (GLenum); +extern void APIENTRY glDrawRangeElements (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +extern void APIENTRY glColorTable (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glColorTableParameterfv (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glColorTableParameteriv (GLenum, GLenum, const GLint *); +extern void APIENTRY glCopyColorTable (GLenum, GLenum, GLint, GLint, GLsizei); +extern void APIENTRY glGetColorTable (GLenum, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetColorTableParameterfv (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetColorTableParameteriv (GLenum, GLenum, GLint *); +extern void APIENTRY glColorSubTable (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glCopyColorSubTable (GLenum, GLsizei, GLint, GLint, GLsizei); +extern void APIENTRY glConvolutionFilter1D (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glConvolutionFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glConvolutionParameterf (GLenum, GLenum, GLfloat); +extern void APIENTRY glConvolutionParameterfv (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glConvolutionParameteri (GLenum, GLenum, GLint); +extern void APIENTRY glConvolutionParameteriv (GLenum, GLenum, const GLint *); +extern void APIENTRY glCopyConvolutionFilter1D (GLenum, GLenum, GLint, GLint, GLsizei); +extern void APIENTRY glCopyConvolutionFilter2D (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +extern void APIENTRY glGetConvolutionFilter (GLenum, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetConvolutionParameterfv (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetConvolutionParameteriv (GLenum, GLenum, GLint *); +extern void APIENTRY glGetSeparableFilter (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +extern void APIENTRY glSeparableFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +extern void APIENTRY glGetHistogram (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetHistogramParameterfv (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetHistogramParameteriv (GLenum, GLenum, GLint *); +extern void APIENTRY glGetMinmax (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetMinmaxParameterfv (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetMinmaxParameteriv (GLenum, GLenum, GLint *); +extern void APIENTRY glHistogram (GLenum, GLsizei, GLenum, GLboolean); +extern void APIENTRY glMinmax (GLenum, GLenum, GLboolean); +extern void APIENTRY glResetHistogram (GLenum); +extern void APIENTRY glResetMinmax (GLenum); +extern void APIENTRY glTexImage3D (GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glCopyTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLBLENDCOLORPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +typedef void (APIENTRY * PFNGLBLENDEQUATIONPROC) (GLenum mode); +typedef void (APIENTRY * PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (APIENTRY * PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRY * PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRY * PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRY * PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +typedef void (APIENTRY * PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRY * PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRY * PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRY * PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRY * PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRY * PFNGLRESETMINMAXPROC) (GLenum target); +typedef void (APIENTRY * PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRY * PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRY * PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glActiveTextureARB (GLenum); +extern void APIENTRY glClientActiveTextureARB (GLenum); +extern void APIENTRY glMultiTexCoord1dARB (GLenum, GLdouble); +extern void APIENTRY glMultiTexCoord1dvARB (GLenum, const GLdouble *); +extern void APIENTRY glMultiTexCoord1fARB (GLenum, GLfloat); +extern void APIENTRY glMultiTexCoord1fvARB (GLenum, const GLfloat *); +extern void APIENTRY glMultiTexCoord1iARB (GLenum, GLint); +extern void APIENTRY glMultiTexCoord1ivARB (GLenum, const GLint *); +extern void APIENTRY glMultiTexCoord1sARB (GLenum, GLshort); +extern void APIENTRY glMultiTexCoord1svARB (GLenum, const GLshort *); +extern void APIENTRY glMultiTexCoord2dARB (GLenum, GLdouble, GLdouble); +extern void APIENTRY glMultiTexCoord2dvARB (GLenum, const GLdouble *); +extern void APIENTRY glMultiTexCoord2fARB (GLenum, GLfloat, GLfloat); +extern void APIENTRY glMultiTexCoord2fvARB (GLenum, const GLfloat *); +extern void APIENTRY glMultiTexCoord2iARB (GLenum, GLint, GLint); +extern void APIENTRY glMultiTexCoord2ivARB (GLenum, const GLint *); +extern void APIENTRY glMultiTexCoord2sARB (GLenum, GLshort, GLshort); +extern void APIENTRY glMultiTexCoord2svARB (GLenum, const GLshort *); +extern void APIENTRY glMultiTexCoord3dARB (GLenum, GLdouble, GLdouble, GLdouble); +extern void APIENTRY glMultiTexCoord3dvARB (GLenum, const GLdouble *); +extern void APIENTRY glMultiTexCoord3fARB (GLenum, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glMultiTexCoord3fvARB (GLenum, const GLfloat *); +extern void APIENTRY glMultiTexCoord3iARB (GLenum, GLint, GLint, GLint); +extern void APIENTRY glMultiTexCoord3ivARB (GLenum, const GLint *); +extern void APIENTRY glMultiTexCoord3sARB (GLenum, GLshort, GLshort, GLshort); +extern void APIENTRY glMultiTexCoord3svARB (GLenum, const GLshort *); +extern void APIENTRY glMultiTexCoord4dARB (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +extern void APIENTRY glMultiTexCoord4dvARB (GLenum, const GLdouble *); +extern void APIENTRY glMultiTexCoord4fARB (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glMultiTexCoord4fvARB (GLenum, const GLfloat *); +extern void APIENTRY glMultiTexCoord4iARB (GLenum, GLint, GLint, GLint, GLint); +extern void APIENTRY glMultiTexCoord4ivARB (GLenum, const GLint *); +extern void APIENTRY glMultiTexCoord4sARB (GLenum, GLshort, GLshort, GLshort, GLshort); +extern void APIENTRY glMultiTexCoord4svARB (GLenum, const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *); +extern void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *); +extern void APIENTRY glMultTransposeMatrixfARB (const GLfloat *); +extern void APIENTRY glMultTransposeMatrixdARB (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRY * PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRY * PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRY * PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSampleCoverageARB (GLclampf, GLboolean); +extern void APIENTRY glSamplePassARB (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSAMPLECOVERAGEARBPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRY * PFNGLSAMPLEPASSARBPROC) (GLenum pass); +#endif + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glCompressedTexImage3DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +extern void APIENTRY glCompressedTexImage2DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +extern void APIENTRY glCompressedTexImage1DARB (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +extern void APIENTRY glCompressedTexSubImage3DARB (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void APIENTRY glCompressedTexSubImage2DARB (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void APIENTRY glCompressedTexSubImage1DARB (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void APIENTRY glGetCompressedTexImageARB (GLenum, GLint, void *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRY * PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, void *img); +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glBlendColorEXT (GLclampf, GLclampf, GLclampf, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLBLENDCOLOREXTPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glPolygonOffsetEXT (GLfloat, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTexImage3DEXT (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRY * PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glGetTexFilterFuncSGIS (GLenum, GLenum, GLfloat *); +extern void APIENTRY glTexFilterFuncSGIS (GLenum, GLenum, GLsizei, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRY * PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTexSubImage1DEXT (GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRY * PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glCopyTexImage1DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLint); +extern void APIENTRY glCopyTexImage2DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint); +extern void APIENTRY glCopyTexSubImage1DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei); +extern void APIENTRY glCopyTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +extern void APIENTRY glCopyTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRY * PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRY * PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRY * PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glGetHistogramEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetHistogramParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetHistogramParameterivEXT (GLenum, GLenum, GLint *); +extern void APIENTRY glGetMinmaxEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetMinmaxParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetMinmaxParameterivEXT (GLenum, GLenum, GLint *); +extern void APIENTRY glHistogramEXT (GLenum, GLsizei, GLenum, GLboolean); +extern void APIENTRY glMinmaxEXT (GLenum, GLenum, GLboolean); +extern void APIENTRY glResetHistogramEXT (GLenum); +extern void APIENTRY glResetMinmaxEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRY * PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRY * PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRY * PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRY * PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRY * PFNGLRESETMINMAXEXTPROC) (GLenum target); +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glConvolutionFilter1DEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glConvolutionFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glConvolutionParameterfEXT (GLenum, GLenum, GLfloat); +extern void APIENTRY glConvolutionParameterfvEXT (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glConvolutionParameteriEXT (GLenum, GLenum, GLint); +extern void APIENTRY glConvolutionParameterivEXT (GLenum, GLenum, const GLint *); +extern void APIENTRY glCopyConvolutionFilter1DEXT (GLenum, GLenum, GLint, GLint, GLsizei); +extern void APIENTRY glCopyConvolutionFilter2DEXT (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +extern void APIENTRY glGetConvolutionFilterEXT (GLenum, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetConvolutionParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetConvolutionParameterivEXT (GLenum, GLenum, GLint *); +extern void APIENTRY glGetSeparableFilterEXT (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +extern void APIENTRY glSeparableFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRY * PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRY * PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRY * PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +#endif + +#ifndef GL_EXT_color_matrix +#define GL_EXT_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glColorTableSGI (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glColorTableParameterfvSGI (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glColorTableParameterivSGI (GLenum, GLenum, const GLint *); +extern void APIENTRY glCopyColorTableSGI (GLenum, GLenum, GLint, GLint, GLsizei); +extern void APIENTRY glGetColorTableSGI (GLenum, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetColorTableParameterfvSGI (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetColorTableParameterivSGI (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRY * PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRY * PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glPixelTexGenSGIX (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glPixelTexGenParameteriSGIS (GLenum, GLint); +extern void APIENTRY glPixelTexGenParameterivSGIS (GLenum, const GLint *); +extern void APIENTRY glPixelTexGenParameterfSGIS (GLenum, GLfloat); +extern void APIENTRY glPixelTexGenParameterfvSGIS (GLenum, const GLfloat *); +extern void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum, GLint *); +extern void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTexImage4DSGIS (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glTexSubImage4DSGIS (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRY * PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#ifdef GL_GLEXT_PROTOTYPES +extern GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei, const GLuint *, GLboolean *); +extern void APIENTRY glBindTextureEXT (GLenum, GLuint); +extern void APIENTRY glDeleteTexturesEXT (GLsizei, const GLuint *); +extern void APIENTRY glGenTexturesEXT (GLsizei, GLuint *); +extern GLboolean APIENTRY glIsTextureEXT (GLuint); +extern void APIENTRY glPrioritizeTexturesEXT (GLsizei, const GLuint *, const GLclampf *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRY * PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRY * PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRY * PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRY * PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRY * PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRY * PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glDetailTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +extern void APIENTRY glGetDetailTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRY * PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSharpenTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +extern void APIENTRY glGetSharpenTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRY * PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSampleMaskSGIS (GLclampf, GLboolean); +extern void APIENTRY glSamplePatternSGIS (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRY * PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glArrayElementEXT (GLint); +extern void APIENTRY glColorPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +extern void APIENTRY glDrawArraysEXT (GLenum, GLint, GLsizei); +extern void APIENTRY glEdgeFlagPointerEXT (GLsizei, GLsizei, const GLboolean *); +extern void APIENTRY glGetPointervEXT (GLenum, GLvoid* *); +extern void APIENTRY glIndexPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +extern void APIENTRY glNormalPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +extern void APIENTRY glTexCoordPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +extern void APIENTRY glVertexPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRY * PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRY * PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRY * PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRY * PFNGLGETPOINTERVEXTPROC) (GLenum pname, GLvoid* *params); +typedef void (APIENTRY * PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRY * PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRY * PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRY * PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glBlendEquationEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSpriteParameterfSGIX (GLenum, GLfloat); +extern void APIENTRY glSpriteParameterfvSGIX (GLenum, const GLfloat *); +extern void APIENTRY glSpriteParameteriSGIX (GLenum, GLint); +extern void APIENTRY glSpriteParameterivSGIX (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glPointParameterfEXT (GLenum, GLfloat); +extern void APIENTRY glPointParameterfvEXT (GLenum, const GLfloat *); +extern void APIENTRY glPointParameterfSGIS (GLenum, GLfloat); +extern void APIENTRY glPointParameterfvSGIS (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#ifdef GL_GLEXT_PROTOTYPES +extern GLint APIENTRY glGetInstrumentsSGIX (void); +extern void APIENTRY glInstrumentsBufferSGIX (GLsizei, GLint *); +extern GLint APIENTRY glPollInstrumentsSGIX (GLint *); +extern void APIENTRY glReadInstrumentsSGIX (GLint); +extern void APIENTRY glStartInstrumentsSGIX (void); +extern void APIENTRY glStopInstrumentsSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLint (APIENTRY * PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRY * PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRY * PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRY * PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRY * PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRY * PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFrameZoomSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTagSampleBufferSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glReferencePlaneSGIX (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFlushRasterSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFLUSHRASTERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFogFuncSGIS (GLsizei, const GLfloat *); +extern void APIENTRY glGetFogFuncSGIS (const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRY * PFNGLGETFOGFUNCSGISPROC) (const GLfloat *points); +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glImageTransformParameteriHP (GLenum, GLenum, GLint); +extern void APIENTRY glImageTransformParameterfHP (GLenum, GLenum, GLfloat); +extern void APIENTRY glImageTransformParameterivHP (GLenum, GLenum, const GLint *); +extern void APIENTRY glImageTransformParameterfvHP (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glGetImageTransformParameterivHP (GLenum, GLenum, GLint *); +extern void APIENTRY glGetImageTransformParameterfvHP (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glColorSubTableEXT (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glCopyColorSubTableEXT (GLenum, GLsizei, GLint, GLint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRY * PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glHintPGI (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glColorTableEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void APIENTRY glGetColorTableEXT (GLenum, GLenum, GLenum, GLvoid *); +extern void APIENTRY glGetColorTableParameterivEXT (GLenum, GLenum, GLint *); +extern void APIENTRY glGetColorTableParameterfvEXT (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRY * PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *data); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glGetListParameterfvSGIX (GLuint, GLenum, GLfloat *); +extern void APIENTRY glGetListParameterivSGIX (GLuint, GLenum, GLint *); +extern void APIENTRY glListParameterfSGIX (GLuint, GLenum, GLfloat); +extern void APIENTRY glListParameterfvSGIX (GLuint, GLenum, const GLfloat *); +extern void APIENTRY glListParameteriSGIX (GLuint, GLenum, GLint); +extern void APIENTRY glListParameterivSGIX (GLuint, GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glIndexMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glIndexFuncEXT (GLenum, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glLockArraysEXT (GLint, GLsizei); +extern void APIENTRY glUnlockArraysEXT (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRY * PFNGLUNLOCKARRAYSEXTPROC) (void); +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glCullParameterdvEXT (GLenum, GLdouble *); +extern void APIENTRY glCullParameterfvEXT (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRY * PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFragmentColorMaterialSGIX (GLenum, GLenum); +extern void APIENTRY glFragmentLightfSGIX (GLenum, GLenum, GLfloat); +extern void APIENTRY glFragmentLightfvSGIX (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glFragmentLightiSGIX (GLenum, GLenum, GLint); +extern void APIENTRY glFragmentLightivSGIX (GLenum, GLenum, const GLint *); +extern void APIENTRY glFragmentLightModelfSGIX (GLenum, GLfloat); +extern void APIENTRY glFragmentLightModelfvSGIX (GLenum, const GLfloat *); +extern void APIENTRY glFragmentLightModeliSGIX (GLenum, GLint); +extern void APIENTRY glFragmentLightModelivSGIX (GLenum, const GLint *); +extern void APIENTRY glFragmentMaterialfSGIX (GLenum, GLenum, GLfloat); +extern void APIENTRY glFragmentMaterialfvSGIX (GLenum, GLenum, const GLfloat *); +extern void APIENTRY glFragmentMaterialiSGIX (GLenum, GLenum, GLint); +extern void APIENTRY glFragmentMaterialivSGIX (GLenum, GLenum, const GLint *); +extern void APIENTRY glGetFragmentLightfvSGIX (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetFragmentLightivSGIX (GLenum, GLenum, GLint *); +extern void APIENTRY glGetFragmentMaterialfvSGIX (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetFragmentMaterialivSGIX (GLenum, GLenum, GLint *); +extern void APIENTRY glLightEnviSGIX (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glDrawRangeElementsEXT (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glApplyTextureEXT (GLenum); +extern void APIENTRY glTextureLightEXT (GLenum); +extern void APIENTRY glTextureMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRY * PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRY * PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glVertexPointervINTEL (GLint, GLenum, const GLvoid* *); +extern void APIENTRY glNormalPointervINTEL (GLenum, const GLvoid* *); +extern void APIENTRY glColorPointervINTEL (GLint, GLenum, const GLvoid* *); +extern void APIENTRY glTexCoordPointervINTEL (GLint, GLenum, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRY * PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const GLvoid* *pointer); +typedef void (APIENTRY * PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRY * PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glPixelTransformParameteriEXT (GLenum, GLenum, GLint); +extern void APIENTRY glPixelTransformParameterfEXT (GLenum, GLenum, GLfloat); +extern void APIENTRY glPixelTransformParameterivEXT (GLenum, GLenum, const GLint *); +extern void APIENTRY glPixelTransformParameterfvEXT (GLenum, GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSecondaryColor3bEXT (GLbyte, GLbyte, GLbyte); +extern void APIENTRY glSecondaryColor3bvEXT (const GLbyte *); +extern void APIENTRY glSecondaryColor3dEXT (GLdouble, GLdouble, GLdouble); +extern void APIENTRY glSecondaryColor3dvEXT (const GLdouble *); +extern void APIENTRY glSecondaryColor3fEXT (GLfloat, GLfloat, GLfloat); +extern void APIENTRY glSecondaryColor3fvEXT (const GLfloat *); +extern void APIENTRY glSecondaryColor3iEXT (GLint, GLint, GLint); +extern void APIENTRY glSecondaryColor3ivEXT (const GLint *); +extern void APIENTRY glSecondaryColor3sEXT (GLshort, GLshort, GLshort); +extern void APIENTRY glSecondaryColor3svEXT (const GLshort *); +extern void APIENTRY glSecondaryColor3ubEXT (GLubyte, GLubyte, GLubyte); +extern void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *); +extern void APIENTRY glSecondaryColor3uiEXT (GLuint, GLuint, GLuint); +extern void APIENTRY glSecondaryColor3uivEXT (const GLuint *); +extern void APIENTRY glSecondaryColor3usEXT (GLushort, GLushort, GLushort); +extern void APIENTRY glSecondaryColor3usvEXT (const GLushort *); +extern void APIENTRY glSecondaryColorPointerEXT (GLint, GLenum, GLsizei, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRY * PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRY * PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTextureNormalEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glMultiDrawArraysEXT (GLenum, GLint *, GLsizei *, GLsizei); +extern void APIENTRY glMultiDrawElementsEXT (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (APIENTRY * PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFogCoordfEXT (GLfloat); +extern void APIENTRY glFogCoordfvEXT (const GLfloat *); +extern void APIENTRY glFogCoorddEXT (GLdouble); +extern void APIENTRY glFogCoorddvEXT (const GLdouble *); +extern void APIENTRY glFogCoordPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRY * PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRY * PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRY * PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRY * PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTangent3bEXT (GLbyte, GLbyte, GLbyte); +extern void APIENTRY glTangent3bvEXT (const GLbyte *); +extern void APIENTRY glTangent3dEXT (GLdouble, GLdouble, GLdouble); +extern void APIENTRY glTangent3dvEXT (const GLdouble *); +extern void APIENTRY glTangent3fEXT (GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTangent3fvEXT (const GLfloat *); +extern void APIENTRY glTangent3iEXT (GLint, GLint, GLint); +extern void APIENTRY glTangent3ivEXT (const GLint *); +extern void APIENTRY glTangent3sEXT (GLshort, GLshort, GLshort); +extern void APIENTRY glTangent3svEXT (const GLshort *); +extern void APIENTRY glBinormal3bEXT (GLbyte, GLbyte, GLbyte); +extern void APIENTRY glBinormal3bvEXT (const GLbyte *); +extern void APIENTRY glBinormal3dEXT (GLdouble, GLdouble, GLdouble); +extern void APIENTRY glBinormal3dvEXT (const GLdouble *); +extern void APIENTRY glBinormal3fEXT (GLfloat, GLfloat, GLfloat); +extern void APIENTRY glBinormal3fvEXT (const GLfloat *); +extern void APIENTRY glBinormal3iEXT (GLint, GLint, GLint); +extern void APIENTRY glBinormal3ivEXT (const GLint *); +extern void APIENTRY glBinormal3sEXT (GLshort, GLshort, GLshort); +extern void APIENTRY glBinormal3svEXT (const GLshort *); +extern void APIENTRY glTangentPointerEXT (GLenum, GLsizei, const GLvoid *); +extern void APIENTRY glBinormalPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRY * PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRY * PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRY * PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRY * PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRY * PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRY * PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRY * PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRY * PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRY * PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRY * PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRY * PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRY * PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRY * PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRY * PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRY * PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFinishTextureSUNX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFINISHTEXTURESUNXPROC) (void); +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glGlobalAlphaFactorbSUN (GLbyte); +extern void APIENTRY glGlobalAlphaFactorsSUN (GLshort); +extern void APIENTRY glGlobalAlphaFactoriSUN (GLint); +extern void APIENTRY glGlobalAlphaFactorfSUN (GLfloat); +extern void APIENTRY glGlobalAlphaFactordSUN (GLdouble); +extern void APIENTRY glGlobalAlphaFactorubSUN (GLubyte); +extern void APIENTRY glGlobalAlphaFactorusSUN (GLushort); +extern void APIENTRY glGlobalAlphaFactoruiSUN (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRY * PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glReplacementCodeuiSUN (GLuint); +extern void APIENTRY glReplacementCodeusSUN (GLushort); +extern void APIENTRY glReplacementCodeubSUN (GLubyte); +extern void APIENTRY glReplacementCodeuivSUN (const GLuint *); +extern void APIENTRY glReplacementCodeusvSUN (const GLushort *); +extern void APIENTRY glReplacementCodeubvSUN (const GLubyte *); +extern void APIENTRY glReplacementCodePointerSUN (GLenum, GLsizei, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const GLvoid* *pointer); +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glColor4ubVertex2fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat); +extern void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *, const GLfloat *); +extern void APIENTRY glColor4ubVertex3fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *, const GLfloat *); +extern void APIENTRY glColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glColor3fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void APIENTRY glNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord2fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord4fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat, GLfloat, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *, const GLubyte *, const GLfloat *); +extern void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLenum *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLenum, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLenum *, const GLubyte *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRY * PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRY * PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRY * PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLenum rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLenum rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLenum *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLenum rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLenum rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLenum rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLenum rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLenum rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLenum rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLenum *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glBlendFuncSeparateEXT (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glVertexWeightfEXT (GLfloat); +extern void APIENTRY glVertexWeightfvEXT (const GLfloat *); +extern void APIENTRY glVertexWeightPointerEXT (GLsizei, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRY * PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRY * PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLsizei size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glFlushVertexArrayRangeNV (void); +extern void APIENTRY glVertexArrayRangeNV (GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRY * PFNGLVERTEXARRAYRANGENVPROC) (GLsizei size, const GLvoid *pointer); +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glCombinerParameterfvNV (GLenum, const GLfloat *); +extern void APIENTRY glCombinerParameterfNV (GLenum, GLfloat); +extern void APIENTRY glCombinerParameterivNV (GLenum, const GLint *); +extern void APIENTRY glCombinerParameteriNV (GLenum, GLint); +extern void APIENTRY glCombinerInputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum); +extern void APIENTRY glCombinerOutputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean); +extern void APIENTRY glFinalCombinerInputNV (GLenum, GLenum, GLenum, GLenum); +extern void APIENTRY glGetCombinerInputParameterfvNV (GLenum, GLenum, GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetCombinerInputParameterivNV (GLenum, GLenum, GLenum, GLenum, GLint *); +extern void APIENTRY glGetCombinerOutputParameterfvNV (GLenum, GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetCombinerOutputParameterivNV (GLenum, GLenum, GLenum, GLint *); +extern void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum, GLenum, GLfloat *); +extern void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRY * PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRY * PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRY * PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRY * PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRY * PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRY * PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRY * PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRY * PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glResizeBuffersMESA (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLRESIZEBUFFERSMESAPROC) (void); +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glWindowPos2dMESA (GLdouble, GLdouble); +extern void APIENTRY glWindowPos2dvMESA (const GLdouble *); +extern void APIENTRY glWindowPos2fMESA (GLfloat, GLfloat); +extern void APIENTRY glWindowPos2fvMESA (const GLfloat *); +extern void APIENTRY glWindowPos2iMESA (GLint, GLint); +extern void APIENTRY glWindowPos2ivMESA (const GLint *); +extern void APIENTRY glWindowPos2sMESA (GLshort, GLshort); +extern void APIENTRY glWindowPos2svMESA (const GLshort *); +extern void APIENTRY glWindowPos3dMESA (GLdouble, GLdouble, GLdouble); +extern void APIENTRY glWindowPos3dvMESA (const GLdouble *); +extern void APIENTRY glWindowPos3fMESA (GLfloat, GLfloat, GLfloat); +extern void APIENTRY glWindowPos3fvMESA (const GLfloat *); +extern void APIENTRY glWindowPos3iMESA (GLint, GLint, GLint); +extern void APIENTRY glWindowPos3ivMESA (const GLint *); +extern void APIENTRY glWindowPos3sMESA (GLshort, GLshort, GLshort); +extern void APIENTRY glWindowPos3svMESA (const GLshort *); +extern void APIENTRY glWindowPos4dMESA (GLdouble, GLdouble, GLdouble, GLdouble); +extern void APIENTRY glWindowPos4dvMESA (const GLdouble *); +extern void APIENTRY glWindowPos4fMESA (GLfloat, GLfloat, GLfloat, GLfloat); +extern void APIENTRY glWindowPos4fvMESA (const GLfloat *); +extern void APIENTRY glWindowPos4iMESA (GLint, GLint, GLint, GLint); +extern void APIENTRY glWindowPos4ivMESA (const GLint *); +extern void APIENTRY glWindowPos4sMESA (GLshort, GLshort, GLshort, GLshort); +extern void APIENTRY glWindowPos4svMESA (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRY * PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRY * PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRY * PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRY * PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRY * PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRY * PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRY * PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRY * PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRY * PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRY * PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRY * PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRY * PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRY * PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRY * PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRY * PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRY * PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRY * PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glMultiModeDrawArraysIBM (GLenum, const GLint *, const GLsizei *, GLsizei, GLint); +extern void APIENTRY glMultiModeDrawElementsIBM (const GLenum *, const GLsizei *, GLenum, const GLvoid* *, GLsizei, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLMULTIMODEDRAWARRAYSIBMPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRY * PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount, GLint modestride); +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glSecondaryColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glEdgeFlagPointerListIBM (GLint, const GLboolean* *, GLint); +extern void APIENTRY glFogCoordPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glIndexPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glNormalPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glTexCoordPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void APIENTRY glVertexPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRY * PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTbufferMask3DFX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glSampleMaskEXT (GLclampf, GLboolean); +extern void APIENTRY glSamplePatternEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRY * PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#endif + +#ifndef GL_SGI_vertex_preclip +#define GL_SGI_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void APIENTRY glTextureColorMaskSGIS (GLboolean, GLboolean, GLboolean, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRY * PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/code/renderer/glext_console.h b/code/renderer/glext_console.h new file mode 100644 index 0000000..588dfbc --- /dev/null +++ b/code/renderer/glext_console.h @@ -0,0 +1,2521 @@ +#ifndef __glext_h_ +#define __glext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +/*************************************************************/ + +/* Header file version number, required by OpenGL ABI for Linux */ +#define GL_GLEXT_VERSION 6 + +#ifndef GL_VERSION_1_2 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_VSYNC 0x813F +#endif + +#ifndef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +#endif + +#ifndef GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif + +#ifndef GL_ARB_texture_compression +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef GL_EXT_blend_color +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef GL_EXT_texture +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#define GL_TPL4_EXT 0x9991 +#define GL_TPL8_EXT 0x9992 +#define GL_TPL16_EXT 0x9993 +#define GL_TPL32_EXT 0x9994 +#define GL_DDS1_EXT 0x9995 +#define GL_DDS5_EXT 0x9996 +#define GL_DDS_RGB16_EXT 0x9997 +#define GL_DDS_RGBA32_EXT 0x9998 +#define GL_RGB_SWIZZLE_EXT 0x9999 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef GL_EXT_histogram +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef GL_EXT_convolution +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +#endif + +#ifndef GL_SGI_color_matrix +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif + +#ifndef GL_SGI_color_table +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef GL_EXT_cmyka +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif + +#ifndef GL_EXT_texture_object +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif + +#ifndef GL_SGIS_multisample +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef GL_SGIX_shadow +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef GL_EXT_light_texture +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif + +#ifndef GL_EXT_secondary_color +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef GL_EXT_fog_coord +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE3_RGB_EXT 0x8583 +#define GL_SOURCE4_RGB_EXT 0x8584 +#define GL_SOURCE5_RGB_EXT 0x8585 +#define GL_SOURCE6_RGB_EXT 0x8586 +#define GL_SOURCE7_RGB_EXT 0x8587 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_SOURCE3_ALPHA_EXT 0x858B +#define GL_SOURCE4_ALPHA_EXT 0x858C +#define GL_SOURCE5_ALPHA_EXT 0x858D +#define GL_SOURCE6_ALPHA_EXT 0x858E +#define GL_SOURCE7_ALPHA_EXT 0x858F +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND3_RGB_EXT 0x8593 +#define GL_OPERAND4_RGB_EXT 0x8594 +#define GL_OPERAND5_RGB_EXT 0x8595 +#define GL_OPERAND6_RGB_EXT 0x8596 +#define GL_OPERAND7_RGB_EXT 0x8597 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#define GL_OPERAND3_ALPHA_EXT 0x859B +#define GL_OPERAND4_ALPHA_EXT 0x859C +#define GL_OPERAND5_ALPHA_EXT 0x859D +#define GL_OPERAND6_ALPHA_EXT 0x859E +#define GL_OPERAND7_ALPHA_EXT 0x859F +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef GL_SUN_triangle_list +#define GL_RESTART_SUN 0x01 +#define GL_REPLACE_MIDDLE_SUN 0x02 +#define GL_REPLACE_OLDEST_SUN 0x03 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +#endif + +#ifndef GL_INGR_color_clamp +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef GL_EXT_texture_cube_map +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW_MATRIX1_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +#endif + +#ifndef GL_NV_register_combiners +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef GL_EXT_multisample +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + + +/*************************************************************/ + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glBlendColor (GLclampf, GLclampf, GLclampf, GLclampf); +extern void glBlendEquation (GLenum); +extern void glDrawRangeElements (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +extern void glColorTable (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glColorTableParameterfv (GLenum, GLenum, const GLfloat *); +extern void glColorTableParameteriv (GLenum, GLenum, const GLint *); +extern void glCopyColorTable (GLenum, GLenum, GLint, GLint, GLsizei); +extern void glGetColorTable (GLenum, GLenum, GLenum, GLvoid *); +extern void glGetColorTableParameterfv (GLenum, GLenum, GLfloat *); +extern void glGetColorTableParameteriv (GLenum, GLenum, GLint *); +extern void glColorSubTable (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glCopyColorSubTable (GLenum, GLsizei, GLint, GLint, GLsizei); +extern void glConvolutionFilter1D (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glConvolutionFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glConvolutionParameterf (GLenum, GLenum, GLfloat); +extern void glConvolutionParameterfv (GLenum, GLenum, const GLfloat *); +extern void glConvolutionParameteri (GLenum, GLenum, GLint); +extern void glConvolutionParameteriv (GLenum, GLenum, const GLint *); +extern void glCopyConvolutionFilter1D (GLenum, GLenum, GLint, GLint, GLsizei); +extern void glCopyConvolutionFilter2D (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +extern void glGetConvolutionFilter (GLenum, GLenum, GLenum, GLvoid *); +extern void glGetConvolutionParameterfv (GLenum, GLenum, GLfloat *); +extern void glGetConvolutionParameteriv (GLenum, GLenum, GLint *); +extern void glGetSeparableFilter (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +extern void glSeparableFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +extern void glGetHistogram (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void glGetHistogramParameterfv (GLenum, GLenum, GLfloat *); +extern void glGetHistogramParameteriv (GLenum, GLenum, GLint *); +extern void glGetMinmax (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void glGetMinmaxParameterfv (GLenum, GLenum, GLfloat *); +extern void glGetMinmaxParameteriv (GLenum, GLenum, GLint *); +extern void glHistogram (GLenum, GLsizei, GLenum, GLboolean); +extern void glMinmax (GLenum, GLenum, GLboolean); +extern void glResetHistogram (GLenum); +extern void glResetMinmax (GLenum); +extern void glTexImage3D (GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void glTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glCopyTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glActiveTextureARB (GLenum); +extern void glClientActiveTextureARB (GLenum); +extern void glMultiTexCoord1dARB (GLenum, GLdouble); +extern void glMultiTexCoord1dvARB (GLenum, const GLdouble *); +extern void glMultiTexCoord1fARB (GLenum, GLfloat); +extern void glMultiTexCoord1fvARB (GLenum, const GLfloat *); +extern void glMultiTexCoord1iARB (GLenum, GLint); +extern void glMultiTexCoord1ivARB (GLenum, const GLint *); +extern void glMultiTexCoord1sARB (GLenum, GLshort); +extern void glMultiTexCoord1svARB (GLenum, const GLshort *); +extern void glMultiTexCoord2dARB (GLenum, GLdouble, GLdouble); +extern void glMultiTexCoord2dvARB (GLenum, const GLdouble *); +extern void glMultiTexCoord2fARB (GLenum, GLfloat, GLfloat); +extern void glMultiTexCoord2fvARB (GLenum, const GLfloat *); +extern void glMultiTexCoord2iARB (GLenum, GLint, GLint); +extern void glMultiTexCoord2ivARB (GLenum, const GLint *); +extern void glMultiTexCoord2sARB (GLenum, GLshort, GLshort); +extern void glMultiTexCoord2svARB (GLenum, const GLshort *); +extern void glMultiTexCoord3dARB (GLenum, GLdouble, GLdouble, GLdouble); +extern void glMultiTexCoord3dvARB (GLenum, const GLdouble *); +extern void glMultiTexCoord3fARB (GLenum, GLfloat, GLfloat, GLfloat); +extern void glMultiTexCoord3fvARB (GLenum, const GLfloat *); +extern void glMultiTexCoord3iARB (GLenum, GLint, GLint, GLint); +extern void glMultiTexCoord3ivARB (GLenum, const GLint *); +extern void glMultiTexCoord3sARB (GLenum, GLshort, GLshort, GLshort); +extern void glMultiTexCoord3svARB (GLenum, const GLshort *); +extern void glMultiTexCoord4dARB (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +extern void glMultiTexCoord4dvARB (GLenum, const GLdouble *); +extern void glMultiTexCoord4fARB (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glMultiTexCoord4fvARB (GLenum, const GLfloat *); +extern void glMultiTexCoord4iARB (GLenum, GLint, GLint, GLint, GLint); +extern void glMultiTexCoord4ivARB (GLenum, const GLint *); +extern void glMultiTexCoord4sARB (GLenum, GLshort, GLshort, GLshort, GLshort); +extern void glMultiTexCoord4svARB (GLenum, const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glLoadTransposeMatrixfARB (const GLfloat *); +extern void glLoadTransposeMatrixdARB (const GLdouble *); +extern void glMultTransposeMatrixfARB (const GLfloat *); +extern void glMultTransposeMatrixdARB (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSampleCoverageARB (GLclampf, GLboolean); +extern void glSamplePassARB (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glCompressedTexImage3DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +extern void glCompressedTexImage2DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +extern void glCompressedTexImage1DARB (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +extern void glCompressedTexSubImage3DARB (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void glCompressedTexSubImage2DARB (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void glCompressedTexSubImage1DARB (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +extern void glGetCompressedTexImageARB (GLenum, GLint, void *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glBlendColorEXT (GLclampf, GLclampf, GLclampf, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glPolygonOffsetEXT (GLfloat, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTexImage3DEXT (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void glTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glGetTexFilterFuncSGIS (GLenum, GLenum, GLfloat *); +extern void glTexFilterFuncSGIS (GLenum, GLenum, GLsizei, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTexSubImage1DEXT (GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glCopyTexImage1DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLint); +extern void glCopyTexImage2DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint); +extern void glCopyTexSubImage1DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei); +extern void glCopyTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +extern void glCopyTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glGetHistogramEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void glGetHistogramParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void glGetHistogramParameterivEXT (GLenum, GLenum, GLint *); +extern void glGetMinmaxEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +extern void glGetMinmaxParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void glGetMinmaxParameterivEXT (GLenum, GLenum, GLint *); +extern void glHistogramEXT (GLenum, GLsizei, GLenum, GLboolean); +extern void glMinmaxEXT (GLenum, GLenum, GLboolean); +extern void glResetHistogramEXT (GLenum); +extern void glResetMinmaxEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glConvolutionFilter1DEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glConvolutionFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glConvolutionParameterfEXT (GLenum, GLenum, GLfloat); +extern void glConvolutionParameterfvEXT (GLenum, GLenum, const GLfloat *); +extern void glConvolutionParameteriEXT (GLenum, GLenum, GLint); +extern void glConvolutionParameterivEXT (GLenum, GLenum, const GLint *); +extern void glCopyConvolutionFilter1DEXT (GLenum, GLenum, GLint, GLint, GLsizei); +extern void glCopyConvolutionFilter2DEXT (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +extern void glGetConvolutionFilterEXT (GLenum, GLenum, GLenum, GLvoid *); +extern void glGetConvolutionParameterfvEXT (GLenum, GLenum, GLfloat *); +extern void glGetConvolutionParameterivEXT (GLenum, GLenum, GLint *); +extern void glGetSeparableFilterEXT (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +extern void glSeparableFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_color_matrix +#define GL_EXT_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glColorTableSGI (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glColorTableParameterfvSGI (GLenum, GLenum, const GLfloat *); +extern void glColorTableParameterivSGI (GLenum, GLenum, const GLint *); +extern void glCopyColorTableSGI (GLenum, GLenum, GLint, GLint, GLsizei); +extern void glGetColorTableSGI (GLenum, GLenum, GLenum, GLvoid *); +extern void glGetColorTableParameterfvSGI (GLenum, GLenum, GLfloat *); +extern void glGetColorTableParameterivSGI (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glPixelTexGenSGIX (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glPixelTexGenParameteriSGIS (GLenum, GLint); +extern void glPixelTexGenParameterivSGIS (GLenum, const GLint *); +extern void glPixelTexGenParameterfSGIS (GLenum, GLfloat); +extern void glPixelTexGenParameterfvSGIS (GLenum, const GLfloat *); +extern void glGetPixelTexGenParameterivSGIS (GLenum, GLint *); +extern void glGetPixelTexGenParameterfvSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTexImage4DSGIS (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +extern void glTexSubImage4DSGIS (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#ifdef GL_GLEXT_PROTOTYPES +extern GLboolean glAreTexturesResidentEXT (GLsizei, const GLuint *, GLboolean *); +extern void glBindTextureEXT (GLenum, GLuint); +extern void glDeleteTexturesEXT (GLsizei, const GLuint *); +extern void glGenTexturesEXT (GLsizei, GLuint *); +extern GLboolean glIsTextureEXT (GLuint); +extern void glPrioritizeTexturesEXT (GLsizei, const GLuint *, const GLclampf *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glDetailTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +extern void glGetDetailTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSharpenTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +extern void glGetSharpenTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSampleMaskSGIS (GLclampf, GLboolean); +extern void glSamplePatternSGIS (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glArrayElementEXT (GLint); +extern void glColorPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +extern void glDrawArraysEXT (GLenum, GLint, GLsizei); +extern void glEdgeFlagPointerEXT (GLsizei, GLsizei, const GLboolean *); +extern void glGetPointervEXT (GLenum, GLvoid* *); +extern void glIndexPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +extern void glNormalPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +extern void glTexCoordPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +extern void glVertexPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glBlendEquationEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSpriteParameterfSGIX (GLenum, GLfloat); +extern void glSpriteParameterfvSGIX (GLenum, const GLfloat *); +extern void glSpriteParameteriSGIX (GLenum, GLint); +extern void glSpriteParameterivSGIX (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glPointParameterfEXT (GLenum, GLfloat); +extern void glPointParameterfvEXT (GLenum, const GLfloat *); +extern void glPointParameterfSGIS (GLenum, GLfloat); +extern void glPointParameterfvSGIS (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#ifdef GL_GLEXT_PROTOTYPES +extern GLint glGetInstrumentsSGIX (void); +extern void glInstrumentsBufferSGIX (GLsizei, GLint *); +extern GLint glPollInstrumentsSGIX (GLint *); +extern void glReadInstrumentsSGIX (GLint); +extern void glStartInstrumentsSGIX (void); +extern void glStopInstrumentsSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFrameZoomSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTagSampleBufferSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glReferencePlaneSGIX (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFlushRasterSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFogFuncSGIS (GLsizei, const GLfloat *); +extern void glGetFogFuncSGIS (const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glImageTransformParameteriHP (GLenum, GLenum, GLint); +extern void glImageTransformParameterfHP (GLenum, GLenum, GLfloat); +extern void glImageTransformParameterivHP (GLenum, GLenum, const GLint *); +extern void glImageTransformParameterfvHP (GLenum, GLenum, const GLfloat *); +extern void glGetImageTransformParameterivHP (GLenum, GLenum, GLint *); +extern void glGetImageTransformParameterfvHP (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glColorSubTableEXT (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glCopyColorSubTableEXT (GLenum, GLsizei, GLint, GLint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glHintPGI (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glColorTableEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +extern void glGetColorTableEXT (GLenum, GLenum, GLenum, GLvoid *); +extern void glGetColorTableParameterivEXT (GLenum, GLenum, GLint *); +extern void glGetColorTableParameterfvEXT (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glGetListParameterfvSGIX (GLuint, GLenum, GLfloat *); +extern void glGetListParameterivSGIX (GLuint, GLenum, GLint *); +extern void glListParameterfSGIX (GLuint, GLenum, GLfloat); +extern void glListParameterfvSGIX (GLuint, GLenum, const GLfloat *); +extern void glListParameteriSGIX (GLuint, GLenum, GLint); +extern void glListParameterivSGIX (GLuint, GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glIndexMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glIndexFuncEXT (GLenum, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glLockArraysEXT (GLint, GLsizei); +extern void glUnlockArraysEXT (void); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glCullParameterdvEXT (GLenum, GLdouble *); +extern void glCullParameterfvEXT (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFragmentColorMaterialSGIX (GLenum, GLenum); +extern void glFragmentLightfSGIX (GLenum, GLenum, GLfloat); +extern void glFragmentLightfvSGIX (GLenum, GLenum, const GLfloat *); +extern void glFragmentLightiSGIX (GLenum, GLenum, GLint); +extern void glFragmentLightivSGIX (GLenum, GLenum, const GLint *); +extern void glFragmentLightModelfSGIX (GLenum, GLfloat); +extern void glFragmentLightModelfvSGIX (GLenum, const GLfloat *); +extern void glFragmentLightModeliSGIX (GLenum, GLint); +extern void glFragmentLightModelivSGIX (GLenum, const GLint *); +extern void glFragmentMaterialfSGIX (GLenum, GLenum, GLfloat); +extern void glFragmentMaterialfvSGIX (GLenum, GLenum, const GLfloat *); +extern void glFragmentMaterialiSGIX (GLenum, GLenum, GLint); +extern void glFragmentMaterialivSGIX (GLenum, GLenum, const GLint *); +extern void glGetFragmentLightfvSGIX (GLenum, GLenum, GLfloat *); +extern void glGetFragmentLightivSGIX (GLenum, GLenum, GLint *); +extern void glGetFragmentMaterialfvSGIX (GLenum, GLenum, GLfloat *); +extern void glGetFragmentMaterialivSGIX (GLenum, GLenum, GLint *); +extern void glLightEnviSGIX (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glDrawRangeElementsEXT (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glApplyTextureEXT (GLenum); +extern void glTextureLightEXT (GLenum); +extern void glTextureMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glVertexPointervINTEL (GLint, GLenum, const GLvoid* *); +extern void glNormalPointervINTEL (GLenum, const GLvoid* *); +extern void glColorPointervINTEL (GLint, GLenum, const GLvoid* *); +extern void glTexCoordPointervINTEL (GLint, GLenum, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glPixelTransformParameteriEXT (GLenum, GLenum, GLint); +extern void glPixelTransformParameterfEXT (GLenum, GLenum, GLfloat); +extern void glPixelTransformParameterivEXT (GLenum, GLenum, const GLint *); +extern void glPixelTransformParameterfvEXT (GLenum, GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSecondaryColor3bEXT (GLbyte, GLbyte, GLbyte); +extern void glSecondaryColor3bvEXT (const GLbyte *); +extern void glSecondaryColor3dEXT (GLdouble, GLdouble, GLdouble); +extern void glSecondaryColor3dvEXT (const GLdouble *); +extern void glSecondaryColor3fEXT (GLfloat, GLfloat, GLfloat); +extern void glSecondaryColor3fvEXT (const GLfloat *); +extern void glSecondaryColor3iEXT (GLint, GLint, GLint); +extern void glSecondaryColor3ivEXT (const GLint *); +extern void glSecondaryColor3sEXT (GLshort, GLshort, GLshort); +extern void glSecondaryColor3svEXT (const GLshort *); +extern void glSecondaryColor3ubEXT (GLubyte, GLubyte, GLubyte); +extern void glSecondaryColor3ubvEXT (const GLubyte *); +extern void glSecondaryColor3uiEXT (GLuint, GLuint, GLuint); +extern void glSecondaryColor3uivEXT (const GLuint *); +extern void glSecondaryColor3usEXT (GLushort, GLushort, GLushort); +extern void glSecondaryColor3usvEXT (const GLushort *); +extern void glSecondaryColorPointerEXT (GLint, GLenum, GLsizei, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTextureNormalEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glMultiDrawArraysEXT (GLenum, GLint *, GLsizei *, GLsizei); +extern void glMultiDrawElementsEXT (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFogCoordfEXT (GLfloat); +extern void glFogCoordfvEXT (const GLfloat *); +extern void glFogCoorddEXT (GLdouble); +extern void glFogCoorddvEXT (const GLdouble *); +extern void glFogCoordPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTangent3bEXT (GLbyte, GLbyte, GLbyte); +extern void glTangent3bvEXT (const GLbyte *); +extern void glTangent3dEXT (GLdouble, GLdouble, GLdouble); +extern void glTangent3dvEXT (const GLdouble *); +extern void glTangent3fEXT (GLfloat, GLfloat, GLfloat); +extern void glTangent3fvEXT (const GLfloat *); +extern void glTangent3iEXT (GLint, GLint, GLint); +extern void glTangent3ivEXT (const GLint *); +extern void glTangent3sEXT (GLshort, GLshort, GLshort); +extern void glTangent3svEXT (const GLshort *); +extern void glBinormal3bEXT (GLbyte, GLbyte, GLbyte); +extern void glBinormal3bvEXT (const GLbyte *); +extern void glBinormal3dEXT (GLdouble, GLdouble, GLdouble); +extern void glBinormal3dvEXT (const GLdouble *); +extern void glBinormal3fEXT (GLfloat, GLfloat, GLfloat); +extern void glBinormal3fvEXT (const GLfloat *); +extern void glBinormal3iEXT (GLint, GLint, GLint); +extern void glBinormal3ivEXT (const GLint *); +extern void glBinormal3sEXT (GLshort, GLshort, GLshort); +extern void glBinormal3svEXT (const GLshort *); +extern void glTangentPointerEXT (GLenum, GLsizei, const GLvoid *); +extern void glBinormalPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFinishTextureSUNX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glGlobalAlphaFactorbSUN (GLbyte); +extern void glGlobalAlphaFactorsSUN (GLshort); +extern void glGlobalAlphaFactoriSUN (GLint); +extern void glGlobalAlphaFactorfSUN (GLfloat); +extern void glGlobalAlphaFactordSUN (GLdouble); +extern void glGlobalAlphaFactorubSUN (GLubyte); +extern void glGlobalAlphaFactorusSUN (GLushort); +extern void glGlobalAlphaFactoruiSUN (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glReplacementCodeuiSUN (GLuint); +extern void glReplacementCodeusSUN (GLushort); +extern void glReplacementCodeubSUN (GLubyte); +extern void glReplacementCodeuivSUN (const GLuint *); +extern void glReplacementCodeusvSUN (const GLushort *); +extern void glReplacementCodeubvSUN (const GLubyte *); +extern void glReplacementCodePointerSUN (GLenum, GLsizei, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glColor4ubVertex2fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat); +extern void glColor4ubVertex2fvSUN (const GLubyte *, const GLfloat *); +extern void glColor4ubVertex3fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void glColor4ubVertex3fvSUN (const GLubyte *, const GLfloat *); +extern void glColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glColor3fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void glNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void glColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void glTexCoord2fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord2fVertex3fvSUN (const GLfloat *, const GLfloat *); +extern void glTexCoord4fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord4fVertex4fvSUN (const GLfloat *, const GLfloat *); +extern void glTexCoord2fColor4ubVertex3fSUN (GLfloat, GLfloat, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *, const GLubyte *, const GLfloat *); +extern void glTexCoord2fColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord2fColor3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void glTexCoord2fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +extern void glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiVertex3fvSUN (const GLenum *, const GLfloat *); +extern void glReplacementCodeuiColor4ubVertex3fSUN (GLenum, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiColor4ubVertex3fvSUN (const GLenum *, const GLubyte *, const GLfloat *); +extern void glReplacementCodeuiColor3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiColor3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiTexCoord2fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *); +extern void glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLenum, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +extern void glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLenum *, const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glBlendFuncSeparateEXT (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glVertexWeightfEXT (GLfloat); +extern void glVertexWeightfvEXT (const GLfloat *); +extern void glVertexWeightPointerEXT (GLsizei, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glFlushVertexArrayRangeNV (void); +extern void glVertexArrayRangeNV (GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glCombinerParameterfvNV (GLenum, const GLfloat *); +extern void glCombinerParameterfNV (GLenum, GLfloat); +extern void glCombinerParameterivNV (GLenum, const GLint *); +extern void glCombinerParameteriNV (GLenum, GLint); +extern void glCombinerInputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum); +extern void glCombinerOutputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean); +extern void glFinalCombinerInputNV (GLenum, GLenum, GLenum, GLenum); +extern void glGetCombinerInputParameterfvNV (GLenum, GLenum, GLenum, GLenum, GLfloat *); +extern void glGetCombinerInputParameterivNV (GLenum, GLenum, GLenum, GLenum, GLint *); +extern void glGetCombinerOutputParameterfvNV (GLenum, GLenum, GLenum, GLfloat *); +extern void glGetCombinerOutputParameterivNV (GLenum, GLenum, GLenum, GLint *); +extern void glGetFinalCombinerInputParameterfvNV (GLenum, GLenum, GLfloat *); +extern void glGetFinalCombinerInputParameterivNV (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glResizeBuffersMESA (void); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glWindowPos2dMESA (GLdouble, GLdouble); +extern void glWindowPos2dvMESA (const GLdouble *); +extern void glWindowPos2fMESA (GLfloat, GLfloat); +extern void glWindowPos2fvMESA (const GLfloat *); +extern void glWindowPos2iMESA (GLint, GLint); +extern void glWindowPos2ivMESA (const GLint *); +extern void glWindowPos2sMESA (GLshort, GLshort); +extern void glWindowPos2svMESA (const GLshort *); +extern void glWindowPos3dMESA (GLdouble, GLdouble, GLdouble); +extern void glWindowPos3dvMESA (const GLdouble *); +extern void glWindowPos3fMESA (GLfloat, GLfloat, GLfloat); +extern void glWindowPos3fvMESA (const GLfloat *); +extern void glWindowPos3iMESA (GLint, GLint, GLint); +extern void glWindowPos3ivMESA (const GLint *); +extern void glWindowPos3sMESA (GLshort, GLshort, GLshort); +extern void glWindowPos3svMESA (const GLshort *); +extern void glWindowPos4dMESA (GLdouble, GLdouble, GLdouble, GLdouble); +extern void glWindowPos4dvMESA (const GLdouble *); +extern void glWindowPos4fMESA (GLfloat, GLfloat, GLfloat, GLfloat); +extern void glWindowPos4fvMESA (const GLfloat *); +extern void glWindowPos4iMESA (GLint, GLint, GLint, GLint); +extern void glWindowPos4ivMESA (const GLint *); +extern void glWindowPos4sMESA (GLshort, GLshort, GLshort, GLshort); +extern void glWindowPos4svMESA (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glMultiModeDrawArraysIBM (GLenum, const GLint *, const GLsizei *, GLsizei, GLint); +extern void glMultiModeDrawElementsIBM (const GLenum *, const GLsizei *, GLenum, const GLvoid* *, GLsizei, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void glSecondaryColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void glEdgeFlagPointerListIBM (GLint, const GLboolean* *, GLint); +extern void glFogCoordPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void glIndexPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void glNormalPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +extern void glTexCoordPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +extern void glVertexPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTbufferMask3DFX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glSampleMaskEXT (GLclampf, GLboolean); +extern void glSamplePatternEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + +#ifndef GL_SGI_vertex_preclip +#define GL_SGI_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#ifdef GL_GLEXT_PROTOTYPES +extern void glTextureColorMaskSGIS (GLboolean, GLboolean, GLboolean, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +#endif + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/code/renderer/matcomp.c b/code/renderer/matcomp.c new file mode 100644 index 0000000..8ca16ae --- /dev/null +++ b/code/renderer/matcomp.c @@ -0,0 +1,361 @@ +#include "MatComp.h" +#include +#include +#include +#include // for memcpy + +#define MC_MASK_X ((1<<(MC_BITS_X))-1) +#define MC_MASK_Y ((1<<(MC_BITS_Y))-1) +#define MC_MASK_Z ((1<<(MC_BITS_Z))-1) +#define MC_MASK_VECT ((1<<(MC_BITS_VECT))-1) + +#define MC_SCALE_VECT (1.0f/(float)((1<<(MC_BITS_VECT-1))-2)) + +#define MC_POS_X (0) +#define MC_SHIFT_X (0) + +#define MC_POS_Y ((((MC_BITS_X))/8)) +#define MC_SHIFT_Y ((((MC_BITS_X)%8))) + +#define MC_POS_Z ((((MC_BITS_X+MC_BITS_Y))/8)) +#define MC_SHIFT_Z ((((MC_BITS_X+MC_BITS_Y)%8))) + +#define MC_POS_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z))/8)) +#define MC_SHIFT_V11 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z)%8))) + +#define MC_POS_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT))/8)) +#define MC_SHIFT_V12 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT)%8))) + +#define MC_POS_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2))/8)) +#define MC_SHIFT_V13 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*2)%8))) + +#define MC_POS_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3))/8)) +#define MC_SHIFT_V21 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*3)%8))) + +#define MC_POS_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4))/8)) +#define MC_SHIFT_V22 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*4)%8))) + +#define MC_POS_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5))/8)) +#define MC_SHIFT_V23 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*5)%8))) + +#define MC_POS_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6))/8)) +#define MC_SHIFT_V31 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*6)%8))) + +#define MC_POS_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7))/8)) +#define MC_SHIFT_V32 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*7)%8))) + +#define MC_POS_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8))/8)) +#define MC_SHIFT_V33 ((((MC_BITS_X+MC_BITS_Y+MC_BITS_Z+MC_BITS_VECT*8)%8))) + +void MC_Compress(const float mat[3][4],unsigned char * _comp) +{ + char comp[MC_COMP_BYTES*2]; + + int i,val; + for (i=0;i=(1<=(1<=(1<=(1<=(1<=(1<=(1<=(1<=(1<=(1<=(1<=(1<_info.txt" file written out by carcass any changes made in here that +// introduce new data should also be reflected in the info-output) + +// 32 bit-flags for ghoul2 bone properties... (all undefined fields will be blank) +// +#define G2BONEFLAG_ALWAYSXFORM 0x00000001 + +// same thing but for surfaces... (Carcass will only generate 1st 2 flags, others are ingame +// +#define G2SURFACEFLAG_ISBOLT 0x00000001 +#define G2SURFACEFLAG_OFF 0x00000002 // saves strcmp()ing for "_off" in surface names +#define G2SURFACEFLAG_SPARE0 0x00000004 // future-expansion fields, saves invalidating models if we add more +#define G2SURFACEFLAG_SPARE1 0x00000008 // +#define G2SURFACEFLAG_SPARE2 0x00000010 // +#define G2SURFACEFLAG_SPARE3 0x00000020 // +#define G2SURFACEFLAG_SPARE4 0x00000040 // +#define G2SURFACEFLAG_SPARE5 0x00000080 // +// +#define G2SURFACEFLAG_NODESCENDANTS 0x00000100 // ingame-stuff, never generated by Carcass.... +#define G2SURFACEFLAG_GENERATED 0x00000200 // + + + +// triangle side-ordering stuff for tags... +// +#define iG2_TRISIDE_MIDDLE 1 +#define iG2_TRISIDE_LONGEST 0 +#define iG2_TRISIDE_SHORTEST 2 + +#define fG2_BONEWEIGHT_RECIPROCAL_MULT ((float)(1.0f/1023.0f)) +#define iG2_BITS_PER_BONEREF 5 +#define iMAX_G2_BONEREFS_PER_SURFACE (1<... + // (note that I've defined it using '>' internally, so it sorts with higher weights being "less", for distance weight-culling + // + #ifdef __cplusplus + bool operator < (const mdxmWeight_t& _X) const {return (boneWeight>_X.boneWeight);} + #endif +} +#ifndef __cplusplus +mdxmWeight_t +#endif +; +*/ +/* +#ifdef __cplusplus +struct mdxaCompBone_t +#else +typedef struct +#endif +{ + unsigned char Comp[24]; // MC_COMP_BYTES is in MatComp.h, but don't want to couple + + // I'm defining this '<' operator so this struct can be used as an STL key... + // + #ifdef __cplusplus + bool operator < (const mdxaCompBone_t& _X) const {return (memcmp(Comp,_X.Comp,sizeof(Comp))<0);} + #endif +} +#ifndef __cplusplus +mdxaCompBone_t +#endif +; +*/ +#ifdef __cplusplus +struct mdxaCompQuatBone_t +#else +typedef struct +#endif +{ + unsigned char Comp[14]; + + // I'm defining this '<' operator so this struct can be used as an STL key... + // + #ifdef __cplusplus + bool operator < (const mdxaCompQuatBone_t& _X) const {return (memcmp(Comp,_X.Comp,sizeof(Comp))<0);} + #endif +} +#ifndef __cplusplus +mdxaCompQuatBone_t +#endif +; + + +#ifndef MDXABONEDEF +typedef struct { + float matrix[3][4]; +} mdxaBone_t; +#endif + +//////////////////////////////////// + + + + + + +// mdxHeader_t - this contains the header for the file, with sanity checking and version checking, plus number of lod's to be expected +// +typedef struct { + // + // ( first 3 fields are same format as MD3/MDR so we can apply easy model-format-type checks ) + // + int ident; // "IDP3" = MD3, "RDM5" = MDR, "2LGM"(GL2 Mesh) = MDX (cruddy char order I know, but I'm following what was there in other versions) + int version; // 1,2,3 etc as per format revision + char name[MAX_QPATH]; // model name (eg "models/players/marine.glm") // note: extension supplied + char animName[MAX_QPATH];// name of animation file this mesh requires // note: extension missing + int animIndex; // filled in by game (carcass defaults it to 0) + + int numBones; // (for ingame version-checks only, ensure we don't ref more bones than skel file has) + + int numLODs; + int ofsLODs; + + int numSurfaces; // now that surfaces are drawn hierarchically, we have same # per LOD + int ofsSurfHierarchy; + + int ofsEnd; // EOF, which of course gives overall file size +} mdxmHeader_t; + + +// for each surface (doesn't actually need a struct for this, just makes source clearer) +// { + typedef struct + { + int offsets[1]; // variable sized (mdxmHeader_t->numSurfaces), each offset points to a mdxmSurfHierarchy_t below + } mdxmHierarchyOffsets_t; +// } + +// for each surface... (mdxmHeader_t->numSurfaces) +// { + // mdxmSurfHierarchy_t - contains hierarchical info for surfaces... + + typedef struct { + char name[MAX_QPATH]; + unsigned int flags; + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use (carcass defaults to 0) + int parentIndex; // this points to the index in the file of the parent surface. -1 if null/root + int numChildren; // number of surfaces which are children of this one + int childIndexes[1]; // [mdxmSurfHierarch_t->numChildren] (variable sized) + } mdxmSurfHierarchy_t; // struct size = (int)( &((mdxmSurfHierarch_t *)0)->childIndexes[ mdxmSurfHierarch_t->numChildren ] ); +// } + + +// for each LOD... (mdxmHeader_t->numLODs) +// { + // mdxLOD_t - this contains the header for this LOD. Contains num of surfaces, offset to surfaces and offset to next LOD. Surfaces are shader sorted, so each surface = 1 shader + + typedef struct { + // (used to contain numSurface/ofsSurfaces fields, but these are same per LOD level now) + // + int ofsEnd; // offset to next LOD + } mdxmLOD_t; + + + typedef struct { // added in GLM version 3 for ingame use at Jake's request + int offsets[1]; // variable sized (mdxmHeader_t->numSurfaces), each offset points to surfaces below + } mdxmLODSurfOffset_t; + + + // for each surface... (mdxmHeader_t->numSurfaces) + // { + // mdxSurface_t - reuse of header format containing surface name, number of bones, offset to poly data and number of polys, offset to vertex information, and number of verts. NOTE offsets are relative to this header. + + typedef struct { + int ident; // this one field at least should be kept, since the game-engine may switch-case (but currently=0 in carcass) + + int thisSurfaceIndex; // 0...mdxmHeader_t->numSurfaces-1 (because of how ingame renderer works) + + int ofsHeader; // this will be a negative number, pointing back to main header + + int numVerts; + int ofsVerts; + + int numTriangles; + int ofsTriangles; + + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + // + int numBoneReferences; + int ofsBoneReferences; + + int ofsEnd; // next surface follows + + } mdxmSurface_t; + + + // for each triangle... (mdxmSurface_t->numTriangles) + // { + // mdxTriangle_t - contains indexes into verts. One struct entry per poly. + + typedef struct { + int indexes[3]; + } mdxmTriangle_t; + // } + + + // for each vert... (mdxmSurface_t->numVerts) + // { + // mdxVertex_t - this is an array with number of verts from the surface definition as its bounds. It contains normal info, texture coors and number of weightings for this bone + // (this is now kept at 32 bytes for cache-aligning) + typedef struct { +#ifdef _XBOX + //short normal[3]; + unsigned int normal; + short vertCoords[3]; + unsigned int tangent; +#else + vec3_t normal; + vec3_t vertCoords; +#endif + + // packed int... + unsigned int uiNmWeightsAndBoneIndexes; // 32 bits. format: + // 31 & 30: 0..3 (= 1..4) weight count + // 29 & 28 (spare) + // 2 bit pairs at 20,22,24,26 are 2-bit overflows from 4 BonWeights below (20=[0], 22=[1]) etc) + // 5-bits each (iG2_BITS_PER_BONEREF) for boneweights + // effectively a packed int, each bone weight converted from 0..1 float to 0..255 int... + // promote each entry to float and multiply by fG2_BONEWEIGHT_RECIPROCAL_MULT to convert. + byte BoneWeightings[iMAX_G2_BONEWEIGHTS_PER_VERT]; // 4 + + } mdxmVertex_t; + + // } vert + +#ifdef __cplusplus + +// these are convenience functions that I can invoked in code. Do NOT change them (because this is a shared file), +// but if you want to copy the logic out and use your own versions then fine... +// +static inline int G2_GetVertWeights( const mdxmVertex_t *pVert ) +{ + int iNumWeights = (pVert->uiNmWeightsAndBoneIndexes >> 30)+1; // 1..4 count + + return iNumWeights; +} + +static inline int G2_GetVertBoneIndex( const mdxmVertex_t *pVert, const int iWeightNum) +{ + int iBoneIndex = (pVert->uiNmWeightsAndBoneIndexes>>(iG2_BITS_PER_BONEREF*iWeightNum))&((1<BoneWeightings[iWeightNum]; + iTemp|= (pVert->uiNmWeightsAndBoneIndexes >> (iG2_BONEWEIGHT_TOPBITS_SHIFT+(iWeightNum*2)) ) & iG2_BONEWEIGHT_TOPBITS_AND; + + fBoneWeight = fG2_BONEWEIGHT_RECIPROCAL_MULT * iTemp; + fTotalWeight += fBoneWeight; + } + + return fBoneWeight; +} +#endif + // for each vert... (mdxmSurface_t->numVerts) (seperated from mdxmVertex_t struct for cache reasons) + // { + // mdxVertex_t - this is an array with number of verts from the surface definition as its bounds. It contains normal info, texture coors and number of weightings for this bone + + typedef struct { +#ifdef _XBOX + short texCoords[2]; +#else + vec2_t texCoords; +#endif + } mdxmVertexTexCoord_t; + + // } vert + + + // } surface +// } LOD + + + +//---------------------------------------------------------------------------- +// seperate file here for animation data... +// + + +// mdxaHeader_t - this contains the header for the file, with sanity checking and version checking, plus number of lod's to be expected +// +typedef struct { + // + // ( first 3 fields are same format as MD3/MDR so we can apply easy model-format-type checks ) + // + int ident; // "IDP3" = MD3, "RDM5" = MDR, "2LGA"(GL2 Anim) = MDXA + int version; // 1,2,3 etc as per format revision + // + char name[MAX_QPATH]; // GLA name (eg "skeletons/marine") // note: extension missing + float fScale; // will be zero if build before this field was defined, else scale it was built with + + // frames and bones are shared by all levels of detail + // + int numFrames; + int ofsFrames; // points at mdxaFrame_t array + int numBones; // (no offset to these since they're inside the frames array) + int ofsCompBonePool; // offset to global compressed-bone pool that all frames use + int ofsSkel; // offset to mdxaSkel_t info + + int ofsEnd; // EOF, which of course gives overall file size + +} mdxaHeader_t; + + +// for each bone... (doesn't actually need a struct for this, just makes source clearer) +// { + typedef struct + { + int offsets[1]; // variable sized (mdxaHeader_t->numBones), each offset points to an mdxaSkel_t below + } mdxaSkelOffsets_t; +// } + + + +// for each bone... (mdxaHeader_t->numBones) +// { + // mdxaSkel_t - contains hierarchical info only... + + typedef struct { + char name[MAX_QPATH]; // name of bone + unsigned int flags; + int parent; // index of bone that is parent to this one, -1 = NULL/root + mdxaBone_t BasePoseMat; // base pose + mdxaBone_t BasePoseMatInv; // inverse, to save run-time calc + int numChildren; // number of children bones + int children[1]; // [mdxaSkel_t->numChildren] (variable sized) + } mdxaSkel_t; // struct size = (int)( &((mdxaSkel_t *)0)->children[ mdxaSkel_t->numChildren ] ); +// } + + + // (offset @ mdxaHeader_t->ofsFrames) + // + // array of 3 byte indices here (hey, 25% saving over 4-byte really adds up)... + // + // + // access as follows to get the index for a given + // + // (iFrameNum * mdxaHeader_t->numBones * 3) + (iBoneNum * 3) + // + // then read the int at that location and AND it with 0x00FFFFFF. I use the struct below simply for easy searches + typedef struct + { + int iIndex; // this struct for pointing purposes, need to and with 0x00FFFFFF to be meaningful + } mdxaIndex_t; + // + // (note that there's then an alignement-pad here to get the next struct back onto 32-bit alignement) + // + // this index then points into the following... + + +// Compressed-bone pool that all frames use (mdxaHeader_t->ofsCompBonePool) (defined at end because size unknown until end) +// for each bone in pool (unknown number, no actual total stored at the moment)... +// { + // mdxaCompBone_t (defined at file top because of struct dependancy) +// } + +//--------------------------------------------------------------------------- + + +#endif // #ifndef MDX_FORMAT_H + +//////////////////////// eof /////////////////////// + + + diff --git a/code/renderer/qgl.h b/code/renderer/qgl.h new file mode 100644 index 0000000..01bd35b --- /dev/null +++ b/code/renderer/qgl.h @@ -0,0 +1,734 @@ +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + +#if defined( __LINT__ ) + +#include + +#elif defined( _WIN32 ) + +#pragma warning (disable: 4201) +#pragma warning (disable: 4214) +#pragma warning (disable: 4514) +#pragma warning (disable: 4032) +#pragma warning (disable: 4201) +#pragma warning (disable: 4214) +#include +#include + +#elif defined( __APPLE__ ) && defined( __MACH__ ) + +#include + +#elif defined( __linux__ ) + +#include +#include +#include + +#else + +#include + +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef WINAPI +#define WINAPI +#endif + + +//=========================================================================== + +/* +** multitexture extension definitions +*/ +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_ACTIVE_TEXTURES_ARB 0x84E2 + +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 + +#define GL_TEXTURE_RECTANGLE_EXT 0x84F5 + +typedef void (APIENTRY * PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRY * PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRY * PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRY * PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRY * PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRY * PFNGLACTIVETEXTUREARBPROC) (GLenum target); +typedef void (APIENTRY * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum target); + + +// Steps to adding a new extension: +// - Add the typedef and function pointer externs here. +// - Define the function pointer in tr_init.cpp and possibly add a cvar to track your ext status. +// - Load the extension in win_glimp.cpp. + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Register Combiner extension definitions. - AReis +/***********************************************************************************************************/ +// NOTE: These are obviously not all the regcom flags. I'm only including the ones I use (to reduce code clutter), so +// if you need any of the other flags, just add them. +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_DISCARD_NV 0x8530 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 + +typedef void (APIENTRY *PFNGLCOMBINERPARAMETERFVNV) (GLenum pname,const GLfloat *params); +typedef void (APIENTRY *PFNGLCOMBINERPARAMETERIVNV) (GLenum pname,const GLint *params); +typedef void (APIENTRY *PFNGLCOMBINERPARAMETERFNV) (GLenum pname,GLfloat param); +typedef void (APIENTRY *PFNGLCOMBINERPARAMETERINV) (GLenum pname,GLint param); +typedef void (APIENTRY *PFNGLCOMBINERINPUTNV) (GLenum stage,GLenum portion,GLenum variable,GLenum input,GLenum mapping, + GLenum componentUsage); +typedef void (APIENTRY *PFNGLCOMBINEROUTPUTNV) (GLenum stage,GLenum portion,GLenum abOutput,GLenum cdOutput,GLenum sumOutput, + GLenum scale, GLenum bias,GLboolean abDotProduct,GLboolean cdDotProduct, + GLboolean muxSum); +typedef void (APIENTRY *PFNGLFINALCOMBINERINPUTNV) (GLenum variable,GLenum input,GLenum mapping,GLenum componentUsage); + +typedef void (APIENTRY *PFNGLGETCOMBINERINPUTPARAMETERFVNV) (GLenum stage,GLenum portion,GLenum variable,GLenum pname,GLfloat *params); +typedef void (APIENTRY *PFNGLGETCOMBINERINPUTPARAMETERIVNV) (GLenum stage,GLenum portion,GLenum variable,GLenum pname,GLint *params); +typedef void (APIENTRY *PFNGLGETCOMBINEROUTPUTPARAMETERFVNV) (GLenum stage,GLenum portion,GLenum pname,GLfloat *params); +typedef void (APIENTRY *PFNGLGETCOMBINEROUTPUTPARAMETERIVNV) (GLenum stage,GLenum portion,GLenum pname,GLint *params); +typedef void (APIENTRY *PFNGLGETFINALCOMBINERINPUTPARAMETERFVNV) (GLenum variable,GLenum pname,GLfloat *params); +typedef void (APIENTRY *PFNGLGETFINALCOMBINERINPUTPARAMETERIVNV) (GLenum variable,GLenum pname,GLfloat *params); +/***********************************************************************************************************/ + +// Declare Register Combiners function pointers. +extern PFNGLCOMBINERPARAMETERFVNV qglCombinerParameterfvNV; +extern PFNGLCOMBINERPARAMETERIVNV qglCombinerParameterivNV; +extern PFNGLCOMBINERPARAMETERFNV qglCombinerParameterfNV; +extern PFNGLCOMBINERPARAMETERINV qglCombinerParameteriNV; +extern PFNGLCOMBINERINPUTNV qglCombinerInputNV; +extern PFNGLCOMBINEROUTPUTNV qglCombinerOutputNV; +extern PFNGLFINALCOMBINERINPUTNV qglFinalCombinerInputNV; +extern PFNGLGETCOMBINERINPUTPARAMETERFVNV qglGetCombinerInputParameterfvNV; +extern PFNGLGETCOMBINERINPUTPARAMETERIVNV qglGetCombinerInputParameterivNV; +extern PFNGLGETCOMBINEROUTPUTPARAMETERFVNV qglGetCombinerOutputParameterfvNV; +extern PFNGLGETCOMBINEROUTPUTPARAMETERIVNV qglGetCombinerOutputParameterivNV; +extern PFNGLGETFINALCOMBINERINPUTPARAMETERFVNV qglGetFinalCombinerInputParameterfvNV; +extern PFNGLGETFINALCOMBINERINPUTPARAMETERIVNV qglGetFinalCombinerInputParameterivNV; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Pixel Format extension definitions. - AReis +/***********************************************************************************************************/ +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 + +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBIVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); +typedef BOOL (WINAPI * PFNWGLGETPIXELFORMATATTRIBFVARBPROC) (HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, FLOAT *pfValues); +typedef BOOL (WINAPI * PFNWGLCHOOSEPIXELFORMATARBPROC) (HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); +/***********************************************************************************************************/ + +// Declare Pixel Format function pointers. +extern PFNWGLGETPIXELFORMATATTRIBIVARBPROC qwglGetPixelFormatAttribivARB; +extern PFNWGLGETPIXELFORMATATTRIBFVARBPROC qwglGetPixelFormatAttribfvARB; +extern PFNWGLCHOOSEPIXELFORMATARBPROC qwglChoosePixelFormatARB; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Pixel Buffer extension definitions. - AReis +/***********************************************************************************************************/ +DECLARE_HANDLE(HPBUFFERARB); + +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_DRAW_TO_PBUFFER_ARB 0x202D +#define WGL_PBUFFER_WIDTH_ARB 0x2034 +#define WGL_PBUFFER_HEIGHT_ARB 0x2035 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 + +typedef HPBUFFERARB (WINAPI * PFNWGLCREATEPBUFFERARBPROC) (HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int *piAttribList); +typedef HDC (WINAPI * PFNWGLGETPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer); +typedef int (WINAPI * PFNWGLRELEASEPBUFFERDCARBPROC) (HPBUFFERARB hPbuffer, HDC hDC); +typedef BOOL (WINAPI * PFNWGLDESTROYPBUFFERARBPROC) (HPBUFFERARB hPbuffer); +typedef BOOL (WINAPI * PFNWGLQUERYPBUFFERARBPROC) (HPBUFFERARB hPbuffer, int iAttribute, int *piValue); +/***********************************************************************************************************/ + +// Declare Pixel Buffer function pointers. +extern PFNWGLCREATEPBUFFERARBPROC qwglCreatePbufferARB; +extern PFNWGLGETPBUFFERDCARBPROC qwglGetPbufferDCARB; +extern PFNWGLRELEASEPBUFFERDCARBPROC qwglReleasePbufferDCARB; +extern PFNWGLDESTROYPBUFFERARBPROC qwglDestroyPbufferARB; +extern PFNWGLQUERYPBUFFERARBPROC qwglQueryPbufferARB; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Render-Texture extension definitions. - AReis +/***********************************************************************************************************/ +#define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 +#define WGL_TEXTURE_FORMAT_ARB 0x2072 +#define WGL_TEXTURE_TARGET_ARB 0x2073 +#define WGL_TEXTURE_RGB_ARB 0x2075 +#define WGL_TEXTURE_RGBA_ARB 0x2076 +#define WGL_TEXTURE_2D_ARB 0x207A +#define WGL_FRONT_LEFT_ARB 0x2083 + +typedef BOOL (WINAPI * PFNWGLBINDTEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLRELEASETEXIMAGEARBPROC) (HPBUFFERARB hPbuffer, int iBuffer); +typedef BOOL (WINAPI * PFNWGLSETPBUFFERATTRIBARBPROC) (HPBUFFERARB hPbuffer, const int * piAttribList); +/***********************************************************************************************************/ + +// Declare Render-Texture function pointers. +extern PFNWGLBINDTEXIMAGEARBPROC qwglBindTexImageARB; +extern PFNWGLRELEASETEXIMAGEARBPROC qwglReleaseTexImageARB; +extern PFNWGLSETPBUFFERATTRIBARBPROC qwglSetPbufferAttribARB; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Vertex and Fragment Program extension definitions. - AReis +/***********************************************************************************************************/ +#ifndef GL_ARB_fragment_program +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#endif + +// NOTE: These are obviously not all the vertex program flags (have you seen how many there actually are!). I'm +// only including the ones I use (to reduce code clutter), so if you need any of the other flags, just add them. +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 + +typedef void (APIENTRY * PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const GLvoid *string); +typedef void (APIENTRY * PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRY * PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRY * PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRY * PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRY * PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRY * PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRY * PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRY * PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRY * PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRY * PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRY * PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRY * PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRY * PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRY * PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRY * PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRY * PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRY * PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, GLvoid *string); +typedef GLboolean (APIENTRY * PFNGLISPROGRAMARBPROC) (GLuint program); +/***********************************************************************************************************/ + +// Declare Vertex and Fragment Program function pointers. +extern PFNGLPROGRAMSTRINGARBPROC qglProgramStringARB; +extern PFNGLBINDPROGRAMARBPROC qglBindProgramARB; +extern PFNGLDELETEPROGRAMSARBPROC qglDeleteProgramsARB; +extern PFNGLGENPROGRAMSARBPROC qglGenProgramsARB; +extern PFNGLPROGRAMENVPARAMETER4DARBPROC qglProgramEnvParameter4dARB; +extern PFNGLPROGRAMENVPARAMETER4DVARBPROC qglProgramEnvParameter4dvARB; +extern PFNGLPROGRAMENVPARAMETER4FARBPROC qglProgramEnvParameter4fARB; +extern PFNGLPROGRAMENVPARAMETER4FVARBPROC qglProgramEnvParameter4fvARB; +extern PFNGLPROGRAMLOCALPARAMETER4DARBPROC qglProgramLocalParameter4dARB; +extern PFNGLPROGRAMLOCALPARAMETER4DVARBPROC qglProgramLocalParameter4dvARB; +extern PFNGLPROGRAMLOCALPARAMETER4FARBPROC qglProgramLocalParameter4fARB; +extern PFNGLPROGRAMLOCALPARAMETER4FVARBPROC qglProgramLocalParameter4fvARB; +extern PFNGLGETPROGRAMENVPARAMETERDVARBPROC qglGetProgramEnvParameterdvARB; +extern PFNGLGETPROGRAMENVPARAMETERFVARBPROC qglGetProgramEnvParameterfvARB; +extern PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC qglGetProgramLocalParameterdvARB; +extern PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC qglGetProgramLocalParameterfvARB; +extern PFNGLGETPROGRAMIVARBPROC qglGetProgramivARB; +extern PFNGLGETPROGRAMSTRINGARBPROC qglGetProgramStringARB; +extern PFNGLISPROGRAMARBPROC qglIsProgramARB; + + +/* +** extension constants +*/ + + +// S3TC compression constants +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 + + +// extensions will be function pointers on all platforms + +extern void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +extern void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +extern void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +extern void ( APIENTRY * qglLockArraysEXT) (GLint, GLint); +extern void ( APIENTRY * qglUnlockArraysEXT) (void); + +extern void ( APIENTRY * qglPointParameterfEXT)( GLenum, GLfloat); +extern void ( APIENTRY * qglPointParameterfvEXT)( GLenum, GLfloat *); + +// Added 10/23/02 by Aurelio Reis. +extern void ( APIENTRY * qglPointParameteriNV)( GLenum, GLint); +extern void ( APIENTRY * qglPointParameterivNV)( GLenum, const GLint *); + +//=========================================================================== + +// non-windows systems will just redefine qgl* to gl* +#if !defined( _WIN32 ) && !defined( __linux__ ) + +#include "qgl_linked.h" + +#else + +// windows systems use a function pointer for each call so we can load minidrivers + +extern void ( APIENTRY * qglAccum )(GLenum op, GLfloat value); +extern void ( APIENTRY * qglAlphaFunc )(GLenum func, GLclampf ref); +extern GLboolean ( APIENTRY * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +extern void ( APIENTRY * qglArrayElement )(GLint i); +extern void ( APIENTRY * qglBegin )(GLenum mode); +extern void ( APIENTRY * qglBindTexture )(GLenum target, GLuint texture); +extern void ( APIENTRY * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +extern void ( APIENTRY * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +extern void ( APIENTRY * qglCallList )(GLuint list); +extern void ( APIENTRY * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +extern void ( APIENTRY * qglClear )(GLbitfield mask); +extern void ( APIENTRY * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( APIENTRY * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +extern void ( APIENTRY * qglClearDepth )(GLclampd depth); +extern void ( APIENTRY * qglClearIndex )(GLfloat c); +extern void ( APIENTRY * qglClearStencil )(GLint s); +extern void ( APIENTRY * qglClipPlane )(GLenum plane, const GLdouble *equation); +extern void ( APIENTRY * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +extern void ( APIENTRY * qglColor3bv )(const GLbyte *v); +extern void ( APIENTRY * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +extern void ( APIENTRY * qglColor3dv )(const GLdouble *v); +extern void ( APIENTRY * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +extern void ( APIENTRY * qglColor3fv )(const GLfloat *v); +extern void ( APIENTRY * qglColor3i )(GLint red, GLint green, GLint blue); +extern void ( APIENTRY * qglColor3iv )(const GLint *v); +extern void ( APIENTRY * qglColor3s )(GLshort red, GLshort green, GLshort blue); +extern void ( APIENTRY * qglColor3sv )(const GLshort *v); +extern void ( APIENTRY * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +extern void ( APIENTRY * qglColor3ubv )(const GLubyte *v); +extern void ( APIENTRY * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +extern void ( APIENTRY * qglColor3uiv )(const GLuint *v); +extern void ( APIENTRY * qglColor3us )(GLushort red, GLushort green, GLushort blue); +extern void ( APIENTRY * qglColor3usv )(const GLushort *v); +extern void ( APIENTRY * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +extern void ( APIENTRY * qglColor4bv )(const GLbyte *v); +extern void ( APIENTRY * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +extern void ( APIENTRY * qglColor4dv )(const GLdouble *v); +extern void ( APIENTRY * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( APIENTRY * qglColor4fv )(const GLfloat *v); +extern void ( APIENTRY * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +extern void ( APIENTRY * qglColor4iv )(const GLint *v); +extern void ( APIENTRY * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +extern void ( APIENTRY * qglColor4sv )(const GLshort *v); +extern void ( APIENTRY * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern void ( APIENTRY * qglColor4ubv )(const GLubyte *v); +extern void ( APIENTRY * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +extern void ( APIENTRY * qglColor4uiv )(const GLuint *v); +extern void ( APIENTRY * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +extern void ( APIENTRY * qglColor4usv )(const GLushort *v); +extern void ( APIENTRY * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +extern void ( APIENTRY * qglColorMaterial )(GLenum face, GLenum mode); +extern void ( APIENTRY * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +extern void ( APIENTRY * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +extern void ( APIENTRY * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +extern void ( APIENTRY * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern void ( APIENTRY * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( APIENTRY * qglCullFace )(GLenum mode); +extern void ( APIENTRY * qglDeleteLists )(GLuint list, GLsizei range); +extern void ( APIENTRY * qglDeleteTextures )(GLsizei n, const GLuint *textures); +extern void ( APIENTRY * qglDepthFunc )(GLenum func); +extern void ( APIENTRY * qglDepthMask )(GLboolean flag); +extern void ( APIENTRY * qglDepthRange )(GLclampd zNear, GLclampd zFar); +extern void ( APIENTRY * qglDisable )(GLenum cap); +extern void ( APIENTRY * qglDisableClientState )(GLenum array); +extern void ( APIENTRY * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +extern void ( APIENTRY * qglDrawBuffer )(GLenum mode); +extern void ( APIENTRY * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +extern void ( APIENTRY * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglEdgeFlag )(GLboolean flag); +extern void ( APIENTRY * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglEdgeFlagv )(const GLboolean *flag); +extern void ( APIENTRY * qglEnable )(GLenum cap); +extern void ( APIENTRY * qglEnableClientState )(GLenum array); +extern void ( APIENTRY * qglEnd )(void); +extern void ( APIENTRY * qglEndList )(void); +extern void ( APIENTRY * qglEvalCoord1d )(GLdouble u); +extern void ( APIENTRY * qglEvalCoord1dv )(const GLdouble *u); +extern void ( APIENTRY * qglEvalCoord1f )(GLfloat u); +extern void ( APIENTRY * qglEvalCoord1fv )(const GLfloat *u); +extern void ( APIENTRY * qglEvalCoord2d )(GLdouble u, GLdouble v); +extern void ( APIENTRY * qglEvalCoord2dv )(const GLdouble *u); +extern void ( APIENTRY * qglEvalCoord2f )(GLfloat u, GLfloat v); +extern void ( APIENTRY * qglEvalCoord2fv )(const GLfloat *u); +extern void ( APIENTRY * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +extern void ( APIENTRY * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +extern void ( APIENTRY * qglEvalPoint1 )(GLint i); +extern void ( APIENTRY * qglEvalPoint2 )(GLint i, GLint j); +extern void ( APIENTRY * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +extern void ( APIENTRY * qglFinish )(void); +extern void ( APIENTRY * qglFlush )(void); +extern void ( APIENTRY * qglFogf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglFogfv )(GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglFogi )(GLenum pname, GLint param); +extern void ( APIENTRY * qglFogiv )(GLenum pname, const GLint *params); +extern void ( APIENTRY * qglFrontFace )(GLenum mode); +extern void ( APIENTRY * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern GLuint ( APIENTRY * qglGenLists )(GLsizei range); +extern void ( APIENTRY * qglGenTextures )(GLsizei n, GLuint *textures); +extern void ( APIENTRY * qglGetBooleanv )(GLenum pname, GLboolean *params); +extern void ( APIENTRY * qglGetClipPlane )(GLenum plane, GLdouble *equation); +extern void ( APIENTRY * qglGetDoublev )(GLenum pname, GLdouble *params); +extern GLenum ( APIENTRY * qglGetError )(void); +extern void ( APIENTRY * qglGetFloatv )(GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetIntegerv )(GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +extern void ( APIENTRY * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +extern void ( APIENTRY * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +extern void ( APIENTRY * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetPixelMapfv )(GLenum map, GLfloat *values); +extern void ( APIENTRY * qglGetPixelMapuiv )(GLenum map, GLuint *values); +extern void ( APIENTRY * qglGetPixelMapusv )(GLenum map, GLushort *values); +extern void ( APIENTRY * qglGetPointerv )(GLenum pname, GLvoid* *params); +extern void ( APIENTRY * qglGetPolygonStipple )(GLubyte *mask); +extern const GLubyte * ( APIENTRY * qglGetString )(GLenum name); +extern void ( APIENTRY * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +extern void ( APIENTRY * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +extern void ( APIENTRY * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +extern void ( APIENTRY * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( APIENTRY * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +extern void ( APIENTRY * qglHint )(GLenum target, GLenum mode); +extern void ( APIENTRY * qglIndexMask )(GLuint mask); +extern void ( APIENTRY * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglIndexd )(GLdouble c); +extern void ( APIENTRY * qglIndexdv )(const GLdouble *c); +extern void ( APIENTRY * qglIndexf )(GLfloat c); +extern void ( APIENTRY * qglIndexfv )(const GLfloat *c); +extern void ( APIENTRY * qglIndexi )(GLint c); +extern void ( APIENTRY * qglIndexiv )(const GLint *c); +extern void ( APIENTRY * qglIndexs )(GLshort c); +extern void ( APIENTRY * qglIndexsv )(const GLshort *c); +extern void ( APIENTRY * qglIndexub )(GLubyte c); +extern void ( APIENTRY * qglIndexubv )(const GLubyte *c); +extern void ( APIENTRY * qglInitNames )(void); +extern void ( APIENTRY * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +extern GLboolean ( APIENTRY * qglIsEnabled )(GLenum cap); +extern GLboolean ( APIENTRY * qglIsList )(GLuint l); +extern GLboolean ( APIENTRY * qglIsTexture )(GLuint texture); +extern void ( APIENTRY * qglLightModelf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglLightModelfv )(GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglLightModeli )(GLenum pname, GLint param); +extern void ( APIENTRY * qglLightModeliv )(GLenum pname, const GLint *params); +extern void ( APIENTRY * qglLightf )(GLenum light, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglLighti )(GLenum light, GLenum pname, GLint param); +extern void ( APIENTRY * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglLineStipple )(GLint factor, GLushort pattern); +extern void ( APIENTRY * qglLineWidth )(GLfloat width); +extern void ( APIENTRY * qglListBase )(GLuint base); +extern void ( APIENTRY * qglLoadIdentity )(void); +extern void ( APIENTRY * qglLoadMatrixd )(const GLdouble *m); +extern void ( APIENTRY * qglLoadMatrixf )(const GLfloat *m); +extern void ( APIENTRY * qglLoadName )(GLuint name); +extern void ( APIENTRY * qglLogicOp )(GLenum opcode); +extern void ( APIENTRY * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +extern void ( APIENTRY * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +extern void ( APIENTRY * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +extern void ( APIENTRY * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +extern void ( APIENTRY * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +extern void ( APIENTRY * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +extern void ( APIENTRY * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +extern void ( APIENTRY * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +extern void ( APIENTRY * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglMateriali )(GLenum face, GLenum pname, GLint param); +extern void ( APIENTRY * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglMatrixMode )(GLenum mode); +extern void ( APIENTRY * qglMultMatrixd )(const GLdouble *m); +extern void ( APIENTRY * qglMultMatrixf )(const GLfloat *m); +extern void ( APIENTRY * qglNewList )(GLuint list, GLenum mode); +extern void ( APIENTRY * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +extern void ( APIENTRY * qglNormal3bv )(const GLbyte *v); +extern void ( APIENTRY * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +extern void ( APIENTRY * qglNormal3dv )(const GLdouble *v); +extern void ( APIENTRY * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +extern void ( APIENTRY * qglNormal3fv )(const GLfloat *v); +extern void ( APIENTRY * qglNormal3i )(GLint nx, GLint ny, GLint nz); +extern void ( APIENTRY * qglNormal3iv )(const GLint *v); +extern void ( APIENTRY * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +extern void ( APIENTRY * qglNormal3sv )(const GLshort *v); +extern void ( APIENTRY * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern void ( APIENTRY * qglPassThrough )(GLfloat token); +extern void ( APIENTRY * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +extern void ( APIENTRY * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +extern void ( APIENTRY * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +extern void ( APIENTRY * qglPixelStoref )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglPixelStorei )(GLenum pname, GLint param); +extern void ( APIENTRY * qglPixelTransferf )(GLenum pname, GLfloat param); +extern void ( APIENTRY * qglPixelTransferi )(GLenum pname, GLint param); +extern void ( APIENTRY * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +extern void ( APIENTRY * qglPointSize )(GLfloat size); +extern void ( APIENTRY * qglPolygonMode )(GLenum face, GLenum mode); +extern void ( APIENTRY * qglPolygonOffset )(GLfloat factor, GLfloat units); +extern void ( APIENTRY * qglPolygonStipple )(const GLubyte *mask); +extern void ( APIENTRY * qglPopAttrib )(void); +extern void ( APIENTRY * qglPopClientAttrib )(void); +extern void ( APIENTRY * qglPopMatrix )(void); +extern void ( APIENTRY * qglPopName )(void); +extern void ( APIENTRY * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +extern void ( APIENTRY * qglPushAttrib )(GLbitfield mask); +extern void ( APIENTRY * qglPushClientAttrib )(GLbitfield mask); +extern void ( APIENTRY * qglPushMatrix )(void); +extern void ( APIENTRY * qglPushName )(GLuint name); +extern void ( APIENTRY * qglRasterPos2d )(GLdouble x, GLdouble y); +extern void ( APIENTRY * qglRasterPos2dv )(const GLdouble *v); +extern void ( APIENTRY * qglRasterPos2f )(GLfloat x, GLfloat y); +extern void ( APIENTRY * qglRasterPos2fv )(const GLfloat *v); +extern void ( APIENTRY * qglRasterPos2i )(GLint x, GLint y); +extern void ( APIENTRY * qglRasterPos2iv )(const GLint *v); +extern void ( APIENTRY * qglRasterPos2s )(GLshort x, GLshort y); +extern void ( APIENTRY * qglRasterPos2sv )(const GLshort *v); +extern void ( APIENTRY * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglRasterPos3dv )(const GLdouble *v); +extern void ( APIENTRY * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglRasterPos3fv )(const GLfloat *v); +extern void ( APIENTRY * qglRasterPos3i )(GLint x, GLint y, GLint z); +extern void ( APIENTRY * qglRasterPos3iv )(const GLint *v); +extern void ( APIENTRY * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +extern void ( APIENTRY * qglRasterPos3sv )(const GLshort *v); +extern void ( APIENTRY * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( APIENTRY * qglRasterPos4dv )(const GLdouble *v); +extern void ( APIENTRY * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( APIENTRY * qglRasterPos4fv )(const GLfloat *v); +extern void ( APIENTRY * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( APIENTRY * qglRasterPos4iv )(const GLint *v); +extern void ( APIENTRY * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( APIENTRY * qglRasterPos4sv )(const GLshort *v); +extern void ( APIENTRY * qglReadBuffer )(GLenum mode); +extern void ( APIENTRY * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +extern void ( APIENTRY * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +extern void ( APIENTRY * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +extern void ( APIENTRY * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +extern void ( APIENTRY * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +extern void ( APIENTRY * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +extern void ( APIENTRY * qglRectiv )(const GLint *v1, const GLint *v2); +extern void ( APIENTRY * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +extern void ( APIENTRY * qglRectsv )(const GLshort *v1, const GLshort *v2); +extern GLint ( APIENTRY * qglRenderMode )(GLenum mode); +extern void ( APIENTRY * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( APIENTRY * qglSelectBuffer )(GLsizei size, GLuint *buffer); +extern void ( APIENTRY * qglShadeModel )(GLenum mode); +extern void ( APIENTRY * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +extern void ( APIENTRY * qglStencilMask )(GLuint mask); +extern void ( APIENTRY * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +extern void ( APIENTRY * qglTexCoord1d )(GLdouble s); +extern void ( APIENTRY * qglTexCoord1dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord1f )(GLfloat s); +extern void ( APIENTRY * qglTexCoord1fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord1i )(GLint s); +extern void ( APIENTRY * qglTexCoord1iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord1s )(GLshort s); +extern void ( APIENTRY * qglTexCoord1sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoord2d )(GLdouble s, GLdouble t); +extern void ( APIENTRY * qglTexCoord2dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord2f )(GLfloat s, GLfloat t); +extern void ( APIENTRY * qglTexCoord2fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord2i )(GLint s, GLint t); +extern void ( APIENTRY * qglTexCoord2iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord2s )(GLshort s, GLshort t); +extern void ( APIENTRY * qglTexCoord2sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +extern void ( APIENTRY * qglTexCoord3dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +extern void ( APIENTRY * qglTexCoord3fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord3i )(GLint s, GLint t, GLint r); +extern void ( APIENTRY * qglTexCoord3iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +extern void ( APIENTRY * qglTexCoord3sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +extern void ( APIENTRY * qglTexCoord4dv )(const GLdouble *v); +extern void ( APIENTRY * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void ( APIENTRY * qglTexCoord4fv )(const GLfloat *v); +extern void ( APIENTRY * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +extern void ( APIENTRY * qglTexCoord4iv )(const GLint *v); +extern void ( APIENTRY * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +extern void ( APIENTRY * qglTexCoord4sv )(const GLshort *v); +extern void ( APIENTRY * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +extern void ( APIENTRY * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +extern void ( APIENTRY * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +extern void ( APIENTRY * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( APIENTRY * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +extern void ( APIENTRY * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +extern void ( APIENTRY * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( APIENTRY * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglVertex2d )(GLdouble x, GLdouble y); +extern void ( APIENTRY * qglVertex2dv )(const GLdouble *v); +extern void ( APIENTRY * qglVertex2f )(GLfloat x, GLfloat y); +extern void ( APIENTRY * qglVertex2fv )(const GLfloat *v); +extern void ( APIENTRY * qglVertex2i )(GLint x, GLint y); +extern void ( APIENTRY * qglVertex2iv )(const GLint *v); +extern void ( APIENTRY * qglVertex2s )(GLshort x, GLshort y); +extern void ( APIENTRY * qglVertex2sv )(const GLshort *v); +extern void ( APIENTRY * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( APIENTRY * qglVertex3dv )(const GLdouble *v); +extern void ( APIENTRY * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( APIENTRY * qglVertex3fv )(const GLfloat *v); +extern void ( APIENTRY * qglVertex3i )(GLint x, GLint y, GLint z); +extern void ( APIENTRY * qglVertex3iv )(const GLint *v); +extern void ( APIENTRY * qglVertex3s )(GLshort x, GLshort y, GLshort z); +extern void ( APIENTRY * qglVertex3sv )(const GLshort *v); +extern void ( APIENTRY * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( APIENTRY * qglVertex4dv )(const GLdouble *v); +extern void ( APIENTRY * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( APIENTRY * qglVertex4fv )(const GLfloat *v); +extern void ( APIENTRY * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( APIENTRY * qglVertex4iv )(const GLint *v); +extern void ( APIENTRY * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( APIENTRY * qglVertex4sv )(const GLshort *v); +extern void ( APIENTRY * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( APIENTRY * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +#if defined( _WIN32 ) + +extern BOOL ( WINAPI * qwglCopyContext)(HGLRC, HGLRC, UINT); +extern HGLRC ( WINAPI * qwglCreateContext)(HDC); +extern HGLRC ( WINAPI * qwglCreateLayerContext)(HDC, int); +extern BOOL ( WINAPI * qwglDeleteContext)(HGLRC); +extern HGLRC ( WINAPI * qwglGetCurrentContext)(VOID); +extern HDC ( WINAPI * qwglGetCurrentDC)(VOID); +extern PROC ( WINAPI * qwglGetProcAddress)(LPCSTR); +extern BOOL ( WINAPI * qwglMakeCurrent)(HDC, HGLRC); +extern BOOL ( WINAPI * qwglShareLists)(HGLRC, HGLRC); +extern BOOL ( WINAPI * qwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); + +extern BOOL ( WINAPI * qwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, + FLOAT, int, LPGLYPHMETRICSFLOAT); + +extern BOOL ( WINAPI * qwglDescribeLayerPlane)(HDC, int, int, UINT, + LPLAYERPLANEDESCRIPTOR); +extern int ( WINAPI * qwglSetLayerPaletteEntries)(HDC, int, int, int, + CONST COLORREF *); +extern int ( WINAPI * qwglGetLayerPaletteEntries)(HDC, int, int, int, + COLORREF *); +extern BOOL ( WINAPI * qwglRealizeLayerPalette)(HDC, int, BOOL); +extern BOOL ( WINAPI * qwglSwapLayerBuffers)(HDC, UINT); + +extern BOOL ( WINAPI * qwglSwapIntervalEXT)( int interval ); + +#endif // _WIN32 + +#if defined( __linux__ ) + +//FX Mesa Functions +extern fxMesaContext (*qfxMesaCreateContext)(GLuint win, GrScreenResolution_t, GrScreenRefresh_t, const GLint attribList[]); +extern fxMesaContext (*qfxMesaCreateBestContext)(GLuint win, GLint width, GLint height, const GLint attribList[]); +extern void (*qfxMesaDestroyContext)(fxMesaContext ctx); +extern void (*qfxMesaMakeCurrent)(fxMesaContext ctx); +extern fxMesaContext (*qfxMesaGetCurrentContext)(void); +extern void (*qfxMesaSwapBuffers)(void); + +//GLX Functions +extern XVisualInfo * (*qglXChooseVisual)( Display *dpy, int screen, int *attribList ); +extern GLXContext (*qglXCreateContext)( Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct ); +extern void (*qglXDestroyContext)( Display *dpy, GLXContext ctx ); +extern Bool (*qglXMakeCurrent)( Display *dpy, GLXDrawable drawable, GLXContext ctx); +extern void (*qglXCopyContext)( Display *dpy, GLXContext src, GLXContext dst, GLuint mask ); +extern void (*qglXSwapBuffers)( Display *dpy, GLXDrawable drawable ); + +#endif // __linux__ + +#endif // _WIN32 && __linux__ + +#endif diff --git a/code/renderer/qgl_console.h b/code/renderer/qgl_console.h new file mode 100644 index 0000000..561ff81 --- /dev/null +++ b/code/renderer/qgl_console.h @@ -0,0 +1,1207 @@ +/* +** QGL.H +*/ + +#ifndef __QGL_H__ +#define __QGL_H__ + +#ifdef _WINDOWS +#include +#include +#endif + +#ifdef _XBOX + +#include +#endif + +#ifdef _GAMECUBE +#include +#include +#endif + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef int GLsizei; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned int GLuint; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef void GLvoid; + +#undef APIENTRY +#define APIENTRY + +#define GL_ACCUM 0x0100 +#define GL_LOAD 0x0101 +#define GL_RETURN 0x0102 +#define GL_MULT 0x0103 +#define GL_ADD 0x0104 + +/* AlphaFunction */ +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 + +/* AttribMask */ +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000fffff + +/* BeginMode */ +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 + +/* BlendingFactorDest */ +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 + +/* BlendingFactorSrc */ +/* GL_ZERO */ +/* GL_ONE */ +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 + +/* Boolean */ +#define GL_TRUE 1 +#define GL_FALSE 0 + +/* ClipPlaneName */ +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 + +/* DataType */ +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 +#define GL_DOUBLE 0x140A + +/* DrawBufferMode */ +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C + +/* ErrorCode */ +#define GL_NO_ERROR 0 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +/* FeedBackMode */ +#define GL_2D 0x0600 +#define GL_3D 0x0601 +#define GL_3D_COLOR 0x0602 +#define GL_3D_COLOR_TEXTURE 0x0603 +#define GL_4D_COLOR_TEXTURE 0x0604 + +/* FeedBackToken */ +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_LINE_RESET_TOKEN 0x0707 + +/* FogMode */ +/* GL_LINEAR */ +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 + +/* FrontFaceDirection */ +#define GL_CW 0x0900 +#define GL_CCW 0x0901 + +/* GetMapTarget */ +#define GL_COEFF 0x0A00 +#define GL_ORDER 0x0A01 +#define GL_DOMAIN 0x0A02 + +/* GetTarget */ +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_POINT_SMOOTH 0x0B10 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_RANGE 0x0B12 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_RANGE 0x0B22 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LIST_MODE 0x0B30 +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_EDGE_FLAG 0x0B43 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_LIGHTING 0x0B50 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_SHADE_MODEL 0x0B54 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_NORMALIZE 0x0BA1 +#define GL_VIEWPORT 0x0BA2 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_FUNC 0x0BC1 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_DITHER 0x0BD0 +#define GL_BLEND_DST 0x0BE0 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND 0x0BE2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_READ_BUFFER 0x0C02 +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_INDEX_MODE 0x0C30 +#define GL_RGBA_MODE 0x0C31 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_RENDER_MODE 0x0C40 +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#define GL_PIXEL_MAP_I_TO_R 0x0C72 +#define GL_PIXEL_MAP_I_TO_G 0x0C73 +#define GL_PIXEL_MAP_I_TO_B 0x0C74 +#define GL_PIXEL_MAP_I_TO_A 0x0C75 +#define GL_PIXEL_MAP_R_TO_R 0x0C76 +#define GL_PIXEL_MAP_G_TO_G 0x0C77 +#define GL_PIXEL_MAP_B_TO_B 0x0C78 +#define GL_PIXEL_MAP_A_TO_A 0x0C79 +#define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 +#define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 +#define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 +#define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 +#define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 +#define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 +#define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 +#define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_MAP_COLOR 0x0D10 +#define GL_MAP_STENCIL 0x0D11 +#define GL_INDEX_SHIFT 0x0D12 +#define GL_INDEX_OFFSET 0x0D13 +#define GL_RED_SCALE 0x0D14 +#define GL_RED_BIAS 0x0D15 +#define GL_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 +#define GL_GREEN_SCALE 0x0D18 +#define GL_GREEN_BIAS 0x0D19 +#define GL_BLUE_SCALE 0x0D1A +#define GL_BLUE_BIAS 0x0D1B +#define GL_ALPHA_SCALE 0x0D1C +#define GL_ALPHA_BIAS 0x0D1D +#define GL_DEPTH_SCALE 0x0D1E +#define GL_DEPTH_BIAS 0x0D1F +#define GL_MAX_EVAL_ORDER 0x0D30 +#define GL_MAX_LIGHTS 0x0D31 +#define GL_MAX_CLIP_PLANES 0x0D32 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_PIXEL_MAP_TABLE 0x0D34 +#define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 +#define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 +#define GL_MAX_NAME_STACK_DEPTH 0x0D37 +#define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 +#define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_INDEX_BITS 0x0D51 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_NAME_STACK_DEPTH 0x0D70 +#define GL_AUTO_NORMAL 0x0D80 +#define GL_MAP1_COLOR_4 0x0D90 +#define GL_MAP1_INDEX 0x0D91 +#define GL_MAP1_NORMAL 0x0D92 +#define GL_MAP1_TEXTURE_COORD_1 0x0D93 +#define GL_MAP1_TEXTURE_COORD_2 0x0D94 +#define GL_MAP1_TEXTURE_COORD_3 0x0D95 +#define GL_MAP1_TEXTURE_COORD_4 0x0D96 +#define GL_MAP1_VERTEX_3 0x0D97 +#define GL_MAP1_VERTEX_4 0x0D98 +#define GL_MAP2_COLOR_4 0x0DB0 +#define GL_MAP2_INDEX 0x0DB1 +#define GL_MAP2_NORMAL 0x0DB2 +#define GL_MAP2_TEXTURE_COORD_1 0x0DB3 +#define GL_MAP2_TEXTURE_COORD_2 0x0DB4 +#define GL_MAP2_TEXTURE_COORD_3 0x0DB5 +#define GL_MAP2_TEXTURE_COORD_4 0x0DB6 +#define GL_MAP2_VERTEX_3 0x0DB7 +#define GL_MAP2_VERTEX_4 0x0DB8 +#define GL_MAP1_GRID_DOMAIN 0x0DD0 +#define GL_MAP1_GRID_SEGMENTS 0x0DD1 +#define GL_MAP2_GRID_DOMAIN 0x0DD2 +#define GL_MAP2_GRID_SEGMENTS 0x0DD3 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 + +/* GetTextureParameter */ +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_BORDER 0x1005 + +/* HintMode */ +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +/* LightName */ +#define GL_LIGHT0 0x4000 +#define GL_LIGHT1 0x4001 +#define GL_LIGHT2 0x4002 +#define GL_LIGHT3 0x4003 +#define GL_LIGHT4 0x4004 +#define GL_LIGHT5 0x4005 +#define GL_LIGHT6 0x4006 +#define GL_LIGHT7 0x4007 + +/* LightParameter */ +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#define GL_SPOT_EXPONENT 0x1205 +#define GL_SPOT_CUTOFF 0x1206 +#define GL_CONSTANT_ATTENUATION 0x1207 +#define GL_LINEAR_ATTENUATION 0x1208 +#define GL_QUADRATIC_ATTENUATION 0x1209 + +/* ListMode */ +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 + +/* LogicOp */ +#define GL_CLEAR 0x1500 +#define GL_AND 0x1501 +#define GL_AND_REVERSE 0x1502 +#define GL_COPY 0x1503 +#define GL_AND_INVERTED 0x1504 +#define GL_NOOP 0x1505 +#define GL_XOR 0x1506 +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_EQUIV 0x1509 +#define GL_INVERT 0x150A +#define GL_OR_REVERSE 0x150B +#define GL_COPY_INVERTED 0x150C +#define GL_OR_INVERTED 0x150D +#define GL_NAND 0x150E +#define GL_SET 0x150F + +/* MaterialParameter */ +#define GL_EMISSION 0x1600 +#define GL_SHININESS 0x1601 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 + +/* MatrixMode */ +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE0 0x1702 +#define GL_TEXTURE1 0x1703 +#define GL_TEXTURE2 0x1704 +#define GL_TEXTURE3 0x1705 + +/* PixelCopyType */ +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 + +/* PixelFormat */ +#define GL_COLOR_INDEX 0x1900 +#define GL_STENCIL_INDEX 0x1901 +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A + +/* PixelType */ +#define GL_BITMAP 0x1A00 + +/* PolygonMode */ +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 + +/* RenderingMode */ +#define GL_RENDER 0x1C00 +#define GL_FEEDBACK 0x1C01 +#define GL_SELECT 0x1C02 + +/* ShadingModel */ +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 + +/* StencilOp */ +/* GL_ZERO */ +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 +/* GL_INVERT */ + +/* StringName */ +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +/* TextureCoordName */ +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 + +/* TextureEnvMode */ +#define GL_MODULATE 0x2100 +#define GL_DECAL 0x2101 + +/* TextureEnvParameter */ +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 + +/* TextureEnvTarget */ +#define GL_TEXTURE_ENV 0x2300 + +/* TextureGenMode */ +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 + +/* TextureGenParameter */ +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 + +/* TextureMagFilter */ +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 + +/* TextureMinFilter */ +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 + +/* TextureParameterName */ +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 + +// PORT: Anisotropy stuff +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + +//PORT - TPL stuff +#define GL_TPL4_EXT 0x9991 +#define GL_TPL8_EXT 0x9992 +#define GL_TPL16_EXT 0x9993 +#define GL_TPL32_EXT 0x9994 + +// PORT: DDS Stuff +#define GL_DDS_RGBA_EXT 0x9998 +#define GL_RGB_SWIZZLE_EXT 0x9999 + +/* TextureWrapMode */ +#define GL_CLAMP 0x2900 +#define GL_REPEAT 0x2901 + +/* ClientAttribMask */ +#define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 +#define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 +#define GL_CLIENT_ALL_ATTRIB_BITS 0xffffffff + +/* polygon_offset */ +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + +/* texture */ +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 + +/* texture_object */ +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 + +/* vertex_array */ +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 +#define GL_VERTEX_ARRAY_SIZE 0x807A +#define GL_VERTEX_ARRAY_TYPE 0x807B +#define GL_VERTEX_ARRAY_STRIDE 0x807C +#define GL_NORMAL_ARRAY_TYPE 0x807E +#define GL_NORMAL_ARRAY_STRIDE 0x807F +#define GL_COLOR_ARRAY_SIZE 0x8081 +#define GL_COLOR_ARRAY_TYPE 0x8082 +#define GL_COLOR_ARRAY_STRIDE 0x8083 +#define GL_INDEX_ARRAY_TYPE 0x8085 +#define GL_INDEX_ARRAY_STRIDE 0x8086 +#define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GL_VERTEX_ARRAY_POINTER 0x808E +#define GL_NORMAL_ARRAY_POINTER 0x808F +#define GL_COLOR_ARRAY_POINTER 0x8090 +#define GL_INDEX_ARRAY_POINTER 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GL_V2F 0x2A20 +#define GL_V3F 0x2A21 +#define GL_C4UB_V2F 0x2A22 +#define GL_C4UB_V3F 0x2A23 +#define GL_C3F_V3F 0x2A24 +#define GL_N3F_V3F 0x2A25 +#define GL_C4F_N3F_V3F 0x2A26 +#define GL_T2F_V3F 0x2A27 +#define GL_T4F_V4F 0x2A28 +#define GL_T2F_C4UB_V3F 0x2A29 +#define GL_T2F_C3F_V3F 0x2A2A +#define GL_T2F_N3F_V3F 0x2A2B +#define GL_T2F_C4F_N3F_V3F 0x2A2C +#define GL_T4F_C4F_N3F_V4F 0x2A2D + +/* Extensions */ +#define GL_EXT_vertex_array 1 +#define GL_EXT_bgra 1 +#define GL_EXT_paletted_texture 1 + +/* EXT_vertex_array */ +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#define GL_DOUBLE_EXT GL_DOUBLE + +/* EXT_bgra */ +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 + +/* EXT_paletted_texture */ + +/* These must match the GL_COLOR_TABLE_*_SGI enumerants */ +#define GL_COLOR_TABLE_FORMAT_EXT 0x80D8 +#define GL_COLOR_TABLE_WIDTH_EXT 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_EXT 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_EXT 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_EXT 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_EXT 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_EXT 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_EXT 0x80DF + +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 + +// VVFIXME New Constants from Jedi +#define GL_VSYNC 0x813F +#define GL_DDS_RGB16_EXT 0x9997 +#define GL_DDS_RGBA32_EXT 0x9998 +#define GL_RGB_SWIZZLE_EXT 0x9999 + +// VVFIXME - New constants for linear format textures. +// These numbers are just made up. This is awful. +#define GL_LIN_RGBA8 0x8E01 +#define GL_LIN_RGBA 0x8E02 +#define GL_LIN_RGB8 0x8E03 +#define GL_LIN_RGB 0x8E04 + + +//=========================================================================== + +/* +** multitexture extension definitions +*/ +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_ACTIVE_TEXTURES_ARB 0x84E2 + +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 + +typedef void ( * PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void ( * PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void ( * PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void ( * PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void ( * PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void ( * PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void ( * PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void ( * PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void ( * PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void ( * PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void ( * PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void ( * PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void ( * PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void ( * PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void ( * PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void ( * PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void ( * PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void ( * PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void ( * PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void ( * PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void ( * PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void ( * PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void ( * PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void ( * PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void ( * PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void ( * PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void ( * PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void ( * PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void ( * PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void ( * PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void ( * PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void ( * PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +typedef void ( * PFNGLACTIVETEXTUREARBPROC) (GLenum target); +typedef void ( * PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum target); + +/* +** extension constants +*/ +extern void ( * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +extern void ( * qglActiveTextureARB )( GLenum texture ); +extern void ( * qglClientActiveTextureARB )( GLenum texture ); + +extern void ( * qglLockArraysEXT) (GLint, GLint); +extern void ( * qglUnlockArraysEXT) (void); + +//----(SA) from Raven +extern void ( * qglPointParameterfEXT)( GLenum, GLfloat); +extern void ( * qglPointParameterfvEXT)( GLenum, GLfloat *); +//----(SA) end + + + +// S3TC compression constants +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +// More, grabbed from wolf code PORT +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + +// And more, also from old wolf code: +// GR - update enumerants +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 + +extern void ( * qglPNTrianglesiATI)(GLenum pname, GLint param); +extern void ( * qglPNTrianglesfATI)(GLenum pname, GLfloat param); + +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C + +//=========================================================================== + + +extern void ( * qglAccum )(GLenum op, GLfloat value); +extern void ( * qglAlphaFunc )(GLenum func, GLclampf ref); +extern GLboolean ( * qglAreTexturesResident )(GLsizei n, const GLuint *textures, GLboolean *residences); +extern void ( * qglArrayElement )(GLint i); +extern void ( * qglBegin )(GLenum mode); +extern void ( * qglBeginEXT )(GLenum mode, GLint verts, GLint colors, GLint normals, GLint tex0, GLint tex1);//, GLint tex2, GLint tex3); +extern GLboolean ( * qglBeginFrame )(void); +extern void ( * qglBeginShadow )(void); +extern void ( * qglBindTexture )(GLenum target, GLuint texture); +extern void ( * qglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +extern void ( * qglBlendFunc )(GLenum sfactor, GLenum dfactor); +extern void ( * qglCallList )(GLuint list); +extern void ( * qglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +extern void ( * qglClear )(GLbitfield mask); +extern void ( * qglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( * qglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +extern void ( * qglClearDepth )(GLclampd depth); +extern void ( * qglClearIndex )(GLfloat c); +extern void ( * qglClearStencil )(GLint s); +extern void ( * qglClipPlane )(GLenum plane, const GLdouble *equation); +extern void ( * qglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +extern void ( * qglColor3bv )(const GLbyte *v); +extern void ( * qglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +extern void ( * qglColor3dv )(const GLdouble *v); +extern void ( * qglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +extern void ( * qglColor3fv )(const GLfloat *v); +extern void ( * qglColor3i )(GLint red, GLint green, GLint blue); +extern void ( * qglColor3iv )(const GLint *v); +extern void ( * qglColor3s )(GLshort red, GLshort green, GLshort blue); +extern void ( * qglColor3sv )(const GLshort *v); +extern void ( * qglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +extern void ( * qglColor3ubv )(const GLubyte *v); +extern void ( * qglColor3ui )(GLuint red, GLuint green, GLuint blue); +extern void ( * qglColor3uiv )(const GLuint *v); +extern void ( * qglColor3us )(GLushort red, GLushort green, GLushort blue); +extern void ( * qglColor3usv )(const GLushort *v); +extern void ( * qglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +extern void ( * qglColor4bv )(const GLbyte *v); +extern void ( * qglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +extern void ( * qglColor4dv )(const GLdouble *v); +extern void ( * qglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +extern void ( * qglColor4fv )(const GLfloat *v); +extern void ( * qglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +extern void ( * qglColor4iv )(const GLint *v); +extern void ( * qglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +extern void ( * qglColor4sv )(const GLshort *v); +extern void ( * qglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +extern void ( * qglColor4ubv )(const GLubyte *v); +extern void ( * qglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +extern void ( * qglColor4uiv )(const GLuint *v); +extern void ( * qglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +extern void ( * qglColor4usv )(const GLushort *v); +extern void ( * qglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +extern void ( * qglColorMaterial )(GLenum face, GLenum mode); +extern void ( * qglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( * qglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +extern void ( * qglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +extern void ( * qglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +extern void ( * qglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +extern void ( * qglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( * qglCullFace )(GLenum mode); +extern void ( * qglDeleteLists )(GLuint list, GLsizei range); +extern void ( * qglDeleteTextures )(GLsizei n, const GLuint *textures); +extern void ( * qglDepthFunc )(GLenum func); +extern void ( * qglDepthMask )(GLboolean flag); +extern void ( * qglDepthRange )(GLclampd zNear, GLclampd zFar); +extern void ( * qglDisable )(GLenum cap); +extern void ( * qglDisableClientState )(GLenum array); +extern void ( * qglDrawArrays )(GLenum mode, GLint first, GLsizei count); +extern void ( * qglDrawBuffer )(GLenum mode); +extern void ( * qglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +extern void ( * qglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglEdgeFlag )(GLboolean flag); +extern void ( * qglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +extern void ( * qglEdgeFlagv )(const GLboolean *flag); +extern void ( * qglEnable )(GLenum cap); +extern void ( * qglEnableClientState )(GLenum array); +extern void ( * qglEnd )(void); +extern void ( * qglEndFrame )(void); +extern void ( * qglEndShadow )(void); +extern void ( * qglEndList )(void); +extern void ( * qglEvalCoord1d )(GLdouble u); +extern void ( * qglEvalCoord1dv )(const GLdouble *u); +extern void ( * qglEvalCoord1f )(GLfloat u); +extern void ( * qglEvalCoord1fv )(const GLfloat *u); +extern void ( * qglEvalCoord2d )(GLdouble u, GLdouble v); +extern void ( * qglEvalCoord2dv )(const GLdouble *u); +extern void ( * qglEvalCoord2f )(GLfloat u, GLfloat v); +extern void ( * qglEvalCoord2fv )(const GLfloat *u); +extern void ( * qglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +extern void ( * qglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +extern void ( * qglEvalPoint1 )(GLint i); +extern void ( * qglEvalPoint2 )(GLint i, GLint j); +extern void ( * qglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +extern void ( * qglFinish )(void); +extern void ( * qglFlush )(void); +extern void ( * qglFlushShadow )(void); +extern void ( * qglFogf )(GLenum pname, GLfloat param); +extern void ( * qglFogfv )(GLenum pname, const GLfloat *params); +extern void ( * qglFogi )(GLenum pname, GLint param); +extern void ( * qglFogiv )(GLenum pname, const GLint *params); +extern void ( * qglFrontFace )(GLenum mode); +extern void ( * qglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern GLuint ( * qglGenLists )(GLsizei range); +extern void ( * qglGenTextures )(GLsizei n, GLuint *textures); +extern void ( * qglGetBooleanv )(GLenum pname, GLboolean *params); +extern void ( * qglGetClipPlane )(GLenum plane, GLdouble *equation); +extern void ( * qglGetDoublev )(GLenum pname, GLdouble *params); +extern GLenum ( * qglGetError )(void); +extern void ( * qglGetFloatv )(GLenum pname, GLfloat *params); +extern void ( * qglGetIntegerv )(GLenum pname, GLint *params); +extern void ( * qglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +extern void ( * qglGetLightiv )(GLenum light, GLenum pname, GLint *params); +extern void ( * qglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +extern void ( * qglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +extern void ( * qglGetMapiv )(GLenum target, GLenum query, GLint *v); +extern void ( * qglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +extern void ( * qglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +extern void ( * qglGetPixelMapfv )(GLenum map, GLfloat *values); +extern void ( * qglGetPixelMapuiv )(GLenum map, GLuint *values); +extern void ( * qglGetPixelMapusv )(GLenum map, GLushort *values); +extern void ( * qglGetPointerv )(GLenum pname, GLvoid* *params); +extern void ( * qglGetPolygonStipple )(GLubyte *mask); +extern const GLubyte * ( * qglGetString )(GLenum name); +extern void ( * qglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( * qglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +extern void ( * qglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +extern void ( * qglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +extern void ( * qglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +extern void ( * qglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +extern void ( * qglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +extern void ( * qglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +extern void ( * qglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +extern void ( * qglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +extern void ( * qglHint )(GLenum target, GLenum mode); +extern void ( * qglIndexedTriToStrip )(GLsizei count, const GLushort *indices); +extern void ( * qglIndexMask )(GLuint mask); +extern void ( * qglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( * qglIndexd )(GLdouble c); +extern void ( * qglIndexdv )(const GLdouble *c); +extern void ( * qglIndexf )(GLfloat c); +extern void ( * qglIndexfv )(const GLfloat *c); +extern void ( * qglIndexi )(GLint c); +extern void ( * qglIndexiv )(const GLint *c); +extern void ( * qglIndexs )(GLshort c); +extern void ( * qglIndexsv )(const GLshort *c); +extern void ( * qglIndexub )(GLubyte c); +extern void ( * qglIndexubv )(const GLubyte *c); +extern void ( * qglInitNames )(void); +extern void ( * qglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +extern GLboolean ( * qglIsEnabled )(GLenum cap); +extern GLboolean ( * qglIsList )(GLuint listArg); +extern GLboolean ( * qglIsTexture )(GLuint texture); +extern void ( * qglLightModelf )(GLenum pname, GLfloat param); +extern void ( * qglLightModelfv )(GLenum pname, const GLfloat *params); +extern void ( * qglLightModeli )(GLenum pname, GLint param); +extern void ( * qglLightModeliv )(GLenum pname, const GLint *params); +extern void ( * qglLightf )(GLenum light, GLenum pname, GLfloat param); +extern void ( * qglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +extern void ( * qglLighti )(GLenum light, GLenum pname, GLint param); +extern void ( * qglLightiv )(GLenum light, GLenum pname, const GLint *params); +extern void ( * qglLineStipple )(GLint factor, GLushort pattern); +extern void ( * qglLineWidth )(GLfloat width); +extern void ( * qglListBase )(GLuint base); +extern void ( * qglLoadIdentity )(void); +extern void ( * qglLoadMatrixd )(const GLdouble *m); +extern void ( * qglLoadMatrixf )(const GLfloat *m); +extern void ( * qglLoadName )(GLuint name); +extern void ( * qglLogicOp )(GLenum opcode); +extern void ( * qglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +extern void ( * qglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +extern void ( * qglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +extern void ( * qglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +extern void ( * qglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +extern void ( * qglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +extern void ( * qglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +extern void ( * qglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +extern void ( * qglMaterialf )(GLenum face, GLenum pname, GLfloat param); +extern void ( * qglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +extern void ( * qglMateriali )(GLenum face, GLenum pname, GLint param); +extern void ( * qglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +extern void ( * qglMatrixMode )(GLenum mode); +extern void ( * qglMultMatrixd )(const GLdouble *m); +extern void ( * qglMultMatrixf )(const GLfloat *m); +extern void ( * qglNewList )(GLuint list, GLenum mode); +extern void ( * qglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +extern void ( * qglNormal3bv )(const GLbyte *v); +extern void ( * qglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +extern void ( * qglNormal3dv )(const GLdouble *v); +extern void ( * qglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +extern void ( * qglNormal3fv )(const GLfloat *v); +extern void ( * qglNormal3i )(GLint nx, GLint ny, GLint nz); +extern void ( * qglNormal3iv )(const GLint *v); +extern void ( * qglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +extern void ( * qglNormal3sv )(const GLshort *v); +extern void ( * qglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( * qglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +extern void ( * qglPassThrough )(GLfloat token); +extern void ( * qglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +extern void ( * qglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +extern void ( * qglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +extern void ( * qglPixelStoref )(GLenum pname, GLfloat param); +extern void ( * qglPixelStorei )(GLenum pname, GLint param); +extern void ( * qglPixelTransferf )(GLenum pname, GLfloat param); +extern void ( * qglPixelTransferi )(GLenum pname, GLint param); +extern void ( * qglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +extern void ( * qglPointSize )(GLfloat size); +extern void ( * qglPolygonMode )(GLenum face, GLenum mode); +extern void ( * qglPolygonOffset )(GLfloat factor, GLfloat units); +extern void ( * qglPolygonStipple )(const GLubyte *mask); +extern void ( * qglPopAttrib )(void); +extern void ( * qglPopClientAttrib )(void); +extern void ( * qglPopMatrix )(void); +extern void ( * qglPopName )(void); +extern void ( * qglPrioritizeTextures )(GLsizei n, const GLuint *textures, const GLclampf *priorities); +extern void ( * qglPushAttrib )(GLbitfield mask); +extern void ( * qglPushClientAttrib )(GLbitfield mask); +extern void ( * qglPushMatrix )(void); +extern void ( * qglPushName )(GLuint name); +extern void ( * qglRasterPos2d )(GLdouble x, GLdouble y); +extern void ( * qglRasterPos2dv )(const GLdouble *v); +extern void ( * qglRasterPos2f )(GLfloat x, GLfloat y); +extern void ( * qglRasterPos2fv )(const GLfloat *v); +extern void ( * qglRasterPos2i )(GLint x, GLint y); +extern void ( * qglRasterPos2iv )(const GLint *v); +extern void ( * qglRasterPos2s )(GLshort x, GLshort y); +extern void ( * qglRasterPos2sv )(const GLshort *v); +extern void ( * qglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( * qglRasterPos3dv )(const GLdouble *v); +extern void ( * qglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( * qglRasterPos3fv )(const GLfloat *v); +extern void ( * qglRasterPos3i )(GLint x, GLint y, GLint z); +extern void ( * qglRasterPos3iv )(const GLint *v); +extern void ( * qglRasterPos3s )(GLshort x, GLshort y, GLshort z); +extern void ( * qglRasterPos3sv )(const GLshort *v); +extern void ( * qglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( * qglRasterPos4dv )(const GLdouble *v); +extern void ( * qglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( * qglRasterPos4fv )(const GLfloat *v); +extern void ( * qglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( * qglRasterPos4iv )(const GLint *v); +extern void ( * qglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( * qglRasterPos4sv )(const GLshort *v); +extern void ( * qglReadBuffer )(GLenum mode); +//extern void ( * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei twidth, GLsizei theight, GLvoid *pixels); +extern void ( * qglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +extern void ( * qglCopyBackBufferToTexEXT ) (float width, float height, float u1, float v1, float u2, float v2); +extern void ( * qglCopyBackBufferToTex ) (void); +extern void ( * qglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +extern void ( * qglRectdv )(const GLdouble *v1, const GLdouble *v2); +extern void ( * qglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +extern void ( * qglRectfv )(const GLfloat *v1, const GLfloat *v2); +extern void ( * qglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +extern void ( * qglRectiv )(const GLint *v1, const GLint *v2); +extern void ( * qglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +extern void ( * qglRectsv )(const GLshort *v1, const GLshort *v2); +extern GLint ( * qglRenderMode )(GLenum mode); +extern void ( * qglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +extern void ( * qglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +extern void ( * qglScaled )(GLdouble x, GLdouble y, GLdouble z); +extern void ( * qglScalef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( * qglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +extern void ( * qglSelectBuffer )(GLsizei size, GLuint *buffer); +extern void ( * qglShadeModel )(GLenum mode); +extern void ( * qglStencilFunc )(GLenum func, GLint ref, GLuint mask); +extern void ( * qglStencilMask )(GLuint mask); +extern void ( * qglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +extern void ( * qglTexCoord1d )(GLdouble s); +extern void ( * qglTexCoord1dv )(const GLdouble *v); +extern void ( * qglTexCoord1f )(GLfloat s); +extern void ( * qglTexCoord1fv )(const GLfloat *v); +extern void ( * qglTexCoord1i )(GLint s); +extern void ( * qglTexCoord1iv )(const GLint *v); +extern void ( * qglTexCoord1s )(GLshort s); +extern void ( * qglTexCoord1sv )(const GLshort *v); +extern void ( * qglTexCoord2d )(GLdouble s, GLdouble t); +extern void ( * qglTexCoord2dv )(const GLdouble *v); +extern void ( * qglTexCoord2f )(GLfloat s, GLfloat t); +extern void ( * qglTexCoord2fv )(const GLfloat *v); +extern void ( * qglTexCoord2i )(GLint s, GLint t); +extern void ( * qglTexCoord2iv )(const GLint *v); +extern void ( * qglTexCoord2s )(GLshort s, GLshort t); +extern void ( * qglTexCoord2sv )(const GLshort *v); +extern void ( * qglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +extern void ( * qglTexCoord3dv )(const GLdouble *v); +extern void ( * qglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +extern void ( * qglTexCoord3fv )(const GLfloat *v); +extern void ( * qglTexCoord3i )(GLint s, GLint t, GLint r); +extern void ( * qglTexCoord3iv )(const GLint *v); +extern void ( * qglTexCoord3s )(GLshort s, GLshort t, GLshort r); +extern void ( * qglTexCoord3sv )(const GLshort *v); +extern void ( * qglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +extern void ( * qglTexCoord4dv )(const GLdouble *v); +extern void ( * qglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +extern void ( * qglTexCoord4fv )(const GLfloat *v); +extern void ( * qglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +extern void ( * qglTexCoord4iv )(const GLint *v); +extern void ( * qglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +extern void ( * qglTexCoord4sv )(const GLshort *v); +extern void ( * qglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( * qglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +extern void ( * qglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( * qglTexEnvi )(GLenum target, GLenum pname, GLint param); +extern void ( * qglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +extern void ( * qglTexGend )(GLenum coord, GLenum pname, GLdouble param); +extern void ( * qglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +extern void ( * qglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +extern void ( * qglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +extern void ( * qglTexGeni )(GLenum coord, GLenum pname, GLint param); +extern void ( * qglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +extern void ( * qglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglTexImage2DEXT )(GLenum target, GLint level, GLint numlevels, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +extern void ( * qglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +extern void ( * qglTexParameteri )(GLenum target, GLenum pname, GLint param); +extern void ( * qglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +extern void ( * qglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +extern void ( * qglTranslated )(GLdouble x, GLdouble y, GLdouble z); +extern void ( * qglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +extern void ( * qglVertex2d )(GLdouble x, GLdouble y); +extern void ( * qglVertex2dv )(const GLdouble *v); +extern void ( * qglVertex2f )(GLfloat x, GLfloat y); +extern void ( * qglVertex2fv )(const GLfloat *v); +extern void ( * qglVertex2i )(GLint x, GLint y); +extern void ( * qglVertex2iv )(const GLint *v); +extern void ( * qglVertex2s )(GLshort x, GLshort y); +extern void ( * qglVertex2sv )(const GLshort *v); +extern void ( * qglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +extern void ( * qglVertex3dv )(const GLdouble *v); +extern void ( * qglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +extern void ( * qglVertex3fv )(const GLfloat *v); +extern void ( * qglVertex3i )(GLint x, GLint y, GLint z); +extern void ( * qglVertex3iv )(const GLint *v); +extern void ( * qglVertex3s )(GLshort x, GLshort y, GLshort z); +extern void ( * qglVertex3sv )(const GLshort *v); +extern void ( * qglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +extern void ( * qglVertex4dv )(const GLdouble *v); +extern void ( * qglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +extern void ( * qglVertex4fv )(const GLfloat *v); +extern void ( * qglVertex4i )(GLint x, GLint y, GLint z, GLint w); +extern void ( * qglVertex4iv )(const GLint *v); +extern void ( * qglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +extern void ( * qglVertex4sv )(const GLshort *v); +extern void ( * qglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +extern void ( * qglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); + +#endif diff --git a/code/renderer/qgl_linked.h b/code/renderer/qgl_linked.h new file mode 100644 index 0000000..af71ba4 --- /dev/null +++ b/code/renderer/qgl_linked.h @@ -0,0 +1,336 @@ + +#define qglAccum glAccum +#define qglAlphaFunc glAlphaFunc +#define qglAreTexturesResident glAreTexturesResident +#define qglArrayElement glArrayElement +#define qglBegin glBegin +#define qglBindTexture glBindTexture +#define qglBitmap glBitmap +#define qglBlendFunc glBlendFunc +#define qglCallList glCallList +#define qglCallLists glCallLists +#define qglClear glClear +#define qglClearAccum glClearAccum +#define qglClearColor glClearColor +#define qglClearDepth glClearDepth +#define qglClearIndex glClearIndex +#define qglClearStencil glClearStencil +#define qglClipPlane glClipPlane +#define qglColor3b glColor3b +#define qglColor3bv glColor3bv +#define qglColor3d glColor3d +#define qglColor3dv glColor3dv +#define qglColor3f glColor3f +#define qglColor3fv glColor3fv +#define qglColor3i glColor3i +#define qglColor3iv glColor3iv +#define qglColor3s glColor3s +#define qglColor3sv glColor3sv +#define qglColor3ub glColor3ub +#define qglColor3ubv glColor3ubv +#define qglColor3ui glColor3ui +#define qglColor3uiv glColor3uiv +#define qglColor3us glColor3us +#define qglColor3usv glColor3usv +#define qglColor4b glColor4b +#define qglColor4bv glColor4bv +#define qglColor4d glColor4d +#define qglColor4dv glColor4dv +#define qglColor4f glColor4f +#define qglColor4fv glColor4fv +#define qglColor4i glColor4i +#define qglColor4iv glColor4iv +#define qglColor4s glColor4s +#define qglColor4sv glColor4sv +#define qglColor4ub glColor4ub +#define qglColor4ubv glColor4ubv +#define qglColor4ui glColor4ui +#define qglColor4uiv glColor4uiv +#define qglColor4us glColor4us +#define qglColor4usv glColor4usv +#define qglColorMask glColorMask +#define qglColorMaterial glColorMaterial +#define qglColorPointer glColorPointer +#define qglCopyPixels glCopyPixels +#define qglCopyTexImage1D glCopyTexImage1D +#define qglCopyTexImage2D glCopyTexImage2D +#define qglCopyTexSubImage1D glCopyTexSubImage1D +#define qglCopyTexSubImage2D glCopyTexSubImage2D +#define qglCullFace glCullFace +#define qglDeleteLists glDeleteLists +#define qglDeleteTextures glDeleteTextures +#define qglDepthFunc glDepthFunc +#define qglDepthMask glDepthMask +#define qglDepthRange glDepthRange +#define qglDisable glDisable +#define qglDisableClientState glDisableClientState +#define qglDrawArrays glDrawArrays +#define qglDrawBuffer glDrawBuffer +#define qglDrawElements glDrawElements +#define qglDrawPixels glDrawPixels +#define qglEdgeFlag glEdgeFlag +#define qglEdgeFlagPointer glEdgeFlagPointer +#define qglEdgeFlagv glEdgeFlagv +#define qglEnable glEnable +#define qglEnableClientState glEnableClientState +#define qglEnd glEnd +#define qglEndList glEndList +#define qglEvalCoord1d glEvalCoord1d +#define qglEvalCoord1dv glEvalCoord1dv +#define qglEvalCoord1f glEvalCoord1f +#define qglEvalCoord1fv glEvalCoord1fv +#define qglEvalCoord2d glEvalCoord2d +#define qglEvalCoord2dv glEvalCoord2dv +#define qglEvalCoord2f glEvalCoord2f +#define qglEvalCoord2fv glEvalCoord2fv +#define qglEvalMesh1 glEvalMesh1 +#define qglEvalMesh2 glEvalMesh2 +#define qglEvalPoint1 glEvalPoint1 +#define qglEvalPoint2 glEvalPoint2 +#define qglFeedbackBuffer glFeedbackBuffer +#define qglFinish glFinish +#define qglFlush glFlush +#define qglFogf glFogf +#define qglFogfv glFogfv +#define qglFogi glFogi +#define qglFogiv glFogiv +#define qglFrontFace glFrontFace +#define qglFrustum glFrustum +#define qglGenLists glGenLists +#define qglGenTextures glGenTextures +#define qglGetBooleanv glGetBooleanv +#define qglGetClipPlane glGetClipPlane +#define qglGetDoublev glGetDoublev +#define qglGetError glGetError +#define qglGetFloatv glGetFloatv +#define qglGetIntegerv glGetIntegerv +#define qglGetLightfv glGetLightfv +#define qglGetLightiv glGetLightiv +#define qglGetMapdv glGetMapdv +#define qglGetMapfv glGetMapfv +#define qglGetMapiv glGetMapiv +#define qglGetMaterialfv glGetMaterialfv +#define qglGetMaterialiv glGetMaterialiv +#define qglGetPixelMapfv glGetPixelMapfv +#define qglGetPixelMapuiv glGetPixelMapuiv +#define qglGetPixelMapusv glGetPixelMapusv +#define qglGetPointerv glGetPointerv +#define qglGetPolygonStipple glGetPolygonStipple +#define qglGetString glGetString +#define qglGetTexGendv glGetTexGendv +#define qglGetTexGenfv glGetTexGenfv +#define qglGetTexGeniv glGetTexGeniv +#define qglGetTexImage glGetTexImage +#define qglGetTexLevelParameterfv glGetTexLevelParameterfv +#define qglGetTexLevelParameteriv glGetTexLevelParameteriv +#define qglGetTexParameterfv glGetTexParameterfv +#define qglGetTexParameteriv glGetTexParameteriv +#define qglHint glHint +#define qglIndexMask glIndexMask +#define qglIndexPointer glIndexPointer +#define qglIndexd glIndexd +#define qglIndexdv glIndexdv +#define qglIndexf glIndexf +#define qglIndexfv glIndexfv +#define qglIndexi glIndexi +#define qglIndexiv glIndexiv +#define qglIndexs glIndexs +#define qglIndexsv glIndexsv +#define qglIndexub glIndexub +#define qglIndexubv glIndexubv +#define qglInitNames glInitNames +#define qglInterleavedArrays glInterleavedArrays +#define qglIsEnabled glIsEnabled +#define qglIsList glIsList +#define qglIsTexture glIsTexture +#define qglLightModelf glLightModelf +#define qglLightModelfv glLightModelfv +#define qglLightModeli glLightModeli +#define qglLightModeliv glLightModeliv +#define qglLightf glLightf +#define qglLightfv glLightfv +#define qglLighti glLighti +#define qglLightiv glLightiv +#define qglLineStipple glLineStipple +#define qglLineWidth glLineWidth +#define qglListBase glListBase +#define qglLoadIdentity glLoadIdentity +#define qglLoadMatrixd glLoadMatrixd +#define qglLoadMatrixf glLoadMatrixf +#define qglLoadName glLoadName +#define qglLogicOp glLogicOp +#define qglMap1d glMap1d +#define qglMap1f glMap1f +#define qglMap2d glMap2d +#define qglMap2f glMap2f +#define qglMapGrid1d glMapGrid1d +#define qglMapGrid1f glMapGrid1f +#define qglMapGrid2d glMapGrid2d +#define qglMapGrid2f glMapGrid2f +#define qglMaterialf glMaterialf +#define qglMaterialfv glMaterialfv +#define qglMateriali glMateriali +#define qglMaterialiv glMaterialiv +#define qglMatrixMode glMatrixMode +#define qglMultMatrixd glMultMatrixd +#define qglMultMatrixf glMultMatrixf +#define qglNewList glNewList +#define qglNormal3b glNormal3b +#define qglNormal3bv glNormal3bv +#define qglNormal3d glNormal3d +#define qglNormal3dv glNormal3dv +#define qglNormal3f glNormal3f +#define qglNormal3fv glNormal3fv +#define qglNormal3i glNormal3i +#define qglNormal3iv glNormal3iv +#define qglNormal3s glNormal3s +#define qglNormal3sv glNormal3sv +#define qglNormalPointer glNormalPointer +#define qglOrtho glOrtho +#define qglPassThrough glPassThrough +#define qglPixelMapfv glPixelMapfv +#define qglPixelMapuiv glPixelMapuiv +#define qglPixelMapusv glPixelMapusv +#define qglPixelStoref glPixelStoref +#define qglPixelStorei glPixelStorei +#define qglPixelTransferf glPixelTransferf +#define qglPixelTransferi glPixelTransferi +#define qglPixelZoom glPixelZoom +#define qglPointSize glPointSize +#define qglPolygonMode glPolygonMode +#define qglPolygonOffset glPolygonOffset +#define qglPolygonStipple glPolygonStipple +#define qglPopAttrib glPopAttrib +#define qglPopClientAttrib glPopClientAttrib +#define qglPopMatrix glPopMatrix +#define qglPopName glPopName +#define qglPrioritizeTextures glPrioritizeTextures +#define qglPushAttrib glPushAttrib +#define qglPushClientAttrib glPushClientAttrib +#define qglPushMatrix glPushMatrix +#define qglPushName glPushName +#define qglRasterPos2d glRasterPos2d +#define qglRasterPos2dv glRasterPos2dv +#define qglRasterPos2f glRasterPos2f +#define qglRasterPos2fv glRasterPos2fv +#define qglRasterPos2i glRasterPos2i +#define qglRasterPos2iv glRasterPos2iv +#define qglRasterPos2s glRasterPos2s +#define qglRasterPos2sv glRasterPos2sv +#define qglRasterPos3d glRasterPos3d +#define qglRasterPos3dv glRasterPos3dv +#define qglRasterPos3f glRasterPos3f +#define qglRasterPos3fv glRasterPos3fv +#define qglRasterPos3i glRasterPos3i +#define qglRasterPos3iv glRasterPos3iv +#define qglRasterPos3s glRasterPos3s +#define qglRasterPos3sv glRasterPos3sv +#define qglRasterPos4d glRasterPos4d +#define qglRasterPos4dv glRasterPos4dv +#define qglRasterPos4f glRasterPos4f +#define qglRasterPos4fv glRasterPos4fv +#define qglRasterPos4i glRasterPos4i +#define qglRasterPos4iv glRasterPos4iv +#define qglRasterPos4s glRasterPos4s +#define qglRasterPos4sv glRasterPos4sv +#define qglReadBuffer glReadBuffer +#define qglReadPixels glReadPixels +#define qglRectd glRectd +#define qglRectdv glRectdv +#define qglRectf glRectf +#define qglRectfv glRectfv +#define qglRecti glRecti +#define qglRectiv glRectiv +#define qglRects glRects +#define qglRectsv glRectsv +#define qglRenderMode glRenderMode +#define qglRotated glRotated +#define qglRotatef glRotatef +#define qglScaled glScaled +#define qglScalef glScalef +#define qglScissor glScissor +#define qglSelectBuffer glSelectBuffer +#define qglShadeModel glShadeModel +#define qglStencilFunc glStencilFunc +#define qglStencilMask glStencilMask +#define qglStencilOp glStencilOp +#define qglTexCoord1d glTexCoord1d +#define qglTexCoord1dv glTexCoord1dv +#define qglTexCoord1f glTexCoord1f +#define qglTexCoord1fv glTexCoord1fv +#define qglTexCoord1i glTexCoord1i +#define qglTexCoord1iv glTexCoord1iv +#define qglTexCoord1s glTexCoord1s +#define qglTexCoord1sv glTexCoord1sv +#define qglTexCoord2d glTexCoord2d +#define qglTexCoord2dv glTexCoord2dv +#define qglTexCoord2f glTexCoord2f +#define qglTexCoord2fv glTexCoord2fv +#define qglTexCoord2i glTexCoord2i +#define qglTexCoord2iv glTexCoord2iv +#define qglTexCoord2s glTexCoord2s +#define qglTexCoord2sv glTexCoord2sv +#define qglTexCoord3d glTexCoord3d +#define qglTexCoord3dv glTexCoord3dv +#define qglTexCoord3f glTexCoord3f +#define qglTexCoord3fv glTexCoord3fv +#define qglTexCoord3i glTexCoord3i +#define qglTexCoord3iv glTexCoord3iv +#define qglTexCoord3s glTexCoord3s +#define qglTexCoord3sv glTexCoord3sv +#define qglTexCoord4d glTexCoord4d +#define qglTexCoord4dv glTexCoord4dv +#define qglTexCoord4f glTexCoord4f +#define qglTexCoord4fv glTexCoord4fv +#define qglTexCoord4i glTexCoord4i +#define qglTexCoord4iv glTexCoord4iv +#define qglTexCoord4s glTexCoord4s +#define qglTexCoord4sv glTexCoord4sv +#define qglTexCoordPointer glTexCoordPointer +#define qglTexEnvf glTexEnvf +#define qglTexEnvfv glTexEnvfv +#define qglTexEnvi glTexEnvi +#define qglTexEnviv glTexEnviv +#define qglTexGend glTexGend +#define qglTexGendv glTexGendv +#define qglTexGenf glTexGenf +#define qglTexGenfv glTexGenfv +#define qglTexGeni glTexGeni +#define qglTexGeniv glTexGeniv +#define qglTexImage1D glTexImage1D +#define qglTexImage2D glTexImage2D +#define qglTexParameterf glTexParameterf +#define qglTexParameterfv glTexParameterfv +#define qglTexParameteri glTexParameteri +#define qglTexParameteriv glTexParameteriv +#define qglTexSubImage1D glTexSubImage1D +#define qglTexSubImage2D glTexSubImage2D +#define qglTranslated glTranslated +#define qglTranslatef glTranslatef +#define qglVertex2d glVertex2d +#define qglVertex2dv glVertex2dv +#define qglVertex2f glVertex2f +#define qglVertex2fv glVertex2fv +#define qglVertex2i glVertex2i +#define qglVertex2iv glVertex2iv +#define qglVertex2s glVertex2s +#define qglVertex2sv glVertex2sv +#define qglVertex3d glVertex3d +#define qglVertex3dv glVertex3dv +#define qglVertex3f glVertex3f +#define qglVertex3fv glVertex3fv +#define qglVertex3i glVertex3i +#define qglVertex3iv glVertex3iv +#define qglVertex3s glVertex3s +#define qglVertex3sv glVertex3sv +#define qglVertex4d glVertex4d +#define qglVertex4dv glVertex4dv +#define qglVertex4f glVertex4f +#define qglVertex4fv glVertex4fv +#define qglVertex4i glVertex4i +#define qglVertex4iv glVertex4iv +#define qglVertex4s glVertex4s +#define qglVertex4sv glVertex4sv +#define qglVertexPointer glVertexPointer +#define qglViewport glViewport + diff --git a/code/renderer/ref_trin.def b/code/renderer/ref_trin.def new file mode 100644 index 0000000..cfbb471 --- /dev/null +++ b/code/renderer/ref_trin.def @@ -0,0 +1,2 @@ +EXPORTS + GetRefAPI diff --git a/code/renderer/tr_WorldEffects.cpp b/code/renderer/tr_WorldEffects.cpp new file mode 100644 index 0000000..fdc1887 --- /dev/null +++ b/code/renderer/tr_WorldEffects.cpp @@ -0,0 +1,2295 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN SOFTWARE - STAR WARS: JK II +// (c) 2002 Activision +// +// World Effects +// +// +//////////////////////////////////////////////////////////////////////////////////////// +#include "../server/exe_headers.h" +#pragma warning( disable : 4512 ) + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Externs & Fwd Decl. +//////////////////////////////////////////////////////////////////////////////////////// +extern qboolean ParseVector( const char **text, int count, float *v ); +extern void SetViewportAndScissor( void ); + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#include "tr_local.h" +#include "tr_WorldEffects.h" +#include "../Ravl/CVec.h" +#include "../Ratl/vector_vs.h" +#include "../Ratl/bits_vs.h" + +#ifdef _XBOX +#include "../win32/glw_win_dx8.h" +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// Defines +//////////////////////////////////////////////////////////////////////////////////////// +#define GLS_ALPHA (GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA) +#define MAX_WIND_ZONES 12 +#define MAX_WEATHER_ZONES 50 // so we can more zones that are smaller +#define MAX_PUFF_SYSTEMS 2 +#define MAX_PARTICLE_CLOUDS 5 + +#ifdef _XBOX +#define POINTCACHE_CELL_SIZE 32.0f + +// Note to Vv: +// you guys may want to look into lowering that number. I've optimized the storage +// space by breaking it up into small boxes (weather zones) around the areas we care about +// in order to speed up load time and reduce memory. A very high number here will mean +// that weather related effects like rain, fog, snow, etc will bleed through to where +// they shouldn't... + +#else +#define POINTCACHE_CELL_SIZE 32.0f +#endif + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Globals +//////////////////////////////////////////////////////////////////////////////////////// +float mMillisecondsElapsed = 0; +float mSecondsElapsed = 0; +bool mFrozen = false; + +CVec3 mGlobalWindVelocity; +CVec3 mGlobalWindDirection; +float mGlobalWindSpeed; +int mParticlesRendered; + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Handy Functions +//////////////////////////////////////////////////////////////////////////////////////// +inline void VectorMA( vec3_t vecAdd, const float scale, const vec3_t vecScale) +{ + vecAdd[0] += (scale * vecScale[0]); + vecAdd[1] += (scale * vecScale[1]); + vecAdd[2] += (scale * vecScale[2]); +} + +inline void VectorFloor(vec3_t in) +{ + in[0] = floorf(in[0]); + in[1] = floorf(in[1]); + in[2] = floorf(in[2]); +} + +inline void VectorCeil(vec3_t in) +{ + in[0] = ceilf(in[0]); + in[1] = ceilf(in[1]); + in[2] = ceilf(in[2]); +} + +inline float FloatRand(void) +{ + return ((float)rand() / (float)RAND_MAX); +} + +inline float fast_flrand(float min, float max) +{ + //return min + (max - min) * flrand; + return Q_flrand(min, max); //fixme? +} + +inline void SnapFloatToGrid(float& f, int GridSize) +{ + f = (int)(f); + + bool fNeg = (f<0); + if (fNeg) + { + f *= -1; // Temporarly make it positive + } + + int Offset = ((int)(f) % (int)(GridSize)); + int OffsetAbs = abs(Offset); + if (OffsetAbs>(GridSize/2)) + { + Offset = (GridSize - OffsetAbs) * -1; + } + + f -= Offset; + + if (fNeg) + { + f *= -1; // Put It Back To Negative + } + + f = (int)(f); + + assert(((int)(f)%(int)(GridSize)) == 0); +} + +inline void SnapVectorToGrid(CVec3& Vec, int GridSize) +{ + SnapFloatToGrid(Vec[0], GridSize); + SnapFloatToGrid(Vec[1], GridSize); + SnapFloatToGrid(Vec[2], GridSize); +} + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Range Structures +//////////////////////////////////////////////////////////////////////////////////////// +struct SVecRange +{ + CVec3 mMins; + CVec3 mMaxs; + + inline void Clear() + { + mMins.Clear(); + mMaxs.Clear(); + } + + inline void Pick(CVec3& V) + { + V[0] = Q_flrand(mMins[0], mMaxs[0]); + V[1] = Q_flrand(mMins[1], mMaxs[1]); + V[2] = Q_flrand(mMins[2], mMaxs[2]); + } + inline void Wrap(CVec3& V) + { + if (V[0]<=mMins[0]) + { + if ((mMins[0]-V[0])>500) + { + Pick(V); + return; + } + V[0] = mMaxs[0] - 10.0f; + } + if (V[0]>=mMaxs[0]) + { + if ((V[0]-mMaxs[0])>500) + { + Pick(V); + return; + } + V[0] = mMins[0] + 10.0f; + } + + if (V[1]<=mMins[1]) + { + if ((mMins[1]-V[1])>500) + { + Pick(V); + return; + } + V[1] = mMaxs[1] - 10.0f; + } + if (V[1]>=mMaxs[1]) + { + if ((V[1]-mMaxs[1])>500) + { + Pick(V); + return; + } + V[1] = mMins[1] + 10.0f; + } + + if (V[2]<=mMins[2]) + { + if ((mMins[2]-V[2])>500) + { + Pick(V); + return; + } + V[2] = mMaxs[2] - 10.0f; + } + if (V[2]>=mMaxs[2]) + { + if ((V[2]-mMaxs[2])>500) + { + Pick(V); + return; + } + V[2] = mMins[2] + 10.0f; + } + } + + inline bool In(const CVec3& V) + { + return (V>mMins && VmMin && VmMin && V TFlags; + + float mAlpha; + TFlags mFlags; + CVec3 mPosition; + CVec3 mVelocity; + float mMass; // A higher number will more greatly resist force and result in greater gravity +}; + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Wind +//////////////////////////////////////////////////////////////////////////////////////// +class CWindZone +{ +public: + bool mGlobal; + SVecRange mRBounds; + SVecRange mRVelocity; + SIntRange mRDuration; + SIntRange mRDeadTime; + float mMaxDeltaVelocityPerUpdate; + float mChanceOfDeadTime; + + CVec3 mCurrentVelocity; + CVec3 mTargetVelocity; + int mTargetVelocityTimeRemaining; + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Initialize - Will setup default values for all data + //////////////////////////////////////////////////////////////////////////////////// + void Initialize() + { + mRBounds.Clear(); + mGlobal = true; + + mRVelocity.mMins = -1500.0f; + mRVelocity.mMins[2] = -10.0f; + mRVelocity.mMaxs = 1500.0f; + mRVelocity.mMaxs[2] = 10.0f; + + mMaxDeltaVelocityPerUpdate = 10.0f; + + mRDuration.mMin = 1000; + mRDuration.mMax = 2000; + + mChanceOfDeadTime = 0.3f; + mRDeadTime.mMin = 1000; + mRDeadTime.mMax = 3000; + + mCurrentVelocity.Clear(); + mTargetVelocity.Clear(); + mTargetVelocityTimeRemaining = 0; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Update - Changes wind when current target velocity expires + //////////////////////////////////////////////////////////////////////////////////// + void Update() + { + if (mTargetVelocityTimeRemaining==0) + { + if (FloatRand() mMaxDeltaVelocityPerUpdate) + { + DeltaVelocityLen = mMaxDeltaVelocityPerUpdate; + } + DeltaVelocity *= (DeltaVelocityLen); + mCurrentVelocity += DeltaVelocity; + } + } +}; +ratl::vector_vs mWindZones; +ratl::vector_vs mLocalWindZones; + +bool R_GetWindVector(vec3_t windVector, vec3_t atpoint) +{ + VectorCopy(mGlobalWindDirection.v, windVector); + if (atpoint && mLocalWindZones.size()) + { + for (int curLocalWindZone=0; curLocalWindZonemRBounds.In(atpoint)) + { + VectorAdd(windVector, mLocalWindZones[curLocalWindZone]->mCurrentVelocity.v, windVector); + } + } + VectorNormalize(windVector); + } + return true; +} + +bool R_GetWindSpeed(float &windSpeed, vec3_t atpoint) +{ + windSpeed = mGlobalWindSpeed; + if (atpoint && mLocalWindZones.size()) + { + for (int curLocalWindZone=0; curLocalWindZonemRBounds.In(atpoint)) + { + windSpeed += VectorLength(mLocalWindZones[curLocalWindZone]->mCurrentVelocity.v); + } + } + } + return true; +} + +bool R_GetWindGusting(vec3_t atpoint) +{ + float windSpeed; + R_GetWindSpeed(windSpeed, atpoint); + return (windSpeed>1000.0f); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Outside Point Cache +//////////////////////////////////////////////////////////////////////////////////////// +class COutside +{ +#define COUTSIDE_STRUCT_VERSION 1 // you MUST increase this any time you change any binary (fields) inside this class, or cahced files will fuck up +public: + //////////////////////////////////////////////////////////////////////////////////// + //Global Public Outside Variables + //////////////////////////////////////////////////////////////////////////////////// + + bool mOutsideShake; + float mOutsidePain; + + CVec3 mFogColor; + int mFogColorInt; + bool mFogColorTempActive; + +private: + //////////////////////////////////////////////////////////////////////////////////// + // The Outside Cache + //////////////////////////////////////////////////////////////////////////////////// + bool mCacheInit; // Has It Been Cached? + + struct SWeatherZone + { + static bool mMarkedOutside; + ulong* mPointCache; // malloc block ptr + + int miPointCacheByteSize; // size of block + SVecRange mExtents; + SVecRange mSize; + int mWidth; + int mHeight; + int mDepth; + + void WriteToDisk( fileHandle_t f ) + { + FS_Write(&mMarkedOutside,sizeof(mMarkedOutside),f); + FS_Write( mPointCache, miPointCacheByteSize, f ); + } + + void ReadFromDisk( fileHandle_t f ) + { + FS_Read(&mMarkedOutside,sizeof(mMarkedOutside),f); + FS_Read( mPointCache, miPointCacheByteSize, f); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Convert To Cell + //////////////////////////////////////////////////////////////////////////////////// + inline void ConvertToCell(const CVec3& pos, int& x, int& y, int& z, int& bit) + { + x = (int)((pos[0] / POINTCACHE_CELL_SIZE) - mSize.mMins[0]); + y = (int)((pos[1] / POINTCACHE_CELL_SIZE) - mSize.mMins[1]); + z = (int)((pos[2] / POINTCACHE_CELL_SIZE) - mSize.mMins[2]); + + bit = (z & 31); + z >>= 5; + } + + //////////////////////////////////////////////////////////////////////////////////// + // CellOutside - Test to see if a given cell is outside + //////////////////////////////////////////////////////////////////////////////////// + inline bool CellOutside(int x, int y, int z, int bit) + { + if ((x < 0 || x >= mWidth) || (y < 0 || y >= mHeight) || (z < 0 || z >= mDepth) || (bit < 0 || bit >= 32)) + { + return !(mMarkedOutside); + } + return (mMarkedOutside==(!!(mPointCache[((z * mWidth * mHeight) + (y * mWidth) + x)]&(1 << bit)))); + } + }; + ratl::vector_vs mWeatherZones; + + +private: + //////////////////////////////////////////////////////////////////////////////////// + // Iteration Variables + //////////////////////////////////////////////////////////////////////////////////// + int mWCells; + int mHCells; + + int mXCell; + int mYCell; + int mZBit; + + int mXMax; + int mYMax; + int mZMax; + + +private: + + + //////////////////////////////////////////////////////////////////////////////////// + // Contents Outside + //////////////////////////////////////////////////////////////////////////////////// + inline bool ContentsOutside(int contents) + { + if (contents&CONTENTS_WATER || contents&CONTENTS_SOLID) + { + return false; + } + if (mCacheInit) + { + if (SWeatherZone::mMarkedOutside) + { + return (!!(contents&CONTENTS_OUTSIDE)); + } + return (!(contents&CONTENTS_INSIDE)); + } + return !!(contents&CONTENTS_OUTSIDE); + } + + + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructor - Will setup default values for all data + //////////////////////////////////////////////////////////////////////////////////// + void Reset() + { + mOutsideShake = false; + mOutsidePain = 0.0; + mCacheInit = false; + SWeatherZone::mMarkedOutside = false; + + mFogColor.Clear(); + mFogColorInt = 0; + mFogColorTempActive = false; + + for (int wz=0; wz> 5; + + Wz.miPointCacheByteSize = (Wz.mWidth * Wz.mHeight * Wz.mDepth) * sizeof(ulong); + Wz.mPointCache = (ulong *)Z_Malloc( Wz.miPointCacheByteSize, TAG_POINTCACHE, qtrue ); + } + else + { + assert("MaxWeatherZones Hit!"==0); + } + } + + const char *GenCachedWeatherFilename(void) + { + return va("maps/%s.weather", sv_mapname->string); + } + + // weather file format... + // + struct WeatherFileHeader_t + { + int m_iVersion; + int m_iChecksum; + + WeatherFileHeader_t() + { + m_iVersion = COUTSIDE_STRUCT_VERSION; + m_iChecksum = sv_mapChecksum->integer; + } + }; + + fileHandle_t WriteCachedWeatherFile( void ) + { + fileHandle_t f = FS_FOpenFileWrite( GenCachedWeatherFilename() ); + if (f) + { + WeatherFileHeader_t WeatherFileHeader; + + FS_Write(&WeatherFileHeader, sizeof(WeatherFileHeader), f); + return f; + } + else + { + VID_Printf( PRINT_WARNING, "(Unable to open weather file \"%s\" for writing!)\n",GenCachedWeatherFilename()); + } + + return 0; + } + + // returns 0 for not-found or invalid file, else open handle to continue read from (which you then close yourself)... + // + fileHandle_t ReadCachedWeatherFile( void ) + { + fileHandle_t f = 0; + FS_FOpenFileRead( GenCachedWeatherFilename(), &f, qfalse ); + +#ifdef _XBOX + // Our checksums don't work anyway - so we trust that the file is correct: + if ( f ) + { + WeatherFileHeader_t WeatherFileHeaderFromDisk; + FS_Read(&WeatherFileHeaderFromDisk, sizeof(WeatherFileHeaderFromDisk), f); + + return f; + } +#else + if ( f ) + { + // ok, it exists, but is it valid for this map?... + // + WeatherFileHeader_t WeatherFileHeaderForCompare; + WeatherFileHeader_t WeatherFileHeaderFromDisk; + + FS_Read(&WeatherFileHeaderFromDisk, sizeof(WeatherFileHeaderFromDisk), f); + + if (!memcmp(&WeatherFileHeaderForCompare, &WeatherFileHeaderFromDisk, sizeof(WeatherFileHeaderFromDisk))) + { + // go for it... + // + return f; + } + + VID_Printf( PRINT_WARNING, "( Cached weather file \"%s\" out of date, regenerating... )\n",GenCachedWeatherFilename()); + FS_FCloseFile( f ); + } +#endif + else + { + VID_Printf( PRINT_WARNING, "( No cached weather file found, generating... )\n"); + } + + return 0; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Cache - Will Scan the World, Creating The Cache + //////////////////////////////////////////////////////////////////////////////////// + void Cache() + { + if (!tr.world || mCacheInit) + { + return; + } + + // all this piece of code does really is fill in the bool "SWeatherZone::mMarkedOutside", plus the mPointCache[] for each zone, + // so we can diskload those. Maybe. + fileHandle_t f = ReadCachedWeatherFile(); + if ( f ) + { + for (int iZone=0; iZonebmodels[0].bounds[0], tr.world->bmodels[0].bounds[1]); + } + + f = WriteCachedWeatherFile(); + + // Iterate Over All Weather Zones + //-------------------------------- + for (int zone=0; zoneglobalFog != -1) + { + // If Non Zero, Try To Set The Color + //----------------------------------- + if (color[0] || color[1] || color[2]) + { + // Remember The Normal Fog Color + //------------------------------- + if (!mOutside.mFogColorTempActive) + { + mOutside.mFogColor = tr.world->fogs[tr.world->globalFog].parms.color; + mOutside.mFogColorInt = tr.world->fogs[tr.world->globalFog].colorInt; + mOutside.mFogColorTempActive = true; + } + + // Set The New One + //----------------- + tr.world->fogs[tr.world->globalFog].parms.color[0] = color[0]; + tr.world->fogs[tr.world->globalFog].parms.color[1] = color[1]; + tr.world->fogs[tr.world->globalFog].parms.color[2] = color[2]; + tr.world->fogs[tr.world->globalFog].colorInt = ColorBytes4 ( + color[0] * tr.identityLight, + color[1] * tr.identityLight, + color[2] * tr.identityLight, + 1.0 ); + } + + // If Unable TO Parse The Command Color Vector, Restore The Previous Fog Color + //----------------------------------------------------------------------------- + else if (mOutside.mFogColorTempActive) + { + mOutside.mFogColorTempActive = false; + + tr.world->fogs[tr.world->globalFog].parms.color[0] = mOutside.mFogColor[0]; + tr.world->fogs[tr.world->globalFog].parms.color[1] = mOutside.mFogColor[1]; + tr.world->fogs[tr.world->globalFog].parms.color[2] = mOutside.mFogColor[2]; + tr.world->fogs[tr.world->globalFog].colorInt = mOutside.mFogColorInt; + } + } + return true; +} + + +#ifdef _XBOX // Xbox point sprite code +static void pointBegin(GLint verts, float size) +{ + assert(!glw_state->inDrawBlock); + + // start the draw block + glw_state->inDrawBlock = true; + glw_state->primitiveMode = D3DPT_POINTLIST; + + // update DX with any pending state changes + glw_state->drawStride = 4; + DWORD mask = D3DFVF_XYZ | D3DFVF_DIFFUSE; + glw_state->device->SetVertexShader(mask); + glw_state->shaderMask = mask; + + if(glw_state->matricesDirty[glwstate_t::MatrixMode_Model]) + { + glw_state->device->SetTransform(D3DTS_VIEW, + glw_state->matrixStack[glwstate_t::MatrixMode_Model]->GetTop()); + + glw_state->matricesDirty[glwstate_t::MatrixMode_Model] = false; + } + + // Update the texture and states + // NOTE: Point sprites ALWAYS go on texture stage 3 + glwstate_t::texturexlat_t::iterator it = glw_state->textureXlat.find(glw_state->currentTexture[0]); + glw_state->device->SetTexture( 3, it->second.mipmap ); + glw_state->device->SetTextureStageState(3, D3DTSS_COLOROP, glw_state->textureEnv[0]); + glw_state->device->SetTextureStageState(3, D3DTSS_COLORARG1, D3DTA_TEXTURE); + glw_state->device->SetTextureStageState(3, D3DTSS_COLORARG2, D3DTA_CURRENT); + glw_state->device->SetTextureStageState(3, D3DTSS_ALPHAOP, glw_state->textureEnv[0]); + glw_state->device->SetTextureStageState(3, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + glw_state->device->SetTextureStageState(3, D3DTSS_ALPHAARG2, D3DTA_CURRENT); + glw_state->device->SetTextureStageState(3, D3DTSS_MAXANISOTROPY, it->second.anisotropy); + glw_state->device->SetTextureStageState(3, D3DTSS_MINFILTER, it->second.minFilter); + glw_state->device->SetTextureStageState(3, D3DTSS_MIPFILTER, it->second.mipFilter); + glw_state->device->SetTextureStageState(3, D3DTSS_MAGFILTER, it->second.magFilter); + glw_state->device->SetTextureStageState(3, D3DTSS_ADDRESSU, it->second.wrapU); + glw_state->device->SetTextureStageState(3, D3DTSS_ADDRESSV, it->second.wrapV); + + glw_state->device->SetTexture( 0, NULL ); + glw_state->device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_DISABLE ); + + float attena = 1.0f, attenb = 0.0f, attenc = 0.01f; + glw_state->device->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_POINTSCALEENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_POINTSIZE, *((DWORD*)&size) ); + glw_state->device->SetRenderState( D3DRS_POINTSIZE_MIN, *((DWORD*)&attenb)); + glw_state->device->SetRenderState( D3DRS_POINTSCALE_A, *((DWORD*)&attena) ); + glw_state->device->SetRenderState( D3DRS_POINTSCALE_B, *((DWORD*)&attenb) ); + glw_state->device->SetRenderState( D3DRS_POINTSCALE_C, *((DWORD*)&attenc) ); + + // set vertex counters + glw_state->numVertices = 0; + glw_state->totalVertices = verts; + int max = glw_state->totalVertices; + if (max > 2040 / glw_state->drawStride) + { + max = 2040 / glw_state->drawStride; + } + glw_state->maxVertices = max; + + // open a draw packet + int num_packets; + if(verts == 0) { + num_packets = 1; + } else { + num_packets = (verts / glw_state->maxVertices) + (!!(verts % glw_state->maxVertices)); + } + int cmd_size = num_packets * 3; + int vert_size = glw_state->drawStride * verts; + + glw_state->device->BeginPush(vert_size + cmd_size + 2, + &glw_state->drawArray); + + glw_state->drawArray[0] = D3DPUSH_ENCODE(D3DPUSH_SET_BEGIN_END, 1); + glw_state->drawArray[1] = glw_state->primitiveMode; + glw_state->drawArray[2] = D3DPUSH_ENCODE( + D3DPUSH_NOINCREMENT_FLAG|D3DPUSH_INLINE_ARRAY, + glw_state->drawStride * glw_state->maxVertices); + glw_state->drawArray += 3; +} + + +static void pointEnd() +{ + glw_state->device->SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE ); + glw_state->device->SetRenderState( D3DRS_POINTSCALEENABLE, FALSE ); + glw_state->device->SetTexture( 3, NULL ); + glw_state->device->SetTextureStageState( 3, D3DTSS_COLOROP, D3DTOP_DISABLE ); +} +#endif // _XBOX + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Particle Cloud +//////////////////////////////////////////////////////////////////////////////////////// +class CParticleCloud +{ +private: + //////////////////////////////////////////////////////////////////////////////////// + // DYNAMIC MEMORY + //////////////////////////////////////////////////////////////////////////////////// + image_t* mImage; + WFXParticle* mParticles; + +private: + //////////////////////////////////////////////////////////////////////////////////// + // RUN TIME VARIANTS + //////////////////////////////////////////////////////////////////////////////////// + float mSpawnSpeed; + CVec3 mSpawnPlaneNorm; + CVec3 mSpawnPlaneRight; + CVec3 mSpawnPlaneUp; + SVecRange mRange; + + CVec3 mCameraPosition; + CVec3 mCameraForward; + CVec3 mCameraLeft; + CVec3 mCameraDown; + CVec3 mCameraLeftPlusUp; + CVec3 mCameraLeftMinusUp; + + + int mParticleCountRender; + int mGLModeEnum; + + bool mPopulated; + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // CONSTANTS + //////////////////////////////////////////////////////////////////////////////////// + bool mOrientWithVelocity; + float mSpawnPlaneSize; + float mSpawnPlaneDistance; + SVecRange mSpawnRange; + + float mGravity; // How much gravity affects the velocity of a particle + CVec4 mColor; // RGBA color + int mVertexCount; // 3 for triangle, 4 for quad, other numbers not supported + + float mWidth; + float mHeight; + + int mBlendMode; // 0 = ALPHA, 1 = SRC->SRC + int mFilterMode; // 0 = LINEAR, 1 = NEAREST + + float mFade; // How much to fade in and out 1.0 = instant, 0.01 = very slow + + SFloatRange mRotation; + float mRotationDelta; + float mRotationDeltaTarget; + float mRotationCurrent; + SIntRange mRotationChangeTimer; + int mRotationChangeNext; + + SFloatRange mMass; // Determines how slowness to accelerate, higher number = slower + float mFrictionInverse; // How much air friction does this particle have 1.0=none, 0.0=nomove + + int mParticleCount; + + bool mWaterParticles; + + + + +public: + //////////////////////////////////////////////////////////////////////////////////// + // Initialize - Create Image, Particles, And Setup All Values + //////////////////////////////////////////////////////////////////////////////////// + void Initialize(int count, const char* texturePath, int VertexCount=4) + { + Reset(); + assert(mParticleCount==0 && mParticles==0); + assert(mImage==0); + + // Create The Image + //------------------ + mImage = R_FindImageFile(texturePath, false, false, false, GL_CLAMP); + if (!mImage) + { + Com_Error(ERR_DROP, "CParticleCloud: Could not texture %s", texturePath); + } + + GL_Bind(mImage); + + + + // Create The Particles + //---------------------- + mParticleCount = count; + mParticles = new WFXParticle[mParticleCount]; + + + + WFXParticle* part=0; + for (int particleNum=0; particleNummPosition.Clear(); + part->mVelocity.Clear(); + part->mAlpha = 0.0f; + mMass.Pick(part->mMass); + } + + mVertexCount = VertexCount; +#ifdef _XBOX // Check for point sprite use + if(mVertexCount == 1) + mGLModeEnum = GL_POINTS; + else +#endif + mGLModeEnum = (mVertexCount==3)?(GL_TRIANGLES):(GL_QUADS); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Reset - Initializes all data to default values + //////////////////////////////////////////////////////////////////////////////////// + void Reset() + { + if (mImage) + { + // TODO: Free Image? + } + mImage = 0; + if (mParticleCount) + { + delete [] mParticles; + } + mParticleCount = 0; + mParticles = 0; + + mPopulated = 0; + + + + // These Are The Default Startup Values For Constant Data + //======================================================== + mOrientWithVelocity = false; + mWaterParticles = false; + + mSpawnPlaneDistance = 500; + mSpawnPlaneSize = 500; + mSpawnRange.mMins = -(mSpawnPlaneDistance*1.25f); + mSpawnRange.mMaxs = (mSpawnPlaneDistance*1.25f); + + mGravity = 300.0f; // Units Per Second + + mColor = 1.0f; + + mVertexCount = 4; + mWidth = 1.0f; + mHeight = 1.0f; + + mBlendMode = 0; + mFilterMode = 0; + + mFade = 10.0f; + + mRotation.Clear(); + mRotationDelta = 0.0f; + mRotationDeltaTarget= 0.0f; + mRotationCurrent = 0.0f; + mRotationChangeNext = -1; + mRotation.mMin = -0.7f; + mRotation.mMax = 0.7f; + mRotationChangeTimer.mMin = 500; + mRotationChangeTimer.mMin = 2000; + + mMass.mMin = 5.0f; + mMass.mMax = 10.0f; + + mFrictionInverse = 0.7f; // No Friction? + } + + //////////////////////////////////////////////////////////////////////////////////// + // Constructor - Will setup default values for all data + //////////////////////////////////////////////////////////////////////////////////// + CParticleCloud() + { + mImage = 0; + mParticleCount = 0; + Reset(); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Initialize - Will setup default values for all data + //////////////////////////////////////////////////////////////////////////////////// + ~CParticleCloud() + { + Reset(); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // UseSpawnPlane - Check To See If We Should Spawn On A Plane, Or Just Wrap The Box + //////////////////////////////////////////////////////////////////////////////////// + inline bool UseSpawnPlane() + { + return (mGravity!=0.0f); + } + + + //////////////////////////////////////////////////////////////////////////////////// + // Update - Applies All Physics Forces To All Contained Particles + //////////////////////////////////////////////////////////////////////////////////// + void Update() + { + WFXParticle* part=0; + CVec3 partForce; + CVec3 partMoved; + CVec3 partToCamera; + bool partRendering; + bool partOutside; + bool partInRange; + bool partInView; + int particleNum; + float particleFade = (mFade * mSecondsElapsed); + int numLocalWindZones = mLocalWindZones.size(); + int curLocalWindZone; + + + // Compute Camera + //---------------- + { + mCameraPosition = backEnd.viewParms.or.origin; + mCameraForward = backEnd.viewParms.or.axis[0]; + mCameraLeft = backEnd.viewParms.or.axis[1]; + mCameraDown = backEnd.viewParms.or.axis[2]; + + if (mRotationChangeNext!=-1) + { + if (mRotationChangeNext==0) + { + mRotation.Pick(mRotationDeltaTarget); + mRotationChangeTimer.Pick(mRotationChangeNext); + if (mRotationChangeNext<=0) + { + mRotationChangeNext = 1; + } + } + mRotationChangeNext--; + + float RotationDeltaDifference = (mRotationDeltaTarget - mRotationDelta); + if (fabsf(RotationDeltaDifference)>0.01) + { + mRotationDelta += RotationDeltaDifference; // Blend To New Delta + } + mRotationCurrent += (mRotationDelta * mSecondsElapsed); + float s = sinf(mRotationCurrent); + float c = cosf(mRotationCurrent); + + CVec3 TempCamLeft(mCameraLeft); + + mCameraLeft *= (c * mWidth); + mCameraLeft.ScaleAdd(mCameraDown, (s * mWidth * -1.0f)); + + mCameraDown *= (c * mHeight); + mCameraDown.ScaleAdd(TempCamLeft, (s * mHeight)); + } + else + { + mCameraLeft *= mWidth; + mCameraDown *= mHeight; + } + } + + + // Compute Global Force + //---------------------- + CVec3 force; + { + force.Clear(); + + // Apply Gravity + //--------------- + force[2] = -1.0f * mGravity; + + // Apply Wind Velocity + //--------------------- + force += mGlobalWindVelocity; + } + + + // Update Range + //-------------- + { + mRange.mMins = mCameraPosition + mSpawnRange.mMins; + mRange.mMaxs = mCameraPosition + mSpawnRange.mMaxs; + + // If Using A Spawn Plane, Increase The Range Box A Bit To Account For Rotation On The Spawn Plane + //------------------------------------------------------------------------------------------------- + if (UseSpawnPlane()) + { + for (int dim=0; dim<3; dim++) + { + if (force[dim]>0.01) + { + mRange.mMins[dim] -= (mSpawnPlaneDistance/2.0f); + } + else if (force[dim]<-0.01) + { + mRange.mMaxs[dim] += (mSpawnPlaneDistance/2.0f); + } + } + mSpawnPlaneNorm = force; + mSpawnSpeed = VectorNormalize(mSpawnPlaneNorm.v); + MakeNormalVectors(mSpawnPlaneNorm.v, mSpawnPlaneRight.v, mSpawnPlaneUp.v); + } + + // Optimization For Quad Position Calculation + //-------------------------------------------- + if (mVertexCount==4) + { + mCameraLeftPlusUp = (mCameraLeft - mCameraDown); + mCameraLeftMinusUp = (mCameraLeft + mCameraDown); + } + else + { + mCameraLeftPlusUp = (mCameraDown + mCameraLeft); // should really be called mCamera Left + Down + } + } + + // Stop All Additional Processing + //-------------------------------- + if (mFrozen) + { + return; + } + + + + // Now Update All Particles + //-------------------------- + mParticleCountRender = 0; + for (particleNum=0; particleNummPosition); // First Time Spawn Location + } + + // Grab The Force And Apply Non Global Wind + //------------------------------------------ + partForce = force; + + if (numLocalWindZones) + { + for (curLocalWindZone=0; curLocalWindZonemRBounds.In(part->mPosition)) + { + partForce += mLocalWindZones[curLocalWindZone]->mCurrentVelocity; + } + } + } + + partForce /= part->mMass; + + + // Apply The Force + //----------------- + part->mVelocity += partForce; + part->mVelocity *= mFrictionInverse; + + part->mPosition.ScaleAdd(part->mVelocity, mSecondsElapsed); + + partToCamera = (part->mPosition - mCameraPosition); + partRendering = part->mFlags.get_bit(WFXParticle::FLAG_RENDER); + partOutside = mOutside.PointOutside(part->mPosition, mWidth, mHeight); + partInRange = mRange.In(part->mPosition); + partInView = (partOutside && partInRange && (partToCamera.Dot(mCameraForward)>0.0f)); + + // Process Respawn + //----------------- + if (!partInRange && !partRendering) + { + part->mVelocity.Clear(); + + // Reselect A Position On The Spawn Plane + //---------------------------------------- + if (UseSpawnPlane()) + { + part->mPosition = mCameraPosition; + part->mPosition -= (mSpawnPlaneNorm* mSpawnPlaneDistance); + part->mPosition += (mSpawnPlaneRight*Q_flrand(-mSpawnPlaneSize, mSpawnPlaneSize)); + part->mPosition += (mSpawnPlaneUp* Q_flrand(-mSpawnPlaneSize, mSpawnPlaneSize)); + } + + // Otherwise, Just Wrap Around To The Other End Of The Range + //----------------------------------------------------------- + else + { + mRange.Wrap(part->mPosition); + } + partInRange = true; + } + + // Process Fade + //-------------- + { + // Start A Fade Out + //------------------ + if (partRendering && !partInView) + { + part->mFlags.clear_bit(WFXParticle::FLAG_FADEIN); + part->mFlags.set_bit(WFXParticle::FLAG_FADEOUT); + } + + // Switch From Fade Out To Fade In + //--------------------------------- + else if (partRendering && partInView && part->mFlags.get_bit(WFXParticle::FLAG_FADEOUT)) + { + part->mFlags.set_bit(WFXParticle::FLAG_FADEIN); + part->mFlags.clear_bit(WFXParticle::FLAG_FADEOUT); + } + + // Start A Fade In + //----------------- + else if (!partRendering && partInView) + { + partRendering = true; + part->mAlpha = 0.0f; + part->mFlags.set_bit(WFXParticle::FLAG_RENDER); + part->mFlags.set_bit(WFXParticle::FLAG_FADEIN); + part->mFlags.clear_bit(WFXParticle::FLAG_FADEOUT); + } + + // Update Fade + //------------- + if (partRendering) + { + + // Update Fade Out + //----------------- + if (part->mFlags.get_bit(WFXParticle::FLAG_FADEOUT)) + { + part->mAlpha -= particleFade; + if (part->mAlpha<=0.0f) + { + part->mAlpha = 0.0f; + part->mFlags.clear_bit(WFXParticle::FLAG_FADEOUT); + part->mFlags.clear_bit(WFXParticle::FLAG_FADEIN); + part->mFlags.clear_bit(WFXParticle::FLAG_RENDER); + partRendering = false; + } + } + + // Update Fade In + //---------------- + else if (part->mFlags.get_bit(WFXParticle::FLAG_FADEIN)) + { + part->mAlpha += particleFade; + if (part->mAlpha>=mColor[3]) + { + part->mFlags.clear_bit(WFXParticle::FLAG_FADEIN); + part->mAlpha = mColor[3]; + } + } + } + } + + // Keep Track Of The Number Of Particles To Render + //------------------------------------------------- + if (part->mFlags.get_bit(WFXParticle::FLAG_RENDER)) + { + mParticleCountRender ++; + } + } + mPopulated = true; + } + + //////////////////////////////////////////////////////////////////////////////////// + // Render - + //////////////////////////////////////////////////////////////////////////////////// + void Render() + { + WFXParticle* part=0; + int particleNum; + CVec3 partDirection; + + + // Set The GL State And Image Binding + //------------------------------------ + GL_State((mBlendMode==0)?(GLS_ALPHA):(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE)); + GL_Bind(mImage); + + + // Enable And Disable Things + //--------------------------- +#ifdef _XBOX // Simpler pointsprite setup on Xbox + if (mGLModeEnum==GL_POINTS) + { + pointBegin(mParticleCountRender, mWidth); + } +#else + if (mGLModeEnum==GL_POINTS && qglPointParameteriNV) + { + qglEnable(GL_POINT_SPRITE_NV); + + qglPointSize(mWidth); + qglPointParameterfEXT( GL_POINT_SIZE_MIN_EXT, 4.0f ); + qglPointParameterfEXT( GL_POINT_SIZE_MAX_EXT, 2047.0f ); + + qglTexEnvi(GL_POINT_SPRITE_NV, GL_COORD_REPLACE_NV, GL_TRUE); + } +#endif + else + { + qglEnable(GL_TEXTURE_2D); + qglDisable(GL_CULL_FACE); + + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mFilterMode==0)?(GL_LINEAR):(GL_NEAREST)); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (mFilterMode==0)?(GL_LINEAR):(GL_NEAREST)); + + + // Setup Matrix Mode And Translation + //----------------------------------- + qglMatrixMode(GL_MODELVIEW); + qglPushMatrix(); + +#ifdef _XBOX + qglBeginEXT(mGLModeEnum, mParticleCountRender*mVertexCount, mParticleCountRender, 0, mParticleCountRender*mVertexCount, 0); +#endif + } + + // Begin + //------- +#ifndef _XBOX + qglBegin(mGLModeEnum); +#endif + for (particleNum=0; particleNummFlags.get_bit(WFXParticle::FLAG_RENDER)) + { + continue; + } + + // If Oriented With Velocity, We Want To Calculate Vertx Offsets Differently For Each Particle + //--------------------------------------------------------------------------------------------- + if (mOrientWithVelocity) + { + partDirection = part->mVelocity; + VectorNormalize(partDirection.v); + mCameraDown = partDirection; + mCameraDown *= (mHeight * -1); + if (mVertexCount==4) + { + mCameraLeftPlusUp = (mCameraLeft - mCameraDown); + mCameraLeftMinusUp = (mCameraLeft + mCameraDown); + } + else + { + mCameraLeftPlusUp = (mCameraDown + mCameraLeft); + } + } + + // Blend Mode Zero -> Apply Alpha Just To Alpha Channel + //------------------------------------------------------ + if (mBlendMode==0) + { + qglColor4f(mColor[0], mColor[1], mColor[2], part->mAlpha); + } + + // Otherwise Apply Alpha To All Channels + //--------------------------------------- + else + { + qglColor4f(mColor[0]*part->mAlpha, mColor[1]*part->mAlpha, mColor[2]*part->mAlpha, mColor[3]*part->mAlpha); + } + + // Render A Point + //---------------- + if (mGLModeEnum==GL_POINTS) + { + qglVertex3fv(part->mPosition.v); + } + + // Render A Triangle + //------------------- + else if (mVertexCount==3) + { + qglTexCoord2f(1.0, 0.0); + qglVertex3f(part->mPosition[0], + part->mPosition[1], + part->mPosition[2]); + + qglTexCoord2f(0.0, 1.0); + qglVertex3f(part->mPosition[0] + mCameraLeft[0], + part->mPosition[1] + mCameraLeft[1], + part->mPosition[2] + mCameraLeft[2]); + + qglTexCoord2f(0.0, 0.0); + qglVertex3f(part->mPosition[0] + mCameraLeftPlusUp[0], + part->mPosition[1] + mCameraLeftPlusUp[1], + part->mPosition[2] + mCameraLeftPlusUp[2]); + } + + // Render A Quad + //--------------- + else + { + // Left bottom. + qglTexCoord2f( 0.0, 0.0 ); + qglVertex3f(part->mPosition[0] - mCameraLeftMinusUp[0], + part->mPosition[1] - mCameraLeftMinusUp[1], + part->mPosition[2] - mCameraLeftMinusUp[2] ); + + // Right bottom. + qglTexCoord2f( 1.0, 0.0 ); + qglVertex3f(part->mPosition[0] - mCameraLeftPlusUp[0], + part->mPosition[1] - mCameraLeftPlusUp[1], + part->mPosition[2] - mCameraLeftPlusUp[2] ); + + // Right top. + qglTexCoord2f( 1.0, 1.0 ); + qglVertex3f(part->mPosition[0] + mCameraLeftMinusUp[0], + part->mPosition[1] + mCameraLeftMinusUp[1], + part->mPosition[2] + mCameraLeftMinusUp[2] ); + + // Left top. + qglTexCoord2f( 0.0, 1.0 ); + qglVertex3f(part->mPosition[0] + mCameraLeftPlusUp[0], + part->mPosition[1] + mCameraLeftPlusUp[1], + part->mPosition[2] + mCameraLeftPlusUp[2] ); + } + } + qglEnd(); + + if (mGLModeEnum==GL_POINTS) + { +#ifdef _XBOX + pointEnd(); +#else + qglDisable(GL_POINT_SPRITE_NV); + qglTexEnvi(GL_POINT_SPRITE_NV, GL_COORD_REPLACE_NV, GL_FALSE); +#endif + } + else + { + qglEnable(GL_CULL_FACE); + qglPopMatrix(); + } + + mParticlesRendered += mParticleCountRender; + } +}; +ratl::vector_vs mParticleClouds; + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Init World Effects - Will Iterate Over All Particle Clouds, Clear Them Out, And Erase +//////////////////////////////////////////////////////////////////////////////////////// +void R_InitWorldEffects(void) +{ + for (int i=0; i1000.0f) + { + mMillisecondsElapsed = 1000.0f; + } + mSecondsElapsed = (mMillisecondsElapsed / 1000.0f); + + + // Make Sure We Are Always Outside Cached + //---------------------------------------- + if (!mOutside.Initialized()) + { + mOutside.Cache(); + } + else + { + // Update All Wind Zones + //----------------------- + if (!mFrozen) + { + mGlobalWindVelocity.Clear(); + for (int wz=0; wzofsFrames<0) // Compressed + { + frameSize = (int)( &((md4CompFrame_t *)0)->bones[ tr.currentModel->md4->numBones ] ); + newFrame = (md4Frame_t *)((byte *)header - header->ofsFrames + ent->e.frame * frameSize ); + oldFrame = (md4Frame_t *)((byte *)header - header->ofsFrames + ent->e.oldframe * frameSize ); + // HACK! These frames actually are md4CompFrames, but the first fields are the same, + // so this will work for this routine. + } + else + { + frameSize = (int)( &((md4Frame_t *)0)->bones[ tr.currentModel->md4->numBones ] ); + newFrame = (md4Frame_t *)((byte *)header + header->ofsFrames + ent->e.frame * frameSize ); + oldFrame = (md4Frame_t *)((byte *)header + header->ofsFrames + ent->e.oldframe * frameSize ); + } + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + + +/* +================= +R_AComputeFogNum + +================= +*/ +static int R_AComputeFogNum( md4Header_t *header, trRefEntity_t *ent ) { + int i; + fog_t *fog; + md4Frame_t *frame; + vec3_t localOrigin; + int frameSize; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + + if (header->ofsFrames<0) // Compressed + { + frameSize = (int)( &((md4CompFrame_t *)0)->bones[ header->numBones ] ); + frame = (md4Frame_t *)((byte *)header - header->ofsFrames + ent->e.frame * frameSize ); + // HACK! These frames actually are md4CompFrames, but the first fields are the same, + // so this will work for this routine. + } + else + { + frameSize = (int)( &((md4Frame_t *)0)->bones[ header->numBones ] ); + frame = (md4Frame_t *)((byte *)header + header->ofsFrames + ent->e.frame * frameSize ); + } + + VectorAdd( ent->e.origin, frame->localOrigin, localOrigin ); + int partialFog = 0; + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + if ( localOrigin[0] - frame->radius >= fog->bounds[0][0] + && localOrigin[0] + frame->radius <= fog->bounds[1][0] + && localOrigin[1] - frame->radius >= fog->bounds[0][1] + && localOrigin[1] + frame->radius <= fog->bounds[1][1] + && localOrigin[2] - frame->radius >= fog->bounds[0][2] + && localOrigin[2] + frame->radius <= fog->bounds[1][2] ) + {//totally inside it + return i; + break; + } + if ( ( localOrigin[0] - frame->radius >= fog->bounds[0][0] && localOrigin[1] - frame->radius >= fog->bounds[0][1] && localOrigin[2] - frame->radius >= fog->bounds[0][2] && + localOrigin[0] - frame->radius <= fog->bounds[1][0] && localOrigin[1] - frame->radius <= fog->bounds[1][1] && localOrigin[2] - frame->radius <= fog->bounds[1][2] ) || + ( localOrigin[0] + frame->radius >= fog->bounds[0][0] && localOrigin[1] + frame->radius >= fog->bounds[0][1] && localOrigin[2] + frame->radius >= fog->bounds[0][2] && + localOrigin[0] + frame->radius <= fog->bounds[1][0] && localOrigin[1] + frame->radius <= fog->bounds[1][1] && localOrigin[2] + frame->radius <= fog->bounds[1][2] ) ) + {//partially inside it + if ( tr.refdef.fogIndex == i || R_FogParmsMatch( tr.refdef.fogIndex, i ) ) + {//take new one only if it's the same one that the viewpoint is in + return i; + break; + } + else if ( !partialFog ) + {//first partialFog + partialFog = i; + } + } + } + //if all else fails, return the first partialFog + return partialFog; +} + +/* +============== +R_AddAnimSurfaces +============== +*/ +void R_AddAnimSurfaces( trRefEntity_t *ent ) { + md4Header_t *header; + md4Surface_t *surface; + md4LOD_t *lod; + shader_t *shader = 0; + shader_t *cust_shader = 0; + int fogNum = 0; + qboolean personalModel; + int cull; + int i, whichLod; + + // don't add third_person objects if not in a portal + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_CAP_FRAMES) { + if (ent->e.frame > tr.currentModel->md4->numFrames-1) + ent->e.frame = tr.currentModel->md4->numFrames-1; + if (ent->e.oldframe > tr.currentModel->md4->numFrames-1) + ent->e.oldframe = tr.currentModel->md4->numFrames-1; + } + else if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= tr.currentModel->md4->numFrames; + ent->e.oldframe %= tr.currentModel->md4->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( (ent->e.frame >= tr.currentModel->md4->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= tr.currentModel->md4->numFrames) + || (ent->e.oldframe < 0) ) + { +#ifdef _DEBUG + VID_Printf (PRINT_ALL, "R_AddAnimSurfaces: no such frame %d to %d for '%s'\n", +#else + VID_Printf (PRINT_DEVELOPER, "R_AddAnimSurfaces: no such frame %d to %d for '%s'\n", +#endif + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + header = tr.currentModel->md4; + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_ACullModel ( header, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // compute LOD + // + lod = (md4LOD_t *)( (byte *)header + header->ofsLODs ); + whichLod = R_ComputeLOD( ent ); + for ( i = 0; i < whichLod; i++) + { + lod = (md4LOD_t*)( (byte *)lod + lod->ofsEnd ); + } + + // + // set up lighting now that we know we aren't culled + // + if ( !personalModel || r_shadows->integer > 1 ) { +#ifdef VV_LIGHTING + VVLightMan.R_SetupEntityLighting( &tr.refdef, ent ); +#else + R_SetupEntityLighting( &tr.refdef, ent ); +#endif + } + + // + // see if we are in a fog volume + // + fogNum = R_AComputeFogNum( header, ent ); + + + // + // draw all surfaces + // + cust_shader = R_GetShaderByHandle( ent->e.customShader ); + + + surface = (md4Surface_t *)( (byte *)lod + lod->ofsSurfaces ); + for ( i = 0 ; i < lod->numSurfaces ; i++ ) { + if ( ent->e.customShader ) { + shader = cust_shader; + } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { + skin_t *skin; + int j; + + skin = R_GetSkinByHandle( ent->e.customSkin ); + + // match the surface name to something in the skin file + shader = tr.defaultShader; + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + // the names have both been lowercased + if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + } else { + shader = R_GetShaderByHandle( surface->shaderIndex ); + } + // we will add shadows even if the main object isn't visible in the view + + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 +#ifndef VV_LIGHTING + && fogNum == 0 +#endif + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t *)surface, tr.shadowShader, 0, qfalse ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t *)surface, tr.projectionShadowShader, 0, qfalse ); + } + + // don't add third_person objects if not viewing through a portal + if ( !personalModel ) { + R_AddDrawSurf( (surfaceType_t *)surface, shader, fogNum, qfalse ); + } + + surface = (md4Surface_t *)( (byte *)surface + surface->ofsEnd ); + } +} + + +/* +============== +RB_SurfaceAnim +============== +*/ +void RB_SurfaceAnim( md4Surface_t *surface ) { + int i, j, k; + float frontlerp, backlerp; + int *triangles; + int indexes; + int baseIndex, baseVertex; + int numVerts; + md4Vertex_t *v; + md4Bone_t bones[MD4_MAX_BONES]; + md4Bone_t tbone[2]; + md4Bone_t *bonePtr, *bone; + md4Header_t *header; + md4Frame_t *frame=0; + md4Frame_t *oldFrame=0; + md4CompFrame_t *cframe=0; + md4CompFrame_t *coldFrame=0; + int frameSize; + qboolean compressed; + + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + frontlerp = 1; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + frontlerp = 1.0 - backlerp; + } + header = (md4Header_t *)((byte *)surface + surface->ofsHeader); + + if (header->ofsFrames<0) // Compressed + { + compressed = qtrue; + frameSize = (int)( &((md4CompFrame_t *)0)->bones[ header->numBones ] ); + cframe = (md4CompFrame_t *)((byte *)header - header->ofsFrames + backEnd.currentEntity->e.frame * frameSize ); + coldFrame = (md4CompFrame_t *)((byte *)header - header->ofsFrames + backEnd.currentEntity->e.oldframe * frameSize ); + } + else + { + compressed = qfalse; + frameSize = (int)( &((md4Frame_t *)0)->bones[ header->numBones ] ); + frame = (md4Frame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.frame * frameSize ); + oldFrame = (md4Frame_t *)((byte *)header + header->ofsFrames + + backEnd.currentEntity->e.oldframe * frameSize ); + } + + + + RB_CheckOverflow( surface->numVerts, surface->numTriangles ); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + baseIndex = tess.numIndexes; + baseVertex = tess.numVertexes; + for (j = 0 ; j < indexes ; j++) { + tess.indexes[baseIndex + j] = baseVertex + triangles[j]; + } + tess.numIndexes += indexes; + + // + // lerp all the needed bones + // + if ( !backlerp && !compressed) + // no lerping needed + bonePtr = frame->bones; + else + { + bonePtr = bones; + if (compressed) + { + for ( i = 0 ; i < header->numBones ; i++ ) + { + if ( !backlerp ) + MC_UnCompress(bonePtr[i].matrix,cframe->bones[i].Comp); + else + { + MC_UnCompress(tbone[0].matrix,cframe->bones[i].Comp); + MC_UnCompress(tbone[1].matrix,coldFrame->bones[i].Comp); + for ( j = 0 ; j < 12 ; j++ ) + ((float *)&bonePtr[i])[j] = frontlerp * ((float *)&tbone[0])[j] + + backlerp * ((float *)&tbone[1])[j]; + } + } + } + else + { + for ( i = 0 ; i < header->numBones*12 ; i++ ) + ((float *)bonePtr)[i] = frontlerp * ((float *)frame->bones)[i] + + backlerp * ((float *)oldFrame->bones)[i]; + } + } + + // + // deform the vertexes by the lerped bones + // + numVerts = surface->numVerts; + v = (md4Vertex_t *) ((byte *)surface + surface->ofsVerts); + for ( j = 0; j < numVerts; j++ ) { + vec3_t tempVert, tempNormal; + md4Weight_t *w; + + VectorClear( tempVert ); + VectorClear( tempNormal ); + w = v->weights; + for ( k = 0 ; k < v->numWeights ; k++, w++ ) { + bone = bonePtr + w->boneIndex; + + tempVert[0] += w->boneWeight * ( DotProduct( bone->matrix[0], w->offset ) + bone->matrix[0][3] ); + tempVert[1] += w->boneWeight * ( DotProduct( bone->matrix[1], w->offset ) + bone->matrix[1][3] ); + tempVert[2] += w->boneWeight * ( DotProduct( bone->matrix[2], w->offset ) + bone->matrix[2][3] ); + + tempNormal[0] += w->boneWeight * DotProduct( bone->matrix[0], v->normal ); + tempNormal[1] += w->boneWeight * DotProduct( bone->matrix[1], v->normal ); + tempNormal[2] += w->boneWeight * DotProduct( bone->matrix[2], v->normal ); + } + + tess.xyz[baseVertex + j][0] = tempVert[0]; + tess.xyz[baseVertex + j][1] = tempVert[1]; + tess.xyz[baseVertex + j][2] = tempVert[2]; + + tess.normal[baseVertex + j][0] = tempNormal[0]; + tess.normal[baseVertex + j][1] = tempNormal[1]; + tess.normal[baseVertex + j][2] = tempNormal[2]; + + tess.texCoords[baseVertex + j][0][0] = v->texCoords[0]; + tess.texCoords[baseVertex + j][0][1] = v->texCoords[1]; + + v = (md4Vertex_t *)&v->weights[v->numWeights]; + } + + tess.numVertexes += surface->numVerts; +} \ No newline at end of file diff --git a/code/renderer/tr_arioche.cpp b/code/renderer/tr_arioche.cpp new file mode 100644 index 0000000..28746cf --- /dev/null +++ b/code/renderer/tr_arioche.cpp @@ -0,0 +1,149 @@ +#include "../server/exe_headers.h" + +#include "tr_local.h" +#include "tr_worldeffects.h" + +// Patches up the loaded map to handle the parameters passed from the UI + +// Remap sky to contents of the cvar ar_sky +// Grab sunlight properties from the indirected sky + +//void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset); +void R_ColorShiftLightingBytes( byte in[4], byte out[4] ); + +void NormalToLatLong( const vec3_t normal, byte bytes[2] ) +{ + // check for singularities + if (!normal[0] && !normal[1]) + { + if ( normal[2] > 0.0f ) + { + bytes[0] = 0; + bytes[1] = 0; // lat = 0, long = 0 + } + else + { + bytes[0] = 128; + bytes[1] = 0; // lat = 0, long = 128 + } + } + else + { + int a, b; + + a = (int)(RAD2DEG( (vec_t)atan2( normal[1], normal[0] ) ) * (255.0f / 360.0f )); + a &= 0xff; + + b = (int)(RAD2DEG( (vec_t)acos( normal[2] ) ) * ( 255.0f / 360.0f )); + b &= 0xff; + + bytes[0] = b; // longitude + bytes[1] = a; // lattitude + } +} + +void R_RMGInit(void) +{ + char newSky[MAX_QPATH]; + char newFog[MAX_QPATH]; + shader_t *sky; + shader_t *fog; + fog_t *gfog; + mgrid_t *grid; + char temp[MAX_QPATH]; + int i; + unsigned short *pos; + + Cvar_VariableStringBuffer("RMG_sky", newSky, MAX_QPATH); + // Get sunlight - this should set up all the sunlight data + sky = R_FindShader( newSky, lightmapsNone, stylesDefault, qfalse ); + + // Remap sky +// R_RemapShader("textures/tools/_sky", newSky, NULL); + + // Fill in the lightgrid with sunlight + if(tr.world->lightGridData) + { +#ifdef _XBOX + byte *memory = (byte *)tr.world->lightGridData; + + byte *array; + array = memory; + memory += 3; + + array[0] = (byte)Com_Clamp(0, 255, tr.sunAmbient[0] * 255.0f); + array[1] = (byte)Com_Clamp(0, 255, tr.sunAmbient[1] * 255.0f); + array[2] = (byte)Com_Clamp(0, 255, tr.sunAmbient[2] * 255.0f); + + array[3] = (byte)Com_Clamp(0, 255, tr.sunLight[0]); + array[4] = (byte)Com_Clamp(0, 255, tr.sunLight[1]); + array[5] = (byte)Com_Clamp(0, 255, tr.sunLight[2]); + + NormalToLatLong(tr.sunDirection, grid->latLong); +#else // _XBOX + grid = tr.world->lightGridData; + grid->ambientLight[0][0] = (byte)Com_Clamp(0, 255, tr.sunAmbient[0] * 255.0f); + grid->ambientLight[0][1] = (byte)Com_Clamp(0, 255, tr.sunAmbient[1] * 255.0f); + grid->ambientLight[0][2] = (byte)Com_Clamp(0, 255, tr.sunAmbient[2] * 255.0f); + R_ColorShiftLightingBytes(grid->ambientLight[0], grid->ambientLight[0]); + + grid->directLight[0][0] = (byte)Com_Clamp(0, 255, tr.sunLight[0]); + grid->directLight[0][1] = (byte)Com_Clamp(0, 255, tr.sunLight[1]); + grid->directLight[0][2] = (byte)Com_Clamp(0, 255, tr.sunLight[2]); + R_ColorShiftLightingBytes(grid->directLight[0], grid->directLight[0]); + + NormalToLatLong(tr.sunDirection, grid->latLong); +#endif // _XBOX + + pos = tr.world->lightGridArray; + for(i=0;inumGridArrayElements;i++) + { + *pos = 0; + pos++; + } + } + + // Override the global fog with the defined one + if(tr.world->globalFog != -1) + { + Cvar_VariableStringBuffer("RMG_fog", newFog, MAX_QPATH); + fog = R_FindShader( newFog, lightmapsNone, stylesDefault, qfalse); + if (fog != tr.defaultShader) + { + gfog = tr.world->fogs + tr.world->globalFog; + gfog->parms = *fog->fogParms; + if (gfog->parms.depthForOpaque) + { + gfog->tcScale = 1.0f / ( gfog->parms.depthForOpaque * 8.0f ); + tr.distanceCull = gfog->parms.depthForOpaque; + Cvar_Set("RMG_distancecull", va("%f", tr.distanceCull)); + } + else + { + gfog->tcScale = 1.0f; + } + gfog->colorInt = ColorBytes4 ( gfog->parms.color[0], + gfog->parms.color[1], + gfog->parms.color[2], 1.0f ); + } + } + + Cvar_VariableStringBuffer("RMG_weather", temp, MAX_QPATH); + + // Set up any weather effects + switch(atol(temp)) + { + case 0: + break; + case 1: + R_WorldEffectCommand("rain init 1000"); + R_WorldEffectCommand("rain outside"); + break; + case 2: + R_WorldEffectCommand("snow init 1000 outside"); + R_WorldEffectCommand("snow outside"); + break; + } +} + +// end diff --git a/code/renderer/tr_backend.cpp b/code/renderer/tr_backend.cpp new file mode 100644 index 0000000..4050445 --- /dev/null +++ b/code/renderer/tr_backend.cpp @@ -0,0 +1,2073 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + +#ifdef VV_LIGHTING +#include "tr_lightmanager.h" +#endif + +#ifdef _XBOX +#include "../win32/glw_win_dx8.h" +#include "../win32/win_highdynamicrange.h" +#endif + +backEndData_t *backEndData; +backEndState_t backEnd; + +bool tr_stencilled = false; +extern qboolean tr_distortionPrePost; //tr_shadows.cpp +extern qboolean tr_distortionNegate; //tr_shadows.cpp +extern void RB_CaptureScreenImage(void); //tr_shadows.cpp +extern void RB_DistortionFill(void); //tr_shadows.cpp +static void RB_DrawGlowOverlay(); +static void RB_BlurGlowTexture(); + +// Whether we are currently rendering only glowing objects or not. +bool g_bRenderGlowingObjects = false; + +// Whether the current hardware supports dynamic glows/flares. +bool g_bDynamicGlowSupported = false; + +static const float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) +#if defined (_XBOX) + 0, 0, 1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +#else + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +#endif +}; + + +/* +** GL_Bind +*/ +void GL_Bind( image_t *image ) { + int texnum; + + if ( !image ) { + VID_Printf( PRINT_WARNING, "GL_Bind: NULL image\n" ); + texnum = tr.defaultImage->texnum; + } else { + texnum = image->texnum; + } + +#ifndef _XBOX + if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option + texnum = tr.dlightImage->texnum; + } +#endif + + if ( glState.currenttextures[glState.currenttmu] != texnum ) { +#ifndef _XBOX + image->frameUsed = tr.frameCount; +#endif + glState.currenttextures[glState.currenttmu] = texnum; + qglBindTexture (GL_TEXTURE_2D, texnum); + } +} + +/* +** GL_SelectTexture +*/ +void GL_SelectTexture( int unit ) +{ + if ( glState.currenttmu == unit ) + { + return; + } + + if ( unit == 0 ) + { + qglActiveTextureARB( GL_TEXTURE0_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE0_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE0_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE0_ARB )\n" ); + } + else if ( unit == 1 ) + { + qglActiveTextureARB( GL_TEXTURE1_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE1_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE1_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE1_ARB )\n" ); + } + else if ( unit == 2 ) + { + qglActiveTextureARB( GL_TEXTURE2_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE2_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE2_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE2_ARB )\n" ); + } + else if ( unit == 3 ) + { + qglActiveTextureARB( GL_TEXTURE3_ARB ); + GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE3_ARB )\n" ); + qglClientActiveTextureARB( GL_TEXTURE3_ARB ); + GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE3_ARB )\n" ); + } + else { + Com_Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit ); + } + + glState.currenttmu = unit; +} + + +/* +** GL_Cull +*/ +void GL_Cull( int cullType ) { + if ( glState.faceCulling == cullType ) { + return; + } + glState.faceCulling = cullType; + if (backEnd.projection2D){ //don't care, we're in 2d when it's always disabled + return; + } + + if ( cullType == CT_TWO_SIDED ) + { + qglDisable( GL_CULL_FACE ); + } + else + { + qglEnable( GL_CULL_FACE ); + + if ( cullType == CT_BACK_SIDED ) + { + if ( backEnd.viewParms.isMirror ) + { + qglCullFace( GL_FRONT ); + } + else + { + qglCullFace( GL_BACK ); + } + } + else + { + if ( backEnd.viewParms.isMirror ) + { + qglCullFace( GL_BACK ); + } + else + { + qglCullFace( GL_FRONT ); + } + } + } +} + +/* +** GL_TexEnv +*/ +void GL_TexEnv( int env ) +{ + if ( env == glState.texEnv[glState.currenttmu] ) + { + return; + } + + glState.texEnv[glState.currenttmu] = env; + + + switch ( env ) + { + case GL_MODULATE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + break; + case GL_REPLACE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + break; + case GL_DECAL: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); + break; + case GL_ADD: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); + break; +#ifdef _XBOX + case GL_NONE: + qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_NONE ); + break; +#endif + default: + Com_Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed\n", env ); + break; + } +} + +/* +** GL_State +** +** This routine is responsible for setting the most commonly changed state +** in Q3. +*/ +void GL_State( unsigned long stateBits ) +{ + unsigned long diff = stateBits ^ glState.glStateBits; + + if ( !diff ) + { + return; + } + + // + // check depthFunc bits + // + if ( diff & GLS_DEPTHFUNC_EQUAL ) + { + if ( stateBits & GLS_DEPTHFUNC_EQUAL ) + { + qglDepthFunc( GL_EQUAL ); + } + else + { + qglDepthFunc( GL_LEQUAL ); + } + } + + // + // check blend bits + // + if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + GLenum srcFactor, dstFactor; + + if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) + { + switch ( stateBits & GLS_SRCBLEND_BITS ) + { + case GLS_SRCBLEND_ZERO: + srcFactor = GL_ZERO; + break; + case GLS_SRCBLEND_ONE: + srcFactor = GL_ONE; + break; + case GLS_SRCBLEND_DST_COLOR: + srcFactor = GL_DST_COLOR; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: + srcFactor = GL_ONE_MINUS_DST_COLOR; + break; + case GLS_SRCBLEND_SRC_ALPHA: + srcFactor = GL_SRC_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: + srcFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_SRCBLEND_DST_ALPHA: + srcFactor = GL_DST_ALPHA; + break; + case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: + srcFactor = GL_ONE_MINUS_DST_ALPHA; + break; + case GLS_SRCBLEND_ALPHA_SATURATE: + srcFactor = GL_SRC_ALPHA_SATURATE; + break; + default: + srcFactor = GL_ONE; // to get warning to shut up + Com_Error( ERR_DROP, "GL_State: invalid src blend state bits\n" ); + break; + } + + switch ( stateBits & GLS_DSTBLEND_BITS ) + { + case GLS_DSTBLEND_ZERO: + dstFactor = GL_ZERO; + break; + case GLS_DSTBLEND_ONE: + dstFactor = GL_ONE; + break; + case GLS_DSTBLEND_SRC_COLOR: + dstFactor = GL_SRC_COLOR; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: + dstFactor = GL_ONE_MINUS_SRC_COLOR; + break; + case GLS_DSTBLEND_SRC_ALPHA: + dstFactor = GL_SRC_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: + dstFactor = GL_ONE_MINUS_SRC_ALPHA; + break; + case GLS_DSTBLEND_DST_ALPHA: + dstFactor = GL_DST_ALPHA; + break; + case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: + dstFactor = GL_ONE_MINUS_DST_ALPHA; + break; + default: + dstFactor = GL_ONE; // to get warning to shut up + Com_Error( ERR_DROP, "GL_State: invalid dst blend state bits\n" ); + break; + } + + qglEnable( GL_BLEND ); + qglBlendFunc( srcFactor, dstFactor ); + } + else + { + qglDisable( GL_BLEND ); + } + } + + // + // check depthmask + // + if ( diff & GLS_DEPTHMASK_TRUE ) + { + if ( stateBits & GLS_DEPTHMASK_TRUE ) + { + qglDepthMask( GL_TRUE ); + } + else + { + qglDepthMask( GL_FALSE ); + } + } + + // + // fill/line mode + // + if ( diff & GLS_POLYMODE_LINE ) + { + if ( stateBits & GLS_POLYMODE_LINE ) + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + } + else + { + qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + + // + // depthtest + // + if ( diff & GLS_DEPTHTEST_DISABLE ) + { + if ( stateBits & GLS_DEPTHTEST_DISABLE ) + { + qglDisable( GL_DEPTH_TEST ); + } + else + { + qglEnable( GL_DEPTH_TEST ); + } + } + + // + // alpha test + // + if ( diff & GLS_ATEST_BITS ) + { + switch ( stateBits & GLS_ATEST_BITS ) + { + case 0: + qglDisable( GL_ALPHA_TEST ); + break; + case GLS_ATEST_GT_0: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GREATER, 0.0f ); + break; + case GLS_ATEST_LT_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_LESS, 0.5f ); + break; + case GLS_ATEST_GE_80: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GEQUAL, 0.5f ); + break; + case GLS_ATEST_GE_C0: + qglEnable( GL_ALPHA_TEST ); + qglAlphaFunc( GL_GEQUAL, 0.75f ); + break; + default: + assert( 0 ); + break; + } + } + + glState.glStateBits = stateBits; +} + + + +/* +================ +RB_Hyperspace + +A player has predicted a teleport, but hasn't arrived yet +================ +*/ +static void RB_Hyperspace( void ) { + float c; + + if ( !backEnd.isHyperspace ) { + // do initialization shit + } + + c = ( backEnd.refdef.time & 255 ) / 255.0f; + qglClearColor( c, c, c, 1 ); + qglClear( GL_COLOR_BUFFER_BIT ); + + backEnd.isHyperspace = qtrue; +} + + +void SetViewportAndScissor( void ) { + qglMatrixMode(GL_PROJECTION); + qglLoadMatrixf( backEnd.viewParms.projectionMatrix ); + qglMatrixMode(GL_MODELVIEW); + + // set the window clipping + qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, + backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); +} + +/* +================= +RB_BeginDrawingView + +Any mirrored or portaled views have already been drawn, so prepare +to actually render the visible surfaces for this view +================= +*/ +void RB_BeginDrawingView (void) { + int clearBits = GL_DEPTH_BUFFER_BIT; + + // sync with gl if needed + if ( r_finish->integer == 1 && !glState.finishCalled ) { + qglFinish (); + glState.finishCalled = qtrue; + } + if ( r_finish->integer == 0 ) { + glState.finishCalled = qtrue; + } + + // we will need to change the projection matrix before drawing + // 2D images again + backEnd.projection2D = qfalse; + + // + // set the modelview matrix for the viewer + // + SetViewportAndScissor(); + + // ensures that depth writes are enabled for the depth clear + GL_State( GLS_DEFAULT ); + + // clear relevant buffers + if ( r_measureOverdraw->integer || r_shadows->integer == 2 || tr_stencilled ) + { + clearBits |= GL_STENCIL_BUFFER_BIT; + tr_stencilled = false; + } + + if (skyboxportal) + { + if ( backEnd.refdef.rdflags & RDF_SKYBOXPORTAL ) + { // portal scene, clear whatever is necessary + if (r_fastsky->integer || (backEnd.refdef.rdflags & RDF_NOWORLDMODEL) ) + { // fastsky: clear color + // try clearing first with the portal sky fog color, then the world fog color, then finally a default + clearBits |= GL_COLOR_BUFFER_BIT; + if (tr.world && tr.world->globalFog != -1) + { + const fog_t *fog = &tr.world->fogs[tr.world->globalFog]; + qglClearColor(fog->parms.color[0], fog->parms.color[1], fog->parms.color[2], 1.0f ); + } + else + { + qglClearColor ( 0.3f, 0.3f, 0.3f, 1.0 ); + } + } + } + } + else + { + if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) && !g_bRenderGlowingObjects ) + { + if (tr.world && tr.world->globalFog != -1) + { + const fog_t *fog = &tr.world->fogs[tr.world->globalFog]; + qglClearColor(fog->parms.color[0], fog->parms.color[1], fog->parms.color[2], 1.0f ); + } + else + { + qglClearColor( 0.3f, 0.3f, 0.3f, 1 ); // FIXME: get color of sky + } + clearBits |= GL_COLOR_BUFFER_BIT; // FIXME: only if sky shaders have been used + } + } + + if ( !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) && ( r_DynamicGlow->integer && !g_bRenderGlowingObjects ) ) + { + if (tr.world && tr.world->globalFog != -1) + { //this is because of a bug in multiple scenes I think, it needs to clear for the second scene but it doesn't normally. + const fog_t *fog = &tr.world->fogs[tr.world->globalFog]; + + qglClearColor(fog->parms.color[0], fog->parms.color[1], fog->parms.color[2], 1.0f ); + clearBits |= GL_COLOR_BUFFER_BIT; + } + } + // If this pass is to just render the glowing objects, don't clear the depth buffer since + // we're sharing it with the main scene (since the main scene has already been rendered). -AReis + if ( g_bRenderGlowingObjects ) + { + clearBits &= ~GL_DEPTH_BUFFER_BIT; + } + + if (clearBits) + { + qglClear( clearBits ); + } + + if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) ) + { + RB_Hyperspace(); + return; + } + else + { + backEnd.isHyperspace = qfalse; + } + + glState.faceCulling = -1; // force face culling to set next time + + // we will only draw a sun if there was sky rendered in this view + backEnd.skyRenderedThisView = qfalse; + + // clip to the plane of the portal + if ( backEnd.viewParms.isPortal ) { + float plane[4]; + double plane2[4]; + + plane[0] = backEnd.viewParms.portalPlane.normal[0]; + plane[1] = backEnd.viewParms.portalPlane.normal[1]; + plane[2] = backEnd.viewParms.portalPlane.normal[2]; + plane[3] = backEnd.viewParms.portalPlane.dist; + + plane2[0] = DotProduct (backEnd.viewParms.or.axis[0], plane); + plane2[1] = DotProduct (backEnd.viewParms.or.axis[1], plane); + plane2[2] = DotProduct (backEnd.viewParms.or.axis[2], plane); + plane2[3] = DotProduct (plane, backEnd.viewParms.or.origin) - plane[3]; + + qglLoadMatrixf( s_flipMatrix ); + qglClipPlane (GL_CLIP_PLANE0, plane2); + qglEnable (GL_CLIP_PLANE0); + } else { + qglDisable (GL_CLIP_PLANE0); + } +} + +#define MAC_EVENT_PUMP_MSEC 5 + +//used by RF_DISTORTION +static inline bool R_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y) +{ + int xcenter, ycenter; + vec3_t local, transformed; + vec3_t vfwd; + vec3_t vright; + vec3_t vup; + float xzi; + float yzi; + + xcenter = glConfig.vidWidth / 2; + ycenter = glConfig.vidHeight / 2; + + //AngleVectors (tr.refdef.viewangles, vfwd, vright, vup); + VectorCopy(tr.refdef.viewaxis[0], vfwd); + VectorCopy(tr.refdef.viewaxis[1], vright); + VectorCopy(tr.refdef.viewaxis[2], vup); + + VectorSubtract (worldCoord, tr.refdef.vieworg, local); + + transformed[0] = DotProduct(local,vright); + transformed[1] = DotProduct(local,vup); + transformed[2] = DotProduct(local,vfwd); + + // Make sure Z is not negative. + if(transformed[2] < 0.01) + { + return false; + } + + xzi = xcenter / transformed[2] * (90.0/tr.refdef.fov_x); + yzi = ycenter / transformed[2] * (90.0/tr.refdef.fov_y); + + *x = xcenter + xzi * transformed[0]; + *y = ycenter - yzi * transformed[1]; + + return true; +} + +//used by RF_DISTORTION +static inline bool R_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y ) +{ + float xF, yF; + bool retVal = R_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF ); + *x = (int)xF; + *y = (int)yF; + return retVal; +} + +/* +================== +RB_RenderDrawSurfList +================== +*/ +//number of possible surfs we can postrender. +//note that postrenders lack much of the optimization that the standard sort-render crap does, +//so it's slower. +#define MAX_POST_RENDERS 128 + +typedef struct +{ + int fogNum; + int entNum; + int dlighted; + int depthRange; + drawSurf_t *drawSurf; + shader_t *shader; +} postRender_t; + +static postRender_t g_postRenders[MAX_POST_RENDERS]; +static int g_numPostRenders = 0; + +void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader, *oldShader; + int fogNum, oldFogNum; + int entityNum, oldEntityNum; + int dlighted, oldDlighted; + int depthRange, oldDepthRange; + int i; + drawSurf_t *drawSurf; + unsigned int oldSort; + float originalTime; + trRefEntity_t *curEnt; + postRender_t *pRender; + bool didShadowPass = false; +#ifdef __MACOS__ + int macEventTime; + + Sys_PumpEvents(); // crutch up the mac's limited buffer queue size + + // we don't want to pump the event loop too often and waste time, so + // we are going to check every shader change + macEventTime = Sys_Milliseconds() + MAC_EVENT_PUMP_MSEC; +#endif + + if (g_bRenderGlowingObjects) + { //only shadow on initial passes + didShadowPass = true; + } + + // save original time for entity shader offsets + originalTime = backEnd.refdef.floatTime; + + // clear the z buffer, set the modelview, etc + RB_BeginDrawingView (); + + // draw everything + oldEntityNum = -1; + backEnd.currentEntity = &tr.worldEntity; + oldShader = NULL; + oldFogNum = -1; + oldDepthRange = qfalse; + oldDlighted = qfalse; + oldSort = (unsigned int) -1; + depthRange = qfalse; + + backEnd.pc.c_surfaces += numDrawSurfs; + + for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) + { + if ( drawSurf->sort == oldSort ) + { + // fast path, same as previous sort + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + continue; + } + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted ); + +#ifdef _XBOX + tr.currentEntityNum = entityNum; +#endif + +#ifndef _XBOX // GLOWXXX + // If we're rendering glowing objects, but this shader has no stages with glow, skip it! + if ( g_bRenderGlowingObjects && !shader->hasGlow ) + { + shader = oldShader; + entityNum = oldEntityNum; + fogNum = oldFogNum; + dlighted = oldDlighted; + continue; + } +#endif + oldSort = drawSurf->sort; + + // + // change the tess parameters if needed + // a "entityMergable" shader is a shader that can have surfaces from seperate + // entities merged into a single batch, like smoke and blood puff sprites + if (entityNum != TR_WORLDENT && + g_numPostRenders < MAX_POST_RENDERS) + { + if ( (backEnd.refdef.entities[entityNum].e.renderfx & RF_DISTORTION)/* || + (backEnd.refdef.entities[entityNum].e.renderfx & RF_FORCE_ENT_ALPHA)*/) + //not sure if we need this alpha fix for sp or not, leaving it out for now -rww + { //must render last + curEnt = &backEnd.refdef.entities[entityNum]; + pRender = &g_postRenders[g_numPostRenders]; + + g_numPostRenders++; + + depthRange = 0; + //figure this stuff out now and store it + if ( curEnt->e.renderfx & RF_NODEPTH ) + { + depthRange = 2; + } + else if ( curEnt->e.renderfx & RF_DEPTHHACK ) + { + depthRange = 1; + } + pRender->depthRange = depthRange; + + //It is not necessary to update the old* values because + //we are not updating now with the current values. + depthRange = oldDepthRange; + + //store off the ent num + pRender->entNum = entityNum; + + //remember the other values necessary for rendering this surf + pRender->drawSurf = drawSurf; + pRender->dlighted = dlighted; + pRender->fogNum = fogNum; + pRender->shader = shader; + + //assure the info is back to the last set state + shader = oldShader; + entityNum = oldEntityNum; + fogNum = oldFogNum; + dlighted = oldDlighted; + + oldSort = (unsigned int)-1; //invalidate this thing, cause we may want to postrender more surfs of the same sort + + //continue without bothering to begin a draw surf + continue; + } + } + + if (shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted + || ( entityNum != oldEntityNum && !shader->entityMergable ) ) + { + if (oldShader != NULL) { +#ifdef __MACOS__ // crutch up the mac's limited buffer queue size + int t; + + t = Sys_Milliseconds(); + if ( t > macEventTime ) { + macEventTime = t + MAC_EVENT_PUMP_MSEC; + Sys_PumpEvents(); + } +#endif + RB_EndSurface(); + +//#ifdef _XBOX +// if (!didShadowPass && shader && shader->sort > SS_BANNER && shader != tr.projectionShadowShader) +//#else + if (!didShadowPass && shader && shader->sort > SS_BANNER) +//#endif + { + RB_ShadowFinish(); + didShadowPass = true; + } + } + RB_BeginSurface( shader, fogNum ); + oldShader = shader; + oldFogNum = fogNum; + oldDlighted = dlighted; + } + + // + // change the modelview matrix if needed + // + if ( entityNum != oldEntityNum ) { + depthRange = qfalse; + + if ( entityNum != TR_WORLDENT ) { + backEnd.currentEntity = &backEnd.refdef.entities[entityNum]; + backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime; + + // set up the transformation matrix + R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.ori ); + + // set up the dynamic lighting if needed + if ( backEnd.currentEntity->needDlights ) { +#ifdef VV_LIGHTING + VVLightMan.R_TransformDlights( &backEnd.ori ); +#else + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori ); +#endif + } + + if ( backEnd.currentEntity->e.renderfx & RF_NODEPTH ) { + // No depth at all, very rare but some things for seeing through walls + depthRange = 2; + } + else if ( backEnd.currentEntity->e.renderfx & RF_DEPTHHACK ) { + // hack the depth range to prevent view model from poking into walls + depthRange = qtrue; + } + } else { + backEnd.currentEntity = &tr.worldEntity; + backEnd.refdef.floatTime = originalTime; + backEnd.ori = backEnd.viewParms.world; +#ifdef VV_LIGHTING + VVLightMan.R_TransformDlights( &backEnd.ori ); +#else + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori ); +#endif + } + + qglLoadMatrixf( backEnd.ori.modelMatrix ); + + // + // change depthrange if needed + // + if ( oldDepthRange != depthRange ) { + switch ( depthRange ) { + default: + case 0: + qglDepthRange (0, 1); + break; + + case 1: + qglDepthRange (0, .3); + break; + + case 2: + qglDepthRange (0, 0); + break; + } + + oldDepthRange = depthRange; + } + + oldEntityNum = entityNum; + } + + // add the triangles for this surface + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + } + + // draw the contents of the last shader batch + if (oldShader != NULL) { + RB_EndSurface(); + } + + if (tr_stencilled && tr_distortionPrePost) + { //ok, cap it now + RB_CaptureScreenImage(); + RB_DistortionFill(); + } + + //render distortion surfs (or anything else that needs to be post-rendered) + if (g_numPostRenders > 0) + { + int lastPostEnt = -1; + + while (g_numPostRenders > 0) + { + g_numPostRenders--; + pRender = &g_postRenders[g_numPostRenders]; + + RB_BeginSurface( pRender->shader, pRender->fogNum ); + + backEnd.currentEntity = &backEnd.refdef.entities[pRender->entNum]; + + backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime; + + // set up the transformation matrix + R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.ori ); + + // set up the dynamic lighting if needed + if ( backEnd.currentEntity->needDlights ) + { +#ifdef VV_LIGHTING + VVLightMan.R_TransformDlights( &backEnd.ori ); +#else + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori ); +#endif + } + + qglLoadMatrixf( backEnd.ori.modelMatrix ); + + depthRange = pRender->depthRange; + switch ( depthRange ) + { + default: + case 0: + qglDepthRange (0, 1); + break; + + case 1: + qglDepthRange (0, .3); + break; + + case 2: + qglDepthRange (0, 0); + break; + } + + if ((backEnd.currentEntity->e.renderfx & RF_DISTORTION) && + lastPostEnt != pRender->entNum) + { //do the capture now, we only need to do it once per ent + int x, y; + int rad = backEnd.currentEntity->e.radius; + + // Hack - prevent this from using + if( rad > SCREEN_IMAGE_MAX_HEIGHT ) + { +#ifndef FINAL_BUILD + Com_Printf( "WARNING: Shrinking screenImage\n" ); +#endif + rad = SCREEN_IMAGE_MAX_HEIGHT; + } + + //We are going to just bind this, and then the CopyTexImage is going to + //stomp over this texture num in texture memory. + GL_Bind( tr.screenImage ); + + if (R_WorldCoordToScreenCoord( backEnd.currentEntity->e.origin, &x, &y )) + { + int cX, cY; + cX = glConfig.vidWidth-x-(rad/2); + cY = glConfig.vidHeight-y-(rad/2); + + if (cX+rad > glConfig.vidWidth) + { //would it go off screen? + cX = glConfig.vidWidth-rad; + } + else if (cX < 0) + { //cap it off at 0 + cX = 0; + } + + if (cY+rad > glConfig.vidHeight) + { //would it go off screen? + cY = glConfig.vidHeight-rad; + } + else if (cY < 0) + { //cap it off at 0 + cY = 0; + } + + //now copy a portion of the screen to this texture +#ifdef _XBOX + qglCopyBackBufferToTexEXT(rad, rad, cX, (480 - cY), (cX + rad), (480 - (cY + rad))); +#else + qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, cX, cY, rad, rad, 0); +#endif + + lastPostEnt = pRender->entNum; + } + } + + rb_surfaceTable[ *pRender->drawSurf->surface ]( pRender->drawSurf->surface ); + RB_EndSurface(); + } + } + + // go back to the world modelview matrix + qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + if ( depthRange ) { + qglDepthRange (0, 1); + } + +#if 0 + RB_DrawSun(); +#endif + if (tr_stencilled && !tr_distortionPrePost) + { //draw in the stencil buffer's cutout + RB_DistortionFill(); + } + if (!didShadowPass) + { + // darken down any stencil shadows + RB_ShadowFinish(); + didShadowPass = true; + } + +#ifdef _XBOX + if (r_hdreffect->integer) + { +// HDREffect.Render(); + } +#endif + + // add light flares on lights that aren't obscured +// RB_RenderFlares(); + +#ifdef __MACOS__ + Sys_PumpEvents(); // crutch up the mac's limited buffer queue size +#endif +} + + +#ifdef _XBOX +static unsigned short indexList[24] = { 0, 3, 2, 1, + 1, 2, 6, 5, + 5, 6, 7, 4, + 4, 7, 3, 0, + 3, 7, 6, 2, + 4, 0, 1, 5 }; + +void RB_RunVisTest(int number, vec3_t bounds[2]) +{ + glw_state->device->SetTransform(D3DTS_VIEW, glw_state->matrixStack[glwstate_t::MatrixMode_Model]->GetTop()); + if(glw_state->matricesDirty[glwstate_t::MatrixMode_Projection]) + glw_state->device->SetTransform(D3DTS_PROJECTION, glw_state->matrixStack[glwstate_t::MatrixMode_Projection]->GetTop()); + + GL_Bind(tr.whiteImage); + glw_state->device->SetTexture(0, NULL); + + GL_State(GLS_DEFAULT); + + DWORD cullmode, zwrite; + glw_state->device->GetRenderState(D3DRS_CULLMODE, &cullmode); + glw_state->device->GetRenderState(D3DRS_ZWRITEENABLE, &zwrite); + + glw_state->device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + glw_state->device->SetRenderState(D3DRS_ZWRITEENABLE, false); + glw_state->device->SetRenderState(D3DRS_COLORWRITEENABLE, 0); + + float fZOffset = -4.0f; + float fZSlopeScale = -2.0f; + glw_state->device->SetRenderState( D3DRS_SOLIDOFFSETENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_POLYGONOFFSETZOFFSET, *((DWORD*)&fZOffset) ); + glw_state->device->SetRenderState( D3DRS_POLYGONOFFSETZSLOPESCALE, *((DWORD*)&fZSlopeScale) ); + + glw_state->device->SetVertexShader(D3DFVF_XYZ); + + D3DVECTOR box[8]; + box[0].x = bounds[0][0]; + box[0].y = bounds[0][1]; + box[0].z = bounds[0][2]; + + box[1].x = bounds[1][0]; + box[1].y = bounds[0][1]; + box[1].z = bounds[0][2]; + + box[2].x = bounds[1][0]; + box[2].y = bounds[1][1]; + box[2].z = bounds[0][2]; + + box[3].x = bounds[0][0]; + box[3].y = bounds[1][1]; + box[3].z = bounds[0][2]; + + box[4].x = bounds[0][0]; + box[4].y = bounds[0][1]; + box[4].z = bounds[1][2]; + + box[5].x = bounds[1][0]; + box[5].y = bounds[0][1]; + box[5].z = bounds[1][2]; + + box[6].x = bounds[1][0]; + box[6].y = bounds[1][1]; + box[6].z = bounds[1][2]; + + box[7].x = bounds[0][0]; + box[7].y = bounds[1][1]; + box[7].z = bounds[1][2]; + + glw_state->device->BeginVisibilityTest(); + glw_state->device->DrawIndexedPrimitiveUP(D3DPT_QUADLIST, 0, 0, 6, indexList, D3DFMT_INDEX16, &box[0], 12); + glw_state->device->EndVisibilityTest(number); + + glw_state->device->SetRenderState(D3DRS_CULLMODE, cullmode); + glw_state->device->SetRenderState(D3DRS_ZWRITEENABLE, zwrite); + glw_state->device->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALL); + glw_state->device->SetRenderState( D3DRS_SOLIDOFFSETENABLE, FALSE ); +} +#endif + + +/* +============================================================================ + +RENDER BACK END THREAD FUNCTIONS + +============================================================================ +*/ + +/* +================ +RB_SetGL2D + +================ +*/ +void RB_SetGL2D (void) { + backEnd.projection2D = qtrue; + + // set 2D virtual screen size + qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); + qglMatrixMode(GL_PROJECTION); + qglLoadIdentity (); +#ifdef _XBOX + extern int Menus_AnyFullScreenVisible(void); + if(glw_state->isWidescreen && !(Menus_AnyFullScreenVisible()) && cls.state == CA_ACTIVE) + qglOrtho (0, 720, 0, 480, 0, 1); + else + qglOrtho (0, 640, 0, 480, 0, 1); +#else + qglOrtho (0, 640, 480, 0, 0, 1); +#endif + qglMatrixMode(GL_MODELVIEW); + qglLoadIdentity (); + + GL_State( GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglDisable( GL_CULL_FACE ); + qglDisable( GL_CLIP_PLANE0 ); + + // set time for 2D shaders + backEnd.refdef.time = Sys_Milliseconds(); + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001; +} + + +/* +============= +RB_SetColor + +============= +*/ +const void *RB_SetColor( const void *data ) { + const setColorCommand_t *cmd; + + cmd = (const setColorCommand_t *)data; + + backEnd.color2D[0] = cmd->color[0] * 255; + backEnd.color2D[1] = cmd->color[1] * 255; + backEnd.color2D[2] = cmd->color[2] * 255; + backEnd.color2D[3] = cmd->color[3] * 255; + + return (const void *)(cmd + 1); +} + +/* +============= +RB_StretchPic +============= +*/ +const void *RB_StretchPic ( const void *data ) { + const stretchPicCommand_t *cmd; + shader_t *shader; + int numVerts, numIndexes; + + cmd = (const stretchPicCommand_t *)data; + + shader = cmd->shader; + if ( shader != tess.shader ) { + if ( tess.numIndexes ) { + RB_EndSurface(); //this might change culling and other states + } + backEnd.currentEntity = &backEnd.entity2D; + RB_BeginSurface( shader, 0 ); + } + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); //set culling and other states + } + + RB_CHECKOVERFLOW( 4, 6 ); + numVerts = tess.numVertexes; + numIndexes = tess.numIndexes; + + tess.numVertexes += 4; + tess.numIndexes += 6; + + tess.indexes[ numIndexes ] = numVerts + 3; + tess.indexes[ numIndexes + 1 ] = numVerts + 0; + tess.indexes[ numIndexes + 2 ] = numVerts + 2; + tess.indexes[ numIndexes + 3 ] = numVerts + 2; + tess.indexes[ numIndexes + 4 ] = numVerts + 0; + tess.indexes[ numIndexes + 5 ] = numVerts + 1; + + *(int *)tess.vertexColors[ numVerts ] = + *(int *)tess.vertexColors[ numVerts + 1 ] = + *(int *)tess.vertexColors[ numVerts + 2 ] = + *(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)backEnd.color2D; + + tess.xyz[ numVerts ][0] = cmd->x; + tess.xyz[ numVerts ][1] = cmd->y; + tess.xyz[ numVerts ][2] = 0; + + tess.texCoords[ numVerts ][0][0] = cmd->s1; + tess.texCoords[ numVerts ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 1 ][1] = cmd->y; + tess.xyz[ numVerts + 1 ][2] = 0; + + tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1; + + tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w; + tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 2 ][2] = 0; + + tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2; + tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2; + + tess.xyz[ numVerts + 3 ][0] = cmd->x; + tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h; + tess.xyz[ numVerts + 3 ][2] = 0; + + tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1; + tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2; + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawRotatePic +============= +*/ +const void *RB_RotatePic ( const void *data ) +{ + const rotatePicCommand_t *cmd; + image_t *image; + shader_t *shader; + + cmd = (const rotatePicCommand_t *)data; + + shader = cmd->shader; + image = &shader->stages[0].bundle[0].image[0]; + + if ( image ) { + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + qglColor4ubv( backEnd.color2D ); + qglPushMatrix(); + + qglTranslatef(cmd->x+cmd->w,cmd->y,0); + qglRotatef(cmd->a, 0.0, 0.0, 1.0); + + GL_Bind( image ); +#ifdef _XBOX + qglBeginEXT (GL_QUADS, 4, 0, 0, 4, 0); +#else + qglBegin (GL_QUADS); +#endif + qglTexCoord2f( cmd->s1, cmd->t1); + qglVertex2f( -cmd->w, 0 ); + qglTexCoord2f( cmd->s2, cmd->t1 ); + qglVertex2f( 0, 0 ); + qglTexCoord2f( cmd->s2, cmd->t2 ); + qglVertex2f( 0, cmd->h ); + qglTexCoord2f( cmd->s1, cmd->t2 ); + qglVertex2f( -cmd->w, cmd->h ); + qglEnd(); + + qglPopMatrix(); + } + + return (const void *)(cmd + 1); +} + +/* +============= +RB_DrawRotatePic2 +============= +*/ +const void *RB_RotatePic2 ( const void *data ) +{ + const rotatePicCommand_t *cmd; + image_t *image; + shader_t *shader; + + cmd = (const rotatePicCommand_t *)data; + + shader = cmd->shader; + + if ( shader->numUnfoggedPasses ) + { + image = &shader->stages[0].bundle[0].image[0]; + + if ( image ) + { + if ( !backEnd.projection2D ) + { + RB_SetGL2D(); + } + + // Get our current blend mode, etc. + GL_State( shader->stages[0].stateBits ); + + qglColor4ubv( backEnd.color2D ); + qglPushMatrix(); + + // rotation point is going to be around the center of the passed in coordinates + qglTranslatef( cmd->x, cmd->y, 0 ); + qglRotatef( cmd->a, 0.0, 0.0, 1.0 ); + + GL_Bind( image ); +#ifdef _XBOX + qglBeginEXT( GL_QUADS, 4, 0, 0, 4, 0); +#else + qglBegin( GL_QUADS ); +#endif + qglTexCoord2f( cmd->s1, cmd->t1); + qglVertex2f( -cmd->w * 0.5f, -cmd->h * 0.5f ); + + qglTexCoord2f( cmd->s2, cmd->t1 ); + qglVertex2f( cmd->w * 0.5f, -cmd->h * 0.5f ); + + qglTexCoord2f( cmd->s2, cmd->t2 ); + qglVertex2f( cmd->w * 0.5f, cmd->h * 0.5f ); + + qglTexCoord2f( cmd->s1, cmd->t2 ); + qglVertex2f( -cmd->w * 0.5f, cmd->h * 0.5f ); + qglEnd(); + + qglPopMatrix(); + + // Hmmm, this is not too cool + GL_State( GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + } + } + + return (const void *)(cmd + 1); +} + +/* +============= +RB_LAGoggles +============= +*/ +const void *RB_LAGoggles( const void *data ) +{ + return data; +} + +/* +============= +RB_ScissorPic +============= +*/ +const void *RB_Scissor ( const void *data ) +{ + const scissorCommand_t *cmd; + + cmd = (const scissorCommand_t *)data; + + if ( !backEnd.projection2D ) + { + RB_SetGL2D(); + } + + if (cmd->x >= 0) + { + qglScissor( cmd->x,(glConfig.vidHeight - cmd->y - cmd->h),cmd->w,cmd->h); + } + else + { + qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight); + } + + return (const void *)(cmd + 1); +} + +/* +============= +RB_DrawSurfs + +============= +*/ +const void *RB_DrawSurfs( const void *data ) { + const drawSurfsCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + cmd = (const drawSurfsCommand_t *)data; + + backEnd.refdef = cmd->refdef; + backEnd.viewParms = cmd->viewParms; + + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + + // Dynamic Glow/Flares: + /* + The basic idea is to render the glowing parts of the scene to an offscreen buffer, then take + that buffer and blur it. After it is sufficiently blurred, re-apply that image back to + the normal screen using a additive blending. To blur the scene I use a vertex program to supply + four texture coordinate offsets that allow 'peeking' into adjacent pixels. In the register + combiner (pixel shader), I combine the adjacent pixels using a weighting factor. - Aurelio + */ + + // Render dynamic glowing/flaring objects. +#ifndef _XBOX // GLOWXXX + if ( !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && g_bDynamicGlowSupported && r_DynamicGlow->integer ) + { + // Copy the normal scene to texture. + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.sceneImage ); + qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + + // Just clear colors, but leave the depth buffer intact so we can 'share' it. + qglClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); + qglClear( GL_COLOR_BUFFER_BIT ); + + // Render the glowing objects. + g_bRenderGlowingObjects = true; + RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs ); + g_bRenderGlowingObjects = false; + qglFinish(); + + // Copy the glow scene to texture. + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.screenGlow ); + qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + + // Resize the viewport to the blur texture size. + const int oldViewWidth = backEnd.viewParms.viewportWidth; + const int oldViewHeight = backEnd.viewParms.viewportHeight; + backEnd.viewParms.viewportWidth = r_DynamicGlowWidth->integer; + backEnd.viewParms.viewportHeight = r_DynamicGlowHeight->integer; + SetViewportAndScissor(); + + // Blur the scene. + RB_BlurGlowTexture(); + + // Copy the finished glow scene back to texture. + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage ); + qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + + // Set the viewport back to normal. + backEnd.viewParms.viewportWidth = oldViewWidth; + backEnd.viewParms.viewportHeight = oldViewHeight; + SetViewportAndScissor(); + qglClear( GL_COLOR_BUFFER_BIT ); + + // Draw the glow additively over the screen. + RB_DrawGlowOverlay(); + } +#endif // _XBOX + + return (const void *)(cmd + 1); +} + + +/* +============= +RB_DrawBuffer + +============= +*/ +const void *RB_DrawBuffer( const void *data ) { + const drawBufferCommand_t *cmd; + + cmd = (const drawBufferCommand_t *)data; + + qglDrawBuffer( cmd->buffer ); + + // clear screen for debugging + // VVFIXME - Does their new check fix our problem with hoth2 cinematic? +#ifndef _XBOX + if (!( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) && tr.world && tr.refdef.rdflags & RDF_doLAGoggles) + { + const fog_t *fog = &tr.world->fogs[tr.world->numfogs]; + + qglClearColor(fog->parms.color[0], fog->parms.color[1], fog->parms.color[2], 1.0f ); + qglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + else if (!( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) && tr.world && tr.world->globalFog != -1 && tr.sceneCount)//don't clear during menus, wait for real scene + { + const fog_t *fog = &tr.world->fogs[tr.world->globalFog]; + + qglClearColor(fog->parms.color[0], fog->parms.color[1], fog->parms.color[2], 1.0f ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + } + else if ( r_clear->integer ) + { // clear screen for debugging + int i = r_clear->integer; + if (i == 42) { + i = Q_irand(0,8); + } + switch (i) + { + default: + qglClearColor( 1, 0, 0.5, 1 ); + break; + case 1: + qglClearColor( 1.0, 0.0, 0.0, 1.0); //red + break; + case 2: + qglClearColor( 0.0, 1.0, 0.0, 1.0); //green + break; + case 3: + qglClearColor( 1.0, 1.0, 0.0, 1.0); //yellow + break; + case 4: + qglClearColor( 0.0, 0.0, 1.0, 1.0); //blue + break; + case 5: + qglClearColor( 0.0, 1.0, 1.0, 1.0); //cyan + break; + case 6: + qglClearColor( 1.0, 0.0, 1.0, 1.0); //magenta + break; + case 7: + qglClearColor( 1.0, 1.0, 1.0, 1.0); //white + break; + case 8: + qglClearColor( 0.0, 0.0, 0.0, 1.0); //black + break; + } + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + } +#endif // _XBOX + + return (const void *)(cmd + 1); +} + +/* +=============== +RB_ShowImages + +Draw all the images to the screen, on top of whatever +was there. This is used to test for texture thrashing. + +Also called by RE_EndRegistration +=============== +*/ +void RB_ShowImages( void ) { + image_t *image; + float x, y, w, h; + int start, end; + + if ( !backEnd.projection2D ) { + RB_SetGL2D(); + } + + qglFinish(); + + start = Sys_Milliseconds(); + + int i=0; +// int iNumImages = + R_Images_StartIteration(); + while ( (image = R_Images_GetNextIteration()) != NULL) + { + w = glConfig.vidWidth / 20; + h = glConfig.vidHeight / 15; + x = i % 20 * w; + y = i / 20 * h; + + // show in proportional size in mode 2 + if ( r_showImages->integer == 2 ) { + w *= image->width / 512.0; + h *= image->height / 512.0; + } + + GL_Bind( image ); +#ifdef _XBOX + qglBeginEXT (GL_QUADS, 4, 0, 0, 4, 0); +#else + qglBegin (GL_QUADS); +#endif + qglTexCoord2f( 0, 0 ); + qglVertex2f( x, y ); + qglTexCoord2f( 1, 0 ); + qglVertex2f( x + w, y ); + qglTexCoord2f( 1, 1 ); + qglVertex2f( x + w, y + h ); + qglTexCoord2f( 0, 1 ); + qglVertex2f( x, y + h ); + qglEnd(); + i++; + } + + qglFinish(); + + end = Sys_Milliseconds(); + //VID_Printf( PRINT_ALL, "%i msec to draw all images\n", end - start ); +} + + +/* +============= +RB_SwapBuffers + +============= +*/ +extern void RB_RenderWorldEffects( void ); +const void *RB_SwapBuffers( const void *data ) { + const swapBuffersCommand_t *cmd; + + // finish any 2D drawing if needed + if ( tess.numIndexes ) { + RB_EndSurface(); + } + + // texture swapping test + if ( r_showImages->integer ) { + RB_ShowImages(); + } + + cmd = (const swapBuffersCommand_t *)data; + + // we measure overdraw by reading back the stencil buffer and + // counting up the number of increments that have happened +#ifndef _XBOX + if ( r_measureOverdraw->integer ) { + int i; + long sum = 0; + unsigned char *stencilReadback; + + stencilReadback = (unsigned char *) Z_Malloc( glConfig.vidWidth * glConfig.vidHeight, TAG_TEMP_WORKSPACE, qfalse ); + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback ); + + for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) { + sum += stencilReadback[i]; + } + + backEnd.pc.c_overDraw += sum; + Z_Free( stencilReadback ); + } +#endif + + if ( !glState.finishCalled ) { + qglFinish(); + } + + GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" ); + + GLimp_EndFrame(); + + backEnd.projection2D = qfalse; + + return (const void *)(cmd + 1); +} + +const void *RB_WorldEffects( const void *data ) +{ + const setModeCommand_t *cmd; + + cmd = (const setModeCommand_t *)data; + + // Always flush the tess buffer + if ( tess.shader && tess.numIndexes ) + { + RB_EndSurface(); + } + RB_RenderWorldEffects(); + + if(tess.shader) + { + RB_BeginSurface( tess.shader, tess.fogNum ); + } + + return (const void *)(cmd + 1); +} + +/* +==================== +RB_ExecuteRenderCommands + +This function will be called syncronously if running without +smp extensions, or asyncronously by another thread. +==================== +*/ +void RB_ExecuteRenderCommands( const void *data ) { + int t1, t2; + + t1 = Sys_Milliseconds (); + + while ( 1 ) { + switch ( *(const int *)data ) { + case RC_SET_COLOR: + data = RB_SetColor( data ); + break; + case RC_STRETCH_PIC: + data = RB_StretchPic( data ); + break; + case RC_ROTATE_PIC: + data = RB_RotatePic( data ); + break; + case RC_ROTATE_PIC2: + data = RB_RotatePic2( data ); + break; + case RC_SCISSOR: + data = RB_Scissor( data ); + break; + case RC_DRAW_SURFS: + data = RB_DrawSurfs( data ); + break; + case RC_DRAW_BUFFER: + data = RB_DrawBuffer( data ); + break; + case RC_SWAP_BUFFERS: + data = RB_SwapBuffers( data ); + break; + case RC_WORLD_EFFECTS: + data = RB_WorldEffects( data ); + break; + case RC_END_OF_LIST: + default: + // stop rendering on this thread + t2 = Sys_Milliseconds (); + backEnd.pc.msec = t2 - t1; + return; + } + } + +} + +#ifndef _XBOX // GLOWXXX +// What Pixel Shader type is currently active (regcoms or fragment programs). +GLuint g_uiCurrentPixelShaderType = 0x0; + +// Begin using a Pixel Shader. +void BeginPixelShader( GLuint uiType, GLuint uiID ) +{ + switch ( uiType ) + { + // Using Register Combiners, so call the Display List that stores it. + case GL_REGISTER_COMBINERS_NV: + { + // Just in case... + if ( !qglCombinerParameterfvNV) + return; + + // Call the list with the regcom in it. + qglEnable( GL_REGISTER_COMBINERS_NV ); + qglCallList( uiID ); + + g_uiCurrentPixelShaderType = GL_REGISTER_COMBINERS_NV; + } + return; + + // Using Fragment Programs, so call the program. + case GL_FRAGMENT_PROGRAM_ARB: + { + // Just in case... + if ( !qglGenProgramsARB ) + return; + + qglEnable( GL_FRAGMENT_PROGRAM_ARB ); + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, uiID ); + + g_uiCurrentPixelShaderType = GL_FRAGMENT_PROGRAM_ARB; + } + return; + } +} + +// Stop using a Pixel Shader and return states to normal. +void EndPixelShader() +{ + if ( g_uiCurrentPixelShaderType == 0x0 ) + return; + + qglDisable( g_uiCurrentPixelShaderType ); +} + +// Hack variable for deciding which kind of texture rectangle thing to do (for some +// reason it acts different on radeon! It's against the spec!). +extern bool g_bTextureRectangleHack; + +static inline void RB_BlurGlowTexture() +{ + qglDisable (GL_CLIP_PLANE0); + GL_Cull( CT_TWO_SIDED ); + + // Go into orthographic 2d mode. + qglMatrixMode(GL_PROJECTION); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho(0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight, 0, -1, 1); + qglMatrixMode(GL_MODELVIEW); + qglPushMatrix(); + qglLoadIdentity(); + + GL_State(GLS_DEPTHTEST_DISABLE); + + ///////////////////////////////////////////////////////// + // Setup vertex and pixel programs. + ///////////////////////////////////////////////////////// + + // NOTE: The 0.25 is because we're blending 4 textures (so = 1.0) and we want a relatively normalized pixel + // intensity distribution, but this won't happen anyways if intensity is higher than 1.0. + float fBlurDistribution = r_DynamicGlowIntensity->value * 0.25f; + float fBlurWeight[4] = { fBlurDistribution, fBlurDistribution, fBlurDistribution, 1.0f }; + + // Enable and set the Vertex Program. + qglEnable( GL_VERTEX_PROGRAM_ARB ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, tr.glowVShader ); + + // Apply Pixel Shaders. + if ( qglCombinerParameterfvNV ) + { + BeginPixelShader( GL_REGISTER_COMBINERS_NV, tr.glowPShader ); + + // Pass the blur weight to the regcom. + qglCombinerParameterfvNV( GL_CONSTANT_COLOR0_NV, (float*)&fBlurWeight ); + } + else if ( qglProgramEnvParameter4fARB ) + { + BeginPixelShader( GL_FRAGMENT_PROGRAM_ARB, tr.glowPShader ); + + // Pass the blur weight to the Fragment Program. + qglProgramEnvParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 0, fBlurWeight[0], fBlurWeight[1], fBlurWeight[2], fBlurWeight[3] ); + } + + ///////////////////////////////////////////////////////// + // Set the blur texture to the 4 texture stages. + ///////////////////////////////////////////////////////// + + // How much to offset each texel by. + float fTexelWidthOffset = 0.1f, fTexelHeightOffset = 0.1f; + + GLuint uiTex = tr.screenGlow; + + qglActiveTextureARB( GL_TEXTURE3_ARB ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + + qglActiveTextureARB( GL_TEXTURE2_ARB ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + + qglActiveTextureARB( GL_TEXTURE1_ARB ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + + qglActiveTextureARB(GL_TEXTURE0_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + + ///////////////////////////////////////////////////////// + // Draw the blur passes (each pass blurs it more, increasing the blur radius ). + ///////////////////////////////////////////////////////// + + //int iTexWidth = backEnd.viewParms.viewportWidth, iTexHeight = backEnd.viewParms.viewportHeight; + int iTexWidth = glConfig.vidWidth, iTexHeight = glConfig.vidHeight; + + for ( int iNumBlurPasses = 0; iNumBlurPasses < r_DynamicGlowPasses->integer; iNumBlurPasses++ ) + { + // Load the Texel Offsets into the Vertex Program. + qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 0, -fTexelWidthOffset, -fTexelWidthOffset, 0.0f, 0.0f ); + qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 1, -fTexelWidthOffset, fTexelWidthOffset, 0.0f, 0.0f ); + qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 2, fTexelWidthOffset, -fTexelWidthOffset, 0.0f, 0.0f ); + qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 3, fTexelWidthOffset, fTexelWidthOffset, 0.0f, 0.0f ); + + // After first pass put the tex coords to the viewport size. + if ( iNumBlurPasses == 1 ) + { + if ( !g_bTextureRectangleHack ) + { + iTexWidth = backEnd.viewParms.viewportWidth; + iTexHeight = backEnd.viewParms.viewportHeight; + } + + uiTex = tr.blurImage; + qglActiveTextureARB( GL_TEXTURE3_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + qglActiveTextureARB( GL_TEXTURE2_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + qglActiveTextureARB( GL_TEXTURE1_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + qglActiveTextureARB(GL_TEXTURE0_ARB ); + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + + // Copy the current image over. + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex ); + qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + } + + // Draw the fullscreen quad. + qglBegin( GL_QUADS ); + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0, iTexHeight ); + qglVertex2f( 0, 0 ); + + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0, 0 ); + qglVertex2f( 0, backEnd.viewParms.viewportHeight ); + + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, iTexWidth, 0 ); + qglVertex2f( backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + + qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, iTexWidth, iTexHeight ); + qglVertex2f( backEnd.viewParms.viewportWidth, 0 ); + qglEnd(); + + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage ); + qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); + + // Increase the texel offsets. + // NOTE: This is possibly the most important input to the effect. Even by using an exponential function I've been able to + // make it look better (at a much higher cost of course). This is cheap though and still looks pretty great. In the future + // I might want to use an actual gaussian equation to correctly calculate the pixel coefficients and attenuates, texel + // offsets, gaussian amplitude and radius... + fTexelWidthOffset += r_DynamicGlowDelta->value; + fTexelHeightOffset += r_DynamicGlowDelta->value; + } + + // Disable multi-texturing. + qglActiveTextureARB( GL_TEXTURE3_ARB ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + + qglActiveTextureARB( GL_TEXTURE2_ARB ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + + qglActiveTextureARB( GL_TEXTURE1_ARB ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + + qglActiveTextureARB(GL_TEXTURE0_ARB ); + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + + qglDisable( GL_VERTEX_PROGRAM_ARB ); + EndPixelShader(); + + qglMatrixMode(GL_PROJECTION); + qglPopMatrix(); + qglMatrixMode(GL_MODELVIEW); + qglPopMatrix(); + + qglDisable( GL_BLEND ); + glState.currenttmu = 0; //this matches the last one we activated +} + +// Draw the glow blur over the screen additively. +static inline void RB_DrawGlowOverlay() +{ + qglDisable (GL_CLIP_PLANE0); + GL_Cull( CT_TWO_SIDED ); + + // Go into orthographic 2d mode. + qglMatrixMode(GL_PROJECTION); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho(0, glConfig.vidWidth, glConfig.vidHeight, 0, -1, 1); + qglMatrixMode(GL_MODELVIEW); + qglPushMatrix(); + qglLoadIdentity(); + + GL_State(GLS_DEPTHTEST_DISABLE); + + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + + // For debug purposes. + if ( r_DynamicGlow->integer != 2 ) + { + // Render the normal scene texture. + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.sceneImage ); + qglBegin(GL_QUADS); + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + qglTexCoord2f( 0, glConfig.vidHeight ); + qglVertex2f( 0, 0 ); + + qglTexCoord2f( 0, 0 ); + qglVertex2f( 0, glConfig.vidHeight ); + + qglTexCoord2f( glConfig.vidWidth, 0 ); + qglVertex2f( glConfig.vidWidth, glConfig.vidHeight ); + + qglTexCoord2f( glConfig.vidWidth, glConfig.vidHeight ); + qglVertex2f( glConfig.vidWidth, 0 ); + qglEnd(); + } + + // One and Inverse Src Color give a very soft addition, while one one is a bit stronger. With one one we can + // use additive blending through multitexture though. + if ( r_DynamicGlowSoft->integer ) + { + qglBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_COLOR ); + } + else + { + qglBlendFunc( GL_ONE, GL_ONE ); + } + qglEnable( GL_BLEND ); + + // Now additively render the glow texture. + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage ); + qglBegin(GL_QUADS); + qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + qglTexCoord2f( 0, r_DynamicGlowHeight->integer ); + qglVertex2f( 0, 0 ); + + qglTexCoord2f( 0, 0 ); + qglVertex2f( 0, glConfig.vidHeight ); + + qglTexCoord2f( r_DynamicGlowWidth->integer, 0 ); + qglVertex2f( glConfig.vidWidth, glConfig.vidHeight ); + + qglTexCoord2f( r_DynamicGlowWidth->integer, r_DynamicGlowHeight->integer ); + qglVertex2f( glConfig.vidWidth, 0 ); + qglEnd(); + + qglDisable( GL_TEXTURE_RECTANGLE_EXT ); + qglEnable( GL_TEXTURE_2D ); + qglBlendFunc( GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR ); + qglDisable( GL_BLEND ); + + qglMatrixMode(GL_PROJECTION); + qglPopMatrix(); + qglMatrixMode(GL_MODELVIEW); + qglPopMatrix(); +} +#endif diff --git a/code/renderer/tr_bsp.cpp b/code/renderer/tr_bsp.cpp new file mode 100644 index 0000000..fe1e5f8 --- /dev/null +++ b/code/renderer/tr_bsp.cpp @@ -0,0 +1,1458 @@ +// tr_map.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + +/* + +Loads and prepares a map file for scene rendering. + +A single entry point: + +void RE_LoadWorldMap( const char *name ); + +*/ + +static world_t s_worldData; +static byte *fileBase; + +int c_subdivisions; +int c_gridVerts; + +void R_RMGInit(void); +//=============================================================================== + +static void HSVtoRGB( float h, float s, float v, float rgb[3] ) +{ + int i; + float f; + float p, q, t; + + h *= 5; + + i = floor( h ); + f = h - i; + + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch ( i ) + { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) { + int shift=0, r, g, b; + + // should NOT do it if overbrightBits is 0 + if (tr.overbrightBits) + shift = 1 - tr.overbrightBits; + + if (!shift) + { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; + return; + } + + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = in[3]; +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +static void R_ColorShiftLightingBytes( byte in[3]) +{ + int shift=0, r, g, b; + + // should NOT do it if overbrightBits is 0 + if (tr.overbrightBits) + shift = 1 - tr.overbrightBits; + + if (!shift) { + return; //no need if not overbright + } + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + in[0] = r; + in[1] = g; + in[2] = b; +} + + +/* +=============== +R_LoadLightmaps + +=============== +*/ +#define LIGHTMAP_SIZE 128 +static void R_LoadLightmaps( lump_t *l, const char *psMapName, world_t &worldData ) +{ + byte *buf, *buf_p; + int len; + MAC_STATIC byte image[LIGHTMAP_SIZE*LIGHTMAP_SIZE*4]; + int i, j; + float maxIntensity = 0; + double sumIntensity = 0; + int count; + + if (&worldData == &s_worldData) + { + tr.numLightmaps = 0; + } + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + // we are about to upload textures + //R_SyncRenderThread(); + + // create all the lightmaps + worldData.startLightMapIndex = tr.numLightmaps; + count = len / (LIGHTMAP_SIZE * LIGHTMAP_SIZE * 3); + tr.numLightmaps += count; + + // if we are in r_vertexLight mode, we don't need the lightmaps at all + if ( r_vertexLight->integer ) { + return; + } + + char sMapName[MAX_QPATH]; + COM_StripExtension(psMapName,sMapName); // will already by MAX_QPATH legal, so no length check + + for ( i = 0 ; i < count ; i++ ) { + // expand the 24 bit on-disk to 32 bit + buf_p = buf + i * LIGHTMAP_SIZE*LIGHTMAP_SIZE * 3; + + if ( r_lightmap->integer == 2 ) + { // color code by intensity as development tool (FIXME: check range) + for ( j = 0; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) + { + float r = buf_p[j*3+0]; + float g = buf_p[j*3+1]; + float b = buf_p[j*3+2]; + float intensity; + float out[3]; + + intensity = 0.33f * r + 0.685f * g + 0.063f * b; + + if ( intensity > 255 ) + intensity = 1.0f; + else + intensity /= 255.0f; + + if ( intensity > maxIntensity ) + maxIntensity = intensity; + + HSVtoRGB( intensity, 1.00, 0.50, out ); + + image[j*4+0] = out[0] * 255; + image[j*4+1] = out[1] * 255; + image[j*4+2] = out[2] * 255; + image[j*4+3] = 255; + + sumIntensity += intensity; + } + } else { + for ( j = 0 ; j < LIGHTMAP_SIZE * LIGHTMAP_SIZE; j++ ) { + R_ColorShiftLightingBytes( &buf_p[j*3], &image[j*4] ); + image[j*4+3] = 255; + } + } + tr.lightmaps[worldData.startLightMapIndex+i] = R_CreateImage( va("$%s/lightmap%d",sMapName,worldData.startLightMapIndex+i), image, + LIGHTMAP_SIZE, LIGHTMAP_SIZE, GL_RGBA, qfalse, qfalse, r_ext_compressed_lightmaps->integer, GL_CLAMP ); + } + + if ( r_lightmap->integer == 2 ) { + VID_Printf( PRINT_ALL, "Brightest lightmap value: %d\n", ( int ) ( maxIntensity * 255 ) ); + } +} + + + +/* +================= +RE_SetWorldVisData + +This is called by the clipmodel subsystem so we can share the 1.8 megs of +space in big maps... +================= +*/ +void RE_SetWorldVisData( const byte *vis ) { + tr.externalVisData = vis; +} + + +/* +================= +R_LoadVisibility +================= +*/ +static void R_LoadVisibility( lump_t *l, world_t &worldData ) { + int len; + byte *buf; + + len = ( worldData.numClusters + 63 ) & ~63; + worldData.novis = ( unsigned char *) Hunk_Alloc( len, qfalse ); + memset( worldData.novis, 0xff, len ); + + len = l->filelen; + if ( !len ) { + return; + } + buf = fileBase + l->fileofs; + + worldData.numClusters = LittleLong( ((int *)buf)[0] ); + worldData.clusterBytes = LittleLong( ((int *)buf)[1] ); + + // CM_Load should have given us the vis data to share, so + // we don't need to allocate another copy + if ( tr.externalVisData ) { + worldData.vis = tr.externalVisData; + } else { + byte *dest; + + dest = (byte *) Hunk_Alloc( len - 8, qfalse ); + memcpy( dest, buf + 8, len - 8 ); + worldData.vis = dest; + } +} + +//=============================================================================== + +qhandle_t R_GetShaderByNum(int shaderNum, world_t &worldData) +{ + qhandle_t shader; + + if ( (shaderNum < 0) || (shaderNum >= worldData.numShaders) ) + { + Com_Printf( "Warning: Bad index for R_GetShaderByNum - %i", shaderNum ); + return(0); + } + shader = RE_RegisterShader(worldData.shaders[ shaderNum ].shader); + return(shader); +} + +/* +=============== +ShaderForShaderNum +=============== +*/ +static shader_t *ShaderForShaderNum( int shaderNum, const short *lightmapNum, const byte *lightmapStyles, const byte *vertexStyles, world_t &worldData ) { + shader_t *shader; + dshader_t *dsh; + const byte *styles; + + styles = lightmapStyles; + + shaderNum = LittleLong( shaderNum ); + if ( shaderNum < 0 || shaderNum >= worldData.numShaders ) { + Com_Error( ERR_DROP, "ShaderForShaderNum: bad num %i", shaderNum ); + } + dsh = &worldData.shaders[ shaderNum ]; + + if (lightmapNum[0] == LIGHTMAP_BY_VERTEX) + { + styles = vertexStyles; + } + + if ( r_vertexLight->integer ) + { + lightmapNum = lightmapsVertex; + styles = vertexStyles; + } + +/* if ( r_fullbright->integer ) + { + lightmapNum = lightmapsFullBright; + styles = vertexStyles; + } +*/ + shader = R_FindShader( dsh->shader, lightmapNum, styles, qtrue ); + + // if the shader had errors, just use default shader + if ( shader->defaultShader ) { + return tr.defaultShader; + } + + return shader; +} + +/* +=============== +ParseFace +=============== +*/ +static void ParseFace( dsurface_t *ds, mapVert_t *verts, msurface_t *surf, int *indexes, byte *&pFaceDataBuffer, world_t &worldData, int index ) +{ + int i, j, k; + srfSurfaceFace_t *cv; + int numPoints, numIndexes; + short lightmapNum[MAXLIGHTMAPS]; + int sfaceSize, ofsIndexes; + + for(i=0;ilightmapNum[i] ); + if (lightmapNum[i] >= 0) + { + lightmapNum[i] += worldData.startLightMapIndex; + } + } + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + if (index && !surf->fogIndex && tr.world && tr.world->globalFog != -1) + { + surf->fogIndex = worldData.globalFog; + } + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum, ds->lightmapStyles, ds->vertexStyles, worldData ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + numPoints = LittleLong( ds->numVerts ); + numIndexes = LittleLong( ds->numIndexes ); + + // create the srfSurfaceFace_t + sfaceSize = ( int ) &((srfSurfaceFace_t *)0)->points[numPoints]; + ofsIndexes = sfaceSize; + sfaceSize += sizeof( int ) * numIndexes; + + cv = (srfSurfaceFace_t *) pFaceDataBuffer;//Hunk_Alloc( sfaceSize ); + pFaceDataBuffer += sfaceSize; // :-) + + cv->surfaceType = SF_FACE; + cv->numPoints = numPoints; + cv->numIndices = numIndexes; + cv->ofsIndices = ofsIndexes; + + verts += LittleLong( ds->firstVert ); + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + cv->points[i][j] = LittleFloat( verts[i].xyz[j] ); + } + for ( j = 0 ; j < 2 ; j++ ) { + cv->points[i][3+j] = LittleFloat( verts[i].st[j] ); + for(k=0;kpoints[i][VERTEX_LM+j+(k*2)] = LittleFloat( verts[i].lightmap[k][j] ); + } + } + for(k=0;kpoints[i][VERTEX_COLOR+k] ); + } + } + + indexes += LittleLong( ds->firstIndex ); + for ( i = 0 ; i < numIndexes ; i++ ) { + ((int *)((byte *)cv + cv->ofsIndices ))[i] = LittleLong( indexes[ i ] ); + } + + // take the plane information from the lightmap vector + for ( i = 0 ; i < 3 ; i++ ) { + cv->plane.normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } + cv->plane.dist = DotProduct( cv->points[0], cv->plane.normal ); + SetPlaneSignbits( &cv->plane ); + cv->plane.type = PlaneTypeForNormal( cv->plane.normal ); + + surf->data = (surfaceType_t *)cv; +} + + +/* +=============== +ParseMesh +=============== +*/ +static void ParseMesh ( dsurface_t *ds, mapVert_t *verts, msurface_t *surf, world_t &worldData, int index) { + srfGridMesh_t *grid; + int i, j, k; + int width, height, numPoints; + MAC_STATIC drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE]; + short lightmapNum[MAXLIGHTMAPS]; + vec3_t bounds[2]; + vec3_t tmpVec; + static surfaceType_t skipData = SF_SKIP; + + for(i=0;ilightmapNum[i] ); + if (lightmapNum[i] >= 0) + { + lightmapNum[i] += worldData.startLightMapIndex; + } + } + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + if (index && !surf->fogIndex && tr.world && tr.world->globalFog != -1) + { + surf->fogIndex = worldData.globalFog; + } + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum, ds->lightmapStyles, ds->vertexStyles, worldData ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + // we may have a nodraw surface, because they might still need to + // be around for movement clipping + if ( worldData.shaders[ LittleLong( ds->shaderNum ) ].surfaceFlags & SURF_NODRAW ) { + surf->data = &skipData; + return; + } + + width = LittleLong( ds->patchWidth ); + height = LittleLong( ds->patchHeight ); + + verts += LittleLong( ds->firstVert ); + numPoints = width * height; + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + points[i].xyz[j] = LittleFloat( verts[i].xyz[j] ); + points[i].normal[j] = LittleFloat( verts[i].normal[j] ); + } + for ( j = 0 ; j < 2 ; j++ ) { + points[i].st[j] = LittleFloat( verts[i].st[j] ); + for(k=0;kdata = (surfaceType_t *)grid; + + // copy the level of detail origin, which is the center + // of the group of all curves that must subdivide the same + // to avoid cracking + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = LittleFloat( ds->lightmapVecs[0][i] ); + bounds[1][i] = LittleFloat( ds->lightmapVecs[1][i] ); + } + VectorAdd( bounds[0], bounds[1], bounds[1] ); + VectorScale( bounds[1], 0.5f, grid->lodOrigin ); + VectorSubtract( bounds[0], grid->lodOrigin, tmpVec ); + grid->lodRadius = VectorLength( tmpVec ); +} + +/* +=============== +ParseTriSurf +=============== +*/ +static void ParseTriSurf( dsurface_t *ds, mapVert_t *verts, msurface_t *surf, int *indexes, world_t &worldData, int index ) { + srfTriangles_t *tri; + int i, j, k; + int numVerts, numIndexes; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + if (index && !surf->fogIndex && tr.world && tr.world->globalFog != -1) + { + surf->fogIndex = worldData.globalFog; + } + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapsVertex, ds->lightmapStyles, ds->vertexStyles, worldData ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + numVerts = LittleLong( ds->numVerts ); + numIndexes = LittleLong( ds->numIndexes ); + + if ( numVerts >= SHADER_MAX_VERTEXES ) { + Com_Error(ERR_DROP, "ParseTriSurf: verts > MAX (%d > %d) on misc_model %s", numVerts, SHADER_MAX_VERTEXES, surf->shader->name ); + } + if ( numIndexes >= SHADER_MAX_INDEXES ) { + Com_Error(ERR_DROP, "ParseTriSurf: indices > MAX (%d > %d) on misc_model %s", numIndexes, SHADER_MAX_INDEXES, surf->shader->name ); + } + + tri = (srfTriangles_t *) Z_Malloc( sizeof( *tri ) + numVerts * sizeof( tri->verts[0] ) + numIndexes * sizeof( tri->indexes[0] ), TAG_HUNKMISCMODELS, qfalse ); + tri->dlightBits = 0; //JIC + tri->surfaceType = SF_TRIANGLES; + tri->numVerts = numVerts; + tri->numIndexes = numIndexes; + tri->verts = (drawVert_t *)(tri + 1); + tri->indexes = (int *)(tri->verts + tri->numVerts ); + + surf->data = (surfaceType_t *)tri; + + // copy vertexes + verts += LittleLong( ds->firstVert ); + ClearBounds( tri->bounds[0], tri->bounds[1] ); + for ( i = 0 ; i < numVerts ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + tri->verts[i].xyz[j] = LittleFloat( verts[i].xyz[j] ); + tri->verts[i].normal[j] = LittleFloat( verts[i].normal[j] ); + } + AddPointToBounds( tri->verts[i].xyz, tri->bounds[0], tri->bounds[1] ); + for ( j = 0 ; j < 2 ; j++ ) { + tri->verts[i].st[j] = LittleFloat( verts[i].st[j] ); + for(k=0;kverts[i].lightmap[k][j] = LittleFloat( verts[i].lightmap[k][j] ); + } + } + for(k=0;kverts[i].color[k] ); + } + } + + // copy indexes + indexes += LittleLong( ds->firstIndex ); + for ( i = 0 ; i < numIndexes ; i++ ) { + tri->indexes[i] = LittleLong( indexes[i] ); + if ( tri->indexes[i] < 0 || tri->indexes[i] >= numVerts ) { + Com_Error( ERR_DROP, "Bad index in triangle surface" ); + } + } +} + +/* +=============== +ParseFlare +=============== +*/ +static void ParseFlare( dsurface_t *ds, mapVert_t *verts, msurface_t *surf, int *indexes, world_t &worldData, int index ) { + srfFlare_t *flare; + int i; + short lightmaps[MAXLIGHTMAPS] = { LIGHTMAP_BY_VERTEX }; + + // get fog volume + surf->fogIndex = LittleLong( ds->fogNum ) + 1; + if (index && !surf->fogIndex && tr.world->globalFog != -1) + { + surf->fogIndex = worldData.globalFog; + } + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmaps, ds->lightmapStyles, ds->vertexStyles, worldData ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + flare = (srfFlare_t *) Hunk_Alloc( sizeof( *flare ), qtrue ); + flare->surfaceType = SF_FLARE; + + surf->data = (surfaceType_t *)flare; + + for ( i = 0 ; i < 3 ; i++ ) { + flare->origin[i] = LittleFloat( ds->lightmapOrigin[i] ); + flare->color[i] = LittleFloat( ds->lightmapVecs[0][i] ); + flare->normal[i] = LittleFloat( ds->lightmapVecs[2][i] ); + } +} + +/* +=============== +R_LoadSurfaces +=============== +*/ +static void R_LoadSurfaces( lump_t *surfs, lump_t *verts, lump_t *indexLump, world_t &worldData, int index ) { + dsurface_t *in; + msurface_t *out; + mapVert_t *dv; + int *indexes; + int count; + int numFaces, numMeshes, numTriSurfs, numFlares; + int i; + + numFaces = 0; + numMeshes = 0; + numTriSurfs = 0; + numFlares = 0; + + in = (dsurface_t *)(fileBase + surfs->fileofs); + if (surfs->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + count = surfs->filelen / sizeof(*in); + + dv = (mapVert_t *)(fileBase + verts->fileofs); + if (verts->filelen % sizeof(*dv)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + + indexes = (int *)(fileBase + indexLump->fileofs); + if ( indexLump->filelen % sizeof(*indexes)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + + out = (struct msurface_s *) Hunk_Alloc ( count * sizeof(*out), qtrue ); + + worldData.surfaces = out; + worldData.numsurfaces = count; + + // new bit, the face code on our biggest map requires over 15,000 mallocs, which was no problem on the hunk, + // bit hits the zone pretty bad (even the tagFree takes about 9 seconds for that many memblocks), + // so special-case pre-alloc enough space for this data (the patches etc can stay as they are)... + // + int iFaceDataSizeRequired = 0; + for ( i = 0 ; i < count ; i++, in++) + { + switch ( LittleLong( in->surfaceType ) ) + { + case MST_PLANAR: + + int sfaceSize = ( int ) &((srfSurfaceFace_t *)0)->points[LittleLong(in->numVerts)]; + sfaceSize += sizeof( int ) * LittleLong(in->numIndexes); + + iFaceDataSizeRequired += sfaceSize; + break; + } + } + in -= count; // back it up, ready for loop-proper + + // since this ptr is to hunk data, I can pass it in and have it advanced without worrying about losing + // the original alloc ptr... + // + byte *pFaceDataBuffer = (byte *)Hunk_Alloc( iFaceDataSizeRequired, qtrue ); + + // now do regular loop... + // + for ( i = 0 ; i < count ; i++, in++, out++ ) { + switch ( LittleLong( in->surfaceType ) ) { + case MST_PATCH: + ParseMesh ( in, dv, out, worldData, index ); + numMeshes++; + break; + case MST_TRIANGLE_SOUP: + ParseTriSurf( in, dv, out, indexes, worldData, index ); + numTriSurfs++; + break; + case MST_PLANAR: + ParseFace( in, dv, out, indexes, pFaceDataBuffer, worldData, index ); + numFaces++; + break; + case MST_FLARE: + ParseFlare( in, dv, out, indexes, worldData, index ); + numFlares++; + break; + default: + Com_Error( ERR_DROP, "Bad surfaceType" ); + } + } + + VID_Printf( PRINT_ALL, "...loaded %d faces, %i meshes, %i trisurfs, %i flares\n", + numFaces, numMeshes, numTriSurfs, numFlares ); +} + + + +/* +================= +R_LoadSubmodels +================= +*/ +static void R_LoadSubmodels( lump_t *l, world_t &worldData, int index ) { + dmodel_t *in; + bmodel_t *out; + int i, j, count; + + in = (dmodel_t *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + count = l->filelen / sizeof(*in); + + worldData.bmodels = out = (bmodel_t *) Hunk_Alloc( count * sizeof(*out), qtrue ); + + for ( i=0 ; itype = MOD_BRUSH; + model->bmodel = out; + if (index) + { + Com_sprintf( model->name, sizeof( model->name ), "*%d-%d", index, i ); + model->bspInstance = true; + } + else + { + Com_sprintf( model->name, sizeof( model->name ), "*%d", i); + } + + for (j=0 ; j<3 ; j++) { + out->bounds[0][j] = LittleFloat (in->mins[j]); + out->bounds[1][j] = LittleFloat (in->maxs[j]); + } +/* +Ghoul2 Insert Start +*/ + + RE_InsertModelIntoHash(model->name, model); +/* +Ghoul2 Insert End +*/ + + out->firstSurface = worldData.surfaces + LittleLong( in->firstSurface ); + out->numSurfaces = LittleLong( in->numSurfaces ); + } +} + + + +//================================================================== + +/* +================= +R_SetParent +================= +*/ +static void R_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents != -1) + return; + R_SetParent (node->children[0], node); + R_SetParent (node->children[1], node); +} + +/* +================= +R_LoadNodesAndLeafs +================= +*/ +static void R_LoadNodesAndLeafs (lump_t *nodeLump, lump_t *leafLump, world_t &worldData) { + int i, j, p; + dnode_t *in; + dleaf_t *inLeaf; + mnode_t *out; + int numNodes, numLeafs; + + in = (dnode_t *)(fileBase + nodeLump->fileofs); + if (nodeLump->filelen % sizeof(dnode_t) || + leafLump->filelen % sizeof(dleaf_t) ) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + } + numNodes = nodeLump->filelen / sizeof(dnode_t); + numLeafs = leafLump->filelen / sizeof(dleaf_t); + + out = (struct mnode_s *) Hunk_Alloc ( (numNodes + numLeafs) * sizeof(*out), qtrue ); + + worldData.nodes = out; + worldData.numnodes = numNodes + numLeafs; + worldData.numDecisionNodes = numNodes; + + // load nodes + for ( i=0 ; imins[j] = LittleLong (in->mins[j]); + out->maxs[j] = LittleLong (in->maxs[j]); + } + + p = LittleLong(in->planeNum); + out->plane = worldData.planes + p; + + out->contents = CONTENTS_NODE; // differentiate from leafs + + for (j=0 ; j<2 ; j++) + { + p = LittleLong (in->children[j]); + if (p >= 0) + out->children[j] = worldData.nodes + p; + else + out->children[j] = worldData.nodes + numNodes + (-1 - p); + } + } + + // load leafs + inLeaf = (dleaf_t *)(fileBase + leafLump->fileofs); + for ( i=0 ; imins[j] = LittleLong (inLeaf->mins[j]); + out->maxs[j] = LittleLong (inLeaf->maxs[j]); + } + + out->cluster = LittleLong(inLeaf->cluster); + out->area = LittleLong(inLeaf->area); + + if ( out->cluster >= worldData.numClusters ) { + worldData.numClusters = out->cluster + 1; + } + + out->firstmarksurface = worldData.marksurfaces + + LittleLong(inLeaf->firstLeafSurface); + out->nummarksurfaces = LittleLong(inLeaf->numLeafSurfaces); + } + + // chain decendants + R_SetParent (worldData.nodes, NULL); +} + +//============================================================================= + +/* +================= +R_LoadShaders +================= +*/ +static void R_LoadShaders( lump_t *l, world_t &worldData ) { + int i, count; + dshader_t *in, *out; + + in = (dshader_t *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + count = l->filelen / sizeof(*in); + out = (dshader_t *) Hunk_Alloc ( count*sizeof(*out), qfalse ); + + worldData.shaders = out; + worldData.numShaders = count; + + memcpy( out, in, count*sizeof(*out) ); + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + count = l->filelen / sizeof(*in); + out = (struct msurface_s **) Hunk_Alloc ( count*sizeof(*out), qtrue ); + + worldData.marksurfaces = out; + worldData.nummarksurfaces = count; + + for ( i=0 ; ifileofs); + if (l->filelen % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + count = l->filelen / sizeof(*in); + out = (struct cplane_s *) Hunk_Alloc ( count*2*sizeof(*out), qtrue ); + + worldData.planes = out; + worldData.numplanes = count; + + for ( i=0 ; inormal[j] = LittleFloat (in->normal[j]); + if (out->normal[j] < 0) { + bits |= 1<dist = LittleFloat (in->dist); + out->type = PlaneTypeForNormal( out->normal ); + out->signbits = bits; + } +} + +/* +================= +R_LoadFogs + +================= +*/ +static void R_LoadFogs( lump_t *l, lump_t *brushesLump, lump_t *sidesLump, world_t &worldData, int index ) { + int i; + fog_t *out; + dfog_t *fogs; + dbrush_t *brushes, *brush; + dbrushside_t *sides; + int count, brushesCount, sidesCount; + int sideNum; + int planeNum; + shader_t *shader; + float d; + int firstSide=0; + short lightmaps[MAXLIGHTMAPS] = { LIGHTMAP_NONE } ; + + fogs = (dfog_t *)(fileBase + l->fileofs); + if (l->filelen % sizeof(*fogs)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + } + count = l->filelen / sizeof(*fogs); + + // create fog strucutres for them + worldData.numfogs = count + 1; + worldData.fogs = (fog_t *)Hunk_Alloc ( (worldData.numfogs+1)*sizeof(*out), qtrue); + worldData.globalFog = -1; + out = worldData.fogs + 1; + + // Copy the global fog from the main world into the bsp instance + if(index) + { + if(tr.world && (tr.world->globalFog != -1)) + { + // Use the nightvision fog slot + worldData.fogs[worldData.numfogs] = tr.world->fogs[tr.world->globalFog]; + worldData.globalFog = worldData.numfogs; + worldData.numfogs++; + } + } + + if ( !count ) { + return; + } + + brushes = (dbrush_t *)(fileBase + brushesLump->fileofs); + if (brushesLump->filelen % sizeof(*brushes)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + } + brushesCount = brushesLump->filelen / sizeof(*brushes); + + sides = (dbrushside_t *)(fileBase + sidesLump->fileofs); + if (sidesLump->filelen % sizeof(*sides)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",tr.worldDir); + } + sidesCount = sidesLump->filelen / sizeof(*sides); + + for ( i=0 ; ioriginalBrushNumber = LittleLong( fogs->brushNum ); + if (out->originalBrushNumber == -1) + { + if(index) + { + Com_Error (ERR_DROP, "LoadMap: global fog not allowed in bsp instances - %s", tr.worldDir); + } + VectorSet(out->bounds[0], MIN_WORLD_COORD, MIN_WORLD_COORD, MIN_WORLD_COORD); + VectorSet(out->bounds[1], MAX_WORLD_COORD, MAX_WORLD_COORD, MAX_WORLD_COORD); + worldData.globalFog = i + 1; + } + else + { + if ( (unsigned)out->originalBrushNumber >= brushesCount ) { + Com_Error( ERR_DROP, "fog brushNumber out of range" ); + } + brush = brushes + out->originalBrushNumber; + + firstSide = LittleLong( brush->firstSide ); + + if ( (unsigned)firstSide > sidesCount - 6 ) { + Com_Error( ERR_DROP, "fog brush sideNumber out of range" ); + } + + // brushes are always sorted with the axial sides first + sideNum = firstSide + 0; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][0] = -worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 1; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][0] = worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 2; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][1] = -worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 3; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][1] = worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 4; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[0][2] = -worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 5; + planeNum = LittleLong( sides[ sideNum ].planeNum ); + out->bounds[1][2] = worldData.planes[ planeNum ].dist; + } + + // get information from the shader for fog parameters + shader = R_FindShader( fogs->shader, lightmaps, stylesDefault, qtrue ); + + assert(shader->fogParms); + if (!shader->fogParms) + {//bad shader!! + out->parms.color[0] = 1.0f; + out->parms.color[1] = 0.0f; + out->parms.color[2] = 0.0f; + out->parms.color[3] = 0.0f; + out->parms.depthForOpaque = 250.0f; + } + else + { + out->parms = *shader->fogParms; + } + out->colorInt = ColorBytes4 ( out->parms.color[0], + out->parms.color[1], + out->parms.color[2], 1.0 ); + + d = out->parms.depthForOpaque < 1 ? 1 : out->parms.depthForOpaque; + out->tcScale = 1.0f / ( d * 8 ); + + // set the gradient vector + sideNum = LittleLong( fogs->visibleSide ); + + if ( sideNum == -1 ) { + out->hasSurface = qfalse; + } else { + out->hasSurface = qtrue; + planeNum = LittleLong( sides[ firstSide + sideNum ].planeNum ); + VectorSubtract( vec3_origin, worldData.planes[ planeNum ].normal, out->surface ); + out->surface[3] = -worldData.planes[ planeNum ].dist; + } + + out++; + } + + if (!index) + { + // Initialise the last fog so we can use it with the LA Goggles + // NOTE: We are might appear to be off the end of the array, but we allocated an extra memory slot above but [purposely] didn't + // increment the total world numFogs to match our array size + VectorSet(out->bounds[0], MIN_WORLD_COORD, MIN_WORLD_COORD, MIN_WORLD_COORD); + VectorSet(out->bounds[1], MAX_WORLD_COORD, MAX_WORLD_COORD, MAX_WORLD_COORD); + out->originalBrushNumber = -1; + out->parms.color[0] = 0.0f; + out->parms.color[1] = 0.0f; + out->parms.color[2] = 0.0f; + out->parms.color[3] = 0.0f; + out->parms.depthForOpaque = 0.0f; + out->colorInt = 0x00000000; + out->tcScale = 0.0f; + out->hasSurface = false; + } +} + + +/* +================ +R_LoadLightGrid + +================ +*/ +void R_LoadLightGrid( lump_t *l, world_t &worldData ) { + int i, j; + vec3_t maxs; + world_t *w; + float *wMins, *wMaxs; + + w = &worldData; + + w->lightGridInverseSize[0] = 1.0 / w->lightGridSize[0]; + w->lightGridInverseSize[1] = 1.0 / w->lightGridSize[1]; + w->lightGridInverseSize[2] = 1.0 / w->lightGridSize[2]; + + wMins = w->bmodels[0].bounds[0]; + wMaxs = w->bmodels[0].bounds[1]; + + for ( i = 0 ; i < 3 ; i++ ) { + w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] ); + maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] ); + w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1; + } + + int numGridDataElements = l->filelen / sizeof(*w->lightGridData); + + w->lightGridData = (mgrid_t *)Hunk_Alloc( l->filelen, qfalse ); + memcpy( w->lightGridData, (void *)(fileBase + l->fileofs), l->filelen ); + + // deal with overbright bits + for ( i = 0 ; i < numGridDataElements ; i++ ) { + for(j=0;jlightGridData[i].ambientLight[j]); + R_ColorShiftLightingBytes(w->lightGridData[i].directLight[j]); + } + } +} + +/* +================ +R_LoadLightGridArray + +================ +*/ +void R_LoadLightGridArray( lump_t *l, world_t &worldData ) { + world_t *w; + + w = &worldData; + + w->numGridArrayElements = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2]; + + if ( l->filelen != (int)(w->numGridArrayElements * sizeof(*w->lightGridArray)) ) { + if (l->filelen>0)//don't warn if not even lit + VID_Printf( PRINT_WARNING, "WARNING: light grid array mismatch\n" ); + w->lightGridData = NULL; + return; + } + + w->lightGridArray = (unsigned short *)Hunk_Alloc( l->filelen, qfalse ); + memcpy( w->lightGridArray, (void *)(fileBase + l->fileofs), l->filelen ); +} + + +/* +================ +R_LoadEntities +================ +*/ +void R_LoadEntities( lump_t *l, world_t &worldData ) { + const char *p, *token; + char keyname[MAX_TOKEN_CHARS]; + char value[MAX_TOKEN_CHARS]; + world_t *w; + float ambient = 1; + + w = &worldData; + w->lightGridSize[0] = 64; + w->lightGridSize[1] = 64; + w->lightGridSize[2] = 128; + + VectorSet(tr.sunAmbient, 1, 1, 1); + tr.distanceCull = 12000;//DEFAULT_DISTANCE_CULL; + + p = (char *)(fileBase + l->fileofs); + + token = COM_ParseExt( &p, qtrue ); + if (!*token || *token != '{') { + return; + } + + // only parse the world spawn + while ( 1 ) { + // parse key + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(keyname, token, sizeof(keyname)); + + // parse value + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(value, token, sizeof(value)); + + // check for remapping of shaders for vertex lighting +/* s = "vertexremapshader"; + if (!Q_strncmp(keyname, s, strlen(s)) ) { + s = strchr(value, ';'); + if (!s) { + VID_Printf( S_COLOR_YELLOW "WARNING: no semi colon in vertexshaderremap '%s'\n", value ); + break; + } + *s++ = 0; + if (r_vertexLight->integer) { + R_RemapShader(value, s, "0"); + } + continue; + } + // check for remapping of shaders + s = "remapshader"; + if (!Q_strncmp(keyname, s, strlen(s)) ) { + s = strchr(value, ';'); + if (!s) { + VID_Printf( S_COLOR_YELLOW "WARNING: no semi colon in shaderremap '%s'\n", value ); + break; + } + *s++ = 0; + R_RemapShader(value, s, "0"); + continue; + } +*/ if (!Q_stricmp(keyname, "distanceCull")) { + sscanf(value, "%f", &tr.distanceCull ); + continue; + } + //check for linear fog -rww + if (!Q_stricmp(keyname, "linFogStart")) { + sscanf(value, "%f", &tr.rangedFog ); + tr.rangedFog = -tr.rangedFog; + continue; + } + // check for a different grid size + if (!Q_stricmp(keyname, "gridsize")) { + sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] ); + continue; + } + // find the optional world ambient for arioche + if (!Q_stricmp(keyname, "_color")) { + sscanf(value, "%f %f %f", &tr.sunAmbient[0], &tr.sunAmbient[1], &tr.sunAmbient[2] ); + continue; + } + if (!Q_stricmp(keyname, "ambient")) { + sscanf(value, "%f", &ambient); + continue; + } + } + //both default to 1 so no harm if not present. + VectorScale( tr.sunAmbient, ambient, tr.sunAmbient); +} + + +/* +================= +RE_LoadWorldMap + +Called directly from cgame +================= +*/ +void RE_LoadWorldMap_Actual( const char *name, world_t &worldData, int index ) { + int i; + dheader_t *header; + byte *buffer = NULL; + qboolean loadedSubBSP = qfalse; + + if ( tr.worldMapLoaded && !index ) { + Com_Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" ); + } + + // set default sun direction to be used if it isn't + // overridden by a shader + if (!index) + { + skyboxportal = 0; + + tr.sunDirection[0] = 0.45f; + tr.sunDirection[1] = 0.3f; + tr.sunDirection[2] = 0.9f; + + VectorNormalize( tr.sunDirection ); + + tr.worldMapLoaded = qtrue; + + // clear tr.world so if the level fails to load, the next + // try will not look at the partially loaded version + tr.world = NULL; + } + + // check for cached disk file from the server first... + // + extern void *gpvCachedMapDiskImage; + extern char gsCachedMapDiskImage[]; + if (gpvCachedMapDiskImage) + { + if (!strcmp(name, gsCachedMapDiskImage)) + { + // we should always get here, if inside the first IF... + // + buffer = (byte *)gpvCachedMapDiskImage; + } + else + { + // this should never happen (ie renderer loading a different map than the server), but just in case... + // + // assert(0); + // Z_Free(gpvCachedMapDiskImage); + // gpvCachedMapDiskImage = NULL; + //rww - this is a valid possibility now because of sub-bsp loading.\ + //it's alright, just keep the current cache + loadedSubBSP = qtrue; + } + } + + if (buffer == NULL) + { + // still needs loading... + // + FS_ReadFile( name, (void **)&buffer ); + if ( !buffer ) { + Com_Error (ERR_DROP, "RE_LoadWorldMap: %s not found", name); + } + } + + memset( &worldData, 0, sizeof( worldData ) ); + + Q_strncpyz( tr.worldDir, name, sizeof( tr.worldDir ) ); + COM_StripExtension( tr.worldDir, tr.worldDir ); + + c_gridVerts = 0; + + header = (dheader_t *)buffer; + fileBase = (byte *)header; + + header->version = LittleLong (header->version); + + if ( header->version != BSP_VERSION ) + { + Com_Error (ERR_DROP, "RE_LoadWorldMap: %s has wrong version number (%i should be %i)", name, header->version, BSP_VERSION); + } + + // swap all the lumps + for (i=0 ; ilumps[LUMP_SHADERS], worldData ); + R_LoadLightmaps( &header->lumps[LUMP_LIGHTMAPS], name, worldData ); + R_LoadPlanes (&header->lumps[LUMP_PLANES], worldData); + R_LoadFogs( &header->lumps[LUMP_FOGS], &header->lumps[LUMP_BRUSHES], &header->lumps[LUMP_BRUSHSIDES], worldData, index ); + R_LoadSurfaces( &header->lumps[LUMP_SURFACES], &header->lumps[LUMP_DRAWVERTS], &header->lumps[LUMP_DRAWINDEXES], worldData, index ); + R_LoadMarksurfaces (&header->lumps[LUMP_LEAFSURFACES], worldData); + R_LoadNodesAndLeafs (&header->lumps[LUMP_NODES], &header->lumps[LUMP_LEAFS], worldData); + R_LoadSubmodels (&header->lumps[LUMP_MODELS], worldData, index); + R_LoadVisibility( &header->lumps[LUMP_VISIBILITY], worldData ); + + if (!index) + { + R_LoadEntities( &header->lumps[LUMP_ENTITIES], worldData ); + R_LoadLightGrid( &header->lumps[LUMP_LIGHTGRID], worldData ); + R_LoadLightGridArray( &header->lumps[LUMP_LIGHTARRAY], worldData ); + + // only set tr.world now that we know the entire level has loaded properly + tr.world = &worldData; + } + + + if (gpvCachedMapDiskImage && !loadedSubBSP) + { + // For the moment, I'm going to keep this disk image around in case we need it to respawn. + // No problem for memory, since it'll only be a NZ ptr if we're not low on physical memory + // ( ie we've got > 96MB). + // + // So don't do this... + // + // Z_Free( gpvCachedMapDiskImage ); + // gpvCachedMapDiskImage = NULL; + } + else + { + FS_FreeFile( buffer ); + } +} + + +// new wrapper used for convenience to tell z_malloc()-fail recovery code whether it's safe to dump the cached-bsp or not. +// +extern qboolean gbUsingCachedMapDataRightNow; +void RE_LoadWorldMap( const char *name ) +{ + gbUsingCachedMapDataRightNow = qtrue; // !!!!!!!!!!!! + + RE_LoadWorldMap_Actual( name, s_worldData, 0 ); + + gbUsingCachedMapDataRightNow = qfalse; // !!!!!!!!!!!! +} diff --git a/code/renderer/tr_bsp_xbox.cpp b/code/renderer/tr_bsp_xbox.cpp new file mode 100644 index 0000000..2570ca3 --- /dev/null +++ b/code/renderer/tr_bsp_xbox.cpp @@ -0,0 +1,1699 @@ +// tr_map.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "tr_local.h" + +#include "../qcommon/cm_local.h" + +/* + +Loads and prepares a map file for scene rendering. + +A single entry point: + +void RE_LoadWorldMap( const char *name ); + +*/ + +world_t s_worldData; +byte *fileBase; +int c_subdivisions; +int c_gridVerts; + +static int flareNum = 0; + +void R_RMGInit(void); +//=============================================================================== + +// We use a special hack to prevent slight differences in channels +// from exploding into big differences, as it causes lighting problems +// later on. This is the maximum channel separation for which we +// enable the hack. +#define MAX_GREYSCALE_CHANNEL_DIFF 15 + +static void R_ColorShiftLightingBytes16( const byte in[4], byte out[2] ) { + // What's the largest separation between the red, green, and blue + // channels? + int chanDiff = max(in[0],max(in[1],in[2])) - + min(in[0],min(in[1],in[2])); + if (chanDiff <= MAX_GREYSCALE_CHANNEL_DIFF) + { + // Ensure that all color channels compress to the same value + byte channelAvg = (in[0] + in[1] + in[2] + 1) / 3; + out[0] = channelAvg & 0xF0; + out[0] |= (channelAvg & 0xF0) >> 4; + out[1] = channelAvg & 0xF0; + out[1] |= (in[3] & 0xF0) >> 4; + + if (channelAvg % 16 >= 8) + { + out[0] |= 0x10; + out[0] |= 0x01; + out[1] |= 0x10; + } + if (in[4] % 16 >= 8) + { + out[1] |= 0x01; + } + return; + } + + // Normal case for vertex colors that are not "near" greyscale + out[0] = in[0] & 0xF0; + out[0] |= (in[1] & 0xF0) >> 4; + out[1] = in[2] & 0xF0; + out[1] |= (in[3] & 0xF0) >> 4; + + if(in[0] % 16 >= 8) { + out[0] |= 0x10; + } + if(in[1] % 16 >= 8) { + out[0] |= 0x1; + } + if(in[2] % 16 >= 8) { + out[1] |= 0x10; + } + if(in[3] % 16 >= 8) { + out[1] |= 0x1; + } +} + + +static void HSVtoRGB( float h, float s, float v, float rgb[3] ) +{ + int i; + float f; + float p, q, t; + + h *= 5; + + i = floor( h ); + f = h - i; + + p = v * ( 1 - s ); + q = v * ( 1 - s * f ); + t = v * ( 1 - s * ( 1 - f ) ); + + switch ( i ) + { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +void R_ColorShiftLightingBytes( byte in[4], byte out[4] ) { + int shift=0, r, g, b; + + // should NOT do it if overbrightBits is 0 + if (tr.overbrightBits) + shift = 1 - tr.overbrightBits; + + if (!shift) + { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = in[3]; + return; + } + + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = in[3]; +} + +/* +=============== +R_ColorShiftLightingBytes + +=============== +*/ +static void R_ColorShiftLightingBytes( byte in[3]) +{ + int shift=0, r, g, b; + + // should NOT do it if overbrightBits is 0 + if (tr.overbrightBits) + shift = 1 - tr.overbrightBits; + + if (!shift) { + return; //no need if not overbright + } + // shift the data based on overbright range + r = in[0] << shift; + g = in[1] << shift; + b = in[2] << shift; + + // normalize by color instead of saturating to white + if ( ( r | g | b ) > 255 ) { + int max; + + max = r > g ? r : g; + max = max > b ? max : b; + r = r * 255 / max; + g = g * 255 / max; + b = b * 255 / max; + } + + in[0] = r; + in[1] = g; + in[2] = b; +} + + +/* +=============== +R_LoadLightmaps + +=============== +*/ +#define LIGHTMAP_SIZE 128 +void R_LoadLightmaps( void *data, int len, const char *psMapName ) { + byte *buf, *buf_p; + int i; + + if ( !len ) { + return; + } + buf = (byte *)data + sizeof(int); + + // we are about to upload textures + R_SyncRenderThread(); + + // create all the lightmaps + int size = *(int*)data; + tr.numLightmaps = len / size; + + byte* image = (byte*)Z_Malloc(size, TAG_TEMP_WORKSPACE, qfalse, 32); + + char sMapName[MAX_QPATH]; + COM_StripExtension(psMapName,sMapName); // will already by MAX_QPATH legal, so no length check + + for ( i = 0 ; i < tr.numLightmaps ; i++ ) { + buf_p = buf + i * size; + memcpy(image, buf_p, size); + + char lmapName[MAX_QPATH + 32]; + Com_sprintf(lmapName, MAX_QPATH + 32, "*%s/lightmap%d",sMapName,i); + tr.lightmaps[i] = R_CreateImage( lmapName, image, + LIGHTMAP_SIZE, LIGHTMAP_SIZE, + GL_DDS_RGB16_EXT, + qfalse, 0, GL_CLAMP); + } + + Z_Free(image); +} + + +/* +================= +RE_SetWorldVisData + +This is called by the clipmodel subsystem so we can share the 1.8 megs of +space in big maps... +================= +*/ +void RE_SetWorldVisData( SPARC *vis ) { + tr.externalVisData = vis; +} + + +/* +================= +R_LoadVisibility +================= +*/ +static void R_LoadVisibility( void ) { + int len; + + len = ( s_worldData.numClusters + 63 ) & ~63; + s_worldData.novis = ( unsigned char *) Hunk_Alloc( len, qfalse ); + memset( s_worldData.novis, 0xff, len ); + + s_worldData.numClusters = cmg.numClusters; + s_worldData.clusterBytes = cmg.clusterBytes; + + // CM_Load should have given us the vis data to share, so + // we don't need to allocate another copy + //if ( tr.externalVisData ) { + s_worldData.vis = tr.externalVisData; + /*} else { + assert(0); + }*/ +} + +//=============================================================================== + +qhandle_t R_GetShaderByNum(int shaderNum, world_t &worldData) +{ + qhandle_t shader; + + if ( (shaderNum < 0) || (shaderNum >= worldData.numShaders) ) + { + Com_Printf( "Warning: Bad index for R_GetShaderByNum - %i", shaderNum ); + return(0); + } + shader = RE_RegisterShader(worldData.shaders[ shaderNum ].shader); + return(shader); +} + +/* +=============== +ShaderForShaderNum +=============== +*/ +static shader_t *ShaderForShaderNum( int shaderNum, const short *lightmapNum, const byte *lightmapStyles ) { + shader_t *shader; + dshader_t *dsh; + + shaderNum = shaderNum; + if ( shaderNum < 0 || shaderNum >= s_worldData.numShaders ) { + Com_Error( ERR_DROP, "ShaderForShaderNum: bad num %i", shaderNum ); + } + dsh = &s_worldData.shaders[ shaderNum ]; + + shader = R_FindShader( dsh->shader, lightmapNum, lightmapStyles, qtrue ); + + // if the shader had errors, just use default shader + if ( shader->defaultShader ) { + return tr.defaultShader; + } + + return shader; +} + +bool NeedVertexColors(shader_t *shader) +{ + int i; + shaderStage_t *stage; + + for(i=0; inumUnfoggedPasses; i++) { + stage = &shader->stages[i]; + switch(stage->rgbGen) { + case CGEN_EXACT_VERTEX: + case CGEN_VERTEX: + case CGEN_ONE_MINUS_VERTEX: + return true; + } + switch(stage->alphaGen) { + case AGEN_VERTEX: + case AGEN_ONE_MINUS_VERTEX: + return true; + } + } + + return false; +} + +int NumLightMaps(shader_t *shader) +{ + int count = 0; + int i; + + for(i=0; ilightmapIndex[i] >= 0) { + count++; + } else { + return count; + } + } + + return count; +} + +int SurfaceFaceSize(int numVerts, int numLightMaps, bool needVertexColors, + int numIndexes) +{ + int sfaceSize = ( int ) &((srfSurfaceFace_t *)0)->srfPoints + + 4 /*sizeof srfPoints*/ + + (numVerts * sizeof(unsigned short) * + (VERTEX_LM + numLightMaps * 2 + +#ifdef COMPRESS_VERTEX_COLORS + (int)needVertexColors * 4)); +#else + (int)needVertexColors * 8)); +#endif + + // Add in tangent size - no, tangent size is included in VERTEX_LM! + + //Indices stored in 8 bits now. + sfaceSize += numIndexes; + + return sfaceSize; +} + + +void BuildDrawVertTangents( drawVert_t *verts, int *indexes, int numIndexes, int numVertexes ) +{ + int i = 0; + + for(i = 0; i < numVertexes; i++) + { + verts[i].tangent[0] = 0.0f; + verts[i].tangent[1] = 0.0f; + verts[i].tangent[2] = 0.0f; + } + + for(i = 0; i < numIndexes; i += 3) + { + vec3_t vec1, vec2, du, dv, cp; + float st0[2], st1[2], st2[2]; + + Q_CastShort2FloatScale(&st0[0], &verts[indexes[i]].dvst[0], 1.f / DRAWVERT_ST_SCALE); + Q_CastShort2FloatScale(&st0[1], &verts[indexes[i]].dvst[1], 1.f / DRAWVERT_ST_SCALE); + + Q_CastShort2FloatScale(&st1[0], &verts[indexes[i+1]].dvst[0], 1.f / DRAWVERT_ST_SCALE); + Q_CastShort2FloatScale(&st1[1], &verts[indexes[i+1]].dvst[1], 1.f / DRAWVERT_ST_SCALE); + + Q_CastShort2FloatScale(&st2[0], &verts[indexes[i+2]].dvst[0], 1.f / DRAWVERT_ST_SCALE); + Q_CastShort2FloatScale(&st2[1], &verts[indexes[i+2]].dvst[1], 1.f / DRAWVERT_ST_SCALE); + + vec1[0] = verts[indexes[i+1]].xyz[0] - verts[indexes[i]].xyz[0]; + vec1[1] = st1[0] - st0[0]; + vec1[2] = st1[1] - st0[1]; + + vec2[0] = verts[indexes[i+2]].xyz[0] - verts[indexes[i]].xyz[0]; + vec2[1] = st2[0] - st0[0]; + vec2[2] = st2[1] - st0[1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[0] = -cp[1] / cp[0]; + dv[0] = -cp[2] / cp[0]; + + vec1[0] = verts[indexes[i+1]].xyz[1] - verts[indexes[i]].xyz[1]; + + vec2[0] = verts[indexes[i+2]].xyz[1] - verts[indexes[i]].xyz[1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[1] = -cp[1] / cp[0]; + dv[1] = -cp[2] / cp[0]; + + vec1[0] = verts[indexes[i+1]].xyz[2] - verts[indexes[i]].xyz[2]; + + vec2[0] = verts[indexes[i+2]].xyz[2] - verts[indexes[i]].xyz[2]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[2] = -cp[1] / cp[0]; + dv[2] = -cp[2] / cp[0]; + + verts[indexes[i]].tangent[0] += du[0]; + verts[indexes[i]].tangent[1] += du[1]; + verts[indexes[i]].tangent[2] += du[2]; + + verts[indexes[i+1]].tangent[0] += du[0]; + verts[indexes[i+1]].tangent[1] += du[1]; + verts[indexes[i+1]].tangent[2] += du[2]; + + verts[indexes[i+2]].tangent[0] += du[0]; + verts[indexes[i+2]].tangent[1] += du[1]; + verts[indexes[i+2]].tangent[2] += du[2]; + } + + for(i = 0; i < numVertexes; i++) + { + VectorNormalizeFast(verts[i].tangent); + } +} + + +void BuildMapVertTangents( mapVert_t *verts, vec3_t *tangents, short *indexes, int numIndexes, int numVertexes ) +{ + int i = 0; + + for(i = 0; i < numVertexes; i++) + { + tangents[i][0] = 0.0f; + tangents[i][1] = 0.0f; + tangents[i][2] = 0.0f; + } + + for(i = 0; i < numIndexes; i += 3) + { + vec3_t vec1, vec2, du, dv, cp; + + vec1[0] = verts[indexes[i+1]].xyz[0] - verts[indexes[i]].xyz[0]; + vec1[1] = (verts[indexes[i+1]].st[0] * POINTS_ST_SCALE) - + (verts[indexes[i]].st[0] * POINTS_ST_SCALE); + vec1[2] = (verts[indexes[i+1]].st[1] * POINTS_ST_SCALE) - + (verts[indexes[i]].st[1] * POINTS_ST_SCALE); + + vec2[0] = verts[indexes[i+2]].xyz[0] - verts[indexes[i]].xyz[0]; + vec2[1] = (verts[indexes[i+2]].st[0] * POINTS_ST_SCALE) - + (verts[indexes[i]].st[0] * POINTS_ST_SCALE); + vec2[2] = (verts[indexes[i+2]].st[1]* POINTS_ST_SCALE) - + (verts[indexes[i]].st[1] * POINTS_ST_SCALE); + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[0] = -cp[1] / cp[0]; + dv[0] = -cp[2] / cp[0]; + + vec1[0] = verts[indexes[i+1]].xyz[1] - verts[indexes[i]].xyz[1]; + + vec2[0] = verts[indexes[i+2]].xyz[1] - verts[indexes[i]].xyz[1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[1] = -cp[1] / cp[0]; + dv[1] = -cp[2] / cp[0]; + + vec1[0] = verts[indexes[i+1]].xyz[2] - verts[indexes[i]].xyz[2]; + + vec2[0] = verts[indexes[i+2]].xyz[2] - verts[indexes[i]].xyz[2]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[2] = -cp[1] / cp[0]; + dv[2] = -cp[2] / cp[0]; + + tangents[indexes[i]][0] += du[0]; + tangents[indexes[i]][1] += du[1]; + tangents[indexes[i]][2] += du[2]; + + tangents[indexes[i+1]][0] += du[0]; + tangents[indexes[i+1]][1] += du[1]; + tangents[indexes[i+1]][2] += du[2]; + + tangents[indexes[i+2]][0] += du[0]; + tangents[indexes[i+2]][1] += du[1]; + tangents[indexes[i+2]][2] += du[2]; + } + + for(i = 0; i < numVertexes; i++) + { + VectorNormalizeFast(tangents[i]); + } +} + +/* +=============== +ParseFace +=============== +*/ +static void ParseFace( dface_t *ds, mapVert_t *verts, msurface_t *surf, short *indexes, byte *&pFaceDataBuffer) +{ + int i, j, k; + srfSurfaceFace_t *cv; + int numPoints, numIndexes; + short lightmapNum[MAXLIGHTMAPS]; + int sfaceSize, ofsIndexes; + vec3_t tangents[1000]; + + for(i=0;ilightmapNum[i] - 4; + } + + // get fog volume + surf->fogIndex = ds->fogNum + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum, ds->lightmapStyles ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + bool needVertexColors = NeedVertexColors(surf->shader); + int numLightMaps = NumLightMaps(surf->shader); + assert(numLightMaps <= 0x7F); + + numPoints = ds->verts & 0xFFF; + if (numPoints > MAX_FACE_POINTS) { + VID_Printf( PRINT_DEVELOPER, "MAX_FACE_POINTS exceeded: %i\n", numPoints); + } + + numIndexes = ds->indexes & 0xFFF; + + // create the srfSurfaceFace_t + sfaceSize = SurfaceFaceSize(numPoints, + numLightMaps, needVertexColors, numIndexes); + ofsIndexes = sfaceSize - numIndexes; + + cv = (srfSurfaceFace_t *) pFaceDataBuffer;//Hunk_Alloc( sfaceSize ); + pFaceDataBuffer += sfaceSize; // :-) + + cv->surfaceType = SF_FACE; + cv->numPoints = numPoints; + cv->numIndices = numIndexes; + cv->ofsIndices = ofsIndexes; + cv->srfPoints = (unsigned short *)(((byte*)cv) + ( int ) &((srfSurfaceFace_t *)0)->srfPoints + 4); + if(needVertexColors) { + cv->flags = 1 << 7; + } else { + cv->flags = 0; + } + cv->flags |= (numLightMaps & 0x7F); + + //Make sure we don't overflow storage. + assert(numPoints < 256); + assert(numIndexes < 65536); + assert(ofsIndexes < 65536); + + int nextSurfPoint = NEXT_SURFPOINT(cv->flags); + verts += ds->verts >> 12; + + indexes += ds->indexes >> 12; + + BuildMapVertTangents(verts, tangents, indexes, numIndexes, numPoints); + + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + *(cv->srfPoints + i * nextSurfPoint + j) = verts[i].xyz[j]; + } + + for ( j = 0; j < 3 ; j++ ) { + assert(tangents[i][j] >= -1 && tangents[i][j] <= 1); + *(cv->srfPoints + i * nextSurfPoint + 3 + j) = (short)(tangents[i][j] * 32767.0f); + } + for ( j = 0 ; j < 2 ; j++ ) { + *(cv->srfPoints + i * nextSurfPoint + 6 + j) = + (short)(verts[i].st[j] * POINTS_ST_SCALE); + + for(k=0;ksrfPoints + i * nextSurfPoint + VERTEX_LM+j+(k*2)) = + verts[i].lightmap[k][j]; + } + } + if(needVertexColors) { + for(k=0;ksrfPoints + i * nextSurfPoint + + VERTEX_COLOR(cv->flags) + k)); +#else + R_ColorShiftLightingBytes( + verts[i].color[k], + (byte*)(cv->srfPoints + i * nextSurfPoint + + VERTEX_COLOR(cv->flags) + 2*k)); +#endif + } + } + } + +// indexes += ds->indexes >> 12; + unsigned char *indexStorage = ((unsigned char*)cv) + cv->ofsIndices; + for ( i = 0 ; i < numIndexes ; i++ ) { + indexStorage[i] = indexes[ i ]; + } + + // take the plane information from the lightmap vector + for ( i = 0 ; i < 3 ; i++ ) { + cv->plane.normal[i] = (float)ds->lightmapVecs[i] / 32767.f; + } + vec3_t fVec; + fVec[0] = (float)((short)cv->srfPoints[0]); + fVec[1] = (float)((short)cv->srfPoints[1]); + fVec[2] = (float)((short)cv->srfPoints[2]); + cv->plane.dist = DotProduct( fVec, cv->plane.normal ); + SetPlaneSignbits( &cv->plane ); + cv->plane.type = PlaneTypeForNormal( cv->plane.normal ); + + surf->data = (surfaceType_t *)cv; +} + + +/* +=============== +ParseMesh +=============== +*/ +static void ParseMesh ( dpatch_t *ds, mapVert_t *verts, msurface_t *surf, + drawVert_t* points, drawVert_t* ctrl, float* errorTable ) { + srfGridMesh_t *grid; + int i, j, k; + int width, height, numPoints; + short lightmapNum[MAXLIGHTMAPS]; + vec3_t bounds[2]; + vec3_t tmpVec; + static surfaceType_t skipData = SF_SKIP; + + for(i=0;ilightmapNum[i] - 4; + } + + // get fog volume + surf->fogIndex = ds->fogNum + 1; + + // get shader value + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapNum, ds->lightmapStyles ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + // we may have a nodraw surface, because they might still need to + // be around for movement clipping + if ( s_worldData.shaders[ ds->shaderNum ].surfaceFlags & SURF_NODRAW ) { + surf->data = &skipData; + return; + } + + width = ds->patchWidth; + height = ds->patchHeight; + + verts += ds->verts >> 12; + numPoints = width * height; + for ( i = 0 ; i < numPoints ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + points[i].xyz[j] = (float)verts[i].xyz[j]; + points[i].normal[j] = (float)verts[i].normal[j] / 32767.f; + } + for ( j = 0 ; j < 2 ; j++ ) { + // Sanity check that alternate fixed point representation + // is good enough + assert( verts[i].st[j] * GRID_DRAWVERT_ST_SCALE < 32767 && + verts[i].st[j] * GRID_DRAWVERT_ST_SCALE >= -32768 ); + points[i].dvst[j] = verts[i].st[j] * GRID_DRAWVERT_ST_SCALE; + for(k=0;kdata = (surfaceType_t *)grid; + + // copy the level of detail origin, which is the center + // of the group of all curves that must subdivide the same + // to avoid cracking + for ( i = 0 ; i < 3 ; i++ ) { + bounds[0][i] = ds->lightmapVecs[0][i]; + bounds[1][i] = ds->lightmapVecs[1][i]; + } + VectorAdd( bounds[0], bounds[1], bounds[1] ); + VectorScale( bounds[1], 0.5f, grid->lodOrigin ); + VectorSubtract( bounds[0], grid->lodOrigin, tmpVec ); + grid->lodRadius = VectorLength( tmpVec ); +} + +/* +=============== +ParseTriSurf +=============== +*/ +static void ParseTriSurf( dtrisurf_t *ds, mapVert_t *verts, msurface_t *surf, short *indexes ) { + srfTriangles_t *tri; + int i, j, k; + int numVerts, numIndexes; + + // get fog volume + surf->fogIndex = ds->fogNum + 1; + + // get shader + surf->shader = ShaderForShaderNum( ds->shaderNum, lightmapsVertex, ds->lightmapStyles ); + if ( r_singleShader->integer && !surf->shader->sky ) { + surf->shader = tr.defaultShader; + } + + numVerts = ds->verts & 0xFFF; + numIndexes = ds->indexes & 0xFFF; + + tri = (srfTriangles_t *) Hunk_Alloc( sizeof( *tri ) + numVerts * sizeof( tri->verts[0] ) + + numIndexes * sizeof( tri->indexes[0] ), qtrue ); + tri->surfaceType = SF_TRIANGLES; + tri->numVerts = numVerts; + tri->numIndexes = numIndexes; + tri->verts = (drawVert_t *)(tri + 1); + tri->indexes = (int *)(tri->verts + tri->numVerts ); + + surf->data = (surfaceType_t *)tri; + + // copy vertexes + verts += ds->verts >> 12; + ClearBounds( tri->bounds[0], tri->bounds[1] ); + for ( i = 0 ; i < numVerts ; i++ ) { + for ( j = 0 ; j < 3 ; j++ ) { + tri->verts[i].xyz[j] = verts[i].xyz[j]; + tri->verts[i].normal[j] = verts[i].normal[j]; + } + AddPointToBounds( tri->verts[i].xyz, tri->bounds[0], tri->bounds[1] ); + for ( j = 0 ; j < 2 ; j++ ) { + // Sanity check that alternate fixed point representation + // is good enough + // MATT! - double check this! + assert( verts[i].st[j] * DRAWVERT_ST_SCALE <= 32767 && + verts[i].st[j] * DRAWVERT_ST_SCALE >= -32768 ); + tri->verts[i].dvst[j] = verts[i].st[j] * DRAWVERT_ST_SCALE; + for(k=0;kverts[i].dvlightmap[k][j] = + ((float)verts[i].lightmap[k][j] / POINTS_LIGHT_SCALE) * + DRAWVERT_LIGHTMAP_SCALE; + } + } + for(k=0;kverts[i].dvcolor[k]); +#else + R_ColorShiftLightingBytes(verts[i].color[k], + tri->verts[i].dvcolor[k]); +#endif + } + } + + // copy indexes + indexes += ds->indexes >> 12; + for ( i = 0 ; i < numIndexes ; i++ ) { + tri->indexes[i] = indexes[i]; + if ( tri->indexes[i] < 0 || tri->indexes[i] >= numVerts ) { + Com_Error( ERR_DROP, "Bad index in triangle surface" ); + } + } + + // Build the tangent vectors + BuildDrawVertTangents(tri->verts, tri->indexes, numIndexes, numVerts); +} + + +/* +=============== +ParseFlare +=============== +*/ +static void ParseFlare( dflare_t *df, msurface_t *surf ) +{ + srfFlare_t *flare; + int i; + + surf->fogIndex = df->fogNum + 1; + + // get shader + surf->shader = ShaderForShaderNum( df->shaderNum, lightmapsVertex, stylesDefault ); + + flare = (srfFlare_t *) Hunk_Alloc( sizeof( *flare ), qtrue ); + flare->surfaceType = SF_FLARE; + + for ( i = 0 ; i < 3 ; i++ ) { + flare->origin[i] = df->origin[i]; + flare->color[i] = df->color[i]; + flare->normal[i] = df->normal[i]; + } + + assert(flareNum <= 255); + flare->number = flareNum++; + flare->visible = -1; + + surf->data = (surfaceType_t *)flare; +} + + +void R_LoadFlares( void *surfaces, int surfacelen ) { + int count, i; + dflare_t *in = NULL; + msurface_t *out; + + count = surfacelen / sizeof(*in); + + flareNum = 0; + + for ( i = 0 ; i < count ; i++ ) { + in = (dflare_t *)surfaces + i; + out = s_worldData.surfaces + in->code; + ParseFlare( in, out ); + } +} + + +/* +=============== +R_LoadSurfaces +=============== +*/ +void R_LoadSurfaces( int count ) { + s_worldData.surfaces = (struct msurface_s *) + Hunk_Alloc ( count * sizeof(msurface_s), qtrue ); + s_worldData.numsurfaces = count; +} + + +/* +=============== +R_LoadPatches +=============== +*/ +void R_LoadPatches( void *verts, int vertlen, + void *surfaces, int surfacelen ) { + dpatch_t *in = NULL; + msurface_t *out; + mapVert_t *dv; + int count; + int i; + + if (surfacelen == 0) { + return; + } + + count = surfacelen / sizeof(*in); + + dv = (mapVert_t *)(verts); + if (vertlen % sizeof(*dv)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + drawVert_t* points = (drawVert_t*)Z_Malloc( + MAX_PATCH_SIZE*MAX_PATCH_SIZE*sizeof(drawVert_t), + TAG_TEMP_WORKSPACE, qfalse); + + drawVert_t* ctrl = (drawVert_t*)Z_Malloc( + MAX_GRID_SIZE*MAX_GRID_SIZE*sizeof(drawVert_t), + TAG_TEMP_WORKSPACE, qfalse); + + float* errorTable = (float*)Z_Malloc( + 2*MAX_GRID_SIZE*sizeof(float), + TAG_TEMP_WORKSPACE, qfalse); + + for ( i = 0 ; i < count ; i++ ) { + in = (dpatch_t *)surfaces + i; + out = s_worldData.surfaces + in->code; + ParseMesh ( in, dv, out, points, ctrl, errorTable ); + } + + Z_Free(errorTable); + Z_Free(ctrl); + Z_Free(points); + + VID_Printf( PRINT_ALL, "...loaded %i meshes\n", count ); +} + + + /* +=============== +R_LoadTriSurfs +=============== +*/ +void R_LoadTriSurfs( void *indexdata, int indexlen, + void *verts, int vertlen, + void *surfaces, int surfacelen ) { + dtrisurf_t *in = NULL; + msurface_t *out; + mapVert_t *dv; + short *indexes; + int count; + int i; + + if (surfacelen == 0) { + return; + } + + count = surfacelen / sizeof(*in); + + dv = (mapVert_t *)(verts); + if (vertlen % sizeof(*dv)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + indexes = (short *)(indexdata); + if ( indexlen % sizeof(*indexes)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + for ( i = 0 ; i < count ; i++ ) { + in = (dtrisurf_t *)surfaces + i; + out = s_worldData.surfaces + in->code; + ParseTriSurf( in, dv, out, indexes ); + } + + VID_Printf( PRINT_ALL, "...loaded %i trisurfs\n", count ); +} + + +/* +=============== +R_LoadFaces +=============== +*/ +void R_LoadFaces( void *indexdata, int indexlen, + void *verts, int vertlen, + void *surfaces, int surfacelen ) { + dface_t *in = NULL; + msurface_t *out; + mapVert_t *dv; + short *indexes; + int count; + int i; + + if (surfacelen == 0) { + return; + } + + count = surfacelen / sizeof(*in); + + dv = (mapVert_t *)(verts); + if (vertlen % sizeof(*dv)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + indexes = (short *)(indexdata); + if ( indexlen % sizeof(*indexes)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + + // new bit, the face code on our biggest map requires over 15,000 mallocs, which was no problem on the hunk, + // bit hits the zone pretty bad (even the tagFree takes about 9 seconds for that many memblocks), + // so special-case pre-alloc enough space for this data (the patches etc can stay as they are)... + // + int nTimes = count / 100; + int nToGo = nTimes; + int iFaceDataSizeRequired = 0; + for ( i = 0 ; i < count ; i++) + { + in = (dface_t *)surfaces + i; + + short lightmapNum[MAXLIGHTMAPS]; + for(int j=0; j<4; j++) { + lightmapNum[j] = (int)in->lightmapNum[j] - 4; + } + shader_t *shader = ShaderForShaderNum( in->shaderNum, lightmapNum, in->lightmapStyles ); + bool needVertexColors = NeedVertexColors(shader); + int numLightMaps = NumLightMaps(shader); + + int sfaceSize = SurfaceFaceSize(in->verts & 0xFFF, + numLightMaps, needVertexColors, + in->indexes & 0xFFF); + + iFaceDataSizeRequired += sfaceSize; + assert(sfaceSize < 100 * 1024); + if (--nToGo <= 0) + { + nToGo = nTimes; + } + } + in -= count; // back it up, ready for loop-proper + + // since this ptr is to hunk data, I can pass it in and have it advanced without worrying about losing + // the original alloc ptr... + // + byte *orgFaceData; + byte *pFaceDataBuffer = (byte *)Hunk_Alloc( iFaceDataSizeRequired, qtrue ); + orgFaceData = pFaceDataBuffer; + + // now do regular loop... + // + for ( i = 0 ; i < count ; i++ ) { + in = (dface_t *)surfaces + i; + out = s_worldData.surfaces + in->code; + ParseFace( in, dv, out, indexes, pFaceDataBuffer ); + if (--nToGo <= 0) + { + nToGo = nTimes; + } + } + + VID_Printf( PRINT_ALL, "...loaded %d faces\n", count ); +} + + +/* +================= +R_LoadSubmodels +================= +*/ +static void R_LoadSubmodels( void *data, int len ) { + dmodel_t *in; + bmodel_t *out; + int i, j, count; + + in = (dmodel_t *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = len / sizeof(*in); + + s_worldData.bmodels = out = (bmodel_t *) Hunk_Alloc( count * sizeof(*out), qtrue ); + + for ( i=0 ; itype = MOD_BRUSH; + model->bmodel = out; + Com_sprintf( model->name, sizeof( model->name ), "*%d", i ); + + for (j=0 ; j<3 ; j++) { + out->bounds[0][j] = in->mins[j]; + out->bounds[1][j] = in->maxs[j]; + } + + RE_InsertModelIntoHash(model->name, model); + + out->firstSurface = s_worldData.surfaces + in->firstSurface; + out->numSurfaces = in->numSurfaces; + } +} + +//================================================================== + +/* +================= +R_SetParent +================= +*/ +static void R_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents != -1) + return; + R_SetParent (node->children[0], node); + R_SetParent (node->children[1], node); +} + +/* +================= +R_LoadNodesAndLeafs +================= +*/ +static void R_LoadNodesAndLeafs (void *nodes, int nodelen, void *leafs, int leaflen) { + int i, j, p; + dnode_t *in; + dleaf_t *inLeaf; + mnode_t *outNode; + mleaf_s *outLeaf; + int numNodes, numLeafs; + + in = (dnode_t *)(nodes); + if (nodelen % sizeof(dnode_t) || + leaflen % sizeof(dleaf_t) ) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + numNodes = nodelen / sizeof(dnode_t); + numLeafs = leaflen / sizeof(dleaf_t); + + outNode = (struct mnode_s *) Hunk_Alloc ( (numNodes) * sizeof(*outNode), qtrue ); + outLeaf = (struct mleaf_s *) Hunk_Alloc ( (numLeafs) * sizeof(*outLeaf), qtrue ); + + s_worldData.nodes = outNode; + s_worldData.leafs = outLeaf; + s_worldData.numnodes = numNodes; + s_worldData.numleafs = numLeafs; + + // load nodes + for ( i=0 ; imins[j] = in->mins[j]; + outNode->maxs[j] = in->maxs[j]; + } + + outNode->planeNum = in->planeNum; + outNode->contents = CONTENTS_NODE; // differentiate from leafs + + for (j=0 ; j<2 ; j++) + { + p = in->children[j]; + if (p >= 0) { + if(p < numNodes) { + outNode->children[j] = s_worldData.nodes + p; + } else { + outNode->children[j] = (mnode_s*) + (s_worldData.leafs + (p - numNodes)); + } + } else { + if(numNodes + (-1 - p) < numNodes) { + outNode->children[j] = s_worldData.nodes + numNodes + (-1 - p); + } else { + outNode->children[j] = (mnode_s*) + (s_worldData.leafs + (-1 - p)); + } + } + } + } + + // load leafs + inLeaf = (dleaf_t *)(leafs); + for ( i=0 ; imins[j] = inLeaf->mins[j]; + outLeaf->maxs[j] = inLeaf->maxs[j]; + } + + outLeaf->cluster = inLeaf->cluster; + outLeaf->area = inLeaf->area; + + if ( outLeaf->cluster >= s_worldData.numClusters ) { + s_worldData.numClusters = outLeaf->cluster + 1; + } + + outLeaf->firstMarkSurfNum = inLeaf->firstLeafSurface; + outLeaf->nummarksurfaces = inLeaf->numLeafSurfaces; + } + + // chain decendants + R_SetParent (s_worldData.nodes, NULL); +} + +//============================================================================= + +/* +================= +R_LoadShaders +================= +*/ +void R_LoadShaders( void ) { + /*s_worldData.shaders = cm.shaders; + s_worldData.numShaders = cm.numShaders;*/ +} + +/* +================= +R_LoadMarksurfaces +================= +*/ +static void R_LoadMarksurfaces (void *data, int len) +{ + int i, count; + int *in; + msurface_t **out; + + in = (int *)(data); + if (len % sizeof(*in)) + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + count = len / sizeof(*in); + out = (struct msurface_s **) Hunk_Alloc ( count*sizeof(*out), qtrue ); + + s_worldData.marksurfaces = out; + s_worldData.nummarksurfaces = count; + + for ( i=0 ; i s_worldData.numsurfaces) + assert(0); + + out[i] = s_worldData.surfaces + in[i]; + + if (out[i]->shader && out[i]->shader->sort == SS_PORTAL) + { + s_worldData.portalPresent = qtrue; + } + } +} + +/* +================= +R_LoadPlanes +================= +*/ +static void R_LoadPlanes( void ) { + //New method - share with server. + s_worldData.planes = cmg.planes; + s_worldData.numplanes = cmg.numPlanes; +} + +/* +================= +R_LoadFogs + +================= +*/ +static void R_LoadFogs( void *fogdata, int foglen, + void *brushdata, int brushlen, + void *sidedata, int sidelen ) { + int i; + fog_t *out; + dfog_t *fogs; + dbrush_t *brushes, *brush; + dbrushside_t *sides; + int count, brushesCount, sidesCount; + int sideNum; + int planeNum; + shader_t *shader; + float d; + int firstSide=0; + short lightmaps[MAXLIGHTMAPS] = { LIGHTMAP_NONE } ; + + fogs = (dfog_t *)(fogdata); + if (foglen % sizeof(*fogs)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + count = foglen / sizeof(*fogs); + + // create fog structres for them + // NOTE: we allocate memory for an extra one so that the LA goggles can turn on their own fog + s_worldData.numfogs = count + 1; + s_worldData.fogs = (fog_t *)Hunk_Alloc (( s_worldData.numfogs + 1)*sizeof(*out), qtrue ); + s_worldData.globalFog = -1; + out = s_worldData.fogs + 1; + + if ( !count ) { + return; + } + + brushes = (dbrush_t *)(brushdata); + if (brushlen % sizeof(*brushes)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + brushesCount = brushlen / sizeof(*brushes); + + sides = (dbrushside_t *)(sidedata); + if (sidelen % sizeof(*sides)) { + Com_Error (ERR_DROP, "LoadMap: funny lump size in %s",s_worldData.name); + } + sidesCount = sidelen / sizeof(*sides); + + for ( i=0 ; ioriginalBrushNumber = fogs->brushNum; + if (out->originalBrushNumber == -1) + { + out->bounds[0][0] = out->bounds[0][1] = out->bounds[0][2] = MIN_WORLD_COORD; + out->bounds[1][0] = out->bounds[1][1] = out->bounds[1][2] = MAX_WORLD_COORD; + s_worldData.globalFog = i+1; + } + else + { + if ( (unsigned)out->originalBrushNumber >= brushesCount ) { + Com_Error( ERR_DROP, "fog brushNumber out of range" ); + } + brush = brushes + out->originalBrushNumber; + + firstSide = brush->firstSide; + + if ( (unsigned)firstSide > sidesCount - 6 ) { + Com_Error( ERR_DROP, "fog brush sideNumber out of range" ); + } + + // brushes are always sorted with the axial sides first + sideNum = firstSide + 0; + planeNum = sides[ sideNum ].planeNum; + out->bounds[0][0] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 1; + planeNum = sides[ sideNum ].planeNum; + out->bounds[1][0] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 2; + planeNum = sides[ sideNum ].planeNum; + out->bounds[0][1] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 3; + planeNum = sides[ sideNum ].planeNum; + out->bounds[1][1] = s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 4; + planeNum = sides[ sideNum ].planeNum; + out->bounds[0][2] = -s_worldData.planes[ planeNum ].dist; + + sideNum = firstSide + 5; + planeNum = sides[ sideNum ].planeNum; + out->bounds[1][2] = s_worldData.planes[ planeNum ].dist; + } + + // get information from the shader for fog parameters + shader = R_FindShader( fogs->shader, lightmaps, stylesDefault, qtrue ); + + out->parms = *shader->fogParms; + out->colorInt = ColorBytes4 ( shader->fogParms->color[0] * tr.identityLight, + shader->fogParms->color[1] * tr.identityLight, + shader->fogParms->color[2] * tr.identityLight, 1.0 ); + + d = shader->fogParms->depthForOpaque < 1 ? 1 : shader->fogParms->depthForOpaque; + out->tcScale = 1.0 / ( d * 8 ); + + // set the gradient vector + sideNum = fogs->visibleSide; + + if ( sideNum == -1 ) { + out->hasSurface = qfalse; + } else { + out->hasSurface = qtrue; + planeNum = sides[ firstSide + sideNum ].planeNum; + VectorSubtract( vec3_origin, s_worldData.planes[ planeNum ].normal, out->surface ); + out->surface[3] = -s_worldData.planes[ planeNum ].dist; + } + + out++; + } + + // Initialise the last fog so we can use it with the LA Goggles + // NOTE: We are might appear to be off the end of the array, but we allocated an extra memory slot above but [purposely] didn't + // increment the total world numFogs to match our array size + VectorSet(out->bounds[0], MIN_WORLD_COORD, MIN_WORLD_COORD, MIN_WORLD_COORD); + VectorSet(out->bounds[1], MAX_WORLD_COORD, MAX_WORLD_COORD, MAX_WORLD_COORD); + out->originalBrushNumber = -1; + out->parms.color[0] = 0.0f; + out->parms.color[1] = 0.0f; + out->parms.color[2] = 0.0f; + out->parms.color[3] = 0.0f; + out->parms.depthForOpaque = 0.0f; + out->colorInt = 0x00000000; + out->tcScale = 0.0f; + out->hasSurface = false; +} + +/* +================ +R_LoadLightGrid + +================ +*/ +void R_LoadLightGrid( void *data, int len ) { + vec3_t maxs; + world_t *w; + int i; + float *wMins, *wMaxs; + + w = &s_worldData; + + w->lightGridInverseSize[0] = 1.0 / w->lightGridSize[0]; + w->lightGridInverseSize[1] = 1.0 / w->lightGridSize[1]; + w->lightGridInverseSize[2] = 1.0 / w->lightGridSize[2]; + + wMins = w->bmodels[0].bounds[0]; + wMaxs = w->bmodels[0].bounds[1]; + + for ( i = 0 ; i < 3 ; i++ ) { + w->lightGridOrigin[i] = w->lightGridSize[i] * ceil( wMins[i] / w->lightGridSize[i] ); + maxs[i] = w->lightGridSize[i] * floor( wMaxs[i] / w->lightGridSize[i] ); + w->lightGridBounds[i] = (maxs[i] - w->lightGridOrigin[i])/w->lightGridSize[i] + 1; + } + + w->lightGridData = (mgrid_t *)Hunk_Alloc( len, qfalse ); + memcpy( w->lightGridData, data, len ); +} + +/* +================ +R_LoadLightGridArray + +================ +*/ +void R_LoadLightGridArray( void *data, int len ) { + world_t *w; + + w = &s_worldData; + + w->numGridArrayElements = w->lightGridBounds[0] * w->lightGridBounds[1] * w->lightGridBounds[2]; + + if ( len != w->numGridArrayElements * sizeof(*w->lightGridArray) ) { + if (len>0)//don't warn if not even lit + VID_Printf( PRINT_WARNING, "WARNING: light grid array mismatch\n" ); + w->lightGridData = NULL; + return; + } + + w->lightGridArray = (unsigned short *)Hunk_Alloc( len, qfalse ); + memcpy( w->lightGridArray, data, len ); +} + +/* +================ +R_LoadEntities +================ +*/ +void R_LoadEntities( void *data, int len ) { + const char *p, *token; + char keyname[MAX_TOKEN_CHARS]; + char value[MAX_TOKEN_CHARS]; + world_t *w; + float ambient = 1; + + w = &s_worldData; + w->lightGridSize[0] = 64; + w->lightGridSize[1] = 64; + w->lightGridSize[2] = 128; + + VectorSet(tr.sunAmbient, 1, 1, 1); + tr.distanceCull = 12000;//DEFAULT_DISTANCE_CULL; + + p = (char *)(data); + + token = COM_ParseExt( &p, qtrue ); + if (!*token || *token != '{') { + return; + } + + // only parse the world spawn + while ( 1 ) { + // parse key + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(keyname, token, sizeof(keyname)); + + // parse value + token = COM_ParseExt( &p, qtrue ); + + if ( !*token || *token == '}' ) { + break; + } + Q_strncpyz(value, token, sizeof(value)); + + if (!Q_stricmp(keyname, "distanceCull")) { + sscanf(value, "%f", &tr.distanceCull ); + continue; + } + //check for linear fog -rww + if (!Q_stricmp(keyname, "linFogStart")) { + sscanf(value, "%f", &tr.rangedFog ); + tr.rangedFog = -tr.rangedFog; + continue; + } + // check for a different grid size + if (!Q_stricmp(keyname, "gridsize")) { + sscanf(value, "%f %f %f", &w->lightGridSize[0], &w->lightGridSize[1], &w->lightGridSize[2] ); + continue; + } + // find the optional world ambient for arioche + if (!Q_stricmp(keyname, "_color")) { + sscanf(value, "%f %f %f", &tr.sunAmbient[0], &tr.sunAmbient[1], &tr.sunAmbient[2] ); + continue; + } + if (!Q_stricmp(keyname, "ambient")) { + sscanf(value, "%f", &ambient); + continue; + } + } + //both default to 1 so no harm if not present. + VectorScale( tr.sunAmbient, ambient, tr.sunAmbient); +} + + +/* +================= +RE_LoadWorldMap + +Called directly from cgame +================= +*/ +void RE_LoadWorldMap_Actual( const char *name, world_t &worldData, int index ) { + char stripName[MAX_QPATH]; + Lump outputLumps[3]; + + // This is no longer correct. The new code supports sub-models, apparently BSPs in + // several chunks. If any map tries to use them, the following COM_Error will go + // off. We haven't hit it yet, but if (when) we do, check out tr_bsp.cpp for changes. + if ( tr.worldMapLoaded ) { + Com_Error( ERR_DROP, "ERROR: attempted to redundantly load world map\n" ); + } + + // set default sun direction to be used if it isn't + // overridden by a shader + skyboxportal = 0; + + tr.sunDirection[0] = 0.45f; + tr.sunDirection[1] = 0.3f; + tr.sunDirection[2] = 0.9f; + + VectorNormalize( tr.sunDirection ); + + Cvar_SetValue( "r_sundir_x", tr.sunDirection[0] ); + Cvar_SetValue( "r_sundir_y", tr.sunDirection[1] ); + Cvar_SetValue( "r_sundir_z", tr.sunDirection[2] ); + + tr.worldMapLoaded = qtrue; + + // clear tr.world so if the level fails to load, the next + // try will not look at the partially loaded version + tr.world = NULL; + + //Preserve data which was already set in cm_load + msurface_t *surfacePtr = s_worldData.surfaces; + int numSurfaces = s_worldData.numsurfaces; + memset( &s_worldData, 0, sizeof( s_worldData ) ); + s_worldData.surfaces = surfacePtr; + s_worldData.numsurfaces = numSurfaces; + //s_worldData.shaders = cm.shaders; + s_worldData.numShaders = cmg.numShaders; + + Q_strncpyz( s_worldData.name, name, sizeof( s_worldData.name ) ); + + Q_strncpyz( s_worldData.baseName, COM_SkipPath( s_worldData.name ), sizeof( s_worldData.name ) ); + COM_StripExtension( s_worldData.baseName, s_worldData.baseName ); + + COM_StripExtension(name, stripName); + + c_gridVerts = 0; + + // load into heap + R_LoadPlanes (); + + outputLumps[0].load(stripName, "fogs"); + outputLumps[1].load(stripName, "brushes"); + outputLumps[2].load(stripName, "brushsides"); + R_LoadFogs( outputLumps[0].data, outputLumps[0].len, + outputLumps[1].data, outputLumps[1].len, + outputLumps[2].data, outputLumps[2].len ); + outputLumps[2].clear(); + outputLumps[1].clear(); + + outputLumps[0].load(stripName, "leafsurfaces"); + R_LoadMarksurfaces (outputLumps[0].data, outputLumps[0].len); + + outputLumps[0].load(stripName, "nodes"); + outputLumps[1].load(stripName, "leafs"); + R_LoadNodesAndLeafs (outputLumps[0].data, outputLumps[0].len, + outputLumps[1].data, outputLumps[1].len); + outputLumps[1].clear(); + + outputLumps[0].load(stripName, "models"); + R_LoadSubmodels (outputLumps[0].data, outputLumps[0].len); + + R_LoadVisibility(); + + outputLumps[0].load(stripName, "entities"); + R_LoadEntities( outputLumps[0].data, outputLumps[0].len ); + outputLumps[0].load(stripName, "lightgrid"); + R_LoadLightGrid( outputLumps[0].data, outputLumps[0].len ); + outputLumps[0].load(stripName, "lightarray"); + R_LoadLightGridArray( outputLumps[0].data, outputLumps[0].len ); + + // only set tr.world now that we know the entire level has loaded properly + tr.world = &s_worldData; + + // Load the light parms for this level + R_LoadLevelLightParms(); + R_GetLightParmsForLevel(); +} + + +// new wrapper used for convenience to tell z_malloc()-fail recovery code whether it's safe to dump the cached-bsp or not. +// +extern qboolean gbUsingCachedMapDataRightNow; +void RE_LoadWorldMap( const char *name ) +{ + memset(entityVisList, -1, sizeof(entityVisList)); + + gbUsingCachedMapDataRightNow = qtrue; // !!!!!!!!!!!! + + RE_LoadWorldMap_Actual( name, s_worldData, 0 ); + + gbUsingCachedMapDataRightNow = qfalse; // !!!!!!!!!!!! +} + + +//A nasty looking function which loops through all images used by all surfaces +//and returns the number of matches for the given image. +#ifndef FINAL_BUILD +int R_SurfaceImageCount(const image_t *image1) +{ + int count = 0; + + for(int i=0; inumUnfoggedPasses; j++){ + for(int k=0; kstages[j].bundle[k].image; + if(image2 != NULL && !Q_stricmp(image1->imgName, image2->imgName)) { + count++; + } + + } + } + } + + return count; +} +#endif diff --git a/code/renderer/tr_cmds.cpp b/code/renderer/tr_cmds.cpp new file mode 100644 index 0000000..e0f60a9 --- /dev/null +++ b/code/renderer/tr_cmds.cpp @@ -0,0 +1,512 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + + +/* +===================== +R_PerformanceCounters +===================== +*/ +void R_PerformanceCounters( void ) { +#ifndef _XBOX + if ( !r_speeds->integer ) { + // clear the counters even if we aren't printing + memset( &tr.pc, 0, sizeof( tr.pc ) ); + memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); + return; + } + + if (r_speeds->integer == 1) { + const float texSize = R_SumOfUsedImages( qfalse )/(8*1048576.0f)*(r_texturebits->integer?r_texturebits->integer:glConfig.colorBits); + VID_Printf (PRINT_ALL, "%i/%i shdrs/srfs %i leafs %i vrts %i/%i tris %.2fMB tex %.2f dc\n", + backEnd.pc.c_shaders, backEnd.pc.c_surfaces, tr.pc.c_leafs, backEnd.pc.c_vertexes, + backEnd.pc.c_indexes/3, backEnd.pc.c_totalIndexes/3, + texSize, backEnd.pc.c_overDraw / (float)(glConfig.vidWidth * glConfig.vidHeight) ); + } else if (r_speeds->integer == 2) { + VID_Printf (PRINT_ALL, "(patch) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_patch_in, tr.pc.c_sphere_cull_patch_clip, tr.pc.c_sphere_cull_patch_out, + tr.pc.c_box_cull_patch_in, tr.pc.c_box_cull_patch_clip, tr.pc.c_box_cull_patch_out ); + VID_Printf (PRINT_ALL, "(md3) %i sin %i sclip %i sout %i bin %i bclip %i bout\n", + tr.pc.c_sphere_cull_md3_in, tr.pc.c_sphere_cull_md3_clip, tr.pc.c_sphere_cull_md3_out, + tr.pc.c_box_cull_md3_in, tr.pc.c_box_cull_md3_clip, tr.pc.c_box_cull_md3_out ); + } else if (r_speeds->integer == 3) { + VID_Printf (PRINT_ALL, "viewcluster: %i\n", tr.viewCluster ); + } else if (r_speeds->integer == 4) { + if ( backEnd.pc.c_dlightVertexes ) { + VID_Printf (PRINT_ALL, "dlight srf:%i culled:%i verts:%i tris:%i\n", + tr.pc.c_dlightSurfaces, tr.pc.c_dlightSurfacesCulled, + backEnd.pc.c_dlightVertexes, backEnd.pc.c_dlightIndexes / 3 ); + } + } + else if (r_speeds->integer == 5 ) + { + VID_Printf( PRINT_ALL, "zFar: %.0f\n", tr.viewParms.zFar ); + } + else if (r_speeds->integer == 6 ) + { + VID_Printf( PRINT_ALL, "flare adds:%i tests:%i renders:%i\n", + backEnd.pc.c_flareAdds, backEnd.pc.c_flareTests, backEnd.pc.c_flareRenders ); + } + else if (r_speeds->integer == 7) { + const float texSize = R_SumOfUsedImages(qtrue) / (1048576.0f); + const float backBuff= glConfig.vidWidth * glConfig.vidHeight * glConfig.colorBits / (8.0f * 1024*1024); + const float depthBuff= glConfig.vidWidth * glConfig.vidHeight * glConfig.depthBits / (8.0f * 1024*1024); + const float stencilBuff= glConfig.vidWidth * glConfig.vidHeight * glConfig.stencilBits / (8.0f * 1024*1024); + VID_Printf (PRINT_ALL, "Tex MB %.2f + buffers %.2f MB = Total %.2fMB\n", + texSize, backBuff*2+depthBuff+stencilBuff, texSize+backBuff*2+depthBuff+stencilBuff); + } +#endif + + memset( &tr.pc, 0, sizeof( tr.pc ) ); + memset( &backEnd.pc, 0, sizeof( backEnd.pc ) ); +} + + +/* +==================== +R_InitCommandBuffers +==================== +*/ +void R_InitCommandBuffers( void ) { +} + +/* +==================== +R_ShutdownCommandBuffers +==================== +*/ +void R_ShutdownCommandBuffers( void ) { +} + +/* +==================== +R_IssueRenderCommands +==================== +*/ +int c_blockedOnRender; +int c_blockedOnMain; + +void R_IssueRenderCommands( qboolean runPerformanceCounters ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData->commands; + + // add an end-of-list command + *(int *)(cmdList->cmds + cmdList->used) = RC_END_OF_LIST; + + // clear it out, in case this is a sync and not a buffer flip + cmdList->used = 0; + + // at this point, the back end thread is idle, so it is ok + // to look at it's performance counters + if ( runPerformanceCounters ) { + R_PerformanceCounters(); + } + + // actually start the commands going + if ( !r_skipBackEnd->integer ) { + // let it start on the new batch + RB_ExecuteRenderCommands( cmdList->cmds ); + } +} + + +/* +==================== +R_SyncRenderThread + +Issue any pending commands and wait for them to complete. +After exiting, the render thread will have completed its work +and will remain idle and the main thread is free to issue +OpenGL calls until R_IssueRenderCommands is called. +==================== +*/ +void R_SyncRenderThread( void ) { +#ifndef _XBOX + if ( !tr.registered ) { + return; + } + R_IssueRenderCommands( qfalse ); +#endif +} + +/* +============ +R_GetCommandBuffer + +make sure there is enough command space, waiting on the +render thread if needed. +============ +*/ +void *R_GetCommandBuffer( int bytes ) { + renderCommandList_t *cmdList; + + cmdList = &backEndData->commands; + + // always leave room for the end of list command + if ( cmdList->used + bytes + 4 > MAX_RENDER_COMMANDS ) { +#if defined(_DEBUG) && defined(_XBOX) + Com_Printf(S_COLOR_RED"Command buffer overflow! Tell Brian.\n"); +#endif + if ( bytes > MAX_RENDER_COMMANDS - 4 ) { + Com_Error( ERR_FATAL, "R_GetCommandBuffer: bad size %i", bytes ); + } + // if we run out of room, just start dropping commands + return NULL; + } + + cmdList->used += bytes; + + return cmdList->cmds + cmdList->used - bytes; +} + + +/* +============= +R_AddDrawSurfCmd + +============= +*/ +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ) { + drawSurfsCommand_t *cmd; + + cmd = (drawSurfsCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_DRAW_SURFS; + + cmd->drawSurfs = drawSurfs; + cmd->numDrawSurfs = numDrawSurfs; + + cmd->refdef = tr.refdef; + cmd->viewParms = tr.viewParms; +} + + +/* +============= +RE_SetColor + +Passing NULL will set the color to white +============= +*/ +void RE_SetColor( const float *rgba ) { + setColorCommand_t *cmd; + + cmd = (setColorCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SET_COLOR; + if ( rgba ) { + cmd->color[0] = rgba[0]; + cmd->color[1] = rgba[1]; + cmd->color[2] = rgba[2]; + cmd->color[3] = rgba[3]; + return; + } + + cmd->color[0] = 1; + cmd->color[1] = 1; + cmd->color[2] = 1; + cmd->color[3] = 1; +} + + +/* +============= +RE_StretchPic +============= +*/ +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + stretchPicCommand_t *cmd; + + cmd = (stretchPicCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_STRETCH_PIC; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; +} + +/* +============= +RE_RotatePic +============= +*/ +void RE_RotatePic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ) { + rotatePicCommand_t *cmd; + + cmd = (rotatePicCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_ROTATE_PIC; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; + cmd->a = a; +} + +/* +============= +RE_RotatePic2 +============= +*/ +void RE_RotatePic2 ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ) { + rotatePicCommand_t *cmd; + + cmd = (rotatePicCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_ROTATE_PIC2; + cmd->shader = R_GetShaderByHandle( hShader ); + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; + cmd->s1 = s1; + cmd->t1 = t1; + cmd->s2 = s2; + cmd->t2 = t2; + cmd->a = a; +} + +void RE_LAGoggles( void ) +{ + tr.refdef.rdflags |= (RDF_doLAGoggles|RDF_doFullbright); + + fog_t *fog = &tr.world->fogs[tr.world->numfogs]; + + fog->parms.color[0] = 0.75f; + fog->parms.color[1] = 0.42f + random() * 0.025f; + fog->parms.color[2] = 0.07f; + fog->parms.color[3] = 1.0f; + fog->parms.depthForOpaque = 10000; + fog->colorInt = ColorBytes4(fog->parms.color[0], fog->parms.color[1], fog->parms.color[2], fog->parms.color[3]); + fog->tcScale = 2.0f / ( fog->parms.depthForOpaque * (1.0f + cos( tr.refdef.floatTime) * 0.1f)); +} + +void RE_RenderWorldEffects(void) +{ + setModeCommand_t *cmd; + + cmd = (setModeCommand_t *)R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_WORLD_EFFECTS; +} + +/* +============= +RE_Scissor +============= +*/ +void RE_Scissor ( float x, float y, float w, float h) +{ + scissorCommand_t *cmd; + + cmd = (scissorCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SCISSOR; + cmd->x = x; + cmd->y = y; + cmd->w = w; + cmd->h = h; +} + +/* +==================== +RE_BeginFrame + +If running in stereo, RE_BeginFrame will be called twice +for each RE_EndFrame +==================== +*/ +void RE_BeginFrame( stereoFrame_t stereoFrame ) { + drawBufferCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + glState.finishCalled = qfalse; + + tr.frameCount++; + tr.frameSceneNum = 0; + + // + // do overdraw measurement + // +#ifndef _XBOX + if ( r_measureOverdraw->integer ) + { + if ( glConfig.stencilBits < 4 ) + { + VID_Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits ); + Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } + else if ( r_shadows->integer == 2 ) + { + VID_Printf( PRINT_ALL, "Warning: stencil shadows and overdraw measurement are mutually exclusive\n" ); + Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } + else + { + R_SyncRenderThread(); + qglEnable( GL_STENCIL_TEST ); + qglStencilMask( ~0U ); + qglClearStencil( 0U ); + qglStencilFunc( GL_ALWAYS, 0U, ~0U ); + qglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + } + r_measureOverdraw->modified = qfalse; + } + else + { + // this is only reached if it was on and is now off + if ( r_measureOverdraw->modified ) { + R_SyncRenderThread(); + qglDisable( GL_STENCIL_TEST ); + r_measureOverdraw->modified = qfalse; + } + } +#endif + + // + // texturemode stuff + // + if ( r_textureMode->modified || r_ext_texture_filter_anisotropic->modified) { + R_SyncRenderThread(); + GL_TextureMode( r_textureMode->string ); + r_textureMode->modified = qfalse; + r_ext_texture_filter_anisotropic->modified = qfalse; + } + + // + // gamma stuff + // + if ( r_gamma->modified ) { + r_gamma->modified = qfalse; + + R_SyncRenderThread(); + R_SetColorMappings(); + } + + // check for errors + if ( !r_ignoreGLErrors->integer ) { + int err; + + R_SyncRenderThread(); + if ( ( err = qglGetError() ) != GL_NO_ERROR ) { + Com_Error( ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!\n", err ); + } + } + + // + // draw buffer stuff + // + cmd = (drawBufferCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_DRAW_BUFFER; + + if ( glConfig.stereoEnabled ) { + if ( stereoFrame == STEREO_LEFT ) { + cmd->buffer = (int)GL_BACK_LEFT; + } else if ( stereoFrame == STEREO_RIGHT ) { + cmd->buffer = (int)GL_BACK_RIGHT; + } else { + Com_Error( ERR_FATAL, "RE_BeginFrame: Stereo is enabled, but stereoFrame was %i", stereoFrame ); + } + } else { + if ( stereoFrame != STEREO_CENTER ) { + Com_Error( ERR_FATAL, "RE_BeginFrame: Stereo is disabled, but stereoFrame was %i", stereoFrame ); + } +// if ( !Q_stricmp( r_drawBuffer->string, "GL_FRONT" ) ) { +// cmd->buffer = (int)GL_FRONT; +// } else + { + cmd->buffer = (int)GL_BACK; + } + } +} + + +/* +============= +RE_EndFrame + +Returns the number of msec spent in the back end +============= +*/ +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) { + swapBuffersCommand_t *cmd; + + if ( !tr.registered ) { + return; + } + cmd = (swapBuffersCommand_t *) R_GetCommandBuffer( sizeof( *cmd ) ); + if ( !cmd ) { + return; + } + cmd->commandId = RC_SWAP_BUFFERS; + +#ifdef _XBOX + if (!qglBeginFrame()) return; +#endif + + R_IssueRenderCommands( qtrue ); + +#ifdef _XBOX + RE_ProcessDissolve(); // render the disolve now + qglEndFrame(); +#endif + + // use the other buffers next frame, because another CPU + // may still be rendering into the current ones + R_ToggleSmpFrame(); + + if ( frontEndMsec ) { + *frontEndMsec = tr.frontEndMsec; + } + tr.frontEndMsec = 0; + if ( backEndMsec ) { + *backEndMsec = backEnd.pc.msec; + } + backEnd.pc.msec = 0; + + for(int i=0;ixyz[0] = 0.5 * (a->xyz[0] + b->xyz[0]); + out->xyz[1] = 0.5 * (a->xyz[1] + b->xyz[1]); + out->xyz[2] = 0.5 * (a->xyz[2] + b->xyz[2]); + + out->dvst[0] = (short)(0.5 * (float)(a->dvst[0] + b->dvst[0])); + out->dvst[1] = (short)(0.5 * (float)(a->dvst[1] + b->dvst[1])); + + out->normal[0] = 0.5 * (a->normal[0] + b->normal[0]); + out->normal[1] = 0.5 * (a->normal[1] + b->normal[1]); + out->normal[2] = 0.5 * (a->normal[2] + b->normal[2]); + + for(k=0;kdvlightmap[k][0] = (short)(0.5 * (float)(a->dvlightmap[k][0] + b->dvlightmap[k][0])); + out->dvlightmap[k][1] = (short)(0.5 * (float)(a->dvlightmap[k][1] + b->dvlightmap[k][1])); + +#ifdef COMPRESS_VERTEX_COLORS + // Need to do averaging per every four bits + for (int j = 0; j < 2; ++j) + { + byte ah, al, bh, bl; + ah = a->dvcolor[k][j] >> 4; + al = a->dvcolor[k][j] & 0x0F; + bh = b->dvcolor[k][j] >> 4; + bl = b->dvcolor[k][j] & 0x0F; + out->dvcolor[k][j] = (((ah+bh) / 2) << 4) | ((al+bl) / 2); + } +#else + out->dvcolor[k][0] = (a->dvcolor[k][0] + b->dvcolor[k][0]) / 2; + out->dvcolor[k][1] = (a->dvcolor[k][1] + b->dvcolor[k][1]) / 2; + out->dvcolor[k][2] = (a->dvcolor[k][2] + b->dvcolor[k][2]) / 2; + out->dvcolor[k][3] = (a->dvcolor[k][3] + b->dvcolor[k][3]) / 2; +#endif + } +} + +#else // _XBOX + +static void LerpDrawVert( drawVert_t *a, drawVert_t *b, drawVert_t *out ) { + int k; + out->xyz[0] = 0.5 * (a->xyz[0] + b->xyz[0]); + out->xyz[1] = 0.5 * (a->xyz[1] + b->xyz[1]); + out->xyz[2] = 0.5 * (a->xyz[2] + b->xyz[2]); + + out->st[0] = 0.5 * (a->st[0] + b->st[0]); + out->st[1] = 0.5 * (a->st[1] + b->st[1]); + + out->normal[0] = 0.5 * (a->normal[0] + b->normal[0]); + out->normal[1] = 0.5 * (a->normal[1] + b->normal[1]); + out->normal[2] = 0.5 * (a->normal[2] + b->normal[2]); + + for(k=0;klightmap[k][0] = 0.5 * (a->lightmap[k][0] + b->lightmap[k][0]); + out->lightmap[k][1] = 0.5 * (a->lightmap[k][1] + b->lightmap[k][1]); + + out->color[k][0] = (a->color[k][0] + b->color[k][0]) >> 1; + out->color[k][1] = (a->color[k][1] + b->color[k][1]) >> 1; + out->color[k][2] = (a->color[k][2] + b->color[k][2]) >> 1; + out->color[k][3] = (a->color[k][3] + b->color[k][3]) >> 1; + } +} +#endif // _XBOX + +/* +============ +Transpose +============ +*/ +#ifdef _XBOX +static void Transpose( int width, int height, drawVert_t* ctrl/*[MAX_GRID_SIZE][MAX_GRID_SIZE]*/ ) { + int i, j; + drawVert_t temp; + + if ( width > height ) { + for ( i = 0 ; i < height ; i++ ) { + for ( j = i + 1 ; j < width ; j++ ) { + if ( j < height ) { + // swap the value + temp = ctrl[j*MAX_GRID_SIZE+i]; + ctrl[j*MAX_GRID_SIZE+i] = ctrl[i*MAX_GRID_SIZE+j]; + ctrl[i*MAX_GRID_SIZE+j] = temp; + } else { + // just copy + ctrl[j*MAX_GRID_SIZE+i] = ctrl[i*MAX_GRID_SIZE+j]; + } + } + } + } else { + for ( i = 0 ; i < width ; i++ ) { + for ( j = i + 1 ; j < height ; j++ ) { + if ( j < width ) { + // swap the value + temp = ctrl[i*MAX_GRID_SIZE+j]; + ctrl[i*MAX_GRID_SIZE+j] = ctrl[j*MAX_GRID_SIZE+i]; + ctrl[j*MAX_GRID_SIZE+i] = temp; + } else { + // just copy + ctrl[i*MAX_GRID_SIZE+j] = ctrl[j*MAX_GRID_SIZE+i]; + } + } + } + } + +} + +#else // _XBOX +static void Transpose( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + drawVert_t temp; + + if ( width > height ) { + for ( i = 0 ; i < height ; i++ ) { + for ( j = i + 1 ; j < width ; j++ ) { + if ( j < height ) { + // swap the value + temp = ctrl[j][i]; + ctrl[j][i] = ctrl[i][j]; + ctrl[i][j] = temp; + } else { + // just copy + ctrl[j][i] = ctrl[i][j]; + } + } + } + } else { + for ( i = 0 ; i < width ; i++ ) { + for ( j = i + 1 ; j < height ; j++ ) { + if ( j < width ) { + // swap the value + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[j][i]; + ctrl[j][i] = temp; + } else { + // just copy + ctrl[i][j] = ctrl[j][i]; + } + } + } + } + +} +#endif + +/* +================= +MakeMeshNormals + +Handles all the complicated wrapping and degenerate cases +================= +*/ +#ifdef _XBOX +static void MakeMeshNormals( int width, int height, drawVert_t* ctrl/*[MAX_GRID_SIZE][MAX_GRID_SIZE]*/ ) { + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count; + vec3_t base; + vec3_t delta; + int x, y; + drawVert_t *dv; + vec3_t around[8], temp; + qboolean good[8]; + qboolean wrapWidth, wrapHeight; + float len; + static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + wrapWidth = qfalse; + for ( i = 0 ; i < height ; i++ ) { + VectorSubtract( ctrl[i*MAX_GRID_SIZE+0].xyz, ctrl[i*MAX_GRID_SIZE+width-1].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == height ) { + wrapWidth = qtrue; + } + + wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + VectorSubtract( ctrl[0*MAX_GRID_SIZE+i].xyz, ctrl[(height-1)*MAX_GRID_SIZE+i].xyz, delta ); + len = VectorLengthSquared( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == width) { + wrapHeight = qtrue; + } + + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + count = 0; + dv = &ctrl[j*MAX_GRID_SIZE+i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = qfalse; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + VectorSubtract( ctrl[y*MAX_GRID_SIZE+x].xyz, base, temp ); + if ( VectorNormalize2( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } else { + good[k] = qtrue; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + CrossProduct( around[(k+1)&7], around[k], normal ); + if ( VectorNormalize2( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + if ( count == 0 ) { +//printf("bad normal\n"); + count = 1; + } + VectorNormalize2( sum, dv->normal ); + } + } +} + +#else // _XBOX + +static void MakeMeshNormals( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j, k, dist; + vec3_t normal; + vec3_t sum; + int count; + vec3_t base; + vec3_t delta; + int x, y; + drawVert_t *dv; + vec3_t around[8], temp; + qboolean good[8]; + qboolean wrapWidth, wrapHeight; + float len; +static int neighbors[8][2] = { + {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} + }; + + wrapWidth = qfalse; + for ( i = 0 ; i < height ; i++ ) { + VectorSubtract( ctrl[i][0].xyz, ctrl[i][width-1].xyz, delta ); + len = VectorLength( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == height ) { + wrapWidth = qtrue; + } + + wrapHeight = qfalse; + for ( i = 0 ; i < width ; i++ ) { + VectorSubtract( ctrl[0][i].xyz, ctrl[height-1][i].xyz, delta ); + len = VectorLength( delta ); + if ( len > 1.0 ) { + break; + } + } + if ( i == width) { + wrapHeight = qtrue; + } + + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + count = 0; + dv = &ctrl[j][i]; + VectorCopy( dv->xyz, base ); + for ( k = 0 ; k < 8 ; k++ ) { + VectorClear( around[k] ); + good[k] = qfalse; + + for ( dist = 1 ; dist <= 3 ; dist++ ) { + x = i + neighbors[k][0] * dist; + y = j + neighbors[k][1] * dist; + if ( wrapWidth ) { + if ( x < 0 ) { + x = width - 1 + x; + } else if ( x >= width ) { + x = 1 + x - width; + } + } + if ( wrapHeight ) { + if ( y < 0 ) { + y = height - 1 + y; + } else if ( y >= height ) { + y = 1 + y - height; + } + } + + if ( x < 0 || x >= width || y < 0 || y >= height ) { + break; // edge of patch + } + VectorSubtract( ctrl[y][x].xyz, base, temp ); + if ( VectorNormalize2( temp, temp ) == 0 ) { + continue; // degenerate edge, get more dist + } else { + good[k] = qtrue; + VectorCopy( temp, around[k] ); + break; // good edge + } + } + } + + VectorClear( sum ); + for ( k = 0 ; k < 8 ; k++ ) { + if ( !good[k] || !good[(k+1)&7] ) { + continue; // didn't get two points + } + CrossProduct( around[(k+1)&7], around[k], normal ); + if ( VectorNormalize2( normal, normal ) == 0 ) { + continue; + } + VectorAdd( normal, sum, sum ); + count++; + } + if ( count == 0 ) { +//printf("bad normal\n"); + count = 1; + } + VectorNormalize2( sum, dv->normal ); + } + } +} +#endif + + +/* +============ +InvertCtrl +============ +*/ +#ifdef _XBOX +static void InvertCtrl( int width, int height, drawVert_t* ctrl/*[MAX_GRID_SIZE][MAX_GRID_SIZE]*/ ) { + int i, j; + drawVert_t temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width/2 ; j++ ) { + temp = ctrl[i*MAX_GRID_SIZE+j]; + ctrl[i*MAX_GRID_SIZE+j] = ctrl[i*MAX_GRID_SIZE+width-1-j]; + ctrl[i*MAX_GRID_SIZE+width-1-j] = temp; + } + } +} + +#else // _XBOX +static void InvertCtrl( int width, int height, drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { + int i, j; + drawVert_t temp; + + for ( i = 0 ; i < height ; i++ ) { + for ( j = 0 ; j < width/2 ; j++ ) { + temp = ctrl[i][j]; + ctrl[i][j] = ctrl[i][width-1-j]; + ctrl[i][width-1-j] = temp; + } + } +} +#endif // _XBOX + +/* +================= +InvertErrorTable +================= +*/ +#ifdef _XBOX +static void InvertErrorTable( float* errorTable/*[2][MAX_GRID_SIZE]*/, int width, int height ) { + int i; + float copy[2][MAX_GRID_SIZE]; + + memcpy( copy, errorTable, sizeof( copy ) ); + + for ( i = 0 ; i < width ; i++ ) { + errorTable[1*MAX_GRID_SIZE+i] = copy[0][i]; //[width-1-i]; + } + + for ( i = 0 ; i < height ; i++ ) { + errorTable[0*MAX_GRID_SIZE+i] = copy[1][height-1-i]; + } + +} +#else // _XBOX +static void InvertErrorTable( float errorTable[2][MAX_GRID_SIZE], int width, int height ) { + int i; + float copy[2][MAX_GRID_SIZE]; + + memcpy( copy, errorTable, sizeof( copy ) ); + + for ( i = 0 ; i < width ; i++ ) { + errorTable[1][i] = copy[0][i]; //[width-1-i]; + } + + for ( i = 0 ; i < height ; i++ ) { + errorTable[0][i] = copy[1][height-1-i]; + } + +} +#endif // _XBOX + +/* +================== +PutPointsOnCurve +================== +*/ +#ifdef _XBOX +static void PutPointsOnCurve( drawVert_t* ctrl/*[MAX_GRID_SIZE][MAX_GRID_SIZE]*/, + int width, int height ) { + int i, j; + drawVert_t prev, next; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 1 ; j < height ; j += 2 ) { + LerpDrawVert( &ctrl[j*MAX_GRID_SIZE+i], &ctrl[(j+1)*MAX_GRID_SIZE+i], &prev ); + LerpDrawVert( &ctrl[j*MAX_GRID_SIZE+i], &ctrl[(j-1)*MAX_GRID_SIZE+i], &next ); + LerpDrawVert( &prev, &next, &ctrl[j*MAX_GRID_SIZE+i] ); + } + } + + + for ( j = 0 ; j < height ; j++ ) { + for ( i = 1 ; i < width ; i += 2 ) { + LerpDrawVert( &ctrl[j*MAX_GRID_SIZE+i], &ctrl[j*MAX_GRID_SIZE+i+1], &prev ); + LerpDrawVert( &ctrl[j*MAX_GRID_SIZE+i], &ctrl[j*MAX_GRID_SIZE+i-1], &next ); + LerpDrawVert( &prev, &next, &ctrl[j*MAX_GRID_SIZE+i] ); + } + } +} + +#else // _XBOX +static void PutPointsOnCurve( drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], + int width, int height ) { + int i, j; + drawVert_t prev, next; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 1 ; j < height ; j += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j+1][i], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j-1][i], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } + + + for ( j = 0 ; j < height ; j++ ) { + for ( i = 1 ; i < width ; i += 2 ) { + LerpDrawVert( &ctrl[j][i], &ctrl[j][i+1], &prev ); + LerpDrawVert( &ctrl[j][i], &ctrl[j][i-1], &next ); + LerpDrawVert( &prev, &next, &ctrl[j][i] ); + } + } +} +#endif // _XBOX + +/* +================= +R_SubdividePatchToGrid + +================= +*/ +#ifdef _XBOX +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, drawVert_t* points, + drawVert_t* ctrl, float* errorTable ) { + int i, j, k, l; + drawVert_t prev, next, mid; + float len, maxLen; + int dir; + int t; + srfGridMesh_t *grid; + drawVert_t *vert; + vec3_t tmpVec; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + ctrl[j*MAX_GRID_SIZE+i] = points[j*width+i]; + } + } + + for ( dir = 0 ; dir < 2 ; dir++ ) { + + for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) { + errorTable[dir*MAX_GRID_SIZE+j] = 0; + } + + // horizontal subdivisions + for ( j = 0 ; j + 2 < width ; j += 2 ) { + // check subdivided midpoints against control points + maxLen = 0; + for ( i = 0 ; i < height ; i++ ) { + vec3_t midxyz; + vec3_t dir; + vec3_t projected; + float d; + + // calculate the point on the curve + for ( l = 0 ; l < 3 ; l++ ) { + midxyz[l] = (ctrl[i*MAX_GRID_SIZE+j].xyz[l] + + ctrl[i*MAX_GRID_SIZE+j+1].xyz[l] * 2 + + ctrl[i*MAX_GRID_SIZE+j+2].xyz[l] ) * 0.25; + } + + // see how far off the line it is + // using dist-from-line will not account for internal + // texture warping, but it gives a lot less polygons than + // dist-from-midpoint + VectorSubtract( midxyz, ctrl[i*MAX_GRID_SIZE+j].xyz, midxyz ); + VectorSubtract( ctrl[i*MAX_GRID_SIZE+j+2].xyz, ctrl[i*MAX_GRID_SIZE+j].xyz, dir ); + VectorNormalize( dir ); + + d = DotProduct( midxyz, dir ); + VectorScale( dir, d, projected ); + VectorSubtract( midxyz, projected, midxyz); + len = VectorLengthSquared( midxyz ); + + if ( len > maxLen ) { + maxLen = len; + } + } + maxLen = sqrt(maxLen); + + // if all the points are on the lines, remove the entire columns + if ( maxLen < 0.1 ) { + errorTable[dir*MAX_GRID_SIZE+j+1] = 999; + continue; + } + + // see if we want to insert subdivided columns + if ( width + 2 > MAX_GRID_SIZE ) { + errorTable[dir*MAX_GRID_SIZE+j+1] = 1.0/maxLen; + continue; // can't subdivide any more + } + + if ( maxLen <= r_subdivisions->value ) { + errorTable[dir*MAX_GRID_SIZE+j+1] = 1.0/maxLen; + continue; // didn't need subdivision + } + + errorTable[dir*MAX_GRID_SIZE+j+2] = 1.0/maxLen; + + // insert two columns and replace the peak + width += 2; + for ( i = 0 ; i < height ; i++ ) { + LerpDrawVert( &ctrl[i*MAX_GRID_SIZE+j], &ctrl[i*MAX_GRID_SIZE+j+1], &prev ); + LerpDrawVert( &ctrl[i*MAX_GRID_SIZE+j+1], &ctrl[i*MAX_GRID_SIZE+j+2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = width - 1 ; k > j + 3 ; k-- ) { + ctrl[i*MAX_GRID_SIZE+k] = ctrl[i*MAX_GRID_SIZE+k-2]; + } + ctrl[i*MAX_GRID_SIZE+j + 1] = prev; + ctrl[i*MAX_GRID_SIZE+j + 2] = mid; + ctrl[i*MAX_GRID_SIZE+j + 3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + Transpose( width, height, ctrl ); + t = width; + width = height; + height = t; + } + + + // put all the aproximating points on the curve + PutPointsOnCurve( ctrl, width, height ); + + // cull out any rows or columns that are colinear + for ( i = 1 ; i < width-1 ; i++ ) { + if ( errorTable[0*MAX_GRID_SIZE+i] != 999 ) { + continue; + } + for ( j = i+1 ; j < width ; j++ ) { + for ( k = 0 ; k < height ; k++ ) { + ctrl[k*MAX_GRID_SIZE+j-1] = ctrl[k*MAX_GRID_SIZE+j]; + } + errorTable[0*MAX_GRID_SIZE+j-1] = errorTable[0*MAX_GRID_SIZE+j]; + } + width--; + } + + for ( i = 1 ; i < height-1 ; i++ ) { + if ( errorTable[1*MAX_GRID_SIZE+i] != 999 ) { + continue; + } + for ( j = i+1 ; j < height ; j++ ) { + for ( k = 0 ; k < width ; k++ ) { + ctrl[(j-1)*MAX_GRID_SIZE+k] = ctrl[j*MAX_GRID_SIZE+k]; + } + errorTable[1*MAX_GRID_SIZE+j-1] = errorTable[1*MAX_GRID_SIZE+j]; + } + height--; + } + +#if 1 + // flip for longest tristrips as an optimization + // the results should be visually identical with or + // without this step + if ( height > width ) { + Transpose( width, height, ctrl ); + InvertErrorTable( errorTable, width, height ); + t = width; + width = height; + height = t; + InvertCtrl( width, height, ctrl ); + } +#endif + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + // copy the results out to a grid + grid = (struct srfGridMesh_s *) Hunk_Alloc( (width * height - 1) * sizeof( drawVert_t ) + sizeof( *grid ) + width * 4 + height * 4, qtrue ); + + grid->widthLodError = (float*)(((char*)grid) + (width * height - 1) * + sizeof(drawVert_t) + sizeof(*grid)); + memcpy( grid->widthLodError, &errorTable[0*MAX_GRID_SIZE], width * 4 ); + + grid->heightLodError = (float*)(((char*)grid->widthLodError) + width * 4); + memcpy( grid->heightLodError, &errorTable[1*MAX_GRID_SIZE], height * 4 ); + + grid->width = width; + grid->height = height; + grid->surfaceType = SF_GRID; + ClearBounds( grid->meshBounds[0], grid->meshBounds[1] ); + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + vert = &grid->verts[j*width+i]; + *vert = ctrl[j*MAX_GRID_SIZE+i]; + AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] ); + } + } + + // compute local origin and bounds + VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin ); + VectorScale( grid->localOrigin, 0.5f, grid->localOrigin ); + VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec ); + grid->meshRadius = VectorLength( tmpVec ); + + VectorCopy( grid->localOrigin, grid->lodOrigin ); + grid->lodRadius = grid->meshRadius; + + return grid; +} + +#else // _XBOX + +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { + int i, j, k, l; + drawVert_t prev, next, mid; + float len, maxLen; + int dir; + int t; + MAC_STATIC drawVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; + float errorTable[2][MAX_GRID_SIZE]; + srfGridMesh_t *grid; + drawVert_t *vert; + vec3_t tmpVec; + + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + ctrl[j][i] = points[j*width+i]; + } + } + + for ( dir = 0 ; dir < 2 ; dir++ ) { + + for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) { + errorTable[dir][j] = 0; + } + + // horizontal subdivisions + for ( j = 0 ; j + 2 < width ; j += 2 ) { + // check subdivided midpoints against control points + maxLen = 0; + for ( i = 0 ; i < height ; i++ ) { + vec3_t midxyz; + vec3_t dir; + vec3_t projected; + float d; + + // calculate the point on the curve + for ( l = 0 ; l < 3 ; l++ ) { + midxyz[l] = (ctrl[i][j].xyz[l] + ctrl[i][j+1].xyz[l] * 2 + + ctrl[i][j+2].xyz[l] ) * 0.25; + } + + // see how far off the line it is + // using dist-from-line will not account for internal + // texture warping, but it gives a lot less polygons than + // dist-from-midpoint + VectorSubtract( midxyz, ctrl[i][j].xyz, midxyz ); + VectorSubtract( ctrl[i][j+2].xyz, ctrl[i][j].xyz, dir ); + VectorNormalize( dir ); + + d = DotProduct( midxyz, dir ); + VectorScale( dir, d, projected ); + VectorSubtract( midxyz, projected, midxyz); + len = VectorLength( midxyz ); + + if ( len > maxLen ) { + maxLen = len; + } + } + + // if all the points are on the lines, remove the entire columns + if ( maxLen < 0.1 ) { + errorTable[dir][j+1] = 999; + continue; + } + + // see if we want to insert subdivided columns + if ( width + 2 > MAX_GRID_SIZE ) { + errorTable[dir][j+1] = 1.0/maxLen; + continue; // can't subdivide any more + } + + if ( maxLen <= r_subdivisions->value ) { + errorTable[dir][j+1] = 1.0/maxLen; + continue; // didn't need subdivision + } + + errorTable[dir][j+2] = 1.0/maxLen; + + // insert two columns and replace the peak + width += 2; + for ( i = 0 ; i < height ; i++ ) { + LerpDrawVert( &ctrl[i][j], &ctrl[i][j+1], &prev ); + LerpDrawVert( &ctrl[i][j+1], &ctrl[i][j+2], &next ); + LerpDrawVert( &prev, &next, &mid ); + + for ( k = width - 1 ; k > j + 3 ; k-- ) { + ctrl[i][k] = ctrl[i][k-2]; + } + ctrl[i][j + 1] = prev; + ctrl[i][j + 2] = mid; + ctrl[i][j + 3] = next; + } + + // back up and recheck this set again, it may need more subdivision + j -= 2; + + } + + Transpose( width, height, ctrl ); + t = width; + width = height; + height = t; + } + + + // put all the aproximating points on the curve + PutPointsOnCurve( ctrl, width, height ); + + // cull out any rows or columns that are colinear + for ( i = 1 ; i < width-1 ; i++ ) { + if ( errorTable[0][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < width ; j++ ) { + for ( k = 0 ; k < height ; k++ ) { + ctrl[k][j-1] = ctrl[k][j]; + } + errorTable[0][j-1] = errorTable[0][j]; + } + width--; + } + + for ( i = 1 ; i < height-1 ; i++ ) { + if ( errorTable[1][i] != 999 ) { + continue; + } + for ( j = i+1 ; j < height ; j++ ) { + for ( k = 0 ; k < width ; k++ ) { + ctrl[j-1][k] = ctrl[j][k]; + } + errorTable[1][j-1] = errorTable[1][j]; + } + height--; + } + +#if 1 + // flip for longest tristrips as an optimization + // the results should be visually identical with or + // without this step + if ( height > width ) { + Transpose( width, height, ctrl ); + InvertErrorTable( errorTable, width, height ); + t = width; + width = height; + height = t; + InvertCtrl( width, height, ctrl ); + } +#endif + + // calculate normals + MakeMeshNormals( width, height, ctrl ); + + // copy the results out to a grid + grid = (struct srfGridMesh_s *) Hunk_Alloc( (width * height - 1) * sizeof( drawVert_t ) + sizeof( *grid ), qtrue ); + + grid->widthLodError = (float *) Hunk_Alloc( width * 4, qfalse ); + memcpy( grid->widthLodError, errorTable[0], width * 4 ); + + grid->heightLodError = (float *) Hunk_Alloc( height * 4, qfalse ); + memcpy( grid->heightLodError, errorTable[1], height * 4 ); + + grid->width = width; + grid->height = height; + grid->surfaceType = SF_GRID; + ClearBounds( grid->meshBounds[0], grid->meshBounds[1] ); + for ( i = 0 ; i < width ; i++ ) { + for ( j = 0 ; j < height ; j++ ) { + vert = &grid->verts[j*width+i]; + *vert = ctrl[j][i]; + AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] ); + } + } + + // compute local origin and bounds + VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin ); + VectorScale( grid->localOrigin, 0.5f, grid->localOrigin ); + VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec ); + grid->meshRadius = VectorLength( tmpVec ); + + VectorCopy( grid->localOrigin, grid->lodOrigin ); + grid->lodRadius = grid->meshRadius; + + return grid; +} +#endif // _XBOX diff --git a/code/renderer/tr_draw.cpp b/code/renderer/tr_draw.cpp new file mode 100644 index 0000000..63669f0 --- /dev/null +++ b/code/renderer/tr_draw.cpp @@ -0,0 +1,1155 @@ +// tr_draw.c +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + +#ifdef _XBOX +#include "../win32/glw_win_dx8.h" +#endif + + +/* +============= +RE_StretchRaw + +Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle. +Used for cinematics. +============= +*/ + +// param 'bDirty' should be true 99% of the time +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int iClient, qboolean bDirty ) +{ +#ifdef _XBOX + assert( 0 ); + return; +#else + + R_SyncRenderThread(); + +//=========== + // Q3Final added this: + // we definately want to sync every frame for the cinematics + //qglFinish(); + +#ifdef TIMEBIND + int start, end; + start = end = 0; // only to stop compiler whining, don't need to be initialised +#endif + // make sure rows and cols are powers of 2 + if ( (cols&(cols-1)) || (rows&(rows-1)) ) + { + Com_Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows); + } + + GL_Bind( tr.scratchImage[iClient] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture... + // + if ( cols != tr.scratchImage[iClient]->width || rows != tr.scratchImage[iClient]->height ) + { + tr.scratchImage[iClient]->width = cols; + tr.scratchImage[iClient]->height = rows; +#ifdef TIMEBIND + if ( r_ignore->integer ) + { + start = Sys_Milliseconds(); + } +#endif + + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + +#ifdef TIMEBIND + if ( r_ignore->integer ) + { + end = Sys_Milliseconds(); + VID_Printf( PRINT_ALL, "qglTexImage2D %i, %i: %i msec\n", cols, rows, end - start ); + } +#endif + } + else + { + if (bDirty) // FIXME: some TA addition or other, not sure why, yet. Should probably be true 99% of the time? + { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + + #ifdef TIMEBIND + if ( r_ignore->integer ) + { + start = Sys_Milliseconds(); + } + #endif + + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + + #ifdef TIMEBIND + if ( r_ignore->integer ) + { + end = Sys_Milliseconds(); + VID_Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start ); + } + #endif + } + } + + + extern void RB_SetGL2D (void); + if (!backEnd.projection2D) + { + RB_SetGL2D(); + } + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + +#ifdef _XBOX + qglBeginEXT (GL_TRIANGLE_STRIP, 4, 0, 0, 4, 0);//, 0, 0); + qglTexCoord2f ( 0.5 / cols, 0.5 / rows ); + qglVertex2f (x, y); + qglTexCoord2f ( ( cols - 0.5 ) / cols , 0.5 / rows ); + qglVertex2f (x+w, y); + qglTexCoord2f ( 0.5 / cols, ( rows - 0.5 ) / rows ); + qglVertex2f (x, y+h); + qglTexCoord2f ( ( cols - 0.5 ) / cols, ( rows - 0.5 ) / rows ); + qglVertex2f (x+w, y+h); + qglEnd (); +#else + qglBegin (GL_QUADS); + qglTexCoord2f ( 0.5 / cols, 0.5 / rows ); + qglVertex2f (x, y); + qglTexCoord2f ( ( cols - 0.5 ) / cols , 0.5 / rows ); + qglVertex2f (x+w, y); + qglTexCoord2f ( ( cols - 0.5 ) / cols, ( rows - 0.5 ) / rows ); + qglVertex2f (x+w, y+h); + qglTexCoord2f ( 0.5 / cols, ( rows - 0.5 ) / rows ); + qglVertex2f (x, y+h); + qglEnd (); +#endif + +#endif // _XBOX +} + + + +void RE_UploadCinematic (int cols, int rows, const byte *data, int client, qboolean dirty) { + + GL_Bind( tr.scratchImage[client] ); + + // if the scratchImage isn't in the format we want, specify it as a new texture + if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { + tr.scratchImage[client]->width = cols; + tr.scratchImage[client]->height = rows; +#ifdef _XBOX + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, cols, rows, 0, GL_LIN_RGBA, GL_UNSIGNED_BYTE, data ); +#else + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); +#endif + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + } else { + if (dirty) { + // otherwise, just subimage upload it so that drivers can tell we are going to be changing + // it and don't try and do a texture compression + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } +} + + + +#if 0 +void RE_GetScreenShot(byte *data, int w, int h) +{ + byte *buffer; + int offset; + int x, y; + int xc, yc; + int xstep, ystep; + int count = 0; + + buffer = (byte *)R_Malloc(glConfig.vidWidth * glConfig.vidHeight * 3); + if(!buffer) + { + return; + } + qglReadPixels (0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGB, GL_UNSIGNED_BYTE, buffer ); + + xstep = (glConfig.vidWidth << 16) / w; + ystep = (glConfig.vidHeight << 16) / h; + yc = 0; + for(y = 0; y < h; y++, yc += ystep) + { + xc = 0; + for(x = 0; x < w; x++, xc += xstep) + { + offset = ((glConfig.vidWidth * (yc >> 16)) + (xc >> 16)) * 3; + *data++ = buffer[offset++]; + *data++ = buffer[offset++]; + *data++ = buffer[offset++]; + *data++ = 0xff; + count++; + } + } + assert(count == w * h); + R_Free(buffer); +} + +#else + +void RE_GetScreenShot(byte *buffer, int w, int h) +{ +#ifndef _XBOX + byte *source; + byte *src, *dst; + int x, y; + int r, g, b; + float xScale, yScale; + int xx, yy; + + qglFinish(); // try and fix broken Radeon cards (7500 & 8500) that don't read screen pixels properly + + source = (byte *)Z_Malloc(glConfig.vidWidth * glConfig.vidHeight * 3, TAG_TEMP_WORKSPACE, qfalse); + if(!source) + { + return; + } + qglReadPixels (0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGB, GL_UNSIGNED_BYTE, source ); + + assert (w == h); + int count = 0; + // resample from source + xScale = glConfig.vidWidth / (4.0 * w); + yScale = glConfig.vidHeight / (3.0 * w); + for ( y = 0 ; y < w ; y++ ) { + for ( x = 0 ; x < w ; x++ ) { + r = g = b = 0; + for ( yy = 0 ; yy < 3 ; yy++ ) { + for ( xx = 0 ; xx < 4 ; xx++ ) { + src = source + 3 * ( glConfig.vidWidth * (int)( (y*3+yy)*yScale ) + (int)( (x*4+xx)*xScale ) ); + r += src[0]; + g += src[1]; + b += src[2]; + } + } + dst = buffer + 4 * ( y * w + x ); + dst[0] = r / 12; + dst[1] = g / 12; + dst[2] = b / 12; + count++; + } + } + + assert(count == w * h); + Z_Free(source); +#endif +} + +#endif + + + + +// this is just a chunk of code from RE_TempRawImage_ReadFromFile() below, subroutinised so I can call it +// from the screen dissolve code as well... +// +static byte *RE_ReSample(byte *pbLoadedPic, int iLoadedWidth, int iLoadedHeight, + byte *pbReSampleBuffer, int *piWidth, int *piHeight + ) +{ + byte *pbReturn = NULL; + + // if not resampling, just return some values and return... + // + if ( pbReSampleBuffer == NULL || (iLoadedWidth == *piWidth && iLoadedHeight == *piHeight) ) + { + // if not resampling, we're done, just return the loaded size... + // + *piWidth = iLoadedWidth; + *piHeight= iLoadedHeight; + pbReturn = pbLoadedPic; + } + else + { + // resample from pbLoadedPic to pbReSampledBuffer... + // + float fXStep = (float)iLoadedWidth / (float)*piWidth; + float fYStep = (float)iLoadedHeight/ (float)*piHeight; + int iTotPixelsPerDownSample = (int)ceil(fXStep) * (int)ceil(fYStep); + + int r,g,b; + + byte *pbDst = pbReSampleBuffer; + + for ( int y=0; y<*piHeight; y++ ) + { + for ( int x=0; x<*piWidth; x++ ) + { + r=g=b=0; + + for ( float yy = (float)y*fYStep; yy < (float)(y+1)*fYStep ; yy+=1 ) + { + for ( float xx = (float)x*fXStep; xx < (float)(x+1)*fXStep ; xx+=1 ) + { + byte *pbSrc = pbLoadedPic + 4 * ( ((int)yy * iLoadedWidth) + (int)xx ); + + assert(pbSrc < pbLoadedPic + (iLoadedWidth * iLoadedHeight * 4) ); + + r += pbSrc[0]; + g += pbSrc[1]; + b += pbSrc[2]; + } + } + + assert(pbDst < pbReSampleBuffer + (*piWidth * *piHeight * 4)); + + pbDst[0] = r / iTotPixelsPerDownSample; + pbDst[1] = g / iTotPixelsPerDownSample; + pbDst[2] = b / iTotPixelsPerDownSample; + pbDst[3] = 255; + pbDst += 4; + } + } + + // set return value... + // + pbReturn = pbReSampleBuffer; + } + + return pbReturn; +} + + +// this is so the server (or anyone else) can get access to raw pixels if they really need to, +// currently it's only used by the server so that savegames can embed a graphic in the auto-save files +// (which can't do a screenshot since they're saved out before the level is drawn). +// +// by default, the pic will be returned as the original dims, but if pbReSampleBuffer != NULL then it's assumed to +// be a big enough buffer to hold the resampled image, which also means that the width and height params are read as +// inputs (as well as still being inherently outputs) and the pic is scaled to that size, and to that buffer. +// +// the return value is either NULL, or a pointer to the pixels to use (which may be either the pbReSampleBuffer param, +// or the local ptr below). +// +// In either case, you MUST call the free-up function afterwards ( RE_TempRawImage_CleanUp() ) to get rid of any temp +// memory after you've finished with the pic. +// +// Note: ALWAYS use the return value if != NULL, even if you passed in a declared resample buffer. This is because the +// resample will get skipped if the values you want are the same size as the pic that it loaded, so it'll return a +// different buffer. +// +// the vertflip param is used for those functions that expect things in OpenGL's upside-down pixel-read format (sigh) +// +// (not brilliantly fast, but it's only used for weird stuff anyway) +// +byte* pbLoadedPic = NULL; + +#ifndef _XBOX +byte* RE_TempRawImage_ReadFromFile(const char *psLocalFilename, int *piWidth, int *piHeight, byte *pbReSampleBuffer, qboolean qbVertFlip) +{ + RE_TempRawImage_CleanUp(); // jic + + byte *pbReturn = NULL; + + if (psLocalFilename && piWidth && piHeight) + { + int iLoadedWidth, iLoadedHeight; + + GLenum format; // VVFIXME + R_LoadImage( psLocalFilename, &pbLoadedPic, &iLoadedWidth, &iLoadedHeight, &format); + if ( pbLoadedPic ) + { + pbReturn = RE_ReSample( pbLoadedPic, iLoadedWidth, iLoadedHeight, + pbReSampleBuffer, piWidth, piHeight); + } + } + + if (pbReturn && qbVertFlip) + { + unsigned long *pSrcLine = (unsigned long *) pbReturn; + unsigned long *pDstLine = (unsigned long *) pbReturn + (*piHeight * *piWidth ); // *4 done by compiler (longs) + pDstLine-= *piWidth; // point at start of last line, not first after buffer + + for (int iLineCount=0; iLineCount<*piHeight/2; iLineCount++) + { + for (int x=0; x<*piWidth; x++) + { + unsigned long l = pSrcLine[x]; + pSrcLine[x] = pDstLine[x]; + pDstLine[x] = l; + } + pSrcLine += *piWidth; + pDstLine -= *piWidth; + } + } + + return pbReturn; +} +#endif // _XBOX + +void RE_TempRawImage_CleanUp(void) +{ + if ( pbLoadedPic ) + { + Z_Free( pbLoadedPic ); + pbLoadedPic = NULL; + } +} + + + +typedef enum +{ + eDISSOLVE_RT_TO_LT = 0, + eDISSOLVE_LT_TO_RT, + eDISSOLVE_TP_TO_BT, + eDISSOLVE_BT_TO_TP, + eDISSOLVE_CIRCULAR_OUT, // new image comes out from centre + // + eDISSOLVE_RAND_LIMIT, // label only, not valid to select + // + // any others... + // + eDISSOLVE_CIRCULAR_IN, // new image comes in from edges + // + eDISSOLVE_NUMBEROF + +} Dissolve_e; + +typedef struct +{ + int iWidth; + int iHeight; + int iUploadWidth; + int iUploadHeight; + int iScratchPadNumber; + image_t *pImage; // old image screen + image_t *pDissolve; // fuzzy thing + image_t *pBlack; // small black image for clearing + int iStartTime; // 0 = not processing + Dissolve_e eDissolveType; + qboolean bTouchNeeded; + +} Dissolve_t; + +static int PowerOf2(int iArg) +{ + if ( (iArg & (iArg-1)) != 0) + { + int iShift=0; + while (iArg) + { + iArg>>=1; + iShift++; + } + + iArg = 1<width, fV0 / (float)pImage->height ); + qglTexCoord2f( 0,0 ); + qglVertex2f( fX0, fY0 ); + + // TR... + // +// qglTexCoord2f( fU1 / (float)pImage->width, fV1 / (float)pImage->height ); + qglTexCoord2f( 1,0 ); + qglVertex2f( fX1, fY1 ); + + // BR... + // +// qglTexCoord2f( fU2 / (float)pImage->width, fV2 / (float)pImage->height ); + qglTexCoord2f( 1,1 ); + qglVertex2f( fX2, fY2); + + // BL... + // +// qglTexCoord2f( fU3 / (float)pImage->width, fV3 / (float)pImage->height ); + qglTexCoord2f( 0,1 ); + qglVertex2f( fX3, fY3); + } + qglEnd (); +} + +static void RE_KillDissolve(void) +{ + Dissolve.iStartTime = 0; + +#ifndef _XBOX + // Xbox reuses tr.screenImage for the dissolve, so killing here is not necessary + if (Dissolve.pImage) + { + R_Images_DeleteImage( Dissolve.pImage ); + Dissolve.pImage = NULL; + } +#endif +} +// Draw the dissolve pic to the screen, over the top of what's already been rendered. +// +// return = qtrue while still processing, for those interested... +// +#define iSAFETY_SPRITE_OVERLAP 2 // #pixels to overlap blit region by, in case some drivers leave onscreen seams +qboolean RE_ProcessDissolve(void) +{ + if (Dissolve.iStartTime) + { + if (Dissolve.bTouchNeeded) + { + // Stuff to avoid music stutter... + // + // The problem is, that if I call RE_InitDissolve() then call RestartMusic, then by the time the music + // has loaded in if it took longer than one second the dissolve would think that it had finished, + // even if it had never actually drawn up. However, if I called RE_InitDissolve() AFTER the music had + // restarted, then the music would stutter on slow video cards or CPUs while I did the binding/resampling. + // + // This way, I restart the millisecond counter the first time we actually get as far as rendering, which + // should let things work properly... + // + Dissolve.bTouchNeeded = qfalse; + Dissolve.iStartTime = Sys_Milliseconds(); + } + + int iDissolvePercentage = ((Sys_Milliseconds() - Dissolve.iStartTime)*100) / (1000.0f * fDISSOLVE_SECONDS); + +// VID_Printf(PRINT_ALL,"iDissolvePercentage %d\n",iDissolvePercentage); + + if (iDissolvePercentage <= 100) + { + extern void RB_SetGL2D (void); + RB_SetGL2D(); + +// GLdouble glD; +// qglGetDoublev(GL_DEPTH_CLEAR_VALUE,&glD); +// qglClearColor(0,0,0,1); + qglClearDepth(1.0f); + qglClear( GL_DEPTH_BUFFER_BIT ); + + float fXScaleFactor; +#ifdef _XBOX + if(glw_state->isWidescreen) + { + Dissolve.iWidth = 720; + fXScaleFactor = 720.0f / (float)Dissolve.iWidth; + } + else +#endif + fXScaleFactor = (float)SCREEN_WIDTH / (float)Dissolve.iWidth; + float fYScaleFactor = (float)SCREEN_HEIGHT/ (float)Dissolve.iHeight; + float x0,y0, x1,y1, x2,y2, x3,y3; + + switch (Dissolve.eDissolveType) + { + case eDISSOLVE_RT_TO_LT: + { + float fXboundary = (float) Dissolve.iWidth - (((float)(Dissolve.iWidth+Dissolve.pDissolve->width)*(float)iDissolvePercentage)/100.0f); + + // blit the fuzzy-dissolve sprite... + // + x0 = fXScaleFactor * fXboundary; + y0 = 0.0f; + x1 = fXScaleFactor * (fXboundary + Dissolve.pDissolve->width); + y1 = 0.0f; + x2 = x1; + y2 = fYScaleFactor * Dissolve.iHeight; + x3 = x0; + y3 = y2; + + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); + + // blit a blank thing over the area the old screen is to be displayed on to enable screen-writing... + // (to the left of fXboundary) + // + x0 = 0.0f; + y0 = 0.0f; + x1 = fXScaleFactor * (fXboundary + iSAFETY_SPRITE_OVERLAP); + y1 = 0.0f; + x2 = x1; + y2 = fYScaleFactor * Dissolve.iHeight; + x3 = x0; + y3 = y2; + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE); + } + break; + + case eDISSOLVE_LT_TO_RT: + { + float fXboundary = (((float)(Dissolve.iWidth+(2*Dissolve.pDissolve->width))*(float)iDissolvePercentage)/100.0f) - Dissolve.pDissolve->width; + + // blit the fuzzy-dissolve sprite... + // + x0 = fXScaleFactor * (fXboundary + Dissolve.pDissolve->width); + y0 = 0.0f; + x1 = fXScaleFactor * fXboundary; + y1 = 0.0f; + x2 = x1; + y2 = fYScaleFactor * Dissolve.iHeight; + x3 = x0; + y3 = y2; + + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); + + // blit a blank thing over the area the old screen is to be displayed on to enable screen-writing... + // (to the right of fXboundary) + // + x0 = fXScaleFactor * (( fXboundary + Dissolve.pDissolve->width) - iSAFETY_SPRITE_OVERLAP); + y0 = 0.0f; + x1 = fXScaleFactor * Dissolve.iWidth; + y0 = 0.0f; + x2 = x1; + y2 = fYScaleFactor * Dissolve.iHeight; + x3 = x0; + y3 = y2; + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE); + } + break; + + case eDISSOLVE_TP_TO_BT: + { + float fYboundary = (((float)(Dissolve.iHeight+(2*Dissolve.pDissolve->width))*(float)iDissolvePercentage)/100.0f) - Dissolve.pDissolve->width; + + // blit the fuzzy-dissolve sprite... + // + x0 = 0.0f; + y0 = fYScaleFactor * (fYboundary + Dissolve.pDissolve->width); + x1 = x0; + y1 = fYScaleFactor * fYboundary; + x2 = fXScaleFactor * Dissolve.iWidth; + y2 = y1; + x3 = x2; + y3 = y0; + + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); + + // blit a blank thing over the area the old screen is to be displayed on to enable screen-writing... + // (underneath fYboundary) + // + x0 = 0.0f; + y0 = fYScaleFactor * ( (fYboundary + Dissolve.pDissolve->width) - iSAFETY_SPRITE_OVERLAP); + x1 = fXScaleFactor * Dissolve.iWidth; + y1 = y0; + x2 = x1; + y2 = fYScaleFactor * Dissolve.iHeight; + x3 = x0; + y3 = y2; + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE); + } + break; + + case eDISSOLVE_BT_TO_TP: + { + float fYboundary = Dissolve.iHeight - (((float)(Dissolve.iHeight+Dissolve.pDissolve->width)*(float)iDissolvePercentage)/100.0f); + + // blit the fuzzy-dissolve sprite... + // + x0 = 0.0f; + y0 = fYScaleFactor * fYboundary; + x1 = x0; + y1 = fYScaleFactor * (fYboundary + Dissolve.pDissolve->width); + x2 = fXScaleFactor * Dissolve.iWidth; + y2 = y1; + x3 = x2; + y3 = y0; + + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); + + // blit a blank thing over the area the old screen is to be displayed on to enable screen-writing... + // (above fYboundary) + // + x0 = 0.0f; + y0 = 0.0f; + x1 = fXScaleFactor * Dissolve.iWidth; + y1 = y0; + x2 = x1; + y2 = fYScaleFactor * (fYboundary + iSAFETY_SPRITE_OVERLAP); + x3 = x0; + y3 = y2; + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE); + } + break; + + case eDISSOLVE_CIRCULAR_IN: + { + float fDiagZoom = ( ((float)Dissolve.iWidth*0.8) * (100-iDissolvePercentage))/100.0f; + + // + // blit circular graphic... + // + x0 = fXScaleFactor * ((Dissolve.iWidth/2) - fDiagZoom); + y0 = fYScaleFactor * ((Dissolve.iHeight/2)- fDiagZoom); + x1 = fXScaleFactor * ((Dissolve.iWidth/2) + fDiagZoom); + y1 = y0; + x2 = x1; + y2 = fYScaleFactor * ((Dissolve.iHeight/2)+ fDiagZoom); + x3 = x0; + y3 = y2; + + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); + } + break; + + case eDISSOLVE_CIRCULAR_OUT: + { + float fDiagZoom = ( ((float)Dissolve.iWidth*0.8) * iDissolvePercentage)/100.0f; + + // + // blit circular graphic... + // + x0 = fXScaleFactor * ((Dissolve.iWidth/2) - fDiagZoom); + y0 = fYScaleFactor * ((Dissolve.iHeight/2)- fDiagZoom); + x1 = fXScaleFactor * ((Dissolve.iWidth/2) + fDiagZoom); + y1 = y0; + x2 = x1; + y2 = fYScaleFactor * ((Dissolve.iHeight/2)+ fDiagZoom); + x3 = x0; + y3 = y2; + + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pDissolve, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE | GLS_ATEST_LT_80); + // now blit the 4 black squares around it to mask off the rest of the screen... + // + // LHS, top to bottom... + // + RE_Blit(0,0, // x0,y0 + x0+iSAFETY_SPRITE_OVERLAP,0, // x1,y1 + x0+iSAFETY_SPRITE_OVERLAP,(fYScaleFactor * Dissolve.iHeight),// x2,y2 + 0,(fYScaleFactor * Dissolve.iHeight), // x3,y3, + Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE + ); + + // RHS top to bottom... + // + RE_Blit(x1-iSAFETY_SPRITE_OVERLAP,0, // x0,y0 + (fXScaleFactor * Dissolve.iWidth),0, // x1,y1 + (fXScaleFactor * Dissolve.iWidth),(fYScaleFactor * Dissolve.iHeight),// x2,y2 + x1-iSAFETY_SPRITE_OVERLAP,(fYScaleFactor * Dissolve.iHeight), // x3,y3, + Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE + ); + + // top... + // + RE_Blit(x0-iSAFETY_SPRITE_OVERLAP,0, // x0,y0 + x1+iSAFETY_SPRITE_OVERLAP,0, // x1,y1 + x1+iSAFETY_SPRITE_OVERLAP,y0 + iSAFETY_SPRITE_OVERLAP, // x2,y2 + x0-iSAFETY_SPRITE_OVERLAP,y0 + iSAFETY_SPRITE_OVERLAP, // x3,y3 + Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE + ); + + // bottom... + // + RE_Blit(x0-iSAFETY_SPRITE_OVERLAP,y3-iSAFETY_SPRITE_OVERLAP, // x0,y0 + x1+iSAFETY_SPRITE_OVERLAP,y2-iSAFETY_SPRITE_OVERLAP, // x1,y1 + x1+iSAFETY_SPRITE_OVERLAP,(fYScaleFactor * Dissolve.iHeight), // x2,y2 + x0-iSAFETY_SPRITE_OVERLAP,(fYScaleFactor * Dissolve.iHeight), // x3,y3 + Dissolve.pBlack, GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ZERO | GLS_DSTBLEND_ONE + ); + } + break; + + default: + { + assert(0); + iDissolvePercentage = 101; // force a dissolve-kill + break; + } + } + + // re-check in case we hit the default case above... + // + if (iDissolvePercentage <= 100) + { + // still dissolving, so now (finally), blit old image over top... + // + x0 = 0.0f; + y0 = 0.0f; +#ifdef _XBOX + x1 = 640; + if(glw_state->isWidescreen) + x1 = 720; +#else + x1 = fXScaleFactor * Dissolve.pImage->width; +#endif + y1 = y0; + x2 = x1; +#ifdef _XBOX + y2 = 480; +#else + y2 = fYScaleFactor * Dissolve.pImage->height; +#endif + x3 = x0; + y3 = y2; + + RE_Blit(x0,y0,x1,y1,x2,y2,x3,y3, Dissolve.pImage,GLS_DEPTHFUNC_EQUAL); + } + } + + if (iDissolvePercentage > 100) + { + RE_KillDissolve(); + } + } + + return qfalse; +} + +#ifdef _XBOX + +/********** +RE_GetCompressedBackbuffer +Creates a 256x256 DXT1 texture that contains the backbuffer +**********/ +static void RE_GetCompressedBackbuffer() +{ + byte* data = (byte*)Z_Malloc(256*256*4,TAG_TEMP_WORKSPACE,qtrue); + + Dissolve.pImage = R_CreateImage( "*DissolveImage", // const char *name + data, // const byte *pic + 256, // int width + 256, // int height + GL_RGBA, //GL_COMPRESSED_RGB_S3TC_DXT1_EXT, + qfalse, // qboolean mipmap + qfalse, // qboolean allowPicmip + GL_CLAMP // int glWrapClampMode + ); + Z_Free(data); + + GL_Bind(Dissolve.pImage); + + /*if(glw_state->isWidescreen) + qglCopyBackBufferToTexEXT(256.0f, 256.0f, 0.0f, 0.0f, 720.0f, 480.0f); + else*/ + qglCopyBackBufferToTexEXT(256.0f, 256.0f, 2.0f, 2.0f, 640.0f, 480.0f); +} +#endif + +// return = qtrue(success) else fail, for those interested... +// +qboolean RE_InitDissolve(qboolean bForceCircularExtroWipe) +{ + R_SyncRenderThread(); + +// VID_Printf( PRINT_ALL, "RE_InitDissolve()\n"); + qboolean bReturn = qfalse; + + if (//Dissolve.iStartTime == 0 // no point in interruping an existing one + //&& + tr.registered == qtrue // ... stops it crashing during first cinematic before the menus... :-) + ) + { + RE_KillDissolve(); // kill any that are already running + +#ifdef _XBOX // Things are much simpler on Xbox, and use far less RAM + + if (1) + { // Silly if(1) to match up with control flow below, as the #ifdef ends inside the block + + GL_Bind(tr.screenImage); + qglCopyBackBufferToTexEXT(512.0f, 256.0f, 1.0f, 1.0f, 640.0f, 480.0f); + Dissolve.pImage = tr.screenImage; + +// RE_GetCompressedBackbuffer(); + Dissolve.iWidth = glConfig.vidWidth; + Dissolve.iHeight = glConfig.vidHeight; + +#else // _XBOX + + int iPow2VidWidth = PowerOf2( glConfig.vidWidth ); + int iPow2VidHeight = PowerOf2( glConfig.vidHeight); + + int iBufferBytes = iPow2VidWidth * iPow2VidHeight * 4; + byte *pBuffer = (byte *) Z_Malloc( iBufferBytes, TAG_TEMP_WORKSPACE, qfalse); + if (pBuffer) + { + // read current screen image... (GL_RGBA should work even on 3DFX in that the RGB parts will be valid at least) + // + qglReadPixels (0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_UNSIGNED_BYTE, pBuffer ); + // + // now expand the pic over the top of itself so that it has a stride value of {PowerOf2(glConfig.vidWidth)} + // (for GL power-of-2 rules) + // + byte *pbSrc = &pBuffer[ glConfig.vidWidth * glConfig.vidHeight * 4]; + byte *pbDst = &pBuffer[ iPow2VidWidth * glConfig.vidHeight * 4]; + // + // ( clear to end, since we've got pbDst nicely setup here) + // + int iClearBytes = &pBuffer[iBufferBytes] - pbDst; + memset(pbDst, 0, iClearBytes); + // + // work out copy/stride vals... + // + iClearBytes = ( iPow2VidWidth - glConfig.vidWidth ) * 4; + int iCopyBytes = glConfig.vidWidth * 4; + // + // do it... + // + for (int y = 0; y < glConfig.vidHeight; y++) + { + pbDst -= iClearBytes; + memset(pbDst,0,iClearBytes); + pbDst -= iCopyBytes; + pbSrc -= iCopyBytes; + memmove(pbDst, pbSrc, iCopyBytes); + } + // + // ok, now we've got the screen image in the top left of the power-of-2 texture square, + // but of course the damn thing's upside down (thanks, GL), so invert it, but only within + // the picture pixels, NOT the upload texture as a whole... + // + byte *pbSwapLineBuffer = (byte *)Z_Malloc( iCopyBytes, TAG_TEMP_WORKSPACE, qfalse); + pbSrc = &pBuffer[0]; + pbDst = &pBuffer[(glConfig.vidHeight-1) * iPow2VidWidth * 4]; + for (y = 0; y < glConfig.vidHeight/2; y++) + { + memcpy(pbSwapLineBuffer, pbDst, iCopyBytes); + memcpy(pbDst, pbSrc, iCopyBytes); + memcpy(pbSrc, pbSwapLineBuffer, iCopyBytes); + pbDst -= iPow2VidWidth*4; + pbSrc += iPow2VidWidth*4; + } + Z_Free(pbSwapLineBuffer); + + // + // Now, in case of busted drivers, 3DFX cards, etc etc we stomp the alphas to 255... + // + byte *pPix = pBuffer; + for (int i=0; i iTexSize) { + Dissolve.iUploadWidth = iTexSize; + } + + if (Dissolve.iUploadHeight > iTexSize) { + Dissolve.iUploadHeight = iTexSize; + } + + // alloc resample buffer... (note slight optimisation to avoid spurious alloc) + // + byte *pbReSampleBuffer = ( iPow2VidWidth == Dissolve.iUploadWidth && + iPow2VidHeight == Dissolve.iUploadHeight + )? + NULL : + (byte*) Z_Malloc( iPow2VidWidth * iPow2VidHeight * 4, TAG_TEMP_WORKSPACE, qfalse); + + // re-sample screen... + // + byte *pbScreenSprite = RE_ReSample( pBuffer, // byte *pbLoadedPic + iPow2VidWidth, // int iLoadedWidth + iPow2VidHeight, // int iLoadedHeight + // + pbReSampleBuffer, // byte *pbReSampleBuffer + &Dissolve.iUploadWidth, // int *piWidth + &Dissolve.iUploadHeight // int *piHeight + ); + + Dissolve.pImage = R_CreateImage("*DissolveImage", // const char *name + pbScreenSprite, // const byte *pic + Dissolve.iUploadWidth, // int width + Dissolve.iUploadHeight, // int height + GL_RGBA, + qfalse, // qboolean mipmap + qfalse, // qboolean allowPicmip + qfalse, // qboolean allowTC + GL_CLAMP // int glWrapClampMode + ); + +#endif // _XBOX + + static byte bBlack[8*8*4]={0}; + for (int j=0; j<8*8*4; j+=4) // itu? + bBlack[j+3]=255; // + + Dissolve.pBlack = R_CreateImage( "*DissolveBlack", // const char *name + bBlack, // const byte *pic + 8, // int width + 8, // int height + GL_RGBA, + qfalse, // qboolean mipmap + qfalse, // qboolean allowPicmip +#ifndef _XBOX + qfalse, // qboolean allowTC +#endif // _XBOX + GL_CLAMP // int glWrapClampMode + ); + +#ifndef _XBOX + if (pbReSampleBuffer) + { + Z_Free(pbReSampleBuffer); + } + Z_Free(pBuffer); +#endif + + // pick dissolve type... + // +#if 0 + // cycles through every dissolve type, for testing... + // + static Dissolve_e eDissolve = (Dissolve_e) 0; + Dissolve.eDissolveType = eDissolve; + eDissolve = (Dissolve_e) (eDissolve+1); + if (eDissolve == eDISSOLVE_RAND_LIMIT) + eDissolve = (Dissolve_e) (eDissolve+1); + if (eDissolve >= eDISSOLVE_NUMBEROF) + eDissolve = (Dissolve_e) 0; +#else + // final (& random) version... + // + Dissolve.eDissolveType = (Dissolve_e) Q_irand( 0, eDISSOLVE_RAND_LIMIT-1); +#endif + + if (bForceCircularExtroWipe) + { + Dissolve.eDissolveType = eDISSOLVE_CIRCULAR_IN; + } + + // ... and load appropriate graphics... + // + + // special tweak, although this code is normally called just before client spawns into world (and + // is therefore pretty much immune to precache issues) I also need to make sure that the inverse + // iris graphic is loaded so for the special case of doing a circular wipe at the end of the last + // level doesn't stall on loading the image. So I'll load it here anyway - to prime the image - + // then allow the random wiper to overwrite the ptr if needed. This way the end of level call + // will be instant. Downside: every level has one extra 256x256 texture. +#ifndef _XBOX // Trying to decipher these comments - looks like no problem taking this out. I want the RAM. + { + Dissolve.pDissolve = R_FindImageFile( "gfx/2d/iris_mono_rev", // const char *name + qfalse, // qboolean mipmap + qfalse, // qboolean allowPicmip + qfalse, // qboolean allowTC + GL_CLAMP // int glWrapClampMode + ); + } +#endif + + extern cvar_t *com_buildScript; + if (com_buildScript->integer) + { + // register any/all of the possible CASE statements below... + // + Dissolve.pDissolve = R_FindImageFile( "gfx/2d/iris_mono", // const char *name + qfalse, // qboolean mipmap + qfalse, // qboolean allowPicmip + qfalse, // qboolean allowTC + GL_CLAMP // int glWrapClampMode + ); + Dissolve.pDissolve = R_FindImageFile( "textures/common/dissolve", // const char *name + qfalse, // qboolean mipmap + qfalse, // qboolean allowPicmip + qfalse, // qboolean allowTC + GL_REPEAT // int glWrapClampMode + ); + } + + switch (Dissolve.eDissolveType) + { + case eDISSOLVE_CIRCULAR_IN: + { + Dissolve.pDissolve = R_FindImageFile( "gfx/2d/iris_mono_rev", // const char *name + qfalse, // qboolean mipmap + qfalse, // qboolean allowPicmip + qfalse, // qboolean allowTC + GL_CLAMP // int glWrapClampMode + ); + } + break; + + case eDISSOLVE_CIRCULAR_OUT: + { + Dissolve.pDissolve = R_FindImageFile( "gfx/2d/iris_mono", // const char *name + qfalse, // qboolean mipmap + qfalse, // qboolean allowPicmip + qfalse, // qboolean allowTC + GL_CLAMP // int glWrapClampMode + ); + } + break; + + default: + { + Dissolve.pDissolve = R_FindImageFile( "textures/common/dissolve", // const char *name + qfalse, // qboolean mipmap + qfalse, // qboolean allowPicmip + qfalse, // qboolean allowTC + GL_REPEAT // int glWrapClampMode + ); + } + break; + } + + // all good?... + // + if (Dissolve.pDissolve) // test if image was found, if not, don't do dissolves + { + Dissolve.iStartTime = Sys_Milliseconds(); // gets overwritten first time, but MUST be set to NZ + Dissolve.bTouchNeeded = qtrue; + bReturn = qtrue; + } + else + { + RE_KillDissolve(); + } + } + } + + return bReturn; +} + diff --git a/code/renderer/tr_flares.cpp b/code/renderer/tr_flares.cpp new file mode 100644 index 0000000..2a4126f --- /dev/null +++ b/code/renderer/tr_flares.cpp @@ -0,0 +1,427 @@ +// tr_flares.c + +#include "tr_local.h" + +/* +============================================================================= + +LIGHT FLARES + +A light flare is an effect that takes place inside the eye when bright light +sources are visible. The size of the flare reletive to the screen is nearly +constant, irrespective of distance, but the intensity should be proportional to the +projected area of the light source. + +A surface that has been flagged as having a light flare will calculate the depth +buffer value that it's midpoint should have when the surface is added. + +After all opaque surfaces have been rendered, the depth buffer is read back for +each flare in view. If the point has not been obscured by a closer surface, the +flare should be drawn. + +Surfaces that have a repeated texture should never be flagged as flaring, because +there will only be a single flare added at the midpoint of the polygon. + +To prevent abrupt popping, the intensity of the flare is interpolated up and +down as it changes visibility. This involves scene to scene state, unlike almost +all other aspects of the renderer, and is complicated by the fact that a single +frame may have multiple scenes. + +RB_RenderFlares() will be called once per view (twice in a mirrored scene, potentially +up to five or more times in a frame with 3D status bar icons). + +============================================================================= +*/ + + +// flare states maintain visibility over multiple frames for fading +// layers: view, mirror, menu +typedef struct flare_s { + struct flare_s *next; // for active chain + + int addedFrame; + + qboolean inPortal; // true if in a portal view of the scene + int frameSceneNum; + void *surface; + int fogNum; + + int fadeTime; + + qboolean visible; // state of last test + float drawIntensity; // may be non 0 even if !visible due to fading + float lightScale; + int windowX, windowY; + float eyeZ; + + vec3_t color; +} flare_t; + +#define MAX_FLARES 128 + +flare_t r_flareStructs[MAX_FLARES]; +flare_t *r_activeFlares, *r_inactiveFlares; + +/* +================== +R_ClearFlares +================== +*/ +void R_ClearFlares( void ) { + int i; + + memset( r_flareStructs, 0, sizeof( r_flareStructs ) ); + r_activeFlares = NULL; + r_inactiveFlares = NULL; + + for ( i = 0 ; i < MAX_FLARES ; i++ ) { + r_flareStructs[i].next = r_inactiveFlares; + r_inactiveFlares = &r_flareStructs[i]; + } +} + + +/* +================== +RB_AddFlare + +This is called at surface tesselation time +================== +*/ +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal, float lightScale) { + int i; + flare_t *f, *oldest; + vec3_t local; + float d; + vec4_t eye, clip, normalized, window; + + backEnd.pc.c_flareAdds++; + + // if the point is off the screen, don't bother adding it + // calculate screen coordinates and depth + R_TransformModelToClip( point, backEnd.or.modelMatrix, + backEnd.viewParms.projectionMatrix, eye, clip ); + + // check to see if the point is completely off screen + for ( i = 0 ; i < 3 ; i++ ) { + if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) { + return; + } + } + + R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window ); + + if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth + || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) { + return; // shouldn't happen, since we check the clip[] above, except for FP rounding + } + + // see if a flare with a matching surface, scene, and view exists + oldest = r_flareStructs; + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->surface == surface && f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + break; + } + } + + // allocate a new one + if (!f ) { + if ( !r_inactiveFlares ) { + // the list is completely full + return; + } + f = r_inactiveFlares; + r_inactiveFlares = r_inactiveFlares->next; + f->next = r_activeFlares; + r_activeFlares = f; + + f->surface = surface; + f->frameSceneNum = backEnd.viewParms.frameSceneNum; + f->inPortal = backEnd.viewParms.isPortal; + f->addedFrame = -1; + } + + if ( f->addedFrame != backEnd.viewParms.frameCount - 1 ) { + f->visible = qfalse; + f->fadeTime = backEnd.refdef.time - 2000; + } + + f->addedFrame = backEnd.viewParms.frameCount; + f->fogNum = fogNum; + f->lightScale = lightScale; + + VectorCopy( color, f->color ); + + // fade the intensity of the flare down as the + // light surface turns away from the viewer + if ( normal ) { + VectorSubtract( backEnd.viewParms.or.origin, point, local ); + VectorNormalizeFast( local ); + d = DotProduct( local, normal ); + VectorScale( f->color, d, f->color ); + } + + // save info needed to test + f->windowX = backEnd.viewParms.viewportX + window[0]; + f->windowY = backEnd.viewParms.viewportY + window[1]; + + f->eyeZ = eye[2]; +} + +/* +================== +RB_AddDlightFlares +================== +*/ +void RB_AddDlightFlares( void ) { + dlight_t *l; + int i, j, k; + fog_t *fog; + + if ( !r_flares->integer ) { + return; + } + + l = backEnd.refdef.dlights; + fog = tr.world->fogs; + for (i=0 ; inumfogs ; j++ ) { + fog = &tr.world->fogs[j]; + for ( k = 0 ; k < 3 ; k++ ) { + if ( l->origin[k] < fog->bounds[0][k] || l->origin[k] > fog->bounds[1][k] ) { + break; + } + } + if ( k == 3 ) { + break; + } + } + if ( j == tr.world->numfogs ) { + j = 0; + } + + RB_AddFlare( (void *)l, j, l->origin, l->color, NULL, 1.0f ); + } +} + +/* +=============================================================================== + +FLARE BACK END + +=============================================================================== +*/ + +/* +================== +RB_TestFlare +================== +*/ +void RB_TestFlare( flare_t *f ) { + float depth; + qboolean visible; + float fade; + float screenZ; + + backEnd.pc.c_flareTests++; + + // doing a readpixels is as good as doing a glFinish(), so + // don't bother with another sync + glState.finishCalled = qfalse; + + // read back the z buffer contents + qglReadPixels( f->windowX, f->windowY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); + + screenZ = backEnd.viewParms.projectionMatrix[14] / + ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + visible = ( -f->eyeZ - -screenZ ) < 24; + + if ( visible ) { + if ( !f->visible ) { + f->visible = qtrue; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = ( ( backEnd.refdef.time - f->fadeTime ) /1000.0f ) * r_flareFade->value; + } else { + if ( f->visible ) { + f->visible = qfalse; + f->fadeTime = backEnd.refdef.time - 1; + } + fade = 1.0 - ( ( backEnd.refdef.time - f->fadeTime ) / 1000.0f ) * r_flareFade->value; + } + + if ( fade < 0 ) { + fade = 0; + } + if ( fade > 1 ) { + fade = 1; + } + + f->drawIntensity = fade; +} + + +/* +================== +RB_RenderFlare +================== +*/ +void RB_RenderFlare( flare_t *f ) { + float size; + vec3_t color; + int iColor[3]; + + backEnd.pc.c_flareRenders++; + + VectorScale( f->color, f->drawIntensity*tr.identityLight, color ); + iColor[0] = color[0] * 255; + iColor[1] = color[1] * 255; + iColor[2] = color[2] * 255; + + size = f->lightScale * backEnd.viewParms.viewportWidth * ( r_flareSize->value/640.0 + 8 / -f->eyeZ ); + + RB_BeginSurface( tr.flareShader, f->fogNum ); + + // FIXME: use quadstamp? + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX - size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY + size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.xyz[tess.numVertexes][0] = f->windowX + size; + tess.xyz[tess.numVertexes][1] = f->windowY - size; + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = iColor[0]; + tess.vertexColors[tess.numVertexes][1] = iColor[1]; + tess.vertexColors[tess.numVertexes][2] = iColor[2]; + tess.vertexColors[tess.numVertexes][3] = 255; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); +} + +/* +================== +RB_RenderFlares + +Because flares are simulating an occular effect, they should be drawn after +everything (all views) in the entire frame has been drawn. + +Because of the way portals use the depth buffer to mark off areas, the +needed information would be lost after each view, so we are forced to draw +flares after each view. + +The resulting artifact is that flares in mirrors or portals don't dim properly +when occluded by something in the main view, and portal flares that should +extend past the portal edge will be overwritten. +================== +*/ +void RB_RenderFlares (void) { + flare_t *f; + flare_t **prev; + qboolean draw; + + if ( !r_flares->integer ) { + return; + } + +// RB_AddDlightFlares(); + + // perform z buffer readback on each flare in this view + draw = qfalse; + prev = &r_activeFlares; + while ( ( f = *prev ) != NULL ) { + // throw out any flares that weren't added last frame + if ( f->addedFrame < backEnd.viewParms.frameCount - 1 ) { + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + + // don't draw any here that aren't from this scene / portal + f->drawIntensity = 0; + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal ) { + RB_TestFlare( f ); + if ( f->drawIntensity ) { + draw = qtrue; + } else { + // this flare has completely faded out, so remove it from the chain + *prev = f->next; + f->next = r_inactiveFlares; + r_inactiveFlares = f; + continue; + } + } + + prev = &f->next; + } + + if ( !draw ) { + return; // none visible + } + + if ( backEnd.viewParms.isPortal ) { + qglDisable (GL_CLIP_PLANE0); + } + + qglPushMatrix(); + qglLoadIdentity(); + qglMatrixMode( GL_PROJECTION ); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho( backEnd.viewParms.viewportX, backEnd.viewParms.viewportX + backEnd.viewParms.viewportWidth, + backEnd.viewParms.viewportY, backEnd.viewParms.viewportY + backEnd.viewParms.viewportHeight, + -99999, 99999 ); + + for ( f = r_activeFlares ; f ; f = f->next ) { + if ( f->frameSceneNum == backEnd.viewParms.frameSceneNum + && f->inPortal == backEnd.viewParms.isPortal + && f->drawIntensity ) { + RB_RenderFlare( f ); + } + } + + qglPopMatrix(); + qglMatrixMode( GL_MODELVIEW ); + qglPopMatrix(); +} + diff --git a/code/renderer/tr_font.cpp b/code/renderer/tr_font.cpp new file mode 100644 index 0000000..6ab6a0b --- /dev/null +++ b/code/renderer/tr_font.cpp @@ -0,0 +1,1785 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "../qcommon/sstring.h" // stl string class won't compile in here (MS shite), so use Gil's. +#include "tr_local.h" +#include "tr_font.h" + +#include "../qcommon/stringed_ingame.h" + +#ifdef _XBOX +#include "../win32/glw_win_dx8.h" +#endif + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// This file is shared in the single and multiplayer codebases, so be CAREFUL WHAT YOU ADD/CHANGE!!!!! +// +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +// These should be consecutive, big, and in order: +#define XB_GLYPH_A 10000 +#define XB_GLYPH_B 10001 +#define XB_GLYPH_W 10002 +#define XB_GLYPH_X 10003 +#define XB_GLYPH_Y 10004 + +const char* xbGlyphShaders[] = { + "gfx/menus/newFront/A", + "gfx/menus/newFront/B", + "gfx/menus/newFront/W", + "gfx/menus/newFront/X", + "gfx/menus/newFront/Y", +}; + +typedef enum +{ + eWestern, // ( I only care about asian languages in here at the moment ) + eRussian, // .. but now I need to care about this, since it uses a different TP + ePolish, // ditto + eKorean, + eTaiwanese, // 15x15 glyphs tucked against BR of 16x16 space + eJapanese, // 15x15 glyphs tucked against TL of 16x16 space + eChinese, // 15x15 glyphs tucked against TL of 16x16 space + eThai, // 16x16 cells with glyphs against left edge, special file (tha_widths.dat) for variable widths +} Language_e; + +// this is to cut down on all the stupid string compares I've been doing, and convert asian stuff to switch-case +// +Language_e GetLanguageEnum() +{ + return eWestern; +/* + static int iSE_Language_ModificationCount = -1234; // any old silly value that won't match the cvar mod count + static Language_e eLanguage = eWestern; + + // only re-strcmp() when language string has changed from what we knew it as... + // + if (iSE_Language_ModificationCount != se_language->modificationCount ) + { + iSE_Language_ModificationCount = se_language->modificationCount; + + if ( Language_IsRussian() ) eLanguage = eRussian; + else if ( Language_IsPolish() ) eLanguage = ePolish; + else if ( Language_IsKorean() ) eLanguage = eKorean; + else if ( Language_IsTaiwanese() ) eLanguage = eTaiwanese; + else if ( Language_IsJapanese() ) eLanguage = eJapanese; + else if ( Language_IsChinese() ) eLanguage = eChinese; + else if ( Language_IsThai() ) eLanguage = eThai; + else eLanguage = eWestern; + } + + return eLanguage; +*/ +} + +struct SBCSOverrideLanguages_t +{ + LPCSTR m_psName; + Language_e m_eLanguage; +}; + +// so I can do some stuff with for-next loops when I add polish etc... +// +SBCSOverrideLanguages_t g_SBCSOverrideLanguages[]= +{ + {"russian", eRussian}, + {"polish", ePolish}, + {NULL, eWestern} +}; + + + +//================================================ +// + +#define sFILENAME_THAI_WIDTHS "fonts/tha_widths.dat" +#define sFILENAME_THAI_CODES "fonts/tha_codes.dat" + +struct ThaiCodes_t +{ + map m_mapValidCodes; + vector m_viGlyphWidths; + string m_strInitFailureReason; // so we don't have to keep retrying to work this out + + void Clear( void ) + { + m_mapValidCodes.clear(); + m_viGlyphWidths.clear(); + m_strInitFailureReason = ""; // if blank, never failed, else says don't bother re-trying + } + + ThaiCodes_t() + { + Clear(); + } + + // convert a supplied 1,2 or 3-byte multiplied-up integer into a valid 0..n index, else -1... + // + int GetValidIndex( int iCode ) + { + map ::iterator it = m_mapValidCodes.find( iCode ); + if (it != m_mapValidCodes.end()) + { + return (*it).second; + } + + return -1; + } + + int GetWidth( int iGlyphIndex ) + { + if (iGlyphIndex < m_viGlyphWidths.size()) + { + return m_viGlyphWidths[ iGlyphIndex ]; + } + + assert(0); + return 0; + } + + // return is error message to display, or NULL for success + const char *Init(void) + { + if (m_mapValidCodes.empty() && m_viGlyphWidths.empty()) + { + if (m_strInitFailureReason.empty()) // never tried and failed already? + { + int *piData = NULL; // note , not , for []-access + // + // read the valid-codes table in... + // + int iBytesRead = FS_ReadFile( sFILENAME_THAI_CODES, (void **) &piData ); + if (iBytesRead > 0 && !(iBytesRead&3)) // valid length and multiple of 4 bytes long + { + int iTableEntries = iBytesRead / sizeof(int); + + for (int i=0; i < iTableEntries; i++) + { + m_mapValidCodes[ piData[i] ] = i; // convert MBCS code to sequential index... + } + FS_FreeFile( piData ); // dispose of original + + // now read in the widths... (I'll keep these in a simple STL vector, so they'all disappear when the entries do... + // + iBytesRead = FS_ReadFile( sFILENAME_THAI_WIDTHS, (void **) &piData ); + if (iBytesRead > 0 && !(iBytesRead&3) && iBytesRead>>2/*sizeof(int)*/ == iTableEntries) + { + for (int i=0; iwestern scaling info for all glyphs + int m_iAsianGlyphsAcross; // needed to dynamically calculate S,T coords + int m_iAsianPagesLoaded; + bool m_bAsianLastPageHalfHeight; + int m_iLanguageModificationCount; // doesn't matter what this is, so long as it's comparable as being changed + + ThaiCodes_t *m_pThaiData; + +public: + char m_sFontName[MAX_QPATH]; // eg "fonts/lcd" // needed for korean font-hint if we need >1 hangul set + int mPointSize; + int mHeight; + int mAscender; + int mDescender; + + bool mbRoundCalcs; // trying to make this !@#$%^ thing work with scaling + int m_iThisFont; // handle to itself + int m_iAltSBCSFont; // -1 == NULL // alternative single-byte font for languages like russian/polish etc that need to override high characters ? + int m_iOriginalFontWhenSBCSOverriden; + float m_fAltSBCSFontScaleFactor; // -1, else amount to adjust returned values by to make them fit the master western font they're substituting for + bool m_bIsFakeAlienLanguage; // ... if true, don't process as MBCS or override as SBCS etc + + CFontInfo(const char *fontName); +// CFontInfo(int fill) { memset(this, fill, sizeof(*this)); } // wtf? + ~CFontInfo(void) {} + + const int GetPointSize(void) const { return(mPointSize); } + const int GetHeight(void) const { return(mHeight); } + const int GetAscender(void) const { return(mAscender); } + const int GetDescender(void) const { return(mDescender); } + + const glyphInfo_t *GetLetter(const unsigned int uiLetter, int *piShader = NULL); + const int GetCollapsedAsianCode(ulong uiLetter) const; + + const int GetLetterWidth(const unsigned int uiLetter); + const int GetLetterHorizAdvance(const unsigned int uiLetter); + const int GetShader(void) const { return(mShader); } + + void FlagNoAsianGlyphs(void) { m_hAsianShaders[0] = 0; m_iLanguageModificationCount = -1; } // used during constructor + bool AsianGlyphsAvailable(void) const { return !!(m_hAsianShaders[0]); } + + void UpdateAsianIfNeeded( bool bForceReEval = false); +}; + +//================================================ + + + + +// round float to one decimal place... +// +float RoundTenth( float fValue ) +{ + return ( floorf( (fValue*10.0f) + 0.5f) ) / 10.0f; +} + + +int g_iCurrentFontIndex; // entry 0 is reserved index for missing/invalid, else ++ with each new font registered +vector g_vFontArray; +typedef map FontIndexMap_t; + FontIndexMap_t g_mapFontIndexes; +int g_iNonScaledCharRange; // this is used with auto-scaling of asian fonts, anything below this number is preserved in scale, anything above is scaled down by 0.75f + +//paletteRGBA_c lastcolour; + +// =============================== some korean stuff ======================================= + +#define KSC5601_HANGUL_HIBYTE_START 0xB0 // range is... +#define KSC5601_HANGUL_HIBYTE_STOP 0xC8 // ... inclusive +#define KSC5601_HANGUL_LOBYTE_LOBOUND 0xA0 // range is... +#define KSC5601_HANGUL_LOBYTE_HIBOUND 0xFF // ...bounding (ie only valid in between these points, but NULLs in charsets for these codes) +#define KSC5601_HANGUL_CODES_PER_ROW 96 // 2 more than the number of glyphs + +extern qboolean Language_IsKorean( void ); + +static inline bool Korean_ValidKSC5601Hangul( byte _iHi, byte _iLo ) +{ + return (_iHi >=KSC5601_HANGUL_HIBYTE_START && + _iHi <=KSC5601_HANGUL_HIBYTE_STOP && + _iLo > KSC5601_HANGUL_LOBYTE_LOBOUND && + _iLo < KSC5601_HANGUL_LOBYTE_HIBOUND + ); +} + +static inline bool Korean_ValidKSC5601Hangul( unsigned int uiCode ) +{ + return Korean_ValidKSC5601Hangul( uiCode >> 8, uiCode & 0xFF ); +} + + +// takes a KSC5601 double-byte hangul code and collapses down to a 0..n glyph index... +// Assumes rows are 96 wide (glyph slots), not 94 wide (actual glyphs), so I can ignore boundary markers +// +// (invalid hangul codes will return 0) +// +static int Korean_CollapseKSC5601HangulCode(unsigned int uiCode) +{ + if (Korean_ValidKSC5601Hangul( uiCode )) + { + uiCode -= (KSC5601_HANGUL_HIBYTE_START * 256) + KSC5601_HANGUL_LOBYTE_LOBOUND; // sneaky maths on both bytes, reduce to 0x0000 onwards + uiCode = ((uiCode >> 8) * KSC5601_HANGUL_CODES_PER_ROW) + (uiCode & 0xFF); + return uiCode; + } + return 0; +} + +static int Korean_InitFields(int &iGlyphTPs, LPCSTR &psLang) +{ + psLang = "kor"; + iGlyphTPs = GLYPH_MAX_KOREAN_SHADERS; + g_iNonScaledCharRange = 255; + return 32; // m_iAsianGlyphsAcross +} + +// ======================== some taiwanese stuff ============================== + +// (all ranges inclusive for Big5)... +// +#define BIG5_HIBYTE_START0 0xA1 // (misc chars + level 1 hanzi) +#define BIG5_HIBYTE_STOP0 0xC6 // +#define BIG5_HIBYTE_START1 0xC9 // (level 2 hanzi) +#define BIG5_HIBYTE_STOP1 0xF9 // +#define BIG5_LOBYTE_LOBOUND0 0x40 // +#define BIG5_LOBYTE_HIBOUND0 0x7E // +#define BIG5_LOBYTE_LOBOUND1 0xA1 // +#define BIG5_LOBYTE_HIBOUND1 0xFE // +#define BIG5_CODES_PER_ROW 160 // 3 more than the number of glyphs + +extern qboolean Language_IsTaiwanese( void ); + +static bool Taiwanese_ValidBig5Code( unsigned int uiCode ) +{ + const byte _iHi = (uiCode >> 8)&0xFF; + if ( (_iHi >= BIG5_HIBYTE_START0 && _iHi <= BIG5_HIBYTE_STOP0) + || (_iHi >= BIG5_HIBYTE_START1 && _iHi <= BIG5_HIBYTE_STOP1) + ) + { + const byte _iLo = uiCode & 0xFF; + + if ( (_iLo >= BIG5_LOBYTE_LOBOUND0 && _iLo <= BIG5_LOBYTE_HIBOUND0) || + (_iLo >= BIG5_LOBYTE_LOBOUND1 && _iLo <= BIG5_LOBYTE_HIBOUND1) + ) + { + return true; + } + } + + return false; +} + + +// only call this when Taiwanese_ValidBig5Code() has already returned true... +// +static bool Taiwanese_IsTrailingPunctuation( unsigned int uiCode ) +{ + // so far I'm just counting the first 21 chars, those seem to be all the basic punctuation... + // + if ( uiCode >= ((BIG5_HIBYTE_START0<<8)|BIG5_LOBYTE_LOBOUND0) && + uiCode < ((BIG5_HIBYTE_START0<<8)|BIG5_LOBYTE_LOBOUND0+20) + ) + { + return true; + } + + return false; +} + + +// takes a BIG5 double-byte code (including level 2 hanzi) and collapses down to a 0..n glyph index... +// Assumes rows are 160 wide (glyph slots), not 157 wide (actual glyphs), so I can ignore boundary markers +// +// (invalid big5 codes will return 0) +// +static int Taiwanese_CollapseBig5Code( unsigned int uiCode ) +{ + if (Taiwanese_ValidBig5Code( uiCode )) + { + uiCode -= (BIG5_HIBYTE_START0 * 256) + BIG5_LOBYTE_LOBOUND0; // sneaky maths on both bytes, reduce to 0x0000 onwards + if ( (uiCode & 0xFF) >= (BIG5_LOBYTE_LOBOUND1-1)-BIG5_LOBYTE_LOBOUND0) + { + uiCode -= ((BIG5_LOBYTE_LOBOUND1-1) - (BIG5_LOBYTE_HIBOUND0+1)) -1; + } + uiCode = ((uiCode >> 8) * BIG5_CODES_PER_ROW) + (uiCode & 0xFF); + return uiCode; + } + return 0; +} + +static int Taiwanese_InitFields(int &iGlyphTPs, LPCSTR &psLang) +{ + psLang = "tai"; + iGlyphTPs = GLYPH_MAX_TAIWANESE_SHADERS; + g_iNonScaledCharRange = 255; + return 64; // m_iAsianGlyphsAcross +} + +// ======================== some Japanese stuff ============================== + + +// ( all ranges inclusive for Shift-JIS ) +// +#define SHIFTJIS_HIBYTE_START0 0x81 +#define SHIFTJIS_HIBYTE_STOP0 0x9F +#define SHIFTJIS_HIBYTE_START1 0xE0 +#define SHIFTJIS_HIBYTE_STOP1 0xEF +// +#define SHIFTJIS_LOBYTE_START0 0x40 +#define SHIFTJIS_LOBYTE_STOP0 0x7E +#define SHIFTJIS_LOBYTE_START1 0x80 +#define SHIFTJIS_LOBYTE_STOP1 0xFC +#define SHIFTJIS_CODES_PER_ROW (((SHIFTJIS_LOBYTE_STOP0-SHIFTJIS_LOBYTE_START0)+1)+((SHIFTJIS_LOBYTE_STOP1-SHIFTJIS_LOBYTE_START1)+1)) + + +extern qboolean Language_IsJapanese( void ); + +static bool Japanese_ValidShiftJISCode( byte _iHi, byte _iLo ) +{ + if ( (_iHi >= SHIFTJIS_HIBYTE_START0 && _iHi <= SHIFTJIS_HIBYTE_STOP0) + || (_iHi >= SHIFTJIS_HIBYTE_START1 && _iHi <= SHIFTJIS_HIBYTE_STOP1) + ) + { + if ( (_iLo >= SHIFTJIS_LOBYTE_START0 && _iLo <= SHIFTJIS_LOBYTE_STOP0) || + (_iLo >= SHIFTJIS_LOBYTE_START1 && _iLo <= SHIFTJIS_LOBYTE_STOP1) + ) + { + return true; + } + } + + return false; +} + +static inline bool Japanese_ValidShiftJISCode( unsigned int uiCode ) +{ + return Japanese_ValidShiftJISCode( uiCode >> 8, uiCode & 0xFF ); +} + + +// only call this when Japanese_ValidShiftJISCode() has already returned true... +// +static bool Japanese_IsTrailingPunctuation( unsigned int uiCode ) +{ + // so far I'm just counting the first 18 chars, those seem to be all the basic punctuation... + // + if ( uiCode >= ((SHIFTJIS_HIBYTE_START0<<8)|SHIFTJIS_LOBYTE_START0) && + uiCode < ((SHIFTJIS_HIBYTE_START0<<8)|SHIFTJIS_LOBYTE_START0+18) + ) + { + return true; + } + + return false; +} + + +// takes a ShiftJIS double-byte code and collapse down to a 0..n glyph index... +// +// (invalid codes will return 0) +// +static int Japanese_CollapseShiftJISCode( unsigned int uiCode ) +{ + if (Japanese_ValidShiftJISCode( uiCode )) + { + uiCode -= ((SHIFTJIS_HIBYTE_START0<<8)|SHIFTJIS_LOBYTE_START0); // sneaky maths on both bytes, reduce to 0x0000 onwards + + if ( (uiCode & 0xFF) >= (SHIFTJIS_LOBYTE_START1)-SHIFTJIS_LOBYTE_START0) + { + uiCode -= ((SHIFTJIS_LOBYTE_START1)-SHIFTJIS_LOBYTE_STOP0)-1; + } + + if ( ((uiCode>>8)&0xFF) >= (SHIFTJIS_HIBYTE_START1)-SHIFTJIS_HIBYTE_START0) + { + uiCode -= (((SHIFTJIS_HIBYTE_START1)-SHIFTJIS_HIBYTE_STOP0)-1) << 8; + } + + uiCode = ((uiCode >> 8) * SHIFTJIS_CODES_PER_ROW) + (uiCode & 0xFF); + + return uiCode; + } + return 0; +} + + +static int Japanese_InitFields(int &iGlyphTPs, LPCSTR &psLang) +{ + psLang = "jap"; + iGlyphTPs = GLYPH_MAX_JAPANESE_SHADERS; + g_iNonScaledCharRange = 255; + return 64; // m_iAsianGlyphsAcross +} + +// ======================== some Chinese stuff ============================== + +#define GB_HIBYTE_START 0xA1 // range is... +#define GB_HIBYTE_STOP 0xF7 // ... inclusive +#define GB_LOBYTE_LOBOUND 0xA0 // range is... +#define GB_LOBYTE_HIBOUND 0xFF // ...bounding (ie only valid in between these points, but NULLs in charsets for these codes) +#define GB_CODES_PER_ROW 95 // 1 more than the number of glyphs + +extern qboolean Language_IsChinese( void ); + +static inline bool Chinese_ValidGBCode( byte _iHi, byte _iLo ) +{ + return (_iHi >=GB_HIBYTE_START && + _iHi <=GB_HIBYTE_STOP && + _iLo > GB_LOBYTE_LOBOUND && + _iLo < GB_LOBYTE_HIBOUND + ); +} + +static inline bool Chinese_ValidGBCode( unsigned int uiCode) +{ + return Chinese_ValidGBCode( uiCode >> 8, uiCode & 0xFF ); +} + + +// only call this when Chinese_ValidGBCode() has already returned true... +// +static bool Chinese_IsTrailingPunctuation( unsigned int uiCode ) +{ + // so far I'm just counting the first 13 chars, those seem to be all the basic punctuation... + // + if ( uiCode > ((GB_HIBYTE_START<<8)|GB_LOBYTE_LOBOUND) && + uiCode < ((GB_HIBYTE_START<<8)|GB_LOBYTE_LOBOUND+14) + ) + { + return true; + } + + return false; +} + + +// takes a GB double-byte code and collapses down to a 0..n glyph index... +// Assumes rows are 96 wide (glyph slots), not 94 wide (actual glyphs), so I can ignore boundary markers +// +// (invalid GB codes will return 0) +// +static int Chinese_CollapseGBCode( unsigned int uiCode ) +{ + if (Chinese_ValidGBCode( uiCode )) + { + uiCode -= (GB_HIBYTE_START * 256) + GB_LOBYTE_LOBOUND; // sneaky maths on both bytes, reduce to 0x0000 onwards + uiCode = ((uiCode >> 8) * GB_CODES_PER_ROW) + (uiCode & 0xFF); + return uiCode; + } + + return 0; +} + +static int Chinese_InitFields(int &iGlyphTPs, LPCSTR &psLang) +{ + psLang = "chi"; + iGlyphTPs = GLYPH_MAX_CHINESE_SHADERS; + g_iNonScaledCharRange = 255; + return 64; // m_iAsianGlyphsAcross +} + +// ======================== some Thai stuff ============================== + +//TIS 620-2533 + +#define TIS_GLYPHS_START 160 +#define TIS_SARA_AM 0xD3 // special case letter, both a new letter and a trailing accent for the prev one +ThaiCodes_t g_ThaiCodes; // the one and only instance of this object + +extern qboolean Language_IsThai( void ); + +/* +static int Thai_IsAccentChar( unsigned int uiCode ) +{ + switch (uiCode) + { + case 209: + case 212: case 213: case 214: case 215: case 216: case 217: case 218: + case 231: case 232: case 233: case 234: case 235: case 236: case 237: case 238: + return true; + } + + return false; +} +*/ + +// returns a valid Thai code (or 0), based on taking 1,2 or 3 bytes from the supplied byte stream +// Fills in with 1,2 or 3 +static int Thai_ValidTISCode( const byte *psString, int &iThaiBytes ) +{ + // try a 1-byte code first... + // + if (psString[0] >= 160) // so western letters drop through and use normal font + { + // this code is heavily little-endian, so someone else will need to port for Mac etc... (not my problem ;-) + // + union CodeToTry_t + { + char sChars[4]; + unsigned int uiCode; + }; + + CodeToTry_t CodeToTry; + CodeToTry.uiCode = 0; // important that we clear all 4 bytes in sChars here + + // thai codes can be up to 3 bytes long, so see how high we can get... + // + for (int i=0; i<3; i++) + { + CodeToTry.sChars[i] = psString[i]; + + int iIndex = g_ThaiCodes.GetValidIndex( CodeToTry.uiCode ); + if (iIndex == -1) + { + // failed, so return previous-longest code... + // + CodeToTry.sChars[i] = 0; + break; + } + } + iThaiBytes = i; + assert(i); // if 'i' was 0, then this may be an error, trying to get a thai accent as standalone char? + return CodeToTry.uiCode; + } + + return 0; +} + +// special case, thai can only break on certain letters, and since the rules are complicated then +// we tell the translators to put an underscore ('_') between each word even though in Thai they're +// all jammed together at final output onscreen... +// +static inline bool Thai_IsTrailingPunctuation( unsigned int uiCode ) +{ + return uiCode == '_'; +} + +// takes a TIS 1,2 or 3 byte code and collapse down to a 0..n glyph index... +// +// (invalid codes will return 0) +// +static int Thai_CollapseTISCode( unsigned int uiCode ) +{ + if (uiCode >= TIS_GLYPHS_START) // so western letters drop through as invalid + { + int iCollapsedIndex = g_ThaiCodes.GetValidIndex( uiCode ); + if (iCollapsedIndex != -1) + { + return iCollapsedIndex; + } + } + + return 0; +} + +static int Thai_InitFields(int &iGlyphTPs, LPCSTR &psLang) +{ + psLang = "tha"; + iGlyphTPs = GLYPH_MAX_THAI_SHADERS; + g_iNonScaledCharRange = INT_MAX; // in other words, don't scale any thai chars down + return 32; // m_iAsianGlyphsAcross +} + + +// ============================================================================ + +// takes char *, returns integer char at that point, and advances char * on by enough bytes to move +// past the letter (either western 1 byte or Asian multi-byte)... +// +// looks messy, but the actual execution route is quite short, so it's fast... +// +// Note that I have to have this 3-param form instead of advancing a passed-in "const char **psText" because of VM-crap where you can only change ptr-contents, not ptrs themselves. Bleurgh. Ditto the qtrue:qfalse crap instead of just returning stuff straight through. +// +unsigned int AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation /* = NULL */) +{ + const byte *psString = (const byte *) psText; // avoid sign-promote bug + unsigned int uiLetter; + +/* + switch ( GetLanguageEnum() ) + { + case eKorean: + { + if ( Korean_ValidKSC5601Hangul( psString[0], psString[1] )) + { + uiLetter = (psString[0] * 256) + psString[1]; + *piAdvanceCount = 2; + + // not going to bother testing for korean punctuation here, since korean already + // uses spaces, and I don't have the punctuation glyphs defined, only the basic 2350 hanguls + // + if ( pbIsTrailingPunctuation) + { + *pbIsTrailingPunctuation = qfalse; + } + + return uiLetter; + } + } + break; + + case eTaiwanese: + { + if ( Taiwanese_ValidBig5Code( (psString[0] * 256) + psString[1] )) + { + uiLetter = (psString[0] * 256) + psString[1]; + *piAdvanceCount = 2; + + // need to ask if this is a trailing (ie like a comma or full-stop) punctuation?... + // + if ( pbIsTrailingPunctuation) + { + *pbIsTrailingPunctuation = Taiwanese_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse; + } + + return uiLetter; + } + } + break; + + case eJapanese: + { + if ( Japanese_ValidShiftJISCode( psString[0], psString[1] )) + { + uiLetter = (psString[0] * 256) + psString[1]; + *piAdvanceCount = 2; + + // need to ask if this is a trailing (ie like a comma or full-stop) punctuation?... + // + if ( pbIsTrailingPunctuation) + { + *pbIsTrailingPunctuation = Japanese_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse; + } + + return uiLetter; + } + } + break; + + case eChinese: + { + if ( Chinese_ValidGBCode( (psString[0] * 256) + psString[1] )) + { + uiLetter = (psString[0] * 256) + psString[1]; + *piAdvanceCount = 2; + + // need to ask if this is a trailing (ie like a comma or full-stop) punctuation?... + // + if ( pbIsTrailingPunctuation) + { + *pbIsTrailingPunctuation = Chinese_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse; + } + + return uiLetter; + } + } + break; + + case eThai: + { + int iThaiBytes; + uiLetter = Thai_ValidTISCode( psString, iThaiBytes ); + if ( uiLetter ) + { + *piAdvanceCount = iThaiBytes; + + if ( pbIsTrailingPunctuation ) + { + *pbIsTrailingPunctuation = Thai_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse; + } + + return uiLetter; + } + } + break; + } +*/ + + // ... must not have been an MBCS code... + // + if( psString[0] == '^' ) + { + // Handle button prompt magic: + *piAdvanceCount = 2; + switch( psString[1] ) + { + case 'A': + uiLetter = XB_GLYPH_A; break; + case 'B': + uiLetter = XB_GLYPH_B; break; + case 'X': + uiLetter = XB_GLYPH_X; break; + case 'Y': + uiLetter = XB_GLYPH_Y; break; + case 'W': + uiLetter = XB_GLYPH_W; break; + default: + *piAdvanceCount = 1; + uiLetter = '^'; break; + } + } + else + { + uiLetter = psString[0]; + *piAdvanceCount = 1; + } + + if (pbIsTrailingPunctuation) + { + *pbIsTrailingPunctuation = (uiLetter == '!' || + uiLetter == '?' || + uiLetter == ',' || + uiLetter == '.' || + uiLetter == ';' || + uiLetter == ':' + ) ? qtrue : qfalse; + } + + return uiLetter; +} + + +// needed for subtitle printing since original code no longer worked once camera bar height was changed to 480/10 +// rather than refdef height / 10. I now need to bodge the coords to come out right. +// +qboolean Language_IsAsian(void) +{ + switch ( GetLanguageEnum() ) + { + case eKorean: + case eTaiwanese: + case eJapanese: + case eChinese: + case eThai: // this is asian, but the query is normally used for scaling + return qtrue; + } + + return qfalse; +} + +qboolean Language_UsesSpaces(void) +{ + // ( korean uses spaces ) + switch ( GetLanguageEnum() ) + { + case eTaiwanese: + case eJapanese: + case eChinese: + case eThai: + return qfalse; + } + + return qtrue; +} + +// ====================================================================== +// name is (eg) "ergo" or "lcd", no extension. +// +// If path present, it's a special language hack for SBCS override languages, eg: "lcd/russian", which means +// just treat the file as "russian", but with the "lcd" part ensuring we don't find a different registered russian font +// +CFontInfo::CFontInfo(const char *_fontName) +{ + int len, i; + void *buff; + dfontdat_t *fontdat; + + // remove any special hack name insertions... + // + char fontName[MAX_QPATH]; + sprintf(fontName,"fonts/%s.fontdat",COM_SkipPath(const_cast(_fontName))); // COM_SkipPath should take a const char *, but it's just possible people use it as a char * I guess, so I have to hack around like this + + // clear some general things... + // + m_pThaiData = NULL; + m_iAltSBCSFont = -1; + m_iThisFont = -1; + m_iOriginalFontWhenSBCSOverriden = -1; + m_fAltSBCSFontScaleFactor = -1; + m_bIsFakeAlienLanguage = !strcmp(_fontName,"aurabesh"); // dont try and make SBCS or asian overrides for this + + len = FS_ReadFile(fontName, NULL); + if (len == sizeof(dfontdat_t)) + { + FS_ReadFile(fontName, &buff); + fontdat = (dfontdat_t *)buff; + + for(i = 0; i < GLYPH_COUNT; i++) + { + mGlyphs[i] = fontdat->mGlyphs[i]; + } + mPointSize = fontdat->mPointSize; + mHeight = fontdat->mHeight; + mAscender = fontdat->mAscender; + mDescender = fontdat->mDescender; +// mAsianHack = fontdat->mKoreanHack; // ignore this crap, it's some junk in the fontdat file that no-one uses + mbRoundCalcs = !!strstr(fontName,"ergo"); + + // cope with bad fontdat headers... + // + if (mHeight == 0) + { + mHeight = mPointSize; + mAscender = mPointSize - Round( ((float)mPointSize/10.0f)+2 ); // have to completely guess at the baseline... sigh. + mDescender = mHeight - mAscender; + } + + FS_FreeFile(buff); + } + else + { + mHeight = 0; + mShader = 0; + } + + Q_strncpyz(m_sFontName, fontName, sizeof(m_sFontName)); + COM_StripExtension( m_sFontName, m_sFontName ); // so we get better error printing if failed to load shader (ie lose ".fontdat") + mShader = RE_RegisterShaderNoMip(m_sFontName); + + FlagNoAsianGlyphs(); + UpdateAsianIfNeeded(true); + + // finished... + g_vFontArray.resize(g_iCurrentFontIndex + 1); + g_vFontArray[g_iCurrentFontIndex++] = this; + + + extern cvar_t *com_buildScript; + if (com_buildScript->integer == 2) + { + Com_Printf( "com_buildScript(2): Registering foreign fonts...\n" ); + static qboolean bDone = qfalse; // Do this once only (for speed)... + if (!bDone) + { + bDone = qtrue; + + char sTemp[MAX_QPATH]; + int iGlyphTPs = 0; + const char *psLang = NULL; + + // SBCS override languages... + // + fileHandle_t f; + for (int i=0; g_SBCSOverrideLanguages[i].m_psName ;i++) + { + char sTemp[MAX_QPATH]; + + sprintf(sTemp,"fonts/%s.tga", g_SBCSOverrideLanguages[i].m_psName ); + FS_FOpenFileRead( sTemp, &f, qfalse ); + if (f) FS_FCloseFile( f ); + + sprintf(sTemp,"fonts/%s.fontdat", g_SBCSOverrideLanguages[i].m_psName ); + FS_FOpenFileRead( sTemp, &f, qfalse ); + if (f) FS_FCloseFile( f ); + } + + // asian MBCS override languages... + // + for (int iLang=0; iLang<5; iLang++) + { + switch (iLang) + { + case 0: m_iAsianGlyphsAcross = Korean_InitFields (iGlyphTPs, psLang); break; + case 1: m_iAsianGlyphsAcross = Taiwanese_InitFields (iGlyphTPs, psLang); break; + case 2: m_iAsianGlyphsAcross = Japanese_InitFields (iGlyphTPs, psLang); break; + case 3: m_iAsianGlyphsAcross = Chinese_InitFields (iGlyphTPs, psLang); break; + case 4: m_iAsianGlyphsAcross = Thai_InitFields (iGlyphTPs, psLang); + { + // additional files needed for Thai language... + // + FS_FOpenFileRead( sFILENAME_THAI_WIDTHS , &f, qfalse ); + if (f) { + FS_FCloseFile( f ); + } + + FS_FOpenFileRead( sFILENAME_THAI_CODES, &f, qfalse ); + if (f) { + FS_FCloseFile( f ); + } + } + break; + } + + for (int i=0; imodificationCount || !AsianGlyphsAvailable() || bForceReEval) + { + m_iLanguageModificationCount = se_language->modificationCount; + + int iGlyphTPs = 0; + const char *psLang = NULL; + + switch ( eLanguage ) + { + case eKorean: m_iAsianGlyphsAcross = Korean_InitFields(iGlyphTPs, psLang); break; + case eTaiwanese: m_iAsianGlyphsAcross = Taiwanese_InitFields(iGlyphTPs, psLang); break; + case eJapanese: m_iAsianGlyphsAcross = Japanese_InitFields(iGlyphTPs, psLang); break; + case eChinese: m_iAsianGlyphsAcross = Chinese_InitFields(iGlyphTPs, psLang); break; + case eThai: + { + m_iAsianGlyphsAcross = Thai_InitFields(iGlyphTPs, psLang); + + if (!m_pThaiData) + { + LPCSTR psFailureReason = g_ThaiCodes.Init(); + if (!psFailureReason[0]) + { + m_pThaiData = &g_ThaiCodes; + } + else + { + // failed to load a needed file, reset to English... + // + Cvar_Set("se_language", "english"); + Com_Error( ERR_DROP, psFailureReason ); + } + } + } + break; + } + + // textures need loading... + // + if (m_sFontName[0]) + { + // Use this sometime if we need to do logic to load alternate-height glyphs to better fit other fonts. + // (but for now, we just use the one glyph set) + // + } + + for (int i = 0; i < iGlyphTPs; i++) + { + // (Note!! assumption for S,T calculations: all Asian glyph textures pages are square except for last one) + // + char sTemp[MAX_QPATH]; + Com_sprintf(sTemp,sizeof(sTemp), "fonts/%s_%d_1024_%d", psLang, 1024/m_iAsianGlyphsAcross, i); + // + // returning 0 here will automatically inhibit Asian glyph calculations at runtime... + // + m_hAsianShaders[i] = RE_RegisterShaderNoMip( sTemp ); + } + + // for now I'm hardwiring these, but if we ever have more than one glyph set per language then they'll be changed... + // + m_iAsianPagesLoaded = iGlyphTPs; // not necessarily true, but will be safe, and show up obvious if something missing + m_bAsianLastPageHalfHeight = true; + + bForceReEval = true; + } + + if (bForceReEval) + { + // now init the Asian member glyph fields to make them come out the same size as the western ones + // that they serve as an alternative for... + // + m_AsianGlyph.width = iCappedHeight; // square Asian chars same size as height of western set + m_AsianGlyph.height = iCappedHeight; // "" + switch (eLanguage) + { + default: m_AsianGlyph.horizAdvance = iCappedHeight; break; + case eKorean: m_AsianGlyph.horizAdvance = iCappedHeight - 1;break; // korean has a small amount of space at the edge of the glyph + + case eTaiwanese: + case eJapanese: + case eChinese: m_AsianGlyph.horizAdvance = iCappedHeight + 3; // need to force some spacing for these +// case eThai: // this is done dynamically elsewhere, since Thai glyphs are variable width + } + m_AsianGlyph.horizOffset = 0; // "" + m_AsianGlyph.baseline = mAscender + ((iCappedHeight - mHeight) >> 1); + } + } + else + { + // not using Asian... + // + FlagNoAsianGlyphs(); + } + } + else + { + // no western glyphs available, so don't attempt to match asian... + // + FlagNoAsianGlyphs(); + } +} + +static CFontInfo *GetFont_Actual(int index) +{ + index &= SET_MASK; + if((index >= 1) && (index < g_iCurrentFontIndex)) + { + CFontInfo *pFont = g_vFontArray[index]; + + if (pFont) + { + pFont->UpdateAsianIfNeeded(); + } + + return pFont; + } + return(NULL); +} + + +// needed to add *piShader param because of multiple TPs, +// if not passed in, then I also skip S,T calculations for re-usable static asian glyphinfo struct... +// +const glyphInfo_t *CFontInfo::GetLetter(const unsigned int uiLetter, int *piShader /* = NULL */) +{ + if ( AsianGlyphsAvailable() ) + { + int iCollapsedAsianCode = GetCollapsedAsianCode( uiLetter ); + if (iCollapsedAsianCode) + { + if (piShader) + { + // (Note!! assumption for S,T calculations: all asian glyph textures pages are square except for last one + // which may or may not be half height) - but not for Thai + // + int iTexturePageIndex = iCollapsedAsianCode / (m_iAsianGlyphsAcross * m_iAsianGlyphsAcross); + + if (iTexturePageIndex > m_iAsianPagesLoaded) + { + assert(0); // should never happen + iTexturePageIndex = 0; + } + + int iOriginalCollapsedAsianCode = iCollapsedAsianCode; // need to back this up (if Thai) for later + iCollapsedAsianCode -= iTexturePageIndex * (m_iAsianGlyphsAcross * m_iAsianGlyphsAcross); + + const int iColumn = iCollapsedAsianCode % m_iAsianGlyphsAcross; + const int iRow = iCollapsedAsianCode / m_iAsianGlyphsAcross; + const bool bHalfT = (iTexturePageIndex == (m_iAsianPagesLoaded - 1) && m_bAsianLastPageHalfHeight); + const int iAsianGlyphsDown = (bHalfT) ? m_iAsianGlyphsAcross / 2 : m_iAsianGlyphsAcross; + + switch ( GetLanguageEnum() ) + { + case eKorean: + default: + { + m_AsianGlyph.s = (float)( iColumn ) / (float)m_iAsianGlyphsAcross; + m_AsianGlyph.t = (float)( iRow ) / (float) iAsianGlyphsDown; + m_AsianGlyph.s2 = (float)( iColumn + 1) / (float)m_iAsianGlyphsAcross; + m_AsianGlyph.t2 = (float)( iRow + 1 ) / (float) iAsianGlyphsDown; + } + break; + + case eTaiwanese: + { + m_AsianGlyph.s = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn ))+1) / 1024.0f; + m_AsianGlyph.t = (float)(((1024 / iAsianGlyphsDown ) * ( iRow ))+1) / 1024.0f; + m_AsianGlyph.s2 = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn+1 )) ) / 1024.0f; + m_AsianGlyph.t2 = (float)(((1024 / iAsianGlyphsDown ) * ( iRow+1 )) ) / 1024.0f; + } + break; + + case eJapanese: + case eChinese: + { + m_AsianGlyph.s = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn )) ) / 1024.0f; + m_AsianGlyph.t = (float)(((1024 / iAsianGlyphsDown ) * ( iRow )) ) / 1024.0f; + m_AsianGlyph.s2 = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn+1 ))-1) / 1024.0f; + m_AsianGlyph.t2 = (float)(((1024 / iAsianGlyphsDown ) * ( iRow+1 ))-1) / 1024.0f; + } + break; + + case eThai: + { + int iGlyphXpos = (1024 / m_iAsianGlyphsAcross) * ( iColumn ); + int iGlyphWidth = g_ThaiCodes.GetWidth( iOriginalCollapsedAsianCode ); + + // very thai-specific language-code... + // + if (uiLetter == TIS_SARA_AM) + { + iGlyphXpos += 9; // these are pixel coords on the source TP, so don't affect scaled output + iGlyphWidth= 20; // + } + m_AsianGlyph.s = (float)(iGlyphXpos) / 1024.0f; + m_AsianGlyph.t = (float)(((1024 / iAsianGlyphsDown ) * ( iRow )) ) / 1024.0f; + // technically this .s2 line should be modified to blit only the correct width, but since + // all Thai glyphs are up against the left edge of their cells and have blank to the cell + // boundary then it's better to keep these calculations simpler... + + m_AsianGlyph.s2 = (float)(iGlyphXpos+iGlyphWidth) / 1024.0f; + m_AsianGlyph.t2 = (float)(((1024 / iAsianGlyphsDown ) * ( iRow+1 ))-1) / 1024.0f; + + // special addition for Thai, need to bodge up the width and advance fields... + // + m_AsianGlyph.width = iGlyphWidth; + m_AsianGlyph.horizAdvance = iGlyphWidth + 1; + } + break; + } + *piShader = m_hAsianShaders[ iTexturePageIndex ]; + } + return &m_AsianGlyph; + } + } + + if (piShader) + { + *piShader = GetShader(); + } + + const glyphInfo_t *pGlyph = &mGlyphs[ uiLetter & 0xff ]; + // + // SBCS language substitution?... + // + if ( m_fAltSBCSFontScaleFactor != -1 ) + { + // sod it, use the asian glyph, that's fine... + // + memcpy(&m_AsianGlyph,pGlyph,sizeof(m_AsianGlyph)); // *before* changin pGlyph! + +// CFontInfo *pOriginalFont = GetFont_Actual( this->m_iOriginalFontWhenSBCSOverriden ); +// pGlyph = &pOriginalFont->mGlyphs[ uiLetter & 0xff ]; + + #define ASSIGN_WITH_ROUNDING(_dst,_src) _dst = mbRoundCalcs ? Round( m_fAltSBCSFontScaleFactor * _src ) : m_fAltSBCSFontScaleFactor * (float)_src; + + ASSIGN_WITH_ROUNDING( m_AsianGlyph.baseline, pGlyph->baseline ); + ASSIGN_WITH_ROUNDING( m_AsianGlyph.height, pGlyph->height ); + ASSIGN_WITH_ROUNDING( m_AsianGlyph.horizAdvance,pGlyph->horizAdvance ); +// m_AsianGlyph.horizOffset = /*Round*/( m_fAltSBCSFontScaleFactor * pGlyph->horizOffset ); + ASSIGN_WITH_ROUNDING( m_AsianGlyph.width, pGlyph->width ); + + pGlyph = &m_AsianGlyph; + } + + return pGlyph; +} + +const int CFontInfo::GetCollapsedAsianCode(ulong uiLetter) const +{ + int iCollapsedAsianCode = 0; + + if (AsianGlyphsAvailable()) + { + switch ( GetLanguageEnum() ) + { + case eKorean: iCollapsedAsianCode = Korean_CollapseKSC5601HangulCode( uiLetter ); break; + case eTaiwanese: iCollapsedAsianCode = Taiwanese_CollapseBig5Code( uiLetter ); break; + case eJapanese: iCollapsedAsianCode = Japanese_CollapseShiftJISCode( uiLetter ); break; + case eChinese: iCollapsedAsianCode = Chinese_CollapseGBCode( uiLetter ); break; + case eThai: iCollapsedAsianCode = Thai_CollapseTISCode( uiLetter ); break; + default: assert(0); /* unhandled asian language */ break; + } + } + + return iCollapsedAsianCode; +} + +const int CFontInfo::GetLetterWidth(unsigned int uiLetter) +{ + const glyphInfo_t *pGlyph = GetLetter( uiLetter ); + return pGlyph->width ? pGlyph->width : mGlyphs['.'].width; +} + +const int CFontInfo::GetLetterHorizAdvance(unsigned int uiLetter) +{ + const glyphInfo_t *pGlyph = GetLetter( uiLetter ); + return pGlyph->horizAdvance ? pGlyph->horizAdvance : mGlyphs['.'].horizAdvance; +} + +// ensure any GetFont calls that need SBCS overriding (such as when playing in Russian) have the appropriate stuff done... +// +static CFontInfo *GetFont_SBCSOverride(CFontInfo *pFont, Language_e eLanguageSBCS, LPCSTR psLanguageNameSBCS ) +{ + if ( !pFont->m_bIsFakeAlienLanguage ) + { + if ( GetLanguageEnum() == eLanguageSBCS ) + { + if ( pFont->m_iAltSBCSFont == -1 ) // no reg attempted yet? + { + // need to register this alternative SBCS font... + // + int iAltFontIndex = RE_RegisterFont( va("%s/%s",COM_SkipPath(pFont->m_sFontName),psLanguageNameSBCS) ); // ensure unique name (eg: "lcd/russian") + CFontInfo *pAltFont = GetFont_Actual( iAltFontIndex ); + if ( pAltFont ) + { + // work out the scaling factor for this font's glyphs...( round it to 1 decimal place to cut down on silly scale factors like 0.53125 ) + // + pAltFont->m_fAltSBCSFontScaleFactor = RoundTenth((float)pFont->GetPointSize() / (float)pAltFont->GetPointSize()); + // + // then override with the main properties of the original font... + // + pAltFont->mPointSize = pFont->GetPointSize();//(float) pAltFont->GetPointSize() * pAltFont->m_fAltSBCSFontScaleFactor; + pAltFont->mHeight = pFont->GetHeight();//(float) pAltFont->GetHeight() * pAltFont->m_fAltSBCSFontScaleFactor; + pAltFont->mAscender = pFont->GetAscender();//(float) pAltFont->GetAscender() * pAltFont->m_fAltSBCSFontScaleFactor; + pAltFont->mDescender = pFont->GetDescender();//(float) pAltFont->GetDescender() * pAltFont->m_fAltSBCSFontScaleFactor; + +// pAltFont->mPointSize = (float) pAltFont->GetPointSize() * pAltFont->m_fAltSBCSFontScaleFactor; +// pAltFont->mHeight = (float) pAltFont->GetHeight() * pAltFont->m_fAltSBCSFontScaleFactor; +// pAltFont->mAscender = (float) pAltFont->GetAscender() * pAltFont->m_fAltSBCSFontScaleFactor; +// pAltFont->mDescender = (float) pAltFont->GetDescender() * pAltFont->m_fAltSBCSFontScaleFactor; + + pAltFont->mbRoundCalcs = true; + pAltFont->m_iOriginalFontWhenSBCSOverriden = pFont->m_iThisFont; + } + pFont->m_iAltSBCSFont = iAltFontIndex; + } + + if ( pFont->m_iAltSBCSFont > 0) + { + return GetFont_Actual( pFont->m_iAltSBCSFont ); + } + } + } + + return NULL; +} + + + +CFontInfo *GetFont(int index) +{ + CFontInfo *pFont = GetFont_Actual( index ); + + if (pFont) + { + // any SBCS overrides? (this has to be pretty quick, and is (sort of))... + // + for (int i=0; g_SBCSOverrideLanguages[i].m_psName; i++) + { + CFontInfo *pAltFont = GetFont_SBCSOverride( pFont, g_SBCSOverrideLanguages[i].m_eLanguage, g_SBCSOverrideLanguages[i].m_psName ); + if (pAltFont) + { + return pAltFont; + } + } + } + + return pFont; +} + + +int RE_Font_StrLenPixels(const char *psText, const int iFontHandle, const float fScale) +{ + int iMaxWidth = 0; + int iThisWidth= 0; + CFontInfo *curfont; + + curfont = GetFont(iFontHandle); + if(!curfont) + { + return(0); + } + + float fScaleA = fScale; + if (Language_IsAsian() && fScale > 0.7f ) + { + fScaleA = fScale * 0.75f; + } + + while(*psText) + { + int iAdvanceCount; + unsigned int uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL ); + psText += iAdvanceCount; + + if (uiLetter == '^' ) + { + if (*psText >= '0' && + *psText <= '9') + { + uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL ); + psText += iAdvanceCount; + continue; + } + } + + if (uiLetter == 0x0A) + { + iThisWidth = 0; + } + else if (uiLetter >= XB_GLYPH_A && uiLetter <= XB_GLYPH_Y) + { + // These are all ALWAYS drawn at 24x24 + iThisWidth += 24; + if (iThisWidth > iMaxWidth) + iMaxWidth = iThisWidth; + } + else + { + int iPixelAdvance = curfont->GetLetterHorizAdvance( uiLetter ); + + float fValue = iPixelAdvance * ((uiLetter > g_iNonScaledCharRange) ? fScaleA : fScale); + iThisWidth += curfont->mbRoundCalcs ? Round( fValue ) : fValue; + if (iThisWidth > iMaxWidth) + { + iMaxWidth = iThisWidth; + } + } + } + + return iMaxWidth; +} + +// not really a font function, but keeps naming consistant... +// +int RE_Font_StrLenChars(const char *psText) +{ + // logic for this function's letter counting must be kept same in this function and RE_Font_DrawString() + // + int iCharCount = 0; + + while ( *psText ) + { + // in other words, colour codes and CR/LF don't count as chars, all else does... + // + int iAdvanceCount; + unsigned int uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL ); + psText += iAdvanceCount; + + switch (uiLetter) + { + case '^': + if (*psText >= '0' && + *psText <= '9') + { + psText++; + } + else + { + iCharCount++; + } + break; // colour code (note next-char skip) + case 10: break; // linefeed + case 13: break; // return + case '_': iCharCount += (GetLanguageEnum() == eThai && (((unsigned char *)psText)[0] >= TIS_GLYPHS_START))?0:1; break; // special word-break hack + default: iCharCount++; break; + } + } + + return iCharCount; +} + +int RE_Font_HeightPixels(const int iFontHandle, const float fScale) +{ + CFontInfo *curfont; + + curfont = GetFont(iFontHandle); + if(curfont) + { + float fValue = curfont->GetPointSize() * fScale; + return curfont->mbRoundCalcs ? Round(fValue) : fValue; + } + return(0); +} + +// iMaxPixelWidth is -1 for "all of string", else pixel display count... +// +void RE_Font_DrawString(int ox, int oy, const char *psText, const float *rgba, const int iFontHandle, int iMaxPixelWidth, const float fScale) +{ + static qboolean gbInShadow = qfalse; // MUST default to this + int x, y, colour, offset; + const glyphInfo_t *pLetter; + qhandle_t hShader; + + assert (psText); + + if(iFontHandle & STYLE_BLINK) + { + if((Sys_Milliseconds() >> 7) & 1) + { + return; + } + } + + CFontInfo *curfont = GetFont(iFontHandle); + if(!curfont || !psText) + { + return; + } + + float fScaleA = fScale; + int iAsianYAdjust = 0; + if (Language_IsAsian() && fScale > 0.7f) + { + fScaleA = fScale * 0.75f; + iAsianYAdjust = /*Round*/((((float)curfont->GetPointSize() * fScale) - ((float)curfont->GetPointSize() * fScaleA))/2); + } + + // Draw a dropshadow if required + if(iFontHandle & STYLE_DROPSHADOW) + { + offset = Round(curfont->GetPointSize() * fScale * 0.075f); + + static const vec4_t v4DKGREY2 = {0.15f, 0.15f, 0.15f, 1}; + + gbInShadow = qtrue; + RE_Font_DrawString(ox + offset, oy + offset, psText, v4DKGREY2, iFontHandle & SET_MASK, iMaxPixelWidth, fScale); + gbInShadow = qfalse; + } + + RE_SetColor( rgba ); + + x = ox; + oy += Round((curfont->GetHeight() - (curfont->GetDescender() >> 1)) * fScale); + + qboolean bNextTextWouldOverflow = qfalse; + while (*psText && !bNextTextWouldOverflow) + { + int iAdvanceCount; + unsigned int uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL ); + psText += iAdvanceCount; + + switch( uiLetter ) + { + case 10: //linefeed + x = ox; + oy += Round(curfont->GetPointSize() * fScale); + if (Language_IsAsian()) + { + oy += 4; // this only comes into effect when playing in asian for "A long time ago in a galaxy" etc, all other text is line-broken in feeder functions + } + break; + case 13: // Return + break; + case 32: // Space + pLetter = curfont->GetLetter(' '); + x += Round(pLetter->horizAdvance * fScale); + bNextTextWouldOverflow = ( iMaxPixelWidth != -1 && ((x-ox)>iMaxPixelWidth) ) ? qtrue : qfalse; // yeuch + break; + case XB_GLYPH_A: + case XB_GLYPH_B: + case XB_GLYPH_W: + case XB_GLYPH_X: + case XB_GLYPH_Y: + bNextTextWouldOverflow = ( iMaxPixelWidth != -1 && (((x+24)-ox)>iMaxPixelWidth) ) ? qtrue : qfalse; + if( !bNextTextWouldOverflow) + { + y = oy - 16; + + hShader = RE_RegisterShaderNoMip( xbGlyphShaders[uiLetter - XB_GLYPH_A] ); + +#ifdef _XBOX + extern int Menus_AnyFullScreenVisible(); + if(glw_state->isWidescreen && cls.state == CA_ACTIVE && !(Menus_AnyFullScreenVisible())) + x += 40; +#endif + RE_StretchPic ( x, // float x + y, // float y + 24.0f, // float w + 24.0f, // float h + 0.0f, // float s1 + 0.0f, // float t1 + 1.0f, // float s2 + 1.0f, // float t2 + //lastcolour.c, + hShader // qhandle_t hShader + ); +#ifdef _XBOX + if(glw_state->isWidescreen && cls.state == CA_ACTIVE && !(Menus_AnyFullScreenVisible())) + x -= 40; +#endif + + x += 24; + } + break; + case '_': // has a special word-break usage if in Thai (and followed by a thai char), and should not be displayed, else treat as normal + if (GetLanguageEnum()== eThai && ((unsigned char *)psText)[0] >= TIS_GLYPHS_START) + { + break; + } + // else drop through and display as normal... + case '^': + if (uiLetter != '_') // necessary because of fallthrough above + { + if (*psText >= '0' && + *psText <= '9') + { + colour = ColorIndex(*psText++); + if (!gbInShadow) + { + RE_SetColor( g_color_table[colour] ); + } + break; + } + } + //purposely falls thrugh + default: + pLetter = curfont->GetLetter( uiLetter, &hShader ); // Description of pLetter + if(!pLetter->width) + { + pLetter = curfont->GetLetter('.'); + } + + float fThisScale = uiLetter > g_iNonScaledCharRange ? fScaleA : fScale; + + // sigh, super-language-specific hack... + // + if (uiLetter == TIS_SARA_AM && GetLanguageEnum() == eThai) + { + x -= Round(7 * fThisScale); + } + + int iAdvancePixels = Round(pLetter->horizAdvance * fThisScale); + bNextTextWouldOverflow = ( iMaxPixelWidth != -1 && (((x+iAdvancePixels)-ox)>iMaxPixelWidth) ) ? qtrue : qfalse; // yeuch + if (!bNextTextWouldOverflow) + { + // this 'mbRoundCalcs' stuff is crap, but the only way to make the font code work. Sigh... + // + y = oy - (curfont->mbRoundCalcs ? Round(pLetter->baseline * fThisScale) : pLetter->baseline * fThisScale); + if (curfont->m_fAltSBCSFontScaleFactor != -1) + { + y+=3; // I'm sick and tired of going round in circles trying to do this legally, so bollocks to it + } + +#ifdef _XBOX + extern int Menus_AnyFullScreenVisible(); + if(glw_state->isWidescreen && cls.state == CA_ACTIVE && !(Menus_AnyFullScreenVisible())) + RE_StretchPic ( x + Round(pLetter->horizOffset * fScale) + 40, // float x + (uiLetter > g_iNonScaledCharRange) ? y - iAsianYAdjust : y, // float y + curfont->mbRoundCalcs ? Round(pLetter->width * fThisScale) : pLetter->width * fThisScale, // float w + curfont->mbRoundCalcs ? Round(pLetter->height * fThisScale) : pLetter->height * fThisScale, // float h + pLetter->s, // float s1 + pLetter->t, // float t1 + pLetter->s2, // float s2 + pLetter->t2, // float t2 + //lastcolour.c, + hShader // qhandle_t hShader + ); + else +#endif + RE_StretchPic ( x + Round(pLetter->horizOffset * fScale), // float x + (uiLetter > g_iNonScaledCharRange) ? y - iAsianYAdjust : y, // float y + curfont->mbRoundCalcs ? Round(pLetter->width * fThisScale) : pLetter->width * fThisScale, // float w + curfont->mbRoundCalcs ? Round(pLetter->height * fThisScale) : pLetter->height * fThisScale, // float h + pLetter->s, // float s1 + pLetter->t, // float t1 + pLetter->s2, // float s2 + pLetter->t2, // float t2 + //lastcolour.c, + hShader // qhandle_t hShader + ); + + x += iAdvancePixels; + } + break; + } + } + //let it remember the old color //RE_SetColor(NULL);; +} + +int RE_RegisterFont(const char *psName) +{ + FontIndexMap_t::iterator it = g_mapFontIndexes.find(psName); + if (it != g_mapFontIndexes.end() ) + { + int iFontIndex = (*it).second; + return iFontIndex; + } + + // Hack. Rather than fix our bazillion menu files, we allow + // dummy fonts that don't register, just use up an index. + if( Q_stricmp(psName, "NOFONT") == 0 ) + { + g_iCurrentFontIndex++; + g_mapFontIndexes[psName] = 0; + return 0; + } + else + // not registered, so... + // + { + CFontInfo *pFont = new CFontInfo(psName); + if (pFont->GetPointSize() > 0) + { + int iFontIndex = g_iCurrentFontIndex - 1; + g_mapFontIndexes[psName] = iFontIndex; + pFont->m_iThisFont = iFontIndex; + return iFontIndex; + } + else + { + g_mapFontIndexes[psName] = 0; // missing/invalid + } + } + + return 0; +} + +void R_InitFonts(void) +{ + g_iCurrentFontIndex = 1; // entry 0 is reserved for "missing/invalid" + g_iNonScaledCharRange = INT_MAX; // default all chars to have no special scaling (other than user supplied) +} + +void R_ShutdownFonts(void) +{ + for(int i = 1; i < g_iCurrentFontIndex; i++) // entry 0 is reserved for "missing/invalid" + { + delete g_vFontArray[i]; + } + g_mapFontIndexes.clear(); + g_vFontArray.clear(); + g_iCurrentFontIndex = 1; // entry 0 is reserved for "missing/invalid" + + g_ThaiCodes.Clear(); +} + +// this is only really for debugging while tinkering with fonts, but harmless to leave in... +// +void R_ReloadFonts_f(void) +{ + // first, grab all the currently-registered fonts IN THE ORDER THEY WERE REGISTERED... + // + vector vstrFonts; + + for (int iFontToFind = 1; iFontToFind < g_iCurrentFontIndex; iFontToFind++) + { + for (FontIndexMap_t::iterator it = g_mapFontIndexes.begin(); it != g_mapFontIndexes.end(); ++it) + { + if (iFontToFind == (*it).second) + { + vstrFonts.push_back( (*it).first ); + break; + } + } + if ( it == g_mapFontIndexes.end() ) + { + break; // couldn't find this font + } + } + if ( iFontToFind == g_iCurrentFontIndex ) // found all of them? + { + // now restart the font system... + // + R_ShutdownFonts(); + R_InitFonts(); + // + // and re-register our fonts in the same order as before (note that some menu items etc cache the string lengths so really a vid_restart is better, but this is just for my testing) + // + for (int iFont = 0; iFont < vstrFonts.size(); iFont++) + { +#ifdef _DEBUG + int iNewFontHandle = RE_RegisterFont( vstrFonts[iFont].c_str() ); + assert( iNewFontHandle == iFont+1 ); +#else + RE_RegisterFont( vstrFonts[iFont].c_str() ); +#endif + } + Com_Printf( "Done.\n" ); + } + else + { + Com_Printf( "Problem encountered finding current fonts, ignoring.\n" ); // poo. Oh well, forget it. + } +} + + +// end diff --git a/code/renderer/tr_font.h b/code/renderer/tr_font.h new file mode 100644 index 0000000..e078783 --- /dev/null +++ b/code/renderer/tr_font.h @@ -0,0 +1,34 @@ +// Filename:- tr_font.h +// +// font support + +// This file is shared in the single and multiplayer codebases, so be CAREFUL WHAT YOU ADD/CHANGE!!!!! + +#ifndef TR_FONT_H +#define TR_FONT_H + + +void R_ShutdownFonts(void); +void R_InitFonts(void); +int RE_RegisterFont(const char *psName); +int RE_Font_StrLenPixels(const char *psText, const int iFontHandle, const float fScale = 1.0f); +int RE_Font_StrLenChars(const char *psText); +int RE_Font_HeightPixels(const int iFontHandle, const float fScale = 1.0f); +void RE_Font_DrawString(int ox, int oy, const char *psText, const float *rgba, const int iFontHandle, int iMaxPixelWidth, const float fScale = 1.0f); + +// Dammit, I can't use this more elegant form because of !@#@!$%% VM code... (can't alter passed in ptrs, only contents of) +// +//unsigned int AnyLanguage_ReadCharFromString( const char **ppsText, qboolean *pbIsTrailingPunctuation = NULL); +// +// so instead we have to use this messier method... +// +unsigned int AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation = NULL); + +qboolean Language_IsAsian(void); +qboolean Language_UsesSpaces(void); + + +#endif // #ifndef TR_FONT_H + +// end + diff --git a/code/renderer/tr_ghoul2.cpp b/code/renderer/tr_ghoul2.cpp new file mode 100644 index 0000000..766a653 --- /dev/null +++ b/code/renderer/tr_ghoul2.cpp @@ -0,0 +1,4906 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "../client/client.h" //FIXME!! EVIL - just include the definitions needed +#include "../client/vmachine.h" + +#ifdef _XBOX +#include "../qcommon/miniheap.h" +#endif + +#if !defined(TR_LOCAL_H) + #include "tr_local.h" +#endif + +#include "MatComp.h" +#if !defined(_QCOMMON_H_) + #include "../qcommon/qcommon.h" +#endif +#if !defined(G2_H_INC) + #include "../ghoul2/G2.h" +#endif + +#ifdef _G2_GORE +#include "../ghoul2/ghoul2_gore.h" +#endif + +#ifdef VV_LIGHTING +#include "../win32/glw_win_dx8.h" +#include "tr_lightmanager.h" +#endif + +#define LL(x) x=LittleLong(x) + +#ifdef G2_PERFORMANCE_ANALYSIS +#include "../qcommon/timing.h" +timing_c G2PerformanceTimer_RB_SurfaceGhoul; + +int G2PerformanceCounter_G2_TransformGhoulBones = 0; + +int G2Time_RB_SurfaceGhoul = 0; + +void G2Time_ResetTimers(void) +{ + G2Time_RB_SurfaceGhoul = 0; + G2PerformanceCounter_G2_TransformGhoulBones = 0; +} + +void G2Time_ReportTimers(void) +{ + Com_Printf("\n---------------------------------\nRB_SurfaceGhoul: %i\nTransformGhoulBones calls: %i\n---------------------------------\n\n", + G2Time_RB_SurfaceGhoul, + G2PerformanceCounter_G2_TransformGhoulBones + ); +} +#endif + +//rww - RAGDOLL_BEGIN +#include +//rww - RAGDOLL_END + +extern cvar_t *r_Ghoul2UnSqash; +extern cvar_t *r_Ghoul2AnimSmooth; +extern cvar_t *r_Ghoul2NoLerp; +extern cvar_t *r_Ghoul2NoBlend; +extern cvar_t *r_Ghoul2UnSqashAfterSmooth; + +bool HackadelicOnClient=false; // means this is a render traversal + +// I hate doing this, but this is the simplest way to get this into the routines it needs to be +mdxaBone_t worldMatrix; +mdxaBone_t worldMatrixInv; +#ifdef _G2_GORE +qhandle_t goreShader=-1; +#endif + +const static mdxaBone_t identityMatrix = +{ + 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f +}; + +class CTransformBone +{ +public: +#ifdef _XBOX + float renderMatrix[16]; +#endif + //rww - RAGDOLL_BEGIN + int touchRender; + //rww - RAGDOLL_END + mdxaBone_t boneMatrix; //final matrix + int parent; // only set once + int touch; // for minimal recalculation +#ifdef _XBOX + // This shouldn't be done like this. use declspec(aligned)?! + int pad[1]; // must be 16-byte aligned! +#endif + CTransformBone() + { + touch=0; + //rww - RAGDOLL_BEGIN + touchRender = 0; + //rww - RAGDOLL_END + } + +}; + +struct SBoneCalc +{ + int newFrame; + int currentFrame; + float backlerp; + float blendFrame; + int blendOldFrame; + bool blendMode; + float blendLerp; +}; + +class CBoneCache; +void G2_TransformBone(int index,CBoneCache &CB); + +class CBoneCache +{ + void SetRenderMatrix(CTransformBone *bone) + { +#ifdef _XBOX + float *src = bone->boneMatrix.matrix[0]; + float *dst = bone->renderMatrix; + + dst[0] = src[0]; + dst[1] = src[4]; + dst[2] = src[8]; + dst[3] = 0; + + dst[4] = src[1]; + dst[5] = src[5]; + dst[6] = src[9]; + dst[7] = 0; + + dst[8] = src[2]; + dst[9] = src[6]; + dst[10] = src[10]; + dst[11] = 0; + + dst[12] = src[3]; + dst[13] = src[7]; + dst[14] = src[11]; + dst[15] = 1; +#endif + } + + void EvalLow(int index) + { + assert(index>=0&&index=0&&mFinalBones[index].parent=0) + { + EvalLow(mFinalBones[index].parent); // make sure parent is evaluated + SBoneCalc &par=mBones[mFinalBones[index].parent]; + mBones[index].newFrame=par.newFrame; + mBones[index].currentFrame=par.currentFrame; + mBones[index].backlerp=par.backlerp; + mBones[index].blendFrame=par.blendFrame; + mBones[index].blendOldFrame=par.blendOldFrame; + mBones[index].blendMode=par.blendMode; + mBones[index].blendLerp=par.blendLerp; + } + G2_TransformBone(index,*this); + SetRenderMatrix(mFinalBones + index); + mFinalBones[index].touch=mCurrentTouch; + } + } +//rww - RAGDOLL_BEGIN + void SmoothLow(int index) + { + if (mSmoothBones[index].touch==mLastTouch) + { + int i; + float *oldM=&mSmoothBones[index].boneMatrix.matrix[0][0]; + float *newM=&mFinalBones[index].boneMatrix.matrix[0][0]; + for (i=0;i<12;i++,oldM++,newM++) + { + *oldM=mSmoothFactor*(*oldM-*newM)+*newM; + } + } + else + { + memcpy(&mSmoothBones[index].boneMatrix,&mFinalBones[index].boneMatrix,sizeof(mdxaBone_t)); + } + mdxaSkelOffsets_t *offsets = (mdxaSkelOffsets_t *)((byte *)header + sizeof(mdxaHeader_t)); + mdxaSkel_t *skel = (mdxaSkel_t *)((byte *)header + sizeof(mdxaHeader_t) + offsets->offsets[index]); + mdxaBone_t tempMatrix; + Multiply_3x4Matrix(&tempMatrix,&mSmoothBones[index].boneMatrix, &skel->BasePoseMat); + float maxl; + maxl=VectorLength(&skel->BasePoseMat.matrix[0][0]); + VectorNormalize(&tempMatrix.matrix[0][0]); + VectorNormalize(&tempMatrix.matrix[1][0]); + VectorNormalize(&tempMatrix.matrix[2][0]); + + VectorScale(&tempMatrix.matrix[0][0],maxl,&tempMatrix.matrix[0][0]); + VectorScale(&tempMatrix.matrix[1][0],maxl,&tempMatrix.matrix[1][0]); + VectorScale(&tempMatrix.matrix[2][0],maxl,&tempMatrix.matrix[2][0]); + Multiply_3x4Matrix(&mSmoothBones[index].boneMatrix,&tempMatrix,&skel->BasePoseMatInv); + // Added by BTO (VV) - I hope this is right. + SetRenderMatrix(mSmoothBones + index); + mSmoothBones[index].touch=mCurrentTouch; +#ifdef _DEBUG + for ( int i = 0; i < 3; i++ ) + { + for ( int j = 0; j < 4; j++ ) + { + assert( !_isnan(mSmoothBones[index].boneMatrix.matrix[i][j])); + } + } +#endif// _DEBUG + } +//rww - RAGDOLL_END +public: + int frameSize; + const mdxaHeader_t *header; + const model_t *mod; + + // these are split for better cpu cache behavior + SBoneCalc *mBones; + CTransformBone *mFinalBones; + + CTransformBone *mSmoothBones; // for render smoothing + mdxaSkel_t **mSkels; + + int mNumBones; + + boneInfo_v *rootBoneList; + mdxaBone_t rootMatrix; + int incomingTime; + + int mCurrentTouch; + //rww - RAGDOLL_BEGIN + int mCurrentTouchRender; + int mLastTouch; + int mLastLastTouch; + //rww - RAGDOLL_END + + // for render smoothing + bool mSmoothingActive; + bool mUnsquash; + float mSmoothFactor; +// int mWraithID; // this is just used for debug prints, can use it for any int of interest in JK2 + + CBoneCache(const model_t *amod,const mdxaHeader_t *aheader) : + mod(amod), + header(aheader) + { + assert(amod); + assert(aheader); + mSmoothingActive=false; + mUnsquash=false; + mSmoothFactor=0.0f; + + mNumBones = header->numBones; + mBones = new SBoneCalc[mNumBones]; + mFinalBones = (CTransformBone*)Z_Malloc(sizeof(CTransformBone) * mNumBones, TAG_GHOUL2, qtrue, 16); + mSmoothBones = (CTransformBone*)Z_Malloc(sizeof(CTransformBone) * mNumBones, TAG_GHOUL2, qtrue, 16); + mSkels = new mdxaSkel_t*[mNumBones]; + mdxaSkelOffsets_t *offsets; + mdxaSkel_t *skel; + offsets = (mdxaSkelOffsets_t *)((byte *)header + sizeof(mdxaHeader_t)); + + int i; + for (i=0;ioffsets[i]); + mSkels[i]=skel; + mFinalBones[i].parent=skel->parent; + } + mCurrentTouch=3; +//rww - RAGDOLL_BEGIN + mLastTouch=2; + mLastLastTouch=1; +//rww - RAGDOLL_END + } + ~CBoneCache () + { + delete [] mBones; + // Alignment + Z_Free(mFinalBones); + Z_Free(mSmoothBones); + delete [] mSkels; + } + + SBoneCalc &Root() + { + assert(mNumBones); + return mBones[0]; + } + const mdxaBone_t &EvalUnsmooth(int index) + { + EvalLow(index); + if (mSmoothingActive&&mSmoothBones[index].touch) + { + return mSmoothBones[index].boneMatrix; + } + return mFinalBones[index].boneMatrix; + } + const mdxaBone_t &Eval(int index) + { + /* + bool wasEval=EvalLow(index); + if (mSmoothingActive) + { + if (mSmoothBones[index].touch!=incomingTime||wasEval) + { + float dif=float(incomingTime)-float(mSmoothBones[index].touch); + if (mSmoothBones[index].touch&&dif<300.0f) + { + + if (dif<16.0f) // 60 fps + { + dif=16.0f; + } + if (dif>100.0f) // 10 fps + { + dif=100.0f; + } + float f=1.0f-pow(1.0f-mSmoothFactor,16.0f/dif); + + int i; + float *oldM=&mSmoothBones[index].boneMatrix.matrix[0][0]; + float *newM=&mFinalBones[index].boneMatrix.matrix[0][0]; + for (i=0;i<12;i++,oldM++,newM++) + { + *oldM=f*(*oldM-*newM)+*newM; + } + if (mUnsquash) + { + mdxaBone_t tempMatrix; + Multiply_3x4Matrix(&tempMatrix,&mSmoothBones[index].boneMatrix, &mSkels[index]->BasePoseMat); + float maxl; + maxl=VectorLength(&mSkels[index]->BasePoseMat.matrix[0][0]); + VectorNormalizeFast(&tempMatrix.matrix[0][0]); + VectorNormalizeFast(&tempMatrix.matrix[1][0]); + VectorNormalizeFast(&tempMatrix.matrix[2][0]); + + VectorScale(&tempMatrix.matrix[0][0],maxl,&tempMatrix.matrix[0][0]); + VectorScale(&tempMatrix.matrix[1][0],maxl,&tempMatrix.matrix[1][0]); + VectorScale(&tempMatrix.matrix[2][0],maxl,&tempMatrix.matrix[2][0]); + Multiply_3x4Matrix(&mSmoothBones[index].boneMatrix,&tempMatrix,&mSkels[index]->BasePoseMatInv); + } + } + else + { + memcpy(&mSmoothBones[index].boneMatrix,&mFinalBones[index].boneMatrix,sizeof(mdxaBone_t)); + } + SetRenderMatrix(mSmoothBones + index); + mSmoothBones[index].touch=incomingTime; + } + return mSmoothBones[index].boneMatrix; + } + return mFinalBones[index].boneMatrix; + */ + //all above is not necessary, smoothing is taken care of when we want to use smoothlow (only when evalrender) + assert(index>=0&&index=0&&index=0&&index=0&&indexBoneWeightings[iWeightNum]; + + iTemp|= (pVert->uiNmWeightsAndBoneIndexes >> (iG2_BONEWEIGHT_TOPBITS_SHIFT+(iWeightNum*2)) ) & iG2_BONEWEIGHT_TOPBITS_AND; + + fBoneWeight = fG2_BONEWEIGHT_RECIPROCAL_MULT * iTemp; + + return fBoneWeight; +} + +#ifdef _XBOX + +static inline void VertTransform(float *out_vert, const float *mat, const float *in_vert) +{ + __asm + { + push ESI + push EDI + push EAX + + mov ESI, in_vert + mov EDI, out_vert + mov EAX, mat + + movaps XMM4, [EAX + 0] // Load matrix columns + movaps XMM5, [EAX + 16] + movaps XMM6, [EAX + 32] + movaps XMM7, [EAX + 48] + + movss XMM0, [ESI + 0] // Compute x * column 0 + shufps XMM0, XMM0, 0x0 + mulps XMM0, XMM4 + + movss XMM1, [ESI + 4] // Compute y * column 1 + shufps XMM1, XMM1, 0x0 + mulps XMM1, XMM5 + + movss XMM2, [ESI + 8] // Compute z * column 2 + shufps XMM2, XMM2, 0x0 + mulps XMM2, XMM6 + + addps XMM0, XMM1 // Add dot products + addps XMM0, XMM2 + addps XMM0, XMM7 // Add translation + + movaps [EDI], XMM0 // Store result + + pop EAX + pop EDI + pop ESI + } +} + +static inline void VertTransformSR(float *out_vert, const float *mat, const float *in_vert) +{ + __asm + { + push ESI + push EDI + push EAX + + mov ESI, in_vert + mov EDI, out_vert + mov EAX, mat + + movaps XMM4, [EAX + 0] // Load matrix columns + movaps XMM5, [EAX + 16] + movaps XMM6, [EAX + 32] + + movss XMM0, [ESI + 0] // Compute x * column 0 + shufps XMM0, XMM0, 0x0 + mulps XMM0, XMM4 + + movss XMM1, [ESI + 4] // Compute y * column 1 + shufps XMM1, XMM1, 0x0 + mulps XMM1, XMM5 + + movss XMM2, [ESI + 8] // Compute z * column 2 + shufps XMM2, XMM2, 0x0 + mulps XMM2, XMM6 + + addps XMM0, XMM1 // Add dot products + addps XMM0, XMM2 + + movaps [EDI], XMM0 // Store result + + pop EAX + pop EDI + pop ESI + } +} + +static inline void VertTransformWeighted(float *out_vert, const float *mat, const float *in_vert, const float *weight) +{ + __asm + { + push ESI + push EDI + push EAX + push EDX + + mov ESI, in_vert + mov EDI, out_vert + mov EAX, mat + mov EDX, weight + + movaps XMM4, [EAX + 0] // Load matrix columns + movaps XMM5, [EAX + 16] + movaps XMM6, [EAX + 32] + movaps XMM7, [EAX + 48] + + movss XMM0, [ESI + 0] // Compute x * column 0 + shufps XMM0, XMM0, 0x0 + mulps XMM0, XMM4 + + movss XMM1, [ESI + 4] // Compute y * column 1 + shufps XMM1, XMM1, 0x0 + mulps XMM1, XMM5 + + movss XMM2, [ESI + 8] // Compute z * column 2 + shufps XMM2, XMM2, 0x0 + mulps XMM2, XMM6 + + addps XMM0, XMM1 // Add dot products + addps XMM0, XMM2 + addps XMM0, XMM7 // Add translation + + movss XMM4, [EDX] // Weight the resulting vector + shufps XMM4, XMM4, 0x0 + mulps XMM0, XMM4 + + movaps XMM5, [EDI] // Add the weighted vector to the current + addps XMM0, XMM5 + + movaps [EDI], XMM0 // Store result + + pop EDX + pop EAX + pop EDI + pop ESI + } +} + +static inline void VertTransformSRWeighted(float *out_vert, const float *mat, const float *in_vert, const float *weight) +{ + __asm + { + push ESI + push EDI + push EAX + push EDX + + mov ESI, in_vert + mov EDI, out_vert + mov EAX, mat + mov EDX, weight + + movaps XMM4, [EAX + 0] // Load matrix columns + movaps XMM5, [EAX + 16] + movaps XMM6, [EAX + 32] + + movss XMM0, [ESI + 0] // Compute x * column 0 + shufps XMM0, XMM0, 0x0 + mulps XMM0, XMM4 + + movss XMM1, [ESI + 4] // Compute y * column 1 + shufps XMM1, XMM1, 0x0 + mulps XMM1, XMM5 + + movss XMM2, [ESI + 8] // Compute z * column 2 + shufps XMM2, XMM2, 0x0 + mulps XMM2, XMM6 + + addps XMM0, XMM1 // Add dot products + addps XMM0, XMM2 + + movss XMM7, [EDX] // Weight the resulting vector + shufps XMM7, XMM7, 0x0 + mulps XMM0, XMM7 + + movaps XMM4, [EDI] // Add the weighted vector to the current + addps XMM0, XMM4 + + movaps [EDI], XMM0 // Store result + + pop EDX + pop EAX + pop EDI + pop ESI + } +} + +static void TransformRenderSurface(const mdxmSurface_t *surf, CBoneCache *bones, shaderCommands_t *out) +{ + const int *boneRefs = (int*) ((byte*)surf + surf->ofsBoneReferences); + int numVerts = surf->numVerts; + const mdxmVertex_t *vert = (mdxmVertex_t *) ((byte *)surf + surf->ofsVerts); + const mdxmVertexTexCoord_t *texCoord = (mdxmVertexTexCoord_t *) &vert[numVerts]; + + int boneIndex = -1; + const float *bone = NULL; + + int baseVert = out->numVertexes; + + while(numVerts--) + { + __declspec (align(16)) vec4_t vec; + __declspec (align(16)) vec4_t nrm; + +#ifdef _XBOX + __declspec (align(16)) vec4_t tan; + + Q_CastShort2FloatScale(&vec[0], &vert->vertCoords[0], 1.f / GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert->vertCoords[1], 1.f / GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert->vertCoords[2], 1.f / GLM_COMP_SIZE); + + if(tess.shader->needsNormal || tess.dlightBits) + { + nrm[0] = (((vert->normal & 0x00FF0000) >> 16) - 128.f) / 127.0f; + nrm[1] = (((vert->normal & 0x0000FF00) >> 8) - 128.f) / 127.0f; + nrm[2] = (((vert->normal & 0x000000FF) >> 0) - 128.f) / 127.0f; + } + + if(tess.shader->needsTangent || tess.dlightBits) + { + tan[0] = (((vert->tangent & 0x00FF0000) >> 16) - 128.f) / 127.0f; + tan[1] = (((vert->tangent & 0x0000FF00) >> 8) - 128.f) / 127.0f; + tan[2] = (((vert->tangent & 0x000000FF) >> 0) - 128.f) / 127.0f; + + out->setTangents = true; + } +#else + VectorCopy(vert->vertCoords, vec); + VectorCopy(vert->normal, nrm); +#endif + + const int numWeights = G2_GetVertWeights( vert ); + + if (numWeights == 1) + { + // Slightly faster single weight path + int index = G2_GetVertBoneIndex( vert, 0 ); + + if ( index != boneIndex ) + { + CTransformBone *tbone = bones->EvalFull(boneRefs[index]); + bone = tbone->renderMatrix; + boneIndex = index; + } + + VertTransform(out->xyz[baseVert], bone, vec); +#ifdef _XBOX + if(tess.shader->needsNormal || tess.dlightBits) + VertTransformSR(out->normal[baseVert], bone, nrm); + + if(tess.shader->needsTangent || tess.dlightBits) + VertTransformSR(out->tangent[baseVert], bone, tan); +#else + VertTransformSR(out->normal[baseVert], bone, nrm); +#endif + } + else + { + // Multi-weight blending path + VectorClear( out->xyz[baseVert] ); + + // Special case for first weight, as it's the only one we use for the normals + boneIndex = G2_GetVertBoneIndex( vert, 0 ); + CTransformBone *tbone = bones->EvalFull(boneRefs[boneIndex]); + bone = tbone->renderMatrix; + + __declspec (align(16)) float weight = G2_GetVertBoneWeightNotSlow( vert, 0 ); + + VertTransformWeighted(out->xyz[baseVert], bone, vec, &weight); +#ifdef _XBOX + if(tess.shader->needsNormal || tess.dlightBits) + VertTransformSR(out->normal[baseVert], bone, nrm); + + if(tess.shader->needsTangent || tess.dlightBits) + VertTransformSR(out->tangent[baseVert], bone, tan); +#else + VertTransformSR(out->normal[baseVert], bone, nrm); +#endif + + for (int k = 1; k < numWeights; ++k) + { + boneIndex = G2_GetVertBoneIndex( vert, k ); + + tbone = bones->EvalFull(boneRefs[boneIndex]); + bone = tbone->renderMatrix; + + weight = G2_GetVertBoneWeightNotSlow( vert, k ); + + VertTransformWeighted(out->xyz[baseVert], bone, vec, &weight); + } + } + +#ifdef _XBOX + if(tess.shader != tr.shadowShader) { + // Don't need tex coords if doing a shadow + Q_CastShort2FloatScale(&out->texCoords[baseVert][0][0], &texCoord->texCoords[0], 1.f / GLM_COMP_UV_SIZE); + Q_CastShort2FloatScale(&out->texCoords[baseVert][0][1], &texCoord->texCoords[1], 1.f / GLM_COMP_UV_SIZE); + } +#else + out->texCoords[baseVert][0][0] = texCoord->texCoords[0]; + out->texCoords[baseVert][0][1] = texCoord->texCoords[1]; +#endif + + ++vert; + ++texCoord; + ++baseVert; + } + + // VVFIXME - BTO - commented this out, as it's still being done in SurfaceGhoul now. + // Really, I ought to move the Gore surfacing in here. +// out->numVertexes += surf->numVerts; +} + +static void TransformCollideSurface(const mdxmSurface_t *surf, CBoneCache *bones, vec3_t scale, float *out) +{ + const int *boneRefs = (int*) ((byte*)surf + surf->ofsBoneReferences); + int numVerts = surf->numVerts; + const mdxmVertex_t *vert = (mdxmVertex_t *) ((byte *)surf + surf->ofsVerts); + const mdxmVertexTexCoord_t *texCoord = (mdxmVertexTexCoord_t *) &vert[numVerts]; + + int boneIndex = -1; + const float *bone = NULL; + +#ifdef _XBOX + vec3_t scl; + scl[0] = scale[0] * 1.f / GLM_COMP_SIZE; + scl[1] = scale[1] * 1.f / GLM_COMP_SIZE; + scl[2] = scale[2] * 1.f / GLM_COMP_SIZE; +#endif + + while(numVerts--) + { + __declspec (align(16)) vec4_t vec; + +#ifdef _XBOX + Q_CastShort2FloatScale(&vec[0], &vert->vertCoords[0], scl[0]); + Q_CastShort2FloatScale(&vec[1], &vert->vertCoords[1], scl[1]); + Q_CastShort2FloatScale(&vec[2], &vert->vertCoords[2], scl[2]); +#else + VectorCopy(vert->vertCoords, vec); +#endif + + const int numWeights = G2_GetVertWeights( vert ); + + if (numWeights == 1) + { + // Slightly faster single weight path + int index = G2_GetVertBoneIndex( vert, 0 ); + + if ( index != boneIndex ) + { + CTransformBone *tbone = bones->EvalFull(boneRefs[index]); + bone = tbone->renderMatrix; + boneIndex = index; + } + + __declspec (align(16)) vec4_t temp; + + VertTransform(temp, bone, vec); + + out[0] = temp[0]; + out[1] = temp[1]; + out[2] = temp[2]; + } + else + { + // Multi-weight blending path + float totalWeight = 0.0f; + + __declspec (align(16)) vec4_t temp; + temp[0] = 0; + temp[1] = 0; + temp[2] = 0; + + for (int k = 0; k < numWeights; ++k) + { + boneIndex = G2_GetVertBoneIndex( vert, k ); + + CTransformBone *tbone = bones->EvalFull(boneRefs[boneIndex]); + bone = tbone->renderMatrix; + + __declspec (align(16)) float weight = + G2_GetVertBoneWeight( vert, k, totalWeight, numWeights ); + + VertTransformWeighted(temp, bone, vec, &weight); + } + + out[0] = temp[0]; + out[1] = temp[1]; + out[2] = temp[2]; + } + +#ifdef _XBOX + Q_CastShort2FloatScale(out + 3, &texCoord->texCoords[0], 1.f / GLM_COMP_UV_SIZE); + Q_CastShort2FloatScale(out + 4, &texCoord->texCoords[1], 1.f / GLM_COMP_UV_SIZE); +#else + out[3] = texCoord->texCoords[0]; + out[4] = texCoord->texCoords[1]; +#endif + + ++vert; + ++texCoord; + out += 5; + } +} + +void R_TransformEachSurface( const mdxmSurface_t *surface, vec3_t scale, CMiniHeap *G2VertSpace, int *TransformedVertsArray,CBoneCache *boneCache) +{ + float *TransformedVerts; + + // alloc some space for the transformed verts to get put in + TransformedVerts = (float *)G2VertSpace->MiniHeapAlloc(surface->numVerts * 5 * 4); + TransformedVertsArray[surface->thisSurfaceIndex] = (int)TransformedVerts; + if (!TransformedVerts) + { + assert(0); + Com_Error(ERR_DROP, "Ran out of transform space for Ghoul2 Models. Adjust MiniHeapSize in SV_SpawnServer.\n"); + } + + TransformCollideSurface(surface, boneCache, scale, TransformedVerts); +} + +#endif + +//rww - RAGDOLL_BEGIN +const mdxaHeader_t *G2_GetModA(CGhoul2Info &ghoul2) +{ + if (!ghoul2.mBoneCache) + { + return 0; + } + + CBoneCache &boneCache=*ghoul2.mBoneCache; + return boneCache.header; +} + +int G2_GetBoneDependents(CGhoul2Info &ghoul2,int boneNum,int *tempDependents,int maxDep) +{ + // fixme, these should be precomputed + if (!ghoul2.mBoneCache||!maxDep) + { + return 0; + } + + CBoneCache &boneCache=*ghoul2.mBoneCache; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + int i; + int ret=0; + for (i=0;inumChildren;i++) + { + if (!maxDep) + { + return i; // number added + } + *tempDependents=skel->children[i]; + assert(*tempDependents>0&&*tempDependentsnumBones); + maxDep--; + tempDependents++; + ret++; + } + for (i=0;inumChildren;i++) + { + int num=G2_GetBoneDependents(ghoul2,skel->children[i],tempDependents,maxDep); + tempDependents+=num; + ret+=num; + maxDep-=num; + assert(maxDep>=0); + if (!maxDep) + { + break; + } + } + return ret; +} + +bool G2_WasBoneRendered(CGhoul2Info &ghoul2,int boneNum) +{ + if (!ghoul2.mBoneCache) + { + return false; + } + CBoneCache &boneCache=*ghoul2.mBoneCache; + + return boneCache.WasRendered(boneNum); +} + +void G2_GetBoneBasepose(CGhoul2Info &ghoul2,int boneNum,mdxaBone_t *&retBasepose,mdxaBone_t *&retBaseposeInv) +{ + if (!ghoul2.mBoneCache) + { + // yikes + retBasepose=const_cast(&identityMatrix); + retBaseposeInv=const_cast(&identityMatrix); + return; + } + assert(ghoul2.mBoneCache); + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + assert(boneNum>=0&&boneNumnumBones); + + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + retBasepose=&skel->BasePoseMat; + retBaseposeInv=&skel->BasePoseMatInv; +} + +char *G2_GetBoneNameFromSkel(CGhoul2Info &ghoul2, int boneNum) +{ + if (!ghoul2.mBoneCache) + { + return NULL; + } + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + assert(boneNum>=0&&boneNumnumBones); + + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + + return skel->name; +} + +void G2_RagGetBoneBasePoseMatrixLow(CGhoul2Info &ghoul2, int boneNum, mdxaBone_t &boneMatrix, mdxaBone_t &retMatrix, vec3_t scale) +{ + assert(ghoul2.mBoneCache); + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + assert(boneNum>=0&&boneNumnumBones); + + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + Multiply_3x4Matrix(&retMatrix, &boneMatrix, &skel->BasePoseMat); + + if (scale[0]) + { + retMatrix.matrix[0][3] *= scale[0]; + } + if (scale[1]) + { + retMatrix.matrix[1][3] *= scale[1]; + } + if (scale[2]) + { + retMatrix.matrix[2][3] *= scale[2]; + } + + VectorNormalize((float*)&retMatrix.matrix[0]); + VectorNormalize((float*)&retMatrix.matrix[1]); + VectorNormalize((float*)&retMatrix.matrix[2]); +} + +void G2_GetBoneMatrixLow(CGhoul2Info &ghoul2,int boneNum,const vec3_t scale,mdxaBone_t &retMatrix,mdxaBone_t *&retBasepose,mdxaBone_t *&retBaseposeInv) +{ + if (!ghoul2.mBoneCache) + { + retMatrix=identityMatrix; + // yikes + retBasepose=const_cast(&identityMatrix); + retBaseposeInv=const_cast(&identityMatrix); + return; + } + mdxaBone_t bolt; + assert(ghoul2.mBoneCache); + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + assert(boneNum>=0&&boneNumnumBones); + + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + Multiply_3x4Matrix(&bolt, &boneCache.Eval(boneNum), &skel->BasePoseMat); // DEST FIRST ARG + retBasepose=&skel->BasePoseMat; + retBaseposeInv=&skel->BasePoseMatInv; + + if (scale[0]) + { + bolt.matrix[0][3] *= scale[0]; + } + if (scale[1]) + { + bolt.matrix[1][3] *= scale[1]; + } + if (scale[2]) + { + bolt.matrix[2][3] *= scale[2]; + } + VectorNormalize((float*)&bolt.matrix[0]); + VectorNormalize((float*)&bolt.matrix[1]); + VectorNormalize((float*)&bolt.matrix[2]); + + Multiply_3x4Matrix(&retMatrix,&worldMatrix, &bolt); + +#ifdef _DEBUG + for ( int i = 0; i < 3; i++ ) + { + for ( int j = 0; j < 4; j++ ) + { + assert( !_isnan(retMatrix.matrix[i][j])); + } + } +#endif// _DEBUG +} + +int G2_GetParentBoneMatrixLow(CGhoul2Info &ghoul2,int boneNum,const vec3_t scale,mdxaBone_t &retMatrix,mdxaBone_t *&retBasepose,mdxaBone_t *&retBaseposeInv) +{ + int parent=-1; + if (ghoul2.mBoneCache) + { + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + assert(boneNum>=0&&boneNumnumBones); + parent=boneCache.GetParent(boneNum); + if (parent<0||parent>=boneCache.header->numBones) + { + parent=-1; + retMatrix=identityMatrix; + // yikes + retBasepose=const_cast(&identityMatrix); + retBaseposeInv=const_cast(&identityMatrix); + } + else + { + G2_GetBoneMatrixLow(ghoul2,parent,scale,retMatrix,retBasepose,retBaseposeInv); + } + } + return parent; +} +//rww - RAGDOLL_END + +void RemoveBoneCache(CBoneCache *boneCache) +{ + delete boneCache; +} + +const mdxaBone_t &EvalBoneCache(int index,CBoneCache *boneCache) +{ + assert(boneCache); + return boneCache->Eval(index); +} + + +class CRenderSurface +{ +public: + int surfaceNum; + surfaceInfo_v &rootSList; + const shader_t *cust_shader; + int fogNum; + qboolean personalModel; + CBoneCache *boneCache; + int renderfx; + const skin_t *skin; + const model_t *currentModel; + int lod; + boltInfo_v &boltList; +#ifdef _G2_GORE + shader_t *gore_shader; + CGoreSet *gore_set; +#endif + + CRenderSurface( + int initsurfaceNum, + surfaceInfo_v &initrootSList, + const shader_t *initcust_shader, + int initfogNum, + qboolean initpersonalModel, + CBoneCache *initboneCache, + int initrenderfx, + const skin_t *initskin, + const model_t *initcurrentModel, + int initlod, +#ifdef _G2_GORE + boltInfo_v &initboltList, + shader_t *initgore_shader, + CGoreSet *initgore_set): +#else + boltInfo_v &initboltList): +#endif + surfaceNum(initsurfaceNum), + rootSList(initrootSList), + cust_shader(initcust_shader), + fogNum(initfogNum), + personalModel(initpersonalModel), + boneCache(initboneCache), + renderfx(initrenderfx), + skin(initskin), + currentModel(initcurrentModel), + lod(initlod), +#ifdef _G2_GORE + boltList(initboltList), + gore_shader(initgore_shader), + gore_set(initgore_set) +#else + boltList(initboltList) +#endif + {} +}; + +#define MAX_RENDER_SURFACES (2048) +static CRenderableSurface RSStorage[MAX_RENDER_SURFACES]; +static unsigned int NextRS=0; + +CRenderableSurface *AllocRS() +{ + CRenderableSurface *ret=&RSStorage[NextRS]; + ret->Init(); + NextRS++; + NextRS%=MAX_RENDER_SURFACES; + return ret; +} + +/* + +All bones should be an identity orientation to display the mesh exactly +as it is specified. + +For all other frames, the bones represent the transformation from the +orientation of the bone in the base frame to the orientation in this +frame. + +*/ + + +/* +============= +R_ACullModel +============= +*/ +static int R_GCullModel( trRefEntity_t *ent ) { + + // scale the radius if need be + float largestScale = ent->e.modelScale[0]; + + if (ent->e.modelScale[1] > largestScale) + { + largestScale = ent->e.modelScale[1]; + } + if (ent->e.modelScale[2] > largestScale) + { + largestScale = ent->e.modelScale[2]; + } + if (!largestScale) + { + largestScale = 1; + } + + // cull bounding sphere + switch ( R_CullLocalPointAndRadius( vec3_origin, ent->e.radius * largestScale) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + return CULL_IN; + } + return CULL_IN; +} + + +/* +================= +R_AComputeFogNum + +================= +*/ +static int R_GComputeFogNum( trRefEntity_t *ent ) { + + int i; + fog_t *fog; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + if ( tr.refdef.rdflags & RDF_doLAGoggles ) + { + return tr.world->numfogs; + } + + int partialFog = 0; + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + if ( ent->e.origin[0] - ent->e.radius >= fog->bounds[0][0] + && ent->e.origin[0] + ent->e.radius <= fog->bounds[1][0] + && ent->e.origin[1] - ent->e.radius >= fog->bounds[0][1] + && ent->e.origin[1] + ent->e.radius <= fog->bounds[1][1] + && ent->e.origin[2] - ent->e.radius >= fog->bounds[0][2] + && ent->e.origin[2] + ent->e.radius <= fog->bounds[1][2] ) + {//totally inside it + return i; + break; + } + if ( ( ent->e.origin[0] - ent->e.radius >= fog->bounds[0][0] && ent->e.origin[1] - ent->e.radius >= fog->bounds[0][1] && ent->e.origin[2] - ent->e.radius >= fog->bounds[0][2] && + ent->e.origin[0] - ent->e.radius <= fog->bounds[1][0] && ent->e.origin[1] - ent->e.radius <= fog->bounds[1][1] && ent->e.origin[2] - ent->e.radius <= fog->bounds[1][2] ) || + ( ent->e.origin[0] + ent->e.radius >= fog->bounds[0][0] && ent->e.origin[1] + ent->e.radius >= fog->bounds[0][1] && ent->e.origin[2] + ent->e.radius >= fog->bounds[0][2] && + ent->e.origin[0] + ent->e.radius <= fog->bounds[1][0] && ent->e.origin[1] + ent->e.radius <= fog->bounds[1][1] && ent->e.origin[2] + ent->e.radius <= fog->bounds[1][2] ) ) + {//partially inside it + if ( tr.refdef.fogIndex == i || R_FogParmsMatch( tr.refdef.fogIndex, i ) ) + {//take new one only if it's the same one that the viewpoint is in + return i; + break; + } + else if ( !partialFog ) + {//first partialFog + partialFog = i; + } + } + } + //if nothing else, use the first partial fog you found + return partialFog; +} + +// work out lod for this entity. +static int G2_ComputeLOD( trRefEntity_t *ent, const model_t *currentModel, int lodBias ) +{ + float flod, lodscale; + float projectedRadius; + int lod; + + if ( currentModel->numLods < 2 ) + { // model has only 1 LOD level, skip computations and bias + return(0); + } + +#ifdef _XBOX + if(strstr(currentModel->name, "jedi_") > 0) + return 0; +#endif + + if (r_lodbias->integer > lodBias) + { + lodBias = r_lodbias->integer; + } + + //**early out, it's going to be max lod + if (lodBias >= currentModel->numLods ) + { + return currentModel->numLods - 1; + } + + + // scale the radius if need be + float largestScale = ent->e.modelScale[0]; + + if (ent->e.modelScale[1] > largestScale) + { + largestScale = ent->e.modelScale[1]; + } + if (ent->e.modelScale[2] > largestScale) + { + largestScale = ent->e.modelScale[2]; + } + if (!largestScale) + { + largestScale = 1; + } + + if ( ( projectedRadius = ProjectRadius( 0.75*largestScale*ent->e.radius, ent->e.origin ) ) != 0 ) //we reduce the radius to make the LOD match other model types which use the actual bound box size + { + lodscale = r_lodscale->value; + if (lodscale > 20) lodscale = 20; + flod = 1.0f - projectedRadius * lodscale; + } + else + { + // object intersects near view plane, e.g. view weapon + flod = 0; + } + + flod *= currentModel->numLods; + lod = myftol( flod ); + + if ( lod < 0 ) + { + lod = 0; + } + else if ( lod >= currentModel->numLods ) + { + lod = currentModel->numLods - 1; + } + + + lod += lodBias; + + if ( lod >= currentModel->numLods ) + lod = currentModel->numLods - 1; + if ( lod < 0 ) + lod = 0; + + return lod; +} + + +void Multiply_3x4Matrix(mdxaBone_t *out,const mdxaBone_t *in2,const mdxaBone_t *in) +{ + // first row of out + out->matrix[0][0] = (in2->matrix[0][0] * in->matrix[0][0]) + (in2->matrix[0][1] * in->matrix[1][0]) + (in2->matrix[0][2] * in->matrix[2][0]); + out->matrix[0][1] = (in2->matrix[0][0] * in->matrix[0][1]) + (in2->matrix[0][1] * in->matrix[1][1]) + (in2->matrix[0][2] * in->matrix[2][1]); + out->matrix[0][2] = (in2->matrix[0][0] * in->matrix[0][2]) + (in2->matrix[0][1] * in->matrix[1][2]) + (in2->matrix[0][2] * in->matrix[2][2]); + out->matrix[0][3] = (in2->matrix[0][0] * in->matrix[0][3]) + (in2->matrix[0][1] * in->matrix[1][3]) + (in2->matrix[0][2] * in->matrix[2][3]) + in2->matrix[0][3]; + // second row of outf out + out->matrix[1][0] = (in2->matrix[1][0] * in->matrix[0][0]) + (in2->matrix[1][1] * in->matrix[1][0]) + (in2->matrix[1][2] * in->matrix[2][0]); + out->matrix[1][1] = (in2->matrix[1][0] * in->matrix[0][1]) + (in2->matrix[1][1] * in->matrix[1][1]) + (in2->matrix[1][2] * in->matrix[2][1]); + out->matrix[1][2] = (in2->matrix[1][0] * in->matrix[0][2]) + (in2->matrix[1][1] * in->matrix[1][2]) + (in2->matrix[1][2] * in->matrix[2][2]); + out->matrix[1][3] = (in2->matrix[1][0] * in->matrix[0][3]) + (in2->matrix[1][1] * in->matrix[1][3]) + (in2->matrix[1][2] * in->matrix[2][3]) + in2->matrix[1][3]; + // third row of out out + out->matrix[2][0] = (in2->matrix[2][0] * in->matrix[0][0]) + (in2->matrix[2][1] * in->matrix[1][0]) + (in2->matrix[2][2] * in->matrix[2][0]); + out->matrix[2][1] = (in2->matrix[2][0] * in->matrix[0][1]) + (in2->matrix[2][1] * in->matrix[1][1]) + (in2->matrix[2][2] * in->matrix[2][1]); + out->matrix[2][2] = (in2->matrix[2][0] * in->matrix[0][2]) + (in2->matrix[2][1] * in->matrix[1][2]) + (in2->matrix[2][2] * in->matrix[2][2]); + out->matrix[2][3] = (in2->matrix[2][0] * in->matrix[0][3]) + (in2->matrix[2][1] * in->matrix[1][3]) + (in2->matrix[2][2] * in->matrix[2][3]) + in2->matrix[2][3]; +} + +static int G2_GetBonePoolIndex( const mdxaHeader_t *pMDXAHeader, int iFrame, int iBone) +{ + assert(iFrame>=0&&iFramenumFrames); + assert(iBone>=0&&iBonenumBones); + const int iOffsetToIndex = (iFrame * pMDXAHeader->numBones * 3) + (iBone * 3); + + mdxaIndex_t *pIndex = (mdxaIndex_t *) ((byte*) pMDXAHeader + pMDXAHeader->ofsFrames + iOffsetToIndex); + + return pIndex->iIndex & 0x00FFFFFF; // this will cause problems for big-endian machines... ;-) +} + +/* + Let the nastiness begin: Virtual memory for GLAs! We swap the bonePool for large + GLAs (humanoid and cinematic) to the HD, and then manage a pool of pages, swapping + in on demand. The theory is that a small portion of the animations are ever used, + or used on one level, and we can get away with this. I hope I'm right. +*/ +struct vvBonePoolPage; +struct vvBonePoolPageTableEntry; + +#define QUATS_PER_PAGE 1024 +#define PAGES_IN_RAM 100 // 1.4 MB - perhaps too small? + +struct vvBonePoolPage +{ + mdxaCompQuatBone_t quats[QUATS_PER_PAGE]; // Data + vvBonePoolPageTableEntry *owner; // Bookkeeping + unsigned long touch; // For LRU +}; + +struct vvBonePoolPageTableEntry +{ + vvBonePoolPageTableEntry() : page(NULL) { } + + vvBonePoolPage *page; // Data, or NULL if not in memory +}; + +struct vvBonePoolClient +{ + // Constructor takes the original size of the compBonePool from the GLA, + // and decides how many pages it will need and such: + vvBonePoolClient( mdxaHeader_t *mdxa, bool bCinematic ) + { + // How big is the original bone pool, and how many pages do we need: + int bonePoolSize = mdxa->ofsEnd - mdxa->ofsCompBonePool; + int numQuats = bonePoolSize / sizeof(mdxaCompQuatBone_t); + assert( !(bonePoolSize % sizeof(mdxaCompQuatBone_t)) ); + + numPages = numQuats / QUATS_PER_PAGE; + if( numQuats % QUATS_PER_PAGE ) + numPages++; + + // Allocate our table that tracks which pages are in memory, and where: + Z_PushNewDeleteTag( TAG_MODEL_GLA ); + pages = new vvBonePoolPageTableEntry[numPages]; + Z_PopNewDeleteTag(); + + // Make the swap file: + h = CreateFile( bCinematic ? "Z:\\cinematicglaswap" : "Z:\\humanoidglaswap" , + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_FLAG_RANDOM_ACCESS | FILE_FLAG_NO_BUFFERING, + NULL ); + if( h == INVALID_HANDLE_VALUE ) + assert( !"Couldn't create gla swap file" ); + + // Dump out the bone pool now, for later retrieval: + byte *pCompBonePool = (byte *)mdxa + mdxa->ofsCompBonePool; + // New (non-buffered IO) - need to round this up to sector size (512): + bonePoolSize = (bonePoolSize + 511) & ~511; + DWORD dwWritten = 0; + if( !WriteFile( h, pCompBonePool, bonePoolSize, &dwWritten, NULL ) || dwWritten != bonePoolSize ) + assert( !"Couldn't write gla swap file" ); + + // Truncate the mdxa: + mdxa->ofsEnd = mdxa->ofsCompBonePool; + } + + ~vvBonePoolClient( void ) + { + // Close our file, and remove the page table: + CloseHandle( h ); + delete [] pages; + } + + vvBonePoolPageTableEntry *pages; + int numPages; + HANDLE h; // We always keep our page file open +}; + +const int precachePages[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 20, + 21, 26, 30, 34, 37, 39, 46, 51, 54, 55, 60, 63, 66, 67, 68, 70, + 73, 74, 76, 77, 82, 83, 84, 88, 89, 92, 97, 101, 103, 104, 105, + 111, 127, 128, 129, 130, 137, 138, 139, 140, 141, 142, 143, 146, + 147, 148, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, + 161, 162, 176, 177, 178, 180, 184, 185, 188, 189, 190, 199, 200, + 201, 202, 205, 208, 211, 212, 216, 223, 241, 242, 244, 251, +}; + +const int numPrecachePages = sizeof(precachePages) / sizeof(precachePages[0]); + +// This is getting silly. +class vvBonePoolManager +{ +public: + vvBonePoolManager( void ) + { + clients[0] = clients[1] = NULL; + glaPages = PAGES_IN_RAM; + Clear(); + } + + // Loads a canned list of pages from humanoid.gla to try and eliminate start-of-level slowdown + void PrecacheBasicAnimations( void ) + { + for( int i = 0; i < numPrecachePages; ++i ) + fetchBone( 0, precachePages[i] * QUATS_PER_PAGE ); + } + + void Register( mdxaHeader_t *mdxa ) + { + bool bCinematic = strstr( mdxa->name, "_humanoid_" ); + int clientIndex = bCinematic ? 1 : 0; + + Z_PushNewDeleteTag( TAG_MODEL_GLA ); + clients[clientIndex] = new vvBonePoolClient( mdxa, bCinematic ); + Z_PopNewDeleteTag(); + + // We're responsible for tagging the mdxa header so we know that we're in charge (0/-1): + mdxa->ofsCompBonePool = -clientIndex; + + // Special: If we just registered the humanoid.gla, and we're on kor2, precache some stuff: + if( !bCinematic ) + PrecacheBasicAnimations(); + } + + vvBonePoolPage *getFreePage( void ) + { + unsigned long minAge = pages[0].touch; + int minIndex = 0; + + // Find oldest page, or any page not in use: + for( int i = 0; i < glaPages; ++i ) + { + // Return an unused page right away: + if( !pages[i].owner ) + return &pages[i]; + + if( pages[i].touch < minAge ) + { + minIndex = i; + minAge = pages[i].touch; + } + } + + // Page was in use - update records: + pages[minIndex].owner->page = NULL; + pages[minIndex].owner = NULL; + return &pages[minIndex]; + } + + // Returns a pointer to the specified bone for the specified client + mdxaCompQuatBone_t *fetchBone( int clientIndex, int index ) + { +/* + static int crap = 0; + if( crap ) + ActivePageList(); +*/ + assert( clientIndex <= 0 && clientIndex >= -1 ); + vvBonePoolClient *client = clients[-clientIndex]; + + int pageIndex = index / QUATS_PER_PAGE; + assert( pageIndex <= client->numPages ); + int pageOffset = index % QUATS_PER_PAGE; + + // Is the data already in memory? + if( client->pages[pageIndex].page ) + { + vvBonePoolPage *p = client->pages[pageIndex].page; + p->touch = sv.time; + return &p->quats[pageOffset]; + } + + // Need to load from disk: + Com_Printf( "*" ); + vvBonePoolPage *p = getFreePage(); + DWORD dwRead = 0; + SetFilePointer( client->h, pageIndex * sizeof(p->quats), NULL, FILE_BEGIN ); + if( !ReadFile( client->h, p->quats, sizeof(p->quats), &dwRead, NULL ) ) //|| dwRead != sizeof(p->quats) ) (fails on last page) + assert( !"Couldn't read from gla swap file" ); + p->touch = sv.time; + p->owner = &client->pages[pageIndex]; + p->owner->page = p; + + return &p->quats[pageOffset]; + } + + // Called between levels to clean up after ourselves: + void Clear( void ) + { + int i; + + for( i = 0; i < PAGES_IN_RAM; ++i ) + pages[i].owner = NULL; + + for( i = 0; i < 2; ++i ) + if( clients[i] ) + { + delete clients[i]; + clients[i] = NULL; + } + + swappedPageCount = 0; + } + + // Used by other code (Bink) to turn part of the pool into a temp buffer: + void *TempAlloc( unsigned long size ) + { + // Make sure that we haven't already been called: + assert( glaPages == PAGES_IN_RAM ); + if( glaPages != PAGES_IN_RAM ) + return NULL; + + // Make sure the requested allocation isn't too large for us: + int tempPages = (size / sizeof(vvBonePoolPage)) + 1; + assert( tempPages < glaPages ); + if( tempPages >= glaPages ) + return NULL; + + // Move our end-of-valid-pages marker down: + glaPages -= tempPages; + // Invalidate all the pages that we're going to steal but remember + // which pages we had loaded, so we can reload them when we free this: + swappedPageCount = 0; + for( int i = glaPages; i < glaPages + tempPages; ++i ) + if( pages[i].owner ) + { + // Which client owns this page? + int clientNum = (clients[1] && + pages[i].owner >= clients[1]->pages && + pages[i].owner < clients[1]->pages + clients[1]->numPages) ? 1 : 0; + swappedClients[swappedPageCount] = clientNum; + + // And what's the index? + int pageNum = pages[i].owner - clients[clientNum]->pages; + swappedPages[swappedPageCount] = pageNum; + + swappedPageCount++; + assert( pageNum >= 0 && pageNum < clients[clientNum]->numPages ); + + // Mark the page as no longer loaded: + pages[i].owner->page = NULL; + } + + // And return the start of the block: + return &pages[glaPages]; + } + + // Free the currently allocated TempAlloc space: + void TempFree( void *p ) + { + // Sanity checks: + assert( (glaPages != PAGES_IN_RAM) && (p == &pages[glaPages]) ); + + // Fix up the data, who knows what kind of crap is in there now: + for( int i = glaPages; i < PAGES_IN_RAM; ++i ) + pages[i].owner = NULL; + + // And start re-using those pages again: + glaPages = PAGES_IN_RAM; + + // Now we re-load anything we threw out to make room for this: + for( int j = 0; j < swappedPageCount; ++j ) + fetchBone( -swappedClients[j], swappedPages[j] * QUATS_PER_PAGE ); + + swappedPageCount = 0; + } + + // Dump out a list of the pages currently loaded: + void ActivePageList( void ) + { + for( int c = 0; c < 2; ++c ) + { + if( !clients[c] ) + continue; + + Sys_Log( "bone-pool.log", va( "Pages for client %d:\n", c ) ); + + for( int i = 0; i < clients[c]->numPages; ++i ) + if( clients[c]->pages[i].page ) + Sys_Log( "bone-pool.log", va( "%d\n", i ) ); + } + } + +private: + vvBonePoolClient *clients[2]; // One for _humanoid, one for cinematic + vvBonePoolPage pages[PAGES_IN_RAM]; + int curTouch; + + // How many pages are being used for GLA. Will be PAGES_IN_RAM, unless TempAlloc + // has been handed out, in which case it will be smaller: + int glaPages; + + // How many pages did we actually throw out when we did a tempalloc, and what + // were they? + int swappedPageCount; + short swappedClients[PAGES_IN_RAM]; + short swappedPages[PAGES_IN_RAM]; +}; + +// Grand-unified bone pool manager thingy: +vvBonePoolManager TheBonePool; + +void ClearTheBonePool( void ) +{ + TheBonePool.Clear(); +} + +void *BonePoolTempAlloc( unsigned long size ) +{ + return TheBonePool.TempAlloc( size ); +} + +void BonePoolTempFree( void *p ) +{ + TheBonePool.TempFree( p ); +} + +/*******************************************************************************************************************/ + + +/*static inline*/ void UnCompressBone(float mat[3][4], int iBoneIndex, const mdxaHeader_t *pMDXAHeader, int iFrame) +{ + // Check for GLAs that are swapped out: + if( pMDXAHeader->ofsCompBonePool <= 0 ) + MC_UnCompressQuat(mat, TheBonePool.fetchBone(pMDXAHeader->ofsCompBonePool, G2_GetBonePoolIndex( pMDXAHeader, iFrame, iBoneIndex ))->Comp); + else + { + mdxaCompQuatBone_t *pCompBonePool = (mdxaCompQuatBone_t *) ((byte *)pMDXAHeader + pMDXAHeader->ofsCompBonePool); + MC_UnCompressQuat(mat, pCompBonePool[ G2_GetBonePoolIndex( pMDXAHeader, iFrame, iBoneIndex ) ].Comp); + } +} + + + +#define DEBUG_G2_TIMING (0) +#define DEBUG_G2_TIMING_RENDER_ONLY (1) + +void G2_TimingModel(boneInfo_t &bone,int currentTime,int numFramesInFile,int ¤tFrame,int &newFrame,float &lerp) +{ + assert(bone.startFrame>=0); + assert(bone.startFrame<=numFramesInFile); + assert(bone.endFrame>=0); + assert(bone.endFrame<=numFramesInFile); + + // yes - add in animation speed to current frame + float animSpeed = bone.animSpeed; + float time; + if (bone.pauseTime) + { + time = (bone.pauseTime - bone.startTime) / 50.0f; + } + else + { + time = (currentTime - bone.startTime) / 50.0f; + } + if (time<0.0f) + { + time=0.0f; + } + float newFrame_g = bone.startFrame + (time * animSpeed); + + int animSize = bone.endFrame - bone.startFrame; + float endFrame = (float)bone.endFrame; + // we are supposed to be animating right? + if (animSize) + { + // did we run off the end? + if (((animSpeed > 0.0f) && (newFrame_g > endFrame - 1)) || + ((animSpeed < 0.0f) && (newFrame_g < endFrame+1))) + { + // yep - decide what to do + if (bone.flags & BONE_ANIM_OVERRIDE_LOOP) + { + // get our new animation frame back within the bounds of the animation set + if (animSpeed < 0.0f) + { + // we don't use this case, or so I am told + // if we do, let me know, I need to insure the mod works + + // should we be creating a virtual frame? + if ((newFrame_g < endFrame+1) && (newFrame_g >= endFrame)) + { + // now figure out what we are lerping between + // delta is the fraction between this frame and the next, since the new anim is always at a .0f; + lerp = float(endFrame+1)-newFrame_g; + // frames are easy to calculate + currentFrame = endFrame; + assert(currentFrame>=0&¤tFrame=0&&newFrame=0&¤tFrame=0&&newFrame=0&&newFrame endFrame - 1) && (newFrame_g < endFrame)) + { + // now figure out what we are lerping between + // delta is the fraction between this frame and the next, since the new anim is always at a .0f; + lerp = (newFrame_g - (int)newFrame_g); + // frames are easy to calculate + currentFrame = (int)newFrame_g; + assert(currentFrame>=0&¤tFrame=0&&newFrame= endFrame) + { + newFrame_g=endFrame+fmod(newFrame_g-endFrame,animSize)-animSize; + } + // now figure out what we are lerping between + // delta is the fraction between this frame and the next, since the new anim is always at a .0f; + lerp = (newFrame_g - (int)newFrame_g); + // frames are easy to calculate + currentFrame = (int)newFrame_g; + assert(currentFrame>=0&¤tFrame= endFrame - 1) + { + newFrame = bone.startFrame; + assert(newFrame>=0&&newFrame=0&&newFrame= bone.startFrame) || (animSize < 10)); + } + else + { + if (((bone.flags & (BONE_ANIM_OVERRIDE_FREEZE)) == (BONE_ANIM_OVERRIDE_FREEZE))) + { + // if we are supposed to reset the default anim, then do so + if (animSpeed > 0.0f) + { + currentFrame = bone.endFrame - 1; + assert(currentFrame>=0&¤tFrame=0&¤tFrame=0&&newFrame 0.0) + { + // frames are easy to calculate + currentFrame = (int)newFrame_g; + + // figure out the difference between the two frames - we have to decide what frame and what percentage of that + // frame we want to display + lerp = (newFrame_g - currentFrame); + + assert(currentFrame>=0&¤tFrame= (int)endFrame) + { + // we only want to lerp with the first frame of the anim if we are looping + if (bone.flags & BONE_ANIM_OVERRIDE_LOOP) + { + newFrame = bone.startFrame; + assert(newFrame>=0&&newFrame=0&&newFrame=0&&newFramebone.startFrame) + { + currentFrame=bone.startFrame; + newFrame = currentFrame; + lerp=0.0f; + } + else + { + newFrame=currentFrame-1; + // are we now on the end frame? + if (newFrame < endFrame+1) + { + // we only want to lerp with the first frame of the anim if we are looping + if (bone.flags & BONE_ANIM_OVERRIDE_LOOP) + { + newFrame = bone.startFrame; + assert(newFrame>=0&&newFrame=0&&newFrame=0&¤tFrame=0&&newFrame=0&¤tFrame=0&&newFrame=0&¤tFrame=0&&newFrame=0.0f&&lerp<=1.0f); + */ +} + +//basically construct a seperate skeleton with full hierarchy to store a matrix +//off which will give us the desired settling position given the frame in the skeleton +//that should be used -rww +int G2_Add_Bone (const model_t *mod, boneInfo_v &blist, const char *boneName); +int G2_Find_Bone(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName); +void G2_RagGetAnimMatrix(CGhoul2Info &ghoul2, const int boneNum, mdxaBone_t &matrix, const int frame) +{ + mdxaBone_t animMatrix; + mdxaSkel_t *skel; + mdxaSkel_t *pskel; + mdxaSkelOffsets_t *offsets; + int parent; + int bListIndex; + int parentBlistIndex; +#ifdef _RAG_PRINT_TEST + bool actuallySet = false; +#endif + + assert(ghoul2.mBoneCache); + assert(ghoul2.animModel); + + offsets = (mdxaSkelOffsets_t *)((byte *)ghoul2.mBoneCache->header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)ghoul2.mBoneCache->header + sizeof(mdxaHeader_t) + offsets->offsets[boneNum]); + + //find/add the bone in the list + if (!skel->name || !skel->name[0]) + { + bListIndex = -1; + } + else + { + bListIndex = G2_Find_Bone(&ghoul2, ghoul2.mBlist, skel->name); + if (bListIndex == -1) + { +#ifdef _RAG_PRINT_TEST + Com_Printf("Attempting to add %s\n", skel->name); +#endif + bListIndex = G2_Add_Bone(ghoul2.animModel, ghoul2.mBlist, skel->name); + } + } + + assert(bListIndex != -1); + + boneInfo_t &bone = ghoul2.mBlist[bListIndex]; + + if (bone.hasAnimFrameMatrix == frame) + { //already calculated so just grab it + matrix = bone.animFrameMatrix; + return; + } + + //get the base matrix for the specified frame + UnCompressBone(animMatrix.matrix, boneNum, ghoul2.mBoneCache->header, frame); + + parent = skel->parent; + if (boneNum > 0 && parent > -1) + { + //recursively call to assure all parent matrices are set up + G2_RagGetAnimMatrix(ghoul2, parent, matrix, frame); + + //assign the new skel ptr for our parent + pskel = (mdxaSkel_t *)((byte *)ghoul2.mBoneCache->header + sizeof(mdxaHeader_t) + offsets->offsets[parent]); + + //taking bone matrix for the skeleton frame and parent's animFrameMatrix into account, determine our final animFrameMatrix + if (!pskel->name || !pskel->name[0]) + { + parentBlistIndex = -1; + } + else + { + parentBlistIndex = G2_Find_Bone(&ghoul2, ghoul2.mBlist, pskel->name); + if (parentBlistIndex == -1) + { + parentBlistIndex = G2_Add_Bone(ghoul2.animModel, ghoul2.mBlist, pskel->name); + } + } + + assert(parentBlistIndex != -1); + + boneInfo_t &pbone = ghoul2.mBlist[parentBlistIndex]; + + assert(pbone.hasAnimFrameMatrix == frame); //this should have been calc'd in the recursive call + + Multiply_3x4Matrix(&bone.animFrameMatrix, &pbone.animFrameMatrix, &animMatrix); + +#ifdef _RAG_PRINT_TEST + if (parentBlistIndex != -1 && bListIndex != -1) + { + actuallySet = true; + } + else + { + Com_Printf("BAD LIST INDEX: %s, %s [%i]\n", skel->name, pskel->name, parent); + } +#endif + } + else + { //root + Multiply_3x4Matrix(&bone.animFrameMatrix, &ghoul2.mBoneCache->rootMatrix, &animMatrix); +#ifdef _RAG_PRINT_TEST + if (bListIndex != -1) + { + actuallySet = true; + } + else + { + Com_Printf("BAD LIST INDEX: %s\n", skel->name); + } +#endif + //bone.animFrameMatrix = ghoul2.mBoneCache->mFinalBones[boneNum].boneMatrix; + //Maybe use this for the root, so that the orientation is in sync with the current + //root matrix? However this would require constant recalculation of this base + //skeleton which I currently do not want. + } + + //never need to figure it out again + bone.hasAnimFrameMatrix = frame; + +#ifdef _RAG_PRINT_TEST + if (!actuallySet) + { + Com_Printf("SET FAILURE\n"); + } +#endif + + matrix = bone.animFrameMatrix; +} + +// transform each individual bone's information - making sure to use any override information provided, both for angles and for animations, as +// well as multiplying each bone's matrix by it's parents matrix +void G2_TransformBone (int child,CBoneCache &BC) +{ + SBoneCalc &TB=BC.mBones[child]; + mdxaBone_t tbone[6]; +// mdxaFrame_t *aFrame=0; +// mdxaFrame_t *bFrame=0; +// mdxaFrame_t *aoldFrame=0; +// mdxaFrame_t *boldFrame=0; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + boneInfo_v &boneList = *BC.rootBoneList; + int j, boneListIndex; + int angleOverride = 0; + +#if DEBUG_G2_TIMING + bool printTiming=false; +#endif + // should this bone be overridden by a bone in the bone list? + boneListIndex = G2_Find_Bone_In_List(boneList, child); + if (boneListIndex != -1) + { + // we found a bone in the list - we need to override something here. + + // do we override the rotational angles? + if ((boneList[boneListIndex].flags) & (BONE_ANGLES_TOTAL)) + { + angleOverride = (boneList[boneListIndex].flags) & (BONE_ANGLES_TOTAL); + } + + // set blending stuff if we need to + if (boneList[boneListIndex].flags & BONE_ANIM_BLEND) + { + float blendTime = BC.incomingTime - boneList[boneListIndex].blendStart; + // only set up the blend anim if we actually have some blend time left on this bone anim - otherwise we might corrupt some blend higher up the hiearchy + if (blendTime>=0.0f&&blendTime < boneList[boneListIndex].blendTime) + { + TB.blendFrame = boneList[boneListIndex].blendFrame; + TB.blendOldFrame = boneList[boneListIndex].blendLerpFrame; + TB.blendLerp = (blendTime / boneList[boneListIndex].blendTime); + TB.blendMode = true; + } + else + { + TB.blendMode = false; + } + } + else if (r_Ghoul2NoBlend->integer||((boneList[boneListIndex].flags) & (BONE_ANIM_OVERRIDE_LOOP | BONE_ANIM_OVERRIDE))) + // turn off blending if we are just doing a straing animation override + { + TB.blendMode = false; + } + + // should this animation be overridden by an animation in the bone list? + if ((boneList[boneListIndex].flags) & (BONE_ANIM_OVERRIDE_LOOP | BONE_ANIM_OVERRIDE)) + { + G2_TimingModel(boneList[boneListIndex],BC.incomingTime,BC.header->numFrames,TB.currentFrame,TB.newFrame,TB.backlerp); + } +#if DEBUG_G2_TIMING + printTiming=true; +#endif + if ((r_Ghoul2NoLerp->integer)||((boneList[boneListIndex].flags) & (BONE_ANIM_NO_LERP))) + { + TB.backlerp = 0.0f; + } + } + // figure out where the location of the bone animation data is + assert(TB.newFrame>=0&&TB.newFramenumFrames); + if (!(TB.newFrame>=0&&TB.newFramenumFrames)) + { + TB.newFrame=0; + } +// aFrame = (mdxaFrame_t *)((byte *)BC.header + BC.header->ofsFrames + TB.newFrame * BC.frameSize ); + assert(TB.currentFrame>=0&&TB.currentFramenumFrames); + if (!(TB.currentFrame>=0&&TB.currentFramenumFrames)) + { + TB.currentFrame=0; + } +// aoldFrame = (mdxaFrame_t *)((byte *)BC.header + BC.header->ofsFrames + TB.currentFrame * BC.frameSize ); + + // figure out where the location of the blended animation data is + assert(!(TB.blendFrame < 0.0 || TB.blendFrame >= (BC.header->numFrames+1))); + if (TB.blendFrame < 0.0 || TB.blendFrame >= (BC.header->numFrames+1) ) + { + TB.blendFrame=0.0; + } +// bFrame = (mdxaFrame_t *)((byte *)BC.header + BC.header->ofsFrames + (int)TB.blendFrame * BC.frameSize ); + assert(TB.blendOldFrame>=0&&TB.blendOldFramenumFrames); + if (!(TB.blendOldFrame>=0&&TB.blendOldFramenumFrames)) + { + TB.blendOldFrame=0; + } +#if DEBUG_G2_TIMING + +#if DEBUG_G2_TIMING_RENDER_ONLY + if (!HackadelicOnClient) + { + printTiming=false; + } +#endif + if (printTiming) + { + char mess[1000]; + if (TB.blendMode) + { + sprintf(mess,"b %2d %5d %4d %4d %4d %4d %f %f\n",boneListIndex,BC.incomingTime,(int)TB.newFrame,(int)TB.currentFrame,(int)TB.blendFrame,(int)TB.blendOldFrame,TB.backlerp,TB.blendLerp); + } + else + { + sprintf(mess,"a %2d %5d %4d %4d %f\n",boneListIndex,BC.incomingTime,TB.newFrame,TB.currentFrame,TB.backlerp); + } + OutputDebugString(mess); + const boneInfo_t &bone=boneList[boneListIndex]; + if (bone.flags&BONE_ANIM_BLEND) + { + sprintf(mess," bfb[%2d] %5d %5d (%5d-%5d) %4.2f %4x bt(%5d-%5d) %7.2f %5d\n", + boneListIndex, + BC.incomingTime, + bone.startTime, + bone.startFrame, + bone.endFrame, + bone.animSpeed, + bone.flags, + bone.blendStart, + bone.blendStart+bone.blendTime, + bone.blendFrame, + bone.blendLerpFrame + ); + } + else + { + sprintf(mess," bfa[%2d] %5d %5d (%5d-%5d) %4.2f %4x\n", + boneListIndex, + BC.incomingTime, + bone.startTime, + bone.startFrame, + bone.endFrame, + bone.animSpeed, + bone.flags + ); + } +// OutputDebugString(mess); + } +#endif +// boldFrame = (mdxaFrame_t *)((byte *)BC.header + BC.header->ofsFrames + TB.blendOldFrame * BC.frameSize ); + +// mdxaCompBone_t *compBonePointer = (mdxaCompBone_t *)((byte *)BC.header + BC.header->ofsCompBonePool); + + assert(child>=0&&childnumBones); +// assert(bFrame->boneIndexes[child]>=0); +// assert(boldFrame->boneIndexes[child]>=0); +// assert(aFrame->boneIndexes[child]>=0); +// assert(aoldFrame->boneIndexes[child]>=0); + + // decide where the transformed bone is going + + // are we blending with another frame of anim? + if (TB.blendMode) + { + float backlerp = TB.blendFrame - (int)TB.blendFrame; + float frontlerp = 1.0 - backlerp; + +// MC_UnCompress(tbone[3].matrix,compBonePointer[bFrame->boneIndexes[child]].Comp); +// MC_UnCompress(tbone[4].matrix,compBonePointer[boldFrame->boneIndexes[child]].Comp); + UnCompressBone(tbone[3].matrix, child, BC.header, TB.blendFrame); + UnCompressBone(tbone[4].matrix, child, BC.header, TB.blendOldFrame); + + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&tbone[5])[j] = (backlerp * ((float *)&tbone[3])[j]) + + (frontlerp * ((float *)&tbone[4])[j]); + } + } + + // + // lerp this bone - use the temp space on the ref entity to put the bone transforms into + // + if (!TB.backlerp) + { +// MC_UnCompress(tbone[2].matrix,compBonePointer[aoldFrame->boneIndexes[child]].Comp); + UnCompressBone(tbone[2].matrix, child, BC.header, TB.currentFrame); + + // blend in the other frame if we need to + if (TB.blendMode) + { + float blendFrontlerp = 1.0 - TB.blendLerp; + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&tbone[2])[j] = (TB.blendLerp * ((float *)&tbone[2])[j]) + + (blendFrontlerp * ((float *)&tbone[5])[j]); + } + } + + if (!child) + { + // now multiply by the root matrix, so we can offset this model should we need to + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.rootMatrix, &tbone[2]); + } + } + else + { + float frontlerp = 1.0 - TB.backlerp; +// MC_UnCompress(tbone[0].matrix,compBonePointer[aFrame->boneIndexes[child]].Comp); +// MC_UnCompress(tbone[1].matrix,compBonePointer[aoldFrame->boneIndexes[child]].Comp); + UnCompressBone(tbone[0].matrix, child, BC.header, TB.newFrame); + UnCompressBone(tbone[1].matrix, child, BC.header, TB.currentFrame); + + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&tbone[2])[j] = (TB.backlerp * ((float *)&tbone[0])[j]) + + (frontlerp * ((float *)&tbone[1])[j]); + } + + // blend in the other frame if we need to + if (TB.blendMode) + { + float blendFrontlerp = 1.0 - TB.blendLerp; + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&tbone[2])[j] = (TB.blendLerp * ((float *)&tbone[2])[j]) + + (blendFrontlerp * ((float *)&tbone[5])[j]); + } + } + + if (!child) + { + // now multiply by the root matrix, so we can offset this model should we need to + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.rootMatrix, &tbone[2]); + } + } + // figure out where the bone hirearchy info is + offsets = (mdxaSkelOffsets_t *)((byte *)BC.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)BC.header + sizeof(mdxaHeader_t) + offsets->offsets[child]); + + int parent=BC.mFinalBones[child].parent; + assert((parent==-1&&child==0)||(parent>=0&&parentBasePoseMat); + float matrixScale = VectorLength((float*)&temp); + static mdxaBone_t toMatrix = + { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f + }; + toMatrix.matrix[0][0]=matrixScale; + toMatrix.matrix[1][1]=matrixScale; + toMatrix.matrix[2][2]=matrixScale; + toMatrix.matrix[0][3]=temp.matrix[0][3]; + toMatrix.matrix[1][3]=temp.matrix[1][3]; + toMatrix.matrix[2][3]=temp.matrix[2][3]; + + Multiply_3x4Matrix(&temp, &toMatrix,&skel->BasePoseMatInv); //dest first arg + + float blendTime = BC.incomingTime - boneList[boneListIndex].boneBlendStart; + float blendLerp = (blendTime / boneList[boneListIndex].boneBlendTime); + if (blendLerp>0.0f) + { + // has started + if (blendLerp>1.0f) + { + // done +// Multiply_3x4Matrix(&bone, &BC.mFinalBones[parent].boneMatrix,&temp); + memcpy (&bone,&temp, sizeof(mdxaBone_t)); + } + else + { +// mdxaBone_t lerp; + // now do the blend into the destination + float blendFrontlerp = 1.0 - blendLerp; + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&bone)[j] = (blendLerp * ((float *)&temp)[j]) + + (blendFrontlerp * ((float *)&tbone[2])[j]); + } +// Multiply_3x4Matrix(&bone, &BC.mFinalBones[parent].boneMatrix,&lerp); + } + } + } + else + { + mdxaBone_t temp, firstPass; + + // give us the matrix the animation thinks we should have, so we can get the correct X&Y coors + Multiply_3x4Matrix(&firstPass, &BC.mFinalBones[parent].boneMatrix, &tbone[2]); + + // are we attempting to blend with the base animation? and still within blend time? + if (boneOverride.boneBlendTime && (((boneOverride.boneBlendTime + boneOverride.boneBlendStart) < BC.incomingTime))) + { + // ok, we are supposed to be blending. Work out lerp + float blendTime = BC.incomingTime - boneList[boneListIndex].boneBlendStart; + float blendLerp = (blendTime / boneList[boneListIndex].boneBlendTime); + + if (blendLerp <= 1) + { + if (blendLerp < 0) + { + assert(0); + } + + // now work out the matrix we want to get *to* - firstPass is where we are coming *from* + Multiply_3x4Matrix(&temp, &firstPass, &skel->BasePoseMat); + + float matrixScale = VectorLength((float*)&temp); + + mdxaBone_t newMatrixTemp; + + if (HackadelicOnClient) + { + for (int i=0; i<3;i++) + { + for(int x=0;x<3; x++) + { + newMatrixTemp.matrix[i][x] = boneOverride.newMatrix.matrix[i][x]*matrixScale; + } + } + + newMatrixTemp.matrix[0][3] = temp.matrix[0][3]; + newMatrixTemp.matrix[1][3] = temp.matrix[1][3]; + newMatrixTemp.matrix[2][3] = temp.matrix[2][3]; + } + else + { + for (int i=0; i<3;i++) + { + for(int x=0;x<3; x++) + { + newMatrixTemp.matrix[i][x] = boneOverride.matrix.matrix[i][x]*matrixScale; + } + } + + newMatrixTemp.matrix[0][3] = temp.matrix[0][3]; + newMatrixTemp.matrix[1][3] = temp.matrix[1][3]; + newMatrixTemp.matrix[2][3] = temp.matrix[2][3]; + } + + Multiply_3x4Matrix(&temp, &newMatrixTemp,&skel->BasePoseMatInv); + + // now do the blend into the destination + float blendFrontlerp = 1.0 - blendLerp; + for ( j = 0 ; j < 12 ; j++ ) + { + ((float *)&bone)[j] = (blendLerp * ((float *)&temp)[j]) + + (blendFrontlerp * ((float *)&firstPass)[j]); + } + } + else + { + bone = firstPass; + } + } + // no, so just override it directly + else + { + + Multiply_3x4Matrix(&temp,&firstPass, &skel->BasePoseMat); + float matrixScale = VectorLength((float*)&temp); + + mdxaBone_t newMatrixTemp; + + if (HackadelicOnClient) + { + for (int i=0; i<3;i++) + { + for(int x=0;x<3; x++) + { + newMatrixTemp.matrix[i][x] = boneOverride.newMatrix.matrix[i][x]*matrixScale; + } + } + + newMatrixTemp.matrix[0][3] = temp.matrix[0][3]; + newMatrixTemp.matrix[1][3] = temp.matrix[1][3]; + newMatrixTemp.matrix[2][3] = temp.matrix[2][3]; + } + else + { + for (int i=0; i<3;i++) + { + for(int x=0;x<3; x++) + { + newMatrixTemp.matrix[i][x] = boneOverride.matrix.matrix[i][x]*matrixScale; + } + } + + newMatrixTemp.matrix[0][3] = temp.matrix[0][3]; + newMatrixTemp.matrix[1][3] = temp.matrix[1][3]; + newMatrixTemp.matrix[2][3] = temp.matrix[2][3]; + } + + Multiply_3x4Matrix(&bone, &newMatrixTemp,&skel->BasePoseMatInv); + } + } + } + else if (angleOverride & BONE_ANGLES_PREMULT) + { + if ((angleOverride&BONE_ANGLES_RAGDOLL) || (angleOverride&BONE_ANGLES_IK)) + { + mdxaBone_t tmp; + if (!child) + { + if (HackadelicOnClient) + { + Multiply_3x4Matrix(&tmp, &BC.rootMatrix, &boneList[boneListIndex].newMatrix); + } + else + { + Multiply_3x4Matrix(&tmp, &BC.rootMatrix, &boneList[boneListIndex].matrix); + } + } + else + { + if (HackadelicOnClient) + { + Multiply_3x4Matrix(&tmp, &BC.mFinalBones[parent].boneMatrix, &boneList[boneListIndex].newMatrix); + } + else + { + Multiply_3x4Matrix(&tmp, &BC.mFinalBones[parent].boneMatrix, &boneList[boneListIndex].matrix); + } + } + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix,&tmp, &tbone[2]); + } + else + { + if (!child) + { + // use the in coming root matrix as our basis + if (HackadelicOnClient) + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.rootMatrix, &boneList[boneListIndex].newMatrix); + } + else + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.rootMatrix, &boneList[boneListIndex].matrix); + } + } + else + { + // convert from 3x4 matrix to a 4x4 matrix + if (HackadelicOnClient) + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.mFinalBones[parent].boneMatrix, &boneList[boneListIndex].newMatrix); + } + else + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.mFinalBones[parent].boneMatrix, &boneList[boneListIndex].matrix); + } + } + } + } + else + // now transform the matrix by it's parent, asumming we have a parent, and we aren't overriding the angles absolutely + if (child) + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &BC.mFinalBones[parent].boneMatrix, &tbone[2]); + } + + // now multiply our resulting bone by an override matrix should we need to + if (angleOverride & BONE_ANGLES_POSTMULT) + { + mdxaBone_t tempMatrix; + memcpy (&tempMatrix,&BC.mFinalBones[child].boneMatrix, sizeof(mdxaBone_t)); + if (HackadelicOnClient) + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &tempMatrix, &boneList[boneListIndex].newMatrix); + } + else + { + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix, &tempMatrix, &boneList[boneListIndex].matrix); + } + } + if (r_Ghoul2UnSqash->integer) + { + mdxaBone_t tempMatrix; + Multiply_3x4Matrix(&tempMatrix,&BC.mFinalBones[child].boneMatrix, &skel->BasePoseMat); + float maxl; + maxl=VectorLength(&skel->BasePoseMat.matrix[0][0]); + VectorNormalize(&tempMatrix.matrix[0][0]); + VectorNormalize(&tempMatrix.matrix[1][0]); + VectorNormalize(&tempMatrix.matrix[2][0]); + + VectorScale(&tempMatrix.matrix[0][0],maxl,&tempMatrix.matrix[0][0]); + VectorScale(&tempMatrix.matrix[1][0],maxl,&tempMatrix.matrix[1][0]); + VectorScale(&tempMatrix.matrix[2][0],maxl,&tempMatrix.matrix[2][0]); + Multiply_3x4Matrix(&BC.mFinalBones[child].boneMatrix,&tempMatrix,&skel->BasePoseMatInv); + } + +} + + +#define GHOUL2_RAG_STARTED 0x0010 + +// start the recursive hirearchial bone transform and lerp process for this model +void G2_TransformGhoulBones(boneInfo_v &rootBoneList,mdxaBone_t &rootMatrix, CGhoul2Info &ghoul2, int time,bool smooth=true) +{ +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceCounter_G2_TransformGhoulBones++; +#endif + assert(ghoul2.aHeader); + assert(ghoul2.currentModel); + assert(ghoul2.currentModel->mdxm); + if (!ghoul2.aHeader->numBones) + { + assert(0); // this would be strange + return; + } + if (!ghoul2.mBoneCache) + { + ghoul2.mBoneCache=new CBoneCache(ghoul2.currentModel,ghoul2.aHeader); + } + ghoul2.mBoneCache->mod=ghoul2.currentModel; + ghoul2.mBoneCache->header=ghoul2.aHeader; + assert((int)ghoul2.mBoneCache->mNumBones==ghoul2.aHeader->numBones); + + ghoul2.mBoneCache->mSmoothingActive=false; + ghoul2.mBoneCache->mUnsquash=false; + + // master smoothing control + float val=r_Ghoul2AnimSmooth->value; + if (smooth&&val>0.0f&&val<1.0f) + { + ghoul2.mBoneCache->mLastTouch=ghoul2.mBoneCache->mLastLastTouch; + + if(ghoul2.mFlags & GHOUL2_RAG_STARTED) + { + int k; + for (k=0;ktime-250 && + bone.firstCollisionTime time) + { + val = 0.2f; + } + else + { + val = 0.8f; + } + break; + } + } + } + + ghoul2.mBoneCache->mSmoothFactor=val; + ghoul2.mBoneCache->mSmoothingActive=true; + if (r_Ghoul2UnSqashAfterSmooth->integer) + { + ghoul2.mBoneCache->mUnsquash=true; + } + } + else + { + ghoul2.mBoneCache->mSmoothFactor=1.0f; + } + ghoul2.mBoneCache->mCurrentTouch++; + +//rww - RAGDOLL_BEGIN + if (HackadelicOnClient) + { + ghoul2.mBoneCache->mLastLastTouch=ghoul2.mBoneCache->mCurrentTouch; + ghoul2.mBoneCache->mCurrentTouchRender=ghoul2.mBoneCache->mCurrentTouch; + } + else + { + ghoul2.mBoneCache->mCurrentTouchRender=0; + } +//rww - RAGDOLL_END + +// ghoul2.mBoneCache->mWraithID=0; + ghoul2.mBoneCache->frameSize = 0;// can be deleted in new G2 format //(int)( &((mdxaFrame_t *)0)->boneIndexes[ ghoul2.aHeader->numBones ] ); + + ghoul2.mBoneCache->rootBoneList=&rootBoneList; + ghoul2.mBoneCache->rootMatrix=rootMatrix; + ghoul2.mBoneCache->incomingTime=time; + + SBoneCalc &TB=ghoul2.mBoneCache->Root(); + TB.newFrame=0; + TB.currentFrame=0; + TB.backlerp=0.0f; + TB.blendFrame=0; + TB.blendOldFrame=0; + TB.blendMode=false; + TB.blendLerp=0; + +} + + +#define MDX_TAG_ORIGIN 2 + +//====================================================================== +// +// Surface Manipulation code + + +// We've come across a surface that's designated as a bolt surface, process it and put it in the appropriate bolt place +void G2_ProcessSurfaceBolt2(CBoneCache &boneCache, const mdxmSurface_t *surface, int boltNum, boltInfo_v &boltList, const surfaceInfo_t *surfInfo, const model_t *mod,mdxaBone_t &retMatrix) +{ + mdxmVertex_t *v, *vert0, *vert1, *vert2; + vec3_t axes[3], sides[3]; + float pTri[3][3], d; + int j, k; + + // now there are two types of tag surface - model ones and procedural generated types - lets decide which one we have here. + if (surfInfo && surfInfo->offFlags == G2SURFACEFLAG_GENERATED) + { + int surfNumber = surfInfo->genPolySurfaceIndex & 0x0ffff; + int polyNumber = (surfInfo->genPolySurfaceIndex >> 16) & 0x0ffff; + + // find original surface our original poly was in. + mdxmSurface_t *originalSurf = (mdxmSurface_t *)G2_FindSurface(mod, surfNumber, surfInfo->genLod); + mdxmTriangle_t *originalTriangleIndexes = (mdxmTriangle_t *)((byte*)originalSurf + originalSurf->ofsTriangles); + + // get the original polys indexes + int index0 = originalTriangleIndexes[polyNumber].indexes[0]; + int index1 = originalTriangleIndexes[polyNumber].indexes[1]; + int index2 = originalTriangleIndexes[polyNumber].indexes[2]; + + // decide where the original verts are + vert0 = (mdxmVertex_t *) ((byte *)originalSurf + originalSurf->ofsVerts); + vert0+=index0; + + vert1 = (mdxmVertex_t *) ((byte *)originalSurf + originalSurf->ofsVerts); + vert1+=index1; + + vert2 = (mdxmVertex_t *) ((byte *)originalSurf + originalSurf->ofsVerts); + vert2+=index2; + + // clear out the triangle verts to be + VectorClear( pTri[0] ); + VectorClear( pTri[1] ); + VectorClear( pTri[2] ); + int *piBoneReferences = (int*) ((byte*)originalSurf + originalSurf->ofsBoneReferences); + +// mdxmWeight_t *w; + + // now go and transform just the points we need from the surface that was hit originally +// w = vert0->weights; + float fTotalWeight = 0.0f; + int iNumWeights = G2_GetVertWeights( vert0 ); + for ( k = 0 ; k < iNumWeights ; k++ ) + { + int iBoneIndex = G2_GetVertBoneIndex( vert0, k ); + float fBoneWeight = G2_GetVertBoneWeight( vert0, k, fTotalWeight, iNumWeights ); + + const mdxaBone_t &bone=boneCache.Eval(piBoneReferences[iBoneIndex]); + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &vert0->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert0->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert0->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[0][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vec ) + bone.matrix[0][3] ); + pTri[0][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vec ) + bone.matrix[1][3] ); + pTri[0][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vec ) + bone.matrix[2][3] ); +#else + pTri[0][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vert0->vertCoords ) + bone.matrix[0][3] ); + pTri[0][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vert0->vertCoords ) + bone.matrix[1][3] ); + pTri[0][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vert0->vertCoords ) + bone.matrix[2][3] ); +#endif + } + +// w = vert1->weights; + fTotalWeight = 0.0f; + iNumWeights = G2_GetVertWeights( vert1 ); + for ( k = 0 ; k < iNumWeights ; k++) + { + int iBoneIndex = G2_GetVertBoneIndex( vert1, k ); + float fBoneWeight = G2_GetVertBoneWeight( vert1, k, fTotalWeight, iNumWeights ); + + const mdxaBone_t &bone=boneCache.Eval(piBoneReferences[iBoneIndex]); + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &vert1->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert1->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert1->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[1][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vec ) + bone.matrix[0][3] ); + pTri[1][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vec ) + bone.matrix[1][3] ); + pTri[1][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vec ) + bone.matrix[2][3] ); +#else + pTri[1][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vert1->vertCoords ) + bone.matrix[0][3] ); + pTri[1][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vert1->vertCoords ) + bone.matrix[1][3] ); + pTri[1][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vert1->vertCoords ) + bone.matrix[2][3] ); +#endif + } + +// w = vert2->weights; + fTotalWeight = 0.0f; + iNumWeights = G2_GetVertWeights( vert2 ); + for ( k = 0 ; k < iNumWeights ; k++) + { + int iBoneIndex = G2_GetVertBoneIndex( vert2, k ); + float fBoneWeight = G2_GetVertBoneWeight( vert2, k, fTotalWeight, iNumWeights ); + + const mdxaBone_t &bone=boneCache.Eval(piBoneReferences[iBoneIndex]); + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &vert2->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &vert2->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &vert2->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[2][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vec ) + bone.matrix[0][3] ); + pTri[2][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vec ) + bone.matrix[1][3] ); + pTri[2][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vec ) + bone.matrix[2][3] ); +#else + pTri[2][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vert2->vertCoords ) + bone.matrix[0][3] ); + pTri[2][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vert2->vertCoords ) + bone.matrix[1][3] ); + pTri[2][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vert2->vertCoords ) + bone.matrix[2][3] ); +#endif + } + + vec3_t normal; + vec3_t up; + vec3_t right; + vec3_t vec0, vec1; + // work out baryCentricK + float baryCentricK = 1.0 - (surfInfo->genBarycentricI + surfInfo->genBarycentricJ); + + // now we have the model transformed into model space, now generate an origin. + retMatrix.matrix[0][3] = (pTri[0][0] * surfInfo->genBarycentricI) + (pTri[1][0] * surfInfo->genBarycentricJ) + (pTri[2][0] * baryCentricK); + retMatrix.matrix[1][3] = (pTri[0][1] * surfInfo->genBarycentricI) + (pTri[1][1] * surfInfo->genBarycentricJ) + (pTri[2][1] * baryCentricK); + retMatrix.matrix[2][3] = (pTri[0][2] * surfInfo->genBarycentricI) + (pTri[1][2] * surfInfo->genBarycentricJ) + (pTri[2][2] * baryCentricK); + + // generate a normal to this new triangle + VectorSubtract(pTri[0], pTri[1], vec0); + VectorSubtract(pTri[2], pTri[1], vec1); + + CrossProduct(vec0, vec1, normal); + VectorNormalize(normal); + + // forward vector + retMatrix.matrix[0][0] = normal[0]; + retMatrix.matrix[1][0] = normal[1]; + retMatrix.matrix[2][0] = normal[2]; + + // up will be towards point 0 of the original triangle. + // so lets work it out. Vector is hit point - point 0 + up[0] = retMatrix.matrix[0][3] - pTri[0][0]; + up[1] = retMatrix.matrix[1][3] - pTri[0][1]; + up[2] = retMatrix.matrix[2][3] - pTri[0][2]; + + // normalise it + VectorNormalize(up); + + // that's the up vector + retMatrix.matrix[0][1] = up[0]; + retMatrix.matrix[1][1] = up[1]; + retMatrix.matrix[2][1] = up[2]; + + // right is always straight + + CrossProduct( normal, up, right ); + // that's the up vector + retMatrix.matrix[0][2] = right[0]; + retMatrix.matrix[1][2] = right[1]; + retMatrix.matrix[2][2] = right[2]; + + + } + // no, we are looking at a normal model tag + else + { + // whip through and actually transform each vertex + v = (mdxmVertex_t *) ((byte *)surface + surface->ofsVerts); + int *piBoneReferences = (int*) ((byte*)surface + surface->ofsBoneReferences); + for ( j = 0; j < 3; j++ ) + { +// mdxmWeight_t *w; + + VectorClear( pTri[j] ); + // w = v->weights; + + const int iNumWeights = G2_GetVertWeights( v ); + + float fTotalWeight = 0.0f; + for ( k = 0 ; k < iNumWeights ; k++) + { + int iBoneIndex = G2_GetVertBoneIndex( v, k ); + float fBoneWeight = G2_GetVertBoneWeight( v, k, fTotalWeight, iNumWeights ); + + const mdxaBone_t &bone=boneCache.Eval(piBoneReferences[iBoneIndex]); + +#ifdef _XBOX + vec3_t vec; + Q_CastShort2FloatScale(&vec[0], &v->vertCoords[0], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[1], &v->vertCoords[1], 1.f / (float)GLM_COMP_SIZE); + Q_CastShort2FloatScale(&vec[2], &v->vertCoords[2], 1.f / (float)GLM_COMP_SIZE); + + pTri[j][0] += fBoneWeight * ( DotProduct( bone.matrix[0], vec ) + bone.matrix[0][3] ); + pTri[j][1] += fBoneWeight * ( DotProduct( bone.matrix[1], vec ) + bone.matrix[1][3] ); + pTri[j][2] += fBoneWeight * ( DotProduct( bone.matrix[2], vec ) + bone.matrix[2][3] ); +#else + pTri[j][0] += fBoneWeight * ( DotProduct( bone.matrix[0], v->vertCoords ) + bone.matrix[0][3] ); + pTri[j][1] += fBoneWeight * ( DotProduct( bone.matrix[1], v->vertCoords ) + bone.matrix[1][3] ); + pTri[j][2] += fBoneWeight * ( DotProduct( bone.matrix[2], v->vertCoords ) + bone.matrix[2][3] ); +#endif + } + + v++;// = (mdxmVertex_t *)&v->weights[/*v->numWeights*/surface->maxVertBoneWeights]; + } + + // clear out used arrays + memset( axes, 0, sizeof( axes ) ); + memset( sides, 0, sizeof( sides ) ); + + // work out actual sides of the tag triangle + for ( j = 0; j < 3; j++ ) + { + sides[j][0] = pTri[(j+1)%3][0] - pTri[j][0]; + sides[j][1] = pTri[(j+1)%3][1] - pTri[j][1]; + sides[j][2] = pTri[(j+1)%3][2] - pTri[j][2]; + } + + // do math trig to work out what the matrix will be from this triangle's translated position + VectorNormalize2( sides[iG2_TRISIDE_LONGEST], axes[0] ); + VectorNormalize2( sides[iG2_TRISIDE_SHORTEST], axes[1] ); + + // project shortest side so that it is exactly 90 degrees to the longer side + d = DotProduct( axes[0], axes[1] ); + VectorMA( axes[0], -d, axes[1], axes[0] ); + VectorNormalize2( axes[0], axes[0] ); + + CrossProduct( sides[iG2_TRISIDE_LONGEST], sides[iG2_TRISIDE_SHORTEST], axes[2] ); + VectorNormalize2( axes[2], axes[2] ); + + // set up location in world space of the origin point in out going matrix + retMatrix.matrix[0][3] = pTri[MDX_TAG_ORIGIN][0]; + retMatrix.matrix[1][3] = pTri[MDX_TAG_ORIGIN][1]; + retMatrix.matrix[2][3] = pTri[MDX_TAG_ORIGIN][2]; + + // copy axis to matrix - do some magic to orient minus Y to positive X and so on so bolt on stuff is oriented correctly + retMatrix.matrix[0][0] = axes[1][0]; + retMatrix.matrix[0][1] = axes[0][0]; + retMatrix.matrix[0][2] = -axes[2][0]; + + retMatrix.matrix[1][0] = axes[1][1]; + retMatrix.matrix[1][1] = axes[0][1]; + retMatrix.matrix[1][2] = -axes[2][1]; + + retMatrix.matrix[2][0] = axes[1][2]; + retMatrix.matrix[2][1] = axes[0][2]; + retMatrix.matrix[2][2] = -axes[2][2]; + } + +} + + +void G2_GetBoltMatrixLow(CGhoul2Info &ghoul2,int boltNum,const vec3_t scale,mdxaBone_t &retMatrix) +{ + if (!ghoul2.mBoneCache) + { + retMatrix=identityMatrix; + return; + } + assert(ghoul2.mBoneCache); + CBoneCache &boneCache=*ghoul2.mBoneCache; + assert(boneCache.mod); + boltInfo_v &boltList=ghoul2.mBltlist; + assert(boltNum>=0&&boltNum=0) + { + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)boneCache.header + sizeof(mdxaHeader_t) + offsets->offsets[boltList[boltNum].boneNumber]); + Multiply_3x4Matrix(&retMatrix, &boneCache.EvalUnsmooth(boltList[boltNum].boneNumber), &skel->BasePoseMat); + } + else if (boltList[boltNum].surfaceNumber>=0) + { + const surfaceInfo_t *surfInfo=0; + { + int i; + for (i=0;isurface<10000) + { + surface = (mdxmSurface_t *)G2_FindSurface(boneCache.mod,surfInfo->surface, 0); + } + G2_ProcessSurfaceBolt2(boneCache,surface,boltNum,boltList,surfInfo,(model_t *)boneCache.mod,retMatrix); + } + else + { + // we have a bolt without a bone or surface, not a huge problem but we ought to at least clear the bolt matrix + retMatrix=identityMatrix; + } +} + + + +void G2API_SetSurfaceOnOffFromSkin (CGhoul2Info *ghlInfo, qhandle_t renderSkin) +{ + int j; + const skin_t *skin = R_GetSkinByHandle( renderSkin ); + //FIXME: using skin handles means we have to increase the numsurfs in a skin, but reading directly would cause file hits, we need another way to cache or just deal with the larger skin_t + + if (skin) + { + ghlInfo->mSlist.clear(); //remove any overrides we had before. + ghlInfo->mMeshFrameNum = 0; + for ( j = 0 ; j < skin->numSurfaces ; j++ ) + { + int flags; + int surfaceNum = G2_IsSurfaceLegal(ghlInfo->currentModel, skin->surfaces[j]->name, &flags); + // the names have both been lowercased + if ( !(flags&G2SURFACEFLAG_OFF) && !strcmp( skin->surfaces[j]->shader->name , "*off") ) + { + G2_SetSurfaceOnOff(ghlInfo, skin->surfaces[j]->name, G2SURFACEFLAG_OFF); + } + else + { + //if ( strcmp( &skin->surfaces[j]->name[strlen(skin->surfaces[j]->name)-4],"_off") ) + if ( (surfaceNum != -1) && (!(flags&G2SURFACEFLAG_OFF)) ) //only turn on if it's not an "_off" surface + { + //G2_SetSurfaceOnOff(ghlInfo, skin->surfaces[j]->name, 0); + } + } + } + } +} + +int zfFaceShaders[3] = { -1, -1, -1 }; +int tfTorsoShader = -1; + +// set up each surface ready for rendering in the back end +void RenderSurfaces(CRenderSurface &RS) +{ + int i; + const shader_t *shader = 0; + int offFlags = 0; +#ifdef _G2_GORE + bool drawGore = true; +#endif + + + assert(RS.currentModel); + assert(RS.currentModel->mdxm); + // back track and get the surfinfo struct for this surface + mdxmSurface_t *surface = (mdxmSurface_t *)G2_FindSurface(RS.currentModel, RS.surfaceNum, RS.lod); + mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)RS.currentModel->mdxm + sizeof(mdxmHeader_t)); + mdxmSurfHierarchy_t *surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surface->thisSurfaceIndex]); + + // see if we have an override surface in the surface list + const surfaceInfo_t *surfOverride = G2_FindOverrideSurface(RS.surfaceNum, RS.rootSList); + + // really, we should use the default flags for this surface unless it's been overriden + offFlags = surfInfo->flags; + + // set the off flags if we have some + if (surfOverride) + { + offFlags = surfOverride->offFlags; + } + + // if this surface is not off, add it to the shader render list + if (!offFlags) + { + if ( RS.cust_shader ) + { + shader = RS.cust_shader; + } + else if ( RS.skin ) + { + int j; + + // match the surface name to something in the skin file + shader = R_GetShaderByHandle( surfInfo->shaderIndex ); //tr.defaultShader; + for ( j = 0 ; j < RS.skin->numSurfaces ; j++ ) + { + // the names have both been lowercased + if ( !strcmp( RS.skin->surfaces[j]->name, surfInfo->name ) ) + { + shader = RS.skin->surfaces[j]->shader; + break; + } + } + } + else + { + shader = R_GetShaderByHandle( surfInfo->shaderIndex ); + } + + // we will add shadows even if the main object isn't visible in the view + // stencil shadows can't do personal models unless I polyhedron clip + //using z-fail now so can do personal models -rww + if ( !RS.personalModel + && r_shadows->integer == 2 + && (RS.renderfx & RF_SHADOW_PLANE ) + && !(RS.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE) + { +#ifdef _XBOX + // I defy anyone to make a cheaper hack than this + if( (shader->index == zfFaceShaders[0] && RS.surfaceNum == 12) || + (shader->index == zfFaceShaders[1] && RS.surfaceNum == 6) || + (shader->index == zfFaceShaders[2] && RS.surfaceNum == 9) || + (shader->index == 0)) { + } + else { +#endif + // set the surface info to point at the where the transformed bone list is going to be for when the surface gets rendered out + CRenderableSurface *newSurf = AllocRS(); +#ifdef _XBOX + // On Xbox, we always use the lowest LOD + mdxmSurface_t *lowsurface = (mdxmSurface_t *)G2_FindSurface(RS.currentModel, RS.surfaceNum, RS.currentModel->numLods-1); + newSurf->surfaceData = lowsurface; +#else + if (surface->numVerts >= SHADER_MAX_VERTEXES/2) + { //we need numVerts*2 xyz slots free in tess to do shadow, if this surf is going to exceed that then let's try the lowest lod -rww + mdxmSurface_t *lowsurface = (mdxmSurface_t *)G2_FindSurface(RS.currentModel, RS.surfaceNum, RS.currentModel->numLods-1); + newSurf->surfaceData = lowsurface; + } + else + { + newSurf->surfaceData = surface; + } +#endif + newSurf->boneCache = RS.boneCache; +#ifdef _XBOX + // Sentry models screw up the stencil shadows.... + if(strstr(shader->name, "sentry") > 0) { + R_AddDrawSurf( (surfaceType_t *)newSurf, tr.projectionShadowShader, 0, qfalse ); + } + else +#endif + R_AddDrawSurf( (surfaceType_t *)newSurf, tr.shadowShader, 0, qfalse ); +#ifdef _XBOX + } +#endif + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 +// && RS.fogNum == 0 + && (RS.renderfx & RF_SHADOW_PLANE ) + && !(RS.renderfx & ( RF_NOSHADOW ) ) + && shader->sort == SS_OPAQUE ) + { // set the surface info to point at the where the transformed bone list is going to be for when the surface gets rendered out + CRenderableSurface *newSurf = AllocRS(); + newSurf->surfaceData = surface; + newSurf->boneCache = RS.boneCache; + R_AddDrawSurf( (surfaceType_t *)newSurf, tr.projectionShadowShader, 0, qfalse ); + } + + // don't add third_person objects if not viewing through a portal + if ( !RS.personalModel ) + { + CRenderableSurface *newSurf = AllocRS(); + + extern bool in_camera; + if(shader->index == tfTorsoShader && in_camera == qtrue) { + mdxmSurface_t *lowsurface = (mdxmSurface_t *)G2_FindSurface(RS.currentModel, RS.surfaceNum, 1); + newSurf->surfaceData = lowsurface; + } + else { + // set the surface info to point at the where the transformed bone list is going to be for when the surface gets rendered out + newSurf->surfaceData = surface; + } + + newSurf->boneCache = RS.boneCache; + R_AddDrawSurf( (surfaceType_t *)newSurf, shader, RS.fogNum, qfalse ); + +#ifdef _G2_GORE + if (RS.gore_set && drawGore) + { + int curTime = G2API_GetTime(tr.refdef.time); + pair::iterator,multimap::iterator> range= + RS.gore_set->mGoreRecords.equal_range(RS.surfaceNum); + multimap::iterator k,kcur; + CRenderableSurface *last=newSurf; + for (k=range.first;k!=range.second;) + { + kcur=k; + k++; + GoreTextureCoordinates *tex=FindGoreRecord((*kcur).second.mGoreTag); + if (!tex || // it is gone, lets get rid of it + (*kcur).second.mDeleteTime && curTime>=(*kcur).second.mDeleteTime) // out of time + { + if (tex) + { + (*tex).~GoreTextureCoordinates(); + //I don't know what's going on here, it should call the destructor for + //this when it erases the record but sometimes it doesn't. -rww + } + + RS.gore_set->mGoreRecords.erase(kcur); + } + else if (tex->tex[RS.lod]) + { + CRenderableSurface *newSurf2 = AllocRS(); + *newSurf2=*newSurf; + newSurf2->goreChain=0; + newSurf2->alternateTex=tex->tex[RS.lod]; + newSurf2->scale=1.0f; + newSurf2->fade=1.0f; + newSurf2->impactTime=1.0f; // done with + int magicFactor42=500; // ms, impact time + if (curTime>(*kcur).second.mGoreGrowStartTime && curTime<(*kcur).second.mGoreGrowStartTime+magicFactor42) + { + newSurf2->impactTime=float(curTime-(*kcur).second.mGoreGrowStartTime)/float(magicFactor42); // linear + } + if (curTime<(*kcur).second.mGoreGrowEndTime) + { + newSurf2->scale=1.0f/((curTime-(*kcur).second.mGoreGrowStartTime)*(*kcur).second.mGoreGrowFactor + (*kcur).second.mGoreGrowOffset); + if (newSurf2->scale<1.0f) + { + newSurf2->scale=1.0f; + } + } + shader_t *gshader; + if ((*kcur).second.shader) + { + gshader=R_GetShaderByHandle((*kcur).second.shader); + } + else + { + gshader=R_GetShaderByHandle(goreShader); + } + + // Set fade on surf. + //Only if we have a fade time set, and let us fade on rgb if we want -rww + if ((*kcur).second.mDeleteTime && (*kcur).second.mFadeTime) + { + if ((*kcur).second.mDeleteTime - curTime < (*kcur).second.mFadeTime) + { + newSurf2->fade=(float)((*kcur).second.mDeleteTime - curTime)/(*kcur).second.mFadeTime; + if ((*kcur).second.mFadeRGB) + { //RGB fades are scaled from 2.0f to 3.0f (simply to differentiate) + newSurf2->fade += 2.0f; + + if (newSurf2->fade < 2.01f) + { + newSurf2->fade = 2.01f; + } + } + } + } + + last->goreChain=newSurf2; + last=newSurf2; + R_AddDrawSurf( (surfaceType_t *)newSurf2,gshader, RS.fogNum, qfalse ); + } + } + } +#endif + } + } + + // if we are turning off all descendants, then stop this recursion now + if (offFlags & G2SURFACEFLAG_NODESCENDANTS) + { + return; + } + + // now recursively call for the children + for (i=0; i< surfInfo->numChildren; i++) + { + RS.surfaceNum = surfInfo->childIndexes[i]; + RenderSurfaces(RS); + } +} + + +// sort all the ghoul models in this list so if they go in reference order. This will ensure the bolt on's are attached to the right place +// on the previous model, since it ensures the model being attached to is built and rendered first. + +// NOTE!! This assumes at least one model will NOT have a parent. If it does - we are screwed +static void G2_Sort_Models(CGhoul2Info_v &ghoul2, int * const modelList, int * const modelCount) +{ + int startPoint, endPoint; + int i, boltTo, j; + + *modelCount = 0; + + // first walk all the possible ghoul2 models, and stuff the out array with those with no parents + for (i=0; i> MODEL_SHIFT) & MODEL_AND; + // is it any of the models we just added to the list? + for (j=startPoint; jvalue); +} + +#ifdef _XBOX +char entityVisList[MAX_GENTITIES + 1000 + 256]; +#endif + +/* +============== +R_AddGHOULSurfaces +============== +*/ +void R_AddGhoulSurfaces( trRefEntity_t *ent ) { + shader_t *cust_shader = 0; +#ifdef _G2_GORE + shader_t *gore_shader = 0; +#endif + int fogNum = 0; + bool personalModel; + int cull; + int i, whichLod, j; + skin_t *skin; + int modelCount; + mdxaBone_t rootMatrix; + + // if we don't want ghoul2 models, then return + if (r_noGhoul2->integer) + { + return; + } + + assert (ent->e.ghoul2); //entity is foo if it has a glm model handle but no ghoul2 pointer! + CGhoul2Info_v &ghoul2 = *ent->e.ghoul2; + + if (!G2_SetupModelPointers(ghoul2)) + { + return; + } + + int currentTime=G2API_GetTime(tr.refdef.time); + + + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + cull = R_GCullModel (ent ); + if ( cull == CULL_OUT ) + { + return; + } + + HackadelicOnClient=true; + // are any of these models setting a new origin? + RootMatrix(ghoul2,currentTime, ent->e.modelScale,rootMatrix); + + // don't add third_person objects if not in a portal + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + + int modelList[32]; + assert(ghoul2.size()<=31); + modelList[31]=548; + + // set up lighting now that we know we aren't culled +#ifdef VV_LIGHTING + if ( !personalModel ) { + VVLightMan.R_SetupEntityLighting( &tr.refdef, ent ); +#else + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); +#endif + } + + // see if we are in a fog volume + fogNum = R_GComputeFogNum( ent ); + + // sort the ghoul 2 models so bolt ons get bolted to the right model + G2_Sort_Models(ghoul2, modelList, &modelCount); + assert(modelList[31]==548); + +#ifdef _G2_GORE + if (goreShader == -1) + { + goreShader=RE_RegisterShader("gfx/damage/burnmark1"); + } +#endif + + // construct a world matrix for this entity + G2_GenerateWorldMatrix(ent->e.angles, ent->e.origin); + + // walk each possible model for this entity and try rendering it out + for (j=0; je.customShader) + { + cust_shader = R_GetShaderByHandle(ent->e.customShader ); + } + else + { + cust_shader = NULL; + // figure out the custom skin thing + if (ent->e.customSkin) + { + skin = R_GetSkinByHandle(ent->e.customSkin ); + } + else if ( ghoul2[i].mSkin > 0 && ghoul2[i].mSkin < tr.numSkins ) + { + skin = R_GetSkinByHandle( ghoul2[i].mSkin ); + } + } + + if (j&&ghoul2[i].mModelBoltLink != -1) + { + int boltMod = (ghoul2[i].mModelBoltLink >> MODEL_SHIFT) & MODEL_AND; + int boltNum = (ghoul2[i].mModelBoltLink >> BOLT_SHIFT) & BOLT_AND; + mdxaBone_t bolt; + G2_GetBoltMatrixLow(ghoul2[boltMod],boltNum,ent->e.modelScale,bolt); + G2_TransformGhoulBones(ghoul2[i].mBlist,bolt, ghoul2[i],currentTime); + } + else + { + G2_TransformGhoulBones(ghoul2[i].mBlist, rootMatrix, ghoul2[i],currentTime); + } + if ( ent->e.renderfx & RF_G2MINLOD ) + { + whichLod = G2_ComputeLOD( ent, ghoul2[i].currentModel, 10 ); + } else + { + whichLod = G2_ComputeLOD( ent, ghoul2[i].currentModel, ghoul2[i].mLodBias ); + } + G2_FindOverrideSurface(-1,ghoul2[i].mSlist); //reset the quick surface override lookup; +#ifdef _G2_GORE + CGoreSet *gore=0; + if (ghoul2[i].mGoreSetTag) + { + gore=FindGoreSet(ghoul2[i].mGoreSetTag); + if (!gore) // my gore is gone, so remove it + { + ghoul2[i].mGoreSetTag=0; + } + } + + CRenderSurface RS(ghoul2[i].mSurfaceRoot, ghoul2[i].mSlist, cust_shader, fogNum, personalModel, ghoul2[i].mBoneCache, ent->e.renderfx, skin,ghoul2[i].currentModel, whichLod, ghoul2[i].mBltlist, gore_shader, gore); +#else + CRenderSurface RS(ghoul2[i].mSurfaceRoot, ghoul2[i].mSlist, cust_shader, fogNum, personalModel, ghoul2[i].mBoneCache, ent->e.renderfx, skin,ghoul2[i].currentModel, whichLod, ghoul2[i].mBltlist); +#endif + if (!personalModel && (RS.renderfx & RF_SHADOW_PLANE) && !bInShadowRange(ent->e.origin)) + { + RS.renderfx |= RF_NOSHADOW; + } + RenderSurfaces(RS); + } + } + HackadelicOnClient=false; +} + +bool G2_NeedsRecalc(CGhoul2Info *ghlInfo,int frameNum) +{ + G2_SetupModelPointers(ghlInfo); + // not sure if I still need this test, probably + if (ghlInfo->mSkelFrameNum!=frameNum|| + !ghlInfo->mBoneCache|| + ghlInfo->mBoneCache->mod!=ghlInfo->currentModel) + { + ghlInfo->mSkelFrameNum=frameNum; + return true; + } + return false; +} + +/* +============== +G2_ConstructGhoulSkeleton - builds a complete skeleton for all ghoul models in a CGhoul2Info_v class - using LOD 0 +============== +*/ +void G2_ConstructGhoulSkeleton( CGhoul2Info_v &ghoul2,const int frameNum,bool checkForNewOrigin,const vec3_t scale) +{ + int i, j; + int modelCount; + mdxaBone_t rootMatrix; + + int modelList[32]; + assert(ghoul2.size()<=31); + modelList[31]=548; + + if (checkForNewOrigin) + { + RootMatrix(ghoul2,frameNum,scale,rootMatrix); + } + else + { + rootMatrix = identityMatrix; + } + + G2_Sort_Models(ghoul2, modelList, &modelCount); + assert(modelList[31]==548); + + for (j=0; j> MODEL_SHIFT) & MODEL_AND; + int boltNum = (ghoul2[i].mModelBoltLink >> BOLT_SHIFT) & BOLT_AND; + + mdxaBone_t bolt; + G2_GetBoltMatrixLow(ghoul2[boltMod],boltNum,scale,bolt); + G2_TransformGhoulBones(ghoul2[i].mBlist,bolt,ghoul2[i],frameNum,checkForNewOrigin); + } + else + { + G2_TransformGhoulBones(ghoul2[i].mBlist,rootMatrix,ghoul2[i],frameNum,checkForNewOrigin); + } + } + } +} + +/* +============== +RB_SurfaceGhoul +============== +*/ +void RB_SurfaceGhoul( CRenderableSurface *surf ) +{ +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceTimer_RB_SurfaceGhoul.Start(); +#endif + + int j, k; + int baseIndex, baseVertex; + int numVerts; + mdxmVertex_t *v; + int *triangles; + int indexes; + glIndex_t *tessIndexes; + mdxmVertexTexCoord_t *pTexCoords; + int *piBoneReferences; + +#ifdef _XBOX + UINT result; + HRESULT hr; + trRefEntity_t *ent = backEnd.currentEntity; + bool isPlayer = false; + vec3_t bounds[2]; + float radius, largestScale; + + isPlayer = strstr(tess.shader->name, "players") > 0 ? true : false; + + // The main player will always be visible + if(ent->e.number == 0 || strstr(tess.shader->name, "rancor")) + { + ent->visible = 1; + entityVisList[0] = 1; + } + + extern bool in_camera; + if(in_camera) + { + entityVisList[ent->e.number] = 1; + ent->visible = 1; + } + + if(ent->visible == -1 && isPlayer) + { + // Get the visibility test from the last frame + if(entityVisList[ent->e.number] == -1) + entityVisList[ent->e.number] = 0; + else { + hr = glw_state->device->GetVisibilityTestResult( ent->e.number, &result, NULL ); + if( hr == D3D_OK) + entityVisList[ent->e.number] = ((int)result) ? 1 : 0; + else + entityVisList[ent->e.number] = 1; + } + + ent->visible = entityVisList[ent->e.number]; + + // Run a visibility test to determine if this model should be rendered + vec3_t v; + float matrix[16]; + + // scale the radius if need be + largestScale = ent->e.modelScale[0]; + + if (ent->e.modelScale[1] > largestScale) + { + largestScale = ent->e.modelScale[1]; + } + if (ent->e.modelScale[2] > largestScale) + { + largestScale = ent->e.modelScale[2]; + } + if (!largestScale) + { + largestScale = 1; + } + + // The mighty hack to prevent insanely huge bounding boxes + // A radius of 1000 for Kyle??????? What the hell... + // The rancors and the map objects are the only ones that + // should have a radius > 64 + radius = ent->e.radius; + /*if(ent->e.radius > 300.0f && strstr(tess.shader->name, "rancor") == 0 ) + radius = 64.0f;*/ + + bounds[0][0] = -(radius * largestScale); + bounds[0][1] = -(radius * largestScale); + bounds[0][2] = -(radius * largestScale); + + bounds[1][0] = radius * largestScale; + bounds[1][1] = radius * largestScale; + bounds[1][2] = radius * largestScale; + + RB_RunVisTest(ent->e.number, bounds); + } + + if(ent->visible == 0) + { + // It's possible for the camera to be inside a bounding volume and falsely + // report the object has failed it's vis test, test for that + vec3_t cameraOrigin; + VectorCopy(backEnd.ori.viewOrigin, cameraOrigin); + + radius = ent->e.radius; + + // scale the radius if need be + largestScale = ent->e.modelScale[0]; + + if (ent->e.modelScale[1] > largestScale) + { + largestScale = ent->e.modelScale[1]; + } + if (ent->e.modelScale[2] > largestScale) + { + largestScale = ent->e.modelScale[2]; + } + if (!largestScale) + { + largestScale = 1; + } + + bounds[0][0] = -(radius * largestScale); + bounds[0][1] = -(radius * largestScale); + bounds[0][2] = -(radius * largestScale); + + bounds[1][0] = radius * largestScale; + bounds[1][1] = radius * largestScale; + bounds[1][2] = radius * largestScale; + + if(cameraOrigin[0] >= bounds[0][0] && + cameraOrigin[0] <= bounds[1][0] && + cameraOrigin[1] >= bounds[0][1] && + cameraOrigin[1] <= bounds[1][1] && + cameraOrigin[2] >= bounds[0][2] && + cameraOrigin[2] <= bounds[1][2]) + ent->visible = 1; + else + { + // Only do the camera/bounding check once per frame + ent->visible = -2; + return; + } + } + + if(ent->visible == -2) + return; +#endif + +#ifdef _G2_GORE + if (surf->alternateTex) + { + + // a gore surface ready to go. + + /* + sizeof(int)+ // num verts + sizeof(int)+ // num tris + sizeof(int)*newNumVerts+ // which verts to copy from original surface + sizeof(float)*4*newNumVerts+ // storgage for deformed verts + sizeof(float)*4*newNumVerts+ // storgage for deformed normal + sizeof(float)*2*newNumVerts+ // texture coordinates + sizeof(int)*newNumTris*3; // new indecies + */ + + int *data=(int *)surf->alternateTex; + numVerts=*data++; + indexes=(*data++); + // first up, sanity check our numbers + RB_CheckOverflow(numVerts,indexes); + indexes*=3; + + data+=numVerts; + + baseIndex = tess.numIndexes; + baseVertex = tess.numVertexes; + + memcpy(&tess.xyz[baseVertex][0],data,sizeof(float)*4*numVerts); + data+=4*numVerts; + memcpy(&tess.normal[baseVertex][0],data,sizeof(float)*4*numVerts); + data+=4*numVerts; + assert(numVerts>0); + + //float *texCoords = tess.texCoords[0][baseVertex]; + float *texCoords = tess.texCoords[baseVertex][0]; + int hack = baseVertex; + //rww - since the array is arranged as such we cannot increment + //the relative memory position to get where we want. Maybe this + //is why sof2 has the texCoords array reversed. In any case, I + //am currently too lazy to get around it. + //Or can you += array[.][x]+2? + if (surf->scale>1.0f) + { + for ( j = 0; j < numVerts; j++) + { + texCoords[0]=((*(float *)data)-0.5f)*surf->scale+0.5f; + data++; + texCoords[1]=((*(float *)data)-0.5f)*surf->scale+0.5f; + data++; + //texCoords+=2;// Size of gore (s,t). + hack++; + texCoords = tess.texCoords[hack][0]; + } + } + else + { + for (j=0;jfade) + { + static int lFade; + static int j; + + if (surf->fade<1.0) + { + tess.fading = true; + lFade = myftol(254.4f*surf->fade); + + for (j=0;jfade > 2.0f && surf->fade < 3.0f) + { //hack to fade out on RGB if desired (don't want to add more to CRenderableSurface) -rww + tess.fading = true; + lFade = myftol(254.4f*(surf->fade-2.0f)); + + for (j=0;jsurfaceData; + + CBoneCache *bones = surf->boneCache; + + +#ifdef VV_LIGHTING + // Set any dynamic lighting needed + if(backEnd.currentEntity->dlightBits) + tess.dlightBits = backEnd.currentEntity->dlightBits; + if(tess.shader == tr.shadowShader) + tess.dlightBits = 0; +#endif + + // first up, sanity check our numbers + RB_CheckOverflow( surface->numVerts, surface->numTriangles ); + + // + // deform the vertexes by the lerped bones + // + + // first up, sanity check our numbers + baseVertex = tess.numVertexes; + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + baseIndex = tess.numIndexes; +#if 0 + indexes = surface->numTriangles * 3; + for (j = 0 ; j < indexes ; j++) { + tess.indexes[baseIndex + j] = baseVertex + triangles[j]; + } + tess.numIndexes += indexes; +#else + indexes = surface->numTriangles; //*3; //unrolled 3 times, don't multiply + tessIndexes = &tess.indexes[baseIndex]; + for (j = 0 ; j < indexes ; j++) { + *tessIndexes++ = baseVertex + *triangles++; + *tessIndexes++ = baseVertex + *triangles++; + *tessIndexes++ = baseVertex + *triangles++; + } + tess.numIndexes += indexes*3; +#endif + + numVerts = surface->numVerts; + +#ifdef _XBOX + TransformRenderSurface(surface, surf->boneCache, &tess); + + +#else + piBoneReferences = (int*) ((byte*)surface + surface->ofsBoneReferences); + baseVertex = tess.numVertexes; + v = (mdxmVertex_t *) ((byte *)surface + surface->ofsVerts); + pTexCoords = (mdxmVertexTexCoord_t *) &v[numVerts]; + +// if (r_ghoul2fastnormals&&r_ghoul2fastnormals->integer==0) +#if 0 + if (0) + { + for ( j = 0; j < numVerts; j++, baseVertex++,v++ ) + { + const int iNumWeights = G2_GetVertWeights( v ); + + float fTotalWeight = 0.0f; + + k=0; + int iBoneIndex = G2_GetVertBoneIndex( v, k ); + float fBoneWeight = G2_GetVertBoneWeight( v, k, fTotalWeight, iNumWeights ); + const mdxaBone_t *bone = &bones->EvalRender(piBoneReferences[iBoneIndex]); + + tess.xyz[baseVertex][0] = fBoneWeight * ( DotProduct( bone->matrix[0], v->vertCoords ) + bone->matrix[0][3] ); + tess.xyz[baseVertex][1] = fBoneWeight * ( DotProduct( bone->matrix[1], v->vertCoords ) + bone->matrix[1][3] ); + tess.xyz[baseVertex][2] = fBoneWeight * ( DotProduct( bone->matrix[2], v->vertCoords ) + bone->matrix[2][3] ); + + tess.normal[baseVertex][0] = fBoneWeight * DotProduct( bone->matrix[0], v->normal ); + tess.normal[baseVertex][1] = fBoneWeight * DotProduct( bone->matrix[1], v->normal ); + tess.normal[baseVertex][2] = fBoneWeight * DotProduct( bone->matrix[2], v->normal ); + + for ( k++ ; k < iNumWeights ; k++) + { + iBoneIndex = G2_GetVertBoneIndex( v, k ); + fBoneWeight = G2_GetVertBoneWeight( v, k, fTotalWeight, iNumWeights ); + + bone = &bones->EvalRender(piBoneReferences[iBoneIndex]); + + tess.xyz[baseVertex][0] += fBoneWeight * ( DotProduct( bone->matrix[0], v->vertCoords ) + bone->matrix[0][3] ); + tess.xyz[baseVertex][1] += fBoneWeight * ( DotProduct( bone->matrix[1], v->vertCoords ) + bone->matrix[1][3] ); + tess.xyz[baseVertex][2] += fBoneWeight * ( DotProduct( bone->matrix[2], v->vertCoords ) + bone->matrix[2][3] ); + + tess.normal[baseVertex][0] += fBoneWeight * DotProduct( bone->matrix[0], v->normal ); + tess.normal[baseVertex][1] += fBoneWeight * DotProduct( bone->matrix[1], v->normal ); + tess.normal[baseVertex][2] += fBoneWeight * DotProduct( bone->matrix[2], v->normal ); + } + + tess.texCoords[baseVertex][0][0] = pTexCoords[j].texCoords[0]; + tess.texCoords[baseVertex][0][1] = pTexCoords[j].texCoords[1]; + } + } + else + { +#endif + float fTotalWeight; + float fBoneWeight; + float t1; + float t2; + const mdxaBone_t *bone; + const mdxaBone_t *bone2; + for ( j = 0; j < numVerts; j++, baseVertex++,v++ ) + { + + bone = &bones->EvalRender(piBoneReferences[G2_GetVertBoneIndex( v, 0 )]); + int iNumWeights = G2_GetVertWeights( v ); + tess.normal[baseVertex][0] = DotProduct( bone->matrix[0], v->normal ); + tess.normal[baseVertex][1] = DotProduct( bone->matrix[1], v->normal ); + tess.normal[baseVertex][2] = DotProduct( bone->matrix[2], v->normal ); + + if (iNumWeights==1) + { + tess.xyz[baseVertex][0] = ( DotProduct( bone->matrix[0], v->vertCoords ) + bone->matrix[0][3] ); + tess.xyz[baseVertex][1] = ( DotProduct( bone->matrix[1], v->vertCoords ) + bone->matrix[1][3] ); + tess.xyz[baseVertex][2] = ( DotProduct( bone->matrix[2], v->vertCoords ) + bone->matrix[2][3] ); + } + else + { + fBoneWeight = G2_GetVertBoneWeightNotSlow( v, 0); + if (iNumWeights==2) + { + bone2 = &bones->EvalRender(piBoneReferences[G2_GetVertBoneIndex( v, 1 )]); + /* + useless transposition + tess.xyz[baseVertex][0] = + v[0]*(w*(bone->matrix[0][0]-bone2->matrix[0][0])+bone2->matrix[0][0])+ + v[1]*(w*(bone->matrix[0][1]-bone2->matrix[0][1])+bone2->matrix[0][1])+ + v[2]*(w*(bone->matrix[0][2]-bone2->matrix[0][2])+bone2->matrix[0][2])+ + w*(bone->matrix[0][3]-bone2->matrix[0][3]) + bone2->matrix[0][3]; + */ + t1 = ( DotProduct( bone->matrix[0], v->vertCoords ) + bone->matrix[0][3] ); + t2 = ( DotProduct( bone2->matrix[0], v->vertCoords ) + bone2->matrix[0][3] ); + tess.xyz[baseVertex][0] = fBoneWeight * (t1-t2) + t2; + t1 = ( DotProduct( bone->matrix[1], v->vertCoords ) + bone->matrix[1][3] ); + t2 = ( DotProduct( bone2->matrix[1], v->vertCoords ) + bone2->matrix[1][3] ); + tess.xyz[baseVertex][1] = fBoneWeight * (t1-t2) + t2; + t1 = ( DotProduct( bone->matrix[2], v->vertCoords ) + bone->matrix[2][3] ); + t2 = ( DotProduct( bone2->matrix[2], v->vertCoords ) + bone2->matrix[2][3] ); + tess.xyz[baseVertex][2] = fBoneWeight * (t1-t2) + t2; + } + else + { + + tess.xyz[baseVertex][0] = fBoneWeight * ( DotProduct( bone->matrix[0], v->vertCoords ) + bone->matrix[0][3] ); + tess.xyz[baseVertex][1] = fBoneWeight * ( DotProduct( bone->matrix[1], v->vertCoords ) + bone->matrix[1][3] ); + tess.xyz[baseVertex][2] = fBoneWeight * ( DotProduct( bone->matrix[2], v->vertCoords ) + bone->matrix[2][3] ); + + fTotalWeight=fBoneWeight; + for (k=1; k < iNumWeights-1 ; k++) + { + bone = &bones->EvalRender(piBoneReferences[G2_GetVertBoneIndex( v, k )]); + fBoneWeight = G2_GetVertBoneWeightNotSlow( v, k); + fTotalWeight += fBoneWeight; + + tess.xyz[baseVertex][0] += fBoneWeight * ( DotProduct( bone->matrix[0], v->vertCoords ) + bone->matrix[0][3] ); + tess.xyz[baseVertex][1] += fBoneWeight * ( DotProduct( bone->matrix[1], v->vertCoords ) + bone->matrix[1][3] ); + tess.xyz[baseVertex][2] += fBoneWeight * ( DotProduct( bone->matrix[2], v->vertCoords ) + bone->matrix[2][3] ); + } + bone = &bones->EvalRender(piBoneReferences[G2_GetVertBoneIndex( v, k )]); + fBoneWeight = 1.0f-fTotalWeight; + + tess.xyz[baseVertex][0] += fBoneWeight * ( DotProduct( bone->matrix[0], v->vertCoords ) + bone->matrix[0][3] ); + tess.xyz[baseVertex][1] += fBoneWeight * ( DotProduct( bone->matrix[1], v->vertCoords ) + bone->matrix[1][3] ); + tess.xyz[baseVertex][2] += fBoneWeight * ( DotProduct( bone->matrix[2], v->vertCoords ) + bone->matrix[2][3] ); + } + } + + tess.texCoords[baseVertex][0][0] = pTexCoords[j].texCoords[0]; + tess.texCoords[baseVertex][0][1] = pTexCoords[j].texCoords[1]; + } +#if 0 + } +#endif +#endif // _XBOX + +#ifdef _G2_GORE + while (surf->goreChain) + { + surf=(CRenderableSurface *)surf->goreChain; + if (surf->alternateTex) + { + // get a gore surface ready to go. + + /* + sizeof(int)+ // num verts + sizeof(int)+ // num tris + sizeof(int)*newNumVerts+ // which verts to copy from original surface + sizeof(float)*4*newNumVerts+ // storgage for deformed verts + sizeof(float)*4*newNumVerts+ // storgage for deformed normal + sizeof(float)*2*newNumVerts+ // texture coordinates + sizeof(int)*newNumTris*3; // new indecies + */ + + int *data=(int *)surf->alternateTex; + int gnumVerts=*data++; + data++; + + float *fdata=(float *)data; + fdata+=gnumVerts; + for (j=0;j=0&&data[j]=0&&data[j]numVerts; + +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_RB_SurfaceGhoul += G2PerformanceTimer_RB_SurfaceGhoul.End(); +#endif +} + +/* +================= +R_LoadMDXM - load a Ghoul 2 Mesh file +================= +*/ +/* + +Some information used in the creation of the JK2 - JKA bone remap table + +These are the old bones: +Complete list of all 72 bones: + +*/ + +int OldToNewRemapTable[72] = { +0,// Bone 0: "model_root": Parent: "" (index -1) +1,// Bone 1: "pelvis": Parent: "model_root" (index 0) +2,// Bone 2: "Motion": Parent: "pelvis" (index 1) +3,// Bone 3: "lfemurYZ": Parent: "pelvis" (index 1) +4,// Bone 4: "lfemurX": Parent: "pelvis" (index 1) +5,// Bone 5: "ltibia": Parent: "pelvis" (index 1) +6,// Bone 6: "ltalus": Parent: "pelvis" (index 1) +6,// Bone 7: "ltarsal": Parent: "pelvis" (index 1) +7,// Bone 8: "rfemurYZ": Parent: "pelvis" (index 1) +8,// Bone 9: "rfemurX": Parent: "pelvis" (index 1) +9,// Bone10: "rtibia": Parent: "pelvis" (index 1) +10,// Bone11: "rtalus": Parent: "pelvis" (index 1) +10,// Bone12: "rtarsal": Parent: "pelvis" (index 1) +11,// Bone13: "lower_lumbar": Parent: "pelvis" (index 1) +12,// Bone14: "upper_lumbar": Parent: "lower_lumbar" (index 13) +13,// Bone15: "thoracic": Parent: "upper_lumbar" (index 14) +14,// Bone16: "cervical": Parent: "thoracic" (index 15) +15,// Bone17: "cranium": Parent: "cervical" (index 16) +16,// Bone18: "ceyebrow": Parent: "face_always_" (index 71) +17,// Bone19: "jaw": Parent: "face_always_" (index 71) +18,// Bone20: "lblip2": Parent: "face_always_" (index 71) +19,// Bone21: "leye": Parent: "face_always_" (index 71) +20,// Bone22: "rblip2": Parent: "face_always_" (index 71) +21,// Bone23: "ltlip2": Parent: "face_always_" (index 71) +22,// Bone24: "rtlip2": Parent: "face_always_" (index 71) +23,// Bone25: "reye": Parent: "face_always_" (index 71) +24,// Bone26: "rclavical": Parent: "thoracic" (index 15) +25,// Bone27: "rhumerus": Parent: "thoracic" (index 15) +26,// Bone28: "rhumerusX": Parent: "thoracic" (index 15) +27,// Bone29: "rradius": Parent: "thoracic" (index 15) +28,// Bone30: "rradiusX": Parent: "thoracic" (index 15) +29,// Bone31: "rhand": Parent: "thoracic" (index 15) +29,// Bone32: "mc7": Parent: "thoracic" (index 15) +34,// Bone33: "r_d5_j1": Parent: "thoracic" (index 15) +35,// Bone34: "r_d5_j2": Parent: "thoracic" (index 15) +35,// Bone35: "r_d5_j3": Parent: "thoracic" (index 15) +30,// Bone36: "r_d1_j1": Parent: "thoracic" (index 15) +31,// Bone37: "r_d1_j2": Parent: "thoracic" (index 15) +31,// Bone38: "r_d1_j3": Parent: "thoracic" (index 15) +32,// Bone39: "r_d2_j1": Parent: "thoracic" (index 15) +33,// Bone40: "r_d2_j2": Parent: "thoracic" (index 15) +33,// Bone41: "r_d2_j3": Parent: "thoracic" (index 15) +32,// Bone42: "r_d3_j1": Parent: "thoracic" (index 15) +33,// Bone43: "r_d3_j2": Parent: "thoracic" (index 15) +33,// Bone44: "r_d3_j3": Parent: "thoracic" (index 15) +34,// Bone45: "r_d4_j1": Parent: "thoracic" (index 15) +35,// Bone46: "r_d4_j2": Parent: "thoracic" (index 15) +35,// Bone47: "r_d4_j3": Parent: "thoracic" (index 15) +36,// Bone48: "rhang_tag_bone": Parent: "thoracic" (index 15) +37,// Bone49: "lclavical": Parent: "thoracic" (index 15) +38,// Bone50: "lhumerus": Parent: "thoracic" (index 15) +39,// Bone51: "lhumerusX": Parent: "thoracic" (index 15) +40,// Bone52: "lradius": Parent: "thoracic" (index 15) +41,// Bone53: "lradiusX": Parent: "thoracic" (index 15) +42,// Bone54: "lhand": Parent: "thoracic" (index 15) +42,// Bone55: "mc5": Parent: "thoracic" (index 15) +43,// Bone56: "l_d5_j1": Parent: "thoracic" (index 15) +44,// Bone57: "l_d5_j2": Parent: "thoracic" (index 15) +44,// Bone58: "l_d5_j3": Parent: "thoracic" (index 15) +43,// Bone59: "l_d4_j1": Parent: "thoracic" (index 15) +44,// Bone60: "l_d4_j2": Parent: "thoracic" (index 15) +44,// Bone61: "l_d4_j3": Parent: "thoracic" (index 15) +45,// Bone62: "l_d3_j1": Parent: "thoracic" (index 15) +46,// Bone63: "l_d3_j2": Parent: "thoracic" (index 15) +46,// Bone64: "l_d3_j3": Parent: "thoracic" (index 15) +45,// Bone65: "l_d2_j1": Parent: "thoracic" (index 15) +46,// Bone66: "l_d2_j2": Parent: "thoracic" (index 15) +46,// Bone67: "l_d2_j3": Parent: "thoracic" (index 15) +47,// Bone68: "l_d1_j1": Parent: "thoracic" (index 15) +48,// Bone69: "l_d1_j2": Parent: "thoracic" (index 15) +48,// Bone70: "l_d1_j3": Parent: "thoracic" (index 15) +52// Bone71: "face_always_": Parent: "cranium" (index 17) +}; + + +/* + +Bone 0: "model_root": + Parent: "" (index -1) + #Kids: 1 + Child 0: (index 1), name "pelvis" + +Bone 1: "pelvis": + Parent: "model_root" (index 0) + #Kids: 4 + Child 0: (index 2), name "Motion" + Child 1: (index 3), name "lfemurYZ" + Child 2: (index 7), name "rfemurYZ" + Child 3: (index 11), name "lower_lumbar" + +Bone 2: "Motion": + Parent: "pelvis" (index 1) + #Kids: 0 + +Bone 3: "lfemurYZ": + Parent: "pelvis" (index 1) + #Kids: 3 + Child 0: (index 4), name "lfemurX" + Child 1: (index 5), name "ltibia" + Child 2: (index 49), name "ltail" + +Bone 4: "lfemurX": + Parent: "lfemurYZ" (index 3) + #Kids: 0 + +Bone 5: "ltibia": + Parent: "lfemurYZ" (index 3) + #Kids: 1 + Child 0: (index 6), name "ltalus" + +Bone 6: "ltalus": + Parent: "ltibia" (index 5) + #Kids: 0 + +Bone 7: "rfemurYZ": + Parent: "pelvis" (index 1) + #Kids: 3 + Child 0: (index 8), name "rfemurX" + Child 1: (index 9), name "rtibia" + Child 2: (index 50), name "rtail" + +Bone 8: "rfemurX": + Parent: "rfemurYZ" (index 7) + #Kids: 0 + +Bone 9: "rtibia": + Parent: "rfemurYZ" (index 7) + #Kids: 1 + Child 0: (index 10), name "rtalus" + +Bone 10: "rtalus": + Parent: "rtibia" (index 9) + #Kids: 0 + +Bone 11: "lower_lumbar": + Parent: "pelvis" (index 1) + #Kids: 1 + Child 0: (index 12), name "upper_lumbar" + +Bone 12: "upper_lumbar": + Parent: "lower_lumbar" (index 11) + #Kids: 1 + Child 0: (index 13), name "thoracic" + +Bone 13: "thoracic": + Parent: "upper_lumbar" (index 12) + #Kids: 5 + Child 0: (index 14), name "cervical" + Child 1: (index 24), name "rclavical" + Child 2: (index 25), name "rhumerus" + Child 3: (index 37), name "lclavical" + Child 4: (index 38), name "lhumerus" + +Bone 14: "cervical": + Parent: "thoracic" (index 13) + #Kids: 1 + Child 0: (index 15), name "cranium" + +Bone 15: "cranium": + Parent: "cervical" (index 14) + #Kids: 1 + Child 0: (index 52), name "face_always_" + +Bone 16: "ceyebrow": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 17: "jaw": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 18: "lblip2": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 19: "leye": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 20: "rblip2": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 21: "ltlip2": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 22: "rtlip2": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 23: "reye": + Parent: "face_always_" (index 52) + #Kids: 0 + +Bone 24: "rclavical": + Parent: "thoracic" (index 13) + #Kids: 0 + +Bone 25: "rhumerus": + Parent: "thoracic" (index 13) + #Kids: 2 + Child 0: (index 26), name "rhumerusX" + Child 1: (index 27), name "rradius" + +Bone 26: "rhumerusX": + Parent: "rhumerus" (index 25) + #Kids: 0 + +Bone 27: "rradius": + Parent: "rhumerus" (index 25) + #Kids: 9 + Child 0: (index 28), name "rradiusX" + Child 1: (index 29), name "rhand" + Child 2: (index 30), name "r_d1_j1" + Child 3: (index 31), name "r_d1_j2" + Child 4: (index 32), name "r_d2_j1" + Child 5: (index 33), name "r_d2_j2" + Child 6: (index 34), name "r_d4_j1" + Child 7: (index 35), name "r_d4_j2" + Child 8: (index 36), name "rhang_tag_bone" + +Bone 28: "rradiusX": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 29: "rhand": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 30: "r_d1_j1": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 31: "r_d1_j2": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 32: "r_d2_j1": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 33: "r_d2_j2": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 34: "r_d4_j1": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 35: "r_d4_j2": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 36: "rhang_tag_bone": + Parent: "rradius" (index 27) + #Kids: 0 + +Bone 37: "lclavical": + Parent: "thoracic" (index 13) + #Kids: 0 + +Bone 38: "lhumerus": + Parent: "thoracic" (index 13) + #Kids: 2 + Child 0: (index 39), name "lhumerusX" + Child 1: (index 40), name "lradius" + +Bone 39: "lhumerusX": + Parent: "lhumerus" (index 38) + #Kids: 0 + +Bone 40: "lradius": + Parent: "lhumerus" (index 38) + #Kids: 9 + Child 0: (index 41), name "lradiusX" + Child 1: (index 42), name "lhand" + Child 2: (index 43), name "l_d4_j1" + Child 3: (index 44), name "l_d4_j2" + Child 4: (index 45), name "l_d2_j1" + Child 5: (index 46), name "l_d2_j2" + Child 6: (index 47), name "l_d1_j1" + Child 7: (index 48), name "l_d1_j2" + Child 8: (index 51), name "lhang_tag_bone" + +Bone 41: "lradiusX": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 42: "lhand": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 43: "l_d4_j1": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 44: "l_d4_j2": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 45: "l_d2_j1": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 46: "l_d2_j2": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 47: "l_d1_j1": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 48: "l_d1_j2": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 49: "ltail": + Parent: "lfemurYZ" (index 3) + #Kids: 0 + +Bone 50: "rtail": + Parent: "rfemurYZ" (index 7) + #Kids: 0 + +Bone 51: "lhang_tag_bone": + Parent: "lradius" (index 40) + #Kids: 0 + +Bone 52: "face_always_": + Parent: "cranium" (index 15) + #Kids: 8 + Child 0: (index 16), name "ceyebrow" + Child 1: (index 17), name "jaw" + Child 2: (index 18), name "lblip2" + Child 3: (index 19), name "leye" + Child 4: (index 20), name "rblip2" + Child 5: (index 21), name "ltlip2" + Child 6: (index 22), name "rtlip2" + Child 7: (index 23), name "reye" + + + +*/ + +qboolean R_LoadMDXM( model_t *mod, void *buffer, const char *mod_name, qboolean &bAlreadyCached ) { + int i, l, j; + mdxmHeader_t *pinmodel, *mdxm; + mdxmLOD_t *lod; + mdxmSurface_t *surf; + int version; + int size; + shader_t *sh; + mdxmSurfHierarchy_t *surfInfo; + +#ifndef _M_IX86 + int k; + int frameSize; +// mdxmTag_t *tag; + mdxmTriangle_t *tri; + mdxmVertex_t *v; + mdxmFrame_t *cframe; + int *boneRef; +#endif + + pinmodel= (mdxmHeader_t *)buffer; + // + // read some fields from the binary, but only LittleLong() them when we know this wasn't an already-cached model... + // + version = (pinmodel->version); + size = (pinmodel->ofsEnd); + + if (!bAlreadyCached) + { + version = LittleLong(version); + size = LittleLong(size); + } + + if (version != MDXM_VERSION) { +#ifdef _DEBUG + Com_Error( ERR_DROP, "R_LoadMDXM: %s has wrong version (%i should be %i)\n", mod_name, version, MDXM_VERSION); +#else + VID_Printf( PRINT_WARNING, "R_LoadMDXM: %s has wrong version (%i should be %i)\n", mod_name, version, MDXM_VERSION); +#endif + return qfalse; + } + + mod->type = MOD_MDXM; + mod->dataSize += size; + + qboolean bAlreadyFound = qfalse; + mdxm = mod->mdxm = (mdxmHeader_t*) //Hunk_Alloc( size ); + RE_RegisterModels_Malloc(size, buffer, mod_name, &bAlreadyFound, TAG_MODEL_GLM); + + assert(bAlreadyCached == bAlreadyFound); + + if (!bAlreadyFound) + { + // horrible new hackery, if !bAlreadyFound then we've just done a tag-morph, so we need to set the + // bool reference passed into this function to true, to tell the caller NOT to do an FS_Freefile since + // we've hijacked that memory block... + // + // Aaaargh. Kill me now... + // +#ifdef _XBOX + // We don't support re-tagging memory + memcpy( mdxm, buffer, size ); // and don't do this now, since it's the same thing +#else + bAlreadyCached = qtrue; + assert( mdxm == buffer ); +#endif + + LL(mdxm->ident); + LL(mdxm->version); + LL(mdxm->numLODs); + LL(mdxm->ofsLODs); + LL(mdxm->numSurfaces); + LL(mdxm->ofsSurfHierarchy); + LL(mdxm->ofsEnd); + } + + // first up, go load in the animation file we need that has the skeletal animation info for this model + mdxm->animIndex = RE_RegisterModel(va ("%s.gla",mdxm->animName)); + if (!strcmp(mdxm->animName,"models/players/_humanoid/_humanoid")) + { //if we're loading the humanoid, look for a cinematic gla for this map + const char*mapname = sv_mapname->string; + if (strcmp(mapname,"nomap") ) + { + if (strrchr(mapname,'/') ) //maps in subfolders use the root name, ( presuming only one level deep!) + { + mapname = strrchr(mapname,'/')+1; + } + RE_RegisterModel(va ("models/players/_humanoid_%s/_humanoid_%s.gla",mapname,mapname)); + } + } + + bool isAnOldModelFile = false; + if (mdxm->numBones == 72 && strstr(mdxm->animName,"_humanoid") ) + { + isAnOldModelFile = true; + } + + if (!mdxm->animIndex) + { + VID_Printf( PRINT_WARNING, "R_LoadMDXM: missing animation file %s for mesh %s\n", mdxm->animName, mdxm->name); + return qfalse; + } + else + { + assert (tr.models[mdxm->animIndex]->mdxa->numBones == mdxm->numBones); + if (tr.models[mdxm->animIndex]->mdxa->numBones != mdxm->numBones) + { + if ( isAnOldModelFile ) + { + VID_Printf( PRINT_WARNING, "R_LoadMDXM: converting jk2 model %s\n", mod_name); + } + else + { +#ifdef _DEBUG + Com_Error( ERR_DROP, "R_LoadMDXM: %s has different bones than anim (%i != %i)\n", mod_name, mdxm->numBones, tr.models[mdxm->animIndex]->mdxa->numBones); +#else + VID_Printf( PRINT_WARNING, "R_LoadMDXM: %s has different bones than anim (%i != %i)\n", mod_name, mdxm->numBones, tr.models[mdxm->animIndex]->mdxa->numBones); +#endif + } + if ( !isAnOldModelFile ) + {//hmm, load up the old JK2 ones anyway? + return qfalse; + } + } + } + + mod->numLods = mdxm->numLODs -1 ; //copy this up to the model for ease of use - it wil get inced after this. + + if (bAlreadyFound) + { + return qtrue; // All done. Stop, go no further, do not LittleLong(), do not pass Go... + } + + surfInfo = (mdxmSurfHierarchy_t *)( (byte *)mdxm + mdxm->ofsSurfHierarchy); + for ( i = 0 ; i < mdxm->numSurfaces ; i++) + { + LL(surfInfo->numChildren); + LL(surfInfo->parentIndex); + + strlwr(surfInfo->name); //just in case + if ( !strcmp( &surfInfo->name[strlen(surfInfo->name)-4],"_off") ) + { + surfInfo->name[strlen(surfInfo->name)-4]=0; //remove "_off" from name + } + + if ( surfInfo->shader[0] == '[' ) + { + surfInfo->shader[0] = 0; //kill the stupid [nomaterial] since carcass doesn't + } + + // do all the children indexs + for (j=0; jnumChildren; j++) + { + LL(surfInfo->childIndexes[j]); + } + + // get the shader name + sh = R_FindShader( surfInfo->shader, lightmapsNone, stylesDefault, qtrue ); + // insert it in the surface list + + if ( !sh->defaultShader ) + { + surfInfo->shaderIndex = sh->index; + } + + if (surfInfo->shaderIndex) + { + RE_RegisterModels_StoreShaderRequest(mod_name, &surfInfo->shader[0], &surfInfo->shaderIndex); + } + + // find the next surface + surfInfo = (mdxmSurfHierarchy_t *)( (byte *)surfInfo + (int)( &((mdxmSurfHierarchy_t *)0)->childIndexes[ surfInfo->numChildren ] )); + } + + // swap all the LOD's (we need to do the middle part of this even for intel, because of shader reg and err-check) + lod = (mdxmLOD_t *) ( (byte *)mdxm + mdxm->ofsLODs ); + for ( l = 0 ; l < mdxm->numLODs ; l++) + { + int triCount = 0; + + LL(lod->ofsEnd); + // swap all the surfaces + surf = (mdxmSurface_t *) ( (byte *)lod + sizeof (mdxmLOD_t) + (mdxm->numSurfaces * sizeof(mdxmLODSurfOffset_t)) ); + for ( i = 0 ; i < mdxm->numSurfaces ; i++) + { + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsVerts); + LL(surf->ofsEnd); + LL(surf->ofsHeader); + LL(surf->numBoneReferences); + LL(surf->ofsBoneReferences); +// LL(surf->maxVertBoneWeights); + + triCount += surf->numTriangles; + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + Com_Error (ERR_DROP, "R_LoadMDXM: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + Com_Error (ERR_DROP, "R_LoadMDXM: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = SF_MDX; + + // register the shaders +#ifndef _M_IX86 +// +// optimisation, we don't bother doing this for standard intel case since our data's already in that format... +// + // FIXME - is this correct? + // do all the bone reference data + boneRef = (int *) ( (byte *)surf + surf->ofsBoneReferences ); + for ( j = 0 ; j < surf->numBoneReferences ; j++ ) + { + LL(boneRef[j]); + } + + + // swap all the triangles + tri = (mdxmTriangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) + { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the vertexes + v = (mdxmVertex_t *) ( (byte *)surf + surf->ofsVerts ); + for ( j = 0 ; j < surf->numVerts ; j++ ) + { + v->normal[0] = LittleFloat( v->normal[0] ); + v->normal[1] = LittleFloat( v->normal[1] ); + v->normal[2] = LittleFloat( v->normal[2] ); + + v->texCoords[0] = LittleFloat( v->texCoords[0] ); + v->texCoords[1] = LittleFloat( v->texCoords[1] ); + + v->numWeights = LittleLong( v->numWeights ); + v->offset[0] = LittleFloat( v->offset[0] ); + v->offset[1] = LittleFloat( v->offset[1] ); + v->offset[2] = LittleFloat( v->offset[2] ); + + for ( k = 0 ; k < /*v->numWeights*/surf->maxVertBoneWeights ; k++ ) + { + v->weights[k].boneIndex = LittleLong( v->weights[k].boneIndex ); + v->weights[k].boneWeight = LittleFloat( v->weights[k].boneWeight ); + } + v = (mdxmVertex_t *)&v->weights[/*v->numWeights*/surf->maxVertBoneWeights]; + } +#endif + + if (isAnOldModelFile) + { + int *boneRef = (int *) ( (byte *)surf + surf->ofsBoneReferences ); + for ( j = 0 ; j < surf->numBoneReferences ; j++ ) + { + assert(boneRef[j] >= 0 && boneRef[j] < 72); + if (boneRef[j] >= 0 && boneRef[j] < 72) + { + boneRef[j]=OldToNewRemapTable[boneRef[j]]; + } + else + { + boneRef[j]=0; + } + } + } + // find the next surface + surf = (mdxmSurface_t *)( (byte *)surf + surf->ofsEnd ); + } + // find the next LOD + lod = (mdxmLOD_t *)( (byte *)lod + lod->ofsEnd ); + } + + return qtrue; +} + +// Hackery ensues - we keep a copy of the humanoid animation data pointer +// so that Bink can do horrible things with it: +//byte *humanoidGLA = NULL; +//bool humanoidGLAInUse = false; + +/* +================= +R_LoadMDXA - load a Ghoul 2 animation file +================= +*/ +qboolean R_LoadMDXA( model_t *mod, void *buffer, const char *mod_name, qboolean &bAlreadyCached ) { + + mdxaHeader_t *pinmodel, *mdxa; + int version; + int size; + +#ifndef _M_IX86 + int j, k, i; + int frameSize; + mdxaFrame_t *cframe; + mdxaSkel_t *boneInfo; +#endif + + pinmodel = (mdxaHeader_t *)buffer; + // + // read some fields from the binary, but only LittleLong() them when we know this wasn't an already-cached model... + // + version = (pinmodel->version); + size = (pinmodel->ofsEnd); + + if (!bAlreadyCached) + { + version = LittleLong(version); + size = LittleLong(size); + } + + if (version != MDXA_VERSION) { + VID_Printf( PRINT_WARNING, "R_LoadMDXA: %s has wrong version (%i should be %i)\n", + mod_name, version, MDXA_VERSION); + return qfalse; + } + + // VV Hackx0ring! Humanoid and cinematic animations have some "fixups" done to them. Bwa ha ha! + if( pinmodel->ofsCompBonePool > 0 && strstr(pinmodel->name, "_humanoid") ) + { + TheBonePool.Register( pinmodel ); + + // This number just changed: + size = pinmodel->ofsEnd; + } + + mod->type = MOD_MDXA; + mod->dataSize += size; + + qboolean bAlreadyFound = qfalse; + mdxa = mod->mdxa = (mdxaHeader_t*) //Hunk_Alloc( size ); + RE_RegisterModels_Malloc(size, buffer, mod_name, &bAlreadyFound, TAG_MODEL_GLA); + + // Save off the humanoid GLA's pointer when we load it - Bink does terrible + // things involving swapping that entire image to disk. +// if( strstr( mod_name, "_humanoid.gla" ) ) +// humanoidGLA = (byte *) mdxa; + + assert(bAlreadyCached == bAlreadyFound); + + if (!bAlreadyFound) + { + // horrible new hackery, if !bAlreadyFound then we've just done a tag-morph, so we need to set the + // bool reference passed into this function to true, to tell the caller NOT to do an FS_Freefile since + // we've hijacked that memory block... + // + // Aaaargh. Kill me now... + // +#ifdef _XBOX + // No re-tagging + memcpy( mdxa, buffer, size ); // and don't do this now, since it's the same thing +#else + bAlreadyCached = qtrue; + assert( mdxa == buffer ); +#endif + + LL(mdxa->ident); + LL(mdxa->version); + LL(mdxa->numFrames); + LL(mdxa->numBones); + LL(mdxa->ofsFrames); + LL(mdxa->ofsEnd); + } + + if ( mdxa->numFrames < 1 ) { + VID_Printf( PRINT_WARNING, "R_LoadMDXA: %s has no frames\n", mod_name ); + return qfalse; + } + + if (bAlreadyFound) + { + return qtrue; // All done, stop here, do not LittleLong() etc. Do not pass go... + } + +#ifndef _M_IX86 + + // + // optimisation, we don't bother doing this for standard intel case since our data's already in that format... + // + + // swap all the skeletal info + boneInfo = (mdxaSkel_t *)( (byte *)mdxa + mdxa->ofsSkel); + for ( i = 0 ; i < mdxa->numBones ; i++) + { + LL(boneInfo->numChildren); + LL(boneInfo->parent); + for (k=0; knumChildren; k++) + { + LL(boneInfo->children[k]); + } + + // get next bone + boneInfo += (int)( &((mdxaSkel_t *)0)->children[ boneInfo->numChildren ] ); + } + + + // swap all the frames + frameSize = (int)( &((mdxaFrame_t *)0)->bones[ mdxa->numBones ] ); + for ( i = 0 ; i < mdxa->numFrames ; i++) + { + cframe = (mdxaFrame_t *) ( (byte *)mdxa + mdxa->ofsFrames + i * frameSize ); + cframe->radius = LittleFloat( cframe->radius ); + for ( j = 0 ; j < 3 ; j++ ) + { + cframe->bounds[0][j] = LittleFloat( cframe->bounds[0][j] ); + cframe->bounds[1][j] = LittleFloat( cframe->bounds[1][j] ); + cframe->localOrigin[j] = LittleFloat( cframe->localOrigin[j] ); + } + for ( j = 0 ; j < mdxa->numBones * sizeof( mdxaBone_t ) / 2 ; j++ ) + { + ((short *)cframe->bones)[j] = LittleShort( ((short *)cframe->bones)[j] ); + } + } +#endif + return qtrue; +} + diff --git a/code/renderer/tr_image.cpp b/code/renderer/tr_image.cpp new file mode 100644 index 0000000..8a9b0c4 --- /dev/null +++ b/code/renderer/tr_image.cpp @@ -0,0 +1,2678 @@ +// tr_image.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "tr_local.h" +#ifndef _XBOX +#include "tr_jpeg_interface.h" +#else +#include "../qcommon/sstring.h" +#include "../zlib/zlib.h" +#endif +#include "../png/png.h" +#include "../qcommon/sstring.h" +#include "../win32/xbox_texture_man.h" + +static byte s_intensitytable[256]; +static unsigned char s_gammatable[256]; + +int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST; +int gl_filter_max = GL_LINEAR; + +#define FILE_HASH_SIZE 1024 // actually, the shader code needs this (from another module, great). +//static image_t* hashTable[FILE_HASH_SIZE]; + +/* +** R_GammaCorrect +*/ +void R_GammaCorrect( byte *buffer, int bufSize ) { + int i; + + for ( i = 0; i < bufSize; i++ ) { + buffer[i] = s_gammatable[buffer[i]]; + } +} + +typedef struct { + char *name; + int minimize, maximize; +} textureMode_t; + +textureMode_t modes[] = { + {"GL_NEAREST", GL_NEAREST, GL_NEAREST}, + {"GL_LINEAR", GL_LINEAR, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, + {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, + {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} +}; + +/* +================ +return a hash value for the filename +================ +*/ +long generateHashValue( const char *fname ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (FILE_HASH_SIZE-1); + return hash; +} + +// makeup a nice clean, consistant name to query for and file under, for map<> usage... +// +char *GenerateImageMappingName( const char *name ) +{ + static char sName[MAX_QPATH]; + int i=0; + char letter; + + while (name[i] != '\0' && ivalue > glConfig.maxTextureFilterAnisotropy ) + { + Cvar_Set( "r_ext_texture_filter_anisotropic", va("%f",glConfig.maxTextureFilterAnisotropy) ); + } + + // change all the existing mipmap texture objects +// int iNumImages = + R_Images_StartIteration(); + while ( (glt = R_Images_GetNextIteration()) != NULL) + { +#ifdef _XBOX + if ( glt->mipcount ) { +#else + if ( glt->mipmap ) { +#endif + GL_Bind (glt); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + + if(glConfig.maxTextureFilterAnisotropy>0) { + if(r_ext_texture_filter_anisotropic->integer>1) { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_ext_texture_filter_anisotropic->value); + } else { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + } + } + } + } +} + +static float R_BytesPerTex (int format) +{ + switch ( format ) { + case 1: + //"I " + return 1; + break; + case 2: + //"IA " + return 2; + break; + case 3: + //"RGB " + return glConfig.colorBits/8.0f; + break; + case 4: + //"RGBA " + return glConfig.colorBits/8.0f; + break; + + case GL_RGBA4: + //"RGBA4" + return 2; + break; + case GL_RGB5: + //"RGB5 " + return 2; + break; + + case GL_RGBA8: + //"RGBA8" + return 4; + break; + case GL_RGB8: + //"RGB8" + return 4; + break; + + case GL_RGB4_S3TC: + //"S3TC " + return 0.33333f; + break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + //"DXT1 " + return 0.33333f; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + //"DXT5 " + return 1; + break; + case GL_DDS1_EXT: + //"DDS1 " + return 0.5f; + break; + case GL_DDS5_EXT: + //"DDS5 " + return 1; + break; + case GL_DDS_RGB16_EXT: + //"DDS16" + return 2; + break; + case GL_DDS_RGBA32_EXT: + //"DDS32" + return 4; + break; + default: + //"???? " + return 4; + } +} + +/* +=============== +R_ImageList_f +=============== +*/ +void R_ImageList_f( void ) { + int i=0; + image_t *image; + int texels = 0; +// int totalFileSizeK = 0; + float texBytes = 0.0f; + const char *yesno[] = {"no ", "yes"}; + int curTexels; + unsigned curBytes; + + VID_Printf (PRINT_ALL, "\n -w-- -h-- -mm- -if-- --size-- surfs --name-------\n"); + + int iNumImages = R_Images_StartIteration(); + while ( (image = R_Images_GetNextIteration()) != NULL) + { + curTexels = image->width * image->height; + texels += curTexels; + + curBytes = curTexels * R_BytesPerTex(image->internalFormat); + if( image->mipcount ) + curBytes *= 1.3; + curBytes = (curBytes + 127) & ~127; + texBytes += curBytes; +// totalFileSizeK += (image->imgfileSize+1023)/1024; + VID_Printf (PRINT_ALL, "%4i: %4i %4i %s ", + i, image->width, image->height, yesno[image->mipcount?1:0] ); + switch ( image->internalFormat ) { + case 1: + VID_Printf( PRINT_ALL, "I " ); + break; + case 2: + VID_Printf( PRINT_ALL, "IA " ); + break; + case 3: + VID_Printf( PRINT_ALL, "RGB " ); + break; + case 4: + VID_Printf( PRINT_ALL, "RGBA " ); + break; + case GL_RGBA8: + VID_Printf( PRINT_ALL, "RGBA8" ); + break; + case GL_RGB8: + VID_Printf( PRINT_ALL, "RGB8 " ); + break; + case GL_RGB4_S3TC: + VID_Printf( PRINT_ALL, "S3TC " ); + break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + VID_Printf( PRINT_ALL, "DXT1 " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + VID_Printf( PRINT_ALL, "DXT5 " ); + break; + case GL_RGBA4: + VID_Printf( PRINT_ALL, "RGBA4" ); + break; + case GL_RGB5: + VID_Printf( PRINT_ALL, "RGB5 " ); + break; + case GL_DDS1_EXT: + VID_Printf( PRINT_ALL, "DDS1 " ); + break; + case GL_DDS5_EXT: + VID_Printf( PRINT_ALL, "DDS5 " ); + break; + case GL_DDS_RGB16_EXT: + VID_Printf( PRINT_ALL, "DDS16" ); + break; + case GL_DDS_RGBA32_EXT: + VID_Printf( PRINT_ALL, "DDS32" ); + break; + default: + VID_Printf( PRINT_ALL, "???? " ); + } + +#ifndef FINAL_BUILD + VID_Printf( PRINT_ALL, " %8u %4i %s\n", curBytes, R_SurfaceImageCount(image), + image->imgName ); +#else + VID_Printf( PRINT_ALL, " %8u %u\n", curBytes, image->imgCode ); +#endif + i++; + } +// VID_Printf (PRINT_ALL, " ---------\n"); + VID_Printf (PRINT_ALL, " -w-- -h-- -mm- -if- --name-------\n"); + VID_Printf (PRINT_ALL, " %i total texels (not including mipmaps)\n", texels ); +// VID_Printf (PRINT_ALL, " %iMB total filesize\n", (totalFileSizeK+1023)/1024 ); + VID_Printf (PRINT_ALL, " %.2fMB total texture mem (not including mipmaps)\n", texBytes/1048576.0f ); + VID_Printf (PRINT_ALL, " %i total images\n\n", iNumImages ); +} + +//======================================================================= + + +/* +================ +R_LightScaleTexture + +Scale up the pixel values in a texture to increase the +lighting range +================ +*/ +static void R_LightScaleTexture (unsigned *in, int inwidth, int inheight, qboolean only_gamma ) +{ + if ( only_gamma ) + { + if ( !glConfig.deviceSupportsGamma ) + { + int i, c; + byte *p; + + p = (byte *)in; + + c = inwidth*inheight; + for (i=0 ; i> 1; + outHeight = inHeight >> 1; + temp = (unsigned int *) Z_Malloc( outWidth * outHeight * 4, TAG_TEMP_WORKSPACE, qfalse ); + + inWidthMask = inWidth - 1; + inHeightMask = inHeight - 1; + + for ( i = 0 ; i < outHeight ; i++ ) { + for ( j = 0 ; j < outWidth ; j++ ) { + outpix = (byte *) ( temp + i * outWidth + j ); + for ( k = 0 ; k < 4 ; k++ ) { + total = + 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 1 * ((byte *)&in[ ((i*2-1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 4 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+1)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k] + + + 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2-1)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2)&inWidthMask) ])[k] + + 2 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+1)&inWidthMask) ])[k] + + 1 * ((byte *)&in[ ((i*2+2)&inHeightMask)*inWidth + ((j*2+2)&inWidthMask) ])[k]; + outpix[k] = total / 36; + } + } + } + + memcpy( in, temp, outWidth * outHeight * 4 ); + Z_Free( temp ); +} + +/* +================ +R_MipMap + +Operates in place, quartering the size of the texture +================ +*/ +static void R_MipMap (byte *in, int width, int height) { + int i, j; + byte *out; + int row; + + if ( width == 1 && height == 1 ) { + return; + } + + if ( !r_simpleMipMaps->integer ) { + R_MipMap2( (unsigned *)in, width, height ); + return; + } + + row = width * 4; + out = in; + width >>= 1; + height >>= 1; + + if ( width == 0 || height == 0 ) { + width += height; // get largest + for (i=0 ; i>1; + out[1] = ( in[1] + in[5] )>>1; + out[2] = ( in[2] + in[6] )>>1; + out[3] = ( in[3] + in[7] )>>1; + } + return; + } + + for (i=0 ; i>2; + out[1] = (in[1] + in[5] + in[row+1] + in[row+5])>>2; + out[2] = (in[2] + in[6] + in[row+2] + in[row+6])>>2; + out[3] = (in[3] + in[7] + in[row+3] + in[row+7])>>2; + } + } +} + + +/* +================== +R_BlendOverTexture + +Apply a color blend over a set of pixels +================== +*/ +static void R_BlendOverTexture( byte *data, int pixelCount, byte blend[4] ) { + int i; + int inverseAlpha; + int premult[3]; + + inverseAlpha = 255 - blend[3]; + premult[0] = blend[0] * blend[3]; + premult[1] = blend[1] * blend[3]; + premult[2] = blend[2] * blend[3]; + + for ( i = 0 ; i < pixelCount ; i++, data+=4 ) { + data[0] = ( data[0] * inverseAlpha + premult[0] ) >> 9; + data[1] = ( data[1] * inverseAlpha + premult[1] ) >> 9; + data[2] = ( data[2] * inverseAlpha + premult[2] ) >> 9; + } +} + +byte mipBlendColors[16][4] = { + {0,0,0,0}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, + {255,0,0,128}, + {0,255,0,128}, + {0,0,255,128}, +}; + + +/* +=============== +Upload32 + +=============== +*/ +static void Upload32( unsigned *data, + int img_width, int img_height, + GLenum format, + int mipcount, + qboolean picmip, + qboolean isLightmap, + int *pformat ) +{ + if (format == GL_RGBA) + { + int samples; + int i, c; + byte *scan; + int width = img_width; + int height = img_height; + + // + // perform optional picmip operation + // + if ( picmip ) { + for(i = 0; i < r_picmip->integer; i++) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + } + } + + // + // clamp to the current upper OpenGL limit + // scale both axis down equally so we don't have to + // deal with a half mip resampling + // + while ( width > glConfig.maxTextureSize || height > glConfig.maxTextureSize ) { + R_MipMap( (byte *)data, width, height ); + width >>= 1; + height >>= 1; + } + + // + // scan the texture for each channel's max values + // and verify if the alpha channel is being used or not + // + c = width*height; + scan = ((byte *)data); + samples = 3; + for ( i = 0; i < c; i++ ) + { + if ( scan[i*4 + 3] != 255 ) + { + samples = 4; + break; + } + } + + // select proper internal format + if ( samples == 3 ) + { + if ( isLightmap && r_texturebitslm->integer > 0 ) + { + // Allow different bit depth when we are a lightmap + if ( r_texturebitslm->integer == 16 ) + { + *pformat = GL_RGB5; + } + else if ( r_texturebitslm->integer == 32 ) + { + *pformat = GL_RGB8; + } + } + else if ( r_texturebits->integer == 16 ) + { + *pformat = GL_RGB5; + } + else if ( r_texturebits->integer == 32 ) + { + *pformat = GL_RGB8; + } + else + { + *pformat = 3; + } + } + else if ( samples == 4 ) + { + if ( r_texturebits->integer == 16 ) + { + *pformat = GL_RGBA4; + } + else if ( r_texturebits->integer == 32 ) + { + *pformat = GL_RGBA8; + } + else + { + *pformat = 4; + } + } + + // copy or resample data as appropriate for first MIP level + if (!mipcount) + { + qglTexImage2D (GL_TEXTURE_2D, 0, *pformat, width, height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, data); + } + else + { + if (mipcount) + { + int miplevel = 0; + int total = 1; + int n = width; + if (height > n) n = height; + while (n > 1) + { + n >>= 1; + ++total; + } + + qglTexImage2DEXT (GL_TEXTURE_2D, 0, total, *pformat, width, height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } + } + else + { + *pformat = format; + + qglTexImage2DEXT (GL_TEXTURE_2D, 0, mipcount, + format, img_width, img_height, 0, format, + GL_UNSIGNED_BYTE, data); + } + + if (mipcount) + { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); + if(glConfig.textureFilterAnisotropicAvailable) + { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f); + } + } + else + { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + GL_CheckErrors(); +} + + +typedef tmap (int, image_t *) AllocatedImages_t; + AllocatedImages_t* AllocatedImages = NULL; + AllocatedImages_t::iterator itAllocatedImages; + +int giTextureBindNum = 1024; // will be set to this anyway at runtime, but wtf? + + +// return = number of images in the list, for those interested +// +int R_Images_StartIteration(void) +{ + if(!AllocatedImages) + return 0; + + itAllocatedImages = AllocatedImages->begin(); + return AllocatedImages->size(); +} + +image_t *R_Images_GetNextIteration(void) +{ + if(!AllocatedImages) + return NULL; + + if (itAllocatedImages == AllocatedImages->end()) + return NULL; + + image_t *pImage = (*itAllocatedImages).second; + ++itAllocatedImages; + return pImage; +} + + +// clean up anything to do with an image_t struct, but caller will have to clear the internal to an image_t struct ready for either struct free() or overwrite... +// +static void R_Images_DeleteImageContents( image_t *pImage ) +{ + assert(pImage); // should never be called with NULL + if (pImage) + { + qglDeleteTextures( 1, &pImage->texnum ); + Z_Free(pImage); + } +} + +static void GL_ResetBinds(void) +{ + memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) ); + if ( qglBindTexture ) + { + if ( qglActiveTextureARB ) + { + GL_SelectTexture( 1 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + GL_SelectTexture( 0 ); + qglBindTexture( GL_TEXTURE_2D, 0 ); + } + else + { + qglBindTexture( GL_TEXTURE_2D, 0 ); + } + } +} + +// special function used in conjunction with "devmapbsp"... +// +void R_Images_DeleteLightMaps(void) +{ + assert( 0 && "This function will wreak havoc on texture pool!" ); + qboolean bEraseOccured = qfalse; + for (AllocatedImages_t::iterator itImage = AllocatedImages->begin(); itImage != AllocatedImages->end(); bEraseOccured?itImage:++itImage) + { + bEraseOccured = qfalse; + + image_t *pImage = (*itImage).second; + + if (pImage->isLightmap) + { + R_Images_DeleteImageContents(pImage); + + AllocatedImages->erase(itImage++); + bEraseOccured = qtrue; + } + } + + GL_ResetBinds(); +} + + +// special function currently only called by Dissolve code... +// +void R_Images_DeleteImage(image_t *pImage) +{ + // Even though we supply the image handle, we need to get the corresponding iterator entry... + // + AllocatedImages_t::iterator itImage = AllocatedImages->find(pImage->imgCode); + if (itImage != AllocatedImages->end()) + { + R_Images_DeleteImageContents(pImage); + AllocatedImages->erase(itImage); + } + else + { + assert(0); + } +} + + +// called only at app startup, vid_restart, app-exit +// +void R_Images_Clear(void) +{ + image_t *pImage; + // int iNumImages = + R_Images_StartIteration(); + while ( (pImage = R_Images_GetNextIteration()) != NULL) + { + R_Images_DeleteImageContents(pImage); + } + + if( AllocatedImages ) + { + AllocatedImages->clear(); + delete AllocatedImages; + AllocatedImages = NULL; + } + + giTextureBindNum = 1024; + + // Is this safe? Sure seems to be. And necessary, to avoid putting more than 4096 + // image swap files on the Z drive. + glw_state->textureBindNum = 1; + + gTextures.Reset(); +/* + extern unsigned long texturePoint; + texturePoint = 0; + + // Avert disaster. I can't conceive of how the second one could be true, + // but I'm so friggin paranoid right now. + extern byte *smallBinkTexture; + smallBinkTexture = NULL; + + extern bool bNextTextureIsSmallBink; + bNextTextureIsSmallBink = false; +*/ +} + + +void RE_RegisterImages_Info_f( void ) +{ + +} + + +// implement this if you need to, do a find for the caller. I don't need it though, so far. +// +//void RE_RegisterImages_LevelLoadBegin(const char *psMapName); + + +// currently, this just goes through all the images and dumps any not referenced on this level... +// +qboolean RE_RegisterImages_LevelLoadEnd(void) +{ + qboolean bEraseOccured = qfalse; + for (AllocatedImages_t::iterator itImage = AllocatedImages->begin(); itImage != AllocatedImages->end(); bEraseOccured?itImage:++itImage) + { + bEraseOccured = qfalse; + + image_t *pImage = (*itImage).second; + + // don't un-register system shaders (*fog, *dlight, *white, *default), but DO de-register lightmaps ("$/lightmap%d") + if (!pImage->isSystem) + { + // image used on this level? + // + if ( pImage->iLastLevelUsedOn != RE_RegisterMedia_GetLevel() ) + { // nope, so dump it... + assert( 0 && "This will wreak havoc on texture pool!" ); + //VID_Printf( PRINT_DEVELOPER, "Dumping image \"%s\"\n",pImage->imgName); + R_Images_DeleteImageContents(pImage); + itImage = AllocatedImages->erase(itImage); + bEraseOccured = qtrue; + } + } + } + + GL_ResetBinds(); + + return bEraseOccured; +} + + +// returns image_t struct if we already have this, else NULL. No disk-open performed +// (important for creating default images). +// +// This is called by both R_FindImageFile and anything that creates default images... +// +static image_t *R_FindImageFile_NoLoad(const char *name, int mipcount, qboolean allowPicmip, int glWrapClampMode ) +{ + if (!name) { + return NULL; + } + + char *pName = GenerateImageMappingName(name); + + // + // see if the image is already loaded + // + int code = crc32(0, (const Bytef *)pName, strlen(pName)); + AllocatedImages_t::iterator itAllocatedImage = AllocatedImages->find(code); + if (itAllocatedImage != AllocatedImages->end()) + { + image_t *pImage = (*itAllocatedImage).second; + + // the white image can be used with any set of parms, but other mismatches are errors... + // + if ( strcmp( pName, "*white" ) ) { + if ( !!pImage->mipcount != !!mipcount ) { + VID_Printf( PRINT_WARNING, "WARNING: reused image %s with mixed mipmap parm\n", pName ); + } +// if ( pImage->allowPicmip != !!allowPicmip ) { +// VID_Printf( PRINT_WARNING, "WARNING: reused image %s with mixed allowPicmip parm\n", pName ); +// } + } + + pImage->iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); + + return pImage; + } + + return NULL; +} + + +/* +================ +R_CreateImage + +This is the only way any image_t are created +================ +*/ +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, + GLenum format, int mipcount, qboolean allowPicmip, + int glWrapClampMode ) +{ + image_t *image; + qboolean isLightmap = qfalse; + + if (strlen(name) >= MAX_QPATH ) { + Com_Error (ERR_DROP, "R_CreateImage: \"%s\" is too long\n", name); + } + + if(glConfig.clampToEdgeAvailable && glWrapClampMode == GL_CLAMP) { + glWrapClampMode = GL_CLAMP_TO_EDGE; + } + + if (name[0] == '$') + { + isLightmap = qtrue; + } + + if ( (width&(width-1)) || (height&(height-1)) ) + { + Com_Error( ERR_FATAL, "R_CreateImage: %s dimensions (%i x %i) not power of 2!\n",name,width,height); + } + + image = R_FindImageFile_NoLoad(name, mipcount, allowPicmip, glWrapClampMode ); + if (image) { + return image; + } + + image = (image_t*) Z_Malloc( sizeof( image_t ), TAG_IMAGE_T, qtrue ); + + qglGenTextures(1, (GLuint*)&image->texnum); + + image->iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); + + image->mipcount = mipcount; +// image->allowPicmip = allowPicmip; + + image->imgCode = crc32(0, (const Bytef *)name, strlen(name)); +#ifndef FINAL_BUILD + Q_strncpyz(image->imgName, name, sizeof(image->imgName)); +#endif + + image->width = width; + image->height = height; + + image->isSystem = (name[0] == '*'); + image->isLightmap = isLightmap; + + GL_SelectTexture( 0 ); + + GL_Bind(image); + + Upload32( (unsigned *)pic, image->width, image->height, + format, + image->mipcount, + allowPicmip, + isLightmap, + &image->internalFormat ); + + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode ); + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode ); + + qglBindTexture( GL_TEXTURE_2D, 0 ); //jfm: i don't know why this is here, but it breaks lightmaps when there's only 1 + glState.currenttextures[glState.currenttmu] = 0; //mark it not bound + + const char* psNewName = GenerateImageMappingName(name); + image->imgCode = crc32(0, (const Bytef *)psNewName, strlen(psNewName)); + + (*AllocatedImages)[ image->imgCode ] = image; + + return image; +} + + +void R_CreateAutomapImage( const char *name, const byte *pic, int width, int height, + qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ) +{ + R_CreateImage(name, pic, width, height, GL_RGBA, mipmap, allowPicmip, glWrapClampMode); +} + +/* +========================================================= + +TARGA LOADING + +========================================================= +*/ + +/* +Ghoul2 Insert Start +*/ +/* +bool LoadTGAPalletteImage ( const char *name, byte **pic, int *width, int *height) +{ + int columns, rows, numPixels; + byte *buf_p; + byte *buffer; + TargaHeader targa_header; + byte *dataStart; + + *pic = NULL; + + // + // load the file + // + FS_ReadFile ( ( char * ) name, (void **)&buffer); + if (!buffer) { + return false; + } + + buf_p = buffer; + + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_length = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.colormap_size = *buf_p++; + targa_header.x_origin = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.y_origin = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.width = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.height = LittleShort ( *(short *)buf_p ); + buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + + if (targa_header.image_type!=1 ) + { + Com_Error (ERR_DROP, "LoadTGAPalletteImage: Only type 1 (uncompressed pallettised) TGA images supported\n"); + } + + if ( targa_header.colormap_type == 0 ) + { + Com_Error( ERR_DROP, "LoadTGAPalletteImage: colormaps ONLY supported\n" ); + } + + columns = targa_header.width; + rows = targa_header.height; + numPixels = columns * rows; + + if (width) + *width = columns; + if (height) + *height = rows; + + *pic = (unsigned char *) Z_Malloc (numPixels, TAG_TEMP_TGA, qfalse); + if (targa_header.id_length != 0) + { + buf_p += targa_header.id_length; // skip TARGA image comment + } + dataStart = buf_p + (targa_header.colormap_length * (targa_header.colormap_size / 4)); + memcpy(*pic, dataStart, numPixels); + FS_FreeFile (buffer); + + return true; +} +*/ + +/* +Ghoul2 Insert End +*/ + +/* +// My TGA loader... +// +//--------------------------------------------------- +#pragma pack(push,1) +typedef struct +{ + byte byIDFieldLength; // must be 0 + byte byColourmapType; // 0 = truecolour, 1 = paletted, else bad + byte byImageType; // 1 = colour mapped (palette), uncompressed, 2 = truecolour, uncompressed, else bad + word w1stColourMapEntry; // must be 0 + word wColourMapLength; // 256 for 8-bit palettes, else 0 for true-colour + byte byColourMapEntrySize; // 24 for 8-bit palettes, else 0 for true-colour + word wImageXOrigin; // ignored + word wImageYOrigin; // ignored + word wImageWidth; // in pixels + word wImageHeight; // in pixels + byte byImagePlanes; // bits per pixel (8 for paletted, else 24 for true-colour) + byte byScanLineOrder; // Image descriptor bytes + // bits 0-3 = # attr bits (alpha chan) + // bits 4-5 = pixel order/dir + // bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way) +} TGAHeader_t; +#pragma pack(pop) + + +// *pic == pic, else NULL for failed. +// +// returns false if found but had a format error, else true for either OK or not-found (there's a reason for this) +// + +int LoadTGA ( const char *name, byte **pic, int *width, int *height) +{ + char sErrorString[1024]; + bool bFormatErrors = false; + + // these don't need to be declared or initialised until later, but the compiler whines that 'goto' skips them. + // + byte *pRGBA = NULL; + byte *pOut = NULL; + byte *pIn = NULL; + + + *pic = NULL; + +#define TGA_FORMAT_ERROR(blah) {sprintf(sErrorString,blah); bFormatErrors = true; goto TGADone;} +//#define TGA_FORMAT_ERROR(blah) Com_Error( ERR_DROP, blah ); + + // + // load the file + // + byte *pTempLoadedBuffer = 0; + const int filelen = FS_ReadFile ( ( char * ) name, (void **)&pTempLoadedBuffer); + if (!pTempLoadedBuffer) { + return 0; + } + + TGAHeader_t *pHeader = (TGAHeader_t *) pTempLoadedBuffer; + + if (pHeader->byColourmapType!=0) + { + TGA_FORMAT_ERROR("LoadTGA: colourmaps not supported\n" ); + } + + if (pHeader->byImageType != 2 && pHeader->byImageType != 3 && pHeader->byImageType != 10) + { + TGA_FORMAT_ERROR("LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RLE-RGB) images supported\n"); + } + + if (pHeader->w1stColourMapEntry != 0) + { + TGA_FORMAT_ERROR("LoadTGA: colourmaps not supported\n" ); + } + + if (pHeader->wColourMapLength !=0 && pHeader->wColourMapLength != 256) + { + TGA_FORMAT_ERROR("LoadTGA: ColourMapLength must be either 0 or 256\n" ); + } + + if (pHeader->byColourMapEntrySize != 0 && pHeader->byColourMapEntrySize != 24) + { + TGA_FORMAT_ERROR("LoadTGA: ColourMapEntrySize must be either 0 or 24\n" ); + } + + if ( ( pHeader->byImagePlanes != 24 && pHeader->byImagePlanes != 32) && (pHeader->byImagePlanes != 8 && pHeader->byImageType != 3)) + { + TGA_FORMAT_ERROR("LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported\n"); + } + + if ((pHeader->byScanLineOrder&0x30)!=0x00 && + (pHeader->byScanLineOrder&0x30)!=0x10 && + (pHeader->byScanLineOrder&0x30)!=0x20 && + (pHeader->byScanLineOrder&0x30)!=0x30 + ) + { + TGA_FORMAT_ERROR("LoadTGA: ScanLineOrder must be either 0x00,0x10,0x20, or 0x30\n"); + } + + + + // these last checks are so i can use ID's RLE-code. I don't dare fiddle with it or it'll probably break... + // + if ( pHeader->byImageType == 10) + { + if ((pHeader->byScanLineOrder & 0x30) != 0x00) + { + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB Images (type 10) must be in bottom-to-top format\n"); + } + if (pHeader->byImagePlanes != 24 && pHeader->byImagePlanes != 32) // probably won't happen, but avoids compressed greyscales? + { + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB Images (type 10) must be 24 or 32 bit\n"); + } + } + + // now read the actual bitmap in... + // + // Image descriptor bytes + // bits 0-3 = # attr bits (alpha chan) + // bits 4-5 = pixel order/dir + // bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way) + // + int iYStart,iXStart,iYStep,iXStep; + + switch(pHeader->byScanLineOrder & 0x30) + { + default: // default case stops the compiler complaining about using uninitialised vars + case 0x00: // left to right, bottom to top + + iXStart = 0; + iXStep = 1; + + iYStart = pHeader->wImageHeight-1; + iYStep = -1; + + break; + + case 0x10: // right to left, bottom to top + + iXStart = pHeader->wImageWidth-1; + iXStep = -1; + + iYStart = pHeader->wImageHeight-1; + iYStep = -1; + + break; + + case 0x20: // left to right, top to bottom + + iXStart = 0; + iXStep = 1; + + iYStart = 0; + iYStep = 1; + + break; + + case 0x30: // right to left, top to bottom + + iXStart = pHeader->wImageWidth-1; + iXStep = -1; + + iYStart = 0; + iYStep = 1; + + break; + } + + // feed back the results... + // + if (width) + *width = pHeader->wImageWidth; + if (height) + *height = pHeader->wImageHeight; + + pRGBA = (byte *) Z_Malloc (pHeader->wImageWidth * pHeader->wImageHeight * 4, TAG_TEMP_TGA, qfalse); + *pic = pRGBA; + pOut = pRGBA; + pIn = pTempLoadedBuffer + sizeof(*pHeader); + + // I don't know if this ID-thing here is right, since comments that I've seen are at the end of the file, + // with a zero in this field. However, may as well... + // + if (pHeader->byIDFieldLength != 0) + pIn += pHeader->byIDFieldLength; // skip TARGA image comment + + byte red,green,blue,alpha; + + if ( pHeader->byImageType == 2 || pHeader->byImageType == 3 ) // RGB or greyscale + { + for (int y=iYStart, iYCount=0; iYCountwImageHeight; y+=iYStep, iYCount++) + { + pOut = pRGBA + y * pHeader->wImageWidth *4; + for (int x=iXStart, iXCount=0; iXCountwImageWidth; x+=iXStep, iXCount++) + { + switch (pHeader->byImagePlanes) + { + case 8: + blue = *pIn++; + green = blue; + red = blue; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = 255; + break; + + case 24: + blue = *pIn++; + green = *pIn++; + red = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = 255; + break; + + case 32: + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = alpha; + break; + + default: + assert(0); // if we ever hit this, someone deleted a header check higher up + TGA_FORMAT_ERROR("LoadTGA: Image can only have 8, 24 or 32 planes for RGB/greyscale\n"); + break; + } + } + } + } + else + if (pHeader->byImageType == 10) // RLE-RGB + { + // I've no idea if this stuff works, I normally reject RLE targas, but this is from ID's code + // so maybe I should try and support it... + // + byte packetHeader, packetSize, j; + + for (int y = pHeader->wImageHeight-1; y >= 0; y--) + { + pOut = pRGBA + y * pHeader->wImageWidth *4; + for (int x=0; xwImageWidth;) + { + packetHeader = *pIn++; + packetSize = 1 + (packetHeader & 0x7f); + if (packetHeader & 0x80) // run-length packet + { + switch (pHeader->byImagePlanes) + { + case 24: + + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = 255; + break; + + case 32: + + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = *pIn++; + break; + + default: + assert(0); // if we ever hit this, someone deleted a header check higher up + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB can only have 24 or 32 planes\n"); + break; + } + + for (j=0; jwImageWidth) // run spans across rows + { + x = 0; + if (y > 0) + y--; + else + goto breakOut; + pOut = pRGBA + y * pHeader->wImageWidth * 4; + } + } + } + else + { // non run-length packet + + for (j=0; jbyImagePlanes) + { + case 24: + + blue = *pIn++; + green = *pIn++; + red = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = 255; + break; + + case 32: + blue = *pIn++; + green = *pIn++; + red = *pIn++; + alpha = *pIn++; + *pOut++ = red; + *pOut++ = green; + *pOut++ = blue; + *pOut++ = alpha; + break; + + default: + assert(0); // if we ever hit this, someone deleted a header check higher up + TGA_FORMAT_ERROR("LoadTGA: RLE-RGB can only have 24 or 32 planes\n"); + break; + } + x++; + if (x == pHeader->wImageWidth) // pixel packet run spans across rows + { + x = 0; + if (y > 0) + y--; + else + goto breakOut; + pOut = pRGBA + y * pHeader->wImageWidth * 4; + } + } + } + } + breakOut:; + } + } + +TGADone: + + FS_FreeFile (pTempLoadedBuffer); + + if (bFormatErrors) + { + Com_Error( ERR_DROP, "%s( File: \"%s\" )\n",sErrorString,name); + } + return filelen; +} +*/ + +/* +========================================================= + +DDS LOADING + +========================================================= +*/ + +void LoadDDS ( const char *name, byte **pic, int *width, int *height, int *mipcount, GLenum *format ) +{ + fileHandle_t h; + int len = FS_FOpenFileRead( name, &h, qfalse ); + if ( h == 0 ) + { + return; + } + + *pic = (byte*)Z_Malloc( len, TAG_TEMP_WORKSPACE, qfalse , 32); + FS_Read( *pic, len, h ); + FS_FCloseFile( h ); + + DWORD dds = MAKEFOURCC('D', 'D', 'S', ' '); + if (*(DWORD*)(*pic) != dds) + { + FS_FreeFile (*pic); + *pic = NULL; + return; + } + + DDS_HEADER *desc = (DDS_HEADER *)(*pic + sizeof(DWORD)); + DWORD dxt1 = MAKEFOURCC('D', 'X', 'T', '1'); + DWORD dxt5 = MAKEFOURCC('D', 'X', 'T', '5'); + + if (desc->ddspf.dwFourCC == dxt1) + { + *format = GL_DDS1_EXT; + } + else if (desc->ddspf.dwFourCC == dxt5) + { + *format = GL_DDS5_EXT; + } + else if (desc->ddspf.dwRGBBitCount == 16) + { + *format = GL_DDS_RGB16_EXT; + } + else if (desc->ddspf.dwRGBBitCount == 32) + { + *format = GL_DDS_RGBA32_EXT; + } + else + { + FS_FreeFile (*pic); + *pic = NULL; + return; + } + + *width = desc->dwWidth; + *height = desc->dwHeight; + *mipcount = desc->dwMipMapCount; +} + + +//=================================================================== + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. +================= +*/ +void R_LoadImage( const char *shortname, byte **pic, int *width, int *height, int *mipcount, GLenum *format ) { + char name[MAX_QPATH]; + + *pic = NULL; + *width = 0; + *height = 0; + + //handle external LMs + if (shortname[0] == '$') { + Q_strncpyz( name, shortname+1, sizeof( name ) ); + } else { + Q_strncpyz( name, shortname, sizeof( name ) ); + } + + // Try DDS first - saves a ton of failed GOB checks on startup: + COM_StripExtension(name, name); + COM_DefaultExtension(name, sizeof(name), ".dds"); + LoadDDS( name, pic, width, height, mipcount, format ); + if( *pic ) + return; +/* + // OK. Now look for TGA: + *format = GL_RGBA; + *mipcount = 1; + + COM_StripExtension(name,name); + COM_DefaultExtension(name, sizeof(name), ".tga"); + LoadTGA( name, pic, width, height ); + + if (*pic) + { + int j = (*width) * (*height) * 4; + byte *buf = *pic; + byte swap; + for (int i = 0 ; i < j ; i+=4 ) { + swap = buf[i]; + buf[i] = buf[i+2]; + buf[i+2] = swap; + } + return; + } +*/ + + // Return whether or not it worked + return; +} + + +#ifndef _XBOX // Only used for terrain +void R_LoadDataImage( const char *name, byte **pic, int *width, int *height) +{ + int len; + char work[MAX_QPATH]; + + *pic = NULL; + *width = 0; + *height = 0; + + len = strlen(name); + if(len >= MAX_QPATH) + { + return; + } + if (len < 5) + { + return; + } +// MD_PushTag(TAG_DATA_IMAGE_LOAD); + + strcpy(work, name); + + COM_DefaultExtension( work, sizeof( work ), ".png" ); + LoadPNG8( work, pic, width, height ); + + if (!pic || !*pic) + { //both png and jpeg failed, try targa + strcpy(work, name); + COM_DefaultExtension( work, sizeof( work ), ".tga" ); + LoadTGA( work, pic, width, height ); + } + + if(*pic) + { +// MD_PopTag(); + return; + } + // Dataimage loading failed + Com_Printf("Couldn't read %s -- dataimage load failed\n", name); +// MD_PopTag(); +} +#endif + +void R_InvertImage(byte *data, int width, int height, int depth) +{ + byte *newData; + byte *oldData; + byte *saveData; + int y, stride; + + stride = width * depth; + + oldData = data + ((height - 1) * stride); + newData = (byte *)Z_Malloc(height * stride, TAG_TEMP_WORKSPACE, qfalse ); + saveData = newData; + + for(y = 0; y < height; y++) + { + memcpy(newData, oldData, stride); + newData += stride; + oldData -= stride; + } + memcpy(data, saveData, height * stride); + Z_Free(saveData); +} + +// Lanczos3 image resampling. Better than bicubic, based on sin(x)/x algorithm + +#define LANCZOS3 (3.0f) +#define M_PI_OVER_3 (M_PI / 3.0f) + +typedef struct +{ + int pixel; + float weight; +} contrib_t; + +typedef struct +{ + int n; // number of contributors + contrib_t *p; // pointer to list of contributions +} contrib_list_t; + +// sin(x)/x * sin(x/3)/(x/3) + +float Lanczos3(float t) +{ + if(!t) + { + return(1.0f); + } + t = (float)fabs(t); + if(t < 3.0f) + { + return(sinf(t * M_PI) * sinf(t * M_PI_OVER_3) / (t * M_PI * t * M_PI_OVER_3)); + } + return(0.0f); +} + +void R_Resample(byte *source, int swidth, int sheight, byte *dest, int dwidth, int dheight, int components) +{ + int i, j, k, l, count, left, right, num; + int pixel; + byte *raster; + float center, weight, scale, width, height; + contrib_list_t *contributors; + const memtag_t usedTag = TAG_TEMP_WORKSPACE; + + byte *work = (byte *)Z_Malloc(dwidth * sheight * components, usedTag, qfalse); + + // Pre calculate filter contributions for rows + contributors = (contrib_list_t *)Z_Malloc(sizeof(contrib_list_t) * dwidth, usedTag, qfalse); + + float xscale = (float)dwidth / (float)swidth; + + if(xscale < 1.0f) + { + width = ceilf(LANCZOS3 / xscale); + scale = xscale; + } + else + { + width = LANCZOS3; + scale = 1.0f; + } + num = ((int)width * 2) + 1; + + for(i = 0; i < dwidth; i++) + { + contributors[i].n = 0; + contributors[i].p = (contrib_t *)Z_Malloc(num * sizeof(contrib_t), usedTag, qfalse); + + center = (float)i / xscale; + left = (int)ceilf(center - width); + right = (int)floorf(center + width); + + for(j = left; j <= right; j++) + { + weight = Lanczos3((center - (float)j) * scale) * scale; + if(j < 0) + { + pixel = -j; + } + else if(j >= swidth) + { + pixel = (swidth - j) + swidth - 1; + } + else + { + pixel = j; + } + count = contributors[i].n++; + contributors[i].p[count].pixel = pixel; + contributors[i].p[count].weight = weight; + } + } + // Apply filters to zoom horizontally from source to work + for(k = 0; k < sheight; k++) + { + raster = source + (k * swidth * components); + for(i = 0; i < dwidth; i++) + { + for(l = 0; l < components; l++) + { + weight = 0.0f; + for(j = 0; j < contributors[i].n; j++) + { + weight += raster[(contributors[i].p[j].pixel * components) + l] * contributors[i].p[j].weight; + } + pixel = (byte)Com_Clamp(0.0f, 255.0f, weight); + work[(k * dwidth * components) + (i * components) + l] = pixel; + } + } + } + // Clean up + for(i = 0; i < dwidth; i++) + { + Z_Free(contributors[i].p); + } + Z_Free(contributors); + + // Columns + contributors = (contrib_list_t *)Z_Malloc(sizeof(contrib_list_t) * dheight, usedTag, qfalse); + + float yscale = (float)dheight / (float)sheight; + if(yscale < 1.0f) + { + height = ceilf(LANCZOS3 / yscale); + scale = yscale; + } + else + { + height = LANCZOS3; + scale = 1.0f; + } + num = ((int)height * 2) + 1; + + for(i = 0; i < dheight; i++) + { + contributors[i].n = 0; + contributors[i].p = (contrib_t *)Z_Malloc(num * sizeof(contrib_t), usedTag, qfalse); + + center = (float)i / yscale; + left = (int)ceilf(center - height); + right = (int)floorf(center + height); + + for(j = left; j <= right; j++) + { + weight = Lanczos3((center - (float)j) * scale) * scale; + if(j < 0) + { + pixel = -j; + } + else if(j >= sheight) + { + pixel = (sheight - j) + sheight - 1; + } + else + { + pixel = j; + } + count = contributors[i].n++; + contributors[i].p[count].pixel = pixel; + contributors[i].p[count].weight = weight; + } + } + // Apply filter to columns + for(k = 0; k < dwidth; k++) + { + for(l = 0; l < components; l++) + { + for(i = 0; i < dheight; i++) + { + weight = 0.0f; + for(j = 0; j < contributors[i].n; j++) + { + weight += work[(contributors[i].p[j].pixel * dwidth * components) + (k * components) + l] * contributors[i].p[j].weight; + } + pixel = (byte)Com_Clamp(0.0f, 255.0f, weight); + dest[(i * dwidth * components) + (k * components) + l] = pixel; + } + } + } + // Clean up + for(i = 0; i < dheight; i++) + { + Z_Free(contributors[i].p); + } + Z_Free(contributors); + Z_Free(work); + +// MD_PopTag(); +} + +/* +=============== +R_FindImageFile + +Finds or loads the given image. +Returns NULL if it fails, not a default image. +============== +*/ +image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ) { + image_t *image; + int width, height; + int mipcount; + byte *pic; + GLenum format; + + if (!name) { + return NULL; + } + + // need to do this here as well as in R_CreateImage, or R_FindImageFile_NoLoad() may complain about + // different clamp parms used... + // + if(glConfig.clampToEdgeAvailable && glWrapClampMode == GL_CLAMP) { + glWrapClampMode = GL_CLAMP_TO_EDGE; + } + + image = R_FindImageFile_NoLoad(name, mipmap, allowPicmip, glWrapClampMode ); + if (image) { + return image; + } + + // + // load the pic from disk + // + R_LoadImage( name, &pic, &width, &height, &mipcount, &format ); + if ( !pic ) { + return NULL; + } + + image = R_CreateImage( ( char * ) name, pic, width, height, format, mipcount, allowPicmip, glWrapClampMode ); + Z_Free( pic ); + return image; +} + +/* +================ +R_CreateDlightImage +================ +*/ +#define DLIGHT_SIZE 64 +static void R_CreateDlightImage( void ) +{ +#ifndef _XBOX + int width, height; + byte *pic; + GLenum format; + + R_LoadImage("gfx/2d/dlight", &pic, &width, &height, &format); + if (pic) + { + tr.dlightImage = R_CreateImage("*dlight", pic, width, height, GL_RGBA, qfalse, qfalse, qfalse, GL_CLAMP ); + Z_Free(pic); + } + else + { // if we dont get a successful load + int x,y; + byte data[DLIGHT_SIZE][DLIGHT_SIZE][4]; + int b; + + // make a centered inverse-square falloff blob for dynamic lighting + for (x=0 ; x 255) { + b = 255; + } else if ( b < 75 ) { + b = 0; + } + data[y][x][0] = + data[y][x][1] = + data[y][x][2] = b; + data[y][x][3] = 255; + } + } + tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, GL_RGBA, qfalse, qfalse, qfalse, GL_CLAMP ); + } +#endif +} + +/* +================= +R_InitFogTable +================= +*/ +void R_InitFogTable( void ) { + int i; + float d; + float exp; + + exp = 0.5; + + for ( i = 0 ; i < FOG_TABLE_SIZE ; i++ ) { + d = pow ( (float)i/(FOG_TABLE_SIZE-1), exp ); + + tr.fogTable[i] = d; + } +} + +/* +================ +R_FogFactor + +Returns a 0.0 to 1.0 fog density value +This is called for each texel of the fog texture on startup +and for each vertex of transparent shaders in fog dynamically +================ +*/ +float R_FogFactor( float s, float t ) { + float d; + + s -= 1.0/512; + if ( s < 0 ) { + return 0; + } + if ( t < 1.0/32 ) { + return 0; + } + if ( t < 31.0/32 ) { + s *= (t - 1.0/32) / (30.0/32); + } + + // we need to leave a lot of clamp range + s *= 8; + + if ( s > 1.0 ) { + s = 1.0; + } + + d = tr.fogTable[ (int)(s * (FOG_TABLE_SIZE-1)) ]; + + return d; +} + +/* +================ +R_CreateFogImage +================ +*/ +#define FOG_S 256 +#define FOG_T 32 +static void R_CreateFogImage( void ) { + int x,y; + byte *data; + float g; + float d; + float borderColor[4]; + + data = (byte*) Z_Malloc( FOG_S * FOG_T * 4, TAG_TEMP_WORKSPACE, qfalse ); + + g = 2.0; + + // S is distance, T is depth + for (x=0 ; xinteger; + if ( !glConfig.deviceSupportsGamma ) { + tr.overbrightBits = 0; // need hardware gamma for overbright + } + + // never overbright in windowed mode + if ( !glConfig.isFullscreen ) + { + tr.overbrightBits = 0; + } + + if ( tr.overbrightBits > 1 ) { + tr.overbrightBits = 1; + } + if ( tr.overbrightBits < 0 ) { + tr.overbrightBits = 0; + } + + tr.identityLight = 1.0 / ( 1 << tr.overbrightBits ); + tr.identityLightByte = 255 * tr.identityLight; + + + if ( r_intensity->value < 1.0f ) { + Cvar_Set( "r_intensity", "1.0" ); + } + + if ( r_gamma->value < 0.5f ) { + Cvar_Set( "r_gamma", "0.5" ); + } else if ( r_gamma->value > 3.0f ) { + Cvar_Set( "r_gamma", "3.0" ); + } + + g = r_gamma->value; + + shift = tr.overbrightBits; + + for ( i = 0; i < 256; i++ ) { + if ( g == 1 ) { + inf = i; + } else { + inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f; + } + inf <<= shift; + if (inf < 0) { + inf = 0; + } + if (inf > 255) { + inf = 255; + } + s_gammatable[i] = inf; + } + + for (i=0 ; i<256 ; i++) { + j = i * r_intensity->value; + if (j > 255) { + j = 255; + } + s_intensitytable[i] = j; + } +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) { + //memset(hashTable, 0, sizeof(hashTable)); // DO NOT DO THIS NOW (because of image cacheing) -ste. + if (!AllocatedImages) + { + AllocatedImages = new AllocatedImages_t; + } + + // build brightness translation tables + R_SetColorMappings(); + + // create default texture and white texture + R_CreateBuiltinImages(); +} + +/* +=============== +R_DeleteTextures +=============== +*/ +// (only gets called during vid_restart now (and app exit), not during map load) +// +void R_DeleteTextures( void ) { + + R_Images_Clear(); + GL_ResetBinds(); +} + +/* +============================================================================ + +SKINS + +============================================================================ +*/ + +/* +================== +CommaParse + +This is unfortunate, but the skin files aren't +compatable with our normal parsing rules. +================== +*/ +static char *CommaParse( char **data_p ) { + int c = 0, len; + char *data; + static char com_token[MAX_TOKEN_CHARS]; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) { + *data_p = NULL; + return com_token; + } + + while ( 1 ) { + // skip whitespace + while( (c = *data) <= ' ') { + if( !c ) { + break; + } + data++; + } + + + c = *data; + + // skip double slash comments + if ( c == '/' && data[1] == '/' ) + { + while (*data && *data != '\n') + data++; + } + // skip /* */ comments + else if ( c=='/' && data[1] == '*' ) + { + while ( *data && ( *data != '*' || data[1] != '/' ) ) + { + data++; + } + if ( *data ) + { + data += 2; + } + } + else + { + break; + } + } + + if ( c == 0 ) { + return ""; + } + + // handle quoted strings + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = ( char * ) 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 && c != ',' ); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + +typedef map AnimationCFGs_t; + AnimationCFGs_t AnimationCFGs; + +// I added this function for development purposes (and it's VM-safe) so we don't have problems +// with our use of cached models but uncached animation.cfg files (so frame sequences are out of sync +// if someone rebuild the model while you're ingame and you change levels)... +// +// Usage: call with psDest == NULL for a size enquire (for malloc), +// then with NZ ptr for it to copy to your supplied buffer... +// +int RE_GetAnimationCFG(const char *psCFGFilename, char *psDest, int iDestSize) +{ + char *psText = NULL; + + AnimationCFGs_t::iterator it = AnimationCFGs.find(psCFGFilename); + if (it != AnimationCFGs.end()) + { + psText = (*it).second; + } + else + { + // not found, so load it... + // + fileHandle_t f; + int iLen = FS_FOpenFileRead( psCFGFilename, &f, FS_READ ); + if (iLen <= 0) + { + return 0; + } + + psText = (char *) Z_Malloc( iLen+1, TAG_ANIMATION_CFG, qfalse ); + + FS_Read( psText, iLen, f ); + psText[iLen] = '\0'; + FS_FCloseFile( f ); + + AnimationCFGs[psCFGFilename] = psText; + } + + if (psText) // sanity, but should always be NZ + { + if (psDest) + { + Q_strncpyz(psDest,psText,iDestSize); + } + + return strlen(psText); + } + + return 0; +} + +// only called from devmapbsp, devmapall, or ... +// +void RE_AnimationCFGs_DeleteAll(void) +{ + for (AnimationCFGs_t::iterator it = AnimationCFGs.begin(); it != AnimationCFGs.end(); ++it) + { + char *psText = (*it).second; + Z_Free(psText); + } + + AnimationCFGs.clear(); +} + +/* +=============== +RE_SplitSkins +input = skinname, possibly being a macro for three skins +return= true if three part skins found +output= qualified names to three skins if return is true, undefined if false +=============== +*/ +bool RE_SplitSkins(const char *INname, char *skinhead, char *skintorso, char *skinlower) +{ //INname= "models/players/jedi_tf/|head01_skin1|torso01|lower01"; + if (strchr(INname, '|')) + { + char name[MAX_QPATH]; + strcpy(name, INname); + char *p = strchr(name, '|'); + *p=0; + p++; + //fill in the base path + strcpy (skinhead, name); + strcpy (skintorso, name); + strcpy (skinlower, name); + + //now get the the individual files + + //advance to second + char *p2 = strchr(p, '|'); + assert(p2); + *p2=0; + p2++; + strcat (skinhead, p); + strcat (skinhead, ".skin"); + + + //advance to third + p = strchr(p2, '|'); + assert(p); + if (!p) + { + return false; + } + *p=0; + p++; + strcat (skintorso,p2); + strcat (skintorso, ".skin"); + + strcat (skinlower,p); + strcat (skinlower, ".skin"); + + return true; + } + return false; +} + + +qhandle_t RE_RegisterIndividualSkin( const char *name , qhandle_t hSkin); +/* +=============== +RE_RegisterSkin + +=============== +*/ +qhandle_t RE_RegisterSkin( const char *name) { + qhandle_t hSkin; + skin_t *skin; + +// if (!cls.cgameStarted && !cls.uiStarted) +// { + //rww - added uiStarted exception because we want ghoul2 models in the menus. + // gwg well we need our skins to set surfaces on and off, so we gotta get em + //return 1; // cope with Ghoul2's calling-the-renderer-before-its-even-started hackery, must be any NZ amount here to trigger configstring setting +// } + + if (!tr.numSkins) + { + R_InitSkins(); //make sure we have numSkins set to at least one. + } + + if ( !name || !name[0] ) { + Com_Printf( "Empty name passed to RE_RegisterSkin\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Skin name exceeds MAX_QPATH\n" ); + return 0; + } + + // see if the skin is already loaded + for ( hSkin = 1; hSkin < tr.numSkins ; hSkin++ ) { + skin = tr.skins[hSkin]; + if ( !Q_stricmp( skin->name, name ) ) { + if( skin->numSurfaces == 0 ) { + return 0; // default skin + } + return(hSkin); + } + } + + if ( tr.numSkins == MAX_SKINS ) { + VID_Printf( PRINT_WARNING, "WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit\n", name ); + return 0; + } + // allocate a new skin + tr.numSkins++; + skin = (skin_t*) Hunk_Alloc( sizeof( skin_t ), qtrue ); + tr.skins[hSkin] = skin; + Q_strncpyz( skin->name, name, sizeof( skin->name ) ); //always make one so it won't search for it again + + // If not a .skin file, load as a single shader - then return + if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { +/* skin->numSurfaces = 1; + skin->surfaces[0] = (skinSurface_t *) Hunk_Alloc( sizeof(skin->surfaces[0]), qtrue ); + skin->surfaces[0]->shader = R_FindShader( name, lightmapsNone, stylesDefault, qtrue ); + return hSkin; +*/ + } + + char skinhead[MAX_QPATH]={0}; + char skintorso[MAX_QPATH]={0}; + char skinlower[MAX_QPATH]={0}; + if ( RE_SplitSkins(name, (char*)&skinhead, (char*)&skintorso, (char*)&skinlower ) ) + {//three part + hSkin = RE_RegisterIndividualSkin(skinhead, hSkin); + if (hSkin) + { + hSkin = RE_RegisterIndividualSkin(skintorso, hSkin); + if (hSkin) + { + hSkin = RE_RegisterIndividualSkin(skinlower, hSkin); + } + } + } + else + {//single skin + hSkin = RE_RegisterIndividualSkin(name, hSkin); + } + return(hSkin); +} + +// given a name, go get the skin we want and return +qhandle_t RE_RegisterIndividualSkin( const char *name , qhandle_t hSkin) +{ + skin_t *skin; + skinSurface_t *surf; + char *text, *text_p; + char *token; + char surfName[MAX_QPATH]; + + // load and parse the skin file + FS_ReadFile( name, (void **)&text ); + if ( !text ) { + VID_Printf( PRINT_ERROR, "WARNING: RE_RegisterSkin( '%s' ) failed to load!\n", name ); + return 0; + } + + assert (tr.skins[hSkin]); //should already be setup, but might be an 3part append + + skin = tr.skins[hSkin]; + + text_p = text; + while ( text_p && *text_p ) { + // get surface name + token = CommaParse( &text_p ); + Q_strncpyz( surfName, token, sizeof( surfName ) ); + + if ( !token[0] ) { + break; + } + // lowercase the surface name so skin compares are faster + Q_strlwr( surfName ); + + if ( *text_p == ',' ) { + text_p++; + } + + if ( !strncmp( token, "tag_", 4 ) ) { //these aren't in there, but just in case you load an id style one... + continue; + } + + // parse the shader name + token = CommaParse( &text_p ); + + if ( !strcmp( &surfName[strlen(surfName)-4], "_off") ) + { + if ( !strcmp( token ,"*off" ) ) + { + continue; //don't need these double offs + } + surfName[strlen(surfName)-4] = 0; //remove the "_off" + } + if (sizeof( skin->surfaces) / sizeof( skin->surfaces[0] ) <= skin->numSurfaces) + { + assert( sizeof( skin->surfaces) / sizeof( skin->surfaces[0] ) > skin->numSurfaces ); + VID_Printf( PRINT_ERROR, "WARNING: RE_RegisterSkin( '%s' ) more than %d surfaces!\n", name, sizeof( skin->surfaces) / sizeof( skin->surfaces[0] ) ); + break; + } + surf = skin->surfaces[ skin->numSurfaces ] = (skinSurface_t *) Hunk_Alloc( sizeof( *skin->surfaces[0] ), qtrue ); + Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); + surf->shader = R_FindShader( token, lightmapsNone, stylesDefault, qtrue ); + skin->numSurfaces++; + } + + FS_FreeFile( text ); + + + // never let a skin have 0 shaders + if ( skin->numSurfaces == 0 ) { + return 0; // use default skin + } + + return hSkin; +} + + +/* +=============== +R_InitSkins +=============== +*/ +void R_InitSkins( void ) { + skin_t *skin; + + tr.numSkins = 1; + + // make the default skin have all default shaders + skin = tr.skins[0] = (skin_t*) Hunk_Alloc( sizeof( skin_t ), qtrue ); + Q_strncpyz( skin->name, "", sizeof( skin->name ) ); + skin->numSurfaces = 1; + skin->surfaces[0] = (skinSurface_t *) Hunk_Alloc( sizeof( *skin->surfaces[0] ), qtrue ); + skin->surfaces[0]->shader = tr.defaultShader; +} + +/* +=============== +R_GetSkinByHandle +=============== +*/ +skin_t *R_GetSkinByHandle( qhandle_t hSkin ) { + if ( hSkin < 1 || hSkin >= tr.numSkins ) { + return tr.skins[0]; + } + return tr.skins[ hSkin ]; +} + +/* +=============== +R_SkinList_f +=============== +*/ +void R_SkinList_f (void) { + int i, j; + skin_t *skin; + + VID_Printf (PRINT_ALL, "------------------\n"); + + for ( i = 0 ; i < tr.numSkins ; i++ ) { + skin = tr.skins[i]; + VID_Printf( PRINT_ALL, "%3i:%s\n", i, skin->name ); + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + VID_Printf( PRINT_ALL, " %s = %s\n", + skin->surfaces[j]->name, skin->surfaces[j]->shader->name ); + } + } + VID_Printf (PRINT_ALL, "------------------\n"); +} + +#ifdef _XBOX + +extern BOOL LoadCompressedScreenshot(const char* filename); + +/* +=============== +R_UpdateSaveGameImage +filename - .xbx format file with a screenshot +=============== +*/ +bool R_UpdateSaveGameImage(const char* filename) +{ + // bind the savegame image + GL_Bind(tr.saveGameImage); + + // replace the texture with the one from the file + return LoadCompressedScreenshot(filename); +} + +#endif // _XBOX + diff --git a/code/renderer/tr_init.cpp b/code/renderer/tr_init.cpp new file mode 100644 index 0000000..4a78248 --- /dev/null +++ b/code/renderer/tr_init.cpp @@ -0,0 +1,1633 @@ +// tr_init.c -- functions that are not called every frame + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "tr_local.h" +#include "tr_stl.h" +#include "tr_jpeg_interface.h" +#include "tr_font.h" +#include "tr_WorldEffects.h" + +glconfig_t glConfig; +glstate_t glState; + +static void GfxInfo_f( void ); + +void R_TerrainInit(void); +void R_TerrainShutdown(void); + +cvar_t *r_verbose; +cvar_t *r_ignore; + +cvar_t *r_displayRefresh; + +cvar_t *r_detailTextures; + +cvar_t *r_znear; + +cvar_t *r_skipBackEnd; + +cvar_t *r_ignorehwgamma; +cvar_t *r_measureOverdraw; + +cvar_t *r_fastsky; +cvar_t *r_drawSun; +cvar_t *r_dynamiclight; +cvar_t *r_dlightBacks; + +cvar_t *r_lodbias; +cvar_t *r_lodscale; + +cvar_t *r_norefresh; +cvar_t *r_drawentities; +cvar_t *r_drawworld; +cvar_t *r_drawfog; +cvar_t *r_speeds; +cvar_t *r_fullbright; +cvar_t *r_novis; +cvar_t *r_nocull; +cvar_t *r_facePlaneCull; +cvar_t *r_showcluster; +cvar_t *r_nocurves; + +cvar_t *r_dlightStyle; +cvar_t *r_surfaceSprites; +cvar_t *r_surfaceWeather; + +cvar_t *r_windSpeed; +cvar_t *r_windAngle; +cvar_t *r_windGust; +cvar_t *r_windDampFactor; +cvar_t *r_windPointForce; +cvar_t *r_windPointX; +cvar_t *r_windPointY; + +cvar_t *r_allowExtensions; + +cvar_t *r_ext_compressed_textures; +cvar_t *r_ext_compressed_lightmaps; +cvar_t *r_ext_preferred_tc_method; +cvar_t *r_ext_gamma_control; +cvar_t *r_ext_multitexture; +cvar_t *r_ext_compiled_vertex_array; +cvar_t *r_ext_texture_env_add; +cvar_t *r_ext_texture_filter_anisotropic; + +cvar_t *r_DynamicGlow; +cvar_t *r_DynamicGlowPasses; +cvar_t *r_DynamicGlowDelta; +cvar_t *r_DynamicGlowIntensity; +cvar_t *r_DynamicGlowSoft; +cvar_t *r_DynamicGlowWidth; +cvar_t *r_DynamicGlowHeight; + +// Point sprite support. +cvar_t *r_ext_point_parameters; +cvar_t *r_ext_nv_point_sprite; + +cvar_t *r_ignoreGLErrors; +cvar_t *r_logFile; + +cvar_t *r_stencilbits; +cvar_t *r_depthbits; +cvar_t *r_colorbits; +cvar_t *r_stereo; +cvar_t *r_primitives; +cvar_t *r_texturebits; +cvar_t *r_texturebitslm; + +cvar_t *r_lightmap; +cvar_t *r_vertexLight; +cvar_t *r_shadows; +cvar_t *r_shadowRange; +cvar_t *r_flares; +cvar_t *r_mode; +cvar_t *r_nobind; +cvar_t *r_singleShader; +cvar_t *r_colorMipLevels; +cvar_t *r_picmip; +cvar_t *r_showtris; +cvar_t *r_showtriscolor; +cvar_t *r_showsky; +cvar_t *r_shownormals; +cvar_t *r_finish; +cvar_t *r_clear; +cvar_t *r_swapInterval; +cvar_t *r_textureMode; +cvar_t *r_offsetFactor; +cvar_t *r_offsetUnits; +cvar_t *r_gamma; +#ifdef _XBOX +cvar_t *s_brightness_volume; +#endif +cvar_t *r_intensity; +cvar_t *r_lockpvs; +cvar_t *r_noportals; +cvar_t *r_portalOnly; + +cvar_t *r_subdivisions; +cvar_t *r_lodCurveError; + +cvar_t *r_fullscreen; + +cvar_t *r_customwidth; +cvar_t *r_customheight; + +cvar_t *r_overBrightBits; + +cvar_t *r_debugSurface; +cvar_t *r_simpleMipMaps; + +cvar_t *r_showImages; + +cvar_t *r_ambientScale; +cvar_t *r_directedScale; +cvar_t *r_debugLight; +cvar_t *r_debugSort; +cvar_t *r_debugStyle; + +cvar_t *r_modelpoolmegs; + +#ifdef _XBOX +cvar_t *r_hdreffect; +cvar_t *r_sundir_x; +cvar_t *r_sundir_y; +cvar_t *r_sundir_z; +cvar_t *r_hdrbloom; +cvar_t *r_hdrcutoff; +#endif + +/* +Ghoul2 Insert Start +*/ + +cvar_t *r_noGhoul2; +cvar_t *r_Ghoul2AnimSmooth; +cvar_t *r_Ghoul2UnSqash; +cvar_t *r_Ghoul2TimeBase=0; +cvar_t *r_Ghoul2NoLerp; +cvar_t *r_Ghoul2NoBlend; +cvar_t *r_Ghoul2BlendMultiplier=0; +cvar_t *r_Ghoul2UnSqashAfterSmooth; + +cvar_t *broadsword=0; +cvar_t *broadsword_kickbones=0; +cvar_t *broadsword_kickorigin=0; +cvar_t *broadsword_playflop=0; +cvar_t *broadsword_dontstopanim=0; +cvar_t *broadsword_waitforshot=0; +cvar_t *broadsword_smallbbox=0; +cvar_t *broadsword_extra1=0; +cvar_t *broadsword_extra2=0; + +cvar_t *broadsword_effcorr=0; +cvar_t *broadsword_ragtobase=0; +cvar_t *broadsword_dircap=0; + +/* +Ghoul2 Insert End +*/ + + +void ( APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, GLfloat s, GLfloat t ); +void ( APIENTRY * qglActiveTextureARB )( GLenum texture ); +void ( APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + +void ( APIENTRY * qglLockArraysEXT)( GLint, GLint); +void ( APIENTRY * qglUnlockArraysEXT) ( void ); + +void ( APIENTRY * qglPointParameterfEXT)( GLenum, GLfloat); +void ( APIENTRY * qglPointParameterfvEXT)( GLenum, GLfloat *); + +// Added 10/23/02 by Aurelio Reis. +void ( APIENTRY * qglPointParameteriNV)( GLenum, GLint); +void ( APIENTRY * qglPointParameterivNV)( GLenum, const GLint *); + +#ifndef _XBOX // GLOWXXX +// Declare Register Combiners function pointers. +PFNGLCOMBINERPARAMETERFVNV qglCombinerParameterfvNV = NULL; +PFNGLCOMBINERPARAMETERIVNV qglCombinerParameterivNV = NULL; +PFNGLCOMBINERPARAMETERFNV qglCombinerParameterfNV = NULL; +PFNGLCOMBINERPARAMETERINV qglCombinerParameteriNV = NULL; +PFNGLCOMBINERINPUTNV qglCombinerInputNV = NULL; +PFNGLCOMBINEROUTPUTNV qglCombinerOutputNV = NULL; +PFNGLFINALCOMBINERINPUTNV qglFinalCombinerInputNV = NULL; +PFNGLGETCOMBINERINPUTPARAMETERFVNV qglGetCombinerInputParameterfvNV = NULL; +PFNGLGETCOMBINERINPUTPARAMETERIVNV qglGetCombinerInputParameterivNV = NULL; +PFNGLGETCOMBINEROUTPUTPARAMETERFVNV qglGetCombinerOutputParameterfvNV = NULL; +PFNGLGETCOMBINEROUTPUTPARAMETERIVNV qglGetCombinerOutputParameterivNV = NULL; +PFNGLGETFINALCOMBINERINPUTPARAMETERFVNV qglGetFinalCombinerInputParameterfvNV = NULL; +PFNGLGETFINALCOMBINERINPUTPARAMETERIVNV qglGetFinalCombinerInputParameterivNV = NULL; + +// Declare Pixel Format function pointers. +PFNWGLGETPIXELFORMATATTRIBIVARBPROC qwglGetPixelFormatAttribivARB = NULL; +PFNWGLGETPIXELFORMATATTRIBFVARBPROC qwglGetPixelFormatAttribfvARB = NULL; +PFNWGLCHOOSEPIXELFORMATARBPROC qwglChoosePixelFormatARB = NULL; + +// Declare Pixel Buffer function pointers. +PFNWGLCREATEPBUFFERARBPROC qwglCreatePbufferARB = NULL; +PFNWGLGETPBUFFERDCARBPROC qwglGetPbufferDCARB = NULL; +PFNWGLRELEASEPBUFFERDCARBPROC qwglReleasePbufferDCARB = NULL; +PFNWGLDESTROYPBUFFERARBPROC qwglDestroyPbufferARB = NULL; +PFNWGLQUERYPBUFFERARBPROC qwglQueryPbufferARB = NULL; + +// Declare Render-Texture function pointers. +PFNWGLBINDTEXIMAGEARBPROC qwglBindTexImageARB = NULL; +PFNWGLRELEASETEXIMAGEARBPROC qwglReleaseTexImageARB = NULL; +PFNWGLSETPBUFFERATTRIBARBPROC qwglSetPbufferAttribARB = NULL; + +// Declare Vertex and Fragment Program function pointers. +PFNGLPROGRAMSTRINGARBPROC qglProgramStringARB = NULL; +PFNGLBINDPROGRAMARBPROC qglBindProgramARB = NULL; +PFNGLDELETEPROGRAMSARBPROC qglDeleteProgramsARB = NULL; +PFNGLGENPROGRAMSARBPROC qglGenProgramsARB = NULL; +PFNGLPROGRAMENVPARAMETER4DARBPROC qglProgramEnvParameter4dARB = NULL; +PFNGLPROGRAMENVPARAMETER4DVARBPROC qglProgramEnvParameter4dvARB = NULL; +PFNGLPROGRAMENVPARAMETER4FARBPROC qglProgramEnvParameter4fARB = NULL; +PFNGLPROGRAMENVPARAMETER4FVARBPROC qglProgramEnvParameter4fvARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4DARBPROC qglProgramLocalParameter4dARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4DVARBPROC qglProgramLocalParameter4dvARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4FARBPROC qglProgramLocalParameter4fARB = NULL; +PFNGLPROGRAMLOCALPARAMETER4FVARBPROC qglProgramLocalParameter4fvARB = NULL; +PFNGLGETPROGRAMENVPARAMETERDVARBPROC qglGetProgramEnvParameterdvARB = NULL; +PFNGLGETPROGRAMENVPARAMETERFVARBPROC qglGetProgramEnvParameterfvARB = NULL; +PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC qglGetProgramLocalParameterdvARB = NULL; +PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC qglGetProgramLocalParameterfvARB = NULL; +PFNGLGETPROGRAMIVARBPROC qglGetProgramivARB = NULL; +PFNGLGETPROGRAMSTRINGARBPROC qglGetProgramStringARB = NULL; +PFNGLISPROGRAMARBPROC qglIsProgramARB = NULL; +#endif + +void RE_SetLightStyle(int style, int color); + +static void AssertCvarRange( cvar_t *cv, float minVal, float maxVal, qboolean shouldBeIntegral, qboolean shouldBeMult2) +{ + if ( shouldBeIntegral ) + { + if ( ( int ) cv->value != cv->integer ) + { + VID_Printf( PRINT_WARNING, "WARNING: cvar '%s' must be integral (%f)\n", cv->name, cv->value ); + Cvar_Set( cv->name, va( "%d", cv->integer ) ); + } + } + + if ( cv->value < minVal ) + { + VID_Printf( PRINT_WARNING, "WARNING: cvar '%s' out of range (%f < %f)\n", cv->name, cv->value, minVal ); + Cvar_Set( cv->name, va( "%f", minVal ) ); + } + else if ( cv->value > maxVal ) + { + VID_Printf( PRINT_WARNING, "WARNING: cvar '%s' out of range (%f > %f)\n", cv->name, cv->value, maxVal ); + Cvar_Set( cv->name, va( "%f", maxVal ) ); + } + + if (shouldBeMult2) + { + if ( (cv->integer&(cv->integer-1)) ) + { + int newvalue; + for (newvalue = 1 ; newvalue < cv->integer ; newvalue<<=1) + ; + VID_Printf( PRINT_WARNING, "WARNING: cvar '%s' must be multiple of 2(%f)\n", cv->name, cv->value ); + Cvar_Set( cv->name, va( "%d", newvalue ) ); + } + } +} + +void R_Splash() +{ +#ifndef _XBOX + image_t *pImage; +/* + const char* s = Cvar_VariableString("se_language"); + if (stricmp(s,"english")) + { + pImage = R_FindImageFile( "menu/splash_eur", qfalse, qfalse, qfalse, GL_CLAMP); + } + else + { + pImage = R_FindImageFile( "menu/splash", qfalse, qfalse, qfalse, GL_CLAMP); + } +*/ + pImage = R_FindImageFile( "menu/splash", qfalse, qfalse, qfalse, GL_CLAMP); + + extern void RB_SetGL2D (void); + RB_SetGL2D(); + if (pImage ) + {//invalid paths? + GL_Bind( pImage ); + } + GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO); + + const int width = 640; + const int height = 480; + const float x1 = 320 - width / 2; + const float x2 = 320 + width / 2; + const float y1 = 240 - height / 2; + const float y2 = 240 + height / 2; + + + qglBegin (GL_TRIANGLE_STRIP); + qglTexCoord2f( 0, 0 ); + qglVertex2f(x1, y1); + qglTexCoord2f( 1 , 0 ); + qglVertex2f(x2, y1); + qglTexCoord2f( 0, 1 ); + qglVertex2f(x1, y2); + qglTexCoord2f( 1, 1 ); + qglVertex2f(x2, y2); + qglEnd(); + + GLimp_EndFrame(); +#endif +} + +/* +** InitOpenGL +** +** This function is responsible for initializing a valid OpenGL subsystem. This +** is done by calling GLimp_Init (which gives us a working OGL subsystem) then +** setting variables, checking GL constants, and reporting the gfx system config +** to the user. +*/ +static void InitOpenGL( void ) +{ + // + // initialize OS specific portions of the renderer + // + // GLimp_Init directly or indirectly references the following cvars: + // - r_fullscreen + // - r_mode + // - r_(color|depth|stencil)bits + // - r_ignorehwgamma + // - r_gamma + // + + if ( glConfig.vidWidth == 0 ) + { + GLimp_Init(); + // print info the first time only + // set default state + GL_SetDefaultState(); + R_Splash(); //get something on screen asap + GfxInfo_f(); + } + else + { + // set default state + GL_SetDefaultState(); + } +} + +/* +================== +GL_CheckErrors +================== +*/ +void GL_CheckErrors( void ) { + int err; + char s[64]; + + err = qglGetError(); + if ( err == GL_NO_ERROR ) { + return; + } + if ( r_ignoreGLErrors->integer ) { + return; + } + switch( err ) { + case GL_INVALID_ENUM: + strcpy( s, "GL_INVALID_ENUM" ); + break; + case GL_INVALID_VALUE: + strcpy( s, "GL_INVALID_VALUE" ); + break; + case GL_INVALID_OPERATION: + strcpy( s, "GL_INVALID_OPERATION" ); + break; + case GL_STACK_OVERFLOW: + strcpy( s, "GL_STACK_OVERFLOW" ); + break; + case GL_STACK_UNDERFLOW: + strcpy( s, "GL_STACK_UNDERFLOW" ); + break; + case GL_OUT_OF_MEMORY: + strcpy( s, "GL_OUT_OF_MEMORY" ); + break; + default: + Com_sprintf( s, sizeof(s), "%i", err); + break; + } + + Com_Error( ERR_FATAL, "GL_CheckErrors: %s", s ); +} + +#ifndef _XBOX + +/* +** R_GetModeInfo +*/ +typedef struct vidmode_s +{ + const char *description; + int width, height; +} vidmode_t; + +const vidmode_t r_vidModes[] = +{ + { "Mode 0: 320x240", 320, 240 }, + { "Mode 1: 400x300", 400, 300 }, + { "Mode 2: 512x384", 512, 384 }, + { "Mode 3: 640x480", 640, 480 }, + { "Mode 4: 800x600", 800, 600 }, + { "Mode 5: 960x720", 960, 720 }, + { "Mode 6: 1024x768", 1024, 768 }, + { "Mode 7: 1152x864", 1152, 864 }, + { "Mode 8: 1280x1024", 1280, 1024 }, + { "Mode 9: 1600x1200", 1600, 1200 }, + { "Mode 10: 2048x1536", 2048, 1536 }, + { "Mode 11: 856x480 (wide)", 856, 480 }, + { "Mode 12: 2400x600(surround)",2400,600 } +}; +static const int s_numVidModes = ( sizeof( r_vidModes ) / sizeof( r_vidModes[0] ) ); + +qboolean R_GetModeInfo( int *width, int *height, int mode ) { + const vidmode_t *vm; + + if ( mode < -1 ) { + return qfalse; + } + if ( mode >= s_numVidModes ) { + return qfalse; + } + + if ( mode == -1 ) { + *width = r_customwidth->integer; + *height = r_customheight->integer; + return qtrue; + } + + vm = &r_vidModes[mode]; + + *width = vm->width; + *height = vm->height; + + return qtrue; +} + +/* +** R_ModeList_f +*/ +static void R_ModeList_f( void ) +{ + int i; + + VID_Printf( PRINT_ALL, "\n" ); + for ( i = 0; i < s_numVidModes; i++ ) + { + VID_Printf( PRINT_ALL, "%s\n", r_vidModes[i].description ); + } + VID_Printf( PRINT_ALL, "\n" ); +} + +#endif // _XBOX + +/* +============================================================================== + + SCREEN SHOTS + +============================================================================== +*/ + +/* +================== +R_TakeScreenshot +================== +*/ +// "filename" param is something like "screenshots/shot0000.tga" +// note that if the last extension is ".jpg", then it'll save a JPG, else TGA +// +void R_TakeScreenshot( int x, int y, int width, int height, char *fileName ) { +#ifndef _XBOX + byte *buffer; + int i, c, temp; + + qboolean bSaveAsJPG = !strnicmp(&fileName[strlen(fileName)-4],".jpg",4); + + if (bSaveAsJPG) + { + // JPG saver expects to be fed RGBA data, though it presumably ignores 'A'... + // + buffer = (unsigned char *) Z_Malloc(glConfig.vidWidth*glConfig.vidHeight*4, TAG_TEMP_WORKSPACE, qfalse); + qglReadPixels( x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer ); + + // gamma correct + if ( tr.overbrightBits>0 && glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer, glConfig.vidWidth * glConfig.vidHeight * 4 ); + } + + SaveJPG(fileName, 95, width, height, buffer); + } + else + { + // TGA... + // + buffer = (unsigned char *) Z_Malloc(glConfig.vidWidth*glConfig.vidHeight*3 + 18, TAG_TEMP_WORKSPACE, qfalse); + memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = width & 255; + buffer[13] = width >> 8; + buffer[14] = height & 255; + buffer[15] = height >> 8; + buffer[16] = 24; // pixel size + + qglReadPixels( x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer+18 ); + + // swap rgb to bgr + c = 18 + width * height * 3; + for (i=18 ; i0 && glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer + 18, glConfig.vidWidth * glConfig.vidHeight * 3 ); + } + FS_WriteFile( fileName, buffer, c ); + } + + Z_Free( buffer ); +#endif +} + +/* +================== +R_ScreenshotFilename +================== +*/ +void R_ScreenshotFilename( int lastNumber, char *fileName, const char *psExt ) { + int a,b,c,d; + + if ( lastNumber < 0 || lastNumber > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot9999%s",psExt ); + return; + } + + a = lastNumber / 1000; + lastNumber -= a*1000; + b = lastNumber / 100; + lastNumber -= b*100; + c = lastNumber / 10; + lastNumber -= c*10; + d = lastNumber; + + Com_sprintf( fileName, MAX_OSPATH, "screenshots/shot%i%i%i%i%s" + , a, b, c, d, psExt ); +} + +/* +==================== +R_LevelShot + +levelshots are specialized 256*256 thumbnails for +the menu system, sampled down from full screen distorted images +==================== +*/ +#define LEVELSHOTSIZE 256 +void R_LevelShot( void ) { +#ifndef _XBOX + char checkname[MAX_OSPATH]; + byte *buffer; + byte *source; + byte *src, *dst; + int x, y; + int r, g, b; + float xScale, yScale; + int xx, yy; + + sprintf( checkname, "levelshots/%s.tga", tr.worldDir + strlen("maps/") ); + + source = (byte*) Z_Malloc( glConfig.vidWidth * glConfig.vidHeight * 3, TAG_TEMP_WORKSPACE, qfalse ); + + buffer = (byte*) Z_Malloc( LEVELSHOTSIZE * LEVELSHOTSIZE*3 + 18, TAG_TEMP_WORKSPACE, qfalse ); + memset (buffer, 0, 18); + buffer[2] = 2; // uncompressed type + buffer[12] = LEVELSHOTSIZE & 255; + buffer[13] = LEVELSHOTSIZE >> 8; + buffer[14] = LEVELSHOTSIZE & 255; + buffer[15] = LEVELSHOTSIZE >> 8; + buffer[16] = 24; // pixel size + + qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGB, GL_UNSIGNED_BYTE, source ); + + // resample from source + xScale = glConfig.vidWidth / (4.0*LEVELSHOTSIZE); + yScale = glConfig.vidHeight / (3.0*LEVELSHOTSIZE); + for ( y = 0 ; y < LEVELSHOTSIZE ; y++ ) { + for ( x = 0 ; x < LEVELSHOTSIZE ; x++ ) { + r = g = b = 0; + for ( yy = 0 ; yy < 3 ; yy++ ) { + for ( xx = 0 ; xx < 4 ; xx++ ) { + src = source + 3 * ( glConfig.vidWidth * (int)( (y*3+yy)*yScale ) + (int)( (x*4+xx)*xScale ) ); + r += src[0]; + g += src[1]; + b += src[2]; + } + } + dst = buffer + 18 + 3 * ( y * LEVELSHOTSIZE + x ); + dst[0] = b / 12; + dst[1] = g / 12; + dst[2] = r / 12; + } + } + + // gamma correct + if ( glConfig.deviceSupportsGamma ) { + R_GammaCorrect( buffer + 18, LEVELSHOTSIZE * LEVELSHOTSIZE * 3 ); + } + + FS_WriteFile( checkname, buffer, LEVELSHOTSIZE * LEVELSHOTSIZE*3 + 18 ); + + Z_Free( buffer ); + Z_Free( source ); + + VID_Printf( PRINT_ALL, "Wrote %s\n", checkname ); +#endif +} + +/* +================== +R_ScreenShot_f + +screenshot +screenshot [silent] +screenshot [levelshot] +screenshot [filename] + +Doesn't print the pacifier message if there is a second arg +================== +*/ +void R_ScreenShot_f (void) { +#ifndef _XBOX + char checkname[MAX_OSPATH]; + int len; + static int lastNumber = -1; + qboolean silent; + + if ( !strcmp( Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + if ( !strcmp( Cmd_Argv(1), "silent" ) ) { + silent = qtrue; + } else { + silent = qfalse; + } + + if ( Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.jpg", Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + // scan for a free number + for ( lastNumber = 0 ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilename( lastNumber, checkname, ".jpg" ); + + len = FS_ReadFile( checkname, NULL ); + if ( len <= 0 ) { + break; // file doesn't exist + } + } + } else { + R_ScreenshotFilename( lastNumber, checkname, ".jpg" ); + } + + if ( lastNumber == 10000 ) { + VID_Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname ); + + if ( !silent ) { + VID_Printf (PRINT_ALL, "Wrote %s\n", checkname); + } +#endif +} + + + +/* +================== +R_ScreenShotTGA_f + +screenshot +screenshot [silent] +screenshot [levelshot] +screenshot [filename] + +Doesn't print the pacifier message if there is a second arg +================== +*/ +void R_ScreenShotTGA_f (void) { +#ifndef _XBOX + char checkname[MAX_OSPATH]; + int len; + static int lastNumber = -1; + qboolean silent; + + if ( !strcmp( Cmd_Argv(1), "levelshot" ) ) { + R_LevelShot(); + return; + } + if ( !strcmp( Cmd_Argv(1), "silent" ) ) { + silent = qtrue; + } else { + silent = qfalse; + } + + if ( Cmd_Argc() == 2 && !silent ) { + // explicit filename + Com_sprintf( checkname, MAX_OSPATH, "screenshots/%s.tga", Cmd_Argv( 1 ) ); + } else { + // scan for a free filename + + // if we have saved a previous screenshot, don't scan + // again, because recording demo avis can involve + // thousands of shots + if ( lastNumber == -1 ) { + // scan for a free number + for ( lastNumber = 0 ; lastNumber <= 9999 ; lastNumber++ ) { + R_ScreenshotFilename( lastNumber, checkname, ".tga" ); + + len = FS_ReadFile( checkname, NULL ); + if ( len <= 0 ) { + break; // file doesn't exist + } + } + } else { + R_ScreenshotFilename( lastNumber, checkname, ".tga" ); + } + + if ( lastNumber == 10000 ) { + VID_Printf (PRINT_ALL, "ScreenShot: Couldn't create a file\n"); + return; + } + + lastNumber++; + } + + + R_TakeScreenshot( 0, 0, glConfig.vidWidth, glConfig.vidHeight, checkname ); + + if ( !silent ) { + VID_Printf (PRINT_ALL, "Wrote %s\n", checkname); + } +#endif +} + + + +//============================================================================ + +/* +** GL_SetDefaultState +*/ +void GL_SetDefaultState( void ) +{ + qglClearDepth( 1.0f ); + + qglCullFace(GL_FRONT); + + qglColor4f (1,1,1,1); + + // initialize downstream texture unit if we're running + // in a multitexture environment + if ( qglActiveTextureARB ) { + GL_SelectTexture( 1 ); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + qglDisable( GL_TEXTURE_2D ); + GL_SelectTexture( 0 ); + } + + qglEnable(GL_TEXTURE_2D); + GL_TextureMode( r_textureMode->string ); + GL_TexEnv( GL_MODULATE ); + + qglShadeModel( GL_SMOOTH ); + qglDepthFunc( GL_LEQUAL ); + + // the vertex array is always enabled, but the color and texture + // arrays are enabled and disabled around the compiled vertex array call + qglEnableClientState (GL_VERTEX_ARRAY); + + // + // make sure our GL state vector is set correctly + // + glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE; + + qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + qglDepthMask( GL_TRUE ); + qglDisable( GL_DEPTH_TEST ); + qglEnable( GL_SCISSOR_TEST ); + qglDisable( GL_CULL_FACE ); + qglDisable( GL_BLEND ); + qglDisable( GL_ALPHA_TEST ); + qglBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); +#ifdef _XBOX + qglDisable( GL_LIGHTING ); +#endif +} + + +/* +================ +GfxInfo_f +================ +*/ +void GfxInfo_f( void ) +{ + cvar_t *sys_cpustring = Cvar_Get( "sys_cpustring", "", CVAR_ROM ); + const char *enablestrings[] = + { + "disabled", + "enabled" + }; + const char *fsstrings[] = + { + "windowed", + "fullscreen" + }; + + const char *tc_table[] = + { + "None", + "GL_S3_s3tc", + "GL_EXT_texture_compression_s3tc", + }; + + VID_Printf( PRINT_ALL, "\nGL_VENDOR: %s\n", glConfig.vendor_string ); + VID_Printf( PRINT_ALL, "GL_RENDERER: %s\n", glConfig.renderer_string ); + VID_Printf( PRINT_ALL, "GL_VERSION: %s\n", glConfig.version_string ); + VID_Printf( PRINT_ALL, "GL_EXTENSIONS: %s\n", glConfig.extensions_string ); + VID_Printf( PRINT_ALL, "GL_MAX_TEXTURE_SIZE: %d\n", glConfig.maxTextureSize ); + VID_Printf( PRINT_ALL, "GL_MAX_ACTIVE_TEXTURES_ARB: %d\n", glConfig.maxActiveTextures ); + VID_Printf( PRINT_ALL, "\nPIXELFORMAT: color(%d-bits) Z(%d-bit) stencil(%d-bits)\n", glConfig.colorBits, glConfig.depthBits, glConfig.stencilBits ); + VID_Printf( PRINT_ALL, "MODE: %d, %d x %d %s hz:", r_mode->integer, glConfig.vidWidth, glConfig.vidHeight, fsstrings[r_fullscreen->integer == 1] ); + if ( glConfig.displayFrequency ) + { + VID_Printf( PRINT_ALL, "%d\n", glConfig.displayFrequency ); + } + else + { + VID_Printf( PRINT_ALL, "N/A\n" ); + } + if ( glConfig.deviceSupportsGamma ) + { + VID_Printf( PRINT_ALL, "GAMMA: hardware w/ %d overbright bits\n", tr.overbrightBits ); + } + else + { + VID_Printf( PRINT_ALL, "GAMMA: software w/ %d overbright bits\n", tr.overbrightBits ); + } + VID_Printf( PRINT_ALL, "CPU: %s\n", sys_cpustring->string ); + + // rendering primitives + { + int primitives; + + // default is to use triangles if compiled vertex arrays are present + VID_Printf( PRINT_ALL, "rendering primitives: " ); + primitives = r_primitives->integer; + if ( primitives == 0 ) { + if ( qglLockArraysEXT ) { + primitives = 2; + } else { + primitives = 1; + } + } + if ( primitives == -1 ) { + VID_Printf( PRINT_ALL, "none\n" ); + } else if ( primitives == 2 ) { + VID_Printf( PRINT_ALL, "single glDrawElements\n" ); + } else if ( primitives == 1 ) { + VID_Printf( PRINT_ALL, "multiple glArrayElement\n" ); + } else if ( primitives == 3 ) { + VID_Printf( PRINT_ALL, "multiple glColor4ubv + glTexCoord2fv + glVertex3fv\n" ); + } + } + + VID_Printf( PRINT_ALL, "texturemode: %s\n", r_textureMode->string ); + VID_Printf( PRINT_ALL, "picmip: %d\n", r_picmip->integer ); + VID_Printf( PRINT_ALL, "texture bits: %d\n", r_texturebits->integer ); + VID_Printf( PRINT_ALL, "lightmap texture bits: %d\n", r_texturebitslm->integer ); + VID_Printf( PRINT_ALL, "multitexture: %s\n", enablestrings[qglActiveTextureARB != 0] ); + VID_Printf( PRINT_ALL, "compiled vertex arrays: %s\n", enablestrings[qglLockArraysEXT != 0 ] ); + VID_Printf( PRINT_ALL, "texenv add: %s\n", enablestrings[glConfig.textureEnvAddAvailable != 0] ); + VID_Printf( PRINT_ALL, "compressed textures: %s\n", enablestrings[glConfig.textureCompression != TC_NONE] ); + VID_Printf( PRINT_ALL, "compressed lightmaps: %s\n", enablestrings[(r_ext_compressed_lightmaps->integer != 0 && glConfig.textureCompression != TC_NONE)] ); + VID_Printf( PRINT_ALL, "texture compression method: %s\n", tc_table[glConfig.textureCompression] ); + Com_Printf ("anisotropic filtering: %s ", enablestrings[(r_ext_texture_filter_anisotropic->integer != 0) && glConfig.maxTextureFilterAnisotropy] ); + Com_Printf ("(%f of %f)\n", r_ext_texture_filter_anisotropic->value, glConfig.maxTextureFilterAnisotropy ); + Com_Printf ("Dynamic Glow: %s\n", enablestrings[r_DynamicGlow->integer] ); + + if ( r_finish->integer ) { + VID_Printf( PRINT_ALL, "Forcing glFinish\n" ); + } + if ( r_displayRefresh ->integer ) { + VID_Printf( PRINT_ALL, "Display refresh set to %d\n", r_displayRefresh->integer ); + } + if (tr.world) + { + VID_Printf( PRINT_ALL, "Light Grid size set to (%.2f %.2f %.2f)\n", tr.world->lightGridSize[0], tr.world->lightGridSize[1], tr.world->lightGridSize[2] ); + } +} + + +/************************************************************************************************ + * R_FogDistance_f * + * Console command to change the global fog opacity distance. If you specify nothing on the * + * command line, it will display the current fog opacity distance. Specifying a float * + * representing the world units away the fog should be completely opaque will change the * + * value. * + * * + * Input * + * none * + * * + * Output / Return * + * none * + * * + ************************************************************************************************/ +void R_FogDistance_f(void) +{ + float distance; + + if (!tr.world) + { + VID_Printf(PRINT_ALL, "R_FogDistance_f: World is not initialized\n"); + return; + } + + if (tr.world->globalFog == -1) + { + VID_Printf(PRINT_ALL, "R_FogDistance_f: World does not have a global fog\n"); + return; + } + + if (Cmd_Argc() <= 1) + { +// should not ever be 0.0 +// if (tr.world->fogs[tr.world->globalFog].tcScale == 0.0) +// { +// distance = 0.0; +// } +// else + { + distance = 1.0 / (8.0 * tr.world->fogs[tr.world->globalFog].tcScale); + } + + VID_Printf(PRINT_ALL, "R_FogDistance_f: Current Distance: %.0f\n", distance); + return; + } + + if (Cmd_Argc() != 2) + { + VID_Printf(PRINT_ALL, "R_FogDistance_f: Invalid number of arguments to set distance\n"); + return; + } + + distance = atof(Cmd_Argv(1)); + if (distance < 1.0) + { + distance = 1.0; + } + tr.world->fogs[tr.world->globalFog].parms.depthForOpaque = distance; + tr.world->fogs[tr.world->globalFog].tcScale = 1.0 / ( distance * 8 ); +} + +/************************************************************************************************ + * R_FogColor_f * + * Console command to change the global fog color. Specifying nothing on the command will * + * display the current global fog color. Specifying a float R G B values between 0.0 and * + * 1.0 will change the fog color. * + * * + * Input * + * none * + * * + * Output / Return * + * none * + * * + ************************************************************************************************/ +void R_FogColor_f(void) +{ + if (!tr.world) + { + VID_Printf(PRINT_ALL, "R_FogColor_f: World is not initialized\n"); + return; + } + + if (tr.world->globalFog == -1) + { + VID_Printf(PRINT_ALL, "R_FogColor_f: World does not have a global fog\n"); + return; + } + + if (Cmd_Argc() <= 1) + { + unsigned i = tr.world->fogs[tr.world->globalFog].colorInt; + + VID_Printf(PRINT_ALL, "R_FogColor_f: Current Color: %0f %0f %0f\n", + ( (byte *)&i )[0] / 255.0, + ( (byte *)&i )[1] / 255.0, + ( (byte *)&i )[2] / 255.0); + return; + } + + if (Cmd_Argc() != 4) + { + VID_Printf(PRINT_ALL, "R_FogColor_f: Invalid number of arguments to set color\n"); + return; + } + + tr.world->fogs[tr.world->globalFog].parms.color[0] = atof(Cmd_Argv(1)); + tr.world->fogs[tr.world->globalFog].parms.color[1] = atof(Cmd_Argv(2)); + tr.world->fogs[tr.world->globalFog].parms.color[2] = atof(Cmd_Argv(3)); + tr.world->fogs[tr.world->globalFog].colorInt = ColorBytes4 ( atof(Cmd_Argv(1)) * tr.identityLight, + atof(Cmd_Argv(2)) * tr.identityLight, + atof(Cmd_Argv(3)) * tr.identityLight, 1.0 ); +} + +/* +=============== +R_Register +=============== +*/ +void R_Register( void ) +{ + // + // latched and archived variables + // + r_allowExtensions = Cvar_Get( "r_allowExtensions", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compressed_textures = Cvar_Get( "r_ext_compress_textures", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compressed_lightmaps = Cvar_Get( "r_ext_compress_lightmaps", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_preferred_tc_method = Cvar_Get( "r_ext_preferred_tc_method", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_gamma_control = Cvar_Get( "r_ext_gamma_control", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_multitexture = Cvar_Get( "r_ext_multitexture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_ext_compiled_vertex_array = Cvar_Get( "r_ext_compiled_vertex_array", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_texture_env_add = Cvar_Get( "r_ext_texture_env_add", "1", CVAR_ARCHIVE | CVAR_LATCH); + r_ext_texture_filter_anisotropic = Cvar_Get( "r_ext_texture_filter_anisotropic", "16", CVAR_ARCHIVE ); + + r_DynamicGlow = Cvar_Get( "r_DynamicGlow", "1", CVAR_ARCHIVE ); + r_DynamicGlowPasses = Cvar_Get( "r_DynamicGlowPasses", "5", CVAR_CHEAT ); + r_DynamicGlowDelta = Cvar_Get( "r_DynamicGlowDelta", "0.8f", CVAR_CHEAT ); + r_DynamicGlowIntensity = Cvar_Get( "r_DynamicGlowIntensity", "1.13f", CVAR_CHEAT ); + r_DynamicGlowSoft = Cvar_Get( "r_DynamicGlowSoft", "1", CVAR_CHEAT ); + r_DynamicGlowWidth = Cvar_Get( "r_DynamicGlowWidth", "320", CVAR_CHEAT | CVAR_LATCH ); + r_DynamicGlowHeight = Cvar_Get( "r_DynamicGlowHeight", "240", CVAR_CHEAT | CVAR_LATCH ); + + // Register point sprite stuff here. + r_ext_point_parameters = Cvar_Get( "r_ext_point_parameters", "1", CVAR_ARCHIVE ); + r_ext_nv_point_sprite = Cvar_Get( "r_ext_nv_point_sprite", "1", CVAR_ARCHIVE ); + + r_picmip = Cvar_Get ("r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorMipLevels = Cvar_Get ("r_colorMipLevels", "0", CVAR_LATCH ); + AssertCvarRange( r_picmip, 0, 16, qtrue, qfalse ); + r_detailTextures = Cvar_Get( "r_detailtextures", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_texturebits = Cvar_Get( "r_texturebits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_texturebitslm = Cvar_Get( "r_texturebitslm", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_colorbits = Cvar_Get( "r_colorbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_stereo = Cvar_Get( "r_stereo", "0", CVAR_ARCHIVE | CVAR_LATCH ); +#ifdef __linux__ + r_stencilbits = Cvar_Get( "r_stencilbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); +#else + r_stencilbits = Cvar_Get( "r_stencilbits", "8", CVAR_ARCHIVE | CVAR_LATCH ); +#endif + r_depthbits = Cvar_Get( "r_depthbits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_overBrightBits = Cvar_Get ("r_overBrightBits", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_ignorehwgamma = Cvar_Get( "r_ignorehwgamma", "0", CVAR_ARCHIVE | CVAR_LATCH); + r_mode = Cvar_Get( "r_mode", "4", CVAR_ARCHIVE | CVAR_LATCH ); + r_fullscreen = Cvar_Get( "r_fullscreen", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_customwidth = Cvar_Get( "r_customwidth", "1600", CVAR_ARCHIVE | CVAR_LATCH ); + r_customheight = Cvar_Get( "r_customheight", "1024", CVAR_ARCHIVE | CVAR_LATCH ); + r_simpleMipMaps = Cvar_Get( "r_simpleMipMaps", "1", CVAR_ARCHIVE | CVAR_LATCH ); + r_vertexLight = Cvar_Get( "r_vertexLight", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_subdivisions = Cvar_Get ("r_subdivisions", "4", CVAR_ARCHIVE | CVAR_LATCH); + r_intensity = Cvar_Get ("r_intensity", "1", CVAR_LATCH|CVAR_ARCHIVE ); + + // + // temporary latched variables that can only change over a restart + // + r_displayRefresh = Cvar_Get( "r_displayRefresh", "0", CVAR_LATCH ); + AssertCvarRange( r_displayRefresh, 0, 200, qtrue, qfalse ); + r_fullbright = Cvar_Get ("r_fullbright", "0", CVAR_CHEAT ); + r_singleShader = Cvar_Get ("r_singleShader", "0", CVAR_CHEAT | CVAR_LATCH ); + + // + // archived variables that can change at any time + // + r_lodCurveError = Cvar_Get( "r_lodCurveError", "250", CVAR_ARCHIVE ); + r_lodbias = Cvar_Get( "r_lodbias", "0", CVAR_ARCHIVE ); + r_flares = Cvar_Get ("r_flares", "1", CVAR_ARCHIVE ); + r_lodscale = Cvar_Get( "r_lodscale", "10", CVAR_ARCHIVE ); + +#ifdef _XBOX + r_znear = Cvar_Get( "r_znear", "2", CVAR_CHEAT ); //lose a lot of precision in the distance +#else + r_znear = Cvar_Get( "r_znear", "4", CVAR_CHEAT ); //if set any lower, you lose a lot of precision in the distance +#endif + AssertCvarRange( r_znear, 0.001f, 200, qfalse, qfalse ); + r_ignoreGLErrors = Cvar_Get( "r_ignoreGLErrors", "1", CVAR_ARCHIVE ); + r_fastsky = Cvar_Get( "r_fastsky", "0", CVAR_ARCHIVE ); + r_drawSun = Cvar_Get( "r_drawSun", "0", CVAR_ARCHIVE ); + r_dynamiclight = Cvar_Get( "r_dynamiclight", "1", CVAR_ARCHIVE ); + r_dlightBacks = Cvar_Get( "r_dlightBacks", "0", CVAR_ARCHIVE ); + r_finish = Cvar_Get ("r_finish", "0", CVAR_ARCHIVE); + r_textureMode = Cvar_Get( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST", CVAR_ARCHIVE ); + r_swapInterval = Cvar_Get( "r_swapInterval", "0", CVAR_ARCHIVE ); +#ifdef __MACOS__ + r_gamma = Cvar_Get( "r_gamma", "1.2", CVAR_ARCHIVE ); +#else + r_gamma = Cvar_Get( "r_gamma", "1", CVAR_ARCHIVE ); +#endif + +#ifdef _XBOX + s_brightness_volume = Cvar_Get( "s_brightness_volume", "1", CVAR_ARCHIVE ); +#endif + r_facePlaneCull = Cvar_Get ("r_facePlaneCull", "1", CVAR_ARCHIVE ); + + r_dlightStyle = Cvar_Get ("r_dlightStyle", "1", CVAR_TEMP); + r_surfaceSprites = Cvar_Get ("r_surfaceSprites", "1", CVAR_TEMP); + r_surfaceWeather = Cvar_Get ("r_surfaceWeather", "0", CVAR_TEMP); + + r_windSpeed = Cvar_Get ("r_windSpeed", "0", 0); + r_windAngle = Cvar_Get ("r_windAngle", "0", 0); + r_windGust = Cvar_Get ("r_windGust", "0", 0); + r_windDampFactor = Cvar_Get ("r_windDampFactor", "0.1", 0); + r_windPointForce = Cvar_Get ("r_windPointForce", "0", 0); + r_windPointX = Cvar_Get ("r_windPointX", "0", 0); + r_windPointY = Cvar_Get ("r_windPointY", "0", 0); + + r_primitives = Cvar_Get( "r_primitives", "0", CVAR_ARCHIVE ); + + r_ambientScale = Cvar_Get( "r_ambientScale", "0.5", CVAR_CHEAT ); + r_directedScale = Cvar_Get( "r_directedScale", "1", CVAR_CHEAT ); + + // + // temporary variables that can change at any time + // + r_showImages = Cvar_Get( "r_showImages", "0", CVAR_CHEAT ); + + r_debugLight = Cvar_Get( "r_debuglight", "0", CVAR_TEMP ); + r_debugStyle = Cvar_Get( "r_debugStyle", "-1", CVAR_CHEAT ); + r_debugSort = Cvar_Get( "r_debugSort", "0", CVAR_CHEAT ); + + r_nocurves = Cvar_Get ("r_nocurves", "0", CVAR_CHEAT ); + r_drawworld = Cvar_Get ("r_drawworld", "1", CVAR_CHEAT ); + r_drawfog = Cvar_Get ("r_drawfog", "2", CVAR_CHEAT ); + r_lightmap = Cvar_Get ("r_lightmap", "0", CVAR_CHEAT ); + r_portalOnly = Cvar_Get ("r_portalOnly", "0", CVAR_CHEAT ); + + r_skipBackEnd = Cvar_Get ("r_skipBackEnd", "0", CVAR_CHEAT); + + r_measureOverdraw = Cvar_Get( "r_measureOverdraw", "0", CVAR_CHEAT ); + r_norefresh = Cvar_Get ("r_norefresh", "0", CVAR_CHEAT); + r_drawentities = Cvar_Get ("r_drawentities", "1", CVAR_CHEAT ); + r_ignore = Cvar_Get( "r_ignore", "1", CVAR_TEMP ); + r_nocull = Cvar_Get ("r_nocull", "0", CVAR_CHEAT); + r_novis = Cvar_Get ("r_novis", "0", CVAR_CHEAT); + r_showcluster = Cvar_Get ("r_showcluster", "0", CVAR_CHEAT); + r_speeds = Cvar_Get ("r_speeds", "0", CVAR_CHEAT); + r_verbose = Cvar_Get( "r_verbose", "0", CVAR_CHEAT ); + r_logFile = Cvar_Get( "r_logFile", "0", CVAR_CHEAT ); + r_debugSurface = Cvar_Get ("r_debugSurface", "0", CVAR_CHEAT); + r_nobind = Cvar_Get ("r_nobind", "0", CVAR_CHEAT); + r_showtris = Cvar_Get ("r_showtris", "0", CVAR_CHEAT); + r_showtriscolor = Cvar_Get ("r_showtriscolor", "0", CVAR_ARCHIVE); + r_showsky = Cvar_Get ("r_showsky", "0", CVAR_CHEAT); + r_shownormals = Cvar_Get ("r_shownormals", "0", CVAR_CHEAT); + r_clear = Cvar_Get ("r_clear", "0", CVAR_CHEAT); + r_offsetFactor = Cvar_Get( "r_offsetfactor", "-1", CVAR_CHEAT ); + r_offsetUnits = Cvar_Get( "r_offsetunits", "-2", CVAR_CHEAT ); + r_lockpvs = Cvar_Get ("r_lockpvs", "0", CVAR_CHEAT); + r_noportals = Cvar_Get ("r_noportals", "0", CVAR_CHEAT); + r_shadows = Cvar_Get( "cg_shadows", "1", 0 ); + r_shadowRange = Cvar_Get( "r_shadowRange", "1000", CVAR_ARCHIVE ); + +#ifdef _XBOX + r_hdreffect = Cvar_Get( "r_hdreffect", "0", 0 ); + r_sundir_x = Cvar_Get( "r_sundir_x", "0.45", 0 ); + r_sundir_y = Cvar_Get( "r_sundir_y", "0.3", 0 ); + r_sundir_z = Cvar_Get( "r_sundir_z", "0.9", 0 ); + r_hdrbloom = Cvar_Get( "r_hdrbloom", "1.0", 0 ); + r_hdrcutoff = Cvar_Get( "r_hdrcutoff", "0.5", 0 ); +#endif +/* +Ghoul2 Insert Start +*/ + r_noGhoul2 = Cvar_Get( "r_noghoul2", "0", CVAR_CHEAT); + r_Ghoul2AnimSmooth = Cvar_Get( "r_ghoul2animsmooth", "0.25", 0); + r_Ghoul2UnSqash = Cvar_Get( "r_ghoul2unsquash", "1", 0); + r_Ghoul2TimeBase = Cvar_Get( "r_ghoul2timebase", "2", 0); + r_Ghoul2NoLerp = Cvar_Get( "r_ghoul2nolerp", "0", 0); + r_Ghoul2NoBlend = Cvar_Get( "r_ghoul2noblend", "0", 0); + r_Ghoul2BlendMultiplier = Cvar_Get( "r_ghoul2blendmultiplier", "1", 0); + r_Ghoul2UnSqashAfterSmooth = Cvar_Get( "r_ghoul2unsquashaftersmooth", "1", 0); + + broadsword = Cvar_Get( "broadsword", "1", 0); + broadsword_kickbones = Cvar_Get( "broadsword_kickbones", "1", 0); + broadsword_kickorigin = Cvar_Get( "broadsword_kickorigin", "1", 0); + broadsword_dontstopanim = Cvar_Get( "broadsword_dontstopanim", "0", 0); + broadsword_waitforshot = Cvar_Get( "broadsword_waitforshot", "0", 0); + broadsword_playflop = Cvar_Get( "broadsword_playflop", "1", 0); + broadsword_smallbbox = Cvar_Get( "broadsword_smallbbox", "0", 0); + broadsword_extra1 = Cvar_Get( "broadsword_extra1", "0", 0); + broadsword_extra2 = Cvar_Get( "broadsword_extra2", "0", 0); + broadsword_effcorr = Cvar_Get( "broadsword_effcorr", "1", 0); + broadsword_ragtobase = Cvar_Get( "broadsword_ragtobase", "2", 0); + broadsword_dircap = Cvar_Get( "broadsword_dircap", "64", 0); + +/* +Ghoul2 Insert End +*/ +extern qboolean Sys_LowPhysicalMemory(); + r_modelpoolmegs = Cvar_Get("r_modelpoolmegs", "20", CVAR_ARCHIVE); + if (Sys_LowPhysicalMemory() ) + { + Cvar_Set("r_modelpoolmegs", "0"); + } + + // make sure all the commands added here are also + // removed in R_Shutdown + Cmd_AddCommand( "imagelist", R_ImageList_f ); + Cmd_AddCommand( "shaderlist", R_ShaderList_f ); + Cmd_AddCommand( "skinlist", R_SkinList_f ); + Cmd_AddCommand( "modellist", R_Modellist_f ); +#ifndef _XBOX + Cmd_AddCommand( "modelist", R_ModeList_f ); +#endif + Cmd_AddCommand( "screenshot", R_ScreenShot_f ); + Cmd_AddCommand( "screenshot_tga", R_ScreenShotTGA_f ); + Cmd_AddCommand( "gfxinfo", GfxInfo_f ); + Cmd_AddCommand( "r_fogDistance", R_FogDistance_f); + Cmd_AddCommand( "r_fogColor", R_FogColor_f); + Cmd_AddCommand( "modelcacheinfo", RE_RegisterModels_Info_f); + Cmd_AddCommand( "imagecacheinfo", RE_RegisterImages_Info_f); +extern void R_WorldEffect_f(void); //TR_WORLDEFFECTS.CPP + Cmd_AddCommand( "r_we", R_WorldEffect_f ); +extern void R_ReloadFonts_f(void); + Cmd_AddCommand( "r_reloadfonts", R_ReloadFonts_f ); + // make sure all the commands added above are also + // removed in R_Shutdown +} + +// need to do this hackery so ghoul2 doesn't crash the game because of ITS hackery... +// +void R_ClearStuffToStopGhoul2CrashingThings(void) +{ + memset( &tr, 0, sizeof( tr ) ); +} + +/* +=============== +R_Init +=============== +*/ +extern void R_InitWorldEffects(); +void R_Init( void ) { + int err; + int i; + + //VID_Printf( PRINT_ALL, "----- R_Init -----\n" ); +#ifdef _XBOX + extern qboolean vidRestartReloadMap; + if (!vidRestartReloadMap) + { + Hunk_Clear(); + + extern void CM_Free(void); + CM_Free(); + + void CM_CleanLeafCache(void); + CM_CleanLeafCache(); + } +#endif + + ShaderEntryPtrs_Clear(); + +#ifdef _XBOX + //Save visibility info as it has already been set. + SPARC *vis = tr.externalVisData; +#endif + + // clear all our internal state + memset( &tr, 0, sizeof( tr ) ); + memset( &backEnd, 0, sizeof( backEnd ) ); + memset( &tess, 0, sizeof( tess ) ); + +#ifdef _XBOX + //Restore visibility info. + tr.externalVisData = vis; +#endif + + Swap_Init(); + +#ifndef FINAL_BUILD + if ( (int)tess.xyz & 15 ) { + Com_Printf( "WARNING: tess.xyz not 16 byte aligned (%x)\n",(int)tess.xyz & 15 ); + } +#endif + + // + // init function tables + // + for ( i = 0; i < FUNCTABLE_SIZE; i++ ) + { + tr.sinTable[i] = sin( DEG2RAD( i * 360.0f / ( ( float ) ( FUNCTABLE_SIZE - 1 ) ) ) ); + tr.squareTable[i] = ( i < FUNCTABLE_SIZE/2 ) ? 1.0f : -1.0f; + tr.sawToothTable[i] = (float)i / FUNCTABLE_SIZE; + tr.inverseSawToothTable[i] = 1.0 - tr.sawToothTable[i]; + + if ( i < FUNCTABLE_SIZE / 2 ) + { + if ( i < FUNCTABLE_SIZE / 4 ) + { + tr.triangleTable[i] = ( float ) i / ( FUNCTABLE_SIZE / 4 ); + } + else + { + tr.triangleTable[i] = 1.0f - tr.triangleTable[i-FUNCTABLE_SIZE / 4]; + } + } + else + { + tr.triangleTable[i] = -tr.triangleTable[i-FUNCTABLE_SIZE/2]; + } + } + + R_InitFogTable(); + + R_NoiseInit(); + + R_Register(); + + backEndData = (backEndData_t *) Hunk_Alloc( sizeof( backEndData_t ), qtrue ); + R_ToggleSmpFrame(); //r_smp + + const color4ub_t color = {0xff, 0xff, 0xff, 0xff}; + for(i=0;iinteger ) + { + // Release the Glow Vertex Shader. + if ( tr.glowVShader ) + { + qglDeleteProgramsARB( 1, &tr.glowVShader ); + } + + // Release Pixel Shader. + if ( tr.glowPShader ) + { + if ( qglCombinerParameteriNV ) + { + // Release the Glow Regcom call list. + qglDeleteLists( tr.glowPShader, 1 ); + } + else if ( qglGenProgramsARB ) + { + // Release the Glow Fragment Shader. + qglDeleteProgramsARB( 1, &tr.glowPShader ); + } + } + + // Release the scene glow texture. + qglDeleteTextures( 1, &tr.screenGlow ); + + // Release the scene texture. + qglDeleteTextures( 1, &tr.sceneImage ); + + // Release the blur texture. + qglDeleteTextures( 1, &tr.blurImage ); + } +#endif + R_SyncRenderThread(); + R_ShutdownCommandBuffers(); +//#ifndef _XBOX + if (destroyWindow) +//#endif + { + R_DeleteTextures(); // only do this for vid_restart now, not during things like map load + } + } + + // shut down platform specific OpenGL stuff + if ( destroyWindow ) { + GLimp_Shutdown(); + } + tr.registered = qfalse; +} + +/* +============= +RE_EndRegistration + +Touch all images to make sure they are resident +============= +*/ +extern qboolean Sys_LowPhysicalMemory(); +void RE_EndRegistration( void ) { + R_SyncRenderThread(); + if (!Sys_LowPhysicalMemory()) { +#ifndef _XBOX +// RB_ShowImages(); +#endif + } +} + + +void RE_GetLightStyle(int style, color4ub_t color) +{ + if (style >= MAX_LIGHT_STYLES) + { + Com_Error( ERR_FATAL, "RE_GetLightStyle: %d is out of range", (int)style ); + return; + } + + *(int *)color = *(int *)styleColors[style]; +} + +void RE_SetLightStyle(int style, int color) +{ + if (style >= MAX_LIGHT_STYLES) + { + Com_Error( ERR_FATAL, "RE_SetLightStyle: %d is out of range", (int)style ); + return; + } + + if (*(int*)styleColors[style] != color) + { + *(int *)styleColors[style] = color; + styleUpdated[style] = true; + } +} + + +/* +@@@@@@@@@@@@@@@@@@@@@ +GetRefAPI + +@@@@@@@@@@@@@@@@@@@@@ +*/ +extern void R_WorldEffectCommand(const char *command); +refexport_t *GetRefAPI ( int apiVersion ) { + static refexport_t re; + + memset( &re, 0, sizeof( re ) ); + + if ( apiVersion != REF_API_VERSION ) { + VID_Printf(PRINT_ALL, "Mismatched REF_API_VERSION: expected %i, got %i\n", + REF_API_VERSION, apiVersion ); + return NULL; + } + + // the RE_ functions are Renderer Entry points + + re.Shutdown = RE_Shutdown; + + re.BeginRegistration = RE_BeginRegistration; + re.RegisterModel = RE_RegisterModel; + re.RegisterSkin = RE_RegisterSkin; + re.GetAnimationCFG = RE_GetAnimationCFG; + re.RegisterShader = RE_RegisterShader; + re.RegisterShaderNoMip = RE_RegisterShaderNoMip; + re.LoadWorld = RE_LoadWorldMap; + re.SetWorldVisData = RE_SetWorldVisData; + re.EndRegistration = RE_EndRegistration; + + re.RegisterMedia_LevelLoadBegin = RE_RegisterMedia_LevelLoadBegin; + re.RegisterMedia_LevelLoadEnd = RE_RegisterMedia_LevelLoadEnd; + + re.BeginFrame = RE_BeginFrame; + re.EndFrame = RE_EndFrame; + + re.ProcessDissolve = RE_ProcessDissolve; + re.InitDissolve = RE_InitDissolve; + + re.MarkFragments = R_MarkFragments; + re.LerpTag = R_LerpTag; + re.ModelBounds = R_ModelBounds; + + re.ClearScene = RE_ClearScene; + re.AddRefEntityToScene = RE_AddRefEntityToScene; + re.GetLighting = RE_GetLighting; + re.AddPolyToScene = RE_AddPolyToScene; + re.AddLightToScene = RE_AddLightToScene; + re.RenderScene = RE_RenderScene; + + re.SetColor = RE_SetColor; + re.DrawStretchPic = RE_StretchPic; + re.DrawStretchRaw = RE_StretchRaw; + re.UploadCinematic = RE_UploadCinematic; + + re.DrawRotatePic = RE_RotatePic; + re.DrawRotatePic2 = RE_RotatePic2; + re.LAGoggles = RE_LAGoggles; + re.Scissor = RE_Scissor; + + re.GetScreenShot = RE_GetScreenShot; +#ifndef _XBOX + re.TempRawImage_ReadFromFile = RE_TempRawImage_ReadFromFile; +#endif + re.TempRawImage_CleanUp = RE_TempRawImage_CleanUp; + + re.GetLightStyle = RE_GetLightStyle; + re.SetLightStyle = RE_SetLightStyle; + re.WorldEffectCommand = R_WorldEffectCommand; + + re.GetBModelVerts = RE_GetBModelVerts; + + re.RegisterFont = RE_RegisterFont; +#ifndef _XBOX + re.Font_StrLenPixels = RE_Font_StrLenPixels; + re.Font_HeightPixels = RE_Font_HeightPixels; + re.Font_DrawString = RE_Font_DrawString; +#endif + re.Font_StrLenChars = RE_Font_StrLenChars; + re.Language_IsAsian = Language_IsAsian; + re.Language_UsesSpaces = Language_UsesSpaces; + re.AnyLanguage_ReadCharFromString = AnyLanguage_ReadCharFromString; + + return &re; +} + diff --git a/code/renderer/tr_jpeg_interface.cpp b/code/renderer/tr_jpeg_interface.cpp new file mode 100644 index 0000000..5592b85 --- /dev/null +++ b/code/renderer/tr_jpeg_interface.cpp @@ -0,0 +1,541 @@ +// Filename:- tr_jpeg_interace.cpp +// + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "tr_local.h" +#include "tr_jpeg_interface.h" + +/* + * Include file for users of JPEG library. + * You will need to have included system headers that define at least + * the typedefs FILE and size_t before you can include jpeglib.h. + * (stdio.h is sufficient on ANSI-conforming systems.) + * You may also wish to include "jerror.h". + */ + +#define JPEG_INTERNALS +#include "../jpeg-6/jpeglib.h" + +// JPG decompression now subroutinised so I can call it from the savegame stuff... +// +// (note, the param "byte* pJPGData" should be a malloc of 4K more than the JPG data because the decompressor will read +// up to 4K beyond what's actually presented during decompression). +// +// This will Z_Malloc the output data buffer that gets fed back into "pic", so Z_Free it yourself later. +// +void Decompress_JPG( const char *filename, byte *pJPGData, unsigned char **pic, int *width, int *height ) +{ + /* This struct contains the JPEG decompression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + */ + struct jpeg_decompress_struct cinfo; + /* We use our private extension JPEG error handler. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPARRAY buffer; /* Output row buffer */ + int row_stride; /* physical row width in output buffer */ + unsigned char *out; + byte *bbuf; + + /* Step 1: allocate and initialize JPEG decompression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + + /* Now we can initialize the JPEG decompression object. */ + jpeg_create_decompress(&cinfo); + + /* Step 2: specify data source (eg, a file) */ + + jpeg_stdio_src(&cinfo, pJPGData); + + /* Step 3: read file parameters with jpeg_read_header() */ + + (void) jpeg_read_header(&cinfo, TRUE); + /* We can ignore the return value from jpeg_read_header since + * (a) suspension is not possible with the stdio data source, and + * (b) we passed TRUE to reject a tables-only JPEG file as an error. + * See libjpeg.doc for more info. + */ + + /* Step 4: set parameters for decompression */ + + /* In this example, we don't need to change any of the defaults set by + * jpeg_read_header(), so we do nothing here. + */ + + /* Step 5: Start decompressor */ + + (void) jpeg_start_decompress(&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* We may need to do some setup of our own at this point before reading + * the data. After jpeg_start_decompress() we have the correct scaled + * output image dimensions available, as well as the output colormap + * if we asked for color quantization. + * In this example, we need to make an output work buffer of the right size. + */ + /* JSAMPLEs per row in output buffer */ + row_stride = cinfo.output_width * cinfo.output_components; + + if (cinfo.output_components!=4 && cinfo.output_components!=1 ) { + VID_Printf(PRINT_WARNING, "JPG %s is unsupported color depth (%d)\n", filename, cinfo.output_components); + } + out = (byte *)Z_Malloc(cinfo.output_width*cinfo.output_height*4, TAG_TEMP_JPG, qfalse ); + + *pic = out; + *width = cinfo.output_width; + *height = cinfo.output_height; + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + + /* Here we use the library's state variable cinfo.output_scanline as the + * loop counter, so that we don't have to keep track ourselves. + */ + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + bbuf = ((out+(row_stride*cinfo.output_scanline))); + buffer = &bbuf; + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + } + + // if we've just loaded a greyscale, then adjust it from 8-bit to 32bit by stretch-copying it over itself... + // (this also does the alpha stuff as well) + // + if (cinfo.output_components == 1) + { + byte *pbDest = (*pic + (cinfo.output_width * cinfo.output_height * 4))-1; + byte *pbSrc = (*pic + (cinfo.output_width * cinfo.output_height ))-1; + int iPixels = cinfo.output_width * cinfo.output_height; + + for (int i=0; idest; + + dest->pub.next_output_byte = dest->outfile; + dest->pub.free_in_buffer = dest->size; +} + + +/* + * Empty the output buffer --- called whenever buffer fills up. + * + * In typical applications, this should write the entire output buffer + * (ignoring the current state of next_output_byte & free_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been dumped. + * + * In applications that need to be able to suspend compression due to output + * overrun, a FALSE return indicates that the buffer cannot be emptied now. + * In this situation, the compressor will return to its caller (possibly with + * an indication that it has not accepted all the supplied scanlines). The + * application should resume compression after it has made more room in the + * output buffer. Note that there are substantial restrictions on the use of + * suspension --- see the documentation. + * + * When suspending, the compressor will back up to a convenient restart point + * (typically the start of the current MCU). next_output_byte & free_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point will be regenerated after resumption, so do not + * write it out when emptying the buffer externally. + */ + +boolean empty_output_buffer (j_compress_ptr cinfo) +{ + return TRUE; +} + + +/* + * Compression initialization. + * Before calling this, all parameters and a data destination must be set up. + * + * We require a write_all_tables parameter as a failsafe check when writing + * multiple datastreams from the same compression object. Since prior runs + * will have left all the tables marked sent_table=TRUE, a subsequent run + * would emit an abbreviated stream (no tables) by default. This may be what + * is wanted, but for safety's sake it should not be the default behavior: + * programmers should have to make a deliberate choice to emit abbreviated + * images. Therefore the documentation and examples should encourage people + * to pass write_all_tables=TRUE; then it will take active thought to do the + * wrong thing. + */ + +GLOBAL void +jpeg_start_compress (j_compress_ptr cinfo, boolean write_all_tables) +{ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + if (write_all_tables) + jpeg_suppress_tables(cinfo, FALSE); /* mark all tables to be written */ + + /* (Re)initialize error mgr and destination modules */ + (*cinfo->err->reset_error_mgr) ((j_common_ptr) cinfo); + (*cinfo->dest->init_destination) (cinfo); + /* Perform master selection of active modules */ + jinit_compress_master(cinfo); + /* Set up for the first pass */ + (*cinfo->master->prepare_for_pass) (cinfo); + /* Ready for application to drive first pass through jpeg_write_scanlines + * or jpeg_write_raw_data. + */ + cinfo->next_scanline = 0; + cinfo->global_state = (cinfo->raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING); +} + + +/* + * Write some scanlines of data to the JPEG compressor. + * + * The return value will be the number of lines actually written. + * This should be less than the supplied num_lines only in case that + * the data destination module has requested suspension of the compressor, + * or if more than image_height scanlines are passed in. + * + * Note: we warn about excess calls to jpeg_write_scanlines() since + * this likely signals an application programmer error. However, + * excess scanlines passed in the last valid call are *silently* ignored, + * so that the application need not adjust num_lines for end-of-image + * when using a multiple-scanline buffer. + */ + +GLOBAL JDIMENSION +jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines) +{ + JDIMENSION row_ctr, rows_left; + + if (cinfo->global_state != CSTATE_SCANNING) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + if (cinfo->next_scanline >= cinfo->image_height) + WARNMS(cinfo, JWRN_TOO_MUCH_DATA); + + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) { + cinfo->progress->pass_counter = (long) cinfo->next_scanline; + cinfo->progress->pass_limit = (long) cinfo->image_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + } + + /* Give master control module another chance if this is first call to + * jpeg_write_scanlines. This lets output of the frame/scan headers be + * delayed so that application can write COM, etc, markers between + * jpeg_start_compress and jpeg_write_scanlines. + */ + if (cinfo->master->call_pass_startup) + (*cinfo->master->pass_startup) (cinfo); + + /* Ignore any extra scanlines at bottom of image. */ + rows_left = cinfo->image_height - cinfo->next_scanline; + if (num_lines > rows_left) + num_lines = rows_left; + + row_ctr = 0; + (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines); + cinfo->next_scanline += row_ctr; + return row_ctr; +} + +/* + * Terminate destination --- called by jpeg_finish_compress + * after all data has been written. Usually needs to flush buffer. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +static int hackSize; + +void term_destination (j_compress_ptr cinfo) +{ + my_dest_ptr dest = (my_dest_ptr) cinfo->dest; + size_t datacount = dest->size - dest->pub.free_in_buffer; + hackSize = datacount; +} + + +/* + * Prepare for output to a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing compression. + */ + +void jpegDest (j_compress_ptr cinfo, byte* outfile, int size) +{ + my_dest_ptr dest; + + /* The destination object is made permanent so that multiple JPEG images + * can be written to the same file without re-executing jpeg_stdio_dest. + * This makes it dangerous to use this manager and a different destination + * manager serially with the same JPEG object, because their private object + * sizes may be different. Caveat programmer. + */ + if (cinfo->dest == NULL) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof(my_destination_mgr)); + } + + dest = (my_dest_ptr) cinfo->dest; + dest->pub.init_destination = init_destination; + dest->pub.empty_output_buffer = empty_output_buffer; + dest->pub.term_destination = term_destination; + dest->outfile = outfile; + dest->size = size; +} + +// returns a Z_Malloc'd piece of mem that you should free up yourself +// +byte *Compress_JPG(int *pOutputSize, int quality, int image_width, int image_height, byte *image_buffer, qboolean bInvertDuringCompression) +{ + /* This struct contains the JPEG compression parameters and pointers to + * working space (which is allocated as needed by the JPEG library). + * It is possible to have several such structures, representing multiple + * compression/decompression processes, in existence at once. We refer + * to any one struct (and its associated working data) as a "JPEG object". + */ + struct jpeg_compress_struct cinfo; + /* This struct represents a JPEG error handler. It is declared separately + * because applications often want to supply a specialized error handler + * (see the second half of this file for an example). But here we just + * take the easy way out and use the standard error handler, which will + * print a message on stderr and call exit() if compression fails. + * Note that this struct must live as long as the main JPEG parameter + * struct, to avoid dangling-pointer problems. + */ + struct jpeg_error_mgr jerr; + /* More stuff */ + JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ + int row_stride; /* physical row width in image buffer */ + + /* Step 1: allocate and initialize JPEG compression object */ + + /* We have to set up the error handler first, in case the initialization + * step fails. (Unlikely, but it could happen if you are out of memory.) + * This routine fills in the contents of struct jerr, and returns jerr's + * address which we place into the link field in cinfo. + */ + cinfo.err = jpeg_std_error(&jerr); + /* Now we can initialize the JPEG compression object. */ + jpeg_create_compress(&cinfo); + + /* Step 2: specify data destination (eg, a file) */ + /* Note: steps 2 and 3 can be done in either order. */ + + /* Here we use the library-supplied code to send compressed data to a + * stdio stream. You can also write your own code to do something else. + * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that + * requires it in order to write binary files. + */ + byte *out = // (unsigned char *)ri.Hunk_AllocateTempMemory(image_width*image_height*4); + (unsigned char *)Z_Malloc(image_width*image_height*4, TAG_TEMP_JPG, qfalse); + + jpegDest(&cinfo, out, image_width*image_height*4); + + /* Step 3: set parameters for compression */ + + /* First we supply a description of the input image. + * Four fields of the cinfo struct must be filled in: + */ + cinfo.image_width = image_width; /* image width and height, in pixels */ + cinfo.image_height = image_height; + cinfo.input_components = 4; /* # of color components per pixel */ + cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ + /* Now use the library's routine to set default compression parameters. + * (You must set at least cinfo.in_color_space before calling this, + * since the defaults depend on the source color space.) + */ + jpeg_set_defaults(&cinfo); + /* Now you can set any non-default parameters you wish to. + * Here we just illustrate the use of quality (quantization table) scaling: + */ + jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); + + /* Step 4: Start compressor */ + + /* TRUE ensures that we will write a complete interchange-JPEG file. + * Pass TRUE unless you are very sure of what you're doing. + */ + jpeg_start_compress(&cinfo, TRUE); + + /* Step 5: while (scan lines remain to be written) */ + /* jpeg_write_scanlines(...); */ + + /* Here we use the library's state variable cinfo.next_scanline as the + * loop counter, so that we don't have to keep track ourselves. + * To keep things simple, we pass one scanline per call; you can pass + * more if you wish, though. + */ + row_stride = image_width * 4; /* JSAMPLEs per row in image_buffer */ + + while (cinfo.next_scanline < cinfo.image_height) { + /* jpeg_write_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could pass + * more than one scanline at a time if that's more convenient. + */ + if (bInvertDuringCompression) + { + row_pointer[0] = & image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride]; + } + else + { + row_pointer[0] = & image_buffer[ cinfo.next_scanline * row_stride]; + } + + jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + /* Step 6: Finish compression */ + + jpeg_finish_compress(&cinfo); + + /* Step 7: release JPEG compression object */ + + /* This is an important step since it will release a good deal of memory. */ + jpeg_destroy_compress(&cinfo); + + /* And we're done! */ + + *pOutputSize = hackSize; + return out; +} + +void SaveJPG(const char * filename, int quality, int image_width, int image_height, unsigned char *image_buffer) +{ + int iOutputSize = 0; + + byte *pbOut = Compress_JPG(&iOutputSize, quality, image_width, image_height, image_buffer, qtrue); + + FS_WriteFile( filename, pbOut, iOutputSize ); + + Z_Free(pbOut); +} + + + +void JPG_ErrorThrow(LPCSTR message) +{ + Com_Error( ERR_FATAL, "JPG: %s\n", message ); +} + +void JPG_MessageOut(LPCSTR message) +{ + VID_Printf(PRINT_ALL, "%s\n", message); +} + +//////////////// eof //////////// + diff --git a/code/renderer/tr_jpeg_interface.h b/code/renderer/tr_jpeg_interface.h new file mode 100644 index 0000000..6b8d383 --- /dev/null +++ b/code/renderer/tr_jpeg_interface.h @@ -0,0 +1,40 @@ +// Filename:- tr_jpeg_interface.h +// +#pragma warning (disable: 4100) //unreferenced formal parameter +#pragma warning (disable: 4127) //conditional expression is constant +#pragma warning (disable: 4244) //int to unsigned short + +#ifndef TR_JPEG_INTERFACE_H +#define TR_JPEG_INTERFACE_H + + +#ifdef __cplusplus +extern "C" +{ +#endif + + +#ifndef LPCSTR +typedef const char * LPCSTR; +#endif + +int LoadJPG( const char *filename, unsigned char **pic, int *width, int *height ); +void SaveJPG( const char *filename, int quality, int image_width, int image_height, unsigned char *image_buffer); + +void JPG_ErrorThrow(LPCSTR message); +void JPG_MessageOut(LPCSTR message); +#define ERROR_STRING_NO_RETURN(message) JPG_ErrorThrow(message) +#define MESSAGE_STRING(message) JPG_MessageOut(message) + + +#ifdef __cplusplus +}; +#endif + + + +#endif // #ifndef TR_JPEG_INTERFACE_H + + +////////////////// eof ////////////////// + diff --git a/code/renderer/tr_landscape.h b/code/renderer/tr_landscape.h new file mode 100644 index 0000000..cf816d6 --- /dev/null +++ b/code/renderer/tr_landscape.h @@ -0,0 +1,193 @@ +#ifndef _INC_LANDSCAPE_H +#define _INC_LANDSCAPE_H + +// Number of TriTreeNodes available +#define POOL_SIZE (50000) + +#define TEXTURE_ALPHA_TL 0x000000ff +#define TEXTURE_ALPHA_TR 0x0000ff00 +#define TEXTURE_ALPHA_BL 0x00ff0000 +#define TEXTURE_ALPHA_BR 0x000000ff + +#define INDEX_TL 0 +#define INDEX_TR 1 +#define INDEX_BL 2 +#define INDEX_BR 3 + +#define VARIANCE_MIN 0.0f +#define VARIANCE_MAX 2000.0f +#define SPLIT_VARIANCE_SIZE 20 +#define SPLIT_VARIANCE_STEP (VARIANCE_MAX / SPLIT_VARIANCE_SIZE) + +#define VectorAverage(a,b,c) (((c)[0]=((a)[0]+(b)[0])*0.5f),((c)[1]=((a)[1]+(b)[1])*0.5f),((c)[2]=((a)[2]+(b)[2])*0.5f)) + +class CTerVert +{ +public: + vec3_t coords; // real world coords of terxel + vec3_t normal; // required to calculate lighting and used in physics + color4ub_t tint; // tint at this terxel + float tex[2]; // texture coordinates at this terxel + int height; // Copy of heightmap data + int tessIndex; // Index of the vert in the tess array + int tessRegistration; // ...... for the tess with this registration + + CTerVert( void ) { memset(this, 0, sizeof(*this)); } + ~CTerVert( void ) { } +}; + +class CTRHeightDetails +{ +private: + qhandle_t mShader; +public: + CTRHeightDetails( void ) { } + ~CTRHeightDetails( void ) { } + + const qhandle_t GetShader( void ) const { return(mShader); } + void SetShader(const qhandle_t shader) { mShader = shader; } +}; + +// +// Information of each patch (tessellated area) of a CTRLandScape +// +class CTRPatch +{ +private: + class CCMLandScape *owner; + class CTRLandScape *localowner; + + CCMPatch *common; + vec3_t mCenter; // Real world center of the patch +// vec3_t mNormal[2]; +// float mDistance[2]; + + CTerVert *mRenderMap; // Modulation value and texture coords per vertex + shader_t *mTLShader; // Dynamically created blended shader for the top left triangle + shader_t *mBRShader; // Dynamically created blended shader for the bottom right triangle + + bool misVisible; // Is this patch visible in the current frame? + +public: + CTRPatch(void) { } + ~CTRPatch(void) { } + + // Accessors + const vec3_t &GetWorld(void) const { return(common->GetWorld()); } + const vec3_t &GetMins(void) const { return(common->GetMins()); } + const vec3_t &GetMaxs(void) const { return(common->GetMaxs()); } + const vec3pair_t &GetBounds(void) const { return(common->GetBounds()); } + shader_t *GetTLShader(void) { return mTLShader; } + shader_t *GetBRShader(void) { return mBRShader; } + + void SetCommon(CCMPatch *in) { common = in; } + const CCMPatch *GetCommon(void) const { return(common); } + bool isVisible(void) { return(misVisible); } + void SetTLShader(qhandle_t in) { mTLShader = R_GetShaderByHandle(in); } + void SetBRShader(qhandle_t in) { mBRShader = R_GetShaderByHandle(in); } + void SetOwner(CCMLandScape *in) { owner = in; } + void SetLocalOwner(CTRLandScape *in) { localowner = in; } + void Clear(void) { memset(this, 0, sizeof(*this)); } + void SetCenter(void) { VectorAverage(common->GetMins(), common->GetMaxs(), mCenter); } + void CalcNormal(void); + + // Prototypes + void SetVisibility(bool visCheck); + void RenderCorner(ivec5_t corner); + void Render(int Part); + void RecurseRender(int depth, ivec5_t left, ivec5_t right, ivec5_t apex); + void SetRenderMap(const int x, const int y); + int RenderWaterVert(int x, int y); + void RenderWater(void); + const bool HasWater(void) const; +}; + + +#define PI_TOP 1 +#define PI_BOTTOM 2 +#define PI_BOTH 3 + +typedef struct SPatchInfo +{ + CTRPatch *mPatch; + shader_t *mShader; + int mPart; +} TPatchInfo; + +// +// The master class used to define an area of terrain +// + +class CTRLandScape +{ +private: + const CCMLandScape *common; + CTRPatch *mTRPatches; // Local patch info + TPatchInfo *mSortedPatches; + + int mPatchMinx, mPatchMaxx; + int mPatchMiny, mPatchMaxy; + int mMaxNode; // terxels * terxels = exit condition for splitting + int mSortedCount; + + float mPatchSize; + + shader_t *mShader; // shader the terrain got its contents from + + CTerVert *mRenderMap; // modulation value and texture coords per vertex + float mTextureScale; // Scale of texture mapped to terrain + + float mScalarSize; + + shader_t *mWaterShader; // Water shader + qhandle_t mFlatShader; // Flat ground shader + + CTRHeightDetails mHeightDetails[HEIGHT_RESOLUTION]; // Array of info specific to height +#if _DEBUG + int mCycleCount; +#endif +public: + CTRLandScape(const char *configstring); + ~CTRLandScape(void); + + // Accessors + const int GetBlockWidth(void) const { return(common->GetBlockWidth()); } + const int GetBlockHeight(void) const { return(common->GetBlockHeight()); } + const vec3_t &GetMins(void) const { return(common->GetMins()); } + const vec3_t &GetMaxs(void) const { return(common->GetMaxs()); } + const vec3_t &GetTerxelSize(void) const { return(common->GetTerxelSize()); } + const vec3_t &GetPatchSize(void) const { return(common->GetPatchSize()); } + const int GetWidth(void) const { return(common->GetWidth()); } + const int GetHeight(void) const { return(common->GetHeight()); } + const int GetRealWidth(void) const { return(common->GetRealWidth()); } + const int GetRealHeight(void) const { return(common->GetRealHeight()); } + + void SetCommon(const CCMLandScape *landscape) { common = landscape; } + const CCMLandScape *GetCommon( void ) const { return(common); } + const thandle_t GetCommonId( void ) const { return(common->GetTerrainId()); } + shader_t *GetShader(void) const { return(mShader); } + CTerVert *GetRenderMap(const int x, const int y) const { return(mRenderMap + x + (y * common->GetRealWidth())); } + CTRPatch *GetPatch(const int x, const int y) const { return(mTRPatches + (common->GetBlockWidth() * y) + x); } + const CTRHeightDetails *GetHeightDetail(int height) const { return(mHeightDetails + height); } + const float GetScalarSize(void) const { return(mScalarSize); } + const int GetMaxNode(void) const { return(mMaxNode); } + + // Prototypes + void CalculateRegion(void); + void Reset(bool visCheck = true); + void Render(void); + void CalculateRealCoords(void); + void CalculateNormals(void); + void CalculateTextureCoords(void); + void CalculateLighting(void); + void CalculateShaders(void); + qhandle_t GetBlendedShader(qhandle_t a, qhandle_t b, qhandle_t c, bool surfaceSprites); + void LoadTerrainDef(const char *td); + void CopyHeightMap(void); + void SetShaders(const int height, const qhandle_t shader); +}; + +void R_CalcTerrainVisBounds(CTRLandScape *landscape); +void R_AddTerrainSurfaces(void); + +#endif //INC_LANDSCAPE_H diff --git a/code/renderer/tr_light.cpp b/code/renderer/tr_light.cpp new file mode 100644 index 0000000..a7010bd --- /dev/null +++ b/code/renderer/tr_light.cpp @@ -0,0 +1,572 @@ +// tr_light.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + +#define DLIGHT_AT_RADIUS 16 +// at the edge of a dlight's influence, this amount of light will be added + +#define DLIGHT_MINIMUM_RADIUS 16 +// never calculate a range less than this to prevent huge light numbers + + +/* +=============== +R_TransformDlights + +Transforms the origins of an array of dlights. +Used by both the front end (for DlightBmodel) and +the back end (before doing the lighting calculation) +=============== +*/ +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or) { + int i; + vec3_t temp; + + for ( i = 0 ; i < count ; i++, dl++ ) { + VectorSubtract( dl->origin, or->origin, temp ); + dl->transformed[0] = DotProduct( temp, or->axis[0] ); + dl->transformed[1] = DotProduct( temp, or->axis[1] ); + dl->transformed[2] = DotProduct( temp, or->axis[2] ); + } +} + +/* +============= +R_DlightBmodel + +Determine which dynamic lights may effect this bmodel +============= +*/ +#ifndef VV_LIGHTING +void R_DlightBmodel( bmodel_t *bmodel, qboolean NoLight ) { + int i, j; + dlight_t *dl; + int mask; + msurface_t *surf; + + // transform all the lights + R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.or ); + + mask = 0; + if (!NoLight) + { + for ( i=0 ; itransformed[j] - bmodel->bounds[1][j] > dl->radius ) { + break; + } + if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) { + break; + } + } + if ( j < 3 ) { + continue; + } + + // we need to check this light + mask |= 1 << i; + } + } + + tr.currentEntity->needDlights = (mask != 0); + tr.currentEntity->dlightBits = mask; + + + // set the dlight bits in all the surfaces + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + surf = bmodel->firstSurface + i; + + if ( *surf->data == SF_FACE ) { + ((srfSurfaceFace_t *)surf->data)->dlightBits = mask; + } else if ( *surf->data == SF_GRID ) { + ((srfGridMesh_t *)surf->data)->dlightBits = mask; + } else if ( *surf->data == SF_TRIANGLES ) { + ((srfTriangles_t *)surf->data)->dlightBits = mask; + } + } +} +#endif // VV_LIGHTING + + +/* +============================================================================= + +LIGHT SAMPLING + +============================================================================= +*/ + +extern cvar_t *r_ambientScale; +extern cvar_t *r_directedScale; +extern cvar_t *r_debugLight; + +/* +================= +R_SetupEntityLightingGrid + +================= +*/ +#ifdef VV_LIGHTING +void R_SetupEntityLightingGrid( trRefEntity_t *ent ) { +#else +static void R_SetupEntityLightingGrid( trRefEntity_t *ent ) { +#endif + vec3_t lightOrigin; + int pos[3]; + int i, j; + float frac[3]; + int gridStep[3]; + vec3_t direction; + float totalFactor; + unsigned short *startGridPos; +#ifdef _XBOX + byte zeroArray[3]; + byte style; + + zeroArray[0] = zeroArray[1] = zeroArray[2] = 0; +#endif + + + if (r_fullbright->integer || (tr.refdef.rdflags & RDF_doLAGoggles) ) + { + ent->ambientLight[0] = ent->ambientLight[1] = ent->ambientLight[2] = 255.0; + ent->directedLight[0] = ent->directedLight[1] = ent->directedLight[2] = 255.0; + VectorCopy( tr.sunDirection, ent->lightDir ); + return; + } + + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } +#define ACCURATE_LIGHTGRID_SAMPLING 1 +#if ACCURATE_LIGHTGRID_SAMPLING + vec3_t startLightOrigin; + VectorCopy( lightOrigin, startLightOrigin ); +#endif + + VectorSubtract( lightOrigin, tr.world->lightGridOrigin, lightOrigin ); + for ( i = 0 ; i < 3 ; i++ ) { + float v; + + v = lightOrigin[i]*tr.world->lightGridInverseSize[i]; + pos[i] = floor( v ); + frac[i] = v - pos[i]; + if ( pos[i] < 0 ) { + pos[i] = 0; + } else if ( pos[i] >= tr.world->lightGridBounds[i] - 1 ) { + pos[i] = tr.world->lightGridBounds[i] - 1; + } + } + + VectorClear( ent->ambientLight ); + VectorClear( ent->directedLight ); + VectorClear( direction ); + + // trilerp the light value + gridStep[0] = 1; + gridStep[1] = tr.world->lightGridBounds[0]; + gridStep[2] = tr.world->lightGridBounds[0] * tr.world->lightGridBounds[1]; + startGridPos = tr.world->lightGridArray + pos[0] * gridStep[0] + + pos[1] * gridStep[1] + pos[2] * gridStep[2]; +#if ACCURATE_LIGHTGRID_SAMPLING + vec3_t startGridOrg; + VectorCopy( tr.world->lightGridOrigin, startGridOrg ); + startGridOrg[0] += pos[0] * tr.world->lightGridSize[0]; + startGridOrg[1] += pos[1] * tr.world->lightGridSize[1]; + startGridOrg[2] += pos[2] * tr.world->lightGridSize[2]; +#endif + totalFactor = 0; + for ( i = 0 ; i < 8 ; i++ ) { + float factor; + mgrid_t *data; + unsigned short *gridPos; + int lat, lng; + vec3_t normal; +#if ACCURATE_LIGHTGRID_SAMPLING + vec3_t gridOrg; + VectorCopy( startGridOrg, gridOrg ); +#endif + + factor = 1.0; + gridPos = startGridPos; + for ( j = 0 ; j < 3 ; j++ ) { + if ( i & (1<lightGridSize[j]; +#endif + } else { + factor *= (1.0 - frac[j]); + } + } + + if (gridPos >= tr.world->lightGridArray + tr.world->numGridArrayElements) + {//we've gone off the array somehow + continue; + } + data = tr.world->lightGridData + *gridPos; + +#ifdef _XBOX + const byte *memory = (const byte *)tr.world->lightGridData + data->data; + + style = data->flags & (1 << 4) ? memory[0] : LS_NONE; + if ( style == LS_NONE ) + { + continue; // ignore samples in walls + } + + totalFactor += factor; + + const byte *array; + + for(j=0;jflags & (1 << (j + 4))) { + style = *memory; + memory++; + } else { + style = LS_NONE; + } + + if (style != LS_NONE) + { + if(data->flags & (1 << j)) { + array = memory; + memory += 3; + } else { + array = zeroArray; + } + + ent->ambientLight[0] += factor * array[0] * styleColors[style][0] / 255.0f; + ent->ambientLight[1] += factor * array[1] * styleColors[style][1] / 255.0f; + ent->ambientLight[2] += factor * array[2] * styleColors[style][2] / 255.0f; + + if(array != zeroArray) { + array = memory; + memory += 3; + } + + ent->directedLight[0] += factor * array[0] * styleColors[style][0] / 255.0f; + ent->directedLight[1] += factor * array[1] * styleColors[style][1] / 255.0f; + ent->directedLight[2] += factor * array[2] * styleColors[style][2] / 255.0f; + } + else + { + break; + } + } + +#else // _XBOX + + if ( data->styles[0] == LS_NONE ) + { + continue; // ignore samples in walls + } + +#if 0 + if ( !SV_inPVS( startLightOrigin, gridOrg ) ) + { + continue; + } +#endif + + totalFactor += factor; + + for(j=0;jstyles[j] != LS_NONE) + { + const byte style= data->styles[j]; + + ent->ambientLight[0] += factor * data->ambientLight[j][0] * styleColors[style][0] / 255.0f; + ent->ambientLight[1] += factor * data->ambientLight[j][1] * styleColors[style][1] / 255.0f; + ent->ambientLight[2] += factor * data->ambientLight[j][2] * styleColors[style][2] / 255.0f; + + ent->directedLight[0] += factor * data->directLight[j][0] * styleColors[style][0] / 255.0f; + ent->directedLight[1] += factor * data->directLight[j][1] * styleColors[style][1] / 255.0f; + ent->directedLight[2] += factor * data->directLight[j][2] * styleColors[style][2] / 255.0f; + } + else + { + break; + } + } + +#endif // _XBOX + + lat = data->latLong[1]; + lng = data->latLong[0]; + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) + + normal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + normal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + normal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + VectorMA( direction, factor, normal, direction ); + +#if ACCURATE_LIGHTGRID_SAMPLING +#ifndef _XBOX + if ( r_debugLight->integer && ent->e.hModel == -1 ) + { + //draw + refEntity_t refEnt; + refEnt.hModel = 0; + refEnt.ghoul2 = NULL; + refEnt.renderfx = 0; + VectorCopy( gridOrg, refEnt.origin ); + vectoangles( normal, refEnt.angles ); + AnglesToAxis( refEnt.angles, refEnt.axis ); + refEnt.reType = RT_MODEL; + RE_AddRefEntityToScene( &refEnt ); + + refEnt.renderfx = RF_DEPTHHACK; + refEnt.reType = RT_SPRITE; + refEnt.customShader = RE_RegisterShader( "gfx/misc/debugAmbient" ); + refEnt.shaderRGBA[0] = data->ambientLight[0][0]; + refEnt.shaderRGBA[1] = data->ambientLight[0][1]; + refEnt.shaderRGBA[2] = data->ambientLight[0][2]; + refEnt.shaderRGBA[3] = 255; + refEnt.radius = factor*50+2.0f; // maybe always give it a minimum size? + refEnt.rotation = 0; // don't let the sprite wobble around + RE_AddRefEntityToScene( &refEnt ); + + refEnt.reType = RT_LINE; + refEnt.customShader = RE_RegisterShader( "gfx/misc/debugArrow" ); + refEnt.shaderRGBA[0] = data->directLight[0][0]; + refEnt.shaderRGBA[1] = data->directLight[0][1]; + refEnt.shaderRGBA[2] = data->directLight[0][2]; + refEnt.shaderRGBA[3] = 255; + VectorCopy( refEnt.origin, refEnt.oldorigin ); + VectorMA( gridOrg, (factor*-255) - 2.0f, normal, refEnt.origin ); // maybe always give it a minimum length + refEnt.radius = 1.5f; + RE_AddRefEntityToScene( &refEnt ); + } +#endif // _XBOX +#endif + } + + if ( totalFactor > 0 && totalFactor < 0.99 ) + { + totalFactor = 1.0 / totalFactor; + VectorScale( ent->ambientLight, totalFactor, ent->ambientLight ); + VectorScale( ent->directedLight, totalFactor, ent->directedLight ); + } + + VectorScale( ent->ambientLight, r_ambientScale->value, ent->ambientLight ); + VectorScale( ent->directedLight, r_directedScale->value, ent->directedLight ); + + VectorNormalize2( direction, ent->lightDir ); +} + + +/* +=============== +LogLight +=============== +*/ +static void LogLight( trRefEntity_t *ent ) { + int max1, max2; + + /* + if ( !(ent->e.renderfx & RF_FIRST_PERSON ) ) { + return; + } + */ + + max1 = VectorLength( ent->ambientLight ); + /* + max1 = ent->ambientLight[0]; + if ( ent->ambientLight[1] > max1 ) { + max1 = ent->ambientLight[1]; + } else if ( ent->ambientLight[2] > max1 ) { + max1 = ent->ambientLight[2]; + } + */ + + max2 = VectorLength( ent->directedLight ); + /* + max2 = ent->directedLight[0]; + if ( ent->directedLight[1] > max2 ) { + max2 = ent->directedLight[1]; + } else if ( ent->directedLight[2] > max2 ) { + max2 = ent->directedLight[2]; + } + */ + + VID_Printf( PRINT_ALL, "amb:%i dir:%i direction: (%4.2f, %4.2f, %4.2f)\n", max1, max2, ent->lightDir[0], ent->lightDir[1], ent->lightDir[2] ); +} + +/* +================= +R_SetupEntityLighting + +Calculates all the lighting values that will be used +by the Calc_* functions +================= +*/ +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) { +#ifndef VV_LIGHTING + int i; + dlight_t *dl; + float power; + vec3_t dir; + float d; + vec3_t lightDir; + vec3_t lightOrigin; + + // lighting calculations + if ( ent->lightingCalculated ) { + return; + } + ent->lightingCalculated = qtrue; + + // + // trace a sample point down to find ambient light + // + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + // if NOWORLDMODEL, only use dynamic lights (menu system, etc) + if ( !(refdef->rdflags & RDF_NOWORLDMODEL ) + && tr.world->lightGridData ) { + R_SetupEntityLightingGrid( ent ); + } else { + ent->ambientLight[0] = ent->ambientLight[1] = + ent->ambientLight[2] = tr.identityLight * 150; + ent->directedLight[0] = ent->directedLight[1] = + ent->directedLight[2] = tr.identityLight * 150; + VectorCopy( tr.sunDirection, ent->lightDir ); + } + + // bonus items and view weapons have a fixed minimum add + if ( ent->e.renderfx & RF_MORELIGHT ) { + ent->ambientLight[0] += tr.identityLight * 96; + ent->ambientLight[1] += tr.identityLight * 96; + ent->ambientLight[2] += tr.identityLight * 96; + } + else { + // give everything a minimum light add + ent->ambientLight[0] += tr.identityLight * 32; + ent->ambientLight[1] += tr.identityLight * 32; + ent->ambientLight[2] += tr.identityLight * 32; + } + + // + // modify the light by dynamic lights + // + d = VectorLength( ent->directedLight ); + VectorScale( ent->lightDir, d, lightDir ); + + for ( i = 0 ; i < refdef->num_dlights ; i++ ) { + dl = &refdef->dlights[i]; + VectorSubtract( dl->origin, lightOrigin, dir ); + d = VectorNormalize( dir ); + + power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius ); + if ( d < DLIGHT_MINIMUM_RADIUS ) { + d = DLIGHT_MINIMUM_RADIUS; + } + d = power / ( d * d ); + + VectorMA( ent->directedLight, d, dl->color, ent->directedLight ); + VectorMA( lightDir, d, dir, lightDir ); + } + + // clamp ambient + for ( i = 0 ; i < 3 ; i++ ) { + if ( ent->ambientLight[i] > tr.identityLightByte ) { + ent->ambientLight[i] = tr.identityLightByte; + } + } + + if ( r_debugLight->integer ) { + LogLight( ent ); + } + + // save out the byte packet version + ((byte *)&ent->ambientLightInt)[0] = myftol( ent->ambientLight[0] ); + ((byte *)&ent->ambientLightInt)[1] = myftol( ent->ambientLight[1] ); + ((byte *)&ent->ambientLightInt)[2] = myftol( ent->ambientLight[2] ); + ((byte *)&ent->ambientLightInt)[3] = 0xff; + + // transform the direction to local space + VectorNormalize( lightDir ); + ent->lightDir[0] = DotProduct( lightDir, ent->e.axis[0] ); + ent->lightDir[1] = DotProduct( lightDir, ent->e.axis[1] ); + ent->lightDir[2] = DotProduct( lightDir, ent->e.axis[2] ); + +#endif // VV_LIGHTING +} + +//pass in origin +qboolean RE_GetLighting( const vec3_t origin, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir) { + trRefEntity_t tr_ent; + + if ( !tr.world || !tr.world->lightGridData) { + ambientLight[0] = ambientLight[1] = ambientLight[2] = 255.0; + directedLight[0] = directedLight[1] = directedLight[2] = 255.0; + VectorCopy( tr.sunDirection, lightDir ); + return qfalse; + } + memset (&tr_ent, 0, sizeof(tr_ent) ); + + if ( ambientLight[0] == 666 ) + {//HAX0R + tr_ent.e.hModel = -1; + } + + VectorCopy (origin, tr_ent.e.origin); + R_SetupEntityLightingGrid( &tr_ent ); + VectorCopy ( tr_ent.ambientLight, ambientLight); + VectorCopy ( tr_ent.directedLight, directedLight); + VectorCopy ( tr_ent.lightDir, lightDir); + return qtrue; +} + +/* +================= +R_LightForPoint +================= +*/ +int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) +{ + trRefEntity_t ent; + + // bk010103 - this segfaults with -nolight maps + if ( tr.world->lightGridData == NULL ) + return qfalse; + + memset(&ent, 0, sizeof(ent)); + VectorCopy( point, ent.e.origin ); + R_SetupEntityLightingGrid( &ent ); + VectorCopy(ent.ambientLight, ambientLight); + VectorCopy(ent.directedLight, directedLight); + VectorCopy(ent.lightDir, lightDir); + + return qtrue; +} diff --git a/code/renderer/tr_lightmanager.cpp b/code/renderer/tr_lightmanager.cpp new file mode 100644 index 0000000..e4c5965 --- /dev/null +++ b/code/renderer/tr_lightmanager.cpp @@ -0,0 +1,944 @@ +/* +** tr_lightmanager.cpp +*/ + +#ifdef VV_LIGHTING + +#include "../server/exe_headers.h" +#include "tr_local.h" + +#include "tr_lightmanager.h" + +#include "../win32/glw_win_dx8.h" +#include "../win32/win_lighteffects.h" + + +VVLightManager VVLightMan; + + +VVLightManager::VVLightManager() +{ + +} + + +void VVLightManager::RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + VVdlight_t *dl; + + if ( !tr.registered ) { + return; + } + if ( num_dlights >= MAX_DLIGHTS ) { + return; + } + if ( intensity <= 0 ) { + return; + } + + dl = &dlights[num_dlights++]; + + VectorCopy (org, dl->origin); + dl->type = LT_POINT; + dl->radius = intensity;// * 5.0f; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; +} + +void VVLightManager::RE_AddLightToScene( VVdlight_t *light ) +{ + VVdlight_t *dl; + + if ( !tr.registered ) { + return; + } + + if( num_dlights >= MAX_DLIGHTS ) { + return; + } + + dl = &dlights[num_dlights++]; + + VectorCopy(light->origin, dl->origin); + VectorCopy(light->direction, dl->direction); + VectorCopy(light->color, dl->color); + dl->attenuation = light->attenuation; + dl->type = light->type; + dl->radius = light->radius; +} + + +//void VVLightManager::RE_AddStaticLightToScene( VVslight_t *light ) +//{ +// VVslight_t *sl; +// +// if( !tr.registered ) { +// return; +// } +// +// if( num_slights >= MAX_NUM_STATIC_LIGHTS ) { +// return; +// } +// +// sl = &slights[num_slights++]; +// +// VectorCopy(light->origin, sl->origin); +// VectorCopy(light->color, sl->color); +// sl->radius = light->radius;// * 2.0f; +//} + + + +void VVLightManager::R_TransformDlights( orientationr_t *orient) { + int i; + vec3_t temp; + VVdlight_t *dl; + + for ( i = 0 ; i < num_dlights ; i++ ) { + dl = &dlights[i]; + VectorSubtract( dl->origin, orient->origin, temp ); + dl->transformed[0] = DotProduct( temp, orient->axis[0] ); + dl->transformed[1] = DotProduct( temp, orient->axis[1] ); + dl->transformed[2] = DotProduct( temp, orient->axis[2] ); + } +} + + + +/* +============= +R_DlightBmodel + +Determine which dynamic lights may effect this bmodel +============= +*/ +void VVLightManager::R_DlightBmodel( bmodel_t *bmodel, qboolean NoLight ) { + int i, j; + VVdlight_t *dl; + int mask; + msurface_t *surf; + + mask = 0; + + // transform all the lights + R_TransformDlights( &tr.or ); + + if (!NoLight) + { + for ( i=0 ; itransformed[j] - bmodel->bounds[1][j] > dl->radius ) { + break; + } + if ( bmodel->bounds[0][j] - dl->transformed[j] > dl->radius ) { + break; + } + } + // Directional lights are always considered (MATT - change that?) + if ( j < 3 && dl->type != LT_DIRECTIONAL ) { + continue; + } + + // we need to check this light + mask |= 1 << i; + } + } + + tr.currentEntity->needDlights = (mask != 0); + + // set the dlight bits in all the surfaces + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + surf = bmodel->firstSurface + i; + + if ( *surf->data == SF_FACE ) { + ((srfSurfaceFace_t *)surf->data)->dlightBits = mask; + } else if ( *surf->data == SF_GRID ) { + ((srfGridMesh_t *)surf->data)->dlightBits = mask; + } else if ( *surf->data == SF_TRIANGLES ) { + ((srfTriangles_t *)surf->data)->dlightBits = mask; + } + } +} + + +int VVLightManager::R_DlightFace( srfSurfaceFace_t *face, int dlightBits ) { + float d; + int i; + VVdlight_t *dl; + + for ( i = 0 ; i < num_dlights ; i++ ) { + + /*if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + }*/ + dlightBits |= (1 << i); + + dl = &dlights[i]; + d = DotProduct( dl->origin, face->plane.normal ) - face->plane.dist; + // Directional lights are always considered (MATT - change that?) + if ( d < -dl->radius || d > dl->radius ) { + // dlight doesn't reach the plane + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + face->dlightBits = dlightBits; + return dlightBits; +} + + +//void VVLightManager::R_SlightFace( srfSurfaceFace_t *face ) { +// float d; +// int i, count = 0; +// VVslight_t *sl; +// +// for ( i = 0; i < num_slights; i++ ) { +// +// if(count > MAX_STATIC_LIGHTS_SURFACE - 1) +// break; +// +// sl = &slights[i]; +// d = DotProduct( sl->origin, face->plane.normal ) - face->plane.dist; +// +// if ( d > -sl->radius && d < sl->radius ) { +// face->slightBits[count++] = i; +// } +// } +//} + + +int VVLightManager::R_DlightGrid( srfGridMesh_t *grid, int dlightBits ) { + int i; + VVdlight_t *dl; + + for ( i = 0 ; i < num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &dlights[i]; + // Directional lights are always considered (MATT - change that?) + if (( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) + && dl->type != LT_DIRECTIONAL ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + dlightBits |= (1 << i ); + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits = dlightBits; + return dlightBits; +} + + +//void VVLightManager::R_SlightGrid( srfGridMesh_t *grid ) { +// int i, count = 0; +// VVslight_t *sl; +// +// for ( i = 0 ; i < num_slights ; i++ ) { +// +// if(count > MAX_STATIC_LIGHTS_SURFACE - 1) +// break; +// +// sl = &slights[i]; +// +// if ( sl->origin[0] - sl->radius > grid->meshBounds[1][0] +// || sl->origin[0] + sl->radius < grid->meshBounds[0][0] +// || sl->origin[1] - sl->radius > grid->meshBounds[1][1] +// || sl->origin[1] + sl->radius < grid->meshBounds[0][1] +// || sl->origin[2] - sl->radius > grid->meshBounds[1][2] +// || sl->origin[2] + sl->radius < grid->meshBounds[0][2] ) { +// // slight doesn't reach the bounds +// } +// else +// { +// grid->slightBits[count++]= i; +// } +// } +//} + + +int VVLightManager::R_DlightTrisurf( srfTriangles_t *surf, int dlightBits ) { + // FIXME: more dlight culling to trisurfs... + surf->dlightBits = dlightBits; + return dlightBits; +} + + +//void VVLightManager::R_SlightTrisurf( srfTriangles_t *surf ) { +// /*int i; +// +// for( i = 0; i < num_slights; i++ ) +// { +// slightBits[i] = 1; +// }*/ +//} + +/* +==================== +R_DlightSurface + +The given surface is going to be drawn, and it touches a leaf +that is touched by one or more dlights, so try to throw out +more dlights if possible. +==================== +*/ +int VVLightManager::R_DlightSurface( msurface_t *surf, int dlightBits ) { + if ( *surf->data == SF_FACE ) { + dlightBits = VVLightManager::R_DlightFace( (srfSurfaceFace_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_GRID ) { + dlightBits = VVLightManager::R_DlightGrid( (srfGridMesh_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_TRIANGLES ) { + dlightBits = VVLightManager::R_DlightTrisurf( (srfTriangles_t *)surf->data, dlightBits ); + } else { + dlightBits = 0; + } + + if ( dlightBits ) { + tr.pc.c_dlightSurfaces++; + } + + return dlightBits; +} + + +/* +================= +R_SetupEntityLighting + +Calculates all the lighting values that will be used +by the Calc_* functions +================= +*/ + +#define DLIGHT_AT_RADIUS 16 +#define DLIGHT_MINIMUM_RADIUS 16 + +void VVLightManager::R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ) { + int i; + VVdlight_t *dl; + vec3_t dir; + float d; + vec3_t lightDir; + vec3_t shadowLightDir; + vec3_t lightOrigin; + float power; + + // lighting calculations + if ( ent->lightingCalculated ) { + return; + } + ent->lightingCalculated = qtrue; + + ent->dlightBits = 0; + + // + // trace a sample point down to find ambient light + // + if ( ent->e.renderfx & RF_LIGHTING_ORIGIN ) { + // seperate lightOrigins are needed so an object that is + // sinking into the ground can still be lit, and so + // multi-part models can be lit identically + VectorCopy( ent->e.lightingOrigin, lightOrigin ); + } else { + VectorCopy( ent->e.origin, lightOrigin ); + } + + // if NOWORLDMODEL, only use dynamic lights (menu system, etc) + if ( !(refdef->rdflags & RDF_NOWORLDMODEL ) + && tr.world->lightGridData ) { + R_SetupEntityLightingGrid( ent ); + } else { + ent->ambientLight[0] = ent->ambientLight[1] = + ent->ambientLight[2] = tr.identityLight * 150; + ent->directedLight[0] = ent->directedLight[1] = + ent->directedLight[2] = tr.identityLight * 150; + // BTO - Fix for UI model rendering. tr.sunDirection is invalid + // pick an arbitrary light direction +// VectorCopy( tr.sunDirection, ent->lightDir ); + ent->lightDir[0] = ent->lightDir[1] = 0.0f; + ent->lightDir[2] = 1.0f; + } + + // bonus items and view weapons have a fixed minimum add + if ( ent->e.renderfx & RF_MORELIGHT ) { + ent->ambientLight[0] += tr.identityLight * 96; + ent->ambientLight[1] += tr.identityLight * 96; + ent->ambientLight[2] += tr.identityLight * 96; + } + else { + // give everything a minimum light add + ent->ambientLight[0] += tr.identityLight * 32; + ent->ambientLight[1] += tr.identityLight * 32; + ent->ambientLight[2] += tr.identityLight * 32; + } + + // + // modify the light by dynamic lights + // + d = VectorLength( ent->directedLight ); + VectorScale( ent->lightDir, d, lightDir ); + VectorScale( ent->lightDir, d, shadowLightDir ); + + for ( i = 0 ; i < num_dlights ; i++ ) { + dl = &dlights[i]; + VectorSubtract( dl->origin, lightOrigin, dir ); + d = VectorNormalize( dir ); + + if( d <= dl->radius ) + { + ent->dlightBits |= (1 << i); + ent->needDlights = qtrue; + } + + power = DLIGHT_AT_RADIUS * ( dl->radius * dl->radius ); + if ( d < DLIGHT_MINIMUM_RADIUS ) { + d = DLIGHT_MINIMUM_RADIUS; + } + d = power / ( d * d ); + + VectorMA( shadowLightDir, d, dir, shadowLightDir ); + } + + // clamp + for ( i = 0 ; i < 3 ; i++ ) { + if ( ent->ambientLight[i] > tr.identityLightByte ) { + ent->ambientLight[i] = tr.identityLightByte; + } + if ( ent->directedLight[i] > tr.identityLightByte ) { + ent->directedLight[i] = tr.identityLightByte; + } + } + + // save out the byte packet version + ((byte *)&ent->ambientLightInt)[0] = myftol( ent->ambientLight[0] ); + ((byte *)&ent->ambientLightInt)[1] = myftol( ent->ambientLight[1] ); + ((byte *)&ent->ambientLightInt)[2] = myftol( ent->ambientLight[2] ); + ((byte *)&ent->ambientLightInt)[3] = 0xff; + + // transform the direction to local space + VectorNormalize( lightDir ); + VectorNormalize( shadowLightDir ); + + ent->shadowDir[0] = DotProduct( shadowLightDir, ent->e.axis[0] ); + ent->shadowDir[1] = DotProduct( shadowLightDir, ent->e.axis[1] ); + ent->shadowDir[2] = DotProduct( shadowLightDir, ent->e.axis[2] ); + + ent->lightDir[0] = DotProduct( lightDir, ent->e.axis[0] ); + ent->lightDir[1] = DotProduct( lightDir, ent->e.axis[1] ); + ent->lightDir[2] = DotProduct( lightDir, ent->e.axis[2] ); +} + +inline void Short2Float(float *f, const short *s) +{ + *f = ((float)*s); +} + +void VVLightManager::ShortToVec3(const short in[3], vec3_t &out) +{ + Short2Float(&out[0], &in[0]); + Short2Float(&out[1], &in[1]); + Short2Float(&out[2], &in[2]); +} + +int VVLightManager::BoxOnPlaneSide (const short emins[3], const short emaxs[3], struct cplane_s *p) +{ + vec3_t mins; + vec3_t maxs; + ShortToVec3(emins, mins); + ShortToVec3(emaxs, maxs); + return ::BoxOnPlaneSide(mins, maxs, &tr.viewParms.frustum[0]); +} + + +void VVLightManager::R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ) { + + do { + int newDlights[2]; + + // if the node wasn't marked as potentially visible, exit + if (node->visframe != tr.visCount) { + return; + } + + // if the bounding volume is outside the frustum, nothing + // inside can be visible OPTIMIZE: don't do this all the way to leafs? + + if ( !r_nocull->integer ) { + int r; + + if ( planeBits & 1 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~1; // all descendants will also be in front + } + } + + if ( planeBits & 2 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~2; // all descendants will also be in front + } + } + + if ( planeBits & 4 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~4; // all descendants will also be in front + } + } + + if ( planeBits & 8 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~8; // all descendants will also be in front + } + } + + } + + if ( node->contents != -1 ) { + break; + } + + // node is just a decision point, so go down both sides + // since we don't care about sort orders, just go positive to negative + + // determine which dlights are needed + newDlights[0] = 0; + newDlights[1] = 0; + if ( dlightBits ) { + int i; + + for ( i = 0 ; i < num_dlights ; i++ ) { + VVdlight_t *dl; + float dist; + + if ( dlightBits & ( 1 << i ) ) { + dl = &dlights[i]; + dist = DotProduct( dl->origin, + tr.world->planes[node->planeNum].normal ) - + tr.world->planes[node->planeNum].dist; + + if ( dist > -dl->radius ) { + newDlights[0] |= ( 1 << i ); + } + if ( dist < dl->radius ) { + newDlights[1] |= ( 1 << i ); + } + } + } + } + + // recurse down the children, front side first + R_RecursiveWorldNode (node->children[0], planeBits, newDlights[0] ); + + // tail recurse + node = node->children[1]; + dlightBits = newDlights[1]; + } while ( 1 ); + + { + // leaf node, so add mark surfaces + int c; + msurface_t *surf, **mark; + mleaf_s *leaf; + + tr.pc.c_leafs++; + + // add to z buffer bounds + if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) { + tr.viewParms.visBounds[0][0] = node->mins[0]; + } + if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) { + tr.viewParms.visBounds[0][1] = node->mins[1]; + } + if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) { + tr.viewParms.visBounds[0][2] = node->mins[2]; + } + + if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) { + tr.viewParms.visBounds[1][0] = node->maxs[0]; + } + if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) { + tr.viewParms.visBounds[1][1] = node->maxs[1]; + } + if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) { + tr.viewParms.visBounds[1][2] = node->maxs[2]; + } + + // add the individual surfaces + leaf = (mleaf_s*)node; + mark = tr.world->marksurfaces + leaf->firstMarkSurfNum; + c = leaf->nummarksurfaces; + while (c--) { + // the surface may have already been added if it + // spans multiple leafs + surf = *mark; +#ifdef _XBOX + // MATT! - this is a temp hack until bspthing starts parsing flares + if(surf->data) +#endif + R_AddWorldSurface( surf, dlightBits ); + mark++; + } + } + +} + +void VVLightManager::RB_CalcDiffuseColorWorld() +{ + trRefEntity_t *ent; + VVdlight_t *dl; + + if(!num_dlights) + return; + + ent = backEnd.currentEntity; + + for(int i = 0, l = 0; i < num_dlights; i++) + { + if ( ( tess.dlightBits & ( 1 << i ) ) ) + { + qglEnable(GL_LIGHTING); + dl = &dlights[i]; + + vec3_t newColor; + newColor[0] = dl->color[0] * 255.0f; + newColor[1] = dl->color[1] * 255.0f; + newColor[2] = dl->color[2] * 255.0f; + + qglLightfv(l, GL_DIFFUSE, newColor); + +/* vec3_t ambient; + ambient[0] = ambient[1] = ambient[2] = 128; + + qglLightfv(l, GL_AMBIENT, ambient);//ent->ambientLight);*/ + + if(dl->type == LT_POINT) { + qglLightfv(l, GL_SPOT_CUTOFF, &dl->radius); + qglLightfv(l, GL_POSITION, dl->origin); + } else if(dl->type == LT_DIRECTIONAL) { + qglLightfv(l, GL_SPOT_DIRECTION, dl->direction); + } + + l++; + } + } + + float color[4]; + color[0] = 255; + color[1] = 255; + color[2] = 255; + color[3] = 255; + qglMaterialfv( GL_FRONT, GL_AMBIENT, color ); +} + + +void VVLightManager::RB_CalcDiffuseColor( DWORD *colors ) +{ + trRefEntity_t *ent; + + ent = backEnd.currentEntity; + + // Make sure to turn lighting on.... + qglEnable(GL_LIGHTING); + + qglLightfv(0, GL_AMBIENT, ent->ambientLight); + qglLightfv(0, GL_DIFFUSE, ent->directedLight); + + VectorNormalize(ent->lightDir); + + vec3_t vLight; + vLight[0] = DotProduct( ent->lightDir, ent->e.axis[0] ); + vLight[1] = DotProduct( ent->lightDir, ent->e.axis[1] ); + vLight[2] = DotProduct( ent->lightDir, ent->e.axis[2] ); + + if(VectorLengthSquared(vLight) <= 0.0001f) + { + vLight[0] = 0.0f; + vLight[1] = 1.0f; + vLight[2] = 0.0f; + } + + qglLightfv(0, GL_SPOT_DIRECTION, vLight); + + memset(colors, 0xffffffff, sizeof(DWORD) * tess.numVertexes); +} + + +void VVLightManager::RB_CalcDiffuseEntityColor( DWORD *colors ) +{ + if ( !backEnd.currentEntity ) + {//error, use the normal lighting + RB_CalcDiffuseColor(colors); + } + + trRefEntity_t *ent; + + ent = backEnd.currentEntity; + + // Make sure to turn lighting on.... + qglEnable(GL_LIGHTING); + + // Modulate ambient by entity color: + vec3_t ambient; + ambient[0] = ent->ambientLight[0] * (ent->e.shaderRGBA[0]/255.0); + ambient[1] = ent->ambientLight[1] * (ent->e.shaderRGBA[1]/255.0); + ambient[2] = ent->ambientLight[2] * (ent->e.shaderRGBA[2]/255.0); + qglLightfv(0, GL_AMBIENT, ambient); + qglLightfv(0, GL_DIFFUSE, ent->directedLight); + + VectorNormalize(ent->lightDir); + + vec3_t vLight; + vLight[0] = DotProduct( ent->lightDir, ent->e.axis[0] ); + vLight[1] = DotProduct( ent->lightDir, ent->e.axis[1] ); + vLight[2] = DotProduct( ent->lightDir, ent->e.axis[2] ); + + if(VectorLengthSquared(vLight) <= 0.0001f) + { + vLight[0] = 0.0f; + vLight[1] = 1.0f; + vLight[2] = 0.0f; + } + + qglLightfv(0, GL_SPOT_DIRECTION, vLight); + + DWORD color = D3DCOLOR_RGBA(backEnd.currentEntity->e.shaderRGBA[0], + backEnd.currentEntity->e.shaderRGBA[1], + backEnd.currentEntity->e.shaderRGBA[2], + backEnd.currentEntity->e.shaderRGBA[3]); + + for(int i = 0; i < tess.numVertexes; i++) + { + colors[i] = color; + } +} + + +//void R_LoadLevelLightdef(const char *filename) +//{ +// const char *text; +// const char *curText; +// char *token; +// VVslight_t light; +// +// VVLightMan.num_slights = 0; +// +// if ( ri.FS_ReadFile( filename, (void**)&curText ) <= 0 ) +// { +// ri.Printf( PRINT_WARNING, "WARNING: no lightdef file found\n" ); +// return; +// } +// +// text = curText; +// +// while(1) +// { +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// +// // Skip to the light's origin +// while(strcmp(token, "origin")) +// { +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// } +// +// // Write the origin +// // X +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// light.origin[0] = atof(token); +// +// // Y +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// light.origin[1] = atof(token); +// +// // Z +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// light.origin[2] = atof(token); +// +// // Skip to the light's range +// while(strcmp(token, "light")) +// { +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// } +// +// // Write the light range +// token = COM_ParseExt( &text, qtrue ); +// if(!token[0]) +// break; +// light.radius = atof(token); +// +// // Default color for now +// light.color[0] = 1.0f; +// light.color[1] = 1.0f; +// light.color[2] = 1.0f; +// +// VVLightMan.RE_AddStaticLightToScene(&light); +// } +// +// ri.FS_FreeFile( (void*)curText ); +//} + +#define MAX_LIGHT_TABLE 55 + +static levelLightParm_t _levelLightParms[MAX_LIGHT_TABLE]; +static bool isLightInit = false; + +static void ClearLightParmTable(void) +{ + memset(_levelLightParms, 0, sizeof(levelLightParm_t) * MAX_LIGHT_TABLE); + isLightInit = false; +} + +/* +** +** R_GetLightParmsForLevel +** +*/ +void R_GetLightParmsForLevel() +{ + if(!isLightInit) + return; + + char levelname[64]; + + COM_StripExtension(tr.world->baseName, levelname); + + for(int i = 0; i < MAX_LIGHT_TABLE; i++) + { + if(Q_stricmp(COM_SkipPath(levelname), _levelLightParms[i].levelName) == 0) + { + if(VectorLength(_levelLightParms[i].sundir)) + { + Cvar_SetValue("r_sundir_x", _levelLightParms[i].sundir[0]); + Cvar_SetValue("r_sundir_y", _levelLightParms[i].sundir[1]); + Cvar_SetValue("r_sundir_z", _levelLightParms[i].sundir[2]); + } + + if(_levelLightParms[i].hdrEnable) + Cvar_Set("r_hdreffect", "1"); + else + Cvar_Set("r_hdreffect", "0"); + Cvar_SetValue("r_hdrbloom", _levelLightParms[i].hdrBloom); + Cvar_SetValue("r_hdrcutoff", _levelLightParms[i].hdrCutoff); + } + } +} + +/* +** +** R_LoadLevelFogTable +** +*/ +void R_LoadLevelLightParms() +{ + const char *lightText; + const char *curText; + char *token; + int level = 0; + + if ( FS_ReadFile( "shaders/lightparms.txt", (void**)&curText ) <= 0 ) + { + Com_Printf( "WARNING: no light parms file found\n" ); + return; + } + + ClearLightParmTable(); + + lightText = curText; + + while(1) + { + // Level name + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + strcpy( _levelLightParms[level].levelName, token ); + + // Sun dir X + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].sundir[0] = atof(token); + + // Sun dir Y + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].sundir[1] = atof(token); + + // Sun dir Z + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].sundir[2] = atof(token); + + // HDR enable + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].hdrEnable = atof(token); + + // HDR bloom + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].hdrBloom = atof(token); + + // HDR cutoff + token = COM_ParseExt( &lightText, qtrue ); + if(!token[0]) + break; + _levelLightParms[level].hdrCutoff = atof(token); + + level++; + if(level >= MAX_LIGHT_TABLE) + break; + } + + isLightInit = true; + + FS_FreeFile( (void*)curText ); +} + +#endif //VV_LIGHTING \ No newline at end of file diff --git a/code/renderer/tr_lightmanager.h b/code/renderer/tr_lightmanager.h new file mode 100644 index 0000000..ce865e6 --- /dev/null +++ b/code/renderer/tr_lightmanager.h @@ -0,0 +1,70 @@ +/* +** tr_lightmanager.h +*/ + +#ifndef TR_LIGHTMANAGER_H +#define TR_LIGHTMANAGER_H + +#include "tr_local.h" + +#define MAX_NUM_STATIC_LIGHTS 256 + +enum VVlight_type { + LT_DIRECTIONAL, + LT_POINT, + LT_SPOT +}; + +typedef struct VVdlight_s { + VVlight_type type; + vec3_t origin; + vec3_t direction; + vec3_t color; + vec3_t transformed; + float radius; + float attenuation; +} VVdlight_t; + +typedef struct VVslight_s { + vec3_t origin; + vec3_t color; + float radius; +} VVslight_t; + + +class VVLightManager { +public: + + int num_dlights; + int num_slights; + VVdlight_t dlights[MAX_DLIGHTS]; + /*VVslight_t slights[MAX_NUM_STATIC_LIGHTS]; + unsigned char slightBits[MAX_NUM_STATIC_LIGHTS];*/ + int currentlight; + + VVLightManager(); + void R_TransformDlights( orientationr_t *orient); + void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); + void RE_AddLightToScene( VVdlight_t *light ); + //void RE_AddStaticLightToScene( VVslight_t *light ); + void R_DlightBmodel( bmodel_t *bmodel, qboolean NoLight ); + int R_DlightFace( srfSurfaceFace_t *face, int dlightBits ); + //void R_SlightFace( srfSurfaceFace_t *face ); + int R_DlightGrid( srfGridMesh_t *grid, int dlightBits ); + //void R_SlightGrid( srfGridMesh_t *grid ); + int R_DlightTrisurf( srfTriangles_t *surf, int dlightBits ); + //void R_SlightTrisurf( srfTriangles_t *surf ); + int R_DlightSurface( msurface_t *surf, int dlightBits ); + void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ); + void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ); + void RB_CalcDiffuseColorWorld(); + void RB_CalcDiffuseColor( DWORD *colors ); + void RB_CalcDiffuseEntityColor( DWORD *colors ); + void ShortToVec3(const short in[3], vec3_t &out); + int BoxOnPlaneSide (const short emins[3], const short emaxs[3], struct cplane_s *p); +}; + +extern VVLightManager VVLightMan; + + +#endif \ No newline at end of file diff --git a/code/renderer/tr_local.h b/code/renderer/tr_local.h new file mode 100644 index 0000000..3aca7c9 --- /dev/null +++ b/code/renderer/tr_local.h @@ -0,0 +1,2188 @@ +#ifndef TR_LOCAL_H +#define TR_LOCAL_H + + +#include "../game/q_shared.h" +#include "../qcommon/qfiles.h" +#include "tr_public.h" +#include "mdx_format.h" +#if defined(_XBOX) +#include "qgl_console.h" +#include "glext_console.h" +#else +#include "qgl.h" +#include "glext.h" +#endif + +#ifdef _XBOX +#define GL_INDEX_TYPE GL_UNSIGNED_SHORT +typedef unsigned short glIndex_t; +#else +#define GL_INDEX_TYPE GL_UNSIGNED_INT +typedef unsigned int glIndex_t; +#endif + +// fast float to int conversion +#if id386 && !(defined __linux__ && defined __i386__) +long myftol( float f ); +#else +#define myftol(x) ((int)(x)) +#endif + + +// 14 bits +// see QSORT_SHADERNUM_SHIFT +#ifdef _XBOX +#define MAX_SHADERS 4096 +#else +#define MAX_SHADERS 8192 +#endif +// can't be increased without changing bit packing for drawsurfs + + +typedef struct dlight_s { + vec3_t origin; + vec3_t color; // range from 0.0 to 1.0, should be color normalized + float radius; + + vec3_t transformed; // origin in local coordinate system +} dlight_t; + + +// a trRefEntity_t has all the information passed in by +// the client game, as well as some locally derived info +typedef struct { + refEntity_t e; + +// float axisLength; // compensate for non-normalized axis + + qboolean needDlights; // true for bmodels that touch a dlight + qboolean lightingCalculated; + vec3_t lightDir; // normalized direction towards light + vec3_t ambientLight; // color normalized to 0-255 + int ambientLightInt; // 32 bit rgba packed + vec3_t directedLight; + int dlightBits; +#ifdef _XBOX + vec3_t shadowDir; + int visible; +#endif +} trRefEntity_t; + +#ifdef _XBOX +extern char entityVisList[MAX_GENTITIES + 1000 + 256];//MAX_MISC_ENTS + MAX_FLARES]; +void RB_RunVisTest(int number, vec3_t bounds[2]); +#endif + +// trRefdef_t holds everything that comes in refdef_t, +// as well as the locally generated scene information +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + int time; // time in milliseconds for shader effects and other time dependent rendering issues + int frametime; + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + qboolean areamaskModified; // qtrue if areamask changed since last scene + + float floatTime; // tr.refdef.time / 1000.0 + + // text messages for deform text shaders +// char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; + + int num_entities; + trRefEntity_t *entities; + +#ifndef VV_LIGHTING + int num_dlights; + struct dlight_s *dlights; +#endif + + int numPolys; + struct srfPoly_s *polys; + + int numDrawSurfs; + struct drawSurf_s *drawSurfs; + + int fogIndex; //what fog brush the vieworg is in + +} trRefdef_t; + +typedef struct { + vec3_t origin; // in world coordinates + vec3_t axis[3]; // orientation in world + vec3_t viewOrigin; // viewParms->or.origin in local coordinates + float modelMatrix[16]; +} orientationr_t; + +typedef struct image_s { + int imgCode; + +#ifndef FINAL_BUILD + char imgName[MAX_QPATH]; // Only in debug, so imagelist cmd is useful +#endif + + USHORT width, height; // source image + + GLuint texnum; // gl texture binding + int internalFormat; + + bool isLightmap; + bool isSystem; + short mipcount; + +// bool allowPicmip; + short iLastLevelUsedOn; +} image_t; + + +//=============================================================================== + +typedef enum { + SS_BAD, + SS_PORTAL, // mirrors, portals, viewscreens + SS_ENVIRONMENT, // sky box + SS_OPAQUE, // opaque + + SS_DECAL, // scorch marks, etc. + SS_SEE_THROUGH, // ladders, grates, grills that may have small blended edges + // in addition to alpha test + SS_BANNER, + + SS_INSIDE, // inside body parts (i.e. heart) + SS_MID_INSIDE, + SS_MIDDLE, + SS_MID_OUTSIDE, + SS_OUTSIDE, // outside body parts (i.e. ribs) + + SS_FOG, + + SS_UNDERWATER, // for items that should be drawn in front of the water plane + + SS_BLEND0, // regular transparency and filters + SS_BLEND1, // generally only used for additive type effects + SS_BLEND2, + SS_BLEND3, + + SS_BLEND6, + SS_STENCIL_SHADOW, + SS_ALMOST_NEAREST, // gun smoke puffs + + SS_NEAREST // blood blobs +} shaderSort_t; + + +#define MAX_SHADER_STAGES 8 + +typedef enum { + GF_NONE, + + GF_SIN, + GF_SQUARE, + GF_TRIANGLE, + GF_SAWTOOTH, + GF_INVERSE_SAWTOOTH, + + GF_NOISE, + GF_RAND, + +} genFunc_t; + + +typedef enum { + DEFORM_NONE, + DEFORM_WAVE, + DEFORM_NORMALS, + DEFORM_BULGE, + DEFORM_MOVE, + DEFORM_PROJECTION_SHADOW, + DEFORM_AUTOSPRITE, + DEFORM_AUTOSPRITE2, + DEFORM_TEXT0, + DEFORM_TEXT1, + DEFORM_TEXT2, + DEFORM_TEXT3, + DEFORM_TEXT4, + DEFORM_TEXT5, + DEFORM_TEXT6, + DEFORM_TEXT7 +} deform_t; + +typedef enum { + AGEN_IDENTITY, + AGEN_SKIP, + AGEN_ENTITY, + AGEN_ONE_MINUS_ENTITY, + AGEN_VERTEX, + AGEN_ONE_MINUS_VERTEX, + AGEN_LIGHTING_SPECULAR, + AGEN_WAVEFORM, + AGEN_PORTAL, + AGEN_BLEND, + AGEN_CONST, + AGEN_DOT, + AGEN_ONE_MINUS_DOT +} alphaGen_t; + +typedef enum { + CGEN_BAD, + CGEN_IDENTITY_LIGHTING, // tr.identityLight + CGEN_IDENTITY, // always (1,1,1,1) + CGEN_SKIP, + CGEN_ENTITY, // grabbed from entity's modulate field + CGEN_ONE_MINUS_ENTITY, // grabbed from 1 - entity.modulate + CGEN_EXACT_VERTEX, // tess.vertexColors + CGEN_VERTEX, // tess.vertexColors * tr.identityLight + CGEN_ONE_MINUS_VERTEX, + CGEN_WAVEFORM, // programmatically generated + CGEN_LIGHTING_DIFFUSE, + CGEN_LIGHTING_DIFFUSE_ENTITY, //diffuse lighting * entity + CGEN_FOG, // standard fog + CGEN_CONST, // fixed color + CGEN_LIGHTMAPSTYLE, +} colorGen_t; + +typedef enum { + TCGEN_BAD, + TCGEN_IDENTITY, // clear to 0,0 + TCGEN_LIGHTMAP, + TCGEN_LIGHTMAP1, + TCGEN_LIGHTMAP2, + TCGEN_LIGHTMAP3, + TCGEN_TEXTURE, + TCGEN_ENVIRONMENT_MAPPED, + TCGEN_FOG, + TCGEN_VECTOR // S and T from world coordinates +} texCoordGen_t; + +typedef enum { + ACFF_NONE, + ACFF_MODULATE_RGB, + ACFF_MODULATE_RGBA, + ACFF_MODULATE_ALPHA +} acff_t; + +typedef enum { + GLFOGOVERRIDE_NONE = 0, + GLFOGOVERRIDE_BLACK, + GLFOGOVERRIDE_WHITE, + + GLFOGOVERRIDE_MAX +} EGLFogOverride; + +typedef struct { + genFunc_t func; + + float base; + float amplitude; + float phase; + float frequency; +} waveForm_t; + +#define TR_MAX_TEXMODS 4 + +typedef enum { + TMOD_NONE, + TMOD_TRANSFORM, + TMOD_TURBULENT, + TMOD_SCROLL, + TMOD_SCALE, + TMOD_STRETCH, + TMOD_ROTATE, + TMOD_ENTITY_TRANSLATE +} texMod_t; + +#define MAX_SHADER_DEFORMS 3 +typedef struct { + deform_t deformation; // vertex coordinate modification type + + vec3_t moveVector; + waveForm_t deformationWave; + float deformationSpread; + + float bulgeWidth; + float bulgeHeight; + float bulgeSpeed; +} deformStage_t; + + +typedef struct { + texMod_t type; + + // used for TMOD_TURBULENT and TMOD_STRETCH + waveForm_t wave; + + // used for TMOD_TRANSFORM + float matrix[2][2]; // s' = s * m[0][0] + t * m[1][0] + trans[0] + float translate[2]; // t' = s * m[0][1] + t * m[0][1] + trans[1] + + // used for TMOD_SCALE +// float scale[2]; // s *= scale[0] + // t *= scale[1] + + // used for TMOD_SCROLL +// float scroll[2]; // s' = s + scroll[0] * time + // t' = t + scroll[1] * time + + // used for TMOD_ROTATE + // + = clockwise + // - = counterclockwise + //float rotateSpeed; + +} texModInfo_t; + + +#define SURFSPRITE_NONE 0 +#define SURFSPRITE_VERTICAL 1 +#define SURFSPRITE_ORIENTED 2 +#define SURFSPRITE_EFFECT 3 +#define SURFSPRITE_WEATHERFX 4 +#define SURFSPRITE_FLATTENED 5 + +#define SURFSPRITE_FACING_NORMAL 0 +#define SURFSPRITE_FACING_UP 1 +#define SURFSPRITE_FACING_DOWN 2 +#define SURFSPRITE_FACING_ANY 3 + + +typedef struct surfaceSprite_s +{ + int surfaceSpriteType; + float width, height, density, wind, windIdle, fadeDist, fadeMax, fadeScale; + float fxAlphaStart, fxAlphaEnd, fxDuration, vertSkew; + vec2_t variance, fxGrow; + int facing; // Hangdown on vertical sprites, faceup on others. +} surfaceSprite_t; + +typedef struct { + image_t *image; + + vec3_t *tcGenVectors; + + texModInfo_t *texMods; + short numTexMods; + short numImageAnimations; + float imageAnimationSpeed; + +// texCoordGen_t tcGen; + byte tcGen; + byte isLightmap; + byte oneShotAnimMap; + byte vertexLightmap; + byte isVideoMap; + + char videoMapHandle; +} textureBundle_t; + + +#define NUM_TEXTURE_BUNDLES 2 + +typedef struct { + byte active; +// byte isDetail; + byte isEnvironment; + byte isSpecular; + byte isBumpMap; + + byte index; // index of stage + byte lightmapStyle; + byte alphaGen; + byte rgbGen; + + byte adjustColorsForFog; + byte mGLFogColorOverride; + + // Whether this object emits a glow or not. + byte glow; + + textureBundle_t bundle[NUM_TEXTURE_BUNDLES]; + + waveForm_t rgbWave; + waveForm_t alphaWave; + + byte constantColor[4]; // for CGEN_CONST and AGEN_CONST + + unsigned int stateBits; // GLS_xxxx mask + + surfaceSprite_t *ss; + +} shaderStage_t; + +struct shaderCommands_s; + +#define LIGHTMAP_2D -4 // shader is for 2D rendering +#define LIGHTMAP_BY_VERTEX -3 // pre-lit triangle models +#define LIGHTMAP_WHITEIMAGE -2 +#define LIGHTMAP_NONE -1 + +typedef enum { + CT_FRONT_SIDED, + CT_BACK_SIDED, + CT_TWO_SIDED +} cullType_t; + +typedef enum { + FP_NONE, // surface is translucent and will just be adjusted properly + FP_EQUAL, // surface is opaque but possibly alpha tested + FP_LE // surface is trnaslucent, but still needs a fog pass (fog surface) +} fogPass_t; + +typedef struct { + float cloudHeight; +// image_t *outerbox[6], *innerbox[6]; + image_t *outerbox[6]; +} skyParms_t; + +typedef struct { + vec3_t color; + float depthForOpaque; +} fogParms_t; + + +typedef struct shader_s { + char name[MAX_QPATH]; // game path, including extension + short lightmapIndex[MAXLIGHTMAPS]; // for a shader to match, both name and lightmapIndex must match + byte styles[MAXLIGHTMAPS]; + + short index; // this shader == tr.shaders[index] + short sortedIndex; // this shader == tr.sortedShaders[sortedIndex] + + float sort; // lower numbered shaders draw before higher numbered + + int surfaceFlags; // if explicitlyDefined, this will have SURF_* flags + int contentFlags; + + char defaultShader; // we want to return index 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + char explicitlyDefined; // found in a .shader file + char entityMergable; // merge across entites optimizable (smoke, blood) + + char isBumpMap; + + skyParms_t *sky; + fogParms_t *fogParms; + + float portalRange; // distance to fog out at + + int multitextureEnv; // 0, GL_MODULATE, GL_ADD (FIXME: put in stage) + + cullType_t cullType; // CT_FRONT_SIDED, CT_BACK_SIDED, or CT_TWO_SIDED + char polygonOffset; // set for decals and other items that must be offset + char noMipMaps; // for console fonts, 2D elements, etc. +// bool noPicMip; // for images that must always be full resolution +// bool noTC; // for images that don't want to be texture compressed (eg skies) +#ifdef _XBOX + char needsNormal; + char needsTangent; +#endif + + + fogPass_t fogPass; // draw a blended pass, possibly with depth test equals + + deformStage_t *deforms[MAX_SHADER_DEFORMS]; + short numDeforms; + + short numUnfoggedPasses; + shaderStage_t *stages; + + float timeOffset; // current time offset for this shader + +//#ifndef _XBOX // GLOWXXX + // True if this shader has a stage with glow in it (just an optimization). + bool hasGlow; +//#endif + +// struct shader_s *remappedShader; // current shader this one is remapped too + struct shader_s *next; +} shader_t; + + +/* +Ghoul2 Insert Start +*/ + // bogus little registration system for hit and location based damage files in hunk memory +/* +typedef struct +{ + byte *loc; + int width; + int height; + char name[MAX_QPATH]; +} hitMatReg_t; + +#define MAX_HITMAT_ENTRIES 1000 + +extern hitMatReg_t hitMatReg[MAX_HITMAT_ENTRIES]; +*/ +/* +Ghoul2 Insert End +*/ + +//================================================================================= + +// skins allow models to be retextured without modifying the model file +typedef struct { + char name[MAX_QPATH]; + shader_t *shader; +} skinSurface_t; + +typedef struct skin_s { + char name[MAX_QPATH]; // game path, including extension + int numSurfaces; + skinSurface_t *surfaces[128]; +} skin_t; + + +typedef struct { + int originalBrushNumber; + vec3_t bounds[2]; + + unsigned colorInt; // in packed byte format + float tcScale; // texture coordinate vector scales + fogParms_t parms; + + // for clipping distance in fog when outside + qboolean hasSurface; + float surface[4]; +} fog_t; + +typedef struct { + orientationr_t or; + orientationr_t world; + vec3_t pvsOrigin; // may be different than or.origin for portals + qboolean isPortal; // true if this view is through a portal + qboolean isMirror; // the portal is a mirror, invert the face culling + int frameSceneNum; // copied from tr.frameSceneNum + int frameCount; // copied from tr.frameCount + cplane_t portalPlane; // clip anything behind this if mirroring + int viewportX, viewportY, viewportWidth, viewportHeight; + float fovX, fovY; + float projectionMatrix[16]; + cplane_t frustum[5]; + vec3_t visBounds[2]; + float zFar; +} viewParms_t; + + +/* +============================================================================== + +SURFACES + +============================================================================== +*/ + +// any changes in surfaceType must be mirrored in rb_surfaceTable[] +typedef enum { + SF_BAD, + SF_SKIP, // ignore + SF_FACE, + SF_GRID, + SF_TRIANGLES, + SF_POLY, + SF_TERRAIN, + SF_MD3, +/* +Ghoul2 Insert Start +*/ + SF_MDX, +/* +Ghoul2 Insert End +*/ + SF_FLARE, + SF_ENTITY, // beams, rails, lightning, etc that can be determined by entity + SF_DISPLAY_LIST, + + SF_NUM_SURFACE_TYPES, + SF_MAX = 0xffffffff // ensures that sizeof( surfaceType_t ) == sizeof( int ) +} surfaceType_t; + +typedef struct drawSurf_s { + unsigned sort; // bit combination for fast compares + surfaceType_t *surface; // any of surface*_t +} drawSurf_t; + +#define MAX_FACE_POINTS 64 + +#define MAX_PATCH_SIZE 32 // max dimensions of a patch mesh in map file +#define MAX_GRID_SIZE 65 // max dimensions of a grid mesh in memory + +// when cgame directly specifies a polygon, it becomes a srfPoly_t +// as soon as it is called +typedef struct srfPoly_s { + surfaceType_t surfaceType; + qhandle_t hShader; + int fogIndex; + int numVerts; + polyVert_t *verts; +} srfPoly_t; + +typedef struct srfDisplayList_s { + surfaceType_t surfaceType; + int listNum; +} srfDisplayList_t; + +#ifdef _XBOX +typedef struct srfFlare_s { + surfaceType_t surfaceType; + unsigned short origin[3]; + unsigned short normal[3]; + byte color[3]; + byte number; + int visible; +} srfFlare_t; + +#else // _XBOX + +typedef struct srfFlare_s { + surfaceType_t surfaceType; + vec3_t origin; + vec3_t normal; + vec3_t color; +} srfFlare_t; + +#endif + +typedef struct srfGridMesh_s { + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits; + + // culling information + vec3_t meshBounds[2]; + vec3_t localOrigin; + float meshRadius; + + // lod information, which may be different + // than the culling information to allow for + // groups of curves that LOD as a unit + vec3_t lodOrigin; + float lodRadius; + + // vertexes + int width, height; + float *widthLodError; + float *heightLodError; + drawVert_t verts[1]; // variable sized +} srfGridMesh_t; + + + +#ifdef _XBOX +// Added tangent size in here +#define VERTEXSIZE (9+(MAXLIGHTMAPS*3)) +#define VERTEX_LM 8 +#define VERTEX_COLOR(flags) (VERTEX_LM + (((flags) & 0x7F) * 2)) +#else +#define VERTEXSIZE (6+(MAXLIGHTMAPS*3)) +#define VERTEX_LM 5 +#define VERTEX_COLOR (5+(MAXLIGHTMAPS*2)) +#endif + + +#define VERTEX_FINAL_COLOR (5+(MAXLIGHTMAPS*3)) + + + +#ifdef _XBOX + +#ifdef COMPRESS_VERTEX_COLORS +#define NEXT_SURFPOINT(flags) (VERTEX_LM + (((flags) & 0x7F) * 2) + ((((flags) & 0x80) >> 7) * 4)); +#else +#define NEXT_SURFPOINT(flags) (VERTEX_LM + (((flags) & 0x7F) * 2) + ((((flags) & 0x80) >> 7) * 8)); +#endif + +#define POINTS_ST_SCALE 128.0f +#define POINTS_LIGHT_SCALE 65536.0f +#define GLM_COMP_SIZE 64.0f +#define GLM_COMP_UV_SIZE 16384.0f + +#pragma pack (push, 1) +typedef struct { + surfaceType_t surfaceType; + cplane_t plane; + + // dynamic lighting information + int dlightBits; + + // triangle definitions (no normals at points) + unsigned char numPoints; + unsigned short numIndices; + unsigned short ofsIndices; + unsigned char flags; //highest bit - true if face uses vertex colors, + //low 7 bits - number of light maps + unsigned short *srfPoints; // variable sized + // there is a variable length list of indices here also +} srfSurfaceFace_t; +#pragma pack (pop) + +#else // _XBOX + +typedef struct { + surfaceType_t surfaceType; + cplane_t plane; + + // dynamic lighting information + int dlightBits; + + // triangle definitions (no normals at points) + int numPoints; + int numIndices; + int ofsIndices; + float points[1][VERTEXSIZE]; // variable sized + // there is a variable length list of indices here also +} srfSurfaceFace_t; +#endif // _XBOX + + +// misc_models in maps are turned into direct geometry by q3map +typedef struct { + surfaceType_t surfaceType; + + // dynamic lighting information + int dlightBits; + + // culling information (FIXME: use this!) + vec3_t bounds[2]; +// vec3_t localOrigin; +// float radius; + + // triangle definitions + int numIndexes; + int *indexes; + + int numVerts; + drawVert_t *verts; +} srfTriangles_t; + + +extern void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])(void *); + +/* +============================================================================== + +BRUSH MODELS + +============================================================================== +*/ + + +// +// in memory representation +// + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + +typedef struct msurface_s { + int viewCount; // if == tr.viewCount, already added + struct shader_s *shader; + int fogIndex; + + surfaceType_t *data; // any of srf*_t +} msurface_t; + + + +#define CONTENTS_NODE -1 + +#ifdef _XBOX +#define CONTENTS_NODE -1 +#pragma pack (push, 1) +typedef struct mnode_s { + // common with leaf and node + signed char contents; // -1 for nodes, to differentiate from leafs + int visframe; // node needs to be traversed if current + short mins[3], maxs[3]; // for bounding box culling + struct mnode_s *parent; + + // node specific + unsigned int planeNum; + struct mnode_s *children[2]; + +} mnode_t; + +struct mleaf_s { + // common with leaf and node + signed char contents; // -1 for nodes, to differentiate from leafs + int visframe; // node needs to be traversed if current + short mins[3], maxs[3]; // for bounding box culling + struct mnode_s *parent; + + // leaf specific + short cluster; + signed char area; + + unsigned short firstMarkSurfNum; + short nummarksurfaces; +}; +#pragma pack (pop) + +#else // _XBOX + +typedef struct mnode_s { + // common with leaf and node + int contents; // -1 for nodes, to differentiate from leafs + int visframe; // node needs to be traversed if current + vec3_t mins, maxs; // for bounding box culling + struct mnode_s *parent; + + // node specific + cplane_t *plane; + struct mnode_s *children[2]; + + // leaf specific + int cluster; + int area; + + msurface_t **firstmarksurface; + int nummarksurfaces; +} mnode_t; + +#endif // _XBOX + +typedef struct { + vec3_t bounds[2]; // for culling + msurface_t *firstSurface; + int numSurfaces; +} bmodel_t; + +#ifdef _XBOX +#pragma pack(push, 1) +typedef struct { + byte flags; + byte latLong[2]; + int data; + + /* + flags has the following bits: + 0 - ambientLight[0] and directLight[0] are not all zeros + 1 - ambientLight[1] and directLight[1] are not all zeros + 2 - ambientLight[2] and directLight[2] are not all zeros + 3 - ambientLight[3] and directLight[3] are not all zeros + 4 - styles[0] is not LS_NONE + 5 - styles[1] is not LS_NONE + 6 - styles[2] is not LS_NONE + 7 - styles[3] is not LS_NONE + + data points to memory which stores ambientLight, directLight and + styles when they are not 0 or LS_NONE. + */ +} mgrid_t; +#pragma pack(pop) + +#else // _XBOX + +typedef struct { + byte ambientLight[MAXLIGHTMAPS][3]; + byte directLight[MAXLIGHTMAPS][3]; + byte styles[MAXLIGHTMAPS]; + byte latLong[2]; +} mgrid_t; + +#endif // _XBOX + +#ifdef _XBOX +template +class SPARC; +#endif +typedef struct { +#ifdef _XBOX + char name[MAX_QPATH]; // ie: maps/tim_dm2.bsp + char baseName[MAX_QPATH]; // ie: tim_dm2 +#endif + + int numShaders; + dshader_t *shaders; + + bmodel_t *bmodels; + + int numplanes; + cplane_t *planes; + + int numnodes; // includes leafs + int numDecisionNodes; + mnode_t *nodes; +#ifdef _XBOX + int numleafs; + mleaf_s *leafs; +#endif + + int numsurfaces; + msurface_t *surfaces; + + int nummarksurfaces; + msurface_t **marksurfaces; + + int numfogs; + fog_t *fogs; + int globalFog; + + int startLightMapIndex; + + vec3_t lightGridOrigin; + vec3_t lightGridSize; + vec3_t lightGridInverseSize; + int lightGridBounds[3]; + mgrid_t *lightGridData; + unsigned short *lightGridArray; + int numGridArrayElements; + + int numClusters; + int clusterBytes; + +#ifdef _XBOX + SPARC *vis; +#else + const byte *vis; // may be passed in by CM_LoadMap to save space +#endif + + byte *novis; // clusterBytes of 0xff +#ifdef _XBOX + qboolean portalPresent; +#endif +} world_t; + +//====================================================================== + +typedef enum { + MOD_BAD, + MOD_BRUSH, + MOD_MESH, +/* +Ghoul2 Insert Start +*/ + MOD_MDXM, + MOD_MDXA +/* +Ghoul2 Insert End +*/ + +} modtype_t; + +typedef struct model_s { + char name[MAX_QPATH]; + modtype_t type; + int index; // model = tr.models[model->mod_index] + + int dataSize; // just for listing purposes + bmodel_t *bmodel; // only if type == MOD_BRUSH + md3Header_t *md3[MD3_MAX_LODS]; // only if type == MOD_MESH +/* +Ghoul2 Insert Start +*/ + mdxmHeader_t *mdxm; // only if type == MOD_GL2M which is a GHOUL II Mesh file NOT a GHOUL II animation file + mdxaHeader_t *mdxa; // only if type == MOD_GL2A which is a GHOUL II Animation file +/* +Ghoul2 Insert End +*/ + unsigned char numLods; + bool bspInstance; // model is a bsp instance +} model_t; + + +#define MAX_MOD_KNOWN 1024 + +void R_ModelInit (void); +model_t *R_GetModelByHandle( qhandle_t hModel ); +void R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ); +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ); + +void R_Modellist_f (void); + +//==================================================== +#define MAX_DRAWIMAGES 2048 +#define MAX_LIGHTMAPS 256 +#define MAX_SKINS 512 //1024 + + +#ifdef _XBOX +#define MAX_DRAWSURFS 0x4000 +#else +#define MAX_DRAWSURFS 0x10000 +#endif +#define DRAWSURF_MASK (MAX_DRAWSURFS-1) + +/* + +the drawsurf sort data is packed into a single 32 bit value so it can be +compared quickly during the qsorting process + +the bits are allocated as follows: + +22 - 31 : sorted shader index +12 - 21 : entity index +3 - 7 : fog index +2 : used to be clipped flag +0 - 1 : dlightmap index +#define QSORT_SHADERNUM_SHIFT 22 +#define QSORT_ENTITYNUM_SHIFT 12 +#define QSORT_FOGNUM_SHIFT 3 + + TTimo - 1.32 +31 : used for alpha fading models (drawn last) +18-30 : sorted shader index +7-17 : entity index +2-6 : fog index +0-1 : dlightmap index +*/ + +#define QSORT_SHADERNUM_SHIFT 18 +#define QSORT_ENTITYNUM_SHIFT 7 +#define QSORT_FOGNUM_SHIFT 2 + +extern int gl_filter_min, gl_filter_max; + +/* +** performanceCounters_t +*/ +typedef struct { + int c_sphere_cull_patch_in, c_sphere_cull_patch_clip, c_sphere_cull_patch_out; + int c_box_cull_patch_in, c_box_cull_patch_clip, c_box_cull_patch_out; + int c_sphere_cull_md3_in, c_sphere_cull_md3_clip, c_sphere_cull_md3_out; + int c_box_cull_md3_in, c_box_cull_md3_clip, c_box_cull_md3_out; + + int c_leafs; + int c_dlightSurfaces; + int c_dlightSurfacesCulled; +} frontEndCounters_t; + +#define FOG_TABLE_SIZE 256 +#define FUNCTABLE_SIZE 1024 +#define FUNCTABLE_SIZE2 10 +#define FUNCTABLE_MASK (FUNCTABLE_SIZE-1) + + +// the renderer front end should never modify glstate_t +typedef struct { + int currenttextures[2]; + int currenttmu; + qboolean finishCalled; + int texEnv[2]; + int faceCulling; + unsigned long glStateBits; +} glstate_t; + + +typedef struct { + int c_surfaces, c_shaders, c_vertexes, c_indexes, c_totalIndexes; + float c_overDraw; + + int c_dlightVertexes; + int c_dlightIndexes; + + int c_flareAdds; + int c_flareTests; + int c_flareRenders; + + int msec; // total msec for backend run +} backEndCounters_t; + +// all state modified by the back end is seperated +// from the front end state +typedef struct { + trRefdef_t refdef; + viewParms_t viewParms; + orientationr_t ori; + backEndCounters_t pc; + qboolean isHyperspace; + trRefEntity_t *currentEntity; + qboolean skyRenderedThisView; // flag for drawing sun + + qboolean projection2D; // if qtrue, drawstretchpic doesn't need to change modes + byte color2D[4]; + qboolean vertexes2D; // shader needs to be finished + trRefEntity_t entity2D; // currentEntity will point at this when doing 2D rendering +} backEndState_t; + +typedef struct srfTerrain_s +{ + surfaceType_t surfaceType; + class CTRLandScape *landscape; +} srfTerrain_t; + +/* +** trGlobals_t +** +** Most renderer globals are defined here. +** backend functions should never modify any of these fields, +** but may read fields that aren't dynamically modified +** by the frontend. +*/ +#ifdef _XBOX +#define NUM_SCRATCH_IMAGES 1 +#else +#define NUM_SCRATCH_IMAGES 16 +#endif + +#define SCREEN_IMAGE_MAX_WIDTH 512 +#define SCREEN_IMAGE_MAX_HEIGHT 256 + +#define SAVE_GAME_IMAGE_W 256 +#define SAVE_GAME_IMAGE_H 128 + +typedef struct { + qboolean registered; // cleared at shutdown, set at beginRegistration + + int visCount; // incremented every time a new vis cluster is entered + int frameCount; // incremented every frame + int sceneCount; // incremented every scene + int viewCount; // incremented every view (twice a scene if portaled) + // and every R_MarkFragments call + + int frameSceneNum; // zeroed at RE_BeginFrame + + qboolean worldMapLoaded; + world_t *world; + char worldDir[MAX_QPATH]; // ie: maps/tim_dm2 + +#ifdef _XBOX + SPARC *externalVisData; // from RE_SetWorldVisData, shared with CM_Load +#else + const byte *externalVisData; // from RE_SetWorldVisData, shared with CM_Load +#endif + + image_t *defaultImage; + image_t *scratchImage[NUM_SCRATCH_IMAGES]; + image_t *fogImage; +#ifndef _XBOX + image_t *dlightImage; // inverse-quare highlight for projective adding +#endif + image_t *whiteImage; // full of 0xff + image_t *identityLightImage; // full of tr.identityLightByte + + image_t *screenImage; //reserve us a gl texnum to use with RF_DISTORTION + +#ifdef _XBOX + image_t *saveGameImage; +#endif //_XBOX + +#ifndef _XBOX // GLOWXXX + // Handle to the Glow Effect Vertex Shader. - AReis + GLuint glowVShader; + + // Handle to the Glow Effect Pixel Shader. - AReis + GLuint glowPShader; + + // Image the glowing objects are rendered to. - AReis + GLuint screenGlow; + + // A rectangular texture representing the normally rendered scene. + GLuint sceneImage; + + // Image used to downsample and blur scene to. - AReis + GLuint blurImage; +#endif + + shader_t *defaultShader; + shader_t *shadowShader; + shader_t *distortionShader; + shader_t *projectionShadowShader; + + shader_t *sunShader; + + int numLightmaps; + image_t *lightmaps[MAX_LIGHTMAPS]; + + trRefEntity_t *currentEntity; + trRefEntity_t worldEntity; // point currentEntity at this when rendering world + int currentEntityNum; + unsigned shiftedEntityNum; // currentEntityNum << QSORT_ENTITYNUM_SHIFT (possible with high bit set for RF_ALPHA_FADE) + model_t *currentModel; + + viewParms_t viewParms; + + float identityLight; // 1.0 / ( 1 << overbrightBits ) + int identityLightByte; // identityLight * 255 + int overbrightBits; // r_overbrightBits->integer, but set to 0 if no hw gamma + + orientationr_t or; // for current entity + + trRefdef_t refdef; + + int viewCluster; + + vec3_t sunLight; // from the sky shader for this level + vec3_t sunDirection; + vec3_t sunAmbient; // from the sky shader (only used for John's terrain system) + + + frontEndCounters_t pc; + int frontEndMsec; // not in pc due to clearing issue + + // + // put large tables at the end, so most elements will be + // within the +/32K indexed range on risc processors + // + model_t *models[MAX_MOD_KNOWN]; + int numModels; + + world_t bspModels[MAX_SUB_BSP]; + int numBSPModels; + + // shader indexes from other modules will be looked up in tr.shaders[] + // shader indexes from drawsurfs will be looked up in sortedShaders[] + // lower indexed sortedShaders must be rendered first (opaque surfaces before translucent) + int numShaders; + shader_t *shaders[MAX_SHADERS]; + shader_t *sortedShaders[MAX_SHADERS]; + int iNumDeniedShaders; // used for error-messages only + + int numSkins; + skin_t *skins[MAX_SKINS]; + + float sinTable[FUNCTABLE_SIZE]; + + float squareTable[FUNCTABLE_SIZE]; + float triangleTable[FUNCTABLE_SIZE]; + float sawToothTable[FUNCTABLE_SIZE]; + float inverseSawToothTable[FUNCTABLE_SIZE]; + float fogTable[FOG_TABLE_SIZE]; + + float rangedFog; + + float distanceCull; + srfTerrain_t landScape; +} trGlobals_t; + +int R_Images_StartIteration(void); +image_t *R_Images_GetNextIteration(void); +void R_Images_Clear(void); +void R_Images_DeleteLightMaps(void); +void R_Images_DeleteImage(image_t *pImage); + + +extern backEndState_t backEnd; +extern trGlobals_t tr; +extern glconfig_t glConfig; // outside of TR since it shouldn't be cleared during ref re-init +extern glstate_t glState; // outside of TR since it shouldn't be cleared during ref re-init + + +// +// cvars +// +extern cvar_t *r_ignore; // used for debugging anything +extern cvar_t *r_verbose; // used for verbose debug spew + +extern cvar_t *r_znear; // near Z clip plane + +extern cvar_t *r_stencilbits; // number of desired stencil bits +extern cvar_t *r_depthbits; // number of desired depth bits +extern cvar_t *r_colorbits; // number of desired color bits, only relevant for fullscreen +extern cvar_t *r_stereo; // desired pixelformat stereo flag +extern cvar_t *r_texturebits; // number of desired texture bits + // 0 = use framebuffer depth + // 16 = use 16-bit textures + // 32 = use 32-bit textures + // all else = error +extern cvar_t *r_texturebitslm; // number of desired lightmap texture bits + +extern cvar_t *r_measureOverdraw; // enables stencil buffer overdraw measurement + +extern cvar_t *r_lodbias; // push/pull LOD transitions +extern cvar_t *r_lodscale; + +extern cvar_t *r_primitives; // "0" = based on compiled vertex array existance + // "1" = glDrawElemet tristrips + // "2" = glDrawElements triangles + // "-1" = no drawing + +extern cvar_t *r_fastsky; // controls whether sky should be cleared or drawn +extern cvar_t *r_drawSun; // controls drawing of sun quad +extern cvar_t *r_dynamiclight; // dynamic lights enabled/disabled +extern cvar_t *r_dlightBacks; // dlight non-facing surfaces for continuity + +extern cvar_t *r_norefresh; // bypasses the ref rendering +extern cvar_t *r_drawentities; // disable/enable entity rendering +extern cvar_t *r_drawworld; // disable/enable world rendering +extern cvar_t *r_drawfog; // disable/enable fog rendering +extern cvar_t *r_speeds; // various levels of information display +extern cvar_t *r_detailTextures; // enables/disables detail texturing stages + +extern cvar_t *r_novis; // disable/enable usage of PVS +extern cvar_t *r_nocull; +extern cvar_t *r_facePlaneCull; // enables culling of planar surfaces with back side test +extern cvar_t *r_nocurves; +extern cvar_t *r_showcluster; + +extern cvar_t *r_dlightStyle; +extern cvar_t *r_surfaceSprites; +extern cvar_t *r_surfaceWeather; + +extern cvar_t *r_windSpeed; +extern cvar_t *r_windAngle; +extern cvar_t *r_windGust; +extern cvar_t *r_windDampFactor; +extern cvar_t *r_windPointForce; +extern cvar_t *r_windPointX; +extern cvar_t *r_windPointY; + +extern cvar_t *r_mode; // video mode +extern cvar_t *r_fullscreen; +extern cvar_t *r_gamma; +extern cvar_t *r_displayRefresh; // optional display refresh option +extern cvar_t *r_ignorehwgamma; // overrides hardware gamma capabilities + +extern cvar_t *r_allowExtensions; // global enable/disable of OpenGL extensions +extern cvar_t *r_ext_compressed_textures; // these control use of specific extensions +extern cvar_t *r_ext_compressed_lightmaps; // turns on compression of lightmaps, off by default +extern cvar_t *r_ext_preferred_tc_method; +extern cvar_t *r_ext_gamma_control; +extern cvar_t *r_ext_texenv_op; +extern cvar_t *r_ext_multitexture; +extern cvar_t *r_ext_compiled_vertex_array; +extern cvar_t *r_ext_texture_env_add; +extern cvar_t *r_ext_texture_filter_anisotropic; + +extern cvar_t *r_DynamicGlow; +extern cvar_t *r_DynamicGlowPasses; +extern cvar_t *r_DynamicGlowDelta; +extern cvar_t *r_DynamicGlowIntensity; +extern cvar_t *r_DynamicGlowSoft; +extern cvar_t *r_DynamicGlowWidth; +extern cvar_t *r_DynamicGlowHeight; + +extern cvar_t *r_nobind; // turns off binding to appropriate textures +extern cvar_t *r_singleShader; // make most world faces use default shader +extern cvar_t *r_colorMipLevels; // development aid to see texture mip usage +extern cvar_t *r_picmip; // controls picmip values +extern cvar_t *r_finish; +extern cvar_t *r_swapInterval; +extern cvar_t *r_textureMode; +extern cvar_t *r_offsetFactor; +extern cvar_t *r_offsetUnits; + +extern cvar_t *r_fullbright; // avoid lightmap pass +extern cvar_t *r_lightmap; // render lightmaps only +extern cvar_t *r_vertexLight; // vertex lighting mode for better performance + +extern cvar_t *r_logFile; // number of frames to emit GL logs +extern cvar_t *r_showtris; // enables wireframe rendering of the world +extern cvar_t *r_showtriscolor; // changes wireframe color +extern cvar_t *r_showsky; // forces sky in front of all surfaces +extern cvar_t *r_shownormals; // draws wireframe normals +extern cvar_t *r_clear; // force screen clear every frame + +extern cvar_t *r_shadows; // controls shadows: 0 = none, 1 = blur, 2 = stencil, 3 = black planar projection +extern cvar_t *r_flares; // light flares + +extern cvar_t *r_intensity; + +extern cvar_t *r_lockpvs; +extern cvar_t *r_noportals; +extern cvar_t *r_portalOnly; + +extern cvar_t *r_subdivisions; +extern cvar_t *r_lodCurveError; +extern cvar_t *r_skipBackEnd; + +extern cvar_t *r_ignoreGLErrors; + +extern cvar_t *r_overBrightBits; + +extern cvar_t *r_debugSurface; +extern cvar_t *r_simpleMipMaps; + +extern cvar_t *r_showImages; +extern cvar_t *r_debugSort; +extern cvar_t *r_debugStyle; + +#ifdef _XBOX +extern cvar_t *r_hdreffect; +extern cvar_t *r_sundir_x; +extern cvar_t *r_sundir_y; +extern cvar_t *r_sundir_z; +extern cvar_t *r_hdrbloom; +extern cvar_t *r_hdrcutoff; +#endif + +/* +Ghoul2 Insert Start +*/ +extern cvar_t *r_noGhoul2; +/* +Ghoul2 Insert End +*/ +//==================================================================== + +// Point sprite stuff. +extern cvar_t *r_ext_point_parameters; +extern cvar_t *r_ext_nv_point_sprite; + + +float R_NoiseGet4f( float x, float y, float z, float t ); +void R_NoiseInit( void ); + +void R_SwapBuffers( int ); + +void R_RenderView( viewParms_t *parms ); + +void R_AddMD3Surfaces( trRefEntity_t *e ); +void R_AddNullModelSurfaces( trRefEntity_t *e ); +void R_AddBeamSurfaces( trRefEntity_t *e ); +void R_AddRailSurfaces( trRefEntity_t *e, qboolean isUnderwater ); +void R_AddLightningBoltSurfaces( trRefEntity_t *e ); + +void R_AddPolygonSurfaces( void ); + +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap ); + +void R_AddDrawSurf( const surfaceType_t *surface, const shader_t *shader, int fogIndex, int dlightMap ); + + +#define CULL_IN 0 // completely unclipped +#define CULL_CLIP 1 // clipped by one or more planes +#define CULL_OUT 2 // completely outside the clipping planes +void R_LocalNormalToWorld (const vec3_t local, vec3_t world); +void R_LocalPointToWorld (const vec3_t local, vec3_t world); +void R_WorldNormalToEntity (const vec3_t localVec, vec3_t world); +//void R_WorldPointToEntity (const vec3_t localVec, vec3_t world); +int R_CullLocalBox (const vec3_t bounds[2]); +int R_CullPointAndRadius( const vec3_t pt, float radius ); +int R_CullLocalPointAndRadius( const vec3_t pt, float radius ); + +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, orientationr_t *or ); + +#ifdef VV_LIGHTING +void R_SetupEntityLightingGrid( trRefEntity_t *ent ); +void R_AddWorldSurface( msurface_t *surf, int dlightBits, qboolean noViewCount = qfalse ); +#endif + +/* +** GL wrapper/helper functions +*/ +void GL_Bind( image_t *image ); +void GL_SetDefaultState (void); +void GL_SelectTexture( int unit ); +void GL_TextureMode( const char *string ); +void GL_CheckErrors( void ); +void GL_State( unsigned long stateVector ); +void GL_TexEnv( int env ); +void GL_Cull( int cullType ); + +#define GLS_SRCBLEND_ZERO 0x00000001 +#define GLS_SRCBLEND_ONE 0x00000002 +#define GLS_SRCBLEND_DST_COLOR 0x00000003 +#define GLS_SRCBLEND_ONE_MINUS_DST_COLOR 0x00000004 +#define GLS_SRCBLEND_SRC_ALPHA 0x00000005 +#define GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA 0x00000006 +#define GLS_SRCBLEND_DST_ALPHA 0x00000007 +#define GLS_SRCBLEND_ONE_MINUS_DST_ALPHA 0x00000008 +#define GLS_SRCBLEND_ALPHA_SATURATE 0x00000009 +#define GLS_SRCBLEND_BITS 0x0000000f + +#define GLS_DSTBLEND_ZERO 0x00000010 +#define GLS_DSTBLEND_ONE 0x00000020 +#define GLS_DSTBLEND_SRC_COLOR 0x00000030 +#define GLS_DSTBLEND_ONE_MINUS_SRC_COLOR 0x00000040 +#define GLS_DSTBLEND_SRC_ALPHA 0x00000050 +#define GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA 0x00000060 +#define GLS_DSTBLEND_DST_ALPHA 0x00000070 +#define GLS_DSTBLEND_ONE_MINUS_DST_ALPHA 0x00000080 +#define GLS_DSTBLEND_BITS 0x000000f0 + +#define GLS_DEPTHMASK_TRUE 0x00000100 + +#define GLS_POLYMODE_LINE 0x00001000 + +#define GLS_DEPTHTEST_DISABLE 0x00010000 +#define GLS_DEPTHFUNC_EQUAL 0x00020000 + +#define GLS_ATEST_GT_0 0x10000000 +#define GLS_ATEST_LT_80 0x20000000 +#define GLS_ATEST_GE_80 0x40000000 +#define GLS_ATEST_GE_C0 0x80000000 +#define GLS_ATEST_BITS 0xF0000000 + +#define GLS_DEFAULT GLS_DEPTHMASK_TRUE +#define GLS_ALPHA (GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA) + +void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int iClient, qboolean bDirty ); +void RE_UploadCinematic (int cols, int rows, const byte *data, int client, qboolean dirty); +void RE_GetScreenShot(byte *data, int w, int h); +#ifndef _XBOX +byte* RE_TempRawImage_ReadFromFile(const char *psLocalFilename, int *piWidth, int *piHeight, byte *pbReSampleBuffer, qboolean qbVertFlip); +#endif +void RE_TempRawImage_CleanUp(); + +void RE_BeginRegistration( glconfig_t *glconfig ); +void RE_LoadWorldMap( const char *mapname ); +#ifdef _XBOX +void RE_SetWorldVisData( SPARC *vis ); +#else +void RE_SetWorldVisData( const byte *vis ); +#endif +qhandle_t RE_RegisterModel( const char *name ); +qhandle_t RE_RegisterSkin( const char *name ); +int RE_GetAnimationCFG(const char *psCFGFilename, char *psDest, int iDestSize); +void RE_Shutdown( qboolean destroyWindow ); + +void RE_RegisterMedia_LevelLoadBegin(const char *psMapName, ForceReload_e eForceReload, qboolean bAllowScreenDissolve); +void RE_RegisterMedia_LevelLoadEnd(void); +int RE_RegisterMedia_GetLevel(void); +// +//void RE_RegisterModels_LevelLoadBegin(const char *psMapName); +qboolean RE_RegisterModels_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel = qfalse ); +void* RE_RegisterModels_Malloc(int iSize, void *pvDiskBufferIfJustLoaded, const char *psModelFileName, qboolean *pqbAlreadyFound, memtag_t eTag); +void RE_RegisterModels_StoreShaderRequest(const char *psModelFileName, const char *psShaderName, const int *piShaderIndexPoke); +void RE_RegisterModels_Info_f(void); +//void RE_RegisterImages_LevelLoadBegin(const char *psMapName); +qboolean RE_RegisterImages_LevelLoadEnd(void); +void RE_RegisterImages_Info_f(void); + + +model_t *R_AllocModel( void ); + +void R_Init( void ); +image_t *R_FindImageFile( const char *name, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ); + +#ifdef _XBOX +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, GLenum format, qboolean mipmap, qboolean allowPicmip, int wrapClampMode); +#else +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, GLenum format, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int wrapClampMode,int fileSize=0); +#endif + +qboolean R_GetModeInfo( int *width, int *height, int mode ); + +void R_SetColorMappings( void ); +void R_GammaCorrect( byte *buffer, int bufSize ); + +void R_ImageList_f( void ); +void R_SkinList_f( void ); +void R_ScreenShot_f( void ); +void R_ScreenShotTGA_f( void ); + +void R_InitFogTable( void ); +float R_FogFactor( float s, float t ); +void R_InitImages( void ); +void R_DeleteTextures( void ); +float R_SumOfUsedImages( qboolean bUseFormat ); +void R_InitSkins( void ); +skin_t *R_GetSkinByHandle( qhandle_t hSkin ); + + +// +// tr_shader.c +// +extern const short lightmapsNone[MAXLIGHTMAPS]; +extern const short lightmaps2d[MAXLIGHTMAPS]; +extern const short lightmapsVertex[MAXLIGHTMAPS]; +extern const short lightmapsFullBright[MAXLIGHTMAPS]; +extern const byte stylesDefault[MAXLIGHTMAPS]; + +qhandle_t RE_RegisterShader( const char *name ); +qhandle_t RE_RegisterShaderNoMip( const char *name ); + +shader_t *R_FindShader( const char *name, const short *lightmapIndex, const byte *styles, qboolean mipRawImage ); +shader_t *R_GetShaderByHandle( qhandle_t hShader ); +void R_InitShaders( void ); +void R_ShaderList_f( void ); + + +/* +==================================================================== + +IMPLEMENTATION SPECIFIC FUNCTIONS + +==================================================================== +*/ + +void GLimp_Init( void ); +void GLimp_Shutdown( void ); +void GLimp_EndFrame( void ); + +void GLimp_LogComment( char *comment ); + +#ifndef _XBOX +void GLimp_SetGamma( unsigned char red[256], + unsigned char green[256], + unsigned char blue[256] ); +#endif + + +#ifdef _XBOX +typedef struct +{ + char levelName[_MAX_PATH]; + vec3_t sundir; + bool hdrEnable; + float hdrBloom; + float hdrCutoff; +} levelLightParm_t; + +void R_GetLightParmsForLevel(); +void R_LoadLevelLightParms(); +#endif + + +/* +==================================================================== + +TESSELATOR/SHADER DECLARATIONS + +==================================================================== +*/ +typedef byte color4ub_t[4]; + +typedef struct stageVars +{ +#ifdef _XBOX + unsigned long colors[SHADER_MAX_VERTEXES]; +#else + color4ub_t colors[SHADER_MAX_VERTEXES]; +#endif + vec2_t texcoords[NUM_TEXTURE_BUNDLES][SHADER_MAX_VERTEXES]; +} stageVars_t; + +#define NUM_TEX_COORDS (MAXLIGHTMAPS+1) + +struct shaderCommands_s +{ + glIndex_t indexes[SHADER_MAX_INDEXES]; + vec4_t xyz[SHADER_MAX_VERTEXES]; + vec4_t normal[SHADER_MAX_VERTEXES]; +#ifdef _XBOX + vec4_t tangent[SHADER_MAX_VERTEXES]; +#endif + vec2_t texCoords[SHADER_MAX_VERTEXES][NUM_TEX_COORDS]; + color4ub_t vertexColors[SHADER_MAX_VERTEXES]; + byte vertexAlphas[SHADER_MAX_VERTEXES][4]; + int vertexDlightBits[SHADER_MAX_VERTEXES]; + +#ifdef _XBOX + DWORD *pXyz; + DWORD *pNormal; + DWORD *pColor; + DWORD *pTex1; + DWORD *pTex2; +#endif + + stageVars_t svars; + + shader_t *shader; + int fogNum; + + int dlightBits; // or together of all vertexDlightBits + + int numIndexes; + int numVertexes; + + // info extracted from current shader + int numPasses; +#ifdef _XBOX + int currentPass; + bool setTangents; +#endif + void (*currentStageIteratorFunc)( void ); + shaderStage_t *xstages; + + int registration; + + qboolean SSInitializedWind; + + //rww - doing a fade, don't compute shader color/alpha overrides + bool fading; +}; + +typedef __declspec(align(16)) shaderCommands_s shaderCommands_t; + +extern shaderCommands_t tess; + +extern color4ub_t styleColors[MAX_LIGHT_STYLES]; +extern bool styleUpdated[MAX_LIGHT_STYLES]; + +void RB_BeginSurface(shader_t *shader, int fogNum ); +void RB_EndSurface(void); +void RB_CheckOverflow( int verts, int indexes ); +#define RB_CHECKOVERFLOW(v,i) if (tess.numVertexes + (v) >= SHADER_MAX_VERTEXES || tess.numIndexes + (i) >= SHADER_MAX_INDEXES ) {RB_CheckOverflow(v,i);} + +void RB_StageIteratorGeneric( void ); +void RB_StageIteratorSky( void ); + +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ); +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ); + +void RB_ShowImages( void ); + +/* +============================================================ + +WORLD MAP + +============================================================ +*/ + +void R_AddBrushModelSurfaces( trRefEntity_t *e ); +void R_AddWorldSurfaces( void ); + + +/* +============================================================ + +FLARES + +============================================================ +*/ + +void R_ClearFlares( void ); + +void RB_AddFlare( void *surface, int fogNum, vec3_t point, vec3_t color, vec3_t normal, float lightScale ); +void RB_AddDlightFlares( void ); +void RB_RenderFlares (void); + +/* +============================================================ + +LIGHTS + +============================================================ +*/ + +void R_DlightBmodel( bmodel_t *bmodel, qboolean NoLight ); +void R_SetupEntityLighting( const trRefdef_t *refdef, trRefEntity_t *ent ); +void R_TransformDlights( int count, dlight_t *dl, orientationr_t *or ); + + +/* +============================================================ + +SHADOWS + +============================================================ +*/ + +void RB_ShadowTessEnd( void ); +void RB_ShadowFinish( void ); +void RB_ProjectionShadowDeform( void ); + +/* +============================================================ + +SKIES + +============================================================ +*/ + +void R_BuildCloudData( shaderCommands_t *shader ); +void R_InitSkyTexCoords( float cloudLayerHeight ); +void R_DrawSkyBox( shaderCommands_t *shader ); +void RB_DrawSun( void ); +void RB_ClipSkyPolygons( shaderCommands_t *shader ); + +/* +============================================================ + +CURVE TESSELATION + +============================================================ +*/ +#ifdef _XBOX +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t* points, + drawVert_t* ctl, float* errorTable ); +#else +srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, + drawVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ); +#endif +/* +Ghoul2 Insert Start +*/ + +float ProjectRadius( float r, vec3_t location ); +/* +Ghoul2 Insert End +*/ + + +/* +============================================================ + +MARKERS, POLYGON PROJECTION ON WORLD POLYGONS + +============================================================ +*/ + +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + +/* +============================================================ + +SCENE GENERATION + +============================================================ +*/ + +void R_ToggleSmpFrame( void ); + +void RE_ClearScene( void ); +void RE_AddRefEntityToScene( const refEntity_t *ent ); +void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void RE_RenderScene( const refdef_t *fd ); + +qboolean RE_GetLighting( const vec3_t origin, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); + +// Only returns a four sided face and normal of the best face to break ( this is for glass right now ) +void RE_GetBModelVerts( int bmodelIndex, vec3_t *verts, vec3_t normal ); + +/* +============================================================= + +ANIMATED MODELS + +============================================================= +*/ + +void R_MakeAnimModel( model_t *model ); +void R_AddAnimSurfaces( trRefEntity_t *ent ); +/* +Ghoul2 Insert Start +*/ +#pragma warning (disable: 4512) //default assignment operator could not be gened +class CBoneCache; + +class CRenderableSurface +{ +public: +#ifdef _G2_GORE + int ident; +#else + const int ident; // ident of this surface - required so the materials renderer knows what sort of surface this refers to +#endif + CBoneCache *boneCache; // pointer to transformed bone list for this surf + mdxmSurface_t *surfaceData; // pointer to surface data loaded into file - only used by client renderer DO NOT USE IN GAME SIDE - if there is a vid restart this will be out of wack on the game +#ifdef _G2_GORE + float *alternateTex; // alternate texture coordinates. + void *goreChain; + + float scale; + float fade; + float impactTime; // this is a number between 0 and 1 that dictates the progression of the bullet impact +#endif + +#ifdef _G2_GORE + CRenderableSurface& operator= ( const CRenderableSurface& src ) + { + ident = src.ident; + boneCache = src.boneCache; + surfaceData = src.surfaceData; + alternateTex = src.alternateTex; + goreChain = src.goreChain; + + return *this; + } +#endif + +CRenderableSurface(): + ident(SF_MDX), + boneCache(0), +#ifdef _G2_GORE + surfaceData(0), + alternateTex(0), + goreChain(0) +#else + surfaceData(0) +#endif + {} + + void Init() + { + boneCache=0; + surfaceData=0; +#ifdef _G2_GORE + ident = SF_MDX; + alternateTex=0; + goreChain=0; +#endif + } +}; + +void R_AddGhoulSurfaces( trRefEntity_t *ent ); +void RB_SurfaceGhoul( CRenderableSurface *surface ); +/* +Ghoul2 Insert End +*/ + + + +/* +============================================================= +============================================================= +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ); +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ); + +void RB_DeformTessGeometry( void ); + +void RB_CalcScaleTexCoords( const float scale[2], float *dstTexCoords ); +void RB_CalcScrollTexCoords( const float scroll[2], float *dstTexCoords ); +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *texCoords ); +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *dstTexCoords ); +void RB_CalcRotateTexCoords( float rotSpeed, float *dstTexCoords ); + +void RB_CalcEnvironmentTexCoords( float *dstTexCoords ); +void RB_CalcFogTexCoords( float *dstTexCoords ); +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *dstTexCoords ); + +#ifdef _XBOX +void RB_CalcWaveColor( const waveForm_t *wf, DWORD *dstColors ); +void RB_CalcColorFromEntity( DWORD *dstColors ); +void RB_CalcColorFromOneMinusEntity( DWORD *dstColors ); +void RB_CalcWaveAlpha( const waveForm_t *wf, DWORD *dstColors ); +void RB_CalcSpecularAlpha( DWORD *alphas ); +void RB_CalcAlphaFromEntity( DWORD *dstColors ); +void RB_CalcAlphaFromOneMinusEntity( DWORD *dstColors ); +void RB_CalcModulateColorsByFog( DWORD *dstColors ); +void RB_CalcModulateAlphasByFog( DWORD *dstColors ); +void RB_CalcModulateRGBAsByFog( DWORD *dstColors ); +#else +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ); +void RB_CalcColorFromEntity( unsigned char *dstColors ); +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ); +void RB_CalcSpecularAlpha( unsigned char *alphas ); +void RB_CalcAlphaFromEntity( unsigned char *dstColors ); +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ); +void RB_CalcModulateColorsByFog( unsigned char *dstColors ); +void RB_CalcModulateAlphasByFog( unsigned char *dstColors ); +void RB_CalcModulateRGBAsByFog( unsigned char *dstColors ); +#endif + +void RB_CalcDiffuseColor( unsigned char *colors ); +void RB_CalcDiffuseEntityColor( unsigned char *colors ); +void RB_CalcDisintegrateColors( unsigned char *colors, colorGen_t rgbGen ); +void RB_CalcDisintegrateVertDeform( void ); +/* +============================================================= + +RENDERER BACK END FUNCTIONS + +============================================================= +*/ + +void RB_RenderThread( void ); +void RB_ExecuteRenderCommands( const void *data ); + +/* +============================================================= + +RENDERER BACK END COMMAND QUEUE + +============================================================= +*/ + +#ifdef _XBOX +#define MAX_RENDER_COMMANDS 0x18000 +#else +#define MAX_RENDER_COMMANDS 0x40000 +#endif + +typedef struct { + byte cmds[MAX_RENDER_COMMANDS]; + int used; +} renderCommandList_t; + +typedef struct { + int commandId; + float color[4]; +} setColorCommand_t; + +typedef struct { + int commandId; + int buffer; +} drawBufferCommand_t; + +typedef struct { + int commandId; + image_t *image; + int width; + int height; + void *data; +} subImageCommand_t; + +typedef struct { + int commandId; +} swapBuffersCommand_t; + +typedef struct { + int commandId; + int buffer; +} endFrameCommand_t; + +typedef struct { + int commandId; + shader_t *shader; + float x, y; + float w, h; + float s1, t1; + float s2, t2; +} stretchPicCommand_t; + +typedef struct { + int commandId; + shader_t *shader; + float x, y; + float w, h; + float s1, t1; + float s2, t2; + float a; +} rotatePicCommand_t; + +typedef struct +{ + int commandId; +} setModeCommand_t; + +typedef struct +{ + int commandId; + float x, y; + float w, h; +} scissorCommand_t; + +typedef struct { + int commandId; + trRefdef_t refdef; + viewParms_t viewParms; + drawSurf_t *drawSurfs; + int numDrawSurfs; +} drawSurfsCommand_t; + +typedef enum { + RC_END_OF_LIST, + RC_SET_COLOR, + RC_STRETCH_PIC, + RC_SCISSOR, + RC_ROTATE_PIC, + RC_ROTATE_PIC2, + RC_DRAW_SURFS, + RC_DRAW_BUFFER, + RC_SWAP_BUFFERS, + RC_WORLD_EFFECTS, +} renderCommand_t; + + +// these are sort of arbitrary limits. +// the limits apply to the sum of all scenes in a frame -- +// the main view, all the 3D icons, etc +#ifdef _XBOX +#define MAX_POLYS 512 +#else +#define MAX_POLYS 2048 +#endif +#define MAX_POLYVERTS ( MAX_POLYS * 4 ) + +// all of the information needed by the back end must be +// contained in a backEndData_t. left over from SMP duplications, +// could optimize to point directly at frontend data instead of copying? +typedef struct { + drawSurf_t drawSurfs[MAX_DRAWSURFS]; +#ifndef VV_LIGHTING + dlight_t dlights[MAX_DLIGHTS]; +#endif + trRefEntity_t entities[MAX_ENTITIES]; + srfPoly_t polys[MAX_POLYS]; + polyVert_t polyVerts[MAX_POLYVERTS]; + renderCommandList_t commands; +} backEndData_t; + +extern backEndData_t *backEndData; + +void *R_GetCommandBuffer( int bytes ); +void RB_ExecuteRenderCommands( const void *data ); + +void R_InitCommandBuffers( void ); +void R_ShutdownCommandBuffers( void ); + +void R_SyncRenderThread( void ); + +void R_AddDrawSurfCmd( drawSurf_t *drawSurfs, int numDrawSurfs ); + +void RE_SetColor( const float *rgba ); +void RE_StretchPic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void RE_RotatePic ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ); +void RE_RotatePic2 ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ); +void RE_RenderWorldEffects(void); +void RE_LAGoggles( void ); +void RE_Scissor ( float x, float y, float w, float h); +void RE_BeginFrame( stereoFrame_t stereoFrame ); +void RE_EndFrame( int *frontEndMsec, int *backEndMsec ); +qboolean RE_ProcessDissolve(void); +qboolean RE_InitDissolve(qboolean bForceCircularExtroWipe); + + +long generateHashValue( const char *fname ); +#ifdef _XBOX +void R_LoadImage( const char *shortname, byte **pic, int *width, int *height, int *mipcount, GLenum *format ); +#else +int R_LoadImage( const char *name, byte **pic, int *width, int *height, GLenum *format ); +#endif +void RE_InsertModelIntoHash(const char *name, model_t *mod); +qboolean R_FogParmsMatch( int fog1, int fog2 ); + +int R_SurfaceImageCount(const image_t*); +/* +Ghoul2 Insert Start +*/ + +// tr_ghoul2.cpp +void Create_Matrix(const float *angle, mdxaBone_t *matrix); +void Multiply_3x4Matrix(mdxaBone_t *out,const mdxaBone_t *in2,const mdxaBone_t *in); +extern qboolean R_LoadMDXM (model_t *mod, void *buffer, const char *name, qboolean &bAlreadyCached ); +extern qboolean R_LoadMDXA (model_t *mod, void *buffer, const char *name, qboolean &bAlreadyCached ); +bool LoadTGAPalletteImage ( const char *name, byte **pic, int *width, int *height); +/* +Ghoul2 Insert End +*/ + +// tr_surfacesprites +void RB_DrawSurfaceSprites( shaderStage_t *stage, shaderCommands_t *input); + +#ifdef _XBOX +struct DDS_PIXELFORMAT +{ + DWORD dwSize; + DWORD dwFlags; + DWORD dwFourCC; + DWORD dwRGBBitCount; + DWORD dwRBitMask; + DWORD dwGBitMask; + DWORD dwBBitMask; + DWORD dwABitMask; +}; + +struct DDS_HEADER +{ + DWORD dwSize; + DWORD dwHeaderFlags; + DWORD dwHeight; + DWORD dwWidth; + DWORD dwPitchOrLinearSize; + DWORD dwDepth; + DWORD dwMipMapCount; + DWORD dwReserved1[11]; + DDS_PIXELFORMAT ddspf; + DWORD dwSurfaceFlags; + DWORD dwCubemapFlags; + DWORD dwReserved2[3]; +}; +#endif + +#endif //TR_LOCAL_H diff --git a/code/renderer/tr_main.cpp b/code/renderer/tr_main.cpp new file mode 100644 index 0000000..deab6fb --- /dev/null +++ b/code/renderer/tr_main.cpp @@ -0,0 +1,1726 @@ +// tr_main.c -- main control flow for each frame + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + +#if !defined(G2_H_INC) + #include "../ghoul2/G2.h" +#endif + +void R_AddTerrainSurfaces(void); + +trGlobals_t tr; + +static float s_flipMatrix[16] = { + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) +#if defined (_XBOX) + 0, 0, 1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +#else + 0, 0, -1, 0, + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 +#endif +}; + +// entities that will have procedurally generated surfaces will just +// point at this for their sorting surface +surfaceType_t entitySurface = SF_ENTITY; + +/* +================= +R_CullLocalBox + +Returns CULL_IN, CULL_CLIP, or CULL_OUT +================= +*/ +int R_CullLocalBox (const vec3_t bounds[2]) { + int i, j; + vec3_t transformed[8]; + float dists[8]; + vec3_t v; + cplane_t *frust; + int anyBack; + int front, back; + + if ( r_nocull->integer==1 ) { + return CULL_CLIP; + } + + // transform into world space + for (i = 0 ; i < 8 ; i++) { + v[0] = bounds[i&1][0]; + v[1] = bounds[(i>>1)&1][1]; + v[2] = bounds[(i>>2)&1][2]; + + VectorCopy( tr.or.origin, transformed[i] ); + VectorMA( transformed[i], v[0], tr.or.axis[0], transformed[i] ); + VectorMA( transformed[i], v[1], tr.or.axis[1], transformed[i] ); + VectorMA( transformed[i], v[2], tr.or.axis[2], transformed[i] ); + } + + // check against frustum planes + anyBack = 0; + for (i = 0 ; i < 5 ; i++) { + frust = &tr.viewParms.frustum[i]; + + front = back = 0; + for (j = 0 ; j < 8 ; j++) { + dists[j] = DotProduct(transformed[j], frust->normal); + if ( dists[j] > frust->dist ) { + front = 1; + if ( back ) { + break; // a point is in front + } + } else { + back = 1; + } + } + if ( !front ) { + // all points were behind one of the planes + return CULL_OUT; + } + anyBack |= back; + } + + if ( !anyBack ) { + return CULL_IN; // completely inside frustum + } + + return CULL_CLIP; // partially clipped +} + +/* +** R_CullLocalPointAndRadius +*/ +int R_CullLocalPointAndRadius( const vec3_t pt, float radius ) +{ + vec3_t transformed; + + R_LocalPointToWorld( pt, transformed ); + + return R_CullPointAndRadius( transformed, radius ); +} + +/* +** R_CullPointAndRadius +*/ +int R_CullPointAndRadius( const vec3_t pt, float radius ) +{ + int i; + float dist; + cplane_t *frust; + qboolean mightBeClipped = qfalse; + + if ( r_nocull->integer==1 ) { + return CULL_CLIP; + } + + // check against frustum planes + for (i = 0 ; i < 5 ; i++) + { + frust = &tr.viewParms.frustum[i]; + + dist = DotProduct( pt, frust->normal) - frust->dist; + if ( dist < -radius ) + { + return CULL_OUT; + } + else if ( dist <= radius ) + { + mightBeClipped = qtrue; + } + } + + if ( mightBeClipped ) + { + return CULL_CLIP; + } + + return CULL_IN; // completely inside frustum +} + + +/* +================= +R_LocalNormalToWorld + +================= +*/ +void R_LocalNormalToWorld (const vec3_t local, vec3_t world) { + world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0]; + world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1]; + world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2]; +} + +/* +================= +R_LocalPointToWorld + +================= +*/ +void R_LocalPointToWorld (const vec3_t local, vec3_t world) { + world[0] = local[0] * tr.or.axis[0][0] + local[1] * tr.or.axis[1][0] + local[2] * tr.or.axis[2][0] + tr.or.origin[0]; + world[1] = local[0] * tr.or.axis[0][1] + local[1] * tr.or.axis[1][1] + local[2] * tr.or.axis[2][1] + tr.or.origin[1]; + world[2] = local[0] * tr.or.axis[0][2] + local[1] * tr.or.axis[1][2] + local[2] * tr.or.axis[2][2] + tr.or.origin[2]; +} + +float preTransEntMatrix[16]; + +void R_InvertMatrix(float *sourcemat, float *destmat) +{ + int i, j, temp=0; + + for (i = 0; i < 3; i++) + { + for (j = 0; j < 3; j++) + { + destmat[j*4 + i] = sourcemat[temp++]; + } + } + for (i = 0; i < 3; i++) + { + temp = i*4; + destmat[temp+3]=0; // destmat[destmat[i][3]=0; + for (j = 0; j < 3; j++) + { + destmat[temp+3]-=destmat[temp+j]*sourcemat[j*4+3]; // dest->matrix[i][3]-=dest->matrix[i][j]*src->matrix[j][3]; + } + } +} + +/* +================= +R_WorldNormalToEntity + +================= +*/ +void R_WorldNormalToEntity (const vec3_t worldvec, vec3_t entvec) +{ + entvec[0] = -worldvec[0] * preTransEntMatrix[0] - worldvec[1] * preTransEntMatrix[4] + worldvec[2] * preTransEntMatrix[8]; + entvec[1] = -worldvec[0] * preTransEntMatrix[1] - worldvec[1] * preTransEntMatrix[5] + worldvec[2] * preTransEntMatrix[9]; + entvec[2] = -worldvec[0] * preTransEntMatrix[2] - worldvec[1] * preTransEntMatrix[6] + worldvec[2] * preTransEntMatrix[10]; +} + +/* +================= +R_WorldPointToEntity + +================= +*/ +/*void R_WorldPointToEntity (vec3_t worldvec, vec3_t entvec) +{ + entvec[0] = worldvec[0] * preTransEntMatrix[0] + worldvec[1] * preTransEntMatrix[4] + worldvec[2] * preTransEntMatrix[8]+preTransEntMatrix[12]; + entvec[1] = worldvec[0] * preTransEntMatrix[1] + worldvec[1] * preTransEntMatrix[5] + worldvec[2] * preTransEntMatrix[9]+preTransEntMatrix[13]; + entvec[2] = worldvec[0] * preTransEntMatrix[2] + worldvec[1] * preTransEntMatrix[6] + worldvec[2] * preTransEntMatrix[10]+preTransEntMatrix[14]; +} +*/ + +/* +================= +R_WorldToLocal + +================= +*/ +void R_WorldToLocal (vec3_t world, vec3_t local) { + local[0] = DotProduct(world, tr.or.axis[0]); + local[1] = DotProduct(world, tr.or.axis[1]); + local[2] = DotProduct(world, tr.or.axis[2]); +} + +/* +========================== +R_TransformModelToClip + +========================== +*/ +void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix, + vec4_t eye, vec4_t dst ) { + int i; + + for ( i = 0 ; i < 4 ; i++ ) { + eye[i] = + src[0] * modelMatrix[ i + 0 * 4 ] + + src[1] * modelMatrix[ i + 1 * 4 ] + + src[2] * modelMatrix[ i + 2 * 4 ] + + 1 * modelMatrix[ i + 3 * 4 ]; + } + + for ( i = 0 ; i < 4 ; i++ ) { + dst[i] = + eye[0] * projectionMatrix[ i + 0 * 4 ] + + eye[1] * projectionMatrix[ i + 1 * 4 ] + + eye[2] * projectionMatrix[ i + 2 * 4 ] + + eye[3] * projectionMatrix[ i + 3 * 4 ]; + } +} + +/* +========================== +R_TransformClipToWindow + +========================== +*/ +void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) { + normalized[0] = clip[0] / clip[3]; + normalized[1] = clip[1] / clip[3]; + normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] ); + + window[0] = 0.5 * ( 1.0 + normalized[0] ) * view->viewportWidth; + window[1] = 0.5 * ( 1.0 + normalized[1] ) * view->viewportHeight; + window[2] = normalized[2]; + + window[0] = (int) ( window[0] + 0.5 ); + window[1] = (int) ( window[1] + 0.5 ); +} + + +/* +========================== +myGlMultMatrix + +========================== +*/ +void myGlMultMatrix( const float *a, const float *b, float *out ) { + int i, j; + + for ( i = 0 ; i < 4 ; i++ ) { + for ( j = 0 ; j < 4 ; j++ ) { + out[ i * 4 + j ] = + a [ i * 4 + 0 ] * b [ 0 * 4 + j ] + + a [ i * 4 + 1 ] * b [ 1 * 4 + j ] + + a [ i * 4 + 2 ] * b [ 2 * 4 + j ] + + a [ i * 4 + 3 ] * b [ 3 * 4 + j ]; + } + } +} + +/* +================= +R_RotateForEntity + +Generates an orientation for an entity and viewParms +Does NOT produce any GL calls +Called by both the front end and the back end +================= +*/ +void R_RotateForEntity( const trRefEntity_t *ent, const viewParms_t *viewParms, + orientationr_t *or ) { +// float glMatrix[16]; + vec3_t delta; + float axisLength; + + if ( ent->e.reType != RT_MODEL ) { + *or = viewParms->world; + return; + } + + VectorCopy( ent->e.origin, or->origin ); + + VectorCopy( ent->e.axis[0], or->axis[0] ); + VectorCopy( ent->e.axis[1], or->axis[1] ); + VectorCopy( ent->e.axis[2], or->axis[2] ); + + preTransEntMatrix[0] = or->axis[0][0]; + preTransEntMatrix[4] = or->axis[1][0]; + preTransEntMatrix[8] = or->axis[2][0]; + preTransEntMatrix[12] = or->origin[0]; + + preTransEntMatrix[1] = or->axis[0][1]; + preTransEntMatrix[5] = or->axis[1][1]; + preTransEntMatrix[9] = or->axis[2][1]; + preTransEntMatrix[13] = or->origin[1]; + + preTransEntMatrix[2] = or->axis[0][2]; + preTransEntMatrix[6] = or->axis[1][2]; + preTransEntMatrix[10] = or->axis[2][2]; + preTransEntMatrix[14] = or->origin[2]; + + preTransEntMatrix[3] = 0; + preTransEntMatrix[7] = 0; + preTransEntMatrix[11] = 0; + preTransEntMatrix[15] = 1; + + myGlMultMatrix( preTransEntMatrix, viewParms->world.modelMatrix, or->modelMatrix ); + + // calculate the viewer origin in the model's space + // needed for fog, specular, and environment mapping + VectorSubtract( viewParms->or.origin, or->origin, delta ); + + // compensate for scale in the axes if necessary + if ( ent->e.nonNormalizedAxes ) { + axisLength = VectorLength( ent->e.axis[0] ); + if ( !axisLength ) { + axisLength = 0; + } else { + axisLength = 1.0 / axisLength; + } + } else { + axisLength = 1.0; + } + + or->viewOrigin[0] = DotProduct( delta, or->axis[0] ) * axisLength; + or->viewOrigin[1] = DotProduct( delta, or->axis[1] ) * axisLength; + or->viewOrigin[2] = DotProduct( delta, or->axis[2] ) * axisLength; +} + +/* +================= +R_RotateForViewer + +Sets up the modelview matrix for a given viewParm +================= +*/ +void R_RotateForViewer (void) +{ + float viewerMatrix[16]; + vec3_t origin; + + memset (&tr.or, 0, sizeof(tr.or)); + tr.or.axis[0][0] = 1; + tr.or.axis[1][1] = 1; + tr.or.axis[2][2] = 1; + VectorCopy (tr.viewParms.or.origin, tr.or.viewOrigin); + + // transform by the camera placement + VectorCopy( tr.viewParms.or.origin, origin ); + + viewerMatrix[0] = tr.viewParms.or.axis[0][0]; + viewerMatrix[4] = tr.viewParms.or.axis[0][1]; + viewerMatrix[8] = tr.viewParms.or.axis[0][2]; + viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8]; + + viewerMatrix[1] = tr.viewParms.or.axis[1][0]; + viewerMatrix[5] = tr.viewParms.or.axis[1][1]; + viewerMatrix[9] = tr.viewParms.or.axis[1][2]; + viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9]; + + viewerMatrix[2] = tr.viewParms.or.axis[2][0]; + viewerMatrix[6] = tr.viewParms.or.axis[2][1]; + viewerMatrix[10] = tr.viewParms.or.axis[2][2]; + viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10]; + + viewerMatrix[3] = 0; + viewerMatrix[7] = 0; + viewerMatrix[11] = 0; + viewerMatrix[15] = 1; + + // convert from our coordinate system (looking down X) + // to OpenGL's coordinate system (looking down -Z) + myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.or.modelMatrix ); + + tr.viewParms.world = tr.or; + +} + +/* +** SetFarClip +*/ +static void SetFarClip( void ) +{ + float farthestCornerDistance = 0; + int i; + + // if not rendering the world (icons, menus, etc) + // set a 2k far clip plane + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + tr.viewParms.zFar = 2048; + return; + } + + // + // set far clipping planes dynamically + // + for ( i = 0; i < 8; i++ ) + { + vec3_t v; + float distance; + + if ( i & 1 ) + { + v[0] = tr.viewParms.visBounds[0][0]; + } + else + { + v[0] = tr.viewParms.visBounds[1][0]; + } + + if ( i & 2 ) + { + v[1] = tr.viewParms.visBounds[0][1]; + } + else + { + v[1] = tr.viewParms.visBounds[1][1]; + } + + if ( i & 4 ) + { + v[2] = tr.viewParms.visBounds[0][2]; + } + else + { + v[2] = tr.viewParms.visBounds[1][2]; + } + + distance = DistanceSquared(tr.viewParms.or.origin, v); + + if ( distance > farthestCornerDistance ) + { + farthestCornerDistance = distance; + } + } + // Bring in the zFar to the distanceCull distance + // The sky renders at zFar so need to move it out a little + // ...and make sure there is a minimum zfar to prevent problems + tr.viewParms.zFar = Com_Clamp(2048.0f, tr.distanceCull * (1.732), sqrtf( farthestCornerDistance )); +} + + +/* +=============== +R_SetupProjection +=============== +*/ +void R_SetupProjection( void ) { + float xmin, xmax, ymin, ymax; + float width, height, depth; + float zNear, zFar; + + // dynamically compute far clip plane distance + SetFarClip(); + + // + // set up projection matrix + // + zNear = r_znear->value; + zFar = tr.viewParms.zFar; + + ymax = zNear * tan( tr.refdef.fov_y * M_PI / 360.0f ); + ymin = -ymax; + + xmax = zNear * tan( tr.refdef.fov_x * M_PI / 360.0f ); + xmin = -xmax; + + width = xmax - xmin; + height = ymax - ymin; + depth = zFar - zNear; + +#if defined (_XBOX) + tr.viewParms.projectionMatrix[0] = 2 * zNear / width; + tr.viewParms.projectionMatrix[4] = 0; + tr.viewParms.projectionMatrix[8] = ( xmax + xmin ) / width; // normally 0 + tr.viewParms.projectionMatrix[12] = 0; + + tr.viewParms.projectionMatrix[1] = 0; + tr.viewParms.projectionMatrix[5] = 2 * zNear / height; + tr.viewParms.projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0 + tr.viewParms.projectionMatrix[13] = 0; + + tr.viewParms.projectionMatrix[2] = 0; + tr.viewParms.projectionMatrix[6] = 0; + tr.viewParms.projectionMatrix[10] = ( zFar + zNear ) / depth; + tr.viewParms.projectionMatrix[14] = -2 * zFar * zNear / depth; + + tr.viewParms.projectionMatrix[3] = 0; + tr.viewParms.projectionMatrix[7] = 0; + tr.viewParms.projectionMatrix[11] = 1; + tr.viewParms.projectionMatrix[15] = 0; +#else + tr.viewParms.projectionMatrix[0] = 2 * zNear / width; + tr.viewParms.projectionMatrix[4] = 0; + tr.viewParms.projectionMatrix[8] = ( xmax + xmin ) / width; // normally 0 + tr.viewParms.projectionMatrix[12] = 0; + + tr.viewParms.projectionMatrix[1] = 0; + tr.viewParms.projectionMatrix[5] = 2 * zNear / height; + tr.viewParms.projectionMatrix[9] = ( ymax + ymin ) / height; // normally 0 + tr.viewParms.projectionMatrix[13] = 0; + + tr.viewParms.projectionMatrix[2] = 0; + tr.viewParms.projectionMatrix[6] = 0; + tr.viewParms.projectionMatrix[10] = -( zFar + zNear ) / depth; + tr.viewParms.projectionMatrix[14] = -2 * zFar * zNear / depth; + + tr.viewParms.projectionMatrix[3] = 0; + tr.viewParms.projectionMatrix[7] = 0; + tr.viewParms.projectionMatrix[11] = -1; + tr.viewParms.projectionMatrix[15] = 0; +#endif +} + +/* +================= +R_SetupFrustum + +Setup that culling frustum planes for the current view +================= +*/ +void R_SetupFrustum (void) { + int i; + float xs, xc; + float ang; + + ang = tr.viewParms.fovX / 180 * M_PI * 0.5; + xs = sin( ang ); + xc = cos( ang ); + + VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[0].normal ); + VectorMA( tr.viewParms.frustum[0].normal, xc, tr.viewParms.or.axis[1], tr.viewParms.frustum[0].normal ); + + VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[1].normal ); + VectorMA( tr.viewParms.frustum[1].normal, -xc, tr.viewParms.or.axis[1], tr.viewParms.frustum[1].normal ); + + ang = tr.viewParms.fovY / 180 * M_PI * 0.5; + xs = sin( ang ); + xc = cos( ang ); + + VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[2].normal ); + VectorMA( tr.viewParms.frustum[2].normal, xc, tr.viewParms.or.axis[2], tr.viewParms.frustum[2].normal ); + + VectorScale( tr.viewParms.or.axis[0], xs, tr.viewParms.frustum[3].normal ); + VectorMA( tr.viewParms.frustum[3].normal, -xc, tr.viewParms.or.axis[2], tr.viewParms.frustum[3].normal ); + + + // this is the far plane + VectorScale( tr.viewParms.or.axis[0],-1.0f, tr.viewParms.frustum[4].normal ); + + for (i=0 ; i<5 ; i++) { + tr.viewParms.frustum[i].type = PLANE_NON_AXIAL; + tr.viewParms.frustum[i].dist = DotProduct (tr.viewParms.or.origin, tr.viewParms.frustum[i].normal); + if (i==4) + { + // far plane does not go through the view point, it goes alot farther.. + tr.viewParms.frustum[i].dist -= tr.distanceCull*1.02f; // a little slack so we don't cull stuff + } + SetPlaneSignbits( &tr.viewParms.frustum[i] ); + } +} + + +/* +================= +R_MirrorPoint +================= +*/ +void R_MirrorPoint (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + vec3_t local; + vec3_t transformed; + float d; + + VectorSubtract( in, surface->origin, local ); + + VectorClear( transformed ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(local, surface->axis[i]); + VectorMA( transformed, d, camera->axis[i], transformed ); + } + + VectorAdd( transformed, camera->origin, out ); +} + +void R_MirrorVector (vec3_t in, orientation_t *surface, orientation_t *camera, vec3_t out) { + int i; + float d; + + VectorClear( out ); + for ( i = 0 ; i < 3 ; i++ ) { + d = DotProduct(in, surface->axis[i]); + VectorMA( out, d, camera->axis[i], out ); + } +} + + +/* +============= +R_PlaneForSurface +============= +*/ +void R_PlaneForSurface (surfaceType_t *surfType, cplane_t *plane) { + srfTriangles_t *tri; + srfGridMesh_t *grid; + srfPoly_t *poly; + drawVert_t *v1, *v2, *v3; + vec4_t plane4; + + if (!surfType) { + memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } + switch (*surfType) { + case SF_FACE: + *plane = ((srfSurfaceFace_t *)surfType)->plane; + return; + case SF_TRIANGLES: + tri = (srfTriangles_t *)surfType; + v1 = tri->verts + tri->indexes[0]; + v2 = tri->verts + tri->indexes[1]; + v3 = tri->verts + tri->indexes[2]; + PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + case SF_POLY: + poly = (srfPoly_t *)surfType; + PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + case SF_GRID: + grid = (srfGridMesh_t *)surfType; + v1 = &grid->verts[0]; + v2 = &grid->verts[1]; + v3 = &grid->verts[2]; + PlaneFromPoints( plane4, v3->xyz, v2->xyz, v1->xyz ); + VectorCopy( plane4, plane->normal ); + plane->dist = plane4[3]; + return; + default: + memset (plane, 0, sizeof(*plane)); + plane->normal[0] = 1; + return; + } +} + +/* +================= +R_GetPortalOrientation + +entityNum is the entity that the portal surface is a part of, which may +be moving and rotating. + +Returns qtrue if it should be mirrored +================= +*/ +qboolean R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum, + orientation_t *surface, orientation_t *camera, + vec3_t pvsOrigin, qboolean *mirror ) { + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + vec3_t transformed; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != TR_WORLDENT ) { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin ); + } else { + plane = originalPlane; + } + + VectorCopy( plane.normal, surface->axis[0] ); + PerpendicularVector( surface->axis[1], surface->axis[0] ); + CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] ); + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // get the pvsOrigin from the entity + VectorCopy( e->e.oldorigin, pvsOrigin ); + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) { + VectorScale( plane.normal, plane.dist, surface->origin ); + VectorCopy( surface->origin, camera->origin ); + VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] ); + VectorCopy( surface->axis[1], camera->axis[1] ); + VectorCopy( surface->axis[2], camera->axis[2] ); + + *mirror = qtrue; + return qtrue; + } + + // project the origin onto the surface plane to get + // an origin point we can rotate around + d = DotProduct( e->e.origin, plane.normal ) - plane.dist; + VectorMA( e->e.origin, -d, surface->axis[0], surface->origin ); + + // now get the camera origin and orientation + VectorCopy( e->e.oldorigin, camera->origin ); + AxisCopy( e->e.axis, camera->axis ); + VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] ); + VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] ); + + // optionally rotate + if ( e->e.frame ) { + // continuous rotate + d = (tr.refdef.time/1000.0f) * e->e.frame; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } else if (e->e.skinNum){ + // bobbing rotate + //d = 4 * sin( tr.refdef.time * 0.003 ); + d = e->e.skinNum; + VectorCopy( camera->axis[1], transformed ); + RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d ); + CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] ); + } + *mirror = qfalse; + return qtrue; + } + + // if we didn't locate a portal entity, don't render anything. + // We don't want to just treat it as a mirror, because without a + // portal entity the server won't have communicated a proper entity set + // in the snapshot + + // unfortunately, with local movement prediction it is easily possible + // to see a surface before the server has communicated the matching + // portal surface entity, so we don't want to print anything here... + + //VID_Printf( PRINT_ALL, "Portal surface without a portal entity\n" ); + + return qfalse; +} + +static qboolean IsMirror( const drawSurf_t *drawSurf, int entityNum ) +{ + int i; + cplane_t originalPlane, plane; + trRefEntity_t *e; + float d; + + // create plane axis for the portal we are seeing + R_PlaneForSurface( drawSurf->surface, &originalPlane ); + + // rotate the plane if necessary + if ( entityNum != TR_WORLDENT ) + { + tr.currentEntityNum = entityNum; + tr.currentEntity = &tr.refdef.entities[entityNum]; + + // get the orientation of the entity + R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.or ); + + // rotate the plane, but keep the non-rotated version for matching + // against the portalSurface entities + R_LocalNormalToWorld( originalPlane.normal, plane.normal ); + plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.or.origin ); + + // translate the original plane + originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.or.origin ); + } + else + { + plane = originalPlane; + } + + // locate the portal entity closest to this plane. + // origin will be the origin of the portal, origin2 will be + // the origin of the camera + for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) + { + e = &tr.refdef.entities[i]; + if ( e->e.reType != RT_PORTALSURFACE ) { + continue; + } + + d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist; + if ( d > 64 || d < -64) { + continue; + } + + // if the entity is just a mirror, don't use as a camera point + if ( e->e.oldorigin[0] == e->e.origin[0] && + e->e.oldorigin[1] == e->e.origin[1] && + e->e.oldorigin[2] == e->e.origin[2] ) + { + return qtrue; + } + + return qfalse; + } + return qfalse; +} + +/* +** SurfIsOffscreen +** +** Determines if a surface is completely offscreen. +*/ +static qboolean SurfIsOffscreen( const drawSurf_t *drawSurf, vec4_t clipDest[128] ) { + float shortest = 1000000000; + int entityNum; + int numTriangles; + shader_t *shader; + int fogNum; + int dlighted; + vec4_t clip, eye; + int i; + unsigned int pointOr = 0; + unsigned int pointAnd = (unsigned int)~0; + + R_RotateForViewer(); + + R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted ); + RB_BeginSurface( shader, fogNum ); + rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface ); + + assert( tess.numVertexes < 128 ); + + for ( i = 0; i < tess.numVertexes; i++ ) + { + int j; + unsigned int pointFlags = 0; + + R_TransformModelToClip( tess.xyz[i], tr.or.modelMatrix, tr.viewParms.projectionMatrix, eye, clip ); + + for ( j = 0; j < 3; j++ ) + { + if ( clip[j] >= clip[3] ) + { + pointFlags |= (1 << (j*2)); + } + else if ( clip[j] <= -clip[3] ) + { + pointFlags |= ( 1 << (j*2+1)); + } + } + pointAnd &= pointFlags; + pointOr |= pointFlags; + } + + // trivially reject + if ( pointAnd ) + { + return qtrue; + } + + // determine if this surface is backfaced and also determine the distance + // to the nearest vertex so we can cull based on portal range. Culling + // based on vertex distance isn't 100% correct (we should be checking for + // range to the surface), but it's good enough for the types of portals + // we have in the game right now. + numTriangles = tess.numIndexes / 3; + + for ( i = 0; i < tess.numIndexes; i += 3 ) + { + vec3_t normal; + float dot; + float len; + + VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.or.origin, normal ); + + len = VectorLengthSquared( normal ); // lose the sqrt + if ( len < shortest ) + { + shortest = len; + } + + if ( ( dot = DotProduct( normal, tess.normal[tess.indexes[i]] ) ) >= 0 ) + { + numTriangles--; + } + } + if ( !numTriangles ) + { + return qtrue; + } + + // mirrors can early out at this point, since we don't do a fade over distance + // with them (although we could) + if ( IsMirror( drawSurf, entityNum ) ) + { + return qfalse; + } + + if ( shortest > (tess.shader->portalRange * tess.shader->portalRange)) + { + return qtrue; + } + + return qfalse; +} + +/* +======================== +R_MirrorViewBySurface + +Returns qtrue if another view has been rendered +======================== +*/ +int recursivePortalCount; +qboolean R_MirrorViewBySurface (drawSurf_t *drawSurf, int entityNum) { + vec4_t clipDest[128]; + viewParms_t newParms; + viewParms_t oldParms; + orientation_t surface, camera; + + // don't recursively mirror + if (tr.viewParms.isPortal) + { + VID_Printf( PRINT_DEVELOPER, "WARNING: recursive mirror/portal found\n" ); + return qfalse; + } + + if ( r_noportals->integer || r_fastsky->integer ) { + return qfalse; + } + + // trivially reject portal/mirror + if ( SurfIsOffscreen( drawSurf, clipDest ) ) { + return qfalse; + } + + // save old viewParms so we can return to it after the mirror view + oldParms = tr.viewParms; + + newParms = tr.viewParms; + newParms.isPortal = qtrue; + if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera, + newParms.pvsOrigin, &newParms.isMirror ) ) { + return qfalse; // bad portal, no portalentity + } + + R_MirrorPoint (oldParms.or.origin, &surface, &camera, newParms.or.origin ); + + VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal ); + newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal ); + + R_MirrorVector (oldParms.or.axis[0], &surface, &camera, newParms.or.axis[0]); + R_MirrorVector (oldParms.or.axis[1], &surface, &camera, newParms.or.axis[1]); + R_MirrorVector (oldParms.or.axis[2], &surface, &camera, newParms.or.axis[2]); + + // OPTIMIZE: restrict the viewport on the mirrored view + + // render the mirror view + R_RenderView (&newParms); + + tr.viewParms = oldParms; + + return qtrue; +} + +/* +================= +R_SpriteFogNum + +See if a sprite is inside a fog volume +================= +*/ +int R_SpriteFogNum( trRefEntity_t *ent ) { + int i; + fog_t *fog; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + if ( tr.refdef.rdflags & RDF_doLAGoggles ) + { + return tr.world->numfogs; + } + + int partialFog = 0; + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + if ( ent->e.origin[0] - ent->e.radius >= fog->bounds[0][0] + && ent->e.origin[0] + ent->e.radius <= fog->bounds[1][0] + && ent->e.origin[1] - ent->e.radius >= fog->bounds[0][1] + && ent->e.origin[1] + ent->e.radius <= fog->bounds[1][1] + && ent->e.origin[2] - ent->e.radius >= fog->bounds[0][2] + && ent->e.origin[2] + ent->e.radius <= fog->bounds[1][2] ) + {//totally inside it + return i; + break; + } + if ( ( ent->e.origin[0] - ent->e.radius >= fog->bounds[0][0] && ent->e.origin[1] - ent->e.radius >= fog->bounds[0][1] && ent->e.origin[2] - ent->e.radius >= fog->bounds[0][2] && + ent->e.origin[0] - ent->e.radius <= fog->bounds[1][0] && ent->e.origin[1] - ent->e.radius <= fog->bounds[1][1] && ent->e.origin[2] - ent->e.radius <= fog->bounds[1][2] ) || + ( ent->e.origin[0] + ent->e.radius >= fog->bounds[0][0] && ent->e.origin[1] + ent->e.radius >= fog->bounds[0][1] && ent->e.origin[2] + ent->e.radius >= fog->bounds[0][2] && + ent->e.origin[0] + ent->e.radius <= fog->bounds[1][0] && ent->e.origin[1] + ent->e.radius <= fog->bounds[1][1] && ent->e.origin[2] + ent->e.radius <= fog->bounds[1][2] ) ) + {//partially inside it + if ( tr.refdef.fogIndex == i || R_FogParmsMatch( tr.refdef.fogIndex, i ) ) + {//take new one only if it's the same one that the viewpoint is in + return i; + break; + } + else if ( !partialFog ) + {//first partialFog + partialFog = i; + } + } + } + + return partialFog; +} + +/* +========================================================================================== + +DRAWSURF SORTING + +========================================================================================== +*/ + +/* +================= +qsort replacement + +================= +*/ +#define SWAP_DRAW_SURF(a,b) temp=((int *)a)[0];((int *)a)[0]=((int *)b)[0];((int *)b)[0]=temp; temp=((int *)a)[1];((int *)a)[1]=((int *)b)[1];((int *)b)[1]=temp; + +/* this parameter defines the cutoff between using quick sort and + insertion sort for arrays; arrays with lengths shorter or equal to the + below value use insertion sort */ + +#define CUTOFF 8 /* testing shows that this is good value */ + +static void shortsort( drawSurf_t *lo, drawSurf_t *hi ) { + drawSurf_t *p, *max; + int temp; + + while (hi > lo) { + max = lo; + for (p = lo + 1; p <= hi; p++ ) { + if ( p->sort > max->sort ) { + max = p; + } + } + SWAP_DRAW_SURF(max, hi); + hi--; + } +} + + +/* sort the array between lo and hi (inclusive) +FIXME: this was lifted and modified from the microsoft lib source... + */ + +void qsortFast ( + void *base, + unsigned num, + unsigned width + ) +{ + char *lo, *hi; /* ends of sub-array currently sorting */ + char *mid; /* points to middle of subarray */ + char *loguy, *higuy; /* traveling pointers for partition step */ + unsigned size; /* size of the sub-array */ + char *lostk[30], *histk[30]; + int stkptr; /* stack for saving sub-array to be processed */ + int temp; + + if ( sizeof(drawSurf_t) != 8 ) { + Com_Error( ERR_DROP, "change SWAP_DRAW_SURF macro" ); + } + + /* Note: the number of stack entries required is no more than + 1 + log2(size), so 30 is sufficient for any array */ + + if (num < 2 || width == 0) + return; /* nothing to do */ + + stkptr = 0; /* initialize stack */ + + lo = (char *) base; + hi = (char *) base + width * (num-1); /* initialize limits */ + + /* this entry point is for pseudo-recursion calling: setting + lo and hi and jumping to here is like recursion, but stkptr is + prserved, locals aren't, so we preserve stuff on the stack */ +recurse: + + size = (hi - lo) / width + 1; /* number of el's to sort */ + + /* below a certain size, it is faster to use a O(n^2) sorting method */ + if (size <= CUTOFF) { + shortsort((drawSurf_t *)lo, (drawSurf_t *)hi); + } + else { + /* First we pick a partititioning element. The efficiency of the + algorithm demands that we find one that is approximately the + median of the values, but also that we select one fast. Using + the first one produces bad performace if the array is already + sorted, so we use the middle one, which would require a very + wierdly arranged array for worst case performance. Testing shows + that a median-of-three algorithm does not, in general, increase + performance. */ + + mid = lo + (size / 2) * width; /* find middle element */ + SWAP_DRAW_SURF(mid, lo); /* swap it to beginning of array */ + + /* We now wish to partition the array into three pieces, one + consisiting of elements <= partition element, one of elements + equal to the parition element, and one of element >= to it. This + is done below; comments indicate conditions established at every + step. */ + + loguy = lo; + higuy = hi + width; + + /* Note that higuy decreases and loguy increases on every iteration, + so loop must terminate. */ + for (;;) { + /* lo <= loguy < hi, lo < higuy <= hi + 1, + A[i] <= A[lo] for lo <= i <= loguy, + A[i] >= A[lo] for higuy <= i <= hi */ + + do { + loguy += width; + } while (loguy <= hi && + ( ((drawSurf_t *)loguy)->sort <= ((drawSurf_t *)lo)->sort ) ); + + /* lo < loguy <= hi+1, A[i] <= A[lo] for lo <= i < loguy, + either loguy > hi or A[loguy] > A[lo] */ + + do { + higuy -= width; + } while (higuy > lo && + ( ((drawSurf_t *)higuy)->sort >= ((drawSurf_t *)lo)->sort ) ); + + /* lo-1 <= higuy <= hi, A[i] >= A[lo] for higuy < i <= hi, + either higuy <= lo or A[higuy] < A[lo] */ + + if (higuy < loguy) + break; + + /* if loguy > hi or higuy <= lo, then we would have exited, so + A[loguy] > A[lo], A[higuy] < A[lo], + loguy < hi, highy > lo */ + + SWAP_DRAW_SURF(loguy, higuy); + + /* A[loguy] < A[lo], A[higuy] > A[lo]; so condition at top + of loop is re-established */ + } + + /* A[i] >= A[lo] for higuy < i <= hi, + A[i] <= A[lo] for lo <= i < loguy, + higuy < loguy, lo <= higuy <= hi + implying: + A[i] >= A[lo] for loguy <= i <= hi, + A[i] <= A[lo] for lo <= i <= higuy, + A[i] = A[lo] for higuy < i < loguy */ + + SWAP_DRAW_SURF(lo, higuy); /* put partition element in place */ + + /* OK, now we have the following: + A[i] >= A[higuy] for loguy <= i <= hi, + A[i] <= A[higuy] for lo <= i < higuy + A[i] = A[lo] for higuy <= i < loguy */ + + /* We've finished the partition, now we want to sort the subarrays + [lo, higuy-1] and [loguy, hi]. + We do the smaller one first to minimize stack usage. + We only sort arrays of length 2 or more.*/ + + if ( higuy - 1 - lo >= hi - loguy ) { + if (lo + width < higuy) { + lostk[stkptr] = lo; + histk[stkptr] = higuy - width; + ++stkptr; + } /* save big recursion for later */ + + if (loguy < hi) { + lo = loguy; + goto recurse; /* do small recursion */ + } + } + else { + if (loguy < hi) { + lostk[stkptr] = loguy; + histk[stkptr] = hi; + ++stkptr; /* save big recursion for later */ + } + + if (lo + width < higuy) { + hi = higuy - width; + goto recurse; /* do small recursion */ + } + } + } + + /* We have sorted the array, except for any pending sorts on the stack. + Check if there are any, and do them. */ + + --stkptr; + if (stkptr >= 0) { + lo = lostk[stkptr]; + hi = histk[stkptr]; + goto recurse; /* pop subarray from stack */ + } + else + return; /* all subarrays done */ +} + + +//========================================================================================== + +/* +================= +R_AddDrawSurf +================= +*/ +void R_AddDrawSurf( const surfaceType_t *surface, const shader_t *shader, int fogIndex, int dlightMap ) +{ + int index; + + // instead of checking for overflow, we just mask the index + // so it wraps around + index = tr.refdef.numDrawSurfs & DRAWSURF_MASK; + + if ( tr.refdef.rdflags & RDF_doLAGoggles ) + { + fogIndex = tr.world->numfogs; + } + + if ( (shader->surfaceFlags & SURF_FORCESIGHT) && !(tr.refdef.rdflags & RDF_ForceSightOn) ) + { //if shader is only seen with ForceSight and we don't have ForceSight on, then don't draw + return; + } + + // the sort data is packed into a single 32 bit value so it can be + // compared quickly during the qsorting process + tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT) + | tr.shiftedEntityNum | ( fogIndex << QSORT_FOGNUM_SHIFT ) | (int)dlightMap; + tr.refdef.drawSurfs[index].surface = (surfaceType_t *)surface; + tr.refdef.numDrawSurfs++; +} + +/* +================= +R_DecomposeSort +================= +*/ +void R_DecomposeSort( unsigned sort, int *entityNum, shader_t **shader, + int *fogNum, int *dlightMap ) { + *fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31; + *shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ]; + *entityNum = ( sort >> QSORT_ENTITYNUM_SHIFT ) & (MAX_ENTITIES-1); + *dlightMap = sort & 3; +} + +/* +================= +R_SortDrawSurfs +================= +*/ +void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs ) { + shader_t *shader; + int fogNum; + int entityNum; + int dlighted; + + // it is possible for some views to not have any surfaces + if ( numDrawSurfs < 1 ) { + // we still need to add it for hyperspace cases + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); + return; + } + + // if we overflowed MAX_DRAWSURFS, the drawsurfs + // wrapped around in the buffer and we will be missing + // the first surfaces, not the last ones + if ( numDrawSurfs > MAX_DRAWSURFS ) { + numDrawSurfs = MAX_DRAWSURFS; +#if defined(_DEBUG) && defined(_XBOX) + Com_Printf(S_COLOR_RED"Draw surface overflow! Tell Brian.\n"); +#endif + } + +#ifndef _XBOX + // sort the drawsurfs by sort type, then orientation, then shader + qsortFast (drawSurfs, numDrawSurfs, sizeof(drawSurf_t) ); +#endif + + // check for any pass through drawing, which + // may cause another view to be rendered first + for ( int i = 0 ; i < numDrawSurfs ; i++ ) { + R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum, &dlighted ); + + if ( shader->sort > SS_PORTAL ) { + break; + } + + // no shader should ever have this sort type + if ( shader->sort == SS_BAD ) { + Com_Error (ERR_DROP, "Shader '%s'with sort == SS_BAD", shader->name ); + } + + // if the mirror was completely clipped away, we may need to check another surface + if ( R_MirrorViewBySurface( (drawSurfs+i), entityNum) ) { + // this is a debug option to see exactly what is being mirrored + if ( r_portalOnly->integer ) { + return; + } + break; // only one mirror view at a time + } + } + +#ifdef _XBOX + qsortFast (drawSurfs, numDrawSurfs, sizeof(drawSurf_t) ); +#endif + + R_AddDrawSurfCmd( drawSurfs, numDrawSurfs ); +} + +/* +============= +R_AddEntitySurfaces +============= +*/ +void R_AddEntitySurfaces (void) { + trRefEntity_t *ent; + shader_t *shader; + + if ( !r_drawentities->integer ) { + return; + } + + for ( tr.currentEntityNum = 0; + tr.currentEntityNum < tr.refdef.num_entities; + tr.currentEntityNum++ ) { + ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum]; + + ent->needDlights = qfalse; + + // preshift the value we are going to OR into the drawsurf sort + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + if ((ent->e.renderfx & RF_ALPHA_FADE)) + { + // we need to make sure this is not sorted before the world..in fact we + // want this to be sorted quite late...like how about last. + // I don't want to use the highest bit, since no doubt someone fumbled + // handling that as an unsigned quantity somewhere + tr.shiftedEntityNum |= 0x80000000; + } + // + // the weapon model must be handled special -- + // we don't want the hacked weapon position showing in + // mirrors, because the true body position will already be drawn + // + if ( (ent->e.renderfx & RF_FIRST_PERSON) && tr.viewParms.isPortal) { + continue; + } + + + // simple generated models, like sprites and beams, are not culled + switch ( ent->e.reType ) { + case RT_PORTALSURFACE: + break; // don't draw anything + case RT_SPRITE: + case RT_ORIENTED_QUAD: + case RT_BEAM: + case RT_CYLINDER: + case RT_LATHE: + case RT_CLOUDS: + case RT_LINE: + case RT_ELECTRICITY: + case RT_SABER_GLOW: + // self blood sprites, talk balloons, etc should not be drawn in the primary + // view. We can't just do this check for all entities, because md3 + // entities may still want to cast shadows from them + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) { + continue; + } + shader = R_GetShaderByHandle( ent->e.customShader ); + R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ), 0 ); + break; + + case RT_MODEL: + // we must set up parts of tr.or for model culling + R_RotateForEntity( ent, &tr.viewParms, &tr.or ); + + tr.currentModel = R_GetModelByHandle( ent->e.hModel ); + if (!tr.currentModel) { + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, 0 ); + } else { + switch ( tr.currentModel->type ) { + case MOD_MESH: + R_AddMD3Surfaces( ent ); + break; + case MOD_BRUSH: + R_AddBrushModelSurfaces( ent ); + break; +/* +Ghoul2 Insert Start +*/ + + case MOD_MDXM: + R_AddGhoulSurfaces( ent); + break; + case MOD_BAD: // null model axis + if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) + { + if (!(ent->e.renderfx & RF_SHADOW_ONLY)) + { + break; + } + } + + if (ent->e.ghoul2 && G2API_HaveWeGhoul2Models(*((CGhoul2Info_v *)ent->e.ghoul2))) + { + R_AddGhoulSurfaces( ent); + break; + } + + R_AddDrawSurf( &entitySurface, tr.defaultShader, 0, false ); + break; +/* +Ghoul2 Insert End +*/ + + default: + Com_Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" ); + break; + } + } + break; + default: + Com_Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" ); + } + } + +} + + +/* +==================== +R_GenerateDrawSurfs +==================== +*/ +#ifdef _XBOX +extern void R_MarkLeaves(mleaf_s*); +void R_GenerateDrawSurfs( bool isPortal ) { + // determine which leaves are in the PVS / areamask + if ( !(tr.refdef.rdflags & RDF_NOWORLDMODEL) ) { + R_MarkLeaves (NULL); + } + + R_AddWorldSurfaces (); + + R_AddPolygonSurfaces(); + +// R_AddTerrainSurfaces(); + + // set the projection matrix with the minimum zfar + // now that we have the world bounded + // this needs to be done before entities are + // added, because they use the projection + // matrix for lod calculation + R_SetupProjection (); + + R_AddEntitySurfaces (); +} + +#else + +void R_GenerateDrawSurfs( void ) { + R_AddWorldSurfaces (); + + R_AddPolygonSurfaces(); + + R_AddTerrainSurfaces(); + + // set the projection matrix with the minimum zfar + // now that we have the world bounded + // this needs to be done before entities are + // added, because they use the projection + // matrix for lod calculation + R_SetupProjection (); + + R_AddEntitySurfaces (); +} +#endif + +/* +================ +R_DebugPolygon +================ +*/ +void R_DebugPolygon( int color, int numPoints, float *points ) { + int i; + + GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + // draw solid shade + + qglColor3f( color&1, (color>>1)&1, (color>>2)&1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + + // draw wireframe outline + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + qglDepthRange( 0, 0 ); + qglColor3f( 1, 1, 1 ); + qglBegin( GL_POLYGON ); + for ( i = 0 ; i < numPoints ; i++ ) { + qglVertex3fv( points + i * 3 ); + } + qglEnd(); + qglDepthRange( 0, 1 ); +} + +/* +==================== +R_DebugGraphics + +Visualization aid for movement clipping debugging +==================== +*/ +void R_DebugGraphics( void ) { + if ( !r_debugSurface->integer ) { + return; + } + + // the render thread can't make callbacks to the main thread + //R_SyncRenderThread(); + + GL_Bind( tr.whiteImage); + GL_Cull( CT_FRONT_SIDED ); + CM_DrawDebugSurface( R_DebugPolygon ); +} + +qboolean R_FogParmsMatch( int fog1, int fog2 ) +{ + for ( int i = 0; i < 2; i++ ) + { + if ( tr.world->fogs[fog1].parms.color[i] != tr.world->fogs[fog2].parms.color[i] ) + { + return qfalse; + } + } + return qtrue; +} + +void R_SetViewFogIndex (void) +{ + if ( tr.world->numfogs > 1 ) + {//more than just the LA goggles + fog_t *fog; + int contents = SV_PointContents( tr.refdef.vieworg, 0 ); + if ( (contents&CONTENTS_FOG) ) + {//only take a tr.refdef.fogIndex if the tr.refdef.vieworg is actually *in* that fog brush (assumption: checks pointcontents for any CONTENTS_FOG, not that particular brush...) + for ( tr.refdef.fogIndex = 1 ; tr.refdef.fogIndex < tr.world->numfogs ; tr.refdef.fogIndex++ ) + { + fog = &tr.world->fogs[tr.refdef.fogIndex]; + if ( tr.refdef.vieworg[0] >= fog->bounds[0][0] + && tr.refdef.vieworg[1] >= fog->bounds[0][1] + && tr.refdef.vieworg[2] >= fog->bounds[0][2] + && tr.refdef.vieworg[0] <= fog->bounds[1][0] + && tr.refdef.vieworg[1] <= fog->bounds[1][1] + && tr.refdef.vieworg[2] <= fog->bounds[1][2] ) + { + break; + } + } + if ( tr.refdef.fogIndex == tr.world->numfogs ) + { + tr.refdef.fogIndex = 0; + } + } + else + { + tr.refdef.fogIndex = 0; + } + } + else + { + tr.refdef.fogIndex = 0; + } +} +void RE_SetLightStyle(int style, int colors ); + +/* +================ +R_RenderView + +A view may be either the actual camera view, +or a mirror / remote location +================ +*/ +void R_RenderView (viewParms_t *parms) { + int firstDrawSurf; + + if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) { + return; + } + + if (r_debugStyle->integer >= 0) + { + int i; + color4ub_t whitecolor = {0xff, 0xff, 0xff, 0xff}; + color4ub_t blackcolor = {0x00, 0x00, 0x00, 0xff}; + + for(i=0;iinteger, *(int*)whitecolor); + } + + tr.viewCount++; + + tr.viewParms = *parms; + tr.viewParms.frameSceneNum = tr.frameSceneNum; + tr.viewParms.frameCount = tr.frameCount; + + firstDrawSurf = tr.refdef.numDrawSurfs; + + tr.viewCount++; + + // set viewParms.world + R_RotateForViewer (); + + R_SetupFrustum (); + + if (!(tr.refdef.rdflags & RDF_NOWORLDMODEL)) + { // Trying to do this with no world is not good. + R_SetViewFogIndex (); + } + +#ifdef _XBOX + R_GenerateDrawSurfs(parms->isPortal); +#else + R_GenerateDrawSurfs(); +#endif + + R_SortDrawSurfs( tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf ); + + // draw main system development information (surface outlines, etc) + R_DebugGraphics(); +} \ No newline at end of file diff --git a/code/renderer/tr_marks.cpp b/code/renderer/tr_marks.cpp new file mode 100644 index 0000000..1b6b37f --- /dev/null +++ b/code/renderer/tr_marks.cpp @@ -0,0 +1,500 @@ +// tr_marks.c -- polygon projection on the world polygons + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "tr_local.h" +//#include "assert.h" + +#define MAX_VERTS_ON_POLY 64 + +#define MARKER_OFFSET 0 // 1 + +/* +============= +R_ChopPolyBehindPlane + +Out must have space for two more vertexes than in +============= +*/ +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +static void R_ChopPolyBehindPlane( int numInPoints, vec3_t inPoints[MAX_VERTS_ON_POLY], + int *numOutPoints, vec3_t outPoints[MAX_VERTS_ON_POLY], + vec3_t normal, vec_t dist, vec_t epsilon) { + float dists[MAX_VERTS_ON_POLY+4]; + int sides[MAX_VERTS_ON_POLY+4]; + int counts[3]; + float dot; + int i, j; + float *p1, *p2, *clip; + float d; + + // don't clip if it might overflow + if ( numInPoints >= MAX_VERTS_ON_POLY - 2 ) { + *numOutPoints = 0; + return; + } + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for ( i = 0 ; i < numInPoints ; i++ ) { + dot = DotProduct( inPoints[i], normal ); + dot -= dist; + dists[i] = dot; + if ( dot > epsilon ) { + sides[i] = SIDE_FRONT; + } else if ( dot < -epsilon ) { + sides[i] = SIDE_BACK; + } else { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *numOutPoints = 0; + + if ( !counts[0] ) { + return; + } + if ( !counts[1] ) { + *numOutPoints = numInPoints; + memcpy( outPoints, inPoints, numInPoints * sizeof(vec3_t) ); + return; + } + + for ( i = 0 ; i < numInPoints ; i++ ) { + p1 = inPoints[i]; + clip = outPoints[ *numOutPoints ]; + + if ( sides[i] == SIDE_ON ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + continue; + } + + if ( sides[i] == SIDE_FRONT ) { + VectorCopy( p1, clip ); + (*numOutPoints)++; + clip = outPoints[ *numOutPoints ]; + } + + if ( sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) { + continue; + } + + // generate a split point + p2 = inPoints[ (i+1) % numInPoints ]; + + d = dists[i] - dists[i+1]; + if ( d == 0 ) { + dot = 0; + } else { + dot = dists[i] / d; + } + + // clip xyz + + for (j=0 ; j<3 ; j++) { + clip[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + (*numOutPoints)++; + } +} + +/* +================= +R_BoxSurfaces_r + +================= +*/ +void R_BoxSurfaces_r(mnode_t *node, vec3_t mins, vec3_t maxs, surfaceType_t **list, int listsize, int *listlength, vec3_t dir) { + + int s, c; + msurface_t *surf, **mark; + + // do the tail recursion in a loop + while ( node->contents == -1 ) { +#ifdef _XBOX + s = BoxOnPlaneSide( mins, maxs, tr.world->planes + node->planeNum ); +#else + s = BoxOnPlaneSide( mins, maxs, node->plane ); +#endif + if (s == 1) { + node = node->children[0]; + } else if (s == 2) { + node = node->children[1]; + } else { + R_BoxSurfaces_r(node->children[0], mins, maxs, list, listsize, listlength, dir); + node = node->children[1]; + } + } + + // add the individual surfaces +#ifdef _XBOX + mleaf_s *leaf = (mleaf_s*)node; + mark = tr.world->marksurfaces + leaf->firstMarkSurfNum; + c = leaf->nummarksurfaces; +#else + mark = node->firstmarksurface; + c = node->nummarksurfaces; +#endif + while (c--) { + // + if (*listlength >= listsize) break; + // + surf = *mark; + + // check if the surface has NOIMPACT or NOMARKS set + if ( ( surf->shader->surfaceFlags & ( SURF_NOIMPACT | SURF_NOMARKS ) ) + || ( surf->shader->contentFlags & CONTENTS_FOG ) ) { + surf->viewCount = tr.viewCount; + } + // extra check for surfaces to avoid list overflows + else if (*(surf->data) == SF_FACE) { + // the face plane should go through the box + s = BoxOnPlaneSide( mins, maxs, &(( srfSurfaceFace_t * ) surf->data)->plane ); + if (s == 1 || s == 2) { + surf->viewCount = tr.viewCount; + } else if (DotProduct((( srfSurfaceFace_t * ) surf->data)->plane.normal, dir) > -0.5) { + // don't add faces that make sharp angles with the projection direction + surf->viewCount = tr.viewCount; + } + } + else if (*(surfaceType_t *) (surf->data) != SF_GRID + && *(surfaceType_t *) (surf->data) != SF_TRIANGLES ) + { + surf->viewCount = tr.viewCount; + } + // check the viewCount because the surface may have + // already been added if it spans multiple leafs + if (surf->viewCount != tr.viewCount) { + surf->viewCount = tr.viewCount; + list[*listlength] = (surfaceType_t *) surf->data; + (*listlength)++; + } + mark++; + } +} + +/* +================= +R_AddMarkFragments + +================= +*/ +void R_AddMarkFragments(int numClipPoints, vec3_t clipPoints[2][MAX_VERTS_ON_POLY], + int numPlanes, vec3_t *normals, float *dists, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer, + int *returnedPoints, int *returnedFragments, + vec3_t mins, vec3_t maxs) { + int pingPong, i; + markFragment_t *mf; + + // chop the surface by all the bounding planes of the to be projected polygon + pingPong = 0; + + for ( i = 0 ; i < numPlanes ; i++ ) { + + R_ChopPolyBehindPlane( numClipPoints, clipPoints[pingPong], + &numClipPoints, clipPoints[!pingPong], + normals[i], dists[i], 0.5 ); + pingPong ^= 1; + if ( numClipPoints == 0 ) { + break; + } + } + // completely clipped away? + if ( numClipPoints == 0 ) { + return; + } + + // add this fragment to the returned list + if ( numClipPoints + (*returnedPoints) > maxPoints ) { + return; // not enough space for this polygon + } + /* + // all the clip points should be within the bounding box + for ( i = 0 ; i < numClipPoints ; i++ ) { + int j; + for ( j = 0 ; j < 3 ; j++ ) { + if (clipPoints[pingPong][i][j] < mins[j] - 0.5) break; + if (clipPoints[pingPong][i][j] > maxs[j] + 0.5) break; + } + if (j < 3) break; + } + if (i < numClipPoints) return; + */ + + mf = fragmentBuffer + (*returnedFragments); + mf->firstPoint = (*returnedPoints); + mf->numPoints = numClipPoints; + memcpy( pointBuffer + (*returnedPoints) * 3, clipPoints[pingPong], numClipPoints * sizeof(vec3_t) ); + + (*returnedPoints) += numClipPoints; + (*returnedFragments)++; +} + +/* +================= +R_MarkFragments + +================= +*/ +int R_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { + int numsurfaces, numPlanes; + int i, j, k, m, n; + surfaceType_t *surfaces[64]; + vec3_t mins, maxs; + int returnedFragments; + int returnedPoints; + vec3_t normals[MAX_VERTS_ON_POLY+2]; + float dists[MAX_VERTS_ON_POLY+2]; + vec3_t clipPoints[2][MAX_VERTS_ON_POLY]; + vec3_t normal; + vec3_t projectionDir; + vec3_t v1, v2; + + //increment view count for double check prevention + tr.viewCount++; + + // + VectorNormalize2( projection, projectionDir ); + // find all the brushes that are to be considered + ClearBounds( mins, maxs ); + for ( i = 0 ; i < numPoints ; i++ ) { + vec3_t temp; + + AddPointToBounds( points[i], mins, maxs ); + VectorAdd( points[i], projection, temp ); + AddPointToBounds( temp, mins, maxs ); + // make sure we get all the leafs (also the one(s) in front of the hit surface) + VectorMA( points[i], -20, projectionDir, temp ); + AddPointToBounds( temp, mins, maxs ); + } + + if (numPoints > MAX_VERTS_ON_POLY) numPoints = MAX_VERTS_ON_POLY; + // create the bounding planes for the to be projected polygon + for ( i = 0 ; i < numPoints ; i++ ) { + VectorSubtract(points[(i+1)%numPoints], points[i], v1); + VectorAdd(points[i], projection, v2); + VectorSubtract(points[i], v2, v2); + CrossProduct(v1, v2, normals[i]); + VectorNormalizeFast(normals[i]); + dists[i] = DotProduct(normals[i], points[i]); + } + // add near and far clipping planes for projection + VectorCopy(projectionDir, normals[numPoints]); + dists[numPoints] = DotProduct(normals[numPoints], points[0]) - 32; + VectorCopy(projectionDir, normals[numPoints+1]); + VectorInverse(normals[numPoints+1]); + dists[numPoints+1] = DotProduct(normals[numPoints+1], points[0]) - 20; + numPlanes = numPoints + 2; + + numsurfaces = 0; + R_BoxSurfaces_r(tr.world->nodes, mins, maxs, surfaces, 64, &numsurfaces, projectionDir); + //assert(numsurfaces <= 64); + //assert(numsurfaces != 64); + + returnedPoints = 0; + returnedFragments = 0; + + for ( i = 0 ; i < numsurfaces ; i++ ) { + + if (*surfaces[i] == SF_GRID) { + const srfGridMesh_t * const cv = (srfGridMesh_t *) surfaces[i]; + for ( m = 0 ; m < cv->height - 1 ; m++ ) { + for ( n = 0 ; n < cv->width - 1 ; n++ ) { + // We triangulate the grid and chop all triangles within + // the bounding planes of the to be projected polygon. + // LOD is not taken into account, not such a big deal though. + // + // It's probably much nicer to chop the grid itself and deal + // with this grid as a normal SF_GRID surface so LOD will + // be applied. However the LOD of that chopped grid must + // be synced with the LOD of the original curve. + // One way to do this; the chopped grid shares vertices with + // the original curve. When LOD is applied to the original + // curve the unused vertices are flagged. Now the chopped curve + // should skip the flagged vertices. This still leaves the + // problems with the vertices at the chopped grid edges. + // + // To avoid issues when LOD applied to "hollow curves" (like + // the ones around many jump pads) we now just add a 2 unit + // offset to the triangle vertices. + // The offset is added in the vertex normal vector direction + // so all triangles will still fit together. + // The 2 unit offset should avoid pretty much all LOD problems. + + const int numClipPoints = 3; + + const drawVert_t * const dv = cv->verts + m * cv->width + n; + + VectorCopy(dv[0].xyz, clipPoints[0][0]); + VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[0].normal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); + VectorCopy(dv[1].xyz, clipPoints[0][2]); + VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[1].normal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.1) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + + VectorCopy(dv[1].xyz, clipPoints[0][0]); + VectorMA(clipPoints[0][0], MARKER_OFFSET, dv[1].normal, clipPoints[0][0]); + VectorCopy(dv[cv->width].xyz, clipPoints[0][1]); + VectorMA(clipPoints[0][1], MARKER_OFFSET, dv[cv->width].normal, clipPoints[0][1]); + VectorCopy(dv[cv->width+1].xyz, clipPoints[0][2]); + VectorMA(clipPoints[0][2], MARKER_OFFSET, dv[cv->width+1].normal, clipPoints[0][2]); + // check the normal of this triangle + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + if (DotProduct(normal, projectionDir) < -0.05) { + // add the fragments of this triangle + R_AddMarkFragments(numClipPoints, clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + } + } + } + else if (*surfaces[i] == SF_FACE) { + const srfSurfaceFace_t * const surf = ( srfSurfaceFace_t * ) surfaces[i]; + // check the normal of this face + if (DotProduct(surf->plane.normal, projectionDir) > -0.5) { + continue; + } + + /* + VectorSubtract(clipPoints[0][0], clipPoints[0][1], v1); + VectorSubtract(clipPoints[0][2], clipPoints[0][1], v2); + CrossProduct(v1, v2, normal); + VectorNormalize(normal); + if (DotProduct(normal, projectionDir) > -0.5) continue; + */ +#ifdef _XBOX + const unsigned char * const indexes = (unsigned char *)( (byte *)surf + surf->ofsIndices ); + int nextSurfPoint = NEXT_SURFPOINT(surf->flags); +#else + const int * const indexes = (int *)( (byte *)surf + surf->ofsIndices ); +#endif + for ( k = 0 ; k < surf->numIndices ; k += 3 ) { + for ( j = 0 ; j < 3 ; j++ ) { +#ifdef _XBOX + const unsigned short* v = surf->srfPoints + nextSurfPoint * indexes[k+j]; + float fVec[3]; + Q_CastShort2Float(&fVec[0], (short*)v + 0); + Q_CastShort2Float(&fVec[1], (short*)v + 1); + Q_CastShort2Float(&fVec[2], (short*)v + 2); + VectorMA( fVec, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] ); +#else + const float * const v = surf->points[0] + VERTEXSIZE * indexes[k+j]; + VectorMA( v, MARKER_OFFSET, surf->plane.normal, clipPoints[0][j] ); +#endif + } + // add the fragments of this face + R_AddMarkFragments( 3 , clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + if ( returnedFragments == maxFragments ) { + return returnedFragments; // not enough space for more fragments + } + } + continue; + } + else if (*surfaces[i] == SF_TRIANGLES) + { + const srfTriangles_t * const surf = ( srfTriangles_t * ) surfaces[i]; + + for ( k = 0 ; k < surf->numIndexes ; k += 3 ) + { + int i1=surf->indexes[k]; + int i2=surf->indexes[k+1]; + int i3=surf->indexes[k+2]; + VectorSubtract(surf->verts[i1].xyz,surf->verts[i2].xyz, v1); + VectorSubtract(surf->verts[i3].xyz,surf->verts[i2].xyz, v2); + CrossProduct(v1, v2, normal); + VectorNormalizeFast(normal); + // check the normal of this triangle + if (DotProduct(normal, projectionDir) < -0.1) + { +#ifdef _XBOX + // This is really ugly, and really inefficient. Of course, the inefficiency + // pales in comparison to the misery that ensues once you realze that + // MARKER_OFFSET is #define'd to be 0. + float fVec[3]; + Q_CastShort2Float(&fVec[0], &surf->verts[i1].xyz[0]); + Q_CastShort2Float(&fVec[1], &surf->verts[i1].xyz[1]); + Q_CastShort2Float(&fVec[2], &surf->verts[i1].xyz[2]); + VectorMA(fVec, MARKER_OFFSET, normal, clipPoints[0][0]); + Q_CastShort2Float(&fVec[0], &surf->verts[i2].xyz[0]); + Q_CastShort2Float(&fVec[1], &surf->verts[i2].xyz[1]); + Q_CastShort2Float(&fVec[2], &surf->verts[i2].xyz[2]); + VectorMA(fVec, MARKER_OFFSET, normal, clipPoints[0][1]); + Q_CastShort2Float(&fVec[0], &surf->verts[i3].xyz[0]); + Q_CastShort2Float(&fVec[1], &surf->verts[i3].xyz[1]); + Q_CastShort2Float(&fVec[2], &surf->verts[i3].xyz[2]); + VectorMA(fVec, MARKER_OFFSET, normal, clipPoints[0][2]); +#else + VectorMA(surf->verts[i1].xyz, MARKER_OFFSET, normal, clipPoints[0][0]); + VectorMA(surf->verts[i2].xyz, MARKER_OFFSET, normal, clipPoints[0][1]); + VectorMA(surf->verts[i3].xyz, MARKER_OFFSET, normal, clipPoints[0][2]); +#endif + // add the fragments of this triangle + R_AddMarkFragments( 3 , clipPoints, + numPlanes, normals, dists, + maxPoints, pointBuffer, + maxFragments, fragmentBuffer, + &returnedPoints, &returnedFragments, mins, maxs); + if ( returnedFragments == maxFragments ) + { + return returnedFragments; // not enough space for more fragments + } + } + } + } + else { + // ignore all other world surfaces + // might be cool to also project polygons on a triangle soup + // however this will probably create huge amounts of extra polys + // even more than the projection onto curves + continue; + } + } + return returnedFragments; +} + diff --git a/code/renderer/tr_mesh.cpp b/code/renderer/tr_mesh.cpp new file mode 100644 index 0000000..6536ede --- /dev/null +++ b/code/renderer/tr_mesh.cpp @@ -0,0 +1,447 @@ +// tr_mesh.c: triangle model functions + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + + +#include "tr_local.h" +#include "MatComp.h" + +#ifdef VV_LIGHTING +#include "tr_lightmanager.h" +#endif + +#ifdef _XBOX +#include "../win32/glw_win_dx8.h" +#include "../win32/win_stencilshadow.h" +#include +#include +#include "../win32/shader_constants.h" +#endif + +float ProjectRadius( float r, vec3_t location ) +{ + float pr; + float dist; + float c; + vec3_t p; + float width; + float depth; + + c = DotProduct( tr.viewParms.or.axis[0], tr.viewParms.or.origin ); + dist = DotProduct( tr.viewParms.or.axis[0], location ) - c; + + if ( dist <= 0 ) + return 0; + + p[0] = 0; + p[1] = Q_fabs( r ); + p[2] = -dist; + + width = p[0] * tr.viewParms.projectionMatrix[1] + + p[1] * tr.viewParms.projectionMatrix[5] + + p[2] * tr.viewParms.projectionMatrix[9] + + tr.viewParms.projectionMatrix[13]; + + depth = p[0] * tr.viewParms.projectionMatrix[3] + + p[1] * tr.viewParms.projectionMatrix[7] + + p[2] * tr.viewParms.projectionMatrix[11] + + tr.viewParms.projectionMatrix[15]; + + pr = width / depth; +#if defined (_XBOX) + pr = -pr; +#endif + + if ( pr > 1.0f ) + pr = 1.0f; + + return pr; +} + +/* +============= +R_CullModel +============= +*/ +static int R_CullModel( md3Header_t *header, trRefEntity_t *ent ) { + vec3_t bounds[2]; + md3Frame_t *oldFrame, *newFrame; + int i; + + // compute frame pointers + newFrame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + oldFrame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.oldframe; + + // cull bounding sphere ONLY if this is not an upscaled entity + if ( !ent->e.nonNormalizedAxes ) + { + if ( ent->e.frame == ent->e.oldframe ) + { + switch ( R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ) ) + { + case CULL_OUT: + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + + case CULL_IN: + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + + case CULL_CLIP: + tr.pc.c_sphere_cull_md3_clip++; + break; + } + } + else + { + int sphereCull, sphereCullB; + + sphereCull = R_CullLocalPointAndRadius( newFrame->localOrigin, newFrame->radius ); + if ( newFrame == oldFrame ) { + sphereCullB = sphereCull; + } else { + sphereCullB = R_CullLocalPointAndRadius( oldFrame->localOrigin, oldFrame->radius ); + } + + if ( sphereCull == sphereCullB ) + { + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_md3_out++; + return CULL_OUT; + } + else if ( sphereCull == CULL_IN ) + { + tr.pc.c_sphere_cull_md3_in++; + return CULL_IN; + } + else + { + tr.pc.c_sphere_cull_md3_clip++; + } + } + } + } + + // calculate a bounding box in the current coordinate system + for (i = 0 ; i < 3 ; i++) { + bounds[0][i] = oldFrame->bounds[0][i] < newFrame->bounds[0][i] ? oldFrame->bounds[0][i] : newFrame->bounds[0][i]; + bounds[1][i] = oldFrame->bounds[1][i] > newFrame->bounds[1][i] ? oldFrame->bounds[1][i] : newFrame->bounds[1][i]; + } + + switch ( R_CullLocalBox( bounds ) ) + { + case CULL_IN: + tr.pc.c_box_cull_md3_in++; + return CULL_IN; + case CULL_CLIP: + tr.pc.c_box_cull_md3_clip++; + return CULL_CLIP; + case CULL_OUT: + default: + tr.pc.c_box_cull_md3_out++; + return CULL_OUT; + } +} + +/* +================= +RE_GetModelBounds + + Returns the bounds of the current model + (qhandle_t)hModel and (int)frame need to be set +================= +*/ + +void RE_GetModelBounds(refEntity_t *refEnt, vec3_t bounds1, vec3_t bounds2) +{ + md3Frame_t *frame; + md3Header_t *header; + model_t *model; + + assert(refEnt); + + model = R_GetModelByHandle( refEnt->hModel ); + assert(model); + header = model->md3[0]; + assert(header); + frame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + refEnt->frame; + assert(frame); + + VectorCopy(frame->bounds[0], bounds1); + VectorCopy(frame->bounds[1], bounds2); +} + +/* +================= +R_ComputeLOD + +================= +*/ +#ifdef _XBOX +int R_ComputeLOD( trRefEntity_t *ent ) { +#else +static int R_ComputeLOD( trRefEntity_t *ent ) { +#endif + float radius; + float flod; + float projectedRadius; + int lod; + + if ( tr.currentModel->numLods < 2 ) + { // model has only 1 LOD level, skip computations and bias + return(0); + } + + // multiple LODs exist, so compute projected bounding sphere + // and use that as a criteria for selecting LOD +// if ( tr.currentModel->md3[0] ) + { //normal md3 + md3Frame_t *frame; + frame = ( md3Frame_t * ) ( ( ( unsigned char * ) tr.currentModel->md3[0] ) + tr.currentModel->md3[0]->ofsFrames ); + frame += ent->e.frame; + radius = RadiusFromBounds( frame->bounds[0], frame->bounds[1] ); + } + + if ( ( projectedRadius = ProjectRadius( radius, ent->e.origin ) ) != 0 ) + { + flod = 1.0f - projectedRadius * r_lodscale->value; + flod *= tr.currentModel->numLods; + } + else + { // object intersects near view plane, e.g. view weapon + flod = 0; + } + + lod = myftol( flod ); + + if ( lod < 0 ) { + lod = 0; + } else if ( lod >= tr.currentModel->numLods ) { + lod = tr.currentModel->numLods - 1; + } + + lod += r_lodbias->integer; + if ( lod >= tr.currentModel->numLods ) + lod = tr.currentModel->numLods - 1; + if ( lod < 0 ) + lod = 0; + + return lod; +} + +/* +================= +R_ComputeFogNum + +================= +*/ +static int R_ComputeFogNum( md3Header_t *header, trRefEntity_t *ent ) { + int i; + fog_t *fog; + md3Frame_t *md3Frame; + vec3_t localOrigin; + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return 0; + } + + if ( tr.refdef.rdflags & RDF_doLAGoggles ) + { + return tr.world->numfogs; + } + + + // FIXME: non-normalized axis issues + md3Frame = ( md3Frame_t * ) ( ( byte * ) header + header->ofsFrames ) + ent->e.frame; + VectorAdd( ent->e.origin, md3Frame->localOrigin, localOrigin ); + + int partialFog = 0; + for ( i = 1 ; i < tr.world->numfogs ; i++ ) { + fog = &tr.world->fogs[i]; + if ( localOrigin[0] - md3Frame->radius >= fog->bounds[0][0] + && localOrigin[0] + md3Frame->radius <= fog->bounds[1][0] + && localOrigin[1] - md3Frame->radius >= fog->bounds[0][1] + && localOrigin[1] + md3Frame->radius <= fog->bounds[1][1] + && localOrigin[2] - md3Frame->radius >= fog->bounds[0][2] + && localOrigin[2] + md3Frame->radius <= fog->bounds[1][2] ) + {//totally inside it + return i; + break; + } + if ( ( localOrigin[0] - md3Frame->radius >= fog->bounds[0][0] && localOrigin[1] - md3Frame->radius >= fog->bounds[0][1] && localOrigin[2] - md3Frame->radius >= fog->bounds[0][2] && + localOrigin[0] - md3Frame->radius <= fog->bounds[1][0] && localOrigin[1] - md3Frame->radius <= fog->bounds[1][1] && localOrigin[2] - md3Frame->radius <= fog->bounds[1][2]) || + ( localOrigin[0] + md3Frame->radius >= fog->bounds[0][0] && localOrigin[1] + md3Frame->radius >= fog->bounds[0][1] && localOrigin[2] + md3Frame->radius >= fog->bounds[0][2] && + localOrigin[0] + md3Frame->radius <= fog->bounds[1][0] && localOrigin[1] + md3Frame->radius <= fog->bounds[1][1] && localOrigin[2] + md3Frame->radius <= fog->bounds[1][2] ) ) + {//partially inside it + if ( tr.refdef.fogIndex == i || R_FogParmsMatch( tr.refdef.fogIndex, i ) ) + {//take new one only if it's the same one that the viewpoint is in + return i; + break; + } + else if ( !partialFog ) + {//first partialFog + partialFog = i; + } + } + } + //if all else fails, return the first partialFog + return partialFog; +} + +/* +================= +R_AddMD3Surfaces + +================= +*/ +void R_AddMD3Surfaces( trRefEntity_t *ent ) { + int i; + md3Header_t *header = 0; + md3Surface_t *surface = 0; + md3Shader_t *md3Shader = 0; + shader_t *shader = 0; + shader_t *main_shader = 0; + int cull; + int lod; + int fogNum; + qboolean personalModel; + + // don't add third_person objects if not in a portal + personalModel = (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal; + + if ( ent->e.renderfx & RF_CAP_FRAMES) { + if (ent->e.frame > tr.currentModel->md3[0]->numFrames-1) + ent->e.frame = tr.currentModel->md3[0]->numFrames-1; + if (ent->e.oldframe > tr.currentModel->md3[0]->numFrames-1) + ent->e.oldframe = tr.currentModel->md3[0]->numFrames-1; + } + else if ( ent->e.renderfx & RF_WRAP_FRAMES ) { + ent->e.frame %= tr.currentModel->md3[0]->numFrames; + ent->e.oldframe %= tr.currentModel->md3[0]->numFrames; + } + + // + // Validate the frames so there is no chance of a crash. + // This will write directly into the entity structure, so + // when the surfaces are rendered, they don't need to be + // range checked again. + // + if ( (ent->e.frame >= tr.currentModel->md3[0]->numFrames) + || (ent->e.frame < 0) + || (ent->e.oldframe >= tr.currentModel->md3[0]->numFrames) + || (ent->e.oldframe < 0) ) + { + VID_Printf (PRINT_ALL, "R_AddMD3Surfaces: no such frame %d to %d for '%s'\n", + ent->e.oldframe, ent->e.frame, + tr.currentModel->name ); + ent->e.frame = 0; + ent->e.oldframe = 0; + } + + // + // compute LOD + // + lod = R_ComputeLOD( ent ); + + header = tr.currentModel->md3[lod]; + + // + // cull the entire model if merged bounding box of both frames + // is outside the view frustum. + // + cull = R_CullModel ( header, ent ); + if ( cull == CULL_OUT ) { + return; + } + + // + // set up lighting now that we know we aren't culled + // +#ifdef VV_LIGHTING + if ( !personalModel ) { + VVLightMan.R_SetupEntityLighting( &tr.refdef, ent ); +#else + if ( !personalModel || r_shadows->integer > 1 ) { + R_SetupEntityLighting( &tr.refdef, ent ); +#endif + } + + // + // see if we are in a fog volume + // + fogNum = R_ComputeFogNum( header, ent ); + + // + // draw all surfaces + // + main_shader = R_GetShaderByHandle( ent->e.customShader ); + + surface = (md3Surface_t *)( (byte *)header + header->ofsSurfaces ); + for ( i = 0 ; i < header->numSurfaces ; i++ ) { + + if ( ent->e.customShader ) {// a little more efficient + shader = main_shader; + } else if ( ent->e.customSkin > 0 && ent->e.customSkin < tr.numSkins ) { + skin_t *skin; + int j; + + skin = R_GetSkinByHandle( ent->e.customSkin ); + + // match the surface name to something in the skin file + shader = tr.defaultShader; + for ( j = 0 ; j < skin->numSurfaces ; j++ ) { + // the names have both been lowercased + if ( !strcmp( skin->surfaces[j]->name, surface->name ) ) { + shader = skin->surfaces[j]->shader; + break; + } + } + } else if ( surface->numShaders <= 0 ) { + shader = tr.defaultShader; + } else { + md3Shader = (md3Shader_t *) ( (byte *)surface + surface->ofsShaders ); + md3Shader += ent->e.skinNum % surface->numShaders; + shader = tr.shaders[ md3Shader->shaderIndex ]; + } + + + // we will add shadows even if the main object isn't visible in the view +#ifndef _XBOX // No MD3 shadows on Xbox + // stencil shadows can't do personal models unless I polyhedron clip + if ( !personalModel + && r_shadows->integer == 2 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t *)surface, tr.shadowShader, 0, qfalse ); + } + + // projection shadows work fine with personal models + if ( r_shadows->integer == 3 + && fogNum == 0 + && (ent->e.renderfx & RF_SHADOW_PLANE ) + && shader->sort == SS_OPAQUE ) { + R_AddDrawSurf( (surfaceType_t *)surface, tr.projectionShadowShader, 0, qfalse ); + } +#endif + + // don't add third_person objects if not viewing through a portal + if ( !personalModel ) { +#ifdef VV_LIGHTING + int dlightBits = ( ent->dlightBits != 0 ); + R_AddDrawSurf( (surfaceType_t *)surface, shader, fogNum, dlightBits ); +#else + R_AddDrawSurf( (surfaceType_t *)surface, shader, fogNum, qfalse ); +#endif + } + + surface = (md3Surface_t *)( (byte *)surface + surface->ofsEnd ); + } + +} + diff --git a/code/renderer/tr_model.cpp b/code/renderer/tr_model.cpp new file mode 100644 index 0000000..0b4afae --- /dev/null +++ b/code/renderer/tr_model.cpp @@ -0,0 +1,1203 @@ +// tr_models.c -- model loading and caching + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "tr_local.h" +#include "MatComp.h" +#include "../qcommon/sstring.h" + +#define LL(x) x=LittleLong(x) + +void RE_LoadWorldMap_Actual( const char *name, world_t &worldData, int index ); //should only be called for sub-bsp instances + +static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *name, qboolean &bAlreadyCached ); + +/* +Ghoul2 Insert Start +*/ + +typedef struct modelHash_s +{ + char name[MAX_QPATH]; + qhandle_t handle; + struct modelHash_s *next; + +}modelHash_t; + +#define FILE_HASH_SIZE 1024 +static modelHash_t *mhHashTable[FILE_HASH_SIZE]; + + +/* +Ghoul2 Insert End +*/ + + + +// This stuff looks a bit messy, but it's kept here as black box, and nothing appears in any .H files for other +// modules to worry about. I may make another module for this sometime. +// +typedef pair StringOffsetAndShaderIndexDest_t; +typedef vector ShaderRegisterData_t; +struct CachedEndianedModelBinary_s +{ + void *pModelDiskImage; + int iAllocSize; // may be useful for mem-query, but I don't actually need it + ShaderRegisterData_t ShaderRegisterData; + + int iLastLevelUsedOn; + + CachedEndianedModelBinary_s() + { + pModelDiskImage = 0; + iLastLevelUsedOn = -1; + iAllocSize = 0; + ShaderRegisterData.clear(); + } +}; +typedef struct CachedEndianedModelBinary_s CachedEndianedModelBinary_t; +typedef map CachedModels_t; + CachedModels_t *CachedModels = NULL; // the important cache item. + +void RE_RegisterModels_StoreShaderRequest(const char *psModelFileName, const char *psShaderName, const int *piShaderIndexPoke) +{ + char sModelName[MAX_QPATH]; + + Q_strncpyz(sModelName,psModelFileName,sizeof(sModelName)); + Q_strlwr (sModelName); + + CachedEndianedModelBinary_t &ModelBin = (*CachedModels)[sModelName]; + + if (ModelBin.pModelDiskImage == NULL) + { + assert(0); // should never happen, means that we're being called on a model that wasn't loaded + } + else + { + const int iNameOffset = psShaderName - (char *)ModelBin.pModelDiskImage; + const int iPokeOffset = (char*) piShaderIndexPoke - (char *)ModelBin.pModelDiskImage; + + ModelBin.ShaderRegisterData.push_back( StringOffsetAndShaderIndexDest_t( iNameOffset,iPokeOffset) ); + } +} + + +static const byte FakeGLAFile[] = +{ +0x32, 0x4C, 0x47, 0x41, 0x06, 0x00, 0x00, 0x00, 0x2A, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x01, 0x00, 0x00, 0x00, +0x14, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, +0x26, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4D, 0x6F, 0x64, 0x56, 0x69, 0x65, 0x77, 0x20, +0x69, 0x6E, 0x74, 0x65, 0x72, 0x6E, 0x61, 0x6C, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, +0x00, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, +0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, +0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, +0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xBF, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, +0x00, 0x80, 0x00, 0x80, 0x00, 0x80 +}; + + +// returns qtrue if loaded, and sets the supplied qbool to true if it was from cache (instead of disk) +// (which we need to know to avoid LittleLong()ing everything again (well, the Mac needs to know anyway)... +// +qboolean RE_RegisterModels_GetDiskFile( const char *psModelFileName, void **ppvBuffer, qboolean *pqbAlreadyCached) +{ + char sModelName[MAX_QPATH]; + + Q_strncpyz(sModelName,psModelFileName,sizeof(sModelName)); + Q_strlwr (sModelName); + + CachedEndianedModelBinary_t &ModelBin = (*CachedModels)[sModelName]; + + if (ModelBin.pModelDiskImage == NULL) + { + // didn't have it cached, so try the disk... + // + + // special case intercept first... + // + if (!strcmp(sDEFAULT_GLA_NAME ".gla" , psModelFileName)) + { + // return fake params as though it was found on disk... + // + void *pvFakeGLAFile = Z_Malloc( sizeof(FakeGLAFile), TAG_FILESYS, qfalse ); + memcpy(pvFakeGLAFile, &FakeGLAFile[0], sizeof(FakeGLAFile)); + *ppvBuffer = pvFakeGLAFile; + *pqbAlreadyCached = qfalse; // faking it like this should mean that it works fine on the Mac as well + return qtrue; + } + + FS_ReadFile( sModelName, ppvBuffer ); + *pqbAlreadyCached = qfalse; + + const bool bSuccess = !!(*ppvBuffer); + + return bSuccess; + } + else + { + *ppvBuffer = ModelBin.pModelDiskImage; + *pqbAlreadyCached = qtrue; + return qtrue; + } +} + + +// if return == true, no further action needed by the caller... +// +void *RE_RegisterModels_Malloc(int iSize, void *pvDiskBufferIfJustLoaded, const char *psModelFileName, qboolean *pqbAlreadyFound, memtag_t eTag) +{ + char sModelName[MAX_QPATH]; + + Q_strncpyz(sModelName,psModelFileName,sizeof(sModelName)); + Q_strlwr (sModelName); + + CachedEndianedModelBinary_t &ModelBin = (*CachedModels)[sModelName]; + + if (ModelBin.pModelDiskImage == NULL) + { + // ... then this entry has only just been created, ie we need to load it fully... + // + // new, instead of doing a Z_Malloc and assigning that we just morph the disk buffer alloc + // then don't thrown it away on return - cuts down on mem overhead + // + // ... groan, but not if doing a limb hierarchy creation (some VV stuff?), in which case it's NULL + // +#ifndef _XBOX // GODDAMN - No, we can't do this. + if ( pvDiskBufferIfJustLoaded ) + { + Z_MorphMallocTag( pvDiskBufferIfJustLoaded, eTag ); + } + else +#endif + { + pvDiskBufferIfJustLoaded = Z_Malloc(iSize,eTag, qfalse ); + } + + ModelBin.pModelDiskImage= pvDiskBufferIfJustLoaded; + ModelBin.iAllocSize = iSize; + *pqbAlreadyFound = qfalse; + } + else + { + // if we already had this model entry, then re-register all the shaders it wanted... + // + const int iEntries = ModelBin.ShaderRegisterData.size(); + for (int i=0; idefaultShader ) + { + *piShaderPokePtr = 0; + } else { + *piShaderPokePtr = sh->index; + } + } + *pqbAlreadyFound = qtrue; // tell caller not to re-Endian or re-Shader this binary + } + + ModelBin.iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); + + return ModelBin.pModelDiskImage; +} + + +// dump any models not being used by this level if we're running low on memory... +// +static int GetModelDataAllocSize(void) +{ + return Z_MemSize( TAG_MODEL_MD3) + + Z_MemSize( TAG_MODEL_GLM) + + Z_MemSize( TAG_MODEL_GLA); +} +extern cvar_t *r_modelpoolmegs; +// +// return qtrue if at least one cached model was freed (which tells z_malloc()-fail recovery code to try again) +// +extern qboolean gbInsideRegisterModel; +qboolean RE_RegisterModels_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel /* = qfalse */) +{ + qboolean bAtLeastoneModelFreed = qfalse; + + if (gbInsideRegisterModel) + { + Com_DPrintf( "(Inside RE_RegisterModel (z_malloc recovery?), exiting...\n"); + } + else + { + int iLoadedModelBytes = GetModelDataAllocSize(); + const int iMaxModelBytes= r_modelpoolmegs->integer * 1024 * 1024; + + qboolean bEraseOccured = qfalse; + for (CachedModels_t::iterator itModel = CachedModels->begin(); itModel != CachedModels->end() && ( bDeleteEverythingNotUsedThisLevel || iLoadedModelBytes > iMaxModelBytes ); bEraseOccured?itModel:++itModel) + { + bEraseOccured = qfalse; + + CachedEndianedModelBinary_t &CachedModel = (*itModel).second; + + qboolean bDeleteThis = qfalse; + + if (bDeleteEverythingNotUsedThisLevel) + { + bDeleteThis = (CachedModel.iLastLevelUsedOn != RE_RegisterMedia_GetLevel()); + } + else + { + bDeleteThis = (CachedModel.iLastLevelUsedOn < RE_RegisterMedia_GetLevel()); + } + + // if it wasn't used on this level, dump it... + // + if (bDeleteThis) + { + #ifdef _DEBUG +// LPCSTR psModelName = (*itModel).first.c_str(); +// VID_Printf( PRINT_DEVELOPER, "Dumping \"%s\"", psModelName); +// VID_Printf( PRINT_DEVELOPER, ", used on lvl %d\n",CachedModel.iLastLevelUsedOn); + #endif + + if (CachedModel.pModelDiskImage) { + Z_Free(CachedModel.pModelDiskImage); + //CachedModel.pModelDiskImage = NULL; // REM for reference, erase() call below negates the need for it. + bAtLeastoneModelFreed = qtrue; + } + + itModel = CachedModels->erase(itModel); + bEraseOccured = qtrue; + + iLoadedModelBytes = GetModelDataAllocSize(); + } + } + } + + //VID_Printf( PRINT_DEVELOPER, "RE_RegisterModels_LevelLoadEnd(): Ok\n"); + + return bAtLeastoneModelFreed; +} + +void RE_RegisterModels_Info_f( void ) +{ + int iTotalBytes = 0; + if(!CachedModels) { + Com_Printf ("%d bytes total (%.2fMB)\n",iTotalBytes, (float)iTotalBytes / 1024.0f / 1024.0f); + return; + } + + int iModels = CachedModels->size(); + int iModel = 0; + + for (CachedModels_t::iterator itModel = CachedModels->begin(); itModel != CachedModels->end(); ++itModel,iModel++) + { + CachedEndianedModelBinary_t &CachedModel = (*itModel).second; + + VID_Printf( PRINT_ALL, "%d/%d: \"%s\" (%d bytes)",iModel,iModels,(*itModel).first.c_str(),CachedModel.iAllocSize ); + + #ifdef _DEBUG + VID_Printf( PRINT_ALL, ", lvl %d\n",CachedModel.iLastLevelUsedOn); + #endif + + iTotalBytes += CachedModel.iAllocSize; + } + VID_Printf( PRINT_ALL, "%d bytes total (%.2fMB)\n",iTotalBytes, (float)iTotalBytes / 1024.0f / 1024.0f); +} + + +static void RE_RegisterModels_DeleteAll(void) +{ + for (CachedModels_t::iterator itModel = CachedModels->begin(); itModel != CachedModels->end(); ) + { + CachedEndianedModelBinary_t &CachedModel = (*itModel).second; + + if (CachedModel.pModelDiskImage) { + Z_Free(CachedModel.pModelDiskImage); + } + + itModel = CachedModels->erase(itModel); + } + + extern void RE_AnimationCFGs_DeleteAll(void); + RE_AnimationCFGs_DeleteAll(); +} + + +static int giRegisterMedia_CurrentLevel=0; +static qboolean gbAllowScreenDissolve = qtrue; +// +// param "bAllowScreenDissolve" is just a convenient way of getting hold of a bool which can be checked by the code that +// issues the InitDissolve command later in RE_RegisterMedia_LevelLoadEnd() +// +void RE_RegisterMedia_LevelLoadBegin(const char *psMapName, ForceReload_e eForceReload, qboolean bAllowScreenDissolve) +{ + gbAllowScreenDissolve = bAllowScreenDissolve; + + tr.numBSPModels = 0; + + // for development purposes we may want to ditch certain media just before loading a map... + // + switch (eForceReload) + { + case eForceReload_BSP: + + CM_DeleteCachedMap(qtrue); + R_Images_DeleteLightMaps(); + break; + + case eForceReload_MODELS: + + RE_RegisterModels_DeleteAll(); + break; + + case eForceReload_ALL: + + // BSP... + // + CM_DeleteCachedMap(qtrue); + R_Images_DeleteLightMaps(); + // + // models... + // + RE_RegisterModels_DeleteAll(); + break; + } + + // at some stage I'll probably want to put some special logic here, like not incrementing the level number + // when going into a map like "brig" or something, so returning to the previous level doesn't require an + // asset reload etc, but for now... + // + // only bump level number if we're not on the same level. + // Note that this will hide uncached models, which is perhaps a bad thing?... + // + static char sPrevMapName[MAX_QPATH]={0}; + if (Q_stricmp( psMapName,sPrevMapName )) + { + Q_strncpyz( sPrevMapName, psMapName, sizeof(sPrevMapName) ); + giRegisterMedia_CurrentLevel++; + } +} + +int RE_RegisterMedia_GetLevel(void) +{ + return giRegisterMedia_CurrentLevel; +} + +extern qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel); + +void RE_RegisterMedia_LevelLoadEnd(void) +{ + RE_RegisterModels_LevelLoadEnd(qfalse); + RE_RegisterImages_LevelLoadEnd(); + SND_RegisterAudio_LevelLoadEnd(qfalse); + + if (gbAllowScreenDissolve) + { + RE_InitDissolve(qfalse); + } + + S_RestartMusic(); + + extern qboolean gbAlreadyDoingLoad; + gbAlreadyDoingLoad = qfalse; +} + + + + +/* +** R_GetModelByHandle +*/ +model_t *R_GetModelByHandle( qhandle_t index ) { + model_t *mod; + + // out of range gets the defualt model + if ( index < 1 || index >= tr.numModels ) { + return tr.models[0]; + } + + mod = tr.models[index]; + + return mod; +} + +//=============================================================================== + +/* +** R_AllocModel +*/ +model_t *R_AllocModel( void ) { + model_t *mod; + + if ( tr.numModels == MAX_MOD_KNOWN ) { + return NULL; + } + + mod = (model_t*) Hunk_Alloc( sizeof( *tr.models[tr.numModels] ), qtrue ); + mod->index= tr.numModels; + tr.models[tr.numModels] = mod; + tr.numModels++; + + return mod; +} + +/* +Ghoul2 Insert Start +*/ + +/* +================ +return a hash value for the filename +================ +*/ +static long generateHashValue( const char *fname, const int size ) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (fname[i] != '\0') { + letter = tolower(fname[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (size-1); + return hash; +} + +void RE_InsertModelIntoHash(const char *name, model_t *mod) +{ + int hash; + modelHash_t *mh; + + hash = generateHashValue(name, FILE_HASH_SIZE); + + // insert this file into the hash table so we can look it up faster later + mh = (modelHash_t*)Hunk_Alloc( sizeof( modelHash_t ), qtrue ); + + mh->next = mhHashTable[hash]; + mh->handle = mod->index; + strcpy(mh->name, name); + mhHashTable[hash] = mh; +} +/* +Ghoul2 Insert End +*/ + + +/* +==================== +RE_RegisterModel + +Loads in a model for the given name + +Zero will be returned if the model fails to load. +An entry will be retained for failed models as an +optimization to prevent disk rescanning if they are +asked for again. +==================== +*/ +static qhandle_t RE_RegisterModel_Actual( const char *name ) +{ + model_t *mod; + unsigned *buf; + int lod; + int ident; + qboolean loaded; +// qhandle_t hModel; + int numLoaded; +/* +Ghoul2 Insert Start +*/ + int hash; + modelHash_t *mh; +/* +Ghoul2 Insert End +*/ + + if ( !name || !name[0] ) { + VID_Printf( PRINT_WARNING, "RE_RegisterModel: NULL name\n" ); + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + VID_Printf( PRINT_DEVELOPER, "Model name exceeds MAX_QPATH\n" ); + return 0; + } + +/* +Ghoul2 Insert Start +*/ +// if (!tr.registered) { +// VID_Printf( PRINT_WARNING, "RE_RegisterModel (%s) called before ready!\n",name ); +// return 0; +// } + // + // search the currently loaded models + // + + hash = generateHashValue(name, FILE_HASH_SIZE); + + // + // see if the model is already loaded + // + for (mh=mhHashTable[hash]; mh; mh=mh->next) { + if (Q_stricmp(mh->name, name) == 0) { + if (tr.models[mh->handle]->type == MOD_BAD) + { + return 0; + } + return mh->handle; + } + } + +/* +Ghoul2 Insert End +*/ + + if (name[0] == '#') + { + char temp[MAX_QPATH]; + + tr.numBSPModels++; +#ifndef DEDICATED + RE_LoadWorldMap_Actual(va("maps/%s.bsp", name + 1), tr.bspModels[tr.numBSPModels - 1], tr.numBSPModels); //this calls R_LoadSubmodels which will put them into the Hash +#endif + Com_sprintf(temp, MAX_QPATH, "*%d-0", tr.numBSPModels); + hash = generateHashValue(temp, FILE_HASH_SIZE); + for (mh=mhHashTable[hash]; mh; mh=mh->next) + { + if (Q_stricmp(mh->name, temp) == 0) + { + return mh->handle; + } + } + + return 0; + } + + // allocate a new model_t + + if ( ( mod = R_AllocModel() ) == NULL ) { + VID_Printf( PRINT_WARNING, "RE_RegisterModel: R_AllocModel() failed for '%s'\n", name); + return 0; + } + + // only set the name after the model has been successfully loaded + Q_strncpyz( mod->name, name, sizeof( mod->name ) ); + + // make sure the render thread is stopped + //R_SyncRenderThread(); + + int iLODStart = 0; + if (strstr (name, ".md3")) { + iLODStart = MD3_MAX_LODS-1; //this loads the md3s in reverse so they can be biased + } + mod->numLods = 0; + + // + // load the files + // + numLoaded = 0; + + for ( lod = iLODStart; lod >= 0 ; lod-- ) { + char filename[1024]; + + strcpy( filename, name ); + + if ( lod != 0 ) { + char namebuf[80]; + + if ( strrchr( filename, '.' ) ) { + *strrchr( filename, '.' ) = 0; + } + sprintf( namebuf, "_%d.md3", lod ); + strcat( filename, namebuf ); + } + + qboolean bAlreadyCached = qfalse; + if (!RE_RegisterModels_GetDiskFile(filename, (void **)&buf, &bAlreadyCached)) + { + if (numLoaded) //we loaded one already, but a higher LOD is missing! + { + Com_Error (ERR_DROP, "R_LoadMD3: %s has LOD %d but is missing LOD %d ('%s')!", mod->name, lod+1, lod, filename); + } + continue; + } + + //loadmodel = mod; // this seems to be fairly pointless + + // important that from now on we pass 'filename' instead of 'name' to all model load functions, + // because 'filename' accounts for any LOD mangling etc so guarantees unique lookups for yet more + // internal caching... + // + ident = *(unsigned *)buf; + if (!bAlreadyCached) + { + ident = LittleLong(ident); + } + + switch (ident) + { + // if you add any new types of model load in this switch-case, tell me, + // or copy what I've done with the cache scheme (-ste). + // + case MDXA_IDENT: + + loaded = R_LoadMDXA( mod, buf, filename, bAlreadyCached ); + break; + + case MDXM_IDENT: + + loaded = R_LoadMDXM( mod, buf, filename, bAlreadyCached ); + break; + + case MD3_IDENT: + + loaded = R_LoadMD3( mod, lod, buf, filename, bAlreadyCached ); + break; + + default: + + VID_Printf (PRINT_WARNING,"RE_RegisterModel: unknown fileid for %s\n", filename); + goto fail; + } + + if (!bAlreadyCached){ // important to check!! + FS_FreeFile (buf); + } + + if ( !loaded ) { + if ( lod == 0 ) { + VID_Printf (PRINT_WARNING,"RE_RegisterModel: cannot load %s\n", filename); + goto fail; + } else { + break; + } + } else { + mod->numLods++; + numLoaded++; + // if we have a valid model and are biased + // so that we won't see any higher detail ones, + // stop loading them + if ( lod <= r_lodbias->integer ) { + break; + } + } + } + + if ( numLoaded ) { + // duplicate into higher lod spots that weren't + // loaded, in case the user changes r_lodbias on the fly + for ( lod-- ; lod >= 0 ; lod-- ) { + mod->numLods++; + mod->md3[lod] = mod->md3[lod+1]; + } +/* +Ghoul2 Insert Start +*/ + + RE_InsertModelIntoHash(name, mod); + return mod->index; +/* +Ghoul2 Insert End +*/ + + } + + +fail: + // we still keep the model_t around, so if the model name is asked for + // again, we won't bother scanning the filesystem + mod->type = MOD_BAD; + RE_InsertModelIntoHash(name, mod); + return 0; +} + + + + +// wrapper function needed to avoid problems with mid-function returns so I can safely use this bool to tell the +// z_malloc-fail recovery code whether it's safe to ditch any model caches... +// +qboolean gbInsideRegisterModel = qfalse; +qhandle_t RE_RegisterModel( const char *name ) +{ + gbInsideRegisterModel = qtrue; // !!!!!!!!!!!!!! + + qhandle_t q = RE_RegisterModel_Actual( name ); + +if (stricmp(&name[strlen(name)-4],".gla")){ + gbInsideRegisterModel = qfalse; // GLA files recursively call this, so don't turn off half way. A reference count would be nice, but if any ERR_DROP ever occurs within the load then the refcount will be knackered from then on +} + + return q; +} + + +/* +================= +R_LoadMD3 +================= +*/ +static qboolean R_LoadMD3 (model_t *mod, int lod, void *buffer, const char *mod_name, qboolean &bAlreadyCached ) { + int i, j; + md3Header_t *pinmodel; + md3Surface_t *surf; + md3Shader_t *shader; + int version; + int size; + +#ifndef _M_IX86 + md3Frame_t *frame; + md3Triangle_t *tri; + md3St_t *st; + md3XyzNormal_t *xyz; + md3Tag_t *tag; +#endif + + + pinmodel= (md3Header_t *)buffer; + // + // read some fields from the binary, but only LittleLong() them when we know this wasn't an already-cached model... + // + version = pinmodel->version; + size = pinmodel->ofsEnd; + + if (!bAlreadyCached) + { + version = LittleLong(version); + size = LittleLong(size); + } + + if (version != MD3_VERSION) { + VID_Printf( PRINT_WARNING, "R_LoadMD3: %s has wrong version (%i should be %i)\n", + mod_name, version, MD3_VERSION); + return qfalse; + } + + mod->type = MOD_MESH; + mod->dataSize += size; + + qboolean bAlreadyFound = qfalse; + mod->md3[lod] = (md3Header_t *) RE_RegisterModels_Malloc(size, buffer, mod_name, &bAlreadyFound, TAG_MODEL_MD3); + + assert(bAlreadyCached == bAlreadyFound); + + if (!bAlreadyFound) + { + // horrible new hackery, if !bAlreadyFound then we've just done a tag-morph, so we need to set the + // bool reference passed into this function to true, to tell the caller NOT to do an FS_Freefile since + // we've hijacked that memory block... + // + // Aaaargh. Kill me now... + // +#ifdef _XBOX + // Yeah. Unless we're on Xbox. Where we don't try and re-tag memory after allocation. + memcpy( mod->md3[lod], buffer, size ); // and don't do this now, since it's the same thing +#else + bAlreadyCached = qtrue; + assert( mod->md3[lod] == buffer ); +#endif + + LL(mod->md3[lod]->ident); + LL(mod->md3[lod]->version); + LL(mod->md3[lod]->numFrames); + LL(mod->md3[lod]->numTags); + LL(mod->md3[lod]->numSurfaces); + LL(mod->md3[lod]->ofsFrames); + LL(mod->md3[lod]->ofsTags); + LL(mod->md3[lod]->ofsSurfaces); + LL(mod->md3[lod]->ofsEnd); + } + + if ( mod->md3[lod]->numFrames < 1 ) { + VID_Printf( PRINT_WARNING, "R_LoadMD3: %s has no frames\n", mod_name ); + return qfalse; + } + + if (bAlreadyFound) + { + return qtrue; // All done. Stop, go no further, do not pass Go... + } + +#ifndef _M_IX86 + // + // optimisation, we don't bother doing this for standard intel case since our data's already in that format... + // + + // swap all the frames + frame = (md3Frame_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames ); + for ( i = 0 ; i < mod->md3[lod]->numFrames ; i++, frame++) { + frame->radius = LittleFloat( frame->radius ); + for ( j = 0 ; j < 3 ; j++ ) { + frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] ); + frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] ); + frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] ); + } + } + + // swap all the tags + tag = (md3Tag_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsTags ); + for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->numFrames ; i++, tag++) { + for ( j = 0 ; j < 3 ; j++ ) { + tag->origin[j] = LittleFloat( tag->origin[j] ); + tag->axis[0][j] = LittleFloat( tag->axis[0][j] ); + tag->axis[1][j] = LittleFloat( tag->axis[1][j] ); + tag->axis[2][j] = LittleFloat( tag->axis[2][j] ); + } + } +#endif + + // swap all the surfaces + surf = (md3Surface_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces ); + for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++) { + LL(surf->flags); + LL(surf->numFrames); + LL(surf->numShaders); + LL(surf->numTriangles); + LL(surf->ofsTriangles); + LL(surf->numVerts); + LL(surf->ofsShaders); + LL(surf->ofsSt); + LL(surf->ofsXyzNormals); + LL(surf->ofsEnd); + + if ( surf->numVerts > SHADER_MAX_VERTEXES ) { + Com_Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)", + mod_name, SHADER_MAX_VERTEXES, surf->numVerts ); + } + if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) { + Com_Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)", + mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles ); + } + + // change to surface identifier + surf->ident = SF_MD3; + + // lowercase the surface name so skin compares are faster + Q_strlwr( surf->name ); + + // strip off a trailing _1 or _2 + // this is a crutch for q3data being a mess + j = strlen( surf->name ); + if ( j > 2 && surf->name[j-2] == '_' ) { + surf->name[j-2] = 0; + } + + // register the shaders + shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders ); + for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) { + shader_t *sh; + + sh = R_FindShader( shader->name, lightmapsNone, stylesDefault, qtrue ); + if ( sh->defaultShader ) { + shader->shaderIndex = 0; + } else { + shader->shaderIndex = sh->index; + } + RE_RegisterModels_StoreShaderRequest(mod_name, &shader->name[0], &shader->shaderIndex); + } + + +#ifndef _M_IX86 +// +// optimisation, we don't bother doing this for standard intel case since our data's already in that format... +// + + // swap all the triangles + tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles ); + for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) { + LL(tri->indexes[0]); + LL(tri->indexes[1]); + LL(tri->indexes[2]); + } + + // swap all the ST + st = (md3St_t *) ( (byte *)surf + surf->ofsSt ); + for ( j = 0 ; j < surf->numVerts ; j++, st++ ) { + st->st[0] = LittleFloat( st->st[0] ); + st->st[1] = LittleFloat( st->st[1] ); + } + + // swap all the XyzNormals + xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals ); + for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ ) + { + xyz->xyz[0] = LittleShort( xyz->xyz[0] ); + xyz->xyz[1] = LittleShort( xyz->xyz[1] ); + xyz->xyz[2] = LittleShort( xyz->xyz[2] ); + + xyz->normal = LittleShort( xyz->normal ); + } +#endif + + // find the next surface + surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd ); + } + + return qtrue; +} + + +//============================================================================= + +void ShaderTableCleanup(); +void CM_LoadShaderText(bool forceReload); +void CM_SetupShaderProperties(void); + +/* +** RE_BeginRegistration +*/ +void RE_BeginRegistration( glconfig_t *glconfigOut ) { +#ifndef _XBOX + ShaderTableCleanup(); +#endif + Hunk_ClearToMark(); + + R_Init(); + *glconfigOut = glConfig; + + tr.viewCluster = -1; // force markleafs to regenerate + RE_ClearScene(); + tr.registered = qtrue; + + R_SyncRenderThread(); +} + +//============================================================================= + +/* +=============== +R_ModelInit +=============== +*/ +void R_ModelInit( void ) +{ +#ifdef _XBOX + // Sorry Raven, but static maps == fragmentation + if (!CachedModels) + { + CachedModels = new CachedModels_t; + } +#else + static CachedModels_t singleton; // sorry vv, your dynamic allocation was a (false) memory leak + CachedModels = &singleton; +#endif + + model_t *mod; + + // leave a space for NULL model + tr.numModels = 0; + + mod = R_AllocModel(); + mod->type = MOD_BAD; +/* +Ghoul2 Insert Start +*/ + + memset(mhHashTable, 0, sizeof(mhHashTable)); +/* +Ghoul2 Insert End +*/ + +} + + +/* +================ +R_Modellist_f +================ +*/ +void R_Modellist_f( void ) { + int i, j; + model_t *mod; + int total; + int lods; + + total = 0; + for ( i = 1 ; i < tr.numModels; i++ ) { + mod = tr.models[i]; + switch (mod->type) + { + default: + assert(0); + VID_Printf( PRINT_ALL, "UNKNOWN : %s\n", mod->name ); + break; + + case MOD_BAD: + VID_Printf( PRINT_ALL, "MOD_BAD : %s\n", mod->name ); + break; + + case MOD_BRUSH: + VID_Printf( PRINT_ALL, "%8i : (%i) %s\n", mod->dataSize, mod->numLods, mod->name ); + break; + + case MOD_MDXA: + + VID_Printf( PRINT_ALL, "%8i : (%i) %s\n", mod->dataSize, mod->numLods, mod->name ); + break; + + case MOD_MDXM: + + VID_Printf( PRINT_ALL, "%8i : (%i) %s\n", mod->dataSize, mod->numLods, mod->name ); + break; + + case MOD_MESH: + + lods = 1; + for ( j = 1 ; j < MD3_MAX_LODS ; j++ ) { + if ( mod->md3[j] && mod->md3[j] != mod->md3[j-1] ) { + lods++; + } + } + VID_Printf( PRINT_ALL, "%8i : (%i) %s\n",mod->dataSize, lods, mod->name ); + break; + } + total += mod->dataSize; + } + VID_Printf( PRINT_ALL, "%8i : Total models\n", total ); + +/* this doesn't work with the new hunks + if ( tr.world ) { + VID_Printf( PRINT_ALL, "%8i : %s\n", tr.world->dataSize, tr.world->name ); + } */ +} + +//============================================================================= + + +/* +================ +R_GetTag for MD3s +================ +*/ +static md3Tag_t *R_GetTag( md3Header_t *mod, int frame, const char *tagName ) { + md3Tag_t *tag; + int i; + + if ( frame >= mod->numFrames ) { + // it is possible to have a bad frame while changing models, so don't error + frame = mod->numFrames - 1; + } + + tag = (md3Tag_t *)((byte *)mod + mod->ofsTags) + frame * mod->numTags; + for ( i = 0 ; i < mod->numTags ; i++, tag++ ) { + if ( !strcmp( tag->name, tagName ) ) { + return tag; // found it + } + } + + return NULL; +} + +/* +================ +R_LerpTag +================ +*/ +void R_LerpTag( orientation_t *tag, qhandle_t handle, int startFrame, int endFrame, + float frac, const char *tagName ) { + md3Tag_t *start, *finish; + int i; + float frontLerp, backLerp; + model_t *model; + + model = R_GetModelByHandle( handle ); + if ( model->md3[0] ) + { + start = R_GetTag( model->md3[0], startFrame, tagName ); + finish = R_GetTag( model->md3[0], endFrame, tagName ); + } + else + { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return; + } + + if ( !start || !finish ) { + AxisClear( tag->axis ); + VectorClear( tag->origin ); + return; + } + + frontLerp = frac; + backLerp = 1.0 - frac; + + for ( i = 0 ; i < 3 ; i++ ) { + tag->origin[i] = start->origin[i] * backLerp + finish->origin[i] * frontLerp; + tag->axis[0][i] = start->axis[0][i] * backLerp + finish->axis[0][i] * frontLerp; + tag->axis[1][i] = start->axis[1][i] * backLerp + finish->axis[1][i] * frontLerp; + tag->axis[2][i] = start->axis[2][i] * backLerp + finish->axis[2][i] * frontLerp; + } + VectorNormalize( tag->axis[0] ); + VectorNormalize( tag->axis[1] ); + VectorNormalize( tag->axis[2] ); +} + + +/* +==================== +R_ModelBounds +==================== +*/ +void R_ModelBounds( qhandle_t handle, vec3_t mins, vec3_t maxs ) { + model_t *model; + + model = R_GetModelByHandle( handle ); + + if ( model->bmodel ) { + VectorCopy( model->bmodel->bounds[0], mins ); + VectorCopy( model->bmodel->bounds[1], maxs ); + return; + } + + if ( model->md3[0] ) { + md3Header_t *header; + md3Frame_t *frame; + header = model->md3[0]; + + frame = (md3Frame_t *)( (byte *)header + header->ofsFrames ); + + VectorCopy( frame->bounds[0], mins ); + VectorCopy( frame->bounds[1], maxs ); + } + else + { + VectorClear( mins ); + VectorClear( maxs ); + return; + } +} + + +#ifdef _XBOX +void R_ModelFree(void) +{ + if (CachedModels) + { + RE_RegisterModels_DeleteAll(); + delete CachedModels; + CachedModels = NULL; + } +} +#endif \ No newline at end of file diff --git a/code/renderer/tr_noise.cpp b/code/renderer/tr_noise.cpp new file mode 100644 index 0000000..639c153 --- /dev/null +++ b/code/renderer/tr_noise.cpp @@ -0,0 +1,89 @@ +// tr_noise.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "tr_local.h" + +#define NOISE_SIZE 256 +#define NOISE_MASK ( NOISE_SIZE - 1 ) + +#define VAL( a ) s_noise_perm[ ( a ) & ( NOISE_MASK )] +#define INDEX( x, y, z, t ) VAL( x + VAL( y + VAL( z + VAL( t ) ) ) ) + +static float s_noise_table[NOISE_SIZE]; +static int s_noise_perm[NOISE_SIZE]; + +#define LERP( a, b, w ) ( a * ( 1.0f - w ) + b * w ) + +static float GetNoiseValue( int x, int y, int z, int t ) +{ + int index = INDEX( ( int ) x, ( int ) y, ( int ) z, ( int ) t ); + + return s_noise_table[index]; +} + +float GetNoiseTime( int t ) +{ + int index = VAL( t ); + + return (1+s_noise_table[index]); +} + +void R_NoiseInit( void ) +{ + int i; + + srand( 1001 ); + + for ( i = 0; i < NOISE_SIZE; i++ ) + { + s_noise_table[i] = ( float ) ( ( ( rand() / ( float ) RAND_MAX ) * 2.0 - 1.0 ) ); + s_noise_perm[i] = ( unsigned char ) ( rand() / ( float ) RAND_MAX * 255 ); + } + srand( com_frameTime ); +} + +float R_NoiseGet4f( float x, float y, float z, float t ) +{ + int i; + int ix, iy, iz, it; + float fx, fy, fz, ft; + float front[4]; + float back[4]; + float fvalue, bvalue, value[2], finalvalue; + + ix = ( int ) floor( x ); + fx = x - ix; + iy = ( int ) floor( y ); + fy = y - iy; + iz = ( int ) floor( z ); + fz = z - iz; + it = ( int ) floor( t ); + ft = t - it; + + for ( i = 0; i < 2; i++ ) + { + front[0] = GetNoiseValue( ix, iy, iz, it + i ); + front[1] = GetNoiseValue( ix+1, iy, iz, it + i ); + front[2] = GetNoiseValue( ix, iy+1, iz, it + i ); + front[3] = GetNoiseValue( ix+1, iy+1, iz, it + i ); + + back[0] = GetNoiseValue( ix, iy, iz + 1, it + i ); + back[1] = GetNoiseValue( ix+1, iy, iz + 1, it + i ); + back[2] = GetNoiseValue( ix, iy+1, iz + 1, it + i ); + back[3] = GetNoiseValue( ix+1, iy+1, iz + 1, it + i ); + + fvalue = LERP( LERP( front[0], front[1], fx ), LERP( front[2], front[3], fx ), fy ); + bvalue = LERP( LERP( back[0], back[1], fx ), LERP( back[2], back[3], fx ), fy ); + + value[i] = LERP( fvalue, bvalue, fz ); + } + + finalvalue = LERP( value[0], value[1], ft ); + + return finalvalue; +} diff --git a/code/renderer/tr_public.h b/code/renderer/tr_public.h new file mode 100644 index 0000000..a24516d --- /dev/null +++ b/code/renderer/tr_public.h @@ -0,0 +1,147 @@ +#ifndef __TR_PUBLIC_H +#define __TR_PUBLIC_H + +#include "tr_types.h" + +#ifdef _XBOX +// Get font functions with default arguments that we need below +#include "tr_font.h" +#endif + +#define REF_API_VERSION 9 + +// +// these are the functions exported by the refresh module +// +#ifdef _XBOX +template class SPARC; +#endif +typedef struct { + // called before the library is unloaded + // if the system is just reconfiguring, pass destroyWindow = qfalse, + // which will keep the screen from flashing to the desktop. + void (*Shutdown)( qboolean destroyWindow ); + + // All data that will be used in a level should be + // registered before rendering any frames to prevent disk hits, + // but they can still be registered at a later time + // if necessary. + // + // BeginRegistration makes any existing media pointers invalid + // and returns the current gl configuration, including screen width + // and height, which can be used by the client to intelligently + // size display elements + void (*BeginRegistration)( glconfig_t *config ); + qhandle_t (*RegisterModel)( const char *name ); + qhandle_t (*RegisterSkin)( const char *name ); + int (*GetAnimationCFG)(const char *psCFGFilename, char *psDest, int iDestSize); + qhandle_t (*RegisterShader)( const char *name ); + qhandle_t (*RegisterShaderNoMip)( const char *name ); + void (*LoadWorld)( const char *name ); + + // these two functions added to help with the new model alloc scheme... + // + void (*RegisterMedia_LevelLoadBegin)(const char *psMapName, ForceReload_e eForceReload, qboolean bAllowScreenDissolve); + void (*RegisterMedia_LevelLoadEnd)(void); + + // the vis data is a large enough block of data that we go to the trouble + // of sharing it with the clipmodel subsystem +#ifdef _XBOX + void (*SetWorldVisData)( SPARC *vis ); +#else + void (*SetWorldVisData)( const byte *vis ); +#endif + + // EndRegistration will draw a tiny polygon with each texture, forcing + // them to be loaded into card memory + void (*EndRegistration)( void ); + + // a scene is built up by calls to R_ClearScene and the various R_Add functions. + // Nothing is drawn until R_RenderScene is called. + void (*ClearScene)( void ); + void (*AddRefEntityToScene)( const refEntity_t *re ); + void (*AddPolyToScene)( qhandle_t hShader , int numVerts, const polyVert_t *verts ); + void (*AddLightToScene)( const vec3_t org, float intensity, float r, float g, float b ); + void (*RenderScene)( const refdef_t *fd ); + qboolean(*GetLighting)( const vec3_t org, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir); + + void (*SetColor)( const float *rgba ); // NULL = 1,1,1,1 + void (*DrawStretchPic) ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); // 0 = white + void (*DrawRotatePic) ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, float a1, qhandle_t hShader ); // 0 = white + void (*DrawRotatePic2) ( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, float a1, qhandle_t hShader ); // 0 = white + void (*LAGoggles)(void); + void (*Scissor) ( float x, float y, float w, float h); // 0 = white + + // Draw images for cinematic rendering, pass as 32 bit rgba + void (*DrawStretchRaw) (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty); + void (*UploadCinematic) (int cols, int rows, const byte *data, int client, qboolean dirty); + + void (*BeginFrame)( stereoFrame_t stereoFrame ); + + // if the pointers are not NULL, timing info will be returned + void (*EndFrame)( int *frontEndMsec, int *backEndMsec ); + + qboolean (*ProcessDissolve)(void); + qboolean (*InitDissolve)(qboolean bForceCircularExtroWipe); + + + // for use with save-games mainly... + void (*GetScreenShot)(byte *data, int w, int h); + + // this is so you can get access to raw pixels from a graphics format (TGA/JPG/BMP etc), + // currently only the save game uses it (to make raw shots for the autosaves) + // + byte* (*TempRawImage_ReadFromFile)(const char *psLocalFilename, int *piWidth, int *piHeight, byte *pbReSampleBuffer, qboolean qbVertFlip); + void (*TempRawImage_CleanUp)(); + + //misc stuff + int (*MarkFragments)( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + + //model stuff + void (*LerpTag)( orientation_t *tag, qhandle_t model, int startFrame, int endFrame, + float frac, const char *tagName ); + void (*ModelBounds)( qhandle_t model, vec3_t mins, vec3_t maxs ); + + void (*GetLightStyle)(int style, color4ub_t color); + void (*SetLightStyle)(int style, int color); + + void (*GetBModelVerts)( int bmodelIndex, vec3_t *vec, vec3_t normal ); + void (*WorldEffectCommand)(const char *command); + + int (*RegisterFont)(const char *name); +#ifdef _XBOX // No default arguments through function pointers. + int Font_HeightPixels(const int index, const float scale = 1.0f) + { + return RE_Font_HeightPixels(index, scale); + } + int Font_StrLenPixels(const char *s, const int index, const float scale = 1.0f) + { + return RE_Font_StrLenPixels(s, index, scale); + } + void Font_DrawString(int x, int y, const char *s, const float *rgba, const int iFontHandle, int iMaxPixelWidth, const float scale = 1.0f) + { + return RE_Font_DrawString(x, y, s, rgba, iFontHandle, iMaxPixelWidth, scale); + } +#else + int (*Font_HeightPixels)(const int index, const float scale = 1.0f); + int (*Font_StrLenPixels)(const char *s, const int index, const float scale = 1.0f); + void (*Font_DrawString)(int x, int y, const char *s, const float *rgba, const int iFontHandle, int iMaxPixelWidth, const float scale = 1.0f); +#endif + int (*Font_StrLenChars) (const char *s); + qboolean (*Language_IsAsian) (void); + qboolean (*Language_UsesSpaces) (void); + unsigned int (*AnyLanguage_ReadCharFromString)( const char *psText, int * piAdvanceCount, qboolean *pbIsTrailingPunctuation /* = NULL */); + +} refexport_t; + + +// this is the only function actually exported at the linker level +// If the module can't init to a valid rendering state, NULL will be +// returned. +refexport_t*GetRefAPI( int apiVersion ); + +#endif // __TR_PUBLIC_H diff --git a/code/renderer/tr_quicksprite.cpp b/code/renderer/tr_quicksprite.cpp new file mode 100644 index 0000000..1d20812 --- /dev/null +++ b/code/renderer/tr_quicksprite.cpp @@ -0,0 +1,229 @@ +// tr_QuickSprite.cpp: implementation of the CQuickSpriteSystem class. +// +////////////////////////////////////////////////////////////////////// +#include "../server/exe_headers.h" +#include "tr_QuickSprite.h" + +extern void R_BindAnimatedImage( const textureBundle_t *bundle ); + + +////////////////////////////////////////////////////////////////////// +// Singleton System +////////////////////////////////////////////////////////////////////// +CQuickSpriteSystem SQuickSprite; + + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CQuickSpriteSystem::CQuickSpriteSystem(void) +{ + int i; + + for (i = 0; i < SHADER_MAX_VERTEXES; i += 4) + { + // Bottom right + mTextureCoords[i + 0][0] = 1.0; + mTextureCoords[i + 0][1] = 1.0; + // Top right + mTextureCoords[i + 1][0] = 1.0; + mTextureCoords[i + 1][1] = 0.0; + // Top left + mTextureCoords[i + 2][0] = 0.0; + mTextureCoords[i + 2][1] = 0.0; + // Bottom left + mTextureCoords[i + 3][0] = 0.0; + mTextureCoords[i + 3][1] = 1.0; + } +} + +CQuickSpriteSystem::~CQuickSpriteSystem(void) +{ +} + +void CQuickSpriteSystem::Flush(void) +{ + if (mNextVert==0) + { + return; + } + + /* + if (mUseFog && r_drawfog->integer == 2 && + mFogIndex == tr.world->globalFog) + { //enable hardware fog when we draw this thing if applicable -rww + fog_t *fog = tr.world->fogs + mFogIndex; + +#ifdef _XBOX + qglFogi(GL_FOG_MODE, GL_EXP2); +#else + qglFogf(GL_FOG_MODE, GL_EXP2); +#endif + qglFogf(GL_FOG_DENSITY, logtestExp2 / fog->parms.depthForOpaque); + qglFogfv(GL_FOG_COLOR, fog->parms.color); + qglEnable(GL_FOG); + } + */ + //this should not be needed, since I just wait to disable fog for the surface til after surface sprites are done + + // + // render the main pass + // + R_BindAnimatedImage( mTexBundle ); + GL_State(mGLStateBits); + + // + // set arrays and lock + // + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + qglTexCoordPointer( 2, GL_FLOAT, 0, mTextureCoords ); + + qglEnableClientState( GL_COLOR_ARRAY); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, mColors ); + + qglVertexPointer (3, GL_FLOAT, 16, mVerts); + + if ( qglLockArraysEXT ) + { + qglLockArraysEXT(0, mNextVert); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + qglDrawArrays(GL_QUADS, 0, mNextVert); + + backEnd.pc.c_vertexes += mNextVert; + backEnd.pc.c_indexes += mNextVert; + backEnd.pc.c_totalIndexes += mNextVert; + + //only for software fog pass (global soft/volumetric) -rww + if (mUseFog && (r_drawfog->integer != 2 || mFogIndex != tr.world->globalFog)) + { + fog_t *fog = tr.world->fogs + mFogIndex; + + // + // render the fog pass + // + GL_Bind( tr.fogImage ); + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + + // + // set arrays and lock + // + qglTexCoordPointer( 2, GL_FLOAT, 0, mFogTextureCoords); +// qglEnableClientState( GL_TEXTURE_COORD_ARRAY); // Done above + + qglDisableClientState( GL_COLOR_ARRAY ); + qglColor4ubv((GLubyte *)&fog->colorInt); + +// qglVertexPointer (3, GL_FLOAT, 16, mVerts); // Done above + + qglDrawArrays(GL_QUADS, 0, mNextVert); + + // Second pass from fog + backEnd.pc.c_totalIndexes += mNextVert; + } + + // + // unlock arrays + // + if (qglUnlockArraysEXT) + { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + + mNextVert=0; +} + + +void CQuickSpriteSystem::StartGroup(textureBundle_t *bundle, unsigned long glbits, int fogIndex ) +{ + mNextVert = 0; + + mTexBundle = bundle; + mGLStateBits = glbits; + if (fogIndex != -1) + { + mUseFog = qtrue; + mFogIndex = fogIndex; + } + else + { + mUseFog = qfalse; + } + + int cullingOn; + qglGetIntegerv(GL_CULL_FACE,&cullingOn); + + if(cullingOn) + { + mTurnCullBackOn=true; + } + else + { + mTurnCullBackOn=false; + } + qglDisable(GL_CULL_FACE); +} + + +void CQuickSpriteSystem::EndGroup(void) +{ + Flush(); + + qglColor4ub(255,255,255,255); + if(mTurnCullBackOn) + { + qglEnable(GL_CULL_FACE); + } +} + + + + +void CQuickSpriteSystem::Add(float *pointdata, color4ub_t color, vec2_t fog) +{ + float *curcoord; + float *curfogtexcoord; + unsigned long *curcolor; + + if (mNextVert>SHADER_MAX_VERTEXES-4) + { + Flush(); + } + + curcoord = mVerts[mNextVert]; + memcpy(curcoord, pointdata, 4*sizeof(vec4_t)); + + // Set up color + curcolor = &mColors[mNextVert]; + *curcolor++ = *(unsigned long *)color; + *curcolor++ = *(unsigned long *)color; + *curcolor++ = *(unsigned long *)color; + *curcolor++ = *(unsigned long *)color; + + if (fog) + { + curfogtexcoord = &mFogTextureCoords[mNextVert][0]; + *curfogtexcoord++ = fog[0]; + *curfogtexcoord++ = fog[1]; + + *curfogtexcoord++ = fog[0]; + *curfogtexcoord++ = fog[1]; + + *curfogtexcoord++ = fog[0]; + *curfogtexcoord++ = fog[1]; + + *curfogtexcoord++ = fog[0]; + *curfogtexcoord++ = fog[1]; + + mUseFog=qtrue; + } + else + { + mUseFog=qfalse; + } + + mNextVert+=4; +} diff --git a/code/renderer/tr_quicksprite.h b/code/renderer/tr_quicksprite.h new file mode 100644 index 0000000..7a277c0 --- /dev/null +++ b/code/renderer/tr_quicksprite.h @@ -0,0 +1,48 @@ +// this include must remain at the top of every CPP file +//#include "../game/q_math.h" +#include "tr_local.h" + +// tr_QuickSprite.h: interface for the CQuickSprite class. +// +////////////////////////////////////////////////////////////////////// + +#if !defined(AFX_TR_QUICKSPRITE_H__6F05EB85_A1ED_4537_9EC0_9F5D82A5D99A__INCLUDED_) +#define AFX_TR_QUICKSPRITE_H__6F05EB85_A1ED_4537_9EC0_9F5D82A5D99A__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +class CQuickSpriteSystem +{ +private: + textureBundle_t *mTexBundle; + unsigned long mGLStateBits; + int mFogIndex; + qboolean mUseFog; + vec4_t mVerts[SHADER_MAX_VERTEXES]; + unsigned int mIndexes[SHADER_MAX_VERTEXES]; // Ideally this would be static, cause it never changes + vec2_t mTextureCoords[SHADER_MAX_VERTEXES]; // Ideally this would be static, cause it never changes + vec2_t mFogTextureCoords[SHADER_MAX_VERTEXES]; + unsigned long mColors[SHADER_MAX_VERTEXES]; + int mNextVert; + qboolean mTurnCullBackOn; + + void Flush(void); + +public: + CQuickSpriteSystem(void); + ~CQuickSpriteSystem(void); + + void StartGroup(textureBundle_t *bundle, unsigned long glbits, int fogIndex = -1); + void EndGroup(void); + + void Add(float *pointdata, color4ub_t color, vec2_t fog=NULL); +}; + +extern CQuickSpriteSystem SQuickSprite; + + +#endif // !defined(AFX_TR_QUICKSPRITE_H__6F05EB85_A1ED_4537_9EC0_9F5D82A5D99A__INCLUDED_) + + diff --git a/code/renderer/tr_scene.cpp b/code/renderer/tr_scene.cpp new file mode 100644 index 0000000..3fabb28 --- /dev/null +++ b/code/renderer/tr_scene.cpp @@ -0,0 +1,405 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "tr_local.h" + +#ifdef VV_LIGHTING +#include "tr_lightmanager.h" +#endif + +int r_firstSceneDrawSurf; + +int r_numdlights; +int r_firstSceneDlight; + +int r_numentities; +int r_firstSceneEntity; + +int r_numpolys; +int r_firstScenePoly; + +int r_numpolyverts; + +int skyboxportal; +int drawskyboxportal; + +/* +==================== +R_ToggleSmpFrame + +==================== +*/ +void R_ToggleSmpFrame( void ) { + + backEndData->commands.used = 0; + + r_firstSceneDrawSurf = 0; + + r_numdlights = 0; + r_firstSceneDlight = 0; + +#ifdef VV_LIGHTING + VVLightMan.num_dlights = 0; +#endif + + r_numentities = 0; + r_firstSceneEntity = 0; + + r_numpolys = 0; + r_firstScenePoly = 0; + + r_numpolyverts = 0; +} + + +/* +==================== +RE_ClearScene + +==================== +*/ +void RE_ClearScene( void ) { + r_firstSceneDlight = r_numdlights; + r_firstSceneEntity = r_numentities; + r_firstScenePoly = r_numpolys; + tr.refdef.rdflags &= ~(RDF_doLAGoggles|RDF_doFullbright); //probably not needed since it gets copied over in RE_RenderScene +} + +/* +=========================================================================== + +DISCRETE POLYS + +=========================================================================== +*/ + +/* +===================== +R_AddPolygonSurfaces + +Adds all the scene's polys into this view's drawsurf list +===================== +*/ +void R_AddPolygonSurfaces( void ) { + int i; + shader_t *sh; + srfPoly_t *poly; + + tr.currentEntityNum = TR_WORLDENT; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + for ( i = 0, poly = tr.refdef.polys; i < tr.refdef.numPolys ; i++, poly++ ) { + sh = R_GetShaderByHandle( poly->hShader ); + R_AddDrawSurf( ( surfaceType_t * )poly, sh, poly->fogIndex, qfalse ); + } +} + +/* +===================== +RE_AddPolyToScene + +===================== +*/ +void RE_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { + srfPoly_t *poly; + int i; + int fogIndex = 0; + fog_t *fog; + vec3_t bounds[2]; + + if ( !tr.registered ) { + return; + } + + if ( !hShader ) { +#ifndef FINAL_BUILD + VID_Printf( PRINT_WARNING, "WARNING: RE_AddPolyToScene: NULL poly shader\n"); +#endif + return; + } + + if ( r_numpolyverts + numVerts > MAX_POLYVERTS || r_numpolys >= MAX_POLYS ) { +#if defined(_DEBUG) + Com_Printf(S_COLOR_RED"Poly overflow! Tell Brian.\n"); +#endif + return; + } + + poly = &backEndData->polys[r_numpolys]; + poly->surfaceType = SF_POLY; + poly->hShader = hShader; + poly->numVerts = numVerts; + poly->verts = &backEndData->polyVerts[r_numpolyverts]; + + memcpy( poly->verts, verts, numVerts * sizeof( *verts ) ); + r_numpolys++; + r_numpolyverts += numVerts; + + // see if it is in a fog volume + if ( !tr.world || tr.world->numfogs == 1) { + fogIndex = 0; + } else { + // find which fog volume the poly is in + VectorCopy( poly->verts[0].xyz, bounds[0] ); + VectorCopy( poly->verts[0].xyz, bounds[1] ); + for ( i = 1 ; i < poly->numVerts ; i++ ) { + AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] ); + } + for ( int fI = 1 ; fI < tr.world->numfogs ; fI++ ) { + fog = &tr.world->fogs[fI]; + if ( bounds[0][0] >= fog->bounds[0][0] + && bounds[0][1] >= fog->bounds[0][1] + && bounds[0][2] >= fog->bounds[0][2] + && bounds[1][0] <= fog->bounds[1][0] + && bounds[1][1] <= fog->bounds[1][1] + && bounds[1][2] <= fog->bounds[1][2] ) + {//completely in this one + fogIndex = fI; + break; + } + else if ( ( bounds[0][0] >= fog->bounds[0][0] && bounds[0][1] >= fog->bounds[0][1] && bounds[0][2] >= fog->bounds[0][2] && + bounds[0][0] <= fog->bounds[1][0] && bounds[0][1] <= fog->bounds[1][1] && bounds[0][2] <= fog->bounds[1][2]) || + ( bounds[1][0] >= fog->bounds[0][0] && bounds[1][1] >= fog->bounds[0][1] && bounds[1][2] >= fog->bounds[0][2] && + bounds[1][0] <= fog->bounds[1][0] && bounds[1][1] <= fog->bounds[1][1] && bounds[1][2] <= fog->bounds[1][2] ) ) + {//partially in this one + if ( tr.refdef.fogIndex == fI || R_FogParmsMatch( tr.refdef.fogIndex, fI ) ) + {//take new one only if it's the same one that the viewpoint is in + fogIndex = fI; + break; + } + else if ( !fogIndex ) + {//didn't find one yet, so use this one + fogIndex = fI; + } + } + } + } + poly->fogIndex = fogIndex; +} + + +//================================================================================= + + +/* +===================== +RE_AddRefEntityToScene + +===================== +*/ +void RE_AddRefEntityToScene( const refEntity_t *ent ) { + if ( !tr.registered ) { + return; + } + if ( r_numentities >= TR_WORLDENT ) { +#ifndef FINAL_BUILD + VID_Printf( PRINT_WARNING, "WARNING: RE_AddRefEntityToScene: too many entities\n"); +#endif + return; + } + if ( ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) { + Com_Error( ERR_DROP, "RE_AddRefEntityToScene: bad reType %i", ent->reType ); + } + + backEndData->entities[r_numentities].e = *ent; + backEndData->entities[r_numentities].lightingCalculated = qfalse; +#ifdef _XBOX + backEndData->entities[r_numentities].visible = -1; +#endif + + r_numentities++; +} + + +/* +===================== +RE_AddLightToScene + +===================== +*/ +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { +#ifndef VV_LIGHTING + dlight_t *dl; + + if ( !tr.registered ) { + return; + } + if ( r_numdlights >= MAX_DLIGHTS ) { + return; + } + if ( intensity <= 0 ) { + return; + } + dl = &backEndData->dlights[r_numdlights++]; + VectorCopy (org, dl->origin); + dl->radius = intensity; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; +#endif // VV_LIGHTING +} + + +/* +@@@@@@@@@@@@@@@@@@@@@ +RE_RenderScene + +Draw a 3D view into a part of the window, then return +to 2D drawing. + +Rendering a scene may require multiple views to be rendered +to handle mirrors, +@@@@@@@@@@@@@@@@@@@@@ +*/ +extern int recursivePortalCount; +void RE_RenderScene( const refdef_t *fd ) { + viewParms_t parms; + int startTime; + static int lastTime = 0; + + if ( !tr.registered ) { + return; + } + GLimp_LogComment( "====== RE_RenderScene =====\n" ); + + if ( r_norefresh->integer ) { + return; + } + + startTime = Sys_Milliseconds(); + + if (!tr.world && !( fd->rdflags & RDF_NOWORLDMODEL ) ) { + Com_Error (ERR_DROP, "R_RenderScene: NULL worldmodel"); + } + +// memcpy( tr.refdef.text, fd->text, sizeof( tr.refdef.text ) ); + + tr.refdef.x = fd->x; + tr.refdef.y = fd->y; + tr.refdef.width = fd->width; + tr.refdef.height = fd->height; + tr.refdef.fov_x = fd->fov_x; + tr.refdef.fov_y = fd->fov_y; + + VectorCopy( fd->vieworg, tr.refdef.vieworg ); + VectorCopy( fd->viewaxis[0], tr.refdef.viewaxis[0] ); + VectorCopy( fd->viewaxis[1], tr.refdef.viewaxis[1] ); + VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] ); + + tr.refdef.time = fd->time; + tr.refdef.frametime = fd->time - lastTime; + tr.refdef.rdflags = fd->rdflags; + + if (fd->rdflags & RDF_SKYBOXPORTAL) + { + skyboxportal = 1; + } + else + { + // cdr - only change last time for the real render, not the portal + lastTime = fd->time; + } + + if (fd->rdflags & RDF_DRAWSKYBOX) + { + drawskyboxportal = 1; + } + else + { + drawskyboxportal = 0; + } + + // copy the areamask data over and note if it has changed, which + // will force a reset of the visible leafs even if the view hasn't moved + tr.refdef.areamaskModified = qfalse; + if ( ! (tr.refdef.rdflags & RDF_NOWORLDMODEL) ) { + int areaDiff; + int i; + + // compare the area bits + areaDiff = 0; + for (i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++) { + areaDiff |= ((int *)tr.refdef.areamask)[i] ^ ((int *)fd->areamask)[i]; + ((int *)tr.refdef.areamask)[i] = ((int *)fd->areamask)[i]; + } + + if ( areaDiff ) { + // a door just opened or something + tr.refdef.areamaskModified = qtrue; + } + } + + + // derived info + + tr.refdef.floatTime = tr.refdef.time * 0.001; + + tr.refdef.numDrawSurfs = r_firstSceneDrawSurf; + tr.refdef.drawSurfs = backEndData->drawSurfs; + + tr.refdef.num_entities = r_numentities - r_firstSceneEntity; + tr.refdef.entities = &backEndData->entities[r_firstSceneEntity]; + +#ifndef VV_LIGHTING + tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight; + tr.refdef.dlights = &backEndData->dlights[r_firstSceneDlight]; +#endif + + tr.refdef.numPolys = r_numpolys - r_firstScenePoly; + tr.refdef.polys = &backEndData->polys[r_firstScenePoly]; + + // turn off dynamic lighting globally by clearing all the + // dlights if it needs to be disabled or if vertex lighting is enabled +#ifndef VV_LIGHTING + if ( r_dynamiclight->integer == 0 || + r_vertexLight->integer == 1 ) { + tr.refdef.num_dlights = 0; + } +#endif + + // a single frame may have multiple scenes draw inside it -- + // a 3D game view, 3D status bar renderings, 3D menus, etc. + // They need to be distinguished by the light flare code, because + // the visibility state for a given surface may be different in + // each scene / view. + tr.frameSceneNum++; + tr.sceneCount++; + + // setup view parms for the initial view + // + // set up viewport + // The refdef takes 0-at-the-top y coordinates, so + // convert to GL's 0-at-the-bottom space + // + memset( &parms, 0, sizeof( parms ) ); + parms.viewportX = tr.refdef.x; + parms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.refdef.height ); + parms.viewportWidth = tr.refdef.width; + parms.viewportHeight = tr.refdef.height; + parms.isPortal = qfalse; + + parms.fovX = tr.refdef.fov_x; + parms.fovY = tr.refdef.fov_y; + + VectorCopy( fd->vieworg, parms.or.origin ); + VectorCopy( fd->viewaxis[0], parms.or.axis[0] ); + VectorCopy( fd->viewaxis[1], parms.or.axis[1] ); + VectorCopy( fd->viewaxis[2], parms.or.axis[2] ); + + VectorCopy( fd->vieworg, parms.pvsOrigin ); + + recursivePortalCount = 0; + R_RenderView( &parms ); + + // the next scene rendered in this frame will tack on after this one + r_firstSceneDrawSurf = tr.refdef.numDrawSurfs; + r_firstSceneEntity = r_numentities; + r_firstSceneDlight = r_numdlights; + r_firstScenePoly = r_numpolys; + + tr.frontEndMsec += Sys_Milliseconds() - startTime; + RE_RenderWorldEffects(); +} diff --git a/code/renderer/tr_shade.cpp b/code/renderer/tr_shade.cpp new file mode 100644 index 0000000..2edbe33 --- /dev/null +++ b/code/renderer/tr_shade.cpp @@ -0,0 +1,2821 @@ +// tr_shade.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "tr_local.h" + +#ifdef VV_LIGHTING +#include "tr_lightmanager.h" +#include "../win32/glw_win_dx8.h" +#include "../win32/win_lighteffects.h" +#endif + +/* + + THIS ENTIRE FILE IS BACK END + + This file deals with applying shaders to surface data in the tess struct. +*/ + +shaderCommands_t tess; +static qboolean setArraysOnce; + +color4ub_t styleColors[MAX_LIGHT_STYLES]; +bool styleUpdated[MAX_LIGHT_STYLES]; + +extern bool g_bRenderGlowingObjects; + +/* +================ +R_ArrayElementDiscrete + +This is just for OpenGL conformance testing, it should never be the fastest +================ +*/ +static void APIENTRY R_ArrayElementDiscrete( GLint index ) { +#ifndef _XBOX + qglColor4ubv( tess.svars.colors[ index ] ); + if ( glState.currenttmu ) { + qglMultiTexCoord2fARB( 0, tess.svars.texcoords[ 0 ][ index ][0], tess.svars.texcoords[ 0 ][ index ][1] ); + qglMultiTexCoord2fARB( 1, tess.svars.texcoords[ 1 ][ index ][0], tess.svars.texcoords[ 1 ][ index ][1] ); + } else { + qglTexCoord2fv( tess.svars.texcoords[ 0 ][ index ] ); + } + qglVertex3fv( tess.xyz[ index ] ); +#endif +} + +/* +=================== +R_DrawStripElements + +=================== +*/ +static int c_vertexes; // for seeing how long our average strips are +static int c_begins; +static void R_DrawStripElements( int numIndexes, const glIndex_t *indexes, void ( APIENTRY *element )(GLint) ) { + int i; + glIndex_t last[3]; + qboolean even; + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + if ( numIndexes <= 0 ) { + return; + } + + // prime the strip + element( indexes[0] ); + element( indexes[1] ); + element( indexes[2] ); + c_vertexes += 3; + + last[0] = indexes[0]; + last[1] = indexes[1]; + last[2] = indexes[2]; + + even = qfalse; + + for ( i = 3; i < numIndexes; i += 3 ) + { + // odd numbered triangle in potential strip + if ( !even ) + { + // check previous triangle to see if we're continuing a strip + if ( ( indexes[i+0] == last[2] ) && ( indexes[i+1] == last[1] ) ) + { + element( indexes[i+2] ); + c_vertexes++; + assert( indexes[i+2] < tess.numVertexes ); + even = qtrue; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + qglEnd(); + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + element( indexes[i+0] ); + element( indexes[i+1] ); + element( indexes[i+2] ); + + c_vertexes += 3; + + even = qfalse; + } + } + else + { + // check previous triangle to see if we're continuing a strip + if ( ( last[2] == indexes[i+1] ) && ( last[0] == indexes[i+0] ) ) + { + element( indexes[i+2] ); + c_vertexes++; + + even = qfalse; + } + // otherwise we're done with this strip so finish it and start + // a new one + else + { + qglEnd(); + + qglBegin( GL_TRIANGLE_STRIP ); + c_begins++; + + element( indexes[i+0] ); + element( indexes[i+1] ); + element( indexes[i+2] ); + c_vertexes += 3; + + even = qfalse; + } + } + + // cache the last three vertices + last[0] = indexes[i+0]; + last[1] = indexes[i+1]; + last[2] = indexes[i+2]; + } + + qglEnd(); +} + +#ifdef _XBOX +qboolean RB_IsCurrentShaderTransparent( void ); +#endif + +/* +================== +R_DrawElements + +Optionally performs our own glDrawElements that looks for strip conditions +instead of using the single glDrawElements call that may be inefficient +without compiled vertex arrays. +================== +*/ +static void R_DrawElements( int numIndexes, const glIndex_t *indexes ) { + int primitives; + + primitives = r_primitives->integer; + + // default is to use triangles if compiled vertex arrays are present + if ( primitives == 0 ) { + if ( qglLockArraysEXT ) { + primitives = 2; + } else { + primitives = 1; + } + } + + + if ( primitives == 2 ) { +#ifdef _XBOX +// if (tess.useConstantColor) +// { +// qglDisableClientState( GL_COLOR_ARRAY ); +// qglColor4ubv( tess.constantColor ); +// } +#endif + qglDrawElements( GL_TRIANGLES, + numIndexes, + GL_INDEX_TYPE, + indexes ); + return; + } + +#ifdef _XBOX + if (primitives == 1 || primitives == 3) + { +// if (tess.useConstantColor) +// { +// qglDisableClientState( GL_COLOR_ARRAY ); +// qglColor4ubv( tess.constantColor ); +// } + /*qglDrawElements( GL_TRIANGLES, + numIndexes, + GL_INDEX_TYPE, + indexes );*/ +#if 1 // VVFIXME : Temporary solution to try and increase framerate + //qglIndexedTriToStrip( numIndexes, indexes ); + + if(strstr(tess.shader->name, "terrain")) { + qglIndexedTriToStrip( numIndexes, indexes ); + } + else + qglDrawElements( GL_TRIANGLES, + numIndexes, + GL_INDEX_TYPE, + indexes ); +#endif + + return; + } +#else // _XBOX + if ( primitives == 1 ) { + R_DrawStripElements( numIndexes, indexes, qglArrayElement ); + return; + } + + if ( primitives == 3 ) { + R_DrawStripElements( numIndexes, indexes, R_ArrayElementDiscrete ); + return; + } +#endif // _XBOX + + // anything else will cause no drawing +} + + + + + +/* +============================================================= + +SURFACE SHADERS + +============================================================= +*/ + + +/* +================= +R_BindAnimatedImage + +================= +*/ +void R_BindAnimatedImage( const textureBundle_t *bundle) { + int index; + + if ( bundle->isVideoMap ) { + CIN_RunCinematic(bundle->videoMapHandle); + CIN_UploadCinematic(bundle->videoMapHandle); + return; + } + + if ((r_fullbright->integer || (tr.refdef.rdflags & RDF_doFullbright) ) && bundle->isLightmap) + { + GL_Bind( tr.whiteImage ); + return; + } + + if ( bundle->numImageAnimations <= 1 ) { + GL_Bind( bundle->image ); + return; + } + + if (backEnd.currentEntity->e.renderfx & RF_SETANIMINDEX ) + { + index = backEnd.currentEntity->e.skinNum; + } + else + { + // it is necessary to do this messy calc to make sure animations line up + // exactly with waveforms of the same frequency + index = myftol( backEnd.refdef.floatTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE ); + index >>= FUNCTABLE_SIZE2; + + if ( index < 0 ) { + index = 0; // may happen with shader time offsets + } + } + + if ( bundle->oneShotAnimMap ) + { + if ( index >= bundle->numImageAnimations ) + { + // stick on last frame + index = bundle->numImageAnimations - 1; + } + } + else + { + // loop + index %= bundle->numImageAnimations; + } + + GL_Bind( *((image_t**)bundle->image + index) ); +} + + +/* +================ +DrawTris + +Draws triangle outlines for debugging +================ +*/ +static void DrawTris (shaderCommands_t *input) +{ + GL_Bind( tr.whiteImage ); + + if ( r_showtriscolor->integer ) + { + int i = r_showtriscolor->integer; + if (i == 42) { + i = Q_irand(0,8); + } + switch (i) + { + case 1: + qglColor3f( 1.0, 0.0, 0.0); //red + break; + case 2: + qglColor3f( 0.0, 1.0, 0.0); //green + break; + case 3: + qglColor3f( 1.0, 1.0, 0.0); //yellow + break; + case 4: + qglColor3f( 0.0, 0.0, 1.0); //blue + break; + case 5: + qglColor3f( 0.0, 1.0, 1.0); //cyan + break; + case 6: + qglColor3f( 1.0, 0.0, 1.0); //magenta + break; + case 7: + qglColor3f( 0.8f, 0.8f, 0.8f); //white/grey + break; + case 8: + qglColor3f( 0.0, 0.0, 0.0); //black + break; + } + } + else + { + qglColor3f( 1.0, 1.0, 1.0); //white + } + + if ( r_showtris->integer == 2 ) + { + // tries to do non-xray style showtris + GL_State( GLS_POLYMODE_LINE ); + + qglEnable( GL_POLYGON_OFFSET_LINE ); + qglPolygonOffset( -1, -2 ); + + qglDisableClientState( GL_COLOR_ARRAY ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + qglVertexPointer( 3, GL_FLOAT, 16, input->xyz ); // padded for SIMD + + if ( qglLockArraysEXT ) + { + qglLockArraysEXT( 0, input->numVertexes ); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + R_DrawElements( input->numIndexes, input->indexes ); + + if ( qglUnlockArraysEXT ) + { + qglUnlockArraysEXT( ); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + + qglDisable( GL_POLYGON_OFFSET_LINE ); + } + else + { + // same old showtris + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + qglDepthRange( 0, 0 ); + + qglDisableClientState (GL_COLOR_ARRAY); + qglDisableClientState (GL_TEXTURE_COORD_ARRAY); + + qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD + + if (qglLockArraysEXT) { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + R_DrawElements( input->numIndexes, input->indexes ); + + if (qglUnlockArraysEXT) { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + + qglDepthRange( 0, 1 ); + } +} + +/* +================ +DrawNormals + +Draws vertex normals for debugging +================ +*/ +static void DrawNormals (shaderCommands_t *input) { + int i; + vec3_t temp; + + GL_Bind( tr.whiteImage ); + qglColor3f (1,1,1); + qglDepthRange( 0, 0 ); // never occluded + GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE ); + + qglBegin (GL_LINES); + for (i = 0 ; i < input->numVertexes ; i++) { + qglVertex3fv (input->xyz[i]); + VectorMA (input->xyz[i], 2, input->normal[i], temp); + qglVertex3fv (temp); + } + qglEnd (); + + qglDepthRange( 0, 1 ); +} + + +/* +============== +RB_BeginSurface + +We must set some things up before beginning any tesselation, +because a surface may be forced to perform a RB_End due +to overflow. +============== +*/ +void RB_BeginSurface( shader_t *shader, int fogNum ) { +// shader_t *state = (shader->remappedShader) ? shader->remappedShader : shader; + shader_t *state = shader; + + tess.numIndexes = 0; + tess.numVertexes = 0; + tess.shader = state;//shader; + tess.fogNum = fogNum; + tess.dlightBits = 0; // will be OR'd in by surface functions + + tess.SSInitializedWind = qfalse; //is this right? + + tess.xstages = state->stages; + tess.numPasses = state->numUnfoggedPasses; + + tess.currentStageIteratorFunc = shader->sky ? RB_StageIteratorSky : RB_StageIteratorGeneric; + + tess.fading = false; + +#ifdef _XBOX + tess.setTangents = false; + tess.pXyz = NULL; + tess.pNormal = NULL; + tess.pColor = NULL; + tess.pTex1 = NULL; + tess.pTex2 = NULL; +#endif + + tess.registration++; +} + +/* +=================== +DrawMultitextured + +output = t0 * t1 or t0 + t1 + +t0 = most upstream according to spec +t1 = most downstream according to spec +=================== +*/ +static void DrawMultitextured( shaderCommands_t *input, int stage ) { + shaderStage_t *pStage; + + pStage = &tess.xstages[stage]; + + GL_State( pStage->stateBits ); + + // + // base + // + GL_SelectTexture( 0 ); + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + R_BindAnimatedImage( &pStage->bundle[0] ); + + // + // lightmap/secondary pass + // + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + + if ( r_lightmap->integer ) { + GL_TexEnv( GL_REPLACE ); + } else { + GL_TexEnv( tess.shader->multitextureEnv ); + } + + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[1] ); + + R_BindAnimatedImage( &pStage->bundle[1] ); + + R_DrawElements( input->numIndexes, input->indexes ); + + // + // disable texturing on TEXTURE1, then select TEXTURE0 + // + qglDisable( GL_TEXTURE_2D ); +#ifdef _XBOX + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); +#endif + + GL_SelectTexture( 0 ); +} + + +#ifdef VV_LIGHTING +static void BuildTangentVectors( void ) { + + memset(tess.tangent, 0, sizeof(vec4_t) * SHADER_MAX_VERTEXES); + + for(int i = 0; i < tess.numIndexes; i += 3) + { + vec3_t vec1, vec2, du, dv, cp; + + vec1[0] = tess.xyz[tess.indexes[i+1]][0] - tess.xyz[tess.indexes[i]][0]; + vec1[1] = tess.svars.texcoords[0][tess.indexes[i+1]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec1[2] = tess.svars.texcoords[0][tess.indexes[i+1]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + vec2[0] = tess.xyz[tess.indexes[i+2]][0] - tess.xyz[tess.indexes[i]][0]; + vec2[1] = tess.svars.texcoords[0][tess.indexes[i+2]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec2[2] = tess.svars.texcoords[0][tess.indexes[i+2]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[0] = -cp[1] / cp[0]; + dv[0] = -cp[2] / cp[0]; + + vec1[0] = tess.xyz[tess.indexes[i+1]][1] - tess.xyz[tess.indexes[i]][1]; + vec1[1] = tess.svars.texcoords[0][tess.indexes[i+1]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec1[2] = tess.svars.texcoords[0][tess.indexes[i+1]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + vec2[0] = tess.xyz[tess.indexes[i+2]][1] - tess.xyz[tess.indexes[i]][1]; + vec2[1] = tess.svars.texcoords[0][tess.indexes[i+2]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec2[2] = tess.svars.texcoords[0][tess.indexes[i+2]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[1] = -cp[1] / cp[0]; + dv[1] = -cp[2] / cp[0]; + + vec1[0] = tess.xyz[tess.indexes[i+1]][2] - tess.xyz[tess.indexes[i]][2]; + vec1[1] = tess.svars.texcoords[0][tess.indexes[i+1]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec1[2] = tess.svars.texcoords[0][tess.indexes[i+1]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + vec2[0] = tess.xyz[tess.indexes[i+2]][2] - tess.xyz[tess.indexes[i]][2]; + vec2[1] = tess.svars.texcoords[0][tess.indexes[i+2]][0] - tess.svars.texcoords[0][tess.indexes[i]][0]; + vec2[2] = tess.svars.texcoords[0][tess.indexes[i+2]][1] - tess.svars.texcoords[0][tess.indexes[i]][1]; + + CrossProduct(vec1, vec2, cp); + + if(cp[0] == 0.0f) + cp[0] = 0.001f; + + du[2] = -cp[1] / cp[0]; + dv[2] = -cp[2] / cp[0]; + + tess.tangent[tess.indexes[i]][0] += du[0]; + tess.tangent[tess.indexes[i]][1] += du[1]; + tess.tangent[tess.indexes[i]][2] += du[2]; + + tess.tangent[tess.indexes[i+1]][0] += du[0]; + tess.tangent[tess.indexes[i+1]][1] += du[1]; + tess.tangent[tess.indexes[i+1]][2] += du[2]; + + tess.tangent[tess.indexes[i+2]][0] += du[0]; + tess.tangent[tess.indexes[i+2]][1] += du[1]; + tess.tangent[tess.indexes[i+2]][2] += du[2]; + } + + for(i = 0; i < tess.numVertexes; i++) + { + VectorNormalizeFast(tess.tangent[i]); + } +} +#endif // VV_LIGHTING + +//--EF_old dlight code...reverting back to Quake III dlight to see if people like that better +// Lifted the whole function because someone hacked the heck out of this and it doesn't seem to +// be a case where it's as easy as just changing the blend mode.... +/* +=================== +ProjectDlightTexture + +Perform dynamic lighting with another rendering pass +=================== +*/ +/* +static void ProjectDlightTexture( void ) { + int l; + vec3_t origin; + float *texCoords; + byte *colors; + byte clipBits[SHADER_MAX_VERTEXES]; + MAC_STATIC float texCoordsArray[SHADER_MAX_VERTEXES][2]; + byte colorArray[SHADER_MAX_VERTEXES][4]; + unsigned hitIndexes[SHADER_MAX_INDEXES]; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + int numIndexes; + vec3_t floatColor; + float scale; + float radius, chord; + dlight_t *dl; + int i; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + texCoords = texCoordsArray[0]; + colors = colorArray[0]; + + dl = &backEnd.refdef.dlights[l]; + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + chord = radius*radius*0.25f; + scale = 1.0f / radius; + floatColor[0] = dl->color[0] * 255f; + floatColor[1] = dl->color[1] * 255f; + floatColor[2] = dl->color[2] * 255f; + + for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) { + vec3_t distVec; + int clip; + float tempColor; + float modulate, dist; + +// if ( 0 ) { +// clipBits[i] = 255; // definately not dlighted +// continue; +// } +// + backEnd.pc.c_dlightVertexes++; + + VectorSubtract( origin, tess.xyz[i], distVec ); + dist = VectorLengthSquared(distVec); + + texCoords[0] = 0.5 + distVec[0] * scale; //xy projection + texCoords[1] = 0.5 + distVec[1] * scale; + + clip = 0; + if ( texCoords[0] < 0 ) { + clip |= 1; + } else if ( texCoords[0] > 1 ) { + clip |= 2; + } + if ( texCoords[1] < 0 ) { + clip |= 4; + } else if ( texCoords[1] > 1 ) { + clip |= 8; + } + clipBits[i] = clip; + + // modulate the strength based on the height and color + if ( dist > chord) { + clip |= 16; + modulate = 255*1.0ff; + } else { + modulate = 255*2*dist*scale*scale; + } + tempColor = floatColor[0] + modulate; + colors[0] = tempColor > 255 ? 255: myftol(tempColor); + + tempColor = floatColor[1] + modulate; + colors[1] = tempColor > 255 ? 255: myftol(tempColor); + + tempColor = floatColor[2] + modulate; + colors[2] = tempColor > 255 ? 255: myftol(tempColor); + +// colors[3] = 255; + if ( distVec[2] > radius ) { + colors[3] = 0; + } else if ( distVec[2] < -radius ) { + colors[3] = 0; + } else { + if ( distVec[2] < 0 ) { + distVec[2] = -distVec[2]; + } + if ( distVec[2] < radius * 0.5 ) { + colors[3] = 255; + } else { + colors[3] = myftol(255* (radius - distVec[2]) * scale); + } + } + + } + + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i+1]; + c = tess.indexes[i+2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) { + continue; // not lighted + } + hitIndexes[numIndexes] = a; + hitIndexes[numIndexes+1] = b; + hitIndexes[numIndexes+2] = c; + numIndexes += 3; + } + + if ( !numIndexes ) { + continue; + } + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + + GL_Bind( tr.dlightImage ); + + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_SRC_COLOR | GLS_DEPTHFUNC_EQUAL);//our way +// GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); //Id way + R_DrawElements( numIndexes, hitIndexes ); + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } +} +*/ + +// Lifted from Quake III to see if people like this kind of dlight better +/* +=================== +ProjectDlightTexture + +Perform dynamic lighting with another rendering pass +=================== +*/ +#ifndef VV_LIGHTING +static void ProjectDlightTexture2( void ) { + int i, l; + vec3_t origin; + byte clipBits[SHADER_MAX_VERTEXES]; + MAC_STATIC float texCoordsArray[SHADER_MAX_VERTEXES][2]; + MAC_STATIC float oldTexCoordsArray[SHADER_MAX_VERTEXES][2]; + MAC_STATIC float vertCoordsArray[SHADER_MAX_VERTEXES][4]; + unsigned int colorArray[SHADER_MAX_VERTEXES]; + glIndex_t hitIndexes[SHADER_MAX_INDEXES]; + int numIndexes; + float radius; + int fogging; + shaderStage_t *dStage; + vec3_t posa; + vec3_t posb; + vec3_t posc; + vec3_t dist; + vec3_t e1; + vec3_t e2; + vec3_t normal; + float fac,modulate; + vec3_t floatColor; + byte colorTemp[4]; + + int needResetVerts=0; + + if ( !backEnd.refdef.num_dlights ) + { + return; + } + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) + { + dlight_t *dl; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + + dl = &backEnd.refdef.dlights[l]; + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + + int clipall = 63; + for ( i = 0 ; i < tess.numVertexes ; i++) + { + int clip; + VectorSubtract( origin, tess.xyz[i], dist ); + + clip = 0; + if ( dist[0] < -radius ) + { + clip |= 1; + } + else if ( dist[0] > radius ) + { + clip |= 2; + } + if ( dist[1] < -radius ) + { + clip |= 4; + } + else if ( dist[1] > radius ) + { + clip |= 8; + } + if ( dist[2] < -radius ) + { + clip |= 16; + } + else if ( dist[2] > radius ) + { + clip |= 32; + } + + clipBits[i] = clip; + clipall &= clip; + } + if ( clipall ) + { + continue; // this surface doesn't have any of this light + } + floatColor[0] = dl->color[0] * 255.0f; + floatColor[1] = dl->color[1] * 255.0f; + floatColor[2] = dl->color[2] * 255.0f; + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) + { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i+1]; + c = tess.indexes[i+2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) + { + continue; // not lighted + } + + // copy the vertex positions + VectorCopy(tess.xyz[a],posa); + VectorCopy(tess.xyz[b],posb); + VectorCopy(tess.xyz[c],posc); + + VectorSubtract( posa, posb,e1); + VectorSubtract( posc, posb,e2); + CrossProduct(e1,e2,normal); +// fac=DotProduct(normal,origin)-DotProduct(normal,posa); +// if (fac <= 0.0f || // backface + if ( (!r_dlightBacks->integer && DotProduct(normal,origin)-DotProduct(normal,posa) <= 0.0f) || // backface + DotProduct(normal,normal) < 1E-8f) // junk triangle + { + continue; + } + VectorNormalize(normal); + fac=DotProduct(normal,origin)-DotProduct(normal,posa); + if (fac >= radius) // out of range + { + continue; + } + modulate = 1.0f-((fac*fac) / (radius*radius)); + fac = 0.5f/sqrtf(radius*radius - fac*fac); + + // save the verts + VectorCopy(posa,vertCoordsArray[numIndexes]); + VectorCopy(posb,vertCoordsArray[numIndexes+1]); + VectorCopy(posc,vertCoordsArray[numIndexes+2]); + + // now we need e1 and e2 to be an orthonormal basis + if (DotProduct(e1,e1) > DotProduct(e2,e2)) + { + VectorNormalize(e1); + CrossProduct(e1,normal,e2); + } + else + { + VectorNormalize(e2); + CrossProduct(normal,e2,e1); + } + VectorScale(e1,fac,e1); + VectorScale(e2,fac,e2); + + VectorSubtract( posa, origin,dist); + texCoordsArray[numIndexes][0]=DotProduct(dist,e1)+0.5f; + texCoordsArray[numIndexes][1]=DotProduct(dist,e2)+0.5f; + + VectorSubtract( posb, origin,dist); + texCoordsArray[numIndexes+1][0]=DotProduct(dist,e1)+0.5f; + texCoordsArray[numIndexes+1][1]=DotProduct(dist,e2)+0.5f; + + VectorSubtract( posc, origin,dist); + texCoordsArray[numIndexes+2][0]=DotProduct(dist,e1)+0.5f; + texCoordsArray[numIndexes+2][1]=DotProduct(dist,e2)+0.5f; + + if ((texCoordsArray[numIndexes][0] < 0.0f && texCoordsArray[numIndexes+1][0] < 0.0f && texCoordsArray[numIndexes+2][0] < 0.0f) || + (texCoordsArray[numIndexes][0] > 1.0f && texCoordsArray[numIndexes+1][0] > 1.0f && texCoordsArray[numIndexes+2][0] > 1.0f) || + (texCoordsArray[numIndexes][1] < 0.0f && texCoordsArray[numIndexes+1][1] < 0.0f && texCoordsArray[numIndexes+2][1] < 0.0f) || + (texCoordsArray[numIndexes][1] > 1.0f && texCoordsArray[numIndexes+1][1] > 1.0f && texCoordsArray[numIndexes+2][1] > 1.0f) ) + { + continue; // didn't end up hitting this tri + } + + // these are the old texture coordinates for the multitexture dlight + + /* old code, get from the svars = wrong + oldTexCoordsArray[numIndexes][0]=tess.svars.texcoords[0][a][0]; + oldTexCoordsArray[numIndexes][1]=tess.svars.texcoords[0][a][1]; + oldTexCoordsArray[numIndexes+1][0]=tess.svars.texcoords[0][b][0]; + oldTexCoordsArray[numIndexes+1][1]=tess.svars.texcoords[0][b][1]; + oldTexCoordsArray[numIndexes+2][0]=tess.svars.texcoords[0][c][0]; + oldTexCoordsArray[numIndexes+2][1]=tess.svars.texcoords[0][c][1]; + */ + oldTexCoordsArray[numIndexes][0]=tess.texCoords[a][0][0]; + oldTexCoordsArray[numIndexes][1]=tess.texCoords[a][0][1]; + oldTexCoordsArray[numIndexes+1][0]=tess.texCoords[b][0][0]; + oldTexCoordsArray[numIndexes+1][1]=tess.texCoords[b][0][1]; + oldTexCoordsArray[numIndexes+2][0]=tess.texCoords[c][0][0]; + oldTexCoordsArray[numIndexes+2][1]=tess.texCoords[c][0][1]; + + colorTemp[0] = myftol(floatColor[0] * modulate); + colorTemp[1] = myftol(floatColor[1] * modulate); + colorTemp[2] = myftol(floatColor[2] * modulate); + colorTemp[3] = 255; + colorArray[numIndexes]=*(unsigned int *)colorTemp; + colorArray[numIndexes+1]=*(unsigned int *)colorTemp; + colorArray[numIndexes+2]=*(unsigned int *)colorTemp; + + hitIndexes[numIndexes] = numIndexes; + hitIndexes[numIndexes+1] = numIndexes+1; + hitIndexes[numIndexes+2] = numIndexes+2; + numIndexes += 3; + + if (numIndexes>=SHADER_MAX_VERTEXES-3) + { + break; // we are out of space, so we are done :) + } + } + + if ( !numIndexes ) { + continue; + } + //don't have fog enabled when we redraw with alpha test, or it will double over + //and screw the tri up -rww + if (r_drawfog->value == 2 && + tr.world && + (tess.fogNum == tr.world->globalFog || tess.fogNum == tr.world->numfogs)) + { + fogging = qglIsEnabled(GL_FOG); + + if (fogging) + { + qglDisable(GL_FOG); + } + } + else + { + fogging = 0; + } + + if (!needResetVerts) + { + needResetVerts=1; + if (qglUnlockArraysEXT) + { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + } + qglVertexPointer (3, GL_FLOAT, 16, vertCoordsArray); // padded for SIMD + + dStage = NULL; + if (tess.shader && qglActiveTextureARB) + { + int i = 0; + while (i < tess.shader->numUnfoggedPasses) + { + const int blendBits = (GLS_SRCBLEND_BITS+GLS_DSTBLEND_BITS); + if (((tess.shader->stages[i].bundle[0].image && !tess.shader->stages[i].bundle[0].isLightmap && !tess.shader->stages[i].bundle[0].numTexMods && tess.shader->stages[i].bundle[0].tcGen != TCGEN_ENVIRONMENT_MAPPED && tess.shader->stages[i].bundle[0].tcGen != TCGEN_FOG) || + (tess.shader->stages[i].bundle[1].image && !tess.shader->stages[i].bundle[1].isLightmap && !tess.shader->stages[i].bundle[1].numTexMods && tess.shader->stages[i].bundle[1].tcGen != TCGEN_ENVIRONMENT_MAPPED && tess.shader->stages[i].bundle[1].tcGen != TCGEN_FOG)) && + (tess.shader->stages[i].stateBits & blendBits) == 0 ) + { //only use non-lightmap opaque stages + dStage = &tess.shader->stages[i]; + break; + } + i++; + } + } + + if (dStage) + { + GL_SelectTexture( 0 ); + GL_State(0); + qglTexCoordPointer( 2, GL_FLOAT, 0, oldTexCoordsArray[0] ); + if (dStage->bundle[0].image && !dStage->bundle[0].isLightmap && !dStage->bundle[0].numTexMods && dStage->bundle[0].tcGen != TCGEN_ENVIRONMENT_MAPPED && dStage->bundle[0].tcGen != TCGEN_FOG) + { + R_BindAnimatedImage( &dStage->bundle[0] ); + } + else + { + R_BindAnimatedImage( &dStage->bundle[1] ); + } + + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + GL_Bind( tr.dlightImage ); + GL_TexEnv( GL_MODULATE ); + + + GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL);// | GLS_ATEST_GT_0); + + R_DrawElements( numIndexes, hitIndexes ); + + qglDisable( GL_TEXTURE_2D ); + GL_SelectTexture(0); + } + else + { + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + + GL_Bind( tr.dlightImage ); + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + //if ( dl->additive ) { + // GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + //} + //else + { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + + R_DrawElements( numIndexes, hitIndexes ); + } + + if (fogging) + { + qglEnable(GL_FOG); + } + + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } + if (needResetVerts) + { + qglVertexPointer (3, GL_FLOAT, 16, tess.xyz); // padded for SIMD + if (qglLockArraysEXT) + { + qglLockArraysEXT(0, tess.numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + } +} +static void ProjectDlightTexture( void ) { + int i, l; + vec3_t origin; + float *texCoords; + byte *colors; + byte clipBits[SHADER_MAX_VERTEXES]; + MAC_STATIC float texCoordsArray[SHADER_MAX_VERTEXES][2]; + byte colorArray[SHADER_MAX_VERTEXES][4]; + glIndex_t hitIndexes[SHADER_MAX_INDEXES]; + int numIndexes; + float scale; + float radius; + int fogging; + vec3_t floatColor; + shaderStage_t *dStage; + + if ( !backEnd.refdef.num_dlights ) { + return; + } + + for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) { + dlight_t *dl; + + if ( !( tess.dlightBits & ( 1 << l ) ) ) { + continue; // this surface definately doesn't have any of this light + } + + texCoords = texCoordsArray[0]; + colors = colorArray[0]; + + dl = &backEnd.refdef.dlights[l]; + VectorCopy( dl->transformed, origin ); + radius = dl->radius; + scale = 1.0f / radius; + + floatColor[0] = dl->color[0] * 255.0f; + floatColor[1] = dl->color[1] * 255.0f; + floatColor[2] = dl->color[2] * 255.0f; + + for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) { + vec3_t dist; + int clip; + float modulate; + + backEnd.pc.c_dlightVertexes++; + + VectorSubtract( origin, tess.xyz[i], dist ); + + int l = 1; + int bestIndex = 0; + float greatest = tess.normal[i][0]; + if (greatest < 0.0f) + { + greatest = -greatest; + } + + if (VectorCompare(tess.normal[i], vec3_origin)) + { //damn you terrain! + bestIndex = 2; + } + else + { + while (l < 3) + { + if ((tess.normal[i][l] > greatest && tess.normal[i][l] > 0.0f) || + (tess.normal[i][l] < -greatest && tess.normal[i][l] < 0.0f)) + { + greatest = tess.normal[i][l]; + if (greatest < 0.0f) + { + greatest = -greatest; + } + bestIndex = l; + } + l++; + } + } + + float dUse = 0.0f; + const float maxScale = 1.5f; + const float maxGroundScale = 1.4f; + const float lightScaleTolerance = 0.1f; + + if (bestIndex == 2) + { + dUse = origin[2]-tess.xyz[i][2]; + if (dUse < 0.0f) + { + dUse = -dUse; + } + dUse = (radius*0.5f)/dUse; + if (dUse > maxGroundScale) + { + dUse = maxGroundScale; + } + else if (dUse < 0.1f) + { + dUse = 0.1f; + } + + if (VectorCompare(tess.normal[i], vec3_origin) || + tess.normal[i][0] > lightScaleTolerance || + tess.normal[i][0] < -lightScaleTolerance || + tess.normal[i][1] > lightScaleTolerance || + tess.normal[i][1] < -lightScaleTolerance) + { //if not perfectly flat, we must use a constant dist + scale = 1.0f / radius; + } + else + { + scale = 1.0f / (radius*dUse); + } + + texCoords[0] = 0.5f + dist[0] * scale; + texCoords[1] = 0.5f + dist[1] * scale; + } + else if (bestIndex == 1) + { + dUse = origin[1]-tess.xyz[i][1]; + if (dUse < 0.0f) + { + dUse = -dUse; + } + dUse = (radius*0.5f)/dUse; + if (dUse > maxScale) + { + dUse = maxScale; + } + else if (dUse < 0.1f) + { + dUse = 0.1f; + } + if (tess.normal[i][0] > lightScaleTolerance || + tess.normal[i][0] < -lightScaleTolerance || + tess.normal[i][2] > lightScaleTolerance || + tess.normal[i][2] < -lightScaleTolerance) + { //if not perfectly flat, we must use a constant dist + scale = 1.0f / radius; + } + else + { + scale = 1.0f / (radius*dUse); + } + + texCoords[0] = 0.5f + dist[0] * scale; + texCoords[1] = 0.5f + dist[2] * scale; + } + else + { + dUse = origin[0]-tess.xyz[i][0]; + if (dUse < 0.0f) + { + dUse = -dUse; + } + dUse = (radius*0.5f)/dUse; + if (dUse > maxScale) + { + dUse = maxScale; + } + else if (dUse < 0.1f) + { + dUse = 0.1f; + } + if (tess.normal[i][2] > lightScaleTolerance || + tess.normal[i][2] < -lightScaleTolerance || + tess.normal[i][1] > lightScaleTolerance || + tess.normal[i][1] < -lightScaleTolerance) + { //if not perfectly flat, we must use a constant dist + scale = 1.0f / radius; + } + else + { + scale = 1.0f / (radius*dUse); + } + + texCoords[0] = 0.5f + dist[1] * scale; + texCoords[1] = 0.5f + dist[2] * scale; + } + + clip = 0; + if ( texCoords[0] < 0.0f ) { + clip |= 1; + } else if ( texCoords[0] > 1.0f ) { + clip |= 2; + } + if ( texCoords[1] < 0.0f ) { + clip |= 4; + } else if ( texCoords[1] > 1.0f ) { + clip |= 8; + } + // modulate the strength based on the height and color + if ( dist[bestIndex] > radius ) { + clip |= 16; + modulate = 0.0f; + } else if ( dist[bestIndex] < -radius ) { + clip |= 32; + modulate = 0.0f; + } else { + dist[bestIndex] = Q_fabs(dist[bestIndex]); + if ( dist[bestIndex] < radius * 0.5f ) { + modulate = 1.0f; + } else { + modulate = 2.0f * (radius - dist[bestIndex]) * scale; + } + } + clipBits[i] = clip; + + colors[0] = myftol(floatColor[0] * modulate); + colors[1] = myftol(floatColor[1] * modulate); + colors[2] = myftol(floatColor[2] * modulate); + colors[3] = 255; + } + // build a list of triangles that need light + numIndexes = 0; + for ( i = 0 ; i < tess.numIndexes ; i += 3 ) { + int a, b, c; + + a = tess.indexes[i]; + b = tess.indexes[i+1]; + c = tess.indexes[i+2]; + if ( clipBits[a] & clipBits[b] & clipBits[c] ) { + continue; // not lighted + } + hitIndexes[numIndexes] = a; + hitIndexes[numIndexes+1] = b; + hitIndexes[numIndexes+2] = c; + numIndexes += 3; + } + + if ( !numIndexes ) { + continue; + } + + //don't have fog enabled when we redraw with alpha test, or it will double over + //and screw the tri up -rww + if (r_drawfog->value == 2 && + tr.world && + (tess.fogNum == tr.world->globalFog || tess.fogNum == tr.world->numfogs)) + { + fogging = qglIsEnabled(GL_FOG); + + if (fogging) + { + qglDisable(GL_FOG); + } + } + else + { + fogging = 0; + } + + + dStage = NULL; + if (tess.shader && qglActiveTextureARB) + { + int i = 0; + while (i < tess.shader->numUnfoggedPasses) + { + const int blendBits = (GLS_SRCBLEND_BITS+GLS_DSTBLEND_BITS); + if (((tess.shader->stages[i].bundle[0].image && !tess.shader->stages[i].bundle[0].isLightmap && !tess.shader->stages[i].bundle[0].numTexMods && tess.shader->stages[i].bundle[0].tcGen != TCGEN_ENVIRONMENT_MAPPED && tess.shader->stages[i].bundle[0].tcGen != TCGEN_FOG) || + (tess.shader->stages[i].bundle[1].image && !tess.shader->stages[i].bundle[1].isLightmap && !tess.shader->stages[i].bundle[1].numTexMods && tess.shader->stages[i].bundle[1].tcGen != TCGEN_ENVIRONMENT_MAPPED && tess.shader->stages[i].bundle[1].tcGen != TCGEN_FOG)) && + (tess.shader->stages[i].stateBits & blendBits) == 0 ) + { //only use non-lightmap opaque stages + dStage = &tess.shader->stages[i]; + break; + } + i++; + } + } + + if (dStage) + { + GL_SelectTexture( 0 ); + GL_State(0); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + if (dStage->bundle[0].image && !dStage->bundle[0].isLightmap && !dStage->bundle[0].numTexMods && dStage->bundle[0].tcGen != TCGEN_ENVIRONMENT_MAPPED && dStage->bundle[0].tcGen != TCGEN_FOG) + { + R_BindAnimatedImage( &dStage->bundle[0] ); + } + else + { + R_BindAnimatedImage( &dStage->bundle[1] ); + } + + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + GL_Bind( tr.dlightImage ); + GL_TexEnv( GL_MODULATE ); + + GL_State(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL);// | GLS_ATEST_GT_0); + + R_DrawElements( numIndexes, hitIndexes ); + + qglDisable( GL_TEXTURE_2D ); + GL_SelectTexture(0); + } + else + { + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] ); + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, colorArray ); + + GL_Bind( tr.dlightImage ); + // include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light + // where they aren't rendered + //if ( dl->additive ) { + // GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + //} + //else + { + GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL ); + } + + R_DrawElements( numIndexes, hitIndexes ); + } + + if (fogging) + { + qglEnable(GL_FOG); + } + + backEnd.pc.c_totalIndexes += numIndexes; + backEnd.pc.c_dlightIndexes += numIndexes; + } +} +#endif // VV_LIGHTING + + +/* +=================== +RB_FogPass + +Blends a fog texture on top of everything else +=================== +*/ +static void RB_FogPass( void ) { + fog_t *fog; + int i; + + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + * ( int * )&tess.svars.colors[i] = fog->colorInt; + } + + RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[0] ); + + GL_Bind( tr.fogImage ); + + if ( tess.shader->fogPass == FP_EQUAL ) { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL ); + } else { + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + } + + R_DrawElements( tess.numIndexes, tess.indexes ); +} + + +/* +=============== +ComputeColors +=============== +*/ +#ifdef _XBOX +static void ComputeColors( shaderStage_t *pStage, alphaGen_t forceAlphaGen, colorGen_t forceRGBGen ) +{ + int i; + + if ( tess.shader != tr.projectionShadowShader && tess.shader != tr.shadowShader && + ( backEnd.currentEntity->e.renderfx & (RF_DISINTEGRATE1|RF_DISINTEGRATE2))) + { + RB_CalcDisintegrateColors( (unsigned char *)tess.svars.colors, (colorGen_t)pStage->rgbGen ); + RB_CalcDisintegrateVertDeform(); + + // We've done some custom alpha and color stuff, so we can skip the rest. Let it do fog though + forceRGBGen = CGEN_SKIP; + forceAlphaGen = AGEN_SKIP; + } + + // + // rgbGen + // + if ( !forceRGBGen ) + { + forceRGBGen = (colorGen_t)pStage->rgbGen; + } + + if ( backEnd.currentEntity->e.renderfx & RF_VOLUMETRIC ) // does not work for rotated models, technically, this should also be a CGEN type, but that would entail adding new shader commands....which is too much work for one thing + { + int i; + float *normal, dot; + DWORD *color; + int numVertexes; + + normal = tess.normal[0]; + color = tess.svars.colors; + + numVertexes = tess.numVertexes; + + for ( i = 0 ; i < numVertexes ; i++, normal += 4, color ++) + { + dot = DotProduct( normal, backEnd.refdef.viewaxis[0] ); + + dot *= dot * dot * dot; + + if ( dot < 0.2f ) // so low, so just clamp it + { + dot = 0.0f; + } + + *color = D3DCOLOR_RGBA( (int)(backEnd.currentEntity->e.shaderRGBA[0] * (1-dot)), + (int)(backEnd.currentEntity->e.shaderRGBA[0] * (1-dot)), + (int)(backEnd.currentEntity->e.shaderRGBA[0] * (1-dot)), + (int)(backEnd.currentEntity->e.shaderRGBA[0] * (1-dot)) ); + } + + forceRGBGen = CGEN_SKIP; + forceAlphaGen = AGEN_SKIP; + } + + if ( !forceAlphaGen ) //set this up so we can override below + { + forceAlphaGen = (alphaGen_t)pStage->alphaGen; + } + + DWORD color; + + switch ( forceRGBGen ) + { + case CGEN_SKIP: + break; + case CGEN_IDENTITY: + memset( tess.svars.colors, 0xffffffff, sizeof(DWORD) * tess.numVertexes ); + break; + default: + case CGEN_IDENTITY_LIGHTING: + color = ((tr.identityLightByte & 0xff) << 24 | + (tr.identityLightByte & 0xff) << 16 | + (tr.identityLightByte & 0xff) << 8 | + (tr.identityLightByte & 0xff) << 0); + memset( tess.svars.colors, color, sizeof(DWORD) * tess.numVertexes ); + break; + case CGEN_LIGHTING_DIFFUSE: +#ifdef VV_LIGHTING + VVLightMan.RB_CalcDiffuseColor( tess.svars.colors ); +#else + RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors ); +#endif + break; + case CGEN_LIGHTING_DIFFUSE_ENTITY: +#ifdef VV_LIGHTING + VVLightMan.RB_CalcDiffuseEntityColor( tess.svars.colors ); +#else + RB_CalcDiffuseEntityColor( ( unsigned char * ) tess.svars.colors ); +#endif + if ( forceAlphaGen == AGEN_IDENTITY && + backEnd.currentEntity->e.shaderRGBA[3] == 0xff + ) + { + forceAlphaGen = AGEN_SKIP; //already got it in this set since it does all 4 components + } + break; + case CGEN_EXACT_VERTEX: + for( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = D3DCOLOR_RGBA( (int)(tess.vertexColors[i][0]), + (int)(tess.vertexColors[i][1]), + (int)(tess.vertexColors[i][2]), + (int)(tess.vertexColors[i][3]) ); + } + break; + case CGEN_CONST: + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i] = D3DCOLOR_RGBA( (int)(pStage->constantColor[0]), + (int)(pStage->constantColor[1]), + (int)(pStage->constantColor[2]), + (int)(pStage->constantColor[3]) ); + } + break; + case CGEN_VERTEX: + if ( tr.identityLight == 1 ) + { + for( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = D3DCOLOR_RGBA( (int)(tess.vertexColors[i][0]), + (int)(tess.vertexColors[i][1]), + (int)(tess.vertexColors[i][2]), + (int)(tess.vertexColors[i][3])); + } + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = D3DCOLOR_RGBA( (int)(tess.vertexColors[i][0] * tr.identityLight), + (int)(tess.vertexColors[i][1] * tr.identityLight), + (int)(tess.vertexColors[i][2] * tr.identityLight), + (int)(tess.vertexColors[i][3])); + } + } + break; + case CGEN_ONE_MINUS_VERTEX: + if ( tr.identityLight == 1 ) + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = D3DCOLOR_XRGB( (int)(255 - tess.vertexColors[i][0]), + (int)(255 - tess.vertexColors[i][1]), + (int)(255 - tess.vertexColors[i][2])); + } + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = D3DCOLOR_XRGB( (int)((255 - tess.vertexColors[i][0]) * tr.identityLight), + (int)((255 - tess.vertexColors[i][1]) * tr.identityLight), + (int)((255 - tess.vertexColors[i][2]) * tr.identityLight)); + } + } + break; + case CGEN_FOG: + { + fog_t *fog; + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + * ( int * )&tess.svars.colors[i] = fog->colorInt; + } + } + break; + case CGEN_WAVEFORM: + RB_CalcWaveColor( &pStage->rgbWave, tess.svars.colors ); + break; + case CGEN_ENTITY: + RB_CalcColorFromEntity( tess.svars.colors ); + if ( forceAlphaGen == AGEN_IDENTITY && + backEnd.currentEntity->e.shaderRGBA[3] == 0xff + ) + { + forceAlphaGen = AGEN_SKIP; //already got it in this set since it does all 4 components + } + + break; + case CGEN_ONE_MINUS_ENTITY: + RB_CalcColorFromOneMinusEntity( tess.svars.colors ); + break; + case CGEN_LIGHTMAPSTYLE: + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i] = *(DWORD *)styleColors[pStage->lightmapStyle]; + } + break; + } + + // + // alphaGen + // + DWORD rgb; + switch ( forceAlphaGen ) + { + case AGEN_SKIP: + break; + case AGEN_IDENTITY: + if ( forceRGBGen != CGEN_IDENTITY && forceRGBGen != CGEN_LIGHTING_DIFFUSE ) { + if ( ( forceRGBGen == CGEN_VERTEX && tr.identityLight != 1 ) || + forceRGBGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | ((255 & 0xff) << 24); + } + } + } + break; + case AGEN_CONST: + if ( forceRGBGen != CGEN_CONST ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | ((pStage->constantColor[3] & 0xff) << 24); + } + } + break; + case AGEN_WAVEFORM: + RB_CalcWaveAlpha( &pStage->alphaWave, tess.svars.colors ); + break; + case AGEN_LIGHTING_SPECULAR: + RB_CalcSpecularAlpha( tess.svars.colors ); + break; + case AGEN_ENTITY: + if ( forceRGBGen != CGEN_ENTITY ) { //already got it in the CGEN_entity since it does all 4 components + RB_CalcAlphaFromEntity( tess.svars.colors ); + } + break; + case AGEN_ONE_MINUS_ENTITY: + RB_CalcAlphaFromOneMinusEntity( tess.svars.colors ); + break; + case AGEN_VERTEX: + if ( forceRGBGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | ((tess.vertexColors[i][3] & 0xff) << 24); + } + } + break; + case AGEN_ONE_MINUS_VERTEX: + for ( i = 0; i < tess.numVertexes; i++ ) + { + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | (((255 - tess.vertexColors[i][3]) & 0xff) << 24); + } + break; + case AGEN_PORTAL: + { + unsigned char alpha; + + for ( i = 0; i < tess.numVertexes; i++ ) + { + float len; + vec3_t v; + + VectorSubtract( tess.xyz[i], backEnd.viewParms.or.origin, v ); + len = VectorLength( v ); + + len /= tess.shader->portalRange; + + if ( len < 0 ) + { + alpha = 0; + } + else if ( len > 1 ) + { + alpha = 0xff; + } + else + { + alpha = len * 0xff; + } + + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | ((alpha & 0xff) << 24); + } + } + break; + case AGEN_BLEND: + if ( forceRGBGen != CGEN_VERTEX ) + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + rgb = (DWORD)((tess.svars.colors[i]) & 0x00ffffff); + tess.svars.colors[i] = rgb | ((tess.vertexAlphas[i][pStage->index] & 0xff) << 24); + } + } + break; + } + + // + // fog adjustment for colors to fade out as fog increases + // + if ( tess.fogNum ) + { + switch ( pStage->adjustColorsForFog ) + { + case ACFF_MODULATE_RGB: + RB_CalcModulateColorsByFog( tess.svars.colors ); + break; + case ACFF_MODULATE_ALPHA: + RB_CalcModulateAlphasByFog( tess.svars.colors ); + break; + case ACFF_MODULATE_RGBA: + RB_CalcModulateRGBAsByFog( tess.svars.colors ); + break; + case ACFF_NONE: + break; + } + } +} + +#else // _XBOX + +static void ComputeColors( shaderStage_t *pStage, alphaGen_t forceAlphaGen, colorGen_t forceRGBGen ) +{ + int i; + + if ( tess.shader != tr.projectionShadowShader && tess.shader != tr.shadowShader && + ( backEnd.currentEntity->e.renderfx & (RF_DISINTEGRATE1|RF_DISINTEGRATE2))) + { + RB_CalcDisintegrateColors( (unsigned char *)tess.svars.colors, pStage->rgbGen ); + RB_CalcDisintegrateVertDeform(); + + // We've done some custom alpha and color stuff, so we can skip the rest. Let it do fog though + forceRGBGen = CGEN_SKIP; + forceAlphaGen = AGEN_SKIP; + } + + // + // rgbGen + // + if ( !forceRGBGen ) + { + forceRGBGen = pStage->rgbGen; + } + + if ( backEnd.currentEntity->e.renderfx & RF_VOLUMETRIC ) // does not work for rotated models, technically, this should also be a CGEN type, but that would entail adding new shader commands....which is too much work for one thing + { + int i; + float *normal, dot; + unsigned char *color; + int numVertexes; + + normal = tess.normal[0]; + color = tess.svars.colors[0]; + + numVertexes = tess.numVertexes; + + for ( i = 0 ; i < numVertexes ; i++, normal += 4, color += 4) + { + dot = DotProduct( normal, backEnd.refdef.viewaxis[0] ); + + dot *= dot * dot * dot; + + if ( dot < 0.2f ) // so low, so just clamp it + { + dot = 0.0f; + } + + color[0] = color[1] = color[2] = color[3] = myftol( backEnd.currentEntity->e.shaderRGBA[0] * (1-dot) ); + } + + forceRGBGen = CGEN_SKIP; + forceAlphaGen = AGEN_SKIP; + } + + if ( !forceAlphaGen ) //set this up so we can override below + { + forceAlphaGen = pStage->alphaGen; + } + + switch ( forceRGBGen ) + { + case CGEN_SKIP: + break; + case CGEN_IDENTITY: + memset( tess.svars.colors, 0xff, tess.numVertexes * 4 ); + break; + default: + case CGEN_IDENTITY_LIGHTING: + memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 ); + break; + case CGEN_LIGHTING_DIFFUSE: + RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_LIGHTING_DIFFUSE_ENTITY: + RB_CalcDiffuseEntityColor( ( unsigned char * ) tess.svars.colors ); + + if ( forceAlphaGen == AGEN_IDENTITY && + backEnd.currentEntity->e.shaderRGBA[3] == 0xff + ) + { + forceAlphaGen = AGEN_SKIP; //already got it in this set since it does all 4 components + } + break; + case CGEN_EXACT_VERTEX: + memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); + break; + case CGEN_CONST: + for ( i = 0; i < tess.numVertexes; i++ ) { + *(int *)tess.svars.colors[i] = *(int *)pStage->constantColor; + } + break; + case CGEN_VERTEX: + if ( tr.identityLight == 1 ) + { + memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) ); + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight; + tess.svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight; + tess.svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight; + tess.svars.colors[i][3] = tess.vertexColors[i][3]; + } + } + break; + case CGEN_ONE_MINUS_VERTEX: + if ( tr.identityLight == 1 ) + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = 255 - tess.vertexColors[i][0]; + tess.svars.colors[i][1] = 255 - tess.vertexColors[i][1]; + tess.svars.colors[i][2] = 255 - tess.vertexColors[i][2]; + } + } + else + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][0] = ( 255 - tess.vertexColors[i][0] ) * tr.identityLight; + tess.svars.colors[i][1] = ( 255 - tess.vertexColors[i][1] ) * tr.identityLight; + tess.svars.colors[i][2] = ( 255 - tess.vertexColors[i][2] ) * tr.identityLight; + } + } + break; + case CGEN_FOG: + { + fog_t *fog; + + fog = tr.world->fogs + tess.fogNum; + + for ( i = 0; i < tess.numVertexes; i++ ) { + * ( int * )&tess.svars.colors[i] = fog->colorInt; + } + } + break; + case CGEN_WAVEFORM: + RB_CalcWaveColor( &pStage->rgbWave, ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_ENTITY: + RB_CalcColorFromEntity( ( unsigned char * ) tess.svars.colors ); + if ( forceAlphaGen == AGEN_IDENTITY && + backEnd.currentEntity->e.shaderRGBA[3] == 0xff + ) + { + forceAlphaGen = AGEN_SKIP; //already got it in this set since it does all 4 components + } + + break; + case CGEN_ONE_MINUS_ENTITY: + RB_CalcColorFromOneMinusEntity( ( unsigned char * ) tess.svars.colors ); + break; + case CGEN_LIGHTMAPSTYLE: + for ( i = 0; i < tess.numVertexes; i++ ) + { + * ( int * )&tess.svars.colors[i] = *(int *)styleColors[pStage->lightmapStyle]; + } + break; + } + + // + // alphaGen + // + + switch ( forceAlphaGen ) + { + case AGEN_SKIP: + break; + case AGEN_IDENTITY: + if ( forceRGBGen != CGEN_IDENTITY && forceRGBGen != CGEN_LIGHTING_DIFFUSE ) { + if ( ( forceRGBGen == CGEN_VERTEX && tr.identityLight != 1 ) || + forceRGBGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = 0xff; + } + } + } + break; + case AGEN_CONST: + if ( forceRGBGen != CGEN_CONST ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = pStage->constantColor[3]; + } + } + break; + case AGEN_WAVEFORM: + RB_CalcWaveAlpha( &pStage->alphaWave, ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_LIGHTING_SPECULAR: + RB_CalcSpecularAlpha( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_ENTITY: + if ( forceRGBGen != CGEN_ENTITY ) { //already got it in the CGEN_entity since it does all 4 components + RB_CalcAlphaFromEntity( ( unsigned char * ) tess.svars.colors ); + } + break; + case AGEN_ONE_MINUS_ENTITY: + RB_CalcAlphaFromOneMinusEntity( ( unsigned char * ) tess.svars.colors ); + break; + case AGEN_VERTEX: + if ( forceRGBGen != CGEN_VERTEX ) { + for ( i = 0; i < tess.numVertexes; i++ ) { + tess.svars.colors[i][3] = tess.vertexColors[i][3]; + } + } + break; + case AGEN_ONE_MINUS_VERTEX: + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][3] = 255 - tess.vertexColors[i][3]; + } + break; + case AGEN_PORTAL: + { + unsigned char alpha; + + for ( i = 0; i < tess.numVertexes; i++ ) + { + float len; + vec3_t v; + + VectorSubtract( tess.xyz[i], backEnd.viewParms.or.origin, v ); + len = VectorLength( v ); + + len /= tess.shader->portalRange; + + if ( len < 0 ) + { + alpha = 0; + } + else if ( len > 1 ) + { + alpha = 0xff; + } + else + { + alpha = len * 0xff; + } + + tess.svars.colors[i][3] = alpha; + } + } + break; + case AGEN_BLEND: + if ( forceRGBGen != CGEN_VERTEX ) + { + for ( i = 0; i < tess.numVertexes; i++ ) + { + tess.svars.colors[i][3] = tess.vertexAlphas[i][pStage->index]; + } + } + break; + } + + // + // fog adjustment for colors to fade out as fog increases + // + if ( tess.fogNum ) + { + switch ( pStage->adjustColorsForFog ) + { + case ACFF_MODULATE_RGB: + RB_CalcModulateColorsByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_MODULATE_ALPHA: + RB_CalcModulateAlphasByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_MODULATE_RGBA: + RB_CalcModulateRGBAsByFog( ( unsigned char * ) tess.svars.colors ); + break; + case ACFF_NONE: + break; + } + } +} + +#endif // _XBOX + +/* +=============== +ComputeTexCoords +=============== +*/ +static void ComputeTexCoords( shaderStage_t *pStage ) { + int i; + int b; + + for ( b = 0; b < NUM_TEXTURE_BUNDLES; b++ ) { + int tm; + + // + // generate the texture coordinates + // + switch ( pStage->bundle[b].tcGen ) + { + case TCGEN_IDENTITY: + memset( tess.svars.texcoords[b], 0, sizeof( float ) * 2 * tess.numVertexes ); + break; + case TCGEN_TEXTURE: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][0][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][0][1]; + } + break; + case TCGEN_LIGHTMAP: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][1][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][1][1]; + } + break; + case TCGEN_LIGHTMAP1: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][2][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][2][1]; + } + break; + case TCGEN_LIGHTMAP2: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][3][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][3][1]; + } + break; + case TCGEN_LIGHTMAP3: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = tess.texCoords[i][4][0]; + tess.svars.texcoords[b][i][1] = tess.texCoords[i][4][1]; + } + break; + case TCGEN_VECTOR: + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + tess.svars.texcoords[b][i][0] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[0] ); + tess.svars.texcoords[b][i][1] = DotProduct( tess.xyz[i], pStage->bundle[b].tcGenVectors[1] ); + } + break; + case TCGEN_FOG: + RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[b] ); + break; + case TCGEN_ENVIRONMENT_MAPPED: +#ifdef _XBOX + tess.shader->stages[tess.currentPass].isEnvironment = qtrue; +#else + RB_CalcEnvironmentTexCoords( ( float * ) tess.svars.texcoords[b] ); +#endif + break; + case TCGEN_BAD: + return; + } + + // + // alter texture coordinates + // + for ( tm = 0; tm < pStage->bundle[b].numTexMods ; tm++ ) { + switch ( pStage->bundle[b].texMods[tm].type ) + { + case TMOD_NONE: + tm = TR_MAX_TEXMODS; // break out of for loop + break; + + case TMOD_TURBULENT: + RB_CalcTurbulentTexCoords( &pStage->bundle[b].texMods[tm].wave, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_ENTITY_TRANSLATE: + RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_SCROLL: + RB_CalcScrollTexCoords( pStage->bundle[b].texMods[tm].translate, //union scroll into translate + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_SCALE: + RB_CalcScaleTexCoords( pStage->bundle[b].texMods[tm].translate, //union scroll into translate + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_STRETCH: + RB_CalcStretchTexCoords( &pStage->bundle[b].texMods[tm].wave, + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_TRANSFORM: + RB_CalcTransformTexCoords( &pStage->bundle[b].texMods[tm], + ( float * ) tess.svars.texcoords[b] ); + break; + + case TMOD_ROTATE: + RB_CalcRotateTexCoords( pStage->bundle[b].texMods[tm].translate[0], //union rotateSpeed into translate[0] + ( float * ) tess.svars.texcoords[b] ); + break; + + default: + Com_Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'\n", pStage->bundle[b].texMods[tm].type, tess.shader->name ); + break; + } + } + } +} + +/* +** RB_IterateStagesGeneric +*/ +static vec4_t GLFogOverrideColors[GLFOGOVERRIDE_MAX] = +{ + { 0.0, 0.0, 0.0, 1.0 }, // GLFOGOVERRIDE_NONE + { 0.0, 0.0, 0.0, 1.0 }, // GLFOGOVERRIDE_BLACK + { 1.0, 1.0, 1.0, 1.0 } // GLFOGOVERRIDE_WHITE +}; + +static const float logtestExp2 = (sqrt( -log( 1.0 / 255.0 ) )); +extern bool tr_stencilled; //tr_backend.cpp +static void RB_IterateStagesGeneric( shaderCommands_t *input ) +{ + int stage; + bool UseGLFog = false; + bool FogColorChange = false; + fog_t *fog = NULL; + + if (tess.fogNum && tess.shader->fogPass && (tess.fogNum == tr.world->globalFog || tess.fogNum == tr.world->numfogs) + && r_drawfog->value == 2) + { // only gl fog global fog and the "special fog" + fog = tr.world->fogs + tess.fogNum; + + if (tr.rangedFog) + { //ranged fog, used for sniper scope + float fStart = fog->parms.depthForOpaque; + float fEnd = tr.distanceCull; + + if (tr.rangedFog < 0.0f) + { //special designer override + fStart = -tr.rangedFog; + fEnd = fog->parms.depthForOpaque; + + if (fStart >= fEnd) + { + fStart = fEnd-1.0f; + } + } + else + { + //the greater tr.rangedFog is, the more fog we will get between the view point and cull distance + if ((tr.distanceCull-fStart) < tr.rangedFog) + { //assure a minimum range between fog beginning and cutoff distance + fStart = tr.distanceCull-tr.rangedFog; + + if (fStart < 16.0f) + { + fStart = 16.0f; + } + } + } + + qglFogi(GL_FOG_MODE, GL_LINEAR); + qglFogf(GL_FOG_START, fStart); + qglFogf(GL_FOG_END, fEnd); + } + else + { + qglFogi(GL_FOG_MODE, GL_EXP2); + qglFogf(GL_FOG_DENSITY, logtestExp2 / fog->parms.depthForOpaque); + } + + if ( g_bRenderGlowingObjects ) + { + const float fogColor[3] = { 0.0f, 0.0f, 0.0f }; + qglFogfv(GL_FOG_COLOR, fogColor ); + } + else + { + qglFogfv(GL_FOG_COLOR, fog->parms.color); + } + + qglEnable(GL_FOG); + UseGLFog = true; + } + + for ( stage = 0; stage < input->shader->numUnfoggedPasses; stage++ ) + { + shaderStage_t *pStage = &tess.xstages[stage]; + if ( !pStage->active ) + { + assert(pStage->active);//wtf? + break; + } + + // Reject this stage if it's not a glow stage but we are doing a glow pass. + if ( g_bRenderGlowingObjects && !pStage->glow ) + { + continue; + } + + int stateBits = pStage->stateBits; + alphaGen_t forceAlphaGen = (alphaGen_t)0; + colorGen_t forceRGBGen = (colorGen_t)0; + +#ifdef _XBOX + tess.currentPass = stage; +#endif + + // allow skipping out to show just lightmaps during development +#ifndef _XBOX + if ( stage && r_lightmap->integer) + { + if ( !( pStage->bundle[0].isLightmap || pStage->bundle[1].isLightmap || pStage->bundle[0].vertexLightmap ) ) + { + continue; // need to keep going in case the LM is in a later stage + } + else + { + stateBits = (GLS_DSTBLEND_ZERO | GLS_SRCBLEND_ONE); //we want to replace the prior stages with this LM, not blend + } + } +#endif + + if ( backEnd.currentEntity ) + { + if ( backEnd.currentEntity->e.renderfx & RF_DISINTEGRATE1 ) + { + // we want to be able to rip a hole in the thing being disintegrated, and by doing the depth-testing it avoids some kinds of artefacts, but will probably introduce others? + // NOTE: adjusting the alphaFunc seems to help a bit + stateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHMASK_TRUE | GLS_ATEST_GE_C0; + } + + if ( backEnd.currentEntity->e.renderfx & RF_ALPHA_FADE ) + { + if ( backEnd.currentEntity->e.shaderRGBA[3] < 255 ) + { + stateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + forceAlphaGen = AGEN_ENTITY; + } + } + + if ( backEnd.currentEntity->e.renderfx & RF_RGB_TINT ) + {//want to use RGBGen from ent + forceRGBGen = CGEN_ENTITY; + } + } + + if (pStage->ss && pStage->ss->surfaceSpriteType) + { + // We check for surfacesprites AFTER drawing everything else + continue; + } + + if (UseGLFog) + { + if (pStage->mGLFogColorOverride) + { + qglFogfv(GL_FOG_COLOR, GLFogOverrideColors[pStage->mGLFogColorOverride]); + FogColorChange = true; + } + else if (FogColorChange && fog) + { + FogColorChange = false; + qglFogfv(GL_FOG_COLOR, fog->parms.color); + } + } + +#ifdef _XBOX + qglDisable(GL_LIGHTING); +#endif + + if (!input->fading) + { //this means ignore this, while we do a fade-out + ComputeColors( pStage, forceAlphaGen, forceRGBGen ); + } + ComputeTexCoords( pStage ); + + if ( !setArraysOnce ) + { + qglEnableClientState( GL_COLOR_ARRAY ); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, input->svars.colors ); + } + + if (pStage->bundle[0].isLightmap && r_debugStyle->integer >= 0) + { + if (pStage->lightmapStyle != r_debugStyle->integer) + { + if (pStage->lightmapStyle == 0) + { + GL_State( GLS_DSTBLEND_ZERO | GLS_SRCBLEND_ZERO ); + R_DrawElements( input->numIndexes, input->indexes ); + } + continue; + } + } + +#ifdef VV_LIGHTING + if(pStage->rgbGen == CGEN_LIGHTING_DIFFUSE || + pStage->rgbGen == CGEN_LIGHTING_DIFFUSE_ENTITY) + { + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer(GL_FLOAT, 16, tess.normal ); + } + + if(pStage->isSpecular) + { + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer(GL_FLOAT, 16, tess.normal ); + if(!tess.setTangents) + BuildTangentVectors(); + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + R_BindAnimatedImage( &pStage->bundle[0] ); + GL_State( stateBits ); + glw_state->lightEffects->RenderSpecular(); + qglDisableClientState( GL_NORMAL_ARRAY ); + continue; + } + if(pStage->isEnvironment) + { + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer( GL_FLOAT, 16, tess.normal ); + R_BindAnimatedImage( &pStage->bundle[0] ); + GL_State( stateBits ); + glw_state->lightEffects->RenderEnvironment(); + qglDisableClientState( GL_NORMAL_ARRAY ); + continue; + } + if(pStage->isBumpMap) + { + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer( GL_FLOAT, 16, tess.normal ); + if(!tess.setTangents) + BuildTangentVectors(); + GL_SelectTexture( 0 ); + R_BindAnimatedImage( &pStage->bundle[0] ); + GL_SelectTexture( 1 ); + qglEnable( GL_TEXTURE_2D ); + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + R_BindAnimatedImage( &pStage->bundle[1] ); + GL_State( stateBits ); + glw_state->lightEffects->RenderBump(); + qglDisable( GL_TEXTURE_2D ); + qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + GL_SelectTexture( 0 ); + qglDisableClientState( GL_NORMAL_ARRAY ); + continue; + } +#endif // VV_LIGHTING + // + // do multitexture + // + if ( pStage->bundle[1].image != 0 ) + { + DrawMultitextured( input, stage ); + } + else + { + static bool lStencilled = false; + + if ( !setArraysOnce ) + { + qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoords[0] ); + } + + // + // set state + // + if ( (tess.shader == tr.distortionShader) || + (backEnd.currentEntity && (backEnd.currentEntity->e.renderfx & RF_DISTORTION)) ) + { //special distortion effect -rww + //tr.screenImage should have been set for this specific entity before we got in here. + GL_Bind( tr.screenImage ); + GL_Cull(CT_TWO_SIDED); + } + else if ( pStage->bundle[0].vertexLightmap && ( r_vertexLight->integer ) && r_lightmap->integer ) + { + GL_Bind( tr.whiteImage ); + } + else + R_BindAnimatedImage( &pStage->bundle[0] ); + + if (tess.shader == tr.distortionShader && + glConfig.stencilBits >= 4) + { //draw it to the stencil buffer! + tr_stencilled = true; + lStencilled = true; + qglEnable(GL_STENCIL_TEST); + // BTO - Xbox fix: High stencil bit is reserved for glow + qglStencilFunc(GL_ALWAYS, 1, 0x7F); //0xFFFFFFFF); + qglStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + //don't depthmask, don't blend.. don't do anything + GL_State(0); + } + else + { + GL_State( stateBits ); + } + + // + // draw + // + R_DrawElements( input->numIndexes, input->indexes ); + + if (lStencilled) + { //re-enable the color buffer, disable stencil test + lStencilled = false; + qglDisable(GL_STENCIL_TEST); + qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } + } + +#ifdef VV_LIGHTING + // Lighting may have been turned on above + qglDisable(GL_LIGHTING); + qglDisableClientState( GL_NORMAL_ARRAY ); + + if(tess.shader == tr.projectionShadowShader) { + qglDisable(GL_STENCIL_TEST); + } +#endif + } + if (FogColorChange) + { + qglFogfv(GL_FOG_COLOR, fog->parms.color); + } +} + +#ifdef _XBOX +qboolean RB_IsCurrentShaderTransparent( void ) +{ + if ( backEnd.currentEntity ) + { + if ( backEnd.currentEntity->e.renderfx & RF_DISINTEGRATE1 ) + { + return qtrue; + } + + if ( backEnd.currentEntity->e.renderfx & RF_ALPHA_FADE && + backEnd.currentEntity->e.shaderRGBA[3] < 255 ) + { + return qtrue; + } + } + + for ( int stage = 0; stage < tess.shader->numUnfoggedPasses; stage++ ) + { + if ( !(tess.xstages[stage].stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) || + tess.xstages[stage].stateBits & GLS_ATEST_BITS ) + { + return qfalse; + } + } + + return qtrue; +} +#endif + +/* +** RB_StageIteratorGeneric +*/ +void RB_StageIteratorGeneric( void ) +{ + shaderCommands_t *input; + int stage; + + input = &tess; + + RB_DeformTessGeometry(); + + // + // log this call + // +#ifndef _XBOX + if ( r_logFile->integer ) + { + // don't just call LogComment, or we will get + // a call to va() every frame! + GLimp_LogComment( va("--- RB_StageIteratorGeneric( %s ) ---\n", tess.shader->name) ); + } +#endif + + // + // set face culling appropriately + // + GL_Cull( input->shader->cullType ); + + // set polygon offset if necessary + if ( input->shader->polygonOffset ) + { + qglEnable( GL_POLYGON_OFFSET_FILL ); + qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value ); + } + + // + // if there is only a single pass then we can enable color + // and texture arrays before we compile, otherwise we need + // to avoid compiling those arrays since they will change + // during multipass rendering + // + if ( tess.numPasses > 1 || input->shader->multitextureEnv ) + { + setArraysOnce = qfalse; + qglDisableClientState (GL_COLOR_ARRAY); + qglDisableClientState (GL_TEXTURE_COORD_ARRAY); + } + else + { + setArraysOnce = qtrue; + + qglEnableClientState( GL_COLOR_ARRAY); + qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors ); + + qglEnableClientState( GL_TEXTURE_COORD_ARRAY); + qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] ); + } + + // If this is a glowing surface, write the glow flag into the stencil buffer +#ifdef _XBOX + if ( r_hdreffect->integer ) + { + // Turn on stenciling, make sure all pixels pass the test + glw_state->device->SetRenderState( D3DRS_STENCILENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_STENCILFUNC, D3DCMP_ALWAYS ); + // Make sure that stencil writes will hit the high bit (the one we care about) + glw_state->device->SetRenderState( D3DRS_STENCILWRITEMASK, 0xFFFFFFFF ); + + if ( input->shader->hasGlow ) + { + // Write only the high (eighth) bit + glw_state->device->SetRenderState( D3DRS_STENCILREF, 0x80 ); + glw_state->device->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE ); + } + else + { + // Clear out the high (eighth) bit + glw_state->device->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_ZERO ); + } + } + else + { +#ifdef _XBOX + if(tess.shader != tr.projectionShadowShader) +#endif + glw_state->device->SetRenderState( D3DRS_STENCILENABLE, FALSE ); + } +#endif + + // + // lock XYZ + // + qglVertexPointer (3, GL_FLOAT, 16, input->xyz); // padded for SIMD + + if (qglLockArraysEXT) + { + qglLockArraysEXT(0, input->numVertexes); + GLimp_LogComment( "glLockArraysEXT\n" ); + } + + // + // enable color and texcoord arrays after the lock if necessary + // + if ( !setArraysOnce ) + { + qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + qglEnableClientState( GL_COLOR_ARRAY ); + } + + // + // call shader function + // + RB_IterateStagesGeneric( input ); + + // + // now do any dynamic lighting needed + // + if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE + && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) ) { +#ifdef VV_LIGHTING + qglEnableClientState( GL_NORMAL_ARRAY ); + qglNormalPointer(GL_FLOAT, 16, tess.normal ); + if(!tess.setTangents) + BuildTangentVectors(); + glw_state->lightEffects->RenderDynamicLights(); + qglDisableClientState( GL_NORMAL_ARRAY ); +#else + if (r_dlightStyle->integer>0) + { + ProjectDlightTexture2(); + } + else + { + ProjectDlightTexture(); + } +#endif + } + + // + // now do fog + // + if (tr.world && (tess.fogNum != tr.world->globalFog || r_drawfog->value != 2) && r_drawfog->value && tess.fogNum && tess.shader->fogPass) + { + RB_FogPass(); + } + + // + // unlock arrays + // + if (qglUnlockArraysEXT) + { + qglUnlockArraysEXT(); + GLimp_LogComment( "glUnlockArraysEXT\n" ); + } + + // + // reset polygon offset + // + if ( input->shader->polygonOffset ) + { + qglDisable( GL_POLYGON_OFFSET_FILL ); + } + + // Now check for surfacesprites. + if (r_surfaceSprites->integer) + { + for ( stage = 1; stage < tess.shader->numUnfoggedPasses; stage++ ) + { + if (tess.xstages[stage].ss && tess.xstages[stage].ss->surfaceSpriteType) + { // Draw the surfacesprite + RB_DrawSurfaceSprites( &tess.xstages[stage], input); + } + } + } + + //don't disable the hardware fog til after we do surface sprites + if (r_drawfog->value == 2 && + tess.fogNum && tess.shader->fogPass && + (tess.fogNum == tr.world->globalFog || tess.fogNum == tr.world->numfogs)) + { + qglDisable(GL_FOG); + } +} + + +/* +** RB_EndSurface +*/ +void RB_EndSurface( void ) { + shaderCommands_t *input; + + input = &tess; + + if (input->numIndexes == 0) { + return; + } + + if (input->indexes[SHADER_MAX_INDEXES-1] != 0) { + Com_Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit"); + } + if (input->xyz[SHADER_MAX_VERTEXES-1][0] != 0) { + Com_Error (ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit"); + } + + if ( tess.shader == tr.shadowShader ) { + RB_ShadowTessEnd(); + return; + } + + // for debugging of sort order issues, stop rendering after a given sort value + if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort ) { + return; + } + + if ( skyboxportal ) + { + // world + if(!(backEnd.refdef.rdflags & RDF_SKYBOXPORTAL)) + { + if(tess.currentStageIteratorFunc == RB_StageIteratorSky) + { // don't process these tris at all + return; + } + } + // portal sky + else + { + if(!drawskyboxportal) + { + if( !(tess.currentStageIteratorFunc == RB_StageIteratorSky)) + { // /only/ process sky tris + return; + } + } + } + } + + // + // update performance counters + // + if (!backEnd.projection2D) + { + backEnd.pc.c_shaders++; + backEnd.pc.c_vertexes += tess.numVertexes; + backEnd.pc.c_indexes += tess.numIndexes; + backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses; + if (tess.fogNum && tess.shader->fogPass && r_drawfog->value == 1) + { // Fogging adds an additional pass + backEnd.pc.c_totalIndexes += tess.numIndexes; + } + } + + // + // call off to shader specific tess end function + // + tess.currentStageIteratorFunc(); + +#ifdef _XBOX + tess.currentPass = 0; +#endif + + // + // draw debugging stuff + // + if ( r_showtris->integer ) + { + DrawTris (input); + } + + if ( r_shownormals->integer ) { + DrawNormals (input); + } + + // clear shader so we can tell we don't have any unclosed surfaces + tess.numIndexes = 0; + + GLimp_LogComment( "----------\n" ); +} + diff --git a/code/renderer/tr_shade_calc.cpp b/code/renderer/tr_shade_calc.cpp new file mode 100644 index 0000000..beb94e7 --- /dev/null +++ b/code/renderer/tr_shade_calc.cpp @@ -0,0 +1,1632 @@ +// tr_shade_calc.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "tr_local.h" +#define WAVEVALUE( table, base, amplitude, phase, freq ) ((base) + table[ myftol( ( ( (phase) + backEnd.refdef.floatTime * (freq) ) * FUNCTABLE_SIZE ) ) & FUNCTABLE_MASK ] * (amplitude)) + +static float *TableForFunc( genFunc_t func ) +{ + switch ( func ) + { + case GF_SIN: + return tr.sinTable; + case GF_TRIANGLE: + return tr.triangleTable; + case GF_SQUARE: + return tr.squareTable; + case GF_SAWTOOTH: + return tr.sawToothTable; + case GF_INVERSE_SAWTOOTH: + return tr.inverseSawToothTable; + case GF_NONE: + default: + break; + } + + Com_Error( ERR_DROP, "TableForFunc called with invalid function '%d' in shader '%s'\n", func, tess.shader->name ); + return NULL; +} + +/* +** EvalWaveForm +** +** Evaluates a given waveForm_t, referencing backEnd.refdef.time directly +*/ +extern float GetNoiseTime( int t ); //from tr_noise, returns 0 to 2 +static float EvalWaveForm( const waveForm_t *wf ) +{ + float *table; + + if ( wf->func == GF_NOISE ) { + return ( wf->base + R_NoiseGet4f( 0, 0, 0, ( backEnd.refdef.floatTime + wf->phase ) * wf->frequency ) * wf->amplitude ); + } else if (wf->func == GF_RAND) { + if( GetNoiseTime( backEnd.refdef.time + wf->phase ) <= wf->frequency ) { + return (wf->base + wf->amplitude); + } else { + return wf->base; + } + } + table = TableForFunc( wf->func ); + return WAVEVALUE( table, wf->base, wf->amplitude, wf->phase, wf->frequency ); +} + +static float EvalWaveFormClamped( const waveForm_t *wf ) +{ + float glow = EvalWaveForm( wf ); + + if ( glow < 0 ) + { + return 0; + } + + if ( glow > 1 ) + { + return 1; + } + + return glow; +} + +/* +** RB_CalcStretchTexCoords +*/ +void RB_CalcStretchTexCoords( const waveForm_t *wf, float *st ) +{ + float p; + texModInfo_t tmi; + + p = 1.0f / EvalWaveForm( wf ); + + tmi.matrix[0][0] = p; + tmi.matrix[1][0] = 0; + tmi.translate[0] = 0.5f - 0.5f * p; + + tmi.matrix[0][1] = 0; + tmi.matrix[1][1] = p; + tmi.translate[1] = 0.5f - 0.5f * p; + + RB_CalcTransformTexCoords( &tmi, st ); +} + +/* +==================================================================== + +DEFORMATIONS + +==================================================================== +*/ + +/* +======================== +RB_CalcDeformVertexes + +======================== +*/ +void RB_CalcDeformVertexes( deformStage_t *ds ) +{ + int i; + vec3_t offset; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float *table; + + if ( ds->deformationWave.frequency == 0 ) + { + scale = EvalWaveForm( &ds->deformationWave ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } + else + { + table = TableForFunc( ds->deformationWave.func ); + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread; + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase + off, + ds->deformationWave.frequency ); + + VectorScale( normal, scale, offset ); + + xyz[0] += offset[0]; + xyz[1] += offset[1]; + xyz[2] += offset[2]; + } + } +} + +/* +========================= +RB_CalcDeformNormals + +Wiggle the normals for wavy environment mapping +========================= +*/ +void RB_CalcDeformNormals( deformStage_t *ds ) { + int i; + float scale; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) { + scale = 0.98f; + scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + backEnd.refdef.floatTime * ds->deformationWave.frequency ); + normal[ 0 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + backEnd.refdef.floatTime * ds->deformationWave.frequency ); + normal[ 1 ] += ds->deformationWave.amplitude * scale; + + scale = 0.98f; + scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale, + backEnd.refdef.floatTime * ds->deformationWave.frequency ); + normal[ 2 ] += ds->deformationWave.amplitude * scale; + + VectorNormalizeFast( normal ); + } +} + +/* +======================== +RB_CalcBulgeVertexes + +======================== +*/ +void RB_CalcBulgeVertexes( deformStage_t *ds ) +{ + int i; + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float scale; + + if ( ds->bulgeSpeed == 0.0f && ds->bulgeWidth == 0.0f ) + { + // We don't have a speed and width, so just use height to expand uniformly + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + xyz[0] += normal[0] * ds->bulgeHeight; + xyz[1] += normal[1] * ds->bulgeHeight; + xyz[2] += normal[2] * ds->bulgeHeight; + } + } + else + { + // I guess do some extra dumb stuff..the fact that it uses ST seems bad though because skin pages may be set up in certain ways that can cause + // very noticeable seams on sufaces ( like on the huge ion_cannon ). + const float *st = ( const float * ) tess.texCoords[0]; + float now; + int off; + + now = backEnd.refdef.time * ds->bulgeSpeed * 0.001f; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4, st += 2 * NUM_TEX_COORDS, normal += 4 ) + { + off = (float)( FUNCTABLE_SIZE / (M_PI*2) ) * ( st[0] * ds->bulgeWidth + now ); + + scale = tr.sinTable[ off & FUNCTABLE_MASK ] * ds->bulgeHeight; + + xyz[0] += normal[0] * scale; + xyz[1] += normal[1] * scale; + xyz[2] += normal[2] * scale; + } + } +} + + +/* +====================== +RB_CalcMoveVertexes + +A deformation that can move an entire surface along a wave path +====================== +*/ +void RB_CalcMoveVertexes( deformStage_t *ds ) { + int i; + float *xyz; + float *table; + float scale; + vec3_t offset; + + table = TableForFunc( ds->deformationWave.func ); + + scale = WAVEVALUE( table, ds->deformationWave.base, + ds->deformationWave.amplitude, + ds->deformationWave.phase, + ds->deformationWave.frequency ); + + VectorScale( ds->moveVector, scale, offset ); + + xyz = ( float * ) tess.xyz; + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + VectorAdd( xyz, offset, xyz ); + } +} + + +/* +============= +DeformText + +Change a polygon into a bunch of text polygons +============= +*/ +void DeformText( const char *text ) { + int i; + vec3_t origin, width, height; + int len; + int ch; + byte color[4]; + float bottom, top; + vec3_t mid; + + height[0] = 0; + height[1] = 0; + height[2] = -1; + CrossProduct( tess.normal[0], height, width ); + + // find the midpoint of the box + VectorClear( mid ); + bottom = WORLD_SIZE; //999999; // WORLD_SIZE instead of MAX_WORLD_COORD so guaranteed to be... + top = -WORLD_SIZE; //-999999; // ... outside the legal range. + for ( i = 0 ; i < 4 ; i++ ) { + VectorAdd( tess.xyz[i], mid, mid ); + if ( tess.xyz[i][2] < bottom ) { + bottom = tess.xyz[i][2]; + } + if ( tess.xyz[i][2] > top ) { + top = tess.xyz[i][2]; + } + } + VectorScale( mid, 0.25f, origin ); + + // determine the individual character size + height[0] = 0; + height[1] = 0; + height[2] = ( top - bottom ) * 0.5f; + + VectorScale( width, height[2] * -0.75f, width ); + + // determine the starting position + len = strlen( text ); + VectorMA( origin, (len-1), width, origin ); + + // clear the shader indexes + tess.numIndexes = 0; + tess.numVertexes = 0; + + color[0] = color[1] = color[2] = color[3] = 255; + + // draw each character + for ( i = 0 ; i < len ; i++ ) { + ch = text[i]; + ch &= 255; + + if ( ch != ' ' ) { + int row, col; + float frow, fcol, size; + + row = ch>>4; + col = ch&15; + + frow = row*0.0625f; + fcol = col*0.0625f; + size = 0.0625f; + + RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size ); + } + VectorMA( origin, -2, width, origin ); + } +} + +/* +================== +GlobalVectorToLocal +================== +*/ +static void GlobalVectorToLocal( const vec3_t in, vec3_t out ) { + out[0] = DotProduct( in, backEnd.ori.axis[0] ); + out[1] = DotProduct( in, backEnd.ori.axis[1] ); + out[2] = DotProduct( in, backEnd.ori.axis[2] ); +} + +/* +===================== +AutospriteDeform + +Assuming all the triangles for this shader are independant +quads, rebuild them as forward facing sprites +===================== +*/ +static void AutospriteDeform( void ) { + int i; + int oldVerts; + float *xyz; + vec3_t mid, delta; + float radius; + vec3_t left, up; + vec3_t leftDir, upDir; + + if ( tess.numVertexes & 3 ) { + Com_Error( ERR_DROP, "Autosprite shader %s had odd vertex count", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + Com_Error( ERR_DROP, "Autosprite shader %s had odd index count", tess.shader->name ); + } + + oldVerts = tess.numVertexes; + tess.numVertexes = 0; + tess.numIndexes = 0; + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.or.axis[1], leftDir ); + GlobalVectorToLocal( backEnd.viewParms.or.axis[2], upDir ); + } else { + VectorCopy( backEnd.viewParms.or.axis[1], leftDir ); + VectorCopy( backEnd.viewParms.or.axis[2], upDir ); + } + + for ( i = 0 ; i < oldVerts ; i+=4 ) { + // find the midpoint + xyz = tess.xyz[i]; + + mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]); + mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]); + mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]); + + VectorSubtract( xyz, mid, delta ); + radius = VectorLength( delta ) * 0.707f; // / sqrt(2) + + VectorScale( leftDir, radius, left ); + VectorScale( upDir, radius, up ); + + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( mid, left, up, tess.vertexColors[i] ); + } +} + + +/* +===================== +Autosprite2Deform + +Autosprite2 will pivot a rectangular quad along the center of its long axis +===================== +*/ +static const glIndex_t edgeVerts[6][2] = { + { 0, 1 }, + { 0, 2 }, + { 0, 3 }, + { 1, 2 }, + { 1, 3 }, + { 2, 3 } +}; + +static void Autosprite2Deform( void ) { + int i, j, k; + int indexes; + float *xyz; + vec3_t forward; + + if ( tess.numVertexes & 3 ) { + VID_Printf( PRINT_WARNING, "Autosprite shader %s had odd vertex count", tess.shader->name ); + } + if ( tess.numIndexes != ( tess.numVertexes >> 2 ) * 6 ) { + VID_Printf( PRINT_WARNING, "Autosprite shader %s had odd index count", tess.shader->name ); + } + + if ( backEnd.currentEntity != &tr.worldEntity ) { + GlobalVectorToLocal( backEnd.viewParms.or.axis[0], forward ); + } else { + VectorCopy( backEnd.viewParms.or.axis[0], forward ); + } + + // this is a lot of work for two triangles... + // we could precalculate a lot of it is an issue, but it would mess up + // the shader abstraction + for ( i = 0, indexes = 0 ; i < tess.numVertexes ; i+=4, indexes+=6 ) { + float lengths[2]; + int nums[2]; + vec3_t mid[2]; + vec3_t major, minor; + float *v1, *v2; + + // find the midpoint + xyz = tess.xyz[i]; + + // identify the two shortest edges + nums[0] = nums[1] = 0; + lengths[0] = lengths[1] = WORLD_SIZE;//999999; // ... instead of MAX_WORLD_COORD, so guaranteed to be outside legal range + + for ( j = 0 ; j < 6 ; j++ ) { + float l; + vec3_t temp; + + v1 = xyz + 4 * edgeVerts[j][0]; + v2 = xyz + 4 * edgeVerts[j][1]; + + VectorSubtract( v1, v2, temp ); + + l = DotProduct( temp, temp ); + if ( l < lengths[0] ) { + nums[1] = nums[0]; + lengths[1] = lengths[0]; + nums[0] = j; + lengths[0] = l; + } else if ( l < lengths[1] ) { + nums[1] = j; + lengths[1] = l; + } + } + + for ( j = 0 ; j < 2 ; j++ ) { + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + mid[j][0] = 0.5f * (v1[0] + v2[0]); + mid[j][1] = 0.5f * (v1[1] + v2[1]); + mid[j][2] = 0.5f * (v1[2] + v2[2]); + } + + // find the vector of the major axis + VectorSubtract( mid[1], mid[0], major ); + + // cross this with the view direction to get minor axis + CrossProduct( major, forward, minor ); + VectorNormalize( minor ); + + // re-project the points + for ( j = 0 ; j < 2 ; j++ ) { + float l; + + v1 = xyz + 4 * edgeVerts[nums[j]][0]; + v2 = xyz + 4 * edgeVerts[nums[j]][1]; + + l = 0.5 * sqrt( lengths[j] ); + + // we need to see which direction this edge + // is used to determine direction of projection + for ( k = 0 ; k < 5 ; k++ ) { + if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0] + && tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) { + break; + } + } + + if ( k == 5 ) { + VectorMA( mid[j], l, minor, v1 ); + VectorMA( mid[j], -l, minor, v2 ); + } else { + VectorMA( mid[j], -l, minor, v1 ); + VectorMA( mid[j], l, minor, v2 ); + } + } + } +} + + +/* +===================== +RB_DeformTessGeometry + +===================== +*/ +#pragma warning( disable : 4710 ) //vectorLength not inlined in AutospriteDeform which is auto-inlined in here +void RB_DeformTessGeometry( void ) { + int i; + deformStage_t *ds; + + for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) { + ds = tess.shader->deforms[ i ]; + + switch ( ds->deformation ) { + case DEFORM_NONE: + break; + case DEFORM_NORMALS: + RB_CalcDeformNormals( ds ); + break; + case DEFORM_WAVE: + RB_CalcDeformVertexes( ds ); + break; + case DEFORM_BULGE: + RB_CalcBulgeVertexes( ds ); + break; + case DEFORM_MOVE: + RB_CalcMoveVertexes( ds ); + break; + case DEFORM_PROJECTION_SHADOW: + RB_ProjectionShadowDeform(); + break; + case DEFORM_AUTOSPRITE: + AutospriteDeform(); + break; + case DEFORM_AUTOSPRITE2: + Autosprite2Deform(); + break; + case DEFORM_TEXT0: + case DEFORM_TEXT1: + case DEFORM_TEXT2: + case DEFORM_TEXT3: + case DEFORM_TEXT4: + case DEFORM_TEXT5: + case DEFORM_TEXT6: + case DEFORM_TEXT7: +// DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] ); + DeformText( "Raven Software" ); + break; + } + } +} +#pragma warning( default: 4710 ) + + +/* +==================================================================== + +COLORS + +==================================================================== +*/ + + +/* +** RB_CalcColorFromEntity +*/ +#ifdef _XBOX +void RB_CalcColorFromEntity( DWORD *dstColors ) +{ + int i; + DWORD *pColors = dstColors; + + if ( !backEnd.currentEntity ) + return; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = D3DCOLOR_RGBA((int)(backEnd.currentEntity->e.shaderRGBA[0]), + (int)(backEnd.currentEntity->e.shaderRGBA[1]), + (int)(backEnd.currentEntity->e.shaderRGBA[2]), + (int)(backEnd.currentEntity->e.shaderRGBA[3])); + } +} +#else +void RB_CalcColorFromEntity( unsigned char *dstColors ) +{ + int i; + int *pColors = ( int * ) dstColors; + int c; + + if ( !backEnd.currentEntity ) + return; + + c = * ( int * ) backEnd.currentEntity->e.shaderRGBA; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = c; + } +} +#endif // _XBOX + +/* +** RB_CalcColorFromOneMinusEntity +*/ +#ifdef _XBOX +void RB_CalcColorFromOneMinusEntity( DWORD *dstColors ) +{ + int i; + DWORD *pColors = dstColors; + unsigned char invModulate[3]; + + if ( !backEnd.currentEntity ) + return; + + invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0]; + invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1]; + invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2]; + invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3]; // this trashes alpha, but the AGEN block fixes it + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = D3DCOLOR_RGBA((int)invModulate[0], + (int)invModulate[1], + (int)invModulate[2], + (int)invModulate[3]); + } +} +#else +void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors ) +{ + int i; + int *pColors = ( int * ) dstColors; + unsigned char invModulate[3]; + int c; + + if ( !backEnd.currentEntity ) + return; + + invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0]; + invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1]; + invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2]; + invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3]; // this trashes alpha, but the AGEN block fixes it + + c = * ( int * ) invModulate; + + for ( i = 0; i < tess.numVertexes; i++, pColors++ ) + { + *pColors = * ( int * ) invModulate; + } +} +#endif // _XBOX + +/* +** RB_CalcAlphaFromEntity +*/ +#ifdef _XBOX +void RB_CalcAlphaFromEntity( DWORD *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + for ( i = 0; i < tess.numVertexes; i++, dstColors ++ ) + { + DWORD rgb = (DWORD)((*dstColors) & 0x00ffffff); + *dstColors = rgb | ((backEnd.currentEntity->e.shaderRGBA[3] & 0xff) << 24); + } +} +#else +void RB_CalcAlphaFromEntity( unsigned char *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = backEnd.currentEntity->e.shaderRGBA[3]; + } +} +#endif + +/* +** RB_CalcAlphaFromOneMinusEntity +*/ +#ifdef _XBOX +void RB_CalcAlphaFromOneMinusEntity( DWORD *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + for ( i = 0; i < tess.numVertexes; i++, dstColors ++ ) + { + DWORD rgb = (DWORD)((*dstColors) & 0x00ffffff); + *dstColors = rgb | (((255 - backEnd.currentEntity->e.shaderRGBA[3]) & 0xff) << 24); + } +} +#else +void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors ) +{ + int i; + + if ( !backEnd.currentEntity ) + return; + + dstColors += 3; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + *dstColors = 0xff - backEnd.currentEntity->e.shaderRGBA[3]; + } +} +#endif + +/* +** RB_CalcWaveColor +*/ +#ifdef _XBOX +void RB_CalcWaveColor( const waveForm_t *wf, DWORD *dstColors ) +{ + int i; + int v; + float glow; + DWORD *colors = dstColors; + byte color[4]; + + if ( wf->func == GF_NOISE ) { + glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( backEnd.refdef.floatTime + wf->phase ) * wf->frequency ) * wf->amplitude; + } else { + glow = EvalWaveForm( wf ) * tr.identityLight; + } + + if ( glow < 0 ) { + glow = 0; + } + else if ( glow > 1 ) { + glow = 1; + } + + v = myftol( 255 * glow ); + color[0] = color[1] = color[2] = v; + color[3] = 255; + + for ( i = 0; i < tess.numVertexes; i++, colors++ ) { + *colors = D3DCOLOR_RGBA(color[0], color[1], color[2], color[3]); + } +} +#else // _XBOX +void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors ) +{ + int i; + int v; + float glow; + int *colors = ( int * ) dstColors; + byte color[4]; + + if ( wf->func == GF_NOISE ) { + glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( backEnd.refdef.floatTime + wf->phase ) * wf->frequency ) * wf->amplitude; + } else { + glow = EvalWaveForm( wf ) * tr.identityLight; + } + + if ( glow < 0 ) { + glow = 0; + } + else if ( glow > 1 ) { + glow = 1; + } + + v = myftol( 255 * glow ); + color[0] = color[1] = color[2] = v; + color[3] = 255; + v = *(int *)color; + + for ( i = 0; i < tess.numVertexes; i++, colors++ ) { + *colors = v; + } +} +#endif // _XBOX + +/* +** RB_CalcWaveAlpha +*/ +#ifdef _XBOX +void RB_CalcWaveAlpha( const waveForm_t *wf, DWORD *dstColors ) +{ + int i; + int v; + float glow; + + glow = EvalWaveFormClamped( wf ); + + v = 255 * glow; + + for ( i = 0; i < tess.numVertexes; i++, dstColors ++ ) + { + DWORD rgb = (DWORD)((*dstColors) & 0x00ffffff); + *dstColors = rgb | ((v & 0xff) << 24); + } +} +#else +void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors ) +{ + int i; + int v; + float glow; + + glow = EvalWaveFormClamped( wf ); + + v = 255 * glow; + + for ( i = 0; i < tess.numVertexes; i++, dstColors += 4 ) + { + dstColors[3] = v; + } +} +#endif + +/* +** RB_CalcModulateColorsByFog +*/ +#ifdef _XBOX +void RB_CalcModulateColorsByFog( DWORD *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors ++ ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + DWORD a, r, g, b; + a = (*colors & 0xff000000) >> 24; + r = ((*colors & 0x00ff0000) >> 16) * f; + g = ((*colors & 0x0000ff00) >> 8) * f; + b = (*colors & 0x000000ff) * f; + *colors = (DWORD)((a << 24) | (r << 16) | (g << 8) | b); + } +} +#else +void RB_CalcModulateColorsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + } +} +#endif + +/* +** RB_CalcModulateAlphasByFog +*/ +#ifdef _XBOX +void RB_CalcModulateAlphasByFog( DWORD *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors ++ ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + DWORD rgb = *colors & 0x00ffffff; + DWORD alpha = ((*colors & 0xff000000) >> 24) * f; + *colors = (alpha << 24) | rgb; + } +} +#else +void RB_CalcModulateAlphasByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[3] *= f; + } +} +#endif + +/* +** RB_CalcModulateRGBAsByFog +*/ +#ifdef _XBOX +void RB_CalcModulateRGBAsByFog( DWORD *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors ++ ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + DWORD a, r, g, b; + a = ((*colors & 0xff000000) >> 24) * f; + r = ((*colors & 0x00ff0000) >> 16) * f; + g = ((*colors & 0x0000ff00) >> 8) * f; + b = (*colors & 0x000000ff) * f; + *colors = (DWORD)((a << 24) | (r << 16) | (g << 8) | b); + } +} +#else +void RB_CalcModulateRGBAsByFog( unsigned char *colors ) { + int i; + float texCoords[SHADER_MAX_VERTEXES][2]; + + // calculate texcoords so we can derive density + // this is not wasted, because it would only have + // been previously called if the surface was opaque + RB_CalcFogTexCoords( texCoords[0] ); + + for ( i = 0; i < tess.numVertexes; i++, colors += 4 ) { + float f = 1.0 - R_FogFactor( texCoords[i][0], texCoords[i][1] ); + colors[0] *= f; + colors[1] *= f; + colors[2] *= f; + colors[3] *= f; + } +} +#endif + + +/* +==================================================================== + +TEX COORDS + +==================================================================== +*/ + +/* +======================== +RB_CalcFogTexCoords + +To do the clipped fog plane really correctly, we should use +projected textures, but I don't trust the drivers and it +doesn't fit our shader data. +======================== +*/ + +void RB_CalcFogTexCoords( float *st ) { + int i; + float *v; + float s, t; + float eyeT; + qboolean eyeOutside; + fog_t *fog; + vec3_t localVec; + vec4_t fogDistanceVector, fogDepthVector; + + fog = tr.world->fogs + tess.fogNum; + + // all fogging distance is based on world Z units + VectorSubtract( backEnd.ori.origin, backEnd.viewParms.or.origin, localVec ); +#ifdef _XBOX + fogDistanceVector[0] = backEnd.ori.modelMatrix[2]; + fogDistanceVector[1] = backEnd.ori.modelMatrix[6]; + fogDistanceVector[2] = backEnd.ori.modelMatrix[10]; +#else + fogDistanceVector[0] = -backEnd.ori.modelMatrix[2]; + fogDistanceVector[1] = -backEnd.ori.modelMatrix[6]; + fogDistanceVector[2] = -backEnd.ori.modelMatrix[10]; +#endif + fogDistanceVector[3] = DotProduct( localVec, backEnd.viewParms.or.axis[0] ); + + // scale the fog vectors based on the fog's thickness + fogDistanceVector[0] *= fog->tcScale; + fogDistanceVector[1] *= fog->tcScale; + fogDistanceVector[2] *= fog->tcScale; + fogDistanceVector[3] *= fog->tcScale; + + // rotate the gradient vector for this orientation + if ( fog->hasSurface ) { + fogDepthVector[0] = fog->surface[0] * backEnd.ori.axis[0][0] + + fog->surface[1] * backEnd.ori.axis[0][1] + fog->surface[2] * backEnd.ori.axis[0][2]; + fogDepthVector[1] = fog->surface[0] * backEnd.ori.axis[1][0] + + fog->surface[1] * backEnd.ori.axis[1][1] + fog->surface[2] * backEnd.ori.axis[1][2]; + fogDepthVector[2] = fog->surface[0] * backEnd.ori.axis[2][0] + + fog->surface[1] * backEnd.ori.axis[2][1] + fog->surface[2] * backEnd.ori.axis[2][2]; + fogDepthVector[3] = -fog->surface[3] + DotProduct( backEnd.ori.origin, fog->surface ); + + eyeT = DotProduct( backEnd.ori.viewOrigin, fogDepthVector ) + fogDepthVector[3]; + } else { + eyeT = 1; // non-surface fog always has eye inside + fogDepthVector[0] = fogDepthVector[1] = fogDepthVector[2] = 0.0f; + fogDepthVector[3] = 1.0f; + } + + // see if the viewpoint is outside + // this is needed for clipping distance even for constant fog + + if ( eyeT < 0 ) { + eyeOutside = qtrue; + } else { + eyeOutside = qfalse; + } + + fogDistanceVector[3] += 1.0/512; + + // calculate density for each point + for (i = 0, v = tess.xyz[0] ; i < tess.numVertexes ; i++, v += 4) { + // calculate the length in fog + s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3]; + t = DotProduct( v, fogDepthVector ) + fogDepthVector[3]; + + // partially clipped fogs use the T axis + if ( eyeOutside ) { + if ( t < 1.0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 1.0/32 + 30.0/32 * t / ( t - eyeT ); // cut the distance at the fog plane + } + } else { + if ( t < 0 ) { + t = 1.0/32; // point is outside, so no fogging + } else { + t = 31.0/32; + } + } + + st[0] = s; + st[1] = t; + st += 2; + } +} + + +/* +** RB_CalcEnvironmentTexCoords +*/ +void RB_CalcEnvironmentTexCoords( float *st ) +{ + int i; + float *v, *normal; + vec3_t viewer; + float d; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + if (backEnd.currentEntity && backEnd.currentEntity->e.renderfx&RF_FIRST_PERSON) //this is a view model so we must use world lights instead of vieworg + { + for (i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 ) + { + d = DotProduct (normal, backEnd.currentEntity->lightDir); + st[0] = normal[0]*d - backEnd.currentEntity->lightDir[0]; + st[1] = normal[1]*d - backEnd.currentEntity->lightDir[1]; + } + } else { //the normal way + for (i = 0 ; i < tess.numVertexes ; i++, v += 4, normal += 4, st += 2 ) + { + VectorSubtract (backEnd.ori.viewOrigin, v, viewer); + VectorNormalizeFast (viewer); + + d = DotProduct (normal, viewer); + st[0] = normal[0]*d - 0.5*viewer[0]; + st[1] = normal[1]*d - 0.5*viewer[1]; + } + } +} + +/* +** RB_CalcTurbulentTexCoords +*/ +void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st ) +{ + int i; + float now; + + now = ( wf->phase + backEnd.refdef.floatTime * wf->frequency ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s + tr.sinTable[ ( ( int ) ( ( ( tess.xyz[i][0] + tess.xyz[i][2] )* 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + st[1] = t + tr.sinTable[ ( ( int ) ( ( tess.xyz[i][1] * 1.0/128 * 0.125 + now ) * FUNCTABLE_SIZE ) ) & ( FUNCTABLE_MASK ) ] * wf->amplitude; + } +} + +/* +** RB_CalcScaleTexCoords +*/ +void RB_CalcScaleTexCoords( const float scale[2], float *st ) +{ + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] *= scale[0]; + st[1] *= scale[1]; + } +} + +/* +** RB_CalcScrollTexCoords +*/ +void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st ) +{ + int i; + float timeScale = backEnd.refdef.floatTime; + float adjustedScrollS, adjustedScrollT; + + adjustedScrollS = scrollSpeed[0] * timeScale; + adjustedScrollT = scrollSpeed[1] * timeScale; + + // clamp so coordinates don't continuously get larger, causing problems + // with hardware limits + adjustedScrollS = adjustedScrollS - floor( adjustedScrollS ); + adjustedScrollT = adjustedScrollT - floor( adjustedScrollT ); + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + st[0] += adjustedScrollS; + st[1] += adjustedScrollT; + } +} + +/* +** RB_CalcTransformTexCoords +*/ +void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st ) +{ + int i; + + for ( i = 0; i < tess.numVertexes; i++, st += 2 ) + { + float s = st[0]; + float t = st[1]; + + st[0] = s * tmi->matrix[0][0] + t * tmi->matrix[1][0] + tmi->translate[0]; + st[1] = s * tmi->matrix[0][1] + t * tmi->matrix[1][1] + tmi->translate[1]; + } +} + + +void RB_CalcRotateTexCoords( float degsPerSecond, float *st ) +{ + float timeScale = backEnd.refdef.floatTime; + float degs; + int index; + float sinValue, cosValue; + texModInfo_t tmi; + + degs = -degsPerSecond * timeScale; + index = degs * ( FUNCTABLE_SIZE / 360.0f ); + + sinValue = tr.sinTable[ index & FUNCTABLE_MASK ]; + cosValue = tr.sinTable[ ( index + FUNCTABLE_SIZE / 4 ) & FUNCTABLE_MASK ]; + + tmi.matrix[0][0] = cosValue; + tmi.matrix[1][0] = -sinValue; + tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue; + + tmi.matrix[0][1] = sinValue; + tmi.matrix[1][1] = cosValue; + tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue; + + RB_CalcTransformTexCoords( &tmi, st ); +} + + + + + + +#if id386 && !(defined __linux__ && defined __i386__) +#pragma warning(disable: 4035) +long myftol( float f ) { + static int tmp; + __asm fld f + __asm fistp tmp + __asm mov eax, tmp +} +#pragma warning(default: 4035) +#endif + +/* +** RB_CalcSpecularAlpha +** +** Calculates specular coefficient and places it in the alpha channel +*/ +vec3_t lightOrigin = { -960, 1980, 96 }; // FIXME: track dynamically + +#ifdef _XBOX +void RB_CalcSpecularAlpha( DWORD *alphas ) { + int i; + float *v, *normal; + vec3_t viewer, reflected; + float l, d; + int a; + vec3_t lightDir; + int numVertexes; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas ++) { + float ilength; + + if (backEnd.currentEntity && + (backEnd.currentEntity->e.hModel||backEnd.currentEntity->e.ghoul2) ) //this is a model so we can use world lights instead fake light + { + VectorCopy (backEnd.currentEntity->lightDir, lightDir); + } else { + VectorSubtract( lightOrigin, v, lightDir ); + VectorNormalizeFast( lightDir ); + } + // calculate the specular color + d = 2 * DotProduct (normal, lightDir); + + // we don't optimize for the d < 0 case since this tends to + // cause visual artifacts such as faceted "snapping" + reflected[0] = normal[0]*d - lightDir[0]; + reflected[1] = normal[1]*d - lightDir[1]; + reflected[2] = normal[2]*d - lightDir[2]; + + VectorSubtract (backEnd.ori.viewOrigin, v, viewer); + ilength = Q_rsqrt( DotProduct( viewer, viewer ) ); + l = DotProduct (reflected, viewer); + l *= ilength; + + if (l < 0) { + a = 0; + } else { + l = l*l; + l = l*l; + a = l * 255; + if (a > 255) { + a = 255; + } + } + DWORD rgb = (DWORD)((*alphas) & 0x00ffffff); + + *alphas = rgb | (a & 0xff) << 24; + } +} +#else // _XBOX +void RB_CalcSpecularAlpha( unsigned char *alphas ) { + int i; + float *v, *normal; + vec3_t viewer, reflected; + float l, d; + int b; + vec3_t lightDir; + int numVertexes; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + alphas += 3; + + numVertexes = tess.numVertexes; + for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4) { + float ilength; + + if (backEnd.currentEntity && + (backEnd.currentEntity->e.hModel||backEnd.currentEntity->e.ghoul2) ) //this is a model so we can use world lights instead fake light + { + VectorCopy (backEnd.currentEntity->lightDir, lightDir); + } else { + VectorSubtract( lightOrigin, v, lightDir ); + VectorNormalizeFast( lightDir ); + } + // calculate the specular color + d = 2 * DotProduct (normal, lightDir); + + // we don't optimize for the d < 0 case since this tends to + // cause visual artifacts such as faceted "snapping" + reflected[0] = normal[0]*d - lightDir[0]; + reflected[1] = normal[1]*d - lightDir[1]; + reflected[2] = normal[2]*d - lightDir[2]; + + VectorSubtract (backEnd.ori.viewOrigin, v, viewer); + ilength = Q_rsqrt( DotProduct( viewer, viewer ) ); + l = DotProduct (reflected, viewer); + l *= ilength; + + if (l < 0) { + b = 0; + } else { + l = l*l; + l = l*l; + b = l * 255; + if (b > 255) { + b = 255; + } + } + + *alphas = b; + } +} +#endif + +/* +** RB_CalcDiffuseColor +** +** The basic vertex lighting calc +*/ +void RB_CalcDiffuseColor( unsigned char *colors ) +{ + int i, j; + float *v, *normal; + float incoming; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + int numVertexes; + + ent = backEnd.currentEntity; + ambientLightInt = ent->ambientLightInt; + VectorCopy( ent->ambientLight, ambientLight ); + VectorCopy( ent->directedLight, directedLight ); + VectorCopy( ent->lightDir, lightDir ); + + v = tess.xyz[0]; + normal = tess.normal[0]; + + numVertexes = tess.numVertexes; + + for ( i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) + { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + *(int *)&colors[i*4] = ambientLightInt; + continue; + } + j = myftol( ambientLight[0] + incoming * directedLight[0] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+0] = j; + + j = myftol( ambientLight[1] + incoming * directedLight[1] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+1] = j; + + j = myftol( ambientLight[2] + incoming * directedLight[2] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+2] = j; + + colors[i*4+3] = 255; + } +} + +/* +** RB_CalcDiffuseColorEntity +** +** The basic vertex lighting calc * Entity Color +*/ +void RB_CalcDiffuseEntityColor( unsigned char *colors ) +{ + int i; + float *v, *normal; + float incoming; + trRefEntity_t *ent; + int ambientLightInt; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + int numVertexes; + float j,r,g,b; + + if ( !backEnd.currentEntity ) + {//error, use the normal lighting + RB_CalcDiffuseColor(colors); + } + + ent = backEnd.currentEntity; + VectorCopy( ent->ambientLight, ambientLight ); + VectorCopy( ent->directedLight, directedLight ); + VectorCopy( ent->lightDir, lightDir ); + + r = backEnd.currentEntity->e.shaderRGBA[0]/255.0f; + g = backEnd.currentEntity->e.shaderRGBA[1]/255.0f; + b = backEnd.currentEntity->e.shaderRGBA[2]/255.0f; + + ((byte *)&ambientLightInt)[0] = myftol( r*ent->ambientLight[0] ); + ((byte *)&ambientLightInt)[1] = myftol( g*ent->ambientLight[1] ); + ((byte *)&ambientLightInt)[2] = myftol( b*ent->ambientLight[2] ); + ((byte *)&ambientLightInt)[3] = backEnd.currentEntity->e.shaderRGBA[3]; + + v = tess.xyz[0]; + normal = tess.normal[0]; + + numVertexes = tess.numVertexes; + + for ( i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) + { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + *(int *)&colors[i*4] = ambientLightInt; + continue; + } + j = ( ambientLight[0] + incoming * directedLight[0] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+0] = myftol(j*r); + + j = ( ambientLight[1] + incoming * directedLight[1] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+1] = myftol(j*g); + + j = ( ambientLight[2] + incoming * directedLight[2] ); + if ( j > 255 ) { + j = 255; + } + colors[i*4+2] = myftol(j*b); + + colors[i*4+3] = backEnd.currentEntity->e.shaderRGBA[3]; + } +} + +//--------------------------------------------------------- +void RB_CalcDisintegrateColors( unsigned char *colors, colorGen_t rgbGen ) +{ + int i, numVertexes; + float dis, threshold; + float *v; + vec3_t temp; + refEntity_t *ent; + + ent = &backEnd.currentEntity->e; + v = tess.xyz[0]; + + // calculate the burn threshold at the given time, anything that passes the threshold will get burnt + threshold = (backEnd.refdef.time - ent->endTime) * 0.045f; // endTime is really the start time, maybe I should just use a completely meaningless substitute? + + numVertexes = tess.numVertexes; + + if ( ent->renderfx & RF_DISINTEGRATE1 ) + { + // this handles the blacken and fading out of the regular player model + for ( i = 0 ; i < numVertexes ; i++, v += 4 ) + { + VectorSubtract( backEnd.currentEntity->e.oldorigin, v, temp ); + + dis = VectorLengthSquared( temp ); + + if ( dis < threshold * threshold ) + { + // completely disintegrated + colors[i*4+3] = 0x00; + } + else if ( dis < threshold * threshold + 60 ) + { + // blacken before fading out + colors[i*4+0] = 0x0; + colors[i*4+1] = 0x0; + colors[i*4+2] = 0x0; + colors[i*4+3] = 0xff; + } + else if ( dis < threshold * threshold + 150 ) + { + // darken more + if ( rgbGen == CGEN_LIGHTING_DIFFUSE_ENTITY ) + { + colors[i*4+0] = backEnd.currentEntity->e.shaderRGBA[0]*0x6f/255.0f; + colors[i*4+1] = backEnd.currentEntity->e.shaderRGBA[1]*0x6f/255.0f; + colors[i*4+2] = backEnd.currentEntity->e.shaderRGBA[2]*0x6f/255.0f; + } + else + { + colors[i*4+0] = 0x6f; + colors[i*4+1] = 0x6f; + colors[i*4+2] = 0x6f; + } + colors[i*4+3] = 0xff; + } + else if ( dis < threshold * threshold + 180 ) + { + // darken at edge of burn + if ( rgbGen == CGEN_LIGHTING_DIFFUSE_ENTITY ) + { + colors[i*4+0] = backEnd.currentEntity->e.shaderRGBA[0]*0xaf/255.0f; + colors[i*4+1] = backEnd.currentEntity->e.shaderRGBA[1]*0xaf/255.0f; + colors[i*4+2] = backEnd.currentEntity->e.shaderRGBA[2]*0xaf/255.0f; + } + else + { + colors[i*4+0] = 0xaf; + colors[i*4+1] = 0xaf; + colors[i*4+2] = 0xaf; + } + colors[i*4+3] = 0xff; + } + else + { + // not burning at all yet + if ( rgbGen == CGEN_LIGHTING_DIFFUSE_ENTITY ) + { + colors[i*4+0] = backEnd.currentEntity->e.shaderRGBA[0]; + colors[i*4+1] = backEnd.currentEntity->e.shaderRGBA[1]; + colors[i*4+2] = backEnd.currentEntity->e.shaderRGBA[2]; + } + else + { + colors[i*4+0] = 0xff; + colors[i*4+1] = 0xff; + colors[i*4+2] = 0xff; + } + colors[i*4+3] = 0xff; + } + } + } + else if ( ent->renderfx & RF_DISINTEGRATE2 ) + { + // this handles the glowing, burning bit that scales away from the model + for ( i = 0 ; i < numVertexes ; i++, v += 4 ) + { + VectorSubtract( backEnd.currentEntity->e.oldorigin, v, temp ); + + dis = VectorLengthSquared( temp ); + + if ( dis < threshold * threshold ) + { + // done burning + colors[i*4+0] = 0x00; + colors[i*4+1] = 0x00; + colors[i*4+2] = 0x00; + colors[i*4+3] = 0x00; + } + else + { + // still full burn + colors[i*4+0] = 0xff; + colors[i*4+1] = 0xff; + colors[i*4+2] = 0xff; + colors[i*4+3] = 0xff; + } + } + } +} + +//--------------------------------------------------------- +void RB_CalcDisintegrateVertDeform( void ) +{ + float *xyz = ( float * ) tess.xyz; + float *normal = ( float * ) tess.normal; + float scale; + vec3_t temp; + + if ( backEnd.currentEntity->e.renderfx & RF_DISINTEGRATE2 ) + { + float threshold = (backEnd.refdef.time - backEnd.currentEntity->e.endTime) * 0.045f; + + for ( int i = 0; i < tess.numVertexes; i++, xyz += 4, normal += 4 ) + { + VectorSubtract( backEnd.currentEntity->e.oldorigin, xyz, temp ); + + scale = VectorLengthSquared( temp ); + + if ( scale < threshold * threshold ) + { + xyz[0] += normal[0] * 2.0f; + xyz[1] += normal[1] * 2.0f; + xyz[2] += normal[2] * 0.5f; + } + else if ( scale < threshold * threshold + 50 ) + { + xyz[0] += normal[0] * 1.0f; + xyz[1] += normal[1] * 1.0f; +// xyz[2] += normal[2] * 1; + } + } + } +} diff --git a/code/renderer/tr_shader.cpp b/code/renderer/tr_shader.cpp new file mode 100644 index 0000000..ff30816 --- /dev/null +++ b/code/renderer/tr_shader.cpp @@ -0,0 +1,4143 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" +#include "tr_stl.h" + +const short lightmapsNone[MAXLIGHTMAPS] = +{ + LIGHTMAP_NONE, + LIGHTMAP_NONE, + LIGHTMAP_NONE, + LIGHTMAP_NONE +}; + +const short lightmaps2d[MAXLIGHTMAPS] = +{ + LIGHTMAP_2D, + LIGHTMAP_2D, + LIGHTMAP_2D, + LIGHTMAP_2D +}; + +const short lightmapsVertex[MAXLIGHTMAPS] = +{ + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX +}; + +const short lightmapsFullBright[MAXLIGHTMAPS] = +{ + LIGHTMAP_WHITEIMAGE, + LIGHTMAP_WHITEIMAGE, + LIGHTMAP_WHITEIMAGE, + LIGHTMAP_WHITEIMAGE +}; + +const byte stylesDefault[MAXLIGHTMAPS] = +{ + LS_NORMAL, + LS_NONE, + LS_NONE, + LS_NONE +}; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Vertex and Pixel Shader definitions. - AReis +/***********************************************************************************************************/ +// This vertex shader basically passes through most values and calculates no lighting. The only +// unusual thing it does is add the inputed texel offsets to all four texture units (this allows +// nearest neighbor pixel peeking). +const unsigned char g_strGlowVShaderARB[] = +{ + "!!ARBvp1.0\ + \ + # Input.\n\ + ATTRIB iPos = vertex.position;\ + ATTRIB iColor = vertex.color;\ + ATTRIB iTex0 = vertex.texcoord[0];\ + ATTRIB iTex1 = vertex.texcoord[1];\ + ATTRIB iTex2 = vertex.texcoord[2];\ + ATTRIB iTex3 = vertex.texcoord[3];\ + \ + # Output.\n\ + OUTPUT oPos = result.position;\ + OUTPUT oColor = result.color;\ + OUTPUT oTex0 = result.texcoord[0];\ + OUTPUT oTex1 = result.texcoord[1];\ + OUTPUT oTex2 = result.texcoord[2];\ + OUTPUT oTex3 = result.texcoord[3];\ + \ + # Constants.\n\ + PARAM ModelViewProj[4]= { state.matrix.mvp };\ + PARAM TexelOffset0 = program.env[0];\ + PARAM TexelOffset1 = program.env[1];\ + PARAM TexelOffset2 = program.env[2];\ + PARAM TexelOffset3 = program.env[3];\ + \ + # Main.\n\ + DP4 oPos.x, ModelViewProj[0], iPos;\ + DP4 oPos.y, ModelViewProj[1], iPos;\ + DP4 oPos.z, ModelViewProj[2], iPos;\ + DP4 oPos.w, ModelViewProj[3], iPos;\ + MOV oColor, iColor;\ + # Notice the optimization of using one texture coord instead of all four.\n\ + ADD oTex0, iTex0, TexelOffset0;\ + ADD oTex1, iTex0, TexelOffset1;\ + ADD oTex2, iTex0, TexelOffset2;\ + ADD oTex3, iTex0, TexelOffset3;\ + \ + END" +}; + +// This Pixel Shader loads four texture units and adds them all together (with a modifier +// multiplied to each in the process). The final output is r0 = t0 + t1 + t2 + t3. +const unsigned char g_strGlowPShaderARB[] = +{ + "!!ARBfp1.0\ + \ + # Input.\n\ + ATTRIB iColor = fragment.color.primary;\ + \ + # Output.\n\ + OUTPUT oColor = result.color;\ + \ + # Constants.\n\ + PARAM Weight = program.env[0];\ + TEMP t0;\ + TEMP t1;\ + TEMP t2;\ + TEMP t3;\ + TEMP r0;\ + \ + # Main.\n\ + TEX t0, fragment.texcoord[0], texture[0], RECT;\ + TEX t1, fragment.texcoord[1], texture[1], RECT;\ + TEX t2, fragment.texcoord[2], texture[2], RECT;\ + TEX t3, fragment.texcoord[3], texture[3], RECT;\ + \ + MUL r0, t0, Weight;\ + MAD r0, t1, Weight, r0;\ + MAD r0, t2, Weight, r0;\ + MAD r0, t3, Weight, r0;\ + \ + MOV oColor, r0;\ + \ + END" +}; +/***********************************************************************************************************/ + + +/* +=============== +R_CreateExtendedName + + Creates a unique shader name taking into account lightstyles +=============== +*/ + +void R_CreateExtendedName(char *extendedName, const char *name, const short *lightmapIndex, const byte *styles) +{ + int i; + + // Set the basename + COM_StripExtension( name, extendedName ); + + // Add in lightmaps + if(lightmapIndex && styles) + { + if(lightmapIndex == lightmapsNone) + { + strcat(extendedName, "_nolightmap"); + } + else if(lightmapIndex == lightmaps2d) + { + strcat(extendedName, "_2d"); + } + else if(lightmapIndex == lightmapsVertex) + { + strcat(extendedName, "_vertex"); + } + else if(lightmapIndex == lightmapsFullBright) + { + strcat(extendedName, "_fullbright"); + } + else + { + for(i = 0; (i < 4) && (styles[i] != 255); i++) + { + switch(lightmapIndex[i]) + { + case LIGHTMAP_NONE: + strcat(extendedName, va("_style(%d,none)", styles[i])); + break; + case LIGHTMAP_2D: + strcat(extendedName, va("_style(%d,2d)", styles[i])); + break; + case LIGHTMAP_BY_VERTEX: + strcat(extendedName, va("_style(%d,vert)", styles[i])); + break; + case LIGHTMAP_WHITEIMAGE: + strcat(extendedName, va("_style(%d,fb)", styles[i])); + break; + default: + strcat(extendedName, va("_style(%d,%d)", styles[i], lightmapIndex[i])); + break; + } + } + } + } +} + +// tr_shader.c -- this file deals with the parsing and definition of shaders + +static char *s_shaderText; + +// the shader is parsed into these global variables, then copied into +// dynamically allocated memory if it is valid. +static shaderStage_t stages[MAX_SHADER_STAGES]; +static shader_t shader; +static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS]; + +#define FILE_HASH_SIZE 1024 +static shader_t* sh_hashTable[FILE_HASH_SIZE]; + +static void ClearGlobalShader(void) +{ + int i; + + memset( &shader, 0, sizeof( shader ) ); + memset( &stages, 0, sizeof( stages ) ); + for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + stages[i].mGLFogColorOverride = GLFOGOVERRIDE_NONE; + } + shader.contentFlags = CONTENTS_SOLID | CONTENTS_OPAQUE; +} + + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShaderLightMap( const char *name, const short *lightmapIndex, const byte *styles ) +{ + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Shader name exceeds MAX_QPATH\n" ); + return 0; + } + + sh = R_FindShader( name, lightmapIndex, styles, qtrue ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + +/* +================== +R_FindShaderByName + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. +================== +*/ +shader_t *R_FindShaderByName( const char *name ) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh; + + if ( (name==NULL) || (name[0] == 0) ) { // bk001205 + return tr.defaultShader; + } + + COM_StripExtension( name, strippedName ); + + hash = generateHashValue(strippedName); + + // + // see if the shader is already loaded + // + for (sh=sh_hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if (Q_stricmp(sh->name, strippedName) == 0) { + // match found + return sh; + } + } + + return tr.defaultShader; +} + +/* +void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) { + char strippedName[MAX_QPATH]; + int hash; + shader_t *sh, *sh2; + qhandle_t h; + + sh = R_FindShaderByName( shaderName ); + if (sh == NULL || sh == tr.defaultShader) { + h = RE_RegisterShaderLightMap(shaderName, lightmapsNone, stylesDefault); + sh = R_GetShaderByHandle(h); + } + if (sh == NULL || sh == tr.defaultShader) { + VID_Printf( PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName ); + return; + } + + sh2 = R_FindShaderByName( newShaderName ); + if (sh2 == NULL || sh2 == tr.defaultShader) { + h = RE_RegisterShaderLightMap(newShaderName, lightmapsNone, stylesDefault); + sh2 = R_GetShaderByHandle(h); + } + + if (sh2 == NULL || sh2 == tr.defaultShader) { + VID_Printf( PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName ); + return; + } + + // remap all the shaders with the given name + // even tho they might have different lightmaps + COM_StripExtension( shaderName, strippedName ); + hash = generateHashValue(strippedName); + for (sh = sh_hashTable[hash]; sh; sh = sh->next) { + if (Q_stricmp(sh->name, strippedName) == 0) { + if (sh != sh2) { + sh->remappedShader = sh2; + } else { + sh->remappedShader = NULL; + } + } + } + if (timeOffset) { + sh2->timeOffset = atof(timeOffset); + } +} +*/ + +/* +=============== +ParseVector +=============== +*/ +qboolean ParseVector( const char **text, int count, float *v ) { + char *token; + int i; + + // FIXME: spaces are currently required after parens, should change parseext... + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "(" ) ) { + VID_Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return qfalse; + } + + for ( i = 0 ; i < count ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + VID_Printf( PRINT_WARNING, "WARNING: missing vector element in shader '%s'\n", shader.name ); + return qfalse; + } + v[i] = atof( token ); + } + + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, ")" ) ) { + VID_Printf( PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +NameToAFunc +=============== +*/ +static unsigned NameToAFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "GT0" ) ) + { + return GLS_ATEST_GT_0; + } + else if ( !Q_stricmp( funcname, "LT128" ) ) + { + return GLS_ATEST_LT_80; + } + else if ( !Q_stricmp( funcname, "GE128" ) ) + { + return GLS_ATEST_GE_80; + } + else if ( !Q_stricmp( funcname, "GE192" ) ) + { + return GLS_ATEST_GE_C0; + } + + VID_Printf( PRINT_WARNING, "WARNING: invalid alphaFunc name '%s' in shader '%s'\n", funcname, shader.name ); + return 0; +} + + +/* +=============== +NameToSrcBlendMode +=============== +*/ +static int NameToSrcBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_SRCBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_SRCBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_DST_COLOR" ) ) + { + return GLS_SRCBLEND_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_COLOR" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_COLOR; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + return GLS_SRCBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA_SATURATE" ) ) + { + return GLS_SRCBLEND_ALPHA_SATURATE; + } + VID_Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_SRCBLEND_ONE; +} + +/* +=============== +NameToDstBlendMode +=============== +*/ +static int NameToDstBlendMode( const char *name ) +{ + if ( !Q_stricmp( name, "GL_ONE" ) ) + { + return GLS_DSTBLEND_ONE; + } + else if ( !Q_stricmp( name, "GL_ZERO" ) ) + { + return GLS_DSTBLEND_ZERO; + } + else if ( !Q_stricmp( name, "GL_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } + else if ( !Q_stricmp( name, "GL_DST_ALPHA" ) ) + { + return GLS_DSTBLEND_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_DST_ALPHA" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA; + } + else if ( !Q_stricmp( name, "GL_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_SRC_COLOR; + } + else if ( !Q_stricmp( name, "GL_ONE_MINUS_SRC_COLOR" ) ) + { + return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR; + } + VID_Printf( PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name ); + return GLS_DSTBLEND_ONE; +} + +/* +=============== +NameToGenFunc +=============== +*/ +static genFunc_t NameToGenFunc( const char *funcname ) +{ + if ( !Q_stricmp( funcname, "sin" ) ) + { + return GF_SIN; + } + else if ( !Q_stricmp( funcname, "square" ) ) + { + return GF_SQUARE; + } + else if ( !Q_stricmp( funcname, "triangle" ) ) + { + return GF_TRIANGLE; + } + else if ( !Q_stricmp( funcname, "sawtooth" ) ) + { + return GF_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "inversesawtooth" ) ) + { + return GF_INVERSE_SAWTOOTH; + } + else if ( !Q_stricmp( funcname, "noise" ) ) + { + return GF_NOISE; + } + else if ( !Q_stricmp( funcname, "random" ) ) + { + return GF_RAND; + } + + + VID_Printf( PRINT_WARNING, "WARNING: invalid genfunc name '%s' in shader '%s'\n", funcname, shader.name ); + return GF_SIN; +} + + +/* +=================== +ParseWaveForm +=================== +*/ +static void ParseWaveForm( const char **text, waveForm_t *wave ) +{ + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->func = NameToGenFunc( token ); + + // BASE, AMP, PHASE, FREQ + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name ); + return; + } + wave->frequency = atof( token ); +} + + +/* +=================== +ParseTexMod +=================== +*/ +static void ParseTexMod( const char *_text, shaderStage_t *stage ) +{ + const char *token; + const char **text = &_text; + texModInfo_t *tmi; + + if ( stage->bundle[0].numTexMods == TR_MAX_TEXMODS ) { + Com_Error( ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name ); + return; + } + + tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; + stage->bundle[0].numTexMods++; + + token = COM_ParseExt( text, qfalse ); + + // + // turb + // + if ( !Q_stricmp( token, "turb" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing tcMod turb parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_TURBULENT; + } + // + // scale + // + else if ( !Q_stricmp( token, "scale" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); //scale unioned + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); //scale unioned + tmi->type = TMOD_SCALE; + } + // + // scroll + // + else if ( !Q_stricmp( token, "scroll" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); //scroll unioned + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); //scroll unioned + tmi->type = TMOD_SCROLL; + } + // + // stretch + // + else if ( !Q_stricmp( token, "stretch" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.func = NameToGenFunc( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.base = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.phase = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name ); + return; + } + tmi->wave.frequency = atof( token ); + + tmi->type = TMOD_STRETCH; + } + // + // transform + // + else if ( !Q_stricmp( token, "transform" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[0][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->matrix[1][1] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0] = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[1] = atof( token ); + + tmi->type = TMOD_TRANSFORM; + } + // + // rotate + // + else if ( !Q_stricmp( token, "rotate" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing tcMod rotate parms in shader '%s'\n", shader.name ); + return; + } + tmi->translate[0]= atof( token ); //rotateSpeed unioned + tmi->type = TMOD_ROTATE; + } + // + // entityTranslate + // + else if ( !Q_stricmp( token, "entityTranslate" ) ) + { + tmi->type = TMOD_ENTITY_TRANSLATE; + } + else + { + VID_Printf( PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name ); + } +} + + + + +/* +/////===== Part of the VERTIGON system =====///// +=================== +ParseSurfaceSprites +=================== +*/ +// surfaceSprites +// +// NOTE: This parsing function used to be 12 pages long and very complex. The new version of surfacesprites +// utilizes optional parameters parsed in ParseSurfaceSpriteOptional. +static void ParseSurfaceSprites(const char *_text, shaderStage_t *stage ) +{ + const char *token; + const char **text = &_text; + float width, height, density, fadedist; + int sstype=SURFSPRITE_NONE; + + // + // spritetype + // + token = COM_ParseExt( text, qfalse ); + + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfaceSprites params in shader '%s'\n", shader.name ); + return; + } + + if (!Q_stricmp(token, "vertical")) + { + sstype = SURFSPRITE_VERTICAL; + } + else if (!Q_stricmp(token, "oriented")) + { + sstype = SURFSPRITE_ORIENTED; + } + else if (!Q_stricmp(token, "effect")) + { + sstype = SURFSPRITE_EFFECT; + } + else if (!Q_stricmp(token, "flattened")) + { + sstype = SURFSPRITE_FLATTENED; + } + else + { + VID_Printf( PRINT_WARNING, "WARNING: invalid type in shader '%s'\n", shader.name ); + return; + } + + // + // width + // + token = COM_ParseExt( text, qfalse ); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfaceSprites params in shader '%s'\n", shader.name ); + return; + } + width=atof(token); + if (width <= 0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid width in shader '%s'\n", shader.name ); + return; + } + + // + // height + // + token = COM_ParseExt( text, qfalse ); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfaceSprites params in shader '%s'\n", shader.name ); + return; + } + height=atof(token); + if (height <= 0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid height in shader '%s'\n", shader.name ); + return; + } + + // + // density + // + token = COM_ParseExt( text, qfalse ); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfaceSprites params in shader '%s'\n", shader.name ); + return; + } + density=atof(token); + if (density <= 0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid density in shader '%s'\n", shader.name ); + return; + } + + // + // fadedist + // + token = COM_ParseExt( text, qfalse ); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfaceSprites params in shader '%s'\n", shader.name ); + return; + } + fadedist=atof(token); + if (fadedist < 32) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid fadedist (%f < 32) in shader '%s'\n", fadedist, shader.name ); + return; + } + + if (!stage->ss) + { + stage->ss = (surfaceSprite_t *)Hunk_Alloc( sizeof( surfaceSprite_t ), qtrue ); + } + + // These are all set by the command lines. + stage->ss->surfaceSpriteType = sstype; + stage->ss->width = width; + stage->ss->height = height; + stage->ss->density = density; + stage->ss->fadeDist = fadedist; + +#ifdef _XBOX + shader.needsNormal = true; +#endif + + // These are defaults that can be overwritten. + stage->ss->fadeMax = fadedist*1.33; + stage->ss->fadeScale = 0.0; + stage->ss->wind = 0.0; + stage->ss->windIdle = 0.0; + stage->ss->variance[0] = 0.0; + stage->ss->variance[1] = 0.0; + stage->ss->facing = SURFSPRITE_FACING_NORMAL; + + // A vertical parameter that needs a default regardless + stage->ss->vertSkew; + + // These are effect parameters that need defaults nonetheless. + stage->ss->fxDuration = 1000; // 1 second + stage->ss->fxGrow[0] = 0.0; + stage->ss->fxGrow[1] = 0.0; + stage->ss->fxAlphaStart = 1.0; + stage->ss->fxAlphaEnd = 0.0; +} + + + + +/* +/////===== Part of the VERTIGON system =====///// +=========================== +ParseSurfaceSpritesOptional +=========================== +*/ +// +// ssFademax +// ssFadescale +// ssVariance +// ssHangdown +// ssAnyangle +// ssFaceup +// ssWind +// ssWindIdle +// ssVertSkew +// ssFXDuration +// ssFXGrow +// ssFXAlphaRange +// ssFXWeather +// +// Optional parameters that will override the defaults set in the surfacesprites command above. +// +static void ParseSurfaceSpritesOptional( const char *param, const char *_text, shaderStage_t *stage ) +{ + const char *token; + const char **text = &_text; + float value; + + if (!stage->ss) + { + stage->ss = (surfaceSprite_t *)Hunk_Alloc( sizeof( surfaceSprite_t ), qtrue ); + } + // + // fademax + // + if (!Q_stricmp(param, "ssFademax")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite fademax in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value <= stage->ss->fadeDist) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite fademax (%.2f <= fadeDist(%.2f)) in shader '%s'\n", value, stage->ss->fadeDist, shader.name ); + return; + } + stage->ss->fadeMax=value; + return; + } + + // + // fadescale + // + if (!Q_stricmp(param, "ssFadescale")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite fadescale in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + stage->ss->fadeScale=value; + return; + } + + // + // variance + // + if (!Q_stricmp(param, "ssVariance")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite variance width in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite variance width in shader '%s'\n", shader.name ); + return; + } + stage->ss->variance[0]=value; + + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite variance height in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite variance height in shader '%s'\n", shader.name ); + return; + } + stage->ss->variance[1]=value; + return; + } + + // + // hangdown + // + if (!Q_stricmp(param, "ssHangdown")) + { + if (stage->ss->facing != SURFSPRITE_FACING_NORMAL) + { + VID_Printf( PRINT_WARNING, "WARNING: Hangdown facing overrides previous facing in shader '%s'\n", shader.name ); + return; + } + stage->ss->facing=SURFSPRITE_FACING_DOWN; + return; + } + + // + // anyangle + // + if (!Q_stricmp(param, "ssAnyangle")) + { + if (stage->ss->facing != SURFSPRITE_FACING_NORMAL) + { + VID_Printf( PRINT_WARNING, "WARNING: Anyangle facing overrides previous facing in shader '%s'\n", shader.name ); + return; + } + stage->ss->facing=SURFSPRITE_FACING_ANY; + return; + } + + // + // faceup + // + if (!Q_stricmp(param, "ssFaceup")) + { + if (stage->ss->facing != SURFSPRITE_FACING_NORMAL) + { + VID_Printf( PRINT_WARNING, "WARNING: Faceup facing overrides previous facing in shader '%s'\n", shader.name ); + return; + } + stage->ss->facing=SURFSPRITE_FACING_UP; + return; + } + + // + // wind + // + if (!Q_stricmp(param, "ssWind")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite wind in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0.0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite wind in shader '%s'\n", shader.name ); + return; + } + stage->ss->wind=value; + if (stage->ss->windIdle <= 0) + { // Also override the windidle, it usually is the same as wind + stage->ss->windIdle = value; + } + return; + } + + // + // windidle + // + if (!Q_stricmp(param, "ssWindidle")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite windidle in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0.0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite windidle in shader '%s'\n", shader.name ); + return; + } + stage->ss->windIdle=value; + return; + } + + // + // vertskew + // + if (!Q_stricmp(param, "ssVertskew")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite vertskew in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0.0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite vertskew in shader '%s'\n", shader.name ); + return; + } + stage->ss->vertSkew=value; + return; + } + + // + // fxduration + // + if (!Q_stricmp(param, "ssFXDuration")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite duration in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value <= 0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite duration in shader '%s'\n", shader.name ); + return; + } + stage->ss->fxDuration=value; + return; + } + + // + // fxgrow + // + if (!Q_stricmp(param, "ssFXGrow")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite grow width in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite grow width in shader '%s'\n", shader.name ); + return; + } + stage->ss->fxGrow[0]=value; + + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite grow height in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite grow height in shader '%s'\n", shader.name ); + return; + } + stage->ss->fxGrow[1]=value; + return; + } + + // + // fxalpharange + // + if (!Q_stricmp(param, "ssFXAlphaRange")) + { + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite fxalpha start in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0 || value > 1.0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite fxalpha start in shader '%s'\n", shader.name ); + return; + } + stage->ss->fxAlphaStart=value; + + token = COM_ParseExt( text, qfalse); + if (token[0]==0) + { + VID_Printf( PRINT_WARNING, "WARNING: missing surfacesprite fxalpha end in shader '%s'\n", shader.name ); + return; + } + value = atof(token); + if (value < 0 || value > 1.0) + { + VID_Printf( PRINT_WARNING, "WARNING: invalid surfacesprite fxalpha end in shader '%s'\n", shader.name ); + return; + } + stage->ss->fxAlphaEnd=value; + return; + } + + // + // fxweather + // + if (!Q_stricmp(param, "ssFXWeather")) + { + if (stage->ss->surfaceSpriteType != SURFSPRITE_EFFECT) + { + VID_Printf( PRINT_WARNING, "WARNING: weather applied to non-effect surfacesprite in shader '%s'\n", shader.name ); + return; + } + stage->ss->surfaceSpriteType = SURFSPRITE_WEATHERFX; + return; + } + + // + // invalid ss command. + // + VID_Printf( PRINT_WARNING, "WARNING: invalid optional surfacesprite param '%s' in shader '%s'\n", param, shader.name ); + return; +} + + +/* +=================== +ParseStage +=================== +*/ +static qboolean ParseStage( shaderStage_t *stage, const char **text ) +{ + char *token; + int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0; + qboolean depthMaskExplicit = qfalse; + + stage->active = true; + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: no matching '}' found\n" ); + return qfalse; + } + + if ( token[0] == '}' ) + { + break; + } + // + // map + // + else if ( !Q_stricmp( token, "map" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + if ( !Q_stricmp( token, "$whiteimage" ) ) + { + stage->bundle[0].image = tr.whiteImage; + continue; + } + else if ( !Q_stricmp( token, "$lightmap" ) ) + { + stage->bundle[0].isLightmap = true; + if ( shader.lightmapIndex[0] < 0 ) { + stage->bundle[0].image = tr.whiteImage; +#ifndef FINAL_BUILD + //VID_Printf( PRINT_WARNING, "WARNING: $lightmap requested but none available '%s'\n", shader.name ); +#endif + } else { + stage->bundle[0].image = tr.lightmaps[shader.lightmapIndex[0]]; + } + continue; + } +#ifdef _XBOX + else if ( !Q_stricmp( token, "$saveGameImage") ) + { + stage->bundle[0].image = tr.saveGameImage; + continue; + } +#endif //_XBOX + else + { + stage->bundle[0].image = R_FindImageFile( token, !shader.noMipMaps, 0, 0, GL_REPEAT ); + if ( !stage->bundle[0].image ) + { + VID_Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + } +#ifdef VV_LIGHTING + // + // specularmap + // + else if ( !Q_stricmp( token, "specularmap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parameter for 'specularmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + stage->bundle[0].image = R_FindImageFile( token, !shader.noMipMaps, 0, 0, GL_REPEAT ); + if ( !stage->bundle[0].image ) + { + VID_Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + + stage->isSpecular = qtrue; + + shader.needsNormal = true; + shader.needsTangent = true; + } +#endif // VV_LIGHTING + // + // clampmap + // + else if ( !Q_stricmp( token, "clampmap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + stage->bundle[0].image = R_FindImageFile( token, !shader.noMipMaps, 0, 0, GL_CLAMP ); + if ( !stage->bundle[0].image ) + { + VID_Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + // + // animMap[/clampanimMap] .... + // + else if ( !Q_stricmp( token, "animMap" ) || !Q_stricmp( token, "clampanimMap" ) || !Q_stricmp( token, "oneshotanimMap" )) + { + #define MAX_IMAGE_ANIMATIONS 32 + image_t *images[MAX_IMAGE_ANIMATIONS]; + bool bClamp = !Q_stricmp( token, "clampanimMap" ); + bool oneShot = !Q_stricmp( token, "oneshotanimMap" ); + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parameter for '%s' keyword in shader '%s'\n", (bClamp ? "animMap":"clampanimMap"), shader.name ); + return qfalse; + } + stage->bundle[0].imageAnimationSpeed = atof( token ); + stage->bundle[0].oneShotAnimMap = oneShot; + + // parse up to MAX_IMAGE_ANIMATIONS animations + while ( 1 ) { + int num; + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) { + break; + } + num = stage->bundle[0].numImageAnimations; + if ( num < MAX_IMAGE_ANIMATIONS ) { + images[num] = R_FindImageFile( token, !shader.noMipMaps, 0, 0, bClamp?GL_CLAMP:GL_REPEAT ); + if ( !images[num] ) + { + VID_Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + stage->bundle[0].numImageAnimations++; + } + } + // Copy image ptrs into an array of ptrs + stage->bundle[0].image = (image_t*) Hunk_Alloc( stage->bundle[0].numImageAnimations * sizeof( image_t* ), qfalse ); + memcpy( stage->bundle[0].image, images, stage->bundle[0].numImageAnimations * sizeof( image_t* ) ); + } +//#ifndef _XBOX + else if ( !Q_stricmp( token, "videoMap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parameter for 'videoMap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + stage->bundle[0].videoMapHandle = CIN_PlayCinematic( token, 0, 0, 256, 256, (CIN_loop | CIN_silent | CIN_shader), NULL); + if (stage->bundle[0].videoMapHandle != -1) { + stage->bundle[0].isVideoMap = true; +#ifdef _XBOX + stage->bundle[0].image = tr.scratchImage[0]; +#else + stage->bundle[0].image = tr.scratchImage[stage->bundle[0].videoMapHandle]; +#endif + } + } +//#endif +#ifdef _XBOX + // + // bumpmap + // + else if ( !Q_stricmp( token, "bumpmap" ) ) + { + token = COM_ParseExt( text, qfalse ); + if( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parameter for 'bumpmap' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + stage->bundle[0].image = R_FindImageFile( token, !shader.noMipMaps, 0, 0, GL_REPEAT ); + if ( !stage->bundle[0].image ) + { + VID_Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + + stage->isBumpMap = qtrue; + shader.isBumpMap = qtrue; + + shader.needsNormal = true; + shader.needsTangent = true; + } +#endif + // + // alphafunc + // + else if ( !Q_stricmp( token, "alphaFunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parameter for 'alphaFunc' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + atestBits = NameToAFunc( token ); + } + // + // depthFunc + // + else if ( !Q_stricmp( token, "depthfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + + if ( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name ); + return qfalse; + } + + if ( !Q_stricmp( token, "lequal" ) ) + { + depthFuncBits = 0; + } + else if ( !Q_stricmp( token, "equal" ) ) + { + depthFuncBits = GLS_DEPTHFUNC_EQUAL; + } + else if ( !Q_stricmp( token, "disable" ) ) + { + depthFuncBits = GLS_DEPTHTEST_DISABLE; + } + else + { + VID_Printf( PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // detail + // + else if ( !Q_stricmp( token, "detail" ) ) + { +// stage->isDetail = true; + } + // + // blendfunc + // or blendfunc + // + else if ( !Q_stricmp( token, "blendfunc" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + // check for "simple" blends first + if ( !Q_stricmp( token, "add" ) ) { + blendSrcBits = GLS_SRCBLEND_ONE; + blendDstBits = GLS_DSTBLEND_ONE; + } else if ( !Q_stricmp( token, "filter" ) ) { + blendSrcBits = GLS_SRCBLEND_DST_COLOR; + blendDstBits = GLS_DSTBLEND_ZERO; + } else if ( !Q_stricmp( token, "blend" ) ) { + blendSrcBits = GLS_SRCBLEND_SRC_ALPHA; + blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else { + // complex double blends + blendSrcBits = NameToSrcBlendMode( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name ); + continue; + } + blendDstBits = NameToDstBlendMode( token ); + } + + // clear depth mask for blended surfaces + if ( !depthMaskExplicit ) + { + depthMaskBits = 0; + } + } + // + // rgbGen + // + else if ( !Q_stricmp( token, "rgbGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->rgbWave ); + stage->rgbGen = CGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + vec3_t color; + + ParseVector( text, 3, color ); + stage->constantColor[0] = 255 * color[0]; + stage->constantColor[1] = 255 * color[1]; + stage->constantColor[2] = 255 * color[2]; + + stage->rgbGen = CGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->rgbGen = CGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "identityLighting" ) ) + { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->rgbGen = CGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + if (shader.lightmapIndex[0] == LIGHTMAP_NONE) + { + VID_Printf( PRINT_ERROR, "ERROR: rgbGen vertex used on a model! in shader '%s'\n", shader.name ); + } + stage->rgbGen = CGEN_VERTEX; + if ( stage->alphaGen == 0 ) { + stage->alphaGen = AGEN_VERTEX; + } + } + else if ( !Q_stricmp( token, "exactVertex" ) ) + { + stage->rgbGen = CGEN_EXACT_VERTEX; + } + else if ( !Q_stricmp( token, "lightingDiffuse" ) ) + { + if (shader.lightmapIndex[0] != LIGHTMAP_NONE) + { + VID_Printf( PRINT_ERROR, "ERROR: rgbGen lightingDiffuse used on a misc_model! in shader '%s'\n", shader.name ); + } + stage->rgbGen = CGEN_LIGHTING_DIFFUSE; + +#ifdef _XBOX + shader.needsNormal = true; +#endif + } + else if ( !Q_stricmp( token, "lightingDiffuseEntity" ) ) + { + if (shader.lightmapIndex[0] != LIGHTMAP_NONE) + { + VID_Printf( PRINT_ERROR, "ERROR: rgbGen lightingDiffuseEntity used on a misc_model! in shader '%s'\n", shader.name ); + } + stage->rgbGen = CGEN_LIGHTING_DIFFUSE_ENTITY; + +#ifdef _XBOX + shader.needsNormal = true; +#endif + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->rgbGen = CGEN_ONE_MINUS_VERTEX; + } + else + { + VID_Printf( PRINT_ERROR, "ERROR: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // alphaGen + // + else if ( !Q_stricmp( token, "alphaGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + ParseWaveForm( text, &stage->alphaWave ); + stage->alphaGen = AGEN_WAVEFORM; + } + else if ( !Q_stricmp( token, "const" ) ) + { + token = COM_ParseExt( text, qfalse ); + stage->constantColor[3] = 255 * atof( token ); + stage->alphaGen = AGEN_CONST; + } + else if ( !Q_stricmp( token, "identity" ) ) + { + stage->alphaGen = AGEN_IDENTITY; + } + else if ( !Q_stricmp( token, "entity" ) ) + { + stage->alphaGen = AGEN_ENTITY; + } + else if ( !Q_stricmp( token, "oneMinusEntity" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_ENTITY; + } + else if ( !Q_stricmp( token, "vertex" ) ) + { + stage->alphaGen = AGEN_VERTEX; + } + else if ( !Q_stricmp( token, "lightingSpecular" ) ) + { + stage->alphaGen = AGEN_LIGHTING_SPECULAR; +#ifdef _XBOX + shader.needsNormal = true; +#endif + } + else if ( !Q_stricmp( token, "oneMinusVertex" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_VERTEX; + } + else if ( !Q_stricmp( token, "dot" ) ) + { + stage->alphaGen = AGEN_DOT; + } + else if ( !Q_stricmp( token, "oneMinusDot" ) ) + { + stage->alphaGen = AGEN_ONE_MINUS_DOT; + } + else if ( !Q_stricmp( token, "portal" ) ) + { + stage->alphaGen = AGEN_PORTAL; + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + shader.portalRange = 256; + VID_Printf( PRINT_WARNING, "WARNING: missing range parameter for alphaGen portal in shader '%s', defaulting to 256\n", shader.name ); + } + else + { + shader.portalRange = atof( token ); + } + } + else + { + VID_Printf( PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name ); + continue; + } + } + // + // tcGen + // + else if ( !Q_stricmp(token, "texgen") || !Q_stricmp( token, "tcGen" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing texgen parm in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "environment" ) ) + { + stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED; +#ifdef _XBOX + shader.needsNormal = true; +#endif + } + else if ( !Q_stricmp( token, "lightmap" ) ) + { + stage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) ) + { + stage->bundle[0].tcGen = TCGEN_TEXTURE; + } + else if ( !Q_stricmp( token, "vector" ) ) + { + stage->bundle[0].tcGenVectors = ( vec3_t *) Hunk_Alloc( 2 * sizeof( vec3_t ), qfalse ); + + ParseVector( text, 3, stage->bundle[0].tcGenVectors[0] ); + ParseVector( text, 3, stage->bundle[0].tcGenVectors[1] ); + + stage->bundle[0].tcGen = TCGEN_VECTOR; + } + else + { + VID_Printf( PRINT_WARNING, "WARNING: unknown texgen parm in shader '%s'\n", shader.name ); + } + } + // + // tcMod <...> + // + else if ( !Q_stricmp( token, "tcMod" ) ) + { + char buffer[1024] = ""; + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + strcat( buffer, token ); + strcat( buffer, " " ); + } + + ParseTexMod( buffer, stage ); + + continue; + } + // + // depthmask + // + else if ( !Q_stricmp( token, "depthwrite" ) ) + { + depthMaskBits = GLS_DEPTHMASK_TRUE; + depthMaskExplicit = qtrue; + + continue; + } + // If this stage has glow... + else if ( Q_stricmp( token, "glow" ) == 0 ) + { + stage->glow = true; + + continue; + } + // + // surfaceSprites ... + // + else if ( !Q_stricmp( token, "surfaceSprites" ) ) + { + char buffer[1024] = ""; + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + strcat( buffer, token ); + strcat( buffer, " " ); + } + + ParseSurfaceSprites( buffer, stage ); + + continue; + } + // + // ssFademax + // ssFadescale + // ssVariance + // ssHangdown + // ssAnyangle + // ssFaceup + // ssWind + // ssWindIdle + // ssDuration + // ssGrow + // ssWeather + // + else if (!Q_stricmpn(token, "ss", 2)) // <--- NOTE ONLY COMPARING FIRST TWO LETTERS + { + char buffer[1024] = ""; + char param[128]; + strcpy(param,token); + + while ( 1 ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + strcat( buffer, token ); + strcat( buffer, " " ); + } + + ParseSurfaceSpritesOptional( param, buffer, stage ); + + continue; + } + else + { + VID_Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name ); + return qfalse; + } + } + + // + // if cgen isn't explicitly specified, use either identity or identitylighting + // + if ( stage->rgbGen == CGEN_BAD ) { + if ( //blendSrcBits == 0 || + blendSrcBits == GLS_SRCBLEND_ONE || + blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) { + stage->rgbGen = CGEN_IDENTITY_LIGHTING; + } else { + stage->rgbGen = CGEN_IDENTITY; + } + } + + + // + // implicitly assume that a GL_ONE GL_ZERO blend mask disables blending + // + if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && + ( blendDstBits == GLS_DSTBLEND_ZERO ) ) + { + blendDstBits = blendSrcBits = 0; + depthMaskBits = GLS_DEPTHMASK_TRUE; + } + + // decide which agens we can skip + if ( stage->alphaGen == AGEN_IDENTITY ) { + if ( stage->rgbGen == CGEN_IDENTITY + || stage->rgbGen == CGEN_LIGHTING_DIFFUSE ) { + stage->alphaGen = AGEN_SKIP; + } + } + + // + // compute state bits + // + stage->stateBits = depthMaskBits | + blendSrcBits | blendDstBits | + atestBits | + depthFuncBits; + + return qtrue; +} + +/* +=============== +ParseDeform + +deformVertexes wave +deformVertexes normal +deformVertexes move +deformVertexes bulge +deformVertexes projectionShadow +deformVertexes autoSprite +deformVertexes autoSprite2 +deformVertexes text[0-7] +=============== +*/ +static void ParseDeform( const char **text ) { + char *token; + deformStage_t *ds; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing deform parm in shader '%s'\n", shader.name ); + return; + } + + if ( shader.numDeforms == MAX_SHADER_DEFORMS ) { + VID_Printf( PRINT_WARNING, "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name ); + return; + } + + shader.deforms[ shader.numDeforms ] = (deformStage_t *)Hunk_Alloc( sizeof( deformStage_t ), qtrue ); + + ds = shader.deforms[ shader.numDeforms ]; + shader.numDeforms++; + + if ( !Q_stricmp( token, "projectionShadow" ) ) { + ds->deformation = DEFORM_PROJECTION_SHADOW; + return; + } + + if ( !Q_stricmp( token, "autosprite" ) ) { + ds->deformation = DEFORM_AUTOSPRITE; + return; + } + + if ( !Q_stricmp( token, "autosprite2" ) ) { + ds->deformation = DEFORM_AUTOSPRITE2; + return; + } + + if ( !Q_stricmpn( token, "text", 4 ) ) { + int n; + + n = token[4] - '0'; + if ( n < 0 || n > 7 ) { + n = 0; + } + ds->deformation = (deform_t) (DEFORM_TEXT0 + n); + return; + } + + if ( !Q_stricmp( token, "bulge" ) ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeWidth = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeHeight = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name ); + return; + } + ds->bulgeSpeed = atof( token ); + + ds->deformation = DEFORM_BULGE; + return; + } + + if ( !Q_stricmp( token, "wave" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + + if ( atof( token ) != 0 ) + { + ds->deformationSpread = 1.0f / atof( token ); + } + else + { + ds->deformationSpread = 100.0f; + VID_Printf( PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_WAVE; + return; + } + + if ( !Q_stricmp( token, "normal" ) ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.amplitude = atof( token ); + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->deformationWave.frequency = atof( token ); + + ds->deformation = DEFORM_NORMALS; + return; + } + + if ( !Q_stricmp( token, "move" ) ) { + int i; + + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + VID_Printf( PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name ); + return; + } + ds->moveVector[i] = atof( token ); + } + + ParseWaveForm( text, &ds->deformationWave ); + ds->deformation = DEFORM_MOVE; + return; + } + + VID_Printf( PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name ); +} + + +/* +=============== +ParseSkyParms + +skyParms +=============== +*/ +static void ParseSkyParms( const char **text ) { + char *token; + const char *suf[6] = {"rt", "lf", "bk", "ft", "up", "dn"}; + char pathname[MAX_QPATH]; + int i; + + shader.sky = (skyParms_t *)Hunk_Alloc( sizeof( skyParms_t ), qtrue ); + + // outerbox + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + VID_Printf( PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name ); + return; + } + if ( strcmp( token, "-" ) ) { + for (i=0 ; i<6 ; i++) { + Com_sprintf( pathname, sizeof(pathname), "%s_%s", token, suf[i] ); + shader.sky->outerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, 0, GL_CLAMP ); + if ( !shader.sky->outerbox[i] ) { + if (i) { + shader.sky->outerbox[i] = shader.sky->outerbox[i-1];//not found, so let's use the previous image + }else{ + shader.sky->outerbox[i] = tr.defaultImage; + } + } + } + } + + // cloudheight + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + VID_Printf( PRINT_WARNING, "WARNING: 'skyParms' missing cloudheight in shader '%s'\n", shader.name ); + return; + } + shader.sky->cloudHeight = atof( token ); + if ( !shader.sky->cloudHeight ) { + shader.sky->cloudHeight = 512; + } + R_InitSkyTexCoords( shader.sky->cloudHeight ); + + + // innerbox + token = COM_ParseExt( text, qfalse ); + if ( strcmp( token, "-" ) ) { + VID_Printf( PRINT_WARNING, "WARNING: in shader '%s' 'skyParms', innerbox is not supported!", shader.name); + } +} + + +/* +================= +ParseSort +================= +*/ +void ParseSort( const char **text ) +{ + char *token; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) { + VID_Printf( PRINT_WARNING, "WARNING: missing sort parameter in shader '%s'\n", shader.name ); + return; + } + + if ( !Q_stricmp( token, "portal" ) ) { + shader.sort = SS_PORTAL; + } else if ( !Q_stricmp( token, "sky" ) ) { + shader.sort = SS_ENVIRONMENT; + } else if ( !Q_stricmp( token, "opaque" ) ) { + shader.sort = SS_OPAQUE; + }else if ( !Q_stricmp( token, "decal" ) ) { + shader.sort = SS_DECAL; + } else if ( !Q_stricmp( token, "seeThrough" ) ) { + shader.sort = SS_SEE_THROUGH; + } else if ( !Q_stricmp( token, "banner" ) ) { + shader.sort = SS_BANNER; + } else if ( !Q_stricmp( token, "additive" ) ) { + shader.sort = SS_BLEND1; + } else if ( !Q_stricmp( token, "nearest" ) ) { + shader.sort = SS_NEAREST; + } else if ( !Q_stricmp( token, "underwater" ) ) { + shader.sort = SS_UNDERWATER; + } else if ( !Q_stricmp( token, "inside" ) ) { + shader.sort = SS_INSIDE; + } else if ( !Q_stricmp( token, "mid_inside" ) ) { + shader.sort = SS_MID_INSIDE; + } else if ( !Q_stricmp( token, "middle" ) ) { + shader.sort = SS_MIDDLE; + } else if ( !Q_stricmp( token, "mid_outside" ) ) { + shader.sort = SS_MID_OUTSIDE; + } else if ( !Q_stricmp( token, "outside" ) ) { + shader.sort = SS_OUTSIDE; + } + else + { + shader.sort = atof( token ); + } +} + + +// this table is also present in q3map + +typedef struct { + char *name; + int clearSolid, surfaceFlags, contents; +} infoParm_t; + + +const infoParm_t infoParms[] = { + // Game content Flags + {"nonsolid", ~CONTENTS_SOLID, 0, 0 }, // special hack to clear solid flag + {"nonopaque", ~CONTENTS_OPAQUE, 0, 0 }, // special hack to clear opaque flag + {"lava", ~CONTENTS_SOLID, 0, CONTENTS_LAVA }, // very damaging + {"slime", ~CONTENTS_SOLID, 0, CONTENTS_SLIME }, // mildly damaging + {"water", ~CONTENTS_SOLID, 0, CONTENTS_WATER }, + {"fog", ~CONTENTS_SOLID, 0, CONTENTS_FOG}, // carves surfaces entering + {"shotclip", ~CONTENTS_SOLID, 0, CONTENTS_SHOTCLIP }, /* block shots, but not people */ + {"playerclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_PLAYERCLIP }, /* block only the player */ + {"monsterclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_MONSTERCLIP }, + {"botclip", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_BOTCLIP }, /* NPC do not enter */ + {"trigger", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_TRIGGER }, + {"nodrop", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc) + {"terrain", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_TERRAIN }, /* use special terrain collsion */ + {"ladder", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_LADDER }, // climb up in it like water + {"abseil", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_ABSEIL }, // can abseil down this brush + {"outside", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_OUTSIDE }, // volume is considered to be in the outside (i.e. not indoors) + {"inside", ~(CONTENTS_SOLID|CONTENTS_OPAQUE),0,CONTENTS_INSIDE }, // volume is considered to be inside (i.e. indoors) + + {"detail", -1, 0, CONTENTS_DETAIL }, // don't include in structural bsp + {"trans", -1, 0, CONTENTS_TRANSLUCENT }, // surface has an alpha component + + /* Game surface flags */ + {"sky", -1, SURF_SKY, 0 }, /* emit light from an environment map */ + {"slick", -1, SURF_SLICK, 0 }, + + {"nodamage", -1, SURF_NODAMAGE, 0 }, + {"noimpact", -1, SURF_NOIMPACT, 0 }, /* don't make impact explosions or marks */ + {"nomarks", -1, SURF_NOMARKS, 0 }, /* don't make impact marks, but still explode */ + {"nodraw", -1, SURF_NODRAW, 0 }, /* don't generate a drawsurface (or a lightmap) */ + {"nosteps", -1, SURF_NOSTEPS, 0 }, + {"nodlight", -1, SURF_NODLIGHT, 0 }, /* don't ever add dynamic lights */ + {"metalsteps", -1, SURF_METALSTEPS,0 }, + {"nomiscents", -1, SURF_NOMISCENTS,0 }, /* No misc ents on this surface */ + {"forcefield", -1, SURF_FORCEFIELD,0 }, + {"forcesight", -1, SURF_FORCESIGHT,0 }, // only visible with force sight +}; + + +/* +=============== +ParseSurfaceParm + +surfaceparm +=============== +*/ +static void ParseSurfaceParm( const char **text ) { + char *token; + int numInfoParms = sizeof(infoParms) / sizeof(infoParms[0]); + int i; + + token = COM_ParseExt( text, qfalse ); + for ( i = 0 ; i < numInfoParms ; i++ ) { + if ( !Q_stricmp( token, infoParms[i].name ) ) { + shader.surfaceFlags |= infoParms[i].surfaceFlags; + shader.contentFlags |= infoParms[i].contents; + shader.contentFlags &= infoParms[i].clearSolid; + break; + } + } +} + +/* +================= +ParseMaterial +================= +*/ +const char *materialNames[MATERIAL_LAST] = +{ + MATERIALS +}; + +static void ParseMaterial( const char **text ) +{ + char *token; + int i; + + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: missing material in shader '%s'\n", shader.name ); + return; + } + for(i = 0; i < MATERIAL_LAST; i++) + { + if ( !stricmp( token, materialNames[i] ) ) + { + shader.surfaceFlags &= ~MATERIAL_MASK;//safety, clear it first + shader.surfaceFlags |= i; + break; + } + } +} + + +/* +================= +ParseShader + +The current text pointer is at the explicit text definition of the +shader. Parse it into the global shader variable. Later functions +will optimize it. +================= +*/ +static qboolean ParseShader( const char **text ) +{ + char *token; + int s = 0; + +#ifdef _XBOX + shader.needsNormal = false; + shader.needsTangent = false; +#endif + + token = COM_ParseExt( text, qtrue ); + if ( token[0] != '{' ) + { + VID_Printf( PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name ); + return qfalse; + } + + while ( 1 ) + { + token = COM_ParseExt( text, qtrue ); + if ( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: no concluding '}' in shader %s\n", shader.name ); + return qfalse; + } + + // end of shader definition + if ( token[0] == '}' ) + { + break; + } + // stage definition + else if ( token[0] == '{' ) + { + if ( !ParseStage( &stages[s], text ) ) + { + return qfalse; + } + stages[s].active = true; +//#ifndef _XBOX // GLOWXXX + if ( stages[s].glow ) + { + shader.hasGlow = true; + } +//#endif + s++; + continue; + } + // sun parms + else if ( !Q_stricmp( token, "q3map_sun" ) || !Q_stricmp( token, "sun" )) { + float a, b; + + token = COM_ParseExt( text, qfalse ); + tr.sunLight[0] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[1] = atof( token ); + token = COM_ParseExt( text, qfalse ); + tr.sunLight[2] = atof( token ); + + VectorNormalize( tr.sunLight ); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + VectorScale( tr.sunLight, a, tr.sunLight); + + token = COM_ParseExt( text, qfalse ); + a = atof( token ); + a = a / 180 * M_PI; + + token = COM_ParseExt( text, qfalse ); + b = atof( token ); + b = b / 180 * M_PI; + + tr.sunDirection[0] = cos( a ) * cos( b ); + tr.sunDirection[1] = sin( a ) * cos( b ); + tr.sunDirection[2] = sin( b ); + +#ifdef _XBOX + Cvar_SetValue( "r_sundir_x", tr.sunDirection[0] ); + Cvar_SetValue( "r_sundir_y", tr.sunDirection[1] ); + Cvar_SetValue( "r_sundir_z", tr.sunDirection[2] ); +#endif + } + else if ( !Q_stricmp( token, "deformVertexes" ) ) { + ParseDeform( text ); + continue; + } + else if ( !Q_stricmp( token, "tesssize" ) ) { + SkipRestOfLine( text ); + continue; + } + // skip stuff that only the QuakeEdRadient needs + else if ( !Q_stricmpn( token, "qer", 3 ) ) { + SkipRestOfLine( text ); + continue; + } + // skip stuff that only the q3map needs + else if ( !Q_stricmpn( token, "q3map", 5 ) ) { + SkipRestOfLine( text ); + continue; + } + // material deprecated as of 11 Jan 01 + // material undeprecated as of 7 May 01 - q3map_material deprecated + else if ( !stricmp( token, "material" ) || !stricmp( token, "q3map_material" ) ) + { + ParseMaterial( text ); + } + // skip stuff that JK2 doesn't use + else if ( !Q_stricmp( token, "lightColor") ) { + SkipRestOfLine( text ); + continue; + } + // surface parms + else if ( !Q_stricmp( token, "surfaceParm" ) ) { + ParseSurfaceParm( text ); + continue; + } + // no mip maps + else if ( !Q_stricmp( token, "nomipmaps" ) ) + { + shader.noMipMaps = true; +// shader.noPicMip = true; + continue; + } + // no picmip adjustment + else if ( !Q_stricmp( token, "nopicmip" ) ) + { +// shader.noPicMip = true; + continue; + } + // polygonOffset + else if ( !Q_stricmp( token, "polygonOffset" ) ) + { + shader.polygonOffset = true; + continue; + } + // polygonOffset + else if ( !Q_stricmp( token, "noTC" ) ) + { +// shader.noTC = true; + continue; + } + // entityMergable, allowing sprite surfaces from multiple entities + // to be merged into one batch. This is a savings for smoke + // puffs and blood, but can't be used for anything where the + // shader calcs (not the surface function) reference the entity color or scroll + else if ( !Q_stricmp( token, "entityMergable" ) ) + { + shader.entityMergable = true; + continue; + } + // fogParms + else if ( !Q_stricmp( token, "fogParms" ) ) + { + shader.fogParms = (fogParms_t *)Hunk_Alloc( sizeof( fogParms_t ), qtrue ); + if ( !ParseVector( text, 3, shader.fogParms->color ) ) { + return qfalse; + } + + token = COM_ParseExt( text, qfalse ); + if ( !token[0] ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name ); + continue; + } + shader.fogParms->depthForOpaque = atof( token ); + + // skip any old gradient directions + SkipRestOfLine( text ); + continue; + } + // portal + else if ( !Q_stricmp(token, "portal") ) + { + shader.sort = SS_PORTAL; + continue; + } + // skyparms + else if ( !Q_stricmp( token, "skyparms" ) ) + { + ParseSkyParms( text ); + continue; + } + // light determines flaring in q3map, not needed here + else if ( !Q_stricmp(token, "light") ) + { + token = COM_ParseExt( text, qfalse ); + continue; + } + // cull + else if ( !Q_stricmp( token, "cull") ) + { + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + { + VID_Printf( PRINT_WARNING, "WARNING: missing cull parms in shader '%s'\n", shader.name ); + continue; + } + + if ( !Q_stricmp( token, "none" ) || !Q_stricmp( token, "twosided" ) || !Q_stricmp( token, "disable" ) ) + { + shader.cullType = CT_TWO_SIDED; + } + else if ( !Q_stricmp( token, "back" ) || !Q_stricmp( token, "backside" ) || !Q_stricmp( token, "backsided" ) ) + { + shader.cullType = CT_BACK_SIDED; + } + else + { + VID_Printf( PRINT_WARNING, "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name ); + } + continue; + } + // sort + else if ( !Q_stricmp( token, "sort" ) ) + { + ParseSort( text ); + continue; + } +/* +Ghoul2 Insert Start +*/ + + // + // location hit mesh load + // + else if ( !Q_stricmp( token, "hitLocation" ) ) + { + + // grab the filename of the hit location texture + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + continue; + } + // + // location hit material mesh load + // + else if ( !Q_stricmp( token, "hitMaterial" ) ) + { + + // grab the filename of the hit location texture + token = COM_ParseExt( text, qfalse ); + if ( token[0] == 0 ) + break; + continue; + + } +/* +Ghoul2 Insert End +*/ + + else + { + VID_Printf( PRINT_WARNING, "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name ); + return qfalse; + } + } + + // + // ignore shaders that don't have any stages, unless it is a sky or fog + // + if ( s == 0 && !shader.sky && !(shader.contentFlags & CONTENTS_FOG ) ) { + return qfalse; + } + + shader.explicitlyDefined = true; + + return qtrue; +} + +/* +======================================================================================== + +SHADER OPTIMIZATION AND FOGGING + +======================================================================================== +*/ + +typedef struct { + int blendA; + int blendB; + + int multitextureEnv; + int multitextureBlend; +} collapse_t; + +static collapse_t collapse[] = { + { 0, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, 0 }, + + { 0, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, 0 }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, + GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR }, + + { 0, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, 0 }, + + { GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, + GL_ADD, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE }, +#if 0 + { 0, GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_SRCBLEND_SRC_ALPHA, + GL_DECAL, 0 }, +#endif + { -1 } +}; + +/* +================ +CollapseMultitexture + +Attempt to combine two stages into a single multitexture stage +FIXME: I think modulated add + modulated add collapses incorrectly +================= +*/ +static qboolean CollapseMultitexture( void ) { + int abits, bbits; + int i; + textureBundle_t tmpBundle; + + if ( !qglActiveTextureARB ) { + return qfalse; + } + + // make sure both stages are active + if ( !stages[0].active || !stages[1].active ) { + return qfalse; + } + + abits = stages[0].stateBits; + bbits = stages[1].stateBits; + + // make sure that both stages have identical state other than blend modes + if ( ( abits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) != + ( bbits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) ) { + return qfalse; + } + + abits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + bbits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + + // search for a valid multitexture blend function + for ( i = 0; collapse[i].blendA != -1 ; i++ ) { + if ( abits == collapse[i].blendA + && bbits == collapse[i].blendB ) { + break; + } + } + + // nothing found + if ( collapse[i].blendA == -1 ) { + return qfalse; + } + + // GL_ADD is a separate extension + if ( collapse[i].multitextureEnv == GL_ADD && !glConfig.textureEnvAddAvailable ) { + return qfalse; + } + + // make sure waveforms have identical parameters + if (( stages[0].rgbGen != stages[1].rgbGen ) || + ( stages[0].alphaGen != stages[1].alphaGen ) ) { + return qfalse; + } + + // an add collapse can only have identity colors + if ( collapse[i].multitextureEnv == GL_ADD && stages[0].rgbGen != CGEN_IDENTITY ) { + return qfalse; + } + + if ( stages[0].rgbGen == CGEN_WAVEFORM ) + { + if ( memcmp( &stages[0].rgbWave, + &stages[1].rgbWave, + sizeof( stages[0].rgbWave ) ) ) + { + return qfalse; + } + } + if ( stages[0].alphaGen == CGEN_WAVEFORM ) + { + if ( memcmp( &stages[0].alphaWave, + &stages[1].alphaWave, + sizeof( stages[0].alphaWave ) ) ) + { + return qfalse; + } + } + + + // make sure that lightmaps are in bundle 1 for 3dfx + if ( stages[0].bundle[0].isLightmap ) + { + tmpBundle = stages[0].bundle[0]; + stages[0].bundle[0] = stages[1].bundle[0]; + stages[0].bundle[1] = tmpBundle; + } + else + { + stages[0].bundle[1] = stages[1].bundle[0]; + } + + // set the new blend state bits + shader.multitextureEnv = collapse[i].multitextureEnv; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= collapse[i].multitextureBlend; + + // + // move down subsequent shaders + // + memmove( &stages[1], &stages[2], sizeof( stages[0] ) * ( MAX_SHADER_STAGES - 2 ) ); + memset( &stages[MAX_SHADER_STAGES-1], 0, sizeof( stages[0] ) ); + + return qtrue; +} + + +/* +============== +SortNewShader + +Positions the most recently created shader in the tr.sortedShaders[] +array so that the shader->sort key is sorted reletive to the other +shaders. + +Sets shader->sortedIndex +============== +*/ +static void SortNewShader( void ) { + int i; + float sort; + shader_t *newShader; + + newShader = tr.shaders[ tr.numShaders - 1 ]; + sort = newShader->sort; + + for ( i = tr.numShaders - 2 ; i >= 0 ; i-- ) { + if ( tr.sortedShaders[ i ]->sort <= sort ) { + break; + } + tr.sortedShaders[i+1] = tr.sortedShaders[i]; + tr.sortedShaders[i+1]->sortedIndex++; + } + + newShader->sortedIndex = i+1; + tr.sortedShaders[i+1] = newShader; +} + + +/* +==================== +GeneratePermanentShader +==================== +*/ +static shader_t *GeneratePermanentShader( void ) { + shader_t *newShader; + int i, b; + int size; + + if ( tr.numShaders == MAX_SHADERS ) { + tr.iNumDeniedShaders++; + VID_Printf( PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS (%d) hit (overflowed by %d)\n", MAX_SHADERS, tr.iNumDeniedShaders); + return tr.defaultShader; + } + + newShader = (shader_t *)Hunk_Alloc( sizeof( shader_t ), qtrue ); + + *newShader = shader; + + if ( shader.sort <= /*SS_OPAQUE*/SS_SEE_THROUGH ) { + newShader->fogPass = FP_EQUAL; + } else if ( shader.contentFlags & CONTENTS_FOG ) { + newShader->fogPass = FP_LE; + } + + tr.shaders[ tr.numShaders ] = newShader; + newShader->index = tr.numShaders; + + tr.sortedShaders[ tr.numShaders ] = newShader; + newShader->sortedIndex = tr.numShaders; + + tr.numShaders++; + + size = newShader->numUnfoggedPasses ? newShader->numUnfoggedPasses * sizeof( stages[0] ) : sizeof( stages[0] ); + newShader->stages = (shaderStage_t *) Hunk_Alloc( size, qtrue ); + + for ( i = 0 ; i < newShader->numUnfoggedPasses ; i++ ) { + if ( !stages[i].active ) { + break; + } + newShader->stages[i] = stages[i]; + + for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) { + if (newShader->stages[i].bundle[b].numTexMods) + { + size = newShader->stages[i].bundle[b].numTexMods * sizeof( texModInfo_t ); + newShader->stages[i].bundle[b].texMods = (texModInfo_t *) Hunk_Alloc( size, qfalse ); + memcpy( newShader->stages[i].bundle[b].texMods, stages[i].bundle[b].texMods, size ); + } + else + { + newShader->stages[i].bundle[b].texMods = 0; //clear the globabl ptr jic + } + } + } + + SortNewShader(); + + // Super hack. Actually, it's an optimization to an existing hack: + extern int zfFaceShaders[3]; + extern int tfTorsoShader; + if( strstr(newShader->name, "jedi_zf/face_01") ) + zfFaceShaders[0] = newShader->index; + else if( strstr(newShader->name, "jedi_zf/face_02") ) + zfFaceShaders[1] = newShader->index; + else if( strstr(newShader->name, "jedi_zf/face_03") ) + zfFaceShaders[2] = newShader->index; + else if( strstr(newShader->name, "jedi_tf/torso_03_clothes") ) + tfTorsoShader = newShader->index; + + const int hash = generateHashValue(newShader->name); + newShader->next = sh_hashTable[hash]; + sh_hashTable[hash] = newShader; + + return newShader; +} + +/* +================= +VertexLightingCollapse + +If vertex lighting is enabled, only render a single +pass, trying to guess which is the correct one to best aproximate +what it is supposed to look like. + + OUTPUT: Number of stages after the collapse (in the case of surfacesprites this isn't one). +================= +*/ +static int VertexLightingCollapse( void ) { + int stage, nextopenstage; + shaderStage_t *bestStage; + int bestImageRank; + int rank; + int finalstagenum=1; + + // if we aren't opaque, just use the first pass + if ( shader.sort == SS_OPAQUE ) { + + // pick the best texture for the single pass + bestStage = &stages[0]; + bestImageRank = -999999; + + for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + rank = 0; + + if ( pStage->bundle[0].isLightmap ) { + rank -= 100; + } + if ( pStage->bundle[0].tcGen != TCGEN_TEXTURE ) { + rank -= 5; + } + if ( pStage->bundle[0].numTexMods ) { + rank -= 5; + } + if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING ) { + rank -= 3; + } + + // SurfaceSprites are most certainly NOT desireable as the collapsed surface texture. + if ( pStage->ss && pStage->ss->surfaceSpriteType) + { + rank -= 1000; + } + + if ( rank > bestImageRank ) { + bestImageRank = rank; + bestStage = pStage; + } + } + + stages[0].bundle[0] = bestStage->bundle[0]; + stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS ); + stages[0].stateBits |= GLS_DEPTHMASK_TRUE; + if ( shader.lightmapIndex[0] == LIGHTMAP_NONE ) { + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; +#ifdef _XBOX + shader.needsNormal = true; +#endif + } else { + stages[0].rgbGen = CGEN_EXACT_VERTEX; + } + stages[0].alphaGen = AGEN_SKIP; + } else { + // don't use a lightmap (tesla coils) + if ( stages[0].bundle[0].isLightmap ) { + stages[0] = stages[1]; + } + + // if we were in a cross-fade cgen, hack it to normal + if ( stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH ) + && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH ) ) { + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + } + } + + for ( stage=1, nextopenstage=1; stage < MAX_SHADER_STAGES; stage++ ) { + shaderStage_t *pStage = &stages[stage]; + + if ( !pStage->active ) { + break; + } + + if (pStage->ss && pStage->ss->surfaceSpriteType) + { + // Copy this stage to the next open stage list (that is, we don't want any inactive stages before this one) + if (nextopenstage != stage) + { + stages[nextopenstage] = *pStage; + stages[nextopenstage].bundle[0] = pStage->bundle[0]; + } + nextopenstage++; + finalstagenum++; + continue; + } + + memset( pStage, 0, sizeof( *pStage ) ); + } + + return finalstagenum; +} + +/* +========================= +FinishShader + +Returns a freshly allocated shader with all the needed info +from the current global working shader +========================= +*/ +static shader_t *FinishShader( void ) { + int stage, lmStage, stageIndex; + qboolean hasLightmapStage; + + hasLightmapStage = qfalse; + + // + // set sky stuff appropriate + // + if ( shader.sky ) { + shader.sort = SS_ENVIRONMENT; + } + + // + // set polygon offset + // + if ( shader.polygonOffset && !shader.sort ) { + shader.sort = SS_DECAL; + } + + for(lmStage=0;lmStageactive && pStage->bundle[0].isLightmap) + { + break; + } + } + + if (lmStage < MAX_SHADER_STAGES) + { + if (shader.lightmapIndex[0] == LIGHTMAP_BY_VERTEX) + { + if (lmStage == 0) //< MAX_SHADER_STAGES-1) + {//copy the rest down over the lightmap slot + memmove(&stages[lmStage], &stages[lmStage+1], sizeof(shaderStage_t) * (MAX_SHADER_STAGES-lmStage-1)); + memset(&stages[MAX_SHADER_STAGES-1], 0, sizeof(shaderStage_t)); + //change blending on the moved down stage + stages[lmStage].stateBits = GLS_DEFAULT; + } + //change anything that was moved down (or the *white if LM is first) to use vertex color + stages[lmStage].rgbGen = CGEN_EXACT_VERTEX; + stages[lmStage].alphaGen = AGEN_SKIP; + lmStage = MAX_SHADER_STAGES; //skip the style checking below + } + } + + if (lmStage < MAX_SHADER_STAGES)// && !r_fullbright->value) + { + int numStyles; + int i; + + for(numStyles=0;numStyles= LS_UNUSED) + { + break; + } + } + numStyles--; + if (numStyles > 0) + { + for(i=MAX_SHADER_STAGES-1;i>lmStage+numStyles;i--) + { + stages[i] = stages[i-numStyles]; + } + + for(i=0;iactive ) { + break; + } + + // check for a missing texture + if ( !pStage->bundle[0].image ) { + VID_Printf( PRINT_WARNING, "Shader %s has a stage with no image\n", shader.name ); + pStage->active = false; + break; + } + + // + // ditch this stage if it's detail and detail textures are disabled + // +#ifndef _XBOX + if ( pStage->isDetail && !r_detailTextures->integer ) { + if ( stage < ( MAX_SHADER_STAGES - 1 ) ) { + memmove( pStage, pStage + 1, sizeof( *pStage ) * ( MAX_SHADER_STAGES - stage - 1 ) ); + memset( pStage + ( MAX_SHADER_STAGES - stage - 1 ), 0, sizeof( *pStage ) ); //clear the last one moved down + stage--; //look at this stage next time around + } + continue; + } +#endif + + pStage->index = stageIndex; + + // + // default texture coordinate generation + // + if ( pStage->bundle[0].isLightmap ) { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_LIGHTMAP; + } + hasLightmapStage = qtrue; + } else { + if ( pStage->bundle[0].tcGen == TCGEN_BAD ) { + pStage->bundle[0].tcGen = TCGEN_TEXTURE; + } + } + + // + // determine sort order and fog color adjustment + // + if ( ( pStage->stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) && + ( stages[0].stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) ) { + int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS; + int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS; + + // fog color adjustment only works for blend modes that have a contribution + // that aproaches 0 as the modulate values aproach 0 -- + // GL_ONE, GL_ONE + // GL_ZERO, GL_ONE_MINUS_SRC_COLOR + // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA + + // modulate, additive + if ( ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE ) ) || + ( ( blendSrcBits == GLS_SRCBLEND_ZERO ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR ) ) ) { + pStage->adjustColorsForFog = ACFF_MODULATE_RGB; + } + // strict blend + else if ( ( blendSrcBits == GLS_SRCBLEND_SRC_ALPHA ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA; + } + // premultiplied alpha + else if ( ( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ) ) + { + pStage->adjustColorsForFog = ACFF_MODULATE_RGBA; + } else { + // we can't adjust this one correctly, so it won't be exactly correct in fog + } + + // don't screw with sort order if this is a portal or environment + if ( !shader.sort ) { + // see through item, like a grill or grate + if ( pStage->stateBits & GLS_DEPTHMASK_TRUE ) + { + shader.sort = SS_SEE_THROUGH; + } + else + { + if (( blendSrcBits == GLS_SRCBLEND_ONE ) && ( blendDstBits == GLS_DSTBLEND_ONE )) + { + // GL_ONE GL_ONE needs to come a bit later + shader.sort = SS_BLEND1; + } + else + { + shader.sort = SS_BLEND0; + } + } + } + } + + //rww - begin hw fog + if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_ONE|GLS_DSTBLEND_ONE)) + { + pStage->mGLFogColorOverride = GLFOGOVERRIDE_BLACK; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_SRC_ALPHA|GLS_DSTBLEND_ONE) && + pStage->alphaGen == AGEN_LIGHTING_SPECULAR && stage) + { + pStage->mGLFogColorOverride = GLFOGOVERRIDE_BLACK; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_ZERO|GLS_DSTBLEND_ZERO)) + { + pStage->mGLFogColorOverride = GLFOGOVERRIDE_WHITE; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_ONE|GLS_DSTBLEND_ZERO)) + { + pStage->mGLFogColorOverride = GLFOGOVERRIDE_WHITE; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == 0 && stage) + { // + pStage->mGLFogColorOverride = GLFOGOVERRIDE_WHITE; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == 0 && pStage->bundle[0].isLightmap && stage < MAX_SHADER_STAGES-1 && + stages[stage+1].bundle[0].isLightmap) + { // multiple light map blending + pStage->mGLFogColorOverride = GLFOGOVERRIDE_WHITE; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_DST_COLOR|GLS_DSTBLEND_ZERO) && pStage->bundle[0].isLightmap) + { //I don't know, it works. -rww + pStage->mGLFogColorOverride = GLFOGOVERRIDE_WHITE; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_DST_COLOR|GLS_DSTBLEND_ZERO)) + { //I don't know, it works. -rww + pStage->mGLFogColorOverride = GLFOGOVERRIDE_BLACK; + } + else if ((pStage->stateBits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_ONE|GLS_DSTBLEND_ONE_MINUS_SRC_COLOR)) + { //I don't know, it works. -rww + pStage->mGLFogColorOverride = GLFOGOVERRIDE_BLACK; + } + else + { + pStage->mGLFogColorOverride = GLFOGOVERRIDE_NONE; + } + //rww - end hw fog + + stageIndex++; + } + + // there are times when you will need to manually apply a sort to + // opaque alpha tested shaders that have later blend passes + if ( !shader.sort ) { + shader.sort = SS_OPAQUE; + } + + // + // if we are in r_vertexLight mode, never use a lightmap texture + // + if ( stage > 1 && ( r_vertexLight->integer ) ) { + stage = VertexLightingCollapse(); + hasLightmapStage = qfalse; + } + + // + // look for multitexture potential + // + if ( stage > 1 && CollapseMultitexture() ) { + stage--; + } + +#ifdef _XBOX + for(int i = 0; i < MAX_SHADER_STAGES; i++) + { + if(stages[i].isBumpMap) + { + // Bumpmap can't be the first stage + assert(i > 0); + + if(stages[i - 1].bundle[1].image) + { + // Previous stage has already been collapsed + stages[i].bundle[1] = stages[i].bundle[0]; + stages[i].bundle[0] = stages[i - 1].bundle[0]; + } + else + { + stages[i - 1].bundle[1] = stages[i].bundle[0]; + stages[i - 1].isBumpMap = qtrue; + + // move down subsequent shaders + memmove( &stages[i], &stages[i+1], sizeof( stages[i-1] ) * ( MAX_SHADER_STAGES - 2 ) ); + memset( &stages[MAX_SHADER_STAGES-1], 0, sizeof( stages[i-1] ) ); + + stage--; + } + } + } +#endif + + + if ( shader.lightmapIndex[0] >= 0 && !hasLightmapStage ) { + VID_Printf( PRINT_ERROR, "ERROR: shader '%s' has lightmap but no lightmap stage!\n", shader.name ); + memcpy(shader.lightmapIndex, lightmapsNone, sizeof(shader.lightmapIndex)); + memcpy(shader.styles, stylesDefault, sizeof(shader.styles)); + } + + + // + // compute number of passes + // + shader.numUnfoggedPasses = stage; + + // fogonly shaders don't have any normal passes + if ( stage == 0 ) { + shader.sort = SS_FOG; + } + + return GeneratePermanentShader(); +} + +//======================================================================================== + +/* +==================== +FindShaderInShaderText + +Scans the combined text description of all the shader files for +the given shader name. + +return NULL if not found + +If found, it will return a valid shader +===================== +*/ +static const char *FindShaderInShaderText( const char *shadername ) { + char *p = s_shaderText; + + if ( !p ) { + return NULL; + } + +#ifdef USE_STL_FOR_SHADER_LOOKUPS + + char sLowerCaseName[MAX_QPATH]; + Q_strncpyz(sLowerCaseName,shadername,sizeof(sLowerCaseName)); + strlwr(sLowerCaseName); // Q_strlwr is pretty gay, so I'm not using it + + return ShaderEntryPtrs_Lookup(sLowerCaseName); + +#else + + char *token; + + // look for label + // note that this could get confused if a shader name is used inside + // another shader definition + while ( 1 ) { + + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + if ( token[0] == '{' ) { + // skip the definition + SkipBracedSection( &p ); + } else if ( !Q_stricmp( token, shadername ) ) { + return p; + } else { + // skip to end of line + SkipRestOfLine( &p ); + } + } + + return NULL; + +#endif +} + +inline qboolean IsShader(shader_t *sh, const char *name, const short *lightmapIndex, const byte *styles) +{ + int i; + + if (Q_stricmp(sh->name, name)) + { + return qfalse; + } + + if (!sh->defaultShader) + { + for(i=0;ilightmapIndex[i] != lightmapIndex[i]) + { + return qfalse; + } + if (sh->styles[i] != styles[i]) + { + return qfalse; + } + } + } + + return qtrue; +} + +/* +=============== +R_FindLightmap ( needed for -external LMs created by ydnar's q3map2 ) +given a (potentially erroneous) lightmap index, attempts to load +an external lightmap image and/or sets the index to a valid number +=============== +*/ +#define EXTERNAL_LIGHTMAP "lm_%04d.tga" // THIS MUST BE IN SYNC WITH Q3MAP2 +static inline const short *R_FindLightmap( const short *lightmapIndex ) +{ + image_t *image; + char fileName[ MAX_QPATH ]; + + // don't bother with vertex lighting + if( *lightmapIndex < 0 ) + return lightmapIndex; + + // does this lightmap already exist? + if( *lightmapIndex < tr.numLightmaps && tr.lightmaps[ *lightmapIndex ] != NULL ) + return lightmapIndex; + + // bail if no world dir + if( tr.worldDir == NULL ) + { + return lightmapsVertex; + } + + // sync up render thread, because we're going to have to load an image + //R_SyncRenderThread(); + + // attempt to load an external lightmap + sprintf( fileName, "$%s/" EXTERNAL_LIGHTMAP, tr.worldDir, *lightmapIndex ); + image = R_FindImageFile( fileName, qfalse, qfalse, r_ext_compressed_lightmaps->integer, GL_CLAMP ); + if( image == NULL ) + { + return lightmapsVertex; + } + + // add it to the lightmap list + if( *lightmapIndex >= tr.numLightmaps ) + tr.numLightmaps = *lightmapIndex + 1; + tr.lightmaps[ *lightmapIndex ] = image; + return lightmapIndex; +} + +/* +=============== +R_FindShader + +Will always return a valid shader, but it might be the +default shader if the real one can't be found. + +In the interest of not requiring an explicit shader text entry to +be defined for every single image used in the game, three default +shader behaviors can be auto-created for any image: + +If lightmapIndex == LIGHTMAP_NONE, then the image will have +dynamic diffuse lighting applied to it, as apropriate for most +entity skin surfaces. + +If lightmapIndex == LIGHTMAP_2D, then the image will be used +for 2D rendering unless an explicit shader is found + +If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use +the vertex rgba modulate values, as apropriate for misc_model +pre-lit surfaces. + +Other lightmapIndex values will have a lightmap stage created +and src*dest blending applied with the texture, as apropriate for +most world construction surfaces. +=============== +*/ +shader_t *R_FindShader( const char *name, const short *lightmapIndex, const byte *styles, qboolean mipRawImage ) { + char strippedName[MAX_QPATH]; + int hash; + const char *shaderText; + image_t *image; + shader_t *sh; + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( S_COLOR_RED"Shader name exceeds MAX_QPATH! %s\n",name ); + return tr.defaultShader; + } + if ( name[0] == 0 ) { + return tr.defaultShader; + } + + // use (fullbright) vertex lighting if the bsp file doesn't have + // lightmaps +/* if ( lightmapIndex[0] >= 0 && lightmapIndex[0] >= tr.numLightmaps ) { + lightmapIndex = lightmapsVertex; + } +*/ + lightmapIndex = R_FindLightmap(lightmapIndex); + + COM_StripExtension( name, strippedName ); + + hash = generateHashValue(strippedName); + + // + // see if the shader is already loaded + // + for (sh=sh_hashTable[hash]; sh; sh=sh->next) { + // NOTE: if there was no shader or image available with the name strippedName + // then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we + // have to check all default shaders otherwise for every call to R_FindShader + // with that same strippedName a new default shader is created. + if (IsShader(sh, strippedName, lightmapIndex, styles)) + { // match found + return sh; + } + } + + // make sure the render thread is stopped, because we are probably + // going to have to upload an image + //R_SyncRenderThread(); + + // clear the global shader + ClearGlobalShader(); + Q_strncpyz(shader.name, strippedName, sizeof(shader.name)); + memcpy(shader.lightmapIndex, lightmapIndex, sizeof(shader.lightmapIndex)); + memcpy(shader.styles, styles, sizeof(shader.styles)); + + // + // attempt to define shader from an explicit parameter file + // + shaderText = FindShaderInShaderText( strippedName ); + if ( shaderText ) { + if ( !ParseShader( &shaderText ) ) { + // had errors, so use default shader + shader.defaultShader = true; + } + sh = FinishShader(); + return sh; + } + + + // + // if not defined in the in-memory shader descriptions, + // look for a single TGA, BMP, or PCX + // + image = R_FindImageFile( name, mipRawImage, mipRawImage, qtrue, mipRawImage ? GL_REPEAT : GL_CLAMP ); + if ( !image ) { + if (strncmp(name, "levelshots", 10 ) && strcmp(name, "*off")) + { //hide these warnings + VID_Printf( PRINT_WARNING, "WARNING: Couldn't find image for shader %s\n", name ); + } + shader.defaultShader = true; + return FinishShader(); + } + + // + // create the default shading commands + // + if ( shader.lightmapIndex[0] == LIGHTMAP_NONE ) { + // dynamic colors at vertexes + stages[0].bundle[0].image = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE; + stages[0].stateBits = GLS_DEFAULT; +#ifdef _XBOX + shader.needsNormal = true; +#endif + } else if ( shader.lightmapIndex[0] == LIGHTMAP_BY_VERTEX ) { + // explicit colors at vertexes + stages[0].bundle[0].image = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_SKIP; + stages[0].stateBits = GLS_DEFAULT; + } else if ( shader.lightmapIndex[0] == LIGHTMAP_2D ) { + // GUI elements + stages[0].bundle[0].image = image; + stages[0].active = true; + stages[0].rgbGen = CGEN_VERTEX; + stages[0].alphaGen = AGEN_VERTEX; + stages[0].stateBits = GLS_DEPTHTEST_DISABLE | + GLS_SRCBLEND_SRC_ALPHA | + GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; + } else if ( shader.lightmapIndex[0] == LIGHTMAP_WHITEIMAGE ) { + // fullbright level + stages[0].bundle[0].image = tr.whiteImage; + stages[0].active = true; + stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image = image; + stages[1].active = true; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } else { + // two pass lightmap + stages[0].bundle[0].image = tr.lightmaps[shader.lightmapIndex[0]]; + stages[0].bundle[0].isLightmap = true; + stages[0].active = true; + stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation + // for identitylight + // light map 0 should always be style 0, which means + // that this will always be on + stages[0].stateBits = GLS_DEFAULT; + + stages[1].bundle[0].image = image; + stages[1].active = true; + stages[1].rgbGen = CGEN_IDENTITY; + stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; + } + + return FinishShader(); +} + +/* +==================== +RE_RegisterShader + +This is the exported shader entry point for the rest of the system +It will always return an index that will be valid. + +This should really only be used for explicit shaders, because there is no +way to ask for different implicit lighting modes (vertex, lightmap, etc) +==================== +*/ +qhandle_t RE_RegisterShader( const char *name ) { + shader_t *sh; + + sh = R_FindShader( name, lightmaps2d, stylesDefault, qtrue ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +RE_RegisterShaderNoMip + +For menu graphics that should never be picmiped +==================== +*/ +qhandle_t RE_RegisterShaderNoMip( const char *name ) { + shader_t *sh; + + sh = R_FindShader( name, lightmaps2d, stylesDefault, qfalse ); + + // we want to return 0 if the shader failed to + // load for some reason, but R_FindShader should + // still keep a name allocated for it, so if + // something calls RE_RegisterShader again with + // the same name, we don't try looking for it again + if ( sh->defaultShader ) { + return 0; + } + + return sh->index; +} + + +/* +==================== +R_GetShaderByHandle + +When a handle is passed in by another module, this range checks +it and returns a valid (possibly default) shader_t to be used internally. +==================== +*/ +shader_t *R_GetShaderByHandle( qhandle_t hShader ) { + if ( hShader < 0 ) { + VID_Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + if ( hShader >= tr.numShaders ) { + VID_Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); + return tr.defaultShader; + } + return tr.shaders[hShader]; +} + +/* +=============== +R_ShaderList_f + +Dump information on all valid shaders to the console +A second parameter will cause it to print in sorted order +=============== +*/ +void R_ShaderList_f (void) { + int i; + int count; + shader_t *shader; + + VID_Printf (PRINT_ALL, "-----------------------\n"); + + count = 0; + for ( i = 0 ; i < tr.numShaders ; i++ ) { + if ( Cmd_Argc() > 1 ) { + shader = tr.sortedShaders[i]; + } else { + shader = tr.shaders[i]; + } + + VID_Printf( PRINT_ALL, "%i ", shader->numUnfoggedPasses ); + + if (shader->lightmapIndex[0] >= 0 ) { + VID_Printf (PRINT_ALL, "L "); + } else { + VID_Printf (PRINT_ALL, " "); + } + if ( shader->multitextureEnv == GL_ADD ) { + VID_Printf( PRINT_ALL, "MT(a) " ); + } else if ( shader->multitextureEnv == GL_MODULATE ) { + VID_Printf( PRINT_ALL, "MT(m) " ); + } else if ( shader->multitextureEnv == GL_DECAL ) { + VID_Printf( PRINT_ALL, "MT(d) " ); + } else { + VID_Printf( PRINT_ALL, " " ); + } + if ( shader->explicitlyDefined ) { + VID_Printf( PRINT_ALL, "E " ); + } else { + VID_Printf( PRINT_ALL, " " ); + } + + if ( shader->sky ) + { + VID_Printf( PRINT_ALL, "sky " ); + } else { + VID_Printf( PRINT_ALL, "gen " ); + } + if ( shader->defaultShader ) { + VID_Printf (PRINT_ALL, ": %s (DEFAULTED)\n", shader->name); + } else { + VID_Printf (PRINT_ALL, ": %s\n", shader->name); + } + count++; + } + VID_Printf (PRINT_ALL, "%i total shaders\n", count); + VID_Printf (PRINT_ALL, "------------------\n"); +} + + + +#ifdef USE_STL_FOR_SHADER_LOOKUPS +// setup my STL shortcut list as to where all the shaders are, saves re-parsing every line for every .TGA request. +// +static void SetupShaderEntryPtrs(void) +{ + const char *p = s_shaderText; + char *token; + + ShaderEntryPtrs_Clear(); // extra safe, though done elsewhere already + + if ( !p ) + return; + + while (1) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + break; // EOF + + if ( token[0] == '{' ) // '}' // counterbrace for matching + { + SkipBracedSection( &p ); + } + else + { + strlwr(token); // token is always a ptr to com_token here, not the original buffer. + // (Not that it matters, except for reasons of speed by not strlwr'ing the whole buffer) + + // token = a string of this shader name, p = ptr within s_shadertext it's found at, so store it... + // + ShaderEntryPtrs_Insert(token,p); + SkipRestOfLine( &p ); // now legally skip over this name and go get the next one + } + } + + //VID_Printf( PRINT_DEVELOPER, "SetupShaderEntryPtrs(): Stored %d shader ptrs\n",ShaderEntryPtrs_Size() ); +} +#endif + + +/* +==================== +ScanAndLoadShaderFiles + +Finds and loads all .shader files, combining them into +a single large text block that can be scanned for shader names +===================== +*/ +#define MAX_SHADER_FILES 1024 +static void ScanAndLoadShaderFiles( void ) +{ + char **shaderFiles; + char *buffers[MAX_SHADER_FILES]; + int bufferSizes[MAX_SHADER_FILES]; + int numShaders; + int i; + long sum = 0; + + // scan for shader files + shaderFiles = FS_ListFiles( "shaders", ".shader", &numShaders ); + + if ( !shaderFiles || !numShaders ) + { + VID_Printf( PRINT_WARNING, "WARNING: no shader files found\n" ); + return; + } + + if ( numShaders > MAX_SHADER_FILES ) { + numShaders = MAX_SHADER_FILES; + } + + // load and store shader files + for ( i = 0; i < numShaders; i++ ) + { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "shaders/%s", shaderFiles[i] ); + //VID_Printf( PRINT_DEVELOPER, "...loading '%s'\n", filename ); + // Looks like stripping out crap in the shaders will save about 200k + FS_ReadFile( filename, (void **)&buffers[i] ); + if ( !buffers[i] ) { + Com_Error( ERR_DROP, "Couldn't load %s", filename ); + } + sum += (bufferSizes[i] = COM_Compress( buffers[i] )); + } + + // free up memory + FS_FreeFileList( shaderFiles ); + + // build single large buffer + s_shaderText = (char *) Hunk_Alloc( sum + numShaders*2, qtrue ); + + // free in reverse order, so the temp files are all dumped + for ( i = numShaders - 1, sum = 0; i >= 0 ; i-- ) { + strcat( s_shaderText + sum, "\n" ); + strcat( s_shaderText + sum, buffers[i] ); + sum += bufferSizes[i]; + FS_FreeFile( buffers[i] ); + } + + #ifdef USE_STL_FOR_SHADER_LOOKUPS + SetupShaderEntryPtrs(); + #endif +} + +/* +==================== +R_CreateBlendedShader + + This takes 4 shaders (one per corner of a quad) and creates a blended shader the fades the textures over + eg. + if [A][A] + [B][B] + then the shader would be texture A at the top fading to texture B at the bottom + + This is highly biased towards terrain shaders ie vertex lit surfaces +==================== +*/ + +static void R_CopyStage(shaderStage_t *orig, shaderStage_t *stage) +{ + // Assumption: this stage has not been collapsed + *stage = *orig; //Just copy the whole thing! + + if (orig->ss) + { //definitely need our own copy of SS so we can modify it + stage->ss = (surfaceSprite_t *)Hunk_Alloc( sizeof( surfaceSprite_t ), qtrue ); + memcpy( stage->ss, orig->ss, sizeof( surfaceSprite_t ) ); + } +} + +static void R_CreateBlendedStage(qhandle_t handle, int idx) +{ + shader_t *work; + + work = R_GetShaderByHandle(handle); + R_CopyStage(work->stages, stages + idx); + stages[idx].rgbGen = CGEN_EXACT_VERTEX; + stages[idx].alphaGen = AGEN_BLEND; + stages[idx].stateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE | GLS_DEPTHMASK_TRUE; + + if (stages[idx].ss) + { + stages[idx].ss->density *= 0.33f; + } +} + +static qhandle_t R_MergeShaders(const char *blendedName, qhandle_t a, qhandle_t b, qhandle_t c, bool surfaceSprites) +{ + shader_t *blended; + shader_t *work; + int current, i; + + // Set up default parameters + ClearGlobalShader(); + Q_strncpyz(shader.name, blendedName, sizeof(shader.name)); + memcpy(shader.lightmapIndex, lightmapsVertex, sizeof(shader.lightmapIndex)); + memcpy(shader.styles, stylesDefault, sizeof(shader.styles)); + shader.fogPass = FP_EQUAL; + + // Get the top left shader and set it up as pass 0 - it should be completely opaque + work = R_GetShaderByHandle(c); + stages[0].active = true; + R_CopyStage(&work->stages[0], stages); + stages[0].rgbGen = CGEN_EXACT_VERTEX; + stages[0].alphaGen = AGEN_BLEND; + stages[0].stateBits = GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ZERO | GLS_DEPTHMASK_TRUE; + shader.multitextureEnv = work->multitextureEnv; //jic + + // Go through the other verts and add a pass + R_CreateBlendedStage(a, 1); + R_CreateBlendedStage(b, 2); + + if ( surfaceSprites ) + { + current = 3; + work = R_GetShaderByHandle(a); + for(i=1;(inumUnfoggedPasses && currentstages[i].ss) + { + stages[current] = work->stages[i]; + // stages[current].ss->density *= 0.33f; + stages[current].ss->density *= 3; + current++; + } + } + + work = R_GetShaderByHandle(b); + for(i=1;(inumUnfoggedPasses && currentstages[i].ss) + { + stages[current] = work->stages[i]; + // stages[current].ss->density *= 0.33f; + stages[current].ss->density *= 3; + current++; + } + } + + work = R_GetShaderByHandle(c); + for(i=1;(inumUnfoggedPasses && currentstages[i].ss) + { + stages[current] = work->stages[i]; + // stages[current].ss->density *= 0.33f; + stages[current].ss->density *= 3; + current++; + } + } + } + + blended = FinishShader(); + return(blended->index); +} + + +// Create a 3 pass shader - the last 2 passes are alpha'd out + +qhandle_t R_CreateBlendedShader(qhandle_t a, qhandle_t b, qhandle_t c, bool surfaceSprites ) +{ + qhandle_t blended; + shader_t *work; + char blendedName[MAX_QPATH]; + char extendedName[MAX_QPATH + MAX_QPATH]; + + Com_sprintf(blendedName, MAX_QPATH, "blend(%d,%d,%d)", a, b, c); + if (!surfaceSprites) + { + strcat(blendedName, "noSS"); + } + + // Find if this shader has already been created + R_CreateExtendedName(extendedName, blendedName, lightmapsVertex, stylesDefault); + work = sh_hashTable[generateHashValue(extendedName/*, FILE_HASH_SIZE*/)]; + for ( ; work; work = work->next) + { + if (Q_stricmp(work->name, extendedName) == 0) + { + return work->index; + } + } + + // Create new shader if it doesn't already exist + blended = R_MergeShaders(extendedName, a, b, c, surfaceSprites); + return(blended); +} + +/* +==================== +CreateInternalShaders +==================== +*/ +static void CreateInternalShaders( void ) { + tr.numShaders = 0; + tr.iNumDeniedShaders = 0; + + // init the default shader + memset( &shader, 0, sizeof( shader ) ); + memset( &stages, 0, sizeof( stages ) ); + + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + + memcpy(shader.lightmapIndex, lightmapsNone, sizeof(shader.lightmapIndex)); + memcpy(shader.styles, stylesDefault, sizeof(shader.styles)); + for ( int i = 0 ; i < MAX_SHADER_STAGES ; i++ ) { + stages[i].bundle[0].texMods = texMods[i]; + } + stages[0].bundle[0].image = tr.defaultImage; + stages[0].active = true; + stages[0].stateBits = GLS_DEFAULT; + tr.defaultShader = FinishShader(); + + // shadow shader is just a marker + Q_strncpyz( shader.name, "", sizeof( shader.name ) ); + shader.sort = SS_BANNER; //SS_STENCIL_SHADOW; + tr.shadowShader = FinishShader(); + + // distortion shader is just a marker + Q_strncpyz( shader.name, "internal_distortion", sizeof( shader.name ) ); + shader.sort = SS_BLEND0; + shader.defaultShader = false; + tr.distortionShader = FinishShader(); + shader.defaultShader = true; + + +#ifndef _XBOX // GLOWXXX + #define GL_PROGRAM_ERROR_STRING_ARB 0x8874 + #define GL_PROGRAM_ERROR_POSITION_ARB 0x864B + + // Allocate and Load the global 'Glow' Vertex Program. - AReis + if ( qglGenProgramsARB ) + { + qglGenProgramsARB( 1, &tr.glowVShader ); + qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, tr.glowVShader ); + qglProgramStringARB( GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen( ( char * ) g_strGlowVShaderARB ), g_strGlowVShaderARB ); + +// const GLubyte *strErr = qglGetString( GL_PROGRAM_ERROR_STRING_ARB ); + int iErrPos = 0; + qglGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, &iErrPos ); + assert( iErrPos == -1 ); + } + + // NOTE: I make an assumption here. If you have (current) nvidia hardware, you obviously support register combiners instead of fragment + // programs, so use those. The problem with this is that nv30 WILL support fragment shaders, breaking this logic. The good thing is that + // if you always ask for regcoms before fragment shaders, you'll always just use regcoms (problem solved... for now). - AReis + + // Load Pixel Shaders (either regcoms or fragprogs). + if ( qglCombinerParameteriNV ) + { + // The purpose of this regcom is to blend all the pixels together from the 4 texture units, but with their + // texture coordinates offset by 1 (or more) texels, effectively letting us blend adjoining pixels. The weight is + // used to either strengthen or weaken the pixel intensity. The more it diffuses (the higher the radius of the glow), + // the higher the intensity should be for a noticable effect. + // Regcom result is: ( tex1 * fBlurWeight ) + ( tex2 * fBlurWeight ) + ( tex2 * fBlurWeight ) + ( tex2 * fBlurWeight ) + + // VV guys, this is the pixel shader you would use instead :-) + /* + // c0 is the blur weight. + ps 1.1 + tex t0 + tex t1 + tex t2 + tex t3 + + mul r0, c0, t0; + madd r0, c0, t1, r0; + madd r0, c0, t2, r0; + madd r0, c0, t3, r0; + */ + tr.glowPShader = qglGenLists( 1 ); + qglNewList( tr.glowPShader, GL_COMPILE ); + qglCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 2 ); + + // spare0 = fBlend * tex0 + fBlend * tex1. + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE1_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE0_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // spare1 = fBlend * tex2 + fBlend * tex3. + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE2_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE3_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_D_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglCombinerOutputNV( GL_COMBINER1_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE1_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // ( A * B ) + ( ( 1 - A ) * C ) + D = ( spare0 * 1 ) + ( ( 1 - spare0 ) * 0 ) + spare1 == spare0 + spare1. + qglFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); + qglEndList(); + } + else if ( qglGenProgramsARB ) + { + qglGenProgramsARB( 1, &tr.glowPShader ); + qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, tr.glowPShader ); + qglProgramStringARB( GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, strlen( ( char * ) g_strGlowPShaderARB ), g_strGlowPShaderARB ); + +// const GLubyte *strErr = qglGetString( GL_PROGRAM_ERROR_STRING_ARB ); + int iErrPos = 0; + qglGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, &iErrPos ); + assert( iErrPos == -1 ); + } +#endif +} + +static void CreateExternalShaders( void ) { + tr.projectionShadowShader = R_FindShader( "projectionShadow", lightmapsNone, stylesDefault, qtrue ); + tr.projectionShadowShader->sort = SS_STENCIL_SHADOW; + tr.sunShader = R_FindShader( "sun", lightmapsVertex, stylesDefault, qtrue ); +} + +/* +================== +R_InitShaders +================== +*/ +void R_InitShaders( void ) { + //VID_Printf( PRINT_ALL, "Initializing Shaders\n" ); + + memset(sh_hashTable, 0, sizeof(sh_hashTable)); +/* +Ghoul2 Insert Start +*/ +// memset(hitMatReg, 0, sizeof(hitMatReg)); +// hitMatCount = 0; +/* +Ghoul2 Insert End +*/ + + + CreateInternalShaders(); + + ScanAndLoadShaderFiles(); + + CreateExternalShaders(); +} diff --git a/code/renderer/tr_shadows.cpp b/code/renderer/tr_shadows.cpp new file mode 100644 index 0000000..74ff2f5 --- /dev/null +++ b/code/renderer/tr_shadows.cpp @@ -0,0 +1,801 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + +#ifdef VV_LIGHTING +#include "tr_lightmanager.h" +#include "../win32/win_stencilshadow.h" +#endif + + +/* + + for a projection shadow: + + point[x] += light vector * ( z - shadow plane ) + point[y] += + point[z] = shadow plane + + 1 0 light[x] / light[z] + +*/ +#ifndef _XBOX + +#define _STENCIL_REVERSE + +typedef struct { + int i2; + int facing; +} edgeDef_t; + +#define MAX_EDGE_DEFS 32 + +static edgeDef_t edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS]; +static int numEdgeDefs[SHADER_MAX_VERTEXES]; +static int facing[SHADER_MAX_INDEXES/3]; + +#endif // _XBOX + +void R_AddEdgeDef( int i1, int i2, int facing ) { +#ifndef _XBOX + int c; + + c = numEdgeDefs[ i1 ]; + if ( c == MAX_EDGE_DEFS ) { + return; // overflow + } + edgeDefs[ i1 ][ c ].i2 = i2; + edgeDefs[ i1 ][ c ].facing = facing; + + numEdgeDefs[ i1 ]++; +#endif // _XBOX +} + +void R_RenderShadowEdges( void ) { +#if defined(VV_LIGHTING) && defined(_XBOX) + return; +#else + + int i; + int c; + int j; + int i2; + int c_edges, c_rejected; +#if 0 + int c2, k; + int hit[2]; +#endif +#ifdef _STENCIL_REVERSE + int numTris; + int o1, o2, o3; +#endif + + // an edge is NOT a silhouette edge if its face doesn't face the light, + // or if it has a reverse paired edge that also faces the light. + // A well behaved polyhedron would have exactly two faces for each edge, + // but lots of models have dangling edges or overfanned edges + c_edges = 0; + c_rejected = 0; + + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + c = numEdgeDefs[ i ]; + for ( j = 0 ; j < c ; j++ ) { + if ( !edgeDefs[ i ][ j ].facing ) { + continue; + } + + //with this system we can still get edges shared by more than 2 tris which + //produces artifacts including seeing the shadow through walls. So for now + //we are going to render all edges even though it is a tiny bit slower. -rww +#if 1 + i2 = edgeDefs[ i ][ j ].i2; + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i ] ); + qglVertex3fv( tess.xyz[ i + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); + qglEnd(); +#else + hit[0] = 0; + hit[1] = 0; + + i2 = edgeDefs[ i ][ j ].i2; + c2 = numEdgeDefs[ i2 ]; + for ( k = 0 ; k < c2 ; k++ ) { + if ( edgeDefs[ i2 ][ k ].i2 == i ) { + hit[ edgeDefs[ i2 ][ k ].facing ]++; + } + } + + // if it doesn't share the edge with another front facing + // triangle, it is a sil edge + if ( hit[ 1 ] == 0 ) { + qglBegin( GL_TRIANGLE_STRIP ); + qglVertex3fv( tess.xyz[ i ] ); + qglVertex3fv( tess.xyz[ i + tess.numVertexes ] ); + qglVertex3fv( tess.xyz[ i2 ] ); + qglVertex3fv( tess.xyz[ i2 + tess.numVertexes ] ); + qglEnd(); + c_edges++; + } else { + c_rejected++; + } +#endif + } + } + +#ifdef _STENCIL_REVERSE + //Carmack Reverse method requires that volumes + //be capped properly -rww + numTris = tess.numIndexes / 3; + + for ( i = 0 ; i < numTris ; i++ ) + { + if ( !facing[i] ) + { + continue; + } + + o1 = tess.indexes[ i*3 + 0 ]; + o2 = tess.indexes[ i*3 + 1 ]; + o3 = tess.indexes[ i*3 + 2 ]; + + qglBegin(GL_TRIANGLES); + qglVertex3fv(tess.xyz[o1]); + qglVertex3fv(tess.xyz[o2]); + qglVertex3fv(tess.xyz[o3]); + qglEnd(); + qglBegin(GL_TRIANGLES); + qglVertex3fv(tess.xyz[o3 + tess.numVertexes]); + qglVertex3fv(tess.xyz[o2 + tess.numVertexes]); + qglVertex3fv(tess.xyz[o1 + tess.numVertexes]); + qglEnd(); + } +#endif +#endif // VV_LIGHTING && _XBOX +} + +//#define _DEBUG_STENCIL_SHADOWS + +/* +================= +RB_ShadowTessEnd + +triangleFromEdge[ v1 ][ v2 ] + + + set triangle from edge( v1, v2, tri ) + if ( facing[ triangleFromEdge[ v1 ][ v2 ] ] && !facing[ triangleFromEdge[ v2 ][ v1 ] ) { + } +================= +*/ +void RB_DoShadowTessEnd( vec3_t lightPos ); +void RB_ShadowTessEnd( void ) +{ +#if defined(VV_LIGHTING) && defined(_XBOX) + if(StencilShadower.BuildFromLight()) + StencilShadower.RenderShadow(); +#else +#if 0 + if (backEnd.currentEntity && + (backEnd.currentEntity->directedLight[0] || + backEnd.currentEntity->directedLight[1] || + backEnd.currentEntity->directedLight[2])) + { //an ent that has its light set for it + RB_DoShadowTessEnd(NULL); + return; + } + +// if (!tess.dlightBits) +// { +// return; +// } + + int i = 0; + dlight_t *dl; + + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori ); +/* while (i < tr.refdef.num_dlights) + { + if (tess.dlightBits & (1 << i)) + { + dl = &tr.refdef.dlights[i]; + + RB_DoShadowTessEnd(dl->transformed); + } + + i++; + } + */ + dl = &tr.refdef.dlights[0]; + + RB_DoShadowTessEnd(dl->transformed); + +#else //old ents-only way + RB_DoShadowTessEnd(NULL); +#endif +#endif // VV_LIGHTING && _XBOX +} + +void RB_DoShadowTessEnd( vec3_t lightPos ) +{ +#ifndef _XBOX + int i; + int numTris; + vec3_t lightDir; + + // we can only do this if we have enough space in the vertex buffers + if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) { + return; + } + + if ( glConfig.stencilBits < 4 ) { + return; + } + +#if 1 //controlled method - try to keep shadows in range so they don't show through so much -rww + vec3_t worldxyz; + vec3_t entLight; + float groundDist; + + VectorCopy( backEnd.currentEntity->lightDir, entLight ); + entLight[2] = 0.0f; + VectorNormalize(entLight); + + //Oh well, just cast them straight down no matter what onto the ground plane. + //This presets no chance of screwups and still looks better than a stupid + //shader blob. + VectorSet(lightDir, entLight[0]*0.3f, entLight[1]*0.3f, 1.0f); + // project vertexes away from light direction + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + //add or.origin to vert xyz to end up with world oriented coord, then figure + //out the ground pos for the vert to project the shadow volume to + VectorAdd(tess.xyz[i], backEnd.ori.origin, worldxyz); + groundDist = worldxyz[2] - backEnd.currentEntity->e.shadowPlane; + groundDist += 16.0f; //fudge factor + VectorMA( tess.xyz[i], -groundDist, lightDir, tess.xyz[i+tess.numVertexes] ); + } +#else + if (lightPos) + { + for ( i = 0 ; i < tess.numVertexes ; i++ ) + { + tess.xyz[i+tess.numVertexes][0] = tess.xyz[i][0]+(( tess.xyz[i][0]-lightPos[0] )*128.0f); + tess.xyz[i+tess.numVertexes][1] = tess.xyz[i][1]+(( tess.xyz[i][1]-lightPos[1] )*128.0f); + tess.xyz[i+tess.numVertexes][2] = tess.xyz[i][2]+(( tess.xyz[i][2]-lightPos[2] )*128.0f); + } + } + else + { + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + + // project vertexes away from light direction + for ( i = 0 ; i < tess.numVertexes ; i++ ) { + VectorMA( tess.xyz[i], -512, lightDir, tess.xyz[i+tess.numVertexes] ); + } + } +#endif + // decide which triangles face the light + memset( numEdgeDefs, 0, 4 * tess.numVertexes ); + + numTris = tess.numIndexes / 3; + for ( i = 0 ; i < numTris ; i++ ) { + int i1, i2, i3; + vec3_t d1, d2, normal; + float *v1, *v2, *v3; + float d; + + i1 = tess.indexes[ i*3 + 0 ]; + i2 = tess.indexes[ i*3 + 1 ]; + i3 = tess.indexes[ i*3 + 2 ]; + + v1 = tess.xyz[ i1 ]; + v2 = tess.xyz[ i2 ]; + v3 = tess.xyz[ i3 ]; + + if (!lightPos) + { + VectorSubtract( v2, v1, d1 ); + VectorSubtract( v3, v1, d2 ); + CrossProduct( d1, d2, normal ); + + d = DotProduct( normal, lightDir ); + } + else + { + float planeEq[4]; + planeEq[0] = v1[1]*(v2[2]-v3[2]) + v2[1]*(v3[2]-v1[2]) + v3[1]*(v1[2]-v2[2]); + planeEq[1] = v1[2]*(v2[0]-v3[0]) + v2[2]*(v3[0]-v1[0]) + v3[2]*(v1[0]-v2[0]); + planeEq[2] = v1[0]*(v2[1]-v3[1]) + v2[0]*(v3[1]-v1[1]) + v3[0]*(v1[1]-v2[1]); + planeEq[3] = -( v1[0]*( v2[1]*v3[2] - v3[1]*v2[2] ) + + v2[0]*(v3[1]*v1[2] - v1[1]*v3[2]) + + v3[0]*(v1[1]*v2[2] - v2[1]*v1[2]) ); + + d = planeEq[0]*lightPos[0]+ + planeEq[1]*lightPos[1]+ + planeEq[2]*lightPos[2]+ + planeEq[3]; + } + + if ( d > 0 ) { + facing[ i ] = 1; + } else { + facing[ i ] = 0; + } + + // create the edges + R_AddEdgeDef( i1, i2, facing[ i ] ); + R_AddEdgeDef( i2, i3, facing[ i ] ); + R_AddEdgeDef( i3, i1, facing[ i ] ); + } + + GL_Bind( tr.whiteImage ); + //qglEnable( GL_CULL_FACE ); + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + +#ifndef _DEBUG_STENCIL_SHADOWS + qglColor3f( 0.2f, 0.2f, 0.2f ); + + // don't write to the color buffer + qglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_ALWAYS, 1, 255 ); +#else + qglColor3f( 1.0f, 0.0f, 0.0f ); + qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + //qglDisable(GL_DEPTH_TEST); +#endif + +#ifdef _STENCIL_REVERSE + qglDepthFunc(GL_LESS); + + //now using the Carmack Reverse -rww + if ( backEnd.viewParms.isMirror ) { + //qglCullFace( GL_BACK ); + GL_Cull(CT_BACK_SIDED); + qglStencilOp( GL_KEEP, GL_INCR, GL_KEEP ); + + R_RenderShadowEdges(); + + //qglCullFace( GL_FRONT ); + GL_Cull(CT_FRONT_SIDED); + qglStencilOp( GL_KEEP, GL_DECR, GL_KEEP ); + + R_RenderShadowEdges(); + } else { + //qglCullFace( GL_FRONT ); + GL_Cull(CT_FRONT_SIDED); + qglStencilOp( GL_KEEP, GL_INCR, GL_KEEP ); + + R_RenderShadowEdges(); + + //qglCullFace( GL_BACK ); + GL_Cull(CT_BACK_SIDED); + qglStencilOp( GL_KEEP, GL_DECR, GL_KEEP ); + + R_RenderShadowEdges(); + } + + qglDepthFunc(GL_LEQUAL); +#else + // mirrors have the culling order reversed + if ( backEnd.viewParms.isMirror ) { + qglCullFace( GL_FRONT ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + qglCullFace( GL_BACK ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + } else { + qglCullFace( GL_BACK ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); + + R_RenderShadowEdges(); + + qglCullFace( GL_FRONT ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + R_RenderShadowEdges(); + } +#endif + + // reenable writing to the color buffer + qglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); + +#ifdef _DEBUG_STENCIL_SHADOWS + qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +#endif // _XBOX +} + + +/* +================= +RB_ShadowFinish + +Darken everything that is is a shadow volume. +We have to delay this until everything has been shadowed, +because otherwise shadows from different body parts would +overlap and double darken. +================= +*/ +void RB_ShadowFinish( void ) { +#if defined(VV_LIGHTING) && defined(_XBOX) + StencilShadower.FinishShadows(); +#else + if ( r_shadows->integer != 2 ) { + return; + } + if ( glConfig.stencilBits < 4 ) { + return; + } + +#ifdef _DEBUG_STENCIL_SHADOWS + return; +#endif + + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_NOTEQUAL, 0, 255 ); + + qglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); + + bool planeZeroBack = false; + if (qglIsEnabled(GL_CLIP_PLANE0)) + { + planeZeroBack = true; + qglDisable (GL_CLIP_PLANE0); + } + GL_Cull(CT_TWO_SIDED); + //qglDisable (GL_CULL_FACE); + + GL_Bind( tr.whiteImage ); + + qglPushMatrix(); + qglLoadIdentity (); + +// qglColor3f( 0.6f, 0.6f, 0.6f ); +// GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO ); + +// qglColor3f( 1, 0, 0 ); +// GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); + + qglColor4f( 0.0f, 0.0f, 0.0f, 0.5f ); + //GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA ); + + qglBegin( GL_QUADS ); + qglVertex3f( -100, 100, -10 ); + qglVertex3f( 100, 100, -10 ); + qglVertex3f( 100, -100, -10 ); + qglVertex3f( -100, -100, -10 ); + qglEnd (); + + qglColor4f(1,1,1,1); + qglDisable( GL_STENCIL_TEST ); + if (planeZeroBack) + { + qglEnable (GL_CLIP_PLANE0); + } + qglPopMatrix(); +#endif // VV_LIGHTING && _XBOX +} + + +/* +================= +RB_ProjectionShadowDeform + +================= +*/ +void RB_ProjectionShadowDeform( void ) { +#ifdef _XBOX + float shadowMat[4][4]; + vec3_t light, ground; + float d, dot; + + ground[0] = backEnd.ori.axis[0][2]; + ground[1] = backEnd.ori.axis[1][2]; + ground[2] = backEnd.ori.axis[2][2]; + d = backEnd.ori.origin[2] - backEnd.currentEntity->e.shadowPlane; + + light[0] = backEnd.currentEntity->lightDir[0]; + light[1] = backEnd.currentEntity->lightDir[1]; + light[2] = backEnd.currentEntity->lightDir[2]; + + dot = ground[0] * light[0] + + ground[1] * light[1] + + ground[2] * light[2]; + // don't let the shadows get too long or go negative + if ( dot < 0.5 ) + { + VectorMA( light, (0.5 - dot), ground, light ); + dot = DotProduct( light, ground ); + } + + shadowMat[0][0] = dot - light[0] * ground[0]; + shadowMat[1][0] = 0.f - light[0] * ground[1]; + shadowMat[2][0] = 0.f - light[0] * ground[2]; + shadowMat[3][0] = 0.f - light[0] * d; + shadowMat[0][1] = 0.f - light[1] * ground[0]; + shadowMat[1][1] = dot - light[1] * ground[1]; + shadowMat[2][1] = 0.f - light[1] * ground[2]; + shadowMat[3][1] = 0.f - light[1] * d; + shadowMat[0][2] = 0.f - light[2] * ground[0]; + shadowMat[1][2] = 0.f - light[2] * ground[1]; + shadowMat[2][2] = dot - light[2] * ground[2]; + shadowMat[3][2] = 0.f - light[2] * d; + shadowMat[0][3] = 0.f; + shadowMat[1][3] = 0.f; + shadowMat[2][3] = 0.f; + shadowMat[3][3] = dot; + + qglMatrixMode(GL_MODELVIEW); + qglMultMatrixf(&shadowMat[0][0]); + + // Turn on stenciling + // This is done to prevent overlapping shadow artifacts + qglEnable( GL_STENCIL_TEST ); + qglStencilFunc( GL_NOTEQUAL, 0x1, 0x7f ); + qglStencilMask( 0x7f ); + qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR ); +#else + float *xyz; + int i; + float h; + vec3_t ground; + vec3_t light; + float groundDist; + float d; + vec3_t lightDir; + + xyz = ( float * ) tess.xyz; + + ground[0] = backEnd.ori.axis[0][2]; + ground[1] = backEnd.ori.axis[1][2]; + ground[2] = backEnd.ori.axis[2][2]; + + groundDist = backEnd.ori.origin[2] - backEnd.currentEntity->e.shadowPlane; + + VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + d = DotProduct( lightDir, ground ); + // don't let the shadows get too long or go negative + if ( d < 0.5 ) { + VectorMA( lightDir, (0.5 - d), ground, lightDir ); + d = DotProduct( lightDir, ground ); + } + d = 1.0 / d; + + light[0] = lightDir[0] * d; + light[1] = lightDir[1] * d; + light[2] = lightDir[2] * d; + + for ( i = 0; i < tess.numVertexes; i++, xyz += 4 ) { + h = DotProduct( xyz, ground ) + groundDist; + + xyz[0] -= light[0] * h; + xyz[1] -= light[1] * h; + xyz[2] -= light[2] * h; + } +#endif // _XBOX +} + +//update tr.screenImage +void RB_CaptureScreenImage(void) +{ + int radX = 2048; + int radY = 2048; + int x = glConfig.vidWidth/2; + int y = glConfig.vidHeight/2; + int cX, cY; + + GL_Bind( tr.screenImage ); + //using this method, we could pixel-filter the texture and all sorts of crazy stuff. + //but, it is slow as hell. + /* + static byte *tmp = NULL; + if (!tmp) + { + tmp = (byte *)Z_Malloc((sizeof(byte)*4)*(glConfig.vidWidth*glConfig.vidHeight), TAG_ICARUS, qtrue); + } + qglReadPixels(0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_UNSIGNED_BYTE, tmp); + qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmp); + */ + + if (radX > glConfig.maxTextureSize) + { + radX = glConfig.maxTextureSize; + } + if (radY > glConfig.maxTextureSize) + { + radY = glConfig.maxTextureSize; + } + + while (glConfig.vidWidth < radX) + { + radX /= 2; + } + while (glConfig.vidHeight < radY) + { + radY /= 2; + } + + cX = x-(radX/2); + cY = y-(radY/2); + + if (cX+radX > glConfig.vidWidth) + { //would it go off screen? + cX = glConfig.vidWidth-radX; + } + else if (cX < 0) + { //cap it off at 0 + cX = 0; + } + + if (cY+radY > glConfig.vidHeight) + { //would it go off screen? + cY = glConfig.vidHeight-radY; + } + else if (cY < 0) + { //cap it off at 0 + cY = 0; + } + +#ifndef _XBOX + qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, cX, cY, radX, radY, 0); +#else + qglCopyBackBufferToTexEXT(radX, radY, cX, cY, (cX + radX), (cY + radY)); +#endif // _XBOX +} + + +//yeah.. not really shadow-related.. but it's stencil-related. -rww +float tr_distortionAlpha = 1.0f; //opaque +float tr_distortionStretch = 0.0f; //no stretch override +qboolean tr_distortionPrePost = qfalse; //capture before postrender phase? +qboolean tr_distortionNegate = qfalse; //negative blend mode +void RB_DistortionFill(void) +{ + float alpha = tr_distortionAlpha; + float spost = 0.0f; + float spost2 = 0.0f; + + if ( glConfig.stencilBits < 4 ) + { + return; + } + + //ok, cap the stupid thing now I guess + if (!tr_distortionPrePost) + { + RB_CaptureScreenImage(); + } + + // BTO - Xbox fix: High stencil bit is used for glow, don't test against that here! + qglEnable(GL_STENCIL_TEST); + qglStencilFunc(GL_NOTEQUAL, 0, 0x7F); //0xFFFFFFFF);' + qglStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + + qglDisable (GL_CLIP_PLANE0); + GL_Cull( CT_TWO_SIDED ); + + //reset the view matrices and go into ortho mode + qglMatrixMode(GL_PROJECTION); + qglPushMatrix(); + qglLoadIdentity(); + qglOrtho(0, glConfig.vidWidth, glConfig.vidHeight, 32, -1, 1); + qglMatrixMode(GL_MODELVIEW); + qglPushMatrix(); + qglLoadIdentity(); + + if (tr_distortionStretch) + { //override + spost = tr_distortionStretch; + spost2 = tr_distortionStretch; + } + else + { //do slow stretchy effect + spost = sin(tr.refdef.time*0.0005f); + if (spost < 0.0f) + { + spost = -spost; + } + spost *= 0.2f; + + spost2 = sin(tr.refdef.time*0.0005f); + if (spost2 < 0.0f) + { + spost2 = -spost2; + } + spost2 *= 0.08f; + } + + if (alpha != 1.0f) + { //blend + GL_State(GLS_SRCBLEND_SRC_ALPHA|GLS_DSTBLEND_SRC_ALPHA); + } + else + { //be sure to reset the draw state + GL_State(0); + } + +#ifdef _XBOX + qglBeginEXT(GL_QUADS, 4, 0, 0, 4, 0); +#else + qglBegin(GL_QUADS); +#endif // _XBOX + qglColor4f(1.0f, 1.0f, 1.0f, alpha); + qglTexCoord2f(0+spost2, 1-spost); + qglVertex2f(0, 0); + + qglTexCoord2f(0+spost2, 0+spost); + qglVertex2f(0, glConfig.vidHeight); + + qglTexCoord2f(1-spost2, 0+spost); + qglVertex2f(glConfig.vidWidth, glConfig.vidHeight); + + qglTexCoord2f(1-spost2, 1-spost); + qglVertex2f(glConfig.vidWidth, 0); + qglEnd(); + + if (tr_distortionAlpha == 1.0f && tr_distortionStretch == 0.0f) + { //no overrides + if (tr_distortionNegate) + { //probably the crazy alternate saber trail + alpha = 0.8f; + GL_State(GLS_SRCBLEND_ZERO|GLS_DSTBLEND_ONE_MINUS_SRC_COLOR); + } + else + { + alpha = 0.5f; + GL_State(GLS_SRCBLEND_SRC_ALPHA|GLS_DSTBLEND_SRC_ALPHA); + } + + spost = sin(tr.refdef.time*0.0008f); + if (spost < 0.0f) + { + spost = -spost; + } + spost *= 0.08f; + + spost2 = sin(tr.refdef.time*0.0008f); + if (spost2 < 0.0f) + { + spost2 = -spost2; + } + spost2 *= 0.2f; + +#ifdef _XBOX + qglBeginEXT(GL_QUADS, 4, 0, 0, 4, 0); +#else + qglBegin(GL_QUADS); +#endif // _XBOX + qglColor4f(1.0f, 1.0f, 1.0f, alpha); + qglTexCoord2f(0+spost2, 1-spost); + qglVertex2f(0, 0); + + qglTexCoord2f(0+spost2, 0+spost); + qglVertex2f(0, glConfig.vidHeight); + + qglTexCoord2f(1-spost2, 0+spost); + qglVertex2f(glConfig.vidWidth, glConfig.vidHeight); + + qglTexCoord2f(1-spost2, 1-spost); + qglVertex2f(glConfig.vidWidth, 0); + qglEnd(); + } + + //pop the view matrices back + qglMatrixMode(GL_PROJECTION); + qglPopMatrix(); + qglMatrixMode(GL_MODELVIEW); + qglPopMatrix(); + + qglDisable( GL_STENCIL_TEST ); +} \ No newline at end of file diff --git a/code/renderer/tr_sky.cpp b/code/renderer/tr_sky.cpp new file mode 100644 index 0000000..0e410fa --- /dev/null +++ b/code/renderer/tr_sky.cpp @@ -0,0 +1,845 @@ +// tr_sky.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + +#define SKY_SUBDIVISIONS 8 +#define HALF_SKY_SUBDIVISIONS (SKY_SUBDIVISIONS/2) + +static float s_cloudTexCoords[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; +static float s_cloudTexP[6][SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; + +/* +=================================================================================== + +POLYGON TO BOX SIDE PROJECTION + +=================================================================================== +*/ + +static vec3_t sky_clip[6] = +{ + {1,1,0}, + {1,-1,0}, + {0,-1,1}, + {0,1,1}, + {1,0,1}, + {-1,0,1} +}; + +static float sky_mins[2][6], sky_maxs[2][6]; +static float sky_min, sky_max; + +/* +================ +AddSkyPolygon +================ +*/ +static void AddSkyPolygon (int nump, vec3_t vecs) +{ + int i,j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; + // s = [0]/[2], t = [1]/[2] + static int vec_to_st[6][3] = + { + {-2,3,1}, + {2,3,-1}, + + {1,3,2}, + {-1,3,-2}, + + {-2,-1,3}, + {-2,1,-3} + + // {-1,2,3}, + // {1,2,-3} + }; + + // decide which face it maps to + VectorCopy (vec3_origin, v); + for (i=0, vp=vecs ; i av[1] && av[0] > av[2]) + { + if (v[0] < 0) + axis = 1; + else + axis = 0; + } + else if (av[1] > av[2] && av[1] > av[0]) + { + if (v[1] < 0) + axis = 3; + else + axis = 2; + } + else + { + if (v[2] < 0) + axis = 5; + else + axis = 4; + } + + // project new texture coords + for (i=0 ; i 0) + dv = vecs[j - 1]; + else + dv = -vecs[-j - 1]; + if (dv < 0.001) + continue; // don't divide by zero + j = vec_to_st[axis][0]; + if (j < 0) + s = -vecs[-j -1] / dv; + else + s = vecs[j-1] / dv; + j = vec_to_st[axis][1]; + if (j < 0) + t = -vecs[-j -1] / dv; + else + t = vecs[j-1] / dv; + + if (s < sky_mins[0][axis]) + sky_mins[0][axis] = s; + if (t < sky_mins[1][axis]) + sky_mins[1][axis] = t; + if (s > sky_maxs[0][axis]) + sky_maxs[0][axis] = s; + if (t > sky_maxs[1][axis]) + sky_maxs[1][axis] = t; + } +} + +#define ON_EPSILON 0.1 // point on plane side epsilon +#define MAX_CLIP_VERTS 64 +/* +================ +ClipSkyPolygon +================ +*/ +static void ClipSkyPolygon (int nump, vec3_t vecs, int stage) +{ + float *norm; + float *v; + qboolean front, back; + float d, e; + float dists[MAX_CLIP_VERTS]; + int sides[MAX_CLIP_VERTS]; + vec3_t newv[2][MAX_CLIP_VERTS]; + int newc[2]; + int i, j; + + if (nump > MAX_CLIP_VERTS-2) + Com_Error (ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS"); + if (stage == 6) + { // fully clipped, so draw it + AddSkyPolygon (nump, vecs); + return; + } + + front = back = qfalse; + norm = sky_clip[stage]; + for (i=0, v = vecs ; i ON_EPSILON) + { + front = qtrue; + sides[i] = SIDE_FRONT; + } + else if (d < -ON_EPSILON) + { + back = qtrue; + sides[i] = SIDE_BACK; + } + else + sides[i] = SIDE_ON; + dists[i] = d; + } + + if (!front || !back) + { // not clipped + ClipSkyPolygon (nump, vecs, stage+1); + return; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + VectorCopy (vecs, (vecs+(i*3)) ); + newc[0] = newc[1] = 0; + + for (i=0, v = vecs ; inumIndexes; i += 3 ) + { + for (j = 0 ; j < 3 ; j++) + { + VectorSubtract( input->xyz[input->indexes[i+j]], + backEnd.viewParms.or.origin, + p[j] ); + } + ClipSkyPolygon( 3, p[0], 0 ); + } +} + +/* +=================================================================================== + +CLOUD VERTEX GENERATION + +=================================================================================== +*/ + +/* +** MakeSkyVec +** +** Parms: s, t range from -1 to 1 +*/ + +static void MakeSkyVec( float s, float t, int axis, float outSt[2], vec3_t outXYZ ) +{ + // 1 = s, 2 = t, 3 = 2048 + static int st_to_vec[6][3] = + { + {3,-1,2}, + {-3,1,2}, + + {1,3,2}, + {-1,-3,2}, + + {-2,-1,3}, // 0 degrees yaw, look straight up + {2,-1,-3} // look straight down + }; + + vec3_t b; + int j, k; + float boxSize; + + boxSize = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + b[0] = s*boxSize; + b[1] = t*boxSize; + b[2] = boxSize; + + for (j=0 ; j<3 ; j++) + { + k = st_to_vec[axis][j]; + if (k < 0) + { + outXYZ[j] = -b[-k - 1]; + } + else + { + outXYZ[j] = b[k - 1]; + } + } + + // avoid bilerp seam + s = (s+1)*0.5; + t = (t+1)*0.5; + if (s < sky_min) + { + s = sky_min; + } + else if (s > sky_max) + { + s = sky_max; + } + + if (t < sky_min) + { + t = sky_min; + } + else if (t > sky_max) + { + t = sky_max; + } + + t = 1.0 - t; + + + if ( outSt ) + { + outSt[0] = s; + outSt[1] = t; + } +} + +static vec3_t s_skyPoints[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1]; +static float s_skyTexCoords[SKY_SUBDIVISIONS+1][SKY_SUBDIVISIONS+1][2]; + +static void DrawSkySide( struct image_s *image, const int mins[2], const int maxs[2] ) +{ + int s, t; + + GL_Bind( image ); + +#ifdef _XBOX + int verts = ((maxs[0]+HALF_SKY_SUBDIVISIONS) - (mins[0]+HALF_SKY_SUBDIVISIONS)) * 2 + 2; +#endif + + for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t < maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { +#ifdef _XBOX + qglBeginEXT( GL_TRIANGLE_STRIP, verts, 0, 0, verts, 0); +#else + qglBegin( GL_TRIANGLE_STRIP ); +#endif + + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + qglTexCoord2fv( s_skyTexCoords[t][s] ); + qglVertex3fv( s_skyPoints[t][s] ); + + qglTexCoord2fv( s_skyTexCoords[t+1][s] ); + qglVertex3fv( s_skyPoints[t+1][s] ); + } + + qglEnd(); + } +} + +static void DrawSkyBox( shader_t *shader ) +{ + int i; + + sky_min = 0.0f; + sky_max = 1.0f; + + memset( s_skyTexCoords, 0, sizeof( s_skyTexCoords ) ); + + for (i=0 ; i<6 ; i++) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = sky_mins[0][i] * HALF_SKY_SUBDIVISIONS; + sky_mins_subd[1] = sky_mins[1][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[0] = sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS; + sky_maxs_subd[1] = sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS; + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + s_skyTexCoords[t][s], + s_skyPoints[t][s] ); + } + } + + DrawSkySide( shader->sky->outerbox[i], + sky_mins_subd, + sky_maxs_subd ); + } + +} + +static void FillCloudySkySide( const int mins[2], const int maxs[2], qboolean addIndexes ) +{ + int s, t; + int vertexStart = tess.numVertexes; + int tHeight, sWidth; + + tHeight = maxs[1] - mins[1] + 1; + sWidth = maxs[0] - mins[0] + 1; + + for ( t = mins[1]+HALF_SKY_SUBDIVISIONS; t <= maxs[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = mins[0]+HALF_SKY_SUBDIVISIONS; s <= maxs[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + VectorAdd( s_skyPoints[t][s], backEnd.viewParms.or.origin, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = s_skyTexCoords[t][s][0]; + tess.texCoords[tess.numVertexes][0][1] = s_skyTexCoords[t][s][1]; + + tess.numVertexes++; + + if ( tess.numVertexes >= SHADER_MAX_VERTEXES ) + { + Com_Error( ERR_DROP, "SHADER_MAX_VERTEXES hit in FillCloudySkySide()\n" ); + } + } + } + + // only add indexes for one pass, otherwise it would draw multiple times for each pass + if ( addIndexes ) { + for ( t = 0; t < tHeight-1; t++ ) + { + for ( s = 0; s < sWidth-1; s++ ) + { + tess.indexes[tess.numIndexes] = vertexStart + s + t * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + + tess.indexes[tess.numIndexes] = vertexStart + s + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + ( t + 1 ) * ( sWidth ); + tess.numIndexes++; + tess.indexes[tess.numIndexes] = vertexStart + s + 1 + t * ( sWidth ); + tess.numIndexes++; + } + } + } +} + +static void FillCloudBox( const shader_t *shader, int stage ) +{ + int i; + + for ( i =0; i < 6; i++ ) + { + int sky_mins_subd[2], sky_maxs_subd[2]; + int s, t; + float MIN_T; + + if ( 1 ) // FIXME? shader->sky->fullClouds ) + { + MIN_T = -HALF_SKY_SUBDIVISIONS; + + // still don't want to draw the bottom, even if fullClouds + if ( i == 5 ) + continue; + } + else + { + switch( i ) + { + case 0: + case 1: + case 2: + case 3: + MIN_T = -1; + break; + case 5: + // don't draw clouds beneath you + continue; + case 4: // top + default: + MIN_T = -HALF_SKY_SUBDIVISIONS; + break; + } + } + + sky_mins[0][i] = floor( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_mins[1][i] = floor( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[0][i] = ceil( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + sky_maxs[1][i] = ceil( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ) / HALF_SKY_SUBDIVISIONS; + + if ( ( sky_mins[0][i] >= sky_maxs[0][i] ) || + ( sky_mins[1][i] >= sky_maxs[1][i] ) ) + { + continue; + } + + sky_mins_subd[0] = myftol( sky_mins[0][i] * HALF_SKY_SUBDIVISIONS ); + sky_mins_subd[1] = myftol( sky_mins[1][i] * HALF_SKY_SUBDIVISIONS ); + sky_maxs_subd[0] = myftol( sky_maxs[0][i] * HALF_SKY_SUBDIVISIONS ); + sky_maxs_subd[1] = myftol( sky_maxs[1][i] * HALF_SKY_SUBDIVISIONS ); + + if ( sky_mins_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_mins_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_mins_subd[1] < MIN_T ) + sky_mins_subd[1] = MIN_T; + else if ( sky_mins_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_mins_subd[1] = HALF_SKY_SUBDIVISIONS; + + if ( sky_maxs_subd[0] < -HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = -HALF_SKY_SUBDIVISIONS; + else if ( sky_maxs_subd[0] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[0] = HALF_SKY_SUBDIVISIONS; + if ( sky_maxs_subd[1] < MIN_T ) + sky_maxs_subd[1] = MIN_T; + else if ( sky_maxs_subd[1] > HALF_SKY_SUBDIVISIONS ) + sky_maxs_subd[1] = HALF_SKY_SUBDIVISIONS; + + // + // iterate through the subdivisions + // + for ( t = sky_mins_subd[1]+HALF_SKY_SUBDIVISIONS; t <= sky_maxs_subd[1]+HALF_SKY_SUBDIVISIONS; t++ ) + { + for ( s = sky_mins_subd[0]+HALF_SKY_SUBDIVISIONS; s <= sky_maxs_subd[0]+HALF_SKY_SUBDIVISIONS; s++ ) + { + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + s_skyPoints[t][s] ); + + s_skyTexCoords[t][s][0] = s_cloudTexCoords[i][t][s][0]; + s_skyTexCoords[t][s][1] = s_cloudTexCoords[i][t][s][1]; + } + } + + // only add indexes for first stage + FillCloudySkySide( sky_mins_subd, sky_maxs_subd, ( stage == 0 ) ); + } +} + +/* +** R_BuildCloudData +*/ +void R_BuildCloudData( shaderCommands_t *input ) +{ + int i; + shader_t *shader; + + shader = input->shader; + + assert( shader->sky ); + + sky_min = 1.0 / 256.0f; // FIXME: not correct? + sky_max = 255.0 / 256.0f; + + // set up for drawing + tess.numIndexes = 0; + tess.numVertexes = 0; + + if ( input->shader->sky->cloudHeight ) + { + for ( i = 0; i < input->shader->numUnfoggedPasses; i++ ) + { + FillCloudBox( input->shader, i ); + } + } +} + +/* +** R_InitSkyTexCoords +** Called when a sky shader is parsed +*/ +#define SQR( a ) ((a)*(a)) + +void R_InitSkyTexCoords( float heightCloud ) +{ + int i, s, t; + float radiusWorld = MAX_WORLD_COORD; + float p; + float sRad, tRad; + vec3_t skyVec; + vec3_t v; + + // init zfar so MakeSkyVec works even though + // a world hasn't been bounded + backEnd.viewParms.zFar = 1024; + + for ( i = 0; i < 6; i++ ) + { + for ( t = 0; t <= SKY_SUBDIVISIONS; t++ ) + { + for ( s = 0; s <= SKY_SUBDIVISIONS; s++ ) + { + // compute vector from view origin to sky side integral point + MakeSkyVec( ( s - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + ( t - HALF_SKY_SUBDIVISIONS ) / ( float ) HALF_SKY_SUBDIVISIONS, + i, + NULL, + skyVec ); + + // compute parametric value 'p' that intersects with cloud layer + p = ( 1.0f / ( 2 * DotProduct( skyVec, skyVec ) ) ) * + ( -2 * skyVec[2] * radiusWorld + + 2 * sqrt( SQR( skyVec[2] ) * SQR( radiusWorld ) + + 2 * SQR( skyVec[0] ) * radiusWorld * heightCloud + + SQR( skyVec[0] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[1] ) * radiusWorld * heightCloud + + SQR( skyVec[1] ) * SQR( heightCloud ) + + 2 * SQR( skyVec[2] ) * radiusWorld * heightCloud + + SQR( skyVec[2] ) * SQR( heightCloud ) ) ); + + s_cloudTexP[i][t][s] = p; + + // compute intersection point based on p + VectorScale( skyVec, p, v ); + v[2] += radiusWorld; + + // compute vector from world origin to intersection point 'v' + VectorNormalize( v ); + + sRad = acos( v[0] ); + tRad = acos( v[1] ); + + s_cloudTexCoords[i][t][s][0] = sRad; + s_cloudTexCoords[i][t][s][1] = tRad; + } + } + } +} + +//====================================================================================== + +/* +** RB_DrawSun +*/ +void RB_DrawSun( void ) { + float size; + float dist; + vec3_t origin, vec1, vec2; + vec3_t temp; + + if ( !backEnd.skyRenderedThisView ) { + return; + } + if ( !r_drawSun->integer ) { + return; + } + qglLoadMatrixf( backEnd.viewParms.world.modelMatrix ); + qglTranslatef (backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2]); + + dist = backEnd.viewParms.zFar / 1.75; // div sqrt(3) + size = dist * 0.4; + + VectorScale( tr.sunDirection, dist, origin ); + PerpendicularVector( vec1, tr.sunDirection ); + CrossProduct( tr.sunDirection, vec1, vec2 ); + + VectorScale( vec1, size, vec1 ); + VectorScale( vec2, size, vec2 ); + + // farthest depth range + qglDepthRange( 1.0, 1.0 ); + + // FIXME: use quad stamp + RB_BeginSurface( tr.sunShader, tess.fogNum ); + VectorCopy( origin, temp ); + VectorSubtract( temp, vec1, temp ); + VectorSubtract( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorAdd( temp, vec1, temp ); + VectorSubtract( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorAdd( temp, vec1, temp ); + VectorAdd( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 1; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + VectorCopy( origin, temp ); + VectorSubtract( temp, vec1, temp ); + VectorAdd( temp, vec2, temp ); + VectorCopy( temp, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = 255; + tess.vertexColors[tess.numVertexes][1] = 255; + tess.vertexColors[tess.numVertexes][2] = 255; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 1; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 0; + tess.indexes[tess.numIndexes++] = 2; + tess.indexes[tess.numIndexes++] = 3; + + RB_EndSurface(); + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); +} + + + + +/* +================ +RB_StageIteratorSky + +All of the visible sky triangles are in tess + +Other things could be stuck in here, like birds in the sky, etc +================ +*/ +void RB_StageIteratorSky( void ) { + if ( r_fastsky->integer ) { + return; + } + + if (skyboxportal && !(backEnd.refdef.rdflags & RDF_SKYBOXPORTAL)) + { + return; + } + + // go through all the polygons and project them onto + // the sky box to see which blocks on each side need + // to be drawn + RB_ClipSkyPolygons( &tess ); + + // r_showsky will let all the sky blocks be drawn in + // front of everything to allow developers to see how + // much sky is getting sucked in + if ( r_showsky->integer ) { + qglDepthRange( 0.0, 0.0 ); + } else { +#ifdef _XBOX + qglDepthRange( 0.99, 1.0 ); +#else + qglDepthRange( 1.0, 1.0 ); +#endif + } + + // draw the outer skybox + if ( tess.shader->sky->outerbox[0] && tess.shader->sky->outerbox[0] != tr.defaultImage ) { + qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight ); + + qglPushMatrix (); + GL_State( 0 ); + qglTranslatef (backEnd.viewParms.or.origin[0], backEnd.viewParms.or.origin[1], backEnd.viewParms.or.origin[2]); + + DrawSkyBox( tess.shader ); + + qglPopMatrix(); + } + + // generate the vertexes for all the clouds, which will be drawn + // by the generic shader routine + R_BuildCloudData( &tess ); + + RB_StageIteratorGeneric(); + + // draw the inner skybox + + + // back to normal depth range + qglDepthRange( 0.0, 1.0 ); + + // note that sky was drawn so we will draw a sun later + backEnd.skyRenderedThisView = qtrue; +} + diff --git a/code/renderer/tr_stl.cpp b/code/renderer/tr_stl.cpp new file mode 100644 index 0000000..e767c4b --- /dev/null +++ b/code/renderer/tr_stl.cpp @@ -0,0 +1,82 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#pragma warning(disable : 4786) // identifier was truncated + +// Filename:- tr_stl.cpp +// +// I mainly made this file because I was getting sick of all the stupid error messages in MS's STL implementation, +// and didn't want them showing up in the renderer files they were used in. This way keeps them more or less invisible +// because of minimal dependancies +// +#include "tr_local.h" // this isn't actually needed other than getting rid of warnings via pragmas +#include "tr_stl.h" + +#pragma warning( push,3 ) + +#pragma warning(disable : 4514) // unreferenced inline function has been removed (within STL, not this code) +#pragma warning(disable : 4710) // +#pragma warning(disable : 4503) // decorated name length xceeded, name was truncated + +#include +#include "../qcommon/sstring.h" // #include +using namespace std; + +typedef map ShaderEntryPtrs_t; + ShaderEntryPtrs_t *ShaderEntryPtrs; + + + + + + +void ShaderEntryPtrs_Clear(void) +{ + if( ShaderEntryPtrs ) + { + delete ShaderEntryPtrs; + ShaderEntryPtrs = NULL; + } +} + +int ShaderEntryPtrs_Size(void) +{ + return ShaderEntryPtrs ? ShaderEntryPtrs->size() : 0; +} + +void ShaderEntryPtrs_Insert(const char *token, const char *p) +{ + if( !ShaderEntryPtrs ) + ShaderEntryPtrs = new ShaderEntryPtrs_t; + + ShaderEntryPtrs_t::iterator it = ShaderEntryPtrs->find(token); + + if (it == ShaderEntryPtrs->end()) + { + (*ShaderEntryPtrs)[token] = p; + } +} + +// returns NULL if not found... +// +const char *ShaderEntryPtrs_Lookup(const char *psShaderName) +{ + if( !ShaderEntryPtrs ) + return NULL; + + ShaderEntryPtrs_t::iterator it = ShaderEntryPtrs->find(psShaderName); + if (it != ShaderEntryPtrs->end()) + { + const char *p = (*it).second; + return p; + } + + return NULL; +} + + + +#pragma warning ( pop ) + diff --git a/code/renderer/tr_stl.h b/code/renderer/tr_stl.h new file mode 100644 index 0000000..644f9e2 --- /dev/null +++ b/code/renderer/tr_stl.h @@ -0,0 +1,31 @@ +// Filename: tr_stl.h +// +// I had to make this new file, because if I put the STL "map" include inside tr_local.h then one of the other header +// files got compile errors because of using "map" in the function protos as a GLEnum, this way seemed simpler... + +#ifndef TR_STL_H + + +// REM this out if you want to compile without using STL (but slower of course) +// +#define USE_STL_FOR_SHADER_LOOKUPS + + + + +#ifdef USE_STL_FOR_SHADER_LOOKUPS + + void ShaderEntryPtrs_Clear(void); + int ShaderEntryPtrs_Size(void); + const char *ShaderEntryPtrs_Lookup(const char *psShaderName); + void ShaderEntryPtrs_Insert(const char *token, const char *p); + +#else + + #define ShaderEntryPtrs_Clear() + +#endif // #ifdef USE_STL_FOR_SHADER_LOOKUPS + +#endif // #ifndef TR_STL_H + + diff --git a/code/renderer/tr_surface.cpp b/code/renderer/tr_surface.cpp new file mode 100644 index 0000000..daef0cd --- /dev/null +++ b/code/renderer/tr_surface.cpp @@ -0,0 +1,2477 @@ +// tr_surf.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + +#ifdef _XBOX +#include "../win32/glw_win_dx8.h" +#endif + +/* + + THIS ENTIRE FILE IS BACK END + +backEnd.currentEntity will be valid. + +Tess_Begin has already been called for the surface's shader. + +The modelview matrix will be set. + +It is safe to actually issue drawing commands here if you don't want to +use the shader system. +*/ + + +//============================================================================ + + +/* +============== +RB_CheckOverflow +============== +*/ +void RB_CheckOverflow( int verts, int indexes ) { + if ( tess.shader == tr.shadowShader ) { + if (tess.numVertexes + verts < SHADER_MAX_VERTEXES/2 + && tess.numIndexes + indexes < SHADER_MAX_INDEXES) { + return; + } + } else + if (tess.numVertexes + verts < SHADER_MAX_VERTEXES + && tess.numIndexes + indexes < SHADER_MAX_INDEXES) { + return; + } + + RB_EndSurface(); + + if ( verts >= SHADER_MAX_VERTEXES ) { + Com_Error(ERR_DROP, "RB_CheckOverflow: verts > MAX (%d > %d)", verts, SHADER_MAX_VERTEXES ); + } + if ( indexes >= SHADER_MAX_INDEXES ) { + Com_Error(ERR_DROP, "RB_CheckOverflow: indices > MAX (%d > %d)", indexes, SHADER_MAX_INDEXES ); + } + + RB_BeginSurface(tess.shader, tess.fogNum ); +} + + +/* +============== +RB_AddQuadStampExt +============== +*/ +void RB_AddQuadStampExt( vec3_t origin, vec3_t left, vec3_t up, byte *color, float s1, float t1, float s2, float t2 ) { + vec3_t normal; + int ndx; + + RB_CHECKOVERFLOW( 4, 6 ); + + ndx = tess.numVertexes; + + // triangle indexes for a simple quad + tess.indexes[ tess.numIndexes ] = ndx; + tess.indexes[ tess.numIndexes + 1 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 2 ] = ndx + 3; + + tess.indexes[ tess.numIndexes + 3 ] = ndx + 3; + tess.indexes[ tess.numIndexes + 4 ] = ndx + 1; + tess.indexes[ tess.numIndexes + 5 ] = ndx + 2; + + tess.xyz[ndx][0] = origin[0] + left[0] + up[0]; + tess.xyz[ndx][1] = origin[1] + left[1] + up[1]; + tess.xyz[ndx][2] = origin[2] + left[2] + up[2]; + + tess.xyz[ndx+1][0] = origin[0] - left[0] + up[0]; + tess.xyz[ndx+1][1] = origin[1] - left[1] + up[1]; + tess.xyz[ndx+1][2] = origin[2] - left[2] + up[2]; + + tess.xyz[ndx+2][0] = origin[0] - left[0] - up[0]; + tess.xyz[ndx+2][1] = origin[1] - left[1] - up[1]; + tess.xyz[ndx+2][2] = origin[2] - left[2] - up[2]; + + tess.xyz[ndx+3][0] = origin[0] + left[0] - up[0]; + tess.xyz[ndx+3][1] = origin[1] + left[1] - up[1]; + tess.xyz[ndx+3][2] = origin[2] + left[2] - up[2]; + + + // constant normal all the way around + VectorSubtract( vec3_origin, backEnd.viewParms.or.axis[0], normal ); + + tess.normal[ndx][0] = tess.normal[ndx+1][0] = tess.normal[ndx+2][0] = tess.normal[ndx+3][0] = normal[0]; + tess.normal[ndx][1] = tess.normal[ndx+1][1] = tess.normal[ndx+2][1] = tess.normal[ndx+3][1] = normal[1]; + tess.normal[ndx][2] = tess.normal[ndx+1][2] = tess.normal[ndx+2][2] = tess.normal[ndx+3][2] = normal[2]; + + // standard square texture coordinates + tess.texCoords[ndx][0][0] = tess.texCoords[ndx][1][0] = s1; + tess.texCoords[ndx][0][1] = tess.texCoords[ndx][1][1] = t1; + + tess.texCoords[ndx+1][0][0] = tess.texCoords[ndx+1][1][0] = s2; + tess.texCoords[ndx+1][0][1] = tess.texCoords[ndx+1][1][1] = t1; + + tess.texCoords[ndx+2][0][0] = tess.texCoords[ndx+2][1][0] = s2; + tess.texCoords[ndx+2][0][1] = tess.texCoords[ndx+2][1][1] = t2; + + tess.texCoords[ndx+3][0][0] = tess.texCoords[ndx+3][1][0] = s1; + tess.texCoords[ndx+3][0][1] = tess.texCoords[ndx+3][1][1] = t2; + + // constant color all the way around + // should this be identity and let the shader specify from entity? + * ( unsigned int * ) &tess.vertexColors[ndx] = + * ( unsigned int * ) &tess.vertexColors[ndx+1] = + * ( unsigned int * ) &tess.vertexColors[ndx+2] = + * ( unsigned int * ) &tess.vertexColors[ndx+3] = + * ( unsigned int * )color; + + tess.numVertexes += 4; + tess.numIndexes += 6; +} + +/* +============== +RB_AddQuadStamp +============== +*/ +void RB_AddQuadStamp( vec3_t origin, vec3_t left, vec3_t up, byte *color ) { + RB_AddQuadStampExt( origin, left, up, color, 0, 0, 1, 1 ); +} + +/* +============== +RB_SurfaceSprite +============== +*/ +static void RB_SurfaceSprite( void ) { + vec3_t left, up; + float radius; + + // calculate the xyz locations for the four corners + radius = backEnd.currentEntity->e.radius; + if ( backEnd.currentEntity->e.rotation == 0 ) { + VectorScale( backEnd.viewParms.or.axis[1], radius, left ); + VectorScale( backEnd.viewParms.or.axis[2], radius, up ); + } else { + float s, c; + float ang; + + ang = M_PI * backEnd.currentEntity->e.rotation / 180; + s = sin( ang ); + c = cos( ang ); + + VectorScale( backEnd.viewParms.or.axis[1], c * radius, left ); + VectorMA( left, -s * radius, backEnd.viewParms.or.axis[2], left ); + + VectorScale( backEnd.viewParms.or.axis[2], c * radius, up ); + VectorMA( up, s * radius, backEnd.viewParms.or.axis[1], up ); + } + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( backEnd.currentEntity->e.origin, left, up, backEnd.currentEntity->e.shaderRGBA ); +} + +/* +======================= +RB_SurfaceOrientedQuad +======================= +*/ +static void RB_SurfaceOrientedQuad( void ) +{ + vec3_t left, up; + float radius; + + // calculate the xyz locations for the four corners + radius = backEnd.currentEntity->e.radius; + MakeNormalVectors( backEnd.currentEntity->e.axis[0], left, up ); + + if ( backEnd.currentEntity->e.rotation == 0 ) + { + VectorScale( left, radius, left ); + VectorScale( up, radius, up ); + } + else + { + vec3_t tempLeft, tempUp; + float s, c; + float ang; + + ang = M_PI * backEnd.currentEntity->e.rotation / 180; + s = sin( ang ); + c = cos( ang ); + + // Use a temp so we don't trash the values we'll need later + VectorScale( left, c * radius, tempLeft ); + VectorMA( tempLeft, -s * radius, up, tempLeft ); + + VectorScale( up, c * radius, tempUp ); + VectorMA( tempUp, s * radius, left, up ); // no need to use the temp anymore, so copy into the dest vector ( up ) + + // This was copied for safekeeping, we're done, so we can move it back to left + VectorCopy( tempLeft, left ); + } + + if ( backEnd.viewParms.isMirror ) + { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( backEnd.currentEntity->e.origin, left, up, backEnd.currentEntity->e.shaderRGBA ); +} + +/* +============== +RB_SurfaceLine +============== +*/ +// +// Values for a proper line render primitive... +// Width +// STScale (how many times to loop a texture) +// alpha +// RGB +// +// Values for proper line object... +// lifetime +// dscale +// startalpha, endalpha +// startRGB, endRGB +// + +static void DoLine( const vec3_t start, const vec3_t end, const vec3_t up, float spanWidth ) +{ + float spanWidth2; + int vbase; + + RB_CHECKOVERFLOW( 4, 6 ); + + vbase = tess.numVertexes; + + spanWidth2 = -spanWidth; + + VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0];// * 0.25;//wtf??not sure why the code would be doing this + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1];// * 0.25; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2];// * 0.25; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3];// * 0.25; + tess.numVertexes++; + + VectorMA( start, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1;//backEnd.currentEntity->e.shaderTexCoord[0]; + tess.texCoords[tess.numVertexes][0][1] = 0; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + VectorMA( end, spanWidth, up, tess.xyz[tess.numVertexes] ); + + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = 1;//backEnd.currentEntity->e.shaderTexCoord[1]; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1;//backEnd.currentEntity->e.shaderTexCoord[0]; + tess.texCoords[tess.numVertexes][0][1] = 1;//backEnd.currentEntity->e.shaderTexCoord[1]; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; +} + +static void DoLine2( const vec3_t start, const vec3_t end, const vec3_t up, float spanWidth, float spanWidth2, const float tcStart, const float tcEnd ) +{ + int vbase; + + RB_CHECKOVERFLOW( 4, 6 ); + + vbase = tess.numVertexes; + + VectorMA( start, spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = tcStart; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0];// * 0.25;//wtf??not sure why the code would be doing this + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1];// * 0.25; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2];// * 0.25; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3];// * 0.25; + tess.numVertexes++; + + VectorMA( start, -spanWidth, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1;//backEnd.currentEntity->e.shaderTexCoord[0]; + tess.texCoords[tess.numVertexes][0][1] = tcStart; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + VectorMA( end, spanWidth2, up, tess.xyz[tess.numVertexes] ); + + tess.texCoords[tess.numVertexes][0][0] = 0; + tess.texCoords[tess.numVertexes][0][1] = tcEnd;//backEnd.currentEntity->e.shaderTexCoord[1]; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + VectorMA( end, -spanWidth2, up, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = 1;//backEnd.currentEntity->e.shaderTexCoord[0]; + tess.texCoords[tess.numVertexes][0][1] = tcEnd;//backEnd.currentEntity->e.shaderTexCoord[1]; + tess.vertexColors[tess.numVertexes][0] = backEnd.currentEntity->e.shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = backEnd.currentEntity->e.shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = backEnd.currentEntity->e.shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = backEnd.currentEntity->e.shaderRGBA[3]; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; +} + +//----------------- +// RB_SurfaceLine +//----------------- +static void RB_SurfaceLine( void ) +{ + refEntity_t *e; + vec3_t right; + vec3_t start, end; + vec3_t v1, v2; + + e = &backEnd.currentEntity->e; + + VectorCopy( e->oldorigin, end ); + VectorCopy( e->origin, start ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.or.origin, v1 ); + VectorSubtract( end, backEnd.viewParms.or.origin, v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + DoLine( start, end, right, e->radius); +} + +/* +============== +RB_SurfaceCylinder +============== +*/ + +#define NUM_CYLINDER_SEGMENTS 40 + +// e->origin holds the bottom point +// e->oldorigin holds the top point +// e->radius holds the radius + +// If a cylinder has a tapered end that has a very small radius, the engine converts it to a cone. Not a huge savings, but the texture mapping is slightly better +// and it uses half as many indicies as the cylinder version +//------------------------------------- +static void RB_SurfaceCone( void ) +//------------------------------------- +{ + static vec3_t points[NUM_CYLINDER_SEGMENTS]; + vec3_t vr, vu, midpoint; + vec3_t tapered, base; + float detail, length; + int i; + int segments; + refEntity_t *e; + + e = &backEnd.currentEntity->e; + + //Work out the detail level of this cylinder + VectorAdd( e->origin, e->oldorigin, midpoint ); + VectorScale(midpoint, 0.5, midpoint); // Average start and end + + VectorSubtract( midpoint, backEnd.viewParms.or.origin, midpoint ); + length = VectorNormalize( midpoint ); + + // this doesn't need to be perfect....just a rough compensation for zoom level is enough + length *= (backEnd.viewParms.fovX / 90.0f); + + detail = 1 - ((float) length / 2048 ); + segments = NUM_CYLINDER_SEGMENTS * detail; + + // 3 is the absolute minimum, but the pop between 3-8 is too noticeable + if ( segments < 8 ) + { + segments = 8; + } + + if ( segments > NUM_CYLINDER_SEGMENTS ) + { + segments = NUM_CYLINDER_SEGMENTS; + } + + // Get the direction vector + MakeNormalVectors( e->axis[0], vr, vu ); + + // we only need to rotate around the larger radius, the smaller radius get's welded + if ( e->radius < e->backlerp ) + { + VectorScale( vu, e->backlerp, vu ); + VectorCopy( e->origin, base ); + VectorCopy( e->oldorigin, tapered ); + } + else + { + VectorScale( vu, e->radius, vu ); + VectorCopy( e->origin, tapered ); + VectorCopy( e->oldorigin, base ); + } + + + // Calculate the step around the cylinder + detail = 360.0f / (float)segments; + + for ( i = 0; i < segments; i++ ) + { + // ring + RotatePointAroundVector( points[i], e->axis[0], vu, detail * i ); + VectorAdd( points[i], base, points[i] ); + } + + // Calculate the texture coords so the texture can wrap around the whole cylinder + detail = 1.0f / (float)segments; + + RB_CHECKOVERFLOW( 2 * (segments+1), 3 * segments ); // this isn't 100% accurate + + int vbase = tess.numVertexes; + + for ( i = 0; i < segments; i++ ) + { + VectorCopy( points[i], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = detail * i; + tess.texCoords[tess.numVertexes][0][1] = 1.0f; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + // We could add this vert once, but using the given texture mapping method, we need to generate different texture coordinates + VectorCopy( tapered, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = detail * i + detail * 0.5f; // set the texture coordinates to the point half-way between the untapered ends....but on the other end of the texture + tess.texCoords[tess.numVertexes][0][1] = 0.0f; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + } + + // last point has the same verts as the first, but does not share the same tex coords, so we have to duplicate it + VectorCopy( points[0], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = detail * i; + tess.texCoords[tess.numVertexes][0][1] = 1.0f; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + VectorCopy( tapered, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = detail * i + detail * 0.5f; + tess.texCoords[tess.numVertexes][0][1] = 0.0f; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + // do the welding + for ( i = 0; i < segments; i++ ) + { + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + vbase += 2; + } +} + +//------------------------------------- +static void RB_SurfaceCylinder( void ) +//------------------------------------- +{ + static vec3_t lower_points[NUM_CYLINDER_SEGMENTS], upper_points[NUM_CYLINDER_SEGMENTS]; + vec3_t vr, vu, midpoint, v1; + float detail, length; + int i; + int segments; + refEntity_t *e; + + e = &backEnd.currentEntity->e; + + // check for tapering + if ( !( e->radius < 0.3f && e->backlerp < 0.3f) && ( e->radius < 0.3f || e->backlerp < 0.3f )) + { + // One end is sufficiently tapered to consider changing it to a cone + RB_SurfaceCone(); + return; + } + + //Work out the detail level of this cylinder + VectorAdd( e->origin, e->oldorigin, midpoint ); + VectorScale(midpoint, 0.5, midpoint); // Average start and end + + VectorSubtract( midpoint, backEnd.viewParms.or.origin, midpoint ); + length = VectorNormalize( midpoint ); + + // this doesn't need to be perfect....just a rough compensation for zoom level is enough + length *= (backEnd.viewParms.fovX / 90.0f); + + detail = 1 - ((float) length / 2048 ); + segments = NUM_CYLINDER_SEGMENTS * detail; + + // 3 is the absolute minimum, but the pop between 3-8 is too noticeable + if ( segments < 8 ) + { + segments = 8; + } + + if ( segments > NUM_CYLINDER_SEGMENTS ) + { + segments = NUM_CYLINDER_SEGMENTS; + } + + //Get the direction vector + MakeNormalVectors( e->axis[0], vr, vu ); + + VectorScale( vu, e->radius, v1 ); // size1 + VectorScale( vu, e->backlerp, vu ); // size2 + + // Calculate the step around the cylinder + detail = 360.0f / (float)segments; + + for ( i = 0; i < segments; i++ ) + { + //Upper ring + RotatePointAroundVector( upper_points[i], e->axis[0], vu, detail * i ); + VectorAdd( upper_points[i], e->origin, upper_points[i] ); + + //Lower ring + RotatePointAroundVector( lower_points[i], e->axis[0], v1, detail * i ); + VectorAdd( lower_points[i], e->oldorigin, lower_points[i] ); + } + + // Calculate the texture coords so the texture can wrap around the whole cylinder + detail = 1.0f / (float)segments; + + RB_CHECKOVERFLOW( 2 * (segments+1), 6 * segments ); // this isn't 100% accurate + + int vbase = tess.numVertexes; + + for ( i = 0; i < segments; i++ ) + { + VectorCopy( upper_points[i], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = detail * i; + tess.texCoords[tess.numVertexes][0][1] = 1.0f; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + VectorCopy( lower_points[i], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = detail * i; + tess.texCoords[tess.numVertexes][0][1] = 0.0f; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + } + + // last point has the same verts as the first, but does not share the same tex coords, so we have to duplicate it + VectorCopy( upper_points[0], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = detail * i; + tess.texCoords[tess.numVertexes][0][1] = 1.0f; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + VectorCopy( lower_points[0], tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = detail * i; + tess.texCoords[tess.numVertexes][0][1] = 0.0f; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + // glue the verts + for ( i = 0; i < segments; i++ ) + { + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 2; + + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; + + vbase += 2; + } +} + +static vec3_t sh1, sh2; +static f_count; + +// Up front, we create a random "shape", then apply that to each line segment...and then again to each of those segments...kind of like a fractal +//---------------------------------------------------------------------------- +static void CreateShape() +//---------------------------------------------------------------------------- +{ + VectorSet( sh1, 0.66f,// + crandom() * 0.1f, // fwd + 0.08f + crandom() * 0.02f, + 0.08f + crandom() * 0.02f ); + + // it seems to look best to have a point on one side of the ideal line, then the other point on the other side. + VectorSet( sh2, 0.33f,// + crandom() * 0.1f, // fwd + -sh1[1] + crandom() * 0.02f, // forcing point to be on the opposite side of the line -- right + -sh1[2] + crandom() * 0.02f );// up +} + +//---------------------------------------------------------------------------- +static void ApplyShape( vec3_t start, vec3_t end, vec3_t right, float sradius, float eradius, int count, float startPerc, float endPerc ) +//---------------------------------------------------------------------------- +{ + vec3_t point1, point2, fwd; + vec3_t rt, up; + float perc, dis; + + if ( count < 1 ) + { + // done recursing + DoLine2( start, end, right, sradius, eradius, startPerc, endPerc ); + return; + } + + CreateShape(); + + VectorSubtract( end, start, fwd ); + dis = VectorNormalize( fwd ) * 0.7f; + MakeNormalVectors( fwd, rt, up ); + + perc = sh1[0]; + + VectorScale( start, perc, point1 ); + VectorMA( point1, 1.0f - perc, end, point1 ); + VectorMA( point1, dis * sh1[1], rt, point1 ); + VectorMA( point1, dis * sh1[2], up, point1 ); + + // do a quick and dirty interpolation of the radius at that point + float rads1, rads2; + + rads1 = sradius * 0.666f + eradius * 0.333f; + rads2 = sradius * 0.333f + eradius * 0.666f; + + // recursion + ApplyShape( start, point1, right, sradius, rads1, count - 1, startPerc, startPerc * 0.666f + endPerc * 0.333f ); + + perc = sh2[0]; + + VectorScale( start, perc, point2 ); + VectorMA( point2, 1.0f - perc, end, point2 ); + VectorMA( point2, dis * sh2[1], rt, point2 ); + VectorMA( point2, dis * sh2[2], up, point2 ); + + // recursion + ApplyShape( point2, point1, right, rads1, rads2, count - 1, startPerc * 0.333f + endPerc * 0.666f, startPerc * 0.666f + endPerc * 0.333f ); + ApplyShape( point2, end, right, rads2, eradius, count - 1, startPerc * 0.333f + endPerc * 0.666f, endPerc ); +} + +//---------------------------------------------------------------------------- +static void DoBoltSeg( vec3_t start, vec3_t end, vec3_t right, float radius ) +//---------------------------------------------------------------------------- +{ + refEntity_t *e; + vec3_t fwd, old; + vec3_t cur, off={10,10,10}; + vec3_t rt, up; + vec3_t temp; + int i; + float dis, oldPerc = 0.0f, perc, oldRadius, newRadius; + + e = &backEnd.currentEntity->e; + + VectorSubtract( end, start, fwd ); + dis = VectorNormalize( fwd ); + + if (dis > 2000) //freaky long + { +// VID_Printf( PRINT_WARNING, "DoBoltSeg: insane distance.\n" ); + dis = 2000; + } + MakeNormalVectors( fwd, rt, up ); + + VectorCopy( start, old ); + + newRadius = oldRadius = radius; + + for ( i = 16; i <= dis; i+= 16 ) + { + // because of our large step size, we may not actually draw to the end. In this case, fudge our percent so that we are basically complete + if ( i + 16 > dis ) + { + perc = 1.0f; + } + else + { + // percentage of the amount of line completed + perc = (float)i / dis; + } + + // create our level of deviation for this point + VectorScale( fwd, Q_crandom(&e->frame) * 3.0f, temp ); // move less in fwd direction, chaos also does not affect this + VectorMA( temp, Q_crandom(&e->frame) * 7.0f * e->angles[0], rt, temp ); // move more in direction perpendicular to line, angles is really the chaos + VectorMA( temp, Q_crandom(&e->frame) * 7.0f * e->angles[0], up, temp ); // move more in direction perpendicular to line + + // track our total level of offset from the ideal line + VectorAdd( off, temp, off ); + + // Move from start to end, always adding our current level of offset from the ideal line + // Even though we are adding a random offset.....by nature, we always move from exactly start....to end + VectorAdd( start, off, cur ); + VectorScale( cur, 1.0f - perc, cur ); + VectorMA( cur, perc, end, cur ); + + if ( e->renderfx & RF_TAPERED ) + { + // This does pretty close to perfect tapering since apply shape interpolates the old and new as it goes along. + // by using one minus the square, the radius stays fairly constant, then drops off quickly at the very point of the bolt + oldRadius = radius * (1.0f-oldPerc*oldPerc); + newRadius = radius * (1.0f-perc*perc); + } + + // Apply the random shape to our line seg to give it some micro-detail-jaggy-coolness. + ApplyShape( cur, old, right, newRadius, oldRadius, 2 - r_lodbias->integer, 0, 1 ); + + // randomly split off to create little tendrils, but don't do it too close to the end and especially if we are not even of the forked variety + if (( e->renderfx & RF_FORKED ) && f_count > 0 && Q_random(&e->frame) > 0.93f && (1.0f - perc) > 0.8f ) + { + vec3_t newDest; + + f_count--; + + // Pick a point somewhere between the current point and the final endpoint + VectorAdd( cur, e->oldorigin, newDest ); + VectorScale( newDest, 0.5f, newDest ); + + // And then add some crazy offset + for ( int t = 0; t < 3; t++ ) + { + newDest[t] += Q_crandom(&e->frame) * 80; + } + + // we could branch off using OLD and NEWDEST, but that would allow multiple forks...whereas, we just want simpler brancing + DoBoltSeg( cur, newDest, right, newRadius ); + } + + // Current point along the line becomes our new old attach point + VectorCopy( cur, old ); + oldPerc = perc; + } +} + +//------------------------------------------ +static void RB_SurfaceElectricity() +//------------------------------------------ +{ + refEntity_t *e; + vec3_t right, fwd; + vec3_t start, end; + vec3_t v1, v2; + float radius, perc = 1.0f, dis; + + e = &backEnd.currentEntity->e; + radius = e->radius; + + VectorCopy( e->origin, start ); + + VectorSubtract( e->oldorigin, start, fwd ); + dis = VectorNormalize( fwd ); + + // see if we should grow from start to end + if ( e->renderfx & RF_GROW ) + { +// perc = 1.0f - ( e->endTime - tr.refdef.time ) / e->angles[1]/*duration*/; + // Hack to make this effect non-framerate dependant + perc = 1.0f - ( Q_irand(0, 5) ) / e->angles[1]; + + if ( perc > 1.0f ) + { + perc = 1.0f; + } + else if ( perc < 0.0f ) + { + perc = 0.0f; + } + } + + VectorMA( start, perc * dis, fwd, e->oldorigin ); + VectorCopy( e->oldorigin, end ); + + // compute side vector + VectorSubtract( start, backEnd.viewParms.or.origin, v1 ); + VectorSubtract( end, backEnd.viewParms.or.origin, v2 ); + CrossProduct( v1, v2, right ); + VectorNormalize( right ); + + // allow now more than three branches on branch type electricity + f_count = 3; + DoBoltSeg( start, end, right, radius ); +} + +/* +============= +RB_SurfacePolychain +============= +*/ +/* // we could try to do something similar to this to get better normals into the tess for these types of surfs. As it stands, any shader pass that +// requires a normal ( env map ) will not work properly since the normals seem to essentially be random garbage. +void RB_SurfacePolychain( srfPoly_t *p ) { + int i; + int numv; + vec3_t a,b,normal={1,0,0}; + + RB_CHECKOVERFLOW( p->numVerts, 3*(p->numVerts - 2) ); + + if ( p->numVerts >= 3 ) + { + VectorSubtract( p->verts[0].xyz, p->verts[1].xyz, a ); + VectorSubtract( p->verts[2].xyz, p->verts[1].xyz, b ); + CrossProduct( a,b, normal ); + VectorNormalize( normal ); + } + + // fan triangles into the tess array + numv = tess.numVertexes; + for ( i = 0; i < p->numVerts; i++ ) { + VectorCopy( p->verts[i].xyz, tess.xyz[numv] ); + tess.texCoords[numv][0][0] = p->verts[i].st[0]; + tess.texCoords[numv][0][1] = p->verts[i].st[1]; + VectorCopy( normal, tess.normal[numv] ); + *(int *)&tess.vertexColors[numv] = *(int *)p->verts[ i ].modulate; + + numv++; + } + + // generate fan indexes into the tess array + for ( i = 0; i < p->numVerts-2; i++ ) { + tess.indexes[tess.numIndexes + 0] = tess.numVertexes; + tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1; + tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2; + tess.numIndexes += 3; + } + + tess.numVertexes = numv; +} +*/ +void RB_SurfacePolychain( srfPoly_t *p ) { + int i; + int numv; + + RB_CHECKOVERFLOW( p->numVerts, 3*(p->numVerts - 2) ); + + // fan triangles into the tess array + numv = tess.numVertexes; + for ( i = 0; i < p->numVerts; i++ ) { + VectorCopy( p->verts[i].xyz, tess.xyz[numv] ); + tess.texCoords[numv][0][0] = p->verts[i].st[0]; + tess.texCoords[numv][0][1] = p->verts[i].st[1]; + *(int *)&tess.vertexColors[numv] = *(int *)p->verts[ i ].modulate; + + numv++; + } + + // generate fan indexes into the tess array + for ( i = 0; i < p->numVerts-2; i++ ) { + tess.indexes[tess.numIndexes + 0] = tess.numVertexes; + tess.indexes[tess.numIndexes + 1] = tess.numVertexes + i + 1; + tess.indexes[tess.numIndexes + 2] = tess.numVertexes + i + 2; + tess.numIndexes += 3; + } + + tess.numVertexes = numv; +} + +inline static ulong ComputeFinalVertexColor(const byte *colors) +{ + int k; + byte result[4]; + ulong r, g, b; + + *(int *)result = *(int *)colors; + if (tess.shader->lightmapIndex[0] != LIGHTMAP_BY_VERTEX ) + { + return *(ulong *)result; + } + if (r_fullbright->integer) + { + result[0] = 255; + result[1] = 255; + result[2] = 255; + return *(ulong *)result; + } + // an optimization could be added here to compute the style[0] (which is always the world normal light) + r = g = b = 0; + for(k = 0; k < MAXLIGHTMAPS; k++) + { + if (tess.shader->styles[k] < LS_UNUSED) + { + byte *styleColor = styleColors[tess.shader->styles[k]]; + + // Slightly faster version, as suggested by MS: + r += colors[0] * styleColor[0]; + g += colors[1] * styleColor[1]; + b += colors[2] * styleColor[2]; + colors += 4; + + + /*r += (ulong)(*colors++) * (ulong)(*styleColor++); + g += (ulong)(*colors++) * (ulong)(*styleColor++); + b += (ulong)(*colors++) * (ulong)(*styleColor); + colors++;*/ + + } + else + { + break; + } + } + + // Again, faster version suggested by MS (avoids int->float->int mess): + /*result[0] = r >> 8; + result[1] = g >> 8; + result[2] = b >> 8; + + result[0] = result[0] > 255 ? 255 : result[0]; + result[1] = result[1] > 255 ? 255 : result[1]; + result[2] = result[2] > 255 ? 255 : result[2];*/ + + // The faster way sometimes produces wrong values + result[0] = Com_Clamp(0, 255, r >> 8); + result[1] = Com_Clamp(0, 255, g >> 8); + result[2] = Com_Clamp(0, 255, b >> 8); + + + return *(ulong *)result; +} + +#ifdef _XBOX +//16 bits in, 32 bits out +inline ulong ComputeFinalVertexColor16(const byte *colors) +{ + int k; + byte result[4]; + byte color32[4]; + ulong r, g, b; + + result[0] = colors[0] & 0xF0; + result[1] = colors[0] << 4; + result[2] = colors[1] & 0xF0; + result[3] = colors[1] << 4; + if (tess.shader->lightmapIndex[0] != LIGHTMAP_BY_VERTEX || r_fullbright->integer ) + { + result[0] = 255; + result[1] = 255; + result[2] = 255; + return *(ulong *)result; + } + // an optimization could be added here to compute the style[0] (which is always the world normal light) + r = g = b = 0; + for(k = 0; k < MAXLIGHTMAPS; k++) + { + if (tess.shader->styles[k] < LS_UNUSED) + { + byte *styleColor = styleColors[tess.shader->styles[k]]; + + color32[0] = colors[k * 2] & 0xF0; + color32[1] = colors[k * 2] << 4; + color32[2] = colors[k * 2 + 1] & 0xF0; + + r += (ulong)(color32[0]) * (ulong)(*styleColor++); + g += (ulong)(color32[1]) * (ulong)(*styleColor++); + b += (ulong)(color32[2]) * (ulong)(*styleColor); + } + else + { + break; + } + } + result[0] = Com_Clamp(0, 255, r >> 8); + result[1] = Com_Clamp(0, 255, g >> 8); + result[2] = Com_Clamp(0, 255, b >> 8); + + return *(ulong *)result; +} +#endif + +/* +============= +RB_SurfaceTriangles +============= +*/ +void RB_SurfaceTriangles( srfTriangles_t *srf ) { + int i, k; + drawVert_t *dv; + float *xyz, *normal, *texCoords; +#ifdef _XBOX + float *tangent; +#endif + byte *color; + int dlightBits; + + dlightBits = srf->dlightBits; + tess.dlightBits |= dlightBits; + + RB_CHECKOVERFLOW( srf->numVerts, srf->numIndexes ); + + for ( i = 0 ; i < srf->numIndexes ; i += 3 ) { + tess.indexes[ tess.numIndexes + i + 0 ] = tess.numVertexes + srf->indexes[ i + 0 ]; + tess.indexes[ tess.numIndexes + i + 1 ] = tess.numVertexes + srf->indexes[ i + 1 ]; + tess.indexes[ tess.numIndexes + i + 2 ] = tess.numVertexes + srf->indexes[ i + 2 ]; + } + tess.numIndexes += srf->numIndexes; + + dv = srf->verts; + xyz = tess.xyz[ tess.numVertexes ]; + normal = tess.normal[ tess.numVertexes ]; + texCoords = tess.texCoords[ tess.numVertexes ][0]; + color = tess.vertexColors[ tess.numVertexes ]; +#ifdef _XBOX + tangent = tess.tangent[ tess.numVertexes ]; +#endif + + for ( i = 0 ; i < srf->numVerts ; i++, dv++) + { +#ifdef _XBOX + xyz[0] = (float)dv->xyz[0]; + xyz[1] = (float)dv->xyz[1]; + xyz[2] = (float)dv->xyz[2]; + xyz += 4; + + if ( tess.shader->needsNormal || tess.dlightBits ) + { + normal[0] = (float)dv->normal[0] / 32767.f; + normal[1] = (float)dv->normal[1] / 32767.f; + normal[2] = (float)dv->normal[2] / 32767.f; + normal += 4; + } + + if( tess.shader->needsTangent || tess.dlightBits ) + { + tangent[0] = dv->tangent[0]; + tangent[1] = dv->tangent[1]; + tangent[2] = dv->tangent[2]; + tangent += 4; + + tess.setTangents = true; + } + + Q_CastShort2FloatScale(&texCoords[0], &dv->dvst[0], 1.f / DRAWVERT_ST_SCALE); + Q_CastShort2FloatScale(&texCoords[1], &dv->dvst[1], 1.f / DRAWVERT_ST_SCALE); + + for(k=0;klightmapIndex[k] >= 0) + { + Q_CastShort2FloatScale(&texCoords[2+(k*2)+0], + &dv->dvlightmap[k][0], 1.f / DRAWVERT_LIGHTMAP_SCALE); + Q_CastShort2FloatScale(&texCoords[2+(k*2)+1], + &dv->dvlightmap[k][1], 1.f / DRAWVERT_LIGHTMAP_SCALE); + } + else + { // can't have an empty slot in the middle, so we are done + break; + } + } + texCoords += NUM_TEX_COORDS*2; + +#ifdef COMPRESS_VERTEX_COLORS + *(unsigned int*)color = ComputeFinalVertexColor16((byte *)dv->dvcolor); +#else + *(unsigned int*)color = ComputeFinalVertexColor((byte *)dv->dvcolor); +#endif + color += 4; +#else // _XBOX + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + xyz += 4; + + //if ( needsNormal ) + { + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + } + normal += 4; + + texCoords[0] = dv->st[0]; + texCoords[1] = dv->st[1]; + + for(k=0;klightmapIndex[k] >= 0) + { + texCoords[2+(k*2)] = dv->lightmap[k][0]; + texCoords[2+(k*2)+1] = dv->lightmap[k][1]; + } + else + { // can't have an empty slot in the middle, so we are done + break; + } + } + texCoords += NUM_TEX_COORDS*2; + + *(unsigned int*)color = ComputeFinalVertexColor((byte *)dv->color); + color += 4; +#endif // _XBOX + } + + for ( i = 0 ; i < srf->numVerts ; i++ ) { + tess.vertexDlightBits[ tess.numVertexes + i] = dlightBits; + } + + tess.numVertexes += srf->numVerts; +} + + + +/* +============== +RB_SurfaceBeam +============== +*/ +static void RB_SurfaceBeam( void ) +{ +#define NUM_BEAM_SEGS 6 + refEntity_t *e; + int i; + vec3_t perpvec; + vec3_t direction, normalized_direction; + vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS]; + vec3_t oldorigin, origin; + + e = &backEnd.currentEntity->e; + + oldorigin[0] = e->oldorigin[0]; + oldorigin[1] = e->oldorigin[1]; + oldorigin[2] = e->oldorigin[2]; + + origin[0] = e->origin[0]; + origin[1] = e->origin[1]; + origin[2] = e->origin[2]; + + normalized_direction[0] = direction[0] = oldorigin[0] - origin[0]; + normalized_direction[1] = direction[1] = oldorigin[1] - origin[1]; + normalized_direction[2] = direction[2] = oldorigin[2] - origin[2]; + + if ( VectorNormalize( normalized_direction ) == 0 ) + return; + + PerpendicularVector( perpvec, normalized_direction ); + + VectorScale( perpvec, 4, perpvec ); + + for ( i = 0; i < NUM_BEAM_SEGS ; i++ ) + { + RotatePointAroundVector( start_points[i], normalized_direction, perpvec, (360.0/NUM_BEAM_SEGS)*i ); +// VectorAdd( start_points[i], origin, start_points[i] ); + VectorAdd( start_points[i], direction, end_points[i] ); + } + + GL_Bind( tr.whiteImage ); + + GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); + + switch(e->skinNum) + { + case 1://Green + qglColor3f( 0, 1, 0 ); + break; + case 2://Blue + qglColor3f( 0.5, 0.5, 1 ); + break; + case 0://red + default: + qglColor3f( 1, 0, 0 ); + break; + } + + qglBegin( GL_TRIANGLE_STRIP ); + for ( i = 0; i <= NUM_BEAM_SEGS; i++ ) { + qglVertex3fv( start_points[ i % NUM_BEAM_SEGS] ); + qglVertex3fv( end_points[ i % NUM_BEAM_SEGS] ); + } + qglEnd(); +} + + +//------------------ +// DoSprite +//------------------ +static void DoSprite( vec3_t origin, float radius, float rotation ) +{ + float s, c; + float ang; + vec3_t left, up; + + ang = M_PI * rotation / 180.0f; + s = sin( ang ); + c = cos( ang ); + + VectorScale( backEnd.viewParms.or.axis[1], c * radius, left ); + VectorMA( left, -s * radius, backEnd.viewParms.or.axis[2], left ); + + VectorScale( backEnd.viewParms.or.axis[2], c * radius, up ); + VectorMA( up, s * radius, backEnd.viewParms.or.axis[1], up ); + + if ( backEnd.viewParms.isMirror ) + { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( origin, left, up, backEnd.currentEntity->e.shaderRGBA ); +} + +//------------------ +// RB_SurfaceSaber +//------------------ +static void RB_SurfaceSaberGlow() +{ + vec3_t end; + refEntity_t *e; + + e = &backEnd.currentEntity->e; + + // Render the glow part of the blade + for ( float i = e->saberLength; i > 0; i -= e->radius * 0.65f ) + { + VectorMA( e->origin, i, e->axis[0], end ); + + DoSprite( end, e->radius, 0.0f );//random() * 360.0f ); + e->radius += 0.017f; + } + + // Big hilt sprite + // Please don't kill me Pat...I liked the hilt glow blob, but wanted a subtle pulse.:) Feel free to ditch it if you don't like it. --Jeff + // Please don't kill me Jeff... The pulse is good, but now I want the halo bigger if the saber is shorter... --Pat + DoSprite( e->origin, 5.5f + random() * 0.25f, 0.0f );//random() * 360.0f ); +} + +/* +** LerpMeshVertexes +*/ +static void LerpMeshVertexes (md3Surface_t *surf, float backlerp) +{ + short *oldXyz, *newXyz, *oldNormals, *newNormals; + float *outXyz, *outNormal; + float oldXyzScale, newXyzScale; + float oldNormalScale, newNormalScale; + int vertNum; + unsigned lat, lng; + int numVerts; + + outXyz = tess.xyz[tess.numVertexes]; + outNormal = tess.normal[tess.numVertexes]; + + newXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.frame * surf->numVerts * 4); + newNormals = newXyz + 3; + + newXyzScale = MD3_XYZ_SCALE * (1.0 - backlerp); + newNormalScale = 1.0 - backlerp; + + numVerts = surf->numVerts; + + if ( backlerp == 0 ) { + // + // just copy the vertexes + // + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + newXyz += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + + outXyz[0] = newXyz[0] * newXyzScale; + outXyz[1] = newXyz[1] * newXyzScale; + outXyz[2] = newXyz[2] * newXyzScale; + + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= (FUNCTABLE_SIZE/256); + lng *= (FUNCTABLE_SIZE/256); + + // decode X as cos( lat ) * sin( long ) + // decode Y as sin( lat ) * sin( long ) + // decode Z as cos( long ) +#ifdef _XBOX + if( tess.shader->needsNormal || tess.dlightBits ) + { + outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + } +#else + outNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + outNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + outNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; +#endif + } + } else { + // + // interpolate and copy the vertex and normal + // + oldXyz = (short *)((byte *)surf + surf->ofsXyzNormals) + + (backEnd.currentEntity->e.oldframe * surf->numVerts * 4); + oldNormals = oldXyz + 3; + + oldXyzScale = MD3_XYZ_SCALE * backlerp; + oldNormalScale = backlerp; + + for (vertNum=0 ; vertNum < numVerts ; vertNum++, + oldXyz += 4, newXyz += 4, oldNormals += 4, newNormals += 4, + outXyz += 4, outNormal += 4) + { + vec3_t uncompressedOldNormal, uncompressedNewNormal; + + // interpolate the xyz + outXyz[0] = oldXyz[0] * oldXyzScale + newXyz[0] * newXyzScale; + outXyz[1] = oldXyz[1] * oldXyzScale + newXyz[1] * newXyzScale; + outXyz[2] = oldXyz[2] * oldXyzScale + newXyz[2] * newXyzScale; + +#ifdef _XBOX + if(tess.shader->needsNormal || tess.dlightBits ) + { +#endif + // FIXME: interpolate lat/long instead? + lat = ( newNormals[0] >> 8 ) & 0xff; + lng = ( newNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + uncompressedNewNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedNewNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedNewNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + lat = ( oldNormals[0] >> 8 ) & 0xff; + lng = ( oldNormals[0] & 0xff ); + lat *= 4; + lng *= 4; + + uncompressedOldNormal[0] = tr.sinTable[(lat+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK] * tr.sinTable[lng]; + uncompressedOldNormal[1] = tr.sinTable[lat] * tr.sinTable[lng]; + uncompressedOldNormal[2] = tr.sinTable[(lng+(FUNCTABLE_SIZE/4))&FUNCTABLE_MASK]; + + outNormal[0] = uncompressedOldNormal[0] * oldNormalScale + uncompressedNewNormal[0] * newNormalScale; + outNormal[1] = uncompressedOldNormal[1] * oldNormalScale + uncompressedNewNormal[1] * newNormalScale; + outNormal[2] = uncompressedOldNormal[2] * oldNormalScale + uncompressedNewNormal[2] * newNormalScale; + + VectorNormalize (outNormal); +#ifdef _XBOX + } +#endif + } + } +} + +/* +============= +RB_SurfaceMesh +============= +*/ +#ifdef _XBOX +void RE_GetModelBounds(refEntity_t *refEnt, vec3_t bounds1, vec3_t bounds2); +#endif +void RB_SurfaceMesh(md3Surface_t *surface) { + int j; + float backlerp; + int *triangles; + float *texCoords; + int indexes; + int Bob, Doug; + int numVerts; + +#ifdef _XBOX + UINT result; + HRESULT hr; + vec3_t bounds[2], v; + trRefEntity_t *ent = backEnd.currentEntity; + + if(ent->e.number == 0) + { + entityVisList[0] = 1; + ent->visible = 1; + } + + extern bool in_camera; + if(in_camera) + { + // Going into an in-game cinematic essentially breaks the vis testing + // So, just send 'em all through + entityVisList[ent->e.number] = 1; + ent->visible = 1; + } + + // If this is a portal surface, send it on through + if(strstr(tr.models[backEnd.currentEntity->e.hModel]->name, "portal")) + { + entityVisList[ent->e.number] = 1; + ent->visible = 1; + } + + // Get the visibility test from the last frame + if(ent->visible == -1) + { + if(entityVisList[ent->e.number] == -1) + entityVisList[ent->e.number] = 0; + else { + hr = glw_state->device->GetVisibilityTestResult( ent->e.number, &result, NULL ); + if( hr == D3D_OK) + entityVisList[ent->e.number] = ((int)result) ? 1 : 0; + else + entityVisList[ent->e.number] = 1; + } + + ent->visible = entityVisList[ent->e.number]; + + // Run a visibility test to determine if this model should be rendered + RE_GetModelBounds( &ent->e, bounds[0], bounds[1] ); + + RB_RunVisTest(ent->e.number, bounds); + } + + if(ent->visible == 0) + { + // It's possible for the camera to be inside a bounding volume and falsely + // report the object has failed it's vis test, test for that + vec3_t cameraOrigin; + VectorCopy(backEnd.ori.viewOrigin, cameraOrigin); + + RE_GetModelBounds( &ent->e, bounds[0], bounds[1] ); + + if(cameraOrigin[0] >= bounds[0][0] && + cameraOrigin[0] <= bounds[1][0] && + cameraOrigin[1] >= bounds[0][1] && + cameraOrigin[1] <= bounds[1][1]) + ent->visible = 1; + else + { + // Only do the camera/bounding check once per frame + ent->visible = -2; + return; + } + } + + if(ent->visible == -2) + return; +#endif + + if ( backEnd.currentEntity->e.oldframe == backEnd.currentEntity->e.frame ) { + backlerp = 0; + } else { + backlerp = backEnd.currentEntity->e.backlerp; + } + +#ifdef VV_LIGHTING + if(backEnd.currentEntity->dlightBits) + tess.dlightBits = backEnd.currentEntity->dlightBits; +#endif + + RB_CHECKOVERFLOW( surface->numVerts, surface->numTriangles*3 ); + + LerpMeshVertexes (surface, backlerp); + + triangles = (int *) ((byte *)surface + surface->ofsTriangles); + indexes = surface->numTriangles * 3; + Bob = tess.numIndexes; + Doug = tess.numVertexes; + for (j = 0 ; j < indexes ; j++) { + tess.indexes[Bob + j] = Doug + triangles[j]; + } + tess.numIndexes += indexes; + + texCoords = (float *) ((byte *)surface + surface->ofsSt); + + numVerts = surface->numVerts; + for ( j = 0; j < numVerts; j++ ) { + tess.texCoords[Doug + j][0][0] = texCoords[j*2+0]; + tess.texCoords[Doug + j][0][1] = texCoords[j*2+1]; + // FIXME: fill in lightmapST for completeness? + } + + tess.numVertexes += surface->numVerts; +} + +//#endif // _XBOX + + +/* +============== +RB_SurfaceFace +============== +*/ +void RB_SurfaceFace( srfSurfaceFace_t *surf ) { + int i, k; + float *normal; + int ndx; + int Bob; + int numPoints; + int dlightBits; + unsigned char *indices; + unsigned short *tessIndexes; + unsigned short *v; + + RB_CHECKOVERFLOW( surf->numPoints, surf->numIndices ); + + dlightBits = surf->dlightBits; + tess.dlightBits |= dlightBits; + + indices = ( unsigned char * ) ( ( ( char * ) surf ) + surf->ofsIndices ); + + Bob = tess.numVertexes; + tessIndexes = tess.indexes + tess.numIndexes; + for ( i = surf->numIndices-1 ; i >= 0 ; i-- ) { + tessIndexes[i] = indices[i] + Bob; + } + + tess.numIndexes += surf->numIndices; + + ndx = tess.numVertexes; + + numPoints = surf->numPoints; + + if ( tess.shader->needsNormal || tess.dlightBits) { + normal = surf->plane.normal; + for ( i = 0, ndx = tess.numVertexes; i < numPoints; i++, ndx++ ) { + VectorCopy( normal, tess.normal[ndx] ); + } + } + + int nextSurfPoint = NEXT_SURFPOINT(surf->flags); + int numLightMaps = surf->flags & 0x7F; + + for ( i = 0, v = surf->srfPoints, ndx = tess.numVertexes; i < numPoints; i++, v += nextSurfPoint, ndx++ ) { + + Q_CastShort2Float(&tess.xyz[ndx][0], (short*)&v[0]); + Q_CastShort2Float(&tess.xyz[ndx][1], (short*)&v[1]); + Q_CastShort2Float(&tess.xyz[ndx][2], (short*)&v[2]); + + if(tess.shader->needsTangent || tess.dlightBits) + { + Q_CastShort2FloatScale(&tess.tangent[ndx][0], (short*)&v[3], 1.f / 32767.0f); + Q_CastShort2FloatScale(&tess.tangent[ndx][1], (short*)&v[4], 1.f / 32767.0f); + Q_CastShort2FloatScale(&tess.tangent[ndx][2], (short*)&v[5], 1.f / 32767.0f); + + tess.setTangents = true; + } + + Q_CastShort2FloatScale(&tess.texCoords[ndx][0][0], (short*)&v[6], 1.f / POINTS_ST_SCALE); + Q_CastShort2FloatScale(&tess.texCoords[ndx][0][1], (short*)&v[7], 1.f / POINTS_ST_SCALE); + + for(k=0;kflags & 0x80) >> 7) { +#ifdef COMPRESS_VERTEX_COLORS + *(unsigned int*) &tess.vertexColors[ndx] = ComputeFinalVertexColor16((byte *)&v[VERTEX_COLOR(surf->flags)]); +#else + *(unsigned int*) &tess.vertexColors[ndx] = ComputeFinalVertexColor((byte *)&v[VERTEX_COLOR(surf->flags)]); +#endif + } + } + + tess.numVertexes += surf->numPoints; +} + + +static float LodErrorForVolume( vec3_t local, float radius ) { + vec3_t world; + float d; + + world[0] = local[0] * backEnd.ori.axis[0][0] + local[1] * backEnd.ori.axis[1][0] + + local[2] * backEnd.ori.axis[2][0] + backEnd.ori.origin[0]; + world[1] = local[0] * backEnd.ori.axis[0][1] + local[1] * backEnd.ori.axis[1][1] + + local[2] * backEnd.ori.axis[2][1] + backEnd.ori.origin[1]; + world[2] = local[0] * backEnd.ori.axis[0][2] + local[1] * backEnd.ori.axis[1][2] + + local[2] * backEnd.ori.axis[2][2] + backEnd.ori.origin[2]; + + VectorSubtract( world, backEnd.viewParms.or.origin, world ); + d = DotProduct( world, backEnd.viewParms.or.axis[0] ); + + if ( d < 0 ) { + d = -d; + } + d -= radius; + if ( d < 1 ) { + d = 1; + } + + return r_lodCurveError->value / d; +} + +/* +============= +RB_SurfaceGrid + +Just copy the grid of points and triangulate +============= +*/ +void RB_SurfaceGrid( srfGridMesh_t *cv ) { + int i, j, k; + float *xyz; + float *texCoords; + float *normal; + unsigned char *color; + drawVert_t *dv; + int rows, irows, vrows; + int used; + int widthTable[MAX_GRID_SIZE]; + int heightTable[MAX_GRID_SIZE]; + float lodError; + int lodWidth, lodHeight; + int numVertexes; + int dlightBits; + int *vDlightBits; + + dlightBits = cv->dlightBits; + tess.dlightBits |= dlightBits; + + // determine the allowable discrepance + lodError = LodErrorForVolume( cv->lodOrigin, cv->lodRadius ); + + // determine which rows and columns of the subdivision + // we are actually going to use + widthTable[0] = 0; + lodWidth = 1; + for ( i = 1 ; i < cv->width-1 ; i++ ) { + if ( cv->widthLodError[i] <= lodError ) { + widthTable[lodWidth] = i; + lodWidth++; + } + } + widthTable[lodWidth] = cv->width-1; + lodWidth++; + + heightTable[0] = 0; + lodHeight = 1; + for ( i = 1 ; i < cv->height-1 ; i++ ) { + if ( cv->heightLodError[i] <= lodError ) { + heightTable[lodHeight] = i; + lodHeight++; + } + } + heightTable[lodHeight] = cv->height-1; + lodHeight++; + + + // very large grids may have more points or indexes than can be fit + // in the tess structure, so we may have to issue it in multiple passes + + used = 0; + rows = 0; + while ( used < lodHeight - 1 ) { + // see how many rows of both verts and indexes we can add without overflowing + do { + vrows = ( SHADER_MAX_VERTEXES - tess.numVertexes ) / lodWidth; + irows = ( SHADER_MAX_INDEXES - tess.numIndexes ) / ( lodWidth * 6 ); + + // if we don't have enough space for at least one strip, flush the buffer + if ( vrows < 2 || irows < 1 ) { + RB_EndSurface(); + RB_BeginSurface(tess.shader, tess.fogNum ); + } else { + break; + } + } while ( 1 ); + + rows = irows; + if ( vrows < irows + 1 ) { + rows = vrows - 1; + } + if ( used + rows > lodHeight ) { + rows = lodHeight - used; + } + + numVertexes = tess.numVertexes; + + xyz = tess.xyz[numVertexes]; + normal = tess.normal[numVertexes]; + texCoords = tess.texCoords[numVertexes][0]; + color = ( unsigned char * ) &tess.vertexColors[numVertexes]; + vDlightBits = &tess.vertexDlightBits[numVertexes]; + +#ifdef _XBOX + for ( i = 0 ; i < rows ; i++ ) { + for ( j = 0 ; j < lodWidth ; j++ ) { + dv = cv->verts + heightTable[ used + i ] * cv->width + + widthTable[ j ]; + + xyz[0] = (float)dv->xyz[0]; + xyz[1] = (float)dv->xyz[1]; + xyz[2] = (float)dv->xyz[2]; + xyz += 4; + + Q_CastShort2FloatScale(&texCoords[0], &dv->dvst[0], 1.f / GRID_DRAWVERT_ST_SCALE); + Q_CastShort2FloatScale(&texCoords[1], &dv->dvst[1], 1.f / GRID_DRAWVERT_ST_SCALE); + + for(k=0;kdvlightmap[k][0], 1.f / DRAWVERT_LIGHTMAP_SCALE); + Q_CastShort2FloatScale(&texCoords[2+(k*2)+1], + &dv->dvlightmap[k][1], 1.f / DRAWVERT_LIGHTMAP_SCALE); + } + texCoords += NUM_TEX_COORDS*2; + + if ( tess.shader->needsNormal || tess.dlightBits) + { + normal[0] = (float)dv->normal[0] / 32767.f; + normal[1] = (float)dv->normal[1] / 32767.f; + normal[2] = (float)dv->normal[2] / 32767.f; + normal += 4; + } +#ifdef COMPRESS_VERTEX_COLORS + *(unsigned *)color = ComputeFinalVertexColor16((byte *)dv->dvcolor); +#else + *(unsigned *)color = ComputeFinalVertexColor((byte *)dv->dvcolor); +#endif + color += 4; + *vDlightBits++ = dlightBits; + } + } +#else + for ( i = 0 ; i < rows ; i++ ) { + for ( j = 0 ; j < lodWidth ; j++ ) { + dv = cv->verts + heightTable[ used + i ] * cv->width + + widthTable[ j ]; + + xyz[0] = dv->xyz[0]; + xyz[1] = dv->xyz[1]; + xyz[2] = dv->xyz[2]; + xyz += 4; + + texCoords[0] = dv->st[0]; + texCoords[1] = dv->st[1]; + + for(k=0;klightmap[k][0]; + texCoords[2+(k*2)+1]= dv->lightmap[k][1]; + } + texCoords += NUM_TEX_COORDS*2; + +// if ( needsNormal ) + { + normal[0] = dv->normal[0]; + normal[1] = dv->normal[1]; + normal[2] = dv->normal[2]; + } + normal += 4; + *(unsigned *)color = ComputeFinalVertexColor((byte *)dv->color); + color += 4; + *vDlightBits++ = dlightBits; + } + } +#endif + + // add the indexes + { + int numIndexes; + int w, h; + + h = rows - 1; + w = lodWidth - 1; + numIndexes = tess.numIndexes; + for (i = 0 ; i < h ; i++) { + for (j = 0 ; j < w ; j++) { + int v1, v2, v3, v4; + + // vertex order to be reckognized as tristrips + v1 = numVertexes + i*lodWidth + j + 1; + v2 = v1 - 1; + v3 = v2 + lodWidth; + v4 = v3 + 1; + + tess.indexes[numIndexes] = v2; + tess.indexes[numIndexes+1] = v3; + tess.indexes[numIndexes+2] = v1; + + tess.indexes[numIndexes+3] = v1; + tess.indexes[numIndexes+4] = v3; + tess.indexes[numIndexes+5] = v4; + numIndexes += 6; + } + } + + tess.numIndexes = numIndexes; + } + + tess.numVertexes += rows * lodWidth; + + used += rows - 1; + } +} + +static inline void Vector2Set(vec2_t a,float b,float c) +{ + a[0] = b; + a[1] = c; +} + +static inline void Vector2Copy(vec2_t src,vec2_t dst) +{ + dst[0] = src[0]; + dst[1] = src[1]; +} + +#define LATHE_SEG_STEP 10 +#define BEZIER_STEP 0.05f // must be in the range of 0 to 1 + +// FIXME: This function is horribly expensive +static void RB_SurfaceLathe() +{ + refEntity_t *e; + vec2_t pt, oldpt, l_oldpt; + vec2_t pt2, oldpt2, l_oldpt2; + float bezierStep, latheStep; + float temp, mu, mum1; + float mum13, mu3, group1, group2; + float s, c, d = 1.0f, pain = 0.0f; + int i, t, vbase; + + e = &backEnd.currentEntity->e; + + if ( e->endTime && e->endTime > backEnd.refdef.time ) + { + d = 1.0f - ( e->endTime - backEnd.refdef.time ) / 1000.0f; + } + + if ( e->frame && e->frame + 1000 > backEnd.refdef.time ) + { + pain = ( backEnd.refdef.time - e->frame ) / 1000.0f; +// pain *= pain; + pain = ( 1.0f - pain ) * 0.08f; + } + + Vector2Set( l_oldpt, e->axis[0][0], e->axis[0][1] ); + + // do scalability stuff...r_lodbias 0-3 + int lod = r_lodbias->integer + 1; + if ( lod > 4 ) + { + lod = 4; + } + bezierStep = BEZIER_STEP * lod; + latheStep = LATHE_SEG_STEP * lod; + + // Do bezier profile strip, then lathe this around to make a 3d model + for ( mu = 0.0f; mu <= 1.01f * d; mu += bezierStep ) + { + // Four point curve + mum1 = 1 - mu; + mum13 = mum1 * mum1 * mum1; + mu3 = mu * mu * mu; + group1 = 3 * mu * mum1 * mum1; + group2 = 3 * mu * mu *mum1; + + // Calc the current point on the curve + for ( i = 0; i < 2; i++ ) + { + l_oldpt2[i] = mum13 * e->axis[0][i] + group1 * e->axis[1][i] + group2 * e->axis[2][i] + mu3 * e->oldorigin[i]; + } + + Vector2Set( oldpt, l_oldpt[0], 0 ); + Vector2Set( oldpt2, l_oldpt2[0], 0 ); + + // lathe patch section around in a complete circle + for ( t = latheStep; t <= 360; t += latheStep ) + { + Vector2Set( pt, l_oldpt[0], 0 ); + Vector2Set( pt2, l_oldpt2[0], 0 ); + + s = sin( DEG2RAD( t )); + c = cos( DEG2RAD( t )); + + // rotate lathe points +//c -s 0 +//s c 0 +//0 0 1 + temp = c * pt[0] - s * pt[1]; + pt[1] = s * pt[0] + c * pt[1]; + pt[0] = temp; + temp = c * pt2[0] - s * pt2[1]; + pt2[1] = s * pt2[0] + c * pt2[1]; + pt2[0] = temp; + + RB_CHECKOVERFLOW( 4, 6 ); + + vbase = tess.numVertexes; + + // Actually generate the necessary verts + VectorSet( tess.normal[tess.numVertexes], oldpt[0], oldpt[1], l_oldpt[1] ); + VectorAdd( e->origin, tess.normal[tess.numVertexes], tess.xyz[tess.numVertexes] ); + VectorNormalize( tess.normal[tess.numVertexes] ); + i = oldpt[0] * 0.1f + oldpt[1] * 0.1f; + tess.texCoords[tess.numVertexes][0][0] = (t-latheStep)/360.0f; + tess.texCoords[tess.numVertexes][0][1] = mu-bezierStep + cos( i + backEnd.refdef.floatTime ) * pain; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + VectorSet( tess.normal[tess.numVertexes], oldpt2[0], oldpt2[1], l_oldpt2[1] ); + VectorAdd( e->origin, tess.normal[tess.numVertexes], tess.xyz[tess.numVertexes] ); + VectorNormalize( tess.normal[tess.numVertexes] ); + i = oldpt2[0] * 0.1f + oldpt2[1] * 0.1f; + tess.texCoords[tess.numVertexes][0][0] = (t-latheStep) / 360.0f; + tess.texCoords[tess.numVertexes][0][1] = mu + cos( i + backEnd.refdef.floatTime ) * pain; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + VectorSet( tess.normal[tess.numVertexes], pt[0], pt[1], l_oldpt[1] ); + VectorAdd( e->origin, tess.normal[tess.numVertexes], tess.xyz[tess.numVertexes] ); + VectorNormalize( tess.normal[tess.numVertexes] ); + i = pt[0] * 0.1f + pt[1] * 0.1f; + tess.texCoords[tess.numVertexes][0][0] = t/360.0f; + tess.texCoords[tess.numVertexes][0][1] = mu-bezierStep + cos( i + backEnd.refdef.floatTime ) * pain; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + VectorSet( tess.normal[tess.numVertexes], pt2[0], pt2[1], l_oldpt2[1] ); + VectorAdd( e->origin, tess.normal[tess.numVertexes], tess.xyz[tess.numVertexes] ); + VectorNormalize( tess.normal[tess.numVertexes] ); + i = pt2[0] * 0.1f + pt2[1] * 0.1f; + tess.texCoords[tess.numVertexes][0][0] = t/360.0f; + tess.texCoords[tess.numVertexes][0][1] = mu + cos( i + backEnd.refdef.floatTime ) * pain; + tess.vertexColors[tess.numVertexes][0] = e->shaderRGBA[0]; + tess.vertexColors[tess.numVertexes][1] = e->shaderRGBA[1]; + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[2]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; + + tess.indexes[tess.numIndexes++] = vbase + 3; + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase; + + // Shuffle new points to old + Vector2Copy( pt, oldpt ); + Vector2Copy( pt2, oldpt2 ); + } + + // shuffle lathe points + Vector2Copy( l_oldpt2, l_oldpt ); + } +} + +#define DISK_DEF 4 +#define TUBE_DEF 6 + +static void RB_SurfaceClouds() +{ + // Disk definition + float diskStripDef[DISK_DEF] = { + 0.0f, + 0.4f, + 0.7f, + 1.0f }; + + float diskAlphaDef[DISK_DEF] = { + 1.0f, + 1.0f, + 0.4f, + 0.0f }; + + float diskCurveDef[DISK_DEF] = { + 0.0f, + 0.0f, + 0.008f, + 0.02f }; + + // tube definition + float tubeStripDef[TUBE_DEF] = { + 0.0f, + 0.05f, + 0.1f, + 0.5f, + 0.7f, + 1.0f }; + + float tubeAlphaDef[TUBE_DEF] = { + 0.0f, + 0.45f, + 1.0f, + 1.0f, + 0.45f, + 0.0f }; + + float tubeCurveDef[TUBE_DEF] = { + 0.0f, + 0.004f, + 0.006f, + 0.01f, + 0.006f, + 0.0f }; + + refEntity_t *e; + vec3_t pt, oldpt; + vec3_t pt2, oldpt2; + float latheStep = 30.0f; + float s, c, temp; + float *stripDef, *alphaDef, *curveDef, ct; + int i, t, vbase; + + e = &backEnd.currentEntity->e; + + // select which type we shall be doing + if ( e->renderfx & RF_GROW ) // doing tube type + { + ct = TUBE_DEF; + stripDef = tubeStripDef; + alphaDef = tubeAlphaDef; + curveDef = tubeCurveDef; + e->backlerp *= -1; // needs to be reversed + } + else + { + ct = DISK_DEF; + stripDef = diskStripDef; + alphaDef = diskAlphaDef; + curveDef = diskCurveDef; + } + + // do the strip def, then lathe this around to make a 3d model + for ( i = 0; i < ct - 1; i++ ) + { + VectorSet( oldpt, (stripDef[i] * (e->radius - e->rotation)) + e->rotation, 0, curveDef[i] * e->radius * e->backlerp ); + VectorSet( oldpt2, (stripDef[i+1] * (e->radius - e->rotation)) + e->rotation, 0, curveDef[i+1] * e->radius * e->backlerp ); + + // lathe section around in a complete circle + for ( t = latheStep; t <= 360; t += latheStep ) + { + // rotate every time except last seg + if ( t < 360.0f ) + { + VectorCopy( oldpt, pt ); + VectorCopy( oldpt2, pt2 ); + + s = sin( DEG2RAD( latheStep )); + c = cos( DEG2RAD( latheStep )); + + // rotate lathe points + temp = c * pt[0] - s * pt[1]; // c -s 0 + pt[1] = s * pt[0] + c * pt[1]; // s c 0 + pt[0] = temp; // 0 0 1 + + temp = c * pt2[0] - s * pt2[1]; // c -s 0 + pt2[1] = s * pt2[0] + c * pt2[1];// s c 0 + pt2[0] = temp; // 0 0 1 + } + else + { + // just glue directly to the def points. + VectorSet( pt, (stripDef[i] * (e->radius - e->rotation)) + e->rotation, 0, curveDef[i] * e->radius * e->backlerp ); + VectorSet( pt2, (stripDef[i+1] * (e->radius - e->rotation)) + e->rotation, 0, curveDef[i+1] * e->radius * e->backlerp ); + } + + RB_CHECKOVERFLOW( 4, 6 ); + + vbase = tess.numVertexes; + + // Actually generate the necessary verts + VectorAdd( e->origin, oldpt, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = tess.xyz[tess.numVertexes][0] * 0.1f; + tess.texCoords[tess.numVertexes][0][1] = tess.xyz[tess.numVertexes][1] * 0.1f; + tess.vertexColors[tess.numVertexes][0] = + tess.vertexColors[tess.numVertexes][1] = + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[0] * alphaDef[i]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + VectorAdd( e->origin, oldpt2, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = tess.xyz[tess.numVertexes][0] * 0.1f; + tess.texCoords[tess.numVertexes][0][1] = tess.xyz[tess.numVertexes][1] * 0.1f; + tess.vertexColors[tess.numVertexes][0] = + tess.vertexColors[tess.numVertexes][1] = + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[0] * alphaDef[i+1]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + VectorAdd( e->origin, pt, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = tess.xyz[tess.numVertexes][0] * 0.1f; + tess.texCoords[tess.numVertexes][0][1] = tess.xyz[tess.numVertexes][1] * 0.1f; + tess.vertexColors[tess.numVertexes][0] = + tess.vertexColors[tess.numVertexes][1] = + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[0] * alphaDef[i]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + VectorAdd( e->origin, pt2, tess.xyz[tess.numVertexes] ); + tess.texCoords[tess.numVertexes][0][0] = tess.xyz[tess.numVertexes][0] * 0.1f; + tess.texCoords[tess.numVertexes][0][1] = tess.xyz[tess.numVertexes][1] * 0.1f; + tess.vertexColors[tess.numVertexes][0] = + tess.vertexColors[tess.numVertexes][1] = + tess.vertexColors[tess.numVertexes][2] = e->shaderRGBA[0] * alphaDef[i+1]; + tess.vertexColors[tess.numVertexes][3] = e->shaderRGBA[3]; + tess.numVertexes++; + + tess.indexes[tess.numIndexes++] = vbase; + tess.indexes[tess.numIndexes++] = vbase + 1; + tess.indexes[tess.numIndexes++] = vbase + 3; + + tess.indexes[tess.numIndexes++] = vbase + 3; + tess.indexes[tess.numIndexes++] = vbase + 2; + tess.indexes[tess.numIndexes++] = vbase; + + // Shuffle new points to old + Vector2Copy( pt, oldpt ); + Vector2Copy( pt2, oldpt2 ); + } + } +} + + +/* +=========================================================================== + +NULL MODEL + +=========================================================================== +*/ + +/* +=================== +RB_SurfaceAxis + +Draws x/y/z lines from the origin for orientation debugging +=================== +*/ +static void RB_SurfaceAxis( void ) { + GL_Bind( tr.whiteImage ); + qglLineWidth( 3 ); +#ifdef _XBOX + qglBeginEXT( GL_LINES, 6, 3, 0, 0, 0); +#else + qglBegin( GL_LINES ); +#endif + qglColor3f( 1,0,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 16,0,0 ); + qglColor3f( 0,1,0 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,16,0 ); + qglColor3f( 0,0,1 ); + qglVertex3f( 0,0,0 ); + qglVertex3f( 0,0,16 ); + qglEnd(); + qglLineWidth( 1 ); +} + +//=========================================================================== + +/* +==================== +RB_SurfaceEntity + +Entities that have a single procedurally generated surface +==================== +*/ +void RB_SurfaceEntity( surfaceType_t *surfType ) { + switch( backEnd.currentEntity->e.reType ) { + case RT_SPRITE: + RB_SurfaceSprite(); + break; + case RT_ORIENTED_QUAD: + RB_SurfaceOrientedQuad(); + break; + case RT_LINE: + RB_SurfaceLine(); + break; + case RT_ELECTRICITY: + RB_SurfaceElectricity(); + break; + case RT_BEAM: + RB_SurfaceBeam(); + break; + case RT_SABER_GLOW: + RB_SurfaceSaberGlow(); + break; + case RT_CYLINDER: + RB_SurfaceCylinder(); + break; + case RT_LATHE: + RB_SurfaceLathe(); + break; + case RT_CLOUDS: + RB_SurfaceClouds(); + break; + default: + RB_SurfaceAxis(); + break; + } + return; +} + +void RB_SurfaceBad( surfaceType_t *surfType ) { + VID_Printf( PRINT_ALL, "Bad surface tesselated.\n" ); +} + + +/* +================== +RB_TestZFlare + +This is called at surface tesselation time +================== +*/ +#ifdef _XBOX +static bool RB_TestZFlare( vec3_t point, srfFlare_t *surf ) { +#else +static bool RB_TestZFlare( vec3_t point) { +#endif + int i; + vec4_t eye, clip, normalized, window; + + // if the point is off the screen, don't bother adding it + // calculate screen coordinates and depth + R_TransformModelToClip( point, backEnd.ori.modelMatrix, + backEnd.viewParms.projectionMatrix, eye, clip ); + + // check to see if the point is completely off screen + for ( i = 0 ; i < 3 ; i++ ) { + if ( clip[i] >= clip[3] || clip[i] <= -clip[3] ) { + return qfalse; + } + } + + R_TransformClipToWindow( clip, &backEnd.viewParms, normalized, window ); + + if ( window[0] < 0 || window[0] >= backEnd.viewParms.viewportWidth + || window[1] < 0 || window[1] >= backEnd.viewParms.viewportHeight ) { + return qfalse; // shouldn't happen, since we check the clip[] above, except for FP rounding + } + +//do test + // read back the z buffer contents +#ifdef _XBOX + UINT result; + HRESULT hr; + DWORD zwrite, colorwrite; + + // Get the visibility test from the last frame + if(surf->visible == -1) + surf->visible = 0; + else { + hr = glw_state->device->GetVisibilityTestResult( (int)surf->number + 2024, &result, NULL ); + if( hr == D3D_OK) + surf->visible = (int)result; + } + + glw_state->device->GetRenderState(D3DRS_ZWRITEENABLE, &zwrite); + + glw_state->device->SetRenderState(D3DRS_ZWRITEENABLE, false); + glw_state->device->SetRenderState(D3DRS_COLORWRITEENABLE, 0); + + glw_state->device->SetTransform(D3DTS_VIEW, + glw_state->matrixStack[glwstate_t::MatrixMode_Model]->GetTop()); + glw_state->device->SetVertexShader(D3DFVF_XYZ); + + glw_state->device->BeginVisibilityTest(); + glw_state->device->Begin(D3DPT_POINTLIST); + glw_state->device->SetVertexData4f(D3DVSDE_VERTEX, point[0], point[1], point[2], 1.0f); + glw_state->device->End(); + glw_state->device->EndVisibilityTest((int)surf->number + 2024); + + glw_state->device->SetRenderState(D3DRS_ZWRITEENABLE, zwrite); + glw_state->device->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_ALL); + + return (surf->visible > 0); + +#else + float depth = 0.0f; + bool visible; + float screenZ; + + if ( r_flares->integer !=1 ) { //skipping the the z-test + return true; + } + // doing a readpixels is as good as doing a glFinish(), so + // don't bother with another sync + glState.finishCalled = qfalse; + qglReadPixels( backEnd.viewParms.viewportX + window[0],backEnd.viewParms.viewportY + window[1], 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); + + screenZ = backEnd.viewParms.projectionMatrix[14] / + ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + visible = ( -eye[2] - -screenZ ) < 24; + return visible; +#endif +} + +void RB_SurfaceFlare( srfFlare_t *surf ) { + vec3_t left, up; + float radius; + byte color[4]; + vec3_t dir; + vec3_t origin; + float d, dist; + + if ( !r_flares->integer ) { + return; + } + +#ifdef _XBOX + vec3_t sorigin, snormal; + + Q_CastShort2Float(&sorigin[0], (short*)&surf->origin[0]); + Q_CastShort2Float(&sorigin[1], (short*)&surf->origin[1]); + Q_CastShort2Float(&sorigin[2], (short*)&surf->origin[2]); + + /*if (!RB_TestZFlare( sorigin) ) { + return; + }*/ + + Q_CastShort2Float(&snormal[0], (short*)&surf->normal[0]); + Q_CastShort2Float(&snormal[1], (short*)&surf->normal[1]); + Q_CastShort2Float(&snormal[2], (short*)&surf->normal[2]); + snormal[0] /= 32767.0f; + snormal[1] /= 32767.0f; + snormal[2] /= 32767.0f; + + // Pull the vertex toward the viewer so we don't get any z-fighting on the vis test + VectorSubtract( backEnd.viewParms.or.origin, sorigin, dir ); + dist = VectorNormalize( dir ); + VectorMA( sorigin, 20, dir, origin ); + + if (!RB_TestZFlare( origin, surf ) ) { + return; + } + + // calculate the xyz locations for the four corners + VectorMA( sorigin, 3, snormal, origin ); +#else + if (!RB_TestZFlare( surf->origin ) ) { + return; + } + + // calculate the xyz locations for the four corners + VectorMA( surf->origin, 3, surf->normal, origin ); + float* snormal = surf->normal; +#endif // _XBOX + + VectorSubtract( origin, backEnd.viewParms.or.origin, dir ); + dist = VectorNormalize( dir ); + + d = -DotProduct( dir, snormal ); + if ( d < 0 ) { + d = -d; + } + + // fade the intensity of the flare down as the + // light surface turns away from the viewer + color[0] = d * 255; + color[1] = d * 255; + color[2] = d * 255; + color[3] = 255; //only gets used if the shader has cgen exact_vertex! + + radius = tess.shader->portalRange ? tess.shader->portalRange: 30; + if (dist < 512.0f) + { + radius = radius * dist / 512.0f; + } + if (radius<5.0f) + { + radius = 5.0f; + } + VectorScale( backEnd.viewParms.or.axis[1], radius, left ); + VectorScale( backEnd.viewParms.or.axis[2], radius, up ); + if ( backEnd.viewParms.isMirror ) { + VectorSubtract( vec3_origin, left, left ); + } + + RB_AddQuadStamp( origin, left, up, color ); +} + + +void RB_SurfaceDisplayList( srfDisplayList_t *surf ) { + // all apropriate state must be set in RB_BeginSurface + // this isn't implemented yet... + qglCallList( surf->listNum ); +} + +void RB_SurfaceSkip( void *surf ) { +} + +void RB_SurfaceTerrain( surfaceInfo_t *surf ); + +void (*rb_surfaceTable[SF_NUM_SURFACE_TYPES])( void *) = { + (void(*)(void*))RB_SurfaceBad, // SF_BAD, + (void(*)(void*))RB_SurfaceSkip, // SF_SKIP, + (void(*)(void*))RB_SurfaceFace, // SF_FACE, + (void(*)(void*))RB_SurfaceGrid, // SF_GRID, + (void(*)(void*))RB_SurfaceTriangles, // SF_TRIANGLES, + (void(*)(void*))RB_SurfacePolychain, // SF_POLY, +#ifdef _XBOX + NULL, +#else + (void(*)(void*))RB_SurfaceTerrain, // SF_TERRAIN, +#endif + (void(*)(void*))RB_SurfaceMesh, // SF_MD3, +/* +Ghoul2 Insert Start +*/ + + (void(*)(void*))RB_SurfaceGhoul, // SF_MDX, +/* +Ghoul2 Insert End +*/ + (void(*)(void*))RB_SurfaceFlare, // SF_FLARE, + (void(*)(void*))RB_SurfaceEntity, // SF_ENTITY + (void(*)(void*))RB_SurfaceDisplayList // SF_DISPLAY_LIST +}; diff --git a/code/renderer/tr_surfacesprites.cpp b/code/renderer/tr_surfacesprites.cpp new file mode 100644 index 0000000..e27ab26 --- /dev/null +++ b/code/renderer/tr_surfacesprites.cpp @@ -0,0 +1,1492 @@ +// tr_surfacesprites.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_QuickSprite.h" +#include "tr_worldeffects.h" +#include //for isnan + +/////===== Part of the VERTIGON system =====///// +// The surfacesprites are a simple system. When a polygon with this shader stage on it is drawn, +// there are randomly distributed images (defined by the shader stage) placed on the surface. +// these are capable of doing effects, grass, or simple oriented sprites. +// They usually stick vertically off the surface, hence the term vertigons. + +// The vertigons are applied as part of the renderer backend. That is, they access OpenGL calls directly. + + +unsigned char randomindex, randominterval; +const float randomchart[256] = { + 0.6554f, 0.6909f, 0.4806f, 0.6218f, 0.5717f, 0.3896f, 0.0677f, 0.7356f, + 0.8333f, 0.1105f, 0.4445f, 0.8161f, 0.4689f, 0.0433f, 0.7152f, 0.0336f, + 0.0186f, 0.9140f, 0.1626f, 0.6553f, 0.8340f, 0.7094f, 0.2020f, 0.8087f, + 0.9119f, 0.8009f, 0.1339f, 0.8492f, 0.9173f, 0.5003f, 0.6012f, 0.6117f, + 0.5525f, 0.5787f, 0.1586f, 0.3293f, 0.9273f, 0.7791f, 0.8589f, 0.4985f, + 0.0883f, 0.8545f, 0.2634f, 0.4727f, 0.3624f, 0.1631f, 0.7825f, 0.0662f, + 0.6704f, 0.3510f, 0.7525f, 0.9486f, 0.4685f, 0.1535f, 0.1545f, 0.1121f, + 0.4724f, 0.8483f, 0.3833f, 0.1917f, 0.8207f, 0.3885f, 0.9702f, 0.9200f, + 0.8348f, 0.7501f, 0.6675f, 0.4994f, 0.0301f, 0.5225f, 0.8011f, 0.1696f, + 0.5351f, 0.2752f, 0.2962f, 0.7550f, 0.5762f, 0.7303f, 0.2835f, 0.4717f, + 0.1818f, 0.2739f, 0.6914f, 0.7748f, 0.7640f, 0.8355f, 0.7314f, 0.5288f, + 0.7340f, 0.6692f, 0.6813f, 0.2810f, 0.8057f, 0.0648f, 0.8749f, 0.9199f, + 0.1462f, 0.5237f, 0.3014f, 0.4994f, 0.0278f, 0.4268f, 0.7238f, 0.5107f, + 0.1378f, 0.7303f, 0.7200f, 0.3819f, 0.2034f, 0.7157f, 0.5552f, 0.4887f, + 0.0871f, 0.3293f, 0.2892f, 0.4545f, 0.0088f, 0.1404f, 0.0275f, 0.0238f, + 0.0515f, 0.4494f, 0.7206f, 0.2893f, 0.6060f, 0.5785f, 0.4182f, 0.5528f, + 0.9118f, 0.8742f, 0.3859f, 0.6030f, 0.3495f, 0.4550f, 0.9875f, 0.6900f, + 0.6416f, 0.2337f, 0.7431f, 0.9788f, 0.6181f, 0.2464f, 0.4661f, 0.7621f, + 0.7020f, 0.8203f, 0.8869f, 0.2145f, 0.7724f, 0.6093f, 0.6692f, 0.9686f, + 0.5609f, 0.0310f, 0.2248f, 0.2950f, 0.2365f, 0.1347f, 0.2342f, 0.1668f, + 0.3378f, 0.4330f, 0.2775f, 0.9901f, 0.7053f, 0.7266f, 0.4840f, 0.2820f, + 0.5733f, 0.4555f, 0.6049f, 0.0770f, 0.4760f, 0.6060f, 0.4159f, 0.3427f, + 0.1234f, 0.7062f, 0.8569f, 0.1878f, 0.9057f, 0.9399f, 0.8139f, 0.1407f, + 0.1794f, 0.9123f, 0.9493f, 0.2827f, 0.9934f, 0.0952f, 0.4879f, 0.5160f, + 0.4118f, 0.4873f, 0.3642f, 0.7470f, 0.0866f, 0.5172f, 0.6365f, 0.2676f, + 0.2407f, 0.7223f, 0.5761f, 0.1143f, 0.7137f, 0.2342f, 0.3353f, 0.6880f, + 0.2296f, 0.6023f, 0.6027f, 0.4138f, 0.5408f, 0.9859f, 0.1503f, 0.7238f, + 0.6054f, 0.2477f, 0.6804f, 0.1432f, 0.4540f, 0.9776f, 0.8762f, 0.7607f, + 0.9025f, 0.9807f, 0.0652f, 0.8661f, 0.7663f, 0.2586f, 0.3994f, 0.0335f, + 0.7328f, 0.0166f, 0.9589f, 0.4348f, 0.5493f, 0.7269f, 0.6867f, 0.6614f, + 0.6800f, 0.7804f, 0.5591f, 0.8381f, 0.0910f, 0.7573f, 0.8985f, 0.3083f, + 0.3188f, 0.8481f, 0.2356f, 0.6736f, 0.4770f, 0.4560f, 0.6266f, 0.4677f +}; + +#define WIND_DAMP_INTERVAL 50 +#define WIND_GUST_TIME 2500.0 +#define WIND_GUST_DECAY (1.0 / WIND_GUST_TIME) + +int lastSSUpdateTime = 0; +float curWindSpeed=0; +float curWindGust=5; +float curWeatherAmount=1; +vec3_t curWindBlowVect={0,0,0}, targetWindBlowVect={0,0,0}; +vec3_t curWindGrassDir={0,0,0}, targetWindGrassDir={0,0,0}; +int totalsurfsprites=0, sssurfaces=0; + +qboolean curWindPointActive=qfalse; +float curWindPointForce = 0; +vec3_t curWindPoint; +int nextGustTime=0; +float gustLeft=0; + +qboolean standardfovinitialized=qfalse; +float standardfovx = 90, standardscalex = 1.0; +float rangescalefactor=1.0; + +vec3_t ssrightvectors[4]; +vec3_t ssfwdvector; +int rightvectorcount; + +trRefEntity_t *ssLastEntityDrawn=NULL; +vec3_t ssViewOrigin, ssViewRight, ssViewUp; + + +static void R_SurfaceSpriteFrameUpdate(void) +{ + float dtime, dampfactor; // Time since last update and damping time for wind changes + float ratio; + vec3_t ang, diff, retwindvec; + float targetspeed; + vec3_t up={0,0,1}; + + if (backEnd.refdef.time == lastSSUpdateTime) + return; + + if (backEnd.refdef.time < lastSSUpdateTime) + { // Time is BEFORE the last update time, so reset everything. + curWindGust = 5; + curWindSpeed = r_windSpeed->value; + nextGustTime = 0; + gustLeft = 0; + curWindGrassDir[0]=curWindGrassDir[1]=curWindGrassDir[2]=0.0f; + } + + // Reset the last entity drawn, since this is a new frame. + ssLastEntityDrawn = NULL; + + // Adjust for an FOV. If things look twice as wide on the screen, pretend the shaders have twice the range. + // ASSUMPTION HERE IS THAT "standard" fov is the first one rendered. + + if (!standardfovinitialized) + { // This isn't initialized yet. + if (backEnd.refdef.fov_x > 50 && backEnd.refdef.fov_x < 135) // I don't consider anything below 50 or above 135 to be "normal". + { + standardfovx = backEnd.refdef.fov_x; + standardscalex = tan(standardfovx * 0.5 * (M_PI/180.0f)); + standardfovinitialized = qtrue; + } + else + { + standardfovx = 90; + standardscalex = tan(standardfovx * 0.5 * (M_PI/180.0f)); + } + rangescalefactor = 1.0; // Don't multiply the shader range by anything. + } + else if (standardfovx == backEnd.refdef.fov_x) + { // This is the standard FOV (or higher), don't multiply the shader range. + rangescalefactor = 1.0; + } + else + { // We are using a non-standard FOV. We need to multiply the range of the shader by a scale factor. + if (backEnd.refdef.fov_x > 135) + { + rangescalefactor = standardscalex / tan(135.0f * 0.5f * (M_PI/180.0f)); + } + else + { + rangescalefactor = standardscalex / tan(backEnd.refdef.fov_x * 0.5 * (M_PI/180.0f)); + } + } + + // Create a set of four right vectors so that vertical sprites aren't always facing the same way. + // First generate a HORIZONTAL forward vector (important). + CrossProduct(ssViewRight, up, ssfwdvector); + + // Right Zero has a nudge forward (10 degrees). + VectorScale(ssViewRight, 0.985f, ssrightvectors[0]); + VectorMA(ssrightvectors[0], 0.174f, ssfwdvector, ssrightvectors[0]); + + // Right One has a big nudge back (30 degrees). + VectorScale(ssViewRight, 0.866f, ssrightvectors[1]); + VectorMA(ssrightvectors[1], -0.5f, ssfwdvector, ssrightvectors[1]); + + + // Right two has a big nudge forward (30 degrees). + VectorScale(ssViewRight, 0.866f, ssrightvectors[2]); + VectorMA(ssrightvectors[2], 0.5f, ssfwdvector, ssrightvectors[2]); + + + // Right three has a nudge back (10 degrees). + VectorScale(ssViewRight, 0.985f, ssrightvectors[3]); + VectorMA(ssrightvectors[3], -0.174f, ssfwdvector, ssrightvectors[3]); + + + // Update the wind. + // If it is raining, get the windspeed from the rain system rather than the cvar + if (R_IsRaining() /*|| R_IsSnowing()*/ || R_IsPuffing() ) + { + curWeatherAmount = 1.0; + } + else + { + curWeatherAmount = r_surfaceWeather->value; + } + + if (R_GetWindSpeed(targetspeed, NULL)) + { // We successfully got a speed from the rain system. + // Set the windgust to 5, since that looks pretty good. + targetspeed *= 0.02f; + if (targetspeed >= 1.0) + { + curWindGust = 300/targetspeed; + } + else + { + curWindGust = 0; + } + } + else + { // Use the cvar. + targetspeed = r_windSpeed->value; // Minimum gust delay, in seconds. + curWindGust = r_windGust->value; + } + + if (targetspeed > 0 && curWindGust) + { + if (gustLeft > 0) + { // We are gusting + // Add an amount to the target wind speed + targetspeed *= 1.0 + gustLeft; + + gustLeft -= (float)(backEnd.refdef.time - lastSSUpdateTime)*WIND_GUST_DECAY; + if (gustLeft <= 0) + { + nextGustTime = backEnd.refdef.time + (curWindGust*1000)*Q_flrand(1.0f,4.0f); + } + } + else if (backEnd.refdef.time >= nextGustTime) + { // See if there is another right now + // Gust next time, mano + gustLeft = Q_flrand(0.75f,1.5f); + } + } + + // See if there is a weather system that will tell us a windspeed. + if (R_GetWindVector(retwindvec, NULL)) + { + retwindvec[2]=0; + //VectorScale(retwindvec, -1.0f, retwindvec); + vectoangles(retwindvec, ang); + } + else + { // Calculate the target wind vector based off cvars + ang[YAW] = r_windAngle->value; + } + + ang[PITCH] = -90.0 + targetspeed; + if (ang[PITCH]>-45.0) + { + ang[PITCH] = -45.0; + } + ang[ROLL] = 0; + + if (targetspeed>0) + { +// ang[YAW] += cos(tr.refdef.time*0.01+flrand(-1.0,1.0))*targetspeed*0.5; +// ang[PITCH] += sin(tr.refdef.time*0.01+flrand(-1.0,1.0))*targetspeed*0.5; + } + + // Get the grass wind vector first + AngleVectors(ang, targetWindGrassDir, NULL, NULL); + targetWindGrassDir[2]-=1.0; +// VectorScale(targetWindGrassDir, targetspeed, targetWindGrassDir); + + // Now get the general wind vector (no pitch) + ang[PITCH]=0; + AngleVectors(ang, targetWindBlowVect, NULL, NULL); + + // Start calculating a smoothing factor so wind doesn't change abruptly between speeds. + dampfactor = 1.0-r_windDampFactor->value; // We must exponent the amount LEFT rather than the amount bled off + dtime = (float)(backEnd.refdef.time - lastSSUpdateTime) * (1.0/(float)WIND_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1". + + // Note that since there are a finite number of "practical" delta millisecond values possible, + // the ratio should be initialized into a chart ultimately. + ratio = pow(dampfactor, dtime); + + // Apply this ratio to the windspeed... + if (fabsf(targetspeed-curWindSpeed) > ratio) + { + curWindSpeed = targetspeed - (ratio * (targetspeed-curWindSpeed)); + } + + + // Use the curWindSpeed to calculate the final target wind vector (with speed) + VectorScale(targetWindBlowVect, curWindSpeed, targetWindBlowVect); + VectorSubtract(targetWindBlowVect, curWindBlowVect, diff); + VectorMA(targetWindBlowVect, -ratio, diff, curWindBlowVect); + + // Update the grass vector now + VectorSubtract(targetWindGrassDir, curWindGrassDir, diff); + VectorMA(targetWindGrassDir, -ratio, diff, curWindGrassDir); + + lastSSUpdateTime = backEnd.refdef.time; + + if (fabsf(r_windPointForce->value - curWindPointForce) > ratio) + {// Make sure not to get infinitly small number here + curWindPointForce = r_windPointForce->value - (ratio * (r_windPointForce->value - curWindPointForce)); + } + assert(!_isnan(curWindPointForce)); + if (curWindPointForce < 0.01) + { + curWindPointActive = qfalse; + } + else + { + curWindPointActive = qtrue; + curWindPoint[0] = r_windPointX->value; + curWindPoint[1] = r_windPointY->value; + curWindPoint[2] = 0; + } + + if (r_surfaceSprites->integer >= 2) + { + Com_Printf("Surfacesprites Drawn: %d, on %d surfaces\n", totalsurfsprites, sssurfaces); + } + + totalsurfsprites=0; + sssurfaces=0; +} + + + +///////////////////////////////////////////// +// Surface sprite calculation and drawing. +///////////////////////////////////////////// + +#define FADE_RANGE 250.0 +#define WINDPOINT_RADIUS 750.0 + +float SSVertAlpha[SHADER_MAX_VERTEXES]; +float SSVertWindForce[SHADER_MAX_VERTEXES]; +vec2_t SSVertWindDir[SHADER_MAX_VERTEXES]; + +qboolean SSAdditiveTransparency=qfalse; +qboolean SSUsingFog=qfalse; + + +///////////////////////////////////////////// +// Vertical surface sprites + +static void RB_VerticalSurfaceSprite(vec3_t loc, float width, float height, byte light, + byte alpha, float wind, float windidle, vec2_t fog, int hangdown, vec2_t skew, bool flattened) +{ + vec3_t loc2, right; + float angle; + float windsway; + float points[16]; + color4ub_t color; + + angle = ((loc[0]+loc[1])*0.02+(tr.refdef.time*0.0015)); + + if (windidle>0.0) + { + windsway = (height*windidle*0.075); + loc2[0] = loc[0]+skew[0]+cos(angle)*windsway; + loc2[1] = loc[1]+skew[1]+sin(angle)*windsway; + + if (hangdown) + { + loc2[2] = loc[2]-height; + } + else + { + loc2[2] = loc[2]+height; + } + } + else + { + loc2[0] = loc[0]+skew[0]; + loc2[1] = loc[1]+skew[1]; + if (hangdown) + { + loc2[2] = loc[2]-height; + } + else + { + loc2[2] = loc[2]+height; + } + } + + if (wind>0.0 && curWindSpeed > 0.001) + { + windsway = (height*wind*0.075); + + // Add the angle + VectorMA(loc2, height*wind, curWindGrassDir, loc2); + // Bob up and down + if (curWindSpeed < 40.0) + { + windsway *= curWindSpeed*(1.0/100.0); + } + else + { + windsway *= 0.4f; + } + loc2[2] += sin(angle*2.5)*windsway; + } + + if ( flattened ) + { + right[0] = sin( DEG2RAD( loc[0] ) ) * width; + right[1] = cos( DEG2RAD( loc[0] ) ) * height; + right[2] = 0.0f; + } + else + { + VectorScale(ssrightvectors[rightvectorcount], width*0.5, right); + } + + color[0]=light; + color[1]=light; + color[2]=light; + color[3]=alpha; + + // Bottom right +// VectorAdd(loc, right, point); + points[0] = loc[0] + right[0]; + points[1] = loc[1] + right[1]; + points[2] = loc[2] + right[2]; + points[3] = 0; + + // Top right +// VectorAdd(loc2, right, point); + points[4] = loc2[0] + right[0]; + points[5] = loc2[1] + right[1]; + points[6] = loc2[2] + right[2]; + points[7] = 0; + + // Top left +// VectorSubtract(loc2, right, point); + points[8] = loc2[0] - right[0] + ssfwdvector[0] * width * 0.2; + points[9] = loc2[1] - right[1] + ssfwdvector[1] * width * 0.2; + points[10] = loc2[2] - right[2]; + points[11] = 0; + + // Bottom left +// VectorSubtract(loc, right, point); + points[12] = loc[0] - right[0]; + points[13] = loc[1] - right[1]; + points[14] = loc[2] - right[2]; + points[15] = 0; + + // Add the sprite to the render list. + SQuickSprite.Add(points, color, fog); +} + +static void RB_VerticalSurfaceSpriteWindPoint(vec3_t loc, float width, float height, byte light, + byte alpha, float wind, float windidle, vec2_t fog, + int hangdown, vec2_t skew, vec2_t winddiff, float windforce, bool flattened) +{ + vec3_t loc2, right; + float angle; + float windsway; + float points[16]; + color4ub_t color; + + if (windforce > 1) + windforce = 1; + +// wind += 1.0-windforce; + + angle = (loc[0]+loc[1])*0.02+(tr.refdef.time*0.0015); + + if (curWindSpeed <80.0) + { + windsway = (height*windidle*0.1)*(1.0+windforce); + loc2[0] = loc[0]+skew[0]+cos(angle)*windsway; + loc2[1] = loc[1]+skew[1]+sin(angle)*windsway; + } + else + { + loc2[0] = loc[0]+skew[0]; + loc2[1] = loc[1]+skew[1]; + } + if (hangdown) + { + loc2[2] = loc[2]-height; + } + else + { + loc2[2] = loc[2]+height; + } + + if (curWindSpeed > 0.001) + { + // Add the angle + VectorMA(loc2, height*wind, curWindGrassDir, loc2); + } + + loc2[0] += height*winddiff[0]*windforce; + loc2[1] += height*winddiff[1]*windforce; + loc2[2] -= height*windforce*(0.75 + 0.15*sin((tr.refdef.time + 500*windforce)*0.01)); + + if ( flattened ) + { + right[0] = sin( DEG2RAD( loc[0] ) ) * width; + right[1] = cos( DEG2RAD( loc[0] ) ) * height; + right[2] = 0.0f; + } + else + { + VectorScale(ssrightvectors[rightvectorcount], width*0.5, right); + } + + + color[0]=light; + color[1]=light; + color[2]=light; + color[3]=alpha; + + // Bottom right +// VectorAdd(loc, right, point); + points[0] = loc[0] + right[0]; + points[1] = loc[1] + right[1]; + points[2] = loc[2] + right[2]; + points[3] = 0; + + // Top right +// VectorAdd(loc2, right, point); + points[4] = loc2[0] + right[0]; + points[5] = loc2[1] + right[1]; + points[6] = loc2[2] + right[2]; + points[7] = 0; + + // Top left +// VectorSubtract(loc2, right, point); + points[8] = loc2[0] - right[0] + ssfwdvector[0] * width * 0.15; + points[9] = loc2[1] - right[1] + ssfwdvector[1] * width * 0.15; + points[10] = loc2[2] - right[2]; + points[11] = 0; + + // Bottom left +// VectorSubtract(loc, right, point); + points[12] = loc[0] - right[0]; + points[13] = loc[1] - right[1]; + points[14] = loc[2] - right[2]; + points[15] = 0; + + // Add the sprite to the render list. + SQuickSprite.Add(points, color, fog); +} + +static void RB_DrawVerticalSurfaceSprites( shaderStage_t *stage, shaderCommands_t *input) +{ + int curindex, curvert; + vec3_t dist; + float triarea; + vec2_t vec1to2, vec1to3; + + vec3_t v1,v2,v3; + float a1,a2,a3; + float l1,l2,l3; + vec2_t fog1, fog2, fog3; + vec2_t winddiff1, winddiff2, winddiff3; + float windforce1, windforce2, windforce3; + + float posi, posj; + float step; + float fa,fb,fc; + + vec3_t curpoint; + float width, height; + float alpha, alphapos, thisspritesfadestart, light; + + byte randomindex2; + + vec2_t skew={0,0}; + vec2_t fogv; + vec2_t winddiffv; + float windforce=0; + qboolean usewindpoint = (qboolean) !! (curWindPointActive && stage->ss->wind > 0); + + float cutdist=stage->ss->fadeMax*rangescalefactor, cutdist2=cutdist*cutdist; + float fadedist=stage->ss->fadeDist*rangescalefactor, fadedist2=fadedist*fadedist; + + assert(cutdist2 != fadedist2); + float inv_fadediff = 1.0/(cutdist2-fadedist2); + + // The faderange is the fraction amount it takes for these sprites to fade out, assuming an ideal fade range of 250 + float faderange = FADE_RANGE/(cutdist-fadedist); + + if (faderange > 1.0) + { // Don't want to force a new fade_rand + faderange = 1.0; + } + + // Quickly calc all the alphas and windstuff for each vertex + for (curvert=0; curvertnumVertexes; curvert++) + { + VectorSubtract(ssViewOrigin, input->xyz[curvert], dist); + SSVertAlpha[curvert] = 1.0 - (VectorLengthSquared(dist) - fadedist2) * inv_fadediff; + } + + // Wind only needs initialization once per tess. + if (usewindpoint && !tess.SSInitializedWind) + { + for (curvert=0; curvertnumVertexes;curvert++) + { // Calc wind at each point + dist[0]=input->xyz[curvert][0] - curWindPoint[0]; + dist[1]=input->xyz[curvert][1] - curWindPoint[1]; + step = (dist[0]*dist[0] + dist[1]*dist[1]); // dist squared + + if (step >= (float)(WINDPOINT_RADIUS*WINDPOINT_RADIUS)) + { // No wind + SSVertWindDir[curvert][0] = 0; + SSVertWindDir[curvert][1] = 0; + SSVertWindForce[curvert]=0; // Should be < 1 + } + else + { + if (step<1) + { // Don't want to divide by zero + SSVertWindDir[curvert][0] = 0; + SSVertWindDir[curvert][1] = 0; + SSVertWindForce[curvert] = curWindPointForce * stage->ss->wind; + } + else + { + step = Q_rsqrt(step); // Equals 1 over the distance. + SSVertWindDir[curvert][0] = dist[0] * step; + SSVertWindDir[curvert][1] = dist[1] * step; + step = 1.0 - (1.0 / (step * WINDPOINT_RADIUS)); // 1- (dist/maxradius) = a scale from 0 to 1 linearly dropping off + SSVertWindForce[curvert] = curWindPointForce * stage->ss->wind * step; // *step means divide by the distance. + } + } + } + tess.SSInitializedWind = qtrue; + } + + for (curindex=0; curindexnumIndexes-2; curindex+=3) + { + curvert = input->indexes[curindex]; + VectorCopy(input->xyz[curvert], v1); + if (stage->ss->facing) + { // Hang down + if (input->normal[curvert][2] > -0.5) + { + continue; + } + } + else + { // Point up + if (input->normal[curvert][2] < 0.5) + { + continue; + } + } + l1 = input->vertexColors[curvert][2]; + a1 = SSVertAlpha[curvert]; + fog1[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog1[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + winddiff1[0] = SSVertWindDir[curvert][0]; + winddiff1[1] = SSVertWindDir[curvert][1]; + windforce1 = SSVertWindForce[curvert]; + + curvert = input->indexes[curindex+1]; + VectorCopy(input->xyz[curvert], v2); + if (stage->ss->facing) + { // Hang down + if (input->normal[curvert][2] > -0.5) + { + continue; + } + } + else + { // Point up + if (input->normal[curvert][2] < 0.5) + { + continue; + } + } + l2 = input->vertexColors[curvert][2]; + a2 = SSVertAlpha[curvert]; + fog2[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog2[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + winddiff2[0] = SSVertWindDir[curvert][0]; + winddiff2[1] = SSVertWindDir[curvert][1]; + windforce2 = SSVertWindForce[curvert]; + + curvert = input->indexes[curindex+2]; + VectorCopy(input->xyz[curvert], v3); + if (stage->ss->facing) + { // Hang down + if (input->normal[curvert][2] > -0.5) + { + continue; + } + } + else + { // Point up + if (input->normal[curvert][2] < 0.5) + { + continue; + } + } + l3 = input->vertexColors[curvert][2]; + a3 = SSVertAlpha[curvert]; + fog3[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog3[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + winddiff3[0] = SSVertWindDir[curvert][0]; + winddiff3[1] = SSVertWindDir[curvert][1]; + windforce3 = SSVertWindForce[curvert]; + + if (a1 <= 0.0 && a2 <= 0.0 && a3 <= 0.0) + { + continue; + } + + // Find the area in order to calculate the stepsize + vec1to2[0] = v2[0] - v1[0]; + vec1to2[1] = v2[1] - v1[1]; + vec1to3[0] = v3[0] - v1[0]; + vec1to3[1] = v3[1] - v1[1]; + + // Now get the cross product of this sum. + triarea = vec1to3[0]*vec1to2[1] - vec1to3[1]*vec1to2[0]; + triarea=fabs(triarea); + if (triarea <= 1.0) + { // Insanely small abhorrent triangle. + continue; + } + step = stage->ss->density * Q_rsqrt(triarea); + + randomindex = (byte)(v1[0]+v1[1]+v2[0]+v2[1]+v3[0]+v3[1]); + randominterval = (byte)(v1[0]+v2[1]+v3[2])|0x03; // Make sure the interval is at least 3, and always odd + rightvectorcount = 0; + + for (posi=0; posi<1.0; posi+=step) + { + for (posj=0; posj<(1.0-posi); posj+=step) + { + fa=posi+randomchart[randomindex]*step; + randomindex += randominterval; + + fb=posj+randomchart[randomindex]*step; + randomindex += randominterval; + + rightvectorcount=(rightvectorcount+1)&3; + + if (fa>1.0) + continue; + + if (fb>(1.0-fa)) + continue; + + fc = 1.0-fa-fb; + + // total alpha, minus random factor so some things fade out sooner. + alphapos = a1*fa + a2*fb + a3*fc; + + // Note that the alpha at this point is a value from 1.0 to 0.0, but represents when to START fading + thisspritesfadestart = faderange + (1.0-faderange) * randomchart[randomindex]; + randomindex += randominterval; + + // Find where the alpha is relative to the fadestart, and calc the real alpha to draw at. + alpha = 1.0 - ((thisspritesfadestart-alphapos)/faderange); + if (alpha > 0.0) + { + if (alpha > 1.0) + alpha=1.0; + + if (SSUsingFog) + { + fogv[0] = fog1[0]*fa + fog2[0]*fb + fog3[0]*fc; + fogv[1] = fog1[1]*fa + fog2[1]*fb + fog3[1]*fc; + } + + if (usewindpoint) + { + winddiffv[0] = winddiff1[0]*fa + winddiff2[0]*fb + winddiff3[0]*fc; + winddiffv[1] = winddiff1[1]*fa + winddiff2[1]*fb + winddiff3[1]*fc; + windforce = windforce1*fa + windforce2*fb + windforce3*fc; + } + + VectorScale(v1, fa, curpoint); + VectorMA(curpoint, fb, v2, curpoint); + VectorMA(curpoint, fc, v3, curpoint); + + light = l1*fa + l2*fb + l3*fc; + if (SSAdditiveTransparency) + { // Additive transparency, scale light value +// light *= alpha; + light = (128 + (light*0.5))*alpha; + alpha = 1.0; + } + + randomindex2 = randomindex; + width = stage->ss->width*(1.0 + (stage->ss->variance[0]*randomchart[randomindex2])); + height = stage->ss->height*(1.0 + (stage->ss->variance[1]*randomchart[randomindex2++])); + if (randomchart[randomindex2++]>0.5) + { + width = -width; + } + if (stage->ss->fadeScale!=0 && alphapos < 1.0) + { + width *= 1.0 + (stage->ss->fadeScale*(1.0-alphapos)); + } + + if (stage->ss->vertSkew != 0) + { // flrand(-vertskew, vertskew) + skew[0] = height * ((stage->ss->vertSkew*2.0f*randomchart[randomindex2++])-stage->ss->vertSkew); + skew[1] = height * ((stage->ss->vertSkew*2.0f*randomchart[randomindex2++])-stage->ss->vertSkew); + } + + if (usewindpoint && windforce > 0 && stage->ss->wind > 0.0) + { + if (SSUsingFog) + { + RB_VerticalSurfaceSpriteWindPoint(curpoint, width, height, (byte)light, (byte)(alpha*255.0), + stage->ss->wind, stage->ss->windIdle, fogv, stage->ss->facing, skew, + winddiffv, windforce, SURFSPRITE_FLATTENED == stage->ss->surfaceSpriteType); + } + else + { + RB_VerticalSurfaceSpriteWindPoint(curpoint, width, height, (byte)light, (byte)(alpha*255.0), + stage->ss->wind, stage->ss->windIdle, NULL, stage->ss->facing, skew, + winddiffv, windforce, SURFSPRITE_FLATTENED == stage->ss->surfaceSpriteType); + } + } + else + { + if (SSUsingFog) + { + RB_VerticalSurfaceSprite(curpoint, width, height, (byte)light, (byte)(alpha*255.0), + stage->ss->wind, stage->ss->windIdle, fogv, stage->ss->facing, skew, SURFSPRITE_FLATTENED == stage->ss->surfaceSpriteType); + } + else + { + RB_VerticalSurfaceSprite(curpoint, width, height, (byte)light, (byte)(alpha*255.0), + stage->ss->wind, stage->ss->windIdle, NULL, stage->ss->facing, skew, SURFSPRITE_FLATTENED == stage->ss->surfaceSpriteType); + } + } + + totalsurfsprites++; + } + } + } + } +} + + +///////////////////////////////////////////// +// Oriented surface sprites + +static void RB_OrientedSurfaceSprite(vec3_t loc, float width, float height, byte light, byte alpha, vec2_t fog, int faceup) +{ + vec3_t loc2, right; + float points[16]; + color4ub_t color; + + color[0]=light; + color[1]=light; + color[2]=light; + color[3]=alpha; + + if (faceup) + { + width *= 0.5; + height *= 0.5; + + // Bottom right + // VectorAdd(loc, right, point); + points[0] = loc[0] + width; + points[1] = loc[1] - width; + points[2] = loc[2] + 1.0; + points[3] = 0; + + // Top right + // VectorAdd(loc, right, point); + points[4] = loc[0] + width; + points[5] = loc[1] + width; + points[6] = loc[2] + 1.0; + points[7] = 0; + + // Top left + // VectorSubtract(loc, right, point); + points[8] = loc[0] - width; + points[9] = loc[1] + width; + points[10] = loc[2] + 1.0; + points[11] = 0; + + // Bottom left + // VectorSubtract(loc, right, point); + points[12] = loc[0] - width; + points[13] = loc[1] - width; + points[14] = loc[2] + 1.0; + points[15] = 0; + } + else + { + VectorMA(loc, height, ssViewUp, loc2); + VectorScale(ssViewRight, width*0.5, right); + + // Bottom right + // VectorAdd(loc, right, point); + points[0] = loc[0] + right[0]; + points[1] = loc[1] + right[1]; + points[2] = loc[2] + right[2]; + points[3] = 0; + + // Top right + // VectorAdd(loc2, right, point); + points[4] = loc2[0] + right[0]; + points[5] = loc2[1] + right[1]; + points[6] = loc2[2] + right[2]; + points[7] = 0; + + // Top left + // VectorSubtract(loc2, right, point); + points[8] = loc2[0] - right[0]; + points[9] = loc2[1] - right[1]; + points[10] = loc2[2] - right[2]; + points[11] = 0; + + // Bottom left + // VectorSubtract(loc, right, point); + points[12] = loc[0] - right[0]; + points[13] = loc[1] - right[1]; + points[14] = loc[2] - right[2]; + points[15] = 0; + } + + // Add the sprite to the render list. + SQuickSprite.Add(points, color, fog); +} + +static void RB_DrawOrientedSurfaceSprites( shaderStage_t *stage, shaderCommands_t *input) +{ + int curindex, curvert; + vec3_t dist; + float triarea, minnormal; + vec2_t vec1to2, vec1to3; + + vec3_t v1,v2,v3; + float a1,a2,a3; + float l1,l2,l3; + vec2_t fog1, fog2, fog3; + + float posi, posj; + float step; + float fa,fb,fc; + + vec3_t curpoint; + float width, height; + float alpha, alphapos, thisspritesfadestart, light; + byte randomindex2; + vec2_t fogv; + + float cutdist=stage->ss->fadeMax*rangescalefactor, cutdist2=cutdist*cutdist; + float fadedist=stage->ss->fadeDist*rangescalefactor, fadedist2=fadedist*fadedist; + + assert(cutdist2 != fadedist2); + float inv_fadediff = 1.0/(cutdist2-fadedist2); + + // The faderange is the fraction amount it takes for these sprites to fade out, assuming an ideal fade range of 250 + float faderange = FADE_RANGE/(cutdist-fadedist); + + if (faderange > 1.0) + { // Don't want to force a new fade_rand + faderange = 1.0; + } + + if (stage->ss->facing) + { // Faceup sprite. + minnormal = 0.99f; + } + else + { // Normal oriented sprite + minnormal = 0.5f; + } + + // Quickly calc all the alphas for each vertex + for (curvert=0; curvertnumVertexes; curvert++) + { + // Calc alpha at each point + VectorSubtract(ssViewOrigin, input->xyz[curvert], dist); + SSVertAlpha[curvert] = 1.0 - (VectorLengthSquared(dist) - fadedist2) * inv_fadediff; + } + + for (curindex=0; curindexnumIndexes-2; curindex+=3) + { + curvert = input->indexes[curindex]; + VectorCopy(input->xyz[curvert], v1); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l1 = input->vertexColors[curvert][2]; + a1 = SSVertAlpha[curvert]; + fog1[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog1[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + + curvert = input->indexes[curindex+1]; + VectorCopy(input->xyz[curvert], v2); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l2 = input->vertexColors[curvert][2]; + a2 = SSVertAlpha[curvert]; + fog2[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog2[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + + curvert = input->indexes[curindex+2]; + VectorCopy(input->xyz[curvert], v3); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l3 = input->vertexColors[curvert][2]; + a3 = SSVertAlpha[curvert]; + fog3[0] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)); + fog3[1] = *((float *)(tess.svars.texcoords[0])+(curvert<<1)+1); + + if (a1 <= 0.0 && a2 <= 0.0 && a3 <= 0.0) + { + continue; + } + + // Find the area in order to calculate the stepsize + vec1to2[0] = v2[0] - v1[0]; + vec1to2[1] = v2[1] - v1[1]; + vec1to3[0] = v3[0] - v1[0]; + vec1to3[1] = v3[1] - v1[1]; + + // Now get the cross product of this sum. + triarea = vec1to3[0]*vec1to2[1] - vec1to3[1]*vec1to2[0]; + triarea=fabs(triarea); + if (triarea <= 1.0) + { // Insanely small abhorrent triangle. + continue; + } + step = stage->ss->density * Q_rsqrt(triarea); + + randomindex = (byte)(v1[0]+v1[1]+v2[0]+v2[1]+v3[0]+v3[1]); + randominterval = (byte)(v1[0]+v2[1]+v3[2])|0x03; // Make sure the interval is at least 3, and always odd + + for (posi=0; posi<1.0; posi+=step) + { + for (posj=0; posj<(1.0-posi); posj+=step) + { + fa=posi+randomchart[randomindex]*step; + randomindex += randominterval; + if (fa>1.0) + continue; + + fb=posj+randomchart[randomindex]*step; + randomindex += randominterval; + if (fb>(1.0-fa)) + continue; + + fc = 1.0-fa-fb; + + // total alpha, minus random factor so some things fade out sooner. + alphapos = a1*fa + a2*fb + a3*fc; + + // Note that the alpha at this point is a value from 1.0 to 0.0, but represents when to START fading + thisspritesfadestart = faderange + (1.0-faderange) * randomchart[randomindex]; + randomindex += randominterval; + + // Find where the alpha is relative to the fadestart, and calc the real alpha to draw at. + alpha = 1.0 - ((thisspritesfadestart-alphapos)/faderange); + + randomindex += randominterval; + if (alpha > 0.0) + { + if (alpha > 1.0) + alpha=1.0; + + if (SSUsingFog) + { + fogv[0] = fog1[0]*fa + fog2[0]*fb + fog3[0]*fc; + fogv[1] = fog1[1]*fa + fog2[1]*fb + fog3[1]*fc; + } + + VectorScale(v1, fa, curpoint); + VectorMA(curpoint, fb, v2, curpoint); + VectorMA(curpoint, fc, v3, curpoint); + + light = l1*fa + l2*fb + l3*fc; + if (SSAdditiveTransparency) + { // Additive transparency, scale light value +// light *= alpha; + light = (128 + (light*0.5))*alpha; + alpha = 1.0; + } + + randomindex2 = randomindex; + width = stage->ss->width*(1.0 + (stage->ss->variance[0]*randomchart[randomindex2])); + height = stage->ss->height*(1.0 + (stage->ss->variance[1]*randomchart[randomindex2++])); + if (randomchart[randomindex2++]>0.5) + { + width = -width; + } + if (stage->ss->fadeScale!=0 && alphapos < 1.0) + { + width *= 1.0 + (stage->ss->fadeScale*(1.0-alphapos)); + } + + if (SSUsingFog) + { + RB_OrientedSurfaceSprite(curpoint, width, height, (byte)light, (byte)(alpha*255.0), fogv, stage->ss->facing); + } + else + { + RB_OrientedSurfaceSprite(curpoint, width, height, (byte)light, (byte)(alpha*255.0), NULL, stage->ss->facing); + } + + totalsurfsprites++; + } + } + } + } +} + + +///////////////////////////////////////////// +// Effect surface sprites + +static void RB_EffectSurfaceSprite(vec3_t loc, float width, float height, byte light, byte alpha, float life, int faceup) +{ + vec3_t loc2, right; + float points[16]; + color4ub_t color; + + color[0]=light; //light; + color[1]=light; //light; + color[2]=light; //light; + color[3]=alpha; //alpha; + + if (faceup) + { + width *= 0.5; + height *= 0.5; + + // Bottom right + // VectorAdd(loc, right, point); + points[0] = loc[0] + width; + points[1] = loc[1] - width; + points[2] = loc[2] + 1.0; + points[3] = 0; + + // Top right + // VectorAdd(loc, right, point); + points[4] = loc[0] + width; + points[5] = loc[1] + width; + points[6] = loc[2] + 1.0; + points[7] = 0; + + // Top left + // VectorSubtract(loc, right, point); + points[8] = loc[0] - width; + points[9] = loc[1] + width; + points[10] = loc[2] + 1.0; + points[11] = 0; + + // Bottom left + // VectorSubtract(loc, right, point); + points[12] = loc[0] - width; + points[13] = loc[1] - width; + points[14] = loc[2] + 1.0; + points[15] = 0; + } + else + { + VectorMA(loc, height, ssViewUp, loc2); + VectorScale(ssViewRight, width*0.5, right); + + // Bottom right + // VectorAdd(loc, right, point); + points[0] = loc[0] + right[0]; + points[1] = loc[1] + right[1]; + points[2] = loc[2] + right[2]; + points[3] = 0; + + // Top right + // VectorAdd(loc2, right, point); + points[4] = loc2[0] + right[0]; + points[5] = loc2[1] + right[1]; + points[6] = loc2[2] + right[2]; + points[7] = 0; + + // Top left + // VectorSubtract(loc2, right, point); + points[8] = loc2[0] - right[0]; + points[9] = loc2[1] - right[1]; + points[10] = loc2[2] - right[2]; + points[11] = 0; + + // Bottom left + // VectorSubtract(loc, right, point); + points[12] = loc[0] - right[0]; + points[13] = loc[1] - right[1]; + points[14] = loc[2] - right[2]; + points[15] = 0; + } + + // Add the sprite to the render list. + SQuickSprite.Add(points, color, NULL); +} + +static void RB_DrawEffectSurfaceSprites( shaderStage_t *stage, shaderCommands_t *input) +{ + int curindex, curvert; + vec3_t dist; + float triarea, minnormal; + vec2_t vec1to2, vec1to3; + + vec3_t v1,v2,v3; + float a1,a2,a3; + float l1,l2,l3; + + float posi, posj; + float step; + float fa,fb,fc; + float effecttime, effectpos; + float density; + + vec3_t curpoint; + float width, height; + float alpha, alphapos, thisspritesfadestart, light; + byte randomindex2; + + float cutdist=stage->ss->fadeMax*rangescalefactor, cutdist2=cutdist*cutdist; + float fadedist=stage->ss->fadeDist*rangescalefactor, fadedist2=fadedist*fadedist; + + float fxalpha = stage->ss->fxAlphaEnd - stage->ss->fxAlphaStart; + qboolean fadeinout=qfalse; + + assert(cutdist2 != fadedist2); + float inv_fadediff = 1.0/(cutdist2-fadedist2); + + // The faderange is the fraction amount it takes for these sprites to fade out, assuming an ideal fade range of 250 + float faderange = FADE_RANGE/(cutdist-fadedist); + if (faderange > 1.0f) + { // Don't want to force a new fade_rand + faderange = 1.0f; + } + + if (stage->ss->facing) + { // Faceup sprite. + minnormal = 0.99f; + } + else + { // Normal oriented sprite + minnormal = 0.5f; + } + + // Make the object fade in. + if (stage->ss->fxAlphaEnd < 0.05 && stage->ss->height >= 0.1 && stage->ss->width >= 0.1) + { // The sprite fades out, and it doesn't start at a pinpoint. Let's fade it in. + fadeinout=qtrue; + } + + if (stage->ss->surfaceSpriteType == SURFSPRITE_WEATHERFX) + { // This effect is affected by weather settings. + if (curWeatherAmount < 0.01) + { // Don't show these effects + return; + } + else + { + density = stage->ss->density / curWeatherAmount; + } + } + else + { + density = stage->ss->density; + } + + // Quickly calc all the alphas for each vertex + for (curvert=0; curvertnumVertexes; curvert++) + { + // Calc alpha at each point + VectorSubtract(ssViewOrigin, input->xyz[curvert], dist); + SSVertAlpha[curvert] = 1.0f - (VectorLengthSquared(dist) - fadedist2) * inv_fadediff; + + // Note this is the proper equation, but isn't used right now because it would be just a tad slower. + // Formula for alpha is 1.0f - ((len-fade)/(cut-fade)) + // Which is equal to (1.0+fade/(cut-fade)) - (len/(cut-fade)) + // So mult=1/(cut-fade), and base=(1+fade*mult). + // SSVertAlpha[curvert] = fadebase - (VectorLength(dist) * fademult); + + } + + for (curindex=0; curindexnumIndexes-2; curindex+=3) + { + curvert = input->indexes[curindex]; + VectorCopy(input->xyz[curvert], v1); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l1 = input->vertexColors[curvert][2]; + a1 = SSVertAlpha[curvert]; + + curvert = input->indexes[curindex+1]; + VectorCopy(input->xyz[curvert], v2); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l2 = input->vertexColors[curvert][2]; + a2 = SSVertAlpha[curvert]; + + curvert = input->indexes[curindex+2]; + VectorCopy(input->xyz[curvert], v3); + if (input->normal[curvert][2] < minnormal) + { + continue; + } + l3 = input->vertexColors[curvert][2]; + a3 = SSVertAlpha[curvert]; + + if (a1 <= 0.0f && a2 <= 0.0f && a3 <= 0.0f) + { + continue; + } + + // Find the area in order to calculate the stepsize + vec1to2[0] = v2[0] - v1[0]; + vec1to2[1] = v2[1] - v1[1]; + vec1to3[0] = v3[0] - v1[0]; + vec1to3[1] = v3[1] - v1[1]; + + // Now get the cross product of this sum. + triarea = vec1to3[0]*vec1to2[1] - vec1to3[1]*vec1to2[0]; + triarea=fabs(triarea); + if (triarea <= 1.0f) + { // Insanely small abhorrent triangle. + continue; + } + step = density * Q_rsqrt(triarea); + + randomindex = (byte)(v1[0]+v1[1]+v2[0]+v2[1]+v3[0]+v3[1]); + randominterval = (byte)(v1[0]+v2[1]+v3[2])|0x03; // Make sure the interval is at least 3, and always odd + + for (posi=0; posi<1.0f; posi+=step) + { + for (posj=0; posj<(1.0-posi); posj+=step) + { + effecttime = (tr.refdef.time+10000.0*randomchart[randomindex])/stage->ss->fxDuration; + effectpos = (float)effecttime - (int)effecttime; + + randomindex2 = randomindex+effecttime; + randomindex += randominterval; + fa=posi+randomchart[randomindex2++]*step; + if (fa>1.0f) + continue; + + fb=posj+randomchart[randomindex2++]*step; + if (fb>(1.0-fa)) + continue; + + fc = 1.0-fa-fb; + + // total alpha, minus random factor so some things fade out sooner. + alphapos = a1*fa + a2*fb + a3*fc; + + // Note that the alpha at this point is a value from 1.0f to 0.0, but represents when to START fading + thisspritesfadestart = faderange + (1.0-faderange) * randomchart[randomindex2]; + randomindex2 += randominterval; + + // Find where the alpha is relative to the fadestart, and calc the real alpha to draw at. + alpha = 1.0f - ((thisspritesfadestart-alphapos)/faderange); + if (alpha > 0.0f) + { + if (alpha > 1.0f) + alpha=1.0f; + + VectorScale(v1, fa, curpoint); + VectorMA(curpoint, fb, v2, curpoint); + VectorMA(curpoint, fc, v3, curpoint); + + light = l1*fa + l2*fb + l3*fc; + randomindex2 = randomindex; + width = stage->ss->width*(1.0f + (stage->ss->variance[0]*randomchart[randomindex2])); + height = stage->ss->height*(1.0f + (stage->ss->variance[1]*randomchart[randomindex2++])); + + width = width + (effectpos*stage->ss->fxGrow[0]*width); + height = height + (effectpos*stage->ss->fxGrow[1]*height); + + // If we want to fade in and out, that's different than a straight fade. + if (fadeinout) + { + if (effectpos > 0.5) + { // Fade out + alpha = alpha*(stage->ss->fxAlphaStart+(fxalpha*(effectpos-0.5)*2.0)); + } + else + { // Fade in + alpha = alpha*(stage->ss->fxAlphaStart+(fxalpha*(0.5-effectpos)*2.0)); + } + } + else + { // Normal fade + alpha = alpha*(stage->ss->fxAlphaStart+(fxalpha*effectpos)); + } + + if (SSAdditiveTransparency) + { // Additive transparency, scale light value +// light *= alpha; + light = (128 + (light*0.5))*alpha; + alpha = 1.0; + } + + if (randomchart[randomindex2]>0.5f) + { + width = -width; + } + if (stage->ss->fadeScale!=0 && alphapos < 1.0f) + { + width *= 1.0f + (stage->ss->fadeScale*(1.0-alphapos)); + } + + if (stage->ss->wind>0.0f && curWindSpeed > 0.001) + { + vec3_t drawpoint; + + VectorMA(curpoint, effectpos*stage->ss->wind, curWindBlowVect, drawpoint); + RB_EffectSurfaceSprite(drawpoint, width, height, (byte)light, (byte)(alpha*255.0f), stage->ss->fxDuration, stage->ss->facing); + } + else + { + RB_EffectSurfaceSprite(curpoint, width, height, (byte)light, (byte)(alpha*255.0f), stage->ss->fxDuration, stage->ss->facing); + } + + totalsurfsprites++; + } + } + } + } +} + +extern void R_WorldToLocal (vec3_t world, vec3_t localVec) ; +extern float preTransEntMatrix[16], invEntMatrix[16]; +extern void R_InvertMatrix(float *sourcemat, float *destmat); + +void RB_DrawSurfaceSprites( shaderStage_t *stage, shaderCommands_t *input) +{ + unsigned long glbits=stage->stateBits; + + R_SurfaceSpriteFrameUpdate(); + + // + // Check fog + // + if ( tess.fogNum && tess.shader->fogPass && r_drawfog->value) + { + SSUsingFog = qtrue; + SQuickSprite.StartGroup(&stage->bundle[0], glbits, tess.fogNum); + } + else + { + SSUsingFog = qfalse; + SQuickSprite.StartGroup(&stage->bundle[0], glbits); + } + + // Special provision in case the transparency is additive. + if ((glbits & (GLS_SRCBLEND_BITS|GLS_DSTBLEND_BITS)) == (GLS_SRCBLEND_ONE|GLS_DSTBLEND_ONE)) + { // Additive transparency, scale light value + SSAdditiveTransparency=qtrue; + } + else + { + SSAdditiveTransparency=qfalse; + } + + + //Check if this is a new entity transformation (incl. world entity), and update the appropriate vectors if so. + if (backEnd.currentEntity != ssLastEntityDrawn) + { + if (backEnd.currentEntity == &tr.worldEntity) + { // Drawing the world, so our job is dead-easy, in the viewparms + VectorCopy(backEnd.viewParms.or.origin, ssViewOrigin); + VectorCopy(backEnd.viewParms.or.axis[1], ssViewRight); + VectorCopy(backEnd.viewParms.or.axis[2], ssViewUp); + } + else + { // Drawing an entity, so we need to transform the viewparms to the model's coordinate system +// R_WorldPointToEntity (backEnd.viewParms.or.origin, ssViewOrigin); + R_WorldNormalToEntity (backEnd.viewParms.or.axis[1], ssViewRight); + R_WorldNormalToEntity (backEnd.viewParms.or.axis[2], ssViewUp); + VectorCopy(backEnd.ori.viewOrigin, ssViewOrigin); +// R_WorldToLocal(backEnd.viewParms.or.axis[1], ssViewRight); +// R_WorldToLocal(backEnd.viewParms.or.axis[2], ssViewUp); + } + ssLastEntityDrawn = backEnd.currentEntity; + } + + switch(stage->ss->surfaceSpriteType) + { + case SURFSPRITE_FLATTENED: + case SURFSPRITE_VERTICAL: + RB_DrawVerticalSurfaceSprites(stage, input); + break; + case SURFSPRITE_ORIENTED: + RB_DrawOrientedSurfaceSprites(stage, input); + break; + case SURFSPRITE_EFFECT: + case SURFSPRITE_WEATHERFX: + RB_DrawEffectSurfaceSprites(stage, input); + break; + } + + SQuickSprite.EndGroup(); + + sssurfaces++; +} + diff --git a/code/renderer/tr_terrain.cpp b/code/renderer/tr_terrain.cpp new file mode 100644 index 0000000..ad10695 --- /dev/null +++ b/code/renderer/tr_terrain.cpp @@ -0,0 +1,1047 @@ +#include "../server/exe_headers.h" + +// this include must remain at the top of every CPP file +#include "tr_local.h" + +#if !defined(GENERICPARSER2_H_INC) + #include "../game/genericparser2.h" +#endif + +// To do: +// Alter variance dependent on global distance from player (colour code this for cg_terrainCollisionDebug) +// Improve texture blending on edge conditions +// Link to neightbouring terrains or architecture (edge conditions) +// Post process generated light data to make sure there are no bands within a patch + +#include "../qcommon/cm_landscape.h" +#include "tr_landscape.h" + +#define VectorSet5(v,x,y,z,a,b) ((v)[0]=(x), (v)[1]=(y), (v)[2]=(z), (v)[3]=(a), (v)[4]=(b)) +#define VectorScaleVectorAdd(c,a,b,o) ((o)[0]=(c)[0]+((a)[0]*(b)[0]),(o)[1]=(c)[1]+((a)[1]*(b)[1]),(o)[2]=(c)[2]+((a)[2]*(b)[2])) + +cvar_t *r_drawTerrain; +cvar_t *r_terrainTessellate; +cvar_t *r_terrainWaterOffset; + +cvar_t *r_count; + +static int TerrainFog = 0; +static float TerrainDistanceCull; + +// +// Render the tree. +// +void CTRPatch::RenderCorner(ivec5_t corner) +{ + if((corner[3] < 0) || (tess.registration != corner[4])) + { + CTerVert *vert; + + vert = mRenderMap + (corner[1] * owner->GetRealWidth()) + corner[0]; + + VectorCopy(vert->coords, tess.xyz[tess.numVertexes]); + VectorCopy(vert->normal, tess.normal[tess.numVertexes]); + + *(ulong *)tess.vertexColors[tess.numVertexes] = *(ulong *)vert->tint; + *(ulong *)tess.vertexAlphas[tess.numVertexes] = corner[2]; + + tess.texCoords[tess.numVertexes][0][0] = vert->tex[0]; + tess.texCoords[tess.numVertexes][0][1] = vert->tex[1]; + + tess.indexes[tess.numIndexes++] = tess.numVertexes; + corner[3] = tess.numVertexes++; + corner[4] = tess.registration; + } + else + { + tess.indexes[tess.numIndexes++] = corner[3]; + } +} + +void CTRPatch::RecurseRender(int depth, ivec5_t left, ivec5_t right, ivec5_t apex) +{ + // All non-leaf nodes have both children, so just check for one + if (depth >= 0) + { + ivec5_t center; + byte *centerAlphas; + byte *leftAlphas; + byte *rightAlphas; + + // Work out the centre of the hypoteneuse + center[0] = (left[0] + right[0]) >> 1; + center[1] = (left[1] + right[1]) >> 1; + + // Work out the relevant texture coefficients at that point + leftAlphas = (byte *)&left[2]; + rightAlphas = (byte *)&right[2]; + centerAlphas = (byte *)¢er[2]; + + centerAlphas[0] = (leftAlphas[0] + rightAlphas[0]) >> 1; + centerAlphas[1] = (leftAlphas[1] + rightAlphas[1]) >> 1; + centerAlphas[2] = (leftAlphas[2] + rightAlphas[2]) >> 1; + centerAlphas[3] = (leftAlphas[3] + rightAlphas[3]) >> 1; + + // Make sure the vert index and tesselation registration are not set + center[3] = -1; + center[4] = 0; + + if (apex[0] == left[0] && apex[0] == center[0]) + { + depth = 0; + } + + RecurseRender(depth-1, apex, left, center); + RecurseRender(depth-1, right, apex, center); + } + else + { + if (left[0] == right[0] && left[0] == apex[0]) + { + return; + } + if (left[1] == right[1] && left[1] == apex[1]) + { + return; + } + // A leaf node! Output a triangle to be rendered. + RB_CheckOverflow(4, 4); + +// assert(left[0] != right[0] || left[1] != right[1]); +// assert(left[0] != apex[0] || left[1] != apex[1]); + + RenderCorner(left); + RenderCorner(right); + RenderCorner(apex); + } +} + +// +// Render the mesh. +// +// The order of triangles is critical to the subdivision working + +void CTRPatch::Render(int Part) +{ + ivec5_t lTL, lTR, lBL, lBR; + int patchTerxels = owner->GetTerxels(); + +// VectorSet5(TL, 0, 0, TEXTURE_ALPHA_TL, -1, 0); + lTL[0] = 0; + lTL[1] = 0; + lTL[2] = TEXTURE_ALPHA_TL; + lTL[3] = -1; + lTL[4] = 0; +// VectorSet5(TR, owner->GetTerxels(), 0, TEXTURE_ALPHA_TR, -1, 0); + lTR[0] = patchTerxels; + lTR[1] = 0; + lTR[2] = TEXTURE_ALPHA_TR; + lTR[3] = -1; + lTR[4] = 0; +// VectorSet5(BL, 0, owner->GetTerxels(), TEXTURE_ALPHA_BL, -1, 0); + lBL[0] = 0; + lBL[1] = patchTerxels; + lBL[2] = TEXTURE_ALPHA_BL; + lBL[3] = -1; + lBL[4] = 0; +// VectorSet5(BR, owner->GetTerxels(), owner->GetTerxels(), TEXTURE_ALPHA_BR, -1, 0); + lBR[0] = patchTerxels; + lBR[1] = patchTerxels; + lBR[2] = TEXTURE_ALPHA_BR; + lBR[3] = -1; + lBR[4] = 0; + + if ((Part & PI_TOP) && mTLShader) + { +/* float d; + + d = DotProduct (backEnd.refdef.vieworg, mNormal[0]) - mDistance[0]; + + if (d <= 0.0)*/ + { + RecurseRender(r_terrainTessellate->integer, lBL, lTR, lTL); + } + } + + if ((Part & PI_BOTTOM) && mBRShader) + { +/* float d; + + d = DotProduct (backEnd.refdef.vieworg, mNormal[1]) - mDistance[1]; + + if (d >= 0.0)*/ + { + RecurseRender(r_terrainTessellate->integer, lTR, lBL, lBR); + } + } +} + +// +// At this point the patch is visible and at least part of it is below water level +// +int CTRPatch::RenderWaterVert(int x, int y) +{ + CTerVert *vert; + + vert = mRenderMap + x + (y * owner->GetRealWidth()); + + if(vert->tessRegistration == tess.registration) + { + return(vert->tessIndex); + } + tess.xyz[tess.numVertexes][0] = vert->coords[0]; + tess.xyz[tess.numVertexes][1] = vert->coords[1]; + tess.xyz[tess.numVertexes][2] = owner->GetWaterHeight(); + + *(ulong *)tess.vertexColors[tess.numVertexes] = 0xffffffff; + + tess.texCoords[tess.numVertexes][0][0] = vert->tex[0]; + tess.texCoords[tess.numVertexes][0][1] = vert->tex[1]; + + vert->tessIndex = tess.numVertexes; + vert->tessRegistration = tess.registration; + + tess.numVertexes++; + return(vert->tessIndex); +} + +void CTRPatch::RenderWater(void) +{ + RB_CheckOverflow(4, 6); + + // Get the neighbouring patches + int TL = RenderWaterVert(0, 0); + int TR = RenderWaterVert(owner->GetTerxels(), 0); + int BL = RenderWaterVert(0, owner->GetTerxels()); + int BR = RenderWaterVert(owner->GetTerxels(), owner->GetTerxels()); + + // TL + tess.indexes[tess.numIndexes++] = BL; + tess.indexes[tess.numIndexes++] = TR; + tess.indexes[tess.numIndexes++] = TL; + + // BR + tess.indexes[tess.numIndexes++] = TR; + tess.indexes[tess.numIndexes++] = BL; + tess.indexes[tess.numIndexes++] = BR; +} + +const bool CTRPatch::HasWater(void) const +{ + owner->SetRealWaterHeight( owner->GetBaseWaterHeight() + r_terrainWaterOffset->integer ); + return(common->GetMins()[2] < owner->GetWaterHeight()); +} + +bool CM_CullWorldBox (const cplane_t *frustum, const vec3pair_t bounds); + +void CTRPatch::SetVisibility(bool visCheck) +{ + if(visCheck) + { + if(DistanceSquared(mCenter, backEnd.refdef.vieworg) > TerrainDistanceCull) + { + misVisible = false; + } + else + { + // Set the visibility of the patch + misVisible = CM_CullWorldBox(backEnd.viewParms.frustum, GetBounds()); + } + } + else + { + misVisible = true; + } +} + +/* +void CTRPatch::CalcNormal(void) +{ + CTerVert *vert1, *vert2, *vert3; + ivec5_t TL, TR, BL, BR; + vec3_t v1, v2; + + VectorSet5(TL, 0, 0, TEXTURE_ALPHA_TL, -1, 0); + VectorSet5(TR, owner->GetTerxels(), 0, TEXTURE_ALPHA_TR, -1, 0); + VectorSet5(BL, 0, owner->GetTerxels(), TEXTURE_ALPHA_BL, -1, 0); + VectorSet5(BR, owner->GetTerxels(), owner->GetTerxels(), TEXTURE_ALPHA_BR, -1, 0); + + vert1 = mRenderMap + (BL[1] * owner->GetRealWidth()) + BL[0]; + vert2 = mRenderMap + (TR[1] * owner->GetRealWidth()) + TR[0]; + vert3 = mRenderMap + (TL[1] * owner->GetRealWidth()) + TL[0]; + VectorSubtract(vert2->coords, vert1->coords, v1); + VectorSubtract(vert3->coords, vert1->coords, v2); + CrossProduct(v1, v2, mNormal[0]); + VectorNormalize(mNormal[0]); + mDistance[0] = DotProduct (vert1->coords, mNormal[0]); + + vert1 = mRenderMap + (BL[1] * owner->GetRealWidth()) + BL[0]; + vert2 = mRenderMap + (TR[1] * owner->GetRealWidth()) + TR[0]; + vert3 = mRenderMap + (BR[1] * owner->GetRealWidth()) + BR[0]; + VectorSubtract(vert2->coords, vert1->coords, v1); + VectorSubtract(vert3->coords, vert1->coords, v2); + CrossProduct(v1, v2, mNormal[1]); + VectorNormalize(mNormal[1]); + mDistance[1] = DotProduct (vert1->coords, mNormal[1]); +} +*/ +// +// Reset all patches, recompute variance if needed +// +void CTRLandScape::Reset(bool visCheck) +{ + int x, y; + CTRPatch *patch; + + TerrainDistanceCull = tr.distanceCull + mPatchSize; + TerrainDistanceCull *= TerrainDistanceCull; + + // Go through the patches performing resets, compute variances, and linking. + for(y = mPatchMiny; y < mPatchMaxy; y++) + { + for(x = mPatchMinx; x < mPatchMaxx; x++, patch++) + { + patch = GetPatch(x, y); + patch->SetVisibility(visCheck); + } + } +} + + +// +// Render each patch of the landscape & adjust the frame variance. +// + +void CTRLandScape::Render(void) +{ + int x, y; + CTRPatch *patch; + TPatchInfo *current; + int i; + + // Render all the visible patches + current = mSortedPatches; + for(i=0;imPatch->isVisible()) + { + if (tess.shader != current->mShader) + { + RB_EndSurface(); + RB_BeginSurface(current->mShader, TerrainFog); + } + current->mPatch->Render(current->mPart); + } + current++; + } + RB_EndSurface(); + + // Render all the water for visible patches + // Done as a separate iteration to reduce the number of tesses created + if(mWaterShader && (mWaterShader != tr.defaultShader)) + { + RB_BeginSurface( mWaterShader, tr.world->globalFog ); + + for(y = mPatchMiny; y < mPatchMaxy; y++ ) + { + for(x = mPatchMinx; x < mPatchMaxx; x++ ) + { + patch = GetPatch(x, y); + if(patch->isVisible() && patch->HasWater()) + { + patch->RenderWater(); + } + } + } + RB_EndSurface(); + } +} + +void CTRLandScape::CalculateRegion(void) +{ + vec3_t mins, maxs, size, offset; + +#if _DEBUG + mCycleCount++; +#endif + VectorCopy(GetPatchSize(), size); + VectorCopy(GetMins(), offset); + + mins[0] = backEnd.refdef.vieworg[0] - tr.distanceCull - (size[0] * 2.0f) - offset[0]; + mins[1] = backEnd.refdef.vieworg[1] - tr.distanceCull - (size[1] * 2.0f) - offset[1]; + + maxs[0] = backEnd.refdef.vieworg[0] + tr.distanceCull + (size[0] * 2.0f) - offset[0]; + maxs[1] = backEnd.refdef.vieworg[1] + tr.distanceCull + (size[1] * 2.0f) - offset[1]; + + mPatchMinx = Com_Clamp(0, GetBlockWidth(), floorf(mins[0] / size[0])); + mPatchMaxx = Com_Clamp(0, GetBlockWidth(), ceilf(maxs[0] / size[0])); + + mPatchMiny = Com_Clamp(0, GetBlockHeight(), floorf(mins[1] / size[1])); + mPatchMaxy = Com_Clamp(0, GetBlockHeight(), ceilf(maxs[1] / size[1])); +} + +void CTRLandScape::CalculateRealCoords(void) +{ + int x, y; + + // Work out the real world coordinates of each heightmap entry + for(y = 0; y < GetRealHeight(); y++) + { + for(x = 0; x < GetRealWidth(); x++) + { + ivec3_t icoords; + int offset; + + offset = (y * GetRealWidth()) + x; + + VectorSet(icoords, x, y, mRenderMap[offset].height); + VectorScaleVectorAdd(GetMins(), icoords, GetTerxelSize(), mRenderMap[offset].coords); + } + } +} + +void CTRLandScape::CalculateNormals(void) +{ + int x, y, offset = 0; + + // Work out the normals for every face + for(y = 0; y < GetHeight(); y++) + { + for(x = 0; x < GetWidth(); x++) + { + vec3_t vcenter, vleft; + + offset = (y * GetRealWidth()) + x; + + VectorSubtract(mRenderMap[offset].coords, mRenderMap[offset + 1].coords, vcenter); + VectorSubtract(mRenderMap[offset].coords, mRenderMap[offset + GetRealWidth()].coords, vleft); + + CrossProduct(vcenter, vleft, mRenderMap[offset].normal); + VectorNormalize(mRenderMap[offset].normal); + } + // Duplicate right edge condition + VectorCopy(mRenderMap[offset].normal, mRenderMap[offset + 1].normal); + } + // Duplicate bottom line + offset = GetHeight() * GetRealWidth(); + for(x = 0; x < GetRealWidth(); x++) + { + VectorCopy(mRenderMap[offset - GetRealWidth() + x].normal, mRenderMap[offset + x].normal); + } +} + +int R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); + +void CTRLandScape::CalculateLighting(void) +{ + int x, y, offset = 0; + + // Work out the vertex normal (average of every attached face normal) and apply to the direction of the light + for(y = 0; y < GetHeight(); y++) + { + for(x = 0; x < GetWidth(); x++) + { + vec3_t ambient; + vec3_t directed, direction; + vec3_t total, tint; + vec_t dp; + + offset = (y * GetRealWidth()) + x; + + // Work out average normal + VectorCopy(GetRenderMap(x, y)->normal, total); + VectorAdd(total, GetRenderMap(x + 1, y)->normal, total); + VectorAdd(total, GetRenderMap(x + 1, y + 1)->normal, total); + VectorAdd(total, GetRenderMap(x, y + 1)->normal, total); + VectorNormalize(total); + + if (!R_LightForPoint(mRenderMap[offset].coords, ambient, directed, direction)) + { + mRenderMap[offset].tint[0] = + mRenderMap[offset].tint[1] = + mRenderMap[offset].tint[2] = 255 >> tr.overbrightBits; + mRenderMap[offset].tint[3] = 255; + continue; + } + + if(mRenderMap[offset].coords[2] < common->GetBaseWaterHeight()) + { + VectorScale(ambient, 0.75f, ambient); + } + + // Both normalised, so -1.0 < dp < 1.0 + dp = Com_Clamp(0.0f, 1.0f, DotProduct(direction, total)); + dp = powf(dp, 3); + VectorScale(ambient, (1.0 - dp) * 0.5, ambient); + VectorMA(ambient, dp, directed, tint); + + // rjr - in R_SetupEntityLighting, ambient light is automatically increased by 32, so do it here to match + // rjr - decided to disable both the lighting boost automatically in there as well as here. + mRenderMap[offset].tint[0] = (byte)Com_Clamp(0.0f, 255.0f, tint[0] ) >> tr.overbrightBits; + mRenderMap[offset].tint[1] = (byte)Com_Clamp(0.0f, 255.0f, tint[1] ) >> tr.overbrightBits; + mRenderMap[offset].tint[2] = (byte)Com_Clamp(0.0f, 255.0f, tint[2] ) >> tr.overbrightBits; + mRenderMap[offset].tint[3] = 0xff; + } + mRenderMap[offset + 1].tint[0] = mRenderMap[offset].tint[0]; + mRenderMap[offset + 1].tint[1] = mRenderMap[offset].tint[1]; + mRenderMap[offset + 1].tint[2] = mRenderMap[offset].tint[2]; + mRenderMap[offset + 1].tint[3] = 0xff; + } + // Duplicate bottom line + offset = GetHeight() * GetRealWidth(); + for(x = 0; x < GetRealWidth(); x++) + { + mRenderMap[offset + x].tint[0] = mRenderMap[offset - GetRealWidth() + x].tint[0]; + mRenderMap[offset + x].tint[1] = mRenderMap[offset - GetRealWidth() + x].tint[1]; + mRenderMap[offset + x].tint[2] = mRenderMap[offset - GetRealWidth() + x].tint[2]; + mRenderMap[offset + x].tint[3] = 0xff; + } +} + +void CTRLandScape::CalculateTextureCoords(void) +{ + int x, y; + + for(y = 0; y < GetRealHeight(); y++) + { + for(x = 0; x < GetRealWidth(); x++) + { + int offset = (y * GetRealWidth()) + x; + + mRenderMap[offset].tex[0] = x * mTextureScale * GetTerxelSize()[0]; + mRenderMap[offset].tex[1] = y * mTextureScale * GetTerxelSize()[1]; + } + } +} + +void CTRLandScape::SetShaders(const int height, const qhandle_t shader) +{ + int i; + + for(i = height; shader && (i < HEIGHT_RESOLUTION); i++) + { + if(!mHeightDetails[i].GetShader()) + { + mHeightDetails[i].SetShader(shader); + } + } +} + +void CTRLandScape::LoadTerrainDef(const char *td) +{ +#ifndef PRE_RELEASE_DEMO + char terrainDef[MAX_QPATH]; + CGenericParser2 parse; + CGPGroup *basegroup, *classes, *items; + + Com_sprintf(terrainDef, MAX_QPATH, "ext_data/RMG/%s.terrain", td); + Com_Printf("R_Terrain: Loading and parsing terrainDef %s.....\n", td); + + mWaterShader = NULL; + mFlatShader = NULL; + + if(!Com_ParseTextFile(terrainDef, parse)) + { + Com_sprintf(terrainDef, MAX_QPATH, "ext_data/arioche/%s.terrain", td); + if(!Com_ParseTextFile(terrainDef, parse)) + { + Com_Printf("Could not open %s\n", terrainDef); + return; + } + } + // The whole file.... + basegroup = parse.GetBaseParseGroup(); + + // The root { } struct + classes = basegroup->GetSubGroups(); + while(classes) + { + items = classes->GetSubGroups(); + while(items) + { + const char* type = items->GetName ( ); + + if(!stricmp( type, "altitudetexture")) + { + int height; + const char *shaderName; + qhandle_t shader; + + // Height must exist - the rest are optional + height = atol(items->FindPairValue("height", "0")); + + // Shader for this height + shaderName = items->FindPairValue("shader", ""); + if(shaderName[0]) + { + shader = RE_RegisterShader(shaderName); + if(shader) + { + SetShaders(height, shader); + } + } + } + else if(!stricmp(type, "water")) + { + mWaterShader = R_GetShaderByHandle(RE_RegisterShader(items->FindPairValue("shader", ""))); + } + else if(!stricmp(type, "flattexture")) + { + mFlatShader = RE_RegisterShader ( items->FindPairValue("shader", "") ); + } + + items = (CGPGroup *)items->GetNext(); + } + classes = (CGPGroup *)classes->GetNext(); + } + + Com_ParseTextFileDestroy(parse); +#endif // PRE_RELEASE_DEMO +} + +qhandle_t R_CreateBlendedShader(qhandle_t a, qhandle_t b, qhandle_t c, bool surfaceSprites ); + +qhandle_t CTRLandScape::GetBlendedShader(qhandle_t a, qhandle_t b, qhandle_t c, bool surfaceSprites) +{ + qhandle_t blended; + + // Special case single pass shader + if((a == b) && (a == c)) + { + return(a); + } + + blended = R_CreateBlendedShader(a, b, c, surfaceSprites ); + return(blended); +} + +static int ComparePatchInfo(const TPatchInfo *arg1, const TPatchInfo *arg2) +{ + shader_t *s1, *s2; + + if ((arg1->mPart & PI_TOP)) + { + s1 = arg1->mPatch->GetTLShader(); + } + else + { + s1 = arg1->mPatch->GetBRShader(); + } + + if ((arg2->mPart & PI_TOP)) + { + s2 = arg2->mPatch->GetTLShader(); + } + else + { + s2 = arg2->mPatch->GetBRShader(); + } + + if (s1 < s2) + { + return -1; + } + else if (s1 > s2) + { + return 1; + } + + return 0; +} + +void CTRLandScape::CalculateShaders(void) +{ +#ifndef PRE_RELEASE_DEMO + int x, y; + int width, height; + int offset; +// int offsets[4]; + qhandle_t handles[4]; + CTRPatch *patch; + qhandle_t *shaders; + TPatchInfo *current = mSortedPatches; + + width = GetWidth ( ) / common->GetTerxels ( ); + height = GetHeight ( ) / common->GetTerxels ( ); + + shaders = new qhandle_t [ (width+1) * (height+1) ]; + + // On the first pass determine all of the shaders for the entire + // terrain assuming no flat ground + offset = 0; + for ( y = 0; y < height + 1; y ++ ) + { + if ( y <= height ) + { + offset = common->GetTerxels ( ) * y * GetRealWidth ( ); + } + else + { + offset = common->GetTerxels ( ) * (y-1) * GetRealWidth ( ); + offset += GetRealWidth ( ); + } + + for ( x = 0; x < width + 1; x ++, offset += common->GetTerxels ( ) ) + { + // Save the shader + shaders[y * width + x] = GetHeightDetail(mRenderMap[offset].height)->GetShader ( ); + } + } + + // On the second pass determine flat ground and replace the shader + // at that point with the flat ground shader + byte* flattenMap = common->GetFlattenMap ( ); + if ( mFlatShader && flattenMap ) + { + for ( y = 1; y < height; y ++ ) + { + for ( x = 1; x < width; x ++ ) + { + int offset; + int xx; + int yy; + bool flat = false; + + offset = (x) * common->GetTerxels ( ); + offset += (y) * common->GetTerxels ( ) * GetRealWidth(); + + offset -= GetRealWidth(); + offset -= 1; + + for ( yy = 0; yy < 3 && !flat; yy++ ) + { + for ( xx = 0; xx < 3 && !flat; xx++ ) + { + if ( flattenMap [ offset + xx] & 0x80) + { + flat = true; + break; + } + } + + offset += GetRealWidth(); + } + // This shader is now a flat shader + if ( flat ) + { + shaders[y * width + x] = mFlatShader; + } + +#ifdef _DEBUG + OutputDebugString ( va("Flat Area: %f %f\n", + GetMins()[0] + (GetMaxs()[0]-GetMins()[0])/width * x, + GetMins()[1] + (GetMaxs()[1]-GetMins()[1])/height * y) ); +#endif + } + } + } + + // Now that the shaders have been determined, set them for each patch + patch = mTRPatches; + mSortedCount = 0; + for ( y = 0; y < height; y ++ ) + { + for ( x = 0; x < width; x ++, patch++ ) + { + bool surfaceSprites = true; + + /* + handles[INDEX_TL] = shaders[ (x + y) * width ]; + handles[INDEX_TR] = shaders[ ((x + 1) + y) * width ]; + handles[INDEX_BL] = shaders[ (x + (y + 1)) * width ]; + handles[INDEX_BR] = shaders[ ((x + 1) + (y + 1)) * width ]; + */ + handles[INDEX_TL] = shaders[ x + y * width ]; + handles[INDEX_TR] = shaders[ x + 1 + y * width ]; + handles[INDEX_BL] = shaders[ x + (y + 1) * width ]; + handles[INDEX_BR] = shaders[ x + 1 + (y + 1) * width ]; + + if ( handles[INDEX_TL] == mFlatShader || + handles[INDEX_TR] == mFlatShader || + handles[INDEX_BL] == mFlatShader || + handles[INDEX_BR] == mFlatShader ) + { + surfaceSprites = false; + } + + patch->SetTLShader(GetBlendedShader(handles[INDEX_TR], handles[INDEX_BL], handles[INDEX_TL], surfaceSprites)); + current->mPatch = patch; + current->mShader = patch->GetTLShader(); + current->mPart = PI_TOP; + + patch->SetBRShader(GetBlendedShader(handles[INDEX_TR], handles[INDEX_BL], handles[INDEX_BR], surfaceSprites)); + if (patch->GetBRShader() == current->mShader) + { + current->mPart |= PI_BOTTOM; + } + else + { + mSortedCount++; + current++; + + current->mPatch = patch; + current->mShader = patch->GetBRShader(); + current->mPart = PI_BOTTOM; + } + mSortedCount++; + current++; + } + } + + // Cleanup our temporary array + delete[] shaders; + + qsort(mSortedPatches, mSortedCount, sizeof(*mSortedPatches), (int (__cdecl *)(const void *,const void *))ComparePatchInfo); + +#endif // PRE_RELEASE_DEMO +} + +void CTRPatch::SetRenderMap(const int x, const int y) +{ + mRenderMap = localowner->GetRenderMap(x, y); +} + +void InitRendererPatches( CCMPatch *patch, void *userdata ) +{ + int tx, ty, bx, by; + CTRPatch *localpatch; + CCMLandScape *owner; + CTRLandScape *localowner; + + // Set owning landscape + localowner = (CTRLandScape *)userdata; + owner = (CCMLandScape *)localowner->GetCommon(); + + // Get TRPatch pointer + tx = patch->GetHeightMapX(); + ty = patch->GetHeightMapY(); + bx = tx / owner->GetTerxels(); + by = ty / owner->GetTerxels(); + + localpatch = localowner->GetPatch(bx, by); + localpatch->Clear(); + + localpatch->SetCommon(patch); + localpatch->SetOwner(owner); + localpatch->SetLocalOwner(localowner); + localpatch->SetRenderMap(tx, ty); + localpatch->SetCenter(); +// localpatch->CalcNormal(); +} + +void CTRLandScape::CopyHeightMap(void) +{ + const CCMLandScape *common = GetCommon(); + const byte *heightMap = common->GetHeightMap(); + CTerVert *renderMap = mRenderMap; + int i; + + for(i = 0; i < common->GetRealArea(); i++) + { + renderMap->height = *heightMap; + renderMap++; + heightMap++; + } +} + +CTRLandScape::~CTRLandScape(void) +{ + if(mTRPatches) + { + Z_Free(mTRPatches); + mTRPatches = NULL; + } + if (mSortedPatches) + { + Z_Free(mSortedPatches); + mSortedPatches = 0; + } + if(mRenderMap) + { + Z_Free(mRenderMap); + mRenderMap = NULL; + } +} + +CCMLandScape *CM_RegisterTerrain(const char *config, bool server); + +qhandle_t R_GetShaderByNum(int shaderNum, world_t &worldData); + +CTRLandScape::CTRLandScape(const char *configstring) +{ +#ifndef PRE_RELEASE_DEMO + int shaderNum; + const CCMLandScape *common; + + memset(this, 0, sizeof(*this)); + + // Sets up the common aspects of the terrain + common = CM_RegisterTerrain(configstring, false); + SetCommon(common); + + tr.landScape.landscape = this; + + mTextureScale = (float)atof(Info_ValueForKey(configstring, "texturescale")) / common->GetTerxels(); + LoadTerrainDef(Info_ValueForKey(configstring, "terrainDef")); + + // To normalise the variance value to a reasonable number + mScalarSize = VectorLengthSquared(common->GetSize()); + + // Calculate and set variance depth + mMaxNode = (Q_log2(common->GetTerxels()) << 1) - 1; + + // Allocate space for the renderer specific data + mRenderMap = (CTerVert *)Z_Malloc(sizeof(CTerVert) * common->GetRealArea(), TAG_R_TERRAIN, qfalse); + + // Copy byte heightmap to rendermap to speed up calcs + CopyHeightMap(); + + // Calculate the real world location for each heightmap entry + CalculateRealCoords(); + + // Calculate the normal of each terxel + CalculateNormals(); + + // Calculate modulation values for the heightmap + CalculateLighting(); + + // Calculate texture coords (not projected - real) + CalculateTextureCoords(); + + Com_Printf ("R_Terrain: Creating renderer patches.....\n"); + // Initialise all terrain patches + mTRPatches = (CTRPatch *)Z_Malloc(sizeof(CTRPatch) * common->GetBlockCount(), TAG_R_TERRAIN, qfalse); + + mSortedCount = 2 * common->GetBlockCount(); + mSortedPatches = (TPatchInfo *)Z_Malloc(sizeof(TPatchInfo) * mSortedCount, TAG_R_TERRAIN, qfalse); + + CM_TerrainPatchIterate(common, InitRendererPatches, this); + + // Calculate shaders dependent on the .terrain file + CalculateShaders(); + + // Get the contents shader + shaderNum = atol(Info_ValueForKey(configstring, "shader")); + + mShader = R_GetShaderByHandle(R_GetShaderByNum(shaderNum, *tr.world)); + + mPatchSize = VectorLength(common->GetPatchSize()); + +#if _DEBUG + mCycleCount = 0; +#endif +#endif // PRE_RELEASE_DEMO +} + +// --------------------------------------------------------------------- + +void RB_SurfaceTerrain( surfaceInfo_t *surf ) +{ + srfTerrain_t *ls = (srfTerrain_t *)surf; + CTRLandScape *landscape = ls->landscape; + + TerrainFog = tr.world->globalFog; + + landscape->CalculateRegion(); + landscape->Reset(); +// landscape->Tessellate(); + landscape->Render(); +} + +void R_CalcTerrainVisBounds(CTRLandScape *landscape) +{ + const CCMLandScape *common = landscape->GetCommon(); + + // Set up the visbounds using terrain data + if ( common->GetMins()[0] < tr.viewParms.visBounds[0][0] ) + { + tr.viewParms.visBounds[0][0] = common->GetMins()[0]; + } + if ( common->GetMins()[1] < tr.viewParms.visBounds[0][1] ) + { + tr.viewParms.visBounds[0][1] = common->GetMins()[1]; + } + if ( common->GetMins()[2] < tr.viewParms.visBounds[0][2] ) + { + tr.viewParms.visBounds[0][2] = common->GetMins()[2]; + } + + if ( common->GetMaxs()[0] > tr.viewParms.visBounds[1][0] ) + { + tr.viewParms.visBounds[1][0] = common->GetMaxs()[0]; + } + if ( common->GetMaxs()[1] > tr.viewParms.visBounds[1][1] ) + { + tr.viewParms.visBounds[1][1] = common->GetMaxs()[1]; + } + if ( common->GetMaxs()[2] > tr.viewParms.visBounds[1][2] ) + { + tr.viewParms.visBounds[1][2] = common->GetMaxs()[2]; + } +} + +void R_AddTerrainSurfaces(void) +{ + CTRLandScape *landscape; + + if (!r_drawTerrain->integer || (tr.refdef.rdflags & RDF_NOWORLDMODEL)) + { + return; + } + + landscape = tr.landScape.landscape; + if(landscape) + { + R_AddDrawSurf( (surfaceType_t *)(&tr.landScape), landscape->GetShader(), 0, qfalse ); + R_CalcTerrainVisBounds(landscape); + } +} + +void RE_InitRendererTerrain( const char *info ) +{ + CTRLandScape *ls; + + if ( !info || !info[0] ) + { + Com_Printf( "RE_RegisterTerrain: NULL name\n" ); + return; + } + + Com_Printf("R_Terrain: Creating RENDERER data.....\n"); + + // Create and register a new landscape structure + ls = new CTRLandScape(info); +} + +void R_TerrainInit(void) +{ + int i; + + for(i = 0; i < MAX_TERRAINS; i++) + { + tr.landScape.surfaceType = SF_TERRAIN; + tr.landScape.landscape = NULL; + } + r_terrainTessellate = Cvar_Get("r_terrainTessellate", "3", CVAR_CHEAT); + r_drawTerrain = Cvar_Get("r_drawTerrain", "1", CVAR_CHEAT); + r_terrainWaterOffset = Cvar_Get("r_terrainWaterOffset", "0", 0); + r_count = Cvar_Get("r_count", "2", 0); +} + +void CM_ShutdownTerrain( thandle_t terrainId); + +void R_TerrainShutdown(void) +{ + CTRLandScape *ls; + + //Com_Printf("R_Terrain: Shutting down RENDERER terrain.....\n"); + ls = tr.landScape.landscape; + if(ls) + { + CM_ShutdownTerrain(0); + delete ls; + tr.landScape.landscape = NULL; + } +} + +// end diff --git a/code/renderer/tr_types.h b/code/renderer/tr_types.h new file mode 100644 index 0000000..663e79b --- /dev/null +++ b/code/renderer/tr_types.h @@ -0,0 +1,244 @@ +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + +#include "..\game\ghoul2_shared.h" + +#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces +#ifdef _XBOX +#define MAX_ENTITIES 1024 // 11 bits, can't be increased without changing drawsurf bit packing (QSORT_ENTITYNUM_SHIFT) +#else +#define MAX_ENTITIES 2048 // 11 bits, can't be increased without changing drawsurf bit packing (QSORT_ENTITYNUM_SHIFT) +#endif +#define TR_WORLDENT (MAX_ENTITIES-1) + +// renderfx flags +#define RF_MORELIGHT 0x00001 // allways have some light (viewmodel, some items) +#define RF_THIRD_PERSON 0x00002 // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON 0x00004 // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK 0x00008 // for view weapon Z crunching +#define RF_NODEPTH 0x00010 // No depth at all (seeing through walls) + +#define RF_VOLUMETRIC 0x00020 // fake volumetric shading + +#define RF_NOSHADOW 0x00040 // don't add stencil shadows + +#define RF_LIGHTING_ORIGIN 0x00080 // use refEntity->lightingOrigin instead of refEntity->origin + // for lighting. This allows entities to sink into the floor + // with their origin going solid, and allows all parts of a + // player to get the same lighting +#define RF_SHADOW_PLANE 0x00100 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 0x00200 // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count +#define RF_CAP_FRAMES 0x00400 // cap the model frames by the maxframes for one shot anims + +#define RF_ALPHA_FADE 0x00800 // hacks blend mode and uses whatever the set alpha is. +#define RF_PULSATE 0x01000 // for things like a dropped saber, where we want to add an extra visual clue +#define RF_RGB_TINT 0x02000 // overrides ent RGB color to the specified color + +#define RF_FORKED 0x04000 // override lightning to have forks +#define RF_TAPERED 0x08000 // lightning tapers +#define RF_GROW 0x10000 // lightning grows from start to end during its life + +#define RF_SETANIMINDEX 0x20000 //use backEnd.currentEntity->e.skinNum for R_BindAnimatedImage + +#define RF_DISINTEGRATE1 0x40000 // does a procedural hole-ripping thing. +#define RF_DISINTEGRATE2 0x80000 // does a procedural hole-ripping thing with scaling at the ripping point + +#define RF_G2MINLOD 0x100000 // force Lowest lod on g2 + +#define RF_SHADOW_ONLY 0x200000 //add surfs for shadowing but don't draw them normally -rww + +#define RF_DISTORTION 0x400000 //area distortion effect -rww + +// refdef flags +#define RDF_NOWORLDMODEL 1 // used for player configuration screen +#define RDF_HYPERSPACE 4 // teleportation effect + +#define RDF_SKYBOXPORTAL 8 +#define RDF_DRAWSKYBOX 16 // the above marks a scene as being a 'portal sky'. this flag says to draw it or not + +#define RDF_doLAGoggles 32 // Light Amp goggles +#define RDF_doFullbright 64 // Light Amp goggles +#define RDF_ForceSightOn 128 // using force sight + + +extern int skyboxportal; +extern int drawskyboxportal; + +typedef byte color4ub_t[4]; + +typedef struct { + vec3_t xyz; + float st[2]; + byte modulate[4]; +} polyVert_t; + +typedef struct poly_s { + qhandle_t hShader; + int numVerts; + polyVert_t *verts; +} poly_t; + +typedef enum +{ + RT_MODEL, + RT_POLY, + RT_SPRITE, + RT_ORIENTED_QUAD, + RT_LINE, + RT_ELECTRICITY, + RT_CYLINDER, + RT_LATHE, + RT_BEAM, + RT_SABER_GLOW, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + RT_CLOUDS, + + RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + +typedef struct { + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) + float shadowPlane; // projection shadows go here, stencils go slightly lower + + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + float origin[3]; // also used as MODEL_BEAM's "from" + int frame; // also used as MODEL_BEAM's diameter + + // previous data for frame interpolation + float oldorigin[3]; // also used as MODEL_BEAM's "to" + int oldframe; + float backlerp; // 0.0 = current, 1.0 = old + + // texturing + int skinNum; // inline skin index + + qhandle_t customSkin; // NULL for default skin + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by colorSrc=vertex shaders + float shaderTexCoord[2]; // texture coordinates used by tcMod=vertex modifiers + float shaderTime; // subtracted from refdef time to control effect start times + + // extra sprite information + float radius; + + // This doesn't have to be unioned, but it does make for more meaningful variable names :) + union + { + float rotation; + float endTime; + float saberLength; + }; + +/* +Ghoul2 Insert Start +*/ + vec3_t angles; // rotation angles - used for Ghoul2 + + vec3_t modelScale; // axis scale for models + CGhoul2Info_v *ghoul2; // has to be at the end of the ref-ent in order for it to be created properly +/* +Ghoul2 Insert End +*/ + +#ifdef _XBOX + int number; +#endif + +} refEntity_t; + + +#define MAX_RENDER_STRINGS 8 +#define MAX_RENDER_STRING_LENGTH 32 + +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + int viewContents; // world contents at vieworg + + // time in milliseconds for shader effects and other time dependent rendering issues + int time; + + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + + // text messages for deform text shaders +// char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; +} refdef_t; + + +typedef enum { + STEREO_CENTER, + STEREO_LEFT, + STEREO_RIGHT +} stereoFrame_t; + + +/* +** glconfig_t +** +** Contains variables specific to the OpenGL configuration +** being run right now. These are constant once the OpenGL +** subsystem is initialized. +*/ +typedef enum { + TC_NONE, + TC_S3TC, + TC_S3TC_DXT +} textureCompression_t; + +typedef struct { + const char *renderer_string; + const char *vendor_string; + const char *version_string; + const char *extensions_string; + + int maxTextureSize; // queried from GL + int maxActiveTextures; // multitexture ability + float maxTextureFilterAnisotropy; + + int colorBits, depthBits, stencilBits; + + qboolean deviceSupportsGamma; + textureCompression_t textureCompression; + qboolean textureEnvAddAvailable; + qboolean textureFilterAnisotropicAvailable; + qboolean clampToEdgeAvailable; + + int vidWidth, vidHeight; + + int displayFrequency; + + // synonymous with "does rendering consume the entire screen?", therefore + // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that + // used CDS. + qboolean isFullscreen; + qboolean stereoEnabled; +} glconfig_t; + + +#if !defined _WIN32 + +#define OPENGL_DRIVER_NAME "libGL.so" + +#else + +#define OPENGL_DRIVER_NAME "opengl32" + +#endif // !defined _WIN32 + + +#endif // __TR_TYPES_H diff --git a/code/renderer/tr_world.cpp b/code/renderer/tr_world.cpp new file mode 100644 index 0000000..3a3f8b9 --- /dev/null +++ b/code/renderer/tr_world.cpp @@ -0,0 +1,1021 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "tr_local.h" + +#ifdef VV_LIGHTING +#include "tr_lightmanager.h" +#endif + +#ifdef _XBOX +#include "../qcommon/sparc.h" +#endif + +static bool lookingForWorstLeaf = false; + +#ifdef _XBOX +static bool GetCoordsForLeaf(int leafNum, vec3_t coords) +{ + srfSurfaceFace_t *face; + msurface_t *surf; + int i; + + for(i=0; ileafs[leafNum].nummarksurfaces; i++) { + surf = *(tr.world->marksurfaces + + tr.world->leafs[leafNum].firstMarkSurfNum + i); + + if(!surf->data || *surf->data != SF_FACE) { + continue; + } + + face = (srfSurfaceFace_t*)surf->data; + Q_CastShort2Float(&coords[0], (short*)(face->srfPoints + 0)); + Q_CastShort2Float(&coords[1], (short*)(face->srfPoints + 1)); + Q_CastShort2Float(&coords[2], (short*)(face->srfPoints + 2)); + return true; + } + + return false; +} +#endif + +/* +================= +R_CullTriSurf + +Returns true if the grid is completely culled away. +Also sets the clipped hint bit in tess +================= +*/ +static qboolean R_CullTriSurf( srfTriangles_t *cv ) { + int boxCull; + + boxCull = R_CullLocalBox( cv->bounds ); + + if ( boxCull == CULL_OUT ) { + return qtrue; + } + return qfalse; +} + +/* +================= +R_CullGrid + +Returns true if the grid is completely culled away. +Also sets the clipped hint bit in tess +================= +*/ +static qboolean R_CullGrid( srfGridMesh_t *cv ) { + int boxCull; + int sphereCull; + + if ( r_nocurves->integer ) { + return qtrue; + } + + if ( tr.currentEntityNum != TR_WORLDENT ) { + sphereCull = R_CullLocalPointAndRadius( cv->localOrigin, cv->meshRadius ); + } else { + sphereCull = R_CullPointAndRadius( cv->localOrigin, cv->meshRadius ); + } + boxCull = CULL_OUT; + + // check for trivial reject + if ( sphereCull == CULL_OUT ) + { + tr.pc.c_sphere_cull_patch_out++; + return qtrue; + } + // check bounding box if necessary + else if ( sphereCull == CULL_CLIP ) + { + tr.pc.c_sphere_cull_patch_clip++; + + boxCull = R_CullLocalBox( cv->meshBounds ); + + if ( boxCull == CULL_OUT ) + { + tr.pc.c_box_cull_patch_out++; + return qtrue; + } + else if ( boxCull == CULL_IN ) + { + tr.pc.c_box_cull_patch_in++; + } + else + { + tr.pc.c_box_cull_patch_clip++; + } + } + else + { + tr.pc.c_sphere_cull_patch_in++; + } + + return qfalse; +} + + +/* +================ +R_CullSurface + +Tries to back face cull surfaces before they are lighted or +added to the sorting list. + +This will also allow mirrors on both sides of a model without recursion. +================ +*/ +static qboolean R_CullSurface( surfaceType_t *surface, shader_t *shader ) { + srfSurfaceFace_t *sface; + float d; + + if ( r_nocull->integer==1 ) { + return qfalse; + } + + if ( *surface == SF_GRID ) { + return R_CullGrid( (srfGridMesh_t *)surface ); + } + + if ( *surface == SF_TRIANGLES ) { + return R_CullTriSurf( (srfTriangles_t *)surface ); + } + + if ( *surface != SF_FACE ) { + return qfalse; + } + + if ( shader->cullType == CT_TWO_SIDED ) { + return qfalse; + } + + // face culling + if ( !r_facePlaneCull->integer ) { + return qfalse; + } + + sface = ( srfSurfaceFace_t * ) surface; + d = DotProduct (tr.or.viewOrigin, sface->plane.normal); + + // don't cull exactly on the plane, because there are levels of rounding + // through the BSP, ICD, and hardware that may cause pixel gaps if an + // epsilon isn't allowed here + if ( shader->cullType == CT_FRONT_SIDED ) { + if ( d < sface->plane.dist - 8 ) { + return qtrue; + } + } else { + if ( d > sface->plane.dist + 8 ) { + return qtrue; + } + } + + return qfalse; +} + + +#ifndef VV_LIGHTING +static int R_DlightFace( srfSurfaceFace_t *face, int dlightBits ) { + float d; + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + d = DotProduct( dl->origin, face->plane.normal ) - face->plane.dist; + if ( !VectorCompare(face->plane.normal, vec3_origin) && (d < -dl->radius || d > dl->radius) ) { + // dlight doesn't reach the plane + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + face->dlightBits = dlightBits; + return dlightBits; +} +#endif // VV_LIGHTING + +#ifndef VV_LIGHTING +static int R_DlightGrid( srfGridMesh_t *grid, int dlightBits ) { + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits = dlightBits; + return dlightBits; +} +#endif // VV_LIGHTING + + +#ifndef VV_LIGHTING +static int R_DlightTrisurf( srfTriangles_t *surf, int dlightBits ) { + // FIXME: more dlight culling to trisurfs... + surf->dlightBits = dlightBits; + return dlightBits; +#if 0 + int i; + dlight_t *dl; + + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) { + if ( ! ( dlightBits & ( 1 << i ) ) ) { + continue; + } + dl = &tr.refdef.dlights[i]; + if ( dl->origin[0] - dl->radius > grid->meshBounds[1][0] + || dl->origin[0] + dl->radius < grid->meshBounds[0][0] + || dl->origin[1] - dl->radius > grid->meshBounds[1][1] + || dl->origin[1] + dl->radius < grid->meshBounds[0][1] + || dl->origin[2] - dl->radius > grid->meshBounds[1][2] + || dl->origin[2] + dl->radius < grid->meshBounds[0][2] ) { + // dlight doesn't reach the bounds + dlightBits &= ~( 1 << i ); + } + } + + if ( !dlightBits ) { + tr.pc.c_dlightSurfacesCulled++; + } + + grid->dlightBits = dlightBits; + return dlightBits; +#endif +} +#endif // VV_LIGHTING + +/* +==================== +R_DlightSurface + +The given surface is going to be drawn, and it touches a leaf +that is touched by one or more dlights, so try to throw out +more dlights if possible. +==================== +*/ +#ifndef VV_LIGHTING +static int R_DlightSurface( msurface_t *surf, int dlightBits ) { + if ( *surf->data == SF_FACE ) { + dlightBits = R_DlightFace( (srfSurfaceFace_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_GRID ) { + dlightBits = R_DlightGrid( (srfGridMesh_t *)surf->data, dlightBits ); + } else if ( *surf->data == SF_TRIANGLES ) { + dlightBits = R_DlightTrisurf( (srfTriangles_t *)surf->data, dlightBits ); + } else { + dlightBits = 0; + } + + if ( dlightBits ) { + tr.pc.c_dlightSurfaces++; + } + + return dlightBits; +} +#endif // VV_LIGHTING + + + +/* +====================== +R_AddWorldSurface +====================== +*/ +#ifdef VV_LIGHTING +void R_AddWorldSurface( msurface_t *surf, int dlightBits, qboolean noViewCount ) { +#else +static void R_AddWorldSurface( msurface_t *surf, int dlightBits, qboolean noViewCount = qfalse ) { +#endif + /* + if ( surf->viewCount == tr.viewCount ) { + return; // already in this view + } + */ + + //rww - changed this to be like sof2mp's so RMG will look right. + //Will this affect anything that is non-rmg? + + if (!noViewCount) + { + if ( surf->viewCount == tr.viewCount ) + { + // already in this view, but lets make sure all the dlight bits are set + if ( *surf->data == SF_FACE ) + { + ((srfSurfaceFace_t *)surf->data)->dlightBits |= dlightBits; + } + else if ( *surf->data == SF_GRID ) + { + ((srfGridMesh_t *)surf->data)->dlightBits |= dlightBits; + } + else if ( *surf->data == SF_TRIANGLES ) + { + ((srfTriangles_t *)surf->data)->dlightBits |= dlightBits; + } + return; + } + surf->viewCount = tr.viewCount; + // FIXME: bmodel fog? + } + +// surf->viewCount = tr.viewCount; + // FIXME: bmodel fog? + + // try to cull before dlighting or adding + if ( R_CullSurface( surf->data, surf->shader ) ) { + return; + } + + // check for dlighting + if ( dlightBits ) { +#ifdef VV_LIGHTING + dlightBits = VVLightMan.R_DlightSurface( surf, dlightBits ); +#else + dlightBits = R_DlightSurface( surf, dlightBits ); +#endif + dlightBits = ( dlightBits != 0 ); + } + + R_AddDrawSurf( surf->data, surf->shader, surf->fogIndex, dlightBits ); +} + +/* +============================================================= + + BRUSH MODELS + +============================================================= +*/ + +/* +================= +R_AddBrushModelSurfaces +================= +*/ +void R_AddBrushModelSurfaces ( trRefEntity_t *ent ) { + bmodel_t *bmodel; + int clip; + model_t *pModel; + int i; + + pModel = R_GetModelByHandle( ent->e.hModel ); + + bmodel = pModel->bmodel; + + clip = R_CullLocalBox( bmodel->bounds ); + if ( clip == CULL_OUT ) { + return; + } + + if(pModel->bspInstance) + { +#ifdef VV_LIGHTING + VVLightMan.R_SetupEntityLighting(&tr.refdef, ent); +#else + R_SetupEntityLighting(&tr.refdef, ent); +#endif + } + +#ifdef VV_LIGHTING + VVLightMan.R_DlightBmodel( bmodel, qfalse ); +#else + R_DlightBmodel( bmodel, qfalse ); +#endif + + for ( i = 0 ; i < bmodel->numSurfaces ; i++ ) { + R_AddWorldSurface( bmodel->firstSurface + i, tr.currentEntity->dlightBits, qtrue ); + } +} + +float GetQuadArea( vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4 ) +{ + vec3_t vec1, vec2, dis1, dis2; + + // Get area of tri1 + VectorSubtract( v1, v2, vec1 ); + VectorSubtract( v1, v4, vec2 ); + CrossProduct( vec1, vec2, dis1 ); + VectorScale( dis1, 0.25f, dis1 ); + + // Get area of tri2 + VectorSubtract( v3, v2, vec1 ); + VectorSubtract( v3, v4, vec2 ); + CrossProduct( vec1, vec2, dis2 ); + VectorScale( dis2, 0.25f, dis2 ); + + // Return addition of disSqr of each tri area + return ( dis1[0] * dis1[0] + dis1[1] * dis1[1] + dis1[2] * dis1[2] + + dis2[0] * dis2[0] + dis2[1] * dis2[1] + dis2[2] * dis2[2] ); +} + +#ifdef _XBOX +float GetQuadArea( unsigned short v1[3], unsigned short v2[3], unsigned short v3[3], unsigned short v4[3]) +{ + vec3_t fv1; + vec3_t fv2; + vec3_t fv3; + vec3_t fv4; + + for(int i=0; i<3; i++) { + Q_CastShort2Float(&fv1[i], (short*)&v1[i]); + Q_CastShort2Float(&fv2[i], (short*)&v2[i]); + Q_CastShort2Float(&fv3[i], (short*)&v3[i]); + Q_CastShort2Float(&fv4[i], (short*)&v4[i]); + } + + return GetQuadArea(fv1, fv2, fv3, fv4); +} +#endif + +void RE_GetBModelVerts( int bmodelIndex, vec3_t *verts, vec3_t normal ) +{ + msurface_t *surfs; + srfSurfaceFace_t *face; + bmodel_t *bmodel; + model_t *pModel; + int i; + // Not sure if we really need to track the best two candidates + int maxDist[2]={0,0}; + int maxIndx[2]={0,0}; + int dist = 0; + float dot1, dot2; + + pModel = R_GetModelByHandle( bmodelIndex ); + bmodel = pModel->bmodel; + + // Loop through all surfaces on the brush and find the best two candidates + for ( i = 0 ; i < bmodel->numSurfaces; i++ ) + { + surfs = bmodel->firstSurface + i; + face = ( srfSurfaceFace_t *)surfs->data; + + // It seems that the safest way to handle this is by finding the area of the faces +#ifdef _XBOX + int nextSurfPoint = NEXT_SURFPOINT(face->flags); + dist = GetQuadArea( face->srfPoints, face->srfPoints + nextSurfPoint, + face->srfPoints + nextSurfPoint * 2, face->srfPoints + + nextSurfPoint * 3 ); +#else + dist = GetQuadArea( face->points[0], face->points[1], face->points[2], face->points[3] ); +#endif + + // Check against the highest max + if ( dist > maxDist[0] ) + { + // Shuffle our current maxes down + maxDist[1] = maxDist[0]; + maxIndx[1] = maxIndx[0]; + + maxDist[0] = dist; + maxIndx[0] = i; + } + // Check against the second highest max + else if ( dist >= maxDist[1] ) + { + // just stomp the old + maxDist[1] = dist; + maxIndx[1] = i; + } + } + + // Hopefully we've found two best case candidates. Now we should see which of these faces the viewer + surfs = bmodel->firstSurface + maxIndx[0]; + face = ( srfSurfaceFace_t *)surfs->data; + dot1 = DotProduct( face->plane.normal, tr.refdef.viewaxis[0] ); + + surfs = bmodel->firstSurface + maxIndx[1]; + face = ( srfSurfaceFace_t *)surfs->data; + dot2 = DotProduct( face->plane.normal, tr.refdef.viewaxis[0] ); + + if ( dot2 < dot1 && dot2 < 0.0f ) + { + i = maxIndx[1]; // use the second face + } + else if ( dot1 < dot2 && dot1 < 0.0f ) + { + i = maxIndx[0]; // use the first face + } + else + { // Possibly only have one face, so may as well use the first face, which also should be the best one + //i = rand() & 1; // ugh, we don't know which to use. I'd hope this would never happen + i = maxIndx[0]; // use the first face + } + + surfs = bmodel->firstSurface + i; + face = ( srfSurfaceFace_t *)surfs->data; + +#ifdef _XBOX + int nextSurfPoint = NEXT_SURFPOINT(face->flags); + for ( int t = 0; t < 4; t++ ) + { + Q_CastShort2Float(&verts[t][0], (short*)(face->srfPoints + nextSurfPoint * t + 0)); + Q_CastShort2Float(&verts[t][1], (short*)(face->srfPoints + nextSurfPoint * t + 1)); + Q_CastShort2Float(&verts[t][2], (short*)(face->srfPoints + nextSurfPoint * t + 2)); + } +#else + for ( int t = 0; t < 4; t++ ) + { + VectorCopy( face->points[t], verts[t] ); + } +#endif +} + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ + + +/* +================ +R_RecursiveWorldNode +================ +*/ +#ifndef VV_LIGHTING +static void R_RecursiveWorldNode( mnode_t *node, int planeBits, int dlightBits ) { + + do { + int newDlights[2]; + + // if the node wasn't marked as potentially visible, exit + if (node->visframe != tr.visCount) { + return; + } + + // if the bounding volume is outside the frustum, nothing + // inside can be visible OPTIMIZE: don't do this all the way to leafs? + + if ( r_nocull->integer!=1 ) { + int r; + + if ( planeBits & 1 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~1; // all descendants will also be in front + } + } + + if ( planeBits & 2 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~2; // all descendants will also be in front + } + } + + if ( planeBits & 4 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~4; // all descendants will also be in front + } + } + + if ( planeBits & 8 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~8; // all descendants will also be in front + } + } + + if ( planeBits & 16 ) { + r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[4]); + if (r == 2) { + return; // culled + } + if ( r == 1 ) { + planeBits &= ~16; // all descendants will also be in front + } + } + + } + + if ( node->contents != -1 ) { + break; + } + + // determine which dlights are needed + if ( r_nocull->integer!=2 ) + { + newDlights[0] = 0; + newDlights[1] = 0; + if ( dlightBits ) + { + int i; + for ( i = 0 ; i < tr.refdef.num_dlights ; i++ ) + { + dlight_t *dl; + float dist; + + if ( dlightBits & ( 1 << i ) ) { + dl = &tr.refdef.dlights[i]; + dist = DotProduct( dl->origin, node->plane->normal ) - node->plane->dist; + + if ( dist > -dl->radius ) { + newDlights[0] |= ( 1 << i ); + } + if ( dist < dl->radius ) { + newDlights[1] |= ( 1 << i ); + } + } + } + } + } + else + { + newDlights[0] = dlightBits; + newDlights[1] = dlightBits; + } + // recurse down the children, front side first + R_RecursiveWorldNode (node->children[0], planeBits, newDlights[0] ); + + // tail recurse + node = node->children[1]; + dlightBits = newDlights[1]; + } while ( 1 ); + + { + // leaf node, so add mark surfaces + int c; + msurface_t *surf, **mark; + + tr.pc.c_leafs++; + + // add to z buffer bounds + if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) { + tr.viewParms.visBounds[0][0] = node->mins[0]; + } + if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) { + tr.viewParms.visBounds[0][1] = node->mins[1]; + } + if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) { + tr.viewParms.visBounds[0][2] = node->mins[2]; + } + + if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) { + tr.viewParms.visBounds[1][0] = node->maxs[0]; + } + if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) { + tr.viewParms.visBounds[1][1] = node->maxs[1]; + } + if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) { + tr.viewParms.visBounds[1][2] = node->maxs[2]; + } + + // add the individual surfaces + mark = node->firstmarksurface; + c = node->nummarksurfaces; + while (c--) { + // the surface may have already been added if it + // spans multiple leafs + surf = *mark; + R_AddWorldSurface( surf, dlightBits ); + mark++; + } + } + +} +#endif // VV_LIGHTING + + +/* +=============== +R_PointInLeaf +=============== +*/ +static mnode_t *R_PointInLeaf( vec3_t p ) { + mnode_t *node; + float d; + cplane_t *plane; + + if ( !tr.world ) { + Com_Error (ERR_DROP, "R_PointInLeaf: bad model"); + } + + node = tr.world->nodes; + while( 1 ) { + if (node->contents != -1) { + break; + } +#ifdef _XBOX + plane = tr.world->planes + node->planeNum; +#else + plane = node->plane; +#endif + d = DotProduct (p,plane->normal) - plane->dist; + if (d > 0) { + node = node->children[0]; + } else { + node = node->children[1]; + } + } + + return node; +} + +/* +============== +R_ClusterPVS +============== +*/ +static const byte *R_ClusterPVS (int cluster) { + if (!tr.world || !tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters ) { + return tr.world->novis; + } + +#ifdef _XBOX + return tr.world->vis->Decompress(cluster * tr.world->clusterBytes, + tr.world->numClusters); +#else + return tr.world->vis + cluster * tr.world->clusterBytes; +#endif +} + +/* +================= +R_inPVS +================= +*/ +#ifdef _XBOX +qboolean R_inPVS( vec3_t p1, vec3_t p2 ) { + mleaf_s *leaf; + byte *vis; + + leaf = (mleaf_s*)R_PointInLeaf( p1 ); + vis = (byte*)CM_ClusterPVS( leaf->cluster ); + leaf = (mleaf_s*)R_PointInLeaf( p2 ); + + if ( !vis || (!(vis[leaf->cluster>>3] & (1<<(leaf->cluster&7)))) ) { + return qfalse; + } + return qtrue; +} +#else // _XBOX + +qboolean R_inPVS( vec3_t p1, vec3_t p2 ) { + mnode_t *leaf; + byte *vis; + + leaf = R_PointInLeaf( p1 ); + vis = CM_ClusterPVS( leaf->cluster ); + leaf = R_PointInLeaf( p2 ); + + if ( !(vis[leaf->cluster>>3] & (1<<(leaf->cluster&7))) ) { + return qfalse; + } + return qtrue; +} +#endif // _XBOX + +/* +=============== +R_MarkLeaves + +Mark the leaves and nodes that are in the PVS for the current +cluster +=============== +*/ +#ifdef _XBOX +void R_MarkLeaves (mleaf_s *leafOverride) { + const byte *vis; + mleaf_s *leaf; + mnode_s *parent; + int i; + int cluster; + + // lockpvs lets designers walk around to determine the + // extent of the current pvs + if ( r_lockpvs->integer ) { + return; + } + + // current viewcluster + if(!leafOverride) { + leaf = (mleaf_s*)R_PointInLeaf( tr.viewParms.pvsOrigin ); + } else { + leaf = leafOverride; + } + cluster = leaf->cluster; + + assert(leaf->contents != -1); + + // if the cluster is the same and the area visibility matrix + // hasn't changed, we don't need to mark everything again + + if ( tr.viewCluster == cluster && !tr.refdef.areamaskModified ) { + return; + } + + tr.visCount++; + tr.viewCluster = cluster; + + if ( r_novis->integer || tr.viewCluster == -1 ) { + for (i=0 ; inumnodes ; i++) { + if (tr.world->nodes[i].contents != CONTENTS_SOLID) { + tr.world->nodes[i].visframe = tr.visCount; + } + } + return; + } + + vis = R_ClusterPVS (tr.viewCluster); + + for (i=0,leaf=tr.world->leafs ; inumleafs ; i++, leaf++) { + cluster = leaf->cluster; + if ( cluster < 0 || cluster >= tr.world->numClusters ) { + continue; + } + + // check general pvs + if ( !(vis[cluster>>3] & (1<<(cluster&7))) ) { + continue; + } + + // check for door connection + if (!lookingForWorstLeaf && + (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) { + continue; // not visible + } + + parent = (mnode_t*)leaf; + assert(leaf->contents != -1); + do { + if (parent->visframe == tr.visCount) + break; + parent->visframe = tr.visCount; + parent = parent->parent; + } while (parent); + } +} +#else // _XBOX + +static void R_MarkLeaves (void) { + const byte *vis; + mnode_t *leaf, *parent; + int i; + int cluster; + + // lockpvs lets designers walk around to determine the + // extent of the current pvs + if ( r_lockpvs->integer ) { + return; + } + + // current viewcluster + leaf = R_PointInLeaf( tr.viewParms.pvsOrigin ); + cluster = leaf->cluster; + + // if the cluster is the same and the area visibility matrix + // hasn't changed, we don't need to mark everything again + + // if r_showcluster was just turned on, remark everything + if ( tr.viewCluster == cluster && !tr.refdef.areamaskModified + && !r_showcluster->modified ) { + return; + } + + if ( r_showcluster->modified || r_showcluster->integer ) { + r_showcluster->modified = qfalse; + if ( r_showcluster->integer ) { + VID_Printf( PRINT_ALL, "cluster:%i area:%i\n", cluster, leaf->area ); + } + } + + tr.visCount++; + tr.viewCluster = cluster; + + if ( r_novis->integer || tr.viewCluster == -1 ) { + for (i=0 ; inumnodes ; i++) { + if (tr.world->nodes[i].contents != CONTENTS_SOLID) { + tr.world->nodes[i].visframe = tr.visCount; + } + } + return; + } + + vis = R_ClusterPVS (tr.viewCluster); + + for (i=0,leaf=tr.world->nodes ; inumnodes ; i++, leaf++) { + cluster = leaf->cluster; + if ( cluster < 0 || cluster >= tr.world->numClusters ) { + continue; + } + + // check general pvs + if ( !(vis[cluster>>3] & (1<<(cluster&7))) ) { + continue; + } + + // check for door connection + if ( (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) { + continue; // not visible + } + + parent = leaf; + do { + if (parent->visframe == tr.visCount) + break; + parent->visframe = tr.visCount; + parent = parent->parent; + } while (parent); + } +} +#endif + + +/* +============= +R_AddWorldSurfaces +============= +*/ +#ifdef _XBOX +void R_AddWorldSurfaces (void) { + if ( !r_drawworld->integer ) { + return; + } + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } + + tr.currentEntityNum = TR_WORLDENT;//ENTITYNUM_WORLD; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + // clear out the visible min/max + ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] ); + + // perform frustum culling and add all the potentially visible surfaces + if ( VVLightMan.num_dlights > MAX_DLIGHTS ) { + VVLightMan.num_dlights = MAX_DLIGHTS ; + } + + VVLightMan.R_RecursiveWorldNode( tr.world->nodes, 15, ( 1 << VVLightMan.num_dlights ) - 1 ); +} + +#else // _XBOX + +void R_AddWorldSurfaces (void) { + if ( !r_drawworld->integer ) { + return; + } + + if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { + return; + } + + tr.currentEntityNum = TR_WORLDENT; + tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; + + // determine which leaves are in the PVS / areamask + R_MarkLeaves (); + + // clear out the visible min/max + ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] ); + + // perform frustum culling and add all the potentially visible surfaces + if ( tr.refdef.num_dlights > 32 ) { + tr.refdef.num_dlights = 32 ; + } + + R_RecursiveWorldNode( tr.world->nodes, 31, ( 1 << tr.refdef.num_dlights ) - 1 ); +} + +#endif // _XBOX diff --git a/code/rufl/hfile.cpp b/code/rufl/hfile.cpp new file mode 100644 index 0000000..9678336 --- /dev/null +++ b/code/rufl/hfile.cpp @@ -0,0 +1,378 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD USEFUL FUNCTION LIBRARY +// (c) 2002 Activision +// +// +// Handle File +// ----------- +// +//////////////////////////////////////////////////////////////////////////////////////// + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#include "hfile.h" +#if !defined(RATL_HANDLE_POOL_VS_INC) + #include "..\Ratl\handle_pool_vs.h" +#endif +#if !defined(RATL_VECTOR_VS_INC) + #include "..\Ratl\vector_vs.h" +#endif +#if !defined(RUFL_HSTRING_INC) + #include "hstring.h" +#endif + + + + +extern bool HFILEopen_read(int& handle, const char* filepath); +extern bool HFILEopen_write(int& handle, const char* filepath); +extern bool HFILEread(int& handle, void* data, int size); +extern bool HFILEwrite(int& handle, const void* data, int size); +extern bool HFILEclose(int& handle); + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Defines +//////////////////////////////////////////////////////////////////////////////////////// +#define MAX_OPEN_FILES 20 + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +struct SOpenFile +{ +#ifdef _XBOX + dllNamespace::hstring mPath; +#else + hstring mPath; +#endif + bool mForRead; + int mHandle; + float mVersion; + int mChecksum; +}; +typedef ratl::handle_pool_vs TFilePool; + +TFilePool& Pool() +{ + static TFilePool TFP; + return TFP; +} + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Constructor +// +// Allocates a new OpenFile structure and initializes it. DOES NOT OPEN! +// +//////////////////////////////////////////////////////////////////////////////////////// +hfile::hfile(const char* file) +{ + if (Pool().full()) + { + mHandle = 0; + assert("HFILE: Too Many Files Open, Unable To Grab An Unused Handle"==0); + return; + } + + mHandle = Pool().alloc(); + + SOpenFile& sfile = Pool()[mHandle]; + + sfile.mPath = file; + sfile.mHandle = 0; + sfile.mForRead = true; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Destructor +// +// Releases the open file structure for resue. Also closes the file if open. +// +//////////////////////////////////////////////////////////////////////////////////////// +hfile::~hfile() +{ + if (is_open()) + { + close(); + } + + if (mHandle && Pool().is_used(mHandle)) + { + Pool().free(mHandle); + } + mHandle = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool hfile::is_open(void) const +{ + if (mHandle && Pool().is_used(mHandle)) + { + return (Pool()[mHandle].mHandle!=0); + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool hfile::is_open_for_read(void) const +{ + if (mHandle && Pool().is_used(mHandle)) + { + SOpenFile& sfile = Pool()[mHandle]; + return (sfile.mHandle!=0 && sfile.mForRead); + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +bool hfile::is_open_for_write(void) const +{ + if (mHandle && Pool().is_used(mHandle)) + { + SOpenFile& sfile = Pool()[mHandle]; + return (sfile.mHandle!=0 && !sfile.mForRead); + } + return false; +} + + +//////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////// +bool hfile::open(float version, int checksum, bool read) +{ + // Make Sure This Is A Valid Handle + //---------------------------------- + if (!mHandle || !Pool().is_used(mHandle)) + { + assert("HFILE: Invalid Handle"==0); + return false; + } + + // Make Sure The File Is Not ALREADY Open + //---------------------------------------- + SOpenFile& sfile = Pool()[mHandle]; + if (sfile.mHandle!=0) + { + assert("HFILE: Attempt To Open An Already Open File"==0); + return false; + } + + sfile.mForRead = read; + if (read) + { + HFILEopen_read(sfile.mHandle, *sfile.mPath); + } + else + { + HFILEopen_write(sfile.mHandle, *sfile.mPath); + } + + // If The Open Failed, Report It And Free The SOpenFile + //------------------------------------------------------ + if (sfile.mHandle==0) + { + if (!read) + { + assert("HFILE: Unable To Open File"==0); + } + return false; + } + + + // Read The File's Header + //------------------------ + if (read) + { + if (!HFILEread(sfile.mHandle, &sfile.mVersion, sizeof(sfile.mVersion))) + { + assert("HFILE: Unable To Read File Header"==0); + close(); + return false; + } + if (!HFILEread(sfile.mHandle, &sfile.mChecksum, sizeof(sfile.mChecksum))) + { + assert("HFILE: Unable To Read File Header"==0); + close(); + return false; + } + + // Make Sure The Checksum & Version Match + //---------------------------------------- + if (sfile.mVersion!=version || sfile.mChecksum!=checksum) + { + close(); + return false; // Failed To Match Checksum Or Version Number -> Old Data + } + } + else + { + sfile.mVersion = version; + sfile.mChecksum = checksum; + + if (!HFILEwrite(sfile.mHandle, &sfile.mVersion, sizeof(sfile.mVersion))) + { + assert("HFILE: Unable To Write File Header"==0); + close(); + return false; + } + if (!HFILEwrite(sfile.mHandle, &sfile.mChecksum, sizeof(sfile.mChecksum))) + { + assert("HFILE: Unable To Write File Header"==0); + close(); + return false; + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////// +bool hfile::close() +{ + if (!mHandle || !Pool().is_used(mHandle)) + { + assert("HFILE: Invalid Handle"==0); + return false; + } + + SOpenFile& sfile = Pool()[mHandle]; + if (sfile.mHandle==0) + { + assert("HFILE: Unable TO Close Unopened File"==0); + return false; + } + + if (!HFILEclose(sfile.mHandle)) + { + sfile.mHandle = 0; + assert("HFILE: Unable To Close File"==0); + return false; + } + sfile.mHandle = 0; + + return true; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Searches for the first block with the matching data size, and reads it in. +//////////////////////////////////////////////////////////////////////////////////// +bool hfile::load(void* data, int datasize) +{ + // Go Ahead And Open The File For Reading + //---------------------------------------- + bool auto_opened = false; + if (!is_open()) + { + if (!open_read()) + { + return false; + } + auto_opened = true; + } + + // Make Sure That The File Is Readable + //------------------------------------- + SOpenFile& sfile = Pool()[mHandle]; + if (!sfile.mForRead) + { + assert("HFILE: Unable to load from a file that is opened for save"==0); + if (auto_opened) + { + close(); + } + return false; + } + + + // Now Read It + //------------- + if (!HFILEread(sfile.mHandle, data, datasize)) + { + assert("HFILE: Unable To Read Object"==0); + if (auto_opened) + { + close(); + } + return false; + } + + // Success! + //---------- + if (auto_opened) + { + close(); + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////// +bool hfile::save(void* data, int datasize) +{ + // Go Ahead And Open The File For Reading + //---------------------------------------- + bool auto_opened = false; + if (!is_open()) + { + if (!open_write()) + { + return false; + } + auto_opened = true; + } + + // Make Sure That The File Is Readable + //------------------------------------- + SOpenFile& sfile = Pool()[mHandle]; + if (sfile.mForRead) + { + assert("HFILE: Unable to save to a file that is opened for read"==0); + if (auto_opened) + { + close(); + } + return false; + } + + + // Write The Actual Object + //------------------------- + if (!HFILEwrite(sfile.mHandle, data, datasize)) + { + assert("HFILE: Unable To Write File Data"==0); + if (auto_opened) + { + close(); + } + return false; + } + + if (auto_opened) + { + close(); + } + return true; +} diff --git a/code/rufl/hfile.h b/code/rufl/hfile.h new file mode 100644 index 0000000..f8173a7 --- /dev/null +++ b/code/rufl/hfile.h @@ -0,0 +1,61 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD USEFUL FUNCTION LIBRARY +// (c) 2002 Activision +// +// +// Handle File +// ----------- +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RUFL_HFILE_INC) +#define RUFL_HFILE_INC + + +//////////////////////////////////////////////////////////////////////////////////////// +// HFile Bindings +// +// These are the standard C hfile bindings, copy these function wrappers to your .cpp +// before including hfile, and modify them if needed to support a different file +// system. +//////////////////////////////////////////////////////////////////////////////////////// +//bool HFILEopen_read(int& handle, const char* filepath) {handle=(int)fopen(filepath, "rb"); return (handle!=0);} +//bool HFILEopen_write(int& handle, const char* filepath) {handle=(int)fopen(filepath, "wb"); return (handle!=0);} +//bool HFILEread(int& handle, void* data, int size) {return (fread(data, size, 1, (FILE*)(handle))>0);} +//bool HFILEwrite(int& handle, const void* data, int size) {return (fwrite(data, size, 1, (FILE*)(handle))>0);} +//bool HFILEclose(int& handle) {return (fclose((FILE*)handle)==0);} + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Handle String Class +//////////////////////////////////////////////////////////////////////////////////////// +class hfile +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructors + //////////////////////////////////////////////////////////////////////////////////// + hfile(const char *file); + ~hfile(); + + bool load(void* data, int datasize); + bool save(void* data, int datasize); + + bool is_open(void) const; + bool is_open_for_read(void) const; + bool is_open_for_write(void) const; + + bool open_read(float version=1.0f, int checksum=0) {return open(version, checksum, true);} + bool open_write(float version=1.0f, int checksum=0) {return open(version, checksum, false);} + + bool close(); + +private: + bool open(float version, int checksum, bool read); + + + int mHandle; +}; + + +#endif // hfile_H \ No newline at end of file diff --git a/code/rufl/hstring.cpp b/code/rufl/hstring.cpp new file mode 100644 index 0000000..3ba7bd9 --- /dev/null +++ b/code/rufl/hstring.cpp @@ -0,0 +1,192 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD USEFUL FUNCTION LIBRARY +// (c) 2002 Activision +// +// +// Handle String +// ------------- +// Handle strings are allocated once in a static buffer (with a hash index), and are +// never cleared out. You should use these for very common string names which are +// redundant or intended to last a long time. +// +// Handle strings are also good for comparison and storage because they compare only +// the handles, which are simple unique integers. +// +//////////////////////////////////////////////////////////////////////////////////////// + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Includes +//////////////////////////////////////////////////////////////////////////////////////// +#include "hstring.h" +#include +#include "..\Ratl\hash_pool_vs.h" + + + +//////////////////////////////////////////////////////////////////////////////////////// +// Defines +//////////////////////////////////////////////////////////////////////////////////////// +#define MAX_HASH 16384 // How Many Hash +#define BLOCK_SIZE 65536 // Size of a string storage block in bytes. + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////// +// The Hash Pool +//////////////////////////////////////////////////////////////////////////////////////// +typedef ratl::hash_pool TStrPool; + + +TStrPool& Pool() +{ + static TStrPool TSP; + return TSP; +} + + + + +#ifdef _XBOX + +// Utility for clearing the pool between levels +void ClearHStringPool( void ) +{ + Pool().clear(); +} + +namespace dllNamespace +{ +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// Constructor +//////////////////////////////////////////////////////////////////////////////////////// +hstring::hstring() +{ + mHandle = 0; +#ifdef _DEBUG + mStr = 0; +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Constructor +//////////////////////////////////////////////////////////////////////////////////////// +hstring::hstring(const char *str) +{ + init(str); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Constructor +//////////////////////////////////////////////////////////////////////////////////////// +hstring::hstring(const hstring &str) +{ + mHandle = str.mHandle; + +#ifdef _DEBUG + mStr = str.mStr; +#endif +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// Assignment +//////////////////////////////////////////////////////////////////////////////////////// +hstring& hstring::operator= (const char *str) +{ + init(str); + return *this; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// Constructor +//////////////////////////////////////////////////////////////////////////////////////// +hstring& hstring::operator= (const hstring &str) +{ + mHandle = str.mHandle; + +#ifdef _DEBUG + mStr = str.mStr; +#endif + return *this; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +const char* hstring::c_str(void) const +{ + if (!mHandle) + { + return(""); + } + return ((const char*)Pool()[mHandle]); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////////// +const char* hstring::operator *(void) const +{ + return c_str(); +} + +//////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////// +int hstring::length() const +{ + return strlen(c_str()); +} + +//////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////// +int hstring::handle() const +{ + return mHandle; +} + + + +//////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////// +void hstring::init(const char *str) +{ + if (!str) + { + mHandle = 0; + } + else + { + mHandle = Pool().get_handle(str, strlen(str)+1); // +1 for null character + } + #ifdef _DEBUG + mStr = (char*)Pool()[mHandle]; + #endif +} + + +//////////////////////////////////////////////////////////////////////////////////// +// +//////////////////////////////////////////////////////////////////////////////////// +#ifdef _DEBUG +float hstring::ave_collisions() {return Pool().average_collisions();} +int hstring::total_strings() {return Pool().total_allocs();} +int hstring::total_bytes() {return Pool().size();} +int hstring::total_finds() {return Pool().total_finds();} +int hstring::total_collisions() {return Pool().total_collisions();} +#endif + +#ifdef _XBOX +} // dllNamespace +#endif diff --git a/code/rufl/hstring.h b/code/rufl/hstring.h new file mode 100644 index 0000000..dc7236c --- /dev/null +++ b/code/rufl/hstring.h @@ -0,0 +1,106 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// RAVEN STANDARD USEFUL FUNCTION LIBRARY +// (c) 2002 Activision +// +// +// Handle String +// ------------- +// Handle strings are allocated once in a static buffer (with a hash index), and are +// never cleared out. You should use these for very common string names which are +// redundant or intended to last a long time. +// +// Handle strings are also good for comparison and storage because they compare only +// the handles, which are simple unique integers. +// +//////////////////////////////////////////////////////////////////////////////////////// +#if !defined(RUFL_HSTRING_INC) +#define RUFL_HSTRING_INC + + +#ifdef _XBOX +namespace dllNamespace +{ +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// The Handle String Class +//////////////////////////////////////////////////////////////////////////////////////// +class hstring +{ +public: + //////////////////////////////////////////////////////////////////////////////////// + // Constructors + //////////////////////////////////////////////////////////////////////////////////// + hstring(); + hstring(const char *str); + hstring(const hstring &str); + + + //////////////////////////////////////////////////////////////////////////////////// + // Assignment + //////////////////////////////////////////////////////////////////////////////////// + hstring& operator= (const char *str); + hstring& operator= (const hstring &str); + + + + //////////////////////////////////////////////////////////////////////////////////// + // Comparison + //////////////////////////////////////////////////////////////////////////////////// + bool operator== (const hstring &str) const {return (mHandle==str.mHandle);} + bool operator< (const hstring &str) const {return (mHandle< str.mHandle);} + bool operator! () const {return (mHandle==0);} + + + //////////////////////////////////////////////////////////////////////////////////// + // Conversion + //////////////////////////////////////////////////////////////////////////////////// + const char* c_str(void) const; + const char* operator *(void) const; + + + //////////////////////////////////////////////////////////////////////////////////// + // Access Functions + //////////////////////////////////////////////////////////////////////////////////// + int length(void) const; + int handle(void) const; + bool empty() const {return handle()==0;} + + + + //////////////////////////////////////////////////////////////////////////////////// + // Debug Statistics Routines + //////////////////////////////////////////////////////////////////////////////////// +#ifdef _DEBUG + static float ave_collisions(); + static int total_strings(); + static int total_bytes(); + static int total_finds(); + static int total_collisions(); +#endif + + + +private: + //////////////////////////////////////////////////////////////////////////////////// + // Helper Functions + //////////////////////////////////////////////////////////////////////////////////// + void init(const char *str); + + + //////////////////////////////////////////////////////////////////////////////////// + // Data + //////////////////////////////////////////////////////////////////////////////////// + int mHandle; + +#ifdef _DEBUG + char* mStr; +#endif +}; + +#ifdef _XBOX +} // dllNamespace +using namespace dllNamespace; +#endif + +#endif // HSTRING_H \ No newline at end of file diff --git a/code/rufl/random.cpp b/code/rufl/random.cpp new file mode 100644 index 0000000..e69de29 diff --git a/code/rufl/random.h b/code/rufl/random.h new file mode 100644 index 0000000..e69de29 diff --git a/code/server/exe_headers.cpp b/code/server/exe_headers.cpp new file mode 100644 index 0000000..1fcd257 --- /dev/null +++ b/code/server/exe_headers.cpp @@ -0,0 +1,5 @@ +// The file that generates the PCH for the whole executable... +// + +#include "../server/exe_headers.h" + diff --git a/code/server/exe_headers.h b/code/server/exe_headers.h new file mode 100644 index 0000000..409cfc6 --- /dev/null +++ b/code/server/exe_headers.h @@ -0,0 +1,13 @@ +// stuff added for PCH files. I want to have a lot of stuff included here so the PCH is pretty rich, +// but without exposing too many extra protos, so for now (while I experiment)... +// + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" + +#include "../client/client.h" +#include "../server/server.h" +#include "../renderer/tr_local.h" + +#pragma hdrstop + diff --git a/code/server/server.h b/code/server/server.h new file mode 100644 index 0000000..83b58ab --- /dev/null +++ b/code/server/server.h @@ -0,0 +1,321 @@ +// server.h + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../game/g_public.h" +#include "../game/bg_public.h" + +#ifndef SERVER_H +#define SERVER_H + + +//============================================================================= + +//#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND + // GAME BOTH REFERENCE !!! +//rww - this won't do.. I need to include bg_public.h in the exe elsewhere. +//I'm including it here instead so we can have our PERS_SCORE value. And have +//it be the proper enum value. + +#define MAX_ENT_CLUSTERS 16 + +typedef struct svEntity_s { + struct worldSector_s *worldSector; + struct svEntity_s *nextEntityInWorldSector; + + entityState_t baseline; // for delta compression of initial sighting +#ifdef _XBOX + signed char numClusters; // if -1, use headnode instead + short clusternums[MAX_ENT_CLUSTERS]; + short lastCluster; // if all the clusters don't fit in clusternums + short areanum, areanum2; + char snapshotCounter; // used to prevent double adding from portal views +#else + int numClusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int lastCluster; // if all the clusters don't fit in clusternums + int areanum, areanum2; + int snapshotCounter; // used to prevent double adding from portal views +#endif +} svEntity_t; + +typedef enum { + SS_DEAD, // no map loaded + SS_LOADING, // spawning level entities + SS_GAME // actively running +} serverState_t; + +typedef struct { + serverState_t state; + int serverId; // changes each server start +#ifdef _XBOX + char snapshotCounter; // incremented for each snapshot built +#else + int snapshotCounter; // incremented for each snapshot built +#endif + int time; // all entities are correct for this time // These 2 saved out + int timeResidual; // <= 1000 / sv_frame->value // during savegame. + float timeResidualFraction; // fraction of a msec accumulated + int nextFrameTime; // when time > nextFrameTime, process world // this doesn't get used anywhere! -Ste + struct cmodel_s *models[MAX_MODELS]; + char *configstrings[MAX_CONFIGSTRINGS]; + // + // be careful, Jake's code uses the 'svEntities' field as a marker to memset-this-far-only inside SV_InitSV()!!!!! + // + char *entityParsePoint; // used during game VM init + + int mLocalSubBSPIndex; + int mLocalSubBSPModelOffset; + char *mLocalSubBSPEntityParsePoint; + + svEntity_t svEntities[MAX_GENTITIES]; +} server_t; + + + +typedef struct { + int areabytes; + byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + playerState_t ps; + int num_entities; + int first_entity; // into the circular sv_packet_entities[] + // the entities MUST be in increasing state number + // order, otherwise the delta compression will fail + int messageSent; // time the message was transmitted + int messageAcked; // time the message was acked + int messageSize; // used to rate drop packets +} clientSnapshot_t; + +typedef enum { + CS_FREE, // can be reused for a new connection + CS_ZOMBIE, // client has been disconnected, but don't reuse + // connection for a couple seconds + CS_CONNECTED, // has been assigned to a client_t, but no gamestate yet + CS_PRIMED, // gamestate has been sent, but client hasn't sent a usercmd + CS_ACTIVE // client is fully in game +} clientState_t; + + +typedef struct client_s { + clientState_t state; + char userinfo[MAX_INFO_STRING]; // name, etc + + char *reliableCommands[MAX_RELIABLE_COMMANDS]; + int reliableSequence; + int reliableAcknowledge; + + int gamestateMessageNum; // netchan->outgoingSequence of gamestate + + usercmd_t lastUsercmd; + int lastMessageNum; // for delta compression + int cmdNum; // command number last executed + int lastClientCommand; // reliable client message sequence + gentity_t *gentity; // SV_GentityNum(clientnum) + char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked + byte *download; // file being downloaded + int downloadsize; // total bytes (can't use EOF because of paks) + int downloadcount; // bytes sent + int deltaMessage; // frame last client usercmd message + int lastPacketTime; // sv.time when packet was last received + int lastConnectTime; // sv.time when connection started + int nextSnapshotTime; // send another snapshot when sv.time >= nextSnapshotTime + qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec + qboolean droppedCommands; // true if enough pakets to pass the cl_packetdup were dropped + int timeoutCount; // must timeout a few frames in a row so debugging doesn't break + clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here + int ping; + int rate; // bytes / second + int snapshotMsec; // requests a snapshot every snapshotMsec unless rate choked + netchan_t netchan; +} client_t; + +//============================================================================= + + +typedef struct { + netadr_t adr; + int challenge; + int time; +} challenge_t; + +// this structure will be cleared only when the game dll changes +typedef struct { + qboolean initialized; // sv_init has completed + client_t *clients; // [sv_maxclients->integer]; + int numSnapshotEntities; // sv_maxclients->integer*PACKET_BACKUP*MAX_PACKET_ENTITIES + int nextSnapshotEntities; // next snapshotEntities to use + entityState_t *snapshotEntities; // [numSnapshotEntities] + int nextHeartbeatTime; +} serverStatic_t; + +//============================================================================= + +extern serverStatic_t svs; // persistant server info across maps +extern server_t sv; // cleared each map + +extern game_export_t *ge; + +extern cvar_t *sv_fps; +extern cvar_t *sv_timeout; +extern cvar_t *sv_zombietime; +extern cvar_t *sv_reconnectlimit; +extern cvar_t *sv_showloss; +extern cvar_t *sv_killserver; +extern cvar_t *sv_mapname; +extern cvar_t *sv_spawntarget; +extern cvar_t *sv_mapChecksum; +extern cvar_t *sv_serverid; +extern cvar_t *sv_testsave; +extern cvar_t *sv_compress_saved_games; + +//=========================================================== + +// +// sv_main.c +// +void SV_FinalMessage (char *message); +void QDECL SV_SendServerCommand( client_t *cl, const char *fmt, ...); + + +void SV_AddOperatorCommands (void); +void SV_RemoveOperatorCommands (void); + + +// +// sv_init.c +// +void SV_SetConfigstring( int index, const char *val ); +void SV_GetConfigstring( int index, char *buffer, int bufferSize ); + +void SV_SetUserinfo( int index, const char *val ); +void SV_GetUserinfo( int index, char *buffer, int bufferSize ); + +void SV_SpawnServer( char *server, ForceReload_e eForceReload, qboolean bAllowScreenDissolve ); + + +// +// sv_client.c +// +void SV_DirectConnect( netadr_t from ); + +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ); +void SV_UserinfoChanged( client_t *cl ); + +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded ); +void SV_DropClient( client_t *drop, const char *reason ); + +void SV_ExecuteClientCommand( client_t *cl, const char *s ); +void SV_ClientThink (client_t *cl, usercmd_t *cmd); + + +// +// sv_snapshot.c +// +void SV_AddServerCommand( client_t *client, const char *cmd ); +void SV_SendMessageToClient( msg_t *msg, client_t *client ); +void SV_SendClientMessages( void ); +void SV_SendClientSnapshot( client_t *client ); + + + +// +// sv_game.c +// +gentity_t *SV_GentityNum( int num ); +svEntity_t *SV_SvEntityForGentity( gentity_t *gEnt ); +gentity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ); +void SV_InitGameProgs (void); +void SV_ShutdownGameProgs (qboolean shutdownCin); +qboolean SV_inPVS (const vec3_t p1, const vec3_t p2); + + +//============================================================ +// +// high level object sorting to reduce interaction tests +// + +void SV_ClearWorld (void); +// called after the world model has been loaded, before linking any entities + +void SV_UnlinkEntity( gentity_t *ent ); +// call before removing an entity, and before trying to move one, +// so it doesn't clip against itself + +void SV_LinkEntity( gentity_t *ent ); +// Needs to be called any time an entity changes origin, mins, maxs, +// or solid. Automatically unlinks if needed. +// sets ent->v.absmin and ent->v.absmax +// sets ent->leafnums[] for pvs determination even if the entity +// is not solid + + +clipHandle_t SV_ClipHandleForEntity( const gentity_t *ent ); + + +void SV_SectorList_f( void ); + + +int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, gentity_t **elist, int maxcount ); +// fills in a table of entity pointers with entities that have bounding boxes +// that intersect the given area. It is possible for a non-axial bmodel +// to be returned that doesn't actually intersect the area on an exact +// test. +// returns the number of pointers filled in +// The world entity is never returned in this list. + + +int SV_PointContents( const vec3_t p, int passEntityNum ); +// returns the CONTENTS_* value from the world and all entities at the given point. + +/* +Ghoul2 Insert Start +*/ +void SV_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + const int passEntityNum, const int contentmask, const EG2_Collision eG2TraceType = G2_NOCOLLIDE, const int useLod = 0); +/* +Ghoul2 Insert End +*/ +// mins and maxs are relative + +// if the entire move stays in a solid volume, trace.allsolid will be set, +// trace.startsolid will be set, and trace.fraction will be 0 + +// if the starting point is in a solid, it will be allowed to move out +// to an open area + +// passEntityNum is explicitly excluded from clipping checks (normally ENTITYNUM_NONE) + + + +/////////////////////////////////////////////// +// +// sv_savegame.cpp +// +void SV_LoadGame_f(void); +void SV_LoadTransition_f(void); +void SV_SaveGame_f(void); +void SV_WipeGame_f(void); +qboolean SV_TryLoadTransition( const char *mapname ); +qboolean SG_WriteSavegame(const char *psPathlessBaseName, qboolean qbAutosave); +qboolean SG_ReadSavegame(const char *psPathlessBaseName); +void SG_WipeSavegame(const char *psPathlessBaseName); +qboolean SG_Append(unsigned long chid, const void *data, int length); +int SG_Read (unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr = NULL); +int SG_ReadOptional (unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr = NULL); +void SG_Shutdown(); +void SG_TestSave(void); + +// +// note that this version number does not mean that a savegame with the same version can necessarily be loaded, +// since anyone can change any loadsave-affecting structure somewhere in a header and change a chunk size. +// What it's used for is for things like mission pack etc if we need to distinguish "street-copy" savegames from +// any new enhanced ones that need to ask for new chunks during loading. +// +#define iSAVEGAME_VERSION 1 +int SG_Version(void); // call this to know what version number a successfully-opened savegame file was +// +extern SavedGameJustLoaded_e eSavedGameJustLoaded; +extern qboolean qbLoadTransition; +// +/////////////////////////////////////////////// +#endif // #ifndef SERVER_H diff --git a/code/server/sv_ccmds.cpp b/code/server/sv_ccmds.cpp new file mode 100644 index 0000000..37c469d --- /dev/null +++ b/code/server/sv_ccmds.cpp @@ -0,0 +1,484 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "server.h" +#include "..\game\weapons.h" +#include "..\game\g_items.h" +#include "..\game\statindex.h" + + +/* +=============================================================================== + +OPERATOR CONSOLE ONLY COMMANDS + +These commands can only be entered from stdin or by a remote operator datagram +=============================================================================== +*/ + +qboolean qbLoadTransition = qfalse; + +/* +================== +SV_SetPlayer + +Returns the player +================== +*/ +static client_t *SV_SetPlayer( void ) { + client_t *cl; + + cl = &svs.clients[0]; + if ( !cl->state ) { + Com_Printf( "Client is not active\n" ); + return NULL; + } + return cl; +} + + +//========================================================= +// don't call this directly, it should only be called from SV_Map_f() or SV_MapTransition_f() +// +static void SV_Map_( ForceReload_e eForceReload ) +{ + char *map; +// char expanded[MAX_QPATH]; + + map = Cmd_Argv(1); + if ( !*map ) { + return; + } + + // make sure the level exists before trying to change, so that + // a typo at the server console won't end the game + if (strchr (map, '\\') ) { + Com_Printf ("Can't have mapnames with a \\\n"); + return; + } + +#ifndef _XBOX // Could check for maps/%s/brushes.mle or something... + Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map); + if ( FS_ReadFile (expanded, NULL) == -1 ) { + Com_Printf ("Can't find map %s\n", expanded); + extern cvar_t *com_buildScript; + if (com_buildScript && com_buildScript->integer) + {//yes, it's happened, someone deleted a map during my build... + Com_Error( ERR_FATAL, "Can't find map %s\n", expanded ); + } + return; + } +#endif + + if (map[0]!='_') + { + SG_WipeSavegame("Checkpoint"); + } + + SV_SpawnServer( map, eForceReload, qtrue ); // start up the map +} + + + +// Save out some player data for later restore if this is a spawn point with KEEP_PREV (spawnflags&1) set... +// +// (now also called by auto-save code to setup the cvars correctly +void SV_Player_EndOfLevelSave(void) +{ + int i; + + // I could just call GetClientState() but that's in sv_bot.cpp, and I'm not sure if that's going to be deleted for + // the single player build, so here's the guts again... + // + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (cl + && + cl->gentity && cl->gentity->client // crash fix for voy4->brig transition when you kill Foster. + // Shouldn't happen, but does sometimes... + ) + { + Cvar_Set( sCVARNAME_PLAYERSAVE, ""); // default to blank + +// clientSnapshot_t* pFrame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK]; + playerState_t* pState = cl->gentity->client; + const char *s2; + // |general info |-force powers |-saber 1 |-saber 2 |-general saber + const char *s = va("%i %i %i %i %i %i %i %f %f %f %i %i %i %i %i %s %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %s %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i", + pState->stats[STAT_HEALTH], + pState->stats[STAT_ARMOR], + pState->stats[STAT_WEAPONS], + pState->stats[STAT_ITEMS], + pState->weapon, + pState->weaponstate, + pState->batteryCharge, + pState->viewangles[0], + pState->viewangles[1], + pState->viewangles[2], + //force power data + pState->forcePowersKnown, + pState->forcePower, + pState->forcePowerMax, + pState->forcePowerRegenRate, + pState->forcePowerRegenAmount, + //saber 1 data + pState->saber[0].name, + pState->saber[0].blade[0].active, + pState->saber[0].blade[1].active, + pState->saber[0].blade[2].active, + pState->saber[0].blade[3].active, + pState->saber[0].blade[4].active, + pState->saber[0].blade[5].active, + pState->saber[0].blade[6].active, + pState->saber[0].blade[7].active, + pState->saber[0].blade[0].color, + pState->saber[0].blade[1].color, + pState->saber[0].blade[2].color, + pState->saber[0].blade[3].color, + pState->saber[0].blade[4].color, + pState->saber[0].blade[5].color, + pState->saber[0].blade[6].color, + pState->saber[0].blade[7].color, + //saber 2 data + pState->saber[1].name, + pState->saber[1].blade[0].active, + pState->saber[1].blade[1].active, + pState->saber[1].blade[2].active, + pState->saber[1].blade[3].active, + pState->saber[1].blade[4].active, + pState->saber[1].blade[5].active, + pState->saber[1].blade[6].active, + pState->saber[1].blade[7].active, + pState->saber[1].blade[0].color, + pState->saber[1].blade[1].color, + pState->saber[1].blade[2].color, + pState->saber[1].blade[3].color, + pState->saber[1].blade[4].color, + pState->saber[1].blade[5].color, + pState->saber[1].blade[6].color, + pState->saber[1].blade[7].color, + //general saber data + pState->saberStylesKnown, + pState->saberAnimLevel, + pState->saberLockEnemy, + pState->saberLockTime + ); + Cvar_Set( sCVARNAME_PLAYERSAVE, s ); + + //ammo + s2 = ""; + for (i=0;i< AMMO_MAX; i++) + { + s2 = va("%s %i",s2, pState->ammo[i]); + } + Cvar_Set( "playerammo", s2 ); + + //inventory + s2 = ""; + for (i=0;i< INV_MAX; i++) + { + s2 = va("%s %i",s2, pState->inventory[i]); + } + Cvar_Set( "playerinv", s2 ); + + // the new JK2 stuff - force powers, etc... + // + s2 = ""; + for (i=0;i< NUM_FORCE_POWERS; i++) + { + s2 = va("%s %i",s2, pState->forcePowerLevel[i]); + } + Cvar_Set( "playerfplvl", s2 ); + } +} + + +// Restart the server on a different map +// +//extern void SCR_PrecacheScreenshot(); //scr_scrn.cpp +static void SV_MapTransition_f(void) +{ + char *spawntarget; + +// SCR_PrecacheScreenshot(); + SV_Player_EndOfLevelSave(); + + spawntarget = Cmd_Argv(2); + if ( *spawntarget != NULL ) + { + Cvar_Set( "spawntarget", spawntarget ); + } + else + { + Cvar_Set( "spawntarget", "" ); + } + + SV_Map_( eForceReload_NOTHING ); +} + +/* +================== +SV_Map_f + +Restart the server on a different map, but clears a cvar so that typing "map blah" doesn't try and preserve +player weapons/ammo/etc from the previous level that you haven't really exited (ie ignores KEEP_PREV on spawn points) +================== +*/ +//void SCR_UnprecacheScreenshot(); //scr_scrn.cpp +static void SV_Map_f( void ) +{ + Cvar_Set( sCVARNAME_PLAYERSAVE, ""); + Cvar_Set( "spawntarget", "" ); + Cvar_Set("tier_storyinfo", "0"); + Cvar_Set("tiers_complete", ""); +// SCR_UnprecacheScreenshot(); + + ForceReload_e eForceReload = eForceReload_NOTHING; // default for normal load + + if ( !Q_stricmp( Cmd_Argv(0), "devmapbsp") ) { + eForceReload = eForceReload_BSP; + } + else + if ( !Q_stricmp( Cmd_Argv(0), "devmapmdl") ) { + eForceReload = eForceReload_MODELS; + } + else + if ( !Q_stricmp( Cmd_Argv(0), "devmapall") ) { + eForceReload = eForceReload_ALL; + } + + SV_Map_( eForceReload ); + + // set the cheat value + // if the level was started with "map ", then + // cheats will not be allowed. If started with "devmap " + // then cheats will be allowed + if ( !Q_stricmpn( Cmd_Argv(0), "devmap", 6 ) ) { + Cvar_Set( "helpUsObi", "1" ); + } else { +#ifdef _XBOX + Cvar_Set( "helpUsObi", "1" ); +#else + Cvar_Set( "helpUsObi", "0" ); +#endif + } +} + +/* +================== +SV_LoadTransition_f +================== +*/ +void SV_LoadTransition_f(void) +{ + char *map; + char *spawntarget; + + map = Cmd_Argv(1); + if ( !*map ) { + return; + } + + qbLoadTransition = qtrue; + +// SCR_PrecacheScreenshot(); + SV_Player_EndOfLevelSave(); + + //Save the full current state of the current map so we can return to it later + SG_WriteSavegame( va("hub/%s", sv_mapname->string), qfalse ); + + //set the spawntarget if there is one + spawntarget = Cmd_Argv(2); + if ( *spawntarget != NULL ) + { + Cvar_Set( "spawntarget", spawntarget ); + } + else + { + Cvar_Set( "spawntarget", "" ); + } + + if ( !SV_TryLoadTransition( map ) ) + {//couldn't load a savegame + SV_Map_( eForceReload_NOTHING ); + } + qbLoadTransition = qfalse; +} +//=============================================================== + +/* +================ +SV_Status_f +================ +*/ +static void SV_Status_f( void ) { + int i, j, l; + client_t *cl; + const char *s; + int ping; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + Com_Printf ("map: %s\n", sv_mapname->string ); + + Com_Printf ("num score ping name lastmsg address qport rate\n"); + Com_Printf ("--- ----- ---- --------------- ------- --------------------- ----- -----\n"); + for (i=0,cl=svs.clients ; i < 1 ; i++,cl++) + { + if (!cl->state) + continue; + Com_Printf ("%3i ", i); + Com_Printf ("%5i ", cl->gentity->client->persistant[PERS_SCORE]); + + if (cl->state == CS_CONNECTED) + Com_Printf ("CNCT "); + else if (cl->state == CS_ZOMBIE) + Com_Printf ("ZMBI "); + else + { + ping = cl->ping < 9999 ? cl->ping : 9999; + Com_Printf ("%4i ", ping); + } + + Com_Printf ("%s", cl->name); + l = 16 - strlen(cl->name); + for (j=0 ; jlastPacketTime ); + + s = NET_AdrToString( cl->netchan.remoteAddress ); + Com_Printf ("%s", s); + l = 22 - strlen(s); + for (j=0 ; jnetchan.qport); + + Com_Printf (" %5i", cl->rate); + + Com_Printf ("\n"); + } + Com_Printf ("\n"); +} + +/* +=========== +SV_Serverinfo_f + +Examine the serverinfo string +=========== +*/ +static void SV_Serverinfo_f( void ) { + Com_Printf ("Server info settings:\n"); + Info_Print ( Cvar_InfoString( CVAR_SERVERINFO ) ); +} + + +/* +=========== +SV_Systeminfo_f + +Examine or change the serverinfo string +=========== +*/ +static void SV_Systeminfo_f( void ) { + Com_Printf ("System info settings:\n"); + Info_Print ( Cvar_InfoString( CVAR_SYSTEMINFO ) ); +} + + +/* +=========== +SV_DumpUser_f + +Examine all a users info strings FIXME: move to game +=========== +*/ +static void SV_DumpUser_f( void ) { + client_t *cl; + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( Cmd_Argc() != 2 ) { + Com_Printf ("Usage: info \n"); + return; + } + + cl = SV_SetPlayer(); + if ( !cl ) { + return; + } + + Com_Printf( "userinfo\n" ); + Com_Printf( "--------\n" ); + Info_Print( cl->userinfo ); +} + +//=========================================================== + +/* +================== +SV_AddOperatorCommands +================== +*/ +void SV_AddOperatorCommands( void ) { + static qboolean initialized; + + if ( initialized ) { + return; + } + initialized = qtrue; + + Cmd_AddCommand ("status", SV_Status_f); + Cmd_AddCommand ("serverinfo", SV_Serverinfo_f); + Cmd_AddCommand ("systeminfo", SV_Systeminfo_f); + Cmd_AddCommand ("dumpuser", SV_DumpUser_f); + Cmd_AddCommand ("sectorlist", SV_SectorList_f); + Cmd_AddCommand ("map", SV_Map_f); + Cmd_AddCommand ("devmap", SV_Map_f); + Cmd_AddCommand ("devmapbsp", SV_Map_f); + Cmd_AddCommand ("devmapmdl", SV_Map_f); + Cmd_AddCommand ("devmapsnd", SV_Map_f); + Cmd_AddCommand ("devmapall", SV_Map_f); + Cmd_AddCommand ("maptransition", SV_MapTransition_f); + Cmd_AddCommand ("load", SV_LoadGame_f); + Cmd_AddCommand ("loadtransition", SV_LoadTransition_f); + Cmd_AddCommand ("save", SV_SaveGame_f); + Cmd_AddCommand ("wipe", SV_WipeGame_f); + +//#ifdef _DEBUG +// extern void UI_Dump_f(void); +// Cmd_AddCommand ("ui_dump", UI_Dump_f); +//#endif +} + +/* +================== +SV_RemoveOperatorCommands +================== +*/ +void SV_RemoveOperatorCommands( void ) { +#if 0 + // removing these won't let the server start again + Cmd_RemoveCommand ("status"); + Cmd_RemoveCommand ("serverinfo"); + Cmd_RemoveCommand ("systeminfo"); + Cmd_RemoveCommand ("dumpuser"); + Cmd_RemoveCommand ("serverrecord"); + Cmd_RemoveCommand ("serverstop"); + Cmd_RemoveCommand ("sectorlist"); +#endif +} + diff --git a/code/server/sv_client.cpp b/code/server/sv_client.cpp new file mode 100644 index 0000000..8317c00 --- /dev/null +++ b/code/server/sv_client.cpp @@ -0,0 +1,605 @@ +// sv_client.c -- server code for dealing with clients + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + + +#include "server.h" + +/* +================== +SV_DirectConnect + +A "connect" OOB command has been received +================== +*/ +void SV_DirectConnect( netadr_t from ) { + char userinfo[MAX_INFO_STRING]; + int i; + client_t *cl, *newcl; + MAC_STATIC client_t temp; + gentity_t *ent; + int clientNum; + int version; + int qport; + int challenge; + char *denied; + + Com_DPrintf ("SVC_DirectConnect ()\n"); + + Q_strncpyz( userinfo, Cmd_Argv(1), sizeof(userinfo) ); + + version = atoi( Info_ValueForKey( userinfo, "protocol" ) ); + if ( version != PROTOCOL_VERSION ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer uses protocol version %i.\n", PROTOCOL_VERSION ); + Com_DPrintf (" rejected connect from version %i\n", version); + return; + } + + qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); + + challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); + + // see if the challenge is valid (local clients don't need to challenge) + if ( !NET_IsLocalAddress (from) ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nNo challenge for address.\n" ); + return; + } else { + // force the "ip" info key to "localhost" + Info_SetValueForKey( userinfo, "ip", "localhost" ); + } + + newcl = &temp; + memset (newcl, 0, sizeof(client_t)); + + // if there is already a slot for this ip, reuse it + for (i=0,cl=svs.clients ; i < 1 ; i++,cl++) + { + if ( cl->state == CS_FREE ) { + continue; + } + if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) + && ( cl->netchan.qport == qport + || from.port == cl->netchan.remoteAddress.port ) ) + { + if (( sv.time - cl->lastConnectTime) + < (sv_reconnectlimit->integer * 1000)) + { + Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); + return; + } + Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); + newcl = cl; + goto gotnewcl; + } + } + + + newcl = NULL; + for ( i = 0; i < 1 ; i++ ) { + cl = &svs.clients[i]; + if (cl->state == CS_FREE) { + newcl = cl; + break; + } + } + + if ( !newcl ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nServer is full.\n" ); + Com_DPrintf ("Rejected a connection.\n"); + return; + } + +gotnewcl: + // build a new connection + // accept the new client + // this is the only place a client_t is ever initialized + *newcl = temp; + clientNum = newcl - svs.clients; + ent = SV_GentityNum( clientNum ); + newcl->gentity = ent; + + // save the address + Netchan_Setup (NS_SERVER, &newcl->netchan , from, qport); + + // save the userinfo + Q_strncpyz( newcl->userinfo, userinfo, sizeof(newcl->userinfo) ); + + // get the game a chance to reject this connection or modify the userinfo + denied = ge->ClientConnect( clientNum, qtrue, eSavedGameJustLoaded ); // firstTime = qtrue + if ( denied ) { + NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied ); + Com_DPrintf ("Game rejected a connection: %s.\n", denied); + return; + } + + SV_UserinfoChanged( newcl ); + + // send the connect packet to the client + NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); + + newcl->state = CS_CONNECTED; + newcl->nextSnapshotTime = sv.time; + newcl->lastPacketTime = sv.time; + newcl->lastConnectTime = sv.time; + + // when we receive the first packet from the client, we will + // notice that it is from a different serverid and that the + // gamestate message was not just sent, forcing a retransmit + newcl->gamestateMessageNum = -1; +} + + +/* +===================== +SV_DropClient + +Called when the player is totally leaving the server, either willingly +or unwillingly. This is NOT called if the entire server is quiting +or crashing -- SV_FinalMessage() will handle that +===================== +*/ +void SV_DropClient( client_t *drop, const char *reason ) { + if ( drop->state == CS_ZOMBIE ) { + return; // already dropped + } + drop->state = CS_ZOMBIE; // become free in a few seconds + + if (drop->download) { + FS_FreeFile (drop->download); + drop->download = NULL; + } + + // call the prog function for removing a client + // this will remove the body, among other things + ge->ClientDisconnect( drop - svs.clients ); + + // tell everyone why they got dropped + SV_SendServerCommand( NULL, "print \"%s %s\n\"", drop->name, reason ); + + // add the disconnect command + SV_SendServerCommand( drop, "disconnect" ); +} + +/* +================ +SV_SendClientGameState + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each new map load. + +It will be resent if the client acknowledges a later message but has +the wrong gamestate. +================ +*/ +void SV_SendClientGameState( client_t *client ) { + int start; + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + + Com_DPrintf ("SV_SendGameState() for %s\n", client->name); + client->state = CS_PRIMED; + + // when we receive the first packet from the client, we will + // notice that it is from a different serverid and that the + // gamestate message was not just sent, forcing a retransmit + client->gamestateMessageNum = client->netchan.outgoingSequence; + + // clear the reliable message list for this client + client->reliableSequence = 0; + client->reliableAcknowledge = 0; + + MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); + + // send the gamestate + MSG_WriteByte( &msg, svc_gamestate ); + MSG_WriteLong( &msg, client->reliableSequence ); + + // write the configstrings + for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { + if (sv.configstrings[start][0]) { + MSG_WriteByte( &msg, svc_configstring ); + MSG_WriteShort( &msg, start ); + MSG_WriteString( &msg, sv.configstrings[start] ); + } + } + + MSG_WriteByte( &msg, 0 ); + + // check for overflow + if ( msg.overflowed ) { + Com_Printf ("WARNING: GameState overflowed for %s\n", client->name); + } + + // deliver this to the client + SV_SendMessageToClient( &msg, client ); +} + + +/* +================== +SV_ClientEnterWorld +================== +*/ +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded ) { + int clientNum; + gentity_t *ent; + + Com_DPrintf ("SV_ClientEnterWorld() from %s\n", client->name); + client->state = CS_ACTIVE; + + // set up the entity for the client + clientNum = client - svs.clients; + ent = SV_GentityNum( clientNum ); + ent->s.number = clientNum; + client->gentity = ent; + + // normally I check 'qbFromSavedGame' to avoid overwriting loaded client data, but this stuff I want + // to be reset so that client packet delta-ing bgins afresh, rather than based on your previous frame + // (which didn't in fact happen now if we've just loaded from a saved game...) + // + client->deltaMessage = -1; + client->cmdNum = 0; + client->nextSnapshotTime = sv.time; // generate a snapshot immediately + + // call the game begin function + ge->ClientBegin( client - svs.clients, cmd, eSavedGameJustLoaded ); +} + +/* +============================================================ + +CLIENT COMMAND EXECUTION + +============================================================ +*/ + + +/* +================= +SV_Disconnect_f + +The client is going to disconnect, so remove the connection immediately FIXME: move to game? +================= +*/ +static void SV_Disconnect_f( client_t *cl ) { + SV_DropClient( cl, "disconnected" ); +} + + + +/* +================= +SV_UserinfoChanged + +Pull specific info from a newly changed userinfo string +into a more C friendly form. +================= +*/ +void SV_UserinfoChanged( client_t *cl ) { + char *val; + int i; + + // name for C code + Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) ); + + // rate command + + // if the client is on the same subnet as the server and we aren't running an + // internet public server, assume they don't need a rate choke + cl->rate = 99999; // lans should not rate limit + + // snaps command + val = Info_ValueForKey (cl->userinfo, "snaps"); + if (strlen(val)) { + i = atoi(val); + if ( i < 1 ) { + i = 1; + } else if ( i > 30 ) { + i = 30; + } + cl->snapshotMsec = 1000/i; + } else { + cl->snapshotMsec = 50; + } +} + + +/* +================== +SV_UpdateUserinfo_f +================== +*/ +static void SV_UpdateUserinfo_f( client_t *cl ) { + Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); + + // call prog code to allow overrides + ge->ClientUserinfoChanged( cl - svs.clients ); + + SV_UserinfoChanged( cl ); +} + +typedef struct { + char *name; + void (*func)( client_t *cl ); +} ucmd_t; + +static ucmd_t ucmds[] = { + {"userinfo", SV_UpdateUserinfo_f}, + {"disconnect", SV_Disconnect_f}, + + {NULL, NULL} +}; + +/* +================== +SV_ExecuteClientCommand +================== +*/ +void SV_ExecuteClientCommand( client_t *cl, const char *s ) { + ucmd_t *u; + + Cmd_TokenizeString( s ); + + // see if it is a server level command + for (u=ucmds ; u->name ; u++) { + if (!strcmp (Cmd_Argv(0), u->name) ) { + u->func( cl ); + break; + } + } + + // pass unknown strings to the game + if (!u->name && sv.state == SS_GAME) { + ge->ClientCommand( cl - svs.clients ); + } +} + +#define MAX_STRINGCMDS 8 + +/* +=============== +SV_ClientCommand +=============== +*/ +static void SV_ClientCommand( client_t *cl, msg_t *msg ) { + int seq; + const char *s; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed it + if ( cl->lastClientCommand >= seq ) { + return; + } + + Com_DPrintf( "clientCommand: %s : %i : %s\n", cl->name, seq, s ); + + // drop the connection if we have somehow lost commands + if ( seq > cl->lastClientCommand + 1 ) { + Com_Printf( "Client %s lost %i clientCommands\n", cl->name, + seq - cl->lastClientCommand + 1 ); + } + + SV_ExecuteClientCommand( cl, s ); + + cl->lastClientCommand = seq; +} + + +//================================================================================== + + +/* +================== +SV_ClientThink +================== +*/ +void SV_ClientThink (client_t *cl, usercmd_t *cmd) { + cl->lastUsercmd = *cmd; + + if ( cl->state != CS_ACTIVE ) { + return; // may have been kicked during the last usercmd + } + + ge->ClientThink( cl - svs.clients, cmd ); +} + +/* +================== +SV_UserMove + +The message usually contains all the movement commands +that were in the last three packets, so that the information +in dropped packets can be recovered. + +On very fast clients, there may be multiple usercmd packed into +each of the backup packets. +================== +*/ +static void SV_UserMove( client_t *cl, msg_t *msg ) { + int i, start; + int cmdNum; + int firstNum; + int cmdCount; + usercmd_t nullcmd; + usercmd_t cmds[MAX_PACKET_USERCMDS]; + usercmd_t *cmd, *oldcmd; + int clientTime; + int serverId; + + cl->reliableAcknowledge = MSG_ReadLong( msg ); + serverId = MSG_ReadLong( msg ); + clientTime = MSG_ReadLong( msg ); + cl->deltaMessage = MSG_ReadLong( msg ); + + // cmdNum is the command number of the most recent included usercmd + cmdNum = MSG_ReadLong( msg ); + cmdCount = MSG_ReadByte( msg ); + + if ( cmdCount < 1 ) { + Com_Printf( "cmdCount < 1\n" ); + return; + } + + if ( cmdCount > MAX_PACKET_USERCMDS ) { + Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" ); + return; + } + + memset( &nullcmd, 0, sizeof(nullcmd) ); + oldcmd = &nullcmd; + for ( i = 0 ; i < cmdCount ; i++ ) { + cmd = &cmds[i]; + MSG_ReadDeltaUsercmd( msg, oldcmd, cmd ); + oldcmd = cmd; + } + + // if this is a usercmd from a previous gamestate, + // ignore it or retransmit the current gamestate + if ( serverId != sv.serverId ) { + // if we can tell that the client has dropped the last + // gamestate we sent them, resend it + if ( cl->netchan.incomingAcknowledged > cl->gamestateMessageNum ) { + Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); + SV_SendClientGameState( cl ); + } + return; + } + + // if this is the first usercmd we have received + // this gamestate, put the client into the world + if ( cl->state == CS_PRIMED ) { + + SV_ClientEnterWorld( cl, &cmds[0], eSavedGameJustLoaded ); +#ifndef _XBOX // No auto-saving for now? + if ( sv_mapname->string[0]!='_' ) + { + char savename[MAX_QPATH]; + if ( eSavedGameJustLoaded == eNO ) + { + SG_WriteSavegame("auto",qtrue); + if ( strnicmp(sv_mapname->string, "academy", 7) != 0) + { + Com_sprintf (savename, sizeof(savename), "auto_%s",sv_mapname->string); + SG_WriteSavegame(savename,qtrue);//can't use va becuase it's nested + } + } + else if ( qbLoadTransition == qtrue ) + { + Com_sprintf (savename, sizeof(savename), "hub/%s", sv_mapname->string ); + SG_WriteSavegame( savename, qfalse );//save a full one + SG_WriteSavegame( "auto", qfalse );//need a copy for auto, too + } + } +#endif + eSavedGameJustLoaded = eNO; + // the moves can be processed normaly + } + + if ( cl->state != CS_ACTIVE ) { + cl->deltaMessage = -1; + return; + } + + + // if there is a time gap from the last packet to this packet, + // fill in with the first command in the packet + + // with a packetdup of 0, firstNum == cmdNum + firstNum = cmdNum - ( cmdCount - 1 ); + if ( cl->cmdNum < firstNum - 1 ) { + cl->droppedCommands = qtrue; + if ( sv_showloss->integer ) { + Com_Printf("Lost %i usercmds from %s\n", firstNum - 1 - cl->cmdNum, + cl->name); + } + if ( cl->cmdNum < firstNum - 6 ) { + cl->cmdNum = firstNum - 6; // don't generate too many + } + while ( cl->cmdNum < firstNum - 1 ) { + cl->cmdNum++; + SV_ClientThink( cl, &cmds[0] ); + } + } + // skip over any usercmd_t we have already executed + start = cl->cmdNum - ( firstNum - 1 ); + for ( i = start ; i < cmdCount ; i++ ) { + SV_ClientThink (cl, &cmds[ i ]); + } + cl->cmdNum = cmdNum; + +} + + +/* +=========================================================================== + +USER CMD EXECUTION + +=========================================================================== +*/ + +/* +=================== +SV_ExecuteClientMessage + +Parse a client packet +=================== +*/ +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { + int c; + + while( 1 ) { + if ( msg->readcount > msg->cursize ) { + SV_DropClient (cl, "had a badread"); + return; + } + + c = MSG_ReadByte( msg ); + if ( c == -1 ) { + break; + } + + switch( c ) { + default: + SV_DropClient( cl,"had an unknown command char" ); + return; + + case clc_nop: + break; + + case clc_move: + SV_UserMove( cl, msg ); + break; + + case clc_clientCommand: + SV_ClientCommand( cl, msg ); + if (cl->state == CS_ZOMBIE) { + return; // disconnect command + } + break; + } + } +} + + +void SV_FreeClient(client_t *client) +{ + int i; + + if (!client) return; + + for(i=0; ireliableCommands[ i] ) { + Z_Free( client->reliableCommands[ i] ); + client->reliableCommands[i] = NULL; + client->reliableSequence = 0; + } + } +} + diff --git a/code/server/sv_game.cpp b/code/server/sv_game.cpp new file mode 100644 index 0000000..486020a --- /dev/null +++ b/code/server/sv_game.cpp @@ -0,0 +1,724 @@ +// sv_game.c -- interface to the game dll + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "../RMG/RM_Headers.h" + +#include "../qcommon/cm_local.h" + +#include "server.h" +#include "..\client\vmachine.h" +#include "..\client\client.h" +#include "..\renderer\tr_local.h" +#include "..\renderer\tr_WorldEffects.h" +/* +Ghoul2 Insert Start +*/ +#if !defined(G2_H_INC) + #include "..\ghoul2\G2.h" +#endif + +/* +Ghoul2 Insert End +*/ + +//prototypes +extern void Sys_UnloadGame( void ); +extern void *Sys_GetGameAPI( void *parms); +extern void Com_WriteCam ( const char *text ); +extern void Com_FlushCamFile(); + +#ifdef _XBOX +extern int *s_entityWavVol; +#else +extern int s_entityWavVol[MAX_GENTITIES]; +#endif + + +// these functions must be used instead of pointer arithmetic, because +// the game allocates gentities with private information after the server shared part +/* +int SV_NumForGentity( gentity_t *ent ) { + int num; + + num = ( (byte *)ent - (byte *)ge->gentities ) / ge->gentitySize; + + return num; +} +*/ +gentity_t *SV_GentityNum( int num ) { + gentity_t *ent; + + assert (num >=0); + ent = (gentity_t *)((byte *)ge->gentities + ge->gentitySize*(num)); + + return ent; +} + +svEntity_t *SV_SvEntityForGentity( gentity_t *gEnt ) { + if ( !gEnt || gEnt->s.number < 0 || gEnt->s.number >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + return &sv.svEntities[ gEnt->s.number ]; +} + +gentity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ) { + int num; + + num = svEnt - sv.svEntities; + return SV_GentityNum( num ); +} + +/* +=============== +SV_GameSendServerCommand + +Sends a command string to a client +=============== +*/ +void SV_GameSendServerCommand( int clientNum, const char *fmt, ... ) { + char msg[8192]; + va_list argptr; + + va_start (argptr,fmt); + vsprintf (msg, fmt, argptr); + va_end (argptr); + + if ( clientNum == -1 ) { + SV_SendServerCommand( NULL, "%s", msg ); + } else { + if ( clientNum < 0 || clientNum >= 1 ) { + return; + } + SV_SendServerCommand( svs.clients + clientNum, "%s", msg ); + } +} + + +/* +=============== +SV_GameDropClient + +Disconnects the client with a message +=============== +*/ +void SV_GameDropClient( int clientNum, const char *reason ) { + if ( clientNum < 0 || clientNum >= 1 ) { + return; + } + SV_DropClient( svs.clients + clientNum, reason ); +} + + +/* +================= +SV_SetBrushModel + +sets mins and maxs for inline bmodels +================= +*/ +void SV_SetBrushModel( gentity_t *ent, const char *name ) { + clipHandle_t h; + vec3_t mins, maxs; + + if (!name) + { + Com_Error( ERR_DROP, "SV_SetBrushModel: NULL model for ent number %d", ent->s.number ); + } + + if (name[0] == '*') + { + ent->s.modelindex = atoi( name + 1 ); + + if (sv.mLocalSubBSPIndex != -1) + { + ent->s.modelindex += sv.mLocalSubBSPModelOffset; + } + + h = CM_InlineModel( ent->s.modelindex ); + + if (sv.mLocalSubBSPIndex != -1) + { + CM_ModelBounds( SubBSP[sv.mLocalSubBSPIndex], h, mins, maxs ); + } + else + { + CM_ModelBounds( cmg, h, mins, maxs); + } + + //CM_ModelBounds( h, mins, maxs ); + + VectorCopy (mins, ent->mins); + VectorCopy (maxs, ent->maxs); + ent->bmodel = qtrue; + + if (0) //com_RMG && com_RMG->integer //fixme: this test really should be do we have bsp instances + { + ent->contents = CM_ModelContents( h, sv.mLocalSubBSPIndex ); + } + else + { + ent->contents = CM_ModelContents( h, -1 ); + } + } + else if (name[0] == '#') + { + ent->s.modelindex = CM_LoadSubBSP(va("maps/%s.bsp", name + 1), qfalse); + CM_ModelBounds( SubBSP[CM_FindSubBSP(ent->s.modelindex)], ent->s.modelindex, mins, maxs ); + + VectorCopy (mins, ent->mins); + VectorCopy (maxs, ent->maxs); + ent->bmodel = qtrue; + + //rwwNOTE: We don't ever want to set contents -1, it includes CONTENTS_LIGHTSABER. + //Lots of stuff will explode if there's a brush with CONTENTS_LIGHTSABER that isn't attached to a client owner. + //ent->contents = -1; // we don't know exactly what is in the brushes + h = CM_InlineModel( ent->s.modelindex ); + ent->contents = CM_ModelContents( h, CM_FindSubBSP(ent->s.modelindex) ); + // ent->contents = CONTENTS_SOLID; + } + else + { + Com_Error( ERR_DROP, "SV_SetBrushModel: %s isn't a brush model (ent %d)", name, ent->s.number ); + } +} + +const char *SV_SetActiveSubBSP(int index) +{ + if (index >= 0) + { + sv.mLocalSubBSPIndex = CM_FindSubBSP(index); + sv.mLocalSubBSPModelOffset = index; + sv.mLocalSubBSPEntityParsePoint = CM_SubBSPEntityString (sv.mLocalSubBSPIndex); + return sv.mLocalSubBSPEntityParsePoint; + } + else + { + sv.mLocalSubBSPIndex = -1; + } + + return NULL; +} + +/* +================= +SV_inPVS + +Also checks portalareas so that doors block sight +================= +*/ +qboolean SV_inPVS (const vec3_t p1, const vec3_t p2) +{ + int leafnum; + int cluster; + int area1, area2; +#ifdef _XBOX + const byte *mask; +#else + byte *mask; +#endif + int start=0; + + if ( com_speeds->integer ) { + start = Sys_Milliseconds (); + } + leafnum = CM_PointLeafnum (p1); + cluster = CM_LeafCluster (leafnum); + area1 = CM_LeafArea (leafnum); + mask = CM_ClusterPVS (cluster); + + leafnum = CM_PointLeafnum (p2); + cluster = CM_LeafCluster (leafnum); + area2 = CM_LeafArea (leafnum); + if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) + { + if ( com_speeds->integer ) { + timeInPVSCheck += Sys_Milliseconds () - start; + } + return qfalse; + } + + if (!CM_AreasConnected (area1, area2)) + { + timeInPVSCheck += Sys_Milliseconds() - start; + return qfalse; // a door blocks sight + } + + if ( com_speeds->integer ) { + timeInPVSCheck += Sys_Milliseconds() - start; + } + return qtrue; +} + + +/* +================= +SV_inPVSIgnorePortals + +Does NOT check portalareas +================= +*/ +qboolean SV_inPVSIgnorePortals( const vec3_t p1, const vec3_t p2) +{ + int leafnum; + int cluster; + int area1, area2; +#ifdef _XBOX + const byte *mask; +#else + byte *mask; +#endif + int start=0; + + if ( com_speeds->integer ) { + start = Sys_Milliseconds (); + } + + leafnum = CM_PointLeafnum (p1); + cluster = CM_LeafCluster (leafnum); + area1 = CM_LeafArea (leafnum); + mask = CM_ClusterPVS (cluster); + + leafnum = CM_PointLeafnum (p2); + cluster = CM_LeafCluster (leafnum); + area2 = CM_LeafArea (leafnum); + + if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) + { + if ( com_speeds->integer ) { + timeInPVSCheck += Sys_Milliseconds() - start; + } + return qfalse; + } + + if ( com_speeds->integer ) { + timeInPVSCheck += Sys_Milliseconds() - start; + } + return qtrue; +} + + +/* +======================== +SV_AdjustAreaPortalState +======================== +*/ +void SV_AdjustAreaPortalState( gentity_t *ent, qboolean open ) { + if ( !(ent->contents&CONTENTS_OPAQUE) ) { +#ifndef FINAL_BUILD +// Com_Printf( "INFO: entity number %d not opaque: not affecting area portal!\n", ent->s.number ); +#endif + return; + } + + svEntity_t *svEnt; + + svEnt = SV_SvEntityForGentity( ent ); + if ( svEnt->areanum2 == -1 ) { + return; + } + CM_AdjustAreaPortalState( svEnt->areanum, svEnt->areanum2, open ); +} + + +/* +================== +SV_GameAreaEntities +================== +*/ +qboolean SV_EntityContact( const vec3_t mins, const vec3_t maxs, const gentity_t *gEnt ) { + const float *origin, *angles; + clipHandle_t ch; + trace_t trace; + + // check for exact collision + origin = gEnt->currentOrigin; + angles = gEnt->currentAngles; + + ch = SV_ClipHandleForEntity( gEnt ); + CM_TransformedBoxTrace ( &trace, vec3_origin, vec3_origin, mins, maxs, + ch, -1, origin, angles ); + + return trace.startsolid; +} + + +/* +=============== +SV_GetServerinfo + +=============== +*/ +void SV_GetServerinfo( char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetServerinfo: bufferSize == %i", bufferSize ); + } + Q_strncpyz( buffer, Cvar_InfoString( CVAR_SERVERINFO ), bufferSize ); +} + +qboolean SV_GetEntityToken( char *buffer, int bufferSize ) +{ + char *s; + + if (sv.mLocalSubBSPIndex == -1) + { + s = COM_Parse( (const char **)&sv.entityParsePoint ); + Q_strncpyz( buffer, s, bufferSize ); + if ( !sv.entityParsePoint && !s[0] ) + { + return qfalse; + } + else + { + return qtrue; + } + } + else + { + s = COM_Parse( (const char **)&sv.mLocalSubBSPEntityParsePoint); + Q_strncpyz( buffer, s, bufferSize ); + if ( !sv.mLocalSubBSPEntityParsePoint && !s[0] ) + { + return qfalse; + } + else + { + return qtrue; + } + } +} + +//============================================== + +/* +=============== +SV_ShutdownGameProgs + +Called when either the entire server is being killed, or +it is changing to a different game directory. +=============== +*/ +void SV_ShutdownGameProgs (qboolean shutdownCin) { + if (!ge) { + return; + } + ge->Shutdown (); + +#ifdef _XBOX + if(shutdownCin) { + SCR_StopCinematic(); + } +#else + SCR_StopCinematic(); +#endif + CL_ShutdownCGame(); //we have cgame burried in here. + + Sys_UnloadGame (); //this kills cgame as well. + + ge = NULL; + cgvm.entryPoint = 0; +} + +// this is a compile-helper function since Z_Malloc can now become a macro with __LINE__ etc +// +static void *G_ZMalloc_Helper( int iSize, memtag_t eTag, qboolean bZeroit) +{ + return Z_Malloc( iSize, eTag, bZeroit ); +} + +//rww - RAGDOLL_BEGIN +void G2API_SetRagDoll(CGhoul2Info_v &ghoul2,CRagDollParams *parms); +void G2API_AnimateG2Models(CGhoul2Info_v &ghoul2, int AcurrentTime,CRagDollUpdateParams *params); + +qboolean G2API_RagPCJConstraint(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t min, vec3_t max); +qboolean G2API_RagPCJGradientSpeed(CGhoul2Info_v &ghoul2, const char *boneName, const float speed); +qboolean G2API_RagEffectorGoal(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t pos); +qboolean G2API_GetRagBonePos(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t pos, vec3_t entAngles, vec3_t entPos, vec3_t entScale); +qboolean G2API_RagEffectorKick(CGhoul2Info_v &ghoul2, const char *boneName, vec3_t velocity); +qboolean G2API_RagForceSolve(CGhoul2Info_v &ghoul2, qboolean force); + +qboolean G2API_SetBoneIKState(CGhoul2Info_v &ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params); +qboolean G2API_IKMove(CGhoul2Info_v &ghoul2, int time, sharedIKMoveParams_t *params); +//rww - RAGDOLL_END + +//This is as good a place as any I guess. +#ifndef _XBOX // Removing terrain from Xbox +void RMG_Init(int terrainID) +{ + if (!TheRandomMissionManager) + { + TheRandomMissionManager = new CRMManager; + } + TheRandomMissionManager->SetLandScape(cmg.landScape); + if (TheRandomMissionManager->LoadMission(qtrue)) + { + TheRandomMissionManager->SpawnMission(qtrue); + } +// cmg.landScapes[args[1]]->UpdatePatches(); + //sv.mRMGChecksum = cm.landScapes[terrainID]->get_rand_seed(); +} + +CCMLandScape *CM_RegisterTerrain(const char *config, bool server); + +int InterfaceCM_RegisterTerrain (const char *info) +{ + return CM_RegisterTerrain(info, false)->GetTerrainId(); +} +#endif // _XBOX + +/* +=============== +SV_InitGameProgs + +Init the game subsystem for a new map +=============== +*/ +void SV_InitGameProgs (void) { + game_import_t import; + int i; + + // unload anything we have now + if ( ge ) { + SV_ShutdownGameProgs (qtrue); + } + +#ifndef _XBOX + if ( !Cvar_VariableIntegerValue("fs_restrict") && !Sys_CheckCD() ) + { + Com_Error( ERR_NEED_CD, SE_GetString("CON_TEXT_NEED_CD") ); //"Game CD not in drive" ); + } +#endif + + // load a new game dll + import.Printf = Com_Printf; + import.WriteCam = Com_WriteCam; + import.FlushCamFile = Com_FlushCamFile; + import.Error = Com_Error; + + import.Milliseconds = Sys_Milliseconds; + + import.DropClient = SV_GameDropClient; + + import.SendServerCommand = SV_GameSendServerCommand; + + + import.linkentity = SV_LinkEntity; + import.unlinkentity = SV_UnlinkEntity; + import.EntitiesInBox = SV_AreaEntities; + import.EntityContact = SV_EntityContact; + import.trace = SV_Trace; + import.pointcontents = SV_PointContents; + import.totalMapContents = CM_TotalMapContents; + import.SetBrushModel = SV_SetBrushModel; + + import.inPVS = SV_inPVS; + import.inPVSIgnorePortals = SV_inPVSIgnorePortals; + + import.SetConfigstring = SV_SetConfigstring; + import.GetConfigstring = SV_GetConfigstring; + + import.SetUserinfo = SV_SetUserinfo; + import.GetUserinfo = SV_GetUserinfo; + + import.GetServerinfo = SV_GetServerinfo; + + import.cvar = Cvar_Get; + import.cvar_set = Cvar_Set; + import.Cvar_VariableIntegerValue = Cvar_VariableIntegerValue; + import.Cvar_VariableStringBuffer = Cvar_VariableStringBuffer; + + import.argc = Cmd_Argc; + import.argv = Cmd_Argv; + import.SendConsoleCommand = Cbuf_AddText; + + import.FS_FOpenFile = FS_FOpenFileByMode; + import.FS_Read = FS_Read; + import.FS_Write = FS_Write; + import.FS_FCloseFile = FS_FCloseFile; + import.FS_ReadFile = FS_ReadFile; + import.FS_FreeFile = FS_FreeFile; + import.FS_GetFileList = FS_GetFileList; + + import.AppendToSaveGame = SG_Append; +#ifndef _XBOX + import.ReadFromSaveGame = SG_Read; + import.ReadFromSaveGameOptional = SG_ReadOptional; +#endif + + import.AdjustAreaPortalState = SV_AdjustAreaPortalState; + import.AreasConnected = CM_AreasConnected; + + import.VoiceVolume = s_entityWavVol; + + import.Malloc = G_ZMalloc_Helper; + import.Free = Z_Free; + import.bIsFromZone = Z_IsFromZone; +/* +Ghoul2 Insert Start +*/ + + import.G2API_AddBolt = G2API_AddBolt; + import.G2API_AttachEnt = G2API_AttachEnt; + import.G2API_AttachG2Model = G2API_AttachG2Model; + import.G2API_CollisionDetect = G2API_CollisionDetect; + import.G2API_DetachEnt = G2API_DetachEnt; + import.G2API_DetachG2Model = G2API_DetachG2Model; + import.G2API_GetAnimFileName = G2API_GetAnimFileName; + import.G2API_GetBoltMatrix = G2API_GetBoltMatrix; + import.G2API_GetBoneAnim = G2API_GetBoneAnim; + import.G2API_GetBoneAnimIndex = G2API_GetBoneAnimIndex; + import.G2API_AddSurface = G2API_AddSurface; + import.G2API_HaveWeGhoul2Models =G2API_HaveWeGhoul2Models; +#ifndef _XBOX + import.G2API_InitGhoul2Model = G2API_InitGhoul2Model; + import.G2API_SetBoneAngles = G2API_SetBoneAngles; + import.G2API_SetBoneAnglesMatrix = G2API_SetBoneAnglesMatrix; + import.G2API_SetBoneAnim = G2API_SetBoneAnim; + import.G2API_SetSkin = G2API_SetSkin; + import.G2API_CopyGhoul2Instance = G2API_CopyGhoul2Instance; + import.G2API_SetBoneAnglesIndex = G2API_SetBoneAnglesIndex; + import.G2API_SetBoneAnimIndex = G2API_SetBoneAnimIndex; +#endif + import.G2API_IsPaused = G2API_IsPaused; + import.G2API_ListBones = G2API_ListBones; + import.G2API_ListSurfaces = G2API_ListSurfaces; + import.G2API_PauseBoneAnim = G2API_PauseBoneAnim; + import.G2API_PauseBoneAnimIndex = G2API_PauseBoneAnimIndex; + import.G2API_PrecacheGhoul2Model = G2API_PrecacheGhoul2Model; + import.G2API_RemoveBolt = G2API_RemoveBolt; + import.G2API_RemoveBone = G2API_RemoveBone; + import.G2API_RemoveGhoul2Model = G2API_RemoveGhoul2Model; + import.G2API_SetLodBias = G2API_SetLodBias; + import.G2API_SetRootSurface = G2API_SetRootSurface; + import.G2API_SetShader = G2API_SetShader; + import.G2API_SetSurfaceOnOff = G2API_SetSurfaceOnOff; + import.G2API_StopBoneAngles = G2API_StopBoneAngles; + import.G2API_StopBoneAnim = G2API_StopBoneAnim; + import.G2API_SetGhoul2ModelFlags = G2API_SetGhoul2ModelFlags; + import.G2API_AddBoltSurfNum = G2API_AddBoltSurfNum; + import.G2API_RemoveSurface = G2API_RemoveSurface; + import.G2API_GetAnimRange = G2API_GetAnimRange; + import.G2API_GetAnimRangeIndex = G2API_GetAnimRangeIndex; + import.G2API_GiveMeVectorFromMatrix = G2API_GiveMeVectorFromMatrix; + import.G2API_GetGhoul2ModelFlags = G2API_GetGhoul2ModelFlags; + import.G2API_CleanGhoul2Models = G2API_CleanGhoul2Models; + import.TheGhoul2InfoArray = TheGhoul2InfoArray; + import.G2API_GetParentSurface = G2API_GetParentSurface; + import.G2API_GetSurfaceIndex = G2API_GetSurfaceIndex; + import.G2API_GetSurfaceName = G2API_GetSurfaceName; + import.G2API_GetGLAName = G2API_GetGLAName; + import.G2API_SetNewOrigin = G2API_SetNewOrigin; + import.G2API_GetBoneIndex = G2API_GetBoneIndex; + import.G2API_StopBoneAnglesIndex = G2API_StopBoneAnglesIndex; + import.G2API_StopBoneAnimIndex = G2API_StopBoneAnimIndex; + import.G2API_SetBoneAnglesMatrixIndex = G2API_SetBoneAnglesMatrixIndex; + import.G2API_SetAnimIndex = G2API_SetAnimIndex; + import.G2API_GetAnimIndex = G2API_GetAnimIndex; + + import.G2API_SaveGhoul2Models = G2API_SaveGhoul2Models; + import.G2API_LoadGhoul2Models = G2API_LoadGhoul2Models; + import.G2API_LoadSaveCodeDestructGhoul2Info = G2API_LoadSaveCodeDestructGhoul2Info; + import.G2API_GetAnimFileNameIndex = G2API_GetAnimFileNameIndex; + import.G2API_GetAnimFileInternalNameIndex = G2API_GetAnimFileInternalNameIndex; + import.G2API_GetSurfaceRenderStatus = G2API_GetSurfaceRenderStatus; + + //rww - RAGDOLL_BEGIN + import.G2API_SetRagDoll = G2API_SetRagDoll; + import.G2API_AnimateG2Models = G2API_AnimateG2Models; + + import.G2API_RagPCJConstraint = G2API_RagPCJConstraint; + import.G2API_RagPCJGradientSpeed = G2API_RagPCJGradientSpeed; + import.G2API_RagEffectorGoal = G2API_RagEffectorGoal; + import.G2API_GetRagBonePos = G2API_GetRagBonePos; + import.G2API_RagEffectorKick = G2API_RagEffectorKick; + import.G2API_RagForceSolve = G2API_RagForceSolve; + + import.G2API_SetBoneIKState = G2API_SetBoneIKState; + import.G2API_IKMove = G2API_IKMove; + //rww - RAGDOLL_END + + import.G2API_AddSkinGore = G2API_AddSkinGore; + import.G2API_ClearSkinGore = G2API_ClearSkinGore; + +#ifndef _XBOX + import.RMG_Init = RMG_Init; + import.CM_RegisterTerrain = InterfaceCM_RegisterTerrain; +#endif + import.SetActiveSubBSP = SV_SetActiveSubBSP; + + import.RE_RegisterSkin = RE_RegisterSkin; + import.RE_GetAnimationCFG = RE_GetAnimationCFG; + + + import.WE_GetWindVector = R_GetWindVector; + import.WE_GetWindGusting = R_GetWindGusting; + import.WE_IsOutside = R_IsOutside; + import.WE_IsOutsideCausingPain = R_IsOutsideCausingPain; + import.WE_GetChanceOfSaberFizz = R_GetChanceOfSaberFizz; + import.WE_IsShaking = R_IsShaking; + import.WE_AddWeatherZone = R_AddWeatherZone; + import.WE_SetTempGlobalFogColor = R_SetTempGlobalFogColor; + + +/* +Ghoul2 Insert End +*/ + + ge = (game_export_t *)Sys_GetGameAPI (&import); + + if (!ge) + Com_Error (ERR_DROP, "failed to load game DLL"); + + //hook up the client while we're here +#ifdef _XBOX + VM_Create("cl"); +#else + if (!VM_Create("cl")) + Com_Error (ERR_DROP, "failed to attach to the client DLL"); +#endif + + if (ge->apiversion != GAME_API_VERSION) + Com_Error (ERR_DROP, "game is version %i, not %i", ge->apiversion, + GAME_API_VERSION); + + sv.entityParsePoint = CM_EntityString(); + + // use the current msec count for a random seed + Z_TagFree(TAG_G_ALLOC); + ge->Init( sv_mapname->string, sv_spawntarget->string, sv_mapChecksum->integer, CM_EntityString(), sv.time, com_frameTime, Com_Milliseconds(), eSavedGameJustLoaded, qbLoadTransition ); + + if(!Q_stricmp(sv_mapname->string, "t1_rail") ) + { + Cvar_Set("in_shaking_rumble","0"); + } + else + { + Cvar_Set("in_shaking_rumble","1"); + } + + // clear all gentity pointers that might still be set from + // a previous level + for ( i = 0 ; i < 1 ; i++ ) { + svs.clients[i].gentity = NULL; + } +} + + + +/* +==================== +SV_GameCommand + +See if the current console command is claimed by the game +==================== +*/ +qboolean SV_GameCommand( void ) { + if ( sv.state != SS_GAME ) { + return qfalse; + } + + return ge->ConsoleCommand(); +} + diff --git a/code/server/sv_init.cpp b/code/server/sv_init.cpp new file mode 100644 index 0000000..c7d76d0 --- /dev/null +++ b/code/server/sv_init.cpp @@ -0,0 +1,752 @@ + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "../client/snd_music.h" // didn't want to put this in snd_local because of rebuild times etc. +#include "server.h" +#include "../win32/xbox_texture_man.h" +#include + +/* +Ghoul2 Insert Start +*/ +#if !defined(TR_LOCAL_H) + #include "../renderer/tr_local.h" +#endif + +#if !defined (MINIHEAP_H_INC) + #include "../qcommon/miniheap.h" +#endif + +void CM_CleanLeafCache(void); +extern void SV_FreeClient(client_t*); + +CMiniHeap *G2VertSpaceServer = NULL; +/* +Ghoul2 Insert End +*/ + + +/* +=============== +SV_SetConfigstring + +=============== +*/ +void SV_SetConfigstring (int index, const char *val) { + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error (ERR_DROP, "SV_SetConfigstring: bad index %i\n", index); + } + + if ( !val ) { + val = ""; + } + + // don't bother broadcasting an update if no change + if ( !strcmp( val, sv.configstrings[ index ] ) ) { + return; + } + + // change the string in sv + Z_Free( sv.configstrings[index] ); + sv.configstrings[index] = CopyString( val ); + + // send it to all the clients if we aren't + // spawning a new server + if ( sv.state == SS_GAME ) { + SV_SendServerCommand( NULL, "cs %i \"%s\"\n", index, val ); + } +} + + + +/* +=============== +SV_GetConfigstring + +=============== +*/ +void SV_GetConfigstring( int index, char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetConfigstring: bufferSize == %i", bufferSize ); + } + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error (ERR_DROP, "SV_GetConfigstring: bad index %i\n", index); + } + if ( !sv.configstrings[index] ) { + buffer[0] = 0; + return; + } + + Q_strncpyz( buffer, sv.configstrings[index], bufferSize ); +} + + +/* +=============== +SV_SetUserinfo + +=============== +*/ +void SV_SetUserinfo( int index, const char *val ) { + if ( index < 0 || index >= 1 ) { + Com_Error (ERR_DROP, "SV_SetUserinfo: bad index %i\n", index); + } + + if ( !val ) { + val = ""; + } + + Q_strncpyz( svs.clients[ index ].userinfo, val, sizeof( svs.clients[ index ].userinfo ) ); +} + + + +/* +=============== +SV_GetUserinfo + +=============== +*/ +void SV_GetUserinfo( int index, char *buffer, int bufferSize ) { + if ( bufferSize < 1 ) { + Com_Error( ERR_DROP, "SV_GetUserinfo: bufferSize == %i", bufferSize ); + } + if ( index < 0 || index >= 1 ) { + Com_Error (ERR_DROP, "SV_GetUserinfo: bad index %i\n", index); + } + Q_strncpyz( buffer, svs.clients[ index ].userinfo, bufferSize ); +} + + +/* +================ +SV_CreateBaseline + +Entity baselines are used to compress non-delta messages +to the clients -- only the fields that differ from the +baseline will be transmitted +================ +*/ +void SV_CreateBaseline( void ) { + gentity_t *svent; + int entnum; + + for ( entnum = 0; entnum < ge->num_entities ; entnum++ ) { + svent = SV_GentityNum(entnum); + if (!svent->inuse) { + continue; + } + if (!svent->linked) { + continue; + } + svent->s.number = entnum; + + // + // take current state as baseline + // + sv.svEntities[entnum].baseline = svent->s; + } +} + + + + +/* +=============== +SV_Startup + +Called when a game is about to begin +=============== +*/ +void SV_Startup( void ) { + if ( svs.initialized ) { + Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" ); + } + + svs.clients = (struct client_s *) Z_Malloc ( sizeof(client_t) * 1, TAG_CLIENTS, qtrue ); + svs.numSnapshotEntities = 2 * 4 * 64; + svs.initialized = qtrue; + + Cvar_Set( "sv_running", "1" ); +} + + +#ifdef _XBOX +//Xbox-only memory freeing. +extern void R_ModelFree(void); +extern void Sys_IORequestQueueClear(void); +extern void Music_Free(void); +extern void AS_FreePartial(void); +extern void G_ASPreCacheFree(void); +extern void Ghoul2InfoArray_Free(void); +extern void Ghoul2InfoArray_Reset(void); +extern void Menu_Reset(void); +extern void G2_FreeRag(void); +extern void ClearAllNavStructures(void); +extern void ClearModelsAlreadyDone(void); +extern void CL_FreeServerCommands(void); +extern void CL_FreeReliableCommands(void); +extern void CM_Free(void); +extern void ShaderEntryPtrs_Clear(void); +extern void G_FreeRoffs(void); +extern void BG_ClearVehicles(void); +extern void ClearHStringPool(void); +extern void ClearTheBonePool(void); +extern char cinematicSkipScript[64]; +extern HANDLE s_BCThread; +extern void IN_HotSwap1Off(void); +extern void IN_HotSwap2Off(void); +extern void IN_HotSwap3Off(void); +extern int cg_saberOnSoundTime[MAX_GENTITIES]; +extern char current_speeders; +extern int zfFaceShaders[3]; +extern int tfTorsoShader; +extern bool dontPillarPush; + +void SV_ClearLastLevel(void) +{ + Menu_Reset(); + Z_TagFree(TAG_G_ALLOC); + Z_TagFree(TAG_UI_ALLOC); + G_FreeRoffs(); + R_ModelFree(); + Music_Free(); + Sys_IORequestQueueClear(); + AS_FreePartial(); + G_ASPreCacheFree(); + Ghoul2InfoArray_Free(); + G2_FreeRag(); + ClearAllNavStructures(); + ClearModelsAlreadyDone(); + CL_FreeServerCommands(); + CL_FreeReliableCommands(); + CM_Free(); + ShaderEntryPtrs_Clear(); + ClearTheBonePool(); + BG_ClearVehicles(); + + cinematicSkipScript[0] = 0; + + if (svs.clients) + { + SV_FreeClient( svs.clients ); + } + + ClearHStringPool(); + + // The bink copier thread is so trivial as to not have any communication + // Rather than polling constantly to clean it up, we just check here. + // This code should only happen ONCE: + if (s_BCThread != INVALID_HANDLE_VALUE) + { + DWORD status; + if (GetExitCodeThread( s_BCThread, &status ) && (status != STILL_ACTIVE)) + { + // Thread is done. Clean up after ourselves: + CloseHandle( s_BCThread ); + s_BCThread = INVALID_HANDLE_VALUE; + } + } + + IN_HotSwap1Off(); + IN_HotSwap2Off(); + IN_HotSwap3Off(); + + memset(&cg_saberOnSoundTime, 0, MAX_GENTITIES); + memset(zfFaceShaders, -1, sizeof(zfFaceShaders)); + tfTorsoShader = -1; + + current_speeders = 0; + + dontPillarPush = false; +} +#endif + +qboolean CM_SameMap(char *server); +qboolean CM_HasTerrain(void); +void Cvar_Defrag(void); + +// Load-time animation hackery: +struct OVERLAYINFO +{ + D3DTexture *texture; + D3DSurface *surface; +}; + +OVERLAYINFO Image; +static int loadingX = 290; + +void InitLoadingAnimation( void ) +{ +/* + // Make our two textures: + Image.texture = new IDirect3DTexture9; + + // Fill in the texture headers: + DWORD pixelSize = + XGSetTextureHeader( 4, + 4, + 1, + 0, + D3DFMT_YUY2, + 0, + Image.texture, + 0, + 0 ); + + // Get pixel data, texNum is unused: + byte *pixels = (byte *)gTextures.Allocate( pixelSize, 0 ); + + // texNum is unused: + Image.texture->Register( pixels ); + + // Turn on overlays: + glw_state->device->EnableOverlay( TRUE ); + + // Get surface pointers: + Image.texture->GetSurfaceLevel( 0, &Image.surface ); + + D3DLOCKED_RECT lock; + Image.surface->LockRect( &lock, NULL, D3DLOCK_TILED ); + + // Grey? + memset( lock.pBits, 0x7f7f7f7f, lock.Pitch * 4 ); + + Image.surface->UnlockRect(); + + // Just to be safe: +// loadingIndex = 0; +*/ +} + +void UpdateLoadingAnimation( void ) +{ +/* + // Draw the image tiny, in the bottom of the screen: + RECT dst_rect = { loadingX, 390, loadingX + 8, 398 }; + RECT src_rect = { 0, 0, 4, 4 }; + + // Update this bugger. + glw_state->device->UpdateOverlay( Image.surface, &src_rect, &dst_rect, FALSE, 0 ); + loadingX += 4; + if (loadingX > 342 ) + loadingX = 290; +*/ +} + +void StopLoadingAnimation( void ) +{ +/* + // Release surfaces: + Image.surface->Release(); + Image.surface = NULL; + + // Clean up the textures we made for the overlay stuff: + Image.texture->BlockUntilNotBusy(); + delete Image.texture; + Image.texture = NULL; + + // Turn overlays back off: + glw_state->device->EnableOverlay( FALSE ); +*/ +} + +/* +================ +SV_SpawnServer + +Change the server to a new map, taking all connected +clients along with it. +================ +*/ +void SV_SpawnServer( char *iServer, ForceReload_e eForceReload, qboolean bAllowScreenDissolve ) +{ + int i; + int checksum; + char server[64]; + + Q_strncpyz( server, iServer, sizeof(server), qtrue ); + +#ifdef XBOX_DEMO + // Pause the timer if "someone is playing" + extern void Demo_TimerPause( bool bPaused ); + Demo_TimerPause( true ); +#endif + +// The following fixes for potential issues only work on Xbox +#ifdef _XBOX + extern qboolean stop_icarus; + stop_icarus = qfalse; + + //Broken scripts may leave the player locked. I think that's always bad. + extern qboolean player_locked; + player_locked = qfalse; + + //If you quit while in Matrix Mode, this never gets cleared! + extern qboolean MatrixMode; + MatrixMode = qfalse; + + // Failsafe to ensure that we don't have rumbling during level load + extern void IN_KillRumbleScripts( void ); + IN_KillRumbleScripts(); +#endif + + RE_RegisterMedia_LevelLoadBegin( server, eForceReload, bAllowScreenDissolve ); + + + Cvar_SetValue( "cl_paused", 0 ); + Cvar_Set( "timescale", "1" );//jic we were skipping + + // shut down the existing game if it is running + SV_ShutdownGameProgs(qtrue); + + Com_Printf ("------ Server Initialization ------\n%s\n", com_version->string); + Com_Printf ("Server: %s\n",server); + Cvar_Set( "ui_mapname", server ); + +#ifndef FINAL_BUILD +// extern unsigned long texturePointMax; +// Com_Printf ("Texture pool highwater mark: %u\n", texturePointMax); +#endif + +#ifdef _XBOX + // disable vsync during load for speed + qglDisable(GL_VSYNC); +#endif + + // Hope this is correct - InitGame gets called later, which does this, + // but UI_DrawConnect (in CL_MapLoading) needs it now, to properly + // mimic CG_DrawInformation: + extern SavedGameJustLoaded_e g_eSavedGameJustLoaded; + g_eSavedGameJustLoaded = eSavedGameJustLoaded; + + // don't let sound stutter and dump all stuff on the hunk + CL_MapLoading(); + + if (!CM_SameMap(server)) + { //rww - only clear if not loading the same map + CM_ClearMap(); + } +#ifndef _XBOX + else if (CM_HasTerrain()) + { //always clear when going between maps with terrain + CM_ClearMap(); + } +#endif + + // Miniheap never changes sizes, so I just put it really early in mem. + G2VertSpaceServer->ResetHeap(); + +#ifdef _XBOX + // Deletes all textures + R_DeleteTextures(); +#endif + Hunk_Clear(); + + // Moved up from below to help reduce fragmentation + if (svs.snapshotEntities) + { + Z_Free(svs.snapshotEntities); + svs.snapshotEntities = NULL; + } + + // wipe the entire per-level structure + // Also moved up, trying to do all freeing before new allocs + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( sv.configstrings[i] ) { + Z_Free( sv.configstrings[i] ); + sv.configstrings[i] = NULL; + } + } + +#ifdef _XBOX + SV_ClearLastLevel(); +#endif + + // Collect all the small allocations done by the cvar system + // This frees, then allocates. Make it the last thing before other + // allocations begin! + Cvar_Defrag(); + +/* + This is useful for debugging memory fragmentation. Please don't + remove it. +*/ +#ifdef _XBOX + // We've over-freed the info array above, this puts it back into a working state + Ghoul2InfoArray_Reset(); + + extern void Z_DumpMemMap_f(void); + extern void Z_Details_f(void); + extern void Z_TagPointers(memtag_t); + Z_DumpMemMap_f(); +// Z_TagPointers(TAG_ALL); + Z_Details_f(); +#endif + + InitLoadingAnimation(); + UpdateLoadingAnimation(); + + // init client structures and svs.numSnapshotEntities + // This is moved down quite a bit, but should be safe. And keeps + // svs.clients right at the beginning of memory + if ( !Cvar_VariableIntegerValue("sv_running") ) { + SV_Startup(); + } + + // clear out those shaders, images and Models +// R_InitImages(); +// R_InitShaders(); +// R_ModelInit(); + + // allocate the snapshot entities + svs.snapshotEntities = (entityState_t *) Z_Malloc (sizeof(entityState_t)*svs.numSnapshotEntities, TAG_CLIENTS, qtrue ); + + Music_SetLevelName(server); + + // toggle the server bit so clients can detect that a + // server has changed +//!@ svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + + // set nextmap to the same map, but it may be overriden + // by the game startup or another console command + Cvar_Set( "nextmap", va("map %s", server) ); + + + memset (&sv, 0, sizeof(sv)); + + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + sv.configstrings[i] = CopyString(""); + } + + sv.time = 1000; + G2API_SetTime(sv.time,G2T_SV_TIME); + +#ifdef _XBOX + UpdateLoadingAnimation(); + CL_StartHunkUsers(); + UpdateLoadingAnimation(); + CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum ); + UpdateLoadingAnimation(); + RE_LoadWorldMap(va("maps/%s.bsp", server)); + UpdateLoadingAnimation(); +#else + CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum, qfalse ); +#endif + + // set serverinfo visible name + Cvar_Set( "mapname", server ); + + Cvar_Set( "sv_mapChecksum", va("%i",checksum) ); + + // serverid should be different each time + sv.serverId = com_frameTime; + Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); + + // clear physics interaction links + SV_ClearWorld (); + + // media configstring setting should be done during + // the loading stage, so connected clients don't have + // to load during actual gameplay + sv.state = SS_LOADING; + + // load and spawn all other entities + SV_InitGameProgs(); + + // run a few frames to allow everything to settle + for ( i = 0 ;i < 3 ; i++ ) { + ge->RunFrame( sv.time ); + sv.time += 100; + G2API_SetTime(sv.time,G2T_SV_TIME); + } + ge->ConnectNavs(sv_mapname->string, sv_mapChecksum->integer); + + // create a baseline for more efficient communications + SV_CreateBaseline (); + + for (i=0 ; i<1 ; i++) { + // clear all time counters, because we have reset sv.time + svs.clients[i].lastPacketTime = 0; + svs.clients[i].lastConnectTime = 0; + svs.clients[i].nextSnapshotTime = 0; + + // send the new gamestate to all connected clients + if (svs.clients[i].state >= CS_CONNECTED) { + char *denied; + + // connect the client again + denied = ge->ClientConnect( i, qfalse, eNO/*qfalse*/ ); // firstTime = qfalse, qbFromSavedGame + if ( denied ) { + // this generally shouldn't happen, because the client + // was connected before the level change + SV_DropClient( &svs.clients[i], denied ); + } else { + svs.clients[i].state = CS_CONNECTED; + // when we get the next packet from a connected client, + // the new gamestate will be sent + } + } + } + + // run another frame to allow things to look at all connected clients + ge->RunFrame( sv.time ); + sv.time += 100; + G2API_SetTime(sv.time,G2T_SV_TIME); + + + // save systeminfo and serverinfo strings + SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString( CVAR_SYSTEMINFO ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + + SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); + cvar_modifiedFlags &= ~CVAR_SERVERINFO; + + // any media configstring setting now should issue a warning + // and any configstring changes should be reliably transmitted + // to all clients + sv.state = SS_GAME; + + // send a heartbeat now so the master will get up to date info + svs.nextHeartbeatTime = -9999999; + + Hunk_SetMark(); +#ifndef _XBOX + Z_Validate(); + Z_Validate(); + Z_Validate(); +#endif + + StopLoadingAnimation(); + + Com_Printf ("-----------------------------------\n"); +} + +/* +=============== +SV_Init + +Only called at main exe startup, not for each game +=============== +*/ +void SV_Init (void) { + SV_AddOperatorCommands (); + + // serverinfo vars + Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM); + sv_mapname = Cvar_Get ("mapname", "nomap", CVAR_SERVERINFO | CVAR_ROM); + + // systeminfo + Cvar_Get ("helpUsObi", "0", CVAR_SYSTEMINFO ); + sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM ); + + // server vars + sv_fps = Cvar_Get ("sv_fps", "20", CVAR_TEMP ); + sv_timeout = Cvar_Get ("sv_timeout", "120", CVAR_TEMP ); + sv_zombietime = Cvar_Get ("sv_zombietime", "2", CVAR_TEMP ); + Cvar_Get ("nextmap", "", CVAR_TEMP ); + sv_spawntarget = Cvar_Get ("spawntarget", "", 0 ); + + sv_reconnectlimit = Cvar_Get ("sv_reconnectlimit", "3", 0); + sv_showloss = Cvar_Get ("sv_showloss", "0", 0); + sv_killserver = Cvar_Get ("sv_killserver", "0", 0); + sv_mapChecksum = Cvar_Get ("sv_mapChecksum", "", CVAR_ROM); + sv_testsave = Cvar_Get ("sv_testsave", "0", 0); + sv_compress_saved_games = Cvar_Get ("sv_compress_saved_games", "1", 0); + + // Only allocated once, no point in moving it around and fragmenting + // create a heap for Ghoul2 to use for game side model vertex transforms used in collision detection + { + static CMiniHeap singleton(132096); + G2VertSpaceServer = &singleton; + } +} + + +/* +================== +SV_FinalMessage + +Used by SV_Shutdown to send a final message to all +connected clients before the server goes down. The messages are sent immediately, +not just stuck on the outgoing message list, because the server is going +to totally exit after returning from this function. +================== +*/ +void SV_FinalMessage( char *message ) { + int i, j; + client_t *cl; + + SV_SendServerCommand( NULL, "print \"%s\"", message ); + SV_SendServerCommand( NULL, "disconnect" ); + + // send it twice, ignoring rate + for ( j = 0 ; j < 2 ; j++ ) { + for (i=0, cl = svs.clients ; i < 1 ; i++, cl++) { + if (cl->state >= CS_CONNECTED) { + // force a snapshot to be sent + cl->nextSnapshotTime = -1; + SV_SendClientSnapshot( cl ); + } + } + } +} + + +/* +================ +SV_Shutdown + +Called when each game quits, +before Sys_Quit or Sys_Error +================ +*/ +void SV_Shutdown( char *finalmsg ) { + int i; + + if ( !com_sv_running || !com_sv_running->integer ) { + return; + } + + //Com_Printf( "----- Server Shutdown -----\n" ); + + if ( svs.clients && !com_errorEntered ) { + SV_FinalMessage( finalmsg ); + } + + SV_RemoveOperatorCommands(); + SV_ShutdownGameProgs(qfalse); + + if (svs.snapshotEntities) + { + Z_Free(svs.snapshotEntities); + svs.snapshotEntities = NULL; + } + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( sv.configstrings[i] ) { + Z_Free( sv.configstrings[i] ); + } + } + + // free current level + memset( &sv, 0, sizeof( sv ) ); + + // free server static data + if ( svs.clients ) { + SV_FreeClient(svs.clients); + Z_Free( svs.clients ); + } + memset( &svs, 0, sizeof( svs ) ); + + // Ensure we free any memory used by the leaf cache. + CM_CleanLeafCache(); + + Cvar_Set( "sv_running", "0" ); + + //Com_Printf( "---------------------------\n" ); +} + diff --git a/code/server/sv_main.cpp b/code/server/sv_main.cpp new file mode 100644 index 0000000..047da70 --- /dev/null +++ b/code/server/sv_main.cpp @@ -0,0 +1,572 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "server.h" +/* +Ghoul2 Insert Start +*/ +#if !defined (MINIHEAP_H_INC) + #include "../qcommon/miniheap.h" +#endif +/* +Ghoul2 Insert End +*/ + +serverStatic_t svs; // persistant server info +server_t sv; // local server +game_export_t *ge; + +cvar_t *sv_fps; // time rate for running non-clients +cvar_t *sv_timeout; // seconds without any message +cvar_t *sv_zombietime; // seconds to sink messages after disconnect +cvar_t *sv_reconnectlimit; // minimum seconds between connect messages +cvar_t *sv_showloss; // report when usercmds are lost +cvar_t *sv_killserver; // menu system can set to 1 to shut server down +cvar_t *sv_mapname; +cvar_t *sv_spawntarget; +cvar_t *sv_mapChecksum; +cvar_t *sv_serverid; +cvar_t *sv_testsave; // Run the savegame enumeration every game frame +cvar_t *sv_compress_saved_games; // compress the saved games on the way out (only affect saver, loader can read both) + +/* +============================================================================= + +EVENT MESSAGES + +============================================================================= +*/ + +/* +=============== +SV_ExpandNewlines + +Converts newlines to "\n" so a line prints nicer +=============== +*/ +char *SV_ExpandNewlines( char *in ) { + static char string[1024]; + int l; + + l = 0; + while ( *in && l < sizeof(string) - 3 ) { + if ( *in == '\n' ) { + string[l++] = '\\'; + string[l++] = 'n'; + } else { + string[l++] = *in; + } + in++; + } + string[l] = 0; + + return string; +} + +/* +====================== +SV_AddServerCommand + +The given command will be transmitted to the client, and is guaranteed to +not have future snapshot_t executed before it is executed +====================== +*/ +void SV_AddServerCommand( client_t *client, const char *cmd ) { + int index; + + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + if ( client->reliableSequence - client->reliableAcknowledge > MAX_RELIABLE_COMMANDS ) { + SV_DropClient( client, "Server command overflow" ); + return; + } + client->reliableSequence++; + index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + if ( client->reliableCommands[ index ] ) { + Z_Free( client->reliableCommands[ index ] ); + } + client->reliableCommands[ index ] = CopyString( cmd ); +} + + +/* +================= +SV_SendServerCommand + +Sends a reliable command string to be interpreted by +the client game module: "cp", "print", "chat", etc +A NULL client will broadcast to all clients +================= +*/ +void SV_SendServerCommand(client_t *cl, const char *fmt, ...) { + va_list argptr; + byte message[MAX_MSGLEN]; + int len; + client_t *client; + int j; + + message[0] = svc_serverCommand; + + va_start (argptr,fmt); + vsprintf ((char *)message+1, fmt,argptr); + va_end (argptr); + len = strlen( (char *)message ) + 1; + + if ( cl != NULL ) { + SV_AddServerCommand( cl, (char *)message ); + return; + } + + // send the data to all relevent clients + for (j = 0, client = svs.clients; j < 1 ; j++, client++) { + if ( client->state < CS_PRIMED ) { + continue; + } + SV_AddServerCommand( client, (char *)message ); + } +} + + + +/* +============================================================================== + +CONNECTIONLESS COMMANDS + +============================================================================== +*/ + +/* +================ +SVC_Status + +Responds with all the info that qplug or qspy can see about the server +and all connected players. Used for getting detailed information after +the simple info query. +================ +*/ +void SVC_Status( netadr_t from ) { + char player[1024]; + char status[MAX_MSGLEN]; + int i; + client_t *cl; + int statusLength; + int playerLength; + int score; + char infostring[MAX_INFO_STRING]; + + strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) ); + + // echo back the parameter to status. so servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) ); + + status[0] = 0; + statusLength = 0; + + for (i=0 ; i < 1 ; i++) { + cl = &svs.clients[i]; + if ( cl->state >= CS_CONNECTED ) { + if ( cl->gentity && cl->gentity->client ) { + score = cl->gentity->client->persistant[PERS_SCORE]; + } else { + score = 0; + } + Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n", + score, cl->ping, cl->name); + playerLength = strlen(player); + if (statusLength + playerLength >= sizeof(status) ) { + break; // can't hold any more + } + strcpy (status + statusLength, player); + statusLength += playerLength; + } + } + + NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status ); +} + +/* +================ +SVC_Info + +Responds with a short info message that should be enough to determine +if a user is interested in a server to do a full status +================ +*/ +static void SVC_Info( netadr_t from ) { + int i, count; + char infostring[MAX_INFO_STRING]; + + count = 0; + for ( i = 0 ; i < 1 ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + count++; + } + } + + infostring[0] = 0; + + // echo back the parameter to status. so servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) ); + + Info_SetValueForKey( infostring, "protocol", va("%i", PROTOCOL_VERSION) ); + //Info_SetValueForKey( infostring, "hostname", sv_hostname->string ); + Info_SetValueForKey( infostring, "mapname", sv_mapname->string ); + Info_SetValueForKey( infostring, "clients", va("%i", count) ); + Info_SetValueForKey( infostring, "sv_maxclients", va("%i", 1) ); + + NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring ); +} + + +/* +================= +SV_ConnectionlessPacket + +A connectionless packet has four leading 0xff +characters to distinguish it from a game channel. +Clients that are in the game can still send +connectionless packets. +================= +*/ +static void SV_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + + MSG_BeginReading( msg ); + MSG_ReadLong( msg ); // skip the -1 marker + + s = MSG_ReadStringLine( msg ); + + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c); + + if (!strcmp(c,"getstatus")) { + SVC_Status( from ); + } else if (!strcmp(c,"getinfo")) { + SVC_Info( from ); + } else if (!strcmp(c,"connect")) { + SV_DirectConnect( from ); + } else if (!strcmp(c,"disconnect")) { + // if a client starts up a local server, we may see some spurious + // server disconnect messages when their new server sees our final + // sequenced messages to the old client + } else { + Com_DPrintf ("bad connectionless packet from %s:\n%s\n" + , NET_AdrToString (from), s); + } +} + + +//============================================================================ + +/* +================= +SV_ReadPackets +================= +*/ +void SV_PacketEvent( netadr_t from, msg_t *msg ) { + int i; + client_t *cl; + int qport; + + // check for connectionless packet (0xffffffff) first + if ( msg->cursize >= 4 && *(int *)msg->data == -1) { + SV_ConnectionlessPacket( from, msg ); + return; + } + + // read the qport out of the message so we can fix up + // stupid address translating routers + MSG_BeginReading( msg ); + MSG_ReadLong( msg ); // sequence number + MSG_ReadLong( msg ); // sequence number + qport = MSG_ReadShort( msg ) & 0xffff; + + // find which client the message is from + for (i=0, cl=svs.clients ; i < 1 ; i++,cl++) { + if (cl->state == CS_FREE) { + continue; + } + if ( !NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) ) { + continue; + } + // it is possible to have multiple clients from a single IP + // address, so they are differentiated by the qport variable + if (cl->netchan.qport != qport) { + continue; + } + + // the IP port can't be used to differentiate them, because + // some address translating routers periodically change UDP + // port assignments + if (cl->netchan.remoteAddress.port != from.port) { + Com_Printf( "SV_ReadPackets: fixing up a translated port\n" ); + cl->netchan.remoteAddress.port = from.port; + } + + // make sure it is a valid, in sequence packet + if (Netchan_Process(&cl->netchan, msg)) { + // zombie clients stil neet to do the Netchan_Process + // to make sure they don't need to retransmit the final + // reliable message, but they don't do any other processing + if (cl->state != CS_ZOMBIE) { + cl->lastPacketTime = sv.time; // don't timeout + cl->frames[ cl->netchan.incomingAcknowledged & PACKET_MASK ] + .messageAcked = sv.time; + SV_ExecuteClientMessage( cl, msg ); + } + } + return; + } + + // if we received a sequenced packet from an address we don't reckognize, + // send an out of band disconnect packet to it + NET_OutOfBandPrint( NS_SERVER, from, "disconnect" ); +} + + +/* +=================== +SV_CalcPings + +Updates the cl->ping variables +=================== +*/ +void SV_CalcPings (void) { + int i, j; + client_t *cl; + int total, count; + int delta; + + for (i=0 ; i < 1 ; i++) { + cl = &svs.clients[i]; + if ( cl->state != CS_ACTIVE ) { + continue; + } + if ( cl->gentity->svFlags & SVF_BOT ) { + continue; + } + + total = 0; + count = 0; + for ( j = 0 ; j < PACKET_BACKUP ; j++ ) { + delta = cl->frames[j].messageAcked - cl->frames[j].messageSent; + if ( delta >= 0 ) { + count++; + total += delta; + } + } + if (!count) { + cl->ping = 999; + } else { + cl->ping = total/count; + if ( cl->ping > 999 ) { + cl->ping = 999; + } + } + + // let the game dll know about the ping + cl->gentity->client->ping = cl->ping; + } +} + +/* +================== +SV_CheckTimeouts + +If a packet has not been received from a client for timeout->integer +seconds, drop the conneciton. Server time is used instead of +realtime to avoid dropping the local client while debugging. + +When a client is normally dropped, the client_t goes into a zombie state +for a few seconds to make sure any final reliable message gets resent +if necessary +================== +*/ +void SV_CheckTimeouts( void ) { + int i; + client_t *cl; + int droppoint; + int zombiepoint; + + droppoint = sv.time - 1000 * sv_timeout->integer; + zombiepoint = sv.time - 1000 * sv_zombietime->integer; + + for (i=0,cl=svs.clients ; i < 1 ; i++,cl++) { + // message times may be wrong across a changelevel + if (cl->lastPacketTime > sv.time) { + cl->lastPacketTime = sv.time; + } + + if (cl->state == CS_ZOMBIE + && cl->lastPacketTime < zombiepoint) { + cl->state = CS_FREE; // can now be reused + continue; + } + if ( cl->state >= CS_CONNECTED && cl->lastPacketTime < droppoint) { + // wait several frames so a debugger session doesn't + // cause a timeout + if ( ++cl->timeoutCount > 5 ) { + SV_DropClient (cl, "timed out"); + cl->state = CS_FREE; // don't bother with zombie state + } + } else { + cl->timeoutCount = 0; + } + } +} + + +/* +================== +SV_CheckPaused +================== +*/ +qboolean SV_CheckPaused( void ) { + if ( !cl_paused->integer ) { + return qfalse; + } + + sv_paused->integer = 1; + return qtrue; +} + +/* +This wonderful hack is needed to avoid rendering frames until several camera related things +have wended their way through the network. The problem is basically that the server asks the +client where the camera is to decide what entities down to the client. However right after +certain transitions the client tends to give a wrong answer. CGCam_Disable is one such time/ +When this happens we want to dump all rendered frame until these things have happened, in +order: + +0) (This state will mean that we are awaiting state 1) +1) The server has run a frame and built a packet +2) The client has computed a camera position +3) The server has run a frame and built a packet +4) The client has recieved a packet (This state also means the game is running normally). + +We will keep track of this here: + +*/ + + +/* +================== +SV_Frame + +Player movement occurs as a result of packet events, which +happen before SV_Frame is called +================== +*/ +extern cvar_t *cl_newClock; +void SV_Frame( int msec,float fractionMsec ) { + int frameMsec; + int startTime=0; + + // the menu kills the server with this cvar + if ( sv_killserver->integer ) { + SV_Shutdown ("Server was killed.\n"); + Cvar_Set( "sv_killserver", "0" ); + return; + } + + if ( !com_sv_running->integer ) { + return; + } + + extern void SE_CheckForLanguageUpdates(void); + SE_CheckForLanguageUpdates(); // will fast-return else load different language if menu changed it + + // allow pause if only the local client is connected + if ( SV_CheckPaused() ) { + return; + } + + // go ahead and let time slip if the server really hitched badly + if ( msec > 1000 ) { + Com_DPrintf( "SV_Frame: Truncating msec of %i to 1000\n", msec ); + msec = 1000; + } + + // if it isn't time for the next frame, do nothing + if ( sv_fps->integer < 1 ) { + Cvar_Set( "sv_fps", "10" ); + } + frameMsec = 1000 / sv_fps->integer ; + + sv.timeResidual += msec; + sv.timeResidualFraction+=fractionMsec; + if (sv.timeResidualFraction>=1.0f) + { + sv.timeResidualFraction-=1.0f; + if (cl_newClock&&cl_newClock->integer) + { + sv.timeResidual++; + } + } + if ( sv.timeResidual < frameMsec ) { + return; + } + + // if time is about to hit the 32nd bit, restart the + // level, which will force the time back to zero, rather + // than checking for negative time wraparound everywhere. + // 2giga-milliseconds = 23 days, so it won't be too often + if ( sv.time > 0x70000000 ) { + SV_Shutdown( "Restarting server due to time wrapping" ); + Com_Printf("You win. if you can play this long and not die, you deserve to win.\n"); + return; + } + + // update infostrings if anything has been changed + if ( cvar_modifiedFlags & CVAR_SERVERINFO ) { + SV_SetConfigstring( CS_SERVERINFO, Cvar_InfoString( CVAR_SERVERINFO ) ); + cvar_modifiedFlags &= ~CVAR_SERVERINFO; + } + if ( cvar_modifiedFlags & CVAR_SYSTEMINFO ) { + SV_SetConfigstring( CS_SYSTEMINFO, Cvar_InfoString( CVAR_SYSTEMINFO ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + } + + if ( com_speeds->integer ) { + startTime = Sys_Milliseconds (); + } + +// SV_BotFrame( sv.time ); + + // run the game simulation in chunks + while ( sv.timeResidual >= frameMsec ) { + sv.timeResidual -= frameMsec; + sv.time += frameMsec; + G2API_SetTime(sv.time,G2T_SV_TIME); + + // let everything in the world think and move + ge->RunFrame( sv.time ); + } + + if ( com_speeds->integer ) { + time_game = Sys_Milliseconds () - startTime; + } + + SG_TestSave(); // returns immediately if not active, used for fake-save-every-cycle to test (mainly) Icarus disk code + + // check timeouts + SV_CheckTimeouts (); + + // update ping based on the last known frame from all clients + SV_CalcPings (); + + // send messages back to the clients + SV_SendClientMessages (); +} + +//============================================================================ + diff --git a/code/server/sv_savegame.cpp b/code/server/sv_savegame.cpp new file mode 100644 index 0000000..8575435 --- /dev/null +++ b/code/server/sv_savegame.cpp @@ -0,0 +1,2998 @@ +// Filename:- sv_savegame.cpp +// +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +// a little naughty, since these are in the renderer, but I need access to them for savegames, so... +// +extern void Decompress_JPG( const char *filename, byte *pJPGData, unsigned char **pic, int *width, int *height ); +extern byte *Compress_JPG(int *pOutputSize, int quality, int image_width, int image_height, byte *image_buffer, qboolean bInvertDuringCompression); + +#define JPEG_IMAGE_QUALITY 95 + +#define SG_USE_ZLIB +#define SG_FULLCOMPRESSION +#define SG_ZLIB_COMPRESSIONLEVEL 9 //Z_DEFAULT_COMPRESSION 0 FAST - 9 SMALL +#define SG_ZLIB_COMPRESSIONLEVEL_CHECKPOINT 1 //Z_DEFAULT_COMPRESSION 0 FAST - 9 SMALL + + +//#define USE_LAST_SAVE_FROM_THIS_MAP // enable this if you want to use the last explicity-loaded savegame from this map + // when respawning after dying, else it'll just load "auto" regardless + // (EF1 behaviour). I should maybe time/date check them though? + +#include "server.h" +#include "..\game\statindex.h" +#include "..\game\weapons.h" +#include "..\game\g_items.h" + +#include "..\zlib\zlib.h" + +#ifdef _XBOX + +#include "..\ui\ui_local.h" + +#include +//support for mbstowcs +HANDLE sg_Handle; + +#define SG_BLOCKSIZE 16384 +#define SG_FILESIZE SG_BLOCKSIZE * 15 +#define SG_IMAGESIZE 1024 * 4 +#define SG_SCREENSHOTSIZE 1024 * 10 +#define SG_METADATASIZE 100 +#define SG_DIRECTORYSIZE ((SG_FILESIZE + SG_IMAGESIZE+SG_SCREENSHOTSIZE+SG_METADATASIZE)/SG_BLOCKSIZE) +1 + +#define SG_BUFFERSIZE 32768 //8192 +byte sg_Buffer[SG_BUFFERSIZE]; +int sg_BufferSize; + +#define SG_FULLBUFFERSIZE 1024 * 2000; +#define SG_ZIB_COMPRESSEDBUFFERSIZE 1024 * 500 + + +byte * sg_FullBuffer; +byte * sg_FullBufferPtr; +byte * sg_FullBufferEnd; + +//byte * sg_testbuffer = NULL; + +//used for save game reading +int sg_CurrentBufferPos; +static char *CHECK_POINT_STRING= "Z:\\Checkpoint.xsv"; + +qboolean bypassFieldCompression; +qboolean gFullCompressionOn; +qboolean g_WriteFieldBufferToFile; +bool bSavingCheckpoint = false; + +extern char g_loadsaveGameName[]; +extern qboolean g_loadsaveGameNameInitialized; + +extern void *TempAlloc( unsigned long size ); +extern void TempFree(); + +#define filepathlength 120 + +struct XValidationHeader +{ + // Length of the file, including header, in bytes + DWORD dwFileLength; + + // File signature (secure hash of file data) + XCALCSIG_SIGNATURE Signature; +}; + +//validation header going into file and coming out of file +XValidationHeader sg_validationHeader; +//validation header calculated on file read to test against file +XValidationHeader sg_validationHeaderRead; + +//signature handle +HANDLE sg_sigHandle; +HANDLE sg_sigHandleRead; + + + +int SG_Write(const void * chid, const int bytesize, fileHandle_t fhSG); +qboolean SG_Close(); +int SG_Seek( fileHandle_t fhSaveGame, long offset, int origin ); +qboolean SG_TestSignature(const char * psPathlessBaseName); + + +int SG_ReadBytes(void * chid, int bytesize, fileHandle_t fhSG); + + +#endif + +#pragma warning(disable : 4786) // identifier was truncated (STL crap) +#pragma warning(disable : 4710) // function was not inlined (STL crap) +#pragma warning(disable : 4512) // yet more STL drivel... + +#include + +using namespace std; + +static char saveGameComment[iSG_COMMENT_SIZE]; + +//#define SG_PROFILE // enable for debug save stats if you want + +int giSaveGameVersion; // filled in when a savegame file is opened +fileHandle_t fhSaveGame = 0; +SavedGameJustLoaded_e eSavedGameJustLoaded = eNO; +qboolean qbSGReadIsTestOnly = qfalse; // this MUST be left in this state +char sLastSaveFileLoaded[MAX_QPATH]={0}; + +#define iSG_MAPCMD_SIZE MAX_QPATH + +#ifndef LPCSTR +typedef const char * LPCSTR; +#endif + + +static char *SG_GetSaveGameMapName(const char *psPathlessBaseName); +static void CompressMem_FreeScratchBuffer(void); + + +#ifdef SG_PROFILE + +class CChid +{ +private: + int m_iCount; + int m_iSize; +public: + CChid() + { + m_iCount = 0; + m_iSize = 0; + } + void Add(int iLength) + { + m_iCount++; + m_iSize += iLength; + } + int GetCount() + { + return m_iCount; + } + int GetSize() + { + return m_iSize; + } +}; + +typedef map CChidInfo_t; +CChidInfo_t save_info; +#endif + +LPCSTR SG_GetChidText(unsigned long chid) +{ + static char chidtext[5]; + + *(unsigned long *)chidtext = BigLong(chid); + chidtext[4] = 0; + + return chidtext; +} + + +static const char *GetString_FailedToOpenSaveGame(const char *psFilename, qboolean bOpen) +{ + static char sTemp[256]; + +// strcpy(sTemp,S_COLOR_RED); + + const char *psReference = bOpen ? "MENUS_FAILED_TO_OPEN_SAVEGAME" : "MENUS3_FAILED_TO_CREATE_SAVEGAME"; + Q_strncpyz(sTemp, va( SE_GetString(psReference), psFilename),sizeof(sTemp)); + strcat(sTemp,"\n"); + return sTemp; +} + +// (copes with up to 8 ptr returns at once) +// +static LPCSTR SG_AddSavePath( LPCSTR psPathlessBaseName ) +{ + static char sSaveName[8][MAX_OSPATH]; + static int i=0; + + i=++i&7; + + if(psPathlessBaseName) + { + char *p = strchr(psPathlessBaseName,'/'); + if (p) + { + while (p) + { + *p = '_'; + p = strchr(p,'/'); + } + } + } + Com_sprintf( sSaveName[i], MAX_OSPATH, "saves/%s.sav", psPathlessBaseName ); + return sSaveName[i]; +} + +void SG_WipeSavegame( LPCSTR psPathlessBaseName ) +{ + LPCSTR psLocalFilename ; +#ifndef _XBOX + psLocalFilename = SG_AddSavePath( psPathlessBaseName ); + FS_DeleteUserGenFile( psLocalFilename ); +#else + + if (strcmp ( "Checkpoint",psPathlessBaseName)==0) + { + psLocalFilename = CHECK_POINT_STRING; + DeleteFile( psLocalFilename); + } + else + { + unsigned short namebuffer[filepathlength]; + mbstowcs(namebuffer, psPathlessBaseName,filepathlength); + //kill the whole directory + //remove it + XDeleteSaveGame( "U:\\", namebuffer); + } +#endif +} + +static qboolean SG_Move( LPCSTR psPathlessBaseName_Src, LPCSTR psPathlessBaseName_Dst ) +{ + + +#ifndef _XBOX + LPCSTR psLocalFilename_Src = SG_AddSavePath( psPathlessBaseName_Src ); + LPCSTR psLocalFilename_Dst = SG_AddSavePath( psPathlessBaseName_Dst ); + + qboolean qbCopyWentOk = FS_MoveUserGenFile( psLocalFilename_Src, psLocalFilename_Dst ); + + if (!qbCopyWentOk) + { + Com_Printf(S_COLOR_RED "Error during savegame-rename. Check \"%s\" for write-protect or disk full!\n", psLocalFilename_Dst ); + return qfalse; + } + + return qtrue; +#else + char psLocalFilenameSrc[filepathlength]; + char psLocalFilenameDest[filepathlength]; + unsigned short widecharstring[filepathlength]; + mbstowcs(widecharstring, psPathlessBaseName_Dst, filepathlength); + + if ( ERROR_SUCCESS != XCreateSaveGame("U:\\", widecharstring, OPEN_ALWAYS, 0, psLocalFilenameDest, filepathlength)) + return qfalse; + mbstowcs(widecharstring, psPathlessBaseName_Src, filepathlength); + if ( ERROR_SUCCESS != XCreateSaveGame("U:\\", widecharstring, OPEN_EXISTING, 0, psLocalFilenameSrc, filepathlength)) + { + return qfalse; + } + + + Q_strcat(psLocalFilenameDest, filepathlength, "JK3SG.xsv"); + Q_strcat(psLocalFilenameSrc, filepathlength, "JK3SG.xsv"); + + CopyFile( psLocalFilenameSrc, psLocalFilenameDest,false); + + + return qtrue; + +#endif +} + + +/* JLFSAVEGAME used to find if there is a file on the xbox */ +#ifdef _XBOX +qboolean SG_Exists(LPCSTR psPathlessBaseName) +{ + char psLocalFilename[filepathlength]; + unsigned short widecharstring[filepathlength]; + mbstowcs(widecharstring, psPathlessBaseName, filepathlength); + if ( ERROR_SUCCESS != XCreateSaveGame("U:\\", widecharstring, CREATE_NEW, 0, psLocalFilename, filepathlength)) + { + return qtrue; + } + if ( ERROR_SUCCESS == XDeleteSaveGame("U:\\", widecharstring)) + { + return qfalse; + } + assert(0); + return qfalse; +} +#endif + + +byte *gpbCompBlock = NULL; +int giCompBlockSize = 0; +static void CompressMem_FreeScratchBuffer(void) +{ + if ( gpbCompBlock ) + { +// Z_Free( gpbCompBlock); + extern void BonePoolTempFree( void *p ); + BonePoolTempFree( gpbCompBlock ); + gpbCompBlock = NULL; + } + giCompBlockSize = 0; +} + +static byte *CompressMem_AllocScratchBuffer(int iSize) +{ + // only alloc new buffer if we need more than the existing one... + // + + if (giCompBlockSize < iSize) + { + CompressMem_FreeScratchBuffer(); + +// gpbCompBlock = (byte *) Z_Malloc(iSize, TAG_TEMP_WORKSPACE, qfalse); + extern void *BonePoolTempAlloc( unsigned long size ); + gpbCompBlock = (byte *) BonePoolTempAlloc( iSize ); + giCompBlockSize = iSize; + } + + + return gpbCompBlock; +} + + + + + + +qboolean gbSGWriteFailed = qfalse; + +static qboolean SG_Create( LPCSTR psPathlessBaseName ) +{ + gbSGWriteFailed = qfalse; + +#ifdef _XBOX + char psLocalFilename[filepathlength]; + char psScreenshotFilename[filepathlength]; + char psBigScreenshotFilename[filepathlength]; + unsigned short widecharstring[filepathlength]; + + if (strcmp ( "Checkpoint",psPathlessBaseName)==0) + { + SG_WipeSavegame( psPathlessBaseName ); + sg_Handle = CreateFile(CHECK_POINT_STRING, GENERIC_WRITE, FILE_SHARE_READ, 0, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + bSavingCheckpoint = true; + } + else + { + bSavingCheckpoint = false; + mbstowcs(widecharstring, psPathlessBaseName, filepathlength); + if ( ERROR_SUCCESS != XCreateSaveGame("U:\\", widecharstring, OPEN_ALWAYS, 0, psLocalFilename, filepathlength)) + return qfalse; + + // create the path for the screenshot file + strcpy(psScreenshotFilename, psLocalFilename); + Q_strcat(psScreenshotFilename, filepathlength, "saveimage.xbx"); + + // create the path for the big (ui) screenshot file + strcpy(psBigScreenshotFilename, psLocalFilename); + Q_strcat(psBigScreenshotFilename, filepathlength, "screenshot.xbx"); + + // create the path for the savegame + Q_strcat(psLocalFilename, filepathlength, "JK3SG.xsv"); + + sg_Handle = CreateFile(psLocalFilename, GENERIC_WRITE, FILE_SHARE_READ, 0, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + } + //clear the buffer + sg_BufferSize = 0; + + DWORD bytesWritten; +// save spot for validation + +//calculate the size of the signature + DWORD dwSigSize = XCalculateSignatureGetSize( XCALCSIG_FLAG_SAVE_GAME ); + DWORD dwHeaderSize = sizeof(DWORD) + dwSigSize; + +//clear the signature + ZeroMemory( &sg_validationHeader, dwHeaderSize ); + + + WriteFile(sg_Handle, // handle to file + &sg_validationHeader, // data buffer + dwHeaderSize, // number of bytes to write + &bytesWritten, // number of bytes written + NULL // overlapped buffer + ); + //start the validation key creation + // Start the signature hash + sg_sigHandle = XCalculateSignatureBegin( 0 ); + if( sg_sigHandle == INVALID_HANDLE_VALUE ) + return FALSE; + + if ( strcmp("Checkpoint", psPathlessBaseName) != 0 ) + { + // attempt to copy the last screenshot to the save game directory + if( !CopyFile("z:\\saveimage.xbx", psScreenshotFilename, FALSE) ) + { + CopyFile("d:\\base\\media\\defaultsaveimage.xbx", psScreenshotFilename, FALSE); + } + + // Ditto for the large screenshot (the one we display in the ui) + if( !CopyFile("z:\\screenshot.xbx", psBigScreenshotFilename, FALSE) ) + { + CopyFile("d:\\base\\media\\defaultsaveimage.xbx", psBigScreenshotFilename, FALSE); + } + } + +#else + SG_WipeSavegame( psPathlessBaseName ); + LPCSTR psLocalFilename = SG_AddSavePath( psPathlessBaseName ); + fhSaveGame = FS_FOpenFileWrite( psLocalFilename ); +#endif + +#ifdef _XBOX + if (!sg_Handle) +#else + if(!fhSaveGame) +#endif + { + Com_Printf(GetString_FailedToOpenSaveGame(psLocalFilename,qfalse));//S_COLOR_RED "Failed to create new savegame file \"%s\"\n", psLocalFilename ); + return qfalse; + } + +#ifdef SG_PROFILE + assert( save_info.empty() ); +#endif + + giSaveGameVersion = iSAVEGAME_VERSION; + SG_Append('_VER', &giSaveGameVersion, sizeof(giSaveGameVersion)); + + return qtrue; +} + +// called from the ERR_DROP stuff just in case the error occured during loading of a saved game, because if +// we didn't do this then we'd run out of quake file handles after the 8th load fail... +// +void SG_Shutdown() +{ + if (fhSaveGame ) + { + FS_FCloseFile( fhSaveGame ); + fhSaveGame = NULL; + } + + eSavedGameJustLoaded = eNO; // important to do this if we ERR_DROP during loading, else next map you load after + // a bad save-file you'll arrive at dead :-) + + // and this bit stops people messing up the laoder by repeatedly stabbing at the load key during loads... + // + extern qboolean gbAlreadyDoingLoad; + gbAlreadyDoingLoad = qfalse; +} + +#ifdef _XBOX +int Compress_ZLIB(const byte *pIn, int iLength, byte *pOut,int &outLength); + +qboolean SG_CloseWrite() +{ + DWORD bytesWritten; + DWORD dwSuccess; + unsigned int filelength ; + + if (gFullCompressionOn) + { + int sg_FullBufferSize; + int sg_CompressedBufferSize; + byte * sg_CompressedBuffer = NULL; + //write out the compressed buffer + sg_FullBufferSize = sg_FullBufferPtr - sg_FullBuffer; +#ifdef _DEBUG + Com_Printf (" FullBufferSize = %i\n", sg_FullBufferSize); +#endif + sg_CompressedBufferSize = SG_ZIB_COMPRESSEDBUFFERSIZE; + sg_CompressedBuffer = CompressMem_AllocScratchBuffer(sg_CompressedBufferSize);//allocate memory + memset(sg_CompressedBuffer, 0, sg_CompressedBufferSize); + + sg_CompressedBufferSize = Compress_ZLIB( sg_FullBuffer,sg_FullBufferSize, sg_CompressedBuffer,sg_CompressedBufferSize); + + //if ( sg_testbuffer) + // Z_Free(sg_testbuffer); + // sg_testbuffer = (byte*) Z_Malloc ( sg_CompressedBufferSize, TAG_TEMP_WORKSPACE, qfalse); + // memcpy(sg_testbuffer, sg_CompressedBuffer, sg_CompressedBufferSize); + // size of original data + if (!WriteFile(sg_Handle, + &sg_FullBufferSize, + sizeof( sg_FullBufferSize), + &bytesWritten, + NULL + )) + return qfalse; + + dwSuccess = XCalculateSignatureUpdate( sg_sigHandle, (BYTE*)(&sg_FullBufferSize), + sizeof( sg_FullBufferSize)); + //size of compressed data + if (!WriteFile(sg_Handle, + &sg_CompressedBufferSize, + sizeof( sg_CompressedBufferSize), + &bytesWritten, + NULL + )) + return qfalse; + + dwSuccess = XCalculateSignatureUpdate( sg_sigHandle, (BYTE*)(&sg_CompressedBufferSize), + sizeof( sg_CompressedBufferSize)); + //get the file size + filelength =GetFileSize (sg_Handle, NULL); + //find out how much space is left in the file + + //If compression didn't happen, write the whole thing. + if(sg_CompressedBufferSize == SG_ZIB_COMPRESSEDBUFFERSIZE) { + if (!WriteFile(sg_Handle, + sg_FullBuffer, + sg_FullBufferSize, + &bytesWritten, + NULL + )) + return qfalse; + + dwSuccess = XCalculateSignatureUpdate( sg_sigHandle, (BYTE*)(sg_FullBuffer), + sg_FullBufferSize); + } else { + //compressed data + if (!WriteFile(sg_Handle, + sg_CompressedBuffer, + sg_CompressedBufferSize, + &bytesWritten, + NULL + )) + return qfalse; + + dwSuccess = XCalculateSignatureUpdate( sg_sigHandle, (BYTE*)(sg_CompressedBuffer), + sg_CompressedBufferSize); + } + + CompressMem_FreeScratchBuffer(); + //get the file size + + + } + else + { + //clear the buffer to the file + if (!WriteFile(sg_Handle, // handle to file + sg_Buffer, // data buffer + sg_BufferSize, // number of bytes to write + &bytesWritten, // number of bytes written + NULL // overlapped buffer + )) + return qfalse; + } + filelength =GetFileSize (sg_Handle, NULL); +// FILL THE SAVE GAME TO 15 BLOCKS + + int fillBufferSize = SG_FILESIZE - filelength - sizeof(fillBufferSize);; + if(fillBufferSize > 0) { + byte * fillBuffer = (byte *) Z_Malloc(fillBufferSize, TAG_TEMP_WORKSPACE, qfalse); + memset ( fillBuffer, 0, fillBufferSize); + + //size of fill data + if (!WriteFile(sg_Handle, + &fillBufferSize, + sizeof( fillBufferSize), + &bytesWritten, + NULL + )) + return qfalse; + + dwSuccess = XCalculateSignatureUpdate( sg_sigHandle, (BYTE*)(&fillBufferSize), + sizeof( fillBufferSize)); + + + if (!WriteFile(sg_Handle, // handle to file + fillBuffer, // data buffer + fillBufferSize, // number of bytes to write + &bytesWritten, // number of bytes written + NULL // overlapped buffer + )) + { + Z_Free (fillBuffer); + return qfalse; + } + dwSuccess = XCalculateSignatureUpdate( sg_sigHandle, (BYTE*)(fillBuffer), + fillBufferSize); + Z_Free (fillBuffer); + } else { + fillBufferSize = 0; + if (!WriteFile(sg_Handle, + &fillBufferSize, + sizeof( fillBufferSize), + &bytesWritten, + NULL + )) + return qfalse; + dwSuccess = XCalculateSignatureUpdate( sg_sigHandle, (BYTE*)(&fillBufferSize), + sizeof( fillBufferSize)); + } + + + //get the length of the file + filelength =GetFileSize (sg_Handle, NULL); + + + + + // create the validation code + sg_validationHeader.dwFileLength = filelength; + // Release signature resources + dwSuccess =XCalculateSignatureEnd( sg_sigHandle, &sg_validationHeader.Signature ); + assert( dwSuccess == ERROR_SUCCESS ); + //seek to the first of the file + SG_Seek(NULL,0,FS_SEEK_SET); + //SetFilePointer(sg_Handle,0,0,FILE_BEGIN); + //write the validation codes + DWORD dwSigSize = XCalculateSignatureGetSize( XCALCSIG_FLAG_SAVE_GAME ); + DWORD dwHeaderSize = sizeof(DWORD) + dwSigSize; + + WriteFile (sg_Handle, &sg_validationHeader,dwHeaderSize,&bytesWritten, NULL); + return SG_Close(); +} +#endif + + + + + + +qboolean SG_Close() +{ +#ifdef _XBOX + CloseHandle(sg_Handle); + sg_Handle = NULL; + +#else + assert( fhSaveGame ); + FS_FCloseFile( fhSaveGame ); +#endif + fhSaveGame = NULL; + +#ifdef SG_PROFILE + if (!sv_testsave->integer) + { + CChidInfo_t::iterator it; + int iCount = 0, iSize = 0; + + Com_DPrintf(S_COLOR_CYAN "================================\n"); + Com_DPrintf(S_COLOR_WHITE "CHID Count Size\n\n"); + for(it = save_info.begin(); it != save_info.end(); ++it) + { + Com_DPrintf("%s %5d %8d\n", SG_GetChidText((*it).first), (*it).second.GetCount(), (*it).second.GetSize()); + iCount += (*it).second.GetCount(); + iSize += (*it).second.GetSize(); + } + Com_DPrintf("\n" S_COLOR_WHITE "%d chunks making %d bytes\n", iCount, iSize); + Com_DPrintf(S_COLOR_CYAN "================================\n"); + save_info.clear(); + } +#endif + + CompressMem_FreeScratchBuffer(); + return qtrue; +} + + +qboolean SG_Open( LPCSTR psPathlessBaseName ) +{ +// if ( fhSaveGame ) // hmmm... +// { // +// SG_Close(); // +// } // + assert( !fhSaveGame); // I'd rather know about this + if(!psPathlessBaseName) + { + return qfalse; + } +//JLFSAVEGAME + + gFullCompressionOn = qfalse; + + +#ifdef _XBOX + unsigned short saveGameName[filepathlength]; + char directoryInfo[filepathlength]; + char psLocalFilename[filepathlength]; + DWORD bytesRead; + + if ( strcmp(psPathlessBaseName, "Checkpoint")==0) + { + sg_Handle = NULL; + sg_Handle = CreateFile(CHECK_POINT_STRING,GENERIC_READ, FILE_SHARE_READ, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + } + else + { + mbstowcs(saveGameName, psPathlessBaseName,filepathlength); + + XCreateSaveGame("U:\\", saveGameName, OPEN_EXISTING, 0,directoryInfo, filepathlength); + + strcpy (psLocalFilename , directoryInfo); + strcat (psLocalFilename , "JK3SG.xsv"); + + sg_Handle = NULL; + sg_Handle = CreateFile(psLocalFilename, GENERIC_READ, FILE_SHARE_READ, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + } + + if (!sg_Handle) +#else + + LPCSTR psLocalFilename = SG_AddSavePath( psPathlessBaseName ); + FS_FOpenFileRead( psLocalFilename, &fhSaveGame, qtrue ); //qtrue = dup handle, so I can close it ok later + if (!fhSaveGame) +#endif + + { +// Com_Printf(S_COLOR_RED "Failed to open savegame file %s\n", psLocalFilename); + Com_DPrintf(GetString_FailedToOpenSaveGame(psLocalFilename, qtrue)); + + return qfalse; + } +#ifdef _XBOX + //read the validation header + + DWORD dwSigSize = XCalculateSignatureGetSize( XCALCSIG_FLAG_SAVE_GAME ); + DWORD dwHeaderSize = sizeof(DWORD) + dwSigSize; + if (!ReadFile( sg_Handle, &sg_validationHeader, dwHeaderSize, &bytesRead, NULL ) || + bytesRead != dwHeaderSize) + { + SG_Close(); + Com_Printf (S_COLOR_RED "File \"%s\" has no sig",psPathlessBaseName); + return qfalse; + } + //initialize buffer data + sg_BufferSize = 0; + sg_CurrentBufferPos =0; + + //check the filesize + unsigned int filelength =GetFileSize (sg_Handle, NULL); + + if (sg_validationHeader.dwFileLength != filelength) + { + SG_Close(); + Com_Printf (S_COLOR_RED "File \"%s\" has wrong length"); + return qfalse; + } + + //start the validation key creation + // Start the signature hash + sg_sigHandleRead = XCalculateSignatureBegin( 0 ); + if( sg_sigHandleRead == INVALID_HANDLE_VALUE ) + { + SG_Close(); + return FALSE; + } + +#endif + giSaveGameVersion=-1;//jic + if(!SG_Read('_VER', &giSaveGameVersion, sizeof(giSaveGameVersion))) + { + SG_Close(); + return qfalse; + } + + if (giSaveGameVersion != iSAVEGAME_VERSION) + { + SG_Close(); + Com_Printf (S_COLOR_RED "File \"%s\" has version # %d (expecting %d)\n",psPathlessBaseName, giSaveGameVersion, iSAVEGAME_VERSION); + return qfalse; + } + + return qtrue; +} + +// you should only call this when you know you've successfully opened a savegame, and you want to query for +// whether it's an old (street-copy) version, or a new (expansion-pack) version +// +int SG_Version(void) +{ + return giSaveGameVersion; +} + +void SV_WipeGame_f(void) +{ + if (Cmd_Argc() != 2) + { + Com_Printf (S_COLOR_RED "USAGE: wipe \n"); + return; + } + if (!stricmp (Cmd_Argv(1), "Checkpoint") ) + { + Com_Printf (S_COLOR_RED "Can't wipe 'Checkpoint'\n"); + return; + } + char fileNameBuffer[filepathlength]; + const char *psFilename = Cmd_Argv(1); + if (g_loadsaveGameName[0] != 0 && g_loadsaveGameNameInitialized == qtrue) + { + strcpy (fileNameBuffer,g_loadsaveGameName); + psFilename = fileNameBuffer; + g_loadsaveGameName[0]=0; + } + + SG_WipeSavegame(psFilename); +// Com_Printf("%s has been wiped\n", Cmd_Argv(1)); // wurde gelöscht in german, but we've only got one string +// Com_Printf("Ok\n"); // no localization of this +} + +/* +// Store given string in saveGameComment for later use when game is +// actually saved +*/ +void SG_StoreSaveGameComment(const char *sComment) +{ + memmove(saveGameComment,sComment,iSG_COMMENT_SIZE); +} + +qboolean SV_TryLoadTransition( const char *mapname ) +{ + char *psFilename = va( "hub/%s", mapname ); + + Com_Printf (S_COLOR_CYAN "Restoring game \"%s\"...\n", psFilename); + + if ( !SG_ReadSavegame( psFilename ) ) + {//couldn't load a savegame + return qfalse; + } + Com_Printf (S_COLOR_CYAN "%s.\n",SE_GetString("MENUS_DONE")); + + return qtrue; +} + +qboolean gbAlreadyDoingLoad = qfalse; + + +//extern void UI_xboxErrorPopup(xbErrorPopupType popup); +void SV_LoadGame_f(void) +{ + char fileNameBuffer[filepathlength]; + if (gbAlreadyDoingLoad) + { + Com_DPrintf ("( Already loading, ignoring extra 'load' commands... )\n"); + return; + } + +// // check server is running +// // +// if ( !com_sv_running->integer ) +// { +// Com_Printf( "Server is not running\n" ); +// return; +// } + + if (Cmd_Argc() != 2) + { + Com_Printf ("USAGE: load \n"); + return; + } + + const char *psFilename = Cmd_Argv(1); + if (strstr (psFilename, "..") || strstr (psFilename, "/") || strstr (psFilename, "\\") ) + { + Com_Printf (S_COLOR_RED "Bad loadgame name.\n"); + return; + } + + if (g_loadsaveGameName[0] != 0 && g_loadsaveGameNameInitialized == qtrue) + { + strcpy (fileNameBuffer,g_loadsaveGameName); + psFilename = fileNameBuffer; + g_loadsaveGameName[0]=0; + } + + + // special case, if doing a respawn then check that the available auto-save (if any) is from the same map + // as we're currently on (if in a map at all), if so, load that "auto", else re-load the last-loaded file... + // + + + if (!stricmp(psFilename, "*respawn")) + { + psFilename = "Checkpoint"; // default to standard respawn behaviour + +/* + // see if there's a last-loaded file to even check against as regards loading... + // + if ( sLastSaveFileLoaded[0] ) + { + LPCSTR psServerInfo = sv.configstrings[CS_SERVERINFO]; + LPCSTR psMapName = Info_ValueForKey( psServerInfo, "mapname" ); + //psMapName = SE_GetString ("MENUS", psMapName); + + char *psMapNameOfAutoSave = NULL; + + + if (!SG_TestSignature("Checkpoint")) + psMapNameOfAutoSave = NULL; + else + psMapNameOfAutoSave = SG_GetSaveGameMapName("Checkpoint"); + + if ( !Q_stricmp(psMapName,"_brig") ) + {//if you're in the brig and there is no autosave, load the last loaded savegame + if ( !psMapNameOfAutoSave ) + { + psFilename = sLastSaveFileLoaded; + } + } + else + { +#ifdef USE_LAST_SAVE_FROM_THIS_MAP + // if the map name within the name of the last save file we explicitly loaded is the same + // as the current map, then use that... + // + char *psMapNameOfLastSaveFileLoaded = SG_GetSaveGameMapName(sLastSaveFileLoaded); + + if (!Q_stricmp(psMapName,psMapNameOfLastSaveFileLoaded))) + { + psFilename = sLastSaveFileLoaded; + } + else +#endif + if (!(psMapName && psMapNameOfAutoSave && !Q_stricmp(psMapName,psMapNameOfAutoSave))) + { + // either there's no auto file, or it's from a different map to the one we've just died on... + // + psFilename = sLastSaveFileLoaded; + } + } + } +*/ + //default will continue to load auto + } + Com_Printf (S_COLOR_CYAN "%s\n",va(SE_GetString("MENUS_LOADING_MAPNAME"), psFilename)); + + gbAlreadyDoingLoad = qtrue; + + Cvar_Set("levelSelectCheat", "-1"); + if (!SG_ReadSavegame(psFilename)) { + extern void Menus_CloseByName(const char *p); + if ( strcmp("Checkpoint", psFilename)==0) + { + Menus_CloseByName( "xbox_error_popup" ); + UI_xboxErrorPopup(XB_POPUP_LOAD_FAILED); + } + else + { + Menus_CloseByName( "xbox_error_popup" ); + UI_xboxErrorPopup(XB_POPUP_LOAD_FAILED); + } + + gbAlreadyDoingLoad = qfalse; // do NOT do this here now, need to wait until client spawn, unless the load failed. + + } else + { + Menus_CloseAll(); + Com_Printf (S_COLOR_CYAN "%s.\n",SE_GetString("MENUS_DONE")); + } +} + +qboolean SG_GameAllowedToSaveHere(qboolean inCamera); + + +//JLF notes +// save game will be in charge of creating a new directory +void SV_SaveGame_f(void) +{ + // check server is running + // + if ( !com_sv_running->integer ) + { + Com_Printf( S_COLOR_RED "Server is not running\n" ); + return; + } + + if (sv.state != SS_GAME) + { + Com_Printf (S_COLOR_RED "You must be in a game to save.\n"); + return; + } + + // check args... + // + if ( Cmd_Argc() != 2 ) + { + Com_Printf( "USAGE: \"save \"\n" ); + return; + } + + + if (svs.clients[0].frames[svs.clients[0].netchan.outgoingSequence & PACKET_MASK].ps.stats[STAT_HEALTH] <= 0) + { + Com_Printf (S_COLOR_RED "\n%s\n", SE_GetString("SP_INGAME_CANT_SAVE_DEAD")); + return; + } + + //this check catches deaths even the instant you die, like during a slo-mo death! + gentity_t *svent; + svent = SV_GentityNum(0); + if (svent->client->stats[STAT_HEALTH]<=0) + { + Com_Printf (S_COLOR_RED "\n%s\n", SE_GetString("SP_INGAME_CANT_SAVE_DEAD")); + return; + } + + char *psFilename = Cmd_Argv(1); + + +// if (!stricmp (psFilename, "current")) +// { +// Com_Printf (S_COLOR_RED "Can't save to 'current'\n"); +// return; +// } + + if (strstr (psFilename, "..") || strstr (psFilename, "/") || strstr (psFilename, "\\") ) + { + Com_Printf (S_COLOR_RED "Bad savegame name.\n"); + return; + } + + if (!SG_GameAllowedToSaveHere(qfalse)) //full check + return; // this prevents people saving via quick-save now during cinematics. + + if ( !stricmp (psFilename, "Checkpoint") || !stricmp (psFilename, "auto")) + { + +#ifdef _XBOX + extern void SCR_PrecacheScreenshot(); //scr_scrn.cpp +// SCR_PrecacheScreenshot(); +#endif + SG_StoreSaveGameComment(""); // clear previous comment/description, which will force time/date comment. + } + + Com_Printf (S_COLOR_CYAN "%s \"%s\"...\n", SE_GetString("CON_TEXT_SAVING_GAME"), psFilename); + + if (SG_WriteSavegame(psFilename, qfalse)) + { + Com_Printf (S_COLOR_CYAN "%s.\n",SE_GetString("MENUS_DONE")); + } + else + { + Com_Printf (S_COLOR_RED "%s.\n",SE_GetString("MENUS_FAILED_TO_OPEN_SAVEGAME")); + } +} + + + +//--------------- +static void WriteGame(qboolean autosave) +{ + SG_Append('GAME', &autosave, sizeof(autosave)); + + if (autosave) + { + // write out player ammo level, health, etc... + // + extern void SV_Player_EndOfLevelSave(void); + SV_Player_EndOfLevelSave(); // this sets up the various cvars needed, so we can then write them to disk + // + char s[MAX_STRING_CHARS]; + + // write health/armour etc... + // + memset(s,0,sizeof(s)); + Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) ); + SG_Append('CVSV', &s, sizeof(s)); + + // write ammo... + // + memset(s,0,sizeof(s)); + Cvar_VariableStringBuffer( "playerammo", s, sizeof(s) ); + SG_Append('AMMO', &s, sizeof(s)); + + // write inventory... + // + memset(s,0,sizeof(s)); + Cvar_VariableStringBuffer( "playerinv", s, sizeof(s) ); + SG_Append('IVTY', &s, sizeof(s)); + + // the new JK2 stuff - force powers, etc... + // + memset(s,0,sizeof(s)); + Cvar_VariableStringBuffer( "playerfplvl", s, sizeof(s) ); + SG_Append('FPLV', &s, sizeof(s)); + } +} + +static qboolean ReadGame (void) +{ + qboolean qbAutoSave; + SG_Read('GAME', (void *)&qbAutoSave, sizeof(qbAutoSave)); + + if (qbAutoSave) + { + char s[MAX_STRING_CHARS]={0}; + + // read health/armour etc... + // + memset(s,0,sizeof(s)); + SG_Read('CVSV', (void *)&s, sizeof(s)); + Cvar_Set( sCVARNAME_PLAYERSAVE, s ); + + // read ammo... + // + memset(s,0,sizeof(s)); + SG_Read('AMMO', (void *)&s, sizeof(s)); + Cvar_Set( "playerammo", s); + + // read inventory... + // + memset(s,0,sizeof(s)); + SG_Read('IVTY', (void *)&s, sizeof(s)); + Cvar_Set( "playerinv", s); + + // read force powers... + // + memset(s,0,sizeof(s)); + SG_Read('FPLV', (void *)&s, sizeof(s)); + Cvar_Set( "playerfplvl", s ); + } + + return qbAutoSave; +} + +//--------------- + + +// write all CVAR_SAVEGAME cvars +// these will be things like model, name, ... +// +extern cvar_t *cvar_vars; // I know this is really unpleasant, but I need access for scanning/writing latched cvars during save games + +void SG_WriteCvars(void) +{ + cvar_t *var; + int iCount = 0; + + + // count the cvars... + // + for (var = cvar_vars; var; var = var->next) + { + if (!(var->flags & CVAR_SAVEGAME)) + { + continue; + } + iCount++; + } + + // store count... + // + SG_Append('CVCN', &iCount, sizeof(iCount)); + + // write 'em... + // + for (var = cvar_vars; var; var = var->next) + { + if (!(var->flags & CVAR_SAVEGAME)) + { + continue; + } + SG_Append('CVAR', var->name, strlen(var->name) + 1); + SG_Append('VALU', var->string, strlen(var->string) + 1); + } +} + +void SG_ReadCvars(void) +{ + int iCount; + char *psName; + char *psValue; +#ifdef _XBOX + char buttonConfigInfo[128]; + char triggerConfigInfo[128]; + + Q_strncpyz(buttonConfigInfo, Cvar_VariableString("ui_buttonconfig"), 128, qfalse); + Q_strncpyz(triggerConfigInfo, Cvar_VariableString("ui_triggerconfig"), 128, qfalse); +#endif + SG_Read('CVCN', &iCount, sizeof(iCount)); + + for (int i = 0; i < iCount; i++) + { + SG_Read('CVAR', NULL, 0, (void **)&psName); + SG_Read('VALU', NULL, 0, (void **)&psValue); + + Cvar_Set (psName, psValue); + + Z_Free( psName ); + Z_Free( psValue ); + } +#ifdef _XBOX + + if ((Q_stricmp(buttonConfigInfo,Cvar_VariableString("ui_buttonconfig"))!=0)||(Q_stricmp(triggerConfigInfo, Cvar_VariableString("ui_triggerconfig"))!=0)) + { +extern void UI_UpdateSettingsCvars( void ); + UI_UpdateSettingsCvars(); + } +#endif + +} + +void SG_WriteServerConfigStrings( void ) +{ + int iCount = 0; + int i; // not in FOR statement in case compiler goes weird by reg-optimising it then failing to get the address later + + // count how many non-blank server strings there are... + // + for ( i=0; i + { + iCount++; + } + } + } + + SG_Append('CSCN', &iCount, sizeof(iCount)); + + // now write 'em... + // + for (i=0; i %s", psMapName ); + switch (difflevel ) + { + case 0: + strcpy(sComment, "@MENUS_APPRENTICE"); + break; + case 1: + strcpy(sComment, "@MENUS_JEDI"); + break; + + case 2: + if (handicap >50) + strcpy(sComment, "@MENUS_JEDI_KNIGHT"); + else + strcpy(sComment, "@MENUS_JEDI_MASTER"); + break; + + default: + strcpy(sComment, "@MENUS_JEDI_MASTER"); + } + } + else + { + strcpy(sComment,saveGameComment); + } + + SG_Append('COMM', sComment, sizeof(sComment)); + + // Add Date/Time/Map stamp + time_t now; + time(&now); + SG_Append('CMTM', &now, sizeof(time_t)); + + Com_DPrintf("Saving: current (%s)\n", sComment); +} + + +// Test to see if the given file name is in the save game directory +// then grab the comment if it's there +// +int SG_GetSaveGameComment(const char *psPathlessBaseName, char *sComment, char *sMapName) +{ + int ret = 0; + time_t tFileTime; + + qbSGReadIsTestOnly = qtrue; // do NOT leave this in this state + + if ( !SG_Open( psPathlessBaseName )) + { + qbSGReadIsTestOnly = qfalse; + return 0; + } + + if (SG_Read( 'COMM', sComment, iSG_COMMENT_SIZE )) + { + if (SG_Read( 'CMTM', &tFileTime, sizeof( time_t ))) //read + { + if (SG_Read('MPCM', sMapName, iSG_MAPCMD_SIZE )) // read + { + ret = tFileTime; + } + } + } + qbSGReadIsTestOnly = qfalse; + + XCalculateSignatureEnd( sg_sigHandleRead, &sg_validationHeaderRead.Signature ); + + if (!SG_Close()) + { + return 0; + } + return ret; +} + + +// read the mapname field from the supplied savegame file +// +// returns NULL if not found +// +static char *SG_GetSaveGameMapName(const char *psPathlessBaseName) +{ + static char sMapName[iSG_MAPCMD_SIZE]={0}; + char *psReturn = NULL; + if (SG_GetSaveGameComment(psPathlessBaseName, NULL, sMapName)) + { + psReturn = sMapName; + } + + return psReturn; +} + + +// pass in qtrue to set as loading screen, else pass in pvDest to read it into there... +// +/* +static qboolean SG_ReadScreenshot(qboolean qbSetAsLoadingScreen, void *pvDest = NULL); +static qboolean SG_ReadScreenshot(qboolean qbSetAsLoadingScreen, void *pvDest) +{ +#ifdef _XBOX + return qfalse; +#else + qboolean bReturn = qfalse; + + // get JPG screenshot data length... + // + int iScreenShotLength = 0; + SG_Read('SHLN', &iScreenShotLength, sizeof(iScreenShotLength)); + // + // alloc enough space plus extra 4K for sloppy JPG-decode reader to not do memory access violation... + // + byte *pJPGData = (byte *) Z_Malloc(iScreenShotLength + 4096,TAG_TEMP_SAVEGAME_WORKSPACE, qfalse); + // + // now read the JPG data... + // + SG_Read('SHOT', pJPGData, iScreenShotLength, 0); + // + // decompress JPG data... + // + byte *pDecompressedPic = NULL; + int iWidth, iHeight; + Decompress_JPG( "[savegame]", pJPGData, &pDecompressedPic, &iWidth, &iHeight ); + // + // if the loaded image is the same size as the game is expecting, then copy it to supplied arg (if present)... + // + if (iWidth == SG_SCR_WIDTH && iHeight == SG_SCR_HEIGHT) + { + bReturn = qtrue; + + if (pvDest) + { + memcpy(pvDest, pDecompressedPic, SG_SCR_WIDTH * SG_SCR_HEIGHT * 4); + } + + if (qbSetAsLoadingScreen) + { + SCR_SetScreenshot((byte *)pDecompressedPic, SG_SCR_WIDTH, SG_SCR_HEIGHT); + } + } + + Z_Free( pJPGData ); + Z_Free( pDecompressedPic ); + + return bReturn; +#endif +} +// Gets the savegame screenshot +// +qboolean SG_GetSaveImage( const char *psPathlessBaseName, void *pvAddress ) +{ + if(!psPathlessBaseName) + { + return qfalse; + } +//JLFSAVEGAME +#if 0 + unsigned short saveGameName[filepathlength]; + char directoryInfo[filepathlength]; + char psLocalFilename[filepathlength]; + DWORD bytesRead; + + mbstowcs(saveGameName, psPathlessBaseName,filepathlength); + + XCreateSaveGame("U:\\", saveGameName, OPEN_ALWAYS, 0,directoryInfo, filepathlength); + + strcpy (psLocalFilename , directoryInfo); + strcat (psLocalFilename , "saveimage.xbx"); + + + sg_Handle = NULL; + sg_Handle = CreateFile(psLocalFilename, GENERIC_READ, FILE_SHARE_READ, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + + if (!sg_Handle) + return qfalse; + + + +#else + + if (!SG_Open(psPathlessBaseName)) + { + return qfalse; + } + + SG_Read('COMM', NULL, 0, NULL); // skip + SG_Read('CMTM', NULL, sizeof( time_t )); + + qboolean bGotSaveImage = SG_ReadScreenshot(qfalse, pvAddress); + + SG_Close(); +#endif + return bGotSaveImage; +} + + +static void SG_WriteScreenshot(qboolean qbAutosave, LPCSTR psMapName) +{ +#ifndef _XBOX + byte *pbRawScreenShot = NULL; + + if( qbAutosave ) + { + // try to read a levelshot (any valid TGA/JPG etc named the same as the map)... + // + int iWidth = SG_SCR_WIDTH; + int iHeight= SG_SCR_HEIGHT; + byte byBlank[SG_SCR_WIDTH * SG_SCR_HEIGHT * 4] = {0}; + + pbRawScreenShot = SCR_TempRawImage_ReadFromFile(va("levelshots/%s.tga",psMapName), &iWidth, &iHeight, byBlank, qtrue); // qtrue = vert flip + } + + if (!pbRawScreenShot) + { + pbRawScreenShot = SCR_GetScreenshot(0); + } + + + int iJPGDataSize = 0; + byte *pJPGData = Compress_JPG(&iJPGDataSize, JPEG_IMAGE_QUALITY, SG_SCR_WIDTH, SG_SCR_HEIGHT, pbRawScreenShot, qfalse); + SG_Append('SHLN', &iJPGDataSize, sizeof(iJPGDataSize)); + SG_Append('SHOT', pJPGData, iJPGDataSize); + Z_Free(pJPGData); + SCR_TempRawImage_CleanUp(); +#endif +} +*/ + +qboolean SG_GameAllowedToSaveHere(qboolean inCamera) +{ + if (!inCamera) { + if ( !com_sv_running || !com_sv_running->integer ) + { + return qfalse; // Com_Printf( S_COLOR_RED "Server is not running\n" ); + } + + if (CL_IsRunningInGameCinematic()) + { + return qfalse; //nope, not during a video + } + + if (sv.state != SS_GAME) + { + return qfalse; // Com_Printf (S_COLOR_RED "You must be in a game to save.\n"); + } + + //No savegames from "_" maps + if ( !sv_mapname || (sv_mapname->string != NULL && sv_mapname->string[0] == '_') ) + { + return qfalse; // Com_Printf (S_COLOR_RED "Cannot save on holodeck or brig.\n"); + } + + if (svs.clients[0].frames[svs.clients[0].netchan.outgoingSequence & PACKET_MASK].ps.stats[STAT_HEALTH] <= 0) + { + return qfalse; // Com_Printf (S_COLOR_RED "\nCan't savegame while dead!\n"); + } + } + if (!ge) + return inCamera; // only happens when called to test if inCamera + + return ge->GameAllowedToSaveHere(); +} + + +qboolean SG_WriteSavegame(const char *psPathlessBaseName, qboolean qbAutosave) +{ + char levelname[128]; + char *levelnameptr; + + // I'm going to jump in front of a fucking bus if I ever have to do something so hacky in the future. + int startOfFunction = Sys_Milliseconds(); + + if (!qbAutosave && !SG_GameAllowedToSaveHere(qfalse)) //full check + return qfalse; // this prevents people saving via quick-save now during cinematics + + int iPrevTestSave = sv_testsave->integer; + sv_testsave->integer = 0; + + // Write out server data... + // + LPCSTR psServerInfo = sv.configstrings[CS_SERVERINFO]; + LPCSTR psMapName = Info_ValueForKey( psServerInfo, "mapname" ); + LPCSTR psUserMapName; + //strcpy(levelname, psMapName); + psUserMapName = SE_GetString ("MENUS", psMapName); + +//JLF +#ifdef _XBOX + char mapname[filepathlength]; + char numberedmapname[filepathlength]; + int mapnumber =0; + char numberbuffer[10]; + char pathlessBaseName[filepathlength]; + strcpy (pathlessBaseName, psPathlessBaseName); + + if (strcmp ("auto",pathlessBaseName)==0) + strcpy(pathlessBaseName,"Checkpoint"); + + if ( !strcmp("Checkpoint",pathlessBaseName)) + { + strcpy(mapname, psUserMapName); + strcpy(numberedmapname, pathlessBaseName); + bypassFieldCompression = qtrue; + } + + + + + //if ( !strcmp("JKSG3",pathlessBaseName)) + //{ + //strcpy(mapname, psMapName); + // strcpy (mapname, pathlessBaseName); + // strcpy( numberedmapname, mapname); + //} + else + { + strcpy(mapname, psUserMapName); + // strcpy(numberedmapname, pathlessBaseName); + //strcpy(numberedmapname, mapname); + bypassFieldCompression = qfalse; + } + +#ifdef SG_FULLCOMPRESSION + // We're about to allocate a ton of memory. Let's throw out all sounds: + extern int SND_FreeOldestSound( void ); + SND_FreeOldestSound(); + + bypassFieldCompression = qtrue; + unsigned long fullBufferSize = SG_FULLBUFFERSIZE; +// sg_FullBuffer = (byte *) Z_Malloc(fullBufferSize, TAG_TEMP_WORKSPACE, qfalse); + // Take this from temp space: + sg_FullBuffer = (byte *) TempAlloc( fullBufferSize ); + + sg_FullBufferPtr = sg_FullBuffer; + sg_FullBufferEnd = sg_FullBuffer + SG_FULLBUFFERSIZE; +#endif + + if (!qbAutosave && strcmp ( "Checkpoint",pathlessBaseName)!=0) + { + do + { + strcpy( numberedmapname, mapname); + Com_sprintf(numberbuffer,sizeof(numberbuffer)," %02i",mapnumber); + strcat ( numberedmapname,numberbuffer); + mapnumber++; + } + while (SG_Exists( numberedmapname)); + } + +/* + while (strcmp("Checkpoint",pathlessBaseName)!=0 && !qbAutosave && SG_Exists( numberedmapname)) + { + strcpy( numberedmapname, mapname); + + Com_sprintf(numberbuffer,sizeof(numberbuffer),"_%02i",mapnumber); + strcat ( numberedmapname,numberbuffer); + mapnumber++; + } +*/ +// SG_Create( numberedmapname); + +#else + if ( !strcmp("quick",pathlessBaseName)) + { + SG_StoreSaveGameComment(va("--> %s <--",psMapName)); + } +#endif //moved up from below + + if (strcmp ( "Checkpoint",pathlessBaseName)==0) + strcpy (numberedmapname,pathlessBaseName); +// if(!SG_Create( "current" )) + gFullCompressionOn = qfalse; + g_WriteFieldBufferToFile = qfalse; + + if(!SG_Create( numberedmapname )) + { + Com_Printf (GetString_FailedToOpenSaveGame(numberedmapname,qfalse));//S_COLOR_RED "Failed to create savegame\n"); + SG_WipeSavegame( numberedmapname ); + sv_testsave->integer = iPrevTestSave; + if ( sg_FullBuffer ) + { +// Z_Free( sg_FullBuffer); + TempFree(); + sg_FullBuffer = NULL; + } + return qfalse; + + } +//#endif +//END JLF + + char sMapCmd[iSG_MAPCMD_SIZE]={0}; + strcpy( sMapCmd,psMapName); // need as array rather than ptr because const strlen needed for MPCM chunk + + SG_WriteComment(qbAutosave, numberedmapname); +// SG_WriteScreenshot(qbAutosave, sMapCmd); + SG_Append('MPCM', sMapCmd, sizeof(sMapCmd)); +#ifdef SG_FULLCOMPRESSION + gFullCompressionOn = qtrue; + g_WriteFieldBufferToFile = qtrue; +#endif + SG_WriteCvars(); + + WriteGame (qbAutosave); + + // Write out all the level data... + // + if (!qbAutosave) + { + SG_Append('TIME', (void *)&sv.time, sizeof(sv.time)); + SG_Append('TIMR', (void *)&sv.timeResidual, sizeof(sv.timeResidual)); + + CM_WritePortalState(); + SG_WriteServerConfigStrings(); + } + ge->WriteLevel(qbAutosave); // always done now, but ent saver only does player if auto + +#ifdef _XBOX + SG_CloseWrite(); +#else + SG_Close(); +#endif + if (gbSGWriteFailed) + { + Com_Printf (GetString_FailedToOpenSaveGame(numberedmapname,qfalse));//S_COLOR_RED "Failed to write savegame!\n"); + SG_WipeSavegame( numberedmapname ); + sv_testsave->integer = iPrevTestSave; + if ( sg_FullBuffer ) + { +// Z_Free( sg_FullBuffer); + TempFree(); + sg_FullBuffer = NULL; + } + return qfalse; + } + + if ( sg_FullBuffer ) + { +// Z_Free( sg_FullBuffer); + TempFree(); + sg_FullBuffer = NULL; + } + sv_testsave->integer = iPrevTestSave; + extern qboolean Script_RunDeferred ( itemDef_t* item, const char **args ); + extern void ui_resetSaveGameList(); + ui_resetSaveGameList(); + + // The first thing that the deferred script is going to do is to close the "Saving" + // popup, but we need it to be up for at least a second, so sit here in a fucking + // busy-loop. See note at start of function, re: bus. + while( Sys_Milliseconds() < startOfFunction + 1000 ) + { + // Do nothing. Yes, nothing. + } + + Script_RunDeferred( NULL, NULL); + return qtrue; +} + +void loadCompressedData(); + +qboolean SG_ReadSavegame(const char *psPathlessBaseName) +{ + char sComment[iSG_COMMENT_SIZE]; + char sMapCmd [iSG_MAPCMD_SIZE]; + qboolean qbAutosave; + + int iPrevTestSave = sv_testsave->integer; + sv_testsave->integer = 0; + + if (!SG_TestSignature(psPathlessBaseName)) + return false; + + if (!SG_Open( psPathlessBaseName )) + { + Com_Printf (GetString_FailedToOpenSaveGame(psPathlessBaseName, qtrue));//S_COLOR_RED "Failed to open savegame \"%s\"\n", psPathlessBaseName); + sv_testsave->integer = iPrevTestSave; + return qfalse; + } + + // this check isn't really necessary, but it reminds me that these two strings may actually be the same physical one. + // + if (psPathlessBaseName != sLastSaveFileLoaded && (strcmp(psPathlessBaseName,"Checkpoint"))) + { + Q_strncpyz(sLastSaveFileLoaded,psPathlessBaseName,sizeof(sLastSaveFileLoaded)); + } + + // Read in all the server data... + // + SG_Read('COMM', sComment, sizeof(sComment)); + Com_DPrintf("Reading: %s\n", sComment); + SG_Read( 'CMTM', NULL, sizeof( time_t )); + + +// SG_ReadScreenshot(qtrue); // qboolean qbSetAsLoadingScreen + SG_Read('MPCM', sMapCmd, sizeof(sMapCmd)); +#ifdef SG_USE_ZLIB +#ifdef SG_FULLCOMPRESSION + loadCompressedData(); + +#endif +#endif + + SG_ReadCvars(); + + // read game state + qbAutosave = ReadGame(); + eSavedGameJustLoaded = (qbAutosave)?eAUTO:eFULL; + + SV_SpawnServer(sMapCmd, eForceReload_NOTHING, (eSavedGameJustLoaded != eFULL) ); // note that this also trashes the whole G_Alloc pool as well (of course) + + // read in all the level data... + // + if (!qbAutosave) + { + SG_Read('TIME', (void *)&sv.time, sizeof(sv.time)); + SG_Read('TIMR', (void *)&sv.timeResidual, sizeof(sv.timeResidual)); + CM_ReadPortalState(); + SG_ReadServerConfigStrings(); + } + ge->ReadLevel(qbAutosave, qbLoadTransition); // always done now, but ent reader only does player if auto + + + //finish reading the file (blank data) + int fillBufferSize = SG_FILESIZE; + DWORD bytesRead; + qboolean dwSuccess; + byte * fillBuffer = (byte *) Z_Malloc(fillBufferSize, TAG_TEMP_WORKSPACE, qfalse); + memset ( fillBuffer, 0, fillBufferSize); + + if(!ReadFile(sg_Handle,&fillBufferSize, sizeof(fillBufferSize), &bytesRead, NULL)) + { + Z_Free (fillBuffer); + return qfalse; + } + if(fillBufferSize) { + dwSuccess = XCalculateSignatureUpdate( sg_sigHandleRead, (BYTE*)(&fillBufferSize),sizeof(fillBufferSize)); + + + if(!ReadFile(sg_Handle,fillBuffer, fillBufferSize, &bytesRead, NULL)) + { + Z_Free (fillBuffer); + return qfalse; + } + dwSuccess = XCalculateSignatureUpdate( sg_sigHandleRead, (BYTE*)(fillBuffer),fillBufferSize); + } + + + Z_Free (fillBuffer); + + //sigend here + dwSuccess =XCalculateSignatureEnd( sg_sigHandleRead, &sg_validationHeaderRead.Signature ); + assert( dwSuccess == ERROR_SUCCESS ); + + DWORD dwSigSize = XCalculateSignatureGetSize( XCALCSIG_FLAG_SAVE_GAME ); + + if ( sg_FullBuffer) + { +// Z_Free(sg_FullBuffer); + TempFree(); + sg_FullBuffer = NULL; + } + + if(!SG_Close()) + { + Com_Printf (GetString_FailedToOpenSaveGame(psPathlessBaseName,qfalse));//S_COLOR_RED "Failed to close savegame\n"); + sv_testsave->integer = iPrevTestSave; + return qfalse; + } + if( memcmp( &sg_validationHeader.Signature, &sg_validationHeaderRead.Signature, dwSigSize ) != 0 ) + { + Com_Printf (GetString_FailedToOpenSaveGame(psPathlessBaseName,qfalse));//S_COLOR_RED "Failed to close savegame\n"); + sv_testsave->integer = iPrevTestSave; + return qfalse; + } + + sv_testsave->integer = iPrevTestSave; + return qtrue; +} +#ifdef SG_USE_ZLIB + +static void* sv_alloc(void* opaque, unsigned int items, unsigned int size) +{ + return TempAlloc(items * size); +} + +static void sv_free(void* opaque, void* address) +{ + //Free does nothing, we'll free it all at the end. +} + + +int ZEXPORT sv_compress (unsigned char* dest, unsigned long *destLen, const unsigned char* source, unsigned long sourceLen, int level) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = sv_alloc; + stream.zfree = sv_free; + stream.opaque = 0; + + err = deflateInit(&stream, level); + if (err != Z_OK) return err; + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + + +int Compress_ZLIB(const byte *pIn, int iLength, byte *pOut,int & outLength) +{ + uLongf outlengthlocal; + outlengthlocal = outLength; + if(bSavingCheckpoint) { + sv_compress ( pOut,&outlengthlocal, pIn, iLength, SG_ZLIB_COMPRESSIONLEVEL_CHECKPOINT); + } else { + sv_compress ( pOut,&outlengthlocal, pIn, iLength, SG_ZLIB_COMPRESSIONLEVEL); + } + outLength = outlengthlocal; + return outLength; +} + + + +#else + +int Compress_RLE(const byte *pIn, int iLength, byte *pOut) +{ + int iCount=0,iOutIndex=0; + + while (iCount < iLength) + { + int iIndex = iCount; + byte b = pIn[iIndex++]; + + while (iIndex1 && pIn[iIndex]!=pIn[iIndex-2])){ + iIndex++; + } + while (iIndex 0) + { + count = (signed char) *pIn++; + if (count>0) + { + memset(pOut,*pIn++,count); + } + else + if (count<0) + { + count = (signed char) -count; + memcpy(pOut,pIn,count); + pIn += count; + } + pOut += count; + iDecompressedBytesRemaining -= count; + } +} +#endif + +// simulate decompression over original data (but don't actually do it), to test de/compress validity... +// +qboolean Verify_RLE(const byte *pOut, const byte *pIn, int iDecompressedBytesRemaining) +{ + signed char count; + const byte *pOutEnd = &pOut[iDecompressedBytesRemaining]; + + while (iDecompressedBytesRemaining > 0) + { + if (pOut >= pOutEnd) + return qfalse; + count = (signed char) *pIn++; + if (count>0) + { + //memset(pOut,*pIn++,count); + int iMemSetByte = *pIn++; + for (int i=0; iinteger) + return -1; + + // malloc enough to cope with uncompressable data (it'll never grow to 2* size, so)... + // +#ifdef SG_USE_ZLIB + pbOut = CompressMem_AllocScratchBuffer(iLength* 1.01 +40 ); +#else + pbOut = CompressMem_AllocScratchBuffer(iLength*2 ); +#endif + // + // compress it... + // +#ifdef SG_USE_ZLIB + int iOutputLength; + iOutputLength =iLength * 1.01 +40; + iOutputLength= Compress_ZLIB(pbData, iLength, pbOut,iOutputLength); +#else + int iOutputLength = Compress_RLE(pbData, iLength, pbOut); +#endif + // + // worth compressing?... + // + if (iOutputLength >= iLength) + return -1; + // + // compression code works? (I'd hope this is always the case, but for safety)... + // +#ifndef SG_USE_ZLIB + if (!Verify_RLE(pbData, pbOut, iLength)) + return -1; +#endif + return iOutputLength; +} + + +#ifdef _XBOX// function for xbox + + + +int SG_WriteFullCompress(const void * chid, const int bytesize, fileHandle_t fhSG) +{ + DWORD bytesWritten; + if (g_WriteFieldBufferToFile) + { + //clear the buffer to the file + if (!WriteFile(sg_Handle, // handle to file + sg_Buffer, // data buffer + sg_BufferSize, // number of bytes to write + &bytesWritten, // number of bytes written + NULL // overlapped buffer + )) + return 0; + //signature work + DWORD dwSuccess = XCalculateSignatureUpdate( sg_sigHandle, (BYTE*)(sg_Buffer), + sg_BufferSize); + + g_WriteFieldBufferToFile = qfalse; + } + int copysize; + + //JLF if we need to make the buffer for savegames smaller!!! + //write out placeholders for sizes for compressed data + //store the offsets for them so that we can comeback and fill them in + // when we do savewrite() + + if ( sg_FullBufferPtr + bytesize >= sg_FullBufferEnd) + { + + Com_Error(ERR_FATAL, "sg_fullBufferPtr overflow\n"); + + } + + + + + if ( sg_FullBufferPtr + bytesize < sg_FullBufferEnd) + { + memcpy(sg_FullBufferPtr, chid, bytesize); + sg_FullBufferPtr += bytesize; + return bytesize; + } + + return -1; +} + + +int SG_Write(const void * chid, const int bytesize, fileHandle_t fhSG) +{ + DWORD bytesWritten; + + if (sg_BufferSize + bytesize>= SG_BUFFERSIZE) + { +#ifdef _SG_FULLCOMPRESSION + compressFileBuffer(); + if (!WriteFile(sg_Handle, // handle to file + sg_Buffer, // data buffer + sg_BufferSize, // number of bytes to write + &bytesWritten, // number of bytes written + NULL // overlapped buffer + )) +#else + if (!WriteFile(sg_Handle, // handle to file + sg_Buffer, // data buffer + sg_BufferSize, // number of bytes to write + &bytesWritten, // number of bytes written + NULL // overlapped buffer + )) +#endif + { + return 0; + } + + DWORD dwSuccess = XCalculateSignatureUpdate( sg_sigHandle, (BYTE*)(sg_Buffer), + sg_BufferSize); + sg_BufferSize = 0; + + + } + if (bytesize >= SG_BUFFERSIZE) + { + if (!WriteFile(sg_Handle, // handle to file + chid, // data buffer + bytesize, // number of bytes to write + &bytesWritten, // number of bytes written + NULL // overlapped buffer + )) + { + return 0; + } + DWORD dwSuccess = XCalculateSignatureUpdate( sg_sigHandle, (BYTE*)(chid), + bytesize); + sg_BufferSize = 0; + } + else + { + + byte * tempptr = &(sg_Buffer[sg_BufferSize]); + memcpy(tempptr, chid, bytesize); + sg_BufferSize += bytesize; + } + return bytesize; +} + + +#else +//pass through function +int SG_Write(const void * chid, const int bytesize, fileHandle_t fhSaveGame) +{ + return FS_Write( chid, bytesize, fhSaveGame); +} + +#endif + + + +qboolean SG_Append(unsigned long chid, const void *pvData, int iLength) +{ + unsigned int uiCksum; + unsigned int uiSaved; + +#ifdef _DEBUG + int i; + unsigned long *pTest; + + pTest = (unsigned long *) pvData; + for (i=0; iinteger) + { + uiCksum = Com_BlockChecksum (pvData, iLength); + if (gFullCompressionOn) + uiSaved = SG_WriteFullCompress(&chid, sizeof(chid), fhSaveGame); + else + uiSaved = SG_Write(&chid, sizeof(chid), fhSaveGame); + +/**/ + byte *pbCompressedData = NULL; + int iCompressedLength = -1; + if (! bypassFieldCompression) + iCompressedLength = CompressMem((byte*)pvData, iLength, pbCompressedData); + if (iCompressedLength != -1) + { + // compressed... (write length field out as -ve) + // + iLength = -iLength; + uiSaved += SG_Write(&iLength, sizeof(iLength), fhSaveGame); + iLength = -iLength; + // + // [compressed length] + // + uiSaved += SG_Write(&iCompressedLength, sizeof(iCompressedLength), fhSaveGame); + // + // compressed data... + // + uiSaved += SG_Write(pbCompressedData, iCompressedLength, fhSaveGame); + // + // CRC... + // + uiSaved += SG_Write(&uiCksum, sizeof(uiCksum), fhSaveGame); + + if (uiSaved != sizeof(chid) + sizeof(iLength) + sizeof(uiCksum) + sizeof(iCompressedLength) + iCompressedLength) + { + Com_Printf(S_COLOR_RED "Failed to write %s chunk\n", SG_GetChidText(chid)); + gbSGWriteFailed = qtrue; + return qfalse; + } + } + else + /**/ + { + // uncompressed... + // + + if (gFullCompressionOn) + uiSaved += SG_WriteFullCompress(&iLength, sizeof(iLength), fhSaveGame); + else + uiSaved += SG_Write(&iLength, sizeof(iLength), fhSaveGame); + // + // uncompressed data... + // + if (gFullCompressionOn) + uiSaved += SG_WriteFullCompress(pvData, iLength, fhSaveGame); + else + uiSaved += SG_Write( pvData, iLength, fhSaveGame); + // + // CRC... + // + + if (gFullCompressionOn) + uiSaved += SG_WriteFullCompress(&uiCksum, sizeof(uiCksum), fhSaveGame); + else + uiSaved += SG_Write(&uiCksum, sizeof(uiCksum), fhSaveGame); + + if (uiSaved != sizeof(chid) + sizeof(iLength) + sizeof(uiCksum) + iLength) + { + Com_Printf(S_COLOR_RED "Failed to write %s chunk\n", SG_GetChidText(chid)); + gbSGWriteFailed = qtrue; + return qfalse; + } + } + + #ifdef SG_PROFILE + save_info[chid].Add(iLength); + #endif + } + + return qtrue; +} + + +#ifdef _XBOX// function for xbox +//SG_ReadBytes replaces FS_Read. I was going to use SG_Read but it is already in use +/* +int SG_ReadBytes(void * chid, int bytesize, fileHandle_t fhSG) +{ + byte* bufferptr; + unsigned char* destptr; + DWORD retBytesRead=0; + DWORD bytesRead =0; + int segmentLength; + + + //bufferptr = (byte*)chid; + //destptr = NULL; + + if (ReadFile(sg_Handle, // handle to file + chid, // data buffer + bytesize, // number of bytes to write + &bytesRead, // number of bytes written + NULL // overlapped buffer + )) + { return bytesRead; + } + else + { + return 0; + } + + return retBytesRead; +} +*/ + +void loadCompressedData() +{ + //get the size of the compressed data + int compressedsize; + int uncompressedsize; + byte * compressbuffer; + DWORD bytesRead; + int bufferTransferSize; + byte * compressBufferPtr; + byte * bufferptr; + + + //read the uncompressed size out of buffer + SG_ReadBytes(&uncompressedsize, sizeof(uncompressedsize), NULL); + + //read the compressed size out of the buffer + SG_ReadBytes(&compressedsize, sizeof(compressedsize), NULL); + + + + //ReadFile(sg_Handle, &compressedsize, sizeof ( compressedsize), &bytesRead,NULL); + + //ReadFile(sg_Handle,&uncompressedsize, sizeof ( uncompressedsize), &bytesRead, NULL); + + //transfer the little buffer over to the big one + + bufferTransferSize = sg_BufferSize - sg_CurrentBufferPos; + compressedsize += bufferTransferSize; + compressbuffer = CompressMem_AllocScratchBuffer(compressedsize); + bufferptr = &(sg_Buffer[sg_CurrentBufferPos]); + memcpy (compressbuffer, bufferptr,bufferTransferSize); + +/* { + byte * cbuffer, *tbuffer; + cbuffer = compressbuffer; + tbuffer = sg_testbuffer; + int i; + for( i = 0 ; i < bufferTransferSize;i++) + { + if ( *cbuffer != *tbuffer) + { + Com_Printf("ZLib Error: wrong data index %i!!!\n", i); + } + cbuffer++; + tbuffer++; + } + } + int memcmpval = memcmp(sg_testbuffer,compressbuffer, bufferTransferSize); + if (memcmpval!=0) + Com_Printf("ZLib Error: wrong data1!!!\n"); +*/ + //get stuff uncompressed and into a buffer + compressBufferPtr = compressbuffer + bufferTransferSize; + //clear the little buffer + sg_BufferSize = 0; + bufferptr = sg_Buffer; + + if(compressedsize - bufferTransferSize == SG_ZIB_COMPRESSEDBUFFERSIZE) { + //File wasn't compressed. + sg_FullBuffer = (byte *) TempAlloc( uncompressedsize ); + memset(sg_FullBuffer, 0, uncompressedsize); + sg_FullBufferPtr = sg_FullBuffer; + sg_FullBufferEnd = sg_FullBuffer + uncompressedsize; + + memcpy(sg_FullBuffer, sg_Buffer + sg_CurrentBufferPos, bufferTransferSize - sg_CurrentBufferPos); + + if(ReadFile(sg_Handle,sg_FullBuffer + bufferTransferSize, uncompressedsize - bufferTransferSize, &bytesRead, NULL)) { + if (bytesRead ==0) + { + CompressMem_FreeScratchBuffer(); + return; + } + + DWORD dwSuccess = XCalculateSignatureUpdate( sg_sigHandleRead, (BYTE*)(sg_FullBuffer + bufferTransferSize), + uncompressedsize - bufferTransferSize); + } + + + } else { + + if(ReadFile(sg_Handle,compressBufferPtr, compressedsize - bufferTransferSize, &bytesRead, NULL)) + + { + if (bytesRead ==0) + { + CompressMem_FreeScratchBuffer(); + return; + } + + DWORD dwSuccess = XCalculateSignatureUpdate( sg_sigHandleRead, (BYTE*)(compressBufferPtr), + compressedsize - bufferTransferSize); + + // if (memcmp(sg_testbuffer,compressbuffer, compressedsize)!=0) + // Com_Printf("ZLib Error: wrong data2!!!\n"); + + // sg_FullBuffer = (byte *) Z_Malloc(uncompressedsize, TAG_TEMP_WORKSPACE, qfalse); + sg_FullBuffer = (byte *) TempAlloc( uncompressedsize ); + sg_FullBufferPtr = sg_FullBuffer; + sg_FullBufferEnd = sg_FullBuffer + uncompressedsize; + + + DeCompress_ZLIB((byte *)sg_FullBuffer, uncompressedsize, compressbuffer, compressedsize); + } + } + sg_CurrentBufferPos = 0; + gFullCompressionOn = qtrue; + CompressMem_FreeScratchBuffer(); +} + + +int SG_ReadBytes(void * chid, int bytesize, fileHandle_t fhSG) +{ + byte* bufferptr; + unsigned char* destptr; + DWORD retBytesRead=0; + DWORD bytesRead =0; + int segmentLength; + + + //bufferptr = (byte*)chid; + //destptr = NULL; + + if ( bytesize < (sg_BufferSize - sg_CurrentBufferPos)) + { + bufferptr = &(sg_Buffer[sg_CurrentBufferPos]); + memcpy(chid,bufferptr, bytesize); + sg_CurrentBufferPos+= bytesize; + retBytesRead = bytesize; + } + else + { + destptr = (byte*)((void*)chid); + + while ( bytesize >0) + { + bufferptr = &(sg_Buffer[sg_CurrentBufferPos]); + segmentLength = sg_BufferSize - sg_CurrentBufferPos; + if (segmentLength <= bytesize) + { + memcpy(destptr, bufferptr, segmentLength); + destptr += segmentLength; + retBytesRead += segmentLength; + bytesize -= segmentLength; + sg_CurrentBufferPos += segmentLength; + } + else + { + memcpy(destptr, bufferptr, bytesize); + destptr += bytesize; + retBytesRead += bytesize; + sg_CurrentBufferPos += bytesize; + bytesize -= bytesize; + + } + + if (sg_BufferSize - sg_CurrentBufferPos <= 0 && bytesize >0) + { + + if (gFullCompressionOn) + { + //find the size of the buffer left + int copysize = SG_BUFFERSIZE; + if (copysize >= sg_FullBufferEnd- sg_FullBufferPtr) + copysize = sg_FullBufferEnd- sg_FullBufferPtr; + memcpy(sg_Buffer, sg_FullBufferPtr, copysize); + sg_FullBufferPtr += copysize; + sg_BufferSize = copysize; + bufferptr = sg_Buffer; + sg_CurrentBufferPos = 0; + if ( copysize == 0) + return 0; + } + else + { + if (ReadFile(sg_Handle, // handle to file + sg_Buffer, // data buffer + SG_BUFFERSIZE, // number of bytes to write + &bytesRead, // number of bytes written + NULL // overlapped buffer + )) + { + if (bytesRead== 0) + return 0; + sg_BufferSize = bytesRead; + sg_CurrentBufferPos = 0; + bufferptr = sg_Buffer; + //sig processing + DWORD dwSuccess = XCalculateSignatureUpdate( sg_sigHandleRead, (BYTE*)(sg_Buffer), + sg_BufferSize); + + } + else + { + return 0; + } + } + } + + } + } + return retBytesRead; +} + + + +// handle offset origin +//fhSaveGame not used (use global variable) +int SG_Seek( fileHandle_t fhSaveGame, long offset, int origin ) +{ + switch (origin) + { + case FS_SEEK_CUR: + return SetFilePointer( + sg_Handle, // handle to file + offset, // bytes to move pointer + NULL, // bytes to move pointer + FILE_CURRENT // starting point + ); + break; + case FS_SEEK_END: + return SetFilePointer( + sg_Handle, // handle to file + offset, // bytes to move pointer + NULL, // bytes to move pointer + FILE_END // starting point + ); + break; + default: + //FS_SEEK_SET: + return SetFilePointer( + sg_Handle, // handle to file + offset, // bytes to move pointer + NULL, // bytes to move pointer + FILE_BEGIN // starting point + ); + } + return 0; +} + +#else +//pass through function +int SG_ReadBytes(void * chid, int bytesize, fileHandle_t fhSaveGame) +{ + return FS_Read( chid, bytesize, fhSaveGame); +} + + +int SG_Seek( fileHandle_t fhSaveGame, long offset, int origin ) +{ + return FS_Seek(fhSaveGame, offset, origin); +} + +#endif + + + +// Pass in pvAddress (or NULL if you want memory to be allocated) +// if pvAddress==NULL && ppvAddressPtr == NULL then the block is discarded/skipped. +// +// If iLength==0 then it counts as a query, else it must match the size found in the file +// +// function doesn't return if error (uses ERR_DROP), unless "qbSGReadIsTestOnly == qtrue", then NZ return = success +// +static int SG_Read_Actual(unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr, qboolean bChunkIsOptional) +{ + unsigned int uiLoadedCksum; + unsigned int uiCksum; + unsigned int uiLoadedLength; + unsigned long ulLoadedChid; + unsigned int uiLoaded; + char sChidText1[MAX_QPATH]; + char sChidText2[MAX_QPATH]; + qboolean qbTransient = qfalse; + + Com_DPrintf("Attempting read of chunk %s length %d\n", SG_GetChidText(chid), iLength); + + // Load in chid and length... + // + uiLoaded = SG_ReadBytes( &ulLoadedChid, sizeof(ulLoadedChid), fhSaveGame); + uiLoaded+= SG_ReadBytes( &uiLoadedLength, sizeof(uiLoadedLength),fhSaveGame); + + qboolean bBlockIsCompressed = ((int)uiLoadedLength < 0); + if ( bBlockIsCompressed) + { + uiLoadedLength = -((int)uiLoadedLength); + } + + // Make sure we are loading the correct chunk... + // + if( ulLoadedChid != chid) + { + if (bChunkIsOptional) + { + SG_Seek( fhSaveGame, -(int)uiLoaded, FS_SEEK_CUR ); + return 0; + } + + strcpy(sChidText1, SG_GetChidText(ulLoadedChid)); + strcpy(sChidText2, SG_GetChidText(chid)); + if (!qbSGReadIsTestOnly) + { + return 0; + //Com_Error(ERR_DROP, "Loaded chunk ID (%s) does not match requested chunk ID (%s)", sChidText1, sChidText2); + } + return 0; + } + + // Find length of chunk and make sure it matches the requested length... + // + if( iLength ) // .. but only if there was one specified + { + if(iLength != (int)uiLoadedLength) + { + if (!qbSGReadIsTestOnly) + { + return 0; + //Com_Error(ERR_DROP, "Loaded chunk (%s) has different length than requested", SG_GetChidText(chid)); + } + return 0; + } + } + iLength = uiLoadedLength; // for retval + + // alloc?... + // + if ( !pvAddress ) + { + pvAddress = Z_Malloc(iLength, TAG_SAVEGAME, qfalse); + // + // Pass load address back... + // + if( ppvAddressPtr ) + { + *ppvAddressPtr = pvAddress; + } + else + { + qbTransient = qtrue; // if no passback addr, mark block for skipping + } + } + + // Load in data and magic number... + // + unsigned int uiCompressedLength=0; + if (bBlockIsCompressed) + { + // + // read compressed data length... + // + uiLoaded += SG_ReadBytes( &uiCompressedLength, sizeof(uiCompressedLength),fhSaveGame); + // + // alloc space... + // + byte *pTempRLEData = (byte *)Z_Malloc(uiCompressedLength, TAG_SAVEGAME, qfalse); + // + // read compressed data... + // + uiLoaded += SG_ReadBytes( pTempRLEData, uiCompressedLength, fhSaveGame ); + // + // decompress it... + // +#ifdef SG_USE_ZLIB + DeCompress_ZLIB((byte *)pvAddress, iLength, pTempRLEData, uiCompressedLength); + +#else + DeCompress_RLE((byte *)pvAddress, pTempRLEData, iLength); +#endif + // + // free workspace... + // + Z_Free( pTempRLEData ); + } + else + { + uiLoaded += SG_ReadBytes( pvAddress, iLength, fhSaveGame ); + } + // Get checksum... + // + uiLoaded += SG_ReadBytes( &uiLoadedCksum, sizeof(uiLoadedCksum), fhSaveGame ); + + // Make sure the checksums match... + // + uiCksum = Com_BlockChecksum( pvAddress, iLength ); + + if ( uiLoadedCksum != uiCksum) + { + + if (!qbSGReadIsTestOnly) + { + return 0; + //Com_Error(ERR_DROP, "Failed checksum check for chunk", SG_GetChidText(chid)); + } + else + { + if ( qbTransient ) + { + Z_Free( pvAddress ); + } + } + return 0; + } + + // Make sure we didn't encounter any read errors... + //size_t + if ( uiLoaded != sizeof(ulLoadedChid) + sizeof(uiLoadedLength) + sizeof(uiLoadedCksum) + (bBlockIsCompressed?sizeof(uiCompressedLength):0) + (bBlockIsCompressed?uiCompressedLength:iLength)) + { + if (!qbSGReadIsTestOnly) + { + return 0; + //Com_Error(ERR_DROP, "Error during loading chunk %s", SG_GetChidText(chid)); + } + else + { + if ( qbTransient ) + { + Z_Free( pvAddress ); + } + } + return 0; + } + + // If we are skipping the chunk, then free the memory... + // + if ( qbTransient ) + { + Z_Free( pvAddress ); + } + + return iLength; +} + +int SG_Read(unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr /* = NULL */) +{ + return SG_Read_Actual(chid, pvAddress, iLength, ppvAddressPtr, qfalse ); // qboolean bChunkIsOptional +} + +int SG_ReadOptional(unsigned long chid, void *pvAddress, int iLength, void **ppvAddressPtr /* = NULL */) +{ + return SG_Read_Actual(chid, pvAddress, iLength, ppvAddressPtr, qtrue); // qboolean bChunkIsOptional +} + + +void SG_TestSave(void) +{ + if (sv_testsave->integer && sv.state == SS_GAME) + { + WriteGame (false); + ge->WriteLevel(false); + } +} + +qboolean SG_TestSignature(const char * psPathlessBaseName) +{ + char sComment[iSG_COMMENT_SIZE]; + char sMapCmd [iSG_MAPCMD_SIZE]; + qboolean qbAutosave; + DWORD dwSuccess ; + + int iPrevTestSave = sv_testsave->integer; + sv_testsave->integer = 0; + //open the file + if (!SG_Open( psPathlessBaseName )) + { + Com_Printf (GetString_FailedToOpenSaveGame(psPathlessBaseName, qtrue));//S_COLOR_RED "Failed to open savegame \"%s\"\n", psPathlessBaseName); + sv_testsave->integer = iPrevTestSave; + return qfalse; + } + + while( SG_ReadBytes(sComment, iSG_COMMENT_SIZE, NULL)) + ; + + +/* + // Read in all the server data... + // + SG_Read('COMM', sComment, sizeof(sComment)); + Com_DPrintf("Reading: %s\n", sComment); + SG_Read( 'CMTM', NULL, sizeof( time_t )); + + +// SG_ReadScreenshot(qtrue); // qboolean qbSetAsLoadingScreen + SG_Read('MPCM', sMapCmd, sizeof(sMapCmd)); + SG_ReadCvars(); + + // read game state + qbAutosave = ReadGame(); + eSavedGameJustLoaded = (qbAutosave)?eAUTO:eFULL; + + SV_SpawnServer(sMapCmd, eForceReload_NOTHING, (eSavedGameJustLoaded != eFULL) ); // note that this also trashes the whole G_Alloc pool as well (of course) + + // read in all the level data... + // + if (!qbAutosave) + { + SG_Read('TIME', (void *)&sv.time, sizeof(sv.time)); + SG_Read('TIMR', (void *)&sv.timeResidual, sizeof(sv.timeResidual)); + CM_ReadPortalState(); + SG_ReadServerConfigStrings(); + } + ge->ReadLevel(qbAutosave, qbLoadTransition); // always done now, but ent reader only does player if auto + +*/ + //sigend here + dwSuccess = XCalculateSignatureEnd( sg_sigHandleRead, &sg_validationHeaderRead.Signature ); + assert( dwSuccess == ERROR_SUCCESS ); + + DWORD dwSigSize = XCalculateSignatureGetSize( XCALCSIG_FLAG_SAVE_GAME ); + + if(!SG_Close()) + { + Com_Printf (GetString_FailedToOpenSaveGame(psPathlessBaseName,qfalse));//S_COLOR_RED "Failed to close savegame\n"); + sv_testsave->integer = iPrevTestSave; + return qfalse; + } + if( memcmp( &sg_validationHeader.Signature, &sg_validationHeaderRead.Signature, dwSigSize ) != 0 ) + { + Com_Printf (GetString_FailedToOpenSaveGame(psPathlessBaseName,qfalse));//S_COLOR_RED "Failed to close savegame\n"); + sv_testsave->integer = iPrevTestSave; + return qfalse; + } + + sv_testsave->integer = iPrevTestSave; + return qtrue; + +} + +//JLF +#ifdef _XBOX + +unsigned long SG_BlocksLeft() +{ + ULARGE_INTEGER lFreeBytesAvailable; + ULARGE_INTEGER lTotalNumberOfBytes; + ULARGE_INTEGER lTotalNumberOfFreeBytes; + + BOOL bSuccess = GetDiskFreeSpaceEx( "U:\\", + &lFreeBytesAvailable, + &lTotalNumberOfBytes, + &lTotalNumberOfFreeBytes ); + + if( !bSuccess ) + return FALSE; + + return (lFreeBytesAvailable.QuadPart/SG_BLOCKSIZE); +} + +unsigned long SG_SaveGameSize() +{ + return 40; // just say savegames will be no bigger than 40 blocks + + //return SG_DIRECTORYSIZE; +} + +unsigned long getGameBlocks(char * psPathlessBaseName) +{ + assert( !sg_Handle); // I'd rather know about this + if(!psPathlessBaseName) + { + return qfalse; + } + + unsigned short saveGameName[filepathlength]; + char directoryInfo[filepathlength]; + char psLocalFilename[filepathlength]; + DWORD bytesRead; + + if ( strcmp(psPathlessBaseName, "Checkpoint")==0) + { + } + else + { + mbstowcs(saveGameName, psPathlessBaseName,filepathlength); + + XCreateSaveGame("U:\\", saveGameName, OPEN_EXISTING, 0,directoryInfo, filepathlength); + + strcpy (psLocalFilename , directoryInfo); + strcat (psLocalFilename , "JK3SG.xsv"); + + sg_Handle = NULL; + sg_Handle = CreateFile(psLocalFilename, GENERIC_READ, FILE_SHARE_READ, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + } + + if (!sg_Handle) + { +// Com_Printf(S_COLOR_RED "Failed to open savegame file %s\n", psLocalFilename); + Com_DPrintf(GetString_FailedToOpenSaveGame(psLocalFilename, qtrue)); + + return 0; + } + + //read the validation header + + + unsigned int filelength =GetFileSize (sg_Handle, NULL); + + SG_Close(); + return filelength/SG_BLOCKSIZE; +} + + +#endif + + + +////////////////// eof //////////////////// + diff --git a/code/server/sv_snapshot.cpp b/code/server/sv_snapshot.cpp new file mode 100644 index 0000000..c9c800b --- /dev/null +++ b/code/server/sv_snapshot.cpp @@ -0,0 +1,749 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + + +#include "..\client\vmachine.h" +#include "server.h" + + +/* +============================================================================= + +Delta encode a client frame onto the network channel + +A normal server packet will look like: + +4 sequence number (high bit set if an oversize fragment) + +1 svc_snapshot +4 last client reliable command +4 serverTime +1 lastframe for delta compression +1 snapFlags +1 areaBytes + + + + +============================================================================= +*/ + +/* +============= +SV_EmitPacketEntities + +Writes a delta update of an entityState_t list to the message. +============= +*/ +static void SV_EmitPacketEntities( clientSnapshot_t *from, clientSnapshot_t *to, msg_t *msg ) { + entityState_t *oldent, *newent; + int oldindex, newindex; + int oldnum, newnum; + int from_num_entities; + + // generate the delta update + if ( !from ) { + from_num_entities = 0; + } else { + from_num_entities = from->num_entities; + } + + newent = NULL; + oldent = NULL; + newindex = 0; + oldindex = 0; + const int num2Send = to->num_entities >= svs.numSnapshotEntities ? svs.numSnapshotEntities : to->num_entities; + + while ( newindex < num2Send || oldindex < from_num_entities ) { + if ( newindex >= num2Send ) { + newnum = 9999; + } else { + newent = &svs.snapshotEntities[(to->first_entity+newindex) % svs.numSnapshotEntities]; + newnum = newent->number; + } + + if ( oldindex >= from_num_entities ) { + oldnum = 9999; + } else { + oldent = &svs.snapshotEntities[(from->first_entity+oldindex) % svs.numSnapshotEntities]; + oldnum = oldent->number; + } + + if ( newnum == oldnum ) { + // delta update from old position + // because the force parm is qfalse, this will not result + // in any bytes being emited if the entity has not changed at all + MSG_WriteEntity(msg, newent, 0); + oldindex++; + newindex++; + continue; + } + + if ( newnum < oldnum ) { + // this is a new entity, send it from the baseline + MSG_WriteEntity (msg, newent, 0); + newindex++; + continue; + } + + if ( newnum > oldnum ) { + // the old entity isn't present in the new message + if(oldent) { + MSG_WriteEntity (msg, NULL, oldent->number); + } + oldindex++; + continue; + } + } + + MSG_WriteBits( msg, (MAX_GENTITIES-1), GENTITYNUM_BITS ); // end of packetentities +} + + + +/* +================== +SV_WriteSnapshotToClient +================== +*/ +static void SV_WriteSnapshotToClient( client_t *client, msg_t *msg ) { + clientSnapshot_t *frame, *oldframe; + int lastframe; + int snapFlags; + + // this is the snapshot we are creating + frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; + + // try to use a previous frame as the source for delta compressing the snapshot + if ( client->deltaMessage <= 0 || client->state != CS_ACTIVE ) { + // client is asking for a retransmit + oldframe = NULL; + lastframe = 0; + } else if ( client->netchan.outgoingSequence - client->deltaMessage + >= (PACKET_BACKUP - 3) ) { + // client hasn't gotten a good message through in a long time + Com_DPrintf ("%s: Delta request from out of date packet.\n", client->name); + oldframe = NULL; + lastframe = 0; + } else { + // we have a valid snapshot to delta from + oldframe = &client->frames[ client->deltaMessage & PACKET_MASK ]; + lastframe = client->netchan.outgoingSequence - client->deltaMessage; + + // the snapshot's entities may still have rolled off the buffer, though + if ( oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities ) { + Com_DPrintf ("%s: Delta request from out of date entities.\n", client->name); + oldframe = NULL; + lastframe = 0; + } + } + + MSG_WriteByte (msg, svc_snapshot); + + // let the client know which reliable clientCommands we have received + MSG_WriteLong( msg, client->lastClientCommand ); + + // send over the current server time so the client can drift + // its view of time to try to match + MSG_WriteLong (msg, sv.time); + + // we must write a message number, because recorded demos won't have + // the same network message sequences + MSG_WriteLong (msg, client->netchan.outgoingSequence ); + MSG_WriteByte (msg, lastframe); // what we are delta'ing from + MSG_WriteLong (msg, client->cmdNum); // we have executed up to here + + snapFlags = client->rateDelayed | ( client->droppedCommands << 1 ); + client->droppedCommands = 0; + + MSG_WriteByte (msg, snapFlags); + + // send over the areabits + MSG_WriteByte (msg, frame->areabytes); + MSG_WriteData (msg, frame->areabits, frame->areabytes); + + // delta encode the playerstate + if ( oldframe ) { + MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps ); + } else { + MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps ); + } + + // delta encode the entities + SV_EmitPacketEntities (oldframe, frame, msg); +} + + +/* +================== +SV_UpdateServerCommandsToClient + +(re)send all server commands the client hasn't acknowledged yet +================== +*/ +static void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ) { + int i; + + // write any unacknowledged serverCommands + for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) { + MSG_WriteByte( msg, svc_serverCommand ); + MSG_WriteLong( msg, i ); + MSG_WriteString( msg, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); + } +} + +/* +============================================================================= + +Build a client snapshot structure + +============================================================================= +*/ + +#define MAX_SNAPSHOT_ENTITIES 1024 +typedef struct { + int numSnapshotEntities; + int snapshotEntities[MAX_SNAPSHOT_ENTITIES]; +} snapshotEntityNumbers_t; + +/* +======================= +SV_QsortEntityNumbers +======================= +*/ +static int SV_QsortEntityNumbers( const void *a, const void *b ) { + int *ea, *eb; + + ea = (int *)a; + eb = (int *)b; + + if ( *ea == *eb ) { + Com_Error( ERR_DROP, "SV_QsortEntityStates: duplicated entity" ); + } + + if ( *ea < *eb ) { + return -1; + } + + return 1; +} + + +/* +=============== +SV_AddEntToSnapshot +=============== +*/ +static void SV_AddEntToSnapshot( svEntity_t *svEnt, gentity_t *gEnt, snapshotEntityNumbers_t *eNums ) { + // if we have already added this entity to this snapshot, don't add again + if ( svEnt->snapshotCounter == sv.snapshotCounter ) { + return; + } + svEnt->snapshotCounter = sv.snapshotCounter; + + // if we are full, silently discard entities + if ( eNums->numSnapshotEntities == MAX_SNAPSHOT_ENTITIES ) { + return; + } + + if (sv.snapshotCounter &1 && eNums->numSnapshotEntities == svs.numSnapshotEntities-1) + { //we're full, and about to wrap around and stomp ents, so half the time send the first set without stomping. + return; + } + + eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s.number; + eNums->numSnapshotEntities++; +} + +//rww - bg_public.h won't cooperate in here +#define EF_PERMANENT 0x00080000 + +float sv_sightRangeForLevel[6] = +{ + 0,//FORCE_LEVEL_0 + 1024.f, //FORCE_LEVEL_1 + 2048.0f,//FORCE_LEVEL_2 + 4096.0f,//FORCE_LEVEL_3 + 4096.0f,//FORCE_LEVEL_4 + 4096.0f//FORCE_LEVEL_5 +}; + +qboolean SV_PlayerCanSeeEnt( gentity_t *ent, int sightLevel ) +{//return true if this ent is in view + //NOTE: this is similar to the func CG_PlayerCanSeeCent in cg_players + vec3_t viewOrg, viewAngles, viewFwd, dir2Ent; + if ( !ent ) + { + return qfalse; + } + if ( VM_Call( CG_CAMERA_POS, viewOrg)) + { + if ( VM_Call( CG_CAMERA_ANG, viewAngles)) + { + float dot = 0.25f;//1.0f; + float range = sv_sightRangeForLevel[sightLevel]; + + VectorSubtract( ent->currentOrigin, viewOrg, dir2Ent ); + float entDist = VectorNormalize( dir2Ent ); + + if ( (ent->s.eFlags&EF_FORCE_VISIBLE) ) + {//no dist check on them? + } + else + { + if ( entDist < 128.0f ) + {//can always see them if they're really close + return qtrue; + } + + if ( entDist > range ) + {//too far away to see them + return qfalse; + } + } + + dot += (0.99f-dot)*entDist/range;//the farther away they are, the more in front they have to be + + AngleVectors( viewAngles, viewFwd, NULL, NULL ); + if ( DotProduct( viewFwd, dir2Ent ) < dot ) + { + return qfalse; + } + return qtrue; + } + } + return qfalse; +} +/* +=============== +SV_AddEntitiesVisibleFromPoint +=============== +*/ +static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame, + snapshotEntityNumbers_t *eNums, qboolean portal ) { + int e, i; + gentity_t *ent; + svEntity_t *svEnt; + int l; + int clientarea, clientcluster; + int leafnum; + int c_fullsend; + const byte *clientpvs; + const byte *bitvector; + qboolean sightOn = qfalse; + + // during an error shutdown message we may need to transmit + // the shutdown message after the server has shutdown, so + // specfically check for it + if ( !sv.state ) { + return; + } + + leafnum = CM_PointLeafnum (origin); + clientarea = CM_LeafArea (leafnum); + clientcluster = CM_LeafCluster (leafnum); + + // calculate the visible areas + frame->areabytes = CM_WriteAreaBits( frame->areabits, clientarea ); + + clientpvs = CM_ClusterPVS (clientcluster); + + c_fullsend = 0; + + if ( !portal ) + {//not if this if through a portal...??? James said to do this... + if ( (frame->ps.forcePowersActive&(1<num_entities ; e++ ) { + ent = SV_GentityNum(e); + + if (!ent->inuse) { + continue; + } + + if (ent->s.eFlags & EF_PERMANENT) + { // he's permanent, so don't send him down! + continue; + } + + if (ent->s.number != e) { + Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n"); + ent->s.number = e; + } + + // never send entities that aren't linked in + if ( !ent->linked ) { + continue; + } + + // entities can be flagged to explicitly not be sent to the client + if ( ent->svFlags & SVF_NOCLIENT ) { + continue; + } + + svEnt = SV_SvEntityForGentity( ent ); + + // don't double add an entity through portals + if ( svEnt->snapshotCounter == sv.snapshotCounter ) { + continue; + } + + // broadcast entities are always sent, and so is the main player so we don't see noclip weirdness + if ( ent->svFlags & SVF_BROADCAST || !e) { + SV_AddEntToSnapshot( svEnt, ent, eNums ); + continue; + } + + if (ent->s.isPortalEnt) + { //rww - portal entities are always sent as well + SV_AddEntToSnapshot( svEnt, ent, eNums ); + continue; + } + + if ( sightOn ) + {//force sight is on, sees through portals, so draw them always if in radius + if ( SV_PlayerCanSeeEnt( ent, frame->ps.forcePowerLevel[FP_SEE] ) ) + {//entity is visible + SV_AddEntToSnapshot( svEnt, ent, eNums ); + continue; + } + } + + // ignore if not touching a PV leaf + // check area + if ( !CM_AreasConnected( clientarea, svEnt->areanum ) ) { + // doors can legally straddle two areas, so + // we may need to check another one + if ( !CM_AreasConnected( clientarea, svEnt->areanum2 ) ) { + continue; // blocked by a door + } + } + + bitvector = clientpvs; + + // check individual leafs + if ( !svEnt->numClusters ) { + continue; + } + l = 0; +#ifdef _XBOX + if(bitvector) { +#endif + for ( i=0 ; i < svEnt->numClusters ; i++ ) { + l = svEnt->clusternums[i]; + if ( bitvector[l >> 3] & (1 << (l&7) ) ) { + break; + } + } +#ifdef _XBOX + } +#endif + + // if we haven't found it to be visible, + // check overflow clusters that coudln't be stored +#ifdef _XBOX + if ( bitvector && i == svEnt->numClusters ) { +#else + if ( i == svEnt->numClusters ) { +#endif + if ( svEnt->lastCluster ) { + for ( ; l <= svEnt->lastCluster ; l++ ) { + if ( bitvector[l >> 3] & (1 << (l&7) ) ) { + break; + } + } + if ( l == svEnt->lastCluster ) { + continue; // not visible + } + } else { + continue; + } + } + + // add it + SV_AddEntToSnapshot( svEnt, ent, eNums ); + + // if its a portal entity, add everything visible from its camera position + if ( ent->svFlags & SVF_PORTAL ) { + SV_AddEntitiesVisibleFromPoint( ent->s.origin2, frame, eNums, qtrue ); +#ifdef _XBOX + //Must get clientpvs again since above call destroyed it. + clientpvs = CM_ClusterPVS (clientcluster); +#endif + } + } +} + +/* +============= +SV_BuildClientSnapshot + +Decides which entities are going to be visible to the client, and +copies off the playerstate and areabits. + +This properly handles multiple recursive portals, but the render +currently doesn't. + +For viewing through other player's eyes, clent can be something other than client->gentity +============= +*/ +static clientSnapshot_t *SV_BuildClientSnapshot( client_t *client ) { + vec3_t org; + clientSnapshot_t *frame; + snapshotEntityNumbers_t entityNumbers; + int i; + gentity_t *ent; + entityState_t *state; + gentity_t *clent; + + // bump the counter used to prevent double adding + sv.snapshotCounter++; + + // this is the frame we are creating + frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; + + // clear everything in this snapshot + entityNumbers.numSnapshotEntities = 0; + memset( frame->areabits, 0, sizeof( frame->areabits ) ); + + clent = client->gentity; + if ( !clent ) { + return frame; + } + + // grab the current playerState_t + frame->ps = *clent->client; + + // this stops the main client entity playerstate from being sent across, which has the effect of breaking + // looping sounds for the main client. So I took it out. +/* { + int clientNum; + svEntity_t *svEnt; + clientNum = frame->ps.clientNum; + if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + svEnt = &sv.svEntities[ clientNum ]; + // never send client's own entity, because it can + // be regenerated from the playerstate + svEnt->snapshotCounter = sv.snapshotCounter; + } +*/ + // find the client's viewpoint + + //if in camera mode use camera position instead + if ( VM_Call( CG_CAMERA_POS, org)) + { + //org[2] += clent->client->viewheight; + } + else + { + VectorCopy( clent->client->origin, org ); + org[2] += clent->client->viewheight; + +//============ + // need to account for lean, or areaportal doors don't draw properly... -slc + if (frame->ps.leanofs != 0) + { + vec3_t right; + //add leaning offset + vec3_t v3ViewAngles; + VectorCopy(clent->client->viewangles, v3ViewAngles); + v3ViewAngles[2] += (float)frame->ps.leanofs/2; + AngleVectors(v3ViewAngles, NULL, right, NULL); + VectorMA(org, (float)frame->ps.leanofs, right, org); + } +//============ + } + VectorCopy( org, frame->ps.serverViewOrg ); + VectorCopy( org, clent->client->serverViewOrg ); + + // add all the entities directly visible to the eye, which + // may include portal entities that merge other viewpoints + SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, qfalse ); + /* + //was in here for debugging- print list of all entities in snapshot when you go over the limit + if ( entityNumbers.numSnapshotEntities >= 256 ) + { + for ( int xxx = 0; xxx < entityNumbers.numSnapshotEntities; xxx++ ) + { + Com_Printf("%d - ", xxx ); + ge->PrintEntClassname( entityNumbers.snapshotEntities[xxx] ); + } + } + else if ( entityNumbers.numSnapshotEntities >= 200 ) + { + Com_Printf(S_COLOR_RED"%d snapshot entities!", entityNumbers.numSnapshotEntities ); + } + else if ( entityNumbers.numSnapshotEntities >= 128 ) + { + Com_Printf(S_COLOR_YELLOW"%d snapshot entities", entityNumbers.numSnapshotEntities ); + } + */ + + // if there were portals visible, there may be out of order entities + // in the list which will need to be resorted for the delta compression + // to work correctly. This also catches the error condition + // of an entity being included twice. + qsort( entityNumbers.snapshotEntities, entityNumbers.numSnapshotEntities, + sizeof( entityNumbers.snapshotEntities[0] ), SV_QsortEntityNumbers ); + + // now that all viewpoint's areabits have been OR'd together, invert + // all of them to make it a mask vector, which is what the renderer wants + for ( i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++ ) { + ((int *)frame->areabits)[i] = ((int *)frame->areabits)[i] ^ -1; + } + + // copy the entity states out + frame->num_entities = 0; + frame->first_entity = svs.nextSnapshotEntities; + for ( i = 0 ; i < entityNumbers.numSnapshotEntities ; i++ ) { + ent = SV_GentityNum(entityNumbers.snapshotEntities[i]); + state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities]; + *state = ent->s; + svs.nextSnapshotEntities++; + frame->num_entities++; + } + + return frame; +} + + +/* +======================= +SV_SendMessageToClient + +Called by SV_SendClientSnapshot and SV_SendClientGameState +======================= +*/ +#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead +void SV_SendMessageToClient( msg_t *msg, client_t *client ) { + int rateMsec; + + // record information about the message + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = sv.time; + + // send the datagram + Netchan_Transmit( &client->netchan, msg->cursize, msg->data ); + + // set nextSnapshotTime based on rate and requested number of updates + + // local clients get snapshots every frame (FIXME: also treat LAN clients) + if ( client->netchan.remoteAddress.type == NA_LOOPBACK ) { + client->nextSnapshotTime = sv.time - 1; + return; + } + + // normal rate / snapshotMsec calculation + rateMsec = ( msg->cursize + HEADER_RATE_BYTES ) * 1000 / client->rate; + if ( rateMsec < client->snapshotMsec ) { + rateMsec = client->snapshotMsec; + client->rateDelayed = qfalse; + } else { + client->rateDelayed = qtrue; + } + + client->nextSnapshotTime = sv.time + rateMsec; + + // if we haven't gotten a message from the client in over a second, we will + // drop to only sending one snapshot a second until they timeout + if ( sv.time - client->lastPacketTime > 1000 || client->state != CS_ACTIVE ) { + if ( client->nextSnapshotTime < sv.time + 1000 ) { + client->nextSnapshotTime = sv.time + 1000; + } + return; + } + +} + +/* +======================= +SV_SendClientEmptyMessage + +This is just an empty message so that we can tell if +the client dropped the gamestate that went out before +======================= +*/ +void SV_SendClientEmptyMessage( client_t *client ) { + msg_t msg; + byte buffer[10]; + + MSG_Init( &msg, buffer, sizeof( buffer ) ); + SV_SendMessageToClient( &msg, client ); +} + +/* +======================= +SV_SendClientSnapshot +======================= +*/ +void SV_SendClientSnapshot( client_t *client ) { + byte msg_buf[MAX_MSGLEN]; + msg_t msg; + + // build the snapshot + SV_BuildClientSnapshot( client ); + + // bots need to have their snapshots build, but + // the query them directly without needing to be sent + if ( client->gentity && client->gentity->svFlags & SVF_BOT ) { + return; + } + + MSG_Init (&msg, msg_buf, sizeof(msg_buf)); + msg.allowoverflow = qtrue; + + // (re)send any reliable server commands + SV_UpdateServerCommandsToClient( client, &msg ); + + // send over all the relevant entityState_t + // and the playerState_t + SV_WriteSnapshotToClient( client, &msg ); + + // check for overflow + if ( msg.overflowed ) { + Com_Printf ("WARNING: msg overflowed for %s\n", client->name); + MSG_Clear (&msg); + } + + SV_SendMessageToClient( &msg, client ); +} + + +/* +======================= +SV_SendClientMessages +======================= +*/ +void SV_SendClientMessages( void ) { + int i; + client_t *c; + + // send a message to each connected client + for (i=0, c = svs.clients ; i < 1 ; i++, c++) { + if (!c->state) { + continue; // not connected + } + + if ( sv.time < c->nextSnapshotTime ) { + continue; // not time yet + } + + if ( c->state != CS_ACTIVE ) { + if ( c->state != CS_ZOMBIE ) { + SV_SendClientEmptyMessage( c ); + } + continue; + } + + SV_SendClientSnapshot( c ); + } +} + diff --git a/code/server/sv_world.cpp b/code/server/sv_world.cpp new file mode 100644 index 0000000..a2105a4 --- /dev/null +++ b/code/server/sv_world.cpp @@ -0,0 +1,1012 @@ +// world.c -- world query functions + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + +#include "../qcommon/cm_local.h" + + /* +Ghoul2 Insert Start +*/ + +#if !defined(GHOUL2_SHARED_H_INC) + #include "..\game\ghoul2_shared.h" //for CGhoul2Info_v +#endif +#if !defined(G2_H_INC) + #include "..\ghoul2\G2.h" +#endif +#if !defined (MINIHEAP_H_INC) + #include "../qcommon/miniheap.h" +#endif + +#ifdef _DEBUG + #include +#endif //_DEBUG +/* +Ghoul2 Insert End +*/ +#if MEM_DEBUG +#include "..\smartheap\heapagnt.h" +#define SV_TRACE_PROFILE (0) +#endif + +#if 0 //G2_SUPERSIZEDBBOX is not being used +static const float superSizedAdd=64.0f; +#endif + +/* +================ +SV_ClipHandleForEntity + +Returns a headnode that can be used for testing or clipping to a +given entity. If the entity is a bsp model, the headnode will +be returned, otherwise a custom box tree will be constructed. +================ +*/ +clipHandle_t SV_ClipHandleForEntity( const gentity_t *ent ) { + if ( ent->bmodel ) { + // explicit hulls in the BSP model + return CM_InlineModel( ent->s.modelindex ); + } + + // create a temp tree from bounding box sizes + return CM_TempBoxModel( ent->mins, ent->maxs );//, ent->contents ); +} + + + +/* +=============================================================================== + +ENTITY CHECKING + +To avoid linearly searching through lists of entities during environment testing, +the world is carved up with an evenly spaced, axially aligned bsp tree. Entities +are kept in chains either at the final leafs, or at the first node that splits +them, which prevents having to deal with multiple fragments of a single entity. + +=============================================================================== +*/ + +typedef struct worldSector_s { + int axis; // -1 = leaf node + float dist; + struct worldSector_s *children[2]; + svEntity_t *entities; +} worldSector_t; + +#define AREA_DEPTH 8 +#define AREA_NODES 1024 + +worldSector_t sv_worldSectors[AREA_NODES]; +int sv_numworldSectors; + +/* +=============== +SV_CreateworldSector + +Builds a uniformly subdivided tree for the given world size +=============== +*/ +worldSector_t *SV_CreateworldSector( int depth, vec3_t mins, vec3_t maxs ) { + worldSector_t *anode; + vec3_t size; + vec3_t mins1, maxs1, mins2, maxs2; + + anode = &sv_worldSectors[sv_numworldSectors]; + sv_numworldSectors++; + + if (depth == AREA_DEPTH) { + anode->axis = -1; + anode->children[0] = anode->children[1] = NULL; + return anode; + } + + VectorSubtract (maxs, mins, size); + if (size[0] > size[1]) { + anode->axis = 0; + } else { + anode->axis = 1; + } + + anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]); + VectorCopy (mins, mins1); + VectorCopy (mins, mins2); + VectorCopy (maxs, maxs1); + VectorCopy (maxs, maxs2); + + maxs1[anode->axis] = mins2[anode->axis] = anode->dist; + + anode->children[0] = SV_CreateworldSector (depth+1, mins2, maxs2); + anode->children[1] = SV_CreateworldSector (depth+1, mins1, maxs1); + + return anode; +} + +/* +=============== +SV_ClearWorld + +=============== +*/ +void SV_ClearWorld( void ) { + clipHandle_t h; + vec3_t mins, maxs; + + memset( sv_worldSectors, 0, sizeof(sv_worldSectors) ); + sv_numworldSectors = 0; + + // get world map bounds + h = CM_InlineModel( 0 ); + CM_ModelBounds( cmg, h, mins, maxs ); + SV_CreateworldSector( 0, mins, maxs ); +} + + +/* +=============== +SV_UnlinkEntity + +=============== +*/ +void SV_UnlinkEntity( gentity_t *gEnt ) { + svEntity_t *ent; + svEntity_t *scan; + worldSector_t *ws; + + // this should never be called with a freed entity + if ( !gEnt->inuse ) { + return; + } + + ent = SV_SvEntityForGentity( gEnt ); + + gEnt->linked = qfalse; + + ws = ent->worldSector; + if ( !ws ) { + return; // not linked in anywhere + } + ent->worldSector = NULL; + + if ( ws->entities == ent ) { + ws->entities = ent->nextEntityInWorldSector; + return; + } + + for ( scan = ws->entities ; scan ; scan = scan->nextEntityInWorldSector ) { + if ( scan->nextEntityInWorldSector == ent ) { + scan->nextEntityInWorldSector = ent->nextEntityInWorldSector; + return; + } + } + + Com_Printf( "WARNING: SV_UnlinkEntity: not found in worldSector\n" ); +} + + +/* +=============== +SV_LinkEntity + +=============== +*/ +#define MAX_TOTAL_ENT_LEAFS 128 +void SV_LinkEntity( gentity_t *gEnt ) { + worldSector_t *node; + int leafs[MAX_TOTAL_ENT_LEAFS]; + int cluster; + int num_leafs; + int i, j, k; + int area; + int lastLeaf; + float *origin, *angles; + svEntity_t *ent; + + // this should never be called with a freed entity + if ( !gEnt->inuse ) { + return; + } + + ent = SV_SvEntityForGentity( gEnt ); + + if ( ent->worldSector ) { + SV_UnlinkEntity( gEnt ); // unlink from old position + } + + // encode the size into the entityState_t for client prediction + if ( gEnt->bmodel ) { + gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value + } else if ( gEnt->contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) { + // assume that x/y are equal and symetric + i = gEnt->maxs[0]; + if (i<1) + i = 1; + if (i>255) + i = 255; + + // z is not symetric + j = (-gEnt->mins[2]); + if (j<1) + j = 1; + if (j>255) + j = 255; + + // and z maxs can be negative... + k = (gEnt->maxs[2]+32); + if (k<1) + k = 1; + if (k>255) + k = 255; + + gEnt->s.solid = (k<<16) | (j<<8) | i; + } else { + gEnt->s.solid = 0; + } + + // get the position + origin = gEnt->currentOrigin; + angles = gEnt->currentAngles; + + // set the abs box + if ( gEnt->bmodel && (angles[0] || angles[1] || angles[2]) ) + { // expand for rotation + float max; + int i; + + max = RadiusFromBounds( gEnt->mins, gEnt->maxs ); + for (i=0 ; i<3 ; i++) { + gEnt->absmin[i] = origin[i] - max; + gEnt->absmax[i] = origin[i] + max; + } + } else { + // normal + VectorAdd (origin, gEnt->mins, gEnt->absmin); + VectorAdd (origin, gEnt->maxs, gEnt->absmax); + } + + // because movement is clipped an epsilon away from an actual edge, + // we must fully check even when bounding boxes don't quite touch + gEnt->absmin[0] -= 1; + gEnt->absmin[1] -= 1; + gEnt->absmin[2] -= 1; + gEnt->absmax[0] += 1; + gEnt->absmax[1] += 1; + gEnt->absmax[2] += 1; + + // link to PVS leafs + ent->numClusters = 0; + ent->lastCluster = 0; + ent->areanum = -1; + ent->areanum2 = -1; + + //get all leafs, including solids + num_leafs = CM_BoxLeafnums( gEnt->absmin, gEnt->absmax, + leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf ); + + // if none of the leafs were inside the map, the + // entity is outside the world and can be considered unlinked + if ( !num_leafs ) { + return; + } + + // set areas, even from clusters that don't fit in the entity array + for (i=0 ; iareanum != -1 && ent->areanum != area) { + if (ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING) { + Com_DPrintf ("Object %i touching 3 areas at %f %f %f\n", + gEnt->s.number, + gEnt->absmin[0], gEnt->absmin[1], gEnt->absmin[2]); + } + ent->areanum2 = area; + } else { + ent->areanum = area; + } + } + } + + // store as many explicit clusters as we can + ent->numClusters = 0; + for (i=0 ; i < num_leafs ; i++) { + cluster = CM_LeafCluster( leafs[i] ); + if ( cluster != -1 ) { + ent->clusternums[ent->numClusters++] = cluster; + if ( ent->numClusters == MAX_ENT_CLUSTERS ) { + break; + } + } + } + + // store off a last cluster if we need to + if ( i != num_leafs ) { + ent->lastCluster = CM_LeafCluster( lastLeaf ); + } + + // find the first world sector node that the ent's box crosses + node = sv_worldSectors; + while (1) + { + if (node->axis == -1) + break; + if ( gEnt->absmin[node->axis] > node->dist) + node = node->children[0]; + else if ( gEnt->absmax[node->axis] < node->dist) + node = node->children[1]; + else + break; // crosses the node + } + + // link it in + ent->worldSector = node; + ent->nextEntityInWorldSector = node->entities; + node->entities = ent; + + gEnt->linked = qtrue; +} + +/* +============================================================================ + +AREA QUERY + +Fills in a list of all entities who's absmin / absmax intersects the given +bounds. This does NOT mean that they actually touch in the case of bmodels. +============================================================================ +*/ + +typedef struct { + const float *mins; + const float *maxs; + gentity_t **list; + int count, maxcount; +} areaParms_t; + + +/* +==================== +SV_AreaEntities_r + +==================== +*/ +void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { + svEntity_t *check, *next; + gentity_t *gcheck; + int count; + + count = 0; + + for ( check = node->entities ; check ; check = next ) { + next = check->nextEntityInWorldSector; + + gcheck = SV_GEntityForSvEntity( check ); + + if ( gcheck->absmin[0] > ap->maxs[0] + || gcheck->absmin[1] > ap->maxs[1] + || gcheck->absmin[2] > ap->maxs[2] + || gcheck->absmax[0] < ap->mins[0] + || gcheck->absmax[1] < ap->mins[1] + || gcheck->absmax[2] < ap->mins[2]) { + continue; + } + + if ( ap->count == ap->maxcount ) { + Com_DPrintf ("SV_AreaEntities: reached maxcount (%d)\n",ap->maxcount); + return; + } + + ap->list[ap->count] = gcheck; + ap->count++; + } + + if (node->axis == -1) { + return; // terminal node + } + + // recurse down both sides + if ( ap->maxs[node->axis] > node->dist ) { + SV_AreaEntities_r ( node->children[0], ap ); + } + if ( ap->mins[node->axis] < node->dist ) { + SV_AreaEntities_r ( node->children[1], ap ); + } +} + +/* +================ +SV_AreaEntities +================ +*/ +int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, gentity_t **elist, int maxcount ) { + areaParms_t ap; + + ap.mins = mins; + ap.maxs = maxs; + ap.list = elist; + ap.count = 0; + ap.maxcount = maxcount; + +#if SV_TRACE_PROFILE +#if MEM_DEBUG + { + int old=dbgMemSetCheckpoint(2003); + malloc(1); + dbgMemSetCheckpoint(old); + } +#endif +#endif + SV_AreaEntities_r( sv_worldSectors, &ap ); + + return ap.count; +} + +/* +=============== +SV_SectorList_f +=============== +*/ +#if 1 + +void SV_SectorList_f( void ) { + int i, c; + worldSector_t *sec; + svEntity_t *ent; + + for ( i = 0 ; i < AREA_NODES ; i++ ) { + sec = &sv_worldSectors[i]; + + c = 0; + for ( ent = sec->entities ; ent ; ent = ent->nextEntityInWorldSector ) { + c++; + } + Com_Printf( "sector %i: %i entities\n", i, c ); + } +} + +#else + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include +#include +#pragma warning (pop) +using namespace std; + +class CBBox +{ +public: + float mMins[3]; + float mMaxs[3]; + + CBBox(vec3_t mins,vec3_t maxs) + { + VectorCopy(mins,mMins); + VectorCopy(maxs,mMaxs); + } +}; + +static multimap > > entStats; + +void SV_AreaEntitiesTree( worldSector_t *node, areaParms_t *ap, int level ) +{ + svEntity_t *check, *next; + gentity_t *gcheck; + int count; + list bblist; + + count = 0; + + for ( check = node->entities ; check ; check = next ) + { + next = check->nextEntityInWorldSector; + + gcheck = SV_GEntityForSvEntity( check ); + + CBBox bBox(gcheck->absmin,gcheck->absmax); + bblist.push_back(bBox); + count++; + } + + entStats.insert(pair > >(level,pair >(count,bblist))); + if (node->axis == -1) + { + return; // terminal node + } + + // recurse down both sides + SV_AreaEntitiesTree ( node->children[0], ap, level+1 ); + SV_AreaEntitiesTree ( node->children[1], ap, level+1 ); +} + +void SV_SectorList_f( void ) +{ + areaParms_t ap; + +// ap.mins = mins; +// ap.maxs = maxs; +// ap.list = list; +// ap.count = 0; +// ap.maxcount = maxcount; + + entStats.clear(); + SV_AreaEntitiesTree(sv_worldSectors,&ap,0); + char mess[1000]; + multimap > >::iterator j; + for(j=entStats.begin();j!=entStats.end();j++) + { + sprintf(mess,"**************************************************\n"); + Sleep(5); + OutputDebugString(mess); + sprintf(mess,"level=%i, count=%i\n",(*j).first,(*j).second.first); + Sleep(5); + OutputDebugString(mess); + list::iterator k; + for(k=(*j).second.second.begin();k!=(*j).second.second.end();k++) + { + sprintf(mess,"mins=%f %f %f, maxs=%f %f %f\n", + (*k).mMins[0],(*k).mMins[1],(*k).mMins[2],(*k).mMaxs[0],(*k).mMaxs[1],(*k).mMaxs[2]); + OutputDebugString(mess); + } + } + +} +#endif + +//=========================================================================== + + +typedef struct { + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + const float *mins; + const float *maxs; // size of the moving object +/* +Ghoul2 Insert Start +*/ + vec3_t start; +/* +Ghoul2 Insert End +*/ + vec3_t end; + int passEntityNum; + int contentmask; +/* +Ghoul2 Insert Start +*/ + EG2_Collision eG2TraceType; + int useLod; + trace_t trace; // make sure nothing goes under here for Ghoul2 collision purposes +/* +Ghoul2 Insert End +*/ +} moveclip_t; + + +/* +==================== +SV_ClipMoveToEntities + +==================== +*/ +void SV_ClipMoveToEntities( moveclip_t *clip ) { + int i, num; + gentity_t *touchlist[MAX_GENTITIES], *touch, *owner; + trace_t trace, oldTrace; + clipHandle_t clipHandle; + const float *origin, *angles; + + num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES); + + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + owner = ( SV_GentityNum( clip->passEntityNum ) )->owner; + } else { + owner = NULL; + } + + for ( i=0 ; itrace.allsolid) { + return; + } + touch = touchlist[i]; + + // see if we should ignore this entity + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + if (touch->s.number == clip->passEntityNum) { + continue; // don't clip against the pass entity + } + if (touch->owner && touch->owner->s.number == clip->passEntityNum) { + continue; // don't clip against own missiles + } + if ( owner == touch) { + continue; // don't clip against owner + } + if ( owner && touch->owner == owner) { + continue; // don't clip against other missiles from our owner + } + } + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( ! ( clip->contentmask & touch->contents ) ) { + continue; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity (touch); + + origin = touch->currentOrigin; + angles = touch->currentAngles; + + + if ( !touch->bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + +#if 0 //G2_SUPERSIZEDBBOX is not being used + bool shrinkBox=true; + + if (clip->eG2TraceType != G2_SUPERSIZEDBBOX) + { + shrinkBox=false; + } + else if (trace.entityNum == touch->s.number&&touch->ghoul2.size()&&!(touch->contents & CONTENTS_LIGHTSABER)) + { + shrinkBox=false; + } + if (shrinkBox) + { + vec3_t sh_mins; + vec3_t sh_maxs; + int j; + for ( j=0 ; j<3 ; j++ ) + { + sh_mins[j]=clip->mins[j]+superSizedAdd; + sh_maxs[j]=clip->maxs[j]-superSizedAdd; + } + CM_TransformedBoxTrace ( &trace, clip->start, clip->end, + sh_mins, sh_maxs, clipHandle, clip->contentmask, + origin, angles); + } + else +#endif + { +#ifdef __MACOS__ + // compiler bug with const + CM_TransformedBoxTrace ( &trace, (float *)clip->start, (float *)clip->end, + (float *)clip->mins, (float *)clip->maxs, clipHandle, clip->contentmask, + origin, angles); +#else + CM_TransformedBoxTrace ( &trace, clip->start, clip->end, + clip->mins, clip->maxs, clipHandle, clip->contentmask, + origin, angles); +#endif + //FIXME: when startsolid in another ent, doesn't return correct entityNum + //ALSO: 2 players can be standing next to each other and this function will + //think they're in each other!!! + } + oldTrace = clip->trace; + + if ( trace.allsolid ) + { + if(!clip->trace.allsolid) + {//We didn't come in here all solid, so set the clip->trace's entityNum + clip->trace.entityNum = touch->s.number; + } + clip->trace.allsolid = qtrue; + trace.entityNum = touch->s.number; + } + else if ( trace.startsolid ) + { + if(!clip->trace.startsolid) + {//We didn't come in here starting solid, so set the clip->trace's entityNum + clip->trace.entityNum = touch->s.number; + } + clip->trace.startsolid = qtrue; + trace.entityNum = touch->s.number; + } + + if ( trace.fraction < clip->trace.fraction ) + { + qboolean oldStart; + + // make sure we keep a startsolid from a previous trace + oldStart = clip->trace.startsolid; + + trace.entityNum = touch->s.number; + clip->trace = trace; + clip->trace.startsolid |= oldStart; + } +/* +Ghoul2 Insert Start +*/ + + // decide if we should do the ghoul2 collision detection right here + if ((trace.entityNum == touch->s.number) && (clip->eG2TraceType != G2_NOCOLLIDE)) + { + // do we actually have a ghoul2 model here? + if (touch->ghoul2.size() && !(touch->contents & CONTENTS_LIGHTSABER)) + { + int oldTraceRecSize = 0; + int newTraceRecSize = 0; + int z; + + // we have to do this because sometimes you may hit a model's bounding box, but not actually penetrate the Ghoul2 Models polygons + // this is, needless to say, not good. So we must check to see if we did actually hit the model, and if not, reset the trace stuff + // to what it was to begin with + + // set our trace record size + for (z=0;ztrace.G2CollisionMap[z].mEntityNum != -1) + { + oldTraceRecSize++; + } + } + + // if we are looking at an entity then use the player state to get it's angles and origin from + float radius; +#if 0 //G2_SUPERSIZEDBBOX is not being used + if (clip->eG2TraceType == G2_SUPERSIZEDBBOX) + { + radius=(clip->maxs[0]-clip->mins[0]-2.0f*superSizedAdd)/2.0f; + } + else +#endif + { + radius=(clip->maxs[0]-clip->mins[0])/2.0f; + } + if (touch->client) + { + vec3_t world_angles; + + world_angles[PITCH] = 0; + //legs do not *always* point toward the viewangles! + //world_angles[YAW] = touch->client->viewangles[YAW]; + world_angles[YAW] = touch->client->legsYaw; + world_angles[ROLL] = 0; + + G2API_CollisionDetect(clip->trace.G2CollisionMap, touch->ghoul2, + world_angles, touch->client->origin, sv.time, touch->s.number, clip->start, clip->end, touch->s.modelScale, G2VertSpaceServer, clip->eG2TraceType, clip->useLod,radius); + } + // no, so use the normal entity state + else + { + //use the correct origin and angles! is this right now? + G2API_CollisionDetect(clip->trace.G2CollisionMap, touch->ghoul2, + touch->currentAngles, touch->currentOrigin, sv.time, touch->s.number, clip->start, clip->end, touch->s.modelScale, G2VertSpaceServer, clip->eG2TraceType, clip->useLod,radius); + } + + // set our new trace record size + + for (z=0;ztrace.G2CollisionMap[z].mEntityNum != -1) + { + newTraceRecSize++; + } + } + + // did we actually touch this model? If not, lets reset this ent as being hit.. + if (newTraceRecSize == oldTraceRecSize) + { + clip->trace = oldTrace; + } + else//this trace was valid, so copy the best collision into quake trace place info + { + for (z=0;ztrace.G2CollisionMap[z].mEntityNum==touch->s.number) + { + clip->trace.plane.normal[0] = clip->trace.G2CollisionMap[z].mCollisionNormal[0]; + clip->trace.plane.normal[1] = clip->trace.G2CollisionMap[z].mCollisionNormal[1]; + clip->trace.plane.normal[2] = clip->trace.G2CollisionMap[z].mCollisionNormal[2]; + break; + } + } + assert(ztrace.plane.normal)>0.1f); + } + } + } +/* +Ghoul2 Insert End +*/ + + } +} + + +/* +================== +SV_Trace + +Moves the given mins/maxs volume through the world from start to end. +passEntityNum and entities owned by passEntityNum are explicitly not checked. +================== +*/ +/* +Ghoul2 Insert Start +*/ +void SV_Trace( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, const int passEntityNum, const int contentmask, const EG2_Collision eG2TraceType, const int useLod ) { +/* +Ghoul2 Insert End +*/ +#ifdef _DEBUG + assert( !_isnan(start[0])&&!_isnan(start[1])&&!_isnan(start[2])&&!_isnan(end[0])&&!_isnan(end[1])&&!_isnan(end[2])); +#endif// _DEBUG + +#if SV_TRACE_PROFILE +#if MEM_DEBUG + { + int old=dbgMemSetCheckpoint(2002); + malloc(1); + dbgMemSetCheckpoint(old); + } +#endif +#endif + + moveclip_t clip; + int i; +// int startMS, endMS; + float world_frac; + + /* + startMS = Sys_Milliseconds (); + numTraces++; + */ + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + memset ( &clip, 0, sizeof ( moveclip_t ) - sizeof(clip.trace.G2CollisionMap )); + + // clip to world + //NOTE: this will stop not only on static architecture but also entity brushes such as + //doors, etc. This prevents us from being able to shorten the trace so that we can + //ignore all ents past this endpoint... perhaps need to check the entityNum in this + //BoxTrace or have it not clip against entity brushes here. + CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask ); + clip.trace.entityNum = clip.trace.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( clip.trace.fraction == 0 ) + {// blocked immediately by the world + *results = clip.trace; +// goto addtime; + return; + } + + clip.contentmask = contentmask; +/* +Ghoul2 Insert Start +*/ + VectorCopy( start, clip.start ); + clip.eG2TraceType = eG2TraceType; + clip.useLod = useLod; +/* +Ghoul2 Insert End +*/ + //Shorten the trace to the size of the trace until it hit the world + VectorCopy( clip.trace.endpos, clip.end ); + //remember the current completion fraction + world_frac = clip.trace.fraction; + //set the fraction back to 1.0 for the trace vs. entities + clip.trace.fraction = 1.0f; + + //VectorCopy( end, clip.end ); + // create the bounding box of the entire move + // we can limit it to the part of the move not + // already clipped off by the world, which can be + // a significant savings for line of sight and shot traces + clip.passEntityNum = passEntityNum; + +#if 0 //G2_SUPERSIZEDBBOX is not being used + vec3_t superMin; + vec3_t superMax; // prison, in boscobel + + if (eG2TraceType==G2_SUPERSIZEDBBOX) + { + for ( i=0 ; i<3 ; i++ ) + { + superMin[i]=mins[i]-superSizedAdd; + superMax[i]=maxs[i]+superSizedAdd; + } + clip.mins = superMin; + clip.maxs = superMax; + } + else +#endif + { + clip.mins = mins; + clip.maxs = maxs; + } + + for ( i=0 ; i<3 ; i++ ) { + if ( end[i] > start[i] ) { + clip.boxmins[i] = clip.start[i] + clip.mins[i] - 1; + clip.boxmaxs[i] = clip.end[i] + clip.maxs[i] + 1; + } else { + clip.boxmins[i] = clip.end[i] + clip.mins[i] - 1; + clip.boxmaxs[i] = clip.start[i] + clip.maxs[i] + 1; + } + } + + // clip to other solid entities + SV_ClipMoveToEntities ( &clip ); + + //scale the trace back down by the previous fraction + clip.trace.fraction *= world_frac; + *results = clip.trace; + +/* +addtime: + endMS = Sys_Milliseconds (); + + timeInTrace += endMS - startMS; +*/ +} + + + +/* +============= +SV_PointContents +============= +*/ +int SV_PointContents( const vec3_t p, int passEntityNum ) { + gentity_t *touch[MAX_GENTITIES], *hit; + int i, num; + int contents, c2; +// int startMS, endMS; + clipHandle_t clipHandle; + const float *angles; + +#if MEM_DEBUG +#if SV_TRACE_PROFILE + { + int old=dbgMemSetCheckpoint(2001); + malloc(1); + dbgMemSetCheckpoint(old); + } +#endif +#endif + + /* + startMS = Sys_Milliseconds (); + numTraces++; + */ + + // get base contents from world + contents = CM_PointContents( p, 0 ); + + // or in contents from all the other entities + num = SV_AreaEntities( p, p, touch, MAX_GENTITIES ); + + for ( i=0 ; is.number == passEntityNum ) { + continue; + } + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity( hit ); + angles = hit->s.angles; + if ( !hit->bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + c2 = CM_TransformedPointContents (p, clipHandle, hit->s.origin, hit->s.angles); + + contents |= c2; + } + + /* + endMS = Sys_Milliseconds (); + timeInTrace += endMS - startMS; + */ + return contents; +} + + diff --git a/code/smartheap/HAW32M.LIB b/code/smartheap/HAW32M.LIB new file mode 100644 index 0000000000000000000000000000000000000000..1fc259ccd36eee6e423be706c79b33ffed4d0c66 GIT binary patch literal 212108 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 900 +#pragma warning(disable : 4507) +#endif + +#if defined(new) +#if defined(MEM_DEBUG) +#undef new +#define DEFINE_NEW_MACRO 1 +#endif +#endif + +#ifdef DEBUG_NEW +#undef DEBUG_NEW +#endif +#ifdef DEBUG_DELETE +#undef DEBUG_DELETE +#endif + +#ifdef MEM_DEBUG +#define DBG_FORMAL , const char MEM_FAR *file, int line +#define DBG_ACTUAL , file, line + + +/* both debug and non-debug versions are both defined so that calls + * to shi_New from inline versions of operator new in modules + * that were not recompiled with MEM_DEBUG will resolve correctly + */ +void MEM_FAR * MEM_ENTRY_ANSI shi_New(unsigned long DBG_FORMAL, unsigned=0, + MEM_POOL=0); + +/* operator new variants: */ + +/* compiler-specific versions of new */ +#if UINT_MAX == 0xFFFFu +#if defined(__BORLANDC__) +#if (defined(__LARGE__) || defined(__COMPACT__) || defined(__HUGE__)) +inline void far *operator new(unsigned long sz DBG_FORMAL) + { return shi_New(sz DBG_ACTUAL); } +#if __BORLANDC__ >= 0x450 +inline void MEM_FAR *operator new[](unsigned long sz DBG_FORMAL) + { return shi_New(sz DBG_ACTUAL); } +#endif /* __BORLANDC__ >= 0x450 */ +#endif /* __LARGE__ */ + +#elif defined(_MSC_VER) +#if (defined(M_I86LM) || defined(M_I86CM) || defined(M_I86HM)) +inline void __huge * operator new(unsigned long count, size_t sz DBG_FORMAL) + { return (void __huge *)shi_New(count * sz DBG_ACTUAL, MEM_HUGE); } +#endif /* M_I86LM */ +#endif /* _MSC_VER */ + +#endif /* compiler-specific versions of new */ + +/* version of new that passes memory allocation flags */ +inline void MEM_FAR *operator new(size_t sz DBG_FORMAL, unsigned flags) + { return shi_New(sz DBG_ACTUAL, flags); } + +/* version of new that allocates from a specified memory pool with alloc flags*/ +inline void MEM_FAR *operator new(size_t sz DBG_FORMAL, MEM_POOL pool) + { return shi_New(sz DBG_ACTUAL, 0, pool); } +inline void MEM_FAR *operator new(size_t sz DBG_FORMAL, MEM_POOL pool, + unsigned flags) + { return shi_New(sz DBG_ACTUAL, flags, pool); } +#ifdef SHI_ARRAY_NEW +inline void MEM_FAR *operator new[](size_t sz DBG_FORMAL, MEM_POOL pool) + { return shi_New(sz DBG_ACTUAL, 0, pool); } +inline void MEM_FAR *operator new[](size_t sz DBG_FORMAL, MEM_POOL pool, + unsigned flags) + { return shi_New(sz DBG_ACTUAL, flags, pool); } +#endif /* SHI_ARRAY_NEW */ + +/* version of new that changes the size of a memory block */ +inline void MEM_FAR *operator new(size_t new_sz DBG_FORMAL, + void MEM_FAR *lpMem, unsigned flags) + { return _dbgMemReAllocPtr1(lpMem, new_sz, flags, MEM_NEW DBG_ACTUAL); } +#ifdef SHI_ARRAY_NEW +inline void MEM_FAR *operator new[](size_t new_sz DBG_FORMAL, + void MEM_FAR *lpMem, unsigned flags) + { return _dbgMemReAllocPtr1(lpMem, new_sz, flags, MEM_NEW DBG_ACTUAL); } +#endif /* SHI_ARRAY_NEW */ + +/* To have HeapAgent track file/line of C++ allocations, + * define new/delete as macros: + * #define new DEBUG_NEW + * #define delete DEBUG_DELETE + * + * In cases where you use explicit placement syntax, or in modules that define + * operator new/delete, you must undefine the new/delete macros, e.g.: + * #undef new + * void *x = new(placementArg) char[30]; // cannot track file/line info + * #define new DEBUG_NEW + * void *y = new char[20]; // resume tracking file/line info + */ + +#if (!(defined(_AFX) && defined(_DEBUG)) \ + && !(defined(_MSC_VER) && _MSC_VER >= 900)) +/* this must be defined out-of-line for _DEBUG MFC and MEM_DEBUG VC++/Win32 */ +inline void MEM_FAR *operator new(size_t sz DBG_FORMAL) + { return shi_New(sz DBG_ACTUAL); } +#else +void MEM_FAR * MEM_ENTRY_ANSI operator new(size_t sz DBG_FORMAL); +#endif /* _AFX && _DEBUG */ + +#ifdef SHI_ARRAY_NEW +inline void MEM_FAR *operator new[](size_t sz DBG_FORMAL) + { return shi_New(sz DBG_ACTUAL); } +#endif /* SHI_ARRAY_NEW */ + +#if !(defined(__IBMCPP__) && defined(__DEBUG_ALLOC__)) +/* debug new/delete built in for IBM Set C++ and Visual Age C++ */ + +#define DEBUG_NEW new(__FILE__, __LINE__) +#define DEBUG_NEW1(x_) new(__FILE__, __LINE__, x_) +#define DEBUG_NEW2(x_, y_) new(__FILE__, __LINE__, x_, y_) +#define DEBUG_NEW3(x_, y_, z_) new(__FILE__, __LINE__, x_, y_, z_) + +#define DEBUG_DELETE _shi_deleteLoc(__FILE__, __LINE__), delete + +#ifdef DEFINE_NEW_MACRO +#ifdef macintosh /* MPW C++ bug precludes new --> DEBUG_NEW --> new(...) */ +#define new new(__FILE__, __LINE__) +#define delete _shi_deleteLoc(__FILE__, __LINE__), delete +#else +#define new DEBUG_NEW +#define delete DEBUG_DELETE +#endif /* macintosh */ +#endif /* DEFINE_NEW_MACRO */ +#endif /* __IBMCPP__ */ +#endif /* MEM_DEBUG */ + +#include "smrtheap.hpp" + +#ifndef __BORLANDC__ +} +#endif /* __BORLANDC__ */ + +#endif /* __cplusplus */ + +#endif /* !defined(_HEAPAGNT_H) */ diff --git a/code/smartheap/SMRTHEAP.C b/code/smartheap/SMRTHEAP.C new file mode 100644 index 0000000..18aa985 --- /dev/null +++ b/code/smartheap/SMRTHEAP.C @@ -0,0 +1,54 @@ +extern int SmartHeap_malloc; +extern int SmartHeap_new; + +static void *refSmartHeap_malloc = &SmartHeap_malloc; + +#if defined(_DEBUG) && !defined(MEM_DEBUG) +#define MEM_DEBUG 1 +#endif + +#if defined(MFC) && !defined(_AFXDLL) + +static void *refSmartHeap_new = &SmartHeap_new; + +#ifdef MEM_DEBUG +#if _MSC_VER < 1000 +#pragma comment(lib, "hamfc32m.lib") +#else +#pragma comment(lib, "hamfc4m.lib") +#endif /* _MSC_VER */ +#else +#if _MSC_VER >= 1000 +#ifdef _MT +#pragma comment(lib, "shmfc4mt.lib") +#else +#pragma comment(lib, "shmfc4m.lib") +#endif /* _MT */ +#endif /* _MSC_VER */ +#endif /* MEM_DEBUG */ + +#endif /* MFC */ + +#if defined(MEM_DEBUG) +#pragma comment(lib, "haw32m.lib") +#elif defined(_DLL) +#ifdef _MT +#ifdef MEM_SMP +#pragma comment(lib, "shdsmpmt.lib") +#else +#pragma comment(lib, "shdw32mt.lib") +#endif /* MEM_SMP */ +#else +#pragma comment(lib, "shdw32m.lib") +#endif /* _MT */ +#else /* _DLL */ +#ifdef _MT +#ifdef MEM_SMP +#pragma comment(lib, "shlsmpmt.lib") +#else +#pragma comment(lib, "shlw32mt.lib") +#endif /* MEM_SMP */ +#else +#pragma comment(lib, "shlw32m.lib") +#endif /* _MT */ +#endif /* MEM_DEBUG */ diff --git a/code/smartheap/SMRTHEAP.H b/code/smartheap/SMRTHEAP.H new file mode 100644 index 0000000..cb5e5d6 --- /dev/null +++ b/code/smartheap/SMRTHEAP.H @@ -0,0 +1,847 @@ +/* smrtheap.h -- SmartHeap (tm) public C header file + * Professional Memory Management Library + * + * Copyright (C) 1991-1999 Compuware Corporation. + * All Rights Reserved. + * + * No part of this source code may be copied, modified or reproduced + * in any form without retaining the above copyright notice. + * This source code, or source code derived from it, may not be redistributed + * without express written permission of the author. + * + */ + +#if !defined(_SMARTHEAP_H) +#define _SMARTHEAP_H + +#include +#include + +#if !defined(macintosh) && !defined(THINK_C) && !defined(__MWERKS__) \ + && !defined(SHANSI) && UINT_MAX == 0xFFFFu \ + && (defined(_Windows) || defined(_WINDOWS) || defined(__WINDOWS__)) + #define MEM_WIN16 +#endif + +#if (UINT_MAX == 0xFFFFu) && (defined(MEM_WIN16) \ + || defined(MSDOS) || defined(__MSDOS__) || defined(__DOS__)) + /* 16-bit X86 */ + #if defined(SYS_DLL) + #if defined(_MSC_VER) && _MSC_VER <= 600 + #define MEM_ENTRY _export _loadds far pascal + #else + #define MEM_ENTRY _export far pascal + #endif + #else + #define MEM_ENTRY far pascal + #endif + #ifdef __WATCOMC__ + #define MEM_ENTRY_ANSI __far + #else + #define MEM_ENTRY_ANSI far cdecl + #endif + #define MEM_FAR far + #if defined(MEM_WIN16) + #define MEM_ENTRY2 _export far pascal + #elif defined(DOS16M) || defined(DOSX286) + #define MEM_ENTRY2 _export _loadds far pascal + #endif + +#else /* not 16-bit X86 */ + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) \ + || defined(__WIN32__) || defined(__NT__) + #define MEM_WIN32 + #if defined(_MSC_VER) + #if defined(_SHI_Pool) && defined(SYS_DLL) + #define MEM_ENTRY1 __declspec(dllexport) + #define MEM_ENTRY4 __declspec(dllexport) extern + #elif !defined(_SHI_Pool) && (defined(MEM_DEBUG) || defined(MEM_DLL)) + #define MEM_ENTRY1 __declspec(dllimport) + #if defined(_M_IX86) || defined(_X86_) + #define MemDefaultPool shi_MemDefaultPool + #define MEM_ENTRY4 __declspec(dllimport) + #endif + #endif + #endif + #if (defined(_MT) || defined(__MT__)) && !defined(MEM_DEBUG) +/* @@@ #define MEM_MT 1 */ + #endif + #if !defined(_MSC_VER) || defined(_M_IX86) || defined(_X86_) + #define MEM_ENTRY __stdcall + #else + #define MEM_ENTRY __cdecl /* for NT/RISC */ + #endif + #ifndef __WATCOMC__ + #define MEM_ENTRY_ANSI __cdecl + #endif + +#elif defined(__OS2__) + #if defined(__BORLANDC__) || defined(__WATCOMC__) + #if defined(SYS_DLL) + #define MEM_ENTRY __export __syscall + #else + #define MEM_ENTRY __syscall + #endif /* SYS_DLL */ + #ifdef __BORLANDC__ + #define MEM_ENTRY_ANSI __stdcall + #endif + #elif defined(__IBMC__) || defined(__IBMCPP__) + #if defined(SYS_DLL) && 0 + #define MEM_ENTRY _Export _System + #else + #define MEM_ENTRY _System + #endif + #define MEM_ENTRY_ANSI _Optlink + #define MEM_ENTRY3 MEM_ENTRY + #define MEM_CALLBACK MEM_ENTRY3 + #define MEM_ENTRY2 + #endif + +#elif defined(__sun) || defined(__hpux) || defined(__osf__) || defined(sgi) + #if defined(_REENTRANT) && !defined(MEM_DEBUG) +/* @@@ #define MEM_MT 1 */ + #endif + +#elif defined(_AIX) + #if defined(_THREAD_SAFE) && !defined(MEM_DEBUG) +/* #define MEM_MT 1 */ + #endif + +#endif /* WIN32, OS2, UNIX */ + +#if defined(__WATCOMC__) && defined(__SW_3S) + /* Watcom stack calling convention */ +#ifndef __OS2__ +#ifdef __WINDOWS_386__ + #pragma aux syscall "*_" parm routine [eax ebx ecx edx fs gs] modify [eax]; +#else + #pragma aux syscall "*_" parm routine [eax ebx ecx edx] modify [eax]; +#endif +#ifndef MEM_ENTRY + #define MEM_ENTRY __syscall +#endif /* MEM_ENTRY */ +#endif +#endif /* Watcom stack calling convention */ + +#endif /* end of system-specific declarations */ + +#ifndef MEM_ENTRY + #define MEM_ENTRY +#endif +#ifndef MEM_ENTRY1 + #define MEM_ENTRY1 +#endif +#ifndef MEM_ENTRY2 + #define MEM_ENTRY2 MEM_ENTRY +#endif +#ifndef MEM_ENTRY3 + #define MEM_ENTRY3 +#endif +#ifndef MEM_ENTRY4 + #define MEM_ENTRY4 extern +#endif +#ifndef MEM_CALLBACK +#define MEM_CALLBACK MEM_ENTRY2 +#endif +#ifndef MEM_ENTRY_ANSI + #define MEM_ENTRY_ANSI +#endif +#ifndef MEM_FAR + #define MEM_FAR +#endif + +#ifdef applec +/* Macintosh: Apple MPW C/C++ passes char/short parms as longs (4 bytes), + * whereas Symantec C/C++ for MPW passes these as words (2 bytes); + * therefore, canonicalize all integer parms as 'int' for this platform. + */ + #define MEM_USHORT unsigned + #define MEM_UCHAR unsigned +#else + #define MEM_USHORT unsigned short + #define MEM_UCHAR unsigned char +#endif /* applec */ + +#ifdef __cplusplus +extern "C" { +#endif + + +#if !defined(MEM_DEBUG) || !(defined(MEM_WIN16) || defined(MEM_WIN32)) +#define SHI_MAJOR_VERSION 5 +#define SHI_MINOR_VERSION 0 +#define SHI_UPDATE_LEVEL 0 +#endif /* !(MEM_WIN16 || MEM_WIN32) */ + + +/*** Types ***/ + +typedef int MEM_BOOL; + +/* Version Masks */ +typedef unsigned MEM_VERSION; +#define MEM_MAJOR_VERSION(v) (((v) & 0xF000u) >> 12) +#define MEM_MINOR_VERSION(v) (((v) & 0x0F00u) >> 8) +#define MEM_UPDATE_VERSION(v) ((v) & 0x00FFu) + +/* Note: these types are struct's rather than integral types to facilitate + * compile-time type-checking. MEM_POOL and MEM_HANDLE should be regarded + * as black boxes, and treated just like handles. + * You should not have any type casts to or from MEM_POOL or MEM_HANDLE; + * nor should you dereference variables of type MEM_POOL or MEM_HANDLE + * (unless you are using SmartHeap to replace NewHandle on the Mac, and + * you have existing code that dereferences handles). + */ +#ifdef _SHI_Pool + typedef struct _SHI_Pool MEM_FAR *MEM_POOL; + typedef struct _SHI_MovHandle MEM_FAR *MEM_HANDLE; +#else + #ifdef THINK_C + typedef void *MEM_POOL; + typedef void **MEM_HANDLE; + #else + typedef struct _SHI_Pool { int reserved; } MEM_FAR *MEM_POOL; + typedef struct _SHI_MovHandle { int reserved; } MEM_FAR *MEM_HANDLE; + #endif +#endif + + +/* Error codes: errorCode field of MEM_ERROR_INFO */ +typedef enum +{ + MEM_NO_ERROR=0, + MEM_INTERNAL_ERROR, + MEM_OUT_OF_MEMORY, + MEM_BLOCK_TOO_BIG, + MEM_ALLOC_ZERO, + MEM_RESIZE_FAILED, + MEM_LOCK_ERROR, + MEM_EXCEEDED_CEILING, + MEM_TOO_MANY_PAGES, + MEM_TOO_MANY_TASKS, + MEM_BAD_MEM_POOL, + MEM_BAD_BLOCK, + MEM_BAD_FREE_BLOCK, + MEM_BAD_HANDLE, + MEM_BAD_POINTER, + MEM_WRONG_TASK, + MEM_NOT_FIXED_SIZE, + MEM_BAD_FLAGS, +#ifdef MEM_DEBUG + MEM_BAD_BUFFER, + MEM_DOUBLE_FREE, + MEM_UNDERWRITE, + MEM_OVERWRITE, + MEM_FREE_BLOCK_WRITE, + MEM_READONLY_MODIFIED, + MEM_NOFREE, + MEM_NOREALLOC, + MEM_LEAKAGE, + MEM_FREE_BLOCK_READ, + MEM_UNINITIALIZED_READ, + MEM_UNINITIALIZED_WRITE, + MEM_OUT_OF_BOUNDS_READ, + MEM_UNDERWRITE_STACK, + MEM_OVERWRITE_STACK, + MEM_FREE_STACK_READ, + MEM_UNINITIALIZED_READ_STACK, + MEM_UNINITIALIZED_WRITE_STACK, + MEM_OUT_OF_BOUNDS_READ_STACK, + MEM_LASTOK, + MEM_BREAKPOINT, + MEM_ERROR_CODE_COUNT, +#endif /* MEM_DEBUG */ + MEM_ERROR_CODE_INT_MAX = INT_MAX /* to ensure enum is full int in size */ +} MEM_ERROR_CODE; + +/* HeapAgent Entry-Point API identifiers: errorAPI field of MEM_ERROR_INFO */ +typedef enum +{ + MEM_NO_API, + MEM_MEMVERSION, + MEM_MEMREGISTERTASK, + MEM_MEMUNREGISTERTASK, + MEM_MEMPOOLINIT, + MEM_MEMPOOLINITFS, + MEM_MEMPOOLFREE, + MEM_MEMPOOLSETPAGESIZE, + MEM_MEMPOOLSETBLOCKSIZEFS, + MEM_MEMPOOLSETFLOOR, + MEM_MEMPOOLSETCEILING, + MEM_MEMPOOLPREALLOCATE, + MEM_MEMPOOLPREALLOCATEHANDLES, + MEM_MEMPOOLSHRINK, + MEM_MEMPOOLSIZE, + MEM_MEMPOOLCOUNT, + MEM_MEMPOOLINFO, + MEM_MEMPOOLFIRST, + MEM_MEMPOOLNEXT, + MEM_MEMPOOLWALK, + MEM_MEMPOOLCHECK, + MEM_MEMALLOC, + MEM_MEMREALLOC, + MEM_MEMFREE, + MEM_MEMLOCK, + MEM_MEMUNLOCK, + MEM_MEMFIX, + MEM_MEMUNFIX, + MEM_MEMLOCKCOUNT, + MEM_MEMISMOVEABLE, + MEM_MEMREFERENCE, + MEM_MEMHANDLE, + MEM_MEMSIZE, + MEM_MEMALLOCPTR, + MEM_MEMREALLOCPTR, + MEM_MEMFREEPTR, + MEM_MEMSIZEPTR, + MEM_MEMCHECKPTR, + MEM_MEMALLOCFS, + MEM_MEMFREEFS, + MEM_MEM_MALLOC, + MEM_MEM_CALLOC, + MEM_MEM_REALLOC, + MEM_MEM_FREE, + MEM_NEW, + MEM_DELETE, + MEM_DBGMEMPOOLSETCHECKFREQUENCY, + MEM_DBGMEMPOOLDEFERFREEING, + MEM_DBGMEMPOOLFREEDEFERRED, + MEM_DBGMEMPROTECTPTR, + MEM_DBGMEMREPORTLEAKAGE, + MEM_MEMPOOLINITNAMEDSHARED, + MEM_MEMPOOLINITNAMEDSHAREDEX, + MEM_MEMPOOLATTACHSHARED, + MEM_DBGMEMPOOLINFO, + MEM_DBGMEMPTRINFO, + MEM_DBGMEMSETTINGSINFO, + MEM_DBGMEMCHECKPTR, + MEM_DBGMEMPOOLSETNAME, + MEM_DBGMEMPOOLSETDEFERQUEUELEN, + MEM_DBGMEMFREEDEFERRED, + MEM_DBGMEMCHECKALL, + MEM_DBGMEMBREAKPOINT, + MEM_MEMPOOLLOCK, + MEM_MEMPOOLUNLOCK, + MEM_MEMPOOLSETSMALLBLOCKSIZE, + MEM_MEMSIZEREQUESTED, + MEM_MSIZE, + MEM_EXPAND, + MEM_GETPROCESSHEAP, + MEM_GETPROCESSHEAPS, + MEM_GLOBALALLOC, + MEM_GLOBALFLAGS, + MEM_GLOBALFREE, + MEM_GLOBALHANDLE, + MEM_GLOBALLOCK, + MEM_GLOBALREALLOC, + MEM_GLOBALSIZE, + MEM_GLOBALUNLOCK, + MEM_HEAPALLOC, + MEM_HEAPCOMPACT, + MEM_HEAPCREATE, + MEM_HEAPDESTROY, + MEM_HEAPFREE, + MEM_HEAPLOCK, + MEM_HEAPREALLOC, + MEM_HEAPSIZE, + MEM_HEAPUNLOCK, + MEM_HEAPVALIDATE, + MEM_HEAPWALK, + MEM_LOCALALLOC, + MEM_LOCALFLAGS, + MEM_LOCALFREE, + MEM_LOCALHANDLE, + MEM_LOCALLOCK, + MEM_LOCALREALLOC, + MEM_LOCALSIZE, + MEM_LOCALUNLOCK, + MEM_MEMPOOLINITREGION, + MEM_TERMINATE, + MEM_HEAPAGENT, + MEM_USER_API, + MEM_API_COUNT, + MEM_API_INT_MAX = INT_MAX /* to ensure enum is full int in size */ +} MEM_API; + +#define MEM_MAXCALLSTACK 16 /* maximum number of call stack frames recorded */ + +/* Error info, passed to error-handling callback routine */ +typedef struct _MEM_ERROR_INFO +{ + MEM_ERROR_CODE errorCode; /* error code identifying type of error */ + MEM_POOL pool; /* pool in which error occurred, if known */ + +/* all fields below this are valid only for debugging lib */ + /* the following seven fields identify the call where error detected */ + MEM_API errorAPI; /* fn ID of entry-point where error detected */ + MEM_POOL argPool; /* memory pool parameter, if applicable */ + void MEM_FAR *argPtr; /* memory pointer parameter, if applicable */ + void MEM_FAR *argBuf; /* result buffer parameter, if applicable */ + MEM_HANDLE argHandle; /* memory handle parameter, if applicable */ + unsigned long argSize; /* size parameter, if applicable */ + unsigned long argCount; /* count parameter, if applicable */ + unsigned argFlags; /* flags parameter, if applicable */ + + /* the following two fields identify the app source file and line */ + const char MEM_FAR *file; /* app source file containing above call */ + int line; /* source line in above file */ + + /* the following two fields identify call instance of error detection */ + unsigned long allocCount; /* enumeration of allocation since 1st alloc */ + unsigned long passCount; /* enumeration of call at at above file/line */ + unsigned checkpoint; /* group with which call has been tagged */ + + /* the following fields, if non-NULL, points to the address where an + overwrite was detected and another MEM_ERROR_INFO structure + identifying where the corrupted object was first created, if known */ + void MEM_FAR *errorAlloc; /* ptr to beginning of alloc related to error */ + void MEM_FAR *corruptAddr; + struct _MEM_ERROR_INFO MEM_FAR *objectCreationInfo; + + unsigned long threadID; /* ID of thread where error detected */ + unsigned long pid; /* ID of process where error detected */ + + void MEM_FAR *callStack[MEM_MAXCALLSTACK]; +} MEM_ERROR_INFO; + +/* Error handling callback function */ +typedef MEM_BOOL (MEM_ENTRY2 * MEM_ENTRY3 MEM_ERROR_FN) + (MEM_ERROR_INFO MEM_FAR *); + + +/* Block Type: field of MEM_POOL_ENTRY, field of MEM_POOL_INFO, + * parameter to MemPoolPreAllocate + */ +typedef enum +{ + MEM_FS_BLOCK = 0x0001u, + MEM_VAR_MOVEABLE_BLOCK = 0x0002u, + MEM_VAR_FIXED_BLOCK = 0x0004u, + MEM_EXTERNAL_BLOCK = 0x0008u, + MEM_BLOCK_TYPE_INT_MAX = INT_MAX /* to ensure enum is full int in size */ +} MEM_BLOCK_TYPE; + +typedef enum +{ + MEM_SMALL_BLOCK_NONE, + MEM_SMALL_BLOCK_SH3, + MEM_SMALL_BLOCK_SH5, + MEM_SMALL_BLOCK_INT_MAX = INT_MAX /* to ensure enum is full int in size */ +} MEM_SMALL_BLOCK_ALLOCATOR; + +/* Pool Entry: parameter to MemPoolWalk */ +typedef struct +{ + void MEM_FAR *entry; + MEM_POOL pool; + MEM_BLOCK_TYPE type; + MEM_BOOL isInUse; + unsigned long size; + MEM_HANDLE handle; + unsigned lockCount; + void MEM_FAR *reserved_ptr; +} MEM_POOL_ENTRY; + +/* Pool Status: returned by MemPoolWalk, MemPoolFirst, MemPoolNext */ +typedef enum +{ + MEM_POOL_OK = 1, + MEM_POOL_CORRUPT = -1, + MEM_POOL_CORRUPT_FATAL = -2, + MEM_POOL_END = 0, + MEM_POOL_STATUS_INT_MAX = INT_MAX /* to ensure enum is full int in size */ +} MEM_POOL_STATUS; + +/* Pointer Status: returned by MemCheckPtr */ +typedef enum +{ + MEM_POINTER_OK = 1, + MEM_POINTER_WILD = 0, + MEM_POINTER_FREE = -1, + MEM_POINTER_STATUS_INT_MAX = INT_MAX /* to ensure enum is full int */ +} MEM_POINTER_STATUS; + +/* Pool Info: parameter to MemPoolInfo, MemPoolFirst, MemPoolNext */ +typedef struct +{ + MEM_POOL pool; + MEM_BLOCK_TYPE type; /* disjunctive combination of block type flags */ + unsigned short blockSizeFS; + unsigned short smallBlockSize; + unsigned pageSize; + unsigned long floor; + unsigned long ceiling; + unsigned flags; + MEM_ERROR_FN errorFn; +} MEM_POOL_INFO; + +/* Flags passed to MemAlloc, MemAllocPtr, MemReAlloc, MemReAllocPtr */ +#define MEM_FIXED 0x0000u /* fixed handle-based block */ +#define MEM_ZEROINIT 0x0001u /* == TRUE for SH 1.5 compatibility */ +#define MEM_MOVEABLE 0x0002u /* moveable handle-based block */ +#define MEM_RESIZEABLE 0x0004u /* reserve space above block */ +#define MEM_RESIZE_IN_PLACE 0x0008u /* do not move block (realloc) */ +#define MEM_NOGROW 0x0010u /* do not grow heap to satisfy request */ +#define MEM_NOEXTERNAL 0x0020u /* reserved for internal use */ +#define MEM_NOCOMPACT 0x0040u /* do not compact to satisfy request */ +#define MEM_NO_SERIALIZE 0x0080u /* do not serialize this request */ +#define MEM_HANDLEBASED 0x4000u /* for internal use */ +#define MEM_RESERVED 0x8000u /* for internal use */ + +#define MEM_UNLOCK_FAILED USHRT_MAX + +/* Flags passed to MemPoolInit, MemPoolInitFS */ +#define MEM_POOL_SHARED 0x0001u /* == TRUE for SH 1.5 compatibility */ +#define MEM_POOL_SERIALIZE 0x0002u /* pool used in more than one thread */ +#define MEM_POOL_VIRTUAL_LOCK 0x0004u /* pool is locked in physical memory */ +#define MEM_POOL_ZEROINIT 0x0008u /* malloc/new from pool zero-inits */ +#define MEM_POOL_REGION 0x0010u /* store pool in user-supplied region*/ +#define MEM_POOL_DEFAULT 0x8000u /* pool with default characteristics */ + +/* Default memory pool for C malloc, C++ new (for backwards compatibility) */ +#define MEM_DEFAULT_POOL MemDefaultPool + +/* define and initialize these variables at file scope to change defaults */ +extern unsigned short MemDefaultPoolBlockSizeFS; +extern unsigned MemDefaultPoolPageSize; +extern unsigned MemDefaultPoolFlags; + +/* define SmartHeap_malloc at file scope if you + * are intentionally _NOT_ linking in the SmartHeap malloc definition + * ditto for SmartHeap operator new, and fmalloc et al. + */ +extern int SmartHeap_malloc; +extern int SmartHeap_far_malloc; +extern int SmartHeap_new; + +#define MEM_ERROR_RET ULONG_MAX + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#ifdef MEM_DEBUG +#include "heapagnt.h" +#endif + +#endif /* !defined(_SMARTHEAP_H) */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef MEM_MT +#ifdef MemDefaultPool +#undef MemDefaultPool +#endif +#define MemDefaultPool shi_getThreadPool() +#ifndef MemInitDefaultPool +#define MemInitDefaultPool() shi_getThreadPool() +#define MemFreeDefaultPool() shi_freeThreadPools() +#endif +MEM_ENTRY1 MEM_POOL MEM_ENTRY shi_getThreadPool(void); +MEM_ENTRY1 MEM_BOOL MEM_ENTRY shi_freeThreadPools(void); + +#else /* MEM_MT */ +MEM_ENTRY4 MEM_POOL MemDefaultPool; +MEM_POOL MEM_ENTRY MemInitDefaultPool(void); +MEM_BOOL MEM_ENTRY MemFreeDefaultPool(void); +#endif /* MEM_MT */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +/*** Function Prototypes ***/ + +#ifndef _SMARTHEAP_PROT +#define _SMARTHEAP_PROT + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _shAPI + #if defined(MEM_DEBUG) && !defined(SHI_NO_MEM_DEBUG) + #define _shAPI(ret, name) MEM_ENTRY1 ret MEM_ENTRY _dbg ## name + #else + #define _shAPI(ret, name) MEM_ENTRY1 ret MEM_ENTRY name + #endif +#endif + +#ifndef _dbgARGS + #if defined(MEM_DEBUG) && !defined(SHI_NO_MEM_DEBUG) + #define _dbgARGS1 const char MEM_FAR *, int + #define _dbgARGS , _dbgARGS1 + #else + #define _dbgARGS1 void + #define _dbgARGS + #endif +#endif + + +/**** HOW TO READ SmartHeap PROTOTYPES **** + * prototypes below have the follow syntax in order to support both debug + * and non-debug APIs with single-source: + * + * _shiAPI(, )([] _dbgARGS); + * + * the above translates to a C prototype as follows: + * + * ([]); + */ + +/* Library Version */ +MEM_ENTRY1 MEM_VERSION MEM_ENTRY MemVersion(void); + +/* Library Registration */ +_shAPI(MEM_BOOL, MemRegisterTask)(_dbgARGS1); +_shAPI(MEM_BOOL, MemUnregisterTask)(_dbgARGS1); + +/* Process heap usage */ +MEM_ENTRY1 void MEM_ENTRY MemProcessSetGrowIncrement(unsigned long); + +/* Memory Pool Functions */ +_shAPI(MEM_POOL, MemPoolInit)(unsigned _dbgARGS); +_shAPI(MEM_POOL, MemPoolInitFS)(MEM_USHORT, unsigned long, + unsigned _dbgARGS); +_shAPI(MEM_POOL, MemPoolInitRegion)(void MEM_FAR *, + unsigned long size, unsigned _dbgARGS); +_shAPI(MEM_POOL, MemPoolInitRegionEx)(void MEM_FAR *addr, + unsigned long size, unsigned flags, void MEM_FAR *security _dbgARGS); +_shAPI(MEM_POOL, MemPoolInitNamedShared)(const char MEM_FAR *, + unsigned long size,unsigned _dbgARGS); +_shAPI(MEM_POOL, MemPoolInitNamedSharedEx)(void MEM_FAR *addr, + unsigned pidCount, unsigned long MEM_FAR *pids, void MEM_FAR *security, + const char MEM_FAR *name, unsigned long size, unsigned flags _dbgARGS); +_shAPI(MEM_POOL, MemPoolAttachShared)(MEM_POOL, const char MEM_FAR * _dbgARGS); +_shAPI(MEM_BOOL, MemPoolFree)(MEM_POOL _dbgARGS); +_shAPI(unsigned, MemPoolSetPageSize)(MEM_POOL, unsigned _dbgARGS); +_shAPI(MEM_BOOL, MemPoolSetBlockSizeFS)(MEM_POOL, MEM_USHORT _dbgARGS); +MEM_ENTRY1 unsigned long MEM_ENTRY MemPoolSetGrowIncrement(MEM_POOL, + unsigned long); +MEM_ENTRY1 MEM_BOOL MEM_ENTRY MemPoolSetSmallBlockAllocator(MEM_POOL, MEM_SMALL_BLOCK_ALLOCATOR); +_shAPI(MEM_BOOL, MemPoolSetSmallBlockSize)(MEM_POOL, MEM_USHORT _dbgARGS); +_shAPI(unsigned long, MemPoolSetFloor)(MEM_POOL, unsigned long _dbgARGS); +_shAPI(unsigned long, MemPoolSetCeiling)(MEM_POOL, unsigned long _dbgARGS); +_shAPI(unsigned long, MemPoolPreAllocate)(MEM_POOL, unsigned long, + MEM_BLOCK_TYPE _dbgARGS); +_shAPI(unsigned long, MemPoolPreAllocateHandles)(MEM_POOL, + unsigned long _dbgARGS); +_shAPI(unsigned long, MemPoolShrink)(MEM_POOL _dbgARGS); +_shAPI(unsigned long, MemPoolSize)(MEM_POOL _dbgARGS); +_shAPI(unsigned long, MemPoolCount)(MEM_POOL _dbgARGS); +_shAPI(MEM_BOOL, MemPoolInfo)(MEM_POOL, void MEM_FAR *, + MEM_POOL_INFO MEM_FAR* _dbgARGS); +_shAPI(MEM_POOL_STATUS, MemPoolFirst)(MEM_POOL_INFO MEM_FAR *, + MEM_BOOL _dbgARGS); +_shAPI(MEM_POOL_STATUS,MemPoolNext)(MEM_POOL_INFO MEM_FAR*,MEM_BOOL _dbgARGS); +_shAPI(MEM_POOL_STATUS,MemPoolWalk)(MEM_POOL,MEM_POOL_ENTRY MEM_FAR*_dbgARGS); +_shAPI(MEM_BOOL, MemPoolCheck)(MEM_POOL _dbgARGS); +_shAPI(MEM_BOOL, MemPoolLock)(MEM_POOL _dbgARGS); +_shAPI(MEM_BOOL, MemPoolUnlock)(MEM_POOL _dbgARGS); + +/* Handle-based API for moveable memory within heap. */ +_shAPI(MEM_HANDLE, MemAlloc)(MEM_POOL, unsigned, unsigned long _dbgARGS); +_shAPI(MEM_HANDLE, MemReAlloc)(MEM_HANDLE,unsigned long,unsigned _dbgARGS); +_shAPI(MEM_BOOL, MemFree)(MEM_HANDLE _dbgARGS); +_shAPI(void MEM_FAR *, MemLock)(MEM_HANDLE _dbgARGS); +_shAPI(unsigned, MemUnlock)(MEM_HANDLE _dbgARGS); +_shAPI(void MEM_FAR *, MemFix)(MEM_HANDLE _dbgARGS); +_shAPI(unsigned, MemUnfix)(MEM_HANDLE _dbgARGS); +_shAPI(unsigned, MemLockCount)(MEM_HANDLE _dbgARGS); +#ifndef MemFlags +#define MemFlags(mem) MemLockCount(mem) +#endif +_shAPI(MEM_BOOL, MemIsMoveable)(MEM_HANDLE _dbgARGS); +_shAPI(unsigned long, MemSize)(MEM_HANDLE _dbgARGS); +_shAPI(unsigned long, MemSizeRequested)(MEM_HANDLE _dbgARGS); +_shAPI(MEM_HANDLE, MemHandle)(void MEM_FAR * _dbgARGS); +#ifndef MEM_REFERENCE + #ifdef MEM_DEBUG + MEM_ENTRY1 void MEM_FAR * MEM_ENTRY _dbgMemReference(MEM_HANDLE, + const char MEM_FAR *, int); + #define MEM_REFERENCE(handle) \ + _dbgMemReference(handle, __FILE__, __LINE__) + #else + #define MEM_REFERENCE(handle) (*(void MEM_FAR * MEM_FAR *)handle) + #endif +#endif + +/* General Heap Allocator (returns direct pointer to memory) */ +_shAPI(void MEM_FAR*,MemAllocPtr)(MEM_POOL,unsigned long,unsigned _dbgARGS); +_shAPI(void MEM_FAR *, MemReAllocPtr)(void MEM_FAR *, unsigned long, + unsigned _dbgARGS); +_shAPI(MEM_BOOL, MemFreePtr)(void MEM_FAR * _dbgARGS); +_shAPI(unsigned long, MemSizePtr)(void MEM_FAR * _dbgARGS); +_shAPI(MEM_POINTER_STATUS, MemCheckPtr)(MEM_POOL, void MEM_FAR * _dbgARGS); + +/* Fixed-Size Allocator */ +_shAPI(void MEM_FAR *, MemAllocFS)(MEM_POOL _dbgARGS); +_shAPI(MEM_BOOL, MemFreeFS)(void MEM_FAR * _dbgARGS); + +/* Error Handling Functions */ +MEM_ENTRY1 MEM_ERROR_FN MEM_ENTRY MemSetErrorHandler(MEM_ERROR_FN); +MEM_ENTRY1 MEM_BOOL MEM_ENTRY MemDefaultErrorHandler(MEM_ERROR_INFO MEM_FAR*); +MEM_ENTRY1 void MEM_ENTRY MemErrorUnwind(void); + +#ifdef MEM_WIN32 +/* patching control */ + +#ifndef MEM_PATCHING_DEFINED +#define MEM_PATCHING_DEFINED +typedef enum +{ + MEM_PATCH_ALL = 0, + MEM_SKIP_PATCHING_THIS_DLL = 1, + MEM_DISABLE_SYSTEM_HEAP_PATCHING = 2, + MEM_DISABLE_ALL_PATCHING = 4|2|1, + MEM_PATCHING_INT_MAX = INT_MAX /* to ensure enum is full int in size */ +} MEM_PATCHING; +#endif /* MEM_PATCHING_DEFINED */ + +#ifdef _MSC_VER +__declspec(dllexport) +#endif +MEM_PATCHING MEM_ENTRY MemSetPatching(const char ***skipDLLs); + +#endif /* MEM_WIN32 */ + +/* Internal routines */ +MEM_ENTRY1 MEM_BOOL MEM_ENTRY _shi_enterCriticalSection(void); +MEM_ENTRY1 void MEM_ENTRY _shi_leaveCriticalSection(void); +MEM_BOOL shi_call_new_handler_msc(size_t, MEM_BOOL); + + +/* Wrapper macros for debugging API */ +#ifndef _SHI_dbgMacros +#ifdef MEM_DEBUG +#define MemRegisterTask() _dbgMemRegisterTask(__FILE__, __LINE__) +#define MemUnregisterTask() _dbgMemUnregisterTask(__FILE__, __LINE__) +#define MemPoolInit(flags) _dbgMemPoolInit(flags, __FILE__, __LINE__) +#define MemPoolInitFS(bs, bc, f) _dbgMemPoolInitFS(bs,bc,f,__FILE__,__LINE__) +#define MemPoolInitRegion(addr, sz, f) \ + _dbgMemPoolInitRegion(addr, sz, f, __FILE__, __LINE__) +#define MemPoolInitRegionEx(addr, sz, f) \ + _dbgMemPoolInitRegionEx(addr, sz, f, s, __FILE__, __LINE__) +#define MemPoolInitNamedShared(nm, sz, f) \ + _dbgMemPoolInitNamedShared(nm, sz, f, __FILE__, __LINE__) +#define MemPoolInitNamedSharedEx(a, c, p, sec, nm, sz, f) \ + _dbgMemPoolInitNamedSharedEx(a, c, p, sec, nm, sz, f, __FILE__, __LINE__) +#define MemPoolAttachShared(p, n) \ + _dbgMemPoolAttachShared(p, n, __FILE__, __LINE__) +#define MemPoolFree(pool) _dbgMemPoolFree(pool, __FILE__, __LINE__) +#define MemPoolSetPageSize(p, s) _dbgMemPoolSetPageSize(p,s,__FILE__,__LINE__) +#define MemPoolSetBlockSizeFS(p, s) \ + _dbgMemPoolSetBlockSizeFS(p, s, __FILE__, __LINE__) +#define MemPoolSetSmallBlockSize(p, s) \ + _dbgMemPoolSetSmallBlockSize(p, s, __FILE__, __LINE__) +#define MemPoolSetFloor(p, f) _dbgMemPoolSetFloor(p, f, __FILE__, __LINE__) +#define MemPoolSetCeiling(p, c) _dbgMemPoolSetCeiling(p,c,__FILE__, __LINE__) +#define MemPoolPreAllocate(p,s,t) \ + _dbgMemPoolPreAllocate(p,s,t,__FILE__, __LINE__) +#define MemPoolPreAllocateHandles(p,h) \ + _dbgMemPoolPreAllocateHandles(p,h,__FILE__, __LINE__) +#define MemPoolShrink(p) _dbgMemPoolShrink(p, __FILE__, __LINE__) +#define MemPoolCheck(p) _dbgMemPoolCheck(p, __FILE__, __LINE__) +#define MemPoolWalk(p, e) _dbgMemPoolWalk(p, e, __FILE__, __LINE__) +#define MemPoolSize(p) _dbgMemPoolSize(p, __FILE__, __LINE__) +#define MemPoolCount(p) _dbgMemPoolCount(p, __FILE__, __LINE__) +#define MemPoolInfo(p,x,i) _dbgMemPoolInfo(p,x,i, __FILE__, __LINE__) +#define MemPoolFirst(i, b) _dbgMemPoolFirst(i, b, __FILE__, __LINE__) +#define MemPoolNext(i, b) _dbgMemPoolNext(i, b, __FILE__, __LINE__) +#define MemPoolLock(p) _dbgMemPoolLock(p, __FILE__, __LINE__) +#define MemPoolUnlock(p) _dbgMemPoolUnlock(p, __FILE__, __LINE__) +#define MemAlloc(p, f, s) _dbgMemAlloc(p, f, s, __FILE__, __LINE__) +#define MemReAlloc(h, s, f) _dbgMemReAlloc(h, s, f, __FILE__, __LINE__) +#define MemFree(h) _dbgMemFree(h, __FILE__, __LINE__) +#define MemLock(h) _dbgMemLock(h, __FILE__, __LINE__) +#define MemUnlock(h) _dbgMemUnlock(h, __FILE__, __LINE__) +#define MemFix(h) _dbgMemFix(h, __FILE__, __LINE__) +#define MemUnfix(h) _dbgMemUnfix(h, __FILE__, __LINE__) +#define MemSize(h) _dbgMemSize(h, __FILE__, __LINE__) +#define MemSizeRequested(h) _dbgMemSizeRequested(h, __FILE__, __LINE__) +#define MemLockCount(h) _dbgMemLockCount(h, __FILE__, __LINE__) +#define MemIsMoveable(h) _dbgMemIsMoveable(h, __FILE__, __LINE__) +#define MemHandle(p) _dbgMemHandle(p, __FILE__, __LINE__) +#define MemAllocPtr(p, s, f) _dbgMemAllocPtr(p, s, f, __FILE__, __LINE__) +#define MemReAllocPtr(p, s, f) _dbgMemReAllocPtr(p, s, f, __FILE__,__LINE__) +#define MemFreePtr(p) _dbgMemFreePtr(p, __FILE__, __LINE__) +#define MemSizePtr(p) _dbgMemSizePtr(p, __FILE__, __LINE__) +#define MemCheckPtr(p, x) _dbgMemCheckPtr(p, x, __FILE__, __LINE__) +#define MemAllocFS(p) _dbgMemAllocFS(p, __FILE__, __LINE__) +#define MemFreeFS(p) _dbgMemFreeFS(p, __FILE__, __LINE__) + +#else /* MEM_DEBUG */ + +/* MEM_DEBUG not defined: define dbgMemXXX as no-op macros + * each macro returns "success" value when MEM_DEBUG not defined + */ +#ifndef dbgMemBreakpoint +#define dbgMemBreakpoint() ((void)0) +#define dbgMemCheckAll() 1 +#define dbgMemCheckPtr(p, f, s) 1 +#define dbgMemDeferFreeing(b) 1 +#define dbgMemFormatCall(i, b, s) 0 +#define dbgMemFormatErrorInfo(i, b, s) 0 +#define dbgMemPoolDeferFreeing(p, b) 1 +#define dbgMemFreeDeferred() 1 +#define dbgMemPoolFreeDeferred(p) 1 +#define dbgMemPoolInfo(p, b) 1 +#define dbgMemPoolSetCheckFrequency(p, f) 1 +#define dbgMemPoolSetDeferQueueLen(p, b) 1 +#define dbgMemPoolSetName(p, n) 1 +#define dbgMemProtectPtr(p, f) 1 +#define dbgMemPtrInfo(p, b) 1 +#define dbgMemReallocMoves(b) 1 +#define dbgMemReportLeakage(p, c1, c2) 1 +#define dbgMemReportWrongTaskRef(b) 1 +#define dbgMemScheduleChecking(b, p, i) 1 +#define dbgMemSetCheckFrequency(f) 1 +#define dbgMemSetCheckpoint(c) 1 +#define dbgMemSetDefaultErrorOutput(x, f) 1 +#define dbgMemSetDeferQueueLen(l) 1 +#define dbgMemSetDeferSizeThreshold(s) 1 +#define dbgMemSetEntryHandler(f) 0 +#define dbgMemSetExitHandler(f) 0 +#define dbgMemSetFreeFill(c) 1 +#define dbgMemSetGuardFill(c) 1 +#define dbgMemSetGuardSize(s) 1 +#define dbgMemSetInUseFill(c) 1 +#define dbgMemSetCallstackChains(s) 1 +#define dbgMemSetStackChecking(s) 1 +#define dbgMemSetSafetyLevel(s) 1 +#define dbgMemSettingsInfo(b) 1 +#define dbgMemSuppressFreeFill(b) 1 +#define dbgMemTotalCount() 1 +#define dbgMemTotalSize() 1 +#define dbgMemWalkHeap(b) MEM_POOL_OK +#endif /* dbgMemBreakpoint */ + +#endif /* MEM_DEBUG */ +#endif /* _SHI_dbgMacros */ + +#if defined(__WATCOMC__) && defined(__SW_3S) +/* Watcom stack calling convention */ + #pragma aux MemDefaultPool "_*"; + #pragma aux MemDefaultPoolBlockSizeFS "_*"; + #pragma aux MemDefaultPoolPageSize "_*"; + #pragma aux MemDefaultPoolFlags "_*"; + #pragma aux SmartHeap_malloc "_*"; + #pragma aux SmartHeap_far_malloc "_*"; + #pragma aux SmartHeap_new "_*"; +#ifdef MEM_DEBUG + #pragma aux dbgMemGuardSize "_*"; + #pragma aux dbgMemGuardFill "_*"; + #pragma aux dbgMemFreeFill "_*"; + #pragma aux dbgMemInUseFill "_*"; +#endif +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* !defined(_SMARTHEAP_PROT) */ diff --git a/code/smartheap/smrtheap.hpp b/code/smartheap/smrtheap.hpp new file mode 100644 index 0000000..8887f0a --- /dev/null +++ b/code/smartheap/smrtheap.hpp @@ -0,0 +1,197 @@ +// smrtheap.hpp -- SmartHeap public C++ header file +// Professional Memory Management Library +// +// Copyright (C) 1991-1999 Compuware Corporation. +// All Rights Reserved. +// +// No part of this source code may be copied, modified or reproduced +// in any form without retaining the above copyright notice. +// This source code, or source code derived from it, may not be redistributed +// without express written permission of the copyright owner. +// +// COMMENTS: +// - Include this header file to call the SmartHeap-specific versions of +// operators new (i.e. with placement syntax), to: +// o allocate from a specific memory pool; +// o specify allocation flags, such as zero-initialization; +// o resize an allocation. +// +// - If you include this header file, you must compile and link shnew.cpp, or +// link with one of the SmartHeap static operator new libraries: +// sh[l|d]XXXX.lib +// +// - Can be used in both EXEs and DLLs. +// +// - For 16-bit x86 platforms, use only in large or compact memory model. +// +// - If you do not want to use SmartHeap's global operator new but you do +// want to use SmartHeap's other facilities in a C++ application, then +// include the smrtheap.h header file but do not include this header file, +// and do not link with shnew.cpp. The two ".Xpp" files are present +// ONLY for the purpose of defining operator new and operator delete. +// +// - Use the MemDefaultPool global variable to refer to a memory pool to pass +// to SmartHeap functions that accept a pool as a parameter, +// e.g. MemPoolCount, MemPoolSize, MemPoolWalk, etc. +// + +#if !defined(_SMARTHEAP_HPP) +#define _SMARTHEAP_HPP + +#if defined(_MSC_VER) \ + && (defined(M_I86LM) || defined(M_I86CM) || defined(M_I86HM)) \ + && !defined(MEM_HUGE) +#define MEM_HUGE 0x8000u +#endif + +#ifndef __BORLANDC__ +/* Borland C++ does not treat extern "C++" correctly */ +extern "C++" +{ +#endif /* __BORLANDC__ */ + +#if defined(_MSC_VER) && _MSC_VER >= 900 +#pragma warning(disable : 4507) +#endif + +#if defined(new) +#if defined(MEM_DEBUG) +#undef new +#endif +#endif + +#include + +#include "smrtheap.h" + +#if defined(new) +#if defined(MEM_DEBUG) +#undef new +#endif +#endif + +#if ((defined(__BORLANDC__) && (__BORLANDC__ >= 0x450)) \ + || (defined(__WATCOMC__) && __WATCOMC__ >= 1000) \ + || (defined(__IBMCPP__) && __IBMCPP__ >= 250) \ + || defined(__hpux) \ + || defined(__osf__) \ + || (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x500) \ + || defined(_AIX43)) +#define SHI_ARRAY_NEW 1 +#define SHI_ARRAY_DELETE 1 +#endif + +#if !(defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x500) +#define SHI_NEWDEFARGS 1 +#endif + +void MEM_FAR * MEM_ENTRY_ANSI shi_New(unsigned long sz, unsigned flags=0, MEM_POOL pool=0); + +// operator new variants: + + +// version of new that passes memory allocation flags +// (e.g. MEM_ZEROINIT to zero-initialize memory) +// call with syntax 'ptr = new (flags) ' +inline void MEM_FAR *operator new(size_t sz, unsigned flags) + { return shi_New(sz, flags); } +#if defined(_MSC_VER) && _MSC_VER >= 1200 +inline void MEM_FAR operator delete(void *p, unsigned) + { ::operator delete(p); } +#endif // _MSC_VER + +// version of new that allocates from a specified memory pool with alloc flags +// call with the syntax 'ptr = new (pool, [flags=0]) ' +inline void MEM_FAR *operator new(size_t sz, MEM_POOL pool, unsigned flags +#ifdef SHI_NEWDEFARGS + =0 +#endif + ) + { return shi_New(sz, flags, pool); } +#if defined(_MSC_VER) && _MSC_VER >= 1200 +inline void MEM_FAR operator delete(void *p, MEM_POOL, unsigned) + { ::operator delete(p); } +#endif // _MSC_VER + +#ifdef SHI_ARRAY_NEW +inline void MEM_FAR *operator new[](size_t sz, MEM_POOL pool, unsigned flags +#ifdef SHI_NEWDEFARGS + =0 +#endif + ) + { return shi_New(sz, flags, pool); } +#endif + +// version of new that changes the size of a memory block previously allocated +// from an SmartHeap memory pool +// call with the syntax 'ptr = new (ptr, flags) ' +#if !defined(__BORLANDC__) && !defined(__HIGHC__) +/* bug in BC++, MetaWare High C++ parsers confuse this with new(file,line) */ +inline void MEM_FAR *operator new(size_t new_sz, void MEM_FAR *lpMem, + unsigned flags) + { return MemReAllocPtr(lpMem, new_sz, flags); } +#if defined(_MSC_VER) && _MSC_VER >= 1200 +inline void MEM_FAR operator delete(void *p, void MEM_FAR *, unsigned) + { ::operator delete(p); } +#endif // _MSC_VER + +#ifdef SHI_ARRAY_NEW +inline void MEM_FAR *operator new[](size_t new_sz, void MEM_FAR *lpMem, + unsigned flags) + { return MemReAllocPtr(lpMem, new_sz, flags); } +#endif // SHI_ARRAY_NEW +#endif + + +// new_handler prototypes: note that MSC/C++ prototype differs from the +// protosed ANSI standard prototype for set_new_handler +#if defined(__MWERKS__) \ + || defined(__hpux) \ + || (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x500) + +#define MEM_CPP_THROW throw() +#define MEM_CPP_THROW1(x) throw(x) +//#elif defined(_MSC_VER) && _MSC_VER >= 1100 && defined(_CPPUNWIND) +//#define MEM_CPP_THROW throw() +#else +#define MEM_CPP_THROW +#define MEM_CPP_THROW1(x) +#endif // __MWERKS__ +#ifndef _CRTIMP +#define _CRTIMP +#endif // _CRTIMP +#ifdef _MSC_VER +_CRTIMP _PNH MEM_ENTRY_ANSI _set_new_handler(_PNH); +#if UINT_MAX == 0xFFFFu +_PNH MEM_ENTRY_ANSI _set_fnew_handler(_PNH); +_PNHH MEM_ENTRY_ANSI _set_hnew_handler(_PNHH); +#endif // UINT_MAX +#endif // _MSC_VER +typedef void (MEM_ENTRY_ANSI * pnh)(); +_CRTIMP pnh MEM_ENTRY_ANSI set_new_handler(pnh) MEM_CPP_THROW; + +#ifndef DBG_FORMAL +#define DBG_FORMAL +#define DBG_ACTUAL +#ifndef DEBUG_NEW +#define DEBUG_NEW new +#endif // DEBUG_NEW +#define DEBUG_NEW1(x_) new(x_) +#define DEBUG_NEW2(x_, y_) new(x_, y_) +#define DEBUG_NEW3(x_, y_, z_) new(x_, y_, z_) +#define DEBUG_DELETE delete +#endif + +#ifdef DEFINE_NEW_MACRO +#define new DEBUG_NEW +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 900 +#pragma warning(default : 4507) +#endif + +#ifndef __BORLANDC__ +} +#endif /* __BORLANDC__ */ + +#endif /* !defined(_SMARTHEAP_HPP) */ diff --git a/code/starwars.plg b/code/starwars.plg new file mode 100644 index 0000000..5dd59a6 --- /dev/null +++ b/code/starwars.plg @@ -0,0 +1,16 @@ + + +

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

A!ebST548YrIuf_-2i_M8E?D<1&7CO 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�Gu2oJ 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